Web TippsUse custom web fonts in Google Sheets charts(08.09.2026 um 17:05 Uhr)
Web TippsIntroducing the new 1Password App for Google Chat(08.09.2026 um 18:02 Uhr)
Web TippsUse custom web fonts in Google Sheets charts(08.09.2026 um 17:05 Uhr)
Web TippsIntroducing the new 1Password App for Google Chat(08.09.2026 um 18:02 Uhr)

🔧 Programmierung 🕛 vor 1 Monat 7 Min Lesezeit
0

Porting a 128-expert MoE (Gemma-4 26B-A4B) to AWS Inferentia2 — where every rank weighted the wrong experts

↗ Quelle (dev.to)
🗣️ Stimme:
📑 Inhaltsübersicht

Of the five Gemma-4 models I ported to AWS Inferentia2, the 26B-A4B was the one that was supposed to

be impossible: a Mixture of Experts. It compiled on the first try — then produced nothing. The

device output was empty. The CPU reference was perfect. My unit tests all passed. The bug was in a

place I'd have sworn couldn't have one.




This is the MoE entry in the series. The dense models (E2B/E4B/12B/31B) are hard because of

architecture; the 26B is hard because it's sparse — 128 experts, top-8 routing — and none of the

AWS vendor stack has ever traced an MoE for Gemma-4.




























Model
google/gemma-4-26B-A4B-it — MoE, ~4B active / 26B total, model_type: gemma4
Hardware
inf2.24xlarge — 12 NeuronCores / 192 GB HBM, TP=8
Result Device greedy decode == CPU fp32 reference (SEQ_MATCH True); prefill 77 ms; "The capital of France is Paris."
Artifacts Docker Hub xbill9/gemma4-optb-26b · HF xbill9/gemma-4-26B-A4B-it-inferentia2






"A4B" is a lie your memory budget will not forgive



The name says 26B-A4B: ~4 billion active parameters per token. That sounds like a 4B model. It

is not. All 128 experts (~49 GB) must be resident in HBM — top-8 routing only reduces compute, not

footprint. So you budget for a 26B, not a 4B, and it needs the 24xlarge's 192 GB, same class as the

2×-larger dense 31B. (This bites again later when we try to shrink it onto a smaller box.)





The architecture: a dual-path FFN nobody warned me about



I inspected the transformers-5.13 module on a meta device before writing a line of port code, and it's

weirder than "MLP replaced by MoE." Every one of the 30 layers runs a shared dense MLP in parallel

with the 128-expert MoE
, combined and passed through four feed-forward layernorms:




CODE
residual (post-attention)
├─ dense: pre_feedforward_layernorm → mlp (2112) → post_feedforward_layernorm_1 ─┐
└─ moe: pre_feedforward_layernorm_2 → router → 128 experts (704) → post_feedforward_layernorm_2 ─┤
hidden = dense + moe ────────────────────────────────────┘
→ post_feedforward_layernorm → + residual → × layer_scalar








  • Router (Gemma4TextRouter): its own RMSNorm + a scale + a per_expert_scale, then
    softmax(128, fp32) → top-8 → renormalize → × per_expert_scale.


  • Experts (Gemma4TextExperts): a fused gate_up_proj [128,1408,2816] + down_proj
    [128,2816,704]
    , and its forward is a sparse gather/scatter loop (torch.where, index_add_).

  • Attention is the same mixed sliding/global layout as the 31B (which I'd already solved).



That sparse expert loop is the crux: it's data-dependent and will not trace to a static Neuron

graph.





The traceable trick: compute all experts, mask to top-8



You can't put a per-token, per-expert gather/scatter in a static HLO graph. So don't. Compute all 128

experts on every token
, weight each by its router weight — which is zero for the non-top-8 — and

sum. Because a non-selected expert contributes 0 × expert(x) = 0, this is mathematically identical

to HF's sparse top-8, but it's a fixed-shape sequence of matmuls the compiler loves. Wasteful in FLOPs

(you compute 128 experts to use 8), correct in every bit, and traceable.



And the beautiful part: the whole thing collapses onto two standard parallel linears that

ModelBuilder already knows how to shard — no custom 3D-parameter sharding:





  • gate_up_proj [128,1408,2816] → [180224, 2816] as one ColumnParallelLinear (rank r gets
    experts 16*r*…16*r*+15)


  • down_proj [128,2816,704] → [2816, 90112] as one RowParallelLinear (input-sharded → all-reduce)



I keep HF's dual-path layer forward and its router unchanged (router replicated; its top_k_weights

already carry the renorm + per-expert scale) and swap only self.experts → my DenseExperts. Before

