diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index c1717cb..1cccc21 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -54,6 +54,13 @@ jobs: push: true build-args: | LOKI_VERSION=${{ github.sha }} + # Sans cache, chaque push recompilait whisper.cpp avec CUDA — une + # quarantaine de minutes — alors que ce binaire ne dépend d'AUCUN + # fichier du dépôt. Les étapes whisperbuild-* sont désormais réutilisées + # tant que leurs lignes du Dockerfile ne bougent pas ; seul le code Go + # et l'assemblage de l'image se refont. + cache-from: type=gha + cache-to: type=gha,mode=max tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha diff --git a/Dockerfile b/Dockerfile index 307695a..53ad936 100644 --- a/Dockerfile +++ b/Dockerfile @@ -66,9 +66,14 @@ RUN git clone --depth 1 https://github.com/ggml-org/whisper.cpp /w \ && cmake -S /w -B /w/build ${WHISPER_CMAKE_FLAGS} \ && cmake --build /w/build -j"$(nproc)" --target whisper-server -# Version CUDA. La MAJEURE de CUDA doit correspondre à celle de l'image -# runtime : le binaire est lié dynamiquement à libcudart, fournie par -# LLAMACPP_IMAGE et non par cette étape. +# Version CUDA : la MÊME que celle de l'image amont (llama.cpp bâtit avec +# CUDA 12.8.1 sur Ubuntu 24.04). Le binaire est lié dynamiquement à libcudart, +# fournie par LLAMACPP_IMAGE et non par cette étape. +# +# ⚠️ 12.8 est un PLANCHER, pas un détail de version. Les GPU Blackwell +# (RTX 50xx, sm_120) n'existent pas pour un nvcc 12.4 : il refuse l'archi, et +# à défaut le binaire ne tourne que par recompilation PTX au chargement, quand +# elle est possible. # # ⚠️ -j"$(nproc)" et JAMAIS -j nu. Avec Make, « -j » sans nombre autorise un # parallélisme ILLIMITÉ. ggml-cuda compte ~200 fichiers d'instanciation de @@ -76,13 +81,21 @@ RUN git clone --depth 1 https://github.com/ggml-org/whisper.cpp /w \ # tué par l'OOM après avoir lancé 92 % des compilations en treize secondes, # sans écrire la moindre ligne d'erreur. L'étape CPU y survivait — peu de # fichiers, compilation légère — ce qui rendait le piège invisible jusqu'ici. -FROM nvidia/cuda:12.4.1-devel-ubuntu22.04 AS whisperbuild-cuda +# +# CUDA_ARCHS borne le travail : ggml compile sinon pour TOUTES les +# architectures qu'il connaît, de Maxwell à Blackwell, et chacune multiplie le +# temps de compilation. La liste par défaut couvre Turing à Blackwell +# (RTX 20xx → 50xx) ; l'élargir pour un GPU plus ancien se fait sans toucher +# au fichier : --build-arg CUDA_ARCHS="61;75;86". +FROM nvidia/cuda:12.8.1-devel-ubuntu24.04 AS whisperbuild-cuda ARG WHISPER_CMAKE_FLAGS +ARG CUDA_ARCHS="75;86;89;120" RUN apt-get update && apt-get install -y --no-install-recommends \ build-essential cmake git ca-certificates \ && rm -rf /var/lib/apt/lists/* RUN git clone --depth 1 https://github.com/ggml-org/whisper.cpp /w \ && cmake -S /w -B /w/build ${WHISPER_CMAKE_FLAGS} -DGGML_CUDA=ON \ + -DCMAKE_CUDA_ARCHITECTURES="${CUDA_ARCHS}" \ && cmake --build /w/build -j"$(nproc)" --target whisper-server \ && strip /w/build/bin/whisper-server diff --git a/internal/loki/web_transcribe_test.go b/internal/loki/web_transcribe_test.go index dd319ee..f2170c8 100644 --- a/internal/loki/web_transcribe_test.go +++ b/internal/loki/web_transcribe_test.go @@ -3,6 +3,7 @@ package loki import ( "os" "regexp" + "strconv" "strings" "testing" ) @@ -122,6 +123,38 @@ func TestDockerfileDeuxBinairesWhisper(t *testing.T) { } } +// L'étape CUDA doit être bâtie avec un nvcc qui CONNAÎT les GPU visés. CUDA +// 12.4 ignore Blackwell (RTX 50xx, sm_120) : il refuse l'architecture, et le +// binaire ne tournerait au mieux que par recompilation PTX au chargement. +// 12.8 est le plancher, et c'est aussi la version avec laquelle llama.cpp bâtit +// l'image amont qui fournit libcudart. +func TestDockerfileCudaAssezRecent(t *testing.T) { + const majeurMin, mineurMin = 12, 8 + trouve := false + for nom, txt := range etapesWhisperbuild(t) { + m := regexp.MustCompile(`nvidia/cuda:(\d+)\.(\d+)`).FindStringSubmatch(txt) + if m == nil { + continue + } + trouve = true + maj, _ := strconv.Atoi(m[1]) + min, _ := strconv.Atoi(m[2]) + if maj < majeurMin || (maj == majeurMin && min < mineurMin) { + t.Errorf("étape %s : CUDA %s.%s — trop ancien pour Blackwell (sm_120), il faut au moins %d.%d", + nom, m[1], m[2], majeurMin, mineurMin) + } + // Sans borne d'architectures, ggml compile de Maxwell à Blackwell : + // chaque architecture multiplie le temps de compilation, et le build + // dépassait quarante minutes. + if !strings.Contains(txt, "CMAKE_CUDA_ARCHITECTURES") { + t.Errorf("étape %s : aucune borne CMAKE_CUDA_ARCHITECTURES — la compilation vise toutes les architectures connues", nom) + } + } + if !trouve { + t.Error("aucune étape whisperbuild ne part d'une image nvidia/cuda") + } +} + func clefs(m map[string]string) []string { out := make([]string, 0, len(m)) for k := range m {