Prhub

#33140 [DSV4] Add official DSV4 reasoning effort support

原始 PR 作者 mmangkad 合并时间 2026-08-05 12:50 文件变更 6 提交数 15 评论 13 代码增减 +321 / -24

执行摘要

DSV4 官方 reasoning effort 三档 profile 支持

PR body 明确指出:DeepSeek-V4-Flash-0731 使用官方 reasoning-effort profile,low 不加前缀、high 沿用旧 'Absolute maximum' 前缀、max 使用新 'Beyond maximum' 前缀;而旧版 DeepSeek-V4-Flash 仍走 preview 契约。为避免硬编码 checkpoint 日期,SGLang 需要检查 checkpoint 自带的 encoding/encoding_dsv4.py 来在启动时一次性判定 prompt 契约;转换后的 checkpoint 则应能通过 dsv4_reasoning_effort_profile 显式指定。

值得精读。核心看点在 chat_encoding.py 的‘不执行代码的 checkpoint 元数据探测’设计(AST + literal_eval、大小上限、三级回退链)以及 encoding_dsv4.py 的 profile 映射表——对需要按模型版本切换 prompt 契约的 serving 框架有直接借鉴价值。建议同时阅读配套单测理解各回退分支的覆盖方式。

讨论亮点

JustinTong0323:"We are working in parallel 🤣 there are some collisions, may you mind I push mine and then you can continue?"

mmangkad:"@JustinTong0323 that should be my last commit, just fixed UT failure (feel free to overwrite)"

两人最初并行开发同一功能,JustinTong0323 在冲突后接管后续提交(检测重构、启动时解析、preview/official 重命名),mmangkad 最终负责端到端验证并 LGTM。

mmangkad:"I verified this end to end on a B300 with both deepseek-ai/DeepSeek-V4-Flash-0731 and deepseek-ai/DeepSeek-V4-Flash. The official profile correctly follows the new low / high / max mapping, while the preview profile preserves the existing high / max behavior and falls back from low to high. The prompt-token counts matched the expected effort prefixes, and all 86 serving-chat unit tests passed."

mmangkad:"I like the preview / official rename too, especially with the official Pro release in mind, since it likely will not use the Flash-specific 0731 date."

评审确认了 profile 按能力形态命名(而非按发布时间)的取舍,为后续 DSV4 Pro 预留了扩展空间。

实现拆解

  1. 双契约文案与映射表(encoding_dsv4.py):将单一 REASONING_EFFORT_MAX 拆为 REASONING_EFFORT_PREVIEW_MAX 与 REASONING_EFFORT_OFFICIAL_MAX,新增嵌套映射 REASONING_EFFORT_PROFILES(preview 支持 high/max,official 支持 low/high/max)。render_message 与 encode_messages 均新增 reasoning_effort_profile 参数(默认 preview);effort 缺省时 official 默认 low、preview 默认 high;非法组合从 assert 改为显式抛 ValueError,便于服务端给出可读错误。
  2. checkpoint 自描述检测(chat_encoding.py):新增 _detect_dsv4_reasoning_effort_profile,优先读本地 encoding/encoding_dsv4.py,缺失时用 hf_hub_download 拉取;设 1 MiB 大小上限,用 ast.parse + ast.literal_eval 只读顶层赋值(不执行 checkpoint 代码);判定优先级为‘official 契约(REASONING_EFFORT_PROMPTS 三档齐全且 DEFAULT_REASONING_EFFORT == low)’>‘preview 契约(存在 REASONING_EFFORT_MAX)’> None。resolve_dsv4_reasoning_effort_profile 按 override > 检测结果 > preview 回退的顺序解析,_validate_dsv4_reasoning_effort_profile 在构造期拦截非法 override。
  3. 启动接线(serving_chat.py):OpenAIServingChat 构造时仅当 chat_encoding_spec == dsv4 才解析一次 _dsv4_reasoning_effort_profile,override 取自 hf_config.to_dict() 的 dsv4_reasoning_effort_profile 键;_apply_jinja_template 按 profile 的 accepted_efforts 过滤 request.reasoning_effort 与 SGLANG_DSV4_REASONING_EFFORT,再透传给 encode_messages,避免编码器内部再做二次判断。
  4. 测试与文档配套:test_serving_chat.py 新增 _create_dsv4_checkpoint 工具(临时目录写 encoding/encoding_dsv4.py)与 5 个测试方法,覆盖 profile 检测、resolution 回退、model config override、非法 override 构造失败以及端到端 prompt 前缀断言;environ.py 与 environment_variables.mdx 更新 SGLANG_DSV4_REASONING_EFFORT 语义并给出 --json-model-override-args 示例。
