family solar_open
Llama's pre-norm block with a mixture of experts on every block: a sigmoid router whose correction bias affects only which experts are chosen, and a shared expert that runs on every token.
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 upstage/Solar-Open-100B: the native modules, standard names and every standard value with its layout.
SolarOpenForCausalLM(
(lm_head): Linear(in_features=4096, out_features=196608, bias=False)
(generator): Generator(
(streamer): Streamer()
)
(embed_tokens): Embedding(196608, 4096, padding_idx=2)
(layers): ModuleList(
(0-47): 48 x SolarOpenDecoderLayer(
(self_attn): SolarOpenAttention(
(q_proj): Linear(in_features=4096, out_features=8192, bias=False)
(k_proj): Linear(in_features=4096, out_features=1024, bias=False)
(v_proj): Linear(in_features=4096, out_features=1024, bias=False)
(o_proj): Linear(in_features=8192, out_features=4096, bias=False)
(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_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): SolarOpenMoE(
(experts): SolarOpenExperts(
(act_fn): SiLUActivation()
)
(router/gate): SolarOpenTopkRouter()
(shared_experts): SolarOpenMLP(
(gate_proj): Linear(in_features=4096, out_features=1280, bias=False)
(up_proj): Linear(in_features=4096, out_features=1280, bias=False)
(down_proj): Linear(in_features=1280, out_features=4096, bias=False)
(act_fn): SiLUActivation()
(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
(mlp_output) -> Residual [batch seq hidden_size]: What the MLP adds to the residual stream
)
(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): SolarOpenRMSNorm((4096,), eps=1e-05)
(post_attention_layernorm): SolarOpenRMSNorm((4096,), 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): SolarOpenRMSNorm((4096,), 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))
out = h + mlp(post_attention_layernorm(h)) # 8 of 128 experts, plus the shared expert
Llama's pre-norm block and Llama's names. Nothing norms or scales a sublayer's output, so
attention_output is o_proj's output and mlp_output the mixture's, shared expert included, and
the identity is the plain sum, exact in float32 on the pinned tiny checkpoint:
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)
Transformers builds SolarOpenMoE on every block; the config's first_k_dense_replace is 0, and
SolarOpenMLP is only the shared expert's class. The experts are moe_intermediate_size (1280) wide,
and so is the one shared expert (n_shared_experts × 1280). model.intermediate_size reads the
config's intermediate_size, 10240, which no module of Solar-Open-100B has.
router (native gate) scores the 128 experts in float32 and takes their sigmoid. It adds
e_score_correction_bias (a buffer) to choose the 8 experts and then reads the weights from the
unbiased sigmoid, renormalizes them to sum to one and multiplies them by routed_scaling_factor, 1.0
on Solar-Open-100B. n_group and topk_group are 1, so no group limits the choice. router_logits
is before the sigmoid:
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()
bias = moe._module.gate.e_score_correction_bias
scores = logits.float().sigmoid()
chosen = (scores + bias).topk(moe.top_k, dim=-1).indices
assert torch.equal(chosen.sort(-1).values, idx.sort(-1).values) # biased choice
weights = scores.gather(-1, idx)
torch.testing.assert_close((weights / weights.sum(-1, keepdim=True)).to(w.dtype), w)
The slots are not sorted by weight. A logit written at router_logits changes both the choice and
the weights.
mlp_output is routed_output + shared_expert_output. The shared expert (mlp.shared_experts)
reads the same normed input as the router, and its output is added after the routed experts', so
read shared_expert_output after routed_output in one trace. An expert ablation leaves it alone:
zeroing expert e's weight where expert_indices == e changes mlp_output on exactly the tokens
that chose e. The other 7 weights are not renormalized, so those tokens lose expert e's term and
keep the others' as they were.
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()
assert torch.equal((ablated != clean).any(-1), (idx == e).any(-1))
Rotary covers the whole head (partial_rotary_factor 1.0) with YaRN, factor 2.0 over
original_max_position_embeddings 65536. YaRN multiplies the rotary cosines and sines by
0.1 * ln(2) + 1 = 1.0693, so attention_queries and attention_keys are 1.0693 times the rotated
projections, and the scores carry that factor twice (1.143), on top of 1 / sqrt(head_dim).
64 query heads of 128 share 8 key/value heads, so q_proj maps the 4096-wide stream to 8192, and an
edit to one key or value head reaches the 8 query heads that read it. The attention interior needs
attn_implementation="eager"; the default load runs sdpa.
logits is lm_head.output, with no cap or scale; lm_head and embed_tokens are separate
weights. The tokenizer prepends nothing, so model.input_ids is the prompt's tokens alone;
<|endoftext|> (id 2) is the end of text.
Solar Open (SolarOpenForCausalLM).
Llama's tree and Llama's block: model.{embed_tokens, layers[i].{input_layernorm, self_attn, post_attention_layernorm, mlp}, norm} and lm_head, the residual
added in the block, attention through the shared eager forward.
The rotary covers partial_rotary_factor of each head (1.0, the whole head, on
Solar-Open-100B). Every block's MLP is a mixture of experts: transformers builds
SolarOpenMoE on every block whatever first_k_dense_replace says. It adds a
shared expert (mlp.shared_experts, a SolarOpenMLP) before it returns; the
experts are moe_intermediate_size wide. SolarOpenMLP is keyed to Mlp, so the
shared expert carries its own mlp_output.