forked from tile-ai/TileOPs
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtest_deltanet_recurrence.py
More file actions
140 lines (108 loc) · 4.42 KB
/
Copy pathtest_deltanet_recurrence.py
File metadata and controls
140 lines (108 loc) · 4.42 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
from tileops.utils import get_backend_name
DEVICE = get_backend_name()
import pytest
import torch
from tests.test_base import FixtureBase, TestBase
from tileops.ops import DeltaNetDecodeOp
from workloads.deltanet import DeltaNetDecodeTest as _DeltaNetDecodeTestWorkload
def deltanet_decode_torch(
q: torch.Tensor,
k: torch.Tensor,
v: torch.Tensor,
beta: torch.Tensor,
state: torch.Tensor,
) -> tuple[torch.Tensor, torch.Tensor]:
"""Pure-PyTorch reference for single-step delta rule (ungated)."""
q, k, v = q.float(), k.float(), v.float()
beta = beta.float()
state = state.float()
old_val = torch.einsum("bhkv,bhk->bhv", state, k)
beta_unsq = beta.unsqueeze(-1)
v_new = beta_unsq * (v - old_val)
o_inter = torch.einsum("bhkv,bhk->bhv", state, q)
qk_dot = torch.einsum("bhk,bhk->bh", q, k).unsqueeze(-1)
o_intra = qk_dot * v_new
o = o_inter + o_intra
new_state = state + k.unsqueeze(-1) * v_new.unsqueeze(-2)
return o, new_state
class DeltaNetDecodeTest(_DeltaNetDecodeTestWorkload, TestBase):
def ref_program(
self,
q: torch.Tensor,
k: torch.Tensor,
v: torch.Tensor,
beta: torch.Tensor,
state: torch.Tensor,
) -> tuple[torch.Tensor, torch.Tensor]:
o, new_state = deltanet_decode_torch(q, k, v, beta, state)
return o.to(self.dtype), new_state.to(self.dtype)
# =============================================================================
# Torch reference implementation (test-only)
# =============================================================================
# =============================================================================
# Correctness tests
# =============================================================================
def _get_tolerances(dtype: torch.dtype) -> dict:
if dtype == torch.float32:
return {"atol": 5e-4, "rtol": 5e-4}
elif dtype == torch.float16:
return {"atol": 1e-2, "rtol": 1e-2}
else: # bfloat16
return {"atol": 2e-2, "rtol": 2e-2}
class DeltaNetDecodeFixture(FixtureBase):
PARAMS = [
("batch, heads, dim_k, dim_v, dtype, tune", [
pytest.param(1, 4, 64, 64, torch.float32, False, marks=pytest.mark.smoke),
pytest.param(1, 4, 64, 64, torch.float16, False, marks=pytest.mark.smoke),
pytest.param(1, 4, 64, 64, torch.bfloat16, False, marks=pytest.mark.smoke),
pytest.param(2, 8, 64, 64, torch.float32, False, marks=pytest.mark.full),
pytest.param(2, 4, 128, 128, torch.float32, False, marks=pytest.mark.full),
pytest.param(2, 8, 64, 64, torch.float16, False, marks=pytest.mark.full),
pytest.param(2, 8, 64, 64, torch.bfloat16, False, marks=pytest.mark.full),
]),
]
@DeltaNetDecodeFixture
def test_deltanet_decode(
batch: int,
heads: int,
dim_k: int,
dim_v: int,
dtype: torch.dtype,
tune: bool,
) -> None:
torch.manual_seed(42)
test = DeltaNetDecodeTest(batch, heads, dim_k, dim_v, dtype)
op = DeltaNetDecodeOp(batch, heads, dim_k, dim_v, dtype, tune=tune)
tols = _get_tolerances(dtype)
test.check(op, *test.gen_inputs(), **tols)
@DeltaNetDecodeFixture
def test_deltanet_decode_multi_step(
batch: int,
heads: int,
dim_k: int,
dim_v: int,
dtype: torch.dtype,
tune: bool,
) -> None:
"""Test multiple sequential decode steps to verify state propagation."""
torch.manual_seed(42)
num_steps = 8
B, H, DK, DV = batch, heads, dim_k, dim_v
op = DeltaNetDecodeOp(B, H, DK, DV, dtype, tune=tune)
tols = _get_tolerances(dtype)
state_op = torch.zeros(B, H, DK, DV, device=DEVICE, dtype=dtype)
state_ref = torch.zeros(B, H, DK, DV, device=DEVICE, dtype=dtype)
for _ in range(num_steps):
q = torch.randn(B, H, DK, device=DEVICE, dtype=dtype) * 0.1
k = torch.randn(B, H, DK, device=DEVICE, dtype=dtype) * 0.1
v = torch.randn(B, H, DV, device=DEVICE, dtype=dtype) * 0.1
beta = torch.rand(B, H, device=DEVICE, dtype=dtype) * 0.5
o_ref, state_ref = deltanet_decode_torch(q, k, v, beta, state_ref)
o_ref = o_ref.to(dtype)
state_ref = state_ref.to(dtype)
with torch.no_grad():
o_op, state_op = op(q, k, v, beta, state_op)
torch.testing.assert_close(o_op, o_ref, **tols)
torch.testing.assert_close(state_op, state_ref, **tols)
if __name__ == "__main__":
pytest.main([__file__, "-vvs"])