[DSV4] Add official DSV4 reasoning effort support (#33140)

Co-authored-by: Xinyuan Tong <xinyuantong.cs@gmail.com>
Co-authored-by: David Orman <ormandj@corenode.com>
This commit is contained in:
Mohammad Miadh Angkad
2026-08-05 12:50:41 +08:00
committed by GitHub
co-authored by Xinyuan Tong David Orman
parent 198a3bc29b
commit 059269594c
6 changed files with 321 additions and 24 deletions
@@ -7,8 +7,103 @@ it here instead of re-deriving it from model architectures themselves.
from __future__ import annotations
import ast
import logging
from pathlib import Path
from typing import Any, Dict, List, Optional
from sglang.srt.entrypoints.openai import encoding_dsv4
logger = logging.getLogger(__name__)
DSV4_REASONING_EFFORT_PROFILE_OVERRIDE = "dsv4_reasoning_effort_profile"
_DSV4_REASONING_EFFORT_ENCODER = "encoding/encoding_dsv4.py"
_MAX_DSV4_ENCODER_BYTES = 1 << 20
def _detect_dsv4_reasoning_effort_profile(
model_path: str, revision: Optional[str] = None
) -> Optional[str]:
encoder_path = Path(model_path) / _DSV4_REASONING_EFFORT_ENCODER
try:
if not encoder_path.is_file():
from huggingface_hub import hf_hub_download
encoder_path = Path(
hf_hub_download(
model_path,
_DSV4_REASONING_EFFORT_ENCODER,
revision=revision,
)
)
if encoder_path.stat().st_size > _MAX_DSV4_ENCODER_BYTES:
return None
tree = ast.parse(encoder_path.read_text(encoding="utf-8"))
except Exception as error:
logger.debug(
"Could not inspect DeepSeek-V4 checkpoint encoder at %s: %s",
encoder_path,
error,
)
return None
assignments = {}
for node in tree.body:
if isinstance(node, ast.Assign):
targets = node.targets
value = node.value
elif isinstance(node, ast.AnnAssign):
targets = [node.target]
value = node.value
else:
continue
for target in targets:
if not isinstance(target, ast.Name):
continue
try:
assignments[target.id] = ast.literal_eval(value)
except (TypeError, ValueError):
continue
prompts = assignments.get("REASONING_EFFORT_PROMPTS")
if (
assignments.get("DEFAULT_REASONING_EFFORT") == "low"
and isinstance(prompts, dict)
and {"low", "high", "max"} <= prompts.keys()
):
return "official"
if "REASONING_EFFORT_MAX" in assignments:
return "preview"
return None
def _validate_dsv4_reasoning_effort_profile(profile: str) -> str:
if profile not in encoding_dsv4.REASONING_EFFORT_PROFILES:
raise ValueError(
f"Invalid {DSV4_REASONING_EFFORT_PROFILE_OVERRIDE}: {profile!r}; "
f"expected one of {list(encoding_dsv4.REASONING_EFFORT_PROFILES)}"
)
return profile
def resolve_dsv4_reasoning_effort_profile(
*,
model_path: str,
revision: Optional[str] = None,
override: Optional[str] = None,
) -> str:
if override is not None:
return _validate_dsv4_reasoning_effort_profile(override)
return (
_detect_dsv4_reasoning_effort_profile(
model_path=model_path,
revision=revision,
)
or "preview"
)
def resolve_chat_encoding_spec(
*,
@@ -60,12 +60,30 @@ tool_calls_block_name: str = "tool_calls"
tool_output_template: str = "<tool_result>{content}</tool_result>"
REASONING_EFFORT_MAX = (
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"
)
REASONING_EFFORT_PROFILES = {
"preview": {
"high": "",
"max": REASONING_EFFORT_PREVIEW_MAX,
},
"official": {
"low": "",
"high": REASONING_EFFORT_PREVIEW_MAX,
"max": REASONING_EFFORT_OFFICIAL_MAX,
},
}
TOOLS_TEMPLATE = """## Tools
You have access to a set of tools to help answer the user's question. You can invoke tools by writing a "<{dsml_token}tool_calls>" block like the following:
@@ -250,6 +268,7 @@ def render_message(
thinking_mode: str,
drop_thinking: bool = True,
reasoning_effort: Optional[str] = None,
reasoning_effort_profile: str = "preview",
) -> str:
"""
Render a single message at the given index into its encoded string form.
@@ -262,7 +281,9 @@ def render_message(
messages: Full list of messages in the conversation.
thinking_mode: Either "chat" or "thinking".
drop_thinking: Whether to drop reasoning content from earlier turns.
reasoning_effort: Optional reasoning effort level ("max", "high", or None).
reasoning_effort: Optional reasoning effort level. The preview profile accepts
"high" and "max"; the official profile accepts "low", "high", and "max".
reasoning_effort_profile: DeepSeek-V4 effort mapping ("preview" or "official").
Returns:
Encoded string for this message.
@@ -290,14 +311,21 @@ def render_message(
if tool_calls:
tool_calls = tool_calls_from_openai_format(tool_calls)
# Reasoning effort prefix (only at index 0 in thinking mode with max effort)
assert reasoning_effort in [
"max",
None,
"high",
], f"Invalid reasoning effort: {reasoning_effort}"
if index == 0 and thinking_mode == "thinking" and reasoning_effort == "max":
prompt += REASONING_EFFORT_MAX
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]
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)}"
)
if index == 0 and thinking_mode == "thinking":
prompt += effort_prompts[reasoning_effort]
if role == "system":
prompt += system_msg_template.format(content=content or "")
@@ -583,6 +611,7 @@ def encode_messages(
drop_thinking: bool = True,
add_default_bos_token: bool = True,
reasoning_effort: Optional[str] = None,
reasoning_effort_profile: str = "preview",
) -> str:
"""
Encode a list of messages into the DeepSeek-V4 prompt format.
@@ -600,7 +629,9 @@ def encode_messages(
drop_thinking: If True, drop reasoning_content from earlier assistant turns
(only keep reasoning for messages after the last user message).
add_default_bos_token: Whether to prepend BOS token at conversation start.
reasoning_effort: Optional reasoning effort level ("max", "high", or None).
reasoning_effort: Optional reasoning effort level. The preview profile accepts
"high" and "max"; the official profile accepts "low", "high", and "max".
reasoning_effort_profile: DeepSeek-V4 effort mapping ("preview" or "official").
Returns:
The encoded prompt string.
@@ -640,6 +671,7 @@ def encode_messages(
thinking_mode=thinking_mode,
drop_thinking=effective_drop_thinking,
reasoning_effort=reasoning_effort,
reasoning_effort_profile=reasoning_effort_profile,
)
return prompt
@@ -24,7 +24,7 @@ from fastapi import Request
from fastapi.responses import ORJSONResponse, StreamingResponse
from jsonschema import Draft202012Validator, SchemaError
from sglang.srt.entrypoints.openai import encoding_dsv4, encoding_dsv32
from sglang.srt.entrypoints.openai import chat_encoding, encoding_dsv4, encoding_dsv32
from sglang.srt.entrypoints.openai.protocol import (
ChatCompletionMessageGenericParam,
ChatCompletionRequest,
@@ -253,6 +253,17 @@ class OpenAIServingChat(OpenAIServingBase):
# Which Python-based chat encoder (if any) bypasses apply_chat_template.
# Values: "dsv32", "dsv4", or custom values set by subclass. None for default.
self.chat_encoding_spec = self._resolve_chat_encoding_spec()
self._dsv4_reasoning_effort_profile = (
chat_encoding.resolve_dsv4_reasoning_effort_profile(
model_path=self.tokenizer_manager.model_path,
revision=self.tokenizer_manager.server_args.revision,
override=self.tokenizer_manager.model_config.hf_config.to_dict().get(
chat_encoding.DSV4_REASONING_EFFORT_PROFILE_OVERRIDE
),
)
if self.chat_encoding_spec == "dsv4"
else None
)
# Resolve the env-configured Inkling effort default once: the env var is
# frozen for the server's lifetime, and a misconfigured value should
@@ -339,11 +350,7 @@ class OpenAIServingChat(OpenAIServingBase):
Override in subclass to add custom encoding specs.
"""
from sglang.srt.entrypoints.openai.chat_encoding import (
resolve_chat_encoding_spec,
)
return resolve_chat_encoding_spec(
return chat_encoding.resolve_chat_encoding_spec(
hf_config=self.tokenizer_manager.model_config.hf_config,
tokenizer=self.tokenizer_manager.tokenizer,
tool_call_parser=self.tool_call_parser,
@@ -1216,16 +1223,18 @@ class OpenAIServingChat(OpenAIServingBase):
# Default encoding (dsv4/dsv32)
if self.chat_encoding_spec == "dsv4":
# V4 encoder only accepts "max" / "high" / None.
# OpenAI protocol defaults to "medium" which V4 rejects; drop it.
# Fallback: if request didn't set it, try env SGLANG_DSV4_REASONING_EFFORT.
effort_source = request.reasoning_effort
if effort_source is None:
env_val = envs.SGLANG_DSV4_REASONING_EFFORT.get()
if env_val:
effort_source = env_val
reasoning_effort_profile = self._dsv4_reasoning_effort_profile
assert reasoning_effort_profile is not None
accepted_efforts = encoding_dsv4.REASONING_EFFORT_PROFILES[
reasoning_effort_profile
]
v4_reasoning_effort = (
effort_source if effort_source in ("max", "high") else None
effort_source if effort_source in accepted_efforts else None
)
if request.task is not None:
encoding_dsv4.attach_task_to_last_user_message(
@@ -1235,6 +1244,7 @@ class OpenAIServingChat(OpenAIServingBase):
messages,
thinking_mode=thinking_mode,
reasoning_effort=v4_reasoning_effort,
reasoning_effort_profile=reasoning_effort_profile,
)
prompt_ids = self.tokenizer_manager.tokenizer.encode(real_input)
else:
+1 -2
View File
@@ -1156,8 +1156,7 @@ class Envs:
# Copy rank-local MoE slices into independent CPU storage before H2D when
# they reference a larger mmap-backed checkpoint storage.
SGLANG_MOE_COPY_WEIGHT_VIEWS_BEFORE_H2D = EnvBool(False)
# Default reasoning_effort for dsv4 chat encoder when request doesn't set it.
# Accepts "", "max", "high" (empty string means unset); other values filtered to None.
# Flash-0731 also accepts "low"; the active profile is checkpoint-resolved.
SGLANG_DSV4_REASONING_EFFORT = EnvStr("")
# Quantize the SWA fp8 KV cache from bf16-rounded values (matches
# trainer-side QAT and the DSA-CP path) instead of fp32 registers.