Coverage for transformer_lens/model_bridge/sources/inspect/source.py: 93%

60 statements  

« prev     ^ index     » next       coverage.py v7.10.1, created at 2026-09-21 19:27 +0000

1"""``boot_inspect`` — wrap an ``inspect_ai`` provider in a RemoteBridge via InspectDriver.""" 

2from __future__ import annotations 

3 

4import logging 

5import warnings 

6from typing import Any, Optional 

7 

8import torch 

9 

10from transformer_lens.factories.architecture_adapter_factory import ( 

11 ArchitectureAdapterFactory, 

12) 

13from transformer_lens.model_bridge.remote_bridge import RemoteBridge 

14from transformer_lens.model_bridge.sources._bridge_builder import ( 

15 build_bridge_config_from_hf, 

16 configure_tokenizer, 

17 skip_tokenizer_for_modality, 

18) 

19from transformer_lens.model_bridge.sources._hf_format import ( 

20 determine_architecture_from_hf_config, 

21) 

22from transformer_lens.utilities.hf_utils import get_hf_token 

23 

24from . import profiles 

25from .driver import InspectDriver 

26 

27# Providers that expose the structural self-check + capture wire format the InspectDriver 

28# consumes. boot_inspect queries supported_kinds on these; others route via for_provider. 

29_TL_BRIDGE_PROVIDERS = {"tl_bridge", "tl_bridge_vllm"} 

30 

31 

32def boot_inspect( 

33 model_name: str, 

34 tokenizer: Optional[Any] = None, 

35 dtype: Optional[torch.dtype] = None, 

36 provider: str = "tl_bridge", 

37 **inspect_kwargs: Any, 

38) -> RemoteBridge: 

39 """Boot a model via an ``inspect_ai`` provider and wrap it in a :class:`RemoteBridge`. 

40 

41 The driver is provider-agnostic: ``provider`` defaults to our own HF-backed 

42 ``tl_bridge`` provider (residual/attn/mlp capture + full affine interventions + 

43 full-sequence logits); ``"vllm-lens"`` targets a running vllm-lens vLLM provider 

44 (residual-only, additive-steering-only) — wire-aligned with its documented format, 

45 but not yet verified against a live provider. 

46 

47 Fireable hooks (``tl_bridge``, TransformerBridge-native names): ``blocks.{i}.hook_in`` 

48 (resid_pre) / ``ln2.hook_in`` (resid_mid) / 

49 ``hook_out`` (resid_post) / ``attn.hook_out`` / ``mlp.hook_out``, plus the head-split 

50 attention hooks where the structural probe finds them: ``attn.hook_q/k/v`` (pre-RoPE 

51 projection outputs; separate-projection archs only — fused qkv gates them), 

52 ``attn.hook_z`` (out-projection input), and ``attn.hook_pattern`` (post-softmax, 

53 capture-only, eager attention required). The provider runs a structural self-check per 

54 model and gates any boundary it can't serve faithfully: ``resid_mid`` for 

55 parallel-residual or norm-variant blocks, ``attn_out``/``mlp_out`` when their submodule 

56 isn't locatable (it warns when it gates one). ``embed``, ``ln_final``, and 

57 ``attn.hook_attn_scores`` are always non-fireable — use ``boot_transformers()`` for 

58 those. 

59 

60 For parity with ``boot_transformers`` the provider loads with the same dtype (fp32 by 

61 default) and eager attention. Full-sequence logits ride on ``return_logits=True`` (the 

62 default); pass ``return_logits=False`` to skip the (seq × d_vocab) payload for pure 

63 activation capture (``run_with_cache`` keeps them since it returns logits). 

64 """ 

65 from inspect_ai.model import get_model 

66 from transformers import AutoConfig, AutoTokenizer 

67 

68 from . import ( # noqa: F401 — import registers @modelapi 

69 transformers_provider as _provider, 

70 ) 

71 

72 hf_token = get_hf_token() 

73 hf_config = AutoConfig.from_pretrained(model_name, token=hf_token) 

74 # Shared resolution (not architectures[0]) handles architectures=None configs via 

75 # model_type and rejects unsupported archs before the provider loads weights. 

76 architecture = determine_architecture_from_hf_config(hf_config) 

77 # Default fp32 to match boot_transformers (which loads/casts fp32 regardless of the 

78 # config's native dtype); an explicit dtype still wins. 

79 resolved_dtype = dtype if dtype is not None else torch.float32 

80 

81 bridge_config = build_bridge_config_from_hf(hf_config, architecture, model_name, resolved_dtype) 

82 adapter = ArchitectureAdapterFactory.select_architecture_adapter(bridge_config) 

