1919from kernels .attention .pa_decode_swa import compile_pa_decode_sw , compile_pa_decode_sw_reduce
2020from kernels .attention .pa_decode_tile import pa_decode_tile
2121from kernels .attention .pa_metadata import compile_pa_decode_metadata
22+ from kernels .attention .pa_metadata_tuning import lookup_pa_metadata_grid_multiplier
2223from kernels .common .tensor_shim import _run_compiled
2324from kernels .common .utils import cdiv
2425
2526# ── Kernel geometry constants ────────────────────────────────────────
2627KV_COMPUTE_BLOCK = 256 # tile size (matches SP3 kTileKV)
27- # Persistent-grid oversubscription for the metadata decode path: launch
28- # CU_count * this many workgroups so the HW keeps multiple workgroups resident
29- # per CU (memory-latency hiding). 1 = original (1 wg/CU).
30- _PA_METADATA_GRID_OVERSUB = 3
3128MFMA_N = 16
3229
33- _PACKED_FP8_QUERY_DTYPES = tuple (
34- dtype
35- for dtype in (
36- torch .uint8 ,
37- getattr (torch , "float8_e4m3fnuz" , None ),
38- getattr (torch , "float8_e4m3fn" , None ),
39- )
40- if dtype is not None
41- )
42-
4330
4431# =====================================================================
4532# Launch API — Persistent Scheduling mode
@@ -54,6 +41,9 @@ def get_pa_metadata(
5441 num_query_heads : int ,
5542 num_kv_heads : int ,
5643 partition_size : int = KV_COMPUTE_BLOCK ,
44+ * ,
45+ per_token_kv : bool | None = None ,
46+ grid_multiplier : int | None = None ,
5747):
5848 """Compute PA metadata (worklist, reduce maps) via get_pa_metadata_v1.
5949
@@ -69,6 +59,11 @@ def get_pa_metadata(
6959 NOTE: the consuming decode kernel must interpret kv_start/kv_end as partition
7060 indices accordingly.
7161
62+ Exact shape/device matches are loaded from FlyDSL's persistent Autotuner
63+ cache. Missing entries default to ``grid_multiplier=1``. ``per_token_kv``
64+ selects scale-mode-specific tuning, and ``grid_multiplier`` is the tuner's
65+ explicit candidate override.
66+
7267 Returns a dict with: work_indptr, work_info_flat, reduce_indptr,
7368 reduce_final_map, reduce_partial_map, num_sm, partial_output,
7469 partial_lse, stride_po_partial, stride_pl_partial.
@@ -81,13 +76,25 @@ def get_pa_metadata(
8176 head_size = query .shape [- 1 ]
8277
8378 props = torch .cuda .get_device_properties (dev )
84- # Oversubscribe the persistent grid: the decode kernel is memory-latency-bound
85- # and only ~3 workgroups/CU fit by VGPR, but the worklist defaults to 1 wg/CU
86- # (grid = CU count). Distributing work across num_cu = CU_count * OVERSUB bins
87- # (and launching that many workgroups) lets the HW keep multiple workgroups
88- # resident per CU → more waves in flight → better latency hiding.
89- base_cu = props .multi_processor_count
90- num_sm = base_cu * _PA_METADATA_GRID_OVERSUB
79+ num_blocks = key_cache .shape [0 ]
80+ if grid_multiplier is None and per_token_kv is not None :
81+ grid_multiplier = lookup_pa_metadata_grid_multiplier (
82+ num_cu = props .multi_processor_count ,
83+ batch_size = batch_size ,
84+ num_blocks = num_blocks ,
85+ query_length = query_length ,
86+ per_token_kv = per_token_kv ,
87+ num_query_heads = num_query_heads ,
88+ num_kv_heads = num_kv_heads ,
89+ head_dim = head_size ,
90+ block_size = key_cache .shape [- 2 ],
91+ device_tensor = query ,
92+ )
93+ if grid_multiplier is None :
94+ grid_multiplier = 1
95+ if grid_multiplier < 1 :
96+ raise ValueError ("grid_multiplier must be positive" )
97+ num_sm = props .multi_processor_count * grid_multiplier
9198 num_sm = (num_sm // num_kv_heads ) * num_kv_heads # keep divisible by num_kv_heads
9299
93100 seqlens_qo_indptr = torch .arange (batch_size + 1 , dtype = torch .int32 , device = dev ) * query_length
@@ -100,18 +107,14 @@ def get_pa_metadata(
100107 partition_indptr = torch .zeros (batch_size + 1 , dtype = torch .int32 , device = dev )
101108 partition_indptr [1 :] = torch .cumsum (_parts_per_batch , dim = 0 ).to (torch .int32 )
102109
103- block_size = key_cache .shape [- 2 ] if len (key_cache .shape ) == 5 else key_cache .shape [- 2 ]
104-
105110 (
106- (work_meta_data_size , work_meta_data_type ),
107111 (work_indptr_size , work_indptr_type ),
108112 (work_info_set_size , work_info_set_type ),
109113 (reduce_indptr_size , reduce_indptr_type ),
110114 (reduce_final_map_size , reduce_final_map_type ),
111115 (reduce_partial_map_size , reduce_partial_map_type ),
112116 ) = get_pa_metadata_info_v1 (batch_size , num_kv_heads , num_cu = num_sm )
113117
114- work_metadata_ptrs = torch .empty (work_meta_data_size , dtype = work_meta_data_type , device = dev )
115118 work_indptr = torch .empty (work_indptr_size , dtype = work_indptr_type , device = dev )
116119 work_info = torch .empty (work_info_set_size , dtype = work_info_set_type , device = dev )
117120 reduce_indptr = torch .empty (reduce_indptr_size , dtype = reduce_indptr_type , device = dev )
@@ -120,24 +123,18 @@ def get_pa_metadata(
120123
121124 get_pa_metadata_v1 (
122125 seqlens_qo_indptr ,
123- kv_indptr ,
124126 context_lengths ,
125- num_query_heads // num_kv_heads ,
126- num_kv_heads ,
127- True ,
128- work_metadata_ptrs ,
129127 work_indptr ,
130128 work_info ,
131129 reduce_indptr ,
132130 reduce_final_map ,
133131 reduce_partial_map ,
132+ query_group_size = num_query_heads // num_kv_heads ,
133+ num_kv_heads = num_kv_heads ,
134134 kv_granularity = partition_size ,
135- block_size = block_size ,
136- max_seqlen_qo = query_length ,
137- uni_seqlen_qo = query_length ,
138- fast_mode = True ,
139- max_split_per_batch = - 1 ,
135+ query_length = query_length ,
140136 num_cu = num_sm ,
137+ stream = torch .cuda .current_stream (dev ),
141138 )
142139
143140 # The FlyDSL get_pa_metadata_v1 produces the reduce_* maps natively
@@ -216,15 +213,11 @@ def _prepare_scale_tensor(
216213
217214
218215def _get_query_input_dtype (query : torch .Tensor ) -> str :
219- if query .dtype in _PACKED_FP8_QUERY_DTYPES :
220- return "packed_fp8"
221216 if query .dtype == torch .bfloat16 :
222217 return "bf16"
223218 if query .dtype == torch .float16 :
224219 return "f16"
225- raise ValueError (
226- f"Unsupported query dtype for pa_decode_ps_launch: { query .dtype } . " "Expected packed FP8/uint8, bf16, or f16."
227- )
220+ raise ValueError (f"Unsupported query dtype for pa_decode_ps_launch: { query .dtype } . Expected bf16 or f16." )
228221
229222
230223def _get_output_dtype_str (output : torch .Tensor ) -> str :
@@ -269,9 +262,8 @@ def get_recommended_splits(
269262 return max (4 , min (n , 8 ))
270263
271264
272- # Small block_size (16/64) is routed through the load-balanced worklist
273- # (metadata) path: `compile_pa_decode_metadata` gathers 256//block_size physical
274- # pages per 256-token partition, for both per-tensor and per-token KV quant.
265+ # Small block sizes use the standalone tile kernel; the metadata decode path
266+ # below is reserved for 1024-token physical pages.
275267_PA_DECODE_PS_SMALL_BLOCK_SIZES = (16 , 64 )
276268
277269
@@ -322,12 +314,6 @@ def pa_decode_ps_launch(
322314 device = dev ,
323315 is_graph_capturing = is_graph_capturing ,
324316 )
325- if query_input_dtype == "packed_fp8" :
326- raise ValueError (
327- "`pa_decode_ps_launch` no longer accepts host query_scale and only supports "
328- "bf16/f16 query inputs with kernel-internal query scale computation."
329- )
330-
331317 # Detect per-token vs per-tensor quantization from scale tensor
332318 # dimensionality: a >1-D scale tensor carries one scale per (block, head,
333319 # token), which enables the per-token K/V path in the metadata kernel.
@@ -516,7 +502,15 @@ def pa_decode_ps_launch(
516502 "CUDA graph capture requires precomputed `metadata`; "
517503 "call `get_pa_metadata()` before capture and pass it via `metadata=`."
518504 )
519- metadata = get_pa_metadata (query , key_cache , context_lengths , kv_indptr , num_query_heads , num_kv_heads )
505+ metadata = get_pa_metadata (
506+ query ,
507+ key_cache ,
508+ context_lengths ,
509+ kv_indptr ,
510+ num_query_heads ,
511+ num_kv_heads ,
512+ per_token_kv = per_token_kv ,
513+ )
520514
521515 work_indptr = metadata ["work_indptr" ]
522516 work_info_flat = metadata ["work_info_flat" ]
@@ -590,7 +584,7 @@ def pa_decode_ps_launch(
590584 max_seqlen_q = query_length ,
591585 final_output = output ,
592586 num_query_heads = num_query_heads ,
593- head_size = int (query .shape [- 1 ]),
587+ head_dim = int (query .shape [- 1 ]),
594588 stream = s ,
595589 )
596590
0 commit comments