From 28e605fb330093fee5a2b98aebc19d9c268c1822 Mon Sep 17 00:00:00 2001 From: Harry Huang Date: Sat, 5 Sep 2026 22:02:20 +0800 Subject: [PATCH] [Bugfix][Qwen4Exp] fix state index strides in fused PLE conv (#55375) Signed-off-by: huanghaoyan.hhy --- tests/models/qwen4_exp/test_ple.py | 24 +++++++++++++++++++++++- vllm/models/qwen4_exp/nvidia/ops/ple.py | 10 ++++++++-- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/tests/models/qwen4_exp/test_ple.py b/tests/models/qwen4_exp/test_ple.py index d7747ebfbd..767f38898b 100644 --- a/tests/models/qwen4_exp/test_ple.py +++ b/tests/models/qwen4_exp/test_ple.py @@ -1077,6 +1077,8 @@ class _ConvBatchCase: dilation: int = 3 spec_query_len: int = 1 graph_padding: int = 0 + state_index_stride: int = 1 + include_null_state: bool = True def _make_conv_metadata( @@ -1123,9 +1125,18 @@ def _make_conv_metadata( dtype=torch.int32, device=device, ) + if case.state_index_stride > 1: + strided_indices = torch.full( + (non_spec_state_indices.numel(), case.state_index_stride), + NULL_BLOCK_ID, + dtype=torch.int32, + device=device, + ) + strided_indices[:, 0] = non_spec_state_indices + non_spec_state_indices = strided_indices[:, 0] if case.num_decodes > 1: non_spec_state_indices[case.num_decodes - 1] = NULL_BLOCK_ID - if num_prefills > 1: + if num_prefills > 1 and case.include_null_state: non_spec_state_indices[case.num_decodes + 1] = NULL_BLOCK_ID spec_query_start_loc = torch.tensor( @@ -1203,6 +1214,15 @@ def _make_conv_metadata( _ConvBatchCase(prefill_query_lens=(37, 0, 5, 128)), id="prefill", ), + pytest.param( + _ConvBatchCase( + prefill_query_lens=(16, 676), + channels=10240, + state_index_stride=4, + include_null_state=False, + ), + id="prefill-strided-state-indices", + ), pytest.param( _ConvBatchCase( spec_query_lens=(1, 4, 4, 0), @@ -1243,6 +1263,8 @@ def test_fused_conv_correctness( ) -> None: device = torch.device("cuda") metadata, num_real_tokens = _make_conv_metadata(case, device) + if case.state_index_stride > 1: + assert metadata.state_indices_tensor.stride(0) == case.state_index_stride module = Qwen4ExpPLELayer.__new__(Qwen4ExpPLELayer) nn.Module.__init__(module) module.conv_state_len = (case.kernel_size - 1) * case.dilation diff --git a/vllm/models/qwen4_exp/nvidia/ops/ple.py b/vllm/models/qwen4_exp/nvidia/ops/ple.py index 3d90773ded..d76e2705a7 100644 --- a/vllm/models/qwen4_exp/nvidia/ops/ple.py +++ b/vllm/models/qwen4_exp/nvidia/ops/ple.py @@ -330,6 +330,7 @@ def _ple_conv_kernel( has_token_map, num_reqs, bs_iters, + state_idx_stride, state_bs, state_ws, state_cs, @@ -386,7 +387,7 @@ def _ple_conv_kernel( else: slot_off = tl.full([], 0, tl.int32) - sid = tl.load(state_idx_ptr + r).to(tl.int64) + sid = tl.load(state_idx_ptr + r * state_idx_stride).to(tl.int64) state_ok = sid != NULL_STATE_ID sid_safe = tl.where(state_ok, sid, 0) if HAS_INIT: @@ -484,6 +485,7 @@ def _ple_conv_writeback_kernel( has_init_ptr, token_idx_ptr, has_token_map, + state_idx_stride, state_bs, state_ws, state_cs, @@ -501,7 +503,7 @@ def _ple_conv_writeback_kernel( c_offs = pid_c * BLOCK_C + tl.arange(0, BLOCK_C) c_mask = c_offs < C - sid = tl.load(state_idx_ptr + r).to(tl.int64) + sid = tl.load(state_idx_ptr + r * state_idx_stride).to(tl.int64) state_ok = sid != NULL_STATE_ID if not state_ok: return @@ -612,6 +614,8 @@ def ple_conv( num_warps = 4 if mode == "prefill" else 8 launch_pdl = current_platform.is_arch_support_pdl() + # Pure-prefill indices can be a strided block-table column view. + state_idx_stride = state_indices.stride(0) # Constexpr flags eliminate accesses to optional None arguments. Without a # token map, state_indices is an unused but device-resident placeholder. @@ -628,6 +632,7 @@ def ple_conv( token_indices is not None, num_reqs, binary_search_iters, + state_idx_stride, state_bs, state_ws, state_cs, @@ -654,6 +659,7 @@ def ple_conv( has_initial_states, token_indices if token_indices is not None else state_indices, token_indices is not None, + state_idx_stride, state_bs, state_ws, state_cs,