diff --git a/pyproject.toml b/pyproject.toml index e4edef2..c4a9191 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,6 +13,7 @@ dependencies = [ "tensordict>=0.10", "torch>=2.7.1", ] + classifiers = [ "Development Status :: 3 - Alpha", "Intended Audience :: Science/Research", @@ -29,6 +30,12 @@ source = "https://github.com/Xmaster6y/tdhook" issues = "https://github.com/Xmaster6y/tdhook/issues" releasenotes = "https://github.com/Xmaster6y/tdhook/releases" +[project.optional-dependencies] +circuit-lens = [ + "circuit-tracer>=0.5.0", + "transformer-lens>=2.16.0", +] + [dependency-groups] dev = [ "captum>=0.8.0", diff --git a/src/tdhook/attribution/__init__.py b/src/tdhook/attribution/__init__.py index 28464ac..1ec1032 100644 --- a/src/tdhook/attribution/__init__.py +++ b/src/tdhook/attribution/__init__.py @@ -2,20 +2,47 @@ Module for attribution methods. """ +# Import order is intentional: legacy modules import Saliency from this package. +# ruff: noqa: I001 + from .lrp import LRP from .saliency import Saliency from .grad_cam import GradCAM from .guided_backpropagation import GuidedBackpropagation from .activation_maximisation import ActivationMaximisation from .integrated_gradients import IntegratedGradients +from .circuit_lens import ( + AttentionContributor, + AttentionSite, + AttributionConventions, + CircuitLensArtifact, + FeatureContributor, + FeatureSite, + LogitContributor, + attention_contributions, + attribute_feature_circuit, + feature_contributions, + logit_contributions, +) __all__ = [ + "LRP", "ActivationMaximisation", + "AttentionContributor", + "AttentionSite", + "AttributionConventions", + "CircuitLensArtifact", + "FeatureContributor", + "FeatureSite", "GradCAM", "GuidedBackpropagation", "IntegratedGradients", + "LogitContributor", "Saliency", - "LRP", + "attention_contributions", + "attribute_feature_circuit", + "feature_contributions", + "logit_contributions", ] # TODO: Implement Occlusion diff --git a/src/tdhook/attribution/circuit_lens.py b/src/tdhook/attribution/circuit_lens.py new file mode 100644 index 0000000..fa3f4ce --- /dev/null +++ b/src/tdhook/attribution/circuit_lens.py @@ -0,0 +1,480 @@ +"""Input-dependent feature-circuit attribution. + +This module implements the local decompositions used by CircuitLens without +depending on a transformer or transcoder implementation. Model integrations +describe their sites with public :class:`~tdhook.targets.Target` objects; +:func:`attribute_feature_circuit` obtains activations and output gradients +through :class:`~tdhook.session.HookSession`. + +All scores are evaluated at the observed input. Autograd supplies the local +Jacobian, so piecewise nonlinearity gates are frozen at their observed state. +Attention scores additionally hold the observed attention pattern fixed and +decompose the value/output path by head and source token. The result is a +local attribution, not an intervention or a finite-difference causal effect. +""" + +from __future__ import annotations + +from collections.abc import Callable, Iterator, Mapping, Sequence +from dataclasses import asdict, dataclass +from itertools import product +from typing import Any + +import torch +from torch import Tensor, nn + +from tdhook.session import CapturedTarget, HookSession +from tdhook.targets import Target + + +@dataclass(frozen=True) +class FeatureSite: + """One transcoder-feature activation selected through a public target. + + ``position`` indexes the non-feature axes after the target has selected + ``target.indices``. It is required for the target feature when the + selected tensor contains more than one scalar and is optional for upstream + sites, whose remaining positions are attributed independently. + """ + + layer: int + target: Target + position: tuple[int, ...] | None = None + + def __post_init__(self) -> None: + if self.layer < 0: + raise ValueError("layer must be non-negative") + if self.target.kind != "activation": + raise ValueError("FeatureSite.target must be an activation target") + if self.position is not None and any(index < 0 for index in self.position): + raise ValueError("FeatureSite.position indices must be non-negative") + + def gradient_target(self) -> Target: + """Return the matching output-gradient target.""" + + return Target( + self.target.module_path, + "gradient", + self.target.feature_axis, + self.target.indices, + output_path=self.target.output_path, + ) + + +@dataclass(frozen=True) +class AttentionSite: + """Captured tensors needed for a frozen-pattern attention decomposition. + + The selected tensors must have shapes ``[heads, query, source]``, + ``[source, heads, head_dim]``, and ``[query, model]`` respectively. A + leading singleton batch dimension is accepted. ``output_weight`` has shape + ``[heads, head_dim, model]``. + """ + + layer: int + pattern: Target + values: Target + output_gradient: Target + output_weight: Tensor + target_position: int + + def __post_init__(self) -> None: + if self.layer < 0: + raise ValueError("layer must be non-negative") + if self.target_position < 0: + raise ValueError("target_position must be non-negative") + if self.pattern.kind != "activation" or self.values.kind != "activation": + raise ValueError("attention pattern and values must be activation targets") + if self.output_gradient.kind != "gradient": + raise ValueError("attention output_gradient must be a gradient target") + if self.pattern.indices != self.values.indices: + raise ValueError("attention pattern and values must select the same heads in the same order") + + +@dataclass(frozen=True) +class AttributionConventions: + """Serializable statement of the attribution semantics.""" + + jacobian: str = "local autograd Jacobian at the observed input" + attention: str = "observed attention pattern frozen; value/output path decomposed" + nonlinearities: str = "observed local derivatives used; piecewise gates frozen" + score: str = "activation times local gradient" + + +@dataclass(frozen=True) +class FeatureContributor: + """Contribution from one upstream feature and non-feature position.""" + + layer: int + feature_index: int + position: tuple[int, ...] + score: float + + +@dataclass(frozen=True) +class AttentionContributor: + """Contribution from one attention head and source token.""" + + layer: int + head_index: int + source_token: int + target_token: int + score: float + + +@dataclass(frozen=True) +class LogitContributor: + """Contribution from the active target feature to one output logit.""" + + token_index: int + score: float + + +@dataclass(frozen=True) +class CircuitLensArtifact: + """JSON-serializable contributors and scores from one workflow run.""" + + target_layer: int + target_feature_index: int + target_position: tuple[int, ...] + target_activation: float + upstream_features: tuple[FeatureContributor, ...] + attention: tuple[AttentionContributor, ...] + output_logits: tuple[LogitContributor, ...] + conventions: AttributionConventions = AttributionConventions() + + def to_dict(self) -> dict[str, object]: + """Return a nested representation accepted by :func:`json.dumps`.""" + + return asdict(self) + + +@dataclass(frozen=True) +class _GradientState: + tensor: Tensor + original: Tensor | None + value: Tensor | None + + +def feature_contributions( + activations: Tensor, + gradients: Tensor, + *, + layer: int, + feature_indices: Sequence[int], + feature_axis: int = -1, +) -> tuple[FeatureContributor, ...]: + """Compute activation-times-gradient scores for upstream features.""" + + if activations.shape != gradients.shape: + raise ValueError("feature activations and gradients must have the same shape") + if layer < 0: + raise ValueError("layer must be non-negative") + axis = _normalized_axis(feature_axis, activations.ndim) + if activations.shape[axis] != len(feature_indices): + raise ValueError("feature_indices must match the selected feature axis") + scores = (activations.detach() * gradients.detach()).to(device="cpu", dtype=torch.float64) + contributors: list[FeatureContributor] = [] + for coordinate in _coordinates(scores.shape): + feature_offset = coordinate[axis] + position = coordinate[:axis] + coordinate[axis + 1 :] + contributors.append( + FeatureContributor(layer, int(feature_indices[feature_offset]), position, float(scores[coordinate])) + ) + return tuple(contributors) + + +def attention_contributions( + pattern: Tensor, + values: Tensor, + output_weight: Tensor, + output_gradient: Tensor, + *, + layer: int, + target_position: int, + head_indices: Sequence[int] | None = None, +) -> tuple[AttentionContributor, ...]: + """Decompose a frozen attention output by head and source token. + + The score for head ``h`` and source ``s`` is + ``pattern[h, q, s] * ``. + """ + + if layer < 0: + raise ValueError("layer must be non-negative") + pattern = _without_singleton_batch("pattern", pattern, 3) + values = _without_singleton_batch("values", values, 3) + output_gradient = _without_singleton_batch("output_gradient", output_gradient, 2) + if output_weight.ndim != 3: + raise ValueError("output_weight must have shape [heads, head_dim, model]") + heads, queries, sources = pattern.shape + if values.shape[:2] != (sources, heads): + raise ValueError("values must have shape [source, heads, head_dim]") + if output_weight.shape[:2] != (heads, values.shape[2]): + raise ValueError("output_weight head and head_dim axes do not match values") + if output_gradient.shape != (queries, output_weight.shape[2]): + raise ValueError("output_gradient must have shape [query, model]") + if target_position < 0 or target_position >= queries: + raise IndexError("target_position is outside the attention query axis") + if head_indices is None: + head_indices = tuple(range(heads)) + if len(head_indices) != heads: + raise ValueError("head_indices must contain one index per selected head") + + q = target_position + projected_values = torch.einsum("shd,hdm->shm", values.detach(), output_weight.detach()) + scores = torch.einsum("hs,shm,m->hs", pattern.detach()[:, q, :], projected_values, output_gradient.detach()[q]).to( + device="cpu", dtype=torch.float64 + ) + return tuple( + AttentionContributor(layer, int(head_indices[head]), source, q, float(scores[head, source])) + for head in range(heads) + for source in range(sources) + ) + + +def logit_contributions( + feature_activation: Tensor | float, + logit_gradients: Tensor, + *, + token_indices: Sequence[int], +) -> tuple[LogitContributor, ...]: + """Compute feature-activation times each selected logit's local gradient.""" + + activation = torch.as_tensor(feature_activation).detach() + if activation.numel() != 1: + raise ValueError("feature_activation must contain one scalar") + gradients = logit_gradients.detach().reshape(-1) + if gradients.numel() != len(token_indices): + raise ValueError("token_indices must contain one index per logit gradient") + if any(index < 0 for index in token_indices): + raise ValueError("token_indices must be non-negative") + scores = (activation.reshape(()) * gradients).to(device="cpu", dtype=torch.float64) + return tuple( + LogitContributor(int(index), float(score)) for index, score in zip(token_indices, scores, strict=True) + ) + + +def attribute_feature_circuit( + model: nn.Module, + *model_args: object, + target_feature: FeatureSite, + upstream_features: Sequence[FeatureSite] = (), + attention_sites: Sequence[AttentionSite] = (), + output_logits: Callable[[object], Tensor] | None = None, + logit_indices: Sequence[int] = (), + model_kwargs: Mapping[str, object] | None = None, + top_k: int | None = None, + positive_only: bool = False, +) -> CircuitLensArtifact: + """Run one input-dependent CircuitLens attribution workflow. + + ``output_logits`` extracts a one-dimensional logit tensor from the model + output. Only ``logit_indices`` are differentiated, avoiding a full-vocab + Jacobian unless the caller explicitly requests it. Every configured site + must be reached exactly once: shared modules need distinct hook points so + forward activations cannot be paired with reverse-order gradients. + Existing gradients on model parameters and caller-owned leaf tensors are + restored after the workflow. + """ + + if not isinstance(model, nn.Module): + raise TypeError("model must be a torch.nn.Module") + if len(target_feature.target.indices) != 1: + raise ValueError("target_feature must select exactly one feature") + if top_k is not None and top_k < 1: + raise ValueError("top_k must be positive") + if logit_indices and output_logits is None: + raise ValueError("output_logits is required when logit_indices are requested") + if any(index < 0 for index in logit_indices): + raise ValueError("logit_indices must be non-negative") + kwargs = {} if model_kwargs is None else dict(model_kwargs) + saved_gradients = _save_gradients(model, model_args, kwargs) + + try: + with HookSession(model) as session: + target_activation = session.capture(target_feature.target, detach=False) + target_gradient = session.capture(target_feature.gradient_target()) + upstream_captures = [ + (site, session.capture(site.target), session.capture(site.gradient_target())) + for site in upstream_features + ] + attention_captures = [ + ( + site, + session.capture(site.pattern), + session.capture(site.values), + session.capture(site.output_gradient), + ) + for site in attention_sites + ] + + output = model(*model_args, **kwargs) + captured_target = _single_capture(target_activation, "target feature activation") + live_target = _site_scalar(captured_target, target_feature) + retain_graph = bool(logit_indices) + live_target.backward(retain_graph=retain_graph) + + upstream = tuple( + contributor + for site, activations, gradients in upstream_captures + for contributor in feature_contributions( + _single_capture(activations, "upstream activation"), + _single_capture(gradients, "upstream gradient"), + layer=site.layer, + feature_indices=site.target.indices, + feature_axis=site.target.feature_axis, + ) + ) + attention = tuple( + contributor + for site, pattern, values, gradient in attention_captures + for contributor in attention_contributions( + _single_capture(pattern, "attention pattern"), + _single_capture(values, "attention values"), + site.output_weight, + _single_capture(gradient, "attention output gradient"), + layer=site.layer, + target_position=site.target_position, + head_indices=site.pattern.indices, + ) + ) + + logit_gradients: list[Tensor] = [] + if logit_indices: + assert output_logits is not None + logits = output_logits(output) + if not isinstance(logits, Tensor) or logits.ndim != 1: + raise ValueError("output_logits must return a one-dimensional tensor") + for offset, token_index in enumerate(logit_indices): + if token_index >= logits.numel(): + raise IndexError(f"logit index {token_index} is out of bounds") + logits[token_index].backward(retain_graph=offset + 1 < len(logit_indices)) + expected_captures = len(logit_indices) + if len(target_gradient.values) != expected_captures: + raise RuntimeError( + "target feature gradient must be captured exactly once per backward objective; " + f"expected {expected_captures}, observed {len(target_gradient.values)}" + ) + captured_logit_gradients = target_gradient.values + logit_gradients = [ + _site_scalar(gradient, target_feature).detach() for gradient in captured_logit_gradients + ] + + target_value = live_target.detach() + logits_artifact = logit_contributions( + target_value, + torch.stack(logit_gradients) if logit_gradients else torch.empty(0), + token_indices=logit_indices, + ) + target_position = _site_position(captured_target, target_feature) + return CircuitLensArtifact( + target_layer=target_feature.layer, + target_feature_index=target_feature.target.indices[0], + target_position=target_position, + target_activation=float(target_value.cpu()), + upstream_features=_rank(upstream, top_k=top_k, positive_only=positive_only), + attention=_rank(attention, top_k=top_k, positive_only=positive_only), + output_logits=_rank(logits_artifact, top_k=top_k, positive_only=positive_only), + ) + finally: + _restore_gradients(saved_gradients) + + +def _single_capture(capture: CapturedTarget, name: str) -> Tensor: + if len(capture.values) != 1: + raise RuntimeError(f"{name} target must be reached exactly once; observed {len(capture.values)} captures") + return capture.values[0] + + +def _site_scalar(value: Tensor, site: FeatureSite) -> Tensor: + axis = _normalized_axis(site.target.feature_axis, value.ndim) + position = _site_position(value, site) + coordinate = list(position) + coordinate.insert(axis, 0) + return value[tuple(coordinate)] + + +def _site_position(value: Tensor, site: FeatureSite) -> tuple[int, ...]: + axis = _normalized_axis(site.target.feature_axis, value.ndim) + position_shape = value.shape[:axis] + value.shape[axis + 1 :] + position = site.position + if position is None: + if torch.Size(position_shape).numel() != 1: + raise ValueError("FeatureSite.position is required when the selected target is not scalar") + return tuple(0 for _ in position_shape) + if len(position) != len(position_shape): + raise ValueError("FeatureSite.position must index every non-feature axis") + if any(index >= size for index, size in zip(position, position_shape, strict=True)): + raise IndexError("FeatureSite.position is outside the selected activation") + return position + + +def _rank(items: Sequence[Any], *, top_k: int | None, positive_only: bool) -> tuple[Any, ...]: + selected = [item for item in items if not positive_only or item.score > 0] + selected.sort(key=lambda item: (-abs(item.score), -item.score, repr(item))) + return tuple(selected if top_k is None else selected[:top_k]) + + +def _normalized_axis(axis: int, ndim: int) -> int: + normalized = axis if axis >= 0 else ndim + axis + if normalized < 0 or normalized >= ndim: + raise ValueError(f"feature_axis {axis} is out of bounds for a {ndim}-D tensor") + return normalized + + +def _without_singleton_batch(name: str, value: Tensor, expected_ndim: int) -> Tensor: + if value.ndim == expected_ndim + 1: + if value.shape[0] != 1: + raise ValueError(f"{name} only supports a singleton batch dimension") + value = value[0] + if value.ndim != expected_ndim: + raise ValueError(f"{name} must have {expected_ndim} dimensions, with an optional singleton batch") + return value + + +def _coordinates(shape: torch.Size) -> tuple[tuple[int, ...], ...]: + return tuple(product(*(range(size) for size in shape))) + + +def _save_gradients(model: nn.Module, *values: object) -> list[_GradientState]: + tensors = (*model.parameters(), *(tensor for value in values for tensor in _iter_tensors(value))) + unique_leaves = {id(tensor): tensor for tensor in tensors if tensor.is_leaf and tensor.requires_grad} + return [ + _GradientState(tensor, tensor.grad, None if tensor.grad is None else tensor.grad.detach().clone()) + for tensor in unique_leaves.values() + ] + + +def _iter_tensors(value: object) -> Iterator[Tensor]: + if isinstance(value, Tensor): + yield value + elif isinstance(value, Mapping): + for item in value.values(): + yield from _iter_tensors(item) + elif isinstance(value, (tuple, list)): + for item in value: + yield from _iter_tensors(item) + + +def _restore_gradients(saved: Sequence[_GradientState]) -> None: + for state in saved: + if state.original is not None: + assert state.value is not None + state.original.copy_(state.value) + state.tensor.grad = state.original + + +__all__ = [ + "AttentionContributor", + "AttentionSite", + "AttributionConventions", + "CircuitLensArtifact", + "FeatureContributor", + "FeatureSite", + "LogitContributor", + "attention_contributions", + "attribute_feature_circuit", + "feature_contributions", + "logit_contributions", +] diff --git a/src/tdhook/session.py b/src/tdhook/session.py index ebb29ab..d3c2083 100644 --- a/src/tdhook/session.py +++ b/src/tdhook/session.py @@ -99,6 +99,7 @@ def capture( *, direction: HookDirection | None = None, prepend: bool = False, + detach: bool = True, ) -> CapturedTarget: """Capture ``target`` while this session is active. @@ -106,7 +107,10 @@ def capture( ``"bwd_pre"`` for gradient targets. Forward inputs use ``"fwd_pre"``; use ``"fwd_pre_kwargs"`` to expose ``(args, kwargs)`` as the hook value. Gradient inputs and outputs use ``"bwd"`` and ``"bwd_pre"`` - respectively. + respectively. By default captures are detached clones. Set + ``detach=False`` when a later attribution objective must backpropagate + from the captured activation; the result then retains its autograd + history and is only valid for the lifetime of the surrounding graph. """ model, builder = self._active_state() @@ -118,32 +122,32 @@ def capture( if target.kind == "parameter": parameter = module.get_parameter(target.parameter) # type: ignore[arg-type] - captured._record(target._select(parameter).detach().clone()) + captured._record(self._captured_value(target._select(parameter), detach=detach)) builder.record(spec) else: def forward_hook(_module: nn.Module, _args: tuple[object, ...], value: object): - captured._record(target.select_output(value).detach().clone()) + captured._record(self._captured_value(target.select_output(value), detach=detach)) def forward_pre_hook(_module: nn.Module, args: tuple[object, ...]): - captured._record(target.select_output(args).detach().clone()) + captured._record(self._captured_value(target.select_output(args), detach=detach)) def forward_pre_kwargs_hook( _module: nn.Module, args: tuple[object, ...], kwargs: dict[str, object], ): - captured._record(target.select_output((args, kwargs)).detach().clone()) + captured._record(self._captured_value(target.select_output((args, kwargs)), detach=detach)) def backward_hook( _module: nn.Module, grad_input: tuple[Tensor | None, ...], _grad_output: tuple[Tensor | None, ...], ): - captured._record(target.select_output(grad_input).detach().clone()) + captured._record(self._captured_value(target.select_output(grad_input), detach=detach)) def backward_pre_hook(_module: nn.Module, values: tuple[Tensor | None, ...]): - captured._record(target.select_output(values).detach().clone()) + captured._record(self._captured_value(target.select_output(values), detach=detach)) hooks = { "fwd": forward_hook, @@ -159,6 +163,10 @@ def backward_pre_hook(_module: nn.Module, values: tuple[Tensor | None, ...]): return captured + @staticmethod + def _captured_value(value: Tensor, *, detach: bool) -> Tensor: + return value.detach().clone() if detach else value + def replace( self, target: Target, diff --git a/tests/attribution/test_circuit_lens.py b/tests/attribution/test_circuit_lens.py new file mode 100644 index 0000000..3412cee --- /dev/null +++ b/tests/attribution/test_circuit_lens.py @@ -0,0 +1,432 @@ +import json + +import pytest +import torch +from torch import nn + +from tdhook.attribution import ( + AttentionSite, + FeatureSite, + attention_contributions, + attribute_feature_circuit, + feature_contributions, + logit_contributions, +) +from tdhook.targets import Target + + +class TargetFeature(nn.Module): + def forward(self, features, attention): + return features @ features.new_tensor([[2.0], [-1.0]]) + attention[:, 0] + + +class ToyCircuit(nn.Module): + def __init__(self): + super().__init__() + self.upstream = nn.Identity() + self.pattern_hook = nn.Identity() + self.values_hook = nn.Identity() + self.attention_output = nn.Identity() + self.target_feature = TargetFeature() + self.logits = nn.Linear(1, 2, bias=False) + self.register_buffer("pattern", torch.tensor([[[[0.25, 0.75]]]])) + self.register_buffer("values", torch.tensor([[[[2.0]], [[4.0]]]])) + self.output_weight = nn.Parameter(torch.tensor([[[2.0]]])) + with torch.no_grad(): + self.logits.weight.copy_(torch.tensor([[2.0], [-1.0]])) + + def forward(self, inputs): + features = self.upstream(inputs) + pattern = self.pattern_hook(self.pattern) + values = self.values_hook(self.values) + attention = torch.einsum("bhqs,bshd,hdm->bqm", pattern, values, self.output_weight) + attention = self.attention_output(attention) + feature = self.target_feature(features, attention) + return self.logits(feature)[0] + + +class ReusedUpstreamCircuit(ToyCircuit): + def forward(self, inputs): + features = self.upstream(inputs) + features = self.upstream(features) + pattern = self.pattern_hook(self.pattern) + values = self.values_hook(self.values) + attention = torch.einsum("bhqs,bshd,hdm->bqm", pattern, values, self.output_weight) + feature = self.target_feature(features, self.attention_output(attention)) + return self.logits(feature)[0] + + +class UnreachedTargetCircuit(ToyCircuit): + def forward(self, inputs): + return self.logits(inputs[:, :1])[0] + + +class DisconnectedLogitsCircuit(ToyCircuit): + def __init__(self): + super().__init__() + self.disconnected_logits = nn.Parameter(torch.tensor([1.0, 2.0])) + + def forward(self, inputs): + features = self.upstream(inputs) + pattern = self.pattern_hook(self.pattern) + values = self.values_hook(self.values) + attention = torch.einsum("bhqs,bshd,hdm->bqm", pattern, values, self.output_weight) + self.target_feature(features, self.attention_output(attention)) + return self.disconnected_logits + + +def target_site(*, indices=(0,), position=(0,)): + return FeatureSite(1, Target("target_feature", "activation", -1, indices), position=position) + + +def upstream_site(): + return FeatureSite(0, Target("upstream", "activation", -1, (0, 1))) + + +def attention_site(model, *, pattern_heads=(0,), value_heads=(0,), target_position=0): + return AttentionSite( + layer=0, + pattern=Target("pattern_hook", "activation", 1, pattern_heads), + values=Target("values_hook", "activation", 2, value_heads), + output_gradient=Target("attention_output", "gradient", -1, (0,)), + output_weight=model.output_weight, + target_position=target_position, + ) + + +def test_analytical_toy_circuit_verifies_all_three_attribution_paths(): + model = ToyCircuit() + model.logits.weight.grad = torch.full_like(model.logits.weight, 7.0) + original_parameter_grad = model.logits.weight.grad + inputs = torch.tensor([[2.0, 1.0]], requires_grad=True) + inputs.grad = torch.tensor([[5.0, 6.0]]) + original_input_grad = inputs.grad + + artifact = attribute_feature_circuit( + model, + inputs, + target_feature=target_site(), + upstream_features=(upstream_site(),), + attention_sites=(attention_site(model),), + output_logits=lambda output: output, + logit_indices=(0, 1), + ) + + assert artifact.target_activation == pytest.approx(10.0) + assert [(item.feature_index, item.position, item.score) for item in artifact.upstream_features] == [ + (0, (0,), 4.0), + (1, (0,), -1.0), + ] + assert [(item.source_token, item.score) for item in artifact.attention] == [(1, 6.0), (0, 1.0)] + assert [(item.token_index, item.score) for item in artifact.output_logits] == [(0, 20.0), (1, -10.0)] + assert "local autograd Jacobian" in artifact.conventions.jacobian + assert "frozen" in artifact.conventions.attention + assert "frozen" in artifact.conventions.nonlinearities + assert model.logits.weight.grad is not None + assert model.logits.weight.grad is original_parameter_grad + assert inputs.grad is original_input_grad + torch.testing.assert_close(model.logits.weight.grad, torch.full_like(model.logits.weight, 7.0)) + torch.testing.assert_close(inputs.grad, torch.tensor([[5.0, 6.0]])) + json.dumps(artifact.to_dict(), sort_keys=True) + + +def test_tensor_attribution_helpers_match_closed_form_scores(): + features = feature_contributions( + torch.tensor([[2.0, 1.0]]), + torch.tensor([[2.0, -1.0]]), + layer=0, + feature_indices=(4, 7), + ) + attention = attention_contributions( + torch.tensor([[[0.25, 0.75]]]), + torch.tensor([[[2.0]], [[4.0]]]), + torch.tensor([[[2.0]]]), + torch.tensor([[1.0]]), + layer=0, + target_position=0, + ) + logits = logit_contributions(10.0, torch.tensor([2.0, -1.0]), token_indices=(3, 9)) + + assert [item.score for item in features] == [4.0, -1.0] + assert [item.score for item in attention] == [1.0, 6.0] + assert [item.score for item in logits] == [20.0, -10.0] + + +def test_attribution_helpers_reject_incompatible_shapes(): + with pytest.raises(ValueError, match="same shape"): + feature_contributions(torch.ones(2), torch.ones(3), layer=0, feature_indices=(0, 1)) + with pytest.raises(ValueError, match="values must have shape"): + attention_contributions( + torch.ones(1, 1, 2), + torch.ones(3, 1, 1), + torch.ones(1, 1, 1), + torch.ones(1, 1), + layer=0, + target_position=0, + ) + with pytest.raises(ValueError, match="one index"): + logit_contributions(1.0, torch.ones(2), token_indices=(0,)) + + +def test_workflow_rejects_reused_hook_sites_instead_of_mispairing_captures(): + model = ReusedUpstreamCircuit() + + with pytest.raises(RuntimeError, match="upstream activation.*exactly once.*2 captures"): + attribute_feature_circuit( + model, + torch.tensor([[2.0, 1.0]], requires_grad=True), + target_feature=target_site(), + upstream_features=(upstream_site(),), + ) + + +def test_attention_site_requires_matching_head_selections(): + model = ToyCircuit() + + with pytest.raises(ValueError, match="same heads in the same order"): + attention_site(model, pattern_heads=(0,), value_heads=(1,)) + + +def test_attention_helper_rejects_negative_target_positions(): + with pytest.raises(IndexError, match="target_position"): + attention_contributions( + torch.ones(1, 1, 1), + torch.ones(1, 1, 1), + torch.ones(1, 1, 1), + torch.ones(1, 1), + layer=0, + target_position=-1, + ) + + +def test_workflow_filters_and_limits_ranked_artifacts_without_logits(): + artifact = attribute_feature_circuit( + ToyCircuit(), + torch.tensor([[2.0, 1.0]], requires_grad=True), + target_feature=target_site(), + upstream_features=(upstream_site(),), + top_k=1, + positive_only=True, + ) + + assert [(item.feature_index, item.score) for item in artifact.upstream_features] == [(0, 4.0)] + assert artifact.output_logits == () + + +def test_workflow_infers_the_only_non_feature_position(): + artifact = attribute_feature_circuit( + ToyCircuit(), + torch.tensor([[2.0, 1.0]], requires_grad=True), + target_feature=target_site(position=None), + ) + + assert artifact.target_position == (0,) + + +def test_workflow_restores_leaf_gradients_passed_in_keyword_mappings(): + inputs = torch.tensor([[2.0, 1.0]], requires_grad=True) + inputs.grad = torch.tensor([[8.0, 9.0]]) + + attribute_feature_circuit( + ToyCircuit(), + target_feature=target_site(), + model_kwargs={"inputs": inputs}, + ) + + torch.testing.assert_close(inputs.grad, torch.tensor([[8.0, 9.0]])) + + +@pytest.mark.parametrize( + ("factory", "message"), + [ + (lambda: FeatureSite(-1, Target("upstream", "activation", -1, (0,))), "layer"), + (lambda: FeatureSite(0, Target("upstream", "gradient", -1, (0,))), "activation target"), + (lambda: FeatureSite(0, Target("upstream", "activation", -1, (0,)), (-1,)), "non-negative"), + ], +) +def test_feature_site_rejects_invalid_metadata(factory, message): + with pytest.raises(ValueError, match=message): + factory() + + +@pytest.mark.parametrize( + ("overrides", "message"), + [ + ({"layer": -1}, "layer"), + ({"target_position": -1}, "target_position"), + ({"pattern": Target("pattern_hook", "gradient", 1, (0,))}, "activation targets"), + ({"output_gradient": Target("attention_output", "activation", -1, (0,))}, "gradient target"), + ], +) +def test_attention_site_rejects_invalid_metadata(overrides, message): + model = ToyCircuit() + arguments = { + "layer": 0, + "pattern": Target("pattern_hook", "activation", 1, (0,)), + "values": Target("values_hook", "activation", 2, (0,)), + "output_gradient": Target("attention_output", "gradient", -1, (0,)), + "output_weight": model.output_weight, + "target_position": 0, + } + arguments.update(overrides) + + with pytest.raises(ValueError, match=message): + AttentionSite(**arguments) + + +@pytest.mark.parametrize( + ("call", "message"), + [ + ( + lambda: feature_contributions(torch.ones(2), torch.ones(2), layer=-1, feature_indices=(0, 1)), + "layer", + ), + ( + lambda: attention_contributions( + torch.ones(1, 1, 1), + torch.ones(1, 1, 1), + torch.ones(1, 1, 1), + torch.ones(1, 1), + layer=-1, + target_position=0, + ), + "layer", + ), + ( + lambda: feature_contributions(torch.ones(2), torch.ones(2), layer=0, feature_indices=(0,)), + "feature_indices", + ), + ( + lambda: feature_contributions( + torch.ones(2), torch.ones(2), layer=0, feature_indices=(0, 1), feature_axis=1 + ), + "feature_axis", + ), + ( + lambda: attention_contributions( + torch.ones(1, 1, 1), + torch.ones(1, 1, 1), + torch.ones(1, 1), + torch.ones(1, 1), + layer=0, + target_position=0, + ), + "output_weight", + ), + ( + lambda: attention_contributions( + torch.ones(1, 1, 1), + torch.ones(1, 1, 2), + torch.ones(1, 1, 1), + torch.ones(1, 1), + layer=0, + target_position=0, + ), + "head and head_dim", + ), + ( + lambda: attention_contributions( + torch.ones(1, 1, 1), + torch.ones(1, 1, 1), + torch.ones(1, 1, 2), + torch.ones(1, 1), + layer=0, + target_position=0, + ), + "output_gradient", + ), + ( + lambda: attention_contributions( + torch.ones(1, 1, 1), + torch.ones(1, 1, 1), + torch.ones(1, 1, 1), + torch.ones(1, 1), + layer=0, + target_position=0, + head_indices=(0, 1), + ), + "head_indices", + ), + ( + lambda: attention_contributions( + torch.ones(2, 1, 1, 1), + torch.ones(1, 1, 1), + torch.ones(1, 1, 1), + torch.ones(1, 1), + layer=0, + target_position=0, + ), + "singleton batch", + ), + ( + lambda: attention_contributions( + torch.ones(1, 1), + torch.ones(1, 1, 1), + torch.ones(1, 1, 1), + torch.ones(1, 1), + layer=0, + target_position=0, + ), + "3 dimensions", + ), + (lambda: logit_contributions(torch.ones(2), torch.ones(1), token_indices=(0,)), "one scalar"), + (lambda: logit_contributions(1.0, torch.ones(1), token_indices=(-1,)), "non-negative"), + ], +) +def test_tensor_helpers_reject_invalid_contracts(call, message): + with pytest.raises((ValueError, IndexError), match=message): + call() + + +@pytest.mark.parametrize( + ("model", "kwargs", "error", "message"), + [ + (object(), {}, TypeError, "torch.nn.Module"), + (ToyCircuit(), {"target_feature": target_site(indices=(0, 1))}, ValueError, "exactly one feature"), + (ToyCircuit(), {"top_k": 0}, ValueError, "top_k"), + (ToyCircuit(), {"logit_indices": (0,)}, ValueError, "output_logits"), + ( + ToyCircuit(), + {"output_logits": lambda output: output, "logit_indices": (-1,)}, + ValueError, + "non-negative", + ), + ( + ToyCircuit(), + {"output_logits": lambda output: output[0], "logit_indices": (0,)}, + ValueError, + "one-dimensional", + ), + ( + ToyCircuit(), + {"output_logits": lambda output: output, "logit_indices": (2,)}, + IndexError, + "out of bounds", + ), + (UnreachedTargetCircuit(), {}, RuntimeError, "target feature activation.*observed 0"), + ( + DisconnectedLogitsCircuit(), + {"output_logits": lambda output: output, "logit_indices": (0,)}, + RuntimeError, + "expected 1, observed 0", + ), + ], +) +def test_workflow_rejects_invalid_contracts(model, kwargs, error, message): + arguments = {"target_feature": target_site(), **kwargs} + + with pytest.raises(error, match=message): + attribute_feature_circuit(model, torch.tensor([[2.0, 1.0]], requires_grad=True), **arguments) + + +@pytest.mark.parametrize( + ("inputs", "position", "message"), + [ + (torch.tensor([[2.0, 1.0], [3.0, 1.0]], requires_grad=True), None, "position is required"), + (torch.tensor([[2.0, 1.0]], requires_grad=True), (), "index every non-feature axis"), + (torch.tensor([[2.0, 1.0]], requires_grad=True), (1,), "outside"), + ], +) +def test_workflow_validates_target_position(inputs, position, message): + with pytest.raises((ValueError, IndexError), match=message): + attribute_feature_circuit(ToyCircuit(), inputs, target_feature=target_site(position=position)) diff --git a/uv.lock b/uv.lock index 60698b0..e2d5cc8 100644 --- a/uv.lock +++ b/uv.lock @@ -157,6 +157,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, ] +[[package]] +name = "anyio" +version = "4.14.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/61/cc/a381afa6efea9f496eff839d4a6a1aed3bfafc7b3ab4b0d1b243a12573dd/anyio-4.14.2.tar.gz", hash = "sha256:cfa139f3ed1a23ee8f88a145ddb5ac7605b8bbfd8592baacd7ce3d8bb4313c7f", size = 260176, upload-time = "2026-07-12T20:29:07.082Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/da/35/f2287558c17e29fafc8ef3daf819bb9834061cfa43bff8014f7df7f63bdc/anyio-4.14.2-py3-none-any.whl", hash = "sha256:9f505dda5ac9f0c8309b5e8bd445a8c2bf7246f3ce950121e45ea15bc41d1494", size = 125813, upload-time = "2026-07-12T20:29:05.763Z" }, +] + [[package]] name = "appnope" version = "0.1.4" @@ -166,6 +179,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl", hash = "sha256:502575ee11cd7a28c0205f379b525beefebab9d161b7c964670864014ed7213c", size = 4321, upload-time = "2024-02-06T09:43:09.663Z" }, ] +[[package]] +name = "astor" +version = "0.8.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/21/75b771132fee241dfe601d39ade629548a9626d1d39f333fde31bc46febe/astor-0.8.1.tar.gz", hash = "sha256:6a6effda93f4e1ce9f618779b2dd1d9d84f1e32812c23a29b3fff6fd7f63fa5e", size = 35090, upload-time = "2019-12-10T01:50:35.51Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c3/88/97eef84f48fa04fbd6750e62dcceafba6c63c81b7ac1420856c8dcc0a3f9/astor-0.8.1-py2.py3-none-any.whl", hash = "sha256:070a54e890cefb5b3739d19f30f5a5ec840ffc9c50ffa7d23cc9fc1a38ebbfc5", size = 27488, upload-time = "2019-12-10T01:50:33.628Z" }, +] + [[package]] name = "astroid" version = "3.3.11" @@ -392,6 +414,28 @@ version = "1.11.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/93/09/7d04d7581ae3bb8b598017941781bceb7959dd1b13e3ebf7b6a2cd843bc9/chess-1.11.2.tar.gz", hash = "sha256:a8b43e5678fdb3000695bdaa573117ad683761e5ca38e591c4826eba6d25bb39", size = 6131385, upload-time = "2025-02-25T19:10:27.328Z" } +[[package]] +name = "circuit-tracer" +version = "0.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "einops" }, + { name = "huggingface-hub" }, + { name = "nnsight" }, + { name = "numpy" }, + { name = "pydantic" }, + { name = "safetensors" }, + { name = "tokenizers" }, + { name = "torch" }, + { name = "tqdm" }, + { name = "transformer-lens" }, + { name = "transformers" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/77/6c/bab9adcbead5a621666d97755ecc7253d853dcc300cc0aeadb01efaad917/circuit_tracer-0.5.0.tar.gz", hash = "sha256:bcf5a045185f389d8bc7d22e056e8448ee32fb9d0d46b2f919bf6c7ef4c70cc8", size = 123639, upload-time = "2026-03-29T02:27:13.825Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/e8/100af8e8738be19d1edc50b4bdf26083e2489e52b9eb9bfc38488b0ef151/circuit_tracer-0.5.0-py3-none-any.whl", hash = "sha256:e457e6558e1c9eb0d3d7c54ce2298492b4e919a4f7f2b8b7e217427284fdc802", size = 153157, upload-time = "2026-03-29T02:27:12.636Z" }, +] + [[package]] name = "click" version = "8.2.1" @@ -665,25 +709,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61", size = 25604, upload-time = "2021-03-08T10:59:24.45Z" }, ] -[[package]] -name = "diffusers" -version = "0.34.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "filelock" }, - { name = "huggingface-hub" }, - { name = "importlib-metadata" }, - { name = "numpy" }, - { name = "pillow" }, - { name = "regex" }, - { name = "requests" }, - { name = "safetensors" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/34/01/eee276cb1ffa1528d3fac8c8382c32d0deef7f089baeefbee254bbbc0a8f/diffusers-0.34.0.tar.gz", hash = "sha256:25d84e779781fb8a78de22ea0f732aac32b619c65548a04e520d0b55e29a54e7", size = 3083860, upload-time = "2025-06-24T14:56:57.438Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f8/e0/d5af850081d479e5bb6f6f310e98e1e2ea6cce9e5d67e2b7978d5690497e/diffusers-0.34.0-py3-none-any.whl", hash = "sha256:b0f642cd57756357bad5d23fe95b61f2e6e30321c93f1302cca6d832a01e6d33", size = 3774402, upload-time = "2025-06-24T14:56:55.089Z" }, -] - [[package]] name = "dill" version = "0.3.8" @@ -992,6 +1017,34 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f0/55/ef77a85ee443ae05a9e9cba1c9f0dd9241eb42da2aeba1dc50f51154c81a/hf_xet-1.1.5-cp37-abi3-win_amd64.whl", hash = "sha256:73e167d9807d166596b4b2f0b585c6d5bd84a26dea32843665a8b58f6edba245", size = 2738931, upload-time = "2025-06-20T21:48:39.482Z" }, ] +[[package]] +name = "httpcore" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, +] + +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, +] + [[package]] name = "huggingface-hub" version = "0.34.3" @@ -1537,35 +1590,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", size = 536198, upload-time = "2023-03-07T16:47:09.197Z" }, ] -[[package]] -name = "msgspec" -version = "0.19.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cf/9b/95d8ce458462b8b71b8a70fa94563b2498b89933689f3a7b8911edfae3d7/msgspec-0.19.0.tar.gz", hash = "sha256:604037e7cd475345848116e89c553aa9a233259733ab51986ac924ab1b976f8e", size = 216934, upload-time = "2024-12-27T17:40:28.597Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/24/d4/2ec2567ac30dab072cce3e91fb17803c52f0a37aab6b0c24375d2b20a581/msgspec-0.19.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aa77046904db764b0462036bc63ef71f02b75b8f72e9c9dd4c447d6da1ed8f8e", size = 187939, upload-time = "2024-12-27T17:39:32.347Z" }, - { url = "https://files.pythonhosted.org/packages/2b/c0/18226e4328897f4f19875cb62bb9259fe47e901eade9d9376ab5f251a929/msgspec-0.19.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:047cfa8675eb3bad68722cfe95c60e7afabf84d1bd8938979dd2b92e9e4a9551", size = 182202, upload-time = "2024-12-27T17:39:33.633Z" }, - { url = "https://files.pythonhosted.org/packages/81/25/3a4b24d468203d8af90d1d351b77ea3cffb96b29492855cf83078f16bfe4/msgspec-0.19.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e78f46ff39a427e10b4a61614a2777ad69559cc8d603a7c05681f5a595ea98f7", size = 209029, upload-time = "2024-12-27T17:39:35.023Z" }, - { url = "https://files.pythonhosted.org/packages/85/2e/db7e189b57901955239f7689b5dcd6ae9458637a9c66747326726c650523/msgspec-0.19.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c7adf191e4bd3be0e9231c3b6dc20cf1199ada2af523885efc2ed218eafd011", size = 210682, upload-time = "2024-12-27T17:39:36.384Z" }, - { url = "https://files.pythonhosted.org/packages/03/97/7c8895c9074a97052d7e4a1cc1230b7b6e2ca2486714eb12c3f08bb9d284/msgspec-0.19.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f04cad4385e20be7c7176bb8ae3dca54a08e9756cfc97bcdb4f18560c3042063", size = 214003, upload-time = "2024-12-27T17:39:39.097Z" }, - { url = "https://files.pythonhosted.org/packages/61/61/e892997bcaa289559b4d5869f066a8021b79f4bf8e955f831b095f47a4cd/msgspec-0.19.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:45c8fb410670b3b7eb884d44a75589377c341ec1392b778311acdbfa55187716", size = 216833, upload-time = "2024-12-27T17:39:41.203Z" }, - { url = "https://files.pythonhosted.org/packages/ce/3d/71b2dffd3a1c743ffe13296ff701ee503feaebc3f04d0e75613b6563c374/msgspec-0.19.0-cp311-cp311-win_amd64.whl", hash = "sha256:70eaef4934b87193a27d802534dc466778ad8d536e296ae2f9334e182ac27b6c", size = 186184, upload-time = "2024-12-27T17:39:43.702Z" }, - { url = "https://files.pythonhosted.org/packages/b2/5f/a70c24f075e3e7af2fae5414c7048b0e11389685b7f717bb55ba282a34a7/msgspec-0.19.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f98bd8962ad549c27d63845b50af3f53ec468b6318400c9f1adfe8b092d7b62f", size = 190485, upload-time = "2024-12-27T17:39:44.974Z" }, - { url = "https://files.pythonhosted.org/packages/89/b0/1b9763938cfae12acf14b682fcf05c92855974d921a5a985ecc197d1c672/msgspec-0.19.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:43bbb237feab761b815ed9df43b266114203f53596f9b6e6f00ebd79d178cdf2", size = 183910, upload-time = "2024-12-27T17:39:46.401Z" }, - { url = "https://files.pythonhosted.org/packages/87/81/0c8c93f0b92c97e326b279795f9c5b956c5a97af28ca0fbb9fd86c83737a/msgspec-0.19.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4cfc033c02c3e0aec52b71710d7f84cb3ca5eb407ab2ad23d75631153fdb1f12", size = 210633, upload-time = "2024-12-27T17:39:49.099Z" }, - { url = "https://files.pythonhosted.org/packages/d0/ef/c5422ce8af73928d194a6606f8ae36e93a52fd5e8df5abd366903a5ca8da/msgspec-0.19.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d911c442571605e17658ca2b416fd8579c5050ac9adc5e00c2cb3126c97f73bc", size = 213594, upload-time = "2024-12-27T17:39:51.204Z" }, - { url = "https://files.pythonhosted.org/packages/19/2b/4137bc2ed45660444842d042be2cf5b18aa06efd2cda107cff18253b9653/msgspec-0.19.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:757b501fa57e24896cf40a831442b19a864f56d253679f34f260dcb002524a6c", size = 214053, upload-time = "2024-12-27T17:39:52.866Z" }, - { url = "https://files.pythonhosted.org/packages/9d/e6/8ad51bdc806aac1dc501e8fe43f759f9ed7284043d722b53323ea421c360/msgspec-0.19.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5f0f65f29b45e2816d8bded36e6b837a4bf5fb60ec4bc3c625fa2c6da4124537", size = 219081, upload-time = "2024-12-27T17:39:55.142Z" }, - { url = "https://files.pythonhosted.org/packages/b1/ef/27dd35a7049c9a4f4211c6cd6a8c9db0a50647546f003a5867827ec45391/msgspec-0.19.0-cp312-cp312-win_amd64.whl", hash = "sha256:067f0de1c33cfa0b6a8206562efdf6be5985b988b53dd244a8e06f993f27c8c0", size = 187467, upload-time = "2024-12-27T17:39:56.531Z" }, - { url = "https://files.pythonhosted.org/packages/3c/cb/2842c312bbe618d8fefc8b9cedce37f773cdc8fa453306546dba2c21fd98/msgspec-0.19.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f12d30dd6266557aaaf0aa0f9580a9a8fbeadfa83699c487713e355ec5f0bd86", size = 190498, upload-time = "2024-12-27T17:40:00.427Z" }, - { url = "https://files.pythonhosted.org/packages/58/95/c40b01b93465e1a5f3b6c7d91b10fb574818163740cc3acbe722d1e0e7e4/msgspec-0.19.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82b2c42c1b9ebc89e822e7e13bbe9d17ede0c23c187469fdd9505afd5a481314", size = 183950, upload-time = "2024-12-27T17:40:04.219Z" }, - { url = "https://files.pythonhosted.org/packages/e8/f0/5b764e066ce9aba4b70d1db8b087ea66098c7c27d59b9dd8a3532774d48f/msgspec-0.19.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19746b50be214a54239aab822964f2ac81e38b0055cca94808359d779338c10e", size = 210647, upload-time = "2024-12-27T17:40:05.606Z" }, - { url = "https://files.pythonhosted.org/packages/9d/87/bc14f49bc95c4cb0dd0a8c56028a67c014ee7e6818ccdce74a4862af259b/msgspec-0.19.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:60ef4bdb0ec8e4ad62e5a1f95230c08efb1f64f32e6e8dd2ced685bcc73858b5", size = 213563, upload-time = "2024-12-27T17:40:10.516Z" }, - { url = "https://files.pythonhosted.org/packages/53/2f/2b1c2b056894fbaa975f68f81e3014bb447516a8b010f1bed3fb0e016ed7/msgspec-0.19.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac7f7c377c122b649f7545810c6cd1b47586e3aa3059126ce3516ac7ccc6a6a9", size = 213996, upload-time = "2024-12-27T17:40:12.244Z" }, - { url = "https://files.pythonhosted.org/packages/aa/5a/4cd408d90d1417e8d2ce6a22b98a6853c1b4d7cb7669153e4424d60087f6/msgspec-0.19.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a5bc1472223a643f5ffb5bf46ccdede7f9795078194f14edd69e3aab7020d327", size = 219087, upload-time = "2024-12-27T17:40:14.881Z" }, - { url = "https://files.pythonhosted.org/packages/23/d8/f15b40611c2d5753d1abb0ca0da0c75348daf1252220e5dda2867bd81062/msgspec-0.19.0-cp313-cp313-win_amd64.whl", hash = "sha256:317050bc0f7739cb30d257ff09152ca309bf5a369854bbf1e57dffc310c1f20f", size = 187432, upload-time = "2024-12-27T17:40:16.256Z" }, -] - [[package]] name = "mujoco" version = "3.3.5" @@ -1794,27 +1818,44 @@ wheels = [ [[package]] name = "nnsight" -version = "0.4.11" +version = "0.7.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "accelerate" }, - { name = "diffusers" }, - { name = "einops" }, + { name = "astor" }, + { name = "cloudpickle" }, + { name = "httpx" }, { name = "ipython" }, - { name = "msgspec" }, - { name = "protobuf" }, { name = "pydantic" }, { name = "python-socketio", extra = ["client"] }, - { name = "sentencepiece" }, - { name = "tokenizers" }, + { name = "rich" }, { name = "toml" }, { name = "torch" }, - { name = "torchvision" }, { name = "transformers" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/3e/96/180ec669059b11e3043d1e6de02400af281a961c34a7e26602fa35150fed/nnsight-0.4.11.tar.gz", hash = "sha256:887acfdad2c92a1babe26503261d1e0b67fa4cc5357f4b2e29099c6da199f5cb", size = 16592500, upload-time = "2025-07-26T22:16:23.904Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f7/35/d3036058eb018d2eb31bfef4a42edfca55fafc31b07f49f4263f68536359/nnsight-0.4.11-py3-none-any.whl", hash = "sha256:ece485639955463762d4eea0708c27647414784a4845e1a0ab9ffb63ca687d9b", size = 103406, upload-time = "2025-07-26T22:16:22.489Z" }, + { name = "zstandard" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a6/9e/76fd632deef926599d3d16c02fd736cf5f8465f49d708d5be13cd6638484/nnsight-0.7.0.tar.gz", hash = "sha256:5bc6678d567ecc5590b823b7bbab2c310c69f8dda6f4064684c6d488563eebee", size = 1912851, upload-time = "2026-05-05T05:40:50.084Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d5/e7/4d8f94663bbe3a15c92c8105b1d6985c02e237497ad632637a4c2b2f5025/nnsight-0.7.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3bc66c4e3ad084bb069bcb9eaa463f4c7b0c1fc56016c13387b4313f1800f69e", size = 264943, upload-time = "2026-05-05T05:40:25.795Z" }, + { url = "https://files.pythonhosted.org/packages/6d/ee/2204233151a6df0d37af23096250d4cc5ecbebb27b082110076164a1c4d6/nnsight-0.7.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:318da7f3cd1e6b93145c2dbb430837446026be626cf511b8ee8c491da7d803fb", size = 272101, upload-time = "2026-05-05T05:40:27.434Z" }, + { url = "https://files.pythonhosted.org/packages/dd/3a/78bb189bf2cd67192e92e2f09b19cb5b277e86f5ebe418965362ce919da6/nnsight-0.7.0-cp311-cp311-win32.whl", hash = "sha256:33e14c52c6f9ce3f07a1709838fd51243660a2a07a18d32c857708a66fa60609", size = 267064, upload-time = "2026-05-05T05:40:28.384Z" }, + { url = "https://files.pythonhosted.org/packages/40/66/923f089e3dc432bf51a272edeaf36ea535d9f344cd5c9a163b70d19e19ff/nnsight-0.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:933bf1d5a3f22dd930184c63989fea25bccd475e5db4df420dc1766950c4963f", size = 267457, upload-time = "2026-05-05T05:40:29.307Z" }, + { url = "https://files.pythonhosted.org/packages/a8/4d/634c1f08e5d34d9d145f81a5b872c5d2a2fcae714c77450e4f14660670b8/nnsight-0.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d988fd477abcc15d413012aa1b6eaef667da81b2d61c86c528b2a154adf259c5", size = 265015, upload-time = "2026-05-05T05:40:30.453Z" }, + { url = "https://files.pythonhosted.org/packages/f5/fa/aee0a17356528f6cf4e0deaf3af2f0a88f660f793fb1fbb10f363682ef6a/nnsight-0.7.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:000f5285a754d63fafc0a677e4518192f16c5fa3f2af347a46e2f27c793ef2b2", size = 272487, upload-time = "2026-05-05T05:40:31.545Z" }, + { url = "https://files.pythonhosted.org/packages/85/77/c59d1e983936e77b2d9fcd4694461f78c66d0af95982b42ffc365fa0c224/nnsight-0.7.0-cp312-cp312-win32.whl", hash = "sha256:2601ccc8a352c09f6f17576ea07943ed29eb3c8d1bba2ec174fbb554942a3821", size = 267080, upload-time = "2026-05-05T05:40:32.909Z" }, + { url = "https://files.pythonhosted.org/packages/f1/1b/d3d60dfbbd1167320ec7f02cf06f4c589a3d2d462c3748f45bba7eaa17eb/nnsight-0.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:408e2ae8b0da3af0b71f3c8dbb2301b671801205f7a3f95ede7f3f6672c5a8a5", size = 267491, upload-time = "2026-05-05T05:40:34.469Z" }, + { url = "https://files.pythonhosted.org/packages/d3/dd/2e2f800876f00a0f38e7ef5e536c53bacf9ac7775efc8b337ba117a4459c/nnsight-0.7.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:753acc23dcc97ed32bfc3f650400f049e94621dc56cd9a372dbbfef868f98753", size = 265005, upload-time = "2026-05-05T05:40:36.066Z" }, + { url = "https://files.pythonhosted.org/packages/0a/e0/335c94482b3c739994cdabaef02a12458c806cbe7ca5883d03380f6fdf8a/nnsight-0.7.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3b59ff8a2d41ed482313fa21f62e5a035068bd68828201cccb928c6e0ded2b4f", size = 272540, upload-time = "2026-05-05T05:40:37.293Z" }, + { url = "https://files.pythonhosted.org/packages/93/18/65f473ae3147156dec3e6d30b5fa386491474964db1af24f478ef467718d/nnsight-0.7.0-cp313-cp313-win32.whl", hash = "sha256:ed4c01b7cc882e3699de56f1ee77cedd7f720797bb3670a690ec1edeeb34f9bb", size = 267067, upload-time = "2026-05-05T05:40:38.35Z" }, + { url = "https://files.pythonhosted.org/packages/67/0f/eb2a6cdff12abcd7264b6e9992c9fe907cfd4c445ea3df1b3aa4165061ff/nnsight-0.7.0-cp313-cp313-win_amd64.whl", hash = "sha256:8cd6b5aac59175a6c436fb4fca713a6a58fc85407f9b4f938a819a467c17108e", size = 267492, upload-time = "2026-05-05T05:40:39.66Z" }, + { url = "https://files.pythonhosted.org/packages/ba/76/6148cd42a1323d218c64ed122f4da775233fb2e44edef0341fd8aa976c7e/nnsight-0.7.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:baaa6409717a8e95cadf7d71fbb376103524f9137a0cc0d8ea55c2656add4326", size = 265011, upload-time = "2026-05-05T05:40:40.693Z" }, + { url = "https://files.pythonhosted.org/packages/29/a7/b1ba47acbe95fafc854170390d29ef3dd0bdaac44576f1554f25e53c8309/nnsight-0.7.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:22f172565dab55583fb2eb0d615697af76e2c22f12644309738bf7174090ccc6", size = 272569, upload-time = "2026-05-05T05:40:42.003Z" }, + { url = "https://files.pythonhosted.org/packages/d1/38/05845f3fc29631450efa5d9b8e17aaff37d2ae4e6eee8f8e38ce0ba5b162/nnsight-0.7.0-cp314-cp314-win32.whl", hash = "sha256:6b360febd90a1c5330c7e881a11aaa51f76f04e4876fe045c7f0481ded9e0398", size = 267182, upload-time = "2026-05-05T05:40:43.146Z" }, + { url = "https://files.pythonhosted.org/packages/ef/00/9300ab917f759d2e4cf2c4ceb23b76827fe5e77379c1933f6b67c0bb680c/nnsight-0.7.0-cp314-cp314-win_amd64.whl", hash = "sha256:65a894add8c93b38d781affac4bf10a58050ed3e46497b2038febf9d8633ce21", size = 267606, upload-time = "2026-05-05T05:40:44.303Z" }, + { url = "https://files.pythonhosted.org/packages/81/36/1f698a62ae01187dd6c4e63a561220ec7999ad489fea796df3733d700d36/nnsight-0.7.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:171a0a1a13f3f8ea8ddc06c98eb3f924c7a8dfcc108be1407c7c071504f0370f", size = 265117, upload-time = "2026-05-05T05:40:45.411Z" }, + { url = "https://files.pythonhosted.org/packages/ec/2e/0df50b4b98896b02fc76dd08997aab977eef96e0fb3a2646c5c695a3389f/nnsight-0.7.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:eeed67fa7d8ef03a1196533ec341dbe1dfb40bb7615c8dee06a22009eba1be0f", size = 273366, upload-time = "2026-05-05T05:40:46.648Z" }, + { url = "https://files.pythonhosted.org/packages/7f/da/f47e1102a9216dc487608ce5b7bc058afb0ea9f3f087e85f83ed877d156d/nnsight-0.7.0-cp314-cp314t-win32.whl", hash = "sha256:7140bf2cab8d4bff46e8f6a1a942185fcdf8f18f62ddb173ca7fe000b6e580cc", size = 267290, upload-time = "2026-05-05T05:40:47.956Z" }, + { url = "https://files.pythonhosted.org/packages/bf/c9/749370592057f790fc60f2542848b09542ca8eb75b4583e1dde01e7e40c9/nnsight-0.7.0-cp314-cp314t-win_amd64.whl", hash = "sha256:544f7e32e277738ca10e85cdd3f07d8965ec3388854fdf14373308580f8fd0f4", size = 267722, upload-time = "2026-05-05T05:40:49.036Z" }, ] [[package]] @@ -3604,6 +3645,12 @@ dependencies = [ { name = "torch" }, ] +[package.optional-dependencies] +circuit-lens = [ + { name = "circuit-tracer" }, + { name = "transformer-lens" }, +] + [package.dev-dependencies] dev = [ { name = "captum" }, @@ -3657,9 +3704,12 @@ scripts = [ [package.metadata] requires-dist = [ + { name = "circuit-tracer", marker = "extra == 'circuit-lens'", specifier = ">=0.5.0" }, { name = "tensordict", specifier = ">=0.10" }, { name = "torch", specifier = ">=2.7.1" }, + { name = "transformer-lens", marker = "extra == 'circuit-lens'", specifier = ">=2.16.0" }, ] +provides-extras = ["circuit-lens"] [package.metadata.requires-dev] dev = [ @@ -3789,27 +3839,28 @@ wheels = [ [[package]] name = "tokenizers" -version = "0.21.4" +version = "0.22.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "huggingface-hub" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c2/2f/402986d0823f8d7ca139d969af2917fefaa9b947d1fb32f6168c509f2492/tokenizers-0.21.4.tar.gz", hash = "sha256:fa23f85fbc9a02ec5c6978da172cdcbac23498c3ca9f3645c5c68740ac007880", size = 351253, upload-time = "2025-07-28T15:48:54.325Z" } +sdist = { url = "https://files.pythonhosted.org/packages/73/6f/f80cfef4a312e1fb34baf7d85c72d4411afde10978d4657f8cdd811d3ccc/tokenizers-0.22.2.tar.gz", hash = "sha256:473b83b915e547aa366d1eee11806deaf419e17be16310ac0a14077f1e28f917", size = 372115, upload-time = "2026-01-05T10:45:15.988Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/98/c6/fdb6f72bf6454f52eb4a2510be7fb0f614e541a2554d6210e370d85efff4/tokenizers-0.21.4-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:2ccc10a7c3bcefe0f242867dc914fc1226ee44321eb618cfe3019b5df3400133", size = 2863987, upload-time = "2025-07-28T15:48:44.877Z" }, - { url = "https://files.pythonhosted.org/packages/8d/a6/28975479e35ddc751dc1ddc97b9b69bf7fcf074db31548aab37f8116674c/tokenizers-0.21.4-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:5e2f601a8e0cd5be5cc7506b20a79112370b9b3e9cb5f13f68ab11acd6ca7d60", size = 2732457, upload-time = "2025-07-28T15:48:43.265Z" }, - { url = "https://files.pythonhosted.org/packages/aa/8f/24f39d7b5c726b7b0be95dca04f344df278a3fe3a4deb15a975d194cbb32/tokenizers-0.21.4-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:39b376f5a1aee67b4d29032ee85511bbd1b99007ec735f7f35c8a2eb104eade5", size = 3012624, upload-time = "2025-07-28T13:22:43.895Z" }, - { url = "https://files.pythonhosted.org/packages/58/47/26358925717687a58cb74d7a508de96649544fad5778f0cd9827398dc499/tokenizers-0.21.4-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2107ad649e2cda4488d41dfd031469e9da3fcbfd6183e74e4958fa729ffbf9c6", size = 2939681, upload-time = "2025-07-28T13:22:47.499Z" }, - { url = "https://files.pythonhosted.org/packages/99/6f/cc300fea5db2ab5ddc2c8aea5757a27b89c84469899710c3aeddc1d39801/tokenizers-0.21.4-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c73012da95afafdf235ba80047699df4384fdc481527448a078ffd00e45a7d9", size = 3247445, upload-time = "2025-07-28T15:48:39.711Z" }, - { url = "https://files.pythonhosted.org/packages/be/bf/98cb4b9c3c4afd8be89cfa6423704337dc20b73eb4180397a6e0d456c334/tokenizers-0.21.4-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f23186c40395fc390d27f519679a58023f368a0aad234af145e0f39ad1212732", size = 3428014, upload-time = "2025-07-28T13:22:49.569Z" }, - { url = "https://files.pythonhosted.org/packages/75/c7/96c1cc780e6ca7f01a57c13235dd05b7bc1c0f3588512ebe9d1331b5f5ae/tokenizers-0.21.4-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cc88bb34e23a54cc42713d6d98af5f1bf79c07653d24fe984d2d695ba2c922a2", size = 3193197, upload-time = "2025-07-28T13:22:51.471Z" }, - { url = "https://files.pythonhosted.org/packages/f2/90/273b6c7ec78af547694eddeea9e05de771278bd20476525ab930cecaf7d8/tokenizers-0.21.4-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51b7eabb104f46c1c50b486520555715457ae833d5aee9ff6ae853d1130506ff", size = 3115426, upload-time = "2025-07-28T15:48:41.439Z" }, - { url = "https://files.pythonhosted.org/packages/91/43/c640d5a07e95f1cf9d2c92501f20a25f179ac53a4f71e1489a3dcfcc67ee/tokenizers-0.21.4-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:714b05b2e1af1288bd1bc56ce496c4cebb64a20d158ee802887757791191e6e2", size = 9089127, upload-time = "2025-07-28T15:48:46.472Z" }, - { url = "https://files.pythonhosted.org/packages/44/a1/dd23edd6271d4dca788e5200a807b49ec3e6987815cd9d0a07ad9c96c7c2/tokenizers-0.21.4-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:1340ff877ceedfa937544b7d79f5b7becf33a4cfb58f89b3b49927004ef66f78", size = 9055243, upload-time = "2025-07-28T15:48:48.539Z" }, - { url = "https://files.pythonhosted.org/packages/21/2b/b410d6e9021c4b7ddb57248304dc817c4d4970b73b6ee343674914701197/tokenizers-0.21.4-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:3c1f4317576e465ac9ef0d165b247825a2a4078bcd01cba6b54b867bdf9fdd8b", size = 9298237, upload-time = "2025-07-28T15:48:50.443Z" }, - { url = "https://files.pythonhosted.org/packages/b7/0a/42348c995c67e2e6e5c89ffb9cfd68507cbaeb84ff39c49ee6e0a6dd0fd2/tokenizers-0.21.4-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:c212aa4e45ec0bb5274b16b6f31dd3f1c41944025c2358faaa5782c754e84c24", size = 9461980, upload-time = "2025-07-28T15:48:52.325Z" }, - { url = "https://files.pythonhosted.org/packages/3d/d3/dacccd834404cd71b5c334882f3ba40331ad2120e69ded32cf5fda9a7436/tokenizers-0.21.4-cp39-abi3-win32.whl", hash = "sha256:6c42a930bc5f4c47f4ea775c91de47d27910881902b0f20e4990ebe045a415d0", size = 2329871, upload-time = "2025-07-28T15:48:56.841Z" }, - { url = "https://files.pythonhosted.org/packages/41/f2/fd673d979185f5dcbac4be7d09461cbb99751554ffb6718d0013af8604cb/tokenizers-0.21.4-cp39-abi3-win_amd64.whl", hash = "sha256:475d807a5c3eb72c59ad9b5fcdb254f6e17f53dfcbb9903233b0dfa9c943b597", size = 2507568, upload-time = "2025-07-28T15:48:55.456Z" }, + { url = "https://files.pythonhosted.org/packages/92/97/5dbfabf04c7e348e655e907ed27913e03db0923abb5dfdd120d7b25630e1/tokenizers-0.22.2-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:544dd704ae7238755d790de45ba8da072e9af3eea688f698b137915ae959281c", size = 3100275, upload-time = "2026-01-05T10:41:02.158Z" }, + { url = "https://files.pythonhosted.org/packages/2e/47/174dca0502ef88b28f1c9e06b73ce33500eedfac7a7692108aec220464e7/tokenizers-0.22.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:1e418a55456beedca4621dbab65a318981467a2b188e982a23e117f115ce5001", size = 2981472, upload-time = "2026-01-05T10:41:00.276Z" }, + { url = "https://files.pythonhosted.org/packages/d6/84/7990e799f1309a8b87af6b948f31edaa12a3ed22d11b352eaf4f4b2e5753/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2249487018adec45d6e3554c71d46eb39fa8ea67156c640f7513eb26f318cec7", size = 3290736, upload-time = "2026-01-05T10:40:32.165Z" }, + { url = "https://files.pythonhosted.org/packages/78/59/09d0d9ba94dcd5f4f1368d4858d24546b4bdc0231c2354aa31d6199f0399/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:25b85325d0815e86e0bac263506dd114578953b7b53d7de09a6485e4a160a7dd", size = 3168835, upload-time = "2026-01-05T10:40:38.847Z" }, + { url = "https://files.pythonhosted.org/packages/47/50/b3ebb4243e7160bda8d34b731e54dd8ab8b133e50775872e7a434e524c28/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bfb88f22a209ff7b40a576d5324bf8286b519d7358663db21d6246fb17eea2d5", size = 3521673, upload-time = "2026-01-05T10:40:56.614Z" }, + { url = "https://files.pythonhosted.org/packages/e0/fa/89f4cb9e08df770b57adb96f8cbb7e22695a4cb6c2bd5f0c4f0ebcf33b66/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1c774b1276f71e1ef716e5486f21e76333464f47bece56bbd554485982a9e03e", size = 3724818, upload-time = "2026-01-05T10:40:44.507Z" }, + { url = "https://files.pythonhosted.org/packages/64/04/ca2363f0bfbe3b3d36e95bf67e56a4c88c8e3362b658e616d1ac185d47f2/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:df6c4265b289083bf710dff49bc51ef252f9d5be33a45ee2bed151114a56207b", size = 3379195, upload-time = "2026-01-05T10:40:51.139Z" }, + { url = "https://files.pythonhosted.org/packages/2e/76/932be4b50ef6ccedf9d3c6639b056a967a86258c6d9200643f01269211ca/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:369cc9fc8cc10cb24143873a0d95438bb8ee257bb80c71989e3ee290e8d72c67", size = 3274982, upload-time = "2026-01-05T10:40:58.331Z" }, + { url = "https://files.pythonhosted.org/packages/1d/28/5f9f5a4cc211b69e89420980e483831bcc29dade307955cc9dc858a40f01/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:29c30b83d8dcd061078b05ae0cb94d3c710555fbb44861139f9f83dcca3dc3e4", size = 9478245, upload-time = "2026-01-05T10:41:04.053Z" }, + { url = "https://files.pythonhosted.org/packages/6c/fb/66e2da4704d6aadebf8cb39f1d6d1957df667ab24cff2326b77cda0dcb85/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:37ae80a28c1d3265bb1f22464c856bd23c02a05bb211e56d0c5301a435be6c1a", size = 9560069, upload-time = "2026-01-05T10:45:10.673Z" }, + { url = "https://files.pythonhosted.org/packages/16/04/fed398b05caa87ce9b1a1bb5166645e38196081b225059a6edaff6440fac/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:791135ee325f2336f498590eb2f11dc5c295232f288e75c99a36c5dbce63088a", size = 9899263, upload-time = "2026-01-05T10:45:12.559Z" }, + { url = "https://files.pythonhosted.org/packages/05/a1/d62dfe7376beaaf1394917e0f8e93ee5f67fea8fcf4107501db35996586b/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:38337540fbbddff8e999d59970f3c6f35a82de10053206a7562f1ea02d046fa5", size = 10033429, upload-time = "2026-01-05T10:45:14.333Z" }, + { url = "https://files.pythonhosted.org/packages/fd/18/a545c4ea42af3df6effd7d13d250ba77a0a86fb20393143bbb9a92e434d4/tokenizers-0.22.2-cp39-abi3-win32.whl", hash = "sha256:a6bf3f88c554a2b653af81f3204491c818ae2ac6fbc09e76ef4773351292bc92", size = 2502363, upload-time = "2026-01-05T10:45:20.593Z" }, + { url = "https://files.pythonhosted.org/packages/65/71/0670843133a43d43070abeb1949abfdef12a86d490bea9cd9e18e37c5ff7/tokenizers-0.22.2-cp39-abi3-win_amd64.whl", hash = "sha256:c9ea31edff2968b44a88f97d784c2f16dc0729b8b143ed004699ebca91f05c48", size = 2747786, upload-time = "2026-01-05T10:45:18.411Z" }, + { url = "https://files.pythonhosted.org/packages/72/f4/0de46cfa12cdcbcd464cc59fde36912af405696f687e53a091fb432f694c/tokenizers-0.22.2-cp39-abi3-win_arm64.whl", hash = "sha256:9ce725d22864a1e965217204946f830c37876eee3b2ba6fc6255e8e903d5fcbc", size = 2612133, upload-time = "2026-01-05T10:45:17.232Z" }, ] [[package]] @@ -4016,7 +4067,7 @@ wheels = [ [[package]] name = "transformer-lens" -version = "2.15.4" +version = "3.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "accelerate" }, @@ -4025,9 +4076,11 @@ dependencies = [ { name = "datasets" }, { name = "einops" }, { name = "fancy-einsum" }, + { name = "huggingface-hub" }, { name = "jaxtyping" }, { name = "numpy", marker = "python_full_version < '3.13'" }, { name = "pandas" }, + { name = "protobuf" }, { name = "rich" }, { name = "sentencepiece" }, { name = "torch" }, @@ -4038,14 +4091,14 @@ dependencies = [ { name = "typing-extensions" }, { name = "wandb" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a1/14/02504ec3b5333f5c09eee59c8fd0d5b4830d82932fd3dd78f6bf88e00dcf/transformer_lens-2.15.4.tar.gz", hash = "sha256:76e3c6049ae110fb1529669345cf76f60b931936b1e351a70a8c73c48fc84440", size = 151406, upload-time = "2025-05-15T21:56:09.573Z" } +sdist = { url = "https://files.pythonhosted.org/packages/bd/a6/6560b02cba7c53a481c21ca31c2695189574f92636265f5ed51c95308e7c/transformer_lens-3.2.1.tar.gz", hash = "sha256:742288b20098f1945a34aa55b5b360aba55d95b89babb1932e67b94b1b4392cb", size = 9198975, upload-time = "2026-05-09T15:20:03.081Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/46/68/ff1fee1043060b70c307e70389dff64b3259e0a6e3197ace5df0477d2983/transformer_lens-2.15.4-py3-none-any.whl", hash = "sha256:f0bccac37410f3568be316d6b688513d5ffec70a88f89c257d2cb9fffae08104", size = 189262, upload-time = "2025-05-15T21:56:08.321Z" }, + { url = "https://files.pythonhosted.org/packages/52/6e/621e5a123b902bfbbda3d05ef6a12754753657bafae56b2a7a8c3a166f35/transformer_lens-3.2.1-py3-none-any.whl", hash = "sha256:e1ef58571bad1305a28b53c172e2aafc5617c9fc1807bc3c52f92e5f67295e90", size = 968640, upload-time = "2026-05-09T15:20:01.121Z" }, ] [[package]] name = "transformers" -version = "4.54.1" +version = "4.57.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "filelock" }, @@ -4059,9 +4112,9 @@ dependencies = [ { name = "tokenizers" }, { name = "tqdm" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/21/6c/4caeb57926f91d943f309b062e22ad1eb24a9f530421c5a65c1d89378a7a/transformers-4.54.1.tar.gz", hash = "sha256:b2551bb97903f13bd90c9467d0a144d41ca4d142defc044a99502bb77c5c1052", size = 9514288, upload-time = "2025-07-29T15:57:22.826Z" } +sdist = { url = "https://files.pythonhosted.org/packages/dd/70/d42a739e8dfde3d92bb2fff5819cbf331fe9657323221e79415cd5eb65ee/transformers-4.57.3.tar.gz", hash = "sha256:df4945029aaddd7c09eec5cad851f30662f8bd1746721b34cc031d70c65afebc", size = 10139680, upload-time = "2025-11-25T15:51:30.139Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cf/18/eb7578f84ef5a080d4e5ca9bc4f7c68e7aa9c1e464f1b3d3001e4c642fce/transformers-4.54.1-py3-none-any.whl", hash = "sha256:c89965a4f62a0d07009d45927a9c6372848a02ab9ead9c318c3d082708bab529", size = 11176397, upload-time = "2025-07-29T15:57:19.692Z" }, + { url = "https://files.pythonhosted.org/packages/6a/6b/2f416568b3c4c91c96e5a365d164f8a4a4a88030aa8ab4644181fdadce97/transformers-4.57.3-py3-none-any.whl", hash = "sha256:c77d353a4851b1880191603d36acb313411d3577f6e2897814f333841f7003f4", size = 11993463, upload-time = "2025-11-25T15:51:26.493Z" }, ] [[package]] @@ -4433,3 +4486,77 @@ sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50e wheels = [ { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" }, ] + +[[package]] +name = "zstandard" +version = "0.25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fd/aa/3e0508d5a5dd96529cdc5a97011299056e14c6505b678fd58938792794b1/zstandard-0.25.0.tar.gz", hash = "sha256:7713e1179d162cf5c7906da876ec2ccb9c3a9dcbdffef0cc7f70c3667a205f0b", size = 711513, upload-time = "2025-09-14T22:15:54.002Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/83/c3ca27c363d104980f1c9cee1101cc8ba724ac8c28a033ede6aab89585b1/zstandard-0.25.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:933b65d7680ea337180733cf9e87293cc5500cc0eb3fc8769f4d3c88d724ec5c", size = 795254, upload-time = "2025-09-14T22:16:26.137Z" }, + { url = "https://files.pythonhosted.org/packages/ac/4d/e66465c5411a7cf4866aeadc7d108081d8ceba9bc7abe6b14aa21c671ec3/zstandard-0.25.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a3f79487c687b1fc69f19e487cd949bf3aae653d181dfb5fde3bf6d18894706f", size = 640559, upload-time = "2025-09-14T22:16:27.973Z" }, + { url = "https://files.pythonhosted.org/packages/12/56/354fe655905f290d3b147b33fe946b0f27e791e4b50a5f004c802cb3eb7b/zstandard-0.25.0-cp311-cp311-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:0bbc9a0c65ce0eea3c34a691e3c4b6889f5f3909ba4822ab385fab9057099431", size = 5348020, upload-time = "2025-09-14T22:16:29.523Z" }, + { url = "https://files.pythonhosted.org/packages/3b/13/2b7ed68bd85e69a2069bcc72141d378f22cae5a0f3b353a2c8f50ef30c1b/zstandard-0.25.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:01582723b3ccd6939ab7b3a78622c573799d5d8737b534b86d0e06ac18dbde4a", size = 5058126, upload-time = "2025-09-14T22:16:31.811Z" }, + { url = "https://files.pythonhosted.org/packages/c9/dd/fdaf0674f4b10d92cb120ccff58bbb6626bf8368f00ebfd2a41ba4a0dc99/zstandard-0.25.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5f1ad7bf88535edcf30038f6919abe087f606f62c00a87d7e33e7fc57cb69fcc", size = 5405390, upload-time = "2025-09-14T22:16:33.486Z" }, + { url = "https://files.pythonhosted.org/packages/0f/67/354d1555575bc2490435f90d67ca4dd65238ff2f119f30f72d5cde09c2ad/zstandard-0.25.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:06acb75eebeedb77b69048031282737717a63e71e4ae3f77cc0c3b9508320df6", size = 5452914, upload-time = "2025-09-14T22:16:35.277Z" }, + { url = "https://files.pythonhosted.org/packages/bb/1f/e9cfd801a3f9190bf3e759c422bbfd2247db9d7f3d54a56ecde70137791a/zstandard-0.25.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9300d02ea7c6506f00e627e287e0492a5eb0371ec1670ae852fefffa6164b072", size = 5559635, upload-time = "2025-09-14T22:16:37.141Z" }, + { url = "https://files.pythonhosted.org/packages/21/88/5ba550f797ca953a52d708c8e4f380959e7e3280af029e38fbf47b55916e/zstandard-0.25.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bfd06b1c5584b657a2892a6014c2f4c20e0db0208c159148fa78c65f7e0b0277", size = 5048277, upload-time = "2025-09-14T22:16:38.807Z" }, + { url = "https://files.pythonhosted.org/packages/46/c0/ca3e533b4fa03112facbe7fbe7779cb1ebec215688e5df576fe5429172e0/zstandard-0.25.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f373da2c1757bb7f1acaf09369cdc1d51d84131e50d5fa9863982fd626466313", size = 5574377, upload-time = "2025-09-14T22:16:40.523Z" }, + { url = "https://files.pythonhosted.org/packages/12/9b/3fb626390113f272abd0799fd677ea33d5fc3ec185e62e6be534493c4b60/zstandard-0.25.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6c0e5a65158a7946e7a7affa6418878ef97ab66636f13353b8502d7ea03c8097", size = 4961493, upload-time = "2025-09-14T22:16:43.3Z" }, + { url = "https://files.pythonhosted.org/packages/cb/d3/23094a6b6a4b1343b27ae68249daa17ae0651fcfec9ed4de09d14b940285/zstandard-0.25.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:c8e167d5adf59476fa3e37bee730890e389410c354771a62e3c076c86f9f7778", size = 5269018, upload-time = "2025-09-14T22:16:45.292Z" }, + { url = "https://files.pythonhosted.org/packages/8c/a7/bb5a0c1c0f3f4b5e9d5b55198e39de91e04ba7c205cc46fcb0f95f0383c1/zstandard-0.25.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:98750a309eb2f020da61e727de7d7ba3c57c97cf6213f6f6277bb7fb42a8e065", size = 5443672, upload-time = "2025-09-14T22:16:47.076Z" }, + { url = "https://files.pythonhosted.org/packages/27/22/503347aa08d073993f25109c36c8d9f029c7d5949198050962cb568dfa5e/zstandard-0.25.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:22a086cff1b6ceca18a8dd6096ec631e430e93a8e70a9ca5efa7561a00f826fa", size = 5822753, upload-time = "2025-09-14T22:16:49.316Z" }, + { url = "https://files.pythonhosted.org/packages/e2/be/94267dc6ee64f0f8ba2b2ae7c7a2df934a816baaa7291db9e1aa77394c3c/zstandard-0.25.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:72d35d7aa0bba323965da807a462b0966c91608ef3a48ba761678cb20ce5d8b7", size = 5366047, upload-time = "2025-09-14T22:16:51.328Z" }, + { url = "https://files.pythonhosted.org/packages/7b/a3/732893eab0a3a7aecff8b99052fecf9f605cf0fb5fb6d0290e36beee47a4/zstandard-0.25.0-cp311-cp311-win32.whl", hash = "sha256:f5aeea11ded7320a84dcdd62a3d95b5186834224a9e55b92ccae35d21a8b63d4", size = 436484, upload-time = "2025-09-14T22:16:55.005Z" }, + { url = "https://files.pythonhosted.org/packages/43/a3/c6155f5c1cce691cb80dfd38627046e50af3ee9ddc5d0b45b9b063bfb8c9/zstandard-0.25.0-cp311-cp311-win_amd64.whl", hash = "sha256:daab68faadb847063d0c56f361a289c4f268706b598afbf9ad113cbe5c38b6b2", size = 506183, upload-time = "2025-09-14T22:16:52.753Z" }, + { url = "https://files.pythonhosted.org/packages/8c/3e/8945ab86a0820cc0e0cdbf38086a92868a9172020fdab8a03ac19662b0e5/zstandard-0.25.0-cp311-cp311-win_arm64.whl", hash = "sha256:22a06c5df3751bb7dc67406f5374734ccee8ed37fc5981bf1ad7041831fa1137", size = 462533, upload-time = "2025-09-14T22:16:53.878Z" }, + { url = "https://files.pythonhosted.org/packages/82/fc/f26eb6ef91ae723a03e16eddb198abcfce2bc5a42e224d44cc8b6765e57e/zstandard-0.25.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7b3c3a3ab9daa3eed242d6ecceead93aebbb8f5f84318d82cee643e019c4b73b", size = 795738, upload-time = "2025-09-14T22:16:56.237Z" }, + { url = "https://files.pythonhosted.org/packages/aa/1c/d920d64b22f8dd028a8b90e2d756e431a5d86194caa78e3819c7bf53b4b3/zstandard-0.25.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:913cbd31a400febff93b564a23e17c3ed2d56c064006f54efec210d586171c00", size = 640436, upload-time = "2025-09-14T22:16:57.774Z" }, + { url = "https://files.pythonhosted.org/packages/53/6c/288c3f0bd9fcfe9ca41e2c2fbfd17b2097f6af57b62a81161941f09afa76/zstandard-0.25.0-cp312-cp312-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:011d388c76b11a0c165374ce660ce2c8efa8e5d87f34996aa80f9c0816698b64", size = 5343019, upload-time = "2025-09-14T22:16:59.302Z" }, + { url = "https://files.pythonhosted.org/packages/1e/15/efef5a2f204a64bdb5571e6161d49f7ef0fffdbca953a615efbec045f60f/zstandard-0.25.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6dffecc361d079bb48d7caef5d673c88c8988d3d33fb74ab95b7ee6da42652ea", size = 5063012, upload-time = "2025-09-14T22:17:01.156Z" }, + { url = "https://files.pythonhosted.org/packages/b7/37/a6ce629ffdb43959e92e87ebdaeebb5ac81c944b6a75c9c47e300f85abdf/zstandard-0.25.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:7149623bba7fdf7e7f24312953bcf73cae103db8cae49f8154dd1eadc8a29ecb", size = 5394148, upload-time = "2025-09-14T22:17:03.091Z" }, + { url = "https://files.pythonhosted.org/packages/e3/79/2bf870b3abeb5c070fe2d670a5a8d1057a8270f125ef7676d29ea900f496/zstandard-0.25.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:6a573a35693e03cf1d67799fd01b50ff578515a8aeadd4595d2a7fa9f3ec002a", size = 5451652, upload-time = "2025-09-14T22:17:04.979Z" }, + { url = "https://files.pythonhosted.org/packages/53/60/7be26e610767316c028a2cbedb9a3beabdbe33e2182c373f71a1c0b88f36/zstandard-0.25.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5a56ba0db2d244117ed744dfa8f6f5b366e14148e00de44723413b2f3938a902", size = 5546993, upload-time = "2025-09-14T22:17:06.781Z" }, + { url = "https://files.pythonhosted.org/packages/85/c7/3483ad9ff0662623f3648479b0380d2de5510abf00990468c286c6b04017/zstandard-0.25.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:10ef2a79ab8e2974e2075fb984e5b9806c64134810fac21576f0668e7ea19f8f", size = 5046806, upload-time = "2025-09-14T22:17:08.415Z" }, + { url = "https://files.pythonhosted.org/packages/08/b3/206883dd25b8d1591a1caa44b54c2aad84badccf2f1de9e2d60a446f9a25/zstandard-0.25.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:aaf21ba8fb76d102b696781bddaa0954b782536446083ae3fdaa6f16b25a1c4b", size = 5576659, upload-time = "2025-09-14T22:17:10.164Z" }, + { url = "https://files.pythonhosted.org/packages/9d/31/76c0779101453e6c117b0ff22565865c54f48f8bd807df2b00c2c404b8e0/zstandard-0.25.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1869da9571d5e94a85a5e8d57e4e8807b175c9e4a6294e3b66fa4efb074d90f6", size = 4953933, upload-time = "2025-09-14T22:17:11.857Z" }, + { url = "https://files.pythonhosted.org/packages/18/e1/97680c664a1bf9a247a280a053d98e251424af51f1b196c6d52f117c9720/zstandard-0.25.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:809c5bcb2c67cd0ed81e9229d227d4ca28f82d0f778fc5fea624a9def3963f91", size = 5268008, upload-time = "2025-09-14T22:17:13.627Z" }, + { url = "https://files.pythonhosted.org/packages/1e/73/316e4010de585ac798e154e88fd81bb16afc5c5cb1a72eeb16dd37e8024a/zstandard-0.25.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f27662e4f7dbf9f9c12391cb37b4c4c3cb90ffbd3b1fb9284dadbbb8935fa708", size = 5433517, upload-time = "2025-09-14T22:17:16.103Z" }, + { url = "https://files.pythonhosted.org/packages/5b/60/dd0f8cfa8129c5a0ce3ea6b7f70be5b33d2618013a161e1ff26c2b39787c/zstandard-0.25.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:99c0c846e6e61718715a3c9437ccc625de26593fea60189567f0118dc9db7512", size = 5814292, upload-time = "2025-09-14T22:17:17.827Z" }, + { url = "https://files.pythonhosted.org/packages/fc/5f/75aafd4b9d11b5407b641b8e41a57864097663699f23e9ad4dbb91dc6bfe/zstandard-0.25.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:474d2596a2dbc241a556e965fb76002c1ce655445e4e3bf38e5477d413165ffa", size = 5360237, upload-time = "2025-09-14T22:17:19.954Z" }, + { url = "https://files.pythonhosted.org/packages/ff/8d/0309daffea4fcac7981021dbf21cdb2e3427a9e76bafbcdbdf5392ff99a4/zstandard-0.25.0-cp312-cp312-win32.whl", hash = "sha256:23ebc8f17a03133b4426bcc04aabd68f8236eb78c3760f12783385171b0fd8bd", size = 436922, upload-time = "2025-09-14T22:17:24.398Z" }, + { url = "https://files.pythonhosted.org/packages/79/3b/fa54d9015f945330510cb5d0b0501e8253c127cca7ebe8ba46a965df18c5/zstandard-0.25.0-cp312-cp312-win_amd64.whl", hash = "sha256:ffef5a74088f1e09947aecf91011136665152e0b4b359c42be3373897fb39b01", size = 506276, upload-time = "2025-09-14T22:17:21.429Z" }, + { url = "https://files.pythonhosted.org/packages/ea/6b/8b51697e5319b1f9ac71087b0af9a40d8a6288ff8025c36486e0c12abcc4/zstandard-0.25.0-cp312-cp312-win_arm64.whl", hash = "sha256:181eb40e0b6a29b3cd2849f825e0fa34397f649170673d385f3598ae17cca2e9", size = 462679, upload-time = "2025-09-14T22:17:23.147Z" }, + { url = "https://files.pythonhosted.org/packages/35/0b/8df9c4ad06af91d39e94fa96cc010a24ac4ef1378d3efab9223cc8593d40/zstandard-0.25.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ec996f12524f88e151c339688c3897194821d7f03081ab35d31d1e12ec975e94", size = 795735, upload-time = "2025-09-14T22:17:26.042Z" }, + { url = "https://files.pythonhosted.org/packages/3f/06/9ae96a3e5dcfd119377ba33d4c42a7d89da1efabd5cb3e366b156c45ff4d/zstandard-0.25.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a1a4ae2dec3993a32247995bdfe367fc3266da832d82f8438c8570f989753de1", size = 640440, upload-time = "2025-09-14T22:17:27.366Z" }, + { url = "https://files.pythonhosted.org/packages/d9/14/933d27204c2bd404229c69f445862454dcc101cd69ef8c6068f15aaec12c/zstandard-0.25.0-cp313-cp313-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:e96594a5537722fdfb79951672a2a63aec5ebfb823e7560586f7484819f2a08f", size = 5343070, upload-time = "2025-09-14T22:17:28.896Z" }, + { url = "https://files.pythonhosted.org/packages/6d/db/ddb11011826ed7db9d0e485d13df79b58586bfdec56e5c84a928a9a78c1c/zstandard-0.25.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bfc4e20784722098822e3eee42b8e576b379ed72cca4a7cb856ae733e62192ea", size = 5063001, upload-time = "2025-09-14T22:17:31.044Z" }, + { url = "https://files.pythonhosted.org/packages/db/00/87466ea3f99599d02a5238498b87bf84a6348290c19571051839ca943777/zstandard-0.25.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:457ed498fc58cdc12fc48f7950e02740d4f7ae9493dd4ab2168a47c93c31298e", size = 5394120, upload-time = "2025-09-14T22:17:32.711Z" }, + { url = "https://files.pythonhosted.org/packages/2b/95/fc5531d9c618a679a20ff6c29e2b3ef1d1f4ad66c5e161ae6ff847d102a9/zstandard-0.25.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:fd7a5004eb1980d3cefe26b2685bcb0b17989901a70a1040d1ac86f1d898c551", size = 5451230, upload-time = "2025-09-14T22:17:34.41Z" }, + { url = "https://files.pythonhosted.org/packages/63/4b/e3678b4e776db00f9f7b2fe58e547e8928ef32727d7a1ff01dea010f3f13/zstandard-0.25.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8e735494da3db08694d26480f1493ad2cf86e99bdd53e8e9771b2752a5c0246a", size = 5547173, upload-time = "2025-09-14T22:17:36.084Z" }, + { url = "https://files.pythonhosted.org/packages/4e/d5/ba05ed95c6b8ec30bd468dfeab20589f2cf709b5c940483e31d991f2ca58/zstandard-0.25.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3a39c94ad7866160a4a46d772e43311a743c316942037671beb264e395bdd611", size = 5046736, upload-time = "2025-09-14T22:17:37.891Z" }, + { url = "https://files.pythonhosted.org/packages/50/d5/870aa06b3a76c73eced65c044b92286a3c4e00554005ff51962deef28e28/zstandard-0.25.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:172de1f06947577d3a3005416977cce6168f2261284c02080e7ad0185faeced3", size = 5576368, upload-time = "2025-09-14T22:17:40.206Z" }, + { url = "https://files.pythonhosted.org/packages/5d/35/398dc2ffc89d304d59bc12f0fdd931b4ce455bddf7038a0a67733a25f550/zstandard-0.25.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3c83b0188c852a47cd13ef3bf9209fb0a77fa5374958b8c53aaa699398c6bd7b", size = 4954022, upload-time = "2025-09-14T22:17:41.879Z" }, + { url = "https://files.pythonhosted.org/packages/9a/5c/36ba1e5507d56d2213202ec2b05e8541734af5f2ce378c5d1ceaf4d88dc4/zstandard-0.25.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1673b7199bbe763365b81a4f3252b8e80f44c9e323fc42940dc8843bfeaf9851", size = 5267889, upload-time = "2025-09-14T22:17:43.577Z" }, + { url = "https://files.pythonhosted.org/packages/70/e8/2ec6b6fb7358b2ec0113ae202647ca7c0e9d15b61c005ae5225ad0995df5/zstandard-0.25.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:0be7622c37c183406f3dbf0cba104118eb16a4ea7359eeb5752f0794882fc250", size = 5433952, upload-time = "2025-09-14T22:17:45.271Z" }, + { url = "https://files.pythonhosted.org/packages/7b/01/b5f4d4dbc59ef193e870495c6f1275f5b2928e01ff5a81fecb22a06e22fb/zstandard-0.25.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:5f5e4c2a23ca271c218ac025bd7d635597048b366d6f31f420aaeb715239fc98", size = 5814054, upload-time = "2025-09-14T22:17:47.08Z" }, + { url = "https://files.pythonhosted.org/packages/b2/e5/fbd822d5c6f427cf158316d012c5a12f233473c2f9c5fe5ab1ae5d21f3d8/zstandard-0.25.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f187a0bb61b35119d1926aee039524d1f93aaf38a9916b8c4b78ac8514a0aaf", size = 5360113, upload-time = "2025-09-14T22:17:48.893Z" }, + { url = "https://files.pythonhosted.org/packages/8e/e0/69a553d2047f9a2c7347caa225bb3a63b6d7704ad74610cb7823baa08ed7/zstandard-0.25.0-cp313-cp313-win32.whl", hash = "sha256:7030defa83eef3e51ff26f0b7bfb229f0204b66fe18e04359ce3474ac33cbc09", size = 436936, upload-time = "2025-09-14T22:17:52.658Z" }, + { url = "https://files.pythonhosted.org/packages/d9/82/b9c06c870f3bd8767c201f1edbdf9e8dc34be5b0fbc5682c4f80fe948475/zstandard-0.25.0-cp313-cp313-win_amd64.whl", hash = "sha256:1f830a0dac88719af0ae43b8b2d6aef487d437036468ef3c2ea59c51f9d55fd5", size = 506232, upload-time = "2025-09-14T22:17:50.402Z" }, + { url = "https://files.pythonhosted.org/packages/d4/57/60c3c01243bb81d381c9916e2a6d9e149ab8627c0c7d7abb2d73384b3c0c/zstandard-0.25.0-cp313-cp313-win_arm64.whl", hash = "sha256:85304a43f4d513f5464ceb938aa02c1e78c2943b29f44a750b48b25ac999a049", size = 462671, upload-time = "2025-09-14T22:17:51.533Z" }, + { url = "https://files.pythonhosted.org/packages/3d/5c/f8923b595b55fe49e30612987ad8bf053aef555c14f05bb659dd5dbe3e8a/zstandard-0.25.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:e29f0cf06974c899b2c188ef7f783607dbef36da4c242eb6c82dcd8b512855e3", size = 795887, upload-time = "2025-09-14T22:17:54.198Z" }, + { url = "https://files.pythonhosted.org/packages/8d/09/d0a2a14fc3439c5f874042dca72a79c70a532090b7ba0003be73fee37ae2/zstandard-0.25.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:05df5136bc5a011f33cd25bc9f506e7426c0c9b3f9954f056831ce68f3b6689f", size = 640658, upload-time = "2025-09-14T22:17:55.423Z" }, + { url = "https://files.pythonhosted.org/packages/5d/7c/8b6b71b1ddd517f68ffb55e10834388d4f793c49c6b83effaaa05785b0b4/zstandard-0.25.0-cp314-cp314-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:f604efd28f239cc21b3adb53eb061e2a205dc164be408e553b41ba2ffe0ca15c", size = 5379849, upload-time = "2025-09-14T22:17:57.372Z" }, + { url = "https://files.pythonhosted.org/packages/a4/86/a48e56320d0a17189ab7a42645387334fba2200e904ee47fc5a26c1fd8ca/zstandard-0.25.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:223415140608d0f0da010499eaa8ccdb9af210a543fac54bce15babbcfc78439", size = 5058095, upload-time = "2025-09-14T22:17:59.498Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ad/eb659984ee2c0a779f9d06dbfe45e2dc39d99ff40a319895df2d3d9a48e5/zstandard-0.25.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2e54296a283f3ab5a26fc9b8b5d4978ea0532f37b231644f367aa588930aa043", size = 5551751, upload-time = "2025-09-14T22:18:01.618Z" }, + { url = "https://files.pythonhosted.org/packages/61/b3/b637faea43677eb7bd42ab204dfb7053bd5c4582bfe6b1baefa80ac0c47b/zstandard-0.25.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ca54090275939dc8ec5dea2d2afb400e0f83444b2fc24e07df7fdef677110859", size = 6364818, upload-time = "2025-09-14T22:18:03.769Z" }, + { url = "https://files.pythonhosted.org/packages/31/dc/cc50210e11e465c975462439a492516a73300ab8caa8f5e0902544fd748b/zstandard-0.25.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e09bb6252b6476d8d56100e8147b803befa9a12cea144bbe629dd508800d1ad0", size = 5560402, upload-time = "2025-09-14T22:18:05.954Z" }, + { url = "https://files.pythonhosted.org/packages/c9/ae/56523ae9c142f0c08efd5e868a6da613ae76614eca1305259c3bf6a0ed43/zstandard-0.25.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a9ec8c642d1ec73287ae3e726792dd86c96f5681eb8df274a757bf62b750eae7", size = 4955108, upload-time = "2025-09-14T22:18:07.68Z" }, + { url = "https://files.pythonhosted.org/packages/98/cf/c899f2d6df0840d5e384cf4c4121458c72802e8bda19691f3b16619f51e9/zstandard-0.25.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:a4089a10e598eae6393756b036e0f419e8c1d60f44a831520f9af41c14216cf2", size = 5269248, upload-time = "2025-09-14T22:18:09.753Z" }, + { url = "https://files.pythonhosted.org/packages/1b/c0/59e912a531d91e1c192d3085fc0f6fb2852753c301a812d856d857ea03c6/zstandard-0.25.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:f67e8f1a324a900e75b5e28ffb152bcac9fbed1cc7b43f99cd90f395c4375344", size = 5430330, upload-time = "2025-09-14T22:18:11.966Z" }, + { url = "https://files.pythonhosted.org/packages/a0/1d/7e31db1240de2df22a58e2ea9a93fc6e38cc29353e660c0272b6735d6669/zstandard-0.25.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:9654dbc012d8b06fc3d19cc825af3f7bf8ae242226df5f83936cb39f5fdc846c", size = 5811123, upload-time = "2025-09-14T22:18:13.907Z" }, + { url = "https://files.pythonhosted.org/packages/f6/49/fac46df5ad353d50535e118d6983069df68ca5908d4d65b8c466150a4ff1/zstandard-0.25.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4203ce3b31aec23012d3a4cf4a2ed64d12fea5269c49aed5e4c3611b938e4088", size = 5359591, upload-time = "2025-09-14T22:18:16.465Z" }, + { url = "https://files.pythonhosted.org/packages/c2/38/f249a2050ad1eea0bb364046153942e34abba95dd5520af199aed86fbb49/zstandard-0.25.0-cp314-cp314-win32.whl", hash = "sha256:da469dc041701583e34de852d8634703550348d5822e66a0c827d39b05365b12", size = 444513, upload-time = "2025-09-14T22:18:20.61Z" }, + { url = "https://files.pythonhosted.org/packages/3a/43/241f9615bcf8ba8903b3f0432da069e857fc4fd1783bd26183db53c4804b/zstandard-0.25.0-cp314-cp314-win_amd64.whl", hash = "sha256:c19bcdd826e95671065f8692b5a4aa95c52dc7a02a4c5a0cac46deb879a017a2", size = 516118, upload-time = "2025-09-14T22:18:17.849Z" }, + { url = "https://files.pythonhosted.org/packages/f0/ef/da163ce2450ed4febf6467d77ccb4cd52c4c30ab45624bad26ca0a27260c/zstandard-0.25.0-cp314-cp314-win_arm64.whl", hash = "sha256:d7541afd73985c630bafcd6338d2518ae96060075f9463d7dc14cfb33514383d", size = 476940, upload-time = "2025-09-14T22:18:19.088Z" }, +]