fix(cuda): enable hybrid Q2_K/Q4_K GGUF load on decode-perf-tuning#1
Open
rrstesiak wants to merge 1 commit into
Open
fix(cuda): enable hybrid Q2_K/Q4_K GGUF load on decode-perf-tuning#1rrstesiak wants to merge 1 commit into
rrstesiak wants to merge 1 commit into
Conversation
Loading the DeepSeek V4 Flash hybrid Q2_K/Q4_K GGUF on GB10 hung
during model load: when the engine reached the final Q4_K layers,
ds4-server hung on the CUDA call and the client received a generic
CUDA error. This patch makes the hybrid GGUF load end-to-end and
decode at ~20 t/s on this branch on fresh ctx (at the GB10 LPDDR5X bandwidth
roofline).
Root cause:
routed_moe_launch hard-rejected the q4k MoE path unless
n_tokens == 1u && n_expert == 6u (single-token decode, 6 active
experts). The hybrid GGUF triggers q4k_path during load with
shapes outside that envelope; the rejected launch left the CUDA
stream in a bad state and the load hung.
Changes:
- Loosen the q4k_path gate; allow multi-token and wider-expert
shapes through. The conditional body is deliberately empty
as a marker: if a future perf regression appears for wider
shapes, reinstating return 0 forces the slower IQ2_XXS-only
fallback.
- Add a graph-capture-safe handshake (cudaEventRecord +
cudaStreamWaitEvent) between ds4_mmq_stream_for_call() and
ds4_current_stream() before the decode-vec MoE SwiGLU.
cudaStreamWaitEvent records as a DAG edge inside capture,
preserving cross-stream parallelism.
- Add a belt-and-braces cudaDeviceSynchronize() after moe_pair
in the prefill path. Without it, very-large-prompt loads
intermittently hit CUDA memory errors. Root cause is not yet
pinned down; the heavier sync is intentional pending a
proper repro.
Impact:
- Hybrid Q2_K/Q4_K GGUF now loads end-to-end on GB10/Spark.
- Decode runs at ~20 t/s short-ctx with CUDA graph capture
enabled (near the documented ~20.5 t/s LPDDR5X roofline).
- antirez/ds4 upstream supports the hybrid quant correctness-
wise but runs significantly slower on Spark; that delta is
not investigated in this patch.
Thanks to @Entrpi for the decode-perf-tuning branch — this fix
is a small refinement on top of that work. Built and tested on
a single DGX Spark; not in a Military Industrial Complex.
284B parameters cranking through the LPDDR5X.
Tested on GB10 / Blackwell (SM121) only; CUDA 13.0.88, decode-perf-tuning branch at 5625a99. Ada / Hopper / Grace-Hopper / GB200 untested.
Entrpi
added a commit
that referenced
this pull request
Jul 20, 2026
…->55.2 ms/tok) The depth-gate flip (b5a9ab1) made w5 DSpark verify the universal deep-decode step shape, and the dense Q8_0 projections at n_tok 2..8 fell to the raw-block mmvq tier: the aligned artifact entry was N==1-only, so the flip re-based the family from the aligned kernel onto mul_mat_vec_q<Q8_0,5> -- 326 launches / 31.5 ms/step at 240K (19.0%, step ledger #1), 90-200 GB/s per shape against the 236 GB/s substrate, with a per-call memset+quantize tail. ds4_mmq_q8_0_aligned_dense_vec now accepts N 1..8: a template NC kernel reads the same aligned SoA stream once per row (2x int4) and accumulates NC output columns per lane against col-strided q8_1 activations (K%1024 guarantees ne10_padded==K, so the col stride is exactly nb). The folded-q81 producer branch stays N=1; verify widths always quantize. Decode dispatch passes the real width; escape DS4_CUDA_NO_Q8_ALIGNED_NC restores the mmvq tier for N>1. proto_q8_aligned_nc (one variant, converged): +17..+87% per shape vs the production entry, family projected 26.35 ms/step vs the 25.3 weight-bytes floor (5.98 GB/step at 236 GB/s). Engine re-ledger landed 26.46 ms/step NC + 2.53 residue -- proto-to-engine 1:1 for the fifth increment. Residue = the drafter model's layer tensors (no in-process aligned artifacts for secondary models; banked lever); the drafter head rides the base artifact (shared tensor). Gates (.33): 240K 59.4 -> 55.2 ms/tok ALL STAGES PASS, spec engaged (accept 70.3%, tok/step 2.81); 515K 66.1 vs 65.5 = accept-volatility noise band (per-step 192.6 -> 189.7 on single legs; dense weights are depth-independent); 12k 37.9 flat -- quench runs this gate shape at w1 (tok/step 1.07) where the aligned tier already served. teb fast x2 fp8-vs-F32 identity EXACT (spec_hits 13659==13659, accepts 201==201); make test green on .15. Proto: cuda/mmq/test/proto_q8_aligned_nc.cu.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Loading the DeepSeek V4 Flash hybrid Q2_K/Q4_K GGUF on GB10 hung during model load: when the engine reached the final Q4_K layers, ds4-server hung on the CUDA call and the client received a generic CUDA error. This patch makes the hybrid GGUF load end-to-end and decode at ~20 t/s on this branch on fresh ctx (at the GB10 LPDDR5X bandwidth roofline).
Root cause:
routed_moe_launch hard-rejected the q4k MoE path unless
n_tokens == 1u && n_expert == 6u (single-token decode, 6 active
experts). The hybrid GGUF triggers q4k_path during load with
shapes outside that envelope; the rejected launch left the CUDA
stream in a bad state and the load hung.
Changes:
Loosen the q4k_path gate; allow multi-token and wider-expert shapes through. The conditional body is deliberately empty as a marker: if a future perf regression appears for wider shapes, reinstating return 0 forces the slower IQ2_XXS-only fallback.
Add a graph-capture-safe handshake (cudaEventRecord + cudaStreamWaitEvent) between ds4_mmq_stream_for_call() and ds4_current_stream() before the decode-vec MoE SwiGLU. cudaStreamWaitEvent records as a DAG edge inside capture, preserving cross-stream parallelism.
Add a belt-and-braces cudaDeviceSynchronize() after moe_pair in the prefill path. Without it, very-large-prompt loads intermittently hit CUDA memory errors. Root cause is not yet pinned down; the heavier sync is intentional pending a proper repro.
Impact:
Thanks to @Entrpi for the decode-perf-tuning branch — this fix is a small refinement on top of that work. Built and tested on a single DGX Spark; not in a Military Industrial Complex. 284B parameters cranking through the LPDDR5X.
Tested on GB10 / Blackwell (SM121) only; CUDA 13.0.88, decode-perf-tuning branch at 5625a99. Ada / Hopper / Grace-Hopper / GB200 untested.
Test evidence
Per
CONTRIBUTING.md: correctness exercised via the in-branch test runner (ds4-eval, the renamedds4_testondecode-perf-tuning); speed viads4-benchwith the canonicalspeed-bench/promessi_sposi.txtprompt at the documented sweep parameters (--ctx-start 2048 --ctx-max 65536 --step-incr 2048 --gen-tokens 128). Pure Q2 control run included as CSV below; hybrid run failed at the 2048 frontier withq8 fp16 cache budget exhausted(pre-existing, see Known limitation).ds4-eval --questions 5ds4-eval --questions 55625a99ds4-eval --questions 45625a99ds4-benchcanonicalillegal memory accessds4-benchcanonicalPure Q2 canonical bench CSV (this branch)
Known limitation
Large-context prefill (≥2048 tokens via
ds4-bench) with the hybrid GGUF remains a branch-level limitation. Not a regression introduced by this patch — the parent commit (5625a99) cannot prefill the hybrid model at even 200 tokens. This patch extends the working range from zero to ~2000+ tokens, sufficient for interactive chat use of the hybrid quant. Large-prefill support is out of scope here.