GCC Code Coverage Report


Directory: ./
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 90.7% 88 / 0 / 97
Functions: 100.0% 3 / 0 / 3
Branches: 51.1% 48 / 0 / 94

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