Prhub

#39896 [MyPy] Fix mypy for `vllm/benchmarks`

原始 PR 作者 hickeyma 合并时间 2026-06-23 23:22 文件变更 24 提交数 14 评论 46 代码增减 +296 / -150

执行摘要

为 vllm/benchmarks 启用 mypy 类型检查并修复所有错误

该项目旨在为 vLLM 启用严格的 mypy 类型检查(参见 Issue #26533)。vllm/benchmarks 目录原本被排除在 mypy 检查之外,导致大量未发现的类型错误。本 PR 消除了这些错误,使 benchmarks 代码能够通过 mypy --python-version 3.10 检查,为将来将整个代码库纳入类型检查铺平道路。

  • 该 PR 是逐步启用类型检查的良好示例,值得学习其变更模式和 review 讨论。
  • 特别关注 FlexibleArgumentParser 的统一使用和 expected_output_len 默认值设计决策。
  • 适合作为代码清理的参考范例。
讨论亮点
  • FlexibleArgumentParser 统一:hmellor 建议在所有 benchmarks 入口点使用 FlexibleArgumentParser,hickeyma 采纳并实施,认为“FlexibleArgumentParser does a lot of clever stuff that vLLM expects”。
  • expected_output_len 默认值:hmellor 建议将默认值从 None 改为 0 以减少空检查,hickeyma 采纳。
  • 最小化 diff:yewentao256 要求“shrink the diff as much as possible, we don't want to introduce too much changes for mypy fix only”,hickeyma 相应回退了一些变化。
  • prompt 类型安全:gemini-code-assist 指出 cast(str, raw_prompt) 不安全,可能导致运行时 TypeError,hickeyma 改用 isinstance 检查。

实现拆解

  1. 更新 mypy 配置:在 tools/pre_commit/mypy.py 中将 vllm/benchmarksSEPARATE_GROUPS 迁移至 FILES,使其参与完整类型检查。
  2. 统一参数解析器:所有 benchmarks 入口点(throughput.py, serve.py, latency.py 等)的 add_cli_args 函数签名从 argparse.ArgumentParser 改为 FlexibleArgumentParser,后者提供更好的类型推断。
  3. 修复 SampleRequest:将 expected_output_len 类型从 int | None 改为 int = 0,消除多处 or 0 操作。
  4. 类型安全的 prompt 处理:在 throughput.py_run_vllm_requests 中,将 prompt 拆包从单行条件改为 isinstance 检查,并添加 assert 确保类型安全。
  5. 修正方法签名:在 datasets.py 中将多个 sample 方法的参数顺序与父类对齐,并添加 max_loras 等可选参数,消除 Signature incompatible 错误。
  6. 增强类型注解:为变量(如 mm_contents, batch_requests, requests, lora_modules_iter)添加显式类型注解;添加运行时断言(assert self.data is not None, assert hasattr(tokenizer, ...))帮助类型推断。
文件 模块 状态 重要度
vllm/benchmarks/datasets/datasets.py 基准测试 modified 7.38
vllm/benchmarks/throughput.py 基准测试 modified 6.91
vllm/benchmarks/serve.py 基准测试 modified 6.45
vllm/benchmarks/lib/endpoint_request_func.py 请求函数库 modified 5.92

关键符号

add_cli_args run_vllm _run_vllm_requests sample get_samples calculate_metrics _get_chat_content _is_chat_messages

关键源码片段

vllm/benchmarks/datasets/datasets.py core-logic

核心数据集模块,包含 SampleRequest dataclass 和所有数据集类的 sample 方法。本 PR 修复了最多的 mypy 错误,包括默认值修改、参数签名对齐、添加类型注解等。

@dataclass
class SampleRequest:
    """Represents a single inference request for benchmarking."""
    prompt: str | list[str] | list[dict]
    prompt_len: int
    # 默认值从 None 改为 0,消除大量空检查
    expected_output_len: int = 0
    multi_modal_data: MultiModalDataDict | dict | list[dict] | None = None
    lora_request: LoRARequest | None = None
    request_id: str | None = None
    timestamp: float | None = None
    # 预先构建的聊天消息,与 prompt 互斥
    chat_messages: list[dict[str, Any]] | None = None
    # 每个请求的覆盖字段,在分发时浅合并
    request_overrides: dict | None = None
# sample 方法中批处理构造的片段
if batchsize > 1:
    batch_requests: list[SampleRequest] = []
    for i in range(0, num_requests, batchsize):
        batch = requests[i : i + batchsize]
        batch_requests.append(
            SampleRequest(
                prompt=[req.prompt for req in batch], # type: ignore[arg-type]
                prompt_len=sum(req.prompt_len for req in batch),
                request_id=request_id_prefix + str(i // batchsize),
            )
        )
vllm/benchmarks/throughput.py core-logic

离线吞吐量基准测试入口,主要改动在 prompt 处理逻辑和参数解析器类型。展示 mypy 错误修复的最佳实践。

def _run_vllm_requests(
    llm: Any,
    requests: list[SampleRequest],
    n: int,
    disable_detokenize: bool,
    do_profile: bool,
    prequeue_requests: bool,
    enable_lora: bool,
) -> tuple[float, list[RequestOutput] | None]:
    prompts: list[TextPrompt | TokensPrompt] = []
    sampling_params: list[SamplingParams] = []
    lora_requests: list[LoRARequest | None] | None = [] if enable_lora else None
​
    for request in requests:
        # 使用 isinstance 代替 in 检查以提高类型安全性
        if isinstance(request.prompt, dict) and "prompt_token_ids" in request.prompt:
            token_ids = request.prompt["prompt_token_ids"]
            assert isinstance(token_ids, list)
            prompt = TokensPrompt(prompt_token_ids=token_ids)
        else:
            assert isinstance(request.prompt, str)
            prompt = TextPrompt(prompt=request.prompt)
​
        if request.multi_modal_data:
            assert isinstance(request.multi_modal_data, dict)
            prompt["multi_modal_data"] = request.multi_modal_data
​
        prompts.append(prompt)
        sampling_params.append(SamplingParams(
            n=n, temperature=1.0, top_p=1.0,
            ignore_eos=True,
            max_tokens=request.expected_output_len,
            detokenize=not disable_detokenize,
        ))
        if lora_requests is not None and request.lora_request is not None:
            lora_requests.append(request.lora_request)
    # ...

评论区精华

使用 FlexibleArgumentParser 统一参数解析器 设计

hmellor 建议在所有 benchmarks 入口点使用 FlexibleArgumentParser 以利用 vLLM 的扩展功能,hickeyma 担心改动接口但最终同意,并在 PR 中统一修改。hmellor 强调 'FlexibleArgumentParser does a lot of clever stuff that vLLM expects to have been done'。

结论:已采纳,所有 add_cli_args 函数改为接受 FlexibleArgumentParser。 · 已解决

expected_output_len 默认值设计 设计

hmellor 建议将 expected_output_len 默认值从 None 改为 0,以消除多处 or 0 操作,简化类型。hickeyma 评估后实施。

结论:已修改为 int = 0。 · 已解决

最小化 diff 避免功能变更 other

yewentao256 要求缩小改动范围,不要引入不必要的功能变更。hickeyma 回退了一些对参数顺序等的修改。

结论:已调整,保持最小变更。 · 已解决

prompt 类型安全转换 正确性

gemini-code-assist 指出 cast(str, raw_prompt) 不安全,可能导致运行时 TypeError。建议使用 isinstance 检查。hickeyma 采纳并重构了 prompt 处理逻辑。

结论:已修复,使用 isinstance 和 assert 确保类型安全。 · 已解决

风险与影响

  1. 默认值变更风险expected_output_lenNone 变为 0,如果外部代码依赖 None 进行判断,可能产生不易发现的 bug。但 benchmarks 工具内部使用已全部适配。
  2. 新增断言:添加的 assert 可能在 Python 优化模式(-O)下被跳过,mypy 仍假设非空,可能掩盖潜在缺陷;但 benchmarks 不常用优化模式。
  3. 参数顺序调整:部分子类 sample 方法的参数顺序调整,可能破坏外部直接调用(若有);但 datasets.py 内部已同步。
  4. FlexibleArgumentParser 兼容性:替换解析器可能改变参数解析行为,但该解析器在 vLLM 中已广泛使用,风险低。
  • 用户影响:无直接用户影响,benchmarks 脚本的行为不变。但运行基准测试的用户会受益于更严格的类型检查,减少潜在错误。
  • 系统影响:无。
  • 团队影响:提升了代码质量,为后续 mypy 全覆盖奠定基础。CI 现在会对 benchmarks 代码进行类型检查,防止回归。
默认值变更 断言依赖

关联 Issue

#26533 [Feature]: Fix all of the mypy check

完整报告

参与讨论