文件 模块 状态 重要度
python/sglang/srt/entrypoints/openai/chat_encoding.py 编码分发 modified 8.09
test/registered/unit/entrypoints/openai/test_serving_chat.py 聊天服务 modified 7.37
python/sglang/srt/entrypoints/openai/encoding_dsv4.py DSV4 编码器 modified 6.92
python/sglang/srt/entrypoints/openai/serving_chat.py 聊天服务 modified 6.73
python/sglang/srt/environ.py 环境变量 modified 4.42
docs/docs/references/environment_variables.mdx 文档 modified 2.34

关键符号

_detect_dsv4_reasoning_effort_profile _validate_dsv4_reasoning_effort_profile resolve_dsv4_reasoning_effort_profile render_message encode_messages

关键源码片段

python/sglang/srt/entrypoints/openai/encoding_dsv4.py core-logic

编码核心:REASONING_EFFORT_PROFILES 定义 preview/official 两套 effort 映射,render_message 与 encode_messages 按 profile 选择前缀并改为显式 ValueError。

# 两代 prompt 契约的前缀文案:
# preview 的 max 沿用 "Absolute maximum";official 的 max 改用 "Beyond maximum"
REASONING_EFFORT_PREVIEW_MAX = (
    "Reasoning Effort: Absolute maximum with no shortcuts permitted.\n"
    "You MUST be very thorough in your thinking and comprehensively decompose the problem to resolve the root cause, rigorously stress-testing your logic against all potential paths, edge cases, and adversarial scenarios.\n"
    "Explicitly write out your entire deliberation process, documenting every intermediate step, considered alternative, and rejected hypothesis to ensure absolutely no assumption is left unchecked.\n\n"
)REASONING_EFFORT_OFFICIAL_MAX = (
    "Reasoning Effort: Beyond maximum — exhaustive, relentless, and uncompromising.\n"
    "You MUST reason with the utmost depth and rigor, leaving absolutely nothing to chance: exhaustively decompose the problem into its most fundamental components, trace every causal chain to its root, and resolve the underlying cause rather than any surface symptom.\n"
    "Do not stop reasoning until you have independently verified the solution from multiple angles and are certain that no assumption remains unchecked and no error remains undiscovered.\n\n"
)# profile -> effort 档位 -> 前缀文案。
# 两个 profile 的 high 档都复用 preview 的 "Absolute maximum" 文案,
# 只有 max 档在 official 下换成 "Beyond maximum"。
REASONING_EFFORT_PROFILES = {
    "preview": {
        "high": "",
        "max": REASONING_EFFORT_PREVIEW_MAX,
    },
    "official": {
        "low": "",
        "high": REASONING_EFFORT_PREVIEW_MAX,
        "max": REASONING_EFFORT_OFFICIAL_MAX,
    },
}
​
​
def render_message(
    index: int,
    messages: List[Dict[str, Any]],
    thinking_mode: str,
    drop_thinking: bool = True,
    reasoning_effort: Optional[str] = None,
    reasoning_effort_profile: str = "preview",
) -> str:
    ...
    # 校验 profile 合法性后取出当前档位映射
    if reasoning_effort_profile not in REASONING_EFFORT_PROFILES:
        raise ValueError(
            f"Invalid reasoning effort profile: {reasoning_effort_profile!r}; "
            f"expected one of {list(REASONING_EFFORT_PROFILES)}"
        )
    effort_prompts = REASONING_EFFORT_PROFILES[reasoning_effort_profile]
    # 未显式指定 effort 时:official 默认 low(无前缀),preview 默认 high(无前缀)
    if reasoning_effort is None:
        reasoning_effort = "low" if reasoning_effort_profile == "official" else "high"
    if reasoning_effort not in effort_prompts:
        raise ValueError(
            f"Invalid reasoning effort {reasoning_effort!r} for profile "
            f"{reasoning_effort_profile!r}; expected one of {list(effort_prompts)}"
        )
    # 前缀只在 thinking 模式的首条消息前插入
    if index == 0 and thinking_mode == "thinking":
        prompt += effort_prompts[reasoning_effort]
    # 后续按 role 渲染 system / developer / user / assistant 消息

