mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-11 17:26:57 +02:00
Parakeet TDT 0.6B v3 (NVIDIA), servi par sherpa-onnx. La v3 et non la v2 : c'est la seule des deux qui parle français — la v2 est anglais seul. CE QUI DISPARAÎT DU DOCKERFILE Deux étapes de compilation, dont une CUDA de ~200 fichiers nvcc qui a été tuée par l'OOM du runner plus d'une fois, et avec elles tout l'appareillage de garde-fous qu'elles réclamaient (GGML_NATIVE=OFF contre le SIGILL en production, bornage des architectures CUDA, deux binaires CPU/CUDA à choisir à l'exécution). À la place : le téléchargement d'un binaire statique de 35 Mo, version épinglée et empreinte SHA-256 vérifiée — le binaire s'exécute sur la machine de l'utilisateur, une release remplacée en amont ne doit pas passer en silence. CE QUI CHANGE DANS LE CODE La forme est la même — un processus local supervisé, éteint après dix minutes d'inactivité. Le dialogue, lui, change : whisper-server exposait du HTTP multipart, sherpa-onnx n'expose qu'un WebSocket dont le protocole tient en deux entiers et des flottants. D'où un client WebSocket et une conversion WAV → float32 côté serveur. Le parcours des blocs du WAV n'est pas du zèle : l'offset 44 codé en dur transforme un bloc LIST intercalé en craquement au début de chaque phrase. DEUX RÉGLAGES DISPARAISSENT, ET C'EST LE MOTEUR QUI L'IMPOSE La LANGUE : Parakeet la détecte lui-même, il n'a aucun drapeau pour la forcer. Le réglage n'aurait servi qu'à mentir. À surveiller : whisper avait précisément écarté la détection automatique parce qu'elle se trompait sur des tranches courtes. Le GPU : le build livré est le statique CPU. Annoncer un sélecteur de carte sans pouvoir l'honorer serait pire que de ne rien annoncer — et un modèle de 0,6 B en int8 sur des tranches de quelques secondes n'en a pas besoin, la carte reste au moteur de chat. MIGRATION Un réglage enregistré du temps de whisper retombe sur le défaut au lieu de casser la dictée. Les modèles ggml de /data/whisper/ ne servent plus à rien mais ne sont PAS effacés : ce sont des données que personne n'a demandé de perdre. Ils sont à supprimer à la main. Le paquet UI regénéré ici couvre aussi les sources du commit précédent. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
79 lines
2.9 KiB
Go
79 lines
2.9 KiB
Go
package loki
|
|
|
|
import (
|
|
"testing"
|
|
)
|
|
|
|
// Les défauts sont appliqués à la LECTURE : un réglage enregistré par une
|
|
// version antérieure, sans le champ, doit être comblé plutôt que de produire un
|
|
// modèle vide passé en argument au serveur.
|
|
func TestDefautsCombles(t *testing.T) {
|
|
testHome(t)
|
|
c := DictateCfg{}.withDefauts()
|
|
if _, ok := asrCatalogue[c.Model]; !ok {
|
|
t.Errorf("modèle par défaut %q hors catalogue", c.Model)
|
|
}
|
|
if c.Reactivity == "" {
|
|
t.Error("réactivité par défaut vide")
|
|
}
|
|
}
|
|
|
|
// Le passage de whisper à Parakeet laisse des réglages enregistrés dont le
|
|
// Model ne veut plus rien dire (« large-v3-turbo-q5_0 »). Ils doivent retomber
|
|
// sur le défaut, sans quoi la dictée serait morte après mise à jour, avec pour
|
|
// seul indice « modèle de dictée inconnu » au premier clic sur le micro.
|
|
func TestModeleWhisperHeriteRetombeSurLeDefaut(t *testing.T) {
|
|
testHome(t)
|
|
c := DictateCfg{Model: "large-v3-turbo-q5_0", Reactivity: "court"}.withDefauts()
|
|
if _, ok := asrCatalogue[c.Model]; !ok {
|
|
t.Fatalf("modèle hérité %q non corrigé", c.Model)
|
|
}
|
|
if c.Reactivity != "court" {
|
|
t.Errorf("réactivité = %q, elle devait être conservée", c.Reactivity)
|
|
}
|
|
}
|
|
|
|
// Valider à l'ÉCRITURE : un modèle hors catalogue accepté en silence ne se
|
|
// manifesterait qu'au premier clic sur le micro, loin du geste qui l'a causé.
|
|
func TestSaveRefuseModeleInconnu(t *testing.T) {
|
|
testHome(t)
|
|
if err := dictateCfgSave(DictateCfg{Model: "nawak", Reactivity: "moyen"}); err == nil {
|
|
t.Error("un modèle hors catalogue a été accepté")
|
|
}
|
|
}
|
|
|
|
func TestSaveLoadAllerRetour(t *testing.T) {
|
|
testHome(t)
|
|
want := DictateCfg{Model: "parakeet-tdt-0.6b-v2", Reactivity: "long"}
|
|
if err := dictateCfgSave(want); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got := dictateCfgLoad(); got != want {
|
|
t.Errorf("relu %+v, attendu %+v", got, want)
|
|
}
|
|
}
|
|
|
|
// Les bornes de découpage pilotent la latence perçue : plus la réserve est
|
|
// courte, plus le texte arrive vite, au prix d'un contexte plus maigre pour le
|
|
// modèle. L'ordre entre les trois réglages est ce qui doit tenir.
|
|
func TestChunkBounds(t *testing.T) {
|
|
court, _ := DictateCfg{Reactivity: "court"}.chunkBounds()
|
|
moyen, _ := DictateCfg{Reactivity: "moyen"}.chunkBounds()
|
|
long, longMax := DictateCfg{Reactivity: "long"}.chunkBounds()
|
|
if !(court < moyen && moyen < long) {
|
|
t.Errorf("réserves non croissantes : court=%v moyen=%v long=%v", court, moyen, long)
|
|
}
|
|
if longMax <= long {
|
|
t.Error("la coupure forcée doit être plus grande que la réserve minimale")
|
|
}
|
|
// Une valeur inconnue doit retomber sur le réglage moyen, pas sur zéro : une
|
|
// réserve nulle couperait à chaque échantillon.
|
|
inconnu, _ := DictateCfg{Reactivity: "nawak"}.chunkBounds()
|
|
if inconnu != moyen {
|
|
t.Errorf("réactivité inconnue = %v, attendu le réglage moyen %v", inconnu, moyen)
|
|
}
|
|
if inconnu <= 0 {
|
|
t.Error("réserve nulle : la dictée couperait à chaque échantillon")
|
|
}
|
|
}
|