GCC Code Coverage Report


Directory: ./
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 98.7% 227 / 0 / 230
Functions: 100.0% 9 / 0 / 9
Branches: 60.1% 137 / 0 / 228

src/cpu/interp2d.cpp
Line Branch Exec Source
1 // ─── CPU arbitrary-scale 2D resample ────────────────────────────────────────
2 //
3 // FP32 scalar host implementations. The general 2D counterpart to the fixed-2x
4 // upsample_*_2x ops in resample.cpp — supports any (H_in, W_in) -> (H_out,
5 // W_out) on an NCHW tensor, with nearest / bilinear / bicubic modes. CPU is
6 // FP32-only.
7 //
8 // Memory layout (NCHW flat — matches resample.cpp):
9 // X / Y / dX / dY : ((n * C + c) * H + h) * W + w
10 //
11 // Sampling convention — PyTorch align_corners=False / half-pixel (matches the
12 // existing upsample_bilinear_2x exactly when (H_out, W_out) == (2H, 2W)):
13 // src_y = (oh + 0.5) * (H_in / H_out) - 0.5
14 // src_x = (ow + 0.5) * (W_in / W_out) - 0.5
15 //
16 // nearest : Y[oh,ow] = X[clamp(round_half_to_even(src), 0, dim-1)]
17 // bilinear : 2x2 tap weighted blend (border-clamped indices)
18 // bicubic : 4x4 cubic-convolution tap, border-clamped — forward only.
19 // mode 2 uses a = -0.5 (Catmull-Rom, matches PIL/Pillow BICUBIC);
20 // mode 3 uses a = -0.75 (matches torch.nn.functional.interpolate
21 // mode="bicubic" and OpenCV). The two differ only in that constant.
22 //
23 // ACCUMULATION:
24 // interp2d_forward — Y OVERWRITTEN.
25 // interp2d_backward — dX OVERWRITTEN (zero-then-scatter; resampling has no
26 // learnable parameters, so the adjoint overwrites dX).
27 //
28 // Identity check: if (H_out, W_out) == (H_in, W_in) and mode == 0 or 1, the
29 // op is the identity (within rounding) — the test relies on this.
30
31 #include <brotensor/tensor.h>
32
33 #include <cmath>
34 #include <stdexcept>
35 #include <string>
36 #include <vector>
37
38 namespace brotensor::detail::cpu {
39
40 namespace {
41
42 82 inline void check_fp32(const ::brotensor::Tensor& t,
43 const char* op, const char* name) {
44
1/2
✓ Branch 0 taken 82 times.
✗ Branch 1 not taken.
82 if (t.dtype != Dtype::FP32) {
45 throw std::runtime_error(std::string("brotensor: ") + op + ": " +
46 name + " must be FP32 (CPU backend is "
47 "FP32-only)");
48 }
49 82 }
50
51 2602 inline int clampi(int v, int lo, int hi) {
52
4/4
✓ Branch 0 taken 149 times.
✓ Branch 1 taken 2453 times.
✓ Branch 2 taken 175 times.
✓ Branch 3 taken 2278 times.
2602 return v < lo ? lo : (v > hi ? hi : v);
53 }
54
55 82 inline void check_args(const char* op,
56 int N, int C, int H_in, int W_in,
57 int H_out, int W_out, int mode,
58 bool allow_bicubic) {
59
1/2
✓ Branch 0 taken 82 times.
✗ Branch 1 not taken.
164 if (N < 0 || C < 0 || H_in < 0 || W_in < 0 ||
60 82 H_out < 0 || W_out < 0) {
61
0/10
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
1 throw std::runtime_error(std::string("brotensor: ") + op +
62 ": N, C, H_in, W_in, H_out, W_out must be "
63 "non-negative");
64 }
65 82 const int max_mode = allow_bicubic ? 3 : 1;
66
2/2
✓ Branch 0 taken 81 times.
✓ Branch 1 taken 1 time.
82 if (mode < 0 || mode > max_mode) {
67 1 const char* msg = allow_bicubic
68 ? ": mode must be 0 (nearest), 1 (bilinear), 2 (bicubic a=-0.5, "
69 "PIL), or 3 (bicubic a=-0.75, torch)"
70 : ": mode must be 0 (nearest) or 1 (bilinear) — bicubic "
71 "backward is not implemented";
72
5/10
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 time.
✗ Branch 5 not taken.
✓ Branch 6 taken 1 time.
✗ Branch 7 not taken.
✓ Branch 8 taken 1 time.
✗ Branch 9 not taken.
1 throw std::runtime_error(std::string("brotensor: ") + op + msg);
73 }
74
3/6
✓ Branch 0 taken 81 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 81 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 81 times.
✗ Branch 5 not taken.
81 if ((H_out > 0 && H_in == 0) || (W_out > 0 && W_in == 0)) {
75 throw std::runtime_error(std::string("brotensor: ") + op +
76 ": input spatial dims must be > 0 when "
77 "output spatial dims are > 0");
78 }
79 82 }
80
81 // Keys cubic-convolution kernel with coefficient `a`. a = -0.5 is Catmull-Rom
82 // (matches PIL/Pillow BICUBIC); a = -0.75 matches PyTorch
83 // interpolate(mode="bicubic") and OpenCV. |t| in [0,1] uses the first branch,
84 // |t| in [1,2] the second, otherwise 0.
85 820 inline float cubic_keys(float t, float a) {
86
2/2
✓ Branch 0 taken 406 times.
✓ Branch 1 taken 414 times.
820 const float at = t < 0.0f ? -t : t;
87
2/2
✓ Branch 0 taken 365 times.
✓ Branch 1 taken 455 times.
820 if (at < 1.0f) {
88 365 return ((a + 2.0f) * at - (a + 3.0f)) * at * at + 1.0f;
89 }
90
2/2
✓ Branch 0 taken 410 times.
✓ Branch 1 taken 45 times.
455 if (at < 2.0f) {
91 // a*t^3 - 5a*t^2 + 8a*t - 4a, Horner in |t|.
92 410 return ((a * at - 5.0f * a) * at + 8.0f * a) * at - 4.0f * a;
93 }
94 45 return 0.0f;
95 820 }
96
97 } // namespace
98
99 // ─── Forward ───────────────────────────────────────────────────────────────
100
101 // Corner-aligned source coordinate: out pixel `o` maps to o*(in-1)/(out-1),
102 // with the degenerate out==1 case pinned to 0 (torch align_corners=True).
103 185 inline double align_corners_src(int o, int in_dim, int out_dim) {
104
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 181 times.
185 if (out_dim <= 1) return 0.0;
105 362 return static_cast<double>(o) * static_cast<double>(in_dim - 1) /
106 181 static_cast<double>(out_dim - 1);
107 185 }
108
109 // Shared forward worker for both the half-pixel (align_corners=False) and the
110 // corner-aligned (align_corners=True) resample — they differ only in the
111 // source-coordinate mapping, so the tap math below is identical.
112 74 static void interp2d_forward_impl(const ::brotensor::Tensor& X,
113 int N, int C, int H_in, int W_in,
114 int H_out, int W_out, int mode, bool align,
115 ::brotensor::Tensor& Y, const char* op) {
116 74 check_fp32(X, op, "X");
117 74 check_args(op, N, C, H_in, W_in, H_out, W_out, mode,
118 /*allow_bicubic=*/true);
119
120 74 const int cols = C * H_out * W_out;
121
4/6
✓ Branch 0 taken 39 times.
✓ Branch 1 taken 35 times.
✓ Branch 2 taken 39 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 39 times.
74 if (Y.rows != N || Y.cols != cols || Y.dtype != Dtype::FP32) {
122 35 Y.resize(N, cols, Dtype::FP32);
123 35 }
124
2/4
✓ Branch 0 taken 74 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 74 times.
74 if (N == 0 || cols == 0) return;
125
126 74 const float* Xp = X.host_f32();
127 74 float* Yp = Y.host_f32_mut();
128
129 // Half-pixel scale (unused on the align path, which reads from in-1/out-1).
130 74 const double sy = static_cast<double>(H_in) / static_cast<double>(H_out);
131 74 const double sx = static_cast<double>(W_in) / static_cast<double>(W_out);
132
133 // Per-row / per-column tap tables. src_y/src_x and the derived taps depend
134 // only on (oh) or (ow) — never on (n, c) — so compute them once here
135 // instead of redoing it inside the (n, c) loop below.
136 struct RowNearest { int iy; };
137 struct ColNearest { int ix; };
138 struct RowBilinear { int y0, y1; float fy; };
139 struct ColBilinear { int x0, x1; float fx; };
140 struct RowBicubic { int iy[4]; float wy[4]; };
141 struct ColBicubic { int ix[4]; float wx[4]; };
142
143 74 std::vector<RowNearest> row_nearest;
144 74 std::vector<ColNearest> col_nearest;
145 74 std::vector<RowBilinear> row_bilinear;
146 74 std::vector<ColBilinear> col_bilinear;
147 74 std::vector<RowBicubic> row_bicubic;
148 74 std::vector<ColBicubic> col_bicubic;
149
150
2/2
✓ Branch 0 taken 66 times.
✓ Branch 1 taken 8 times.
74 if (mode == 0) {
151
1/2
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
8 row_nearest.resize(H_out);
152
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 62 times.
70 for (int oh = 0; oh < H_out; ++oh) {
153
3/4
✓ Branch 0 taken 14 times.
✓ Branch 1 taken 48 times.
✓ Branch 2 taken 14 times.
✗ Branch 3 not taken.
62 const double src_y = align ? align_corners_src(oh, H_in, H_out)
154 48 : (oh + 0.5) * sy - 0.5;
155
1/2
✓ Branch 0 taken 62 times.
✗ Branch 1 not taken.
62 row_nearest[oh].iy = clampi(
156 62 static_cast<int>(std::nearbyint(src_y)), 0, H_in - 1);
157 62 }
158
1/2
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
8 col_nearest.resize(W_out);
159
2/2
✓ Branch 0 taken 76 times.
✓ Branch 1 taken 8 times.
84 for (int ow = 0; ow < W_out; ++ow) {
160
3/4
✓ Branch 0 taken 17 times.
✓ Branch 1 taken 59 times.
✓ Branch 2 taken 17 times.
✗ Branch 3 not taken.
76 const double src_x = align ? align_corners_src(ow, W_in, W_out)
161 59 : (ow + 0.5) * sx - 0.5;
162
1/2
✓ Branch 0 taken 76 times.
✗ Branch 1 not taken.
76 col_nearest[ow].ix = clampi(
163 76 static_cast<int>(std::nearbyint(src_x)), 0, W_in - 1);
164 76 }
165
2/2
✓ Branch 0 taken 57 times.
✓ Branch 1 taken 9 times.
74 } else if (mode == 1) {
166
1/2
✓ Branch 0 taken 57 times.
✗ Branch 1 not taken.
57 row_bilinear.resize(H_out);
167
2/2
✓ Branch 0 taken 57 times.
✓ Branch 1 taken 321 times.
378 for (int oh = 0; oh < H_out; ++oh) {
168
3/4
✓ Branch 0 taken 41 times.
✓ Branch 1 taken 280 times.
✓ Branch 2 taken 41 times.
✗ Branch 3 not taken.
321 const double src_y = align ? align_corners_src(oh, H_in, H_out)
169 280 : (oh + 0.5) * sy - 0.5;
170 321 const int y0 = static_cast<int>(std::floor(src_y));
171 321 row_bilinear[oh].fy = static_cast<float>(src_y - y0);
172
1/2
✓ Branch 0 taken 321 times.
✗ Branch 1 not taken.
321 row_bilinear[oh].y0 = clampi(y0, 0, H_in - 1);
173
1/2
✓ Branch 0 taken 321 times.
✗ Branch 1 not taken.
321 row_bilinear[oh].y1 = clampi(y0 + 1, 0, H_in - 1);
174 321 }
175
1/2
✓ Branch 0 taken 57 times.
✗ Branch 1 not taken.
57 col_bilinear.resize(W_out);
176
2/2
✓ Branch 0 taken 388 times.
✓ Branch 1 taken 57 times.
445 for (int ow = 0; ow < W_out; ++ow) {
177
3/4
✓ Branch 0 taken 61 times.
✓ Branch 1 taken 327 times.
✓ Branch 2 taken 61 times.
✗ Branch 3 not taken.
388 const double src_x = align ? align_corners_src(ow, W_in, W_out)
178 327 : (ow + 0.5) * sx - 0.5;
179 388 const int x0 = static_cast<int>(std::floor(src_x));
180 388 col_bilinear[ow].fx = static_cast<float>(src_x - x0);
181
1/2
✓ Branch 0 taken 388 times.
✗ Branch 1 not taken.
388 col_bilinear[ow].x0 = clampi(x0, 0, W_in - 1);
182
1/2
✓ Branch 0 taken 388 times.
✗ Branch 1 not taken.
388 col_bilinear[ow].x1 = clampi(x0 + 1, 0, W_in - 1);
183 388 }
184 57 } else {
185 // bicubic — 4x4 cubic-convolution, border-clamped.
186 // mode 2: a=-0.5 (PIL); mode 3: a=-0.75 (torch).
187 9 const float a = (mode == 3) ? -0.75f : -0.5f;
188
1/2
✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
9 row_bicubic.resize(H_out);
189
2/2
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 89 times.
98 for (int oh = 0; oh < H_out; ++oh) {
190
3/4
✓ Branch 0 taken 24 times.
✓ Branch 1 taken 65 times.
✓ Branch 2 taken 24 times.
✗ Branch 3 not taken.
89 const double src_y = align ? align_corners_src(oh, H_in, H_out)
191 65 : (oh + 0.5) * sy - 0.5;
192 89 const int y0 = static_cast<int>(std::floor(src_y));
193 89 const float fy = static_cast<float>(src_y - y0);
194
2/2
✓ Branch 0 taken 356 times.
✓ Branch 1 taken 89 times.
445 for (int k = 0; k < 4; ++k) {
195
1/2
✓ Branch 0 taken 356 times.
✗ Branch 1 not taken.
356 row_bicubic[oh].wy[k] = cubic_keys(fy - (k - 1), a);
196
1/2
✓ Branch 0 taken 356 times.
✗ Branch 1 not taken.
356 row_bicubic[oh].iy[k] = clampi(y0 + k - 1, 0, H_in - 1);
197 356 }
198 89 }
199
1/2
✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
9 col_bicubic.resize(W_out);
200
2/2
✓ Branch 0 taken 116 times.
✓ Branch 1 taken 9 times.
125 for (int ow = 0; ow < W_out; ++ow) {
201
3/4
✓ Branch 0 taken 28 times.
✓ Branch 1 taken 88 times.
✓ Branch 2 taken 28 times.
✗ Branch 3 not taken.
116 const double src_x = align ? align_corners_src(ow, W_in, W_out)
202 88 : (ow + 0.5) * sx - 0.5;
203 116 const int x0 = static_cast<int>(std::floor(src_x));
204 116 const float fx = static_cast<float>(src_x - x0);
205
2/2
✓ Branch 0 taken 464 times.
✓ Branch 1 taken 116 times.
580 for (int k = 0; k < 4; ++k) {
206
1/2
✓ Branch 0 taken 464 times.
✗ Branch 1 not taken.
464 col_bicubic[ow].wx[k] = cubic_keys(fx - (k - 1), a);
207
1/2
✓ Branch 0 taken 464 times.
✗ Branch 1 not taken.
464 col_bicubic[ow].ix[k] = clampi(x0 + k - 1, 0, W_in - 1);
208 464 }
209 116 }
210 }
211
212
2/2
✓ Branch 0 taken 92 times.
✓ Branch 1 taken 74 times.
166 for (int n = 0; n < N; ++n) {
213
2/2
✓ Branch 0 taken 192 times.
✓ Branch 1 taken 92 times.
284 for (int c = 0; c < C; ++c) {
214 192 const int xbase = (n * C + c) * H_in * W_in;
215 192 const int ybase = (n * C + c) * H_out * W_out;
216
217
2/2
✓ Branch 0 taken 1895 times.
✓ Branch 1 taken 192 times.
2087 for (int oh = 0; oh < H_out; ++oh) {
218
2/2
✓ Branch 0 taken 23937 times.
✓ Branch 1 taken 1895 times.
25832 for (int ow = 0; ow < W_out; ++ow) {
219
2/2
✓ Branch 0 taken 20955 times.
✓ Branch 1 taken 2982 times.
23937 if (mode == 0) {
220 // nearest — round_half_to_even then clamp.
221 2982 const int iy = row_nearest[oh].iy;
222 2982 const int ix = col_nearest[ow].ix;
223 2982 Yp[ybase + oh * W_out + ow] =
224 2982 Xp[xbase + iy * W_in + ix];
225
2/2
✓ Branch 0 taken 14795 times.
✓ Branch 1 taken 6160 times.
23937 } else if (mode == 1) {
226 // bilinear — 2x2 tap, border-clamped.
227 14795 const RowBilinear& r = row_bilinear[oh];
228 14795 const ColBilinear& cx = col_bilinear[ow];
229 14795 const float v00 = Xp[xbase + r.y0 * W_in + cx.x0];
230 14795 const float v01 = Xp[xbase + r.y0 * W_in + cx.x1];
231 14795 const float v10 = Xp[xbase + r.y1 * W_in + cx.x0];
232 14795 const float v11 = Xp[xbase + r.y1 * W_in + cx.x1];
233 14795 const float top = v00 + (v01 - v00) * cx.fx;
234 14795 const float bot = v10 + (v11 - v10) * cx.fx;
235 14795 Yp[ybase + oh * W_out + ow] =
236 14795 top + (bot - top) * r.fy;
237 14795 } else {
238 // bicubic — 4x4 cubic-convolution, border-clamped.
239 6160 const RowBicubic& r = row_bicubic[oh];
240 6160 const ColBicubic& cx = col_bicubic[ow];
241 6160 float acc = 0.0f;
242
2/2
✓ Branch 0 taken 24640 times.
✓ Branch 1 taken 6160 times.
30800 for (int j = 0; j < 4; ++j) {
243 24640 float row = 0.0f;
244
2/2
✓ Branch 0 taken 98560 times.
✓ Branch 1 taken 24640 times.
123200 for (int i = 0; i < 4; ++i) {
245 98560 row += cx.wx[i] * Xp[xbase + r.iy[j] * W_in + cx.ix[i]];
246 98560 }
247 24640 acc += r.wy[j] * row;
248 24640 }
249 6160 Yp[ybase + oh * W_out + ow] = acc;
250 }
251 23937 }
252 1895 }
253 192 }
254 92 }
255 74 }
256
257 62 void interp2d_forward(const ::brotensor::Tensor& X,
258 int N, int C, int H_in, int W_in,
259 int H_out, int W_out, int mode,
260 ::brotensor::Tensor& Y) {
261 124 interp2d_forward_impl(X, N, C, H_in, W_in, H_out, W_out, mode,
262 62 /*align=*/false, Y, "interp2d_forward");
263 62 }
264
265 12 void interp2d_align_corners_forward(const ::brotensor::Tensor& X,
266 int N, int C, int H_in, int W_in,
267 int H_out, int W_out, int mode,
268 ::brotensor::Tensor& Y) {
269 24 interp2d_forward_impl(X, N, C, H_in, W_in, H_out, W_out, mode,
270 12 /*align=*/true, Y, "interp2d_align_corners_forward");
271 12 }
272
273 // ─── Backward ──────────────────────────────────────────────────────────────
274
275 7 void interp2d_backward(const ::brotensor::Tensor& dY,
276 int N, int C, int H_in, int W_in,
277 int H_out, int W_out, int mode,
278 ::brotensor::Tensor& dX) {
279 7 check_fp32(dY, "interp2d_backward", "dY");
280 7 check_args("interp2d_backward", N, C, H_in, W_in, H_out, W_out, mode,
281 /*allow_bicubic=*/false);
282
283 7 const int cols_in = C * H_in * W_in;
284
1/6
✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
7 if (dX.rows != N || dX.cols != cols_in || dX.dtype != Dtype::FP32) {
285 7 dX.resize(N, cols_in, Dtype::FP32);
286 7 }
287
2/4
✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 7 times.
7 if (N == 0 || cols_in == 0) return;
288
289 7 const float* dYp = dY.host_f32();
290 7 float* dXp = dX.host_f32_mut();
291
292 // Adjoint: zero dX, then scatter each output gradient onto the input
293 // position(s) it sampled — with the same weights as the forward pass.
294 7 const int total_in = N * cols_in;
295
2/2
✓ Branch 0 taken 7060 times.
✓ Branch 1 taken 7 times.
7067 for (int i = 0; i < total_in; ++i) dXp[i] = 0.0f;
296
297
2/4
✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 7 times.
7 if (H_out == 0 || W_out == 0) return;
298
299 7 const double sy = static_cast<double>(H_in) / static_cast<double>(H_out);
300 7 const double sx = static_cast<double>(W_in) / static_cast<double>(W_out);
301
302 // Per-row / per-column tap tables — same rationale as the forward pass:
303 // src_y/src_x and the derived taps depend only on (oh)/(ow), not (n, c).
304 struct RowNearest { int iy; };
305 struct ColNearest { int ix; };
306 struct RowBilinear { int y0, y1; float fy; };
307 struct ColBilinear { int x0, x1; float fx; };
308
309 7 std::vector<RowNearest> row_nearest;
310 7 std::vector<ColNearest> col_nearest;
311 7 std::vector<RowBilinear> row_bilinear;
312 7 std::vector<ColBilinear> col_bilinear;
313
314
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 4 times.
7 if (mode == 0) {
315
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 row_nearest.resize(H_out);
316
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 21 times.
24 for (int oh = 0; oh < H_out; ++oh) {
317 21 const double src_y = (oh + 0.5) * sy - 0.5;
318
1/2
✓ Branch 0 taken 21 times.
✗ Branch 1 not taken.
21 row_nearest[oh].iy = clampi(
319 21 static_cast<int>(std::nearbyint(src_y)), 0, H_in - 1);
320 21 }
321
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 col_nearest.resize(W_out);
322
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 23 times.
26 for (int ow = 0; ow < W_out; ++ow) {
323 23 const double src_x = (ow + 0.5) * sx - 0.5;
324
1/2
✓ Branch 0 taken 23 times.
✗ Branch 1 not taken.
23 col_nearest[ow].ix = clampi(
325 23 static_cast<int>(std::nearbyint(src_x)), 0, W_in - 1);
326 23 }
327 3 } else {
328
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 row_bilinear.resize(H_out);
329
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 46 times.
50 for (int oh = 0; oh < H_out; ++oh) {
330 46 const double src_y = (oh + 0.5) * sy - 0.5;
331 46 const int y0 = static_cast<int>(std::floor(src_y));
332 46 row_bilinear[oh].fy = static_cast<float>(src_y - y0);
333
1/2
✓ Branch 0 taken 46 times.
✗ Branch 1 not taken.
46 row_bilinear[oh].y0 = clampi(y0, 0, H_in - 1);
334
1/2
✓ Branch 0 taken 46 times.
✗ Branch 1 not taken.
46 row_bilinear[oh].y1 = clampi(y0 + 1, 0, H_in - 1);
335 46 }
336
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 col_bilinear.resize(W_out);
337
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 45 times.
49 for (int ow = 0; ow < W_out; ++ow) {
338 45 const double src_x = (ow + 0.5) * sx - 0.5;
339 45 const int x0 = static_cast<int>(std::floor(src_x));
340 45 col_bilinear[ow].fx = static_cast<float>(src_x - x0);
341
1/2
✓ Branch 0 taken 45 times.
✗ Branch 1 not taken.
45 col_bilinear[ow].x0 = clampi(x0, 0, W_in - 1);
342
1/2
✓ Branch 0 taken 45 times.
✗ Branch 1 not taken.
45 col_bilinear[ow].x1 = clampi(x0 + 1, 0, W_in - 1);
343 45 }
344 }
345
346
2/2
✓ Branch 0 taken 14 times.
✓ Branch 1 taken 7 times.
21 for (int n = 0; n < N; ++n) {
347
2/2
✓ Branch 0 taken 50 times.
✓ Branch 1 taken 14 times.
64 for (int c = 0; c < C; ++c) {
348 50 const int xbase = (n * C + c) * H_in * W_in;
349 50 const int ybase = (n * C + c) * H_out * W_out;
350
351
2/2
✓ Branch 0 taken 751 times.
✓ Branch 1 taken 50 times.
801 for (int oh = 0; oh < H_out; ++oh) {
352
2/2
✓ Branch 0 taken 11344 times.
✓ Branch 1 taken 751 times.
12095 for (int ow = 0; ow < W_out; ++ow) {
353 11344 const float g = dYp[ybase + oh * W_out + ow];
354
355
2/2
✓ Branch 0 taken 10650 times.
✓ Branch 1 taken 694 times.
11344 if (mode == 0) {
356 694 const int iy = row_nearest[oh].iy;
357 694 const int ix = col_nearest[ow].ix;
358 694 dXp[xbase + iy * W_in + ix] += g;
359 694 } else {
360 10650 const RowBilinear& r = row_bilinear[oh];
361 10650 const ColBilinear& cx = col_bilinear[ow];
362 10650 const float w00 = (1.0f - r.fy) * (1.0f - cx.fx);
363 10650 const float w01 = (1.0f - r.fy) * cx.fx;
364 10650 const float w10 = r.fy * (1.0f - cx.fx);
365 10650 const float w11 = r.fy * cx.fx;
366 10650 dXp[xbase + r.y0 * W_in + cx.x0] += w00 * g;
367 10650 dXp[xbase + r.y0 * W_in + cx.x1] += w01 * g;
368 10650 dXp[xbase + r.y1 * W_in + cx.x0] += w10 * g;
369 10650 dXp[xbase + r.y1 * W_in + cx.x1] += w11 * g;
370 }
371 11344 }
372 751 }
373 50 }
374 14 }
375 7 }
376
377 } // namespace brotensor::detail::cpu
378