Skip to content

Commit a364261

Browse files
committed
perf(cpu): run stft's frames on the thread pool
Frames are independent — each reads its own slice of the signal and writes its own row of spec — so this is the pool's ordinary case, and a mel front end is thousands of frames of a double-precision DFT. It was worth taking because it was the host half of an ASR encoder's cost: brosoundml's FastConformer over 18 s of 16 kHz audio is 1 801 frames at n_fft 512, and that was 212 ms on one core against 136 ms for everything the GPU did. 30 ms now. The scratch buffers move inside the item rather than being hoisted, because two workers sharing them is a data race. fft_core's twiddle table is thread_local and keyed on (N, sign), so each worker builds it once and every frame after the first reuses it — which is why the buffers being fresh costs nothing.
1 parent 7c864dd commit a364261

1 file changed

Lines changed: 34 additions & 26 deletions

File tree

src/cpu/stft.cpp

Lines changed: 34 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
// multi-resolution STFT loss.
3737

3838
#include <brotensor/detail/cpu/fft_core.h>
39+
#include <brotensor/detail/cpu/thread_pool.h>
3940
#include <brotensor/tensor.h>
4041

4142
#include <algorithm>
@@ -158,34 +159,41 @@ void stft(const ::brotensor::Tensor& signal, const ::brotensor::Tensor& window,
158159
? 1.0 / std::sqrt(static_cast<double>(n_fft))
159160
: 1.0;
160161

161-
std::vector<Cd> buf(static_cast<std::size_t>(n_fft)), out;
162-
for (int b = 0; b < N; ++b) {
162+
// One frame per work item. Frames are independent — each reads its own slice
163+
// of the signal and writes its own row of `spec` — so this is the pool's
164+
// ordinary case, and it is worth taking: a mel front end is thousands of
165+
// frames of a double-precision DFT and it was the *host* half of an ASR
166+
// encoder's cost. Measured on a FastConformer over 18 s of 16 kHz audio
167+
// (1 801 frames, n_fft 512): 212 ms on one core.
168+
//
169+
// The scratch buffers are per item rather than hoisted, because two workers
170+
// sharing them is a data race. fft_core's twiddle table is thread_local and
171+
// keyed on (N, sign), so each worker builds it once and every frame after
172+
// the first reuses it — which is why the buffers being fresh costs nothing.
173+
parallel_for(static_cast<std::size_t>(N) * static_cast<std::size_t>(g.frames),
174+
[&](std::size_t item) {
175+
const int b = static_cast<int>(item / static_cast<std::size_t>(g.frames));
176+
const int f = static_cast<int>(item % static_cast<std::size_t>(g.frames));
163177
const float* srow = sig + static_cast<std::size_t>(b) * signal_len;
164-
for (int f = 0; f < g.frames; ++f) {
165-
// Build the windowed n_fft frame buffer (real, im = 0).
166-
for (int i = 0; i < n_fft; ++i) buf[static_cast<std::size_t>(i)] = Cd{};
167-
const int base = f * hop_length; // padded-position start
168-
for (int j = 0; j < win_length; ++j) {
169-
const int i = g.pad_lo + j;
170-
const int p = base + i;
171-
const int s = padded_index(p, signal_len, n_fft, center);
172-
buf[static_cast<std::size_t>(i)] =
173-
{static_cast<double>(srow[s]) *
174-
static_cast<double>(win[j]),
175-
0.0};
176-
}
177-
dft_1d(buf, out, -1); // unscaled forward DFT
178-
float* dst = sp + static_cast<std::size_t>(
179-
static_cast<std::size_t>(b) * g.frames + f) *
180-
out_cols;
181-
for (int k = 0; k < g.bins; ++k) {
182-
dst[2 * k] = static_cast<float>(
183-
out[static_cast<std::size_t>(k)].re * norm);
184-
dst[2 * k + 1] = static_cast<float>(
185-
out[static_cast<std::size_t>(k)].im * norm);
186-
}
178+
179+
std::vector<Cd> buf(static_cast<std::size_t>(n_fft)), out;
180+
const int base = f * hop_length; // padded-position start
181+
for (int j = 0; j < win_length; ++j) {
182+
const int i = g.pad_lo + j;
183+
const int p = base + i;
184+
const int s = padded_index(p, signal_len, n_fft, center);
185+
buf[static_cast<std::size_t>(i)] =
186+
{static_cast<double>(srow[s]) * static_cast<double>(win[j]), 0.0};
187187
}
188-
}
188+
dft_1d(buf, out, -1); // unscaled forward DFT
189+
float* dst = sp + item * static_cast<std::size_t>(out_cols);
190+
for (int k = 0; k < g.bins; ++k) {
191+
dst[2 * k] = static_cast<float>(
192+
out[static_cast<std::size_t>(k)].re * norm);
193+
dst[2 * k + 1] = static_cast<float>(
194+
out[static_cast<std::size_t>(k)].im * norm);
195+
}
196+
});
189197
}
190198

191199
// ════════════════════════════════════════════════════════════════════════════

0 commit comments

Comments
 (0)