执行摘要
- 一句话:修复多模态基准测试提示生成中特殊标记污染问题
- 推荐动作:建议合并。此PR修复了真实用户发现的多模态基准测试数据生成正确性问题,代码变更简洁,有单元测试覆盖,且通过了review的讨论和验证。
功能与动机
在运行多模态合成基准测试(--dataset-name image)时,gen_mm_prompt生成的提示文本可能包含如<|video_pad|>等特殊标记,而这些标记在没有对应payload的情况下会导致服务器返回No data iterator found for token错误,使得基准测试结果不可靠。该问题在Qwen3-VL等模型中复现,且通过payload审计确认8/12900条提示包含被禁止的特殊标记。此PR旨在从生成侧剔除所有特殊标记,确保基准测试数据的合法性。
实现拆解
-
提取辅助函数并添加缓存:在python/sglang/benchmark/datasets/common.py中新增get_available_multimodal_text_tokens(tokenizer, image_pad_id),使用@lru_cache(maxsize=1)装饰以避免重复词汇表扫描。该函数从tokenizer.all_special_ids构建排除集,并额外将image_pad_id加入排除集(若不为None)。最终返回get_available_tokens(tokenizer)中不在排除集中的token id列表。
-
更新gen_mm_prompt:将原本直接调用tokenizer.get_vocab().values()并逐一移除image_pad_id的逻辑,改为调用新的get_available_multimodal_text_tokens函数,简化实现并统一过滤逻辑。同时修复了image_pad_id检查条件,从if image_pad_id:改为if image_pad_id is not None:以正确处理token id为0的情况。
-
添加单元测试:在test/registered/bench_fn/test_benchmark_datasets_api.py中新增test_gen_mm_prompt_excludes_special_tokens方法。使用create_lightweight_tokenizer构造包含多模态特殊标记的轻量tokenizer,通过mockrandom.choices捕获候选池,断言候选池中不包含任何特殊标记的token id,并确保候选池非空。
关键文件:
python/sglang/benchmark/datasets/common.py(模块 基准生成;类别 source;类型 core-logic;符号 get_available_multimodal_text_tokens): 核心变更,新增过滤辅助函数并修改gen_mm_prompt以正确排除所有特殊标记。
test/registered/bench_fn/test_benchmark_datasets_api.py(模块 基准测试;类别 test;类型 test-coverage;符号 test_gen_mm_prompt_excludes_special_tokens, fake_choices): 新增确定性单元测试,验证特殊标记排除逻辑。
关键符号:get_available_multimodal_text_tokens, gen_mm_prompt
关键源码片段
python/sglang/benchmark/datasets/common.py
核心变更,新增过滤辅助函数并修改gen_mm_prompt以正确排除所有特殊标记。
import random
from functools import lru_cache
# 复用已缓存的通用有效 Token 获取函数
from sglang.benchmark.datasets.common import get_available_tokens
# 缓存装饰器确保多模态文本 Token 池只计算一次
@lru_cache(maxsize=1)
def get_available_multimodal_text_tokens(tokenizer, image_pad_id):
"""Get valid token ids for synthetic multimodal text prompts."""
# 收集所有特殊 Token 的 ID,防御性地使用 or [] 避免 None
excluded_token_ids = set(getattr(tokenizer, "all_special_ids", []) or [])
if image_pad_id is not None:
excluded_token_ids.add(image_pad_id)
# 仅从已缓存的通用池中过滤,避免重复扫描完整词表
return [
token_id
for token_id in get_available_tokens(tokenizer)
if token_id not in excluded_token_ids
]
def gen_mm_prompt(tokenizer, image_pad_id, token_num):
"""Generate a random prompt of specified token length using tokenizer vocabulary."""
# 直接使用过滤后的多模态安全池
all_available_tokens = get_available_multimodal_text_tokens(tokenizer, image_pad_id)
selected_tokens = random.choices(all_available_tokens, k=token_num)
return tokenizer.decode(selected_tokens)
test/registered/bench_fn/test_benchmark_datasets_api.py
新增确定性单元测试,验证特殊标记排除逻辑。
def test_gen_mm_prompt_excludes_special_tokens(self):
# 创建轻量 tokenizer,包含标准词汇
tokenizer = create_lightweight_tokenizer()
# 注入一组多模态特殊标记(模拟 Qwen3-VL 等模型)
multimodal_special_tokens = [
"<|image_pad|>",
"<|video_pad|>",
"<|vision_start|>",
"<|vision_end|>",
"<|vision_pad|>",
]
tokenizer.add_special_tokens(
{"additional_special_tokens": multimodal_special_tokens}
)
special_token_ids = set(
tokenizer.convert_tokens_to_ids(multimodal_special_tokens)
)
image_pad_id = tokenizer.convert_tokens_to_ids("<|image_pad|>")
# 使用字典捕获 random.choices 被调用时的候选池
captured_population = {}
def fake_choices(population, k):
captured_population["tokens"] = population
return population[:k]
# 替换 random.choices 以拦截候选池,避免真正随机采样
with patch(
"sglang.benchmark.datasets.common.random.choices",
side_effect=fake_choices,
):
gen_mm_prompt(tokenizer, image_pad_id, token_num=8)
sampled_pool = set(captured_population["tokens"])
# 断言候选池中没有任何特殊标记
self.assertFalse(special_token_ids & sampled_pool)
# 断言候选池非空
self.assertTrue(sampled_pool)
评论区精华
Review过程中有两个核心讨论:
风险与影响
- 风险:此变更仅影响基准测试数据生成路径,不涉及模型前向、解码或服务性能。主要风险在于
tokenizer.all_special_ids可能在不兼容的tokenizer中返回非预期值(如空列表),但代码中已有防御性取值getattr(tokenizer, "all_special_ids", []) or []。此外,缓存机制假设tokenizer和image_pad_id在单次基准测试运行中不会变化,这符合现有使用场景。单元测试覆盖了典型的Qwen3-VL特殊标记集合,但仍需注意其他tokenizer引入的新特殊标记类型。
- 影响:直接用户:执行
python -m sglang.bench_serving --dataset-name image的基准测试用户将不再因特殊标记导致HTTP 400错误,基准测试结果更加稳定。间接用户:依赖SGLang基准测试结果进行模型评估的开发者和CI系统将获得更可靠的数据。影响范围限定在基准测试模块,不影响生产推理路径。团队维护:新增的辅助函数与现有get_available_tokens风格一致,易于理解和扩展。
- 风险标记:数据生成路径变更
关联脉络
参与讨论