nnterp · encyclopedia llama · nnterp.families.llama

family llama

Llama

The reference block: one RMSNorm before each sublayer and none after, so what reaches the residual stream is the attention's and the MLP's own output, with rotary positions applied to queries and keys inside the attention.

The block

01 · the residual stream

One block of the stack. Hover any part for its nnterp name; click to pin the card.

embed_tokenstoken_embeddings
→
layers× 32
→
normfinal_norm_output
→
lm_headlm_head_output
→
logitsthe output
i = 0

identity layers[i].input + self_attn.attention_output + mlp.mlp_output == layer_output

The API

02 · support()

Values, by host

⚠︎ marks a value this checkpoint has only under some load: hover it for the condition.

model LlamaForCausalLMlayoutwhat it isread at
logits Logits [batch seq vocab_size] The logits the model returns, after anything it does past lm_head (a softcap, a scale)

the module's own output

token_embeddings Residual [batch seq hidden_size] The embedding module's output; layers[0].input is what enters the first block

the child module embed_tokens's output

final_norm_output Residual [batch seq hidden_size] The residual stream after the final norm, what lm_head reads

the child module norm's output

lm_head_output Logits [batch seq vocab_size] The head's logits before anything the model does past lm_head (a softcap, a scale)

the child module lm_head's output

next_token_probs NextTokenProbs [batch vocab_size] The next-token distribution at the last position; derived, read-only

the module's own output

input_ids Tokens [batch seq] The token ids the model was called with

the module's own input

attention_mask Tokens [batch seq] The attention mask the model was called with; zeros are padding

the module's own input

input_size scalar [batch, seq] of the current call; read-only

the module's own input

model.layers[i] LlamaDecoderLayerlayoutwhat it isread at
layer_input Residual [batch seq hidden_size] The residual stream entering the block

the module's own input

attention_norm_output Residual [batch seq hidden_size] The normed stream entering the attention

the child module input_layernorm's output

layer_mid Residual [batch seq hidden_size] The residual stream after the attention add

the child module post_attention_layernorm's input

mlp_norm_output Residual [batch seq hidden_size] The normed stream entering the MLP

the child module post_attention_layernorm's output

mlp_input Residual [batch seq hidden_size] What the MLP is called with

the child module mlp's input

layer_output Residual [batch seq hidden_size] The residual stream leaving the block, a tensor even when the block returns a tuple

the module's own output

model.layers[i].self_attn LlamaAttentionlayoutwhat it isread at
⚠︎attention_queries Queries [batch num_heads seq qk_head_dim] The queries entering attention

inside the forward: the arguments of attention_interface_1, element 1

⚠︎attention_keys Keys [batch num_kv_heads seq qk_head_dim] The keys entering attention

inside the forward: the arguments of attention_interface_1, element 2

⚠︎attention_values Values [batch num_kv_heads seq head_dim] The values entering attention

inside the forward: the arguments of attention_interface_1, element 3

⚠︎attention_scores Pattern [batch num_heads query key] The attention scores entering the softmax, masked

inside the forward: the input of nn_functional_softmax_0 inside attention_interface_1

attention_premix HeadOutputs [batch seq num_heads head_dim] Every head's output side by side, as the output projection takes it

the child module o_proj's input

attention_output Residual [batch seq hidden_size] What the attention adds to the residual stream

the module's own output

⚠︎attention_probabilities Pattern [batch num_heads query key] The attention pattern the values are mixed with

inside the forward: the output of nn_functional_dropout_0 inside attention_interface_1

⚠︎attention_head_outputs HeadOutputs [batch seq num_heads head_dim] The per-head outputs before the output projection

inside the forward: the output of attention_interface_1, element 0

model.layers[i].mlp LlamaMLPlayoutwhat it isread at
mlp_activation Neurons [batch seq intermediate_size] The activation function's output

the child module act_fn'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

the child module down_proj's input

mlp_output Residual [batch seq hidden_size] What the MLP adds to the residual stream

the module's own output

Printout

What print(model) shows on meta-llama/Llama-3.1-8B: the native modules, standard names and every standard value with its layout.

