Prhub

#55353 [Deprecation] Deprecate items scheduled for 0.29

原始 PR 作者 yewentao256 合并时间 2026-09-11 23:17 文件变更 28 提交数 5 评论 14 代码增减 +62 / -305

执行摘要

清理 0.29 到期弃用项:移除隐式同步属性与旧配置项

PR body 只有一句 “Deprecate items scheduled for 0.29”,动机来自代码中已经写死的到期标记:vllm/config/multimodal.py 的 _get_mm_hasher_algorithm() 通过 get_from_deprecated_env_if_set("VLLM_MM_HASHER_ALGORITHM", "v0.27", "mm_hasher_algorithm") 做环境变量回退;vllm/config/cache.py 用 "v0.29" 标记 VLLM_PREFIX_CACHE_RETENTION_INTERVAL 回退;vllm/config/attention.py 的 use_fp4_indexer_cache 告警明确写着 “deprecated and will be removed in v0.29”;vllm/v1/attention/backend.py 的 seq_lens_cpu / num_computed_tokens_cpu 属性被 @deprecated 标注为 “Prefer using device seq_lens directly to avoid implicit H<>D sync which breaks full async scheduling”。也就是说,这些 API 一旦被误用就会引入隐式 GPU-CPU 同步,直接破坏 full async scheduling 的收益,因此到达承诺的删除窗口后需要彻底切断,而不是继续保留会静默降级的兼容层。

建议精读,但只精读三处,其余可略过:① vllm/v1/attention/backend.py 的契约收缩与 unpadded() 的替代传递路径,理解“为什么 CPU 侧长度必须显式获取”;② vllm/models/deepseek_v4/compressor.py 与 vllm/v1/attention/backends/mamba_attn.py 的 async_spec_decode 守卫写法——这是“乐观上界不可用于精确判断”的范式,任何新增读取 seq_lens_cpu_upper_bound 的内核都应照此判断;③ tests/v1/e2e/spec_decode/draft_model/test_async.py 用 VLLM_GPU_SYNC_CHECK=error 取代自建 fixture 的取舍。对使用方,需要在发布说明中明确列出被删除的环境变量与配置别名,并检查自有启动脚本中对 envs.VLLM_MM_HASHER_ALGORITHM 之类的直接引用。设计上值得讨论的遗留问题:ReplaySSM 的校验是否应前移到启动期参数校验,以及是否该给所有 seq_lens_cpu_upper_bound 消费点加统一断言。

讨论亮点

唯一实质性 review 交锋集中在测试改造上(tests/v1/e2e/spec_decode/draft_model/test_async.py:46):

coderabbitai[bot](标记 Major / Functional Correctness):“Update the test setup around the GPU sync check to patch gpu_sync_debug._SYNC_CHECK_MODE directly instead of only setting VLLM_GPU_SYNC_CHECK via monkeypatch.setenv. Apply the patch before exercising the single-GPU UniProcExecutor, preserving the "error" mode...”

mgoin 回复:“@yewentao256 I believe in CI we actually enable sync error by default, so this might not be needed”

讨论的实质是:新测试把断言责任交给 worker 侧读取的环境变量,如果 vllm 已在 fixture 阶段被导入、或 gpu_sync_debug 在导入时就把模式固化成 _SYNC_CHECK_MODE,那么 monkeypatch.setenv 可能不会生效,测试会退化成“只验证能跑通”。mgoin 认为 CI 已经默认开启 sync error,因此这行 setenv 可能多余——但这同时意味着该测试在本地开发环境下的同步断言强度更弱。最终 PR 未做修改即合并(sfeng33 直接 APPROVED,mgoin 的 COMMENTED 也落在同一行),属于“维护者接受现状、未形成明确结论”的状态。

