"""GQA fused-attention decode kernel — long context (ADR-0060). Each rank holds an ``S_local = S_kv / (C·P)`` slice of K, V and the full Q (replicated). The local attention is computed via an S_kv-axis tile sweep (ADR-0063 §A.2) so per-rank scratch is bounded by ``TILE_S_KV`` regardless of ``S_local``. The partial ``(m, ℓ, O)`` is then reduced via a 2-level pattern (intra-CUBE row+col, then inter-CUBE), and the root writes the final output. Inter-CUBE reduce (selected by the ``sub_w`` launch arg): - ``sub_w == 0`` (default): 1D chain along ``W``, root at CUBE 0. Used for ``C ∈ {1, 4}`` panels. - ``sub_w >= 2``: ADR-0060 §4.2 prescribed **lrab-adapted center-root mesh reduce** over a ``sub_w × sub_h`` sub-mesh (``sub_h = C // sub_w``). Phase 1 row reduce converges at ``root_col = sub_w//2``; Phase 2 col reduce on ``root_col`` converges at ``root_row = sub_h//2``. Root cube id: ``root_row * sub_w + root_col``. Used for the C=8 single-KV-group LLaMA-3.1-70B target (sub_w=4, sub_h=2, root=cube 6). Requires ``sub_w >= 2 and sub_h >= 2`` — degenerate 1×N / N×1 layouts must use the 1D-chain path with ``sub_w=0``. Topology / SFR: - Requires ``configure_sfr_intercube_multisip`` when ``P > 1`` or ``C > 1`` (provides disjoint ``intra_*`` and ``E/W/N/S`` namespaces). - Intra-CUBE PEs are arranged as a 2×4 grid (no wrap). - Inter-CUBE CUBEs: 1D row (``sub_w == 0``) or rectangular sub-mesh of the SIP's 4×4 CUBE mesh starting at origin (0, 0) with ``sub_w == mesh_w`` (``sub_w >= 2``). Layout caveats: - GEMMs use ``tl.dot`` (no composite epilogue / ``softmax_scale``). - K is loaded as ``(d_head, S_local)`` via byte-conserving reshape of the deployed ``(S_local, h_kv·d_head)`` slice — correct for zero / symmetric inputs (ADR-0060 §3 reshape-not-transpose caveat). """ from __future__ import annotations TILE_S_KV = 1024 # ADR-0063 §A.2 S_kv-axis tile sweep (per-tile width). def _merge_running(m_local, l_local, O_local, m_other, l_other, O_other, *, tl): """Online-softmax merge of two partial ``(m, ℓ, O)`` triples.""" m_new = tl.maximum(m_local, m_other) scale_old = tl.exp(m_local - m_new) scale_new = tl.exp(m_other - m_new) l_new = l_local * scale_old + l_other * scale_new O_new = O_local * scale_old + O_other * scale_new return m_new, l_new, O_new def gqa_attention_decode_long_kernel( q_ptr: int, k_ptr: int, v_ptr: int, o_ptr: int, T_q: int, S_kv: int, h_q: int, h_kv: int, d_head: int, C: int, P: int, sub_w: int = 0, *, tl, ) -> None: """GQA decode with M-fold + S_kv tile sweep + 2-level reduce-to-root. Tensor layout: Q : (T_q, h_q · d_head) replicated on every rank; loaded as (G·T_q, d_head) — byte-conserving and math-correct for T_q=1. K : (S_kv, h_kv · d_head) sharded row_wise by (cube, pe); each rank loads its (d_head, S_local) slice via byte-conserving reshape. V : (S_kv, h_kv · d_head) sharded row_wise by (cube, pe); each rank loads its (S_local, d_head) slice. O : (T_q, h_q · d_head) — only PE 0 of the root CUBE stores (CUBE 0 for ``sub_w=0``; geometric center cube for ``sub_w>=2``). """ if sub_w > 0: sub_h = C // sub_w if sub_w < 2 or sub_h < 2 or sub_w * sub_h != C: raise ValueError( f"sub_w={sub_w} requires sub_w>=2 and sub_h>=2 and " f"sub_w*sub_h==C; got sub_h={sub_h}, C={C}. " "Use sub_w=0 for the 1D-chain path." ) root_col = sub_w // 2 root_row = sub_h // 2 root_cube = root_row * sub_w + root_col else: sub_h = 0 root_col = 0 root_row = 0 root_cube = 0 G = h_q // h_kv n_ranks = C * P S_local = S_kv // n_ranks pe_id = tl.program_id(axis=0) cube_id = tl.program_id(axis=1) # ── Local attention (S_kv-axis tile sweep, ADR-0063 §A.2) ── Q = tl.load(q_ptr, shape=(G * T_q, d_head), dtype="f16") n_tiles = (S_local + TILE_S_KV - 1) // TILE_S_KV KV_ROW_BYTES = d_head * 2 # f16 # Tile 0: establishes persistent (m_local, l_local, O_local). # # Cannot be folded into the Tiles 1..N loop (kernbench-only limitation): # - persistent (m, ℓ, O) must live OUTSIDE ``tl.scratch_scope``, # otherwise scope teardown discards them before the next tile's # merge can read them; # - kernbench has no scratch-backed initializer — ``tl.zeros`` / # ``tl.full`` return addr=0 handles with no backing storage, so # they cannot be overwritten via ``tl.copy_to`` to seed (-inf, 0, 0). # So Tile 0 computes the initial running state directly; Tiles 1..N # fold into it. Triton port: limitation does not apply (SSA tensors # stay live across iterations) — a single unified loop suffices. tile_s0 = min(TILE_S_KV, S_local) K_T = tl.load(k_ptr, shape=(d_head, tile_s0), dtype="f16") V = tl.load(v_ptr, shape=(tile_s0, d_head), dtype="f16") scores = tl.dot(Q, K_T) m_local = tl.max(scores, axis=-1) centered = scores - m_local exp_scores = tl.exp(centered) l_local = tl.sum(exp_scores, axis=-1) O_local = tl.dot(exp_scores, V) # Tiles 1..n_tiles-1: fold into running state via online-softmax merge. # Triton port: drop the ``with tl.scratch_scope():`` line and replace # each ``copy_to`` with a Python rebind. for tile_idx in range(1, n_tiles): tile_start = tile_idx * TILE_S_KV tile_s = min(TILE_S_KV, S_local - tile_start) with tl.scratch_scope(): K_T_t = tl.load(k_ptr + tile_start * KV_ROW_BYTES, shape=(d_head, tile_s), dtype="f16") V_t = tl.load(v_ptr + tile_start * KV_ROW_BYTES, shape=(tile_s, d_head), dtype="f16") scores_t = tl.dot(Q, K_T_t) m_tile = tl.max(scores_t, axis=-1) centered_t = scores_t - m_tile exp_scores_t = tl.exp(centered_t) l_tile = tl.sum(exp_scores_t, axis=-1) O_tile = tl.dot(exp_scores_t, V_t) m_new, l_new, O_new = _merge_running( m_local, l_local, O_local, m_tile, l_tile, O_tile, tl=tl, ) tl.copy_to(m_local, m_new) tl.copy_to(l_local, l_new) tl.copy_to(O_local, O_new) # ── Communication: chain reduce-to-root at PE 0 of CUBE 0 ── PE_GRID_COLS = 4 pe_col = pe_id % PE_GRID_COLS pe_row = pe_id // PE_GRID_COLS pe_cols_used = min(PE_GRID_COLS, P) pe_rows_used = (P + PE_GRID_COLS - 1) // PE_GRID_COLS # Level-2 row chain (intra-CUBE, along intra_W, leftward). if pe_cols_used > 1: if pe_col < pe_cols_used - 1: with tl.scratch_scope(): m_other = tl.recv(dir="intra_E", shape=m_local.shape, dtype="f16") l_other = tl.recv(dir="intra_E", shape=l_local.shape, dtype="f16") O_other = tl.recv(dir="intra_E", shape=O_local.shape, dtype="f16") m_new, l_new, O_new = _merge_running( m_local, l_local, O_local, m_other, l_other, O_other, tl=tl, ) tl.copy_to(m_local, m_new) tl.copy_to(l_local, l_new) tl.copy_to(O_local, O_new) if pe_col > 0: tl.send(dir="intra_W", src=m_local) tl.send(dir="intra_W", src=l_local) tl.send(dir="intra_W", src=O_local) # Level-2 col bridge (intra-CUBE, along intra_N, row-1 → row-0). if pe_col == 0 and pe_rows_used > 1: if pe_row < pe_rows_used - 1: with tl.scratch_scope(): m_other = tl.recv(dir="intra_S", shape=m_local.shape, dtype="f16") l_other = tl.recv(dir="intra_S", shape=l_local.shape, dtype="f16") O_other = tl.recv(dir="intra_S", shape=O_local.shape, dtype="f16") m_new, l_new, O_new = _merge_running( m_local, l_local, O_local, m_other, l_other, O_other, tl=tl, ) tl.copy_to(m_local, m_new) tl.copy_to(l_local, l_new) tl.copy_to(O_local, O_new) if pe_row > 0: tl.send(dir="intra_N", src=m_local) tl.send(dir="intra_N", src=l_local) tl.send(dir="intra_N", src=O_local) # Level-1 inter-CUBE reduce (only PE 0 of each CUBE participates). if pe_id == 0 and C > 1 and sub_w == 0: # 1D chain along W, leftward; root at CUBE 0. if cube_id < C - 1: with tl.scratch_scope(): m_other = tl.recv(dir="E", shape=m_local.shape, dtype="f16") l_other = tl.recv(dir="E", shape=l_local.shape, dtype="f16") O_other = tl.recv(dir="E", shape=O_local.shape, dtype="f16") m_new, l_new, O_new = _merge_running( m_local, l_local, O_local, m_other, l_other, O_other, tl=tl, ) tl.copy_to(m_local, m_new) tl.copy_to(l_local, l_new) tl.copy_to(O_local, O_new) if cube_id > 0: tl.send(dir="W", src=m_local) tl.send(dir="W", src=l_local) tl.send(dir="W", src=O_local) elif pe_id == 0 and sub_w > 0: # ── ADR-0060 §4.2 lrab-adapted center-root mesh reduce ── # Adapts Phases 1-2 of lrab_hierarchical_allreduce.py: # bidirectional row reduce converges at root_col, bidirectional # col reduce on root_col converges at root_row. Reduce-only # (Phases 3-5 dropped: no inter-SIP, no broadcast — attention # needs the answer at one rank). Plain ``+`` replaced with the # log-sum-exp ``_merge_running`` for (m, ℓ, O). row = cube_id // sub_w col = cube_id % sub_w # Phase 1: row reduce — converge at col == root_col. if col == 0 and root_col > 0: tl.send(dir="E", src=m_local) tl.send(dir="E", src=l_local) tl.send(dir="E", src=O_local) elif 0 < col < root_col: with tl.scratch_scope(): m_other = tl.recv(dir="W", shape=m_local.shape, dtype="f16") l_other = tl.recv(dir="W", shape=l_local.shape, dtype="f16") O_other = tl.recv(dir="W", shape=O_local.shape, dtype="f16") m_new, l_new, O_new = _merge_running( m_local, l_local, O_local, m_other, l_other, O_other, tl=tl, ) tl.copy_to(m_local, m_new) tl.copy_to(l_local, l_new) tl.copy_to(O_local, O_new) tl.send(dir="E", src=m_local) tl.send(dir="E", src=l_local) tl.send(dir="E", src=O_local) elif col == root_col: if root_col > 0: with tl.scratch_scope(): m_other = tl.recv(dir="W", shape=m_local.shape, dtype="f16") l_other = tl.recv(dir="W", shape=l_local.shape, dtype="f16") O_other = tl.recv(dir="W", shape=O_local.shape, dtype="f16") m_new, l_new, O_new = _merge_running( m_local, l_local, O_local, m_other, l_other, O_other, tl=tl, ) tl.copy_to(m_local, m_new) tl.copy_to(l_local, l_new) tl.copy_to(O_local, O_new) if sub_w - 1 > root_col: with tl.scratch_scope(): m_other = tl.recv(dir="E", shape=m_local.shape, dtype="f16") l_other = tl.recv(dir="E", shape=l_local.shape, dtype="f16") O_other = tl.recv(dir="E", shape=O_local.shape, dtype="f16") m_new, l_new, O_new = _merge_running( m_local, l_local, O_local, m_other, l_other, O_other, tl=tl, ) tl.copy_to(m_local, m_new) tl.copy_to(l_local, l_new) tl.copy_to(O_local, O_new) elif root_col < col < sub_w - 1: with tl.scratch_scope(): m_other = tl.recv(dir="E", shape=m_local.shape, dtype="f16") l_other = tl.recv(dir="E", shape=l_local.shape, dtype="f16") O_other = tl.recv(dir="E", shape=O_local.shape, dtype="f16") m_new, l_new, O_new = _merge_running( m_local, l_local, O_local, m_other, l_other, O_other, tl=tl, ) tl.copy_to(m_local, m_new) tl.copy_to(l_local, l_new) tl.copy_to(O_local, O_new) tl.send(dir="W", src=m_local) tl.send(dir="W", src=l_local) tl.send(dir="W", src=O_local) elif col == sub_w - 1 and sub_w - 1 > root_col: tl.send(dir="W", src=m_local) tl.send(dir="W", src=l_local) tl.send(dir="W", src=O_local) # Phase 2: col reduce on col == root_col — converge at row == root_row. if col == root_col: if row == 0 and root_row > 0: tl.send(dir="S", src=m_local) tl.send(dir="S", src=l_local) tl.send(dir="S", src=O_local) elif 0 < row < root_row: with tl.scratch_scope(): m_other = tl.recv(dir="N", shape=m_local.shape, dtype="f16") l_other = tl.recv(dir="N", shape=l_local.shape, dtype="f16") O_other = tl.recv(dir="N", shape=O_local.shape, dtype="f16") m_new, l_new, O_new = _merge_running( m_local, l_local, O_local, m_other, l_other, O_other, tl=tl, ) tl.copy_to(m_local, m_new) tl.copy_to(l_local, l_new) tl.copy_to(O_local, O_new) tl.send(dir="S", src=m_local) tl.send(dir="S", src=l_local) tl.send(dir="S", src=O_local) elif row == root_row: if root_row > 0: with tl.scratch_scope(): m_other = tl.recv(dir="N", shape=m_local.shape, dtype="f16") l_other = tl.recv(dir="N", shape=l_local.shape, dtype="f16") O_other = tl.recv(dir="N", shape=O_local.shape, dtype="f16") m_new, l_new, O_new = _merge_running( m_local, l_local, O_local, m_other, l_other, O_other, tl=tl, ) tl.copy_to(m_local, m_new) tl.copy_to(l_local, l_new) tl.copy_to(O_local, O_new) if sub_h - 1 > root_row: with tl.scratch_scope(): m_other = tl.recv(dir="S", shape=m_local.shape, dtype="f16") l_other = tl.recv(dir="S", shape=l_local.shape, dtype="f16") O_other = tl.recv(dir="S", shape=O_local.shape, dtype="f16") m_new, l_new, O_new = _merge_running( m_local, l_local, O_local, m_other, l_other, O_other, tl=tl, ) tl.copy_to(m_local, m_new) tl.copy_to(l_local, l_new) tl.copy_to(O_local, O_new) elif root_row < row < sub_h - 1: with tl.scratch_scope(): m_other = tl.recv(dir="S", shape=m_local.shape, dtype="f16") l_other = tl.recv(dir="S", shape=l_local.shape, dtype="f16") O_other = tl.recv(dir="S", shape=O_local.shape, dtype="f16") m_new, l_new, O_new = _merge_running( m_local, l_local, O_local, m_other, l_other, O_other, tl=tl, ) tl.copy_to(m_local, m_new) tl.copy_to(l_local, l_new) tl.copy_to(O_local, O_new) tl.send(dir="N", src=m_local) tl.send(dir="N", src=l_local) tl.send(dir="N", src=O_local) elif row == sub_h - 1 and sub_h - 1 > root_row: tl.send(dir="N", src=m_local) tl.send(dir="N", src=l_local) tl.send(dir="N", src=O_local) # ── Final normalise + store (root only) ── if pe_id == 0 and cube_id == root_cube: O_final = O_local / l_local tl.store(o_ptr, O_final)