Skip to content
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
c6be02e
feat(flydsl): Add paged-attention Tile kernel
fsx950223 Jul 22, 2026
9183f94
refactor(flydsl): Remove unused memory helpers
fsx950223 Jul 22, 2026
5824387
refactor(flydsl): Separate PA decode kernel from host
fsx950223 Jul 23, 2026
ed3e0d6
refactor(flydsl): Rename PA decode host API
fsx950223 Jul 23, 2026
da010a7
refactor(flydsl): Merge PA decode host modules
fsx950223 Jul 23, 2026
430b8cd
feat(flydsl): Add Gluon-compatible PA decode dispatch
fsx950223 Jul 24, 2026
6f8d4fd
fix(ci): Resolve Ruff 0.16 lint failures
fsx950223 Jul 24, 2026
3500f28
fix(flydsl): use in-tree PA buffer helpers
fsx950223 Aug 14, 2026
48e320d
fix(flydsl): support FlyDSL 0.3 vector APIs
fsx950223 Aug 14, 2026
10ad6d3
[FlyDSL] fix(pa_decode): rename to pa_decode_flydsl and fix review bl…
fsx950223 Aug 21, 2026
e65d6ba
refactor(pa_decode): move FlyDSL reduce kernel
fsx950223 Sep 8, 2026
7bac868
refactor(flydsl): address pa decode review feedback
fsx950223 Sep 8, 2026
8de2af2
style(flydsl): format scalar buffer helper
fsx950223 Sep 8, 2026
5b232b1
refactor(flydsl): use buffer tensor helper in pa decode
fsx950223 Sep 8, 2026
03d44cb
refactor(flydsl): inline pa decode buffer loaders
fsx950223 Sep 8, 2026
e847b80
refactor(flydsl): use pointer ABI for pa decode tile
fsx950223 Sep 8, 2026
27b6f58
[FlyDSL] Fix PA edge cases and add tuned page-128 support (#5474)
zhiding512 Sep 12, 2026
da579b8
[FlyDSL] Fix phase-split PA decode softmax overflow
Bernard-Liu Sep 13, 2026
be0963c
[FlyDSL] Remove duplicate group_idx calculation in PA reduce
Bernard-Liu Sep 13, 2026
77572ba
fix(flydsl): import PA decode function explicitly
fsx950223 Sep 14, 2026
3bcde89
perf(flydsl): prototype page16 PA V/K pipeline
fsx950223 Sep 14, 2026
68cf491
perf(flydsl): add page16 PA IGLP variant
fsx950223 Sep 14, 2026
cdc4c74
perf(flydsl): allow page16 PA pipeline for all split counts
fsx950223 Sep 14, 2026
7bc3660
perf(flydsl): add Gluon numeric-policy PA ablation
fsx950223 Sep 14, 2026
f4f8431
fix(flydsl): address PA decode review follow-ups
fsx950223 Sep 14, 2026
d39ba45
feat(flydsl): extend PA reduction to 256 partitions
fsx950223 Sep 14, 2026
dbfff5f
fix(flydsl): mask partial PA reduction waves
fsx950223 Sep 14, 2026
cbec769
perf(flydsl): streamline single-tile PA softmax
fsx950223 Sep 14, 2026
f03c6af
perf(flydsl): tune native page128 long-context decode
fsx950223 Sep 14, 2026
c154696
[FlyDSL] fix(pa_decode): allow a packed V cache to span fewer blocks
zhiding512 Sep 14, 2026
71241bb
[FlyDSL] test(pa_decode): sweep served TP4 batches over both page sizes
zhiding512 Sep 14, 2026
959a148
perf(flydsl): parallelize large PA reductions
fsx950223 Sep 14, 2026
610db54
test(flydsl): model parallel PA reduction
fsx950223 Sep 14, 2026
53cac03
perf(flydsl): use eight PA reduction groups
fsx950223 Sep 14, 2026
5ae39e4
test(flydsl): expose transposed-V PA sweep option
fsx950223 Sep 14, 2026
af6f4db
Merge PR 4332 latest head into FlyDSL long-context optimization
fsx950223 Sep 14, 2026
d5edb5c
feat(flydsl): make PA split clamp configurable
fsx950223 Sep 14, 2026
38291a4
test(flydsl): remove standalone PA reducer model test
fsx950223 Sep 14, 2026
42edab9
test(flydsl): remove Gluon-specific PA cases
fsx950223 Sep 14, 2026
29689c3
test(flydsl): add per-token PA performance sweep
fsx950223 Sep 14, 2026
60d4474
[FlyDSL] perf(pa_decode): improve page-16 and page-128 load scheduling
zhiding512 Sep 14, 2026
3d53dca
Revert PA load-scheduling optimization for separate review
zhiding512 Sep 14, 2026
64a07d6
perf(flydsl): optimize FP8 per-token MTP4 paged attention
fsx950223 Sep 15, 2026
ae7bb72
refactor(flydsl): remove PA prefetch environment overrides
fsx950223 Sep 15, 2026
60e6331
refactor(flydsl): address PA kernel cleanup feedback
fsx950223 Sep 15, 2026
b4d34f6
test(flydsl): extend and streamline PA benchmark
fsx950223 Sep 15, 2026
b7b549c
perf(flydsl): optimize MTP2 and MTP3 decode
fsx950223 Sep 15, 2026
cecd335
[FlyDSL] perf(pa_decode): add dynamic KV work planning and tune parti…
zhiding512 Sep 16, 2026
d699fb2
fix(flydsl): satisfy lint for plan re-export
fsx950223 Sep 16, 2026
939d64a
refactor(flydsl): consolidate PA tuning constants
fsx950223 Sep 16, 2026
ed84952
perf(flydsl): unify per-token V prefetch policy
fsx950223 Sep 17, 2026
2f295c6
feat(flydsl): add sliding windows and attention sinks to PA decode
fsx950223 Sep 17, 2026
fd82533
test(flydsl): consolidate PA decode into one parametrized test
fsx950223 Sep 17, 2026
380b949
refactor(flydsl): consolidate PA decode constants and conditions
fsx950223 Sep 17, 2026
2100893
refactor(flydsl): centralize PA decode scheduling
fsx950223 Sep 17, 2026
a4902d0
refactor(flydsl): unify PA decode scheduling and compilation
fsx950223 Sep 17, 2026
466e94c
refactor(flydsl): align PA decode API and test arguments
fsx950223 Sep 17, 2026
0b9f100
fix(flydsl): exclude work_plan from the Torch operator schema
fsx950223 Sep 17, 2026
98dd157
refactor(flydsl): simplify and broaden PA decode plan scheduling
fsx950223 Sep 18, 2026
c5911aa
style(flydsl): fix PA decode Black formatting
fsx950223 Sep 18, 2026
aa7ac40
test(flydsl): consolidate PA decode unit cases
fsx950223 Sep 18, 2026
d42e8f0
Merge branch 'main' into flydsl-pa-decode-tile
fsx950223 Sep 21, 2026
1ef243a
fix(flydsl): use Numeric bitcast in DPP XOR helper
fsx950223 Sep 21, 2026
408aed7
fix(flydsl): cap planned PA decode partitions at device CU count
fsx950223 Sep 21, 2026
436ea3a
docs(flydsl): trim PA decode comments
fsx950223 Sep 21, 2026
12f43f8
refactor(flydsl): defer PA decode custom-op registration
fsx950223 Sep 21, 2026
d9d5663
refactor(flydsl): remove unused kernel helpers
fsx950223 Sep 21, 2026
452a91d
refactor(flydsl): simplify PA decode entry and DPP bitcast
fsx950223 Sep 21, 2026
2bd7e88
Merge branch 'main' into flydsl-pa-decode-tile
fsx950223 Sep 21, 2026
09b194f
Merge branch 'main' into flydsl-pa-decode-tile
coderfeli Sep 22, 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
14 changes: 14 additions & 0 deletions .github/scripts/aiter_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,20 @@ for file in "${sharded_files[@]}"; do
} | tee -a latest_test.log
test_cmd=(env AITER_MLA_DECODE_PERSISTENT_MAX_BATCH=0 timeout 60m python3 "$file")
;;
op_tests/test_flydsl_pa_decode.py)
# The CLI sweep does not collect parametrized pytest regressions.
test_cmd=(
timeout 60m
bash -c '
set -euo pipefail
test_file=$1
python3 -m pytest -q \
"${test_file}::test_large_negative_logits_preserve_online_softmax"
python3 "$test_file"
'
_ "$file"
)
;;
op_tests/test_gemm_a6w6.py)
{
echo "Running tuned dispatch plus every compatible A6W6 ASM kernel"
Expand Down
62 changes: 62 additions & 0 deletions aiter/ops/attention.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,76 @@
from ..jit.core import compile_ops, is_experimental_enabled
from ..jit.utils.chip_info import get_cu_num, get_gfx

try:
from aiter.ops.flydsl import pa_decode as _pa_decode_flydsl
except (ImportError, AttributeError, RuntimeError, OSError):
_pa_decode_flydsl = None

MD_NAME = "module_attention"


def pa_decode_flydsl(
output: torch.Tensor,
query: torch.Tensor,
key_cache: torch.Tensor,
value_cache: torch.Tensor,
context_lengths: torch.Tensor,
block_tables: torch.Tensor,
softmax_scale: float,
query_length: int,
max_context_partition_num: int,
context_partition_size: int = 256,
compute_type: torch.dtype = torch.bfloat16,
query_scale: torch.Tensor = None,
key_scale: torch.Tensor = None,
value_scale: torch.Tensor = None,
exp_sums: torch.Tensor = None,
max_logits: torch.Tensor = None,
temporary_output: torch.Tensor = None,
alibi_slopes: torch.Tensor = None,
sinks: torch.Tensor = None,
sliding_window: int = 0,
ps: bool = True,
) -> None:
if _pa_decode_flydsl is None:
raise RuntimeError("pa_decode_flydsl requires the `flydsl` package")
_pa_decode_flydsl(
output,
query,
key_cache,
value_cache,
context_lengths,
block_tables,
softmax_scale,
query_length,
max_context_partition_num,
context_partition_size,
compute_type,
query_scale,
key_scale,
value_scale,
exp_sums,
max_logits,
temporary_output,
alibi_slopes,
sinks,
sliding_window,
ps,
)


direct_register_custom_op(
"pa_decode_gluon",
pa_decode_gluon,
["output", "exp_sums", "max_logits", "temporary_output"],
)

direct_register_custom_op(
"pa_decode_flydsl",
pa_decode_flydsl,
["output", "exp_sums", "max_logits", "temporary_output"],
)


def gen_pa_fwd_native_fake(
# [num_seqs, num_heads, head_size]
Expand Down
2 changes: 2 additions & 0 deletions aiter/ops/flydsl/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@
".gather_kv_b_proj",
"gather_kv_b_proj_flydsl_supported",
),
"pa_decode": (".pa_decode", "pa_decode"),
}

