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 |