Prhub

#46290 [ROCm][P/D] Fix MoRIIO WRITE mode for mixed KV layouts

原始 PR 作者 tanpinsiang 合并时间 2026-06-23 12:12 文件变更 6 提交数 12 评论 5 代码增减 +1104 / -195

执行摘要

修复 MoRIIO WRITE 混合 KV 布局正确性问题

如 PR 描述所述,前一个 PR #46039 使 MoRIIO READ 模式支持了混合 KV 缓存布局,但 WRITE 模式仍存在两个正确性问题:

1) 跨层重用了一个 request-wide 的 offset tuple,而不同层(如密集 K/V 层 vs 仅索引层)可能有不同的缓存几何;
2) 计数注册的缓存张数而非实际由 WRITE hook 调度的层来完成 WRITE。本 PR 针对这些问题进行修复,确保混合缓存布局下 WRITE 的正确性。

值得精读:此 PR 展示了如何在不改动上层调度逻辑的前提下,在分布式 KV 传输中处理异构缓存几何。moriio_layout.py 中的内核块布局检测算法和 moriio_engine.py 中的写入完成跟踪机制值得借鉴。使用 MoRIIO WRITE 模式的团队应关注此 PR 的变更。

讨论亮点

Review 中仅有一条来自 depthfirst-app[bot] 的自动安全扫描评论,指出 _send_transfer_release 方法中 hostport 来自用户控制的 kv_transfer_params,可能被攻击者利用连接到内部服务,严重性为 MEDIUM,建议增加验证。该评论未在合并前得到明确回复或修改,但 PR 已合并,说明团队可能评估认为该风险在 MoRIIO 使用场景中可控。

