Skip to content

Commit ee10b3c

Browse files
committed
turboquant: support head_dim = any positive multiple of 128
Relaxes the head_dim == 128 hard guard in llama-kv-cache.cpp to a multiple-of-128 check. Qwen 3.5, Gemma, and other head_dim=256 families now load with -ctk turbo3 / -ctv turbo3 cleanly. The cpy.cu kernels need no change: ggml stores a 256-dim row as two back-to-back block_turbo3 entries (blck_size = 128 × type_size = 50 per block × 2 blocks/head). cpy_turbo3_f16_cuda dispatches one CUDA block per 128-element block, so a 256-dim head just gets two thread blocks per row instead of one. Validated on RTX 5080: - Qwen3.5-9B Q4_K_M (n_embd_head_k=256), -c 32768 -fa 1 FP16 KV: 7968 MiB TURBO3 KV: 7140 MiB (~830 MiB saved) - Qwen2.5-Coder-14B Q4_K_M (head_dim=128 baseline) still reproduces 15.3 GB FP16 vs 10.9 GB TURBO3, ~4.4 GB saved. Spec: doc/specs/2026-05-30-turboquant-kv-cache.md
1 parent 05bb5ae commit ee10b3c

1 file changed

Lines changed: 20 additions & 12 deletions

File tree

src/llama-kv-cache.cpp

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -96,28 +96,36 @@ llama_kv_cache::llama_kv_cache(
9696

9797
GGML_ASSERT(kv_size % n_pad == 0);
9898

99-
// TurboQuant requires head_dim == 128 (the FWHT block size, QK_TURBO).
100-
// Hard-fail at cache construction rather than at first attention call
101-
// so the agent sees a clear error from llama_new_context_with_model
102-
// instead of an opaque CUDA assert mid-token. See spec
103-
// doc/specs/2026-05-30-turboquant-kv-cache.md §3.3 for the rationale.
99+
// TurboQuant requires head_dim to be a positive multiple of QK_TURBO
100+
// (= 128, the FWHT block size). 128 covers Llama 3 / Qwen 2.5 / Mistral
101+
// 7B / mach-fund; 256 covers Qwen 3.5 and Gemma. The KV row is then
102+
// stored as head_dim/QK_TURBO consecutive turbo blocks per head — the
103+
// ggml type machinery handles this naturally because blck_size is 128
104+
// and ggml_row_size multiplies type_size by (n_per_row / blck_size).
105+
// The cpy.cu kernels run one CUDA thread block per 128-block so a
106+
// 256-dim head dispatches two thread blocks per row with no kernel
107+
// change. Hard-fail here at cache construction rather than at the first
108+
// attention call so the agent surfaces a clear error from
109+
// llama_new_context_with_model instead of an opaque CUDA assert. See
110+
// spec doc/specs/2026-05-30-turboquant-kv-cache.md §3.3.
104111
const bool type_k_is_turbo = (type_k == GGML_TYPE_TURBO3 || type_k == GGML_TYPE_TURBO2);
105112
const bool type_v_is_turbo = (type_v == GGML_TYPE_TURBO3 || type_v == GGML_TYPE_TURBO2);
106113
if (type_k_is_turbo || type_v_is_turbo) {
107-
// Walk all KV-bearing layers; reject if any has a non-128 head_dim.
108114
const uint32_t n_layer_kv_check = hparams.n_layer_kv();
109115
for (uint32_t il = 0; il < n_layer_kv_check; ++il) {
110116
const int32_t hd_k = (int32_t) hparams.n_embd_head_k(il);
111117
const int32_t hd_v = (int32_t) hparams.n_embd_head_v(il);
112-
if (type_k_is_turbo && hd_k != 128) {
118+
if (type_k_is_turbo && (hd_k <= 0 || hd_k % 128 != 0)) {
113119
throw std::runtime_error(
114-
"TurboQuant cache_type_k requires n_embd_head_k == 128; got "
115-
+ std::to_string(hd_k) + " on layer " + std::to_string(il));
120+
"TurboQuant cache_type_k requires n_embd_head_k to be a positive "
121+
"multiple of 128; got " + std::to_string(hd_k)
122+
+ " on layer " + std::to_string(il));
116123
}
117-
if (type_v_is_turbo && hd_v != 128) {
124+
if (type_v_is_turbo && (hd_v <= 0 || hd_v % 128 != 0)) {
118125
throw std::runtime_error(
119-
"TurboQuant cache_type_v requires n_embd_head_v == 128; got "
120-
+ std::to_string(hd_v) + " on layer " + std::to_string(il));
126+
"TurboQuant cache_type_v requires n_embd_head_v to be a positive "
127+
"multiple of 128; got " + std::to_string(hd_v)
128+
+ " on layer " + std::to_string(il));
121129
}
122130
}
123131
}

0 commit comments

Comments
 (0)