From 425e1a209e8c8ce728112256500a1baabb44fe7d Mon Sep 17 00:00:00 2001 From: nv-guomingz <137257613+nv-guomingz@users.noreply.github.com> Date: Fri, 17 Jul 2026 14:49:16 +0800 Subject: [PATCH] [https://nvbugs/6395830][fix] Qwen-VL mRoPE: move seq-slot delta cache update to model device Text-only startup of the Qwen3.6-27B dense VL checkpoint crashes during KV-cache capacity estimation in the shared Qwen-VL mRoPE path (`_prepare_qwen_vl_mrope_config`): mrope_position_deltas_cache.index_copy_(0, seq_slots, deltas) RuntimeError: Expected all tensors to be on the same device, but got source is on cpu, different from other tensors on cuda:0 Root cause: checkpoints carrying `language_model_only: false` with the dense `Qwen3_5ForConditionalGeneration` arch are routed to `QwenImageBenchModel`, whose `multimodal_data_device_paths` was missing the `mrope_config.mrope_position_ids` / `mrope_config.mrope_position_deltas` entries that its sibling `_Qwen3_5VLModel` lists. The engine's H2D move (`MultimodalParams.to_device` with `target_keywords`) therefore skips the mRoPE tensors, leaving the deltas CPU-resident when the GPU seq-slot cache write (introduced in #11943) consumes them. MoE VL checkpoints (`Qwen3_5MoeForConditionalGeneration`) never match the image-bench route and are unaffected. Fix both layers: - `QwenImageBenchModel.multimodal_data_device_paths`: add the two `mrope_config.*` entries, matching `_Qwen3_5VLModel`, so mRoPE tensors ride the engine's pinned async H2D move like every other Qwen-VL model. - `_prepare_qwen_vl_mrope_config`: normalize `deltas` onto the cache device before `index_copy_` as a defensive backstop for any path that still reaches the write branch with CPU tensors (no-op when devices already match). Signed-off-by: nv-guomingz <137257613+nv-guomingz@users.noreply.github.com> --- tensorrt_llm/_torch/models/modeling_qwen2vl.py | 9 ++++++++- tensorrt_llm/_torch/models/modeling_qwen_image_bench.py | 6 ++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/tensorrt_llm/_torch/models/modeling_qwen2vl.py b/tensorrt_llm/_torch/models/modeling_qwen2vl.py index 0a4107bd3d9b..2079bfdf3127 100644 --- a/tensorrt_llm/_torch/models/modeling_qwen2vl.py +++ b/tensorrt_llm/_torch/models/modeling_qwen2vl.py @@ -154,7 +154,14 @@ def _prepare_qwen_vl_mrope_config( if len(delta_tensors) != num_seq_slots: raise RuntimeError( "Missing MRoPE position deltas for seq-slot cache update") - deltas = torch.cat(delta_tensors, dim=0) + # `delta_tensors` originate from per-request `multimodal_data` and may + # be CPU-resident when the owning model's `multimodal_data_device_paths` + # does not cover `mrope_config.*` (or a path skips the engine's H2D + # move), while the seq-slot cache and `seq_slots` live on the model + # device. `index_copy_` requires all tensors on the same device. + deltas = torch.cat(delta_tensors, + dim=0).to(device=mrope_position_deltas_cache.device, + non_blocking=True) mrope_position_deltas_cache.index_copy_(0, seq_slots, deltas) if position_ids is not None \ diff --git a/tensorrt_llm/_torch/models/modeling_qwen_image_bench.py b/tensorrt_llm/_torch/models/modeling_qwen_image_bench.py index 92d3cccb5550..061d21bc0df0 100644 --- a/tensorrt_llm/_torch/models/modeling_qwen_image_bench.py +++ b/tensorrt_llm/_torch/models/modeling_qwen_image_bench.py @@ -40,10 +40,16 @@ class _QwenImageBenchModelMixin: @property def multimodal_data_device_paths(self) -> List[str]: + # Keep the mrope_config entries in sync with `_Qwen3_5VLModel` + # (modeling_qwen3_5.py): the shared Qwen-VL mRoPE seq-slot cache path + # consumes `mrope_position_deltas` on the model device, so the engine + # must move them H2D along with the rest of the multimodal payload. return [ "image.pixel_values", "video.pixel_values_videos", "multimodal_embedding", + "mrope_config.mrope_position_ids", + "mrope_config.mrope_position_deltas", ] @property