sync 2e7068faf55e
Browse files- README.md +83 -0
- build/webgpu/bench.json +252 -0
- build/webgpu/dft-tiled-real.wgsl.jinja +140 -0
- build/webgpu/fft-radix2-dit-shared-inplace.wgsl.jinja +149 -0
- build/webgpu/fft-radix2-dit-storage-pass.wgsl.jinja +215 -0
- build/webgpu/fft-radix2-dit-storage.wgsl.jinja +84 -0
- build/webgpu/fft-real-packed-stockham-shared.wgsl.jinja +156 -0
- build/webgpu/fft-stockham-shared.wgsl.jinja +292 -0
- build/webgpu/fft-stockham-twiddles.wgsl.jinja +125 -0
- build/webgpu/manifest.json +644 -0
- build/webgpu/metadata.json +25 -0
- build/webgpu/stft-general.wgsl.jinja +164 -0
- build/webgpu/test.json +847 -0
README.md
CHANGED
|
@@ -1,3 +1,86 @@
|
|
| 1 |
---
|
|
|
|
| 2 |
license: apache-2.0
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
library_name: kernels
|
| 3 |
license: apache-2.0
|
| 4 |
+
tags:
|
| 5 |
+
- kernel
|
| 6 |
+
- webgpu
|
| 7 |
+
- wgsl
|
| 8 |
---
|
| 9 |
+
# ai.onnx.STFT
|
| 10 |
+
|
| 11 |
+
`ai.onnx` · standard ONNX operator · ONNX opset ≥ 17
|
| 12 |
+
|
| 13 |
+
## Description
|
| 14 |
+
|
| 15 |
+
Computes the short-time Fourier transform (STFT) of a rank-3 real or complex signal by sliding a complete frame over the input and applying the DFT. The `frameStep` scalar represents the required positive `frame_step` input. At least one of `window` or `frameLength` is required; when both are supplied, their lengths must match. The output contains the real and imaginary components of every frequency bin for each complete frame.
|
| 16 |
+
|
| 17 |
+
See the [ONNX `STFT` spec](https://onnx.ai/onnx/operators/onnx__STFT.html) for the reference semantics.
|
| 18 |
+
|
| 19 |
+
## Inputs
|
| 20 |
+
|
| 21 |
+
| Name | Bind key | Logical dtype | Rank | Shape | Description | Presence |
|
| 22 |
+
| --- | --- | --- | --- | --- | --- | --- |
|
| 23 |
+
| `signal` | `signal` | `T` | `3` | — | Real input signal with shape `(batch_size, signal_length, 1)`, or complex input with shape `(batch_size, signal_length, 2)`. | required |
|
| 24 |
+
| `window` | `window` | `T` | `1` | — | Optional 1-D window tensor with shape `(window_length)` to multiply by each frame before the DFT. | optional |
|
| 25 |
+
|
| 26 |
+
## Outputs
|
| 27 |
+
|
| 28 |
+
| Name | Bind key | Logical dtype | Rank | Shape | Description | Presence |
|
| 29 |
+
| --- | --- | --- | --- | --- | --- | --- |
|
| 30 |
+
| `output` | `output` | `T` | `4` | derived; see description | STFT result with shape `(batch_size, frames, dft_unique_bins, 2)` in one-sided mode or `(batch_size, frames, frame_length, 2)` in two-sided mode, with real and imaginary parts in the last dimension. `frames = floor((signal_length - frame_length) / frame_step) + 1`; partial trailing frames are not emitted. | required |
|
| 31 |
+
|
| 32 |
+
## Runtime arguments
|
| 33 |
+
|
| 34 |
+
| Name | Kind | Semantic | Description | Presence |
|
| 35 |
+
| --- | --- | --- | --- | --- |
|
| 36 |
+
| `frameStep` | `i32` | `frame_step` | Positive number of signal positions between the starts of consecutive frames. | required |
|
| 37 |
+
| `frameLength` | `i32` | `frame_length` | Optional number of signal values in each frame; must equal the window length when both are supplied. | optional |
|
| 38 |
+
|
| 39 |
+
## Attributes
|
| 40 |
+
|
| 41 |
+
Default values (overridable per request):
|
| 42 |
+
|
| 43 |
+
| Attribute | Default | Description |
|
| 44 |
+
| --- | --- | --- |
|
| 45 |
+
| `onesided` | `1` | When 1 (default), returns `floor(n_fft / 2) + 1` non-redundant frequency bins with indices `[0, ..., floor(n_fft / 2)]` for a real-valued input (RFFT); set to 0 to return the full spectrum. |
|
| 46 |
+
|
| 47 |
+
## Type constraints
|
| 48 |
+
|
| 49 |
+
| Variable | Allowed dtypes |
|
| 50 |
+
| --- | --- |
|
| 51 |
+
| `T` | `float32` |
|
| 52 |
+
|
| 53 |
+
## Files
|
| 54 |
+
|
| 55 |
+
- [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, provenance)
|
| 56 |
+
- [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
|
| 57 |
+
- [`test.json`](build/webgpu/test.json) — correctness cases
|
| 58 |
+
- [`bench.json`](build/webgpu/bench.json) — benchmark + tuning cases
|
| 59 |
+
- [`dft-tiled-real.wgsl.jinja`](build/webgpu/dft-tiled-real.wgsl.jinja)
|
| 60 |
+
- [`fft-radix2-dit-shared-inplace.wgsl.jinja`](build/webgpu/fft-radix2-dit-shared-inplace.wgsl.jinja)
|
| 61 |
+
- [`fft-radix2-dit-storage-pass.wgsl.jinja`](build/webgpu/fft-radix2-dit-storage-pass.wgsl.jinja)
|
| 62 |
+
- [`fft-radix2-dit-storage.wgsl.jinja`](build/webgpu/fft-radix2-dit-storage.wgsl.jinja)
|
| 63 |
+
- [`fft-real-packed-stockham-shared.wgsl.jinja`](build/webgpu/fft-real-packed-stockham-shared.wgsl.jinja)
|
| 64 |
+
- [`fft-stockham-shared.wgsl.jinja`](build/webgpu/fft-stockham-shared.wgsl.jinja)
|
| 65 |
+
- [`fft-stockham-twiddles.wgsl.jinja`](build/webgpu/fft-stockham-twiddles.wgsl.jinja)
|
| 66 |
+
- [`stft-general.wgsl.jinja`](build/webgpu/stft-general.wgsl.jinja)
|
| 67 |
+
|
| 68 |
+
## Use with `@huggingface/kernels`
|
| 69 |
+
|
| 70 |
+
The loader derives every required output's shape and logical dtype from the manifest contract and this call.
|
| 71 |
+
It then allocates the result tensors automatically.
|
| 72 |
+
|
| 73 |
+
The `version: 1` option selects the published kernel contract; it is independent of any operator opset, contrib `since_version`, or model version.
|
| 74 |
+
|
| 75 |
+
Replace each `*Data` placeholder with a typed array containing the corresponding input data.
|
| 76 |
+
|
| 77 |
+
```js
|
| 78 |
+
import { getKernel } from "@huggingface/kernels";
|
| 79 |
+
|
| 80 |
+
const kernel = await getKernel("webgpu-kernels/ai.onnx.STFT", { version: 1 });
|
| 81 |
+
const { output } = await kernel({
|
| 82 |
+
signal: { data: signalData, shape: [1, 4, 1] },
|
| 83 |
+
window: { data: windowData, shape: [4] },
|
| 84 |
+
frameStep: 4,
|
| 85 |
+
});
|
| 86 |
+
```
|
build/webgpu/bench.json
ADDED
|
@@ -0,0 +1,252 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"op": "ai.onnx.STFT",
|
| 3 |
+
"tunableSpace": { "WORKGROUP_SIZE": [64, 128, 256] },
|
| 4 |
+
"cases": [
|
| 5 |
+
{
|
| 6 |
+
"name": "batch4_signal1024_frame256_step128",
|
| 7 |
+
"inputs": {
|
| 8 |
+
"signal": { "dtype": "float32", "shape": [4, 1024, 1] },
|
| 9 |
+
"window": { "dtype": "float32", "shape": [256] }
|
| 10 |
+
},
|
| 11 |
+
"outputs": { "output": { "dtype": "float32", "shape": [4, 7, 129, 2] } },
|
| 12 |
+
"args": { "frameStep": 128 }
|
| 13 |
+
},
|
| 14 |
+
{
|
| 15 |
+
"name": "fft_frame256_signal2048_step128_batch4",
|
| 16 |
+
"preset": "smoke",
|
| 17 |
+
"vars": { "frames": 60, "n": 256, "log2n": 8 },
|
| 18 |
+
"inputs": {
|
| 19 |
+
"signal": { "dtype": "float32", "shape": [4, 2048, 1], "dist": "normal", "seed": 911 },
|
| 20 |
+
"window": { "dtype": "float32", "shape": [256], "dist": "uniform", "seed": 912 }
|
| 21 |
+
},
|
| 22 |
+
"outputs": { "output": { "dtype": "float32", "shape": [4, 15, 129, 2] } },
|
| 23 |
+
"bench": { "metrics": [{ "type": "gflops", "value": "5 * args.frames * args.n * args.log2n" }] },
|
| 24 |
+
"args": { "frameStep": 128 }
|
| 25 |
+
},
|
| 26 |
+
{
|
| 27 |
+
"name": "fft_frame512_signal4096_step256_batch2",
|
| 28 |
+
"preset": "smoke",
|
| 29 |
+
"vars": { "frames": 30, "n": 512, "log2n": 9 },
|
| 30 |
+
"inputs": {
|
| 31 |
+
"signal": { "dtype": "float32", "shape": [2, 4096, 1], "dist": "normal", "seed": 913 },
|
| 32 |
+
"window": { "dtype": "float32", "shape": [512], "dist": "uniform", "seed": 914 }
|
| 33 |
+
},
|
| 34 |
+
"outputs": { "output": { "dtype": "float32", "shape": [2, 15, 257, 2] } },
|
| 35 |
+
"bench": { "metrics": [{ "type": "gflops", "value": "5 * args.frames * args.n * args.log2n" }] },
|
| 36 |
+
"args": { "frameStep": 256 }
|
| 37 |
+
},
|
| 38 |
+
{
|
| 39 |
+
"name": "fft_frame1024_signal131072_step256_audio",
|
| 40 |
+
"preset": "smoke",
|
| 41 |
+
"vars": { "frames": 509, "n": 1024, "log2n": 10 },
|
| 42 |
+
"inputs": {
|
| 43 |
+
"signal": { "dtype": "float32", "shape": [1, 131072, 1], "dist": "normal", "seed": 915 },
|
| 44 |
+
"window": { "dtype": "float32", "shape": [1024], "dist": "uniform", "seed": 916 }
|
| 45 |
+
},
|
| 46 |
+
"outputs": { "output": { "dtype": "float32", "shape": [1, 509, 513, 2] } },
|
| 47 |
+
"bench": { "primary": true, "metrics": [{ "type": "gflops", "value": "5 * args.frames * args.n * args.log2n" }] },
|
| 48 |
+
"args": { "frameStep": 256 }
|
| 49 |
+
},
|
| 50 |
+
{
|
| 51 |
+
"name": "pow2_gate_FFT_win512_signal8192_step160",
|
| 52 |
+
"preset": "smoke",
|
| 53 |
+
"vars": { "batch": 2, "frames": 49, "bins": 257, "n": 512, "log2n": 9 },
|
| 54 |
+
"inputs": {
|
| 55 |
+
"signal": { "dtype": "float32", "shape": [2, 8192, 1], "dist": "normal", "seed": 2001 },
|
| 56 |
+
"window": { "dtype": "float32", "shape": [512], "dist": "uniform", "seed": 2002 }
|
| 57 |
+
},
|
| 58 |
+
"outputs": { "output": { "dtype": "float32", "shape": [2, 49, 257, 2] } },
|
| 59 |
+
"bench": { "metrics": [{ "type": "gflops", "value": "5 * args.batch * args.frames * args.n * args.log2n" }] },
|
| 60 |
+
"args": { "frameStep": 160 }
|
| 61 |
+
},
|
| 62 |
+
{
|
| 63 |
+
"name": "mixed_radix_win480_signal8192_step160_tail15",
|
| 64 |
+
"preset": "smoke",
|
| 65 |
+
"vars": { "batch": 2, "frames": 49, "bins": 241, "n": 480, "log2radix2": 5, "tail": 15 },
|
| 66 |
+
"inputs": {
|
| 67 |
+
"signal": { "dtype": "float32", "shape": [2, 8192, 1], "dist": "normal", "seed": 2001 },
|
| 68 |
+
"window": { "dtype": "float32", "shape": [480], "dist": "uniform", "seed": 2003 }
|
| 69 |
+
},
|
| 70 |
+
"outputs": { "output": { "dtype": "float32", "shape": [2, 49, 241, 2] } },
|
| 71 |
+
"bench": {
|
| 72 |
+
"metrics": [
|
| 73 |
+
{
|
| 74 |
+
"type": "gflops",
|
| 75 |
+
"value": "args.batch * args.frames * (5 * args.n * args.log2radix2 + 8 * args.bins * args.tail)"
|
| 76 |
+
}
|
| 77 |
+
]
|
| 78 |
+
},
|
| 79 |
+
"args": { "frameStep": 160 }
|
| 80 |
+
},
|
| 81 |
+
{
|
| 82 |
+
"name": "mixed_radix_win400_signal16000_step160_batch4_tail25",
|
| 83 |
+
"preset": "smoke",
|
| 84 |
+
"vars": { "batch": 4, "frames": 98, "bins": 201, "n": 400, "log2radix2": 4, "tail": 25 },
|
| 85 |
+
"inputs": {
|
| 86 |
+
"signal": { "dtype": "float32", "shape": [4, 16000, 1], "dist": "normal", "seed": 2010 },
|
| 87 |
+
"window": { "dtype": "float32", "shape": [400], "dist": "uniform", "seed": 2011 }
|
| 88 |
+
},
|
| 89 |
+
"outputs": { "output": { "dtype": "float32", "shape": [4, 98, 201, 2] } },
|
| 90 |
+
"bench": {
|
| 91 |
+
"metrics": [
|
| 92 |
+
{
|
| 93 |
+
"type": "gflops",
|
| 94 |
+
"value": "args.batch * args.frames * (5 * args.n * args.log2radix2 + 8 * args.bins * args.tail)"
|
| 95 |
+
}
|
| 96 |
+
]
|
| 97 |
+
},
|
| 98 |
+
"args": { "frameStep": 160 }
|
| 99 |
+
},
|
| 100 |
+
{
|
| 101 |
+
"name": "radix2_win512_signal16000_step160_batch4",
|
| 102 |
+
"preset": "smoke",
|
| 103 |
+
"vars": { "batch": 4, "frames": 97, "bins": 257, "n": 512, "log2n": 9 },
|
| 104 |
+
"inputs": {
|
| 105 |
+
"signal": { "dtype": "float32", "shape": [4, 16000, 1], "dist": "normal", "seed": 2010 },
|
| 106 |
+
"window": { "dtype": "float32", "shape": [512], "dist": "uniform", "seed": 2012 }
|
| 107 |
+
},
|
| 108 |
+
"outputs": { "output": { "dtype": "float32", "shape": [4, 97, 257, 2] } },
|
| 109 |
+
"bench": { "metrics": [{ "type": "gflops", "value": "5 * args.batch * args.frames * args.n * args.log2n" }] },
|
| 110 |
+
"args": { "frameStep": 160 }
|
| 111 |
+
},
|
| 112 |
+
{
|
| 113 |
+
"name": "dispatch_cliff_NAIVE_batch48_frames1397_win512_step160",
|
| 114 |
+
"preset": "stress",
|
| 115 |
+
"vars": { "batch": 48, "frames": 1397, "bins": 257, "n": 512, "log2n": 9, "signalLength": 224000 },
|
| 116 |
+
"inputs": {
|
| 117 |
+
"signal": { "dtype": "float32", "shape": [48, 224000, 1], "dist": "normal", "seed": 2020 },
|
| 118 |
+
"window": { "dtype": "float32", "shape": [512], "dist": "uniform", "seed": 2021 }
|
| 119 |
+
},
|
| 120 |
+
"outputs": { "output": { "dtype": "float32", "shape": [48, 1397, 257, 2] } },
|
| 121 |
+
"bench": {
|
| 122 |
+
"metrics": [
|
| 123 |
+
{ "type": "gflops", "value": "5 * args.batch * args.frames * args.n * args.log2n" },
|
| 124 |
+
{
|
| 125 |
+
"type": "bandwidth",
|
| 126 |
+
"value": "4 * (args.batch * args.signalLength + args.n + args.batch * args.frames * args.bins * 2)"
|
| 127 |
+
}
|
| 128 |
+
]
|
| 129 |
+
},
|
| 130 |
+
"args": { "frameStep": 160 }
|
| 131 |
+
},
|
| 132 |
+
{
|
| 133 |
+
"name": "dispatch_cliff_sibling_FFT_batch32_frames1397_win512_step160",
|
| 134 |
+
"preset": "stress",
|
| 135 |
+
"vars": { "batch": 32, "frames": 1397, "bins": 257, "n": 512, "log2n": 9, "signalLength": 224000 },
|
| 136 |
+
"inputs": {
|
| 137 |
+
"signal": { "dtype": "float32", "shape": [32, 224000, 1], "dist": "normal", "seed": 2020 },
|
| 138 |
+
"window": { "dtype": "float32", "shape": [512], "dist": "uniform", "seed": 2022 }
|
| 139 |
+
},
|
| 140 |
+
"outputs": { "output": { "dtype": "float32", "shape": [32, 1397, 257, 2] } },
|
| 141 |
+
"bench": {
|
| 142 |
+
"metrics": [
|
| 143 |
+
{ "type": "gflops", "value": "5 * args.batch * args.frames * args.n * args.log2n" },
|
| 144 |
+
{
|
| 145 |
+
"type": "bandwidth",
|
| 146 |
+
"value": "4 * (args.batch * args.signalLength + args.n + args.batch * args.frames * args.bins * 2)"
|
| 147 |
+
}
|
| 148 |
+
]
|
| 149 |
+
},
|
| 150 |
+
"args": { "frameStep": 160 }
|
| 151 |
+
},
|
| 152 |
+
{
|
| 153 |
+
"name": "large_window_FFT_win4096_signal131072_step1024",
|
| 154 |
+
"preset": "smoke",
|
| 155 |
+
"vars": { "batch": 1, "frames": 125, "bins": 2049, "n": 4096, "log2n": 12 },
|
| 156 |
+
"inputs": {
|
| 157 |
+
"signal": { "dtype": "float32", "shape": [1, 131072, 1], "dist": "normal", "seed": 2030 },
|
| 158 |
+
"window": { "dtype": "float32", "shape": [4096], "dist": "uniform", "seed": 2031 }
|
| 159 |
+
},
|
| 160 |
+
"outputs": { "output": { "dtype": "float32", "shape": [1, 125, 2049, 2] } },
|
| 161 |
+
"bench": { "metrics": [{ "type": "gflops", "value": "5 * args.batch * args.frames * args.n * args.log2n" }] },
|
| 162 |
+
"args": { "frameStep": 1024 }
|
| 163 |
+
},
|
| 164 |
+
{
|
| 165 |
+
"name": "large_window_FFT_win8192_signal131072_step2048",
|
| 166 |
+
"preset": "stress",
|
| 167 |
+
"vars": { "batch": 1, "frames": 61, "bins": 4097, "n": 8192, "log2n": 13 },
|
| 168 |
+
"inputs": {
|
| 169 |
+
"signal": { "dtype": "float32", "shape": [1, 131072, 1], "dist": "normal", "seed": 2030 },
|
| 170 |
+
"window": { "dtype": "float32", "shape": [8192], "dist": "uniform", "seed": 2033 }
|
| 171 |
+
},
|
| 172 |
+
"outputs": { "output": { "dtype": "float32", "shape": [1, 61, 4097, 2] } },
|
| 173 |
+
"bench": { "metrics": [{ "type": "gflops", "value": "5 * args.batch * args.frames * args.n * args.log2n" }] },
|
| 174 |
+
"args": { "frameStep": 2048 }
|
| 175 |
+
},
|
| 176 |
+
{
|
| 177 |
+
"name": "large_window_sibling_FFT_win2048_signal131072_step1024",
|
| 178 |
+
"preset": "smoke",
|
| 179 |
+
"vars": { "batch": 1, "frames": 127, "bins": 1025, "n": 2048, "log2n": 11 },
|
| 180 |
+
"inputs": {
|
| 181 |
+
"signal": { "dtype": "float32", "shape": [1, 131072, 1], "dist": "normal", "seed": 2030 },
|
| 182 |
+
"window": { "dtype": "float32", "shape": [2048], "dist": "uniform", "seed": 2032 }
|
| 183 |
+
},
|
| 184 |
+
"outputs": { "output": { "dtype": "float32", "shape": [1, 127, 1025, 2] } },
|
| 185 |
+
"bench": { "metrics": [{ "type": "gflops", "value": "5 * args.batch * args.frames * args.n * args.log2n" }] },
|
| 186 |
+
"args": { "frameStep": 1024 }
|
| 187 |
+
},
|
| 188 |
+
{
|
| 189 |
+
"name": "mixed_radix_win1000_tail125_batch2_step250_twosided",
|
| 190 |
+
"preset": "stress",
|
| 191 |
+
"vars": { "batch": 2, "frames": 13, "bins": 1000, "n": 1000 },
|
| 192 |
+
"attrs": { "onesided": 0 },
|
| 193 |
+
"inputs": {
|
| 194 |
+
"signal": { "dtype": "float32", "shape": [2, 4000, 1], "dist": "normal", "seed": 3120 },
|
| 195 |
+
"window": { "dtype": "float32", "shape": [1000], "dist": "uniform", "seed": 3121 }
|
| 196 |
+
},
|
| 197 |
+
"outputs": { "output": { "dtype": "float32", "shape": [2, 13, 1000, 2], "dist": "empty" } },
|
| 198 |
+
"bench": { "metrics": [{ "type": "gflops", "value": "5 * args.batch * args.frames * args.bins * args.n" }] },
|
| 199 |
+
"args": { "frameStep": 250 }
|
| 200 |
+
},
|
| 201 |
+
{
|
| 202 |
+
"name": "tiled_win2047_nonpow2_boundary_batch2_step256",
|
| 203 |
+
"preset": "stress",
|
| 204 |
+
"vars": { "batch": 2, "frames": 30, "bins": 1024, "n": 2047 },
|
| 205 |
+
"inputs": {
|
| 206 |
+
"signal": { "dtype": "float32", "shape": [2, 9471, 1], "dist": "normal", "seed": 3101 },
|
| 207 |
+
"window": { "dtype": "float32", "shape": [2047], "dist": "uniform", "seed": 3102 }
|
| 208 |
+
},
|
| 209 |
+
"outputs": { "output": { "dtype": "float32", "shape": [2, 30, 1024, 2], "dist": "empty" } },
|
| 210 |
+
"bench": { "metrics": [{ "type": "gflops", "value": "5 * args.batch * args.frames * args.bins * args.n" }] },
|
| 211 |
+
"args": { "frameStep": 256 }
|
| 212 |
+
},
|
| 213 |
+
{
|
| 214 |
+
"name": "mixed_radix_win1536_batch4_step384",
|
| 215 |
+
"preset": "stress",
|
| 216 |
+
"vars": { "batch": 4, "frames": 13, "bins": 769, "n": 1536 },
|
| 217 |
+
"inputs": {
|
| 218 |
+
"signal": { "dtype": "float32", "shape": [4, 6144, 1], "dist": "normal", "seed": 3110 },
|
| 219 |
+
"window": { "dtype": "float32", "shape": [1536], "dist": "uniform", "seed": 3111 }
|
| 220 |
+
},
|
| 221 |
+
"outputs": { "output": { "dtype": "float32", "shape": [4, 13, 769, 2], "dist": "empty" } },
|
| 222 |
+
"bench": { "metrics": [{ "type": "gflops", "value": "5 * args.batch * args.frames * args.bins * args.n" }] },
|
| 223 |
+
"args": { "frameStep": 384 }
|
| 224 |
+
},
|
| 225 |
+
{
|
| 226 |
+
"name": "tiled_win513_nonpow2_boundary_batch2_signal8193_step160",
|
| 227 |
+
"preset": "stress",
|
| 228 |
+
"vars": { "batch": 2, "frames": 49, "bins": 257, "n": 513 },
|
| 229 |
+
"attrs": { "onesided": 1 },
|
| 230 |
+
"inputs": {
|
| 231 |
+
"signal": { "dtype": "float32", "shape": [2, 8193, 1], "dist": "normal", "seed": 3120 },
|
| 232 |
+
"window": { "dtype": "float32", "shape": [513], "dist": "uniform", "seed": 3121 }
|
| 233 |
+
},
|
| 234 |
+
"outputs": { "output": { "dtype": "float32", "shape": [2, 49, 257, 2], "dist": "empty" } },
|
| 235 |
+
"bench": { "metrics": [{ "type": "gflops", "value": "5 * args.batch * args.frames * args.bins * args.n" }] },
|
| 236 |
+
"args": { "frameStep": 160 }
|
| 237 |
+
},
|
| 238 |
+
{
|
| 239 |
+
"name": "audio-no-window-frame512-batch8-signal160000-pathology",
|
| 240 |
+
"preset": "stress",
|
| 241 |
+
"provenance": {
|
| 242 |
+
"notes": "Large no-window transform that exercises rank3_no_window; the windowed FFT control isolates the optional-window route gap."
|
| 243 |
+
},
|
| 244 |
+
"vars": { "batch": 8, "frames": 997, "bins": 257, "n": 512, "log2n": 9 },
|
| 245 |
+
"attrs": { "onesided": 1 },
|
| 246 |
+
"inputs": { "signal": { "dtype": "float32", "shape": [8, 160000, 1], "dist": "normal", "seed": 3130 } },
|
| 247 |
+
"outputs": { "output": { "dtype": "float32", "shape": [8, 997, 257, 2], "dist": "empty" } },
|
| 248 |
+
"bench": { "metrics": [{ "type": "gflops", "value": "5 * args.batch * args.frames * args.n * args.log2n" }] },
|
| 249 |
+
"args": { "frameStep": 160, "frameLength": 512 }
|
| 250 |
+
}
|
| 251 |
+
]
|
| 252 |
+
}
|
build/webgpu/dft-tiled-real.wgsl.jinja
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// Portable direct transform for large real signals. A 256-lane workgroup owns
|
| 2 |
+
// 256 adjacent frequency bins and stages one input row or windowed STFT frame
|
| 3 |
+
// once, avoiding a global reload per (frequency, sample) pair. Twiddles advance
|
| 4 |
+
// by complex multiplication and reset from sin/cos every 64 samples to bound
|
| 5 |
+
// phase drift without transcendental functions in the inner loop.
|
| 6 |
+
{{ env.wgsl.resourceDeclarations }}
|
| 7 |
+
// Backend-stable unit-circle twiddles for DFT kernels. Retain the phase as a
|
| 8 |
+
// rational turn and route all polynomial evaluation through the shared precise
|
| 9 |
+
// trig core so FFT, direct DFT, STFT, and cosine windows agree exactly.
|
| 10 |
+
//
|
| 11 |
+
// phase is in [0, period). sign is -1 for a forward transform and +1 for an
|
| 12 |
+
// inverse transform. The result is (cos, sin).
|
| 13 |
+
// Backend-stable f32 sine/cosine core.
|
| 14 |
+
//
|
| 15 |
+
// Shader transcendental accuracy is implementation-defined, and some portable
|
| 16 |
+
// backends are only accurate to roughly 1e-4. Each path retains the most accurate
|
| 17 |
+
// available phase representation, reduces it to [-pi, pi], then uses these
|
| 18 |
+
// polynomials. The half-pi core is degree 13 for sine and degree 12 for cosine;
|
| 19 |
+
// truncation error is well below one f32 ULP over its documented interval.
|
| 20 |
+
const PRECISE_TRIG_PI: f32 = 3.141592653589793;
|
| 21 |
+
const PRECISE_TRIG_TWO_PI: f32 = 6.283185307179586;
|
| 22 |
+
const PRECISE_TRIG_HALF_PI: f32 = 1.5707963267948966;
|
| 23 |
+
|
| 24 |
+
// Input must be in [-pi/2, pi/2]. Returns (cos(x), sin(x)).
|
| 25 |
+
fn precise_sincos_half_pi(x: f32) -> vec2<f32> {
|
| 26 |
+
let x2 = x * x;
|
| 27 |
+
|
| 28 |
+
var sinPolynomial = 1.6059043836821613e-10;
|
| 29 |
+
sinPolynomial = fma(sinPolynomial, x2, -2.505210838544172e-8);
|
| 30 |
+
sinPolynomial = fma(sinPolynomial, x2, 2.7557319223985893e-6);
|
| 31 |
+
sinPolynomial = fma(sinPolynomial, x2, -1.984126984126984e-4);
|
| 32 |
+
sinPolynomial = fma(sinPolynomial, x2, 8.333333333333333e-3);
|
| 33 |
+
sinPolynomial = fma(sinPolynomial, x2, -1.6666666666666666e-1);
|
| 34 |
+
let sine = x * fma(sinPolynomial, x2, 1.0);
|
| 35 |
+
|
| 36 |
+
var cosPolynomial = 2.08767569878681e-9;
|
| 37 |
+
cosPolynomial = fma(cosPolynomial, x2, -2.755731922398589e-7);
|
| 38 |
+
cosPolynomial = fma(cosPolynomial, x2, 2.48015873015873e-5);
|
| 39 |
+
cosPolynomial = fma(cosPolynomial, x2, -1.388888888888889e-3);
|
| 40 |
+
cosPolynomial = fma(cosPolynomial, x2, 4.1666666666666664e-2);
|
| 41 |
+
cosPolynomial = fma(cosPolynomial, x2, -5.0e-1);
|
| 42 |
+
let cosine = fma(cosPolynomial, x2, 1.0);
|
| 43 |
+
|
| 44 |
+
return vec2<f32>(cosine, sine);
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
// Input must be in [-pi, pi]. Returns (cos(x), sin(x)).
|
| 48 |
+
fn precise_sincos_centered(x: f32) -> vec2<f32> {
|
| 49 |
+
var folded = x;
|
| 50 |
+
var cosineSign = 1.0;
|
| 51 |
+
if (folded > PRECISE_TRIG_HALF_PI) {
|
| 52 |
+
folded = PRECISE_TRIG_PI - folded;
|
| 53 |
+
cosineSign = -1.0;
|
| 54 |
+
} else if (folded < -PRECISE_TRIG_HALF_PI) {
|
| 55 |
+
folded = -PRECISE_TRIG_PI - folded;
|
| 56 |
+
cosineSign = -1.0;
|
| 57 |
+
}
|
| 58 |
+
let value = precise_sincos_half_pi(folded);
|
| 59 |
+
return vec2<f32>(cosineSign * value.x, value.y);
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
fn dft_precise_twiddle(phase: u32, period: u32, sign: f32) -> vec2<f32> {
|
| 64 |
+
let reducedPhase = phase % period;
|
| 65 |
+
if (reducedPhase == 0u) {
|
| 66 |
+
return vec2<f32>(1.0, sign * 0.0);
|
| 67 |
+
}
|
| 68 |
+
if (period % 4u == 0u) {
|
| 69 |
+
let quarter = period / 4u;
|
| 70 |
+
if (reducedPhase == quarter) {
|
| 71 |
+
return vec2<f32>(0.0, sign);
|
| 72 |
+
}
|
| 73 |
+
if (reducedPhase == 2u * quarter) {
|
| 74 |
+
return vec2<f32>(-1.0, sign * 0.0);
|
| 75 |
+
}
|
| 76 |
+
if (reducedPhase == 3u * quarter) {
|
| 77 |
+
return vec2<f32>(0.0, -sign);
|
| 78 |
+
}
|
| 79 |
+
}
|
| 80 |
+
// Center the rational turn before multiplying by 2*pi. This avoids the
|
| 81 |
+
// cancellation in `2*pi*(N-1)/N - 2*pi` for bins near the end of a row.
|
| 82 |
+
let turns = f32(reducedPhase) / f32(period);
|
| 83 |
+
let centeredTurns = turns - floor(turns + 0.5);
|
| 84 |
+
return precise_sincos_centered(sign * PRECISE_TRIG_TWO_PI * centeredTurns);
|
| 85 |
+
}
|
| 86 |
+
|
| 87 |
+
const N: u32 = {{ source.transformLength }}u;
|
| 88 |
+
const NUM_BINS: u32 = {{ source.numBins }}u;
|
| 89 |
+
const WG: u32 = {{ source.workgroupSize }}u;
|
| 90 |
+
const TWIDDLE_RESET: u32 = 64u;
|
| 91 |
+
|
| 92 |
+
var<workgroup> signalTile: array<f32, N>;
|
| 93 |
+
|
| 94 |
+
@compute @workgroup_size(WG, 1, 1)
|
| 95 |
+
fn main(
|
| 96 |
+
@builtin(workgroup_id) wg: vec3<u32>,
|
| 97 |
+
@builtin(local_invocation_id) lid: vec3<u32>,
|
| 98 |
+
) {
|
| 99 |
+
let lane = lid.x;
|
| 100 |
+
let frame = wg.y;
|
| 101 |
+
let batch = wg.z;
|
| 102 |
+
let start = frame * params.frameStep;
|
| 103 |
+
|
| 104 |
+
for (var n = lane; n < N; n = n + WG) {
|
| 105 |
+
let signalIndex = start + n;
|
| 106 |
+
signalTile[n] = signal[batch * params.signalLength + signalIndex] * window[n];
|
| 107 |
+
}
|
| 108 |
+
workgroupBarrier();
|
| 109 |
+
|
| 110 |
+
let freq = wg.x * WG + lane;
|
| 111 |
+
if (freq >= NUM_BINS) {
|
| 112 |
+
return;
|
| 113 |
+
}
|
| 114 |
+
|
| 115 |
+
let stepTwiddle = dft_precise_twiddle(freq, N, -1.0);
|
| 116 |
+
var real = 0.0;
|
| 117 |
+
var imag = 0.0;
|
| 118 |
+
|
| 119 |
+
for (var base = 0u; base < N; base = base + TWIDDLE_RESET) {
|
| 120 |
+
// Reduce the reset phase in integer space first. Computing angleStep*base
|
| 121 |
+
// directly loses enough f32 precision at high bins/long signals to create
|
| 122 |
+
// visible drift even though each 64-sample recurrence remains stable.
|
| 123 |
+
let phase = (freq * base) % N;
|
| 124 |
+
var twiddle = dft_precise_twiddle(phase, N, -1.0);
|
| 125 |
+
let end = min(N, base + TWIDDLE_RESET);
|
| 126 |
+
for (var n = base; n < end; n = n + 1u) {
|
| 127 |
+
let value = signalTile[n];
|
| 128 |
+
real = real + value * twiddle.x;
|
| 129 |
+
imag = imag + value * twiddle.y;
|
| 130 |
+
twiddle = vec2<f32>(
|
| 131 |
+
twiddle.x * stepTwiddle.x - twiddle.y * stepTwiddle.y,
|
| 132 |
+
twiddle.x * stepTwiddle.y + twiddle.y * stepTwiddle.x,
|
| 133 |
+
);
|
| 134 |
+
}
|
| 135 |
+
}
|
| 136 |
+
|
| 137 |
+
let outputBase = ((batch * params.numFrames + frame) * NUM_BINS + freq) * 2u;
|
| 138 |
+
output[outputBase] = real;
|
| 139 |
+
output[outputBase + 1u] = imag;
|
| 140 |
+
}
|
build/webgpu/fft-radix2-dit-shared-inplace.wgsl.jinja
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// Workgroup-resident in-place radix-2 DIT FFT.
|
| 2 |
+
//
|
| 3 |
+
// This route fills the gap between the double-buffered Stockham kernel
|
| 4 |
+
// (16 bytes/element) and the global-scratch DIT fallback. A single complex
|
| 5 |
+
// array needs only 8 bytes/element, extending the resident FFT range one
|
| 6 |
+
// power-of-two rung beyond the 16-byte/element Stockham kernel on every device.
|
| 7 |
+
// Each workgroup owns one transform; all stages remain local and use uniform
|
| 8 |
+
// barriers, so only the input and requested spectrum touch global memory.
|
| 9 |
+
// STFT is forward-only, so the transform sign is fixed at -1.
|
| 10 |
+
{{ env.wgsl.resourceDeclarations }}
|
| 11 |
+
// Backend-stable unit-circle twiddles for DFT kernels. Retain the phase as a
|
| 12 |
+
// rational turn and route all polynomial evaluation through the shared precise
|
| 13 |
+
// trig core so FFT, direct DFT, STFT, and cosine windows agree exactly.
|
| 14 |
+
//
|
| 15 |
+
// phase is in [0, period). sign is -1 for a forward transform and +1 for an
|
| 16 |
+
// inverse transform. The result is (cos, sin).
|
| 17 |
+
// Backend-stable f32 sine/cosine core.
|
| 18 |
+
//
|
| 19 |
+
// Shader transcendental accuracy is implementation-defined, and some portable
|
| 20 |
+
// backends are only accurate to roughly 1e-4. Each path retains the most accurate
|
| 21 |
+
// available phase representation, reduces it to [-pi, pi], then uses these
|
| 22 |
+
// polynomials. The half-pi core is degree 13 for sine and degree 12 for cosine;
|
| 23 |
+
// truncation error is well below one f32 ULP over its documented interval.
|
| 24 |
+
const PRECISE_TRIG_PI: f32 = 3.141592653589793;
|
| 25 |
+
const PRECISE_TRIG_TWO_PI: f32 = 6.283185307179586;
|
| 26 |
+
const PRECISE_TRIG_HALF_PI: f32 = 1.5707963267948966;
|
| 27 |
+
|
| 28 |
+
// Input must be in [-pi/2, pi/2]. Returns (cos(x), sin(x)).
|
| 29 |
+
fn precise_sincos_half_pi(x: f32) -> vec2<f32> {
|
| 30 |
+
let x2 = x * x;
|
| 31 |
+
|
| 32 |
+
var sinPolynomial = 1.6059043836821613e-10;
|
| 33 |
+
sinPolynomial = fma(sinPolynomial, x2, -2.505210838544172e-8);
|
| 34 |
+
sinPolynomial = fma(sinPolynomial, x2, 2.7557319223985893e-6);
|
| 35 |
+
sinPolynomial = fma(sinPolynomial, x2, -1.984126984126984e-4);
|
| 36 |
+
sinPolynomial = fma(sinPolynomial, x2, 8.333333333333333e-3);
|
| 37 |
+
sinPolynomial = fma(sinPolynomial, x2, -1.6666666666666666e-1);
|
| 38 |
+
let sine = x * fma(sinPolynomial, x2, 1.0);
|
| 39 |
+
|
| 40 |
+
var cosPolynomial = 2.08767569878681e-9;
|
| 41 |
+
cosPolynomial = fma(cosPolynomial, x2, -2.755731922398589e-7);
|
| 42 |
+
cosPolynomial = fma(cosPolynomial, x2, 2.48015873015873e-5);
|
| 43 |
+
cosPolynomial = fma(cosPolynomial, x2, -1.388888888888889e-3);
|
| 44 |
+
cosPolynomial = fma(cosPolynomial, x2, 4.1666666666666664e-2);
|
| 45 |
+
cosPolynomial = fma(cosPolynomial, x2, -5.0e-1);
|
| 46 |
+
let cosine = fma(cosPolynomial, x2, 1.0);
|
| 47 |
+
|
| 48 |
+
return vec2<f32>(cosine, sine);
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
// Input must be in [-pi, pi]. Returns (cos(x), sin(x)).
|
| 52 |
+
fn precise_sincos_centered(x: f32) -> vec2<f32> {
|
| 53 |
+
var folded = x;
|
| 54 |
+
var cosineSign = 1.0;
|
| 55 |
+
if (folded > PRECISE_TRIG_HALF_PI) {
|
| 56 |
+
folded = PRECISE_TRIG_PI - folded;
|
| 57 |
+
cosineSign = -1.0;
|
| 58 |
+
} else if (folded < -PRECISE_TRIG_HALF_PI) {
|
| 59 |
+
folded = -PRECISE_TRIG_PI - folded;
|
| 60 |
+
cosineSign = -1.0;
|
| 61 |
+
}
|
| 62 |
+
let value = precise_sincos_half_pi(folded);
|
| 63 |
+
return vec2<f32>(cosineSign * value.x, value.y);
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
fn dft_precise_twiddle(phase: u32, period: u32, sign: f32) -> vec2<f32> {
|
| 68 |
+
let reducedPhase = phase % period;
|
| 69 |
+
if (reducedPhase == 0u) {
|
| 70 |
+
return vec2<f32>(1.0, sign * 0.0);
|
| 71 |
+
}
|
| 72 |
+
if (period % 4u == 0u) {
|
| 73 |
+
let quarter = period / 4u;
|
| 74 |
+
if (reducedPhase == quarter) {
|
| 75 |
+
return vec2<f32>(0.0, sign);
|
| 76 |
+
}
|
| 77 |
+
if (reducedPhase == 2u * quarter) {
|
| 78 |
+
return vec2<f32>(-1.0, sign * 0.0);
|
| 79 |
+
}
|
| 80 |
+
if (reducedPhase == 3u * quarter) {
|
| 81 |
+
return vec2<f32>(0.0, -sign);
|
| 82 |
+
}
|
| 83 |
+
}
|
| 84 |
+
// Center the rational turn before multiplying by 2*pi. This avoids the
|
| 85 |
+
// cancellation in `2*pi*(N-1)/N - 2*pi` for bins near the end of a row.
|
| 86 |
+
let turns = f32(reducedPhase) / f32(period);
|
| 87 |
+
let centeredTurns = turns - floor(turns + 0.5);
|
| 88 |
+
return precise_sincos_centered(sign * PRECISE_TRIG_TWO_PI * centeredTurns);
|
| 89 |
+
}
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
const N: u32 = {{ source.fftLength }}u;
|
| 93 |
+
const N_HALF: u32 = N / 2u;
|
| 94 |
+
const NUM_BINS: u32 = {{ source.numBins }}u;
|
| 95 |
+
const WG: u32 = {{ tunables.WORKGROUP_SIZE }}u;
|
| 96 |
+
const SIGN: f32 = -1.0;
|
| 97 |
+
var<workgroup> fftData: array<vec2<f32>, {{ source.fftLength }}>;
|
| 98 |
+
|
| 99 |
+
@compute @workgroup_size(WG, 1, 1)
|
| 100 |
+
fn main(@builtin(workgroup_id) wgid: vec3<u32>,
|
| 101 |
+
@builtin(local_invocation_id) lid: vec3<u32>,
|
| 102 |
+
@builtin(num_workgroups) nwg: vec3<u32>) {
|
| 103 |
+
let transform = wgid.x + wgid.y * nwg.x;
|
| 104 |
+
let batch = transform / params.numFrames;
|
| 105 |
+
let frame = transform - batch * params.numFrames;
|
| 106 |
+
let start = frame * params.frameStep;
|
| 107 |
+
|
| 108 |
+
for (var position = lid.x; position < N; position = position + WG) {
|
| 109 |
+
let n = reverseBits(position) >> (32u - {{ source.log2Length }}u);
|
| 110 |
+
let signalIndex = start + n;
|
| 111 |
+
let inputBase = (batch * params.signalLength + signalIndex) * {{ source.inComp }}u;
|
| 112 |
+
let w = window[n];
|
| 113 |
+
var value = vec2<f32>(signal[inputBase] * w, 0.0);
|
| 114 |
+
{% if source.inComp == 2 %}
|
| 115 |
+
value.y = signal[inputBase + 1u] * w;
|
| 116 |
+
{% endif %}
|
| 117 |
+
fftData[position] = value;
|
| 118 |
+
}
|
| 119 |
+
workgroupBarrier();
|
| 120 |
+
|
| 121 |
+
{% for stage in range(1, source.log2Length + 1) %}
|
| 122 |
+
{
|
| 123 |
+
const SPAN: u32 = 1u << {{ stage }}u;
|
| 124 |
+
const HALF: u32 = SPAN >> 1u;
|
| 125 |
+
for (var butterfly = lid.x; butterfly < N_HALF; butterfly = butterfly + WG) {
|
| 126 |
+
let group = butterfly / HALF;
|
| 127 |
+
let j = butterfly - group * HALF;
|
| 128 |
+
let lo = group * SPAN + j;
|
| 129 |
+
let hi = lo + HALF;
|
| 130 |
+
let a = fftData[lo];
|
| 131 |
+
let b = fftData[hi];
|
| 132 |
+
let twiddle = dft_precise_twiddle(j, SPAN, SIGN);
|
| 133 |
+
let t = vec2<f32>(
|
| 134 |
+
b.x * twiddle.x - b.y * twiddle.y,
|
| 135 |
+
b.x * twiddle.y + b.y * twiddle.x,
|
| 136 |
+
);
|
| 137 |
+
fftData[lo] = a + t;
|
| 138 |
+
fftData[hi] = a - t;
|
| 139 |
+
}
|
| 140 |
+
workgroupBarrier();
|
| 141 |
+
}
|
| 142 |
+
{% endfor %}
|
| 143 |
+
|
| 144 |
+
for (var bin = lid.x; bin < NUM_BINS; bin = bin + WG) {
|
| 145 |
+
let dst = (transform * NUM_BINS + bin) * 2u;
|
| 146 |
+
output[dst] = fftData[bin].x;
|
| 147 |
+
output[dst + 1u] = fftData[bin].y;
|
| 148 |
+
}
|
| 149 |
+
}
|
build/webgpu/fft-radix2-dit-storage-pass.wgsl.jinja
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// Globally staged radix-2 DIT FFT. A single-workgroup storage FFT is efficient
|
| 2 |
+
// when a device can keep a whole transform resident, but architectures that
|
| 3 |
+
// serialize storage barriers or spill a large workgroup can become severely
|
| 4 |
+
// under-occupied. This route exposes every transform element or butterfly to
|
| 5 |
+
// the global scheduler. Separate dispatches provide the required stage barriers
|
| 6 |
+
// while preserving the same bit-reversed DIT algorithm and scratch layout.
|
| 7 |
+
// STFT is forward-only, so the transform sign is fixed at -1.
|
| 8 |
+
{{ env.wgsl.resourceDeclarations }}
|
| 9 |
+
{% if source.phase == "local_prefix" or source.phase == "stage" %}
|
| 10 |
+
// Backend-stable unit-circle twiddles for DFT kernels. Retain the phase as a
|
| 11 |
+
// rational turn and route all polynomial evaluation through the shared precise
|
| 12 |
+
// trig core so FFT, direct DFT, STFT, and cosine windows agree exactly.
|
| 13 |
+
//
|
| 14 |
+
// phase is in [0, period). sign is -1 for a forward transform and +1 for an
|
| 15 |
+
// inverse transform. The result is (cos, sin).
|
| 16 |
+
{% set needPreciseTrigCentered = true %}
|
| 17 |
+
{% set needPreciseTrigTwoPi = true %}
|
| 18 |
+
// Backend-stable f32 sine/cosine core.
|
| 19 |
+
//
|
| 20 |
+
// Shader transcendental accuracy is implementation-defined, and some portable
|
| 21 |
+
// backends are only accurate to roughly 1e-4. Each path retains the most accurate
|
| 22 |
+
// available phase representation, reduces it to [-pi, pi], then uses these
|
| 23 |
+
// polynomials. The half-pi core is degree 13 for sine and degree 12 for cosine;
|
| 24 |
+
// truncation error is well below one f32 ULP over its documented interval.
|
| 25 |
+
{% if needPreciseTrigCentered %}
|
| 26 |
+
const PRECISE_TRIG_PI: f32 = 3.141592653589793;
|
| 27 |
+
{% endif %}
|
| 28 |
+
{% if needPreciseTrigTwoPi %}
|
| 29 |
+
const PRECISE_TRIG_TWO_PI: f32 = 6.283185307179586;
|
| 30 |
+
{% endif %}
|
| 31 |
+
{% if needPreciseTrigCentered %}
|
| 32 |
+
const PRECISE_TRIG_HALF_PI: f32 = 1.5707963267948966;
|
| 33 |
+
{% endif %}
|
| 34 |
+
|
| 35 |
+
// Input must be in [-pi/2, pi/2]. Returns (cos(x), sin(x)).
|
| 36 |
+
fn precise_sincos_half_pi(x: f32) -> vec2<f32> {
|
| 37 |
+
let x2 = x * x;
|
| 38 |
+
|
| 39 |
+
var sinPolynomial = 1.6059043836821613e-10;
|
| 40 |
+
sinPolynomial = fma(sinPolynomial, x2, -2.505210838544172e-8);
|
| 41 |
+
sinPolynomial = fma(sinPolynomial, x2, 2.7557319223985893e-6);
|
| 42 |
+
sinPolynomial = fma(sinPolynomial, x2, -1.984126984126984e-4);
|
| 43 |
+
sinPolynomial = fma(sinPolynomial, x2, 8.333333333333333e-3);
|
| 44 |
+
sinPolynomial = fma(sinPolynomial, x2, -1.6666666666666666e-1);
|
| 45 |
+
let sine = x * fma(sinPolynomial, x2, 1.0);
|
| 46 |
+
|
| 47 |
+
var cosPolynomial = 2.08767569878681e-9;
|
| 48 |
+
cosPolynomial = fma(cosPolynomial, x2, -2.755731922398589e-7);
|
| 49 |
+
cosPolynomial = fma(cosPolynomial, x2, 2.48015873015873e-5);
|
| 50 |
+
cosPolynomial = fma(cosPolynomial, x2, -1.388888888888889e-3);
|
| 51 |
+
cosPolynomial = fma(cosPolynomial, x2, 4.1666666666666664e-2);
|
| 52 |
+
cosPolynomial = fma(cosPolynomial, x2, -5.0e-1);
|
| 53 |
+
let cosine = fma(cosPolynomial, x2, 1.0);
|
| 54 |
+
|
| 55 |
+
return vec2<f32>(cosine, sine);
|
| 56 |
+
}
|
| 57 |
+
{% if needPreciseTrigCentered %}
|
| 58 |
+
|
| 59 |
+
// Input must be in [-pi, pi]. Returns (cos(x), sin(x)).
|
| 60 |
+
fn precise_sincos_centered(x: f32) -> vec2<f32> {
|
| 61 |
+
var folded = x;
|
| 62 |
+
var cosineSign = 1.0;
|
| 63 |
+
if (folded > PRECISE_TRIG_HALF_PI) {
|
| 64 |
+
folded = PRECISE_TRIG_PI - folded;
|
| 65 |
+
cosineSign = -1.0;
|
| 66 |
+
} else if (folded < -PRECISE_TRIG_HALF_PI) {
|
| 67 |
+
folded = -PRECISE_TRIG_PI - folded;
|
| 68 |
+
cosineSign = -1.0;
|
| 69 |
+
}
|
| 70 |
+
let value = precise_sincos_half_pi(folded);
|
| 71 |
+
return vec2<f32>(cosineSign * value.x, value.y);
|
| 72 |
+
}
|
| 73 |
+
{% endif %}
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
fn dft_precise_twiddle(phase: u32, period: u32, sign: f32) -> vec2<f32> {
|
| 77 |
+
let reducedPhase = phase % period;
|
| 78 |
+
if (reducedPhase == 0u) {
|
| 79 |
+
return vec2<f32>(1.0, sign * 0.0);
|
| 80 |
+
}
|
| 81 |
+
if (period % 4u == 0u) {
|
| 82 |
+
let quarter = period / 4u;
|
| 83 |
+
if (reducedPhase == quarter) {
|
| 84 |
+
return vec2<f32>(0.0, sign);
|
| 85 |
+
}
|
| 86 |
+
if (reducedPhase == 2u * quarter) {
|
| 87 |
+
return vec2<f32>(-1.0, sign * 0.0);
|
| 88 |
+
}
|
| 89 |
+
if (reducedPhase == 3u * quarter) {
|
| 90 |
+
return vec2<f32>(0.0, -sign);
|
| 91 |
+
}
|
| 92 |
+
}
|
| 93 |
+
// Center the rational turn before multiplying by 2*pi. This avoids the
|
| 94 |
+
// cancellation in `2*pi*(N-1)/N - 2*pi` for bins near the end of a row.
|
| 95 |
+
let turns = f32(reducedPhase) / f32(period);
|
| 96 |
+
let centeredTurns = turns - floor(turns + 0.5);
|
| 97 |
+
return precise_sincos_centered(sign * PRECISE_TRIG_TWO_PI * centeredTurns);
|
| 98 |
+
}
|
| 99 |
+
|
| 100 |
+
|
| 101 |
+
{% endif %}
|
| 102 |
+
const N: u32 = {{ source.fftLength }}u;
|
| 103 |
+
{% if source.phase == "stage" %}
|
| 104 |
+
const N_HALF: u32 = N / 2u;
|
| 105 |
+
{% endif %}
|
| 106 |
+
{% if source.phase == "finish" %}
|
| 107 |
+
const NUM_BINS: u32 = {{ source.numBins }}u;
|
| 108 |
+
{% endif %}
|
| 109 |
+
const WG: u32 = {{ tunables.WORKGROUP_SIZE }}u;
|
| 110 |
+
{% if source.phase == "stage" %}
|
| 111 |
+
const SPAN: u32 = 1u << {{ source.stage }}u;
|
| 112 |
+
const HALF: u32 = SPAN >> 1u;
|
| 113 |
+
const SIGN: f32 = -1.0;
|
| 114 |
+
{% elif source.phase == "local_prefix" %}
|
| 115 |
+
const LOCAL_N: u32 = {{ source.localLength }}u;
|
| 116 |
+
const BLOCKS_PER_TRANSFORM: u32 = N / LOCAL_N;
|
| 117 |
+
const SIGN: f32 = -1.0;
|
| 118 |
+
var<workgroup> localData: array<vec2<f32>, {{ source.localLength }}>;
|
| 119 |
+
{% endif %}
|
| 120 |
+
|
| 121 |
+
{% if source.phase != "local_prefix" %}fn folded_global_index(gid: vec3<u32>, nwg: vec3<u32>) -> u32 {
|
| 122 |
+
return gid.x + gid.y * nwg.x * WG;
|
| 123 |
+
}{% endif %}
|
| 124 |
+
|
| 125 |
+
|
| 126 |
+
@compute @workgroup_size(WG, 1, 1)
|
| 127 |
+
fn main({% if source.phase != "local_prefix" %}@builtin(global_invocation_id) gid: vec3<u32>,
|
| 128 |
+
{% else %}@builtin(local_invocation_id) lid: vec3<u32>,
|
| 129 |
+
@builtin(workgroup_id) wgid: vec3<u32>,
|
| 130 |
+
{% endif %}
|
| 131 |
+
@builtin(num_workgroups) nwg: vec3<u32>) {
|
| 132 |
+
{% if source.phase != "local_prefix" %}
|
| 133 |
+
let flat = folded_global_index(gid, nwg);
|
| 134 |
+
{% endif %}
|
| 135 |
+
let transforms = arrayLength(&scratch) / (N * 2u);
|
| 136 |
+
|
| 137 |
+
{% if source.phase == "local_prefix" %}
|
| 138 |
+
let flatGroup = wgid.x + wgid.y * nwg.x;
|
| 139 |
+
let transform = flatGroup / BLOCKS_PER_TRANSFORM;
|
| 140 |
+
if (transform >= transforms) { return; }
|
| 141 |
+
let block = flatGroup - transform * BLOCKS_PER_TRANSFORM;
|
| 142 |
+
let position = block * LOCAL_N + lid.x;
|
| 143 |
+
let n = reverseBits(position) >> (32u - {{ source.log2Length }}u);
|
| 144 |
+
let batch = transform / params.numFrames;
|
| 145 |
+
let frame = transform - batch * params.numFrames;
|
| 146 |
+
let start = frame * params.frameStep;
|
| 147 |
+
let signalIndex = start + n;
|
| 148 |
+
let inputBase = (batch * params.signalLength + signalIndex) * {{ source.inComp }}u;
|
| 149 |
+
let w = window[n];
|
| 150 |
+
var value = vec2<f32>(signal[inputBase] * w, 0.0);
|
| 151 |
+
{% if source.inComp == 2 %}
|
| 152 |
+
value.y = signal[inputBase + 1u] * w;
|
| 153 |
+
{% endif %}
|
| 154 |
+
localData[lid.x] = value;
|
| 155 |
+
workgroupBarrier();
|
| 156 |
+
|
| 157 |
+
{% for stage in range(1, source.localLog2Length + 1) %}
|
| 158 |
+
if (lid.x < LOCAL_N / 2u) {
|
| 159 |
+
const LOCAL_SPAN_{{ stage }}: u32 = 1u << {{ stage }}u;
|
| 160 |
+
const LOCAL_HALF_{{ stage }}: u32 = LOCAL_SPAN_{{ stage }} >> 1u;
|
| 161 |
+
let group = lid.x / LOCAL_HALF_{{ stage }};
|
| 162 |
+
let j = lid.x - group * LOCAL_HALF_{{ stage }};
|
| 163 |
+
let lo = group * LOCAL_SPAN_{{ stage }} + j;
|
| 164 |
+
let hi = lo + LOCAL_HALF_{{ stage }};
|
| 165 |
+
let a = localData[lo];
|
| 166 |
+
let b = localData[hi];
|
| 167 |
+
let twiddle = dft_precise_twiddle(j, LOCAL_SPAN_{{ stage }}, SIGN);
|
| 168 |
+
let t = vec2<f32>(
|
| 169 |
+
b.x * twiddle.x - b.y * twiddle.y,
|
| 170 |
+
b.x * twiddle.y + b.y * twiddle.x,
|
| 171 |
+
);
|
| 172 |
+
localData[lo] = a + t;
|
| 173 |
+
localData[hi] = a - t;
|
| 174 |
+
}
|
| 175 |
+
workgroupBarrier();
|
| 176 |
+
{% endfor %}
|
| 177 |
+
|
| 178 |
+
let dst = (transform * N + position) * 2u;
|
| 179 |
+
scratch[dst] = localData[lid.x].x;
|
| 180 |
+
scratch[dst + 1u] = localData[lid.x].y;
|
| 181 |
+
|
| 182 |
+
{% elif source.phase == "stage" %}
|
| 183 |
+
let count = transforms * N_HALF;
|
| 184 |
+
if (flat >= count) { return; }
|
| 185 |
+
let transform = flat / N_HALF;
|
| 186 |
+
let butterfly = flat - transform * N_HALF;
|
| 187 |
+
let group = butterfly / HALF;
|
| 188 |
+
let j = butterfly - group * HALF;
|
| 189 |
+
let lo = (transform * N + group * SPAN + j) * 2u;
|
| 190 |
+
let hi = lo + HALF * 2u;
|
| 191 |
+
let a = vec2<f32>(scratch[lo], scratch[lo + 1u]);
|
| 192 |
+
let b = vec2<f32>(scratch[hi], scratch[hi + 1u]);
|
| 193 |
+
let twiddle = dft_precise_twiddle(j, SPAN, SIGN);
|
| 194 |
+
let t = vec2<f32>(
|
| 195 |
+
b.x * twiddle.x - b.y * twiddle.y,
|
| 196 |
+
b.x * twiddle.y + b.y * twiddle.x,
|
| 197 |
+
);
|
| 198 |
+
let sum = a + t;
|
| 199 |
+
let difference = a - t;
|
| 200 |
+
scratch[lo] = sum.x;
|
| 201 |
+
scratch[lo + 1u] = sum.y;
|
| 202 |
+
scratch[hi] = difference.x;
|
| 203 |
+
scratch[hi + 1u] = difference.y;
|
| 204 |
+
|
| 205 |
+
{% else %}
|
| 206 |
+
let count = transforms * NUM_BINS;
|
| 207 |
+
if (flat >= count) { return; }
|
| 208 |
+
let transform = flat / NUM_BINS;
|
| 209 |
+
let bin = flat - transform * NUM_BINS;
|
| 210 |
+
let src = (transform * N + bin) * 2u;
|
| 211 |
+
let dst = (transform * NUM_BINS + bin) * 2u;
|
| 212 |
+
output[dst] = scratch[src];
|
| 213 |
+
output[dst + 1u] = scratch[src + 1u];
|
| 214 |
+
{% endif %}
|
| 215 |
+
}
|
build/webgpu/fft-radix2-dit-storage.wgsl.jinja
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// Portable single-dispatch radix-2 DIT FFT for transforms too large for the
|
| 2 |
+
// guaranteed workgroup-storage budget. One workgroup owns each transform and
|
| 3 |
+
// uses either its full-spectrum output or a private global scratch row as an
|
| 4 |
+
// in-place complex buffer. Uniform storageBarrier calls replace the global
|
| 5 |
+
// barrier that would otherwise require one dispatch per FFT stage.
|
| 6 |
+
//
|
| 7 |
+
// Direct output is used only when each transform owns a contiguous full-spectrum
|
| 8 |
+
// row. Scratch-backed DFT supports one-sided output. STFT accepts real or
|
| 9 |
+
// complex windowed frames and copies NUM_BINS after the final stage.
|
| 10 |
+
{{ env.wgsl.resourceDeclarations }}
|
| 11 |
+
{% set STFT = source.mode == "stft" %}
|
| 12 |
+
|
| 13 |
+
const PI: f32 = 3.141592653589793;
|
| 14 |
+
const N: u32 = {{ source.fftLength }}u;
|
| 15 |
+
const N_HALF: u32 = N / 2u;
|
| 16 |
+
const LOG2_N: u32 = {{ source.log2Length }}u;
|
| 17 |
+
const NUM_BINS: u32 = {{ source.numBins }}u;
|
| 18 |
+
const WG: u32 = {{ tunables.WORKGROUP_SIZE }}u;
|
| 19 |
+
const SIGN: f32 = {{ "1.0" if source.inverse else "-1.0" }};
|
| 20 |
+
|
| 21 |
+
@compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
|
| 22 |
+
fn main(
|
| 23 |
+
@builtin(workgroup_id) wgid: vec3<u32>,
|
| 24 |
+
@builtin(local_invocation_id) lid: vec3<u32>,
|
| 25 |
+
@builtin(num_workgroups) nwg: vec3<u32>,
|
| 26 |
+
) {
|
| 27 |
+
let tid = lid.x;
|
| 28 |
+
let transform = wgid.x + wgid.y * nwg.x;
|
| 29 |
+
let transformBase = transform * N * 2u;
|
| 30 |
+
|
| 31 |
+
let batch = transform / params.numFrames;
|
| 32 |
+
let frame = transform - batch * params.numFrames;
|
| 33 |
+
let start = frame * params.frameStep;
|
| 34 |
+
|
| 35 |
+
for (var n = tid; n < N; n = n + WG) {
|
| 36 |
+
var value = vec2<f32>(0.0);
|
| 37 |
+
let signalIndex = start + n;
|
| 38 |
+
let inputBase = (batch * params.signalLength + signalIndex) * {{ source.inComp }}u;
|
| 39 |
+
let w = window[n];
|
| 40 |
+
value.x = signal[inputBase] * w;
|
| 41 |
+
{% if source.inComp == 2 %}
|
| 42 |
+
value.y = signal[inputBase + 1u] * w;
|
| 43 |
+
{% endif %}
|
| 44 |
+
let reversed = reverseBits(n) >> (32u - LOG2_N);
|
| 45 |
+
let dst = transformBase + reversed * 2u;
|
| 46 |
+
scratch[dst] = value.x;
|
| 47 |
+
scratch[dst + 1u] = value.y;
|
| 48 |
+
}
|
| 49 |
+
storageBarrier();
|
| 50 |
+
|
| 51 |
+
{% for stage in range(1, source.log2Length + 1) %}
|
| 52 |
+
{
|
| 53 |
+
const SPAN: u32 = 1u << {{ stage }}u;
|
| 54 |
+
const HALF: u32 = SPAN >> 1u;
|
| 55 |
+
for (var butterfly = tid; butterfly < N_HALF; butterfly = butterfly + WG) {
|
| 56 |
+
let group = butterfly / HALF;
|
| 57 |
+
let j = butterfly - group * HALF;
|
| 58 |
+
let lo = transformBase + (group * SPAN + j) * 2u;
|
| 59 |
+
let hi = lo + HALF * 2u;
|
| 60 |
+
let a = vec2<f32>(scratch[lo], scratch[lo + 1u]);
|
| 61 |
+
let b = vec2<f32>(scratch[hi], scratch[hi + 1u]);
|
| 62 |
+
let angle = SIGN * 2.0 * PI * f32(j) / f32(SPAN);
|
| 63 |
+
let c = cos(angle);
|
| 64 |
+
let sn = sin(angle);
|
| 65 |
+
let t = vec2<f32>(b.x * c - b.y * sn, b.x * sn + b.y * c);
|
| 66 |
+
let sum = a + t;
|
| 67 |
+
let difference = a - t;
|
| 68 |
+
scratch[lo] = sum.x;
|
| 69 |
+
scratch[lo + 1u] = sum.y;
|
| 70 |
+
scratch[hi] = difference.x;
|
| 71 |
+
scratch[hi + 1u] = difference.y;
|
| 72 |
+
}
|
| 73 |
+
storageBarrier();
|
| 74 |
+
}
|
| 75 |
+
{% endfor %}
|
| 76 |
+
|
| 77 |
+
const SCALE: f32 = 1.0;
|
| 78 |
+
for (var k = tid; k < NUM_BINS; k = k + WG) {
|
| 79 |
+
let src = transformBase + k * 2u;
|
| 80 |
+
let dst = (transform * NUM_BINS + k) * 2u;
|
| 81 |
+
output[dst] = scratch[src] * SCALE;
|
| 82 |
+
output[dst + 1u] = scratch[src + 1u] * SCALE;
|
| 83 |
+
}
|
| 84 |
+
}
|
build/webgpu/fft-real-packed-stockham-shared.wgsl.jinja
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{{ env.wgsl.resourceDeclarations }}
|
| 2 |
+
|
| 3 |
+
// Real-input RFFT specialization. Pack even/odd samples into one N/2-point
|
| 4 |
+
// complex transform, then reconstruct the N/2+1 non-redundant bins. This cuts
|
| 5 |
+
// the Stockham butterfly count almost in half while preserving the same fused
|
| 6 |
+
// window load as the full complex STFT kernel.
|
| 7 |
+
const N: u32 = {{ source.fftLength }}u;
|
| 8 |
+
const PACKED_N: u32 = N / 2u;
|
| 9 |
+
const PACKED_HALF: u32 = PACKED_N / 2u;
|
| 10 |
+
const NUM_BINS: u32 = PACKED_N + 1u;
|
| 11 |
+
const FFT_LANES: u32 = {{ tunables.WORKGROUP_SIZE }}u;
|
| 12 |
+
const TRANSFORMS_PER_WG: u32 = {{ source.transformsPerWorkgroup | default(1) }}u;
|
| 13 |
+
const WG: u32 = FFT_LANES * TRANSFORMS_PER_WG;
|
| 14 |
+
const STAGE_TWIDDLES: u32 = PACKED_N - 1u;
|
| 15 |
+
const SHARED_STRIDE: u32 = 2u * PACKED_N;
|
| 16 |
+
|
| 17 |
+
var<workgroup> shRe: array<f32, SHARED_STRIDE * TRANSFORMS_PER_WG>;
|
| 18 |
+
var<workgroup> shIm: array<f32, SHARED_STRIDE * TRANSFORMS_PER_WG>;
|
| 19 |
+
|
| 20 |
+
fn loadPacked(frameBase: u32, packed: u32, validTransform: bool) -> vec2<f32> {
|
| 21 |
+
let even = 2u * packed;
|
| 22 |
+
let odd = even + 1u;
|
| 23 |
+
var value = vec2<f32>(0.0);
|
| 24 |
+
if (validTransform) {
|
| 25 |
+
value.x = signal[frameBase + even] * window[even];
|
| 26 |
+
value.y = signal[frameBase + odd] * window[odd];
|
| 27 |
+
}
|
| 28 |
+
return value;
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
@compute @workgroup_size(WG)
|
| 32 |
+
fn main(@builtin(workgroup_id) wgid: vec3<u32>,
|
| 33 |
+
@builtin(local_invocation_id) lid: vec3<u32>) {
|
| 34 |
+
let transform = lid.x / FFT_LANES;
|
| 35 |
+
let tid = lid.x - transform * FFT_LANES;
|
| 36 |
+
let batch = wgid.y;
|
| 37 |
+
let frame = wgid.x * TRANSFORMS_PER_WG + transform;
|
| 38 |
+
let validTransform = frame < params.numFrames;
|
| 39 |
+
let start = frame * params.frameStep;
|
| 40 |
+
let frameBase = batch * params.signalLength + start;
|
| 41 |
+
let sharedBase = transform * SHARED_STRIDE;
|
| 42 |
+
|
| 43 |
+
// Fuse pairs of radix-2 Stockham stages into radix-4 butterflies. An odd
|
| 44 |
+
// packed log2 length starts with one radix-2 stage, after which every stage
|
| 45 |
+
// can be paired. The second stage's root B is sufficient: A=B^2 and C=B^3.
|
| 46 |
+
// The first butterfly loads windowed samples directly, avoiding a separate
|
| 47 |
+
// shared-memory load phase and its barrier.
|
| 48 |
+
var srcOff = sharedBase + PACKED_N;
|
| 49 |
+
var dstOff = sharedBase;
|
| 50 |
+
var radix = 1u;
|
| 51 |
+
if ((countTrailingZeros(PACKED_N) & 1u) != 0u) {
|
| 52 |
+
for (var j = tid; j < PACKED_HALF; j = j + FFT_LANES) {
|
| 53 |
+
let a = loadPacked(frameBase, j, validTransform);
|
| 54 |
+
let b = loadPacked(frameBase, PACKED_HALF + j, validTransform);
|
| 55 |
+
shRe[srcOff + 2u * j] = a.x + b.x;
|
| 56 |
+
shIm[srcOff + 2u * j] = a.y + b.y;
|
| 57 |
+
shRe[srcOff + 2u * j + 1u] = a.x - b.x;
|
| 58 |
+
shIm[srcOff + 2u * j + 1u] = a.y - b.y;
|
| 59 |
+
}
|
| 60 |
+
workgroupBarrier();
|
| 61 |
+
radix = 2u;
|
| 62 |
+
} else {
|
| 63 |
+
for (var j = tid; j < PACKED_N / 4u; j = j + FFT_LANES) {
|
| 64 |
+
let x0 = loadPacked(frameBase, j, validTransform);
|
| 65 |
+
let x1 = loadPacked(frameBase, PACKED_N / 4u + j, validTransform);
|
| 66 |
+
let x2 = loadPacked(frameBase, PACKED_N / 2u + j, validTransform);
|
| 67 |
+
let x3 = loadPacked(frameBase, 3u * PACKED_N / 4u + j, validTransform);
|
| 68 |
+
let evenSum = x0 + x2;
|
| 69 |
+
let evenDiff = x0 - x2;
|
| 70 |
+
let oddSum = x1 + x3;
|
| 71 |
+
let oddDiff = x1 - x3;
|
| 72 |
+
shRe[srcOff + 4u * j] = evenSum.x + oddSum.x;
|
| 73 |
+
shIm[srcOff + 4u * j] = evenSum.y + oddSum.y;
|
| 74 |
+
shRe[srcOff + 4u * j + 1u] = evenDiff.x + oddDiff.y;
|
| 75 |
+
shIm[srcOff + 4u * j + 1u] = evenDiff.y - oddDiff.x;
|
| 76 |
+
shRe[srcOff + 4u * j + 2u] = evenSum.x - oddSum.x;
|
| 77 |
+
shIm[srcOff + 4u * j + 2u] = evenSum.y - oddSum.y;
|
| 78 |
+
shRe[srcOff + 4u * j + 3u] = evenDiff.x - oddDiff.y;
|
| 79 |
+
shIm[srcOff + 4u * j + 3u] = evenDiff.y + oddDiff.x;
|
| 80 |
+
}
|
| 81 |
+
workgroupBarrier();
|
| 82 |
+
radix = 4u;
|
| 83 |
+
}
|
| 84 |
+
for (; radix < PACKED_N; radix = radix * 4u) {
|
| 85 |
+
for (var j = tid; j < PACKED_N / 4u; j = j + FFT_LANES) {
|
| 86 |
+
let jm = j & (radix - 1u);
|
| 87 |
+
let outBase = ((j / radix) * (4u * radix)) + jm;
|
| 88 |
+
|
| 89 |
+
let x0r = shRe[srcOff + j];
|
| 90 |
+
let x0i = shIm[srcOff + j];
|
| 91 |
+
let x1r = shRe[srcOff + PACKED_N / 4u + j];
|
| 92 |
+
let x1i = shIm[srcOff + PACKED_N / 4u + j];
|
| 93 |
+
let x2r = shRe[srcOff + PACKED_N / 2u + j];
|
| 94 |
+
let x2i = shIm[srcOff + PACKED_N / 2u + j];
|
| 95 |
+
let x3r = shRe[srcOff + 3u * PACKED_N / 4u + j];
|
| 96 |
+
let x3i = shIm[srcOff + 3u * PACKED_N / 4u + j];
|
| 97 |
+
|
| 98 |
+
let twiddleIndex = 2u * radix - 1u + jm;
|
| 99 |
+
let br = twiddles[2u * twiddleIndex];
|
| 100 |
+
let bi = twiddles[2u * twiddleIndex + 1u];
|
| 101 |
+
let ar = br * br - bi * bi;
|
| 102 |
+
let ai = 2.0 * br * bi;
|
| 103 |
+
let cr = ar * br - ai * bi;
|
| 104 |
+
let ci = ar * bi + ai * br;
|
| 105 |
+
|
| 106 |
+
let u1r = x1r * br - x1i * bi;
|
| 107 |
+
let u1i = x1r * bi + x1i * br;
|
| 108 |
+
let u2r = x2r * ar - x2i * ai;
|
| 109 |
+
let u2i = x2r * ai + x2i * ar;
|
| 110 |
+
let u3r = x3r * cr - x3i * ci;
|
| 111 |
+
let u3i = x3r * ci + x3i * cr;
|
| 112 |
+
|
| 113 |
+
let evenSumR = x0r + u2r;
|
| 114 |
+
let evenSumI = x0i + u2i;
|
| 115 |
+
let evenDiffR = x0r - u2r;
|
| 116 |
+
let evenDiffI = x0i - u2i;
|
| 117 |
+
let oddSumR = u1r + u3r;
|
| 118 |
+
let oddSumI = u1i + u3i;
|
| 119 |
+
let oddDiffR = u1r - u3r;
|
| 120 |
+
let oddDiffI = u1i - u3i;
|
| 121 |
+
|
| 122 |
+
shRe[dstOff + outBase] = evenSumR + oddSumR;
|
| 123 |
+
shIm[dstOff + outBase] = evenSumI + oddSumI;
|
| 124 |
+
shRe[dstOff + outBase + radix] = evenDiffR + oddDiffI;
|
| 125 |
+
shIm[dstOff + outBase + radix] = evenDiffI - oddDiffR;
|
| 126 |
+
shRe[dstOff + outBase + 2u * radix] = evenSumR - oddSumR;
|
| 127 |
+
shIm[dstOff + outBase + 2u * radix] = evenSumI - oddSumI;
|
| 128 |
+
shRe[dstOff + outBase + 3u * radix] = evenDiffR - oddDiffI;
|
| 129 |
+
shIm[dstOff + outBase + 3u * radix] = evenDiffI + oddDiffR;
|
| 130 |
+
}
|
| 131 |
+
workgroupBarrier();
|
| 132 |
+
let swap = srcOff;
|
| 133 |
+
srcOff = dstOff;
|
| 134 |
+
dstOff = swap;
|
| 135 |
+
}
|
| 136 |
+
|
| 137 |
+
let outRow = (batch * params.numFrames + frame) * NUM_BINS;
|
| 138 |
+
for (var k = tid; validTransform && k < NUM_BINS; k = k + FFT_LANES) {
|
| 139 |
+
let packedK = k & (PACKED_N - 1u);
|
| 140 |
+
let mirrorK = (PACKED_N - packedK) & (PACKED_N - 1u);
|
| 141 |
+
let zr = shRe[srcOff + packedK];
|
| 142 |
+
let zi = shIm[srcOff + packedK];
|
| 143 |
+
let mr = shRe[srcOff + mirrorK];
|
| 144 |
+
let mi = shIm[srcOff + mirrorK];
|
| 145 |
+
let sumRe = zr + mr;
|
| 146 |
+
let sumIm = zi - mi;
|
| 147 |
+
let diffRe = zr - mr;
|
| 148 |
+
let diffIm = zi + mi;
|
| 149 |
+
let rootIndex = STAGE_TWIDDLES + k;
|
| 150 |
+
let c = twiddles[2u * rootIndex];
|
| 151 |
+
let sn = twiddles[2u * rootIndex + 1u];
|
| 152 |
+
let outBase = (outRow + k) * 2u;
|
| 153 |
+
output[outBase] = 0.5 * (sumRe + c * diffIm + sn * diffRe);
|
| 154 |
+
output[outBase + 1u] = 0.5 * (sumIm - c * diffRe + sn * diffIm);
|
| 155 |
+
}
|
| 156 |
+
}
|
build/webgpu/fft-stockham-shared.wgsl.jinja
ADDED
|
@@ -0,0 +1,292 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{{ env.wgsl.resourceDeclarations }}
|
| 2 |
+
{% set MIXED_TAIL = source.mixedRadixTail if source.mixedRadixTail is defined else false %}
|
| 3 |
+
{% set USE_TWIDDLE_TABLE = source.useTwiddleTable if source.useTwiddleTable is defined else false %}
|
| 4 |
+
{% set NEED_PRECISE_TWIDDLE = not USE_TWIDDLE_TABLE or MIXED_TAIL %}
|
| 5 |
+
{% if NEED_PRECISE_TWIDDLE %}
|
| 6 |
+
// Backend-stable unit-circle twiddles for DFT kernels. Retain the phase as a
|
| 7 |
+
// rational turn and route all polynomial evaluation through the shared precise
|
| 8 |
+
// trig core so FFT, direct DFT, STFT, and cosine windows agree exactly.
|
| 9 |
+
//
|
| 10 |
+
// phase is in [0, period). sign is -1 for a forward transform and +1 for an
|
| 11 |
+
// inverse transform. The result is (cos, sin).
|
| 12 |
+
{% set needPreciseTrigCentered = true %}
|
| 13 |
+
{% set needPreciseTrigTwoPi = true %}
|
| 14 |
+
// Backend-stable f32 sine/cosine core.
|
| 15 |
+
//
|
| 16 |
+
// Shader transcendental accuracy is implementation-defined, and some portable
|
| 17 |
+
// backends are only accurate to roughly 1e-4. Each path retains the most accurate
|
| 18 |
+
// available phase representation, reduces it to [-pi, pi], then uses these
|
| 19 |
+
// polynomials. The half-pi core is degree 13 for sine and degree 12 for cosine;
|
| 20 |
+
// truncation error is well below one f32 ULP over its documented interval.
|
| 21 |
+
{% if needPreciseTrigCentered %}
|
| 22 |
+
const PRECISE_TRIG_PI: f32 = 3.141592653589793;
|
| 23 |
+
{% endif %}
|
| 24 |
+
{% if needPreciseTrigTwoPi %}
|
| 25 |
+
const PRECISE_TRIG_TWO_PI: f32 = 6.283185307179586;
|
| 26 |
+
{% endif %}
|
| 27 |
+
{% if needPreciseTrigCentered %}
|
| 28 |
+
const PRECISE_TRIG_HALF_PI: f32 = 1.5707963267948966;
|
| 29 |
+
{% endif %}
|
| 30 |
+
|
| 31 |
+
// Input must be in [-pi/2, pi/2]. Returns (cos(x), sin(x)).
|
| 32 |
+
fn precise_sincos_half_pi(x: f32) -> vec2<f32> {
|
| 33 |
+
let x2 = x * x;
|
| 34 |
+
|
| 35 |
+
var sinPolynomial = 1.6059043836821613e-10;
|
| 36 |
+
sinPolynomial = fma(sinPolynomial, x2, -2.505210838544172e-8);
|
| 37 |
+
sinPolynomial = fma(sinPolynomial, x2, 2.7557319223985893e-6);
|
| 38 |
+
sinPolynomial = fma(sinPolynomial, x2, -1.984126984126984e-4);
|
| 39 |
+
sinPolynomial = fma(sinPolynomial, x2, 8.333333333333333e-3);
|
| 40 |
+
sinPolynomial = fma(sinPolynomial, x2, -1.6666666666666666e-1);
|
| 41 |
+
let sine = x * fma(sinPolynomial, x2, 1.0);
|
| 42 |
+
|
| 43 |
+
var cosPolynomial = 2.08767569878681e-9;
|
| 44 |
+
cosPolynomial = fma(cosPolynomial, x2, -2.755731922398589e-7);
|
| 45 |
+
cosPolynomial = fma(cosPolynomial, x2, 2.48015873015873e-5);
|
| 46 |
+
cosPolynomial = fma(cosPolynomial, x2, -1.388888888888889e-3);
|
| 47 |
+
cosPolynomial = fma(cosPolynomial, x2, 4.1666666666666664e-2);
|
| 48 |
+
cosPolynomial = fma(cosPolynomial, x2, -5.0e-1);
|
| 49 |
+
let cosine = fma(cosPolynomial, x2, 1.0);
|
| 50 |
+
|
| 51 |
+
return vec2<f32>(cosine, sine);
|
| 52 |
+
}
|
| 53 |
+
{% if needPreciseTrigCentered %}
|
| 54 |
+
|
| 55 |
+
// Input must be in [-pi, pi]. Returns (cos(x), sin(x)).
|
| 56 |
+
fn precise_sincos_centered(x: f32) -> vec2<f32> {
|
| 57 |
+
var folded = x;
|
| 58 |
+
var cosineSign = 1.0;
|
| 59 |
+
if (folded > PRECISE_TRIG_HALF_PI) {
|
| 60 |
+
folded = PRECISE_TRIG_PI - folded;
|
| 61 |
+
cosineSign = -1.0;
|
| 62 |
+
} else if (folded < -PRECISE_TRIG_HALF_PI) {
|
| 63 |
+
folded = -PRECISE_TRIG_PI - folded;
|
| 64 |
+
cosineSign = -1.0;
|
| 65 |
+
}
|
| 66 |
+
let value = precise_sincos_half_pi(folded);
|
| 67 |
+
return vec2<f32>(cosineSign * value.x, value.y);
|
| 68 |
+
}
|
| 69 |
+
{% endif %}
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
fn dft_precise_twiddle(phase: u32, period: u32, sign: f32) -> vec2<f32> {
|
| 73 |
+
let reducedPhase = phase % period;
|
| 74 |
+
if (reducedPhase == 0u) {
|
| 75 |
+
return vec2<f32>(1.0, sign * 0.0);
|
| 76 |
+
}
|
| 77 |
+
if (period % 4u == 0u) {
|
| 78 |
+
let quarter = period / 4u;
|
| 79 |
+
if (reducedPhase == quarter) {
|
| 80 |
+
return vec2<f32>(0.0, sign);
|
| 81 |
+
}
|
| 82 |
+
if (reducedPhase == 2u * quarter) {
|
| 83 |
+
return vec2<f32>(-1.0, sign * 0.0);
|
| 84 |
+
}
|
| 85 |
+
if (reducedPhase == 3u * quarter) {
|
| 86 |
+
return vec2<f32>(0.0, -sign);
|
| 87 |
+
}
|
| 88 |
+
}
|
| 89 |
+
// Center the rational turn before multiplying by 2*pi. This avoids the
|
| 90 |
+
// cancellation in `2*pi*(N-1)/N - 2*pi` for bins near the end of a row.
|
| 91 |
+
let turns = f32(reducedPhase) / f32(period);
|
| 92 |
+
let centeredTurns = turns - floor(turns + 0.5);
|
| 93 |
+
return precise_sincos_centered(sign * PRECISE_TRIG_TWO_PI * centeredTurns);
|
| 94 |
+
}
|
| 95 |
+
|
| 96 |
+
{% endif %}
|
| 97 |
+
{% set TAIL_ANCHOR = 32 %}
|
| 98 |
+
{% set REANCHOR_TAIL = MIXED_TAIL
|
| 99 |
+
and source.fftLength / source.radix2Length > TAIL_ANCHOR %}
|
| 100 |
+
|
| 101 |
+
// Radix-2 Stockham autosort FFT: one transform per workgroup, entirely in
|
| 102 |
+
// shared memory. log2(N) ping-pong stages alternate between the two halves of
|
| 103 |
+
// a 2*N scratch pair (no bit reversal; the autosort write pattern leaves the
|
| 104 |
+
// final stage in natural order). Threads stride over the N/2 butterflies of
|
| 105 |
+
// each stage with a workgroupBarrier between stages. Large transform batches
|
| 106 |
+
// may bind a compact precomputed twiddle table; smaller batches evaluate the
|
| 107 |
+
// same roots inline and avoid the extra dispatch.
|
| 108 |
+
// Mode "dft" views the input as [outer, N, inner, inComp] and the output as
|
| 109 |
+
// [outer, numBins, inner, 2]; params.inner carries the (runtime) inner count
|
| 110 |
+
// and dispatch.x = outer * inner workgroups. Mode "stft" runs one workgroup
|
| 111 |
+
// per (batch, frame): the window multiply is fused into the frame load. STFT
|
| 112 |
+
// selection admits only complete frames, so every frame load is in bounds. The
|
| 113 |
+
// inverse+onesided (IRFFT) combination is outside this specialization.
|
| 114 |
+
// Mode "logmel" runs one workgroup per spectrogram frame of
|
| 115 |
+
// a real mono signal: the frame load is centered (start - N/2) with reflection
|
| 116 |
+
// below zero and zeros at/after params.audioLength, and the epilogue folds the
|
| 117 |
+
// one-sided power spectrum through packed triangular mel bands into log10
|
| 118 |
+
// mel-major output instead of storing complex bins.
|
| 119 |
+
|
| 120 |
+
const N: u32 = {{ source.fftLength }}u;
|
| 121 |
+
const N_HALF: u32 = N / 2u;
|
| 122 |
+
{% if MIXED_TAIL %}
|
| 123 |
+
const RADIX2_N: u32 = {{ source.radix2Length }}u;
|
| 124 |
+
const RADIX2_HALF: u32 = RADIX2_N / 2u;
|
| 125 |
+
const TAIL_RADIX: u32 = N / RADIX2_N;
|
| 126 |
+
{% if REANCHOR_TAIL %}
|
| 127 |
+
const TAIL_ANCHOR: u32 = {{ TAIL_ANCHOR }}u;
|
| 128 |
+
{% endif %}
|
| 129 |
+
{% endif %}
|
| 130 |
+
const NUM_BINS: u32 = {{ source.numBins }}u;
|
| 131 |
+
const WG: u32 = {{ tunables.WORKGROUP_SIZE }}u;
|
| 132 |
+
{% if NEED_PRECISE_TWIDDLE %}
|
| 133 |
+
// Twiddle = exp(SIGN * i * 2*pi * k / span); SIGN flips for the inverse
|
| 134 |
+
// transform, exactly like the naive kernels' `sign` select.
|
| 135 |
+
const SIGN: f32 = {{ "1.0" if source.inverse else "-1.0" }};
|
| 136 |
+
{% endif %}
|
| 137 |
+
|
| 138 |
+
{% if MIXED_TAIL %}
|
| 139 |
+
// Walk consecutive powers of one unit root in each mixed-radix tail. A single
|
| 140 |
+
// backend-stable root evaluation per output bin replaces one precise
|
| 141 |
+
// polynomial evaluation per tail residue for every supported factorization.
|
| 142 |
+
fn complex_mul(a: vec2<f32>, b: vec2<f32>) -> vec2<f32> {
|
| 143 |
+
return vec2<f32>(a.x * b.x - a.y * b.y, a.x * b.y + a.y * b.x);
|
| 144 |
+
}
|
| 145 |
+
{% endif %}
|
| 146 |
+
|
| 147 |
+
// Ping-pong scratch: stage reads [srcOff, srcOff+N), writes [dstOff, dstOff+N).
|
| 148 |
+
var<workgroup> shRe: array<f32, 2u * N>;
|
| 149 |
+
var<workgroup> shIm: array<f32, 2u * N>;
|
| 150 |
+
|
| 151 |
+
@compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
|
| 152 |
+
fn main(@builtin(workgroup_id) wgid: vec3<u32>,
|
| 153 |
+
@builtin(local_invocation_id) lid: vec3<u32>,
|
| 154 |
+
@builtin(num_workgroups) nwg: vec3<u32>) {
|
| 155 |
+
let tid = lid.x;
|
| 156 |
+
// Flat workgroup index, 2D-folded: dispatches that would exceed the
|
| 157 |
+
// maxComputeWorkgroupsPerDimension limit on a single axis split the
|
| 158 |
+
// one-workgroup-per-(batch,frame) [or per-(outer,inner)] grid across x/y and
|
| 159 |
+
// recover the flat index here. Collapses to wgid.x when nwg.y == 1 (every DFT
|
| 160 |
+
// dispatch and any STFT dispatch under the per-axis limit).
|
| 161 |
+
let flat = wgid.x + wgid.y * nwg.x;
|
| 162 |
+
|
| 163 |
+
let batch = flat / params.numFrames;
|
| 164 |
+
let frame = flat - batch * params.numFrames;
|
| 165 |
+
let start = frame * params.frameStep;
|
| 166 |
+
// Windowed frame load (window multiply fused into the first read). The mixed
|
| 167 |
+
// path transposes n = n2 + TAIL_RADIX*n1 into contiguous n2 Stockham rows.
|
| 168 |
+
for (var n = tid; n < N; n = n + WG) {
|
| 169 |
+
{% if MIXED_TAIL %}
|
| 170 |
+
let sample_n = (n / RADIX2_N) + TAIL_RADIX * (n % RADIX2_N);
|
| 171 |
+
{% else %}
|
| 172 |
+
let sample_n = n;
|
| 173 |
+
{% endif %}
|
| 174 |
+
let signal_index = start + sample_n;
|
| 175 |
+
let base = (batch * params.signalLength + signal_index) * {{ source.inComp }}u;
|
| 176 |
+
{% if source.hasWindow is defined and not source.hasWindow %}
|
| 177 |
+
let w = 1.0;
|
| 178 |
+
{% else %}
|
| 179 |
+
let w = window[sample_n];
|
| 180 |
+
{% endif %}
|
| 181 |
+
let xr = signal[base] * w;
|
| 182 |
+
{% if source.inComp == 2 %}
|
| 183 |
+
let xi = signal[base + 1u] * w;
|
| 184 |
+
{% else %}
|
| 185 |
+
let xi = 0.0;
|
| 186 |
+
{% endif %}
|
| 187 |
+
shRe[n] = xr;
|
| 188 |
+
shIm[n] = xi;
|
| 189 |
+
}
|
| 190 |
+
workgroupBarrier();
|
| 191 |
+
|
| 192 |
+
// Stockham stages. Stage s (ns = 2^s) reads a = src[j], b = src[j + RADIX2_N/2],
|
| 193 |
+
// applies w = exp(SIGN*i*pi*(j mod ns)/ns) to b, and writes
|
| 194 |
+
// dst[(j div ns)*2*ns + (j mod ns)] = a + w*b
|
| 195 |
+
// dst[(j div ns)*2*ns + (j mod ns) + ns] = a - w*b
|
| 196 |
+
// |angle| <= pi, so twiddle accuracy never depends on large-argument
|
| 197 |
+
// sin/cos reduction (unlike the naive kernels' k*n angles).
|
| 198 |
+
var srcOff = 0u;
|
| 199 |
+
var dstOff = N;
|
| 200 |
+
{% if MIXED_TAIL %}
|
| 201 |
+
for (var s = 0u; (1u << s) < RADIX2_N; s = s + 1u) {
|
| 202 |
+
let ns = 1u << s;
|
| 203 |
+
for (var j = tid; j < N_HALF; j = j + WG) {
|
| 204 |
+
let sequence = j / RADIX2_HALF;
|
| 205 |
+
let local_j = j - sequence * RADIX2_HALF;
|
| 206 |
+
let sequence_base = sequence * RADIX2_N;
|
| 207 |
+
let jm = local_j & (ns - 1u);
|
| 208 |
+
let outBase = sequence_base + ((local_j >> s) << (s + 1u)) + jm;
|
| 209 |
+
let ar = shRe[srcOff + sequence_base + local_j];
|
| 210 |
+
let ai = shIm[srcOff + sequence_base + local_j];
|
| 211 |
+
let br = shRe[srcOff + sequence_base + RADIX2_HALF + local_j];
|
| 212 |
+
let bi = shIm[srcOff + sequence_base + RADIX2_HALF + local_j];
|
| 213 |
+
let twiddle = dft_precise_twiddle(jm, 2u * ns, SIGN);
|
| 214 |
+
let c = twiddle.x;
|
| 215 |
+
let sn = twiddle.y;
|
| 216 |
+
let tr = br * c - bi * sn;
|
| 217 |
+
let ti = br * sn + bi * c;
|
| 218 |
+
shRe[dstOff + outBase] = ar + tr;
|
| 219 |
+
shIm[dstOff + outBase] = ai + ti;
|
| 220 |
+
shRe[dstOff + outBase + ns] = ar - tr;
|
| 221 |
+
shIm[dstOff + outBase + ns] = ai - ti;
|
| 222 |
+
}
|
| 223 |
+
{% else %}
|
| 224 |
+
for (var s = 0u; (1u << s) < N; s = s + 1u) {
|
| 225 |
+
let ns = 1u << s;
|
| 226 |
+
for (var j = tid; j < N_HALF; j = j + WG) {
|
| 227 |
+
let jm = j & (ns - 1u);
|
| 228 |
+
let outBase = ((j >> s) << (s + 1u)) + jm;
|
| 229 |
+
let ar = shRe[srcOff + j];
|
| 230 |
+
let ai = shIm[srcOff + j];
|
| 231 |
+
let br = shRe[srcOff + N_HALF + j];
|
| 232 |
+
let bi = shIm[srcOff + N_HALF + j];
|
| 233 |
+
{% if USE_TWIDDLE_TABLE %}
|
| 234 |
+
let twiddleIndex = ns - 1u + jm;
|
| 235 |
+
let c = twiddles[2u * twiddleIndex];
|
| 236 |
+
let sn = twiddles[2u * twiddleIndex + 1u];
|
| 237 |
+
{% else %}
|
| 238 |
+
let twiddle = dft_precise_twiddle(jm, 2u * ns, SIGN);
|
| 239 |
+
let c = twiddle.x;
|
| 240 |
+
let sn = twiddle.y;
|
| 241 |
+
{% endif %}
|
| 242 |
+
let tr = br * c - bi * sn;
|
| 243 |
+
let ti = br * sn + bi * c;
|
| 244 |
+
shRe[dstOff + outBase] = ar + tr;
|
| 245 |
+
shIm[dstOff + outBase] = ai + ti;
|
| 246 |
+
shRe[dstOff + outBase + ns] = ar - tr;
|
| 247 |
+
shIm[dstOff + outBase + ns] = ai - ti;
|
| 248 |
+
}
|
| 249 |
+
{% endif %}
|
| 250 |
+
workgroupBarrier();
|
| 251 |
+
let swap = srcOff;
|
| 252 |
+
srcOff = dstOff;
|
| 253 |
+
dstOff = swap;
|
| 254 |
+
}
|
| 255 |
+
|
| 256 |
+
// The final swap leaves the natural-order result at srcOff. The 1/N inverse
|
| 257 |
+
// scale is a power-of-two reciprocal, so the multiply is exact and matches
|
| 258 |
+
// the naive kernels' division.
|
| 259 |
+
let outRow = (batch * params.numFrames + frame) * NUM_BINS;
|
| 260 |
+
for (var k = tid; k < NUM_BINS; k = k + WG) {
|
| 261 |
+
let outBase = (outRow + k) * 2u;
|
| 262 |
+
{% if MIXED_TAIL %}
|
| 263 |
+
let k1 = k % RADIX2_N;
|
| 264 |
+
var sum_re = 0.0;
|
| 265 |
+
var sum_im = 0.0;
|
| 266 |
+
let tail_step = dft_precise_twiddle(k, N, SIGN);
|
| 267 |
+
var tail_twiddle = vec2<f32>(1.0, SIGN * 0.0);
|
| 268 |
+
for (var n2 = 0u; n2 < TAIL_RADIX; n2 = n2 + 1u) {
|
| 269 |
+
{% if REANCHOR_TAIL %}
|
| 270 |
+
// Restart the walk from a precise root every TAIL_ANCHOR steps. At
|
| 271 |
+
// n2 == 0 this reproduces the initializer exactly (phase 0 returns the
|
| 272 |
+
// literal unit), so the anchored and unanchored entries agree bit
|
| 273 |
+
// for bit and only the chain length changes.
|
| 274 |
+
if (n2 % TAIL_ANCHOR == 0u) {
|
| 275 |
+
tail_twiddle = dft_precise_twiddle(k * n2, N, SIGN);
|
| 276 |
+
}
|
| 277 |
+
{% endif %}
|
| 278 |
+
let v_index = srcOff + n2 * RADIX2_N + k1;
|
| 279 |
+
let vr = shRe[v_index];
|
| 280 |
+
let vi = shIm[v_index];
|
| 281 |
+
sum_re = sum_re + vr * tail_twiddle.x - vi * tail_twiddle.y;
|
| 282 |
+
sum_im = sum_im + vr * tail_twiddle.y + vi * tail_twiddle.x;
|
| 283 |
+
tail_twiddle = complex_mul(tail_twiddle, tail_step);
|
| 284 |
+
}
|
| 285 |
+
output[outBase] = sum_re;
|
| 286 |
+
output[outBase + 1u] = sum_im;
|
| 287 |
+
{% else %}
|
| 288 |
+
output[outBase] = shRe[srcOff + k];
|
| 289 |
+
output[outBase + 1u] = shIm[srcOff + k];
|
| 290 |
+
{% endif %}
|
| 291 |
+
}
|
| 292 |
+
}
|
build/webgpu/fft-stockham-twiddles.wgsl.jinja
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{{ env.wgsl.resourceDeclarations }}
|
| 2 |
+
// Backend-stable unit-circle twiddles for DFT kernels. Retain the phase as a
|
| 3 |
+
// rational turn and route all polynomial evaluation through the shared precise
|
| 4 |
+
// trig core so FFT, direct DFT, STFT, and cosine windows agree exactly.
|
| 5 |
+
//
|
| 6 |
+
// phase is in [0, period). sign is -1 for a forward transform and +1 for an
|
| 7 |
+
// inverse transform. The result is (cos, sin).
|
| 8 |
+
// Backend-stable f32 sine/cosine core.
|
| 9 |
+
//
|
| 10 |
+
// Shader transcendental accuracy is implementation-defined, and some portable
|
| 11 |
+
// backends are only accurate to roughly 1e-4. Each path retains the most accurate
|
| 12 |
+
// available phase representation, reduces it to [-pi, pi], then uses these
|
| 13 |
+
// polynomials. The half-pi core is degree 13 for sine and degree 12 for cosine;
|
| 14 |
+
// truncation error is well below one f32 ULP over its documented interval.
|
| 15 |
+
const PRECISE_TRIG_PI: f32 = 3.141592653589793;
|
| 16 |
+
const PRECISE_TRIG_TWO_PI: f32 = 6.283185307179586;
|
| 17 |
+
const PRECISE_TRIG_HALF_PI: f32 = 1.5707963267948966;
|
| 18 |
+
|
| 19 |
+
// Input must be in [-pi/2, pi/2]. Returns (cos(x), sin(x)).
|
| 20 |
+
fn precise_sincos_half_pi(x: f32) -> vec2<f32> {
|
| 21 |
+
let x2 = x * x;
|
| 22 |
+
|
| 23 |
+
var sinPolynomial = 1.6059043836821613e-10;
|
| 24 |
+
sinPolynomial = fma(sinPolynomial, x2, -2.505210838544172e-8);
|
| 25 |
+
sinPolynomial = fma(sinPolynomial, x2, 2.7557319223985893e-6);
|
| 26 |
+
sinPolynomial = fma(sinPolynomial, x2, -1.984126984126984e-4);
|
| 27 |
+
sinPolynomial = fma(sinPolynomial, x2, 8.333333333333333e-3);
|
| 28 |
+
sinPolynomial = fma(sinPolynomial, x2, -1.6666666666666666e-1);
|
| 29 |
+
let sine = x * fma(sinPolynomial, x2, 1.0);
|
| 30 |
+
|
| 31 |
+
var cosPolynomial = 2.08767569878681e-9;
|
| 32 |
+
cosPolynomial = fma(cosPolynomial, x2, -2.755731922398589e-7);
|
| 33 |
+
cosPolynomial = fma(cosPolynomial, x2, 2.48015873015873e-5);
|
| 34 |
+
cosPolynomial = fma(cosPolynomial, x2, -1.388888888888889e-3);
|
| 35 |
+
cosPolynomial = fma(cosPolynomial, x2, 4.1666666666666664e-2);
|
| 36 |
+
cosPolynomial = fma(cosPolynomial, x2, -5.0e-1);
|
| 37 |
+
let cosine = fma(cosPolynomial, x2, 1.0);
|
| 38 |
+
|
| 39 |
+
return vec2<f32>(cosine, sine);
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
// Input must be in [-pi, pi]. Returns (cos(x), sin(x)).
|
| 43 |
+
fn precise_sincos_centered(x: f32) -> vec2<f32> {
|
| 44 |
+
var folded = x;
|
| 45 |
+
var cosineSign = 1.0;
|
| 46 |
+
if (folded > PRECISE_TRIG_HALF_PI) {
|
| 47 |
+
folded = PRECISE_TRIG_PI - folded;
|
| 48 |
+
cosineSign = -1.0;
|
| 49 |
+
} else if (folded < -PRECISE_TRIG_HALF_PI) {
|
| 50 |
+
folded = -PRECISE_TRIG_PI - folded;
|
| 51 |
+
cosineSign = -1.0;
|
| 52 |
+
}
|
| 53 |
+
let value = precise_sincos_half_pi(folded);
|
| 54 |
+
return vec2<f32>(cosineSign * value.x, value.y);
|
| 55 |
+
}
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
fn dft_precise_twiddle(phase: u32, period: u32, sign: f32) -> vec2<f32> {
|
| 59 |
+
let reducedPhase = phase % period;
|
| 60 |
+
if (reducedPhase == 0u) {
|
| 61 |
+
return vec2<f32>(1.0, sign * 0.0);
|
| 62 |
+
}
|
| 63 |
+
if (period % 4u == 0u) {
|
| 64 |
+
let quarter = period / 4u;
|
| 65 |
+
if (reducedPhase == quarter) {
|
| 66 |
+
return vec2<f32>(0.0, sign);
|
| 67 |
+
}
|
| 68 |
+
if (reducedPhase == 2u * quarter) {
|
| 69 |
+
return vec2<f32>(-1.0, sign * 0.0);
|
| 70 |
+
}
|
| 71 |
+
if (reducedPhase == 3u * quarter) {
|
| 72 |
+
return vec2<f32>(0.0, -sign);
|
| 73 |
+
}
|
| 74 |
+
}
|
| 75 |
+
// Center the rational turn before multiplying by 2*pi. This avoids the
|
| 76 |
+
// cancellation in `2*pi*(N-1)/N - 2*pi` for bins near the end of a row.
|
| 77 |
+
let turns = f32(reducedPhase) / f32(period);
|
| 78 |
+
let centeredTurns = turns - floor(turns + 0.5);
|
| 79 |
+
return precise_sincos_centered(sign * PRECISE_TRIG_TWO_PI * centeredTurns);
|
| 80 |
+
}
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
// One compact twiddle table shared by every transform in a large Stockham
|
| 84 |
+
// dispatch. Stage s uses ns=2^s distinct roots at table offset ns-1, matching
|
| 85 |
+
// the exact angle expression in the shared Stockham FFT path. Precomputing
|
| 86 |
+
// each root once avoids repeating sin/cos for every frame while preserving the
|
| 87 |
+
// same f32 twiddle values and butterfly arithmetic.
|
| 88 |
+
// STFT is forward-only, so the transform sign is fixed at -1.
|
| 89 |
+
const N: u32 = {{ source.fftLength }}u;
|
| 90 |
+
const SIGN: f32 = -1.0;
|
| 91 |
+
{% if source.realPacked %}
|
| 92 |
+
const PACKED_N: u32 = N / 2u;
|
| 93 |
+
const STAGE_TWIDDLES: u32 = PACKED_N - 1u;
|
| 94 |
+
|
| 95 |
+
{% endif %}
|
| 96 |
+
@compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
|
| 97 |
+
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
|
| 98 |
+
let tableIndex = gid.x;
|
| 99 |
+
{% if source.realPacked %}
|
| 100 |
+
if (tableIndex >= N) {
|
| 101 |
+
return;
|
| 102 |
+
}
|
| 103 |
+
if (tableIndex < STAGE_TWIDDLES) {
|
| 104 |
+
let ns = 1u << (31u - countLeadingZeros(tableIndex + 1u));
|
| 105 |
+
let jm = tableIndex - (ns - 1u);
|
| 106 |
+
let twiddle = dft_precise_twiddle(jm, 2u * ns, SIGN);
|
| 107 |
+
twiddles[2u * tableIndex] = twiddle.x;
|
| 108 |
+
twiddles[2u * tableIndex + 1u] = twiddle.y;
|
| 109 |
+
return;
|
| 110 |
+
}
|
| 111 |
+
let k = tableIndex - STAGE_TWIDDLES;
|
| 112 |
+
let twiddle = dft_precise_twiddle(k, N, SIGN);
|
| 113 |
+
twiddles[2u * tableIndex] = twiddle.x;
|
| 114 |
+
twiddles[2u * tableIndex + 1u] = twiddle.y;
|
| 115 |
+
{% else %}
|
| 116 |
+
if (tableIndex >= N - 1u) {
|
| 117 |
+
return;
|
| 118 |
+
}
|
| 119 |
+
let ns = 1u << (31u - countLeadingZeros(tableIndex + 1u));
|
| 120 |
+
let jm = tableIndex - (ns - 1u);
|
| 121 |
+
let twiddle = dft_precise_twiddle(jm, 2u * ns, SIGN);
|
| 122 |
+
twiddles[2u * tableIndex] = twiddle.x;
|
| 123 |
+
twiddles[2u * tableIndex + 1u] = twiddle.y;
|
| 124 |
+
{% endif %}
|
| 125 |
+
}
|
build/webgpu/manifest.json
ADDED
|
@@ -0,0 +1,644 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"domain": "ai.onnx",
|
| 3 |
+
"name": "STFT",
|
| 4 |
+
"sinceVersion": 17,
|
| 5 |
+
"description": "Computes the short-time Fourier transform (STFT) of a rank-3 real or complex signal by sliding a complete frame over the input and applying the DFT. The `frameStep` scalar represents the required positive `frame_step` input. At least one of `window` or `frameLength` is required; when both are supplied, their lengths must match. The output contains the real and imaginary components of every frequency bin for each complete frame.",
|
| 6 |
+
"inputs": [
|
| 7 |
+
{
|
| 8 |
+
"role": "signal",
|
| 9 |
+
"dtype": "T",
|
| 10 |
+
"rank": 3,
|
| 11 |
+
"description": "Real input signal with shape `(batch_size, signal_length, 1)`, or complex input with shape `(batch_size, signal_length, 2)`."
|
| 12 |
+
},
|
| 13 |
+
{
|
| 14 |
+
"role": "window",
|
| 15 |
+
"dtype": "T",
|
| 16 |
+
"rank": 1,
|
| 17 |
+
"optional": true,
|
| 18 |
+
"description": "Optional 1-D window tensor with shape `(window_length)` to multiply by each frame before the DFT."
|
| 19 |
+
}
|
| 20 |
+
],
|
| 21 |
+
"outputs": [
|
| 22 |
+
{
|
| 23 |
+
"role": "output",
|
| 24 |
+
"dtype": "T",
|
| 25 |
+
"rank": 4,
|
| 26 |
+
"description": "STFT result with shape `(batch_size, frames, dft_unique_bins, 2)` in one-sided mode or `(batch_size, frames, frame_length, 2)` in two-sided mode, with real and imaginary parts in the last dimension. `frames = floor((signal_length - frame_length) / frame_step) + 1`; partial trailing frames are not emitted.",
|
| 27 |
+
"shape": ["dim(shapes.signal, 0)", "expectedFrames", "floor(frameLength / 2) + 1 if attrs.onesided == 1 else frameLength", 2]
|
| 28 |
+
}
|
| 29 |
+
],
|
| 30 |
+
"attributes": { "onesided": 1 },
|
| 31 |
+
"attributeDescriptions": {
|
| 32 |
+
"onesided": "When 1 (default), returns `floor(n_fft / 2) + 1` non-redundant frequency bins with indices `[0, ..., floor(n_fft / 2)]` for a real-valued input (RFFT); set to 0 to return the full spectrum."
|
| 33 |
+
},
|
| 34 |
+
"attributeConstraints": { "onesided": { "values": [0, 1] } },
|
| 35 |
+
"typeConstraints": { "T": ["float32"] },
|
| 36 |
+
"args": {
|
| 37 |
+
"signal": { "kind": "tensor", "semantic": "signal", "role": "input" },
|
| 38 |
+
"window": { "kind": "tensor", "semantic": "window", "role": "input", "required": false },
|
| 39 |
+
"output": { "kind": "tensor", "semantic": "output", "role": "output" },
|
| 40 |
+
"frameStep": {
|
| 41 |
+
"kind": "i32",
|
| 42 |
+
"semantic": "frame_step",
|
| 43 |
+
"role": "attribute",
|
| 44 |
+
"description": "Positive number of signal positions between the starts of consecutive frames."
|
| 45 |
+
},
|
| 46 |
+
"frameLength": {
|
| 47 |
+
"kind": "i32",
|
| 48 |
+
"semantic": "frame_length",
|
| 49 |
+
"role": "attribute",
|
| 50 |
+
"required": false,
|
| 51 |
+
"description": "Optional number of signal values in each frame; must equal the window length when both are supplied."
|
| 52 |
+
}
|
| 53 |
+
},
|
| 54 |
+
"tunables": {
|
| 55 |
+
"WORKGROUP_SIZE": 128,
|
| 56 |
+
"TWIDDLE_TABLE_MIN_TRANSFORMS": 4096,
|
| 57 |
+
"TILED_REAL_MIN_LENGTH": 512,
|
| 58 |
+
"GLOBAL_STAGE_LOCAL_LENGTH": 256,
|
| 59 |
+
"GLOBAL_STAGE_MAX_DEVICE_STORAGE_BYTES": 16384
|
| 60 |
+
},
|
| 61 |
+
"derive": {
|
| 62 |
+
"windowLength": "dim(shapes.window, 0) if (present.window and ranks.window == 1) else 0",
|
| 63 |
+
"frameLength": "windowLength if (present.window and ranks.window == 1) else (args.frameLength if args.frameLength is defined else 0)",
|
| 64 |
+
"frameLengthMatchesWindow": "args.frameLength is undefined or not present.window or args.frameLength == windowLength",
|
| 65 |
+
"frameControlsValid": "args.frameStep > 0 and frameLength > 0 and frameLength <= dim(shapes.signal, 1) and frameLengthMatchesWindow",
|
| 66 |
+
"expectedFrames": "floor((dim(shapes.signal, 1) - frameLength) / args.frameStep) + 1 if frameControlsValid else 0",
|
| 67 |
+
"rank3OutputLayout": "ranks.signal == 3 and ranks.output == 4 and dim(shapes.output, 0) == dim(shapes.signal, 0) and dim(shapes.output, 1) == expectedFrames and dim(shapes.signal, 2) >= 1 and dim(shapes.signal, 2) <= 2 and dim(shapes.output, 3) == 2",
|
| 68 |
+
"rank3WindowLayout": "present.window and ranks.window == 1 and rank3OutputLayout and frameControlsValid",
|
| 69 |
+
"rank3WindowSpectrum": "false if not present.window else ((attrs.onesided == 0 and dim(shapes.output, 2) == dim(shapes.window, 0)) or (attrs.onesided != 0 and dim(shapes.signal, 2) == 1 and dim(shapes.output, 2) == floor(dim(shapes.window, 0) / 2) + 1))",
|
| 70 |
+
"rank3WindowDispatchFits": "dim(shapes.output, 1) <= device.limits.maxComputeWorkgroupsPerDimension and dim(shapes.output, 0) <= device.limits.maxComputeWorkgroupsPerDimension",
|
| 71 |
+
"rank3WindowSharedFftFits": "false if not present.window else (rank3WindowDispatchFits and pow2ceil(dim(shapes.window, 0)) == dim(shapes.window, 0) and dim(shapes.window, 0) >= 4 and dim(shapes.window, 0) * 16 <= device.limits.maxComputeWorkgroupStorageSize)",
|
| 72 |
+
"rank3WindowFftRadix2": "1 if not present.window else pick([[dim(shapes.window, 0) % 256 == 0, 256], [dim(shapes.window, 0) % 128 == 0, 128], [dim(shapes.window, 0) % 64 == 0, 64], [dim(shapes.window, 0) % 32 == 0, 32], [dim(shapes.window, 0) % 16 == 0, 16], [dim(shapes.window, 0) % 8 == 0, 8], [dim(shapes.window, 0) % 4 == 0, 4]], 1)",
|
| 73 |
+
"rank3WindowFftTailRadix": "1 if not present.window else dim(shapes.window, 0) / rank3WindowFftRadix2",
|
| 74 |
+
"rank3WindowMixedFftFits": "false if not present.window else (rank3WindowDispatchFits and pow2ceil(dim(shapes.window, 0)) != dim(shapes.window, 0) and rank3WindowFftRadix2 >= 4 and rank3WindowFftTailRadix > 1 and rank3WindowFftTailRadix <= 128 and dim(shapes.window, 0) * 16 <= device.limits.maxComputeWorkgroupStorageSize)",
|
| 75 |
+
"rank3WindowFftLog2": "0 if not present.window else log2ceil(dim(shapes.window, 0))",
|
| 76 |
+
"rank3WindowRadix2ScratchFits": "false if not present.window else (rank3WindowDispatchFits and pow2ceil(dim(shapes.window, 0)) == dim(shapes.window, 0) and dim(shapes.window, 0) >= 4 and dim(shapes.window, 0) * 16 > device.limits.maxComputeWorkgroupStorageSize and dim(shapes.output, 0) * dim(shapes.output, 1) > 0 and dim(shapes.output, 0) * dim(shapes.output, 1) * dim(shapes.window, 0) * 2 * 4 <= device.limits.maxStorageBufferBindingSize and dim(shapes.output, 0) * dim(shapes.output, 1) * dim(shapes.window, 0) * 2 * 4 <= device.limits.maxBufferSize)",
|
| 77 |
+
"rank3WindowInplaceFftFits": "false if not present.window else (rank3WindowDispatchFits and pow2ceil(dim(shapes.window, 0)) == dim(shapes.window, 0) and dim(shapes.window, 0) >= 4 and dim(shapes.window, 0) * 16 > device.limits.maxComputeWorkgroupStorageSize and dim(shapes.window, 0) * 8 <= device.limits.maxComputeWorkgroupStorageSize and dim(shapes.output, 0) * dim(shapes.output, 1) > 0)",
|
| 78 |
+
"rank3WindowGlobalFftFits": "false if not present.window else (rank3WindowRadix2ScratchFits and dim(shapes.window, 0) >= 2 * tunables.GLOBAL_STAGE_LOCAL_LENGTH and dim(shapes.window, 0) * 8 > device.limits.maxComputeWorkgroupStorageSize)",
|
| 79 |
+
"rank3WindowTiledRealFits": "false if not present.window else (rank3WindowDispatchFits and ranks.signal == 3 and dim(shapes.signal, 2) == 1 and dim(shapes.window, 0) >= tunables.TILED_REAL_MIN_LENGTH and dim(shapes.window, 0) * 4 <= device.limits.maxComputeWorkgroupStorageSize and dim(shapes.output, 0) > 0 and dim(shapes.output, 1) > 0 and ceilDiv(dim(shapes.output, 2), 256) <= device.limits.maxComputeWorkgroupsPerDimension)",
|
| 80 |
+
"packedFourWayFits": "false if not present.window else (device.limits.maxComputeInvocationsPerWorkgroup >= 256 and device.limits.maxComputeWorkgroupSizeX >= 256 and dim(shapes.window, 0) * 32 <= device.limits.maxComputeWorkgroupStorageSize)",
|
| 81 |
+
"packedTransformsPerWorkgroup": "4 if packedFourWayFits else 1",
|
| 82 |
+
"rank3NoWindowLayout": "not present.window and args.frameLength is defined and rank3OutputLayout and frameControlsValid",
|
| 83 |
+
"rank3NoWindowSpectrum": "(attrs.onesided == 0 and dim(shapes.output, 2) == frameLength) or (attrs.onesided != 0 and dim(shapes.signal, 2) == 1 and dim(shapes.output, 2) == floor(frameLength / 2) + 1)",
|
| 84 |
+
"rank3NoWindowSharedFftFits": "false if ranks.signal != 3 else ((attrs.onesided == 0 or (dim(shapes.signal, 2) == 1 and dim(shapes.output, 2) >= 3)) and pow2ceil(frameLength) == frameLength and frameLength >= 4 and frameLength * 16 <= device.limits.maxComputeWorkgroupStorageSize and dim(shapes.output, 1) <= device.limits.maxComputeWorkgroupsPerDimension and dim(shapes.output, 0) <= device.limits.maxComputeWorkgroupsPerDimension)"
|
| 85 |
+
},
|
| 86 |
+
"bindingSets": {
|
| 87 |
+
"stftWindowFft": [
|
| 88 |
+
{
|
| 89 |
+
"name": "signal",
|
| 90 |
+
"arg": "signal",
|
| 91 |
+
"semantic": "signal",
|
| 92 |
+
"buffer": { "type": "read-only-storage" },
|
| 93 |
+
"elementType": "f32"
|
| 94 |
+
},
|
| 95 |
+
{
|
| 96 |
+
"name": "window",
|
| 97 |
+
"arg": "window",
|
| 98 |
+
"semantic": "window",
|
| 99 |
+
"buffer": { "type": "read-only-storage" },
|
| 100 |
+
"elementType": "f32"
|
| 101 |
+
},
|
| 102 |
+
{ "name": "output", "arg": "output", "semantic": "output", "buffer": { "type": "storage" }, "elementType": "f32" },
|
| 103 |
+
{
|
| 104 |
+
"name": "params",
|
| 105 |
+
"semantic": "kernel.params",
|
| 106 |
+
"buffer": { "type": "uniform" },
|
| 107 |
+
"struct": {
|
| 108 |
+
"name": "Params",
|
| 109 |
+
"fields": [
|
| 110 |
+
{ "name": "signalLength", "type": "u32", "value": "dim(shapes.signal, 1)" },
|
| 111 |
+
{ "name": "numFrames", "type": "u32", "value": "dim(shapes.output, 1)" },
|
| 112 |
+
{ "name": "frameStep", "type": "u32", "value": "args.frameStep" }
|
| 113 |
+
]
|
| 114 |
+
}
|
| 115 |
+
}
|
| 116 |
+
],
|
| 117 |
+
"stftTwiddleWrite": [
|
| 118 |
+
{ "name": "twiddles", "semantic": "fft_twiddles", "buffer": { "type": "storage" }, "elementType": "f32" }
|
| 119 |
+
],
|
| 120 |
+
"stftWindowFftTwiddles": [
|
| 121 |
+
{
|
| 122 |
+
"name": "signal",
|
| 123 |
+
"arg": "signal",
|
| 124 |
+
"semantic": "signal",
|
| 125 |
+
"buffer": { "type": "read-only-storage" },
|
| 126 |
+
"elementType": "f32"
|
| 127 |
+
},
|
| 128 |
+
{
|
| 129 |
+
"name": "window",
|
| 130 |
+
"arg": "window",
|
| 131 |
+
"semantic": "window",
|
| 132 |
+
"buffer": { "type": "read-only-storage" },
|
| 133 |
+
"elementType": "f32"
|
| 134 |
+
},
|
| 135 |
+
{
|
| 136 |
+
"name": "twiddles",
|
| 137 |
+
"semantic": "fft_twiddles",
|
| 138 |
+
"buffer": { "type": "read-only-storage" },
|
| 139 |
+
"elementType": "f32"
|
| 140 |
+
},
|
| 141 |
+
{ "name": "output", "arg": "output", "semantic": "output", "buffer": { "type": "storage" }, "elementType": "f32" },
|
| 142 |
+
{
|
| 143 |
+
"name": "params",
|
| 144 |
+
"semantic": "kernel.params",
|
| 145 |
+
"buffer": { "type": "uniform" },
|
| 146 |
+
"struct": {
|
| 147 |
+
"name": "Params",
|
| 148 |
+
"fields": [
|
| 149 |
+
{ "name": "signalLength", "type": "u32", "value": "dim(shapes.signal, 1)" },
|
| 150 |
+
{ "name": "numFrames", "type": "u32", "value": "dim(shapes.output, 1)" },
|
| 151 |
+
{ "name": "frameStep", "type": "u32", "value": "args.frameStep" }
|
| 152 |
+
]
|
| 153 |
+
}
|
| 154 |
+
}
|
| 155 |
+
],
|
| 156 |
+
"stftNoWindowFft": [
|
| 157 |
+
{
|
| 158 |
+
"name": "signal",
|
| 159 |
+
"arg": "signal",
|
| 160 |
+
"semantic": "signal",
|
| 161 |
+
"buffer": { "type": "read-only-storage" },
|
| 162 |
+
"elementType": "f32"
|
| 163 |
+
},
|
| 164 |
+
{ "name": "output", "arg": "output", "semantic": "output", "buffer": { "type": "storage" }, "elementType": "f32" },
|
| 165 |
+
{
|
| 166 |
+
"name": "params",
|
| 167 |
+
"semantic": "kernel.params",
|
| 168 |
+
"buffer": { "type": "uniform" },
|
| 169 |
+
"struct": {
|
| 170 |
+
"name": "Params",
|
| 171 |
+
"fields": [
|
| 172 |
+
{ "name": "signalLength", "type": "u32", "value": "dim(shapes.signal, 1)" },
|
| 173 |
+
{ "name": "numFrames", "type": "u32", "value": "dim(shapes.output, 1)" },
|
| 174 |
+
{ "name": "frameStep", "type": "u32", "value": "args.frameStep" }
|
| 175 |
+
]
|
| 176 |
+
}
|
| 177 |
+
}
|
| 178 |
+
],
|
| 179 |
+
"stftWindowScratch": [
|
| 180 |
+
{
|
| 181 |
+
"name": "signal",
|
| 182 |
+
"arg": "signal",
|
| 183 |
+
"semantic": "signal",
|
| 184 |
+
"buffer": { "type": "read-only-storage" },
|
| 185 |
+
"elementType": "f32"
|
| 186 |
+
},
|
| 187 |
+
{
|
| 188 |
+
"name": "window",
|
| 189 |
+
"arg": "window",
|
| 190 |
+
"semantic": "window",
|
| 191 |
+
"buffer": { "type": "read-only-storage" },
|
| 192 |
+
"elementType": "f32"
|
| 193 |
+
},
|
| 194 |
+
{ "name": "scratch", "semantic": "fft_scratch", "buffer": { "type": "storage" }, "elementType": "f32" },
|
| 195 |
+
{ "name": "output", "arg": "output", "semantic": "output", "buffer": { "type": "storage" }, "elementType": "f32" },
|
| 196 |
+
{
|
| 197 |
+
"name": "params",
|
| 198 |
+
"semantic": "kernel.params",
|
| 199 |
+
"buffer": { "type": "uniform" },
|
| 200 |
+
"struct": {
|
| 201 |
+
"name": "Params",
|
| 202 |
+
"fields": [
|
| 203 |
+
{ "name": "signalLength", "type": "u32", "value": "dim(shapes.signal, 1)" },
|
| 204 |
+
{ "name": "numFrames", "type": "u32", "value": "dim(shapes.output, 1)" },
|
| 205 |
+
{ "name": "frameStep", "type": "u32", "value": "args.frameStep" }
|
| 206 |
+
]
|
| 207 |
+
}
|
| 208 |
+
}
|
| 209 |
+
],
|
| 210 |
+
"stftWindowScratchLocalPrefix": [
|
| 211 |
+
{
|
| 212 |
+
"name": "signal",
|
| 213 |
+
"arg": "signal",
|
| 214 |
+
"semantic": "signal",
|
| 215 |
+
"buffer": { "type": "read-only-storage" },
|
| 216 |
+
"elementType": "f32"
|
| 217 |
+
},
|
| 218 |
+
{
|
| 219 |
+
"name": "window",
|
| 220 |
+
"arg": "window",
|
| 221 |
+
"semantic": "window",
|
| 222 |
+
"buffer": { "type": "read-only-storage" },
|
| 223 |
+
"elementType": "f32"
|
| 224 |
+
},
|
| 225 |
+
{ "name": "scratch", "semantic": "fft_scratch", "buffer": { "type": "storage" }, "elementType": "f32" },
|
| 226 |
+
{
|
| 227 |
+
"name": "params",
|
| 228 |
+
"semantic": "kernel.params",
|
| 229 |
+
"buffer": { "type": "uniform" },
|
| 230 |
+
"struct": {
|
| 231 |
+
"name": "Params",
|
| 232 |
+
"fields": [
|
| 233 |
+
{ "name": "signalLength", "type": "u32", "value": "dim(shapes.signal, 1)" },
|
| 234 |
+
{ "name": "numFrames", "type": "u32", "value": "dim(shapes.output, 1)" },
|
| 235 |
+
{ "name": "frameStep", "type": "u32", "value": "args.frameStep" }
|
| 236 |
+
]
|
| 237 |
+
}
|
| 238 |
+
}
|
| 239 |
+
],
|
| 240 |
+
"stftWindowScratchStage": [
|
| 241 |
+
{ "name": "scratch", "semantic": "fft_scratch", "buffer": { "type": "storage" }, "elementType": "f32" }
|
| 242 |
+
],
|
| 243 |
+
"stftWindowScratchFinish": [
|
| 244 |
+
{ "name": "scratch", "semantic": "fft_scratch", "buffer": { "type": "read-only-storage" }, "elementType": "f32" },
|
| 245 |
+
{ "name": "output", "arg": "output", "semantic": "output", "buffer": { "type": "storage" }, "elementType": "f32" }
|
| 246 |
+
],
|
| 247 |
+
"stftWindowGeneral": [
|
| 248 |
+
{
|
| 249 |
+
"name": "signal",
|
| 250 |
+
"arg": "signal",
|
| 251 |
+
"semantic": "signal",
|
| 252 |
+
"buffer": { "type": "read-only-storage" },
|
| 253 |
+
"elementType": "f32"
|
| 254 |
+
},
|
| 255 |
+
{
|
| 256 |
+
"name": "window",
|
| 257 |
+
"arg": "window",
|
| 258 |
+
"semantic": "window",
|
| 259 |
+
"buffer": { "type": "read-only-storage" },
|
| 260 |
+
"elementType": "f32"
|
| 261 |
+
},
|
| 262 |
+
{ "name": "output", "arg": "output", "semantic": "output", "buffer": { "type": "storage" }, "elementType": "f32" },
|
| 263 |
+
{
|
| 264 |
+
"name": "params",
|
| 265 |
+
"semantic": "kernel.params",
|
| 266 |
+
"buffer": { "type": "uniform" },
|
| 267 |
+
"struct": {
|
| 268 |
+
"name": "Params",
|
| 269 |
+
"fields": [
|
| 270 |
+
{ "name": "count", "type": "u32", "value": "numel(shapes.output) / 2" },
|
| 271 |
+
{ "name": "signalLength", "type": "u32", "value": "dim(shapes.signal, 1)" },
|
| 272 |
+
{ "name": "signalComponents", "type": "u32", "value": "dim(shapes.signal, 2)" },
|
| 273 |
+
{ "name": "frameLength", "type": "u32", "value": "dim(shapes.window, 0)" },
|
| 274 |
+
{ "name": "frames", "type": "u32", "value": "dim(shapes.output, 1)" },
|
| 275 |
+
{ "name": "bins", "type": "u32", "value": "dim(shapes.output, 2)" },
|
| 276 |
+
{ "name": "frameStep", "type": "u32", "value": "args.frameStep" }
|
| 277 |
+
]
|
| 278 |
+
}
|
| 279 |
+
}
|
| 280 |
+
],
|
| 281 |
+
"stftNoWindowGeneral": [
|
| 282 |
+
{
|
| 283 |
+
"name": "signal",
|
| 284 |
+
"arg": "signal",
|
| 285 |
+
"semantic": "signal",
|
| 286 |
+
"buffer": { "type": "read-only-storage" },
|
| 287 |
+
"elementType": "f32"
|
| 288 |
+
},
|
| 289 |
+
{ "name": "output", "arg": "output", "semantic": "output", "buffer": { "type": "storage" }, "elementType": "f32" },
|
| 290 |
+
{
|
| 291 |
+
"name": "params",
|
| 292 |
+
"semantic": "kernel.params",
|
| 293 |
+
"buffer": { "type": "uniform" },
|
| 294 |
+
"struct": {
|
| 295 |
+
"name": "Params",
|
| 296 |
+
"fields": [
|
| 297 |
+
{ "name": "count", "type": "u32", "value": "numel(shapes.output) / 2" },
|
| 298 |
+
{ "name": "signalLength", "type": "u32", "value": "dim(shapes.signal, 1)" },
|
| 299 |
+
{ "name": "signalComponents", "type": "u32", "value": "dim(shapes.signal, 2)" },
|
| 300 |
+
{ "name": "frameLength", "type": "u32", "value": "args.frameLength" },
|
| 301 |
+
{ "name": "frames", "type": "u32", "value": "dim(shapes.output, 1)" },
|
| 302 |
+
{ "name": "bins", "type": "u32", "value": "dim(shapes.output, 2)" },
|
| 303 |
+
{ "name": "frameStep", "type": "u32", "value": "args.frameStep" }
|
| 304 |
+
]
|
| 305 |
+
}
|
| 306 |
+
}
|
| 307 |
+
]
|
| 308 |
+
},
|
| 309 |
+
"variants": [
|
| 310 |
+
{
|
| 311 |
+
"id": "rank3_no_window_fft",
|
| 312 |
+
"priority": 20,
|
| 313 |
+
"when": ["rank3NoWindowLayout", "rank3NoWindowSpectrum", "rank3NoWindowSharedFftFits"],
|
| 314 |
+
"passes": [
|
| 315 |
+
{
|
| 316 |
+
"id": "main",
|
| 317 |
+
"name": "STFT.NoWindowFftShared",
|
| 318 |
+
"source": {
|
| 319 |
+
"shader": "fft-stockham-shared.wgsl.jinja",
|
| 320 |
+
"inputs": {
|
| 321 |
+
"mode": "\"stft\"",
|
| 322 |
+
"fftLength": "frameLength",
|
| 323 |
+
"numBins": "dim(shapes.output, 2)",
|
| 324 |
+
"inComp": "dim(shapes.signal, 2)",
|
| 325 |
+
"inverse": false,
|
| 326 |
+
"hasWindow": false
|
| 327 |
+
}
|
| 328 |
+
},
|
| 329 |
+
"bindings": "stftNoWindowFft",
|
| 330 |
+
"dispatch": { "x": "dim(shapes.output, 1)", "y": "dim(shapes.output, 0)" }
|
| 331 |
+
}
|
| 332 |
+
]
|
| 333 |
+
},
|
| 334 |
+
{
|
| 335 |
+
"id": "rank3_window_fft_shared_inplace",
|
| 336 |
+
"priority": 32,
|
| 337 |
+
"requires": { "limits": { "maxComputeInvocationsPerWorkgroup": 256, "maxComputeWorkgroupSizeX": 256 } },
|
| 338 |
+
"tunables": { "WORKGROUP_SIZE": 256 },
|
| 339 |
+
"when": ["rank3WindowLayout", "rank3WindowSpectrum", "rank3WindowInplaceFftFits"],
|
| 340 |
+
"passes": [
|
| 341 |
+
{
|
| 342 |
+
"id": "main",
|
| 343 |
+
"name": "STFT.FftSharedInplace",
|
| 344 |
+
"source": {
|
| 345 |
+
"shader": "fft-radix2-dit-shared-inplace.wgsl.jinja",
|
| 346 |
+
"inputs": {
|
| 347 |
+
"fftLength": "dim(shapes.window, 0)",
|
| 348 |
+
"log2Length": "rank3WindowFftLog2",
|
| 349 |
+
"numBins": "dim(shapes.output, 2)",
|
| 350 |
+
"inComp": "dim(shapes.signal, 2)"
|
| 351 |
+
}
|
| 352 |
+
},
|
| 353 |
+
"bindings": "stftWindowFft",
|
| 354 |
+
"dispatch": { "x": "dim(shapes.output, 1)", "y": "dim(shapes.output, 0)" }
|
| 355 |
+
}
|
| 356 |
+
]
|
| 357 |
+
},
|
| 358 |
+
{
|
| 359 |
+
"id": "rank3_window_fft_global_stages",
|
| 360 |
+
"priority": 31,
|
| 361 |
+
"requires": { "limits": { "maxComputeInvocationsPerWorkgroup": 256, "maxComputeWorkgroupSizeX": 256 } },
|
| 362 |
+
"tunables": { "WORKGROUP_SIZE": 256 },
|
| 363 |
+
"demoteWhen": ["device.limits.maxComputeWorkgroupStorageSize > tunables.GLOBAL_STAGE_MAX_DEVICE_STORAGE_BYTES"],
|
| 364 |
+
"when": ["rank3WindowLayout", "rank3WindowSpectrum", "rank3WindowGlobalFftFits"],
|
| 365 |
+
"intermediates": [
|
| 366 |
+
{
|
| 367 |
+
"id": "fft_scratch",
|
| 368 |
+
"dtype": "float32",
|
| 369 |
+
"shape": "[dim(shapes.output, 0) * dim(shapes.output, 1) * dim(shapes.window, 0) * 2]"
|
| 370 |
+
}
|
| 371 |
+
],
|
| 372 |
+
"passes": [
|
| 373 |
+
{
|
| 374 |
+
"id": "local_prefix",
|
| 375 |
+
"name": "STFT.FftLocalPrefix",
|
| 376 |
+
"source": {
|
| 377 |
+
"shader": "fft-radix2-dit-storage-pass.wgsl.jinja",
|
| 378 |
+
"inputs": {
|
| 379 |
+
"phase": "\"local_prefix\"",
|
| 380 |
+
"fftLength": "dim(shapes.window, 0)",
|
| 381 |
+
"log2Length": "rank3WindowFftLog2",
|
| 382 |
+
"localLength": "tunables.GLOBAL_STAGE_LOCAL_LENGTH",
|
| 383 |
+
"localLog2Length": "log2ceil(tunables.GLOBAL_STAGE_LOCAL_LENGTH)",
|
| 384 |
+
"numBins": "dim(shapes.output, 2)",
|
| 385 |
+
"inComp": "dim(shapes.signal, 2)"
|
| 386 |
+
}
|
| 387 |
+
},
|
| 388 |
+
"bindings": "stftWindowScratchLocalPrefix",
|
| 389 |
+
"dispatch": {
|
| 390 |
+
"workgroups": "dim(shapes.output, 0) * dim(shapes.output, 1) * dim(shapes.window, 0) / tunables.GLOBAL_STAGE_LOCAL_LENGTH"
|
| 391 |
+
}
|
| 392 |
+
},
|
| 393 |
+
{
|
| 394 |
+
"id": "stages",
|
| 395 |
+
"repeat": { "count": "rank3WindowFftLog2 - log2ceil(tunables.GLOBAL_STAGE_LOCAL_LENGTH)", "index": "stage" },
|
| 396 |
+
"passes": [
|
| 397 |
+
{
|
| 398 |
+
"id": "butterflies",
|
| 399 |
+
"name": "STFT.FftGlobalStage",
|
| 400 |
+
"source": {
|
| 401 |
+
"shader": "fft-radix2-dit-storage-pass.wgsl.jinja",
|
| 402 |
+
"inputs": {
|
| 403 |
+
"phase": "\"stage\"",
|
| 404 |
+
"stage": "repeat.stage + log2ceil(tunables.GLOBAL_STAGE_LOCAL_LENGTH) + 1",
|
| 405 |
+
"fftLength": "dim(shapes.window, 0)",
|
| 406 |
+
"log2Length": "rank3WindowFftLog2",
|
| 407 |
+
"numBins": "dim(shapes.output, 2)",
|
| 408 |
+
"inComp": "dim(shapes.signal, 2)"
|
| 409 |
+
}
|
| 410 |
+
},
|
| 411 |
+
"bindings": "stftWindowScratchStage",
|
| 412 |
+
"dispatch": {
|
| 413 |
+
"threads": "dim(shapes.output, 0) * dim(shapes.output, 1) * dim(shapes.window, 0) / 2",
|
| 414 |
+
"workgroupSize": "tunables.WORKGROUP_SIZE"
|
| 415 |
+
}
|
| 416 |
+
}
|
| 417 |
+
]
|
| 418 |
+
},
|
| 419 |
+
{
|
| 420 |
+
"id": "finish",
|
| 421 |
+
"name": "STFT.FftGlobalFinish",
|
| 422 |
+
"source": {
|
| 423 |
+
"shader": "fft-radix2-dit-storage-pass.wgsl.jinja",
|
| 424 |
+
"inputs": {
|
| 425 |
+
"phase": "\"finish\"",
|
| 426 |
+
"fftLength": "dim(shapes.window, 0)",
|
| 427 |
+
"log2Length": "rank3WindowFftLog2",
|
| 428 |
+
"numBins": "dim(shapes.output, 2)",
|
| 429 |
+
"inComp": "dim(shapes.signal, 2)"
|
| 430 |
+
}
|
| 431 |
+
},
|
| 432 |
+
"bindings": "stftWindowScratchFinish",
|
| 433 |
+
"dispatch": {
|
| 434 |
+
"threads": "dim(shapes.output, 0) * dim(shapes.output, 1) * dim(shapes.output, 2)",
|
| 435 |
+
"workgroupSize": "tunables.WORKGROUP_SIZE"
|
| 436 |
+
}
|
| 437 |
+
}
|
| 438 |
+
]
|
| 439 |
+
},
|
| 440 |
+
{
|
| 441 |
+
"id": "rank3_window_fft_storage_inplace",
|
| 442 |
+
"priority": 30,
|
| 443 |
+
"when": ["rank3WindowLayout", "rank3WindowSpectrum", "rank3WindowRadix2ScratchFits"],
|
| 444 |
+
"intermediates": [
|
| 445 |
+
{
|
| 446 |
+
"id": "fft_scratch",
|
| 447 |
+
"dtype": "float32",
|
| 448 |
+
"shape": "[dim(shapes.output, 0) * dim(shapes.output, 1) * dim(shapes.window, 0) * 2]"
|
| 449 |
+
}
|
| 450 |
+
],
|
| 451 |
+
"passes": [
|
| 452 |
+
{
|
| 453 |
+
"id": "main",
|
| 454 |
+
"name": "STFT.FftStorageInplace",
|
| 455 |
+
"source": {
|
| 456 |
+
"shader": "fft-radix2-dit-storage.wgsl.jinja",
|
| 457 |
+
"inputs": {
|
| 458 |
+
"mode": "\"stft\"",
|
| 459 |
+
"fftLength": "dim(shapes.window, 0)",
|
| 460 |
+
"log2Length": "rank3WindowFftLog2",
|
| 461 |
+
"numBins": "dim(shapes.output, 2)",
|
| 462 |
+
"inComp": "dim(shapes.signal, 2)",
|
| 463 |
+
"inverse": false
|
| 464 |
+
}
|
| 465 |
+
},
|
| 466 |
+
"bindings": "stftWindowScratch",
|
| 467 |
+
"dispatch": { "x": "dim(shapes.output, 1)", "y": "dim(shapes.output, 0)" }
|
| 468 |
+
}
|
| 469 |
+
]
|
| 470 |
+
},
|
| 471 |
+
{
|
| 472 |
+
"id": "rank3_window_tiled_real",
|
| 473 |
+
"priority": 19,
|
| 474 |
+
"requires": { "limits": { "maxComputeInvocationsPerWorkgroup": 256, "maxComputeWorkgroupSizeX": 256 } },
|
| 475 |
+
"tunables": { "WORKGROUP_SIZE": 256 },
|
| 476 |
+
"when": ["rank3WindowLayout", "rank3WindowSpectrum", "rank3WindowTiledRealFits"],
|
| 477 |
+
"passes": [
|
| 478 |
+
{
|
| 479 |
+
"id": "main",
|
| 480 |
+
"name": "STFT.TiledReal",
|
| 481 |
+
"source": {
|
| 482 |
+
"shader": "dft-tiled-real.wgsl.jinja",
|
| 483 |
+
"inputs": {
|
| 484 |
+
"mode": "\"stft\"",
|
| 485 |
+
"transformLength": "dim(shapes.window, 0)",
|
| 486 |
+
"numBins": "dim(shapes.output, 2)",
|
| 487 |
+
"workgroupSize": "tunables.WORKGROUP_SIZE"
|
| 488 |
+
}
|
| 489 |
+
},
|
| 490 |
+
"bindings": "stftWindowFft",
|
| 491 |
+
"dispatch": {
|
| 492 |
+
"x": "ceilDiv(dim(shapes.output, 2), 256)",
|
| 493 |
+
"y": "dim(shapes.output, 1)",
|
| 494 |
+
"z": "dim(shapes.output, 0)"
|
| 495 |
+
}
|
| 496 |
+
}
|
| 497 |
+
]
|
| 498 |
+
},
|
| 499 |
+
{
|
| 500 |
+
"id": "rank3_window_rfft_packed",
|
| 501 |
+
"priority": 22,
|
| 502 |
+
"tunables": { "WORKGROUP_SIZE": 64 },
|
| 503 |
+
"when": ["rank3WindowLayout", "rank3WindowSpectrum", "rank3WindowSharedFftFits", "attrs.onesided != 0", "dim(shapes.signal, 2) == 1", "dim(shapes.window, 0) >= 64", "dim(shapes.output, 0) * dim(shapes.output, 1) >= tunables.TWIDDLE_TABLE_MIN_TRANSFORMS"],
|
| 504 |
+
"intermediates": [{ "id": "fft_twiddles", "dtype": "float32", "shape": "[2 * dim(shapes.window, 0)]" }],
|
| 505 |
+
"passes": [
|
| 506 |
+
{
|
| 507 |
+
"id": "twiddles",
|
| 508 |
+
"name": "STFT.RealPackedTwiddleTable",
|
| 509 |
+
"source": {
|
| 510 |
+
"shader": "fft-stockham-twiddles.wgsl.jinja",
|
| 511 |
+
"inputs": { "fftLength": "dim(shapes.window, 0)", "realPacked": 1 }
|
| 512 |
+
},
|
| 513 |
+
"bindings": "stftTwiddleWrite",
|
| 514 |
+
"dispatch": { "threads": "dim(shapes.window, 0)", "workgroupSize": "tunables.WORKGROUP_SIZE" }
|
| 515 |
+
},
|
| 516 |
+
{
|
| 517 |
+
"id": "main",
|
| 518 |
+
"name": "STFT.RealPackedFftShared",
|
| 519 |
+
"source": {
|
| 520 |
+
"shader": "fft-real-packed-stockham-shared.wgsl.jinja",
|
| 521 |
+
"inputs": { "fftLength": "dim(shapes.window, 0)", "transformsPerWorkgroup": "packedTransformsPerWorkgroup" }
|
| 522 |
+
},
|
| 523 |
+
"bindings": "stftWindowFftTwiddles",
|
| 524 |
+
"dispatch": {
|
| 525 |
+
"x": "ceilDiv(dim(shapes.output, 1), packedTransformsPerWorkgroup)",
|
| 526 |
+
"y": "dim(shapes.output, 0)"
|
| 527 |
+
}
|
| 528 |
+
}
|
| 529 |
+
]
|
| 530 |
+
},
|
| 531 |
+
{
|
| 532 |
+
"id": "rank3_window_fft_twiddles",
|
| 533 |
+
"priority": 21,
|
| 534 |
+
"when": ["rank3WindowLayout", "rank3WindowSpectrum", "rank3WindowSharedFftFits", "dim(shapes.window, 0) >= 64", "dim(shapes.output, 0) * dim(shapes.output, 1) >= tunables.TWIDDLE_TABLE_MIN_TRANSFORMS"],
|
| 535 |
+
"intermediates": [{ "id": "fft_twiddles", "dtype": "float32", "shape": "[2 * (dim(shapes.window, 0) - 1)]" }],
|
| 536 |
+
"passes": [
|
| 537 |
+
{
|
| 538 |
+
"id": "twiddles",
|
| 539 |
+
"name": "STFT.FftTwiddleTable",
|
| 540 |
+
"source": {
|
| 541 |
+
"shader": "fft-stockham-twiddles.wgsl.jinja",
|
| 542 |
+
"inputs": { "fftLength": "dim(shapes.window, 0)", "realPacked": 0 }
|
| 543 |
+
},
|
| 544 |
+
"bindings": "stftTwiddleWrite",
|
| 545 |
+
"dispatch": { "threads": "dim(shapes.window, 0) - 1", "workgroupSize": "tunables.WORKGROUP_SIZE" }
|
| 546 |
+
},
|
| 547 |
+
{
|
| 548 |
+
"id": "main",
|
| 549 |
+
"name": "STFT.FftSharedTwiddles",
|
| 550 |
+
"source": {
|
| 551 |
+
"shader": "fft-stockham-shared.wgsl.jinja",
|
| 552 |
+
"inputs": {
|
| 553 |
+
"mode": "\"stft\"",
|
| 554 |
+
"fftLength": "dim(shapes.window, 0)",
|
| 555 |
+
"numBins": "dim(shapes.output, 2)",
|
| 556 |
+
"inComp": "dim(shapes.signal, 2)",
|
| 557 |
+
"inverse": false,
|
| 558 |
+
"useTwiddleTable": true
|
| 559 |
+
}
|
| 560 |
+
},
|
| 561 |
+
"bindings": "stftWindowFftTwiddles",
|
| 562 |
+
"dispatch": { "x": "dim(shapes.output, 1)", "y": "dim(shapes.output, 0)" }
|
| 563 |
+
}
|
| 564 |
+
]
|
| 565 |
+
},
|
| 566 |
+
{
|
| 567 |
+
"id": "rank3_window_fft_mixed",
|
| 568 |
+
"priority": 25,
|
| 569 |
+
"when": ["rank3WindowLayout", "rank3WindowSpectrum", "rank3WindowMixedFftFits"],
|
| 570 |
+
"passes": [
|
| 571 |
+
{
|
| 572 |
+
"id": "main",
|
| 573 |
+
"name": "STFT.FftMixedRecurrent",
|
| 574 |
+
"source": {
|
| 575 |
+
"shader": "fft-stockham-shared.wgsl.jinja",
|
| 576 |
+
"inputs": {
|
| 577 |
+
"mode": "\"stft\"",
|
| 578 |
+
"fftLength": "dim(shapes.window, 0)",
|
| 579 |
+
"radix2Length": "rank3WindowFftRadix2",
|
| 580 |
+
"mixedRadixTail": true,
|
| 581 |
+
"numBins": "dim(shapes.output, 2)",
|
| 582 |
+
"inComp": "dim(shapes.signal, 2)",
|
| 583 |
+
"inverse": false
|
| 584 |
+
}
|
| 585 |
+
},
|
| 586 |
+
"bindings": "stftWindowFft",
|
| 587 |
+
"dispatch": { "x": "dim(shapes.output, 1)", "y": "dim(shapes.output, 0)" }
|
| 588 |
+
}
|
| 589 |
+
]
|
| 590 |
+
},
|
| 591 |
+
{
|
| 592 |
+
"id": "rank3_window_fft",
|
| 593 |
+
"priority": 20,
|
| 594 |
+
"when": ["rank3WindowLayout", "rank3WindowSpectrum", "rank3WindowSharedFftFits"],
|
| 595 |
+
"passes": [
|
| 596 |
+
{
|
| 597 |
+
"id": "main",
|
| 598 |
+
"name": "STFT.FftShared",
|
| 599 |
+
"source": {
|
| 600 |
+
"shader": "fft-stockham-shared.wgsl.jinja",
|
| 601 |
+
"inputs": {
|
| 602 |
+
"mode": "\"stft\"",
|
| 603 |
+
"fftLength": "dim(shapes.window, 0)",
|
| 604 |
+
"numBins": "dim(shapes.output, 2)",
|
| 605 |
+
"inComp": "dim(shapes.signal, 2)",
|
| 606 |
+
"inverse": false
|
| 607 |
+
}
|
| 608 |
+
},
|
| 609 |
+
"bindings": "stftWindowFft",
|
| 610 |
+
"dispatch": { "x": "dim(shapes.output, 1)", "y": "dim(shapes.output, 0)" }
|
| 611 |
+
}
|
| 612 |
+
]
|
| 613 |
+
},
|
| 614 |
+
{
|
| 615 |
+
"id": "rank3_window",
|
| 616 |
+
"priority": 0,
|
| 617 |
+
"supersededBy": ["rank3_window_fft_shared_inplace", "rank3_window_fft_global_stages", "rank3_window_fft_storage_inplace", "rank3_window_fft_mixed", "rank3_window_rfft_packed", "rank3_window_fft_twiddles", "rank3_window_fft", "rank3_window_tiled_real"],
|
| 618 |
+
"when": ["rank3WindowLayout", "rank3WindowSpectrum"],
|
| 619 |
+
"passes": [
|
| 620 |
+
{
|
| 621 |
+
"id": "main",
|
| 622 |
+
"name": "STFT",
|
| 623 |
+
"source": { "shader": "stft-general.wgsl.jinja", "inputs": { "hasWindowInput": true } },
|
| 624 |
+
"bindings": "stftWindowGeneral",
|
| 625 |
+
"dispatch": { "threads": "numel(shapes.output) / 2", "workgroupSize": "tunables.WORKGROUP_SIZE" }
|
| 626 |
+
}
|
| 627 |
+
]
|
| 628 |
+
},
|
| 629 |
+
{
|
| 630 |
+
"id": "rank3_no_window",
|
| 631 |
+
"priority": 10,
|
| 632 |
+
"when": ["rank3NoWindowLayout", "rank3NoWindowSpectrum"],
|
| 633 |
+
"passes": [
|
| 634 |
+
{
|
| 635 |
+
"id": "main",
|
| 636 |
+
"name": "STFT.GeneralNoWindow",
|
| 637 |
+
"source": { "shader": "stft-general.wgsl.jinja", "inputs": { "hasWindowInput": false } },
|
| 638 |
+
"bindings": "stftNoWindowGeneral",
|
| 639 |
+
"dispatch": { "threads": "numel(shapes.output) / 2", "workgroupSize": "tunables.WORKGROUP_SIZE" }
|
| 640 |
+
}
|
| 641 |
+
]
|
| 642 |
+
}
|
| 643 |
+
]
|
| 644 |
+
}
|
build/webgpu/metadata.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "ai.onnx.STFT",
|
| 3 |
+
"id": "_ai_onnx_stft_webgpu_285ab84",
|
| 4 |
+
"version": 1,
|
| 5 |
+
"license": "Apache-2.0",
|
| 6 |
+
"backend": { "type": "webgpu" },
|
| 7 |
+
"digest": {
|
| 8 |
+
"algorithm": "sha256",
|
| 9 |
+
"files": {
|
| 10 |
+
"bench.json": "OKXSfrXKgDJBKuWa61pzqlE0q6XeDp3+Ac78kZJzvEs=",
|
| 11 |
+
"dft-tiled-real.wgsl.jinja": "jWXC4IENyJwpE7YFeiMBTtrmtncyfXddBe6JQBB3+h4=",
|
| 12 |
+
"fft-radix2-dit-shared-inplace.wgsl.jinja": "IGz/c29DBNlDXBpYQk9d44DHXtmORm86rJSyJxDQ4oU=",
|
| 13 |
+
"fft-radix2-dit-storage-pass.wgsl.jinja": "kDmHLr6NfPBnO2OPHYAkBgCA/dUzsHq9yIJS/v4eIpk=",
|
| 14 |
+
"fft-radix2-dit-storage.wgsl.jinja": "rlD1QCYyRVzQf4VEZTseGFYvUzjKNRneHUatxpz5Y9k=",
|
| 15 |
+
"fft-real-packed-stockham-shared.wgsl.jinja": "w9XapiIdKLjNy/3BwXrzmnBzUYWCzSvOoHE6lMQQJok=",
|
| 16 |
+
"fft-stockham-shared.wgsl.jinja": "ihNoaimYQY8F/oomzVbEh9g/g08yolKLYVQn79cZG1o=",
|
| 17 |
+
"fft-stockham-twiddles.wgsl.jinja": "6/+Ugc8XhOPl7iNTPc0Lwd0Moh8AFw+RVQZSWCxJbKY=",
|
| 18 |
+
"manifest.json": "stekrpAUb0OTNiTqln6FHfTBeHeZmISRKX/dP8eEkVU=",
|
| 19 |
+
"stft-general.wgsl.jinja": "UGbzUabSlzivMPxP2LJ2YRNK5Z6msagBugJvn3Tpu0s=",
|
| 20 |
+
"test.json": "Y05Ud3usGRQ2tsWy3twV3mndN+3FjHo3LjOwTA24Nog="
|
| 21 |
+
}
|
| 22 |
+
},
|
| 23 |
+
"provenance": { "kernel": { "sha": "2e7068faf55e7f43df740015f6d1ee49391a41c5", "dirty": false } },
|
| 24 |
+
"webgpu": { "manifestSpec": "1.0", "specialized": true, "opPath": "ops/ai.onnx.STFT" }
|
| 25 |
+
}
|
build/webgpu/stft-general.wgsl.jinja
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{% macro flat_index_2d(name="i", bound="params.count", guardInline=false, note="dispatch-limit") %}
|
| 2 |
+
{% if note == "dispatch-limit" %}
|
| 3 |
+
// 2D-folded flat index: gid.y carries the high bits past the
|
| 4 |
+
// maxComputeWorkgroupsPerDimension dispatch limit (outputs > 16.7M elements).
|
| 5 |
+
{% elif note == "limit" %}
|
| 6 |
+
// 2D-folded flat index: gid.y carries the high bits past the
|
| 7 |
+
// maxComputeWorkgroupsPerDimension limit.
|
| 8 |
+
{% elif note == "device-axis" %}
|
| 9 |
+
// The flat dispatch is folded across x/y at the device's per-axis workgroup
|
| 10 |
+
// limit; gid.y carries the high portion of the output index.
|
| 11 |
+
{% elif note == "vec4-limit" %}
|
| 12 |
+
// 2D-folded flat vec4 index: gid.y carries the high bits past the
|
| 13 |
+
// maxComputeWorkgroupsPerDimension limit (the dispatch caps x and spills into y).
|
| 14 |
+
{% elif note == "element-limit" %}
|
| 15 |
+
// 2D-folded flat element index: gid.y carries the high bits past the
|
| 16 |
+
// maxComputeWorkgroupsPerDimension limit.
|
| 17 |
+
{% elif note == "dispatch" %}
|
| 18 |
+
// 2D-folded flat index: gid.y carries the high bits past the
|
| 19 |
+
// maxComputeWorkgroupsPerDimension dispatch limit.
|
| 20 |
+
{% endif %}
|
| 21 |
+
{% if bound == "" %}
|
| 22 |
+
let {{ name }} = gid.x + gid.y * nwg.x * {{ tunables.WORKGROUP_SIZE }}u;
|
| 23 |
+
{%- elif guardInline %}
|
| 24 |
+
let {{ name }} = gid.x + gid.y * nwg.x * {{ tunables.WORKGROUP_SIZE }}u;
|
| 25 |
+
if ({{ name }} >= {{ bound }}) { return; }
|
| 26 |
+
{%- else %}
|
| 27 |
+
let {{ name }} = gid.x + gid.y * nwg.x * {{ tunables.WORKGROUP_SIZE }}u;
|
| 28 |
+
if ({{ name }} >= {{ bound }}) {
|
| 29 |
+
return;
|
| 30 |
+
}
|
| 31 |
+
{%- endif %}
|
| 32 |
+
{% endmacro %}
|
| 33 |
+
|
| 34 |
+
{{ env.wgsl.resourceDeclarations }}
|
| 35 |
+
// Backend-stable unit-circle twiddles for DFT kernels. Retain the phase as a
|
| 36 |
+
// rational turn and route all polynomial evaluation through the shared precise
|
| 37 |
+
// trig core so FFT, direct DFT, STFT, and cosine windows agree exactly.
|
| 38 |
+
//
|
| 39 |
+
// phase is in [0, period). sign is -1 for a forward transform and +1 for an
|
| 40 |
+
// inverse transform. The result is (cos, sin).
|
| 41 |
+
// Backend-stable f32 sine/cosine core.
|
| 42 |
+
//
|
| 43 |
+
// Shader transcendental accuracy is implementation-defined, and some portable
|
| 44 |
+
// backends are only accurate to roughly 1e-4. Each path retains the most accurate
|
| 45 |
+
// available phase representation, reduces it to [-pi, pi], then uses these
|
| 46 |
+
// polynomials. The half-pi core is degree 13 for sine and degree 12 for cosine;
|
| 47 |
+
// truncation error is well below one f32 ULP over its documented interval.
|
| 48 |
+
const PRECISE_TRIG_PI: f32 = 3.141592653589793;
|
| 49 |
+
const PRECISE_TRIG_TWO_PI: f32 = 6.283185307179586;
|
| 50 |
+
const PRECISE_TRIG_HALF_PI: f32 = 1.5707963267948966;
|
| 51 |
+
|
| 52 |
+
// Input must be in [-pi/2, pi/2]. Returns (cos(x), sin(x)).
|
| 53 |
+
fn precise_sincos_half_pi(x: f32) -> vec2<f32> {
|
| 54 |
+
let x2 = x * x;
|
| 55 |
+
|
| 56 |
+
var sinPolynomial = 1.6059043836821613e-10;
|
| 57 |
+
sinPolynomial = fma(sinPolynomial, x2, -2.505210838544172e-8);
|
| 58 |
+
sinPolynomial = fma(sinPolynomial, x2, 2.7557319223985893e-6);
|
| 59 |
+
sinPolynomial = fma(sinPolynomial, x2, -1.984126984126984e-4);
|
| 60 |
+
sinPolynomial = fma(sinPolynomial, x2, 8.333333333333333e-3);
|
| 61 |
+
sinPolynomial = fma(sinPolynomial, x2, -1.6666666666666666e-1);
|
| 62 |
+
let sine = x * fma(sinPolynomial, x2, 1.0);
|
| 63 |
+
|
| 64 |
+
var cosPolynomial = 2.08767569878681e-9;
|
| 65 |
+
cosPolynomial = fma(cosPolynomial, x2, -2.755731922398589e-7);
|
| 66 |
+
cosPolynomial = fma(cosPolynomial, x2, 2.48015873015873e-5);
|
| 67 |
+
cosPolynomial = fma(cosPolynomial, x2, -1.388888888888889e-3);
|
| 68 |
+
cosPolynomial = fma(cosPolynomial, x2, 4.1666666666666664e-2);
|
| 69 |
+
cosPolynomial = fma(cosPolynomial, x2, -5.0e-1);
|
| 70 |
+
let cosine = fma(cosPolynomial, x2, 1.0);
|
| 71 |
+
|
| 72 |
+
return vec2<f32>(cosine, sine);
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
// Input must be in [-pi, pi]. Returns (cos(x), sin(x)).
|
| 76 |
+
fn precise_sincos_centered(x: f32) -> vec2<f32> {
|
| 77 |
+
var folded = x;
|
| 78 |
+
var cosineSign = 1.0;
|
| 79 |
+
if (folded > PRECISE_TRIG_HALF_PI) {
|
| 80 |
+
folded = PRECISE_TRIG_PI - folded;
|
| 81 |
+
cosineSign = -1.0;
|
| 82 |
+
} else if (folded < -PRECISE_TRIG_HALF_PI) {
|
| 83 |
+
folded = -PRECISE_TRIG_PI - folded;
|
| 84 |
+
cosineSign = -1.0;
|
| 85 |
+
}
|
| 86 |
+
let value = precise_sincos_half_pi(folded);
|
| 87 |
+
return vec2<f32>(cosineSign * value.x, value.y);
|
| 88 |
+
}
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
fn dft_precise_twiddle(phase: u32, period: u32, sign: f32) -> vec2<f32> {
|
| 92 |
+
let reducedPhase = phase % period;
|
| 93 |
+
if (reducedPhase == 0u) {
|
| 94 |
+
return vec2<f32>(1.0, sign * 0.0);
|
| 95 |
+
}
|
| 96 |
+
if (period % 4u == 0u) {
|
| 97 |
+
let quarter = period / 4u;
|
| 98 |
+
if (reducedPhase == quarter) {
|
| 99 |
+
return vec2<f32>(0.0, sign);
|
| 100 |
+
}
|
| 101 |
+
if (reducedPhase == 2u * quarter) {
|
| 102 |
+
return vec2<f32>(-1.0, sign * 0.0);
|
| 103 |
+
}
|
| 104 |
+
if (reducedPhase == 3u * quarter) {
|
| 105 |
+
return vec2<f32>(0.0, -sign);
|
| 106 |
+
}
|
| 107 |
+
}
|
| 108 |
+
// Center the rational turn before multiplying by 2*pi. This avoids the
|
| 109 |
+
// cancellation in `2*pi*(N-1)/N - 2*pi` for bins near the end of a row.
|
| 110 |
+
let turns = f32(reducedPhase) / f32(period);
|
| 111 |
+
let centeredTurns = turns - floor(turns + 0.5);
|
| 112 |
+
return precise_sincos_centered(sign * PRECISE_TRIG_TWO_PI * centeredTurns);
|
| 113 |
+
}
|
| 114 |
+
|
| 115 |
+
|
| 116 |
+
// General direct STFT for rank-3 real or complex signals. The frame length is
|
| 117 |
+
// the window length when a window is bound, otherwise the adapted frame_length
|
| 118 |
+
// scalar. Window samples default to 1.0 when no window input is present.
|
| 119 |
+
|
| 120 |
+
@compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
|
| 121 |
+
fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) nwg: vec3<u32>) {
|
| 122 |
+
{{ flat_index_2d("index", guardInline=true, note="") }}
|
| 123 |
+
|
| 124 |
+
let bin = index % params.bins;
|
| 125 |
+
var t = index / params.bins;
|
| 126 |
+
let frame = t % params.frames;
|
| 127 |
+
let batch = t / params.frames;
|
| 128 |
+
let start = frame * params.frameStep;
|
| 129 |
+
let frame_length = params.frameLength;
|
| 130 |
+
|
| 131 |
+
var real = 0.0;
|
| 132 |
+
var imag = 0.0;
|
| 133 |
+
let phasePeriod = max(frame_length, 1u);
|
| 134 |
+
let phaseStep = bin % phasePeriod;
|
| 135 |
+
var phase = 0u;
|
| 136 |
+
for (var n = 0u; n < frame_length; n = n + 1u) {
|
| 137 |
+
let signal_index = start + n;
|
| 138 |
+
let signal_base = (batch * params.signalLength + signal_index) * params.signalComponents;
|
| 139 |
+
{% if source.hasWindowInput %}
|
| 140 |
+
let w = window[n];
|
| 141 |
+
{% else %}
|
| 142 |
+
let w = 1.0;
|
| 143 |
+
{% endif %}
|
| 144 |
+
let xr = signal[signal_base] * w;
|
| 145 |
+
var xi = 0.0;
|
| 146 |
+
if (params.signalComponents == 2u) {
|
| 147 |
+
xi = signal[signal_base + 1u] * w;
|
| 148 |
+
}
|
| 149 |
+
// Reduce the integer phase before conversion. The stable unit-circle
|
| 150 |
+
// approximation avoids backend-dependent residuals at exact DFT roots.
|
| 151 |
+
let twiddle = dft_precise_twiddle(phase, frame_length, -1.0);
|
| 152 |
+
let c = twiddle.x;
|
| 153 |
+
let s = twiddle.y;
|
| 154 |
+
real = real + xr * c - xi * s;
|
| 155 |
+
imag = imag + xr * s + xi * c;
|
| 156 |
+
// Overflow-safe modular increment. A direct `bin * n` wraps u32 for long
|
| 157 |
+
// transforms before the modulus is applied.
|
| 158 |
+
let distanceToWrap = phasePeriod - phaseStep;
|
| 159 |
+
phase = select(phase + phaseStep, phase - distanceToWrap, phase >= distanceToWrap);
|
| 160 |
+
}
|
| 161 |
+
let output_base = index * 2u;
|
| 162 |
+
output[output_base] = real;
|
| 163 |
+
output[output_base + 1u] = imag;
|
| 164 |
+
}
|
build/webgpu/test.json
ADDED
|
@@ -0,0 +1,847 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"op": "ai.onnx.STFT",
|
| 3 |
+
"fixtureArrays": {
|
| 4 |
+
"onnx_backend_arange128_cos_window16_input_window": [1, 0.961939766, 0.853553391, 0.691341716, 0.5, 0.308658284, 0.146446609, 0.038060234, 0, 0.038060234, 0.146446609, 0.308658284, 0.5, 0.691341716, 0.853553391, 0.961939766],
|
| 5 |
+
"ort_complex_input_batched_twosided_frame_length_input_signal": [1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 99, 0, 99, 0, 99, 0, 99, 0, 99, 0, 99, 0, 99, 0, 99, 0]
|
| 6 |
+
},
|
| 7 |
+
"cases": [
|
| 8 |
+
{
|
| 9 |
+
"name": "dispatch_cliff_rank3_over_8m_elements",
|
| 10 |
+
"attrs": { "onesided": 1 },
|
| 11 |
+
"inputs": {
|
| 12 |
+
"signal": { "dtype": "float32", "shape": [1, 2200000, 1] },
|
| 13 |
+
"window": { "dtype": "float32", "shape": [6], "data": { "kind": "constant", "value": 1.0 } }
|
| 14 |
+
},
|
| 15 |
+
"outputs": { "output": { "dtype": "float32", "shape": [1, 2199995, 4, 2], "tolerance": 0.001 } },
|
| 16 |
+
"args": { "frameStep": 1 }
|
| 17 |
+
},
|
| 18 |
+
{
|
| 19 |
+
"name": "many_frames_fft_twiddle_table_win64",
|
| 20 |
+
"provenance": {
|
| 21 |
+
"notes": "Exercises the shared twiddle-table FFT selected for thousands of independent transforms without making correctness coverage depend on the much larger dispatch-cliff benchmark allocation."
|
| 22 |
+
},
|
| 23 |
+
"inputs": {
|
| 24 |
+
"signal": { "dtype": "float32", "shape": [1, 4159, 1] },
|
| 25 |
+
"window": { "dtype": "float32", "shape": [64] }
|
| 26 |
+
},
|
| 27 |
+
"outputs": { "output": { "dtype": "float32", "shape": [1, 4096, 33, 2], "tolerance": 0.0001 } },
|
| 28 |
+
"args": { "frameStep": 1 }
|
| 29 |
+
},
|
| 30 |
+
{
|
| 31 |
+
"name": "many_frames_fft_twiddle_table_win64_twosided",
|
| 32 |
+
"provenance": {
|
| 33 |
+
"notes": "Twosided sibling of the twiddle-table selector above. The packed real-FFT kernel that otherwise wins at this transform count requires onesided != 0, so a full-spectrum output is what leaves the shared twiddle-table FFT as the selected route."
|
| 34 |
+
},
|
| 35 |
+
"attrs": { "onesided": 0 },
|
| 36 |
+
"inputs": {
|
| 37 |
+
"signal": {
|
| 38 |
+
"dtype": "float32",
|
| 39 |
+
"shape": [1, 4159, 1],
|
| 40 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.017, "cosStep": 0.029, "scale": 1.0 }
|
| 41 |
+
},
|
| 42 |
+
"window": {
|
| 43 |
+
"dtype": "float32",
|
| 44 |
+
"shape": [64],
|
| 45 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.007, "cosStep": 0.013, "scale": 0.5, "offset": 0.5 }
|
| 46 |
+
}
|
| 47 |
+
},
|
| 48 |
+
"outputs": {
|
| 49 |
+
"output": { "dtype": "float32", "shape": [1, 4096, 64, 2], "tolerance": 0.001, "relTolerance": 0.0001 }
|
| 50 |
+
},
|
| 51 |
+
"args": { "frameStep": 1 }
|
| 52 |
+
},
|
| 53 |
+
{
|
| 54 |
+
"name": "many_frames_packed_rfft_win512_radix4",
|
| 55 |
+
"provenance": {
|
| 56 |
+
"notes": "Selector and correctness lock for the packed real N=512 path. The 4097 transforms cross the twiddle-table threshold and leave one partial four-transform workgroup on capable devices."
|
| 57 |
+
},
|
| 58 |
+
"inputs": {
|
| 59 |
+
"signal": {
|
| 60 |
+
"dtype": "float32",
|
| 61 |
+
"shape": [1, 4608, 1],
|
| 62 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.017, "cosStep": 0.029, "scale": 1.0 }
|
| 63 |
+
},
|
| 64 |
+
"window": {
|
| 65 |
+
"dtype": "float32",
|
| 66 |
+
"shape": [512],
|
| 67 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.007, "cosStep": 0.013, "scale": 0.5, "offset": 0.5 }
|
| 68 |
+
}
|
| 69 |
+
},
|
| 70 |
+
"outputs": {
|
| 71 |
+
"output": { "dtype": "float32", "shape": [1, 4097, 257, 2], "tolerance": 0.01, "relTolerance": 0.0001 }
|
| 72 |
+
},
|
| 73 |
+
"args": { "frameStep": 1 }
|
| 74 |
+
},
|
| 75 |
+
{
|
| 76 |
+
"name": "real_window_onesided",
|
| 77 |
+
"inputs": {
|
| 78 |
+
"signal": {
|
| 79 |
+
"dtype": "float32",
|
| 80 |
+
"shape": [1, 6, 1],
|
| 81 |
+
"data": { "kind": "values", "values": [1.0, 2.0, 3.0, 4.0, 5.0, 6.0] }
|
| 82 |
+
},
|
| 83 |
+
"window": { "dtype": "float32", "shape": [4], "data": { "kind": "values", "values": [0.5, 1.0, 1.0, 0.5] } }
|
| 84 |
+
},
|
| 85 |
+
"outputs": { "output": { "dtype": "float32", "shape": [1, 2, 3, 2], "tolerance": 0.00002 } },
|
| 86 |
+
"args": { "frameStep": 2 }
|
| 87 |
+
},
|
| 88 |
+
{
|
| 89 |
+
"name": "real_subnormal_impulse_window_onesided_gpu_gap",
|
| 90 |
+
"skipGpu": {
|
| 91 |
+
"category": "permanent",
|
| 92 |
+
"reason": "Portable WGSL floating-point semantics do not guarantee preservation of the subnormal values required by this fixture. Backend evidence: Metal FTZ: subnormal 1e-40 impulse flushes to zero on GPU, every one-sided bin reads 0 instead of the subnormal value (tolerance 0)."
|
| 93 |
+
},
|
| 94 |
+
"provenance": {
|
| 95 |
+
"source": "onnxruntime/test/providers/cpu/signal/signal_ops_test.cc",
|
| 96 |
+
"test": "SignalOpsTest.STFTFloat",
|
| 97 |
+
"notes": "Focused amplitude edge: a one-frame rectangular STFT of a subnormal impulse should preserve that value in every real one-sided bin."
|
| 98 |
+
},
|
| 99 |
+
"attrs": { "onesided": 1 },
|
| 100 |
+
"inputs": {
|
| 101 |
+
"signal": {
|
| 102 |
+
"dtype": "float32",
|
| 103 |
+
"shape": [1, 4, 1],
|
| 104 |
+
"data": { "kind": "values", "values": [1e-40, 0.0, 0.0, 0.0] }
|
| 105 |
+
},
|
| 106 |
+
"window": { "dtype": "float32", "shape": [4], "data": { "kind": "values", "values": [1.0, 1.0, 1.0, 1.0] } }
|
| 107 |
+
},
|
| 108 |
+
"outputs": { "output": { "dtype": "float32", "shape": [1, 1, 3, 2], "tolerance": 0 } },
|
| 109 |
+
"args": { "frameStep": 1 }
|
| 110 |
+
},
|
| 111 |
+
{
|
| 112 |
+
"name": "complex_twosided",
|
| 113 |
+
"attrs": { "onesided": 0 },
|
| 114 |
+
"inputs": {
|
| 115 |
+
"signal": {
|
| 116 |
+
"dtype": "float32",
|
| 117 |
+
"shape": [1, 4, 2],
|
| 118 |
+
"data": { "kind": "values", "values": [1.0, 0.0, 2.0, 1.0, 3.0, -1.0, 4.0, 0.0] }
|
| 119 |
+
},
|
| 120 |
+
"window": { "dtype": "float32", "shape": [2], "data": { "kind": "values", "values": [1.0, 0.5] } }
|
| 121 |
+
},
|
| 122 |
+
"outputs": { "output": { "dtype": "float32", "shape": [1, 2, 2, 2], "tolerance": 0.00002 } },
|
| 123 |
+
"args": { "frameStep": 2 }
|
| 124 |
+
},
|
| 125 |
+
{
|
| 126 |
+
"name": "real_three_frames_window",
|
| 127 |
+
"inputs": {
|
| 128 |
+
"signal": {
|
| 129 |
+
"dtype": "float32",
|
| 130 |
+
"shape": [1, 5, 1],
|
| 131 |
+
"data": { "kind": "values", "values": [1.0, 2.0, 3.0, 4.0, 5.0] }
|
| 132 |
+
},
|
| 133 |
+
"window": { "dtype": "float32", "shape": [3], "data": { "kind": "values", "values": [0.25, 1.0, 0.25] } }
|
| 134 |
+
},
|
| 135 |
+
"outputs": { "output": { "dtype": "float32", "shape": [1, 3, 2, 2], "tolerance": 0.00002 } },
|
| 136 |
+
"args": { "frameStep": 1 }
|
| 137 |
+
},
|
| 138 |
+
{
|
| 139 |
+
"name": "ort_scalar_frame_step_windowed",
|
| 140 |
+
"provenance": {
|
| 141 |
+
"source": "onnxruntime/test/providers/cpu/signal/signal_ops_test.cc",
|
| 142 |
+
"test": "SignalOpsTest.STFTFloat",
|
| 143 |
+
"notes": "Compact scalar-frame-step projection of ORT's windowed STFT test."
|
| 144 |
+
},
|
| 145 |
+
"inputs": {
|
| 146 |
+
"signal": {
|
| 147 |
+
"dtype": "float32",
|
| 148 |
+
"shape": [1, 6, 1],
|
| 149 |
+
"data": { "kind": "values", "values": [1.0, 2.0, 3.0, 4.0, 5.0, 6.0] }
|
| 150 |
+
},
|
| 151 |
+
"window": { "dtype": "float32", "shape": [4], "data": { "kind": "values", "values": [1.0, 1.0, 1.0, 1.0] } }
|
| 152 |
+
},
|
| 153 |
+
"outputs": { "output": { "dtype": "float32", "shape": [1, 2, 3, 2], "tolerance": 0.00002 } },
|
| 154 |
+
"args": { "frameStep": 2 }
|
| 155 |
+
},
|
| 156 |
+
{
|
| 157 |
+
"name": "real_twosided_window",
|
| 158 |
+
"attrs": { "onesided": 0 },
|
| 159 |
+
"inputs": {
|
| 160 |
+
"signal": {
|
| 161 |
+
"dtype": "float32",
|
| 162 |
+
"shape": [1, 6, 1],
|
| 163 |
+
"data": { "kind": "values", "values": [0.0, 1.0, 0.0, -1.0, 0.0, 1.0] }
|
| 164 |
+
},
|
| 165 |
+
"window": { "dtype": "float32", "shape": [4], "data": { "kind": "values", "values": [1.0, 0.5, 0.5, 1.0] } }
|
| 166 |
+
},
|
| 167 |
+
"outputs": { "output": { "dtype": "float32", "shape": [1, 2, 4, 2], "tolerance": 0.00002 } },
|
| 168 |
+
"args": { "frameStep": 2 }
|
| 169 |
+
},
|
| 170 |
+
{
|
| 171 |
+
"name": "batch2_onesided_stride2_window3",
|
| 172 |
+
"inputs": {
|
| 173 |
+
"signal": {
|
| 174 |
+
"dtype": "float32",
|
| 175 |
+
"shape": [2, 5, 1],
|
| 176 |
+
"data": { "kind": "values", "values": [1.0, 2.0, 3.0, 4.0, 5.0, -1.0, 0.0, 1.0, 0.0, -1.0] }
|
| 177 |
+
},
|
| 178 |
+
"window": { "dtype": "float32", "shape": [3], "data": { "kind": "values", "values": [0.5, 1.0, 0.25] } }
|
| 179 |
+
},
|
| 180 |
+
"outputs": { "output": { "dtype": "float32", "shape": [2, 2, 2, 2], "tolerance": 0.00002 } },
|
| 181 |
+
"args": { "frameStep": 2 }
|
| 182 |
+
},
|
| 183 |
+
{
|
| 184 |
+
"name": "single_frame_full_length_onesided",
|
| 185 |
+
"inputs": {
|
| 186 |
+
"signal": {
|
| 187 |
+
"dtype": "float32",
|
| 188 |
+
"shape": [1, 4, 1],
|
| 189 |
+
"data": { "kind": "values", "values": [1.0, 0.0, -1.0, 0.0] }
|
| 190 |
+
},
|
| 191 |
+
"window": { "dtype": "float32", "shape": [4], "data": { "kind": "constant", "value": 1.0 } }
|
| 192 |
+
},
|
| 193 |
+
"outputs": { "output": { "dtype": "float32", "shape": [1, 1, 3, 2], "tolerance": 0.00002 } },
|
| 194 |
+
"args": { "frameStep": 4 }
|
| 195 |
+
},
|
| 196 |
+
{
|
| 197 |
+
"name": "ort_constant_64_window16",
|
| 198 |
+
"provenance": {
|
| 199 |
+
"source": "onnxruntime/test/providers/cpu/signal/signal_ops_test.cc",
|
| 200 |
+
"test": "SignalOpsTest.STFTFloat"
|
| 201 |
+
},
|
| 202 |
+
"inputs": {
|
| 203 |
+
"signal": { "dtype": "float32", "shape": [1, 64, 1], "data": { "kind": "constant", "value": 1.0 } },
|
| 204 |
+
"window": { "dtype": "float32", "shape": [16], "data": { "kind": "constant", "value": 1.0 } }
|
| 205 |
+
},
|
| 206 |
+
"outputs": {
|
| 207 |
+
"output": {
|
| 208 |
+
"dtype": "float32",
|
| 209 |
+
"shape": [1, 7, 9, 2],
|
| 210 |
+
"tolerance": 0.00002,
|
| 211 |
+
"data": {
|
| 212 |
+
"kind": "values",
|
| 213 |
+
"values": [16.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 16.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 16.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 16.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 16.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 16.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 16.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
|
| 214 |
+
}
|
| 215 |
+
}
|
| 216 |
+
},
|
| 217 |
+
"args": { "frameStep": 8 }
|
| 218 |
+
},
|
| 219 |
+
{
|
| 220 |
+
"name": "onnx_backend_arange128_cos_window16",
|
| 221 |
+
"provenance": { "source": "cmake/external/onnx/onnx/backend/test/data/node/test_stft_with_window" },
|
| 222 |
+
"inputs": {
|
| 223 |
+
"signal": {
|
| 224 |
+
"dtype": "float32",
|
| 225 |
+
"shape": [1, 128, 1],
|
| 226 |
+
"data": { "kind": "linspace", "start": 0.0, "end": 127.0 }
|
| 227 |
+
},
|
| 228 |
+
"window": {
|
| 229 |
+
"dtype": "float32",
|
| 230 |
+
"shape": [16],
|
| 231 |
+
"data": {
|
| 232 |
+
"kind": "values",
|
| 233 |
+
"values": { "$ref": "#/fixtureArrays/onnx_backend_arange128_cos_window16_input_window" }
|
| 234 |
+
}
|
| 235 |
+
}
|
| 236 |
+
},
|
| 237 |
+
"outputs": { "output": { "dtype": "float32", "shape": [1, 15, 9, 2], "tolerance": 0.00025 } },
|
| 238 |
+
"args": { "frameStep": 8 }
|
| 239 |
+
},
|
| 240 |
+
{
|
| 241 |
+
"name": "real_batch2_twosided_stride2",
|
| 242 |
+
"attrs": { "onesided": 0 },
|
| 243 |
+
"inputs": {
|
| 244 |
+
"signal": {
|
| 245 |
+
"dtype": "float32",
|
| 246 |
+
"shape": [2, 5, 1],
|
| 247 |
+
"data": { "kind": "values", "values": [1.0, 2.0, 3.0, 4.0, 5.0, -1.0, 0.0, 1.0, 0.0, -1.0] }
|
| 248 |
+
},
|
| 249 |
+
"window": { "dtype": "float32", "shape": [3], "data": { "kind": "values", "values": [1.0, 0.5, 0.25] } }
|
| 250 |
+
},
|
| 251 |
+
"outputs": { "output": { "dtype": "float32", "shape": [2, 2, 3, 2], "tolerance": 0.00002 } },
|
| 252 |
+
"args": { "frameStep": 2 }
|
| 253 |
+
},
|
| 254 |
+
{
|
| 255 |
+
"name": "onnx_backend_stft_rectangular_window",
|
| 256 |
+
"provenance": {
|
| 257 |
+
"source": "cmake/external/onnx/onnx/backend/test/data/node/test_stft",
|
| 258 |
+
"notes": "Official rectangular-window fixture: window is omitted and frame_length supplies the DFT size."
|
| 259 |
+
},
|
| 260 |
+
"inputs": {
|
| 261 |
+
"signal": {
|
| 262 |
+
"dtype": "float32",
|
| 263 |
+
"shape": [1, 128, 1],
|
| 264 |
+
"data": {
|
| 265 |
+
"kind": "values",
|
| 266 |
+
"values": [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0, 20.0, 21.0, 22.0, 23.0, 24.0, 25.0, 26.0, 27.0, 28.0, 29.0, 30.0, 31.0, 32.0, 33.0, 34.0, 35.0, 36.0, 37.0, 38.0, 39.0, 40.0, 41.0, 42.0, 43.0, 44.0, 45.0, 46.0, 47.0, 48.0, 49.0, 50.0, 51.0, 52.0, 53.0, 54.0, 55.0, 56.0, 57.0, 58.0, 59.0, 60.0, 61.0, 62.0, 63.0, 64.0, 65.0, 66.0, 67.0, 68.0, 69.0, 70.0, 71.0, 72.0, 73.0, 74.0, 75.0, 76.0, 77.0, 78.0, 79.0, 80.0, 81.0, 82.0, 83.0, 84.0, 85.0, 86.0, 87.0, 88.0, 89.0, 90.0, 91.0, 92.0, 93.0, 94.0, 95.0, 96.0, 97.0, 98.0, 99.0, 100.0, 101.0, 102.0, 103.0, 104.0, 105.0, 106.0, 107.0, 108.0, 109.0, 110.0, 111.0, 112.0, 113.0, 114.0, 115.0, 116.0, 117.0, 118.0, 119.0, 120.0, 121.0, 122.0, 123.0, 124.0, 125.0, 126.0, 127.0]
|
| 267 |
+
}
|
| 268 |
+
}
|
| 269 |
+
},
|
| 270 |
+
"outputs": { "output": { "dtype": "float32", "shape": [1, 15, 9, 2], "tolerance": 0.00025 } },
|
| 271 |
+
"args": { "frameStep": 8, "frameLength": 16 }
|
| 272 |
+
},
|
| 273 |
+
{
|
| 274 |
+
"name": "ort_constant_64_window16_twosided",
|
| 275 |
+
"provenance": {
|
| 276 |
+
"source": "onnxruntime/test/providers/cpu/signal/signal_ops_test.cc",
|
| 277 |
+
"test": "SignalOpsTest.STFTFloat",
|
| 278 |
+
"notes": "Two-sided projection of ORT's constant-window STFT setup; validates the full spectrum instead of the default one-sided bins."
|
| 279 |
+
},
|
| 280 |
+
"attrs": { "onesided": 0 },
|
| 281 |
+
"inputs": {
|
| 282 |
+
"signal": { "dtype": "float32", "shape": [1, 64, 1], "data": { "kind": "constant", "value": 1.0 } },
|
| 283 |
+
"window": { "dtype": "float32", "shape": [16], "data": { "kind": "constant", "value": 1.0 } }
|
| 284 |
+
},
|
| 285 |
+
"outputs": { "output": { "dtype": "float32", "shape": [1, 7, 16, 2], "tolerance": 0.00025 } },
|
| 286 |
+
"args": { "frameStep": 8 }
|
| 287 |
+
},
|
| 288 |
+
{
|
| 289 |
+
"name": "ort_frame_length_without_window",
|
| 290 |
+
"provenance": {
|
| 291 |
+
"source": "onnxruntime/test/providers/cpu/signal/signal_ops_test.cc",
|
| 292 |
+
"test": "SignalOpsTest.STFTFloat",
|
| 293 |
+
"notes": "Covers the ONNX optional-input form where frame_length is provided and the window input is omitted."
|
| 294 |
+
},
|
| 295 |
+
"inputs": {
|
| 296 |
+
"signal": {
|
| 297 |
+
"dtype": "float32",
|
| 298 |
+
"shape": [1, 6, 1],
|
| 299 |
+
"data": { "kind": "values", "values": [1.0, 2.0, 3.0, 4.0, 5.0, 6.0] }
|
| 300 |
+
}
|
| 301 |
+
},
|
| 302 |
+
"outputs": {
|
| 303 |
+
"output": {
|
| 304 |
+
"dtype": "float32",
|
| 305 |
+
"shape": [1, 2, 3, 2],
|
| 306 |
+
"tolerance": 0.00002,
|
| 307 |
+
"data": { "kind": "values", "values": [10.0, 0.0, -2.0, 2.0, -2.0, 0.0, 18.0, 0.0, -2.0, 2.0, -2.0, 0.0] }
|
| 308 |
+
}
|
| 309 |
+
},
|
| 310 |
+
"args": { "frameStep": 2, "frameLength": 4 }
|
| 311 |
+
},
|
| 312 |
+
{
|
| 313 |
+
"name": "ort_complex_input_batched_twosided_frame_length",
|
| 314 |
+
"provenance": {
|
| 315 |
+
"source": "onnxruntime/test/providers/cpu/signal/signal_ops_test.cc",
|
| 316 |
+
"test": "SignalOpsTest.STFTFloatComplexInputBatched",
|
| 317 |
+
"notes": "Compact optional-window projection of ORT's batched complex-input STFT regression."
|
| 318 |
+
},
|
| 319 |
+
"attrs": { "onesided": 0 },
|
| 320 |
+
"inputs": {
|
| 321 |
+
"signal": {
|
| 322 |
+
"dtype": "float32",
|
| 323 |
+
"shape": [2, 8, 2],
|
| 324 |
+
"data": {
|
| 325 |
+
"kind": "values",
|
| 326 |
+
"values": { "$ref": "#/fixtureArrays/ort_complex_input_batched_twosided_frame_length_input_signal" }
|
| 327 |
+
}
|
| 328 |
+
}
|
| 329 |
+
},
|
| 330 |
+
"outputs": {
|
| 331 |
+
"output": {
|
| 332 |
+
"dtype": "float32",
|
| 333 |
+
"shape": [2, 3, 4, 2],
|
| 334 |
+
"tolerance": 0.00002,
|
| 335 |
+
"data": {
|
| 336 |
+
"kind": "values",
|
| 337 |
+
"values": [4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 396.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 396.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 396.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
|
| 338 |
+
}
|
| 339 |
+
}
|
| 340 |
+
},
|
| 341 |
+
"args": { "frameStep": 2, "frameLength": 4 }
|
| 342 |
+
},
|
| 343 |
+
{
|
| 344 |
+
"name": "fft_window256_signal1024_batch2_onesided",
|
| 345 |
+
"inputs": {
|
| 346 |
+
"signal": {
|
| 347 |
+
"dtype": "float32",
|
| 348 |
+
"shape": [2, 1024, 1],
|
| 349 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.017, "cosStep": 0.029, "scale": 1.0 }
|
| 350 |
+
},
|
| 351 |
+
"window": {
|
| 352 |
+
"dtype": "float32",
|
| 353 |
+
"shape": [256],
|
| 354 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.013, "cosStep": 0.011, "scale": 0.5, "offset": 0.5 }
|
| 355 |
+
}
|
| 356 |
+
},
|
| 357 |
+
"outputs": {
|
| 358 |
+
"output": { "dtype": "float32", "shape": [2, 4, 129, 2], "tolerance": 0.001, "relTolerance": 0.0001 }
|
| 359 |
+
},
|
| 360 |
+
"args": { "frameStep": 256 }
|
| 361 |
+
},
|
| 362 |
+
{
|
| 363 |
+
"name": "fft_window512_complex_twosided_step256",
|
| 364 |
+
"attrs": { "onesided": 0 },
|
| 365 |
+
"inputs": {
|
| 366 |
+
"signal": {
|
| 367 |
+
"dtype": "float32",
|
| 368 |
+
"shape": [1, 2048, 2],
|
| 369 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.019, "cosStep": 0.023, "scale": 1.0 }
|
| 370 |
+
},
|
| 371 |
+
"window": {
|
| 372 |
+
"dtype": "float32",
|
| 373 |
+
"shape": [512],
|
| 374 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.009, "cosStep": 0.007, "scale": 0.5, "offset": 0.5 }
|
| 375 |
+
}
|
| 376 |
+
},
|
| 377 |
+
"outputs": {
|
| 378 |
+
"output": { "dtype": "float32", "shape": [1, 7, 512, 2], "tolerance": 0.01, "relTolerance": 0.0001 }
|
| 379 |
+
},
|
| 380 |
+
"args": { "frameStep": 256 }
|
| 381 |
+
},
|
| 382 |
+
{
|
| 383 |
+
"name": "ort_complex_batched_no_window_frame_length",
|
| 384 |
+
"provenance": {
|
| 385 |
+
"source": "onnxruntime/test/providers/cpu/signal/signal_ops_test.cc",
|
| 386 |
+
"test": "SignalOpsTest.STFTFloatComplexInputBatched",
|
| 387 |
+
"notes": "ORT's optional-window path uses frame_length as the rectangular-window length. This case locks the optimized no-window FFT route for batched complex input."
|
| 388 |
+
},
|
| 389 |
+
"attrs": { "onesided": 0 },
|
| 390 |
+
"inputs": {
|
| 391 |
+
"signal": {
|
| 392 |
+
"dtype": "float32",
|
| 393 |
+
"shape": [2, 8, 2],
|
| 394 |
+
"data": {
|
| 395 |
+
"kind": "values",
|
| 396 |
+
"values": { "$ref": "#/fixtureArrays/ort_complex_input_batched_twosided_frame_length_input_signal" }
|
| 397 |
+
}
|
| 398 |
+
}
|
| 399 |
+
},
|
| 400 |
+
"outputs": { "output": { "dtype": "float32", "shape": [2, 3, 4, 2], "tolerance": 0.00005 } },
|
| 401 |
+
"args": { "frameStep": 2, "frameLength": 4 }
|
| 402 |
+
},
|
| 403 |
+
{
|
| 404 |
+
"name": "ort_complex_batched_frame_offsets_twosided",
|
| 405 |
+
"provenance": {
|
| 406 |
+
"source": "onnxruntime/test/providers/cpu/signal/signal_ops_test.cc",
|
| 407 |
+
"test": "SignalOpsTest.STFTFloatComplexInputBatched",
|
| 408 |
+
"notes": "Compact projection of ORT's batched complex-input frame-offset regression."
|
| 409 |
+
},
|
| 410 |
+
"attrs": { "onesided": 0 },
|
| 411 |
+
"inputs": {
|
| 412 |
+
"signal": {
|
| 413 |
+
"dtype": "float32",
|
| 414 |
+
"shape": [2, 8, 2],
|
| 415 |
+
"data": {
|
| 416 |
+
"kind": "values",
|
| 417 |
+
"values": { "$ref": "#/fixtureArrays/ort_complex_input_batched_twosided_frame_length_input_signal" }
|
| 418 |
+
}
|
| 419 |
+
},
|
| 420 |
+
"window": { "dtype": "float32", "shape": [4], "data": { "kind": "constant", "value": 1.0 } }
|
| 421 |
+
},
|
| 422 |
+
"outputs": { "output": { "dtype": "float32", "shape": [2, 3, 4, 2], "tolerance": 0.00005 } },
|
| 423 |
+
"args": { "frameStep": 2 }
|
| 424 |
+
},
|
| 425 |
+
{
|
| 426 |
+
"name": "fft_window512_real_twosided_step256",
|
| 427 |
+
"provenance": {
|
| 428 |
+
"notes": "Real/full-spectrum coverage for the power-of-two FFT path; complements complex/twosided N=512 and real/onesided N=1024 fixtures."
|
| 429 |
+
},
|
| 430 |
+
"attrs": { "onesided": 0 },
|
| 431 |
+
"inputs": {
|
| 432 |
+
"signal": {
|
| 433 |
+
"dtype": "float32",
|
| 434 |
+
"shape": [1, 1024, 1],
|
| 435 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.017, "cosStep": 0.031, "scale": 1.0 }
|
| 436 |
+
},
|
| 437 |
+
"window": {
|
| 438 |
+
"dtype": "float32",
|
| 439 |
+
"shape": [512],
|
| 440 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.007, "cosStep": 0.013, "scale": 0.5, "offset": 0.5 }
|
| 441 |
+
}
|
| 442 |
+
},
|
| 443 |
+
"outputs": {
|
| 444 |
+
"output": { "dtype": "float32", "shape": [1, 3, 512, 2], "tolerance": 0.01, "relTolerance": 0.0001 }
|
| 445 |
+
},
|
| 446 |
+
"args": { "frameStep": 256 }
|
| 447 |
+
},
|
| 448 |
+
{
|
| 449 |
+
"name": "fft_window1024_onesided_step512",
|
| 450 |
+
"inputs": {
|
| 451 |
+
"signal": {
|
| 452 |
+
"dtype": "float32",
|
| 453 |
+
"shape": [1, 4096, 1],
|
| 454 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.011, "cosStep": 0.027, "scale": 1.0 }
|
| 455 |
+
},
|
| 456 |
+
"window": {
|
| 457 |
+
"dtype": "float32",
|
| 458 |
+
"shape": [1024],
|
| 459 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.005, "cosStep": 0.003, "scale": 0.5, "offset": 0.5 }
|
| 460 |
+
}
|
| 461 |
+
},
|
| 462 |
+
"outputs": {
|
| 463 |
+
"output": { "dtype": "float32", "shape": [1, 7, 513, 2], "tolerance": 0.004, "relTolerance": 0.0001 }
|
| 464 |
+
},
|
| 465 |
+
"args": { "frameStep": 512 }
|
| 466 |
+
},
|
| 467 |
+
{
|
| 468 |
+
"name": "fft_window4096_radix2_dit_onesided",
|
| 469 |
+
"provenance": {
|
| 470 |
+
"notes": "Locks the capability-selected 4096-point radix-2 DIT routes: workgroup-resident on devices with 32 KiB of shared storage, and globally staged/storage-backed on lower-limit devices."
|
| 471 |
+
},
|
| 472 |
+
"inputs": {
|
| 473 |
+
"signal": {
|
| 474 |
+
"dtype": "float32",
|
| 475 |
+
"shape": [1, 4096, 1],
|
| 476 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.011, "cosStep": 0.027, "scale": 1.0 }
|
| 477 |
+
},
|
| 478 |
+
"window": {
|
| 479 |
+
"dtype": "float32",
|
| 480 |
+
"shape": [4096],
|
| 481 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.005, "cosStep": 0.003, "scale": 0.5, "offset": 0.5 }
|
| 482 |
+
}
|
| 483 |
+
},
|
| 484 |
+
"outputs": {
|
| 485 |
+
"output": { "dtype": "float32", "shape": [1, 1, 2049, 2], "tolerance": 0.04, "relTolerance": 0.0002 }
|
| 486 |
+
},
|
| 487 |
+
"args": { "frameStep": 4096 }
|
| 488 |
+
},
|
| 489 |
+
{
|
| 490 |
+
"name": "fft_window2048_radix2_dit_onesided_min_storage",
|
| 491 |
+
"provenance": {
|
| 492 |
+
"notes": "The in-place radix-2 rung is selected inside a BAND — rank3WindowInplaceFftFits wants window*16 > maxComputeWorkgroupStorageSize (the two-buffer form does not fit) and window*8 <= it (the in-place one does) — so the band moves with the device and no single window covers every tier. window 4096 is the band at 32768 (the adapter here); 2048 is the band at the 16384 WebGPU guaranteed minimum, which is what the min-limits tier reports. Without this sibling the rung is rendered on every low-storage tier and executed on none of them. Deliberately no requires.limits: outside its band the case is still valid, it just selects the staged rung."
|
| 493 |
+
},
|
| 494 |
+
"inputs": {
|
| 495 |
+
"signal": {
|
| 496 |
+
"dtype": "float32",
|
| 497 |
+
"shape": [1, 2048, 1],
|
| 498 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.011, "cosStep": 0.027, "scale": 1.0 }
|
| 499 |
+
},
|
| 500 |
+
"window": {
|
| 501 |
+
"dtype": "float32",
|
| 502 |
+
"shape": [2048],
|
| 503 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.005, "cosStep": 0.003, "scale": 0.5, "offset": 0.5 }
|
| 504 |
+
}
|
| 505 |
+
},
|
| 506 |
+
"outputs": {
|
| 507 |
+
"output": { "dtype": "float32", "shape": [1, 1, 1025, 2], "tolerance": 0.04, "relTolerance": 0.0002 }
|
| 508 |
+
},
|
| 509 |
+
"args": { "frameStep": 2048 }
|
| 510 |
+
},
|
| 511 |
+
{
|
| 512 |
+
"name": "fft_window4096_radix2_dit_complex_twosided",
|
| 513 |
+
"provenance": {
|
| 514 |
+
"notes": "Exercises the complex load and full-spectrum store arms shared by the capability-selected 4096-point DIT routes."
|
| 515 |
+
},
|
| 516 |
+
"attrs": { "onesided": 0 },
|
| 517 |
+
"inputs": {
|
| 518 |
+
"signal": {
|
| 519 |
+
"dtype": "float32",
|
| 520 |
+
"shape": [1, 4096, 2],
|
| 521 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.011, "cosStep": 0.027, "scale": 0.2 }
|
| 522 |
+
},
|
| 523 |
+
"window": {
|
| 524 |
+
"dtype": "float32",
|
| 525 |
+
"shape": [4096],
|
| 526 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.005, "cosStep": 0.003, "scale": 0.5, "offset": 0.5 }
|
| 527 |
+
}
|
| 528 |
+
},
|
| 529 |
+
"outputs": {
|
| 530 |
+
"output": { "dtype": "float32", "shape": [1, 1, 4096, 2], "tolerance": 0.05, "relTolerance": 0.0003 }
|
| 531 |
+
},
|
| 532 |
+
"args": { "frameStep": 4096 }
|
| 533 |
+
},
|
| 534 |
+
{
|
| 535 |
+
"name": "empty_zero_dim",
|
| 536 |
+
"attrs": { "onesided": 1 },
|
| 537 |
+
"inputs": {
|
| 538 |
+
"signal": { "dtype": "float32", "shape": [0, 6, 1], "data": { "kind": "values", "values": [] } },
|
| 539 |
+
"window": { "dtype": "float32", "shape": [4], "data": { "kind": "values", "values": [0.5, 1.0, 1.0, 0.5] } }
|
| 540 |
+
},
|
| 541 |
+
"outputs": { "output": { "dtype": "float32", "shape": [0, 2, 3, 2], "tolerance": 0 } },
|
| 542 |
+
"args": { "frameStep": 2 }
|
| 543 |
+
},
|
| 544 |
+
{
|
| 545 |
+
"name": "mixed_stockham_n400_real_onesided_tail25",
|
| 546 |
+
"provenance": {
|
| 547 |
+
"notes": "Real onesided 400-point mixed-radix path: 16-point shared Stockham transforms over 25 residue classes, followed by the direct radix-25 tail."
|
| 548 |
+
},
|
| 549 |
+
"attrs": { "onesided": 1 },
|
| 550 |
+
"inputs": {
|
| 551 |
+
"signal": {
|
| 552 |
+
"dtype": "float32",
|
| 553 |
+
"shape": [1, 880, 1],
|
| 554 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.021, "cosStep": 0.013, "scale": 1.0 }
|
| 555 |
+
},
|
| 556 |
+
"window": {
|
| 557 |
+
"dtype": "float32",
|
| 558 |
+
"shape": [400],
|
| 559 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.0079, "cosStep": 0.0079, "scale": 0.5 }
|
| 560 |
+
}
|
| 561 |
+
},
|
| 562 |
+
"outputs": { "output": { "dtype": "float32", "shape": [1, 4, 201, 2], "tolerance": 0.01, "relTolerance": 0.001 } },
|
| 563 |
+
"args": { "frameStep": 160 }
|
| 564 |
+
},
|
| 565 |
+
{
|
| 566 |
+
"name": "mixed_stockham_n288_real_onesided_tail9",
|
| 567 |
+
"provenance": {
|
| 568 |
+
"notes": "Generic mixed-radix recurrence with a 32-point shared Stockham factor and radix-9 tail."
|
| 569 |
+
},
|
| 570 |
+
"attrs": { "onesided": 1 },
|
| 571 |
+
"inputs": {
|
| 572 |
+
"signal": {
|
| 573 |
+
"dtype": "float32",
|
| 574 |
+
"shape": [1, 432, 1],
|
| 575 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.023, "cosStep": 0.017, "scale": 0.7 }
|
| 576 |
+
},
|
| 577 |
+
"window": {
|
| 578 |
+
"dtype": "float32",
|
| 579 |
+
"shape": [288],
|
| 580 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.011, "cosStep": 0.007, "scale": 0.5, "offset": 0.5 }
|
| 581 |
+
}
|
| 582 |
+
},
|
| 583 |
+
"outputs": { "output": { "dtype": "float32", "shape": [1, 2, 145, 2], "tolerance": 0.02, "relTolerance": 0.001 } },
|
| 584 |
+
"args": { "frameStep": 144 }
|
| 585 |
+
},
|
| 586 |
+
{
|
| 587 |
+
"name": "mixed_stockham_n400_complex_twosided",
|
| 588 |
+
"provenance": { "notes": "Complex/full-spectrum branch of the 16x25 mixed-radix decomposition." },
|
| 589 |
+
"attrs": { "onesided": 0 },
|
| 590 |
+
"inputs": {
|
| 591 |
+
"signal": {
|
| 592 |
+
"dtype": "float32",
|
| 593 |
+
"shape": [1, 400, 2],
|
| 594 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.017, "cosStep": 0.031, "scale": 0.3 }
|
| 595 |
+
},
|
| 596 |
+
"window": {
|
| 597 |
+
"dtype": "float32",
|
| 598 |
+
"shape": [400],
|
| 599 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.0079, "cosStep": 0.0079, "scale": 0.5 }
|
| 600 |
+
}
|
| 601 |
+
},
|
| 602 |
+
"outputs": { "output": { "dtype": "float32", "shape": [1, 1, 400, 2], "tolerance": 0.02, "relTolerance": 0.001 } },
|
| 603 |
+
"args": { "frameStep": 400 }
|
| 604 |
+
},
|
| 605 |
+
{
|
| 606 |
+
"name": "mixed_stockham_n480_real_twosided_tail15",
|
| 607 |
+
"provenance": {
|
| 608 |
+
"notes": "Real full-spectrum 480-point mixed-radix path: 32-point shared Stockham transforms over 15 residue classes, followed by the direct radix-15 tail."
|
| 609 |
+
},
|
| 610 |
+
"attrs": { "onesided": 0 },
|
| 611 |
+
"inputs": {
|
| 612 |
+
"signal": {
|
| 613 |
+
"dtype": "float32",
|
| 614 |
+
"shape": [1, 960, 1],
|
| 615 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.017, "cosStep": 0.029, "scale": 1.0 }
|
| 616 |
+
},
|
| 617 |
+
"window": {
|
| 618 |
+
"dtype": "float32",
|
| 619 |
+
"shape": [480],
|
| 620 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.0065, "cosStep": 0.0065, "scale": 0.5 }
|
| 621 |
+
}
|
| 622 |
+
},
|
| 623 |
+
"outputs": { "output": { "dtype": "float32", "shape": [1, 3, 480, 2], "tolerance": 0.02, "relTolerance": 0.001 } },
|
| 624 |
+
"args": { "frameStep": 240 }
|
| 625 |
+
},
|
| 626 |
+
{
|
| 627 |
+
"name": "mixed_stockham_n496_real_onesided_tail31",
|
| 628 |
+
"provenance": {
|
| 629 |
+
"notes": "Generic mixed-radix recurrence at the supported tail bound: a 16-point shared Stockham factor and radix-31 tail."
|
| 630 |
+
},
|
| 631 |
+
"attrs": { "onesided": 1 },
|
| 632 |
+
"inputs": {
|
| 633 |
+
"signal": {
|
| 634 |
+
"dtype": "float32",
|
| 635 |
+
"shape": [1, 744, 1],
|
| 636 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.019, "cosStep": 0.031, "scale": 0.6 }
|
| 637 |
+
},
|
| 638 |
+
"window": {
|
| 639 |
+
"dtype": "float32",
|
| 640 |
+
"shape": [496],
|
| 641 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.009, "cosStep": 0.005, "scale": 0.5, "offset": 0.5 }
|
| 642 |
+
}
|
| 643 |
+
},
|
| 644 |
+
"outputs": { "output": { "dtype": "float32", "shape": [1, 2, 249, 2], "tolerance": 0.02, "relTolerance": 0.001 } },
|
| 645 |
+
"args": { "frameStep": 248 }
|
| 646 |
+
},
|
| 647 |
+
{
|
| 648 |
+
"name": "mixed_stockham_n132_real_onesided_tail33",
|
| 649 |
+
"provenance": {
|
| 650 |
+
"notes": "First tail past the unanchored bound: a 4-point shared Stockham factor and radix-33 tail, so the tail walk re-anchors exactly once."
|
| 651 |
+
},
|
| 652 |
+
"attrs": { "onesided": 1 },
|
| 653 |
+
"inputs": {
|
| 654 |
+
"signal": {
|
| 655 |
+
"dtype": "float32",
|
| 656 |
+
"shape": [1, 198, 1],
|
| 657 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.019, "cosStep": 0.031, "scale": 0.6 }
|
| 658 |
+
},
|
| 659 |
+
"window": {
|
| 660 |
+
"dtype": "float32",
|
| 661 |
+
"shape": [132],
|
| 662 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.031, "cosStep": 0.017, "scale": 0.5, "offset": 0.5 }
|
| 663 |
+
}
|
| 664 |
+
},
|
| 665 |
+
"outputs": { "output": { "dtype": "float32", "shape": [1, 2, 67, 2], "tolerance": 0.02, "relTolerance": 0.001 } },
|
| 666 |
+
"args": { "frameStep": 66 }
|
| 667 |
+
},
|
| 668 |
+
{
|
| 669 |
+
"name": "window_and_frame_length_copresent_fft",
|
| 670 |
+
"inputs": {
|
| 671 |
+
"signal": { "dtype": "float32", "shape": [1, 64, 1], "data": { "kind": "constant", "value": 1.0 } },
|
| 672 |
+
"window": {
|
| 673 |
+
"dtype": "float32",
|
| 674 |
+
"shape": [16],
|
| 675 |
+
"data": {
|
| 676 |
+
"kind": "values",
|
| 677 |
+
"values": { "$ref": "#/fixtureArrays/onnx_backend_arange128_cos_window16_input_window" }
|
| 678 |
+
}
|
| 679 |
+
}
|
| 680 |
+
},
|
| 681 |
+
"outputs": { "output": { "dtype": "float32", "shape": [1, 7, 9, 2], "tolerance": 0.0002 } },
|
| 682 |
+
"args": { "frameStep": 8, "frameLength": 16 }
|
| 683 |
+
},
|
| 684 |
+
{
|
| 685 |
+
"name": "mixed_stockham_n1000_real_twosided_tail125",
|
| 686 |
+
"provenance": {
|
| 687 |
+
"notes": "Window length 1000 factors as 8 x 125 and exercises the shared Stockham mixed-radix route after the tail-bound transition. Odd lengths 513 and 777 retain independent tiled-real coverage."
|
| 688 |
+
},
|
| 689 |
+
"attrs": { "onesided": 0 },
|
| 690 |
+
"inputs": {
|
| 691 |
+
"signal": {
|
| 692 |
+
"dtype": "float32",
|
| 693 |
+
"shape": [1, 2000, 1],
|
| 694 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.0091, "cosStep": 0.0071, "scale": 1.0 }
|
| 695 |
+
},
|
| 696 |
+
"window": {
|
| 697 |
+
"dtype": "float32",
|
| 698 |
+
"shape": [1000],
|
| 699 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.0031, "cosStep": 0.0031, "scale": 0.5 }
|
| 700 |
+
}
|
| 701 |
+
},
|
| 702 |
+
"outputs": {
|
| 703 |
+
"output": { "dtype": "float32", "shape": [1, 2, 1000, 2], "tolerance": 0.05, "relTolerance": 0.002 }
|
| 704 |
+
},
|
| 705 |
+
"args": { "frameStep": 1000 }
|
| 706 |
+
},
|
| 707 |
+
{
|
| 708 |
+
"name": "fft_pow2_win8_drops_partial_last_frame",
|
| 709 |
+
"attrs": { "onesided": 1 },
|
| 710 |
+
"inputs": {
|
| 711 |
+
"signal": {
|
| 712 |
+
"dtype": "float32",
|
| 713 |
+
"shape": [1, 12, 1],
|
| 714 |
+
"data": { "kind": "values", "values": [1.0, -2.0, 3.0, -4.0, 5.0, -6.0, 7.0, -8.0, 9.0, -10.0, 11.0, -12.0] }
|
| 715 |
+
},
|
| 716 |
+
"window": {
|
| 717 |
+
"dtype": "float32",
|
| 718 |
+
"shape": [8],
|
| 719 |
+
"data": { "kind": "values", "values": [1.0, 0.9, 0.8, 0.7, 0.6, 0.5, 0.4, 0.3] }
|
| 720 |
+
}
|
| 721 |
+
},
|
| 722 |
+
"outputs": {
|
| 723 |
+
"output": { "dtype": "float32", "shape": [1, 1, 5, 2], "tolerance": 0.0002, "relTolerance": 0.0001 }
|
| 724 |
+
},
|
| 725 |
+
"args": { "frameStep": 8 },
|
| 726 |
+
"provenance": {
|
| 727 |
+
"notes": "The final four samples cannot fill an eight-sample frame, so ONNX floor framing emits only the complete frame at offset zero."
|
| 728 |
+
}
|
| 729 |
+
},
|
| 730 |
+
{
|
| 731 |
+
"name": "fft_pow2_win4_complex_twosided_drops_partial_last_frame",
|
| 732 |
+
"attrs": { "onesided": 0 },
|
| 733 |
+
"inputs": {
|
| 734 |
+
"signal": {
|
| 735 |
+
"dtype": "float32",
|
| 736 |
+
"shape": [1, 6, 2],
|
| 737 |
+
"data": { "kind": "values", "values": [1.0, 0.5, 2.0, -0.5, 3.0, 1.0, -4.0, 0.0, 5.0, -2.0, 6.0, 0.25] }
|
| 738 |
+
},
|
| 739 |
+
"window": { "dtype": "float32", "shape": [4], "data": { "kind": "values", "values": [1.0, 0.75, 0.5, 0.25] } }
|
| 740 |
+
},
|
| 741 |
+
"outputs": {
|
| 742 |
+
"output": { "dtype": "float32", "shape": [1, 1, 4, 2], "tolerance": 0.0002, "relTolerance": 0.0001 }
|
| 743 |
+
},
|
| 744 |
+
"args": { "frameStep": 4 },
|
| 745 |
+
"provenance": {
|
| 746 |
+
"notes": "The frame at offset four would extend past the six-sample signal, so ONNX floor framing emits only the complete frame at offset zero."
|
| 747 |
+
}
|
| 748 |
+
},
|
| 749 |
+
{
|
| 750 |
+
"name": "fft_pow2_win16_window_with_interior_zeros",
|
| 751 |
+
"attrs": { "onesided": 1 },
|
| 752 |
+
"inputs": {
|
| 753 |
+
"signal": { "dtype": "float32", "shape": [1, 32, 1], "data": { "kind": "linspace", "start": 0.0, "end": 31.0 } },
|
| 754 |
+
"window": {
|
| 755 |
+
"dtype": "float32",
|
| 756 |
+
"shape": [16],
|
| 757 |
+
"data": {
|
| 758 |
+
"kind": "values",
|
| 759 |
+
"values": [0.0, 0.25, 0.5, 0.75, 1.0, 0.75, 0.5, 0.0, 0.0, 0.5, 0.75, 1.0, 0.75, 0.5, 0.25, 0.0]
|
| 760 |
+
}
|
| 761 |
+
}
|
| 762 |
+
},
|
| 763 |
+
"outputs": {
|
| 764 |
+
"output": { "dtype": "float32", "shape": [1, 3, 9, 2], "tolerance": 0.0002, "relTolerance": 0.0001 }
|
| 765 |
+
},
|
| 766 |
+
"args": { "frameStep": 8 }
|
| 767 |
+
},
|
| 768 |
+
{
|
| 769 |
+
"name": "dispatch_fold_fft_batch2_frames32800_win4_onesided",
|
| 770 |
+
"inputs": {
|
| 771 |
+
"signal": {
|
| 772 |
+
"dtype": "float32",
|
| 773 |
+
"shape": [2, 32803, 1],
|
| 774 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.017, "cosStep": 0.029, "scale": 1.0 }
|
| 775 |
+
},
|
| 776 |
+
"window": { "dtype": "float32", "shape": [4], "data": { "kind": "values", "values": [0.5, 1.0, 1.0, 0.5] } }
|
| 777 |
+
},
|
| 778 |
+
"outputs": {
|
| 779 |
+
"output": { "dtype": "float32", "shape": [2, 32800, 3, 2], "tolerance": 0.001, "relTolerance": 0.0001 }
|
| 780 |
+
},
|
| 781 |
+
"args": { "frameStep": 1 }
|
| 782 |
+
},
|
| 783 |
+
{
|
| 784 |
+
"name": "rank3_window_win513_nonpow2_onesided_compact",
|
| 785 |
+
"provenance": {
|
| 786 |
+
"notes": "Compact selector and correctness lock for the first non-power-of-two window above 512. Three frames exercise frame addressing; 257 bins exercise the partial second 256-frequency block."
|
| 787 |
+
},
|
| 788 |
+
"attrs": { "onesided": 1 },
|
| 789 |
+
"inputs": {
|
| 790 |
+
"signal": {
|
| 791 |
+
"dtype": "float32",
|
| 792 |
+
"shape": [1, 1025, 1],
|
| 793 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.0091, "cosStep": 0.0071, "scale": 1.0 }
|
| 794 |
+
},
|
| 795 |
+
"window": {
|
| 796 |
+
"dtype": "float32",
|
| 797 |
+
"shape": [513],
|
| 798 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.0061, "cosStep": 0.0061, "scale": 0.5 }
|
| 799 |
+
}
|
| 800 |
+
},
|
| 801 |
+
"outputs": { "output": { "dtype": "float32", "shape": [1, 3, 257, 2], "tolerance": 0.02, "relTolerance": 0.001 } },
|
| 802 |
+
"args": { "frameStep": 256 }
|
| 803 |
+
},
|
| 804 |
+
{
|
| 805 |
+
"name": "rank3_window_win777_tiled_real_generic",
|
| 806 |
+
"provenance": { "notes": "Non-513 correctness lock for the storage-bounded tiled real STFT path." },
|
| 807 |
+
"attrs": { "onesided": 1 },
|
| 808 |
+
"inputs": {
|
| 809 |
+
"signal": {
|
| 810 |
+
"dtype": "float32",
|
| 811 |
+
"shape": [1, 777, 1],
|
| 812 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.0067, "cosStep": 0.0091, "scale": 0.5 }
|
| 813 |
+
},
|
| 814 |
+
"window": {
|
| 815 |
+
"dtype": "float32",
|
| 816 |
+
"shape": [777],
|
| 817 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.0041, "cosStep": 0.0051, "scale": 0.5 }
|
| 818 |
+
}
|
| 819 |
+
},
|
| 820 |
+
"outputs": { "output": { "dtype": "float32", "shape": [1, 1, 389, 2], "tolerance": 0.04, "relTolerance": 0.002 } },
|
| 821 |
+
"args": { "frameStep": 777 }
|
| 822 |
+
},
|
| 823 |
+
{
|
| 824 |
+
"name": "rank3_window_fft8192_storage_generic",
|
| 825 |
+
"provenance": {
|
| 826 |
+
"notes": "Non-4096 correctness lock for the geometry-derived large radix-2 FFT; device storage capacity selects the single-dispatch or globally staged implementation."
|
| 827 |
+
},
|
| 828 |
+
"attrs": { "onesided": 1 },
|
| 829 |
+
"inputs": {
|
| 830 |
+
"signal": {
|
| 831 |
+
"dtype": "float32",
|
| 832 |
+
"shape": [1, 8192, 1],
|
| 833 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.0045, "cosStep": 0.0085, "scale": 0.25 }
|
| 834 |
+
},
|
| 835 |
+
"window": {
|
| 836 |
+
"dtype": "float32",
|
| 837 |
+
"shape": [8192],
|
| 838 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.0025, "cosStep": 0.0015, "scale": 0.5, "offset": 0.5 }
|
| 839 |
+
}
|
| 840 |
+
},
|
| 841 |
+
"outputs": {
|
| 842 |
+
"output": { "dtype": "float32", "shape": [1, 1, 4097, 2], "tolerance": 0.08, "relTolerance": 0.0005 }
|
| 843 |
+
},
|
| 844 |
+
"args": { "frameStep": 8192 }
|
| 845 |
+
}
|
| 846 |
+
]
|
| 847 |
+
}
|