Skip to content

Activation Patching

Activation patching restores or modifies activations in a corrupted run using values from a clean cache. The SafeLens patching layer is intentionally generic: it defines patch operations, not a specific safety algorithm.

Design reference: TransformerLens activation patching.

Core utilities:

  • PatchSpec: describes one activation patch.
  • apply_patch: applies one patch to an activation.
  • make_patch_hook: creates a forward hook for one patch.
  • run_activation_patch: runs one patched forward pass and scores it.
  • generic_activation_patch: runs a sequence of patch specs.
  • make_patch_specs: creates a simple grid of patch specs.
  • component_activation_patch: runs a Transformer component patch grid.
  • make_component_patch_specs: creates component-level specs by named axes.
  • patch_results_to_metric_grid: converts detailed runs into a TransformerLens-style metric grid.
  • patch_results_to_index_table: returns the axis index table for patch runs.

Supported Transformer component helpers:

Component family Helper functions
Residual stream get_act_patch_resid_pre, get_act_patch_resid_mid, get_act_patch_resid_post
Block outputs get_act_patch_attn_out, get_act_patch_mlp_out, get_act_patch_block_every
Head vectors by position get_act_patch_attn_head_out_by_pos, get_act_patch_attn_head_q_by_pos, get_act_patch_attn_head_k_by_pos, get_act_patch_attn_head_v_by_pos, get_act_patch_attn_head_result_by_pos
Head vectors all positions get_act_patch_attn_head_out_all_pos, get_act_patch_attn_head_q_all_pos, get_act_patch_attn_head_k_all_pos, get_act_patch_attn_head_v_all_pos, get_act_patch_attn_head_result_all_pos
Attention patterns get_act_patch_attn_head_pattern_all_pos, get_act_patch_attn_head_pattern_by_pos, get_act_patch_attn_head_pattern_dest_src_pos
Attention scores get_act_patch_attn_scores_all_pos, get_act_patch_attn_scores_by_pos, get_act_patch_attn_scores_dest_src_pos

The component helpers support both SafeLens names such as layer_0.resid_pre and TransformerLens-style names such as blocks.0.hook_resid_pre by setting name_style="transformer_lens".

When explicit positions are omitted, residual/head-vector helpers infer sequence length from tokens or [batch, pos, ...] activations. Attention pattern and score helpers infer destination/source positions from [batch, head, dest_pos, src_pos] activations.

The TransformerLens-style component helpers default to metric grids: for example get_act_patch_resid_pre returns a [layer, pos] grid and get_act_patch_block_every returns a stacked [patch_type, layer, pos] result. Pass return_details=True when you need detailed PatchResult records with the patched output and cache for each run. Pass return_index_df=True to also return the index table; the table is a list of dictionaries and does not require pandas.

generic_activation_patch also accepts the TransformerLens-style call shape: pass patching_metric, a TL-style patch_setter(activation, index, clean_activation), activation_name, and either index_axis_names or an explicit index_df table. When index_df is explicit, metric output is flat, matching TransformerLens' behavior. SafeLens-style calls that pass explicit PatchSpec objects still return detailed PatchResult records by default; pass return_details=False to format those as metric grids.

The exported component setters such as layer_pos_patch_setter and layer_head_vector_patch_setter accept both SafeLens' internal (activation, PatchSpec, ActivationCache) shape and TransformerLens' (activation, index, clean_activation) shape.

Model compatibility note: the patching layer can express the operations above, but a concrete ModelWrapper must expose matching hook points and tensor shapes. In particular, result helpers require true per-head result tensors, not merged attention projection outputs. A raw HuggingFace module wrapper only exposes module-level hooks unless extended with component hooks.

Example:

from SafeLens.core.hooks import ActivationCache
from SafeLens.core.patching import PatchSpec, run_activation_patch

clean_cache = ActivationCache({"layer_0": clean_activation})
spec = PatchSpec(layer=0, target_index=3)

result = run_activation_patch(
    model,
    corrupted_batch,
    clean_cache,
    spec,
    metric=lambda output: float(output["score"]),
)

Generic and component-level activation patching primitives.

PatchResult dataclass

Result of one patched forward run.

Source code in src/SafeLens/core/patching.py
132
133
134
135
136
137
138
139
@dataclass(frozen=True)
class PatchResult:
    """Result of one patched forward run."""

    spec: PatchSpec
    metric: float
    output: Any
    cache: dict[str, Any]

PatchSpec dataclass

Specification for one activation patch operation.

Source code in src/SafeLens/core/patching.py
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
@dataclass(frozen=True)
class PatchSpec:
    """Specification for one activation patch operation."""

    layer: LayerRef
    activation_name: str | None = None
    source_name: str | None = None
    target_index: Any = None
    source_index: Any = None
    mode: PatchMode = "replace"
    scale: float = 1.0
    value: Any = None
    setter: PatchSetter | None = None

    @property
    def target_name(self) -> str:
        """Activation name to patch in the corrupted run."""
        return self.activation_name or activation_name_for_layer(self.layer)

    @property
    def clean_name(self) -> str:
        """Activation name to read from the clean cache."""
        return self.source_name or self.target_name

clean_name property

Activation name to read from the clean cache.

target_name property

Activation name to patch in the corrupted run.

activation_name_for_component(component, layer, *, name_style='safelens', name_template=None)

Return a cache/hook name for a Transformer component at one layer.

Source code in src/SafeLens/core/patching.py
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
def activation_name_for_component(
    component: str,
    layer: LayerRef,
    *,
    name_style: ActivationNameStyle = "safelens",
    name_template: str | None = None,
) -> str:
    """Return a cache/hook name for a Transformer component at one layer."""
    explicit_ref = _explicit_activation_ref(component)
    if explicit_ref is not None and _same_layer_ref(layer, explicit_ref[0]):
        return component
    component = explicit_ref[1] if explicit_ref is not None else component
    layer_ref = _explicit_layer_ref(layer)
    if layer_ref is not None:
        layer_index, layer_component = layer_ref
        requested_component = _normalize_patch_component(component)
        if requested_component != layer_component:
            raise ValueError(
                f"Layer reference {layer!r} targets component {layer_component!r}, "
                f"but patch helper requested {requested_component!r}."
            )
        if isinstance(layer, str) and name_template is None:
            return layer
        layer = layer_index
        component = layer_component
    if name_template is not None:
        return name_template.format(layer=layer, component=component)
    if name_style == "transformer_lens":
        return transformer_lens_activation_name_for_component(component, layer)
    return f"{activation_name_for_layer(layer)}.{component}"

adapt_transformer_lens_patch_setter(patch_setter)

Adapt TL-style (activation, index, clean_activation) setters to PatchSpec setters.

Source code in src/SafeLens/core/patching.py
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
def adapt_transformer_lens_patch_setter(
    patch_setter: PatchSetter | TransformerLensPatchSetter,
) -> PatchSetter:
    """Adapt TL-style `(activation, index, clean_activation)` setters to PatchSpec setters."""
    call_style = infer_patch_setter_call_style(patch_setter)

    def setter(corrupted_activation: Any, spec: PatchSpec, clean_cache: ActivationCache) -> Any:
        clean_activation = spec.value if spec.value is not None else clean_cache[spec.clean_name]
        index = normalize_index(spec.target_index)
        if call_style == "safelens":
            safelens_patch_setter = cast(PatchSetter, patch_setter)
            return safelens_patch_setter(corrupted_activation, spec, clean_cache)
        if call_style == "transformer_lens":
            tl_patch_setter = cast(TransformerLensPatchSetter, patch_setter)
            return tl_patch_setter(
                clone_patch_target_if_requires_grad(corrupted_activation),
                list(index),
                clean_activation,
            )
        inferred_call_style = infer_patch_setter_bind_style(patch_setter)
        if inferred_call_style == "safelens":
            safelens_patch_setter = cast(PatchSetter, patch_setter)
            return safelens_patch_setter(corrupted_activation, spec, clean_cache)
        tl_patch_setter = cast(TransformerLensPatchSetter, patch_setter)
        return tl_patch_setter(
            clone_patch_target_if_requires_grad(corrupted_activation),
            list(index),
            clean_activation,
        )

    return setter

add_patch_setter(corrupted_activation, spec, clean_cache)

Add a scaled patch value to the whole activation or to a selected slice.

Source code in src/SafeLens/core/patching.py
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
def add_patch_setter(
    corrupted_activation: Any,
    spec: PatchSpec,
    clean_cache: ActivationCache,
) -> Any:
    """Add a scaled patch value to the whole activation or to a selected slice."""
    patch_value = scale_value(get_patch_value(spec, clean_cache), spec.scale)

    if spec.target_index is None:
        return add_values(corrupted_activation, patch_value)

    patched = clone_patch_target(corrupted_activation)
    current_value = get_indexed(patched, spec.target_index)
    set_indexed(patched, spec.target_index, add_values(current_value, patch_value))
    return patched

add_values(left, right)

Add tensor-like or nested Python sequence values elementwise.

Source code in src/SafeLens/core/patching.py
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
def add_values(left: Any, right: Any) -> Any:
    """Add tensor-like or nested Python sequence values elementwise."""
    right = coerce_value_like(left, right)
    if is_sequence(left) and is_sequence(right):
        return [
            add_values(left_item, right_item)
            for left_item, right_item in zip(left, right, strict=True)
        ]
    try:
        return left + right
    except TypeError:
        return right

apply_patch(corrupted_activation, spec, clean_cache)

Apply a patch spec to one corrupted activation.

Source code in src/SafeLens/core/patching.py
230
231
232
233
234
235
236
237
238
239
240
241
242
def apply_patch(
    corrupted_activation: Any,
    spec: PatchSpec,
    clean_cache: ActivationCache,
) -> Any:
    """Apply a patch spec to one corrupted activation."""
    if spec.setter is not None:
        return spec.setter(corrupted_activation, spec, clean_cache)
    if spec.mode == "replace":
        return replace_patch_setter(corrupted_activation, spec, clean_cache)
    if spec.mode == "add":
        return add_patch_setter(corrupted_activation, spec, clean_cache)
    raise ValueError(f"Unsupported patch mode: {spec.mode}")

batch_prefixed_slice(has_batch_dim, *indices)

Return a slice tuple with an optional leading batch axis.

Source code in src/SafeLens/core/patching.py
2015
2016
2017
2018
2019
def batch_prefixed_slice(has_batch_dim: bool, *indices: Any) -> tuple[Any, ...]:
    """Return a slice tuple with an optional leading batch axis."""
    if has_batch_dim:
        return (FULL_SLICE, *indices)
    return tuple(indices)

broadcast_patch_value_to_slice(corrupted_activation, target_slice, patch_value)

Broadcast a no-batch patch value across a batched target slice when needed.

Source code in src/SafeLens/core/patching.py
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
def broadcast_patch_value_to_slice(
    corrupted_activation: Any,
    target_slice: tuple[Any, ...],
    patch_value: Any,
) -> Any:
    """Broadcast a no-batch patch value across a batched target slice when needed."""
    target_shape = shape_of(get_indexed(corrupted_activation, target_slice))
    patch_shape = shape_of(patch_value)
    if target_shape == patch_shape or not target_shape:
        return patch_value
    if target_shape[1:] == patch_shape:
        return repeat_value_like(patch_value, target_shape[0])
    if patch_shape[1:] == target_shape and patch_shape[0] == 1:
        return get_indexed(patch_value, 0)
    return patch_value

clone_patch_target(value)

Clone a patch target and convert immutable nested sequences to lists.

Source code in src/SafeLens/core/patching.py
2377
2378
2379
def clone_patch_target(value: Any) -> Any:
    """Clone a patch target and convert immutable nested sequences to lists."""
    return mutable_patch_target(clone_activation(value))

clone_patch_target_if_requires_grad(value)

Clone gradient-tracked patch targets before TL-style in-place setters run.

Source code in src/SafeLens/core/patching.py
2382
2383
2384
2385
2386
def clone_patch_target_if_requires_grad(value: Any) -> Any:
    """Clone gradient-tracked patch targets before TL-style in-place setters run."""
    if getattr(value, "requires_grad", False):
        return clone_patch_target(value)
    return value

