Prhub

#54292 [Perf] Pin CPU tensors before non_blocking H2D in three MM paths

原始 PR 作者 JasonKeyiL 合并时间 2026-08-29 11:13 文件变更 3 提交数 1 评论 3 代码增减 +5 / -11

执行摘要

三处 MM 路径 H2D 改为 async_tensor_h2d 以固定 host 张量

PR 描述指出,torch.from_numpy 返回的是 pageable CPU 张量,对其调用 .to(device, non_blocking=True) 时,CUDA 驱动会静默地通过固定缓冲区暂存,导致 host 阻塞。这个问题由 #53491 的增强同步检查暴露,同类 stall 在 #54266 中已针对 Qwen2-VL 修复;本 PR 覆盖 MM 路径上三个独立站点,延续同一修复模式。

值得精读,尤其是 async_tensor_h2d 的实现,它揭示了 vLLM 中 host 到设备传输的性能陷阱以及统一助手的必要性。可结合 #53491 和 #54266 了解整体迁移路径。

讨论亮点

review 过程简单:Claude Bot 因 fork 自动 review 被禁用,仅提示维护者可手动触发;维护者 njhill 直接批准,无具体讨论内容。

实现拆解

实现分为三步:

  1. 在 vllm/model_executor/layers/attention/mm_encoder_attention.py 中,将 maybe_compute_seq_lens 内 torch.from_numpy(sequence_lengths).to(device, non_blocking=True) 替换为 async_tensor_h2d(sequence_lengths, device=device)。该函数仅用于 FlashInfer 后端,构造 sequence_lengths 后立即 H2D。
  2. 在 vllm/model_executor/models/kanana_v.py 的 CustomQwen2VLVE.forward 中,对 cu_seqlens 的 H2D 做同样替换,async_tensor_h2d 会自动将 numpy 数组转换为 tensor 并固定内存。
  3. 在 vllm/model_executor/models/kimi_k25_vit.py 的 prepare_encoder_cudagraph_metadata 中,将 merge_gather_idx 的 H2D 替换为 async_tensor_h2d。
    每处都新增了 from vllm.utils.torch_utils import async_tensor_h2d 导入。改动仅涉及调用替换,不改变数值与 dtype,无测试或配置配套变更。
文件 模块 状态 重要度
vllm/model_executor/layers/attention/mm_encoder_attention.py 注意力层 modified 5.57
vllm/model_executor/models/kanana_v.py Kanana-V 模型 modified 5.94
vllm/model_executor/models/kimi_k25_vit.py Kimi-ViT 模型 modified 5.85

关键符号

maybe_compute_seq_lens CustomQwen2VLVE.forward prepare_encoder_cudagraph_metadata

关键源码片段

vllm/model_executor/models/kanana_v.py data-contract

Kanana-V 视觉塔 forward 中 cu_seqlens 的 H2D 路径被修复,直接影响 Kanana-V 模型的多模态编码性能。

        # Compute cu_seqlens in numpy then move to device, same as base model.
        cu_seqlens = np.repeat(
            grid_thw_np[:, 1] * grid_thw_np[:, 2],
            grid_thw_np[:, 0],
        ).cumsum(axis=0, dtype=np.int32)
        cu_seqlens = np.concatenate([np.zeros(1, dtype=np.int32), cu_seqlens])
        # 使用 async_tensor_h2d 而非 torch.from_numpy(...).to(non_blocking=True) 以避免 host 阻塞
        cu_seqlens = async_tensor_h2d(cu_seqlens, device=self.device)
vllm/model_executor/models/kimi_k25_vit.py data-contract

Kimi-K2.5 视觉 CUDA graph 元数据 merge_gather_idx 的 H2D 路径被修复,影响 CUDA graph 捕获时的性能。

    def prepare_encoder_cudagraph_metadata(
        self,
        grid_thw_list: list[list[int]],
        *,
        max_batch_size: int,
        max_seqlen_override: int | None = None,
        device: torch.device,
    ) -> dict[str, torch.Tensor | None]:
        """Precompute fixed-buffer metadata for image encoder CUDA graphs."""
        grid_thw_list = [list(map(int, grid)) for grid in grid_thw_list]
        metadata = self.encoder.prepare_encoder_metadata(
            grid_thw_list,
            device=device,
            max_batch_size=max_batch_size,
            max_seqlen_override=max_seqlen_override,
        )
        metadata["pos_embeds"] = self.patch_embed.pos_emb.get_pos_embeds(
            grid_thw_list
        ).to(device=device)
        merge_gather_idx = build_image_merge_gather_idx(
            grid_thw_list, self.merge_kernel_size
        )
        # 关键改动:使用 async_tensor_h2d 固定 host 缓冲区,避免非阻塞 H2D 阻塞 host
        metadata["merge_gather_idx"] = async_tensor_h2d(merge_gather_idx, device=device)
        return metadata

评论区精华

Claude review 因 fork 禁用 other

Claude Bot 表示 fork PR 自动 review 被禁用,需维护者手动触发。

结论:未产生额外讨论,维护者 njhill 直接批准。 · 已解决

风险与影响

风险极低:三处改动均为等价调用替换,不改变张量值、dtype 或计算逻辑。潜在风险为 async_tensor_h2d 的默认 dtype 推断可能与原 torch.from_numpy 有差异,但助手函数会保留 numpy dtype;另外若目标设备不支持 pin memory 或该助手在非 CUDA 平台处理有差异,需关注,但本 PR 涉及路径均为 CUDA。

影响范围:Kanana-V、Kimi-K2.5 以及所有使用 FlashInfer MMEncoderAttention 的模型。通过消除 H2D 的 host 阻塞,可降低多模态推理中编码器前向的延迟,尤其在批量处理时收益明显。对用户无感知变化,对团队是低风险性能优化。

无测试覆盖 核心路径变更 跨模块影响

关联 Issue

#54266 [Perf] Pin Qwen VL rotary position IDs before H2D

完整报告

参与讨论