83 if tokenizer is None and not skip_tokenizer_for_modality(adapter.cfg): 

84 tokenizer = AutoTokenizer.from_pretrained(model_name, token=hf_token) 

85 if tokenizer is not None: 

86 # Match boot_transformers' tokenizer setup so to_tokens(str) is token-identical. 

87 tokenizer = configure_tokenizer(tokenizer, adapter.cfg) 

88 

89 if provider == "tl_bridge": 

90 # The provider's raw HF forward must match boot_transformers' load: same dtype, 

91 # eager attention (TL forces eager — SDPA/flash diverge and accumulate with depth), 

92 # and auth/remote-code so gated/custom models load at all. 

93 inspect_kwargs["model_kwargs"] = _provider_model_kwargs( 

94 dict(inspect_kwargs.get("model_kwargs", {})), adapter, resolved_dtype, hf_token 

95 ) 

96 elif provider == "tl_bridge_vllm": 96 ↛ 104line 96 didn't jump to line 104 because the condition on line 96 was always true

97 # Otherwise the provider defaults to the HF-config dtype and bridge_config.dtype 

98 # lies about what the engine actually loaded. 

99 inspect_kwargs["dtype"] = resolved_dtype 

100 

101 # memoize=False: inspect_ai caches get_model by name, which would (a) return a stale 

102 # model ignoring a changed dtype/kwargs on re-boot and (b) keep weights resident past 

103 # close(). Each boot must honor its own args and own its model's lifecycle. 

104 model = get_model(f"{provider}/{model_name}", memoize=False, **inspect_kwargs) 

105 # Both TL-bridge providers (HF + vLLM) restrict their profile to the boundaries the 

106 # provider's structural self-check / overlay found this model can serve; warn only 

107 # if it gated something. Third-party providers (e.g. vllm-lens) route via for_provider. 

108 if provider in _TL_BRIDGE_PROVIDERS: 108 ↛ 123line 108 didn't jump to line 123 because the condition on line 108 was always true

109 api = getattr(model, "api", None) 

110 kinds = None 

111 note = "" 

112 # Default True for back-compat; vLLM provider sets False so RemoteBridge.forward 

113 # rejects loss/both (otherwise loss over -inf earlier positions silently NaNs). 

114 psl = True 

115 if api is not None: 115 ↛ 119line 115 didn't jump to line 119 because the condition on line 115 was always true

116 kinds = api.supported_kinds() if hasattr(api, "supported_kinds") else None 

117 note = api.capability_note() if hasattr(api, "capability_note") else "" 

118 psl = bool(getattr(api, "provides_sequence_logits", True)) 

119 profile = profiles.TLBridgeProfile(supported_kinds=kinds, provides_sequence_logits=psl) 

120 if note: 

121 warnings.warn(note, UserWarning, stacklevel=2) 

122 else: 

123 profile = profiles.for_provider(provider) 

124 driver = InspectDriver(model=model, adapter=adapter, tokenizer=tokenizer, profile=profile) 

125 bridge = RemoteBridge(adapter=adapter, tokenizer=tokenizer, driver=driver) 

126 _log_hook_summary(model_name, architecture, provider, driver) 

127 return bridge 

128 

129 

130def _provider_model_kwargs( 

131 model_kwargs: dict[str, Any], adapter: Any, dtype: torch.dtype, hf_token: Optional[str] 

132) -> dict[str, Any]: 

133 """Load kwargs for the HF provider that mirror boot_transformers, so the provider's 

134 raw forward matches the bridge. Caller-supplied keys win (setdefault).""" 

135 model_kwargs.setdefault("torch_dtype", dtype) 

136 # boot_transformers forces eager unless the adapter pins an implementation. 

137 model_kwargs.setdefault( 

138 "attn_implementation", getattr(adapter.cfg, "attn_implementation", None) or "eager" 

139 ) 

140 if hf_token: 140 ↛ 142line 140 didn't jump to line 142 because the condition on line 140 was always true

141 model_kwargs.setdefault("token", hf_token) 

142 return model_kwargs 

143 

144 

145def _log_hook_summary( 

146 model_name: str, architecture: str, provider: str, driver: InspectDriver 

147) -> None: 

148 log = logging.getLogger("transformer_lens.inspect") 

149 fireable = sorted(driver.supported_hook_points) 

150 log.info( 

151 "Inspect source on %s (%s) via provider %r serves %d fireable hook(s).", 

152 model_name, 

153 architecture, 

154 provider, 

155 len(fireable), 

156 ) 

157 

158 

159__all__ = ["boot_inspect"]