实现拆解

  1. 按几何计算偏移量(moriio_layout.py:新增 _spec_dim_matches_kernel_layout_matches_select_kernel_block_layout 函数,自动检测内核块 KV 缓存几何(支持分离/交错布局),在 get_layer_transfer_geometry 中区分块大小所在轴,计算正确的 block_stridekernel_blocks_per_block

  2. 缓存 WRITE 偏移计划(moriio_engine.py:引入 _get_write_geometry_key 函数,基于张量形状、步长和 dtype 生成键;MoRIIOWriter 新增 _scheduled_writes_scheduled_layers_sealed_writes 字典,按 transfer_id 跟踪每个传输的调度写入层数和密封计数。schedule_write 返回 bool 标记是否真正调度,seal_pending_transfers 在模型前向后密封期望的写入数。

  3. 修复完成逻辑(moriio_engine.py:新增 _finalize_if_complete 检查调度写入是否全部完成,只有完成后才触发通知和释放。新增 _clear_transfer_state 清理传输状态,_is_transfer_terminal 检查传输是否已完成(避免重复调度)。

  4. 连接器侧配套(moriio_connector.py:实现 wait_for_save 非空(仅在 WRITE 模式下等待);新增 _send_transfer_release 发送结构化 release 消息,_release_write_prefill_blocks 在写入完成后释放预填充块。update_state_after_alloc 中从 kv_transfer_params 获取 remote 地址而非仅解析 request_id。

  5. 数据结构更新(moriio_common.pyRemoteAllocInfo 增加 writes_expectedcompletion_* 字段和 transfer_offsets 字典(按几何键缓存偏移量),支持完成通知去重。

  6. 单元测试(test_moriio_kv_layout.pytest_moriio_connector.py:新增 577 行和 77 行测试代码,覆盖偏移量计算、写入完成通知、结构化/普通消息处理、生产者块释放、内核块布局等场景。

文件 模块 状态 重要度
vllm/distributed/kv_transfer/kv_connector/v1/moriio/moriio_engine.py 写入引擎 modified 9.15
vllm/distributed/kv_transfer/kv_connector/v1/moriio/moriio_connector.py 连接器 modified 8.39
vllm/distributed/kv_transfer/kv_connector/v1/moriio/moriio_layout.py 传输布局 modified 8.25
tests/v1/kv_connector/unit/test_moriio_kv_layout.py 布局测试 modified 7.67
tests/v1/kv_connector/unit/test_moriio_connector.py 连接测试 modified 6.98

关键符号

_get_write_geometry_key schedule_write is_scheduled seal_pending_transfers _finalize_if_complete _send_transfer_release _release_write_prefill_blocks wait_for_save _spec_dim_matches _kernel_layout_matches _select_kernel_block_layout get_layer_transfer_geometry

关键源码片段

vllm/distributed/kv_transfer/kv_connector/v1/moriio/moriio_engine.py core-logic

核心写入引擎,新增按几何计算偏移量和写入完成跟踪。

# vllm/distributed/kv_transfer/kv_connector/v1/moriio/moriio_engine.py# WRITE mode 状态机关键数据结构# 基于张量 shape、stride、dtype 生成几何键,用于缓存偏移量
WriteGeometryKey = tuple[tuple[int, ...], tuple[int, ...], torch.dtype]def _get_write_geometry_key(kv_cache: torch.Tensor) -> WriteGeometryKey:
    return (tuple(kv_cache.shape), tuple(kv_cache.stride()), kv_cache.dtype)class MoRIIOWriter:
    def __init__(self, worker: "MoRIIOConnectorWorker"):
        # ... 其他属性
        self._write_state_lock = threading.Lock()
        # 每个 transfer_id 已调度的写入层数
        self._scheduled_writes: dict[TransferId, int] = defaultdict(int)
        # 每个 transfer_id 已调度的层名称集合(用于去重)
        self._scheduled_layers: dict[TransferId, set[str]] = defaultdict(set)
        # 每个 transfer_id 密封后的期望写入总数(在前向之后设置)
        self._sealed_writes: dict[TransferId, int] = {}
​
    def schedule_write(self, task: WriteTask) -> bool:
        """调度一个写入任务,返回是否真正调度(避免重复调度和已完成传输)"""
        self.ensure_worker_started()
        if self._is_transfer_terminal(task.transfer_id):
            return False
        with self._write_state_lock:
            if self._is_transfer_terminal(task.transfer_id):
                return False
            # 跳过同一层已被调度的情况
            if task.layer_name in self._scheduled_layers[task.transfer_id]:
                return False
            self._scheduled_layers[task.transfer_id].add(task.layer_name)
            self._scheduled_writes[task.transfer_id] += 1
        self._write_task_q.put(task)
        return True
​
    def seal_pending_transfers(self) -> None:
        """前向完成后的密封:将当前调度计数保存为 sealed 值"""
        with self._write_state_lock:
            for xfer_id, count in self._scheduled_writes.items():
                if xfer_id not in self._sealed_writes:
                    self._sealed_writes[xfer_id] = count
            # 尝试完成所有已密封传输
            for xfer_id in list(self._sealed_writes.keys()):
                self._finalize_if_complete(xfer_id)
vllm/distributed/kv_transfer/kv_connector/v1/moriio/moriio_connector.py core-logic

连接器层新增结构化 release 和预填充块释放逻辑。

# vllm/distributed/kv_transfer/kv_connector/v1/moriio/moriio_connector.pyclass MoRIIOConnectorScheduler:
    def _send_transfer_release(self, transfer_id: TransferId, host: str, port: int):
        """向远端发送结构化 release 消息,通知传输完成"""
        path = make_zmq_path("tcp", host, port)
        if path not in self.paths:
            ctx = zmq.Context.instance()
            sock = make_zmq_socket(
                ctx=ctx, path=path, socket_type=zmq.DEALER, bind=False
            )
            self.paths[path] = sock
        self.paths[path].send(
            msgpack.dumps({"type": "release", "transfer_id": transfer_id})
        )
​
    def _release_write_prefill_blocks(
        self, request_id: ReqId, params: dict[str, Any]
    ):
        """在写入完成后释放预填充块:获取远端地址并发送 release"""
        transfer_id = params.get("transfer_id")
        if transfer_id is None:
            logger.warning(
                "Cannot release WRITE prefill blocks for request %s: "
                "missing transfer_id", request_id
            )
            return
        remote_host = params.get("remote_host")
        remote_notify_port = params.get("remote_notify_port")
        if remote_host is None or remote_notify_port is None:
            # 回退到从 request_id 解析(传统方式)
            try:
                peer_zmq = get_peer_zmq_from_request_id(
                    request_id, is_producer=False
                )
                remote_host, _, remote_notify_port = parse_moriio_zmq_address(peer_zmq)
            except ValueError:
                logger.warning(...)
                return
        remote_notify_port = int(remote_notify_port)
        for tp_index in range(self.tp_size):
            target_port = remote_notify_port + get_port_offset(
                params.get("remote_dp_rank", 0), tp_index
            )
            self._send_transfer_release(transfer_id, remote_host, target_port)

评论区精华

ZMQ 连接安全风险 安全

depthfirst-app[bot] 指出 _send_transfer_release 根据用户控制的 host/port 发起连接,可能被 SSRF 利用。

结论:未在合并前修复或回复,但该函数接收的参数由调度器内部构造,外部攻击面有限。 · 待处理

风险与影响

  1. ZMQ 连接无验证(moriio_connector.py:399-410_send_transfer_release 根据用户提供的 host/port 发起连接,存在 SSRF 风险。但由于 MoRIIO 通常运行在受控集群内,且 kv_transfer_params 由调度器内部构造,外部攻击面较小。

  2. 新状态管理竞态(moriio_engine.py:引入 _write_state_lock 保护 _scheduled_writes_scheduled_layers_sealed_writes,但 _write_worker_loop 运行在独立线程中,与主线程通过 Queue 通信,需要确保锁顺序一致以防止死锁。

  3. 内核块布局自动检测误判(moriio_layout.py:_select_kernel_block_layout:当 shape[2]shape[3] 都匹配 spec 且值不同时会引发 ValueError,但若值相等则直接返回,可能掩盖布局歧义。

  4. 性能影响:按传输分离状态增加了字典查找和锁开销,但通常 WRITE 操作本身是 I/O 密集的,CPU 锁争用影响有限。

影响范围:仅限使用 MoRIIO WRITE 模式进行 P/D 分离推理的用户(主要运行在 AMD ROCm 上)。
影响程度:修复了混合 KV 缓存布局(如 MiniMax-M3 等模型)下的写入正确性,对这类模型是功能修复。内部重构不会影响 API 或外部接口。
回归风险:对仅使用 READ 模式或单一布局的用户无影响;单元测试覆盖了混合布局的主要路径,E2E 验证通过 GSM8K 和基准测试。

ZMQ 连接无验证 新状态管理竞态风险 内核块布局自动检测可能误判

关联 Issue

未识别关联 Issue

当前没有检测到明确关联的 Issue 链接,后续同步到相关引用后会出现在这里。

完整报告

参与讨论