gigl.transforms.sequence_layout#
Shared pieces for building per-anchor token sequences.
select_sequences picks each anchor’s tokens from its candidates into a padded
[num_sequences, max_seq_len] grid, whatever generated the candidates (k-hop
expansion, PPR edges). PackedLayout holds the offsets
gigl.nn.packed_attention needs to run on real tokens only, and
pack_sequences computes it from the grid’s sequence lengths.
Classes#
Where each sequence lives in the packed token buffer. |
|
The tokens |
Functions#
|
Computes what is needed to drop the padding from a padded grid of sequences. |
|
Picks each anchor's tokens from its candidates, with fixed shapes. |
Module Contents#
- class gigl.transforms.sequence_layout.PackedLayout[source]#
Bases:
NamedTupleWhere each sequence lives in the packed token buffer.
- cu_seqlens[source]#
[num_sequences + 1]int32 cumulative sequence lengths, on the same device as the tokens. Sequenceioccupies[cu_seqlens[i], cu_seqlens[i + 1]). The attention kernel requires int32, so the total token count must fit in int32.
- class gigl.transforms.sequence_layout.SelectedSequences[source]#
Bases:
NamedTupleThe tokens
select_sequencespicked for each anchor.- nodes[source]#
[num_anchors, max_seq_len]long node ids, the anchor in column 0 ifinclude_anchor, -1 from the sequence length on.
- source[source]#
[num_anchors, max_seq_len]long column ofcandidateseach token came from, so a per-candidate payload follows withpayload.gather(1, source.clamp(min=0)). It is -1 at the anchor and at padding.
- gigl.transforms.sequence_layout.pack_sequences(lengths, max_seq_len)[source]#
Computes what is needed to drop the padding from a padded grid of sequences.
Sequence
iis valid in its firstlengths[i]positions. Any[num_sequences, max_seq_len]per-position tensor gathers into packed order withgrid.flatten()[flat_index], andcu_seqlensmarks where each packed sequence starts. This costs one host read, of the total token count; it is why the packed buffer holds exactly the real tokens.A length above
max_seq_lenis clamped to it, because the grid has no positions beyond that. Checking for it would cost another host read.Example
>>> flat_index, packed = pack_sequences(torch.tensor([2, 1, 3]), max_seq_len=3) >>> flat_index.tolist() [0, 1, 3, 6, 7, 8] >>> packed.cu_seqlens.tolist() [0, 2, 3, 6]
- Parameters:
max_seq_len (int) – Width of the padded grid.
lengths (jaxtyping.Int64[torch.Tensor, num_sequences])
- Returns:
[num_tokens]long indices of the valid positions in the flattened grid, sequence by sequence.The packed layout.
- Return type:
tuple[jaxtyping.Int64[torch.Tensor, num_tokens], PackedLayout]
- gigl.transforms.sequence_layout.select_sequences(anchor_indices, candidates, max_seq_len, priority=None, ties_by_column=False, include_anchor=True)[source]#
Picks each anchor’s tokens from its candidates, with fixed shapes.
Every anchor’s sequence is the anchor, then its distinct candidate nodes other than the anchor, ordered by
priority(ties by ascending node id, or by column withties_by_column) and cut atmax_seq_len. A node that appears in several columns keeps the lowest column, so put the preferred copy of a node first. This is the selection step shared by the k-hop and PPR sequence builders: they differ in how they generate the candidates and in whatpriorityranks.Example
Two anchors, 0 and 1. Node 3 is a duplicate candidate of anchor 0, anchor 0 is among its own candidates, and -1 is padding:
>>> anchors = torch.tensor([0, 1]) >>> candidates = torch.tensor([[5, 3, 0, 3, -1], [4, 1, 6, 4, 2]]) >>> nodes, source, lengths = select_sequences(anchors, candidates, 4) >>> nodes.tolist() [[0, 3, 5, -1], [1, 2, 4, 6]] >>> lengths.tolist() [3, 4]
Gather a per-candidate payload with
source, zeroing the anchor and padding tokens:>>> payload = torch.arange(10, 20).reshape(2, 5) >>> (payload.gather(1, source.clamp(min=0)) * (source >= 0)).tolist() [[0, 11, 10, 0], [0, 19, 15, 17]]
A
priorityranks the candidates (smaller first); equal priorities go by node id, or by column withties_by_column:>>> priority = torch.tensor([[0, 1, 0, 1, 0], [2, 9, 0, 5, 0]]) >>> select_sequences(anchors, candidates, 4, priority).nodes.tolist() [[0, 5, 3, -1], [1, 2, 6, 4]] >>> select_sequences( ... anchors, candidates, 4, priority, ties_by_column=True ... ).nodes.tolist() [[0, 5, 3, -1], [1, 6, 2, 4]]
Without
include_anchorthe candidates are the whole sequence, so anchor 0 stays in its sequence as the candidate it is:>>> select_sequences(anchors, candidates, 4, include_anchor=False).source.tolist() [[2, 1, 0, -1], [1, 4, 0, 2]]
- Parameters:
anchor_indices (jaxtyping.Int[torch.Tensor, num_anchors]) –
[num_anchors]node ids of the anchors.candidates (jaxtyping.Int64[torch.Tensor, num_anchors num_candidates]) –
[num_anchors, num_candidates]long candidate node ids, -1 at padding. The anchor itself may appear. There can be at most2 ** 26columns.max_seq_len (int) – Tokens per sequence, anchor included.
priority (Optional[torch.Tensor]) –
[num_anchors, num_candidates]integer or float rank of each candidate; smaller comes first. If None, candidates are ordered by ascending node id, which costs one sort instead of two. Pass the column index to keep the candidates’ own order.ties_by_column (bool) – Whether candidates with equal
prioritystay in column order instead of ascending node id. It has no effect withoutpriority.include_anchor (bool) – Whether column 0 holds the anchor, which is then dropped from the candidates. If False, the sequence is only candidates, and the anchor stays in it if it is one of them.
- Returns:
nodes,sourceandlengths.- Return type:
The
SelectedSequences