Repository navigation
[Triton/Gluon] Add 3D neighborhood flash attention (na3d_flash) #5230
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Boss2002n
merged 56 commits into
ROCm:main
from
jjuvonen-amd:feat/3d-neighborhood-flash-attn
Oct 2, 2026
Merged
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 19f8b88
na3d_flash kernel: input type and bounds validation
jjuvonen-amd 85671cc
use repr= in jit to include autotuned configs in kernel names
jjuvonen-amd 90a4b4e
fix cost heuristics in _na3d_sdpa_exact, correct docstring for rightm…
jjuvonen-amd 508338d
avoid importing from kernels module in test
jjuvonen-amd c526551
black format
jjuvonen-amd b448a1b
fix cross-row bug when W % BLOCK_Q != 0 and add test shape to validat…
jjuvonen-amd 6dda9d3
remove unnecessary correct_rightmost_block parameter and related tests
jjuvonen-amd 2617cc8
Prune autotune configs where BLOCK_KV is too small for KW
jjuvonen-amd 3cf7288
fix global state leak in pytest, fix config prune conditions
jjuvonen-amd 510e65a
use actually highest value W in min(), instead of arbitary high value
jjuvonen-amd e00b5d3
Add a fast-fail validation to attn call
jjuvonen-amd 0846c23
remove duplicate check
jjuvonen-amd af1eab5
cast indices to int64 to avoid potential OOB reads/writes
jjuvonen-amd 028f9ca
cast int multiplications to int64 to avoid potential OOB reads/writes
jjuvonen-amd bdd1f75
cast stride_bnh to int64 to avoid exceeding int32 range
jjuvonen-amd 82cfbbe
drop lru_cache on _na3d_sdpa_mask in test file
jjuvonen-amd 400e965
replace f-string logging with lazy %-style logging
jjuvonen-amd 1a3857a
add arch guard to public na3d_flash_attn launcher
jjuvonen-amd 44e8fe4
split test_na3d_flash.py into separate bench and test files
jjuvonen-amd fa74f05
perf opt: na3d_flash to use default (B,T,H,W,NH,HD) layout to avoid p…
jjuvonen-amd 4497db9
assert gap in autotune configs with descriptive message
jjuvonen-amd 1b90778
benchmark only valid dtypes (currently bf16) and skip others with war…
jjuvonen-amd 9ac3486
remove unsupported fp16 dtype example from bench docstring
jjuvonen-amd 3cedf1d
annotate strides as tl.int64 and refactor bench file gfx check
jjuvonen-amd aa8e3fd
renamed autotune configs for clarity
jjuvonen-amd 98607a7
assert lower bounds of KT, KH and KW
jjuvonen-amd 17c39a7
assert HD >= 16 in na3d_flash
jjuvonen-amd 9035e3b
verify that q/k/v are CUDA tensors
jjuvonen-amd ce22c69
fix num_stages forwarding bug in autotune configs
jjuvonen-amd d9b9afd
use runtime variant of get_gfx
jjuvonen-amd 2c18894
fix checkAllClose asserts in tests
jjuvonen-amd 085f542
tighten test boundary check from 5% mismatch to 1%
jjuvonen-amd c81c79a
correct HBM formula in bench to report effective rate
jjuvonen-amd c6e7afe
add test shapes for KW > 17 edge case
jjuvonen-amd a97d6c6
document na3d_flash_attn is forward-only
jjuvonen-amd 24f3120
use runtime gfx check in bench and tests
jjuvonen-amd 44c90fd
fix W.to() crash when W=1, add test shape with W=1
jjuvonen-amd e9eff68
set default configs and keep opt-in autotune
jjuvonen-amd c307a74
pruner to read named_args and kwargs; derive triton fallback configs …
jjuvonen-amd f8e3679
Merge remote-tracking branch 'origin/main' into feat/3d-neighborhood-…
jjuvonen-amd bf37653
removed opt-in @triton.autotune
jjuvonen-amd a80699c
test(na3d_flash): widen large-KW shape to W=128 to catch wrong config
jjuvonen-amd fd4c13a
test(na3d_flash): use assertAllclose so failures keep diagnostics
jjuvonen-amd 4fad404
bench(na3d_flash): match decoder v layout so timing includes staging …
jjuvonen-amd 35a5f84
docs(na3d_flash): document full supported domain in the launcher docs…
jjuvonen-amd 2886f58
Merge branch 'main' into feat/3d-neighborhood-flash-attn
vgokhale 23b9b43
tune(na3d_flash): use measured gfx942 tile for the large-KW config (n…
jjuvonen-amd a597430
docs(na3d_flash): describe reference masks as deduplicated, not cached
jjuvonen-amd 1ac0278
Merge branch 'main' into feat/3d-neighborhood-flash-attn
jjuvonen-amd 1f16800
Merge branch 'main' into feat/3d-neighborhood-flash-attn
Boss2002n 26481fb
Update comments to reflect edge-case coverage changes
jjuvonen-amd d239b26
Merge branch 'main' into feat/3d-neighborhood-flash-attn
Boss2002n b5b227d
Merge branch 'main' into feat/3d-neighborhood-flash-attn
Boss2002n 5c55f98
Merge branch 'main' into feat/3d-neighborhood-flash-attn
Boss2002n 92eef2a
Merge branch 'main' into feat/3d-neighborhood-flash-attn
Boss2002n File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
170 changes: 170 additions & 0 deletions
170
aiter/ops/triton/_triton_kernels/attention/na3d_flash.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 | ||
|
|
||
| # 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), | ||
|
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), | ||
| ] | ||
|
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( | ||
|
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 | ||
|
|
||
|
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 | ||
|
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], | ||
| ) | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.