@@ -296,11 +296,83 @@ def pa_decode_ps_launch(
296296 """
297297 num_query_heads = query .shape [1 ]
298298 num_kv_heads = key_cache .shape [1 ]
299- trans_v = len (value_cache .shape ) == 5
300- query_input_dtype = _get_query_input_dtype (query )
301-
299+ batch_size = context_lengths .shape [0 ]
300+ if query .shape [0 ] % batch_size != 0 :
301+ raise ValueError (f"query.shape[0] ({ query .shape [0 ]} ) must be divisible by " f"batch_size ({ batch_size } )" )
302+ if num_query_heads % num_kv_heads != 0 :
303+ raise ValueError (f"num_query_heads ({ num_query_heads } ) must be divisible by " f"num_kv_heads ({ num_kv_heads } )" )
304+ query_length = query .shape [0 ] // batch_size
305+ query_group_size = num_query_heads // num_kv_heads
306+ head_size = query .shape [- 1 ]
307+ value_head_size = output .shape [- 1 ]
308+ block_size = key_cache .shape [- 2 ]
302309 dev = query .device
303310 is_graph_capturing = _is_current_stream_capturing ()
311+ s = stream or torch .cuda .current_stream ()
312+
313+ if max_context_partition_num <= 0 :
314+ raise ValueError ("max_context_partition_num must be positive." )
315+
316+ # Small physical pages use the standalone tile kernel. Dispatch before the
317+ # FP8 metadata/SW setup so BF16 KV stays unscaled and asymmetric V uses the
318+ # tile wrapper's value-sized workspace allocation and validation.
319+ if block_size in _PA_DECODE_PS_SMALL_BLOCK_SIZES and sliding_window == 0 :
320+ if block_tables is None :
321+ raise ValueError (
322+ f"pa_decode_ps_launch: block_size={ block_size } requires `block_tables` "
323+ "(per-sequence physical block index table)."
324+ )
325+
326+ tile_key_scale = key_scale
327+ tile_value_scale = value_scale
328+ if key_cache .dtype != torch .bfloat16 :
329+ tile_key_scale = _prepare_scale_tensor (
330+ "key_scale" ,
331+ key_scale ,
332+ device = dev ,
333+ is_graph_capturing = is_graph_capturing ,
334+ )
335+ tile_value_scale = _prepare_scale_tensor (
336+ "value_scale" ,
337+ value_scale ,
338+ device = dev ,
339+ is_graph_capturing = is_graph_capturing ,
340+ )
341+ if tile_key_scale .ndim > 1 :
342+ num_blocks = key_cache .shape [0 ]
343+ tile_key_scale = tile_key_scale .reshape (num_blocks , num_kv_heads , block_size )
344+ tile_value_scale = tile_value_scale .reshape (num_blocks , num_kv_heads , block_size )
345+
346+ pa_decode_tile (
347+ output ,
348+ query ,
349+ key_cache ,
350+ value_cache ,
351+ block_tables ,
352+ context_lengths ,
353+ tile_key_scale ,
354+ tile_value_scale ,
355+ softmax_scale = softmax_scale ,
356+ stream = s ,
357+ num_partitions = max_context_partition_num ,
358+ pmax = max_logits ,
359+ psum = exp_sums ,
360+ pout = temporary_output ,
361+ )
362+ return "ps_small_block"
363+
364+ is_bf16_kv = key_cache .dtype == torch .bfloat16
365+ is_asymmetric = value_head_size != head_size
366+ if is_bf16_kv or is_asymmetric :
367+ unsupported_path = "sliding-window" if sliding_window > 0 else "page-1024 metadata"
368+ raise ValueError (
369+ "BF16 KV and asymmetric value dimensions currently require "
370+ "block_size 16 or 64 with sliding_window=0; "
371+ f"the { unsupported_path } path is not supported."
372+ )
373+
374+ trans_v = len (value_cache .shape ) == 5
375+ query_input_dtype = _get_query_input_dtype (query )
304376
305377 key_scale = _prepare_scale_tensor (
306378 "key_scale" ,
@@ -319,28 +391,16 @@ def pa_decode_ps_launch(
319391 # token), which enables the per-token K/V path in the metadata kernel.
320392 per_token_kv = key_scale .ndim > 1
321393
322- query_length = query .shape [0 ] // context_lengths .shape [0 ]
323- query_group_size = num_query_heads // num_kv_heads
324- batch_size = context_lengths .shape [0 ]
325- head_size = query .shape [- 1 ]
326-
327394 # Strides for key_scale/value_scale
328395 stride_ks_block = key_scale .stride (0 ) if per_token_kv else 0
329396 stride_ks_head = key_scale .stride (1 ) if per_token_kv else 0
330397
331- s = stream or torch .cuda .current_stream ()
332-
333- if max_context_partition_num <= 0 :
334- raise ValueError ("max_context_partition_num must be positive for sliding-window decode." )
335398 if is_graph_capturing and (exp_sums is None or max_logits is None or temporary_output is None ):
336399 raise ValueError (
337400 "CUDA graph capture requires preallocated `exp_sums`, `max_logits`, "
338401 "and `temporary_output` for the sliding-window path."
339402 )
340- # ── small-block (block_size 16/64) → tile kernel ──
341- # Key cache shape is [num_blocks, num_kv_heads, head_size // 16, block_size, 16].
342- block_size = key_cache .shape [- 2 ]
343- if block_size in _PA_DECODE_PS_SMALL_BLOCK_SIZES or sliding_window > 0 :
403+ if sliding_window > 0 :
344404 eqgs = query_length * query_group_size
345405 if exp_sums is None :
346406 exp_sums = torch .zeros (
@@ -454,48 +514,6 @@ def pa_decode_ps_launch(
454514 )
455515 return "ps_sw_partitioned"
456516
457- if block_size in _PA_DECODE_PS_SMALL_BLOCK_SIZES :
458- if block_tables is None :
459- raise ValueError (
460- f"pa_decode_ps_launch: block_size={ block_size } requires `block_tables` "
461- "(per-sequence physical block index table)."
462- )
463- if is_graph_capturing :
464- # Buffer sizes must be fixed ahead of capture and stay identical
465- # across every replay, so require the caller to have preallocated
466- # exp_sums/max_logits/temporary_output, exactly as the other PS
467- # paths already require.
468- if exp_sums is None or max_logits is None or temporary_output is None :
469- raise ValueError (
470- "CUDA graph capture requires preallocated `exp_sums`, `max_logits`, "
471- "and `temporary_output` for the tile-backed small-block PS path."
472- )
473- # pa_decode_tile requires an exact [num_blocks, num_kv_heads,
474- # block_size] per-token scale shape; callers here may pass an extra
475- # trailing singleton dim (e.g. from a pertoken-quant helper), which
476- # reshape away without changing the strides.
477- if per_token_kv :
478- num_blocks = key_cache .shape [0 ]
479- key_scale = key_scale .reshape (num_blocks , num_kv_heads , block_size )
480- value_scale = value_scale .reshape (num_blocks , num_kv_heads , block_size )
481- pa_decode_tile (
482- output ,
483- query ,
484- key_cache ,
485- value_cache ,
486- block_tables ,
487- context_lengths ,
488- key_scale ,
489- value_scale ,
490- softmax_scale = softmax_scale ,
491- stream = s ,
492- num_partitions = max_context_partition_num ,
493- pmax = max_logits ,
494- psum = exp_sums ,
495- pout = temporary_output ,
496- )
497- return "ps_small_block"
498-
499517 if metadata is None :
500518 if is_graph_capturing :
501519 raise ValueError (
0 commit comments