Skip to content

Add a hard reasoning-token budget and DeepSeek V4.1 sampling ranges - #167

Merged
lukealonso merged 3 commits into
NVIDIA:mainfrom
yatesdr:flare-reasoning-budget
Nov 2, 2026
Merged

lukealonso merged 3 commits into
NVIDIA:mainfrom
yatesdr:flare-reasoning-budget

Conversation

@yatesdr

@yatesdr yatesdr commented Nov 2, 2026 •

Copy link
Copy Markdown
Collaborator

What

Adds a hard reasoning-token budget and declares DeepSeek V4.1 sampling ranges for top_p and repetition_penalty, so clients can apply the controls that suppress reasoning repetition.

Why

DeepSeek V4.1 Flash repeats short reasoning openers ("let me go, let me write" ×N) in 10–19% of later turns of multi-turn agent conversations. We measured this on Flare, on vLLM with the same checkpoint and on DeepSeek's own API, and Flare's per-token probabilities match vLLM's on the repeated text within vLLM's own run-to-run noise, so it is model behaviour, not an engine defect. Flare could not apply any client-side mitigation: a thinking budget only selected an effort level, unknown budget fields were ignored, and with no declared ranges every requested top_p or penalty was replaced by the recipe default.

Changes

  1. DeepSeek V4.1 ranges (ignition/recipes/deepseek41/recipe.py): top_p [0.95, 1] (the V4.1-Flash card recommends 0.95 or 1.0; its benchmarks use 0.95) and repetition_penalty [1, 1.1] (measured below). Defaults are unchanged. Takes effect in packages exported after this change.

  2. Hard reasoning budget. thinking_token_budget on Chat Completions, Responses and Messages caps generated reasoning tokens. The first pass runs with the budget as its output limit; if the output parser reports reasoning still open, the contract's single-token reasoning close is emitted through the normal output path (stops, grammar, streaming, usage) and the request continues from history plus the close, reusing the preemption park and readmission path. No injection when reasoning already ended, the parser is still deciding (a tool opener), or a grammar disallows the close. EOS, stops, errors and cancellation never continue. Output starting in content is accepted with no cut. On formats that cannot honour a hard cap, thinking_token_budget is rejected while Messages thinking.budget_tokens keeps its documented advisory meaning. The forced token has no logprob entry. Metric: reasoning_budget_cuts_total.

  3. Operator default: [server] thinking_token_budget, validated at startup with the same check requests use and advertised in /v1/models.

  4. Export fix (follow-up commit): the operator default is a serving setting and is no longer added to the server options used for package export (it had made every export fail with "unrecognized field name thinking_token_budget"). A test now parses the export server options into the package's serving contract, with the budget unset and set.

Requests without a budget take the existing paths unchanged.

Testing

