From 2ccb614649be2d468e42a4d7d57aec3301a8cdd7 Mon Sep 17 00:00:00 2001 From: Ralph Mayola Date: Sun, 23 Aug 2026 18:45:14 +0200 Subject: [PATCH] A chaque moteur son modele, et un forcage manuel MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit app.py passait CONFIG["model"] — un depot MLX — quel que soit le moteur detecte. Sur une machine sans Apple Silicon, faster-whisper aurait recu un depot qu'il ne sait pas charger. Le defaut etait invisible ici, la detection rendant toujours "mlx" sur ce Mac : aucun test ne pouvait le voir. MODELES_PAR_MOTEUR associe chaque moteur a son depot. Les valeurs viennent de la mesure du jour (trois echantillons, amorce active, CPU Apple Silicon) : faster-whisper base 0,52 s 10/13 termes techniques 142 Mo faster-whisper small 1,42 s 11/13 464 Mo faster-whisper medium 4,40 s 13/13 1460 Mo MLX medium (GPU) 0,75 s 11/13 489 Mo Le GPU vise donc la precision (medium, 13/13, il absorbe le cout) ; le CPU vise la latence (small, 11/13 — AUTANT que le MLX utilise aujourd'hui — pour 1,4 s). `medium` sur CPU serait plus juste, mais ces 4,4 s sont celles d'un CPU Apple Silicon : sur un CPU Intel ou Windows, le meme modele peut demander trois a quatre fois plus. La commande de diagnostic mesurera la machine reelle et pourra promouvoir `medium`. CONFIG["model"] passe a None et devient un FORCAGE manuel qui prime sur la table, au lieu d'un reglage mort qu'on modifie sans effet. 115 tests. Comportement inchange sur ce Mac : moteur mlx, modele identique. Co-Authored-By: Claude Opus 5 (1M context) --- app.py | 7 +++-- core.py | 41 +++++++++++++++++++++++++- tests/test_modele_par_moteur.py | 52 +++++++++++++++++++++++++++++++++ 3 files changed, 96 insertions(+), 4 deletions(-) create mode 100644 tests/test_modele_par_moteur.py diff --git a/app.py b/app.py index 1ae1971..0b02808 100644 --- a/app.py +++ b/app.py @@ -29,7 +29,7 @@ from PyObjCTools import AppHelper from pynput import keyboard from controller import ZonzaController -from core import CONFIG, validate_config +from core import CONFIG, modele_retenu, validate_config from interface import classe_overlay_pour from moteur import choisir_transcripteur, detecter_moteur from systeme import choisir_systeme @@ -141,9 +141,10 @@ def main(): return main._lock = lock # garder le verrou vivant tant que le process vit validate_config(CONFIG) + moteur = detecter_moteur() transcripteur = choisir_transcripteur( - nom=detecter_moteur(), - modele=CONFIG["model"], + nom=moteur, + modele=modele_retenu(moteur, CONFIG["model"]), langue=CONFIG["language"], amorce=CONFIG["initial_prompt"], ) diff --git a/core.py b/core.py index bd01e02..02440cc 100644 --- a/core.py +++ b/core.py @@ -6,7 +6,9 @@ Tout ce qui est ici est testable sans micro, sans clavier, sans modèle. import re CONFIG = { - "model": "mlx-community/whisper-medium", + # Forcage manuel du modele. None = on suit MODELES_PAR_MOTEUR, qui choisit + # d'apres le moteur detecte. Renseigner cette cle prime sur la table. + "model": None, "language": "fr", "hotkey": "++d", # Cmd+Ctrl+D (libre : ni macOS ni VS Code ne l'utilisent) "sounds": True, @@ -163,6 +165,43 @@ def duree_vie_overlay_coherente(config): return config["duree_max_overlay_s"] >= config["max_duration_s"] + MARGE_TRANSCRIPTION_S +# --- modele par moteur ---------------------------------------------------- +# Chaque moteur a son propre format de depot : MLX lit `mlx-community/*`, +# faster-whisper lit `Systran/faster-whisper-*`. Passer l'un a l'autre echoue. +# +# Mesure du 2026-08-23, trois echantillons, amorce de vocabulaire active, +# sur le CPU d'un Apple Silicon : +# faster-whisper base 0,52 s 10/13 termes techniques 142 Mo +# faster-whisper small 1,42 s 11/13 464 Mo +# faster-whisper medium 4,40 s 13/13 1460 Mo +# MLX medium (GPU) 0,75 s 11/13 489 Mo +# +# D'ou les choix : le GPU vise la precision (medium, 13/13, le GPU absorbe le +# cout) ; le CPU vise la latence (small, 11/13 — soit AUTANT que le MLX utilise +# aujourd'hui — pour 1,4 s ici). `medium` sur CPU serait plus juste, mais 4,4 s +# ont ete mesures sur un CPU Apple Silicon, rapide : sur un CPU Intel ou Windows, +# le meme modele peut demander trois a quatre fois plus. La commande de +# diagnostic (etape 4) mesurera la machine reelle et pourra promouvoir `medium`. +MODELES_PAR_MOTEUR = { + "mlx": "mlx-community/whisper-medium", + "faster-gpu": "Systran/faster-whisper-medium", + "faster-cpu": "Systran/faster-whisper-small", +} + + +def modele_retenu(nom_moteur, forcage=None): + """Modele finalement utilise : le forcage manuel s'il est renseigne, sinon la + table. Une chaine vide est traitee comme une absence de forcage.""" + return forcage or modele_pour_moteur(nom_moteur) + + +def modele_pour_moteur(nom): + """Rend le depot de modele adapte a ce moteur. Leve KeyError si inconnu.""" + if nom not in MODELES_PAR_MOTEUR: + raise KeyError(f"Aucun modele connu pour le moteur : {nom}") + return MODELES_PAR_MOTEUR[nom] + + def validate_config(config): """Valide la config ; lève ValueError si une valeur est aberrante.""" if config["min_duration_s"] < 0: diff --git a/tests/test_modele_par_moteur.py b/tests/test_modele_par_moteur.py new file mode 100644 index 0000000..3e4e33a --- /dev/null +++ b/tests/test_modele_par_moteur.py @@ -0,0 +1,52 @@ +"""À chaque moteur son modèle. + +`app.py` passait `CONFIG["model"]` — un dépôt MLX — quel que soit le moteur détecté. +Sur une machine sans Apple Silicon, `faster-whisper` aurait reçu un dépôt qu'il ne +sait pas charger. Le défaut était invisible ici, puisque la détection rend toujours +`mlx` sur ce Mac. +""" +import pytest + +from core import MODELES_PAR_MOTEUR, modele_pour_moteur + + +def test_chaque_moteur_connu_a_son_modele(): + for nom in ("mlx", "faster-cpu", "faster-gpu"): + assert modele_pour_moteur(nom) + + +def test_mlx_garde_le_modele_d_aujourd_hui(): + assert modele_pour_moteur("mlx") == "mlx-community/whisper-medium" + + +def test_les_moteurs_faster_ne_recoivent_jamais_un_depot_mlx(): + for nom in ("faster-cpu", "faster-gpu"): + assert "mlx" not in modele_pour_moteur(nom).lower() + + +def test_le_gpu_vise_la_precision_le_cpu_la_latence(): + # mesuré le 2026-08-23 : medium = 13/13 termes mais 4,4 s sur CPU ; small = 11/13 en 1,4 s + assert "medium" in modele_pour_moteur("faster-gpu") + assert "small" in modele_pour_moteur("faster-cpu") + + +def test_un_moteur_inconnu_est_refuse_clairement(): + with pytest.raises(KeyError) as e: + modele_pour_moteur("vosk") + assert "vosk" in str(e.value) + + +def test_la_table_ne_contient_que_les_moteurs_reconnus(): + assert set(MODELES_PAR_MOTEUR) == {"mlx", "faster-cpu", "faster-gpu"} + + +def test_le_reglage_manuel_prime_sur_la_table(): + from core import modele_retenu + assert modele_retenu("faster-cpu", forcage=None) == modele_pour_moteur("faster-cpu") + assert modele_retenu("faster-cpu", forcage="Systran/faster-whisper-medium") \ + == "Systran/faster-whisper-medium" + + +def test_un_forcage_vide_est_ignore(): + from core import modele_retenu + assert modele_retenu("mlx", forcage="") == modele_pour_moteur("mlx")