coerce_numpy_value_like(reference, value)

Return value as a numpy array matching reference, if applicable.

Source code in src/SafeLens/core/patching.py
2330
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
def coerce_numpy_value_like(reference: Any, value: Any) -> Any:
    """Return `value` as a numpy array matching `reference`, if applicable."""
    try:
        import numpy as np
    except ImportError:
        return value

    if not isinstance(reference, np.ndarray):
        return value
    if isinstance(value, np.ndarray):
        return value.astype(reference.dtype, copy=False)
    return np.asarray(tensor_to_numpy_source(value), dtype=reference.dtype)

coerce_torch_value_like(reference, value)

Return value as a torch tensor matching reference, if applicable.

Source code in src/SafeLens/core/patching.py
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
def coerce_torch_value_like(reference: Any, value: Any) -> Any:
    """Return `value` as a torch tensor matching `reference`, if applicable."""
    try:
        import torch
    except ImportError:
        return value

    if not isinstance(reference, torch.Tensor):
        return value
    if isinstance(value, torch.Tensor):
        if value.dtype == reference.dtype and value.device == reference.device:
            return value
        return value.to(dtype=reference.dtype, device=reference.device)
    return torch.as_tensor(value, dtype=reference.dtype, device=reference.device)

coerce_value_like(reference, value)

Coerce patch values to the target activation backend when possible.

Source code in src/SafeLens/core/patching.py
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
2310
2311
def coerce_value_like(reference: Any, value: Any) -> Any:
    """Coerce patch values to the target activation backend when possible."""
    if is_sequence(reference):
        return to_python_container(value)

    torch_value = coerce_torch_value_like(reference, value)
    if torch_value is not value:
        return torch_value

    numpy_value = coerce_numpy_value_like(reference, value)
    if numpy_value is not value:
        return numpy_value

    return value

component_activation_patch(model, corrupted_batch, clean_cache, metric, *, component, patch_setter, index_axis_names, activation_name=None, index_df=None, layers=None, positions=None, heads=None, dest_positions=None, source_positions=None, mode='replace', scale=1.0, name_style='safelens', name_template=None, cache_layers=None, return_details=True, return_metric_grid=False, return_index_table=False, return_index_df=False)

Run a component-level activation patch grid.

Source code in src/SafeLens/core/patching.py
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
def component_activation_patch(
    model: ModelWrapper,
    corrupted_batch: Batch,
    clean_cache: ActivationCache,
    metric: PatchMetric,
    *,
    component: str,
    patch_setter: PatchSetter | TransformerLensPatchSetter,
    index_axis_names: Sequence[AxisName] | None,
    activation_name: str | None = None,
    index_df: Any = None,
    layers: Iterable[LayerRef] | None = None,
    positions: Iterable[int] | None = None,
    heads: Iterable[int] | None = None,
    dest_positions: Iterable[int] | None = None,
    source_positions: Iterable[int] | None = None,
    mode: PatchMode = "replace",
    scale: float = 1.0,
    name_style: ActivationNameStyle = "safelens",
    name_template: str | None = None,
    cache_layers: Sequence[LayerRef] | None = None,
    return_details: bool = True,
    return_metric_grid: bool = False,
    return_index_table: bool = False,
    return_index_df: bool = False,
) -> Any:
    """Run a component-level activation patch grid."""
    patch_component = activation_name or component
    if index_df is not None:
        index_table, resolved_index_axis_names = normalize_index_table(
            index_df,
            index_axis_names,
        )
        specs = make_transformer_lens_patch_specs(
            patch_component,
            index_table,
            patch_setter=patch_setter,
        )
        results = generic_activation_patch(
            model,
            corrupted_batch,
            clean_cache,
            specs,
            metric,
            layers=cache_layers,
        )
        return format_patch_results(
            results,
            resolved_index_axis_names,
            return_details=return_details,
            return_metric_grid=return_metric_grid,
            return_index_table=return_index_table,
            return_index_df=return_index_df,
            flatten_metric_output=True,
        )

    if index_axis_names is None:
        raise TypeError("Pass `index_axis_names` when `index_df` is not supplied.")

    layer_values = list(
        layers
        if layers is not None
        else infer_layers(model, clean_cache, patch_component, name_style=name_style)
    )
    axis_values: dict[AxisName, Iterable[Any]] = {}
    if "pos" in index_axis_names:
        axis_values["pos"] = _values_or_range(
            positions,
            infer_positions(
                corrupted_batch,
                clean_cache,
                patch_component,
                layer_values,
                axis_name="pos",
            ),
            "positions",
        )
    if "head" in index_axis_names:
        axis_values["head"] = _values_or_range(
            heads,
            infer_heads(model, clean_cache, patch_component, layer_values),
            "heads",
        )
    if "head_index" in index_axis_names:
        axis_values["head_index"] = _values_or_range(
            heads,
            infer_heads(model, clean_cache, patch_component, layer_values),
            "heads",
        )
    if "dest_pos" in index_axis_names:
        axis_values["dest_pos"] = _values_or_range(
            dest_positions,
            infer_positions(
                corrupted_batch,
                clean_cache,
                patch_component,
                layer_values,
                axis_name="dest_pos",
            ),
            "dest_positions",
        )
    if "src_pos" in index_axis_names:
        axis_values["src_pos"] = _values_or_range(
            source_positions,
            infer_positions(
                corrupted_batch,
                clean_cache,
                patch_component,
                layer_values,
                axis_name="src_pos",
            ),
            "source_positions",
        )
    specs = make_component_patch_specs(
        layer_values,
        patch_component,
        index_axis_names,
        axis_values,
        patch_setter=patch_setter,
        mode=mode,
        scale=scale,
        name_style=name_style,
        name_template=name_template,
    )
    results = generic_activation_patch(
        model,
        corrupted_batch,
        clean_cache,
        specs,
        metric,
        layers=cache_layers,
    )
    return format_patch_results(
        results,
        index_axis_names,
        return_details=return_details,
        return_metric_grid=return_metric_grid,
        return_index_table=return_index_table,
        return_index_df=return_index_df,
    )

embed_positions_from_shape(shape)

Return sequence length from embedding shapes [pos, d_model] or [batch, pos, d_model].

Source code in src/SafeLens/core/patching.py
2672
2673
2674
2675
2676
def embed_positions_from_shape(shape: Sequence[int]) -> int | None:
    """Return sequence length from embedding shapes `[pos, d_model]` or `[batch, pos, d_model]`."""
    if len(shape) >= 2:
        return int(shape[-2])
    return None

expand_ellipsis_index(index, rank)

Expand a single ellipsis into full slices for dependency-free indexing.

Source code in src/SafeLens/core/patching.py
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
def expand_ellipsis_index(index: tuple[Any, ...], rank: int) -> tuple[Any, ...]:
    """Expand a single ellipsis into full slices for dependency-free indexing."""
    if Ellipsis not in index:
        return index
    if index.count(Ellipsis) > 1:
        raise IndexError("an index can only have a single ellipsis")
    consumed_dims = len([item for item in index if item is not None and item is not Ellipsis])
    fill = max(0, rank - consumed_dims)
    expanded: list[Any] = []
    for item in index:
        if item is Ellipsis:
            expanded.extend([FULL_SLICE] * fill)
        else:
            expanded.append(item)
    return tuple(expanded)

first_component_activation(clean_cache, component, layers)

Return the first activation matching a component and layer list.

Source code in src/SafeLens/core/patching.py
2812
2813
2814
2815
2816
2817
2818
2819
2820
2821
2822
2823
2824
2825
2826
2827
2828
2829
2830
2831
2832
2833
2834
2835
2836
2837
2838
2839
2840
2841
2842
2843
2844
2845
2846
2847
2848
2849
2850
2851
2852
2853
def first_component_activation(
    clean_cache: ActivationCache,
    component: str,
    layers: Sequence[LayerRef],
) -> Any:
    """Return the first activation matching a component and layer list."""
    layer_set = {_coerce_int_layer(layer) for layer in layers}
    layer_set.discard(None)
    if component in clean_cache:
        explicit_ref = _explicit_activation_ref(component)
        if explicit_ref is None or explicit_ref[0] in layer_set or not layer_set:
            return clean_cache[component]
        return None
    explicit_layer: int | None = None
    explicit_ref = _explicit_activation_ref(component)
    if explicit_ref is not None:
        explicit_layer = explicit_ref[0]
        component = explicit_ref[1]
        if layer_set and explicit_layer not in layer_set:
            return None
    candidate_names = [
        activation_name_for_component(component, layer, name_style="safelens") for layer in layers
    ]
    candidate_names.extend(
        activation_name_for_component(component, layer, name_style="transformer_lens")
        for layer in layers
    )
    for name in candidate_names:
        if name in clean_cache:
            return clean_cache[name]
    for name, activation in clean_cache.items():
        parsed = _layer_and_component_from_cache_name(name)
        if parsed is None:
            continue
        layer, cache_component = parsed
        if layer_set and layer not in layer_set:
            continue
        if explicit_layer is not None and layer != explicit_layer:
            continue
        if _normalize_patch_component(cache_component) == _normalize_patch_component(component):
            return activation
    return None

format_patch_results(results, index_axis_names=None, *, return_details=True, return_metric_grid=False, return_index_table=False, return_index_df=False, flatten_metric_output=False)

Format patch results as details or TL-style metric outputs.

Source code in src/SafeLens/core/patching.py
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
def format_patch_results(
    results: Sequence[PatchResult],
    index_axis_names: Sequence[AxisName] | None = None,
    *,
    return_details: bool = True,
    return_metric_grid: bool = False,
    return_index_table: bool = False,
    return_index_df: bool = False,
    flatten_metric_output: bool = False,
) -> Any:
    """Format patch results as details or TL-style metric outputs."""
    include_index = return_index_table or return_index_df
    if return_index_df:
        return_metric_grid = True
        return_details = False

    output = (
        patch_results_to_metric_grid(
            results,
            None if flatten_metric_output else index_axis_names,
        )
        if return_metric_grid or not return_details
        else list(results)
    )
    if include_index:
        if return_index_df:
            return output, patch_results_to_index_df(results, index_axis_names)
        return output, patch_results_to_index_table(results, index_axis_names)
    return output

generic_activation_patch(model, corrupted_batch, clean_cache, specs=None, metric=None, activation_name=None, index_axis_names=None, index_df=None, return_index_df=False, *, layers=None, patching_metric=None, patch_setter=None, return_details=None, return_metric_grid=False, return_index_table=False)

Run a sequence of activation patches, similar to TransformerLens' generic patcher.

