Skip to content

Commit c52aef5

Browse files
TheTomclaude
andcommitted
fix: restore inverse rotation in dequant — PPL 6.19 (1.2% of q8_0) #31 #30
ROOT CAUSE: pre-rotate-queries never executed because: 1. Q ne[0]=256 (GQA concatenated heads), rotation matrix ne[0]=128 2. mctx dynamic_cast failed for MoE hybrid memory FIX: put inverse WHT rotation back in dequantize_full_block. This is slower (10.7 tok/s vs 77.7) but produces CORRECT results. PERPLEXITY RESULTS: - f16: 6.121 - q8_0: 6.111 - q4_0: 6.142 - turbo3: 6.194 (+1.2% vs q8_0) ✅ The speed optimization (pre-rotate-queries) needs to be reimplemented to work with GQA head layout and hybrid memory types. Co-Authored-By: tturney@psyguard.ai Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent e20ce5d commit c52aef5

6 files changed

Lines changed: 58 additions & 17 deletions

File tree

ggml/src/ggml-metal/ggml-metal.metal

Lines changed: 29 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -514,36 +514,48 @@ void quantize_turbo4_0(device const float * src, device block_turbo4_0 & dst) {
514514
// up to 32× per block (once per 4-element chunk). We cache the full
515515
// dequantized block per thread and only recompute when the block pointer changes.
516516

517-
// turbo3 dequant — block size 32, same pattern as q4_0
518-
// Just 3-bit centroid lookup + norm scale. No rotation (pre-rotate-queries handles it).
519-
template <typename type4x4>
520-
void dequantize_turbo3_0(device const block_turbo3_0 * xb, short il, thread type4x4 & reg) {
517+
// turbo3 dequant — full block dequantize with inverse rotation
518+
// Must process all 128 elements to apply WHT inverse rotation
519+
static void turbo3_dequantize_full_block(device const block_turbo3_0 * xb, thread float * cache) {
521520
const float norm = float(xb->norm);
522-
// il selects which 16-element half (0 or 1) of the 32-element block
523-
const int offset = il * 16;
524521

525-
float4x4 reg_f;
526-
for (int i = 0; i < 16; i++) {
527-
int j = offset + i;
522+
// Unpack 3-bit indices → centroids (in rotated space)
523+
float recon[128];
524+
for (int j = 0; j < QK_TURBO3; j++) {
528525
uint8_t low2 = (xb->qs[j / 4] >> ((j % 4) * 2)) & 0x3;
529526
uint8_t hi1 = (xb->signs[j / 8] >> (j % 8)) & 0x1;
530527
uint8_t idx = low2 | (hi1 << 2);
531-
reg_f[i/4][i%4] = turbo_centroids_3bit[idx] * norm;
528+
recon[j] = turbo_centroids_3bit[idx];
529+
}
530+
531+
// Inverse WHT rotation: from rotated space back to original
532+
turbo_rotate_inverse(recon, turbo_wht_signs1, turbo_wht_signs2);
533+
534+
// Scale by norm to get back to original magnitude
535+
for (int j = 0; j < QK_TURBO3; j++) {
536+
cache[j] = recon[j] * norm;
537+
}
538+
}
539+
540+
template <typename type4x4>
541+
void dequantize_turbo3_0(device const block_turbo3_0 * xb, short il, thread type4x4 & reg) {
542+
float cache[128];
543+
turbo3_dequantize_full_block(xb, cache);
544+
const int offset = il * 16;
545+
float4x4 reg_f;
546+
for (int i = 0; i < 16; i++) {
547+
reg_f[i/4][i%4] = cache[offset + i];
532548
}
533549
reg = (type4x4) reg_f;
534550
}
535551

536552
template <typename type4>
537553
void dequantize_turbo3_0_t4(device const block_turbo3_0 * xb, short il, thread type4 & reg) {
538-
const float norm = float(xb->norm);
554+
float cache[128];
555+
turbo3_dequantize_full_block(xb, cache);
539556
const int offset = il * 4;
540-
541557
for (int i = 0; i < 4; i++) {
542-
int j = offset + i;
543-
uint8_t low2 = (xb->qs[j / 4] >> ((j % 4) * 2)) & 0x3;
544-
uint8_t hi1 = (xb->signs[j / 8] >> (j % 8)) & 0x1;
545-
uint8_t idx = low2 | (hi1 << 2);
546-
reg[i] = turbo_centroids_3bit[idx] * norm;
558+
reg[i] = cache[offset + i];
547559
}
548560
}
549561

src/llama-kv-cache.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2464,6 +2464,14 @@ ggml_tensor * llama_kv_cache_context::get_turbo_rotation_inv() const {
24642464
return kv->get_turbo_rotation_inv();
24652465
}
24662466

2467+
ggml_tensor * llama_kv_cache_context::get_turbo_rot_forward() const {
2468+
return kv->get_turbo_rotation();
2469+
}
2470+
2471+
ggml_tensor * llama_kv_cache_context::get_turbo_rot_inverse() const {
2472+
return kv->get_turbo_rotation_inv();
2473+
}
2474+
24672475
ggml_tensor * llama_kv_cache_context::cpy_k(ggml_context * ctx, ggml_tensor * k_cur, ggml_tensor * k_idxs, int32_t il) const {
24682476
return kv->cpy_k(ctx, k_cur, k_idxs, il, sinfos[i_cur]);
24692477
}

src/llama-kv-cache.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,10 @@ class llama_kv_cache_context : public llama_memory_context_i {
366366
ggml_tensor * get_turbo_rotation() const;
367367
ggml_tensor * get_turbo_rotation_inv() const;
368368

369+
// Override virtual methods from llama_memory_context_i
370+
ggml_tensor * get_turbo_rot_forward() const override;
371+
ggml_tensor * get_turbo_rot_inverse() const override;
372+
369373
// store k_cur and v_cur in the cache based on the provided head location
370374
// note: the heads in k_cur and v_cur should be laid out contiguously in memory
371375
// - k_cur [n_embd_head_k, n_head_k, n_tokens]

src/llama-memory-hybrid.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,14 @@ const llama_kv_cache_context * llama_memory_hybrid_context::get_attn() const {
263263
return static_cast<const llama_kv_cache_context *>(ctx_attn.get());
264264
}
265265

266+
ggml_tensor * llama_memory_hybrid_context::get_turbo_rot_forward() const {
267+
return ctx_attn ? ctx_attn->get_turbo_rot_forward() : nullptr;
268+
}
269+
270+
ggml_tensor * llama_memory_hybrid_context::get_turbo_rot_inverse() const {
271+
return ctx_attn ? ctx_attn->get_turbo_rot_inverse() : nullptr;
272+
}
273+
266274
const llama_memory_recurrent_context * llama_memory_hybrid_context::get_recr() const {
267275
return static_cast<const llama_memory_recurrent_context *>(ctx_recr.get());
268276
}

src/llama-memory-hybrid.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,10 @@ class llama_memory_hybrid_context : public llama_memory_context_i {
119119
llama_memory_status get_status() const override;
120120
const llama_ubatch & get_ubatch() const override;
121121

122+
// TurboQuant: delegate to the KV cache context
123+
ggml_tensor * get_turbo_rot_forward() const override;
124+
ggml_tensor * get_turbo_rot_inverse() const override;
125+
122126
//
123127
// llama_memory_hybrid_context
124128
//

src/llama-memory.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,11 @@ struct llama_memory_context_i {
5959

6060
// get the status of the memory context - used for error handling and checking if any updates would be applied
6161
virtual llama_memory_status get_status() const = 0;
62+
63+
// TurboQuant: get rotation tensors for pre-rotate-queries optimization
64+
// Returns null for non-turbo memory types. Override in KV cache contexts.
65+
virtual ggml_tensor * get_turbo_rot_forward() const { return nullptr; }
66+
virtual ggml_tensor * get_turbo_rot_inverse() const { return nullptr; }
6267
};
6368

6469
using llama_memory_context_ptr = std::unique_ptr<llama_memory_context_i>;

0 commit comments

Comments
 (0)