Nightly 2026-08-03: first MoE architecture (OLMoE-1B-7B), parity-verified - #16
Merged
Conversation
…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>
There was a problem hiding this comment.
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
GqaAttentionto 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(); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 updatetook 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 --incompatibleoffered 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 acargo upgrade. tokenizers 0.23.1 remains current. Green +Cargo.lockcommitted 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'sffn_{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'sone_hot, which reads indices back to the host and would sync every layer.GqaAttentionlearned 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 offolmoe.*GGUF metadata (expert counts, eitherfeed_forward_lengthspelling,expert_weights_norm), untied head,CausalLmimpl. GGUF-only by design: HF ships the experts unfused asmlp.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 theolmopre (stock GPT-2 regex + NFC, per the checkpoint's owntokenizer.json).Parity gate — PASSED, first run
parity_gguf.rsgains anolmoeleg; the harness is now generic over the backend so this tier can compare onCpu(the ~28 GB f32 build does not fit a 16 GB card). llama.cpp runs the same Q4_K_M file:[1992, 17833, 11202, 4943, 1394]"To solve this problem, we need to identify the first five prime numbers. A prime number is a natural number greater than"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'stokenizer.jsonacross an 8-prompt battery (specials, CJK/emoji, whitespace runs, contractions, empty).Every prior gate re-verified after the attention change
<tool_call>emitted202 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
selectoff 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.selectmaterializes 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-moeplus 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.rsdefends 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.mdrecords 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
B::FloatElemseam Mummu's dtype pinning rides.🤖 Generated with Claude Code