__all__ = [
Expand All @@ -104,6 +105,7 @@
"flydsl_qk_norm_rope_quant",
"gather_kv_b_proj_flydsl",
"gather_kv_b_proj_flydsl_supported",
"pa_decode",
]


Expand Down
23 changes: 23 additions & 0 deletions aiter/ops/flydsl/kernels/dpp_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,26 @@ def update_dpp_i32(
[],
**kw,
)


def dpp_xor_f32(src, offset: int, **kw):
"""Return ``src`` from the lane selected by a 16-lane XOR DPP pattern."""
from flydsl._mlir.dialects import arith as _arith_dialect
from flydsl.expr.typing import T

src_i32 = _to_ir(src).bitcast(T.i32)
if offset == 8:
out_i32 = update_dpp_i32(src_i32, src_i32, 280, 0xF, 0xC, False, **kw)
out_i32 = update_dpp_i32(out_i32, src_i32, 264, 0xF, 0x3, False, **kw)
elif offset == 4:
out_i32 = update_dpp_i32(src_i32, src_i32, 276, 0xF, 0xA, False, **kw)
out_i32 = update_dpp_i32(out_i32, src_i32, 260, 0xF, 0x5, False, **kw)
elif offset == 2:
out_i32 = update_dpp_i32(src_i32, src_i32, 78, 0xF, 0xF, False, **kw)
elif offset == 1:
out_i32 = update_dpp_i32(src_i32, src_i32, 177, 0xF, 0xF, False, **kw)
else:
raise ValueError(
f"dpp_xor_f32 only supports 16-lane offsets 1, 2, 4, 8; got {offset}"
)
return _arith_dialect.BitcastOp(T.f32, out_i32).result
249 changes: 249 additions & 0 deletions aiter/ops/flydsl/kernels/pa_decode_reduce.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
# SPDX-License-Identifier: Apache-2.0
# Copyright (C) 2025-2026 FlyDSL Project Contributors

