Skip to content

Optimize anchor-only graph transformer inference - #732

Draft
kmontemayor2-sc wants to merge 11 commits into
mainfrom
kmonte/anchor-only-final-layer
Draft

Optimize anchor-only graph transformer inference#732
kmontemayor2-sc wants to merge 11 commits into
mainfrom
kmonte/anchor-only-final-layer

Conversation

@kmontemayor2-sc

@kmontemayor2-sc kmontemayor2-sc commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator

Summary

At batch size 1,120 on a Tesla T4, this reduces median graph-transformer transform + encoder latency from 1,298.46 ms to 781.69 ms and raises throughput from 862.6 to 1,432.8 anchors/s (+66.1%).

T4 metric (B=1,120) GiGL main This PR Delta
Transform + encoder median 1,298.46 ms 781.69 ms -39.8%
Encoder median 1,287.69 ms 762.94 ms -40.8%
Anchor throughput 862.6/s 1,432.8/s +66.1%
Peak allocated memory 9.158 GB 9.158 GB 0%

anchor_only readout discards every final-layer output except token zero. The specialized final-layer path keeps full-sequence keys, values, and source relation features, but computes query-side attention, relation-message targets, output projection, and feed-forward work only for the anchor.

The encoder selection logic enables this path during evaluation with anchor_only readout. Relation-aware attention uses a rectangular anchor-query bias while preserving full-sequence keys.

Relation-aware T4 measurements retain the same benefit:

B=1,120 mode Full final layer Anchor final layer Throughput change
Edge-type bilinear attention 1,540.78 ms 932.41 ms +65.25%
Edge-type HGT attention 1,592.89 ms 961.55 ms +65.66%

Both cases use nonzero learned relation parameters and match the full path within 1.0431e-7 maximum absolute error.

Correctness

The production-count T4 fixture produced:

Output metric Value
Cosine similarity mean 1.0
Maximum absolute error 1.0431e-7
Relative L2 error 2.2403e-7

The new real-tensor tests compare the specialized output with an independently evaluated full-sequence reference. They exercise:

  • plain attention;
  • learned linear relation messages;
  • learned attention-weighted relation messages;
  • ragged valid masks and additive attention bias;
  • nonzero bilinear and HGT relation-aware attention with rectangular anchor-query bias.

Validation

  • 66/66 graph-transformer unit tests pass.
  • Ruff lint and format checks pass.
  • Changed-file ty checks pass.
  • git diff --check passes.
  • Real T4 A/B used BAGL's production graph-transformer initializer with production-scale node, edge, sequence, and feature dimensions.

The benchmark graph topology, input features, and initial weights are synthetic. A checkpoint-backed L4 validation remains the next deployment gate.

Full diff: 322d351...6989558

Comment thread gigl/nn/graph_transformer.py Outdated
Anchor output of shape ``(batch, 1, model_dim)``.

"""
batch_size, seq_len, model_dim = x.shape

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.

I'm a little wary that we have some copied over logic in this separate forward, changes in the original forward might not be captured. Can the anchor only be a condition in the existing forward?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good point - we now have a shared path :)


return x

def _zero_relation_parameter_dependency(self, reference: Tensor) -> Tensor:

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.

Do we need this? Should be unrelated to this PR?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_forward_anchor_only() filters relation edges whose query position is not the anchor. During training, if that filters every edge, the full-sequence path still includes the relation parameters in autograd and yields zero gradients, while the optimized path would mark them unused (grad=None). _zero_relation_parameter_dependency() adds a zero-valued dependency only in that case, preserving DDP unused-parameter handling and optimizer semantics without changing the forward value or gradients. It is required for training parity, not inference.

Comment on lines +1933 to +1960
final_encoder_layer = (
cast(GraphTransformerEncoderLayer, encoder_layers[-1])
if use_anchor_only_final_layer
else None
)
for encoder_layer_module in encoder_layers:
encoder_layer = cast(
GraphTransformerEncoderLayer,
encoder_layer_module,
)
if encoder_layer is final_encoder_layer:
break
x = encoder_layer(
x,
attn_bias=attn_bias,
pairwise_relation_indices=pairwise_relation_indices,
valid_mask=valid_mask,
)

output_valid_mask = valid_mask
if final_encoder_layer is not None:
x = final_encoder_layer._forward_anchor_only(
x,
attn_bias=attn_bias,
pairwise_relation_indices=pairwise_relation_indices,
valid_mask=valid_mask,
)
output_valid_mask = valid_mask[:, :1]

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.

include some comments on what and why we do this

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do these comments help make it clear?

Comment thread gigl/nn/graph_transformer.py Outdated
# anchor query/output and shorten the mask to match that one-token result.
output_valid_mask = valid_mask
if final_encoder_layer is not None:
x = final_encoder_layer._forward_anchor_only(

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.

Direct method calls skip nn.Module.__call__, so for the final layer this silently bypasses forward hooks and per layer checkpoint wrappers.

Suggestion: have forward pass x.size(1) if query_seq_len is None else query_seq_len to _forward_query_prefix, and call the layer here as:

x = final_encoder_layer(
    x,
    attn_bias=attn_bias,
    valid_mask=valid_mask,
    pairwise_relation_indices=pairwise_relation_indices,
    query_seq_len=1,
)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good callout, done :)

@kmontemayor2-sc
kmontemayor2-sc force-pushed the kmonte/anchor-only-final-layer branch from 58aa3d2 to 3960125 Compare August 10, 2026 23:15
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.

4 participants