Prhub

#30615 [Bench] Add fixed-prompt mode and per-request spec accept length metrics

原始 PR 作者 hnyls2002 合并时间 2026-07-09 17:06 文件变更 4 提交数 10 评论 7 代码增减 +180 / -45

执行摘要

新增固定 prompt 模式与 per-request spec_accept_length 指标

引用 PR body: 'Bench/eval tooling for speculative decoding: --fixed-prompt-file + --apply-chat-template in one_batch_server for a controlled constant accept length across batch sizes, and per-request spec_accept_length collection in bench_serving and run_eval.' 该变更旨在解决此前投机解码评估缺少可控 prompt 和 per-request 指标的问题。

该 PR 值得关注,特别是对从事 LLM 推理和投机解码评估的工程师。其中 _encode_fixed_prompt 的设计体现了对不同模型编码路径的抽象,FIXME 注释也暴露了客户-服务器编码逻辑重复的问题,未来可能需统一。

讨论亮点

Review 中三个核心讨论:

  • 离线环境 fast-path 检查(high priority):_is_deepseek_v4_model 直接调用 AutoConfig.from_pretrained,在无网络环境会失败并返回 False,导致绕过 DeepSeek-V4 自定义编码。建议增加基于名称的字符串快速检查。作者后续通过复用 is_deepseek_v4 函数修复。
  • iter_time 计算保护(medium priority):当 last_ttft 为 0(即 TTFT 测量失败)时,decode_wall 会等于总延迟,导致 iter_time 指标严重膨胀。需增加 last_ttft > 0 守卫。已在 commit "guard iter_time on ttft" 中修复。
  • CompletionSampler 缺少 meta_info(medium priority):目前 CompletionSampler 未传递 record_meta_info,导致 completion 模式(如 GSM8K few-shot)无法收集 spec_accept_length。建议在调用 completions.create 时传入 extra_body={'return_meta_info': True}。该问题尚未确认是否修复。

实现拆解

  1. python/sglang/benchmark/one_batch_server.py 中新增 fixed_prompt_fileapply_chat_template 配置项,并实现 _encode_fixed_prompt 函数。该函数支持三种编码路径:纯 tokenizer encode、DeepSeek-V4 自定义编码(通过 _is_deepseek_v4_model 检测)、以及标准 HF chat template 编码。通过 @lru_cache 加速模型类型判断。

  2. python/sglang/benchmark/serving.pyRequestFuncOutput 数据类中新增 spec_accept_length 字段,并在非流式和流式响应的解析分支中从 meta_info 提取该值(非流式从 response_json['choices'][0]['meta_info'],流式从每个 chunk 的 meta_info)。

  3. python/sglang/test/simple_eval_common.pyChatCompletionSampler 中新增 record_meta_info 参数,当其启用时,在 API 请求中加入 return_meta_info: true,并将返回的 meta_info 收集到 _meta_infos 列表中。

  4. python/sglang/test/run_eval.py 中新增 print_accept_length_summary 函数,统计所有 sampler 收集的 spec_accept_length 并输出 n/mean/min/max;在单次和多次 eval 流程中收集 sampler 并最终调用该函数。

文件 模块 状态 重要度
python/sglang/benchmark/one_batch_server.py 基准测试工具 modified 8.16
python/sglang/benchmark/serving.py 基准测试工具 modified 5.9
python/sglang/test/run_eval.py 评估脚本 modified 5.73
python/sglang/test/simple_eval_common.py 评估基类 modified 4.91

关键符号

_is_deepseek_v4_model _encode_fixed_prompt print_accept_length_summary

关键源码片段

python/sglang/benchmark/one_batch_server.py dependency-wiring

核心变更文件,新增固定 prompt 模式和 DeepSeek-V4 编码支持

@lru_cache(maxsize=None)
def _is_deepseek_v4_model(name_or_path: str) -> bool:
    # 尝试通过 HuggingFace config 判断模型是否为 DeepSeek-V4
    from transformers import AutoConfig
    from sglang.srt.configs.model_config import is_deepseek_v4
​
    try:
        hf_config = AutoConfig.from_pretrained(name_or_path, trust_remote_code=True)
    except Exception as e:
        # 离线环境无法加载时,默认返回 False 并警告
        print(
            f"Warning: could not load config for {name_or_path!r} ({e}); "
            "assuming a non-DeepSeek-V4 model for --apply-chat-template."
        )
        return False
    return is_deepseek_v4(hf_config)
