HiramKHackenbacker commited on
Commit
22deb78
·
verified ·
1 Parent(s): 1076e4f

model card: standard structure, measured facts, verified license declarations

Browse files
Files changed (1) hide show
  1. README.md +250 -262
README.md CHANGED
@@ -1,7 +1,8 @@
1
  ---
2
- license: gemma
3
- license_link: https://ai.google.dev/gemma/terms
4
  base_model: google/gemma-4-26B-A4B-it-qat-q4_0-unquantized
 
5
  library_name: coreai
6
  pipeline_tag: text-generation
7
  tags:
@@ -9,6 +10,7 @@ tags:
9
  - aimodel
10
  - apple-silicon
11
  - on-device
 
12
  - quantized
13
  - int4
14
  - qat
@@ -19,30 +21,61 @@ tags:
19
 
20
  # Gemma 4 26B-A4B (MoE) — Core AI (.aimodel)
21
 
22
- `google/gemma-4-26B-A4B-it-qat-q4_0-unquantized` converted to a Core AI `.aimodel` bundle
23
- for Apple silicon by [visible-cx](https://huggingface.co/visible-cx). This is a derivative
24
- artifact: Google's QAT-trained weights rounded onto int4 and re-expressed as a Core AI
25
- graph, with the 128-expert sparse branch of every layer lowered onto a Metal gather kernel.
 
26
 
27
  It is a **sparse mixture-of-experts** model — 26.5B total parameters, roughly 4B active per
28
- token.
 
29
 
30
- > ⚠️ **UNQUALIFIED.** This bundle has never produced a token, on any machine. The graph
31
- > exports and the producer fingerprint passes; the block wiring is verified against the
32
- > reference implementation (below). Nothing else is established.
33
  >
34
  > ⚠️ **The 16 GB tier is excluded.** Weights are ~17.6 GB resident against a 16 GB Mac's
35
- > ~10.7 GB Metal working set. **Minimum practical machine memory: 24–32 GB.**
 
36
 
 
 
 
 
 
 
37
 
38
- > ⚠️ **Expected known issue — long unguided generations (unverified on this model).**
39
- > On the sibling E2B/E4B exports, the Core AI runtime retains one compiled
40
- > specialization per sequence-length signature, costing roughly 80 MB of GPU
41
- > allocations per generated token until process exit; the decode graphs in this
42
- > repository share the length-dependent structure that causes it. Until a
43
- > shape-stable re-export exists for this model, treat these bundles as suitable for
44
- > guided/constrained and short generations; budget memory for long free-form runs
45
- > accordingly. Measured details: the gemma-4-E2B-CoreAI card's known-issue section.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
 
47
  ## Provenance
48
 
@@ -54,16 +87,16 @@ token.
54
  | Toolchain base | `apple/coreai-models` @ `b1cb71b8522d99408059fa0b98b8742171bcb0b8` + the [coreai-model-zoo](https://github.com/john-rocky/coreai-model-zoo) python overlay, plus three new modules (below) |
55
  | Toolchain | `coreai-torch 0.4.1`, `coreai-core 1.0.0b2`, `coreai-opt 0.2.1`, `torch 2.9.0` |
56
  | Producer fingerprint | `coreai-core 1.0.0b2` — verified on the inner asset `metadata.json` |
57
- | Asset creation date | `20260817T215629Z` |
58
  | Vocab | 262,144 |
59
- | Export functions | `main` (S=1 decode) only |
60
 
61
- "QAT-unquantized" means QAT-*trained*, stored bf16; the int4 rounding happens at export
62
- onto the ggml q4_0 grid the training already targeted. There is no separate pre-quantised
63
- int4 artifact to prefer.
64
 
65
- **To reproduce this conversion you need a ≥128 GB host**: the export peaks at
66
- **102.25 GB RSS** (`VmHWM`) on Linux x86_64.
 
67
 
68
  ### Architecture
69
 
@@ -79,11 +112,10 @@ int4 artifact to prefer.
79
  | `head_dim` / `global_head_dim` | **256 / 512** | 256 / 512 | 256 / 512 |
80
  | `sliding_window` | **1024** | 1024 | 512 |
81
  | Per-Layer Embeddings | **none** | none | 256 |
82
- | KV-shared layers | **0** | 0 | 18 |
83
 
84
- Same attention family as the dense 12B/31B — same dual head_dim, same `attention_k_eq_v`
85
- (full layers carry no `v_proj`), same dual RoPE, same softcap. There are no Per-Layer
86
- Embeddings and therefore no gather-table sidecar in this repo.
87
 
88
  Every layer carries a dense MLP **and** a sparse branch, in parallel:
89
 
@@ -98,11 +130,11 @@ x = residual + post_feedforward_layernorm(h1 + h2)
98
  x = x * layer_scalar
99
  ```
100
 
101
- Two details are load-bearing: **the router reads the un-normalised residual**, and **the
102
- experts are GELU-gated** (`gelu_pytorch_tanh`), where the `SwitchGLU` primitive's default
103
- is SiLU. The router itself: scale-free RMSNorm → `× scale × hidden**-0.5` → linear to 128 →
104
- fp32 softmax over all experts → top-8 → renormalise to sum 1 → multiply by a learned
105
- `per_expert_scale` gathered at the selected ids.
106
 
107
  ### Compression recipe
108
 
@@ -113,271 +145,227 @@ fp32 softmax over all experts → top-8 → renormalise to sum 1 → multiply by
113
  | **Router** (`proj`, `scale`, `per_expert_scale`) | **fp16**, excluded by name | routers are the quantization-sensitive part of an MoE and the whole set is 0.02 GB |
114
  | `embed_tokens` (in-graph) | fp16 | the gather stays exact; the head is untied and quantized separately |
115
 
116
- `sym8` on the experts would put them at 22.8 GB before anything else, so int4 is not
117
- optional. `aff4` was chosen over a k-means palette (`km4`) because this checkpoint was
118
- QAT-trained on per-block-32 q4_0 (`w = (q-8)·d`, 16 uniform levels), and an affine int4
119
- block-32 grid represents that **exactly** (`scale = d`, `bias = -8d`). A palette fits one
120
- 16-entry codebook per 32 output rows across the whole K axis and structurally cannot follow
121
- per-block QAT scales. The price is aux bytes: `aff4` stores an fp16 scale *and* bias per
122
- 32-element K block, i.e. 5 bits/param effective against `km4`'s ~4.03 — about 3 GB of the
123
- bundle.
124
 
125
- The `down` projection's K of 704 is padded to 768 by the kernel's automatic `_kpad256`
126
- treatment — 64 zero columns, ~0.5% wasted expert bytes. `gate`/`up` need no padding.
127
 
128
- **The gather kernel is load-bearing.** Without `MetalSwitchGLU`, the sparse branch lowers
129
- to a dense matmul that reads all 128 experts every token — a 16× over-read.
130
 
131
  ### New source modules
132
 
133
- Three files were authored for this port; nothing in the shared toolchain was modified:
134
-
135
  | File | Contents |
136
  |---|---|
137
  | `coreai_models/models/macos/gemma4_moe_text.py` | `Gemma4MoeConfig`, `Gemma4MoeRouter`, `GeluGLU`, `Gemma4MoeDecoderLayer`, `Gemma4MoeForCausalLM`, and a loader that splits the checkpoint's fused `gate_up_proj [E, 2*704, 2816]` into the `SwitchGLU`'s separate stacks |
138
  | `coreai_models/models/macos/gemma4_moe_pipelined.py` | `Gemma4MoePipelinedForCausalLM` — subclasses the dense pipelined core and overrides one method; the attention half is inherited unchanged |
139
  | `coreai-model-zoo/conversion/export_gemma4_moe_decode_pipelined.py` | the recipe |
140
 
141
- ## Contents
142
 
143
- | Path | Bytes | Files | Manifest context | Functions |
144
- |---|---:|---|---|---|
145
- | `gpu-pipelined/gemma4_26b_a4b_qat_decode_int4linsym_moeaff4_msdpa_g8` | 17,580,059,414 | 8 | 16384 | main (decode only) |
 
 
 
 
 
 
 
146
 
147
- | File | Bytes |
148
- |---|---:|
149
- | `…aimodel/main.mlirb` | 17,547,866,346 |
150
- | `…aimodel/main.hash` | 32 |
151
- | `…aimodel/metadata.json` | 105 |
152
- | `metadata.json` (bundle manifest) | 689 |
153
- | `tokenizer/tokenizer.json` | 32,169,626 |
154
- | `tokenizer/chat_template.jinja` | 18,683 |
155
- | `tokenizer/tokenizer_config.json` | 3,730 |
156
- | `tokenizer/generation_config.json` | 203 |
157
 
158
- Manifest context is 16384. `--max-ctx` sets `language.max_context_length` and nothing else;
159
- lower it if the machine cannot afford the KV — that is a metadata edit, not a re-export.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
160
 
161
- **Stop token:** `eos_token = "<turn|>"` (id 106), the turn terminator Gemma 4 emits, applied
162
- by the export script itself. A host that stops on the raw upstream `<eos>` will overrun
163
- every reply.
164
 
165
- ### Decode only, and a prefill variant is not deliverable by a flag
 
 
166
 
167
- There is no `prefill` function. Two independent kernel blockers:
 
 
 
 
168
 
169
- 1. `--metal-sdpa`'s flash-decode kernel is structurally q=1 with no causal mask. The full
170
- attention layers need that kernel for the same scratch-heap reason the dense 12B/31B do.
171
- 2. `MetalSwitchGLU.forward` is itself decode-only it asserts a token batch of 1 and
172
- expands the single activation row across the routed slots. A `BatchedMetalSwitchGLU`
173
- (sort-by-expert grouped GEMM) exists for q>1, so this half is reachable, but it has
174
- never been exported for this model.
175
 
176
- A prefill variant therefore needs a new flash-**prefill** Metal kernel plus the first
177
- export of the batched MoE kernel, plus a numerics gate for each. Prompts are prefilled one
178
- token at a time.
179
 
180
- ## Requirements
 
 
 
181
 
182
- - **Apple silicon Mac with ≥24–32 GB unified memory**, Core AI runtime.
183
- - **Engine contract: 2 inputs** (`input_ids`, `position_ids`) → logits, one growing KV pair,
184
- no static inputs and no per-step mask.
185
- - **States:** `keyCache` / `valueCache` `Float16, 30 × 1 × 8 × ? × 512` — one growing pair,
186
- 30 slots (no KV sharing). The dynamic sequence dim means the runtime resolves
187
- `GrowingKVCache`, not a static allocation at the manifest maximum.
188
- - **KV cost: 491,520 bytes per token of context** (fp16):
189
 
190
- | Context | KV |
191
- |---|---|
192
- | 4096 | 2.01 GB |
193
- | 8192 | 4.03 GB |
194
- | **16384** | **8.05 GB** |
195
- | 32768 | 16.1 GB |
196
 
197
- - **Resident weights, ~17.6 GB:**
 
 
198
 
199
- | | bytes |
200
- |---|---|
201
- | Routed experts, `aff4` (incl. `_kpad256` zero columns) | ~14.7 GB |
202
- | Attention + dense MLP + untied head, int4 block-32 | ~1.4 GB |
203
- | `embed_tokens`, fp16 in-graph | ~1.5 GB |
204
- | Router, fp16 | ~0.02 GB |
205
- | **Total** | **~17.6 GB** |
206
- | + KV at 4096 | ~19.6 GB |
207
- | + KV at 16384 | ~25.7 GB |
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
208
 
209
- | Tier | Verdict |
210
- |---|---|
211
- | 16 GB | **no**weights alone are ~1.6× the entire ~10.7 GB Metal working set |
212
- | 24–32 GB | yes at ctx ≤ 8192 (~21.6 GB); 16384 is tight |
213
- | 64 GB | yes, at any context |
214
 
215
- This table is arithmetic from state shapes and weight formats — a prediction to check,
216
- not a measured tier claim. The residency mechanism for MoE bundles on macOS (mmap'd vs
217
- wired expert slabs) has not been measured.
218
 
219
- 25 of the 30 layers have `head_dim` 256 zero-padded to 512, and all 30 slots grow linearly
220
- even though 25 of them only ever attend a 1024-token window. A ring-buffered sliding cache
221
- would be roughly 5× cheaper per token; that is model authoring, not a flag.
222
- - The bundle manifest declares `runtime_env COREAI_CHUNK_THRESHOLD=1`.
223
 
224
- ## Performance
 
225
 
226
- **No measurement exists, on any machine.** Every figure in this card is either a byte count,
227
- an export-host measurement, or derived from state shapes and weight formats. No token has
228
- been generated from this bundle.
 
 
 
 
 
 
 
 
 
 
 
229
 
230
- The intended argument for this model is throughput at comparable size against the dense 31B
231
- 17.6 GB vs 20 GB, with ~4B active parameters per token read through a gather kernel — and
232
- quality per token against the 12B at ~2.1× the footprint. The nearest measured analogue is
233
- LFM2.5-8B-A1B at 140 tok/s through its gather kernel versus 39 tok/s for the same weights
234
- read as a dense over-read. Neither claim is measured for this bundle.
 
 
 
 
 
 
235
 
236
  ## Status
237
 
238
  | Artifact | Status |
239
  |---|---|
240
- | `gpu-pipelined/gemma4_26b_a4b_qat_decode_int4linsym_moeaff4_msdpa_g8` | **UNQUALIFIED** — never executed on target hardware. Established: the checkpoint is the QAT source, the graph exports, the producer fingerprint passes, and the MoE block wiring matches the reference implementation to 3.5e-7 relative error on real layer-0 weights (below). |
 
241
 
242
  A qualifying run would need, in priority order:
243
 
244
- 1. **A decode oracle** — generate and compare against an fp32 HF reference. This is the gate
245
- that catches an int4 or kernel-level error which the authoring gate below cannot see.
246
- 2. **Isolate the two Metal kernels.** `gather_qmm` (the `aff4` variant) and the flash-decode
247
- SDPA have never been in the same graph, on any model. If decode produces garbage,
248
- re-export without `--metal-sdpa` first — it is an optimisation, the MoE kernel is not.
249
- 3. **Measure residency**, both dirty footprint and maximum resident including mmap. No MoE
250
- bundle has been measured this way.
251
- 4. **Compare tok/s against the dense 12B and 31B** on the same machine.
252
- 5. **Router sanity at depth.** 128 experts / top-8 with a learned `per_expert_scale` is far
253
- sparser routing than the LFM2.5-8B-A1B's 32/top-4. Check expert-utilisation spread on a
254
- real prompt set before trusting long-form output.
255
-
256
- ### Verification
257
-
258
- **MoE block wiring verified against the reference implementation.** `transformers` 4.57.6
259
- (the pinned export environment) has no `gemma4`, so the gate transcribes
260
- `Gemma4TextDecoderLayer.forward` (the MoE-block half), `Gemma4TextRouter.forward` and
261
- `Gemma4TextExperts.forward` from upstream and runs both implementations on the real layer-0
262
- weights in fp32:
263
-
264
- ```
265
- T=1: max|dFFN|=7.629e-05 rel=3.453e-07 | same top-8 ids: True max|dw|=0.000e+00
266
- T=4: max|dFFN|=9.537e-05 rel=2.735e-07 | same top-8 ids: True max|dw|=0.000e+00
267
- ```
268
-
269
- Identical expert selection, bit-identical routing weights, ~3.5e-7 relative error on the
270
- block output. **This gates the authoring** — the wiring, the fused-weight split, the
271
- activation choice, the norm placement, the router maths. It does **not** gate the exported
272
- graph, the int4 rounding, or either Metal kernel. The attention half was not re-gated: it
273
- is the dense `Attention` class already used in the 12B and 31B bundles.
274
 
275
  ## License
276
 
277
- The upstream QAT checkpoint carries Apache-2.0 metadata and is ungated, but it is a Gemma
278
- model and **the Gemma terms apply downstream**, which is why this repo declares
279
- `license: gemma`. Use of these weights is subject to the
280
- [Gemma Terms of Use](https://ai.google.dev/gemma/terms), the
281
- [Gemma Prohibited Use Policy](https://ai.google.dev/gemma/prohibited_use_policy) and the
282
- [Gemma 4 license](https://ai.google.dev/gemma/docs/gemma_4_license). Those obligations
283
- travel with any redistribution of this bundle. The contribution here is the port and the
284
- recipe, not the weights.
285
-
286
-
287
- ## `hybrid-mf64/` bundles
288
-
289
- > **EXPERIMENTAL — not qualified.** These bundles have not passed a Mac-side
290
- > oracle/parity gate or a device benchmark. Do not route production traffic to
291
- > them until they have.
292
-
293
- **What this is.** The 26B-A4B with a `prefill` entrypoint, from one copy of the weights
294
- the published `gpu-pipelined/` decode bundle already ships. Two things had to be
295
- per-entrypoint at once, and both are:
296
-
297
- * **Experts.** `BatchedMetalSwitchGLU` sorts the `S*k` (token, expert) pairs of a 64-token
298
- chunk by expert id so each expert slab is read once, and falls through to
299
- `MetalSwitchGLU.forward` verbatim whenever `b*s == 1`. One metalization, both
300
- entrypoints, **decode arithmetic unchanged** — `main` traces the identical q=1 gather
301
- matvec the published decode bundle carries.
302
- * **Attention.** The 26B inherits the dense 12B/31B full-attention layers, so it needs the
303
- flash-decode Metal kernel for the scratch-heap reason, and that kernel is structurally
304
- q=1. `SplitSDPA` branches on `query.shape[2]`, which is a concrete int in each trace, so
305
- `main` gets the kernel and `prefill` gets the MPSGraph composite (query axis +
306
- `is_causal=True` intra-chunk mask). Same technique as the 31B `hybrid-pf64/` bundle.
307
-
308
- Both kernels are registered on one converter before either `add_pytorch_module`.
309
-
310
- **This is `mf64`, not `mf64-tp`, and it cannot be `-tp` today.** The token-tiled prefill
311
- kernel (`moe_metal_tiled`, the 8B-A1B `mf64-tp` experiment) is **sym8-only** —
312
- `TiledBatchedMetalSwitchGLU.__init__` raises for any other scheme. The 26B ships **aff4**
313
- experts, and that is not incidental: the checkpoint is QAT-trained on the ggml q4_0
314
- per-block-32 grid, which an affine int4 block-32 grid represents exactly. An aff4 tiled
315
- kernel is future work, gated on two things — the 8B `mf64-tp` Mac verdict proving the
316
- tiling principle at all, and then an aff4 equality proof of the kind `moek_tilegate.py`
317
- gives the sym8 kernel. What this bundle uses instead is the **shipped** batched path,
318
- the one behind the 8B `mf64` bundle that has a Mac measurement.
319
-
320
- **Measured on the conversion box (Linux x86_64, no GPU).** Export wall 305 s; peak RSS
321
- **87.5 GB** (well under the 12B's 68.7 → 31B's 148.9 band this was budgeted against, and
322
- never near the 75% kill line); bundle 17,580,916,612 B; producer `coreai-core 1.0.0b2`;
323
- ctx 16384; `function_map {"main": ["main", "prefill"]}`. Trace accounting: all 5 full
324
- layers took the metal branch at S=1 **and** the composite branch at S=64, 5/5 both ways —
325
- the export fails if either count is 0. 30 MoE layers metalized. Graph bytecode declares
326
- `input_ids`, `position_ids`, `keyCache`, `valueCache`, `logits`, `prefill`, 90
327
- `gather_qmm_int4aff` call sites (30 layers x gate/up/down) and
328
- `gemma4_dense_full_sdpa_occ8` — **2 inputs, no `mask`**, which is the arity
329
- `CoreAISequentialEngine` requires.
330
-
331
- **Numerics gate (box-side, `gate_hybrid_sdpa.json`).** The 26B is a *third* distinct
332
- block-GQA mapping and was gated as its own case: 16 query heads, **2** global KV heads
333
- replicated across 8 cache slots, so the kernel's `kv = h / (H / slots)` = `h//2` must land
334
- on real head `(h//2)//4 = h//8`. Kernel reference vs the composite at those exact shapes:
335
- max abs 2.50e-2 (S=137) and 1.17e-2 (S=1024) — the composite's own fp16 error. Against an
336
- fp32 ground truth the kernel is the more accurate of the two: **9.74e-4 / 74.9 dB** at
337
- S=137 and 9.76e-4 / 75.1 dB at S=1024, versus the composite's 53.0 and 57.9 dB. The 12B
338
- (1 global head, rep 8) and 31B (4 global heads, rep 4 over 16 slots) rows are in the same
339
- file as controls; a mapping bug that cancelled in those would show here. `MetalDenseFullSDPA`
340
- also evaluates in eager at 26B dims and matches its own reference exactly (max abs 0.0).
341
- This gates the mapping and the scale, not the Metal source (no GPU on the box).
342
-
343
- **Tier — read the arithmetic, not the bundle size.** KV is `2 x 30 layers x 8 KV slots x
344
- 512 head_dim x fp16` = **491,520 B/token**:
345
-
346
- | Context | KV | + 17.6 GB weights | Verdict |
347
- |---|---|---|---|
348
- | 4096 | 2.01 GB | 19.6 GB | 32 GB machine |
349
- | 8192 | 4.03 GB | 21.6 GB | 32 GB machine, marginal against a ~21-24 GB working set |
350
- | 16384 (this manifest) | 8.05 GB | 25.6 GB | **64 GB machine** |
351
-
352
- So: a 32 GB Mac can run it at shallow context, the manifest's full 16384 wants 64 GB, and
353
- no 16 GB machine runs it at any context.
354
-
355
- **EXPERIMENTAL — unqualified until a big-machine measurement exists.** Nothing here has
356
- produced a token. In priority order:
357
-
358
- 1. *Does it load and decode?* Decode should be indistinguishable from the published
359
- `gpu-pipelined/` bundle: same weights, same q=1 gather kernel, same flash-decode SDPA.
360
- A decode regression against that bundle would mean the batched wrapper is not falling
361
- through at `b*s == 1` as its source says it does.
362
- 2. *Prefill throughput.* The **falsifiable prediction**: the batched kernel's anchor is the
363
- 8B-A1B `mf64` bundle's measured **6.3 ms/prompt-token**, and this bundle should land in
364
- that mechanism's class rather than the 21.5 ms/token of a decode-only bundle walking
365
- the prompt one token at a time. Do not read 6.3 ms as the expected number for this
366
- model: the 26B activates ~4B parameters per token against the 8B-A1B's ~1B, and its
367
- dense MLP runs in parallel with the expert branch, so a figure several times 6.3 ms
368
- would still confirm the mechanism. What would falsify it is prefill landing at or above
369
- the per-token cost of decode — that would mean the sorted grouped-GEMM path is not
370
- engaging and the chunk is being walked serially.
371
- 3. *Chunk parity.* A prompt walked in 64-token chunks must give the same logits as the same
372
- prompt walked token by token. Two independent reasons to check it here rather than
373
- assume: the entrypoints use different attention implementations, and the expert path
374
- does a sort/unsort round trip that the q=1 path does not.
375
- 4. *Compare against the dense 31B on the same machine.* That is the whole case for this
376
- model — 17.6 GB and ~4B active per token against 20 GB and 31B dense.
377
-
378
- Nothing already published was touched. `gpu-pipelined/gemma4_26b_a4b_qat_decode_int4linsym_moeaff4_msdpa_g8`
379
- is unchanged and remains the only 26B artifact anyone has run.
380
-
381
- | Bundle | Source checkpoint | Context | Functions | Bytes | Producer | Created |
382
- |---|---|---|---|---|---|---|
383
- | `hybrid-mf64/gemma4_26b_a4b_qat_decode_int4linsym_moeaff4_msdpa_g8_pf64` | google/gemma-4-26B-A4B-it-qat-q4_0-unquantized | 16384 | main+prefill | 17,580,916,612 | coreai-core 1.0.0b2 | 20260818T141434Z |
 
1
  ---
2
+ license: apache-2.0
3
+ license_link: https://ai.google.dev/gemma/docs/gemma_4_license
4
  base_model: google/gemma-4-26B-A4B-it-qat-q4_0-unquantized
5
+ base_model_relation: quantized
6
  library_name: coreai
7
  pipeline_tag: text-generation
8
  tags:
 
10
  - aimodel
11
  - apple-silicon
12
  - on-device
13
+ - coreai-kit
14
  - quantized
15
  - int4
16
  - qat
 
21
 
22
  # Gemma 4 26B-A4B (MoE) — Core AI (.aimodel)
23
 
24
+ `google/gemma-4-26B-A4B-it-qat-q4_0-unquantized` converted to Core AI `.aimodel` bundles for
25
+ Apple silicon by [visible-cx](https://huggingface.co/visible-cx). These are derivative
26
+ artifacts: Google's QAT-trained weights rounded onto int4 and re-expressed as a Core AI graph,
27
+ with the 128-expert sparse branch of every layer lowered onto a Metal gather kernel. They load
28
+ through Core AI on macOS and are not usable by PyTorch, GGUF or MLX.
29
 
30
  It is a **sparse mixture-of-experts** model — 26.5B total parameters, roughly 4B active per
31
+ token. This is a **new port**, not a reproduction of a published zoo bundle: three source
32
+ modules were authored for it (below), and nothing in the shared toolchain was modified.
33
 
34
+ > ⚠️ **UNQUALIFIED. Neither bundle has ever produced a token, on any machine.** The graphs
35
+ > export, the producer fingerprints pass, and the MoE block wiring is verified against the
36
+ > reference implementation to 3.5e-7 relative error. Nothing else is established.
37
  >
38
  > ⚠️ **The 16 GB tier is excluded.** Weights are ~17.6 GB resident against a 16 GB Mac's
39
+ > ~10.7 GB Metal working set. **Minimum practical machine memory: 32 GB at shallow context,
40
+ > 64 GB at the manifest's 16384.**
41
 
42
+ > ⚠️ **Expected known issue — long unguided generations (unverified on this model).** On the
43
+ > sibling E2B/E4B exports the Core AI runtime retains one compiled specialization per
44
+ > sequence-length signature, costing roughly 81 MB of GPU allocations per generated token until
45
+ > process exit, and the decode graphs here share the length-dependent structure that causes it.
46
+ > Guided decoding is not a defence — **shape reuse is**. Measured details: the
47
+ > [gemma-4-E2B-CoreAI](https://huggingface.co/visible-cx/gemma-4-E2B-CoreAI) card.
48
 
49
+ ## Contents
50
+
51
+ | Path | Bytes | Files | Context | Functions | Status |
52
+ |---|---:|---|---|---|---|
53
+ | `gpu-pipelined/gemma4_26b_a4b_qat_decode_int4linsym_moeaff4_msdpa_g8` | 17,580,059,414 | 8 | 16384 | main (decode only) | UNQUALIFIED |
54
+ | `hybrid-mf64/gemma4_26b_a4b_qat_decode_int4linsym_moeaff4_msdpa_g8_pf64` | 17,580,916,612 | 8 | 16384 | main + prefill | EXPERIMENTAL |
55
+
56
+ `gpu-pipelined/`, file by file:
57
+
58
+ | File | Bytes |
59
+ |---|---:|
60
+ | `…aimodel/main.mlirb` | 17,547,866,346 |
61
+ | `…aimodel/main.hash` | 32 |
62
+ | `…aimodel/metadata.json` | 105 |
63
+ | `metadata.json` (bundle manifest) | 689 |
64
+ | `tokenizer/tokenizer.json` | 32,169,626 |
65
+ | `tokenizer/chat_template.jinja` | 18,683 |
66
+ | `tokenizer/tokenizer_config.json` | 3,730 |
67
+ | `tokenizer/generation_config.json` | 203 |
68
+
69
+ `hybrid-mf64/` is the same layout with `main.mlirb` at 17,548,723,515 B — **857,169 B more than
70
+ the decode-only build**, which is the whole cost of a second entrypoint over deduplicated
71
+ weights.
72
+
73
+ Manifest context is 16384. `--max-ctx` sets `language.max_context_length` and nothing else;
74
+ lower it if the machine cannot afford the KV — that is a metadata edit, not a re-export.
75
+
76
+ **Stop token:** `eos_token = "<turn|>"` (id 106), the turn terminator Gemma 4 emits, applied by
77
+ the export script itself. A host that stops on the raw upstream `<eos>` will overrun every
78
+ reply.
79
 
80
  ## Provenance
81
 
 
87
  | Toolchain base | `apple/coreai-models` @ `b1cb71b8522d99408059fa0b98b8742171bcb0b8` + the [coreai-model-zoo](https://github.com/john-rocky/coreai-model-zoo) python overlay, plus three new modules (below) |
88
  | Toolchain | `coreai-torch 0.4.1`, `coreai-core 1.0.0b2`, `coreai-opt 0.2.1`, `torch 2.9.0` |
89
  | Producer fingerprint | `coreai-core 1.0.0b2` — verified on the inner asset `metadata.json` |
90
+ | Asset creation date | `20260817T215629Z` (`gpu-pipelined/`), `20260818T141434Z` (`hybrid-mf64/`) |
91
  | Vocab | 262,144 |
 
92
 
93
+ "QAT-unquantized" means QAT-*trained*, stored bf16; the int4 rounding happens at export onto the
94
+ ggml q4_0 grid the training already targeted. There is no separate pre-quantised int4 artifact
95
+ to prefer.
96
 
97
+ **To reproduce this conversion you need a ≥128 GB host**: the decode-only export peaks at
98
+ **102.25 GB RSS** (`VmHWM`) on Linux x86_64; the `hybrid-mf64/` export peaked at **87.5 GB** in
99
+ 305 s.
100
 
101
  ### Architecture
102
 
 
112
  | `head_dim` / `global_head_dim` | **256 / 512** | 256 / 512 | 256 / 512 |
113
  | `sliding_window` | **1024** | 1024 | 512 |
114
  | Per-Layer Embeddings | **none** | none | 256 |
 
115
 
116
+ Same attention family as the dense 12B/31B — same dual head_dim, same `attention_k_eq_v` (full
117
+ layers carry no `v_proj`), same dual RoPE, same softcap. **There are no Per-Layer Embeddings and
118
+ therefore no gather-table sidecar in this repo**, unlike E2B/E4B.
119
 
120
  Every layer carries a dense MLP **and** a sparse branch, in parallel:
121
 
 
130
  x = x * layer_scalar
131
  ```
132
 
133
+ Two details are load-bearing: **the router reads the un-normalised residual**, and **the experts
134
+ are GELU-gated** (`gelu_pytorch_tanh`), where the `SwitchGLU` primitive's default is SiLU. The
135
+ router itself: scale-free RMSNorm → `× scale × hidden**-0.5` → linear to 128 → fp32 softmax over
136
+ all experts → top-8 → renormalise to sum 1 → multiply by a learned `per_expert_scale` gathered
137
+ at the selected ids.
138
 
139
  ### Compression recipe
140
 
 
145
  | **Router** (`proj`, `scale`, `per_expert_scale`) | **fp16**, excluded by name | routers are the quantization-sensitive part of an MoE and the whole set is 0.02 GB |
146
  | `embed_tokens` (in-graph) | fp16 | the gather stays exact; the head is untied and quantized separately |
147
 
148
+ `sym8` on the experts would put them at 22.8 GB before anything else, so int4 is not optional.
149
+ `aff4` was chosen over a k-means palette (`km4`) because this checkpoint was QAT-trained on
150
+ per-block-32 q4_0 (`w = (q8)·d`, 16 uniform levels), and an affine int4 block-32 grid represents
151
+ that **exactly** (`scale = d`, `bias = 8d`). A palette fits one 16-entry codebook per 32 output
152
+ rows across the whole K axis and structurally cannot follow per-block QAT scales. The price is
153
+ aux bytes: `aff4` stores an fp16 scale *and* bias per 32-element K block, ~5 bits/param effective
154
+ against `km4`'s ~4.03 — about 3 GB of the bundle.
 
155
 
156
+ The `down` projection's K of 704 is padded to 768 by the kernel's automatic `_kpad256` treatment
157
+ — 64 zero columns, ~0.5% wasted expert bytes. `gate`/`up` need no padding.
158
 
159
+ **The gather kernel is load-bearing.** Without `MetalSwitchGLU`, the sparse branch lowers to a
160
+ dense matmul that reads all 128 experts every token — a 16× over-read.
161
 
162
  ### New source modules
163
 
 
 
164
  | File | Contents |
165
  |---|---|
166
  | `coreai_models/models/macos/gemma4_moe_text.py` | `Gemma4MoeConfig`, `Gemma4MoeRouter`, `GeluGLU`, `Gemma4MoeDecoderLayer`, `Gemma4MoeForCausalLM`, and a loader that splits the checkpoint's fused `gate_up_proj [E, 2*704, 2816]` into the `SwitchGLU`'s separate stacks |
167
  | `coreai_models/models/macos/gemma4_moe_pipelined.py` | `Gemma4MoePipelinedForCausalLM` — subclasses the dense pipelined core and overrides one method; the attention half is inherited unchanged |
168
  | `coreai-model-zoo/conversion/export_gemma4_moe_decode_pipelined.py` | the recipe |
169
 
170
+ ## Requirements
171
 
172
+ - **Apple silicon Mac with ≥32 GB unified memory**, Core AI runtime.
173
+ - **Engine contract: 2 inputs** (`input_ids`, `position_ids`) → logits, one growing KV pair, no
174
+ static inputs and no per-step mask. Verified by reading the graph bytecode of `hybrid-mf64/`:
175
+ `input_ids`, `position_ids`, `keyCache`, `valueCache`, `logits`, `prefill`, 90
176
+ `gather_qmm_int4aff` call sites (30 layers × gate/up/down) and `gemma4_dense_full_sdpa_occ8`
177
+ — **2 inputs, no `mask`**, which is the arity the sequential engine requires.
178
+ - **States:** `keyCache` / `valueCache` `Float16, 30 × 1 × 8 × ? × 512` — one growing pair, 30
179
+ slots (no KV sharing). Dynamic sequence dim → `GrowingKVCache`.
180
+ - **KV cost: 491,520 bytes per token of context** (fp16) — 2.01 GB at 4096, 8.05 GB at 16384.
181
+ - **Resident weights, ~17.6 GB:**
182
 
183
+ | | bytes |
184
+ |---|---|
185
+ | Routed experts, `aff4` (incl. `_kpad256` zero columns) | ~14.7 GB |
186
+ | Attention + dense MLP + untied head, int4 block-32 | ~1.4 GB |
187
+ | `embed_tokens`, fp16 in-graph | ~1.5 GB |
188
+ | Router, fp16 | ~0.02 GB |
189
+ | **Total** | **~17.6 GB** |
 
 
 
190
 
191
+ | Context | KV | + weights | Verdict |
192
+ |---|---|---|---|
193
+ | 4096 | 2.01 GB | 19.6 GB | 32 GB machine |
194
+ | 8192 | 4.03 GB | 21.6 GB | 32 GB machine, marginal against a ~21–24 GB working set |
195
+ | 16384 (this manifest) | 8.05 GB | 25.6 GB | **64 GB machine** |
196
+ | 16 GB machine | — | — | **no, at any context** |
197
+
198
+ **This table is arithmetic from state shapes and weight formats — a prediction to check, not a
199
+ measured tier claim.** The residency mechanism for MoE bundles on macOS has never been
200
+ measured, and this project's one check of a bundle-bytes proxy against a real compile found it
201
+ **1.35 GiB optimistic** on a large bundle.
202
+
203
+ 25 of the 30 layers have `head_dim` 256 zero-padded to 512, and all 30 slots grow linearly even
204
+ though 25 of them only ever attend a 1024-token window. A ring-buffered sliding cache would be
205
+ roughly 5× cheaper per token; that is model authoring, not a flag.
206
+ - The bundle manifest declares `runtime_env COREAI_CHUNK_THRESHOLD=1`.
207
 
208
+ ## Measurements
 
 
209
 
210
+ **None. No measurement of any kind exists, on any machine, for either bundle.** Every figure in
211
+ this card is a byte count, an export-host reading, a box-side torch comparison, or arithmetic
212
+ from state shapes and weight formats. No token has been generated.
213
 
214
+ The intended argument for this model is throughput at comparable size against the dense 31B —
215
+ 17.6 GB vs 20 GB, with ~4B active parameters per token read through a gather kernel — and
216
+ quality per token against the 12B at ~2.1× the footprint. The nearest *measured* analogue is
217
+ LFM2.5-8B-A1B at 140 tok/s through its gather kernel against 39 tok/s for the same weights read
218
+ as a dense over-read. **Neither claim is measured for this bundle.**
219
 
220
+ One datapoint about the *checkpoint*, clearly scoped because it is not about these bundles: the
221
+ same model served remotely (Cloudflare's `@cf/google/gemma-4-26b-a4b-it`) answered eleven real
222
+ report prompts 11/11 schema-valid under a tuned recipe and passed two grounding probes that the
223
+ local 8B-A1B fails it identified a vendor-versus-subject confusion correctly and attributed an
224
+ opinion rather than asserting it as fact. That is evidence about the weights' grounding, on a
225
+ completely different serving path, and says nothing about this Core AI conversion.
226
 
227
+ ### What *has* been gated, box-side
 
 
228
 
229
+ **MoE block wiring, against the reference implementation.** `transformers` 4.57.6 (the pinned
230
+ export environment) has no `gemma4`, so the gate transcribes `Gemma4TextDecoderLayer.forward`
231
+ (the MoE-block half), `Gemma4TextRouter.forward` and `Gemma4TextExperts.forward` from upstream
232
+ and runs both implementations on the real layer-0 weights in fp32:
233
 
234
+ ```
235
+ T=1: max|dFFN|=7.629e-05 rel=3.453e-07 | same top-8 ids: True max|dw|=0.000e+00
236
+ T=4: max|dFFN|=9.537e-05 rel=2.735e-07 | same top-8 ids: True max|dw|=0.000e+00
237
+ ```
 
 
 
238
 
239
+ Identical expert selection, bit-identical routing weights, ~3.5e-7 relative error on the block
240
+ output. **This gates the authoring** — the wiring, the fused-weight split, the activation choice,
241
+ the norm placement, the router maths. It does **not** gate the exported graph, the int4 rounding,
242
+ or either Metal kernel.
 
 
243
 
244
+ **Attention kernel numerics** (`hybrid-mf64/`). The 26B is a *third* distinct block-GQA mapping
245
+ and was gated as its own case: 16 query heads, **2** global KV heads replicated across 8 cache
246
+ slots, so the kernel's `kv = h / (H / slots)` = `h//2` must land on real head `(h//2)//4 = h//8`.
247
 
248
+ | | vs the MPSGraph composite | vs an fp32 ground truth |
249
+ |---|---|---|
250
+ | max abs, S=137 | 2.50e-2 | **9.74e-4 PSNR 74.9 dB** |
251
+ | max abs, S=1024 | 1.17e-2 | 9.76e-4 75.1 dB |
252
+ | the composite itself, vs fp32 | — | 53.0 and 57.9 dB |
253
+
254
+ The divergence from the composite is the composite's own fp16 error; against fp32 the kernel is
255
+ the more accurate of the two. The 12B (1 global head, rep 8) and 31B (4 global heads, rep 4 over
256
+ 16 slots) rows are in the same file as controls — a mapping bug that cancelled in those would
257
+ show here. This gates the mapping and the scale, **not the Metal source** (no GPU on the box).
258
+
259
+ **Trace accounting** (`hybrid-mf64/`): all 5 full layers took the metal branch at S=1 **and** the
260
+ composite branch at S=64, 5/5 both ways — the export fails if either count is 0. 30 MoE layers
261
+ metalized.
262
+
263
+ ## `hybrid-mf64/` — decode and prefill from one copy of the weights
264
+
265
+ The 26B-A4B with a `prefill` entrypoint. Two things had to be per-entrypoint at once, and both
266
+ are:
267
+
268
+ - **Experts.** `BatchedMetalSwitchGLU` sorts the `S*k` (token, expert) pairs of a 64-token chunk
269
+ by expert id so each expert slab is read once, and falls through to `MetalSwitchGLU.forward`
270
+ verbatim whenever `b*s == 1`. One metalization, both entrypoints, **decode arithmetic
271
+ unchanged** — `main` traces the identical q=1 gather matvec the published decode bundle
272
+ carries.
273
+ - **Attention.** The 26B inherits the dense 12B/31B full-attention layers, so it needs the
274
+ flash-decode Metal kernel for the scratch-heap reason, and that kernel is structurally q=1.
275
+ `SplitSDPA` branches on `query.shape[2]`, a concrete int in each trace, so `main` gets the
276
+ kernel and `prefill` gets the MPSGraph composite (query axis + `is_causal=True` intra-chunk
277
+ mask). Same technique as the 31B `hybrid-pf64/` bundle.
278
+
279
+ **This is `mf64`, not `mf64-tp`, and it cannot be `-tp` today.** The token-tiled prefill kernel
280
+ is **sym8-only** — `TiledBatchedMetalSwitchGLU.__init__` raises for any other scheme — and the
281
+ 26B ships `aff4` experts for the QAT-grid reason above. An `aff4` tiled kernel is future work,
282
+ gated on the 8B `mf64-tp` Mac verdict (**which came back negative**: that kernel failed its gate
283
+ on divergence and delivered 1.17× rather than the predicted 6.5×) and then an `aff4` equality
284
+ proof. What this bundle uses instead is the **shipped** batched path, the one behind the 8B
285
+ `mf64` bundle that has a Mac measurement.
286
+
287
+ **The falsifiable prediction, for whoever runs it first.** The batched kernel's anchor is the
288
+ 8B-A1B `mf64` bundle's measured **6.3 ms/prompt-token**, and this bundle should land in that
289
+ mechanism's class rather than the ~21.5 ms/token of a decode-only bundle walking the prompt one
290
+ token at a time. Do not read 6.3 ms as the expected number: the 26B activates ~4B parameters per
291
+ token against the 8B-A1B's ~1B, and its dense MLP runs in parallel with the expert branch, so
292
+ several times 6.3 ms would still confirm the mechanism. **What would falsify it** is prefill
293
+ landing at or above the per-token cost of decode — that would mean the sorted grouped-GEMM path
294
+ is not engaging and the chunk is being walked serially.
295
+
296
+ ### Decode-only, and a prefill variant is not deliverable by a flag
297
+
298
+ `gpu-pipelined/` has no `prefill` function, for two independent kernel reasons:
299
 
300
+ 1. `--metal-sdpa`'s flash-decode kernel is structurally q=1 with no causal mask. The full
301
+ attention layers need that kernel for the same scratch-heap reason the dense 12B/31B do.
302
+ 2. `MetalSwitchGLU.forward` is itself decode-onlyit asserts a token batch of 1 and expands the
303
+ single activation row across the routed slots.
 
304
 
305
+ `hybrid-mf64/` is the bundle that solves both at once.
 
 
306
 
307
+ ## Usage
 
 
 
308
 
309
+ Swift Package Manager, via [CoreAIKit](https://github.com/john-rocky/coreai-kit) — a community
310
+ package, not affiliated with Apple, requiring macOS 27 beta:
311
 
312
+ ```swift
313
+ .package(url: "https://github.com/john-rocky/coreai-kit", branch: "main")
314
+ // target dependency: .product(name: "CoreAIKit", package: "coreai-kit")
315
+ ```
316
+
317
+ ```swift
318
+ import CoreAIKit
319
+
320
+ let model = ModelID(
321
+ "visible-cx/gemma4-26b-a4b-CoreAI",
322
+ path: "hybrid-mf64/gemma4_26b_a4b_qat_decode_int4linsym_moeaff4_msdpa_g8_pf64")
323
+
324
+ let chat = try await ChatSession(model: model) // ~17.6 GB download on first use
325
+ ```
326
 
327
+ These are plain 2-input graphs **no PLE table sidecar, no static input buffers** — so either
328
+ engine will load them, and the sequential engine is available for grammar-constrained decoding
329
+ (`configuration.engineVariant = .sequential`). Budget disk for the compiled graph on top of the
330
+ 17.6 GB bundle, and expect a long cold compile.
331
+
332
+ ## Integrity
333
+
334
+ Core AI `.aimodel` bundles are **not byte-reproducible**: the exporter is not deterministic even
335
+ against itself. Verify by digesting the exact published bytes rather than by rebuilding. Every
336
+ bundle carries `main.hash`, the raw 32 bytes of `sha256(main.mlirb)`; on the Hub the same value
337
+ is recoverable from the LFS oid without fetching the 17.6 GB file.
338
 
339
  ## Status
340
 
341
  | Artifact | Status |
342
  |---|---|
343
+ | `gpu-pipelined/…_moeaff4_msdpa_g8` | **UNQUALIFIED** — never executed on target hardware. Established: the checkpoint is the QAT source, the graph exports, the producer fingerprint passes, and the MoE block wiring matches the reference implementation to 3.5e-7 relative error. |
344
+ | `hybrid-mf64/…_msdpa_g8_pf64` | **EXPERIMENTAL — not qualified.** Adds an S=64 `prefill` entrypoint over the same weights, with box-side gates on the block-GQA mapping and the trace accounting. No Mac-side oracle, no device benchmark. Do not route production traffic to it. |
345
 
346
  A qualifying run would need, in priority order:
347
 
348
+ 1. **A decode oracle** — generate and compare against an fp32 HF reference. This is the gate that
349
+ catches an int4 or kernel-level error which the authoring gate cannot see.
350
+ 2. **Isolate the two Metal kernels.** `gather_qmm` (the `aff4` variant) and the flash-decode SDPA
351
+ have never been in the same graph, on any model. If decode produces garbage, re-export without
352
+ `--metal-sdpa` first — it is an optimisation, the MoE kernel is not.
353
+ 3. **Chunk parity** on `hybrid-mf64/`. Two independent reasons to check rather than assume: the
354
+ entrypoints use different attention implementations, and the expert path does a sort/unsort
355
+ round trip the q=1 path does not.
356
+ 4. **Measure residency**, both dirty footprint and wired growth. No MoE Gemma bundle has been
357
+ measured this way, and the ~17.6 GB figure above is arithmetic.
358
+ 5. **Compare tok/s against the dense 12B and 31B** on the same machine. That is the whole case
359
+ for this model.
360
+ 6. **Router sanity at depth.** 128 experts / top-8 with a learned `per_expert_scale` is far
361
+ sparser routing than the LFM2.5-8B-A1B's 32/top-4. Check expert-utilisation spread on a real
362
+ prompt set before trusting long-form output.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
363
 
364
  ## License
365
 
366
+ Google publishes the upstream QAT checkpoint under **Apache-2.0** with a `license_link` to the
367
+ [Gemma 4 license](https://ai.google.dev/gemma/docs/gemma_4_license), and this repo mirrors that
368
+ declaration. Use is governed by those terms and by the
369
+ [Gemma Prohibited Use Policy](https://ai.google.dev/gemma/prohibited_use_policy); the
370
+ obligations travel with any redistribution of these bundles. The contribution here is the port
371
+ and the recipe, not the weights.