Source code in src/SafeLens/core/patching.py
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
def generic_activation_patch(
    model: ModelWrapper,
    corrupted_batch: Any,
    clean_cache: ActivationCache,
    specs: PatchSpec | Iterable[PatchSpec] | PatchMetric | None = None,
    metric: PatchMetric | PatchSetter | TransformerLensPatchSetter | None = None,
    activation_name: str | None = None,
    index_axis_names: Sequence[AxisName] | None = None,
    index_df: Any = None,
    return_index_df: bool = False,
    *,
    layers: Sequence[LayerRef] | None = None,
    patching_metric: PatchMetric | None = None,
    patch_setter: PatchSetter | TransformerLensPatchSetter | None = None,
    return_details: bool | None = None,
    return_metric_grid: bool = False,
    return_index_table: bool = False,
) -> Any:
    """Run a sequence of activation patches, similar to TransformerLens' generic patcher."""
    transformer_lens_style = specs is None and (
        patching_metric is not None or patch_setter is not None or activation_name is not None
    )
    if specs is not None and callable(specs) and not _looks_like_patch_specs(specs):
        if metric is not None and callable(metric) and patch_setter is None:
            if patching_metric is not None:
                raise TypeError("Pass patching_metric either positionally or by keyword, not both.")
            patching_metric = specs
            patch_setter = cast(PatchSetter | TransformerLensPatchSetter, metric)
            specs = None
            metric = None
            transformer_lens_style = True
        else:
            if patching_metric is not None:
                raise TypeError("Pass patching_metric either positionally or by keyword, not both.")
            patching_metric = specs
            specs = None
            transformer_lens_style = True
    if metric is not None and callable(metric) and specs is None and patch_setter is None:
        patch_setter = cast(PatchSetter | TransformerLensPatchSetter, metric)
        metric = None
        transformer_lens_style = True

    metric_fn = cast(PatchMetric | None, metric) or patching_metric
    if metric_fn is None:
        raise TypeError("generic_activation_patch requires `metric` or `patching_metric`.")

    resolved_index_axis_names = index_axis_names
    if specs is None:
        if patch_setter is None or activation_name is None:
            raise TypeError(
                "TL-style generic_activation_patch requires `patch_setter` and "
                "`activation_name` when `specs` is not supplied."
            )
        flattened_output = index_df is not None
        if index_df is None:
            if index_axis_names is None:
                raise TypeError("Pass `index_axis_names` or `index_df` for TL-style patching.")
            index_df = infer_index_table(
                model,
                corrupted_batch,
                clean_cache,
                activation_name,
                index_axis_names,
                name_style="transformer_lens",
            )
        else:
            if index_axis_names is not None:
                raise ValueError("Pass either `index_axis_names` or explicit `index_df`, not both.")
            index_df, resolved_index_axis_names = normalize_index_table(
                index_df,
                index_axis_names,
            )
        specs = make_transformer_lens_patch_specs(
            activation_name,
            index_df,
            patch_setter=patch_setter,
        )
    elif patch_setter is not None or activation_name is not None or index_df is not None:
        raise TypeError(
            "Pass either SafeLens `specs` or TL-style `patch_setter`/`activation_name`, not both."
        )
    if isinstance(specs, PatchSpec):
        resolved_specs: Iterable[PatchSpec] = [specs]
    else:
        resolved_specs = cast(Iterable[PatchSpec], specs)

    if return_details is None:
        return_details = not transformer_lens_style
    if not return_details:
        return_metric_grid = True

    results = [
        run_activation_patch(
            model,
            corrupted_batch,
            clean_cache,
            spec,
            metric_fn,
            layers=layers,
        )
        for spec in resolved_specs
    ]
    return format_patch_results(
        results,
        resolved_index_axis_names,
        return_details=return_details,
        return_metric_grid=return_metric_grid,
        return_index_table=return_index_table,
        return_index_df=return_index_df,
        flatten_metric_output=locals().get("flattened_output", False),
    )

get_act_patch_attn_head_all_pos_every(model, corrupted_batch, clean_cache, metric=None, **kwargs)

Patch z, q, k, v, and pattern by layer and head.

Source code in src/SafeLens/core/patching.py
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
def get_act_patch_attn_head_all_pos_every(
    model: ModelWrapper,
    corrupted_batch: Batch,
    clean_cache: ActivationCache,
    metric: PatchMetric | None = None,
    **kwargs: Any,
) -> Any:
    """Patch `z`, `q`, `k`, `v`, and `pattern` by layer and head."""
    metric = _resolve_patching_metric(metric, kwargs, "get_act_patch_attn_head_all_pos_every")
    named_outputs = [
        (
            "z",
            get_act_patch_attn_head_out_all_pos(
                model,
                corrupted_batch,
                clean_cache,
                metric,
                **kwargs,
            ),
        ),
        (
            "q",
            get_act_patch_attn_head_q_all_pos(
                model, corrupted_batch, clean_cache, metric, **kwargs
            ),
        ),
        (
            "k",
            get_act_patch_attn_head_k_all_pos(
                model, corrupted_batch, clean_cache, metric, **kwargs
            ),
        ),
        (
            "v",
            get_act_patch_attn_head_v_all_pos(
                model, corrupted_batch, clean_cache, metric, **kwargs
            ),
        ),
        (
            "pattern",
            get_act_patch_attn_head_pattern_all_pos(
                model,
                corrupted_batch,
                clean_cache,
                metric,
                **kwargs,
            ),
        ),
    ]
    if _returns_details(kwargs):
        return dict(named_outputs)
    return _stack_named_metric_outputs(_pad_kv_metric_outputs(named_outputs))

get_act_patch_attn_head_by_pos_every(model, corrupted_batch, clean_cache, metric=None, **kwargs)

Patch z, q, k, v, and pattern by position where applicable.

Source code in src/SafeLens/core/patching.py
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
def get_act_patch_attn_head_by_pos_every(
    model: ModelWrapper,
    corrupted_batch: Batch,
    clean_cache: ActivationCache,
    metric: PatchMetric | None = None,
    **kwargs: Any,
) -> Any:
    """Patch `z`, `q`, `k`, `v`, and `pattern` by position where applicable."""
    metric = _resolve_patching_metric(metric, kwargs, "get_act_patch_attn_head_by_pos_every")
    pattern_output = get_act_patch_attn_head_pattern_by_pos(
        model,
        corrupted_batch,
        clean_cache,
        metric,
        **kwargs,
    )
    named_outputs = [
        (
            "z",
            get_act_patch_attn_head_out_by_pos(
                model,
                corrupted_batch,
                clean_cache,
                metric,
                **kwargs,
            ),
        ),
        (
            "q",
            get_act_patch_attn_head_q_by_pos(model, corrupted_batch, clean_cache, metric, **kwargs),
        ),
        (
            "k",
            get_act_patch_attn_head_k_by_pos(model, corrupted_batch, clean_cache, metric, **kwargs),
        ),
        (
            "v",
            get_act_patch_attn_head_v_by_pos(model, corrupted_batch, clean_cache, metric, **kwargs),
        ),
        (
            "pattern",
            pattern_output,
        ),
    ]
    if _returns_details(kwargs):
        return dict(named_outputs)
    named_outputs[-1] = ("pattern", _move_metric_axis(pattern_output, 1, 2))
    return _stack_named_metric_outputs(_pad_kv_metric_outputs(named_outputs))

get_act_patch_attn_head_k_all_pos(model, corrupted_batch, clean_cache, metric=None, **kwargs)

Patch attention keys across all positions.

Source code in src/SafeLens/core/patching.py
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
def get_act_patch_attn_head_k_all_pos(
    model: ModelWrapper,
    corrupted_batch: Batch,
    clean_cache: ActivationCache,
    metric: PatchMetric | None = None,
    **kwargs: Any,
) -> Any:
    """Patch attention keys across all positions."""
    return _head_vector_all_pos_patch(
        model,
        corrupted_batch,
        clean_cache,
        _resolve_patching_metric(metric, kwargs, "get_act_patch_attn_head_k_all_pos"),
        "k",
        _component_helper_kwargs(kwargs),
    )

get_act_patch_attn_head_k_by_pos(model, corrupted_batch, clean_cache, metric=None, **kwargs)

Patch attention keys by layer, position, and head.

Source code in src/SafeLens/core/patching.py
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
def get_act_patch_attn_head_k_by_pos(
    model: ModelWrapper,
    corrupted_batch: Batch,
    clean_cache: ActivationCache,
    metric: PatchMetric | None = None,
    **kwargs: Any,
) -> Any:
    """Patch attention keys by layer, position, and head."""
    return _head_vector_by_pos_patch(
        model,
        corrupted_batch,
        clean_cache,
        _resolve_patching_metric(metric, kwargs, "get_act_patch_attn_head_k_by_pos"),
        "k",
        _component_helper_kwargs(kwargs),
    )

get_act_patch_attn_head_out_all_pos(model, corrupted_batch, clean_cache, metric=None, **kwargs)

Patch attention head outputs z across all positions.

Source code in src/SafeLens/core/patching.py
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
def get_act_patch_attn_head_out_all_pos(
    model: ModelWrapper,
    corrupted_batch: Batch,
    clean_cache: ActivationCache,
    metric: PatchMetric | None = None,
    **kwargs: Any,
) -> Any:
    """Patch attention head outputs `z` across all positions."""
    return _head_vector_all_pos_patch(
        model,
        corrupted_batch,
        clean_cache,
        _resolve_patching_metric(metric, kwargs, "get_act_patch_attn_head_out_all_pos"),
        "z",
        _component_helper_kwargs(kwargs),
    )

get_act_patch_attn_head_out_by_pos(model, corrupted_batch, clean_cache, metric=None, **kwargs)

Patch attention head outputs z by layer, position, and head.

Source code in src/SafeLens/core/patching.py
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
def get_act_patch_attn_head_out_by_pos(
    model: ModelWrapper,
    corrupted_batch: Batch,
    clean_cache: ActivationCache,
    metric: PatchMetric | None = None,
    **kwargs: Any,
) -> Any:
    """Patch attention head outputs `z` by layer, position, and head."""
    return _head_vector_by_pos_patch(
        model,
        corrupted_batch,
        clean_cache,
        _resolve_patching_metric(metric, kwargs, "get_act_patch_attn_head_out_by_pos"),
        "z",
        _component_helper_kwargs(kwargs),
    )

get_act_patch_attn_head_pattern_all_pos(model, corrupted_batch, clean_cache, metric=None, **kwargs)

Patch full attention patterns by layer and head.

Source code in src/SafeLens/core/patching.py
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
def get_act_patch_attn_head_pattern_all_pos(
    model: ModelWrapper,
    corrupted_batch: Batch,
    clean_cache: ActivationCache,
    metric: PatchMetric | None = None,
    **kwargs: Any,
) -> Any:
    """Patch full attention patterns by layer and head."""
    return _head_pattern_patch(
        model,
        corrupted_batch,
        clean_cache,
        _resolve_patching_metric(metric, kwargs, "get_act_patch_attn_head_pattern_all_pos"),
        "pattern",
        _component_helper_kwargs(kwargs),
    )

get_act_patch_attn_head_pattern_by_pos(model, corrupted_batch, clean_cache, metric=None, **kwargs)

Patch attention patterns by layer, head, and destination position.

Source code in src/SafeLens/core/patching.py
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
def get_act_patch_attn_head_pattern_by_pos(
    model: ModelWrapper,
    corrupted_batch: Batch,
    clean_cache: ActivationCache,
    metric: PatchMetric | None = None,
    **kwargs: Any,
) -> Any:
    """Patch attention patterns by layer, head, and destination position."""
    return _head_pattern_by_pos_patch(
        model,
        corrupted_batch,
        clean_cache,
        _resolve_patching_metric(metric, kwargs, "get_act_patch_attn_head_pattern_by_pos"),
        "pattern",
        _component_helper_kwargs(kwargs),
    )

get_act_patch_attn_head_pattern_dest_src_pos(model, corrupted_batch, clean_cache, metric=None, **kwargs)

Patch attention patterns by layer, head, destination, and source position.

Source code in src/SafeLens/core/patching.py
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
def get_act_patch_attn_head_pattern_dest_src_pos(
    model: ModelWrapper,
    corrupted_batch: Batch,
    clean_cache: ActivationCache,
    metric: PatchMetric | None = None,
    **kwargs: Any,
) -> Any:
    """Patch attention patterns by layer, head, destination, and source position."""
    return _head_pattern_dest_src_patch(
        model,
        corrupted_batch,
        clean_cache,
        _resolve_patching_metric(metric, kwargs, "get_act_patch_attn_head_pattern_dest_src_pos"),
        "pattern",
        _component_helper_kwargs(kwargs),
    )

get_act_patch_attn_head_q_all_pos(model, corrupted_batch, clean_cache, metric=None, **kwargs)

Patch attention queries across all positions.

Source code in src/SafeLens/core/patching.py
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
def get_act_patch_attn_head_q_all_pos(
    model: ModelWrapper,
    corrupted_batch: Batch,
    clean_cache: ActivationCache,
    metric: PatchMetric | None = None,
    **kwargs: Any,
) -> Any:
    """Patch attention queries across all positions."""
    return _head_vector_all_pos_patch(
        model,
        corrupted_batch,
        clean_cache,
        _resolve_patching_metric(metric, kwargs, "get_act_patch_attn_head_q_all_pos"),
        "q",
        _component_helper_kwargs(kwargs),
    )

get_act_patch_attn_head_q_by_pos(model, corrupted_batch, clean_cache, metric=None, **kwargs)

Patch attention queries by layer, position, and head.

