src/cpu/resample.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | // ─── CPU resample ops (CHUNK 4) ──────────────────────────────────────────── | ||
| 2 | // | ||
| 3 | // FP32 scalar host implementations. Ports src/cuda/resample.cu — FP32 path | ||
| 4 | // only (CPU is FP32-only). 2x spatial nearest / bilinear upsample, 2x average | ||
| 5 | // downsample, plus their backward passes. All tensors NCHW row-major. | ||
| 6 | // | ||
| 7 | // Memory layout (matches the GPU exactly): | ||
| 8 | // X / Y / dX / dY : NCHW — ((n*C + c) * H + h) * W + w | ||
| 9 | // Upsample: H_out = 2*H, W_out = 2*W. | ||
| 10 | // Downsample: H_out = H/2, W_out = W/2 (H, W must be even). | ||
| 11 | // | ||
| 12 | // Bilinear sampling convention (matches the GPU kernel verbatim — HALF-PIXEL, | ||
| 13 | // NOT align-corners): | ||
| 14 | // src_y = (oh + 0.5) * 0.5 - 0.5 (scale 0.5 because output is 2x input) | ||
| 15 | // src_x = (ow + 0.5) * 0.5 - 0.5 | ||
| 16 | // y0 = floor(src_y), fy = src_y - y0 (then border-clamped indices) | ||
| 17 | // value = bilinear interp of the 4 clamped neighbours. | ||
| 18 | // | ||
| 19 | // ACCUMULATION (matches the GPU kernels): | ||
| 20 | // upsample_nearest_2x — Y OVERWRITTEN. | ||
| 21 | // upsample_bilinear_2x — Y OVERWRITTEN. | ||
| 22 | // downsample_avg_2x — Y OVERWRITTEN. | ||
| 23 | // upsample_nearest_2x_backward — dX OVERWRITTEN (gather of 4 dY values). | ||
| 24 | // upsample_bilinear_2x_backward— dX OVERWRITTEN (GPU memsets dX to 0 then | ||
| 25 | // atomic-scatters; net effect = overwrite). | ||
| 26 | // downsample_avg_2x_backward — dX OVERWRITTEN. | ||
| 27 | |||
| 28 | #include <brotensor/tensor.h> | ||
| 29 | |||
| 30 | #include <cmath> | ||
| 31 | #include <stdexcept> | ||
| 32 | #include <string> | ||
| 33 | #include <vector> | ||
| 34 | |||
| 35 | namespace brotensor::detail::cpu { | ||
| 36 | |||
| 37 | namespace { | ||
| 38 | |||
| 39 | 41 | inline void check_fp32(const ::brotensor::Tensor& t, | |
| 40 | const char* op, const char* name) { | ||
| 41 |
1/2✓ Branch 0 taken 41 times.
✗ Branch 1 not taken.
|
41 | if (t.dtype != Dtype::FP32) { |
| 42 | ✗ | throw std::runtime_error(std::string(op) + ": " + name + | |
| 43 | " must be FP32 (CPU backend is FP32-only)"); | ||
| 44 | } | ||
| 45 | 41 | } | |
| 46 | |||
| 47 | 692 | inline int clampi(int v, int lo, int hi) { | |
| 48 |
4/4✓ Branch 0 taken 28 times.
✓ Branch 1 taken 664 times.
✓ Branch 2 taken 28 times.
✓ Branch 3 taken 636 times.
|
692 | return v < lo ? lo : (v > hi ? hi : v); |
| 49 | } | ||
| 50 | |||
| 51 | } // namespace | ||
| 52 | |||
| 53 | // ─── Forward ─────────────────────────────────────────────────────────────── | ||
| 54 | |||
| 55 | 8 | void upsample_nearest_2x(const ::brotensor::Tensor& X, | |
| 56 | int N, int C, int H, int W, | ||
| 57 | ::brotensor::Tensor& Y) { | ||
| 58 | 8 | check_fp32(X, "upsample_nearest_2x", "X"); | |
| 59 | 8 | const int H_out = 2 * H, W_out = 2 * W; | |
| 60 | 8 | const int cols = C * H_out * W_out; | |
| 61 |
1/6✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
|
8 | if (Y.rows != N || Y.cols != cols || Y.dtype != Dtype::FP32) { |
| 62 | 8 | Y.resize(N, cols, Dtype::FP32); | |
| 63 | 8 | } | |
| 64 |
2/4✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 8 times.
|
8 | if (N == 0 || cols == 0) return; |
| 65 | |||
| 66 | 8 | const float* Xp = X.host_f32(); | |
| 67 | 8 | float* Yp = Y.host_f32_mut(); | |
| 68 | |||
| 69 |
2/2✓ Branch 0 taken 8 times.
✓ Branch 1 taken 13 times.
|
21 | for (int n = 0; n < N; ++n) { |
| 70 |
2/2✓ Branch 0 taken 60 times.
✓ Branch 1 taken 13 times.
|
73 | for (int c = 0; c < C; ++c) { |
| 71 |
2/2✓ Branch 0 taken 736 times.
✓ Branch 1 taken 60 times.
|
796 | for (int oh = 0; oh < H_out; ++oh) { |
| 72 | 736 | const int ih = oh / 2; | |
| 73 |
2/2✓ Branch 0 taken 11872 times.
✓ Branch 1 taken 736 times.
|
12608 | for (int ow = 0; ow < W_out; ++ow) { |
| 74 | 11872 | const int iw = ow / 2; | |
| 75 | 11872 | Yp[((n * C + c) * H_out + oh) * W_out + ow] = | |
| 76 | 11872 | Xp[((n * C + c) * H + ih) * W + iw]; | |
| 77 | 11872 | } | |
| 78 | 736 | } | |
| 79 | 60 | } | |
| 80 | 13 | } | |
| 81 | 8 | } | |
| 82 | |||
| 83 | 8 | void upsample_bilinear_2x(const ::brotensor::Tensor& X, | |
| 84 | int N, int C, int H, int W, | ||
| 85 | ::brotensor::Tensor& Y) { | ||
| 86 | 8 | check_fp32(X, "upsample_bilinear_2x", "X"); | |
| 87 | 8 | const int H_out = 2 * H, W_out = 2 * W; | |
| 88 | 8 | const int cols = C * H_out * W_out; | |
| 89 |
1/6✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
|
8 | if (Y.rows != N || Y.cols != cols || Y.dtype != Dtype::FP32) { |
| 90 | 8 | Y.resize(N, cols, Dtype::FP32); | |
| 91 | 8 | } | |
| 92 |
2/4✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 8 times.
|
8 | if (N == 0 || cols == 0) return; |
| 93 | |||
| 94 | 8 | const float* Xp = X.host_f32(); | |
| 95 | 8 | float* Yp = Y.host_f32_mut(); | |
| 96 | |||
| 97 | // src_y/src_x and the derived taps depend only on (oh)/(ow), never on | ||
| 98 | // (n, c) — precompute once instead of redoing it inside the (n, c) loop. | ||
| 99 | struct RowTap { int y0, y1; float fy; }; | ||
| 100 | struct ColTap { int x0, x1; float fx; }; | ||
| 101 | 8 | std::vector<RowTap> row_tap(H_out); | |
| 102 |
2/2✓ Branch 0 taken 8 times.
✓ Branch 1 taken 88 times.
|
96 | for (int oh = 0; oh < H_out; ++oh) { |
| 103 | 88 | const float src_y = (oh + 0.5f) * 0.5f - 0.5f; | |
| 104 | 88 | const int y0 = static_cast<int>(std::floor(src_y)); | |
| 105 | 88 | row_tap[oh].fy = src_y - y0; | |
| 106 |
1/2✓ Branch 0 taken 88 times.
✗ Branch 1 not taken.
|
88 | row_tap[oh].y0 = clampi(y0, 0, H - 1); |
| 107 |
1/2✓ Branch 0 taken 88 times.
✗ Branch 1 not taken.
|
88 | row_tap[oh].y1 = clampi(y0 + 1, 0, H - 1); |
| 108 | 88 | } | |
| 109 |
1/2✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
|
8 | std::vector<ColTap> col_tap(W_out); |
| 110 |
2/2✓ Branch 0 taken 8 times.
✓ Branch 1 taken 106 times.
|
114 | for (int ow = 0; ow < W_out; ++ow) { |
| 111 | 106 | const float src_x = (ow + 0.5f) * 0.5f - 0.5f; | |
| 112 | 106 | const int x0 = static_cast<int>(std::floor(src_x)); | |
| 113 | 106 | col_tap[ow].fx = src_x - x0; | |
| 114 |
1/2✓ Branch 0 taken 106 times.
✗ Branch 1 not taken.
|
106 | col_tap[ow].x0 = clampi(x0, 0, W - 1); |
| 115 |
1/2✓ Branch 0 taken 106 times.
✗ Branch 1 not taken.
|
106 | col_tap[ow].x1 = clampi(x0 + 1, 0, W - 1); |
| 116 | 106 | } | |
| 117 | |||
| 118 |
2/2✓ Branch 0 taken 13 times.
✓ Branch 1 taken 8 times.
|
21 | for (int n = 0; n < N; ++n) { |
| 119 |
2/2✓ Branch 0 taken 60 times.
✓ Branch 1 taken 13 times.
|
73 | for (int c = 0; c < C; ++c) { |
| 120 | 60 | const int base = (n * C + c) * H; | |
| 121 |
2/2✓ Branch 0 taken 736 times.
✓ Branch 1 taken 60 times.
|
796 | for (int oh = 0; oh < H_out; ++oh) { |
| 122 | 736 | const RowTap& r = row_tap[oh]; | |
| 123 |
2/2✓ Branch 0 taken 11872 times.
✓ Branch 1 taken 736 times.
|
12608 | for (int ow = 0; ow < W_out; ++ow) { |
| 124 | 11872 | const ColTap& cx = col_tap[ow]; | |
| 125 | 11872 | const float v00 = Xp[(base + r.y0) * W + cx.x0]; | |
| 126 | 11872 | const float v01 = Xp[(base + r.y0) * W + cx.x1]; | |
| 127 | 11872 | const float v10 = Xp[(base + r.y1) * W + cx.x0]; | |
| 128 | 11872 | const float v11 = Xp[(base + r.y1) * W + cx.x1]; | |
| 129 | 11872 | const float top = v00 + (v01 - v00) * cx.fx; | |
| 130 | 11872 | const float bot = v10 + (v11 - v10) * cx.fx; | |
| 131 | 11872 | Yp[((n * C + c) * H_out + oh) * W_out + ow] = | |
| 132 | 11872 | top + (bot - top) * r.fy; | |
| 133 | 11872 | } | |
| 134 | 736 | } | |
| 135 | 60 | } | |
| 136 | 13 | } | |
| 137 | 8 | } | |
| 138 | |||
| 139 | 7 | void downsample_avg_2x(const ::brotensor::Tensor& X, | |
| 140 | int N, int C, int H, int W, | ||
| 141 | ::brotensor::Tensor& Y) { | ||
| 142 | 7 | check_fp32(X, "downsample_avg_2x", "X"); | |
| 143 |
1/2✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
|
7 | if ((H & 1) || (W & 1)) { |
| 144 | ✗ | throw std::runtime_error("downsample_avg_2x: H and W must be even"); | |
| 145 | } | ||
| 146 | 7 | const int H_out = H / 2, W_out = W / 2; | |
| 147 | 7 | const int cols = C * H_out * W_out; | |
| 148 |
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 (Y.rows != N || Y.cols != cols || Y.dtype != Dtype::FP32) { |
| 149 | 7 | Y.resize(N, cols, Dtype::FP32); | |
| 150 | 7 | } | |
| 151 |
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 == 0) return; |
| 152 | |||
| 153 | 7 | const float* Xp = X.host_f32(); | |
| 154 | 7 | float* Yp = Y.host_f32_mut(); | |
| 155 | |||
| 156 |
2/2✓ Branch 0 taken 7 times.
✓ Branch 1 taken 11 times.
|
18 | for (int n = 0; n < N; ++n) { |
| 157 |
2/2✓ Branch 0 taken 54 times.
✓ Branch 1 taken 11 times.
|
65 | for (int c = 0; c < C; ++c) { |
| 158 |
2/2✓ Branch 0 taken 172 times.
✓ Branch 1 taken 54 times.
|
226 | for (int oh = 0; oh < H_out; ++oh) { |
| 159 | 172 | const int ih = oh * 2; | |
| 160 |
2/2✓ Branch 0 taken 712 times.
✓ Branch 1 taken 172 times.
|
884 | for (int ow = 0; ow < W_out; ++ow) { |
| 161 | 712 | const int iw = ow * 2; | |
| 162 | 712 | const int b = ((n * C + c) * H + ih) * W + iw; | |
| 163 | 712 | Yp[((n * C + c) * H_out + oh) * W_out + ow] = | |
| 164 | 2136 | 0.25f * (Xp[b] + Xp[b + 1] + | |
| 165 | 1424 | Xp[b + W] + Xp[b + W + 1]); | |
| 166 | 712 | } | |
| 167 | 172 | } | |
| 168 | 54 | } | |
| 169 | 11 | } | |
| 170 | 7 | } | |
| 171 | |||
| 172 | // ─── Backward ────────────────────────────────────────────────────────────── | ||
| 173 | |||
| 174 | 6 | void upsample_nearest_2x_backward(const ::brotensor::Tensor& dY, | |
| 175 | int N, int C, int H, int W, | ||
| 176 | ::brotensor::Tensor& dX) { | ||
| 177 | 6 | check_fp32(dY, "upsample_nearest_2x_backward", "dY"); | |
| 178 | 6 | const int H_out = 2 * H, W_out = 2 * W; | |
| 179 | 6 | const int cols_in = C * H * W; | |
| 180 |
1/6✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
|
6 | if (dX.rows != N || dX.cols != cols_in || dX.dtype != Dtype::FP32) { |
| 181 | 6 | dX.resize(N, cols_in, Dtype::FP32); | |
| 182 | 6 | } | |
| 183 |
2/4✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
|
6 | if (N == 0 || cols_in == 0) return; |
| 184 | |||
| 185 | 6 | const float* dYp = dY.host_f32(); | |
| 186 | 6 | float* dXp = dX.host_f32_mut(); | |
| 187 | |||
| 188 | // One accumulation per input pixel; gather the 4 contributing dY values. | ||
| 189 |
2/2✓ Branch 0 taken 6 times.
✓ Branch 1 taken 10 times.
|
16 | for (int n = 0; n < N; ++n) { |
| 190 |
2/2✓ Branch 0 taken 46 times.
✓ Branch 1 taken 10 times.
|
56 | for (int c = 0; c < C; ++c) { |
| 191 |
2/2✓ Branch 0 taken 312 times.
✓ Branch 1 taken 46 times.
|
358 | for (int ih = 0; ih < H; ++ih) { |
| 192 |
2/2✓ Branch 0 taken 2592 times.
✓ Branch 1 taken 312 times.
|
2904 | for (int iw = 0; iw < W; ++iw) { |
| 193 | 2592 | float s = 0.0f; | |
| 194 |
2/2✓ Branch 0 taken 5184 times.
✓ Branch 1 taken 2592 times.
|
7776 | for (int a = 0; a < 2; ++a) { |
| 195 |
2/2✓ Branch 0 taken 10368 times.
✓ Branch 1 taken 5184 times.
|
15552 | for (int b = 0; b < 2; ++b) { |
| 196 | 31104 | s += dYp[((n * C + c) * H_out + 2 * ih + a) * | |
| 197 | 20736 | W_out + 2 * iw + b]; | |
| 198 | 10368 | } | |
| 199 | 5184 | } | |
| 200 | 2592 | dXp[((n * C + c) * H + ih) * W + iw] = s; // overwrite | |
| 201 | 2592 | } | |
| 202 | 312 | } | |
| 203 | 46 | } | |
| 204 | 10 | } | |
| 205 | 6 | } | |
| 206 | |||
| 207 | 6 | void upsample_bilinear_2x_backward(const ::brotensor::Tensor& dY, | |
| 208 | int N, int C, int H, int W, | ||
| 209 | ::brotensor::Tensor& dX) { | ||
| 210 | 6 | check_fp32(dY, "upsample_bilinear_2x_backward", "dY"); | |
| 211 | 6 | const int H_out = 2 * H, W_out = 2 * W; | |
| 212 | 6 | const int cols_in = C * H * W; | |
| 213 |
1/6✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
|
6 | if (dX.rows != N || dX.cols != cols_in || dX.dtype != Dtype::FP32) { |
| 214 | 6 | dX.resize(N, cols_in, Dtype::FP32); | |
| 215 | 6 | } | |
| 216 |
2/4✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 6 times.
|
6 | if (N == 0 || cols_in == 0) return; |
| 217 | |||
| 218 | 6 | const float* dYp = dY.host_f32(); | |
| 219 | 6 | float* dXp = dX.host_f32_mut(); | |
| 220 | |||
| 221 | // GPU memsets dX to 0 then atomic-scatters — net effect is OVERWRITE. | ||
| 222 | 6 | const int total_in = N * cols_in; | |
| 223 |
2/2✓ Branch 0 taken 2592 times.
✓ Branch 1 taken 6 times.
|
2598 | for (int i = 0; i < total_in; ++i) dXp[i] = 0.0f; |
| 224 | |||
| 225 | // Same per-row / per-column tap hoist as the forward pass. | ||
| 226 | struct RowTap { int y0, y1; float fy; }; | ||
| 227 | struct ColTap { int x0, x1; float fx; }; | ||
| 228 | 6 | std::vector<RowTap> row_tap(H_out); | |
| 229 |
2/2✓ Branch 0 taken 6 times.
✓ Branch 1 taken 72 times.
|
78 | for (int oh = 0; oh < H_out; ++oh) { |
| 230 | 72 | const float src_y = (oh + 0.5f) * 0.5f - 0.5f; | |
| 231 | 72 | const int y0 = static_cast<int>(std::floor(src_y)); | |
| 232 | 72 | row_tap[oh].fy = src_y - y0; | |
| 233 |
1/2✓ Branch 0 taken 72 times.
✗ Branch 1 not taken.
|
72 | row_tap[oh].y0 = clampi(y0, 0, H - 1); |
| 234 |
1/2✓ Branch 0 taken 72 times.
✗ Branch 1 not taken.
|
72 | row_tap[oh].y1 = clampi(y0 + 1, 0, H - 1); |
| 235 | 72 | } | |
| 236 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | std::vector<ColTap> col_tap(W_out); |
| 237 |
2/2✓ Branch 0 taken 6 times.
✓ Branch 1 taken 80 times.
|
86 | for (int ow = 0; ow < W_out; ++ow) { |
| 238 | 80 | const float src_x = (ow + 0.5f) * 0.5f - 0.5f; | |
| 239 | 80 | const int x0 = static_cast<int>(std::floor(src_x)); | |
| 240 | 80 | col_tap[ow].fx = src_x - x0; | |
| 241 |
1/2✓ Branch 0 taken 80 times.
✗ Branch 1 not taken.
|
80 | col_tap[ow].x0 = clampi(x0, 0, W - 1); |
| 242 |
1/2✓ Branch 0 taken 80 times.
✗ Branch 1 not taken.
|
80 | col_tap[ow].x1 = clampi(x0 + 1, 0, W - 1); |
| 243 | 80 | } | |
| 244 | |||
| 245 |
2/2✓ Branch 0 taken 10 times.
✓ Branch 1 taken 6 times.
|
16 | for (int n = 0; n < N; ++n) { |
| 246 |
2/2✓ Branch 0 taken 46 times.
✓ Branch 1 taken 10 times.
|
56 | for (int c = 0; c < C; ++c) { |
| 247 | 46 | const int base = (n * C + c) * H; | |
| 248 |
2/2✓ Branch 0 taken 624 times.
✓ Branch 1 taken 46 times.
|
670 | for (int oh = 0; oh < H_out; ++oh) { |
| 249 | 624 | const RowTap& r = row_tap[oh]; | |
| 250 |
2/2✓ Branch 0 taken 10368 times.
✓ Branch 1 taken 624 times.
|
10992 | for (int ow = 0; ow < W_out; ++ow) { |
| 251 | 10368 | const ColTap& cx = col_tap[ow]; | |
| 252 | 10368 | const float w00 = (1.0f - r.fy) * (1.0f - cx.fx); | |
| 253 | 10368 | const float w01 = (1.0f - r.fy) * cx.fx; | |
| 254 | 10368 | const float w10 = r.fy * (1.0f - cx.fx); | |
| 255 | 10368 | const float w11 = r.fy * cx.fx; | |
| 256 | 10368 | const float g = | |
| 257 | 10368 | dYp[((n * C + c) * H_out + oh) * W_out + ow]; | |
| 258 | 10368 | dXp[(base + r.y0) * W + cx.x0] += w00 * g; | |
| 259 | 10368 | dXp[(base + r.y0) * W + cx.x1] += w01 * g; | |
| 260 | 10368 | dXp[(base + r.y1) * W + cx.x0] += w10 * g; | |
| 261 | 10368 | dXp[(base + r.y1) * W + cx.x1] += w11 * g; | |
| 262 | 10368 | } | |
| 263 | 624 | } | |
| 264 | 46 | } | |
| 265 | 10 | } | |
| 266 | 6 | } | |
| 267 | |||
| 268 | 6 | void downsample_avg_2x_backward(const ::brotensor::Tensor& dY, | |
| 269 | int N, int C, int H, int W, | ||
| 270 | ::brotensor::Tensor& dX) { | ||
| 271 | 6 | check_fp32(dY, "downsample_avg_2x_backward", "dY"); | |
| 272 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | if ((H & 1) || (W & 1)) { |
| 273 | ✗ | throw std::runtime_error("downsample_avg_2x_backward: H and W must be even"); | |
| 274 | } | ||
| 275 | 6 | const int H_out = H / 2, W_out = W / 2; | |
| 276 | 6 | const int cols_in = C * H * W; | |
| 277 |
1/6✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
|
6 | if (dX.rows != N || dX.cols != cols_in || dX.dtype != Dtype::FP32) { |
| 278 | 6 | dX.resize(N, cols_in, Dtype::FP32); | |
| 279 | 6 | } | |
| 280 |
2/4✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 6 times.
|
6 | if (N == 0 || cols_in == 0) return; |
| 281 | |||
| 282 | 6 | const float* dYp = dY.host_f32(); | |
| 283 | 6 | float* dXp = dX.host_f32_mut(); | |
| 284 | |||
| 285 | // One read per input pixel; each maps to a single output pixel, scale 1/4. | ||
| 286 |
2/2✓ Branch 0 taken 6 times.
✓ Branch 1 taken 10 times.
|
16 | for (int n = 0; n < N; ++n) { |
| 287 |
2/2✓ Branch 0 taken 46 times.
✓ Branch 1 taken 10 times.
|
56 | for (int c = 0; c < C; ++c) { |
| 288 |
2/2✓ Branch 0 taken 312 times.
✓ Branch 1 taken 46 times.
|
358 | for (int ih = 0; ih < H; ++ih) { |
| 289 | 312 | const int oh = ih / 2; | |
| 290 |
2/2✓ Branch 0 taken 2592 times.
✓ Branch 1 taken 312 times.
|
2904 | for (int iw = 0; iw < W; ++iw) { |
| 291 | 2592 | const int ow = iw / 2; | |
| 292 | 2592 | dXp[((n * C + c) * H + ih) * W + iw] = | |
| 293 | 2592 | 0.25f * dYp[((n * C + c) * H_out + oh) * W_out + ow]; | |
| 294 | 2592 | } | |
| 295 | 312 | } | |
| 296 | 46 | } | |
| 297 | 10 | } | |
| 298 | 6 | } | |
| 299 | |||
| 300 | } // namespace brotensor::detail::cpu | ||
| 301 |