-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Expand file tree
/
Copy pathtest_compare.py
More file actions
109 lines (87 loc) · 3.41 KB
/
Copy pathtest_compare.py
File metadata and controls
109 lines (87 loc) · 3.41 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
# Copyright (c) Meta Platforms, Inc. and affiliates.
# All rights reserved.
#
# This source code is licensed under the BSD-style license found in the
# LICENSE file in the root directory of this source tree.
"""`aten.{eq,ne,le,ge,lt,gt}.Scalar` export + golden for the WebGPU backend.
Each scalar comparison lowers to a single `aten.<op>.Scalar` node that the
kernel computes as `cmp(self[i], scalar)` and writes as a byte-packed bool. The
delegation test locks that every variant partitions to `VulkanBackend`; the
golden test locks the fp32 module output against the fp64 torch truth. The
deterministic ramp is exact in fp32 and straddles (and hits) the scalar, so both
precisions agree bit-for-bit and every op has mixed True/False cases; a
non-multiple-of-4 numel exercises the kernel tail (4 elems per u32 word).
"""
from __future__ import annotations
import unittest
import torch
from executorch.backends.vulkan.partitioner.vulkan_partitioner import VulkanPartitioner
from executorch.exir import to_edge_transform_and_lower
SCALAR = 0.0
OPS = ("eq", "ne", "le", "ge", "lt", "gt")
SHAPES = {"tail": (3, 5), "3d": (2, 4, 8)}
_TORCH_OP = {
"eq": torch.eq,
"ne": torch.ne,
"le": torch.le,
"ge": torch.ge,
"lt": torch.lt,
"gt": torch.gt,
}
class CompareModule(torch.nn.Module):
def __init__(self, op: str, scalar: float) -> None:
super().__init__()
self.op = op
self.scalar = scalar
def forward(self, x: torch.Tensor) -> torch.Tensor:
if self.op == "eq":
return x == self.scalar
if self.op == "ne":
return x != self.scalar
if self.op == "le":
return x <= self.scalar
if self.op == "ge":
return x >= self.scalar
if self.op == "lt":
return x < self.scalar
return x > self.scalar
def _det_input(shape: tuple[int, ...]) -> torch.Tensor:
"""Deterministic fp32 spanning [-0.5, 0.5] in 1/16 steps (exact in fp32,
hits and straddles 0.0)."""
n = 1
for d in shape:
n *= d
flat = torch.arange(n, dtype=torch.float32)
return (((flat % 17) - 8) / 16.0).reshape(shape)
def _export(m: torch.nn.Module, x: torch.Tensor):
ep = torch.export.export(m, (x,))
return to_edge_transform_and_lower(
ep, partitioner=[VulkanPartitioner()]
).to_executorch()
def _delegated(et) -> bool:
return any(
d.id == "VulkanBackend"
for plan in et.executorch_program.execution_plan
for d in plan.delegates
)
class TestCompare(unittest.TestCase):
def test_export_delegates(self) -> None:
for op in OPS:
for name, shape in SHAPES.items():
with self.subTest(op=op, shape=name):
x = _det_input(shape)
et = _export(CompareModule(op, SCALAR).eval(), x)
self.assertTrue(
_delegated(et),
f"Expected a VulkanBackend delegate ({op}.Scalar {name})",
)
def test_module_matches_fp64_golden(self) -> None:
for op in OPS:
for name, shape in SHAPES.items():
with self.subTest(op=op, shape=name):
x = _det_input(shape)
got = CompareModule(op, SCALAR)(x)
golden = _TORCH_OP[op](x.double(), float(SCALAR))
torch.testing.assert_close(got, golden)
if __name__ == "__main__":
unittest.main()