GCC Code Coverage Report


Directory: ./
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 87.1% 61 / 0 / 70
Functions: 75.0% 3 / 0 / 4
Branches: 46.7% 42 / 0 / 90

src/cpu/unfold2d.cpp
Line Branch Exec Source
1 // ─── CPU 2D neighborhood unfold (im2col, spatial-preserving) ────────────────
2 //
3 // FP32 scalar host implementation of unfold2d_forward. For every output pixel
4 // it gathers the kH×kW window around the corresponding input position into a
5 // dedicated channel block — the "keep the spatial grid, add a neighbor axis"
6 // flavour of im2col (DSINE NRN propagation, neighborhood attention, guided /
7 // bilateral filtering), as opposed to torch.nn.Unfold's column-collapse form.
8 //
9 // Layout (NCHW flat, matches pad2d.cpp / interp2d.cpp):
10 // X : ((n*C + c)*H + h)*W + w
11 // Y : ((n*C + (c*kK + k))*H_out + oy)*W_out + ox
12 // with kK = kH*kW, k = ky*kW + kx, and
13 // H_out = (H + pad_top + pad_bottom - kH)/stride_h + 1 (W_out analogous).
14 // Y[n, c, k, oy, ox] = X[n, c, oy*stride_h - pad_top + ky,
15 // ox*stride_w - pad_left + kx]
16 // with out-of-range source positions resolved by `mode`:
17 // 0 = zero, 1 = reflect (no edge repeat), 2 = replicate (clamp to edge).
18 // For stride 1 and pad (kH-1)/2 this is the same-size neighborhood unfold
19 // (H_out == H, W_out == W) DSINE's get_unfold uses with kH=kW=5, mode=2.
20 //
21 // ── ACCUMULATION ── Y OVERWRITTEN. Inference-only: no backward (the bro
22 // pipeline never trains through this), so no adjoint slot.
23
24 #include <brotensor/tensor.h>
25
26 #include <stdexcept>
27 #include <string>
28
29 namespace brotensor::detail::cpu {
30
31 namespace {
32
33 [[noreturn]] inline void fail(const char* op, const std::string& reason) {
34 throw std::runtime_error(std::string("brotensor: ") + op + ": " + reason);
35 }
36
37 5 inline void check_fp32(const ::brotensor::Tensor& t,
38 const char* op, const char* name) {
39
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 if (t.dtype != Dtype::FP32)
40 fail(op, std::string(name) + " must be FP32 (CPU backend is FP32-only)");
41 5 }
42
43 // Map an output-window position to a source index in [0, L), or -1 for a
44 // zero-padded slot. Identical convention to pad2d's pad_src.
45 13152 inline int unf_src(int coord, int L, int mode) {
46
4/4
✓ Branch 0 taken 12452 times.
✓ Branch 1 taken 700 times.
✓ Branch 2 taken 682 times.
✓ Branch 3 taken 11770 times.
13152 if (coord >= 0 && coord < L) return coord;
47
2/2
✓ Branch 0 taken 1182 times.
✓ Branch 1 taken 200 times.
1382 if (mode == 0) return -1; // zero
48
4/4
✓ Branch 0 taken 990 times.
✓ Branch 1 taken 192 times.
✓ Branch 2 taken 486 times.
✓ Branch 3 taken 504 times.
1182 if (mode == 2) return coord < 0 ? 0 : L - 1; // replicate (clamp)
49
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 192 times.
192 if (L == 1) return 0; // reflect, degenerate
50 192 int q = coord;
51 192 const int period = 2 * (L - 1);
52 192 q %= period;
53
2/2
✓ Branch 0 taken 96 times.
✓ Branch 1 taken 96 times.
192 if (q < 0) q += period;
54
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 192 times.
192 return q < L ? q : period - q;
55 13152 }
56
57 } // namespace
58
59 5 void unfold2d_forward(const ::brotensor::Tensor& X,
60 int N, int C, int H, int W,
61 int kH, int kW,
62 int stride_h, int stride_w,
63 int pad_top, int pad_bottom,
64 int pad_left, int pad_right,
65 int mode,
66 ::brotensor::Tensor& Y) {
67 5 const char* op = "unfold2d_forward";
68 5 check_fp32(X, op, "X");
69
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 if (N < 0 || C < 1 || H < 1 || W < 1)
70 fail(op, "C/H/W must be >=1 and N >=0");
71
1/4
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
5 if (kH < 1 || kW < 1) fail(op, "kH/kW must be >=1");
72
1/4
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
5 if (stride_h < 1 || stride_w < 1) fail(op, "stride must be >=1");
73
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 if (pad_top < 0 || pad_bottom < 0 || pad_left < 0 || pad_right < 0)
74 fail(op, "pad counts must be >=0");
75
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 if (mode < 0 || mode > 2)
76 fail(op, "mode must be 0 (zero), 1 (reflect) or 2 (replicate)");
77
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 if (X.rows != N || X.cols != C * H * W)
78 fail(op, "X shape must be (N, C*H*W)");
79
80 5 const int H_out = (H + pad_top + pad_bottom - kH) / stride_h + 1;
81 5 const int W_out = (W + pad_left + pad_right - kW) / stride_w + 1;
82
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 if (H_out < 1 || W_out < 1)
83 fail(op, "kernel/padding/stride yield empty output");
84 5 const int kK = kH * kW;
85 5 const int cols_out = C * kK * H_out * W_out;
86
1/6
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
5 if (Y.rows != N || Y.cols != cols_out || Y.dtype != Dtype::FP32)
87 5 Y.resize(N, cols_out, Dtype::FP32);
88
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 if (N == 0) return;
89
90 5 const float* Xp = X.host_f32();
91 5 float* Yp = Y.host_f32_mut();
92
93
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 6 times.
11 for (int n = 0; n < N; ++n) {
94
2/2
✓ Branch 0 taken 16 times.
✓ Branch 1 taken 6 times.
22 for (int c = 0; c < C; ++c) {
95 16 const float* x_chan = Xp + (static_cast<long>(n) * C + c) * H * W;
96
2/2
✓ Branch 0 taken 51 times.
✓ Branch 1 taken 16 times.
67 for (int ky = 0; ky < kH; ++ky) {
97
2/2
✓ Branch 0 taken 177 times.
✓ Branch 1 taken 51 times.
228 for (int kx = 0; kx < kW; ++kx) {
98 177 const int k = ky * kW + kx;
99 354 float* y_blk = Yp +
100 354 ((static_cast<long>(n) * C + c) * kK + k) *
101 354 H_out * W_out;
102
2/2
✓ Branch 0 taken 1371 times.
✓ Branch 1 taken 177 times.
1548 for (int oy = 0; oy < H_out; ++oy) {
103 2742 const int sy = unf_src(oy * stride_h - pad_top + ky,
104 1371 H, mode);
105 1371 float* y_row = y_blk + static_cast<long>(oy) * W_out;
106
2/2
✓ Branch 0 taken 1347 times.
✓ Branch 1 taken 24 times.
1371 if (sy < 0) {
107
2/2
✓ Branch 0 taken 192 times.
✓ Branch 1 taken 24 times.
216 for (int ox = 0; ox < W_out; ++ox) y_row[ox] = 0.0f;
108 24 continue;
109 }
110 1347 const float* x_row = x_chan + static_cast<long>(sy) * W;
111
2/2
✓ Branch 0 taken 11781 times.
✓ Branch 1 taken 1347 times.
13128 for (int ox = 0; ox < W_out; ++ox) {
112 23562 const int sx = unf_src(ox * stride_w - pad_left + kx,
113 11781 W, mode);
114
2/2
✓ Branch 0 taken 11605 times.
✓ Branch 1 taken 176 times.
11781 y_row[ox] = sx < 0 ? 0.0f : x_row[sx];
115 11781 }
116 1347 }
117 177 }
118 51 }
119 16 }
120 6 }
121 5 }
122
123 } // namespace brotensor::detail::cpu
124