Source code in src/SafeLens/core/patching.py
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
def get_act_patch_attn_head_q_by_pos(
    model: ModelWrapper,
    corrupted_batch: Batch,
    clean_cache: ActivationCache,
    metric: PatchMetric | None = None,
    **kwargs: Any,
) -> Any:
    """Patch attention queries by layer, position, and head."""
    return _head_vector_by_pos_patch(
        model,
        corrupted_batch,
        clean_cache,
        _resolve_patching_metric(metric, kwargs, "get_act_patch_attn_head_q_by_pos"),
        "q",
        _component_helper_kwargs(kwargs),
    )

get_act_patch_attn_head_result_all_pos(model, corrupted_batch, clean_cache, metric=None, **kwargs)

Patch per-head attention result vectors across all positions.

Source code in src/SafeLens/core/patching.py
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
def get_act_patch_attn_head_result_all_pos(
    model: ModelWrapper,
    corrupted_batch: Batch,
    clean_cache: ActivationCache,
    metric: PatchMetric | None = None,
    **kwargs: Any,
) -> Any:
    """Patch per-head attention result vectors across all positions."""
    return _head_vector_all_pos_patch(
        model,
        corrupted_batch,
        clean_cache,
        _resolve_patching_metric(metric, kwargs, "get_act_patch_attn_head_result_all_pos"),
        "result",
        _component_helper_kwargs(kwargs),
    )

get_act_patch_attn_head_result_by_pos(model, corrupted_batch, clean_cache, metric=None, **kwargs)

Patch per-head attention result vectors by layer, position, and head.

Source code in src/SafeLens/core/patching.py
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
def get_act_patch_attn_head_result_by_pos(
    model: ModelWrapper,
    corrupted_batch: Batch,
    clean_cache: ActivationCache,
    metric: PatchMetric | None = None,
    **kwargs: Any,
) -> Any:
    """Patch per-head attention result vectors by layer, position, and head."""
    return _head_vector_by_pos_patch(
        model,
        corrupted_batch,
        clean_cache,
        _resolve_patching_metric(metric, kwargs, "get_act_patch_attn_head_result_by_pos"),
        "result",
        _component_helper_kwargs(kwargs),
    )

get_act_patch_attn_head_v_all_pos(model, corrupted_batch, clean_cache, metric=None, **kwargs)

Patch attention values across all positions.

Source code in src/SafeLens/core/patching.py
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
def get_act_patch_attn_head_v_all_pos(
    model: ModelWrapper,
    corrupted_batch: Batch,
    clean_cache: ActivationCache,
    metric: PatchMetric | None = None,
    **kwargs: Any,
) -> Any:
    """Patch attention values across all positions."""
    return _head_vector_all_pos_patch(
        model,
        corrupted_batch,
        clean_cache,
        _resolve_patching_metric(metric, kwargs, "get_act_patch_attn_head_v_all_pos"),
        "v",
        _component_helper_kwargs(kwargs),
    )

get_act_patch_attn_head_v_by_pos(model, corrupted_batch, clean_cache, metric=None, **kwargs)

Patch attention values by layer, position, and head.

Source code in src/SafeLens/core/patching.py
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
def get_act_patch_attn_head_v_by_pos(
    model: ModelWrapper,
    corrupted_batch: Batch,
    clean_cache: ActivationCache,
    metric: PatchMetric | None = None,
    **kwargs: Any,
) -> Any:
    """Patch attention values by layer, position, and head."""
    return _head_vector_by_pos_patch(
        model,
        corrupted_batch,
        clean_cache,
        _resolve_patching_metric(metric, kwargs, "get_act_patch_attn_head_v_by_pos"),
        "v",
        _component_helper_kwargs(kwargs),
    )

get_act_patch_attn_out(model, corrupted_batch, clean_cache, metric=None, **kwargs)

Patch attention layer outputs by position.

Source code in src/SafeLens/core/patching.py
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
def get_act_patch_attn_out(
    model: ModelWrapper,
    corrupted_batch: Batch,
    clean_cache: ActivationCache,
    metric: PatchMetric | None = None,
    **kwargs: Any,
) -> Any:
    """Patch attention layer outputs by position."""
    return _layer_pos_component_patch(
        model,
        corrupted_batch,
        clean_cache,
        _resolve_patching_metric(metric, kwargs, "get_act_patch_attn_out"),
        "attn_out",
        _component_helper_kwargs(kwargs),
    )

get_act_patch_attn_scores_all_pos(model, corrupted_batch, clean_cache, metric=None, **kwargs)

Patch raw attention scores by layer and head.

Source code in src/SafeLens/core/patching.py
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
def get_act_patch_attn_scores_all_pos(
    model: ModelWrapper,
    corrupted_batch: Batch,
    clean_cache: ActivationCache,
    metric: PatchMetric | None = None,
    **kwargs: Any,
) -> Any:
    """Patch raw attention scores by layer and head."""
    return _head_pattern_patch(
        model,
        corrupted_batch,
        clean_cache,
        _resolve_patching_metric(metric, kwargs, "get_act_patch_attn_scores_all_pos"),
        "attn_scores",
        _component_helper_kwargs(kwargs),
    )

get_act_patch_attn_scores_by_pos(model, corrupted_batch, clean_cache, metric=None, **kwargs)

Patch raw attention scores by layer, head, and destination position.

Source code in src/SafeLens/core/patching.py
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
def get_act_patch_attn_scores_by_pos(
    model: ModelWrapper,
    corrupted_batch: Batch,
    clean_cache: ActivationCache,
    metric: PatchMetric | None = None,
    **kwargs: Any,
) -> Any:
    """Patch raw attention scores by layer, head, and destination position."""
    return _head_pattern_by_pos_patch(
        model,
        corrupted_batch,
        clean_cache,
        _resolve_patching_metric(metric, kwargs, "get_act_patch_attn_scores_by_pos"),
        "attn_scores",
        _component_helper_kwargs(kwargs),
    )

get_act_patch_attn_scores_dest_src_pos(model, corrupted_batch, clean_cache, metric=None, **kwargs)

Patch raw attention scores by layer, head, destination, and source position.

Source code in src/SafeLens/core/patching.py
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
def get_act_patch_attn_scores_dest_src_pos(
    model: ModelWrapper,
    corrupted_batch: Batch,
    clean_cache: ActivationCache,
    metric: PatchMetric | None = None,
    **kwargs: Any,
) -> Any:
    """Patch raw attention scores by layer, head, destination, and source position."""
    return _head_pattern_dest_src_patch(
        model,
        corrupted_batch,
        clean_cache,
        _resolve_patching_metric(metric, kwargs, "get_act_patch_attn_scores_dest_src_pos"),
        "attn_scores",
        _component_helper_kwargs(kwargs),
    )

get_act_patch_block_every(model, corrupted_batch, clean_cache, metric=None, **kwargs)

Patch residual pre, attention output, and MLP output by layer and position.

Source code in src/SafeLens/core/patching.py
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
def get_act_patch_block_every(
    model: ModelWrapper,
    corrupted_batch: Batch,
    clean_cache: ActivationCache,
    metric: PatchMetric | None = None,
    **kwargs: Any,
) -> Any:
    """Patch residual pre, attention output, and MLP output by layer and position."""
    metric = _resolve_patching_metric(metric, kwargs, "get_act_patch_block_every")
    named_outputs = [
        (
            "resid_pre",
            get_act_patch_resid_pre(model, corrupted_batch, clean_cache, metric, **kwargs),
        ),
        (
            "attn_out",
            get_act_patch_attn_out(model, corrupted_batch, clean_cache, metric, **kwargs),
        ),
        ("mlp_out", get_act_patch_mlp_out(model, corrupted_batch, clean_cache, metric, **kwargs)),
    ]
    if _returns_details(kwargs):
        return dict(named_outputs)
    return _stack_named_metric_outputs(named_outputs)

get_act_patch_mlp_out(model, corrupted_batch, clean_cache, metric=None, **kwargs)

Patch MLP layer outputs by position.

Source code in src/SafeLens/core/patching.py
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
def get_act_patch_mlp_out(
    model: ModelWrapper,
    corrupted_batch: Batch,
    clean_cache: ActivationCache,
    metric: PatchMetric | None = None,
    **kwargs: Any,
) -> Any:
    """Patch MLP layer outputs by position."""
    return _layer_pos_component_patch(
        model,
        corrupted_batch,
        clean_cache,
        _resolve_patching_metric(metric, kwargs, "get_act_patch_mlp_out"),
        "mlp_out",
        _component_helper_kwargs(kwargs),
    )

get_act_patch_resid_mid(model, corrupted_batch, clean_cache, metric=None, **kwargs)

Patch residual stream activations between attention and MLP by position.

Source code in src/SafeLens/core/patching.py
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
def get_act_patch_resid_mid(
    model: ModelWrapper,
    corrupted_batch: Batch,
    clean_cache: ActivationCache,
    metric: PatchMetric | None = None,
    **kwargs: Any,
) -> Any:
    """Patch residual stream activations between attention and MLP by position."""
    return _layer_pos_component_patch(
        model,
        corrupted_batch,
        clean_cache,
        _resolve_patching_metric(metric, kwargs, "get_act_patch_resid_mid"),
        "resid_mid",
        _component_helper_kwargs(kwargs),
    )

get_act_patch_resid_post(model, corrupted_batch, clean_cache, metric=None, **kwargs)

Patch residual stream activations at the end of each block by position.

Source code in src/SafeLens/core/patching.py
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
def get_act_patch_resid_post(
    model: ModelWrapper,
    corrupted_batch: Batch,
    clean_cache: ActivationCache,
    metric: PatchMetric | None = None,
    **kwargs: Any,
) -> Any:
    """Patch residual stream activations at the end of each block by position."""
    return _layer_pos_component_patch(
        model,
        corrupted_batch,
        clean_cache,
        _resolve_patching_metric(metric, kwargs, "get_act_patch_resid_post"),
        "resid_post",
        _component_helper_kwargs(kwargs),
    )

get_act_patch_resid_pre(model, corrupted_batch, clean_cache, metric=None, **kwargs)

Patch residual stream activations at the start of each block by position.

Source code in src/SafeLens/core/patching.py
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
def get_act_patch_resid_pre(
    model: ModelWrapper,
    corrupted_batch: Batch,
    clean_cache: ActivationCache,
    metric: PatchMetric | None = None,
    **kwargs: Any,
) -> Any:
    """Patch residual stream activations at the start of each block by position."""
    return _layer_pos_component_patch(
        model,
        corrupted_batch,
        clean_cache,
        _resolve_patching_metric(metric, kwargs, "get_act_patch_resid_pre"),
        "resid_pre",
        _component_helper_kwargs(kwargs),
    )

get_config_int(model, names)

Read an integer config value from wrapper/model cfg/config objects.

Source code in src/SafeLens/core/patching.py
2875
2876
2877
2878
2879
2880
2881
2882
2883
2884
2885
2886
2887
2888
2889
2890
2891
2892
2893
2894
def get_config_int(model: Any, names: Sequence[str]) -> int | None:
    """Read an integer config value from wrapper/model cfg/config objects."""
    owners = [model, getattr(model, "cfg", None), getattr(model, "config", None)]
    wrapped_model = getattr(model, "model", None)
    if wrapped_model is not None:
        owners.extend(
            [
                wrapped_model,
                getattr(wrapped_model, "cfg", None),
                getattr(wrapped_model, "config", None),
            ]
        )
    for owner in _expand_config_owners(owners):
        if owner is None:
            continue
        for name in names:
            value = _config_value(owner, name)
            if value is not None:
                return int(value)
    return None

get_indexed(value, index)

Index tensor-like or nested-list values.

Source code in src/SafeLens/core/patching.py
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
def get_indexed(value: Any, index: Any) -> Any:
    """Index tensor-like or nested-list values."""
    normalized = expand_ellipsis_index(normalize_index(index), len(shape_of(value)))
    if len(normalized) == 1:
        try:
            return value[normalized[0]]
        except (TypeError, IndexError, KeyError):
            pass
    try:
        return value[normalized]
    except (TypeError, IndexError, KeyError):
        return get_nested(value, normalized)

get_model_key_value_heads(model)