"""Partitioned-softmax reduction kernel for FlyDSL paged attention."""

from functools import lru_cache

import flydsl.compiler as flyc
import flydsl.expr as fx
from flydsl.expr.typing import T


@lru_cache(maxsize=256)
def compile_pa_decode_ps_reduce(
*,
max_context_partition_num: int,
head_size: int,
output_dtype_str: str,
logits_dtype_str: str,
sink_dtype_str: str,
use_sinks: bool,
):
"""Build the partitioned-softmax reduction used by ``pa_decode``.

Each wave reduces the partition statistics independently, then reuses the
per-partition weights to combine one output element per thread. The host
API limits the partition count to one wave, so the LDS path from the more
general Gluon implementation is unnecessary here.
"""
if not 1 <= max_context_partition_num <= 64:
raise ValueError(
"max_context_partition_num must be in [1, 64], "
f"got {max_context_partition_num}"
)
if head_size <= 0 or head_size > 1024 or head_size % 64:
raise ValueError(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This breaks Gluon’s Triton fallback for configurations unsupported by this reducer: the caller only catches ImportError. On gfx950, I reproduced this through pa_decode_gluon with BF16, head_size=96, ps=True, 4 partitions, and the C++ reducer disabled. The same input succeeds with FlyDSL disabled.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the concrete repro. Confirmed: compile_pa_decode_ps_reduce raises ValueError for head_size=96 while the Gluon dispatch only treats ImportError as unsupported, so the Triton fallback is bypassed. I will convert unsupported-configuration failures at the FlyDSL adapter boundary to ImportError without broadly swallowing internal ValueError exceptions, and add a fallback regression for the BF16/D96/NP4 case. I will keep this thread open until the code and test land.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Implemented in f4f8431. The Gluon wrapper now capability-checks the optional FlyDSL reducer before selecting it, so BF16/D96/NP4 falls through to Triton. Direct FlyDSL compile validation still raises ValueError, and errors after a supported FlyDSL reducer is selected are no longer swallowed. Added regressions for both D96 fallback and propagation of an internal ImportError on a supported D128 configuration.

f"head_size must be a multiple of 64 in [64, 1024], got {head_size}"
)

