Prhub

#33255 [misc] Improve benchmark determinism and dataset API coverage

原始 PR 作者 hnyls2002 合并时间 2026-08-02 16:39 文件变更 6 提交数 3 评论 3 代码增减 +199 / -10

执行摘要

基准数据集采样支持 --seed 确定性,新增图像前缀缓存复用测试

PR body 明确列出目标:Make dataset sampling deterministic under --seed (sorted vocab; reset RNG after processor init), add gsm8k --chat-template-kwargs, skip worker-state guards gracefully when one_batch_server points at a PD router, add an image prefix-cache reuse test, and extend dataset API tests (incl. the media-kwargs processor contract used by the upcoming Kimi K3 processor, #32541)。另外指出 verify_single_image_response 中的断言此前恒真(or "person" 被当 truthy 字面量),重新武装后可能暴露潜在失败。

值得精读:可学习如何用“排序 + RNG 重置”让基于全局随机状态的采样在跨 tokenizer 版本时保持确定性,以及如何通过端到端贪心请求校验图片前缀缓存正确性。断言修复的 caution 说明体现了对隐性坏味道的复盘,建议关注后续 VLM 套件的失败信号。

讨论亮点

本 PR 没有 review 评论。PR body 中作者主动提示:verify_single_image_response 的关键字断言此前恒真(“person” 被当作 truthy 字面量),重新武装后可能让现有 VLM 套件浮现潜在失败。合入前作者通过 /rerun-test test_benchmark_datasets_api.py test_vision_openai_server_a.py 确认了 ubuntu-latest 与 1-gpu-h100 两个测试通过。

实现拆解

  1. 规范化 vocab 采样池python/sglang/benchmark/datasets/common.pyget_available_tokens 由列表推导改为 sorted(),消除 tokenizer 版本间 vocab dict 迭代序差异对 random.choices 采样结果的影响;配合 lru_cache 保证缓存结果顺序稳定。
  2. RNG 重置与 Kimi K3 契约python/sglang/benchmark/datasets/image.pyImageDataset 新增 seed 字段,from_argsargs.seed 取值;load()get_processor() 之后对 randomnp.randomseed(self.seed) 重设,抵消 processor 初始化对全局 RNG 的消耗;create_mm_data_row 的 medias 分支从仅 KimiK25Processor 扩展为包含 KimiK3Processor
  3. gsm8k chat-template kwargsbenchmark/gsm8k/bench_sglang.py 新增 --chat-template-kwargsjson.loads 解析,默认 {"enable_thinking": true}),将 apply_chat_template 调用从硬编码 enable_thinking=True 改为 **args.chat_template_kwargs,兼容不同模型的 thinking 开关取名差异(enable_thinking vs thinking)。
  4. PD router 守卫python/sglang/benchmark/one_batch_server.pyserver_inforouter_managerinternal_states 为空时打印警告并把 max-running-requests 与 token-capacity 守卫置为 inf,避免对 PD router 场景误断言;否则维持原有断言逻辑。
  5. 测试配套python/sglang/test/vlm_utils.py 新增 test_image_prefix_cache_reuse(同图重复输出一致、异图输出不同,拦截图片 KV 错误复用)并修复 verify_single_image_response 恒真断言;test/registered/bench_fn/test_benchmark_datasets_api.py 新增 KimiK3Processor 模拟类、media contract 测试、seed 与 processor 初始化独立性测试、vocab 顺序无关性测试。
文件 模块 状态 重要度
test/registered/bench_fn/test_benchmark_datasets_api.py 数据集 API modified 7.66
python/sglang/test/vlm_utils.py VLM 测试 modified 6.5
python/sglang/benchmark/one_batch_server.py 基准服务器 modified 6.18
python/sglang/benchmark/datasets/image.py 基准数据集 modified 6.07
benchmark/gsm8k/bench_sglang.py GSM8K 基准 modified 5.57
python/sglang/benchmark/datasets/common.py 基准数据集 modified 5.03

关键符号

ImageDataset.load ImageDataset.from_args get_available_tokens create_mm_data_row verify_single_image_response test_image_prefix_cache_reuse KimiK3Processor.__call__ test_image_sampler_uses_kimi_k3_media_contract test_image_dataset_seed_is_independent_of_processor_initialization test_gen_mm_prompt_is_independent_of_vocab_order

关键源码片段

test/registered/bench_fn/test_benchmark_datasets_api.py test-coverage

新增大规模数据集 API 测试:Kimi K3 media contract、seed 与 processor 初始化独立性、vocab 顺序无关性

def test_image_dataset_seed_is_independent_of_processor_initialization(self):
    # 构造带 seed 的 ImageDataset,并用“带 RNG 副作用”的 processor 初始化
    # 模拟真实环境:get_processor 初始化本身会偷偷消耗全局 random / np.random 状态,
    # 若不重置,同一 seed 会因 processor 初始化次数不同而产生不同采样结果。
    dataset = ImageDataset.from_args(
        make_args(
            num_prompts=3,
            image_resolution="random:8x16-16x32",
            seed=20260717,
        )
    )
    processor_init_count = 0
​
    def get_processor_with_rng_side_effects(_model_id):
        nonlocal processor_init_count
        processor_init_count += 1
        random.random()
        np.random.random(processor_init_count)
        return self.processor
​
    with patch(
        "sglang.benchmark.datasets.image.get_processor",
        side_effect=get_processor_with_rng_side_effects,
    ):
        first = dataset.load(model_id="test-model")
        random.seed(999)
        np.random.seed(999)
        second = dataset.load(model_id="test-model")
