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
- Start server using the launch command above.
- Send a chat completion request with
max_tokens: 1024 and extra_body.thinking_budget: 200.
- Observe that
reasoning_content far exceeds 200 tokens and content is null.
title: "[Bug] thinking_budget not enforced for Qwen3.6 — reasoning consumes all max_tokens"
labels: ["bug", "reasoning", "qwen3"]
Summary
The
thinking_budgetparameter does not limit reasoning token count for Qwen3.6 models in SGLang. Despite settingthinking_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
Qwen3_5ForConditionalGeneration)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.8How 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-processorAPI Payload:
{ "custom_logit_processor": "{\"callable\": \"<serialized_dill_hex>\"}", "custom_params": {"thinking_budget": 200} }Result: Failed. We discovered that
Qwen3ThinkingBudgetLogitProcessorhardcodes 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-thinkingwithpreferred-sampling-params:Server flags:
--enable-strict-thinking \ --preferred-sampling-params '{"custom_params": {"thinking_budget": 200}}' \ --reasoning-parser qwen3-thinkingPer-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
contentwas alwaysnull. This behavior persisted regardless of whether speculative decoding was enabled or disabled.Results
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.pyThe processor hardcodes thinking token IDs that don't match the deployed model:
151667248068151668248069After 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-thinkingenabled andthinking_budgetpassed 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: 200is set, reasoning should be limited to ~200 tokens regardless of whether custom logit processor or strict thinking is used. Remainingmax_tokensshould be available for the final answer.Reproduction Steps
max_tokens: 1024andextra_body.thinking_budget: 200.reasoning_contentfar exceeds 200 tokens andcontentisnull.