entoscope.operation¶
The shared machinery every operation is built from.
Its classes are importable from entoscope.operation, and ConfigFactory from entoscope too.
from_dict and to_dict, which read a config from its raw shape and write it back, are in
entoscope.util.data.
- class entoscope.operation.config.AbstractConfig(*, config_id: str | None = None)[source]¶
Bases:
objectThe base of every operation’s config.
- Variables:
config_id (str | None) – The id of the YAML entry the config was read from, set by
ConfigFactory. It titles the run’s plan and result pages, and a config built in code may leave it unset.
- class entoscope.operation.config.ConfigFactory(config_path: str)[source]¶
Bases:
Generic[T]Reads configs out of a YAML file of
config_id: configentries.The type argument is the class an entry is read into, so it is required:
ConfigFactory[LensConfig]("configs.yaml").create("my_lens"). Reading is strict, so an unknown or missing key raises, and the file is read once per factory. A value may name environment variables as${NAME}: each is replaced by the variable’s value, or byNAMEitself when it is unset, and a value that is a single variable holding digits becomes an int.- Parameters:
config_path – The YAML file to read.
- create(config_id: str) T[source]¶
Read one entry of the file into the factory’s config class.
- Parameters:
config_id – The entry to read, which also becomes the config’s
config_id.- Returns:
The config the entry describes.
- Raises:
KeyError – If the file has no entry
config_id.ValueError – If the entry has a key the config does not, lacks a required one, or holds a value its field cannot take.
The method config a config runs its method with, and how a method names its class.
- class entoscope.operation.method.MethodConfig(*, type: str)[source]¶
Bases:
objectThe base of every method config: the method a config runs, and that method’s settings.
Each operation’s method config base extends this one and names its
factory.typehas no default because one method config class serves every method that takes it (each method with no fields of its own takes its operation’s base), so only the instance can say which method it configures.- Variables:
type (str) – The name the method is registered under.
- classmethod factory() type[RegistryFactory[Any]][source]¶
Name the factory the operation’s methods register with.
- Returns:
The operation’s method factory.
- Raises:
NotImplementedError – If the class names no factory.
- classmethod from_config(value: Any) MethodConfig[source]¶
Read a config file’s
methodinto the method config class its method takes.- Parameters:
value – The method’s name alone, or a mapping of
typeand the method’s fields.- Returns:
An instance of the config class the method named by
typedeclares.- Raises:
ValueError – If
valueis neither a name nor a mapping, names notype, names a method nothing registered, or has a key the method config does not.
- entoscope.operation.method.method_config_class(method: type) type[MethodConfig][source]¶
Find the config class a method or a dataset names as its first type argument.
A TypeVar left open gives its bound, and a class naming none takes that of the class it extends. Type arguments are erased at runtime, so the subscripted base in a class statement is their only record.
- Parameters:
method – A method or dataset class.
- Returns:
The config class it is configured with.
- Raises:
TypeError – If neither the class nor any class it extends names a config class.
Name-to-class registries: the extension points a user fills, a dataset or an operation’s method.
The built-ins register through the same register a user calls, so a name entoscope ships with differs
from a user’s only in having been taken first.
- class entoscope.operation.registry.RegistryFactory[source]¶
Bases:
Generic[T]Resolves a name to the class registered under it, which extends the class the factory names.
A subclass names that class as its type argument and what an implementation is called in messages as
kind, and each keeps its own entries. A name belongs to one implementation, so a typo or a second implementation cannot shadow the first one, while defining the same class again replaces it, which is what re-running a notebook cell does.- Variables:
kind (ClassVar[str]) – What an implementation is called in messages, such as
"lens method".
- classmethod register(name: str) Callable[[ImplementationT], ImplementationT][source]¶
Register the decorated class under
name.The class is checked as it is registered rather than at its first run.
- Parameters:
name – The name a config’s
typeselects the class by.- Returns:
A class decorator that registers the class and returns it unchanged.
- Raises:
ValueError – If
nameis empty or already taken by another class.TypeError – If the decorated class does not extend the factory’s base class, or leaves one of its abstract methods unimplemented.
- class entoscope.operation.input.OperationInput[source]¶
Bases:
ABCThe base of every operation’s input: a config together with the prompts it runs on.
An input fills itself lazily through
prepare, which resolves what a run iterates and then gathers the activations it reads. Each of the two is done once, soplanfollowed byapplyon the same input resolves the index once.- Variables:
config (entoscope.operation.config.AbstractConfig) – The config the input runs.
- abstractmethod prepare(model: TransformerBridge | CachedModel, *, index_only: bool = False) Self[source]¶
Resolve the index a run iterates, then gather the activations it reads on top of it.
The index needs no forward pass, so
index_onlylets an operation be planned against a model without spending the compute. An input that gathers no activations, its method running the model itself, is ready once its index is.- Parameters:
model – The model the run reads.
index_only – Whether to stop after the index, before any forward pass.
- Returns:
The input itself, prepared in place.
- class entoscope.operation.operator.Operator[source]¶
Bases:
ABCRuns an operation’s method over its input and hands every result to a collector.
An operator is built from
(model, collector_class=...). The collector class decides what a run leaves behind, which is how a run opts in or out of its HTML page. The method is the one the input’s config names, built when the run starts.- abstractmethod apply(operation_input: OperationInput, save_dir: str | None = None, return_collector: bool = False, verbose: bool = True) AbstractCollector | None[source]¶
Run the operation and save what it collected into
save_dir.The method is built before any forward pass, so a config that names something the run cannot do fails before the model is touched. Where the results were saved is logged through the
entoscopelogger.- Parameters:
operation_input – The config and prompts to run, prepared in place as the run needs.
save_dir – The directory to write the run’s files into, created if missing.
Nonewrites nothing.return_collector – Whether to return the collector.
verbose – Whether to show a progress bar.
- Returns:
The collector, whose
resultholds what the run collected, ifreturn_collectoris set, andNoneotherwise.
- abstractmethod plan(operation_input: OperationInput, save_dir: str | None = None, verbose: bool = True) OperationPlan[source]¶
Describe the run
applywould make, without a forward pass and without touchingsave_dir.The input’s index is resolved exactly as the run resolves it, and kept: passing the same input to
applyafterwards does not resolve it again.- Parameters:
operation_input – The config and prompts the run would use.
save_dir – The directory the run would write into, named in the plan’s outputs.
verbose – Whether to also log the plan.
- Returns:
The plan – the run’s steps, forward and backward passes, activations, outputs and index.
- class entoscope.operation.plan.OperationPlan(operation: str, method: str, config: AbstractConfig, steps: int, steps_label: str, forward_passes: int, backward_passes: int = 0, batches: int | None = None, index: list[DataFrame] = <factory>, prompts: list[tuple[str, str, int]]=<factory>, activations: list[str] = <factory>, reads_full_cache: bool = False, save_dir: str | None = None, outputs: list[str] = <factory>)[source]¶
Bases:
objectWhat a run would do, as an operator’s
planreports it.str(plan)givesdescribe().- Variables:
operation (str) – The operation’s name, such as
"Lens".method (str) – The name of the method the config runs.
config (entoscope.operation.config.AbstractConfig) – The config the run would be made under.
steps (int) – How many units of work the run makes.
steps_label (str) – What a unit of work is: an index element, a probe, a patched run or a prompt.
forward_passes (int) – Every forward pass the run makes, the cached runs of the prompts included.
backward_passes (int) – Every backward pass the run makes.
batches (int | None) – How many batches the run makes, set only for a method that runs the model a batch at a time.
index (list[pandas.DataFrame]) – One frame per index, the same frame
index.csvwould hold.prompts (list[tuple[str, str, int]]) – One
(label, prompt, token count)per prompt.activations (list[str]) – The transformer activation names the run caches.
reads_full_cache (bool) – Whether the run caches every activation, in which case
activationsis empty.save_dir (str | None) – The directory the run would write into.
outputs (list[str]) – The file names the run would write into
save_dir.
- class entoscope.operation.collector.AbstractCollector(operation_input: InputT, model: TransformerBridge | CachedModel, save_dir: str | None = None)[source]¶
Bases:
ABC,Generic[InputT]Accumulates one operation run’s per-location results and writes them to
save_dir.A subclass owns the shape of a result (a DataFrame, a list of graphs, …), which it allocates as it is constructed, fills one location’s cell at a time through
collectand exposes asresult.savecreatessave_dirand hands it to_save_result, which owns everything the run leaves behind: its result, the index files beside it and the config it ran under.Every collector takes the model whether or not it reads one, so an operator constructs whichever
collector_classit was given the same way, and swapping in a collector that labels the prompt’s tokens (or readscfg) for one that does not is only a class name.- Parameters:
operation_input – The input the run iterates.
model – The model the run reads.
save_dir – The directory
savewrites into, orNoneto write nothing.
- abstract property result: Any¶
What the run collected so far.
- abstractmethod collect(*args: Any, **kwargs: Any) None[source]¶
Take one result from the run’s method, filed under the index position it belongs to.
- Parameters:
*args – The result and its position, as each collector declares them.
**kwargs – The same, by keyword.
- output_files() list[str][source]¶
List the file names
savewrites intosave_dir, which a plan reports as the run’s output.Subclasses extend this to mirror their own
_save_result. It is deliberately not abstract:collector_classis an extension point, so a collector that declares no files keeps working and is reported as writing none. A plan asks a collector nothing was collected into, so an override may read the input but never the collected result.- Returns:
The file names, relative to
save_dir.
- class entoscope.operation.collector.FrameCollector(operation_input: InputT, model: TransformerBridge | CachedModel, save_dir: str | None = None)[source]¶
Bases:
AbstractCollector[InputT]A collector whose result is a frame with one row per row of the index the run swept.
Cells arrive one at a time under the index position they fill and are glued back onto that index’s own columns, so a subclass says only which indices the run has and what a cell holds. It writes
result.csv, an index CSV per index andconfig.json.- Parameters:
operation_input – The input the run iterates.
model – The model the run reads.
save_dir – The directory
savewrites into, orNoneto write nothing.
- property result: DataFrame¶
The index’s columns followed by the collected cells, one row per index row.
- output_files() list[str][source]¶
List the file names
savewrites intosave_dir, which a plan reports as the run’s output.Subclasses extend this to mirror their own
_save_result. It is deliberately not abstract:collector_classis an extension point, so a collector that declares no files keeps working and is reported as writing none. A plan asks a collector nothing was collected into, so an override may read the input but never the collected result.- Returns:
The file names, relative to
save_dir.
- class entoscope.operation.collector.FigureMixin[source]¶
Bases:
Generic[FigureT]Names the figure a collector draws, read off the collector’s own type argument.
The figure is the last type parameter of whichever
FigureMixina collector names, so it is stated once, in the base list. An explicit assignment still wins, and an intermediate subclass that leaves the parameter free resolves nothing and inherits.- Variables:
figure_class (type[entoscope.operation.collector.FigureT]) – The
Figurethe collector draws.
- class entoscope.operation.collector.VisualizationCollector(operation_input: InputT, model: TransformerBridge | CachedModel, save_dir: str | None = None)[source]¶
Bases:
FrameCollector[InputT],FigureMixin[FigureT],ABCA
FrameCollectorthat also draws its frame as an interactive Plotlyresult.html.Kept apart from the base collectors so that anything which only needs the CSVs builds no figure, and so visualization is opt-in by collector choice rather than by a config flag. Every
GridFigureis built from the same four arguments and reads what it draws off them, so a subclass names its figure in its base list and nothing else. The page’s plots are handed back asfigurestoo, so a run that writes nothing can still be looked at.- property figures: dict[Any, go.Figure]¶
The plots
result.htmlholds, drawn from what the run collected so far.Keyed by the figure’s panels, in page order. They are drawn afresh on every read.
- output_files() list[str][source]¶
List the file names
savewrites intosave_dir, which a plan reports as the run’s output.Subclasses extend this to mirror their own
_save_result. It is deliberately not abstract:collector_classis an extension point, so a collector that declares no files keeps working and is reported as writing none. A plan asks a collector nothing was collected into, so an override may read the input but never the collected result.- Returns:
The file names, relative to
save_dir.
Index-grid plotting shared by the operations whose result is a tidy frame over an Index.
An Index lays its rows out as dense per-axis codes and the result frame carries the original
source_*/target_* values, so laying one out is the same problem for every operation and only
the cell payload differs. Everything produced here ends up inside plotly HTML, so values are escaped
once, in format_value, rather than at each call site. Figure, the page every operation’s
HTML is written through, grid-drawn or not, lives here too.
A run rarely reads one number off itself: patching names several metrics and the lens keeps a whole top-k. A page is therefore a list of panels, each drawn as its own plot from the same result, and an operation says only what its panels are.
- entoscope.operation.visualization.format_value(value: Any) str[source]¶
Format a result cell for a page, HTML-escaped.
- Parameters:
value – An enum, a list or tuple, a float,
Noneor anythingstrcan print.- Returns:
An enum’s value, a list in brackets, a float to four significant digits,
"-"for a missing value, orstr(value).
- entoscope.operation.visualization.label_column(frame: DataFrame, axis: str, prefixes: Sequence[str]) str | None[source]¶
Find the
source_*ortarget_*column holding the real values behindaxis’s dense codes.A column that is not fully populated is rejected: concatenating a shorter source index onto a longer target one leaves NaN behind, and a half-filled axis labels nothing.
- Parameters:
frame – An index’s frame, as
Index.to_dflays it out.axis – An axis of the index, such as
"layer".prefixes – The column prefixes to try, in order of preference.
- Returns:
The first fully populated column, or
Nonewhen there is none.
- entoscope.operation.visualization.tick_text(frame: DataFrame, grid: IndexGrid, axis: str, prefixes: Sequence[str], tokens: dict[str, list[str]]) dict[int, str][source]¶
Label every code of one axis of the grid.
A position is labeled with its token, a row without a value on the axis with its full coordinates.
- Parameters:
frame – The index’s own frame, one row per result row, as
Index.to_dflays it out.grid – The grid.
axis – The axis to label.
prefixes – The column prefixes to read values from, in order of preference.
tokens – The prompt’s token strings, keyed by the position column they label.
- Returns:
The label of each of the axis’s codes.
- entoscope.operation.visualization.describe_row(row: Series, prefix: str) str[source]¶
Describe the location of one index row on one side of the index.
- Parameters:
row – A row of the index’s frame, as
Index.to_dflays it out.prefix – The side to describe,
"source_"or"target_".
- Returns:
The activation followed by every axis the row names, such as
attn.z · layer 3 · head 5.
- entoscope.operation.visualization.diverging_range(values: Sequence[float], midpoint: float) tuple[float, float][source]¶
Centre a colour range on
midpointwide enough for every value.- Parameters:
values – The values to colour, NaN and infinities ignored, as no finite range holds them.
midpoint – The value drawn in the middle colour.
- Returns:
The range’s low and high ends, one unit either side of
midpointwhen every value equals it.
- entoscope.operation.visualization.write_html(path: str, title: str, *figures: Figure) None[source]¶
Write figures one under the other into a self-contained HTML page.
plotly.js is embedded once, with the first figure, so the page opens with no network.
- Parameters:
path – The file to write.
title – The page’s title.
*figures – The figures, in page order.
- class entoscope.operation.visualization.Figure[source]¶
Bases:
ABC,Generic[PanelT]A self-contained interactive HTML page, one plotly figure per panel.
titlenames the page, and each panel titles its own plot, so the two do not have to repeat.- abstract property title: str¶
The page’s title.
- abstractmethod panels() list[PanelT][source]¶
Name the page’s panels.
- Returns:
The panels, in page order, each drawn as its own plot.
- abstractmethod figure(panel: PanelT) Figure[source]¶
Draw one panel.
- Parameters:
panel – One of
panels().- Returns:
The panel’s plot.
- class entoscope.operation.visualization.IndexGrid(x_axis: str | None, y_axes: list[str], codes: dict[str, list[int]], x_codes: list[int], y_keys: list[tuple[int, ...]], rows: dict[tuple[tuple[int, ...], int], int])[source]¶
Bases:
objectWhere each result row sits once the axes a run varies over are laid out.
Built from the index’s own layout codes rather than from the result frame, so the axis rule can be read, and tested, without a run behind it. An axis the run does not vary over is dropped: it offers no comparison and belongs in the subtitle. Axes beyond the first two fold into composite y keys rather than being aggregated away, so nothing is hidden.
y_keysascends and plotly draws the first at the bottom, putting layer 0 there as the circuit figures do.- Variables:
x_axis (str | None) – The axis across, the last varying one in
AXIS_ORDER, orNonewhen nothing varies.y_axes (list[str]) – The other varying axes, folded together into the rows.
codes (dict[str, list[int]]) – Every axis’s code for every result row.
x_codes (list[int]) – The distinct codes across, ascending.
y_keys (list[tuple[int, ...]]) – The distinct row keys, one code per
y_axesentry, ascending.rows (dict[tuple[tuple[int, ...], int], int]) – The result row at each
(y_key, x_code)cell.
- classmethod of(layout: DataFrame) IndexGrid[source]¶
Lay out an index.
- Parameters:
layout – The index’s layout codes, one column per axis, as
Index.layout_dfholds them.- Returns:
The grid.
- property is_bar: bool¶
Whether at most one axis varies, so the grid is drawn as a bar chart.
- entoscope.operation.visualization.x_labels(frame: DataFrame, grid: IndexGrid, prefixes: Sequence[str], tokens: dict[str, list[str]]) list[str][source]¶
Label the grid’s columns.
- Parameters:
frame – The index’s own frame, one row per result row, as
Index.to_dflays it out.grid – The grid.
prefixes – The column prefixes to read values from, in order of preference.
tokens – The prompt’s token strings, keyed by the position column they label.
- Returns:
One label per x code, or the single row’s location when no axis varies.
- entoscope.operation.visualization.y_labels(frame: DataFrame, grid: IndexGrid, prefixes: Sequence[str], tokens: dict[str, list[str]]) list[str][source]¶
Label the grid’s rows.
- Parameters:
frame – The index’s own frame, one row per result row, as
Index.to_dflays it out.grid – The grid.
prefixes – The column prefixes to read values from, in order of preference.
tokens – The prompt’s token strings, keyed by the position column they label.
- Returns:
One label per row key, each axis named when several fold into the rows.
- entoscope.operation.visualization.cell_labels(frame: DataFrame, grid: IndexGrid, prefixes: Sequence[str], tokens: dict[str, list[str]]) list[str][source]¶
Give every result row the coordinates of its cell.
- Parameters:
frame – The index’s own frame, one row per result row, as
Index.to_dflays it out.grid – The grid.
prefixes – The column prefixes to read values from, in order of preference.
tokens – The prompt’s token strings, keyed by the position column they label.
- Returns:
One label per result row, such as
layer 3 · position 4: " France".
- entoscope.operation.visualization.titled(kind: str, config: AbstractConfig) str[source]¶
Title a page after the operation and, when it has one, the config’s id.
- Parameters:
kind – What the page shows, such as
"Logit lens".config – The config the run was made under.
- Returns:
kind, followed by the config’s id in parentheses when it has one.
- class entoscope.operation.visualization.GridFigure(result: DataFrame, operation_input: InputT, model: TransformerBridge | CachedModel, index: Index)[source]¶
Bases:
Figure[PanelT],Generic[InputT,PanelT]An operation’s result frame drawn as the grid its index describes, once per panel.
Choosing the axes, pivoting the rows onto them and falling back to a bar when only one of them varies is the same work whatever was run, so it lives here. A subclass says only what its own result means: which column a panel holds its value in, how to colour it, and what a cell reads on hover. Those answers are read off the run’s input, which comes along for that reason, and because it lets a collector name a figure class and construct it without knowing what produced the frame. Every panel shares one grid and one set of axis labels, so those are resolved once, at construction.
- Parameters:
result – The run’s result frame.
operation_input – The input the run iterated.
model – The model the run read.
index – The index that laid the result out, whose layout codes build the grid.
- Variables:
coordinates – The index’s own frame, which every cell, tick and hover is labelled from: the result frame also holds the values, and a value column named with a side’s prefix would read as a coordinate.
prefixes – The column prefixes axis values are read from, in order of preference: the index’s own
layout_prefixes, so the axes are labelled by the side the grid was laid out along.colorscale – The plotly colour scale cells are drawn with.
- abstractmethod heading(panel: PanelT) str[source]¶
Name one panel’s plot.
- Parameters:
panel – One of
panels().- Returns:
The plot’s heading, shown above the subtitle line.
- abstractmethod subtitle(panel: PanelT) str[source]¶
Describe what one panel’s plot holds, under its heading.
- Parameters:
panel – One of
panels().- Returns:
The subtitle, as HTML.
- abstractmethod value_column(panel: PanelT) str[source]¶
Name the result column one panel colours its cells by.
- Parameters:
panel – One of
panels().- Returns:
A column of the result frame.
- abstractmethod hover(panel: PanelT, position: int, coordinate: str) str[source]¶
Write what one cell shows on hover.
- Parameters:
panel – One of
panels().position – The result row the cell is built from.
coordinate – The cell’s laid-out coordinates, from
cell_labels.
- Returns:
The hover text, as HTML.
- value_title(panel: PanelT) str[source]¶
Title one panel’s colour bar.
- Parameters:
panel – One of
panels().- Returns:
The title, as text, by default the value column’s name.
- value_range(panel: PanelT, values: Sequence[float]) tuple[float, float][source]¶
Choose the range one panel’s colours span.
- Parameters:
panel – One of
panels().values – The panel’s values, one per result row.
- Returns:
The low and high ends of the colour range, by default 0 and 1.
- bar_yaxis(panel: PanelT) dict[source]¶
Lay out the value axis of one panel drawn as a bar chart.
- Parameters:
panel – One of
panels().- Returns:
plotly
yaxissettings, by default only the axis title.
- cell_text(panel: PanelT) list[str] | None[source]¶
Choose the label printed inside each cell of one panel.
The size cap is applied by
figure, so a subclass answers for its own result alone.- Parameters:
panel – One of
panels().- Returns:
One label per result row, or
Noneto leave the cells bare, the default.
- str_tokens() dict[str, list[str]][source]¶
Read the prompt’s token strings, to label the position axes with.
Read once at construction. A figure whose index carries no position, or a run with no prompt to read one from, labels its ticks with the coordinates alone.
- Returns:
The token strings for each position column of the result, keyed by that column. Empty by default.