Skip to content

[Bug Report] Gemma 4 MLPs omit hook_pre, hook_pre_linear, and hook_post #1646

Description

@hijohnnylin

Summary

Gemma3ArchitectureAdapter maps each block's MLP with self._gated_mlp(), which creates a GatedMLPBridge and supplies the standard gated-MLP aliases:

hook_pre        -> gate.hook_out
hook_pre_linear -> in.hook_out
hook_post       -> out.hook_in

Gemma4ArchitectureAdapter maps the same gate_proj / up_proj / down_proj structure to a bare GeneralizedComponent. As a result, Gemma 4 exposes only the MLP boundary hooks (hook_in and hook_out); the three neuron-basis hooks are absent from hook_dict.

This is a mapping gap rather than an architectural limitation: Hugging Face's Gemma4TextMLP is an ordinary gated MLP, and the TransformerLens adapter already identifies all three projection submodules.

Versions

  • transformer-lens==3.7.0
  • transformers==5.15.0
  • Python 3.12

I also checked the v3.7.1 source. The Gemma 4 MLP mapping is unchanged.

Reproduction

This is a config-only reproduction; it does not load model weights.

transformers==5.15.0 also exposes a separate Gemma 4 heterogeneous-config crash in TransformerLens. The two-line opt-in below is used only to get past that independent crash and inspect the adapter mapping. It is not a proposed runtime workaround.

pip install "transformer-lens==3.7.1" "transformers==5.15.0"
python gemma4_mlp_hooks.py
# gemma4_mlp_hooks.py
from transformers import AutoConfig
from transformer_lens.config import TransformerBridgeConfig
from transformer_lens.factories.architecture_adapter_factory import (
    ArchitectureAdapterFactory,
)
from transformer_lens.model_bridge.sources.transformers import (
    determine_architecture_from_hf_config,
    map_default_transformer_lens_config,
)

for model_id in ("google/gemma-3-270m", "google/gemma-4-E2B"):
    hf_config = AutoConfig.from_pretrained(model_id)

    # Isolate the adapter-mapping question from the separate heterogeneous-config crash.
    for config_obj in (hf_config, hf_config.get_text_config()):
        config_obj.allow_global_per_layer_attribute_access = True

    mapped = map_default_transformer_lens_config(hf_config)
    cfg = TransformerBridgeConfig.from_dict(dict(mapped.__dict__))
    cfg.architecture = determine_architecture_from_hf_config(hf_config)
    adapter = ArchitectureAdapterFactory.select_architecture_adapter(cfg)
    mlp = adapter.component_mapping["blocks"].submodules["mlp"]
    aliases = getattr(type(mlp), "hook_aliases", {})

    print(f"{model_id:22s} {type(adapter).__name__}")
    print(
        f"{'':22s} mlp -> {type(mlp).__name__}, "
        f"submodules {sorted(mlp.submodules)}"
    )
    print(f"{'':22s} hook_aliases {aliases or '{}'}")

Observed:

google/gemma-3-270m    Gemma3ArchitectureAdapter
                       mlp -> GatedMLPBridge, submodules ['gate', 'in', 'out']
                       hook_aliases {'hook_pre': 'gate.hook_out', 'hook_pre_linear': 'in.hook_out', 'hook_post': 'out.hook_in'}
google/gemma-4-E2B     Gemma4ArchitectureAdapter
                       mlp -> GeneralizedComponent, submodules ['gate', 'in', 'out']
                       hook_aliases {}

The two adapters identify the same three MLP projections, but only Gemma 3 installs the compatibility aliases.

Expected behavior

For the dense Gemma4TextMLP submodule, these hooks should be available with the same semantics as other gated MLPs:

blocks.N.mlp.hook_pre
blocks.N.mlp.hook_pre_linear
blocks.N.mlp.hook_post

Actual behavior

None of those names is present for Gemma 4. Code written against the Gemma 3 hook interface fails when it attempts to select the corresponding Gemma 4 MLP activation.

Root cause

Gemma 3 uses the gated-MLP helper:

GatedMLPBridge defines the expected aliases:

Gemma 4 instead uses a bare GeneralizedComponent, despite naming gate_proj, up_proj, and down_proj:

The Hugging Face module has exactly those three projections:

Impact

The missing aliases block the standard TransformerLens MLP-neuron interface on Gemma 4. The failure is loud (hook_dict has no matching key), but it prevents existing activation-caching and intervention code from using the same hook names that work on Gemma 3 and other gated-MLP families.

Suggested fix

Replace the bare MLP mapping with the same helper used by Gemma 3:

"mlp": self._gated_mlp(),

The submodule names already match the helper's defaults.

Suggested regression tests:

  1. Assert that all three aliases are present on a Gemma 4 block.
  2. Assert that they resolve to gate_proj output, up_proj output, and down_proj input.
  3. Cover a standard-width layer and a double-wide MLP layer on E2B/E4B.
  4. Cover a model with Gemma 4's optional MoE branch to ensure changing the dense mlp mapping does not disturb the separate router/expert components.

Metadata

Metadata

Assignees

No one assigned

    Labels

    TransformerBridgeBug specific to the new TransformerBridge systembugSomething isn't workingcomplexity-simpleSimple issues, which may be good for beginners

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions