family gpt_oss
Llama's pre-norm block with a mixture of experts on every block, sliding-window and full-attention blocks in turn, and a learned sink logit per head in the softmax, so a pattern row sums to less than one.
One block of the stack. Hover any part for its nnterp name; click to pin the card. The slider picks a block; this family's blocks differ by layer_types.
One block of the stack. Hover any part for its nnterp name; click to pin the card. The slider picks a block; this family's blocks differ by layer_types.
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 openai/gpt-oss-20b: the native modules, standard names and every standard value with its layout.
GptOssForCausalLM(
(lm_head): Linear(in_features=2880, out_features=201088, bias=False)
(generator): Generator(
(streamer): Streamer()
)
(embed_tokens): Embedding(201088, 2880, padding_idx=199999)
(layers): ModuleList(
(0-23): 24 x GptOssDecoderLayer(
(self_attn): GptOssAttention(
(q_proj): Linear(in_features=2880, out_features=4096, bias=True)
(k_proj): Linear(in_features=2880, out_features=512, bias=True)
(v_proj): Linear(in_features=2880, out_features=512, bias=True)
(o_proj): Linear(in_features=4096, out_features=2880, bias=True)
(attention_scores) -> Pattern [batch num_heads query key]: The attention scores entering the softmax, masked
(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_premix) -> HeadOutputs [batch seq num_heads head_dim]: Every head's output side by side, as the output projection takes it
(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
)
(mlp): GptOssMLP(
(router): GptOssTopKRouter()
(experts): GptOssExperts()
(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
(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) -> ExpertOutputs [batch seq top_k hidden_size]: Each routing slot's weighted expert output
(routed_output) -> Residual [batch seq hidden_size]: The routed experts' combined output
(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): GptOssRMSNorm((2880,), eps=1e-05)
(post_attention_layernorm): GptOssRMSNorm((2880,), eps=1e-05)
(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): GptOssRMSNorm((2880,), 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 openai/gpt-oss-120b: the native modules, standard names and every standard value with its layout.
GptOssForCausalLM(
(lm_head): Linear(in_features=2880, out_features=201088, bias=False)
(generator): Generator(
(streamer): Streamer()
)
(embed_tokens): Embedding(201088, 2880, padding_idx=199999)
(layers): ModuleList(
(0-35): 36 x GptOssDecoderLayer(
(self_attn): GptOssAttention(
(q_proj): Linear(in_features=2880, out_features=4096, bias=True)
(k_proj): Linear(in_features=2880, out_features=512, bias=True)
(v_proj): Linear(in_features=2880, out_features=512, bias=True)
(o_proj): Linear(in_features=4096, out_features=2880, bias=True)
(attention_scores) -> Pattern [batch num_heads query key]: The attention scores entering the softmax, masked
(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_premix) -> HeadOutputs [batch seq num_heads head_dim]: Every head's output side by side, as the output projection takes it
(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
)
(mlp): GptOssMLP(
(router): GptOssTopKRouter()
(experts): GptOssExperts()
(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
(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) -> ExpertOutputs [batch seq top_k hidden_size]: Each routing slot's weighted expert output
(routed_output) -> Residual [batch seq hidden_size]: The routed experts' combined output
(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): GptOssRMSNorm((2880,), eps=1e-05)
(post_attention_layernorm): GptOssRMSNorm((2880,), eps=1e-05)
(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): GptOssRMSNorm((2880,), 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_attn(input_layernorm(x)) # a sink column in the softmax
out = h + mlp(post_attention_layernorm(h)) # 4 experts per token
Llama's pre-norm block and Llama's names. The block returns a tensor and adds each sublayer's
output to the stream as it is, so the identity is the plain sum. The MLP module returns
(hidden_states, router_scores); mlp_output is the first element, and mlp.output the tuple.
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)
Each attention module holds sinks, one learned logit per query head ([num_heads], 64 on 20b and
120b), on sliding and full blocks alike. The sink's share of the softmax mixes no value:
attention_probabilities has seq key columns whose rows sum to less than one, and
1 - row sum is the sink's share.
attention_scores is read at the masked scores just before the sink column joins, so it has the
pattern's shape and softmax(attention_scores) is not the pattern. Appending the column rebuilds it:
attn = model.layers[0].self_attn
with model.trace(prompt):
scores = attn.attention_scores.save() # masked, before the sink joins
pattern = attn.attention_probabilities.save()
assert (pattern.float().sum(-1) < 1).all() # the sink keeps the rest
sinks = attn._module.sinks.to(scores.dtype)
column = sinks.view(1, -1, 1, 1).expand(scores.shape[0], -1, scores.shape[2], 1)
combined = torch.cat([scores, column], dim=-1)
combined = combined - combined.max(dim=-1, keepdim=True).values
full = combined.softmax(-1)
torch.testing.assert_close(full[..., :-1], pattern)
sink_share = full[..., -1] # [batch, num_heads, query]
The softmax runs in the scores' dtype, not in float32. A head's output is the values weighted by
these rows, so a row's sum is the share of that query's attention that reaches the tokens; the sink
takes the rest. An edit to attention_scores is renormalized against the sink. An edit to
attention_probabilities sets the weights directly, and its rows need not sum to anything.
layer_types alternates sliding_attention and full_attention, starting with a sliding block:
the even blocks attend over the latest sliding_window (128) positions and the odd blocks over the
whole prefix. On a prompt longer than the window, a sliding block's pattern is zero outside the
window, so each row has at most 128 nonzero key columns:
with model.trace(long_prompt): # more than 128 tokens
pattern = model.layers[0].self_attn.attention_probabilities.save()
assert ((pattern[0, :, -1] > 0).sum(-1) <= 128).all() # block 0 slides
transformers offers no SDPA path for GPT-OSS (_supports_sdpa = False), so a load without
attn_implementation runs the eager forward and every attention value is available. The 20b and 120b
configs carry an MXFP4 quantization_config for the experts only; attention, router, embeddings
and lm_head are not quantized.
64 query heads of width 64 share 8 key/value heads, so attention_keys and attention_values are
[batch, 8, seq, 64] and query head h reads key/value head h // 8: an edit to one key/value
head reaches 8 query heads. q_proj, k_proj, v_proj and o_proj all add a bias
(attention_bias). The rotary embedding is YaRN (rope_parameters: factor 32 over an original
4,096 positions, rope_theta 150,000), and attention_queries and attention_keys are read after it.
YaRN multiplies the rotary cosines and sines by 0.1 * ln(32) + 1 = 1.347, so both are 1.347 times
the rotated projections.
The router is a biased linear map to num_experts logits (32 on 20b, 128 on 120b); it keeps the 4
largest and takes a softmax over those 4 alone, so expert_weights sum to one at every token and
router_logits outside the top 4 do not enter the weights. There is no shared expert:
routed_output is mlp_output, and expert_outputs sums to it.
moe = model.layers[1].mlp
with model.trace(prompt):
logits = moe.router_logits.save() # [batch, seq, num_experts]
w = moe.expert_weights.save() # [batch, seq, top_k]
idx = moe.expert_indices.save()
each = moe.expert_outputs.save() # [batch, seq, top_k, hidden_size]
routed = moe.routed_output.save()
top = logits.topk(moe.top_k, dim=-1)
torch.testing.assert_close(top.values.softmax(-1).to(w.dtype), w)
assert torch.equal(top.indices, idx)
torch.testing.assert_close(each.sum(2), routed)
The experts hold their weights as stacked parameters, experts.gate_up_proj
([num_experts, hidden_size, 2 × intermediate]) and experts.down_proj, each with a bias. The
gate and up halves are interleaved, gate on the even columns and up on the odd ones. The gate is
clamped above at 7 and the up half to [−7, 7] (swiglu_limit, 7.0 in every released config), and
the activation is (up + 1) · gate · sigmoid(1.702 · gate) (swiglu_alpha, whose 1.702 default no
released config overrides). An expert's output includes its down_proj bias, so every slot adds a
weighted bias, and expert_outputs is each slot's output times its weight.
Zeroing expert e's weight where expert_indices == e removes that slot's term and leaves the
token's other 3 weights as they were (they are not renormalized). mlp_output changes on exactly
the tokens that chose e, and an expert no token chose has no effect:
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):
moe.expert_weights = moe.expert_weights.masked_fill(moe.expert_indices == e, 0)
ablated = moe.mlp_output.save()
changed = (ablated != clean).any(-1) # [batch, seq]
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. The final norm is a plain RMSNorm whose gain is norm.weight.
The tokenizer prepends nothing, so model.input_ids is the prompt's tokens alone;
<|startoftext|> is id 199998 and the end-of-sequence token is <|return|>, id 200002.
GPT-OSS (GptOssForCausalLM).
Llama's tree with a mixture-of-experts MLP that returns (hidden_states, router_scores), sliding-window layers, and an attention sink: each head
carries a learned logit that joins the softmax as one extra key column and is
dropped afterwards. So the pattern's rows sum to less than one, and the
softmax's input is one key wider than the pattern and shifted by its row max;
the standard attention_scores is therefore read one step earlier, at the
masked scores just before the sink joins them (a binding that exists only
when an attention mask is passed, as it is on every prompt).