LlamaForCausalLM(
  (lm_head): Linear(in_features=4096, out_features=128256, bias=False)
  (generator): Generator(
    (streamer): Streamer()
  )
  (embed_tokens): Embedding(128256, 4096)
  (layers): ModuleList(
    (0-31): 32 x LlamaDecoderLayer(
      (self_attn): LlamaAttention(
        (q_proj): Linear(in_features=4096, out_features=4096, 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=4096, 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): LlamaMLP(
        (gate_proj): Linear(in_features=4096, out_features=14336, bias=False)
        (up_proj): Linear(in_features=4096, out_features=14336, bias=False)
        (down_proj): Linear(in_features=14336, 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
      )
      (input_layernorm): LlamaRMSNorm((4096,), eps=1e-05)
      (post_attention_layernorm): LlamaRMSNorm((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): LlamaRMSNorm((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
)

Config

model.num_layers32
model.hidden_size4096
model.num_heads32
model.num_kv_heads8
model.head_dim128
model.qk_head_dim128
model.vocab_size128256
model.intermediate_size14336
architecturesLlamaForCausalLM
num_hidden_layers32
hidden_size4096
intermediate_size14336
num_attention_heads32
num_key_value_heads8
head_dim128
vocab_size128256
max_position_embeddings131072
hidden_actsilu
rms_norm_eps1e-05
tie_word_embeddingsFalse
dtypebfloat16
attention_biasFalse

The Notes

03 · what to know before you run

The block, in order

h   = x + self_attn(input_layernorm(x))
out = h + mlp(post_attention_layernorm(h))

Two RMSNorms per block, each before its sublayer, none after. post_attention_layernorm is named for where it sits on the stream, after the attention's add: it is the MLP's input norm. Some families with a norm after each sublayer give the name to a different module (on Gemma-2 it is the norm after the attention), so check the block's forward before porting a hook by name.

The contributions are the modules' outputs

attention_output is self_attn.output[0] and mlp_output is mlp.output: the same tensors, added to the stream with nothing in between. The identity holds exactly, in float32 on the pinned checkpoint and in bfloat16 on Llama-3.2-1B:

with model.trace(prompt):
    x = model.layers[5].input.save()
    attn = model.layers[5].self_attn.attention_output.save()
    mlp = model.layers[5].mlp.mlp_output.save()
    out = model.layers[5].layer_output.save()

torch.testing.assert_close(x + attn + mlp, out)

So scaling a module's output scales its contribution: model.layers[i].mlp.output[:] *= 0.5 and model.layers[i].mlp.mlp_output[:] *= 0.5 give the same logits. The inputs are the norms' outputs: self_attn.input is input_layernorm.output, mlp.input is post_attention_layernorm.output, which nnterp serves as attention_norm_output and mlp_norm_output. The stream between the two adds is model.layers[i].layer_mid, read as post_attention_layernorm.input; it equals model.layers[i].input + model.layers[i].self_attn.attention_output.

The default load runs the same attention as eager

A default load runs sdpa, and the six attention interior values report unavailable; attn_implementation="eager" serves them. Llama has no score softcap, window or sink, so the two paths compute the same function: on Llama-3.2-1B in float32 their logits differ by about 2e-5, and in bfloat16 by up to 0.25 with the same top token at every position of a test prompt. The query scale is head_dim ** -0.5 (128 ** -0.5 on 8B, 64 ** -0.5 on 3.2-1B).

Grouped-query attention on Llama-3 and Llama-2-70B

8B and 3.2-1B have 32 query heads over 8 key/value heads, 3.2-3B 24 over 8, 70B (Llama-2 and Llama-3) 64 over 8, 405B 128 over 8. attention_keys and attention_values are read before repeat_kv, [batch, 8, seq, head_dim], and query head h reads key/value head h // 4 on 8B: an edit to key/value head j reaches query heads 4j to 4j + 3, a contiguous block. Llama-2-7B, Code Llama 7B and the pinned checkpoint have one key/value head per query head, so the same code edits one head there.

attn = model.layers[5].self_attn
groups = model.num_heads // model.num_kv_heads   # 4 on 8B and 3.2-1B
with model.trace(prompt):
    attn.attention_values[:, 1] = 0             # key/value head 1
    heads = attn.attention_head_outputs.save()  # only query heads 4 to 7 change

Queries and keys are read after the rotary embedding

attention_queries and attention_keys are rotated by position; the projections before it are the outputs of q_proj and k_proj. The two agree at position 0, where the rotation is the identity, and nowhere else. A key or query moved to another position keeps the rotation of the position it was read at. The cosines and sines are computed once per forward by the native model.model.rotary_emb and passed to every block. Llama-3.1, 3.2 and 3.3 use rope_type llama3 (rope_theta 500000, low frequencies rescaled by factor), Meta-Llama-3 the plain rotary with rope_theta 500000, and Llama-2 the plain rotary with rope_theta 10000. Both sides can be read in one trace:

attn = model.layers[5].self_attn
with model.trace(prompt):
    q_raw = attn.source.self_q_proj_0.output.save()   # before the rotary
    q = attn.attention_queries.save()                 # after it

Position 0 is a sink with a very large norm

On Llama-3.2-1B the residual stream at position 0 has a norm near 420 after blocks 1 and 5, against a mean of 2.4 and 4.5 at the other positions, and the heads of blocks 0, 1, 5, 8 and 15 put 0.6 to 0.85 of their attention on it on average, by block and prompt. The Llama-3 tokenizer prepends its beginning-of-text token (id 128000); without it the first real token takes the role (norm 865 after block 5). A mean over positions that includes position 0 is dominated by it: slice [:, 1:] before averaging activations for steering vectors, mean ablation or probes.

with model.trace(prompt):
    resid = model.layers[5].layer_output.save()

resid[0].float().norm(dim=-1)        # 422 at position 0, 4 to 5 elsewhere on 3.2-1B

Target tokens differ by tokenizer

On Llama-3, "Paris" (60704) and " Paris" (12366) are two whole-word tokens, and get_first_tokens(["Paris"], model) returns both. Llama-2's SentencePiece tokenizer prefixes the word marker itself: "Paris" and " Paris" both encode to ▁Paris (3681), and get_first_tokens(["Paris"], model) returns [2177, 3681], where 2177 is the mid-word fragment Par. Decode the target ids on the checkpoint you run.

The readout and the embeddings

model.logits is model.lm_head.output: no cap and no scale, so project_on_vocab is lm_head(norm(hidden)) and a logit lens at the last block equals logits. The final norm's gain is model.norm.weight as stored. Llama-3.2-1B and 3B tie lm_head to embed_tokens (one parameter), so an edit to embed_tokens.weight is an edit to the unembedding; the other Meta checkpoints do not.

What this family module covers

The module serves every checkpoint whose config says model_type llama: besides Meta's Llama-2, Llama-3, 3.1, 3.2 and 3.3, that includes Code Llama, TinyLlama, DeepSeek-R1-Distill-Llama-8B, SmolLM2 and Helium-1-2B. Their settings differ: SmolLM2-135M ties its embeddings, has 9 query heads over 3 key/value heads, and its tokenizer adds no beginning-of-sequence token; Code Llama 7B has rope_theta 1000000. Llama 4 is the llama4_text family.

Sparse autoencoders and transcoders

  • Llama Scope (OpenMOSS-Team/Llama-Scope), on Llama-3.1-8B, every block. The R SAEs read the stream after the block, model.layers[i].layer_output; A reads model.layers[i].self_attn.attention_output; M reads model.layers[i].mlp.mlp_output. The TC transcoders read the normed stream, model.layers[i].mlp_input, and predict mlp_output. Inputs are scaled to a norm of sqrt(hidden_size) before encoding.
  • EleutherAI EleutherAI/sae-llama-3-8b-32x, on Meta-Llama-3-8B: residual-stream SAEs whose hook points layers.i are the block outputs, model.layers[i].layer_output, and embed_tokens is model.token_embeddings.
  • circuit-tracer transcoders on Llama-3.2-1B, per-layer (mntss/transcoder-Llama-3.2-1B) and cross-layer (mntss/clt-llama-3.2-1b-524k). They read hook_resid_mid, the stream before the MLP's norm, model.layers[i].layer_mid, and write hook_mlp_out, model.layers[i].mlp.mlp_output:
with model.trace(prompt):
    mid = model.layers[i].layer_mid.save()           # hook_resid_mid
    out = model.layers[i].mlp.mlp_output.save()      # hook_mlp_out

From the family module

04 · nnterp/families/llama.py

Llama (LlamaForCausalLM), the family the standard vocabulary is taken from.

Block names (input_layernorm, self_attn, post_attention_layernorm, mlp) and lm_head are already the standard ones. The only change is lifting the containers out of model.model: model.layers instead of model.model.layers. Multimodal wrappers around a Llama text model keep it at model.language_model (Llava, DeepSeek-VL, Janus) or model.text_model (Idefics 3, SmolVLM); RENAME carries those spellings too.

Llava 1.5 (LlavaForConditionalGeneration, model_type llava), VipLlava and LLaVA-NeXT add a CLIP tower at model.vision_tower, which is vision (a Vision), and the projector model.multi_modal_projector, which is projector. Llava feeds the projector vision.layers[-2].layer_output without its CLS token (vision_feature_layer=-2, vision_feature_select_strategy="default"), not the tower's output; VipLlava concatenates several blocks' streams; LLaVA-NeXT unpads the projector's output and adds a newline token per row. vision.image_features is read at the scatter (ImageScatter), so it is what enters the text model on all three. CLIP's post_layernorm norms only the pooled CLS token, never the patches, so it is not vision.norm.

DeepSeek-VL keeps a SigLIP tower at model.vision_model with the projector model.aligner; Idefics 3 and SmolVLM keep their SigLIP-shaped ViT at model.vision_model (one row per image tile) with the pixel-shuffling model.connector as projector, and merge the features in a helper, inputs_merger. Those towers' post_layernorm does norm the patches; since a rename key cannot tell it from CLIP's, SiglipVision serves it as vision.norm.