Source code for entoscope.lens.config
from __future__ import annotations
from dataclasses import dataclass
from typing import TYPE_CHECKING
from entoscope.model.config import POSITION_AXES, ModelAxis, ModelLocatorConfig
from entoscope.operation.config import AbstractConfig
from entoscope.operation.method import MethodConfig
if TYPE_CHECKING:
from entoscope.lens.method.factory import LensMethodFactory
[docs]
@dataclass(kw_only=True)
class LensMethodConfig(MethodConfig):
"""The method config of a lens that takes no fields, such as ``logit_lens``.
A lens with fields of its own names a dataclass extending this one.
"""
[docs]
@classmethod
def factory(cls) -> type[LensMethodFactory]:
"""Name the factory lens methods register with.
Returns:
``LensMethodFactory``.
"""
from entoscope.lens.method.factory import LensMethodFactory
return LensMethodFactory
[docs]
@dataclass(kw_only=True)
class LensConfig(AbstractConfig):
"""What a lens run reads and how.
In a YAML file ``method`` is the mapping read strictly into the lens's method config class, or just
the lens's type for its defaults, so a file naming a lens nothing registered or a field the lens
does not have fails as it is read.
Attributes:
method: The lens's method config, whose ``type`` names the lens.
top_k: How many tokens to keep per row.
source: Where to read. A lens that reads activations reads one position per row, and one that
reads weights reads no position.
Raises:
ValueError: If ``top_k`` is below 1, or ``source`` does not name the positions the lens reads.
"""
method: LensMethodConfig
top_k: int = 1
source: ModelLocatorConfig
def __post_init__(self):
if self.top_k < 1:
raise ValueError(f"top_k must be at least 1, got {self.top_k}")
source = self.source
if not LensMethodConfig.factory().get(self.method.type).reads_activations:
named = {*source.location, *source.incremental, *(key for row in source.coordinates or [] for key in row)}
for axis in POSITION_AXES:
if axis in named:
raise ValueError(
f"'{axis.value}' is not a location of '{self.method.type}': it reads the model's weights, "
"which have no position axis"
)
return
if source.coordinates is not None:
one_position_per_row = all(
isinstance(coordinate.get(ModelAxis.POSITION), int) for coordinate in source.coordinates
)
else:
one_position_per_row = ModelAxis.POSITION in source.location and ModelAxis.POSITION not in (
*source.group,
*source.incremental,
*source.combination.location,
)
if not one_position_per_row:
raise ValueError(
f"'{self.method.type}' ranks the tokens of one position per row, so its locator names a single "
"'position' in every coordinate, or has 'position' in 'location' without grouping it"
)