cuda: native PTQ1_0 MMQ tile loader (closes the dequantize-to-cuBLAS prefill fallback) - #160
Conversation
Large-batch PTQ1_0 matmul had no MMQ path, so it fell back to a full dequantize to fp16 followed by cuBLAS: measured 2.26x slower than PQ2_0 at n=512 while its decode already led. Add the tile loader, the MMQ type dispatch and the DECL_MMQ_CASE instance so PTQ1_0 takes the same quantized GEMM path as the other low-bit types, plus a packed, coalesced dequantize for the remaining conversion sites. Ported from a handoff worktree whose gates recorded 45/45 PTQ1_0 MUL_MAT on CUDA and an H100 perf pass at the pre-#152 base; its Metal and FWHT parts are superseded by #152 and #157 and are not included. This branch has not yet been compiled or run on CUDA hardware by the committer; that verification is the gate before it is pushed.
There was a problem hiding this comment.
🟡 Changes recommended
PTQ1_0 is currently prevented from using MMQ for batch sizes > 64, which can reintroduce the cuBLAS fallback for large-batch prefill and undermines the PR’s stated goal.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
This PR adds native CUDA MMQ (quantized matmul) support for GGML_TYPE_PTQ1_0, aiming to avoid the dequantize-to-fp16 + cuBLAS fallback for large-batch matmul by providing a PTQ1_0 MMQ tile loader, dispatch wiring, and a dedicated PTQ1_0 dequantize kernel for remaining conversion paths.
Changes:
- Add PTQ1_0 MMQ tile loading + type dispatch/instantiation so PTQ1_0 can use the quantized GEMM path.
- Add PTQ1_0 MMQ configs for Ampere and update related CUDA selection heuristics (MMVQ/MMQ).
- Add a CUDA-only, coalesced PTQ1_0 dequantize kernel and wire it into the
to_{bf16,fp16,fp32}conversion dispatch.
File summaries
| File | Description |
|---|---|
| ggml/src/ggml-cuda/vecdotq.cuh | Adds PTQ1_0 MMQ VDR define used by MMQ util selection. |
| ggml/src/ggml-cuda/template-instances/mmq-instance-ptq1_0.cu | Adds the PTQ1_0 MMQ template instantiation (CUDA-only). |
| ggml/src/ggml-cuda/template-instances/generate_cu_files.py | Generates the PTQ1_0 MMQ instance with CUDA-only guards. |
| ggml/src/ggml-cuda/mmvq.cu | Adds a PTQ1_0-specific MMVQ threshold on NVIDIA Turing+. |
| ggml/src/ggml-cuda/mmq.cuh | Wires PTQ1_0 into MMQ ds-layout selection, tile sizing, util dispatch, and extern declarations. |
| ggml/src/ggml-cuda/mmq.cu | Adds PTQ1_0 MMQ type dispatch and a PTQ1_0 MMQ selection rule. |
| ggml/src/ggml-cuda/mmq-load-tiles.cuh | Implements the PTQ1_0 MMQ tile loader and packed decode path (CUDA-only). |
| ggml/src/ggml-cuda/mmq-config-ampere.cuh | Adds Ampere MMQ config cases for PTQ1_0. |
| ggml/src/ggml-cuda/convert.cu | Adds a coalesced PTQ1_0 dequantize kernel and routes conversions to it on CUDA (non-HIP). |
Review details
- Files reviewed: 9/9 changed files
- Comments generated: 1
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| #if !defined(GGML_USE_HIP) | ||
| if (type == GGML_TYPE_PTQ1_0) { | ||
| return ne11 <= MMQ_PTQ1_0_MAX_BATCH_SIZE; | ||
| } | ||
| #endif |
|
Tested on our L40S (CUDA 12.8, sm_89), PR head d14a927 against prism 7113f64 in separate worktrees. As written (MMQ cap 64): correct and safe to merge.
Follow-up for a separate PR: raise or remove
The fp16 dequantize-to-cuBLAS fallback is the source of the extra PTQ1_0 error on CUDA. With MMQ at full prefill the 27B PTQ1_0 clears the top-1 certification gate and lands at the same level as PQ2_0; today it is the only shipped band that fails certification on CUDA. Cost: MMQ is about 7% slower than cuBLAS at pp512 (27B 1689 → 1578, 9B 5721 → 4786, 4B 9249 → 8361) and faster below roughly ubatch 400 (27B pp128 775 → 1377, pp256 1546 vs 1543 cuBLAS). Decode unchanged either way. Suggested shape for that PR: cap at 512 or above (or drop the cap) so the default ubatch takes the MMQ path and PTQ1_0 certifies on CUDA, and expose the threshold as an env var for A/B. If the 7% at pp512 matters more than certification for some deployment, that env var is the knob. HIP stays excluded by the Merging as is; the cap change can be its own PR. |
|
Approving. This fills the gap correctly and the mechanism is the right one for prefill. The structural problem it fixes is real and was easy to miss: PTQ1_0 had zero references in The tile loader does the thing that makes this a prefill win specifically. Eight threads cooperatively decode each 128-weight block once into shared memory, so the base-3 unpack amortises across the tile width instead of being repaid per output column. That is also why it cannot help decode, which has no tile to amortise over, and it matches what we saw on TQ1_0 where prefill went 5.6x while decode regressed. The lane split checks out too: lanes 0 to 3 cover One thing worth stating plainly in the description rather than leaving in the footnotes. This verifies correctness, not the performance claim. 45/45 MUL_MAT and 75/75 MUL_MAT_ID on an L40S establish that the path is right, and the cited prefill number is from a pre-#152 base on different hardware. So the honest claim today is "the dequantize-to-cuBLAS fallback is gone", not "prefill matches PQ2_0". That distinction matters because the question this came from was specifically whether prefill can match, and that is still unmeasured on the current tree. Two smaller notes, neither blocking: Lane 7 of the 8 The Related, and worth landing alongside: the |
|
Correcting my own review above. Copilot's comment is right and my approval was wrong on the central point, so treat this as retracting the approval until the selection rule changes.
So MMQ is selected for PTQ1_0 only up to ne11 = 64. At prefill n = 512, which is the exact case this PR exists to fix and the one measured at 2.26x slower than PQ2_0,
This also reframes something I said in my earlier comment. I described the missing end-to-end prefill A/B as a rigour gap, and asked for the number without expecting it to change the verdict. That was too generous. A fresh prefill measurement on the current tree would have shown no improvement at n = 512, because the path is gated off. The absent measurement was concealing a functional gap rather than merely leaving a real win unquantified. Measuring first would have caught this before review did. Two ways forward, depending on why the bound exists. If 64 is a deliberate guard because the loader or the Ampere config is not yet correct or not yet faster above that, then say so in the code and in the description, and the PR is a decode-and-small-batch improvement rather than a prefill fix, which means the title and the stated goal need changing. If 64 was carried over from the dp4a threshold by analogy with Either way the gate should be settled by an end-to-end prefill A/B at n = 512 on the current tree, since that number is what the PR claims and it is currently unmeasured. |
Resolves the conflicts with PrismML-Eng#160 (PTQ1_0 mmvq multi-column path uses the compile-time gate flag) and PrismML-Eng#165 (gated_delta_net kernel carries both the raw-gates flag and the GB10 exp(g) precompute flag; the precompute only engages when raw gates are off).
Native MMQ support for PTQ1_0 on CUDA. Before this, PTQ1_0 had no entry in the quantized-matmul path, so large-batch matmul fell back to a full dequantize to fp16 followed by cuBLAS, measured at 2.26x slower than PQ2_0 at n=512 even though PTQ1_0 already led on decode.
What
Adds the PTQ1_0 MMQ tile loader, the type dispatch, and the
DECL_MMQ_CASEinstance so PTQ1_0 takes the same quantized GEMM path as the other low-bit types, plus a packed coalesced dequantize for the remaining conversion sites.Provenance
Ported from an internal handoff branch whose gates recorded 45/45 PTQ1_0 MUL_MAT on CUDA and an H100 perf pass, but that was against a pre-#152 base. Its Metal and FWHT parts are superseded by the already-landed Metal PTQ1_0 and FWHT work and are excluded; this is the CUDA MMQ half only, rebased onto current
prism.Verification (this PR, on an L40S, sm_89)
test-backend-ops test -o MUL_MAT -b CUDA0: PTQ1_0 45 OK, 0 FAIL. (33 further cases report not-supported; those are thetype_b=f16shapes, identical on the Metal backend, unrelated to this change.)test-backend-ops test -o MUL_MAT_ID -b CUDA0: PTQ1_0 75 OK, 0 FAIL.Correctness of the PTQ1_0 CUDA matmul and per-expert matmul is verified on current
prism.What is not in this PR
A fresh end-to-end prefill A/B was not re-measured on this run. The win is the removal of the dequantize-to-cuBLAS fallback by giving PTQ1_0 a real MMQ path; the prior handoff measured that on H100. A maintainer can take the current-tree prefill number, or I can on request. Decode is unchanged (this is the prefill/large-batch path).