Repository navigation
perf(cuda): fused decode/verify/MTP kernels and graph launch reductions on 0.1.39 - #783
stuchapin909 wants to merge 5 commits into
Conversation
d37990f to
e1f292a
Compare
2x RTX 3090, Swift 1.5 IQ3_XXS, partially resident, layer split: +6.5-7.8% decode, but greedy output not identical to 0.1.39Another data point, for the configuration nobody has posted yet: the IQ3_XXS pack on a 2-GPU layer split with only part of the experts in VRAM. Setup. 2x RTX 3090 24 GB (sm_86, PCIe x8, 13.4 GB/s measured, no NVLink), Ryzen 9 9950X3D, 156 GB RAM, Ubuntu 24.04 (container), driver 595.71.05, CUDA 12.0. Swift 1.5 IQ3_XXS (GSQ-RCO). Both arms are source builds with identical CMake flags (Release, Engine args (setup's own): Speed8 real prompts (code, explanations, prose), 1,024 output tokens each, one request at a time, thinking on. Engine-side reciprocal mean TPOT. Arms alternated stock, PR, stock, PR with a fresh engine start each.
Both PR runs beat both stock runs in each mode, with the same acceptance, so the windows really are cheaper. Prompt reading was not measured. Exactness: not identical on this configurationGate: 5 greedy prompts (chat, code, math, prose, and a 21,348-token prompt), 256 tokens each, through the HTTP server, thinking text included in the comparison. Exactness settings on both arms:
For example (chat): stock
The texts stay coherent, and the first difference is never at token 0, so this looks like a changed accumulation order in a path that only runs on a layer split or with partial residency (your parity runs were IQ2_XS, fully resident). If you add kill switches for the four commits, or point me at the most likely kernels, I can bisect on this box; the gate takes about 4 minutes per arm. |
…lizations - fused_gr.cu: add pre-unpacked BF16 8-wide dot helpers (Bf16x8, unpack8, dot8u, dot8u_ptr) and template gr_up_multi_kernel / gr_down_staged_kernel on <MAX_T, EXACT_T> to eliminate dynamic loop bounds for T=1..8 (STRATA_GR_DOWN_MAX4=0 opts out of the MAX_T=4 cap for ct <= 4; STRATA_NO_MULTI_GR=1 falls back to 0.1.39 dispatch). Preserves f16_from_f32 / STRATA_DP4A / STRATA_LDG portability from f166564. - gr.cu / gr.hpp: add gr_read_multi and gr_write_multi for multi-token hyper-connection read/write in a single launch (STRATA_NO_MULTI_GR=1 selects per-token gr_read / gr_write). - native_gr_norm.cu / .hpp: add native_gr_rms_norm_weighted_multi. - native_gr_postops.cu / .hpp: add native_gr_pre_gated_multi and native_gr_post_multi. - native_bf16.cu: template bf16_f32_mmvf_multi_kernel on <BLOCK_SIZE, NT, EXACT_T> for exact token counts in bf16_gemv_fp32_mmvf_multi. - quantize_act.cu: use warp shuffle reduction in quantize_q8_0_scaled_kernel.
…sions - iq_kernels.cu: add 16-lane sub-warp row_dot_80_sub16<TY, NC, EXACT_N, STAGE_GRID>, template mmvq_multi_kernel on <TY, NC, STAGE_GRID, EXACT_N> with launch_mmvq_multi_nc dispatch, and add 16-lane / 8-lane sub-warp grouped expert gate+up and down kernels in native_gu_multi_kernel / native_down_multi_kernel (STRATA_NO_SUB16_GU=1 falls back to 32-lane row_dot_multi). - native_mmvq.cu: add SmallTraits<Q40Block, 4>, SmallTraits<Q50Block, 4>, SmallTraits<Q80Block, 8>, and SmallTraits<IQ4NLBlock, 4> 2-row-per-warp (ROWS = 2) multi-column MMVQ dispatch in launch_multi_n when !multi_exact. - native_moe.cu: add float4-vectorized combine_k10_vec4 for k=10 expert combination in native_moe_combine and native_moe_combine_multi. - native_router.cu: add route_multi kernel for multi-token top-10 routing in native_router_top10_multi. - s2_expert_grouped.cu: add swiglu_quantize_q8_0_scaled_kernel to fuse SwiGLU and scaled Q8_0 quantization in moe_grouped_s2 while writing back gate_up[idx] so s2_expert_grouped_parity remains bitwise identical (STRATA_OLD_GROUPED=1 selects the previous path). - shared_expert.cu: add float4-vectorized sigmoid_scale_rows_vec4_kernel and launch_sigmoid_scale_rows. - elementwise.cu / .hpp: template copy_rows_from_mapped_kernel<ZERO_HITS>.
…resident_plan
- kv_q4.cu / .hpp & kv_q8.cu / .hpp: add kv_append_q4_steps and
kv_append_q8_steps to append K and V across multiple tokens in a single
3D grid launch.
- native_qsa_indexer.cu / .hpp: add native_qsa_indexer_append_steps for
multi-token indexer append.
- native_rope.cu / .hpp & rope_parity.cpp: add norm_rope_kernel<TAB> and
native_qsa_rms_norm_rope to fuse per-head RMSNorm and RoPE application
with exact FMA evaluation order matching norm<256> + apply (0 ULP verified
by Check 6 in rope_parity).
- verify_kernels.cu:
- Template resident_plan_kernel<ALL_RESIDENT> (kResidentPlanMax = 128,
preserving f945515) and replace thread-0's serial O(n^2) bubble sort
with parallel shared-memory grouping and a single packed
(my_cnt << 16) | (is_first ? 1 : 0) prefix scan over __shfl_up_sync
(width 32, no __ballot_sync, wave32 and gfx906 wave64 safe).
- Template gdn_ab_multi_kernel<MAX_T, EXACT_T> and
gdn_step_norm_multi_kernel<ALL_OUT>.
- fused_gdn.cu, qsa_select.cu, kv_stream.cu: register/loop staging refinements.
…A graphs
- verify.cpp / verify.hpp:
- Dispatch gr_read_multi, gr_write_multi, native_qsa_rms_norm_rope,
kv_append_q8_steps, kv_append_q4_steps, and
native_qsa_indexer_append_steps across verify/batch tokens in
Verifier::record_window and Verifier::capture_commit.
- Add runtime env kill switches STRATA_NO_MULTI_GR=1,
STRATA_NO_NORM_ROPE=1, and STRATA_NO_BATCH_KV_STEP=1 for isolated A/B
bisection without recompiling.
- Preserve 0.1.39's hit_out + moe_hit_add on the !all_resident_ path and
synchronous --layer-split commit/stage ordering by default (deee447).
- mtp.cpp:
- Dispatch gr_read_multi, gr_write_multi, native_qsa_rms_norm_rope,
kv_append_q8_steps, and kv_append_q4_steps inside
MtpDrafter::record_forward (honoring STRATA_NO_MULTI_GR,
STRATA_NO_NORM_ROPE, and STRATA_NO_BATCH_KV_STEP).
e1f292a to
4327807
Compare
|
Thanks for running the 4-minute gate and sharing the detailed output, @GonzMartinez — that caught a subtle Root Cause of the Greedy Exactness Divergence (
|
…P kernels and graph launch reductions
…#783, Niko1221#742, Niko1221#743, Niko1221#731, Niko1221#764, k1, k2b, the sm_75 BF16 GEMM path), with the measured numbers
Reply to PR #783 (stuchapin909) — posted via gh as q8atnightAnother 2-card row, for the config class GonzMartinez flagged (layer split + partial
Conclusion from this rig: neutral-to-mildly-positive speed (decode +3–4 % at best, prefill 0), |
|
Single RTX 4090 (sm_89, Linux, 32 GB RAM), Flash-Next GSQ-RCO IQ2_XS,
PR faster in both rounds on all three shapes; draft acceptance and expert-tier hits look unchanged. Parity: all |
|
Follow-up from the single-4090 report above: decode at depth, same rig and config (262K, q4_0, resident experts). Haystack prompt with a recall question, then the same prompt plus a 300-token summary for decode. Fresh process per build, two rounds:
That's +14% decode at 119K and +9.5% at 238K, bigger than the +2 to +5% I saw on short prompts. Prompt reading is unchanged, and recall was correct in every run. |
Re-run on
|
| arm | chat | code | math | prose | long (21K) |
|---|---|---|---|---|---|
4327807 default |
char 220 | char 272 | identical | char 468 | char 160 |
all four off (STRATA_NO_MULTI_GR=1 STRATA_NO_SUB16_GU=1 STRATA_NO_NORM_ROPE=1 STRATA_NO_BATCH_KV_STEP=1) |
char 220 | char 272 | identical | char 468 | char 160 |
STRATA_NO_MULTI_GR=1 only |
same | same | identical | same | same |
STRATA_NO_SUB16_GU=1 only |
same | same | identical | same | same |
STRATA_NO_NORM_ROPE=1 only |
same | same | identical | same | same |
STRATA_NO_BATCH_KV_STEP=1 only |
same | same | identical | same | same |
Stock reproduced itself in my earlier runs (5/5 identical across fresh starts), and all six PR runs here gave the same hashes. So on this configuration the divergence is in a part of the stack that none of the four switches turns off, and the norm/RoPE fix doesn't reach it. Candidates by elimination, from the commit messages: the 2-row MMVQ kernels and MoE/shared-expert fusions in commit 2 beyond the gate/up/down sub-warp path, the parallel resident_plan in commit 3, or the graph wiring in commit 4. A switch for any of those would let me narrow it down; each gate run takes about a minute here.
One housekeeping note: the force-push converted 23 of the 26 touched files to CRLF line endings, so the PR diff against e1f292a shows ~14,000 changed lines; with --ignore-cr-at-eol it's 180 lines in 7 files.
|
Our IQ3_S gate row for the config nobody has covered — IQ3_S + partial residency on a 2× 3090 rig Same-box arms: stock
Conclusion on our side: not adopted. If the fused kernels get an exact path for partially-resident IQ3_S (or a |
…tributes The force-push that produced 4327807 rewrote the line endings of 23 source files (.cu/.cpp/.hpp) from LF to CRLF. Nothing in their content changed, but the PR diff against v0.1.39 read as ~12,900 phantom changed lines -- a reviewer had to run `--ignore-cr-at-eol` to find the real 180 lines in 7 files. This commit converts those files back to LF with no content change (`git diff --ignore-cr-at-eol` against the parent is empty except for .gitattributes), and adds `* text=auto eol=lf` so a Windows checkout cannot reintroduce it. The existing exceptions still win: *.bat stays CRLF, bench/prompts/** and tests/data/native_experts/** stay untouched. Reported by @GonzMartinez on Niko1221#783.
|
Closing this. The four-commit stack is too much to review or bisect: @GonzMartinez and @q8atnight both still see greedy mismatch vs 0.1.39 on partial-resident / layer-split IQ3, and the kill switches do not cover it. Speed on fully resident boxes is real; that is not enough to keep an open PR that changes answers. Thanks @GonzMartinez, @q8atnight, and @T-Crypt for the gates. We will not ask for another re-run on this branch. If we come back it will be one testable change at a time, default-exact against 0.1.39. Branch stays on the fork for the record. |
…tomic in kv_stream resolve (#783 PR-a) A CTA of the fixed 256-CTA grid whose first block is past the batch's largest n_bid returns before staging the queries. Same scores, same bits. STRATA_QSA_EARLY_EXIT=0 restores the old staging. kv_stream resolve_kernel: only threads that looked something up add to the shared counter. Ported from #783 (stuchapin909, head 4327807), split per piece. Co-authored-by: stuchapin909 <178119976+stuchapin909@users.noreply.github.com> Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…with an all-outputs path, and the gdn_parity T=1..8 memcmp check (#783 PR-b) gdn_ab_multi is instantiated per T=1..6 (generic above) with the weight unpack hoisted out of the token loop; gdn_step_norm_multi prefetches the next token's q/k while the current one runs, and when no state is written and every token is an output (the verify half) it skips the per-token bookkeeping. gdn_step_norm_kernel splits its reduction buffer so one barrier goes. gdn_parity gains section 5: for every T = 1..8, gdn_ab_multi against fused_gdn_ab, and gdn_step_norm_multi (verify half, commit half, t_out_begin, state-only commit) against the single-token kernel chained, compared with memcmp. Ported from #783 (stuchapin909, head 4327807), split per piece. Co-authored-by: stuchapin909 <178119976+stuchapin909@users.noreply.github.com> Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…sigmoid scale (#783 PR-c) native_router_top10_multi runs one warp per token (8 per CTA) instead of one CTA per token; native_moe_combine(_multi) takes a float4 kernel for k=10 with a shared row on aligned buffers; the shared expert's sigmoid-scale is vectorised. Same arithmetic order, so same bits. New native_multi_parity (memcmp): the multi router against the single call for n=1..19 on random, tied and NaN logits, and the combine against a host replay of its contract for the float4 and scalar paths. Ported from #783 (stuchapin909, head 4327807), split per piece. Co-authored-by: stuchapin909 <178119976+stuchapin909@users.noreply.github.com> Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ommit graph and the MTP catch-up (#783 PR-d) kv_append_q8_steps / kv_append_q4_steps append n cells in one 3D-grid launch per pool, and native_qsa_indexer_append_steps walks the n indexer cells in order inside one CTA (one barrier between cells). Verify record_window, the commit graph and MtpDrafter::record_forward use them; a multi-slot batch keeps the per-token loop (each row has its own K/V). STRATA_NO_BATCH_KV_STEP=1 restores the per-token calls. The kv single-cell calls are now the n=1 case of the same kernel. kv_steps_parity (new, memcmp) covers INT8, Q4_0 and the hybrid's folded single-plane calls for n=1..8; qsa_parity gains append_steps against the sequential indexer append at strides 1 and 5 under none/linear/YaRN. Ported from #783 (stuchapin909, head 4327807), split per piece. Co-authored-by: stuchapin909 <178119976+stuchapin909@users.noreply.github.com> Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
PR-e) The plan kernel grouped duplicate experts and numbered the groups with thread 0 walking all entries; every entry now finds its group in parallel and a packed (entries << 16 | groups) warp prefix scan numbers them. The residency check, the empty plan and *plan_err of #871 are kept as they are. static_asserts pin the 4-warp / packing limits. resident_plan_parity (new, memcmp): n=1..80 entries over pools of 3..512 experts with and without a slot table, with and without a skip word, plus the non-resident case, against a host replay of the plan's contract. Ported from #783 (stuchapin909, head 4327807), split per piece. Co-authored-by: stuchapin909 <178119976+stuchapin909@users.noreply.github.com> Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…he MTP drafter, off on HIP (#783 PR-f) native_qsa_rms_norm_rope does norm<256> and the rope in one launch with the same FMA order as the pair (register barriers keep nvcc from contracting across the norm/rope boundary); rope_parity check 6 compares it with native_qsa_rms_norm_weighted + native_rope_apply by memcmp for head_dim 128/256, strided q|gate and contiguous inputs, unscaled and YaRN. The in-place rope also launches one 32-thread block per row. Verify's norm_rope and q split, and the MTP drafter's q path (all T tokens at once; K for a one-token window) use it. native_norm_rope_usable() gates it: on the CUDA build it is on unless STRATA_NO_NORM_ROPE=1; on the HIP build it is off until rope_parity check 6 passes on AMD hardware, and STRATA_NORM_ROPE=1 turns it on there. The MTP q path's split, FWHT and gate-apply now run over all T tokens in one call each (row-independent). Ported from #783 (stuchapin909, head 4327807), split per piece. Co-authored-by: stuchapin909 <178119976+stuchapin909@users.noreply.github.com> Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…iko1221#783 PR-e) The plan kernel grouped duplicate experts and numbered the groups with thread 0 walking all entries; every entry now finds its group in parallel and a packed (entries << 16 | groups) warp prefix scan numbers them. The residency check, the empty plan and *plan_err of Niko1221#871 are kept as they are. static_asserts pin the 4-warp / packing limits. resident_plan_parity (new, memcmp): n=1..80 entries over pools of 3..512 experts with and without a slot table, with and without a skip word, plus the non-resident case, against a host replay of the plan's contract. Ported from Niko1221#783 (stuchapin909, head 4327807), split per piece. Co-authored-by: stuchapin909 <178119976+stuchapin909@users.noreply.github.com>
…he MTP drafter, off on HIP (Niko1221#783 PR-f) native_qsa_rms_norm_rope does norm<256> and the rope in one launch with the same FMA order as the pair (register barriers keep nvcc from contracting across the norm/rope boundary); rope_parity check 6 compares it with native_qsa_rms_norm_weighted + native_rope_apply by memcmp for head_dim 128/256, strided q|gate and contiguous inputs, unscaled and YaRN. The in-place rope also launches one 32-thread block per row. Verify's norm_rope and q split, and the MTP drafter's q path (all T tokens at once; K for a one-token window) use it. native_norm_rope_usable() gates it: on the CUDA build it is on unless STRATA_NO_NORM_ROPE=1; on the HIP build it is off until rope_parity check 6 passes on AMD hardware, and STRATA_NORM_ROPE=1 turns it on there. The MTP q path's split, FWHT and gate-apply now run over all T tokens in one call each (row-independent). Ported from Niko1221#783 (stuchapin909, head 4327807), split per piece. Co-authored-by: stuchapin909 <178119976+stuchapin909@users.noreply.github.com>
…6 projections (Niko1221#783 PR-g) gr_read_multi / gr_write_multi (and native_gr_rms_norm_weighted_multi, native_gr_pre_gated_multi, native_gr_post_multi under them) run T tokens in one launch per step; the verify window's PLE writes, last-layer write and final mixer, and the MTP drafter's write and final mixer use them. The BF16 MMVF multi kernel and the GR up / staged down kernels are instantiated per exact T (1..6, generic above), the BF16 weight tile is unpacked once for all tokens, with the same FMA order as before. STRATA_NO_MULTI_GR=1 restores the per-token calls and the generic kernels. STRATA_GR_DOWN_MAX4 keeps its meaning from the previous commit (sm_120 default on, explicit setting decides); this commit does not change that default. gr_multi_parity (new, memcmp): gr_read_multi (with and without injection) and gr_write_multi (in place and out of place) against the single-token calls at the real geometry, T=1..8. Ported from Niko1221#783 (stuchapin909, head 4327807), split per piece. Co-authored-by: stuchapin909 <178119976+stuchapin909@users.noreply.github.com>
…MVQ columns, decoded-once small-format tables, two rows per block (Niko1221#783 PR-h) iq_kernels.cu: row_dot_80_sub16 (two rows per warp, all lanes active) for the n_embd == 2560 gate/up of every split format, exact-N instantiations of the multi-column MMVQ (launch_mmvq_multi_nc), the 3-entry tail in the grouped gate/up and down kernels. native_mmvq.cu: SmallTraits for Q4_0, Q5_0, Q8_0 and IQ4_NL decode the weight chunks and block scale once per (row, block) and apply them to every activation column with the arithmetic of small_q8_dot, and the multi-column launch runs two rows per block for any K. Kill switches: STRATA_NO_SUB16_GU=1 (sub-warp gate/up/down), STRATA_OLD_IQ_MMVQ=1 (per-column kernels, existing), STRATA_NO_MMVQ_ROWS2=1 (one row per block at large K). mmvq_multi_parity, iq_multi_parity, native_grouped_parity and native_expert_parity pin the columns to the single-column results. Ported from Niko1221#783 (stuchapin909, head 4327807), split per piece. Co-authored-by: stuchapin909 <178119976+stuchapin909@users.noreply.github.com>
…o1221#783 PR-i) MtpDrafter::draft now runs the round for T = a + 1: the catch-up writes the K/V of the accepted cells [0, a) and the full layer covers row a, instead of writing all T cells and rewriting cell a. No later attention reaches a cell past the one being drafted and the next round writes it first, so the draft tokens and probabilities are unchanged. draft_first (a = 0) no longer writes its repeated rows at all. STRATA_MTP_CATCHUP_ALL=1 restores the whole-window catch-up (and the old staging sizes). Ported from Niko1221#783 (stuchapin909, head 4327807), split per piece. Co-authored-by: stuchapin909 <178119976+stuchapin909@users.noreply.github.com>
…erts are in the window (Niko1221#783 PR-j) STRATA_SH_FORK_LATE=0 forks at the top as before. The other two commit-4 changes of the PR are not taken: the shared-expert Q8_1 reuse is already in f-140 as the opt-in STRATA_VERIFY_QDEDUP (and the fused q8_1 store), and the PLE native-key batch is already in f-140 as the opt-in STRATA_PLE_BATCH=1; both would have changed output on the native packs by default. Dropped on purpose: STRATA_VERIFY_PRELAUNCH and the dead copy_rows_from_mapped<ZERO_HITS>. Ported from Niko1221#783 (stuchapin909, head 4327807), split per piece. Co-authored-by: stuchapin909 <178119976+stuchapin909@users.noreply.github.com>
…GLU + Q8_0 in the grouped S2 experts (Niko1221#783 PR-k) quantize_q8_0_scaled_kernel reduces the block's max with a warp shuffle (one warp per 32-value block) instead of one thread walking the block; fmax ignores NaN the same way in both. moe_grouped_s2 fuses SwiGLU with that quantize and writes the activation back to gate_up, so the grouped parity checks see the same buffer. STRATA_S2_SWIGLU_Q8=0 keeps the two launches; a static_assert pins FF % 32 == 0 for the fused kernel. Ported from Niko1221#783 (stuchapin909, head 4327807), split per piece. Co-authored-by: stuchapin909 <178119976+stuchapin909@users.noreply.github.com>
…PR-c, MTP wiring) For a window of more than one token the drafter routes with one multi-column router GEMV and one multi-row top-10 and combines all rows in one launch (verify already did; each row is bitwise the single-token call, native_multi_parity). Needs the native router (512 experts, k = 10) and the native combine; otherwise the per-token calls stay. Ported from Niko1221#783 (stuchapin909, head 4327807), split per piece. Co-authored-by: stuchapin909 <178119976+stuchapin909@users.noreply.github.com>
…1#783 PR-c) Found by native_multi_parity on gfx1151 (Aurora): the float4 combine differed from the scalar kernel there, because the first pair's other product was contracted into the FMA. Written as fmaf per expert it is the pinned contract (first product rounded, FMA per expert in order) on every compiler. Ported from Niko1221#783 (stuchapin909, head 4327807), split per piece. Co-authored-by: stuchapin909 <178119976+stuchapin909@users.noreply.github.com>
…== 0 branch (Niko1221#783 PR-b) On gfx1151 the shuffle reduction of the sum of squares inside the divergent-looking branch rounded differently for ~3% of the outputs than the single-token kernel (gdn_parity section 5, Aurora); the base kernel had the shuffles outside it, and with them back out the multi kernel is bitwise the single one for T=1..8 on gfx1151. No change for CUDA. Ported from Niko1221#783 (stuchapin909, head 4327807), split per piece. Co-authored-by: stuchapin909 <178119976+stuchapin909@users.noreply.github.com>
…kernel is not enabled (Niko1221#783 PR-f) On the HIP build the fused kernel is off until check 6 passes there, so check 6 reports SKIPPED unless STRATA_NORM_ROPE=1 asks for it. (gfx1151 on Aurora, 0.1.40: with STRATA_NORM_ROPE=1 it does not pass - about 7% of the rotated elements differ - which is why the kernel stays off on HIP.) Ported from Niko1221#783 (stuchapin909, head 4327807), split per piece. Co-authored-by: stuchapin909 <178119976+stuchapin909@users.noreply.github.com>
… keep f-140's file (Niko1221#783 PR-k)
…_MULTI_GR flag (Niko1221#783 PR-g)
A decode window is a long chain of small kernels, and on a fast card many of them are over before the next one has fetched its weights. With programmatic dependent launch the window's quantizations and dense projections (multi-column MMVQ, multi-row BF16 GEMV) start while the kernel before them is still running: they load their first weights, then wait (griddepcontrol.wait) for its output. Nothing is written before the wait, so every value is the one an ordinary launch computes. - pdl.hpp: pdl_wait / pdl_trigger and launch_pdl. The attribute is used only inside the verify window's capture (pdl_scope), with a CUDA 12.3+ runtime, on sm_90+ (STRATA_EMULATE_CC counts), for a kernel whose loaded code is sm_90+ (ptxVersion and binaryVersion >= 90: older PTX, JIT-compiled, has no wait and would race), and only when every node the capturing stream's next node depends on is a kernel. So there is never a programmatic edge after a memcpy, memset, host or event node. After a join of branches each joined kernel gets a programmatic edge (the wait covers them all); STRATA_DF_PDL=2 allows a single predecessor only. - On CUDA the PDL kernels' activations and outputs are no longer __restrict__ (the compiler could otherwise load them before the wait); the weights keep it. For every architecture: the host pass must see the device pass's signature, or MSVC rejects the template stubs. - HIP: ordinary launches, no-op device functions, the kernels' plain loops. - copy_rows_strided replaces the q/gate split's cudaMemcpy2DAsync on the batched QSA path: a memcpy node would end the PDL chain there. A copy: the same bytes, PDL on or off. - The profiler's stamps pass the early launch on inside the window. - STRATA_VERIFY_NODES also counts the programmatic edges and those that are not kernel to kernel (0 expected). - STRATA_DF_PDL=0: ordinary launches. Byte-identical: PDL changes when a kernel starts, not what it computes. Off on pre-sm_90 cards (the 4060 Ti) and on HIP. Checked: pdl_parity (new test) captures a verify-shaped chain (quantize, small-K and large-K MMVQ, BF16 GEMV, the shared Q8_1 buffer rewritten, a memcpy node, a fork and a join) with and without PDL: bitwise equal outputs over 200 replays with every buffer NaN-poisoned before each; programmatic edges only kernel to kernel, and never into a node that also follows a non-kernel node; present on sm_90+, absent elsewhere. In our fork the same kernels gave identical windows over full requests (per-layer hashes of the MoE inputs and the final residual). Measured together with the next commit (PDL is on the 5080 only: the 4060 Ti, sm_89, has none); the numbers are there. PR Niko1221#904's PDL commit ported onto 0.1.40 (8 conflicts resolved against 0.1.40's EXACT_T bf16 kernels, Niko1221#783's rows rule, TSUM and the w2/y2 pair outputs; launch_pdl needs the defaulted arguments spelled out). pdl_parity passes on sm_120; no window-time change here.
…py (#1188, #1169) #783's batched append of a verify window's cells passed no host copy for the hybrid K8V4 format (int8 and q4_0 pass st.host, and the per-token path passes K8V4's two halves). With --kv-resident, a block that was not resident when its cells were appended came back from the host copy without them, so the tokens a window wrote were lost: free generation degenerated into repetition (#1188, #1135) and parked conversations lost their decode-written turns (#1169). The batched path now passes the same host halves as the per-token path.
…y path, PLE post-ops in one pass (all opt-in) Ported from #904 onto the 0.1.40 verify window. Every switch is OFF by default, so default output and the default launch sequence are unchanged: - STRATA_DF_PDL=1: programmatic dependent launch (pdl.hpp) for the window's quantizations and dense projections (multi-column MMVQ, multi-row BF16 GEMV); sm_90+ only, CUDA 12.3+; HIP and older cards keep ordinary launches. - STRATA_DF_PLE=1: the window's hyper-connection write and PLE post-ops in one pass, which also writes the commit's history snapshots (native_gr_post_multi with strides, native_ple_postops_batch_snap). - STRATA_DF_QB_Q4=1: a Q4_0 KV cache takes the batched QSA query path too. - STRATA_DF_BRANCH=1: the mixer's side branches inside the window graph. Left opt-in because the author saw a stall on Linux with NVML queries. The q/gate split now uses copy_rows_strided (a kernel) instead of a cudaMemcpy2DAsync so a PDL chain stays kernel to kernel; the bytes are the same. Left out: STRATA_DF_KVAPP (kv_append_q4_step_multi). 0.1.40 already appends a window's K/V cells in one launch (kv_append_q4_steps, #783 PR-d). Merged by hand with the 0.1.40 forms of the same code: the exact-T BF16 multi-token kernel, the PAIR/TS MMVQ variants, QFUSE (q8_attn), the fused norm+rope and the batched residual write. Co-authored-by: Francesco Albano <119073350+Hardin22@users.noreply.github.com> (PR #904)
…tomic in kv_stream resolve (Niko1221#783 PR-a) A CTA of the fixed 256-CTA grid whose first block is past the batch's largest n_bid returns before staging the queries. Same scores, same bits. STRATA_QSA_EARLY_EXIT=0 restores the old staging. kv_stream resolve_kernel: only threads that looked something up add to the shared counter. Ported from Niko1221#783 (stuchapin909, head 4327807), split per piece. Co-authored-by: stuchapin909 <178119976+stuchapin909@users.noreply.github.com>
…with an all-outputs path, and the gdn_parity T=1..8 memcmp check (Niko1221#783 PR-b) gdn_ab_multi is instantiated per T=1..6 (generic above) with the weight unpack hoisted out of the token loop; gdn_step_norm_multi prefetches the next token's q/k while the current one runs, and when no state is written and every token is an output (the verify half) it skips the per-token bookkeeping. gdn_step_norm_kernel splits its reduction buffer so one barrier goes. gdn_parity gains section 5: for every T = 1..8, gdn_ab_multi against fused_gdn_ab, and gdn_step_norm_multi (verify half, commit half, t_out_begin, state-only commit) against the single-token kernel chained, compared with memcmp. Ported from Niko1221#783 (stuchapin909, head 4327807), split per piece. Co-authored-by: stuchapin909 <178119976+stuchapin909@users.noreply.github.com>
…sigmoid scale (Niko1221#783 PR-c) native_router_top10_multi runs one warp per token (8 per CTA) instead of one CTA per token; native_moe_combine(_multi) takes a float4 kernel for k=10 with a shared row on aligned buffers; the shared expert's sigmoid-scale is vectorised. Same arithmetic order, so same bits. New native_multi_parity (memcmp): the multi router against the single call for n=1..19 on random, tied and NaN logits, and the combine against a host replay of its contract for the float4 and scalar paths. Ported from Niko1221#783 (stuchapin909, head 4327807), split per piece. Co-authored-by: stuchapin909 <178119976+stuchapin909@users.noreply.github.com>
…ommit graph and the MTP catch-up (Niko1221#783 PR-d) kv_append_q8_steps / kv_append_q4_steps append n cells in one 3D-grid launch per pool, and native_qsa_indexer_append_steps walks the n indexer cells in order inside one CTA (one barrier between cells). Verify record_window, the commit graph and MtpDrafter::record_forward use them; a multi-slot batch keeps the per-token loop (each row has its own K/V). STRATA_NO_BATCH_KV_STEP=1 restores the per-token calls. The kv single-cell calls are now the n=1 case of the same kernel. kv_steps_parity (new, memcmp) covers INT8, Q4_0 and the hybrid's folded single-plane calls for n=1..8; qsa_parity gains append_steps against the sequential indexer append at strides 1 and 5 under none/linear/YaRN. Ported from Niko1221#783 (stuchapin909, head 4327807), split per piece. Co-authored-by: stuchapin909 <178119976+stuchapin909@users.noreply.github.com>
…iko1221#783 PR-e) The plan kernel grouped duplicate experts and numbered the groups with thread 0 walking all entries; every entry now finds its group in parallel and a packed (entries << 16 | groups) warp prefix scan numbers them. The residency check, the empty plan and *plan_err of Niko1221#871 are kept as they are. static_asserts pin the 4-warp / packing limits. resident_plan_parity (new, memcmp): n=1..80 entries over pools of 3..512 experts with and without a slot table, with and without a skip word, plus the non-resident case, against a host replay of the plan's contract. Ported from Niko1221#783 (stuchapin909, head 4327807), split per piece. Co-authored-by: stuchapin909 <178119976+stuchapin909@users.noreply.github.com>
…he MTP drafter, off on HIP (Niko1221#783 PR-f) native_qsa_rms_norm_rope does norm<256> and the rope in one launch with the same FMA order as the pair (register barriers keep nvcc from contracting across the norm/rope boundary); rope_parity check 6 compares it with native_qsa_rms_norm_weighted + native_rope_apply by memcmp for head_dim 128/256, strided q|gate and contiguous inputs, unscaled and YaRN. The in-place rope also launches one 32-thread block per row. Verify's norm_rope and q split, and the MTP drafter's q path (all T tokens at once; K for a one-token window) use it. native_norm_rope_usable() gates it: on the CUDA build it is on unless STRATA_NO_NORM_ROPE=1; on the HIP build it is off until rope_parity check 6 passes on AMD hardware, and STRATA_NORM_ROPE=1 turns it on there. The MTP q path's split, FWHT and gate-apply now run over all T tokens in one call each (row-independent). Ported from Niko1221#783 (stuchapin909, head 4327807), split per piece. Co-authored-by: stuchapin909 <178119976+stuchapin909@users.noreply.github.com>
…6 projections (Niko1221#783 PR-g) gr_read_multi / gr_write_multi (and native_gr_rms_norm_weighted_multi, native_gr_pre_gated_multi, native_gr_post_multi under them) run T tokens in one launch per step; the verify window's PLE writes, last-layer write and final mixer, and the MTP drafter's write and final mixer use them. The BF16 MMVF multi kernel and the GR up / staged down kernels are instantiated per exact T (1..6, generic above), the BF16 weight tile is unpacked once for all tokens, with the same FMA order as before. STRATA_NO_MULTI_GR=1 restores the per-token calls and the generic kernels. STRATA_GR_DOWN_MAX4 keeps its meaning from the previous commit (sm_120 default on, explicit setting decides); this commit does not change that default. gr_multi_parity (new, memcmp): gr_read_multi (with and without injection) and gr_write_multi (in place and out of place) against the single-token calls at the real geometry, T=1..8. Ported from Niko1221#783 (stuchapin909, head 4327807), split per piece. Co-authored-by: stuchapin909 <178119976+stuchapin909@users.noreply.github.com>
|
One more data point from a fully-resident box, closing out the speed question here rather than reopening the review: RTX PRO 6000 Blackwell 96 GB, IQ3_S, engine 0.1.43.1 (which carries your |
Summary
This is the follow-up to #646 rebased cleanly onto
0.1.39(6f32ec0). Since #646 merged only the initial commit on that branch (884c14e), this PR brings in the remaining verify-window, MTP, and batch-decode kernel and CUDA-graph fusions split into 4 self-contained commits by subsystem.Measured apples-to-apples on 2x RTX 3090 24 GB (
IQ2_XS,--layer-split25/23, 100% of all 24,576 experts resident in VRAM on both baseline and PR):IQ2_XS, 100% VRAM Resident)0.1.39(#646884c14e)0.1.39+ This PR (#783)lru_code,T = 5..8)15.92 ms/win~15.10 ms/win13.07 ms/win(13.07–15.02across suite)0.1.39(-17.9% vs pre-#646)lru_code/skiplist/prose)164.1 / 156.4 / 151.0 tok/s~170.2 / 162.0 / 156.5 tok/s186.6 / 177.1 / 170.4 tok/s0.1.39(+13.7% vs pre-#646)210.7 – 217.5 tok/s(Note: Compared to
0.1.38's default26/22split where GPU 0 held491/512experts at18.31 ms/win/149.2 tok/s, combining0.1.39's25/23all-resident split with #646 + #783 reduces verify window time from18.31 ms/winto13.07 ms/win.)All changes are scoped strictly to
include/strata/{core,kernels}andsrc/{core,kernels/cuda}(32 files), preserve 0-ULP bitwise parity across all 25 GPU parity tests, and maintain full HIP (gfx906wave64 + RDNA wave32) compatibility.Commits & What's Included
1. Multi-token GR / RMSNorm / post-op kernels and exact-
Tspecializations (fused_gr.cu,gr.cu,native_gr_norm.cu,native_gr_postops.cu,native_bf16.cu,quantize_act.cu)fused_gr.cu:Bf16x8,unpack8,dot8u,dot8u_ptr) so weight tiles are unpacked once across tokens.gr_up_multi_kernel<MAX_T, EXACT_T>andgr_down_staged_kernel<MAX_T, EXACT_T>to eliminate dynamic loop bounds forT = 1..8(STRATA_GR_DOWN_MAX4=0disables theMAX_T=4specialization forct <= 4).f16_from_f32,STRATA_DP4A, andSTRATA_LDGfromf166564for HIP/ROCm portability.gr.cu(gr_read_multi,gr_write_multi): Multi-token hyper-connection read and write acrossn_toktokens in one launch.native_gr_norm.cu(native_gr_rms_norm_weighted_multi): Multi-token weighted RMSNorm acrossn_tokrows.native_gr_postops.cu(native_gr_pre_gated_multi,native_gr_post_multi): Multi-token pre-gated mixing and residual post-ops.native_bf16.cu(bf16_f32_mmvf_multi_kernel<BLOCK_SIZE, NT, EXACT_T>): Compile-time token-count dispatch inbf16_gemv_fp32_mmvf_multi.quantize_act.cu: Warp-shuffle reduction inquantize_q8_0_scaled_kernel.2. IQ/MMVQ sub-warp & 2-row kernels and MoE / shared-expert fusions (
iq_kernels.cu,native_mmvq.cu,native_moe.cu,native_router.cu,s2_expert_grouped.cu,shared_expert.cu,elementwise.cu)iq_kernels.cu:row_dot_80_sub16<TY, NC, EXACT_N, STAGE_GRID>and templatesmmvq_multi_kernel<TY, NC, STAGE_GRID, EXACT_N>(launch_mmvq_multi_nc).gu_split) innative_gu_multi_kernelalongside0.1.39'sdown_iq4nl_multi_kernel.native_mmvq.cu: AddsSmallTraits<Q40Block, 4>,SmallTraits<Q50Block, 4>,SmallTraits<Q80Block, 8>, andSmallTraits<IQ4NLBlock, 4>2-row-per-warp (ROWS = 2) multi-column MMVQ specialization inlaunch_multi_nwhen!multi_exact.native_moe.cu(combine_k10_vec4):float4-vectorizednative_moe_combineandnative_moe_combine_multi.native_router.cu(route_multi,native_router_top10_multi): Multi-token top-10 router kernel acrossn_toktokens in a single launch.s2_expert_grouped.cu(swiglu_quantize_q8_0_scaled_kernel): Fuses SwiGLU and scaledQ8_0quantization inmoe_grouped_s2while writing backgate_up[idx]sos2_expert_grouped_parityremains 100% bitwise identical (STRATA_OLD_GROUPED=1selects the previous path).shared_expert.cu(sigmoid_scale_rows_vec4_kernel,launch_sigmoid_scale_rows):float4-vectorized sigmoid gating inshared_expertandshared_expert_multi.elementwise.cu(copy_rows_from_mapped_kernel<ZERO_HITS>): Allowscopy_rows_from_mapped(zero_hits = false) to skip redundant zero-fill when all experts are resident.3. Batched KV append, QSA indexer/RoPE fusion, and parallel
resident_plan_kernel(kv_q4.cu,kv_q8.cu,native_qsa_indexer.cu,native_rope.cu,verify_kernels.cu,fused_gdn.cu,qsa_select.cu,kv_stream.cu)kv_q4.cu(kv_append_q4_steps) &kv_q8.cu(kv_append_q8_steps): Append K and V acrossn_toktokens in a single 3D grid launch.native_qsa_indexer.cu(native_qsa_indexer_append_steps): Multi-token QSA indexer append in a single launch.native_rope.cu(norm_rope_kernel<TAB>,native_qsa_rms_norm_rope): Fuses per-head QSA RMSNorm and RoPE application.verify_kernels.cu:resident_plan_kernel<ALL_RESIDENT>: KeepskResidentPlanMax = 128(f945515) and replaces thread-0's serial(my_cnt << 16) | (is_first ? 1 : 0)prefix scan using__shfl_up_sync(0xffffffffu, pref, d). Using width-32__shfl_up_syncwithout__ballot_synckeeps the scan identical on both 32-lane warps (CUDA / RDNA) and 64-lane wavefronts (gfx906).gdn_ab_multi_kernel<MAX_T, EXACT_T>andgdn_step_norm_multi_kernel<ALL_OUT>.4. Wire multi-token kernels into
VerifierandMtpDrafterCUDA graphs (verify.cpp,mtp.cpp,verify.hpp)verify.cpp:Verifier::record_windowwithnative_gr_rms_norm_weighted_multi,native_gr_pre_gated_multi,native_gr_post_multi,native_qsa_rms_norm_rope,kv_append_q8_steps,kv_append_q4_steps,native_qsa_indexer_append_steps, andnative_router_top10_multi.copy_rows_from_mappedzero-fill on the all-residentdirect_partspath.STRATA_FUSE_HEAD_GR=1(fused LM-head GR read) andSTRATA_VERIFY_PRELAUNCH=1(cross-stage prelaunch) are both opt-in (default off) so default--layer-splitcommit and stage ordering remain identical to0.1.39(deee447).mtp.cpp:gr_read_multi,gr_write_multi,native_gr_rms_norm_weighted_multi,native_qsa_rms_norm_rope,kv_append_q8_steps,kv_append_q4_steps,native_qsa_indexer_append_steps, andnative_router_top10_multiinsideMtpDrafter::record_forward.Verification
GPU Parity Suite (CTest — 25/25 Passed)
Built and ran the GPU parity test suite on Windows 11 / CUDA 13.2 (2x NVIDIA GeForce RTX 3090 24 GB):
mmvq_multi_parity— Passediq_multi_parity— Passediq_parity_fixtures/iq_parity— Passednative_grouped_parity— Passedshared_expert_parity— Passeds2_expert_grouped_parity— Passed (bitwise identical across all 22 checks)s2_gemv_parity/s2_gemv_q8_parity— Passedgr_parity— Passedgdn_parity— Passedqsa_parity/qsa_topk_parity— Passedrope_parity— Passedquantize_act_parity— Passedroute_window_parity/router_top10_parity— Passedelementwise_parity— Passedbf16_gemv_parity— Passedsampler_parity— Passedcvec_parity— Passedkv_q8_parity/kv_q4_parity/kv_hybrid_parity/kv_stream_parity— Passed