Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
9b301cb
add 3d neighborhood flash attention
jjuvonen-amd Sep 3, 2026
19f8b88
na3d_flash kernel: input type and bounds validation
jjuvonen-amd Sep 3, 2026
85671cc
use repr= in jit to include autotuned configs in kernel names
jjuvonen-amd Sep 3, 2026
90a4b4e
fix cost heuristics in _na3d_sdpa_exact, correct docstring for rightm…
jjuvonen-amd Sep 3, 2026
508338d
avoid importing from kernels module in test
jjuvonen-amd Sep 3, 2026
c526551
black format
jjuvonen-amd Sep 3, 2026
b448a1b
fix cross-row bug when W % BLOCK_Q != 0 and add test shape to validat…
jjuvonen-amd Sep 3, 2026
6dda9d3
remove unnecessary correct_rightmost_block parameter and related tests
jjuvonen-amd Sep 3, 2026
2617cc8
Prune autotune configs where BLOCK_KV is too small for KW
jjuvonen-amd Sep 3, 2026
3cf7288
fix global state leak in pytest, fix config prune conditions
jjuvonen-amd Sep 3, 2026
510e65a
use actually highest value W in min(), instead of arbitary high value
jjuvonen-amd Sep 3, 2026
e00b5d3
Add a fast-fail validation to attn call
jjuvonen-amd Sep 3, 2026
0846c23
remove duplicate check
jjuvonen-amd Sep 3, 2026
af1eab5
cast indices to int64 to avoid potential OOB reads/writes
jjuvonen-amd Sep 3, 2026
028f9ca
cast int multiplications to int64 to avoid potential OOB reads/writes
jjuvonen-amd Sep 3, 2026
bdd1f75
cast stride_bnh to int64 to avoid exceeding int32 range
jjuvonen-amd Sep 3, 2026
82cfbbe
drop lru_cache on _na3d_sdpa_mask in test file
jjuvonen-amd Sep 4, 2026
400e965
replace f-string logging with lazy %-style logging
jjuvonen-amd Sep 4, 2026
1a3857a
add arch guard to public na3d_flash_attn launcher
jjuvonen-amd Sep 4, 2026
44e8fe4
split test_na3d_flash.py into separate bench and test files
jjuvonen-amd Sep 4, 2026
fa74f05
perf opt: na3d_flash to use default (B,T,H,W,NH,HD) layout to avoid p…
jjuvonen-amd Sep 6, 2026
4497db9
assert gap in autotune configs with descriptive message
jjuvonen-amd Sep 6, 2026
1b90778
benchmark only valid dtypes (currently bf16) and skip others with war…
jjuvonen-amd Sep 6, 2026
9ac3486
remove unsupported fp16 dtype example from bench docstring
jjuvonen-amd Sep 6, 2026
3cedf1d
annotate strides as tl.int64 and refactor bench file gfx check
jjuvonen-amd Sep 6, 2026
aa8e3fd
renamed autotune configs for clarity
jjuvonen-amd Sep 7, 2026
98607a7
assert lower bounds of KT, KH and KW
jjuvonen-amd Sep 7, 2026
17c39a7
assert HD >= 16 in na3d_flash
jjuvonen-amd Sep 7, 2026
9035e3b
verify that q/k/v are CUDA tensors
jjuvonen-amd Sep 7, 2026
ce22c69
fix num_stages forwarding bug in autotune configs
jjuvonen-amd Sep 7, 2026
d9b9afd
use runtime variant of get_gfx
jjuvonen-amd Sep 7, 2026
2c18894
fix checkAllClose asserts in tests
jjuvonen-amd Sep 7, 2026
085f542
tighten test boundary check from 5% mismatch to 1%
jjuvonen-amd Sep 7, 2026
c81c79a
correct HBM formula in bench to report effective rate
jjuvonen-amd Sep 8, 2026
c6e7afe
add test shapes for KW > 17 edge case
jjuvonen-amd Sep 8, 2026
a97d6c6
document na3d_flash_attn is forward-only
jjuvonen-amd Sep 8, 2026
24f3120
use runtime gfx check in bench and tests
jjuvonen-amd Sep 8, 2026
44c90fd
fix W.to() crash when W=1, add test shape with W=1
jjuvonen-amd Sep 9, 2026
e9eff68
set default configs and keep opt-in autotune
jjuvonen-amd Sep 14, 2026
c307a74
pruner to read named_args and kwargs; derive triton fallback configs …
jjuvonen-amd Sep 14, 2026
f8e3679
Merge remote-tracking branch 'origin/main' into feat/3d-neighborhood-…
jjuvonen-amd Sep 17, 2026
bf37653
removed opt-in @triton.autotune
jjuvonen-amd Sep 17, 2026
a80699c
test(na3d_flash): widen large-KW shape to W=128 to catch wrong config
jjuvonen-amd Sep 17, 2026
fd4c13a
test(na3d_flash): use assertAllclose so failures keep diagnostics
jjuvonen-amd Sep 17, 2026
4fad404
bench(na3d_flash): match decoder v layout so timing includes staging …
jjuvonen-amd Sep 17, 2026
35a5f84
docs(na3d_flash): document full supported domain in the launcher docs…
jjuvonen-amd Sep 17, 2026
2886f58
Merge branch 'main' into feat/3d-neighborhood-flash-attn
vgokhale Sep 25, 2026
23b9b43
tune(na3d_flash): use measured gfx942 tile for the large-KW config (n…
jjuvonen-amd Sep 26, 2026
a597430
docs(na3d_flash): describe reference masks as deduplicated, not cached
jjuvonen-amd Sep 26, 2026
1ac0278
Merge branch 'main' into feat/3d-neighborhood-flash-attn
jjuvonen-amd Sep 30, 2026
1f16800
Merge branch 'main' into feat/3d-neighborhood-flash-attn
Boss2002n Sep 30, 2026
26481fb
Update comments to reflect edge-case coverage changes
jjuvonen-amd Sep 30, 2026
d239b26
Merge branch 'main' into feat/3d-neighborhood-flash-attn
Boss2002n Sep 30, 2026
b5b227d
Merge branch 'main' into feat/3d-neighborhood-flash-attn
Boss2002n Oct 1, 2026
5c55f98
Merge branch 'main' into feat/3d-neighborhood-flash-attn
Boss2002n Oct 1, 2026
92eef2a
Merge branch 'main' into feat/3d-neighborhood-flash-attn
Boss2002n Oct 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
170 changes: 170 additions & 0 deletions aiter/ops/triton/_triton_kernels/attention/na3d_flash.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
# SPDX-License-Identifier: MIT
# Copyright (C) 2024-2026, Advanced Micro Devices, Inc. All rights reserved.
"""Triton flash-style 3D neighborhood attention.

Each query token attends to the (KT x KH x KW) neighborhood centered on its
(t, h, w) grid position, with inward border-shift so every window has the full
KT x KH x KW keys. The kernel processes BLOCK_Q consecutive queries per program;
all queries in one block share the same (t, h) row (requires W >= BLOCK_Q).

Algorithm:
- Q is loaded once into registers and held for all KT x KH inner iterations.
- Each iteration loads one (t_kv, h_kv) row of K and V at the shared W window.
- Running online softmax in log2 space (exp2, log2(e) folded into scale).
- Autotuned over BLOCK_Q (16, 32), num_stages (2, 3, 4), num_warps (4, 8).
"""

import triton
import triton.language as tl

Comment thread
jjuvonen-amd marked this conversation as resolved.
Resolved
# Autotune configs.
# BLOCK_KV = next_pow2(BLOCK_Q + KW - 1) covers the union of all BLOCK_Q
# queries' W windows in one chunk:
# BLOCK_Q=16 -> BLOCK_KV=32
# BLOCK_Q=32 -> BLOCK_KV=64
# The W >= BLOCK_Q constraint is enforced by the pruner below.
_CONFIGS = [
triton.Config({"BLOCK_Q": 16, "BLOCK_KV": 32, "num_stages": 2}, num_warps=4),
triton.Config({"BLOCK_Q": 16, "BLOCK_KV": 32, "num_stages": 2}, num_warps=8),
triton.Config({"BLOCK_Q": 16, "BLOCK_KV": 32, "num_stages": 3}, num_warps=4),
triton.Config({"BLOCK_Q": 16, "BLOCK_KV": 32, "num_stages": 3}, num_warps=8),
Comment thread
jjuvonen-amd marked this conversation as resolved.
Resolved
triton.Config({"BLOCK_Q": 16, "BLOCK_KV": 32, "num_stages": 4}, num_warps=4),
triton.Config({"BLOCK_Q": 16, "BLOCK_KV": 32, "num_stages": 4}, num_warps=8),
# BLOCK_Q=32 halves program count but doubles BLOCK_KV (more masked compute).
triton.Config({"BLOCK_Q": 32, "BLOCK_KV": 64, "num_stages": 2}, num_warps=4),
triton.Config({"BLOCK_Q": 32, "BLOCK_KV": 64, "num_stages": 2}, num_warps=8),
]
Comment thread
jjuvonen-amd marked this conversation as resolved.
Resolved


def _prune_configs(configs, named_args, **kwargs):
"""Remove configs where BLOCK_Q > W.

Ensures all queries in one block share the same (t, h) row (W >= BLOCK_Q required).
"""
W = named_args["W"]
return [c for c in configs if c.kwargs["BLOCK_Q"] <= W]


@triton.autotune(
configs=_CONFIGS,
key=["KT", "KH", "KW", "HD", "W"],
prune_configs_by={"early_config_prune": _prune_configs},
)
@triton.jit
def _na3d_flash_fwd(
Comment thread
jjuvonen-amd marked this conversation as resolved.
Resolved
Q_ptr,
K_ptr,
V_ptr,
Out_ptr,
stride_bnh,
stride_seq,
T,
H,
W,
SEQ,
HD: tl.constexpr, # head dimension: any power-of-2 value
KT: tl.constexpr, # neighborhood depth: constexpr -> static loop bound
KH: tl.constexpr, # neighborhood height: constexpr -> static loop bound
KW: tl.constexpr, # neighborhood width: used in W-direction masking
BLOCK_Q: tl.constexpr,
BLOCK_KV: tl.constexpr, # power-of-2, >= BLOCK_Q + KW - 1
):
"""Flash-attention inner kernel. Requires W >= BLOCK_Q (same (t,h) row per block).

See _prune_configs for the W >= BLOCK_Q constraint.
"""
pid_q = tl.program_id(0)
pid_bnh = tl.program_id(1)
HW = H * W

q_start = pid_q * BLOCK_Q
q_offs = tl.arange(0, BLOCK_Q)
q_idx = q_start + q_offs
q_mask = q_idx < SEQ

q_t = q_idx // HW
q_h = (q_idx % HW) // W
q_w = q_idx % W

Comment thread
jjuvonen-amd marked this conversation as resolved.
Resolved
# Inward-shifted centered neighborhood window starts.
q_t_ws = tl.minimum(tl.maximum(q_t - KT // 2, 0), T - KT)
q_h_ws = tl.minimum(tl.maximum(q_h - KH // 2, 0), H - KH)
q_w_ws = tl.minimum(tl.maximum(q_w - KW // 2, 0), W - KW)

# Scalar window starts (shared across all BLOCK_Q queries in this block).
INF_I = 999999
t_ws = tl.min(tl.where(q_mask, q_t_ws, INF_I))
h_ws = tl.min(tl.where(q_mask, q_h_ws, INF_I))
w_lo = tl.min(tl.where(q_mask, q_w_ws, INF_I))

hd_offs = tl.arange(0, HD)
kv_offs = tl.arange(0, BLOCK_KV)

# Cast to int64: pid_bnh (int32) x stride_bnh can exceed int32 range.
base = pid_bnh.to(tl.int64) * stride_bnh
kv_w = w_lo + kv_offs
kv_ok = kv_w < W # W-boundary guard, constant across the KT x KH loop

# Q loaded once into registers for all KT x KH iterations.
Q_tile = tl.load(
Q_ptr + base + q_idx[:, None] * stride_seq + hd_offs[None, :],
mask=q_mask[:, None],
other=0.0,
).to(tl.bfloat16)

m_i = tl.full((BLOCK_Q,), -3e38, dtype=tl.float32)
l_i = tl.zeros((BLOCK_Q,), dtype=tl.float32)
acc = tl.zeros((BLOCK_Q, HD), dtype=tl.float32)

# Flat KT x KH loop with static trip count - enables Triton's software pipeliner.
# valid_w, kv_ok, and q_mask are loop-invariant; the compiler's LICM handles them
# without explicit pre-computation (pre-computing would raise VGPR pressure and
# reduce occupancy on CDNA).
for kv_idx in range(KT * KH):
dt = kv_idx // KH
dh = kv_idx % KH
t_kv = t_ws + dt
h_kv = h_ws + dh

row_base = t_kv * HW + h_kv * W
kv_flat = row_base + kv_w
Comment thread
jjuvonen-amd marked this conversation as resolved.
Resolved

K_T = tl.load(
K_ptr + base + kv_flat[None, :] * stride_seq + hd_offs[:, None],
mask=kv_ok[None, :],
other=0.0,
).to(tl.bfloat16)

V_tile = tl.load(
V_ptr + base + kv_flat[:, None] * stride_seq + hd_offs[None, :],
mask=kv_ok[:, None],
other=0.0,
).to(tl.bfloat16)

# QK in log2 space: fold log2(e) into the scale to use exp2 throughout.
scores = tl.dot(Q_tile, K_T, out_dtype=tl.float32) * 1.4426950408889634

valid_w = (kv_w[None, :] >= q_w_ws[:, None]) & (
kv_w[None, :] < q_w_ws[:, None] + KW
)
scores = tl.where(
valid_w & kv_ok[None, :] & q_mask[:, None], scores, float("-inf")
)

m_new = tl.maximum(m_i, tl.max(scores, axis=1))
exp_s = tl.exp2(scores - m_new[:, None])
exp_diff = tl.exp2(m_i - m_new)
l_i = l_i * exp_diff + tl.sum(exp_s, axis=1)
acc = acc * exp_diff[:, None] + tl.dot(
exp_s.to(tl.bfloat16), V_tile, out_dtype=tl.float32
)
m_i = m_new

safe_l = tl.where(l_i > 0, l_i, 1.0)
acc = acc / safe_l[:, None]

tl.store(
Out_ptr + base + q_idx[:, None] * stride_seq + hd_offs[None, :],
acc.to(tl.bfloat16),
mask=q_mask[:, None],
)
Loading
Loading