gigl.transforms.static_khop#
Builds each anchor’s k-hop token sequence with fixed-shape tensor ops.
This is the layout half of the packed backend of GraphTransformerEncoder. For
every anchor it picks which nodes become its tokens (the anchor, then its
neighbours within hop_distance hops), and pack_sequences (in gigl.transforms.sequence_layout) turns the padded
result into the offsets that gigl.nn.packed_attention needs to run the
transformer on real tokens only.
- Why this exists:
Sequences are short and uneven (about 90 real tokens out of a 256-token budget in our models), so a padded
[num_sequences, max_seq_len]batch spends most of its compute and memory on padding. Packing the real tokens into one buffer, withcu_seqlensmarking the boundaries, avoids that.The sparse k-hop path in
gigl.transforms.graph_transformer(sparse matmul,coalesce,unique,nonzero, bool-mask indexing) produces tensors whose sizes depend on the data. It needs many host syncs and blockstorch.compile. This module instead uses a dense neighbour table and only sort, gather and scatter, whose output shapes are known up front. It produces the same layout as that path’s default: anchor first, then neighbours in ascending node id.A dense table has one cost: the candidates before deduplication grow as
num_anchors * width ** hop_distance, and one hub node setswidthfor the whole batch.khop_layoutcounts the candidates before it builds anything and raisesKhopLayoutTooLargeErrorpastmax_candidates.GraphTransformerEncodercatches that and runs the batch on the padded path.The whole path makes exactly two host reads (each is a GPU sync): the table width (the largest per-node degree, all edge types combined) and, in
pack_sequences, the total token count.
How a layout is built:
edge_index [2, num_edges]
| sort edges by source; an edge's slot is its rank in its source's group
v
neighbour table [num_nodes + 1, width] width = largest degree (host read 1)
| row i = nodes one hop from i, -1 pads;
| the extra last row is all -1
| hop h: gather the rows of the hop h - 1 frontier -> [num_anchors, width ** h]
v
candidates [num_anchors, 1 + width + ... + width ** hop_distance]
| select_sequences: the first copy of a node is its first-reach hop, so
| drop repeats, padding and the anchor itself
v
anchor first, then neighbours in ascending node id (or by hop, then node id,
or in expansion order, per ``ordering``), cut at max_seq_len
|
v
layout [num_anchors, max_seq_len] of (node << hop_bits) | hop (-1 at padding), lengths
|
| pack_sequences(lengths, max_seq_len) (host read 2: total tokens)
v
flat_index + PackedLayout(cu_seqlens, max_seq_len) for gigl.nn.packed_attention
- Constraints:
Memory is
O(num_anchors * width ** hop_distance); seeMAX_LAYOUT_CANDIDATESfor the limit.cu_seqlensis int32, so the total token count must fit in int32.
Example
>>> from gigl.transforms.sequence_layout import pack_sequences
>>> edge_index = torch.tensor([[0, 1, 2, 3], [1, 2, 3, 4]])
>>> layout, lengths, hop_bits = khop_layout(
... edge_index,
... num_nodes=5,
... anchor_indices=torch.arange(2),
... hop_distance=2,
... max_seq_len=4,
... sampling_direction="out",
... )
>>> hop_bits
2
>>> (layout >> hop_bits).tolist()
[[0, 1, 2, -1], [1, 2, 3, -1]]
>>> (layout & 3).tolist()
[[0, 1, 2, 3], [0, 1, 2, 3]]
>>> lengths.tolist()
[3, 3]
>>> flat_index, packed = pack_sequences(lengths, max_seq_len=4)
>>> (layout.flatten()[flat_index] >> hop_bits).tolist()
[0, 1, 2, 1, 2, 3]
>>> packed.cu_seqlens.tolist()
[0, 3, 6]
Attributes#
Exceptions#
The k-hop layout would have more candidates than the allowed maximum. |
Classes#
The k-hop token layout of every anchor, as built by |
Functions#
|
Builds the k-hop token layout of every anchor; the entry point of this module. |
Module Contents#
- exception gigl.transforms.static_khop.KhopLayoutTooLargeError[source]#
Bases:
ValueErrorThe k-hop layout would have more candidates than the allowed maximum.
Initialize self. See help(type(self)) for accurate signature.
- class gigl.transforms.static_khop.KhopLayout[source]#
Bases:
NamedTupleThe k-hop token layout of every anchor, as built by
khop_layout.- layout[source]#
[num_anchors, max_seq_len]long. Each valid cell packs a token’s local node id and first-reach hop as(node << hop_bits) | hop; decode withcell >> hop_bitsandcell & ((1 << hop_bits) - 1). Column 0 is the anchor (hop 0), followed by its neighbours withinhop_distancehops in theorderingorder. The hop is the fewest hops from the anchor to the node. Cells at and after the sequence length are -1, so only decode the valid ones.
- gigl.transforms.static_khop.khop_layout(edge_index, num_nodes, anchor_indices, hop_distance, max_seq_len, sampling_direction='out', max_candidates=MAX_LAYOUT_CANDIDATES, ordering='node_id')[source]#
Builds the k-hop token layout of every anchor; the entry point of this module.
Builds the neighbour table, checks the candidate budget, then expands and deduplicates each anchor’s neighbourhood into a padded
[num_anchors, max_seq_len]layout. Passlengthstopack_sequencesto drop the padding. A host read of the table width is the only sync here.- Parameters:
edge_index (jaxtyping.Int[torch.Tensor, 2 num_edges]) –
[2, num_edges]int32 or int64 local node ids in[0, num_nodes).num_nodes (int) – Number of nodes.
anchor_indices (jaxtyping.Int[torch.Tensor, num_anchors]) –
[num_anchors]int32 or int64 local node ids of the anchors.hop_distance (int) – Number of hops to expand.
max_seq_len (int) – Tokens per sequence, anchor included.
sampling_direction (Literal['in', 'out']) –
"in"reverses the edges,"out"keeps them.max_candidates (int) – Maximum number of candidates plus neighbour-table cells; see
MAX_LAYOUT_CANDIDATES.ordering (KhopOrdering) – How neighbours are ordered, and so which are cut at
max_seq_len:"node_id"by ascending node id,"hop"by first-reach hop and then node id,"input"by first reach in the expansion (hop, then the parent’s position, then the slot in the parent’s neighbour-table row, which follows the edge order).
- Returns:
layout,lengthsandhop_bits.- Return type:
The
KhopLayout- Raises:
ValueError – If
sampling_directionis not"in"or"out", ororderingis not"node_id","hop"or"input".KhopLayoutTooLargeError – If the table cells plus candidates exceed
max_candidates.