Skip to content

feat(gemma3n): StableHLO export harness (PLE as graph input) + gemma3n/hybrid-AI docs - #394

Merged
michalharakal merged 2 commits into
developfrom
feat/gemma3n-iree-export
Sep 2, 2026
Merged

michalharakal merged 2 commits into
developfrom
feat/gemma3n-iree-export

Conversation

@michalharakal

Copy link
Copy Markdown
Contributor

Stacked on #393 (the gemma3n DSL lane). The mobile leg of #377, plus the docs.

Export harness — exportGemma3n

SmolLM2/FunctionGemma redecode pattern: trace gemma3nNetwork() → StableHLO, bf16 external params, in-graph argMax tail, manifest.json. The mobile-honest design decision, per the "why FP32 on Android?" review comment:

  • The PLE table never enters the accelerator parameters. per_layer_inputs [1, seq, L, 256] is the graph's second input, computed on the CPU from the packed PLE table at runtime — exactly PLE's design point in Google's Gemma 3n guide (per-layer embeddings live off-accelerator). The archive carries trunk + token embedding only; params emit bf16 (FP32 exists only as a transient host-side form during export; int8 is a mechanical port of FunctionGemma's rewriteGlobalsToInt8, tracked).
  • PerLayerEmbedding gains a traceable indexSelect path under ctx.isRecording; Gemma3nModel.externalPerLayerInputs is the injection point; AltUp narrows are rank-general for the trace's [1, seq, H] working rank.
  • GEMMA3N_LAYERS=N truncates the trunk for end-to-end pipeline verification on smaller hosts (PLE coverage stays full-width so tensors bind shape-exact).

Blocked upstream — honestly

Full E2B emission hits two engine defects, filed + precisely characterized in SKaiNET#1247:

  1. VoidTensorOps materializes real zero buffers per traced op and constant extraction duplicates every weight — dense originals + retained zeros + graph copies OOM a 46 GB heap on a 48 GB host.
  2. The HLO converter drops the embedding-gather's weight operand on this trace shape and cascades to a compute-free module that exits 0 (failures only as MLIR comments).

The harness turns both traps into hard errors: a function-signature arity check (weights leaking as args = the packed-tensor mode) and an emission-completeness check (converter failure comments / missing outputs). The moment the engine lands fixes, exportGemma3n produces the servable artifact with no further transformers changes expected.

