Gemma 4 cutover + multi-backend libelizainference — living plan
Status doc for the Gemma 4 cutover campaign. Supersedes the research issue
#8794 (Gemma is real + shipped; the open questions there are answered below).
Continuously updated as milestones land.
Goal
Cut eliza-1 over from the Qwen3.5/3.6 backbone to Gemma 4 entirely
(remove Qwen), and make every platform run the fastest in-process backend
behind one FFI pipe (libelizainference):
| Backend |
Where |
§11 status |
| llama.cpp |
everywhere (CPU / CUDA / Vulkan-Mali-Adreno / Metal) |
owned pipe (today) |
| LiteRT-LM (compiled-in) |
Android NPU (Tensor / Qualcomm QNN / MediaTek NeuroPilot), opt. desktop/iOS GPU |
owned pipe ✅ |
| CoreML / MLX (compiled-in) |
Apple (mac first, iOS later) |
owned pipe ✅ |
| AICore / Apple Foundation |
opportunistic fast-path |
external (not owned) |
§11 ("single on-device runtime") is reinterpreted from "llama.cpp-only" to
"one managed library, one pipe, no sidecar/subprocess/TCP." LiteRT-LM and
MLX are embeddable in-process C++ libraries → linked into libelizainference,
exposing the same FFI streaming symbols FfiStreamingRunner already drives.
AICore is an out-of-process Android system service (Binder IPC) → it stays
an opportunistic adapter like apple-foundation.ts, never the owned backend.
Gemma 4 — validated facts (2026)
Shipped 2026-04-02 (MTP drafters 04-16, 12B Unified 06-03).
- Sizes: E2B (~2.3B eff / 5.1B w/ embeddings), E4B (~4.5B / 8B), 12B Unified
(11.95B dense, encoder-free, 256K ctx, 262,144 vocab), 31B dense, 26B-A4B
(MoE, 4B active). E2B ≈ 1.3 GB Q4, 2–3 GB RAM → mobile tier.
- Architecture: dense; alternating local sliding-window (512/1024) + global
full-attention; dual RoPE; Per-Layer Embeddings (PLE); shared KV cache
(last N layers reuse earlier KV). No SSM/Gated-DeltaNet. Dual head dims
(global_head_dim + SWA head_dim).
- MTP: official separate drafter models (speculative decode ~3×, no quality
loss). Our catalog's drafterFile? already supports separate-drafter MTP.
- Modalities: image + audio (USM conformer) + video.
- NPU fit: dense graphs are NPU-delegate-friendly; LiteRT-LM ships
pre-converted Gemma 4 .litertlm bundles. Qwen3.5-hybrid is hostile to NPU
delegates — the strongest technical reason to cut over.
- Our llama.cpp fork already speaks Gemma 4:
LLM_ARCH_GEMMA4 + gemma4.cpp
/ gemma4a.cpp (audio) / gemma4v.cpp (vision) + converter (text/MoE/vision/
audio). It already implements SWA (LLAMA_SWA_TYPE_STANDARD), shared KV
(n_kv_shared_layers → n_layer_kv_from_start reuse cb), PLE
(per_layer_tok_embd), and interleaved-SWA KV (llama_kv_cache_iswa).
RAM / performance: llama.cpp is not optimal for Gemma out of the box
Gemma 4 is notorious for RAM blow-up in stock llama.cpp. Root causes + our levers
(several are config, not new kernels — "Gemma already does a lot of this"):
- KV-cache layout (the ~40% fix) — landed upstream ~2026-04-05 (align KV
memory layout to Gemma's MHA/grouping). Our merge-base is 2026-05-14, so we
likely already have it; the M1 sync to upstream master confirms + grabs the
rest.
ctx-checkpoints accumulation (#21690) — Gemma-only: server KV
checkpoints grow unbounded (64 GB filled in 4–5 × 16K-prompt). Fix: bound
ctx-checkpoints (1/0) + -np 1 in our fused defaults. Pure config.
- Windowed SWA KV (
swa_full=false) — llama_kv_cache_iswa already sizes
SWA layers to n_swa (512/1024) + n_ubatch when swa_full=false. Most layers
are SWA, so this is the dominant KV saving. Default swa_full=false (it's
already plumbed through capacitor-llama/types.ts + the iOS shim).
- PLE memory —
per_layer_tok_embd is {n_embd_per_layer·n_layer, n_vocab}
(~2.8B params for E2B). With mmap ON the OS pages it from disk (≈ LiteRT's
"mmap embeddings, keep on disk until needed"). Never force --no-mmap for
Gemma; on GPU backends, pin PLE to CPU/mmap rather than copying it to
VRAM. (Watch Vulkan #18317: Vulkan can't run mmap=0.)
- Prefix-KV reuse caveat (#21468) — Gemma's shared-KV layers log "cache reuse
not supported" even with -fa --swa-full. So prefix reuse is partial on
shared-KV layers — better than Qwen3.5 (which can't at all) but not free. Our
stream_reset_keep work must account for this.
Leverage Google's C++ work, don't reinvent: LiteRT-LM already does
PLE-mmap + windowed-SWA + NPU optimally → use it as the in-process backend on
capable hardware. For llama.cpp, absorb upstream Gemma memory PRs and set
Gemma-aware defaults rather than porting our Qwen kernels blindly.
Where our kernels still add value (M6), re-scoped:
- TurboQuant low-precision weight quant — orthogonal to KV; applies to the
dense FFN/attention weights. Keep.
- QJL/PolarQuant KV quant — now applied on top of the windowed SWA KV +
the global-attention KV (where 256K context still hurts). Re-parameterize from
uniform head_dim=128 to Gemma's dual dims (global ~256 + SWA). Validate it
still wins after SWA+shared-KV already shrank the cache.
- Re-run the §8 8/8 kernel verify matrix per buildable backend for the new
geometry.
Frozen contracts re-opened (owner-approved 2026-06-22)
- Training base lock (
model_registry.py, training/AGENTS.md) — Qwen → Gemma 4.
tokenizerFamily "qwen35"→"gemma4"; vocab 248,320 → 262,144 (memory_calc.py).
- KV geometry + kernels — head_dim 128 uniform → Gemma dual dims.
- Same-file MTP NextN head → Gemma separate drafter GGUF.
- EOT
<|im_end|> → <end_of_turn> (3 scorer files).
- Abliteration Gated-DeltaNet surgery → dense surgery.
native/AGENTS.md §11 reinterpreted (single lib + single pipe).
eliza-1 v1 is base-not-fine-tuned (releaseState=base-v1) → v1 cutover is
swap base + re-optimize, not a retraining run.
Milestones (PR per milestone → develop)
- M1 — llama.cpp upstream sync + PR absorption. Merge ggml-org/master (602
commits ahead of our base; we are 867 ahead with kernels/voice) into the fork;
review + absorb all relevant Gemma/MTP/LiteRT/CoreML/AICore/KV/quant PRs (incl.
open #21587 Gemma4 BPE SIGSEGV, #24590 Gemma4Assistant memory-fit, #21690
ctx-checkpoints, #21468 cache-reuse). Build + verify Gemma 4 E2B/E4B
text+vision+audio+MTP on CPU/CUDA.
- M2 — Code cutover Qwen→Gemma (registry, catalog/types, memory_calc, EOT,
abliterate, AGENTS; remove Qwen; tier map E2B/E4B/12B/31B(/26B-A4B); Gemma-aware
runtime defaults: swa_full=false, bounded ctx-checkpoints, mmap-on/PLE-on-CPU).
- M3 — Multi-backend FFI seam (backend abstraction +
backend-selector).
- M4 — LiteRT-LM in-process backend (Android NPU delegate ladder).
- M5 — CoreML/MLX in-process backend (mac first, iOS later).
- M6 — Kernel re-optimization for Gemma geometry (TurboQuant weight-quant +
QJL/Polar KV-quant on windowed/global KV) across CPU/CUDA/Vulkan-Mali/Metal/NPU
- low-precision + long context; re-verify 8/8.
- M7 — Verification everywhere (web / desktop app / Pixel / bench harnesses +
PR_EVIDENCE).
What's verifiable in-session vs needs hardware
- Here (Linux x64 + CUDA): llama.cpp merge + CPU/CUDA builds; Gemma 4
text/vision/audio/MTP gen; code cutover; FFI seam; bench (llama-bench,
e2e_loop_bench) on CPU/CUDA; web + desktop-app smoke.
- Needs Mac: Metal kernels, CoreML/MLX backends, iOS.
- Needs Pixel/Android device: Vulkan-Mali kernels, LiteRT NPU (Tensor/QNN),
on-device tok/s + RSS. (Prior on-device work used adb/CDP on a Pixel 9a.)
Hardware-gated items are scoped + scaffolded here and marked for device
verification; nothing is claimed "verified" without the evidence.
Acceptance criteria
Gemma 4 cutover + multi-backend
libelizainference— living planGoal
Cut eliza-1 over from the Qwen3.5/3.6 backbone to Gemma 4 entirely
(remove Qwen), and make every platform run the fastest in-process backend
behind one FFI pipe (
libelizainference):§11 ("single on-device runtime") is reinterpreted from "llama.cpp-only" to
"one managed library, one pipe, no sidecar/subprocess/TCP." LiteRT-LM and
MLX are embeddable in-process C++ libraries → linked into
libelizainference,exposing the same FFI streaming symbols
FfiStreamingRunneralready drives.AICore is an out-of-process Android system service (Binder IPC) → it stays
an opportunistic adapter like
apple-foundation.ts, never the owned backend.Gemma 4 — validated facts (2026)
Shipped 2026-04-02 (MTP drafters 04-16, 12B Unified 06-03).
(11.95B dense, encoder-free, 256K ctx, 262,144 vocab), 31B dense, 26B-A4B
(MoE, 4B active). E2B ≈ 1.3 GB Q4, 2–3 GB RAM → mobile tier.
full-attention; dual RoPE; Per-Layer Embeddings (PLE); shared KV cache
(last N layers reuse earlier KV). No SSM/Gated-DeltaNet. Dual head dims
(
global_head_dim+ SWAhead_dim).loss). Our catalog's
drafterFile?already supports separate-drafter MTP.pre-converted Gemma 4
.litertlmbundles. Qwen3.5-hybrid is hostile to NPUdelegates — the strongest technical reason to cut over.
LLM_ARCH_GEMMA4+gemma4.cpp/
gemma4a.cpp(audio) /gemma4v.cpp(vision) + converter (text/MoE/vision/audio). It already implements SWA (
LLAMA_SWA_TYPE_STANDARD), shared KV(
n_kv_shared_layers→n_layer_kv_from_startreuse cb), PLE(
per_layer_tok_embd), and interleaved-SWA KV (llama_kv_cache_iswa).RAM / performance: llama.cpp is not optimal for Gemma out of the box
Gemma 4 is notorious for RAM blow-up in stock llama.cpp. Root causes + our levers
(several are config, not new kernels — "Gemma already does a lot of this"):
memory layout to Gemma's MHA/grouping). Our merge-base is 2026-05-14, so we
likely already have it; the M1 sync to upstream master confirms + grabs the
rest.
ctx-checkpointsaccumulation (#21690) — Gemma-only: server KVcheckpoints grow unbounded (64 GB filled in 4–5 × 16K-prompt). Fix: bound
ctx-checkpoints(1/0) +-np 1in our fused defaults. Pure config.swa_full=false) —llama_kv_cache_iswaalready sizesSWA layers to
n_swa (512/1024) + n_ubatchwhenswa_full=false. Most layersare SWA, so this is the dominant KV saving. Default
swa_full=false(it'salready plumbed through
capacitor-llama/types.ts+ the iOS shim).per_layer_tok_embdis{n_embd_per_layer·n_layer, n_vocab}(~2.8B params for E2B). With mmap ON the OS pages it from disk (≈ LiteRT's
"mmap embeddings, keep on disk until needed"). Never force
--no-mmapforGemma; on GPU backends, pin PLE to CPU/mmap rather than copying it to
VRAM. (Watch Vulkan #18317: Vulkan can't run
mmap=0.)not supported" even with
-fa --swa-full. So prefix reuse is partial onshared-KV layers — better than Qwen3.5 (which can't at all) but not free. Our
stream_reset_keepwork must account for this.Leverage Google's C++ work, don't reinvent: LiteRT-LM already does
PLE-mmap + windowed-SWA + NPU optimally → use it as the in-process backend on
capable hardware. For llama.cpp, absorb upstream Gemma memory PRs and set
Gemma-aware defaults rather than porting our Qwen kernels blindly.
Where our kernels still add value (M6), re-scoped:
dense FFN/attention weights. Keep.
the global-attention KV (where 256K context still hurts). Re-parameterize from
uniform head_dim=128 to Gemma's dual dims (global ~256 + SWA). Validate it
still wins after SWA+shared-KV already shrank the cache.
geometry.
Frozen contracts re-opened (owner-approved 2026-06-22)
model_registry.py,training/AGENTS.md) — Qwen → Gemma 4.tokenizerFamily "qwen35"→"gemma4"; vocab 248,320 → 262,144 (memory_calc.py).<|im_end|>→<end_of_turn>(3 scorer files).native/AGENTS.md§11 reinterpreted (single lib + single pipe).eliza-1 v1 is base-not-fine-tuned (
releaseState=base-v1) → v1 cutover isswap base + re-optimize, not a retraining run.
Milestones (PR per milestone → develop)
commits ahead of our base; we are 867 ahead with kernels/voice) into the fork;
review + absorb all relevant Gemma/MTP/LiteRT/CoreML/AICore/KV/quant PRs (incl.
open #21587 Gemma4 BPE SIGSEGV, #24590 Gemma4Assistant memory-fit, #21690
ctx-checkpoints, #21468 cache-reuse). Build + verify Gemma 4 E2B/E4B
text+vision+audio+MTP on CPU/CUDA.
abliterate, AGENTS; remove Qwen; tier map E2B/E4B/12B/31B(/26B-A4B); Gemma-aware
runtime defaults:
swa_full=false, bounded ctx-checkpoints, mmap-on/PLE-on-CPU).backend-selector).QJL/Polar KV-quant on windowed/global KV) across CPU/CUDA/Vulkan-Mali/Metal/NPU
PR_EVIDENCE).
What's verifiable in-session vs needs hardware
text/vision/audio/MTP gen; code cutover; FFI seam; bench (
llama-bench,e2e_loop_bench) on CPU/CUDA; web + desktop-app smoke.on-device tok/s + RSS. (Prior on-device work used adb/CDP on a Pixel 9a.)
Hardware-gated items are scoped + scaffolded here and marked for device
verification; nothing is claimed "verified" without the evidence.
Acceptance criteria
libelizainference(text+vision+audio+MTP) on every buildable backend.