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

60 statements  

« prev     ^ index     » next       coverage.py v7.10.1, created at 2026-10-10 05:10 +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 

10# Module import (not a name import): architecture_adapter_factory imports model_bridge, 

11# which imports this module, so its names may not be defined yet at this point. 

12from transformer_lens.factories import architecture_adapter_factory 

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 ``tl_bridge`` accepts a binary, single-row ``attention_mask`` matching the input 

66 length. Masked forwards use padding-aware positions where the model accepts 2-D 

67 position IDs; model-owned position derivation is left intact. The vLLM Inspect 

68 providers reject masks rather than silently ignore them. 

69 """ 

70 from inspect_ai.model import get_model 

71 from transformers import AutoConfig, AutoTokenizer 

72 

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

74 transformers_provider as _provider, 

75 ) 

76 

77 hf_token = get_hf_token() 

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

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

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

81 architecture = determine_architecture_from_hf_config(hf_config) 

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

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

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

85 

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

87 adapter = architecture_adapter_factory.ArchitectureAdapterFactory.select_architecture_adapter( 

88 bridge_config 

89 ) 

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

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

92 if tokenizer is not None: 

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

94 tokenizer = configure_tokenizer(tokenizer, adapter.cfg) 

95 

96 if provider == "tl_bridge": 

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

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

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

100 inspect_kwargs["model_kwargs"] = _provider_model_kwargs( 

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

102 ) 

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

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

105 # lies about what the engine actually loaded. 

106 inspect_kwargs["dtype"] = resolved_dtype 

107 

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

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

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

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

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

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

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

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

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

117 kinds = None 

118 note = "" 

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

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

121 psl = True 

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

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

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

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

126 profile = profiles.TLBridgeProfile( 

127 supported_kinds=kinds, 

128 provides_sequence_logits=psl, 

129 supports_attention_mask=bool(getattr(api, "supports_attention_mask", False)), 

130 ) 

131 if note: 

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

133 else: 

134 profile = profiles.for_provider(provider) 

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

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

137 _log_hook_summary(model_name, architecture, provider, driver) 

138 return bridge 

139 

140 

141def _provider_model_kwargs( 

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

143) -> dict[str, Any]: 

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

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

146 model_kwargs.setdefault("torch_dtype", dtype) 

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

148 model_kwargs.setdefault( 

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

150 ) 

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

152 model_kwargs.setdefault("token", hf_token) 

153 return model_kwargs 

154 

155 

156def _log_hook_summary( 

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

158) -> None: 

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

160 fireable = sorted(driver.supported_hook_points) 

161 log.info( 

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

163 model_name, 

164 architecture, 

165 provider, 

166 len(fireable), 

167 ) 

168 

169 

170__all__ = ["boot_inspect"]