Skip to content

Commit 32f7bfc

Browse files
authored
Make hooks seedable and added instance-level properties (#396)
* Made requires and produces to instance level variables * WIP * Added id to each hook * Updated with add attribute to hook * Updated BaseDGHook * Applied ruff format * Applied add_attribute_to_batch to add produce attribute to the batch * typo * Rename add attribute to batch function and added comments to SeedableHook
1 parent 0600303 commit 32f7bfc

22 files changed

Lines changed: 1003 additions & 342 deletions

examples/linkproppred/tgn.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ def eval(
205205
)
206206
train_key, val_key, test_key = hm.keys
207207
hm.register_shared(nbr_hook)
208-
hm.register_shared(DeduplicationHook())
208+
hm.register_shared(DeduplicationHook(seed_nodes_keys=['neg', 'nbr_nids']))
209209

210210
train_loader = DGDataLoader(train_dg, args.bsize, hook_manager=hm)
211211
val_loader = DGDataLoader(val_dg, args.bsize, hook_manager=hm)

test/unit/test_hooks/test_batch_analytics_hook.py

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,29 @@ def test_hook_dependancies():
5050
'num_repeated_node_events',
5151
}
5252

53+
hook_with_id = BatchAnalyticsHook(id='foo')
54+
assert hook_with_id.requires == {
55+
'edge_src',
56+
'edge_dst',
57+
'edge_time',
58+
'node_x_time',
59+
'node_x_nids',
60+
}
61+
assert hook_with_id.produces == {
62+
'num_edge_events_foo',
63+
'num_node_events_foo',
64+
'num_unique_timestamps_foo',
65+
'num_unique_nodes_foo',
66+
'avg_degree_foo',
67+
'num_repeated_edge_events_foo',
68+
'num_repeated_node_events_foo',
69+
}
70+
71+
72+
def test_hook_repre():
73+
hook_with_id = BatchAnalyticsHook(id='foo')
74+
assert 'foo' in hook_with_id.__repr__()
75+
5376

5477
def test_hook_reset_state():
5578
assert BatchAnalyticsHook.has_state is False
@@ -60,8 +83,6 @@ def test_basic_analytics_num_events_and_timestamps(dg):
6083
batch = dg.materialize()
6184
processed_batch = hook(dg, batch)
6285

63-
# assert batch.node_x_nids is not None
64-
6586
# edge and node events
6687
assert processed_batch.num_edge_events == 3
6788
assert processed_batch.num_node_events == 3
@@ -121,3 +142,24 @@ def test_basic_analytics_empty_edges_and_nodes(dg):
121142

122143
# _count_repeated_node_events: node_x_nids.numel() == 0 -> 0
123144
assert processed_batch.num_repeated_node_events == 0
145+
146+
147+
def test_hook_with_id(dg):
148+
hook = BatchAnalyticsHook(id='foo')
149+
batch = dg.materialize()
150+
151+
batch = dg.materialize()
152+
processed_batch = hook(dg, batch)
153+
154+
expected_produce = [
155+
'num_edge_events_foo',
156+
'num_node_events_foo',
157+
'num_unique_timestamps_foo',
158+
'num_unique_nodes_foo',
159+
'avg_degree_foo',
160+
'num_repeated_edge_events_foo',
161+
'num_repeated_node_events_foo',
162+
]
163+
164+
for produce in expected_produce:
165+
assert hasattr(processed_batch, produce)

test/unit/test_hooks/test_deduplication_hook.py

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,18 @@ def dg():
1616

1717

1818
def test_hook_dependancies():
19-
assert DeduplicationHook.requires == {'edge_src', 'edge_dst'}
20-
assert DeduplicationHook.produces == {'unique_nids', 'global_to_local'}
19+
hook = DeduplicationHook()
20+
assert hook.requires == {'edge_src', 'edge_dst'}
21+
assert hook.produces == {'unique_nids', 'global_to_local'}
22+
23+
hook_with_id = DeduplicationHook(id='foo')
24+
assert hook_with_id.requires == {'edge_src', 'edge_dst'}
25+
assert hook_with_id.produces == {'unique_nids_foo', 'global_to_local_foo'}
26+
27+
28+
def test_hook_repre():
29+
hook_with_id = DeduplicationHook(id='foo')
30+
assert 'foo' in hook_with_id.__repr__()
2131

2232

2333
def test_hook_reset_state():
@@ -39,8 +49,23 @@ def test_dedup(dg):
3949
)
4050

4151

52+
def test_dedup_with_id(dg):
53+
hook = DeduplicationHook(id='foo')
54+
batch = dg.materialize()
55+
processed_batch = hook(dg, batch)
56+
torch.testing.assert_close(
57+
processed_batch.unique_nids_foo, torch.IntTensor([1, 2, 4, 8])
58+
)
59+
torch.testing.assert_close(
60+
processed_batch.global_to_local_foo(batch.edge_src), torch.IntTensor([1, 1, 0])
61+
)
62+
torch.testing.assert_close(
63+
processed_batch.global_to_local_foo(batch.edge_dst), torch.IntTensor([1, 2, 3])
64+
)
65+
66+
4267
def test_dedup_with_negatives(dg):
43-
hook = DeduplicationHook()
68+
hook = DeduplicationHook(seed_nodes_keys=['neg'])
4469
batch = dg.materialize()
4570
batch.neg = torch.IntTensor([1, 5, 10]) # add some mock negatives
4671

