Xenova HF Staff commited on
Commit
34eff6e
·
verified ·
1 Parent(s): 11c8823

sync 2e7068faf55e

Browse files
README.md CHANGED
@@ -1,3 +1,85 @@
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.DFT
10
+
11
+ `ai.onnx` · standard ONNX operator · ONNX opset ≥ 20
12
+
13
+ ## Description
14
+
15
+ Computes the discrete Fourier Transform (DFT) of the input along a specified axis. For a signal of length N, output bin k is `sum_{n=0}^{N-1} exp(-2*pi*j*k*n/N) * x[n]`; the inverse divides by N and negates the exponent sign. Supports forward/inverse, real-to-complex (RFFT), and complex-to-real (IRFFT) modes via the `onesided` and `inverse` attributes.
16
+
17
+ See the [ONNX `DFT` spec](https://onnx.ai/onnx/operators/onnx__DFT.html) for the reference semantics.
18
+
19
+ ## Inputs
20
+
21
+ | Name | Bind key | Logical dtype | WebGPU storage | Rank | Shape | Description | Presence |
22
+ | --- | --- | --- | --- | --- | --- | --- | --- |
23
+ | `input` | `input` | `T` | same as logical dtype | — | — | Input signal tensor; the last dimension is 1 for real values or 2 for complex `(real, imaginary)` pairs. | required |
24
+ | `dft_length` | `dft_length` | `L` | same as logical dtype | `0` | — | Optional int32 scalar controlling the signal length used for the transform; input is zero-padded or truncated to this length. | optional |
25
+ | `axis` | `axis` | `I` | `int32` | `0` | — | Optional logical int64 scalar specifying the dimension over which to compute the DFT (the last axis is reserved for the real/imaginary component and is excluded); signed axes use int32 WebGPU storage and default to `-2` when omitted. | optional |
26
+
27
+ ## Outputs
28
+
29
+ | Name | Bind key | Logical dtype | Rank | Shape | Description | Presence |
30
+ | --- | --- | --- | --- | --- | --- | --- |
31
+ | `output` | `output` | `T` | same as `input` | — | DFT result tensor; last dimension is 2 (complex) for forward DFT and RFFT, or 1 (real) for IRFFT. | required |
32
+
33
+ ## Attributes
34
+
35
+ Default values (overridable per request):
36
+
37
+ | Attribute | Default | Description |
38
+ | --- | --- | --- |
39
+ | `inverse` | `0` | When set to 1, computes the inverse DFT (IDFT/IRFFT) instead of the forward transform; default is 0 (forward). |
40
+ | `onesided` | `0` | When set to 1, exploits conjugate symmetry to return only the non-redundant half of the spectrum (RFFT for forward, IRFFT for inverse); default is 0 (full spectrum). |
41
+
42
+ ## Type constraints
43
+
44
+ | Variable | Allowed dtypes |
45
+ | --- | --- |
46
+ | `T` | `float32` |
47
+ | `L` | `int32` |
48
+ | `I` | `int64` |
49
+
50
+ ## Files
51
+
52
+ - [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, provenance)
53
+ - [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
54
+ - [`test.json`](build/webgpu/test.json) — correctness cases
55
+ - [`bench.json`](build/webgpu/bench.json) — benchmark + tuning cases
56
+ - [`dft-contiguous-naive.wgsl.jinja`](build/webgpu/dft-contiguous-naive.wgsl.jinja)
57
+ - [`dft-general.wgsl.jinja`](build/webgpu/dft-general.wgsl.jinja)
58
+ - [`dft-rank4.wgsl.jinja`](build/webgpu/dft-rank4.wgsl.jinja)
59
+ - [`dft-tiled-real.wgsl.jinja`](build/webgpu/dft-tiled-real.wgsl.jinja)
60
+ - [`fft-radix2-dit-storage.wgsl.jinja`](build/webgpu/fft-radix2-dit-storage.wgsl.jinja)
61
+ - [`fft-stockham-shared.wgsl.jinja`](build/webgpu/fft-stockham-shared.wgsl.jinja)
62
+
63
+ ## Use with `@huggingface/kernels`
64
+
65
+ The loader automatically allocates outputs whose metadata it can derive from the manifest contract and this call.
66
+
67
+ The explicit `outputs` entries provide shape and logical dtype metadata for the results listed below:
68
+
69
+ - `output`
70
+
71
+ Each entry either requests an optional result or supplies metadata that cannot be inferred from the inputs.
72
+
73
+ The `version: 1` option selects the published kernel contract; it is independent of any operator opset, contrib `since_version`, or model version.
74
+
75
+ Replace each `*Data` placeholder with a typed array containing the corresponding input data.
76
+
77
+ ```js
78
+ import { getKernel } from "@huggingface/kernels";
79
+
80
+ const kernel = await getKernel("webgpu-kernels/ai.onnx.DFT", { version: 1 });
81
+ // Explicit destinations request optional results or supply metadata that cannot be inferred.
82
+ const { output } = await kernel({ input: { data: inputData, shape: [1, 1, 1, 1, 1, 1, 2, 1] } }, {
83
+ outputs: { output: { shape: [1, 1, 1, 1, 1, 1, 2, 2], dtype: "float32" } },
84
+ });
85
+ ```
build/webgpu/bench.json ADDED
@@ -0,0 +1,147 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "op": "ai.onnx.DFT",
3
+ "tunableSpace": { "WORKGROUP_SIZE": [64, 128, 256] },
4
+ "cases": [
5
+ {
6
+ "name": "batch4_len64_complex",
7
+ "inputs": { "input": { "dtype": "float32", "shape": [4, 64, 2] } },
8
+ "outputs": { "output": { "dtype": "float32", "shape": [4, 64, 2] } }
9
+ },
10
+ {
11
+ "name": "fft_batch_axis1_len1024_batch64_real_full",
12
+ "preset": "smoke",
13
+ "vars": { "transforms": 64, "n": 1024, "log2n": 10 },
14
+ "inputs": { "input": { "dtype": "float32", "shape": [64, 1024, 1], "dist": "normal", "seed": 901 } },
15
+ "outputs": { "output": { "dtype": "float32", "shape": [64, 1024, 2] } },
16
+ "bench": {
17
+ "primary": true,
18
+ "metrics": [{ "type": "gflops", "value": "5 * args.transforms * args.n * args.log2n" }]
19
+ }
20
+ },
21
+ {
22
+ "name": "fft_batch_axis1_len2048_batch32_real_onesided",
23
+ "preset": "smoke",
24
+ "attrs": { "onesided": 1 },
25
+ "vars": { "transforms": 32, "n": 2048, "log2n": 11 },
26
+ "inputs": { "input": { "dtype": "float32", "shape": [32, 2048, 1], "dist": "normal", "seed": 902 } },
27
+ "outputs": { "output": { "dtype": "float32", "shape": [32, 1025, 2] } },
28
+ "bench": { "metrics": [{ "type": "gflops", "value": "5 * args.transforms * args.n * args.log2n" }] }
29
+ },
30
+ {
31
+ "name": "fft_rank4_axis1_len1024_batch2_inner8",
32
+ "preset": "smoke",
33
+ "attrs": {},
34
+ "vars": { "transforms": 16, "n": 1024, "log2n": 10 },
35
+ "inputs": {
36
+ "axis": { "dtype": "int32", "shape": [], "data": { "kind": "values", "values": [1] } },
37
+ "input": { "dtype": "float32", "shape": [2, 1024, 8, 1], "dist": "normal", "seed": 903 }
38
+ },
39
+ "outputs": { "output": { "dtype": "float32", "shape": [2, 1024, 8, 2] } },
40
+ "bench": { "metrics": [{ "type": "gflops", "value": "5 * args.transforms * args.n * args.log2n" }] }
41
+ },
42
+ {
43
+ "name": "fft_rank2_axis0_len512_single",
44
+ "preset": "smoke",
45
+ "attrs": {},
46
+ "vars": { "transforms": 1, "n": 512, "log2n": 9 },
47
+ "inputs": { "input": { "dtype": "float32", "shape": [512, 1], "dist": "normal", "seed": 904 } },
48
+ "outputs": { "output": { "dtype": "float32", "shape": [512, 2] } },
49
+ "bench": { "metrics": [{ "type": "gflops", "value": "5 * args.transforms * args.n * args.log2n" }] }
50
+ },
51
+ {
52
+ "name": "tiled_batch_axis1_len1000_batch64_real_full",
53
+ "preset": "stress",
54
+ "vars": { "transforms": 64, "n": 1000 },
55
+ "inputs": { "input": { "dtype": "float32", "shape": [64, 1000, 1], "dist": "normal", "seed": 905 } },
56
+ "outputs": { "output": { "dtype": "float32", "shape": [64, 1000, 2] } },
57
+ "bench": { "metrics": [{ "type": "gflops", "value": "8 * args.transforms * args.n * args.n" }] }
58
+ },
59
+ {
60
+ "name": "fft_storage_inplace_batch_axis1_len4096_batch8_real_full",
61
+ "preset": "stress",
62
+ "vars": { "transforms": 8, "n": 4096, "log2n": 12 },
63
+ "attrs": { "inverse": 0, "onesided": 0 },
64
+ "inputs": { "input": { "dtype": "float32", "shape": [8, 4096, 1], "dist": "normal", "seed": 921, "scale": 2 } },
65
+ "outputs": { "output": { "dtype": "float32", "shape": [8, 4096, 2], "dist": "empty" } },
66
+ "bench": {
67
+ "primary": true,
68
+ "metrics": [{ "type": "gflops", "value": "5 * args.transforms * args.n * args.log2n" }]
69
+ }
70
+ },
71
+ {
72
+ "name": "tiled_batch_axis1_len4095_batch8_fft_boundary_minus1",
73
+ "preset": "stress",
74
+ "vars": { "transforms": 8, "n": 4095 },
75
+ "attrs": { "inverse": 0, "onesided": 0 },
76
+ "inputs": { "input": { "dtype": "float32", "shape": [8, 4095, 1], "dist": "normal", "seed": 925, "scale": 2 } },
77
+ "outputs": { "output": { "dtype": "float32", "shape": [8, 4095, 2], "dist": "empty" } },
78
+ "bench": { "metrics": [{ "type": "gflops", "value": "8 * args.transforms * args.n * args.n" }] }
79
+ },
80
+ {
81
+ "name": "fft_storage_scratch_batch_axis1_len4096_batch8_real_onesided",
82
+ "preset": "stress",
83
+ "vars": { "transforms": 8, "n": 4096, "log2n": 12 },
84
+ "attrs": { "inverse": 0, "onesided": 1 },
85
+ "inputs": { "input": { "dtype": "float32", "shape": [8, 4096, 1], "dist": "normal", "seed": 924, "scale": 2 } },
86
+ "outputs": { "output": { "dtype": "float32", "shape": [8, 2049, 2], "dist": "empty" } },
87
+ "bench": { "metrics": [{ "type": "gflops", "value": "5 * args.transforms * args.n * args.log2n" }] }
88
+ },
89
+ {
90
+ "name": "general_len_pow2_1024_batch32_dftlength_demotes_fft",
91
+ "preset": "stress",
92
+ "vars": { "transforms": 32, "n": 1024, "log2n": 10 },
93
+ "attrs": { "inverse": 0, "onesided": 0 },
94
+ "inputs": {
95
+ "input": { "dtype": "float32", "shape": [32, 1024, 1], "dist": "normal", "seed": 922, "scale": 2 },
96
+ "dft_length": { "dtype": "int32", "shape": [], "dist": "constant", "value": 1024 }
97
+ },
98
+ "outputs": { "output": { "dtype": "float32", "shape": [32, 1024, 2], "dist": "empty" } },
99
+ "bench": { "metrics": [{ "type": "gflops", "value": "5 * args.transforms * args.n * args.log2n" }] }
100
+ },
101
+ {
102
+ "name": "audio-rfft-explicit-length2048-batch32-pathology",
103
+ "preset": "model",
104
+ "attrs": { "inverse": 0, "onesided": 1 },
105
+ "vars": { "transforms": 32, "n": 2048, "log2n": 11 },
106
+ "inputs": {
107
+ "input": { "dtype": "float32", "shape": [32, 2048, 1], "dist": "normal", "seed": 923, "scale": 2 },
108
+ "dft_length": { "dtype": "int32", "shape": [], "dist": "constant", "value": 2048 }
109
+ },
110
+ "outputs": { "output": { "dtype": "float32", "shape": [32, 1025, 2], "dist": "empty" } },
111
+ "bench": { "metrics": [{ "type": "gflops", "value": "5 * args.transforms * args.n * args.log2n" }] }
112
+ },
113
+ {
114
+ "name": "audio-fft-explicit-axis2048-batch32-pathology",
115
+ "preset": "stress",
116
+ "provenance": {
117
+ "notes": "Explicit-axis FFT paired with an equal-element-count implicit-axis control to expose the optional-axis dispatch boundary."
118
+ },
119
+ "attrs": { "inverse": 0, "onesided": 0 },
120
+ "vars": { "transforms": 32, "n": 2048, "log2n": 11 },
121
+ "inputs": {
122
+ "input": { "dtype": "float32", "shape": [32, 2048, 1], "dist": "normal", "seed": 926, "scale": 2 },
123
+ "axis": { "dtype": "int32", "shape": [], "dist": "constant", "value": 1 }
124
+ },
125
+ "outputs": { "output": { "dtype": "float32", "shape": [32, 2048, 2], "dist": "empty" } },
126
+ "bench": { "metrics": [{ "type": "gflops", "value": "5 * args.transforms * args.n * args.log2n" }] }
127
+ },
128
+ {
129
+ "name": "fft_storage_generic_batch_axis1_len8192_batch4_real_onesided",
130
+ "preset": "stress",
131
+ "vars": { "transforms": 4, "n": 8192, "log2n": 13 },
132
+ "attrs": { "inverse": 0, "onesided": 1 },
133
+ "inputs": { "input": { "dtype": "float32", "shape": [4, 8192, 1], "dist": "normal", "seed": 927, "scale": 2 } },
134
+ "outputs": { "output": { "dtype": "float32", "shape": [4, 4097, 2], "dist": "empty" } },
135
+ "bench": { "metrics": [{ "type": "gflops", "value": "5 * args.transforms * args.n * args.log2n" }] }
136
+ },
137
+ {
138
+ "name": "tiled_real_nonpow2_len4097_batch2_onesided",
139
+ "preset": "stress",
140
+ "vars": { "transforms": 2, "n": 4097, "bins": 2049 },
141
+ "attrs": { "inverse": 0, "onesided": 1 },
142
+ "inputs": { "input": { "dtype": "float32", "shape": [2, 4097, 1], "dist": "normal", "seed": 928, "scale": 2 } },
143
+ "outputs": { "output": { "dtype": "float32", "shape": [2, 2049, 2], "dist": "empty" } },
144
+ "bench": { "metrics": [{ "type": "gflops", "value": "8 * args.transforms * args.bins * args.n" }] }
145
+ }
146
+ ]
147
+ }
build/webgpu/dft-contiguous-naive.wgsl.jinja ADDED
@@ -0,0 +1,170 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {{ env.wgsl.resourceDeclarations }}
2
+ // Backend-stable unit-circle twiddles for DFT kernels. Retain the phase as a
3
+ // rational turn and route all polynomial evaluation through the shared precise
4
+ // trig core so FFT, direct DFT, STFT, and cosine windows agree exactly.
5
+ //
6
+ // phase is in [0, period). sign is -1 for a forward transform and +1 for an
7
+ // inverse transform. The result is (cos, sin).
8
+ // Backend-stable f32 sine/cosine core.
9
+ //
10
+ // Shader transcendental accuracy is implementation-defined, and some portable
11
+ // backends are only accurate to roughly 1e-4. Each path retains the most accurate
12
+ // available phase representation, reduces it to [-pi, pi], then uses these
13
+ // polynomials. The half-pi core is degree 13 for sine and degree 12 for cosine;
14
+ // truncation error is well below one f32 ULP over its documented interval.
15
+ const PRECISE_TRIG_PI: f32 = 3.141592653589793;
16
+ const PRECISE_TRIG_TWO_PI: f32 = 6.283185307179586;
17
+ const PRECISE_TRIG_HALF_PI: f32 = 1.5707963267948966;
18
+
19
+ // Input must be in [-pi/2, pi/2]. Returns (cos(x), sin(x)).
20
+ fn precise_sincos_half_pi(x: f32) -> vec2<f32> {
21
+ let x2 = x * x;
22
+
23
+ var sinPolynomial = 1.6059043836821613e-10;
24
+ sinPolynomial = fma(sinPolynomial, x2, -2.505210838544172e-8);
25
+ sinPolynomial = fma(sinPolynomial, x2, 2.7557319223985893e-6);
26
+ sinPolynomial = fma(sinPolynomial, x2, -1.984126984126984e-4);
27
+ sinPolynomial = fma(sinPolynomial, x2, 8.333333333333333e-3);
28
+ sinPolynomial = fma(sinPolynomial, x2, -1.6666666666666666e-1);
29
+ let sine = x * fma(sinPolynomial, x2, 1.0);
30
+
31
+ var cosPolynomial = 2.08767569878681e-9;
32
+ cosPolynomial = fma(cosPolynomial, x2, -2.755731922398589e-7);
33
+ cosPolynomial = fma(cosPolynomial, x2, 2.48015873015873e-5);
34
+ cosPolynomial = fma(cosPolynomial, x2, -1.388888888888889e-3);
35
+ cosPolynomial = fma(cosPolynomial, x2, 4.1666666666666664e-2);
36
+ cosPolynomial = fma(cosPolynomial, x2, -5.0e-1);
37
+ let cosine = fma(cosPolynomial, x2, 1.0);
38
+
39
+ return vec2<f32>(cosine, sine);
40
+ }
41
+
42
+ // Input must be in [-pi, pi]. Returns (cos(x), sin(x)).
43
+ fn precise_sincos_centered(x: f32) -> vec2<f32> {
44
+ var folded = x;
45
+ var cosineSign = 1.0;
46
+ if (folded > PRECISE_TRIG_HALF_PI) {
47
+ folded = PRECISE_TRIG_PI - folded;
48
+ cosineSign = -1.0;
49
+ } else if (folded < -PRECISE_TRIG_HALF_PI) {
50
+ folded = -PRECISE_TRIG_PI - folded;
51
+ cosineSign = -1.0;
52
+ }
53
+ let value = precise_sincos_half_pi(folded);
54
+ return vec2<f32>(cosineSign * value.x, value.y);
55
+ }
56
+
57
+
58
+ fn dft_precise_twiddle(phase: u32, period: u32, sign: f32) -> vec2<f32> {
59
+ let reducedPhase = phase % period;
60
+ if (reducedPhase == 0u) {
61
+ return vec2<f32>(1.0, sign * 0.0);
62
+ }
63
+ if (period % 4u == 0u) {
64
+ let quarter = period / 4u;
65
+ if (reducedPhase == quarter) {
66
+ return vec2<f32>(0.0, sign);
67
+ }
68
+ if (reducedPhase == 2u * quarter) {
69
+ return vec2<f32>(-1.0, sign * 0.0);
70
+ }
71
+ if (reducedPhase == 3u * quarter) {
72
+ return vec2<f32>(0.0, -sign);
73
+ }
74
+ }
75
+ // Center the rational turn before multiplying by 2*pi. This avoids the
76
+ // cancellation in `2*pi*(N-1)/N - 2*pi` for bins near the end of a row.
77
+ let turns = f32(reducedPhase) / f32(period);
78
+ let centeredTurns = turns - floor(turns + 0.5);
79
+ return precise_sincos_centered(sign * PRECISE_TRIG_TWO_PI * centeredTurns);
80
+ }
81
+
82
+
83
+ fn is_nyquist(freq: u32, signal_length: u32) -> bool {
84
+ return signal_length % 2u == 0u && freq * 2u == signal_length;
85
+ }
86
+
87
+ {% macro dft_naive_accumulate(transform_index, input_axis_length, output_axis_length, batch_index, other_index, include_batch=true, include_other=true) %}
88
+ var real = 0.0;
89
+ var imag = 0.0;
90
+
91
+ let inverse = params.inverse != 0u;
92
+ let onesided_inverse = inverse && params.onesided != 0u;
93
+ let signal_length = select({{ input_axis_length }}, {{ output_axis_length }}, onesided_inverse);
94
+
95
+ if (onesided_inverse) {
96
+ for (var freq = 0u; freq < {{ input_axis_length }}; freq = freq + 1u) {
97
+ let input_base = transform_input_base(freq{% if include_batch %}, {{ batch_index }}{% endif %}{% if include_other %}, {{ other_index }}{% endif %});
98
+ let xr = input[input_base];
99
+ var xi = 0.0;
100
+ if (params.inputComponents == 2u) {
101
+ xi = input[input_base + 1u];
102
+ }
103
+ let phase = ({{ transform_index }} * freq) % signal_length;
104
+ let twiddle = dft_precise_twiddle(phase, signal_length, 1.0);
105
+ let c = twiddle.x;
106
+ let s = twiddle.y;
107
+ var weight = 1.0;
108
+ if (freq != 0u && !is_nyquist(freq, signal_length)) {
109
+ weight = 2.0;
110
+ }
111
+ real = real + weight * (xr * c - xi * s);
112
+ imag = imag + weight * (xr * s + xi * c);
113
+ }
114
+ real = real / f32(signal_length);
115
+ imag = imag / f32(signal_length);
116
+ } else {
117
+ let sign = select(-1.0, 1.0, inverse);
118
+ for (var n = 0u; n < {{ input_axis_length }}; n = n + 1u) {
119
+ let input_base = transform_input_base(n{% if include_batch %}, {{ batch_index }}{% endif %}{% if include_other %}, {{ other_index }}{% endif %});
120
+ let xr = input[input_base];
121
+ var xi = 0.0;
122
+ if (params.inputComponents == 2u) {
123
+ xi = input[input_base + 1u];
124
+ }
125
+ let phase = ({{ transform_index }} * n) % signal_length;
126
+ let twiddle = dft_precise_twiddle(phase, signal_length, sign);
127
+ let c = twiddle.x;
128
+ let s = twiddle.y;
129
+ real = real + xr * c - xi * s;
130
+ imag = imag + xr * s + xi * c;
131
+ }
132
+ if (inverse) {
133
+ real = real / f32(signal_length);
134
+ imag = imag / f32(signal_length);
135
+ }
136
+ }
137
+ {% endmacro %}
138
+
139
+
140
+ fn transform_input_base(sample: u32{% if source.batched %}, batch: u32{% endif %}) -> u32 {
141
+ {% if source.batched %}
142
+ return (batch * params.signalLength + sample) * params.inputComponents;
143
+ {% else %}
144
+ return sample * params.inputComponents;
145
+ {% endif %}
146
+ }
147
+
148
+ @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
149
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) nwg: vec3<u32>) {
150
+ // Fold a long 1D output grid into y at the device dispatch limit.
151
+ let index = gid.x + gid.y * nwg.x * {{ tunables.WORKGROUP_SIZE }}u;
152
+ if (index >= params.count) {
153
+ return;
154
+ }
155
+
156
+ {% if source.batched %}
157
+ let transform_index = index % params.outputLength;
158
+ let batch = index / params.outputLength;
159
+ {{ dft_naive_accumulate("transform_index", "params.signalLength", "params.outputLength", "batch", "0u", true, false) }}
160
+ {% else %}
161
+ let transform_index = index;
162
+ {{ dft_naive_accumulate("transform_index", "params.signalLength", "params.outputLength", "0u", "0u", false, false) }}
163
+ {% endif %}
164
+
165
+ let output_base = index * params.outputComponents;
166
+ output[output_base] = real;
167
+ if (params.outputComponents == 2u) {
168
+ output[output_base + 1u] = imag;
169
+ }
170
+ }
build/webgpu/dft-general.wgsl.jinja ADDED
@@ -0,0 +1,221 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {{ env.wgsl.resourceDeclarations }}
2
+ // Backend-stable unit-circle twiddles for DFT kernels. Retain the phase as a
3
+ // rational turn and route all polynomial evaluation through the shared precise
4
+ // trig core so FFT, direct DFT, STFT, and cosine windows agree exactly.
5
+ //
6
+ // phase is in [0, period). sign is -1 for a forward transform and +1 for an
7
+ // inverse transform. The result is (cos, sin).
8
+ // Backend-stable f32 sine/cosine core.
9
+ //
10
+ // Shader transcendental accuracy is implementation-defined, and some portable
11
+ // backends are only accurate to roughly 1e-4. Each path retains the most accurate
12
+ // available phase representation, reduces it to [-pi, pi], then uses these
13
+ // polynomials. The half-pi core is degree 13 for sine and degree 12 for cosine;
14
+ // truncation error is well below one f32 ULP over its documented interval.
15
+ const PRECISE_TRIG_PI: f32 = 3.141592653589793;
16
+ const PRECISE_TRIG_TWO_PI: f32 = 6.283185307179586;
17
+ const PRECISE_TRIG_HALF_PI: f32 = 1.5707963267948966;
18
+
19
+ // Input must be in [-pi/2, pi/2]. Returns (cos(x), sin(x)).
20
+ fn precise_sincos_half_pi(x: f32) -> vec2<f32> {
21
+ let x2 = x * x;
22
+
23
+ var sinPolynomial = 1.6059043836821613e-10;
24
+ sinPolynomial = fma(sinPolynomial, x2, -2.505210838544172e-8);
25
+ sinPolynomial = fma(sinPolynomial, x2, 2.7557319223985893e-6);
26
+ sinPolynomial = fma(sinPolynomial, x2, -1.984126984126984e-4);
27
+ sinPolynomial = fma(sinPolynomial, x2, 8.333333333333333e-3);
28
+ sinPolynomial = fma(sinPolynomial, x2, -1.6666666666666666e-1);
29
+ let sine = x * fma(sinPolynomial, x2, 1.0);
30
+
31
+ var cosPolynomial = 2.08767569878681e-9;
32
+ cosPolynomial = fma(cosPolynomial, x2, -2.755731922398589e-7);
33
+ cosPolynomial = fma(cosPolynomial, x2, 2.48015873015873e-5);
34
+ cosPolynomial = fma(cosPolynomial, x2, -1.388888888888889e-3);
35
+ cosPolynomial = fma(cosPolynomial, x2, 4.1666666666666664e-2);
36
+ cosPolynomial = fma(cosPolynomial, x2, -5.0e-1);
37
+ let cosine = fma(cosPolynomial, x2, 1.0);
38
+
39
+ return vec2<f32>(cosine, sine);
40
+ }
41
+
42
+ // Input must be in [-pi, pi]. Returns (cos(x), sin(x)).
43
+ fn precise_sincos_centered(x: f32) -> vec2<f32> {
44
+ var folded = x;
45
+ var cosineSign = 1.0;
46
+ if (folded > PRECISE_TRIG_HALF_PI) {
47
+ folded = PRECISE_TRIG_PI - folded;
48
+ cosineSign = -1.0;
49
+ } else if (folded < -PRECISE_TRIG_HALF_PI) {
50
+ folded = -PRECISE_TRIG_PI - folded;
51
+ cosineSign = -1.0;
52
+ }
53
+ let value = precise_sincos_half_pi(folded);
54
+ return vec2<f32>(cosineSign * value.x, value.y);
55
+ }
56
+
57
+
58
+ fn dft_precise_twiddle(phase: u32, period: u32, sign: f32) -> vec2<f32> {
59
+ let reducedPhase = phase % period;
60
+ if (reducedPhase == 0u) {
61
+ return vec2<f32>(1.0, sign * 0.0);
62
+ }
63
+ if (period % 4u == 0u) {
64
+ let quarter = period / 4u;
65
+ if (reducedPhase == quarter) {
66
+ return vec2<f32>(0.0, sign);
67
+ }
68
+ if (reducedPhase == 2u * quarter) {
69
+ return vec2<f32>(-1.0, sign * 0.0);
70
+ }
71
+ if (reducedPhase == 3u * quarter) {
72
+ return vec2<f32>(0.0, -sign);
73
+ }
74
+ }
75
+ // Center the rational turn before multiplying by 2*pi. This avoids the
76
+ // cancellation in `2*pi*(N-1)/N - 2*pi` for bins near the end of a row.
77
+ let turns = f32(reducedPhase) / f32(period);
78
+ let centeredTurns = turns - floor(turns + 0.5);
79
+ return precise_sincos_centered(sign * PRECISE_TRIG_TWO_PI * centeredTurns);
80
+ }
81
+
82
+
83
+ // General naive DFT with an optional runtime transform axis and an optional
84
+ // runtime dft_length (opset-20 `axis` / `dft_length` tensor inputs). The output
85
+ // shape already encodes the per-axis transform length, so we read every output
86
+ // element's logical coords from the output dims, locate the transform axis, and
87
+ // sum over the (zero-padded / truncated) signal of `signal_length` samples.
88
+ //
89
+ // Layout: the last tensor dimension is the complex-component axis (1 = real,
90
+ // 2 = complex). The dims passed in params are the logical dims (tensor dims
91
+ // minus the component axis) packed into 7 slots; leading unused slots hold 1.
92
+ // `pad = 7 - n_logical` maps a logical axis to its padded slot, so the row-major
93
+ // stride math is uniform.
94
+ //
95
+ // Packing depends on whether the axis input is present. The default -2 axis
96
+ // uses a right-aligned (outer, axis, inner) shape. A runtime axis instead keeps
97
+ // every logical dimension right-aligned in the same seven slots (rank <= 8).
98
+
99
+ fn is_nyquist(freq: u32, signal_length: u32) -> bool {
100
+ return signal_length % 2u == 0u && freq * 2u == signal_length;
101
+ }
102
+
103
+ fn resolve_axis() -> u32 {
104
+ {% if source.hasAxisInput %}
105
+ let raw = axis_in[0];
106
+ let rank = i32(params.rank);
107
+ let norm = select(raw, raw + rank, raw < 0);
108
+ return u32(norm);
109
+ {% else %}
110
+ return params.axisAttr;
111
+ {% endif %}
112
+ }
113
+
114
+ @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
115
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) nwg: vec3<u32>) {
116
+ // 2D-folded flat index over output elements (one per complex sample).
117
+ let index = gid.x + gid.y * nwg.x * {{ tunables.WORKGROUP_SIZE }}u;
118
+ if (index >= params.count) { return; }
119
+
120
+ let in_dims = array<u32, 7>(params.inD0, params.inD1, params.inD2, params.inD3, params.inD4, params.inD5, params.inD6);
121
+ let out_dims = array<u32, 7>(params.outD0, params.outD1, params.outD2, params.outD3, params.outD4, params.outD5, params.outD6);
122
+
123
+ // pad = number of leading 1-slots; logical dims live in [pad .. 6].
124
+ let n_logical = params.rank - 1u;
125
+ let pad = 7u - n_logical;
126
+ let resolved_axis = resolve_axis();
127
+ {% if source.skipRuntimeAxis1 %}
128
+ // Complementary to the guarded shared-memory FFT pass in the dynamic-axis
129
+ // specialization. Axis 1 is already complete, so this dispatch is a uniform
130
+ // no-op for that case.
131
+ if (resolved_axis == 1u) { return; }
132
+ {% endif %}
133
+ let transform_slot = resolved_axis + pad;
134
+
135
+ // Decode the right-aligned logical output coords from the flat index.
136
+ var coords = array<u32, 7>(0u, 0u, 0u, 0u, 0u, 0u, 0u);
137
+ var rem = index;
138
+ for (var slot = 7u; slot > pad; slot = slot - 1u) {
139
+ let dim = out_dims[slot - 1u];
140
+ coords[slot - 1u] = rem % dim;
141
+ rem = rem / dim;
142
+ }
143
+
144
+ let transform_index = coords[transform_slot];
145
+ let input_axis_length = in_dims[transform_slot];
146
+ let output_axis_length = out_dims[transform_slot];
147
+
148
+ let inverse = params.inverse != 0u;
149
+ let onesided_inverse = inverse && params.onesided != 0u;
150
+ var signal_length = input_axis_length;
151
+ {% if source.hasDftLengthInput %}
152
+ if (dft_len_in[0] > 0) { signal_length = u32(dft_len_in[0]); }
153
+ {% else %}
154
+ if (params.dftLengthAttr > 0u) { signal_length = params.dftLengthAttr; }
155
+ {% endif %}
156
+ if (onesided_inverse) { signal_length = output_axis_length; }
157
+
158
+ var real = 0.0;
159
+ var imag = 0.0;
160
+
161
+ if (onesided_inverse) {
162
+ for (var freq = 0u; freq < input_axis_length; freq = freq + 1u) {
163
+ var in_coords = coords;
164
+ in_coords[transform_slot] = freq;
165
+ var in_linear = 0u;
166
+ for (var slot = 0u; slot < 7u; slot = slot + 1u) {
167
+ in_linear = in_linear * in_dims[slot] + in_coords[slot];
168
+ }
169
+ let in_base = in_linear * params.inputComponents;
170
+ let xr = input[in_base];
171
+ var xi = 0.0;
172
+ if (params.inputComponents == 2u) { xi = input[in_base + 1u]; }
173
+ // Range-reduce the phase: (k*n mod N) keeps |angle| < 2*PI so cos/sin stay
174
+ // accurate. f32(k)*f32(n) for k,n up to ~2047 reaches ~12867 rad where f32
175
+ // ULP (~1e-3 rad) corrupts the twiddle; k*n is exact in u32 here.
176
+ let kn_mod = (transform_index * freq) % signal_length;
177
+ let twiddle = dft_precise_twiddle(kn_mod, signal_length, 1.0);
178
+ let c = twiddle.x;
179
+ let s = twiddle.y;
180
+ var weight = 1.0;
181
+ if (freq != 0u && !is_nyquist(freq, signal_length)) { weight = 2.0; }
182
+ real = real + weight * (xr * c - xi * s);
183
+ imag = imag + weight * (xr * s + xi * c);
184
+ }
185
+ real = real / f32(signal_length);
186
+ imag = imag / f32(signal_length);
187
+ } else {
188
+ let sign = select(-1.0, 1.0, inverse);
189
+ for (var n = 0u; n < signal_length; n = n + 1u) {
190
+ if (n >= input_axis_length) { continue; }
191
+ var in_coords = coords;
192
+ in_coords[transform_slot] = n;
193
+ var in_linear = 0u;
194
+ for (var slot = 0u; slot < 7u; slot = slot + 1u) {
195
+ in_linear = in_linear * in_dims[slot] + in_coords[slot];
196
+ }
197
+ let in_base = in_linear * params.inputComponents;
198
+ let xr = input[in_base];
199
+ var xi = 0.0;
200
+ if (params.inputComponents == 2u) { xi = input[in_base + 1u]; }
201
+ // Range-reduce the phase (see onesided_inverse branch): (k*n mod N) keeps
202
+ // |angle| < 2*PI; k*n is exact in u32 for the naive path's index range.
203
+ let kn_mod = (transform_index * n) % signal_length;
204
+ let twiddle = dft_precise_twiddle(kn_mod, signal_length, sign);
205
+ let c = twiddle.x;
206
+ let s = twiddle.y;
207
+ real = real + xr * c - xi * s;
208
+ imag = imag + xr * s + xi * c;
209
+ }
210
+ if (inverse) {
211
+ real = real / f32(signal_length);
212
+ imag = imag / f32(signal_length);
213
+ }
214
+ }
215
+
216
+ let output_base = index * params.outputComponents;
217
+ output[output_base] = real;
218
+ if (params.outputComponents == 2u) {
219
+ output[output_base + 1u] = imag;
220
+ }
221
+ }
build/webgpu/dft-rank4.wgsl.jinja ADDED
@@ -0,0 +1,205 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% macro flat_index_2d(name="i", bound="params.count", guardInline=false, note="dispatch-limit") %}
2
+ {% if note == "dispatch-limit" %}
3
+ // 2D-folded flat index: gid.y carries the high bits past the
4
+ // maxComputeWorkgroupsPerDimension dispatch limit (outputs > 16.7M elements).
5
+ {% elif note == "limit" %}
6
+ // 2D-folded flat index: gid.y carries the high bits past the
7
+ // maxComputeWorkgroupsPerDimension limit.
8
+ {% elif note == "device-axis" %}
9
+ // The flat dispatch is folded across x/y at the device's per-axis workgroup
10
+ // limit; gid.y carries the high portion of the output index.
11
+ {% elif note == "vec4-limit" %}
12
+ // 2D-folded flat vec4 index: gid.y carries the high bits past the
13
+ // maxComputeWorkgroupsPerDimension limit (the dispatch caps x and spills into y).
14
+ {% elif note == "element-limit" %}
15
+ // 2D-folded flat element index: gid.y carries the high bits past the
16
+ // maxComputeWorkgroupsPerDimension limit.
17
+ {% elif note == "dispatch" %}
18
+ // 2D-folded flat index: gid.y carries the high bits past the
19
+ // maxComputeWorkgroupsPerDimension dispatch limit.
20
+ {% endif %}
21
+ {% if bound == "" %}
22
+ let {{ name }} = gid.x + gid.y * nwg.x * {{ tunables.WORKGROUP_SIZE }}u;
23
+ {%- elif guardInline %}
24
+ let {{ name }} = gid.x + gid.y * nwg.x * {{ tunables.WORKGROUP_SIZE }}u;
25
+ if ({{ name }} >= {{ bound }}) { return; }
26
+ {%- else %}
27
+ let {{ name }} = gid.x + gid.y * nwg.x * {{ tunables.WORKGROUP_SIZE }}u;
28
+ if ({{ name }} >= {{ bound }}) {
29
+ return;
30
+ }
31
+ {%- endif %}
32
+ {% endmacro %}
33
+
34
+ {{ env.wgsl.resourceDeclarations }}
35
+ // Backend-stable unit-circle twiddles for DFT kernels. Retain the phase as a
36
+ // rational turn and route all polynomial evaluation through the shared precise
37
+ // trig core so FFT, direct DFT, STFT, and cosine windows agree exactly.
38
+ //
39
+ // phase is in [0, period). sign is -1 for a forward transform and +1 for an
40
+ // inverse transform. The result is (cos, sin).
41
+ // Backend-stable f32 sine/cosine core.
42
+ //
43
+ // Shader transcendental accuracy is implementation-defined, and some portable
44
+ // backends are only accurate to roughly 1e-4. Each path retains the most accurate
45
+ // available phase representation, reduces it to [-pi, pi], then uses these
46
+ // polynomials. The half-pi core is degree 13 for sine and degree 12 for cosine;
47
+ // truncation error is well below one f32 ULP over its documented interval.
48
+ const PRECISE_TRIG_PI: f32 = 3.141592653589793;
49
+ const PRECISE_TRIG_TWO_PI: f32 = 6.283185307179586;
50
+ const PRECISE_TRIG_HALF_PI: f32 = 1.5707963267948966;
51
+
52
+ // Input must be in [-pi/2, pi/2]. Returns (cos(x), sin(x)).
53
+ fn precise_sincos_half_pi(x: f32) -> vec2<f32> {
54
+ let x2 = x * x;
55
+
56
+ var sinPolynomial = 1.6059043836821613e-10;
57
+ sinPolynomial = fma(sinPolynomial, x2, -2.505210838544172e-8);
58
+ sinPolynomial = fma(sinPolynomial, x2, 2.7557319223985893e-6);
59
+ sinPolynomial = fma(sinPolynomial, x2, -1.984126984126984e-4);
60
+ sinPolynomial = fma(sinPolynomial, x2, 8.333333333333333e-3);
61
+ sinPolynomial = fma(sinPolynomial, x2, -1.6666666666666666e-1);
62
+ let sine = x * fma(sinPolynomial, x2, 1.0);
63
+
64
+ var cosPolynomial = 2.08767569878681e-9;
65
+ cosPolynomial = fma(cosPolynomial, x2, -2.755731922398589e-7);
66
+ cosPolynomial = fma(cosPolynomial, x2, 2.48015873015873e-5);
67
+ cosPolynomial = fma(cosPolynomial, x2, -1.388888888888889e-3);
68
+ cosPolynomial = fma(cosPolynomial, x2, 4.1666666666666664e-2);
69
+ cosPolynomial = fma(cosPolynomial, x2, -5.0e-1);
70
+ let cosine = fma(cosPolynomial, x2, 1.0);
71
+
72
+ return vec2<f32>(cosine, sine);
73
+ }
74
+
75
+ // Input must be in [-pi, pi]. Returns (cos(x), sin(x)).
76
+ fn precise_sincos_centered(x: f32) -> vec2<f32> {
77
+ var folded = x;
78
+ var cosineSign = 1.0;
79
+ if (folded > PRECISE_TRIG_HALF_PI) {
80
+ folded = PRECISE_TRIG_PI - folded;
81
+ cosineSign = -1.0;
82
+ } else if (folded < -PRECISE_TRIG_HALF_PI) {
83
+ folded = -PRECISE_TRIG_PI - folded;
84
+ cosineSign = -1.0;
85
+ }
86
+ let value = precise_sincos_half_pi(folded);
87
+ return vec2<f32>(cosineSign * value.x, value.y);
88
+ }
89
+
90
+
91
+ fn dft_precise_twiddle(phase: u32, period: u32, sign: f32) -> vec2<f32> {
92
+ let reducedPhase = phase % period;
93
+ if (reducedPhase == 0u) {
94
+ return vec2<f32>(1.0, sign * 0.0);
95
+ }
96
+ if (period % 4u == 0u) {
97
+ let quarter = period / 4u;
98
+ if (reducedPhase == quarter) {
99
+ return vec2<f32>(0.0, sign);
100
+ }
101
+ if (reducedPhase == 2u * quarter) {
102
+ return vec2<f32>(-1.0, sign * 0.0);
103
+ }
104
+ if (reducedPhase == 3u * quarter) {
105
+ return vec2<f32>(0.0, -sign);
106
+ }
107
+ }
108
+ // Center the rational turn before multiplying by 2*pi. This avoids the
109
+ // cancellation in `2*pi*(N-1)/N - 2*pi` for bins near the end of a row.
110
+ let turns = f32(reducedPhase) / f32(period);
111
+ let centeredTurns = turns - floor(turns + 0.5);
112
+ return precise_sincos_centered(sign * PRECISE_TRIG_TWO_PI * centeredTurns);
113
+ }
114
+
115
+
116
+ fn is_nyquist(freq: u32, signal_length: u32) -> bool {
117
+ return signal_length % 2u == 0u && freq * 2u == signal_length;
118
+ }
119
+
120
+ {% macro dft_naive_accumulate(transform_index, input_axis_length, output_axis_length, batch_index, other_index, include_batch=true, include_other=true) %}
121
+ var real = 0.0;
122
+ var imag = 0.0;
123
+
124
+ let inverse = params.inverse != 0u;
125
+ let onesided_inverse = inverse && params.onesided != 0u;
126
+ let signal_length = select({{ input_axis_length }}, {{ output_axis_length }}, onesided_inverse);
127
+
128
+ if (onesided_inverse) {
129
+ for (var freq = 0u; freq < {{ input_axis_length }}; freq = freq + 1u) {
130
+ let input_base = transform_input_base(freq{% if include_batch %}, {{ batch_index }}{% endif %}{% if include_other %}, {{ other_index }}{% endif %});
131
+ let xr = input[input_base];
132
+ var xi = 0.0;
133
+ if (params.inputComponents == 2u) {
134
+ xi = input[input_base + 1u];
135
+ }
136
+ let phase = ({{ transform_index }} * freq) % signal_length;
137
+ let twiddle = dft_precise_twiddle(phase, signal_length, 1.0);
138
+ let c = twiddle.x;
139
+ let s = twiddle.y;
140
+ var weight = 1.0;
141
+ if (freq != 0u && !is_nyquist(freq, signal_length)) {
142
+ weight = 2.0;
143
+ }
144
+ real = real + weight * (xr * c - xi * s);
145
+ imag = imag + weight * (xr * s + xi * c);
146
+ }
147
+ real = real / f32(signal_length);
148
+ imag = imag / f32(signal_length);
149
+ } else {
150
+ let sign = select(-1.0, 1.0, inverse);
151
+ for (var n = 0u; n < {{ input_axis_length }}; n = n + 1u) {
152
+ let input_base = transform_input_base(n{% if include_batch %}, {{ batch_index }}{% endif %}{% if include_other %}, {{ other_index }}{% endif %});
153
+ let xr = input[input_base];
154
+ var xi = 0.0;
155
+ if (params.inputComponents == 2u) {
156
+ xi = input[input_base + 1u];
157
+ }
158
+ let phase = ({{ transform_index }} * n) % signal_length;
159
+ let twiddle = dft_precise_twiddle(phase, signal_length, sign);
160
+ let c = twiddle.x;
161
+ let s = twiddle.y;
162
+ real = real + xr * c - xi * s;
163
+ imag = imag + xr * s + xi * c;
164
+ }
165
+ if (inverse) {
166
+ real = real / f32(signal_length);
167
+ imag = imag / f32(signal_length);
168
+ }
169
+ }
170
+ {% endmacro %}
171
+
172
+
173
+ fn input_index(batch: u32, d1: u32, d2: u32) -> u32 {
174
+ return ((batch * params.inputD1 + d1) * params.inputD2 + d2) * params.inputComponents;
175
+ }
176
+
177
+ fn output_index(batch: u32, d1: u32, d2: u32) -> u32 {
178
+ return ((batch * params.outputD1 + d1) * params.outputD2 + d2) * params.outputComponents;
179
+ }
180
+
181
+ fn transform_input_base(sample: u32, batch: u32, other: u32) -> u32 {
182
+ return input_index(batch, other, sample);
183
+ }
184
+
185
+ @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
186
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) nwg: vec3<u32>) {
187
+ {{ flat_index_2d("index", guardInline=true) }}
188
+
189
+ let out_d2 = index % params.outputD2;
190
+ var t = index / params.outputD2;
191
+ let out_d1 = t % params.outputD1;
192
+ let batch = t / params.outputD1;
193
+ let transform_index = out_d2;
194
+ let other_index = out_d1;
195
+ let input_axis_length = params.inputD2;
196
+ let output_axis_length = params.outputD2;
197
+
198
+ {{ dft_naive_accumulate("transform_index", "input_axis_length", "output_axis_length", "batch", "other_index") }}
199
+
200
+ let out_base = output_index(batch, out_d1, out_d2);
201
+ output[out_base] = real;
202
+ if (params.outputComponents == 2u) {
203
+ output[out_base + 1u] = imag;
204
+ }
205
+ }
build/webgpu/dft-tiled-real.wgsl.jinja ADDED
@@ -0,0 +1,136 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Portable direct transform for large real signals. A 256-lane workgroup owns
2
+ // 256 adjacent frequency bins and stages one input row or windowed STFT frame
3
+ // once, avoiding a global reload per (frequency, sample) pair. Twiddles advance
4
+ // by complex multiplication and reset from sin/cos every 64 samples to bound
5
+ // phase drift without transcendental functions in the inner loop.
6
+ {{ env.wgsl.resourceDeclarations }}
7
+ // Backend-stable unit-circle twiddles for DFT kernels. Retain the phase as a
8
+ // rational turn and route all polynomial evaluation through the shared precise
9
+ // trig core so FFT, direct DFT, STFT, and cosine windows agree exactly.
10
+ //
11
+ // phase is in [0, period). sign is -1 for a forward transform and +1 for an
12
+ // inverse transform. The result is (cos, sin).
13
+ // Backend-stable f32 sine/cosine core.
14
+ //
15
+ // Shader transcendental accuracy is implementation-defined, and some portable
16
+ // backends are only accurate to roughly 1e-4. Each path retains the most accurate
17
+ // available phase representation, reduces it to [-pi, pi], then uses these
18
+ // polynomials. The half-pi core is degree 13 for sine and degree 12 for cosine;
19
+ // truncation error is well below one f32 ULP over its documented interval.
20
+ const PRECISE_TRIG_PI: f32 = 3.141592653589793;
21
+ const PRECISE_TRIG_TWO_PI: f32 = 6.283185307179586;
22
+ const PRECISE_TRIG_HALF_PI: f32 = 1.5707963267948966;
23
+
24
+ // Input must be in [-pi/2, pi/2]. Returns (cos(x), sin(x)).
25
+ fn precise_sincos_half_pi(x: f32) -> vec2<f32> {
26
+ let x2 = x * x;
27
+
28
+ var sinPolynomial = 1.6059043836821613e-10;
29
+ sinPolynomial = fma(sinPolynomial, x2, -2.505210838544172e-8);
30
+ sinPolynomial = fma(sinPolynomial, x2, 2.7557319223985893e-6);
31
+ sinPolynomial = fma(sinPolynomial, x2, -1.984126984126984e-4);
32
+ sinPolynomial = fma(sinPolynomial, x2, 8.333333333333333e-3);
33
+ sinPolynomial = fma(sinPolynomial, x2, -1.6666666666666666e-1);
34
+ let sine = x * fma(sinPolynomial, x2, 1.0);
35
+
36
+ var cosPolynomial = 2.08767569878681e-9;
37
+ cosPolynomial = fma(cosPolynomial, x2, -2.755731922398589e-7);
38
+ cosPolynomial = fma(cosPolynomial, x2, 2.48015873015873e-5);
39
+ cosPolynomial = fma(cosPolynomial, x2, -1.388888888888889e-3);
40
+ cosPolynomial = fma(cosPolynomial, x2, 4.1666666666666664e-2);
41
+ cosPolynomial = fma(cosPolynomial, x2, -5.0e-1);
42
+ let cosine = fma(cosPolynomial, x2, 1.0);
43
+
44
+ return vec2<f32>(cosine, sine);
45
+ }
46
+
47
+ // Input must be in [-pi, pi]. Returns (cos(x), sin(x)).
48
+ fn precise_sincos_centered(x: f32) -> vec2<f32> {
49
+ var folded = x;
50
+ var cosineSign = 1.0;
51
+ if (folded > PRECISE_TRIG_HALF_PI) {
52
+ folded = PRECISE_TRIG_PI - folded;
53
+ cosineSign = -1.0;
54
+ } else if (folded < -PRECISE_TRIG_HALF_PI) {
55
+ folded = -PRECISE_TRIG_PI - folded;
56
+ cosineSign = -1.0;
57
+ }
58
+ let value = precise_sincos_half_pi(folded);
59
+ return vec2<f32>(cosineSign * value.x, value.y);
60
+ }
61
+
62
+
63
+ fn dft_precise_twiddle(phase: u32, period: u32, sign: f32) -> vec2<f32> {
64
+ let reducedPhase = phase % period;
65
+ if (reducedPhase == 0u) {
66
+ return vec2<f32>(1.0, sign * 0.0);
67
+ }
68
+ if (period % 4u == 0u) {
69
+ let quarter = period / 4u;
70
+ if (reducedPhase == quarter) {
71
+ return vec2<f32>(0.0, sign);
72
+ }
73
+ if (reducedPhase == 2u * quarter) {
74
+ return vec2<f32>(-1.0, sign * 0.0);
75
+ }
76
+ if (reducedPhase == 3u * quarter) {
77
+ return vec2<f32>(0.0, -sign);
78
+ }
79
+ }
80
+ // Center the rational turn before multiplying by 2*pi. This avoids the
81
+ // cancellation in `2*pi*(N-1)/N - 2*pi` for bins near the end of a row.
82
+ let turns = f32(reducedPhase) / f32(period);
83
+ let centeredTurns = turns - floor(turns + 0.5);
84
+ return precise_sincos_centered(sign * PRECISE_TRIG_TWO_PI * centeredTurns);
85
+ }
86
+
87
+ const N: u32 = {{ source.transformLength }}u;
88
+ const NUM_BINS: u32 = {{ source.numBins }}u;
89
+ const WG: u32 = {{ source.workgroupSize }}u;
90
+ const TWIDDLE_RESET: u32 = 64u;
91
+
92
+ var<workgroup> signalTile: array<f32, N>;
93
+
94
+ @compute @workgroup_size(WG, 1, 1)
95
+ fn main(
96
+ @builtin(workgroup_id) wg: vec3<u32>,
97
+ @builtin(local_invocation_id) lid: vec3<u32>,
98
+ ) {
99
+ let lane = lid.x;
100
+ let batch = wg.y;
101
+ for (var n = lane; n < N; n = n + WG) {
102
+ signalTile[n] = input[(batch * N + n) * params.inner];
103
+ }
104
+ workgroupBarrier();
105
+
106
+ let freq = wg.x * WG + lane;
107
+ if (freq >= NUM_BINS) {
108
+ return;
109
+ }
110
+
111
+ let stepTwiddle = dft_precise_twiddle(freq, N, -1.0);
112
+ var real = 0.0;
113
+ var imag = 0.0;
114
+
115
+ for (var base = 0u; base < N; base = base + TWIDDLE_RESET) {
116
+ // Reduce the reset phase in integer space first. Computing angleStep*base
117
+ // directly loses enough f32 precision at high bins/long signals to create
118
+ // visible drift even though each 64-sample recurrence remains stable.
119
+ let phase = (freq * base) % N;
120
+ var twiddle = dft_precise_twiddle(phase, N, -1.0);
121
+ let end = min(N, base + TWIDDLE_RESET);
122
+ for (var n = base; n < end; n = n + 1u) {
123
+ let value = signalTile[n];
124
+ real = real + value * twiddle.x;
125
+ imag = imag + value * twiddle.y;
126
+ twiddle = vec2<f32>(
127
+ twiddle.x * stepTwiddle.x - twiddle.y * stepTwiddle.y,
128
+ twiddle.x * stepTwiddle.y + twiddle.y * stepTwiddle.x,
129
+ );
130
+ }
131
+ }
132
+
133
+ let outputBase = (batch * NUM_BINS + freq) * 2u;
134
+ output[outputBase] = real;
135
+ output[outputBase + 1u] = imag;
136
+ }
build/webgpu/fft-radix2-dit-storage.wgsl.jinja ADDED
@@ -0,0 +1,233 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Portable single-dispatch radix-2 DIT FFT for transforms too large for the
2
+ // guaranteed workgroup-storage budget. One workgroup owns each transform and
3
+ // uses either its full-spectrum output or a private global scratch row as an
4
+ // in-place complex buffer. Uniform storageBarrier calls replace the global
5
+ // barrier that would otherwise require one dispatch per FFT stage.
6
+ //
7
+ // Direct output is used only when each transform owns a contiguous full-spectrum
8
+ // row. Scratch-backed DFT supports one-sided output. STFT accepts real or
9
+ // complex windowed frames and copies NUM_BINS after the final stage.
10
+ {{ env.wgsl.resourceDeclarations }}
11
+ {% set STFT = source.mode == "stft" %}
12
+ {% set USE_SCRATCH = source.mode != "dft" %}
13
+ {% if not STFT %}
14
+ // Backend-stable unit-circle twiddles for DFT kernels. Retain the phase as a
15
+ // rational turn and route all polynomial evaluation through the shared precise
16
+ // trig core so FFT, direct DFT, STFT, and cosine windows agree exactly.
17
+ //
18
+ // phase is in [0, period). sign is -1 for a forward transform and +1 for an
19
+ // inverse transform. The result is (cos, sin).
20
+ {% set needPreciseTrigCentered = true %}
21
+ {% set needPreciseTrigTwoPi = true %}
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
+ {% if needPreciseTrigCentered %}
30
+ const PRECISE_TRIG_PI: f32 = 3.141592653589793;
31
+ {% endif %}
32
+ {% if needPreciseTrigTwoPi %}
33
+ const PRECISE_TRIG_TWO_PI: f32 = 6.283185307179586;
34
+ {% endif %}
35
+ {% if needPreciseTrigCentered %}
36
+ const PRECISE_TRIG_HALF_PI: f32 = 1.5707963267948966;
37
+ {% endif %}
38
+
39
+ // Input must be in [-pi/2, pi/2]. Returns (cos(x), sin(x)).
40
+ fn precise_sincos_half_pi(x: f32) -> vec2<f32> {
41
+ let x2 = x * x;
42
+
43
+ var sinPolynomial = 1.6059043836821613e-10;
44
+ sinPolynomial = fma(sinPolynomial, x2, -2.505210838544172e-8);
45
+ sinPolynomial = fma(sinPolynomial, x2, 2.7557319223985893e-6);
46
+ sinPolynomial = fma(sinPolynomial, x2, -1.984126984126984e-4);
47
+ sinPolynomial = fma(sinPolynomial, x2, 8.333333333333333e-3);
48
+ sinPolynomial = fma(sinPolynomial, x2, -1.6666666666666666e-1);
49
+ let sine = x * fma(sinPolynomial, x2, 1.0);
50
+
51
+ var cosPolynomial = 2.08767569878681e-9;
52
+ cosPolynomial = fma(cosPolynomial, x2, -2.755731922398589e-7);
53
+ cosPolynomial = fma(cosPolynomial, x2, 2.48015873015873e-5);
54
+ cosPolynomial = fma(cosPolynomial, x2, -1.388888888888889e-3);
55
+ cosPolynomial = fma(cosPolynomial, x2, 4.1666666666666664e-2);
56
+ cosPolynomial = fma(cosPolynomial, x2, -5.0e-1);
57
+ let cosine = fma(cosPolynomial, x2, 1.0);
58
+
59
+ return vec2<f32>(cosine, sine);
60
+ }
61
+ {% if needPreciseTrigCentered %}
62
+
63
+ // Input must be in [-pi, pi]. Returns (cos(x), sin(x)).
64
+ fn precise_sincos_centered(x: f32) -> vec2<f32> {
65
+ var folded = x;
66
+ var cosineSign = 1.0;
67
+ if (folded > PRECISE_TRIG_HALF_PI) {
68
+ folded = PRECISE_TRIG_PI - folded;
69
+ cosineSign = -1.0;
70
+ } else if (folded < -PRECISE_TRIG_HALF_PI) {
71
+ folded = -PRECISE_TRIG_PI - folded;
72
+ cosineSign = -1.0;
73
+ }
74
+ let value = precise_sincos_half_pi(folded);
75
+ return vec2<f32>(cosineSign * value.x, value.y);
76
+ }
77
+ {% endif %}
78
+
79
+
80
+ fn dft_precise_twiddle(phase: u32, period: u32, sign: f32) -> vec2<f32> {
81
+ let reducedPhase = phase % period;
82
+ if (reducedPhase == 0u) {
83
+ return vec2<f32>(1.0, sign * 0.0);
84
+ }
85
+ if (period % 4u == 0u) {
86
+ let quarter = period / 4u;
87
+ if (reducedPhase == quarter) {
88
+ return vec2<f32>(0.0, sign);
89
+ }
90
+ if (reducedPhase == 2u * quarter) {
91
+ return vec2<f32>(-1.0, sign * 0.0);
92
+ }
93
+ if (reducedPhase == 3u * quarter) {
94
+ return vec2<f32>(0.0, -sign);
95
+ }
96
+ }
97
+ // Center the rational turn before multiplying by 2*pi. This avoids the
98
+ // cancellation in `2*pi*(N-1)/N - 2*pi` for bins near the end of a row.
99
+ let turns = f32(reducedPhase) / f32(period);
100
+ let centeredTurns = turns - floor(turns + 0.5);
101
+ return precise_sincos_centered(sign * PRECISE_TRIG_TWO_PI * centeredTurns);
102
+ }
103
+
104
+ {% endif %}
105
+
106
+ {% if STFT %}
107
+ const PI: f32 = 3.141592653589793;
108
+ {% endif %}
109
+ const N: u32 = {{ source.fftLength }}u;
110
+ const N_HALF: u32 = N / 2u;
111
+ const LOG2_N: u32 = {{ source.log2Length }}u;
112
+ {% if USE_SCRATCH %}
113
+ const NUM_BINS: u32 = {{ source.numBins }}u;
114
+ {% endif %}
115
+ const WG: u32 = {{ tunables.WORKGROUP_SIZE }}u;
116
+ const SIGN: f32 = {{ "1.0" if source.inverse else "-1.0" }};
117
+
118
+ @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
119
+ fn main(
120
+ @builtin(workgroup_id) wgid: vec3<u32>,
121
+ @builtin(local_invocation_id) lid: vec3<u32>,
122
+ @builtin(num_workgroups) nwg: vec3<u32>,
123
+ ) {
124
+ let tid = lid.x;
125
+ let transform = wgid.x + wgid.y * nwg.x;
126
+ let transformBase = transform * N * 2u;
127
+
128
+ {% if STFT %}
129
+ let batch = transform / params.numFrames;
130
+ let frame = transform - batch * params.numFrames;
131
+ let start = frame * params.frameStep;
132
+ {% else %}
133
+ let outerIndex = transform / params.inner;
134
+ let innerIndex = transform - outerIndex * params.inner;
135
+ {% endif %}
136
+
137
+ for (var n = tid; n < N; n = n + WG) {
138
+ var value = vec2<f32>(0.0);
139
+ {% if STFT %}
140
+ let signalIndex = start + n;
141
+ let inputBase = (batch * params.signalLength + signalIndex) * {{ source.inComp }}u;
142
+ let w = window[n];
143
+ value.x = signal[inputBase] * w;
144
+ {% if source.inComp == 2 %}
145
+ value.y = signal[inputBase + 1u] * w;
146
+ {% endif %}
147
+ {% else %}
148
+ let inputBase = ((outerIndex * N + n) * params.inner + innerIndex) * {{ source.inComp }}u;
149
+ value.x = input[inputBase];
150
+ {% if source.inComp == 2 %}
151
+ value.y = input[inputBase + 1u];
152
+ {% endif %}
153
+ {% endif %}
154
+ let reversed = reverseBits(n) >> (32u - LOG2_N);
155
+ let dst = transformBase + reversed * 2u;
156
+ {% if USE_SCRATCH %}
157
+ scratch[dst] = value.x;
158
+ scratch[dst + 1u] = value.y;
159
+ {% else %}
160
+ output[dst] = value.x;
161
+ output[dst + 1u] = value.y;
162
+ {% endif %}
163
+ }
164
+ storageBarrier();
165
+
166
+ {% for stage in range(1, source.log2Length + 1) %}
167
+ {
168
+ const SPAN: u32 = 1u << {{ stage }}u;
169
+ const HALF: u32 = SPAN >> 1u;
170
+ for (var butterfly = tid; butterfly < N_HALF; butterfly = butterfly + WG) {
171
+ let group = butterfly / HALF;
172
+ let j = butterfly - group * HALF;
173
+ let lo = transformBase + (group * SPAN + j) * 2u;
174
+ let hi = lo + HALF * 2u;
175
+ {% if USE_SCRATCH %}
176
+ let a = vec2<f32>(scratch[lo], scratch[lo + 1u]);
177
+ let b = vec2<f32>(scratch[hi], scratch[hi + 1u]);
178
+ {% else %}
179
+ let a = vec2<f32>(output[lo], output[lo + 1u]);
180
+ let b = vec2<f32>(output[hi], output[hi + 1u]);
181
+ {% endif %}
182
+ {% if STFT %}
183
+ let angle = SIGN * 2.0 * PI * f32(j) / f32(SPAN);
184
+ let c = cos(angle);
185
+ let sn = sin(angle);
186
+ {% else %}
187
+ let twiddle = dft_precise_twiddle(j, SPAN, SIGN);
188
+ let c = twiddle.x;
189
+ let sn = twiddle.y;
190
+ {% endif %}
191
+ let t = vec2<f32>(b.x * c - b.y * sn, b.x * sn + b.y * c);
192
+ let sum = a + t;
193
+ let difference = a - t;
194
+ {% if USE_SCRATCH %}
195
+ scratch[lo] = sum.x;
196
+ scratch[lo + 1u] = sum.y;
197
+ scratch[hi] = difference.x;
198
+ scratch[hi + 1u] = difference.y;
199
+ {% else %}
200
+ output[lo] = sum.x;
201
+ output[lo + 1u] = sum.y;
202
+ output[hi] = difference.x;
203
+ output[hi + 1u] = difference.y;
204
+ {% endif %}
205
+ }
206
+ storageBarrier();
207
+ }
208
+ {% endfor %}
209
+
210
+ {% if source.inverse %}
211
+ const SCALE: f32 = 1.0 / f32(N);
212
+ {% elif USE_SCRATCH %}
213
+ const SCALE: f32 = 1.0;
214
+ {% endif %}
215
+ {% if USE_SCRATCH %}
216
+ for (var k = tid; k < NUM_BINS; k = k + WG) {
217
+ let src = transformBase + k * 2u;
218
+ {% if STFT %}
219
+ let dst = (transform * NUM_BINS + k) * 2u;
220
+ {% else %}
221
+ let dst = ((outerIndex * NUM_BINS + k) * params.inner + innerIndex) * 2u;
222
+ {% endif %}
223
+ output[dst] = scratch[src] * SCALE;
224
+ output[dst + 1u] = scratch[src + 1u] * SCALE;
225
+ }
226
+ {% elif source.inverse %}
227
+ for (var k = tid; k < N; k = k + WG) {
228
+ let index = transformBase + k * 2u;
229
+ output[index] = output[index] * SCALE;
230
+ output[index + 1u] = output[index + 1u] * SCALE;
231
+ }
232
+ {% endif %}
233
+ }
build/webgpu/fft-stockham-shared.wgsl.jinja ADDED
@@ -0,0 +1,193 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {{ env.wgsl.resourceDeclarations }}
2
+ // Backend-stable unit-circle twiddles for DFT kernels. Retain the phase as a
3
+ // rational turn and route all polynomial evaluation through the shared precise
4
+ // trig core so FFT, direct DFT, STFT, and cosine windows agree exactly.
5
+ //
6
+ // phase is in [0, period). sign is -1 for a forward transform and +1 for an
7
+ // inverse transform. The result is (cos, sin).
8
+ // Backend-stable f32 sine/cosine core.
9
+ //
10
+ // Shader transcendental accuracy is implementation-defined, and some portable
11
+ // backends are only accurate to roughly 1e-4. Each path retains the most accurate
12
+ // available phase representation, reduces it to [-pi, pi], then uses these
13
+ // polynomials. The half-pi core is degree 13 for sine and degree 12 for cosine;
14
+ // truncation error is well below one f32 ULP over its documented interval.
15
+ const PRECISE_TRIG_PI: f32 = 3.141592653589793;
16
+ const PRECISE_TRIG_TWO_PI: f32 = 6.283185307179586;
17
+ const PRECISE_TRIG_HALF_PI: f32 = 1.5707963267948966;
18
+
19
+ // Input must be in [-pi/2, pi/2]. Returns (cos(x), sin(x)).
20
+ fn precise_sincos_half_pi(x: f32) -> vec2<f32> {
21
+ let x2 = x * x;
22
+
23
+ var sinPolynomial = 1.6059043836821613e-10;
24
+ sinPolynomial = fma(sinPolynomial, x2, -2.505210838544172e-8);
25
+ sinPolynomial = fma(sinPolynomial, x2, 2.7557319223985893e-6);
26
+ sinPolynomial = fma(sinPolynomial, x2, -1.984126984126984e-4);
27
+ sinPolynomial = fma(sinPolynomial, x2, 8.333333333333333e-3);
28
+ sinPolynomial = fma(sinPolynomial, x2, -1.6666666666666666e-1);
29
+ let sine = x * fma(sinPolynomial, x2, 1.0);
30
+
31
+ var cosPolynomial = 2.08767569878681e-9;
32
+ cosPolynomial = fma(cosPolynomial, x2, -2.755731922398589e-7);
33
+ cosPolynomial = fma(cosPolynomial, x2, 2.48015873015873e-5);
34
+ cosPolynomial = fma(cosPolynomial, x2, -1.388888888888889e-3);
35
+ cosPolynomial = fma(cosPolynomial, x2, 4.1666666666666664e-2);
36
+ cosPolynomial = fma(cosPolynomial, x2, -5.0e-1);
37
+ let cosine = fma(cosPolynomial, x2, 1.0);
38
+
39
+ return vec2<f32>(cosine, sine);
40
+ }
41
+
42
+ // Input must be in [-pi, pi]. Returns (cos(x), sin(x)).
43
+ fn precise_sincos_centered(x: f32) -> vec2<f32> {
44
+ var folded = x;
45
+ var cosineSign = 1.0;
46
+ if (folded > PRECISE_TRIG_HALF_PI) {
47
+ folded = PRECISE_TRIG_PI - folded;
48
+ cosineSign = -1.0;
49
+ } else if (folded < -PRECISE_TRIG_HALF_PI) {
50
+ folded = -PRECISE_TRIG_PI - folded;
51
+ cosineSign = -1.0;
52
+ }
53
+ let value = precise_sincos_half_pi(folded);
54
+ return vec2<f32>(cosineSign * value.x, value.y);
55
+ }
56
+
57
+
58
+ fn dft_precise_twiddle(phase: u32, period: u32, sign: f32) -> vec2<f32> {
59
+ let reducedPhase = phase % period;
60
+ if (reducedPhase == 0u) {
61
+ return vec2<f32>(1.0, sign * 0.0);
62
+ }
63
+ if (period % 4u == 0u) {
64
+ let quarter = period / 4u;
65
+ if (reducedPhase == quarter) {
66
+ return vec2<f32>(0.0, sign);
67
+ }
68
+ if (reducedPhase == 2u * quarter) {
69
+ return vec2<f32>(-1.0, sign * 0.0);
70
+ }
71
+ if (reducedPhase == 3u * quarter) {
72
+ return vec2<f32>(0.0, -sign);
73
+ }
74
+ }
75
+ // Center the rational turn before multiplying by 2*pi. This avoids the
76
+ // cancellation in `2*pi*(N-1)/N - 2*pi` for bins near the end of a row.
77
+ let turns = f32(reducedPhase) / f32(period);
78
+ let centeredTurns = turns - floor(turns + 0.5);
79
+ return precise_sincos_centered(sign * PRECISE_TRIG_TWO_PI * centeredTurns);
80
+ }
81
+
82
+
83
+ // Radix-2 Stockham autosort FFT: one transform per workgroup, entirely in
84
+ // shared memory. log2(N) ping-pong stages alternate between the two halves of
85
+ // a 2*N scratch pair (no bit reversal; the autosort write pattern leaves the
86
+ // final stage in natural order). Threads stride over the N/2 butterflies of
87
+ // each stage with a workgroupBarrier between stages. Large transform batches
88
+ // may bind a compact precomputed twiddle table; smaller batches evaluate the
89
+ // same roots inline and avoid the extra dispatch.
90
+ // Mode "dft" views the input as [outer, N, inner, inComp] and the output as
91
+ // [outer, numBins, inner, 2]; params.inner carries the (runtime) inner count
92
+ // and dispatch.x = outer * inner workgroups. Mode "stft" runs one workgroup
93
+ // per (batch, frame): the window multiply is fused into the frame load. STFT
94
+ // selection admits only complete frames, so every frame load is in bounds. The
95
+ // inverse+onesided (IRFFT) combination is outside this specialization.
96
+ // Mode "logmel" runs one workgroup per spectrogram frame of
97
+ // a real mono signal: the frame load is centered (start - N/2) with reflection
98
+ // below zero and zeros at/after params.audioLength, and the epilogue folds the
99
+ // one-sided power spectrum through packed triangular mel bands into log10
100
+ // mel-major output instead of storing complex bins.
101
+
102
+ const N: u32 = {{ source.fftLength }}u;
103
+ const N_HALF: u32 = N / 2u;
104
+ const NUM_BINS: u32 = {{ source.numBins }}u;
105
+ const WG: u32 = {{ tunables.WORKGROUP_SIZE }}u;
106
+ // Twiddle = exp(SIGN * i * 2*pi * k / span); SIGN flips for the inverse
107
+ // transform, exactly like the naive kernels' `sign` select.
108
+ const SIGN: f32 = {{ "1.0" if source.inverse else "-1.0" }};
109
+
110
+
111
+ // Ping-pong scratch: stage reads [srcOff, srcOff+N), writes [dstOff, dstOff+N).
112
+ var<workgroup> shRe: array<f32, 2u * N>;
113
+ var<workgroup> shIm: array<f32, 2u * N>;
114
+
115
+ @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
116
+ fn main(@builtin(workgroup_id) wgid: vec3<u32>,
117
+ @builtin(local_invocation_id) lid: vec3<u32>,
118
+ @builtin(num_workgroups) nwg: vec3<u32>) {
119
+ let tid = lid.x;
120
+ {% if source.runtimeAxis is defined and source.runtimeAxis %}
121
+ // Dynamic-axis DFT pairs this axis-specialized pass with a general pass.
122
+ // The uniform guard ensures exactly one performs work for each invocation.
123
+ let rawAxis = axis_in[0];
124
+ let normalizedAxis = select(rawAxis, rawAxis + {{ source.axisRank }}, rawAxis < 0);
125
+ if (normalizedAxis != {{ source.expectedAxis }}) { return; }
126
+ {% endif %}
127
+ // Flat workgroup index, 2D-folded: dispatches that would exceed the
128
+ // maxComputeWorkgroupsPerDimension limit on a single axis split the
129
+ // one-workgroup-per-(batch,frame) [or per-(outer,inner)] grid across x/y and
130
+ // recover the flat index here. Collapses to wgid.x when nwg.y == 1 (every DFT
131
+ // dispatch and any STFT dispatch under the per-axis limit).
132
+ let flat = wgid.x + wgid.y * nwg.x;
133
+
134
+ let outerIndex = flat / params.inner;
135
+ let innerIndex = flat - outerIndex * params.inner;
136
+ for (var n = tid; n < N; n = n + WG) {
137
+ let base = ((outerIndex * N + n) * params.inner + innerIndex) * {{ source.inComp }}u;
138
+ shRe[n] = input[base];
139
+ {% if source.inComp == 2 %}
140
+ shIm[n] = input[base + 1u];
141
+ {% else %}
142
+ shIm[n] = 0.0;
143
+ {% endif %}
144
+ }
145
+ workgroupBarrier();
146
+
147
+ // Stockham stages. Stage s (ns = 2^s) reads a = src[j], b = src[j + RADIX2_N/2],
148
+ // applies w = exp(SIGN*i*pi*(j mod ns)/ns) to b, and writes
149
+ // dst[(j div ns)*2*ns + (j mod ns)] = a + w*b
150
+ // dst[(j div ns)*2*ns + (j mod ns) + ns] = a - w*b
151
+ // |angle| <= pi, so twiddle accuracy never depends on large-argument
152
+ // sin/cos reduction (unlike the naive kernels' k*n angles).
153
+ var srcOff = 0u;
154
+ var dstOff = N;
155
+ for (var s = 0u; (1u << s) < N; s = s + 1u) {
156
+ let ns = 1u << s;
157
+ for (var j = tid; j < N_HALF; j = j + WG) {
158
+ let jm = j & (ns - 1u);
159
+ let outBase = ((j >> s) << (s + 1u)) + jm;
160
+ let ar = shRe[srcOff + j];
161
+ let ai = shIm[srcOff + j];
162
+ let br = shRe[srcOff + N_HALF + j];
163
+ let bi = shIm[srcOff + N_HALF + j];
164
+ let twiddle = dft_precise_twiddle(jm, 2u * ns, SIGN);
165
+ let c = twiddle.x;
166
+ let sn = twiddle.y;
167
+ let tr = br * c - bi * sn;
168
+ let ti = br * sn + bi * c;
169
+ shRe[dstOff + outBase] = ar + tr;
170
+ shIm[dstOff + outBase] = ai + ti;
171
+ shRe[dstOff + outBase + ns] = ar - tr;
172
+ shIm[dstOff + outBase + ns] = ai - ti;
173
+ }
174
+ workgroupBarrier();
175
+ let swap = srcOff;
176
+ srcOff = dstOff;
177
+ dstOff = swap;
178
+ }
179
+
180
+ // The final swap leaves the natural-order result at srcOff. The 1/N inverse
181
+ // scale is a power-of-two reciprocal, so the multiply is exact and matches
182
+ // the naive kernels' division.
183
+ {% if source.inverse %}
184
+ const SCALE: f32 = 1.0 / f32(N);
185
+ {% else %}
186
+ const SCALE: f32 = 1.0;
187
+ {% endif %}
188
+ for (var k = tid; k < NUM_BINS; k = k + WG) {
189
+ let outBase = ((outerIndex * NUM_BINS + k) * params.inner + innerIndex) * 2u;
190
+ output[outBase] = shRe[srcOff + k] * SCALE;
191
+ output[outBase + 1u] = shIm[srcOff + k] * SCALE;
192
+ }
193
+ }
build/webgpu/manifest.json ADDED
@@ -0,0 +1,816 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "domain": "ai.onnx",
3
+ "name": "DFT",
4
+ "sinceVersion": 20,
5
+ "description": "Computes the discrete Fourier Transform (DFT) of the input along a specified axis. For a signal of length N, output bin k is `sum_{n=0}^{N-1} exp(-2*pi*j*k*n/N) * x[n]`; the inverse divides by N and negates the exponent sign. Supports forward/inverse, real-to-complex (RFFT), and complex-to-real (IRFFT) modes via the `onesided` and `inverse` attributes.",
6
+ "inputs": [
7
+ {
8
+ "role": "input",
9
+ "dtype": "T",
10
+ "description": "Input signal tensor; the last dimension is 1 for real values or 2 for complex `(real, imaginary)` pairs."
11
+ },
12
+ {
13
+ "role": "dft_length",
14
+ "dtype": "L",
15
+ "optional": true,
16
+ "description": "Optional int32 scalar controlling the signal length used for the transform; input is zero-padded or truncated to this length.",
17
+ "rank": 0
18
+ },
19
+ {
20
+ "role": "axis",
21
+ "dtype": "I",
22
+ "optional": true,
23
+ "description": "Optional logical int64 scalar specifying the dimension over which to compute the DFT (the last axis is reserved for the real/imaginary component and is excluded); signed axes use int32 WebGPU storage and default to `-2` when omitted.",
24
+ "rank": 0
25
+ }
26
+ ],
27
+ "outputs": [
28
+ {
29
+ "role": "output",
30
+ "dtype": "T",
31
+ "description": "DFT result tensor; last dimension is 2 (complex) for forward DFT and RFFT, or 1 (real) for IRFFT.",
32
+ "rank": "ranks.input"
33
+ }
34
+ ],
35
+ "attributes": { "inverse": 0, "onesided": 0 },
36
+ "attributeDescriptions": {
37
+ "inverse": "When set to 1, computes the inverse DFT (IDFT/IRFFT) instead of the forward transform; default is 0 (forward).",
38
+ "onesided": "When set to 1, exploits conjugate symmetry to return only the non-redundant half of the spectrum (RFFT for forward, IRFFT for inverse); default is 0 (full spectrum)."
39
+ },
40
+ "attributeConstraints": { "inverse": { "values": [0, 1] }, "onesided": { "values": [0, 1] } },
41
+ "typeConstraints": { "T": ["float32"], "L": ["int32"], "I": ["int64"] },
42
+ "args": {
43
+ "input": { "kind": "tensor", "semantic": "input", "role": "input" },
44
+ "dft_length": { "kind": "tensor", "semantic": "dft_length", "role": "dft_length", "required": false },
45
+ "axis": {
46
+ "kind": "tensor",
47
+ "semantic": "axis",
48
+ "role": "axis",
49
+ "dtype": "int32",
50
+ "narrowing": "checked",
51
+ "required": false
52
+ },
53
+ "output": { "kind": "tensor", "semantic": "output", "role": "output" }
54
+ },
55
+ "tunables": { "WORKGROUP_SIZE": 128, "TILED_REAL_WORKGROUP_SIZE": 256, "TILED_REAL_MIN_LENGTH": 512 },
56
+ "derive": {
57
+ "dftAxisAttr": "ranks.input - 2",
58
+ "dftAttrOuter": "outer(shapes.input, dftAxisAttr)",
59
+ "dftAttrInputInner": "inner(shapes.input, dftAxisAttr) / dim(shapes.input, ranks.input - 1)",
60
+ "dftAttrOutputOuter": "outer(shapes.output, dftAxisAttr)",
61
+ "dftAttrOutputInner": "inner(shapes.output, dftAxisAttr) / dim(shapes.output, ranks.output - 1)",
62
+ "rank3BatchDispatchFits": "dim(shapes.input, 0) <= device.limits.maxComputeWorkgroupsPerDimension",
63
+ "rank3Axis1SharedFftFits": "rank3BatchDispatchFits and pow2ceil(dim(shapes.input, 1)) == dim(shapes.input, 1) and dim(shapes.input, 1) >= 4 and dim(shapes.input, 1) * 16 <= device.limits.maxComputeWorkgroupStorageSize",
64
+ "rank3Axis1StorageFftFits": "rank3BatchDispatchFits and pow2ceil(dim(shapes.input, 1)) == dim(shapes.input, 1) and dim(shapes.input, 1) >= 4 and dim(shapes.input, 1) * 16 > device.limits.maxComputeWorkgroupStorageSize",
65
+ "rank3Axis1FftLog2": "log2ceil(dim(shapes.input, 1))",
66
+ "rank3Axis1ScratchFits": "dim(shapes.input, 0) * dim(shapes.input, 1) * 2 * 4 <= device.limits.maxStorageBufferBindingSize and dim(shapes.input, 0) * dim(shapes.input, 1) * 2 * 4 <= device.limits.maxBufferSize"
67
+ },
68
+ "bindingSets": {
69
+ "contiguousTransform": [
70
+ {
71
+ "name": "input",
72
+ "arg": "input",
73
+ "semantic": "input",
74
+ "buffer": { "type": "read-only-storage" },
75
+ "elementType": "f32"
76
+ },
77
+ { "name": "output", "arg": "output", "semantic": "output", "buffer": { "type": "storage" }, "elementType": "f32" },
78
+ {
79
+ "name": "params",
80
+ "semantic": "kernel.params",
81
+ "buffer": { "type": "uniform" },
82
+ "struct": { "name": "Params", "fields": [{ "name": "inner", "type": "u32", "value": 1 }] }
83
+ }
84
+ ],
85
+ "dftScratch": [
86
+ {
87
+ "name": "input",
88
+ "arg": "input",
89
+ "semantic": "input",
90
+ "buffer": { "type": "read-only-storage" },
91
+ "elementType": "f32"
92
+ },
93
+ { "name": "scratch", "semantic": "fft_scratch", "buffer": { "type": "storage" }, "elementType": "f32" },
94
+ { "name": "output", "arg": "output", "semantic": "output", "buffer": { "type": "storage" }, "elementType": "f32" },
95
+ {
96
+ "name": "params",
97
+ "semantic": "kernel.params",
98
+ "buffer": { "type": "uniform" },
99
+ "struct": { "name": "Params", "fields": [{ "name": "inner", "type": "u32", "value": 1 }] }
100
+ }
101
+ ],
102
+ "fftLen": [
103
+ {
104
+ "name": "input",
105
+ "arg": "input",
106
+ "semantic": "input",
107
+ "buffer": { "type": "read-only-storage" },
108
+ "elementType": "f32"
109
+ },
110
+ {
111
+ "name": "dft_len_in",
112
+ "arg": "dft_length",
113
+ "semantic": "dft_length",
114
+ "buffer": { "type": "read-only-storage" },
115
+ "elementType": "i32",
116
+ "length": 1
117
+ },
118
+ { "name": "output", "arg": "output", "semantic": "output", "buffer": { "type": "storage" }, "elementType": "f32" },
119
+ {
120
+ "name": "params",
121
+ "semantic": "kernel.params",
122
+ "buffer": { "type": "uniform" },
123
+ "struct": { "name": "Params", "fields": [{ "name": "inner", "type": "u32", "value": 1 }] }
124
+ }
125
+ ],
126
+ "fftLenStatic": [
127
+ {
128
+ "name": "input",
129
+ "arg": "input",
130
+ "semantic": "input",
131
+ "buffer": { "type": "read-only-storage" },
132
+ "elementType": "f32"
133
+ },
134
+ { "name": "output", "arg": "output", "semantic": "output", "buffer": { "type": "storage" }, "elementType": "f32" },
135
+ {
136
+ "name": "params",
137
+ "semantic": "kernel.params",
138
+ "buffer": { "type": "uniform" },
139
+ "struct": { "name": "Params", "fields": [{ "name": "inner", "type": "u32", "value": 1 }] }
140
+ }
141
+ ],
142
+ "fftAxis": [
143
+ {
144
+ "name": "input",
145
+ "arg": "input",
146
+ "semantic": "input",
147
+ "buffer": { "type": "read-only-storage" },
148
+ "elementType": "f32"
149
+ },
150
+ {
151
+ "name": "axis_in",
152
+ "arg": "axis",
153
+ "semantic": "axis",
154
+ "buffer": { "type": "read-only-storage" },
155
+ "elementType": "i32",
156
+ "length": 1
157
+ },
158
+ { "name": "output", "arg": "output", "semantic": "output", "buffer": { "type": "storage" }, "elementType": "f32" },
159
+ {
160
+ "name": "params",
161
+ "semantic": "kernel.params",
162
+ "buffer": { "type": "uniform" },
163
+ "struct": { "name": "Params", "fields": [{ "name": "inner", "type": "u32", "value": 1 }] }
164
+ }
165
+ ],
166
+ "sharedFftTransform": [
167
+ {
168
+ "name": "input",
169
+ "arg": "input",
170
+ "semantic": "input",
171
+ "buffer": { "type": "read-only-storage" },
172
+ "elementType": "f32"
173
+ },
174
+ { "name": "output", "arg": "output", "semantic": "output", "buffer": { "type": "storage" }, "elementType": "f32" },
175
+ {
176
+ "name": "params",
177
+ "semantic": "kernel.params",
178
+ "buffer": { "type": "uniform" },
179
+ "struct": {
180
+ "name": "Params",
181
+ "fields": [
182
+ {
183
+ "name": "inner",
184
+ "type": "u32",
185
+ "value": "inner(shapes.input, dftAxisAttr) / dim(shapes.input, ranks.input - 1)"
186
+ }
187
+ ]
188
+ }
189
+ }
190
+ ],
191
+ "rank2Naive": [
192
+ {
193
+ "name": "input",
194
+ "arg": "input",
195
+ "semantic": "input",
196
+ "buffer": { "type": "read-only-storage" },
197
+ "elementType": "f32"
198
+ },
199
+ { "name": "output", "arg": "output", "semantic": "output", "buffer": { "type": "storage" }, "elementType": "f32" },
200
+ {
201
+ "name": "params",
202
+ "semantic": "kernel.params",
203
+ "buffer": { "type": "uniform" },
204
+ "struct": {
205
+ "name": "Params",
206
+ "fields": [
207
+ { "name": "count", "type": "u32", "value": "numel(shapes.output) / dim(shapes.output, 1)" },
208
+ { "name": "signalLength", "type": "u32", "value": "dim(shapes.input, 0)" },
209
+ { "name": "inputComponents", "type": "u32", "value": "dim(shapes.input, 1)" },
210
+ { "name": "outputComponents", "type": "u32", "value": "dim(shapes.output, 1)" },
211
+ { "name": "outputLength", "type": "u32", "value": "dim(shapes.output, 0)" },
212
+ { "name": "inverse", "type": "u32", "value": "attrs.inverse" },
213
+ { "name": "onesided", "type": "u32", "value": "attrs.onesided" }
214
+ ]
215
+ }
216
+ }
217
+ ],
218
+ "rank3Naive": [
219
+ {
220
+ "name": "input",
221
+ "arg": "input",
222
+ "semantic": "input",
223
+ "buffer": { "type": "read-only-storage" },
224
+ "elementType": "f32"
225
+ },
226
+ { "name": "output", "arg": "output", "semantic": "output", "buffer": { "type": "storage" }, "elementType": "f32" },
227
+ {
228
+ "name": "params",
229
+ "semantic": "kernel.params",
230
+ "buffer": { "type": "uniform" },
231
+ "struct": {
232
+ "name": "Params",
233
+ "fields": [
234
+ { "name": "count", "type": "u32", "value": "numel(shapes.output) / dim(shapes.output, 2)" },
235
+ { "name": "signalLength", "type": "u32", "value": "dim(shapes.input, 1)" },
236
+ { "name": "inputComponents", "type": "u32", "value": "dim(shapes.input, 2)" },
237
+ { "name": "outputComponents", "type": "u32", "value": "dim(shapes.output, 2)" },
238
+ { "name": "outputLength", "type": "u32", "value": "dim(shapes.output, 1)" },
239
+ { "name": "inverse", "type": "u32", "value": "attrs.inverse" },
240
+ { "name": "onesided", "type": "u32", "value": "attrs.onesided" }
241
+ ]
242
+ }
243
+ }
244
+ ],
245
+ "rank4Naive": [
246
+ {
247
+ "name": "input",
248
+ "arg": "input",
249
+ "semantic": "input",
250
+ "buffer": { "type": "read-only-storage" },
251
+ "elementType": "f32"
252
+ },
253
+ { "name": "output", "arg": "output", "semantic": "output", "buffer": { "type": "storage" }, "elementType": "f32" },
254
+ {
255
+ "name": "params",
256
+ "semantic": "kernel.params",
257
+ "buffer": { "type": "uniform" },
258
+ "struct": {
259
+ "name": "Params",
260
+ "fields": [
261
+ { "name": "count", "type": "u32", "value": "numel(shapes.output) / dim(shapes.output, 3)" },
262
+ { "name": "inputD1", "type": "u32", "value": "dim(shapes.input, 1)" },
263
+ { "name": "inputD2", "type": "u32", "value": "dim(shapes.input, 2)" },
264
+ { "name": "outputD1", "type": "u32", "value": "dim(shapes.output, 1)" },
265
+ { "name": "outputD2", "type": "u32", "value": "dim(shapes.output, 2)" },
266
+ { "name": "inputComponents", "type": "u32", "value": "dim(shapes.input, 3)" },
267
+ { "name": "outputComponents", "type": "u32", "value": "dim(shapes.output, 3)" },
268
+ { "name": "inverse", "type": "u32", "value": "attrs.inverse" },
269
+ { "name": "onesided", "type": "u32", "value": "attrs.onesided" }
270
+ ]
271
+ }
272
+ }
273
+ ],
274
+ "genNone": [
275
+ {
276
+ "name": "input",
277
+ "arg": "input",
278
+ "semantic": "input",
279
+ "buffer": { "type": "read-only-storage" },
280
+ "elementType": "f32"
281
+ },
282
+ { "name": "output", "arg": "output", "semantic": "output", "buffer": { "type": "storage" }, "elementType": "f32" },
283
+ {
284
+ "name": "params",
285
+ "semantic": "kernel.params",
286
+ "buffer": { "type": "uniform" },
287
+ "struct": {
288
+ "name": "Params",
289
+ "fields": [
290
+ { "name": "count", "type": "u32", "value": "numel(shapes.output) / dim(shapes.output, ranks.output - 1)" },
291
+ { "name": "rank", "type": "u32", "value": "4" },
292
+ { "name": "inputComponents", "type": "u32", "value": "dim(shapes.input, ranks.input - 1)" },
293
+ { "name": "outputComponents", "type": "u32", "value": "dim(shapes.output, ranks.output - 1)" },
294
+ { "name": "inverse", "type": "u32", "value": "attrs.inverse" },
295
+ { "name": "onesided", "type": "u32", "value": "attrs.onesided" },
296
+ { "name": "inD0", "type": "u32", "value": 1 },
297
+ { "name": "inD1", "type": "u32", "value": 1 },
298
+ { "name": "inD2", "type": "u32", "value": 1 },
299
+ { "name": "inD3", "type": "u32", "value": 1 },
300
+ { "name": "inD4", "type": "u32", "value": "dftAttrOuter" },
301
+ { "name": "inD5", "type": "u32", "value": "dim(shapes.input, dftAxisAttr)" },
302
+ { "name": "inD6", "type": "u32", "value": "dftAttrInputInner" },
303
+ { "name": "outD0", "type": "u32", "value": 1 },
304
+ { "name": "outD1", "type": "u32", "value": 1 },
305
+ { "name": "outD2", "type": "u32", "value": 1 },
306
+ { "name": "outD3", "type": "u32", "value": 1 },
307
+ { "name": "outD4", "type": "u32", "value": "dftAttrOutputOuter" },
308
+ { "name": "outD5", "type": "u32", "value": "dim(shapes.output, dftAxisAttr)" },
309
+ { "name": "outD6", "type": "u32", "value": "dftAttrOutputInner" },
310
+ { "name": "axisAttr", "type": "u32", "value": "1" },
311
+ { "name": "dftLengthAttr", "type": "u32", "value": 0 }
312
+ ]
313
+ }
314
+ }
315
+ ],
316
+ "genAxis": [
317
+ {
318
+ "name": "input",
319
+ "arg": "input",
320
+ "semantic": "input",
321
+ "buffer": { "type": "read-only-storage" },
322
+ "elementType": "f32"
323
+ },
324
+ { "name": "output", "arg": "output", "semantic": "output", "buffer": { "type": "storage" }, "elementType": "f32" },
325
+ {
326
+ "name": "axis_in",
327
+ "arg": "axis",
328
+ "semantic": "axis",
329
+ "buffer": { "type": "read-only-storage" },
330
+ "elementType": "i32",
331
+ "length": 1
332
+ },
333
+ {
334
+ "name": "params",
335
+ "semantic": "kernel.params",
336
+ "buffer": { "type": "uniform" },
337
+ "struct": {
338
+ "name": "Params",
339
+ "fields": [
340
+ { "name": "count", "type": "u32", "value": "numel(shapes.output) / dim(shapes.output, ranks.output - 1)" },
341
+ { "name": "rank", "type": "u32", "value": "ranks.input" },
342
+ { "name": "inputComponents", "type": "u32", "value": "dim(shapes.input, ranks.input - 1)" },
343
+ { "name": "outputComponents", "type": "u32", "value": "dim(shapes.output, ranks.output - 1)" },
344
+ { "name": "inverse", "type": "u32", "value": "attrs.inverse" },
345
+ { "name": "onesided", "type": "u32", "value": "attrs.onesided" },
346
+ { "name": "inD0", "type": "u32", "value": "dim(shapes.input, ranks.input - 8) if ranks.input >= 8 else 1" },
347
+ { "name": "inD1", "type": "u32", "value": "dim(shapes.input, ranks.input - 7) if ranks.input >= 7 else 1" },
348
+ { "name": "inD2", "type": "u32", "value": "dim(shapes.input, ranks.input - 6) if ranks.input >= 6 else 1" },
349
+ { "name": "inD3", "type": "u32", "value": "dim(shapes.input, ranks.input - 5) if ranks.input >= 5 else 1" },
350
+ { "name": "inD4", "type": "u32", "value": "dim(shapes.input, ranks.input - 4) if ranks.input >= 4 else 1" },
351
+ { "name": "inD5", "type": "u32", "value": "dim(shapes.input, ranks.input - 3) if ranks.input >= 3 else 1" },
352
+ { "name": "inD6", "type": "u32", "value": "dim(shapes.input, ranks.input - 2)" },
353
+ {
354
+ "name": "outD0",
355
+ "type": "u32",
356
+ "value": "dim(shapes.output, ranks.output - 8) if ranks.output >= 8 else 1"
357
+ },
358
+ {
359
+ "name": "outD1",
360
+ "type": "u32",
361
+ "value": "dim(shapes.output, ranks.output - 7) if ranks.output >= 7 else 1"
362
+ },
363
+ {
364
+ "name": "outD2",
365
+ "type": "u32",
366
+ "value": "dim(shapes.output, ranks.output - 6) if ranks.output >= 6 else 1"
367
+ },
368
+ {
369
+ "name": "outD3",
370
+ "type": "u32",
371
+ "value": "dim(shapes.output, ranks.output - 5) if ranks.output >= 5 else 1"
372
+ },
373
+ {
374
+ "name": "outD4",
375
+ "type": "u32",
376
+ "value": "dim(shapes.output, ranks.output - 4) if ranks.output >= 4 else 1"
377
+ },
378
+ {
379
+ "name": "outD5",
380
+ "type": "u32",
381
+ "value": "dim(shapes.output, ranks.output - 3) if ranks.output >= 3 else 1"
382
+ },
383
+ { "name": "outD6", "type": "u32", "value": "dim(shapes.output, ranks.output - 2)" },
384
+ { "name": "dftLengthAttr", "type": "u32", "value": 0 }
385
+ ]
386
+ }
387
+ }
388
+ ],
389
+ "genLen": [
390
+ {
391
+ "name": "input",
392
+ "arg": "input",
393
+ "semantic": "input",
394
+ "buffer": { "type": "read-only-storage" },
395
+ "elementType": "f32"
396
+ },
397
+ { "name": "output", "arg": "output", "semantic": "output", "buffer": { "type": "storage" }, "elementType": "f32" },
398
+ {
399
+ "name": "dft_len_in",
400
+ "arg": "dft_length",
401
+ "semantic": "dft_length",
402
+ "buffer": { "type": "read-only-storage" },
403
+ "elementType": "i32",
404
+ "length": 1
405
+ },
406
+ {
407
+ "name": "params",
408
+ "semantic": "kernel.params",
409
+ "buffer": { "type": "uniform" },
410
+ "struct": {
411
+ "name": "Params",
412
+ "fields": [
413
+ { "name": "count", "type": "u32", "value": "numel(shapes.output) / dim(shapes.output, ranks.output - 1)" },
414
+ { "name": "rank", "type": "u32", "value": "4" },
415
+ { "name": "inputComponents", "type": "u32", "value": "dim(shapes.input, ranks.input - 1)" },
416
+ { "name": "outputComponents", "type": "u32", "value": "dim(shapes.output, ranks.output - 1)" },
417
+ { "name": "inverse", "type": "u32", "value": "attrs.inverse" },
418
+ { "name": "onesided", "type": "u32", "value": "attrs.onesided" },
419
+ { "name": "inD0", "type": "u32", "value": 1 },
420
+ { "name": "inD1", "type": "u32", "value": 1 },
421
+ { "name": "inD2", "type": "u32", "value": 1 },
422
+ { "name": "inD3", "type": "u32", "value": 1 },
423
+ { "name": "inD4", "type": "u32", "value": "dftAttrOuter" },
424
+ { "name": "inD5", "type": "u32", "value": "dim(shapes.input, dftAxisAttr)" },
425
+ { "name": "inD6", "type": "u32", "value": "dftAttrInputInner" },
426
+ { "name": "outD0", "type": "u32", "value": 1 },
427
+ { "name": "outD1", "type": "u32", "value": 1 },
428
+ { "name": "outD2", "type": "u32", "value": 1 },
429
+ { "name": "outD3", "type": "u32", "value": 1 },
430
+ { "name": "outD4", "type": "u32", "value": "dftAttrOutputOuter" },
431
+ { "name": "outD5", "type": "u32", "value": "dim(shapes.output, dftAxisAttr)" },
432
+ { "name": "outD6", "type": "u32", "value": "dftAttrOutputInner" },
433
+ { "name": "axisAttr", "type": "u32", "value": "1" }
434
+ ]
435
+ }
436
+ }
437
+ ],
438
+ "genAxisLen": [
439
+ {
440
+ "name": "input",
441
+ "arg": "input",
442
+ "semantic": "input",
443
+ "buffer": { "type": "read-only-storage" },
444
+ "elementType": "f32"
445
+ },
446
+ { "name": "output", "arg": "output", "semantic": "output", "buffer": { "type": "storage" }, "elementType": "f32" },
447
+ {
448
+ "name": "dft_len_in",
449
+ "arg": "dft_length",
450
+ "semantic": "dft_length",
451
+ "buffer": { "type": "read-only-storage" },
452
+ "elementType": "i32",
453
+ "length": 1
454
+ },
455
+ {
456
+ "name": "axis_in",
457
+ "arg": "axis",
458
+ "semantic": "axis",
459
+ "buffer": { "type": "read-only-storage" },
460
+ "elementType": "i32",
461
+ "length": 1
462
+ },
463
+ {
464
+ "name": "params",
465
+ "semantic": "kernel.params",
466
+ "buffer": { "type": "uniform" },
467
+ "struct": {
468
+ "name": "Params",
469
+ "fields": [
470
+ { "name": "count", "type": "u32", "value": "numel(shapes.output) / dim(shapes.output, ranks.output - 1)" },
471
+ { "name": "rank", "type": "u32", "value": "ranks.input" },
472
+ { "name": "inputComponents", "type": "u32", "value": "dim(shapes.input, ranks.input - 1)" },
473
+ { "name": "outputComponents", "type": "u32", "value": "dim(shapes.output, ranks.output - 1)" },
474
+ { "name": "inverse", "type": "u32", "value": "attrs.inverse" },
475
+ { "name": "onesided", "type": "u32", "value": "attrs.onesided" },
476
+ { "name": "inD0", "type": "u32", "value": "dim(shapes.input, ranks.input - 8) if ranks.input >= 8 else 1" },
477
+ { "name": "inD1", "type": "u32", "value": "dim(shapes.input, ranks.input - 7) if ranks.input >= 7 else 1" },
478
+ { "name": "inD2", "type": "u32", "value": "dim(shapes.input, ranks.input - 6) if ranks.input >= 6 else 1" },
479
+ { "name": "inD3", "type": "u32", "value": "dim(shapes.input, ranks.input - 5) if ranks.input >= 5 else 1" },
480
+ { "name": "inD4", "type": "u32", "value": "dim(shapes.input, ranks.input - 4) if ranks.input >= 4 else 1" },
481
+ { "name": "inD5", "type": "u32", "value": "dim(shapes.input, ranks.input - 3) if ranks.input >= 3 else 1" },
482
+ { "name": "inD6", "type": "u32", "value": "dim(shapes.input, ranks.input - 2)" },
483
+ {
484
+ "name": "outD0",
485
+ "type": "u32",
486
+ "value": "dim(shapes.output, ranks.output - 8) if ranks.output >= 8 else 1"
487
+ },
488
+ {
489
+ "name": "outD1",
490
+ "type": "u32",
491
+ "value": "dim(shapes.output, ranks.output - 7) if ranks.output >= 7 else 1"
492
+ },
493
+ {
494
+ "name": "outD2",
495
+ "type": "u32",
496
+ "value": "dim(shapes.output, ranks.output - 6) if ranks.output >= 6 else 1"
497
+ },
498
+ {
499
+ "name": "outD3",
500
+ "type": "u32",
501
+ "value": "dim(shapes.output, ranks.output - 5) if ranks.output >= 5 else 1"
502
+ },
503
+ {
504
+ "name": "outD4",
505
+ "type": "u32",
506
+ "value": "dim(shapes.output, ranks.output - 4) if ranks.output >= 4 else 1"
507
+ },
508
+ {
509
+ "name": "outD5",
510
+ "type": "u32",
511
+ "value": "dim(shapes.output, ranks.output - 3) if ranks.output >= 3 else 1"
512
+ },
513
+ { "name": "outD6", "type": "u32", "value": "dim(shapes.output, ranks.output - 2)" }
514
+ ]
515
+ }
516
+ }
517
+ ]
518
+ },
519
+ "variants": [
520
+ {
521
+ "id": "batch_runtime_axis1_fft",
522
+ "priority": 30,
523
+ "when": ["present.axis", "numel(shapes.axis) == 1", "not present.dft_length", "ranks.input == 3", "ranks.output == 3", "attrs.onesided == 0", "dim(shapes.input, 0) == dim(shapes.output, 0)", "dim(shapes.input, 1) == dim(shapes.output, 1)", "rank3Axis1SharedFftFits", "dim(shapes.input, 2) >= 1", "dim(shapes.input, 2) <= 2", "dim(shapes.output, 2) == 2"],
524
+ "passes": [
525
+ {
526
+ "id": "axis1_fft",
527
+ "name": "DFT.BatchRuntimeAxis1FftShared",
528
+ "source": {
529
+ "shader": "fft-stockham-shared.wgsl.jinja",
530
+ "inputs": {
531
+ "mode": "\"dft\"",
532
+ "fftLength": "dim(shapes.input, 1)",
533
+ "numBins": "dim(shapes.output, 1)",
534
+ "inComp": "dim(shapes.input, 2)",
535
+ "inverse": "attrs.inverse != 0",
536
+ "runtimeAxis": true,
537
+ "axisRank": 3,
538
+ "expectedAxis": 1
539
+ }
540
+ },
541
+ "bindings": "fftAxis",
542
+ "dispatch": { "x": "dim(shapes.input, 0)" }
543
+ },
544
+ {
545
+ "id": "other_axis_general",
546
+ "name": "DFT.BatchRuntimeOtherAxisGeneral",
547
+ "source": {
548
+ "shader": "dft-general.wgsl.jinja",
549
+ "inputs": { "hasAxisInput": true, "hasDftLengthInput": false, "skipRuntimeAxis1": true }
550
+ },
551
+ "bindings": "genAxis",
552
+ "dispatch": {
553
+ "threads": "numel(shapes.output) / dim(shapes.output, ranks.output - 1)",
554
+ "workgroupSize": "tunables.WORKGROUP_SIZE"
555
+ }
556
+ }
557
+ ]
558
+ },
559
+ {
560
+ "id": "batch_axis1_fft_storage_onesided",
561
+ "priority": 32,
562
+ "when": ["not present.axis", "not present.dft_length", "ranks.input == 3", "ranks.output == 3", "dftAxisAttr == 1", "attrs.onesided != 0", "attrs.inverse == 0", "dim(shapes.input, 0) == dim(shapes.output, 0)", "dim(shapes.input, 0) > 0", "rank3BatchDispatchFits", "rank3Axis1StorageFftFits", "rank3Axis1ScratchFits", "dim(shapes.output, 1) == floor(dim(shapes.input, 1) / 2) + 1", "dim(shapes.input, 2) == 1", "dim(shapes.output, 2) == 2"],
563
+ "intermediates": [
564
+ { "id": "fft_scratch", "dtype": "float32", "shape": "[dim(shapes.input, 0) * dim(shapes.input, 1) * 2]" }
565
+ ],
566
+ "passes": [
567
+ {
568
+ "id": "main",
569
+ "name": "DFT.BatchAxis1FftStorageOnesided",
570
+ "source": {
571
+ "shader": "fft-radix2-dit-storage.wgsl.jinja",
572
+ "inputs": {
573
+ "mode": "\"dft_scratch\"",
574
+ "fftLength": "dim(shapes.input, 1)",
575
+ "log2Length": "rank3Axis1FftLog2",
576
+ "numBins": "dim(shapes.output, 1)",
577
+ "inComp": 1,
578
+ "inverse": false
579
+ }
580
+ },
581
+ "bindings": "dftScratch",
582
+ "dispatch": { "x": "dim(shapes.input, 0)" }
583
+ }
584
+ ]
585
+ },
586
+ {
587
+ "id": "batch_axis1_fft_storage_inplace",
588
+ "priority": 31,
589
+ "when": ["not present.axis", "not present.dft_length", "ranks.input == 3", "ranks.output == 3", "dftAxisAttr == 1", "attrs.onesided == 0", "dim(shapes.input, 0) == dim(shapes.output, 0)", "rank3BatchDispatchFits", "rank3Axis1StorageFftFits", "dim(shapes.output, 1) == dim(shapes.input, 1)", "dim(shapes.input, 2) >= 1", "dim(shapes.input, 2) <= 2", "dim(shapes.output, 2) == 2"],
590
+ "passes": [
591
+ {
592
+ "id": "main",
593
+ "name": "DFT.BatchAxis1FftStorageInplace",
594
+ "source": {
595
+ "shader": "fft-radix2-dit-storage.wgsl.jinja",
596
+ "inputs": {
597
+ "mode": "\"dft\"",
598
+ "fftLength": "dim(shapes.input, 1)",
599
+ "log2Length": "rank3Axis1FftLog2",
600
+ "numBins": "dim(shapes.output, 1)",
601
+ "inComp": "dim(shapes.input, 2)",
602
+ "inverse": "attrs.inverse != 0"
603
+ }
604
+ },
605
+ "bindings": "contiguousTransform",
606
+ "dispatch": { "x": "dim(shapes.input, 0)" }
607
+ }
608
+ ]
609
+ },
610
+ {
611
+ "id": "batch_axis1_tiled_real_nonpow2",
612
+ "priority": 29,
613
+ "requires": { "limits": { "maxComputeInvocationsPerWorkgroup": 256, "maxComputeWorkgroupSizeX": 256 } },
614
+ "when": ["not present.axis", "not present.dft_length", "ranks.input == 3", "ranks.output == 3", "dftAxisAttr == 1", "attrs.inverse == 0", "dim(shapes.input, 0) == dim(shapes.output, 0)", "dim(shapes.input, 0) > 0", "rank3BatchDispatchFits", "dim(shapes.input, 1) >= tunables.TILED_REAL_MIN_LENGTH", "pow2ceil(dim(shapes.input, 1)) != dim(shapes.input, 1)", "dim(shapes.input, 1) * 4 <= device.limits.maxComputeWorkgroupStorageSize", "dim(shapes.input, 2) == 1", "dim(shapes.output, 2) == 2", "((attrs.onesided == 0 and dim(shapes.output, 1) == dim(shapes.input, 1)) or (attrs.onesided != 0 and dim(shapes.output, 1) == floor(dim(shapes.input, 1) / 2) + 1))", "ceilDiv(dim(shapes.output, 1), tunables.TILED_REAL_WORKGROUP_SIZE) <= device.limits.maxComputeWorkgroupsPerDimension"],
615
+ "passes": [
616
+ {
617
+ "id": "main",
618
+ "name": "DFT.BatchAxis1TiledRealNonPowerOfTwo",
619
+ "source": {
620
+ "shader": "dft-tiled-real.wgsl.jinja",
621
+ "inputs": {
622
+ "mode": "\"dft\"",
623
+ "transformLength": "dim(shapes.input, 1)",
624
+ "numBins": "dim(shapes.output, 1)",
625
+ "workgroupSize": "tunables.TILED_REAL_WORKGROUP_SIZE"
626
+ }
627
+ },
628
+ "bindings": "contiguousTransform",
629
+ "dispatch": {
630
+ "x": "ceilDiv(dim(shapes.output, 1), tunables.TILED_REAL_WORKGROUP_SIZE)",
631
+ "y": "dim(shapes.input, 0)"
632
+ }
633
+ }
634
+ ]
635
+ },
636
+ {
637
+ "id": "axis_fft_shared",
638
+ "priority": 30,
639
+ "when": ["not present.axis", "not present.dft_length", "ranks.input == ranks.output", "((ranks.input == 2 and dftAxisAttr == 0) or (ranks.input == 3 and dftAxisAttr == 1 and dim(shapes.input, 0) == dim(shapes.output, 0)) or (ranks.input == 4 and dftAxisAttr == 2 and dim(shapes.input, 0) == dim(shapes.output, 0) and dim(shapes.input, 1) == dim(shapes.output, 1)))", "pow2ceil(dim(shapes.input, dftAxisAttr)) == dim(shapes.input, dftAxisAttr)", "dim(shapes.input, dftAxisAttr) >= 4", "dim(shapes.input, dftAxisAttr) * 16 <= device.limits.maxComputeWorkgroupStorageSize", "outer(shapes.input, dftAxisAttr) * inner(shapes.input, dftAxisAttr) / dim(shapes.input, ranks.input - 1) <= device.limits.maxComputeWorkgroupsPerDimension", "dim(shapes.input, ranks.input - 1) >= 1", "dim(shapes.input, ranks.input - 1) <= 2", "((attrs.onesided == 0 and dim(shapes.output, dftAxisAttr) == dim(shapes.input, dftAxisAttr) and dim(shapes.output, ranks.input - 1) == 2) or (attrs.onesided != 0 and attrs.inverse == 0 and dim(shapes.input, ranks.input - 1) == 1 and dim(shapes.output, dftAxisAttr) == floor(dim(shapes.input, dftAxisAttr) / 2) + 1 and dim(shapes.output, ranks.input - 1) == 2))"],
640
+ "passes": [
641
+ {
642
+ "id": "main",
643
+ "name": "DFT.AxisFftShared",
644
+ "source": {
645
+ "shader": "fft-stockham-shared.wgsl.jinja",
646
+ "inputs": {
647
+ "mode": "\"dft\"",
648
+ "fftLength": "dim(shapes.input, dftAxisAttr)",
649
+ "numBins": "dim(shapes.output, dftAxisAttr)",
650
+ "inComp": "dim(shapes.input, ranks.input - 1)",
651
+ "inverse": "attrs.inverse != 0"
652
+ }
653
+ },
654
+ "bindings": "sharedFftTransform",
655
+ "dispatch": {
656
+ "x": "outer(shapes.input, dftAxisAttr) * inner(shapes.input, dftAxisAttr) / dim(shapes.input, ranks.input - 1)"
657
+ }
658
+ }
659
+ ]
660
+ },
661
+ {
662
+ "id": "batch_axis1_fft_explicit_equal_length",
663
+ "priority": 31,
664
+ "when": ["not present.axis", "present.dft_length", "numel(shapes.dft_length) == 1", "ranks.input == 3", "ranks.output == 3", "dftAxisAttr == 1", "dim(shapes.input, 0) == dim(shapes.output, 0)", "rank3Axis1SharedFftFits", "dim(shapes.input, 2) >= 1", "dim(shapes.input, 2) <= 2", "((attrs.onesided == 0 and dim(shapes.output, 1) == dim(shapes.input, 1) and dim(shapes.output, 2) == 2) or (attrs.onesided != 0 and attrs.inverse == 0 and dim(shapes.input, 2) == 1 and dim(shapes.output, 1) == floor(dim(shapes.input, 1) / 2) + 1 and dim(shapes.output, 2) == 2))"],
665
+ "passes": [
666
+ {
667
+ "id": "main",
668
+ "name": "DFT.BatchAxis1FftSharedExplicitLength",
669
+ "source": {
670
+ "shader": "fft-stockham-shared.wgsl.jinja",
671
+ "inputs": {
672
+ "mode": "\"dft\"",
673
+ "fftLength": "dim(shapes.input, 1)",
674
+ "numBins": "dim(shapes.output, 1)",
675
+ "inComp": "dim(shapes.input, 2)",
676
+ "inverse": "attrs.inverse != 0"
677
+ }
678
+ },
679
+ "bindings": "fftLenStatic",
680
+ "dispatch": { "x": "dim(shapes.input, 0)" }
681
+ }
682
+ ]
683
+ },
684
+ {
685
+ "id": "rank2_axis0",
686
+ "priority": 20,
687
+ "when": ["not present.axis", "not present.dft_length", "ranks.input == 2", "ranks.output == 2", "dftAxisAttr == 0", "dim(shapes.input, 1) >= 1", "dim(shapes.input, 1) <= 2", "((attrs.onesided == 0 and dim(shapes.output, 0) == dim(shapes.input, 0) and dim(shapes.output, 1) == 2) or (attrs.onesided != 0 and attrs.inverse == 0 and dim(shapes.input, 1) == 1 and dim(shapes.output, 0) == floor(dim(shapes.input, 0) / 2) + 1 and dim(shapes.output, 1) == 2) or (attrs.onesided != 0 and attrs.inverse != 0 and dim(shapes.input, 1) == 2 and dim(shapes.input, 0) == floor(dim(shapes.output, 0) / 2) + 1 and dim(shapes.output, 1) == 1))"],
688
+ "passes": [
689
+ {
690
+ "id": "main",
691
+ "name": "DFT.Rank2Axis0",
692
+ "source": { "shader": "dft-contiguous-naive.wgsl.jinja", "inputs": { "batched": false } },
693
+ "bindings": "rank2Naive",
694
+ "dispatch": {
695
+ "threads": "numel(shapes.output) / dim(shapes.output, 1)",
696
+ "workgroupSize": "tunables.WORKGROUP_SIZE"
697
+ }
698
+ }
699
+ ]
700
+ },
701
+ {
702
+ "id": "batch_axis1",
703
+ "priority": 10,
704
+ "when": ["not present.axis", "not present.dft_length", "ranks.input == 3", "ranks.output == 3", "dftAxisAttr == 1", "dim(shapes.input, 0) == dim(shapes.output, 0)", "dim(shapes.input, 2) >= 1", "dim(shapes.input, 2) <= 2", "((attrs.onesided == 0 and dim(shapes.output, 1) == dim(shapes.input, 1) and dim(shapes.output, 2) == 2) or (attrs.onesided != 0 and attrs.inverse == 0 and dim(shapes.input, 2) == 1 and dim(shapes.output, 1) == floor(dim(shapes.input, 1) / 2) + 1 and dim(shapes.output, 2) == 2) or (attrs.onesided != 0 and attrs.inverse != 0 and dim(shapes.input, 2) == 2 and dim(shapes.input, 1) == floor(dim(shapes.output, 1) / 2) + 1 and dim(shapes.output, 2) == 1))"],
705
+ "passes": [
706
+ {
707
+ "id": "main",
708
+ "name": "DFT",
709
+ "source": { "shader": "dft-contiguous-naive.wgsl.jinja", "inputs": { "batched": true } },
710
+ "bindings": "rank3Naive",
711
+ "dispatch": {
712
+ "threads": "numel(shapes.output) / dim(shapes.output, 2)",
713
+ "workgroupSize": "tunables.WORKGROUP_SIZE"
714
+ }
715
+ }
716
+ ]
717
+ },
718
+ {
719
+ "id": "rank4_axis2",
720
+ "priority": 10,
721
+ "when": ["not present.axis", "not present.dft_length", "ranks.input == 4", "ranks.output == 4", "dftAxisAttr == 2", "dim(shapes.input, 0) == dim(shapes.output, 0)", "dim(shapes.input, 1) == dim(shapes.output, 1)", "dim(shapes.input, 3) >= 1", "dim(shapes.input, 3) <= 2", "((attrs.onesided == 0 and dim(shapes.output, 2) == dim(shapes.input, 2) and dim(shapes.output, 3) == 2) or (attrs.onesided != 0 and attrs.inverse == 0 and dim(shapes.input, 3) == 1 and dim(shapes.output, 2) == floor(dim(shapes.input, 2) / 2) + 1 and dim(shapes.output, 3) == 2) or (attrs.onesided != 0 and attrs.inverse != 0 and dim(shapes.input, 3) == 2 and dim(shapes.input, 2) == floor(dim(shapes.output, 2) / 2) + 1 and dim(shapes.output, 3) == 1))"],
722
+ "passes": [
723
+ {
724
+ "id": "main",
725
+ "name": "DFT.Rank4Axis2",
726
+ "shader": "dft-rank4.wgsl.jinja",
727
+ "bindings": "rank4Naive",
728
+ "dispatch": {
729
+ "threads": "numel(shapes.output) / dim(shapes.output, 3)",
730
+ "workgroupSize": "tunables.WORKGROUP_SIZE"
731
+ }
732
+ }
733
+ ]
734
+ },
735
+ {
736
+ "id": "general_attr",
737
+ "priority": 5,
738
+ "when": ["not present.axis", "not present.dft_length", "ranks.input >= 2", "dftAxisAttr <= ranks.input - 2", "ranks.input == ranks.output", "dim(shapes.input, ranks.input - 1) >= 1", "dim(shapes.input, ranks.input - 1) <= 2", "dim(shapes.output, ranks.output - 1) >= 1", "dim(shapes.output, ranks.output - 1) <= 2"],
739
+ "passes": [
740
+ {
741
+ "id": "main",
742
+ "name": "DFT.General",
743
+ "source": {
744
+ "shader": "dft-general.wgsl.jinja",
745
+ "inputs": { "hasAxisInput": false, "hasDftLengthInput": false, "skipRuntimeAxis1": false }
746
+ },
747
+ "bindings": "genNone",
748
+ "dispatch": {
749
+ "threads": "numel(shapes.output) / dim(shapes.output, ranks.output - 1)",
750
+ "workgroupSize": "tunables.WORKGROUP_SIZE"
751
+ }
752
+ }
753
+ ]
754
+ },
755
+ {
756
+ "id": "general_axis_len",
757
+ "priority": 5,
758
+ "when": ["present.axis and numel(shapes.axis) == 1", "present.dft_length and numel(shapes.dft_length) == 1", "ranks.input >= 2", "ranks.input <= 8", "ranks.input == ranks.output", "dim(shapes.input, ranks.input - 1) >= 1", "dim(shapes.input, ranks.input - 1) <= 2", "dim(shapes.output, ranks.output - 1) >= 1", "dim(shapes.output, ranks.output - 1) <= 2"],
759
+ "passes": [
760
+ {
761
+ "id": "main",
762
+ "name": "DFT.General",
763
+ "source": {
764
+ "shader": "dft-general.wgsl.jinja",
765
+ "inputs": { "hasAxisInput": true, "hasDftLengthInput": true, "skipRuntimeAxis1": false }
766
+ },
767
+ "bindings": "genAxisLen",
768
+ "dispatch": {
769
+ "threads": "numel(shapes.output) / dim(shapes.output, ranks.output - 1)",
770
+ "workgroupSize": "tunables.WORKGROUP_SIZE"
771
+ }
772
+ }
773
+ ]
774
+ },
775
+ {
776
+ "id": "general_axis",
777
+ "priority": 5,
778
+ "when": ["present.axis and numel(shapes.axis) == 1", "not present.dft_length", "ranks.input >= 2", "ranks.input <= 8", "ranks.input == ranks.output", "dim(shapes.input, ranks.input - 1) >= 1", "dim(shapes.input, ranks.input - 1) <= 2", "dim(shapes.output, ranks.output - 1) >= 1", "dim(shapes.output, ranks.output - 1) <= 2"],
779
+ "passes": [
780
+ {
781
+ "id": "main",
782
+ "name": "DFT.General",
783
+ "source": {
784
+ "shader": "dft-general.wgsl.jinja",
785
+ "inputs": { "hasAxisInput": true, "hasDftLengthInput": false, "skipRuntimeAxis1": false }
786
+ },
787
+ "bindings": "genAxis",
788
+ "dispatch": {
789
+ "threads": "numel(shapes.output) / dim(shapes.output, ranks.output - 1)",
790
+ "workgroupSize": "tunables.WORKGROUP_SIZE"
791
+ }
792
+ }
793
+ ]
794
+ },
795
+ {
796
+ "id": "general_len",
797
+ "priority": 5,
798
+ "when": ["not present.axis", "present.dft_length and numel(shapes.dft_length) == 1", "ranks.input >= 2", "dftAxisAttr <= ranks.input - 2", "ranks.input == ranks.output", "dim(shapes.input, ranks.input - 1) >= 1", "dim(shapes.input, ranks.input - 1) <= 2", "dim(shapes.output, ranks.output - 1) >= 1", "dim(shapes.output, ranks.output - 1) <= 2"],
799
+ "passes": [
800
+ {
801
+ "id": "main",
802
+ "name": "DFT.General",
803
+ "source": {
804
+ "shader": "dft-general.wgsl.jinja",
805
+ "inputs": { "hasAxisInput": false, "hasDftLengthInput": true, "skipRuntimeAxis1": false }
806
+ },
807
+ "bindings": "genLen",
808
+ "dispatch": {
809
+ "threads": "numel(shapes.output) / dim(shapes.output, ranks.output - 1)",
810
+ "workgroupSize": "tunables.WORKGROUP_SIZE"
811
+ }
812
+ }
813
+ ]
814
+ }
815
+ ]
816
+ }
build/webgpu/metadata.json ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "ai.onnx.DFT",
3
+ "id": "_ai_onnx_dft_webgpu_dbca2e5",
4
+ "version": 1,
5
+ "license": "Apache-2.0",
6
+ "backend": { "type": "webgpu" },
7
+ "digest": {
8
+ "algorithm": "sha256",
9
+ "files": {
10
+ "bench.json": "jbPgs835f9m0aqqbSqY7zJJ+sRdHAdkjUzasXhjUEMM=",
11
+ "dft-contiguous-naive.wgsl.jinja": "HizdEBeuURKgl0Zxsz3xs62LJA6qmXvTYeYgC/XW5FE=",
12
+ "dft-general.wgsl.jinja": "fRvFH2YPz8DQLaTWSwXfScJErAFyeim5yqU39h+DYIc=",
13
+ "dft-rank4.wgsl.jinja": "QYtIZb6gpPc4fPIglkSt50Nr3KgbmuqoZk3EsuCgS3w=",
14
+ "dft-tiled-real.wgsl.jinja": "hbc3jiXVFIDZzBLo4O5hKQDqPIrNFdNktE85DhjWAzY=",
15
+ "fft-radix2-dit-storage.wgsl.jinja": "xLm5AhxAvxBdznovw1gQsQsaJGxusSmdtcaxUs00i2w=",
16
+ "fft-stockham-shared.wgsl.jinja": "7J4RLO2Q8puehryHRRHVhrrNxjNik5718G/qu6zoF6I=",
17
+ "manifest.json": "9IvgHvMofuhCPOTfi61UqnxP5X0IdE11o+JDI1V+dSs=",
18
+ "test.json": "pmZjTxGLBuze2IB7hjXRl0ks8BfzGeJ1OWCsaNDm/ao="
19
+ }
20
+ },
21
+ "provenance": { "kernel": { "sha": "2e7068faf55e7f43df740015f6d1ee49391a41c5", "dirty": false } },
22
+ "webgpu": { "manifestSpec": "1.0", "specialized": true, "opPath": "ops/ai.onnx.DFT" }
23
+ }
build/webgpu/test.json ADDED
@@ -0,0 +1,1374 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "op": "ai.onnx.DFT",
3
+ "fixtureArrays": {
4
+ "ort_rank2_complex_axis0_output_output": [36, 0, -4, 9.65685, -4, 4, -4, 1.65685, -4, 0, -4, -1.65685, -4, -4, -4, -9.65685]
5
+ },
6
+ "cases": [
7
+ {
8
+ "name": "dispatch_cliff_batch_axis1_over_8m_elements",
9
+ "attrs": { "inverse": 0, "onesided": 0 },
10
+ "inputs": { "input": { "dtype": "float32", "shape": [2099200, 4, 1] } },
11
+ "outputs": { "output": { "dtype": "float32", "shape": [2099200, 4, 2], "tolerance": 0.001 } }
12
+ },
13
+ {
14
+ "name": "dispatch_cliff_rank4_axis1_over_8m_elements",
15
+ "attrs": { "inverse": 0, "onesided": 0 },
16
+ "inputs": {
17
+ "input": { "dtype": "float32", "shape": [1050000, 4, 2, 1] },
18
+ "axis": { "dtype": "int32", "shape": [], "data": { "kind": "values", "values": [1] } }
19
+ },
20
+ "outputs": { "output": { "dtype": "float32", "shape": [1050000, 4, 2, 2], "tolerance": 0.001 } }
21
+ },
22
+ {
23
+ "name": "dispatch_cliff_rank4_axis2_over_8m_elements",
24
+ "attrs": { "inverse": 0, "onesided": 0 },
25
+ "inputs": { "input": { "dtype": "float32", "shape": [1050000, 2, 4, 1] } },
26
+ "outputs": { "output": { "dtype": "float32", "shape": [1050000, 2, 4, 2], "tolerance": 0.001 } }
27
+ },
28
+ {
29
+ "name": "real_full_default_axis",
30
+ "inputs": {
31
+ "input": {
32
+ "dtype": "float32",
33
+ "shape": [1, 4, 1],
34
+ "data": { "kind": "values", "values": [1.0, 2.0, 0.0, -1.0] }
35
+ }
36
+ },
37
+ "outputs": { "output": { "dtype": "float32", "shape": [1, 4, 2], "tolerance": 0.00001 } }
38
+ },
39
+ {
40
+ "name": "default_axis_minus2_rank2",
41
+ "provenance": {
42
+ "source": "ONNX DFT-20 schema",
43
+ "notes": "Omitting the optional axis input must use -2. For rank 2 that normalizes to axis 0, the only signal axis."
44
+ },
45
+ "inputs": {
46
+ "input": { "dtype": "float32", "shape": [4, 1], "data": { "kind": "values", "values": [1.0, 0.0, 0.0, 0.0] } }
47
+ },
48
+ "outputs": {
49
+ "output": {
50
+ "dtype": "float32",
51
+ "shape": [4, 2],
52
+ "tolerance": 0.00001,
53
+ "data": { "kind": "values", "values": [1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0] }
54
+ }
55
+ }
56
+ },
57
+ {
58
+ "name": "default_axis_minus2_rank4",
59
+ "provenance": {
60
+ "source": "ONNX DFT-20 schema",
61
+ "notes": "Omitting the optional axis input must use -2. For rank 4 that is axis 2, independently transforming each [batch, inner] signal."
62
+ },
63
+ "inputs": {
64
+ "input": {
65
+ "dtype": "float32",
66
+ "shape": [1, 2, 4, 1],
67
+ "data": { "kind": "values", "values": [1.0, 0.0, 0.0, 0.0, 2.0, 0.0, 0.0, 0.0] }
68
+ }
69
+ },
70
+ "outputs": {
71
+ "output": {
72
+ "dtype": "float32",
73
+ "shape": [1, 2, 4, 2],
74
+ "tolerance": 0.00001,
75
+ "data": {
76
+ "kind": "values",
77
+ "values": [1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 2.0, 0.0, 2.0, 0.0, 2.0, 0.0, 2.0, 0.0]
78
+ }
79
+ }
80
+ }
81
+ },
82
+ {
83
+ "name": "real_subnormal_impulse_preserves_all_bins_gpu_gap",
84
+ "skipGpu": {
85
+ "category": "permanent",
86
+ "reason": "Portable WGSL floating-point semantics do not guarantee preservation of the subnormal values required by this fixture. Backend evidence: Metal FTZ: subnormal 1e-40 input flushes to zero on GPU, every output bin reads 0 instead of the subnormal value (tolerance 0)."
87
+ },
88
+ "provenance": {
89
+ "source": "onnxruntime/test/providers/cpu/signal/signal_ops_test.cc",
90
+ "test": "SignalOpsTest.DFT17_Float_radix2",
91
+ "notes": "Focused amplitude edge: a real impulse of 1e-40 has a DFT with every real frequency bin equal to that subnormal value."
92
+ },
93
+ "attrs": { "inverse": 0, "onesided": 0 },
94
+ "inputs": {
95
+ "input": {
96
+ "dtype": "float32",
97
+ "shape": [1, 4, 1],
98
+ "data": { "kind": "values", "values": [1e-40, 0.0, 0.0, 0.0] }
99
+ }
100
+ },
101
+ "outputs": { "output": { "dtype": "float32", "shape": [1, 4, 2], "tolerance": 0 } }
102
+ },
103
+ {
104
+ "name": "complex_subnormal_impulse_preserves_all_bins_gpu_gap",
105
+ "skipGpu": {
106
+ "category": "permanent",
107
+ "reason": "Portable WGSL floating-point semantics do not guarantee preservation of the subnormal values required by this fixture. Backend evidence: Metal FTZ: subnormal 1e-40 input flushes to zero on GPU, every output bin reads 0 instead of the subnormal value (tolerance 0)."
108
+ },
109
+ "provenance": {
110
+ "source": "onnxruntime/test/providers/cpu/signal/signal_ops_test.cc",
111
+ "test": "SignalOpsTest.DFT17_Float_radix2",
112
+ "notes": "Complex companion to the real impulse edge: a subnormal complex impulse at sample zero should appear unchanged in every frequency bin."
113
+ },
114
+ "attrs": { "inverse": 0, "onesided": 0 },
115
+ "inputs": {
116
+ "input": {
117
+ "dtype": "float32",
118
+ "shape": [1, 4, 2],
119
+ "data": { "kind": "values", "values": [1e-40, -1e-40, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] }
120
+ }
121
+ },
122
+ "outputs": { "output": { "dtype": "float32", "shape": [1, 4, 2], "tolerance": 0 } }
123
+ },
124
+ {
125
+ "name": "complex_inverse_subnormal_dc_preserves_time_signal_gpu_gap",
126
+ "skipGpu": {
127
+ "category": "permanent",
128
+ "reason": "Portable WGSL floating-point semantics do not guarantee preservation of the subnormal values required by this fixture. Backend evidence: Metal FTZ: subnormal 1e-40 input flushes to zero on GPU, every output bin reads 0 instead of the subnormal value (tolerance 0)."
129
+ },
130
+ "provenance": {
131
+ "source": "onnxruntime/test/providers/cpu/signal/signal_ops_test.cc",
132
+ "test": "SignalOpsTest.DFT17_Float_inverse",
133
+ "notes": "Inverse companion: a subnormal DC bin scaled by 1/N should produce a valid finite subnormal constant signal."
134
+ },
135
+ "attrs": { "inverse": 1, "onesided": 0 },
136
+ "inputs": {
137
+ "input": {
138
+ "dtype": "float32",
139
+ "shape": [1, 4, 2],
140
+ "data": { "kind": "values", "values": [4e-40, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] }
141
+ }
142
+ },
143
+ "outputs": { "output": { "dtype": "float32", "shape": [1, 4, 2], "tolerance": 0 } }
144
+ },
145
+ {
146
+ "name": "rank2_axis0_subnormal_impulse_preserves_all_bins_gpu_gap",
147
+ "skipGpu": {
148
+ "category": "permanent",
149
+ "reason": "Portable WGSL floating-point semantics do not guarantee preservation of the subnormal values required by this fixture. Backend evidence: Metal FTZ: subnormal 1e-40 input flushes to zero on GPU, every output bin reads 0 instead of the subnormal value (tolerance 0)."
150
+ },
151
+ "provenance": {
152
+ "source": "onnxruntime/test/providers/cpu/signal/signal_ops_test.cc",
153
+ "test": "SignalOpsTest.DFT17_Float_radix2",
154
+ "notes": "Rank-2 axis-0 FFT companion: a real subnormal impulse should preserve the same subnormal real value in every bin."
155
+ },
156
+ "attrs": { "inverse": 0, "onesided": 0 },
157
+ "inputs": {
158
+ "input": { "dtype": "float32", "shape": [4, 1], "data": { "kind": "values", "values": [1e-40, 0.0, 0.0, 0.0] } }
159
+ },
160
+ "outputs": { "output": { "dtype": "float32", "shape": [4, 2], "tolerance": 0 } }
161
+ },
162
+ {
163
+ "name": "rank4_axis1_subnormal_impulse_preserves_all_bins_gpu_gap",
164
+ "skipGpu": {
165
+ "category": "permanent",
166
+ "reason": "Portable WGSL floating-point semantics do not guarantee preservation of the subnormal values required by this fixture. Backend evidence: Metal FTZ: subnormal 1e-40 input flushes to zero on GPU, every output bin reads 0 instead of the subnormal value (tolerance 0)."
167
+ },
168
+ "provenance": {
169
+ "source": "onnxruntime/test/providers/cpu/signal/signal_ops_test.cc",
170
+ "test": "SignalOpsTest.DFT17_Float_radix2",
171
+ "notes": "Rank-4 axis-1 FFT companion: the subnormal impulse is along the first signal axis with one inner position."
172
+ },
173
+ "attrs": { "inverse": 0, "onesided": 0 },
174
+ "inputs": {
175
+ "axis": { "dtype": "int32", "shape": [], "data": { "kind": "values", "values": [1] } },
176
+ "input": {
177
+ "dtype": "float32",
178
+ "shape": [1, 4, 1, 1],
179
+ "data": { "kind": "values", "values": [1e-40, 0.0, 0.0, 0.0] }
180
+ }
181
+ },
182
+ "outputs": { "output": { "dtype": "float32", "shape": [1, 4, 1, 2], "tolerance": 0 } }
183
+ },
184
+ {
185
+ "name": "rank4_axis2_subnormal_impulse_preserves_all_bins_gpu_gap",
186
+ "skipGpu": {
187
+ "category": "permanent",
188
+ "reason": "Portable WGSL floating-point semantics do not guarantee preservation of the subnormal values required by this fixture. Backend evidence: Metal FTZ: subnormal 1e-40 input flushes to zero on GPU, every output bin reads 0 instead of the subnormal value (tolerance 0)."
189
+ },
190
+ "provenance": {
191
+ "source": "onnxruntime/test/providers/cpu/signal/signal_ops_test.cc",
192
+ "test": "SignalOpsTest.DFT17_Float_radix2",
193
+ "notes": "Rank-4 axis-2 FFT companion: the subnormal impulse is along the second signal axis with one outer channel."
194
+ },
195
+ "attrs": { "inverse": 0, "onesided": 0 },
196
+ "inputs": {
197
+ "input": {
198
+ "dtype": "float32",
199
+ "shape": [1, 1, 4, 1],
200
+ "data": { "kind": "values", "values": [1e-40, 0.0, 0.0, 0.0] }
201
+ }
202
+ },
203
+ "outputs": { "output": { "dtype": "float32", "shape": [1, 1, 4, 2], "tolerance": 0 } }
204
+ },
205
+ {
206
+ "name": "negative_axis_minus2_rank3",
207
+ "provenance": {
208
+ "source": "https://github.com/onnx/onnx/blob/main/docs/Operators.md#DFT",
209
+ "notes": "DFT axis accepts negative values in [-r, -2]; for rank 3, axis=-2 is the last signal axis and matches axis=1."
210
+ },
211
+ "attrs": {},
212
+ "inputs": {
213
+ "input": {
214
+ "dtype": "float32",
215
+ "shape": [1, 4, 1],
216
+ "data": { "kind": "values", "values": [1.0, 2.0, 0.0, -1.0] }
217
+ }
218
+ },
219
+ "outputs": { "output": { "dtype": "float32", "shape": [1, 4, 2], "tolerance": 0.00001 } }
220
+ },
221
+ {
222
+ "name": "ort_axis_input_overrides_default_axis0",
223
+ "provenance": {
224
+ "source": "onnxruntime/test/providers/cpu/signal/signal_ops_test.cc",
225
+ "test": "SignalOpsTest.DFT20_invertible_real",
226
+ "notes": "Opset-20 axis is an input tensor. This same-shape axis=0 case is a regression lock ensuring the runtime axis overrides the default axis attribute."
227
+ },
228
+ "inputs": {
229
+ "input": {
230
+ "dtype": "float32",
231
+ "shape": [4, 3, 1],
232
+ "data": { "kind": "fillFloat32", "sinStep": 0.09, "cosStep": 0.21, "scale": 4.0 }
233
+ },
234
+ "axis": { "dtype": "int32", "shape": [], "data": { "kind": "values", "values": [0] } }
235
+ },
236
+ "outputs": { "output": { "dtype": "float32", "shape": [4, 3, 2], "tolerance": 0.0001 } }
237
+ },
238
+ {
239
+ "name": "ort_axis_input_overrides_default_rank4_axis2",
240
+ "provenance": {
241
+ "source": "onnxruntime/test/providers/cpu/signal/signal_ops_test.cc",
242
+ "test": "SignalOpsTest.DFT20_invertible_real",
243
+ "notes": "Rank-4 projection of the opset-20 axis-input path. The output shape also matches the default axis, so this locks correct runtime-axis decoding rather than allowing a silent axis-1 transform."
244
+ },
245
+ "inputs": {
246
+ "input": {
247
+ "dtype": "float32",
248
+ "shape": [1, 4, 4, 1],
249
+ "data": { "kind": "fillFloat32", "sinStep": 0.07, "cosStep": 0.17, "scale": 3.0 }
250
+ },
251
+ "axis": { "dtype": "int32", "shape": [], "data": { "kind": "values", "values": [2] } }
252
+ },
253
+ "outputs": { "output": { "dtype": "float32", "shape": [1, 4, 4, 2], "tolerance": 0.0001 } }
254
+ },
255
+ {
256
+ "name": "real_onesided_even",
257
+ "attrs": { "onesided": 1 },
258
+ "inputs": {
259
+ "input": {
260
+ "dtype": "float32",
261
+ "shape": [1, 4, 1],
262
+ "data": { "kind": "values", "values": [1.0, 2.0, 0.0, -1.0] }
263
+ }
264
+ },
265
+ "outputs": { "output": { "dtype": "float32", "shape": [1, 3, 2], "tolerance": 0.00001 } }
266
+ },
267
+ {
268
+ "name": "complex_inverse",
269
+ "attrs": { "inverse": 1 },
270
+ "inputs": {
271
+ "input": {
272
+ "dtype": "float32",
273
+ "shape": [1, 4, 2],
274
+ "data": { "kind": "values", "values": [2.0, 0.0, 1.0, -1.0, 0.0, 0.0, 1.0, 1.0] }
275
+ }
276
+ },
277
+ "outputs": { "output": { "dtype": "float32", "shape": [1, 4, 2], "tolerance": 0.00001 } }
278
+ },
279
+ {
280
+ "name": "real_onesided_odd_length",
281
+ "attrs": { "onesided": 1 },
282
+ "inputs": {
283
+ "input": {
284
+ "dtype": "float32",
285
+ "shape": [1, 5, 1],
286
+ "data": { "kind": "values", "values": [0.0, 1.0, 2.0, 3.0, 4.0] }
287
+ }
288
+ },
289
+ "outputs": { "output": { "dtype": "float32", "shape": [1, 3, 2], "tolerance": 0.00001 } }
290
+ },
291
+ {
292
+ "name": "real_full_batch2",
293
+ "inputs": {
294
+ "input": {
295
+ "dtype": "float32",
296
+ "shape": [2, 4, 1],
297
+ "data": { "kind": "values", "values": [1.0, 0.0, -1.0, 0.0, 0.0, 2.0, 0.0, -2.0] }
298
+ }
299
+ },
300
+ "outputs": { "output": { "dtype": "float32", "shape": [2, 4, 2], "tolerance": 0.00001 } }
301
+ },
302
+ {
303
+ "name": "complex_inverse_length3",
304
+ "attrs": { "inverse": 1 },
305
+ "inputs": {
306
+ "input": {
307
+ "dtype": "float32",
308
+ "shape": [1, 3, 2],
309
+ "data": { "kind": "values", "values": [6.0, 0.0, -3.0, 1.7320508, -3.0, -1.7320508] }
310
+ }
311
+ },
312
+ "outputs": { "output": { "dtype": "float32", "shape": [1, 3, 2], "tolerance": 0.00001 } }
313
+ },
314
+ {
315
+ "name": "real_radix2_onesided_length8_ort",
316
+ "provenance": {
317
+ "source": "onnxruntime/test/providers/cpu/signal/signal_ops_test.cc",
318
+ "test": "SignalOpsTest.DFT17_Float_radix2_onesided"
319
+ },
320
+ "attrs": { "onesided": 1 },
321
+ "inputs": {
322
+ "input": {
323
+ "dtype": "float32",
324
+ "shape": [1, 8, 1],
325
+ "data": { "kind": "values", "values": [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0] }
326
+ }
327
+ },
328
+ "outputs": { "output": { "dtype": "float32", "shape": [1, 5, 2], "tolerance": 0.00002 } }
329
+ },
330
+ {
331
+ "name": "fft_batch_axis1_len256_batch4_real_onesided_perf_compact",
332
+ "provenance": {
333
+ "notes": "Compact correctness sibling for the batch-axis onesided FFT benchmark; preserves real input, axis=1, power-of-two length, and onesided output without benchmark-scale tensors."
334
+ },
335
+ "attrs": { "onesided": 1 },
336
+ "inputs": { "input": { "dtype": "float32", "shape": [4, 256, 1] } },
337
+ "outputs": { "output": { "dtype": "float32", "shape": [4, 129, 2], "tolerance": 0.0002 } }
338
+ },
339
+ {
340
+ "name": "complex_full_batch2_length3",
341
+ "inputs": {
342
+ "input": {
343
+ "dtype": "float32",
344
+ "shape": [2, 3, 2],
345
+ "data": { "kind": "values", "values": [1.0, 0.0, 0.0, 1.0, -1.0, 0.0, 2.0, 0.0, 0.0, -1.0, 1.0, 1.0] }
346
+ }
347
+ },
348
+ "outputs": { "output": { "dtype": "float32", "shape": [2, 3, 2], "tolerance": 0.00001 } }
349
+ },
350
+ {
351
+ "name": "real_naive_length5_full_ort",
352
+ "provenance": {
353
+ "source": "onnxruntime/test/providers/cpu/signal/signal_ops_test.cc",
354
+ "test": "SignalOpsTest.DFT17_Float_naive"
355
+ },
356
+ "inputs": {
357
+ "input": {
358
+ "dtype": "float32",
359
+ "shape": [1, 5, 1],
360
+ "data": { "kind": "values", "values": [1.0, 2.0, 3.0, 4.0, 5.0] }
361
+ }
362
+ },
363
+ "outputs": { "output": { "dtype": "float32", "shape": [1, 5, 2], "tolerance": 0.00002 } }
364
+ },
365
+ {
366
+ "name": "real_radix2_length8_full_ort",
367
+ "provenance": {
368
+ "source": "onnxruntime/test/providers/cpu/signal/signal_ops_test.cc",
369
+ "test": "SignalOpsTest.DFT17_Float_radix2"
370
+ },
371
+ "inputs": {
372
+ "input": {
373
+ "dtype": "float32",
374
+ "shape": [1, 8, 1],
375
+ "data": { "kind": "values", "values": [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0] }
376
+ }
377
+ },
378
+ "outputs": { "output": { "dtype": "float32", "shape": [1, 8, 2], "tolerance": 0.00004 } }
379
+ },
380
+ {
381
+ "name": "real_naive_length5_onesided_ort",
382
+ "provenance": {
383
+ "source": "onnxruntime/test/providers/cpu/signal/signal_ops_test.cc",
384
+ "test": "SignalOpsTest.DFT17_Float_naive_onesided"
385
+ },
386
+ "attrs": { "onesided": 1 },
387
+ "inputs": {
388
+ "input": {
389
+ "dtype": "float32",
390
+ "shape": [1, 5, 1],
391
+ "data": { "kind": "values", "values": [1.0, 2.0, 3.0, 4.0, 5.0] }
392
+ }
393
+ },
394
+ "outputs": { "output": { "dtype": "float32", "shape": [1, 3, 2], "tolerance": 0.00002 } }
395
+ },
396
+ {
397
+ "name": "ort_dft_length_truncates_forward",
398
+ "provenance": {
399
+ "source": "onnxruntime/test/providers/cpu/signal/signal_ops_test.cc",
400
+ "test": "SignalOpsTest.DFT20_Float_naive",
401
+ "notes": "Valid opset-20 dft_length input that truncates the transform length below the input extent."
402
+ },
403
+ "inputs": {
404
+ "input": {
405
+ "dtype": "float32",
406
+ "shape": [1, 5, 1],
407
+ "data": { "kind": "values", "values": [1.0, 2.0, 3.0, 4.0, 5.0] }
408
+ },
409
+ "dft_length": { "dtype": "int32", "shape": [], "data": { "kind": "values", "values": [3] } },
410
+ "axis": { "dtype": "int32", "shape": [], "data": { "kind": "values", "values": [1] } }
411
+ },
412
+ "outputs": {
413
+ "output": {
414
+ "dtype": "float32",
415
+ "shape": [1, 3, 2],
416
+ "tolerance": 0.00002,
417
+ "data": { "kind": "values", "values": [6.0, 0.0, -1.5, 0.866025, -1.5, -0.866025] }
418
+ }
419
+ }
420
+ },
421
+ {
422
+ "name": "ort_dft_length_zero_pads_forward",
423
+ "provenance": {
424
+ "source": "onnxruntime/test/providers/cpu/signal/signal_ops_test.cc",
425
+ "test": "SignalOpsTest.DFT20_Float_naive",
426
+ "notes": "Valid opset-20 dft_length input that zero-pads the transform length beyond the input extent."
427
+ },
428
+ "inputs": {
429
+ "input": { "dtype": "float32", "shape": [1, 3, 1], "data": { "kind": "values", "values": [1.0, 2.0, 3.0] } },
430
+ "dft_length": { "dtype": "int32", "shape": [], "data": { "kind": "values", "values": [5] } },
431
+ "axis": { "dtype": "int32", "shape": [], "data": { "kind": "values", "values": [1] } }
432
+ },
433
+ "outputs": {
434
+ "output": {
435
+ "dtype": "float32",
436
+ "shape": [1, 5, 2],
437
+ "tolerance": 0.00002,
438
+ "data": {
439
+ "kind": "values",
440
+ "values": [6.0, 0.0, -0.809017, -3.665469, 0.309017, 1.677599, 0.309017, -1.677599, -0.809017, 3.665469]
441
+ }
442
+ }
443
+ }
444
+ },
445
+ {
446
+ "name": "complex_inverse_length5_ort",
447
+ "provenance": {
448
+ "source": "onnxruntime/test/providers/cpu/signal/signal_ops_test.cc",
449
+ "test": "SignalOpsTest.DFT17_Float_inverse"
450
+ },
451
+ "attrs": { "inverse": 1 },
452
+ "inputs": {
453
+ "input": {
454
+ "dtype": "float32",
455
+ "shape": [1, 5, 2],
456
+ "data": {
457
+ "kind": "values",
458
+ "values": [15.0, 0.0, -2.499999, 3.440955, -2.5, 0.8123, -2.499999, -0.812299, -2.500003, -3.440953]
459
+ }
460
+ }
461
+ },
462
+ "outputs": { "output": { "dtype": "float32", "shape": [1, 5, 2], "tolerance": 0.0001 } }
463
+ },
464
+ {
465
+ "name": "ort_irfft_radix2_even_length",
466
+ "provenance": {
467
+ "source": "onnxruntime/test/providers/cpu/signal/signal_ops_test.cc",
468
+ "test": "SignalOpsTest.DFT20_IRFFT_radix2"
469
+ },
470
+ "attrs": { "onesided": 1, "inverse": 1 },
471
+ "inputs": {
472
+ "input": {
473
+ "dtype": "float32",
474
+ "shape": [1, 5, 2],
475
+ "data": { "kind": "values", "values": [36.0, 0.0, -4.0, 9.65685, -4.0, 4.0, -4.0, 1.65685, -4.0, 0.0] }
476
+ }
477
+ },
478
+ "outputs": {
479
+ "output": {
480
+ "dtype": "float32",
481
+ "shape": [1, 8, 1],
482
+ "tolerance": 0.0001,
483
+ "data": { "kind": "values", "values": [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0] }
484
+ }
485
+ }
486
+ },
487
+ {
488
+ "name": "ort_irfft_radix2_rank3_axis0",
489
+ "provenance": {
490
+ "source": "onnxruntime/test/providers/cpu/signal/signal_ops_test.cc",
491
+ "test": "SignalOpsTest.DFT20_IRFFT_radix2",
492
+ "notes": "Axis-0 rank-3 projection of ORT's even-length IRFFT case."
493
+ },
494
+ "attrs": { "onesided": 1, "inverse": 1 },
495
+ "inputs": {
496
+ "input": {
497
+ "dtype": "float32",
498
+ "shape": [5, 1, 2],
499
+ "data": { "kind": "values", "values": [36.0, 0.0, -4.0, 9.65685, -4.0, 4.0, -4.0, 1.65685, -4.0, 0.0] }
500
+ },
501
+ "dft_length": { "dtype": "int32", "shape": [], "data": { "kind": "values", "values": [8] } },
502
+ "axis": { "dtype": "int32", "shape": [], "data": { "kind": "values", "values": [0] } }
503
+ },
504
+ "outputs": {
505
+ "output": {
506
+ "dtype": "float32",
507
+ "shape": [8, 1, 1],
508
+ "tolerance": 0.0001,
509
+ "data": { "kind": "values", "values": [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0] }
510
+ }
511
+ }
512
+ },
513
+ {
514
+ "name": "ort_irfft_naive_odd_length",
515
+ "provenance": {
516
+ "source": "onnxruntime/test/providers/cpu/signal/signal_ops_test.cc",
517
+ "test": "SignalOpsTest.DFT20_IRFFT_naive",
518
+ "notes": "Odd-length IRFFT needs explicit dft_length to disambiguate from the even default."
519
+ },
520
+ "attrs": { "onesided": 1, "inverse": 1 },
521
+ "inputs": {
522
+ "input": {
523
+ "dtype": "float32",
524
+ "shape": [1, 3, 2],
525
+ "data": { "kind": "values", "values": [15.0, 0.0, -2.499999, 3.440955, -2.5, 0.8123] }
526
+ },
527
+ "dft_length": { "dtype": "int32", "shape": [], "data": { "kind": "values", "values": [5] } },
528
+ "axis": { "dtype": "int32", "shape": [], "data": { "kind": "values", "values": [1] } }
529
+ },
530
+ "outputs": {
531
+ "output": {
532
+ "dtype": "float32",
533
+ "shape": [1, 5, 1],
534
+ "tolerance": 0.0001,
535
+ "data": { "kind": "values", "values": [1.0, 2.0, 3.0, 4.0, 5.0] }
536
+ }
537
+ }
538
+ },
539
+ {
540
+ "name": "ort_invertible_real_rank4_axis1_projection",
541
+ "provenance": {
542
+ "source": "onnxruntime/test/providers/cpu/signal/signal_ops_test.cc",
543
+ "test": "SignalOpsTest.DFT20_invertible_real",
544
+ "notes": "Single forward DFT projection from ORT's rank-4 real invertibility sweep."
545
+ },
546
+ "attrs": {},
547
+ "inputs": {
548
+ "axis": { "dtype": "int32", "shape": [], "data": { "kind": "values", "values": [1] } },
549
+ "input": {
550
+ "dtype": "float32",
551
+ "shape": [2, 3, 2, 1],
552
+ "data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.19, "scale": 8.0 }
553
+ }
554
+ },
555
+ "outputs": { "output": { "dtype": "float32", "shape": [2, 3, 2, 2], "tolerance": 0.0001 } }
556
+ },
557
+ {
558
+ "name": "ort_invertible_complex_rank4_axis2_projection",
559
+ "provenance": {
560
+ "source": "onnxruntime/test/providers/cpu/signal/signal_ops_test.cc",
561
+ "test": "SignalOpsTest.DFT20_invertible_complex",
562
+ "notes": "Single forward DFT projection from ORT's rank-4 complex invertibility sweep."
563
+ },
564
+ "attrs": {},
565
+ "inputs": {
566
+ "input": {
567
+ "dtype": "float32",
568
+ "shape": [2, 2, 4, 2],
569
+ "data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.23, "scale": 5.0 }
570
+ }
571
+ },
572
+ "outputs": { "output": { "dtype": "float32", "shape": [2, 2, 4, 2], "tolerance": 0.0001 } }
573
+ },
574
+ {
575
+ "name": "ort_invertible_complex_rank4_axis0",
576
+ "provenance": {
577
+ "source": "onnxruntime/test/providers/cpu/signal/signal_ops_test.cc",
578
+ "test": "SignalOpsTest.DFT20_invertible_complex",
579
+ "notes": "Single forward DFT projection from ORT's rank-4 complex invertibility sweep."
580
+ },
581
+ "attrs": {},
582
+ "inputs": {
583
+ "axis": { "dtype": "int32", "shape": [], "data": { "kind": "values", "values": [0] } },
584
+ "input": {
585
+ "dtype": "float32",
586
+ "shape": [4, 2, 3, 2],
587
+ "data": { "kind": "fillFloat32", "sinStep": 0.07, "cosStep": 0.17, "scale": 3.0 }
588
+ }
589
+ },
590
+ "outputs": { "output": { "dtype": "float32", "shape": [4, 2, 3, 2], "tolerance": 0.0001 } }
591
+ },
592
+ {
593
+ "name": "ort_invertible_real_rank3_axis0",
594
+ "provenance": {
595
+ "source": "onnxruntime/test/providers/cpu/signal/signal_ops_test.cc",
596
+ "test": "SignalOpsTest.DFT20_invertible_real",
597
+ "notes": "Single forward DFT projection from ORT's axis-0 invertibility sweep."
598
+ },
599
+ "attrs": {},
600
+ "inputs": {
601
+ "axis": { "dtype": "int32", "shape": [], "data": { "kind": "values", "values": [0] } },
602
+ "input": {
603
+ "dtype": "float32",
604
+ "shape": [4, 3, 1],
605
+ "data": { "kind": "fillFloat32", "sinStep": 0.09, "cosStep": 0.21, "scale": 4.0 }
606
+ }
607
+ },
608
+ "outputs": { "output": { "dtype": "float32", "shape": [4, 3, 2], "tolerance": 0.0001 } }
609
+ },
610
+ {
611
+ "name": "ort_rank2_complex_axis0",
612
+ "provenance": {
613
+ "source": "onnxruntime/test/providers/cpu/signal/signal_ops_test.cc",
614
+ "test": "SignalOpsTest.DFT20_2D_complex"
615
+ },
616
+ "attrs": {},
617
+ "inputs": {
618
+ "input": {
619
+ "dtype": "float32",
620
+ "shape": [8, 2],
621
+ "data": {
622
+ "kind": "values",
623
+ "values": [1.0, 0.0, 2.0, 0.0, 3.0, 0.0, 4.0, 0.0, 5.0, 0.0, 6.0, 0.0, 7.0, 0.0, 8.0, 0.0]
624
+ }
625
+ }
626
+ },
627
+ "outputs": {
628
+ "output": {
629
+ "dtype": "float32",
630
+ "shape": [8, 2],
631
+ "tolerance": 0.0001,
632
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/ort_rank2_complex_axis0_output_output" } }
633
+ }
634
+ }
635
+ },
636
+ {
637
+ "name": "ort_rank2_real_onesided_axis0",
638
+ "provenance": {
639
+ "source": "onnxruntime/test/providers/cpu/signal/signal_ops_test.cc",
640
+ "test": "SignalOpsTest.DFT20_2D_real"
641
+ },
642
+ "attrs": { "onesided": 1 },
643
+ "inputs": {
644
+ "input": {
645
+ "dtype": "float32",
646
+ "shape": [8, 1],
647
+ "data": { "kind": "values", "values": [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0] }
648
+ }
649
+ },
650
+ "outputs": {
651
+ "output": {
652
+ "dtype": "float32",
653
+ "shape": [5, 2],
654
+ "tolerance": 0.0001,
655
+ "data": { "kind": "values", "values": [36.0, 0.0, -4.0, 9.65685, -4.0, 4.0, -4.0, 1.65685, -4.0, 0.0] }
656
+ }
657
+ }
658
+ },
659
+ {
660
+ "name": "ort_rank2_real_full_axis0",
661
+ "provenance": {
662
+ "source": "onnxruntime/test/providers/cpu/signal/signal_ops_test.cc",
663
+ "test": "SignalOpsTest.DFT20_2D_real"
664
+ },
665
+ "attrs": {},
666
+ "inputs": {
667
+ "input": {
668
+ "dtype": "float32",
669
+ "shape": [8, 1],
670
+ "data": { "kind": "values", "values": [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0] }
671
+ },
672
+ "dft_length": { "dtype": "int32", "shape": [], "data": { "kind": "values", "values": [8] } },
673
+ "axis": { "dtype": "int32", "shape": [], "data": { "kind": "values", "values": [0] } }
674
+ },
675
+ "outputs": {
676
+ "output": {
677
+ "dtype": "float32",
678
+ "shape": [8, 2],
679
+ "tolerance": 0.0001,
680
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/ort_rank2_complex_axis0_output_output" } }
681
+ }
682
+ }
683
+ },
684
+ {
685
+ "name": "ort_rank2_irfft_even_axis0",
686
+ "provenance": {
687
+ "source": "onnxruntime/test/providers/cpu/signal/signal_ops_test.cc",
688
+ "test": "SignalOpsTest.DFT20_2D_complex_onesided_inverse"
689
+ },
690
+ "attrs": { "onesided": 1, "inverse": 1 },
691
+ "inputs": {
692
+ "input": {
693
+ "dtype": "float32",
694
+ "shape": [5, 2],
695
+ "data": { "kind": "values", "values": [36.0, 0.0, -4.0, 9.65685, -4.0, 4.0, -4.0, 1.65685, -4.0, 0.0] }
696
+ }
697
+ },
698
+ "outputs": {
699
+ "output": {
700
+ "dtype": "float32",
701
+ "shape": [8, 1],
702
+ "tolerance": 0.0001,
703
+ "data": { "kind": "values", "values": [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0] }
704
+ }
705
+ }
706
+ },
707
+ {
708
+ "name": "ort_rank2_irfft_odd_axis0",
709
+ "provenance": {
710
+ "source": "onnxruntime/test/providers/cpu/signal/signal_ops_test.cc",
711
+ "test": "SignalOpsTest.DFT20_IRFFT_naive",
712
+ "notes": "Rank-2 axis-0 projection of ORT's odd-length IRFFT case; explicit output length disambiguates odd from even."
713
+ },
714
+ "attrs": { "onesided": 1, "inverse": 1 },
715
+ "inputs": {
716
+ "input": {
717
+ "dtype": "float32",
718
+ "shape": [3, 2],
719
+ "data": { "kind": "values", "values": [15.0, 0.0, -2.499999, 3.440955, -2.5, 0.8123] }
720
+ },
721
+ "dft_length": { "dtype": "int32", "shape": [], "data": { "kind": "values", "values": [5] } },
722
+ "axis": { "dtype": "int32", "shape": [], "data": { "kind": "values", "values": [0] } }
723
+ },
724
+ "outputs": {
725
+ "output": {
726
+ "dtype": "float32",
727
+ "shape": [5, 1],
728
+ "tolerance": 0.0001,
729
+ "data": { "kind": "values", "values": [1.0, 2.0, 3.0, 4.0, 5.0] }
730
+ }
731
+ }
732
+ },
733
+ {
734
+ "name": "onnx_backend_dft_axis1_rank4",
735
+ "provenance": {
736
+ "source": "cmake/external/onnx/onnx/backend/test/data/node/test_dft",
737
+ "notes": "Uses the official shape and attributes with deterministic generated data to avoid committing the large random tensor literal."
738
+ },
739
+ "attrs": {},
740
+ "inputs": {
741
+ "axis": { "dtype": "int32", "shape": [], "data": { "kind": "values", "values": [1] } },
742
+ "input": {
743
+ "dtype": "float32",
744
+ "shape": [1, 10, 10, 1],
745
+ "data": { "kind": "fillFloat32", "sinStep": 0.019, "cosStep": 0.031, "scale": 1.0 }
746
+ }
747
+ },
748
+ "outputs": { "output": { "dtype": "float32", "shape": [1, 10, 10, 2], "tolerance": 0.0001 } }
749
+ },
750
+ {
751
+ "name": "onnx_backend_dft_axis2_rank4",
752
+ "provenance": {
753
+ "source": "cmake/external/onnx/onnx/backend/test/data/node/test_dft_axis",
754
+ "notes": "Uses the official shape and attributes with deterministic generated data to avoid committing the large random tensor literal."
755
+ },
756
+ "attrs": {},
757
+ "inputs": {
758
+ "input": {
759
+ "dtype": "float32",
760
+ "shape": [1, 10, 10, 1],
761
+ "data": { "kind": "fillFloat32", "sinStep": 0.019, "cosStep": 0.031, "scale": 1.0 }
762
+ }
763
+ },
764
+ "outputs": { "output": { "dtype": "float32", "shape": [1, 10, 10, 2], "tolerance": 0.0001 } }
765
+ },
766
+ {
767
+ "name": "onnx_backend_dft_inverse_axis1_rank4",
768
+ "provenance": {
769
+ "source": "cmake/external/onnx/onnx/backend/test/data/node/test_dft_inverse",
770
+ "notes": "Uses the official shape and attributes with deterministic generated data to avoid committing the large random tensor literal."
771
+ },
772
+ "attrs": { "inverse": 1 },
773
+ "inputs": {
774
+ "axis": { "dtype": "int32", "shape": [], "data": { "kind": "values", "values": [1] } },
775
+ "input": {
776
+ "dtype": "float32",
777
+ "shape": [1, 10, 10, 2],
778
+ "data": { "kind": "fillFloat32", "sinStep": 0.017, "cosStep": 0.029, "scale": 1.0 }
779
+ }
780
+ },
781
+ "outputs": { "output": { "dtype": "float32", "shape": [1, 10, 10, 2], "tolerance": 0.0001 } }
782
+ },
783
+ {
784
+ "name": "onnx_backend_dft_irfft_axis1_rank4",
785
+ "provenance": {
786
+ "source": "cmake/external/onnx/onnx/backend/test/data/node/test_dft_irfft",
787
+ "notes": "Uses the official shape and attributes with deterministic generated data to avoid committing the large random tensor literal."
788
+ },
789
+ "attrs": { "inverse": 1, "onesided": 1 },
790
+ "inputs": {
791
+ "axis": { "dtype": "int32", "shape": [], "data": { "kind": "values", "values": [1] } },
792
+ "input": {
793
+ "dtype": "float32",
794
+ "shape": [1, 6, 10, 2],
795
+ "data": { "kind": "fillFloat32", "sinStep": 0.023, "cosStep": 0.037, "scale": 1.0 }
796
+ }
797
+ },
798
+ "outputs": { "output": { "dtype": "float32", "shape": [1, 10, 10, 1], "tolerance": 0.0001 } }
799
+ },
800
+ {
801
+ "name": "onnx_backend_dft_rfft_axis1_rank4",
802
+ "provenance": {
803
+ "source": "cmake/external/onnx/onnx/backend/test/data/node/test_dft_rfft",
804
+ "notes": "Uses the official shape and attributes with deterministic generated data to avoid committing the large random tensor literal."
805
+ },
806
+ "attrs": { "onesided": 1 },
807
+ "inputs": {
808
+ "axis": { "dtype": "int32", "shape": [], "data": { "kind": "values", "values": [1] } },
809
+ "input": {
810
+ "dtype": "float32",
811
+ "shape": [1, 10, 10, 1],
812
+ "data": { "kind": "fillFloat32", "sinStep": 0.021, "cosStep": 0.034, "scale": 1.0 }
813
+ }
814
+ },
815
+ "outputs": { "output": { "dtype": "float32", "shape": [1, 6, 10, 2], "tolerance": 0.0001 } }
816
+ },
817
+ {
818
+ "name": "onnx_backend_dft_opset19_axis1_rank4",
819
+ "provenance": {
820
+ "source": "cmake/external/onnx/onnx/backend/test/data/node/test_dft_opset19",
821
+ "notes": "Uses the official shape and attributes with deterministic generated data to avoid committing the large random tensor literal."
822
+ },
823
+ "attrs": {},
824
+ "inputs": {
825
+ "axis": { "dtype": "int32", "shape": [], "data": { "kind": "values", "values": [1] } },
826
+ "input": {
827
+ "dtype": "float32",
828
+ "shape": [1, 10, 10, 1],
829
+ "data": { "kind": "fillFloat32", "sinStep": 0.026, "cosStep": 0.041, "scale": 1.0 }
830
+ }
831
+ },
832
+ "outputs": { "output": { "dtype": "float32", "shape": [1, 10, 10, 2], "tolerance": 0.0001 } }
833
+ },
834
+ {
835
+ "name": "onnx_backend_dft_axis_opset19_axis2_rank4",
836
+ "provenance": {
837
+ "source": "cmake/external/onnx/onnx/backend/test/data/node/test_dft_axis_opset19",
838
+ "notes": "Uses the official shape and attributes with deterministic generated data to avoid committing the large random tensor literal."
839
+ },
840
+ "attrs": {},
841
+ "inputs": {
842
+ "input": {
843
+ "dtype": "float32",
844
+ "shape": [1, 10, 10, 1],
845
+ "data": { "kind": "fillFloat32", "sinStep": 0.026, "cosStep": 0.041, "scale": 1.0 }
846
+ }
847
+ },
848
+ "outputs": { "output": { "dtype": "float32", "shape": [1, 10, 10, 2], "tolerance": 0.0001 } }
849
+ },
850
+ {
851
+ "name": "onnx_backend_dft_inverse_opset19_axis1_rank4",
852
+ "provenance": {
853
+ "source": "cmake/external/onnx/onnx/backend/test/data/node/test_dft_inverse_opset19",
854
+ "notes": "Uses the official shape and attributes with deterministic generated data to avoid committing the large random tensor literal."
855
+ },
856
+ "attrs": { "inverse": 1 },
857
+ "inputs": {
858
+ "axis": { "dtype": "int32", "shape": [], "data": { "kind": "values", "values": [1] } },
859
+ "input": {
860
+ "dtype": "float32",
861
+ "shape": [1, 10, 10, 2],
862
+ "data": { "kind": "fillFloat32", "sinStep": 0.027, "cosStep": 0.039, "scale": 1.0 }
863
+ }
864
+ },
865
+ "outputs": { "output": { "dtype": "float32", "shape": [1, 10, 10, 2], "tolerance": 0.0001 } }
866
+ },
867
+ {
868
+ "name": "onnx_backend_dft_irfft_opset19_axis1_rank4",
869
+ "provenance": {
870
+ "source": "cmake/external/onnx/onnx/backend/test/data/node/test_dft_irfft_opset19",
871
+ "notes": "Uses the official shape and attributes with deterministic generated data to avoid committing the large random tensor literal."
872
+ },
873
+ "attrs": { "inverse": 1, "onesided": 1 },
874
+ "inputs": {
875
+ "axis": { "dtype": "int32", "shape": [], "data": { "kind": "values", "values": [1] } },
876
+ "input": {
877
+ "dtype": "float32",
878
+ "shape": [1, 6, 10, 2],
879
+ "data": { "kind": "fillFloat32", "sinStep": 0.025, "cosStep": 0.043, "scale": 1.0 }
880
+ }
881
+ },
882
+ "outputs": { "output": { "dtype": "float32", "shape": [1, 10, 10, 1], "tolerance": 0.0001 } }
883
+ },
884
+ {
885
+ "name": "onnx_backend_dft_rfft_opset19_axis1_rank4",
886
+ "provenance": {
887
+ "source": "cmake/external/onnx/onnx/backend/test/data/node/test_dft_rfft_opset19",
888
+ "notes": "Uses the official shape and attributes with deterministic generated data to avoid committing the large random tensor literal."
889
+ },
890
+ "attrs": { "onesided": 1 },
891
+ "inputs": {
892
+ "axis": { "dtype": "int32", "shape": [], "data": { "kind": "values", "values": [1] } },
893
+ "input": {
894
+ "dtype": "float32",
895
+ "shape": [1, 10, 10, 1],
896
+ "data": { "kind": "fillFloat32", "sinStep": 0.029, "cosStep": 0.046, "scale": 1.0 }
897
+ }
898
+ },
899
+ "outputs": { "output": { "dtype": "float32", "shape": [1, 6, 10, 2], "tolerance": 0.0001 } }
900
+ },
901
+ {
902
+ "name": "fft_rank2_axis0_real_onesided_len256",
903
+ "attrs": { "onesided": 1 },
904
+ "inputs": {
905
+ "input": {
906
+ "dtype": "float32",
907
+ "shape": [256, 1],
908
+ "data": { "kind": "fillFloat32", "sinStep": 0.021, "cosStep": 0.031, "scale": 1.0 }
909
+ }
910
+ },
911
+ "outputs": { "output": { "dtype": "float32", "shape": [129, 2], "tolerance": 0.001, "relTolerance": 0.0001 } }
912
+ },
913
+ {
914
+ "name": "fft_rank2_axis0_complex_full_len512",
915
+ "attrs": {},
916
+ "inputs": {
917
+ "input": {
918
+ "dtype": "float32",
919
+ "shape": [512, 2],
920
+ "data": { "kind": "fillFloat32", "sinStep": 0.017, "cosStep": 0.029, "scale": 1.0 }
921
+ }
922
+ },
923
+ "outputs": { "output": { "dtype": "float32", "shape": [512, 2], "tolerance": 0.02, "relTolerance": 0.0001 } }
924
+ },
925
+ {
926
+ "name": "fft_batch_axis1_real_full_len1024_batch2",
927
+ "attrs": {},
928
+ "inputs": {
929
+ "input": {
930
+ "dtype": "float32",
931
+ "shape": [2, 1024, 1],
932
+ "data": { "kind": "fillFloat32", "sinStep": 0.013, "cosStep": 0.041, "scale": 1.0 }
933
+ }
934
+ },
935
+ "outputs": { "output": { "dtype": "float32", "shape": [2, 1024, 2], "tolerance": 0.1, "relTolerance": 0.0001 } }
936
+ },
937
+ {
938
+ "name": "tiled_real_nonpow2_len513_full",
939
+ "provenance": {
940
+ "notes": "Compact correctness lock for the large non-power-of-two tiled direct DFT, including its 64-lane tunable dispatch, final partial frequency workgroup, and twiddle resets."
941
+ },
942
+ "tunables": { "WORKGROUP_SIZE": 64 },
943
+ "inputs": {
944
+ "input": {
945
+ "dtype": "float32",
946
+ "shape": [1, 513, 1],
947
+ "data": { "kind": "fillFloat32", "sinStep": 0.013, "cosStep": 0.027, "scale": 0.5 }
948
+ }
949
+ },
950
+ "outputs": { "output": { "dtype": "float32", "shape": [1, 513, 2], "tolerance": 0.03, "relTolerance": 0.0002 } }
951
+ },
952
+ {
953
+ "name": "tiled_real_nonpow2_len4095_full_boundary",
954
+ "provenance": {
955
+ "notes": "Boundary-length accuracy lock for the non-power-of-two tiled DFT. Exercises all shared-memory slots and many twiddle resets at the largest supported signal length."
956
+ },
957
+ "inputs": {
958
+ "input": {
959
+ "dtype": "float32",
960
+ "shape": [1, 4095, 1],
961
+ "data": { "kind": "fillFloat32", "sinStep": 0.0013, "cosStep": 0.0027, "scale": 0.05 }
962
+ }
963
+ },
964
+ "outputs": { "output": { "dtype": "float32", "shape": [1, 4095, 2], "tolerance": 0.1, "relTolerance": 0.0005 } }
965
+ },
966
+ {
967
+ "name": "fft_batch_axis1_complex_inverse_len512",
968
+ "attrs": { "inverse": 1 },
969
+ "inputs": {
970
+ "input": {
971
+ "dtype": "float32",
972
+ "shape": [1, 512, 2],
973
+ "data": { "kind": "fillFloat32", "sinStep": 0.023, "cosStep": 0.037, "scale": 1.0 }
974
+ }
975
+ },
976
+ "outputs": { "output": { "dtype": "float32", "shape": [1, 512, 2], "tolerance": 0.0001, "relTolerance": 0.0001 } }
977
+ },
978
+ {
979
+ "name": "fft_batch_axis1_real_onesided_len2048",
980
+ "attrs": { "onesided": 1 },
981
+ "inputs": {
982
+ "input": {
983
+ "dtype": "float32",
984
+ "shape": [1, 2048, 1],
985
+ "data": { "kind": "fillFloat32", "sinStep": 0.011, "cosStep": 0.019, "scale": 1.0 }
986
+ }
987
+ },
988
+ "outputs": { "output": { "dtype": "float32", "shape": [1, 1025, 2], "tolerance": 0.06, "relTolerance": 0.0001 } }
989
+ },
990
+ {
991
+ "name": "fft_batch_axis1_real_full_len4096_storage_inplace",
992
+ "provenance": {
993
+ "notes": "Locks the portable single-dispatch storage-buffer radix-2 route above the shared Stockham size ceiling."
994
+ },
995
+ "inputs": {
996
+ "input": {
997
+ "dtype": "float32",
998
+ "shape": [1, 4096, 1],
999
+ "data": { "kind": "fillFloat32", "sinStep": 0.009, "cosStep": 0.017, "scale": 0.5 }
1000
+ }
1001
+ },
1002
+ "outputs": { "output": { "dtype": "float32", "shape": [1, 4096, 2], "tolerance": 0.35, "relTolerance": 0.001 } }
1003
+ },
1004
+ {
1005
+ "name": "fft_batch_axis1_complex_inverse_len4096_storage_inplace",
1006
+ "provenance": {
1007
+ "notes": "The storage-buffer radix-2 route only takes over above the shared-memory ceiling at length 2048, and every case that long was a forward transform of a real signal. This one is the inverse of a complex signal, which is the route's other three knobs at once: the twiddle sign, the two-component load, and the 1/N scaling."
1008
+ },
1009
+ "attrs": { "inverse": 1, "onesided": 0 },
1010
+ "inputs": {
1011
+ "input": {
1012
+ "dtype": "float32",
1013
+ "shape": [1, 4096, 2],
1014
+ "data": { "kind": "fillFloat32", "sinStep": 0.009, "cosStep": 0.017, "scale": 0.5 }
1015
+ }
1016
+ },
1017
+ "outputs": { "output": { "dtype": "float32", "shape": [1, 4096, 2], "tolerance": 0.0001, "relTolerance": 0.001 } }
1018
+ },
1019
+ {
1020
+ "name": "fft_batch_axis1_real_onesided_len4096_storage_scratch",
1021
+ "provenance": {
1022
+ "notes": "Covers the one-dispatch storage-buffer DIT route when a onesided output is too small to serve as its own FFT scratch."
1023
+ },
1024
+ "attrs": { "onesided": 1 },
1025
+ "inputs": {
1026
+ "input": {
1027
+ "dtype": "float32",
1028
+ "shape": [1, 4096, 1],
1029
+ "data": { "kind": "fillFloat32", "sinStep": 0.009, "cosStep": 0.017, "scale": 0.5 }
1030
+ }
1031
+ },
1032
+ "outputs": { "output": { "dtype": "float32", "shape": [1, 2049, 2], "tolerance": 0.35, "relTolerance": 0.001 } }
1033
+ },
1034
+ {
1035
+ "name": "fft_rank4_axis1_real_full_len256_inner3",
1036
+ "attrs": {},
1037
+ "inputs": {
1038
+ "axis": { "dtype": "int32", "shape": [], "data": { "kind": "values", "values": [1] } },
1039
+ "input": {
1040
+ "dtype": "float32",
1041
+ "shape": [2, 256, 3, 1],
1042
+ "data": { "kind": "fillFloat32", "sinStep": 0.027, "cosStep": 0.033, "scale": 1.0 }
1043
+ }
1044
+ },
1045
+ "outputs": {
1046
+ "output": { "dtype": "float32", "shape": [2, 256, 3, 2], "tolerance": 0.015, "relTolerance": 0.0001 }
1047
+ }
1048
+ },
1049
+ {
1050
+ "name": "fft_rank4_axis1_real_onesided_len16_inner5",
1051
+ "attrs": { "onesided": 1 },
1052
+ "inputs": {
1053
+ "axis": { "dtype": "int32", "shape": [], "data": { "kind": "values", "values": [1] } },
1054
+ "input": {
1055
+ "dtype": "float32",
1056
+ "shape": [1, 16, 5, 1],
1057
+ "data": { "kind": "fillFloat32", "sinStep": 0.043, "cosStep": 0.021, "scale": 1.0 }
1058
+ }
1059
+ },
1060
+ "outputs": { "output": { "dtype": "float32", "shape": [1, 9, 5, 2], "tolerance": 0.0001 } }
1061
+ },
1062
+ {
1063
+ "name": "fft_rank4_axis2_real_onesided_len512",
1064
+ "attrs": { "onesided": 1 },
1065
+ "inputs": {
1066
+ "input": {
1067
+ "dtype": "float32",
1068
+ "shape": [2, 3, 512, 1],
1069
+ "data": { "kind": "fillFloat32", "sinStep": 0.023, "cosStep": 0.039, "scale": 1.0 }
1070
+ }
1071
+ },
1072
+ "outputs": {
1073
+ "output": { "dtype": "float32", "shape": [2, 3, 257, 2], "tolerance": 0.002, "relTolerance": 0.0001 }
1074
+ }
1075
+ },
1076
+ {
1077
+ "name": "empty_zero_dim",
1078
+ "attrs": { "inverse": 0, "onesided": 0 },
1079
+ "inputs": { "input": { "dtype": "float32", "shape": [0, 4, 1], "data": { "kind": "values", "values": [] } } },
1080
+ "outputs": { "output": { "dtype": "float32", "shape": [0, 4, 2], "tolerance": 0 } }
1081
+ },
1082
+ {
1083
+ "name": "ort_caseB_empty",
1084
+ "attrs": { "onesided": 1 },
1085
+ "inputs": { "input": { "dtype": "float32", "shape": [1, 0, 1], "data": { "kind": "values", "values": [] } } },
1086
+ "outputs": {
1087
+ "output": {
1088
+ "dtype": "float32",
1089
+ "shape": [1, 1, 2],
1090
+ "data": { "kind": "values", "values": [0.0, 0.0] },
1091
+ "tolerance": 0.001
1092
+ }
1093
+ }
1094
+ },
1095
+ {
1096
+ "name": "irfft_naive_axis1_over_128_dispatch_guard",
1097
+ "provenance": {
1098
+ "source": "synthetic",
1099
+ "test": "naive batch_axis1 IRFFT, numel(output) > 128 dispatch coverage",
1100
+ "notes": "Odd output length 255 (non-power-of-2) forces the naive batch_axis1 variant (FFT radix-2 paths don't apply). dim(output,2)==1 on the onesided-inverse path, so the dispatch numerator must be numel(output)/dim(output,2)=255, not numel(output)/2=127: the latter only launches ceil(127/WG) workgroups and silently drops outputs >= ~128. numel(output)=255 > WG(128) so the drop is observable."
1101
+ },
1102
+ "attrs": { "inverse": 1, "onesided": 1 },
1103
+ "inputs": {
1104
+ "input": {
1105
+ "dtype": "float32",
1106
+ "shape": [1, 128, 2],
1107
+ "data": { "kind": "fillFloat32", "sinStep": 0.013, "cosStep": 0.019, "scale": 1.0 }
1108
+ },
1109
+ "dft_length": { "dtype": "int32", "shape": [], "data": { "kind": "values", "values": [255] } },
1110
+ "axis": { "dtype": "int32", "shape": [], "data": { "kind": "values", "values": [1] } }
1111
+ },
1112
+ "outputs": { "output": { "dtype": "float32", "shape": [1, 255, 1], "tolerance": 0.01, "relTolerance": 0.01 } }
1113
+ },
1114
+ {
1115
+ "name": "dft_length_default_axis_no_axis_input",
1116
+ "provenance": {
1117
+ "notes": "dft_length input present, axis input omitted (default transform axis 1). Exercises the general_len variant: forward DFT of a real rank-3 signal where dft_length matches the signal extent."
1118
+ },
1119
+ "inputs": {
1120
+ "input": {
1121
+ "dtype": "float32",
1122
+ "shape": [1, 4, 1],
1123
+ "data": { "kind": "values", "values": [1.0, 2.0, 0.0, -1.0] }
1124
+ },
1125
+ "dft_length": { "dtype": "int32", "shape": [], "data": { "kind": "values", "values": [4] } }
1126
+ },
1127
+ "outputs": { "output": { "dtype": "float32", "shape": [1, 4, 2], "tolerance": 0.00001 } }
1128
+ },
1129
+ {
1130
+ "name": "general_axis_input_len2048_naive_vs_fft_batch32",
1131
+ "attrs": { "inverse": 0, "onesided": 0 },
1132
+ "inputs": {
1133
+ "input": {
1134
+ "dtype": "float32",
1135
+ "shape": [32, 2048, 1],
1136
+ "data": { "kind": "fillFloat32", "sinStep": 0.0007, "cosStep": 0.0013, "scale": 1.0 }
1137
+ },
1138
+ "axis": { "dtype": "int32", "shape": [], "data": { "kind": "values", "values": [1] } }
1139
+ },
1140
+ "outputs": { "output": { "dtype": "float32", "shape": [32, 2048, 2], "tolerance": 0.05, "relTolerance": 0.01 } }
1141
+ },
1142
+ {
1143
+ "name": "general_axis_input_gidy_fold_over_8m_rows",
1144
+ "attrs": { "inverse": 0, "onesided": 0 },
1145
+ "inputs": {
1146
+ "input": {
1147
+ "dtype": "float32",
1148
+ "shape": [2099200, 4, 1],
1149
+ "data": { "kind": "fillFloat32", "sinStep": 0.013, "cosStep": 0.019, "scale": 1.0 }
1150
+ },
1151
+ "axis": { "dtype": "int32", "shape": [], "data": { "kind": "values", "values": [1] } }
1152
+ },
1153
+ "outputs": {
1154
+ "output": { "dtype": "float32", "shape": [2099200, 4, 2], "tolerance": 0.0001, "relTolerance": 0.0001 }
1155
+ }
1156
+ },
1157
+ {
1158
+ "name": "whisper_nfft400_naive_batch_axis1_onesided",
1159
+ "attrs": { "onesided": 1 },
1160
+ "inputs": {
1161
+ "input": {
1162
+ "dtype": "float32",
1163
+ "shape": [200, 400, 1],
1164
+ "data": { "kind": "fillFloat32", "sinStep": 0.011, "cosStep": 0.017, "scale": 1.0 }
1165
+ }
1166
+ },
1167
+ "outputs": { "output": { "dtype": "float32", "shape": [200, 201, 2], "tolerance": 0.02, "relTolerance": 0.001 } }
1168
+ },
1169
+ {
1170
+ "name": "irfft_batch_axis1_len512_reconstruct_real",
1171
+ "attrs": { "inverse": 1, "onesided": 1 },
1172
+ "inputs": {
1173
+ "input": {
1174
+ "dtype": "float32",
1175
+ "shape": [8, 257, 2],
1176
+ "data": { "kind": "fillFloat32", "sinStep": 0.023, "cosStep": 0.031, "scale": 1.0 }
1177
+ }
1178
+ },
1179
+ "outputs": { "output": { "dtype": "float32", "shape": [8, 512, 1], "tolerance": 0.02, "relTolerance": 0.01 } }
1180
+ },
1181
+ {
1182
+ "name": "general_len_rank4_axis1_dftlength_zeropad_len6",
1183
+ "provenance": {
1184
+ "notes": "dft_length=6 zero-pads a rank-4 axis-1 signal of length 4 up to 6 samples, forcing the general_len variant (rank-4 coordinate decode + zero-pad guard n>=input_axis_length). Untested rank-4 dft_length path."
1185
+ },
1186
+ "attrs": { "inverse": 0, "onesided": 0 },
1187
+ "inputs": {
1188
+ "axis": { "dtype": "int32", "shape": [], "data": { "kind": "values", "values": [1] } },
1189
+ "input": {
1190
+ "dtype": "float32",
1191
+ "shape": [2, 4, 3, 1],
1192
+ "data": { "kind": "fillFloat32", "sinStep": 0.037, "cosStep": 0.061, "scale": 2.0 }
1193
+ },
1194
+ "dft_length": { "dtype": "int32", "shape": [], "data": { "kind": "values", "values": [6] } }
1195
+ },
1196
+ "outputs": {
1197
+ "output": { "dtype": "float32", "shape": [2, 6, 3, 2], "tolerance": 0.0002, "relTolerance": 0.0001 }
1198
+ }
1199
+ },
1200
+ {
1201
+ "name": "general_irfft_rank4_axis2_odd_len5_no_nyquist",
1202
+ "provenance": {
1203
+ "source": "onnxruntime/test/providers/cpu/signal/signal_ops_test.cc",
1204
+ "test": "SignalOpsTest.DFT20_IRFFT_naive",
1205
+ "notes": "Rank-4 axis-2 odd-length IRFFT via general_axis_len: odd output length 5 has no exact Nyquist bin, so no weight-2 doubling should occur. Exercises the general kernel onesided_inverse branch + rank-4 coordinate decode."
1206
+ },
1207
+ "attrs": { "inverse": 1, "onesided": 1 },
1208
+ "inputs": {
1209
+ "input": {
1210
+ "dtype": "float32",
1211
+ "shape": [2, 2, 3, 2],
1212
+ "data": { "kind": "fillFloat32", "sinStep": 0.029, "cosStep": 0.047, "scale": 3.0 }
1213
+ },
1214
+ "dft_length": { "dtype": "int32", "shape": [], "data": { "kind": "values", "values": [5] } },
1215
+ "axis": { "dtype": "int32", "shape": [], "data": { "kind": "values", "values": [2] } }
1216
+ },
1217
+ "outputs": { "output": { "dtype": "float32", "shape": [2, 2, 5, 1], "tolerance": 0.0005, "relTolerance": 0.001 } }
1218
+ },
1219
+ {
1220
+ "name": "general_axis_len_negative_axis_rank3_dftlength_truncate",
1221
+ "provenance": {
1222
+ "notes": "Negative axis input (-2 -> axis 1 for rank 3) combined with a truncating dft_length (6 -> 4) forces general_axis_len and exercises runtime resolve_axis() negative normalization together with signal truncation. Untested combination."
1223
+ },
1224
+ "attrs": { "inverse": 0, "onesided": 0 },
1225
+ "inputs": {
1226
+ "input": {
1227
+ "dtype": "float32",
1228
+ "shape": [3, 6, 1],
1229
+ "data": { "kind": "fillFloat32", "sinStep": 0.033, "cosStep": 0.052, "scale": 2.0 }
1230
+ },
1231
+ "dft_length": { "dtype": "int32", "shape": [], "data": { "kind": "values", "values": [4] } },
1232
+ "axis": { "dtype": "int32", "shape": [], "data": { "kind": "values", "values": [-2] } }
1233
+ },
1234
+ "outputs": { "output": { "dtype": "float32", "shape": [3, 4, 2], "tolerance": 0.0002, "relTolerance": 0.0001 } }
1235
+ },
1236
+ {
1237
+ "name": "general_len_rank2_axis0_dftlength_zeropad_odd",
1238
+ "provenance": {
1239
+ "notes": "Rank-2 axis-0 with dft_length=5 zero-padding a length-3 signal forces general_len; validates the rank-2 (n_logical=1, pad=2) coordinate decode of dft-general under zero-padding."
1240
+ },
1241
+ "attrs": { "inverse": 0, "onesided": 0 },
1242
+ "inputs": {
1243
+ "input": { "dtype": "float32", "shape": [3, 1], "data": { "kind": "values", "values": [1.0, 2.0, 3.0] } },
1244
+ "dft_length": { "dtype": "int32", "shape": [], "data": { "kind": "values", "values": [5] } }
1245
+ },
1246
+ "outputs": { "output": { "dtype": "float32", "shape": [5, 2], "tolerance": 0.00002 } }
1247
+ },
1248
+ {
1249
+ "name": "fft_explicit_equal_length128_batch4_onesided",
1250
+ "provenance": {
1251
+ "notes": "Correctness lock for the optimized explicit-dft_length route when the runtime length equals the power-of-two signal length."
1252
+ },
1253
+ "attrs": { "inverse": 0, "onesided": 1 },
1254
+ "inputs": {
1255
+ "input": {
1256
+ "dtype": "float32",
1257
+ "shape": [4, 128, 1],
1258
+ "data": { "kind": "fillFloat32", "sinStep": 0.033, "cosStep": 0.052, "scale": 2.0 }
1259
+ },
1260
+ "dft_length": { "dtype": "int32", "shape": [], "data": { "kind": "values", "values": [128] } }
1261
+ },
1262
+ "outputs": { "output": { "dtype": "float32", "shape": [4, 65, 2], "tolerance": 0.002, "relTolerance": 0.001 } }
1263
+ },
1264
+ {
1265
+ "name": "fft_runtime_axis1_full_len16_batch2",
1266
+ "provenance": {
1267
+ "notes": "A scalar runtime axis selects axis 1, checking the guarded Stockham FFT and the complementary general-axis pass on the same input."
1268
+ },
1269
+ "attrs": { "inverse": 0, "onesided": 0 },
1270
+ "inputs": {
1271
+ "input": {
1272
+ "dtype": "float32",
1273
+ "shape": [2, 16, 1],
1274
+ "data": { "kind": "fillFloat32", "sinStep": 0.031, "cosStep": 0.047, "scale": 1.5 }
1275
+ },
1276
+ "axis": { "dtype": "int32", "shape": [], "data": { "kind": "values", "values": [1] } }
1277
+ },
1278
+ "outputs": {
1279
+ "output": { "dtype": "float32", "shape": [2, 16, 2], "tolerance": 0.00002, "relTolerance": 0.00001 }
1280
+ }
1281
+ },
1282
+ {
1283
+ "name": "fft_batch_axis1_real_full_len8192_storage_inplace_generic",
1284
+ "provenance": { "notes": "Non-4096 correctness lock for the geometry-derived storage-buffer radix-2 route." },
1285
+ "inputs": {
1286
+ "input": {
1287
+ "dtype": "float32",
1288
+ "shape": [1, 8192, 1],
1289
+ "data": { "kind": "fillFloat32", "sinStep": 0.0045, "cosStep": 0.0085, "scale": 0.25 }
1290
+ }
1291
+ },
1292
+ "outputs": { "output": { "dtype": "float32", "shape": [1, 8192, 2], "tolerance": 0.7, "relTolerance": 0.002 } }
1293
+ },
1294
+ {
1295
+ "name": "fft_batch_axis1_real_onesided_len8192_storage_scratch_generic",
1296
+ "provenance": { "notes": "Non-4096 correctness lock for the geometry-derived one-sided radix-2 scratch route." },
1297
+ "attrs": { "onesided": 1 },
1298
+ "inputs": {
1299
+ "input": {
1300
+ "dtype": "float32",
1301
+ "shape": [1, 8192, 1],
1302
+ "data": { "kind": "fillFloat32", "sinStep": 0.0045, "cosStep": 0.0085, "scale": 0.25 }
1303
+ }
1304
+ },
1305
+ "outputs": { "output": { "dtype": "float32", "shape": [1, 4097, 2], "tolerance": 0.7, "relTolerance": 0.002 } }
1306
+ },
1307
+ {
1308
+ "name": "tiled_real_nonpow2_len4097_onesided_above_old_boundary",
1309
+ "provenance": {
1310
+ "notes": "Proves the tiled direct transform is bounded by device workgroup storage rather than the former 4095 shape cutoff."
1311
+ },
1312
+ "attrs": { "onesided": 1 },
1313
+ "inputs": {
1314
+ "input": {
1315
+ "dtype": "float32",
1316
+ "shape": [1, 4097, 1],
1317
+ "data": { "kind": "fillFloat32", "sinStep": 0.0013, "cosStep": 0.0027, "scale": 0.05 }
1318
+ }
1319
+ },
1320
+ "outputs": { "output": { "dtype": "float32", "shape": [1, 2049, 2], "tolerance": 0.15, "relTolerance": 0.001 } }
1321
+ },
1322
+ {
1323
+ "name": "rank5_batched_axis3",
1324
+ "provenance": {
1325
+ "notes": "ONNX DFT permits arbitrary batch rank around the transformed axis. With the axis given as an attribute the general kernel needs only (outer, axis, inner), so the batch rank never reaches it."
1326
+ },
1327
+ "attrs": { "inverse": 0, "onesided": 0 },
1328
+ "inputs": {
1329
+ "input": {
1330
+ "dtype": "float32",
1331
+ "shape": [2, 1, 2, 4, 1],
1332
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.29, "scale": 0.5 }
1333
+ }
1334
+ },
1335
+ "outputs": { "output": { "dtype": "float32", "shape": [2, 1, 2, 4, 2], "tolerance": 0.00001 } }
1336
+ },
1337
+ {
1338
+ "name": "rank6_axis2_batch_and_inner",
1339
+ "provenance": {
1340
+ "notes": "A rank the packing has never seen, with batch dims on both sides of the transform axis (outer 4, inner 2). If any rank ceiling had merely been raised rather than removed, this is the case that finds it."
1341
+ },
1342
+ "attrs": { "inverse": 0, "onesided": 0 },
1343
+ "inputs": {
1344
+ "axis": { "dtype": "int32", "shape": [], "data": { "kind": "values", "values": [2] } },
1345
+ "input": {
1346
+ "dtype": "float32",
1347
+ "shape": [2, 2, 3, 1, 2, 1],
1348
+ "data": { "kind": "fillFloat32", "sinStep": 0.23, "cosStep": 0.41, "scale": 0.6 }
1349
+ }
1350
+ },
1351
+ "outputs": { "output": { "dtype": "float32", "shape": [2, 2, 3, 1, 2, 2], "tolerance": 0.00001 } }
1352
+ },
1353
+ {
1354
+ "name": "rank8_default_axis_contract_ceiling",
1355
+ "provenance": { "notes": "Exercises the manifest's advertised rank-8 ceiling with the ONNX default axis (-2)." },
1356
+ "attrs": { "inverse": 0, "onesided": 0 },
1357
+ "inputs": {
1358
+ "input": {
1359
+ "dtype": "float32",
1360
+ "shape": [1, 1, 1, 1, 1, 1, 2, 1],
1361
+ "data": { "kind": "values", "values": [1.0, 2.0] }
1362
+ }
1363
+ },
1364
+ "outputs": {
1365
+ "output": {
1366
+ "dtype": "float32",
1367
+ "shape": [1, 1, 1, 1, 1, 1, 2, 2],
1368
+ "tolerance": 0.00001,
1369
+ "data": { "kind": "values", "values": [3.0, 0.0, -1.0, 0.0] }
1370
+ }
1371
+ }
1372
+ }
1373
+ ]
1374
+ }