dtype_map = {
"f32": fx.Float32,
"f16": fx.Float16,
"bf16": fx.BFloat16,
}
try:
output_dtype = dtype_map[output_dtype_str]
logits_dtype = dtype_map[logits_dtype_str]
sink_dtype = dtype_map[sink_dtype_str]
except KeyError as exc:
raise ValueError(f"Unsupported FlyDSL dtype: {exc.args[0]!r}") from exc

warp_size = 64
log2e = 1.4426950408889634
reduce_width = (
1
if max_context_partition_num == 1
else 1 << ((max_context_partition_num - 1).bit_length())
)
reduce_shuffle_offsets = [
offset for offset in (32, 16, 8, 4, 2, 1) if offset < reduce_width
]

@flyc.kernel(known_block_size=[head_size, 1, 1])
def pa_decode_ps_reduce_kernel(
output_ptr: fx.Pointer,
exp_sums_ptr: fx.Pointer,
max_logits_ptr: fx.Pointer,
logits_ptr: fx.Pointer,
sink_token_ptr: fx.Pointer,
stride_output_bs: fx.Int32,
stride_output_len: fx.Int32,
stride_output_kv_head: fx.Int32,
stride_output_group_size: fx.Int32,
stride_exp_sums_seq: fx.Int32,
stride_exp_sums_head: fx.Int32,
stride_exp_sums_part: fx.Int32,
stride_logits_seq: fx.Int32,
stride_logits_head: fx.Int32,
stride_logits_part: fx.Int32,
stride_logits_group: fx.Int32,
query_group_size: fx.Int32,
):
tid = fx.thread_idx.x
batch_idx = fx.block_idx.x
kv_head_idx = fx.block_idx.y
eqgs_idx = fx.block_idx.z

output = fx.recast_iter(output_dtype, output_ptr)
exp_sums = fx.recast_iter(fx.Float32, exp_sums_ptr)
max_logits = fx.recast_iter(fx.Float32, max_logits_ptr)
logits = fx.recast_iter(logits_dtype, logits_ptr)
if fx.const_expr(use_sinks):
sink_token = fx.recast_iter(sink_dtype, sink_token_ptr)

zero_f = fx.Float32(0.0)
one_f = fx.Float32(1.0)
neg_inf = fx.Float32(float("-inf"))
c_log2e = fx.Float32(log2e)
zero_i = fx.Int32(0)
c_warp_size = fx.Int32(warp_size)
c_wave_mask = fx.Int32(warp_size - 1)
c_part_num = fx.Int32(max_context_partition_num)
c_reduce_width = fx.Int32(reduce_width)
c_four = fx.Int32(4)
c_qgs = query_group_size
lane = tid & c_wave_mask
group_idx = eqgs_idx % c_qgs

def _wave_reduce_max(value):
reduced = value
for offset in reduce_shuffle_offsets:
reduced = reduced.maximumf(
reduced.shuffle_xor(fx.Int32(offset), c_warp_size)
)
return reduced