Unit (CI's Linux commands, on current main including #162): helios passed with 0 failed; flare CLI tests match main. Tests cover decoding and validation on every protocol, exact cut counts at the output-limit edges, preemption through the cut, close or EOS as the N-th token, multi-token speculative completion at the pass limit, tool calls including a pass ending on an undecided tool opener (with and without a grammar), streaming on Chat and Messages, and the configuration path.

Real model: DeepSeek V4.1 Flash 1017, TP4 on an SM120 host (4× RTX PRO 6000), MTP 5 unless noted.

Check Result
Budget 64, MTP 0 and 5 exactly 64 reasoning tokens before the forced close in 60/60 requests; answer continues
Streaming Chat and Messages parse as reasoning then content
Tool conversation with cuts every turn still produced a tool call or a final answer; cut counter matches the client
No budget vs a budget that cannot fire, same process, MTP 0 30/30 identical streams including logprobs
Time from cut to next token ~28 ms alone, ~0.2 s median at concurrency 4; decode speed unchanged

Repeat rates on a multi-turn agent workload (fresh seed per request, 599 later-turn and 120 single-turn answers per arm):

Arm Later turns, any repeat Single prompts, any repeat
none 18.4% 44.2%
budget 1536 17.2% 31.7%
budget 4000 18.9% 39.2%
top_p 0.95 20.4% 42.5%
repetition_penalty 1.05 8.2% (vs none: −10.2 points, Holm p ≈ 1e-6) 35.8%
  • Budgets never fire on these later turns (repeating turns reason ~280 tokens); they cut long single-prompt reasoning and about a third of its tokens. 22–30% of cut answers finish their thought in the visible content.
  • repetition_penalty 1.05 is the effective mitigation for multi-turn repeats; 1.1 started to hurt output validity, hence the upper bound. Quality was measured by proxies (valid tool calls and answers, grounding of tool arguments), not task accuracy.

Notes

  • The repetition_penalty range is based on our measurements rather than the model card.
  • In 4 of 85 cuts on the test host the next token took ~30 s; it did not reproduce in 139 further cuts and matches that host's NVMe completion-timeout stalls on disk-backed embedding reads (it affects requests without a budget too).

DeepSeek V4.1 declared no sampling ranges, so every requested sampling
value was replaced by the recipe default. Requests can now use top_p in
[0.95, 1] (the V4.1-Flash card recommends 0.95 or 1.0 and its benchmarks
use 0.95) and repetition_penalty in [1, 1.1]. Defaults are unchanged, so
requests that set nothing behave as before. Ranges are package metadata:
they take effect in packages exported after this change.

Validation: the DeepSeek V4.1 declaration test passes with the new
expectation. No package export or GPU run yet.

Design-Signed-Off-By: yatesdr; design-review=1c01
A request's thinking budget only selected a reasoning effort; reasoning
itself was unbounded, and unknown budget fields were ignored. Reasoning
models can loop in their reasoning (DeepSeek V4.1 Flash repeats short
openers in 10-19% of multi-turn agent turns on every engine), and a hard
cap is the documented mitigation.

thinking_token_budget (Chat Completions, Responses, Messages) caps the
reasoning tokens a request may generate. The first pass runs with the
budget as its output limit. If the output parser reports that reasoning
is still unambiguously open when the pass ends, the contract's
single-token reasoning close is emitted through the normal output path
(stop matching, grammar, streaming, usage) and the request continues
from history plus the close with the remaining output limit, reusing
the preemption park and readmission path. If reasoning already ended,
the parser is still deciding (a tool opener), or a grammar disallows
the close, the request continues without injection. EOS, stops, errors
and cancellation never continue. Requests whose output starts in
content are accepted with no cut. On a format that cannot honour a hard
cap, thinking_token_budget is rejected, while Messages
thinking.budget_tokens keeps its documented advisory meaning (it sets
the effort only); on formats that can, it also sets the cap. Raw
Completions reject the field. The forced token has no logprob entry.
Requests without a budget take the existing paths unchanged.

[server] thinking_token_budget sets an operator default for requests
that set none; it flows from the Flare configuration through flare
serve, is validated at startup with the same check requests use, and is
advertised in the flare metadata of /v1/models.
Metric: reasoning_budget_cuts_total.

Validation: upstream CI's helios command (runtime-package,server, with
the prepared templates) 1204 passed, 0 failed; flare CLI tests match
main (the same pre-existing hub-test hang and environment failures);
focused Python CLI tests pass. Tests cover decoding and validation on
every protocol, exact cut counts at the output-limit edges, preemption
through the cut, close or EOS as the N-th token, multi-token speculative
completion at the pass limit, tool calls (including a pass ending on an
undecided tool opener, with and without a grammar), streaming on Chat
and Messages, and the configuration path. Independent reviews and a
whole-branch simplicity pass; findings addressed.
Not yet run: real-model checks and a fixed-seed comparison without a
budget against the previous build.

Design-Signed-Off-By: yatesdr; design-review=1c01
The [server] thinking_token_budget default was added to the server
options ignition builds for both debug-serve and package export, so
every export failed in the native exporter ("unrecognized field name
thinking_token_budget"; the package's serving contract has no such
field), even with the budget unset. The budget is a serving setting:
it is now added only on the debug-serve path, and flare serve reads it
from the configuration as before. Export options are unchanged from
before the budget.

