Skip to content

Run relation attention on packed tokens in GraphTransformerEncoder - #857

Draft
kmontemayor2-sc wants to merge 32 commits into
kmonte/gt-packed-pairwise-biasfrom
kmonte/gt-packed-unified
Draft

kmontemayor2-sc wants to merge 32 commits into
kmonte/gt-packed-pairwise-biasfrom
kmonte/gt-packed-unified

Conversation

@kmontemayor2-sc

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

Copy link
Copy Markdown
Collaborator

Summary

GraphTransformerEncoder on CUDA now keeps configs with relation_attention_mode="edge_type_bilinear" or "edge_type_hgt" on the packed-token path instead of falling back to padded sequences. Together with the earlier packed-path PRs in this stack, this leaves only ppr_weight, ppr_relation_features, non-khop sequences, hop_distance > 4, CPU, and oversized k-hop candidate sets on the padded path.

The PR also removes a per-batch cost from the packed pairwise attention bias (the bias is now made contiguous once per forward) and adds a training test that compiles the whole encoder.

Approach

  • Each layer turns its per-edge relation scores into a per-layer pair bias on top of any incoming pairwise bias, and attention runs on the existing padded-grid backend. The incoming bias is copied, never written in place, so layers do not see each other's scores.
  • The score computation is shared with the padded path. It scores each relation segment with batched matmuls, and one scatter writes all scores into the grid. This removes the per-relation .item() reads that made the padded path graph-break under torch.compile, and the packed path does not recompile when per-relation edge counts change between batches.
  • The pairwise bias projection returns a permuted view; the encoder now calls .contiguous() once per forward so each layer's attention reads a dense tensor.

User-facing changes

  • relation_attention_mode values edge_type_bilinear and edge_type_hgt are accepted on the packed path (any relation_message_mode, both readout modes, with or without pairwise biases). Embeddings and gradients match the padded path up to float error. No config or API changes.
  • GraphTransformerEncoderLayer.forward_packed now raises ValueError only when layout.relation_edges is missing for a relation attention or message mode; it no longer rejects relation attention.
  • The packed-tokens CHANGELOG.md entry now lists every config the packed path supports at this point in the stack, including the neighbor readout, attention dropout and pairwise biases from the earlier PRs.
  • Known limitation: with relation attention under per-layer torch.compile, the packed path builds 35 graphs in inference (3 after the first batch) and 60 in training, with long warmup (about 108 s in inference). Timing is stable after warmup. This is not fixed here.

Verification

  • ruff check, ruff format --check and dprint check pass at this tip.
  • make unit_test_py (including type_check, CPU) at an earlier tip of this branch: 1563 tests, OK, 45 skipped.
  • New CPU tests: packed vs padded outputs for bilinear and HGT (with message modes, out sampling direction, neighbor readout, pairwise bias), training gradients, filtered-edge gradients, and no recompiles across batches.
  • New CUDA tests: whole-encoder inductor compile in training against eager gradients (8 configs, including relation attention, pairwise bias and anchor bias), and relation training gradients against CPU.
  • 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: 322 passed, 0 skipped.
  • Benchmark parity: min cosine >= 0.9999997 against padded fp32 eager in every inference cell below; 0 recompiles in every cell.

Performance

One L4, torch 2.8.0+cu128, random weights, 3 layers, hidden 512, 8 heads, anchor_only readout, max_seq_len 256. Inference: 512 anchors per batch, fp32 with TF32, mean of 40 batches. Training: bf16 autocast, mean of 24 steps. Times are ms per batch or step; memory is peak allocated.

Relation attention edge_type_bilinear, padded vs packed:

Mode Padded Packed
Inference, eager 280.8 ms / 6982 MiB 206.6 ms / 5604 MiB
Inference, per-layer compile 246.5 ms / 6059 MiB 126.4 ms / 5152 MiB
Training, eager (quarter-size batch) 1095.4 ms / 18386 MiB 676.8 ms / 12195 MiB
Training, per-layer compile (quarter-size batch) 901.0 ms / 14413 MiB 452.9 ms / 10619 MiB

The padded per-layer compile has 52 (inference) and 62 (training) graph breaks in _get_relation_segments and .item() reads; the packed path has 0 and 1. The compiled ratios therefore flatter the packed path; the eager ratios (1.4x inference, 1.6x training) are the conservative ones.

Pairwise bias, padded vs packed, at this PR's tip:

