Repository navigation
experts: smaller and fewer launches for the verify window's PCIe call - #363
BlueKingMuch wants to merge 1 commit into
Conversation
…; SwiGLU and q8_1 in one pass (bitwise) native_expert_grouped launched a block row per possible group (cap = window tokens x 10) for gate/up and down, and SwiGLU and the q8_1 quantization as two kernels over all cap entries. The verify window's PCIe call has no group at all at pcie_frac 0 and usually none otherwise, yet every layer paid ~17k blocks that started only to return plus two passes over entries that are not its own. - gate/up and down stride over the groups (rows y, y + gridDim.y, ...); the PCIe call runs 4 block rows (kPcieGroupRows), the VRAM call keeps one per possible group. Each group's rows are computed by the same warp code in the same order, so the results do not depend on the grid. - SwiGLU + q8_1 in one kernel over the call's own entries [grp_start[0], grp_start[n]); the product is rounded on its own (__fmul_rn; on HIP a plain product under contract(off)) before the block sum, as when it went through memory, and the quantizer body is quantize_q8_1_kernel's (factored out, its PTX unchanged). - STRATA_GROUPED_V1=1 keeps the old launches for A/B timing; native_grouped_parity checks old vs new bitwise (every gate/up x down format, 0..cap groups, entries from 0 and past 0, grid_groups 1..4 and > cap). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FKZxC8sUotEzdWXws2FTUC
|
Tested on the R9700 (gfx1201, ROCm 10.2.0a / hipBLASLt 1.5.0, Swift 1.5 IQ3_XXS, Build / correctness
Kernel launches drop as claimed (rocprofv3
Decode speed: a wash (same binary,
So the ~2.7% fewer kernels don't move throughput on this card — decode here is ~34% idle with median kernel 2.64 µs, and the removed blocks were the ones that already returned immediately. I believe it's still a real (if small) reduction in GPU work — it just isn't the decode bottleneck on gfx1201 at ~69 tok/s. Possibly more visibly useful at low clocks, on multi-stream/MTP-heavy servers, or on other cards; happy to share the rocprof CSVs if useful. |
|
Thanks @bsorensen110 for testing it on gfx1201, and for the launch counts: good to see the reduction reproduce too on AMD. On CUDA it does show up in decode, I think because there the GPU sets the pace. 0.1.32 with this PR on top (it merges without conflicts; I run it that way), RTX 4080 SUPER 32 GB, IQ3_S at 262K, 256-token requests,
Here the GPU wait is 64-71 % of a round, and it drops by the same 0.4-0.5 ms. With your card ~34 % idle in decode, the removed blocks come off time the GPU spent waiting anyway, which fits your numbers. btw: i don't only work on/with Nvidia, i have a 2nd System with an RDNA2 RX6800 too where I am looking into enabling (better) support here and maybe do some performance tuning. Might as well help on newer gfx Archs too then. |
…iko1221#374 Niko1221#363 Niko1221#407 Niko1221#413 onto 0.1.35 One dpct run of upstream main + the seven PRs, 3-way merged into the files they touch (sycl/tools/merge_upstream.py); their header changes are forked into sycl/include until upstream merges them. - Niko1221#385 stager waits for a buffer's previous DMA on every job (port: polled seq) - Niko1221#463 decode waits for an adaptive swap before reading residency - Niko1221#453 drafter's batched K/V on the KV-streaming ring - Niko1221#374 first chunk's PLE rows read beside layer 0; port fixes: the next read starts at c0 + T (the port's first chunk is shorter), and the upload is marked by a polled sequence number instead of a host event wait, which deadlocked prompts past ~4K tokens under the Level Zero v2 adapter - Niko1221#363 group stride + fused SwiGLU/q8_1 for the PCIe expert call, re-applied to the port's lane kernels (fp contract off, as upstream's HIP form) - Niko1221#407 --adapt-tuned (opt-in), Niko1221#413 key-head DeltaNet recurrence (opt-in via STRATA_GDN_KEYHEAD=1: 8% slower on the B70; its NVIDIA gate and SM-holding parity bench not ported) Tested on the B70: native_grouped_parity, iq_multi, gr, qsa, kv_hybrid, quantize_act, qsa_prompt_attn parity pass; Coder 19/2,184-token, IQ2_XS and 40K outputs identical to 0.1.33; Coder 2,184-token prompt 784 -> 825 tok/s; 128K int8 KV streaming 895 / 67.0 tok/s; IQ2_XS decode +1.7% (Niko1221#363). Co-authored-by: sergqwer, constantindjonkam, architectds, BlueKingMuch (upstream PR authors) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rb4SRxFuLpffQAAXBf4HxA
|
Released in 0.1.38: the PCIe call's group stride and SwiGLU + q8_1 in one pass. Bitwise here as well ( |
…0.1.35 One dpct run of upstream main + the seven PRs, 3-way merged into the files they touch (sycl/tools/merge_upstream.py); their header changes are forked into sycl/include until upstream merges them. - #385 stager waits for a buffer's previous DMA on every job (port: polled seq) - #463 decode waits for an adaptive swap before reading residency - #453 drafter's batched K/V on the KV-streaming ring - #374 first chunk's PLE rows read beside layer 0; port fixes: the next read starts at c0 + T (the port's first chunk is shorter), and the upload is marked by a polled sequence number instead of a host event wait, which deadlocked prompts past ~4K tokens under the Level Zero v2 adapter - #363 group stride + fused SwiGLU/q8_1 for the PCIe expert call, re-applied to the port's lane kernels (fp contract off, as upstream's HIP form) - #407 --adapt-tuned (opt-in), #413 key-head DeltaNet recurrence (opt-in via STRATA_GDN_KEYHEAD=1: 8% slower on the B70; its NVIDIA gate and SM-holding parity bench not ported) Tested on the B70: native_grouped_parity, iq_multi, gr, qsa, kv_hybrid, quantize_act, qsa_prompt_attn parity pass; Coder 19/2,184-token, IQ2_XS and 40K outputs identical to 0.1.33; Coder 2,184-token prompt 784 -> 825 tok/s; 128K int8 KV streaming 895 / 67.0 tok/s; IQ2_XS decode +1.7% (#363). Co-authored-by: sergqwer, constantindjonkam, architectds, BlueKingMuch (upstream PR authors)
…iko1221#374 Niko1221#363 Niko1221#407 Niko1221#413 onto 0.1.35 One dpct run of upstream main + the seven PRs, 3-way merged into the files they touch (sycl/tools/merge_upstream.py); their header changes are forked into sycl/include until upstream merges them. - Niko1221#385 stager waits for a buffer's previous DMA on every job (port: polled seq) - Niko1221#463 decode waits for an adaptive swap before reading residency - Niko1221#453 drafter's batched K/V on the KV-streaming ring - Niko1221#374 first chunk's PLE rows read beside layer 0; port fixes: the next read starts at c0 + T (the port's first chunk is shorter), and the upload is marked by a polled sequence number instead of a host event wait, which deadlocked prompts past ~4K tokens under the Level Zero v2 adapter - Niko1221#363 group stride + fused SwiGLU/q8_1 for the PCIe expert call, re-applied to the port's lane kernels (fp contract off, as upstream's HIP form) - Niko1221#407 --adapt-tuned (opt-in), Niko1221#413 key-head DeltaNet recurrence (opt-in via STRATA_GDN_KEYHEAD=1: 8% slower on the B70; its NVIDIA gate and SM-holding parity bench not ported) Tested on the B70: native_grouped_parity, iq_multi, gr, qsa, kv_hybrid, quantize_act, qsa_prompt_attn parity pass; Coder 19/2,184-token, IQ2_XS and 40K outputs identical to 0.1.33; Coder 2,184-token prompt 784 -> 825 tok/s; 128K int8 KV streaming 895 / 67.0 tok/s; IQ2_XS decode +1.7% (Niko1221#363). Co-authored-by: sergqwer, constantindjonkam, architectds, BlueKingMuch (upstream PR authors)
…iko1221#374 Niko1221#363 Niko1221#407 Niko1221#413 onto 0.1.35 One dpct run of upstream main + the seven PRs, 3-way merged into the files they touch (sycl/tools/merge_upstream.py); their header changes are forked into sycl/include until upstream merges them. - Niko1221#385 stager waits for a buffer's previous DMA on every job (port: polled seq) - Niko1221#463 decode waits for an adaptive swap before reading residency - Niko1221#453 drafter's batched K/V on the KV-streaming ring - Niko1221#374 first chunk's PLE rows read beside layer 0; port fixes: the next read starts at c0 + T (the port's first chunk is shorter), and the upload is marked by a polled sequence number instead of a host event wait, which deadlocked prompts past ~4K tokens under the Level Zero v2 adapter - Niko1221#363 group stride + fused SwiGLU/q8_1 for the PCIe expert call, re-applied to the port's lane kernels (fp contract off, as upstream's HIP form) - Niko1221#407 --adapt-tuned (opt-in), Niko1221#413 key-head DeltaNet recurrence (opt-in via STRATA_GDN_KEYHEAD=1: 8% slower on the B70; its NVIDIA gate and SM-holding parity bench not ported) Tested on the B70: native_grouped_parity, iq_multi, gr, qsa, kv_hybrid, quantize_act, qsa_prompt_attn parity pass; Coder 19/2,184-token, IQ2_XS and 40K outputs identical to 0.1.33; Coder 2,184-token prompt 784 -> 825 tok/s; 128K int8 KV streaming 895 / 67.0 tok/s; IQ2_XS decode +1.7% (Niko1221#363). Co-authored-by: sergqwer, constantindjonkam, architectds, BlueKingMuch (upstream PR authors) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rb4SRxFuLpffQAAXBf4HxA
…ing at half of it, so a text block always arrives /v1/messages and /v1/chat/completions with max_tokens 200-600 and the thinking on spent all of max_tokens on the thinking: the 0.1.45 answer room starts at max_tokens 1024 and a client that gave no budget of its own got no text block. For the deepseek41 family only, with no budget from the client or the config, the thinking is now capped at max_tokens / 2 for max_tokens from 48 up to 1023 (the existing wrap-up, </think> and the answer follow in the other half). A client's own reasoning_budget_tokens / budget_tokens wins, "answer_room": false turns it off, thinking off is unchanged, other families (Qwen) are untouched. Maya #363 closes the thinking in the engine instead; the server's #123 budget already does that for every engine, so only the short-cap gap needed closing. Tests (serve/test_dsv41.py SmallCap, fake engine that thinks without end): max_tokens 200, 400 and 600 on both APIs return a non-empty text block with the thinking wrapped up at half; thinking off answers directly; a client budget wins; a tool call under a short cap still parses; another family keeps the old behaviour. serve suite: 1025 tests OK (15 skipped).
…uest nor the config names a budget Same default as Maya #363. For the deepseek41 family only: Service.reasoning_budget() answers 32768 when the request has no reasoning_budget_tokens / thinking_budget_tokens and the config has no reasoning_budget_tokens at all (a config value, 0 included, wins; a request's 0 turns it off). The existing wrap-up (#123) closes the thinking at the budget, and the answer-room clamp still cuts it to leave the answer its share of max_tokens. Thinking off and other families unchanged. Tests in serve/test_dsv41.py: the default, a request's 0 and own value, the config's value and 0, another family, and a 5000-token cap that is wrapped up and answered. serve suite: 1027 tests OK (15 skipped).
Every verify window calls
native_expert_groupedtwice per layer: for the experts in VRAM and for the ones that come over PCIe. Both calls launched gate/up and down with one block row per possible group (cap_groups= window tokens x top-k) and ran SwiGLU and the q8_1 quantization as two kernels over allcap_entries. The PCIe call has no group at all atpcie_frac0 and rarely more than one otherwise, yet every layer paidcap_groupsx (2n_ff/ 8 +n_embd/ 8) blocks for it that readn_groupsand returned (about 17k with this model's 3.4-token windows), plus two passes over entries that were not its own. The graph is captured once per session and a request may still setpcie_fracafterwards, so the call stays in the graph (the graph without the PCIe path was left out when #109 was merged); this PR makes it cheap instead.What changes
native_gu_kernel,native_down_kerneland IQ kernels: decode each weight part once for every column and entry, bitwise identical #242's_multivariants) run groupgin block rowg % gridDim.y(rowsy, y + gridDim.y, ...below*n_groups). The verify window's PCIe call launcheskPcieGroupRows= 4 block rows; the VRAM call and every other caller keep one per possible group (grid_groupsdefaults to 0 =cap_groups). A group's rows are computed by the same warp code in the same order whichever block row runs it, so the results do not depend on the grid.[grp_start[0], grp_start[n_groups]). The SwiGLU product is rounded on its own before the q8_1 block sums, as it was when it went through memory (__fmul_rn). The quantizer body isquantize_q8_1_kernel's, factored out into a device function; that kernel's PTX is unchanged.STRATA_GROUPED_V1=1keeps the old launches, for A/B timing.It is on by default since it changes no bit. If you prefer it opt-in, that is the default of
g_grouped_v1.Exactness
native_grouped_parity(a GPU ctest next toiq_multi_parity) runs the new launches againstSTRATA_GROUPED_V1's and compares the outputs bit for bit: every gate/up format with every down format the dispatcher takes (gate/up IQ2_XXS, IQ2_XS, IQ3_XXS, IQ3_S, IQ2_S, IQ4_XS, IQ1_M, Q2_0, Q4_K, Q5_K, Q8_0; down IQ4_NL, IQ4_XS, Q2_0, Q5_1, Q8_0), calls of 0 tocapgroups, entries from 0 and from past 0 (the PCIe call's entries follow the VRAM call's), scattered destinations, stale scratch, andgrid_groups0, 1, 2, 3, 4 and abovecap. The rows a call must not write are compared too (both runs start from the same NaN pattern). On this branch (0.1.31): 0 failures.pcie_frac=0) give the same 256 tokens in the same number of rounds with and withoutSTRATA_GROUPED_V1=1, greedy and sampled (T 1.0, top_p 0.95, top_k 20, seed 1).Measured
RTX 4080 SUPER 32 GB (sm_89), Ryzen 7 5800X3D, 64 GB DDR4, PCIe 3.0 x16, Windows 11, CUDA 13.3.
The calls alone, on this branch:
native_grouped_parity --benchcaptures a window's 48 calls in a graph, each layer with its own experts (2560 x 768, IQ3_S gate/up, IQ4_XS down), so the weights stream from DRAM as in the engine. The two variants' launches alternate; medians of 40 launches each:STRATA_GROUPED_V1=1)pcie_frac0)Kernel by kernel (Nsight Systems on the same bench), the VRAM call's gate/up and down take the same time in both variants (86.2 / 85.8 us and 54.7 / 54.8 us), and the fused SwiGLU + q8_1 kernel takes 2.06 us against 1.41 + 1.47 us for the two it replaces.
One thing to watch when timing this on a desktop card: under sustained load this one steps its clock down after a fraction of a second (gate/up went from 80 to 89 us within one run), so timing one variant after the other can show a difference of that size that has nothing to do with the change. That is why the bench alternates them.
In the engine: 0.1.31 (9259cad) with this commit only, plus two measurement aids that change no computed value: #320 (so the profile's columns add up) and a local patch that lets a request ask for a fixed length and for no timing-dependent parts (
ignore_eos,suffix=0,adapt=0). IQ3_S,--max-context 262144, int8 KV, 11055 experts in VRAM; every comparison within that build (STRATA_GROUPED_V1unset against=1).GPU stages (
STRATA_VERIFY_PROFILE=1, 8K context, ms per verify window, DeltaNet and attention layers summed):STRATA_GROUPED_V1=1)The PCIe call loses its 0.47 ms in every run I made, also in a 0.1.30-based build and next to other open PRs. How much of it the window keeps depends on what the GPU waits for next: in these runs it mostly waited longer for the host side instead (+0.43 ms for the plan and the CPU experts), so the window got 0.08 ms shorter; in the 0.1.30-based run it kept all of it (18.97 -> 18.50 ms).
The deterministic requests above (the same 256 tokens in 97 / 103 rounds; with expert swaps off the CPU experts take about 7.5 ms per round): round 24.79 -> 24.49 ms greedy and 25.13 -> 24.85 ms sampled (-0.30 / -0.28 ms, +1.1 % tokens/s), GPU wait per round -0.31 / -0.26 ms.
Decode, 5 requests of 256 tokens per cell, prompt-lookup drafts and expert swaps on (8K greedy / sampled, 64K greedy / sampled):
--pcie-frac 0.28: GPU wait per round, ms--pcie-frac 0.28: tokens/sThe GPU wait per round drops in all eight cells (by 0.09 to 0.46 ms). The tokens/s stay within the scatter of single requests (104 to 130 within one cell here; without the SSD keep-alive of #317 some requests also waited for SSD reads): +2.2 / -0.3 / +6.9 / +2.0 % at
pcie_frac0, -1.9 / +2.6 / +1.9 / -1.7 % at 0.28.In short: a fixed 0.47 ms of GPU time per window, tokens and bits unchanged. What it buys in tokens/s depends on whether the GPU or the CPU experts set the pace on a given machine; about 1 % in the deterministic runs here.
To reproduce:
STRATA_GROUPED_V1=1against unset,STRATA_VERIFY_PROFILE=1for the stages,native_grouped_parity --benchfor the calls alone.