@@ -60,7 +85,7 @@ def test_dedup_with_negatives(dg):
6085

6186

6287
def test_dedup_with_nbrs(dg):
63-
hook = DeduplicationHook()
88+
hook = DeduplicationHook(seed_nodes_keys=['nbr_nids'])
6489
batch = dg.materialize()
6590
batch.nbr_nids = [ # add some mock neighbours
6691
torch.IntTensor([1, 5]), # First hop
@@ -107,7 +132,7 @@ def node_only_graph():
107132

108133
def test_dedup_node_only_batch(node_only_graph):
109134
hm = HookManager(keys=['unit'])
110-
hm.register('unit', DeduplicationHook())
135+
hm.register('unit', DeduplicationHook(seed_nodes_keys=['node_x_nids']))
111136
loader = DGDataLoader(node_only_graph, batch_size=3, hook_manager=hm)
112137
with hm.activate('unit'):
113138
batch_iter = iter(loader)

test/unit/test_hooks/test_device_transfer_hook.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@ def dg():
1515

1616

1717
def test_hook_dependancies():
18-
assert DeviceTransferHook.requires == set()
19-
assert DeviceTransferHook.produces == set()
18+
hook = DeviceTransferHook('cpu')
19+
assert hook.requires == set()
20+
assert hook.produces == set()
2021

2122

2223
def test_hook_reset_state():

test/unit/test_hooks/test_hook_manager.py

Lines changed: 39 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -11,29 +11,47 @@
1111

1212

1313
class MockHook(StatelessHook):
14-
produces = {'foo'}
14+
_cls_produces = {'foo'}
15+
16+
def __init__(self, id: str = None):
17+
super().__init__()
18+
self._id = id
19+
self.__post_init__()
1520

1621
def __call__(self, dg: DGraph, batch: DGBatch) -> DGBatch:
1722
batch.edge_time *= 2
1823
return batch
1924

2025

2126
class MockHookRequires(StatelessHook):
22-
requires = {'foo'}
27+
_cls_requires = {'foo'}
28+
29+
def __init__(self, id: str = None):
30+
super().__init__()
31+
self._id = id
32+
self.__post_init__()
2333

2434
def __call__(self, dg: DGraph, batch: DGBatch) -> DGBatch:
2535
return batch
2636

2737

28-
class DeduplicationMockHook(StatelessHook):
38+
class MockHookRequiresWoof(StatelessHook):
39+
_cls_requires = {'foo_woof'}
40+
41+
def __init__(self, id: str = None):
42+
super().__init__()
43+
self._id = id
44+
self.__post_init__()
45+
2946
def __call__(self, dg: DGraph, batch: DGBatch) -> DGBatch:
3047
return batch
3148

3249

3350
class MockHookWithState(StatefulHook):
34-
has_state: bool = True
35-
36-
def __init__(self) -> None:
51+
def __init__(self, id: str = None) -> None:
52+
super().__init__()
53+
self._id = id
54+
self.has_state = True
3755
self.x = 0
3856

3957
def __call__(self, dg: DGraph, batch: DGBatch) -> DGBatch:
@@ -165,8 +183,8 @@ def test_resolve_hooks_by_key():
165183
def test_resolve_hooks_no_solution_no_dag():
166184
h1 = MockHook()
167185
h2 = MockHook()
168-
h1.requires, h1.produces = {'x'}, {'y'}
169-
h2.requires, h2.produces = {'y'}, {'x'}
186+
h1._requires, h1._produces = {'x'}, {'y'}
187+
h2._requires, h2._produces = {'y'}, {'x'}
170188

171189
# Cycle-like missing dependency
172190
hm = HookManager(keys=['train'])
@@ -210,8 +228,8 @@ def test_topo_sort_cached(dg, monkeypatch):
210228
hm = HookManager(keys=['train'])
211229

212230
h1, h2 = MockHook(), MockHook()
213-
h1.requires, h1.produces = set(), {'x'}
214-
h2.requires, h2.produces = {'x'}, {'y'}
231+
h1._requires, h1._produces = set(), {'x'}
232+
h2._requires, h2._produces = {'x'}, {'y'}
215233

216234
hm.register('train', h1)
217235
hm.register('train', h2)
@@ -237,7 +255,7 @@ def test_topo_sort_cached_invalidated(dg, monkeypatch):
237255
hm = HookManager(keys=['train'])
238256

239257
h1 = MockHook()
240-
h1.requires, h1.produces = set(), {'x'}
258+
h1._requires, h1._produces = set(), {'x'}
241259

242260
hm.register('train', h1)
243261
call_count = {'n': 0}
@@ -266,8 +284,8 @@ def fake_topo_sort(hooks_list):
266284
def test_topo_sort_no_solution_no_dag(dg):
267285
h1 = MockHook()
268286
h2 = MockHook()
269-
h1.requires, h1.produces = {'x'}, {'y'}
270-
h2.requires, h2.produces = {'y'}, {'x'}
287+
h1._requires, h1._produces = {'x'}, {'y'}
288+
h2._requires, h2._produces = {'y'}, {'x'}
271289

272290
# Cycle-like missing dependency
273291
hm = HookManager(keys=['train'])
@@ -362,8 +380,8 @@ def test_activate_ctx():
362380

363381
def test_topo_sort_neg_before_nbr():
364382
mock_neg_hook, mock_nbr_hook = MockHook(), MockHook()
365-
mock_neg_hook.requires, mock_neg_hook.produces = set(), {'neg'}
366-
mock_nbr_hook.requires, mock_nbr_hook.produces = set(), {'nbr_nids'}
383+
mock_neg_hook._requires, mock_neg_hook._produces = set(), {'neg'}
384+
mock_nbr_hook._requires, mock_nbr_hook._produces = set(), {'nbr_nids'}
367385

368386
# Register neg first in foo, nbr first in bar
369387
hm = HookManager(keys=['foo', 'bar'])
@@ -381,25 +399,14 @@ def test_topo_sort_neg_before_nbr():
381399
assert bar_hooks.index(mock_neg_hook) < bar_hooks.index(mock_nbr_hook)
382400

383401

384-
def test_force_last_dedup_hook():
385-
# @TODO: Dedup hook is temporarily forced to run at the end.
386-
# This test potentially needs to be updated once instance-level require is introduced to DGHook.
387-
h1 = MockHook()
388-
h2 = MockHookRequires()
389-
h3 = DeduplicationMockHook()
402+
def test_resolve_hooks_with_id_by_key():
403+
h1 = MockHook(id='woof') # MockHook produces with _woof suffix
404+
h2 = MockHookRequiresWoof()
390405

391-
hm = HookManager(keys=['train', 'val'])
392-
hm.register('train', h3)
406+
hm = HookManager(keys=['train'])
393407
hm.register('train', h2)
394408
hm.register('train', h1)
395-
hm.register('val', h3)
396-
hm.register('val', h2)
397-
hm.register('val', h1)
398409

399-
hm.resolve_hooks()
400-
assert len(hm._key_to_hooks['train']) == 3
401-
assert len(hm._key_to_hooks['val']) == 3
410+
hm.resolve_hooks('train')
411+
assert len(hm._key_to_hooks['train']) == 2
402412
assert hm._key_to_hooks['train'].index(h1) < hm._key_to_hooks['train'].index(h2)
403-
assert hm._key_to_hooks['val'].index(h1) < hm._key_to_hooks['val'].index(h2)
404-
assert hm._key_to_hooks['train'].index(h2) < hm._key_to_hooks['train'].index(h3)
405-
assert hm._key_to_hooks['val'].index(h2) < hm._key_to_hooks['val'].index(h3)

test/unit/test_hooks/test_negative_edge_sampler_hook.py

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,18 @@ def data():
1414

1515

1616
def test_hook_dependancies():
17-
assert NegativeEdgeSamplerHook.requires == {'edge_src', 'edge_dst', 'edge_time'}
18-
assert NegativeEdgeSamplerHook.produces == {'neg', 'neg_time'}
17+
hook = NegativeEdgeSamplerHook(low=0, high=10)
18+
assert hook.requires == {'edge_src', 'edge_dst', 'edge_time'}
19+
assert hook.produces == {'neg', 'neg_time'}
20+
21+
hook_with_id = NegativeEdgeSamplerHook(low=0, high=10, id='foo')
22+
assert hook_with_id.requires == {'edge_src', 'edge_dst', 'edge_time'}
23+
assert hook_with_id.produces == {'neg_foo', 'neg_time_foo'}
24+
25+
26+
def test_hook_repre():
27+
hook_with_id = NegativeEdgeSamplerHook(low=0, high=10, id='foo')
28+
assert 'foo' in hook_with_id.__repr__()
1929

2030

2131
def test_hook_reset_state():
@@ -42,6 +52,17 @@ def test_negative_edge_sampler(data):
4252
assert batch.neg_time.shape == batch.neg.shape
4353

4454

55+
def test_negative_edge_sampler_with_id(data):
56+
dg = DGraph(data)
57+
hook = NegativeEdgeSamplerHook(low=0, high=10, id='foo')
58+
batch = hook(dg, dg.materialize())
59+
assert isinstance(batch, DGBatch)
60+
assert torch.is_tensor(batch.neg_foo)
61+
assert torch.is_tensor(batch.neg_time_foo)
62+
assert batch.neg_foo.shape == batch.edge_dst.shape
63+
assert batch.neg_time_foo.shape == batch.neg_foo.shape
64+
65+
4566
@pytest.fixture
4667
def node_only_data():
4768
edge_index = torch.IntTensor([[1, 2], [2, 3], [3, 4]])

0 commit comments

Comments
 (0)