Cache length after prefill (P=3): 3 Running 5 decode steps with cached-vs-uncached parity checks: step cache_len max|diff| parity_ok --------------------------------------------- 1 4 1.788e-07 True 2 5 1.788e-07 True 3 6 1.192e-07 True 4 7 2.384e-07 True 5 8 2.384e-07 True --------------------------------------------- All 5 steps within tolerance 1e-05: True PASS: cached and uncached next-token logits agree for every decode step. Cache length after prefill (P=3): 3 Per-step cache growth check (4 decode steps): step expected actual per_layer ok ------------------------------------------------- 1 4 4 True True 2 5 5 True True 3 6 6 True True 4 7 7 True True ------------------------------------------------- Final cache length: 7 (expected 7) All layers grow by exactly one column per step: True PASS: one KV append per layer per step; cache shape [B, H_kv, P+S, D_HEAD] confirmed. Raw KV-cache bytes from the lab model dimensions L (layers) = 2 H_q (Q heads) = 2 D_HEAD = 16 S (cached tok) = 64 B (batch) = 1 b (bytes/elem) = 2 (FP16) Variant H_kv Per-layer bytes Total (2 layers) Ratio vs MHA ---------------------------------------------------------------------- MHA 2 8,192 16,384 1.00 GQA 1 4,096 8,192 0.50 ---------------------------------------------------------------------- MHA: 8,192 bytes per layer; 16,384 bytes total across 2 layers GQA: 4,096 bytes per layer; 8,192 bytes total across 2 layers GQA figures are calculated from H_kv=1, not measured from an instantiated cache. GQA / MHA ratio: 0.50 (50% reduction) Note: cache-size ratios only. Total model memory is dominated by weights, and latency depends on kernel fusion, bandwidth, and compute vs memory bound. This is raw tensor bytes, not GPU allocator memory. PASS: per-layer and total byte figures are consistent. ============================================================ BROKEN cache-position case (new K/V overwrites slot 0) ============================================================ step 1: max|cached - uncached| = 1.654e-01 (tolerance 1e-05) -> MISMATCH (expected) step 2: max|cached - uncached| = 2.335e-01 (tolerance 1e-05) -> MISMATCH (expected) step 3: max|cached - uncached| = 2.875e-01 (tolerance 1e-05) -> MISMATCH (expected) All 3 steps show mismatch (expected for broken cache): True ============================================================ FIXED cache-position case (correct write at write_position) ============================================================ step 1: max|cached - uncached| = 2.384e-07 (tolerance 1e-05) -> OK step 2: max|cached - uncached| = 2.384e-07 (tolerance 1e-05) -> OK step 3: max|cached - uncached| = 2.384e-07 (tolerance 1e-05) -> OK All 3 steps within tolerance: True ============================================================ DIAGNOSIS AND REPAIR ============================================================ Bug: Each new K/V pair was written to cache slot 0 instead of being appended at write_position (the current cache length). The cache never grew, and the newest token overwrote the oldest entry, so later reads attended to a corrupted key/value history. Fix: Write new K/V at slot `k_cache.shape[2]` (exactly the current cache length), i.e. the next free position. After the fix, cached and uncached logits agree within FP32 tolerance. PASS: broken cache-position detected and repaired.