From f10b46ea41aff0c7af5418f5aca6c4f357586253 Mon Sep 17 00:00:00 2001 From: emerard <113128214+emerardd@users.noreply.github.com> Date: Thu, 20 Aug 2026 14:53:12 +0800 Subject: [PATCH 1/3] Fix direct Bridge config flag assignment --- docs/source/content/migrating_to_v3.md | 2 +- .../test_config_flag_assignment.py | 129 ++++++++++++++++++ .../config/transformer_bridge_config.py | 45 +++++- transformer_lens/model_bridge/bridge.py | 14 +- 4 files changed, 183 insertions(+), 7 deletions(-) create mode 100644 tests/unit/model_bridge/test_config_flag_assignment.py diff --git a/docs/source/content/migrating_to_v3.md b/docs/source/content/migrating_to_v3.md index f5047bf2f..25142674b 100644 --- a/docs/source/content/migrating_to_v3.md +++ b/docs/source/content/migrating_to_v3.md @@ -122,7 +122,7 @@ For the full mapping of legacy → canonical names and the expected tensor shape Two semantic differences inside `enable_compatibility_mode()` worth knowing if you are porting activation-patching, DLA, or attribution-patching code: -- **`blocks.{i}.hook_mlp_in` fires pre-ln2** (matching legacy `HookedTransformer`). Use `bridge.set_use_hook_mlp_in(True)` to enable it — setting `cfg.use_hook_mlp_in = True` directly is honored when blocks share the bridge's `cfg`, but the setter is the supported entry point. The pre-ln2 placement means cached values from one run can be patched into another and re-flow through `ln2 → mlp` consistently across the bridge and `HookedTransformer`. +- **`blocks.{i}.hook_mlp_in` fires pre-ln2** (matching legacy `HookedTransformer`). Enable it with `bridge.set_use_hook_mlp_in(True)` or `bridge.cfg.use_hook_mlp_in = True`; direct config assignment routes through the same validation and propagation path as the setter. The pre-ln2 placement means cached values from one run can be patched into another and re-flow through `ln2 → mlp` consistently across the bridge and `HookedTransformer`. - **`hook_q_input` / `hook_k_input` / `hook_v_input` / `hook_attn_in`** also fire pre-ln1 in compat mode. On the per-head LN application that follows, the bridge routes through the raw HF norm rather than the `NormalizationBridge` wrapper, so `ln1`'s sub-hooks (`hook_in`, `hook_normalized`, `hook_scale`) do **not** fire once per head the way legacy `LayerNormPre` would. Q/K/V projections downstream still match legacy numerically; only the intermediate LN sub-hook firing is suppressed. Post-norm architectures (OLMo 2, BERT-style encoders) and MLA blocks (DeepSeek V2/V3/R1) do not participate in the pre-ln1 capture — `MLABlockBridge` does not expose those aliases, and post-norm models would read the post-attention residual instead of the block input. diff --git a/tests/unit/model_bridge/test_config_flag_assignment.py b/tests/unit/model_bridge/test_config_flag_assignment.py new file mode 100644 index 000000000..24af09345 --- /dev/null +++ b/tests/unit/model_bridge/test_config_flag_assignment.py @@ -0,0 +1,129 @@ +"""Tests for direct assignment of Bridge-managed hook flags (#1689).""" + +from __future__ import annotations + +import copy + +import pytest +import torch +from torch import nn +from transformers import GPT2Config, GPT2LMHeadModel + +from transformer_lens.config import TransformerBridgeConfig +from transformer_lens.model_bridge import TransformerBridge +from transformer_lens.model_bridge.sources._bridge_builder import ( + build_bridge_from_module, +) + + +def _cfg() -> TransformerBridgeConfig: + return TransformerBridgeConfig( + d_model=32, + d_head=16, + n_heads=2, + n_layers=1, + n_ctx=8, + d_vocab=16, + d_mlp=64, + act_fn="gelu", + normalization_type="LN", + seed=0, + ) + + +def _tiny_gpt2_bridge() -> TransformerBridge: + hf_config = GPT2Config( + n_layer=1, + n_head=2, + n_embd=32, + n_positions=8, + n_ctx=8, + vocab_size=16, + ) + hf_model = GPT2LMHeadModel(hf_config).eval() + return build_bridge_from_module( + hf_model, + "GPT2LMHeadModel", + hf_config=hf_config, + tokenizer=None, + device="cpu", + ) + + +@pytest.mark.parametrize( + ("flag_name", "hook_name"), + [ + ("use_attn_result", "blocks.0.attn.hook_result"), + ("use_attn_in", "blocks.0.attn.hook_attn_in"), + ("use_hook_mlp_in", "blocks.0.hook_mlp_in"), + ("use_split_qkv_input", "blocks.0.attn.hook_q_input"), + ], +) +def test_direct_assignment_matches_setter_hook_behavior(flag_name: str, hook_name: str) -> None: + bridge = _tiny_gpt2_bridge() + tokens = torch.randint(0, bridge.cfg.d_vocab, (1, 8)) + + setattr(bridge.cfg, flag_name, True) + _, direct_cache = bridge.run_with_cache(tokens, names_filter=[hook_name]) + + setattr(bridge.cfg, flag_name, False) + getattr(bridge, f"set_{flag_name}")(True) + _, setter_cache = bridge.run_with_cache(tokens, names_filter=[hook_name]) + + assert list(direct_cache) == [hook_name] + assert list(setter_cache) == [hook_name] + assert direct_cache[hook_name].shape == setter_cache[hook_name].shape + + +def test_direct_assignment_preserves_mutual_exclusivity() -> None: + bridge = _tiny_gpt2_bridge() + + bridge.cfg.use_split_qkv_input = True + with pytest.raises(ValueError, match="mutually exclusive"): + bridge.cfg.use_attn_in = True + assert bridge.cfg.use_attn_in is False + + bridge.cfg.use_split_qkv_input = False + bridge.cfg.use_attn_in = True + with pytest.raises(ValueError, match="mutually exclusive"): + bridge.cfg.use_split_qkv_input = True + assert bridge.cfg.use_split_qkv_input is False + + +@pytest.mark.parametrize("flag_name", ["use_attn_result", "use_attn_in", "use_split_qkv_input"]) +def test_direct_assignment_preserves_unsupported_architecture_errors( + monkeypatch: pytest.MonkeyPatch, flag_name: str +) -> None: + bridge = TransformerBridge.boot_native(_cfg()) + + class _FakeBlock(nn.Module): + def __init__(self) -> None: + super().__init__() + self.attn = nn.Identity() + + monkeypatch.setattr(bridge, "blocks", nn.ModuleList([_FakeBlock()]), raising=True) + + with pytest.raises(NotImplementedError, match=flag_name): + setattr(bridge.cfg, flag_name, True) + assert getattr(bridge.cfg, flag_name) is False + + +def test_deepcopied_live_config_is_not_bound_to_original_bridge() -> None: + bridge = TransformerBridge.boot_native(_cfg()) + copied_cfg = copy.deepcopy(bridge.cfg) + + copied_cfg.use_hook_mlp_in = True + + assert copied_cfg.use_hook_mlp_in is True + assert bridge.cfg.use_hook_mlp_in is False + + +def test_deepcopied_bridge_rebinds_its_config() -> None: + bridge = TransformerBridge.boot_native(_cfg()) + copied_bridge = copy.deepcopy(bridge) + + copied_bridge.cfg.use_hook_mlp_in = True + + assert copied_bridge.cfg.use_hook_mlp_in is True + assert copied_bridge.blocks[0].config.use_hook_mlp_in is True + assert bridge.cfg.use_hook_mlp_in is False diff --git a/transformer_lens/config/transformer_bridge_config.py b/transformer_lens/config/transformer_bridge_config.py index e09d55e75..008f7c8bd 100644 --- a/transformer_lens/config/transformer_bridge_config.py +++ b/transformer_lens/config/transformer_bridge_config.py @@ -1,6 +1,7 @@ """Configuration class for TransformerBridge.""" -from typing import Optional +import weakref +from typing import Any, Optional import torch @@ -18,6 +19,17 @@ class TransformerBridgeConfig(TransformerLensConfig): Also includes all HookedTransformerConfig fields for compatibility. """ + __slots__ = ("_bridge_ref",) + + _BRIDGE_MANAGED_HOOK_FLAGS = frozenset( + { + "use_attn_result", + "use_attn_in", + "use_hook_mlp_in", + "use_split_qkv_input", + } + ) + def __init__( self, d_model: int, @@ -109,6 +121,7 @@ def __init__( **kwargs, ): """Initialize TransformerBridgeConfig.""" + object.__setattr__(self, "_bridge_ref", None) super().__init__( d_model=d_model, d_head=d_head, @@ -204,9 +217,37 @@ def __init__( self.vision_num_layers = vision_num_layers self.vision_num_heads = vision_num_heads self.mm_tokens_per_image = mm_tokens_per_image - self.__post_init__() + def __setattr__(self, name: str, value: Any) -> None: + """Route live Bridge hook-flag assignments through their public setters.""" + if name in self._BRIDGE_MANAGED_HOOK_FLAGS: + bridge_ref = getattr(self, "_bridge_ref", None) + bridge = bridge_ref() if bridge_ref is not None else None + if bridge is not None: + getattr(bridge, f"set_{name}")(value) + return + super().__setattr__(name, value) + + def __getstate__(self) -> dict[str, Any]: + """Serialize config data without retaining its live Bridge binding.""" + return self.__dict__.copy() + + def __setstate__(self, state: dict[str, Any]) -> None: + """Restore an unbound config copy.""" + self.__dict__.update(state) + object.__setattr__(self, "_bridge_ref", None) + + def _bind_bridge(self, bridge: Any) -> None: + """Bind runtime hook-flag assignments to a constructed Bridge.""" + object.__setattr__(self, "_bridge_ref", weakref.ref(bridge)) + + def _set_bridge_managed_hook_flag(self, name: str, value: bool) -> None: + """Set a managed flag without re-entering the Bridge setter.""" + if name not in self._BRIDGE_MANAGED_HOOK_FLAGS: + raise ValueError(f"Unknown Bridge-managed hook flag: {name}") + object.__setattr__(self, name, value) + def __post_init__(self): """Post-initialization processing.""" # dtype is guaranteed to be set at this point diff --git a/transformer_lens/model_bridge/bridge.py b/transformer_lens/model_bridge/bridge.py index ea7fe953b..c22dfb240 100644 --- a/transformer_lens/model_bridge/bridge.py +++ b/transformer_lens/model_bridge/bridge.py @@ -241,6 +241,12 @@ def __init__(self, model: nn.Module, adapter: ArchitectureAdapter, tokenizer: An # train() recurses, so this stamps the wrappers with the model's mode. original_model.train(original_model.training) self.train(original_model.training) + self.cfg._bind_bridge(self) + + def __setstate__(self, state: dict[str, Any]) -> None: + """Restore runtime config routing after deepcopy or deserialization.""" + super().__setstate__(state) + self.cfg._bind_bridge(self) @property def tokenizer(self) -> Any: @@ -4951,7 +4957,7 @@ def set_use_attn_result(self, use_attn_result: bool): """ if use_attn_result: self._validate_attention_fork_supported("use_attn_result") - self.cfg.use_attn_result = use_attn_result + self.cfg._set_bridge_managed_hook_flag("use_attn_result", use_attn_result) self._propagate_attention_flag("use_attn_result", use_attn_result) def set_use_split_qkv_input(self, use_split_qkv_input: bool): @@ -4966,7 +4972,7 @@ def set_use_split_qkv_input(self, use_split_qkv_input: bool): "Call set_use_attn_in(False) before enabling use_split_qkv_input." ) self._validate_attention_fork_supported("use_split_qkv_input") - self.cfg.use_split_qkv_input = use_split_qkv_input + self.cfg._set_bridge_managed_hook_flag("use_split_qkv_input", use_split_qkv_input) self._propagate_attention_flag("use_split_qkv_input", use_split_qkv_input) def set_use_attn_in(self, use_attn_in: bool): @@ -4984,7 +4990,7 @@ def set_use_attn_in(self, use_attn_in: bool): "Call set_use_split_qkv_input(False) before enabling use_attn_in." ) self._validate_attention_fork_supported("use_attn_in") - self.cfg.use_attn_in = use_attn_in + self.cfg._set_bridge_managed_hook_flag("use_attn_in", use_attn_in) self._propagate_attention_flag("use_attn_in", use_attn_in) def set_use_hook_mlp_in(self, use_hook_mlp_in: bool) -> None: @@ -4993,7 +4999,7 @@ def set_use_hook_mlp_in(self, use_hook_mlp_in: bool) -> None: See :py:meth:`HookedTransformer.set_use_hook_mlp_in`. """ - self.cfg.use_hook_mlp_in = use_hook_mlp_in + self.cfg._set_bridge_managed_hook_flag("use_hook_mlp_in", use_hook_mlp_in) if not hasattr(self, "blocks"): return for block in self.blocks: From fe789df43cb9a2c2da8be1f3b8ba8ed3f5100da9 Mon Sep 17 00:00:00 2001 From: emerard <113128214+emerardd@users.noreply.github.com> Date: Fri, 21 Aug 2026 12:06:52 +0800 Subject: [PATCH 2/3] Address Bridge config assignment review feedback --- .../test_config_flag_assignment.py | 77 ++++++++++++++++++- .../config/transformer_bridge_config.py | 4 +- transformer_lens/model_bridge/bridge.py | 16 +++- 3 files changed, 89 insertions(+), 8 deletions(-) diff --git a/tests/unit/model_bridge/test_config_flag_assignment.py b/tests/unit/model_bridge/test_config_flag_assignment.py index 24af09345..02ca6b55b 100644 --- a/tests/unit/model_bridge/test_config_flag_assignment.py +++ b/tests/unit/model_bridge/test_config_flag_assignment.py @@ -3,11 +3,12 @@ from __future__ import annotations import copy +import gc import pytest import torch from torch import nn -from transformers import GPT2Config, GPT2LMHeadModel +from transformers import GPT2Config, GPT2LMHeadModel, LlamaConfig, LlamaForCausalLM from transformer_lens.config import TransformerBridgeConfig from transformer_lens.model_bridge import TransformerBridge @@ -50,6 +51,34 @@ def _tiny_gpt2_bridge() -> TransformerBridge: ) +def _tiny_llama_bridge() -> TransformerBridge: + hf_config = LlamaConfig( + hidden_size=32, + intermediate_size=64, + num_hidden_layers=1, + num_attention_heads=2, + num_key_value_heads=2, + vocab_size=16, + max_position_embeddings=8, + ) + hf_model = LlamaForCausalLM(hf_config).eval() + return build_bridge_from_module( + hf_model, + "LlamaForCausalLM", + hf_config=hf_config, + tokenizer=None, + device="cpu", + ) + + +@pytest.fixture(params=["gpt2", "llama"], ids=["shared-config", "cloned-config"]) +def bridge_with_config_mode(request: pytest.FixtureRequest) -> TransformerBridge: + bridge = _tiny_gpt2_bridge() if request.param == "gpt2" else _tiny_llama_bridge() + attn_config_is_shared = bridge.blocks[0].attn.config is bridge.cfg + assert attn_config_is_shared is (request.param == "gpt2") + return bridge + + @pytest.mark.parametrize( ("flag_name", "hook_name"), [ @@ -59,8 +88,10 @@ def _tiny_gpt2_bridge() -> TransformerBridge: ("use_split_qkv_input", "blocks.0.attn.hook_q_input"), ], ) -def test_direct_assignment_matches_setter_hook_behavior(flag_name: str, hook_name: str) -> None: - bridge = _tiny_gpt2_bridge() +def test_direct_assignment_matches_setter_hook_behavior( + bridge_with_config_mode: TransformerBridge, flag_name: str, hook_name: str +) -> None: + bridge = bridge_with_config_mode tokens = torch.randint(0, bridge.cfg.d_vocab, (1, 8)) setattr(bridge.cfg, flag_name, True) @@ -127,3 +158,43 @@ def test_deepcopied_bridge_rebinds_its_config() -> None: assert copied_bridge.cfg.use_hook_mlp_in is True assert copied_bridge.blocks[0].config.use_hook_mlp_in is True assert bridge.cfg.use_hook_mlp_in is False + + +def test_shallow_copied_bridge_does_not_replace_live_config_binding() -> None: + bridge = TransformerBridge.boot_native(_cfg()) + copied_bridge = copy.copy(bridge) + + assert copied_bridge.cfg is bridge.cfg + assert bridge.cfg._bridge_ref() is bridge + + del copied_bridge + gc.collect() + bridge.cfg.use_hook_mlp_in = True + + assert bridge.blocks[0].config.use_hook_mlp_in is True + + +def test_attention_flag_propagation_does_not_dispatch_bound_cloned_config() -> None: + bridge = _tiny_llama_bridge() + cloned_cfg = bridge.blocks[0].attn.config + other_bridge = TransformerBridge.boot_native(_cfg()) + assert cloned_cfg is not bridge.cfg + cloned_cfg._bind_bridge(other_bridge) + + bridge.set_use_attn_in(True) + + assert cloned_cfg.use_attn_in is True + assert other_bridge.cfg.use_attn_in is False + + +def test_mlp_flag_propagation_does_not_dispatch_bound_cloned_config() -> None: + bridge = _tiny_gpt2_bridge() + cloned_cfg = bridge.blocks[0].config + other_bridge = TransformerBridge.boot_native(_cfg()) + assert cloned_cfg is not bridge.cfg + cloned_cfg._bind_bridge(other_bridge) + + bridge.set_use_hook_mlp_in(True) + + assert cloned_cfg.use_hook_mlp_in is True + assert other_bridge.cfg.use_hook_mlp_in is False diff --git a/transformer_lens/config/transformer_bridge_config.py b/transformer_lens/config/transformer_bridge_config.py index 008f7c8bd..21132dac9 100644 --- a/transformer_lens/config/transformer_bridge_config.py +++ b/transformer_lens/config/transformer_bridge_config.py @@ -240,7 +240,9 @@ def __setstate__(self, state: dict[str, Any]) -> None: def _bind_bridge(self, bridge: Any) -> None: """Bind runtime hook-flag assignments to a constructed Bridge.""" - object.__setattr__(self, "_bridge_ref", weakref.ref(bridge)) + bridge_ref = getattr(self, "_bridge_ref", None) + if bridge_ref is None or bridge_ref() is None: + object.__setattr__(self, "_bridge_ref", weakref.ref(bridge)) def _set_bridge_managed_hook_flag(self, name: str, value: bool) -> None: """Set a managed flag without re-entering the Bridge setter.""" diff --git a/transformer_lens/model_bridge/bridge.py b/transformer_lens/model_bridge/bridge.py index c22dfb240..5f572d528 100644 --- a/transformer_lens/model_bridge/bridge.py +++ b/transformer_lens/model_bridge/bridge.py @@ -5006,11 +5006,19 @@ def set_use_hook_mlp_in(self, use_hook_mlp_in: bool) -> None: block_cfg = getattr(block, "config", None) if block_cfg is not None and block_cfg is not self.cfg: try: - block_cfg.use_hook_mlp_in = use_hook_mlp_in - except Exception: + self._write_propagated_hook_flag(block_cfg, "use_hook_mlp_in", use_hook_mlp_in) + except (AttributeError, TypeError): pass block._use_hook_mlp_in = use_hook_mlp_in + @staticmethod + def _write_propagated_hook_flag(config: Any, flag_name: str, value: bool) -> None: + """Write a cloned config flag without dispatching through its live Bridge.""" + if isinstance(config, TransformerBridgeConfig): + config._set_bridge_managed_hook_flag(flag_name, value) + else: + object.__setattr__(config, flag_name, value) + def _propagate_attention_flag(self, flag_name: str, value: bool) -> None: """Mirror `bridge.cfg.` onto every block's attention config. @@ -5031,8 +5039,8 @@ def _propagate_attention_flag(self, flag_name: str, value: bool) -> None: attn_cfg = getattr(attn, "config", None) if attn_cfg is not None and attn_cfg is not self.cfg: try: - setattr(attn_cfg, flag_name, value) - except Exception: + self._write_propagated_hook_flag(attn_cfg, flag_name, value) + except (AttributeError, TypeError): # Some cfg objects may be frozen/immutable. Skip silently — # the block simply won't honor the flag, which is the # same outcome as before this fix. From 5d066b739d47bc54431fc3e7e9fabd5804afa6fa Mon Sep 17 00:00:00 2001 From: emerard <113128214+emerardd@users.noreply.github.com> Date: Fri, 21 Aug 2026 23:57:36 +0800 Subject: [PATCH 3/3] Warn on reused live Bridge configs --- .../test_config_flag_assignment.py | 25 ++++++++++++++++++- .../config/transformer_bridge_config.py | 12 ++++++++- transformer_lens/model_bridge/bridge.py | 5 ++-- 3 files changed, 37 insertions(+), 5 deletions(-) diff --git a/tests/unit/model_bridge/test_config_flag_assignment.py b/tests/unit/model_bridge/test_config_flag_assignment.py index 02ca6b55b..d613312a0 100644 --- a/tests/unit/model_bridge/test_config_flag_assignment.py +++ b/tests/unit/model_bridge/test_config_flag_assignment.py @@ -11,10 +11,14 @@ from transformers import GPT2Config, GPT2LMHeadModel, LlamaConfig, LlamaForCausalLM from transformer_lens.config import TransformerBridgeConfig +from transformer_lens.factories.architecture_adapter_factory import ( + ArchitectureAdapterFactory, +) from transformer_lens.model_bridge import TransformerBridge from transformer_lens.model_bridge.sources._bridge_builder import ( build_bridge_from_module, ) +from transformer_lens.model_bridge.sources.native import NativeModel def _cfg() -> TransformerBridgeConfig: @@ -162,7 +166,8 @@ def test_deepcopied_bridge_rebinds_its_config() -> None: def test_shallow_copied_bridge_does_not_replace_live_config_binding() -> None: bridge = TransformerBridge.boot_native(_cfg()) - copied_bridge = copy.copy(bridge) + with pytest.warns(UserWarning, match="already bound to another live"): + copied_bridge = copy.copy(bridge) assert copied_bridge.cfg is bridge.cfg assert bridge.cfg._bridge_ref() is bridge @@ -174,6 +179,24 @@ def test_shallow_copied_bridge_does_not_replace_live_config_binding() -> None: assert bridge.blocks[0].config.use_hook_mlp_in is True +def test_constructor_warns_when_live_bridge_already_owns_config() -> None: + cfg = _cfg() + cfg.architecture = "TransformerLensNative" + first_model = NativeModel(cfg) + second_model = NativeModel(cfg) + first_adapter = ArchitectureAdapterFactory.select_architecture_adapter(cfg) + second_adapter = ArchitectureAdapterFactory.select_architecture_adapter(cfg) + first_adapter.prepare_model(first_model) + second_adapter.prepare_model(second_model) + first_bridge = TransformerBridge(first_model, first_adapter, tokenizer=None) + + with pytest.warns(UserWarning, match="already bound to another live"): + second_bridge = TransformerBridge(second_model, second_adapter, tokenizer=None) + + assert second_bridge.cfg is first_bridge.cfg + assert cfg._bridge_ref() is first_bridge + + def test_attention_flag_propagation_does_not_dispatch_bound_cloned_config() -> None: bridge = _tiny_llama_bridge() cloned_cfg = bridge.blocks[0].attn.config diff --git a/transformer_lens/config/transformer_bridge_config.py b/transformer_lens/config/transformer_bridge_config.py index 21132dac9..a8f9becc6 100644 --- a/transformer_lens/config/transformer_bridge_config.py +++ b/transformer_lens/config/transformer_bridge_config.py @@ -1,5 +1,6 @@ """Configuration class for TransformerBridge.""" +import warnings import weakref from typing import Any, Optional @@ -241,8 +242,17 @@ def __setstate__(self, state: dict[str, Any]) -> None: def _bind_bridge(self, bridge: Any) -> None: """Bind runtime hook-flag assignments to a constructed Bridge.""" bridge_ref = getattr(self, "_bridge_ref", None) - if bridge_ref is None or bridge_ref() is None: + bound_bridge = bridge_ref() if bridge_ref is not None else None + if bound_bridge is None: object.__setattr__(self, "_bridge_ref", weakref.ref(bridge)) + elif bound_bridge is not bridge: + warnings.warn( + "TransformerBridgeConfig is already bound to another live " + "TransformerBridge; declining to bind it to this instance. " + "Direct assignments to Bridge-managed hook flags will continue " + "to configure the existing TransformerBridge.", + stacklevel=3, + ) def _set_bridge_managed_hook_flag(self, name: str, value: bool) -> None: """Set a managed flag without re-entering the Bridge setter.""" diff --git a/transformer_lens/model_bridge/bridge.py b/transformer_lens/model_bridge/bridge.py index 5f572d528..8eac0693b 100644 --- a/transformer_lens/model_bridge/bridge.py +++ b/transformer_lens/model_bridge/bridge.py @@ -5041,9 +5041,8 @@ def _propagate_attention_flag(self, flag_name: str, value: bool) -> None: try: self._write_propagated_hook_flag(attn_cfg, flag_name, value) except (AttributeError, TypeError): - # Some cfg objects may be frozen/immutable. Skip silently — - # the block simply won't honor the flag, which is the - # same outcome as before this fix. + # Some config-like objects reject attributes even when + # bypassing their custom __setattr__ implementation. pass def _validate_attention_fork_supported(self, flag_name: str) -> None: