Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs_new/docs/references/environment_variables.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -712,7 +712,7 @@ SGLang supports various environment variables that can be used to configure its
</tr>
<tr>
<td style={{padding: "9px 12px", fontWeight: 500, backgroundColor: "rgba(255,255,255,0.02)"}}><code>SGLANG_DSV4_REASONING_EFFORT</code></td>
<td style={{padding: "9px 12px", backgroundColor: "rgba(255,255,255,0.05)"}}>Default <code>reasoning_effort</code> for the DeepSeek V4 chat encoder when a request does not set it (accepts <code>max</code>, <code>high</code>; empty means unset).</td>
<td style={{padding: "9px 12px", backgroundColor: "rgba(255,255,255,0.05)"}}>Default <code>reasoning_effort</code> for the DeepSeek V4 chat encoder when a request does not set it. Accepts <code>high</code> and <code>max</code>; checkpoints with bundled DSpark config also accept <code>low</code> and use the checkpoint's low/high/max mapping. Empty means unset.</td>
<td style={{padding: "9px 12px", backgroundColor: "rgba(255,255,255,0.02)"}}><code>""</code></td>
</tr>
<tr>
Expand Down
62 changes: 47 additions & 15 deletions python/sglang/srt/entrypoints/openai/encoding_dsv4.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,25 @@

tool_output_template: str = "<tool_result>{content}</tool_result>"

REASONING_EFFORT_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_PROMPTS: Dict[str, str] = {
"low": "",
"high": (
"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"
),
"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"
),
}
DEFAULT_REASONING_EFFORT = "low"
LEGACY_REASONING_EFFORT_MAP: Dict[Optional[str], str] = {
None: "low",
"high": "low",
"max": "high",
}

TOOLS_TEMPLATE = """## Tools

Expand Down Expand Up @@ -250,6 +264,7 @@ def render_message(
thinking_mode: str,
drop_thinking: bool = True,
reasoning_effort: Optional[str] = None,
use_three_tier_reasoning_effort: bool = False,
) -> str:
"""
Render a single message at the given index into its encoded string form.
Expand All @@ -262,7 +277,10 @@ 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: In three-tier mode, one of "low", "high", "max", with
None treated as "low". Otherwise one of None, "high", "max", where
"high" adds no prefix and "max" uses the prompt now named "high".
use_three_tier_reasoning_effort: Use the newer checkpoint's three effort tiers.

Returns:
Encoded string for this message.
Expand Down Expand Up @@ -290,14 +308,22 @@ 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
# The original checkpoint maps max to the prompt now named high; high adds
# nothing. The 0731 checkpoint defines low/high/max as distinct tiers.
if use_three_tier_reasoning_effort:
reasoning_effort = reasoning_effort or DEFAULT_REASONING_EFFORT
assert reasoning_effort in REASONING_EFFORT_PROMPTS, (
f"Invalid reasoning effort: {reasoning_effort}, "
f"expected one of {list(REASONING_EFFORT_PROMPTS)}"
)
else:
assert reasoning_effort in LEGACY_REASONING_EFFORT_MAP, (
f"Invalid reasoning effort: {reasoning_effort}, "
f"expected one of {list(LEGACY_REASONING_EFFORT_MAP)}"
)
reasoning_effort = LEGACY_REASONING_EFFORT_MAP[reasoning_effort]
if index == 0 and thinking_mode == "thinking":
prompt += REASONING_EFFORT_PROMPTS[reasoning_effort]

