Skip to content

Run per-pair attention biases on the varlen kernel at inference - #858

Draft
kmontemayor2-sc wants to merge 19 commits into
kmonte/gt-packed-unifiedfrom
kmonte/gt-varlen-bias-infer
Draft

kmontemayor2-sc wants to merge 19 commits into
kmonte/gt-packed-unifiedfrom
kmonte/gt-varlen-bias-infer

Conversation

@kmontemayor2-sc

@kmontemayor2-sc kmontemayor2-sc commented Nov 5, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Packed-token attention with a per-pair bias (pairwise bias or relation attention) always used the padded-grid SDPA backend, even when nothing needs a gradient. This change sends those calls to the varlen kernel at inference, and folds the relation score into a per-key bias in anchor-only last layers.

Approach

  • _varlen_attention takes an optional per-sequence attn_bias. The kernel indexes the bias of sequence i at i * stride(0), so the grid is passed as a strided view. When one head and full sequences make that view reach past the grid, the grid is copied into a zero buffer that holds the view. The kernel has no backward for a bias with cu_seqlens, so a call with a bias raises ValueError when the bias, the query, the key or the value requires grad; packed_attention routes those calls to the padded path.
  • packed_attention dispatch: dropout goes to the padded path. A per-pair bias with a gradient and all tokens queried goes to the padded path. Otherwise the kernel runs. With query_is_first_token, row 0 of the per-pair bias is added to the per-key bias and folded into q and k, so it runs on the kernel with or without a gradient.
  • With first_token_output, every relation edge targets the anchor, so the relation score depends only on the key. The layer adds it to bias.per_key instead of building a per-pair grid. The shared score computation moved to _relation_attention_scores.
  • The per-pair bias is made contiguous before the strided view.

User-facing changes

No API changes. Packed inference with pairwise_attention_bias_attr_names or relation_attention_mode is faster and uses less memory; training with all tokens queried is unchanged.

Verification

  • ruff check, ruff format --check and dprint check pass at this tip.
  • New CUDA tests: kernel vs CPU padded reference (fp32 and bf16, per-key on and off, max_seq_len not a multiple of 8, one head with full sequences, permuted-view bias); dispatch routing; the bias-requires-grad error; encoder-level CUDA vs CPU for pairwise, pairwise plus key bias, and relation bilinear/hgt across readout modes; whole-encoder compile with no recompiles.
  • CUDA run on one L4 of the five graph-transformer test modules (tests/unit/nn/packed_attention_test.py, tests/unit/nn/packed_graph_transformer_test.py, tests/unit/nn/graph_transformer_test.py, tests/unit/transforms/graph_transformer_test.py, tests/unit/transforms/static_khop_test.py) at this tip: 340 passed, 0 skipped.
  • Inference parity: min cosine >= 0.9999997 vs padded fp32 eager; 0 recompiles.

Performance

One L4, torch 2.8.0+cu128, random weights, 3 layers, hidden 512, 8 heads (head dim 64), anchor_only readout, max_seq_len 256, pairwise bias, 512 anchors per batch, fp32 with TF32, mean of 40 batches. Both columns are on packed tokens: grid backend forced vs the new varlen dispatch.

Inference Grid backend forced Varlen dispatch
Eager 185.7 ms / 5606 MiB 123.8 ms / 4895 MiB
Per-layer compile 145.4 ms / 5410 MiB 100.0 ms / 4224 MiB

The training anchor-only fold was measured earlier at 1% to 7% faster, because only the last of three layers changes. The kernel still holds the dense per-pair bias; it skips the scores and padded tokens.

🤖 Generated with Claude Code

kmontemayor and others added 13 commits October 30, 2026 19:26
…gradient; anchor-only last layers fold them per key

The aten varlen forward takes a per-sequence bias; only its backward rejects one.
packed_attention passes the dense per-pair grid to the kernel (through the custom op)
when nothing needs a gradient. With first-token queries row 0 of a per-pair bias is a
per-key bias, so it folds into per_key and runs on the kernel in training too, and
relation attention scores of the anchor-only last layer are added per key.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ed view

The pairwise bias is a permuted view of a linear layer's output, so its last-dim
stride is above 1; the kernel reads a last-dim stride of 1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The fake returned a query-shaped output, which is wider than the real output when a
per-key fold widens q and k but not v; compiled calls then failed the stride check.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The varlen kernel takes the per-sequence bias grid as a strided
[1, num_heads, num_query_tokens, num_key_tokens] view. With one head and
full sequences, that view reaches past the end of the grid, so as_strided
raised. With two or more heads the view always fits.

_kernel_bias_view copies the grid into a zero buffer that holds the whole
view when the view does not fit. The kernel reads only the rows and columns
of each sequence, so the extra zeros are never read.

Also remove the ternaries in packed_attention and in the dispatch test.

Tests: a CUDA test with one head and full sequences, for a max_seq_len that
is and is not a multiple of 8, against the padded path.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The encoder Notes said pairwise and relation biases always run on the grid
backend. They run on the varlen kernel without a gradient and in anchor-only
last layers. Also split a run-on layer docstring sentence and shorten the
_kernel_bias_view summary.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

# Conflicts:
#	gigl/nn/packed_attention.py
kmontemayor and others added 6 commits November 5, 2026 00:57
The backward passes no bias but reuses a logsumexp that included it, so the
gradients would be wrong. Move the seed and offset comment back above the ops
it describes.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
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