Skip to content

Commit 0527773

Browse files
add Dao-style standard FHT accuracy tests (新增 Dao 风格标准 FHT 精度测试) (flagos-ai#4863)
1 parent 424111a commit 0527773

2 files changed

Lines changed: 140 additions & 17 deletions

File tree

benchmark/test_hadamard_transform.py

Lines changed: 40 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,23 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
import math
16+
1517
import pytest
1618
import torch
19+
import torch.nn.functional as F
1720
import triton
1821
from packaging.version import Version
1922

2023
import flag_gems
2124

2225
from . import base, consts
2326

27+
try:
28+
from scipy.linalg import hadamard as scipy_hadamard
29+
except ImportError: # pragma: no cover
30+
scipy_hadamard = None
31+
2432
_TRITON_VERSION = Version(triton.__version__.split("+")[0])
2533
_SKIP_JOIN_BUG = _TRITON_VERSION < Version("3.5.0")
2634
_skip_if_join_bug = pytest.mark.skipif(
@@ -37,8 +45,8 @@
3745
(1024, 512),
3846
(1024, 1024),
3947
(1024, 4096),
40-
(1024, 16384),
41-
(1024, 32768),
48+
# (1024, 16384), # scipy full matrix too slow; temporarily commented
49+
# (1024, 32768),
4250
(8192, 256),
4351
(8192, 512),
4452
(8192, 1024),
@@ -56,20 +64,32 @@ def ht_input_fn(shape, dtype, device):
5664
yield (torch.randn(batch, dim, dtype=dtype, device=device),)
5765

5866

59-
def _hadamard_matrix(n: int, device) -> torch.Tensor:
60-
H = torch.tensor([[1.0]], device=device)
61-
while H.shape[0] < n:
62-
H = torch.cat([torch.cat([H, H], dim=1), torch.cat([H, -H], dim=1)], dim=0)
63-
return H
67+
def _hadamard_transform_ref(x, scale=1.0):
68+
"""Reference matching tests/test_hadamard_transform.py (Dao scipy matrix multiply)."""
69+
if scipy_hadamard is None:
70+
raise ImportError("Please install scipy")
71+
x_shape = x.shape
72+
dim = x.shape[-1]
73+
x = x.reshape(-1, dim)
74+
log_dim = math.ceil(math.log2(dim)) if dim > 0 else 0
75+
dim_padded = 1 << log_dim if dim > 0 else 1
76+
if dim != dim_padded:
77+
x = F.pad(x, (0, dim_padded - dim))
78+
out = F.linear(
79+
x,
80+
torch.tensor(
81+
scipy_hadamard(dim_padded, dtype=float),
82+
dtype=x.dtype,
83+
device=x.device,
84+
),
85+
)
86+
out = out * scale
87+
return out[..., :dim].reshape(*x_shape)
6488

6589

6690
def torch_ht(x):
67-
"""Reference: Hadamard matrix multiply in fp32."""
68-
dim = x.shape[-1]
69-
padded = 1 << (dim - 1).bit_length() if dim > 1 else 1
70-
H = _hadamard_matrix(padded, x.device).to(x.dtype)
71-
x_padded = torch.nn.functional.pad(x, (0, padded - dim))
72-
return (x_padded @ H.T)[..., :dim]
91+
"""Benchmark baseline: same scipy Hadamard ref as correctness tests."""
92+
return _hadamard_transform_ref(x)
7393

7494

7595
class HadamardBenchmark(base.GenericBenchmark2DOnly):
@@ -79,6 +99,10 @@ class HadamardBenchmark(base.GenericBenchmark2DOnly):
7999
def set_more_shapes(self):
80100
return []
81101

102+
def set_shapes(self, *args, **kwargs):
103+
# Force _FHT_SHAPES; do not fall back to GenericBenchmark2DOnly in core_shapes.yaml
104+
self.shapes = self.DEFAULT_SHAPES
105+
82106

83107
@pytest.mark.hadamard_transform
84108
@_skip_if_join_bug
@@ -145,11 +169,11 @@ def ht_mn_input_fn(shape, dtype, device):
145169

146170

147171
def torch_ht_mn(x):
148-
"""Reference: pad to next power of 2 and run standard FHT."""
172+
"""Benchmark baseline: pad to next power of 2, then same scipy ref (not gems)."""
149173
dim = x.shape[-1]
150174
padded = 1 << (dim - 1).bit_length()
151-
x_padded = torch.nn.functional.pad(x, (0, padded - dim))
152-
return flag_gems.hadamard_transform(x_padded)[..., :dim]
175+
x_padded = F.pad(x, (0, padded - dim))
176+
return _hadamard_transform_ref(x_padded)[..., :dim]
153177

154178

155179
def gems_ht_mn(x):

tests/test_hadamard_transform.py

Lines changed: 100 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,11 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
import math
16+
1517
import pytest
1618
import torch
19+
import torch.nn.functional as F
1720
import triton
1821
from packaging.version import Version
1922

@@ -22,13 +25,40 @@
2225
from . import accuracy_utils as utils
2326
from . import conftest as cfg
2427

28+
try:
29+
from scipy.linalg import hadamard as scipy_hadamard
30+
except ImportError: # pragma: no cover
31+
scipy_hadamard = None
32+
2533
_TRITON_VERSION = Version(triton.__version__.split("+")[0])
2634
_SKIP_JOIN_BUG = _TRITON_VERSION < Version("3.5.0")
2735
_skip_if_join_bug = pytest.mark.skipif(
2836
_SKIP_JOIN_BUG,
2937
reason=f"triton {triton.__version__} has tt.join layout bug (fixed in 3.5.0)",
3038
)
3139

40+
# Dao-style dims for standard FHT (exclude 16384/32768: scipy full matrix too large)
41+
if cfg.QUICK_MODE:
42+
HADAMARD_DIMS = [64, 137, 256, 512, 1024]
43+
else:
44+
HADAMARD_DIMS = [
45+
1,
46+
2,
47+
4,
48+
8,
49+
16,
50+
32,
51+
64,
52+
128,
53+
256,
54+
512,
55+
137,
56+
1024,
57+
2048,
58+
4096,
59+
8192,
60+
]
61+
3262
if cfg.QUICK_MODE:
3363
HADAMARD_MN_CASES = [
3464
(1536, 3, "12N"),
@@ -57,6 +87,72 @@
5787
}
5888

5989

90+
def _hadamard_transform_ref(x, scale=1.0):
91+
"""Reference matching Dao-AILab fast_hadamard_transform_interface.hadamard_transform_ref."""
92+
if scipy_hadamard is None:
93+
raise ImportError("Please install scipy")
94+
x_shape = x.shape
95+
dim = x.shape[-1]
96+
x = x.reshape(-1, dim)
97+
log_dim = math.ceil(math.log2(dim)) if dim > 0 else 0
98+
dim_padded = 1 << log_dim if dim > 0 else 1
99+
if dim != dim_padded:
100+
x = F.pad(x, (0, dim_padded - dim))
101+
out = F.linear(
102+
x,
103+
torch.tensor(
104+
scipy_hadamard(dim_padded, dtype=float),
105+
dtype=x.dtype,
106+
device=x.device,
107+
),
108+
)
109+
out = out * scale
110+
return out[..., :dim].reshape(*x_shape)
111+
112+
113+
@pytest.mark.hadamard_transform
114+
@_skip_if_join_bug
115+
@pytest.mark.parametrize("dtype", utils.FLOAT_DTYPES)
116+
@pytest.mark.parametrize("dim", HADAMARD_DIMS)
117+
def test_hadamard_transform(dim, dtype):
118+
"""Dao-style accuracy test vs scipy Hadamard matrix multiply (fp32 ground truth)."""
119+
if scipy_hadamard is None:
120+
pytest.skip("scipy is required for hadamard_transform_ref")
121+
122+
atol = 3e-3 if dtype == torch.float32 else 5e-3
123+
if dtype == torch.bfloat16:
124+
atol = 5e-2
125+
126+
torch.random.manual_seed(0)
127+
batch_size = 15
128+
device = flag_gems.device
129+
x = torch.randn(batch_size, dim, device=device, dtype=dtype).requires_grad_()
130+
x_ref = x.detach().clone().requires_grad_()
131+
x_pt = x.detach().clone().requires_grad_()
132+
scale = 1 / math.sqrt(dim)
133+
134+
out = flag_gems.hadamard_transform(x, scale=scale)
135+
out_ref = _hadamard_transform_ref(x_ref.float(), scale=scale)
136+
out_pt = _hadamard_transform_ref(x_pt, scale=scale)
137+
138+
out_err = (out.float() - out_ref).abs().max().item()
139+
pt_err = (out_pt.float() - out_ref).abs().max().item()
140+
assert (
141+
out_err < 2 * pt_err + atol
142+
), f"forward dim={dim} dtype={dtype}: out_err={out_err}, pt_err={pt_err}, atol={atol}"
143+
144+
g = torch.randn_like(out)
145+
out.backward(g)
146+
out_ref.backward(g)
147+
out_pt.backward(g)
148+
149+
dx_err = (x.grad.float() - x_ref.grad.float()).abs().max().item()
150+
dx_pt_err = (x_pt.grad.float() - x_ref.grad.float()).abs().max().item()
151+
assert (
152+
dx_err < 2 * dx_pt_err + atol
153+
), f"backward dim={dim} dtype={dtype}: dx_err={dx_err}, dx_pt_err={dx_pt_err}, atol={atol}"
154+
155+
60156
def _ref_mn(x: torch.Tensor, M: int) -> torch.Tensor:
61157
"""Reference: 2-kernel version (H_M column transform in fp32 + standard FHT)."""
62158
*leading, dim = x.shape
@@ -93,7 +189,10 @@ def _ref_mn(x: torch.Tensor, M: int) -> torch.Tensor:
93189

94190
ym = torch.stack(rows, dim=1).reshape(batch * M, n_cols) # keep fp32
95191
ym = flag_gems.hadamard_transform(ym) # FHT in fp32
96-
return ym.to(orig_dtype).reshape(*leading, dim)
192+
out = ym.to(orig_dtype).reshape(*leading, dim)
193+
if cfg.TO_CPU:
194+
out = out.cpu()
195+
return out
97196

98197

99198
@pytest.mark.hadamard_transform_mn

0 commit comments

Comments
 (0)