spending a cent on hardware, I unit-tested the math and the TP=8 sharding against HF on CPU:

MAXDIFF ≈ 2e-6, cosine 1.0. The math was right.





The bug: SEQ_MATCH-perfect math, empty device output



First device run: it compiled (the first-of-its-kind MoE trace on Neuron — 30 MoE layers,

MB_TRACED). And the output was ''. Empty. First token = end-of-turn, immediately.




  • CPU reference: "The capital of France is Paris."

  • My all-experts-dense math: unit-tested identical to HF ✅

  • My TP=8 sharding logic: unit-tested identical to HF ✅

  • Device: nothing ❌



When your math is proven, your sharding logic is proven, and the device is still wrong, the bug is in

the gap between "how I think NxD's primitives behave" and "how they actually behave under the trace."



Here it was. To weight rank r's experts by the router, I scattered the dense weight matrix per-rank

with scatter_to_tensor_model_parallel_region. That function picks its slice using

get_tensor_model_parallel_rank()a Python int. ModelBuilder compiles one rank and replicates

the graph across all 8 at runtime. So the trace baked rank 0's slice (Wd[:, 0:16]) into the graph,

and at runtime every rank weighted experts 0–15 while its gate_up computed its own experts

(16–31, 32–47, …). Total misalignment → garbage → the model confidently ends the turn.



The auto-sharded ColumnParallel/RowParallel don't have this problem because their weights are

pre-sliced at load time
— the forward graph is rank-agnostic. A standalone collective on a runtime

activation
is not.



The fix is the exact mechanism NxD's own MoE module uses (enable_spmd_rank): register an SPMDRank

module (a [1] int32 parameter checkpoint-loaded from arange(TP), so each rank gets its own number)

and scatter with the runtime-rank-aware variant:




CODE
Wl = scatter_to_process_group_spmd(Wd, 1, self.spmd_rank.get_rank())  # each rank gets ITS experts






Recompile. CPU GEN == DEV GEN == "The capital of France is Paris.", SEQ_MATCH True, 77 ms

prefill. The last and hardest of the five Gemma-4 variants was on Inferentia.



The general lesson, now taped to my monitor: any standalone tensor-parallel collective on a

runtime activation needs the SPMD-rank variant under ModelBuilder — auto-sharded parallel linears are

fine because their weights are pre-sliced; runtime tensors are not.






A packaging trap worth 20 wasted minutes



Publishing, the server wouldn't load the model: PytorchStreamReader ... failed finding central

directory
. My background "mirror to S3" loop had uploaded the 64.6 GB neff while torch.jit.save was

still writing it
— a truncated 21.9 GB partial — and I'd terminated the box before the final clean

upload finished. Fix: never mirror an artifact mid-write; assert zipfile.is_zipfile(...) then do one

clean upload. (The "real" neff being 3× the size of the corrupt partial was the tell.)






Can it run on a smaller box? Yes, no, and "deeper than it looks"



The experts are ~93% of the weight, so quantizing them is the obvious lever. I built an int8-expert

variant (QuantizedColumnParallel/QuantizedRowParallel, per-channel symmetric) — two NxD autograd

