GCC Code Coverage Report


Directory: ./
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 94.4% 84 / 0 / 89
Functions: 100.0% 5 / 0 / 5
Branches: 40.4% 46 / 0 / 114

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