interlens.interp.routing¶
interlens.interp.routing
¶
Mixture-of-Experts routing capture and statistics.
Reads which experts an MoE model routes each token to — the discrete, cheap-to-interpret counterpart of
residual-stream capture. Like capture_activations, capture is a single clean forward pass over the full
token sequence (provably complete, one extra forward) rather than hooks accumulated across a decode loop: both
OlmoeForCausalLM and Qwen3MoeForCausalLM (and other HF MoE families) return per-MoE-layer router logits
natively via output_router_logits=True, so no module hooks are needed at all.
Typical use: replay a saved conversation view through the MoE, get per-token routing with
capture_router_logits, compute per-message expert-usage distributions with routing_stats restricted to
message_token_spans, and compare conditions with js_divergence / topk_expert_overlap.
RouterSteeringSpec
dataclass
¶
A causal intervention on MoE routing: add a per-expert bias to the gate logits during generation, nudging
which experts fire. The routing analogue of :class:SteeringSpec (which steers the residual stream) — here
forward hooks sit on each sparse layer's mlp.gate (the router nn.Linear whose output is the
[tokens, n_experts] logits) and add coef * bias before top-k selection.
Build it with :meth:toward_load to push routing toward a target expert-usage distribution (e.g. the MoE's
solo-on-domain RoutingStats.expert_load): the bias is log(target + eps), so adding it acts like a
log-prior that shifts the router's softmax toward the target experts, with coef the strength (0 = no-op).
A summary (layers, coef, per-layer bias norm) is available via :meth:summary for reproducibility.
register
¶
Register the gate-bias hooks on model's sparse layers; returns handles (caller removes after use).
Source code in src/interlens/interp/routing.py
toward_load
classmethod
¶
toward_load(
target_load: Tensor,
layers: tuple[int, ...],
coef: float = 1.0,
eps: float = 1e-06,
) -> "RouterSteeringSpec"
Steer toward a target expert-usage distribution target_load ([len(layers), n_experts], rows the
fraction of routing mass per expert). Bias = log(target_load + eps) (a log-prior on expert choice).
Source code in src/interlens/interp/routing.py
RoutingCapture
¶
Bases: NamedTuple
Per-token routing at one MoE layer, from one capture_router_logits pass.
router_logits is the raw pre-softmax gate output [seq, n_experts] (cpu fp32), or None when the
capture was compacted with top_k_only=True. topk_experts / topk_probs are always present: the
k selected expert ids (int16) and their softmax router probabilities (fp16), [seq, k] each.
RoutingStats
dataclass
¶
RoutingStats(
expert_load: Tensor,
expert_mass: Tensor | None,
layers: tuple[int, ...],
n_tokens: int,
top_k: int,
)
Aggregate expert-usage distributions over a set of token positions.
expert_load[l, e] is the fraction of top-k selections at layer l that went to expert e
(rows sum to 1 — the discrete "which experts fired" histogram). expert_mass[l, e] is the mean softmax
router probability (None if the captures were top_k_only — full logits are needed for mass).
layers are the decoder-layer indices of the rows; n_tokens is how many positions were pooled.
capture_router_logits
¶
capture_router_logits(
model: "PreTrainedModel",
input_ids: Tensor,
layers: tuple[int, ...] | None = None,
top_k_only: bool = False,
offload: str = "cpu",
) -> list[RoutingCapture]
One clean forward pass over input_ids ([1, seq]); return per-MoE-layer RoutingCapture.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model
|
'PreTrainedModel'
|
an HF MoE causal LM whose |
required |
input_ids
|
Tensor
|
full token sequence to route, shape |
required |
layers
|
tuple[int, ...] | None
|
decoder-layer indices to keep (default: all sparse layers, per |
None
|
top_k_only
|
bool
|
drop the full |
False
|
offload
|
str
|
device for the returned tensors (default |
'cpu'
|
Source code in src/interlens/interp/routing.py
js_divergence
¶
Per-layer Jensen–Shannon divergence (symmetric, bounded by ln 2) → [n_layers].
kl_divergence
¶
Per-layer KL(p || q) between expert distributions [n_layers, n_experts] → [n_layers].
Source code in src/interlens/interp/routing.py
message_token_spans
¶
message_token_spans(
tokenizer: "PreTrainedTokenizerBase", view: list[dict]
) -> list[tuple[int, int]]
Token span (start, end) of each message of view in the fully-rendered chat-template sequence.
Method: render the string prefixes apply_chat_template(view[:i], tokenize=False) for each i and
require each to be a string-prefix of the next (raised on non-prefix-stable templates). The full string is
then tokenized once with return_offsets_mapping=True and each char boundary is mapped to the first
token whose offset starts at/after it — prefix strings are never tokenized independently, because a
tokenization of a prefix string is not in general a prefix of the full tokenization.
Note this describes the final replayed view (the whole conversation templated once). Live generation builds the sequence incrementally, but for standard chat templates the rendered text is identical, so spans match. The returned spans cover each message's rendered chunk including its role header/footer markup.
Source code in src/interlens/interp/routing.py
moe_layer_indices
¶
Decoder-layer indices that carry a sparse MoE block.
HF MoE models return router_logits only for the sparse layers, in layer order, with no index
attached. This maps that tuple back to real decoder-layer indices by checking each layer's mlp for a
router gate submodule — which handles mixed stacks (e.g. Qwen-MoE decoder_sparse_step /
mlp_only_layers leaving some layers dense) as well as fully-sparse stacks like OLMoE.
Source code in src/interlens/interp/routing.py
moe_num_experts
¶
Number of routed experts per MoE layer (config.num_experts — same field name in OLMoE/Qwen-MoE).
moe_topk
¶
routing_stats
¶
routing_stats(
captures: list[RoutingCapture],
n_experts: int,
spans: list[tuple[int, int]] | None = None,
) -> RoutingStats
Pool per-token routing into per-layer expert-usage distributions.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
captures
|
list[RoutingCapture]
|
output of |
required |
n_experts
|
int
|
total routed experts ( |
required |
spans
|
list[tuple[int, int]] | None
|
optional |
None
|
Source code in src/interlens/interp/routing.py
topk_expert_overlap
¶
Per-layer fraction of overlap between the k most-used experts of p and of q → [n_layers].