Docs

  • explanation/gemma3n.adoc — why Gemma 3n (MatFormer, PLE, KV sharing, per Google's developer guide) and why SKaiNET fits it: one DSL definition serving eager + compiled, packed/MAPPED loading, per-layer heterogeneity as a plain Kotlin loop, parity-gated faithfulness. In the nav.
  • docs/specs/matformer-hybrid-on-device-ai.md — pre-PRD design note: MatFormer slice-view design for SKaiNET (prefix weight views, slice configs, per-slice vmfbs over one parameter archive) and the hybrid on-device/cloud architecture (draft-first + escalate-on-evidence, L0–L3 routing layers, stream-buffer seamlessness), with papers and reference implementations.
  • README + antora index: gemma3n row flips to verified; dsl-vs-handcoded updated.

Verification

  • Eager parity gate re-verified green after every change on this branch.
  • gemma3n/gemma jvmTest + apiCheck green (dumps regenerated).
  • Truncated export exercised end-to-end up to the engine converter defect; both new guards fire correctly on the defective outputs they were built for.

🤖 Generated with Claude Code

michalharakal and others added 2 commits September 2, 2026 09:02
…d vs llama.cpp (#377)

Gemma 3n gets a proper DSL definition and stops depending on the
hand-rolled Gemma3nRuntime for GGUF inference. The recon showed that
runtime was never faithful to real checkpoints: it loaded the PLE
tensors but never applied them, had no Laurel block, ignored the AltUp
modality router (host-side coefficient math), and its E2B_DEFAULT
claimed AltUp/sparsity are E4B-only — the real gemma-3n-E2B GGUF has
altup.num_inputs=4 and first-10-layer activation sparsity. Gate was
0/5 for a reason.

The new lane, faithful to HF modeling_gemma3n.py (read from source):

- Gemma3nAltUpBlock: predict/correct with the tanh modality router
  (router_norm * 1/hidden -> modality_router -> tanh), per-layer
  coefficient linears, correct_output_scale.
- Gemma3nAltUpGlobals: stream init/merge over the GGUF's 3D
  altup_proj/altup_unembd_proj stacks with per-token magnitude
  renormalization (the ggml ne-order vs row-major reinterpret is
  handled by reshaping to [numExtra, H, H] before slicing).
- Gemma3nLaurelBlock; Gemma3nSparseGeGluFFN (in-graph Gaussian-top-k,
  population std, per-layer std multipliers from the GGUF, -inf = off);
  Gemma3nPerLayerApply (PLE delta added to the NON-active streams,
  reusing gemma's PerLayerEmbedding whose math is identical to HF).
- gemma3nNetwork() + Gemma3nModel: GemmaModel-pattern orchestrator
  threading 4 AltUp streams per layer (predict -> attn+Laurel with
  1/sqrt2 gating -> sparse FFN -> correct -> PLE), per-type shared KV
  for the last 10 layers via OwnerReadOnlyKVCache, hybrid
  sliding/global attention with dual RoPE bases, qk-norm +
  parameterless v-norm, attention scale 1.0. Everything through
  ctx.ops — traceable for the StableHLO/IREE mobile path (next PR).
- Metadata/parser now read the real llama.cpp GGUF keys:
  sliding_window_pattern booleans, per-layer activation_sparsity_scale
  (1.6449/-inf encoding), rope.freq_base fallback (1M global, 10k SWA
  default), rms_norm_eps, per-layer feed_forward_length.
- Gemma3nNetworkLoader: engine loading stays packed/MAPPED (PLE table
  row-dequants on demand), strict binding both directions.
- Routing: kgemma GEMMA3N/GGUF and skainet-cli gemma3n now run the DSL
  lane; SafeTensors stays on the legacy runtime until the DSL grows
  that leg.

Gate (the last ungated generative family): Gemma3nGoldenTokenParityTest
asserts full 32-step greedy text equality vs mainline llama.cpp b10621
on gemma-3n-E2B-it-Q4_K_M — GREEN. Fixture prompt chosen for greedy
decisiveness (min top-1/top-2 margin 1.77 nats; "The capital of France
is" hits a measured 0.12-nat tie at step 3 that Q4_K noise flips).
Wired into smoke-reference (gemma3n_gguf_url + 20g heap arg);
smoke-models.json gains a Gemma3n-E2B row. Both CLIs decode
" 2, 3, 5, 7, ..." end-to-end.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…n/hybrid-AI docs

The mobile leg of the #377 DSL arc. Gemma3nExportHarness + exportGemma3n
(SmolLM2/FunctionGemma redecode pattern): trace gemma3nNetwork() ->
StableHLO, bf16 external params, in-graph argMax tail, manifest.

The mobile-honest design decision: per_layer_inputs is a graph INPUT
([1, seq, L, pleDim], computed on the CPU from the packed PLE table at
runtime) instead of baking the 262k x 7680 table into the accelerator
parameters. That is PLE's design point per Google's Gemma 3n guide -
per-layer embeddings live off-accelerator - and it keeps the archive
trunk-only. PerLayerEmbedding gained a traceable indexSelect path while
ctx.isRecording (the eager packed row-dequant gather is host-side and
would bake constants); Gemma3nModel.externalPerLayerInputs is the
injection point; AltUp's coefficient narrows are rank-general so the
trace's [1, seq, H] working rank flows through.

Full E2B emission is BLOCKED on engine SKaiNET#1247, precisely
characterized there: (a) VoidTensorOps materializes real zero buffers
per traced op and constant extraction duplicates every weight - dense
originals + zeros + copies OOM a 46 GB heap on a 48 GB host; (b) the
HLO converter drops the embedding-gather's weight operand on this trace
shape and cascades to a compute-free module while exiting 0. The
harness now hard-fails on both: a signature arity check (weights leaked
as function args = the packed-tensor mode) and an emission-completeness
check (converter failure comments / empty return). GEMMA3N_LAYERS=N
truncates the trunk (metadata + tensor filter, PLE table coverage kept
full-width) so the whole pipeline is verifiable on smaller hosts the
moment the engine lands fixes.

Docs: explanation/gemma3n.adoc - why Gemma 3n's mobile-first
architecture (MatFormer, PLE, KV sharing per Google's developer guide)
and why SKaiNET fits it (one DSL definition serving eager + compiled,
packed/MAPPED loading, per-layer heterogeneity, parity-gated
faithfulness); wired into nav. README + index verified-matrix rows flip
gemma3n to verified. docs/specs/matformer-hybrid-on-device-ai.md -
pre-PRD design note: MatFormer slice-view design for SKaiNET and the
hybrid on-device/cloud architecture (draft-first, escalate-on-evidence,
L0-L3 routing layers, stream-buffer seamlessness), with paper and
reference-implementation links.

Eager lane unaffected: parity gate re-verified green after the
changes; gemma3n/gemma suites + apiCheck green (dumps regenerated).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@michalharakal
michalharakal merged commit 9c806bb into develop Sep 2, 2026
4 checks passed
@michalharakal
michalharakal deleted the feat/gemma3n-iree-export branch September 2, 2026 08:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant