Skip to content

Nightly 2026-08-03: first MoE architecture (OLMoE-1B-7B), parity-verified - #16

Merged
physics515 merged 4 commits into
mainfrom
mummu-nightly-2026-08-03
Aug 3, 2026
Merged

Nightly 2026-08-03: first MoE architecture (OLMoE-1B-7B), parity-verified#16
physics515 merged 4 commits into
mainfrom
mummu-nightly-2026-08-03

Conversation

@physics515

Copy link
Copy Markdown
Owner

Nightly run 2026-08-03. Four increments; the headline is that the zoo is no longer dense-only — OLMoE-1B-7B is ported from scratch and passed its llama.cpp parity gate on the first run.

1. Dependency freshness (b327514)

cargo update took 8 packages to their latest compatible versions (clap 4.6.4→4.6.5, clap_builder 4.6.2→4.6.5, cubecl-hip-sys 7.2→7.14, data-encoding, hybrid-array, ipnet, libredox, time 0.3.54→0.3.55). cargo upgrade --incompatible offered exactly one major — wgpu 29 → 30 — which stays held, per the standing pin rationale: wgpu is not ours to pick, burn 0.21 resolves 29 transitively, so it unblocks with a burn bump rather than a cargo upgrade. tokenizers 0.23.1 remains current. Green + Cargo.lock committed as a standalone increment.

2. First MoE architecture — OLMoE-1B-7B (a5208a2)

Closes the P2 first MoE architecture item from the 2026-07-30 colibri parity scan.

nn::SparseMoe — a softmax top-k router over a fused 3-D expert bank in [experts, out, in] layout, which is the row-major twin of ggml's ffn_{gate,up,down}_exps, so 64 experts load as three tensors per layer instead of 192. Compute is dense-mask: every expert processes every token and the router's weight row (exactly zero off the top-k) scales the rest away — numerically identical to the sparse formulation and it keeps the whole forward on-device with no data-dependent gather. The routing scatter is an on-device arange-compare rather than burn's one_hot, which reads indices back to the host and would sync every layer.

GqaAttention learned OLMoE's q/k-norm placement — RMSNorm over the whole projection before the head split, versus the per-head placement Qwen3/LFM2 use. Which one applies is inferred from the loaded gamma's own width (head_dim → per-head, n·head_dim → projection), so the module shape is unchanged and every existing checkpoint loads identically. The cache-equivalence proof now runs all three norm modes.

