Xenova HF Staff commited on
Commit
32be2c7
·
verified ·
1 Parent(s): 459b738

sync 2e7068faf55e

Browse files
README.md CHANGED
@@ -1,3 +1,77 @@
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.HannWindow
10
+
11
+ `ai.onnx` · standard ONNX operator · ONNX opset ≥ 17
12
+
13
+ ## Description
14
+
15
+ Generates a Hann window of a given length using the formula `0.5 - 0.5 * cos(2π * n / N)`, where `N` is `size` for periodic mode or `size - 1` for symmetric mode. The output is a 1-D tensor of length `size`.
16
+
17
+ See the [ONNX `HannWindow` spec](https://onnx.ai/onnx/operators/onnx__HannWindow.html) for the reference semantics.
18
+
19
+ ## Inputs
20
+
21
+ | Name | Bind key | Logical dtype | Rank | Shape | Description | Presence |
22
+ | --- | --- | --- | --- | --- | --- | --- |
23
+ | `size` | `size` | `T1` | `0` | — | Scalar indicating the number of elements in the output window. | required |
24
+
25
+ ## Outputs
26
+
27
+ | Name | Bind key | Logical dtype | Rank | Shape | Description | Presence |
28
+ | --- | --- | --- | --- | --- | --- | --- |
29
+ | `output` | `y` | `T2` | `1` | — | 1-D Hann window tensor of shape `[size]`. | required |
30
+
31
+ ## Attributes
32
+
33
+ Default values (overridable per request):
34
+
35
+ | Attribute | Default | Description |
36
+ | --- | --- | --- |
37
+ | `output_datatype` | `1` | Data type of the output tensor, encoded as a DataType enum value from TensorProto (default `1` = FLOAT). |
38
+ | `periodic` | `1` | If `1` (default), returns a periodic window of length `size`; if `0`, returns a symmetric window of length `size`. |
39
+
40
+ ## Type constraints
41
+
42
+ | Variable | Allowed dtypes |
43
+ | --- | --- |
44
+ | `T1` | `int32` |
45
+ | `T2` | `float32`, `float16`, `uint32`, `int32`, `uint8`, `int8`, `int16` |
46
+
47
+ ## Files
48
+
49
+ - [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, provenance)
50
+ - [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
51
+ - [`test.json`](build/webgpu/test.json) — correctness cases
52
+ - [`bench.json`](build/webgpu/bench.json) — benchmark + tuning cases
53
+ - [`window-cosine-sum.wgsl.jinja`](build/webgpu/window-cosine-sum.wgsl.jinja)
54
+
55
+ ## Use with `@huggingface/kernels`
56
+
57
+ The loader automatically allocates outputs whose metadata it can derive from the manifest contract and this call.
58
+
59
+ The explicit `outputs` entries provide shape and logical dtype metadata for the results listed below:
60
+
61
+ - `y`
62
+
63
+ Each entry either requests an optional result or supplies metadata that cannot be inferred from the inputs.
64
+
65
+ The `version: 1` option selects the published kernel contract; it is independent of any operator opset, contrib `since_version`, or model version.
66
+
67
+ Replace each `*Data` placeholder with a typed array containing the corresponding input data.
68
+
69
+ ```js
70
+ import { getKernel } from "@huggingface/kernels";
71
+
72
+ const kernel = await getKernel("webgpu-kernels/ai.onnx.HannWindow", { version: 1 });
73
+ // Explicit destinations request optional results or supply metadata that cannot be inferred.
74
+ const { y } = await kernel({ size: { data: sizeData, shape: [] } }, {
75
+ outputs: { y: { shape: [1], dtype: "float32" } },
76
+ });
77
+ ```
build/webgpu/bench.json ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "op": "ai.onnx.HannWindow",
3
+ "cases": [
4
+ {
5
+ "name": "hann-f32-4096",
6
+ "inputs": { "size": { "dtype": "int32", "shape": [], "data": { "kind": "values", "values": [4096] } } },
7
+ "outputs": { "y": { "dtype": "float32", "shape": [4096] } }
8
+ },
9
+ {
10
+ "name": "hann-f16-1m",
11
+ "preset": "edge",
12
+ "attrs": { "output_datatype": 10 },
13
+ "inputs": { "size": { "dtype": "int32", "shape": [], "data": { "kind": "values", "values": [1048576] } } },
14
+ "outputs": { "y": { "dtype": "float16", "shape": [1048576], "dist": "empty" } },
15
+ "bench": { "metrics": [{ "type": "bandwidth", "value": "2 * numel(shapes.y)", "name": "write-BW" }] }
16
+ },
17
+ {
18
+ "name": "hann-f32-1m",
19
+ "preset": "smoke",
20
+ "inputs": { "size": { "dtype": "int32", "shape": [], "data": { "kind": "values", "values": [1048576] } } },
21
+ "outputs": { "y": { "dtype": "float32", "shape": [1048576], "dist": "empty" } },
22
+ "bench": { "metrics": [{ "type": "bandwidth", "value": "numel(shapes.y) * 4", "name": "write-BW" }] }
23
+ }
24
+ ]
25
+ }
build/webgpu/manifest.json ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "domain": "ai.onnx",
3
+ "name": "HannWindow",
4
+ "sinceVersion": 17,
5
+ "description": "Generates a Hann window of a given length using the formula `0.5 - 0.5 * cos(2π * n / N)`, where `N` is `size` for periodic mode or `size - 1` for symmetric mode. The output is a 1-D tensor of length `size`.",
6
+ "inputs": [
7
+ {
8
+ "role": "size",
9
+ "dtype": "T1",
10
+ "description": "Scalar indicating the number of elements in the output window.",
11
+ "rank": 0
12
+ }
13
+ ],
14
+ "outputs": [
15
+ { "role": "output", "dtype": "T2", "description": "1-D Hann window tensor of shape `[size]`.", "rank": 1 }
16
+ ],
17
+ "attributes": { "output_datatype": 1, "periodic": 1 },
18
+ "attributeDescriptions": {
19
+ "output_datatype": "Data type of the output tensor, encoded as a DataType enum value from TensorProto (default `1` = FLOAT).",
20
+ "periodic": "If `1` (default), returns a periodic window of length `size`; if `0`, returns a symmetric window of length `size`."
21
+ },
22
+ "attributeConstraints": { "periodic": { "values": [0, 1] } },
23
+ "typeConstraints": { "T1": ["int32"], "T2": ["float32", "float16", "uint32", "int32", "uint8", "int8", "int16"] },
24
+ "args": {
25
+ "size": { "kind": "tensor", "semantic": "size", "role": "input" },
26
+ "y": { "kind": "tensor", "semantic": "output", "role": "output" }
27
+ },
28
+ "tunables": { "WORKGROUP_SIZE": 256 },
29
+ "derive": {},
30
+ "variants": [
31
+ {
32
+ "id": "generic",
33
+ "when": "ranks.output == 1 and attrs.output_datatype == onnxDtypeCode(logicalDtypes.T2) and f16Ok(tensorDtypes.output)",
34
+ "constants": {
35
+ "usesF16": "tensorDtypes.output == \"float16\"",
36
+ "integerOutput": "tensorDtypes.output != \"float32\" and tensorDtypes.output != \"float16\"",
37
+ "outScalar": "dtypes.T2"
38
+ },
39
+ "passes": [
40
+ {
41
+ "id": "main",
42
+ "name": "HannWindow",
43
+ "source": { "shader": "window-cosine-sum.wgsl.jinja", "inputs": { "a0": "0.5", "a1": "0.5", "a2": "0" } },
44
+ "bindings": [
45
+ {
46
+ "name": "y",
47
+ "arg": "y",
48
+ "semantic": "output",
49
+ "buffer": { "type": "storage" },
50
+ "elementType": "$outScalar"
51
+ },
52
+ {
53
+ "name": "params",
54
+ "semantic": "kernel.params",
55
+ "buffer": { "type": "uniform" },
56
+ "struct": {
57
+ "name": "Params",
58
+ "fields": [
59
+ { "name": "count", "type": "u32", "value": "numel(shapes.output)" },
60
+ {
61
+ "name": "denom",
62
+ "type": "f32",
63
+ "value": "dim(shapes.output, 0) if attrs.periodic else dim(shapes.output, 0) - 1"
64
+ }
65
+ ]
66
+ }
67
+ }
68
+ ],
69
+ "dispatch": { "gridStride": "numel(shapes.output)", "workgroupSize": "tunables.WORKGROUP_SIZE" }
70
+ }
71
+ ]
72
+ }
73
+ ]
74
+ }
build/webgpu/metadata.json ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "ai.onnx.HannWindow",
3
+ "id": "_ai_onnx_hannwindow_webgpu_d631c24",
4
+ "version": 1,
5
+ "license": "Apache-2.0",
6
+ "backend": { "type": "webgpu" },
7
+ "digest": {
8
+ "algorithm": "sha256",
9
+ "files": {
10
+ "bench.json": "PEIGhBPxuOnWnbnYZ7QAl796F7fkVaYB3agt7stTl7c=",
11
+ "manifest.json": "pLxvdWH8iSFyF9krpRrWQGiCCJ/bAW8+ksjxaaY7D1U=",
12
+ "test.json": "f/qlcQHxoUOnaLhNSSG6jsdaOpn929gU3yLsEAi5XyY=",
13
+ "window-cosine-sum.wgsl.jinja": "T17RHnl56gmwE9BtwVPMeL6Gb+bO/QFqpqKZ9q6EiBE="
14
+ }
15
+ },
16
+ "provenance": { "kernel": { "sha": "2e7068faf55e7f43df740015f6d1ee49391a41c5", "dirty": false } },
17
+ "webgpu": { "manifestSpec": "1.0", "specialized": true, "opPath": "ops/ai.onnx.HannWindow" }
18
+ }
build/webgpu/test.json ADDED
@@ -0,0 +1,217 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "op": "ai.onnx.HannWindow",
3
+ "cases": [
4
+ {
5
+ "name": "periodic_f32_size16",
6
+ "inputs": { "size": { "dtype": "int32", "shape": [], "data": { "kind": "values", "values": [16] } } },
7
+ "outputs": { "y": { "dtype": "float32", "shape": [16], "tolerance": 0.00001 } }
8
+ },
9
+ {
10
+ "name": "symmetric_f32_size16",
11
+ "attrs": { "periodic": 0 },
12
+ "inputs": { "size": { "dtype": "int32", "shape": [], "data": { "kind": "values", "values": [16] } } },
13
+ "outputs": { "y": { "dtype": "float32", "shape": [16], "tolerance": 0.00001 } }
14
+ },
15
+ {
16
+ "name": "periodic_f32_size10",
17
+ "provenance": { "source": "cmake/external/onnx/onnx/backend/test/data/node/test_hannwindow" },
18
+ "inputs": { "size": { "dtype": "int32", "shape": [], "data": { "kind": "values", "values": [10] } } },
19
+ "outputs": { "y": { "dtype": "float32", "shape": [10], "tolerance": 0.00001 } }
20
+ },
21
+ {
22
+ "name": "symmetric_f32_size10_onnx_backend",
23
+ "provenance": { "source": "cmake/external/onnx/onnx/backend/test/data/node/test_hannwindow_symmetric" },
24
+ "attrs": { "periodic": 0 },
25
+ "inputs": { "size": { "dtype": "int32", "shape": [], "data": { "kind": "values", "values": [10] } } },
26
+ "outputs": { "y": { "dtype": "float32", "shape": [10], "tolerance": 0.00001 } }
27
+ },
28
+ {
29
+ "name": "periodic_f32_size0_empty",
30
+ "inputs": { "size": { "dtype": "int32", "shape": [], "data": { "kind": "values", "values": [0] } } },
31
+ "outputs": { "y": { "dtype": "float32", "shape": [0], "tolerance": 0 } }
32
+ },
33
+ {
34
+ "name": "periodic_f32_size1_boundary",
35
+ "inputs": { "size": { "dtype": "int32", "shape": [], "data": { "kind": "values", "values": [1] } } },
36
+ "outputs": { "y": { "dtype": "float32", "shape": [1], "tolerance": 0.00001 } }
37
+ },
38
+ {
39
+ "name": "symmetric_f32_size1_nan_boundary",
40
+ "provenance": {
41
+ "source": "onnxruntime/test/providers/cpu/signal/signal_ops_test.cc",
42
+ "test": "SignalOpsTest.HannWindowFloat",
43
+ "notes": "Additional ORT-validated edge: symmetric size 1 uses denominator length - 1 = 0 and produces NaN."
44
+ },
45
+ "attrs": { "periodic": 0 },
46
+ "inputs": { "size": { "dtype": "int32", "shape": [], "data": { "kind": "values", "values": [1] } } },
47
+ "outputs": {
48
+ "y": {
49
+ "dtype": "float32",
50
+ "shape": [1],
51
+ "tolerance": 0,
52
+ "allowNaN": true,
53
+ "data": { "kind": "values", "values": ["NaN"] }
54
+ }
55
+ }
56
+ },
57
+ {
58
+ "name": "symmetric_f32_odd_size7",
59
+ "attrs": { "periodic": 0 },
60
+ "inputs": { "size": { "dtype": "int32", "shape": [], "data": { "kind": "values", "values": [7] } } },
61
+ "outputs": { "y": { "dtype": "float32", "shape": [7], "tolerance": 0.00001 } }
62
+ },
63
+ {
64
+ "name": "periodic_f16_size32",
65
+ "attrs": { "output_datatype": 10 },
66
+ "inputs": { "size": { "dtype": "int32", "shape": [], "data": { "kind": "values", "values": [32] } } },
67
+ "outputs": { "y": { "dtype": "float16", "shape": [32], "tolerance": 0.002 } }
68
+ },
69
+ {
70
+ "name": "ort_periodic_uint32_output_size8",
71
+ "provenance": {
72
+ "source": "ONNX Runtime CPUExecutionProvider",
73
+ "notes": "ONNX HannWindow computes in float and applies output_datatype=UINT32 as a final cast."
74
+ },
75
+ "attrs": { "output_datatype": 12 },
76
+ "inputs": { "size": { "dtype": "int32", "shape": [], "data": { "kind": "values", "values": [8] } } },
77
+ "outputs": {
78
+ "y": {
79
+ "dtype": "uint32",
80
+ "shape": [8],
81
+ "tolerance": 0,
82
+ "data": { "kind": "values", "values": [0, 0, 0, 0, 1, 0, 0, 0] }
83
+ }
84
+ }
85
+ },
86
+ {
87
+ "name": "ort_symmetric_int32_output_size1_zero_cast",
88
+ "provenance": {
89
+ "source": "ONNX Runtime CPUExecutionProvider",
90
+ "notes": "The symmetric size-one formula produces NaN in float; ONNX Cast to INT32 maps that value to zero."
91
+ },
92
+ "attrs": { "output_datatype": 6, "periodic": 0 },
93
+ "inputs": { "size": { "dtype": "int32", "shape": [], "data": { "kind": "values", "values": [1] } } },
94
+ "outputs": {
95
+ "y": { "dtype": "int32", "shape": [1], "tolerance": 0, "data": { "kind": "values", "values": [0] } }
96
+ }
97
+ },
98
+ {
99
+ "name": "onnx_standard_periodic_int8_output_size8",
100
+ "attrs": { "output_datatype": 3 },
101
+ "inputs": { "size": { "dtype": "int32", "shape": [], "data": { "kind": "values", "values": [8] } } },
102
+ "outputs": {
103
+ "y": {
104
+ "dtype": "int8",
105
+ "shape": [8],
106
+ "tolerance": 0,
107
+ "data": { "kind": "values", "values": [0, 0, 0, 0, 1, 0, 0, 0] }
108
+ }
109
+ }
110
+ },
111
+ {
112
+ "name": "onnx_standard_periodic_uint8_output_size8",
113
+ "attrs": { "output_datatype": 2 },
114
+ "inputs": { "size": { "dtype": "int32", "shape": [], "data": { "kind": "values", "values": [8] } } },
115
+ "outputs": {
116
+ "y": {
117
+ "dtype": "uint8",
118
+ "shape": [8],
119
+ "tolerance": 0,
120
+ "data": { "kind": "values", "values": [0, 0, 0, 0, 1, 0, 0, 0] }
121
+ }
122
+ }
123
+ },
124
+ {
125
+ "name": "onnx_standard_periodic_int16_output_size8",
126
+ "attrs": { "output_datatype": 5 },
127
+ "inputs": { "size": { "dtype": "int32", "shape": [], "data": { "kind": "values", "values": [8] } } },
128
+ "outputs": {
129
+ "y": {
130
+ "dtype": "int16",
131
+ "shape": [8],
132
+ "tolerance": 0,
133
+ "data": { "kind": "values", "values": [0, 0, 0, 0, 1, 0, 0, 0] }
134
+ }
135
+ }
136
+ },
137
+ {
138
+ "name": "periodic_f32_size257_phase_accuracy_gpu_gap",
139
+ "skipGpu": {
140
+ "category": "todo",
141
+ "reason": "The current f32 cosine and range-reduction path crosses the final rounding boundary for this periodic size-257 window. A correctly rounded approximation or software-extended precision could close this implementable gap."
142
+ },
143
+ "provenance": {
144
+ "source": "onnxruntime/test/providers/cpu/signal/signal_ops_test.cc",
145
+ "test": "SignalOpsTest.HannWindowFloat",
146
+ "notes": "Medium odd-sized periodic window stresses f32 phase formation across many non-cardinal cosine positions; CPU and ORT agree more tightly than the current WGSL f32 cosine-sum path."
147
+ },
148
+ "inputs": { "size": { "dtype": "int32", "shape": [], "data": { "kind": "values", "values": [257] } } },
149
+ "outputs": { "y": { "dtype": "float32", "shape": [257], "tolerance": 6e-8, "relTolerance": 0 } }
150
+ },
151
+ {
152
+ "name": "symmetric_f32_size257_phase_accuracy_gpu_gap",
153
+ "skipGpu": {
154
+ "category": "todo",
155
+ "reason": "The current f32 cosine and range-reduction path crosses the final rounding boundary for this symmetric size-257 window. A correctly rounded approximation or software-extended precision could close this implementable gap."
156
+ },
157
+ "provenance": {
158
+ "source": "onnxruntime/test/providers/cpu/signal/signal_ops_test.cc",
159
+ "test": "SignalOpsTest.HannWindowFloat",
160
+ "notes": "Symmetric odd-sized companion to the periodic phase edge; the length-1 denominator shifts every non-cardinal cosine sample and exposes f32 phase drift in the WGSL path."
161
+ },
162
+ "attrs": { "periodic": 0 },
163
+ "inputs": { "size": { "dtype": "int32", "shape": [], "data": { "kind": "values", "values": [257] } } },
164
+ "outputs": { "y": { "dtype": "float32", "shape": [257], "tolerance": 6e-8, "relTolerance": 0 } }
165
+ },
166
+ {
167
+ "name": "ort_periodic_f32_size32",
168
+ "provenance": {
169
+ "source": "onnxruntime/test/providers/cpu/signal/signal_ops_test.cc",
170
+ "test": "SignalOpsTest.HannWindowFloat"
171
+ },
172
+ "inputs": { "size": { "dtype": "int32", "shape": [], "data": { "kind": "values", "values": [32] } } },
173
+ "outputs": {
174
+ "y": {
175
+ "dtype": "float32",
176
+ "shape": [32],
177
+ "tolerance": 0.00001,
178
+ "data": {
179
+ "kind": "values",
180
+ "values": [0.0, 0.009607, 0.03806, 0.084265, 0.146447, 0.222215, 0.308658, 0.402455, 0.5, 0.597545, 0.691342, 0.777785, 0.853553, 0.915735, 0.96194, 0.990393, 1.0, 0.990393, 0.96194, 0.915735, 0.853553, 0.777785, 0.691342, 0.597545, 0.5, 0.402455, 0.308658, 0.222215, 0.146447, 0.084265, 0.03806, 0.009607]
181
+ }
182
+ }
183
+ }
184
+ },
185
+ {
186
+ "name": "periodic_int32_size8",
187
+ "inputs": { "size": { "dtype": "int32", "shape": [], "data": { "kind": "values", "values": [8] } } },
188
+ "outputs": { "y": { "dtype": "float32", "shape": [8], "tolerance": 0.00001 } }
189
+ },
190
+ {
191
+ "name": "symmetric_f32_size2_denom1",
192
+ "attrs": { "periodic": 0 },
193
+ "inputs": { "size": { "dtype": "int32", "shape": [], "data": { "kind": "values", "values": [2] } } },
194
+ "outputs": {
195
+ "y": {
196
+ "dtype": "float32",
197
+ "shape": [2],
198
+ "tolerance": 0.00001,
199
+ "data": { "kind": "values", "values": [0.0, 0.0] }
200
+ }
201
+ }
202
+ },
203
+ {
204
+ "name": "symmetric_f32_size4_cardinal_verify",
205
+ "attrs": { "periodic": 0 },
206
+ "inputs": { "size": { "dtype": "int32", "shape": [], "data": { "kind": "values", "values": [4] } } },
207
+ "outputs": { "y": { "dtype": "float32", "shape": [4], "tolerance": 0.00001 } }
208
+ },
209
+ {
210
+ "name": "symmetric_f16_size16",
211
+ "attrs": { "output_datatype": 10, "periodic": 0 },
212
+ "requires": { "features": ["shader-f16"] },
213
+ "inputs": { "size": { "dtype": "int32", "shape": [], "data": { "kind": "values", "values": [16] } } },
214
+ "outputs": { "y": { "dtype": "float16", "shape": [16], "tolerance": 0.002 } }
215
+ }
216
+ ]
217
+ }
build/webgpu/window-cosine-sum.wgsl.jinja ADDED
@@ -0,0 +1,129 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Cosine-sum window:
2
+ // w = a0 - a1*cos(phase) + a2*cos(2*phase), phase = 2*pi*n/N
3
+ // Compiled coefficients select Hann, Hamming, or Blackman. N (params.denom) is
4
+ // the window length for a periodic window, or length - 1 for a symmetric window.
5
+ //
6
+ // Matching the f64-rounded target within roughly 0.5 ULP requires two
7
+ // details beyond a naive `a0 - a1*cos(2*pi*n/N)`: keep n/N as a rational turn and
8
+ // use the backend-stable unit-circle polynomial, and form a0 - a1*c with one
9
+ // rounding via fma(-a1, c, a0). WGSL builtin transcendental accuracy is
10
+ // implementation-defined, and a rounded multiply followed by subtraction would
11
+ // double-round. Grid strides cover large windows with a clamped dispatch.
12
+ {% if usesF16 %}
13
+ enable f16;
14
+ {% endif %}
15
+ {{ env.wgsl.resourceDeclarations }}
16
+ // Backend-stable unit-circle twiddles for DFT kernels. Retain the phase as a
17
+ // rational turn and route all polynomial evaluation through the shared precise
18
+ // trig core so FFT, direct DFT, STFT, and cosine windows agree exactly.
19
+ //
20
+ // phase is in [0, period). sign is -1 for a forward transform and +1 for an
21
+ // inverse transform. The result is (cos, sin).
22
+ // Backend-stable f32 sine/cosine core.
23
+ //
24
+ // Shader transcendental accuracy is implementation-defined, and some portable
25
+ // backends are only accurate to roughly 1e-4. Each path retains the most accurate
26
+ // available phase representation, reduces it to [-pi, pi], then uses these
27
+ // polynomials. The half-pi core is degree 13 for sine and degree 12 for cosine;
28
+ // truncation error is well below one f32 ULP over its documented interval.
29
+ const PRECISE_TRIG_PI: f32 = 3.141592653589793;
30
+ const PRECISE_TRIG_TWO_PI: f32 = 6.283185307179586;
31
+ const PRECISE_TRIG_HALF_PI: f32 = 1.5707963267948966;
32
+
33
+ // Input must be in [-pi/2, pi/2]. Returns (cos(x), sin(x)).
34
+ fn precise_sincos_half_pi(x: f32) -> vec2<f32> {
35
+ let x2 = x * x;
36
+
37
+ var sinPolynomial = 1.6059043836821613e-10;
38
+ sinPolynomial = fma(sinPolynomial, x2, -2.505210838544172e-8);
39
+ sinPolynomial = fma(sinPolynomial, x2, 2.7557319223985893e-6);
40
+ sinPolynomial = fma(sinPolynomial, x2, -1.984126984126984e-4);
41
+ sinPolynomial = fma(sinPolynomial, x2, 8.333333333333333e-3);
42
+ sinPolynomial = fma(sinPolynomial, x2, -1.6666666666666666e-1);
43
+ let sine = x * fma(sinPolynomial, x2, 1.0);
44
+
45
+ var cosPolynomial = 2.08767569878681e-9;
46
+ cosPolynomial = fma(cosPolynomial, x2, -2.755731922398589e-7);
47
+ cosPolynomial = fma(cosPolynomial, x2, 2.48015873015873e-5);
48
+ cosPolynomial = fma(cosPolynomial, x2, -1.388888888888889e-3);
49
+ cosPolynomial = fma(cosPolynomial, x2, 4.1666666666666664e-2);
50
+ cosPolynomial = fma(cosPolynomial, x2, -5.0e-1);
51
+ let cosine = fma(cosPolynomial, x2, 1.0);
52
+
53
+ return vec2<f32>(cosine, sine);
54
+ }
55
+
56
+ // Input must be in [-pi, pi]. Returns (cos(x), sin(x)).
57
+ fn precise_sincos_centered(x: f32) -> vec2<f32> {
58
+ var folded = x;
59
+ var cosineSign = 1.0;
60
+ if (folded > PRECISE_TRIG_HALF_PI) {
61
+ folded = PRECISE_TRIG_PI - folded;
62
+ cosineSign = -1.0;
63
+ } else if (folded < -PRECISE_TRIG_HALF_PI) {
64
+ folded = -PRECISE_TRIG_PI - folded;
65
+ cosineSign = -1.0;
66
+ }
67
+ let value = precise_sincos_half_pi(folded);
68
+ return vec2<f32>(cosineSign * value.x, value.y);
69
+ }
70
+
71
+
72
+ fn dft_precise_twiddle(phase: u32, period: u32, sign: f32) -> vec2<f32> {
73
+ let reducedPhase = phase % period;
74
+ if (reducedPhase == 0u) {
75
+ return vec2<f32>(1.0, sign * 0.0);
76
+ }
77
+ if (period % 4u == 0u) {
78
+ let quarter = period / 4u;
79
+ if (reducedPhase == quarter) {
80
+ return vec2<f32>(0.0, sign);
81
+ }
82
+ if (reducedPhase == 2u * quarter) {
83
+ return vec2<f32>(-1.0, sign * 0.0);
84
+ }
85
+ if (reducedPhase == 3u * quarter) {
86
+ return vec2<f32>(0.0, -sign);
87
+ }
88
+ }
89
+ // Center the rational turn before multiplying by 2*pi. This avoids the
90
+ // cancellation in `2*pi*(N-1)/N - 2*pi` for bins near the end of a row.
91
+ let turns = f32(reducedPhase) / f32(period);
92
+ let centeredTurns = turns - floor(turns + 0.5);
93
+ return precise_sincos_centered(sign * PRECISE_TRIG_TWO_PI * centeredTurns);
94
+ }
95
+
96
+
97
+ const WG: u32 = {{ tunables.WORKGROUP_SIZE }}u;
98
+
99
+ fn cos_turns(phase: u32, period: u32) -> f32 {
100
+ return dft_precise_twiddle(phase % period, period, 1.0).x;
101
+ }
102
+
103
+ @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
104
+ fn main(
105
+ @builtin(global_invocation_id) gid: vec3<u32>,
106
+ @builtin(num_workgroups) nwg: vec3<u32>
107
+ ) {
108
+ let stride = nwg.x * WG;
109
+ for (var i = gid.x; i < params.count; i += stride) {
110
+ if (params.denom == 0.0) {
111
+ {% if integerOutput %}
112
+ // The window is evaluated in f32 before the output cast. Spell the
113
+ // symmetric-size-one integer result directly instead of converting NaN.
114
+ y[i] = {{ outScalar }}(0);
115
+ {% else %}
116
+ // Do not synthesize NaN with 0/0: fast-math can fold identical operands to
117
+ // 1. Incorporating the runtime uniform also keeps
118
+ // WGSL validators from rejecting a compile-time non-finite constant.
119
+ let nan_bits = bitcast<u32>(params.denom) | 0x7fc00000u;
120
+ y[i] = {{ outScalar }}(bitcast<f32>(nan_bits));
121
+ {% endif %}
122
+ continue;
123
+ }
124
+ let period = u32(params.denom);
125
+ // w = a0 - a1*cos(2*pi*turns) [+ a2*cos(4*pi*turns)], fused single-rounding.
126
+ var w = fma(-({{ source.a1 }}), cos_turns(i, period), {{ source.a0 }});
127
+ y[i] = {{ outScalar }}(w);
128
+ }
129
+ }