Xenova HF Staff commited on
Commit
7cf85fb
·
verified ·
1 Parent(s): a31d703

sync 2e7068faf55e

Browse files
README.md CHANGED
@@ -1,3 +1,68 @@
1
  ---
 
2
  license: apache-2.0
 
 
 
 
3
  ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ library_name: kernels
3
  license: apache-2.0
4
+ tags:
5
+ - kernel
6
+ - webgpu
7
+ - wgsl
8
  ---
9
+ # ai.onnx.Elu
10
+
11
+ `ai.onnx` · standard ONNX operator · ONNX opset ≥ 6
12
+
13
+ ## Description
14
+
15
+ Applies the Exponential Linear Unit activation elementwise: `f(x) = x` for `x >= 0`, and `f(x) = alpha * (exp(x) - 1)` for `x < 0`. The `alpha` coefficient controls the saturation value for negative inputs.
16
+
17
+ See the [ONNX `Elu` spec](https://onnx.ai/onnx/operators/onnx__Elu.html) for the reference semantics.
18
+
19
+ ## Inputs
20
+
21
+ | Name | Bind key | Logical dtype | Rank | Shape | Description | Presence |
22
+ | --- | --- | --- | --- | --- | --- | --- |
23
+ | `X` | `x` | `T` | — | — | Values transformed elementwise by the ELU activation. | required |
24
+
25
+ ## Outputs
26
+
27
+ | Name | Bind key | Logical dtype | Rank | Shape | Description | Presence |
28
+ | --- | --- | --- | --- | --- | --- | --- |
29
+ | `Y` | `y` | `T` | same as `X` | same as `X` | Output tensor with ELU applied elementwise; same shape as X. | required |
30
+
31
+ ## Attributes
32
+
33
+ Default values (overridable per request):
34
+
35
+ | Attribute | Default | Description |
36
+ | --- | --- | --- |
37
+ | `alpha` | `1` | Scaling coefficient applied to the exponential term for negative inputs; defaults to `1.0`. |
38
+
39
+ ## Type constraints
40
+
41
+ | Variable | Allowed dtypes |
42
+ | --- | --- |
43
+ | `T` | `float32`, `float16` |
44
+
45
+ ## Files
46
+
47
+ - [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, provenance)
48
+ - [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
49
+ - [`test.json`](build/webgpu/test.json) — correctness cases
50
+ - [`bench.json`](build/webgpu/bench.json) — benchmark + tuning cases
51
+ - [`unary-scalar.wgsl.jinja`](build/webgpu/unary-scalar.wgsl.jinja)
52
+ - [`unary-vec4.wgsl.jinja`](build/webgpu/unary-vec4.wgsl.jinja)
53
+
54
+ ## Use with `@huggingface/kernels`
55
+
56
+ The loader derives every required output's shape and logical dtype from the manifest contract and this call.
57
+ It then allocates the result tensors automatically.
58
+
59
+ The `version: 1` option selects the published kernel contract; it is independent of any operator opset, contrib `since_version`, or model version.
60
+
61
+ Replace each `*Data` placeholder with a typed array containing the corresponding input data.
62
+
63
+ ```js
64
+ import { getKernel } from "@huggingface/kernels";
65
+
66
+ const kernel = await getKernel("webgpu-kernels/ai.onnx.Elu", { version: 1 });
67
+ const { y } = await kernel({ x: { data: xData, shape: [3, 4, 5] } });
68
+ ```
build/webgpu/bench.json ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "op": "ai.onnx.Elu",
3
+ "cases": [
4
+ {
5
+ "name": "1m_f32",
6
+ "inputs": { "x": { "dtype": "float32", "shape": [1048576] } },
7
+ "outputs": { "y": { "dtype": "float32", "shape": [1048576] } }
8
+ },
9
+ {
10
+ "name": "elu-f32-1m",
11
+ "preset": "smoke",
12
+ "vars": { "dtype": "float32", "count": 1048576 },
13
+ "inputs": {
14
+ "x": { "shape": [1048576], "dtype": "float32", "dist": "uniform", "seed": 5101, "scale": 4, "offset": -2 }
15
+ },
16
+ "outputs": { "y": { "shape": [1048576], "dtype": "float32" } },
17
+ "bench": {
18
+ "primary": true,
19
+ "metrics": [{ "type": "bandwidth", "value": "args.count * dtypeBytes(args.dtype) * 2" }]
20
+ }
21
+ },
22
+ {
23
+ "name": "elu-f32-1m-scalar-fallback",
24
+ "preset": "smoke",
25
+ "vars": { "dtype": "float32", "count": 1048577 },
26
+ "inputs": {
27
+ "x": { "shape": [1048577], "dtype": "float32", "dist": "uniform", "seed": 5102, "scale": 4, "offset": -2 }
28
+ },
29
+ "outputs": { "y": { "shape": [1048577], "dtype": "float32" } },
30
+ "bench": { "metrics": [{ "type": "bandwidth", "value": "args.count * dtypeBytes(args.dtype) * 2" }] }
31
+ },
32
+ {
33
+ "name": "elu-f16-1m",
34
+ "preset": "smoke",
35
+ "vars": { "dtype": "float16", "count": 1048576 },
36
+ "inputs": {
37
+ "x": { "shape": [1048576], "dtype": "float16", "dist": "uniform", "seed": 5103, "scale": 4, "offset": -2 }
38
+ },
39
+ "outputs": { "y": { "shape": [1048576], "dtype": "float16" } },
40
+ "bench": { "metrics": [{ "type": "bandwidth", "value": "args.count * dtypeBytes(args.dtype) * 2" }] }
41
+ }
42
+ ]
43
+ }
build/webgpu/manifest.json ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "domain": "ai.onnx",
3
+ "name": "Elu",
4
+ "sinceVersion": 6,
5
+ "description": "Applies the Exponential Linear Unit activation elementwise: `f(x) = x` for `x >= 0`, and `f(x) = alpha * (exp(x) - 1)` for `x < 0`. The `alpha` coefficient controls the saturation value for negative inputs.",
6
+ "inputs": [{ "role": "X", "dtype": "T", "description": "Values transformed elementwise by the ELU activation." }],
7
+ "outputs": [
8
+ {
9
+ "role": "Y",
10
+ "dtype": "T",
11
+ "rank": "ranks.X",
12
+ "description": "Output tensor with ELU applied elementwise; same shape as X.",
13
+ "shape": "shapes.X"
14
+ }
15
+ ],
16
+ "attributes": { "alpha": 1 },
17
+ "attributeDescriptions": {
18
+ "alpha": "Scaling coefficient applied to the exponential term for negative inputs; defaults to `1.0`."
19
+ },
20
+ "typeConstraints": { "T": ["float32", "float16"] },
21
+ "args": {
22
+ "x": { "kind": "tensor", "semantic": "X", "role": "input" },
23
+ "y": { "kind": "tensor", "semantic": "Y", "role": "output" }
24
+ },
25
+ "tunables": { "WORKGROUP_SIZE": 256 },
26
+ "constants": { "scalar": "dtypes.T", "usesF16": "dtypes.T == \"f16\"" },
27
+ "variants": [
28
+ {
29
+ "id": "same_layout_vec4",
30
+ "when": ["numel(shapes.X) > 0", "numel(shapes.X) % 4 == 0", "numel(shapes.X) == numel(shapes.Y)", "f16Ok(dtypes.T)"],
31
+ "constants": { "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"" },
32
+ "passes": [
33
+ {
34
+ "id": "main",
35
+ "name": "Elu.vec4",
36
+ "source": { "shader": "unary-vec4.wgsl.jinja", "inputs": { "op": "\"elu\"", "alpha": "attrs.alpha" } },
37
+ "bindings": [
38
+ { "name": "x", "arg": "x", "buffer": { "type": "read-only-storage" }, "elementType": "$vectorScalar" },
39
+ { "name": "y", "arg": "y", "buffer": { "type": "storage" }, "elementType": "$vectorScalar" },
40
+ {
41
+ "name": "params",
42
+ "semantic": "kernel.params",
43
+ "buffer": { "type": "uniform" },
44
+ "struct": {
45
+ "name": "Params",
46
+ "fields": [{ "name": "count", "type": "u32", "value": "numel(shapes.y) / 4" }]
47
+ }
48
+ }
49
+ ],
50
+ "dispatch": { "threads": "numel(shapes.Y) / 4", "workgroupSize": "tunables.WORKGROUP_SIZE" }
51
+ }
52
+ ],
53
+ "priority": 20
54
+ },
55
+ {
56
+ "id": "elementwise",
57
+ "when": "numel(shapes.X) == numel(shapes.Y) and (f16Ok(dtypes.T))",
58
+ "passes": [
59
+ {
60
+ "id": "main",
61
+ "name": "Elu",
62
+ "source": { "shader": "unary-scalar.wgsl.jinja", "inputs": { "op": "\"elu\"", "itemsPerInvocation": 4 } },
63
+ "bindings": [
64
+ { "name": "x", "arg": "x", "buffer": { "type": "read-only-storage" }, "elementType": "$scalar" },
65
+ { "name": "y", "arg": "y", "buffer": { "type": "storage" }, "elementType": "$scalar" },
66
+ {
67
+ "name": "params",
68
+ "semantic": "kernel.params",
69
+ "buffer": { "type": "uniform" },
70
+ "struct": {
71
+ "name": "Params",
72
+ "fields": [
73
+ { "name": "count", "type": "u32", "value": "numel(shapes.y)" },
74
+ { "name": "alpha", "type": "f32", "value": "attrs.alpha" }
75
+ ]
76
+ }
77
+ }
78
+ ],
79
+ "dispatch": { "threads": "ceilDiv(numel(shapes.Y), 4)", "workgroupSize": "tunables.WORKGROUP_SIZE" }
80
+ }
81
+ ]
82
+ }
83
+ ]
84
+ }
build/webgpu/metadata.json ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "ai.onnx.Elu",
3
+ "id": "_ai_onnx_elu_webgpu_e27eb6b",
4
+ "version": 1,
5
+ "license": "Apache-2.0",
6
+ "backend": { "type": "webgpu" },
7
+ "digest": {
8
+ "algorithm": "sha256",
9
+ "files": {
10
+ "bench.json": "Fk685NfBNB1Zgf3q/ubBERo+pUczrEG/VeM9wSAtSok=",
11
+ "manifest.json": "JjDQbSvDifYG9piBDzIk8VmUoutDLm+HypEnzQnbcFE=",
12
+ "test.json": "PyjZAriKI7qWc4jh94V9Fm13hBYUuT9eK5Xlqeu9/24=",
13
+ "unary-scalar.wgsl.jinja": "jIfgc/qPA0E/qtbjHrzlkPXrKcr5NTj6MdZCGS4hk1g=",
14
+ "unary-vec4.wgsl.jinja": "NsfNFarG5uL16oqVeYJMOzArQ8At8c6otRbQZ697x6g="
15
+ }
16
+ },
17
+ "provenance": { "kernel": { "sha": "2e7068faf55e7f43df740015f6d1ee49391a41c5", "dirty": false } },
18
+ "webgpu": { "manifestSpec": "1.0", "specialized": true, "opPath": "ops/ai.onnx.Elu" }
19
+ }
build/webgpu/test.json ADDED
@@ -0,0 +1,187 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "op": "ai.onnx.Elu",
3
+ "fixtureArrays": {
4
+ "onnx_backend_input_x": [1.764052391052246, 0.40015721321105957, 0.978738009929657, 2.2408931255340576, 1.8675580024719238, -0.9772778749465942, 0.9500884413719177, -0.15135720372200012, -0.10321885347366333, 0.4105985164642334, 0.14404356479644775, 1.4542734622955322, 0.7610377073287964, 0.12167501449584961, 0.44386324286460876, 0.3336743414402008, 1.4940791130065918, -0.2051582634449005, 0.3130677044391632, -0.8540957570075989, -2.5529897212982178, 0.653618574142456, 0.8644362092018127, -0.7421650290489197, 2.269754648208618, -1.4543657302856445, 0.04575851559638977, -0.18718385696411133, 1.5327792167663574, 1.4693588018417358, 0.154947429895401, 0.37816253304481506, -0.8877857327461243, -1.980796456336975, -0.34791216254234314, 0.15634897351264954, 1.2302906513214111, 1.202379822731018, -0.38732680678367615, -0.302302747964859, -1.0485529899597168, -1.420017957687378, -1.7062702178955078, 1.950775384902954, -0.5096521973609924, -0.4380742907524109, -1.2527953386306763, 0.7774903774261475, -1.6138978004455566, -0.21274028718471527, -0.8954665660858154, 0.38690251111984253, -0.5108051300048828, -1.18063223361969, -0.02818222902715206, 0.4283318817615509, 0.06651721894741058, 0.30247190594673157, -0.6343221068382263, -0.3627411723136902]
5
+ },
6
+ "cases": [
7
+ {
8
+ "name": "f32_values",
9
+ "attrs": { "alpha": 0.5 },
10
+ "inputs": {
11
+ "x": {
12
+ "dtype": "float32",
13
+ "shape": [6],
14
+ "data": { "kind": "values", "values": [-3.0, -1.0, 0.0, 0.5, 1.0, 3.0] }
15
+ }
16
+ },
17
+ "outputs": { "y": { "dtype": "float32", "shape": [6], "tolerance": 0.000001 } }
18
+ },
19
+ {
20
+ "name": "f32_non_finite_and_saturation_edges",
21
+ "attrs": { "alpha": 0.5 },
22
+ "inputs": {
23
+ "x": {
24
+ "dtype": "float32",
25
+ "shape": [7],
26
+ "data": { "kind": "values", "values": ["-Infinity", -1000.0, 0.0, 0.0, 1000.0, "Infinity", "NaN"] }
27
+ }
28
+ },
29
+ "outputs": { "y": { "dtype": "float32", "shape": [7], "tolerance": 0.000001, "allowNaN": true } }
30
+ },
31
+ {
32
+ "name": "f32_negative_subnormal_expm1_tail_gpu_gap",
33
+ "skipGpu": {
34
+ "category": "permanent",
35
+ "reason": "Portable WGSL floating-point semantics do not guarantee preservation of the subnormal values required by this fixture. Backend evidence: WebGPU/Metal flushes subnormals to zero (f32 and f16); the kernel cannot preserve denormal inputs/outputs bit-exactly."
36
+ },
37
+ "provenance": {
38
+ "source": "onnxruntime/test/providers/cpu/activation/activation_op_test.cc",
39
+ "test": "ActivationOpTest.Elu",
40
+ "notes": "ORT CPU rounds alpha * (exp(x) - 1) to zero for tiny negative inputs; current WebGPU leaks the original subnormal through."
41
+ },
42
+ "attrs": { "alpha": 0.5 },
43
+ "inputs": {
44
+ "x": {
45
+ "dtype": "float32",
46
+ "shape": [4],
47
+ "data": { "kind": "values", "values": [-1e-39, -1e-40, 1e-40, 1e-39] }
48
+ }
49
+ },
50
+ "outputs": { "y": { "dtype": "float32", "shape": [4], "tolerance": 1e-43 } }
51
+ },
52
+ {
53
+ "name": "f32_near_zero_negative_expm1_cancellation",
54
+ "provenance": {
55
+ "source": "onnxruntime/test/providers/cpu/activation/activation_op_test.cc",
56
+ "test": "ActivationOpTest.Elu",
57
+ "notes": "Valid normal-range ELU cancellation edge: exp(x) - 1 must preserve small negative values near zero, not round them to a coarser f32 exp step."
58
+ },
59
+ "attrs": { "alpha": 0.1 },
60
+ "inputs": {
61
+ "x": {
62
+ "dtype": "float32",
63
+ "shape": [4],
64
+ "data": { "kind": "values", "values": [-1e-7, -5e-7, -0.000001, -0.00001] }
65
+ }
66
+ },
67
+ "outputs": { "y": { "dtype": "float32", "shape": [4], "tolerance": 3e-9 } }
68
+ },
69
+ {
70
+ "name": "f32_negative_subnormal_expm1_tail_scalar_gpu_gap",
71
+ "skipGpu": {
72
+ "category": "permanent",
73
+ "reason": "Portable WGSL floating-point semantics do not guarantee preservation of the subnormal values required by this fixture. Backend evidence: WebGPU/Metal flushes subnormals to zero (f32 and f16); the kernel cannot preserve denormal inputs/outputs bit-exactly."
74
+ },
75
+ "provenance": {
76
+ "source": "onnxruntime/test/providers/cpu/activation/activation_op_test.cc",
77
+ "test": "ActivationOpTest.Elu",
78
+ "notes": "Scalar-path companion: tiny negative inputs round the expm1 branch to zero while positive subnormals pass through."
79
+ },
80
+ "attrs": { "alpha": 0.5 },
81
+ "inputs": {
82
+ "x": { "dtype": "float32", "shape": [3], "data": { "kind": "values", "values": [-1e-40, 0.0, 1e-40] } }
83
+ },
84
+ "outputs": { "y": { "dtype": "float32", "shape": [3], "tolerance": 1e-43 } }
85
+ },
86
+ {
87
+ "name": "f32_near_zero_negative_expm1_cancellation_scalar",
88
+ "provenance": {
89
+ "source": "onnxruntime/test/providers/cpu/activation/activation_op_test.cc",
90
+ "test": "ActivationOpTest.Elu",
91
+ "notes": "Scalar-path companion for the normal-range ELU cancellation edge."
92
+ },
93
+ "attrs": { "alpha": 0.1 },
94
+ "inputs": {
95
+ "x": { "dtype": "float32", "shape": [3], "data": { "kind": "values", "values": [-1e-7, -5e-7, -0.000001] } }
96
+ },
97
+ "outputs": { "y": { "dtype": "float32", "shape": [3], "tolerance": 3e-9 } }
98
+ },
99
+ {
100
+ "name": "f16_values",
101
+ "attrs": { "alpha": 1.25 },
102
+ "inputs": {
103
+ "x": {
104
+ "dtype": "float16",
105
+ "shape": [7],
106
+ "data": { "kind": "values", "values": [-8.0, -2.0, -0.25, 0.0, 0.25, 2.0, 8.0] }
107
+ }
108
+ },
109
+ "outputs": { "y": { "dtype": "float16", "shape": [7], "tolerance": 0.001 } }
110
+ },
111
+ {
112
+ "name": "ort_alpha_0_1_activation_extremes",
113
+ "provenance": {
114
+ "source": "onnxruntime/test/providers/cpu/activation/activation_op_test.cc",
115
+ "test": "ActivationOpTest.Elu",
116
+ "notes": "ORT shared activation vector with alpha set to 0.1."
117
+ },
118
+ "attrs": { "alpha": 0.1 },
119
+ "inputs": {
120
+ "x": {
121
+ "dtype": "float32",
122
+ "shape": [13],
123
+ "data": {
124
+ "kind": "values",
125
+ "values": [-1.0, 0.0, 1.0, 100.0, -100.0, 1000.0, -1000.0, 1.1754943508222875e-38, 1.1754943508222876e-39, -1.1754943508222876e-39, 3.4028234663852886e+38, -3.4028234663852886e+38, "Infinity"]
126
+ }
127
+ }
128
+ },
129
+ "outputs": { "y": { "dtype": "float32", "shape": [13], "tolerance": 0.000001 } }
130
+ },
131
+ {
132
+ "name": "onnx_backend_alpha2_example",
133
+ "provenance": {
134
+ "source": "cmake/external/onnx/onnx/backend/test/data/node/test_elu_example",
135
+ "test": "test_elu_example"
136
+ },
137
+ "attrs": { "alpha": 2 },
138
+ "inputs": { "x": { "dtype": "float32", "shape": [3], "data": { "kind": "values", "values": [-1.0, 0.0, 1.0] } } },
139
+ "outputs": { "y": { "dtype": "float32", "shape": [3], "tolerance": 0.000001 } }
140
+ },
141
+ {
142
+ "name": "onnx_backend_elu",
143
+ "provenance": { "source": "cmake/external/onnx/onnx/backend/test/data/node/test_elu" },
144
+ "attrs": { "alpha": 2 },
145
+ "inputs": {
146
+ "x": {
147
+ "dtype": "float32",
148
+ "shape": [3, 4, 5],
149
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/onnx_backend_input_x" } }
150
+ }
151
+ },
152
+ "outputs": { "y": { "dtype": "float32", "shape": [3, 4, 5], "tolerance": 0.00001 } }
153
+ },
154
+ {
155
+ "name": "onnx_backend_default_alpha",
156
+ "provenance": { "source": "cmake/external/onnx/onnx/backend/test/data/node/test_elu_default" },
157
+ "inputs": {
158
+ "x": {
159
+ "dtype": "float32",
160
+ "shape": [3, 4, 5],
161
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/onnx_backend_input_x" } }
162
+ }
163
+ },
164
+ "outputs": { "y": { "dtype": "float32", "shape": [3, 4, 5], "tolerance": 0.00001 } }
165
+ },
166
+ {
167
+ "name": "vec4_f16_lanes",
168
+ "inputs": {
169
+ "x": {
170
+ "dtype": "float16",
171
+ "shape": [16],
172
+ "data": {
173
+ "kind": "values",
174
+ "values": [-6.0, -4.0, -3.0, -2.0, -1.5, -1.0, -0.5, -0.25, 0.0, 0.25, 0.5, 1.0, 1.5, 2.0, 4.0, 6.0]
175
+ }
176
+ }
177
+ },
178
+ "outputs": { "y": { "dtype": "float16", "shape": [16], "tolerance": 0.001, "relTolerance": 0.002 } }
179
+ },
180
+ {
181
+ "name": "empty_input_zero_dim",
182
+ "attrs": { "alpha": 0.5 },
183
+ "inputs": { "x": { "dtype": "float32", "shape": [0], "data": { "kind": "values", "values": [] } } },
184
+ "outputs": { "y": { "dtype": "float32", "shape": [0], "tolerance": 0 } }
185
+ }
186
+ ]
187
+ }
build/webgpu/unary-scalar.wgsl.jinja ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% macro flat_tail_open() %}
2
+ @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
3
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) nwg: vec3<u32>) {
4
+ // 2D-folded flat index: gid.y carries the high bits when the element count exceeds the
5
+ // maxComputeWorkgroupsPerDimension limit (the dispatch caps x and spills the rest into y).
6
+ let invocation = gid.x + gid.y * nwg.x * {{ tunables.WORKGROUP_SIZE }}u;
7
+ // Tail-safe scalar x4 keeps vector-like dispatch density without requiring
8
+ // the logical tensor length (or its storage binding) to be vec4 aligned.
9
+ let begin = invocation * {{ source.itemsPerInvocation }}u;
10
+ let end = min(begin + {{ source.itemsPerInvocation }}u, params.count);
11
+ for (var i = begin; i < end; i = i + 1u) {
12
+ {%- endmacro %}
13
+ {% macro flat_tail_close() %}
14
+ }
15
+ {% endmacro %}
16
+
17
+ // Scalar unary fallback. Each branch retains the operation's numeric hardening,
18
+ // including Payne-Hanek trigonometric range reduction and NaN/overflow guards.
19
+ {% if usesF16 %}
20
+ enable f16;
21
+ {% endif %}
22
+ {{ env.wgsl.resourceDeclarations }}
23
+ fn expm1_safe(x: f32) -> f32 {
24
+ // exp(x) - 1 without the catastrophic cancellation of the literal exp(x) - 1.0,
25
+ // which loses all significance for small |x| (exp(x) rounds to 1.0). A 6-term
26
+ // Maclaurin series is exact to < 1 ULP for |x| < 0.125 and matches the direct
27
+ // form at the switchover, so the normal range is unchanged.
28
+ if (x > -0.125 && x < 0.125) {
29
+ return x * (1.0 + x * (0.5 + x * (0.16666666666666666 + x * (0.041666666666666664 + x * (0.008333333333333333 + x * 0.001388888888888889)))));
30
+ }
31
+ return exp(x) - 1.0;
32
+ }
33
+ {{ flat_tail_open() }}
34
+ let v = f32(x[i]);
35
+ y[i] = {{ scalar }}(select(params.alpha * expm1_safe(v), v, v >= 0.0));
36
+ {{ flat_tail_close() -}}
37
+ }
build/webgpu/unary-vec4.wgsl.jinja ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Loads and stores vec4<T> (128 bits) while retaining scalar per-component
2
+ // arithmetic, including per-component helper calls for guard-heavy operations.
3
+ {% if usesF16 %}
4
+ enable f16;
5
+ {% endif %}
6
+ {{ env.wgsl.resourceDeclarations }}
7
+
8
+ fn expm1_safe(x: vec4<f32>) -> vec4<f32> {
9
+ // exp(x) - 1 without the catastrophic cancellation of the literal exp(x) - 1.0,
10
+ // which loses all significance for small |x| (exp(x) rounds to 1.0). A 6-term
11
+ // Maclaurin series is exact to < 1 ULP for |x| < 0.125 and matches the direct
12
+ // form at the switchover, so the normal range is unchanged. Componentwise.
13
+ let series = x * (1.0 + x * (0.5 + x * (0.16666666666666666 + x * (0.041666666666666664 + x * (0.008333333333333333 + x * 0.001388888888888889)))));
14
+ let direct = exp(x) - 1.0;
15
+ return select(direct, series, abs(x) < vec4<f32>(0.125));
16
+ }
17
+ const ELU_ALPHA: f32 = f32({{ source.alpha }});
18
+
19
+ @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
20
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) nwg: vec3<u32>) {
21
+ // 2D-folded flat index: gid.y carries the high bits when the element count exceeds the
22
+ // maxComputeWorkgroupsPerDimension limit (the dispatch caps x and spills the rest into y).
23
+ let i = gid.x + gid.y * nwg.x * {{ tunables.WORKGROUP_SIZE }}u;
24
+ if (i >= params.count) {
25
+ return;
26
+ }
27
+ let xv = x[i];
28
+ let fv = vec4<f32>(xv);
29
+ y[i] = {{ vectorScalar }}(select(ELU_ALPHA * expm1_safe(fv), fv, fv >= vec4<f32>(0.0)));
30
+ }