From d8ed86bbba72cf4ff6cfaacd6ed6995dd8d9497e Mon Sep 17 00:00:00 2001 From: Michael SCHAL Date: Fri, 21 Aug 2026 22:25:53 +0200 Subject: [PATCH] =?UTF-8?q?Image=20:=20CUDA=2012.8=20pour=20Blackwell,=20a?= =?UTF-8?q?rchitectures=20born=C3=A9es,=20cache=20des=20couches?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Le build ne mourait plus, mais tournait encore après trente-sept minutes. Et il aurait produit un binaire inutilisable sur la moitié du matériel visé. Les GPU de la machine cible sont une RTX 5060 Ti (Blackwell, sm_120) et une RTX 3060 (Ampere, sm_86). L'étape de compilation était épinglée sur nvidia/cuda:12.4.1 : ce nvcc-là ne CONNAÎT pas Blackwell. Il refuse l'architecture, et le binaire ne tournerait au mieux que par recompilation PTX au chargement. 12.8.1 sur Ubuntu 24.04 est désormais utilisé — exactement ce avec quoi llama.cpp bâtit l'image amont qui fournit libcudart. Pour la durée, deux causes distinctes : - Sans borne, ggml compile pour TOUTES les architectures qu'il connaît, de Maxwell à Blackwell. CMAKE_CUDA_ARCHITECTURES ramène le travail à quatre (Turing → Blackwell), et l'ARG CUDA_ARCHS permet d'élargir sans toucher au fichier pour un GPU plus ancien. - Surtout : ce binaire ne dépend d'AUCUN fichier du dépôt, et il était pourtant recompilé à chaque push. Le cache de couches GitHub Actions le réutilise tant que ses lignes du Dockerfile ne bougent pas ; seuls le code Go et l'assemblage de l'image se refont. Un test vérifie le plancher CUDA et la présence de la borne d'architectures : ces deux fautes ne se voient pas à la lecture et coûtent quarante minutes à découvrir. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01W3ewMAsXhkw9RY11kb9Dc9 --- .github/workflows/docker-publish.yml | 7 ++++++ Dockerfile | 21 ++++++++++++++---- internal/loki/web_transcribe_test.go | 33 ++++++++++++++++++++++++++++ 3 files changed, 57 insertions(+), 4 deletions(-) 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 {