Downloads · 30 days
0
okayuji/Gemma-4-E2B-it-coreml-speculative
Gemma-4-E2B-it-coreml-speculative is a text generation model from okayuji. Use it when you need the model to write or continue text. It is set up for coreml. The card lists the license as apache-2.0.
Run Google's Gemma 4 E2B IT on an iPhone's Apple Neural Engine, or on an Apple silicon Mac, with Core ML. This bundle includes text generation, image understanding, speech transcription, and lossless prompt-lookup spe…
Downloads · 30 days
0
Access
Public
Updated Oct 23, 2026
Repo size
5.8 GB
Likes
3
Public
Click a slice to open those files.
.bin5.8 GB · 99%
From the Hugging Face model README
Run Google's Gemma 4 E2B IT on an iPhone's Apple Neural Engine, or on an Apple silicon Mac, with Core ML. This bundle includes text generation, image understanding, speech transcription, and lossless prompt-lookup speculation. The companion app also provides Live Camera captions and conversation-cache save / restore.
Swift runtime & app · iPhone setup & model guide · Measured device results · Verification
Requires: iPhone on iOS 26 with Xcode 26 for the app, or Apple silicon Mac on macOS 26+ for the CLI; ~5.9 GB disk with image and audio encoders.
For a Mac CLI check with the published runtime:
hf download okayuji/Gemma-4-E2B-it-coreml-speculative --local-dir ./gemma-4-e2b-speculative-pal6
git clone https://github.com/oka-yuji/coreml-llm-samples
cd coreml-llm-samples
swift run -c release --package-path CoreLLMKit corellm-chat --model ../gemma-4-e2b-speculative-pal6 --compute cpuOnly --stats \
--prompt "List three fruits, one per line."
On an iPhone: open CoreMLSamples.xcodeproj at the repository root, run the CoreMLSamples-iOS scheme on your phone, download okayuji/Gemma-4-E2B-it-coreml-speculative from Models, then choose Load in Chat. The device guide covers signing, bundle copy, speculation and checkpoint controls; downloads support progress, cancel and delete.
This repository contains weights and configuration. The companion ChunkedSpeculativeChain runtime drives the multifunction graph and host-side KV cache used by the CLI and app. Image and audio input are worked examples of feeding soft tokens into that chain; the audio notes include the numerical failure found on the Neural Engine.
--compute all uses the ANE; cpuOnly checks output without its specialization cost. The first ANE run per launch specializes kernels once; see Operational notes.
Apple M4 Max (128 GB) / macOS 26.5.2, the corellm-chat CLI, --compute cpuOnly, greedy
(temperature 0), one process per condition (2026-07-28). On-device speeds are in the
table below.
Prompt:
Extract every person, place, and date from this note as JSON with keys people, places, dates: Maya flew from Osaka to Reykjavik on March 3, 2026, and met Dr. Chen and Lena Novak at the Harpa concert hall on March 5, 2026.
Output — the model wrapped its JSON in a code fence; shown verbatim, identical with speculation off and on (both 212 bytes, same SHA-256):
```json
{
"people": [
"Maya",
"Dr. Chen",
"Lena Novak"
],
"places": [
"Osaka",
"Reykjavik",
"Harpa concert hall"
],
"dates": [
"March 3, 2026",
"March 5, 2026"
]
}
```
| Run | prompt → gen | TTFT | Decode | Speculation |
|---|---|---|---|---|
Speculation off (--no-mtp) | 72 → 99 (eos) | 1.52 s | 52.5 ms/tok (19.0 tok/s) | — |
| Speculation on | 72 → 99 (eos) | 1.58 s | 43.3 ms/tok (23.1 tok/s) | draft acceptance 0.54 |
Both runs produced byte-identical output (212 bytes, identical SHA-256) — "lossless speculation" means only the speed changes, never the text. Here speculation was worth ×1.22 (19.0 → 23.1 tok/s) because the copied spans — names, places, dates — are drafted straight from the prompt; on free-form prose it is near break-even.
Mac sample source: the author's 2026-07-28-e2b-published-bundle-mac-spec-ab.md,
measured with the published pal6 bundle. It records the three matching output hashes and the
off/on timings above; the underlying lab record is not public.
Measured on iPhone 15 (A16 / 6 GB) and iPhone 17 Pro (A19 Pro / 12 GB), iOS 26, Neural Engine,
via the companion DemoApp / CLI, one process per condition unless noted. Every message logs to
Documents/metrics.jsonl; the device records are dated 2026-07-25 … 2026-07-27.
| Claim | Measurement | Conditions |
|---|---|---|
| Speculation is bit-exact (lossless) | plain decode == prompt-lookup speculation, byte-identical (138 B); 5 rounds, 26/35 accepted (pldAcceptRate ≈ 0.74); separately logged draftAcceptanceRate 0.775; trajectory matches Mac cpuOnly bit-for-bit | iPhone 15 ANE, verbatim 36→36 tok. The device gate used the int8 verify head on cpuOnly; the shipped pal6 head is argmax-identical to it (Mac gate, see Verification) |
| Decode speed, speculation off | 11.9 tok/s (83 ms/tok steady); field per-token median ~11.5 tok/s | iPhone 15, short prompt |
| Decode speed, newer SoC | ~16 tok/s (60–64 ms/tok median) | iPhone 17 Pro / A19 Pro, same bundle, speculation on (field use); a controlled short-prompt run measured 61 ms/tok (16.3 tok/s) |
| Multi-turn KV reuse | 96% reused → TTFT 1.28 s | iPhone 15, 323-token context, 3rd turn (field); iPhone 17 Pro 83% reused → 1.04 s |
| First model load (one-time) | 120.4 s (A16) / 88.9 s (A19) | ANE kernel specialization, once per device lifetime; later launches reuse the on-disk cache |
| KV restore across machines | byte-identical continuation; import 0.044–0.057 s | Mac cpuOnly int8 KV (6.54 MB) restored on iPhone ANE; init-done footprint 276 MB (greedy) / 191 MB (spec, verify-4) |
The on-device lossless gate, verbatim. The byte-identical device result in the table was measured with a verbatim-repeat prompt on the iPhone 15 (A16) Neural Engine — the case where prompt-lookup speculation fires hardest.
Prompt (Japanese — "Repeat this word sequence exactly three times: apple banana orange grape peach"):
次の単語列をそのまま3回くり返して書いてください: りんご ばなな みかん ぶどう もも
Output (identical with speculation off and on, 138 bytes — the five fruit words repeated three times):
りんご ばなな みかん ぶどう ももりんご ばなな みかん ぶどう ももりんご ばなな みかん ぶどう もも
Plain decode ran at 11.9 tok/s (83 ms/tok) after a one-time 63.5 s ANE kernel specialization on
that launch; speculation fired for 5 rounds and accepted 26 of 35 drafted tokens (pldAcceptRate ≈ 0.74; separately logged draftAcceptanceRate 0.775),
its trajectory matching a Mac cpuOnly reference bit-for-bit.
Acceptance-metric clarification (2026-10-23): the earlier card placed 0.775 beside 26/35.
The 2026-07-25 device record reports two distinct fields: pldAcceptRate 0.74 and
draftAcceptanceRate 0.775. The count ratio is 26/35, approximately 0.74; 0.775 is retained
as the separately logged metric, not as that fraction. The source is the author's
2026-07-25-e2b-r4a-pld-device.md (lab record, not public).
On the on-device speedup, honestly: the Mac run under Sample output shows a clean
lossless ×1.22 (19.0 → 23.1 tok/s). On the 6 GB phone, though, the warm on/off speedup was
never cleanly isolated — the first speculation run pays a one-time verify-graph specialization, and
attempts to capture a warm on/off pair were cut short when the device degraded under memory pressure.
So for the phone I report speculation as lossless with measured acceptance, not a device speedup
multiplier. Acceptance is high on verbatim / quoting / repetitive / extraction spans (draftAcceptanceRate 0.775 on the
device verbatim run, 0.54 on the Mac extraction) and near break-even — mostly fallback — on free-form
chat; a structured JSON-extraction restore run measured 0.19. Selecting a friendly prompt would inflate
the multiplier, so I don't quote a device one.
The same bundle carries two extra encoders next to the language chunks. The companion runtime looks for
them inside the bundle directory, next to manifest.json, and turns each feature on only when its
encoder is present — a bundle without them still chats, with the attachment buttons simply absent.
| Input | How it is encoded | Context cost |
|---|---|---|
| Image | resized to 768x768, 2304 patches through a 16-layer RoPE ViT, projected to 256 soft tokens | 256 of 2,048, whatever the source resolution |
| Audio | 16 kHz mono, 128-band log-mel, causal conformer tower at 25 tokens per second of sound | up to 750 of 2,048 (the 30-second cap) |
Follow-up questions about an attached image cost nothing extra: the image stays in the KV cache, so the follow-up prefills only the new words (0.66 s to first token against 5.92 s for the turn that carried the image).
Live Camera is a demo in the companion app rather than a property of the bundle: it captions the camera feed cycle after cycle, in English or Japanese, resetting the context every cycle so a caption never drifts on the previous one. Every cycle prefills the same 279 tokens, which is how the reset is checked rather than assumed.
Measured with the companion DemoApp, one process per condition:
| Metric | iPhone 17 Pro | Apple M4 Max |
|---|---|---|
| Vision encode, warm | 0.72 s | 0.10 s on the GPU / 0.71 s on the full compute-unit set |
| Transcription, 5 s clip, TTFT | 3.26 s warm | 5.12 s first turn, 1.39 s from the second on |
| Live Camera, warm cycle | 4.52 s | 2.32 s |
| Live Camera, footprint | 418–438 MB | 5.34 GB |
| Live Camera, Stop to idle | 0.070 s | 0.053 s |
The first Live Camera cycle is the expensive one. Cold, it takes 80 s on an iPhone 17 Pro —
ANE kernel specialization, not inference. Pressing Start therefore opens a Preparing… gate that pays
about 70 s of it up front (measured 69.62 s / 69.37 s / 69.55 s across three cold runs), which
leaves a 5 s first caption instead of a 80-second stare and keeps the thermal state at fair
instead of serious. The compiled result is cached per device, so later launches skip it.
The audio tower is where fp16 on the Neural Engine bites. Converted naively, the fp16 audio graph
computes garbage on cu=all — per-token cosine 0.53 mean, -0.045 min against the reference,
while the same graph on cpuAndGPU scores 0.999998. The cause is a 1024-dim square sum reaching
~2.1e8 against an fp16 ceiling of 65504, over 3000x past overflow. Keeping one operation (softmax)
in fp32 fixes it at no measured speed cost (86.3 ms vs 126 ms for the plain-fp16 graph). The int8
audio tower was converted and then dropped: it is deterministically broken on cpuAndGPU
(cosine 0.127) and, even where its cosine looks fine, it flips downstream tokens at margins up to
9.7. fp16 ships for that reason, and the tower is pinned to cpuAndGPU.
architecture: gemma4, 35 layers, hidden 1536; chunk
bounds [0,15] [15,25] [25,35]). Each chunk is one .mlmodelc exposing a decode function and
offset-prefill functions, driven by a host-side KV cache — not MLState. That host-side KV is
what lets the graph run on the ANE and lets the KV be dumped to disk.pal6 palettized chunk weights plus int8 token-embedding and per-layer-embedding (PLE)
sidecars (embed_int8.bin, ple_int8.bin, each with a *_scale_f32.bin). sidecarStage = int8.prefill4 + a batched lmhead_v4
head), the memory-minimal single-width policy. The batched verify head is pal6 (grouped-16,
~291 MiB, argmax-lossless against the int8 head) and runs on cpuOnly while the forward pass
stays on the ANE. Running that batched head directly on the ANE is not lossless — the batched
forward's near-ties flip an argmax that plain sequential decoding does not — so the head is pinned to
cpuOnly to keep output bit-exact.lmhead (int8, ~385 MiB) is the plain decode head; lmhead_v4 (pal6, ~291 MiB) is the batched
verify head.coreml-corellm-r1 (provisional; declared in manifest.json format).The metrics show an app footprint of a few hundred MB, which is easy to misread. The honest picture is three ledgers:
phys_footprint, 229–395 MB in short chat
(the mem field in the metrics, and what Xcode's gauge reports).So "a 400 MB LLM" means the app's ledger is ~400 MB while the whole device is using ~2 GB in short chat and ~3 GB+ during wide prefill. That is why a 6 GB phone can be jetsam-killed even when the app footprint looks small: the weights you asked the OS to wire count against the whole-device budget.
Low-memory default (under 7 GB RAM): speculation defaults off. On a 6 GB phone the default session
is plain decode + KV restore — the configuration proven safe on 6 GB — and the verify assets are
not loaded until you turn speculation on. With speculation on, a 6 GB phone can stall a few seconds per
verify round when the cpuOnly head's weights are paged out under memory pressure; pal6 shrinks that
head from 385 MiB to 291 MiB to reduce, not eliminate, the paging. Devices with 8 GB or more keep
speculation on by default with the verify assets warmed at model load.
From manifest.json (promptPrefix / promptSuffix). These are not the stock Gemma
<start_of_turn> markers — a loader that assumes them will silently produce a different token
sequence. A single turn is BOS + promptPrefix + user text + promptSuffix:
<bos><|turn>user
{user text}<turn|>
<|turn>model
Verbatim from manifest.json (note the trailing newlines):
"promptPrefix": "<|turn>user\n",
"promptSuffix": "<turn|>\n<|turn>model\n"
BOS is token id 2 (generation_config.json); prepend it if your tokenizer does not. The runtime
stops on EOS ids 1 and 106 (convert_config.json; generation_config.json additionally lists
50). Use tokenizer.json from this repository.
Total download ~5.9 GB. Everything is shipped as compiled .mlmodelc folders (the loader uses
them directly — no .mlpackage compile step, which on the audio tower alone would cost ~76 s on every
first load).
| File | Role | Size |
|---|---|---|
mf_chunk_0_15_pal6.mlmodelc | Transformer layers 0–14 (pal6; decode + offset prefill) | ~421 MiB |
mf_chunk_15_25_pal6.mlmodelc | Transformer layers 15–24 | ~480 MiB |
mf_chunk_25_35_pal6.mlmodelc | Transformer layers 25–34 | ~480 MiB |
lmhead.mlmodelc | Decode LM head (int8) | ~385 MiB |
lmhead_v4.mlmodelc | Batched verify(4) head (pal6, grouped-16) | ~291 MiB |
embed_int8.bin (+ embed_scale_f32.bin) | Token embedding table (int8) | ~384 MiB |
ple_int8.bin (+ ple_scale_f32.bin) | Per-layer-embedding sidecars (int8) | ~2,240 MiB |
tokenizer.json / tokenizer_config.json / chat_template.jinja | Tokenizer + chat template | ~31 MiB |
manifest.json / convert_config.json / generation_config.json | Bundle manifest, chain config, generation defaults | <1 MB |
vision_fp16.mlmodelc | Vision encoder — 768x768 image to 256 soft tokens (fp16) | ~312 MB |
audio_fp16.mlmodelc | Audio encoder — log-mel to 25 soft tokens per second (fp16) | ~590 MB |
Size units corrected on 2026-10-23: the earlier card labeled the chunk/head/embedding binary
estimates as MB and the sidecar total as GB. The PLE table's old ~2.24 GB estimate was
2,240 MiB; the Hub file is 2,348,810,240 bytes. The *_scale_f32.bin companions are additional
small files. Decimal MB is retained for the vision and audio encoders. This check uses the Hub
inventory at revision b5d4b90ea363c8c227e66a2f80904f81a3929af0; memory measurements elsewhere
retain their original units.
The two encoders are optional in the sense that the language path does not need them; delete them and the bundle is a 4.9 GB text model with the attachment buttons gone.
corellm-chat CLI.computeUnits = all. The KV cache is host-side (not MLState), which is what
makes ANE execution + KV dump/restore possible.vision_fp16.mlmodelc and
audio_fp16.mlmodelc. Incremental refresh of a downloaded bundle is not exposed in the UI.cpuOnly verify head pages out between rounds. It does not corrupt output (still lossless).
Devices with 8 GB+ do not show this.metrics.jsonl. Every message, checkpoint, and launch appends one JSON line to
Documents/metrics.jsonl (token counts, finishReason, TTFT and per-token latencies, speculation
stats, KV op timings, staged phys_footprint and available memory, thermal, battery). Retrieve it
with the Files app (metrics.jsonl in the app's folder under On My iPhone) or devicectl device copy from.pal6 reduces but does not remove it.generation_config.json carries the base model's sampling defaults, but the shipped chain runs
greedy; there is no temperature / top-k / top-p path in this configuration.cpuOnly). The pal6 verify head's argmax / PLD-lossless equivalence
to the int8 head was gated on a Mac (cu=all); it is an argmax-identical drop-in, and a full
on-device re-gate of the pal6 head is noted as follow-up in the project records."It runs" and "it computes the same thing as the reference" are different claims; this bundle makes the second.
pldAcceptRate ≈ 0.74; separately logged draftAcceptanceRate 0.775)) matched a Mac cpuOnly
reference bit-for-bit (2026-07-25).cpuOnly) and restored on
the iPhone ANE produced a byte-identical continuation of a 355-token structured-extraction
context (2026-07-26).pal6 verify head (Mac, cu=all). The shipped pal6 grouped-16 verify head is
argmax-identical to the int8 head on real prefill hidden states (exact 4/4; margin 56/56, zero
flips) and PLD-lossless on cpuOnly (exact 5/5). The head-on-ANE variant was found non-lossless
for both int8 and pal6, which is why the head is pinned to cpuOnly.A 12B Core ML speculative bundle —
lube8163/gemma-4-12b-coreml-iphone-practical-chat
— added speculative decoding the same week (2026-07-25), pairing a width-4 verifier with an MTP drafter
on a 12B model. The differences here are stated as fact, not evaluation:
I have not reproduced their bundle; the comparison is taken from their published card.
Documents/metrics.jsonl — full per-token arrays,
speculation stats, KV timings, memory, and thermal state per message / checkpoint / launch.docs/e2b-speculative-device.md.This bundle runs with the Core ML runtime in coreml-llm-samples. For separate Core AI / Foundation Models conversions and examples, see coreai-samples. That project is a related collection for comparing the frameworks; its runtime uses different model assets and does not load this Core ML bundle.
google/gemma-4-E2B-it. The artifacts
here are derived from Gemma 4 E2B IT: the original weights have been modified — converted to a
Core ML graph, quantized (pal6 palettization + int8 embedding / PLE sidecars + int8 and pal6 heads),
split into three multifunction chunks, and given added offset-prefill and verify functions.