Xenova HF Staff commited on
Commit
0f33bb4
·
verified ·
1 Parent(s): f6e5406

sync 91d990483a17

Browse files
README.md CHANGED
@@ -18,15 +18,15 @@ See the [ONNX `Cos` spec](https://onnx.ai/onnx/operators/onnx__Cos.html) for the
18
 
19
  ## Inputs
20
 
21
- | Name | Bind key | Logical dtype | Rank | Shape | Description | Presence |
22
  | --- | --- | --- | --- | --- | --- | --- |
23
- | `input` | `x` | `T` | — | — | Angles in radians whose cosine is computed elementwise. | required |
24
 
25
  ## Outputs
26
 
27
- | Name | Bind key | Logical dtype | Rank | Shape | Description | Presence |
28
  | --- | --- | --- | --- | --- | --- | --- |
29
- | `output` | `y` | `T` | same as `input` | same as `input` | Elementwise cosine of the input tensor. | required |
30
 
31
  ## Type constraints
32
 
@@ -36,7 +36,7 @@ See the [ONNX `Cos` spec](https://onnx.ai/onnx/operators/onnx__Cos.html) for the
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 `Cos` spec](https://onnx.ai/onnx/operators/onnx__Cos.html) for the
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` | `input` | `T` | — | — | Angles in radians whose cosine is computed elementwise. | required |
24
 
25
  ## Outputs
26
 
27
+ | Name | Upstream name | Logical dtype | Rank | Shape | Description | Presence |
28
  | --- | --- | --- | --- | --- | --- | --- |
29
+ | `y` | `output` | `T` | same as `x` | same as `x` | Elementwise cosine of the input tensor. | 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.Cos",
3
  "cases": [
4
  {
5
  "name": "1m_f32",
 
1
  {
 
2
  "cases": [
3
  {
4
  "name": "1m_f32",
build/webgpu/manifest.json CHANGED
@@ -2,30 +2,16 @@
2
  "domain": "ai.onnx",
3
  "name": "Cos",
4
  "sinceVersion": 7,
5
- "description": "Computes the cosine of each element in the input tensor. The output has the same shape and type as the input.",
6
- "inputs": [
7
- { "role": "input", "dtype": "T", "description": "Angles in radians whose cosine is computed elementwise." }
8
- ],
9
- "outputs": [
10
- {
11
- "role": "output",
12
- "dtype": "T",
13
- "rank": "ranks.input",
14
- "description": "Elementwise cosine of the input tensor.",
15
- "shape": "shapes.input"
16
- }
17
- ],
18
  "typeConstraints": { "T": ["float32", "float16"] },
19
- "args": {
20
- "x": { "kind": "tensor", "semantic": "input", "role": "input" },
21
- "y": { "kind": "tensor", "semantic": "output", "role": "output" }
22
- },
23
- "tunables": { "WORKGROUP_SIZE": 256 },
24
  "variants": [
25
  {
26
  "id": "same_layout_vec4",
 
27
  "when": ["numel(shapes.x) > 0", "numel(shapes.x) % 4 == 0", "numel(shapes.x) == numel(shapes.y)", "f16Ok(dtypes.T)"],
28
- "constants": {
29
  "scalar": "dtypes.T",
30
  "usesF16": "dtypes.T == \"f16\"",
31
  "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\""
@@ -34,47 +20,54 @@
34
  {
35
  "id": "main",
36
  "name": "Cos.vec4",
37
- "source": { "shader": "unary-vec4.wgsl.jinja", "inputs": { "op": "\"cos\"" } },
38
- "bindings": [
39
- { "name": "x", "arg": "x", "buffer": { "type": "read-only-storage" }, "elementType": "$vectorScalar" },
40
- { "name": "y", "arg": "y", "buffer": { "type": "storage" }, "elementType": "$vectorScalar" },
41
- {
42
- "name": "params",
43
- "semantic": "kernel.params",
44
- "buffer": { "type": "uniform" },
45
- "struct": {
46
- "name": "Params",
47
- "fields": [{ "name": "count", "type": "u32", "value": "numel(shapes.y) / 4" }]
48
- }
49
- }
50
- ],
51
- "dispatch": { "threads": "numel(shapes.y) / 4", "workgroupSize": "tunables.WORKGROUP_SIZE" }
52
  }
53
- ],
54
- "priority": 20
55
  },
56
  {
57
  "id": "elementwise",
58
  "when": ["numel(shapes.x) == numel(shapes.y)", "f16Ok(dtypes.T)"],
59
- "constants": { "scalar": "dtypes.T", "usesF16": "dtypes.T == \"f16\"" },
60
  "passes": [
61
  {
62
  "id": "main",
63
  "name": "Cos",
64
- "source": { "shader": "unary-scalar.wgsl.jinja", "inputs": { "op": "\"cos\"", "itemsPerInvocation": 4 } },
65
- "bindings": [
66
- { "name": "x", "arg": "x", "buffer": { "type": "read-only-storage" }, "elementType": "$scalar" },
67
- { "name": "y", "arg": "y", "buffer": { "type": "storage" }, "elementType": "$scalar" },
68
- {
69
- "name": "params",
70
- "semantic": "kernel.params",
71
- "buffer": { "type": "uniform" },
72
- "struct": { "name": "Params", "fields": [{ "name": "count", "type": "u32", "value": "numel(shapes.y)" }] }
73
- }
74
- ],
75
- "dispatch": { "threads": "ceilDiv(numel(shapes.y), 4)", "workgroupSize": "tunables.WORKGROUP_SIZE" }
76
  }
77
  ]
78
  }
79
- ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80
  }
 
2
  "domain": "ai.onnx",
3
  "name": "Cos",
4
  "sinceVersion": 7,
5
+ "inputs": { "x": { "onnx": "input", "dtype": "T" } },
6
+ "outputs": { "y": { "onnx": "output", "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": "Cos.vec4",
23
+ "shader": "unary-vec4.wgsl.jinja",
24
+ "derive": {
25
+ "op": "\"cos\"",
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": "Cos",
45
+ "shader": "unary-scalar.wgsl.jinja",
46
+ "derive": { "op": "\"cos\"", "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
+ "bindings": {
58
+ "x": { "buffer": "read-only-storage", "elementType": "$vectorScalar" },
59
+ "y": { "buffer": "storage", "elementType": "$vectorScalar" },
60
+ "params_unary": {
61
+ "buffer": "uniform",
62
+ "struct": [{ "name": "count", "type": "u32", "value": "numel(shapes.y) / 4" }],
63
+ "name": "params"
64
+ },
65
+ "x_2": { "buffer": "read-only-storage", "name": "x", "elementType": "$scalar" },
66
+ "y_2": { "buffer": "storage", "name": "y", "elementType": "$scalar" },
67
+ "params_2_unary": {
68
+ "buffer": "uniform",
69
+ "name": "params",
70
+ "struct": [{ "name": "count", "type": "u32", "value": "numel(shapes.y)" }]
71
+ }
72
+ }
73
  }
build/webgpu/metadata.json CHANGED
@@ -1,19 +1,22 @@
1
  {
2
  "name": "ai.onnx.Cos",
3
- "id": "_ai_onnx_cos_webgpu_cac3d94",
4
  "version": 1,
5
  "license": "Apache-2.0",
6
  "backend": { "type": "webgpu" },
7
  "digest": {
8
  "algorithm": "sha256",
9
  "files": {
10
- "bench.json": "aMFVQ+6rhvrMngby96efMmsldwq0VRJ9P0ICGr91lUY=",
11
- "manifest.json": "jHDCQwVeQCIpu3FV1PiYQD9ymkzPseA2K6yGVJf3wBk=",
12
- "test.json": "4f0wcy6ce8pie62+H13dy87SkZOfWv4TkyeqalgROmw=",
13
- "unary-scalar.wgsl.jinja": "VPyTY5cV6Zjidm4VpLmKEhg9VJDzG6N8+WZ/QJc+mlo=",
14
- "unary-vec4.wgsl.jinja": "1g3ktdx4TPnyT48hkpcIvkPRV8APmLzL3NESPtJPryA="
15
  }
16
  },
17
- "provenance": { "kernel": { "sha": "2e7068faf55e7f43df740015f6d1ee49391a41c5", "dirty": false } },
18
- "webgpu": { "manifestSpec": "1.0", "specialized": true, "opPath": "ops/ai.onnx.Cos" }
 
 
 
19
  }
 
1
  {
2
  "name": "ai.onnx.Cos",
3
+ "id": "_ai_onnx_cos_webgpu_b8cb940",
4
  "version": 1,
5
  "license": "Apache-2.0",
6
  "backend": { "type": "webgpu" },
7
  "digest": {
8
  "algorithm": "sha256",
9
  "files": {
10
+ "bench.json": "hZB5P81WgcOr8dQM++HnJ3SeV7se1ce5xBKtyIc7wAU=",
11
+ "manifest.json": "zfXDJZ9MmehTi2+hmO6LmOc6d0G/3MoiI/BBrdGNbec=",
12
+ "test.json": "nCOlkbGujYFc4xvpMBa8GIhuE9gsEvUwrN7hbkN056A=",
13
+ "unary-scalar.wgsl.jinja": "u8Oh5cH6SLO8tlNHu5CQY6yrH7ZuJnUDAV2p++iIGTc=",
14
+ "unary-vec4.wgsl.jinja": "h+TvunU/ZWWcEFtLA63R/pj1TbBBTYdsbcKWfpAWVbY="
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.Cos",
3
  "cases": [
4
  {
5
  "name": "f32_values",
@@ -30,7 +29,7 @@
30
  "name": "f32_large_argument_range_reduction_accuracy_gpu_gap",
31
  "skipGpu": {
32
  "category": "todo",
33
- "reason": "The current f32 large-argument range reduction differs from the CPU reference by about one ULP. A more accurate range reduction or software-extended precision could close this implementable gap."
34
  },
35
  "provenance": {
36
  "source": "onnxruntime/test/providers/cpu/math/element_wise_ops_test.cc",
@@ -53,12 +52,12 @@
53
  "name": "f32_large_argument_range_reduction_accuracy_scalar_gpu_gap",
54
  "skipGpu": {
55
  "category": "todo",
56
- "reason": "The current scalar f32 large-argument range reduction differs from the CPU reference by about one ULP. A more accurate range reduction or software-extended precision could close this implementable gap."
57
  },
58
  "provenance": {
59
  "source": "onnxruntime/test/providers/cpu/math/element_wise_ops_test.cc",
60
  "test": "MathOpTest.CosFloat",
61
- "notes": "Scalar-path companion for strict large-argument Cos range-reduction accuracy."
62
  },
63
  "inputs": {
64
  "x": {
 
1
  {
 
2
  "cases": [
3
  {
4
  "name": "f32_values",
 
29
  "name": "f32_large_argument_range_reduction_accuracy_gpu_gap",
30
  "skipGpu": {
31
  "category": "todo",
32
+ "reason": "The package's f32 large-argument range reduction differs from the correctly rounded expected value by about one ULP. More accurate range reduction or software-extended precision could close the gap."
33
  },
34
  "provenance": {
35
  "source": "onnxruntime/test/providers/cpu/math/element_wise_ops_test.cc",
 
52
  "name": "f32_large_argument_range_reduction_accuracy_scalar_gpu_gap",
53
  "skipGpu": {
54
  "category": "todo",
55
+ "reason": "The package's scalar f32 large-argument range reduction differs from the correctly rounded expected value by about one ULP. More accurate range reduction or software-extended precision could close the gap."
56
  },
57
  "provenance": {
58
  "source": "onnxruntime/test/providers/cpu/math/element_wise_ops_test.cc",
59
  "test": "MathOpTest.CosFloat",
60
+ "notes": "Unaligned scalar-path inputs exercise strict large-argument Cos range-reduction accuracy."
61
  },
62
  "inputs": {
63
  "x": {
build/webgpu/unary-scalar.wgsl.jinja CHANGED
@@ -1,21 +1,22 @@
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 %}
@@ -102,11 +103,10 @@ fn reduce_pio2_fast(ax: f32) -> Pio2 {
102
  {% set needPreciseTrigTwoPi = false %}
103
  // Backend-stable f32 sine/cosine core.
104
  //
105
- // Shader transcendental accuracy is implementation-defined, and some portable
106
- // backends are only accurate to roughly 1e-4. Each path retains the most accurate
107
- // available phase representation, reduces it to [-pi, pi], then uses these
108
- // polynomials. The half-pi core is degree 13 for sine and degree 12 for cosine;
109
- // truncation error is well below one f32 ULP over its documented interval.
110
  {% if needPreciseTrigCentered %}
111
  const PRECISE_TRIG_PI: f32 = 3.141592653589793;
112
  {% endif %}
 
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 %}
 
103
  {% set needPreciseTrigTwoPi = false %}
104
  // Backend-stable f32 sine/cosine core.
105
  //
106
+ // Shader transcendental accuracy is implementation-defined. Each path retains
107
+ // its available phase representation, reduces it to [-pi, pi], then evaluates
108
+ // explicit polynomials in a fixed order. The half-pi core is degree 13 for sine
109
+ // and degree 12 for cosine.
 
110
  {% if needPreciseTrigCentered %}
111
  const PRECISE_TRIG_PI: f32 = 3.141592653589793;
112
  {% endif %}
build/webgpu/unary-vec4.wgsl.jinja CHANGED
@@ -1,8 +1,5 @@
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
  {% macro emit_reduce_pio2() %}
@@ -87,11 +84,10 @@ fn reduce_pio2_fast(ax: f32) -> Pio2 {
87
  {% set needPreciseTrigTwoPi = false %}
88
  // Backend-stable f32 sine/cosine core.
89
  //
90
- // Shader transcendental accuracy is implementation-defined, and some portable
91
- // backends are only accurate to roughly 1e-4. Each path retains the most accurate
92
- // available phase representation, reduces it to [-pi, pi], then uses these
93
- // polynomials. The half-pi core is degree 13 for sine and degree 12 for cosine;
94
- // truncation error is well below one f32 ULP over its documented interval.
95
  {% if needPreciseTrigCentered %}
96
  const PRECISE_TRIG_PI: f32 = 3.141592653589793;
97
  {% endif %}
@@ -184,15 +180,37 @@ fn cos_accurate(x: f32) -> f32 {
184
  {{ emit_trig_reduction_support() }}
185
  {{ emit_cos_accurate() }}
186
 
 
 
 
 
 
187
  @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
188
- fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) nwg: vec3<u32>) {
189
  // 2D-folded flat index: gid.y carries the high bits when the element count exceeds the
190
- // maxComputeWorkgroupsPerDimension limit (the dispatch caps x and spills the rest into y).
191
- let i = gid.x + gid.y * nwg.x * {{ tunables.WORKGROUP_SIZE }}u;
 
 
 
 
 
 
 
 
 
 
 
 
192
  if (i >= params.count) {
193
  return;
194
  }
 
 
195
  let xv = x[i];
196
  let fv = vec4<f32>(xv);
197
  y[i] = {{ vectorScalar }}(vec4<f32>(cos_accurate(fv.x), cos_accurate(fv.y), cos_accurate(fv.z), cos_accurate(fv.w)));
 
 
 
198
  }
 
1
+ // Loads and stores vec4<T> while evaluating the selected unary operation per
2
+ // component.
 
 
 
3
  {{ env.wgsl.resourceDeclarations }}
4
 
5
  {% macro emit_reduce_pio2() %}
 
84
  {% set needPreciseTrigTwoPi = false %}
85
  // Backend-stable f32 sine/cosine core.
86
  //
87
+ // Shader transcendental accuracy is implementation-defined. Each path retains
88
+ // its available phase representation, reduces it to [-pi, pi], then evaluates
89
+ // explicit polynomials in a fixed order. The half-pi core is degree 13 for sine
90
+ // and degree 12 for cosine.
 
91
  {% if needPreciseTrigCentered %}
92
  const PRECISE_TRIG_PI: f32 = 3.141592653589793;
93
  {% endif %}
 
180
  {{ emit_trig_reduction_support() }}
181
  {{ emit_cos_accurate() }}
182
 
183
+ {% set vec4PerThread = vec4PerThread %}
184
+ {% if vec4PerThread > 1 %}
185
+ const ITEMS: u32 = {{ vec4PerThread }}u;
186
+ {% endif %}
187
+
188
  @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
189
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
190
  // 2D-folded flat index: gid.y carries the high bits when the element count exceeds the
191
+ // per-axis dispatch fold width (the dispatch caps x and spills the rest into y).
192
+ {% if vec4PerThread > 1 %}
193
+ // Each invocation walks ITEMS vec4 groups a span apart. Consecutive lanes
194
+ // access consecutive words on every step, while each lane can keep several
195
+ // independent loads in flight.
196
+ let tid = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ tunables.WORKGROUP_SIZE }}u;
197
+ let span = (params.count + ITEMS - 1u) / ITEMS;
198
+ for (var j = 0u; j < ITEMS; j = j + 1u) {
199
+ let i = tid + j * span;
200
+ if (i >= params.count) {
201
+ break;
202
+ }
203
+ {% else %}
204
+ let i = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ tunables.WORKGROUP_SIZE }}u;
205
  if (i >= params.count) {
206
  return;
207
  }
208
+ {% endif %}
209
+
210
  let xv = x[i];
211
  let fv = vec4<f32>(xv);
212
  y[i] = {{ vectorScalar }}(vec4<f32>(cos_accurate(fv.x), cos_accurate(fv.y), cos_accurate(fv.z), cos_accurate(fv.w)));
213
+ {% if vec4PerThread > 1 %}
214
+ }
215
+ {% endif %}
216
  }