|
36 | 36 | // multi-resolution STFT loss. |
37 | 37 |
|
38 | 38 | #include <brotensor/detail/cpu/fft_core.h> |
| 39 | +#include <brotensor/detail/cpu/thread_pool.h> |
39 | 40 | #include <brotensor/tensor.h> |
40 | 41 |
|
41 | 42 | #include <algorithm> |
@@ -158,34 +159,41 @@ void stft(const ::brotensor::Tensor& signal, const ::brotensor::Tensor& window, |
158 | 159 | ? 1.0 / std::sqrt(static_cast<double>(n_fft)) |
159 | 160 | : 1.0; |
160 | 161 |
|
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)); |
163 | 177 | 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}; |
187 | 187 | } |
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 | + }); |
189 | 197 | } |
190 | 198 |
|
191 | 199 | // ════════════════════════════════════════════════════════════════════════════ |
|
0 commit comments