fix(ds4): build CUDA kernels for the target architecture - #11840
Open
FiloSpaTeam wants to merge 2 commits into
Open
fix(ds4): build CUDA kernels for the target architecture#11840FiloSpaTeam wants to merge 2 commits into
FiloSpaTeam wants to merge 2 commits into
Conversation
FiloSpaTeam
force-pushed
the
fix/ds4-cuda-arch
branch
from
September 2, 2026 09:19
a4abd50 to
b5ae0d0
Compare
The ds4 backend compiled its CUDA objects with no -arch. Upstream's Makefile leaves CUDA_ARCH empty and its `cuda` target refuses to build without one, offering `cuda-spark` (sm_121) and `cuda-generic` (native) instead. We invoke its object targets directly, which bypasses that guard, so nvcc fell back to its default architecture and the kernels ran as JIT'd PTX on the real GPU. On GB10 (sm_121) that silently corrupted inference: any prompt over roughly 128 tokens produced text unrelated to the input and never closed its thinking block, so content came back empty and the chat showed only reasoning; longer prompts failed with "cuda decode failed". It also cost close to two orders of magnitude of prefill throughput. Measured on one box, same model, same prompt, same GPU, upstream ds4 at the pinned commit, differing only in the nvcc flags: make -B ds4 (archless, as we build it) garbage output 4.21 t/s make cuda-spark (compute_121a/sm_121a) correct output 325.70 t/s Select an architecture list from CUDA_MAJOR_VERSION, which the backend matrix already declares for both ds4 cublas entries but Dockerfile.ds4 never forwarded. Upstream's CUDA_ARCH takes a single value, so it cannot express the fat binary these images need; NVCC_ARCH_FLAGS is overridden instead, since a command-line assignment wins over its `:=`. The lists are copied from vllm-cpp rather than invented so the two CUDA images cover the same GPUs, with l4t/arm64 covering Orin, Thor and GB10. An empty CUDA_MAJOR_VERSION keeps upstream's `native` behaviour for local developer builds, and no CI runner has a GPU to enumerate. DS4_CUDA_HAVE_MXF4 is deliberately left unset: upstream defines it only for single-arch sm_120/sm_121 builds and guards it with a plain #ifdef rather than __CUDA_ARCH__, so it cannot be combined with older archs. It gates an optional MXFP4 indexer fast path whose #ifndef branch returns 0 and falls back cleanly, so omitting it costs speed on GB10, not correctness. Assisted-by: Claude Code:claude-opus-5 Signed-off-by: Claudio Maradonna <git@codeshifter.xyz>
The architecture fix has no automated guard: every existing e2e spec uses a short prompt, and the miscompiled backend answered short prompts correctly. The corruption only appears once a prompt spans more than one prefill batch, so the whole suite passed against a backend that produced garbage in normal use. Add an opt-in "long_prefill" capability to the backend e2e suite that sends a prompt well past one batch with a known needle and asserts the answer still reflects it, and document in the ds4 guide why the build must never omit an nvcc architecture, how to check which flags a configuration resolves to without compiling, and how to run the new spec. Assisted-by: Claude Code:claude-opus-5 Signed-off-by: Claudio Maradonna <git@codeshifter.xyz>
FiloSpaTeam
force-pushed
the
fix/ds4-cuda-arch
branch
from
September 2, 2026 16:09
b5ae0d0 to
bdd509f
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
The ds4 backend compiled its CUDA objects with no
-arch.backend/cpp/ds4/Makefiledrives upstream's object targets directly (
$(MAKE) -C ds4 ds4.o ds4_cuda.o ...),which bypasses upstream's own guard — its
cudatarget refuses to build unlessCUDA_ARCHis set, offeringcuda-spark(sm_121) andcuda-generic(native)instead. nvcc therefore fell back to its default architecture and the kernels ran
as JIT'd PTX on the real device.
On a DGX Spark / GB10 (sm_121) that silently corrupted inference: any prompt over
roughly 128 tokens produced text unrelated to the input and never closed its
thinking block, so
contentcame back empty and the chat showed only reasoning.Longer prompts failed with
ds4 generation failed: cuda decode failed. In a chatthis presents as "only the first message works", because the first prompt is short
and every later turn carries enough history to cross the boundary. Short prompts
stayed correct throughout, which is why nothing caught it.
Isolated against upstream ds4 alone, same box, same model, same prompt, same GPU,
differing only in the nvcc flags:
make -B ds4(archless, as LocalAI built it)make cuda-spark(compute_121a/sm_121a)Dockerfile.ds4now forwardsCUDA_MAJOR_VERSION, which the backend matrixalready declares for both ds4 cublas entries but nothing consumed, and the backend
Makefile selects a gencode list from it plus
uname -mand passes it to thesub-make as
NVCC_ARCH_FLAGS. Upstream'sCUDA_ARCHtakes a single value so itcannot express the fat binary these images need; a command-line assignment wins
over its
:=. The architecture lists are copied frombackend/go/vllm-cpp/Makefilerather than invented, so the two CUDA images cover the same GPUs. An empty
CUDA_MAJOR_VERSIONkeeps upstream'snativebehaviour for local developerbuilds; an unrecognised one is a hard error, since no CI runner has a GPU and a
silent
nativethere is the exact failure being fixed.DS4_CUDA_HAVE_MXF4is deliberately left unset. Upstream defines it only forsingle-arch sm_120/sm_121 builds and guards it with a plain
#ifdefrather than__CUDA_ARCH__, so it cannot be combined with older archs in one fat binary. Itgates an optional MXFP4 indexer fast path whose
#ifndefbranch returns 0 andfalls back to the generic path, so omitting it costs some speed on GB10, not
correctness. Worth revisiting if upstream adds
__CUDA_ARCH__guards.The second commit adds the regression coverage that was missing: an opt-in
long_prefillcapability in the backend e2e suite. Every existing spec uses ashort prompt, so the entire suite passed against a backend that produced garbage
in normal use.
Notes for Reviewers
How it was tested — all of the following on a DGX Spark (GB10, sm_121, CUDA 13.0,
DeepSeek-V4-FlashIQ2XXS), against ds4 pin8db89fe:CUDA_MAJOR_VERSION→CUDA_ARCH=native;CUDA_MAJOR_VERSION=12→ hard error. (Do not usemake -nhere — the recipe is
+$(MAKE) ...and the+prefix runs it even under-n.)sm_87andsm_110; the gencode list appears on every nvcc line.prefill_chunkworkaround in the model config. Prompts of 120/126/128/130/322/1022/2022 tokens all answer correctly with non-empty content and
finish=stop;the multi-turn chat that originally failed returns a coherent answer.
when the corruption was worked around by capping
prefill_chunk, and from 5.1sfor the archless path that returned garbage.
Reproducing it — with a GPU whose architecture is outside nvcc's default, build
the backend, point a model at it and run:
long_prefillfails on an archless build and passes on an arch-correct one, whilepredictpasses either way..agents/ds4-backend.mddocuments both this and theflag check.
Not verified — the amd64 architecture list is exercised by neither the local
build nor the checks above, only by CI.
103ais the entry I'd watch. Eightarchitectures on a 1.4 MB
.cuwill also lengthen that build noticeably; trimmingthe list is reasonable if the cost is unwelcome, since ds4 needs ~80 GB resident on
CUDA and the smaller consumer parts cannot run it regardless.
Possibly related —
go/moss-transcribe-cpplooks like the same class of bug andships four CUDA images: upstream's
CMakeLists.txt:19doesset(GGML_NATIVE ON CACHE BOOL "" FORCE), which overrides the-DGGML_NATIVE=OFFthe backend passes, so vendored ggml selects
CMAKE_CUDA_ARCHITECTURES=nativeon aGPU-less runner. Not addressed here; happy to open it separately. The llama-cpp
family is unaffected — ggml's own default list is sane.
Signed commits