src/cpu/concat.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | // ─── CPU concat ops (CHUNK 1) ────────────────────────────────────────────── | ||
| 2 | // | ||
| 3 | // FP32 scalar host implementations. Ports the GPU kernels in | ||
| 4 | // src/cuda/concat.cu — same layout/contracts, FP32 path only. | ||
| 5 | // | ||
| 6 | // concat_batched_rows — concat parts along the column axis; all | ||
| 7 | // parts share row count B, out is | ||
| 8 | // (B, sum cols). | ||
| 9 | // concat_nchw_channels — concat NCHW tensors along the channel | ||
| 10 | // axis; out is (N, total_C*H*W). | ||
| 11 | // concat_nchw_channels_backward— inverse of concat_nchw_channels; slices | ||
| 12 | // dY back into per-part tensors (overwrite). | ||
| 13 | |||
| 14 | #include <brotensor/tensor.h> | ||
| 15 | |||
| 16 | #include <cstddef> | ||
| 17 | #include <stdexcept> | ||
| 18 | #include <vector> | ||
| 19 | |||
| 20 | namespace brotensor::detail::cpu { | ||
| 21 | |||
| 22 | 7 | void concat_batched_rows(const std::vector<const ::brotensor::Tensor*>& parts, | |
| 23 | ::brotensor::Tensor& out) { | ||
| 24 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
|
7 | if (parts.empty()) { out.resize(0, 0, ::brotensor::Dtype::FP32); return; } |
| 25 | 7 | int B = 0; | |
| 26 | 7 | int total_cols = 0; | |
| 27 |
2/2✓ Branch 0 taken 17 times.
✓ Branch 1 taken 7 times.
|
24 | for (const auto* p : parts) { |
| 28 |
1/2✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
|
17 | if (!p) continue; |
| 29 |
2/2✓ Branch 0 taken 10 times.
✓ Branch 1 taken 7 times.
|
17 | if (B == 0) B = p->rows; |
| 30 | 17 | total_cols += p->cols; | |
| 31 | } | ||
| 32 |
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 (out.rows != B || out.cols != total_cols || |
| 33 | ✗ | out.dtype != ::brotensor::Dtype::FP32) { | |
| 34 | 7 | out.resize(B, total_cols, ::brotensor::Dtype::FP32); | |
| 35 | 7 | } | |
| 36 |
2/4✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 7 times.
|
7 | if (B == 0 || total_cols == 0) return; |
| 37 | |||
| 38 | 7 | float* op = out.host_f32_mut(); | |
| 39 | 7 | int col_off = 0; | |
| 40 |
2/2✓ Branch 0 taken 7 times.
✓ Branch 1 taken 17 times.
|
24 | for (const auto* p : parts) { |
| 41 |
1/2✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
|
17 | if (!p) continue; |
| 42 | 17 | const int d = p->cols; | |
| 43 |
1/2✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
|
17 | if (d == 0) continue; |
| 44 | 17 | const float* pp = p->host_f32(); | |
| 45 |
2/2✓ Branch 0 taken 131 times.
✓ Branch 1 taken 17 times.
|
148 | for (int b = 0; b < B; ++b) { |
| 46 | 131 | const float* src = pp + static_cast<std::size_t>(b) * d; | |
| 47 | 131 | float* dst = op + static_cast<std::size_t>(b) * total_cols + col_off; | |
| 48 |
2/2✓ Branch 0 taken 1838 times.
✓ Branch 1 taken 131 times.
|
1969 | for (int j = 0; j < d; ++j) dst[j] = src[j]; |
| 49 | 131 | } | |
| 50 | 17 | col_off += d; | |
| 51 | } | ||
| 52 | 7 | } | |
| 53 | |||
| 54 | 10 | void concat_nchw_channels(const std::vector<const ::brotensor::Tensor*>& parts, | |
| 55 | int N, int H, int W, | ||
| 56 | const std::vector<int>& C_per_part, | ||
| 57 | ::brotensor::Tensor& out) { | ||
| 58 |
1/2✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
|
10 | if (parts.size() != C_per_part.size()) { |
| 59 | ✗ | throw std::runtime_error("concat_nchw_channels: parts.size() != C_per_part.size()"); | |
| 60 | } | ||
| 61 | 10 | int total_C = 0; | |
| 62 |
2/2✓ Branch 0 taken 20 times.
✓ Branch 1 taken 10 times.
|
30 | for (std::size_t i = 0; i < parts.size(); ++i) { |
| 63 | 20 | const auto* p = parts[i]; | |
| 64 | 20 | const int Ci = C_per_part[i]; | |
| 65 |
1/4✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
|
20 | if (!p) throw std::runtime_error("concat_nchw_channels: null part"); |
| 66 |
2/4✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 20 times.
✗ Branch 3 not taken.
|
40 | if (p->size() != static_cast<int>( |
| 67 | 20 | static_cast<std::size_t>(N) * Ci * H * W)) { | |
| 68 | ✗ | throw std::runtime_error("concat_nchw_channels: part size mismatch (expected N*C_i*H*W)"); | |
| 69 | } | ||
| 70 | 20 | total_C += Ci; | |
| 71 | 20 | } | |
| 72 | 10 | const int total_cols = total_C * H * W; | |
| 73 |
1/6✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
|
10 | if (out.rows != N || out.cols != total_cols || |
| 74 | ✗ | out.dtype != ::brotensor::Dtype::FP32) { | |
| 75 | 10 | out.resize(N, total_cols, ::brotensor::Dtype::FP32); | |
| 76 | 10 | } | |
| 77 |
2/4✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 10 times.
|
10 | if (N == 0 || total_cols == 0) return; |
| 78 | |||
| 79 | 10 | const std::size_t HW = static_cast<std::size_t>(H) * W; | |
| 80 | 10 | float* op = out.host_f32_mut(); | |
| 81 | 10 | std::size_t c_off = 0; // running channel offset | |
| 82 |
2/2✓ Branch 0 taken 10 times.
✓ Branch 1 taken 20 times.
|
30 | for (std::size_t i = 0; i < parts.size(); ++i) { |
| 83 | 20 | const int Ci = C_per_part[i]; | |
| 84 |
1/2✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
|
20 | if (Ci == 0) continue; |
| 85 | 20 | const std::size_t part_chunk = static_cast<std::size_t>(Ci) * HW; | |
| 86 | 20 | const float* pp = parts[i]->host_f32(); | |
| 87 |
2/2✓ Branch 0 taken 44 times.
✓ Branch 1 taken 20 times.
|
64 | for (int n = 0; n < N; ++n) { |
| 88 | 44 | const float* src = pp + static_cast<std::size_t>(n) * part_chunk; | |
| 89 | 88 | float* dst = op + static_cast<std::size_t>(n) * total_cols | |
| 90 | 44 | + c_off * HW; | |
| 91 |
2/2✓ Branch 0 taken 4283 times.
✓ Branch 1 taken 44 times.
|
4327 | for (std::size_t k = 0; k < part_chunk; ++k) dst[k] = src[k]; |
| 92 | 44 | } | |
| 93 | 20 | c_off += static_cast<std::size_t>(Ci); | |
| 94 | 20 | } | |
| 95 | 10 | } | |
| 96 | |||
| 97 | 10 | void concat_nchw_channels_backward(const ::brotensor::Tensor& dY, | |
| 98 | int N, int H, int W, | ||
| 99 | const std::vector<int>& C_per_part, | ||
| 100 | const std::vector<::brotensor::Tensor*>& parts) { | ||
| 101 |
1/2✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
|
10 | if (parts.size() != C_per_part.size()) { |
| 102 | ✗ | throw std::runtime_error("concat_nchw_channels_backward: parts.size() != C_per_part.size()"); | |
| 103 | } | ||
| 104 | 10 | int total_C = 0; | |
| 105 |
2/2✓ Branch 0 taken 20 times.
✓ Branch 1 taken 10 times.
|
30 | for (int Ci : C_per_part) total_C += Ci; |
| 106 | 10 | const int expected_cols = total_C * H * W; | |
| 107 |
1/2✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
|
10 | if (dY.rows != N || dY.cols != expected_cols) { |
| 108 | ✗ | throw std::runtime_error("concat_nchw_channels_backward: dY shape mismatch (expected N x total_C*H*W)"); | |
| 109 | } | ||
| 110 | |||
| 111 | 10 | const std::size_t HW = static_cast<std::size_t>(H) * W; | |
| 112 | 10 | const float* dyp = dY.host_f32(); | |
| 113 | 10 | std::size_t c_off = 0; | |
| 114 |
2/2✓ Branch 0 taken 20 times.
✓ Branch 1 taken 10 times.
|
30 | for (std::size_t i = 0; i < parts.size(); ++i) { |
| 115 | 20 | ::brotensor::Tensor* p = parts[i]; | |
| 116 | 20 | const int Ci = C_per_part[i]; | |
| 117 |
1/4✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
|
20 | if (!p) throw std::runtime_error("concat_nchw_channels_backward: null part"); |
| 118 | 20 | const int cols = Ci * H * W; | |
| 119 |
1/6✗ Branch 0 not taken.
✓ Branch 1 taken 20 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
|
20 | if (p->rows != N || p->cols != cols || |
| 120 | ✗ | p->dtype != ::brotensor::Dtype::FP32) { | |
| 121 | 20 | p->resize(N, cols, ::brotensor::Dtype::FP32); | |
| 122 | 20 | } | |
| 123 |
3/6✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 20 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 20 times.
|
20 | if (Ci == 0 || N == 0 || HW == 0) { |
| 124 | ✗ | c_off += static_cast<std::size_t>(Ci); | |
| 125 | ✗ | continue; | |
| 126 | } | ||
| 127 | 20 | const std::size_t part_chunk = static_cast<std::size_t>(Ci) * HW; | |
| 128 | 20 | float* pp = p->host_f32_mut(); | |
| 129 |
2/2✓ Branch 0 taken 44 times.
✓ Branch 1 taken 20 times.
|
64 | for (int n = 0; n < N; ++n) { |
| 130 | 88 | const float* src = dyp + static_cast<std::size_t>(n) * expected_cols | |
| 131 | 44 | + c_off * HW; | |
| 132 | 44 | float* dst = pp + static_cast<std::size_t>(n) * part_chunk; | |
| 133 |
2/2✓ Branch 0 taken 4283 times.
✓ Branch 1 taken 44 times.
|
4327 | for (std::size_t k = 0; k < part_chunk; ++k) dst[k] = src[k]; |
| 134 | 44 | } | |
| 135 | 20 | c_off += static_cast<std::size_t>(Ci); | |
| 136 | 20 | } | |
| 137 | 10 | } | |
| 138 | |||
| 139 | } // namespace brotensor::detail::cpu | ||
| 140 |