Skip to content

Fix determinism leaks in TurboQuant KV cache and WHT state management - #281

Open
giveen wants to merge 3 commits into
TheTom:feature/turboquant-kv-cachefrom
giveen:fix/h1-h2-turbo-kv-determinism
Open

Fix determinism leaks in TurboQuant KV cache and WHT state management#281
giveen wants to merge 3 commits into
TheTom:feature/turboquant-kv-cachefrom
giveen:fix/h1-h2-turbo-kv-determinism

Conversation

@giveen

@giveen giveen commented Aug 8, 2026

Copy link
Copy Markdown

Fix determinism leaks in TurboQuant KV cache and WHT state management

Branch: fix/h1-h2-turbo-kv-determinism
Base: TurboQuant fork of llama.cpp (commit 27a68cf58)
Files changed: src/llama-kv-cache.cpp, ggml/src/ggml-turbo-quant.c, ggml/src/ggml-cpu/ops.cpp (+38 / -58)
Date: 2026-08-08
Models tested: Qwen3-8B-Q8_0.gguf (PPL/greedy), gemma-4-26B-A4B-it + MTP draft (Speculative Decoding)


Executive Summary

Verdict: NO REGRESSION — proven bit-identical on CPU and GPU, including MTP speculative decoding.

This PR addresses two distinct determinism and thread-safety hazards in the TurboQuant path (+38/-58, net negative). All four validation layers confirm zero regression: full test suite green, CPU bit-identical, GPU bit-identical, and MTP acceptance identical.

(An early GPU "regression" featuring PPL deltas and a SOFT_MAX crash was root-caused to a scratch build linking distro CUDA 12.4 libraries instead of the CUDA 13.3 toolkit. When built against identical toolchains, baseline and feature binaries match identically across all benchmarks.)


1. Description of Fixes

1.1 H1 — KV cache layer-adaptive policy leak (src/llama-kv-cache.cpp)

Bug: The TURBO_LAYER_ADAPTIVE mode resolution was implemented as a static const lambda declared inside the per-layer constructor loop of llama_kv_cache::llama_kv_cache. Because C++ initializes static locals on first execution, the first cache instance's resolved policy was silently reused by every subsequent cache instance in the process.

This impacted core scenarios targeted by this fork:

  • MTP / Speculative Decoding: Target and draft caches are constructed sequentially; the second cache inherited the first's mode even when its own cache types demanded a different resolution (e.g., a turbo2-V draft cache losing its auto-enabled Boundary V policy).
  • Multi-model / Multi-context servers: Any secondary context inherited the policy computed for the first.

Fix: Hoisted policy resolution to the constructor scope as a per-instance const int adaptive_mode, computed once against that instance's own environment and types.

Design Note: Shared (MTP draft) caches explicitly resolve their own policy from their own types; the mode is not inherited from the parent cache (deliberately distinct from attn_rot, which does inherit).

1.2 H2 — WHT group-size global state (ggml-turbo-quant.c + ggml-cpu/ops.cpp)

Bug: A process-global int turbo3_cpu_wht_group_size was defined in ggml-turbo-quant.c (libggml-base) and extern-declared in ops.cpp (libggml-cpu). The CPU SET_ROWS handler wrote the WHT group size into this global as a side effect, which the turbo2_0/turbo3_0 quantization functions later read.

This introduced two primary hazards:

  1. Cross-library shared mutable state: Symbol interposition behavior varies across platforms. ELF may unify them, but macOS (two-level namespaces) and DLL targets will not—making group-size propagation silently link-order and platform dependent.
  2. Hidden execution order dependency: Quantization correctness implicitly relied on the SET_ROWS write path executing beforehand, creating a non-thread-safe side channel by design.

Fix: Removed the global entirely and replaced it with a pure function turbo_wht_group_size(k) inside the codec. It derives the group size deterministically from the row length: 128 if k % 128 == 0 else 64. Removed the SET_ROWS side-channel write. This selection strictly matches the graph-side inverse-WHT group selection in llama-graph.cpp.


2. Validation & Verification

2.1 Unit and Integration Test Gates

Gate Result Notes
ctest 56/57 PASS test-tokenizers-ggml-vocabs failed due to environment LFS fetch issue (unrelated)
test-backend-ops PASS CUDA0: 21446/21446 passed (/tmp/backend-ops-final.log)
test-turbo-quant PASS Codec round-trip MSE/cosine gates
test-quantize-fns PASS TQ3_1S/TQ4_1S + rotated-domain sizing
test-chat PASS All standard functional checks

2.2 CPU Determinism (Gold-Standard Gate)

Tested via llama-perplexity with Qwen3-8B-Q8_0.gguf on CPU (-t 1, --temp 0):

  • PPL: Bit-identical on all 6 KV cache configurations baseline vs. feature.
  • Greedy Generation: Token-exact MD5 hashes across all configurations.

2.3 GPU Parity (CUDA 13.3 Toolkit)

Tested via llama-perplexity with Qwen3-8B-Q8_0.gguf (-ngl 99, 4 chunks):

K/V Config Baseline PPL Feature PPL Match?
f16 / f16 1.3254 ± 0.06038 1.3254 ± 0.06038 Exact
q8_0 / q8_0 1.3288 ± 0.05980 1.3288 ± 0.05980 Exact
turbo2 / turbo2 4.5732 ± 0.29883 4.5732 ± 0.29883 Exact
turbo3 / turbo3 2.2768 ± 0.13339 2.2768 ± 0.13339 Exact
turbo4 / turbo4 1.7355 ± 0.09365 1.7355 ± 0.09365 Exact
q8_0 / turbo3 1.3138 ± 0.05683 1.3138 ± 0.05683 Exact

2.4 MTP / Speculative Decoding

Tested with gemma-4-26B-A4B-it + MTP draft (--spec-type draft-mtp, q8_0/turbo2 on both caches, 256 tokens):

Metric Baseline Feature Status
Draft Acceptance 0.79365 (50/63) 0.79365 (50/63) Identical
MTP Execution Active Active No crashes

3. Toolchain & Build Requirements

When building scratch binaries for A/B testing on systems with distro-provided CUDA 12.x packages, explicitly pass -DCUDAToolkit_ROOT=/usr/local/cuda. Failing to pass this flag allows CMake to silently resolve system CUDA 12.4 runtime libraries instead of the CUDA 13.3 toolkit, leading to cuBLAS numerical shifts and potential SOFT_MAX kernel failure artifacts.


4. Diff Summary

  • src/llama-kv-cache.cpp (+28/-24): Removed static const int adaptive_mode lambda inside the per-layer loop. Added per-instance const int adaptive_mode resolution at constructor top level.
  • ggml/src/ggml-turbo-quant.c (+10/-16): Removed GGML_API int turbo3_cpu_wht_group_size. Added static int turbo_wht_group_size(int64_t k). Updated quantizers to call pure function.
  • ggml/src/ggml-cpu/ops.cpp (0/-17): Removed extern "C" declaration block and SET_ROWS side-channel global write.

AI Assisted: Yes

@github-actions github-actions Bot added documentation Improvements or additions to documentation ggml labels Aug 8, 2026
@lxdlam

lxdlam commented Aug 11, 2026

Copy link
Copy Markdown

Vote for this, and it may close #282.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation ggml

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants