src/cpu/self_attention_decomposed_rel_pos.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | // ─── CPU self-attention with decomposed 2D relative-position bias ────────── | ||
| 2 | // | ||
| 3 | // FP32 scalar host reference for the SAM / ViTDet image-encoder attention. | ||
| 4 | // Multi-head self-attention where the pre-softmax bias is the *decomposed* | ||
| 5 | // 2D relative-position term from Dosovitskiy-style ViTDet (segment_anything | ||
| 6 | // `add_decomposed_rel_pos`): | ||
| 7 | // | ||
| 8 | // r_q = Q_h[q] (projected, UNSCALED query) | ||
| 9 | // bias[h,q,k] = r_q . Rh[qh, kh] + r_q . Rw[qw, kw] | ||
| 10 | // S[h,q,k] = scale * (Q_h[q] . K_h[k]) + bias[h,q,k] | ||
| 11 | // O = concat_h( softmax_k(S[h]) @ V_h ) @ Wo | ||
| 12 | // | ||
| 13 | // where a token index t maps to grid coords (t / grid_w, t % grid_w) over a | ||
| 14 | // grid_h × grid_w patch grid (so L == grid_h*grid_w), and | ||
| 15 | // Rh[qh, kh] = rel_pos_h[(qh - kh) + (grid_h - 1)] (length head_dim) | ||
| 16 | // Rw[qw, kw] = rel_pos_w[(qw - kw) + (grid_w - 1)]. | ||
| 17 | // | ||
| 18 | // This is the q*size == k*size case of segment_anything's get_rel_pos (no | ||
| 19 | // rel-pos interpolation): rel_pos_h has exactly 2*grid_h-1 rows, indexed by the | ||
| 20 | // signed query−key row offset shifted into [0, 2*grid_h-2]. Windowed blocks | ||
| 21 | // call this per window (grid_h == grid_w == window); global blocks call it once | ||
| 22 | // over the full 64×64 grid. The bias is data-dependent (it reads Q), which is | ||
| 23 | // why it can't be expressed through the static-bias self_attention_bias op. | ||
| 24 | // | ||
| 25 | // Unlike that static (num_heads*L, L) bias, the decomposed form is never | ||
| 26 | // materialised: per (head, query) we precompute length-grid_h and length-grid_w | ||
| 27 | // dot-product vectors and add bias[k] = relh[kh] + relw[kw] inside the score | ||
| 28 | // loop — O(L*(grid_h+grid_w)*head_dim) work, not O(L*L*head_dim). | ||
| 29 | // | ||
| 30 | // qkv and output projections carry optional biases (SAM uses both). Wq/Wk/Wv/Wo | ||
| 31 | // are (D, D); per-head split takes contiguous weight rows hh*dh .. hh*dh+dh. | ||
| 32 | |||
| 33 | #include <brotensor/tensor.h> | ||
| 34 | |||
| 35 | #include <cmath> | ||
| 36 | #include <stdexcept> | ||
| 37 | #include <string> | ||
| 38 | #include <vector> | ||
| 39 | |||
| 40 | namespace brotensor::detail::cpu { | ||
| 41 | |||
| 42 | namespace { | ||
| 43 | |||
| 44 | 712 | inline void check_fp32(const ::brotensor::Tensor& t, const char* name) { | |
| 45 |
1/2✓ Branch 0 taken 712 times.
✗ Branch 1 not taken.
|
712 | if (t.dtype != Dtype::FP32) { |
| 46 | ✗ | throw std::runtime_error( | |
| 47 | ✗ | std::string("self_attention_decomposed_rel_pos_forward: ") + name + | |
| 48 | " must be FP32 (CPU backend is FP32-only)"); | ||
| 49 | } | ||
| 50 | 712 | } | |
| 51 | |||
| 52 | } // namespace | ||
| 53 | |||
| 54 | 66 | void self_attention_decomposed_rel_pos_forward( | |
| 55 | const ::brotensor::Tensor& X, | ||
| 56 | const ::brotensor::Tensor& Wq, const ::brotensor::Tensor* bq, | ||
| 57 | const ::brotensor::Tensor& Wk, const ::brotensor::Tensor* bk, | ||
| 58 | const ::brotensor::Tensor& Wv, const ::brotensor::Tensor* bv, | ||
| 59 | const ::brotensor::Tensor& Wo, const ::brotensor::Tensor* bo, | ||
| 60 | const ::brotensor::Tensor& rel_pos_h, | ||
| 61 | const ::brotensor::Tensor& rel_pos_w, | ||
| 62 | int num_heads, int grid_h, int grid_w, float scale, | ||
| 63 | ::brotensor::Tensor& O) { | ||
| 64 | 66 | check_fp32(X, "X"); | |
| 65 | 66 | check_fp32(Wq, "Wq"); check_fp32(Wk, "Wk"); | |
| 66 | 66 | check_fp32(Wv, "Wv"); check_fp32(Wo, "Wo"); | |
| 67 | 66 | check_fp32(rel_pos_h, "rel_pos_h"); check_fp32(rel_pos_w, "rel_pos_w"); | |
| 68 | |||
| 69 | 66 | const int L = X.rows; | |
| 70 | 66 | const int D = X.cols; | |
| 71 |
1/2✓ Branch 0 taken 66 times.
✗ Branch 1 not taken.
|
66 | if (num_heads <= 0 || D % num_heads != 0) |
| 72 | ✗ | throw std::runtime_error( | |
| 73 | "self_attention_decomposed_rel_pos_forward: num_heads must divide D"); | ||
| 74 |
1/2✓ Branch 0 taken 66 times.
✗ Branch 1 not taken.
|
66 | if (grid_h <= 0 || grid_w <= 0 || grid_h * grid_w != L) |
| 75 | ✗ | throw std::runtime_error( | |
| 76 | "self_attention_decomposed_rel_pos_forward: grid_h*grid_w must equal X.rows"); | ||
| 77 |
1/2✓ Branch 0 taken 66 times.
✗ Branch 1 not taken.
|
132 | if (Wq.rows != D || Wq.cols != D || Wk.rows != D || Wk.cols != D || |
| 78 | 66 | Wv.rows != D || Wv.cols != D || Wo.rows != D || Wo.cols != D) | |
| 79 | ✗ | throw std::runtime_error( | |
| 80 | "self_attention_decomposed_rel_pos_forward: Wq/Wk/Wv/Wo must be (D, D)"); | ||
| 81 | |||
| 82 | 66 | const int H = num_heads; | |
| 83 | 66 | const int dh = D / H; | |
| 84 |
1/2✓ Branch 0 taken 66 times.
✗ Branch 1 not taken.
|
66 | if (rel_pos_h.rows != 2 * grid_h - 1 || rel_pos_h.cols != dh) |
| 85 | ✗ | throw std::runtime_error( | |
| 86 | "self_attention_decomposed_rel_pos_forward: rel_pos_h must be (2*grid_h-1, head_dim)"); | ||
| 87 |
1/2✓ Branch 0 taken 66 times.
✗ Branch 1 not taken.
|
66 | if (rel_pos_w.rows != 2 * grid_w - 1 || rel_pos_w.cols != dh) |
| 88 | ✗ | throw std::runtime_error( | |
| 89 | "self_attention_decomposed_rel_pos_forward: rel_pos_w must be (2*grid_w-1, head_dim)"); | ||
| 90 | |||
| 91 | 330 | auto bias_ptr = [&](const ::brotensor::Tensor* b, const char* name) -> const float* { | |
| 92 |
3/4✓ Branch 0 taken 240 times.
✓ Branch 1 taken 24 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 240 times.
|
264 | if (!b || !b->data) return nullptr; |
| 93 | 240 | check_fp32(*b, name); | |
| 94 |
1/2✓ Branch 0 taken 240 times.
✗ Branch 1 not taken.
|
240 | if (b->size() != D) |
| 95 | ✗ | throw std::runtime_error( | |
| 96 | ✗ | std::string("self_attention_decomposed_rel_pos_forward: ") + name + | |
| 97 | " must have D entries"); | ||
| 98 | 240 | return b->host_f32(); | |
| 99 | 264 | }; | |
| 100 | 66 | const float* bqp = bias_ptr(bq, "bq"); | |
| 101 | 66 | const float* bkp = bias_ptr(bk, "bk"); | |
| 102 | 66 | const float* bvp = bias_ptr(bv, "bv"); | |
| 103 | 66 | const float* bop = bias_ptr(bo, "bo"); | |
| 104 | |||
| 105 |
4/6✓ Branch 0 taken 40 times.
✓ Branch 1 taken 26 times.
✓ Branch 2 taken 40 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 40 times.
|
66 | if (O.rows != L || O.cols != D || O.dtype != Dtype::FP32) |
| 106 | 26 | O.resize(L, D, Dtype::FP32); | |
| 107 |
2/4✓ Branch 0 taken 66 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 66 times.
|
66 | if (L == 0 || D == 0) return; |
| 108 | |||
| 109 | 66 | const float* Xp = X.host_f32(); | |
| 110 | 66 | const float* Wqp = Wq.host_f32(); | |
| 111 | 66 | const float* Wkp = Wk.host_f32(); | |
| 112 | 66 | const float* Wvp = Wv.host_f32(); | |
| 113 | 66 | const float* Wop = Wo.host_f32(); | |
| 114 | 66 | const float* Rhp = rel_pos_h.host_f32(); | |
| 115 | 66 | const float* Rwp = rel_pos_w.host_f32(); | |
| 116 | 66 | float* Op = O.host_f32_mut(); | |
| 117 | |||
| 118 | // Per-head projections: Qh / Kh / Vh laid out (H*L, dh), each plus its bias. | ||
| 119 | 66 | std::vector<float> Qh(static_cast<size_t>(H) * L * dh); | |
| 120 |
1/2✓ Branch 0 taken 66 times.
✗ Branch 1 not taken.
|
66 | std::vector<float> Kh(static_cast<size_t>(H) * L * dh); |
| 121 |
1/2✓ Branch 0 taken 66 times.
✗ Branch 1 not taken.
|
66 | std::vector<float> Vh(static_cast<size_t>(H) * L * dh); |
| 122 | 264 | auto project = [&](const float* W, const float* b, std::vector<float>& Out) { | |
| 123 |
2/2✓ Branch 0 taken 759 times.
✓ Branch 1 taken 198 times.
|
957 | for (int hh = 0; hh < H; ++hh) { |
| 124 |
2/2✓ Branch 0 taken 16329 times.
✓ Branch 1 taken 759 times.
|
17088 | for (int i = 0; i < L; ++i) { |
| 125 | 16329 | const float* xr = Xp + static_cast<size_t>(i) * D; | |
| 126 |
2/2✓ Branch 0 taken 130632 times.
✓ Branch 1 taken 16329 times.
|
146961 | for (int j = 0; j < dh; ++j) { |
| 127 | 130632 | const int o = hh * dh + j; | |
| 128 | 130632 | const float* wr = W + static_cast<size_t>(o) * D; | |
| 129 |
2/2✓ Branch 0 taken 110928 times.
✓ Branch 1 taken 19704 times.
|
130632 | float acc = b ? b[o] : 0.0f; |
| 130 |
2/2✓ Branch 0 taken 5579712 times.
✓ Branch 1 taken 130632 times.
|
5710344 | for (int k = 0; k < D; ++k) acc += xr[k] * wr[k]; |
| 131 | 130632 | Out[(static_cast<size_t>(hh) * L + i) * dh + j] = acc; | |
| 132 | 130632 | } | |
| 133 | 16329 | } | |
| 134 | 759 | } | |
| 135 | 198 | }; | |
| 136 |
1/2✓ Branch 0 taken 66 times.
✗ Branch 1 not taken.
|
66 | project(Wqp, bqp, Qh); |
| 137 |
1/2✓ Branch 0 taken 66 times.
✗ Branch 1 not taken.
|
66 | project(Wkp, bkp, Kh); |
| 138 |
1/2✓ Branch 0 taken 66 times.
✗ Branch 1 not taken.
|
66 | project(Wvp, bvp, Vh); |
| 139 | |||
| 140 |
1/2✓ Branch 0 taken 66 times.
✗ Branch 1 not taken.
|
66 | std::vector<float> Yc(static_cast<size_t>(L) * D, 0.0f); |
| 141 |
1/2✓ Branch 0 taken 66 times.
✗ Branch 1 not taken.
|
66 | std::vector<float> srow(L); |
| 142 |
1/2✓ Branch 0 taken 66 times.
✗ Branch 1 not taken.
|
66 | std::vector<float> relh(grid_h); // per-key-row rel-h bias for current query |
| 143 |
1/2✓ Branch 0 taken 66 times.
✗ Branch 1 not taken.
|
66 | std::vector<float> relw(grid_w); // per-key-col rel-w bias for current query |
| 144 | |||
| 145 |
2/2✓ Branch 0 taken 253 times.
✓ Branch 1 taken 66 times.
|
319 | for (int hh = 0; hh < H; ++hh) { |
| 146 |
2/2✓ Branch 0 taken 5443 times.
✓ Branch 1 taken 253 times.
|
5696 | for (int i = 0; i < L; ++i) { |
| 147 | 5443 | const float* qr = &Qh[(static_cast<size_t>(hh) * L + i) * dh]; | |
| 148 | 5443 | const int qh = i / grid_w; | |
| 149 | 5443 | const int qw = i % grid_w; | |
| 150 | |||
| 151 | // Decompose the relative-position bias: relh[kh] = q . rel_pos_h[qh-kh+gh-1], | ||
| 152 | // relw[kw] = q . rel_pos_w[qw-kw+gw-1]. bias(kh,kw) = relh[kh] + relw[kw]. | ||
| 153 |
2/2✓ Branch 0 taken 29883 times.
✓ Branch 1 taken 5443 times.
|
35326 | for (int kh = 0; kh < grid_h; ++kh) { |
| 154 | 29883 | const float* rr = Rhp + static_cast<size_t>(qh - kh + grid_h - 1) * dh; | |
| 155 | 29883 | float acc = 0.0f; | |
| 156 |
2/2✓ Branch 0 taken 239064 times.
✓ Branch 1 taken 29883 times.
|
268947 | for (int c = 0; c < dh; ++c) acc += qr[c] * rr[c]; |
| 157 | 29883 | relh[kh] = acc; | |
| 158 | 29883 | } | |
| 159 |
2/2✓ Branch 0 taken 30549 times.
✓ Branch 1 taken 5443 times.
|
35992 | for (int kw = 0; kw < grid_w; ++kw) { |
| 160 | 30549 | const float* rr = Rwp + static_cast<size_t>(qw - kw + grid_w - 1) * dh; | |
| 161 | 30549 | float acc = 0.0f; | |
| 162 |
2/2✓ Branch 0 taken 244392 times.
✓ Branch 1 taken 30549 times.
|
274941 | for (int c = 0; c < dh; ++c) acc += qr[c] * rr[c]; |
| 163 | 30549 | relw[kw] = acc; | |
| 164 | 30549 | } | |
| 165 | |||
| 166 | // scores = scale * Q.K + decomposed rel-pos bias | ||
| 167 | 5443 | float row_max = -1e30f; | |
| 168 |
2/2✓ Branch 0 taken 188749 times.
✓ Branch 1 taken 5443 times.
|
194192 | for (int j = 0; j < L; ++j) { |
| 169 | 188749 | const float* kr = &Kh[(static_cast<size_t>(hh) * L + j) * dh]; | |
| 170 | 188749 | float s = 0.0f; | |
| 171 |
2/2✓ Branch 0 taken 1509992 times.
✓ Branch 1 taken 188749 times.
|
1698741 | for (int c = 0; c < dh; ++c) s += qr[c] * kr[c]; |
| 172 | 188749 | s = s * scale + relh[j / grid_w] + relw[j % grid_w]; | |
| 173 | 188749 | srow[j] = s; | |
| 174 |
2/2✓ Branch 0 taken 165953 times.
✓ Branch 1 taken 22796 times.
|
188749 | if (s > row_max) row_max = s; |
| 175 | 188749 | } | |
| 176 | // softmax | ||
| 177 | 5443 | float sum = 0.0f; | |
| 178 |
2/2✓ Branch 0 taken 188749 times.
✓ Branch 1 taken 5443 times.
|
194192 | for (int j = 0; j < L; ++j) { |
| 179 | 188749 | const float e = std::exp(srow[j] - row_max); | |
| 180 | 188749 | srow[j] = e; | |
| 181 | 188749 | sum += e; | |
| 182 | 188749 | } | |
| 183 |
1/2✓ Branch 0 taken 5443 times.
✗ Branch 1 not taken.
|
5443 | const float inv = sum > 0.0f ? 1.0f / sum : 0.0f; |
| 184 | // weighted sum of V | ||
| 185 |
2/2✓ Branch 0 taken 43544 times.
✓ Branch 1 taken 5443 times.
|
48987 | for (int c = 0; c < dh; ++c) { |
| 186 | 43544 | float acc = 0.0f; | |
| 187 |
2/2✓ Branch 0 taken 1509992 times.
✓ Branch 1 taken 43544 times.
|
1553536 | for (int j = 0; j < L; ++j) |
| 188 | 3019984 | acc += srow[j] * inv * | |
| 189 | 1509992 | Vh[(static_cast<size_t>(hh) * L + j) * dh + c]; | |
| 190 | 43544 | Yc[static_cast<size_t>(i) * D + (hh * dh + c)] = acc; | |
| 191 | 43544 | } | |
| 192 | 5443 | } | |
| 193 | 253 | } | |
| 194 | |||
| 195 | // Output projection O = Yconcat @ Wo^T (+ bo). | ||
| 196 |
2/2✓ Branch 0 taken 1191 times.
✓ Branch 1 taken 66 times.
|
1257 | for (int i = 0; i < L; ++i) { |
| 197 | 1191 | const float* yr = &Yc[static_cast<size_t>(i) * D]; | |
| 198 |
2/2✓ Branch 0 taken 43544 times.
✓ Branch 1 taken 1191 times.
|
44735 | for (int c = 0; c < D; ++c) { |
| 199 | 43544 | const float* wr = Wop + static_cast<size_t>(c) * D; | |
| 200 |
2/2✓ Branch 0 taken 36976 times.
✓ Branch 1 taken 6568 times.
|
43544 | float acc = bop ? bop[c] : 0.0f; |
| 201 |
2/2✓ Branch 0 taken 1859904 times.
✓ Branch 1 taken 43544 times.
|
1903448 | for (int k = 0; k < D; ++k) acc += yr[k] * wr[k]; |
| 202 | 43544 | Op[static_cast<size_t>(i) * D + c] = acc; | |
| 203 | 43544 | } | |
| 204 | 1191 | } | |
| 205 | 66 | } | |
| 206 | |||
| 207 | // Windowed variant: split the (grid_h, grid_w) token grid into window x window | ||
| 208 | // tiles (zero-padding the bottom/right up to a multiple of window, SAM's | ||
| 209 | // window_partition), run the decomposed-rel-pos attention above independently | ||
| 210 | // per tile with the SAME weights / rel-pos, then crop the padding back off. | ||
| 211 | // Reuses the single-grid kernel above — the only new work is the gather/scatter | ||
| 212 | // across windows. | ||
| 213 | 10 | void self_attention_decomposed_rel_pos_windowed_forward( | |
| 214 | const ::brotensor::Tensor& X, | ||
| 215 | const ::brotensor::Tensor& Wq, const ::brotensor::Tensor* bq, | ||
| 216 | const ::brotensor::Tensor& Wk, const ::brotensor::Tensor* bk, | ||
| 217 | const ::brotensor::Tensor& Wv, const ::brotensor::Tensor* bv, | ||
| 218 | const ::brotensor::Tensor& Wo, const ::brotensor::Tensor* bo, | ||
| 219 | const ::brotensor::Tensor& rel_pos_h, | ||
| 220 | const ::brotensor::Tensor& rel_pos_w, | ||
| 221 | int num_heads, int grid_h, int grid_w, int window, float scale, | ||
| 222 | ::brotensor::Tensor& O) { | ||
| 223 | using ::brotensor::Tensor; | ||
| 224 | 10 | const char* fn = "self_attention_decomposed_rel_pos_windowed_forward"; | |
| 225 | 10 | check_fp32(X, "X"); | |
| 226 | 10 | const int L = X.rows; | |
| 227 | 10 | const int D = X.cols; | |
| 228 |
1/2✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
|
10 | if (window <= 0) |
| 229 | ✗ | throw std::runtime_error(std::string(fn) + ": window must be >= 1"); | |
| 230 |
1/2✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
|
10 | if (grid_h <= 0 || grid_w <= 0 || grid_h * grid_w != L) |
| 231 | ✗ | throw std::runtime_error(std::string(fn) + ": grid_h*grid_w must equal X.rows"); | |
| 232 | |||
| 233 |
1/6✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
|
10 | if (O.rows != L || O.cols != D || O.dtype != Dtype::FP32) |
| 234 | 10 | O.resize(L, D, Dtype::FP32); | |
| 235 |
2/4✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 10 times.
|
10 | if (L == 0 || D == 0) return; |
| 236 | |||
| 237 | 10 | const int pad_h = (window - grid_h % window) % window; | |
| 238 | 10 | const int pad_w = (window - grid_w % window) % window; | |
| 239 | 10 | const int nw_h = (grid_h + pad_h) / window; | |
| 240 | 10 | const int nw_w = (grid_w + pad_w) / window; | |
| 241 | 10 | const int ww = window * window; | |
| 242 | |||
| 243 | 10 | const float* Xp = X.host_f32(); | |
| 244 | 10 | float* Op = O.host_f32_mut(); | |
| 245 | |||
| 246 | 10 | Tensor win_in = Tensor::mat(ww, D); // host FP32, zeroed | |
| 247 | 10 | Tensor win_out; | |
| 248 |
2/2✓ Branch 0 taken 22 times.
✓ Branch 1 taken 10 times.
|
32 | for (int nh = 0; nh < nw_h; ++nh) { |
| 249 |
2/2✓ Branch 0 taken 50 times.
✓ Branch 1 taken 22 times.
|
72 | for (int nw = 0; nw < nw_w; ++nw) { |
| 250 | // Gather this window's tokens (token-major), zeroing padded cells. | ||
| 251 |
1/2✓ Branch 0 taken 50 times.
✗ Branch 1 not taken.
|
50 | float* wi = win_in.host_f32_mut(); |
| 252 |
2/2✓ Branch 0 taken 188 times.
✓ Branch 1 taken 50 times.
|
238 | for (int lh = 0; lh < window; ++lh) { |
| 253 | 188 | const int h = nh * window + lh; | |
| 254 |
2/2✓ Branch 0 taken 716 times.
✓ Branch 1 taken 188 times.
|
904 | for (int lw = 0; lw < window; ++lw) { |
| 255 | 716 | const int w = nw * window + lw; | |
| 256 | 716 | float* dst = wi + static_cast<size_t>(lh * window + lw) * D; | |
| 257 |
4/4✓ Branch 0 taken 620 times.
✓ Branch 1 taken 96 times.
✓ Branch 2 taken 74 times.
✓ Branch 3 taken 546 times.
|
716 | if (h < grid_h && w < grid_w) { |
| 258 | 546 | const float* src = | |
| 259 | 546 | Xp + static_cast<size_t>(h * grid_w + w) * D; | |
| 260 |
2/2✓ Branch 0 taken 16400 times.
✓ Branch 1 taken 546 times.
|
16946 | for (int c = 0; c < D; ++c) dst[c] = src[c]; |
| 261 | 546 | } else { | |
| 262 |
2/2✓ Branch 0 taken 5136 times.
✓ Branch 1 taken 170 times.
|
5306 | for (int c = 0; c < D; ++c) dst[c] = 0.0f; |
| 263 | } | ||
| 264 | 716 | } | |
| 265 | 188 | } | |
| 266 | |||
| 267 |
1/2✓ Branch 0 taken 50 times.
✗ Branch 1 not taken.
|
50 | self_attention_decomposed_rel_pos_forward( |
| 268 | 50 | win_in, Wq, bq, Wk, bk, Wv, bv, Wo, bo, rel_pos_h, rel_pos_w, | |
| 269 | 50 | num_heads, window, window, scale, win_out); | |
| 270 | |||
| 271 | // Scatter back, dropping the padded tokens. | ||
| 272 |
1/2✓ Branch 0 taken 50 times.
✗ Branch 1 not taken.
|
50 | const float* wo = win_out.host_f32(); |
| 273 |
2/2✓ Branch 0 taken 188 times.
✓ Branch 1 taken 50 times.
|
238 | for (int lh = 0; lh < window; ++lh) { |
| 274 | 188 | const int h = nh * window + lh; | |
| 275 |
2/2✓ Branch 0 taken 26 times.
✓ Branch 1 taken 162 times.
|
188 | if (h >= grid_h) continue; |
| 276 |
2/2✓ Branch 0 taken 620 times.
✓ Branch 1 taken 162 times.
|
782 | for (int lw = 0; lw < window; ++lw) { |
| 277 | 620 | const int w = nw * window + lw; | |
| 278 |
2/2✓ Branch 0 taken 74 times.
✓ Branch 1 taken 546 times.
|
620 | if (w >= grid_w) continue; |
| 279 | 546 | const float* src = | |
| 280 | 546 | wo + static_cast<size_t>(lh * window + lw) * D; | |
| 281 | 546 | float* dst = Op + static_cast<size_t>(h * grid_w + w) * D; | |
| 282 |
2/2✓ Branch 0 taken 16400 times.
✓ Branch 1 taken 546 times.
|
16946 | for (int c = 0; c < D; ++c) dst[c] = src[c]; |
| 283 | 546 | } | |
| 284 | 162 | } | |
| 285 | 50 | } | |
| 286 | 22 | } | |
| 287 | 10 | } | |
| 288 | |||
| 289 | } // namespace brotensor::detail::cpu | ||
| 290 |