Mode Padded Packed
Inference, eager 359.5 ms / 8135 MiB 187.6 ms / 5606 MiB
Inference, per-layer compile 274.8 ms / 6172 MiB 146.4 ms / 5410 MiB
Training, eager 854.8 ms / 11893 MiB 371.1 ms / 6266 MiB
Training, per-layer compile 517.5 ms / 8477 MiB 278.6 ms / 5455 MiB

The base branch's packed pairwise-bias inference was 293.2 ms eager and 184.9 ms per-layer compile (same config, measured at the base tip). In a same-process A/B, the contiguous bias takes packed inference eager from 290.9 to 188.6 ms (fp32 output max abs diff 1.3e-7, gradient relative L2 5.3e-7). Per-layer-compile inference peak rises 256 MiB (5154 to 5410 MiB).

🤖 Generated with Claude Code

kmontemayor and others added 27 commits October 28, 2026 15:44
… a per-layer pair bias

edge_type_bilinear and edge_type_hgt no longer force the padded path. Each
layer scatters the per-edge relation scores into the padded-grid pair bias
(on top of any pairwise bias) and attends on the grid SDPA backend. The
packed layout now carries relation edges for either relation mode.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…r syncs or breaks a compiled graph

to_dense_batch compared the longest sequence with max_seq_len on the host and
the boolean gather ran nonzero; both broke every compiled layer that uses the
grid backend into several graphs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d the pairwise-bias compile caveat

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…id per forward, shared score scatter

- Relation attention scores use matmul plus an elementwise sum instead of a
  three-operand einsum, whose opt_einsum path guarded on per-relation edge
  counts and recompiled every batch under torch.compile.
- The padded-key mask, per-key and per-pair biases are combined into one grid
  once per encoder forward (PackedAttentionBias.grid); layers use it as is and
  relation attention adds its scores to a single copy.
- The padded and packed paths share _accumulate_relation_attention_scores,
  which also drops the padded path's per-relation host reads.
- Tests: pairwise bias with relation attention against the padded path, and a
  compile test over batches with different edge counts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A one-L4 measurement showed rebuilding the grid per layer wins or ties the
once-per-forward grid: compiled 4-9% faster, 20-25% lower compiled-inference
peak, equal training memory. Remove PackedAttentionBias.grid and the
once-per-forward build; relation attention copies bias.per_pair per layer.
The matmul+sum relation scores and the shared score-accumulation helper stay.

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 whole-encoder compile tests ran only at inference. This CUDA test
compiles the whole encoder with inductor in train mode. For each config it
compares the loss and every parameter gradient to the eager encoder over five
batches, and checks that the batches after the second do not recompile.

Configs: anchor_only and neighbor readout, per-key anchor bias, pairwise bias
(with both readouts), bilinear and HGT relation attention, and the
edge_type_attention relation message mode.

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>

# Conflicts:
#	gigl/nn/graph_transformer.py
# Conflicts:
#	gigl/nn/graph_transformer.py
_lookup_pairwise_relative_features returns None only without pairwise
matrices, which the configured projection rules out. The explicit check
also gives _pairwise_pair_bias a Tensor type.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The pairwise projection output is permuted to [batch, heads, seq, seq], so
its last dim has a stride of num_heads. Every layer's attention read that
strided bias. One contiguous copy in _packed_attention_bias removes the
strided read from each layer.

One L4, 3 layers, hidden 512, 8 heads, max_seq_len 256, random weights,
pairwise bias. Same process and batches; strided bias in every layer vs
contiguous once:
- inference (512 anchors, fp32+TF32), eager: 290.9 vs 188.6 ms,
  peak 7920 vs 5615 MiB
- inference, per-layer compile: 183.9 vs 147.0 ms, peak 5163 vs 5419 MiB
- training (bf16 autocast), eager: 661.8 vs 370.3 ms, peak 8726 vs 6263 MiB
- training, per-layer compile: 368.1 vs 276.4 ms, peak 6666 vs 5452 MiB
fp32 parity of the two: output max abs diff 1.3e-7, gradient relative L2
5.3e-7.

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

# Conflicts:
#	gigl/nn/graph_transformer.py
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

# Conflicts:
#	CHANGELOG.md
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>
kmontemayor and others added 2 commits November 5, 2026 00:55
Conflicts resolved: CHANGELOG.md (kept the PR 5c entry with the readout and khop
wording), tests/unit/nn/packed_graph_transformer_test.py (kept the self-loop
and all-filtered assertions inside the shared filtered-edge helper; took the
PPR hop-embedding test).
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
kmontemayor and others added 3 commits November 5, 2026 03:45
… accumulator

Tests now call _add_relation_attention_bias with attn_bias=None.

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