HiramKHackenbacker commited on
Commit
e057ddd
Β·
verified Β·
1 Parent(s): bc12efc

docs: descriptive model card

Browse files
Files changed (1) hide show
  1. README.md +325 -73
README.md CHANGED
@@ -1,87 +1,339 @@
1
  ---
 
 
2
  base_model: google/gemma-4-26B-A4B-it-qat-q4_0-unquantized
3
  library_name: coreai
4
- license: gemma
5
  tags:
6
- - core-ai
7
- - aimodel
8
- - moe
9
- - on-device
10
- - apple-silicon
11
- extra_gated_heading: Access Gemma on Hugging Face
12
- extra_gated_description: This artifact is a derivative of
13
- google/gemma-4-26B-A4B-it-qat-q4_0-unquantized and is governed by the Gemma Terms of
14
- Use, the Gemma Prohibited Use Policy and the Gemma license.
 
15
  ---
16
 
17
- # Gemma 4 26B-A4B - Core AI (MoE, int4)
 
 
 
 
 
 
 
 
 
 
 
18
 
19
- `gpu-pipelined/gemma4_26b_a4b_qat_decode_int4linsym_moeaff4_msdpa_g8` - a decode-only (S=1) Core AI bundle for Apple's pipelined GPU
20
- engine, converted from
21
- [`google/gemma-4-26B-A4B-it-qat-q4_0-unquantized`](https://huggingface.co/google/gemma-4-26B-A4B-it-qat-q4_0-unquantized).
 
 
 
 
 
 
22
 
23
- 26.5B total parameters, **128 experts, top-8 routed** (~4B active per token), 30 layers
24
- (25 sliding / 5 full attention), vocab 262,144.
25
 
26
  | | |
27
  |---|---|
28
- | Bundle bytes | 17,580,059,414 |
29
- | Context (manifest) | 16384 |
30
- | Functions | main |
31
- | Producer | `coreai-core 1.0.0b2` |
32
- | Created | `20260817T215629Z` |
33
- | KV cache | fp16, one growing pair `[30, 1, 8, S, 512]` |
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
 
35
- ## Compression
 
 
 
 
 
36
 
37
- | Weights | Scheme |
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
  |---|---|
39
- | Routed experts (128/layer) | **affine int4**, per-(output-row, K-block-32) scale+bias, MSE-optimal clip - read through the `gather_qmm` custom Metal kernel so decode fetches only the 8 routed experts, not all 128 |
40
- | Attention, dense MLP, LM head (untied) | linear int4, per-block-32, plain absmax (`symmetric`) - the ggml q4_0 grid the QAT checkpoint was trained on |
41
- | Router (`router.proj`, `scale`, `per_expert_scale`) | **fp16** - routers are the quantization-sensitive part of an MoE and this one is ~0.02 GB |
42
- | `embed_tokens` (in-graph) | fp16 - the gather stays exact |
43
- | Norms / RoPE / SDPA | no quantized weight |
44
-
45
- Affine int4 block-32 was chosen over k-means int4 because it can represent the
46
- checkpoint's QAT q4_0 grid exactly (`scale = d`, `bias = -8d`), whereas a 16-entry
47
- codebook spanning the whole K axis cannot follow per-block QAT scales.
48
-
49
- ## Hardware target
50
-
51
- **Macs with >= 24-32 GB unified memory.** Resident weights are ~17.6 GB (the bundle
52
- size above is the weights), before any KV cache; KV costs **491,520 B per token of
53
- context** (fp16, one growing `[30, 1, 8, S, 512]` pair), i.e. 2.0 GB at 4096 and 8.1 GB
54
- at the manifest's 16384.
55
-
56
- **This bundle is NOT for the 16 GB tier.** A 16 GB Mac's Metal
57
- `recommendedMaxWorkingSetSize` is roughly 10.7 GB - the weights alone are over that
58
- before a single token of context. Do not select it there.
59
-
60
- ## Status: UNQUALIFIED - community tier
61
-
62
- **Converted and fingerprinted; never run.** The conversion box is Linux with no GPU
63
- delegate, so this bundle has never produced a token, and **no Mac-side qualification is
64
- scheduled** - the machine that commissioned it cannot load it. Every performance or
65
- quality claim you might expect here is therefore absent on purpose: there are no
66
- measured tok/s, no oracle margin, no residency measurement.
67
-
68
- What *was* gated, on the box: the MoE branch against a literal transcription of
69
- `transformers`' `Gemma4TextDecoderLayer` / `Gemma4TextRouter` / `Gemma4TextExperts` on
70
- the real layer-0 weights - fp32 relative error **3.5e-7**, identical top-8 expert
71
- selection, bit-identical routing weights. That gates the **authoring** (block wiring,
72
- the fused `gate_up_proj` split, the GELU gate, router maths). It does not gate the
73
- exported graph, the int4 rounding, or either Metal kernel.
74
-
75
- Two Metal kernels run in this graph - `gather_qmm` (`aff4`) for the experts and the
76
- flash-decode SDPA for the full-attention layers - and **they have never been combined in
77
- any model before**. If decode produces garbage, re-export without `--metal-sdpa` first:
78
- it is an optimisation, the MoE kernel is not, and that isolates the fault in one run.
79
-
80
- Decode-only (`main`, S=1). There is no prefill function: the flash-decode SDPA kernel is
81
- structurally q=1 with no causal mask, and `MetalSwitchGLU` is likewise decode-only, so a
82
- prefill variant needs two new kernels rather than a flag.
83
-
84
- ## Licence
85
-
86
- Derivative of a Gemma model; the **Gemma Terms of Use**, the Gemma Prohibited Use Policy
87
- and the Gemma licence apply.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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:
8
+ - core-ai
9
+ - aimodel
10
+ - apple-silicon
11
+ - on-device
12
+ - quantized
13
+ - int4
14
+ - qat
15
+ - moe
16
+ - mixture-of-experts
17
+ - gemma4
18
  ---
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 the [Visible](https://visible.cx) project. **Derivative artifact**:
24
+ Google's QAT-trained weights rounded onto int4 and re-expressed as a Core AI graph, with the
25
+ 128-expert sparse branch of every layer lowered onto a Metal gather kernel.
26
+
27
+ This is a **sparse mixture-of-experts** model β€” 26.5B total parameters, roughly 4B active
28
+ per token β€” and it is the first Gemma 4 MoE to exist as a Core AI bundle at all. Producing
29
+ it required authoring a Gemma 4 MoE decoder layer, not just wiring two finished halves
30
+ together: `coreai_models` shipped five Gemma 4 modules and not one line of MoE in any of
31
+ them.
32
 
33
+ > ⚠️ **UNQUALIFIED, community tier.** Nothing here has produced a token, on any machine, and
34
+ > **no Mac-side qualification is scheduled** β€” the machine that commissioned the conversion
35
+ > cannot load the result. It is published so people with larger Macs have the artifact. It
36
+ > is not published as a verified one. Read the qualification section before using it for
37
+ > anything.
38
+ >
39
+ > ⚠️ **The 16 GB tier is excluded outright.** Weights are ~17.6 GB resident, against a
40
+ > 16 GB Mac's entire ~10.7 GB Metal working set. Declared target: **Macs with β‰₯24–32 GB
41
+ > unified memory.**
42
 
43
+ ## Provenance
 
44
 
45
  | | |
46
  |---|---|
47
+ | Base checkpoint | `google/gemma-4-26B-A4B-it-qat-q4_0-unquantized` @ `f1e06dc520982d9b9edd76859fdb7ab209449949` |
48
+ | Checkpoint size | 51,644,341,801 B, bf16, 1,013 tensors across 2 shards; ungated |
49
+ | Recipe | `export_gemma4_moe_decode_pipelined.py int4lin --lin-sym --metal-sdpa --max-ctx 16384` |
50
+ | 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) |
51
+ | Toolchain | `coreai-torch 0.4.1`, `coreai-core 1.0.0b2`, `coreai-opt 0.2.1`, `torch 2.9.0` |
52
+ | Producer fingerprint | `coreai-core 1.0.0b2` β€” **PASS**, verified on the inner asset `metadata.json` |
53
+ | Asset creation date | `20260817T215629Z` |
54
+ | Vocab | 262,144 |
55
+ | Conversion | **102.25 GB peak RSS** (`VmHWM`), Linux x86_64 on a 247 GB box |
56
+
57
+ "QAT-unquantized" means QAT-*trained*, stored bf16; the int4 rounding happens at export onto
58
+ the ggml q4_0 grid the training already targeted. There is no separate pre-quantised int4
59
+ artifact to prefer.
60
+
61
+ ### Architecture, and why this is a small 31B rather than a big E4B
62
+
63
+ | | 26B-A4B | 31B | E4B |
64
+ |---|---|---|---|
65
+ | `hidden_size` | **2816** | 5376 | 2560 |
66
+ | layers | **30** (25 sliding / 5 full) | 60 | 42 |
67
+ | dense MLP `intermediate_size` | **2112** | 21504 | 10240 |
68
+ | `num_experts` / `top_k` | **128 / 8** | β€” | β€” |
69
+ | `moe_intermediate_size` | **704** | β€” | β€” |
70
+ | attention heads | **16** | 32 | 8 |
71
+ | KV heads (sliding / full) | **8 / 2** | 16 / 4 | 2 / β€” |
72
+ | `head_dim` / `global_head_dim` | **256 / 512** | 256 / 512 | 256 / 512 |
73
+ | `sliding_window` | **1024** | 1024 | 512 |
74
+ | Per-Layer Embeddings | **none** | none | 256 |
75
+ | KV-shared layers | **0** | 0 | 18 |
76
+
77
+ Same attention family as the dense 12B/31B, same dual head_dim, same `attention_k_eq_v`
78
+ (full layers carry no `v_proj`), same dual RoPE, same softcap. **Everything the E2B/E4B path
79
+ exists to handle is absent here**, which is why the port started from the dense model rather
80
+ than the pipelined PLE one, and why there is no gather-table sidecar in this repo.
81
+
82
+ ### The block that had to be authored
83
+
84
+ Every layer carries a dense MLP **and** a sparse branch, in parallel β€” this is not "MoE
85
+ replaces the FFN", and getting that wrong would have produced a plausible model with wrong
86
+ outputs:
87
+
88
+ ```python
89
+ residual = x # post-attention hidden
90
+ h = mlp(pre_feedforward_layernorm(x)) # dense branch
91
+ h1 = post_feedforward_layernorm_1(h)
92
+ w, idx = router(residual) # routes on the RAW residual
93
+ h2 = experts(pre_feedforward_layernorm_2(residual), idx) # sparse branch
94
+ h2 = post_feedforward_layernorm_2(sum_k w_k * h2_k)
95
+ x = residual + post_feedforward_layernorm(h1 + h2)
96
+ x = x * layer_scalar
97
+ ```
98
+
99
+ Two details are load-bearing and both are silently wrong if missed: **the router reads the
100
+ un-normalised residual**, and **the experts are GELU-gated** (`gelu_pytorch_tanh`), where the
101
+ `SwitchGLU` primitive's default is SiLU. Building it with the default would have produced a
102
+ model that loads, traces, exports and fingerprints β€” and generates wrong text.
103
+
104
+ The router itself: scale-free RMSNorm β†’ `Γ— scale Γ— hidden**-0.5` β†’ linear to 128 β†’ fp32
105
+ softmax over all experts β†’ top-8 β†’ renormalise to sum 1 β†’ multiply by a learned
106
+ `per_expert_scale` gathered at the selected ids.
107
+
108
+ ### Compression recipe
109
 
110
+ | Weights | Scheme | Why |
111
+ |---|---|---|
112
+ | **Routed experts** (128/layer, 22.84 G params) | **affine int4** (`aff4`), per-(output row, K-block-32) scale + bias, MSE-optimal clip over 6 candidates | see below |
113
+ | Attention, dense MLP, untied LM head | linear **int4** per-block-32, plain absmax (`--lin-sym`) | the ggml q4_0 grid the QAT checkpoint was trained on β€” the 12B/31B recipe verbatim |
114
+ | **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 |
115
+ | `embed_tokens` (in-graph) | fp16 | the gather stays exact; the head is untied and quantized separately |
116
 
117
+ `sym8` on the experts would have put them at 22.8 GB before anything else β€” a bundle nobody
118
+ can load β€” so int4 is not optional. `aff4` was chosen over a k-means palette (`km4`)
119
+ deliberately and against the usual zoo preference, because that preference is about
120
+ **non-QAT** weight distributions: this checkpoint was QAT-trained on per-block-32 q4_0
121
+ (`w = (q-8)Β·d`, 16 uniform levels), and an affine int4 block-32 grid can represent that
122
+ **exactly** (`scale = d`, `bias = -8d`). A palette fits one 16-entry codebook per 32 output
123
+ rows across the whole K axis and structurally cannot follow per-block QAT scales β€” choosing
124
+ it would throw away the reason for using a QAT checkpoint. The price is aux bytes: `aff4`
125
+ stores an fp16 scale *and* bias per 32-element K block, i.e. **5 bits/param effective**
126
+ against `km4`'s ~4.03. That is ~3 GB of the bundle, and it is the right ~3 GB.
127
+
128
+ The `down` projection's K of 704 is padded to 768 by the kernel's automatic `_kpad256`
129
+ treatment β€” 64 zero columns, ~0.5% wasted expert bytes. `gate`/`up` need no padding.
130
+
131
+ **The gather kernel is the whole point.** Without `MetalSwitchGLU`, the sparse branch lowers
132
+ to a dense matmul that reads **all 128 experts every token** β€” a 16Γ— over-read that would
133
+ make this bundle slower per token than the dense 31B while being worse at everything.
134
+
135
+ ### New source, kept with the documentation
136
+
137
+ Three files were authored for this port and are a clean, self-contained zoo contribution;
138
+ nothing in the shared toolchain was modified:
139
+
140
+ | File | What |
141
  |---|---|
142
+ | `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 |
143
+ | `coreai_models/models/macos/gemma4_moe_pipelined.py` | `Gemma4MoePipelinedForCausalLM` β€” subclasses the dense pipelined core, overrides one method. The attention half needed nothing: the unified KV pair, the head-count repeat, the head_dim zero-pad and the two-state engine contract are all inherited |
144
+ | `coreai-model-zoo/conversion/export_gemma4_moe_decode_pipelined.py` | the recipe |
145
+
146
+ ## Contents
147
+
148
+ | Path | Bytes | Files | Manifest context | Functions |
149
+ |---|---:|---|---|---|
150
+ | `gpu-pipelined/gemma4_26b_a4b_qat_decode_int4linsym_moeaff4_msdpa_g8` | 17,580,059,414 | 8 | 16384 | main (decode only) |
151
+
152
+ | File | Bytes |
153
+ |---|---:|
154
+ | `…aimodel/main.mlirb` | 17,547,866,346 |
155
+ | `…aimodel/main.hash` | 32 |
156
+ | `…aimodel/metadata.json` | 105 |
157
+ | `metadata.json` (bundle manifest) | 689 |
158
+ | `tokenizer/tokenizer.json` | 32,169,626 |
159
+ | `tokenizer/chat_template.jinja` | 18,683 |
160
+ | `tokenizer/tokenizer_config.json` | 3,730 |
161
+ | `tokenizer/generation_config.json` | 203 |
162
+
163
+ Context was set to 16384 once, from the start, because `--max-ctx` does not change the
164
+ exported graph β€” it sets `language.max_context_length` in the manifest and nothing else.
165
+ Lower it if the machine cannot afford the KV; that is a metadata edit, not a re-export.
166
+
167
+ ### Decode only β€” and a prefill variant is not deliverable
168
+
169
+ There is no `prefill` function, and one cannot be added by a flag. Two independent kernel
170
+ blockers:
171
+
172
+ 1. `--metal-sdpa`'s flash-decode kernel is structurally q=1 with no causal mask. The full
173
+ attention layers need that kernel for the same scratch-heap reason the dense 12B/31B do,
174
+ so this model inherits the blocker verbatim.
175
+ 2. `MetalSwitchGLU.forward` is itself decode-only β€” it asserts a token batch of 1 and
176
+ expands the single activation row across the routed slots. A `BatchedMetalSwitchGLU`
177
+ (sort-by-expert grouped GEMM) does exist for q>1, so this half is *reachable*, but it has
178
+ never been exported for any model.
179
+
180
+ A prefill variant therefore needs a new flash-**prefill** Metal kernel plus the first export
181
+ of the batched MoE kernel, plus a numerics gate for each. That is kernel work, not a
182
+ conversion job. Prompts are prefilled one token at a time.
183
+
184
+ ## Requirements
185
+
186
+ - **Apple silicon Mac with β‰₯24–32 GB unified memory**, Core AI runtime. The 16 GB tier is
187
+ excluded and not narrowly.
188
+ - **Engine contract: 2 inputs** (`input_ids`, `position_ids`) β†’ logits, one growing KV pair,
189
+ no static inputs and no per-step mask.
190
+ - **States:** `keyCache` / `valueCache` `Float16, 30 Γ— 1 Γ— 8 Γ— ? Γ— 512` β€” one growing pair,
191
+ 30 slots (no KV sharing), read straight off the exported bundle. The dynamic sequence dim
192
+ means the runtime resolves `GrowingKVCache`, not a static allocation at the manifest
193
+ maximum.
194
+ - **KV cost: 491,520 bytes per token of context** (fp16):
195
+
196
+ | Context | KV |
197
+ |---|---|
198
+ | 4096 | 2.01 GB |
199
+ | 8192 | 4.03 GB |
200
+ | **16384** | **8.05 GB** |
201
+ | 32768 | 16.1 GB |
202
+
203
+ - **Resident weights, ~17.6 GB:**
204
+
205
+ | | bytes |
206
+ |---|---|
207
+ | Routed experts, `aff4` (incl. `_kpad256` zero columns) | ~14.7 GB |
208
+ | Attention + dense MLP + untied head, int4 block-32 | ~1.4 GB |
209
+ | `embed_tokens`, fp16 in-graph | ~1.5 GB |
210
+ | Router, fp16 | ~0.02 GB |
211
+ | **Total** | **~17.6 GB** |
212
+ | + KV at 4096 | ~19.6 GB |
213
+ | + KV at 16384 | ~25.7 GB |
214
+
215
+ | Tier | Verdict |
216
+ |---|---|
217
+ | 16 GB | **no** β€” weights alone are ~1.6Γ— the entire ~10.7 GB Metal working set |
218
+ | 24–32 GB | yes at ctx ≀ 8192 (~21.6 GB); 16384 is tight |
219
+ | 64 GB | yes, at any context |
220
+
221
+ ⚠️ **Do not turn this table into a hard memory floor in a model catalog.** It is
222
+ arithmetic from state shapes and weight formats β€” a prediction to check, not a measured
223
+ tier claim. The residency mechanism for MoE bundles on macOS (mmap'd vs wired expert
224
+ slabs) has **never been measured for any MoE bundle**, and the low end of the range above
225
+ is deliberately conservative because of that. The first machine that loads this model
226
+ should produce the measurement before anything depends on it.
227
+
228
+ 25 of the 30 layers have `head_dim` 256 zero-padded to 512, and all 30 slots grow linearly
229
+ even though 25 of them only ever attend a 1024-token window. A ring-buffered sliding cache
230
+ would be roughly 5Γ— cheaper per token here. That is model authoring, not a flag.
231
+
232
+ ## Measured performance
233
+
234
+ **None. On any machine.** No token has been generated from this bundle. Every figure in
235
+ this card is measured on Linux (export cost, byte counts) or derived from state shapes and
236
+ weight formats; **none is a runtime measurement**, and no Mac-side run is scheduled.
237
+
238
+ For contrast, every measured number elsewhere in this catalog comes from a 10-row guided
239
+ enrichment harness on a 16 GB M2 Pro Mac. This model cannot load on that machine, so there
240
+ is not a slower number here β€” there is no number.
241
+
242
+ The intended argument for this model is speed at comparable size against the dense 31B β€”
243
+ 17.6 GB vs 20 GB, but ~4B active parameters per token read through a gather kernel instead
244
+ of 31B dense β€” and quality per token against the 12B at ~2.1Γ— the footprint. The nearest
245
+ measured analogue is the LFM2.5 8B-A1B at 140 tok/s through its gather kernel versus 39
246
+ tok/s for the same weights read as a dense over-read. **Neither claim is measured for this
247
+ bundle and neither is scheduled to be.**
248
+
249
+ ## Qualification status
250
+
251
+ | Artifact | Status |
252
+ |---|---|
253
+ | `gpu-pipelined/gemma4_26b_a4b_qat_decode_int4linsym_moeaff4_msdpa_g8` | **UNQUALIFIED β€” community tier.** Never measured on target hardware; no Mac-side qualification scheduled. What *is* established: the checkpoint is the QAT source, the block wiring is faithful to `transformers` at 3.5e-7 relative error on real layer-0 weights, the graph exports, and the producer fingerprint passes. That is the whole of it. |
254
+
255
+ What a qualifying machine would have to run, in priority order:
256
+
257
+ 1. **A decode oracle** β€” generate and compare against an fp32 HF reference. This is the gate
258
+ that catches an int4 or kernel-level error which the authoring gate below cannot see.
259
+ 2. **The two Metal kernels have never run together.** `gather_qmm` (the `aff4` variant) and
260
+ the flash-decode SDPA have never been in the same graph, on any model. If decode produces
261
+ garbage, re-export without `--metal-sdpa` first β€” it is an optimisation, the MoE kernel is
262
+ not β€” and that isolates which kernel is at fault in one run.
263
+ 3. **Measure residency properly**, both numbers: `/usr/bin/time -l` (footprint = dirty;
264
+ maximum resident = including mmap) and `vmmap --summary <pid>` during generation. This is
265
+ the first MoE bundle Visible has built and the tier map has been waiting on exactly this.
266
+ 4. **Compare tok/s against the dense 12B and 31B** on the same machine. The entire case for
267
+ this model is that ~4B active params through the gather kernel beats 12B dense on
268
+ quality-per-second. If it does not, the bundle has no slot.
269
+ 5. **Router sanity at depth.** 128 experts / top-8 with a learned `per_expert_scale` is far
270
+ sparser routing than the LFM 8B-A1B's 32/top-4. Check expert-utilisation spread on a real
271
+ prompt set before trusting long-form output.
272
+
273
+ ## Verification
274
+
275
+ ### The oracle gate β€” what it proves, and what it does not
276
+
277
+ `transformers` 4.57.6 (the pinned export environment) has no `gemma4` at all, so there was
278
+ no importable reference on the conversion box. Rather than install `transformers` 5.x beside
279
+ a pinned toolchain, the gate transcribes `Gemma4TextDecoderLayer.forward` (the MoE-block
280
+ half), `Gemma4TextRouter.forward` and `Gemma4TextExperts.forward` literally from upstream
281
+ and runs both implementations on the **real layer-0 weights** in fp32:
282
+
283
+ ```
284
+ T=1: max|dFFN|=7.629e-05 rel=3.453e-07 | same top-8 ids: True max|dw|=0.000e+00
285
+ T=4: max|dFFN|=9.537e-05 rel=2.735e-07 | same top-8 ids: True max|dw|=0.000e+00
286
+ ORACLE PASS (fp32, real layer-0 weights)
287
+ ```
288
+
289
+ Identical expert selection, bit-identical routing weights, ~3e-7 relative error on the block
290
+ output. **That gates the authoring** β€” the wiring, the fused-weight split, the activation
291
+ choice, the norm placement, the router maths. It does **not** gate the exported graph, the
292
+ int4 rounding, or either Metal kernel. The attention half was deliberately not re-gated: it
293
+ is the dense `Attention` class already shipped in the 12B and 31B bundles.
294
+
295
+ ### Everything else
296
+
297
+ - **Producer fingerprint:** the inner `<name>.aimodel/metadata.json` reads
298
+ `producer: "coreai-core 1.0.0b2"` β€” PASS. `coreai-torch 0.4.0` / `coreai-core 1.0.0b1`
299
+ produce ODIE-poisoned bundles that abort **in-process** at load, taking the host
300
+ application down uncatchably. The field is in the inner asset metadata; the bundle
301
+ manifest never carries a `producer` field.
302
+ - **Stop token:** `eos_token = "<turn|>"` (id 106). Gemma 4 ends a *turn* with `<turn|>`; the
303
+ raw upstream `<eos>` ends the whole sequence and a host stopping on it overruns every
304
+ reply. Unlike the E2B/E4B/12B/31B bundles, where this correction was applied by hand after
305
+ export, **this recipe applies it in the export script itself.**
306
+ - **`chat_template.jinja` is present** (18,683 B). The E2B/E4B export scripts miss it because
307
+ their `allow_patterns` only fetch `.json` files; this script fetches and copies it
308
+ explicitly and *raises* if it is absent.
309
+ - **Determinism:** this bundle was exported twice from the same checkpoint with identical
310
+ flags. The two runs differ by **28 bytes** (17,580,059,442 vs 17,580,059,414) and their
311
+ peak RSS by 164 kB. Conversion is not byte-deterministic, so integrity rests on per-file
312
+ SHA-256 of the artifact actually published, never on rebuilding.
313
+ - **Not wired into the app.** There is no Core AI model-id case for this model, and adding
314
+ one means an enum case, a descriptor, a unified-id mapping and a catalog variant β€” four
315
+ places, where the descriptor lookup force-unwraps, so a half-done addition crashes rather
316
+ than degrades. It should not be wired in before gates 1–3 above pass; an unqualified entry
317
+ in an automatic model-selection path is worse than no entry.
318
+
319
+ ### A note on why this repo is public
320
+
321
+ This bundle was converted before it could be published: the `visible-cx` organisation was at
322
+ its private-storage cap and the LFS push returned
323
+ `403 Private repository storage limit reached`. Of the three ways out β€” publish public, raise
324
+ the plan, or delete a pinned artifact to make room β€” publishing publicly under the Gemma
325
+ terms is how Google itself distributes Gemma derivatives, and that decision was taken by the
326
+ project owner rather than by a conversion run. Until it was, the bundle existed on exactly
327
+ one machine, and recreating it costs a 50 GB download plus ~25 minutes on a 247 GB box at
328
+ 102 GB peak.
329
+
330
+ ## License
331
+
332
+ The upstream QAT checkpoint carries Apache-2.0 metadata and is ungated, but it is a Gemma
333
+ model and **the Gemma terms apply downstream**, which is why this repo declares
334
+ `license: gemma`. Use of these weights is subject to the
335
+ [Gemma Terms of Use](https://ai.google.dev/gemma/terms), the
336
+ [Gemma Prohibited Use Policy](https://ai.google.dev/gemma/prohibited_use_policy) and the
337
+ [Gemma 4 license](https://ai.google.dev/gemma/docs/gemma_4_license). Those obligations travel
338
+ with any redistribution of this bundle. Visible's contribution is the port, the recipe and
339
+ the honest statement of what has and has not been verified β€” not the weights.