src/cpu/cross_attention.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | // CPU backend — cross-attention family (CHUNK 5). | ||
| 2 | // | ||
| 3 | // Ground truth: src/cuda/cross_attention.cu and | ||
| 4 | // src/cuda/cross_attention_with_attn.cu. | ||
| 5 | // | ||
| 6 | // Four ops: | ||
| 7 | // * cross_attention_forward — public forward, O only. | ||
| 8 | // * cross_attention_forward_with_attn — forward + head-averaged AttnAvg, | ||
| 9 | // optional pre-softmax logit bias. | ||
| 10 | // * cross_attention_forward_train — forward, emits per-head caches. | ||
| 11 | // * cross_attention_backward — backward, accumulates dW*. | ||
| 12 | // | ||
| 13 | // DTYPE DECISIONS | ||
| 14 | // The op_table groups these as "FP16 inference + FP32 train". The CPU | ||
| 15 | // backend is FP32-only (per CLAUDE.md). All four CPU impls run the FP32 | ||
| 16 | // scalar math identical to cross_attention.cu's FP32 train-core kernels. | ||
| 17 | // - cross_attention_forward: the CUDA op delegates to the FP16 flash | ||
| 18 | // path for FP16 inputs and to the FP32 train-core for FP32 inputs. The | ||
| 19 | // CPU op always runs the FP32 train-core math. The parity test feeds | ||
| 20 | // FP32 to CPU and FP16 to GPU, comparing with a loose FP16-scale | ||
| 21 | // tolerance. | ||
| 22 | // - cross_attention_forward_with_attn: CUDA op is FP16-only. CPU runs | ||
| 23 | // FP32; parity test quantises through FP16 and uses loose tolerance. | ||
| 24 | // - cross_attention_forward_train / cross_attention_backward: FP32 on | ||
| 25 | // both backends — straightforward FP32<->FP32 parity. | ||
| 26 | // | ||
| 27 | // CONVENTIONS (verified against cross_attention.cu) | ||
| 28 | // * Weight layout: Wq/Wo are (D, D); Wk/Wv are (D, D_ctx). Projection is | ||
| 29 | // out(hh,i,j) = sum_k In(i,k) * W(hh*dh + j, k) — i.e. In @ W^T with the | ||
| 30 | // head slice taken as contiguous rows hh*dh..hh*dh+dh of W. | ||
| 31 | // * Per-head split: Qh/Kh/Vh are (H*L, dh), row-major by (head, token). | ||
| 32 | // Yconcat is (Lq, D) with head hh occupying columns hh*dh..hh*dh+dh. | ||
| 33 | // * Softmax scale: 1/sqrt(dh). | ||
| 34 | // * Mask: length-Lk key-validity buffer (1=valid, 0=invalid). Masked keys | ||
| 35 | // are excluded from the softmax denominator and forced to 0 probability. | ||
| 36 | // Query-side gating is enabled only when Lq == Lk (gate_query): an | ||
| 37 | // invalid query row produces a zero Attn row and a zero O row. | ||
| 38 | // cross_attention_forward_with_attn does NOT do query-side gating (it | ||
| 39 | // follows cxa_row_softmax_kernel which only masks keys). | ||
| 40 | // * Intermediates for the *_train pair: | ||
| 41 | // Qh (H*Lq, dh) per-head Q projection of X | ||
| 42 | // Kh (H*Lk, dh) per-head K projection of Ctx | ||
| 43 | // Vh (H*Lk, dh) per-head V projection of Ctx | ||
| 44 | // Attnh (H*Lq, Lk) per-head softmax probabilities | ||
| 45 | // Yconcat (Lq, D) per-head Attn@V written into head-strided columns | ||
| 46 | // O (Lq, D) Yconcat @ Wo^T (query-gated) | ||
| 47 | // * Backward accumulation: dWq/dWk/dWv/dWo ACCUMULATE (+=). dX/dCtx are | ||
| 48 | // OVERWRITTEN. Matches cx_dW_proj_kernel (+=) and cx_dX/cx_dCtx kernels. | ||
| 49 | |||
| 50 | #include <brotensor/tensor.h> | ||
| 51 | #include <brotensor/detail/cpu/thread_pool.h> | ||
| 52 | |||
| 53 | #include <cmath> | ||
| 54 | #include <vector> | ||
| 55 | |||
| 56 | namespace brotensor::detail::cpu { | ||
| 57 | |||
| 58 | namespace { | ||
| 59 | |||
| 60 | // out(hh,i,j) = sum_k In(i,k) * W(hh*dh + j, k). In: (L, Din). W: (D, Din). | ||
| 61 | // Out: (H*L, dh). Each hh owns rows [hh*L, hh*L+L) of Out exclusively (In/W | ||
| 62 | // read-only), so the head axis parallelizes with no cross-thread writes. | ||
| 63 | 54 | void cx_proj(const float* In, const float* W, float* Out, | |
| 64 | int L, int Din, int H, int dh) { | ||
| 65 |
1/2✓ Branch 0 taken 54 times.
✗ Branch 1 not taken.
|
252 | parallel_for(static_cast<std::size_t>(H), [&](std::size_t hhi) { |
| 66 | 198 | const int hh = static_cast<int>(hhi); | |
| 67 | 198 | const int row_off = hh * dh; | |
| 68 |
2/2✓ Branch 0 taken 1883 times.
✓ Branch 1 taken 198 times.
|
2081 | for (int i = 0; i < L; ++i) { |
| 69 | 1883 | const float* xr = In + static_cast<std::size_t>(i) * Din; | |
| 70 | 1883 | const std::size_t out_row = | |
| 71 | 1883 | (static_cast<std::size_t>(hh) * L + i) * dh; | |
| 72 |
2/2✓ Branch 0 taken 12101 times.
✓ Branch 1 taken 1883 times.
|
13984 | for (int j = 0; j < dh; ++j) { |
| 73 | 12101 | const float* wr = W + static_cast<std::size_t>(row_off + j) * Din; | |
| 74 | 12101 | float acc = 0.0f; | |
| 75 |
2/2✓ Branch 0 taken 351872 times.
✓ Branch 1 taken 12101 times.
|
363973 | for (int k = 0; k < Din; ++k) acc += xr[k] * wr[k]; |
| 76 | 12101 | Out[out_row + j] = acc; | |
| 77 | 12101 | } | |
| 78 | 1883 | } | |
| 79 | 198 | }); | |
| 80 | 54 | } | |
| 81 | |||
| 82 | // Core FP32 forward shared by cross_attention_forward and | ||
| 83 | // cross_attention_forward_train. | ||
| 84 | 13 | void cross_attention_forward_core(const ::brotensor::Tensor& X, | |
| 85 | const ::brotensor::Tensor& Ctx, | ||
| 86 | const ::brotensor::Tensor& Wq, | ||
| 87 | const ::brotensor::Tensor& Wk, | ||
| 88 | const ::brotensor::Tensor& Wv, | ||
| 89 | const ::brotensor::Tensor& Wo, | ||
| 90 | const float* d_mask, | ||
| 91 | int num_heads, | ||
| 92 | ::brotensor::Tensor& Qh, | ||
| 93 | ::brotensor::Tensor& Kh, | ||
| 94 | ::brotensor::Tensor& Vh, | ||
| 95 | ::brotensor::Tensor& Attnh, | ||
| 96 | ::brotensor::Tensor& Yconcat, | ||
| 97 | ::brotensor::Tensor& O) { | ||
| 98 | using ::brotensor::Dtype; | ||
| 99 | 13 | const int Lq = X.rows; | |
| 100 | 13 | const int D = X.cols; | |
| 101 | 13 | const int Lk = Ctx.rows; | |
| 102 | 13 | const int Dctx = Ctx.cols; | |
| 103 | 13 | const int H = num_heads; | |
| 104 |
1/2✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
|
13 | const int dh = (H > 0) ? D / H : 0; |
| 105 | |||
| 106 |
1/6✗ Branch 0 not taken.
✓ Branch 1 taken 13 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
|
13 | if (Qh.rows != H * Lq || Qh.cols != dh || Qh.dtype != Dtype::FP32) |
| 107 | 13 | Qh.resize(H * Lq, dh, Dtype::FP32); | |
| 108 |
1/6✗ Branch 0 not taken.
✓ Branch 1 taken 13 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
|
13 | if (Kh.rows != H * Lk || Kh.cols != dh || Kh.dtype != Dtype::FP32) |
| 109 | 13 | Kh.resize(H * Lk, dh, Dtype::FP32); | |
| 110 |
1/6✗ Branch 0 not taken.
✓ Branch 1 taken 13 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
|
13 | if (Vh.rows != H * Lk || Vh.cols != dh || Vh.dtype != Dtype::FP32) |
| 111 | 13 | Vh.resize(H * Lk, dh, Dtype::FP32); | |
| 112 |
1/6✗ Branch 0 not taken.
✓ Branch 1 taken 13 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
|
13 | if (Attnh.rows != H * Lq || Attnh.cols != Lk || Attnh.dtype != Dtype::FP32) |
| 113 | 13 | Attnh.resize(H * Lq, Lk, Dtype::FP32); | |
| 114 |
1/6✗ Branch 0 not taken.
✓ Branch 1 taken 13 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
|
13 | if (Yconcat.rows != Lq || Yconcat.cols != D || Yconcat.dtype != Dtype::FP32) |
| 115 | 13 | Yconcat.resize(Lq, D, Dtype::FP32); | |
| 116 |
1/6✗ Branch 0 not taken.
✓ Branch 1 taken 13 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
|
13 | if (O.rows != Lq || O.cols != D || O.dtype != Dtype::FP32) |
| 117 | 13 | O.resize(Lq, D, Dtype::FP32); | |
| 118 |
4/8✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 13 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 13 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 13 times.
|
13 | if (Lq == 0 || Lk == 0 || D == 0 || H == 0) return; |
| 119 | |||
| 120 | 13 | const int gate_query = (Lq == Lk) ? 1 : 0; | |
| 121 | 13 | const float inv_sqrtdh = 1.0f / std::sqrt(static_cast<float>(dh)); | |
| 122 | |||
| 123 | 13 | const float* Xp = X.host_f32(); | |
| 124 | 13 | const float* Ctxp = Ctx.host_f32(); | |
| 125 | 13 | const float* Wqp = Wq.host_f32(); | |
| 126 | 13 | const float* Wkp = Wk.host_f32(); | |
| 127 | 13 | const float* Wvp = Wv.host_f32(); | |
| 128 | 13 | const float* Wop = Wo.host_f32(); | |
| 129 | 13 | float* Qp = Qh.host_f32_mut(); | |
| 130 | 13 | float* Kp = Kh.host_f32_mut(); | |
| 131 | 13 | float* Vp = Vh.host_f32_mut(); | |
| 132 | 13 | float* Ap = Attnh.host_f32_mut(); | |
| 133 | 13 | float* Yp = Yconcat.host_f32_mut(); | |
| 134 | 13 | float* Op = O.host_f32_mut(); | |
| 135 | |||
| 136 | // Q from X (D, D); K, V from Ctx (D, Dctx). | ||
| 137 | 13 | cx_proj(Xp, Wqp, Qp, Lq, D, H, dh); | |
| 138 | 13 | cx_proj(Ctxp, Wkp, Kp, Lk, Dctx, H, dh); | |
| 139 | 13 | cx_proj(Ctxp, Wvp, Vp, Lk, Dctx, H, dh); | |
| 140 | |||
| 141 | // Scores -> masked row softmax -> Attnh. Each hh owns rows | ||
| 142 | // [hh*Lq, hh*Lq+Lq) of Attnh exclusively, so parallelizes over hh. | ||
| 143 |
1/2✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
|
56 | parallel_for(static_cast<std::size_t>(H), [&](std::size_t hhi) { |
| 144 | 43 | const int hh = static_cast<int>(hhi); | |
| 145 |
2/2✓ Branch 0 taken 345 times.
✓ Branch 1 taken 43 times.
|
388 | for (int i = 0; i < Lq; ++i) { |
| 146 | 345 | float* arow = Ap + (static_cast<std::size_t>(hh) * Lq + i) * Lk; | |
| 147 |
6/6✓ Branch 0 taken 119 times.
✓ Branch 1 taken 226 times.
✓ Branch 2 taken 30 times.
✓ Branch 3 taken 89 times.
✓ Branch 4 taken 22 times.
✓ Branch 5 taken 8 times.
|
345 | if (gate_query && d_mask && d_mask[i] < 0.5f) { |
| 148 |
2/2✓ Branch 0 taken 64 times.
✓ Branch 1 taken 8 times.
|
72 | for (int j = 0; j < Lk; ++j) arow[j] = 0.0f; |
| 149 | 8 | continue; | |
| 150 | } | ||
| 151 | 337 | const float* qr = Qp + (static_cast<std::size_t>(hh) * Lq + i) * dh; | |
| 152 | 337 | float m = -1e30f; | |
| 153 |
2/2✓ Branch 0 taken 3267 times.
✓ Branch 1 taken 337 times.
|
3604 | for (int j = 0; j < Lk; ++j) { |
| 154 |
4/4✓ Branch 0 taken 457 times.
✓ Branch 1 taken 2810 times.
✓ Branch 2 taken 282 times.
✓ Branch 3 taken 175 times.
|
3267 | if (d_mask && d_mask[j] < 0.5f) { arow[j] = 0.0f; continue; } |
| 155 | 3092 | const float* kr = | |
| 156 | 3092 | Kp + (static_cast<std::size_t>(hh) * Lk + j) * dh; | |
| 157 | 3092 | float s = 0.0f; | |
| 158 |
2/2✓ Branch 0 taken 14927 times.
✓ Branch 1 taken 3092 times.
|
18019 | for (int k = 0; k < dh; ++k) s += qr[k] * kr[k]; |
| 159 | 3092 | s *= inv_sqrtdh; | |
| 160 | 3092 | arow[j] = s; | |
| 161 |
2/2✓ Branch 0 taken 2110 times.
✓ Branch 1 taken 982 times.
|
3092 | if (s > m) m = s; |
| 162 | 3092 | } | |
| 163 | 337 | float sum = 0.0f; | |
| 164 |
2/2✓ Branch 0 taken 3385 times.
✓ Branch 1 taken 337 times.
|
3722 | for (int j = 0; j < Lk; ++j) { |
| 165 |
4/4✓ Branch 0 taken 530 times.
✓ Branch 1 taken 2855 times.
✓ Branch 2 taken 365 times.
✓ Branch 3 taken 165 times.
|
3385 | if (d_mask && d_mask[j] < 0.5f) { arow[j] = 0.0f; continue; } |
| 166 | 3220 | const float e = std::exp(arow[j] - m); | |
| 167 | 3220 | arow[j] = e; | |
| 168 | 3220 | sum += e; | |
| 169 | 3220 | } | |
| 170 |
1/2✓ Branch 0 taken 337 times.
✗ Branch 1 not taken.
|
337 | const float inv = sum > 0.0f ? 1.0f / sum : 0.0f; |
| 171 |
2/2✓ Branch 0 taken 3848 times.
✓ Branch 1 taken 337 times.
|
4185 | for (int j = 0; j < Lk; ++j) arow[j] *= inv; |
| 172 | 337 | } | |
| 173 | 43 | }); | |
| 174 | |||
| 175 | // Attn @ V -> Yconcat(i, hh*dh+k). Each hh writes only its own column | ||
| 176 | // range [hh*dh, hh*dh+dh) of every row of Yconcat, so parallelizes | ||
| 177 | // over hh. | ||
| 178 |
1/2✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
|
56 | parallel_for(static_cast<std::size_t>(H), [&](std::size_t hhi) { |
| 179 | 43 | const int hh = static_cast<int>(hhi); | |
| 180 |
2/2✓ Branch 0 taken 361 times.
✓ Branch 1 taken 43 times.
|
404 | for (int i = 0; i < Lq; ++i) { |
| 181 | 361 | const float* arow = | |
| 182 | 361 | Ap + (static_cast<std::size_t>(hh) * Lq + i) * Lk; | |
| 183 |
2/2✓ Branch 0 taken 2184 times.
✓ Branch 1 taken 361 times.
|
2545 | for (int k = 0; k < dh; ++k) { |
| 184 | 2184 | float acc = 0.0f; | |
| 185 |
2/2✓ Branch 0 taken 21930 times.
✓ Branch 1 taken 2184 times.
|
24114 | for (int j = 0; j < Lk; ++j) { |
| 186 | 21930 | const float vv = | |
| 187 | 21930 | Vp[(static_cast<std::size_t>(hh) * Lk + j) * dh + k]; | |
| 188 | 21930 | acc += arow[j] * vv; | |
| 189 | 21930 | } | |
| 190 | 2184 | Yp[static_cast<std::size_t>(i) * D + (hh * dh + k)] = acc; | |
| 191 | 2184 | } | |
| 192 | 361 | } | |
| 193 | 43 | }); | |
| 194 | |||
| 195 | // Output projection O = Yconcat @ Wo^T, query-gated. Each i owns row i | ||
| 196 | // of O exclusively, so parallelizes over i. | ||
| 197 |
1/2✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
|
93 | parallel_for(static_cast<std::size_t>(Lq), [&](std::size_t ii) { |
| 198 | 80 | const int i = static_cast<int>(ii); | |
| 199 | 80 | float* orow = Op + static_cast<std::size_t>(i) * D; | |
| 200 |
6/6✓ Branch 0 taken 29 times.
✓ Branch 1 taken 51 times.
✓ Branch 2 taken 7 times.
✓ Branch 3 taken 22 times.
✓ Branch 4 taken 5 times.
✓ Branch 5 taken 2 times.
|
80 | if (gate_query && d_mask && d_mask[i] < 0.5f) { |
| 201 |
2/2✓ Branch 0 taken 64 times.
✓ Branch 1 taken 2 times.
|
66 | for (int c = 0; c < D; ++c) orow[c] = 0.0f; |
| 202 | 2 | return; | |
| 203 | } | ||
| 204 | 78 | const float* yr = Yp + static_cast<std::size_t>(i) * D; | |
| 205 |
2/2✓ Branch 0 taken 78 times.
✓ Branch 1 taken 2518 times.
|
2596 | for (int c = 0; c < D; ++c) { |
| 206 | 2518 | const float* wr = Wop + static_cast<std::size_t>(c) * D; | |
| 207 | 2518 | float acc = 0.0f; | |
| 208 |
2/2✓ Branch 0 taken 63604 times.
✓ Branch 1 taken 2518 times.
|
66122 | for (int k = 0; k < D; ++k) acc += yr[k] * wr[k]; |
| 209 | 2518 | orow[c] = acc; | |
| 210 | 2518 | } | |
| 211 | 80 | }); | |
| 212 | 13 | } | |
| 213 | |||
| 214 | } // namespace | ||
| 215 | |||
| 216 | // ─── cross_attention_forward (FP32 scalar; O only) ───────────────────────── | ||
| 217 | |||
| 218 | 8 | void cross_attention_forward(const ::brotensor::Tensor& X, | |
| 219 | const ::brotensor::Tensor& Ctx, | ||
| 220 | const ::brotensor::Tensor& Wq, | ||
| 221 | const ::brotensor::Tensor& Wk, | ||
| 222 | const ::brotensor::Tensor& Wv, | ||
| 223 | const ::brotensor::Tensor& Wo, | ||
| 224 | const float* d_mask, | ||
| 225 | int num_heads, | ||
| 226 | ::brotensor::Tensor& O) { | ||
| 227 | using ::brotensor::Tensor; | ||
| 228 | using ::brotensor::Dtype; | ||
| 229 | 8 | Tensor Qh, Kh, Vh, Attnh, Yconcat; | |
| 230 |
2/4✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
|
16 | cross_attention_forward_core(X, Ctx, Wq, Wk, Wv, Wo, d_mask, num_heads, |
| 231 | 8 | Qh, Kh, Vh, Attnh, Yconcat, O); | |
| 232 | 8 | } | |
| 233 | |||
| 234 | // ─── cross_attention_forward_train (FP32; emits per-head caches) ──────────── | ||
| 235 | |||
| 236 | 5 | void cross_attention_forward_train(const ::brotensor::Tensor& X, | |
| 237 | const ::brotensor::Tensor& Ctx, | ||
| 238 | const ::brotensor::Tensor& Wq, | ||
| 239 | const ::brotensor::Tensor& Wk, | ||
| 240 | const ::brotensor::Tensor& Wv, | ||
| 241 | const ::brotensor::Tensor& Wo, | ||
| 242 | const float* d_mask, | ||
| 243 | int num_heads, | ||
| 244 | ::brotensor::Tensor& Qh, | ||
| 245 | ::brotensor::Tensor& Kh, | ||
| 246 | ::brotensor::Tensor& Vh, | ||
| 247 | ::brotensor::Tensor& Attnh, | ||
| 248 | ::brotensor::Tensor& Yconcat, | ||
| 249 | ::brotensor::Tensor& O) { | ||
| 250 | 10 | cross_attention_forward_core(X, Ctx, Wq, Wk, Wv, Wo, d_mask, num_heads, | |
| 251 | 5 | Qh, Kh, Vh, Attnh, Yconcat, O); | |
| 252 | 5 | } | |
| 253 | |||
| 254 | // ─── cross_attention_forward_with_attn (FP32; + head-averaged AttnAvg) ───── | ||
| 255 | |||
| 256 | 5 | void cross_attention_forward_with_attn(const ::brotensor::Tensor& X, | |
| 257 | const ::brotensor::Tensor& Ctx, | ||
| 258 | const ::brotensor::Tensor& Wq, | ||
| 259 | const ::brotensor::Tensor& Wk, | ||
| 260 | const ::brotensor::Tensor& Wv, | ||
| 261 | const ::brotensor::Tensor& Wo, | ||
| 262 | const float* d_mask, | ||
| 263 | const ::brotensor::Tensor* attn_logit_bias, | ||
| 264 | int num_heads, | ||
| 265 | ::brotensor::Tensor& O, | ||
| 266 | ::brotensor::Tensor& AttnAvg) { | ||
| 267 | using ::brotensor::Dtype; | ||
| 268 | 5 | const int Lq = X.rows; | |
| 269 | 5 | const int D = X.cols; | |
| 270 | 5 | const int Lk = Ctx.rows; | |
| 271 | 5 | const int Dctx = Ctx.cols; | |
| 272 | 5 | const int H = num_heads; | |
| 273 |
1/2✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
|
5 | const int dh = (H > 0) ? D / H : 0; |
| 274 | |||
| 275 |
1/6✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
|
5 | if (O.rows != Lq || O.cols != D || O.dtype != Dtype::FP32) |
| 276 | 5 | O.resize(Lq, D, Dtype::FP32); | |
| 277 |
1/6✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
|
5 | if (AttnAvg.rows != Lq || AttnAvg.cols != Lk || AttnAvg.dtype != Dtype::FP32) |
| 278 | 5 | AttnAvg.resize(Lq, Lk, Dtype::FP32); | |
| 279 |
4/8✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 5 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 5 times.
|
5 | if (Lq == 0 || Lk == 0 || D == 0 || H == 0) return; |
| 280 | |||
| 281 | 5 | const float inv_sqrtdh = 1.0f / std::sqrt(static_cast<float>(dh)); | |
| 282 | |||
| 283 | 5 | const float* Xp = X.host_f32(); | |
| 284 | 5 | const float* Ctxp = Ctx.host_f32(); | |
| 285 | 5 | const float* Wqp = Wq.host_f32(); | |
| 286 | 5 | const float* Wkp = Wk.host_f32(); | |
| 287 | 5 | const float* Wvp = Wv.host_f32(); | |
| 288 | 5 | const float* Wop = Wo.host_f32(); | |
| 289 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 3 times.
|
5 | const float* bias = attn_logit_bias ? attn_logit_bias->host_f32() : nullptr; |
| 290 | 5 | float* Op = O.host_f32_mut(); | |
| 291 | 5 | float* AAp = AttnAvg.host_f32_mut(); | |
| 292 | |||
| 293 | 5 | std::vector<float> Q(static_cast<std::size_t>(H) * Lq * dh, 0.0f); | |
| 294 |
1/2✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
|
5 | std::vector<float> Kbuf(static_cast<std::size_t>(H) * Lk * dh, 0.0f); |
| 295 |
1/2✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
|
5 | std::vector<float> Vbuf(static_cast<std::size_t>(H) * Lk * dh, 0.0f); |
| 296 |
1/2✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
|
5 | std::vector<float> Attn(static_cast<std::size_t>(H) * Lq * Lk, 0.0f); |
| 297 |
1/2✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
|
5 | std::vector<float> Yc(static_cast<std::size_t>(Lq) * D, 0.0f); |
| 298 | |||
| 299 |
1/2✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
|
5 | cx_proj(Xp, Wqp, Q.data(), Lq, D, H, dh); |
| 300 |
1/2✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
|
5 | cx_proj(Ctxp, Wkp, Kbuf.data(), Lk, Dctx, H, dh); |
| 301 |
1/2✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
|
5 | cx_proj(Ctxp, Wvp, Vbuf.data(), Lk, Dctx, H, dh); |
| 302 | |||
| 303 | // Scores (+ optional bias) -> key-masked row softmax. No query gating — | ||
| 304 | // matches cxa_row_softmax_kernel which only masks keys. Each hh owns | ||
| 305 | // rows [hh*Lq, hh*Lq+Lq) of Attn exclusively, so parallelizes over hh. | ||
| 306 |
2/4✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
|
23 | parallel_for(static_cast<std::size_t>(H), [&](std::size_t hhi) { |
| 307 | 18 | const int hh = static_cast<int>(hhi); | |
| 308 |
2/2✓ Branch 0 taken 134 times.
✓ Branch 1 taken 18 times.
|
152 | for (int i = 0; i < Lq; ++i) { |
| 309 | 134 | float* arow = Attn.data() + (static_cast<std::size_t>(hh) * Lq + i) * Lk; | |
| 310 | 134 | const float* qr = Q.data() + (static_cast<std::size_t>(hh) * Lq + i) * dh; | |
| 311 | 134 | float m = -1e30f; | |
| 312 |
2/2✓ Branch 0 taken 1016 times.
✓ Branch 1 taken 134 times.
|
1150 | for (int j = 0; j < Lk; ++j) { |
| 313 | 1016 | const float* kr = | |
| 314 | 1016 | Kbuf.data() + (static_cast<std::size_t>(hh) * Lk + j) * dh; | |
| 315 | 1016 | float s = 0.0f; | |
| 316 |
2/2✓ Branch 0 taken 6538 times.
✓ Branch 1 taken 1016 times.
|
7554 | for (int k = 0; k < dh; ++k) s += qr[k] * kr[k]; |
| 317 | 1016 | s *= inv_sqrtdh; | |
| 318 |
2/2✓ Branch 0 taken 588 times.
✓ Branch 1 taken 428 times.
|
1016 | if (bias) s += bias[static_cast<std::size_t>(i) * Lk + j]; |
| 319 | 1016 | arow[j] = s; | |
| 320 |
4/4✓ Branch 0 taken 283 times.
✓ Branch 1 taken 733 times.
✓ Branch 2 taken 181 times.
✓ Branch 3 taken 102 times.
|
1016 | if (d_mask && d_mask[j] < 0.5f) continue; |
| 321 |
2/2✓ Branch 0 taken 572 times.
✓ Branch 1 taken 342 times.
|
914 | if (s > m) m = s; |
| 322 | 914 | } | |
| 323 | 134 | float sum = 0.0f; | |
| 324 |
2/2✓ Branch 0 taken 1107 times.
✓ Branch 1 taken 134 times.
|
1241 | for (int j = 0; j < Lk; ++j) { |
| 325 |
4/4✓ Branch 0 taken 331 times.
✓ Branch 1 taken 776 times.
✓ Branch 2 taken 218 times.
✓ Branch 3 taken 113 times.
|
1107 | if (d_mask && d_mask[j] < 0.5f) { arow[j] = 0.0f; continue; } |
| 326 | 994 | const float e = std::exp(arow[j] - m); | |
| 327 | 994 | arow[j] = e; | |
| 328 | 994 | sum += e; | |
| 329 | 994 | } | |
| 330 |
1/2✓ Branch 0 taken 134 times.
✗ Branch 1 not taken.
|
134 | const float inv = sum > 0.0f ? 1.0f / sum : 0.0f; |
| 331 |
2/2✓ Branch 0 taken 1106 times.
✓ Branch 1 taken 134 times.
|
1240 | for (int j = 0; j < Lk; ++j) arow[j] *= inv; |
| 332 | 134 | } | |
| 333 | 18 | }); | |
| 334 | |||
| 335 | // AttnAvg(i, j) = (1/H) sum_h Attn(h, i, j). Each (i, j) writes exactly | ||
| 336 | // once from a private serial sum over hh, so parallelizes over i (each i | ||
| 337 | // owns row i of AttnAvg exclusively). | ||
| 338 |
2/4✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
|
29 | parallel_for(static_cast<std::size_t>(Lq), [&](std::size_t ii) { |
| 339 | 24 | const int i = static_cast<int>(ii); | |
| 340 |
2/2✓ Branch 0 taken 183 times.
✓ Branch 1 taken 24 times.
|
207 | for (int j = 0; j < Lk; ++j) { |
| 341 | 183 | float acc = 0.0f; | |
| 342 |
2/2✓ Branch 0 taken 638 times.
✓ Branch 1 taken 183 times.
|
821 | for (int hh = 0; hh < H; ++hh) |
| 343 | 638 | acc += Attn[(static_cast<std::size_t>(hh) * Lq + i) * Lk + j]; | |
| 344 | 183 | AAp[static_cast<std::size_t>(i) * Lk + j] = | |
| 345 | 183 | acc / static_cast<float>(H); | |
| 346 | 183 | } | |
| 347 | 24 | }); | |
| 348 | |||
| 349 | // Attn @ V -> Yconcat(i, hh*dh+k). Each hh writes only its own column | ||
| 350 | // range of every row of Yc, so parallelizes over hh. | ||
| 351 |
2/4✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
|
23 | parallel_for(static_cast<std::size_t>(H), [&](std::size_t hhi) { |
| 352 | 18 | const int hh = static_cast<int>(hhi); | |
| 353 |
2/2✓ Branch 0 taken 134 times.
✓ Branch 1 taken 18 times.
|
152 | for (int i = 0; i < Lq; ++i) { |
| 354 | 134 | const float* arow = | |
| 355 | 134 | Attn.data() + (static_cast<std::size_t>(hh) * Lq + i) * Lk; | |
| 356 |
2/2✓ Branch 0 taken 772 times.
✓ Branch 1 taken 134 times.
|
906 | for (int k = 0; k < dh; ++k) { |
| 357 | 772 | float acc = 0.0f; | |
| 358 |
2/2✓ Branch 0 taken 4140 times.
✓ Branch 1 taken 772 times.
|
4912 | for (int j = 0; j < Lk; ++j) { |
| 359 | 4140 | const float vv = | |
| 360 | 4140 | Vbuf[(static_cast<std::size_t>(hh) * Lk + j) * dh + k]; | |
| 361 | 4140 | acc += arow[j] * vv; | |
| 362 | 4140 | } | |
| 363 | 772 | Yc[static_cast<std::size_t>(i) * D + (hh * dh + k)] = acc; | |
| 364 | 772 | } | |
| 365 | 134 | } | |
| 366 | 18 | }); | |
| 367 | |||
| 368 | // Output projection O = Yconcat @ Wo^T (no query gating). Each i owns | ||
| 369 | // row i of O exclusively, so parallelizes over i. | ||
| 370 |
2/4✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
|
31 | parallel_for(static_cast<std::size_t>(Lq), [&](std::size_t ii) { |
| 371 | 26 | const int i = static_cast<int>(ii); | |
| 372 | 26 | const float* yr = Yc.data() + static_cast<std::size_t>(i) * D; | |
| 373 | 26 | float* orow = Op + static_cast<std::size_t>(i) * D; | |
| 374 |
2/2✓ Branch 0 taken 732 times.
✓ Branch 1 taken 26 times.
|
758 | for (int c = 0; c < D; ++c) { |
| 375 | 732 | const float* wr = Wop + static_cast<std::size_t>(c) * D; | |
| 376 | 732 | float acc = 0.0f; | |
| 377 |
2/2✓ Branch 0 taken 16210 times.
✓ Branch 1 taken 732 times.
|
16942 | for (int k = 0; k < D; ++k) acc += yr[k] * wr[k]; |
| 378 | 732 | orow[c] = acc; | |
| 379 | 732 | } | |
| 380 | 26 | }); | |
| 381 | 5 | } | |
| 382 | |||
| 383 | // ─── cross_attention_backward (FP32; accumulates dW*, overwrites dX/dCtx) ── | ||
| 384 | |||
| 385 | 5 | void cross_attention_backward(const ::brotensor::Tensor& dO, | |
| 386 | const ::brotensor::Tensor& X, | ||
| 387 | const ::brotensor::Tensor& Ctx, | ||
| 388 | const ::brotensor::Tensor& Qh, | ||
| 389 | const ::brotensor::Tensor& Kh, | ||
| 390 | const ::brotensor::Tensor& Vh, | ||
| 391 | const ::brotensor::Tensor& Attnh, | ||
| 392 | const ::brotensor::Tensor& Yconcat, | ||
| 393 | const ::brotensor::Tensor& Wq, | ||
| 394 | const ::brotensor::Tensor& Wk, | ||
| 395 | const ::brotensor::Tensor& Wv, | ||
| 396 | const ::brotensor::Tensor& Wo, | ||
| 397 | const float* d_mask, | ||
| 398 | int num_heads, | ||
| 399 | ::brotensor::Tensor& dX, | ||
| 400 | ::brotensor::Tensor& dCtx, | ||
| 401 | ::brotensor::Tensor& dWq, | ||
| 402 | ::brotensor::Tensor& dWk, | ||
| 403 | ::brotensor::Tensor& dWv, | ||
| 404 | ::brotensor::Tensor& dWo) { | ||
| 405 | using ::brotensor::Dtype; | ||
| 406 | 5 | const int Lq = X.rows; | |
| 407 | 5 | const int D = X.cols; | |
| 408 | 5 | const int Lk = Ctx.rows; | |
| 409 | 5 | const int Dctx = Ctx.cols; | |
| 410 | 5 | const int H = num_heads; | |
| 411 |
1/2✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
|
5 | const int dh = (H > 0) ? D / H : 0; |
| 412 | |||
| 413 |
3/6✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 5 times.
|
5 | if (dX.rows != Lq || dX.cols != D || dX.dtype != Dtype::FP32) |
| 414 | ✗ | dX.resize(Lq, D, Dtype::FP32); | |
| 415 |
3/6✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 5 times.
|
5 | if (dCtx.rows != Lk || dCtx.cols != Dctx || dCtx.dtype != Dtype::FP32) |
| 416 | ✗ | dCtx.resize(Lk, Dctx, Dtype::FP32); | |
| 417 |
4/8✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 5 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 5 times.
|
5 | if (Lq == 0 || Lk == 0 || D == 0 || H == 0) return; |
| 418 | |||
| 419 | 5 | const int gate_query = (Lq == Lk) ? 1 : 0; | |
| 420 | 5 | const float inv_sqrtdh = 1.0f / std::sqrt(static_cast<float>(dh)); | |
| 421 | |||
| 422 | 5 | const float* dOp = dO.host_f32(); | |
| 423 | 5 | const float* Xp = X.host_f32(); | |
| 424 | 5 | const float* Cp = Ctx.host_f32(); | |
| 425 | 5 | const float* Qp = Qh.host_f32(); | |
| 426 | 5 | const float* Kp = Kh.host_f32(); | |
| 427 | 5 | const float* Vp = Vh.host_f32(); | |
| 428 | 5 | const float* Ap = Attnh.host_f32(); | |
| 429 | 5 | const float* Yp = Yconcat.host_f32(); | |
| 430 | 5 | const float* Wqp = Wq.host_f32(); | |
| 431 | 5 | const float* Wkp = Wk.host_f32(); | |
| 432 | 5 | const float* Wvp = Wv.host_f32(); | |
| 433 | 5 | const float* Wop = Wo.host_f32(); | |
| 434 | 5 | float* dXp = dX.host_f32_mut(); | |
| 435 | 5 | float* dCp = dCtx.host_f32_mut(); | |
| 436 | 5 | float* dWqp = dWq.host_f32_mut(); | |
| 437 | 5 | float* dWkp = dWk.host_f32_mut(); | |
| 438 | 5 | float* dWvp = dWv.host_f32_mut(); | |
| 439 | 5 | float* dWop = dWo.host_f32_mut(); | |
| 440 | |||
| 441 | 5 | std::vector<float> dYc(static_cast<std::size_t>(Lq) * D, 0.0f); | |
| 442 |
1/2✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
|
5 | std::vector<float> dAttn(static_cast<std::size_t>(H) * Lq * Lk, 0.0f); |
| 443 |
1/2✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
|
5 | std::vector<float> dVh(static_cast<std::size_t>(H) * Lk * dh, 0.0f); |
| 444 |
1/2✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
|
5 | std::vector<float> dScores(static_cast<std::size_t>(H) * Lq * Lk, 0.0f); |
| 445 |
1/2✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
|
5 | std::vector<float> dQh(static_cast<std::size_t>(H) * Lq * dh, 0.0f); |
| 446 |
1/2✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
|
5 | std::vector<float> dKh(static_cast<std::size_t>(H) * Lk * dh, 0.0f); |
| 447 | |||
| 448 | // dYconcat = dO @ Wo (query-gated; overwrite). Each i owns row i of dYc | ||
| 449 | // exclusively, so parallelizes over i. | ||
| 450 |
2/4✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
|
45 | parallel_for(static_cast<std::size_t>(Lq), [&](std::size_t ii) { |
| 451 | 40 | const int i = static_cast<int>(ii); | |
| 452 |
4/4✓ Branch 0 taken 15 times.
✓ Branch 1 taken 25 times.
✓ Branch 2 taken 8 times.
✓ Branch 3 taken 7 times.
|
40 | const bool valid = !(gate_query && d_mask && d_mask[i] < 0.5f); |
| 453 |
2/2✓ Branch 0 taken 40 times.
✓ Branch 1 taken 1616 times.
|
1656 | for (int k = 0; k < D; ++k) { |
| 454 | 1616 | float acc = 0.0f; | |
| 455 |
2/2✓ Branch 0 taken 1576 times.
✓ Branch 1 taken 40 times.
|
1616 | if (valid) { |
| 456 |
2/2✓ Branch 0 taken 67588 times.
✓ Branch 1 taken 1576 times.
|
69164 | for (int c = 0; c < D; ++c) |
| 457 | 135176 | acc += Wop[static_cast<std::size_t>(c) * D + k] * | |
| 458 | 67588 | dOp[static_cast<std::size_t>(i) * D + c]; | |
| 459 | 1576 | } | |
| 460 | 1616 | dYc[static_cast<std::size_t>(i) * D + k] = acc; | |
| 461 | 1616 | } | |
| 462 | 40 | }); | |
| 463 | // dWo accumulates. c-outer with a private serial sum over i — each | ||
| 464 | // (c, k) cell is written exactly once, so this parallelizes over c | ||
| 465 | // (unlike ops_impl.cpp's mha_backward/attention_backward dWo, which use | ||
| 466 | // a token-outermost accumulate-in-place order and are left | ||
| 467 | // single-threaded there). | ||
| 468 |
2/4✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
|
222 | parallel_for(static_cast<std::size_t>(D), [&](std::size_t ci) { |
| 469 | 217 | const int c = static_cast<int>(ci); | |
| 470 |
2/2✓ Branch 0 taken 8027 times.
✓ Branch 1 taken 217 times.
|
8244 | for (int k = 0; k < D; ++k) { |
| 471 | 8027 | float acc = 0.0f; | |
| 472 |
2/2✓ Branch 0 taken 53591 times.
✓ Branch 1 taken 8027 times.
|
61618 | for (int i = 0; i < Lq; ++i) { |
| 473 |
6/6✓ Branch 0 taken 10700 times.
✓ Branch 1 taken 42891 times.
✓ Branch 2 taken 5922 times.
✓ Branch 3 taken 4778 times.
✓ Branch 4 taken 4257 times.
✓ Branch 5 taken 1665 times.
|
53591 | if (gate_query && d_mask && d_mask[i] < 0.5f) continue; |
| 474 | 103852 | acc += dOp[static_cast<std::size_t>(i) * D + c] * | |
| 475 | 51926 | Yp[static_cast<std::size_t>(i) * D + k]; | |
| 476 | 51926 | } | |
| 477 | 8027 | dWop[static_cast<std::size_t>(c) * D + k] += acc; | |
| 478 | 8027 | } | |
| 479 | 217 | }); | |
| 480 | |||
| 481 | // Per-head dAttn and dVh. Each hh owns rows [hh*Lq, hh*Lq+Lq) of dAttn | ||
| 482 | // and [hh*Lk, hh*Lk+Lk) of dVh exclusively (dVh's inner reduction over i | ||
| 483 | // sums only into this head's own private slice), so parallelizes over hh. | ||
| 484 |
2/4✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
|
31 | parallel_for(static_cast<std::size_t>(H), [&](std::size_t hhi) { |
| 485 | 26 | const int hh = static_cast<int>(hhi); | |
| 486 |
2/2✓ Branch 0 taken 220 times.
✓ Branch 1 taken 26 times.
|
246 | for (int i = 0; i < Lq; ++i) { |
| 487 |
2/2✓ Branch 0 taken 2401 times.
✓ Branch 1 taken 220 times.
|
2621 | for (int j = 0; j < Lk; ++j) { |
| 488 | 2401 | float acc = 0.0f; | |
| 489 |
2/2✓ Branch 0 taken 5671 times.
✓ Branch 1 taken 2401 times.
|
8072 | for (int k = 0; k < dh; ++k) { |
| 490 | 5671 | const float dy = | |
| 491 | 5671 | dYc[static_cast<std::size_t>(i) * D + (hh * dh + k)]; | |
| 492 | 5671 | const float vv = | |
| 493 | 5671 | Vp[(static_cast<std::size_t>(hh) * Lk + j) * dh + k]; | |
| 494 | 5671 | acc += dy * vv; | |
| 495 | 5671 | } | |
| 496 | 2401 | dAttn[(static_cast<std::size_t>(hh) * Lq + i) * Lk + j] = acc; | |
| 497 | 2401 | } | |
| 498 | 220 | } | |
| 499 |
2/2✓ Branch 0 taken 337 times.
✓ Branch 1 taken 26 times.
|
363 | for (int j = 0; j < Lk; ++j) { |
| 500 |
2/2✓ Branch 0 taken 2487 times.
✓ Branch 1 taken 337 times.
|
2824 | for (int k = 0; k < dh; ++k) { |
| 501 | 2487 | float acc = 0.0f; | |
| 502 |
2/2✓ Branch 0 taken 22632 times.
✓ Branch 1 taken 2487 times.
|
25119 | for (int i = 0; i < Lq; ++i) { |
| 503 | 22632 | const float a = | |
| 504 | 22632 | Ap[(static_cast<std::size_t>(hh) * Lq + i) * Lk + j]; | |
| 505 | 22632 | const float dy = | |
| 506 | 22632 | dYc[static_cast<std::size_t>(i) * D + (hh * dh + k)]; | |
| 507 | 22632 | acc += a * dy; | |
| 508 | 22632 | } | |
| 509 | 2487 | dVh[(static_cast<std::size_t>(hh) * Lk + j) * dh + k] = acc; | |
| 510 | 2487 | } | |
| 511 | 337 | } | |
| 512 | 26 | }); | |
| 513 | |||
| 514 | // Per-head row-softmax backward -> dScores. Each hh owns rows | ||
| 515 | // [hh*Lq, hh*Lq+Lq) of dScores exclusively, so parallelizes over hh. | ||
| 516 |
2/4✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
|
31 | parallel_for(static_cast<std::size_t>(H), [&](std::size_t hhi) { |
| 517 | 26 | const int hh = static_cast<int>(hhi); | |
| 518 |
2/2✓ Branch 0 taken 222 times.
✓ Branch 1 taken 26 times.
|
248 | for (int i = 0; i < Lq; ++i) { |
| 519 | 222 | const float* prow = | |
| 520 | 222 | Ap + (static_cast<std::size_t>(hh) * Lq + i) * Lk; | |
| 521 | 222 | const float* dprow = | |
| 522 | 222 | dAttn.data() + (static_cast<std::size_t>(hh) * Lq + i) * Lk; | |
| 523 | 222 | float* drow = | |
| 524 | 222 | dScores.data() + (static_cast<std::size_t>(hh) * Lq + i) * Lk; | |
| 525 |
6/6✓ Branch 0 taken 57 times.
✓ Branch 1 taken 165 times.
✓ Branch 2 taken 28 times.
✓ Branch 3 taken 29 times.
✓ Branch 4 taken 20 times.
✓ Branch 5 taken 8 times.
|
222 | if (gate_query && d_mask && d_mask[i] < 0.5f) { |
| 526 |
2/2✓ Branch 0 taken 64 times.
✓ Branch 1 taken 8 times.
|
72 | for (int j = 0; j < Lk; ++j) drow[j] = 0.0f; |
| 527 | 8 | continue; | |
| 528 | } | ||
| 529 | 214 | float dot = 0.0f; | |
| 530 |
2/2✓ Branch 0 taken 2796 times.
✓ Branch 1 taken 214 times.
|
3010 | for (int j = 0; j < Lk; ++j) dot += dprow[j] * prow[j]; |
| 531 |
2/2✓ Branch 0 taken 2894 times.
✓ Branch 1 taken 214 times.
|
3108 | for (int j = 0; j < Lk; ++j) { |
| 532 |
4/4✓ Branch 0 taken 438 times.
✓ Branch 1 taken 2456 times.
✓ Branch 2 taken 298 times.
✓ Branch 3 taken 140 times.
|
2894 | if (d_mask && d_mask[j] < 0.5f) drow[j] = 0.0f; |
| 533 | 2754 | else drow[j] = prow[j] * (dprow[j] - dot) * inv_sqrtdh; | |
| 534 | 2894 | } | |
| 535 | 214 | } | |
| 536 | 26 | }); | |
| 537 | |||
| 538 | // Per-head dQh, dKh. Each hh owns rows [hh*Lq, hh*Lq+Lq) of dQh and | ||
| 539 | // [hh*Lk, hh*Lk+Lk) of dKh exclusively, so parallelizes over hh. | ||
| 540 |
2/4✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
|
31 | parallel_for(static_cast<std::size_t>(H), [&](std::size_t hhi) { |
| 541 | 26 | const int hh = static_cast<int>(hhi); | |
| 542 |
2/2✓ Branch 0 taken 211 times.
✓ Branch 1 taken 26 times.
|
237 | for (int i = 0; i < Lq; ++i) { |
| 543 |
2/2✓ Branch 0 taken 1434 times.
✓ Branch 1 taken 211 times.
|
1645 | for (int k = 0; k < dh; ++k) { |
| 544 | 1434 | float acc = 0.0f; | |
| 545 |
2/2✓ Branch 0 taken 6528 times.
✓ Branch 1 taken 1434 times.
|
7962 | for (int j = 0; j < Lk; ++j) { |
| 546 | 6528 | const float ds = | |
| 547 | 6528 | dScores[(static_cast<std::size_t>(hh) * Lq + i) * Lk + j]; | |
| 548 | 6528 | const float kk = | |
| 549 | 6528 | Kp[(static_cast<std::size_t>(hh) * Lk + j) * dh + k]; | |
| 550 | 6528 | acc += ds * kk; | |
| 551 | 6528 | } | |
| 552 | 1434 | dQh[(static_cast<std::size_t>(hh) * Lq + i) * dh + k] = acc; | |
| 553 | 1434 | } | |
| 554 | 211 | } | |
| 555 |
2/2✓ Branch 0 taken 329 times.
✓ Branch 1 taken 26 times.
|
355 | for (int j = 0; j < Lk; ++j) { |
| 556 |
2/2✓ Branch 0 taken 2452 times.
✓ Branch 1 taken 329 times.
|
2781 | for (int k = 0; k < dh; ++k) { |
| 557 | 2452 | float acc = 0.0f; | |
| 558 |
2/2✓ Branch 0 taken 22610 times.
✓ Branch 1 taken 2452 times.
|
25062 | for (int i = 0; i < Lq; ++i) { |
| 559 | 22610 | const float ds = | |
| 560 | 22610 | dScores[(static_cast<std::size_t>(hh) * Lq + i) * Lk + j]; | |
| 561 | 22610 | const float qq = | |
| 562 | 22610 | Qp[(static_cast<std::size_t>(hh) * Lq + i) * dh + k]; | |
| 563 | 22610 | acc += ds * qq; | |
| 564 | 22610 | } | |
| 565 | 2452 | dKh[(static_cast<std::size_t>(hh) * Lk + j) * dh + k] = acc; | |
| 566 | 2452 | } | |
| 567 | 329 | } | |
| 568 | 26 | }); | |
| 569 | |||
| 570 | // dWq (D, D) accumulates against X. | ||
| 571 | // dWq(hh*dh+j, k) += sum_i dQh(hh,i,j) * X(i,k). | ||
| 572 | // wrow-outer with a private serial sum over i — each (wrow, k) cell is | ||
| 573 | // written exactly once, so this parallelizes over wrow. | ||
| 574 |
2/4✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
|
226 | parallel_for(static_cast<std::size_t>(D), [&](std::size_t wrowi) { |
| 575 | 221 | const int wrow = static_cast<int>(wrowi); | |
| 576 | 221 | const int hh = wrow / dh; | |
| 577 | 221 | const int j = wrow % dh; | |
| 578 |
2/2✓ Branch 0 taken 7360 times.
✓ Branch 1 taken 221 times.
|
7581 | for (int k = 0; k < D; ++k) { |
| 579 | 7360 | float acc = 0.0f; | |
| 580 |
2/2✓ Branch 0 taken 17125 times.
✓ Branch 1 taken 7360 times.
|
24485 | for (int i = 0; i < Lq; ++i) { |
| 581 | 17125 | const float xv = Xp[static_cast<std::size_t>(i) * D + k]; | |
| 582 | 17125 | acc += dQh[(static_cast<std::size_t>(hh) * Lq + i) * dh + j] * xv; | |
| 583 | 17125 | } | |
| 584 | 7360 | dWqp[static_cast<std::size_t>(wrow) * D + k] += acc; | |
| 585 | 7360 | } | |
| 586 | 221 | }); | |
| 587 | // dWk, dWv (D, Dctx) accumulate against Ctx. Same wrow-outer | ||
| 588 | // private-sum-inner pattern, so parallelizes over wrow. | ||
| 589 |
2/4✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
|
221 | parallel_for(static_cast<std::size_t>(D), [&](std::size_t wrowi) { |
| 590 | 216 | const int wrow = static_cast<int>(wrowi); | |
| 591 | 216 | const int hh = wrow / dh; | |
| 592 | 216 | const int j = wrow % dh; | |
| 593 |
2/2✓ Branch 0 taken 6350 times.
✓ Branch 1 taken 216 times.
|
6566 | for (int k = 0; k < Dctx; ++k) { |
| 594 | 6350 | float ak = 0.0f, av = 0.0f; | |
| 595 |
2/2✓ Branch 0 taken 23567 times.
✓ Branch 1 taken 6350 times.
|
29917 | for (int i = 0; i < Lk; ++i) { |
| 596 | 23567 | const float cv = Cp[static_cast<std::size_t>(i) * Dctx + k]; | |
| 597 | 23567 | ak += dKh[(static_cast<std::size_t>(hh) * Lk + i) * dh + j] * cv; | |
| 598 | 23567 | av += dVh[(static_cast<std::size_t>(hh) * Lk + i) * dh + j] * cv; | |
| 599 | 23567 | } | |
| 600 | 6350 | const std::size_t idx = static_cast<std::size_t>(wrow) * Dctx + k; | |
| 601 | 6350 | dWkp[idx] += ak; | |
| 602 | 6350 | dWvp[idx] += av; | |
| 603 | 6350 | } | |
| 604 | 216 | }); | |
| 605 | |||
| 606 | // dX(i,k) = sum over heads, j: dQh(hh,i,j) * Wq(hh*dh+j, k). Overwrite. | ||
| 607 | // Each i owns row i of dX exclusively, so parallelizes over i. | ||
| 608 |
2/4✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
|
40 | parallel_for(static_cast<std::size_t>(Lq), [&](std::size_t ii) { |
| 609 | 35 | const int i = static_cast<int>(ii); | |
| 610 |
2/2✓ Branch 0 taken 1665 times.
✓ Branch 1 taken 35 times.
|
1700 | for (int k = 0; k < D; ++k) { |
| 611 | 1665 | float acc = 0.0f; | |
| 612 |
2/2✓ Branch 0 taken 8157 times.
✓ Branch 1 taken 1665 times.
|
9822 | for (int hh = 0; hh < H; ++hh) { |
| 613 |
2/2✓ Branch 0 taken 20518 times.
✓ Branch 1 taken 8157 times.
|
28675 | for (int j = 0; j < dh; ++j) { |
| 614 | 20518 | const int wrow = hh * dh + j; | |
| 615 | 20518 | const float gq = | |
| 616 | 20518 | dQh[(static_cast<std::size_t>(hh) * Lq + i) * dh + j]; | |
| 617 | 20518 | acc += gq * Wqp[static_cast<std::size_t>(wrow) * D + k]; | |
| 618 | 20518 | } | |
| 619 | 8157 | } | |
| 620 | 1665 | dXp[static_cast<std::size_t>(i) * D + k] = acc; | |
| 621 | 1665 | } | |
| 622 | 35 | }); | |
| 623 | |||
| 624 | // dCtx(j,k) = sum over heads, m: dKh*Wk + dVh*Wv at (hh*dh+m, k). | ||
| 625 | // Overwrite. Each j owns row j of dCtx exclusively, so parallelizes | ||
| 626 | // over j. | ||
| 627 |
2/4✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
|
59 | parallel_for(static_cast<std::size_t>(Lk), [&](std::size_t ji) { |
| 628 | 54 | const int j = static_cast<int>(ji); | |
| 629 |
2/2✓ Branch 0 taken 1872 times.
✓ Branch 1 taken 54 times.
|
1926 | for (int k = 0; k < Dctx; ++k) { |
| 630 | 1872 | float acc = 0.0f; | |
| 631 |
2/2✓ Branch 0 taken 9518 times.
✓ Branch 1 taken 1872 times.
|
11390 | for (int hh = 0; hh < H; ++hh) { |
| 632 |
2/2✓ Branch 0 taken 51060 times.
✓ Branch 1 taken 9518 times.
|
60578 | for (int m = 0; m < dh; ++m) { |
| 633 | 51060 | const int wrow = hh * dh + m; | |
| 634 | 51060 | const std::size_t widx = | |
| 635 | 51060 | static_cast<std::size_t>(wrow) * Dctx + k; | |
| 636 | 51060 | const float gk = | |
| 637 | 51060 | dKh[(static_cast<std::size_t>(hh) * Lk + j) * dh + m]; | |
| 638 | 51060 | const float gv = | |
| 639 | 51060 | dVh[(static_cast<std::size_t>(hh) * Lk + j) * dh + m]; | |
| 640 | 51060 | acc += gk * Wkp[widx] + gv * Wvp[widx]; | |
| 641 | 51060 | } | |
| 642 | 9518 | } | |
| 643 | 1872 | dCp[static_cast<std::size_t>(j) * Dctx + k] = acc; | |
| 644 | 1872 | } | |
| 645 | 54 | }); | |
| 646 | 5 | } | |
| 647 | |||
| 648 | } // namespace brotensor::detail::cpu | ||
| 649 |