Skip to content

feat(projection_kernel): add attention-head subspace affinity - #1721

Open
janmenjayap wants to merge 2 commits into
TransformerLensOrg:devfrom
janmenjayap:feat/projection-kernel
Open

feat(projection_kernel): add attention-head subspace affinity#1721
janmenjayap wants to merge 2 commits into
TransformerLensOrg:devfrom
janmenjayap:feat/projection-kernel

Conversation

@janmenjayap

@janmenjayap janmenjayap commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Description

Implements #1720.

Adds a basis-invariant Projection Kernel analysis surface in two reviewable commits:

  1. model-independent reduced-SVD basis extraction, principal angles, raw and normalized PK,
    numerical-rank metadata, roundoff-bound handling, and random-subspace moments;
  2. a TransformerBridge OQ/OK/OV head-affinity wrapper with native GQA KV-head identity,
    hybrid-layer indices, forward/all layer masks, bounded-memory tiled scoring, and ranking.

The implementation promotes fp16/bf16 inputs to fp32 for stable SVDs, preserves float64,
detaches model weights from autograd, and reports role/layer/head context for malformed or
rank-deficient weights. The accompanying guide documents orientation, rank semantics,
limitations, GQA behavior, and the distinction from Composition Score, with runnable numerical
and GPT-2 TransformerBridge examples.

Validation

  • Focused PK suite: 67 passed.
  • make check-format: passed.
  • uv run mypy .: passed (392 source files).
  • uv run build-docs: passed with no PK-specific warnings.
  • uv build: source distribution and wheel built successfully.
  • Local PR test surfaces (make test-pr did not exit successfully because of the three
    integration failures described below):
    • unit: 5,363 passed;
    • docstring: 18 passed;
    • acceptance: 209 passed;
    • integration: 1,394 passed, including all PK tests; two gated Gemma loads failed because the
      local HF token lacks access, and one existing GraniteMoeHybrid MPS parity test reproduced the
      investigated PyTorch 2.10 MPS numerical divergence.
  • GitHub CI: all applicable compatibility, coverage, formatting, typing, docstring, benchmark,
    and notebook checks passed.

Checklist

  • I have read and understood the contribution guidelines.
  • I have added tests that prove my fix is effective or that my feature works.
  • I have added necessary documentation (if appropriate).
  • My changes generate no new warnings.
  • No changelog file is required by this repository; the user-facing change is documented in
    docs/source/content/projection_kernel.md.

Type of change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation update

@janmenjayap
janmenjayap marked this pull request as ready for review August 24, 2026 16:06
@janmenjayap janmenjayap changed the title Add projection-kernel attention-head subspace affinity feat(projection_kernel): add attention-head subspace affinity Aug 24, 2026

@jlarson4 jlarson4 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Excellent work here @janmenjayap! This is a solid implementation of your plan from #1720. Just a couple comments to tighten things up before merging


overlap = first.T @ second
score = _clamp_projection_scores(overlap.square().sum(), min(subspace_a.rank, subspace_b.rank))
cosines = torch.linalg.svdvals(overlap)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

torch.linalg.svdvals has no MPS kernel, so projection_kernel raises NotImplementedError on MPS tensors while orthonormal_subspace survives via the CPU fallback. Let's route the small overlap matrix through .cpu() or use sqrt(eigvalsh(overlapᵀoverlap)), whichever you prefer.

input_shape = (matrix.shape[0], matrix.shape[1])
requested_rank = _validate_rank(rank, min(input_shape))
compute_dtype = _compute_dtype(matrix.dtype)
effective_rtol = _validate_rtol(rtol, input_shape, compute_dtype)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The default rank tolerance derives from the compute dtype, so a genuinely rank-deficient fp16/bf16 head passes the full-column-rank gate with a quantization-noise basis direction. Can we derive rtol from the storage dtype's eps (or warn at _extract_bases for low-precision inputs)?

It would also be nice to add a low-precision case to the rank guard test.

if validated_layer_order == "forward":
layer_tensor = torch.tensor(layer_indices, device=result_device)
layer_mask = layer_tensor[:, None] < layer_tensor[None, :]
valid_mask = layer_mask[:, None, :, None].expand_as(scores)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the forward branch valid_mask is a stride-0 broadcast view, so a user write to one entry flips its aliased copies; the all branch returns a materialized tensor. Add .contiguous().

) from error
head_bases.append(subspace.basis.to(device=device))
head_ranks.append(subspace.measured_rank)
layer_bases.append(torch.stack(head_bases))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The basis stack is fully materialized per role (~2.15 GB/role at 32L/32H/4096) while only the overlap temp is tiled, and top_pairs builds every valid pair before taking k. A docs note on the practical model-size ceiling now, with stack tiling + torch.topk as a follow-up, would cover it.

overlap = first.T @ second
score = _clamp_projection_scores(overlap.square().sum(), min(subspace_a.rank, subspace_b.rank))
cosines = torch.linalg.svdvals(overlap)
angles = torch.acos(cosines.clamp(min=0.0, max=1.0))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With check_orthonormal=False and a hand-built basis, a cosine of 1.3 stays visible in cosines but clamps to angle 0.0, while score raises on the analogous violation. Apply the same tolerance-then-raise rule to the acos clamp, or document that angles assumes orthonormal inputs.

attribute = f"W_{role}"
try:
matrix = getattr(attn, attribute)
except (AttributeError, RuntimeError, ValueError) as error:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

require_readable_weight raises NotImplementedError to mean "unsupported", but the except (AttributeError, RuntimeError, ValueError) here catches it via its RuntimeError base and converts it to ValueError. Let NotImplementedError re-raise with the added role/layer context.

target_layer_indices: Tuple[int, ...]
source_head_kind: HeadKind
target_head_kind: HeadKind
source_ranks: torch.Tensor

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

source_ranks/target_ranks hold measured rank while the bases carry selected rank, and the docstring documents neither. Please add two lines naming which rank each field carries.

input_shape: Shape of the matrix from which the basis was extracted.
"""

basis: torch.Tensor

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sibling modules annotate tensors with jaxtyping.Float shape strings; this module is bare torch.Tensor throughout, so runtime shape-checking enforces nothing here. Should we align with its siblings or add a one-line comment recording that the deviation is deliberate?

if (
isinstance(value.rank, bool)
or not isinstance(value.rank, int)
or value.rank < 1

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#1720's plan lists "deficient" among the explicit-value cases and validates "moments" plural. Please add an exact-value rank-deficient PK case and a variance assertion.


__all__ = [
"DirectLogitAttribution",
"AttentionHeadRef",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new entries broke __all__'s alphabetical ordering, please restore alphabetical ordering

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants