GCC Code Coverage Report


Directory: brotensor/
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 98.7% 229 / 0 / 232
Functions: 100.0% 9 / 0 / 9
Branches: 60.2% 148 / 0 / 246

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