if role == "system":
prompt += system_msg_template.format(content=content or "")
Expand Down Expand Up @@ -583,6 +609,7 @@ def encode_messages(
drop_thinking: bool = True,
add_default_bos_token: bool = True,
reasoning_effort: Optional[str] = None,
use_three_tier_reasoning_effort: bool = False,
) -> str:
"""
Encode a list of messages into the DeepSeek-V4 prompt format.
Expand All @@ -600,7 +627,11 @@ 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: Only takes effect in thinking mode. In three-tier mode,
one of "low", "high", "max", with None treated as "low". Otherwise
one of None, "high", "max", where "high" adds no prefix and "max"
uses the prompt now named "high".
use_three_tier_reasoning_effort: Use the newer checkpoint's three effort tiers.

Returns:
The encoded prompt string.
Expand Down Expand Up @@ -640,6 +671,7 @@ def encode_messages(
thinking_mode=thinking_mode,
drop_thinking=effective_drop_thinking,
reasoning_effort=reasoning_effort,
use_three_tier_reasoning_effort=use_three_tier_reasoning_effort,
)

return prompt
Expand Down
20 changes: 18 additions & 2 deletions python/sglang/srt/entrypoints/openai/serving_chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,17 @@ def __init__(
# 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_uses_three_tier_reasoning_effort = False
if self.chat_encoding_spec == "dsv4":
from sglang.srt.speculative.dspark_components.dspark_config import (
checkpoint_bundles_dspark_draft,
)

self._dsv4_uses_three_tier_reasoning_effort = (
checkpoint_bundles_dspark_draft(
getattr(self.tokenizer_manager.model_config, "hf_config", None)
)
)

# Resolve the env-configured Inkling effort default once: the env var is
# frozen for the server's lifetime, and a misconfigured value should
Expand Down Expand Up @@ -986,16 +997,20 @@ def _apply_jinja_template(

# 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
valid_efforts = (
("low", "high", "max")
if self._dsv4_uses_three_tier_reasoning_effort
else ("high", "max")
)
v4_reasoning_effort = (
effort_source if effort_source in ("max", "high") else None
effort_source if effort_source in valid_efforts else None
)
if request.task is not None:
encoding_dsv4.attach_task_to_last_user_message(
Expand All @@ -1005,6 +1020,7 @@ def _apply_jinja_template(
messages,
thinking_mode=thinking_mode,
reasoning_effort=v4_reasoning_effort,
use_three_tier_reasoning_effort=self._dsv4_uses_three_tier_reasoning_effort,
)
prompt_ids = self.tokenizer_manager.tokenizer.encode(real_input)
else:
Expand Down
2 changes: 2 additions & 0 deletions python/sglang/srt/environ.py
Original file line number Diff line number Diff line change
Expand Up @@ -1086,6 +1086,8 @@ class Envs:
SGLANG_DSV4_FP4_DEQUANT = 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.
# "low" is additionally accepted only for checkpoints selected by the bundled
# DSpark marker, which use the three-tier low/high/max mapping.
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.
Expand Down
149 changes: 149 additions & 0 deletions test/registered/unit/entrypoints/openai/test_serving_chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ def __init__(self):
# Mock hf_config for _resolve_chat_encoding_spec check
mock_hf_config = Mock()
mock_hf_config.architectures = ["LlamaForCausalLM"]
mock_hf_config.dspark_block_size = None
mock_hf_config.dspark_markov_rank = None
mock_hf_config.dspark_noise_token_id = None
mock_hf_config.dspark_target_layer_ids = None
self.model_config.hf_config = mock_hf_config

self.chat_template_name: Optional[str] = "llama-3"
Expand Down Expand Up @@ -1256,6 +1260,68 @@ def test_dpsk_v32_encoding_path(self):
serving_chat = OpenAIServingChat(tm, TemplateManager())
self.assertEqual(serving_chat.chat_encoding_spec, "dsv4")

def test_dsv4_0731_reasoning_effort_detection(self):
from sglang.srt.parser.template_manager import TemplateManager

tm = _MockTokenizerManager()
mock_hf_config = tm.model_config.hf_config
mock_hf_config.architectures = ["DeepseekV4ForCausalLM"]

serving_chat = OpenAIServingChat(tm, TemplateManager())
self.assertFalse(serving_chat._dsv4_uses_three_tier_reasoning_effort)
request = ChatCompletionRequest(
model="x",
messages=[{"role": "user", "content": "Hello"}],
reasoning_effort="max",
)
serving_chat._process_messages(request, is_multimodal=False)
legacy_prompt = tm.tokenizer.encode.call_args.args[0]
self.assertIn("Reasoning Effort: Absolute maximum", legacy_prompt)
self.assertNotIn("Reasoning Effort: Beyond maximum", legacy_prompt)
for effort in ("low", "medium"):
request = ChatCompletionRequest(
model="x",
messages=[{"role": "user", "content": "Hello"}],
reasoning_effort=effort,
)
serving_chat._process_messages(request, is_multimodal=False)
self.assertNotIn("Reasoning Effort:", tm.tokenizer.encode.call_args.args[0])

mock_hf_config.dspark_block_size = 5
serving_chat = OpenAIServingChat(tm, TemplateManager())
self.assertTrue(serving_chat._dsv4_uses_three_tier_reasoning_effort)
request = ChatCompletionRequest(
model="x",
messages=[{"role": "user", "content": "Hello"}],
reasoning_effort="max",
)
serving_chat._process_messages(request, is_multimodal=False)
prompt_0731 = tm.tokenizer.encode.call_args.args[0]
self.assertIn("Reasoning Effort: Beyond maximum", prompt_0731)

request = ChatCompletionRequest(
model="x",
messages=[{"role": "user", "content": "Hello"}],
reasoning_effort="medium",
)
serving_chat._process_messages(request, is_multimodal=False)
self.assertNotIn("Reasoning Effort:", tm.tokenizer.encode.call_args.args[0])

mock_hf_config.dspark_block_size = None
mock_hf_config.dspark_markov_rank = 2
serving_chat = OpenAIServingChat(tm, TemplateManager())
self.assertTrue(serving_chat._dsv4_uses_three_tier_reasoning_effort)
request = ChatCompletionRequest(
model="x",
messages=[{"role": "user", "content": "Hello"}],
reasoning_effort="max",
)
serving_chat._process_messages(request, is_multimodal=False)
self.assertIn(
"Reasoning Effort: Beyond maximum", tm.tokenizer.encode.call_args.args[0]
)
mock_hf_config.dspark_markov_rank = None

# ------------- dsv4 task + latest_reminder -------------
def test_dsv4_task_field_schema(self):
"""Top-level `task` accepts the 6 DS task tokens and rejects others."""
Expand Down Expand Up @@ -1406,6 +1472,89 @@ def test_dsv4_task_and_reminder_encode_end_to_end(self):
)
self.assertIn("<|Assistant|>", out)

def test_dsv4_reasoning_effort_checkpoint_compatibility(self):
from sglang.srt.entrypoints.openai import encoding_dsv4

messages = [{"role": "user", "content": "Hello"}]
base = "<|begin▁of▁sentence|><|User|>Hello" "<|Assistant|><think>"
high_prefix = (
"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"
)
max_prefix = (
"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"
)

self.assertEqual(
encoding_dsv4.encode_messages(messages, thinking_mode="thinking"),
base,
)
self.assertEqual(
encoding_dsv4.encode_messages(
messages, thinking_mode="thinking", reasoning_effort="high"
),
base,
)
self.assertEqual(
encoding_dsv4.encode_messages(
messages, thinking_mode="thinking", reasoning_effort="max"
),
f"<|begin▁of▁sentence|>{high_prefix}{base[len('<|begin▁of▁sentence|>'):]}",
)

with self.assertRaises(AssertionError):
encoding_dsv4.encode_messages(
messages, thinking_mode="thinking", reasoning_effort="low"
)

self.assertEqual(
encoding_dsv4.encode_messages(
messages,
thinking_mode="thinking",
use_three_tier_reasoning_effort=True,
),
base,
)
self.assertEqual(
encoding_dsv4.encode_messages(
messages,
thinking_mode="thinking",
reasoning_effort="low",
use_three_tier_reasoning_effort=True,
),
base,
)
self.assertEqual(
encoding_dsv4.encode_messages(
messages,
thinking_mode="thinking",
reasoning_effort="high",
use_three_tier_reasoning_effort=True,
),
f"<|begin▁of▁sentence|>{high_prefix}{base[len('<|begin▁of▁sentence|>'):]}",
)
self.assertEqual(
encoding_dsv4.encode_messages(
messages,
thinking_mode="thinking",
reasoning_effort="max",
use_three_tier_reasoning_effort=True,
),
f"<|begin▁of▁sentence|>{max_prefix}{base[len('<|begin▁of▁sentence|>'):]}",
)
self.assertEqual(
encoding_dsv4.encode_messages(
messages,
thinking_mode="chat",
reasoning_effort="max",
use_three_tier_reasoning_effort=True,
),
"<|begin▁of▁sentence|><|User|>Hello<|Assistant|></think>",
)

def test_streaming_abort_yields_error(self):
"""Test that an abort finish reason during streaming correctly yields an error and stops."""
err_msg = "Aborted by scheduler"
Expand Down
Loading