family jetmoe
Llama's pre-norm block in which both sublayers are mixtures of experts: besides the MLP's, a router picks attention experts per token, each with its own query and output projections over shared keys and values.
One block of the stack. Hover any part for its nnterp name; click to pin the card.
identity layers[i].input + self_attn.attention_output + mlp.mlp_output == layer_output
⚠︎ marks a value this checkpoint has only under some load: hover it for the condition.
What print(model) shows on jetmoe/jetmoe-8b: the native modules, standard names and every standard value with its layout.
JetMoeForCausalLM(
(lm_head): Linear(in_features=2048, out_features=32000, bias=False)
(generator): Generator(
(streamer): Streamer()
)
(embed_tokens): Embedding(32000, 2048)
(layers): ModuleList(
(0-23): 24 x JetMoeDecoderLayer(
(mlp): JetMoeMoE(
(act_fn/activation): SiLUActivation()
(input_linear): JetMoeParallelExperts()
(down_proj/output_linear): JetMoeParallelExperts()
(router): JetMoeTopKGating(
(layer): Linear(in_features=2048, out_features=8, bias=False)
)
(router_logits) -> RouterLogits [batch seq num_experts]: The router's logits, one per expert, before the scoring
(expert_weights) -> ExpertWeights [batch seq top_k]: The weight each selected expert's output is scaled by
(expert_indices) -> ExpertIndices [batch seq top_k]: The expert each routing slot sends the token to
(expert_outputs): Unavailable: JetMoE's mixture runs its experts over the slots sorted by expert; no tensor holds them in token order
(routed_output) -> Residual [batch seq hidden_size]: The routed experts' combined output, before the mixture's bias
(mlp_activation) -> Neurons [batch seq intermediate_size]: The activation function's output
(mlp_neurons) -> Neurons [batch seq intermediate_size]: What the down projection receives: act(gate)·up on a gated MLP, the activation itself otherwise
(shared_expert_output) -> Residual [batch seq hidden_size]: The shared expert's output
(mlp_output) -> Residual [batch seq hidden_size]: What the MLP adds to the residual stream
)
(input_layernorm): JetMoeRMSNorm((2048,), eps=1e-06)
(post_attention_layernorm): JetMoeRMSNorm((2048,), eps=1e-06)
(self_attn/self_attention): JetMoeAttention(
(experts): JetMoeMoA(
(input_linear): JetMoeParallelExperts()
(down_proj/output_linear): JetMoeParallelExperts()
(router): JetMoeTopKGating(
(layer): Linear(in_features=2048, out_features=8, bias=False)
)
)
(kv_proj): Linear(in_features=2048, out_features=4096, bias=False)
(attention_premix): Unavailable: a mixture of attention heads: the output projection is per expert, inside experts
(attention_queries) -> Queries [batch num_heads seq qk_head_dim]: The queries entering attention
(attention_keys) -> Keys [batch num_kv_heads seq qk_head_dim]: The keys entering attention
(attention_values) -> Values [batch num_kv_heads seq head_dim]: The values entering attention
(attention_scores) -> Pattern [batch num_heads query key]: The attention scores entering the softmax, masked
(attention_output) -> Residual [batch seq hidden_size]: What the attention adds to the residual stream
(attention_probabilities) -> Pattern [batch num_heads query key]: The attention pattern the values are mixed with
(attention_head_outputs) -> HeadOutputs [batch seq num_heads head_dim]: The per-head outputs before the output projection
)
(layer_input) -> Residual [batch seq hidden_size]: The residual stream entering the block
(attention_norm_output) -> Residual [batch seq hidden_size]: The normed stream entering the attention
(layer_mid) -> Residual [batch seq hidden_size]: The residual stream after the attention add
(mlp_norm_output) -> Residual [batch seq hidden_size]: The normed stream entering the MLP
(mlp_input) -> Residual [batch seq hidden_size]: What the MLP is called with
(layer_output) -> Residual [batch seq hidden_size]: The residual stream leaving the block, a tensor even when the block returns a tuple
)
)
(norm): JetMoeRMSNorm((2048,), eps=1e-05)
(logits) -> Logits [batch seq vocab_size]: The logits the model returns, after anything it does past lm_head (a softcap, a scale)
(token_embeddings) -> Residual [batch seq hidden_size]: The embedding module's output; layers[0].input is what enters the first block
(final_norm_output) -> Residual [batch seq hidden_size]: The residual stream after the final norm, what lm_head reads
(lm_head_output) -> Logits [batch seq vocab_size]: The head's logits before anything the model does past lm_head (a softcap, a scale)
(next_token_probs) -> NextTokenProbs [batch vocab_size]: The next-token distribution at the last position; derived, read-only
(input_ids) -> Tokens [batch seq]: The token ids the model was called with
(attention_mask) -> Tokens [batch seq]: The attention mask the model was called with; zeros are padding
(input_size): [batch, seq] of the current call; read-only
)What print(model) shows on jetmoe/jetmoe-8b-sft: the native modules, standard names and every standard value with its layout.
JetMoeForCausalLM(
(lm_head): Linear(in_features=2048, out_features=32000, bias=False)
(generator): Generator(
(streamer): Streamer()
)
(embed_tokens): Embedding(32000, 2048)
(layers): ModuleList(
(0-23): 24 x JetMoeDecoderLayer(
(mlp): JetMoeMoE(
(act_fn/activation): SiLUActivation()
(input_linear): JetMoeParallelExperts()
(down_proj/output_linear): JetMoeParallelExperts()
(router): JetMoeTopKGating(
(layer): Linear(in_features=2048, out_features=8, bias=False)
)
(router_logits) -> RouterLogits [batch seq num_experts]: The router's logits, one per expert, before the scoring
(expert_weights) -> ExpertWeights [batch seq top_k]: The weight each selected expert's output is scaled by
(expert_indices) -> ExpertIndices [batch seq top_k]: The expert each routing slot sends the token to
(expert_outputs): Unavailable: JetMoE's mixture runs its experts over the slots sorted by expert; no tensor holds them in token order
(routed_output) -> Residual [batch seq hidden_size]: The routed experts' combined output, before the mixture's bias
(mlp_activation) -> Neurons [batch seq intermediate_size]: The activation function's output
(mlp_neurons) -> Neurons [batch seq intermediate_size]: What the down projection receives: act(gate)·up on a gated MLP, the activation itself otherwise
(shared_expert_output) -> Residual [batch seq hidden_size]: The shared expert's output
(mlp_output) -> Residual [batch seq hidden_size]: What the MLP adds to the residual stream
)
(input_layernorm): JetMoeRMSNorm((2048,), eps=1e-06)
(post_attention_layernorm): JetMoeRMSNorm((2048,), eps=1e-06)
(self_attn/self_attention): JetMoeAttention(
(experts): JetMoeMoA(
(input_linear): JetMoeParallelExperts()
(down_proj/output_linear): JetMoeParallelExperts()
(router): JetMoeTopKGating(
(layer): Linear(in_features=2048, out_features=8, bias=False)
)
)
(kv_proj): Linear(in_features=2048, out_features=4096, bias=False)
(attention_premix): Unavailable: a mixture of attention heads: the output projection is per expert, inside experts
(attention_queries) -> Queries [batch num_heads seq qk_head_dim]: The queries entering attention
(attention_keys) -> Keys [batch num_kv_heads seq qk_head_dim]: The keys entering attention
(attention_values) -> Values [batch num_kv_heads seq head_dim]: The values entering attention
(attention_scores) -> Pattern [batch num_heads query key]: The attention scores entering the softmax, masked
(attention_output) -> Residual [batch seq hidden_size]: What the attention adds to the residual stream
(attention_probabilities) -> Pattern [batch num_heads query key]: The attention pattern the values are mixed with
(attention_head_outputs) -> HeadOutputs [batch seq num_heads head_dim]: The per-head outputs before the output projection
)
(layer_input) -> Residual [batch seq hidden_size]: The residual stream entering the block
(attention_norm_output) -> Residual [batch seq hidden_size]: The normed stream entering the attention
(layer_mid) -> Residual [batch seq hidden_size]: The residual stream after the attention add
(mlp_norm_output) -> Residual [batch seq hidden_size]: The normed stream entering the MLP
(mlp_input) -> Residual [batch seq hidden_size]: What the MLP is called with
(layer_output) -> Residual [batch seq hidden_size]: The residual stream leaving the block, a tensor even when the block returns a tuple
)
)
(norm): JetMoeRMSNorm((2048,), eps=1e-05)
(logits) -> Logits [batch seq vocab_size]: The logits the model returns, after anything it does past lm_head (a softcap, a scale)
(token_embeddings) -> Residual [batch seq hidden_size]: The embedding module's output; layers[0].input is what enters the first block
(final_norm_output) -> Residual [batch seq hidden_size]: The residual stream after the final norm, what lm_head reads
(lm_head_output) -> Logits [batch seq vocab_size]: The head's logits before anything the model does past lm_head (a softcap, a scale)
(next_token_probs) -> NextTokenProbs [batch vocab_size]: The next-token distribution at the last position; derived, read-only
(input_ids) -> Tokens [batch seq]: The token ids the model was called with
(attention_mask) -> Tokens [batch seq]: The attention mask the model was called with; zeros are padding
(input_size): [batch, seq] of the current call; read-only
)h = x + self_attention(input_layernorm(x)) # 2 of 8 attention experts per token
out = h + mlp(post_attention_layernorm(h)) # 2 of 8 MLP experts per token, + bias
Llama's pre-norm block, with the attention under self_attention, aliased self_attn. Both
sublayers are mixtures with routers of their own. The attention returns
(attn_output, attn_weights, router_logits) and attention_output is the first element; the
block returns a tensor, and the identity is the plain sum:
with model.trace(prompt):
x = model.layers[1].input.save()
attn = model.layers[1].self_attn.attention_output.save()
mlp = model.layers[1].mlp.mlp_output.save()
out = model.layers[1].layer_output.save()
torch.testing.assert_close(x + attn + mlp, out)
self_attn.experts holds 8 attention experts, each a query projection (experts.input_linear,
hidden_size → num_kv_heads × head_dim) and an output projection (experts.output_linear, back to
hidden_size); there is no q_proj or o_proj. Per token, experts.router takes the top 2 of 8
logits and a softmax over those 2. Each chosen expert projects the token's queries for 16 heads,
so a token has 2 × 16 = 32 query heads (num_heads), and slot s holds heads 16 s to 16 s + 15.
Keys and values come from one shared kv_proj, 16 heads of 128, and are tiled (repeat, not
interleaved) to all 32 heads: head h is slot h // 16 over key/value head h % 16.
attention_output is the gated sum of the chosen experts' output projections, each over its
slot's 16 head outputs, plus the mixture's bias experts.bias. nnterp serves no standard value
for the attention's routing. Its logits are experts.router.layer.output, flat over tokens
([batch × seq, num_local_experts]); the third element of self_attn.output holds them in float32:
attn = model.layers[1].self_attn
with model.trace(prompt):
logits = attn.experts.router.layer.output.save() # [batch * seq, num_experts]
with model.trace(prompt):
heads = attn.attention_head_outputs.save() # [batch, seq, num_heads, head_dim]
out = attn.attention_output.save()
k, kv = model.config.num_experts_per_tok, model.num_kv_heads
top = logits.topk(k, dim=-1)
gates, idx = top.values.softmax(-1), top.indices # [batch * seq, k]
experts = attn._module.experts
W = experts.output_linear.weight[idx] # each slot's expert
slots = heads.reshape(-1, k, kv * model.head_dim, 1) # each slot's 16 heads
terms = (W @ slots).squeeze(-1) # [batch * seq, k, hidden_size]
manual = (gates[..., None] * terms).sum(1) + experts.bias
torch.testing.assert_close(manual.view_as(out), out)
attention_keys and attention_values have num_heads heads, two copies of the 16. Each copy is
its own slice of the tensor, so an edit to head h of attention_keys reaches slot h // 16's
queries only; edit key/value head j at heads j and j + 16, or edit kv_proj.output, to reach every
query that reads it. The expert behind a query head changes from token to token, so a query head
index names a slot, not an expert.
Zeroing the head outputs of the slots that chose expert e removes exactly that expert's term
from attention_output at those tokens, since its output projection has no bias; the other
slot's term and experts.bias stay. attention_output changes on exactly the tokens whose
attention router chose e:
attn, e = model.layers[1].self_attn, 3
k, kv = model.config.num_experts_per_tok, model.num_kv_heads
with model.trace(prompt):
logits = attn.experts.router.layer.output.save()
clean = attn.attention_output.save()
idx = logits.topk(k, dim=-1).indices.view(*clean.shape[:2], k) # [batch, seq, k]
with model.trace(prompt):
for s in range(k):
attn.attention_head_outputs[:, :, s * kv:(s + 1) * kv][idx[:, :, s] == e] = 0
ablated = attn.attention_output.save()
changed = (ablated != clean).any(-1)
assert torch.equal(changed, (idx == e).any(-1))
The attention runs on transformers' shared interface: the six interior values need
attn_implementation="eager", and the default sdpa load computes the same attention without
them. The head width is kv_channels (128), so head_dim is 128 while hidden_size / num_heads
is 64: the 32 query heads are 4096 wide in all, twice the stream.
The MLP's router (mlp.router) takes the top 2 of its 8 logits (mlp.router.layer.output) and a
softmax over those 2, so expert_weights sum to one. It then sorts the slots by expert and the
mixture runs input_linear (gate and up, 2 × intermediate_size wide, the first half through
SiLU) and output_linear over the sorted slots, so expert_outputs is unavailable.
expert_indices and expert_weights are read before the sort, in token order, and
routed_output is the routed sum before the mixture adds its bias:
moe = model.layers[1].mlp
with model.trace(prompt):
routed = moe.routed_output.save()
out = moe.mlp_output.save()
torch.testing.assert_close(out - routed, moe._module.bias.expand_as(out))
The router computes the indices before the weights, so a trace reads router_logits, then
expert_indices, then expert_weights. An ablation reads the indices first:
moe, e = model.layers[1].mlp, 3
with model.trace(prompt):
idx = moe.expert_indices.save()
clean = moe.mlp_output.save()
with model.trace(prompt):
hit = moe.expert_indices == e # indices first
moe.expert_weights = moe.expert_weights.masked_fill(hit, 0)
ablated = moe.mlp_output.save()
changed = (ablated != clean).any(-1)
assert torch.equal(changed, (idx == e).any(-1)) # only the tokens that chose e
logits is lm_head.output, with no cap or scale, and project_on_vocab on the last block's
layer_output equals logits. lm_head and embed_tokens share one weight. The
tokenizer prepends <s> (id 1), so model.input_ids starts with it.
JetMoE (JetMoeForCausalLM).
Llama's tree with the attention under its own name: model.{embed_tokens, layers[i].{input_layernorm, self_attention, post_attention_layernorm, mlp}, norm}
and lm_head, the residual added in the block, which returns a bare tensor.
self_attention is aliased self_attn.
The attention is a mixture of attention heads: a router picks
num_experts_per_tok experts per token, each expert projects that token's
queries for num_key_value_heads heads (experts.map) and later projects their
outputs back (experts.reduce, in place of an o_proj). Keys and values come
from one shared kv_proj and are tiled (repeat, not interleaved) to every
query head before the shared eager forward, so the interface sees num_heads
key/value heads and interface head h is routing slot h // num_kv_heads over
kv head h % num_kv_heads: which expert's parameters a head's query came from
varies by token. The attention returns (attn_output, attn_weights, router_logits). The MLP is a mixture of experts (Moe) that returns the routed hidden
states plus a bias as one tensor.
The MLP's mixture has no experts module: its router (JetMoeTopKGating) takes a
top-k of the logits of a child nn.Linear (router.layer), softmaxes the top-k,
and sorts the slots by expert; the mixture then runs input_linear and
output_linear over the sorted slots. So expert_indices and expert_weights
are the router's top-k indices and gates, [tokens, top_k] in token order, before
the sort; routed_output is the mixture's routed sum before + bias; and the
per-slot outputs, sorted by expert, are unavailable.