实现拆解

  1. 收缩 CommonAttentionMetadata 的 CPU 侧契约(改动核心):在 vllm/v1/attention/backend.py 删除 _seq_lens_cpu、_num_computed_tokens_cpu 两个惰性字段与 seq_lens_cpu、num_computed_tokens_cpu 两个 @deprecated property,同时移除 typing_extensions.deprecated 导入;unpadded() 不再切片这两个私有字段,改为透传 seq_lens_cpu_upper_bound。保留 device 侧 compute_num_computed_tokens()(seq_lens - query_lens)与 token_to_req_indices() 缓存。影响:所有需要 CPU 长度的调用点要么显式 seq_lens.cpu(),要么接受 seq_lens_cpu_upper_bound 的乐观语义,否则必须自行加守卫。

  2. 生产者与消费者同步迁移:vllm/v1/worker/gpu_model_runner.py 不再构造 _seq_lens_cpu / _num_computed_tokens_cpu(并删掉 async spec decode 下把二者置 None 的特殊分支),只保留 seq_lens_cpu_upper_bound = self.optimistic_seq_lens_cpu[:num_reqs_padded];vllm/v1/worker/ubatch_utils.py::_make_metadata_with_slice 去掉两个字段的切片与 tokens_skipped 修正逻辑。其余读取方——vllm/v1/attention/backends/utils.py、vllm/v1/spec_decode/llm_base_proposer.py、vllm/v1/attention/backends/mla/indexer.py、vllm/v1/attention/backends/mla/rocm_aiter_mla_sparse.py、vllm/model_executor/models/whisper_causal.py、vllm/model_executor/layers/attention/cross_attention.py、vllm/distributed/kv_transfer/kv_connector/v1/offloading/scheduler.py、benchmarks/attention_benchmarks/mla_runner.py——同步删除对旧字段/旧属性的引用。

  3. 为“需要精确 CPU 长度”的两条路径加显式守卫:vllm/models/deepseek_v4/compressor.py::_get_c128_boundary 从读 metadata._num_computed_tokens_cpu 改为 seq_lens_cpu_upper_bound - query_lens 反推每个请求的 start;CompressorMetadataBuilder.build 新增 async_spec_decode = async_scheduling and speculative_config is not None,在该组合下直接把 c128_boundary 置 None,因为 upper bound 在 async spec decode 行上只是“假设 draft 全被接受”的乐观值。vllm/v1/attention/backends/mamba_attn.py::_compute_common_metadata 中 ReplaySSM 分支改为读取 seq_lens_cpu_upper_bound 并自算 query_lens_cpu 得到 num_computed_d,若 decode_base_cpu / seq_lens_cpu 缺失或处于 async spec decode,则抛 ValueError(文案改为 “requires exact CPU sequence lengths and decode-base counts”),把“用错长度”的静默风险改成显式失败。

  4. 配置与环境变量清理(对用户的破坏面):vllm/envs.py 删除 VLLM_MM_HASHER_ALGORITHM 与 VLLM_PREFIX_CACHE_RETENTION_INTERVAL 的声明、env_with_choices 注册和读取 lambda;vllm/config/multimodal.py 删除 _get_mm_hasher_algorithm(),mm_hasher_algorithm 直接默认 "blake3";vllm/config/cache.py 删除 _get_prefix_cache_retention_interval(),prefix_cache_retention_interval 改为 Field(default=0, ge=0);vllm/config/attention.py 删除 use_fp4_indexer_cache 别名、__post_init__ 中的告警与 True -> "mxfp4" 折叠逻辑,以及 compute_hash() 里的 ignored_factors={"use_fp4_indexer_cache"}(连带删除 init_logger/logger);vllm/platforms/rocm.py 移除 CUDA_VISIBLE_DEVICES → HIP_VISIBLE_DEVICES 的自动拷贝。三处默认值(blake3 / 0 / indexer_kv_dtype="auto")与旧行为一致,只有“通过环境变量或旧字段覆盖”的用法失效。

  5. 测试配套:tests/v1/e2e/spec_decode/draft_model/test_async.py 删除约 60 行手写 sync_tracker fixture(multiprocessing.Value 计数 + 直接替换 CommonAttentionMetadata.seq_lens_cpu property + 打印调用栈),改为 monkeypatch.setenv("VLLM_GPU_SYNC_CHECK", "error"),把“禁止隐式同步”的断言下沉到 worker 内置的 GPU sync check,同时删掉 torch._dynamo.reset() 与 subprocess 输出依赖;tests/engine/test_arg_utils.py 删除已失效的 test_prefix_cache_retention_interval_from_deprecated_env。整体上源码与测试同步收敛,没有新增正向功能测试。

