forked from tile-ai/TileOPs
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtest_base.py
More file actions
132 lines (103 loc) · 4.33 KB
/
Copy pathtest_base.py
File metadata and controls
132 lines (103 loc) · 4.33 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
import logging
import threading
from abc import abstractmethod
from functools import partial
from typing import Any
import torch
from workloads.workload_base import FixtureBase, FixtureMeta, WorkloadBase
_logger = logging.getLogger("tileops.ops")
# Thread-local storage for conftest hook to pick up per-test Op info.
_check_result = threading.local()
# Re-export for backward compatibility with tests that import from here.
__all__ = [
"FixtureBase",
"FixtureMeta",
"TestBase",
"allclose_compare",
"exact_compare",
]
def _to_tuple(outputs):
"""Normalize outputs to a tuple."""
if isinstance(outputs, torch.Tensor):
return (outputs,)
if isinstance(outputs, list):
return tuple(outputs)
if isinstance(outputs, tuple):
return outputs
raise ValueError(f"Unsupported output type: {type(outputs)}")
def allclose_compare(output: torch.Tensor, output_ref: torch.Tensor, atol: float = 1e-8, rtol: float = 1e-5) -> None:
"""Default comparison using torch.allclose."""
output, output_ref = torch.broadcast_tensors(output, output_ref)
torch.testing.assert_close(
output,
output_ref,
atol=atol,
rtol=rtol,
equal_nan=True,
)
def exact_compare(output: torch.Tensor, output_ref: torch.Tensor) -> None:
"""Exact comparison for bool/int outputs."""
assert torch.equal(output, output_ref), "output does not exactly match reference"
class TestBase(WorkloadBase):
"""Abstract base class for op correctness testing.
Inherits gen_inputs() from WorkloadBase.
Subclasses must implement ref_program() for correctness checking.
Provides check() for comparing op output against reference.
"""
__test__ = False
@abstractmethod
def ref_program(self, *inputs: Any) -> Any:
"""Reference implementation for correctness checking.
Must be overridden by every concrete test subclass.
Should return the same outputs as the op under test, using a
simpler/trusted implementation (e.g. PyTorch built-ins).
"""
raise NotImplementedError
def check(self,
op,
*inputs: torch.Tensor,
compare=None,
atol: float = 1e-08,
rtol: float = 1e-05) -> None:
"""Check the correctness of the op against ref_program.
Args:
op: The operator to test.
*inputs: Input tensors.
compare: Custom comparison callable(output, output_ref) or list of
callables (one per output). Defaults to allclose_compare.
atol: Absolute tolerance for default allclose_compare.
rtol: Relative tolerance for default allclose_compare.
"""
if compare is None:
compare = partial(allclose_compare, atol=atol, rtol=rtol)
op_name = op.__class__.__name__
op_module = op.__class__.__module__
try:
outputs_ref = self.ref_program(*inputs)
except RuntimeError as e:
if "out of memory" in str(e):
_logger.warning("op=%s module=%s status=skip_oom", op_name, op_module)
return
raise e
outputs_ref = _to_tuple(outputs_ref)
with torch.no_grad():
outputs = op(*inputs)
outputs = _to_tuple(outputs)
assert len(outputs) == len(outputs_ref), \
f"outputs: {len(outputs)} and outputs_ref: {len(outputs_ref)} have different size"
# Compute error metrics before comparison (so we report even on failure).
max_abs_err = 0.0
for output, output_ref in zip(outputs, outputs_ref, strict=True):
if output_ref is not None:
err = (output.float() - output_ref.float()).abs().max().item()
max_abs_err = max(max_abs_err, err)
# Store for conftest hook to attach to pytest report.
_check_result.op_name = op_name
_check_result.op_module = op_module
_check_result.max_abs_err = max_abs_err
comparators = [compare] * len(outputs) if callable(compare) else list(compare)
for output, output_ref, cmp in zip(outputs, outputs_ref, comparators, strict=True):
if output_ref is not None:
cmp(output, output_ref)
_logger.info("op=%s module=%s status=pass max_abs_err=%.2e",
op_name, op_module, max_abs_err)