MoE: a terminator for the sticky stage marker - #72
Merged
Conversation
Markers are sticky, and a program does not stop where its MoE region does. The last MoE marker therefore ran to the end of the file, and every instruction after it -- an lm_head, the next sublayer -- was billed to a MoE stage, feeding the shared-vs-routed ratio the profile exists to report. This is a hazard the marker mechanism introduced: under the legacy substring rules the epilogue fell back to `other`, a conservative miss. Turning markers on converted it into a confident wrong answer. `non_moe` joins MOE_STAGES and `moe_end_marker()` emits it -- a separate entry point from `moe_stage_marker(MOE_END_STAGE)` because closing a region and setting one are different acts, and the caller who has to remember it is assembling a decoder program, not writing an emitter. The name is spelled literally inside MOE_STAGES rather than as `MOE_END_STAGE`. Both repositories recover that set with a parser that refuses anything it cannot evaluate, so a name reference fails to parse rather than resolving. A test holds the constant and the entry equal instead. Also documents two limits on `moe_shared_gate_v0` that were only discoverable by hitting them: `name` must be unique within a program, and `rows` is bounded near 297 by FPRAM -- `one` and `neg_one` must each be at least `rows` long alongside the per-token gate scalars, so the ceiling is near `rows * 3`. Cannot land alone: the emulator holds `StageKind` equal to MOE_STAGES in both directions, and fails with "compiler emits @stage=non_moe but no StageKind matches it" until the companion adds the variant. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
qichao-arlo-wang
force-pushed
the
feat/moe-stage-terminator
branch
from
August 9, 2026 12:07
2269bee to
8a6b94b
Compare
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.
Two defects that produce a plausible-looking wrong answer rather than an error.
1. The MoE region had no end
Markers are sticky, and a program does not stop where its MoE region does. The last MoE marker therefore ran to the end of the file, and every instruction after it — an lm_head, the next sublayer — was billed to a MoE stage.
Measured on a shared-expert program followed by one ordinary
linear_projection:That number feeds
shared_branch_fraction, whichtest_branch_split.pycalls "the headline claim". It is latent today only because every current testbench program ends at the combine.Worth stating plainly: this is a hazard the marker mechanism introduced. Under the legacy substring rules the epilogue fell back to
other— a conservative miss. Turning markers on converted it into a confident wrong answer.non_moejoinsMOE_STAGES;moe_end_marker()emits it. Separate entry point frommoe_stage_marker(MOE_END_STAGE)because closing a region and setting one are different acts.The name is spelled literally inside
MOE_STAGES, not asMOE_END_STAGE. Both repos recover that set with a parser that refuses anything it cannot evaluate, so aNamereference fails to parse rather than resolving — the guard I hardened last round doing exactly its job. A test holds the constant and the entry equal instead.2. A duplicate VRAM/MRAM name used to succeed
VirtualMemoryManager.allocatehad no name registry, unlikeFPRAMAllocator. A second allocation under the same name returned a fresh block, the symbol table pointed at it, and the first was leaked — an emitter still holding the old address writes into memory nothing owns. No error.It now raises.
freeandresetrelease the name, so allocate/free/allocate is unaffected.Verifying this was safe, not assuming it
allocateto report duplicate names, ran the shared-expert, router-policy and all six routed-MoE suites → zero duplicates.test-aten-{softmax,rms-norm,layer-norm,ffn,flash-attention,rope,embedding-add,linear}andtest-large-immediate→ all pass.test-aten-bmmfails — and fails identically on unmodified main (emulator exit 101). Pre-existing, not caused here.This is a change to shared infrastructure inside an otherwise MoE-scoped PR. It is here rather than deferred because documentation alone leaves the silent leak in place, and the leak is the dangerous half.
Also
moe_shared_gate_v0gained documentation for two limits that were only discoverable by hitting them:namemust be unique within a program (the scratch buffers derive from it), androwsis bounded near 830 by FPRAM.Validation
30 compiler guards pass (
test_moe_stage_attribution+test_moe_topk_policy_encoding+ the newtest_moe_stage_terminator).just test-moe-shared-allpasses with 3 bit-exact comparisons; all six routed-MoE suites pass. New guard wired intoci.yml.🤖 Generated with Claude Code