Xenova HF Staff commited on
Commit
0ea7f70
·
verified ·
1 Parent(s): e5a2dd7

sync 91d990483a17

Browse files
README.md CHANGED
@@ -18,15 +18,15 @@ See the [ONNX `Mish` spec](https://onnx.ai/onnx/operators/onnx__Mish.html) for t
18
 
19
  ## Inputs
20
 
21
- | Name | Bind key | Logical dtype | Rank | Shape | Description | Presence |
22
  | --- | --- | --- | --- | --- | --- | --- |
23
- | `X` | `x` | `T` | — | — | Values transformed elementwise by the Mish 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 the same shape as `X`, with Mish applied elementwise. | required |
30
 
31
  ## Type constraints
32
 
@@ -36,7 +36,7 @@ See the [ONNX `Mish` spec](https://onnx.ai/onnx/operators/onnx__Mish.html) for t
36
 
37
  ## Files
38
 
39
- - [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, provenance)
40
  - [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
41
  - [`test.json`](build/webgpu/test.json) — correctness cases
42
  - [`bench.json`](build/webgpu/bench.json) — benchmark + tuning cases
@@ -45,10 +45,14 @@ See the [ONNX `Mish` spec](https://onnx.ai/onnx/operators/onnx__Mish.html) for t
45
 
46
  ## Use with `@huggingface/kernels`
47
 
48
- The loader derives every required output's shape and logical dtype from the manifest contract and this call.
49
- It then allocates the result tensors automatically.
 
 
 
50
 
51
  The `version: 1` option selects the published kernel contract; it is independent of any operator opset, contrib `since_version`, or model version.
 
52
 
53
  Replace each `*Data` placeholder with a typed array containing the corresponding input data.
54
 
 
18
 
19
  ## Inputs
20
 
21
+ | Name | Upstream name | Logical dtype | Rank | Shape | Description | Presence |
22
  | --- | --- | --- | --- | --- | --- | --- |
23
+ | `x` | `X` | `T` | — | — | Values transformed elementwise by the Mish 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 the same shape as `X`, with Mish applied elementwise. | required |
30
 
31
  ## Type constraints
32
 
 
36
 
37
  ## Files
38
 
39
+ - [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, per-variant templates, provenance)
40
  - [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
41
  - [`test.json`](build/webgpu/test.json) — correctness cases
42
  - [`bench.json`](build/webgpu/bench.json) — benchmark + tuning cases
 
45
 
46
  ## Use with `@huggingface/kernels`
47
 
48
+ ```sh
49
+ npm install --save-exact @huggingface/kernels@0.0.1-preview.2
50
+ ```
51
+
52
+ Required output shapes and logical data types are inferred from the supplied inputs and attributes; result tensors are allocated automatically.
53
 
54
  The `version: 1` option selects the published kernel contract; it is independent of any operator opset, contrib `since_version`, or model version.
55
+ It follows the `v1` branch as fixes land. To pin exact artifact bytes, pass a 40-character commit `revision` instead of `version`.
56
 
57
  Replace each `*Data` placeholder with a typed array containing the corresponding input data.
58
 
build/webgpu/bench.json CHANGED
@@ -1,5 +1,4 @@
1
  {
2
- "op": "ai.onnx.Mish",
3
  "cases": [
4
  {
5
  "name": "1m_f32",
 
1
  {
 
2
  "cases": [
3
  {
4
  "name": "1m_f32",
build/webgpu/manifest.json CHANGED
@@ -2,28 +2,16 @@
2
  "domain": "ai.onnx",
3
  "name": "Mish",
4
  "sinceVersion": 18,
5
- "description": "Applies the Mish activation function elementwise: `mish(x) = x * tanh(ln(1 + e^x))`. Mish is a self-regularized non-monotonic activation that smoothly gates its input using a softplus-based tanh.",
6
- "inputs": [{ "role": "X", "dtype": "T", "description": "Values transformed elementwise by the Mish activation." }],
7
- "outputs": [
8
- {
9
- "role": "Y",
10
- "dtype": "T",
11
- "rank": "ranks.X",
12
- "description": "Output tensor with the same shape as `X`, with Mish applied elementwise.",
13
- "shape": "shapes.X"
14
- }
15
- ],
16
  "typeConstraints": { "T": ["float32", "float16"] },
17
- "args": {
18
- "x": { "kind": "tensor", "semantic": "X", "role": "input" },
19
- "y": { "kind": "tensor", "semantic": "Y", "role": "output" }
20
- },
21
- "tunables": { "WORKGROUP_SIZE": 256 },
22
  "variants": [
23
  {
24
  "id": "same_layout_vec4",
 
25
  "when": ["numel(shapes.x) > 0", "numel(shapes.x) % 4 == 0", "numel(shapes.x) == numel(shapes.y)", "f16Ok(dtypes.T)"],
26
- "constants": {
27
  "scalar": "dtypes.T",
28
  "usesF16": "dtypes.T == \"f16\"",
29
  "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\""
@@ -32,47 +20,103 @@
32
  {
33
  "id": "main",
34
  "name": "Mish.vec4",
35
- "source": { "shader": "unary-vec4.wgsl.jinja", "inputs": { "op": "\"mish\"" } },
36
- "bindings": [
37
- { "name": "x", "arg": "x", "buffer": { "type": "read-only-storage" }, "elementType": "$vectorScalar" },
38
- { "name": "y", "arg": "y", "buffer": { "type": "storage" }, "elementType": "$vectorScalar" },
39
- {
40
- "name": "params",
41
- "semantic": "kernel.params",
42
- "buffer": { "type": "uniform" },
43
- "struct": {
44
- "name": "Params",
45
- "fields": [{ "name": "count", "type": "u32", "value": "numel(shapes.y) / 4" }]
46
- }
47
- }
48
- ],
49
- "dispatch": { "threads": "numel(shapes.y) / 4", "workgroupSize": "tunables.WORKGROUP_SIZE" }
50
  }
51
- ],
52
- "priority": 20
53
  },
54
  {
55
  "id": "elementwise",
56
  "when": ["numel(shapes.x) == numel(shapes.y)", "f16Ok(dtypes.T)"],
57
- "constants": { "scalar": "dtypes.T", "usesF16": "dtypes.T == \"f16\"" },
58
  "passes": [
59
  {
60
  "id": "main",
61
  "name": "Mish",
62
- "source": { "shader": "unary-scalar.wgsl.jinja", "inputs": { "op": "\"mish\"", "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": { "name": "Params", "fields": [{ "name": "count", "type": "u32", "value": "numel(shapes.y)" }] }
71
- }
72
- ],
73
- "dispatch": { "threads": "ceilDiv(numel(shapes.y), 4)", "workgroupSize": "tunables.WORKGROUP_SIZE" }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
  }
75
  ]
76
  }
77
- ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
  }
 
2
  "domain": "ai.onnx",
3
  "name": "Mish",
4
  "sinceVersion": 18,
5
+ "inputs": { "x": { "onnx": "X", "dtype": "T" } },
6
+ "outputs": { "y": { "onnx": "Y", "dtype": "T", "rank": "ranks.x", "shape": "shapes.x" } },
 
 
 
 
 
 
 
 
 
7
  "typeConstraints": { "T": ["float32", "float16"] },
8
+ "tunables": { "WORKGROUP_SIZE": { "default": 256 } },
 
 
 
 
9
  "variants": [
10
  {
11
  "id": "same_layout_vec4",
12
+ "priority": 20,
13
  "when": ["numel(shapes.x) > 0", "numel(shapes.x) % 4 == 0", "numel(shapes.x) == numel(shapes.y)", "f16Ok(dtypes.T)"],
14
+ "derive": {
15
  "scalar": "dtypes.T",
16
  "usesF16": "dtypes.T == \"f16\"",
17
  "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\""
 
20
  {
21
  "id": "main",
22
  "name": "Mish.vec4",
23
+ "shader": "unary-vec4.wgsl.jinja",
24
+ "derive": {
25
+ "op": "\"mish\"",
26
+ "vec4PerThread": "4 if numel(shapes.y) * dtypeBytes(tensorDtypes.y) <= 16777216 else 1"
27
+ },
28
+ "bindings": ["x", "y", "params_unary"],
29
+ "dispatch": {
30
+ "x": "min(ceilDiv((ceilDiv(numel(shapes.y) / 4, 4 if numel(shapes.y) * dtypeBytes(tensorDtypes.y) <= 16777216 else 1)), (tunables.WORKGROUP_SIZE)), 65535)",
31
+ "y": "ceilDiv(ceilDiv((ceilDiv(numel(shapes.y) / 4, 4 if numel(shapes.y) * dtypeBytes(tensorDtypes.y) <= 16777216 else 1)), (tunables.WORKGROUP_SIZE)), 65535)",
32
+ "z": 1
33
+ }
 
 
 
 
34
  }
35
+ ]
 
36
  },
37
  {
38
  "id": "elementwise",
39
  "when": ["numel(shapes.x) == numel(shapes.y)", "f16Ok(dtypes.T)"],
40
+ "derive": { "scalar": "dtypes.T", "usesF16": "dtypes.T == \"f16\"" },
41
  "passes": [
42
  {
43
  "id": "main",
44
  "name": "Mish",
45
+ "shader": "unary-scalar.wgsl.jinja",
46
+ "derive": { "op": "\"mish\"", "itemsPerInvocation": 4 },
47
+ "bindings": ["x_2", "y_2", "params_2_unary"],
48
+ "dispatch": {
49
+ "x": "min(ceilDiv((ceilDiv(numel(shapes.y), 4)), (tunables.WORKGROUP_SIZE)), 65535)",
50
+ "y": "ceilDiv(ceilDiv((ceilDiv(numel(shapes.y), 4)), (tunables.WORKGROUP_SIZE)), 65535)",
51
+ "z": 1
52
+ }
53
+ }
54
+ ]
55
+ },
56
+ {
57
+ "id": "same_layout_vec4_tail",
58
+ "priority": 19,
59
+ "when": ["numel(shapes.x) > 4", "numel(shapes.x) % 4 != 0", "numel(shapes.x) == numel(shapes.y)", "f16Ok(dtypes.T)"],
60
+ "derive": {
61
+ "scalar": "dtypes.T",
62
+ "usesF16": "dtypes.T == \"f16\"",
63
+ "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\""
64
+ },
65
+ "passes": [
66
+ {
67
+ "id": "bulk",
68
+ "name": "Mish.vec4Bulk",
69
+ "shader": "unary-vec4.wgsl.jinja",
70
+ "derive": {
71
+ "op": "\"mish\"",
72
+ "vec4PerThread": "4 if numel(shapes.y) * dtypeBytes(tensorDtypes.y) <= 16777216 else 1"
73
+ },
74
+ "bindings": ["x", "y", "params_unary_tail"],
75
+ "dispatch": {
76
+ "x": "min(ceilDiv((ceilDiv(floor(numel(shapes.y) / 4), 4 if numel(shapes.y) * dtypeBytes(tensorDtypes.y) <= 16777216 else 1)), (tunables.WORKGROUP_SIZE)), 65535)",
77
+ "y": "ceilDiv(ceilDiv((ceilDiv(floor(numel(shapes.y) / 4), 4 if numel(shapes.y) * dtypeBytes(tensorDtypes.y) <= 16777216 else 1)), (tunables.WORKGROUP_SIZE)), 65535)",
78
+ "z": 1
79
+ }
80
+ },
81
+ {
82
+ "id": "tail",
83
+ "name": "Mish.tail",
84
+ "shader": "unary-scalar.wgsl.jinja",
85
+ "derive": { "op": "\"mish\"", "itemsPerInvocation": 4, "tailOnly": true },
86
+ "bindings": ["x_2", "y_2", "params_2_unary_tail"],
87
+ "dispatch": {
88
+ "x": "min(ceilDiv((1), (tunables.WORKGROUP_SIZE)), 65535)",
89
+ "y": "ceilDiv(ceilDiv((1), (tunables.WORKGROUP_SIZE)), 65535)",
90
+ "z": 1
91
+ }
92
  }
93
  ]
94
  }
95
+ ],
96
+ "bindings": {
97
+ "x": { "buffer": "read-only-storage", "elementType": "$vectorScalar" },
98
+ "y": { "buffer": "storage", "elementType": "$vectorScalar" },
99
+ "params_unary": {
100
+ "buffer": "uniform",
101
+ "struct": [{ "name": "count", "type": "u32", "value": "numel(shapes.y) / 4" }],
102
+ "name": "params"
103
+ },
104
+ "x_2": { "buffer": "read-only-storage", "name": "x", "elementType": "$scalar" },
105
+ "y_2": { "buffer": "storage", "name": "y", "elementType": "$scalar" },
106
+ "params_2_unary": {
107
+ "buffer": "uniform",
108
+ "name": "params",
109
+ "struct": [{ "name": "count", "type": "u32", "value": "numel(shapes.y)" }]
110
+ },
111
+ "params_unary_tail": {
112
+ "buffer": "uniform",
113
+ "struct": [{ "name": "count", "type": "u32", "value": "floor(numel(shapes.y) / 4)" }],
114
+ "name": "params"
115
+ },
116
+ "params_2_unary_tail": {
117
+ "buffer": "uniform",
118
+ "name": "params",
119
+ "struct": [{ "name": "count", "type": "u32", "value": "numel(shapes.y)" }]
120
+ }
121
+ }
122
  }
build/webgpu/metadata.json CHANGED
@@ -1,19 +1,26 @@
1
  {
2
  "name": "ai.onnx.Mish",
3
- "id": "_ai_onnx_mish_webgpu_a99258a",
4
  "version": 1,
5
  "license": "Apache-2.0",
6
  "backend": { "type": "webgpu" },
7
  "digest": {
8
  "algorithm": "sha256",
9
  "files": {
10
- "bench.json": "faDpALYIY3uK5S1wPVMgs/oHhjLDA9SG3vsgQ+Ft7Ds=",
11
- "manifest.json": "gjrNDsfxKYT9/Za4zImkT0t/ZzxPQ13mFYJOvZuRB7I=",
12
- "test.json": "jWKrZonQPkjiI5K5te3ZdyIZIxACG7fMVoAvKQjVg2Y=",
13
- "unary-scalar.wgsl.jinja": "j5Dt18TaEi25ZySk2z4iEyQcRtkAEYQlOoO60mqG668=",
14
- "unary-vec4.wgsl.jinja": "UOWANhQMot9qLN3iIhH89q5NrRkwCD7lAPQ5Fx1bE9Y="
15
  }
16
  },
17
- "provenance": { "kernel": { "sha": "2e7068faf55e7f43df740015f6d1ee49391a41c5", "dirty": false } },
18
- "webgpu": { "manifestSpec": "1.0", "specialized": true, "opPath": "ops/ai.onnx.Mish" }
 
 
 
 
 
 
 
19
  }
 
1
  {
2
  "name": "ai.onnx.Mish",
3
+ "id": "_ai_onnx_mish_webgpu_096f2a8",
4
  "version": 1,
5
  "license": "Apache-2.0",
6
  "backend": { "type": "webgpu" },
7
  "digest": {
8
  "algorithm": "sha256",
9
  "files": {
10
+ "bench.json": "TfLj3+DuyE9y+n5bGsOjK1xu3f0uDeBcIAhyvYzR5rU=",
11
+ "manifest.json": "XKRUFo4lxoSn+9aekUCsUGHOcbdj9tv9Hg+rYA2lXvY=",
12
+ "test.json": "OUDrD3ZrcYkRW2c77hqlGo/d6Ws8hsUgNk/hMv1pz3c=",
13
+ "unary-scalar.wgsl.jinja": "z8hXeS1n3GN14/DcKymOFL+ZIqEcu5GJeOjnyypRWJU=",
14
+ "unary-vec4.wgsl.jinja": "e1wb44e3IKm1LIkdMYM87pnMkTOzqU32SvJPVN7EG64="
15
  }
16
  },
17
+ "provenance": { "kernel": { "sha": "91d990483a174128daf7673f3f37a7c890493ae1", "dirty": false } },
18
+ "webgpu": {
19
+ "manifestSpec": "2.0",
20
+ "variants": {
21
+ "same_layout_vec4": ["unary-vec4.wgsl.jinja"],
22
+ "elementwise": ["unary-scalar.wgsl.jinja"],
23
+ "same_layout_vec4_tail": ["unary-scalar.wgsl.jinja", "unary-vec4.wgsl.jinja"]
24
+ }
25
+ }
26
  }
build/webgpu/test.json CHANGED
@@ -1,5 +1,4 @@
1
  {
2
- "op": "ai.onnx.Mish",
3
  "cases": [
4
  {
5
  "name": "f32_values",
@@ -51,7 +50,7 @@
51
  "provenance": {
52
  "source": "cmake/external/onnx/onnx/backend/test/data/node/test_mish",
53
  "test": "test_mish",
54
- "notes": "Scalar-path companion to the vec4 negative-tail stability case: non-multiple-of-4 shape must still use log1p-style softplus for tiny finite outputs."
55
  },
56
  "inputs": {
57
  "x": { "dtype": "float32", "shape": [3], "data": { "kind": "values", "values": [-19.0, -18.0, -17.0] } }
@@ -83,7 +82,7 @@
83
  "provenance": {
84
  "source": "cmake/external/onnx/onnx/backend/test/data/node/test_mish",
85
  "test": "test_mish",
86
- "notes": "Scalar-path companion for Mish values whose softplus term is subnormal but whose final product is finite."
87
  },
88
  "inputs": {
89
  "x": { "dtype": "float32", "shape": [3], "data": { "kind": "values", "values": [-80.0, -88.0, -90.0] } }
@@ -120,7 +119,7 @@
120
  {
121
  "name": "f32_nonfinite_endpoints",
122
  "provenance": {
123
- "notes": "ORT CPU validated nonfinite edge: -Infinity follows the literal formula into NaN, +Infinity stays +Infinity."
124
  },
125
  "inputs": {
126
  "x": {
 
1
  {
 
2
  "cases": [
3
  {
4
  "name": "f32_values",
 
50
  "provenance": {
51
  "source": "cmake/external/onnx/onnx/backend/test/data/node/test_mish",
52
  "test": "test_mish",
53
+ "notes": "A non-multiple-of-four shape exercises log1p-style softplus on the scalar path for tiny finite negative-tail outputs."
54
  },
55
  "inputs": {
56
  "x": { "dtype": "float32", "shape": [3], "data": { "kind": "values", "values": [-19.0, -18.0, -17.0] } }
 
82
  "provenance": {
83
  "source": "cmake/external/onnx/onnx/backend/test/data/node/test_mish",
84
  "test": "test_mish",
85
+ "notes": "On the scalar path, Mish inputs with a subnormal softplus term must still produce the finite expected product."
86
  },
87
  "inputs": {
88
  "x": { "dtype": "float32", "shape": [3], "data": { "kind": "values", "values": [-80.0, -88.0, -90.0] } }
 
119
  {
120
  "name": "f32_nonfinite_endpoints",
121
  "provenance": {
122
+ "notes": "Nonfinite edge: -Infinity follows the literal formula into NaN, +Infinity stays +Infinity."
123
  },
124
  "inputs": {
125
  "x": {
build/webgpu/unary-scalar.wgsl.jinja CHANGED
@@ -1,50 +1,58 @@
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 tanh_safe(x: f32) -> f32 {
24
- // The hardware tanh evaluates (e^2x - 1)/(e^2x + 1), which is Inf/Inf = NaN
25
- // once e^2x overflows (here softplus(v) ~ v grows without bound, and overflows
26
- // to +Inf for v > ~88). tanh saturates to 1 within f32 precision by x ~ 9, so
27
- // clamp the tail; this keeps mish(v) -> v for large v instead of NaN.
28
  if (x > 10.0) { return 1.0; }
29
  if (x < -10.0) { return -1.0; }
30
- // The hardware tanh also collapses to 0 (or a single ULP step) for tiny x,
31
- // because its internal e^2x rounds back to 1.0; tanh(x) == x to full f32
32
- // precision well before |x| ~ 1e-4 (the x^3/3 term is < 1 ULP), so return x
33
- // directly. Without this the small softplus tail of mish flushes to zero.
34
  if (x > -1.0e-4 && x < 1.0e-4) { return x; }
35
  return tanh(x);
36
- }
 
37
  fn log1p_small_positive(y: f32) -> f32 {
38
- // log1p(y) for small positive y via series, avoiding the catastrophic
39
- // cancellation of log(1.0 + y) where 1.0 + y rounds back to 1.0 (mirrors the
40
- // Softplus kernel's helper).
41
  return y * (1.0 + y * (-0.5 + y * (0.3333333333333333 + y * (-0.25 + y * 0.2))));
42
  }
43
  fn mish_softplus(v: f32) -> f32 {
44
  // Numerically stable softplus = log(1 + e^v). The naive log(1.0 + exp(v))
45
  // collapses to 0 once exp(v) underflows below the 1.0 + x rounding step
46
- // (v <~ -16), which zeros mish's small finite negative tail. Match the CPU
47
- // log1p form: series for the tiny tail, identity-shifted log otherwise.
48
  if (v > 20.0) { return v; }
49
  if (v < -8.0) { return log1p_small_positive(exp(v)); }
50
  return log(1.0 + exp(-abs(v))) + max(v, 0.0);
 
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
+ {% if tailOnly is defined and tailOnly %}
11
+ // Tail of a vec4 bulk pass: lane zero alone covers the elements the packed
12
+ // pass left behind, from the last multiple of the item count to the end.
13
+ if (invocation != 0u) {
14
+ return;
15
+ }
16
+ let begin = params.count - params.count % {{ itemsPerInvocation }}u;
17
+ let end = params.count;
18
+ {% else %}
19
+ let begin = invocation * {{ itemsPerInvocation }}u;
20
+ let end = min(begin + {{ itemsPerInvocation }}u, params.count);
21
+ {% endif %}
22
  for (var i = begin; i < end; i = i + 1u) {
23
  {%- endmacro %}
24
  {% macro flat_tail_close() %}
25
  }
26
  {% endmacro %}
27
 
28
+ // Scalar unary elementwise implementation. Specialization emits only the
29
+ // selected operation and any numerical helper it requires.
30
  {% if usesF16 %}
31
  enable f16;
32
  {% endif %}
33
  {{ env.wgsl.resourceDeclarations }}
34
+ {% macro emit_tanh_safe() %}
35
  fn tanh_safe(x: f32) -> f32 {
36
+ // tanh rounds to its saturated value for these tails in f32. Return that
37
+ // value directly, including for infinite input, before invoking the builtin.
 
 
38
  if (x > 10.0) { return 1.0; }
39
  if (x < -10.0) { return -1.0; }
40
+ // For tiny |x|, return x directly to preserve its sign and magnitude without
41
+ // relying on backend-specific builtin behavior near zero.
 
 
42
  if (x > -1.0e-4 && x < 1.0e-4) { return x; }
43
  return tanh(x);
44
+ }{% endmacro %}
45
+ {{ emit_tanh_safe() }}
46
  fn log1p_small_positive(y: f32) -> f32 {
47
+ // log1p(y) for small positive y via series, avoiding cancellation in
48
+ // log(1.0 + y) when 1.0 + y rounds back to 1.0.
 
49
  return y * (1.0 + y * (-0.5 + y * (0.3333333333333333 + y * (-0.25 + y * 0.2))));
50
  }
51
  fn mish_softplus(v: f32) -> f32 {
52
  // Numerically stable softplus = log(1 + e^v). The naive log(1.0 + exp(v))
53
  // collapses to 0 once exp(v) underflows below the 1.0 + x rounding step
54
+ // (v <~ -16), which zeros Mish's small finite negative tail. Use a series for
55
+ // the tiny tail and the identity-shifted logarithm otherwise.
56
  if (v > 20.0) { return v; }
57
  if (v < -8.0) { return log1p_small_positive(exp(v)); }
58
  return log(1.0 + exp(-abs(v))) + max(v, 0.0);
build/webgpu/unary-vec4.wgsl.jinja CHANGED
@@ -1,34 +1,29 @@
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 tanh_safe(x: f32) -> f32 {
9
- // The hardware tanh evaluates (e^2x - 1)/(e^2x + 1), which is Inf/Inf = NaN
10
- // once e^2x overflows (x > ~44, and at x == +Inf). tanh saturates to +/-1
11
- // within f32 precision by |x| ~ 9, so clamp the tail to the exact saturated
12
- // value.
13
  if (x > 10.0) { return 1.0; }
14
  if (x < -10.0) { return -1.0; }
15
- // The hardware tanh also collapses to 0 (or a single ULP step) for tiny x,
16
- // because its internal e^2x rounds back to 1.0; tanh(x) == x to full f32
17
- // precision well before |x| ~ 1e-4 (the x^3/3 term is < 1 ULP), so return x
18
- // directly. Without this the small softplus tail of mish flushes to zero.
19
  if (x > -1.0e-4 && x < 1.0e-4) { return x; }
20
  return tanh(x);
21
- }
 
22
  fn mish_log1p_small_positive(y: f32) -> f32 {
23
  // log1p(y) via series for small positive y, avoiding the log(1.0 + y)
24
- // cancellation where 1.0 + y rounds back to 1.0 (mirrors the Softplus helper).
25
  return y * (1.0 + y * (-0.5 + y * (0.3333333333333333 + y * (-0.25 + y * 0.2))));
26
  }
27
  fn mish_softplus(v: f32) -> f32 {
28
  // Numerically stable softplus = log(1 + e^v). The naive log(1.0 + exp(v))
29
  // collapses to 0 once exp(v) underflows below the 1.0 + x rounding step
30
- // (v <~ -16), which zeros mish's small finite negative tail. Match the CPU
31
- // log1p form: series for the tiny tail, identity-shifted log otherwise.
32
  if (v > 20.0) { return v; }
33
  if (v < -8.0) { return mish_log1p_small_positive(exp(v)); }
34
  return log(1.0 + exp(-abs(v))) + max(v, 0.0);
@@ -38,15 +33,37 @@ fn mish_value(v: f32) -> f32 {
38
  return v * tanh_safe(softplus);
39
  }
40
 
 
 
 
 
 
41
  @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
42
- fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) nwg: vec3<u32>) {
43
  // 2D-folded flat index: gid.y carries the high bits when the element count exceeds the
44
- // maxComputeWorkgroupsPerDimension limit (the dispatch caps x and spills the rest into y).
45
- let i = gid.x + gid.y * nwg.x * {{ tunables.WORKGROUP_SIZE }}u;
 
 
 
 
 
 
 
 
 
 
 
 
46
  if (i >= params.count) {
47
  return;
48
  }
 
 
49
  let xv = x[i];
50
  let fv = vec4<f32>(xv);
51
  y[i] = {{ vectorScalar }}(vec4<f32>(mish_value(fv.x), mish_value(fv.y), mish_value(fv.z), mish_value(fv.w)));
 
 
 
52
  }
 
1
+ // Loads and stores vec4<T> while evaluating the selected unary operation per
2
+ // component.
 
 
 
3
  {{ env.wgsl.resourceDeclarations }}
4
 
5
+ {% macro emit_tanh_safe() %}
6
  fn tanh_safe(x: f32) -> f32 {
7
+ // tanh rounds to its saturated value for these tails in f32. Return that
8
+ // value directly, including for infinite input, before invoking the builtin.
 
 
9
  if (x > 10.0) { return 1.0; }
10
  if (x < -10.0) { return -1.0; }
11
+ // For tiny |x|, return x directly to preserve its sign and magnitude without
12
+ // relying on backend-specific builtin behavior near zero.
 
 
13
  if (x > -1.0e-4 && x < 1.0e-4) { return x; }
14
  return tanh(x);
15
+ }{% endmacro %}
16
+ {{ emit_tanh_safe() }}
17
  fn mish_log1p_small_positive(y: f32) -> f32 {
18
  // log1p(y) via series for small positive y, avoiding the log(1.0 + y)
19
+ // cancellation where 1.0 + y rounds back to 1.0.
20
  return y * (1.0 + y * (-0.5 + y * (0.3333333333333333 + y * (-0.25 + y * 0.2))));
21
  }
22
  fn mish_softplus(v: f32) -> f32 {
23
  // Numerically stable softplus = log(1 + e^v). The naive log(1.0 + exp(v))
24
  // collapses to 0 once exp(v) underflows below the 1.0 + x rounding step
25
+ // (v <~ -16), which zeros Mish's small finite negative tail. Use a series for
26
+ // the tiny tail and the identity-shifted logarithm otherwise.
27
  if (v > 20.0) { return v; }
28
  if (v < -8.0) { return mish_log1p_small_positive(exp(v)); }
29
  return log(1.0 + exp(-abs(v))) + max(v, 0.0);
 
33
  return v * tanh_safe(softplus);
34
  }
35
 
36
+ {% set vec4PerThread = vec4PerThread %}
37
+ {% if vec4PerThread > 1 %}
38
+ const ITEMS: u32 = {{ vec4PerThread }}u;
39
+ {% endif %}
40
+
41
  @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
42
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
43
  // 2D-folded flat index: gid.y carries the high bits when the element count exceeds the
44
+ // per-axis dispatch fold width (the dispatch caps x and spills the rest into y).
45
+ {% if vec4PerThread > 1 %}
46
+ // Each invocation walks ITEMS vec4 groups a span apart. Consecutive lanes
47
+ // access consecutive words on every step, while each lane can keep several
48
+ // independent loads in flight.
49
+ let tid = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ tunables.WORKGROUP_SIZE }}u;
50
+ let span = (params.count + ITEMS - 1u) / ITEMS;
51
+ for (var j = 0u; j < ITEMS; j = j + 1u) {
52
+ let i = tid + j * span;
53
+ if (i >= params.count) {
54
+ break;
55
+ }
56
+ {% else %}
57
+ let i = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ tunables.WORKGROUP_SIZE }}u;
58
  if (i >= params.count) {
59
  return;
60
  }
61
+ {% endif %}
62
+
63
  let xv = x[i];
64
  let fv = vec4<f32>(xv);
65
  y[i] = {{ vectorScalar }}(vec4<f32>(mish_value(fv.x), mish_value(fv.y), mish_value(fv.z), mish_value(fv.w)));
66
+ {% if vec4PerThread > 1 %}
67
+ }
68
+ {% endif %}
69
  }