Skip to content

Commit 860faaf

Browse files
jli-melchiorclaude
andcommitted
opt(flydsl): adaptive ROWS_PER_WG for decode vs prefill in qk_norm_rope_quant gfx1250
R=32 (1024-thread workgroup) maximises waves/workgroup and is optimal for prefill, but at small token counts it launches too few workgroups (65*ceil(T/32)) to fill all 256 CUs, leaving them idle. Select the packing per launch by token count: T <= 96 (decode) uses R=16 to spread across more CUs; larger T keeps R=32. rows_per_wg threads through _build_kernel and the cached compile so the two variants coexist (distinct kernel names r16/r32). decode T=64 (kernel-trace min): 5.53us -> 5.33us; prefill unchanged (T=16384 ~440us). Correctness verified across the R boundary (T=1,2,33,64,96,97,128,512) for all quant configs, q_weight, and the kv_write smoke test. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 088ddc3 commit 860faaf

1 file changed

Lines changed: 25 additions & 0 deletions

File tree

aiter/ops/flydsl/kernels/qk_norm_rope_quant_gfx1250.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,19 @@ def _cached_from_dlpack(t: torch.Tensor):
109109
# overlaps one row's load->reduce->store latency with another row's memory ops
110110
# (the kernel is latency-bound on that chain, not bandwidth- or ALU-bound).
111111
# Each wave (thread_idx.y) still processes exactly one (head, token) row.
112+
#
113+
# Adaptive by token count (chosen per launch in the public API):
114+
# - Large T (prefill): ROWS_PER_WG (=32, full 1024-thread workgroup) maximises
115+
# waves/workgroup so few workgroups saturate 64 waves/CU.
116+
# - Small T (decode, T <= SMALL_T_THRESHOLD): a smaller R yields MORE
117+
# workgroups (grid = (H+1) * ceil(T/R)), spreading across more CUs when the
118+
# total row count is too small to fill the machine at R=32.
112119
ROWS_PER_WG = 32
120+
ROWS_PER_WG_SMALL = 16
121+
# At/below this token count, R=32 launches < ~256 workgroups (65*ceil(T/32)),
122+
# leaving CUs idle; the small-R variant fills more of them. 65*ceil(96/32)=195
123+
# vs 65*ceil(96/16)=390 -- the crossover where extra workgroups still help.
124+
SMALL_T_THRESHOLD = 96
113125

114126
# SQRT2 has no aiter dependency, so it stays at module level.
115127
_SQRT2 = math.sqrt(2.0)
@@ -260,6 +272,7 @@ def _build_kernel(
260272
scale_dtype: str,
261273
q_weighted: bool,
262274
kv_write: bool = False,
275+
rows_per_wg: int = ROWS_PER_WG,
263276
):
264277
"""Build the @flyc.kernel + @flyc.jit launcher for a given config.
265278
@@ -281,6 +294,9 @@ def _build_kernel(
281294
VEC = D // BLOCK_THREADS
282295
ROPE_THREAD_LO = NOPE // VEC
283296
PAIRS_PER_THREAD = VEC // 2
297+
# Local rebind so every ROWS_PER_WG reference below picks up the per-build
298+
# value (adaptive: R=32 for prefill, R=16 for small-T decode).
299+
ROWS_PER_WG = rows_per_wg
284300

285301
assert (
286302
D % BLOCK_THREADS == 0
@@ -909,6 +925,7 @@ def compile_flydsl_qk_norm_rope_quant_gfx1250(
909925
scale_dtype: str,
910926
q_weighted: bool,
911927
kv_write: bool = False,
928+
rows_per_wg: int = ROWS_PER_WG,
912929
):
913930
"""Compile (and cache) the gfx1250 wave32 launcher for a given config."""
914931
launcher = _build_kernel(
@@ -920,6 +937,7 @@ def compile_flydsl_qk_norm_rope_quant_gfx1250(
920937
scale_dtype=scale_dtype,
921938
q_weighted=q_weighted,
922939
kv_write=kv_write,
940+
rows_per_wg=rows_per_wg,
923941
)
924942
launcher.compile_hints = dict(_DEFAULT_COMPILE_HINTS)
925943
return launcher
@@ -1076,6 +1094,12 @@ def flydsl_qk_norm_rope_quant_gfx1250(
10761094
ssm_arg = q.new_empty(1, dtype=torch.int32)
10771095
bid_arg = q.new_empty(1, dtype=torch.int32)
10781096

1097+
# Adaptive workgroup packing: small-T (decode) launches too few workgroups
1098+
# at R=32 to fill all CUs, so use the small-R variant there; large-T
1099+
# (prefill) keeps R=32 for max waves/workgroup. Selected once per launch by
1100+
# total token count (chunking below stays within one regime for real cases).
1101+
rows_per_wg = ROWS_PER_WG_SMALL if T_tok <= SMALL_T_THRESHOLD else ROWS_PER_WG
1102+
10791103
launcher = compile_flydsl_qk_norm_rope_quant_gfx1250(
10801104
num_q_heads=H,
10811105
head_dim=D,
@@ -1085,6 +1109,7 @@ def flydsl_qk_norm_rope_quant_gfx1250(
10851109
scale_dtype=scale_dtype,
10861110
q_weighted=q_weighted,
10871111
kv_write=kv_write,
1112+
rows_per_wg=rows_per_wg,
10881113
)
10891114

10901115
if stream is None:

0 commit comments

Comments
 (0)