GCC Code Coverage Report


Directory: ./
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 100.0% 9 / 0 / 9
Functions: 100.0% 2 / 0 / 2
Branches: -% 0 / 0 / 0

include/brotensor/ops/attention.h
Line Branch Exec Source
1 #pragma once
2
3 // brotensor ops/attention.h — Attention (non-flash): single-head, MHA, self/cross, bias, decomposed rel-pos, masks.
4
5 #include "../tensor.h"
6 #include <cstdint>
7
8 namespace brotensor {
9
10
11 // Build a slot-validity mask on-device. For k in [0, K):
12 // mask[k] = (x[offset + k*stride] > 0.5f) ? 1.0f : 0.0f
13 // mask resized to (K, 1).
14 void build_slot_mask(const Tensor& x, int offset, int K, int stride,
15 Tensor& mask);
16
17
18 // Single-head scaled dot-product self-attention. Square (D,D) projections,
19 // no biases.
20 // X: (N,D). Wq, Wk, Wv, Wo: each (D,D).
21 // d_mask: optional length-N device mask (1 valid / 0 invalid); may be null.
22 // Invalid keys are excluded from the softmax denominator; invalid
23 // query rows produce zero output.
24 // O: (N,D) output, resized if mis-shaped.
25 // Backward caches (out-params): Q, K, V each (N,D); Attn (N,N) post-softmax
26 // weights; Y_pre_Wo (N,D) = Attn @ V before the output projection.
27 void attention_forward(const Tensor& X,
28 const Tensor& Wq, const Tensor& Wk,
29 const Tensor& Wv, const Tensor& Wo,
30 const float* d_mask,
31 Tensor& Q, Tensor& K, Tensor& V,
32 Tensor& Attn, Tensor& Y_pre_Wo,
33 Tensor& O);
34
35
36 // Backward of attention_forward.
37 // dO: (N,D) upstream. X, Q, K, V, Attn, Y_pre_Wo: forward caches.
38 // Wq, Wk, Wv, Wo: (D,D) forward weights. d_mask: as forward (or null).
39 // dX: (N,D) overwritten. dWq, dWk, dWv, dWo: (D,D) accumulated — caller zeros.
40 void attention_backward(const Tensor& dO,
41 const Tensor& X,
42 const Tensor& Q, const Tensor& K,
43 const Tensor& V, const Tensor& Attn,
44 const Tensor& Y_pre_Wo,
45 const Tensor& Wq, const Tensor& Wk,
46 const Tensor& Wv, const Tensor& Wo,
47 const float* d_mask,
48 Tensor& dX,
49 Tensor& dWq, Tensor& dWk,
50 Tensor& dWv, Tensor& dWo);
51
52
53 // ─── Multi-head self-attention ─────────────────────────────────────────────
54
55 // Multi-head scaled dot-product self-attention. Square (D,D) projections,
56 // split into num_heads heads of head_dim = D / num_heads; num_heads must
57 // divide D.
58 // X: (K,D). Wq, Wk, Wv, Wo: each (D,D).
59 // bq, bk, bv, bo: optional length-D bias vectors (any shape with D
60 // elements, FP32). Added row-wise after the matching
61 // projection: Q/K/V get bq/bk/bv post-projection, O gets
62 // bo post-Wo. Any of the four may be null to skip that
63 // bias term.
64 // d_mask: optional length-K device mask (1 valid / 0 invalid); may be null.
65 // Same semantics as single-head attention.
66 // O: (K,D) output, resized if mis-shaped.
67 // Backward caches (out-params, resized if mis-shaped): Qh, Kh, Vh
68 // (num_heads*K, head_dim) with head h in rows [h*K, (h+1)*K); Attnh
69 // (num_heads*K, K) per-head softmax weights; Yconcat (K,D) pre-Wo concat
70 // (does NOT include bo — bo is folded into O directly).
71 void mha_forward(const Tensor& X,
72 const Tensor& Wq, const Tensor& Wk,
73 const Tensor& Wv, const Tensor& Wo,
74 const Tensor* bq, const Tensor* bk,
75 const Tensor* bv, const Tensor* bo,
76 const float* d_mask,
77 int num_heads,
78 Tensor& Qh, Tensor& Kh, Tensor& Vh,
79 Tensor& Attnh, Tensor& Yconcat,
80 Tensor& O);
81
82
83 // Bias-less convenience overload — forwards to the bias-aware mha_forward
84 // with bq/bk/bv/bo == nullptr. Preserves the original call shape so existing
85 // callers don't change.
86 16 inline void mha_forward(const Tensor& X,
87 const Tensor& Wq, const Tensor& Wk,
88 const Tensor& Wv, const Tensor& Wo,
89 const float* d_mask,
90 int num_heads,
91 Tensor& Qh, Tensor& Kh, Tensor& Vh,
92 Tensor& Attnh, Tensor& Yconcat,
93 Tensor& O) {
94 32 mha_forward(X, Wq, Wk, Wv, Wo,
95 nullptr, nullptr, nullptr, nullptr,
96 16 d_mask, num_heads,
97 16 Qh, Kh, Vh, Attnh, Yconcat, O);
98 16 }
99
100
101 // Backward of mha_forward.
102 // dO: (K,D) upstream. X, Qh, Kh, Vh, Attnh, Yconcat: forward caches.
103 // Wq, Wk, Wv, Wo: (D,D) forward weights. d_mask: as forward (or null).
104 // num_heads must match forward.
105 // dX: (K,D) overwritten. dWq, dWk, dWv, dWo: (D,D) accumulated — caller zeros.
106 // dbq, dbk, dbv, dbo: optional length-D bias gradients, accumulated
107 // (caller zeros). Pass null to skip — must match the
108 // null/non-null pattern of the forward biases.
109 void mha_backward(const Tensor& dO,
110 const Tensor& X,
111 const Tensor& Qh, const Tensor& Kh,
112 const Tensor& Vh, const Tensor& Attnh,
113 const Tensor& Yconcat,
114 const Tensor& Wq, const Tensor& Wk,
115 const Tensor& Wv, const Tensor& Wo,
116 const float* d_mask,
117 int num_heads,
118 Tensor& dX,
119 Tensor& dWq, Tensor& dWk,
120 Tensor& dWv, Tensor& dWo,
121 Tensor* dbq = nullptr, Tensor* dbk = nullptr,
122 Tensor* dbv = nullptr, Tensor* dbo = nullptr);
123
124
125 // ─── Cross / self attention + masks ────────────────────────────────────────
126
127 // Causal mask helper: fills the length-L FP32 buffer for query row q,
128 // mask[k] = (k <= q) ? 1.0f : 0.0f
129 // resized to (L,1) if mis-shaped. The attention kernels consume a per-row
130 // length-Lk mask; for fully causal self-attention launch attention per query.
131 void build_causal_mask_row(int L, int q, Tensor& mask);
132
133
134 // Cross-attention: like mha_forward but K and V are projected from a separate
135 // context tensor. Dispatched on X.dtype:
136 // FP16 — flash-attention inference path; caches not exposed (use
137 // cross_attention_forward_train if you need them).
138 // FP32 — training-aware path (allocates scratch caches internally).
139 // X: (Lq,D) query input.
140 // Ctx: (Lk,D_ctx) key/value input; Lk, D_ctx may differ from Lq, D.
141 // Ctx.dtype must match X.dtype.
142 // Wq, Wo: (D,D). Wk, Wv: (D,D_ctx) (rectangular for cross-attention).
143 // d_mask: optional length-Lk FP32 mask (1 valid / 0 invalid); may be null.
144 // num_heads divides D.
145 // O: (Lq,D) output, same dtype as X, resized if mis-shaped.
146 void cross_attention_forward(const Tensor& X,
147 const Tensor& Ctx,
148 const Tensor& Wq, const Tensor& Wk,
149 const Tensor& Wv, const Tensor& Wo,
150 const float* d_mask,
151 int num_heads,
152 Tensor& O);
153
154
155 // Cross-attention with a head-averaged attention map and an optional
156 // pre-softmax logit bias. FP16 only, FP32 accumulation, no backward. Same math
157 // as cross_attention_forward, plus:
158 // * if attn_logit_bias is non-null it is added to the scaled QK^T scores
159 // before softmax, broadcast across heads;
160 // * AttnAvg receives the across-head average of the softmax weights.
161 // X: (Lq,D); Ctx: (Lk,D_ctx); Wq, Wo: (D,D); Wk, Wv: (D,D_ctx) — all FP16.
162 // d_mask: optional length-Lk FP32 mask (1 valid / 0 invalid); may be null.
163 // attn_logit_bias: optional (Lq,Lk) FP32 pre-softmax bias; may be null.
164 // num_heads divides D.
165 // O: (Lq,D) FP16; AttnAvg: (Lq,Lk) FP16 — both resized + dtype-set if needed.
166 void cross_attention_forward_with_attn(const Tensor& X,
167 const Tensor& Ctx,
168 const Tensor& Wq, const Tensor& Wk,
169 const Tensor& Wv, const Tensor& Wo,
170 const float* d_mask,
171 const Tensor* attn_logit_bias,
172 int num_heads,
173 Tensor& O,
174 Tensor& AttnAvg);
175
176
177 // FP32 training-side self-attention forward — thin wrapper over mha_forward
178 // (the mha case with Ctx == X). All tensors FP32.
179 // X, O: (L,D). Wq, Wk, Wv, Wo: (D,D).
180 // d_mask: optional length-L FP32 mask; may be null. num_heads divides D.
181 // Caches (resized if mis-shaped): Qh, Kh, Vh (num_heads*L, D/num_heads);
182 // Attnh (num_heads*L, L); Yconcat (L,D).
183 void self_attention_forward_train(const Tensor& X,
184 const Tensor& Wq, const Tensor& Wk,
185 const Tensor& Wv, const Tensor& Wo,
186 const float* d_mask,
187 int num_heads,
188 Tensor& Qh, Tensor& Kh, Tensor& Vh,
189 Tensor& Attnh, Tensor& Yconcat,
190 Tensor& O);
191
192
193 // FP32 training-side self-attention backward — thin wrapper over mha_backward.
194 // All tensors FP32.
195 // dO: (L,D) upstream. X, Qh, Kh, Vh, Attnh, Yconcat: forward caches.
196 // Wq, Wk, Wv, Wo: (D,D) forward weights. d_mask: as forward (or null).
197 // num_heads must match forward.
198 // dX: (L,D) overwritten. dWq, dWk, dWv, dWo: (D,D) accumulated — caller zeros.
199 void self_attention_backward(const Tensor& dO,
200 const Tensor& X,
201 const Tensor& Qh, const Tensor& Kh,
202 const Tensor& Vh, const Tensor& Attnh,
203 const Tensor& Yconcat,
204 const Tensor& Wq, const Tensor& Wk,
205 const Tensor& Wv, const Tensor& Wo,
206 const float* d_mask,
207 int num_heads,
208 Tensor& dX,
209 Tensor& dWq, Tensor& dWk,
210 Tensor& dWv, Tensor& dWo);
211
212
213 // Per-text-token spatial moments of a cross-attention map. Given Attn (Lq,Lk)
214 // with Lq = h_lat*w_lat a flattened row-major image-token grid (q = y*w_lat+x),
215 // for each text token k:
216 // mass[k] = sum_q Attn[q,k]
217 // centroid[k,0] = sum_q y(q)*Attn[q,k] / max(mass[k], 1e-8) (y)
218 // centroid[k,1] = sum_q x(q)*Attn[q,k] / max(mass[k], 1e-8) (x)
219 // (centroid set to (0,0) when mass[k] is ~ 0).
220 // Attn: (Lq,Lk) FP16. mass: (Lk,1) FP32. centroid: (Lk,2) FP32 [y,x].
221 // mass and centroid resized if mis-shaped. FP32 reductions over FP16 input.
222 void attention_token_moments(const Tensor& Attn,
223 int h_lat, int w_lat,
224 Tensor& mass,
225 Tensor& centroid);
226
227
228 // FP32 training-side cross-attention forward. mha_forward math with a separate
229 // Ctx for K/V and rectangular Wk/Wv. All tensors FP32.
230 // X: (Lq,D); Ctx: (Lk,D_ctx); Wq, Wo: (D,D); Wk, Wv: (D,D_ctx).
231 // d_mask: optional length-Lk FP32 mask; may be null. num_heads divides D.
232 // Caches (resized if mis-shaped): Qh (num_heads*Lq, D/num_heads);
233 // Kh, Vh (num_heads*Lk, D/num_heads); Attnh (num_heads*Lq, Lk);
234 // Yconcat (Lq,D). O: (Lq,D), resized if mis-shaped.
235 void cross_attention_forward_train(const Tensor& X,
236 const Tensor& Ctx,
237 const Tensor& Wq, const Tensor& Wk,
238 const Tensor& Wv, const Tensor& Wo,
239 const float* d_mask,
240 int num_heads,
241 Tensor& Qh, Tensor& Kh, Tensor& Vh,
242 Tensor& Attnh, Tensor& Yconcat,
243 Tensor& O);
244
245
246 // FP32 training-side cross-attention backward.
247 // dO: (Lq,D) upstream. X, Ctx, Qh, Kh, Vh, Attnh, Yconcat: forward caches.
248 // Wq, Wo: (D,D); Wk, Wv: (D,D_ctx). d_mask: as forward (or null).
249 // num_heads must match forward.
250 // dX: (Lq,D), dCtx: (Lk,D_ctx) — overwritten.
251 // dWq, dWo: (D,D); dWk, dWv: (D,D_ctx) — accumulated, caller zeros.
252 void cross_attention_backward(const Tensor& dO,
253 const Tensor& X,
254 const Tensor& Ctx,
255 const Tensor& Qh, const Tensor& Kh,
256 const Tensor& Vh, const Tensor& Attnh,
257 const Tensor& Yconcat,
258 const Tensor& Wq, const Tensor& Wk,
259 const Tensor& Wv, const Tensor& Wo,
260 const float* d_mask,
261 int num_heads,
262 Tensor& dX,
263 Tensor& dCtx,
264 Tensor& dWq, Tensor& dWk,
265 Tensor& dWv, Tensor& dWo);
266
267
268 // FP16 self-attention — thin wrapper over the cross-attention kernel with
269 // Ctx = X. Conventions as cross_attention_forward (FP16 path).
270 // X, O: (L,D) FP16. Wq, Wk, Wv, Wo: (D,D) FP16.
271 // d_mask: optional length-L FP32 mask; may be null. num_heads divides D.
272 // O resized if mis-shaped.
273 void self_attention_forward(const Tensor& X,
274 const Tensor& Wq, const Tensor& Wk,
275 const Tensor& Wv, const Tensor& Wo,
276 const float* d_mask,
277 int num_heads,
278 Tensor& O);
279
280
281 // Multi-head self-attention with an optional additive pre-softmax bias. Per
282 // head h:
283 // S[q,k] = scale*(Q_h[q].K_h[k]) + attn_bias[h*L+q, k]
284 // O = (softmax_k S) @ V_h, concatenated over heads, projected by Wo.
285 // The additive bias is the primitive behind T5 relative-position bias and
286 // ALiBi. `scale` multiplies the raw dot product BEFORE the bias: pass
287 // 1/sqrt(head_dim) for standard attention, or 1.0 for T5.
288 // X, O: (L,D). Wq, Wk, Wv, Wo: (D,D), same dtype as X.
289 // bq, bk, bv, bo: optional length-D projection biases (X.dtype), added
290 // row-wise after the matching projection — bq/bk/bv to Q/K/V,
291 // bo after Wo. Any may be null. This makes the op a full
292 // biased MHA + additive-bias attention (the Swin window-attn
293 // block: qkv/proj bias + relative-position bias as attn_bias).
294 // d_mask: optional length-L FP32 key mask (also gates padded query rows);
295 // may be null.
296 // attn_bias: optional (num_heads*L, L) FP32 — row h*L+q is head h query q's
297 // length-L bias. Null => plain scaled self-attention.
298 // num_heads divides D. O resized + dtype-set to match X.
299 // Dispatched on X.dtype (FP32/FP16/BF16); FP32 math; attn_bias is FP32 on every
300 // backend. Scores are materialised (L,L) per head — for encoder-length seqs.
301 void self_attention_bias_forward(const Tensor& X,
302 const Tensor& Wq, const Tensor& Wk,
303 const Tensor& Wv, const Tensor& Wo,
304 const Tensor* bq, const Tensor* bk,
305 const Tensor* bv, const Tensor* bo,
306 const float* d_mask,
307 const Tensor* attn_bias,
308 int num_heads, float scale,
309 Tensor& O);
310
311 // Bias-less-projection convenience overload — preserves the original call shape
312 // (no qkv/proj biases) by forwarding bq/bk/bv/bo == nullptr.
313 33 inline void self_attention_bias_forward(const Tensor& X,
314 const Tensor& Wq, const Tensor& Wk,
315 const Tensor& Wv, const Tensor& Wo,
316 const float* d_mask,
317 const Tensor* attn_bias,
318 int num_heads, float scale,
319 Tensor& O) {
320 66 self_attention_bias_forward(X, Wq, Wk, Wv, Wo,
321 nullptr, nullptr, nullptr, nullptr,
322 33 d_mask, attn_bias, num_heads, scale, O);
323 33 }
324
325
326 // Multi-head self-attention with a DECOMPOSED 2D relative-position bias — the
327 // SAM / ViTDet image-encoder attention (segment_anything add_decomposed_rel_pos).
328 // A token index t maps to grid coords (t/grid_w, t%grid_w) over a grid_h*grid_w
329 // patch grid (so X.rows == grid_h*grid_w). Per head, with r_q the projected,
330 // UNSCALED query:
331 // bias[q,k] = r_q . rel_pos_h[(qh-kh)+grid_h-1] + r_q . rel_pos_w[(qw-kw)+grid_w-1]
332 // S[q,k] = scale*(Q[q].K[k]) + bias[q,k] (scale multiplies the dot only)
333 // O = concat_h( softmax_k S @ V_h ) @ Wo
334 // Unlike self_attention_bias_forward the bias is data-dependent (reads Q) and is
335 // never materialised as (num_heads*L, L) — it's factored into length-grid_h and
336 // length-grid_w terms. Windowed blocks call this per window (grid == window);
337 // global blocks call it once over the full grid.
338 // X, O: (L, D). Wq/Wk/Wv/Wo: (D, D). bq/bk/bv/bo: optional (D,1), null to skip.
339 // rel_pos_h: (2*grid_h-1, head_dim). rel_pos_w: (2*grid_w-1, head_dim).
340 // num_heads divides D. scale: typically 1/sqrt(head_dim).
341 // Dispatched on X.dtype; O resized + dtype-set to match X.
342 void self_attention_decomposed_rel_pos_forward(
343 const Tensor& X,
344 const Tensor& Wq, const Tensor* bq,
345 const Tensor& Wk, const Tensor* bk,
346 const Tensor& Wv, const Tensor* bv,
347 const Tensor& Wo, const Tensor* bo,
348 const Tensor& rel_pos_h, const Tensor& rel_pos_w,
349 int num_heads, int grid_h, int grid_w, float scale,
350 Tensor& O);
351
352
353 // Windowed multi-head self-attention with a decomposed 2D relative-position
354 // bias — the SAM/ViTDet *windowed* encoder block (segment_anything runs most
355 // blocks over non-overlapping local windows, a few globally). Splits the
356 // (grid_h, grid_w) token grid into window x window tiles and runs the
357 // decomposed-rel-pos attention above INDEPENDENTLY within each tile, sharing
358 // one set of weights and rel-pos tables. The bottom/right of the grid is
359 // zero-padded up to a multiple of `window` (SAM's window_partition pad) and the
360 // padding is cropped back off the output, so grid_h/grid_w need not be
361 // multiples of `window`. For a grid that is exactly one window this is the
362 // plain decomposed-rel-pos op.
363 // X, O: (grid_h*grid_w, D), token-major (row = h*grid_w + w).
364 // Wq/Wk/Wv/Wo: (D,D). bq/bk/bv/bo: optional (D,1), null to skip.
365 // rel_pos_h, rel_pos_w: (2*window-1, head_dim) — sized for the window, not
366 // the full grid.
367 // num_heads divides D. scale: typically 1/sqrt(head_dim).
368 // Dispatched on X.dtype; O resized + dtype-set to match X.
369 void self_attention_decomposed_rel_pos_windowed_forward(
370 const Tensor& X,
371 const Tensor& Wq, const Tensor* bq,
372 const Tensor& Wk, const Tensor* bk,
373 const Tensor& Wv, const Tensor* bv,
374 const Tensor& Wo, const Tensor* bo,
375 const Tensor& rel_pos_h, const Tensor& rel_pos_w,
376 int num_heads, int grid_h, int grid_w, int window, float scale,
377 Tensor& O);
378
379
380 // W8A16 variant of self_attention_bias_forward — quantised T5-bias attention.
381 // Identical math and semantics, but each projection weight is an INT8 (D,D)
382 // matrix paired with an FP32 (D,1) per-output-row dequant scale (the
383 // quantize_int8_per_row_host convention). Activations stay FP16; the attention
384 // core is FP32 internally. GPU-only.
385 // X, O: (L,D) FP16. Wq/Wk/Wv/Wo_int8: (D,D) INT8. sq/sk/sv/so: (D,1) FP32.
386 // d_mask: optional length-L FP32 key mask; may be null.
387 // attn_bias: optional (num_heads*L, L) FP32 bias; may be null.
388 // num_heads divides D. scale: as in self_attention_bias_forward.
389 // O resized as needed.
390 void self_attention_bias_int8w_fp16(const Tensor& X,
391 const Tensor& Wq_int8, const Tensor& sq,
392 const Tensor& Wk_int8, const Tensor& sk,
393 const Tensor& Wv_int8, const Tensor& sv,
394 const Tensor& Wo_int8, const Tensor& so,
395 const float* d_mask,
396 const Tensor* attn_bias,
397 int num_heads, float scale,
398 Tensor& O);
399
400 } // namespace brotensor
401