GLM-5.2 vLLM Thinking Budget: Keep a Final Answer
Independent research — not an official Z.ai publication.Identity and provider disclosure
Original editorial diagram. It explains capacity and parser gates; it is not a vLLM screenshot, model trace, benchmark, or proof that a particular budget is correct for your prompts.
A short GLM-5.2 request can end with finish_reason: "length", a full
completion allowance, and no visible answer when thinking consumes the whole
output. Raising max_tokens can postpone that failure, but it does not create
an independent ceiling for reasoning. Disabling thinking can work for a simple
dispatch task, but it also changes the model behavior being evaluated.
vLLM exposes a third option: cap only the thinking span. Its current documentation says token counting starts after the reasoning-start marker and, at the configured limit, vLLM forces the reasoning-end marker. That mechanism can preserve capacity for prose or a tool call. It cannot guarantee that the remaining tokens contain a correct answer, a valid call, or a natural stop.
This guide pins the official GLM-5.2 checkpoint at
b4734de4…,
the vLLM v0.28.0 tag at
2cf0a691…,
and the official GLM recipe at
5943215a….
The machine-readable evidence receipt
hashes 21 public artifacts, verifies eight input-value cases, reconstructs one
reported 87-token layout, and calculates seven answer-reserve cells.
In this guide
Section titled “In this guide”- Separate the three output controls
- Start from a pinned vLLM launch
- Verify the GLM-5.2 marker contract
- Send a bounded request
- Reserve answer capacity explicitly
- Interpret the upstream GLM-5.2 runs
- Validate accepted budget values
- Count token IDs, not characters or chunks
- Gate streaming tool calls separately
- Use the qualified version boundary
- Run a one-variable acceptance matrix
- Diagnose failures without hiding them
- Rent a canary cluster only after source checks
- Frequently asked questions
- Sources and method
Separate the three output controls
Section titled “Separate the three output controls”The similar names hide different contracts. Decide which surface you operate before copying a field.
| Control | Audited surface | What it changes | What it does not prove |
|---|---|---|---|
max_tokens |
Z.AI hosted API and vLLM-compatible requests | Maximum generated completion capacity | How much is thinking versus visible content |
reasoning_effort |
Z.AI’s documented GLM-5.2 API | A hosted reasoning-effort level such as low, high, or max | A fixed number of reasoning tokens |
thinking_token_budget |
vLLM sampling extension in this audit | Maximum tokens counted inside the configured reasoning span | Hosted Z.AI acceptance, answer correctness, or adequate reasoning |
enable_thinking: false |
GLM chat-template argument | Starts generation with an empty thinking block | Equivalent behavior to a small numeric budget |
The GLM-5.2 max-tokens guide sizes the outer completion and context envelope. The reasoning-effort guide covers the hosted effort labels. This page begins only after you have chosen self-hosted vLLM and need a numeric ceiling inside that outer output allowance.
Do not send thinking_token_budget blindly through a generic provider proxy.
The current Z.AI parameter documentation describes max_tokens, thinking,
and reasoning_effort, but this audit did not find the vLLM field in that
hosted contract. A proxy may reject it, remove it, forward it to a different
engine, or accept it without enforcement. Record the final route and response,
not merely the request object your application created.
Start from a pinned vLLM launch
Section titled “Start from a pinned vLLM launch”The official vLLM recipe pairs GLM-5.2 with the glm45 reasoning parser. Tool
work adds the glm47 tool parser and automatic tool choice. Pin the image and
checkpoint rather than using an unqualified nightly:
vllm serve zai-org/GLM-5.2-FP8 \ --served-model-name glm-5.2-fp8 \ --tensor-parallel-size 8 \ --reasoning-parser glm45 \ --chat-template-content-format string \ --max-model-len 131072For a tool-call canary, add the two documented recipe controls:
--tool-call-parser glm47 \ --enable-auto-tool-choiceThe launch is incomplete without an exact image digest, vLLM version and commit, model repository and revision, Transformers version, GPU topology, driver/runtime, served context, MTP configuration, and the final parsed arguments. Preserve those values beside each result. A copied command on a different runner, vendor fork, or checkpoint can exercise a different parser and sampler path.
--reasoning-parser matters because vLLM documents budget enforcement only
when it knows the reasoning boundaries. The field being accepted by the HTTP
schema is not proof that a usable start marker, end marker, and natural end
marker were resolved. Treat a startup warning about reasoning configuration as
a failed preflight, not as harmless logging.
Verify the GLM-5.2 marker contract
Section titled “Verify the GLM-5.2 marker contract”The pinned official tokenizer and template make the GLM-5.2 boundary concrete:
| Role | Text | Token ID | Why it matters |
|---|---|---|---|
| Thinking starts | <think> |
154841 |
The prompt template primes this marker when thinking is enabled. |
| Thinking ends | </think> |
154842 |
vLLM forces this boundary after the numeric budget is exhausted. |
| Reported stop | <|user|> |
154827 |
It is one of three EOS IDs in the pinned generation config and closes the attributed token layout. |
The official chat template ends an enabled generation prompt with <think>.
When enable_thinking is false, it ends with <think></think> instead. That
is why thinking_token_budget: 0 and disabled thinking are related but not
identical: zero remains a valid vLLM budget on a thinking-primed request,
whereas the template switch closes the block before decoding starts.
Audit the files actually installed in your image without initializing CUDA:
from importlib.metadata import distributionfrom pathlib import Path
dist = distribution("vllm")root = Path(dist.locate_file(""))sampling = (root / "vllm/sampling_params.py").read_text()protocol = ( root / "vllm/entrypoints/openai/chat_completion/protocol.py").read_text()
print("vllm:", dist.version)print("budget_validation:", "validate_thinking_token_budget" in sampling)print("chat_field:", "thinking_token_budget" in protocol)Hash both files and record the container digest. Those booleans confirm two source surfaces only; they do not execute the GPU sampler, resolve GLM markers, or validate a model response. A fork can rename equivalent code, so inspect the diff when a boolean is false rather than changing it to force a pass.
Send a bounded request
Section titled “Send a bounded request”Begin with a short, acceptance-test prompt and enough outer capacity to observe
both reasoning and content. The value 32 below is a fixture, not a general
recommendation:
curl -sS http://127.0.0.1:8000/v1/chat/completions \ -H 'Content-Type: application/json' \ -d '{ "model": "glm-5.2-fp8", "messages": [ {"role": "user", "content": "Return exactly two sentences: state the decision, then one reason."} ], "temperature": 0, "max_tokens": 192, "thinking_token_budget": 32, "return_token_ids": true }'If your OpenAI client does not model the extension, put it in extra_body:
from openai import OpenAI
client = OpenAI(base_url="http://127.0.0.1:8000/v1", api_key="local")response = client.chat.completions.create( model="glm-5.2-fp8", messages=[{ "role": "user", "content": "Return exactly two sentences: state the decision, then one reason.", }], temperature=0, max_tokens=192, extra_body={ "thinking_token_budget": 32, "return_token_ids": True, },)
choice = response.choices[0]assert choice.message.content and choice.message.content.strip()assert choice.finish_reason in {"stop", "tool_calls"}Do not print or persist private reasoning text just to prove the cap. Preserve token counts, marker positions, finish reason, response shape, latency, and a hash or task-specific acceptance result. If your policy permits reasoning retention, store it behind the same access, deletion, and incident controls as the prompt and answer.
Reserve answer capacity explicitly
Section titled “Reserve answer capacity explicitly”Choose a minimum visible-answer allowance from the application contract. For
the attributed GLM-5.2 token-inspected layout, one </think> token and one
terminal stop token also consumed completion capacity. A conservative planning
cell is therefore:
visible_capacity = max( 0, max_tokens - thinking_token_budget - 1 end marker - 1 stop token)This is capacity math, not a content guarantee. A response can stop early, repeat, call a tool, emit more protocol tokens, or fail validation. Tool XML or JSON needs its own reserve. Still, the cell catches configurations that cannot possibly hold the required answer under the audited layout.
max_tokens |
Thinking cap | Required visible reserve | Calculated capacity | Gate |
|---|---|---|---|---|
| 192 | 16 | 64 | 174 | Pass with 110-token margin |
| 192 | 32 | 64 | 158 | Pass with 94-token margin |
| 192 | 126 | 64 | 64 | Exact boundary; operationally fragile |
| 192 | 127 | 64 | 63 | Fail before a model call |
| 4,096 | 512 | 1,024 | 3,582 | Pass with 2,558-token margin |
| 4,096 | 3,070 | 1,024 | 1,024 | Exact boundary; operationally fragile |
| 4,096 | 3,071 | 1,024 | 1,023 | Fail before a model call |
Do not deploy an exact-boundary row. Leave margin for protocol variation and then measure actual accepted-task quality. If the task genuinely needs longer reasoning, raise the outer cap or reduce the required answer size deliberately; do not silently steal tokens from the answer.
Interpret the upstream GLM-5.2 runs
Section titled “Interpret the upstream GLM-5.2 runs”Open vLLM issue
#48201 reports a GLM-5.2
test on v0.24.0 with max_tokens: 192. These are reporter observations, not
our benchmark and not a vLLM default recommendation:
| Reported arm | Reasoning observation | Visible content | Finish state |
|---|---|---|---|
| No explicit budget | 344 characters; completion consumed all 192 tokens | 0 characters | length |
| Budget 16 | 31 reasoning characters | 287 content characters | length |
| Budget 32, token-inspected run | 32 reasoning tokens | 53 content tokens | stop at 87 completion tokens |
The token-inspected row reconstructs exactly:
32 reasoning + 1 </think> + 53 content + 1 stop = 87 completion tokensCharacters are included only because that is what two issue rows published; they are not comparable to token counts. The useful result is narrower: one unbounded fixture exhausted the completion with no content, while bounded fixtures crossed the reasoning boundary and produced content. It does not prove that 16 or 32 is optimal for another language, prompt, task, sampler, model revision, or acceptance threshold.
The same issue reports one streaming tool call with a budget of 16 and the
expected delta.tool_calls shape. That is a single attributed success, not an
availability rate or parser guarantee. Reproduce it against your pinned
server before granting any tool permission.
Validate accepted budget values
Section titled “Validate accepted budget values”The v0.28.0 sampling source has an explicit input contract. Test it at your API boundary so a serializer cannot turn an intended integer into another type:
| Request value | v0.28.0 source result | Operational meaning |
|---|---|---|
Omitted or null |
Accepted, normalized to no budget | No explicit reasoning limit |
-1 |
Accepted, normalized to no budget | Unlimited sentinel, not “disable thinking” |
0 |
Accepted as zero | Force the configured end when the thinking span begins |
16, 32 |
Accepted | Exact non-negative integer cap |
-2 |
Rejected | Other negative values are invalid |
1.5 |
Rejected | Floats are invalid even when numerically positive |
true |
Rejected | Booleans are invalid even though Python treats bool as an int subclass |
Fail before reaching the model when the field is absent after serialization, changes type, exceeds your application limit, or is greater than the outer completion allowance. vLLM’s source accepting a very large integer is not a reason to let untrusted callers reserve it. Apply a service-side maximum tied to cost, latency, queue fairness, and visible-answer capacity.
Also reject an adapter that converts -1 to zero. Those values mean opposite
things here: unlimited versus immediate forced close. Log the normalized
budget beside max_tokens and the resolved route.
Count token IDs, not characters or chunks
Section titled “Count token IDs, not characters or chunks”Streaming chunks do not equal tokens. One chunk can carry several token IDs, and Unicode character length depends on content. The current vLLM E2E test for other reasoning models counts IDs between markers for exactly this reason.
For GLM-5.2, concatenate prompt and decode IDs, find the last start marker, and then find the first end marker after it:
THINK_START = 154841THINK_END = 154842EOS_IDS = {154820, 154827, 154829}
def inspect_layout(prompt_ids: list[int], decode_ids: list[int]) -> dict: all_ids = prompt_ids + decode_ids start = max(i for i, token in enumerate(all_ids) if token == THINK_START) try: end = next( i for i in range(start + 1, len(all_ids)) if all_ids[i] == THINK_END ) except StopIteration as exc: raise AssertionError("missing </think> marker") from exc
reasoning_tokens = end - start - 1 terminal_is_eos = bool(decode_ids and decode_ids[-1] in EOS_IDS) return { "reasoning_tokens": reasoning_tokens, "end_marker_index": end, "terminal_is_eos": terminal_is_eos, }Require reasoning_tokens <= thinking_token_budget; for the current forced
boundary path, test equality on a prompt known to exceed the budget. A smaller
count may mean the model ended thinking naturally, which is valid. Missing
markers, marker text inside final content, or a budgeted response that never
crosses into content is a failed canary.
Do not hard-code the IDs across checkpoint revisions. Resolve them from the exact tokenizer, compare with the stored revision, and stop if they change. The same token number can have a different meaning in another tokenizer.
Gate streaming tool calls separately
Section titled “Gate streaming tool calls separately”Tool calls add a second transition: reasoning must close before the tool parser
can emit a structured function. A historical GLM-5.2 issue
#46040 reported
<tool_call> markup appearing before </think>. The reasoning parser consumed
the markup, leaving no usable tool_calls. Another reporter reproduced it on
NVIDIA as well as the original ROCm setup, so the incident was not safely
classified as one hardware backend. The issue was closed after a maintainer
pointed to merged streaming-parser PR
#45915.
That history does not justify skipping a current test. For each pinned build, run at least these arms:
| Arm | Thinking | Budget | Tools | Required result |
|---|---|---|---|---|
| Plain answer control | Enabled | 32 | None | Reasoning ends, non-empty content, no raw markers |
| Natural-end control | Enabled | Large bounded value | None | Model may end below cap; content remains valid |
| Tool transition | Enabled | 16 or tested value | One deterministic tool | Reasoning ends before delta.tool_calls; arguments parse as JSON |
| No-thinking tool | Disabled in template | None | Same tool | Valid tool call without reasoning output |
| Non-streaming parity | Same as tool transition | Same | Same | Same function and schema contract as the stream |
For the tool arm, require exactly one allowed function name, valid schema,
bounded arguments, finish_reason: "tool_calls", no </think> inside the
argument string, and no raw <tool_call> markup in visible content or
reasoning. Execute only a synthetic read-only function. A well-shaped call is
still untrusted input and grants no authority.
The complete GLM-5.2 tool-calling guide covers argument validation, tool-result messages, retry limits, and idempotency. A thinking budget fixes neither authorization nor the application loop.
Use the qualified version boundary
Section titled “Use the qualified version boundary”Do not compress three different evidence levels into “supported since v0.23.0.”
| vLLM version | What the source establishes | What remains unproven |
|---|---|---|
| v0.23.0 | Official GLM-5.2 model card and recipe set this as the general serving floor. | This audit does not establish the GLM budget field on that tag. |
| v0.24.0 | Issue #48201 reports successful numeric-budget and streaming-tool fixtures. | The report is not a merged GLM-specific regression test or default. |
| v0.28.0 | Tagged source accepts, forwards, and enforces the generic budget; its current E2E suite covers other reasoning models. | The GLM-specific defaults/regression RFC remains open. |
For a new deployment, start evaluation from v0.28.0 or a later pinned stable release because that is the stable source audited here—not because this page proves every GLM-5.2 topology on v0.28.0. If you operate v0.24.0, reproduce the exact field and parser behavior before considering an upgrade; do not treat an issue report as your receipt. If you operate v0.23.0, do not infer a numeric budget from the general model-support floor.
A vendor image can backport or remove behavior. Compare its exact source, resolve the running commit, and test the endpoint. Tags and package metadata are evidence only when they describe the code that actually serves requests.
Run a one-variable acceptance matrix
Section titled “Run a one-variable acceptance matrix”Freeze a small corpus with distinct answer contracts: short factual response, two-sentence decision, bounded code edit explanation, one deterministic tool call, and one task that genuinely benefits from longer reasoning. Keep prompt, checkpoint, sampler, outer cap, server flags, and concurrency constant while changing only the budget.
| Gate | Record | Reject when |
|---|---|---|
| Marker | Start/end IDs and positions | End marker missing, duplicated unexpectedly, or leaks into content |
| Capacity | Prompt, completion, reasoning, content token counts | Required answer reserve is impossible or actual content is empty |
| Semantics | Task-specific validator | A lower cap changes an accepted answer into a wrong or incomplete one |
| Stop | finish_reason and terminal token |
Unexpected length, missing tool transition, or repeated scaffolding |
| Stream | Ordered delta types and token IDs | Tool content arrives inside reasoning or chunks cannot reconstruct the response |
| Service | TTFT, TPOT, total latency, queue time, errors | Tail regression, worker restart, 5xx, or stuck request exceeds the gate |
| Cost | Generated tokens and retries | Savings are erased by retries or human repair |
Use at least four arms: omitted budget, low bounded budget, higher bounded budget, and thinking disabled. Add MTP on/off and streaming/non-streaming only after the simple matrix passes; otherwise too many variables change at once.
Select by cost and latency per accepted result, not shortest reasoning. A low cap that forces three retries is worse than one longer accepted call. A high cap that consistently leaves the required content reserve can be correct for complex tasks. The output contract, not a fashionable integer, is the decision boundary.
Diagnose failures without hiding them
Section titled “Diagnose failures without hiding them”| Symptom | First check | Safer next action |
|---|---|---|
| HTTP rejects the field | Type, exact endpoint, installed protocol source | Stop; do not remove the field and silently run unbounded. |
| Field accepted but reasoning exceeds it | Reasoning parser/config, marker IDs, running commit | Fail the source preflight and inspect the resolved parser. |
finish_reason: length, no content |
Outer cap and observed reasoning span | Lower a tested budget or raise the outer cap while preserving a written answer reserve. |
| Empty content with an end marker | Template, stop IDs, output parser | Compare raw token IDs; do not manufacture content from private reasoning. |
Raw <tool_call> inside reasoning |
Parser transition and version/source | Stop tool execution; reproduce with one synthetic function and inspect PR #45915 lineage. |
| Valid call but wrong arguments | Schema and task acceptance | Reject arguments; the budget is not an argument validator. |
| Budget 0 behaves unlike disabled thinking | Template prefix | Test enable_thinking: false as a separate arm. |
| Hosted API ignores or rejects the field | Route contract | Use documented hosted controls; do not describe the vLLM extension as portable. |
Never “fix” an empty answer by copying reasoning into content. That leaks a
different data class, breaks downstream semantics, and hides the actual
capacity failure. Likewise, never strip malformed tool markup and execute what
remains. Stop at the parser boundary and preserve a sanitized token-shape
receipt.
Rent a canary cluster only after source checks
Section titled “Rent a canary cluster only after source checks”Marker lookup, field validation, answer-capacity math, request serialization, and parser-source inspection require no GPU. Spend on a cluster only when the remaining question is whether the pinned GLM-5.2 image passes your production-shaped budget and tool matrix. Write down GPU count/HBM, interconnect, driver, image digest, checkpoint revision, storage, egress, maximum spend, request corpus, pass/fail gates, rollback, and deletion plan before provisioning.
If that test is not economically justified, keep the source audit, use a smaller model to validate the application harness, or use a documented hosted route with its native controls. Smaller-model success verifies the harness, not GLM-5.2 quality or parser parity.
Frequently asked questions
Section titled “Frequently asked questions”Is thinking_token_budget an official Z.AI API field?
Section titled “Is thinking_token_budget an official Z.AI API field?”Not in the hosted contract audited here. This guide verifies it as a vLLM
sampling extension. Z.AI documents thinking, reasoning_effort, and
max_tokens for its hosted route. Check the exact endpoint instead of assuming
OpenAI-compatible extensions are portable.
What GLM-5.2 thinking budget should I use?
Section titled “What GLM-5.2 thinking budget should I use?”There is no universal number established here. Start from the minimum visible answer or tool-call capacity, choose bounded candidate values, and select the lowest cost per accepted result. The 16 and 32 values are attributed issue fixtures, not defaults.
Does max_tokens include thinking?
Section titled “Does max_tokens include thinking?”For the audited vLLM behavior, thinking and visible content share the generated completion allowance. That is why a separate reasoning cap can preserve answer capacity. See the outer-limit guide for the context and output distinction.
Does budget 0 disable thinking?
Section titled “Does budget 0 disable thinking?”No. Zero is a valid numeric budget that forces the configured reasoning end as
the thinking span begins. Disabling thinking changes the chat-template prefix
to an empty <think></think> block. Test them as separate configurations.
Does -1 mean no thinking?
Section titled “Does -1 mean no thinking?”No. v0.28.0 normalizes -1 to no explicit budget, meaning unlimited within
the ordinary completion constraints. Use the template’s documented disable
control when you intend no thinking.
Can a budget guarantee a final answer?
Section titled “Can a budget guarantee a final answer?”No. It can reserve theoretical capacity and force a boundary. The model can still stop early, produce invalid content, miss the task, or fail the parser. Require a non-empty, semantically accepted result.
Should I count reasoning characters?
Section titled “Should I count reasoning characters?”No. Count token IDs between the pinned start and end markers. Character counts and stream-chunk counts are not token counts.
Is vLLM v0.23.0 enough?
Section titled “Is vLLM v0.23.0 enough?”It is the official general GLM-5.2 serving floor in the pinned model card and recipe. This audit does not establish the numeric GLM budget on that tag. The runtime report uses v0.24.0; the audited current generic source is v0.28.0.
Is a valid tool call safe to execute?
Section titled “Is a valid tool call safe to execute?”No. Validate the function allowlist and JSON schema, authorize the user and action, cap spend and retries, and require idempotency or approval for writes. Thinking length does not expand permissions.
Sources and method
Section titled “Sources and method”Primary and authoritative sources checked on August 27, 2026:
- official GLM-5.2 checkpoint at
b4734de4…— model card support floor, tokenizer IDs, EOS set, and thinking template; - vLLM v0.28.0 reasoning-budget documentation and sampling source — field validation and forced-boundary contract;
- official vLLM GLM-5.2 recipe at
5943215a…—glm45reasoning andglm47tool-parser launch profile; - vLLM issue #48201 — attributed v0.24.0 GLM-5.2 budget, token-layout, and streaming-tool observations; it remains an open RFC, not an official default;
- vLLM issue #46040 and merged PR #45915 — historical reasoning/tool parser incident and upstream fix attribution;
- Z.AI GLM-5.2 guide and core parameters — hosted-route boundary for thinking, effort, and output caps;
- Z.AI GLM-5.2 release page and Zhipu AI research index — required first-party discovery checks.
One exact Google query—GLM-5.2 vLLM thinking_token_budget—used the
budgeted SerpAPI client with ten requested results, no pagination, and one paid
search unit. It returned nine organic results led by the official vLLM recipe,
Z.AI release, and a GLM parser issue, but no dedicated guide for the combined
budget/reserve/parser task. The approximate result count is not search volume
and is not used as a demand claim. The query was recorded as high-value because
it confirmed a distinct supply gap and added the parser regression gate.
The site audit covered all 92 prepublication canonicals and 85 registered
content pages. No existing page mentioned thinking_token_budget. The nearest
pages own different jobs: total output capacity, hosted reasoning effort, and
the hosted function loop. The new intent is the numeric self-hosted vLLM
reasoning ceiling plus answer-reserve and parser verification.
The evidence generator reproduced only deterministic contracts: 21 source
hashes, eight accepted/rejected field values, seven capacity cells, the pinned
marker IDs, and the issue’s 32 + 1 + 53 + 1 = 87 token layout. We made zero
model calls, zero local GPU runs, zero upstream test runs, and zero Docker
runs. A task-owned shallow vLLM source checkout was removed after inspection.
Upstream model and hardware results remain attributed to their reporters.
