GCC Code Coverage Report


Directory: ./
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 97.3% 73 / 0 / 75
Functions: 100.0% 7 / 0 / 7
Branches: 39.7% 31 / 0 / 78

src/cpu/log_exp_round.cpp
Line Branch Exec Source
1 // ─── CPU log / exp / round elementwise ops (CHUNK 6, family G) ─────────────
2 //
3 // FP32 scalar host implementations, modelled on silu_forward/backward in
4 // activations.cpp. CPU is FP32-only.
5 //
6 // log_forward / log_backward y = log(x); dX = dY / x
7 // exp_forward / exp_backward y = exp(x); dX = dY * exp(x)
8 // round_forward y = round-half-to-even(x) (torch.round)
9 // round_backward straight-through estimator: dX = dY
10 //
11 // log_forward / log_backward: the caller owns the x > 0 precondition. These
12 // ops do NOT guard the input — for x <= 0 they return the IEEE result
13 // (log(0) = -inf, log(<0) = NaN; 1/x for the backward) so a mis-clamped
14 // pipeline fails loudly. No floor is applied.
15 //
16 // All elementwise: the output is resized + dtype-set to match the input; the
17 // input and output may alias. None of these ops has a learnable parameter, so
18 // every backward OVERWRITES dX (it does not accumulate).
19
20 #include <brotensor/tensor.h>
21
22 #include <cmath>
23 #include <stdexcept>
24 #include <string>
25
26 namespace brotensor::detail::cpu {
27
28 namespace {
29
30 116 inline void check_fp32(const ::brotensor::Tensor& t,
31 const char* op, const char* name) {
32
1/2
✓ Branch 0 taken 116 times.
✗ Branch 1 not taken.
116 if (t.dtype != Dtype::FP32) {
33 throw std::runtime_error(std::string("brotensor: ") + op + ": " +
34 name + " must be FP32 (CPU backend is "
35 "FP32-only)");
36 }
37 116 }
38
39 } // namespace
40
41 // ─── log ───────────────────────────────────────────────────────────────────
42
43 49 void log_forward(const ::brotensor::Tensor& x, ::brotensor::Tensor& y) {
44 49 check_fp32(x, "log_forward", "x");
45
4/6
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 48 times.
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 1 time.
49 if (y.rows != x.rows || y.cols != x.cols || y.dtype != Dtype::FP32) {
46 48 y.resize(x.rows, x.cols, Dtype::FP32);
47 48 }
48 49 const int n = x.size();
49
1/2
✓ Branch 0 taken 49 times.
✗ Branch 1 not taken.
49 if (n == 0) return;
50 49 const float* xp = x.host_f32();
51 49 float* yp = y.host_f32_mut();
52
2/2
✓ Branch 0 taken 4693 times.
✓ Branch 1 taken 49 times.
4742 for (int i = 0; i < n; ++i) yp[i] = std::log(xp[i]);
53 49 }
54
55 3 void log_backward(const ::brotensor::Tensor& x, const ::brotensor::Tensor& dY,
56 ::brotensor::Tensor& dX) {
57 3 check_fp32(x, "log_backward", "x");
58 3 check_fp32(dY, "log_backward", "dY");
59
1/6
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
3 if (dX.rows != x.rows || dX.cols != x.cols || dX.dtype != Dtype::FP32) {
60 3 dX.resize(x.rows, x.cols, Dtype::FP32);
61 3 }
62 3 const int n = x.size();
63
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 if (n == 0) return;
64 3 const float* xp = x.host_f32();
65 3 const float* dyp = dY.host_f32();
66 3 float* dxp = dX.host_f32_mut(); // overwrite — dX may alias dY
67
2/2
✓ Branch 0 taken 312 times.
✓ Branch 1 taken 3 times.
315 for (int i = 0; i < n; ++i) dxp[i] = dyp[i] / xp[i];
68 3 }
69
70 // ─── exp ───────────────────────────────────────────────────────────────────
71
72 45 void exp_forward(const ::brotensor::Tensor& x, ::brotensor::Tensor& y) {
73 45 check_fp32(x, "exp_forward", "x");
74
1/6
✗ Branch 0 not taken.
✓ Branch 1 taken 45 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
45 if (y.rows != x.rows || y.cols != x.cols || y.dtype != Dtype::FP32) {
75 45 y.resize(x.rows, x.cols, Dtype::FP32);
76 45 }
77 45 const int n = x.size();
78
1/2
✓ Branch 0 taken 45 times.
✗ Branch 1 not taken.
45 if (n == 0) return;
79 45 const float* xp = x.host_f32();
80 45 float* yp = y.host_f32_mut();
81
2/2
✓ Branch 0 taken 4586 times.
✓ Branch 1 taken 45 times.
4631 for (int i = 0; i < n; ++i) yp[i] = std::exp(xp[i]);
82 45 }
83
84 5 void exp_backward(const ::brotensor::Tensor& x, const ::brotensor::Tensor& dY,
85 ::brotensor::Tensor& dX) {
86 5 check_fp32(x, "exp_backward", "x");
87 5 check_fp32(dY, "exp_backward", "dY");
88
4/6
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 1 time.
5 if (dX.rows != x.rows || dX.cols != x.cols || dX.dtype != Dtype::FP32) {
89 4 dX.resize(x.rows, x.cols, Dtype::FP32);
90 4 }
91 5 const int n = x.size();
92
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 if (n == 0) return;
93 5 const float* xp = x.host_f32();
94 5 const float* dyp = dY.host_f32();
95 5 float* dxp = dX.host_f32_mut(); // overwrite — dX may alias dY
96 // Read all inputs before writing so an in-place dX==dY alias is safe.
97
2/2
✓ Branch 0 taken 335 times.
✓ Branch 1 taken 5 times.
340 for (int i = 0; i < n; ++i) dxp[i] = dyp[i] * std::exp(xp[i]);
98 5 }
99
100 // ─── round ─────────────────────────────────────────────────────────────────
101
102 4 void round_forward(const ::brotensor::Tensor& x, ::brotensor::Tensor& y) {
103 4 check_fp32(x, "round_forward", "x");
104
1/6
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
4 if (y.rows != x.rows || y.cols != x.cols || y.dtype != Dtype::FP32) {
105 4 y.resize(x.rows, x.cols, Dtype::FP32);
106 4 }
107 4 const int n = x.size();
108
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 if (n == 0) return;
109 4 const float* xp = x.host_f32();
110 4 float* yp = y.host_f32_mut();
111 // std::nearbyint = round-half-to-even under the default FE_TONEAREST
112 // rounding mode — matches torch.round / numpy.round.
113
2/2
✓ Branch 0 taken 3763 times.
✓ Branch 1 taken 4 times.
3767 for (int i = 0; i < n; ++i) yp[i] = std::nearbyint(xp[i]);
114 4 }
115
116 2 void round_backward(const ::brotensor::Tensor& dY, ::brotensor::Tensor& dX) {
117 2 check_fp32(dY, "round_backward", "dY");
118
1/6
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
2 if (dX.rows != dY.rows || dX.cols != dY.cols || dX.dtype != Dtype::FP32) {
119 2 dX.resize(dY.rows, dY.cols, Dtype::FP32);
120 2 }
121 2 const int n = dY.size();
122
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (n == 0) return;
123 2 const float* dyp = dY.host_f32();
124 2 float* dxp = dX.host_f32_mut(); // overwrite — dX may alias dY
125 // Straight-through estimator: round() has zero gradient a.e. and is
126 // non-differentiable at the half-integers, so we pass dY straight
127 // through unchanged (identity) to keep gradients flowing.
128
2/2
✓ Branch 0 taken 286 times.
✓ Branch 1 taken 2 times.
288 for (int i = 0; i < n; ++i) dxp[i] = dyp[i];
129 2 }
130
131 } // namespace brotensor::detail::cpu
132