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

sync 91d990483a17

Browse files
README.md CHANGED
@@ -18,15 +18,15 @@ See the [ONNX `Elu` spec](https://onnx.ai/onnx/operators/onnx__Elu.html) for the
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
 
@@ -44,7 +44,7 @@ Default values (overridable per request):
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
@@ -53,10 +53,14 @@ Default values (overridable per request):
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
 
 
18
 
19
  ## Inputs
20
 
21
+ | Name | Upstream name | 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 | Upstream name | 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
 
 
44
 
45
  ## Files
46
 
47
+ - [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, per-variant templates, 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
 
53
 
54
  ## Use with `@huggingface/kernels`
55
 
56
+ ```sh
57
+ npm install --save-exact @huggingface/kernels@0.0.1-preview.2
58
+ ```
59
+
60
+ Required output shapes and logical data types are inferred from the supplied inputs and attributes; result tensors are allocated automatically.
61
 
62
  The `version: 1` option selects the published kernel contract; it is independent of any operator opset, contrib `since_version`, or model version.
63
+ It follows the `v1` branch as fixes land. To pin exact artifact bytes, pass a 40-character commit `revision` instead of `version`.
64
 
65
  Replace each `*Data` placeholder with a typed array containing the corresponding input data.
66
 
build/webgpu/bench.json CHANGED
@@ -1,5 +1,4 @@
1
  {
2
- "op": "ai.onnx.Elu",
3
  "cases": [
4
  {
5
  "name": "1m_f32",
 
1
  {
 
2
  "cases": [
3
  {
4
  "name": "1m_f32",
build/webgpu/manifest.json CHANGED
@@ -2,81 +2,66 @@
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
  }
 
2
  "domain": "ai.onnx",
3
  "name": "Elu",
4
  "sinceVersion": 6,
5
+ "inputs": { "x": { "onnx": "X", "dtype": "T" } },
6
+ "outputs": { "y": { "onnx": "Y", "dtype": "T", "rank": "ranks.x", "shape": "shapes.x" } },
7
+ "attributes": { "alpha": { "default": 1 } },
 
 
 
 
 
 
 
 
 
 
 
 
8
  "typeConstraints": { "T": ["float32", "float16"] },
9
+ "tunables": { "WORKGROUP_SIZE": { "default": 256 } },
10
+ "derive": { "scalar": "dtypes.T", "usesF16": "dtypes.T == \"f16\"" },
 
 
 
 
11
  "variants": [
12
  {
13
  "id": "same_layout_vec4",
14
+ "priority": 20,
15
+ "when": ["numel(shapes.x) > 0", "numel(shapes.x) % 4 == 0", "numel(shapes.x) == numel(shapes.y)", "f16Ok(dtypes.T)"],
16
+ "derive": { "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"" },
17
  "passes": [
18
  {
19
  "id": "main",
20
  "name": "Elu.vec4",
21
+ "shader": "unary-vec4.wgsl.jinja",
22
+ "derive": {
23
+ "op": "\"elu\"",
24
+ "alpha": "attrs.alpha",
25
+ "vec4PerThread": "4 if numel(shapes.y) * dtypeBytes(tensorDtypes.y) <= 16777216 else 1"
26
+ },
27
  "bindings": [
28
+ { "arg": "x", "elementType": "$vectorScalar" },
29
+ { "arg": "y", "elementType": "$vectorScalar" },
30
+ { "name": "params", "struct": [{ "name": "count", "type": "u32", "value": "numel(shapes.y) / 4" }] }
 
 
 
 
 
 
 
 
31
  ],
32
+ "dispatch": {
33
+ "x": "min(ceilDiv((ceilDiv(numel(shapes.y) / 4, 4 if numel(shapes.y) * dtypeBytes(tensorDtypes.y) <= 16777216 else 1)), (tunables.WORKGROUP_SIZE)), 65535)",
34
+ "y": "ceilDiv(ceilDiv((ceilDiv(numel(shapes.y) / 4, 4 if numel(shapes.y) * dtypeBytes(tensorDtypes.y) <= 16777216 else 1)), (tunables.WORKGROUP_SIZE)), 65535)",
35
+ "z": 1
36
+ }
37
  }
38
+ ]
 
39
  },
40
  {
41
  "id": "elementwise",
42
+ "when": ["numel(shapes.x) == numel(shapes.y) and (f16Ok(dtypes.T))"],
43
  "passes": [
44
  {
45
  "id": "main",
46
  "name": "Elu",
47
+ "shader": "unary-scalar.wgsl.jinja",
48
+ "derive": { "op": "\"elu\"", "itemsPerInvocation": 4 },
49
  "bindings": [
50
+ "x",
51
+ "y",
52
  {
53
  "name": "params",
54
+ "struct": [
55
+ { "name": "count", "type": "u32", "value": "numel(shapes.y)" },
56
+ { "name": "alpha", "type": "f32", "value": "attrs.alpha" }
57
+ ]
 
 
 
 
 
58
  }
59
  ],
60
+ "dispatch": {
61
+ "x": "min(ceilDiv((ceilDiv(numel(shapes.y), 4)), (tunables.WORKGROUP_SIZE)), 65535)",
62
+ "y": "ceilDiv(ceilDiv((ceilDiv(numel(shapes.y), 4)), (tunables.WORKGROUP_SIZE)), 65535)",
63
+ "z": 1
64
+ }
65
  }
66
  ]
67
  }
build/webgpu/metadata.json CHANGED
@@ -1,19 +1,22 @@
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
  }
 
1
  {
2
  "name": "ai.onnx.Elu",
3
+ "id": "_ai_onnx_elu_webgpu_ec52d7b",
4
  "version": 1,
5
  "license": "Apache-2.0",
6
  "backend": { "type": "webgpu" },
7
  "digest": {
8
  "algorithm": "sha256",
9
  "files": {
10
+ "bench.json": "eiwKWjWWOzA7C9z26++rXtUz5yy870QjGR4DlTbyUxw=",
11
+ "manifest.json": "OzK0Gx6etOts1GvfOigK0vvpHTSzMjS+/QIH16Y2wmw=",
12
+ "test.json": "y6pMkqiL7rwwNdPkRUdOQj3ngy0de2tjjzkXYQirH7k=",
13
+ "unary-scalar.wgsl.jinja": "fBv4sZpdbUvk1gkgyMWwk82P4NXrnUMyuMwGhA4CXUg=",
14
+ "unary-vec4.wgsl.jinja": "JXyoLX0+h/n4xm23tuhcE47PvUrjjUk03Qo+Vuldn/s="
15
  }
16
  },
17
+ "provenance": { "kernel": { "sha": "91d990483a174128daf7673f3f37a7c890493ae1", "dirty": false } },
18
+ "webgpu": {
19
+ "manifestSpec": "2.0",
20
+ "variants": { "same_layout_vec4": ["unary-vec4.wgsl.jinja"], "elementwise": ["unary-scalar.wgsl.jinja"] }
21
+ }
22
  }
build/webgpu/test.json CHANGED
@@ -1,5 +1,4 @@
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
  },
@@ -37,7 +36,7 @@
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": {
@@ -75,7 +74,7 @@
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": {
@@ -88,7 +87,7 @@
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": {
 
1
  {
 
2
  "fixtureArrays": {
3
  "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]
4
  },
 
36
  "provenance": {
37
  "source": "onnxruntime/test/providers/cpu/activation/activation_op_test.cc",
38
  "test": "ActivationOpTest.Elu",
39
+ "notes": "For tiny negative inputs, alpha*(exp(x)-1) rounds to zero; returning the original subnormal is incorrect."
40
  },
41
  "attrs": { "alpha": 0.5 },
42
  "inputs": {
 
74
  "provenance": {
75
  "source": "onnxruntime/test/providers/cpu/activation/activation_op_test.cc",
76
  "test": "ActivationOpTest.Elu",
77
+ "notes": "On an unaligned scalar path, tiny negative inputs round the expm1 branch to zero while positive subnormals pass through."
78
  },
79
  "attrs": { "alpha": 0.5 },
80
  "inputs": {
 
87
  "provenance": {
88
  "source": "onnxruntime/test/providers/cpu/activation/activation_op_test.cc",
89
  "test": "ActivationOpTest.Elu",
90
+ "notes": "Unaligned scalar-path inputs exercise ELU cancellation near zero in the normal range."
91
  },
92
  "attrs": { "alpha": 0.1 },
93
  "inputs": {
build/webgpu/unary-scalar.wgsl.jinja CHANGED
@@ -1,30 +1,32 @@
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
  }
 
1
  {% macro flat_tail_open() %}
2
  @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
3
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
4
  // 2D-folded flat index: gid.y carries the high bits when the element count exceeds the
5
+ // dispatch's per-axis workgroup fold width (the dispatch caps x and spills the rest into y).
6
+ let invocation = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ 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
+ {% set itemsPerInvocation = itemsPerInvocation if itemsPerInvocation is defined else 4 %}
10
+ let begin = invocation * {{ itemsPerInvocation }}u;
11
+ let end = min(begin + {{ itemsPerInvocation }}u, params.count);
12
  for (var i = begin; i < end; i = i + 1u) {
13
  {%- endmacro %}
14
  {% macro flat_tail_close() %}
15
  }
16
  {% endmacro %}
17
 
18
+ // Scalar unary elementwise implementation. Specialization emits only the
19
+ // selected operation and any numerical helper it requires.
20
  {% if usesF16 %}
21
  enable f16;
22
  {% endif %}
23
  {{ env.wgsl.resourceDeclarations }}
24
+ {% set alpha = alpha | default(0) %}
25
  fn expm1_safe(x: f32) -> f32 {
26
  // exp(x) - 1 without the catastrophic cancellation of the literal exp(x) - 1.0,
27
  // which loses all significance for small |x| (exp(x) rounds to 1.0). A 6-term
28
+ // Maclaurin series avoids that cancellation for |x| < 0.125; use the direct
29
+ // form elsewhere.
30
  if (x > -0.125 && x < 0.125) {
31
  return x * (1.0 + x * (0.5 + x * (0.16666666666666666 + x * (0.041666666666666664 + x * (0.008333333333333333 + x * 0.001388888888888889)))));
32
  }
build/webgpu/unary-vec4.wgsl.jinja CHANGED
@@ -1,30 +1,50 @@
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
  }
 
1
+ // Loads and stores vec4<T> while evaluating the selected unary operation per
2
+ // component.
 
 
 
3
  {{ env.wgsl.resourceDeclarations }}
4
 
5
+ {% set alpha = alpha | default(0) %}
6
  fn expm1_safe(x: vec4<f32>) -> vec4<f32> {
7
  // exp(x) - 1 without the catastrophic cancellation of the literal exp(x) - 1.0,
8
  // which loses all significance for small |x| (exp(x) rounds to 1.0). A 6-term
9
+ // Maclaurin series avoids that cancellation for |x| < 0.125; use the direct
10
+ // form elsewhere. Componentwise.
11
  let series = x * (1.0 + x * (0.5 + x * (0.16666666666666666 + x * (0.041666666666666664 + x * (0.008333333333333333 + x * 0.001388888888888889)))));
12
  let direct = exp(x) - 1.0;
13
  return select(direct, series, abs(x) < vec4<f32>(0.125));
14
  }
15
+ const ELU_ALPHA: f32 = f32({{ alpha }});
16
+
17
+ {% set vec4PerThread = vec4PerThread %}
18
+ {% if vec4PerThread > 1 %}
19
+ const ITEMS: u32 = {{ vec4PerThread }}u;
20
+ {% endif %}
21
 
22
  @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
23
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
24
  // 2D-folded flat index: gid.y carries the high bits when the element count exceeds the
25
+ // per-axis dispatch fold width (the dispatch caps x and spills the rest into y).
26
+ {% if vec4PerThread > 1 %}
27
+ // Each invocation walks ITEMS vec4 groups a span apart. Consecutive lanes
28
+ // access consecutive words on every step, while each lane can keep several
29
+ // independent loads in flight.
30
+ let tid = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ tunables.WORKGROUP_SIZE }}u;
31
+ let span = (params.count + ITEMS - 1u) / ITEMS;
32
+ for (var j = 0u; j < ITEMS; j = j + 1u) {
33
+ let i = tid + j * span;
34
+ if (i >= params.count) {
35
+ break;
36
+ }
37
+ {% else %}
38
+ let i = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ tunables.WORKGROUP_SIZE }}u;
39
  if (i >= params.count) {
40
  return;
41
  }
42
+ {% endif %}
43
+
44
  let xv = x[i];
45
  let fv = vec4<f32>(xv);
46
  y[i] = {{ vectorScalar }}(select(ELU_ALPHA * expm1_safe(fv), fv, fv >= vec4<f32>(0.0)));
47
+ {% if vec4PerThread > 1 %}
48
+ }
49
+ {% endif %}
50
  }