Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
a0fe5cb
refactor(ablp): extract per-anchor label loop into _loop_set_labels o…
Jun 25, 2026
00f4cdb
feat(ablp): add vectorized_set_labels kernel, equivalence-tested vs l…
Jun 25, 2026
fd3fda5
feat(ablp): add AnchorLabels edge-list container + edge_list_set_labe…
Jun 25, 2026
3101506
test(ablp): add CUDA device-placement regression for label-remap kernels
Jun 25, 2026
d51bd7a
feat(ablp): vectorized label remap always + use_list_output ctor flag
Jun 25, 2026
efa446d
docs(ablp): document vectorized remap, use_list_output, and AnchorLabels
Jun 25, 2026
8c91d66
docs(examples): consume AnchorLabels edge-list in homogeneous training
Jun 25, 2026
1f1c069
docs(examples): consume AnchorLabels edge-list in heterogeneous training
Jun 25, 2026
f60df78
docs(examples): consume AnchorLabels edge-list in graph-store homogen…
Jun 25, 2026
ee4adeb
docs(examples): consume AnchorLabels edge-list in graph-store heterog…
Jun 25, 2026
dab9f8f
feat(distributed): export AnchorLabels from gigl.distributed
Jun 25, 2026
bd97d03
style(ablp): apply ruff formatting to ABLP label-output examples and …
Jun 25, 2026
e6bd5b9
refactor(ablp): drop order-reproduction argsort; add readability refa…
Jun 25, 2026
594ddba
docs(examples): clarify AnchorLabels read is loss-equivalent, not ord…
Jun 25, 2026
641b24e
refactor(ablp): single AnchorLabels label-remap kernel + dict view
Jun 26, 2026
5a58a53
docs(ablp): why-first docstrings, worked examples, tensor-dim annotat…
Jun 26, 2026
b70e626
docs(ablp): worked example for use_list_output=True; harden node-map …
kmonte Jun 29, 2026
aa978ef
docs(ablp): scrub history-relative comments in label-remap code
kmonte Jun 29, 2026
b86604d
refactor: represent ABLP labels as edge index tensors
kmonte Jul 28, 2026
03248f9
fix(ablp): key label node map off label edge types, not supervision e…
kmonte Jul 28, 2026
8a11a8d
Enable multiple GraphStore supervision edge types
kmonte Jul 30, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
443 changes: 443 additions & 0 deletions docs/plans/20260730-graph-store-multiple-supervision-edge-types.md

Large diffs are not rendered by default.

20 changes: 7 additions & 13 deletions examples/link_prediction/graph_store/heterogeneous_training.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@ def _setup_dataloaders(
channel_size=sampling_worker_shared_channel_size,
process_start_gap_seconds=process_start_gap_seconds,
shuffle=shuffle,
use_edge_index_output=True,
)

print(f"---Rank {rank} finished setting up main loader for split={split}")
Expand Down Expand Up @@ -294,23 +295,16 @@ def _compute_loss(
)

# Extracting local query, random negative, positive, hard_negative, and random_negative indices.
query_node_idx: torch.Tensor = torch.arange(
main_data[query_node_type].batch_size
).to(device)
query_node_idx = torch.arange(main_data[query_node_type].batch_size, device=device)
random_negative_batch_size = random_negative_data[labeled_node_type].batch_size

positive_idx: torch.Tensor = torch.cat(list(main_data.y_positive.values())).to(
device
)
repeated_query_node_idx = query_node_idx.repeat_interleave(
torch.tensor([len(v) for v in main_data.y_positive.values()]).to(device)
)
positive_label_edge_index: torch.Tensor = main_data.y_positive
repeated_query_node_idx = query_node_idx[positive_label_edge_index[0]]
positive_idx = positive_label_edge_index[1]
if hasattr(main_data, "y_negative"):
hard_negative_idx: torch.Tensor = torch.cat(
list(main_data.y_negative.values())
).to(device)
hard_negative_idx: torch.Tensor = main_data.y_negative[1]
else:
hard_negative_idx = torch.empty(0, dtype=torch.long).to(device)
hard_negative_idx = torch.empty(0, dtype=torch.long, device=device)

# Use local IDs to get the corresponding embeddings in the tensors

Expand Down
18 changes: 7 additions & 11 deletions examples/link_prediction/graph_store/homogeneous_training.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,7 @@ def _setup_dataloaders(
channel_size=sampling_worker_shared_channel_size,
process_start_gap_seconds=process_start_gap_seconds,
shuffle=shuffle,
use_edge_index_output=True,
)

