From 036ef41706ac77fa780c60c65a94888cadb12b24 Mon Sep 17 00:00:00 2001 From: Julian Ng-Thow-Hing Date: Fri, 10 Jul 2026 11:24:23 -0700 Subject: [PATCH] Update [ghstack-poisoned] --- backends/webgpu/test/op_tests/cases.py | 35 +++++++++++++++ backends/webgpu/test/ops/test_gelu.py | 59 ++++++++++++++++++++++++++ 2 files changed, 94 insertions(+) create mode 100644 backends/webgpu/test/ops/test_gelu.py diff --git a/backends/webgpu/test/op_tests/cases.py b/backends/webgpu/test/op_tests/cases.py index 1b6b0fa7085..cbd13097c2a 100644 --- a/backends/webgpu/test/op_tests/cases.py +++ b/backends/webgpu/test/op_tests/cases.py @@ -264,3 +264,38 @@ def _cat_suite() -> WebGPUTestSuite: ], golden_dtype="float32", # concatenation copies values; fp64 bit-identical ) +from executorch.backends.webgpu.test.ops.test_gelu import ( + _det_input as _gelu_det_input, + GeluModule, + N as _GELU_N, +) + + +def _gelu_full_range(_shape) -> torch.Tensor: + # Reuse the deterministic linspace(-6, 6) spanning negatives/zero/positives. + return _gelu_det_input() + + +@register_op_test("gelu") +def _gelu_suite() -> WebGPUTestSuite: + # erf ("none") is the Florence-2/BART + PyTorch default; tanh is the approx. + return WebGPUTestSuite( + module_factory=lambda approximate: GeluModule(approximate), + cases=[ + Case(name="erf_vec", construct={"approximate": "none"}, inputs=((M1,),)), + Case(name="erf_mat", construct={"approximate": "none"}, inputs=((M1, M2),)), + Case( + name="erf_rank3", + construct={"approximate": "none"}, + inputs=((S1, M1, M2),), + ), + Case(name="tanh_mat", construct={"approximate": "tanh"}, inputs=((M1, M2),)), + Case( + name="erf_range", + construct={"approximate": "none"}, + inputs=(InputSpec(shape=(_GELU_N,), gen=_gelu_full_range),), + ), + ], + atol=1e-4, + rtol=1e-3, + ) diff --git a/backends/webgpu/test/ops/test_gelu.py b/backends/webgpu/test/ops/test_gelu.py new file mode 100644 index 00000000000..5fb6d73fb5d --- /dev/null +++ b/backends/webgpu/test/ops/test_gelu.py @@ -0,0 +1,59 @@ +# 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.gelu.default` module + input for the WebGPU op-test framework. + +`GeluModule`, `N`, and `_det_input` are imported by `cases.py` to drive the +declarative op-test suite. `GeluTest` is the export-delegation smoke test. The +`approximate` kwarg selects the exact (erf) path ("none", PyTorch's default and +the Florence-2/BART path) or the tanh approximation; the deterministic input +spans negatives, zero, and the saturation region. +""" + +import unittest + +import torch + +from executorch.backends.vulkan.partitioner.vulkan_partitioner import VulkanPartitioner +from executorch.exir import to_edge_transform_and_lower + +# Input length; the deterministic input spans negatives, zero, and positives. +N = 64 + + +class GeluModule(torch.nn.Module): + def __init__(self, approximate: str = "none") -> None: + super().__init__() + self.approximate = approximate + + def forward(self, x: torch.Tensor) -> torch.Tensor: + return torch.nn.functional.gelu(x, approximate=self.approximate) + + +def _det_input() -> torch.Tensor: + """Deterministic fp32 input spanning negatives, zero, and positives.""" + return torch.linspace(-6.0, 6.0, N, dtype=torch.float32) + + +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() + + +class GeluTest(unittest.TestCase): + def test_export_delegates(self) -> None: + for approximate in ("none", "tanh"): + et = _export(GeluModule(approximate).eval(), _det_input()) + found = any( + d.id == "VulkanBackend" + for plan in et.executorch_program.execution_plan + for d in plan.delegates + ) + self.assertTrue( + found, f"Expected a VulkanBackend delegate (gelu {approximate})" + )