You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Follow-up to #51. That issue covered architectures where the sublayer module output includes the residual (BLOOM, MPT, DBRX) and is being fixed on dev. This is the neighbouring semantic gap in the other direction: architectures where the module output is less than the residual contribution, because a post-sublayer norm sits outside the module.
The pattern
Gemma-2 and Gemma-3 (Gemma2DecoderLayer / Gemma3DecoderLayer, transformers 4.56):
GLM-4 (Glm4DecoderLayer) — same shape, attention-side norm spelled unambiguously, which frees post_attention_layernorm to play its Llama role (pre-MLP norm):
OLMo-2 (Olmo2DecoderLayer): same two post-norms, and no pre-attention norm at all.
So attentions_output[i] / mlps_output[i] (the self_attn / mlp module outputs) are the tensors before the post-norm; what is actually added to the residual stream is the post-norm output. layers_output[i] == layers_input[i] + attentions_output[i] + mlps_output[i] fails on these models — measured relative error on the tiny test zoo: gemma-2 0.78, gemma-3 15, glm-4 1.9 (0.0 on every plain pre-LN architecture).
Naming trap: Gemma's post_attention_layernorm normalizes the attention output; Llama's identically-named module normalizes the residual on its way into the MLP. Detection must not key on that name alone.
How interp-engine handles it
decoderesearch/interp-engine keeps both tensors as separate named capture points rather than redefining one (interp_engine/facts.py, "post-sublayer (sandwich) norms"; interp_engine/model.py::resolve_point):
attn_out / mlp_out = raw sublayer module output.
attn_out_post / mlp_out_post = defined as the residual contribution: resolves to the post-sublayer norm's output when one exists, and aliases the raw point on models without post-norms, so callers never branch on architecture. Their pinned invariant is resid_post == resid_pre + attn_out_post + mlp_out_post (tests/test_sandwich_norms.py).
Sandwich detection keys only on the MLP-side norm name (post_feedforward_layernorm, post_mlp_layernorm, post_mlp_norm, post_ffn_norm, post_norm2, ...), never on post_attention_layernorm alone — exactly because of the Gemma/Llama collision. Attention-side spellings: post_self_attn_layernorm, post_norm1, then the ambiguous post_attention_layernorm / post_attn_norm only when an MLP-side norm was found.
resid_mid is taken as the input of the pre-MLP norm (pre_feedforward_layernorm on Gemma-2/3, post_attention_layernorm on Llama-shaped blocks and GLM-4).
Proposal
Follow the same split instead of remapping attentions_output on these models:
add contribution accessors (attentions_output_post / mlps_output_post, or *_contribution) that target the post-sublayer norm output where one exists and alias *_output elsewhere, with a RenameConfig override analogous to attn_output_source / mlp_output_source;
detect the block shape from the MLP-side norm names as above;
pin layers_output == layers_input + attentions_output_post + mlps_output_post in the tests for gemma-2/3, glm-4, olmo-2 tiny models.
Note the docs on dev currently state the caveat explicitly; this issue is about closing it.
Opened by Claude (Fable 5) via Claude Code on behalf of @Butanium, from the #51 investigation.
Co-Authored-By: Claude noreply@anthropic.com
Follow-up to #51. That issue covered architectures where the sublayer module output includes the residual (BLOOM, MPT, DBRX) and is being fixed on
dev. This is the neighbouring semantic gap in the other direction: architectures where the module output is less than the residual contribution, because a post-sublayer norm sits outside the module.The pattern
Gemma-2 and Gemma-3 (
Gemma2DecoderLayer/Gemma3DecoderLayer, transformers 4.56):GLM-4 (
Glm4DecoderLayer) — same shape, attention-side norm spelled unambiguously, which freespost_attention_layernormto play its Llama role (pre-MLP norm):OLMo-2 (
Olmo2DecoderLayer): same two post-norms, and no pre-attention norm at all.So
attentions_output[i]/mlps_output[i](theself_attn/mlpmodule outputs) are the tensors before the post-norm; what is actually added to the residual stream is the post-norm output.layers_output[i] == layers_input[i] + attentions_output[i] + mlps_output[i]fails on these models — measured relative error on the tiny test zoo: gemma-2 0.78, gemma-3 15, glm-4 1.9 (0.0 on every plain pre-LN architecture).Naming trap: Gemma's
post_attention_layernormnormalizes the attention output; Llama's identically-named module normalizes the residual on its way into the MLP. Detection must not key on that name alone.How interp-engine handles it
decoderesearch/interp-engine keeps both tensors as separate named capture points rather than redefining one (
interp_engine/facts.py, "post-sublayer (sandwich) norms";interp_engine/model.py::resolve_point):attn_out/mlp_out= raw sublayer module output.attn_out_post/mlp_out_post= defined as the residual contribution: resolves to the post-sublayer norm's output when one exists, and aliases the raw point on models without post-norms, so callers never branch on architecture. Their pinned invariant isresid_post == resid_pre + attn_out_post + mlp_out_post(tests/test_sandwich_norms.py).post_feedforward_layernorm,post_mlp_layernorm,post_mlp_norm,post_ffn_norm,post_norm2, ...), never onpost_attention_layernormalone — exactly because of the Gemma/Llama collision. Attention-side spellings:post_self_attn_layernorm,post_norm1, then the ambiguouspost_attention_layernorm/post_attn_normonly when an MLP-side norm was found.resid_midis taken as the input of the pre-MLP norm (pre_feedforward_layernormon Gemma-2/3,post_attention_layernormon Llama-shaped blocks and GLM-4).Proposal
Follow the same split instead of remapping
attentions_outputon these models:attentions_output/mlps_outputas the module output (never residual-added, per Cross-architecture semantic inconsistency: attentions_output / mlps_output include the residual on BLOOM #51);attentions_output_post/mlps_output_post, or*_contribution) that target the post-sublayer norm output where one exists and alias*_outputelsewhere, with aRenameConfigoverride analogous toattn_output_source/mlp_output_source;layers_output == layers_input + attentions_output_post + mlps_output_postin the tests for gemma-2/3, glm-4, olmo-2 tiny models.Note the docs on
devcurrently state the caveat explicitly; this issue is about closing it.Opened by Claude (Fable 5) via Claude Code on behalf of @Butanium, from the #51 investigation.
Co-Authored-By: Claude noreply@anthropic.com