Validation: a new test parses the export server options into the
package's ServingContract with the budget unset and set; it fails
without this change and passes with it. Serving-default tests and the
CLI tests that run without preparation dependencies pass (106); CI's
helios command passes. A DeepSeek V4.1 Flash export failed at "Export
native package" before this change.

Design-Signed-Off-By: yatesdr; design-review=1c01
@yatesdr
yatesdr force-pushed the flare-reasoning-budget branch from 993b985 to 7cb2751 Compare November 2, 2026 14:18
@lukealonso
lukealonso merged commit 902c88e into NVIDIA:main Nov 2, 2026
jayinsine added a commit to jayinsine/spark-local-inference that referenced this pull request Nov 4, 2026
…ages

Since NVIDIA#167, DeepSeek V4.1 declares a top-p range of [0.95, 1].
Vocabulary-parallel sampling supports only unfiltered target
distributions: its softmax marks a row invalid when top-p is not 1,
min-p is not 0, or top-k is active. A package exported with
--vocabulary-parallel-sampling after NVIDIA#167 therefore admits top_p 0.95,
every speculative round of that request fails verification, and the
request ends with HTTP 500 "prepared model reported device error 6"
(INVALID_SPECULATION). The same package serves top_p 0.95 with
--mtp-draft-tokens 0, and packages exported before NVIDIA#167 clamp top-p
to 1.

SamplingDefaults.unfiltered() returns the policy without top-k, top-p,
and min-p ranges and rejects defaults that enable those filters.
DeepSeek V4.1's PreparationConfig packages that policy when
vocabulary-parallel sampling is enabled and validates it on
construction. As for any absent range, requests are clamped to the
default (top-p 1), which keeps the documented requirement that these
filters stay disabled for this option. Other ranges, including
repetition_penalty [1, 1.1], are unchanged, and packages exported
without the option keep the top-p range. The preparation and API
documents describe the packaged policy.

Validation:
- New tests cover the unfiltered policy (non-filter ranges kept for
  top-k -1 and 0, enabled filter defaults rejected) and the DeepSeek
  export adapter with and without --vocabulary-parallel-sampling. The
  adapter test fails without the bootstrap change.
- tests/preparation/test_frontend_metadata.py,
  tests/recipes/test_declarations.py, tests/cli/test_planning.py,
  tests/cli/test_speculative_graph_budget.py,
  tests/recipes/test_deepseek41_frontend.py,
  tests/preparation/test_native_recipe_contract.py and
  tests/preparation/test_runtime_package.py, with Heliopause built with
  the qualification feature: 296 passed, 37 skipped, 11 failed. The
  same 11 fail on cc696a6 without this change: five need the Kimi K3
  checkpoint, two are the deepseek41 and muse_spark default-sampling
  expectations, two are runtime collective-ladder cases, and three are
  graph-budget cases.
- GPU: exported a derivative of the qualified DeepSeek-V4.1-Flash-1017
  revision at TP4 on four RTX PRO 6000 Blackwell GPUs (C32, MTP 7,
  --vocabulary-parallel-sampling, NVFP4 draft head, FP8 Markov head;
  NVIDIA#154 applied locally so the C32 export fits) and served it with
  flare run. flare inspect shows no top-p range and keeps the
  repetition-penalty range; a package exported from 902c88e shows
  top-p [0.95, 1]. Chat requests with top_p 0.95 at C1 and C8 and one
  streamed request all returned 200 with 3.4-3.8 accepted drafts per
  verification round, where the 902c88e package returned device
  error 6. Functional API checks passed 34/34 and decode throughput was
  unchanged.

Limitations: on these packages a request for top_p below 1 receives
top-p 1, as before NVIDIA#167, rather than a filtered distribution. Top-p
support in vocabulary-parallel verification would change its sampling
kernels and is not attempted here. Only single-host TP4 was exercised.

Design-Signed-Off-By: Claude Opus 5.5 (agent for Josh Cartu); design-review=a38f
Signed-off-by: Josh Cartu <j@mail99.me>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants