GCC Code Coverage Report


Directory: ./
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 100.0% 97 / 0 / 97
Functions: 100.0% 16 / 0 / 16
Branches: 53.1% 34 / 0 / 64

src/cpu/activations.cpp
Line Branch Exec Source
1 // ─── CPU activation ops (CHUNK 2) ──────────────────────────────────────────
2 //
3 // FP32 scalar host implementations. Ports the GPU activation kernels in
4 // src/cuda/elementwise.cu — kernel math reproduced verbatim, FP32 path only.
5 //
6 // silu — x * sigmoid(x)
7 // gelu — tanh-approximation GELU (PyTorch approximate="tanh")
8 // gelu_exact — erf-based GELU (PyTorch approximate="none")
9 // quick_gelu — x * sigmoid(1.702 * x) (OpenAI CLIP)
10 //
11 // Each has a matching backward op. Outputs are sized to mirror the GPU op
12 // (resize if shape/dtype differs); backward writes dX (overwrite, not
13 // accumulate — matches the GPU which writes dX[i] directly).
14
15 #include <brotensor/tensor.h>
16
17 #include <cmath>
18
19 namespace brotensor::detail::cpu {
20
21 namespace {
22
23 46977 inline float silu_scalar(float v) {
24 46977 return v / (1.0f + std::exp(-v));
25 }
26
27 6913 inline float silu_grad_scalar(float v) {
28 // d/dx [x * sigmoid(x)] = sigmoid(x) * (1 + x * (1 - sigmoid(x))).
29 6913 const float s = 1.0f / (1.0f + std::exp(-v));
30 6913 return s * (1.0f + v * (1.0f - s));
31 }
32
33 1089 inline float gelu_tanh_scalar(float v) {
34 // GELU with tanh approximation (matches PyTorch's approximate="tanh").
35 1089 constexpr float kSqrt2OverPi = 0.7978845608f;
36 1089 const float u = kSqrt2OverPi * (v + 0.044715f * v * v * v);
37 1089 return 0.5f * v * (1.0f + std::tanh(u));
38 }
39
40 513 inline float gelu_tanh_grad_scalar(float v) {
41 // Derivative of gelu_tanh_scalar w.r.t. v.
42 513 constexpr float kSqrt2OverPi = 0.7978845608f;
43 513 const float u = kSqrt2OverPi * (v + 0.044715f * v * v * v);
44 513 const float t = std::tanh(u);
45 513 const float dudx = kSqrt2OverPi * (1.0f + 3.0f * 0.044715f * v * v);
46 513 return 0.5f * (1.0f + t) + 0.5f * v * (1.0f - t * t) * dudx;
47 }
48
49 577 inline float gelu_exact_scalar(float v) {
50 // Exact GELU: 0.5 * x * (1 + erf(x / sqrt(2))).
51 577 constexpr float kInvSqrt2 = 0.70710678118654752440f;
52 577 return 0.5f * v * (1.0f + std::erf(v * kInvSqrt2));
53 }
54
55 513 inline float gelu_exact_grad_scalar(float v) {
56 // d/dx [0.5*x*(1+erf(x/√2))] = 0.5*(1+erf(x/√2)) + x*φ(x).
57 513 constexpr float kInvSqrt2 = 0.70710678118654752440f;
58 513 constexpr float kInvSqrt2Pi = 0.39894228040143267794f; // 1/sqrt(2π)
59 513 const float cdf_term = 0.5f * (1.0f + std::erf(v * kInvSqrt2));
60 513 const float pdf = kInvSqrt2Pi * std::exp(-0.5f * v * v);
61 513 return cdf_term + v * pdf;
62 }
63
64 577 inline float quick_gelu_scalar(float v) {
65 // OpenAI CLIP's QuickGELU: x * sigmoid(1.702 * x).
66 577 return v / (1.0f + std::exp(-1.702f * v));
67 }
68
69 513 inline float quick_gelu_grad_scalar(float v) {
70 // d/dx [x * sigmoid(1.702*x)] = s + x * 1.702 * s * (1 - s).
71 513 const float s = 1.0f / (1.0f + std::exp(-1.702f * v));
72 513 return s + v * 1.702f * s * (1.0f - s);
73 }
74
75 } // namespace
76
77 // ─── silu ──────────────────────────────────────────────────────────────────
78
79 37 void silu_forward(const ::brotensor::Tensor& x, ::brotensor::Tensor& y) {
80
3/4
✓ Branch 0 taken 16 times.
✓ Branch 1 taken 21 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 16 times.
37 if (y.rows != x.rows || y.cols != x.cols) y.resize(x.rows, x.cols);
81 37 const int n = x.size();
82
1/2
✓ Branch 0 taken 37 times.
✗ Branch 1 not taken.
37 if (n == 0) return;
83 37 const float* xp = x.host_f32();
84 37 float* yp = y.host_f32_mut();
85
2/2
✓ Branch 0 taken 46977 times.
✓ Branch 1 taken 37 times.
47014 for (int i = 0; i < n; ++i) yp[i] = silu_scalar(xp[i]);
86 37 }
87
88 19 void silu_backward(const ::brotensor::Tensor& x, const ::brotensor::Tensor& dY,
89 ::brotensor::Tensor& dX) {
90
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 19 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
19 if (dX.rows != x.rows || dX.cols != x.cols) dX.resize(x.rows, x.cols);
91 19 const int n = x.size();
92
1/2
✓ Branch 0 taken 19 times.
✗ Branch 1 not taken.
19 if (n == 0) return;
93 19 const float* xp = x.host_f32();
94 19 const float* dyp = dY.host_f32();
95 19 float* dxp = dX.host_f32_mut();
96
2/2
✓ Branch 0 taken 6913 times.
✓ Branch 1 taken 19 times.
6932 for (int i = 0; i < n; ++i) dxp[i] = dyp[i] * silu_grad_scalar(xp[i]);
97 19 }
98
99 // ─── gelu (tanh approximation) ─────────────────────────────────────────────
100
101 5 void gelu_forward(const ::brotensor::Tensor& x, ::brotensor::Tensor& y) {
102
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
5 if (y.rows != x.rows || y.cols != x.cols) y.resize(x.rows, x.cols);
103 5 const int n = x.size();
104
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 if (n == 0) return;
105 5 const float* xp = x.host_f32();
106 5 float* yp = y.host_f32_mut();
107
2/2
✓ Branch 0 taken 1089 times.
✓ Branch 1 taken 5 times.
1094 for (int i = 0; i < n; ++i) yp[i] = gelu_tanh_scalar(xp[i]);
108 5 }
109
110 3 void gelu_backward(const ::brotensor::Tensor& x, const ::brotensor::Tensor& dY,
111 ::brotensor::Tensor& dX) {
112
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
3 if (dX.rows != x.rows || dX.cols != x.cols) dX.resize(x.rows, x.cols);
113 3 const int n = x.size();
114
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 if (n == 0) return;
115 3 const float* xp = x.host_f32();
116 3 const float* dyp = dY.host_f32();
117 3 float* dxp = dX.host_f32_mut();
118
2/2
✓ Branch 0 taken 513 times.
✓ Branch 1 taken 3 times.
516 for (int i = 0; i < n; ++i) dxp[i] = dyp[i] * gelu_tanh_grad_scalar(xp[i]);
119 3 }
120
121 // ─── gelu_exact (erf-based) ────────────────────────────────────────────────
122
123 4 void gelu_exact_forward(const ::brotensor::Tensor& x, ::brotensor::Tensor& y) {
124
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
4 if (y.rows != x.rows || y.cols != x.cols) y.resize(x.rows, x.cols);
125 4 const int n = x.size();
126
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 if (n == 0) return;
127 4 const float* xp = x.host_f32();
128 4 float* yp = y.host_f32_mut();
129
2/2
✓ Branch 0 taken 577 times.
✓ Branch 1 taken 4 times.
581 for (int i = 0; i < n; ++i) yp[i] = gelu_exact_scalar(xp[i]);
130 4 }
131
132 3 void gelu_exact_backward(const ::brotensor::Tensor& x,
133 const ::brotensor::Tensor& dY,
134 ::brotensor::Tensor& dX) {
135
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
3 if (dX.rows != x.rows || dX.cols != x.cols) dX.resize(x.rows, x.cols);
136 3 const int n = x.size();
137
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 if (n == 0) return;
138 3 const float* xp = x.host_f32();
139 3 const float* dyp = dY.host_f32();
140 3 float* dxp = dX.host_f32_mut();
141
2/2
✓ Branch 0 taken 513 times.
✓ Branch 1 taken 3 times.
516 for (int i = 0; i < n; ++i) dxp[i] = dyp[i] * gelu_exact_grad_scalar(xp[i]);
142 3 }
143
144 // ─── quick_gelu ────────────────────────────────────────────────────────────
145
146 4 void quick_gelu_forward(const ::brotensor::Tensor& x, ::brotensor::Tensor& y) {
147
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
4 if (y.rows != x.rows || y.cols != x.cols) y.resize(x.rows, x.cols);
148 4 const int n = x.size();
149
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 if (n == 0) return;
150 4 const float* xp = x.host_f32();
151 4 float* yp = y.host_f32_mut();
152
2/2
✓ Branch 0 taken 577 times.
✓ Branch 1 taken 4 times.
581 for (int i = 0; i < n; ++i) yp[i] = quick_gelu_scalar(xp[i]);
153 4 }
154
155 3 void quick_gelu_backward(const ::brotensor::Tensor& x,
156 const ::brotensor::Tensor& dY,
157 ::brotensor::Tensor& dX) {
158
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
3 if (dX.rows != x.rows || dX.cols != x.cols) dX.resize(x.rows, x.cols);
159 3 const int n = x.size();
160
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 if (n == 0) return;
161 3 const float* xp = x.host_f32();
162 3 const float* dyp = dY.host_f32();
163 3 float* dxp = dX.host_f32_mut();
164
2/2
✓ Branch 0 taken 513 times.
✓ Branch 1 taken 3 times.
516 for (int i = 0; i < n; ++i) dxp[i] = dyp[i] * quick_gelu_grad_scalar(xp[i]);
165 3 }
166
167 } // namespace brotensor::detail::cpu
168