stft-mel
Fused STFT to mel spectrogram in a single pass, loadable through kernels.
Framing, window, real FFT, power spectrum, mel projection, and log collapse
into one traversal, so no complex spectrogram is ever materialized. The
reference baseline is torch.stft plus a dense einsum mel projection, and a
float64 STFT and projection for accuracy.
The Whisper front end runs before every transcription, and on a CPU it is one of the few remaining places where a fixed, well-understood transform still costs milliseconds per utterance. This kernel computes it as a single vertical pass across frames: no lane-crossing shuffle anywhere in the FFT, no complex intermediate written to memory, and a banded mel projection that touches only the nonzeros. Thirty seconds of audio becomes 80 mel bands in about two milliseconds.
The kernel's own output: 30 s of a synthetic voiced signal at 16 kHz transformed to 80 log-mel bands, painted as the transform advances, in 2.1 ms end to end.
Usage
import kernels
sm = kernels.get_kernel("phanerozoic/stft-mel", version=1, trust_remote_code=True)
mel = sm.MelSpectrogram(sr=16000, n_fft=400, hop=160, n_mels=80)
feats = mel(audio) # [B, 80, n_frames] log-mel, Whisper's front end
MelSpectrogram caches the window and filterbank; reuse one instance across
calls. log_mel(audio, ...) is the one-shot form, and spectrogram(...)
returns magnitude or power without the mel stage. version selects the
release branch; trust_remote_code is required by kernels for publishers
without the trusted-publisher mark.
API
| Symbol | Purpose |
|---|---|
MelSpectrogram(sr, n_fft, hop, n_mels, ...) |
cached window + filterbank; call it on audio |
log_mel(audio, ...) |
one-shot log-mel |
spectrogram(audio, ..., power) |
magnitude or power spectrogram without the mel stage |
Method
The transform is vectorized across frames, not within one: lane v of every
SIMD register holds frame f0+v, so each butterfly is a vertical operation
and no lane-crossing shuffle occurs anywhere in the FFT. Twiddles are
identical across lanes and broadcast from a table. STFT always has far more
frames than lanes, which makes this the natural axis and gives the scalar
dataflow widened by the vector length with no restructuring.
The real transform of size n_fft runs as a complex Stockham autosort FFT
of size n_fft/2 plus a split step, over mixed radices 2/3/4/5. Each pass
is instantiated at a compile-time radix, which removes the branch from the
inner loop and makes the twiddle index exact without a modulo. Mel filters
are triangular, so each output bin reads a short contiguous run of frequency
bins; the bank is stored banded as (start, length, weights) and the
projection costs O(nnz) rather than O(n_mels * n_freq), under a quarter
of the dense weights at n_fft=400, n_mels=80. On x86-64 the binary carries
both an AVX2 and a baseline lowering behind an IFUNC resolver, selected at
load time; compiling for a single ISA measured 1.85x slower on non-native
hosts.
Measured
30 s of mono audio, best of 20 runs, against torch.stft followed by a
dense einsum mel projection; both produce log-mel:
| config | stft-mel | torch | speedup |
|---|---|---|---|
| x86-64 (i9-12900H, 14 threads, portable build) | |||
| 16 kHz, n_fft=400, hop=160, 80 mels | 2.32 ms | 2.58 ms | 1.11x |
| 22 kHz, n_fft=1024, hop=256, 80 mels | 4.40 ms | 5.77 ms | 1.31x |
| 44 kHz, n_fft=2048, hop=512, 128 mels | 9.74 ms | 13.72 ms | 1.41x |
| aarch64 (Cortex-A76, 4 threads) | |||
| 16 kHz, n_fft=400, hop=160, 80 mels | 7.02 ms | 11.40 ms | 1.62x |
| 22 kHz, n_fft=1024, hop=256, 80 mels | 13.38 ms | 23.03 ms | 1.72x |
| 44 kHz, n_fft=2048, hop=512, 128 mels | 26.39 ms | 48.24 ms | 1.83x |
The Whisper front end runs 30 s of audio in 2.32 ms on x86-64 and 7.02 ms on a Raspberry Pi 5.
Accuracy
Single precision throughout. Against a float64 STFT and float64 mel
projection, the maximum relative error over a well-conditioned bank is
within a small factor of what torch.stft in float32 achieves on the same
input, and the test suite asserts that bound rather than comparing two
float32 implementations to each other. Banks with more mel bins than the FFT
can resolve, such as n_mels=128 at n_fft=400, produce filters one or two
bins wide; a mel value is then a single float32 product and relative error
reflects float32 conditioning rather than the transform. Both this kernel
and torch.stft show it.
Requirements and limits
n_fftmust be even, andn_fft/2must factor over {2, 3, 5}. That covers every value used in audio: 400, 480, 512, 800, 960, 1024, 2048. Anything else raises rather than falling back silently.- CPU,
x86_64-linuxandaarch64-linux, for PyTorch 2.11, 2.12, and 2.13 (six prebuilt variants). Each architecture is built on its own host and validated against the prebuilt binary.
References
Stockham autosort FFT; the Whisper log-mel front end as the target configuration; triangular mel filterbank construction.
License
Apache-2.0.
- Downloads last month
- -
- OS
- linux
- Arch
- x86_64aarch64
- Kernel Builder
- 19aaa64