feat(jacobian_lens): add dynamic J-space coordinate-patch hooks - #1749
Open
janmenjayap wants to merge 5 commits into
Open
feat(jacobian_lens): add dynamic J-space coordinate-patch hooks#1749janmenjayap wants to merge 5 commits into
janmenjayap wants to merge 5 commits into
Conversation
- Add solve_coordinate_patch_positions: model-free loop applying solve_coordinate_patch independently to every (batch, position) pair in a [batch, num_positions, d_model] chunk - Support an optional caller-owned decomposition_cache keyed (layer, batch_idx, position); a hit skips get_sparse_decomposition, a miss solves once and stores - Fail fast (no try/except) when a source is inactive at any pair, so no partial write reaches the activation tensor - Validate 3-D activations and matching position_labels length - Add unit tests for offline parity, batch independence, fail-fast, cache miss/hit behavior, and label-length validation
- move D_MODEL/N_LAYERS/D_VOCAB/SEQ_LEN/SKIP_FIRST/CORPUS, _ToyBlock, _CausalSumBlock, _ToyTokenizer, _ToyBridge, _NotABridge, _lens, and the toy_model fixture from test_jacobian_lens.py into conftest.py - import the shared symbols back into test_jacobian_lens.py and remove the now-unused contextmanager and HookPoint imports - centralize the shared test setup so additional hook tests can reuse the same fixtures without duplicating roughly 90 lines of test code
- Add JacobianLens.coordinate_patch_hooks, a forward-hook variant of the offline coordinate_patch primitive, following the swap_hooks builder pattern - Solve one J-space coordinate patch per (batch, position) pair at each layer via solve_coordinate_patch_positions, with an optional caller-owned decomposition_cache keyed (layer, batch_idx, position) - Require positions explicitly and reject identical source/target tokens; fail fast on any inactive source rather than partially patching a batch - Warn once per call naming the layer x position count that performs a live vocabulary-scale solve on every cache miss - Add a dedicated test file covering shape parity, warning-once, cache hit/miss, oracle parity with offline coordinate_patch, and uncaught error and warning propagation through the hook
Export solve_coordinate_patch_positions from tools.analysis and document the distinction between offline and dynamic/hooked coordinate patching. Replace the stale dynamic-patching claim in jacobian_lens_fitting.md and add documentation covering required positions, caller-owned decomposition_cache, per-pair fail-fast behavior, and the once-per-call cost warning. Add a cached GPT-2 integration test verifying alpha=0 is an exact no-op and untouched positions remain bit-identical.
…__init__ Commit 02ffc27 accidentally added sparse_probing imports and __all__ entries to transformer_lens/tools/analysis/__init__.py without committing the sparse_probing module itself. On CI (which only checks out tracked files) importing transformer_lens.tools.analysis raised ModuleNotFoundError, breaking package import and failing every job that imports transformer_lens (unit, docstring, compatibility, benchmark, coverage, notebooks). Remove the out-of-scope sparse_probing exports; that work belongs to its own PR.
janmenjayap
force-pushed
the
feat/jacobian-lens-coordinate-patch-hooks
branch
from
September 5, 2026 18:05
f1c30b2 to
fbdbf29
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds
JacobianLens.coordinate_patch_hooks(...), a forward-hook variant of the offlinecoordinate_patch(...)primitive shipped in #1741, so an anchored J-space coordinate edit can runlive inside
model.run_with_hooks(...)/model.generate(...)instead of only on onealready-captured activation.
Underneath, a new model-free core function,
solve_coordinate_patch_positions(...), loopssolve_coordinate_patch(...)over every(batch_idx, position)pair in a[batch, num_positions, d_model]chunk, with an optional caller-owned decomposition cache keyed(layer, batch_idx, position).coordinate_patch_hooks(...)is a thin wrapper that builds one forward hook per layer —following the same
_resid_post_hook_name/ eager dictionary-build pattern asswap_hooks— whosehook body slices the requested positions, calls the core loop, and writes the result back.
This is PR2 of the plan tracked by the companion issue. Arbitrary multi-slot permutations, a
causal-swap benchmark, and HookedTransformer support remain explicitly deferred (PR3/PR4).
Fixes #1748
Motivation
coordinate_patch(...)edits one pre-captured activation offline and returns a report-onlyCoordinatePatch. Studying how a patched coordinate propagates through the rest of a forward pass —or patching during
generate(...)— previously required manually re-capturing, patching, andre-injecting activations outside the hook system. #1739 deferred the hooked variant because each
hooked position would otherwise fire its own vocabulary-scale sparse decomposition live during the
forward pass. This PR ships that variant while making the per-position cost explicit (a required
positionsargument and a construction-time warning) rather than hiding it behind a convenient API.What ships (commit by commit)
feat(jacobian_lens_coordinate_patch): add per-position patch loop coreNew model-free
solve_coordinate_patch_positions(...)injacobian_lens_coordinate_patch.py:loops
solve_coordinate_patch(...)over every(batch_idx, position)pair — never once perposition shared across the batch, since two batch rows can have different active supports.
Supports an optional caller-owned
decomposition_cachekeyed(layer, batch_idx, position); acache hit skips
get_sparse_decomposition(...), a miss solves once and populates it. Notry/exceptaround the per-pair loop — an inactive source at any one pair propagatessolve_coordinate_patch's existingValueErrorout of the whole call, so nothing is a partialwrite. Model-free tests first, mirroring PR1's own core-before-wrapper split.
test(tools): extract shared J-lens toy-bridge fixtures into conftestMechanical move, no behavior change. Extracts the
_ToyBridgefakeTransformerBridge(and itssupporting constants/classes/fixture) that
test_jacobian_lens.pyalready defined intotests/unit/tools/conftest.py, so the new hook-test file in commit 3 can reuse it instead ofduplicating ~90 lines that would otherwise risk drifting between two copies.
test_jacobian_lens.pyimports from the conftest; its pass/fail set is byte-identical to before the move.
feat(jacobian_lens): expose coordinate_patch_hooks on the BridgeJacobianLens.coordinate_patch_hooks(model, source_token, target_token, layers, *, positions, decomposition_cache=None, ...)resolves token inputs and layers exactly likecoordinate_patch,then builds one forward hook per layer via
solve_coordinate_patch_positions.positionsisrequired — no full-sequence default, since a silent default would trigger a vocabulary-scale
sparse solve at every position. Emits one
UserWarningper call, at hook-construction time,naming
len(layers) x len(positions)— not a per-position warning, and not an attempt to reportthe exact number of live (non-cache-hit) solves, which is only known at forward time. New
dedicated test file (
test_jacobian_lens_coordinate_patch_hooks.py), not folded into thealready-1600+-line
test_jacobian_lens.py.feat(jacobian_lens): export and document coordinate_patch_hooksExports
solve_coordinate_patch_positionsfromtransformer_lens.tools.analysis, extends thepackage docstring, replaces the now-stale "dynamic patching would require a vocabulary-scale
solve" claim in
jacobian_lens_fitting.mdwith a new#### Dynamic coordinate-patch hookssubsection, and adds one cached-model GPT-2 integration test asserting algebraic invariants
only (
alpha=0is an exact no-op; untouched positions are bit-identical through a realrun_with_hookspass) — not a token-flip claim.Design decisions (per the tracking issue)
(batch_idx, position)pairs explicitly, never once per position shared across the batchtry/except ValueError+warnings.warn+ copy the row through unedited) is a two-line hunk if preferreddecomposition_cacheis caller-owned (Optional[MutableMapping[Tuple[int, int, int], JSpaceDecomposition]]), not aJacobianLensattribute#2coordinate_patch_hooks(...)call, at construction time, naminglayers x positionscounts (not the exact number of live solves)Please weigh in on
#2and#3in particular — they're the most likely to get pushback and are cheapto change if so.
Testing
tests/unit/tools/test_jacobian_lens_coordinate_patch.py— model-free core: parity withsolve_coordinate_patch, batch independence, fail-fast on any inactive source, cache miss/hitbehavior, cache-hit vs. fresh-solve identical output, mismatched
position_labelsrejection.tests/unit/tools/test_jacobian_lens.py— unchanged behavior after the conftest extraction(byte-identical pass/fail set).
tests/unit/tools/test_jacobian_lens_coordinate_patch_hooks.py— wrapper: hook-list shape mirrorsswap_hooks, once-per-call warning naming layer/position counts, unfitted-layer rejection,empty-
positionsrejection, identical-token rejection, only-requested-positions-change, oracleparity with offline
coordinate_patch, cache hits skip resolve across repeated hook firings,core errors and core warnings propagate uncaught through the hook.
tests/integration/test_jacobian_lens.py— one cached GPT-2 test:alpha=0no-op andbit-identical untouched positions through a real
run_with_hookspass.Local gates green:
uv run mypy .—Success: no issues found in 397 source files.uv run pytest tests/unit/tools/test_jacobian_lens_coordinate_patch.py tests/unit/tools/test_jacobian_lens.py tests/unit/tools/test_jacobian_lens_coordinate_patch_hooks.py—157 passed.source .env && uv run pytest tests/integration/test_jacobian_lens.py -k coordinate_patch_hooks—1 passed.Related work
implemented the
coordinate_patch(...)primitive this PR wraps. Strict dependency; no overlap.JacobianLens.swap_hooksre-reads live coordinates, causing odd/even cancellation across layer bands #1746 / Add clean-coordinate clamping for JacobianLens swaps #1747 (open) — a bug report thatswap_hooksre-reads live lens coordinates at everyhooked layer, making a multi-layer swap band involutive (odd/even cancellation), and a proposal to
add clean-coordinate clamping for J-lens swaps. No functional conflict with this PR — different
method, different semantics — but two things reviewers should note: (1) both areas touch
jacobian_lens.py, the__init__.pyexports, andjacobian_lens_fitting.md, so whichever landssecond needs a mechanical rebase; and (2)
coordinate_patch_hooksshares the same structuralproperty — it re-decomposes the live activation at each hooked layer, so a multi-layer install
compounds across the band. Unlike a swap, a
substitutepatch is not an involution, so it does notexhibit the exact odd/even cancellation
JacobianLens.swap_hooksre-reads live coordinates, causing odd/even cancellation across layer bands #1746 describes; a clean-coordinate clamp variant ofcoordinate patching is a reasonable future PR if the same multi-layer concern proves material. This
PR does not attempt to fix or depend on
JacobianLens.swap_hooksre-reads live coordinates, causing odd/even cancellation across layer bands #1746 / Add clean-coordinate clamping for JacobianLens swaps #1747.Out of scope (deferred to a follow-up PR)
Arbitrary multi-slot permutations, any behavioral / causal-swap benchmark (PR3), and
HookedTransformer support (PR4) remain deferred to follow-up PRs.
Checklist
make test-prrun attached (unit + docstring + acceptance + integration).uv run build-docsrun clean (new#### Dynamic coordinate-patch hookssection renders).#2(fail-fast vs. skip-with-warning) and#3(caller-owned
decomposition_cacheshape).