API reference¶
The public surface of the engram package, generated from the source docstrings.
from engram import (
EditorConfig, EngramEditor, edit_llm, get_engram, apply_engram,
CovarianceCollector, Statistics,
EngramResult, LayerScaleInfo,
count_ratio, weight_norm, effective_rank, uniform, compose,
LayerHandler, LinearHandler, Conv1DHandler,
)
EditorConfig
dataclass
¶
Configuration for engram extraction.
Attributes:
| Name | Type | Description |
|---|---|---|
storage_device |
Optional[Union[str, device]]
|
Device for accumulating/holding covariance matrices.
|
absorb_bias |
bool
|
When |
EngramEditor
¶
EngramEditor(
model: Module,
config: Optional[EditorConfig] = None,
adapters: Optional[List[Any]] = None,
)
Covariance-based engram extractor for PyTorch / HuggingFace models.
The editor hooks every module whose type is in self.registry (by default
nn.Linear and, when transformers is importable, HF Conv1D for the
GPT-2 family). To restrict covariance to answer tokens, pass a mask_fn to
collect_statistics (e.g. mask_fn=lambda b: b["labels"] != -100).
Example::
editor = EngramEditor(model, EditorConfig())
target = editor.collect_statistics(forget_loader, batch_fn=bf) # Statistics
total = editor.collect_statistics(all_loader, batch_fn=bf)
edited = editor.edit(target, total, alpha=1.0) # paper edit (count_ratio)
# or, to reuse / re-scale the projection without recollecting:
# engram = editor.compute_engram_weights(target, total) # EngramResult
# edited = editor.apply(engram, alpha=0.6, scale=weight_norm(1.0))
Source code in src/engram/editor.py
collect_statistics
¶
collect_statistics(
dataloader: Iterable[Any],
target_modules: Optional[Union[str, List[str]]] = None,
batch_fn: Optional[Callable[[Any], Any]] = None,
mask_fn: Optional[Callable[[Any], Tensor]] = None,
layers_to_transform: Optional[
Union[int, List[int]]
] = None,
layers_pattern: Optional[Union[str, List[str]]] = None,
target_layers: Optional[List[str]] = None,
) -> Statistics
Accumulate the mean input covariance mean(x^T x) + counts per supported layer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dataloader
|
Iterable[Any]
|
iterable of batches. |
required |
target_modules
|
Optional[Union[str, List[str]]]
|
which modules to collect, LoRA/PEFT convention. A
list matches by name suffix ( |
None
|
layers_to_transform
|
Optional[Union[int, List[int]]]
|
restrict to these decoder-layer indices (an int or
list of ints), like PEFT. Combined with |
None
|
layers_pattern
|
Optional[Union[str, List[str]]]
|
container name(s) holding the layer index
( |
None
|
target_layers
|
Optional[List[str]]
|
deprecated alias of |
None
|
batch_fn
|
Optional[Callable[[Any], Any]]
|
maps a batch to model inputs (a tensor, a tuple of
positional args, or a dict of keyword args). Defaults to
|
None
|
mask_fn
|
Optional[Callable[[Any], Tensor]]
|
optional |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
A |
Statistics
|
class: |
Statistics
|
on |
|
Statistics
|
input dim, or |
|
Statistics
|
( |
Source code in src/engram/editor.py
merge_statistics
staticmethod
¶
merge_statistics(*stats: Statistics) -> Statistics
Count-weighted merge of several :class:Statistics (used to build totals).
compute_engram_weights
¶
compute_engram_weights(
target_covariances: Union[Statistics, List[Statistics]],
total_covariance: Statistics,
*,
compute_erank: bool = False
) -> EngramResult
Compute the per-layer engram projection P = W . C_target . pinv(C_total).
C_* are the mean covariances from :meth:collect_statistics. P is the
pure projection — the paper's n / N sample-count factor is not folded in
here; it is applied at edit time by the scaling function (default
:func:engram.scaling.count_ratio), so the scaling can be swapped without
recomputing. Whether a layer was bias-absorbed is inferred from the covariance
size (D == in + 1), kept consistent with collection without re-passing a flag.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
target_covariances
|
Union[Statistics, List[Statistics]]
|
statistics of the data to isolate (the "forget"/target
set). A single :class: |
required |
total_covariance
|
Statistics
|
statistics of the total/reference set. |
required |
compute_erank
|
bool
|
also compute each layer's target/total effective rank and store
them in the result (needed only by :func: |
False
|
Returns:
| Name | Type | Description |
|---|---|---|
An |
EngramResult
|
class: |
EngramResult
|
plus the per-layer scale inputs (counts |
|
EngramResult
|
|
Source code in src/engram/editor.py
180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 | |
apply
¶
apply(
engram: EngramResult,
*,
alpha: float = 1.0,
scale: Optional[ScaleFn] = None,
inplace: bool = False
) -> Module
Subtract the engram from the model: W <- W - alpha * f_l * P_l.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
engram
|
EngramResult
|
result of :meth: |
required |
alpha
|
float
|
global edit strength (the paper's strength; |
1.0
|
scale
|
Optional[ScaleFn]
|
a scaling function |
None
|
inplace
|
bool
|
edit |
False
|
Returns:
| Type | Description |
|---|---|
Module
|
the edited model. Fused-expert keys are written to their 3D-Parameter slices |
Module
|
via the registered adapter. |
Source code in src/engram/editor.py
edit
¶
edit(
target_covariances: Union[Statistics, List[Statistics]],
total_covariance: Statistics,
*,
alpha: float = 1.0,
scale: Optional[ScaleFn] = None,
inplace: bool = False,
compute_erank: bool = False
) -> Module
One call: :meth:compute_engram_weights then :meth:apply; returns the edited model.
Source code in src/engram/editor.py
save_statistics
¶
save_statistics(
stats: Statistics, path: Union[str, Path]
) -> None
Save collected :class:Statistics (mean covariances + counts) with torch.save.
load_statistics
¶
load_statistics(path: Union[str, Path]) -> Statistics
Load :class:Statistics onto the storage device (the model's device by default).
edit_llm
¶
edit_llm(
model: Module,
tokenizer: Any,
forget: Iterable[Item],
total: Iterable[Item],
*,
alpha: float = 1.0,
scale: Optional[ScaleFn] = None,
target_modules: Optional[Union[str, List[str]]] = None,
layers_to_transform: Optional[
Union[int, List[int]]
] = None,
layers_pattern: Optional[Union[str, List[str]]] = None,
max_length: int = 512,
batch_size: int = 8,
inplace: bool = False,
config: Optional[EditorConfig] = None,
adapters: Optional[List[Any]] = None
) -> Module
Collect forget/total covariances over text and apply the engram, in one call.
Exactly :func:get_engram followed by :func:apply_engram. To sweep alpha without
recollecting, call those two directly: engram = get_engram(...) once, then
apply_engram(model, engram, alpha=...) per setting.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model
|
Module
|
a HuggingFace causal LM (or any module whose |
required |
tokenizer
|
Any
|
a HF tokenizer (used as |
required |
forget
|
Iterable[Item]
|
the set to unlearn — an iterable of |
required |
total
|
Iterable[Item]
|
the reference set (typically |
required |
alpha
|
float
|
global edit strength. |
1.0
|
scale
|
Optional[ScaleFn]
|
per-layer scaling function (default :func: |
None
|
target_modules
|
Optional[Union[str, List[str]]]
|
restrict which layers are edited (LoRA/PEFT convention; list =
name suffix, str = regex), forwarded to :meth: |
None
|
layers_to_transform
|
Optional[Union[int, List[int]]]
|
restrict to these decoder-layer indices (forwarded). |
None
|
layers_pattern
|
Optional[Union[str, List[str]]]
|
the layer-index container name, e.g. |
None
|
max_length
|
int
|
tokenizer truncation length. |
512
|
batch_size
|
int
|
covariance-collection batch size. |
8
|
inplace
|
bool
|
edit |
False
|
config
|
Optional[EditorConfig]
|
an :class: |
None
|
adapters
|
Optional[List[Any]]
|
optional fused-MoE adapters (e.g. |
None
|
Returns:
| Type | Description |
|---|---|
Module
|
the edited model. |
Source code in src/engram/llm.py
get_engram
¶
get_engram(
model: Module,
tokenizer: Any,
forget: Iterable[Item],
total: Iterable[Item],
*,
target_modules: Optional[Union[str, List[str]]] = None,
layers_to_transform: Optional[
Union[int, List[int]]
] = None,
layers_pattern: Optional[Union[str, List[str]]] = None,
max_length: int = 512,
batch_size: int = 8,
compute_erank: bool = False,
config: Optional[EditorConfig] = None,
adapters: Optional[List[Any]] = None
) -> EngramResult
Tokenize forget/total, collect covariances, and compute the engram (alpha-free).
The expensive half of :func:edit_llm — two covariance passes plus one
pseudo-inverse per layer — separated out so you can run it once and then call
:func:apply_engram cheaply for as many alpha / scale settings as you like
(the projection P = W . C_target . pinv(C_total) carries no alpha). Item types
and selection knobs are the same as :func:edit_llm.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model
|
Module
|
a HuggingFace causal LM (forward takes |
required |
tokenizer
|
Any
|
a HF tokenizer (no chat template is applied). |
required |
forget
|
Iterable[Item]
|
the set to unlearn — an iterable of |
required |
total
|
Iterable[Item]
|
the reference set (typically |
required |
target_modules
|
Optional[Union[str, List[str]]]
|
restrict which layers are collected (LoRA/PEFT convention). |
None
|
layers_to_transform
|
Optional[Union[int, List[int]]]
|
restrict to these decoder-layer indices. |
None
|
layers_pattern
|
Optional[Union[str, List[str]]]
|
the layer-index container name, e.g. |
None
|
max_length
|
int
|
tokenizer truncation length. |
512
|
batch_size
|
int
|
covariance-collection batch size. |
8
|
compute_erank
|
bool
|
also compute per-layer effective ranks (needed only by
:func: |
False
|
config
|
Optional[EditorConfig]
|
an :class: |
None
|
adapters
|
Optional[List[Any]]
|
optional fused-MoE adapters (e.g. |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
an |
EngramResult
|
class: |
Source code in src/engram/llm.py
apply_engram
¶
apply_engram(
model: Module,
engram: EngramResult,
*,
alpha: float = 1.0,
scale: Optional[ScaleFn] = None,
inplace: bool = False,
adapters: Optional[List[Any]] = None
) -> Module
Apply a precomputed engram (from :func:get_engram): W <- W - alpha * f_l * P_l.
Cheap — a model copy plus one subtraction per layer — so call it repeatedly with
different alpha / scale to tune the forget/retain trade-off without
re-collecting covariances. alpha=0 is a no-op (returns the unedited model).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model
|
Module
|
the same model :func: |
required |
engram
|
EngramResult
|
the :class: |
required |
alpha
|
float
|
global edit strength. |
1.0
|
scale
|
Optional[ScaleFn]
|
per-layer scaling function (default :func: |
None
|
inplace
|
bool
|
edit |
False
|
adapters
|
Optional[List[Any]]
|
fused-MoE adapters, if any were passed to :func: |
None
|
Returns:
| Type | Description |
|---|---|
Module
|
the edited model. |
Source code in src/engram/llm.py
LinearHandler
¶
Conv1DHandler
¶
Bases: LayerHandler
Handler for HuggingFace Conv1D (GPT-2). Weight is stored [in, out].
The effective linear operator is weight.T (shape [out, in]), so the
canonical matrix is weight.t() and the engram weight part is transposed
back to [in, out] to match module.weight.
LayerHandler
¶
Bases: ABC
Abstract per-layer-type handler.
absorb_bias is threaded through so a single handler covers both the
plain (y = Wx) and bias-absorbed (y = [W|b][x;1]) cases.
CovarianceCollector
¶
CovarianceCollector(
model: Module,
config: EditorConfig,
registry: Dict[Type[Module], LayerHandler],
target_modules: Optional[Union[str, List[str]]] = None,
layers_to_transform: Optional[
Union[int, List[int]]
] = None,
layers_pattern: Optional[Union[str, List[str]]] = None,
adapters: Optional[List[Any]] = None,
)
Context manager accumulating per-layer mean input covariance + sample count.
Source code in src/engram/collectors.py
set_mask
¶
Set the per-batch token mask and reset routing-alignment state.
Statistics & scaling¶
collect_statistics returns a Statistics (mean covariances + sample counts);
compute_engram_weights returns an EngramResult of per-layer projections. The
per-layer edit weighting is a pluggable scaling function — see
Guide → Scaling.
Statistics
dataclass
¶
Per-layer mean input covariance (cov) and sample count (count).
cov[name] is the mean of x^T x over the count[name] rows that entered
layer name during collection. Keys are module names, or
"<experts>.gate_up_proj.<e>" / "....down_proj.<e>" for fused-MoE experts.
Behaves like a read-only mapping over cov (stats[name], name in stats,
iteration) with the parallel count dict alongside.
to
¶
Move every covariance to device (counts are plain ints, copied as-is).
merge
staticmethod
¶
Count-weighted merge: C = sum(n_i C_i) / sum(n_i), N = sum(n_i).
Equivalent to having collected over the concatenated token streams. Keys are
unioned; a key present in only some inputs contributes only its own
(count, mean). Combined incrementally (C += n_i/(N+n_i) (C_i - C)) so
no large n_i * C_i intermediate is formed.
Source code in src/engram/stats.py
save
¶
load
staticmethod
¶
Load a :class:Statistics. Rejects the legacy raw-covariance dict format.
Source code in src/engram/stats.py
EngramResult
dataclass
¶
EngramResult(
layers: Dict[str, LayerScaleInfo] = dict(),
bias: Dict[str, Tensor] = dict(),
)
Output of :meth:EngramEditor.compute_engram_weights: projections + scale inputs.
layers[name].projection is the pure engram projection (no n / N);
bias[name] is the matching bias projection for bias-absorbed layers. Pass the
whole result to :meth:EngramEditor.apply.
LayerScaleInfo
dataclass
¶
LayerScaleInfo(
name: str,
weight_fro: float,
projection: Tensor,
n: int,
N: int,
target_erank: Optional[float] = None,
total_erank: Optional[float] = None,
)
Per-layer inputs handed to a scaling function.
count_ratio
¶
f_l = (n_l / N_l) ** power. Default (power=1) reproduces the paper exactly.
n_l / N_l is the target/total sample-count ratio implicit in the paper's summed
covariances. n == 0 or N == 0 -> 0 (a layer no target token reached is not edited).
Source code in src/engram/scaling.py
weight_norm
¶
f_l = (rel_l / max rel) ** power with rel_l = ||P_l|| / ||W_l||.
Edits each layer in proportion to how strongly the engram occupies it (the relative
Frobenius norm). This is the per-layer "adaptive" weighting; compose with
:func:count_ratio to also keep the paper's n / N factor.
Source code in src/engram/scaling.py
effective_rank
¶
f_l = (er(C_target_l) / er(C_total_l)) ** power per layer.
The ratio of the effective rank (see :func:_erank) of the target covariance to
that of the total — how dimensionally rich the forget inputs are relative to the whole
at each layer. Needs the per-layer effective ranks, i.e.
compute_engram_weights(..., compute_erank=True).
Source code in src/engram/scaling.py
uniform
¶
f_l = 1 for every layer (subtract the bare projection, ignoring counts/norms).
compose
¶
Multiply scaling functions per layer: f_l = prod_i fns[i](infos)[l].
e.g. compose(count_ratio(1.0), weight_norm(1.0)) is the paper's n / N
weighting further modulated by relative weight-norm.
Source code in src/engram/scaling.py
MoE (optional)¶
Support for transformers ≥5 fused-expert MoE layers lives in a separate, detachable module — import it explicitly; the core never depends on it:
from engram import EngramEditor
from engram.moe import FusedExpertAdapter, apply_engram_weights
editor = EngramEditor(model, adapters=[FusedExpertAdapter()])
See Guide → Mixture-of-experts.
FusedExpertAdapter
¶
Collects per-expert input covariance for standard fused MoE experts (opt-in).
Plugs into the core via three calls: attach/detach (the collector adds
its hooks during a covariance pass) and owns/weight_for (the editor asks
it to resolve covariance keys it doesn't recognize as modules).
Source code in src/engram/moe.py
owns
¶
True if key names a fused-expert slice present on model.
Stateless — resolved from model, so it works whether or not a covariance
pass was run this session (e.g. applying engrams loaded from disk).
Source code in src/engram/moe.py
apply_delta
¶
Subtract delta from the expert's 3D-Parameter slice, in place.
Source code in src/engram/moe.py
apply_engram_weights
¶
apply_engram_weights(
model: Module,
weight_engrams: Dict[str, Tensor],
alpha: float = 1.0,
) -> None
Low-level: subtract alpha * weight_engrams[key] from each weight, handling fused keys.
weight_engrams is a plain {key: delta} of final per-key tensors (already
scaled by any per-layer factor). Real-module keys edit module.weight; fused-expert
keys ("<experts>.gate_up_proj.<e>") edit param.data[e] of the 3D Parameter. The
primary path is :meth:EngramEditor.apply, which dispatches fused keys here via the
adapter and applies the pluggable scaling for you; use this only when you hold raw deltas.