# Common API [Home](../README.md) / [Documentation](README.md) Model-specific setup lives in the [model guides](models/README.md). Individual factory parameters and examples live in the [method guides](methods/README.md). ## Bind and inspect ```python from vlmprobe import Prober probe = Prober(model, processor=processor, adapter="auto", result_device="cpu") support = probe.describe() print(support["methods"]["lens.logit"]) # {"available": True, "sites": [...], "requires": ..., "missing": None} ``` `model` is a caller-owned model instance. Automatic selection uses exact known classes, registered factories, or a model's `probing_adapter()` protocol. For another architecture, pass an explicit [adapter](ADAPTERS.md). `describe()` runs no inference and exposes method sites and missing capabilities. ## Prepare inputs ```python inputs = probe.prepare(prompt=formatted_prompt, image=pil_image, device="cuda:0") result = probe.lens.logit(layers=[0]).run(inputs) # Existing processor output works directly. native_inputs = processor(text=formatted_prompt, images=pil_image, return_tensors="pt") native_inputs = {k: v.to("cuda:0") for k, v in native_inputs.items()} result = probe.lens.logit(layers=[0]).run(native_inputs) ``` `prepare(*, prompt, image=None, layout=None, device=None, **kwargs)` forwards processor arguments and returns `ProbeInputs`. Apply the model's chat template first, as shown on each model page. The optional layout uses expanded sequence coordinates. Native dictionary values need not all be tensors; `prepare` moves tensor values only. Probing requires full-sequence inputs and rejects KV caches, `use_cache=True`, and nonzero `logits_to_keep`. For spatial plots, inspect [input geometry and preprocessing](INPUT_GEOMETRY.md). The `visual_grid` helper uses audited model-specific grid metadata and checks token counts. `preprocessing_report` is an opt-in diagnostic; it never changes the processor output or claims that geometric retention proves subject visibility. ## Select layers and tokens MJ-Lens uses an explicit **decoder block-input** convention; ordinary lens indices refer to the residual sites below. Its source/target modalities and final-normalization target are declared by the adapter. See the [MJ-Lens guide](methods/mj_lens.md) before reusing another lens's layer IDs. Factory configuration is reusable: `method = probe.lens.logit(...)`, followed by `method.run(batch)` for each batch. Layers are zero-based. For native HF adapters, residual layer `i` is the next block's input (or final norm input), including any visual injection after block `i`. | Selector | Meaning | | --- | --- | | `layers=None` | All available sites for that method; propagation uses the contiguous prefix from layer 0. | | `layers=3` or `layers=[3, 7]` | Explicit unique layer indices, preserving order. | | `tokens="all"` | All valid positions. | | `tokens="visual"` / `"text"` | Positions selected by the adapter's visual mask or its valid complement. | | `tokens="last_prompt"` | Last prompt position, or last valid position if no prompt mask exists. | | `tokens="prediction"` | Last original prompt position plus valid generated positions outside `layout.prompt`; used for generation-step steering. | | `tokens=4` / `[4, 5]` | Expanded sequence positions, valid in every batch example. | | `tokens=boolean_mask` | Explicit `[batch, expanded_sequence]` selection, excluding padding. | `queries` and `keys` use the same selector conventions. Residual interventions at multiple layers are separate experiments by default. Steering and knockout accept `joint=True` to edit all selected live sites together. Rollout and relevance compose consecutive attention layers. For attention maps, query/key selectors are applied after dense attention and the required gradients are computed. Returned maps keep full native token axes, with unselected entries masked to zero; the selectors are not a sparse execution or memory-saving mode. Attention tensors scale quadratically with sequence length. Integration samples run sequentially, so reducing `steps` mainly changes runtime rather than the dense per-sample memory requirement. See [attention memory and precision](methods/attention_maps.md#memory-and-numerical-precision). ### Joint interventions and generation ```python window = list(range(8, 17)) # nine layers, center 12 effect = probe.causal.knockout( layers=window, queries=question_mask, keys=visual_mask, joint=True, ).run(inputs, metric=metric) vsv = probe.causal.vsv(image_inputs, same_text_without_image) steering = vsv.configure(strength=0.17, tokens="prediction") caption = steering.generate(image_inputs, direction=vsv.directions, max_new_tokens=144, do_sample=False) ``` Joint `.run` returns one score per batch item, rather than a stack of separate layer effects. `.forward(..., capture=[site, ...])` executes only the live intervention forward and returns a `ForwardTrace`. Joint steering also provides uncached greedy `.generate` for one unpadded example. Image placeholders must already be expanded. `prediction` fixes the original prompt boundary and reapplies edits to all prior generation positions in each full-prefix replay. Teacher-forced analyses must set `TokenLayout.prompt` to that same boundary. Outputs include `generated_ids`, `fullsequence_ids`, and `decoded_text` metadata. Original EAP-IG is configured with `probe.causal.eap_ig(graph="transformer", steps=5)`; the native GPT-2 provider supports input-embedding integration and actual complement-edge circuit evaluation. `graph="activation"` retains the explicitly declared edge-message interpolation variant. See the [method guide](methods/eap_ig.md). ## Metrics and explicit layouts For a ViT vision-classification control, use `ClassScore(class_id)` on the native `[B,C]` logits. It supports `kind="logit"` or `"probability"` and keeps patch coordinates separate from the output-class axis. See [ViT](models/vit.md) and the [attention-map interfaces](methods/attention_maps.md). For single-token candidate answers: ```python from vlmprobe import TokenMargin positive = processor.tokenizer.encode(" red", add_special_tokens=False) negative = processor.tokenizer.encode(" blue", add_special_tokens=False) assert len(positive) == len(negative) == 1 metric = TokenMargin(positive[0], negative[0], position="last_prompt") result = probe.causal.patch(layers=[0]).run(corrupt, source=clean, metric=metric) ``` The score is positive-token logit minus negative-token logit. It needs exactly one prediction position per example. For teacher-forced multi-token answers, supply the full prompt+answer sequence and an answer mask: ```python from vlmprobe import ProbeInputs, TokenLayout, SequenceLogProb inputs = ProbeInputs(model_kwargs, TokenLayout( valid=valid_mask, visual=visual_mask, prompt=prompt_mask, token_ids=expanded_ids, )) metric = SequenceLogProb(answer_mask, reduction="sum") ``` Masks are boolean `[B,T]`; token IDs are long `[B,T]`, aligned with actual model logits. Sequence scoring shifts logits by one and excludes visual/padding targets. Each answer token needs a preceding prediction position; EOS is scored only when selected. Score separate complete inputs for different candidate answers. Custom metrics may be `callable(logits) -> scalar_or_batch_tensor` or objects with `score(logits, layout)`. Preserve the graph for gradient methods. Attention profiles, Rollout and head logit attribution are target-independent. Their common `.run(inputs, metric=None)` signature accepts only an omitted or `None` metric; a non-`None` value raises `ValueError` before any model forward. These results record `metadata["metric"] = None`. Relevance, Grad-CAM, ATTATTR, TAM, Beyond Intuition and attention interventions require an explicit target metric and record the target they evaluate. Grad-CAM pools gradients only over selected keys permitted by the model's actual query-specific attention mask, including causal and local-window restrictions. Integration methods accumulate FP16/BF16 gradients in at least FP32 and preserve FP64, then average in that accumulator precision. These numerical and masking conventions are described in the [attention-map guide](methods/attention_maps.md#what-each-method-computes). ## Aligned interventions `alignment="strict"` checks valid/visual/prompt masks, expanded token IDs, and adapter-declared grid/position metadata. Source and receiver use identical coordinates. For intentionally changed text with verified positional alignment, `alignment="position"` relaxes only token-ID equality. Residual patching does not remap positions. Path patching can map selected donor sender positions onto selected base sender positions within an otherwise structurally aligned batch. ### Controlled paths ```python path = probe.causal.path( senders=[(0, 0)], receivers=[(1, 0, "v")], sender_tokens="visual", receiver_tokens="visual", ) result = path.run(corrupt, donor=clean, metric=metric) ``` This executes base, donor, controlled, and receiver-only forwards. It freezes every attention-head output in the controlled run, then replaces only the chosen sender positions with donor values. MLPs and normalization recompute; receiver Q/K/V is cached independently of its frozen head output. `receivers="residual"` selects the final residual before readout normalization. Multiple endpoints form one joint intervention with `.run()`. Use `.sweep()` to measure each sender independently through the configured receiver set: ```python senders = [(layer, head) for layer, point in sorted(probe.spec.path_heads.items()) for head in range(point.heads)] result = probe.causal.path(senders=senders, receivers="residual").sweep( clean, donor=corrupt, metric=metric, alignment="position", ) print(result.tensors["effect"].shape) # [independent paths, batch] ``` The sweep shares the unmodified base/donor caches, while running both controlled and receiver-only forwards for every sender. It executes `2 + 2N` forwards for `N` paths and preserves the same intervention rules as individual `.run()` calls. An optional `progress(completed, total, sender)` callback reports each completed path. The [notebook](../demos/path_patching_demo.ipynb) adds dataset preparation, per-example normalization, plotting, and saved experiment metadata. `donor_tokens` defaults to `sender_tokens`; explicit masks can select different role positions in each example, with equal selected counts per pair. Receiver injection always uses base coordinates. K/V indices name physical KV heads in GQA. See the [method guide](methods/path_patching.md) for scope and model support. ## Fitted-lens artifacts Tuned, Attention, Jacobian and MJ-Lens need calibration or `load` before `run`. The model's parameters and accumulated gradients are preserved. ```python binding = { "model_id": "checkpoint-and-revision", "tokenizer_id": "tokenizer-and-revision", "readout_id": "final-norm-and-lm-head-v1", "calibration_id": "dataset-split-and-preprocessing-v1", } lens = probe.lens.tuned(layers=[0], binding=binding) lens.fit(calibration_inputs, steps=100, lr=1e-3) lens.save("artifacts/tuned") restored = probe.lens.tuned(layers=[0], binding=binding).load("artifacts/tuned") result = restored.run(evaluation_inputs) ``` `fit` accepts one batch; `fit` and `load` return the bound object. Tuned/Attention retain weights across repeated fits but restart optimizers; their losses are available in `.losses[layer]`. Jacobian fitting replaces the transport estimate. For Tuned/Attention dataset calibration, use `.fit_batches(train_inputs, validation_inputs, directory, ...)`: it caches inputs once, keeps Adam across minibatches, validates each epoch and saves resumable optimizer state. Inspect `.training_history[layer]`. Jacobian Lens instead uses `vlmprobe.training.fit_jacobian_lenses`; MJ-Lens uses its own modality-conditioned `method.fit_batches(input_batches, ...)`. Neither derivative estimator uses Adam. MJ calibration records causal-pair counts, odd/even split-half diagnostics and resume counts. Its [complete guide](methods/mj_lens.md) explains block-input indices and text-versus-visual output heads. The [training guide](TRAINING.md) covers this interface, disk-backed readouts, the validated author-checkpoint importer and grouped Jacobian estimation. Artifact saving requires explicit identities and writes one file per layer. Loading validates the binding and dimensions. These files use this library's format, not a third-party lens checkpoint format. To reproduce the released Attention Lens initialization, new head decoders can use `head = model.get_output_embeddings()` followed by `.fit(calibration_inputs, initial_unembedding=head.weight, initial_bias=getattr(head, "bias", None), ...)`. The weight shape is `[V,D_residual]`; the optional bias is `[V]`. No final LayerNorm is inserted into head decoders. Supplying these options to an already fitted/loaded lens raises an error, preventing an accidental reset. See the [training guide](TRAINING.md). ## Results and persistence [MJ-Lens](methods/mj_lens.md) uses `probe.lens.mj(layers=..., source="visual", target="text", binding=...)`. Its source layers are **block inputs**, with L0 at the embedding stream. `.fit(inputs)` replaces one-batch estimates; `.fit_batches(input_batches, directory=..., resume=...)` accumulates exact causal-pair means with odd/even prompt diagnostics. It estimates derivatives without an optimizer. These coordinates and normalization differ from the original `probe.lens.jacobian()` and their artifacts are not interchangeable. Janus visual outputs additionally require a shared calibrated `fit_visual_readout(...)` and explicit `MJBatch` output-position masks/context. `.save(directory)` and `.load(directory)` validate model revision, source and target channel, source/final sites, readout, tokenizer/codebook and calibration identities. `decode_mj_codes(...)` requires a real codebook grid and native decoder, and only accepts many visual-source positions. See the [complete walkthrough](../examples/tutorials/lens_mj.py). `ProbeResult` contains `.method`, `.tensors`, and `.metadata`. Outputs are detached and moved to `result_device` (CPU by default). | Method | Main outputs | | --- | --- | | Logit, Tuned, Jacobian | `logits[L,N,V]`, `positions[N,2]`; Jacobian also `transported[L,N,D]` | | MJ-Lens | `logits[L,N,A]`, `transported[L,N,D]`, `positions[N,2]`; visual output adds `code_ids[L,N]`. `A` is the selected output alphabet. | | Embed | `token_ids[1,N,K]`, `similarities[1,N,K]`, norms and positions | | Attention Lens | `head_logits[L,N,H,V]`, summed logits and positions | | Patchscope | Target `logits[L,B,T_target,V]` | | Exact layer interventions | Baseline/intervention scores and effect: `[L,B]` or `[L]` | | Joint steering / knockout | Baseline/intervention scores and effect: `[B]` or scalar | | Path patching | Baseline/donor/intervention scores and effect: `[B]` or scalar; joint endpoint set | | Path patching `.sweep()` | Shared baseline/donor `[B]`; intervention/effect `[N,B]`; `senders[N,2]`. Scalar metrics omit the batch axis. | | Attribution patching | `token_scores[L,B,T]`, batch-summed `estimated_effect[L]` | | EAP-IG, activation graph | `edge_scores[E]`, scalar endpoints/effect/completeness error | | EAP-IG, transformer graph | `edge_scores[E]`, `eap_scores[E]`, endpoint scores, edge indices; actual recovery curves from `.evaluate` | | Attention profile | `entropy[L,B,H,Q]`, `group_mass[L,B,H,Q,G]` | | Head logit attribution | `head_logits[L,B,H,Q,V]`, `summed_logits[L,B,Q,V]` | | Rollout / relevance | Final `[B,T,T]` and intermediate `[L,B,T,T]` matrices | | Attention Grad-CAM | `cam[L,B,Q,K]`, `head_weights[L,B,H,Q]`, selected query/key masks | | Attention Attribution | Signed `attribution[L,B,H,Q,K]`, summed head/token scores | | TAM / Beyond Intuition | Final `map[B,T,T]`, perception/state and integrated feedback | | Chefer DTD | Class logits/targets, real LRP CAMs/gradients and spatial `patch_relevance[B,H_patch,W_patch]` | `N` packs selected positions in batch/token order. Layer indices, configuration, and provenance are recorded in metadata. ```python result.save("outputs/probe") tensors = torch.load("outputs/probe/tensors.pt", weights_only=True) ``` The accompanying `metadata.json` records the operation. Models and processors are not serialized. Execution is scoped to one model forward at a time; concurrent or nested execution on the same adapter is unsupported. ## Sparse autoencoders Configure `probe.sae.topk(layer=..., tokens=...)`, fit on frozen-model train/validation inputs with `method.fit(...)`, then inspect `method.run(...)` and `method.profile(...)`, or measure `method.reconstruct()` and `method.edit(...)`. `probe.sae.load(...)` restores the same native interface. The [SAE API](methods/sae/API.md) lists every dictionary/training option and result shape; the [complete tutorial](methods/sae/examples/README.md) includes image inputs, training, native artifacts, model cards and visualization.