|
| 1 | +# Copyright (c) Meta Platforms, Inc. and affiliates. |
| 2 | +# All rights reserved. |
| 3 | +# |
| 4 | +# This source code is licensed under the BSD-style license found in the |
| 5 | +# LICENSE file in the root directory of this source tree. |
| 6 | + |
| 7 | +"""WebGPU op-test cases. |
| 8 | +
|
| 9 | +Declarative per-op suites for the manifest-driven op-test framework, mirroring the |
| 10 | +Vulkan op-test authoring ergonomics. Each op reuses its `nn.Module` + generators from |
| 11 | +the per-op `test_*.py`; new ops append a `@register_op_test` entry in their own tests-diff. |
| 12 | +""" |
| 13 | + |
| 14 | +import torch |
| 15 | + |
| 16 | +from executorch.backends.webgpu.test.op_tests.test_suite import ( |
| 17 | + Case, |
| 18 | + InputSpec, |
| 19 | + M1, |
| 20 | + M2, |
| 21 | + register_op_test, |
| 22 | + S, |
| 23 | + S1, |
| 24 | + S2, |
| 25 | + WebGPUTestSuite, |
| 26 | + XS, |
| 27 | +) |
| 28 | +from executorch.backends.webgpu.test.ops.add.test_add import ( |
| 29 | + AddChainedModule, |
| 30 | + AddModule, |
| 31 | + AddSelfModule, |
| 32 | +) |
| 33 | +from executorch.backends.webgpu.test.ops.rms_norm.test_rms_norm import ( |
| 34 | + _CASES, |
| 35 | + _linspace_weight, |
| 36 | + _ramp, |
| 37 | + RmsNormModule, |
| 38 | +) |
| 39 | + |
| 40 | +# rms_norm coverage is exactly the 14 cases the native test covered. |
| 41 | +RMS_NORM_CASES = _CASES |
| 42 | + |
| 43 | + |
| 44 | +def _add_factory(variant: str = "regular") -> torch.nn.Module: |
| 45 | + return { |
| 46 | + "regular": AddModule, |
| 47 | + "self": AddSelfModule, |
| 48 | + "chained": AddChainedModule, |
| 49 | + }[variant]() |
| 50 | + |
| 51 | + |
| 52 | +@register_op_test("add") |
| 53 | +def _add_suite() -> WebGPUTestSuite: |
| 54 | + # Same-shape numeric coverage only: broadcast adds stay export-smoke in |
| 55 | + # ops/add/test_add.py because the kernel can't broadcast. |
| 56 | + return WebGPUTestSuite( |
| 57 | + module_factory=_add_factory, |
| 58 | + cases=[ |
| 59 | + Case( |
| 60 | + name="regular_2d", |
| 61 | + construct={"variant": "regular"}, |
| 62 | + inputs=((M1, M2), (M1, M2)), |
| 63 | + ), |
| 64 | + Case( |
| 65 | + name="regular_3d", |
| 66 | + construct={"variant": "regular"}, |
| 67 | + inputs=((S, S1, S2), (S, S1, S2)), |
| 68 | + ), |
| 69 | + Case( |
| 70 | + name="regular_4d", |
| 71 | + construct={"variant": "regular"}, |
| 72 | + inputs=((XS, S, S1, S2), (XS, S, S1, S2)), |
| 73 | + ), |
| 74 | + Case(name="self", construct={"variant": "self"}, inputs=((M1, M2),)), |
| 75 | + # "scalar" (x+3.0) is intentionally OMITTED — the WebGPU add kernel can't |
| 76 | + # do scalar/broadcast adds (0x30 at runtime); it stays export-smoke. |
| 77 | + Case( |
| 78 | + name="chained", |
| 79 | + construct={"variant": "chained"}, |
| 80 | + inputs=((M1, M2), (M1, M2)), |
| 81 | + ), |
| 82 | + ], |
| 83 | + ) |
| 84 | + |
| 85 | + |
| 86 | +def _rms_norm_factory(hidden: int, eps: float, weight_fn) -> torch.nn.Module: |
| 87 | + model = RmsNormModule(hidden, eps=eps) |
| 88 | + with torch.no_grad(): |
| 89 | + model.weight.copy_(weight_fn(hidden)) |
| 90 | + return model |
| 91 | + |
| 92 | + |
| 93 | +@register_op_test("rms_norm") |
| 94 | +def _rms_norm_suite() -> WebGPUTestSuite: |
| 95 | + cases = [] |
| 96 | + for c in RMS_NORM_CASES: |
| 97 | + shape = c["shape"] |
| 98 | + hidden = shape[-1] |
| 99 | + weight_fn = c.get("weight_fn", _linspace_weight) |
| 100 | + input_fn = c.get("input_fn", _ramp) |
| 101 | + cases.append( |
| 102 | + Case( |
| 103 | + name=c["name"], |
| 104 | + construct={"hidden": hidden, "eps": 1e-6, "weight_fn": weight_fn}, |
| 105 | + inputs=(InputSpec(shape=shape, gen=input_fn),), |
| 106 | + ) |
| 107 | + ) |
| 108 | + return WebGPUTestSuite(module_factory=_rms_norm_factory, cases=cases) |
0 commit comments