resample-poly
Polyphase rational resampling for audio, loadable through kernels. Filter
construction matches torchaudio's, so this is a drop-in replacement and
results agree to floating-point rounding. The reference baseline is the
conv1d filter-bank formulation torchaudio uses.
Sample-rate conversion sits at the front of every speech pipeline, and the standard implementation stores each polyphase filter padded to a common width: at 44.1k to 16k a kernel row is 475 entries of which 33 are support, and the padding is multiplied and summed like everything else. This kernel stores only each phase's real support, then picks between two formulations by the one thing that decides which is faster, taps per output, so the result is never slower than the baseline it replaces and the gain lands on downsampling, the direction speech pipelines run.
30 s of mono audio on 14 x86 threads: 44.1k to 16k in 1.73 ms against the conv1d bank's 2.22 (1.28x) on the fused path at 33.4 taps per output, and 16k to 24k at parity on the bank path at 12.3 taps, which is exactly where the plan switches.
Usage
import kernels
rp = kernels.get_kernel("phanerozoic/resample-poly", version=1, trust_remote_code=True)
r = rp.Resampler(44100, 16000)
y = r(x) # [T] or [B, T]
y = rp.resample(x, 44100, 16000) # one-shot
version selects the release branch; trust_remote_code is required by
kernels for publishers without the trusted-publisher mark.
API
| Symbol | Purpose |
|---|---|
Resampler(orig_freq, new_freq, ...) |
cached plan and filters; call it on audio |
resample(x, orig_freq, new_freq, ...) |
one-shot form |
plan.fused / plan.taps_per_output |
which path was selected and why |
Method
Resampling by L/M is nominally zero-stuff by L, low-pass, keep every Mth
sample. The polyphase identity removes both the zeros and the discards:
output n depends only on filter phase (n*M) mod L and a short run of
input around floor(n*M/L). torchaudio expresses this as a conv1d with
new_freq output channels and stride orig_freq, storing per-phase filters
padded to a common width; here each phase keeps only its own support, as an
offset and a run of taps, which is 7% of the padded bank.
Fewer multiplies did not translate into speed everywhere, and measuring that
was the point. The fused path computes one dot product per output and
reduces it, and that reduction is fixed cost per output sample: at 44.1k to
48k it does 13x fewer multiplies than the bank and still ran 0.5x, because
a phase there carries only 12 taps. The crossover measured between 12 and 17
taps per output, so the plan picks: above the threshold the fused kernel,
below it the bank formulation. Both produce the same output, and the
selected path is exposed as plan.fused and plan.taps_per_output.
The filter is a windowed sinc, lowpass_filter_width taps per side,
rolloff fraction of Nyquist, with sinc_interp_hann or
sinc_interp_kaiser windows, all matching torchaudio. Entries clamped past
the filter half width carry a window value of exactly cos(pi/2)^2, so the
trimmed support is exact rather than thresholded, and the test suite asserts
that every dropped entry is zero.
Measured
30 s of mono audio, best of 25 runs after warmup:
| conversion | resample-poly | conv1d bank | speedup | taps/out | path |
|---|---|---|---|---|---|
| x86-64 (i9-12900H, 14 threads) | |||||
| 44.1k to 16k | 1.73 ms | 2.22 ms | 1.28x | 33.4 | fused |
| 48k to 16k | 3.56 ms | 4.19 ms | 1.18x | 37.0 | fused |
| 16k to 24k | 0.87 ms | 0.87 ms | 1.00x | 12.3 | bank |
| 44.1k to 48k | 2.69 ms | 2.73 ms | 1.02x | 12.1 | bank |
| aarch64 (Cortex-A76) | |||||
| 44.1k to 16k | 6.31 ms | 14.41 ms | 2.28x | 33.4 | fused |
| 48k to 16k | 9.79 ms | 24.21 ms | 2.47x | 37.0 | fused |
| 16k to 24k | 9.03 ms | 9.05 ms | 1.00x | 12.3 | bank |
| 44.1k to 48k | 21.11 ms | 20.98 ms | 0.99x | 12.1 | bank |
44.1k to 16k runs 30 s of audio in 1.73 ms on x86-64 (a 16,000x realtime factor) and 6.31 ms on a Raspberry Pi 5. The stored taps are 5,345 of 76,000 padded-bank entries, 7.0%. Speedups shift with thread count, since the fused path parallelizes over output blocks and the bank over convolution work.
Correctness
Output agrees with the torchaudio filter-bank formulation to floating-point rounding across all four conversions. At equal rates the bank collapses to one phase but the low-pass still applies, so the result is band-limited rather than an identity, which is torchaudio's behavior too.
Requirements and limits
- CPU,
x86_64-linuxandaarch64-linux, for PyTorch 2.11, 2.12, and 2.13 (six prebuilt variants), each architecture built on its own host and validated against the prebuilt binary. - Rational conversions only; the plan is fixed at construction for a given rate pair.
References
The polyphase decomposition of rational resampling; torchaudio's windowed sinc filter construction as the compatibility target.
License
Apache-2.0.
- Downloads last month
- -
- OS
- linux
- Arch
- x86_64aarch64
- Kernel Builder
- 19aaa64
