Skip to content

Commit 44dd81f

Browse files
committed
[Test] Add Pytest tests for cumsum_gdn and the fused sigmoid gating rule
cumsum_gdn parametrised over the forward and reverse scan, both layouts and the fragment buffer. Sequence lengths that are not a multiple of the chunk (250 against 32) and odd head counts stay in: those are what exercise the tail. The fused sigmoid gating rule takes its shapes as parameters and asserts the output and the final state against golden(), so the test drives it directly on the hundred alternating short sequences its guard uses. That guard wraps the call in a thread pool, which only matters when several cases run at once and there is one.
1 parent 0280195 commit 44dd81f

2 files changed

Lines changed: 110 additions & 0 deletions

File tree

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import importlib.util
2+
import sys
3+
from pathlib import Path
4+
from types import ModuleType
5+
6+
7+
def _load_example() -> ModuleType:
8+
source = Path(__file__).with_name("fused_sigmoid_gating_delta_rule_varlen.py")
9+
spec = importlib.util.spec_from_file_location("_fused_sigmoid_gating_example_for_test", source)
10+
if spec is None or spec.loader is None:
11+
raise ImportError(f"Cannot load example module: {source}")
12+
13+
module = importlib.util.module_from_spec(spec)
14+
original_argv = sys.argv
15+
try:
16+
sys.argv = [str(source)]
17+
spec.loader.exec_module(module)
18+
finally:
19+
sys.argv = original_argv
20+
21+
return module
22+
23+
24+
# main() takes its shapes as parameters and asserts the output and the final
25+
# state against golden(), so this drives it directly. The example's own guard
26+
# wraps the same call in a thread pool, which only matters when several cases
27+
# run at once and there is one.
28+
def test_fused_sigmoid_gating_delta_rule_varlen() -> None:
29+
example = _load_example()
30+
31+
# A hundred short sequences of alternating length, which is what makes this
32+
# the varlen case rather than a padded batch.
33+
example.main(seqlens=[4, 8] * 50, nk=16, nv=32, dk=128, dv=128)
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import importlib.util
2+
import sys
3+
from pathlib import Path
4+
from types import ModuleType
5+
6+
import pytest
7+
8+
9+
def _load_example() -> ModuleType:
10+
source = Path(__file__).with_name("example_cumsum.py")
11+
spec = importlib.util.spec_from_file_location("_cumsum_gdn_example_for_test", source)
12+
if spec is None or spec.loader is None:
13+
raise ImportError(f"Cannot load example module: {source}")
14+
15+
module = importlib.util.module_from_spec(spec)
16+
original_argv = sys.argv
17+
try:
18+
sys.argv = [str(source)]
19+
spec.loader.exec_module(module)
20+
finally:
21+
sys.argv = original_argv
22+
23+
return module
24+
25+
26+
# Shapes come from the example. Sequence lengths that are not a multiple of the
27+
# chunk (250 against 32) and head counts that are odd are what exercise the tail
28+
# handling, so both stay in.
29+
@pytest.mark.parametrize(
30+
"batch, heads, seq_len, chunk, reverse, head_first",
31+
[
32+
(2, 32, 256, 32, False, True),
33+
(2, 32, 256, 32, True, True),
34+
(2, 7, 250, 32, False, False),
35+
(2, 7, 250, 32, True, False),
36+
(4, 8, 512, 64, True, True),
37+
],
38+
)
39+
def test_chunk_cumsum(batch, heads, seq_len, chunk, reverse, head_first) -> None:
40+
import torch
41+
42+
example = _load_example()
43+
44+
shape = (batch, heads, seq_len) if head_first else (batch, seq_len, heads)
45+
torch.manual_seed(0)
46+
g = torch.randn(shape).npu().to(torch.float)
47+
48+
actual = example.chunk_cumsum(g, chunk, reverse=reverse, head_first=head_first)
49+
expected = example.ref_chunk_cumsum(g, chunk, reverse=reverse, head_first=head_first)
50+
51+
torch.testing.assert_close(actual.cpu(), expected.cpu(), rtol=1e-5, atol=1e-5)
52+
53+
54+
# use_fragment selects a different buffer for the running sum; the reference is
55+
# the same either way.
56+
@pytest.mark.parametrize(
57+
"batch, heads, seq_len, chunk, reverse, head_first",
58+
[
59+
(2, 32, 256, 32, False, True),
60+
(2, 32, 256, 32, True, True),
61+
(2, 7, 250, 32, False, False),
62+
(1, 16, 128, 64, True, True),
63+
],
64+
)
65+
def test_chunk_cumsum_fragment(batch, heads, seq_len, chunk, reverse, head_first) -> None:
66+
import torch
67+
68+
example = _load_example()
69+
70+
shape = (batch, heads, seq_len) if head_first else (batch, seq_len, heads)
71+
torch.manual_seed(0)
72+
g = torch.randn(shape).npu().to(torch.float)
73+
74+
actual = example.chunk_cumsum(g, chunk, reverse=reverse, head_first=head_first, use_fragment=True)
75+
expected = example.ref_chunk_cumsum(g, chunk, reverse=reverse, head_first=head_first)
76+
77+
torch.testing.assert_close(actual.cpu(), expected.cpu(), rtol=1e-5, atol=1e-5)

0 commit comments

Comments
 (0)