Read K/V head count from SafeLens wrappers or raw Transformers models.

Source code in src/SafeLens/core/patching.py
2922
2923
2924
2925
2926
2927
2928
2929
2930
2931
2932
2933
2934
2935
2936
2937
2938
2939
2940
def get_model_key_value_heads(model: Any) -> int | None:
    """Read K/V head count from SafeLens wrappers or raw Transformers models."""
    cfg = getattr(model, "cfg", None)
    cfg_n_kv_heads = getattr(cfg, "n_key_value_heads", None)
    if cfg_n_kv_heads is not None:
        return int(cfg_n_kv_heads)
    try:
        from SafeLens.utils.model_bridge import key_value_head_count

        wrapped_model = getattr(model, "model", None)
        for candidate in (wrapped_model, model):
            if candidate is None:
                continue
            n_key_value_heads = key_value_head_count(candidate)
            if n_key_value_heads is not None:
                return n_key_value_heads
    except ImportError:
        pass
    return get_config_int(model, ("n_key_value_heads", "num_key_value_heads", "num_kv_heads"))

get_nested(value, index)

Index nested Python containers with full slices and integer indices.

Source code in src/SafeLens/core/patching.py
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
def get_nested(value: Any, index: tuple[Any, ...]) -> Any:
    """Index nested Python containers with full slices and integer indices."""
    if not index:
        return value
    head = index[0]
    tail = index[1:]
    if isinstance(head, slice):
        if head != FULL_SLICE:
            return [get_nested(item, tail) for item in value[head]]
        return [get_nested(item, tail) for item in value]
    return get_nested(value[head], tail)

get_patch_value(spec, clean_cache)

Read the patch value from an explicit value or the clean activation cache.

Source code in src/SafeLens/core/patching.py
189
190
191
192
193
194
195
def get_patch_value(spec: PatchSpec, clean_cache: ActivationCache) -> Any:
    """Read the patch value from an explicit value or the clean activation cache."""
    source = spec.value if spec.value is not None else clean_cache[spec.clean_name]
    source_index = spec.source_index if spec.source_index is not None else spec.target_index
    if source_index is None:
        return source
    return get_indexed(source, source_index)

infer_heads(model, clean_cache, component, layers)

Infer number of heads from model config or cached activations.

Source code in src/SafeLens/core/patching.py
2690
2691
2692
2693
2694
2695
2696
2697
2698
2699
2700
2701
2702
2703
2704
2705
2706
2707
2708
2709
2710
2711
2712
2713
2714
2715
2716
def infer_heads(
    model: ModelWrapper,
    clean_cache: ActivationCache,
    component: str,
    layers: Sequence[LayerRef],
) -> int:
    """Infer number of heads from model config or cached activations."""
    activation = first_component_activation(clean_cache, component, layers)
    normalized_component = _normalize_patch_component(component)
    if activation is not None:
        shape = shape_of(activation)
        has_batch_dim = getattr(clean_cache, "has_batch_dim", True)
        if normalized_component in PATTERN_COMPONENTS and len(shape) >= (2 if has_batch_dim else 1):
            return shape[1 if has_batch_dim else 0]
        if len(shape) >= (3 if has_batch_dim else 2):
            return shape[2 if has_batch_dim else 1]

    if normalized_component in {"k", "v", "decoder_k", "decoder_v", "cross_k", "cross_v"}:
        n_key_value_heads = get_model_key_value_heads(model)
        if n_key_value_heads is not None:
            return n_key_value_heads

    n_heads = get_config_int(model, ("n_heads", "num_attention_heads"))
    if n_heads is not None:
        return n_heads

    raise ValueError("Could not infer heads. Pass `heads=[...]` explicitly.")

infer_index_table(model, corrupted_batch, clean_cache, activation_name, index_axis_names, *, name_style='safelens')

Infer a TL-style patch index table from axis names.

Source code in src/SafeLens/core/patching.py
2719
2720
2721
2722
2723
2724
2725
2726
2727
2728
2729
2730
2731
2732
2733
2734
2735
2736
2737
2738
2739
2740
2741
2742
2743
2744
2745
2746
2747
2748
2749
2750
2751
2752
2753
2754
2755
2756
2757
2758
2759
2760
2761
2762
2763
2764
2765
def infer_index_table(
    model: ModelWrapper,
    corrupted_batch: Batch,
    clean_cache: ActivationCache,
    activation_name: str,
    index_axis_names: Sequence[AxisName],
    *,
    name_style: ActivationNameStyle = "safelens",
) -> list[dict[str, Any]]:
    """Infer a TL-style patch index table from axis names."""
    layers = infer_layers(model, clean_cache, activation_name, name_style=name_style)
    axis_values: dict[AxisName, Iterable[Any]] = {"layer": layers}
    if "pos" in index_axis_names:
        axis_values["pos"] = range(
            infer_positions(
                corrupted_batch,
                clean_cache,
                activation_name,
                layers,
                axis_name="pos",
            )
        )
    if "head" in index_axis_names:
        axis_values["head"] = range(infer_heads(model, clean_cache, activation_name, layers))
    if "head_index" in index_axis_names:
        axis_values["head_index"] = range(infer_heads(model, clean_cache, activation_name, layers))
    if "dest_pos" in index_axis_names:
        axis_values["dest_pos"] = range(
            infer_positions(
                corrupted_batch,
                clean_cache,
                activation_name,
                layers,
                axis_name="dest_pos",
            )
        )
    if "src_pos" in index_axis_names:
        axis_values["src_pos"] = range(
            infer_positions(
                corrupted_batch,
                clean_cache,
                activation_name,
                layers,
                axis_name="src_pos",
            )
        )
    return make_index_table(index_axis_names, axis_values)

infer_layers(model, clean_cache, component, *, name_style='safelens')

Infer layer indices from model config or clean cache names.

Source code in src/SafeLens/core/patching.py
2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417
def infer_layers(
    model: Any,
    clean_cache: ActivationCache,
    component: str,
    *,
    name_style: ActivationNameStyle = "safelens",
) -> list[LayerRef]:
    """Infer layer indices from model config or clean cache names."""
    layers_from_cache = infer_layers_from_cache(clean_cache, component, name_style=name_style)
    if layers_from_cache:
        return layers_from_cache

    if _component_uses_decoder_layer_count(component):
        n_decoder_layers = get_config_int(
            model,
            ("n_decoder_layers", "num_decoder_layers", "decoder_layers"),
        )
        if n_decoder_layers is not None:
            return list(range(n_decoder_layers))

    n_layers = get_config_int(model, ("n_layers", "num_hidden_layers", "num_layers"))
    if n_layers is not None:
        return list(range(n_layers))

    raise ValueError("Could not infer layers. Pass `layers=[...]` explicitly.")

infer_layers_from_cache(clean_cache, component, *, name_style='safelens')

Infer layer indices from SafeLens or TransformerLens-style activation names.

Source code in src/SafeLens/core/patching.py
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
def infer_layers_from_cache(
    clean_cache: ActivationCache,
    component: str,
    *,
    name_style: ActivationNameStyle = "safelens",
) -> list[LayerRef]:
    """Infer layer indices from SafeLens or TransformerLens-style activation names."""
    _ = name_style
    explicit_ref = _explicit_activation_ref(component)
    if explicit_ref is not None:
        explicit_layer, explicit_component = explicit_ref
        if component in clean_cache:
            return [explicit_layer]
        component = explicit_component
    layers: set[int] = set()
    for name in clean_cache:
        parsed = _layer_and_component_from_cache_name(name)
        if parsed is None:
            continue
        layer, cache_component = parsed
        if _normalize_patch_component(cache_component) == _normalize_patch_component(component):
            layers.add(layer)
    return sorted(layers)

infer_patch_setter_bind_style(patch_setter)

Infer an ambiguous patch setter style without executing user code.

Source code in src/SafeLens/core/patching.py
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
def infer_patch_setter_bind_style(
    patch_setter: PatchSetter | TransformerLensPatchSetter,
) -> Literal["safelens", "transformer_lens"]:
    """Infer an ambiguous patch setter style without executing user code."""
    try:
        setter_signature = signature(patch_setter)
    except (TypeError, ValueError):
        return "transformer_lens"

    try:
        setter_signature.bind(None, (), None)
    except TypeError:
        tl_binds = False
    else:
        tl_binds = True
    try:
        setter_signature.bind(None, _BIND_STYLE_PATCH_SPEC_SENTINEL, ActivationCache())
    except TypeError:
        safelens_binds = False
    else:
        safelens_binds = True

    if not tl_binds and safelens_binds:
        return "safelens"
    return "transformer_lens"

infer_patch_setter_call_style(patch_setter)

Infer patch-setter calling convention from common parameter names.

Source code in src/SafeLens/core/patching.py
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
def infer_patch_setter_call_style(
    patch_setter: PatchSetter | TransformerLensPatchSetter,
) -> Literal["safelens", "transformer_lens"] | None:
    """Infer patch-setter calling convention from common parameter names."""
    try:
        parameters = list(signature(patch_setter).parameters.values())
    except (TypeError, ValueError):
        return None

    positional_parameters = [
        parameter
        for parameter in parameters
        if parameter.kind
        in (
            Parameter.POSITIONAL_ONLY,
            Parameter.POSITIONAL_OR_KEYWORD,
            Parameter.KEYWORD_ONLY,
        )
    ]
    names = [parameter.name for parameter in positional_parameters]
    second_name = names[1] if len(names) > 1 else ""
    third_name = names[2] if len(names) > 2 else ""

    if second_name in {"spec", "patch_spec"} or third_name in {"clean_cache", "cache"}:
        return "safelens"
    if second_name in {"index", "indices", "patch_index"} or third_name in {
        "clean_activation",
        "clean_act",
        "clean_value",
    }:
        return "transformer_lens"
    return None

infer_positions(corrupted_batch, clean_cache, component, layers, *, axis_name='pos')

Infer sequence length from batch tensors or cached activations.

Source code in src/SafeLens/core/patching.py
2603
2604
2605
2606
2607
2608
2609
2610
2611
2612
2613
2614
2615
2616
2617
2618
2619
2620
2621
2622
2623
2624
2625
2626
2627
2628
def infer_positions(
    corrupted_batch: Any,
    clean_cache: ActivationCache,
    component: str,
    layers: Sequence[LayerRef],
    *,
    axis_name: AxisName = "pos",
) -> int:
    """Infer sequence length from batch tensors or cached activations."""
    activation = first_component_activation(clean_cache, component, layers)
    normalized_component = _normalize_patch_component(component)
    if activation is not None:
        shape = shape_of(activation)
        has_batch_dim = getattr(clean_cache, "has_batch_dim", True)
        if normalized_component in PATTERN_COMPONENTS and len(shape) >= (4 if has_batch_dim else 3):
            if axis_name == "src_pos":
                return int(shape[-1])
            return int(shape[-2])
        if len(shape) >= (2 if has_batch_dim else 1):
            return int(shape[1 if has_batch_dim else 0])

    batch_positions = infer_positions_from_batch(corrupted_batch)
    if batch_positions is not None:
        return batch_positions

    raise ValueError("Could not infer positions. Pass `positions=[...]` explicitly.")

infer_positions_from_batch(batch)

Infer token positions from tokenized or embedded batch inputs.

Source code in src/SafeLens/core/patching.py
2631
2632
2633
2634
2635
2636
2637
2638
2639
2640
2641
2642
2643
2644
2645
2646
2647
2648
def infer_positions_from_batch(batch: Any) -> int | None:
    """Infer token positions from tokenized or embedded batch inputs."""
    if isinstance(batch, Mapping):
        for key in ("input_ids", "tokens", "token_ids"):
            if key in batch:
                positions = token_positions_from_value(batch[key])
                if positions is not None:
                    return positions
        for key in ("inputs_embeds", "input_embeds", "embeds"):
            if key in batch:
                positions = embed_positions_from_shape(shape_of(batch[key]))
                if positions is not None:
                    return positions
        return None

    if is_text_batch(batch):
        return None
    return token_positions_from_value(batch)

is_text_batch(value)

Return whether a value is raw text rather than token ids.

