Offer a half-precision KV cache behind a flag (default off) - #109
Merged
Conversation
llama.cpp holds cache_k and cache_v as F16, so attention there reads back
rounded keys and values. This runtime kept them F32, making it more
precise than the reference rather than equivalent to it.
Localized by replaying layer 0 of Qwen3-Embedding-0.6B Q6_K op by op
against llama-eval-callback. Every matmul already matched; the first
divergence was the attention output projection, whose only differing
input was V. Rounding V to F16 reproduces llama.cpp:
F32 V F16 V llama.cpp
node_27 (attn_out) 0.635219 0.619453 0.619455
l_out-0 -7.648265 -7.676750 -7.676746
Agreement against llama.cpp 6ea215d17, minimum cosine over the probe set:
Qwen3-Embedding-0.6B Q8_0 0.9995014 -> 0.9996100
E5-Mistral-7B Q4_K_M 0.9995117 -> 0.9994905
Qwen3-Embedding-0.6B Q4_K_M 0.9969991 -> 0.9977295
Qwen3-Embedding-0.6B Q5_K_M 0.9969529 -> 0.9972010
Qwen3-Embedding-0.6B Q6_K 0.9969038 -> 0.9970416
Both published embedding artifacts still reproduce, and the qwen3 Q8_0
one improves. Greedy generation is byte-identical on the published
qwen3_0_6b_q4_0 across 48 tokens, so no recorded token reference moves.
This does not by itself lift the qwen3 K-quant variants over the 0.999
floor; a second source of divergence remains, tracked separately.
llama.cpp holds cache_k and cache_v as F16, so attention there reads back
rounded keys and values. This runtime keeps F32, making it more precise
than the reference rather than equivalent to it.
Localized by replaying layer 0 of Qwen3-Embedding-0.6B Q6_K op by op
against llama-eval-callback. Every matmul already matched; the first
divergence was the attention output projection, whose only differing
input was V. Rounding V to F16 reproduces llama.cpp:
F32 V F16 V llama.cpp
node_27 (attn_out) 0.635219 0.619453 0.619455
l_out-0 -7.648265 -7.676750 -7.676746
Enabling it raises agreement on every embedding artifact measured:
Qwen3-Embedding-0.6B Q8_0 0.9995014 -> 0.9996100
Qwen3-Embedding-0.6B Q4_K_M 0.9969991 -> 0.9977295
Qwen3-Embedding-0.6B Q6_K 0.9969038 -> 0.9970416
It is off by default because two llama.cpp equivalence references
disagree about it: rounding reproduces Qwen3 layer output exactly, but
flips one greedy token on the pinned SQLCoder Q5_K_M fixture, which
asserts token equality against llama.cpp b9960.
actual [13, 917, 29901, 4576]
expected [13, 917, 29901, 274]
Default behaviour is therefore unchanged and no recorded evidence moves.
Select it with -Dmodels.purejava.halfPrecisionKvCache=true.
This does not on its own lift the qwen3 K-quant variants over the 0.999
floor; a second source of divergence remains.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
llama.cpp holds
cache_k/cache_vas F16, so attention there reads back rounded keys and values. This runtime keeps F32 — more precise than the reference rather than equivalent to it.How it was localized
Replayed layer 0 of Qwen3-Embedding-0.6B Q6_K op by op against
llama-eval-callback. Every matmul already matched — Q6_K dequant is bit-exact, q8_K activation quantization matches-127.f/maxwith upper-only clamp, and the Q6_K×Q8_K dot matches to 3e-8 at both cols=1024 and cols=2048.The first divergence was
node_27, the attention output projection. Its only differing input was V:node_27l_out-0Why it is off by default
Two llama.cpp equivalence references disagree about this change.
Rounding reproduces Qwen3 layer output exactly and raises embedding agreement everywhere measured:
But it flips one greedy token on the pinned SQLCoder Q5_K_M fixture, which asserts token equality against llama.cpp b9960:
Since one llama.cpp reference says the rounding is right and another says it is wrong, it cannot be the default until that is resolved. Default behaviour is unchanged, so no recorded evidence moves and the full slow-model suite is unaffected. Select it with
-Dmodels.purejava.halfPrecisionKvCache=true.Scope
This does not on its own lift the qwen3 K-quant variants over the 0.999 floor — 0.99773 is closer but still short. A second source remains, tracked in the follow-up task.
Separately, this work found that
:models-bench:rundoes not forward arbitrary-Dsystem properties (onlymodels.native.kernels.library), so any tuning-flag experiment run through Gradlerunsilently had no effect. The toggle sweep was re-done with directjava -cpinvocation; the conclusions held, but the earlier method was invalid.🤖 Generated with Claude Code