Xenova HF Staff commited on
Commit
f6e5406
·
verified ·
1 Parent(s): 77b755a

sync 2e7068faf55e

Browse files
README.md CHANGED
@@ -1,3 +1,60 @@
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.Cos
10
+
11
+ `ai.onnx` · standard ONNX operator · ONNX opset ≥ 7
12
+
13
+ ## Description
14
+
15
+ Computes the cosine of each element in the input tensor. The output has the same shape and type as the input.
16
+
17
+ See the [ONNX `Cos` spec](https://onnx.ai/onnx/operators/onnx__Cos.html) for the reference semantics.
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
+
33
+ | Variable | Allowed dtypes |
34
+ | --- | --- |
35
+ | `T` | `float32`, `float16` |
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
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
+ 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
+
55
+ ```js
56
+ import { getKernel } from "@huggingface/kernels";
57
+
58
+ const kernel = await getKernel("webgpu-kernels/ai.onnx.Cos", { version: 1 });
59
+ const { y } = await kernel({ x: { data: xData, shape: [] } });
60
+ ```
build/webgpu/bench.json ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "op": "ai.onnx.Cos",
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": "cos-f32-1m-vec4-healthy",
11
+ "preset": "smoke",
12
+ "vars": { "dtype": "float32", "count": 1048576 },
13
+ "inputs": { "x": { "shape": [1048576], "dtype": "float32", "dist": "normal", "seed": 8801, "scale": 3 } },
14
+ "outputs": { "y": { "shape": [1048576], "dtype": "float32" } },
15
+ "bench": {
16
+ "primary": true,
17
+ "metrics": [{ "type": "bandwidth", "value": "args.count * dtypeBytes(args.dtype) * 2" }]
18
+ }
19
+ },
20
+ {
21
+ "name": "cos-f32-scalar-path-nonaligned",
22
+ "preset": "smoke",
23
+ "vars": { "dtype": "float32", "count": 1048577 },
24
+ "inputs": { "x": { "shape": [1048577], "dtype": "float32", "dist": "normal", "seed": 8802, "scale": 3 } },
25
+ "outputs": { "y": { "shape": [1048577], "dtype": "float32" } },
26
+ "bench": { "metrics": [{ "type": "bandwidth", "value": "args.count * dtypeBytes(args.dtype) * 2" }] }
27
+ },
28
+ {
29
+ "name": "cos-f16-1m-vec4",
30
+ "preset": "smoke",
31
+ "vars": { "dtype": "float16", "count": 1048576 },
32
+ "inputs": { "x": { "shape": [1048576], "dtype": "float16", "dist": "normal", "seed": 8803, "scale": 3 } },
33
+ "outputs": { "y": { "shape": [1048576], "dtype": "float16" } },
34
+ "bench": { "metrics": [{ "type": "bandwidth", "value": "args.count * dtypeBytes(args.dtype) * 2" }] }
35
+ },
36
+ {
37
+ "name": "cos-f16-scalar-path-nonaligned",
38
+ "preset": "smoke",
39
+ "vars": { "dtype": "float16", "count": 1048577 },
40
+ "inputs": { "x": { "shape": [1048577], "dtype": "float16", "dist": "normal", "seed": 8804, "scale": 3 } },
41
+ "outputs": { "y": { "shape": [1048577], "dtype": "float16" } },
42
+ "bench": { "metrics": [{ "type": "bandwidth", "value": "args.count * dtypeBytes(args.dtype) * 2" }] }
43
+ }
44
+ ]
45
+ }
build/webgpu/manifest.json ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
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 ~ \">\""
32
+ },
33
+ "passes": [
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
+ }
build/webgpu/metadata.json ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ }
build/webgpu/test.json ADDED
@@ -0,0 +1,238 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "op": "ai.onnx.Cos",
3
+ "cases": [
4
+ {
5
+ "name": "f32_values",
6
+ "inputs": {
7
+ "x": {
8
+ "dtype": "float32",
9
+ "shape": [6],
10
+ "data": { "kind": "values", "values": [-3.0, -1.0, 0.0, 0.5, 1.0, 3.0] }
11
+ }
12
+ },
13
+ "outputs": { "y": { "dtype": "float32", "shape": [6], "tolerance": 0.000001 } }
14
+ },
15
+ {
16
+ "name": "large_argument_range_reduction",
17
+ "inputs": {
18
+ "x": {
19
+ "dtype": "float32",
20
+ "shape": [8],
21
+ "data": {
22
+ "kind": "values",
23
+ "values": [1000000.0, 10000000.0, 10000000000000.0, 100000000000000000000.0, -1000000000000000.0, -123456.78, 314159.265, 2500000000.0]
24
+ }
25
+ }
26
+ },
27
+ "outputs": { "y": { "dtype": "float32", "shape": [8], "tolerance": 0.0001 } }
28
+ },
29
+ {
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",
37
+ "test": "MathOpTest.CosFloat",
38
+ "notes": "Finite large arguments stress Cos range reduction; this uses a stricter tolerance than the broad smoke case."
39
+ },
40
+ "inputs": {
41
+ "x": {
42
+ "dtype": "float32",
43
+ "shape": [8],
44
+ "data": {
45
+ "kind": "values",
46
+ "values": [1000000.0, 10000000.0, 10000000000000.0, 100000000000000000000.0, -1000000000000000.0, -123456.78, 314159.265, 2500000000.0]
47
+ }
48
+ }
49
+ },
50
+ "outputs": { "y": { "dtype": "float32", "shape": [8], "tolerance": 1e-8, "relTolerance": 0 } }
51
+ },
52
+ {
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": {
65
+ "dtype": "float32",
66
+ "shape": [7],
67
+ "data": {
68
+ "kind": "values",
69
+ "values": [1000000.0, 10000000.0, 10000000000000.0, -1000000000000000.0, -123456.78, 314159.265, 2500000000.0]
70
+ }
71
+ }
72
+ },
73
+ "outputs": { "y": { "dtype": "float32", "shape": [7], "tolerance": 1e-8, "relTolerance": 0 } }
74
+ },
75
+ {
76
+ "name": "special_values_infinity_nan",
77
+ "inputs": {
78
+ "x": {
79
+ "dtype": "float32",
80
+ "shape": [5],
81
+ "data": { "kind": "values", "values": ["-Infinity", 0.0, 0.0, "Infinity", "NaN"] }
82
+ }
83
+ },
84
+ "outputs": { "y": { "dtype": "float32", "shape": [5], "tolerance": 0.000001, "allowNaN": true } }
85
+ },
86
+ {
87
+ "name": "rank0_scalar",
88
+ "inputs": { "x": { "dtype": "float32", "shape": [], "data": { "kind": "values", "values": [1.25] } } },
89
+ "outputs": { "y": { "dtype": "float32", "shape": [], "tolerance": 0.000001 } }
90
+ },
91
+ {
92
+ "name": "ort_float_opset22",
93
+ "provenance": {
94
+ "source": "onnxruntime/test/providers/cpu/math/element_wise_ops_test.cc",
95
+ "test": "MathOpTest.Cos_Opset22"
96
+ },
97
+ "inputs": {
98
+ "x": { "dtype": "float32", "shape": [4], "data": { "kind": "values", "values": [1.1, -1.1, 2.2, -2.2] } }
99
+ },
100
+ "outputs": {
101
+ "y": {
102
+ "dtype": "float32",
103
+ "shape": [4],
104
+ "tolerance": 0.000001,
105
+ "data": {
106
+ "kind": "values",
107
+ "values": [0.4535961151123047, 0.4535961151123047, -0.5885010957717896, -0.5885010957717896]
108
+ }
109
+ }
110
+ }
111
+ },
112
+ {
113
+ "name": "onnx_backend_example",
114
+ "provenance": {
115
+ "source": "cmake/external/onnx/onnx/backend/test/data/node/test_cos_example",
116
+ "test": "test_cos_example"
117
+ },
118
+ "inputs": { "x": { "dtype": "float32", "shape": [3], "data": { "kind": "values", "values": [-1.0, 0.0, 1.0] } } },
119
+ "outputs": { "y": { "dtype": "float32", "shape": [3], "tolerance": 0.000001 } }
120
+ },
121
+ {
122
+ "name": "onnx_backend_cos",
123
+ "provenance": { "source": "cmake/external/onnx/onnx/backend/test/data/node/test_cos" },
124
+ "inputs": {
125
+ "x": {
126
+ "dtype": "float32",
127
+ "shape": [3, 4, 5],
128
+ "data": {
129
+ "kind": "values",
130
+ "values": [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]
131
+ }
132
+ }
133
+ },
134
+ "outputs": { "y": { "dtype": "float32", "shape": [3, 4, 5], "tolerance": 0.00001 } }
135
+ },
136
+ {
137
+ "name": "onnx_backend_cos_example",
138
+ "provenance": { "source": "cmake/external/onnx/onnx/backend/test/data/node/test_cos_example" },
139
+ "inputs": { "x": { "dtype": "float32", "shape": [3], "data": { "kind": "values", "values": [-1.0, 0.0, 1.0] } } },
140
+ "outputs": { "y": { "dtype": "float32", "shape": [3], "tolerance": 0.00001 } }
141
+ },
142
+ {
143
+ "name": "vec4_f32_nonfinite",
144
+ "inputs": {
145
+ "x": {
146
+ "dtype": "float32",
147
+ "shape": [8],
148
+ "data": { "kind": "values", "values": ["-Infinity", -1.0, 0.0, 0.5, 1.0, "Infinity", "NaN", 2.0] }
149
+ }
150
+ },
151
+ "outputs": { "y": { "dtype": "float32", "shape": [8], "tolerance": 0.000001, "allowNaN": true } }
152
+ },
153
+ {
154
+ "name": "vec4_f16_lanes",
155
+ "inputs": {
156
+ "x": {
157
+ "dtype": "float16",
158
+ "shape": [16],
159
+ "data": {
160
+ "kind": "values",
161
+ "values": [-100.0, -50.0, -20.0, -10.0, -6.0, -3.0, -1.5, -0.5, 0.0, 0.5, 1.5, 3.0, 6.0, 10.0, 50.0, 100.0]
162
+ }
163
+ }
164
+ },
165
+ "outputs": { "y": { "dtype": "float16", "shape": [16], "tolerance": 0.001, "relTolerance": 0.002 } }
166
+ },
167
+ {
168
+ "name": "empty_input_zero_dim",
169
+ "inputs": { "x": { "dtype": "float32", "shape": [0], "data": { "kind": "values", "values": [] } } },
170
+ "outputs": { "y": { "dtype": "float32", "shape": [0], "tolerance": 0 } }
171
+ },
172
+ {
173
+ "name": "f16_scalar_path_odd_numel",
174
+ "inputs": {
175
+ "x": {
176
+ "dtype": "float16",
177
+ "shape": [7],
178
+ "data": { "kind": "values", "values": [-2.0, -1.0, -0.5, 0.0, 0.5, 1.0, 2.0] }
179
+ }
180
+ },
181
+ "outputs": { "y": { "dtype": "float16", "shape": [7], "tolerance": 0.001, "relTolerance": 0.002 } }
182
+ },
183
+ {
184
+ "name": "f16_realistic_2d_finite",
185
+ "inputs": {
186
+ "x": {
187
+ "dtype": "float16",
188
+ "shape": [2, 64],
189
+ "data": {
190
+ "kind": "values",
191
+ "values": [-3.140625, -3.046875, -2.953125, -2.859375, -2.765625, -2.671875, -2.578125, -2.484375, -2.390625, -2.296875, -2.203125, -2.109375, -2.015625, -1.921875, -1.828125, -1.734375, -1.640625, -1.546875, -1.453125, -1.359375, -1.265625, -1.171875, -1.078125, -0.984375, -0.890625, -0.796875, -0.703125, -0.609375, -0.515625, -0.421875, -0.328125, -0.234375, -0.140625, -0.046875, 0.046875, 0.140625, 0.234375, 0.328125, 0.421875, 0.515625, 0.609375, 0.703125, 0.796875, 0.890625, 0.984375, 1.078125, 1.171875, 1.265625, 1.359375, 1.453125, 1.546875, 1.640625, 1.734375, 1.828125, 1.921875, 2.015625, 2.109375, 2.203125, 2.296875, 2.390625, 2.484375, 2.578125, 2.671875, 2.765625, -3.0, -2.5, -2.0, -1.5, -1.0, -0.5, 0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 0.25, 0.75, 1.25, 1.75, 2.25, 2.75, -0.25, -0.75, -1.25, -1.75, -2.25, -2.75, 0.125, 0.375, 0.625, 0.875, 1.125, 1.375, 1.625, 1.875, 2.125, 2.375, 2.625, 2.875, -0.125, -0.375, -0.625, -0.875, -1.125, -1.375, -1.625, -1.875, -2.125, -2.375, -2.625, -2.875, 3.0, -3.0, 1.0, -1.0, 0.5, -0.5, 2.0, -2.0, 1.5, -1.5, 2.5, -2.5, 0.0, 0.0, 3.0]
192
+ }
193
+ }
194
+ },
195
+ "outputs": { "y": { "dtype": "float16", "shape": [2, 64], "tolerance": 0.001, "relTolerance": 0.002 } }
196
+ },
197
+ {
198
+ "name": "f32_scalar_path_finite_odd_numel",
199
+ "inputs": {
200
+ "x": { "dtype": "float32", "shape": [5], "data": { "kind": "values", "values": [-2.5, -0.75, 0.0, 1.25, 3.5] } }
201
+ },
202
+ "outputs": { "y": { "dtype": "float32", "shape": [5], "tolerance": 0.000001 } }
203
+ },
204
+ {
205
+ "name": "reduce_threshold_boundary_10000",
206
+ "inputs": {
207
+ "x": {
208
+ "dtype": "float32",
209
+ "shape": [4],
210
+ "data": { "kind": "values", "values": [9999.0, 10000.0, -9999.0, -10000.0] }
211
+ }
212
+ },
213
+ "outputs": { "y": { "dtype": "float32", "shape": [4], "tolerance": 0.001 } }
214
+ },
215
+ {
216
+ "name": "f16_cos_range_finite_and_nonfinite_vec4",
217
+ "inputs": {
218
+ "x": {
219
+ "dtype": "float16",
220
+ "shape": [8],
221
+ "data": { "kind": "values", "values": ["-Infinity", -100.0, -1.0, 0.0, 1.0, 100.0, "Infinity", "NaN"] }
222
+ }
223
+ },
224
+ "outputs": { "y": { "dtype": "float16", "shape": [8], "tolerance": 0.002, "allowNaN": true } }
225
+ },
226
+ {
227
+ "name": "f32_vec4_sustained_1024",
228
+ "inputs": {
229
+ "x": {
230
+ "dtype": "float32",
231
+ "shape": [1024],
232
+ "data": { "kind": "linspace", "start": -6.283185, "end": 6.283185 }
233
+ }
234
+ },
235
+ "outputs": { "y": { "dtype": "float32", "shape": [1024], "tolerance": 0.00001 } }
236
+ }
237
+ ]
238
+ }
build/webgpu/unary-scalar.wgsl.jinja ADDED
@@ -0,0 +1,205 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ {% macro emit_reduce_pio2() %}
24
+ fn reduce_pio2(ax: f32) -> Pio2 {
25
+ let ix = bitcast<u32>(ax);
26
+ let e = (ix >> 23u) & 0xffu;
27
+ let mant = (ix & 0x7fffffu) | 0x800000u; // 24-bit mantissa
28
+ let p = i32(e) - 150; // ax = mant * 2^p
29
+ // 96-bit accumulator a2:a1:a0 holding (ax * 2/pi mod 4) * 2^94.
30
+ var a0 = 0u; var a1 = 0u; var a2 = 0u;
31
+ for (var k = 0u; k < 14u; k = k + 1u) {
32
+ let shift = p - 24 * (i32(k) + 1) + 94;
33
+ if (shift <= -48 || shift >= 96) { continue; }
34
+ // prod = mant * TWO_OVER_PI[k] (<= 48 bits) -> phi32:plo
35
+ let t = TWO_OVER_PI[k];
36
+ let mlo = mant & 0xffffu; let mhi = mant >> 16u;
37
+ let tlo = t & 0xffffu; let thi = t >> 16u;
38
+ let ll = mlo * tlo;
39
+ let mid = mlo * thi + mhi * tlo; // <= 25 bits, no overflow
40
+ let hh = mhi * thi;
41
+ let plo = ll + ((mid & 0xffffu) << 16u);
42
+ let carry = select(0u, 1u, plo < ll);
43
+ let phi32 = hh + (mid >> 16u) + carry; // <= 16 bits
44
+ // Place (phi32:plo) << shift as a 64-bit value (v1:v0) at offset off >= 0.
45
+ var v0 = 0u; var v1 = 0u; var off = 0;
46
+ if (shift < 0) {
47
+ let s = u32(-shift); // 1..47
48
+ if (s < 32u) {
49
+ v0 = (plo >> s) | (phi32 << (32u - s));
50
+ v1 = phi32 >> s;
51
+ } else {
52
+ v0 = phi32 >> (s - 32u);
53
+ v1 = 0u;
54
+ }
55
+ off = 0;
56
+ } else {
57
+ v0 = plo; v1 = phi32; off = shift; // 0..95
58
+ }
59
+ // Spread (v1:v0) across 96-bit limbs starting at bit `off`.
60
+ let li = off / 32; let b = u32(off % 32);
61
+ var w0 = 0u; var w1 = 0u; var w2 = 0u;
62
+ if (b == 0u) { w0 = v0; w1 = v1; w2 = 0u; }
63
+ else {
64
+ w0 = v0 << b;
65
+ w1 = (v1 << b) | (v0 >> (32u - b));
66
+ w2 = v1 >> (32u - b);
67
+ }
68
+ var t0 = 0u; var t1 = 0u; var t2 = 0u;
69
+ if (li == 0) { t0 = w0; t1 = w1; t2 = w2; }
70
+ else if (li == 1) { t1 = w0; t2 = w1; }
71
+ else { t2 = w0; }
72
+ // 96-bit add acc += t (bits above 2^96 are multiples of 4, so discarded).
73
+ let n0 = a0 + t0; let c0 = select(0u, 1u, n0 < a0);
74
+ let n1a = a1 + t1; let c1a = select(0u, 1u, n1a < a1);
75
+ let n1 = n1a + c0; let c1b = select(0u, 1u, n1 < n1a);
76
+ let n2 = a2 + t2 + c1a + c1b;
77
+ a0 = n0; a1 = n1; a2 = n2;
78
+ }
79
+ var n = a2 >> 30u; // integer part mod 4
80
+ var phi = f32(a2 & 0x3fffffffu) * (1.0 / 1073741824.0)
81
+ + f32(a1) * (1.0 / 4611686018427387904.0); // fraction in [0,1)
82
+ if (phi >= 0.5) { phi = phi - 1.0; n = n + 1u; }
83
+ var out: Pio2;
84
+ out.octant = n & 3u;
85
+ out.r = phi * PIO2_F;
86
+ return out;
87
+ }{% endmacro %}
88
+ {% macro emit_reduce_pio2_fast() %}
89
+ fn reduce_pio2_fast(ax: f32) -> Pio2 {
90
+ // Cody-Waite reduction for ordinary magnitudes. Splitting pi/2 keeps the
91
+ // residual accurate even when n is thousands; larger inputs use Payne-Hanek.
92
+ let nFloat = floor(fma(ax, INV_PIO2_F, 0.5));
93
+ var residual = fma(-nFloat, PIO2_HI_F, ax);
94
+ residual = fma(-nFloat, PIO2_LO_F, residual);
95
+ var out: Pio2;
96
+ out.octant = u32(nFloat) & 3u;
97
+ out.r = residual;
98
+ return out;
99
+ }{% endmacro %}
100
+ {% macro emit_trig_reduction_support() %}
101
+ {% set needPreciseTrigCentered = false %}
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 %}
113
+ {% if needPreciseTrigTwoPi %}
114
+ const PRECISE_TRIG_TWO_PI: f32 = 6.283185307179586;
115
+ {% endif %}
116
+ {% if needPreciseTrigCentered %}
117
+ const PRECISE_TRIG_HALF_PI: f32 = 1.5707963267948966;
118
+ {% endif %}
119
+
120
+ // Input must be in [-pi/2, pi/2]. Returns (cos(x), sin(x)).
121
+ fn precise_sincos_half_pi(x: f32) -> vec2<f32> {
122
+ let x2 = x * x;
123
+
124
+ var sinPolynomial = 1.6059043836821613e-10;
125
+ sinPolynomial = fma(sinPolynomial, x2, -2.505210838544172e-8);
126
+ sinPolynomial = fma(sinPolynomial, x2, 2.7557319223985893e-6);
127
+ sinPolynomial = fma(sinPolynomial, x2, -1.984126984126984e-4);
128
+ sinPolynomial = fma(sinPolynomial, x2, 8.333333333333333e-3);
129
+ sinPolynomial = fma(sinPolynomial, x2, -1.6666666666666666e-1);
130
+ let sine = x * fma(sinPolynomial, x2, 1.0);
131
+
132
+ var cosPolynomial = 2.08767569878681e-9;
133
+ cosPolynomial = fma(cosPolynomial, x2, -2.755731922398589e-7);
134
+ cosPolynomial = fma(cosPolynomial, x2, 2.48015873015873e-5);
135
+ cosPolynomial = fma(cosPolynomial, x2, -1.388888888888889e-3);
136
+ cosPolynomial = fma(cosPolynomial, x2, 4.1666666666666664e-2);
137
+ cosPolynomial = fma(cosPolynomial, x2, -5.0e-1);
138
+ let cosine = fma(cosPolynomial, x2, 1.0);
139
+
140
+ return vec2<f32>(cosine, sine);
141
+ }
142
+ {% if needPreciseTrigCentered %}
143
+
144
+ // Input must be in [-pi, pi]. Returns (cos(x), sin(x)).
145
+ fn precise_sincos_centered(x: f32) -> vec2<f32> {
146
+ var folded = x;
147
+ var cosineSign = 1.0;
148
+ if (folded > PRECISE_TRIG_HALF_PI) {
149
+ folded = PRECISE_TRIG_PI - folded;
150
+ cosineSign = -1.0;
151
+ } else if (folded < -PRECISE_TRIG_HALF_PI) {
152
+ folded = -PRECISE_TRIG_PI - folded;
153
+ cosineSign = -1.0;
154
+ }
155
+ let value = precise_sincos_half_pi(folded);
156
+ return vec2<f32>(cosineSign * value.x, value.y);
157
+ }
158
+ {% endif %}
159
+
160
+
161
+ // 24-bit words of 2/pi used by the exact Payne-Hanek large-input reducer.
162
+ const TWO_OVER_PI: array<u32, 14> = array<u32, 14>(
163
+ 0xa2f983u, 0x6e4e44u, 0x1529fcu, 0x2757d1u, 0xf534ddu, 0xc0db62u,
164
+ 0x95993cu, 0x439041u, 0xfe5163u, 0xabdebbu, 0xc561b7u, 0x246e3au,
165
+ 0x424dd2u, 0xe00649u
166
+ );
167
+ const PIO2_F: f32 = 1.5707963267948966;
168
+ const INV_PIO2_F: f32 = 0.6366197723675814;
169
+ const PIO2_HI_F: f32 = 1.570796251296997;
170
+ const PIO2_LO_F: f32 = 7.549789415861596e-8;
171
+ const REDUCE_THRESHOLD: f32 = 1.0e4;
172
+
173
+ struct Pio2 { octant: u32, r: f32 };
174
+
175
+ {{ emit_reduce_pio2_fast() }}
176
+
177
+ {{ emit_reduce_pio2() }}
178
+ {%- endmacro %}
179
+ {% macro emit_cos_accurate() %}
180
+ fn cos_accurate(x: f32) -> f32 {
181
+ let ax = abs(x); // cos is even
182
+ let ax_bits = bitcast<u32>(ax);
183
+ if (((ax_bits >> 23u) & 0xffu) == 0xffu) {
184
+ // Avoid Inf-Inf / NaN-NaN arithmetic: setting the quiet-NaN bit is robust
185
+ // even on backends that compile arithmetic under a no-NaN assumption.
186
+ return bitcast<f32>(ax_bits | 0x00400000u);
187
+ }
188
+ var red: Pio2;
189
+ if (ax < REDUCE_THRESHOLD) { red = reduce_pio2_fast(ax); }
190
+ else { red = reduce_pio2(ax); }
191
+ let reduced = precise_sincos_half_pi(red.r);
192
+ switch (red.octant) {
193
+ case 0u: { return reduced.x; }
194
+ case 1u: { return -reduced.y; }
195
+ case 2u: { return -reduced.x; }
196
+ default: { return reduced.y; }
197
+ }
198
+ }{% endmacro %}
199
+ {{ emit_trig_reduction_support() }}
200
+
201
+ {{ emit_cos_accurate() }}
202
+ {{ flat_tail_open() }}
203
+ y[i] = {{ scalar }}(cos_accurate(f32(x[i])));
204
+ {{ flat_tail_close() -}}
205
+ }
build/webgpu/unary-vec4.wgsl.jinja ADDED
@@ -0,0 +1,198 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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() %}
9
+ fn reduce_pio2(ax: f32) -> Pio2 {
10
+ let ix = bitcast<u32>(ax);
11
+ let e = (ix >> 23u) & 0xffu;
12
+ let mant = (ix & 0x7fffffu) | 0x800000u; // 24-bit mantissa
13
+ let p = i32(e) - 150; // ax = mant * 2^p
14
+ // 96-bit accumulator a2:a1:a0 holding (ax * 2/pi mod 4) * 2^94.
15
+ var a0 = 0u; var a1 = 0u; var a2 = 0u;
16
+ for (var k = 0u; k < 14u; k = k + 1u) {
17
+ let shift = p - 24 * (i32(k) + 1) + 94;
18
+ if (shift <= -48 || shift >= 96) { continue; }
19
+ // prod = mant * TWO_OVER_PI[k] (<= 48 bits) -> phi32:plo
20
+ let t = TWO_OVER_PI[k];
21
+ let mlo = mant & 0xffffu; let mhi = mant >> 16u;
22
+ let tlo = t & 0xffffu; let thi = t >> 16u;
23
+ let ll = mlo * tlo;
24
+ let mid = mlo * thi + mhi * tlo; // <= 25 bits, no overflow
25
+ let hh = mhi * thi;
26
+ let plo = ll + ((mid & 0xffffu) << 16u);
27
+ let carry = select(0u, 1u, plo < ll);
28
+ let phi32 = hh + (mid >> 16u) + carry; // <= 16 bits
29
+ // Place (phi32:plo) << shift as a 64-bit value (v1:v0) at offset off >= 0.
30
+ var v0 = 0u; var v1 = 0u; var off = 0;
31
+ if (shift < 0) {
32
+ let s = u32(-shift); // 1..47
33
+ if (s < 32u) {
34
+ v0 = (plo >> s) | (phi32 << (32u - s));
35
+ v1 = phi32 >> s;
36
+ } else {
37
+ v0 = phi32 >> (s - 32u);
38
+ v1 = 0u;
39
+ }
40
+ off = 0;
41
+ } else {
42
+ v0 = plo; v1 = phi32; off = shift; // 0..95
43
+ }
44
+ // Spread (v1:v0) across 96-bit limbs starting at bit `off`.
45
+ let li = off / 32; let b = u32(off % 32);
46
+ var w0 = 0u; var w1 = 0u; var w2 = 0u;
47
+ if (b == 0u) { w0 = v0; w1 = v1; w2 = 0u; }
48
+ else {
49
+ w0 = v0 << b;
50
+ w1 = (v1 << b) | (v0 >> (32u - b));
51
+ w2 = v1 >> (32u - b);
52
+ }
53
+ var t0 = 0u; var t1 = 0u; var t2 = 0u;
54
+ if (li == 0) { t0 = w0; t1 = w1; t2 = w2; }
55
+ else if (li == 1) { t1 = w0; t2 = w1; }
56
+ else { t2 = w0; }
57
+ // 96-bit add acc += t (bits above 2^96 are multiples of 4, so discarded).
58
+ let n0 = a0 + t0; let c0 = select(0u, 1u, n0 < a0);
59
+ let n1a = a1 + t1; let c1a = select(0u, 1u, n1a < a1);
60
+ let n1 = n1a + c0; let c1b = select(0u, 1u, n1 < n1a);
61
+ let n2 = a2 + t2 + c1a + c1b;
62
+ a0 = n0; a1 = n1; a2 = n2;
63
+ }
64
+ var n = a2 >> 30u; // integer part mod 4
65
+ var phi = f32(a2 & 0x3fffffffu) * (1.0 / 1073741824.0)
66
+ + f32(a1) * (1.0 / 4611686018427387904.0); // fraction in [0,1)
67
+ if (phi >= 0.5) { phi = phi - 1.0; n = n + 1u; }
68
+ var out: Pio2;
69
+ out.octant = n & 3u;
70
+ out.r = phi * PIO2_F;
71
+ return out;
72
+ }{% endmacro %}
73
+ {% macro emit_reduce_pio2_fast() %}
74
+ fn reduce_pio2_fast(ax: f32) -> Pio2 {
75
+ // Cody-Waite reduction for ordinary magnitudes. Splitting pi/2 keeps the
76
+ // residual accurate even when n is thousands; larger inputs use Payne-Hanek.
77
+ let nFloat = floor(fma(ax, INV_PIO2_F, 0.5));
78
+ var residual = fma(-nFloat, PIO2_HI_F, ax);
79
+ residual = fma(-nFloat, PIO2_LO_F, residual);
80
+ var out: Pio2;
81
+ out.octant = u32(nFloat) & 3u;
82
+ out.r = residual;
83
+ return out;
84
+ }{% endmacro %}
85
+ {% macro emit_trig_reduction_support() %}
86
+ {% set needPreciseTrigCentered = false %}
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 %}
98
+ {% if needPreciseTrigTwoPi %}
99
+ const PRECISE_TRIG_TWO_PI: f32 = 6.283185307179586;
100
+ {% endif %}
101
+ {% if needPreciseTrigCentered %}
102
+ const PRECISE_TRIG_HALF_PI: f32 = 1.5707963267948966;
103
+ {% endif %}
104
+
105
+ // Input must be in [-pi/2, pi/2]. Returns (cos(x), sin(x)).
106
+ fn precise_sincos_half_pi(x: f32) -> vec2<f32> {
107
+ let x2 = x * x;
108
+
109
+ var sinPolynomial = 1.6059043836821613e-10;
110
+ sinPolynomial = fma(sinPolynomial, x2, -2.505210838544172e-8);
111
+ sinPolynomial = fma(sinPolynomial, x2, 2.7557319223985893e-6);
112
+ sinPolynomial = fma(sinPolynomial, x2, -1.984126984126984e-4);
113
+ sinPolynomial = fma(sinPolynomial, x2, 8.333333333333333e-3);
114
+ sinPolynomial = fma(sinPolynomial, x2, -1.6666666666666666e-1);
115
+ let sine = x * fma(sinPolynomial, x2, 1.0);
116
+
117
+ var cosPolynomial = 2.08767569878681e-9;
118
+ cosPolynomial = fma(cosPolynomial, x2, -2.755731922398589e-7);
119
+ cosPolynomial = fma(cosPolynomial, x2, 2.48015873015873e-5);
120
+ cosPolynomial = fma(cosPolynomial, x2, -1.388888888888889e-3);
121
+ cosPolynomial = fma(cosPolynomial, x2, 4.1666666666666664e-2);
122
+ cosPolynomial = fma(cosPolynomial, x2, -5.0e-1);
123
+ let cosine = fma(cosPolynomial, x2, 1.0);
124
+
125
+ return vec2<f32>(cosine, sine);
126
+ }
127
+ {% if needPreciseTrigCentered %}
128
+
129
+ // Input must be in [-pi, pi]. Returns (cos(x), sin(x)).
130
+ fn precise_sincos_centered(x: f32) -> vec2<f32> {
131
+ var folded = x;
132
+ var cosineSign = 1.0;
133
+ if (folded > PRECISE_TRIG_HALF_PI) {
134
+ folded = PRECISE_TRIG_PI - folded;
135
+ cosineSign = -1.0;
136
+ } else if (folded < -PRECISE_TRIG_HALF_PI) {
137
+ folded = -PRECISE_TRIG_PI - folded;
138
+ cosineSign = -1.0;
139
+ }
140
+ let value = precise_sincos_half_pi(folded);
141
+ return vec2<f32>(cosineSign * value.x, value.y);
142
+ }
143
+ {% endif %}
144
+
145
+
146
+ // 24-bit words of 2/pi used by the exact Payne-Hanek large-input reducer.
147
+ const TWO_OVER_PI: array<u32, 14> = array<u32, 14>(
148
+ 0xa2f983u, 0x6e4e44u, 0x1529fcu, 0x2757d1u, 0xf534ddu, 0xc0db62u,
149
+ 0x95993cu, 0x439041u, 0xfe5163u, 0xabdebbu, 0xc561b7u, 0x246e3au,
150
+ 0x424dd2u, 0xe00649u
151
+ );
152
+ const PIO2_F: f32 = 1.5707963267948966;
153
+ const INV_PIO2_F: f32 = 0.6366197723675814;
154
+ const PIO2_HI_F: f32 = 1.570796251296997;
155
+ const PIO2_LO_F: f32 = 7.549789415861596e-8;
156
+ const REDUCE_THRESHOLD: f32 = 1.0e4;
157
+
158
+ struct Pio2 { octant: u32, r: f32 };
159
+
160
+ {{ emit_reduce_pio2_fast() }}
161
+
162
+ {{ emit_reduce_pio2() }}
163
+ {%- endmacro %}
164
+ {% macro emit_cos_accurate() %}
165
+ fn cos_accurate(x: f32) -> f32 {
166
+ let ax = abs(x); // cos is even
167
+ let ax_bits = bitcast<u32>(ax);
168
+ if (((ax_bits >> 23u) & 0xffu) == 0xffu) {
169
+ // Avoid Inf-Inf / NaN-NaN arithmetic: setting the quiet-NaN bit is robust
170
+ // even on backends that compile arithmetic under a no-NaN assumption.
171
+ return bitcast<f32>(ax_bits | 0x00400000u);
172
+ }
173
+ var red: Pio2;
174
+ if (ax < REDUCE_THRESHOLD) { red = reduce_pio2_fast(ax); }
175
+ else { red = reduce_pio2(ax); }
176
+ let reduced = precise_sincos_half_pi(red.r);
177
+ switch (red.octant) {
178
+ case 0u: { return reduced.x; }
179
+ case 1u: { return -reduced.y; }
180
+ case 2u: { return -reduced.x; }
181
+ default: { return reduced.y; }
182
+ }
183
+ }{% endmacro %}
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
+ }