Repository navigation
cuda: EXL3 projections behind qlinear (#593) - #740
Conversation
kernels/cuda/exl3.cu decodes EXL3 trellis tiles as checkpoints store them ([K/16, N/16, 16 bits] int16), with core/exl3_format.zig's codebooks (3inst, mcg, mul1) and whole widths 1-8. Rows are rotated by suh and the 128-point Hadamard transform, scaled by 2^-4 and rounded once to fp16; each tile decodes once straight into the B fragments of two mma.m16n8k16 (the tile's value order is theirs), fp32 sums, then the output transform, svh and 2^-3 (docs/recipes/exl3.md, Prompts). Decode rows go 1-16 a launch, read as A fragments from rows written in fragment order; matrices with fewer than 96 column blocks split their input blocks into a shape-fixed number of parts that the last part sums in order. Prompt rows go 64 a block from shared memory. Every row is independent, so a row's bits are the same at every row count on its path. qlinear takes it as Weight.exl3, refused with FormatNotLoaded when the module is absent. tf-cuda-test exl3 [DIR]: every codebook state and random tiles at every width bit-equal to exl3_format; FP64 references from exl3_format.dequantize with a bound for the kernels' rounding order, worst column and aggregate error; decode windows of 1-16 rows and prompts of 2-300 rows byte-equal to each row alone; DIR adds real matrices of a checkpoint and times them.
…3 experts Checked ash hart PRs open+closed (read-only). Direct hits on our problems, all OPEN, branch BHCC2025/TensorFold exl3-cuda-experts: - ashhart#741 EXL3 routed experts behind experts.Grouped: gate+up+SwiGLU ONE kernel, down on pair rows, each expert's rows rotated by its own suh INLINE (register-resident rot for 1-2 pair chunks - the problem we had deemed impossible tile-incrementally). Rides on grouped.zig's Plan (in our tree). 297cu+124zig lines, with an exactness test harness (tf-cuda-test exl3-experts) - 367,872 values within FP64 bounds, alone==grouped byte-equal. - ashhart#740 EXL3 projections behind qlinear (the stacked base). - ashhart#718 branch-free trellis window extraction (perf). - ashhart#729 share decoded weights across prefill rows (perf). - ashhart#728 MiMo-V2.6 CUDA EXL3 two-GPU (another full CUDA EXL3 family). - ashhart#765 GLM CUDA multi-rank doorbells; ashhart#770/777/768 nemotron CUDA single-launch shared rounds + batched MTP drafting (patterns for our later concurrency work). PLAN: vendored exl3_experts.cu/.zig + exl3.cuh into our tree (this commit, unwired). Next: build wiring (fatbin + mod), port moeBlock to Grouped.decode with the router->plan plumbing from grouped.zig, run their exl3-experts test on our checkpoint, parity, bench. Our own grouped kernels (stage-1 parity) stay as fallback.
|
A receipt from a second GB10, on a different checkpoint: Setup. GB10 sm_121, driver 580.178.04, kernel 7.0.0-1019-nvidia, Zig 0.17.0, CUDA 13.0. Tested #741 at
With DIR: the first two real matrices pass with
The test then stops with Both real EXL3 matrices reached before the failure passed; the remaining selected matrices were not tested. Would it make sense to report known non-EXL3 projections as skips, while still failing for missing expected tensors and requiring at least one real EXL3 matrix to be checked? |
…ght, and fails when none is EXL3
|
Thanks for the second GB10 and the |
|
Confirmed on the same
|
|
Thank you, @BHCC2025, for the EXL3 qlinear projections and checkpoint tests. We merged #740 for 1.1.0 in cd4f084, retaining your commits. This merge leaves the tree unchanged because the shared projection work and plain The common implementation comes through #837, with #806's tiles available explicitly and EXL3 supported by qlinear's FP32 decode and prompt outputs. Posted by Ash's AI Assistant Synthena |
The tree is the one before this merge. ashhart#740's projections are its shared commit with ashhart#741, an earlier copy of ashhart#806's, which landed above, and its test change, skipping a matrix a checkpoint stores as plain .weight, is in ashhart#806's tests as kept.
…lamaV3's notice One line for the CUDA module (ashhart#837, ashhart#806, ashhart#741, ashhart#740) and one for Metal's shared prefill tiles (ashhart#729). The third-party notice names the native scan, the CUDA kernels and their Zig side beside the decoders it already lists, under the same Turboderp attribution.
EXL3 projections on the native CUDA engine, behind
qlinear: the EXL3 item of #593's step 4, claimed in #593. An operator-only change in the shape of #681: no model uses it yet. Routed experts follow in a stacked PR.What it adds
kernels/cuda/exl3.cu,exl3.cuh: EXL3 trellis tiles as checkpoints store them ([K/16, N/16, 16 * bits]int16), every codebookcore/exl3_format.zigdefines (3inst, mcg, mul1), whole widths 1 to 8. Hand-written, on the structure of the EXLR8 CUDA kernels already in the tree.cuda/exl3_linear.zig: the weight view (trellis,suh,svh, width, codebook) and its kernels;cuda/qlinear.zigtakes it asWeight.exl3, refused withFormatNotLoadedwhen the module is not loaded, like the other formats.tf-cuda-test exl3 [DIR].Arithmetic
One arithmetic for decode and prompt rows, the one
docs/recipes/exl3.md(Prompts) describes for the Python engine's EXL3 path:x * suh, then the 128-point Hadamard transform in a fixed order of fp32 adds, times 2^-4 (exact), rounded once to fp16. fp16 keeps three more mantissa bits than the bf16 activations it comes from; the 2^-4 keeps the transform's growth well inside fp16's range.mma.m16n8k16(a tile's value order is theirs), with fp32 sums. mul1 values come from one fused fp16 multiply-add of an exact sum, the single rounding the format specifies.svhand 2^-3, one rounding to bf16.Decode rows go 1 to 16 a launch, their A fragments read from rows the rotation writes in fragment order. A matrix with fewer than 96 column blocks splits its input blocks into a number of parts fixed by its shape, and the last part to finish adds the parts in part order. Prompt rows go 64 a block through shared memory. Rows never mix, so each row's bits are the same at every row count on its path.
Tests (GB10)
tf-cuda-test exl3 /path/to/GLM-5.3-Flash-exl3-4.05bpw: 12 pass, 0 fail, 0 skip.exl3_format.codebookValuebit for bit.exl3_format.unpack'sW_qbit for bit.exl3_format.dequantize, each value within a bound derived from the kernels' rounding order (fp16 inputs, fp32 sums, bf16 out), worst column and aggregate error reported: 11 synthetic matrices (widths 1-8, all three codebooks, split and unsplit), 98,304 values, worst 0.14 of the bound, worst column 2.8e-3 of the largest output.qlinear.DIR, seven real matrices of turboderp's GLM-5.3-Flash EXL3 4.05bpw (mul1; K=4, 5 and 6): 21,504 FP64 values, worst 0.031 of the bound.Also
zig build test test-cuda-hostpasses,zig fmt --checkis clean, andtools/zig/lean_check.pyreports 539 problems, the same 539 as0efbd447.Timings (GB10, the same matrices)
One row and 16 rows are decode calls; 512 rows is a prompt. ExLlamaV3's own
LinearEXL3on the same tensors and GPU in the right-hand columns, for scale (its decode rounds outputs to fp16).The small matrices sit in L2 when timed back to back, so their single-row times are mostly the two launches (rotation, projection); a captured decode graph removes most of that.
Environment
TensorFold
0efbd447(1.0.8) plus this commit; Zig 0.17.0; NVIDIA GB10 (sm_121), driver 580.178.04, CUDA 13.0.88; Ubuntu 24.04.5, kernel 6.17. Checkpoint: turboderp/GLM-5.3-Flash-exl3, 4.05bpw, revision2a30229e.zig build fatbins -Dsm=86,120,121builds the new kernels for every release target.Not run
bench_concurrent,prefill_coldor drafted-versus-plain receipt; those come with a family (GLM-5.3-Flash and full GLM-5.3 on the native CUDA engine (tracker) #593 step 3).Matrixtakes whole widths); no checkpoint we have uses them.