src/cpu/resample1d.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | // ─── CPU 1D resampling ops (CHUNK 6, family E) ───────────────────────────── | ||
| 2 | // | ||
| 3 | // FP32 scalar host implementations. Arbitrary-scale resampling along the | ||
| 4 | // length axis of an NCL audio tensor — the 1D, arbitrary-ratio analogue of | ||
| 5 | // the fixed-2x NCHW resample ops in resample.cpp. CPU is FP32-only. | ||
| 6 | // | ||
| 7 | // Memory layout (NCL flat — consistent with conv1d.cpp / snake): | ||
| 8 | // X / Y / dX / dY : ((n * C + c) * L) + l | ||
| 9 | // resample1d_forward : (N, C, L_in) -> (N, C, L_out) | ||
| 10 | // | ||
| 11 | // Sampling convention — PyTorch align_corners=False: | ||
| 12 | // src = (dst + 0.5) * (L_in / L_out) - 0.5 | ||
| 13 | // nearest : Y[dst] = X[ clamp(round_half_to_even(src), 0, L_in-1) ] | ||
| 14 | // linear : s = clamp(src, 0, L_in-1), x0 = floor(s), | ||
| 15 | // x1 = min(x0+1, L_in-1), f = s - x0 | ||
| 16 | // Y[dst] = (1-f) * X[x0] + f * X[x1] | ||
| 17 | // | ||
| 18 | // ACCUMULATION: | ||
| 19 | // resample1d_forward — Y OVERWRITTEN. | ||
| 20 | // resample1d_backward — dX OVERWRITTEN (zero-then-scatter; resampling has no | ||
| 21 | // learnable parameters, so the adjoint overwrites dX). | ||
| 22 | |||
| 23 | #include <brotensor/tensor.h> | ||
| 24 | |||
| 25 | #include <cmath> | ||
| 26 | #include <stdexcept> | ||
| 27 | #include <string> | ||
| 28 | |||
| 29 | namespace brotensor::detail::cpu { | ||
| 30 | |||
| 31 | namespace { | ||
| 32 | |||
| 33 | 157 | inline void check_fp32(const ::brotensor::Tensor& t, | |
| 34 | const char* op, const char* name) { | ||
| 35 |
1/2✓ Branch 0 taken 157 times.
✗ Branch 1 not taken.
|
157 | if (t.dtype != Dtype::FP32) { |
| 36 | ✗ | throw std::runtime_error(std::string("brotensor: ") + op + ": " + | |
| 37 | ✗ | name + " must be FP32 (CPU backend is " | |
| 38 | "FP32-only)"); | ||
| 39 | } | ||
| 40 | 157 | } | |
| 41 | |||
| 42 | 702 | inline int clampi(int v, int lo, int hi) { | |
| 43 |
2/4✗ Branch 0 not taken.
✓ Branch 1 taken 702 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 702 times.
|
702 | return v < lo ? lo : (v > hi ? hi : v); |
| 44 | } | ||
| 45 | |||
| 46 | 157 | inline void check_args(const char* op, int N, int C, int L_in, int L_out, | |
| 47 | int mode) { | ||
| 48 |
1/2✓ Branch 0 taken 157 times.
✗ Branch 1 not taken.
|
157 | if (N < 0 || C < 0 || L_in < 0 || L_out < 0) { |
| 49 | ✗ | throw std::runtime_error(std::string("brotensor: ") + op + | |
| 50 | ": N, C, L_in, L_out must be non-negative"); | ||
| 51 | } | ||
| 52 |
3/4✓ Branch 0 taken 144 times.
✓ Branch 1 taken 13 times.
✓ Branch 2 taken 144 times.
✗ Branch 3 not taken.
|
157 | if (mode != 0 && mode != 1) { |
| 53 | ✗ | throw std::runtime_error(std::string("brotensor: ") + op + | |
| 54 | ": mode must be 0 (nearest) or 1 (linear)"); | ||
| 55 | } | ||
| 56 |
2/4✓ Branch 0 taken 157 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 157 times.
✗ Branch 3 not taken.
|
157 | if (L_out > 0 && L_in == 0) { |
| 57 | ✗ | throw std::runtime_error(std::string("brotensor: ") + op + | |
| 58 | ": L_in must be > 0 when L_out > 0"); | ||
| 59 | } | ||
| 60 | 157 | } | |
| 61 | |||
| 62 | } // namespace | ||
| 63 | |||
| 64 | // ─── Forward ─────────────────────────────────────────────────────────────── | ||
| 65 | |||
| 66 | 146 | void resample1d_forward(const ::brotensor::Tensor& X, | |
| 67 | int N, int C, int L_in, int L_out, int mode, | ||
| 68 | ::brotensor::Tensor& Y) { | ||
| 69 | 146 | check_fp32(X, "resample1d_forward", "X"); | |
| 70 | 146 | check_args("resample1d_forward", N, C, L_in, L_out, mode); | |
| 71 | |||
| 72 | 146 | const int cols = C * L_out; | |
| 73 |
1/6✗ Branch 0 not taken.
✓ Branch 1 taken 146 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
|
146 | if (Y.rows != N || Y.cols != cols || Y.dtype != Dtype::FP32) { |
| 74 | 146 | Y.resize(N, cols, Dtype::FP32); | |
| 75 | 146 | } | |
| 76 |
2/4✓ Branch 0 taken 146 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 146 times.
|
146 | if (N == 0 || cols == 0) return; |
| 77 | |||
| 78 | 146 | const float* Xp = X.host_f32(); | |
| 79 | 146 | float* Yp = Y.host_f32_mut(); | |
| 80 | |||
| 81 | 292 | const double scale = static_cast<double>(L_in) / | |
| 82 | 146 | static_cast<double>(L_out); | |
| 83 | |||
| 84 |
2/2✓ Branch 0 taken 146 times.
✓ Branch 1 taken 291 times.
|
437 | for (int n = 0; n < N; ++n) { |
| 85 |
2/2✓ Branch 0 taken 630 times.
✓ Branch 1 taken 291 times.
|
921 | for (int c = 0; c < C; ++c) { |
| 86 | 630 | const int xbase = (n * C + c) * L_in; | |
| 87 | 630 | const int ybase = (n * C + c) * L_out; | |
| 88 |
2/2✓ Branch 0 taken 5004 times.
✓ Branch 1 taken 630 times.
|
5634 | for (int dst = 0; dst < L_out; ++dst) { |
| 89 | 5004 | const double src = (dst + 0.5) * scale - 0.5; | |
| 90 |
2/2✓ Branch 0 taken 4554 times.
✓ Branch 1 taken 450 times.
|
5004 | if (mode == 0) { |
| 91 | // nearest — round-half-to-even, then clamp. | ||
| 92 | 450 | const int idx = clampi( | |
| 93 | 450 | static_cast<int>(std::nearbyint(src)), 0, L_in - 1); | |
| 94 | 450 | Yp[ybase + dst] = Xp[xbase + idx]; | |
| 95 | 450 | } else { | |
| 96 | // linear — clamp src into range, then split into taps. | ||
| 97 | 4554 | double s = src; | |
| 98 |
2/2✓ Branch 0 taken 4351 times.
✓ Branch 1 taken 203 times.
|
4554 | if (s < 0.0) s = 0.0; |
| 99 |
2/2✓ Branch 0 taken 4351 times.
✓ Branch 1 taken 203 times.
|
4554 | if (s > L_in - 1) s = L_in - 1; |
| 100 | 4554 | const int x0 = static_cast<int>(std::floor(s)); | |
| 101 |
2/2✓ Branch 0 taken 4343 times.
✓ Branch 1 taken 211 times.
|
4554 | const int x1 = (x0 + 1 < L_in) ? x0 + 1 : L_in - 1; |
| 102 | 4554 | const float f = static_cast<float>(s - x0); | |
| 103 | 9108 | Yp[ybase + dst] = (1.0f - f) * Xp[xbase + x0] + | |
| 104 | 4554 | f * Xp[xbase + x1]; | |
| 105 | } | ||
| 106 | 5004 | } | |
| 107 | 630 | } | |
| 108 | 291 | } | |
| 109 | 146 | } | |
| 110 | |||
| 111 | // ─── Backward ────────────────────────────────────────────────────────────── | ||
| 112 | |||
| 113 | 11 | void resample1d_backward(const ::brotensor::Tensor& dY, | |
| 114 | int N, int C, int L_in, int L_out, int mode, | ||
| 115 | ::brotensor::Tensor& dX) { | ||
| 116 | 11 | check_fp32(dY, "resample1d_backward", "dY"); | |
| 117 | 11 | check_args("resample1d_backward", N, C, L_in, L_out, mode); | |
| 118 | |||
| 119 | 11 | const int cols_in = C * L_in; | |
| 120 |
1/6✗ Branch 0 not taken.
✓ Branch 1 taken 11 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
|
11 | if (dX.rows != N || dX.cols != cols_in || dX.dtype != Dtype::FP32) { |
| 121 | 11 | dX.resize(N, cols_in, Dtype::FP32); | |
| 122 | 11 | } | |
| 123 |
2/4✓ Branch 0 taken 11 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 11 times.
|
11 | if (N == 0 || cols_in == 0) return; |
| 124 | |||
| 125 | 11 | const float* dYp = dY.host_f32(); | |
| 126 | 11 | float* dXp = dX.host_f32_mut(); | |
| 127 | |||
| 128 | // Adjoint: zero dX, then scatter each output gradient onto the input | ||
| 129 | // position(s) it sampled — with the same weights as the forward pass. | ||
| 130 | 11 | const int total_in = N * cols_in; | |
| 131 |
2/2✓ Branch 0 taken 1000 times.
✓ Branch 1 taken 11 times.
|
1011 | for (int i = 0; i < total_in; ++i) dXp[i] = 0.0f; |
| 132 | |||
| 133 |
1/2✓ Branch 0 taken 11 times.
✗ Branch 1 not taken.
|
11 | if (L_out == 0) return; |
| 134 | |||
| 135 | 22 | const double scale = static_cast<double>(L_in) / | |
| 136 | 11 | static_cast<double>(L_out); | |
| 137 | |||
| 138 |
2/2✓ Branch 0 taken 11 times.
✓ Branch 1 taken 24 times.
|
35 | for (int n = 0; n < N; ++n) { |
| 139 |
2/2✓ Branch 0 taken 80 times.
✓ Branch 1 taken 24 times.
|
104 | for (int c = 0; c < C; ++c) { |
| 140 | 80 | const int xbase = (n * C + c) * L_in; | |
| 141 | 80 | const int ybase = (n * C + c) * L_out; | |
| 142 |
2/2✓ Branch 0 taken 1548 times.
✓ Branch 1 taken 80 times.
|
1628 | for (int dst = 0; dst < L_out; ++dst) { |
| 143 | 1548 | const double src = (dst + 0.5) * scale - 0.5; | |
| 144 | 1548 | const float g = dYp[ybase + dst]; | |
| 145 |
2/2✓ Branch 0 taken 1296 times.
✓ Branch 1 taken 252 times.
|
1548 | if (mode == 0) { |
| 146 | 252 | const int idx = clampi( | |
| 147 | 252 | static_cast<int>(std::nearbyint(src)), 0, L_in - 1); | |
| 148 | 252 | dXp[xbase + idx] += g; | |
| 149 | 252 | } else { | |
| 150 | 1296 | double s = src; | |
| 151 |
2/2✓ Branch 0 taken 1256 times.
✓ Branch 1 taken 40 times.
|
1296 | if (s < 0.0) s = 0.0; |
| 152 |
2/2✓ Branch 0 taken 1256 times.
✓ Branch 1 taken 40 times.
|
1296 | if (s > L_in - 1) s = L_in - 1; |
| 153 | 1296 | const int x0 = static_cast<int>(std::floor(s)); | |
| 154 |
2/2✓ Branch 0 taken 1256 times.
✓ Branch 1 taken 40 times.
|
1296 | const int x1 = (x0 + 1 < L_in) ? x0 + 1 : L_in - 1; |
| 155 | 1296 | const float f = static_cast<float>(s - x0); | |
| 156 | 1296 | dXp[xbase + x0] += (1.0f - f) * g; | |
| 157 | 1296 | dXp[xbase + x1] += f * g; | |
| 158 | } | ||
| 159 | 1548 | } | |
| 160 | 80 | } | |
| 161 | 24 | } | |
| 162 | 11 | } | |
| 163 | |||
| 164 | } // namespace brotensor::detail::cpu | ||
| 165 |