GCC Code Coverage Report


Directory: ./
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 93.2% 69 / 0 / 74
Functions: 100.0% 4 / 0 / 4
Branches: 30.0% 27 / 0 / 90

src/cpu/l2_norm.cpp
Line Branch Exec Source
1 // ─── CPU L2-norm (per-head, last-dim) ──────────────────────────────────────
2 //
3 // FP32-only host implementation. Used by Gated DeltaNet to L2-normalise q and
4 // k per head before the recurrence — distinct from rms_norm (sum vs. mean of
5 // squares, no learnable gamma, no sqrt(d) factor).
6 //
7 // Layout: (L, num_heads * head_dim), row-major; head h occupies columns
8 // [h*head_dim, (h+1)*head_dim). Same convention as rope_forward and rms_norm.
9
10 #include <brotensor/tensor.h>
11
12 #include <cmath>
13 #include <stdexcept>
14 #include <string>
15
16 namespace brotensor::detail::cpu {
17
18 namespace {
19
20 79 inline void check_fp32(const ::brotensor::Tensor& t,
21 const char* op, const char* name) {
22
1/2
✓ Branch 0 taken 79 times.
✗ Branch 1 not taken.
79 if (t.dtype != Dtype::FP32) {
23 throw std::runtime_error(std::string(op) + ": " + name +
24 " must be FP32 (CPU backend is FP32-only)");
25 }
26 79 }
27
28 79 inline void check_shape(const ::brotensor::Tensor& t,
29 int head_dim, int num_heads,
30 const char* op, const char* name) {
31
1/2
✓ Branch 0 taken 79 times.
✗ Branch 1 not taken.
79 if (head_dim <= 0) {
32 throw std::runtime_error(std::string(op) + ": head_dim must be positive");
33 }
34
1/2
✓ Branch 0 taken 79 times.
✗ Branch 1 not taken.
79 if (num_heads <= 0) {
35 throw std::runtime_error(std::string(op) + ": num_heads must be positive");
36 }
37
1/2
✓ Branch 0 taken 79 times.
✗ Branch 1 not taken.
79 if (t.cols != num_heads * head_dim) {
38 throw std::runtime_error(std::string(op) + ": " + name +
39 ".cols != num_heads * head_dim");
40 }
41 79 }
42
43 } // namespace
44
45 67 void l2_norm_forward(const ::brotensor::Tensor& X,
46 int head_dim, int num_heads, float eps,
47 ::brotensor::Tensor& Y) {
48 67 check_fp32(X, "l2_norm_forward", "X");
49 67 check_shape(X, head_dim, num_heads, "l2_norm_forward", "X");
50 67 const int L = X.rows;
51 67 const int D = X.cols;
52
1/6
✗ Branch 0 not taken.
✓ Branch 1 taken 67 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
67 if (Y.rows != L || Y.cols != D || Y.dtype != Dtype::FP32) {
53 67 Y.resize(L, D, Dtype::FP32);
54 67 }
55
2/4
✓ Branch 0 taken 67 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 67 times.
67 if (L == 0 || D == 0) return;
56
57 67 const float* xp = X.host_f32();
58 67 float* yp = Y.host_f32_mut();
59
60
2/2
✓ Branch 0 taken 67 times.
✓ Branch 1 taken 213 times.
280 for (int r = 0; r < L; ++r) {
61
2/2
✓ Branch 0 taken 468 times.
✓ Branch 1 taken 213 times.
681 for (int h = 0; h < num_heads; ++h) {
62 468 const int off = r * D + h * head_dim;
63 // sum of squares
64 468 float sumsq = 0.0f;
65
2/2
✓ Branch 0 taken 6730 times.
✓ Branch 1 taken 468 times.
7198 for (int d = 0; d < head_dim; ++d) {
66 6730 const float v = xp[off + d];
67 6730 sumsq += v * v;
68 6730 }
69 468 const float inv = 1.0f / std::sqrt(sumsq + eps);
70
2/2
✓ Branch 0 taken 6730 times.
✓ Branch 1 taken 468 times.
7198 for (int d = 0; d < head_dim; ++d) {
71 6730 yp[off + d] = xp[off + d] * inv;
72 6730 }
73 468 }
74 213 }
75 67 }
76
77 6 void l2_norm_backward(const ::brotensor::Tensor& X,
78 int head_dim, int num_heads, float eps,
79 const ::brotensor::Tensor& dY,
80 ::brotensor::Tensor& dX) {
81 6 check_fp32(X, "l2_norm_backward", "X");
82 6 check_fp32(dY, "l2_norm_backward", "dY");
83 6 check_shape(X, head_dim, num_heads, "l2_norm_backward", "X");
84 6 check_shape(dY, head_dim, num_heads, "l2_norm_backward", "dY");
85
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 if (dY.rows != X.rows) {
86 throw std::runtime_error("l2_norm_backward: dY.rows != X.rows");
87 }
88 6 const int L = X.rows;
89 6 const int D = X.cols;
90
1/6
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
6 if (dX.rows != L || dX.cols != D || dX.dtype != Dtype::FP32) {
91 6 dX.resize(L, D, Dtype::FP32);
92 6 }
93
2/4
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 6 times.
6 if (L == 0 || D == 0) return;
94
95 6 const float* xp = X.host_f32();
96 6 const float* gp = dY.host_f32();
97 6 float* dxp = dX.host_f32_mut();
98
99 // dX_d = n * dY_d - x_d * n^3 * sum_d' (x_d' * dY_d')
100 // = n * (dY_d - x_d * n^2 * dot(x, dY))
101
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 26 times.
32 for (int r = 0; r < L; ++r) {
102
2/2
✓ Branch 0 taken 84 times.
✓ Branch 1 taken 26 times.
110 for (int h = 0; h < num_heads; ++h) {
103 84 const int off = r * D + h * head_dim;
104 84 float sumsq = 0.0f;
105 84 float dot = 0.0f;
106
2/2
✓ Branch 0 taken 4645 times.
✓ Branch 1 taken 84 times.
4729 for (int d = 0; d < head_dim; ++d) {
107 4645 const float v = xp[off + d];
108 4645 const float gv = gp[off + d];
109 4645 sumsq += v * v;
110 4645 dot += v * gv;
111 4645 }
112 84 const float n2 = 1.0f / (sumsq + eps); // n^2
113 84 const float n = std::sqrt(n2); // n
114 84 const float c = dot * n2; // x . dY / (||x||^2 + eps)
115
2/2
✓ Branch 0 taken 4645 times.
✓ Branch 1 taken 84 times.
4729 for (int d = 0; d < head_dim; ++d) {
116 4645 dxp[off + d] = n * (gp[off + d] - xp[off + d] * c);
117 4645 }
118 84 }
119 26 }
120 6 }
121
122 } // namespace brotensor::detail::cpu
123