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 |