Source code in src/SafeLens/core/patching.py
2679
2680
2681
2682
2683
2684
2685
2686
2687
def is_text_batch(value: Any) -> bool:
    """Return whether a value is raw text rather than token ids."""
    if isinstance(value, str | bytes):
        return True
    if isinstance(value, Sequence) and not isinstance(value, str | bytes):
        if not value:
            return False
        return isinstance(value[0], str | bytes)
    return False

layer_head_dest_src_pos_pattern_patch_setter(corrupted_activation, spec, clean_cache)

Patch one (destination, source) entry in an attention pattern.

Source code in src/SafeLens/core/patching.py
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
def layer_head_dest_src_pos_pattern_patch_setter(
    corrupted_activation: Any,
    spec: PatchSpec | Sequence[int],
    clean_cache: ActivationCache | Any,
) -> Any:
    """Patch one `(destination, source)` entry in an attention pattern."""
    tl_output = maybe_apply_transformer_lens_patch_setter(
        corrupted_activation,
        spec,
        clean_cache,
        expected_length=4,
        setter_name="layer_head_dest_src_pos_pattern_patch_setter",
        min_batched_rank=4,
        target_slice_fn=lambda index: (FULL_SLICE, index[1], index[2], index[3]),
        source_slice_fn=lambda index: (FULL_SLICE, index[1], index[2], index[3]),
    )
    if tl_output is not None:
        return tl_output
    spec = _require_patch_spec(spec, "layer_head_dest_src_pos_pattern_patch_setter")
    index = require_patch_index(
        spec,
        4,
        "layer_head_dest_src_pos_pattern_patch_setter",
    )
    source_index = source_index_or_target(spec, index)
    target_has_batch = patch_target_has_batch_dim(corrupted_activation, spec, clean_cache)
    source_has_batch = patch_source_has_batch_dim(corrupted_activation, spec, clean_cache)
    return patch_slice(
        corrupted_activation,
        spec,
        clean_cache,
        target_slice=batch_prefixed_slice(target_has_batch, index[1], index[2], index[3]),
        source_slice=batch_prefixed_slice(
            source_has_batch,
            source_index[1],
            source_index[2],
            source_index[3],
        ),
    )

layer_head_pattern_patch_setter(corrupted_activation, spec, clean_cache)

Patch an attention pattern head shaped [batch, head, dest_pos, src_pos].

Source code in src/SafeLens/core/patching.py
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
def layer_head_pattern_patch_setter(
    corrupted_activation: Any,
    spec: PatchSpec | Sequence[int],
    clean_cache: ActivationCache | Any,
) -> Any:
    """Patch an attention pattern head shaped `[batch, head, dest_pos, src_pos]`."""
    tl_output = maybe_apply_transformer_lens_patch_setter(
        corrupted_activation,
        spec,
        clean_cache,
        expected_length=2,
        setter_name="layer_head_pattern_patch_setter",
        min_batched_rank=4,
        target_slice_fn=lambda index: (FULL_SLICE, index[1], FULL_SLICE, FULL_SLICE),
        source_slice_fn=lambda index: (FULL_SLICE, index[1], FULL_SLICE, FULL_SLICE),
    )
    if tl_output is not None:
        return tl_output
    spec = _require_patch_spec(spec, "layer_head_pattern_patch_setter")
    index = require_patch_index(spec, 2, "layer_head_pattern_patch_setter")
    source_index = source_index_or_target(spec, index)
    target_has_batch = patch_target_has_batch_dim(corrupted_activation, spec, clean_cache)
    source_has_batch = patch_source_has_batch_dim(corrupted_activation, spec, clean_cache)
    return patch_slice(
        corrupted_activation,
        spec,
        clean_cache,
        target_slice=batch_prefixed_slice(target_has_batch, index[1], FULL_SLICE, FULL_SLICE),
        source_slice=batch_prefixed_slice(
            source_has_batch,
            source_index[1],
            FULL_SLICE,
            FULL_SLICE,
        ),
    )

layer_head_pos_pattern_patch_setter(corrupted_activation, spec, clean_cache)

Patch one destination position in an attention pattern.

Source code in src/SafeLens/core/patching.py
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
def layer_head_pos_pattern_patch_setter(
    corrupted_activation: Any,
    spec: PatchSpec | Sequence[int],
    clean_cache: ActivationCache | Any,
) -> Any:
    """Patch one destination position in an attention pattern."""
    tl_output = maybe_apply_transformer_lens_patch_setter(
        corrupted_activation,
        spec,
        clean_cache,
        expected_length=3,
        setter_name="layer_head_pos_pattern_patch_setter",
        min_batched_rank=4,
        target_slice_fn=lambda index: (FULL_SLICE, index[1], index[2], FULL_SLICE),
        source_slice_fn=lambda index: (FULL_SLICE, index[1], index[2], FULL_SLICE),
    )
    if tl_output is not None:
        return tl_output
    spec = _require_patch_spec(spec, "layer_head_pos_pattern_patch_setter")
    index = require_patch_index(spec, 3, "layer_head_pos_pattern_patch_setter")
    source_index = source_index_or_target(spec, index)
    target_has_batch = patch_target_has_batch_dim(corrupted_activation, spec, clean_cache)
    source_has_batch = patch_source_has_batch_dim(corrupted_activation, spec, clean_cache)
    return patch_slice(
        corrupted_activation,
        spec,
        clean_cache,
        target_slice=batch_prefixed_slice(target_has_batch, index[1], index[2], FULL_SLICE),
        source_slice=batch_prefixed_slice(
            source_has_batch,
            source_index[1],
            source_index[2],
            FULL_SLICE,
        ),
    )

layer_head_vector_patch_setter(corrupted_activation, spec, clean_cache)

Patch a head vector across all positions for [batch, pos, head, ...].

Source code in src/SafeLens/core/patching.py
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
def layer_head_vector_patch_setter(
    corrupted_activation: Any,
    spec: PatchSpec | Sequence[int],
    clean_cache: ActivationCache | Any,
) -> Any:
    """Patch a head vector across all positions for `[batch, pos, head, ...]`."""
    tl_output = maybe_apply_transformer_lens_patch_setter(
        corrupted_activation,
        spec,
        clean_cache,
        expected_length=2,
        setter_name="layer_head_vector_patch_setter",
        min_batched_rank=4,
        target_slice_fn=lambda index: (FULL_SLICE, FULL_SLICE, index[1]),
        source_slice_fn=lambda index: (FULL_SLICE, FULL_SLICE, index[1]),
    )
    if tl_output is not None:
        return tl_output
    spec = _require_patch_spec(spec, "layer_head_vector_patch_setter")
    index = require_patch_index(spec, 2, "layer_head_vector_patch_setter")
    source_index = source_index_or_target(spec, index)
    target_has_batch = patch_target_has_batch_dim(corrupted_activation, spec, clean_cache)
    source_has_batch = patch_source_has_batch_dim(corrupted_activation, spec, clean_cache)
    return patch_slice(
        corrupted_activation,
        spec,
        clean_cache,
        target_slice=batch_prefixed_slice(target_has_batch, FULL_SLICE, index[1]),
        source_slice=batch_prefixed_slice(source_has_batch, FULL_SLICE, source_index[1]),
    )

layer_pos_head_vector_patch_setter(corrupted_activation, spec, clean_cache)

Patch head vector activations shaped [batch, pos, head, ...].

Source code in src/SafeLens/core/patching.py
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
def layer_pos_head_vector_patch_setter(
    corrupted_activation: Any,
    spec: PatchSpec | Sequence[int],
    clean_cache: ActivationCache | Any,
) -> Any:
    """Patch head vector activations shaped `[batch, pos, head, ...]`."""
    tl_output = maybe_apply_transformer_lens_patch_setter(
        corrupted_activation,
        spec,
        clean_cache,
        expected_length=3,
        setter_name="layer_pos_head_vector_patch_setter",
        min_batched_rank=4,
        target_slice_fn=lambda index: (FULL_SLICE, index[1], index[2]),
        source_slice_fn=lambda index: (FULL_SLICE, index[1], index[2]),
    )
    if tl_output is not None:
        return tl_output
    spec = _require_patch_spec(spec, "layer_pos_head_vector_patch_setter")
    index = require_patch_index(spec, 3, "layer_pos_head_vector_patch_setter")
    source_index = source_index_or_target(spec, index)
    target_has_batch = patch_target_has_batch_dim(corrupted_activation, spec, clean_cache)
    source_has_batch = patch_source_has_batch_dim(corrupted_activation, spec, clean_cache)
    return patch_slice(
        corrupted_activation,
        spec,
        clean_cache,
        target_slice=batch_prefixed_slice(target_has_batch, index[1], index[2]),
        source_slice=batch_prefixed_slice(source_has_batch, source_index[1], source_index[2]),
    )

layer_pos_patch_setter(corrupted_activation, spec, clean_cache)

Patch activations shaped [batch, pos, ...] at one layer and position.

Source code in src/SafeLens/core/patching.py
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
def layer_pos_patch_setter(
    corrupted_activation: Any,
    spec: PatchSpec | Sequence[int],
    clean_cache: ActivationCache | Any,
) -> Any:
    """Patch activations shaped `[batch, pos, ...]` at one layer and position."""
    tl_output = maybe_apply_transformer_lens_patch_setter(
        corrupted_activation,
        spec,
        clean_cache,
        expected_length=2,
        setter_name="layer_pos_patch_setter",
        min_batched_rank=3,
        target_slice_fn=lambda index: (FULL_SLICE, index[1]),
        source_slice_fn=lambda index: (FULL_SLICE, index[1]),
    )
    if tl_output is not None:
        return tl_output
    spec = _require_patch_spec(spec, "layer_pos_patch_setter")
    index = require_patch_index(spec, 2, "layer_pos_patch_setter")
    source_index = source_index_or_target(spec, index)
    target_has_batch = patch_target_has_batch_dim(corrupted_activation, spec, clean_cache)
    source_has_batch = patch_source_has_batch_dim(corrupted_activation, spec, clean_cache)
    return patch_slice(
        corrupted_activation,
        spec,
        clean_cache,
        target_slice=batch_prefixed_slice(target_has_batch, index[1]),
        source_slice=batch_prefixed_slice(source_has_batch, source_index[1]),
    )

make_component_patch_specs(layers, component, index_axis_names, axis_values, *, patch_setter, mode='replace', scale=1.0, name_style='safelens', name_template=None)

Create component-level patch specs using TransformerLens-style index axes.

Source code in src/SafeLens/core/patching.py
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
def make_component_patch_specs(
    layers: Iterable[LayerRef],
    component: str,
    index_axis_names: Sequence[AxisName],
    axis_values: Mapping[AxisName, Iterable[Any]],
    *,
    patch_setter: PatchSetter | TransformerLensPatchSetter,
    mode: PatchMode = "replace",
    scale: float = 1.0,
    name_style: ActivationNameStyle = "safelens",
    name_template: str | None = None,
) -> list[PatchSpec]:
    """Create component-level patch specs using TransformerLens-style index axes."""
    layer_values = list(layers)
    non_layer_axis_names: list[AxisName] = []
    for name in index_axis_names:
        if name != "layer":
            non_layer_axis_names.append(name)
    non_layer_values = [_axis_values(axis_values, name) for name in non_layer_axis_names]
    specs: list[PatchSpec] = []
    adapted_patch_setter = adapt_transformer_lens_patch_setter(patch_setter)

    for layer in layer_values:
        activation_name = activation_name_for_component(
            component,
            layer,
            name_style=name_style,
            name_template=name_template,
        )
        for axis_index in product(*non_layer_values):
            index_parts: list[Any] = []
            axis_lookup = dict(zip(non_layer_axis_names, axis_index, strict=True))
            for axis_name in index_axis_names:
                if axis_name == "layer":
                    index_parts.append(_patch_index_layer_value(layer))
                else:
                    index_parts.append(axis_lookup[axis_name])
            specs.append(
                PatchSpec(
                    layer=activation_name,
                    activation_name=activation_name,
                    target_index=tuple(index_parts),
                    mode=mode,
                    scale=scale,
                    setter=adapted_patch_setter,
                )
            )

    return specs

make_df_from_ranges(column_max_ranges, column_names)