文件 模块 状态 重要度
vllm/v1/attention/backend.py 注意力元数据 modified 7.44
vllm/models/deepseek_v4/compressor.py 压缩器后端 modified 6.47
vllm/v1/attention/backends/mamba_attn.py 线性注意力 modified 6.4
vllm/v1/worker/gpu_model_runner.py 模型执行器 modified 6.26
vllm/config/cache.py 缓存配置 modified 6.44
vllm/config/multimodal.py 多模态配置 modified 6.5
vllm/config/attention.py 注意力配置 modified 6.3
vllm/envs.py 环境变量 modified 5.9
vllm/platforms/rocm.py 平台适配 modified 5.71
tests/v1/e2e/spec_decode/draft_model/test_async.py 同步检测测试 modified 6.84
vllm/v1/worker/ubatch_utils.py 批切分工具 modified 5.8
tests/engine/test_arg_utils.py 参数测试 modified 4.87

关键符号

CommonAttentionMetadata.seq_lens_cpu CommonAttentionMetadata.num_computed_tokens_cpu CommonAttentionMetadata.compute_num_computed_tokens CommonAttentionMetadata.unpadded _get_c128_boundary CompressorMetadataBuilder.build _compute_common_metadata _make_metadata_with_slice _get_mm_hasher_algorithm _get_prefix_cache_retention_interval AttentionConfig.__post_init__ AttentionConfig.compute_hash test_no_sync_with_spec_decode test_prefix_cache_retention_interval_from_deprecated_env

关键源码片段

vllm/v1/attention/backend.py core-logic

本次契约收缩的中心:删除会触发隐式 D2H 同步的 seq_lens_cpu / num_computed_tokens_cpu 属性与私有字段,并让 unpadded() 改传 seq_lens_cpu_upper_bound,决定了所有下游 backend 如何获取 CPU 侧长度。

# vllm/v1/attention/backend.py —— 迁移后的最终形态(节选)
@dataclass
class CommonAttentionMetadata:
    # 旧的 _seq_lens_cpu / _num_computed_tokens_cpu 惰性字段已被删除,
    # 连带删除两个 @deprecated property:它们首次访问时会执行
    # seq_lens.to("cpu"),即一次隐式 D2H 同步,直接破坏 full async scheduling。
    # 现在想拿 CPU 侧序列长度只能显式调用 seq_lens.cpu(),
    # 或者使用下面这个语义为“上界”的 CPU 字段。
    seq_lens_cpu_upper_bound: torch.Tensor | None = None
    """(batch_size,) CPU upper bound on seq_lens. Precise for prefill rows
    and for all rows outside async spec decode; optimistic for async-spec
    decode rows (assumes every draft was accepted). Not safe for kernels
    that need exact per-row context lengths on decode rows."""
​
    _num_computed_tokens_cache: torch.Tensor | None = None
    _token_to_req_indices_cache: torch.Tensor | None = None
​
    # 保留下来的 device 侧实现:全程不碰 CPU,供注意力内核直接使用。
    def compute_num_computed_tokens(self) -> torch.Tensor:
        """Compute num_computed_tokens on device (seq_lens - query_lens)."""
        if self._num_computed_tokens_cache is None:
            query_lens = self.query_start_loc[1:] - self.query_start_loc[:-1]
            self._num_computed_tokens_cache = self.seq_lens - query_lens
        return self._num_computed_tokens_cache
