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

sync 91d990483a17

Browse files
README.md CHANGED
@@ -18,17 +18,17 @@ See the [ONNX `DFT` spec](https://onnx.ai/onnx/operators/onnx__DFT.html) for the
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
 
@@ -49,28 +49,32 @@ Default values (overridable per request):
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
 
 
18
 
19
  ## Inputs
20
 
21
+ | Name | Logical dtype | WebGPU storage | Rank | Shape | Description | Presence |
22
+ | --- | --- | --- | --- | --- | --- | --- |
23
+ | `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` | `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` | `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 | Logical dtype | Rank | Shape | Description | Presence |
30
+ | --- | --- | --- | --- | --- | --- |
31
+ | `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
 
 
49
 
50
  ## Files
51
 
52
+ - [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, per-variant templates, 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-runtime-axis-fft-shared.wgsl.jinja`](build/webgpu/dft-runtime-axis-fft-shared.wgsl.jinja)
60
  - [`dft-tiled-real.wgsl.jinja`](build/webgpu/dft-tiled-real.wgsl.jinja)
61
  - [`fft-radix2-dit-storage.wgsl.jinja`](build/webgpu/fft-radix2-dit-storage.wgsl.jinja)
62
  - [`fft-stockham-shared.wgsl.jinja`](build/webgpu/fft-stockham-shared.wgsl.jinja)
63
 
64
  ## Use with `@huggingface/kernels`
65
 
66
+ ```sh
67
+ npm install --save-exact @huggingface/kernels@0.0.1-preview.2
68
+ ```
69
 
70
+ Outputs with inferable metadata are allocated automatically. Explicit `outputs` entries request optional results or provide metadata that cannot be inferred from the supplied inputs and attributes.
71
 
72
+ This example supplies explicit metadata for:
73
 
74
+ - `output`
75
 
76
  The `version: 1` option selects the published kernel contract; it is independent of any operator opset, contrib `since_version`, or model version.
77
+ It follows the `v1` branch as fixes land. To pin exact artifact bytes, pass a 40-character commit `revision` instead of `version`.
78
 
79
  Replace each `*Data` placeholder with a typed array containing the corresponding input data.
80
 
build/webgpu/bench.json CHANGED
@@ -1,5 +1,4 @@
1
  {
2
- "op": "ai.onnx.DFT",
3
  "tunableSpace": { "WORKGROUP_SIZE": [64, 128, 256] },
4
  "cases": [
5
  {
 
1
  {
 
2
  "tunableSpace": { "WORKGROUP_SIZE": [64, 128, 256] },
3
  "cases": [
4
  {
build/webgpu/dft-contiguous-naive.wgsl.jinja CHANGED
@@ -1,17 +1,16 @@
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;
@@ -137,8 +136,8 @@ fn is_nyquist(freq: u32, signal_length: u32) -> bool {
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;
@@ -146,14 +145,14 @@ fn transform_input_base(sample: u32{% if source.batched %}, batch: u32{% 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) }}
 
1
  {{ env.wgsl.resourceDeclarations }}
2
  // Backend-stable unit-circle twiddles for DFT kernels. Retain the phase as a
3
+ // rational turn and route polynomial evaluation through one trigonometric core
4
+ // for FFT, direct DFT, STFT, and cosine-window calculations.
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. Each path retains
11
+ // its available phase representation, reduces it to [-pi, pi], then evaluates
12
+ // explicit polynomials in a fixed order. The half-pi core is degree 13 for sine
13
+ // and degree 12 for cosine.
 
14
  const PRECISE_TRIG_PI: f32 = 3.141592653589793;
15
  const PRECISE_TRIG_TWO_PI: f32 = 6.283185307179586;
16
  const PRECISE_TRIG_HALF_PI: f32 = 1.5707963267948966;
 
136
  {% endmacro %}
137
 
138
 
139
+ fn transform_input_base(sample: u32{% if batched %}, batch: u32{% endif %}) -> u32 {
140
+ {% if batched %}
141
  return (batch * params.signalLength + sample) * params.inputComponents;
142
  {% else %}
143
  return sample * params.inputComponents;
 
145
  }
146
 
147
  @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
148
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
149
  // Fold a long 1D output grid into y at the device dispatch limit.
150
+ let index = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ tunables.WORKGROUP_SIZE }}u;
151
  if (index >= params.count) {
152
  return;
153
  }
154
 
155
+ {% if batched %}
156
  let transform_index = index % params.outputLength;
157
  let batch = index / params.outputLength;
158
  {{ dft_naive_accumulate("transform_index", "params.signalLength", "params.outputLength", "batch", "0u", true, false) }}
build/webgpu/dft-general.wgsl.jinja CHANGED
@@ -1,17 +1,19 @@
 
 
 
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;
@@ -101,7 +103,7 @@ fn is_nyquist(freq: u32, signal_length: u32) -> bool {
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);
@@ -112,9 +114,9 @@ fn resolve_axis() -> u32 {
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);
@@ -124,13 +126,20 @@ fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups)
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);
@@ -148,7 +157,7 @@ fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups)
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; }
 
1
+ {% set skipSharedFftAxes = skipSharedFftAxes | default(false) %}
2
+ {% set sharedFftMinLength = sharedFftMinLength | default(0) %}
3
+ {% set sharedFftMaxLength = sharedFftMaxLength | default(0) %}
4
  {{ env.wgsl.resourceDeclarations }}
5
  // Backend-stable unit-circle twiddles for DFT kernels. Retain the phase as a
6
+ // rational turn and route polynomial evaluation through one trigonometric core
7
+ // for FFT, direct DFT, STFT, and cosine-window calculations.
8
  //
9
  // phase is in [0, period). sign is -1 for a forward transform and +1 for an
10
  // inverse transform. The result is (cos, sin).
11
  // Backend-stable f32 sine/cosine core.
12
  //
13
+ // Shader transcendental accuracy is implementation-defined. Each path retains
14
+ // its available phase representation, reduces it to [-pi, pi], then evaluates
15
+ // explicit polynomials in a fixed order. The half-pi core is degree 13 for sine
16
+ // and degree 12 for cosine.
 
17
  const PRECISE_TRIG_PI: f32 = 3.141592653589793;
18
  const PRECISE_TRIG_TWO_PI: f32 = 6.283185307179586;
19
  const PRECISE_TRIG_HALF_PI: f32 = 1.5707963267948966;
 
103
  }
104
 
105
  fn resolve_axis() -> u32 {
106
+ {% if hasAxisInput %}
107
  let raw = axis_in[0];
108
  let rank = i32(params.rank);
109
  let norm = select(raw, raw + rank, raw < 0);
 
114
  }
115
 
116
  @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
117
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
118
  // 2D-folded flat index over output elements (one per complex sample).
119
+ let index = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ tunables.WORKGROUP_SIZE }}u;
120
  if (index >= params.count) { return; }
121
 
122
  let in_dims = array<u32, 7>(params.inD0, params.inD1, params.inD2, params.inD3, params.inD4, params.inD5, params.inD6);
 
126
  let n_logical = params.rank - 1u;
127
  let pad = 7u - n_logical;
128
  let resolved_axis = resolve_axis();
129
+ {% if skipRuntimeAxis1 %}
130
+ // In this specialization, a resolved axis of 1 returns uniformly without
131
+ // writing output elements.
 
132
  if (resolved_axis == 1u) { return; }
133
  {% endif %}
134
  let transform_slot = resolved_axis + pad;
135
+ {% if skipSharedFftAxes %}
136
+ // Power-of-two axis lengths in the configured inclusive range return
137
+ // uniformly without writing output elements in this pass.
138
+ {
139
+ let axis_len = in_dims[transform_slot];
140
+ if (axis_len >= {{ sharedFftMinLength }}u && axis_len <= {{ sharedFftMaxLength }}u && (axis_len & (axis_len - 1u)) == 0u) { return; }
141
+ }
142
+ {% endif %}
143
 
144
  // Decode the right-aligned logical output coords from the flat index.
145
  var coords = array<u32, 7>(0u, 0u, 0u, 0u, 0u, 0u, 0u);
 
157
  let inverse = params.inverse != 0u;
158
  let onesided_inverse = inverse && params.onesided != 0u;
159
  var signal_length = input_axis_length;
160
+ {% if hasDftLengthInput %}
161
  if (dft_len_in[0] > 0) { signal_length = u32(dft_len_in[0]); }
162
  {% else %}
163
  if (params.dftLengthAttr > 0u) { signal_length = params.dftLengthAttr; }
build/webgpu/dft-rank4.wgsl.jinja CHANGED
@@ -1,30 +1,30 @@
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
  }
@@ -33,18 +33,17 @@
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;
@@ -183,7 +182,7 @@ fn transform_input_base(sample: u32, batch: u32, other: u32) -> u32 {
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;
 
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 dispatch's
4
+ // per-axis workgroup fold width (outputs > 16.7M elements).
5
  {% elif note == "limit" %}
6
+ // 2D-folded flat index: gid.y carries the high bits past the dispatch's
7
+ // per-axis workgroup fold width.
8
  {% elif note == "device-axis" %}
9
+ // The flat dispatch is folded across x/y at a fixed per-axis workgroup
10
+ // width; 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 dispatch's
13
+ // per-axis workgroup fold width (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
+ // dispatch's per-axis workgroup fold width.
17
  {% elif note == "dispatch" %}
18
+ // 2D-folded flat index: gid.y carries the high bits past the dispatch's
19
+ // per-axis workgroup fold width.
20
  {% endif %}
21
  {% if bound == "" %}
22
+ let {{ name }} = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ tunables.WORKGROUP_SIZE }}u;
23
  {%- elif guardInline %}
24
+ let {{ name }} = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ tunables.WORKGROUP_SIZE }}u;
25
  if ({{ name }} >= {{ bound }}) { return; }
26
  {%- else %}
27
+ let {{ name }} = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ tunables.WORKGROUP_SIZE }}u;
28
  if ({{ name }} >= {{ bound }}) {
29
  return;
30
  }
 
33
 
34
  {{ env.wgsl.resourceDeclarations }}
35
  // Backend-stable unit-circle twiddles for DFT kernels. Retain the phase as a
36
+ // rational turn and route polynomial evaluation through one trigonometric core
37
+ // for FFT, direct DFT, STFT, and cosine-window calculations.
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. Each path retains
44
+ // its available phase representation, reduces it to [-pi, pi], then evaluates
45
+ // explicit polynomials in a fixed order. The half-pi core is degree 13 for sine
46
+ // and degree 12 for cosine.
 
47
  const PRECISE_TRIG_PI: f32 = 3.141592653589793;
48
  const PRECISE_TRIG_TWO_PI: f32 = 6.283185307179586;
49
  const PRECISE_TRIG_HALF_PI: f32 = 1.5707963267948966;
 
182
  }
183
 
184
  @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
185
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
186
  {{ flat_index_2d("index", guardInline=true) }}
187
 
188
  let out_d2 = index % params.outputD2;
build/webgpu/dft-runtime-axis-fft-shared.wgsl.jinja ADDED
@@ -0,0 +1,186 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {{ env.wgsl.resourceDeclarations }}
2
+ // Backend-stable unit-circle twiddles for DFT kernels. Retain the phase as a
3
+ // rational turn and route polynomial evaluation through one trigonometric core
4
+ // for FFT, direct DFT, STFT, and cosine-window calculations.
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. Each path retains
11
+ // its available phase representation, reduces it to [-pi, pi], then evaluates
12
+ // explicit polynomials in a fixed order. The half-pi core is degree 13 for sine
13
+ // and degree 12 for cosine.
14
+ const PRECISE_TRIG_PI: f32 = 3.141592653589793;
15
+ const PRECISE_TRIG_TWO_PI: f32 = 6.283185307179586;
16
+ const PRECISE_TRIG_HALF_PI: f32 = 1.5707963267948966;
17
+
18
+ // Input must be in [-pi/2, pi/2]. Returns (cos(x), sin(x)).
19
+ fn precise_sincos_half_pi(x: f32) -> vec2<f32> {
20
+ let x2 = x * x;
21
+
22
+ var sinPolynomial = 1.6059043836821613e-10;
23
+ sinPolynomial = fma(sinPolynomial, x2, -2.505210838544172e-8);
24
+ sinPolynomial = fma(sinPolynomial, x2, 2.7557319223985893e-6);
25
+ sinPolynomial = fma(sinPolynomial, x2, -1.984126984126984e-4);
26
+ sinPolynomial = fma(sinPolynomial, x2, 8.333333333333333e-3);
27
+ sinPolynomial = fma(sinPolynomial, x2, -1.6666666666666666e-1);
28
+ let sine = x * fma(sinPolynomial, x2, 1.0);
29
+
30
+ var cosPolynomial = 2.08767569878681e-9;
31
+ cosPolynomial = fma(cosPolynomial, x2, -2.755731922398589e-7);
32
+ cosPolynomial = fma(cosPolynomial, x2, 2.48015873015873e-5);
33
+ cosPolynomial = fma(cosPolynomial, x2, -1.388888888888889e-3);
34
+ cosPolynomial = fma(cosPolynomial, x2, 4.1666666666666664e-2);
35
+ cosPolynomial = fma(cosPolynomial, x2, -5.0e-1);
36
+ let cosine = fma(cosPolynomial, x2, 1.0);
37
+
38
+ return vec2<f32>(cosine, sine);
39
+ }
40
+
41
+ // Input must be in [-pi, pi]. Returns (cos(x), sin(x)).
42
+ fn precise_sincos_centered(x: f32) -> vec2<f32> {
43
+ var folded = x;
44
+ var cosineSign = 1.0;
45
+ if (folded > PRECISE_TRIG_HALF_PI) {
46
+ folded = PRECISE_TRIG_PI - folded;
47
+ cosineSign = -1.0;
48
+ } else if (folded < -PRECISE_TRIG_HALF_PI) {
49
+ folded = -PRECISE_TRIG_PI - folded;
50
+ cosineSign = -1.0;
51
+ }
52
+ let value = precise_sincos_half_pi(folded);
53
+ return vec2<f32>(cosineSign * value.x, value.y);
54
+ }
55
+
56
+
57
+ fn dft_precise_twiddle(phase: u32, period: u32, sign: f32) -> vec2<f32> {
58
+ let reducedPhase = phase % period;
59
+ if (reducedPhase == 0u) {
60
+ return vec2<f32>(1.0, sign * 0.0);
61
+ }
62
+ if (period % 4u == 0u) {
63
+ let quarter = period / 4u;
64
+ if (reducedPhase == quarter) {
65
+ return vec2<f32>(0.0, sign);
66
+ }
67
+ if (reducedPhase == 2u * quarter) {
68
+ return vec2<f32>(-1.0, sign * 0.0);
69
+ }
70
+ if (reducedPhase == 3u * quarter) {
71
+ return vec2<f32>(0.0, -sign);
72
+ }
73
+ }
74
+ // Center the rational turn before multiplying by 2*pi. This avoids the
75
+ // cancellation in `2*pi*(N-1)/N - 2*pi` for bins near the end of a row.
76
+ let turns = f32(reducedPhase) / f32(period);
77
+ let centeredTurns = turns - floor(turns + 0.5);
78
+ return precise_sincos_centered(sign * PRECISE_TRIG_TWO_PI * centeredTurns);
79
+ }
80
+
81
+
82
+ // Shared-memory radix-2 Stockham FFT along a dynamically selected transform
83
+ // axis for inputs of any supported rank. The opset-20 `axis` input is a tensor,
84
+ // so the transform axis, its length, and the (outer, inner) geometry are resolved
85
+ // per dispatch from the logical dimensions in `params`. Shared scratch is sized
86
+ // for `MAX_N`.
87
+ // A resolved axis outside the inclusive `[MIN_N, MAX_N]` power-of-two range
88
+ // returns uniformly before any barrier and does not write output elements.
89
+ //
90
+ // One transform per workgroup: dispatch covers the largest transform count any
91
+ // admissible axis can need; workgroups past the resolved count exit uniformly.
92
+ // Stage s of the log2(N) ping-pong reads a = src[j], b = src[j + N/2], applies
93
+ // w = exp(SIGN*i*pi*(j mod ns)/ns) to b and writes the autosort pair, so the
94
+ // natural-order spectrum lands in the last stage's destination half. The
95
+ // twiddle angle never exceeds pi.
96
+ const MIN_N: u32 = {{ fftMinLength }}u;
97
+ const MAX_N: u32 = {{ fftMaxLength }}u;
98
+ const WG: u32 = {{ tunables.WORKGROUP_SIZE }}u;
99
+ const SIGN: f32 = {{ "1.0" if inverse else "-1.0" }};
100
+
101
+ var<workgroup> shRe: array<f32, 2u * MAX_N>;
102
+ var<workgroup> shIm: array<f32, 2u * MAX_N>;
103
+
104
+ fn resolve_axis() -> u32 {
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
+ }
110
+
111
+ @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
112
+ fn main(@builtin(workgroup_id) wgid: vec3<u32>,
113
+ @builtin(local_invocation_id) lid: vec3<u32>) {
114
+ let tid = lid.x;
115
+ let dims = array<u32, 7>(params.inD0, params.inD1, params.inD2, params.inD3, params.inD4, params.inD5, params.inD6);
116
+ let pad = 7u - (params.rank - 1u);
117
+ let slot = resolve_axis() + pad;
118
+ let n = dims[slot];
119
+ // This pass accepts power-of-two lengths in the inclusive configured range.
120
+ let admissible = n >= MIN_N && n <= MAX_N && (n & (n - 1u)) == 0u;
121
+ if (!admissible) { return; }
122
+ var outer = 1u;
123
+ var inner = 1u;
124
+ for (var s = 0u; s < 7u; s = s + 1u) {
125
+ let d = dims[s];
126
+ outer = select(outer, outer * d, s < slot);
127
+ inner = select(inner, inner * d, s > slot);
128
+ }
129
+ // Flat transform index over the 2D-folded workgroup grid (baked fold width).
130
+ let flat = wgid.x + wgid.y * {{ DISPATCH_FOLD_WIDTH }}u;
131
+ if (flat >= outer * inner) { return; }
132
+ let outerIndex = flat / inner;
133
+ let innerIndex = flat - outerIndex * inner;
134
+
135
+ for (var k = tid; k < n; k = k + WG) {
136
+ let base = ((outerIndex * n + k) * inner + innerIndex) * {{ inComp }}u;
137
+ shRe[k] = input[base];
138
+ {% if inComp == 2 %}
139
+ shIm[k] = input[base + 1u];
140
+ {% else %}
141
+ shIm[k] = 0.0;
142
+ {% endif %}
143
+ }
144
+ workgroupBarrier();
145
+
146
+ let nHalf = n / 2u;
147
+ var srcOff = 0u;
148
+ var dstOff = MAX_N;
149
+ for (var s = 0u; (1u << s) < n; s = s + 1u) {
150
+ let ns = 1u << s;
151
+ for (var j = tid; j < nHalf; j = j + WG) {
152
+ let jm = j & (ns - 1u);
153
+ let outBase = ((j >> s) << (s + 1u)) + jm;
154
+ let ar = shRe[srcOff + j];
155
+ let ai = shIm[srcOff + j];
156
+ let br = shRe[srcOff + nHalf + j];
157
+ let bi = shIm[srcOff + nHalf + j];
158
+ let twiddle = dft_precise_twiddle(jm, 2u * ns, SIGN);
159
+ let c = twiddle.x;
160
+ let sn = twiddle.y;
161
+ let tr = br * c - bi * sn;
162
+ let ti = br * sn + bi * c;
163
+ shRe[dstOff + outBase] = ar + tr;
164
+ shIm[dstOff + outBase] = ai + ti;
165
+ shRe[dstOff + outBase + ns] = ar - tr;
166
+ shIm[dstOff + outBase + ns] = ai - ti;
167
+ }
168
+ workgroupBarrier();
169
+ let swap = srcOff;
170
+ srcOff = dstOff;
171
+ dstOff = swap;
172
+ }
173
+
174
+ // The final swap leaves the natural-order result at srcOff. In inverse mode,
175
+ // the scale is 1/n; this route selects a power-of-two n.
176
+ {% if inverse %}
177
+ let scale = 1.0 / f32(n);
178
+ {% else %}
179
+ let scale = 1.0;
180
+ {% endif %}
181
+ for (var k = tid; k < n; k = k + WG) {
182
+ let outBase = ((outerIndex * n + k) * inner + innerIndex) * 2u;
183
+ output[outBase] = shRe[srcOff + k] * scale;
184
+ output[outBase + 1u] = shIm[srcOff + k] * scale;
185
+ }
186
+ }
build/webgpu/dft-tiled-real.wgsl.jinja CHANGED
@@ -5,18 +5,17 @@
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;
@@ -84,9 +83,9 @@ fn dft_precise_twiddle(phase: u32, period: u32, sign: f32) -> vec2<f32> {
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>;
 
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 polynomial evaluation through one trigonometric core
9
+ // for FFT, direct DFT, STFT, and cosine-window calculations.
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. Each path retains
16
+ // its available phase representation, reduces it to [-pi, pi], then evaluates
17
+ // explicit polynomials in a fixed order. The half-pi core is degree 13 for sine
18
+ // and degree 12 for cosine.
 
19
  const PRECISE_TRIG_PI: f32 = 3.141592653589793;
20
  const PRECISE_TRIG_TWO_PI: f32 = 6.283185307179586;
21
  const PRECISE_TRIG_HALF_PI: f32 = 1.5707963267948966;
 
83
  return precise_sincos_centered(sign * PRECISE_TRIG_TWO_PI * centeredTurns);
84
  }
85
 
86
+ const N: u32 = {{ transformLength }}u;
87
+ const NUM_BINS: u32 = {{ numBins }}u;
88
+ const WG: u32 = {{ workgroupSizeSpec }}u;
89
  const TWIDDLE_RESET: u32 = 64u;
90
 
91
  var<workgroup> signalTile: array<f32, N>;
build/webgpu/fft-radix2-dit-storage.wgsl.jinja CHANGED
@@ -8,12 +8,12 @@
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).
@@ -21,11 +21,10 @@
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 %}
@@ -106,14 +105,14 @@ fn dft_precise_twiddle(phase: u32, period: u32, sign: f32) -> vec2<f32> {
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(
@@ -138,16 +137,16 @@ fn main(
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 %}
@@ -163,7 +162,7 @@ fn main(
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;
@@ -207,7 +206,7 @@ fn main(
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;
@@ -223,7 +222,7 @@ fn main(
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;
 
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 = modeSpec == "stft" %}
12
+ {% set USE_SCRATCH = modeSpec != "dft" %}
13
  {% if not STFT %}
14
  // Backend-stable unit-circle twiddles for DFT kernels. Retain the phase as a
15
+ // rational turn and route polynomial evaluation through one trigonometric core
16
+ // for FFT, direct DFT, STFT, and cosine-window calculations.
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).
 
21
  {% set needPreciseTrigTwoPi = true %}
22
  // Backend-stable f32 sine/cosine core.
23
  //
24
+ // Shader transcendental accuracy is implementation-defined. Each path retains
25
+ // its available phase representation, reduces it to [-pi, pi], then evaluates
26
+ // explicit polynomials in a fixed order. The half-pi core is degree 13 for sine
27
+ // and degree 12 for cosine.
 
28
  {% if needPreciseTrigCentered %}
29
  const PRECISE_TRIG_PI: f32 = 3.141592653589793;
30
  {% endif %}
 
105
  {% if STFT %}
106
  const PI: f32 = 3.141592653589793;
107
  {% endif %}
108
+ const N: u32 = {{ fftLength }}u;
109
  const N_HALF: u32 = N / 2u;
110
+ const LOG2_N: u32 = {{ log2Length }}u;
111
  {% if USE_SCRATCH %}
112
+ const NUM_BINS: u32 = {{ numBins }}u;
113
  {% endif %}
114
  const WG: u32 = {{ tunables.WORKGROUP_SIZE }}u;
115
+ const SIGN: f32 = {{ "1.0" if inverse else "-1.0" }};
116
 
117
  @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
118
  fn main(
 
137
  var value = vec2<f32>(0.0);
138
  {% if STFT %}
139
  let signalIndex = start + n;
140
+ let inputBase = (batch * params.signalLength + signalIndex) * {{ inComp }}u;
141
  let w = window[n];
142
  value.x = signal[inputBase] * w;
143
+ {% if inComp == 2 %}
144
  value.y = signal[inputBase + 1u] * w;
145
  {% endif %}
146
  {% else %}
147
+ let inputBase = ((outerIndex * N + n) * params.inner + innerIndex) * {{ inComp }}u;
148
  value.x = input[inputBase];
149
+ {% if inComp == 2 %}
150
  value.y = input[inputBase + 1u];
151
  {% endif %}
152
  {% endif %}
 
162
  }
163
  storageBarrier();
164
 
165
+ {% for stage in range(1, log2Length + 1) %}
166
  {
167
  const SPAN: u32 = 1u << {{ stage }}u;
168
  const HALF: u32 = SPAN >> 1u;
 
206
  }
207
  {% endfor %}
208
 
209
+ {% if inverse %}
210
  const SCALE: f32 = 1.0 / f32(N);
211
  {% elif USE_SCRATCH %}
212
  const SCALE: f32 = 1.0;
 
222
  output[dst] = scratch[src] * SCALE;
223
  output[dst + 1u] = scratch[src + 1u] * SCALE;
224
  }
225
+ {% elif inverse %}
226
  for (var k = tid; k < N; k = k + WG) {
227
  let index = transformBase + k * 2u;
228
  output[index] = output[index] * SCALE;
build/webgpu/fft-stockham-shared.wgsl.jinja CHANGED
@@ -1,17 +1,18 @@
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;
@@ -84,28 +85,20 @@ fn dft_precise_twiddle(phase: u32, period: u32, sign: f32) -> vec2<f32> {
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).
@@ -117,26 +110,23 @@ 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;
@@ -148,8 +138,7 @@ fn main(@builtin(workgroup_id) wgid: vec3<u32>,
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) {
@@ -178,9 +167,8 @@ fn main(@builtin(workgroup_id) wgid: vec3<u32>,
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;
 
1
  {{ env.wgsl.resourceDeclarations }}
2
+ {% set axisRank = axisRank | default(0) %}
3
+ {% set expectedAxis = expectedAxis | default(0) %}
4
  // Backend-stable unit-circle twiddles for DFT kernels. Retain the phase as a
5
+ // rational turn and route polynomial evaluation through one trigonometric core
6
+ // for FFT, direct DFT, STFT, and cosine-window calculations.
7
  //
8
  // phase is in [0, period). sign is -1 for a forward transform and +1 for an
9
  // inverse transform. The result is (cos, sin).
10
  // Backend-stable f32 sine/cosine core.
11
  //
12
+ // Shader transcendental accuracy is implementation-defined. Each path retains
13
+ // its available phase representation, reduces it to [-pi, pi], then evaluates
14
+ // explicit polynomials in a fixed order. The half-pi core is degree 13 for sine
15
+ // and degree 12 for cosine.
 
16
  const PRECISE_TRIG_PI: f32 = 3.141592653589793;
17
  const PRECISE_TRIG_TWO_PI: f32 = 6.283185307179586;
18
  const PRECISE_TRIG_HALF_PI: f32 = 1.5707963267948966;
 
85
  // shared memory. log2(N) ping-pong stages alternate between the two halves of
86
  // a 2*N scratch pair (no bit reversal; the autosort write pattern leaves the
87
  // final stage in natural order). Threads stride over the N/2 butterflies of
88
+ // each stage with a workgroupBarrier between stages.
89
+ // This specialization evaluates roots inline.
90
+ // The input is [outer, N, inner, inComp] and the output is
 
91
  // [outer, numBins, inner, 2]; params.inner carries the (runtime) inner count
92
+ // and dispatch.x = outer * inner workgroups. The inverse-plus-onesided
93
+ // combination is outside this specialization.
94
+
95
+ const N: u32 = {{ fftLength }}u;
 
 
 
 
 
 
 
96
  const N_HALF: u32 = N / 2u;
97
+ const NUM_BINS: u32 = {{ numBins }}u;
98
  const WG: u32 = {{ tunables.WORKGROUP_SIZE }}u;
99
  // Twiddle = exp(SIGN * i * 2*pi * k / span); SIGN flips for the inverse
100
+ // transform.
101
+ const SIGN: f32 = {{ "1.0" if inverse else "-1.0" }};
102
 
103
 
104
  // Ping-pong scratch: stage reads [srcOff, srcOff+N), writes [dstOff, dstOff+N).
 
110
  @builtin(local_invocation_id) lid: vec3<u32>,
111
  @builtin(num_workgroups) nwg: vec3<u32>) {
112
  let tid = lid.x;
113
+ {% if runtimeAxis is defined and runtimeAxis %}
114
+ // This axis specialization returns uniformly unless the normalized dynamic
115
+ // axis equals its baked axis.
116
  let rawAxis = axis_in[0];
117
+ let normalizedAxis = select(rawAxis, rawAxis + {{ axisRank }}, rawAxis < 0);
118
+ if (normalizedAxis != {{ expectedAxis }}) { return; }
119
  {% endif %}
120
+ // Flat workgroup index for a grid folded across x and y. This reduces to
121
+ // wgid.x when the dispatch uses one row.
 
 
 
122
  let flat = wgid.x + wgid.y * nwg.x;
123
 
124
  let outerIndex = flat / params.inner;
125
  let innerIndex = flat - outerIndex * params.inner;
126
  for (var n = tid; n < N; n = n + WG) {
127
+ let base = ((outerIndex * N + n) * params.inner + innerIndex) * {{ inComp }}u;
128
  shRe[n] = input[base];
129
+ {% if inComp == 2 %}
130
  shIm[n] = input[base + 1u];
131
  {% else %}
132
  shIm[n] = 0.0;
 
138
  // applies w = exp(SIGN*i*pi*(j mod ns)/ns) to b, and writes
139
  // dst[(j div ns)*2*ns + (j mod ns)] = a + w*b
140
  // dst[(j div ns)*2*ns + (j mod ns) + ns] = a - w*b
141
+ // |angle| <= pi, which bounds the argument supplied to sin and cos.
 
142
  var srcOff = 0u;
143
  var dstOff = N;
144
  for (var s = 0u; (1u << s) < N; s = s + 1u) {
 
167
  }
168
 
169
  // The final swap leaves the natural-order result at srcOff. The 1/N inverse
170
+ // scale is an exactly representable power-of-two reciprocal applied once.
171
+ {% if inverse %}
 
172
  const SCALE: f32 = 1.0 / f32(N);
173
  {% else %}
174
  const SCALE: f32 = 1.0;
build/webgpu/manifest.json CHANGED
@@ -2,521 +2,251 @@
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,
@@ -525,33 +255,30 @@
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
  ]
@@ -567,18 +294,21 @@
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
  ]
@@ -591,18 +321,16 @@
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
  ]
@@ -610,22 +338,20 @@
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)"
@@ -636,22 +362,33 @@
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
  }
@@ -666,17 +403,15 @@
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
  ]
@@ -689,11 +424,28 @@
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
  ]
@@ -706,11 +458,28 @@
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
  ]
@@ -724,10 +493,28 @@
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
  ]
@@ -740,14 +527,13 @@
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
  ]
@@ -760,14 +546,13 @@
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
  ]
@@ -780,14 +565,13 @@
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
  ]
@@ -800,14 +584,13 @@
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
  ]
 
2
  "domain": "ai.onnx",
3
  "name": "DFT",
4
  "sinceVersion": 20,
5
+ "inputs": {
6
+ "input": { "dtype": "T" },
7
+ "dft_length": { "dtype": "L", "rank": 0, "optional": true },
8
+ "axis": { "dtype": "I", "rank": 0, "optional": true, "storage": "int32", "narrowing": "checked" }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  },
10
+ "outputs": { "output": { "dtype": "T", "rank": "ranks.input" } },
11
+ "attributes": { "inverse": { "default": 0 }, "onesided": { "default": 0 } },
12
  "attributeConstraints": { "inverse": { "values": [0, 1] }, "onesided": { "values": [0, 1] } },
13
  "typeConstraints": { "T": ["float32"], "L": ["int32"], "I": ["int64"] },
14
+ "tunables": {
15
+ "WORKGROUP_SIZE": { "default": 128 },
16
+ "TILED_REAL_WORKGROUP_SIZE": { "default": 256 },
17
+ "TILED_REAL_MIN_LENGTH": { "default": 512 },
18
+ "RUNTIME_AXIS_FFT_MIN_LENGTH": { "default": 64 }
 
 
 
 
 
 
 
19
  },
 
20
  "derive": {
21
  "dftAxisAttr": "ranks.input - 2",
22
  "dftAttrOuter": "outer(shapes.input, dftAxisAttr)",
23
  "dftAttrInputInner": "inner(shapes.input, dftAxisAttr) / dim(shapes.input, ranks.input - 1)",
24
  "dftAttrOutputOuter": "outer(shapes.output, dftAxisAttr)",
25
  "dftAttrOutputInner": "inner(shapes.output, dftAxisAttr) / dim(shapes.output, ranks.output - 1)",
26
+ "rank3BatchDispatchFits": "dim(shapes.input, 0) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535)",
27
  "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",
28
  "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",
29
  "rank3Axis1FftLog2": "log2ceil(dim(shapes.input, 1))",
30
+ "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",
31
+ "fftAnyAxisLen0": "dim(shapes.input, 0) if ranks.input - 1 > 0 else 0",
32
+ "fftAnyAxisLen1": "dim(shapes.input, 1) if ranks.input - 1 > 1 else 0",
33
+ "fftAnyAxisLen2": "dim(shapes.input, 2) if ranks.input - 1 > 2 else 0",
34
+ "fftAnyAxisLen3": "dim(shapes.input, 3) if ranks.input - 1 > 3 else 0",
35
+ "fftAnyAxisLen4": "dim(shapes.input, 4) if ranks.input - 1 > 4 else 0",
36
+ "fftAnyAxisLen5": "dim(shapes.input, 5) if ranks.input - 1 > 5 else 0",
37
+ "fftAnyAxisLen6": "dim(shapes.input, 6) if ranks.input - 1 > 6 else 0",
38
+ "fftAnyAxisCand0": "fftAnyAxisLen0 if (fftAnyAxisLen0 >= tunables.RUNTIME_AXIS_FFT_MIN_LENGTH and pow2ceil(fftAnyAxisLen0) == fftAnyAxisLen0 and fftAnyAxisLen0 * 16 <= device.limits.maxComputeWorkgroupStorageSize) else 0",
39
+ "fftAnyAxisCand1": "fftAnyAxisLen1 if (fftAnyAxisLen1 >= tunables.RUNTIME_AXIS_FFT_MIN_LENGTH and pow2ceil(fftAnyAxisLen1) == fftAnyAxisLen1 and fftAnyAxisLen1 * 16 <= device.limits.maxComputeWorkgroupStorageSize) else 0",
40
+ "fftAnyAxisCand2": "fftAnyAxisLen2 if (fftAnyAxisLen2 >= tunables.RUNTIME_AXIS_FFT_MIN_LENGTH and pow2ceil(fftAnyAxisLen2) == fftAnyAxisLen2 and fftAnyAxisLen2 * 16 <= device.limits.maxComputeWorkgroupStorageSize) else 0",
41
+ "fftAnyAxisCand3": "fftAnyAxisLen3 if (fftAnyAxisLen3 >= tunables.RUNTIME_AXIS_FFT_MIN_LENGTH and pow2ceil(fftAnyAxisLen3) == fftAnyAxisLen3 and fftAnyAxisLen3 * 16 <= device.limits.maxComputeWorkgroupStorageSize) else 0",
42
+ "fftAnyAxisCand4": "fftAnyAxisLen4 if (fftAnyAxisLen4 >= tunables.RUNTIME_AXIS_FFT_MIN_LENGTH and pow2ceil(fftAnyAxisLen4) == fftAnyAxisLen4 and fftAnyAxisLen4 * 16 <= device.limits.maxComputeWorkgroupStorageSize) else 0",
43
+ "fftAnyAxisCand5": "fftAnyAxisLen5 if (fftAnyAxisLen5 >= tunables.RUNTIME_AXIS_FFT_MIN_LENGTH and pow2ceil(fftAnyAxisLen5) == fftAnyAxisLen5 and fftAnyAxisLen5 * 16 <= device.limits.maxComputeWorkgroupStorageSize) else 0",
44
+ "fftAnyAxisCand6": "fftAnyAxisLen6 if (fftAnyAxisLen6 >= tunables.RUNTIME_AXIS_FFT_MIN_LENGTH and pow2ceil(fftAnyAxisLen6) == fftAnyAxisLen6 and fftAnyAxisLen6 * 16 <= device.limits.maxComputeWorkgroupStorageSize) else 0",
45
+ "fftAnyAxisMaxLen": "max(fftAnyAxisCand0, max(fftAnyAxisCand1, max(fftAnyAxisCand2, max(fftAnyAxisCand3, max(fftAnyAxisCand4, max(fftAnyAxisCand5, fftAnyAxisCand6))))))",
46
+ "fftAnyAxisMinLen": "min((fftAnyAxisCand0 if fftAnyAxisCand0 > 0 else 1073741824), min((fftAnyAxisCand1 if fftAnyAxisCand1 > 0 else 1073741824), min((fftAnyAxisCand2 if fftAnyAxisCand2 > 0 else 1073741824), min((fftAnyAxisCand3 if fftAnyAxisCand3 > 0 else 1073741824), min((fftAnyAxisCand4 if fftAnyAxisCand4 > 0 else 1073741824), min((fftAnyAxisCand5 if fftAnyAxisCand5 > 0 else 1073741824), (fftAnyAxisCand6 if fftAnyAxisCand6 > 0 else 1073741824)))))))",
47
+ "fftAnyAxisLogicalCount": "numel(shapes.input) / dim(shapes.input, ranks.input - 1) if ranks.input >= 1 and dim(shapes.input, ranks.input - 1) > 0 else 0",
48
+ "fftAnyAxisTransforms": "fftAnyAxisLogicalCount / fftAnyAxisMinLen if fftAnyAxisMaxLen >= tunables.RUNTIME_AXIS_FFT_MIN_LENGTH else 0"
49
+ },
50
+ "bindings": {
51
+ "input": { "buffer": "read-only-storage", "elementType": "f32" },
52
+ "axis_in": { "arg": "axis", "buffer": "read-only-storage", "elementType": "i32", "length": 1 },
53
+ "output": { "buffer": "storage", "elementType": "f32" },
54
+ "params_2": {
55
+ "name": "params",
56
+ "buffer": "uniform",
57
+ "struct": [
58
+ { "name": "count", "type": "u32", "value": "numel(shapes.output) / dim(shapes.output, ranks.output - 1)" },
59
+ { "name": "rank", "type": "u32", "value": "ranks.input" },
60
+ { "name": "inputComponents", "type": "u32", "value": "dim(shapes.input, ranks.input - 1)" },
61
+ { "name": "outputComponents", "type": "u32", "value": "dim(shapes.output, ranks.output - 1)" },
62
+ { "name": "inverse", "type": "u32", "value": "attrs.inverse" },
63
+ { "name": "onesided", "type": "u32", "value": "attrs.onesided" },
64
+ { "name": "inD0", "type": "u32", "value": "dim(shapes.input, ranks.input - 8) if ranks.input >= 8 else 1" },
65
+ { "name": "inD1", "type": "u32", "value": "dim(shapes.input, ranks.input - 7) if ranks.input >= 7 else 1" },
66
+ { "name": "inD2", "type": "u32", "value": "dim(shapes.input, ranks.input - 6) if ranks.input >= 6 else 1" },
67
+ { "name": "inD3", "type": "u32", "value": "dim(shapes.input, ranks.input - 5) if ranks.input >= 5 else 1" },
68
+ { "name": "inD4", "type": "u32", "value": "dim(shapes.input, ranks.input - 4) if ranks.input >= 4 else 1" },
69
+ { "name": "inD5", "type": "u32", "value": "dim(shapes.input, ranks.input - 3) if ranks.input >= 3 else 1" },
70
+ { "name": "inD6", "type": "u32", "value": "dim(shapes.input, ranks.input - 2)" },
71
+ { "name": "outD0", "type": "u32", "value": "dim(shapes.output, ranks.output - 8) if ranks.output >= 8 else 1" },
72
+ { "name": "outD1", "type": "u32", "value": "dim(shapes.output, ranks.output - 7) if ranks.output >= 7 else 1" },
73
+ { "name": "outD2", "type": "u32", "value": "dim(shapes.output, ranks.output - 6) if ranks.output >= 6 else 1" },
74
+ { "name": "outD3", "type": "u32", "value": "dim(shapes.output, ranks.output - 5) if ranks.output >= 5 else 1" },
75
+ { "name": "outD4", "type": "u32", "value": "dim(shapes.output, ranks.output - 4) if ranks.output >= 4 else 1" },
76
+ { "name": "outD5", "type": "u32", "value": "dim(shapes.output, ranks.output - 3) if ranks.output >= 3 else 1" },
77
+ { "name": "outD6", "type": "u32", "value": "dim(shapes.output, ranks.output - 2)" },
78
+ { "name": "dftLengthAttr", "type": "u32", "value": 0 }
79
+ ]
80
+ },
81
+ "params": { "buffer": "uniform", "struct": [{ "name": "inner", "type": "u32", "value": 1 }] },
82
+ "params_8": {
83
+ "name": "params",
84
+ "buffer": "uniform",
85
+ "struct": [
86
+ { "name": "count", "type": "u32", "value": "numel(shapes.output) / dim(shapes.output, ranks.output - 1)" },
87
+ { "name": "rank", "type": "u32", "value": "4" },
88
+ { "name": "inputComponents", "type": "u32", "value": "dim(shapes.input, ranks.input - 1)" },
89
+ { "name": "outputComponents", "type": "u32", "value": "dim(shapes.output, ranks.output - 1)" },
90
+ { "name": "inverse", "type": "u32", "value": "attrs.inverse" },
91
+ { "name": "onesided", "type": "u32", "value": "attrs.onesided" },
92
+ { "name": "inD0", "type": "u32", "value": 1 },
93
+ { "name": "inD1", "type": "u32", "value": 1 },
94
+ { "name": "inD2", "type": "u32", "value": 1 },
95
+ { "name": "inD3", "type": "u32", "value": 1 },
96
+ { "name": "inD4", "type": "u32", "value": "dftAttrOuter" },
97
+ { "name": "inD5", "type": "u32", "value": "dim(shapes.input, dftAxisAttr)" },
98
+ { "name": "inD6", "type": "u32", "value": "dftAttrInputInner" },
99
+ { "name": "outD0", "type": "u32", "value": 1 },
100
+ { "name": "outD1", "type": "u32", "value": 1 },
101
+ { "name": "outD2", "type": "u32", "value": 1 },
102
+ { "name": "outD3", "type": "u32", "value": 1 },
103
+ { "name": "outD4", "type": "u32", "value": "dftAttrOutputOuter" },
104
+ { "name": "outD5", "type": "u32", "value": "dim(shapes.output, dftAxisAttr)" },
105
+ { "name": "outD6", "type": "u32", "value": "dftAttrOutputInner" },
106
+ { "name": "axisAttr", "type": "u32", "value": "1" },
107
+ { "name": "dftLengthAttr", "type": "u32", "value": 0 }
108
+ ]
109
+ },
110
+ "dft_len_in": { "arg": "dft_length", "buffer": "read-only-storage", "elementType": "i32", "length": 1 },
111
+ "params_9": {
112
+ "name": "params",
113
+ "buffer": "uniform",
114
+ "struct": [
115
+ { "name": "count", "type": "u32", "value": "numel(shapes.output) / dim(shapes.output, ranks.output - 1)" },
116
+ { "name": "rank", "type": "u32", "value": "ranks.input" },
117
+ { "name": "inputComponents", "type": "u32", "value": "dim(shapes.input, ranks.input - 1)" },
118
+ { "name": "outputComponents", "type": "u32", "value": "dim(shapes.output, ranks.output - 1)" },
119
+ { "name": "inverse", "type": "u32", "value": "attrs.inverse" },
120
+ { "name": "onesided", "type": "u32", "value": "attrs.onesided" },
121
+ { "name": "inD0", "type": "u32", "value": "dim(shapes.input, ranks.input - 8) if ranks.input >= 8 else 1" },
122
+ { "name": "inD1", "type": "u32", "value": "dim(shapes.input, ranks.input - 7) if ranks.input >= 7 else 1" },
123
+ { "name": "inD2", "type": "u32", "value": "dim(shapes.input, ranks.input - 6) if ranks.input >= 6 else 1" },
124
+ { "name": "inD3", "type": "u32", "value": "dim(shapes.input, ranks.input - 5) if ranks.input >= 5 else 1" },
125
+ { "name": "inD4", "type": "u32", "value": "dim(shapes.input, ranks.input - 4) if ranks.input >= 4 else 1" },
126
+ { "name": "inD5", "type": "u32", "value": "dim(shapes.input, ranks.input - 3) if ranks.input >= 3 else 1" },
127
+ { "name": "inD6", "type": "u32", "value": "dim(shapes.input, ranks.input - 2)" },
128
+ { "name": "outD0", "type": "u32", "value": "dim(shapes.output, ranks.output - 8) if ranks.output >= 8 else 1" },
129
+ { "name": "outD1", "type": "u32", "value": "dim(shapes.output, ranks.output - 7) if ranks.output >= 7 else 1" },
130
+ { "name": "outD2", "type": "u32", "value": "dim(shapes.output, ranks.output - 6) if ranks.output >= 6 else 1" },
131
+ { "name": "outD3", "type": "u32", "value": "dim(shapes.output, ranks.output - 5) if ranks.output >= 5 else 1" },
132
+ { "name": "outD4", "type": "u32", "value": "dim(shapes.output, ranks.output - 4) if ranks.output >= 4 else 1" },
133
+ { "name": "outD5", "type": "u32", "value": "dim(shapes.output, ranks.output - 3) if ranks.output >= 3 else 1" },
134
+ { "name": "outD6", "type": "u32", "value": "dim(shapes.output, ranks.output - 2)" }
135
+ ]
136
+ },
137
+ "params_10": {
138
+ "name": "params",
139
+ "buffer": "uniform",
140
+ "struct": [
141
+ { "name": "count", "type": "u32", "value": "numel(shapes.output) / dim(shapes.output, ranks.output - 1)" },
142
+ { "name": "rank", "type": "u32", "value": "4" },
143
+ { "name": "inputComponents", "type": "u32", "value": "dim(shapes.input, ranks.input - 1)" },
144
+ { "name": "outputComponents", "type": "u32", "value": "dim(shapes.output, ranks.output - 1)" },
145
+ { "name": "inverse", "type": "u32", "value": "attrs.inverse" },
146
+ { "name": "onesided", "type": "u32", "value": "attrs.onesided" },
147
+ { "name": "inD0", "type": "u32", "value": 1 },
148
+ { "name": "inD1", "type": "u32", "value": 1 },
149
+ { "name": "inD2", "type": "u32", "value": 1 },
150
+ { "name": "inD3", "type": "u32", "value": 1 },
151
+ { "name": "inD4", "type": "u32", "value": "dftAttrOuter" },
152
+ { "name": "inD5", "type": "u32", "value": "dim(shapes.input, dftAxisAttr)" },
153
+ { "name": "inD6", "type": "u32", "value": "dftAttrInputInner" },
154
+ { "name": "outD0", "type": "u32", "value": 1 },
155
+ { "name": "outD1", "type": "u32", "value": 1 },
156
+ { "name": "outD2", "type": "u32", "value": 1 },
157
+ { "name": "outD3", "type": "u32", "value": 1 },
158
+ { "name": "outD4", "type": "u32", "value": "dftAttrOutputOuter" },
159
+ { "name": "outD5", "type": "u32", "value": "dim(shapes.output, dftAxisAttr)" },
160
+ { "name": "outD6", "type": "u32", "value": "dftAttrOutputInner" },
161
+ { "name": "axisAttr", "type": "u32", "value": "1" }
162
+ ]
163
+ }
164
  },
165
+ "variants": [
166
+ {
167
+ "id": "runtime_axis_shared_fft",
168
+ "priority": 6,
169
+ "when": ["present.axis", "numel(shapes.axis) == 1", "not present.dft_length", "attrs.onesided == 0", "ranks.input >= 2", "ranks.input <= 8", "ranks.input == ranks.output", "ranks.input != 3", "dim(shapes.input, ranks.input - 1) >= 1", "dim(shapes.input, ranks.input - 1) <= 2", "dim(shapes.output, ranks.output - 1) == 2", "numel(shapes.output) == 2 * fftAnyAxisLogicalCount", "fftAnyAxisMaxLen >= tunables.RUNTIME_AXIS_FFT_MIN_LENGTH", "tunables.RUNTIME_AXIS_FFT_MIN_LENGTH >= 4", "pow2ceil(tunables.RUNTIME_AXIS_FFT_MIN_LENGTH) == tunables.RUNTIME_AXIS_FFT_MIN_LENGTH", "fftAnyAxisMaxLen * 16 <= device.limits.maxComputeWorkgroupStorageSize", "ceilDiv(fftAnyAxisTransforms, min(device.limits.maxComputeWorkgroupsPerDimension, 65535)) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535)"],
170
+ "passes": [
171
+ {
172
+ "id": "shared_fft",
173
+ "name": "DFT.RuntimeAxisFftShared",
174
+ "shader": "dft-runtime-axis-fft-shared.wgsl.jinja",
175
+ "derive": {
176
+ "fftMaxLength": "fftAnyAxisMaxLen",
177
+ "fftMinLength": "tunables.RUNTIME_AXIS_FFT_MIN_LENGTH",
178
+ "inComp": "dim(shapes.input, ranks.input - 1)",
179
+ "inverse": "attrs.inverse != 0"
180
+ },
181
+ "bindings": [
182
+ "input",
183
+ "axis_in",
184
+ "output",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
185
  {
186
+ "name": "params",
187
+ "struct": [
188
+ { "name": "rank", "type": "u32", "value": "ranks.input" },
189
+ {
190
+ "name": "inD0",
191
+ "type": "u32",
192
+ "value": "dim(shapes.input, ranks.input - 8) if ranks.input >= 8 else 1"
193
+ },
194
+ {
195
+ "name": "inD1",
196
+ "type": "u32",
197
+ "value": "dim(shapes.input, ranks.input - 7) if ranks.input >= 7 else 1"
198
+ },
199
+ {
200
+ "name": "inD2",
201
+ "type": "u32",
202
+ "value": "dim(shapes.input, ranks.input - 6) if ranks.input >= 6 else 1"
203
+ },
204
+ {
205
+ "name": "inD3",
206
+ "type": "u32",
207
+ "value": "dim(shapes.input, ranks.input - 5) if ranks.input >= 5 else 1"
208
+ },
209
+ {
210
+ "name": "inD4",
211
+ "type": "u32",
212
+ "value": "dim(shapes.input, ranks.input - 4) if ranks.input >= 4 else 1"
213
+ },
214
+ {
215
+ "name": "inD5",
216
+ "type": "u32",
217
+ "value": "dim(shapes.input, ranks.input - 3) if ranks.input >= 3 else 1"
218
+ },
219
+ {
220
+ "name": "inD6",
221
+ "type": "u32",
222
+ "value": "dim(shapes.input, ranks.input - 2) if ranks.input >= 2 else 1"
223
+ }
224
+ ]
225
  }
226
+ ],
227
+ "dispatch": { "x": "min(fftAnyAxisTransforms, 65535)", "y": "ceilDiv(fftAnyAxisTransforms, 65535)", "z": 1 }
228
+ },
229
+ {
230
+ "id": "other_axis_general",
231
+ "name": "DFT.RuntimeAxisOtherGeneral",
232
+ "shader": "dft-general.wgsl.jinja",
233
+ "derive": {
234
+ "hasAxisInput": true,
235
+ "hasDftLengthInput": false,
236
+ "skipRuntimeAxis1": false,
237
+ "skipSharedFftAxes": true,
238
+ "sharedFftMinLength": "tunables.RUNTIME_AXIS_FFT_MIN_LENGTH",
239
+ "sharedFftMaxLength": "fftAnyAxisMaxLen"
240
+ },
241
+ "bindings": ["input", "output", "axis_in", "params_2"],
242
+ "dispatch": {
243
+ "x": "min(ceilDiv((numel(shapes.output) / dim(shapes.output, ranks.output - 1)), (tunables.WORKGROUP_SIZE)), 65535)",
244
+ "y": "ceilDiv(ceilDiv((numel(shapes.output) / dim(shapes.output, ranks.output - 1)), (tunables.WORKGROUP_SIZE)), 65535)",
245
+ "z": 1
246
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
247
  }
248
+ ]
249
+ },
 
 
250
  {
251
  "id": "batch_runtime_axis1_fft",
252
  "priority": 30,
 
255
  {
256
  "id": "axis1_fft",
257
  "name": "DFT.BatchRuntimeAxis1FftShared",
258
+ "shader": "fft-stockham-shared.wgsl.jinja",
259
+ "derive": {
260
+ "modeSpec": "\"dft\"",
261
+ "fftLength": "dim(shapes.input, 1)",
262
+ "numBins": "dim(shapes.output, 1)",
263
+ "inComp": "dim(shapes.input, 2)",
264
+ "inverse": "attrs.inverse != 0",
265
+ "runtimeAxis": true,
266
+ "axisRank": 3,
267
+ "expectedAxis": 1
 
 
268
  },
269
+ "bindings": ["input", "axis_in", "output", "params"],
270
  "dispatch": { "x": "dim(shapes.input, 0)" }
271
  },
272
  {
273
  "id": "other_axis_general",
274
  "name": "DFT.BatchRuntimeOtherAxisGeneral",
275
+ "shader": "dft-general.wgsl.jinja",
276
+ "derive": { "hasAxisInput": true, "hasDftLengthInput": false, "skipRuntimeAxis1": true },
277
+ "bindings": ["input", "output", "axis_in", "params_2"],
 
 
278
  "dispatch": {
279
+ "x": "min(ceilDiv((numel(shapes.output) / dim(shapes.output, ranks.output - 1)), (tunables.WORKGROUP_SIZE)), 65535)",
280
+ "y": "ceilDiv(ceilDiv((numel(shapes.output) / dim(shapes.output, ranks.output - 1)), (tunables.WORKGROUP_SIZE)), 65535)",
281
+ "z": 1
282
  }
283
  }
284
  ]
 
294
  {
295
  "id": "main",
296
  "name": "DFT.BatchAxis1FftStorageOnesided",
297
+ "shader": "fft-radix2-dit-storage.wgsl.jinja",
298
+ "derive": {
299
+ "modeSpec": "\"dft_scratch\"",
300
+ "fftLength": "dim(shapes.input, 1)",
301
+ "log2Length": "rank3Axis1FftLog2",
302
+ "numBins": "dim(shapes.output, 1)",
303
+ "inComp": 1,
304
+ "inverse": false
 
 
305
  },
306
+ "bindings": [
307
+ "input",
308
+ { "scratch": "fft_scratch", "name": "scratch", "elementType": "f32" },
309
+ "output",
310
+ "params"
311
+ ],
312
  "dispatch": { "x": "dim(shapes.input, 0)" }
313
  }
314
  ]
 
321
  {
322
  "id": "main",
323
  "name": "DFT.BatchAxis1FftStorageInplace",
324
+ "shader": "fft-radix2-dit-storage.wgsl.jinja",
325
+ "derive": {
326
+ "modeSpec": "\"dft\"",
327
+ "fftLength": "dim(shapes.input, 1)",
328
+ "log2Length": "rank3Axis1FftLog2",
329
+ "numBins": "dim(shapes.output, 1)",
330
+ "inComp": "dim(shapes.input, 2)",
331
+ "inverse": "attrs.inverse != 0"
 
 
332
  },
333
+ "bindings": ["input", "output", "params"],
334
  "dispatch": { "x": "dim(shapes.input, 0)" }
335
  }
336
  ]
 
338
  {
339
  "id": "batch_axis1_tiled_real_nonpow2",
340
  "priority": 29,
341
+ "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) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535)"],
342
  "requires": { "limits": { "maxComputeInvocationsPerWorkgroup": 256, "maxComputeWorkgroupSizeX": 256 } },
 
343
  "passes": [
344
  {
345
  "id": "main",
346
  "name": "DFT.BatchAxis1TiledRealNonPowerOfTwo",
347
+ "shader": "dft-tiled-real.wgsl.jinja",
348
+ "derive": {
349
+ "modeSpec": "\"dft\"",
350
+ "transformLength": "dim(shapes.input, 1)",
351
+ "numBins": "dim(shapes.output, 1)",
352
+ "workgroupSizeSpec": "tunables.TILED_REAL_WORKGROUP_SIZE"
 
 
353
  },
354
+ "bindings": ["input", "output", "params"],
355
  "dispatch": {
356
  "x": "ceilDiv(dim(shapes.output, 1), tunables.TILED_REAL_WORKGROUP_SIZE)",
357
  "y": "dim(shapes.input, 0)"
 
362
  {
363
  "id": "axis_fft_shared",
364
  "priority": 30,
365
+ "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) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535)", "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))"],
366
  "passes": [
367
  {
368
  "id": "main",
369
  "name": "DFT.AxisFftShared",
370
+ "shader": "fft-stockham-shared.wgsl.jinja",
371
+ "derive": {
372
+ "modeSpec": "\"dft\"",
373
+ "fftLength": "dim(shapes.input, dftAxisAttr)",
374
+ "numBins": "dim(shapes.output, dftAxisAttr)",
375
+ "inComp": "dim(shapes.input, ranks.input - 1)",
376
+ "inverse": "attrs.inverse != 0"
 
 
377
  },
378
+ "bindings": [
379
+ "input",
380
+ "output",
381
+ {
382
+ "name": "params",
383
+ "struct": [
384
+ {
385
+ "name": "inner",
386
+ "type": "u32",
387
+ "value": "inner(shapes.input, dftAxisAttr) / dim(shapes.input, ranks.input - 1)"
388
+ }
389
+ ]
390
+ }
391
+ ],
392
  "dispatch": {
393
  "x": "outer(shapes.input, dftAxisAttr) * inner(shapes.input, dftAxisAttr) / dim(shapes.input, ranks.input - 1)"
394
  }
 
403
  {
404
  "id": "main",
405
  "name": "DFT.BatchAxis1FftSharedExplicitLength",
406
+ "shader": "fft-stockham-shared.wgsl.jinja",
407
+ "derive": {
408
+ "modeSpec": "\"dft\"",
409
+ "fftLength": "dim(shapes.input, 1)",
410
+ "numBins": "dim(shapes.output, 1)",
411
+ "inComp": "dim(shapes.input, 2)",
412
+ "inverse": "attrs.inverse != 0"
 
 
413
  },
414
+ "bindings": ["input", "output", "params"],
415
  "dispatch": { "x": "dim(shapes.input, 0)" }
416
  }
417
  ]
 
424
  {
425
  "id": "main",
426
  "name": "DFT.Rank2Axis0",
427
+ "shader": "dft-contiguous-naive.wgsl.jinja",
428
+ "derive": { "batched": false },
429
+ "bindings": [
430
+ "input",
431
+ "output",
432
+ {
433
+ "name": "params",
434
+ "struct": [
435
+ { "name": "count", "type": "u32", "value": "numel(shapes.output) / dim(shapes.output, 1)" },
436
+ { "name": "signalLength", "type": "u32", "value": "dim(shapes.input, 0)" },
437
+ { "name": "inputComponents", "type": "u32", "value": "dim(shapes.input, 1)" },
438
+ { "name": "outputComponents", "type": "u32", "value": "dim(shapes.output, 1)" },
439
+ { "name": "outputLength", "type": "u32", "value": "dim(shapes.output, 0)" },
440
+ { "name": "inverse", "type": "u32", "value": "attrs.inverse" },
441
+ { "name": "onesided", "type": "u32", "value": "attrs.onesided" }
442
+ ]
443
+ }
444
+ ],
445
  "dispatch": {
446
+ "x": "min(ceilDiv((numel(shapes.output) / dim(shapes.output, 1)), (tunables.WORKGROUP_SIZE)), 65535)",
447
+ "y": "ceilDiv(ceilDiv((numel(shapes.output) / dim(shapes.output, 1)), (tunables.WORKGROUP_SIZE)), 65535)",
448
+ "z": 1
449
  }
450
  }
451
  ]
 
458
  {
459
  "id": "main",
460
  "name": "DFT",
461
+ "shader": "dft-contiguous-naive.wgsl.jinja",
462
+ "derive": { "batched": true },
463
+ "bindings": [
464
+ "input",
465
+ "output",
466
+ {
467
+ "name": "params",
468
+ "struct": [
469
+ { "name": "count", "type": "u32", "value": "numel(shapes.output) / dim(shapes.output, 2)" },
470
+ { "name": "signalLength", "type": "u32", "value": "dim(shapes.input, 1)" },
471
+ { "name": "inputComponents", "type": "u32", "value": "dim(shapes.input, 2)" },
472
+ { "name": "outputComponents", "type": "u32", "value": "dim(shapes.output, 2)" },
473
+ { "name": "outputLength", "type": "u32", "value": "dim(shapes.output, 1)" },
474
+ { "name": "inverse", "type": "u32", "value": "attrs.inverse" },
475
+ { "name": "onesided", "type": "u32", "value": "attrs.onesided" }
476
+ ]
477
+ }
478
+ ],
479
  "dispatch": {
480
+ "x": "min(ceilDiv((numel(shapes.output) / dim(shapes.output, 2)), (tunables.WORKGROUP_SIZE)), 65535)",
481
+ "y": "ceilDiv(ceilDiv((numel(shapes.output) / dim(shapes.output, 2)), (tunables.WORKGROUP_SIZE)), 65535)",
482
+ "z": 1
483
  }
484
  }
485
  ]
 
493
  "id": "main",
494
  "name": "DFT.Rank4Axis2",
495
  "shader": "dft-rank4.wgsl.jinja",
496
+ "bindings": [
497
+ "input",
498
+ "output",
499
+ {
500
+ "name": "params",
501
+ "struct": [
502
+ { "name": "count", "type": "u32", "value": "numel(shapes.output) / dim(shapes.output, 3)" },
503
+ { "name": "inputD1", "type": "u32", "value": "dim(shapes.input, 1)" },
504
+ { "name": "inputD2", "type": "u32", "value": "dim(shapes.input, 2)" },
505
+ { "name": "outputD1", "type": "u32", "value": "dim(shapes.output, 1)" },
506
+ { "name": "outputD2", "type": "u32", "value": "dim(shapes.output, 2)" },
507
+ { "name": "inputComponents", "type": "u32", "value": "dim(shapes.input, 3)" },
508
+ { "name": "outputComponents", "type": "u32", "value": "dim(shapes.output, 3)" },
509
+ { "name": "inverse", "type": "u32", "value": "attrs.inverse" },
510
+ { "name": "onesided", "type": "u32", "value": "attrs.onesided" }
511
+ ]
512
+ }
513
+ ],
514
  "dispatch": {
515
+ "x": "min(ceilDiv((numel(shapes.output) / dim(shapes.output, 3)), (tunables.WORKGROUP_SIZE)), 65535)",
516
+ "y": "ceilDiv(ceilDiv((numel(shapes.output) / dim(shapes.output, 3)), (tunables.WORKGROUP_SIZE)), 65535)",
517
+ "z": 1
518
  }
519
  }
520
  ]
 
527
  {
528
  "id": "main",
529
  "name": "DFT.General",
530
+ "shader": "dft-general.wgsl.jinja",
531
+ "derive": { "hasAxisInput": false, "hasDftLengthInput": false, "skipRuntimeAxis1": false },
532
+ "bindings": ["input", "output", "params_8"],
 
 
533
  "dispatch": {
534
+ "x": "min(ceilDiv((numel(shapes.output) / dim(shapes.output, ranks.output - 1)), (tunables.WORKGROUP_SIZE)), 65535)",
535
+ "y": "ceilDiv(ceilDiv((numel(shapes.output) / dim(shapes.output, ranks.output - 1)), (tunables.WORKGROUP_SIZE)), 65535)",
536
+ "z": 1
537
  }
538
  }
539
  ]
 
546
  {
547
  "id": "main",
548
  "name": "DFT.General",
549
+ "shader": "dft-general.wgsl.jinja",
550
+ "derive": { "hasAxisInput": true, "hasDftLengthInput": true, "skipRuntimeAxis1": false },
551
+ "bindings": ["input", "output", "dft_len_in", "axis_in", "params_9"],
 
 
552
  "dispatch": {
553
+ "x": "min(ceilDiv((numel(shapes.output) / dim(shapes.output, ranks.output - 1)), (tunables.WORKGROUP_SIZE)), 65535)",
554
+ "y": "ceilDiv(ceilDiv((numel(shapes.output) / dim(shapes.output, ranks.output - 1)), (tunables.WORKGROUP_SIZE)), 65535)",
555
+ "z": 1
556
  }
557
  }
558
  ]
 
565
  {
566
  "id": "main",
567
  "name": "DFT.General",
568
+ "shader": "dft-general.wgsl.jinja",
569
+ "derive": { "hasAxisInput": true, "hasDftLengthInput": false, "skipRuntimeAxis1": false },
570
+ "bindings": ["input", "output", "axis_in", "params_2"],
 
 
571
  "dispatch": {
572
+ "x": "min(ceilDiv((numel(shapes.output) / dim(shapes.output, ranks.output - 1)), (tunables.WORKGROUP_SIZE)), 65535)",
573
+ "y": "ceilDiv(ceilDiv((numel(shapes.output) / dim(shapes.output, ranks.output - 1)), (tunables.WORKGROUP_SIZE)), 65535)",
574
+ "z": 1
575
  }
576
  }
577
  ]
 
584
  {
585
  "id": "main",
586
  "name": "DFT.General",
587
+ "shader": "dft-general.wgsl.jinja",
588
+ "derive": { "hasAxisInput": false, "hasDftLengthInput": true, "skipRuntimeAxis1": false },
589
+ "bindings": ["input", "output", "dft_len_in", "params_10"],
 
 
590
  "dispatch": {
591
+ "x": "min(ceilDiv((numel(shapes.output) / dim(shapes.output, ranks.output - 1)), (tunables.WORKGROUP_SIZE)), 65535)",
592
+ "y": "ceilDiv(ceilDiv((numel(shapes.output) / dim(shapes.output, ranks.output - 1)), (tunables.WORKGROUP_SIZE)), 65535)",
593
+ "z": 1
594
  }
595
  }
596
  ]
build/webgpu/metadata.json CHANGED
@@ -1,23 +1,42 @@
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
  }
 
1
  {
2
  "name": "ai.onnx.DFT",
3
+ "id": "_ai_onnx_dft_webgpu_568fc49",
4
  "version": 1,
5
  "license": "Apache-2.0",
6
  "backend": { "type": "webgpu" },
7
  "digest": {
8
  "algorithm": "sha256",
9
  "files": {
10
+ "bench.json": "9LPWvdkDFFGTQxPP69GFp4Z0lS287DSsZ2wxuGciXgY=",
11
+ "dft-contiguous-naive.wgsl.jinja": "K15oJXeOCAbiVr0x6U2+1TPOyj0PF+XNoFySxMFA4Do=",
12
+ "dft-general.wgsl.jinja": "pYttCZq/3h30wY4Vc6w4dEJJbEGvkbiHZqRxtXHLP7E=",
13
+ "dft-rank4.wgsl.jinja": "d4PWfZuv7gtxBLxVu7Wm7sH/MOsS7ZnoKpV1vcHqu1A=",
14
+ "dft-runtime-axis-fft-shared.wgsl.jinja": "6umWYtDkA3ebbfshjFFhmOVuWsygSpHu7vxGcdLd1Yc=",
15
+ "dft-tiled-real.wgsl.jinja": "mGmAT2TYrf1PWr+Y5Ebb33AMc/WT6Xr1/7icjk2feo0=",
16
+ "fft-radix2-dit-storage.wgsl.jinja": "oNrqAz6ZO92qD2wadfbTW006CaScjjvw3y6Hxp1+ydI=",
17
+ "fft-stockham-shared.wgsl.jinja": "J/1t+Z+A7lI2kgu0Om0U8+JNhrIQbCZOj2J9Fcdxd78=",
18
+ "manifest.json": "okwMs167d8CX1HETnuai4MaDVz17Sq8v1sKLmYwthOw=",
19
+ "test.json": "6+aX7hB91x77ThRU2xfezaBt56pn5UKH10Lano/9zag="
20
  }
21
  },
22
+ "provenance": { "kernel": { "sha": "91d990483a174128daf7673f3f37a7c890493ae1", "dirty": false } },
23
+ "webgpu": {
24
+ "manifestSpec": "2.0",
25
+ "variants": {
26
+ "runtime_axis_shared_fft": ["dft-general.wgsl.jinja", "dft-runtime-axis-fft-shared.wgsl.jinja"],
27
+ "batch_runtime_axis1_fft": ["dft-general.wgsl.jinja", "fft-stockham-shared.wgsl.jinja"],
28
+ "batch_axis1_fft_storage_onesided": ["fft-radix2-dit-storage.wgsl.jinja"],
29
+ "batch_axis1_fft_storage_inplace": ["fft-radix2-dit-storage.wgsl.jinja"],
30
+ "batch_axis1_tiled_real_nonpow2": ["dft-tiled-real.wgsl.jinja"],
31
+ "axis_fft_shared": ["fft-stockham-shared.wgsl.jinja"],
32
+ "batch_axis1_fft_explicit_equal_length": ["fft-stockham-shared.wgsl.jinja"],
33
+ "rank2_axis0": ["dft-contiguous-naive.wgsl.jinja"],
34
+ "batch_axis1": ["dft-contiguous-naive.wgsl.jinja"],
35
+ "rank4_axis2": ["dft-rank4.wgsl.jinja"],
36
+ "general_attr": ["dft-general.wgsl.jinja"],
37
+ "general_axis_len": ["dft-general.wgsl.jinja"],
38
+ "general_axis": ["dft-general.wgsl.jinja"],
39
+ "general_len": ["dft-general.wgsl.jinja"]
40
+ }
41
+ }
42
  }
build/webgpu/test.json CHANGED
@@ -1,5 +1,4 @@
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
  },
@@ -109,7 +108,7 @@
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": {
@@ -130,7 +129,7 @@
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": {
@@ -151,7 +150,7 @@
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": {
@@ -168,7 +167,7 @@
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": {
@@ -190,7 +189,7 @@
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": {
@@ -223,7 +222,7 @@
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": {
@@ -330,7 +329,7 @@
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] } },
@@ -1181,7 +1180,7 @@
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": {
@@ -1219,7 +1218,7 @@
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": {
@@ -1248,7 +1247,7 @@
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": {
@@ -1307,7 +1306,7 @@
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": {
@@ -1337,7 +1336,7 @@
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": {
@@ -1369,6 +1368,109 @@
1369
  "data": { "kind": "values", "values": [3.0, 0.0, -1.0, 0.0] }
1370
  }
1371
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1372
  }
1373
  ]
1374
  }
 
1
  {
 
2
  "fixtureArrays": {
3
  "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]
4
  },
 
108
  "provenance": {
109
  "source": "onnxruntime/test/providers/cpu/signal/signal_ops_test.cc",
110
  "test": "SignalOpsTest.DFT17_Float_radix2",
111
+ "notes": "A subnormal complex impulse at sample zero must appear unchanged in every frequency bin."
112
  },
113
  "attrs": { "inverse": 0, "onesided": 0 },
114
  "inputs": {
 
129
  "provenance": {
130
  "source": "onnxruntime/test/providers/cpu/signal/signal_ops_test.cc",
131
  "test": "SignalOpsTest.DFT17_Float_inverse",
132
+ "notes": "An inverse transform of a subnormal DC bin, scaled by 1/N, must produce a finite subnormal constant signal."
133
  },
134
  "attrs": { "inverse": 1, "onesided": 0 },
135
  "inputs": {
 
150
  "provenance": {
151
  "source": "onnxruntime/test/providers/cpu/signal/signal_ops_test.cc",
152
  "test": "SignalOpsTest.DFT17_Float_radix2",
153
+ "notes": "A rank-2 axis-0 FFT of a real subnormal impulse must preserve that real value in every bin."
154
  },
155
  "attrs": { "inverse": 0, "onesided": 0 },
156
  "inputs": {
 
167
  "provenance": {
168
  "source": "onnxruntime/test/providers/cpu/signal/signal_ops_test.cc",
169
  "test": "SignalOpsTest.DFT17_Float_radix2",
170
+ "notes": "A rank-4 axis-1 FFT places a subnormal impulse along the first signal axis with one inner position."
171
  },
172
  "attrs": { "inverse": 0, "onesided": 0 },
173
  "inputs": {
 
189
  "provenance": {
190
  "source": "onnxruntime/test/providers/cpu/signal/signal_ops_test.cc",
191
  "test": "SignalOpsTest.DFT17_Float_radix2",
192
+ "notes": "A rank-4 axis-2 FFT places a subnormal impulse along the second signal axis with one outer channel."
193
  },
194
  "attrs": { "inverse": 0, "onesided": 0 },
195
  "inputs": {
 
222
  "provenance": {
223
  "source": "onnxruntime/test/providers/cpu/signal/signal_ops_test.cc",
224
  "test": "SignalOpsTest.DFT20_invertible_real",
225
+ "notes": "In opset 20 the axis is an input tensor; axis=0 must override the operator's default axis on this shape."
226
  },
227
  "inputs": {
228
  "input": {
 
329
  {
330
  "name": "fft_batch_axis1_len256_batch4_real_onesided_perf_compact",
331
  "provenance": {
332
+ "notes": "A compact real-input FFT along batch axis 1 uses a power-of-two length and one-sided output without benchmark-scale tensors."
333
  },
334
  "attrs": { "onesided": 1 },
335
  "inputs": { "input": { "dtype": "float32", "shape": [4, 256, 1] } },
 
1180
  {
1181
  "name": "general_len_rank4_axis1_dftlength_zeropad_len6",
1182
  "provenance": {
1183
+ "notes": "dft_length=6 zero-pads a rank-4 axis-1 signal from four to six samples, exercising rank-4 coordinate decoding and the zero-pad boundary."
1184
  },
1185
  "attrs": { "inverse": 0, "onesided": 0 },
1186
  "inputs": {
 
1218
  {
1219
  "name": "general_axis_len_negative_axis_rank3_dftlength_truncate",
1220
  "provenance": {
1221
+ "notes": "For rank 3, axis=-2 resolves to axis 1; dft_length=4 then truncates the six-sample signal on the general runtime-axis path."
1222
  },
1223
  "attrs": { "inverse": 0, "onesided": 0 },
1224
  "inputs": {
 
1247
  {
1248
  "name": "fft_explicit_equal_length128_batch4_onesided",
1249
  "provenance": {
1250
+ "notes": "An explicit dft_length equal to the 128-sample signal length exercises the optimized power-of-two path without padding or truncation."
1251
  },
1252
  "attrs": { "inverse": 0, "onesided": 1 },
1253
  "inputs": {
 
1306
  {
1307
  "name": "tiled_real_nonpow2_len4097_onesided_above_old_boundary",
1308
  "provenance": {
1309
+ "notes": "A 4,097-sample non-power-of-two transform exercises tiled direct DFT at the workgroup-storage eligibility boundary."
1310
  },
1311
  "attrs": { "onesided": 1 },
1312
  "inputs": {
 
1336
  {
1337
  "name": "rank6_axis2_batch_and_inner",
1338
  "provenance": {
1339
+ "notes": "A rank-6 input with batch dimensions on both sides of the transform axis exercises coordinate decoding with outer size 4 and inner size 2."
1340
  },
1341
  "attrs": { "inverse": 0, "onesided": 0 },
1342
  "inputs": {
 
1368
  "data": { "kind": "values", "values": [3.0, 0.0, -1.0, 0.0] }
1369
  }
1370
  }
1371
+ },
1372
+ {
1373
+ "name": "runtime_axis_fft_rank4_axis1_real_len64_inner3",
1374
+ "attrs": {},
1375
+ "inputs": {
1376
+ "axis": { "dtype": "int32", "shape": [], "data": { "kind": "values", "values": [1] } },
1377
+ "input": {
1378
+ "dtype": "float32",
1379
+ "shape": [2, 64, 3, 1],
1380
+ "data": { "kind": "fillFloat32", "sinStep": 0.027, "cosStep": 0.033, "scale": 1.0 }
1381
+ }
1382
+ },
1383
+ "outputs": {
1384
+ "output": { "dtype": "float32", "shape": [2, 64, 3, 2], "tolerance": 0.005, "relTolerance": 0.0001 }
1385
+ }
1386
+ },
1387
+ {
1388
+ "name": "runtime_axis_fft_rank4_axis0_complex_inverse_len128",
1389
+ "attrs": { "inverse": 1 },
1390
+ "inputs": {
1391
+ "axis": { "dtype": "int32", "shape": [], "data": { "kind": "values", "values": [0] } },
1392
+ "input": {
1393
+ "dtype": "float32",
1394
+ "shape": [128, 2, 3, 2],
1395
+ "data": { "kind": "fillFloat32", "sinStep": 0.07, "cosStep": 0.17, "scale": 3.0 }
1396
+ }
1397
+ },
1398
+ "outputs": {
1399
+ "output": { "dtype": "float32", "shape": [128, 2, 3, 2], "tolerance": 0.005, "relTolerance": 0.0001 }
1400
+ }
1401
+ },
1402
+ {
1403
+ "name": "runtime_axis_fft_rank2_axis0_real_len256",
1404
+ "attrs": {},
1405
+ "inputs": {
1406
+ "axis": { "dtype": "int32", "shape": [], "data": { "kind": "values", "values": [0] } },
1407
+ "input": {
1408
+ "dtype": "float32",
1409
+ "shape": [256, 1],
1410
+ "data": { "kind": "fillFloat32", "sinStep": 0.019, "cosStep": 0.041, "scale": 1.0 }
1411
+ }
1412
+ },
1413
+ "outputs": { "output": { "dtype": "float32", "shape": [256, 2], "tolerance": 0.01, "relTolerance": 0.0001 } }
1414
+ },
1415
+ {
1416
+ "name": "runtime_axis_fft_rank5_axis2_len64_batch_and_inner",
1417
+ "attrs": {},
1418
+ "inputs": {
1419
+ "axis": { "dtype": "int32", "shape": [], "data": { "kind": "values", "values": [2] } },
1420
+ "input": {
1421
+ "dtype": "float32",
1422
+ "shape": [2, 2, 64, 3, 1],
1423
+ "data": { "kind": "fillFloat32", "sinStep": 0.031, "cosStep": 0.023, "scale": 1.0 }
1424
+ }
1425
+ },
1426
+ "outputs": {
1427
+ "output": { "dtype": "float32", "shape": [2, 2, 64, 3, 2], "tolerance": 0.005, "relTolerance": 0.0001 }
1428
+ }
1429
+ },
1430
+ {
1431
+ "name": "runtime_axis_general_len10_beside_fft_candidate_axis0",
1432
+ "attrs": {},
1433
+ "inputs": {
1434
+ "axis": { "dtype": "int32", "shape": [], "data": { "kind": "values", "values": [1] } },
1435
+ "input": {
1436
+ "dtype": "float32",
1437
+ "shape": [64, 10, 2, 1],
1438
+ "data": { "kind": "fillFloat32", "sinStep": 0.05, "cosStep": 0.02, "scale": 1.0 }
1439
+ }
1440
+ },
1441
+ "outputs": {
1442
+ "output": { "dtype": "float32", "shape": [64, 10, 2, 2], "tolerance": 0.002, "relTolerance": 0.0001 }
1443
+ }
1444
+ },
1445
+ {
1446
+ "name": "runtime_axis_fft_shorter_candidate_axis2_len64_beside_len1024",
1447
+ "attrs": {},
1448
+ "inputs": {
1449
+ "axis": { "dtype": "int32", "shape": [], "data": { "kind": "values", "values": [2] } },
1450
+ "input": {
1451
+ "dtype": "float32",
1452
+ "shape": [2, 1024, 64, 1],
1453
+ "data": { "kind": "fillFloat32", "sinStep": 0.011, "cosStep": 0.017, "scale": 1.0 }
1454
+ }
1455
+ },
1456
+ "outputs": {
1457
+ "output": { "dtype": "float32", "shape": [2, 1024, 64, 2], "tolerance": 0.005, "relTolerance": 0.0001 }
1458
+ }
1459
+ },
1460
+ {
1461
+ "name": "runtime_axis_fft_negative_axis_rank4_len256_complex",
1462
+ "attrs": {},
1463
+ "inputs": {
1464
+ "axis": { "dtype": "int32", "shape": [], "data": { "kind": "values", "values": [-3] } },
1465
+ "input": {
1466
+ "dtype": "float32",
1467
+ "shape": [3, 256, 2, 2],
1468
+ "data": { "kind": "fillFloat32", "sinStep": 0.013, "cosStep": 0.037, "scale": 2.0 }
1469
+ }
1470
+ },
1471
+ "outputs": {
1472
+ "output": { "dtype": "float32", "shape": [3, 256, 2, 2], "tolerance": 0.02, "relTolerance": 0.0001 }
1473
+ }
1474
  }
1475
  ]
1476
  }