def _wave_reduce_sum(value):
reduced = value
for offset in reduce_shuffle_offsets:
reduced = fx.Float32(
reduced.addf(
reduced.shuffle_xor(fx.Int32(offset), c_warp_size),
fastmath="fast",
)
)
return reduced

lane_in_range = lane < c_part_num
lane_in_reduce = lane < c_reduce_width
part_sum = zero_f
part_max = neg_inf
if lane_in_reduce:
part_idx = lane_in_range.select(lane, zero_i)
stats_offset = (
batch_idx * stride_exp_sums_seq
+ kv_head_idx * stride_exp_sums_head
+ part_idx * stride_exp_sums_part
+ eqgs_idx
)
loaded_sum = fx.Float32(exp_sums[stats_offset])
loaded_max = fx.Float32(max_logits[stats_offset])
part_sum = lane_in_range.select(loaded_sum, zero_f)
part_max = lane_in_range.select(loaded_max, neg_inf)

global_max = _wave_reduce_max(part_max)
safe_global_max = (global_max > neg_inf).select(global_max, zero_f)
part_scale = (part_max > neg_inf).select(
fx.exp2((part_max - safe_global_max) * c_log2e, fastmath="fast"),
zero_f,
)
scaled_sum = part_sum * part_scale
global_exp_sum = _wave_reduce_sum(scaled_sum)
if fx.const_expr(use_sinks):
sink_value = fx.Float32(sink_token[kv_head_idx * c_qgs + group_idx])
sink_scale = (global_max > neg_inf).select(
fx.exp2(
(sink_value - safe_global_max) * c_log2e,
fastmath="fast",
),
zero_f,
)
global_exp_sum = global_exp_sum + sink_scale
safe_global_exp_sum = (global_exp_sum > zero_f).select(global_exp_sum, one_f)
weight_local = scaled_sum / safe_global_exp_sum
weight_local_i32 = weight_local.bitcast(fx.Int32)

acc = zero_f
for part_idx in fx.range_constexpr(max_context_partition_num):
c_part_idx = fx.Int32(part_idx)
weight_i32 = fx.Int32(
fx.rocdl.ds_bpermute(
T.i32,
c_part_idx * c_four,
weight_local_i32,
)
)
weight = weight_i32.bitcast(fx.Float32)
logits_offset = (
batch_idx * stride_logits_seq
+ kv_head_idx * stride_logits_head
+ c_part_idx * stride_logits_part
+ eqgs_idx * stride_logits_group
+ tid
)
part_logits = fx.Float32(logits[logits_offset])
acc = acc + part_logits * weight

query_idx = eqgs_idx // c_qgs
output_offset = (
batch_idx * stride_output_bs
+ query_idx * stride_output_len
+ kv_head_idx * stride_output_kv_head
+ group_idx * stride_output_group_size
+ tid
)
output[output_offset] = acc.to(output_dtype)

@flyc.jit
def launch_pa_decode_ps_reduce_kernel(
output: fx.Pointer,
exp_sums: fx.Pointer,
max_logits: fx.Pointer,
logits: fx.Pointer,
sink_token: fx.Pointer,
stride_output_bs: fx.Int32,
stride_output_len: fx.Int32,
stride_output_kv_head: fx.Int32,
stride_output_group_size: fx.Int32,
stride_exp_sums_seq: fx.Int32,
stride_exp_sums_head: fx.Int32,
stride_exp_sums_part: fx.Int32,
stride_logits_seq: fx.Int32,
stride_logits_head: fx.Int32,
stride_logits_part: fx.Int32,
stride_logits_group: fx.Int32,
query_seq_len: fx.Int32,
query_group_size: fx.Int32,
batch_size: fx.Int32,
num_kv_heads: fx.Int32,
stream: fx.Stream,
):
pa_decode_ps_reduce_kernel(
output,
exp_sums,
max_logits,
logits,
sink_token,
stride_output_bs,
stride_output_len,
stride_output_kv_head,
stride_output_group_size,
stride_exp_sums_seq,
stride_exp_sums_head,
stride_exp_sums_part,
stride_logits_seq,
stride_logits_head,
stride_logits_part,
stride_logits_group,
query_group_size,
).launch(
grid=(batch_size, num_kv_heads, query_seq_len * query_group_size),
block=(head_size, 1, 1),
stream=stream,
)

return {
"launch": launch_pa_decode_ps_reduce_kernel,
"kernel": pa_decode_ps_reduce_kernel,
}
Loading
Loading