logger.info(f"---Rank {rank} finished setting up main loader for split={split}")
Expand Down Expand Up @@ -302,21 +303,16 @@ def _compute_loss(
main_embeddings = model(data=main_data, device=device)
random_negative_embeddings = model(data=random_negative_data, device=device)

query_node_idx: torch.Tensor = torch.arange(main_data.batch_size).to(device)
query_node_idx = torch.arange(main_data.batch_size, device=device)
random_negative_batch_size = random_negative_data.batch_size

positive_idx: torch.Tensor = torch.cat(list(main_data.y_positive.values())).to(
device
)
repeated_query_node_idx = query_node_idx.repeat_interleave(
torch.tensor([len(v) for v in main_data.y_positive.values()]).to(device)
)
positive_label_edge_index: torch.Tensor = main_data.y_positive
repeated_query_node_idx = query_node_idx[positive_label_edge_index[0]]
positive_idx = positive_label_edge_index[1]
if hasattr(main_data, "y_negative"):
hard_negative_idx: torch.Tensor = torch.cat(
list(main_data.y_negative.values())
).to(device)
hard_negative_idx: torch.Tensor = main_data.y_negative[1]
else:
hard_negative_idx = torch.empty(0, dtype=torch.long).to(device)
hard_negative_idx = torch.empty(0, dtype=torch.long, device=device)

# Use local IDs to get the corresponding embeddings in the tensors

Expand Down
22 changes: 7 additions & 15 deletions examples/link_prediction/heterogeneous_training.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ def _setup_dataloaders(
# This is done so that each process on the current machine which initializes a `main_loader` doesn't compete for memory, causing potential OOM
process_start_gap_seconds=process_start_gap_seconds,
shuffle=shuffle,
use_edge_index_output=True,
)

logger.info(f"---Rank {rank} finished setting up main loader")
Expand Down Expand Up @@ -218,25 +219,16 @@ def _compute_loss(
# Local in this case refers to the local index in the batch, while global subsequently refers to the node's unique global ID across all nodes in the dataset.
# Global ids are stored in data.node, ex. `data.node = [50, 20, 10]` where the `0` is the local index for global id `50`. Note that each global id in data.node is unique.
# TODO (mkolodner-sc): If GLT migrates towards using PyG's `.n_id` field instead of `.node`, update this code.
query_node_idx: torch.Tensor = torch.arange(
main_data[query_node_type].batch_size
).to(device)
query_node_idx = torch.arange(main_data[query_node_type].batch_size, device=device)
random_negative_batch_size = random_negative_data[labeled_node_type].batch_size

# main_data.y_positive is a dict[query_node_local_index: int, labeled_node_local_indices: torch.Tensor], even in the heterogeneous setting.
positive_idx: torch.Tensor = torch.cat(list(main_data.y_positive.values())).to(
device
)
# We also extract a repeated query node index tensor which upsamples each query node based on the number of positives it has
repeated_query_node_idx = query_node_idx.repeat_interleave(
torch.tensor([len(v) for v in main_data.y_positive.values()]).to(device)
)
positive_label_edge_index: torch.Tensor = main_data.y_positive
repeated_query_node_idx = query_node_idx[positive_label_edge_index[0]]
positive_idx = positive_label_edge_index[1]
if hasattr(main_data, "y_negative"):
hard_negative_idx: torch.Tensor = torch.cat(
list(main_data.y_negative.values())
).to(device)
hard_negative_idx: torch.Tensor = main_data.y_negative[1]
else:
hard_negative_idx = torch.empty(0, dtype=torch.long).to(device)
hard_negative_idx = torch.empty(0, dtype=torch.long, device=device)

# Use local IDs to get the corresponding embeddings in the tensors

Expand Down
21 changes: 8 additions & 13 deletions examples/link_prediction/homogeneous_training.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ def _setup_dataloaders(
# This is done so that each process on the current machine which initializes a `main_loader` doesn't compete for memory, causing potential OOM
process_start_gap_seconds=process_start_gap_seconds,
shuffle=shuffle,
use_edge_index_output=True,
)

logger.info(f"---Rank {rank} finished setting up main loader")
Expand Down Expand Up @@ -187,23 +188,17 @@ def _compute_loss(
# Extracting local query, random negative, positive, hard_negative, and random_negative indices.
# Local in this case refers to the local index in the batch, while global subsequently refers to the node's unique global ID across all nodes in the dataset.
# Global ids are stored in data.node, ex. `data.node = [50, 20, 10]` where the `0` is the local index for global id `50`. Note that each global id in data.node is unique.
query_node_idx: torch.Tensor = torch.arange(main_data.batch_size).to(device)
query_node_idx = torch.arange(main_data.batch_size, device=device)
random_negative_batch_size = random_negative_data.batch_size

# main_data.y_positive is a dict[query_node_local_index: int, labeled_node_local_indices: torch.Tensor]
positive_idx: torch.Tensor = torch.cat(list(main_data.y_positive.values())).to(
device
)
# We also extract a repeated query node index tensor which upsamples each query node based on the number of positives it has
repeated_query_node_idx = query_node_idx.repeat_interleave(
torch.tensor([len(v) for v in main_data.y_positive.values()]).to(device)
)
# Label edge indices are [anchor_local_id, label_local_id] pairs.
positive_label_edge_index: torch.Tensor = main_data.y_positive
repeated_query_node_idx = query_node_idx[positive_label_edge_index[0]]
positive_idx = positive_label_edge_index[1]
if hasattr(main_data, "y_negative"):
hard_negative_idx: torch.Tensor = torch.cat(
list(main_data.y_negative.values())
).to(device)
hard_negative_idx: torch.Tensor = main_data.y_negative[1]
else:
hard_negative_idx = torch.empty(0, dtype=torch.long).to(device)
hard_negative_idx = torch.empty(0, dtype=torch.long, device=device)

# Use local IDs to get the corresponding embeddings in the tensors

Expand Down
Loading