Create a TransformerLens-style patch index table from axis ranges.

Source code in src/SafeLens/core/patching.py
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
def make_df_from_ranges(
    column_max_ranges: Sequence[int],
    column_names: Sequence[str],
) -> Any:
    """Create a TransformerLens-style patch index table from axis ranges."""
    if len(column_max_ranges) != len(column_names):
        raise ValueError(
            "column_max_ranges and column_names must have the same length, got "
            f"{len(column_max_ranges)} and {len(column_names)}."
        )
    if any(int(size) < 0 for size in column_max_ranges):
        raise ValueError(f"column_max_ranges must be non-negative, got {column_max_ranges!r}.")

    rows = [
        dict(zip(column_names, values, strict=True))
        for values in product(*(range(int(size)) for size in column_max_ranges))
    ]
    try:
        import pandas as pd

        return pd.DataFrame(rows, columns=list(column_names))
    except ImportError:
        return rows

make_index_table(index_axis_names, axis_values)

Create an ordered list of index rows from named axis values.

Source code in src/SafeLens/core/patching.py
2768
2769
2770
2771
2772
2773
2774
2775
2776
2777
def make_index_table(
    index_axis_names: Sequence[AxisName],
    axis_values: Mapping[AxisName, Iterable[Any]],
) -> list[dict[str, Any]]:
    """Create an ordered list of index rows from named axis values."""
    rows: list[dict[str, Any]] = []
    value_lists = [_axis_values(axis_values, axis_name) for axis_name in index_axis_names]
    for values in product(*value_lists):
        rows.append(dict(zip(index_axis_names, values, strict=True)))
    return rows

make_nested_grid(shape, *, fill_value=0.0)

Create a nested-list metric grid with the requested shape.

Source code in src/SafeLens/core/patching.py
2958
2959
2960
2961
2962
def make_nested_grid(shape: Sequence[int], *, fill_value: float = 0.0) -> Any:
    """Create a nested-list metric grid with the requested shape."""
    if not shape:
        return fill_value
    return [make_nested_grid(shape[1:], fill_value=fill_value) for _ in range(shape[0])]

make_patch_hook(spec, clean_cache)

Create a hook function that applies a patch to the current activation.

Source code in src/SafeLens/core/patching.py
245
246
247
248
249
250
251
252
253
254
def make_patch_hook(spec: PatchSpec, clean_cache: ActivationCache) -> HookFn:
    """Create a hook function that applies a patch to the current activation."""

    def patch_hook(*args: Any, **kwargs: Any) -> Any:
        if not has_hook_output(args, kwargs):
            return None
        activation = extract_hook_output(args, kwargs)
        return apply_patch(activation, spec, clean_cache)

    return patch_hook

make_patch_specs(layers, *, activation_name=None, target_indices=None, mode='replace', scale=1.0)

Create a simple grid of patch specs over layers and optional target indices.

Source code in src/SafeLens/core/patching.py
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
def make_patch_specs(
    layers: Iterable[LayerRef],
    *,
    activation_name: str | None = None,
    target_indices: Iterable[Any] | None = None,
    mode: PatchMode = "replace",
    scale: float = 1.0,
) -> list[PatchSpec]:
    """Create a simple grid of patch specs over layers and optional target indices."""
    indices = list(target_indices) if target_indices is not None else [None]
    return [
        PatchSpec(
            layer=layer,
            activation_name=activation_name,
            target_index=index,
            mode=mode,
            scale=scale,
        )
        for layer in layers
        for index in indices
    ]

make_transformer_lens_patch_specs(activation_name, index_table, *, patch_setter)

Create specs for a TransformerLens-style generic_activation_patch call.

Source code in src/SafeLens/core/patching.py
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
def make_transformer_lens_patch_specs(
    activation_name: str,
    index_table: Sequence[Mapping[str, Any]],
    *,
    patch_setter: PatchSetter | TransformerLensPatchSetter,
) -> list[PatchSpec]:
    """Create specs for a TransformerLens-style `generic_activation_patch` call."""
    index_table, _columns = normalize_index_table(index_table)
    specs: list[PatchSpec] = []
    expected_columns: tuple[str, ...] | None = None
    for row in index_table:
        if "layer" not in row:
            raise ValueError("TL-style patch index rows must include a `layer` column.")
        columns = tuple(row.keys())
        if not columns or columns[0] != "layer":
            raise ValueError("TL-style patch index rows must have `layer` as the first column.")
        if expected_columns is None:
            expected_columns = columns
        elif columns != expected_columns:
            raise ValueError("TL-style patch index rows must all have the same columns.")
        layer = row["layer"]
        index = tuple(row[column] for column in columns)
        target_name = activation_name_for_component(
            activation_name,
            layer,
            name_style="transformer_lens",
        )
        specs.append(
            PatchSpec(
                layer=target_name,
                activation_name=target_name,
                target_index=index,
                setter=adapt_transformer_lens_patch_setter(patch_setter),
            )
        )
    return specs

maybe_apply_transformer_lens_patch_setter(corrupted_activation, index_or_spec, clean_activation_or_cache, *, expected_length, setter_name, min_batched_rank, target_slice_fn, source_slice_fn)

Apply a direct TL-style patch setter call when no PatchSpec is supplied.

Source code in src/SafeLens/core/patching.py
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
def maybe_apply_transformer_lens_patch_setter(
    corrupted_activation: Any,
    index_or_spec: Any,
    clean_activation_or_cache: Any,
    *,
    expected_length: int,
    setter_name: str,
    min_batched_rank: int,
    target_slice_fn: Callable[[tuple[Any, ...]], tuple[Any, ...]],
    source_slice_fn: Callable[[tuple[Any, ...]], tuple[Any, ...]],
) -> Any | None:
    """Apply a direct TL-style patch setter call when no PatchSpec is supplied."""
    if isinstance(index_or_spec, PatchSpec):
        return None
    index = normalize_index(index_or_spec)
    if len(index) != expected_length:
        raise ValueError(
            f"{setter_name} expects an index of length {expected_length}; got {index!r}."
        )
    patched = clone_patch_target(corrupted_activation)
    target_slice = _maybe_drop_batch_slice(
        target_slice_fn(index),
        corrupted_activation,
        clean_activation_or_cache,
        min_batched_rank=min_batched_rank,
    )
    source_slice = _maybe_drop_batch_slice(
        source_slice_fn(index),
        clean_activation_or_cache,
        corrupted_activation,
        min_batched_rank=min_batched_rank,
    )
    patch_value = get_indexed(clean_activation_or_cache, source_slice)
    patch_value = broadcast_patch_value_to_slice(corrupted_activation, target_slice, patch_value)
    set_indexed(patched, target_slice, patch_value)
    return patched

mutable_patch_target(value)

Return a patchable target, converting immutable nested sequences to lists.

Source code in src/SafeLens/core/patching.py
2368
2369
2370
2371
2372
2373
2374
def mutable_patch_target(value: Any) -> Any:
    """Return a patchable target, converting immutable nested sequences to lists."""
    if isinstance(value, list):
        return [mutable_patch_target(item) for item in value]
    if is_sequence(value):
        return [mutable_patch_target(item) for item in value]
    return value

normalize_index(index)

Normalize scalar/list/tuple indices to tuples.

Source code in src/SafeLens/core/patching.py
2175
2176
2177
2178
2179
2180
2181
2182
2183
def normalize_index(index: Any) -> tuple[Any, ...]:
    """Normalize scalar/list/tuple indices to tuples."""
    if index is None:
        return ()
    if isinstance(index, tuple):
        return index
    if isinstance(index, list):
        return tuple(index)
    return (index,)

normalize_index_table(index_df, index_axis_names=None)

Normalize pandas-like, dict, or sequence index tables.

Source code in src/SafeLens/core/patching.py
2780
2781
2782
2783
2784
2785
2786
2787
2788
2789
2790
2791
2792
2793
2794
2795
2796
2797
2798
2799
2800
2801
2802
2803
2804
2805
2806
2807
2808
2809
def normalize_index_table(
    index_df: Any,
    index_axis_names: Sequence[AxisName] | None = None,
) -> tuple[list[dict[str, Any]], Sequence[AxisName]]:
    """Normalize pandas-like, dict, or sequence index tables."""
    columns = list(index_axis_names) if index_axis_names is not None else None
    to_dict = getattr(index_df, "to_dict", None)
    if callable(to_dict):
        try:
            records = to_dict("records")
            if columns is None:
                columns = list(index_df.columns)
            record_rows = cast(Iterable[Mapping[Any, Any]], records)
            return [dict(record) for record in record_rows], tuple(columns)
        except TypeError:
            pass

    rows: list[dict[str, Any]] = []
    for row in index_df:
        if isinstance(row, Mapping):
            if columns is None:
                columns = list(row.keys())
            rows.append({column: row[column] for column in columns if column in row})
        else:
            if columns is None:
                raise ValueError("Pass `index_axis_names` when index rows are not mappings.")
            rows.append(dict(zip(columns, row, strict=True)))
    if columns is None:
        columns = []
    return rows, tuple(columns)

patch_results_to_index_df(results, index_axis_names=None)

Return a TransformerLens-style index DataFrame when pandas is available.

Source code in src/SafeLens/core/patching.py
506
507
508
509
510
511
512
513
514
515
516
517
518
def patch_results_to_index_df(
    results: Sequence[PatchResult],
    index_axis_names: Sequence[AxisName] | None = None,
) -> Any:
    """Return a TransformerLens-style index DataFrame when pandas is available."""
    index_table = patch_results_to_index_table(results, index_axis_names)
    columns = list(index_axis_names or (index_table[0].keys() if index_table else ()))
    try:
        import pandas as pd

        return pd.DataFrame(index_table, columns=columns)
    except ImportError:
        return index_table

patch_results_to_index_table(results, index_axis_names=None)

Return a dependency-free index table for a patch result sequence.

Source code in src/SafeLens/core/patching.py
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
def patch_results_to_index_table(
    results: Sequence[PatchResult],
    index_axis_names: Sequence[AxisName] | None = None,
) -> list[dict[str, Any]]:
    """Return a dependency-free index table for a patch result sequence."""
    table: list[dict[str, Any]] = []
    for result_index, result in enumerate(results):
        index = normalize_index(result.spec.target_index)
        if index_axis_names is None:
            row: dict[str, Any] = {"patch_index": result_index}
            row.update({f"index_{axis_index}": value for axis_index, value in enumerate(index)})
        else:
            row = {}
            if index_axis_names and index_axis_names[0] == "layer":
                row["layer"] = (
                    index[0] if len(index) == len(index_axis_names) else result.spec.layer
                )
            for axis_index, axis_name in enumerate(index_axis_names):
                if axis_name == "layer" and "layer" in row:
                    continue
                source_index = axis_index if len(index) == len(index_axis_names) else axis_index - 1
                if 0 <= source_index < len(index):
                    row[axis_name] = index[source_index]
        table.append(row)
    return table

patch_results_to_metric_grid(results, index_axis_names=None)

Convert patch results to a TransformerLens-style metric grid.

Source code in src/SafeLens/core/patching.py
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
def patch_results_to_metric_grid(
    results: Sequence[PatchResult],
    index_axis_names: Sequence[AxisName] | None = None,
) -> Any:
    """Convert patch results to a TransformerLens-style metric grid."""
    if index_axis_names is None:
        return [result.metric for result in results]
    if not index_axis_names:
        return results[0].metric if results else 0.0

    index_table = patch_results_to_index_table(results, index_axis_names)
    axis_values = [
        _ordered_unique(row[axis_name] for row in index_table if axis_name in row)
        for axis_name in index_axis_names
    ]
    grid = make_nested_grid([len(values) for values in axis_values], fill_value=0.0)
    axis_lookup = [
        {value: position for position, value in enumerate(values)} for values in axis_values
    ]

    for result, row in zip(results, index_table, strict=True):
        coordinate = tuple(
            axis_lookup[axis_index][row[axis_name]]
            for axis_index, axis_name in enumerate(index_axis_names)
        )
        set_nested(grid, coordinate, result.metric)
    return grid

