Skip to content

Add a fixed-shape k-hop sequence layout for packed graph transformer tokens - #814

Queued
kmontemayor2-sc wants to merge 10 commits into
mainfrom
kmonte/gt-static-khop
Queued

kmontemayor2-sc wants to merge 10 commits into
mainfrom
kmonte/gt-static-khop

Conversation

@kmontemayor2-sc

@kmontemayor2-sc kmontemayor2-sc commented Oct 23, 2026 •

Copy link
Copy Markdown
Collaborator

PR 1/3 for Packed Attention in GT

  1. Add a fixed-shape k-hop sequence layout for packed graph transformer tokens #814
  2. Add varlen attention over packed sequences #815
  3. Run supported GraphTransformerEncoder configs on packed tokens #811

Summary

  • The packed GraphTransformerEncoder backend needs, for every anchor, the list of neighbour tokens within k hops and
    the hop distance of each token. The existing sparse k-hop builders in gigl/transforms/graph_transformer.py produce
    this with data-dependent shapes (sparse matmul, coalesce, unique, nonzero, bool-mask indexing). That costs many
    host syncs and blocks torch.compile.
  • Hop distance on that path also comes from an all-pairs hop matrix, which is the memory-heavy part.
  • This PR adds gigl/transforms/static_khop.py, which builds the same layout with fixed shapes and per-token hop
    distance, and no all-pairs matrix.
  • First of three PRs: this one (k-hop layout), then the packed attention PR (kmonte/gt-packed-attention), then the
    encoder integration (Run supported GraphTransformerEncoder configs on packed tokens #811, kmonte/gt-packed-backend). Nothing uses this module yet, so behavior is unchanged.

Approach

  • A dense neighbour table ([num_nodes + 1, width], -1 padded, width = largest node degree) turns each hop into a
    gather. Only sort, gather and scatter are used, so output shapes are known up front.
  • Each token is one int64, (node << hop_bits) | hop. The layout scatters and gathers one tensor, and consumers decode
    it with a shift and a mask.
  • The order matches the existing sparse builders: anchor first, then neighbours in ascending node id (or by hop and then
    node id with prioritize_hop_order), cut at max_seq_len. The hop is the first-reach hop.
  • Memory is O(num_anchors * width ** hop_distance), and one hub node sets width for the whole batch. khop_layout
    counts the cells (candidates plus the neighbour table) before allocating them and raises KhopLayoutTooLargeError
    past a 2^25-cell budget (MAX_LAYOUT_CANDIDATES). The caller can then fall back to the padded path.
  • The whole path makes two host reads: the table width, and in pack_sequences the total token count.
  • pack_sequences turns the per-sequence lengths into flat gather indices and PackedSequences (int32 cu_seqlens
    offsets, max_seq_len), which the attention PR consumes.

User-facing changes

  • New public API in gigl/transforms/static_khop.py, all additive and unused so far:
    • khop_layout
    • pack_sequences
    • PackedSequences
    • KhopLayoutTooLargeError
    • MAX_LAYOUT_CANDIDATES
  • khop_layout accepts int32 or int64 edge indices and anchors.
  • No existing behavior changes. No CHANGELOG entry; the integration PR's entry covers the feature.

Verification

  • tests/unit/transforms/static_khop_test.py, 7 tests, pass (CPU):
    • The layout equals the existing sparse k-hop builders across configurations.
    • Hop order truncation drops the farthest hops first, and hop is first-reach.
    • Over-budget layouts and huge hub tables raise before allocating.
    • pack_sequences offsets are correct.
  • static_khop doctests: 9 examples pass.
  • ruff and ty are clean.
  • CUDA: job gt-split-ut-20261023-165324, one L4 (g2-standard-48, cuda:0), run on the final integration tree, which
    contains this PR's code unchanged: static_khop_test 7 tests pass. The same job ran the whole stack's tests, 181 in
    total, all passing and none skipped.

🤖 Generated with Claude Code

…tokens

Adds gigl/transforms/static_khop.py (khop_layout, pack_sequences,
PackedSequences, KhopLayoutTooLargeError) and its tests. Nothing uses it
yet; the attention module and the encoder integration follow in later PRs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Comment thread gigl/transforms/static_khop.py Resolved
Comment thread gigl/transforms/static_khop.py Resolved
Comment thread gigl/transforms/static_khop.py Resolved
kmontemayor and others added 4 commits October 29, 2026 16:56
…e to packed_layout.py

- khop_layout raises ValueError for a sampling_direction other than 'in' or
  'out', instead of treating every other value as 'out'.
- PackedSequences is renamed PackedLayout and, with pack_sequences, moves to
  gigl/transforms/packed_layout.py, since neither depends on how the
  sequences were built. static_khop.py keeps only the k-hop code.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…lace prioritize_hop_order with an ordering option

select_sequences dedupes each anchor's candidates (the lowest column of a node wins), orders them by an optional priority and cuts them at max_seq_len, returning the nodes, the source column of every token and the lengths. The k-hop layout generates candidates through the neighbour table and calls it. khop_layout takes ordering in {node_id, hop, input} instead of prioritize_hop_order.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The module holds the pieces shared by the per-anchor sequence builders (selection,
the packed type and packing), not only the packed layout.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The builder pads each anchor's ragged PPR edges into a [anchors, edges] table and
lets select_sequences order them by descending weight, then gathers the weights
and relation features by the returned source column. select_sequences gains
include_anchor (no anchor slot; the anchor stays if it is a candidate) and
ties_by_column (priority ties keep column order instead of ascending node id).
Output matches the previous builder on random PPR batches, and it is faster on
CPU.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
kmontemayor and others added 3 commits October 30, 2026 18:12
The test counts unique graphs, which includes compiles from earlier tests in
the same process.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SelectedSequences(nodes, source, lengths) and KhopLayout(layout, lengths,
hop_bits) replace the bare tuples. select_sequences and pack_sequences get
function-level examples; the test that runs the docstring examples came in
with the previous commit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kmontemayor2-sc
kmontemayor2-sc marked this pull request as ready for review October 30, 2026 18:40

@yliu2-sc yliu2-sc 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.

we'll have separate PR for PPR?

@kmontemayor2-sc

Copy link
Copy Markdown
Collaborator Author

we'll have separate PR for PPR?

Yes, and we will support many more GT configs.

@kmontemayor2-sc
kmontemayor2-sc added this pull request to the merge queue Nov 3, 2026
Any commits made after this event will not be merged.

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.

3 participants