@@ -138,3 +138,72 @@ The block size 32 change + MSE-only + pre-rotate-queries may have introduced a b
1381384 . The non-vec flash attention instantiation might use wrong nl parameter
139139
140140MUST FIX before claiming any quality results.
141+
142+ ## Quality Bisection Log
143+
144+ Goal: find which change broke perplexity (165.6 vs 6.1 baseline).
145+
146+ Changes applied (in order):
147+ 1 . Pre-rotate-queries: rotation moved from dequant to Q in attention graph
148+ 2 . MSE-only: dropped QJL, 3-bit centroids (2-bit qs + 1-bit signs)
149+ 3 . Block size 32: storage blocks shrunk from 128 to 32 elements
150+
151+ Bisection plan:
152+ - [ ] Test A: revert block 32 → 128, keep MSE-only + pre-rotate
153+ - [ ] Test B: revert MSE-only → 2bit+QJL, keep block 128 + pre-rotate
154+ - [ ] Test C: revert pre-rotate → rotation in dequant, keep block 128 + 2bit+QJL
155+ - [ ] If A/B/C all still bad: check the rotation matrix consistency
156+
157+ ## Bisection Results
158+
159+ ### Test A: Disable Q rotation → PPL 165.6 (STILL BAD)
160+ Pre-rotate-queries is NOT the problem. Q rotation has no effect on perplexity.
161+
162+ ### Root Cause Found: V CACHE IN ROTATED SPACE
163+
164+ The bug: both K and V caches go through the same turbo3 quantize (which rotates)
165+ and dequant (which returns rotated values without inverse rotation).
166+
167+ For K cache: this is correct because Q is also rotated → <R* q, R* k> = <q, k> ✓
168+ For V cache: this is WRONG because the attention output = attn_weights @ V
169+ needs to be in ORIGINAL space, not rotated space.
170+
171+ The fix: V cache must NOT be rotated during quantize, or the attention output
172+ must be inverse-rotated after the V multiplication.
173+
174+ Options:
175+ 1 . Skip rotation for V in quantize — only rotate K
176+ 2 . Add inverse rotation after attention output: out = R^T @ (attn @ R* V) = attn @ V ✓
177+ 3 . Use f16 for V cache (only compress K) — mixed types need support
178+
179+ Option 1 is simplest: in the SET_ROWS kernel, check if this is K or V and
180+ skip rotation for V. The dequant already skips rotation.
181+ Problem: the SET_ROWS kernel doesn't know if it's writing K or V.
182+
183+ Option 2 is cleanest: add R^T multiplication to attention output in the graph.
184+ This is one ggml_mul_mat per layer (same as Q rotation). Cost is negligible.
185+
186+ ### Root Cause #1 : V cache dequant returns rotated-space values
187+ Python verified: cosine(input, dequant_output) = 0.02 (garbage)
188+ After inverse rotation: cosine = 0.987 (correct)
189+
190+ ### Root Cause #2 : dynamic_cast to llama_kv_cache_context FAILS
191+ The Qwen 3.5 MoE uses llama_memory_hybrid_context (not llama_kv_cache_context).
192+ The dynamic_cast returns null, so the V inverse rotation never executes.
193+ This also means the Q pre-rotation never executed — explaining why the
194+ chat server seemed to work (no rotation applied = raw quantize/dequant, which
195+ happens to produce plausible-looking text at low context but garbage perplexity).
196+
197+ ### Fix needed
198+ 1 . Store turbo rotation tensors in llm_graph_context (not KV cache)
199+ OR access them through the hybrid memory interface
200+ 2 . Apply V inverse rotation through a non-cast path
201+ 3 . Verify Q rotation also executes (it was also using the same cast)
202+
203+ ### The "coherent text" mystery explained
204+ The model produced grammatical text because:
205+ - Without Q rotation, attention scores are computed in wrong space but still produce
206+ some attention pattern (just not the right one)
207+ - Without V inverse rotation, the output is in rotated space which is orthogonal to
208+ the correct space — but each layer compounds the error
209+ - Short conversations look plausible but perplexity reveals the content is wrong
0 commit comments