Skip to content

[Bug] thinking_budget not enforced for Qwen3.6 — reasoning consumes all max_tokens #25536

Description

@HH1162

title: "[Bug] thinking_budget not enforced for Qwen3.6 — reasoning consumes all max_tokens"
labels: ["bug", "reasoning", "qwen3"]

Summary

The thinking_budget parameter does not limit reasoning token count for Qwen3.6 models in SGLang. Despite setting thinking_budget: 200, the model generates ~1400 tokens of reasoning content and leaves zero tokens for the final answer (content: null).

This was tested on SGLang v0.5.11 → v0.5.12 with Qwen3.6-27B-FP8. Note that this behavior persists regardless of whether speculative decoding is enabled or disabled.

Environment

  • SGLang version: 0.5.11 → upgraded to 0.5.12
  • Model: Qwen3.6-27B-FP8 (Qwen3_5ForConditionalGeneration)
  • GPU: NVIDIA RTX PRO 6000 Blackwell 96GB
  • Quantization: FP8
  • OS: Linux 6.17.0-29-generic

Server Launch Command

sglang serve \
  --model-path /path/to/qwen3.6-fp8 \
  --trust-remote-code \
  --attention-backend flashinfer \
  --context-length 260000 \
  --dtype auto \
  --quantization fp8 \
  --enable-cache-report \
  --max-running-requests 16 \
  --max-queued-requests 128 \
  --enable-dynamic-chunking \
  --chunked-prefill-size 8192 \
  --radix-eviction-policy lfu \
  --kv-cache-dtype fp8_e4m3 \
  --enable-strict-thinking \
  --tp-size 1 \
  --host 127.0.0.1 \
  --mamba-scheduler-strategy extra_buffer \
  --preferred-sampling-params '{"custom_params": {"thinking_budget": 200}}' \
  --chat-template /path/to/chat_template.jinja \
  --reasoning-parser qwen3-thinking \
  --tool-call-parser qwen3_coder \
  --enable-prefill-delayer \
  --enable-breakable-cuda-graph \
  --port 1234 \
  --max-prefill-tokens 65535 \
  --mem-fraction-static 0.8

How thinking_budget Was Passed

We tried two different approaches to enforce the budget, neither of which worked.

Phase 1: Custom Logit Processor

Initially, we attempted to use the built-in custom logit processor mechanism:

Server flag: --enable-custom-logit-processor

API Payload:

{
  "custom_logit_processor": "{\"callable\": \"<serialized_dill_hex>\"}",
  "custom_params": {"thinking_budget": 200}
}

Result: Failed. We discovered that Qwen3ThinkingBudgetLogitProcessor hardcodes token IDs (151667/151668) that don't match our model (248068/248069). After manually patching the source to fix the IDs, the processor still did not enforce the budget — reasoning output reached ~6215 characters unconstrained. The logit processor appeared to generate the thinking end token (EOS for reasoning), but the model continued generating reasoning content as if it hadn't seen it.

Phase 2: Strict Thinking

After the logit processor approach failed, we switched to using --enable-strict-thinking with preferred-sampling-params:

Server flags:

--enable-strict-thinking \
--preferred-sampling-params '{"custom_params": {"thinking_budget": 200}}' \
--reasoning-parser qwen3-thinking

Per-request payload:

{
  "model": "Qwen3-27B",
  "messages": [{"role": "user", "content": "请详细推导鸡兔同笼问题(35只头94只脚)的至少3种解法"}],
  "max_tokens": 1024,
  "extra_body": {"thinking_budget": 200}
}

Result: Also failed. The reasoning content was not limited (~1400 tokens generated vs 200 requested), and content was always null. This behavior persisted regardless of whether speculative decoding was enabled or disabled.

Results

Test reasoning_content content total_tokens
Run 1 2702 chars (~1351 tokens) null 1058
Run 2 2738 chars (~1369 tokens) null 1058
Run 3 2860 chars (~1430 tokens) null 1058

Expected: reasoning ~200 tokens, content ~800+ tokens.
Actual: All tokens consumed by reasoning, no answer produced. Budget exceeded by 7x.

Issues Found

1. Token ID mismatch in Qwen3ThinkingBudgetLogitProcessor

File: sglang/srt/sampling/custom_logit_processor.py

The processor hardcodes thinking token IDs that don't match the deployed model:

Token Hardcoded in SGLang Actual in Model Tokenizer
THINKING_START_TOKEN_ID 151667 248068
THINKING_END_TOKEN_ID 151668 248069

After manually patching the source to use the correct IDs (248068/248069), the logit processor still did not enforce the budget — reasoning output reached ~6215 characters unconstrained. The logit processor appeared to generate the thinking end token (EOS for reasoning), but the model continued generating reasoning content as if it hadn't seen it, suggesting the logit processor's token suppression is not being applied during generation.

2. --enable-strict-thinking does not enforce thinking_budget

Even with --enable-strict-thinking enabled and thinking_budget passed via both global params and per-request payload, the reasoning content was not limited. The flag appears to accept the parameter but does not implement hard token counting during generation.

We also tested with speculative decoding enabled (--speculative-algorithm NEXTN) and disabled — the budget constraint failed in both cases, confirming the issue lies within the strict thinking/logit processor implementation itself.

Expected Behavior

When thinking_budget: 200 is set, reasoning should be limited to ~200 tokens regardless of whether custom logit processor or strict thinking is used. Remaining max_tokens should be available for the final answer.

Reproduction Steps

  1. Start server using the launch command above.
  2. Send a chat completion request with max_tokens: 1024 and extra_body.thinking_budget: 200.
  3. Observe that reasoning_content far exceeds 200 tokens and content is null.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions