Skip to content

[Bug] GLM-5.3-Flash thinking output degenerates into repeated '!' under multi-tool agentic prompts #36669

Open

Description

@ech0potato

Summary

When serving zai-org/GLM-5.3-Flash (native FP8 checkpoint) with SGLang, requests with a complex system prompt + multiple tools + tool_choice=auto cause the model's reasoning/thinking output to degenerate into an endless stream of ! characters. Simple requests work correctly.

Environment

  • Image: lmsysorg/sglang:glm-5.3-flash
  • SGLang: 0.0.0.dev1+gd6ab04bdf1
  • Hardware: 8x NVIDIA H20 (Hopper sm90), TP=8, EP=8
  • Model: zai-org/GLM-5.3-Flash (native FP8)

Launch args:

python3 -m sglang.launch_server --model-path /model \
  --tp-size 8 --ep-size 8 \
  --kv-cache-dtype bfloat16 \
  --dsa-prefill-backend tilelang --dsa-decode-backend tilelang \
  --moe-runner-backend deep_gemm --disable-shared-experts-fusion \
  --reasoning-parser glm45 --tool-call-parser glm47

Repro

Simple request (works fine):

curl http://127.0.0.1:8001/v1/chat/completions \
  -H "Content-Type: application/json" -d '{
    "model": "/model",
    "messages": [{"role": "user", "content": "1+1=? answer with a number only"}],
    "max_tokens": 100
  }'
# -> content "2", reasoning content is normal

Complex request (degenerates):

curl http://127.0.0.1:8001/v1/chat/completions \
  -H "Content-Type: application/json" -d '{
    "model": "/model",
    "messages": [
      {"role": "system", "content": "You are a coding agent. Be concise. Use tools when needed. You have bash, read, write, edit, glob, grep tools available... (long agentic system prompt ~2000 tokens)"},
      {"role": "user", "content": "1+1=? answer with a number only"}
    ],
    "max_tokens": 200,
    "tool_choice": "auto",
    "tools": [
      {"type":"function","function":{"name":"bash","description":"run shell","parameters":{"type":"object","properties":{"command":{"type":"string"}},"required":["command"]}}},
      {"type":"function","function":{"name":"read","description":"read file","parameters":{"type":"object","properties":{"path":{"type":"string"}},"required":["path"]}}},
      {"type":"function","function":{"name":"write","description":"write file","parameters":{"type":"object","properties":{"path":{"type":"string"},"content":{"type":"string"}},"required":["path","content"]}}}
    ]
  }'

Observed behavior

The streaming response's reasoning_content field contains only ! characters (hundreds of them, e.g. 1800+ ! in a single response), and content is empty. The model never produces a real answer.

What I've already tried

  1. --reasoning-parser glm45 -> degenerates (reasoning_content = !!!!!)
  2. --reasoning-parser deepseek-r1 -> degenerates (reasoning_content = !!!!!)
  3. No reasoning-parser -> <think> block leaks into content (reasoning not stripped)
  4. Disabling speculative decoding (MTP) entirely -> still degenerates (rules out MTP as the cause)
  5. chat_template_kwargs {"reasoning_effort": "low"} -> still degenerates

Notes / hypothesis

  • GLM-5.3-Flash thinking is always-on (the chat template injects <think> unconditionally), controlled by reasoning_effort (low/high/max) rather than enable_thinking.
  • Simple requests (short prompt, no tools, or a single simple tool) work correctly.
  • The degeneration is triggered specifically by complex system prompt + multiple tools + tool_choice=auto.
  • This may be related to how the reasoning parser interacts with tool-call parsing (glm47) for this model — the model appears to enter a repetition loop once it must reason about multiple tool definitions.
  • Related: [Bug] NEXTN/MTP speculative decoding fails to load MTP MoE weights under TP>1 for Glm5NextForConditionalGeneration (GLM-5.3-Flash) #36653 (MTP under TP>1) is a separate issue; this degeneration happens with MTP disabled too.

