Xenova HF Staff commited on
Commit
f2bf786
·
verified ·
1 Parent(s): 662da36

sync 6fdf6301e2bb

Browse files
README.md CHANGED
@@ -39,14 +39,14 @@ See the [ONNX `Tanh` spec](https://onnx.ai/onnx/operators/onnx__Tanh.html) for t
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
43
  - [`unary-scalar.wgsl.jinja`](build/webgpu/unary-scalar.wgsl.jinja)
44
  - [`unary-vec4.wgsl.jinja`](build/webgpu/unary-vec4.wgsl.jinja)
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.
@@ -60,5 +60,5 @@ Replace each `*Data` placeholder with a typed array containing the corresponding
60
  import { getKernel } from "@huggingface/kernels";
61
 
62
  const kernel = await getKernel("webgpu-kernels/ai.onnx.Tanh", { version: 1 });
63
- const { y } = await kernel({ x: { data: xData, shape: [] } });
64
  ```
 
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 cases
43
  - [`unary-scalar.wgsl.jinja`](build/webgpu/unary-scalar.wgsl.jinja)
44
  - [`unary-vec4.wgsl.jinja`](build/webgpu/unary-vec4.wgsl.jinja)
45
 
46
  ## Use with `@huggingface/kernels`
47
 
48
  ```sh
49
+ npm install --save-exact @huggingface/kernels@0.0.1-preview.3
50
  ```
51
 
52
  Required output shapes and logical data types are inferred from the supplied inputs and attributes; result tensors are allocated automatically.
 
60
  import { getKernel } from "@huggingface/kernels";
61
 
62
  const kernel = await getKernel("webgpu-kernels/ai.onnx.Tanh", { version: 1 });
63
+ const { y } = await kernel({ x: { data: xData, shape: [3] } });
64
  ```
build/webgpu/manifest.json CHANGED
@@ -11,20 +11,13 @@
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 ~ \">\""
18
- },
19
  "passes": [
20
  {
21
  "id": "main",
22
- "name": "Tanh.vec4",
23
  "shader": "unary-vec4.wgsl.jinja",
24
- "derive": {
25
- "op": "\"tanh\"",
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)",
@@ -37,14 +30,14 @@
37
  {
38
  "id": "scalar",
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": "Tanh",
45
  "shader": "unary-scalar.wgsl.jinja",
46
- "derive": { "op": "\"tanh\"", "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)",
@@ -56,34 +49,29 @@
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": "Tanh.vec4Bulk",
69
  "shader": "unary-vec4.wgsl.jinja",
70
  "derive": {
71
- "op": "\"tanh\"",
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": "Tanh.tail",
84
  "shader": "unary-scalar.wgsl.jinja",
85
- "derive": { "op": "\"tanh\"", "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)",
@@ -96,25 +84,15 @@
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
  }
 
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": { "scalar": "dtypes.T", "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"" },
 
 
 
 
15
  "passes": [
16
  {
17
  "id": "main",
18
+ "name": "Tanh.Vec4",
19
  "shader": "unary-vec4.wgsl.jinja",
20
+ "derive": { "vec4PerThread": "4 if numel(shapes.y) * dtypeBytes(tensorDtypes.y) <= 16777216 else 1" },
 
 
 
21
  "bindings": ["x", "y", "params_unary"],
22
  "dispatch": {
23
  "x": "min(ceilDiv((ceilDiv(numel(shapes.y) / 4, 4 if numel(shapes.y) * dtypeBytes(tensorDtypes.y) <= 16777216 else 1)), (tunables.WORKGROUP_SIZE)), 65535)",
 
30
  {
31
  "id": "scalar",
32
  "when": ["numel(shapes.x) == numel(shapes.y)", "f16Ok(dtypes.T)"],
33
+ "derive": { "scalar": "dtypes.T" },
34
  "passes": [
35
  {
36
  "id": "main",
37
  "name": "Tanh",
38
  "shader": "unary-scalar.wgsl.jinja",
39
+ "derive": { "itemsPerInvocation": 4 },
40
+ "bindings": ["x_scalar", "y_scalar", "params_2_unary"],
41
  "dispatch": {
42
  "x": "min(ceilDiv((ceilDiv(numel(shapes.y), 4)), (tunables.WORKGROUP_SIZE)), 65535)",
43
  "y": "ceilDiv(ceilDiv((ceilDiv(numel(shapes.y), 4)), (tunables.WORKGROUP_SIZE)), 65535)",
 
49
  {
50
  "id": "same_layout_vec4_tail",
51
  "priority": 19,
52
+ "when": ["numel(shapes.x) > 4", "numel(shapes.x) % 4 != 0", "numel(shapes.x) == numel(shapes.y)", "f16Ok(dtypes.T)", "ceilDiv(numel(shapes.y), 4) >= ceilDiv(tunables.MIN_TAIL_ELEMENTS | default(0), 4)"],
53
+ "derive": { "scalar": "dtypes.T", "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"" },
 
 
 
 
54
  "passes": [
55
  {
56
  "id": "bulk",
57
+ "name": "Tanh.Vec4Bulk",
58
  "shader": "unary-vec4.wgsl.jinja",
59
  "derive": {
60
+ "vec4PerThread": "tunables.TAIL_VEC4_PER_THREAD | default(4 if numel(shapes.y) * dtypeBytes(tensorDtypes.y) <= 16777216 else 1)"
 
61
  },
62
  "bindings": ["x", "y", "params_unary_tail"],
63
  "dispatch": {
64
+ "x": "min(ceilDiv((ceilDiv(floor(numel(shapes.y) / 4), vec4PerThread)), (tunables.WORKGROUP_SIZE)), 65535)",
65
+ "y": "ceilDiv(ceilDiv((ceilDiv(floor(numel(shapes.y) / 4), vec4PerThread)), (tunables.WORKGROUP_SIZE)), 65535)",
66
  "z": 1
67
  }
68
  },
69
  {
70
  "id": "tail",
71
+ "name": "Tanh.Tail",
72
  "shader": "unary-scalar.wgsl.jinja",
73
+ "derive": { "itemsPerInvocation": 4, "tailOnly": true },
74
+ "bindings": ["x_scalar", "y_scalar", "params_2_unary_tail"],
75
  "dispatch": {
76
  "x": "min(ceilDiv((1), (tunables.WORKGROUP_SIZE)), 65535)",
77
  "y": "ceilDiv(ceilDiv((1), (tunables.WORKGROUP_SIZE)), 65535)",
 
84
  "bindings": {
85
  "x": { "buffer": "read-only-storage", "elementType": "$vectorScalar" },
86
  "y": { "buffer": "storage", "elementType": "$vectorScalar" },
87
+ "params_unary": { "struct": [{ "name": "count", "type": "u32", "value": "numel(shapes.y) / 4" }], "name": "params" },
88
+ "x_scalar": { "buffer": "read-only-storage", "name": "x", "elementType": "$scalar" },
89
+ "y_scalar": { "buffer": "storage", "name": "y", "elementType": "$scalar" },
90
+ "params_2_unary": { "name": "params", "struct": [{ "name": "count", "type": "u32", "value": "numel(shapes.y)" }] },
 
 
 
 
 
 
 
 
91
  "params_unary_tail": {
 
92
  "struct": [{ "name": "count", "type": "u32", "value": "floor(numel(shapes.y) / 4)" }],
93
  "name": "params"
94
  },
95
  "params_2_unary_tail": {
 
96
  "name": "params",
97
  "struct": [{ "name": "count", "type": "u32", "value": "numel(shapes.y)" }]
98
  }
build/webgpu/metadata.json CHANGED
@@ -1,6 +1,6 @@
1
  {
2
  "name": "ai.onnx.Tanh",
3
- "id": "_ai_onnx_tanh_webgpu_e799420",
4
  "version": 1,
5
  "license": "Apache-2.0",
6
  "backend": { "type": "webgpu" },
@@ -8,15 +8,15 @@
8
  "algorithm": "sha256",
9
  "files": {
10
  "bench.json": "RnXzzrufm42zv2CsF1m967BosaRKOF9QYe3VV219CEs=",
11
- "manifest.json": "IXlIZTqkPYG1aflezhJhBZhuofy7SJTnUQ1Gqdrpkc8=",
12
- "test.json": "2BDGX+xy3u+uSe/U8FVXTWYznJzeSBv8BczW/s06ixI=",
13
- "unary-scalar.wgsl.jinja": "HaNuP3u6dDbIs+NJvsK1lzzK6QWnlWLHHc0T/qho7g4=",
14
- "unary-vec4.wgsl.jinja": "pz7xSOlVXRtQ1i7OpGZzVYi+x0cY/DgX73TGGFNOXmM="
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
  "scalar": ["unary-scalar.wgsl.jinja"],
 
1
  {
2
  "name": "ai.onnx.Tanh",
3
+ "id": "_ai_onnx_tanh_webgpu_80179bf",
4
  "version": 1,
5
  "license": "Apache-2.0",
6
  "backend": { "type": "webgpu" },
 
8
  "algorithm": "sha256",
9
  "files": {
10
  "bench.json": "RnXzzrufm42zv2CsF1m967BosaRKOF9QYe3VV219CEs=",
11
+ "manifest.json": "aaVwij2m5ZcwSI0h75dtzCnu/AhdoTzwKbAUZZxZz9k=",
12
+ "test.json": "EM9ZVdzYCk7XIYovEuL1dePwuWNWOAZs8oaWeuA/2Rc=",
13
+ "unary-scalar.wgsl.jinja": "ltP+E80gaUwt38kWaUo2bwPm+Uk3l1K9e9U+DmxBx28=",
14
+ "unary-vec4.wgsl.jinja": "4ccLQCNyFZb8+HLGq1UKpUKV+gEv+6VmV/bWC8h58c0="
15
  }
16
  },
17
+ "provenance": { "kernel": { "sha": "6fdf6301e2bbcc2f03bf1eaf493b7ad55ef33afc", "dirty": false } },
18
  "webgpu": {
19
+ "manifestSpec": "2.1",
20
  "variants": {
21
  "same_layout_vec4": ["unary-vec4.wgsl.jinja"],
22
  "scalar": ["unary-scalar.wgsl.jinja"],
build/webgpu/test.json CHANGED
@@ -199,7 +199,7 @@
199
  {
200
  "name": "f16_scalar_fallback_saturation_and_tiny",
201
  "provenance": {
202
- "source": "repository-authored",
203
  "notes": "Seven float16 elements exercise scalar processing. Exact float16 inputs cover saturation at magnitude 10 and the tiny-normal identity branch at 2^-13 without relying on subnormal preservation."
204
  },
205
  "inputs": {
@@ -214,7 +214,7 @@
214
  {
215
  "name": "f16_scalar_fallback_midrange",
216
  "provenance": {
217
- "source": "repository-authored",
218
  "notes": "Second odd-numel (5%4!=0) f16 scalar-fallback case over the steep mid-range of tanh where output precision matters most; complements the saturation/tiny case. f16-exact inputs (quarter-fractions)."
219
  },
220
  "inputs": {
 
199
  {
200
  "name": "f16_scalar_fallback_saturation_and_tiny",
201
  "provenance": {
202
+ "source": "synthetic",
203
  "notes": "Seven float16 elements exercise scalar processing. Exact float16 inputs cover saturation at magnitude 10 and the tiny-normal identity branch at 2^-13 without relying on subnormal preservation."
204
  },
205
  "inputs": {
 
214
  {
215
  "name": "f16_scalar_fallback_midrange",
216
  "provenance": {
217
+ "source": "synthetic",
218
  "notes": "Second odd-numel (5%4!=0) f16 scalar-fallback case over the steep mid-range of tanh where output precision matters most; complements the saturation/tiny case. f16-exact inputs (quarter-fractions)."
219
  },
220
  "inputs": {
build/webgpu/unary-scalar.wgsl.jinja CHANGED
@@ -22,16 +22,11 @@ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
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.
@@ -41,9 +36,9 @@ fn tanh_safe(x: f32) -> f32 {
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
  {{ flat_tail_open() }}
47
  y[i] = {{ scalar }}(tanh_safe(f32(x[i])));
48
- {{ flat_tail_close() -}}
49
  }
 
22
  for (var i = begin; i < end; i = i + 1u) {
23
  {%- endmacro %}
24
  {% macro flat_tail_close() %}
25
+ }{% endmacro %}
26
+ // Scalar unary elementwise kernel: applies the selected operation, with the
27
+ // numerical helpers it uses, to each element.
 
 
 
 
 
28
  {{ env.wgsl.resourceDeclarations }}
29
+
30
  fn tanh_safe(x: f32) -> f32 {
31
  // tanh rounds to its saturated value for these tails in f32. Return that
32
  // value directly, including for infinite input, before invoking the builtin.
 
36
  // relying on backend-specific builtin behavior near zero.
37
  if (x > -1.0e-4 && x < 1.0e-4) { return x; }
38
  return tanh(x);
39
+ }
40
+
41
  {{ flat_tail_open() }}
42
  y[i] = {{ scalar }}(tanh_safe(f32(x[i])));
43
+ {{ flat_tail_close() }}
44
  }
build/webgpu/unary-vec4.wgsl.jinja CHANGED
@@ -2,7 +2,6 @@
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.
@@ -12,36 +11,44 @@ fn tanh_safe(x: f32) -> f32 {
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
 
18
- {% set vec4PerThread = vec4PerThread %}
 
 
 
 
 
 
 
 
 
19
  {% if vec4PerThread > 1 %}
20
  const ITEMS: u32 = {{ vec4PerThread }}u;
21
- {% endif %}
22
 
 
23
  @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
24
  fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
25
- // 2D-folded flat index: gid.y carries the high bits when the element count exceeds the
26
- // per-axis dispatch fold width (the dispatch caps x and spills the rest into y).
27
  {% if vec4PerThread > 1 %}
28
  // Each invocation walks ITEMS vec4 groups a span apart. Consecutive lanes
29
  // access consecutive words on every step, while each lane can keep several
30
  // independent loads in flight.
31
- let tid = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ tunables.WORKGROUP_SIZE }}u;
32
  let span = (params.count + ITEMS - 1u) / ITEMS;
 
 
 
 
 
 
33
  for (var j = 0u; j < ITEMS; j = j + 1u) {
34
  let i = tid + j * span;
35
  if (i >= params.count) {
36
  break;
37
  }
38
  {% else %}
39
- let i = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ tunables.WORKGROUP_SIZE }}u;
40
- if (i >= params.count) {
41
- return;
42
- }
43
  {% endif %}
44
-
45
  let xv = x[i];
46
  let fv = vec4<f32>(xv);
47
  y[i] = {{ vectorScalar }}(vec4<f32>(tanh_safe(fv.x), tanh_safe(fv.y), tanh_safe(fv.z), tanh_safe(fv.w)));
 
2
  // component.
3
  {{ env.wgsl.resourceDeclarations }}
4
 
 
5
  fn tanh_safe(x: f32) -> f32 {
6
  // tanh rounds to its saturated value for these tails in f32. Return that
7
  // value directly, including for infinite input, before invoking the builtin.
 
11
  // relying on backend-specific builtin behavior near zero.
12
  if (x > -1.0e-4 && x < 1.0e-4) { return x; }
13
  return tanh(x);
14
+ }
 
15
 
16
+ {% macro flat_index_2d(workgroupSize, name="i", bound="params.count", guardInline=false) %}
17
+ {% set wgTerm = workgroupSize ~ "u" if workgroupSize is number else workgroupSize %}
18
+ // 2D-folded flat index: gid.y carries the high bits past the dispatch's
19
+ // per-axis workgroup fold width.
20
+ {% if bound == "" %}
21
+ let {{ name }} = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ wgTerm }};{% else %}
22
+ let {{ name }} = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ wgTerm }};
23
+ if ({{ name }} >= {{ bound }}) {
24
+ return;
25
+ }{% endif %}{% endmacro %}
26
  {% if vec4PerThread > 1 %}
27
  const ITEMS: u32 = {{ vec4PerThread }}u;
 
28
 
29
+ {% endif %}
30
  @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
31
  fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
 
 
32
  {% if vec4PerThread > 1 %}
33
  // Each invocation walks ITEMS vec4 groups a span apart. Consecutive lanes
34
  // access consecutive words on every step, while each lane can keep several
35
  // independent loads in flight.
36
+ {{ flat_index_2d(tunables.WORKGROUP_SIZE, "tid", "") }}
37
  let span = (params.count + ITEMS - 1u) / ITEMS;
38
+ // Lanes at or past span exist only because the dispatch rounds up to whole
39
+ // workgroups. Lane span + k would start on lane k's second group and rewrite
40
+ // up to ITEMS - 1 groups another lane already stored.
41
+ if (tid >= span) {
42
+ return;
43
+ }
44
  for (var j = 0u; j < ITEMS; j = j + 1u) {
45
  let i = tid + j * span;
46
  if (i >= params.count) {
47
  break;
48
  }
49
  {% else %}
50
+ {{ flat_index_2d(tunables.WORKGROUP_SIZE) }}
 
 
 
51
  {% endif %}
 
52
  let xv = x[i];
53
  let fv = vec4<f32>(xv);
54
  y[i] = {{ vectorScalar }}(vec4<f32>(tanh_safe(fv.x), tanh_safe(fv.y), tanh_safe(fv.z), tanh_safe(fv.w)));