src/cpu/rope.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | // ─── CPU RoPE ops (CHUNK 2) ──────────────────────────────────────────────── | ||
| 2 | // | ||
| 3 | // FP32 scalar host implementations. Ports src/cuda/rope.cu — FP32 path only. | ||
| 4 | // | ||
| 5 | // Rotary position embedding: per head, rotate consecutive pairs | ||
| 6 | // (x_{2i}, x_{2i+1}) by angle theta = pos * theta_base^{-2i/head_dim}, where | ||
| 7 | // pos = row + seq_offset. | ||
| 8 | // | ||
| 9 | // X / Y layout: (L, num_heads * head_dim), row-major. Within a row, heads | ||
| 10 | // are contiguous head_dim-sized blocks; within a head the pairs are | ||
| 11 | // (2i, 2i+1). | ||
| 12 | // forward: Y[2i] = x0*c - x1*s ; Y[2i+1] = x0*s + x1*c | ||
| 13 | // backward: dX[2i] = dy0*c + dy1*s ; dX[2i+1] = -dy0*s + dy1*c (R(θ)^T) | ||
| 14 | // | ||
| 15 | // Both directions overwrite their output (the GPU kernels write directly). | ||
| 16 | |||
| 17 | #include <brotensor/tensor.h> | ||
| 18 | |||
| 19 | #include <cmath> | ||
| 20 | #include <stdexcept> | ||
| 21 | #include <string> | ||
| 22 | #include <vector> | ||
| 23 | |||
| 24 | namespace brotensor::detail::cpu { | ||
| 25 | |||
| 26 | namespace { | ||
| 27 | |||
| 28 | 569 | inline float rope_theta(int pair_i, int head_dim, float base) { | |
| 29 | // theta_i = base^{-2i/head_dim} = exp(-2i/hd * log(base)). | ||
| 30 | 1707 | return std::exp(-static_cast<float>(2 * pair_i) / | |
| 31 | 1138 | static_cast<float>(head_dim) * std::log(base)); | |
| 32 | } | ||
| 33 | |||
| 34 | } // namespace | ||
| 35 | |||
| 36 | 9 | void rope_forward(const ::brotensor::Tensor& X, int head_dim, int num_heads, | |
| 37 | int seq_offset, float theta_base, ::brotensor::Tensor& Y) { | ||
| 38 |
1/2✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
|
9 | if (head_dim <= 0 || (head_dim & 1) != 0) { |
| 39 | ✗ | throw std::runtime_error("rope_forward: head_dim must be a positive even integer"); | |
| 40 | } | ||
| 41 |
1/2✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
|
9 | if (num_heads <= 0) { |
| 42 | ✗ | throw std::runtime_error("rope_forward: num_heads must be positive"); | |
| 43 | } | ||
| 44 |
1/2✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
|
9 | if (X.cols != num_heads * head_dim) { |
| 45 | ✗ | throw std::runtime_error("rope_forward: X.cols != num_heads * head_dim"); | |
| 46 | } | ||
| 47 | 9 | const int L = X.rows; | |
| 48 |
1/4✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
|
9 | if (Y.rows != L || Y.cols != X.cols) Y.resize(L, X.cols); |
| 49 | 9 | const int half = head_dim / 2; | |
| 50 |
1/2✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
|
9 | if (L * num_heads * half == 0) return; |
| 51 | 9 | const int D = num_heads * head_dim; | |
| 52 | 9 | const float* Xp = X.host_f32(); | |
| 53 | 9 | float* Yp = Y.host_f32_mut(); | |
| 54 | 9 | std::vector<float> cos_row(half); | |
| 55 |
1/2✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
|
9 | std::vector<float> sin_row(half); |
| 56 |
2/2✓ Branch 0 taken 9 times.
✓ Branch 1 taken 71 times.
|
80 | for (int row = 0; row < L; ++row) { |
| 57 | 71 | const int pos = row + seq_offset; | |
| 58 |
2/2✓ Branch 0 taken 71 times.
✓ Branch 1 taken 381 times.
|
452 | for (int i = 0; i < half; ++i) { |
| 59 | 381 | const float theta = | |
| 60 |
1/2✓ Branch 0 taken 381 times.
✗ Branch 1 not taken.
|
381 | static_cast<float>(pos) * rope_theta(i, head_dim, theta_base); |
| 61 | 381 | cos_row[i] = std::cos(theta); | |
| 62 | 381 | sin_row[i] = std::sin(theta); | |
| 63 | 381 | } | |
| 64 |
2/2✓ Branch 0 taken 186 times.
✓ Branch 1 taken 71 times.
|
257 | for (int h = 0; h < num_heads; ++h) { |
| 65 | 186 | const int base_off = row * D + h * head_dim; | |
| 66 |
2/2✓ Branch 0 taken 859 times.
✓ Branch 1 taken 186 times.
|
1045 | for (int i = 0; i < half; ++i) { |
| 67 | 859 | const float c = cos_row[i]; | |
| 68 | 859 | const float s = sin_row[i]; | |
| 69 | 859 | const float x0 = Xp[base_off + 2 * i]; | |
| 70 | 859 | const float x1 = Xp[base_off + 2 * i + 1]; | |
| 71 | 859 | Yp[base_off + 2 * i] = x0 * c - x1 * s; | |
| 72 | 859 | Yp[base_off + 2 * i + 1] = x0 * s + x1 * c; | |
| 73 | 859 | } | |
| 74 | 186 | } | |
| 75 | 71 | } | |
| 76 | 9 | } | |
| 77 | |||
| 78 | 4 | void rope_backward(const ::brotensor::Tensor& dY, int head_dim, int num_heads, | |
| 79 | int seq_offset, float theta_base, ::brotensor::Tensor& dX) { | ||
| 80 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | if (head_dim <= 0 || (head_dim & 1) != 0) { |
| 81 | ✗ | throw std::runtime_error("rope_backward: head_dim must be a positive even integer"); | |
| 82 | } | ||
| 83 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | if (num_heads <= 0) { |
| 84 | ✗ | throw std::runtime_error("rope_backward: num_heads must be positive"); | |
| 85 | } | ||
| 86 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | if (dY.cols != num_heads * head_dim) { |
| 87 | ✗ | throw std::runtime_error("rope_backward: dY.cols != num_heads * head_dim"); | |
| 88 | } | ||
| 89 | 4 | const int L = dY.rows; | |
| 90 |
1/4✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
|
4 | if (dX.rows != L || dX.cols != dY.cols) dX.resize(L, dY.cols); |
| 91 | 4 | const int half = head_dim / 2; | |
| 92 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | if (L * num_heads * half == 0) return; |
| 93 | 4 | const int D = num_heads * head_dim; | |
| 94 | 4 | const float* dYp = dY.host_f32(); | |
| 95 | 4 | float* dXp = dX.host_f32_mut(); | |
| 96 | 4 | std::vector<float> cos_row(half); | |
| 97 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | std::vector<float> sin_row(half); |
| 98 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 33 times.
|
37 | for (int row = 0; row < L; ++row) { |
| 99 | 33 | const int pos = row + seq_offset; | |
| 100 |
2/2✓ Branch 0 taken 33 times.
✓ Branch 1 taken 188 times.
|
221 | for (int i = 0; i < half; ++i) { |
| 101 | 188 | const float theta = | |
| 102 |
1/2✓ Branch 0 taken 188 times.
✗ Branch 1 not taken.
|
188 | static_cast<float>(pos) * rope_theta(i, head_dim, theta_base); |
| 103 | 188 | cos_row[i] = std::cos(theta); | |
| 104 | 188 | sin_row[i] = std::sin(theta); | |
| 105 | 188 | } | |
| 106 |
2/2✓ Branch 0 taken 82 times.
✓ Branch 1 taken 33 times.
|
115 | for (int h = 0; h < num_heads; ++h) { |
| 107 | 82 | const int base_off = row * D + h * head_dim; | |
| 108 |
2/2✓ Branch 0 taken 408 times.
✓ Branch 1 taken 82 times.
|
490 | for (int i = 0; i < half; ++i) { |
| 109 | 408 | const float c = cos_row[i]; | |
| 110 | 408 | const float s = sin_row[i]; | |
| 111 | 408 | const float dy0 = dYp[base_off + 2 * i]; | |
| 112 | 408 | const float dy1 = dYp[base_off + 2 * i + 1]; | |
| 113 | // Inverse rotation (transpose of R(θ)). | ||
| 114 | 408 | dXp[base_off + 2 * i] = dy0 * c + dy1 * s; | |
| 115 | 408 | dXp[base_off + 2 * i + 1] = -dy0 * s + dy1 * c; | |
| 116 | 408 | } | |
| 117 | 82 | } | |
| 118 | 33 | } | |
| 119 | 4 | } | |
| 120 | |||
| 121 | // ─── RoPE with explicit cos/sin tables ───────────────────────────────────── | ||
| 122 | // | ||
| 123 | // Ports src/cuda/rope.cu — rope_apply / rope_apply_backward, FP32 path. | ||
| 124 | // cos_tbl / sin_tbl are (L, head_dim/2): one rotation angle per (row, pair), | ||
| 125 | // shared across all heads. The caller owns position semantics (this is what | ||
| 126 | // supports 2D axial RoPE). Both directions overwrite their output. | ||
| 127 | |||
| 128 | namespace { | ||
| 129 | |||
| 130 | 10 | inline void check_tables(const ::brotensor::Tensor& cos_tbl, | |
| 131 | const ::brotensor::Tensor& sin_tbl, | ||
| 132 | const char* op, int L, int half) { | ||
| 133 |
1/2✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
|
10 | if (cos_tbl.dtype != Dtype::FP32 || sin_tbl.dtype != Dtype::FP32) { |
| 134 | ✗ | throw std::runtime_error(std::string(op) + ": cos_tbl / sin_tbl must be FP32"); | |
| 135 | } | ||
| 136 |
1/2✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
|
10 | if (cos_tbl.size() != L * half || sin_tbl.size() != L * half) { |
| 137 | ✗ | throw std::runtime_error(std::string(op) + | |
| 138 | ": cos_tbl / sin_tbl must each be (L, head_dim/2)"); | ||
| 139 | } | ||
| 140 | 10 | } | |
| 141 | |||
| 142 | } // namespace | ||
| 143 | |||
| 144 | 8 | void rope_apply(const ::brotensor::Tensor& X, const ::brotensor::Tensor& cos_tbl, | |
| 145 | const ::brotensor::Tensor& sin_tbl, int head_dim, int num_heads, | ||
| 146 | ::brotensor::Tensor& Y) { | ||
| 147 |
1/2✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
|
8 | if (head_dim <= 0 || (head_dim & 1) != 0) { |
| 148 | ✗ | throw std::runtime_error("rope_apply: head_dim must be a positive even integer"); | |
| 149 | } | ||
| 150 |
1/2✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
|
8 | if (num_heads <= 0) { |
| 151 | ✗ | throw std::runtime_error("rope_apply: num_heads must be positive"); | |
| 152 | } | ||
| 153 |
1/2✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
|
8 | if (X.dtype != Dtype::FP32) { |
| 154 | ✗ | throw std::runtime_error("rope_apply: X must be FP32 (CPU backend is FP32-only)"); | |
| 155 | } | ||
| 156 |
1/2✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
|
8 | if (X.cols != num_heads * head_dim) { |
| 157 | ✗ | throw std::runtime_error("rope_apply: X.cols != num_heads * head_dim"); | |
| 158 | } | ||
| 159 | 8 | const int L = X.rows; | |
| 160 | 8 | const int half = head_dim / 2; | |
| 161 | 8 | check_tables(cos_tbl, sin_tbl, "rope_apply", L, half); | |
| 162 |
1/6✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
|
8 | if (Y.rows != L || Y.cols != X.cols || Y.dtype != Dtype::FP32) { |
| 163 | 8 | Y.resize(L, X.cols, Dtype::FP32); | |
| 164 | 8 | } | |
| 165 |
1/2✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
|
8 | if (L * num_heads * half == 0) return; |
| 166 | 8 | const int D = num_heads * head_dim; | |
| 167 | 8 | const float* Xp = X.host_f32(); | |
| 168 | 8 | const float* Cp = cos_tbl.host_f32(); | |
| 169 | 8 | const float* Sp = sin_tbl.host_f32(); | |
| 170 | 8 | float* Yp = Y.host_f32_mut(); | |
| 171 |
2/2✓ Branch 0 taken 8 times.
✓ Branch 1 taken 66 times.
|
74 | for (int row = 0; row < L; ++row) { |
| 172 |
2/2✓ Branch 0 taken 176 times.
✓ Branch 1 taken 66 times.
|
242 | for (int h = 0; h < num_heads; ++h) { |
| 173 | 176 | const int base_off = row * D + h * head_dim; | |
| 174 |
2/2✓ Branch 0 taken 816 times.
✓ Branch 1 taken 176 times.
|
992 | for (int i = 0; i < half; ++i) { |
| 175 | 816 | const float c = Cp[row * half + i]; | |
| 176 | 816 | const float s = Sp[row * half + i]; | |
| 177 | 816 | const float x0 = Xp[base_off + 2 * i]; | |
| 178 | 816 | const float x1 = Xp[base_off + 2 * i + 1]; | |
| 179 | 816 | Yp[base_off + 2 * i] = x0 * c - x1 * s; | |
| 180 | 816 | Yp[base_off + 2 * i + 1] = x0 * s + x1 * c; | |
| 181 | 816 | } | |
| 182 | 176 | } | |
| 183 | 66 | } | |
| 184 | 8 | } | |
| 185 | |||
| 186 | // ─── RoPE with explicit PER-HEAD cos/sin tables ─────────────────────────── | ||
| 187 | // | ||
| 188 | // Like rope_apply, but cos_tbl / sin_tbl are (L*num_heads, head_dim/2): one | ||
| 189 | // angle per (row, head, pair). Rows are head-minor within each token, i.e. the | ||
| 190 | // table row for (row, h) is (row*num_heads + h). Inference-only. | ||
| 191 | |||
| 192 | 2 | void rope_apply_perhead(const ::brotensor::Tensor& X, | |
| 193 | const ::brotensor::Tensor& cos_tbl, | ||
| 194 | const ::brotensor::Tensor& sin_tbl, | ||
| 195 | int head_dim, int num_heads, ::brotensor::Tensor& Y) { | ||
| 196 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | if (head_dim <= 0 || (head_dim & 1) != 0) { |
| 197 | ✗ | throw std::runtime_error("rope_apply_perhead: head_dim must be a positive even integer"); | |
| 198 | } | ||
| 199 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | if (num_heads <= 0) { |
| 200 | ✗ | throw std::runtime_error("rope_apply_perhead: num_heads must be positive"); | |
| 201 | } | ||
| 202 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | if (X.dtype != Dtype::FP32) { |
| 203 | ✗ | throw std::runtime_error("rope_apply_perhead: X must be FP32 (CPU backend is FP32-only)"); | |
| 204 | } | ||
| 205 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | if (X.cols != num_heads * head_dim) { |
| 206 | ✗ | throw std::runtime_error("rope_apply_perhead: X.cols != num_heads * head_dim"); | |
| 207 | } | ||
| 208 | 2 | const int L = X.rows; | |
| 209 | 2 | const int half = head_dim / 2; | |
| 210 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | if (cos_tbl.dtype != Dtype::FP32 || sin_tbl.dtype != Dtype::FP32) { |
| 211 | ✗ | throw std::runtime_error("rope_apply_perhead: cos_tbl / sin_tbl must be FP32"); | |
| 212 | } | ||
| 213 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | if (cos_tbl.size() != L * num_heads * half || sin_tbl.size() != L * num_heads * half) { |
| 214 | ✗ | throw std::runtime_error( | |
| 215 | "rope_apply_perhead: cos_tbl / sin_tbl must each be (L*num_heads, head_dim/2)"); | ||
| 216 | } | ||
| 217 |
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 (Y.rows != L || Y.cols != X.cols || Y.dtype != Dtype::FP32) { |
| 218 | 2 | Y.resize(L, X.cols, Dtype::FP32); | |
| 219 | 2 | } | |
| 220 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | if (L * num_heads * half == 0) return; |
| 221 | 2 | const int D = num_heads * head_dim; | |
| 222 | 2 | const float* Xp = X.host_f32(); | |
| 223 | 2 | const float* Cp = cos_tbl.host_f32(); | |
| 224 | 2 | const float* Sp = sin_tbl.host_f32(); | |
| 225 | 2 | float* Yp = Y.host_f32_mut(); | |
| 226 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 10 times.
|
12 | for (int row = 0; row < L; ++row) { |
| 227 |
2/2✓ Branch 0 taken 30 times.
✓ Branch 1 taken 10 times.
|
40 | for (int h = 0; h < num_heads; ++h) { |
| 228 | 30 | const int base_off = row * D + h * head_dim; | |
| 229 | 30 | const int tbl_off = (row * num_heads + h) * half; | |
| 230 |
2/2✓ Branch 0 taken 120 times.
✓ Branch 1 taken 30 times.
|
150 | for (int i = 0; i < half; ++i) { |
| 231 | 120 | const float c = Cp[tbl_off + i]; | |
| 232 | 120 | const float s = Sp[tbl_off + i]; | |
| 233 | 120 | const float x0 = Xp[base_off + 2 * i]; | |
| 234 | 120 | const float x1 = Xp[base_off + 2 * i + 1]; | |
| 235 | 120 | Yp[base_off + 2 * i] = x0 * c - x1 * s; | |
| 236 | 120 | Yp[base_off + 2 * i + 1] = x0 * s + x1 * c; | |
| 237 | 120 | } | |
| 238 | 30 | } | |
| 239 | 10 | } | |
| 240 | 2 | } | |
| 241 | |||
| 242 | 2 | void rope_apply_backward(const ::brotensor::Tensor& dY, | |
| 243 | const ::brotensor::Tensor& cos_tbl, | ||
| 244 | const ::brotensor::Tensor& sin_tbl, | ||
| 245 | int head_dim, int num_heads, ::brotensor::Tensor& dX) { | ||
| 246 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | if (head_dim <= 0 || (head_dim & 1) != 0) { |
| 247 | ✗ | throw std::runtime_error("rope_apply_backward: head_dim must be a positive even integer"); | |
| 248 | } | ||
| 249 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | if (num_heads <= 0) { |
| 250 | ✗ | throw std::runtime_error("rope_apply_backward: num_heads must be positive"); | |
| 251 | } | ||
| 252 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | if (dY.dtype != Dtype::FP32) { |
| 253 | ✗ | throw std::runtime_error("rope_apply_backward: dY must be FP32 (CPU backend is FP32-only)"); | |
| 254 | } | ||
| 255 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | if (dY.cols != num_heads * head_dim) { |
| 256 | ✗ | throw std::runtime_error("rope_apply_backward: dY.cols != num_heads * head_dim"); | |
| 257 | } | ||
| 258 | 2 | const int L = dY.rows; | |
| 259 | 2 | const int half = head_dim / 2; | |
| 260 | 2 | check_tables(cos_tbl, sin_tbl, "rope_apply_backward", L, half); | |
| 261 |
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 != L || dX.cols != dY.cols || dX.dtype != Dtype::FP32) { |
| 262 | 2 | dX.resize(L, dY.cols, Dtype::FP32); | |
| 263 | 2 | } | |
| 264 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | if (L * num_heads * half == 0) return; |
| 265 | 2 | const int D = num_heads * head_dim; | |
| 266 | 2 | const float* dYp = dY.host_f32(); | |
| 267 | 2 | const float* Cp = cos_tbl.host_f32(); | |
| 268 | 2 | const float* Sp = sin_tbl.host_f32(); | |
| 269 | 2 | float* dXp = dX.host_f32_mut(); | |
| 270 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 18 times.
|
20 | for (int row = 0; row < L; ++row) { |
| 271 |
2/2✓ Branch 0 taken 60 times.
✓ Branch 1 taken 18 times.
|
78 | for (int h = 0; h < num_heads; ++h) { |
| 272 | 60 | const int base_off = row * D + h * head_dim; | |
| 273 |
2/2✓ Branch 0 taken 288 times.
✓ Branch 1 taken 60 times.
|
348 | for (int i = 0; i < half; ++i) { |
| 274 | 288 | const float c = Cp[row * half + i]; | |
| 275 | 288 | const float s = Sp[row * half + i]; | |
| 276 | 288 | const float dy0 = dYp[base_off + 2 * i]; | |
| 277 | 288 | const float dy1 = dYp[base_off + 2 * i + 1]; | |
| 278 | 288 | dXp[base_off + 2 * i] = dy0 * c + dy1 * s; | |
| 279 | 288 | dXp[base_off + 2 * i + 1] = -dy0 * s + dy1 * c; | |
| 280 | 288 | } | |
| 281 | 60 | } | |
| 282 | 18 | } | |
| 283 | 2 | } | |
| 284 | |||
| 285 | } // namespace brotensor::detail::cpu | ||
| 286 |