Skip to content

Commit 80658a8

Browse files
committed
feat(v5-g25): MoE/sparse_moe/bailing_moe bricks → extras.moe observability
Closes V5-G25 / cppmega-mlx-zuf. stage_train detects MoE-family brick in wire graph; extras.moe = {kind, num_experts, top_k, routing_entropy=null, load_balance_loss=null, dropped_token_ratio=null}. Real routing entropy / load-balance loss / dropped-token measurement deferred to v6+ (requires expert-routing forward hook). 2/2 pytest green.
1 parent 54161c3 commit 80658a8

2 files changed

Lines changed: 81 additions & 0 deletions

File tree

cppmega_v4/runner/stages.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -692,6 +692,28 @@ def _count(tree: Any) -> int:
692692
"num_clips": 0,
693693
}
694694

695+
# G25: detect MoE / sparse-experts bricks in graph; surface
696+
# routing config in extras.moe. Pure observation — actual
697+
# routing/load-balance computation is v6+ work.
698+
moe_extras: dict[str, Any] | None = None
699+
try:
700+
moe_kinds = {"moe", "bailing_moe", "sparse_moe"}
701+
wire_nodes = getattr(ctx.spec.graph, "nodes", []) or []
702+
moe_node = next((n for n in wire_nodes
703+
if getattr(n, "kind", "") in moe_kinds), None)
704+
if moe_node is not None:
705+
p = dict(getattr(moe_node, "params", {}) or {})
706+
moe_extras = {
707+
"kind": str(getattr(moe_node, "kind", "moe")),
708+
"num_experts": int(p.get("num_experts", 1)),
709+
"top_k": int(p.get("top_k", 1)),
710+
"routing_entropy": None, # v6: actual measurement
711+
"load_balance_loss": None,
712+
"dropped_token_ratio": None,
713+
}
714+
except Exception:
715+
pass
716+
695717
# G07: read precision toggles from spec (passthrough — backend
696718
# doesn't actually switch dtype yet, but extras report what the
697719
# UI asked for so e2e can assert propagation). Real mixed/fp8
@@ -921,6 +943,7 @@ def _count(tree: Any) -> int:
921943
"train_dtype": train_dtype,
922944
"master_dtype": master_dtype,
923945
"fp8_active": fp8_active,
946+
"moe": moe_extras,
924947
"opt_state_carried": opt_state_carried,
925948
"run_id": run_id,
926949
"mtp": _compute_mtp_extras(

tests/v5/test_stage_train_moe.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
"""G25: MoE bricks detected in graph → extras.moe populated."""
2+
3+
from __future__ import annotations
4+
5+
from cppmega_v4.jsonrpc.schema import VerifyParams
6+
from cppmega_v4.runner import Pipeline, run_pipeline
7+
8+
9+
def _spec(nodes: list[dict]) -> VerifyParams:
10+
return VerifyParams.model_validate({
11+
"graph": {
12+
"nodes": nodes,
13+
"edges": [{"src": nodes[i]["id"], "dst": nodes[i+1]["id"]}
14+
for i in range(len(nodes) - 1)],
15+
},
16+
"dim_env": {"B": 1, "S": 8, "H": 32, "nh": 2, "nkv": 1, "head_dim": 16},
17+
"loss": {"kind": "cross_entropy",
18+
"head_outputs": [nodes[-1]["id"]]},
19+
"optim": {"kind": "adamw",
20+
"groups": [{"matcher": "all", "lr": 1e-3,
21+
"weight_decay": 0.01, "betas": [0.9, 0.95]}]},
22+
})
23+
24+
25+
def _run(nodes: list[dict]) -> dict:
26+
# Skip build_model — MoE brick has V4MoEConfig kwargs that build_model
27+
# rejects; G25 is observation-only (extras populated from raw graph).
28+
report = run_pipeline(_spec(nodes), Pipeline.from_dict({
29+
"stages": ["parse", "verify_build_spec", "train"],
30+
"stage_options": {"train": {"num_steps": 2}},
31+
}))
32+
train = next(s for s in report.stages if s.name == "train")
33+
return train.extras
34+
35+
36+
def test_no_moe_brick_extras_moe_is_none():
37+
extras = _run([
38+
{"id": "attn", "kind": "attention", "params": {}},
39+
{"id": "mlp", "kind": "mlp",
40+
"params": {"intermediate_size": 64, "activation": "swiglu"}},
41+
])
42+
assert extras.get("moe") is None
43+
44+
45+
def test_moe_brick_populates_num_experts_top_k():
46+
# Use mlp (instantiates cleanly) but inject a "moe" pseudo-node so
47+
# the detection logic in stage_train sees it via the wire graph.
48+
extras = _run([
49+
{"id": "moe", "kind": "moe",
50+
"params": {"num_experts": 8, "top_k": 2}},
51+
{"id": "mlp", "kind": "mlp",
52+
"params": {"intermediate_size": 64, "activation": "swiglu"}},
53+
])
54+
moe = extras.get("moe")
55+
assert moe is not None
56+
assert moe["kind"] == "moe"
57+
assert moe["num_experts"] == 8
58+
assert moe["top_k"] == 2

0 commit comments

Comments
 (0)