QK D-loop
Loop over Q/K head-dim slices.
Q [BQ, BD] × Kᵀ [BD, BK] → S [BQ, BK]
Logical attention view is Q × Kᵀ. The source comment says S = K * Q because of the MMA operand/layout convention. Repeated over all D tiles; each partial MMA accumulates into tSrS.
/* GEMM 1: S = K * Q */
clear(tSrS);
for (int D = 0; D < size<4>(tKgK); D++) {
copy(copy_q, tQgQ(_, _, _, D), tQrQ);
copy(copy_k_cur, tKgK_cur(_, _, _, k_idx, D), tKrK);
reorder(tQrQ, tSrQ);
reorder(tKrK, tSrK);
cute::gemm(mma_qk, tSrQ, tSrK, tSrS);
}
V prefetch VV-loop
Before PV GEMM, prefetch V splits for the current K block.
V split storage [BV, BK]
Math view is effectively [BK, BV] after MMA-layout reorder.
for (int VV = 0; VV < VTiles; VV++) {
prefetch(prefetch_v_cur,
pVgV_cur(_, _, _, VV, k_idx));
}
Mask
Elementwise update of score tile.
S [BQ, BK] → S [BQ, BK]
Depending on compile-time flags and boundary conditions, causal, full, or remainder masking changes score values but not the logical tile shape.
if constexpr (!is_cache && CausalMask) {
...
tSrS(i) = ElementS(-INFINITY);
}
if (check_remainder_k && K == total_blk - 1) {
...
tSrS(i) = sycl::fmin(tSrS(i), ...);
}
Online softmax
Reduce over K columns, then broadcast row state back to the score tile.
S [BQ, BK] → row_max [BQ] + row_sum [BQ] → P [BQ, BK]
softmax() returns rescale [BQ]; then tSrS is reordered into the PV A-operand layout tArP.
auto rescale = softmax(K == blk_k0,
tSrS, tA_max, tA_sum);
reorder(tSrS, tArP);
// inside softmax()
auto tS_bmax = reduce<1>(tS, sycl::maximum{});
tS(i) = sycl::native::exp2(params.scale * tS(i) - ...);
auto tS_bsum = reduce<1>(tS, sycl::plus{});
tS_sum(i) += tS_bsum(i);
PV VV-loop
Loop over V/output splits.
P [BQ, BK] × V [BK, BV] → O_split [BQ, BV]
If this is not the first K block, old O is row-rescaled by rescale [BQ] before adding the current PV result.
for (int VV = 0; VV < VTiles; VV++) {
copy(copy_v_cur, tVgV_cur(_, _, _, VV, k_idx), tVrV);
reorder(tVrV, tArV);
if (K != blk_k0)
tArA(_, _, _, VV)(i) *= broadcast<0>(rescale, ...);
cute::gemm(mma_pv, tArP, tArV, tArA(_, _, _, VV));
}
Future-K prefetch D-loop
After processing current K, request the future K block.
K_next = K + Stages → all [BK, BD] slices
This keeps the K pipeline ahead of compute.
int K_next = K + Stages;
for (int D = 0; D < size<4>(pKgK); D++) {
prefetch(prefetch_k,
pKgK(_, _, _, K_next - kblocks_cache, D));
}