mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-11 17:26:57 +02:00
Merge pull request #18 from R0m1k3/fix/whisper-binaire-non-portable
Dictée : whisper-cli compilé pour la machine de build, pas pour la tienne
This commit is contained in:
3 files changed
+96
-3
No files matched your search
+10
@@ -36,6 +36,14 @@ RUN GOBIN=/out GOTOOLCHAIN=auto go install golang.org/x/tools/gopls@latest
|
||||
# trimballer, et pas de compilation CUDA (des minutes de build pour un gain
|
||||
# nul sur des dictées de quelques secondes). Ubuntu 22.04 : glibc plus
|
||||
# ancienne que l'image runtime, donc compatible quoi qu'elle embarque.
|
||||
#
|
||||
# ⚠️ GGML_NATIVE=OFF est OBLIGATOIRE. Par défaut ggml compile en -march=native,
|
||||
# c'est-à-dire pour le processeur DU RUNNER DE BUILD — un Xeon récent chez
|
||||
# GitHub, avec AVX-512 et AMX. Le binaire partait alors sur une machine qui
|
||||
# n'a pas ces instructions et mourait d'un SIGILL en pleine transcription :
|
||||
# « AMX is not ready to be used! », puis plus rien, l'interface affichant un
|
||||
# échec sans raison. OFF retombe sur la ligne de base AVX2/FMA/F16C de ggml,
|
||||
# présente sur tout x86-64 depuis 2013.
|
||||
FROM ubuntu:22.04 AS whisperbuild
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
build-essential cmake git ca-certificates \
|
||||
@@ -43,6 +51,8 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
RUN git clone --depth 1 https://github.com/ggml-org/whisper.cpp /w \
|
||||
&& cmake -S /w -B /w/build -DCMAKE_BUILD_TYPE=Release \
|
||||
-DBUILD_SHARED_LIBS=OFF -DWHISPER_BUILD_TESTS=OFF \
|
||||
-DGGML_NATIVE=OFF -DGGML_AVX512=OFF -DGGML_AMX_TILE=OFF \
|
||||
-DGGML_AMX_INT8=OFF -DGGML_AMX_BF16=OFF \
|
||||
&& cmake --build /w/build -j --target whisper-cli
|
||||
|
||||
# ── Étape 2 : runtime sur l'image serveur CUDA officielle ───────────────
|
||||
|
||||
@@ -8,6 +8,7 @@ package loki
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
@@ -180,12 +181,46 @@ func handleTranscribe(w http.ResponseWriter, r *http.Request) {
|
||||
cmd.Stderr = &stderr
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
msg := strings.TrimSpace(stderr.String())
|
||||
if msg == "" {
|
||||
msg = err.Error()
|
||||
// Ne garder QUE stderr est trompeur quand le binaire meurt d'un signal :
|
||||
// la dernière ligne écrite avant l'exécution du coup fatal ressemble à
|
||||
// une explication (« AMX is not ready to be used! ») alors que la vraie
|
||||
// cause est la mort brutale. On dit donc toujours comment il a fini —
|
||||
// un SIGILL/SIGSEGV désigne un binaire compilé pour un autre
|
||||
// processeur, pas un problème d'audio.
|
||||
msg := whisperExitReason(err)
|
||||
if last := lastLine(stderr.String()); last != "" {
|
||||
msg += " — dernière sortie : " + last
|
||||
}
|
||||
sendJSON(w, 500, map[string]any{"error": "whisper-cli : " + msg})
|
||||
return
|
||||
}
|
||||
sendJSON(w, 200, map[string]any{"text": strings.TrimSpace(string(out))})
|
||||
}
|
||||
|
||||
// whisperExitReason traduit la fin du processus en une phrase utilisable.
|
||||
// ProcessState.String() dit déjà « exit status 2 » ou « signal: illegal
|
||||
// instruction » ; le second cas mérite son explication, parce que rien dans la
|
||||
// dictée ne laisse deviner que le binaire ne tourne pas sur ce processeur.
|
||||
func whisperExitReason(err error) string {
|
||||
var ee *exec.ExitError
|
||||
if !errors.As(err, &ee) {
|
||||
return err.Error()
|
||||
}
|
||||
st := ee.ProcessState.String()
|
||||
if strings.Contains(st, "signal:") {
|
||||
return st + " (binaire compilé pour un autre processeur : image à reconstruire)"
|
||||
}
|
||||
return st
|
||||
}
|
||||
|
||||
// lastLine : la dernière ligne non vide. whisper-cli bavarde beaucoup avant de
|
||||
// tomber ; seule la fin renseigne, et un pavé ne tient pas dans un bandeau.
|
||||
func lastLine(s string) string {
|
||||
lines := strings.Split(strings.TrimSpace(s), "\n")
|
||||
for i := len(lines) - 1; i >= 0; i-- {
|
||||
if l := strings.TrimSpace(lines[i]); l != "" {
|
||||
return l
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package loki
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// La dictée est morte en production parce que whisper-cli avait été compilé
|
||||
// avec -march=native, donc pour le processeur du runner GitHub (AVX-512, AMX)
|
||||
// et pas pour la machine qui fait tourner l'image : SIGILL en pleine
|
||||
// transcription. Le drapeau qui l'évite ne se voit pas à l'exécution — rien ne
|
||||
// le rappelle au prochain qui touchera cette étape de build. Ce test le fait.
|
||||
func TestDockerfileWhisperNonNatif(t *testing.T) {
|
||||
b, err := os.ReadFile("../../Dockerfile")
|
||||
if err != nil {
|
||||
t.Fatalf("lecture du Dockerfile : %v", err)
|
||||
}
|
||||
src := string(b)
|
||||
_, apres, ok := strings.Cut(src, "AS whisperbuild")
|
||||
if !ok {
|
||||
t.Fatal("étape whisperbuild introuvable dans le Dockerfile")
|
||||
}
|
||||
// L'étape suivante commence au FROM d'après : ne pas déborder dessus.
|
||||
if fin := strings.Index(apres, "\nFROM "); fin >= 0 {
|
||||
apres = apres[:fin]
|
||||
}
|
||||
for _, drapeau := range []string{"-DGGML_NATIVE=OFF", "-DGGML_AMX_TILE=OFF"} {
|
||||
if !strings.Contains(apres, drapeau) {
|
||||
t.Errorf("l'étape whisperbuild ne passe plus %s : le binaire sera compilé pour le processeur du runner et mourra d'un SIGILL ailleurs", drapeau)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLastLine(t *testing.T) {
|
||||
cas := []struct{ in, want string }{
|
||||
{"", ""},
|
||||
{" \n\n ", ""},
|
||||
{"une seule ligne", "une seule ligne"},
|
||||
{"AMX is not ready to be used!\nread_audio_data: ...\n", "read_audio_data: ..."},
|
||||
{"fin utile\n\n \n", "fin utile"},
|
||||
}
|
||||
for _, c := range cas {
|
||||
if got := lastLine(c.in); got != c.want {
|
||||
t.Errorf("lastLine(%q) = %q, attendu %q", c.in, got, c.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user