A chaque moteur son modele, et un forcage manuel
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) <noreply@anthropic.com>
This commit is contained in:
parent
6974a3406c
commit
2ccb614649
7
app.py
7
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"],
|
||||
)
|
||||
|
||||
41
core.py
41
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": "<cmd>+<ctrl>+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:
|
||||
|
||||
52
tests/test_modele_par_moteur.py
Normal file
52
tests/test_modele_par_moteur.py
Normal file
@ -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")
|
||||
Loading…
Reference in New Issue
Block a user