quirks to fix on the way (.clone() the down output; monkeypatch scale_dequantize to be out-of-place,

it does x *= scale in place on an async-comm view). The result was genuinely good:





  • int8 is numerically PERFECTSEQ_MATCH True, token-for-token identical to fp32.


  • int8 is genuinely smaller — neff 41.8 GB vs 64.6 GB bf16, exactly the 22.8 GB expert saving
    (the compiler keeps it int8, doesn't upcast).



But it fits only the 24xlarge. On a 2-core box (inf2.8xlarge/inf2.xlarge, 32 GB HBM, TP=2) it

Allocation Failures — experts drop to 11.4 GB/rank, but lm_head + the compiled graph + Neuron's

runtime reserve push it ~3–4 GB over the 16 GB core. And inf2 has no 4-core SKU to split the

difference. Fitting a 2-core box needs fp4 experts — which, unlike int8's five-line quantizer, is a

deep NxD microscaling integration (packed uint16, from_float hooks) with real accuracy risk. That's

a separate expedition.



So the shipped set is bf16 (24xlarge) and a validated int8 (24xlarge, smaller/faster, bf16-exact

quality)
— both published — with fp4-for-2-cores documented as the next frontier.






Takeaways





  1. A data-dependent op won't trace? Make it fixed-shape. All-experts-dense trades FLOPs for a static
    graph and exact correctness. Optimize routing later.


  2. Unit-test math AND sharding logic on CPU before you pay for a NeuronCore. It's what let me say
    with certainty "the bug is in the primitives, not my code."


  3. Runtime-rank is the MoE-on-ModelBuilder gotcha. Weights get pre-sliced; runtime activations need
    the SPMD-rank scatter. If the device is empty while CPU + unit tests are perfect, look here.


  4. "Active params" is a compute number, not a memory number. MoE saves FLOPs, not HBM.


  5. int8 per-channel was free accuracy-wise — a smaller, faster serving artifact at zero quality cost.
    The sub-8-bit step is where the real work (and risk) begins.






Artifacts




  • Docker Hub: xbill9/gemma4-optb-26b (bf16) · xbill9/gemma4-optb-26b-int8

  • HF: xbill9/gemma-4-26B-A4B-it-inferentia2 (bf16) · -int8

  • Recipe: tp_mb_moe.py (the DenseExperts + SPMDRank scatter) · tp_mb_moe_int8.py



Written with AI assistance (the port, the debugging, and this write-up were done in a Claude Code

session); every log line quoted is from a real run on real hardware.

Vollständiger Original-Bericht
Ausführliche Details, Code-Beispiele & Hersteller-Stellungnahme auf dev.to.
↗ Original-Artikel auf dev.to lesen
Wie bewertest du diesen Beitrag?
1 Klick Feedback
Teilen mit Netzwerk & Team:

Community-Analysen & Experten-Meinungen 0

Verfasse deine eigene Analyse, teile Workarounds oder diskutiere diesen Vorfall im Blog.
Noch keine Community-Analyse verfasst. Markiere einen Textabschnitt oder klicke oben auf Eigene Analyse verfassen“!
Community Pulse: Relevanz-Einschätzung
1 Klick Experten-Votum
🔴 Akute Relevanz 0%
🟡 In Evaluierung 0%
🟢 Keine Auswirkung 0%
Spannende Innovation 0%
Verwandte Story-Cluster & Quellen (Vektor-KI)
Port 8095 Engine
3 Quellen
Use custom web fonts in Google Sheets charts
2 Quellen
Introducing the new 1Password App for Google Chat
1 Quelle
Context-aware access controls are available for Gemini Enterprise in the Admin console
Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten Porting a 128-expert MoE (Gemma-4 26B-A4B) to AWS Inferentia2 — where every rank weighted the wrong experts

Thematisch verwandte Begriffe: Porting, 128expert, Gemma4, 26BA4B · 6 Treffer

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...