gpu: gate_kquant fails when the PTX comes from the Rust toolchain's llc (LLVM 23), which cuda-oxide picks ahead of LLVM 21
Maintainers usually reply within 1 day
Nobody has claimed this yet.
Assessment
- Difficulty
- 5/5
- Estimated time
- Over a week
- Newbie friendliness
- 25/100
- Issue type
- Bug
- Clarity
- Mostly clear
- Activity status
- Active
- Tech stack
- rust
- Domain
- backend, performance, testing
Research direction
Start with the rounding comments in crates/gpu/src/cores.rs:235-246 and crates/gpu/src/kquant/walk.rs:15-23, then inspect the one-column and m-column walks named in the report. Reproduce with cargo oxide build --arch sm_86 -- -p bloomery-gpu-gates --features gpu --release --bin gate_kquant and run target/release/gate_kquant. Done when the reported mcol and tiles comparisons pass across the failing toolchain and cubin builds.
Written by the indexing model from the issue text.
Description
gate-gpu-kquant failed on an RTX 5090 in a fixture-tier run and passed on a GB10. The card is not the cause. The llc that made the PTX is. Rebuilt on the RTX 5090 with CUDA_OXIDE_LLC pointing at LLVM 21.1.8's llc, the gate passes there too.
What fails
Two clauses, four lines, the same values in every failing run:
mcol[K=4096] n_sb=16 rows=64 m=1..8 column_is_one_column_walk=[true, false, false, false, false, false, false, false] FAIL m=2 first_mismatch=slot 0 row 1 got 0xc376a8fd want 0xc376a8fe
mcol[K=8192] n_sb=32 rows=64 m=1..8 column_is_one_column_walk=[true, false, false, false, false, false, false, false] FAIL m=2 first_mismatch=slot 0 row 3 got 0x43c883ec want 0x43c883ea
tiles[gate_up_q4k swiglu_clamp_10] entries=150 rows=2048 entry_rows_are_the_sel_rows=false fault="none" FAIL first_mismatch=slot 0 row 1 got 0xa6ef9cb2 want 0xa6ef9c3c
tiles[gate_up_q4k swiglu_clamp_0.5] entries=150 rows=2048 entry_rows_are_the_sel_rows=false fault="none" FAIL first_mismatch=slot 0 row 1 got 0xa4bfb08e want 0xa4bfb030
The first mismatches are 1 and 2 ULP apart in mcol and 118 and 94 ULP apart in tiles (the gate prints only the first mismatch of a clause). Every comparison of an m-column walk against the one-column walk at K >= 4096 fails, and every one at K <= 2304 passes, which fits the gate's own note that a split term over a nonzero sum needs K >= 4096 (gate_kquant.rs:25-27). tiles[gate_up_q5k ...] and tiles[down_q5k] run at K = 2048, and q4k_sibling[K=4096], which compares two one-column walks, passes.
Which builds fail
All from main e8e32a38, -p bloomery-gpu-gates --features gpu --release --bin gate_kquant:
| Card | Build | PTX or cubin from | Result |
|---|---|---|---|
| RTX 5090 | --arch sm_86 |
the Rust toolchain's llc, LLVM 23.1.0-rust (CUDA_OXIDE_LLC unset) |
the four lines fail |
| RTX 5090 | --arch sm_120 |
the same | the four lines fail |
| RTX 5090 | --arch sm_86, CUDA_OXIDE_LLC=<LLVM 21.1.8>/bin/llc |
LLVM 21.1.8 | pass |
| GB10 | --arch sm_86, CUDA_OXIDE_LLC=<LLVM 21.1.8>/bin/llc |
LLVM 21.1.8 | pass |
| RTX 5090 | --arch sm_120 --materialize-cubin |
libNVVM and nvJitLink, CUDA 13.3 | the four lines fail |
| GB10 | --arch sm_121 --materialize-cubin |
libNVVM and nvJitLink, CUDA 13.3.1 | the four lines fail |
How I narrowed it down:
- The two LLVM 21 PTX files (built on x86_64 and on aarch64) have the same instruction mix in
q5k_gemv_sel,q4k_gemv_sel,kq_gate_up_act_q4k,kq_gate_up_act_q4k_tilesand the gate'skq_gemv_mcol_q5k.q4k_gemv_selis byte-identical, and the others are identical once the//comments are stripped. - LLVM 23's PTX fuses one more multiply and add per walk than LLVM 21's: one more
fma.rn.f32and one fewermul.f32andadd.f32inq5k_gemv_sel,q4k_gemv_selandkq_gemv_mcol_q5k, and two of each inkq_gate_up_act_q4kandkq_gate_up_act_q4k_tiles, which walk the gate rows and the up rows. Inq4k_gemv_sel, for example, the counts offma.rn.f32,add.f32andmul.f32are 9, 5 and 12 under LLVM 23, and 8, 6 and 13 under LLVM 21. - It is not the driver JIT. For the same PTX, the SASS that driver 595.99 JIT-compiled on the RTX 5090 (cut out of a fresh
CUDA_CACHE_PATH) is identical, encodings included, to offlineptxas13.3.73-arch=sm_120for all five kernels. On the GB10 the driver JIT (610.43.02) matchesptxas13.3-arch=sm_121athe same way. - It is not the host CPU flags. Dropping the x86_64 table from
.cargo/cuda-oxide.tomlgives byte-identical PTX.
The doc comment on q4k_acc (crates/gpu/src/cores.rs:235-246) says the m-column walk spells out the rounding that the one-column walk's += compiles to. In each pair of unrolled iterations, the first term's multiply and add sit in different basic blocks, "so they stay two rounded operations". The Q5_K walk reuses that rule (crates/gpu/src/kquant/walk.rs:15-23). LLVM 23 seems to fuse one more multiply and add per walk than that comment expects, so the mirror no longer matches. I have not traced which source expression the extra fma.rn.f32 comes from. The libNVVM cubins fail with exactly the values LLVM 23 gives.
Why the toolchain's llc gets picked
rust-toolchain.toml installs llvm-tools, and cuda-oxide takes CUDA_OXIDE_LLC first, then the Rust toolchain's llc, then llc-23, llc-22 and llc-21 on PATH (docs/BUILD.md:42). On the RTX 5090, cargo oxide doctor reports llc (LLVM)... ✓ LLVM version 23.1.0-rust-1.100.0-nightly. So a contributor who follows the table in docs/BUILD.md, with LLVM 21 on PATH and no CUDA_OXIDE_LLC, builds with LLVM 23. That is how my RTX 5090 builds ran, the fixture-tier run below included.
Questions
- Which
llcdo the box and the release builds use? If it is LLVM 21 through the development machine's environment file, would you want the repository to pin it (for exampleCUDA_OXIDE_LLCin the recipes, or a check that refuses another major), since the default pick is LLVM 23? - The grouped tiles (
kquant/tiles.rs:94-95) and the Qwen3 MoE prefill projections (arch/qwen3moe/proj.rs:276) serve prompts through the m-column walk while decode uses the one-column walk. With LLVM 23 they differ in the last bits. Does anything depend on them being equal, for example MTP verification or a prompt against a decode step of the same token? - Should the one-column walk spell its operations too (
fma_rn_f32/add_rn_f32/mul_rn_f32, as the m-column walk does), so that neitherllcnor libNVVM decides the bits? The plan board'ssm_120cubin (rig-logdocs/bloomery/plan-triage.md) would go through libNVVM if it is built with--materialize-cubin, and both libNVVM builds above fail this gate.
Setup
- RTX 5090 32 GB, driver 595.99.02. AMD Ryzen 9 9950X. Ubuntu 26.04.1, Linux 7.0.0-38-generic. CUDA 13.3 (nvcc and ptxas 13.3.73), nightly-2026-08-28 (its
llcis LLVM 23.1.0-rust), LLVM 21.1.8 from the release tarball, cargo-oxide at the pinned87bc9518. - GB10 (DGX Spark), host driver 595.84. The gates ran in the
nvidia/cuda:13.3.1-devel-ubuntu24.04container (docker run --gpus all -e NVIDIA_DISABLE_REQUIRE=1), withCUDA_OXIDE_LLCset to LLVM 21.1.8'sllc. The container's forward-compat libcuda 610.43.02 is the one loaded (checked withLD_DEBUG=libs). - On the RTX 5090,
68609e1cbuilt against cuda-oxide4da6c13844also fails with the same four lines.
Commands
# fails: the toolchain's llc
cargo oxide build --arch sm_86 -- -p bloomery-gpu-gates --features gpu --release --bin gate_kquant
target/release/gate_kquant
# passes
CUDA_OXIDE_LLC=/path/to/LLVM-21.1.8/bin/llc CARGO_TARGET_DIR=target-llc21 \
cargo oxide build --arch sm_86 -- -p bloomery-gpu-gates --features gpu --release --bin gate_kquant
target-llc21/release/gate_kquant
# fails: libNVVM
CARGO_TARGET_DIR=target-cubin cargo oxide build --arch sm_120 --materialize-cubin -- -p bloomery-gpu-gates --features gpu --release --bin gate_kquant
target-cubin/release/gate_kquant
# the driver JIT's SASS: run with an empty cache, then cut the cubin out of the cache entry
CUDA_CACHE_PATH=$(mktemp -d) CUDA_CACHE_MAXSIZE=4294967296 target/release/gate_kquant
The rest of the fixture tier on the RTX 5090 (toolchain llc)
The 42 fixture-tier gates, run serially with BLOOMERY_BOX_LOCAL=1 BLOOMERY_TIER=fixture on main e8e32a38. Fixtures came from fixture fetch (qwen38, v41 with its r8 sidecar, glm5next), and the reference sets from the Hugging Face dataset at revision 189cdb5b.
| Outcome | Gates | Why |
|---|---|---|
| pass (18) | gate-app, gate-ds41-bind, gate-ds41-place, gate-gpu-cand, gate-gpu-ds41-lib, gate-gpu-ds41-unpack, gate-gpu-glm-mla, gate-gpu-glm-router, gate-gpu-glm-sel, gate-gpu-glm5next-gemm, gate-gpu-hc-gated, gate-gpu-lib, gate-gpu-linear, gate-gpu-ple, gate-gpu-q6k-ids, gate-gpu-swap, gate-gpu-xstream, gate-q38-serve-rules | |
| fail (13) | gate-gpu-kquant | above |
| gate-gpu-glm5next-e2e, gate-gpu-glm5next-mtp, gate-gpu-glm5next-residency, gate-gpu-glm5next-stagger | glm5next/corpus-prose.ids is not in the data directory |
|
| gate-gpu-iq, gate-gpu-iq-sel | ref-synth/iq2_xs.blocks is missing ("run just gate-1-1 first") |
|
| gate-gpu-qwen35moe-attn, gate-gpu-qwen4exp-e2e, gate-gpu-qwen4exp-mtp, gate-gpu-vision | their reference sets (ref_qwen35moe, ref_qwen4exp, ref-mtp/qwen4exp_prose64_n64_k1, ref-vision/deepseek41v) are not in the data directory |
|
| gate-gpu-qwen35moe-e2e | needs the real Qwen3.6 file | |
| gate-gpu-qwen38-serve | --place gate: no visible device is 3090 |
|
| rc 64 (4) | gate-gpu-ds41-callstream, gate-gpu-ds41-twocard, gate-gpu-glm5next-twocard, gate-gpu-qwen38-twocard | two-card runs, refused in local mode |
| rc 66 (7) | gate-ds41-load, gate-gpu-ds41-dspark-loop, gate-gpu-ds41-media, gate-gpu-ds41-prefill, gate-gpu-ds41-residency-a, gate-gpu-ds41-step, gate-gpu-ds41-tier | no DSpark fixture draft |
Apart from gate-gpu-kquant, gate-gpu-lib and gate-gpu-swap, the outcomes match an earlier GB10 run of the same list on an older tree. Those two failed there and pass here. With a different tree I read nothing into that.
이슈 보고일 뿐 따로 PR을 게시할 계획은 현재 없습니다.
- Dominant language
- Rust
- Stars
- 21
- Forks
- 4
- Avg merge
- 2h 33m
- Merged PRs (30d)
- 794
Getting set up
- No Dockerfile or Docker Compose file
- Has a pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from midagedev/bloomery
-
good first issue hardware help wanted no-box serve
Difficulty 2/5 1-3 hours Newbie friendliness 70/100
Maintainers usually reply within 1 day
-
good first issue hardware help wanted no-box
Difficulty 2/5 1-3 hours Newbie friendliness 73/100
Maintainers usually reply within 1 day
-
Difficulty 4/5 1-2 days Newbie friendliness 8/100
midagedev/bloomery#1124 · 1 comment ·
Maintainers usually reply within 1 day
-
gate-gpu-qwen38-twocard red on main: two-card prompt calls differPossibly taken @juliankang4 claimed this today. Open
Difficulty 4/5 3-5 days Newbie friendliness 18/100
midagedev/bloomery#1120 · 3 comments ·
Maintainers usually reply within 1 day
-
enhancement gpu help wanted host model placement tracking
Difficulty 5/5 Over a week Newbie friendliness 25/100
midagedev/bloomery#1022 · 1 comment ·
Maintainers usually reply within 1 day
All issues in midagedev/bloomery
Similar issues
-
example
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
rust-lang-nursery/rust-cookbook#954 ·
Maintainers usually reply within 1 day
-
Difficulty 1/5 1-3 hours Newbie friendliness 70/100
OpenZeppelin/openzeppelin-relayer#967 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 62/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
Maintainers usually reply within 1 day
-
src/runs.rs:1987: a missing --source-run-id now answers 400 "Source run ... does not exist", not 404Opennightly-audit
Difficulty 2/5 1-3 hours Newbie friendliness 62/100
antithesishq/snouty#602 ·
Maintainers usually reply within 1 day