​
    # TODO(lucas): remove once we have FULL-CG spec-decode support
    def unpadded(
        self, num_actual_tokens: int, num_actual_reqs: int
    ) -> "CommonAttentionMetadata":
        # unpadded() 不再搬运已删除的私有字段,而是把 upper bound 一并切片带走,
        # 这样下游 backend 仍能拿到 CPU 侧近似长度,且不会触发任何同步。
        maybe_slice_reqs = lambda x: x[:num_actual_reqs] if x is not None else None
        return CommonAttentionMetadata(
            query_start_loc=self.query_start_loc[: num_actual_reqs + 1],
            query_start_loc_cpu=self.query_start_loc_cpu[: num_actual_reqs + 1],
            seq_lens=self.seq_lens[:num_actual_reqs],
            num_reqs=num_actual_reqs,
            num_actual_tokens=num_actual_tokens,
            max_query_len=self.max_query_len,
            max_seq_len=self.max_seq_len,
            block_table_tensor=self.block_table_tensor[:num_actual_reqs],
            slot_mapping=self.slot_mapping[:num_actual_tokens],
            seq_lens_cpu_upper_bound=maybe_slice_reqs(self.seq_lens_cpu_upper_bound),
            is_prefilling=maybe_slice_reqs(self.is_prefilling),
            rswa_prefix_lens=maybe_slice_reqs(self.rswa_prefix_lens),
            replayssm_decode_base_cpu=maybe_slice_reqs(self.replayssm_decode_base_cpu),
        )
vllm/models/deepseek_v4/compressor.py data-contract

C128 边界判定原本依赖被删除的 _num_computed_tokens_cpu,本次改为从 seq_lens_cpu_upper_bound 反推,并新增 async_spec_decode 守卫,是“乐观上界不可用于精确判断”的范式实现。

# vllm/models/deepseek_v4/compressor.py —— C128 边界判定与 async spec decode 守卫
def _get_c128_boundary(metadata: CommonAttentionMetadata) -> bool | None:
    # 过去直接读 metadata._num_computed_tokens_cpu(已被删除的隐式同步字段);
    # 现在从 CPU 上界减去本步 query 长度来反推每个请求的 start:
    # start = seq_lens - query_lens,与 upper bound 的推导口径保持一致。
    seq_lens_cpu = metadata.seq_lens_cpu_upper_bound
    if seq_lens_cpu is None:
        return None
​
    query_lens = metadata.query_start_loc_cpu[1:] - metadata.query_start_loc_cpu[:-1]
    starts = seq_lens_cpu - query_lens
    starts_list = starts.tolist()
    query_start_loc = metadata.query_start_loc_cpu.tolist()
    return any(
        start % 128 + query_start_loc[i + 1] - query_start_loc[i] >= 128
        for i, start in enumerate(starts_list)
    )
​
​
class CompressorMetadataBuilder(AttentionMetadataBuilder):
    def build(
        self,
        common_prefix_len: int,
        common_attn_metadata: CommonAttentionMetadata,
        fast_build: bool = False,
    ) -> CompressorMetadata:
        token_to_req_indices = common_attn_metadata.token_to_req_indices(
            self.token_to_req_indices
        )
        num_decode_tokens = None
        if _prefer_two_stage_compressor():
            _, _, num_decode_tokens, _ = split_decodes_and_prefills(
                common_attn_metadata, decode_threshold=1
            )
        # async spec decode 下 seq_lens_cpu_upper_bound 是“假设所有 draft 都被接受”
        # 的乐观值,用它判断是否跨 128 边界会算错,因此这种情况下直接放弃该优化。
        async_spec_decode = (
            self.vllm_config.scheduler_config.async_scheduling
            and self.vllm_config.speculative_config is not None
        )
        return CompressorMetadata(
            block_table=common_attn_metadata.block_table_tensor.clamp_(min=0),
            slot_mapping=common_attn_metadata.slot_mapping,
            block_size=self.block_size,
            token_to_req_indices=token_to_req_indices,
            num_decode_tokens=num_decode_tokens,
            c128_boundary=(
                _get_c128_boundary(common_attn_metadata)
                if self.block_size == 8 and not async_spec_decode
                else None
            ),
        )
