gpu: gate_kquant fails when the PTX comes from the Rust toolchain's llc (LLVM 23), which cuda-oxide picks ahead of LLVM 21

Closed
#54 2 comments 0 reactions 0 assignees View on GitHub

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

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

gpu

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_tiles and the gate's kq_gemv_mcol_q5k. q4k_gemv_sel is 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.f32 and one fewer mul.f32 and add.f32 in q5k_gemv_sel, q4k_gemv_sel and kq_gemv_mcol_q5k, and two of each in kq_gate_up_act_q4k and kq_gate_up_act_q4k_tiles, which walk the gate rows and the up rows. In q4k_gemv_sel, for example, the counts of fma.rn.f32, add.f32 and mul.f32 are 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 offline ptxas 13.3.73 -arch=sm_120 for all five kernels. On the GB10 the driver JIT (610.43.02) matches ptxas 13.3 -arch=sm_121a the same way.
  • It is not the host CPU flags. Dropping the x86_64 table from .cargo/cuda-oxide.toml gives 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

  1. Which llc do 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 example CUDA_OXIDE_LLC in the recipes, or a check that refuses another major), since the default pick is LLVM 23?
  2. 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?
  3. 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 neither llc nor libNVVM decides the bits? The plan board's sm_120 cubin (rig-log docs/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 llc is LLVM 23.1.0-rust), LLVM 21.1.8 from the release tarball, cargo-oxide at the pinned 87bc9518.
  • GB10 (DGX Spark), host driver 595.84. The gates ran in the nvidia/cuda:13.3.1-devel-ubuntu24.04 container (docker run --gpus all -e NVIDIA_DISABLE_REQUIRE=1), with CUDA_OXIDE_LLC set to LLVM 21.1.8's llc. The container's forward-compat libcuda 610.43.02 is the one loaded (checked with LD_DEBUG=libs).
  • On the RTX 5090, 68609e1c built against cuda-oxide 4da6c13844 also 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

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from midagedev/bloomery

All issues in midagedev/bloomery

Similar issues

More Rust issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.