Skip to content

gemma export debt: tied-embedding dedup (#260), true-dynamic with_past default (#248), shared-KV trace fidelity (#194) - #290

Merged
michalharakal merged 3 commits into
developfrom
feat/gemma-export-debt
Aug 11, 2026
Merged

michalharakal merged 3 commits into
developfrom
feat/gemma-export-debt

Conversation

@michalharakal

Copy link
Copy Markdown
Contributor

Three items of gemma export debt, one commit each, all verified against the real FunctionGemma-270M Q5_K_M checkpoint.

T2.3 — shared-KV cache trace fidelity (#194)

SharedPositionalKVCache, PaddedSharedPositionalKVCache and OwnerReadOnlyKVCache read K/V back through the delegate's heap buffer (writeAt/sliceView, plus padHeadDim's raw copyToFloatArray + ctx.fromData), bypassing ctx.ops. Under tracing that baked an all-zero KV buffer as stablehlo.constant and disconnected the computed k_proj/v_proj — the same SKaiNET#763 bug #193 fixed for PositionalKVCache — for any model with kvSharedLayers > 0.

Fix mirrors #193: the write-through variants get a ctx.isRecording branch that wires K/V functionally via ops.concat history; OwnerReadOnlyKVCache returns the owner's traced history (tracedKeys/tracedValues, now internal).

T2.1 — tied embedding exported twice (#260)

Gemma4WeightLoader's tied-output fallback re-read the token_embd bytes into a SECOND tensor for output.weight → two BufferHandles → the compiled export externalized the 262153x640 tied weight as two independent util.globals + two archive blobs (77% of the archive). Both GGUF paths (sequential + streaming) now alias the already-loaded embedding tensor into output.weight; the trace sees ONE weight and the export emits one @t0 global consumed by both the embedding gather and the lm-head transpose/matmul.

T2.2 — retire the SENTINEL_PAST=7919 hack (#248, item "Dynamic KV dims")

exportWithPast traced the dynamic KV graph at a sentinel prime (7919) and regex-relaxed x7919x/x7920xx?x in the emitted text. Engine 0.38+ traces a real Dim.DYNAMIC extent (previously opt-in via GEMMA_TRUE_DYNAMIC=1). True-dynamic is now the default; GEMMA_SENTINEL_PAST=1 rolls back to the legacy sentinel trace (relaxSeqDimToDynamic retained only for that rollback, marked LEGACY).

Verification

Task Check Result
T2.3 (#194) New KvCacheTraceFidelityTest.tracedKvSharedDecoderKeepsComputedKV (3-block sandwich Gemma, kvSharedLayers=1) green — fails on old code (zero-KV constants), passes with fix; no dense<0.0> KV constants, all projection dot_generals present
T2.3 (#194) KVCacheVariantsTest (eager shared-cache semantics) green
T2.1 (#260) FunctionGemmaExportTest w/ real GGUF: exactly ONE 262153x640 util.global in gemma-gen.mlir (new assertion) green — was 2 globals before the fix
T2.1 (#260) bf16 gemma.safetensors size 536,692,404 B (~512 MiB), down from 872,248,326 B (~832 MiB)
T2.1 (#260) deduped gemma-gen.mliriree-compile llvm-cpu vmfb compiles (skainet/iree-compiler:3.11.0)
T2.1 (#260) FunctionGemmaInt8QuantTest (int8 archive window re-based to the deduped ~512 MiB bf16 → ~257 MiB int8) green
T2.2 (#248) FunctionGemmaWithPastMlirDumpTest (default path now true-dynamic) green — 1x1x?x256 caches on inputs AND returns, zero sentinel dims leaked
T2.2 (#248) default-emitted gemma-with-past.mliriree-compile llvm-cpu vmfb compiles (skainet/iree-compiler:3.11.0)
T2.2 (#248) FunctionGemmaWithPastCpuTest (two-graph prefill+decode loop vs llama.cpp/board oracle) green — token-for-token
All :transformer-core / :llm-inference:gemma / :llm-runtime:kgemma full jvmTest + apiCheck (GEMMA_GGUF set) green

Closes #194, closes #260. #248 stays open — the bf16/int8 text rewrites and the forwardWithPast/refsFor items remain.

🤖 Generated with Claude Code

michalharakal and others added 3 commits August 11, 2026 07:56
The shared-cache variants (SharedPositionalKVCache,
PaddedSharedPositionalKVCache, OwnerReadOnlyKVCache) still read K/V back
through the delegate's heap buffer (writeAt/sliceView, plus padHeadDim's
raw copyToFloatArray + ctx.fromData), bypassing ctx.ops. Under tracing
(ctx.isRecording, embedConstants=true) that baked an all-zero KV buffer
as stablehlo.constant and disconnected the computed k_proj/v_proj —
the same SKaiNET#763 bug fixed for PositionalKVCache in #193 — for any
model with kvSharedLayers > 0.

Mirror the #193 fix:
- SharedPositionalKVCache / PaddedSharedPositionalKVCache get an
  ctx.isRecording branch that wires K/V functionally via ops.concat
  history (the padded write + slice-back read is an identity on the
  layer's own data, so no pad op is needed on the traced path).
- OwnerReadOnlyKVCache returns the owner PositionalKVCache's traced
  K/V history (tracedKeys/tracedValues now internal) so followers
  attend over the owner's computed projections.

Verified by a new KvCacheTraceFidelityTest case tracing a 3-block
sandwich-norm Gemma with kvSharedLayers = 1: fails on the old code
(zero KV constants), passes with the fix; no zero-KV stablehlo.constant
and all projection dot_generals present.

Closes #194

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…he embedding twice (#260)

When the checkpoint carries no explicit output.weight (tied embeddings,
as in FunctionGemma-270M), Gemma4WeightLoader's fallback re-read the
token_embd bytes into a SECOND tensor. Two tensors -> two BufferHandles
-> the compiled export externalized the 262153x640 tied weight as two
independent util.globals + two archive blobs: 77% of the weight archive
(832 MiB bf16 where ~512 MiB would do) and ~640 MiB extra per in-memory
copy during export.

Both GGUF paths (sequential + streaming) now retain the loaded
token_embd tensor and alias it (same Tensor, same BufferHandle) into
output.weight. The trace then sees ONE weight, and the export emits one
@t0 global consumed by both the embedding gather and the lm-head
transpose/matmul.

Verified with the real FunctionGemma-270M Q5_K_M checkpoint
(FunctionGemmaExportTest):
- exactly one 262153x640 util.global in gemma-gen.mlir (new assertion)
- bf16 gemma.safetensors: 536,692,404 bytes (~512 MiB), down from
  872,248,326 (~832 MiB)
- deduped gemma-gen.mlir still iree-compiles (llvm-cpu vmfb).

Closes #260

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…the SENTINEL_PAST=7919 hack (#248)

exportWithPast's dynamic KV graph was traced at a concrete sentinel
prime (7919) and then regex-relaxed (`x7919x`/`x7920x` -> `x?x`) in the
emitted StableHLO text — the most fragile of the MLIR-text rewrites
tracked in #248 (a magic prime that must never collide with a real dim
or SSA id). Engine 0.38+ traces a real `Dim.DYNAMIC` extent through
concat/reshape and emits dynamic_broadcast_in_dim, previously opt-in
via GEMMA_TRUE_DYNAMIC=1.

Flip the default: true-dynamic tracing is now the standard path and
needs no post-emit text rewrite. GEMMA_SENTINEL_PAST=1 rolls back to
the legacy sentinel trace (relaxSeqDimToDynamic retained only for that
rollback, marked LEGACY for removal with the remaining #248 items); the
env var is passed through the exportFunctionGemma task.

Verified with the real FunctionGemma-270M Q5_K_M checkpoint:
- FunctionGemmaWithPastMlirDumpTest green — default emit carries
  `1x1x?x256` caches on inputs AND returns, zero sentinel dims leaked
- emitted gemma-with-past.mlir compiles to a host vmfb
  (iree-compile --iree-input-type=stablehlo --iree-hal-target-backends=llvm-cpu)
- FunctionGemmaWithPastCpuTest (two-graph prefill+decode loop vs the
  llama.cpp/board oracle) green.

Refs #248

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@michalharakal
michalharakal merged commit 7ecce21 into develop Aug 11, 2026
2 checks passed
@michalharakal
michalharakal deleted the feat/gemma-export-debt branch August 11, 2026 07:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant