TranscrIA
August 2, 2026 · View on GitHub
Statut : đą Plan complet livrĂ© sur
main(commits6423fa1âe379e0f). CĆur + activation runtime + re-queue diffĂ©rĂ© (§7.2) + concurrence (v1.1) + idle-stop minimal (v1.2), testĂ©s (ruff/mypy/pytest, couverture ~77 %). Ăvolutions possibles : idle-stop par tĂąche de fond, relocalisation par dĂ©faut, profil d'install « nĆud seul » (cf. §10/§13). Auteur : Martossien Date : 2026-05-30 Objectif : lever l'asymĂ©trie de gestion VRAM entre le service maison et le STT vLLM, et formaliser les deux topologies de dĂ©ploiement (tout-en-un / frontale + ressources), pour faire passer TranscrIA d'un « clone » Ă un produit auto-hĂ©bergeable professionnel. PrĂ©requis de lecture :MIGRATION_API_SERVEUR_GPU.md(plan de migration global).Banc containerisĂ© (vLLM) : un dĂ©ploiement split entiĂšrement Docker â nĆud GPU servant diarisation + STT Cohere via vLLM + LLM d'arbitrage Qwen3.6-27B-FP8 via vLLM â est dĂ©crit dans
PLAN_TEST_SPLIT_VLLM.md(docker-compose.split-gpu.yml,Dockerfile.resource-node) et résumé dansDOCKER.md.
Ătat d'implĂ©mentation (v1)
| Brique | Module / route | Ătat |
|---|---|---|
| Planificateur VRAM (fractionĂtotal, place/relocate/busy) | transcria/gpu/stt_vram_planner.py | â |
| Correctif allocator (pas de VRAM locale pour phase distante) | transcria/workflow/runner.py | â |
| Superviseur cycle de vie A/B/C | transcria/gpu/stt_engine_supervisor.py | â |
| DĂ©tection ressources + inventaire | GET /capabilities (inference_service) | â |
| Auto-lancement STT Ă la demande | POST /engines/ensure | â |
| Admission §7.2 + prĂ©-vol | transcria/inference/resource_gate.py, branchĂ© dans PipelineService.run_process ET dans la transcription rapide du rĂ©sumĂ© (runner._preflight_remote_stt) â sinon, sur un nĆud frais, le rĂ©sumĂ© tourne hors pipeline et le moteur STT n'est jamais lancĂ© (« connection refused ») | â |
| Panneau d'Ă©tat frontale | GET /api/resources/status + dashboard_status.html | â |
| Concurrence STT par tour (v1.1) | inference.stt.concurrency (transcria/stt/transcription.py) | â |
| Re-queue diffĂ©rĂ© avec backoff (§7.2) | QueueStore.requeue_later + job_executor (scheduled_at) | â |
| Idle-stop moteurs externes (v1.2) | SttEngineSupervisor.reap_idle (opportuniste via /capabilities) | â (minimal) |
| Admission VRAM distante (backpressure) | remote_vram_admits â donnĂ©es GPU manquantes â None (dĂ©fĂ©rer au prĂ©-vol, pas de famine) + log throttlĂ© scheduler | â |
| Check d'install GPU du nĆud | doctor.check_inference_node_gpus (nĆud joignable doit Ă©numĂ©rer ses GPU via /capabilities) | â |
| LLM d'arbitrage DISTANTE (cycle de vie non gĂ©rĂ© localement) | vram_manager._is_remote_arbitrage() honore l'endpoint rĂ©solu par opencode_setup.resolve_arbitrage_endpoint (env TRANSCRIA_ARBITRAGE_LLM_HOST > services.arbitrage_llm_host > 127.0.0.1), source unique partagĂ©e avec le provider opencode ; sondes + admission, ne lance/arrĂȘte jamais une LLM distante | â (2026-06-23) |
Diarisation : placement auto (carte la plus libre, plus de cuda:0 figĂ©) | diarization.py â squim_scorer.pick_device au chargement (diarization.device: auto) | â (2026-06-23) |
resource_node.engines[].gpu_mem appliquĂ© au lancement (pas seulement Ă l'admission) | make_script_launcher transmet STT_GPU_MEM | â (2026-06-23) |
| Banc split entiĂšrement containerisĂ© (vLLM) â validĂ© E2E fichier son rĂ©el | docker-compose.split-gpu.yml, Dockerfile.{worker,resource-node}, scripts/launch_arbitrage_vllm.sh, scripts/verify_split_topology.py â cf. PLAN_TEST_SPLIT_VLLM.md | â (2026-06-23) |
| CapacitĂ© d'admission du nĆud configurable + concurrence validĂ©e sous charge | resource_node.max_concurrent_jobs annoncĂ© dans /capabilities ; available_remote_slots = min(node_max, stt_slots) (moteurs sĂ©rialisĂ©s exclus) ; verrou LLM no-op si distant. Split robuste jusqu'Ă 8 jobs, sweet spot â4 â cf. PLAN_TEST_CHARGE.md | â (2026-06-23) |
Validation E2E (2026-06-23, 8Ă RTX 3090) : frontale CPU â STT Cohere (vLLM) + diarisation (pyannote, auto-placĂ©e) + LLM Qwen3.6-27B-FP8 (vLLM, TP=4, FP8 Marlin) tout en distant, pipeline complet produisant SRT/ZIP/DOCX. 14 correctifs (cf.
PLAN_TEST_SPLIT_VLLM.md§ journal).
0. Résumé exécutif
Les adaptateurs distants existent et sont validés E2E (STT, diarisation, voice-embed, avec LLM d'arbitrage). Reste un manque évident : la gestion de la VRAM n'est pas symétrique.
| Ressource | Gestion VRAM aujourd'hui |
|---|---|
Service Flask inference_service (diarize / voice-embed) | Autonome â A/B/C in-process (charge Ă la demande, 503 si saturĂ©, dĂ©chargement idle) |
| LLM d'arbitrage (llama.cpp) | GĂ©rĂ© par la frontale â VRAMManager + arbitrage_script/stop_script (CAS A/B/C) |
| STT via vLLM (cohere/whisper) | Statique â serveurs rĂ©sidents lancĂ©s Ă la main, aucun arbitrage VRAM |
La cible : donner au STT vLLM la mĂȘme autonomie, en Ă©tendant un pattern qui existe dĂ©jĂ (celui de la LLM d'arbitrage), sans construire d'orchestrateur de process complexe.
Principe directeur : l'admin dĂ©cide du placement (quels moteurs, quels GPU) ; le service dĂ©cide du quand (dĂ©marrage Ă la demande, rĂ©utilisation, arrĂȘt sur idle, contention). Le code n'est jamais intrusif sur le placement.
1. Les deux topologies de déploiement
TOUT-EN-UN (une machine) SPLIT (frontale + ressources)
ââââââââââââââââââââââââââââââââ âââââââââââââââââââ HTTP ââââââââââââââââââââââââââââ
â TranscrIA (web, DB, workflow, â â TranscrIA â ââââââââș â NĆud ressources â
â calendrier, lexique, exports) â â FRONTALE â â âą service ressources â
â + ressources GPU locales â â (CPU, pas de â ââââââââ â âą vLLM STT (cohere/whisper)â
â (vLLM, llama.cpp, Flask) â â modĂšle chargĂ©) â status â âą llama.cpp (arbitrage) â
ââââââââââââââââââââââââââââââââ âââââââââââââââââââ â âą Flask (diarize/v-embed) â
ââââââââââââââââââââââââââââ
| Tout-en-un | Split | |
|---|---|---|
| Frontale | web, DB, calendrier, workflow, lexique, participants, exports | idem (le calendrier reste toujours ici) |
| Ressources | mĂȘmes process, sur la mĂȘme machine | sur une (ou des) machine(s) dĂ©diĂ©e(s) |
| Niveau | grand public / mono-poste | admin systÚme (assumé : doc claire, pas de « clic-bouton ») |
| Qui lance les moteurs | le service local (Ă la demande, A/B/C) | l'admin dĂ©clare ; le service du nĆud gĂšre le cycle de vie |
Le calendrier / la planification sont de la logique métier : ils restent cÎté frontale dans les deux cas.
1 bis. All-in-one = son propre nĆud de ressources (0.3.6)
Depuis les runtimes STT servis (audio.cpp/parakeet.cpp â docs/EXTERNAL_STT_RUNTIMES.md),
la section resource_node.engines[] vaut AUSSI pour l'all-in-one : une URL de backend
loopback + un moteur homonyme dĂ©clarĂ© â le prĂ©-vol des jobs lance le moteur en process
(resource_gate._ensure_local_served_stt â build_stt_supervisor().ensure_ready(), mĂȘme
cycle A/B/C que /engines/ensure) â aucun nĆud de contrĂŽle requis, aucun faux WARN doctor.
Le chemin split est INCHANGĂ : dĂšs qu'un inference.url/inference.nodes existe, il prime
(le nĆud assure ses moteurs lui-mĂȘme). SantĂ© par moteur : health_path (dĂ©faut /v1/models)
et health_mode (http_2xx défaut ; http_any = toute réponse HTTP prouve la vie, réservé
aux serveurs mono-modĂšle qui chargent leurs poids AVANT de binder â jamais pour un vLLM).
2. Placement (admin) vs cycle de vie (service)
C'est le point qui garantit la non-intrusivité.
2.1 Placement = l'admin
- Quels moteurs, sur quels GPU, combien d'instances. Déclaré via les
scripts/launch_stt_*.sh(+launch_arbitrage.sh) et un manifeste lu par le service (cf. §6). - L'admin peut partager une grosse carte entre plusieurs instances (mĂȘme
STT_GPU, ports distincts,STT_GPU_MEMréduit pour chacune) ou répartir sur plusieurs cartes. Les scripts le permettent déjà . Le code n'impose ni ne réécrit ce choix.
2.2 Cycle de vie = le service (configurable)
à partir de ce que l'admin a déclaré, le service peut :
- CAS A â moteur dĂ©jĂ up et sain â rĂ©utilise directement ;
- CAS B â moteur dĂ©clarĂ© mais Ă©teint, VRAM disponible â le dĂ©marre (via son script) puis sert ;
- CAS C â VRAM saturĂ©e â 503 +
Retry-After(la frontale re-queue), avec relocalisation optionnelle avant d'abandonner (cf. §4) ; - idle-stop â arrĂȘte un moteur inactif depuis N secondes (opt-in, off par dĂ©faut, cf. §3).
C'est exactement le pattern déjà utilisé pour la LLM d'arbitrage (
VRAMManager+ scripts), généralisé aux moteurs STT vLLM. On ne réinvente rien.
3. Idle-stop : pourquoi off par défaut
| Type de modÚle | Décharger sur idle ? |
|---|---|
| In-process (service Flask) | Oui, dĂ©jĂ le cas (idle_timeout_s) â charge/dĂ©charge en VRAM, peu coĂ»teux |
| Serveur externe (vLLM, llama.cpp) | Opt-in, off par défaut |
ArrĂȘter un serveur vLLM externe = tuer le process â on perd le cache chaud et le redĂ©marrage coĂ»te 25â105 s (compile JIT FlashInfer). Donc :
- défaut : moteurs STT résidents (réactivité maximale) ;
- l'idle-stop ne se justifie que sous contention VRAM â c'est le rĂŽle du CAS C, pas d'un timer
systématique. Opt-in par moteur (
idle_timeout_s> 0).
Implémenté (v1.2, minimal) :
SttEngineSupervisor.reap_idle()arrĂȘte un moteur dĂ©clarĂ© avecidle_timeout_s > 0, up, et dont le dernierensure_readydĂ©passe le timeout. DĂ©clenchĂ© de façon opportuniste (poll/capabilities~10 s + chaqueensure_ready), sans tĂąche de fond. Non intrusif : ne touche que les moteurs qu'on a nous-mĂȘmes servis (_last_used). Ăvolution possible : reaper en tĂąche de fond ou dĂ©clenchement sous contention CAS C.
4. Gestion VRAM au lancement : deux niveaux
Niveau 1 â prĂ©-check (toujours actif)
Avant de démarrer un moteur sur le GPU assigné : lire la VRAM libre (nvidia-smi) et refuser
proprement (503 / message clair) si ça ne tient pas, au lieu de laisser le process OOM-crasher.
~20 lignes ; c'est l'essentiel du bénéfice « éviter un crash ».
Niveau 2 â relocalisation auto (le « plus » pro)
Si le GPU assignĂ© ne tient pas : parcourir les autres GPU, prendre le premier oĂč ça rentre,
surcharger le placement (STT_GPU) et lancer lĂ .
- Log bruyant systĂ©matique (« GPU 3 plein â repli sur GPU 5 ») â filet de sĂ©curitĂ©, pas de magie.
- Réutilise le verrou existant du
VRAMManagerpour Ă©viter que deux lancements concurrents visent le mĂȘme GPU. - S'enchaĂźne sur le CAS C : avant de renvoyer 503, on tente une relocalisation si activĂ©e.
â ïž SĂ©mantique VRAM spĂ©cifique Ă vLLM (Ă ne pas oublier)
vLLM réserve une fraction de la VRAM totale de la carte (--gpu-memory-utilization 0.85),
pas la taille du modĂšle. Donc :
« ça rentre » â VRAM_libre â„ fraction Ă VRAM_totale (et NON â„ taille_modĂšle)
Conséquences :
- packer plusieurs instances sur une carte impose de baisser la fraction de chacune (c'est Ă l'admin) ;
- le calcul de relocalisation/pré-check doit raisonner en fraction à total, pas en taille de modÚle ;
- contrepartie positive : cette rĂ©servation alimente le batching continu de vLLM â une mĂȘme instance peut servir plusieurs requĂȘtes concomitantes.
5. Concurrence â implĂ©mentĂ©e et validĂ©e sous charge (2026-06-23)
Deux axes de concurrence, tous deux exploitant le batching continu de vLLM :
Inter-jobs (admission) â le scheduler lance jusqu'Ă workflow.execution.max_concurrent_jobs
pipelines en parallĂšle (pool bornĂ© 1-8), plafonnĂ© par la capacitĂ© d'admission du nĆud
resource_node.max_concurrent_jobs (annoncée dans /capabilities, défaut 1). Les moteurs in-process
sérialisés (diarisation/voice-embed) ne bornent plus l'admission : ils s'auto-sérialisent via leur
verrou moteur (les jobs en surplus y font la queue), tandis que la LLM d'arbitrage (vLLM) batche
les requĂȘtes concurrentes â pour le STT servi, cela dĂ©pend du runtime : vLLM batche,
audiocpp_server SĂRIALISE (cf. §5 intra-job, multi-instance). Le verrou LLM de l'allocator est no-op pour une LLM distante (elle batche
seule ; le sérialiser l'étranglerait).
Intra-job (STT par tour) â inference.stt.concurrency (>1) transcrit les tours de parole d'un
mĂȘme job en parallĂšle (ThreadPoolExecutor, backends concurrent_safe).
â ïž Le batching dĂ©pend du runtime serveur : vLLM batche les requĂȘtes concurrentes, mais
audiocpp_server (qwen3asr/nemotron servis) les sérialise sous un mutex global par modÚle
(mesurĂ© : 2 requĂȘtes concurrentes = 2Ă le temps d'une seule). Sur audio.cpp, monter concurrency
seul ne gagne que le recouvrement I/O ; le parallélisme réel s'obtient en multi-instance :
resource_node:
engines:
- {name: qwen3asr, script: scripts/launch_stt_qwen3asr.sh, gpu: 1, port: 8021}
- {name: qwen3asr-gpu0, backend: qwen3asr, # 2e instance du MĂME backend
script: scripts/launch_stt_qwen3asr.sh, gpu: 0, port: 8022}
inference:
stt:
concurrency: 4 # â„ nombre d'instances
backends:
qwen3asr:
url: http://127.0.0.1:8021/v1
extra_urls: [http://127.0.0.1:8022/v1]
« Assurer qwen3asr » (pré-vol all-in-one comme /engines/ensure en split) démarre toutes les
instances ; la premiÚre porte le verdict, les autres sont best-effort. CÎté client, chaque worker
STT garde son instance (affinité) et bascule sur les vivantes si la sienne tombe. Mono-GPU :
laisser une seule instance (dĂ©faut inchangĂ©) â deux instances sur la mĂȘme carte se partagent le
mĂȘme moteur de calcul, gain Ă mesurer avant d'en mettre.
Validation (test de charge, docs/archive/PLAN_TEST_CHARGE.md, 8ĂRTX 3090) : split robuste jusqu'Ă 8 jobs
concurrents (0 Ă©chec serveur), dĂ©bit qui scale 1â4 puis le LLM 27B sature (compute-bound, GPU Ă
100 %). Sweet spot â 4 sur ce matĂ©riel : rĂ©gler max_concurrent_jobs (frontale et nĆud) au sweet
spot â le surplus attend en file (claim atomique, aucun perdu) plutĂŽt que de thrasher la LLM â
meilleure latence ET débit qu'en sur-souscription. La sur-charge dégrade en douceur (ralentit), ne
plante pas.
6. Le service de ressources
Candidat : inference_service Flask étendu (il fait déjà l'A/B/C in-process pour
diarize/voice-embed) â pas de nouveau service Ă maintenir.
Responsabilités ajoutées :
- Détection au démarrage : énumÚre GPU, VRAM libre, modÚles présents localement, moteurs déclarés dans le manifeste.
GET /capabilities: ce que le nĆud peut servir (moteurs, modĂšles, GPU, fraction VRAM).GET /health: Ă©tat temps rĂ©el (moteurs up/down, VRAM, CAS A/B/C courant) â interrogeable par la frontale sans auth (supervision).- Cycle de vie des moteurs dĂ©clarĂ©s (CAS A/B/C, prĂ©-check, relocalisation opt-in, idle-stop opt-in).
- Pas d'UI : le nĆud ressources reste mince ; l'affichage est sur la frontale (§7).
inference_service (étendu)
âââ /health â feu vert/rouge par moteur, VRAM (libre)
âââ /ready â prĂȘt Ă servir (200 mĂȘme modĂšles dĂ©chargĂ©s â CAS B = chargeable) (libre)
âââ /capabilities â inventaire ressources & moteurs (libre)
âââ /infer/diarize (existant)
âââ /infer/voice-embed (existant)
âââ superviseur VRAM ââ pilote launch_stt_*.sh / stop_stt.sh (placement admin respectĂ©)
7. Visibilité & résilience cÎté frontale
La frontale interroge périodiquement /health + /capabilities et affiche :
- le mode de déploiement (tout-en-un / frontale+ressources) ;
- un feu vert/rouge par moteur : STT cohere, STT whisper, LLM arbitrage, service diarize/voice-embed ;
- VRAM / activité par GPU.
ââ Ătat des ressources ââââââââââââââââââââââââââââ
â Mode : frontale + ressources (192.168.1.59) â
â â STT cohere up GPU3 3.9/24 GiB â
â â STT whisper up GPU5 2.9/24 GiB â
â â LLM arbitrage up GPU0 â
â â diarize/v-embed up GPU6 (idle, dĂ©chargĂ©) â
ââââââââââââââââââââââââââââââââââââââââââââââââââââ
7.1 Politique de polling
- Fréquence :
/healthtoutes les ~10 s (lĂ©ger, sans auth) ;/capabilitiesĂ la connexion + au changement d'Ă©tat. - Timeout court (~3 s) ; au-delĂ , le moteur/nĆud est marquĂ© rouge dans le panneau.
- Le polling est best-effort : il alimente l'affichage, il ne bloque jamais le rendu de l'UI.
7.2 IndisponibilitĂ© des ressources (mode dĂ©gradĂ©) â dĂ©cidĂ©
Scénario probable en split (réseau, redémarrage, crash GPU). Politique explicite :
| Situation | Comportement |
|---|---|
| Indispo transitoire (503 / timeout ponctuel) | re-queue diffĂ©rĂ© (implĂ©mentĂ© : QueueStore.requeue_later + scheduled_at) â le job attend puis re-tente, il n'Ă©choue pas |
| VRAM locale insuffisante pour une phase GPU (STT/transcription/diarisation/locuteurs) | mise en attente waiting_vram (statut d'exĂ©cution non terminal), pas de FAILED : le job re-queue et reprend automatiquement dĂšs libĂ©ration de la VRAM. L'admin est alertĂ© une seule fois par Ă©pisode (e-mail + log WARNING + bandeau in-app). TranscrIA ne tue jamais un process GPU tiers (force_free_gpu reste bridĂ© aux kill_patterns dans la fenĂȘtre calendaire). CĂŽtĂ© rĂ©sumĂ© synchrone, le client relance /summary automatiquement. Voir le dĂ©tail §7.2-bis ci-dessous. |
| Indispo prolongĂ©e (nĆud rouge) | nouvelles transcriptions acceptĂ©es mais mises en file (jamais perdues), statut clair « ressources indisponibles » + notification ; on ne bloque pas la soumission et on ne boucle pas indĂ©finiment en silence |
FenĂȘtre de retry dĂ©passĂ©e (max_unavailable_s, configurable) | le job est marquĂ© Ă©chec avec raison explicite (pas de crash, pas de blocage) |
fallback_local actif et GPU local prĂ©sent | bascule locale possible ; en frontale CPU-only, pas de fallback â file + notification est la seule issue saine |
Principe : jamais d'Ă©chec silencieux ni de spin infini. Le job est soit en file (visible), soit en attente VRAM (visible, admin alertĂ©), soit en Ă©chec explicite aprĂšs une fenĂȘtre bornĂ©e.
7.2-bis â Attente de VRAM locale (mĂ©canique)
Une VRAM insuffisante est traitée comme une indisponibilité transitoire, jamais comme un échec :
- Détection : les phases GPU (
WorkflowRunner.run_transcription/run_diarization/run_speaker_detection, et_run_quick_transcriptionpour le résumé) renvoient un signal{"vram_wait": True, "required_mb", "phase", "reason"}au lieu d'appelerupdate_state(FAILED). - File principale :
PipelineService._run_pipeline_stepspropagevram_wait;JobExecutorService._run_processre-queue viaQueueStore.requeue_later+ marquemark_execution_waiting_vram. Le scheduler (_resources_available) garde le job en attente tant queGPUAllocator.can_allocateĂ©choue, puis le redispatche â reprise automatique. - Pipeline reprenable + admission par phases restantes : le pipeline saute les phases dĂ©jĂ
faites au redispatch (
extra_data.pipeline.completed_phases, cf.docs/PIPELINE_REPRISE.md) â un re-queue ne refait pas le STT. Et l'admission n'exige que la VRAM des phases restantes (_done_profile_phasesâ_local_required_mbexclut les phases faites) : un job oĂč il ne reste que la correction exige la VRAM LLM, pas le STT. C'est ce qui rĂ©sout « par construction » le cas d'un STT bloquĂ© qui boucle, et permet Ărun_correctionde renvoyer simplementvram_wait. - Frontal
role=websans GPU (split) : aucune phase GPU n'est exĂ©cutĂ©e sur le frontal. Les Ă©tapes GPU synchrones du wizard â rĂ©sumĂ© (api_summary) et dĂ©tection de locuteurs (api_speakers_detect) â sont enfilĂ©es sur le worker GPU (modes de filesummary/speakers,JobExecutorService.STEP_MODES) ; le client pollGET /statuset la page se rafraĂźchit. Le frontal ne fait qu'orchestrer ; toute la charge GPU (STT, diarisation, dĂ©tection, LLM comprise) est portĂ©e par la machine GPU (worker/nĆud de ressources). La dĂ©cision repose sur le rĂŽle, pas sur une dĂ©tection matĂ©rielle (un Ă©ventuel petit GPU frontal est ignorĂ©). Rappel : la LLM d'arbitrage est locale au worker, pas servie par le nĆud de ressources â donc le worker doit avoir un GPU. Une LLM 35B en CPU est inexploitable (â100-300Ă plus lente). - Fichiers de jobs en split (frontale â machine worker) : les deux tiers partagent la base mais
PAS le disque â l'audio uploadĂ© sur la frontale, le contexte (invitation/lexique/mapping) et les
artefacts produits par le worker (SRT, qualité, clips, résumé) doivent circuler. Solution intégrée :
storage.shared_backend: pgâ les fichiers sont rĂ©pliquĂ©s via PostgreSQL (push Ă l'upload/ enfilage et Ă chaque checkpoint de phase, matĂ©rialisation paresseuse cĂŽtĂ© frontale, intĂ©gritĂ© sha256, purge de l'audio aux Ă©tats terminaux). Le nĆud de ressources, lui, ne stocke jamais de fichier utilisateur (audio reçu par upload HTTP en fichier temporaire, supprimĂ© Ă la fin de la requĂȘte). DĂ©tails :docs/STOCKAGE_PARTAGE_JOBS.md; garde-fou : check « Stockage des fichiers de jobs (split) » dudoctor. - RĂ©sumĂ© synchrone (
api_summary,role=all) : la 1Êłá” tentative reste synchrone (UX immĂ©diate). Survram_wait, l'Ă©tat prĂ©-rĂ©sumĂ© est restaurĂ©, le job passewaiting_vram, et une reprise serveur est enfilĂ©e (submit_process(mode="summary"), profil VRAMsummary_stt). Le scheduler relance alorsrun_summaryvia_run_processdĂšs que l'admission VRAM le permet â mĂȘme sans page ouverte._run_processtraite ce mode Ă part :run_summarygĂšre l'Ă©tat (SUMMARY_DONE/FAILED), l'exĂ©cuteur libĂšre seulement la file (pas deCOMPLETEDni d'e-mail propriĂ©taire). Le wizard (wizard.js) ne relance plus/summary; il pollGET /statuset recharge Ăsummary_done(zĂ©ro double-exĂ©cution :api_summaryrefuse une relance synchrone tant qu'une entrĂ©esummaryest active). - Alerte admin (une fois par Ă©pisode) :
transcria/notifications/admin_alerts.alert_admin_vram_waitâ e-mail aux comptes ADMIN actifs (send_admin_vram_alert_async) + logWARNINGstructurĂ©. L'anti-spam repose sur un drapeau persistantextra_data.vram_alert_sent, rĂ©armĂ© uniquement aux transitions terminales (completed/failed/cancelled) â pas Ă chaque re-dispatch. - Bandeau in-app :
base.htmlaffiche le nombre de jobs en attente (JobStore.count_waiting_vram) aux administrateurs, via le context processorinject_vram_waiting_count. - DĂ©blocage par arrĂȘt de NOTRE LLM inactive (catĂ©gorie 1, Ă deux niveaux) : si un STT/diarisation
manque de VRAM parce que la LLM d'arbitrage chaude (souvent étalée sur tous les GPU via
--tensor-split) la dĂ©tient encore, l'attente serait sans fin (rien ne la libĂšre). On arrĂȘte alors proprement notre LLM d'arbitrage si elle est inactive (verrou LLM libre = aucun job ne s'en sert ;stop_arbitrage_llm, relancĂ©e Ă la phase de correction) â helper partagĂ©transcria/gpu/vram_reclaim.stop_idle_arbitrage_llm. Deux points de dĂ©clenchement :- en cours de phase (
WorkflowRunner, surGPUSessionError) puis re-réservation ; - à l'admission du scheduler (
_resources_available) avant dispatch â indispensable, car sinon un job en file resteraitwaitingindĂ©finiment derriĂšre notre propre LLM (le reclaim de phase ne tourne jamais tant que le job n'est pas dispatchĂ©). On ne stoppe jamais la LLM « pour la phase LLM elle-mĂȘme » : la phasellm_arbitrationest dĂ©jĂ ignorĂ©e Ă l'admission quand la LLM est partagĂ©e (llm_shared). Le besoin dĂ©clencheur est toujours une phase non-LLM. C'est notre process gĂ©rĂ©, jamais un tiers ; indĂ©pendant du calendrier.
- en cours de phase (
- VRAM de la LLM d'arbitrage = besoin MULTI-GPU (audit du 11/06/2026) : la LLM (ex. 35B Q8
â 60 Go) s'Ă©tale sur plusieurs cartes via son script de lancement (
CUDA_VISIBLE_DEVICES+--tensor-split) â son besoin ne tient JAMAIS sur un seul GPU. L'ancien modĂšle (rĂ©servation mono-GPU dellm_vram_mb) Ă©tait insatisfaisable par construction : code mort tant que la LLM tournait, et deadlockvram_waitdĂšs qu'il fallait la relancer (aprĂšs un reclaim, un crash, ou en lancement Ă la demande). De plus le drapeau stockĂ©llm_sharedĂ©tait inconditionnellement vrai â l'admission ne vĂ©rifiait jamais la LLM. Nouveau modĂšle :gpu.llm_vram_mb= empreinte totale,gpu.llm_gpu_indices= cartes du script (dĂ©faut : tous les GPU visibles) ; le besoin par carte = total Ă· nb de cartes ;GPUAllocator.can_host_llm(lecture) ettry_reserve_llm(rĂ©servation tout-ou-rien, une part par carte, libĂ©rĂ©e d'un bloc parrelease_phase) ;- phases
summary_llmetllm_arbitrationdu runner : réservation multi-GPU ; - admission (
_llm_admissible) sur la vĂ©ritĂ© vivante : LLM en marche â rĂ©ellement partagĂ©e (rien Ă exiger) ; Ă©teinte âcan_host_llmrequis ; le max mono-GPU (_local_required_mb) ne compte plus que les phases NON-LLM (STT/diarisation). Ă recalibrer (llm_vram_mb+llm_gpu_indices) Ă chaque changement de modĂšle/script.
- Parc hĂ©tĂ©rogĂšne (cartes de 2 Ă 64 Go) : aucune taille n'est supposĂ©e â la mesure rĂ©elle
par carte (nvidia-smi) est l'autorité, les besoins par moteur sont des réglages. En plus :
gpu.llm_vram_mb_per_gpudéclare une part inégale par carte (--tensor-split 3,1sur 24+8 Go) ; la sélection de GPU des petites phases préfÚre les cartes hors placement LLM (à disponibilité suffisante) pour ne pas bloquer la relance de la LLM ; etgpu.min_free_vram_mb(marge par carte) est à réduire sur les petites cartes. - Politique
gpu.preemption(rĂ©glable dans/admin/configâ « Ressources GPU ») :own-only(dĂ©faut, infra partagĂ©e) : catĂ©gorie 1 seulement (nos process trackĂ©s inactifs).aggressive: autorise en plus la prĂ©emption de serveurs d'infĂ©rence tiers (kill_patterns, process non trackĂ©s viaforce_free_gpu), uniquement dans la fenĂȘtre calendaireforce_gpu. Ă rĂ©server Ă un GPU dĂ©diĂ© Ă TranscrIA. La distinction « Ă nous / tiers » s'appuie surGPUAllocator._tracked_pids(PID que nous avons lancĂ©s).
En tout-en-un, le service ressources est local : une indisponibilitĂ© = crash process, restaurĂ© par systemd en quelques secondes. Seule la 1Êłá” ligne (transitoire) s'applique alors â le job patiente via re-queue le temps du redĂ©marrage. Les cas « prolongĂ© » et « fenĂȘtre dĂ©passĂ©e » sont des prĂ©occupations de la topologie split (rĂ©seau, nĆud distant, crash GPU durable).
8. Configuration (esquisse)
deployment:
mode: all_in_one # all_in_one | frontale | resource_node
inference:
mode: remote # local | remote | hybrid (existant)
url: "http://192.168.1.59:8002" # service Flask ressources
transport: { audio: upload } # OBLIGATOIRE en distant (cf. §9)
resilience: { timeout_s: 1800, retries: 2, max_unavailable_s: 600 } # cf. §7.2 (mode dégradé)
stt:
backends:
cohere: { url: "http://192.168.1.59:8003/v1", model: cohere-transcribe, response_format: json }
whisper: { url: "http://192.168.1.59:8005/v1", model: whisper-large-v3, response_format: verbose_json }
# CĂŽtĂ© nĆud ressources uniquement : manifeste des moteurs gĂ©rĂ©s.
resource_node:
vram:
preflight: true # niveau 1 â toujours
auto_relocate: true # niveau 2 â repli GPU si saturĂ© (log bruyant)
engines:
- name: cohere ; script: scripts/launch_stt_cohere.sh ; gpu: 3 ; gpu_mem: 0.85 ; idle_timeout_s: 0
- name: whisper ; script: scripts/launch_stt_whisper.sh ; gpu: 5 ; gpu_mem: 0.85 ; idle_timeout_s: 0
idle_timeout_s: 0 = résident (défaut). gpu/gpu_mem = placement admin, jamais réécrit
(seule la relocalisation peut surcharger gpu, et seulement si auto_relocate: true).
9. Rappels & correctifs liés
transport.audio: uploadobligatoire en distant.file_refenvoie un chemin que le nĆud distant ne peut pas rĂ©soudre (filesystem non partagĂ©). DĂ©montrĂ© par les tests d'intĂ©gration.- Correctif allocator (bug silencieux, Ă prioriser) : en mode distant,
try_reserve(job_id, phase, âŠ)rĂ©serve quand mĂȘme de la VRAM pour les phasesstt/diarizationalors que rien ne se charge localement (observĂ© :phase=stt gpu=5 vram=6000pendant un run 100 % distant). Impact : fausse contention VRAM â OOM possible ou rejets Ă tort de tĂąches locales ; et incohĂ©rence sur une frontale CPU-only (rĂ©server une VRAM qui n'existe pas). Correction : ne pas rĂ©server de VRAM locale pour une phase servie Ă distance (WorkflowRunner._phase_runs_remotelyâ_reserve_gpu_phaseretourne une rĂ©servation Ă 0 VRAM). Toutes les phases sont couvertes :run_transcription(stt, via_reserve_gpu_phase),_run_quick_transcription(summary_stt) etrun_diarization(diarization) sautent toute rĂ©servation/_gpu_sessionlocale quand la phase est servie Ă distance (sinon rĂ©servation fantĂŽme â 6000 Mo STT, 2000 Mo diarisation â et attente VRAM Ă tort sur un tier sans GPU). La dĂ©tection de locuteurs du rĂ©sumĂ© (pyannote) reste toujours locale (jamais dĂ©lĂ©guĂ©e). - SĂ©curitĂ© rĂ©seau : clĂ© API partagĂ©e dĂ©jĂ en place (Flask
enforce_api_key; vLLM--api-key). Un 401 est dĂ©finitif (pas de retry ni de bascule locale) â testĂ©. - ObservabilitĂ© du lancement LLM d'arbitrage :
VRAMManager.launch_arbitrage_llm(etScriptLLMBackend.ensure_available) capturent la sortie du script dansservices.arbitrage_log_path(dĂ©faut/tmp/arbitrage_llm_<port>.log, comme le superviseur STT le fait dĂ©jĂ viastt_<name>_<port>.log). L'attente du port dĂ©tecte la mort prĂ©coce du process (proc.poll()) et abandonne sans attendre les 600 s ; en cas d'Ă©chec (mort prĂ©coce ou timeout), le code de sortie et les derniĂšres lignes du log sont Ă©crits enERROR. Sans cela, un dĂ©marrage ratĂ© (binaire absent, OOM,--tensor-splitâ nb GPUs) restait invisible â seul subsistait le placeholder « RĂ©sumĂ© indisponible (LLM non configurĂ©e) ». Cf. dĂ©pannageINSTALL.md§12.
10. Déploiement sur l'autre machine
- Installation : un profil dédié
resource-nodeexiste dĂ©sormais (nĆud ressources seul, sans frontale web ni base applicative) â./install.sh --profile resource-node(gĂ©nĂšre le manifesteresource_node.engines, configure la clĂ© API, n'installe pas de base). En conteneur : image GPU +python -m transcria.deploy.entrypoint resource-node(cf.DOCKER.md). DĂ©pendances : pyannote,librosa/soundfile, ffmpeg, llama.cpp (cf.INSTALL.md). - ParamĂštres exposĂ©s cĂŽtĂ© nĆud (manifeste §8, ports, fractions VRAM, clĂ© API).
- DĂ©tection ressources : GPU, VRAM, modĂšles prĂ©sents â au dĂ©marrage + via
/capabilities. - Réseau : bind
0.0.0.0, ports (service 8002, STT 8003/8005/8007, arbitrage 8080), pare-feu. - Supervision / redĂ©marrage : dĂ©cidĂ© â units systemd pour tous les serveurs persistants (vLLM,
llama.cpp, service Flask), avec
Restart=on-failure. C'est la réponse v1 au « qui redémarre un moteur tombé ». Un agent de redémarrage interne au service reste une option v2 si besoin.
11. Arbitrages (pistes écartées)
| Piste | Décision | Raison |
|---|---|---|
| B â STT dans le service Flask in-process (load/offload via transformers) | â Ă©cartĂ©e | perd le dĂ©bit et le batching continu de vLLM |
| Superviseur de process complet (supervision fine, redĂ©marrages, arbitrage hĂ©tĂ©rogĂšne) | â Ă©cartĂ©e (v1) | usine Ă gaz, fragile ; on Ă©tend l'existant Ă la place |
| A â Ă©tendre le pattern arbitrage-LLM aux STT vLLM | â retenue | rĂ©utilise VRAMManager + scripts, incrĂ©mental, non intrusif |
12. Plan d'implémentation (incrémental)
Ătat (commits
6423fa1âe379e0f) : tout le plan est livrĂ©. items 1-6 â , re-queue diffĂ©rĂ© §7.2 â , relocalisation auto (7) cĂąblĂ©e opt-in â , idle-stop (8) â en version minimale (rĂ©clamation opportuniste via/capabilities, sans tĂąche de fond).
v1 (cĆur)
- PrĂ©-check VRAM (niveau 1) au lancement des moteurs STT â transforme l'OOM en 503 clair.
- Correctif allocator (bug silencieux §9) : pas de rĂ©servation VRAM locale pour une phase distante â remontĂ© car corruption de comptabilitĂ© VRAM, indĂ©pendant et peu risquĂ©.
- Cycle de vie STT (CAS A/B/C) via scripts +
VRAMManager, calqué sur l'arbitrage LLM. /capabilities+ détection ressources au démarrage du service.- Panneau d'état frontale (mode + feu vert) + mode dégradé (§7.2).
v1.1 (gain UX immĂ©diat) 6. Concurrence bornĂ©e du STT par tour (§5) â latence la plus visible cĂŽtĂ© utilisateur.
v1.2 (confort) 7. Relocalisation auto (niveau 2) opt-in + log bruyant. 8. idle-stop opt-in par moteur.
13. Risques & points ouverts
- Sémantique fraction-de-total de vLLM (§4) : bien la coder dans le pré-check/relocalisation.
- Courses au dĂ©marrage concurrent â verrou
VRAMManager(dĂ©jĂ prĂ©sent) Ă rĂ©utiliser strictement. - Cold start (25â105 s) sur CAS B / relocalisation : la frontale gĂšre l'attente via re-queue
différé (
requeue_later+scheduled_at, implĂ©mentĂ© §7.2). - En split, redĂ©marrage d'un moteur tombĂ© : dĂ©cidĂ© â systemd
Restart=on-failureen v1 (§10) ; agent interne au service en option v2.