Barbados / bundle /source /utils /expert_freq.py
Vykos's picture
Release Barbados looped MoE research prototype
d5147ba verified
Raw History Blame Contribute Delete
3.69 kB
"""Per-step MoE expert call frequency (layer × loop). Rank0 JSONL, no collectives."""
from __future__ import annotations
import json
import os
from collections import defaultdict
from pathlib import Path
from typing import Optional
from torch import Tensor, nn
from models.transformer import TransformerBlock
_ACTIVE: Optional["ExpertFreqLogger"] = None
def get_active_expert_logger() -> Optional["ExpertFreqLogger"]:
return _ACTIVE
def set_active_expert_logger(logger: Optional["ExpertFreqLogger"]) -> None:
global _ACTIVE
_ACTIVE = logger
class ExpertFreqLogger:
"""Accumulate routing counts during forward; flush one JSONL line per step."""
def __init__(self, model: nn.Module, out_path: str | Path, *, every: int = 1) -> None:
self.every = max(1, int(every))
self.out_path = Path(out_path)
self.out_path.parent.mkdir(parents=True, exist_ok=True)
self._moe_tag_by_id: dict[int, str] = {}
self._sum: dict[tuple[str, str, int], float] = defaultdict(float)
self._cycle = ""
self._discover(model)
def _discover(self, model: nn.Module) -> None:
backbone = getattr(model, "model", model)
for level_name, short in (("H_level", "H"), ("L_level", "L")):
level = getattr(backbone, level_name, None)
if level is None:
continue
core = getattr(level, "core", None)
if core is None or not hasattr(core, "layers"):
continue
for i, block in enumerate(core.layers):
if not isinstance(block, TransformerBlock):
continue
if getattr(block, "ffn_type", "") == "moe":
self._moe_tag_by_id[id(block.mlp)] = f"{short}/L{i:02d}"
if self._moe_tag_by_id:
return
layers = getattr(backbone, "layers", None)
if layers is None:
return
for i, block in enumerate(layers):
if not isinstance(block, TransformerBlock):
continue
if getattr(block, "ffn_type", "") == "moe":
self._moe_tag_by_id[id(block.mlp)] = f"H/L{i:02d}"
def begin_step(self) -> None:
self._sum.clear()
self._cycle = ""
def set_cycle(self, level: str, idx: int) -> None:
self._cycle = f"c{level}{int(idx):02d}"
def record(self, moe_module: nn.Module, load_counts: Tensor) -> None:
tag = self._moe_tag_by_id.get(id(moe_module))
if tag is None:
return
counts = load_counts.detach()
if counts.device.type != "cpu":
counts = counts.float().cpu()
else:
counts = counts.float()
cyc = self._cycle
for eid in range(int(counts.numel())):
self._sum[(tag, cyc, eid)] += float(counts[eid].item())
def should_flush(self, step: int) -> bool:
return step > 0 and step % self.every == 0
def flush(self, step: int) -> None:
if not self.should_flush(step) or not self._sum:
self._sum.clear()
return
layers: dict[str, dict[str, list[float]]] = {}
n_exp = 0
for (tag, cyc, eid), v in self._sum.items():
n_exp = max(n_exp, eid + 1)
bucket = layers.setdefault(tag, {})
row = bucket.setdefault(cyc or "cH00", [])
while len(row) <= eid:
row.append(0.0)
row[eid] += float(v)
rec = {"step": int(step), "n_experts": n_exp, "layers": layers}
with open(self.out_path, "a", encoding="utf-8") as f:
f.write(json.dumps(rec, separators=(",", ":")) + "\n")
self._sum.clear()