models::olmoe — config-driven off olmoe.* GGUF metadata (expert counts, either feed_forward_length spelling, expert_weights_norm), untied head, CausalLm impl. GGUF-only by design: HF ships the experts unfused as mlp.experts.{i}.*, so a safetensors path needs a 64-way concat-on-import — split out as its own roadmap item. Registry gains the allenai Q4_K_M catalog entry; the tokenizer registry gains the olmo pre (stock GPT-2 regex + NFC, per the checkpoint's own tokenizer.json).

Parity gate — PASSED, first run

parity_gguf.rs gains an olmoe leg; the harness is now generic over the backend so this tier can compare on Cpu (the ~28 GB f32 build does not fit a 16 GB card). llama.cpp runs the same Q4_K_M file:

  • top-5 first-forward ids match exactly in order: [1992, 17833, 11202, 4943, 1394]
  • 24-token greedy sequence byte-identical: "To solve this problem, we need to identify the first five prime numbers. A prime number is a natural number greater than"
  • max |Δlogprob| 3.687691131310693e-1, inside the shared 7.5e-1 tolerance and right beside Qwen3's 4.02e-1

Real-model verification

real_olmoe.rs: the registry spec fetched the 4.21 GB GGUF, the one file loaded 16 layers × 64 experts in 92 s and greedy-decoded "2 + 2 equals 4." on the CPU backend (sanity spread 36.9). Its GGUF-built tokenizer is byte-identical to the checkpoint's tokenizer.json across an 8-prompt battery (specials, CJK/emoji, whitespace runs, contractions, empty).

Every prior gate re-verified after the attention change

Gate Result
Qwen3 GGUF vs llama.cpp bit-identical — top-5 exact in order, max |Δlogprob| 4.015608155114805e-1, greedy byte-identical
Qwen2.5 both legs Candle max |Δlogit| 1.9e-5; Ollama greedy byte-identical
GPU budget 104.4 ms TTFT / 13.2 tok/s (budgets 150 ms / 10 tok/s)
CPU budget 15.3 tok/s (budget 6)
Template byte gate 10/10 byte-identical
Qwen3 real-GPU tool call clean parseable <tool_call> emitted

202 unit tests.

3. Docs + a recorded negative result (a01d829)

The P2 item ticks [x] with its gate numbers. Two follow-ups split out: HF safetensors import, and making routed-expert compute pay.

That second item records a measured negative result instead of a shipped regression. The obvious optimization — gather only the 8 routed expert slices per decode step via a device-side select off the router's own index tensor — is exact-same-math (a unit test confirms the two paths agree to 1e-6) but measured 1.58 s/token against the dense path's 1.15 on burn-flex: the ~200 MB per-layer-per-token gather copy costs more than the dense matmul it removes, i.e. select materializes where the dense path streams. It was reverted rather than shipped, and the roadmap now names the three routes worth trying (a fused kernel that never materializes the gathered bank; measuring on GPU, blocked until a MoE fits VRAM; llama.cpp's --n-cpu-moe-style placement that moves whole banks instead of slicing them) and what each is blocked on.

Research folded in: burn 0.22 is still pre-release (0.22.0-pre.1 remains newest — the P0 migration item stays gated), CubeCL tagged 0.11.0-pre.1 the same day (Metal backend, new CPU runtime, frontend refactor — all arriving with the burn bump), and llama.cpp's --n-cpu-moe plus its two-tier GPU+RAM expert-cache RFC (#20757) as prior art for the P6 streaming tier.

4. MoE budget row + regression gate (aa88a00)

budget_moe.rs defends the new tier like every other path: load 81.9 s (ceiling 300) and decode 0.76 s/token warm (ceiling 2.0, ~2.6× headroom for host-load noise). Budgeted in seconds/token rather than tok/s because the dense-mask forward touches all 7B params per token — s/token is the honest unit and the number the routed-compute work has to beat. bench/BASELINE.md records both the 0.76 warm figure and the 1.15 end-to-end one with what each measures, so the gap reads as methodology rather than drift.

What's next

  • Routed-expert compute — the 0.76 s/token baseline is now the target; the three candidate routes are scoped in P2.
  • OLMoE from HF safetensors — mechanical concat-on-import, wants its own ~14 GB fixture.
  • burn 0.22 migration — still gated on a stable release; the pre-release telegraphs breaking changes to exactly the B::FloatElem seam Mummu's dtype pinning rides.
  • P9 keep-quantized VRAM — the lever that would put a MoE on the 16 GB card at all.

🤖 Generated with Claude Code

Justin Icenhour and others added 4 commits August 3, 2026 06:27
…becl-hip-sys 7.14, ...)

wgpu 29->30 remains intentionally held: burn 0.21 resolves wgpu 29
transitively, so the major unblocks with a burn bump, not a cargo
upgrade. tokenizers 0.23 already current. 191 tests green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
nn::SparseMoe: softmax top-k router over a fused 3-D expert bank
([experts, out, in] - the GGUF ffn_*_exps layout). Dense-mask first
cut: every expert computes, the router's sparse weight row (built
on-device via arange-compare; burn's one_hot syncs to host) zeroes
the unrouted 56/64. Verified against a hand-rolled per-token sparse
reference (softmax -> top-k -> per-expert SwiGLU) plus k=E, renorm,
position-independence, and zero-input legs.

GqaAttention learns the OLMoE q/k-norm placement: whole-projection
RMSNorm before the head split, inferred from the loaded gamma width
(head_dim = per-head, n*head_dim = projection) so the module shape
is unchanged and existing checkpoints load identically; the cache-
equivalence proof now runs all three norm modes.

models::olmoe: config from olmoe.* GGUF metadata (expert counts,
either feed_forward_length spelling, expert_weights_norm honored),
GGUF-only loader (HF ships experts unfused - a 64-way concat import
is a follow-up), untied head, CausalLm impl. Registry gains the
allenai Q4_K_M catalog entry; tokenizer registry gains the 'olmo'
pre (stock GPT-2 regex + NFC, per the checkpoint's tokenizer.json).

Ignored real suites: real_olmoe (registry fetch -> CPU load ->
greedy decode; tokenizer byte-battery vs HF tokenizer.json) and an
olmoe leg in parity_gguf, whose harness is now generic over the
backend (the ~28 GB f32 build compares on Cpu). 202 unit tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ROADMAP: the P2 first-MoE item closes with its gate numbers (top-5 ids
exact in order, 24-token greedy byte-identical vs llama.cpp on the same
Q4_K_M, max |dlogprob| 3.69e-1; 1.15 s/token on CPU, tokenizer
byte-identical to tokenizer.json). Two follow-ups split out: HF
safetensors import (experts ship unfused) and making routed-expert
compute pay.

That second one records a measured NEGATIVE result rather than a
shipped regression: gathering the 8 routed expert slices with a
device-side select is exact-same-math (unit test: agrees to 1e-6) but
ran 1.58 s/token vs the dense path's 1.15 on burn-flex - the ~200 MB
per-layer-per-token gather copy costs more than the dense matmul it
removes. Reverted; the roadmap names the three routes worth trying and
what each is blocked on. The one test improvement from the experiment
is kept: the reference check now covers t=1 (decode) as well as t=5.

Research folded: burn 0.22 still pre-release (migration stays gated),
CubeCL 0.11.0-pre.1 tagged, llama.cpp --n-cpu-moe + its two-tier expert
cache RFC as prior art for the P6 streaming tier.

README: five architectures, the MoE feature, and the OLMoE parity leg.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
budget_moe.rs gates OLMoE-1B-7B on the CPU backend straight from its
Q4_K_M GGUF: load 81.9s (ceiling 300) and decode 0.76 s/token warm
(ceiling 2.0, ~2.6x headroom for host-load noise - this backend's
decode tracks CPU availability). The tier is budgeted in seconds per
token rather than tok/s: the dense-mask expert forward touches all 7B
params per token, so s/token is the honest unit and the number the
routed-compute work has to beat.

BASELINE.md records both figures with what each measures - 0.76
s/token warm (the gate) and 1.15 end-to-end (real_olmoe, prefill
amortized over the answer) - so the gap reads as methodology, not
drift; plus the rejected routed-gather measurement.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings August 3, 2026 14:52
@physics515
physics515 merged commit aa69727 into main Aug 3, 2026
1 check passed

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This nightly update expands Mummu’s model zoo beyond dense-only architectures by adding an OLMoE Mixture-of-Experts (MoE) implementation (GGUF-first), updating attention q/k-norm handling to support OLMoE’s projection-wide norm placement, and introducing parity + real-weights + budget gates for the new tier.

Changes:

  • Add OLMoE MoE architecture support (nn::SparseMoe, models::olmoe) with GGUF import, plus new parity/real-weights validation.
  • Extend GqaAttention to support (and infer) per-head vs projection-wide q/k RMSNorm placement.
  • Add MoE CPU perf budget gate + baseline documentation; update dependency lockfile and roadmap/readme narrative.

Reviewed changes

Copilot reviewed 17 out of 18 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
ROADMAP.md Marks first MoE architecture as shipped; records MoE follow-ups and related research.
README.md Updates public-facing architecture list and adds an MoE overview section.
crates/mummu/tests/real_olmoe.rs Adds ignored real-weights validation for OLMoE GGUF load + decode + tokenizer parity.
crates/mummu/tests/parity_gguf.rs Generalizes GGUF parity harness over backend and adds an OLMoE parity leg.
crates/mummu/src/tokenizer.rs Adds olmo tokenizer pre-spec for OLMo/OLMoE family.
crates/mummu/src/registry.rs Adds Architecture::Olmoe and registers the OLMoE Q4_K_M GGUF catalog entry.
crates/mummu/src/nn/moe.rs Introduces nn::SparseMoe and fused expert-bank implementation + unit tests.
crates/mummu/src/nn/mod.rs Exposes the new MoE module/types from nn.
crates/mummu/src/nn/attention.rs Adds projection-wide q/k norm placement support + updated tests.
crates/mummu/src/models/qwen3.rs Plumbs the new attention config field for Qwen3 (per-head norm).
crates/mummu/src/models/qwen2.rs Plumbs the new attention config field for Qwen2 (no q/k norm).
crates/mummu/src/models/olmoe.rs Adds the OLMoE model implementation and GGUF import path.
crates/mummu/src/models/mod.rs Exposes the new models::olmoe module.
crates/mummu/src/models/lfm2.rs Plumbs the new attention config field for LFM2 (per-head norm).
crates/mummu/src/gguf.rs Adds GgufValue::as_bool helper used by OLMoE metadata parsing.
crates/mummu-bench/tests/budget_moe.rs Adds ignored MoE CPU decode/load budget gate for OLMoE.
Cargo.lock Updates several dependencies to latest compatible versions.
bench/BASELINE.md Records MoE baseline numbers and documents the routed-gather negative result.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +134 to +135
let classes =
Tensor::<B, 1, Int>::arange(0..e as i64, &xt.device()).reshape([1, 1, e as i32]);
Comment on lines +425 to +428
/// Negative space: the projection-wide norm is a different function than
/// the per-head norm (RMS over 16 values vs over 4) — same weights, same
/// input, different outputs. Guards against the placement silently
/// collapsing to one branch.
.and_then(mummu::gguf::GgufValue::as_str)
.map(String::from)
})
.unwrap_or_default();
.and_then(GgufValue::as_str)
.map(String::from)
})
.unwrap_or_default();
.and_then(GgufValue::as_str)
.map(String::from)
})
.unwrap_or_default();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants