diff --git a/vllm/model_executor/kernels/linear/mixed_precision/hybrid_w4a16.py b/vllm/model_executor/kernels/linear/mixed_precision/hybrid_w4a16.py index 987ba349750f..f602b493f1a6 100644 --- a/vllm/model_executor/kernels/linear/mixed_precision/hybrid_w4a16.py +++ b/vllm/model_executor/kernels/linear/mixed_precision/hybrid_w4a16.py @@ -23,7 +23,7 @@ permute_param_layout_, ) from vllm.platforms import current_platform -from vllm.platforms.rocm import on_gfx1x, on_gfx1103, on_gfx1151 +from vllm.platforms.rocm import on_gfx1x, on_gfx1103, on_gfx1150, on_gfx1151 from vllm.scalar_type import scalar_types from vllm.triton_utils import tl, triton from vllm.utils.torch_utils import direct_register_custom_op @@ -275,6 +275,16 @@ def _select_skinny_gfx11_config( ) -> tuple[int, int, int, int]: """Return (BLOCK_M, BLOCK_N, BLOCK_K, num_warps) for the gfx11 skinny GEMM.""" if dtype == torch.float16: + # Profile-guided default for Qwen3-VL-like multimodal prefill. + qwen3_prefill_shapes = { + (19456, 2560), # gate_up_proj-like + (2560, 9728), # down_proj-like + (6144, 2560), # qkv_proj-like + (2560, 4096), # o_proj-like + } + if on_gfx1150() and 576 <= M <= 832 and (N, K) in qwen3_prefill_shapes: + return 64, 256, 64, 8 + # Packed-dequant path (fp16 on gfx1x). Cold-optimal tiers from a broad # rotating-buffer cudagraph sweep over the catalog (weights row-padded # exactly as production via pack_skinny_int4, so the K%4096 cliff is diff --git a/vllm/platforms/rocm.py b/vllm/platforms/rocm.py index 170a6e74f984..a1d35f7677be 100644 --- a/vllm/platforms/rocm.py +++ b/vllm/platforms/rocm.py @@ -193,6 +193,7 @@ def _get_gcn_arch() -> str: _ON_GFX11 = "gfx11" in _GCN_ARCH _ON_GFX1100 = "gfx1100" in _GCN_ARCH _ON_GFX1103 = "gfx1103" in _GCN_ARCH +_ON_GFX1150 = "gfx1150" in _GCN_ARCH _ON_GFX1151 = "gfx1151" in _GCN_ARCH _ON_GFX12X = any(arch in _GCN_ARCH for arch in ["gfx12"]) _ON_MI3XX = any(arch in _GCN_ARCH for arch in ["gfx942", "gfx950"]) @@ -288,6 +289,8 @@ def on_gfx1100() -> bool: def on_gfx1103() -> bool: return _ON_GFX1103 +def on_gfx1150() -> bool: + return _ON_GFX1150 def on_gfx1151() -> bool: return _ON_GFX1151 diff --git a/vllm/v1/attention/ops/triton_unified_attention.py b/vllm/v1/attention/ops/triton_unified_attention.py index 6dc583674a80..000e401f5210 100644 --- a/vllm/v1/attention/ops/triton_unified_attention.py +++ b/vllm/v1/attention/ops/triton_unified_attention.py @@ -750,6 +750,25 @@ def _is_gemma3_attention(head_size: int, sliding_window: int) -> bool: return sliding_window == 1024 and head_size in (128, 256) +def _is_qwen3_vl_4b_prefill_signature( + *, + head_size: int, + num_query_heads: int, + num_kv_heads: int, + max_seqlen_q: int, +) -> bool: + """Detect Qwen3-VL-4B text prefill attention shape. + + This signature is intentionally strict to avoid affecting unrelated models. + """ + return ( + head_size == 128 + and num_query_heads == 32 + and num_kv_heads == 8 + and max_seqlen_q >= 256 + ) + + def _get_tile_size( head_size: int, sliding_window: int, @@ -1088,15 +1107,35 @@ def unified_attention( grid: tuple[Any, ...] config = {} + tile_size_prefill = TILE_SIZE_PREFILL use_swapped_grid = not use_3d and current_platform.is_gfx1151() + # Optional tuning overrides for targeted profiling/experiments. + # These are intentionally no-op unless explicitly set. + if not use_3d: + # Qwen3-VL-4B prefill default on Navi/gfx11: + # BM64/T32/W4/S1/EU4 was the best end-to-end TTFT setting in local sweeps. + if current_platform.is_navi() and _is_qwen3_vl_4b_prefill_signature( + head_size=head_size, + num_query_heads=num_query_heads, + num_kv_heads=num_kv_heads, + max_seqlen_q=max_seqlen_q, + ): + BLOCK_M = 64 + BLOCK_Q = BLOCK_M // num_queries_per_kv + total_num_q_blocks = q.shape[0] // BLOCK_Q + num_seqs + tile_size_prefill = 32 + num_warps = 4 + num_stages = 1 + waves_per_eu = 4 + if not use_3d: if use_swapped_grid: grid = (num_kv_heads, total_num_q_blocks) else: grid = (total_num_q_blocks, num_kv_heads) - tile_size = TILE_SIZE_PREFILL + tile_size = tile_size_prefill else: tile_size = TILE_SIZE_DECODE