GCC Code Coverage Report


Directory: ./
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 83.8% 31 / 0 / 37
Functions: 66.7% 2 / 0 / 3
Branches: 29.5% 13 / 0 / 44

src/cpu/l2_normalize.cpp
Line Branch Exec Source
1 // ─── CPU L2 normalization over the channel axis (NCHW) ──────────────────────
2 //
3 // FP32 scalar host implementation of l2_normalize_nchw_forward. For every
4 // spatial position (n, h, w), normalizes the length-C channel vector to unit
5 // L2 norm with an epsilon floor on the divisor:
6 // Y[n,c,h,w] = X[n,c,h,w] / max(sqrt(sum_c X[n,c,h,w]^2), eps)
7 // The per-pixel vector normalize used by surface-normal / direction-field
8 // models (DSINE pred-normal normalize), feature-map L2 norm, and cosine-sim
9 // preprocessing. Distinct from l2_norm_forward (gated-deltanet per-head, last
10 // dim of a (L, H*D) layout) — this one is NCHW with the unit axis = channels.
11 //
12 // Reduction accumulates in double for stability; output is FP32.
13 //
14 // ── ACCUMULATION ── Y OVERWRITTEN. X and Y may alias. Inference-only: no
15 // backward (the bro pipeline never trains through this).
16
17 #include <brotensor/tensor.h>
18
19 #include <cmath>
20 #include <stdexcept>
21 #include <string>
22
23 namespace brotensor::detail::cpu {
24
25 namespace {
26
27 [[noreturn]] inline void fail(const char* op, const std::string& reason) {
28 throw std::runtime_error(std::string("brotensor: ") + op + ": " + reason);
29 }
30
31 5 inline void check_fp32(const ::brotensor::Tensor& t,
32 const char* op, const char* name) {
33
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 if (t.dtype != Dtype::FP32)
34 fail(op, std::string(name) + " must be FP32 (CPU backend is FP32-only)");
35 5 }
36
37 } // namespace
38
39 5 void l2_normalize_nchw_forward(const ::brotensor::Tensor& X,
40 int N, int C, int H, int W,
41 float eps,
42 ::brotensor::Tensor& Y) {
43 5 const char* op = "l2_normalize_nchw_forward";
44 5 check_fp32(X, op, "X");
45
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 if (N < 0 || C < 1 || H < 1 || W < 1)
46 fail(op, "C/H/W must be >=1 and N >=0");
47
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 if (X.rows != N || X.cols != C * H * W)
48 fail(op, "X shape must be (N, C*H*W)");
49
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 != C * H * W || Y.dtype != Dtype::FP32)
50 5 Y.resize(N, C * H * W, Dtype::FP32);
51
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 if (N == 0) return;
52
53 5 const float* Xp = X.host_f32();
54 5 float* Yp = Y.host_f32_mut();
55 5 const int HW = H * W;
56 5 const double epsd = static_cast<double>(eps);
57
58
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 6 times.
11 for (int n = 0; n < N; ++n) {
59 6 const float* x_img = Xp + static_cast<long>(n) * C * HW;
60 6 float* y_img = Yp + static_cast<long>(n) * C * HW;
61
2/2
✓ Branch 0 taken 155 times.
✓ Branch 1 taken 6 times.
161 for (int p = 0; p < HW; ++p) {
62 155 double ss = 0.0;
63
2/2
✓ Branch 0 taken 4828 times.
✓ Branch 1 taken 155 times.
4983 for (int c = 0; c < C; ++c) {
64 4828 const double v = x_img[static_cast<long>(c) * HW + p];
65 4828 ss += v * v;
66 4828 }
67 155 const double inv = 1.0 / std::max(std::sqrt(ss), epsd);
68
2/2
✓ Branch 0 taken 4828 times.
✓ Branch 1 taken 155 times.
4983 for (int c = 0; c < C; ++c)
69 4828 y_img[static_cast<long>(c) * HW + p] =
70 4828 static_cast<float>(x_img[static_cast<long>(c) * HW + p] * inv);
71 155 }
72 6 }
73 5 }
74
75 } // namespace brotensor::detail::cpu
76