Repository navigation
exl3: zero the split-K counters once as they grow, not before every call - #1475
diwakergupta wants to merge 1 commit into
Conversation
|
Thanks @diwakergupta. This covers everything we asked on #1407: the tiles are opt-in, the choice depends only on the layer and the dtypes, the counters are zeroed on the calls' stream with a test that fails on the legacy stream, and the 16/17-row receipts on the bf16 cache are the ones we wanted. Merged onto current main, fmt, lean,
Once it's rebased, we'll run the dense pack on a GB10 with tails of 16 and 17 rows past a kept state, concurrent == solo, drafted == plain, and cold prefill against #1465 and #1467, plus a MoE EXL3 checkpoint against main. |
5ba6ae6 to
64bf587
Compare
|
Thanks @diwakergupta for the quick rebase. The last commit now sits on #1465 and is line for line the commit we reviewed, and the description now says the counters change reaches the MoE EXL3 checkpoints too. Merged onto current main, fmt, lean (no new findings), Two small things:
If you get the chance, the same three cold ~8,145-token prompts on #1467 on your 3090 Ti would still help us choose between the two prompt paths (its kernels build for sm_86). It isn't a blocker; our GB10 timing will decide. A heads-up: this PR and #1507 both add tests at the same place in Next we'll run the dense pack on a GB10: resumed against fresh with tails of 16 and 17 rows past a kept state (greedy and seeded), concurrent == solo, drafted == plain, cold prefill against #1465 and #1467 on the same prompts, and a MoE EXL3 checkpoint against main by token hash. |
|
Thanks @ashhart. Pushed
I timed the three cold ~8,145-token prompts on the 3090 Ti, alternating #1465, #1467 (
So on |
|
Thanks @diwakergupta, both are done, and thank you for timing #1467 on the 3090 Ti. With the tiles about 5% ahead Next, after #1465, we'll run the dense pack with DFlash2 on a GB10:
We've started the CI runs that were waiting on our approval. |
|
Thanks @diwakergupta. We ran this on a GB10 (sm_121) against main, with Qwen3.8-27B EXL3 3.50bpw, fresh servers per arm and alternating base/new rounds. The prefill gain is real and large: 2,048-token prompts go from 507 to 820 tok/s (+62%), 8,192 from 492 to 800 (+63%), and 32,768 from 473 to 754 (+59%). Two things stop us landing it as it stands:
We're parking this for now, not closing it. Once the tokens match main, we'll rerun the same cells and land it if they're clean. If you'd like, we can share the exact prompts behind the differing rows. |
…very call Dense.blocks zeroed its split counters on the calls' stream before every 128-row block: one memset per projection per decode step. The linear's last arriving program of each block already resets its counter, so the counters are zeroed only when they grow. reserve() takes the stream they are zeroed on.
ee02a88 to
17d96c4
Compare
|
Thanks @ashhart, and for the GB10 numbers. The tiles sum in a different order from the linear, so their tokens can't match
The description has the full table. |
What this changes
exl3.Densezeroed its split-K counters before every 128-row block of every call: onecuMemsetD8Asyncper projection per decode step, and per 128 prompt rows. Each block's last arriving program inexl3_linear.cualready resets its own counter once the partials are summed (a.counters[pass * NB + nb] = 0), so the counters only need zeroing when the buffer grows.Dense.grownCounterszeroes the counters on the calls' stream when they grow, and returns the same buffer otherwise.blocks()no longer memsets.reserve()takes that stream, so the zeroing happens at load and a graph capture allocates and memsets nothing.Densepath, MoE included. The arithmetic is unchanged.This is what's left of the prompt-tiles PR. The 64-row tensor-core tiles are dropped: on the GB10 they changed tokens against
main(7 of 12 prompt rows), because the tiles sum in a different order from the linear. #1467's mid-M kernels keep the linear's bits.Receipt
Environment. RTX 3090 Ti (24 GB,
sm_86).turboderp/Qwen3.8-27B-exl3branch3.50bpw(rev8351c54e),z-lab/Qwen3.8-27B-DFlash2(rev50307d4c) at 4 bits, bf16 KV cache,--context 32768. Both binaries came fromzig build native -Dsm=86, one frommain(a0d24896) and one from this branch. Each ran twice, alternating (main, this,main, this), on a restarted server.Exactness.
tools/bench_concurrent.py --tokens 384 --reps 3 --alone --serial, the default code and chat cells (greedy and sampled) at--levels 1,4, and--levels 1with--promptsof two long prompts (5,080 and 2,278 tokens):"draft": falsein every cell on both binaries: 0 unequal, 0 failed.token_sha256of this branch equalsmain's in 528/528 replies.Decode speed. Per-stream tok/s, runs 1 / 2 (each a median of 3). Tokens per round are equal except in the sampled 4-stream cells, where they vary from run to run on both binaries:
mainPrompt speed. Three distinct cold ~8,145-token prompts per run (
cached_tokens0,max_tokens1),prefill_secondsfrom the reply:main14.61 s mean, this PR 14.57 s (+0.3%).Tests. The full checklist suite (
zig build test test-kernel-contract test-golden test-server-security test-pull-security fuzz-cortheon test-cuda-host) plustest-exl3-cuda-hostandtest-exl3-prompt-host: 485/488 steps succeeded. The one failure iskeepalive_test"a silent engine's stream gets keepalives before its first token", which crashes the same way onmain(a0d24896) on this machine.lean_new.pyagainstmain: 0 new problems. New host test:the counters are zeroed on the calls' stream only as they grow(exl3.zig, withexl3_mock's memset counts). It pins one memset per growth on the named stream; onmainthere is no such function, andblocks()memsets every call.Not run. GB10 /
sm_121. Metal (not affected). MoE EXL3 checkpoints, which take the sameblocks().Checklist
"draft": false, concurrent equals alone, and this branch's tokens equalmain's.exl3_linear.cu).sm_86only.