Current behavior
Parent: #138.
MMQ is gated on type at ggml/src/ggml-cuda/mmq.cu:269. TQ1_0 is not in the
accepted list and falls to default: mmq_supported = false at mmq.cu:298. There
is also no entry for the type in any of the per-architecture MMQ configuration
tables (mmq-config-*.cuh), and no tile loader for it in mmq.cuh.
So even with a dequantize kernel present, every batched matmul on TQ1_0 weights
goes through a full dequantize of the weight tensor to F16 followed by a cuBLAS
GEMM. That reads the weights, writes them out at 16 bits per value, and reads them
back, which throws away the entire point of a 1.6875 bpw format on a
bandwidth-bound prefill.
Measured impact
Internal measurement, large mixture-of-experts model, one NVIDIA data-center GPU,
llama-bench with r=3. With a dequantize kernel added and no MMQ path, pp512
goes from 36.75 to 207.06 (+/- 21.07) tokens per second. That is 5.6x better than
the CPU fallback and still about 13x short of the 2722.59 that a low-bit type with
a real MMQ tile loader reaches on the same model and GPU.
The 13x is the size of the prize here. It is not a tuning gap, it is the cost of
materializing F16 weights on every prefill.
Upstream state
No CUDA MMQ support for either ternary type exists upstream.
ggml-org#11183 is the reference implementation to work from: it adds
TQ2_0 mmvq, MMQ tile loading, an mma path and the dequantize-and-cuBLAS fallback,
with perplexity and throughput tables. It has been open since 2025-01-10 and
stalled on a profiling follow-up that was never finished; the author and the
reviewer both said as much on the thread in May 2026. Nothing blocks reusing its
structure. Note its caveat that the CUDA path uses a 32-element activation scale
against the CPU path's 256-element one, so generation quality can differ slightly
from CPU inference even when both are correct.
TQ1_0 was explicitly out of scope of ggml-org#11183, and its base-3 packing needs a
different tile loader from TQ2_0's flat 2-bit layout, so this cannot be a
copy and paste.
Proposed approach
Write an MMQ tile loader for TQ1_0 that unpacks a 256-element block into the shared
memory tile once, amortizing each packed byte across its five trits, and reuse the
existing int8 tile machinery from there. Because the format is symmetric ternary
with no zero point, the activation sum term can be dropped from the dot product.
Add per-architecture entries to the MMQ configuration tables, and add the type to
the ggml_cuda_should_use_mmq switch only once the loader exists, so the type
never advertises MMQ without a kernel behind it.
Sequencing note: this issue is worth doing even if the mat-vec issue stays
unresolved, because prefill and decode take independent paths. If decode ends up
parked on the dequantize-and-cuBLAS route as a stopgap, an MMQ loader still fixes
prefill on its own.
Acceptance criteria
test-backend-ops -b CUDA0 -o MUL_MAT passes for tq1_0 with the type
uncommented in all_types and base_types, exercised at batch sizes large
enough to select MMQ rather than the mat-vec path.
test-backend-ops -b CUDA0 -o MUL_MAT_ID passes for tq1_0 at batched shapes.
llama-bench pp512 on the same model and GPU: TQ1_0 within 25 percent of Q2_0.
- No dequantize-to-F16 of the weight tensor on the prefill path, confirmed by a
profile showing no full-tensor conversion kernel, not by reading the dispatch
code.
- Perplexity on TQ1_0 through CUDA within noise of TQ1_0 through CPU on the same
model, allowing for the activation scale granularity difference noted above.
- Other types' MMQ numbers unchanged, since the configuration tables and tile
machinery are shared.
Current behavior
Parent: #138.
MMQ is gated on type at
ggml/src/ggml-cuda/mmq.cu:269. TQ1_0 is not in theaccepted list and falls to
default: mmq_supported = falseatmmq.cu:298. Thereis also no entry for the type in any of the per-architecture MMQ configuration
tables (
mmq-config-*.cuh), and no tile loader for it inmmq.cuh.So even with a dequantize kernel present, every batched matmul on TQ1_0 weights
goes through a full dequantize of the weight tensor to F16 followed by a cuBLAS
GEMM. That reads the weights, writes them out at 16 bits per value, and reads them
back, which throws away the entire point of a 1.6875 bpw format on a
bandwidth-bound prefill.
Measured impact
Internal measurement, large mixture-of-experts model, one NVIDIA data-center GPU,
llama-benchwithr=3. With a dequantize kernel added and no MMQ path, pp512goes from 36.75 to 207.06 (+/- 21.07) tokens per second. That is 5.6x better than
the CPU fallback and still about 13x short of the 2722.59 that a low-bit type with
a real MMQ tile loader reaches on the same model and GPU.
The 13x is the size of the prize here. It is not a tuning gap, it is the cost of
materializing F16 weights on every prefill.
Upstream state
No CUDA MMQ support for either ternary type exists upstream.
ggml-org#11183 is the reference implementation to work from: it adds
TQ2_0 mmvq, MMQ tile loading, an mma path and the dequantize-and-cuBLAS fallback,
with perplexity and throughput tables. It has been open since 2025-01-10 and
stalled on a profiling follow-up that was never finished; the author and the
reviewer both said as much on the thread in May 2026. Nothing blocks reusing its
structure. Note its caveat that the CUDA path uses a 32-element activation scale
against the CPU path's 256-element one, so generation quality can differ slightly
from CPU inference even when both are correct.
TQ1_0 was explicitly out of scope of ggml-org#11183, and its base-3 packing needs a
different tile loader from TQ2_0's flat 2-bit layout, so this cannot be a
copy and paste.
Proposed approach
Write an MMQ tile loader for TQ1_0 that unpacks a 256-element block into the shared
memory tile once, amortizing each packed byte across its five trits, and reuse the
existing int8 tile machinery from there. Because the format is symmetric ternary
with no zero point, the activation sum term can be dropped from the dot product.
Add per-architecture entries to the MMQ configuration tables, and add the type to
the
ggml_cuda_should_use_mmqswitch only once the loader exists, so the typenever advertises MMQ without a kernel behind it.
Sequencing note: this issue is worth doing even if the mat-vec issue stays
unresolved, because prefill and decode take independent paths. If decode ends up
parked on the dequantize-and-cuBLAS route as a stopgap, an MMQ loader still fixes
prefill on its own.
Acceptance criteria
test-backend-ops -b CUDA0 -o MUL_MATpasses fortq1_0with the typeuncommented in
all_typesandbase_types, exercised at batch sizes largeenough to select MMQ rather than the mat-vec path.
test-backend-ops -b CUDA0 -o MUL_MAT_IDpasses fortq1_0at batched shapes.llama-benchpp512 on the same model and GPU: TQ1_0 within 25 percent of Q2_0.profile showing no full-tensor conversion kernel, not by reading the dispatch
code.
model, allowing for the activation scale granularity difference noted above.
machinery are shared.