vllm/v1/attention/backends/mamba_attn.py core-logic

Mamba ReplaySSM 改为读取 seq_lens_cpu_upper_bound 并自算 query 长度,且在 async spec decode 或长度缺失时改为显式 ValueError,是本次唯一把“静默用错数据”改成“显式失败”的路径。

# vllm/v1/attention/backends/mamba_attn.py —— ReplaySSM 的精确长度要求
if self.use_replayssm and not self.use_flashinfer_replayssm and num_decodes > 0:
    decode_base_cpu = common_attn_metadata.replayssm_decode_base_cpu
    # 旧实现读 common_attn_metadata._num_computed_tokens_cpu(已删除);
    # 现在改为“CPU 上界 - 本步 query 长度”,并显式拒绝无法保证精确性的组合。
    seq_lens_cpu = common_attn_metadata.seq_lens_cpu_upper_bound
    async_spec_decode = (
        self.vllm_config.scheduler_config.async_scheduling
        and self.vllm_config.speculative_config is not None
    )
    if decode_base_cpu is None or seq_lens_cpu is None or async_spec_decode:
        # 从“静默算错 write position”改成显式报错:
        # async spec decode 下 upper bound 是乐观值,不能用来推导 decode 写入位置。
        raise ValueError(
            "--use-replayssm requires exact CPU sequence lengths and "
            "decode-base counts to derive decode write positions"
        )
    query_lens_cpu = (
        common_attn_metadata.query_start_loc_cpu[1 : num_decodes + 1]
        - common_attn_metadata.query_start_loc_cpu[:num_decodes]
    )
    num_computed_d = seq_lens_cpu[:num_decodes] - query_lens_cpu
    decode_base_d = decode_base_cpu[:num_decodes]
    align_mode = self.vllm_config.cache_config.mamba_cache_mode == "align"
    block_size = self.kv_cache_spec.block_size
    # 注意:query_lens_cpu 现在在更早的位置计算,后续
    # decode_steps_cpu / valid_decode_rows 等都复用同一个 CPU 张量。

评论区精华

新测试用 monkeypatch.setenv 设置 VLLM_GPU_SYNC_CHECK 是否可靠 测试

coderabbitai[bot] 在 tests/v1/e2e/spec_decode/draft_model/test_async.py:46 标为 Major/Functional Correctness:建议直接 patch gpu_sync_debug._SYNC_CHECK_MODE,并在驱动单 GPU UniProcExecutor 之前应用该 patch,否则仅靠 monkeypatch.setenv("VLLM_GPU_SYNC_CHECK", "error") 可能因为 vllm 已导入或模式已固化而失效。mgoin 回复认为 CI 默认已开启 sync error,这一行可能并不必要。

结论:PR 未采纳修改即合并(sfeng33 APPROVED,mgoin 的 COMMENTED 落在同一行),维护者接受现状;但“setenv 生效时机”与“是否依赖 CI 默认值”没有形成书面结论。 · unresolved

移除隐式同步属性后,测试断言责任向 worker 内置检查转移 设计

sync_tracker fixture 之所以存在,是因为 CommonAttentionMetadata.seq_lens_cpu 的惰性初始化就是隐式 D2H 同步的唯一入口;该属性被删除后,原 fixture 同时失去 patch 目标和诊断价值(调用栈打印)。改为依赖 VLLM_GPU_SYNC_CHECK=error 由 worker 侧统一拒绝任何隐式同步,测试代码从约 70 行降到几行,但也把“断言是否真的生效”交给了运行环境配置。