​
    # 两次 load 之间刻意重置 RNG,验证数据行完全一致,
    # 证明确定性来自 load() 内部的 seed 重置而不是外部巧合。
    self.assertEqual(
        [
            (row.prompt, row.prompt_len, row.output_len, row.image_data)
            for row in first
        ],
        [
            (row.prompt, row.prompt_len, row.output_len, row.image_data)
            for row in second
        ],
    )
python/sglang/test/vlm_utils.py test-coverage

新增图片前缀缓存复用端到端测试,并修复 verify_single_image_response 恒真断言

def test_image_prefix_cache_reuse(self):
    """图片前缀(radix)缓存跨请求正确性测试。    重复相同图片必须复用多模态前缀且输出不变;不同图片绝不能用前一张
    图片的 KV。这能拦截 image-token pad_value 或 feature-hash 回归导致的
    “静默复用错误图片 KV”级正确性 bug,而这种 bug 在单请求测试中不可见。
    纯贪心请求级校验:无需额外 server 参数,radix cache 默认开启。
    """
    client = openai.Client(api_key=self.api_key, base_url=self.base_url)
​
    def describe(url: str) -> str:
        response = client.chat.completions.create(
            model="default",
            messages=[
                {
                    "role": "user",
                    "content": [
                        {"type": "image_url", "image_url": {"url": url}},
                        {"type": "text", "text": "Describe this image in one sentence."},
                    ],
                },
            ],
            temperature=0,
            max_tokens=32,
            **(self.get_vision_request_kwargs()),
        )
        assert response.usage.prompt_tokens > 0
        content = response.choices[0].message.content
        assert isinstance(content, str) and content
        return content
​
    # miss -> compute,然后 hit -> 复用同一张图片的 prefix
    first = describe(IMAGE_MAN_IRONING_URL)
    repeat = describe(IMAGE_MAN_IRONING_URL)
    # 不同图片必须独立计算,不能复用 `first` 的 KV
    other = describe(IMAGE_SGL_LOGO_URL)
    # 另一张图片占据缓存后再回来,同一张图片的 KV 也不能被污染
    first_again = describe(IMAGE_MAN_IRONING_URL)
​
    self.assertEqual(first, repeat, "重复相同图片输出变化,prefix 复用破坏贪心确定性。")
    self.assertEqual(first, first_again, "相同图片隔了其他请求后输出变化,KV 可能跨请求串扰。")
    self.assertNotEqual(first, other, "不同图片输出却相同,可能错误复用了 prefix 缓存中的 KV。")
python/sglang/benchmark/datasets/image.py dependency-wiring

核心确定性修复:ImageDataset 增加 seed 并在 load() 重置 RNG;同时支持 KimiK3Processor 的 medias 契约

def load(self, tokenizer=None, model_id=None) -> List[DatasetRow]:
    processor = get_processor(model_id)
    # processor 初始化可能消耗全局 RNG 状态,例如加载词表或构建 chat template;
    # 这里在采样前重置随机种子,确保 --seed 能固定生成的 prompts、图片尺寸与内容。
    random.seed(self.seed)
    np.random.seed(self.seed)
    return sample_image_requests(
        num_requests=self.num_requests,
        image_count=self.image_count,
        input_len=self.input_len,
        output_len=self.output_len,
        range_ratio=self.range_ratio,
        processor=processor,
        image_content=self.image_content,
        image_format=self.image_format,
        image_resolution=self.image_resolution,
        backend=self.backend,
        random_image_count=self.random_image_count,
    )

评论区精华

verify_single_image_response 断言重新启用 正确性

作者在 PR body 中主动说明:该断言此前恒真,因为 `"man" in text or "person" or "driver" in text` 中的 `"person"` 被当作 truthy 字面量求值;重新武装为真实检查后,可能让现有 VLM 套件浮现潜在失败。

结论:PR 已通过 `any(keyword in text for keyword in ("man", "person", "driver"))` 修复,并以 /rerun-test 验证 test_vision_openai_server_a.py 在 H100 上通过。 · 已解决

风险与影响

  1. vlm_utils.py 断言修复verify_single_image_response 现在真正要求输出包含关键字,MiniCPM 等模型可能因输出措辞不含关键字而在现有套件失败;注释已为 MiniCPMO 的 iron 场景留了后备词,但覆盖面仍有限。
  2. 排序 vocab 行为变化get_available_tokens 排序会改变任意随机 prompt 的具体 token 序列,若调用方隐式依赖旧顺序,复现基线可能变化(这正是修复目标,但需留意对现有 benchmark 结果对比的影响)。
  3. 全局 RNG 重置副作用ImageDataset.load 重置 random / np.random 全局状态,会使基准进程后续逻辑的随机序列与参数 seed 耦合;多线程或复用同一进程的调用方需注意全局状态副作用。
  4. PD router 守卫绕过:依赖 server_info 中的 router_manager 字段存在;若 router 响应结构变化或字段缺失,可能误入断言分支或过度放宽并发边界。
  5. gsm8k kwargs 兼容性--chat-template-kwargsjson.loads 解析,非法 JSON 会在命令行阶段抛错;模型不认识的 kwarg(如对 Qwen 传 thinking)会被 apply_chat_template 拒绝。

对使用 --seed 的 benchmark 用户,图片与文本数据集生成从此可复现;gsm8k 可针对不同模型透传 thinking 开关;PD router 部署下 one_batch_server 不再因 internal states 缺失而崩溃。对测试基础设施,新增的 prefix-cache 与 seed 独立性测试可提前拦截图片 KV 复用和 processor 初始化导致的不确定性回归,并为 Kimi K3(PR#32541)的 media-kwargs 契约提供测试锚点。

断言修复可能暴露 VLM 套件失败 全局 RNG 重置的副作用 PD 路由守卫放宽并发边界 依赖 tokenizer vocab 顺序

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论