评论区精华

并行开发冲突与接力 other

JustinTong0323 在 issue 中表示两人并行开发产生碰撞("We are working in parallel 🤣 there are some collisions, may you mind I push mine and then you can continue?"),mmangkad 回应其最后一个提交刚修复 UT 失败,允许覆盖。

结论:JustinTong0323 接管后续提交(检测重构、启动期解析、preview/official 重命名),mmangkad 最终完成端到端验证并 LGTM。 · 已解决

B300 端到端验证两种 profile 测试

mmangkad 用 deepseek-ai/DeepSeek-V4-Flash-0731 与 DeepSeek-V4-Flash 在 B300 上验证:official profile 正确遵循 low/high/max 映射,preview 保留 high/max 行为并从 low 回退到 high;prompt-token 计数匹配预期 effort 前缀,86 个 serving-chat 单测全部通过。

结论:验证通过,确认两代契约行为符合预期且无回归。 · 已解决

preview/official 命名取舍 设计

mmangkad 认可 preview/official 重命名("I like the preview / official rename too, especially with the official Pro release in mind, since it likely will not use the Flash-specific 0731 date"),认为按能力形态命名比按日期命名更稳健。

结论:采用 preview/official 命名,为后续 DSV4 Pro 发布预留扩展空间。 · 已解决

风险与影响

  1. chat_encoding.py 的检测依赖 AST 静态解析:若官方 encoder 改为条件赋值、函数返回或文件超过 1 MiB,检测将回退到 preview,Silently 使用旧契约(不崩溃但可能不符合预期)。
  2. 启动期网络依赖:model_path 非本地路径时 hf_hub_download 会阻塞启动流程,Hub 不可达、revision 缺失或仓库未包含该文件都会走回退分支。
  3. 前缀缓存与行为变化:同一对话在 preview(high 无前缀)与 official(low 无前缀但 max 前缀不同)下产生不同 token 前缀,radix cache 跨契约不共享,可能影响 prefix cache 命中率;preview 下显式请求 low 会被过滤为 None 并静默回退到 high。
  4. override 依赖 hf_config.to_dict() 保留 dsv4_reasoning_effort_profile 键,若模型配置转换处理丢失该字段,转换后的 checkpoint 无法显式指定 profile。
  5. serving_chat.py 中使用 assert reasoning_effort_profile is not None,在 python -O 模式下断言失效(当前构造期已保证非 None,风险较低)。

用户侧:DeepSeek-V4-Flash-0731 用户自动获得官方 low/high/max 三档 effort 语义,旧版 DeepSeek-V4-Flash 用户行为保持不变(向后兼容);OpenAI 兼容层新增 dsv4_reasoning_effort_profile 模型配置覆盖点,转换的 checkpoint 可自声明契约。系统侧:启动流程新增一次最多 1 MiB 的 encoder 文件读取与 AST 解析;请求编码路径增加一次按 profile 过滤 effort 的字典查找,开销可忽略。团队侧:为 DSV4 Pro 或后续版本预留了 checkpoint 自描述 + 显式 override 双通道,降低模型发布时间表对 SGLang 代码的耦合。

启动期 Hugging Face Hub 网络依赖 AST 检测对 checkpoint 结构敏感 profile 切换影响前缀缓存命中 preview 档 low 请求静默回退 high override 依赖模型配置保留字段

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论