Prhub

#49180 [Bugfix][Benchmarks] Restore --skip-tokenizer-init with custom dataset

原始 PR 作者 mgazz 合并时间 2026-07-24 19:30 文件变更 3 提交数 3 评论 2 代码增减 +185 / -9

执行摘要

修复 custom dataset 与 --skip-tokenizer-init 的兼容性

PR #39896 在 serve.py 的 main_async 中添加了全局 assert tokenizer is not None,导致使用 --skip-tokenizer-init 配合 --dataset-name custom 的基准测试(如 Prithvi-EO 地理空间模型)直接抛出 AssertionError。

建议合入。修复明确,测试充分。类型签名调整值得关注,但已在合理范围内。

讨论亮点

无实质 review 讨论。作者在 Issue 评论中说明 CI 失败的 4 个测试与本 PR 无关,属于环境问题(No module named 'vllm._C')。审核者 DarkLight1337 直接批准。

实现拆解

  1. 移除全局断言vllm/benchmarks/serve.py):删除 main_async 中的 assert tokenizer is not None,并将 calculate_metricsbenchmarktokenizer 类型改为 TokenizerLike | None
  2. 按需断言vllm/benchmarks/datasets/datasets.py):将 get_samplestokenizer 参数类型放宽为 TokenizerLike | None。在依赖 tokenizer 的数据集分支(sonnethftimed_trace 及通用 else)中添加针对性 assert tokenizer is not None;对 customcustom_imagecustom_audio 分支不做要求。
  3. 测试覆盖tests/benchmarks/test_skip_tokenizer_init.py):新增回归测试,直接调用 main_async() 并 mock 网络请求,验证 --skip-tokenizer-init + --dataset-name custom 不再抛出 AssertionError。
文件 模块 状态 重要度
vllm/benchmarks/datasets/datasets.py 数据集加载 modified 6.23
vllm/benchmarks/serve.py 主流程 modified 4.33
tests/benchmarks/test_skip_tokenizer_init.py 测试覆盖 added 7.04

关键符号

get_samples main_async calculate_metrics benchmark

关键源码片段

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

核心修改:将 get_samples 的 tokenizer 参数改为可空,并在需要 tokenizer 的分支添加针对性断言。

def get_samples(args, tokenizer: TokenizerLike | None) -> list[SampleRequest]:
    # 初始化一些默认值
    if not hasattr(args, "request_id_prefix"):
        args.request_id_prefix = ""
    if hasattr(args, "random_range_ratio") and isinstance(args.random_range_ratio, str):
        args.random_range_ratio = _parse_range_ratio(args.random_range_ratio)
​
    # Custom 数据集:sample() 内部处理了 tokenizer=None 情形
    if args.dataset_name == "custom":
        dataset = CustomDataset(dataset_path=args.dataset_path, ...)
        input_requests = dataset.sample(tokenizer=tokenizer, ...)
    elif args.dataset_name == "custom_image":
        ... # 类似,不强制 tokenizer
    elif args.dataset_name == "custom_audio":
        ... # 类似,不强制 tokenizer
    elif args.dataset_name == "sonnet":
        # Sonnet 数据集需要 tokenizer 进行 chat template 格式化
        assert tokenizer is not None, "Tokenizer must be initialized for the 'sonnet' dataset."
        sonnet_dataset = SonnetDataset(...)
        input_requests = sonnet_dataset.sample(tokenizer=tokenizer, ...)
    elif args.dataset_name == "hf":
        assert tokenizer is not None, "Tokenizer must be initialized for the 'hf' dataset."
        ...
    elif args.dataset_name == "timed_trace":
        assert tokenizer is not None, "Tokenizer must be initialized for the 'timed_trace' dataset."
        ...
    else:
        # 其他数据集也要求 tokenizer
        assert tokenizer is not None, f"Tokenizer must be initialized for the '{args.dataset_name}' dataset."
        dataset_mapping = {...}
        ...
    return input_requests
tests/benchmarks/test_skip_tokenizer_init.py test-coverage

新测试文件,提供回归验证,确保修复后可正常工作。

import argparse, asyncio, json
from pathlib import Path
from unittest.mock import AsyncMock, patch
import pytest
import vllm.benchmarks.serve as serve_module# 模拟 Prithvi-EO 数据集的 prompt
_PRITHVI_PROMPT = {
    "data": {
        "data": "https://.../India_900498_S2Hand.tif",
        "data_format": "url",
        "out_data_format": "b64_json",
        "indices": [1, 2, 3, 8, 11, 12],
    },
    "priority": 0,
    "softmax": False,
}
​
​
def _write_dataset(path: Path) -> None:
    # 写入一行 JSON 作为数据集
    path.write_text(json.dumps({"prompt": _PRITHVI_PROMPT}) + "\n")
​
​
def _args(dataset_path: str) -> argparse.Namespace:
    # 构造与 --skip-tokenizer-init 真实调用等价的 Namespace
    return argparse.Namespace(
        dataset_name="custom",
        dataset_path=dataset_path,
        tokenizer=None,
        skip_tokenizer_init=True,
        backend="vllm-pooling",
        # ... 其他参数省略,但保持与生产代码一致 ...
    )
​
​
@pytest.mark.benchmark
def test_main_async_skip_tokenizer_init_does_not_raise(tmp_path: Path) -> None:
    # 回归测试:确保不会抛出 AssertionError
    dataset_path = tmp_path / "dataset.jsonl"
    _write_dataset(dataset_path)
    args = _args(str(dataset_path))
    # Mock benchmark 避免真实网络请求
    with patch.object(serve_module, "benchmark", AsyncMock(return_value={})):
        asyncio.run(serve_module.main_async(args))

评论区精华

CI 测试失败原因 测试

作者指出 4 个 CI 测试失败(AssertionError: DP adjusted local rank 0 is out of bounds for 0 devices, No module named 'vllm._C')与本 PR 无关,因为修改的 serve.py 和 datasets.py 不涉及 GPU/CUDA 初始化。

结论:确认 CI 失败由环境问题引起,非本 PR 引入。 · 已解决

风险与影响

风险低:修改仅限于 benchmark 命令行工具,不影响核心推理。需注意 tokenizer: TokenizerLike | None 类型变更可能影响外部直接调用者,但 benchmark 模块通常不对外暴露。测试覆盖了关键回归路径,各依赖 tokenizer 的分支仍保持原有断言,行为不变。

修复了回归,恢复无 tokenizer 模型(如 Prithvi-EO)的基准测试能力。对已有使用 sonnet/hf 等数据集的行为无影响。影响范围限于使用 --skip-tokenizer-init 的 benchmark 用户。

回归修复 类型签名变更

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论