Repository navigation
EXL3 Flash Next: deep prefill (3/8) - #1443
BlivionIaG wants to merge 3 commits into
Conversation
a23cd77 to
0b8304d
Compare
|
The prefill speedup is impressive: the 16k-token prompt goes from 56 s to 10.5 s. The blocker is arithmetic. The deep path is on by default for segments of 512 rows or more ( Keeping cache states on the step grid ( To land:
Once prompt rows match decode, the grid is no longer needed for exactness. The ring, overlap and prefetch work can stay as it is. |
3011ec2 to
8f85bfb
Compare
8f85bfb to
5207d74
Compare
8759332 to
5485ced
Compare
…inned host RAM, sm_120 gate
Ranks split the experts by contiguous runs of leaves (the routed ids by eights, the shared expert in the last leaf), the routers are replicated, and MTP drafts run on every rank. The MoE branch has one canonical reduction order at any tensor-parallel degree: each leaf is an fp32 fma chain over the row's slots in pick order starting at +0, and the 64 leaves are added in a fixed balanced binary tree. One rank evaluates the whole tree. A rank of d computes the maximal complete subtrees of its run (one for a power-of-two d), one fixed-size all-gather of those fp32 [rows, 2560] partials follows, and every rank finishes the same tree, so TP=1 and TP=d write the same bits. Two ranks exchange 10 KB a row a layer. A host test emulates d = 1 to 8 against the whole tree and against the definition without shortcuts, and mutations of the order, a slot, a leaf and the tree shape must change bits.
Expert-major prefill for a Flash Next EXL3 checkpoint whose routed experts live in host memory: deep chunks of up to 4096 rows, the experts streamed through a ring of pool lines while the rows compute, and the dense projections of a chunk on tensor tiles. Every row is the decode kernels' bytes whatever the chunk or segment length: * Dense projections of 64 rows or more run tf_exl3_promptd_*: the rows are rotated by Linear.matmul's own tf_exl3_rot_in, each 64-row block sums the same chains matmul's plan cuts (sk splits of wk chains of k tiles, an fp32 mma chain from zero each, added in warp order and then split order) and finishes with its transform, scale and one rounding. * The 64-member expert tiles (tf_exl3pf_gu, tf_exl3pf_down) take 32 members a pass and sum K as the grouped kernels do (four chains of a split, then the splits, then 0 + each), with the same rotation and epilogue code. Transposed mma operands give the same fp32 bits as the decode kernels' roles. * The branch of a row is slice 2's canonical reduction at any tensor-parallel degree: 64 leaves of eight routed ids (the shared expert in the last), a leaf an fmaf chain in slot order from +0, a fixed balanced tree over the leaves. A rank sums the maximal complete subtrees of its contiguous leaf run (tf_exl3pf_tp_combine), the block partials travel in one explicit fp32 all-gather per layer (fixed size, [rows, 2560] a rank at a power of two) and every rank finishes the tree the same way (tf_exl3pf_tp_finish). One rank sums the whole tree and exchanges nothing; TP=1 and TP=2 write the same bits, and the host tests replay the leaf sums and the gather for 1 to 8 virtual ranks against the single-rank tree bit for bit. Tests: exl3-prefill dense and prompt compare the tiles with Linear.matmul and runSlots byte for byte on the pack's layers; exl3-prefill equal runs one prompt through the batched pass and through deep chunks of several sizes and compares the last row's logits and the greedy tokens; exl3-tpprefill requires the same bytes for deep against batched and for two ranks against one. tf_exl3pf_gu and tf_exl3pf_down are registered as replacements of the grouped decode form and pass gates 1 to 3 of the kernel contract on random layers, with a control; gate 4 stays a named finding. tf-cuda-test exl3-prefill-contract runs those gates alone.
5485ced to
70bbd11
Compare
Part of #1419.
Draft, slice 3 of 7 in the chain of #1419. Stacked on slice 2: this branch contains the earlier slices, review only the last commit. Numbers marked "pending" are being measured and will be filled in before this leaves draft.
What
Deep prefill for the offloaded pack: long prompts run through a tiled prefill path that overlaps the expert copies with compute (ring of staged expert lines, paired head, window/writeback, expert-parallel prefill), instead of the batched pass of slice 2.
Exactness
The deep path accumulates in a different order than the batched pass, so it is not bit-identical to slice 2 on long prompts: the docs output hash changes from
1882ea29ebf6(slice 2) toe6834625432c; the 40-token and Zig prompts are unchanged (90e6c65ca5ba,6dfc89b1abb6). Both paths are self-consistent: drafted equals plain, a resumed prompt-cache request equals a fresh one, and reruns are equal. This is a reordered accumulation inside the prefill kernels; FP64 per-class receipt: pending.Notes on the measurements
Prefill is ~5x faster on long prompts; short prompts and decode are unchanged. The deep path reserves 727 MB more device memory than slice 2.
Tests run
On this tree:
zig build native -Dnvcc=nvcc -Dsm=120,test-flashnext-exl3-host(111 pass),compile-cuda-tests,test-kernel-contracton sm_120;zig fmt --check;python3 tools/zig/lean_check.pyat 428 problems, the same set as upstream main; no file we touch over 600 lines. Linuxzig build testand the HIP build/test were run on slices 1, 2 and 7 with no failure and the same skips as main; the CI-style runs for this exact commit are pending. Not run: other architectures,test-goldenon a Mac for this slice (run on slice 1).Notes
sm_120 only; the loader gate from slice 1 refuses other architectures. No tool or AI trailers; no block comments.
Receipts (2x RTX 5060 Ti 16 GB sm_120, PCIe 4.0 x8, EPYC 7452, 251 GB RAM, driver 615.71, CUDA 13.4, Zig 0.17.0)
Measured on this slice's tree before the rebase onto current main (same code); a smoke run on the rebased heads of slices 1, 2 and 7 reproduces every output hash above and drafted equals plain, with plain timings within 5%. Open item: with drafts on, cold TTFT was higher on that run (slice 7 Zig 19.2 s against 10.9 s plain, docs 21.0 s against 10.7 s); the earlier run above shows 11.5 s and 11.2 s, so it is being re-checked. 192 output tokens, temperature 0; cold is the first request after server start, warm the identical repeat; the bench has no prompt cache. Peak memory and the prompt-cache check are in the receipt files.
bench2.py: three prompts (short = 40 tokens, zig = 16,022 tokens of source files, docs = 12,780 tokens of docs), 192 new tokens, temperature 0,ignore_eos, thinking off, streamed over the OpenAI chat API, two repetitions each. Cold = the first request of that prompt after server start (includes first-touch and graph capture), warm = the identical repeat. The prompt cache is off (--prompt-cache-gib 0or none), so the warm repeat prefills again from scratch. Prefill tok/s = prompt tokens / TTFT; decode tok/s = (gen-1) / (last-first chunk span); mean ITL = that span / (gen-1).Plain decode (no drafts)
Drafted decode (MTP head loaded, drafts enabled)
drafted == plain
bench_openai 256-token greedy token hashes, plain vs drafted: fibonacci-raw: plain ['97f8951316ab'] drafted ['97f8951316ab'] -> equal; gpu-chat-no-think: plain ['de2cc2940523'] drafted ['de2cc2940523'] -> equal
Repo-client receipts
bench_openai decode, 256 tokens x3 greedy (plain)
bench_cache cold prefill, exact token-id prompts,
--require-cold(plain)bench_openai decode, 256 tokens x3 greedy (drafted)
bench_cache cold prefill, exact token-id prompts,
--require-cold(drafted)bench_concurrent, 1 and 2 streams,
--alone --serial(PARALLEL=2 server)