src/cpu/self_attention_bias.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | // ─── CPU self-attention with additive pre-softmax bias ───────────────────── | ||
| 2 | // | ||
| 3 | // FP32 scalar host implementation. Ports src/cuda/self_attention_bias.cu — | ||
| 4 | // FP32 path only (the CPU backend is FP32-only per CLAUDE.md). | ||
| 5 | // | ||
| 6 | // Multi-head self-attention with an optional per-head (L, L) additive bias: | ||
| 7 | // S[h,q,k] = scale * (Q_h[q] . K_h[k]) + attn_bias[h*L+q, k] | ||
| 8 | // O = concat_h( softmax_k(S[h]) @ V_h ) @ Wo | ||
| 9 | // | ||
| 10 | // Wq/Wk/Wv/Wo are (D, D); per-head split takes contiguous weight rows | ||
| 11 | // hh*dh .. hh*dh+dh. d_mask is a length-L key-validity buffer that also gates | ||
| 12 | // padded query rows (their output row is zeroed). attn_bias is FP32 or null. | ||
| 13 | // O is fully overwritten. | ||
| 14 | |||
| 15 | #include <brotensor/tensor.h> | ||
| 16 | #include <brotensor/detail/cpu/thread_pool.h> | ||
| 17 | |||
| 18 | #include <algorithm> | ||
| 19 | #include <cmath> | ||
| 20 | #include <stdexcept> | ||
| 21 | #include <string> | ||
| 22 | #include <vector> | ||
| 23 | |||
| 24 | namespace brotensor::detail::cpu { | ||
| 25 | |||
| 26 | namespace { | ||
| 27 | |||
| 28 | 143 | inline void check_fp32(const ::brotensor::Tensor& t, | |
| 29 | const char* name) { | ||
| 30 |
1/2✓ Branch 0 taken 143 times.
✗ Branch 1 not taken.
|
143 | if (t.dtype != Dtype::FP32) { |
| 31 | ✗ | throw std::runtime_error(std::string("self_attention_bias_forward: ") + | |
| 32 | ✗ | name + " must be FP32 (CPU backend is FP32-only)"); | |
| 33 | } | ||
| 34 | 143 | } | |
| 35 | |||
| 36 | } // namespace | ||
| 37 | |||
| 38 | 21 | void self_attention_bias_forward(const ::brotensor::Tensor& X, | |
| 39 | const ::brotensor::Tensor& Wq, | ||
| 40 | const ::brotensor::Tensor& Wk, | ||
| 41 | const ::brotensor::Tensor& Wv, | ||
| 42 | const ::brotensor::Tensor& Wo, | ||
| 43 | const ::brotensor::Tensor* bq, | ||
| 44 | const ::brotensor::Tensor* bk, | ||
| 45 | const ::brotensor::Tensor* bv, | ||
| 46 | const ::brotensor::Tensor* bo, | ||
| 47 | const float* d_mask, | ||
| 48 | const ::brotensor::Tensor* attn_bias, | ||
| 49 | int num_heads, float scale, | ||
| 50 | ::brotensor::Tensor& O) { | ||
| 51 | 21 | check_fp32(X, "X"); | |
| 52 | 21 | check_fp32(Wq, "Wq"); check_fp32(Wk, "Wk"); | |
| 53 | 21 | check_fp32(Wv, "Wv"); check_fp32(Wo, "Wo"); | |
| 54 | 21 | const int L = X.rows; | |
| 55 | 21 | const int D = X.cols; | |
| 56 |
1/2✓ Branch 0 taken 21 times.
✗ Branch 1 not taken.
|
21 | if (num_heads <= 0 || D % num_heads != 0) { |
| 57 | ✗ | throw std::runtime_error("self_attention_bias_forward: num_heads must divide D"); | |
| 58 | } | ||
| 59 |
1/2✓ Branch 0 taken 21 times.
✗ Branch 1 not taken.
|
42 | if (Wq.rows != D || Wq.cols != D || Wk.rows != D || Wk.cols != D || |
| 60 | 21 | Wv.rows != D || Wv.cols != D || Wo.rows != D || Wo.cols != D) { | |
| 61 | ✗ | throw std::runtime_error("self_attention_bias_forward: Wq/Wk/Wv/Wo must be (D, D)"); | |
| 62 | } | ||
| 63 | 21 | const int H = num_heads; | |
| 64 | 21 | const int dh = D / H; | |
| 65 | 21 | const float* bias = nullptr; | |
| 66 |
3/4✓ Branch 0 taken 16 times.
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 16 times.
|
21 | if (attn_bias && attn_bias->data) { |
| 67 | 16 | check_fp32(*attn_bias, "attn_bias"); | |
| 68 |
1/2✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
|
16 | if (attn_bias->size() != H * L * L) { |
| 69 | ✗ | throw std::runtime_error("self_attention_bias_forward: attn_bias must be (num_heads*L, L)"); | |
| 70 | } | ||
| 71 | 16 | bias = attn_bias->host_f32(); | |
| 72 | 16 | } | |
| 73 | // Optional length-D projection biases (added post-projection). | ||
| 74 | 105 | auto bias_ptr = [&](const ::brotensor::Tensor* b, const char* name) -> const float* { | |
| 75 |
3/4✓ Branch 0 taken 16 times.
✓ Branch 1 taken 68 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 16 times.
|
84 | if (!b || !b->data) return nullptr; |
| 76 | 16 | check_fp32(*b, name); | |
| 77 |
1/2✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
|
16 | if (b->size() != D) |
| 78 | ✗ | throw std::runtime_error(std::string("self_attention_bias_forward: ") + | |
| 79 | ✗ | name + " must have D entries"); | |
| 80 | 16 | return b->host_f32(); | |
| 81 | 84 | }; | |
| 82 | 21 | const float* bqp = bias_ptr(bq, "bq"); | |
| 83 | 21 | const float* bkp = bias_ptr(bk, "bk"); | |
| 84 | 21 | const float* bvp = bias_ptr(bv, "bv"); | |
| 85 | 21 | const float* bop = bias_ptr(bo, "bo"); | |
| 86 |
1/6✗ Branch 0 not taken.
✓ Branch 1 taken 21 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
|
21 | if (O.rows != L || O.cols != D || O.dtype != Dtype::FP32) { |
| 87 | 21 | O.resize(L, D, Dtype::FP32); | |
| 88 | 21 | } | |
| 89 |
2/4✓ Branch 0 taken 21 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 21 times.
|
21 | if (L == 0 || D == 0) return; |
| 90 | |||
| 91 | 21 | const float* Xp = X.host_f32(); | |
| 92 | 21 | const float* Wqp = Wq.host_f32(); | |
| 93 | 21 | const float* Wkp = Wk.host_f32(); | |
| 94 | 21 | const float* Wvp = Wv.host_f32(); | |
| 95 | 21 | const float* Wop = Wo.host_f32(); | |
| 96 | 21 | float* Op = O.host_f32_mut(); | |
| 97 | |||
| 98 | // Per-head projections: Qh / Kh / Vh laid out (H*L, dh). | ||
| 99 | 21 | std::vector<float> Qh(static_cast<size_t>(H) * L * dh); | |
| 100 |
1/2✓ Branch 0 taken 21 times.
✗ Branch 1 not taken.
|
21 | std::vector<float> Kh(static_cast<size_t>(H) * L * dh); |
| 101 |
1/2✓ Branch 0 taken 21 times.
✗ Branch 1 not taken.
|
21 | std::vector<float> Vh(static_cast<size_t>(H) * L * dh); |
| 102 | 84 | auto project = [&](const float* W, const float* b, std::vector<float>& Out) { | |
| 103 |
2/2✓ Branch 0 taken 315 times.
✓ Branch 1 taken 63 times.
|
378 | for (int hh = 0; hh < H; ++hh) { |
| 104 |
2/2✓ Branch 0 taken 5427 times.
✓ Branch 1 taken 315 times.
|
5742 | for (int i = 0; i < L; ++i) { |
| 105 | 5427 | const float* xr = Xp + static_cast<size_t>(i) * D; | |
| 106 |
2/2✓ Branch 0 taken 77016 times.
✓ Branch 1 taken 5427 times.
|
82443 | for (int j = 0; j < dh; ++j) { |
| 107 | 77016 | const int o = hh * dh + j; | |
| 108 | 77016 | const float* wr = W + static_cast<size_t>(o) * D; | |
| 109 |
2/2✓ Branch 0 taken 6336 times.
✓ Branch 1 taken 70680 times.
|
77016 | float acc = b ? b[o] : 0.0f; |
| 110 |
2/2✓ Branch 0 taken 10889280 times.
✓ Branch 1 taken 77016 times.
|
10966296 | for (int k = 0; k < D; ++k) acc += xr[k] * wr[k]; |
| 111 | 77016 | Out[(static_cast<size_t>(hh) * L + i) * dh + j] = acc; | |
| 112 | 77016 | } | |
| 113 | 5427 | } | |
| 114 | 315 | } | |
| 115 | 63 | }; | |
| 116 |
1/2✓ Branch 0 taken 21 times.
✗ Branch 1 not taken.
|
21 | project(Wqp, bqp, Qh); |
| 117 |
1/2✓ Branch 0 taken 21 times.
✗ Branch 1 not taken.
|
21 | project(Wkp, bkp, Kh); |
| 118 |
1/2✓ Branch 0 taken 21 times.
✗ Branch 1 not taken.
|
21 | project(Wvp, bvp, Vh); |
| 119 | |||
| 120 | // Yconcat (L, D): per-head attention output, concatenated. | ||
| 121 |
1/2✓ Branch 0 taken 21 times.
✗ Branch 1 not taken.
|
21 | std::vector<float> Yc(static_cast<size_t>(L) * D, 0.0f); |
| 122 |
1/2✓ Branch 0 taken 21 times.
✗ Branch 1 not taken.
|
21 | std::vector<float> srow(L); |
| 123 | |||
| 124 |
2/2✓ Branch 0 taken 105 times.
✓ Branch 1 taken 21 times.
|
126 | for (int hh = 0; hh < H; ++hh) { |
| 125 |
2/2✓ Branch 0 taken 1809 times.
✓ Branch 1 taken 105 times.
|
1914 | for (int i = 0; i < L; ++i) { |
| 126 |
4/4✓ Branch 0 taken 160 times.
✓ Branch 1 taken 1649 times.
✓ Branch 2 taken 68 times.
✓ Branch 3 taken 92 times.
|
1809 | if (d_mask && d_mask[i] < 0.5f) continue; // padded query → 0 row |
| 127 | // scores = scale * Q.K + bias | ||
| 128 | 1717 | const float* qr = &Qh[(static_cast<size_t>(hh) * L + i) * dh]; | |
| 129 | 1717 | float row_max = -1e30f; | |
| 130 |
2/2✓ Branch 0 taken 54715 times.
✓ Branch 1 taken 1717 times.
|
56432 | for (int j = 0; j < L; ++j) { |
| 131 |
4/4✓ Branch 0 taken 992 times.
✓ Branch 1 taken 53723 times.
✓ Branch 2 taken 428 times.
✓ Branch 3 taken 564 times.
|
54715 | if (d_mask && d_mask[j] < 0.5f) { srow[j] = 0.0f; continue; } |
| 132 | 54151 | const float* kr = &Kh[(static_cast<size_t>(hh) * L + j) * dh]; | |
| 133 | 54151 | float s = 0.0f; | |
| 134 |
2/2✓ Branch 0 taken 836408 times.
✓ Branch 1 taken 54151 times.
|
890559 | for (int k = 0; k < dh; ++k) s += qr[k] * kr[k]; |
| 135 | 54151 | s *= scale; | |
| 136 |
2/2✓ Branch 0 taken 2496 times.
✓ Branch 1 taken 51655 times.
|
54151 | if (bias) s += bias[(static_cast<size_t>(hh) * L + i) * L + j]; |
| 137 | 54151 | srow[j] = s; | |
| 138 |
2/2✓ Branch 0 taken 47507 times.
✓ Branch 1 taken 6644 times.
|
54151 | if (s > row_max) row_max = s; |
| 139 | 54151 | } | |
| 140 | // softmax over valid keys | ||
| 141 | 1717 | float sum = 0.0f; | |
| 142 |
2/2✓ Branch 0 taken 54715 times.
✓ Branch 1 taken 1717 times.
|
56432 | for (int j = 0; j < L; ++j) { |
| 143 |
4/4✓ Branch 0 taken 992 times.
✓ Branch 1 taken 53723 times.
✓ Branch 2 taken 428 times.
✓ Branch 3 taken 564 times.
|
54715 | if (d_mask && d_mask[j] < 0.5f) { srow[j] = 0.0f; continue; } |
| 144 | 54151 | const float e = std::exp(srow[j] - row_max); | |
| 145 | 54151 | srow[j] = e; | |
| 146 | 54151 | sum += e; | |
| 147 | 54151 | } | |
| 148 |
1/2✓ Branch 0 taken 1717 times.
✗ Branch 1 not taken.
|
1717 | const float inv = sum > 0.0f ? 1.0f / sum : 0.0f; |
| 149 | // weighted sum of V | ||
| 150 |
2/2✓ Branch 0 taken 24936 times.
✓ Branch 1 taken 1717 times.
|
26653 | for (int k = 0; k < dh; ++k) { |
| 151 | 24936 | float acc = 0.0f; | |
| 152 |
2/2✓ Branch 0 taken 840920 times.
✓ Branch 1 taken 24936 times.
|
865856 | for (int j = 0; j < L; ++j) { |
| 153 |
4/4✓ Branch 0 taken 7936 times.
✓ Branch 1 taken 832984 times.
✓ Branch 2 taken 3424 times.
✓ Branch 3 taken 4512 times.
|
840920 | if (d_mask && d_mask[j] < 0.5f) continue; |
| 154 | 1672816 | acc += srow[j] * inv * | |
| 155 | 836408 | Vh[(static_cast<size_t>(hh) * L + j) * dh + k]; | |
| 156 | 836408 | } | |
| 157 | 24936 | Yc[static_cast<size_t>(i) * D + (hh * dh + k)] = acc; | |
| 158 | 24936 | } | |
| 159 | 1717 | } | |
| 160 | 105 | } | |
| 161 | |||
| 162 | // Output projection O = Yconcat @ Wo^T, query-mask gated. | ||
| 163 |
2/2✓ Branch 0 taken 335 times.
✓ Branch 1 taken 21 times.
|
356 | for (int i = 0; i < L; ++i) { |
| 164 |
4/4✓ Branch 0 taken 24 times.
✓ Branch 1 taken 311 times.
✓ Branch 2 taken 10 times.
✓ Branch 3 taken 14 times.
|
335 | if (d_mask && d_mask[i] < 0.5f) { |
| 165 |
2/2✓ Branch 0 taken 736 times.
✓ Branch 1 taken 14 times.
|
750 | for (int c = 0; c < D; ++c) Op[static_cast<size_t>(i) * D + c] = 0.0f; |
| 166 | 14 | continue; | |
| 167 | } | ||
| 168 | 321 | const float* yr = &Yc[static_cast<size_t>(i) * D]; | |
| 169 |
2/2✓ Branch 0 taken 24936 times.
✓ Branch 1 taken 321 times.
|
25257 | for (int c = 0; c < D; ++c) { |
| 170 | 24936 | const float* wr = Wop + static_cast<size_t>(c) * D; | |
| 171 |
2/2✓ Branch 0 taken 2112 times.
✓ Branch 1 taken 22824 times.
|
24936 | float acc = bop ? bop[c] : 0.0f; |
| 172 |
2/2✓ Branch 0 taken 3587776 times.
✓ Branch 1 taken 24936 times.
|
3612712 | for (int k = 0; k < D; ++k) acc += yr[k] * wr[k]; |
| 173 | 24936 | Op[static_cast<size_t>(i) * D + c] = acc; | |
| 174 | 24936 | } | |
| 175 | 321 | } | |
| 176 | 21 | } | |
| 177 | |||
| 178 | // ─── Transformer-XL relative-position bias ───────────────────────────────── | ||
| 179 | // | ||
| 180 | // Bias[h*T + q, k] = sum_d Qv[q, h*dk + d] * Pk[(T-1-q) + k, h*dk + d]. | ||
| 181 | // | ||
| 182 | // The rel_shift is expressed as the column offset (T-1-q) rather than by | ||
| 183 | // building matrix_bd and shifting it: the shifted read is the same cost and | ||
| 184 | // there is no (T, 2T-1) intermediate. Parallelised over (head, query) rows, | ||
| 185 | // which is num_heads*T of them — hundreds, so the pool has something to spread. | ||
| 186 | 3 | void rel_pos_bias_xl_forward(const ::brotensor::Tensor& Qv, | |
| 187 | const ::brotensor::Tensor& Pk, | ||
| 188 | int num_heads, int head_dim, | ||
| 189 | ::brotensor::Tensor& Bias) { | ||
| 190 | 3 | check_fp32(Qv, "Qv"); | |
| 191 | 3 | check_fp32(Pk, "Pk"); | |
| 192 | 3 | const int T = Qv.rows; | |
| 193 | 3 | const int D = Qv.cols; | |
| 194 |
1/2✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
|
3 | if (num_heads <= 0 || head_dim <= 0 || num_heads * head_dim != D) |
| 195 | ✗ | throw std::runtime_error("rel_pos_bias_xl_forward: num_heads*head_dim " | |
| 196 | "must equal Qv.cols"); | ||
| 197 |
1/2✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
|
3 | if (Pk.cols != D || Pk.rows != 2 * T - 1) |
| 198 | ✗ | throw std::runtime_error("rel_pos_bias_xl_forward: Pk must be " | |
| 199 | "(2*Qv.rows - 1, Qv.cols)"); | ||
| 200 |
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 (Bias.rows != num_heads * T || Bias.cols != T || |
| 201 | ✗ | Bias.dtype != Dtype::FP32) { | |
| 202 | 3 | Bias = ::brotensor::Tensor::empty_on(Qv.device, num_heads * T, T, | |
| 203 | Dtype::FP32); | ||
| 204 | 3 | } | |
| 205 | |||
| 206 | 3 | const float* qv = static_cast<const float*>(Qv.data); | |
| 207 | 3 | const float* pk = static_cast<const float*>(Pk.data); | |
| 208 | 3 | float* out = static_cast<float*>(Bias.data); | |
| 209 | |||
| 210 |
2/4✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
|
6 | parallel_for(static_cast<std::size_t>(num_heads) * static_cast<std::size_t>(T), |
| 211 | 1844 | [&](std::size_t row) { | |
| 212 | 1841 | const int h = static_cast<int>(row / static_cast<std::size_t>(T)); | |
| 213 | 1841 | const int q = static_cast<int>(row % static_cast<std::size_t>(T)); | |
| 214 | 1841 | const int co = h * head_dim; | |
| 215 | 1841 | const float* qrow = qv + static_cast<std::size_t>(q) * D + co; | |
| 216 | 1841 | const int base = T - 1 - q; | |
| 217 | 1841 | float* orow = out + row * static_cast<std::size_t>(T); | |
| 218 |
2/2✓ Branch 0 taken 397922 times.
✓ Branch 1 taken 1841 times.
|
399763 | for (int k = 0; k < T; ++k) { |
| 219 | 397922 | const float* prow = | |
| 220 | 397922 | pk + static_cast<std::size_t>(base + k) * D + co; | |
| 221 | 397922 | float s = 0.0f; | |
| 222 |
2/2✓ Branch 0 taken 17655135 times.
✓ Branch 1 taken 397922 times.
|
18053057 | for (int d = 0; d < head_dim; ++d) s += qrow[d] * prow[d]; |
| 223 | 397922 | orow[k] = s; | |
| 224 | 397922 | } | |
| 225 | 1841 | }); | |
| 226 | 3 | } | |
| 227 | |||
| 228 | } // namespace brotensor::detail::cpu | ||
| 229 |