​
​
def _encode_fixed_prompt(
    tok_inner, prompt_text: str, apply_chat_template: bool
) -> List[int]:
    # 如果不应用聊天模板,直接 encode
    if not apply_chat_template:
        return tok_inner.encode(prompt_text)
​
    messages = [{"role": "user", "content": prompt_text}]
    # DeepSeek-V4 使用自定义编码,不走 HF chat template
    if _is_deepseek_v4_model(getattr(tok_inner, "name_or_path", "") or ""):
        from sglang.srt.entrypoints.openai import encoding_dsv4
        real_input = encoding_dsv4.encode_messages(messages, thinking_mode="chat")
        return tok_inner.encode(real_input)
    # 普通模型必须有 chat template
    if getattr(tok_inner, "chat_template", None) is None:
        raise ValueError(
            "--apply-chat-template requires a tokenizer with a chat template, "
            f"but {getattr(tok_inner, 'name_or_path', tok_inner)!r} has none."
        )
    return tok_inner.apply_chat_template(
        messages, add_generation_prompt=True, tokenize=True
    )
python/sglang/test/simple_eval_common.py test-coverage

ChatCompletionSampler 新增 record_meta_info 参数和 _meta_infos 收集

def __call__(self, message_list: MessageList) -> str:
    # 前置逻辑:系统消息前缀
    if self.system_message:
        message_list = [
            self._pack_message("system", self.system_message)
        ] + message_list
    extra_body = self.extra_body
    # 如果需要记录 meta_info,在 extra_body 中添加 return_meta_info
    if self.record_meta_info:
        extra_body = {**(self.extra_body or {}), "return_meta_info": True}
    trial = 0
    while trial < 6:
        try:
            response = self.client.chat.completions.create(
                model=self.model,
                messages=message_list,
                temperature=self.temperature,
                top_p=self.top_p,
                max_tokens=self.max_tokens,
                reasoning_effort=self.reasoning_effort,
                extra_body=extra_body, # 使用组装后的 extra_body
            )
            # 收集 meta_info(包含 spec_accept_length)
            if self.record_meta_info:
                meta_info = getattr(response.choices[0], "meta_info", None)
                if meta_info:
                    self._meta_infos.append(meta_info)
            # 记录 completion tokens
            if response.usage and response.usage.completion_tokens is not None:
                self._completion_tokens.append(response.usage.completion_tokens)
            return response.choices[0].message.content or ""
        except openai.BadRequestError as e:
            print("Bad Request Error", e)
            return ""
        except Exception as e:
            # 指数退避重试
            exception_backoff = 2 ** trial
            print(f"Rate limit exception, wait {exception_backoff}s", e)
            time.sleep(exception_backoff)
            trial += 1
    return ""

评论区精华

离线环境 fast-path 检查 正确性

gemini-code-assist 建议在 _is_deepseek_v4_model 中加入基于名称的字符串快速检查,以避免 AutoConfig.from_pretrained 在离线环境失败导致的静默回退。

结论:作者后续通过复用 is_deepseek_v4 模型检测函数修复。 · 已解决

iter_time 计算保护 正确性

gemini-code-assist 指出若 last_ttft 为 0,decode_wall 可能等于总延迟,导致 iter_time 失真,建议增加 last_ttft > 0 守卫。

结论:已通过 commit 'guard iter_time on ttft' 修复。 · 已解决

CompletionSampler meta_info 支持 测试

gemini-code-assist 指出 CompletionSampler 未传递 return_meta_info,导致 completion 模式无法收集 spec_accept_length,建议在 completions.create 中传入 extra_body。

结论:待确认是否修复;patch 中已为 ChatCompletionSampler 添加 record_meta_info 支持,但 CompletionSampler 可能仍未覆盖。 · unresolved

风险与影响

  1. _is_deepseek_v4_model 仍可能因网络问题回退,但已有 fast-path 降低风险。
  2. iter_time 指标可能因不完整的 last_ttft 而失真,已添加守卫。
  3. 若服务器未启用投机解码,spec_accept_length 可能缺失,代码中已做了容错(or 0.0)。
  4. 代码中包含 FIXME 注释指出编码分派逻辑与 server 侧重复,存在维护一致性风险。
  • 用户:使用 one_batch_serverbench_serving 的工程师可以获得更精确的投机解码 benchmark 数据;使用 run_eval 的开发者可以在 eval 结果中直接看到投机接受长度统计。
  • 系统:无性能影响,仅在 benchmark 和 eval 过程中增加少量额外 HTTP 字段和日志输出。
  • 团队:提供了更丰富的投机解码量化工具,有助于后续优化。
离线环境兼容风险 指标计算可能失真 编码分派逻辑重复

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论