44# This source code is licensed under the BSD-style license found in the
55# LICENSE file in the root directory of this source tree.
66
7- # pyre-strict
8-
97import copy
108from typing import Any , Callable , Dict , final , List , Optional , Tuple
119
1816 OpKey ,
1917 vulkan_supported_ops ,
2018)
21- from executorch .backends .vulkan .partitioner .vulkan_partitioner import VulkanPartitioner
19+ from executorch .backends .vulkan .partitioner .vulkan_partitioner import (
20+ VulkanPartitioner ,
21+ VulkanSupportedOperators ,
22+ )
2223from executorch .exir .backend .partitioner import Partitioner , PartitionResult
2324from executorch .exir .dialects ._ops import ops as exir_ops
2425from torch .export import ExportedProgram
@@ -54,8 +55,17 @@ def _layer_norm_check(
5455 return all (d == 1 for d in normalized_shape [:- 1 ]) and vulkan_check (node )
5556
5657
57- # Ops whose WebGPU kernels cover less than the Vulkan registry advertises, so the
58- # unsupported forms stay on CPU instead of failing at delegate init.
58+ def _fp32_unary_features () -> OpFeatures :
59+ return OpFeatures (
60+ inputs_storage = vk_utils .ANY_STORAGE ,
61+ inputs_dtypes = {torch .float32 },
62+ supports_resize = True ,
63+ )
64+
65+
66+ # Ops whose WebGPU kernels cover less (or more) than the Vulkan registry
67+ # advertises, so unsupported forms stay on CPU instead of failing at delegate
68+ # init, and WebGPU-only kernels are delegated.
5969_WEBGPU_OP_FEATURES : Dict [OpKey , OpFeatures ] = {
6070 # fp32 and int32 kernels only; int64 is downcast to int32 on device.
6171 exir_ops .edge .aten .arange .start_step : OpFeatures (
@@ -66,12 +76,16 @@ def _layer_norm_check(
6676 exir_ops .edge .aten .native_layer_norm .default : _webgpu_features (
6777 exir_ops .edge .aten .native_layer_norm .default , _layer_norm_check
6878 ),
79+ exir_ops .edge .aten .elu .default : _fp32_unary_features (),
80+ exir_ops .edge .aten .silu .default : _fp32_unary_features (),
6981}
7082
7183
7284# Vulkan fusion patterns whose fused ops the WebGPU runtime does not implement.
7385_WEBGPU_SKIP_FUSION_PATTERNS = {"conv2d_with_epilogue" }
7486
87+ _MUL_TARGETS = (torch .ops .aten .mul .Tensor , exir_ops .edge .aten .mul .Tensor )
88+
7589
7690@final
7791class WebGPUPartitioner (Partitioner ):
@@ -102,7 +116,59 @@ def __init__(
102116 def ops_to_not_decompose (
103117 self , ep : ExportedProgram
104118 ) -> Tuple [List [torch ._ops .OpOverload ], Optional [Callable [[torch .fx .Node ], bool ]]]:
105- return self ._vulkan_partitioner .ops_to_not_decompose (ep )
119+ ops , vulkan_filter = self ._vulkan_partitioner .ops_to_not_decompose (ep )
120+ silu_support = self ._silu_support ()
121+
122+ def filter_fn (node : torch .fx .Node ) -> bool :
123+ if node .target in (
124+ torch .ops .aten .silu .default ,
125+ exir_ops .edge .aten .silu .default ,
126+ ):
127+ return self ._keeps_silu (node , ep , silu_support )
128+ return vulkan_filter is None or vulkan_filter (node )
129+
130+ return ops + [torch .ops .aten .silu .default ], filter_fn
131+
132+ def _silu_support (self ) -> VulkanSupportedOperators :
133+ """The partitioner's own node support check, also accepting the aten
134+ silu that ops_to_not_decompose sees before the edge conversion."""
135+ vulkan = self ._vulkan_partitioner
136+ aten_silu = torch .ops .aten .silu .default
137+ edge_silu = exir_ops .edge .aten .silu .default
138+ allowlist = vulkan .operator_allowlist
139+ if allowlist is not None and edge_silu in allowlist :
140+ allowlist = allowlist | {aten_silu }
141+ blocklist = vulkan .operator_blocklist
142+ if edge_silu in blocklist :
143+ blocklist = blocklist | {aten_silu }
144+ return vulkan .make_supported_operators (
145+ op_features_overrides = {
146+ ** _WEBGPU_OP_FEATURES ,
147+ aten_silu : _WEBGPU_OP_FEATURES [edge_silu ],
148+ },
149+ operator_allowlist = allowlist ,
150+ operator_blocklist = blocklist ,
151+ )
152+
153+ def _keeps_silu (
154+ self ,
155+ node : torch .fx .Node ,
156+ ep : ExportedProgram ,
157+ silu_support : VulkanSupportedOperators ,
158+ ) -> bool :
159+ """Whether `node` stays an aten.silu for the WebGPU silu kernel.
160+
161+ A kept op that is not delegated fails lowering, so keep it only where
162+ the partitioner will delegate it: in the top-level graph (control-flow
163+ submodules are not partitioned) and passing the partitioner's support
164+ check. Where it feeds a mul it decomposes to x * sigmoid(x), as before,
165+ so the SwiGLU fusion still matches.
166+ """
167+ return (
168+ node .graph is ep .graph_module .graph
169+ and not any (user .target in _MUL_TARGETS for user in node .users )
170+ and silu_support .is_node_supported ({}, node )
171+ )
106172
107173 def partition (self , exported_program : ExportedProgram ) -> PartitionResult :
108174 return self ._vulkan_partitioner .partition (exported_program )
0 commit comments