Arrêtez de payer pour le silence : silence trim cloud et idle unload du Whisper local dans Hermes Agent

Les messages vocaux sont le moyen de saisie le plus naturel sur les plateformes de messagerie d’Hermes Agent — maintenez le bouton, parlez, et la transcription part à l’agent. Mais si vous utilisez un provider cloud de speech-to-text (STT), chaque note vocale vous coûte discrètement deux fois : une fois pour le silence.
Une note vocale de 13 secondes ne contient peut-être que 6 secondes de parole réelle — les 7 autres secondes, c’est vous qui réfléchissez. Le Whisper cloud facture à la minute audio, et le silence est facturé au même tarif que la parole (OpenAI facture $0.006/min). Pire, Whisper hallucine des mots sur les passages silencieux — c’est pourquoi les transcriptions cloud contiennent parfois des phrases qui n’ont jamais été prononcées.
Deux PR Hermes Agent récemment fusionnées corrigent exactement ces deux problèmes, toutes deux essentiellement plug-and-play (l’une activée par défaut, l’autre une simple ligne de config) :
- #77581 — Silence trim avant upload pour le STT cloud : réduit les longues pauses avec ffmpeg avant l’upload. Résultat mesuré : une note vocale de 13.2s réduite à 6.2s (-53%) avec une transcription parfaitement équivalente.
- #81027 — Idle unload pour le modèle Whisper local : décharge automatiquement le modèle local après 5 minutes d’inactivité, libérant ~370MB de RAM/VRAM, et le recharge de manière transparente au prochain message vocal.
Contexte : deux routes STT, locale et cloud
Avant les nouvelles fonctionnalités, voici l’architecture. Le STT se trouve dans la section stt de config.yaml ; provider choisit la route :
stt:
enabled: true
provider: local # local | groq | openai | mistral | xai | elevenlabs | deepinfra
language: "en" # global language hint, avoids wrong-language detection on short clips
Route locale (local) : faster-whisper tourne sur votre propre machine — gratuit, privé, mais le modèle reste résident en mémoire. Le durcissement précédent avait déjà ajouté Silero VAD (voice activity detection), donc le silence n’atteint jamais le modèle.
Route cloud (groq/openai/mistral/xai/elevenlabs/deepinfra) : l’audio est uploadé brut vers une API tierce et facturé à la minute audio. Le problème : la route cloud n’a jamais eu de protection équivalente au VAD — l’audio brut, pauses comprises, partait tel quel.
Les deux PR comblent exactement ces lacunes : le silence trim pour la route cloud, l’idle unload pour la route locale.
Partie 1 — Silence trim cloud : réduire les pauses avant l’upload
L’activer
C’est activé par défaut — trois réglages :
stt:
cloud_trim_silence: true # false = always upload the original audio
cloud_trim_threshold_db: -40 # audio quieter than this counts as silence
cloud_trim_keep_ms: 300 # keep 300ms of each pause, preserving word boundaries and pacing
Le flux : avant l’upload, toute séquence de plus de 12 secondes voit ses longues pauses réduites en espaces de 300ms via le filtre silenceremove de ffmpeg (l’audio sous -40dB compte comme du silence), puis la version trimée est uploadée. ffmpeg était déjà une dépendance de ce chemin de code exact (transcodage CAF), donc aucune nouvelle dépendance.
Les chiffres mesurés (vrai run de l’auteur de la PR)
original: 13.15 s (speech 3s + pause 7s + speech 3s)
INFO Trimmed silence from voicenote.wav before cloud STT upload (13.2s -> 6.2s, -53%)
trimmed: 6.24 s
L’audio trimé a été vérifié avec faster-whisper : les deux segments se transcrivent à l’identique par rapport à l’original — pauses supprimées, parole intacte.
La barrière des 12 secondes : pourquoi les courtes séquences échappent au trim
Tout ce qui précède dit « séquences de plus de 12s ». Cette barrière est un contrôle de coût délibéré : les courtes séquences reçoivent un seul ffprobe d’environ ~50ms et sautent l’encodage. Le raisonnement est concret — sur une séquence sous les 12s, l’économie maximale possible est d’environ ~10%, soit environ 1 seconde d’audio, et plusieurs providers facturent de toute façon un minimum par requête (Groq facture un minimum de 10s). L’encodage ne peut jamais s’amortir sur les courtes séquences ; seules les séquences assez longues pour en bénéficier réellement paient le coût d’encodage.
Strictement best-effort : le trim ne peut jamais casser la transcription
C’est le cœur de la conception : le trim est best-effort — chaque mode d’échec upload l’original intact, et la transcription n’échoue jamais à cause du trim :
| Condition | Behavior |
|---|---|
cloud_trim_silence: false |
Original uploads |
| ffmpeg/ffprobe missing | Original uploads |
| Clip shorter than 12s | Original uploads (one probe, no encode) |
| Trim command fails / times out | Original uploads |
| Trimmed result ~empty (mostly-silence clip) | Original uploads — the provider, not a client-side dB heuristic, decides whether it contains speech |
| Trim saves <10% | Original uploads |
Les deux dernières lignes méritent un second regard : la question de savoir si un enregistrement presque silencieux « contient de la parole » est laissée au provider (qui a sa propre détection vocale), et ré-encoder pour une économie <10% est un pur gaspillage — le trim abandonne donc tout simplement.
Quand le désactiver
Le seuil -40dB traite les environnements calmes comme du silence. Si vos messages vocaux sont souvent de la musique, des sons ambiants ou du bruit blanc plutôt que de la parole (par exemple pour demander à l’agent d’identifier une chanson ou un enregistrement de terrain), passez cloud_trim_silence: false pour rétablir les uploads bruts — la même philosophie que vad: false sur la route locale.
Partie 2 — Idle unload du Whisper local : la fuite de 370MB que vous n’aviez pas remarquée
Le problème : charger une fois, retenir pour toujours
Le modèle local faster-whisper est un singleton : il se charge au premier message vocal et ne se libère jamais — pendant toute la durée de vie du processus. Le modèle base retient environ 370MB, même si aucun message vocal n’arrive pendant des heures ou des jours.
C’est particulièrement gaspilleur sur les processus gateway Hermes à longue durée de vie — surtout sur les machines où Whisper entre en compétition avec un LLM local pour le même GPU : la VRAM que Whisper verrouille est de la VRAM que votre modèle local ne peut pas utiliser.
L’activer
stt:
local:
model: "base" # tiny | base | small | medium | large-v3
unload_after_idle_seconds: 300 # 0 = never unload (default); 300 = unload after 5 idle minutes
La valeur par défaut 0 signifie ne jamais décharger — zéro changement de comportement pour les utilisateurs existants. Passez-la à 300 (recommandé pour les setups gateway) et :
- Un thread de surveillance vérifie toutes les 30 secondes ; dès que le temps d’inactivité dépasse le seuil, la référence au modèle est abandonnée pour le GC
- Le prochain message vocal le recharge de manière transparente via le chemin lazy-load existant — aucune différence visible pour l’utilisateur, à part un délai de chargement du modèle
- La config est relue à chaque cycle : modifiez la valeur dans
config.yamlet elle prend effet en un intervalle de vérification — pas de redémarrage du processus ; remettre la valeur à 0 en pleine inactivité annule même le déchargement en attente
Les chiffres mémoire honnêtes : GPU vs CPU
L’auteur de la PR a mesuré les deux honnêtement, et cela vaut la peine d’être cité :
- CUDA/GPU : une fois l’objet modèle passé au GC, la VRAM est rendue à l’appareil — la grande victoire pour les setups qui partagent le GPU.
- CPU (mesuré sur macOS) : les références Python sont abandonnées et la mémoire devient réutilisable, mais l’allocateur C++ de ctranslate2 ne rend pas les pages à l’OS, donc le RSS bouge à peine (mesuré : 388MB avant et après). Sous Linux, le comportement de
malloc_trimde glibc peut en rendre une partie.
En clair : sur GPU c’est une vraie libération ; sur CPU cela rend surtout la mémoire réutilisable — le modèle ne verrouille plus des centaines de Mo d’objets vivants, et un modèle de taille différente configuré plus tard se charge dans l’espace récupéré au lieu de faire grossir davantage le processus. Dans les deux cas, ce n’est plus « charger une fois, retenir pour toujours ».
Partie 3 — Configs recommandées par cas d’usage
| Scenario | Recommendation |
|---|---|
| Long-running gateway, local LLM on the same GPU | local.unload_after_idle_seconds: 300 (strongly recommended — VRAM is truly freed) |
| Desktop/CLI, occasional voice, tight RAM | local.unload_after_idle_seconds: 600 — unload after 10 idle minutes |
| Frequent voice messages, latency-sensitive | Keep 0 — avoid the model-load wait on the first message |
| Cloud STT (groq/openai, etc.) | Keep cloud_trim_silence: true (default) — costs drop immediately |
| Voice messages are often music/ambient | cloud_trim_silence: false |
Après modification, ouvrez le fichier avec hermes config edit, ou vérifiez les valeurs actuelles avec hermes config get stt.local.unload_after_idle_seconds.
Quatre conseils pratiques
- STT cloud + commandes vocales courtes, le meilleur combo : la barrière des 12s fait que les commandes ordinaires (« vérifie la météo de demain ») ne déclenchent jamais le trim ni ne paient le coût d’encodage — le trim n’intervient que pour les longues notes vocales.
- Ne sautez pas l’indication de langue : un
stt.language: "en"global s’applique aussi aux providers cloud (la config par provider gagne), et les courtes commandes vocales cassent souvent parce que l’auto-détection de Whisper devine la mauvaise langue. - Vérifiez avant de modifier :
hermes config get stt.cloud_trim_silenceaffiche directement la valeur effective ; lancezhermes config checkaprès vos modifications pour valider la syntaxe. - Local et cloud sont interchangeables selon les besoins : changez le champ
providerà tout moment. Vous voulez zéro coût ?local(gratuit mais résident en mémoire — associez-le à l’idle unload). Vous voulez un multilingue très précis ? Route cloud — et le silence trim garde la facture sous contrôle.
Résumé
Ensemble, les deux PR font le ménage sur les deux routes STT : la route cloud ne paie plus pour les pauses et ne voit plus ses transcriptions polluées par des hallucinations de silence ; la route locale ne verrouille plus 370MB de RAM/VRAM à tourner à vide. De petites configs, zéro nouvelle dépendance, des gains purement incrémentaux — les utilisateurs intensifs en voix (surtout les setups gateway résident + STT cloud) devraient les activer dès aujourd’hui.
Pour aller plus loin sur les capacités vocales d’Hermes Agent et la configuration associée, consultez les notes de version du Voice Patch v0.19.1, le guide d’installation et les notes de version Herald v0.20.0.