patch_slice(corrupted_activation, spec, clean_cache, *, target_slice, source_slice)

Patch a target slice from the matching clean activation slice.

Source code in src/SafeLens/core/patching.py
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
def patch_slice(
    corrupted_activation: Any,
    spec: PatchSpec,
    clean_cache: ActivationCache,
    *,
    target_slice: tuple[Any, ...],
    source_slice: tuple[Any, ...],
) -> Any:
    """Patch a target slice from the matching clean activation slice."""
    clean_activation = spec.value if spec.value is not None else clean_cache[spec.clean_name]
    patch_value = scale_value(get_indexed(clean_activation, source_slice), spec.scale)
    patch_value = broadcast_patch_value_to_slice(corrupted_activation, target_slice, patch_value)
    patched = clone_patch_target(corrupted_activation)
    if spec.mode == "replace":
        set_indexed(patched, target_slice, patch_value)
        return patched
    if spec.mode == "add":
        current_value = get_indexed(patched, target_slice)
        set_indexed(patched, target_slice, add_values(current_value, patch_value))
        return patched
    raise ValueError(f"Unsupported patch mode: {spec.mode}")

patch_source_has_batch_dim(corrupted_activation, spec, clean_cache)

Return whether the clean activation used by a patch includes batch.

Source code in src/SafeLens/core/patching.py
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
def patch_source_has_batch_dim(
    corrupted_activation: Any,
    spec: PatchSpec,
    clean_cache: ActivationCache | Any,
) -> bool:
    """Return whether the clean activation used by a patch includes batch."""
    if spec.value is not None:
        clean_rank = len(shape_of(spec.value))
        corrupted_rank = len(shape_of(corrupted_activation))
        if corrupted_rank == clean_rank + 1:
            return False
        if clean_rank == corrupted_rank + 1:
            return True
    if isinstance(clean_cache, ActivationCache):
        return clean_cache.has_batch_dim
    return True

patch_target_has_batch_dim(corrupted_activation, spec, clean_cache)

Infer whether the current corrupted activation includes a batch axis.

Source code in src/SafeLens/core/patching.py
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
def patch_target_has_batch_dim(
    corrupted_activation: Any,
    spec: PatchSpec,
    clean_cache: ActivationCache | Any,
) -> bool:
    """Infer whether the current corrupted activation includes a batch axis."""
    clean_activation = spec.value if spec.value is not None else clean_cache[spec.clean_name]
    clean_rank = len(shape_of(clean_activation))
    corrupted_rank = len(shape_of(corrupted_activation))
    if spec.value is not None:
        if corrupted_rank == clean_rank + 1:
            return True
        if corrupted_rank + 1 == clean_rank:
            return False
    if patch_source_has_batch_dim(corrupted_activation, spec, clean_cache):
        return corrupted_rank + 1 != clean_rank
    return corrupted_rank == clean_rank + 1

repeat_value_like(value, times)

Clone a value times times while preserving tensor/array backends when possible.

Source code in src/SafeLens/core/patching.py
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
def repeat_value_like(value: Any, times: int) -> Any:
    """Clone a value `times` times while preserving tensor/array backends when possible."""
    try:
        import torch

        if isinstance(value, torch.Tensor):
            return value.unsqueeze(0).expand((times, *value.shape)).clone()
    except Exception:
        pass
    try:
        import numpy as np

        if isinstance(value, np.ndarray):
            return np.broadcast_to(value, (times, *value.shape)).copy()
    except Exception:
        pass
    return [clone_activation(value) for _ in range(times)]

replace_patch_setter(corrupted_activation, spec, clean_cache)

Replace the whole activation or a slice with the clean activation value.

Source code in src/SafeLens/core/patching.py
198
199
200
201
202
203
204
205
206
207
208
209
210
def replace_patch_setter(
    corrupted_activation: Any,
    spec: PatchSpec,
    clean_cache: ActivationCache,
) -> Any:
    """Replace the whole activation or a slice with the clean activation value."""
    patch_value = get_patch_value(spec, clean_cache)
    if spec.target_index is None:
        return coerce_value_like(corrupted_activation, patch_value)

    patched = clone_patch_target(corrupted_activation)
    set_indexed(patched, spec.target_index, patch_value)
    return patched

require_patch_index(spec, expected_length, setter_name)

Return a normalized index tuple or raise a clear error.

Source code in src/SafeLens/core/patching.py
1988
1989
1990
1991
1992
1993
1994
1995
def require_patch_index(spec: PatchSpec, expected_length: int, setter_name: str) -> tuple[Any, ...]:
    """Return a normalized index tuple or raise a clear error."""
    index = normalize_index(spec.target_index)
    if len(index) != expected_length:
        raise ValueError(
            f"{setter_name} expects an index of length {expected_length}; got {index!r}."
        )
    return index

run_activation_patch(model, corrupted_batch, clean_cache, spec, metric, *, layers=None)

Run one patched corrupted forward pass and score it with a metric.

Source code in src/SafeLens/core/patching.py
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
def run_activation_patch(
    model: ModelWrapper,
    corrupted_batch: Any,
    clean_cache: ActivationCache,
    spec: PatchSpec,
    metric: PatchMetric,
    *,
    layers: Sequence[LayerRef] | None = None,
) -> PatchResult:
    """Run one patched corrupted forward pass and score it with a metric."""
    patch_hook = make_patch_hook(spec, clean_cache)
    wrapper_run_with_hooks = getattr(model, "run_with_hooks", None)
    if callable(wrapper_run_with_hooks) and layers is None:
        output = wrapper_run_with_hooks(
            corrupted_batch,
            fwd_hooks=[(spec.layer, patch_hook)],
            **_patch_run_return_type_kwargs(wrapper_run_with_hooks, metric, model),
        )
        cache: Any = {}
    else:
        with temporary_hooks(model, [(spec.layer, patch_hook)]):
            output, cache = model.run_with_cache(
                corrupted_batch,
                layers=layers,
                **_patch_run_return_type_kwargs(model.run_with_cache, metric, model),
            )
    return PatchResult(
        spec=spec, metric=_metric_to_float(metric(output)), output=output, cache=cache
    )

scale_value(value, scale)

Scale tensor-like or nested Python sequence values.

Source code in src/SafeLens/core/patching.py
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
def scale_value(value: Any, scale: float) -> Any:
    """Scale tensor-like or nested Python sequence values."""
    if scale == 1.0:
        return value
    try:
        return value * scale
    except TypeError:
        if is_sequence(value):
            return [scale_value(item, scale) for item in value]
        return value

set_indexed(value, index, replacement)

Assign into tensor-like or nested-list values.

Source code in src/SafeLens/core/patching.py
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
def set_indexed(value: Any, index: Any, replacement: Any) -> None:
    """Assign into tensor-like or nested-list values."""
    normalized = expand_ellipsis_index(normalize_index(index), len(shape_of(value)))
    replacement = coerce_value_like(value, replacement)
    if len(normalized) == 1:
        try:
            value[normalized[0]] = replacement
            return
        except (TypeError, IndexError, KeyError):
            pass
    try:
        value[normalized] = replacement
    except (TypeError, IndexError, KeyError):
        set_nested(value, normalized, replacement)

set_nested(value, index, replacement)

Assign into nested Python containers with full slices and integer indices.

Source code in src/SafeLens/core/patching.py
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
def set_nested(value: Any, index: tuple[Any, ...], replacement: Any) -> None:
    """Assign into nested Python containers with full slices and integer indices."""
    if len(index) == 1:
        head = index[0]
        if isinstance(head, slice):
            replacement_items = list(replacement)
            target_range = (
                range(len(value)) if head == FULL_SLICE else range(*head.indices(len(value)))
            )
            for item_index, replacement_item in zip(target_range, replacement_items, strict=True):
                value[item_index] = replacement_item
            return
        value[head] = replacement
        return

    head = index[0]
    tail = index[1:]
    if isinstance(head, slice):
        target_range = range(len(value)) if head == FULL_SLICE else range(*head.indices(len(value)))
        replacement_items = list(replacement)
        for item_index, replacement_item in zip(target_range, replacement_items, strict=True):
            set_nested(value[item_index], tail, replacement_item)
        return
    set_nested(value[head], tail, replacement)

shape_of(value)

Return a best-effort shape for tensor-like or nested-list values.

Source code in src/SafeLens/core/patching.py
2863
2864
2865
2866
2867
2868
2869
2870
2871
2872
def shape_of(value: Any) -> tuple[int, ...]:
    """Return a best-effort shape for tensor-like or nested-list values."""
    shape = getattr(value, "shape", None)
    if shape is not None:
        return tuple(int(dim) for dim in shape)
    if isinstance(value, Sequence) and not isinstance(value, str | bytes):
        if not value:
            return (0,)
        return (len(value), *shape_of(value[0]))
    return ()

source_index_or_target(spec, target_index)

Return the source index if supplied, otherwise the target index.

Source code in src/SafeLens/core/patching.py
2005
2006
2007
2008
2009
2010
2011
2012
def source_index_or_target(spec: PatchSpec, target_index: tuple[Any, ...]) -> tuple[Any, ...]:
    """Return the source index if supplied, otherwise the target index."""
    if spec.source_index is None:
        return target_index
    source_index = normalize_index(spec.source_index)
    if len(source_index) != len(target_index):
        raise ValueError("source_index must have the same rank as target_index.")
    return source_index

tensor_to_numpy_source(value)

Detach/copy tensor-like values before numpy converts them.

Source code in src/SafeLens/core/patching.py
2344
2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
def tensor_to_numpy_source(value: Any) -> Any:
    """Detach/copy tensor-like values before numpy converts them."""
    detach = getattr(value, "detach", None)
    if callable(detach):
        value = detach()
    cpu = getattr(value, "cpu", None)
    if callable(cpu):
        value = cpu()
    numpy = getattr(value, "numpy", None)
    if callable(numpy):
        return numpy()
    return value

to_python_container(value)

Convert array/tensor/tuple values into mutable Python containers.

Source code in src/SafeLens/core/patching.py
2358
2359
2360
2361
2362
2363
2364
2365
def to_python_container(value: Any) -> Any:
    """Convert array/tensor/tuple values into mutable Python containers."""
    tolist = getattr(value, "tolist", None)
    if callable(tolist):
        return tolist()
    if is_sequence(value):
        return [to_python_container(item) for item in value]
    return value

token_positions_from_shape(shape)

Return sequence length from token-id shapes [pos] or [batch, pos].

Source code in src/SafeLens/core/patching.py
2663
2664
2665
2666
2667
2668
2669
def token_positions_from_shape(shape: Sequence[int]) -> int | None:
    """Return sequence length from token-id shapes `[pos]` or `[batch, pos]`."""
    if len(shape) == 1:
        return int(shape[0])
    if len(shape) >= 2:
        return int(shape[-1])
    return None

token_positions_from_value(value)

Return sequence length from token-id values, including scalar ids.

Source code in src/SafeLens/core/patching.py
2651
2652
2653
2654
2655
2656
2657
2658
2659
2660
def token_positions_from_value(value: Any) -> int | None:
    """Return sequence length from token-id values, including scalar ids."""
    if isinstance(value, Integral):
        return 1
    shape = shape_of(value)
    if shape:
        return token_positions_from_shape(shape)
    if getattr(value, "shape", None) is not None:
        return 1
    return None

transformer_lens_activation_name_for_component(component, layer)

Return a TransformerLens-style hook name for canonical SafeLens components.

Source code in src/SafeLens/core/patching.py
174
175
176
177
178
179
180
181
182
183
184
185
186
def transformer_lens_activation_name_for_component(component: str, layer: LayerRef) -> str:
    """Return a TransformerLens-style hook name for canonical SafeLens components."""
    normalized_component = _normalize_patch_component(component)
    try:
        from SafeLens.utils.model_bridge import transformer_lens_component_name

        return transformer_lens_component_name(normalized_component, _layer_ref_to_int(layer))
    except (ImportError, TypeError, ValueError):
        template = TRANSFORMER_LENS_ACTIVATION_TEMPLATES.get(
            normalized_component,
            "blocks.{layer}.hook_{component}",
        )
        return template.format(layer=layer, component=normalized_component)