结论:测试与实现同步收敛,维护者接受以环境变量方式兜底;代价是本地(非 CI)执行时同步断言强度不确定。 · 已采纳

风险与影响

  1. 破坏性升级路径缺显式报错:vllm/envs.py 删除 VLLM_MM_HASHER_ALGORITHM / VLLM_PREFIX_CACHE_RETENTION_INTERVAL 后,用户继续导出这两个变量不会有任何提示,行为静默退回默认值(blake3 / 0);如果外部代码仍引用 envs.VLLM_MM_HASHER_ALGORITHM,则会直接 AttributeError。同理 use_fp4_indexer_cache 字段被删除后,旧 JSON/CLI 传参是报错还是被 @config 忽略,材料中未体现,需要 release note 明确。
  2. seq_lens_cpu_upper_bound 的语义陷阱:该字段在 async spec decode 行上是乐观上界(“assumes every draft was accepted”),只适合不需要逐行精确上下文长度的场景。本 PR 只在 DeepSeek-V4 compressor 与 Mamba ReplaySSM 两处加了 async_spec_decode 守卫,vllm/v1/worker/ubatch_utils.py 与各 attention backend 中其余仍在读取该字段的路径没有在材料里显示同等判断,存在后续误用该上界做精确判断的隐患。
  3. Mamba ReplaySSM 的失败模式前移不到位:_compute_common_metadata 在每步构建 metadata 时抛 ValueError,意味着 --use-replayssm + async scheduling + spec decode 的组合会在运行期(而非启动校验期)才失败;虽然从“静默算错 write position”变成“显式报错”是改善,但错误出现时机对生产更不友好。
  4. ROCm 设备可见性行为变更:vllm/platforms/rocm.py 不再把 CUDA_VISIBLE_DEVICES 自动拷贝到 HIP_VISIBLE_DEVICES,只设置前者的 ROCm 用户会看到可选设备集合变化,可能表现为静默失效或直接报错(取决于剩余校验分支),也影响容器编排脚本。
  5. 测试断言强度与可诊断性下降:如讨论所述,VLLM_GPU_SYNC_CHECK 的生效时机未经证实;同时删除 sync_tracker 后失去附带的调用栈打印,一旦回归,定位隐式同步来源的成本变高。
  6. 正向影响:删除惰性 .to("cpu") 属性后,误触发 D2H 同步的入口被彻底封死,对 full async scheduling 下的 spec decode 吞吐是结构性收益,“性能”标签正来自此。

对用户:这是一次需要迁移的破坏性变更——使用 VLLM_MM_HASHER_ALGORITHM(FIPS 场景常用 sha256/sha512)、VLLM_PREFIX_CACHE_RETENTION_INTERVAL、--use-fp4-indexer-cache 或 ROCm 上只设 CUDA_VISIBLE_DEVICES 的部署必须调整启动参数,否则会静默回退默认值;但默认路径(blake3、interval=0、indexer_kv_dtype=auto)行为不变,因此多数用户升级无感。对系统:CommonAttentionMetadata 的数据契约被收缩,async spec decode + 混合 KV 缓存组、DeepSeek-V4 C128A compressor、Mamba ReplaySSM 这些依赖 CPU 侧长度的路径改由 upper bound + 显式守卫驱动,正确性责任从“隐式同步”转移到调用点判断。对团队:改动横跨 v1 worker、attention backend、config、platforms 与 ROCm 路径(28 个文件),但大多是机械替换,真正的设计决策集中在两处守卫;同时确立了“弃用到期即删除、不再保留静默兼容层”的维护节奏。

破坏性变更:删除环境变量与配置别名 隐式同步属性移除,元数据契约收缩 乐观上界可能被误用于精确判断 Mamba ReplaySSM 运行期报错而非启动校验 ROCm 设备可见性行为变更 测试同步断言可靠性存疑

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论