Activity

  1. RenhaoPan commented on Aug 27, 2026

    @RenhaoPan

    same issue,GLM-5.3-flash thinking mode is alway on;
    "chat_template_kwargs": { "thinking": False } can't effect

  2. handsome-chips commented on Aug 28, 2026

    @handsome-chips

    same problem~

  3. b8zhong commented on Sep 3, 2026

    @b8zhong
    Collaborator

    Hi, can you please test #36143 @handsome-chips @RenhaoPan @ech0potato if your problem is on SM90?

  4. self-assigned this
    on Sep 3, 2026
  5. Lei-010 commented on Oct 8, 2026

    @Lei-010

    Cross-platform data point from vLLM (not SGLang), relevant to your note that reasoning_effort: low still degenerates: check what your template's default branch does — on the Aug-26 HF chat_template.jinja, any reasoning_effort not in ['low','high'] silently clamps to max, so unset/medium requests all run at max effort.

    Our environment (for context):

    • GLM-5.3-Flash native FP8, self-hosted vLLM v0.30.1rc1.dev539 nightly + flashinfer 0.7.1rc2, container image vllm/vllm-openai:v0.30.0-fix14
    • 8x NVIDIA B300 (SM120), TP=8, no EP
    • Reasoning parser glm45, tool-call parser glm47
    • DSA backends: tilelang prefill+decode
    • effort knob tested via top-level reasoning_effort (chat_template.jinja dated Aug 26: effective_reasoning_effort = reasoning_effort if reasoning_effort in ['low','high'] else 'max' — note anything not low/high, incl. medium, silently falls back to max)

    On vLLM 0.30.1 nightly we measured (synthetic 50-60 turn runs, 15K-token inert prefix, complex tasks, temp=0):

    effort (as rendered) reasoning runaway (content=0, finish=length) avg reasoning chars
    max (= template default, incl. explicit medium) 8/8 on derivation-heavy task 2000-5000
    high 0/60 72
    low 0/50 ~30

    Your "! flooding" reproduction with reasoning_effort=low (if the value actually reached the template) would suggest a second, effort-independent degeneration path — consistent with the accumulated-decode state issue isolated in vllm#56868 (~200K accumulated decode tokens there). Our runs max out ~60K accumulated decode tokens and never hit the !-style collapse on either stack, so we can't discriminate yet.

    Also noting for completeness: enable_thinking / chat_template_kwargs.enable_thinking is a no-op on this template — only reasoning_effort (top-level or as chat_template_kwarg) gates thinking.

  6. MMuzzammil1 commented on Oct 14, 2026

    @MMuzzammil1
    Contributor

    @ech0potato have you faced something similar with GLM-5.2-FP8 Model? I'm facing a similar issue, so kind of wondering if they have the similar origins. 🤔

  7. linkedlist771 commented on Oct 15, 2026

    @linkedlist771

    I have used this in the ZCode, which turns out fine even in the 1M long context.

  8. ByteSage404 commented on Oct 16, 2026

    @ByteSage404

    I have the same problem with self-hosted AI. It errors out for complex tasks. OpenAI is running fine. GLM 3.5 Flash is running on two Nvidia sparks.

  9. Swipe4057 commented on Oct 16, 2026

    @Swipe4057
    Contributor

    glm5.3 flash – issue in 'main' as of today

  10. removed their assignment
    on Oct 16, 2026
  11. yibol9768-alt commented on Oct 17, 2026

    @yibol9768-alt
    Contributor

    Data point from 8x H20: I could reproduce the all-! thinking output exactly, but only with a corrupted copy of the checkpoint. The bad copy had the same file sizes as the official one, but all 62 sha256 hashes differed and the layer-45 MTP expert weights contained NaN. With that copy, greedy decoding gives !!!!... (token 0) and every logprob is -ln(154880) = -11.95, i.e. the logits are degenerate.

    With the official weights (sha256 verified), SGLang main (ed8c3139f) with the launch flags from this issue was clean. That covered complex system prompts (2k and 6.4k tokens) with 3 tools and tool_choice=auto, multi-turn tool-result conversations, with and without EAGLE, about 500 requests, and none degenerated.

    Details and the official hashes are in #43085 (comment). Quick test that needs no hashing: send a greedy /generate with "return_logprob": true. If every logprob is about -11.95, re-download the weights and verify them before debugging the server.

  12. Lei-010 commented on Oct 24, 2026

    @Lei-010

    A data point against the corrupted-checkpoint explanation, for the report this issue came from:

    Our GLM-5.3-Flash deployment that produced the degenerate output was verified against the official HF LFS hashes (zai-org/GLM-5.3-Flash): all 63 files (62 shards + tokenizer) match, 328.4 GB in total, 0 mismatched / 0 missing / 0 extra shards — so at least in our case the weights were not silently corrupted, and the !!!!… degeneration has to be explained by something other than a bad copy of the checkpoint.

    Given that, the remaining suspects in our setup are the SM120 paths themselves rather than the files: native sparse-MLA (FLASHINFER_MLA_SPARSE_SM120) plus MTP/speculative decoding, where we also see draft-acceptance collapse (0% for several k values). If anyone has a clean-weights repro that still degenerates, that narrows it to the model runner rather than the checkpoint, which is the more useful report for this thread.

  13. justicel commented on Nov 6, 2026

    @justicel

    A data point from our deployment that might help separate the causes here:

    We see the same all-! output, but as a persistent engine-state problem rather than a per-request one. After a single ~1M-token request on v0.5.23 (also reproduced on the 20261028 nightly, where it degenerates inside that first request), every subsequent request on the pod degenerates — empty content or the same ! wall — MTP accept rate drops to 0.00, grammar-constrained requests start 500ing, and /flush_cache doesn't clear any of it. A restart does. v0.5.22 is clean under the same test: 7 more ~1M requests across all three replicas today (fresh prompt each time), all correct, accept rate 0.54-0.74 after each round.

    The NaN-in-layer-45 observation above caught my eye, because layer 45 is the NextN draft layer — and the commit I suspect most in the v0.5.22 → v0.5.23 window (#44098, which shrinks the KDA state pool from 35 to 34 slots per buffer) does so on the premise that the NextN layer never reads or writes KDA state. We run speculative_attention_mode=prefill, so the draft does execute during prefill. If that premise is wrong on some path, the pool shrink turns into an out-of-bounds write next to the pool, which would fit the "garbage state until restart" behavior we see.

    Full repro and config in #48964.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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