reasoning-effort: add a fourth tier so 0731's max is actually reachable - #686
Open
rell666 wants to merge 6 commits into
Open
reasoning-effort: add a fourth tier so 0731's max is actually reachable#686rell666 wants to merge 6 commits into
max is actually reachable#686rell666 wants to merge 6 commits into
Conversation
added 6 commits
August 4, 2026 18:51
DeepSeek-V4-Flash-0731 has three reasoning-effort prompts, not two:
"low" (empty), "high" ("Absolute maximum...") and "max" ("Beyond
maximum - exhaustive, relentless, and uncompromising..."). ds4 carried
only one constant, byte-identical to DeepSeek "high", so every ds4 tier
sat one notch below its name and 0731 top tier was unreachable.
- APPEND DS4_THINK_ULTRA to ds4_think_mode. The three existing
enumerators keep their names and values: ~120 literals across
ds4_server.c / ds4_cli.c / ds4_agent.c / ds4_eval.c and the tests
spell them out, and a rename would silently re-point all of them.
- Add DS4_REASONING_EFFORT_ULTRA_PREFIX, copied byte-for-byte from
DeepSeeks encoding/encoding_dsv4.py including the em dash U+2014.
Verified: sha256 of the C string equals sha256 of the Python string
(53fb31b8392ec5b4a926481943efc67636748f137c1a49d493a7af4d4fa55d2c;
the pre-existing MAX prefix is f7a24f3b... == DeepSeek "high").
- Replace the single-constant accessor with
ds4_think_effort_prefix(mode), returning "" for NONE and HIGH.
ds4_think_max_prefix() stays as a shim.
- ds4_chat_append_effort_prefix(e, tokens, mode) generalises
ds4_chat_append_max_effort_prefix(), which stays as a shim.
LANDMINE, no compiler warning: ds4_think_mode_enabled() was
"mode == HIGH || mode == MAX". Left alone, ULTRA would silently
disable thinking at ~25 call sites. ULTRA is now listed there.
The context gate steps down ONE tier (ULTRA -> MAX -> HIGH) rather
than collapsing straight to HIGH.
…escape New default mapping (--reasoning-effort-map deepseek): minimal/low/medium -> DS4_THINK_HIGH (DeepSeek "low", no prefix) high/xhigh -> DS4_THINK_MAX (DeepSeek "high") max -> DS4_THINK_ULTRA (DeepSeek "max") none -> DS4_THINK_NONE --reasoning-effort-map legacy restores the pre-patch table byte for byte, so the old behaviour is one flag away. Unknown names still return false in both maps, so the HTTP layer keeps answering 400. LANDMINE, no compiler warning: think_mode_from_enabled() collapsed everything non-MAX to HIGH, which would have destroyed ULTRA before the renderer saw it. It is now a pass-through and only decides thinking on/off. Renderers take the prefix from ds4_think_effort_prefix() instead of testing == DS4_THINK_MAX, and rendered_chat_system_region() strips any prefixed tier rather than only the MAX one -- otherwise the ULTRA preamble leaks into tool-error recovery messages.
Expose the new top tier everywhere the other tiers are exposed: --think-ultra in ds4, ds4-agent and ds4-eval, /think-ultra in the REPL, and both in the shared ds4_help.c tables. Three call sites had to stop testing == DS4_THINK_MAX: - repl_chat_apply_max_prefix() took a bool. With two prefixed tiers a bool cannot express "still prefixed, but with different text", so switching /think-max <-> /think-ultra would have left the previous tier prompt in the transcript. It is now repl_chat_apply_effort_prefix(engine, chat, mode), remembers which tier it inserted, and replaces rather than keeps on a tier change. - agent_worker_build_system_tokens() gated the prefix on think_mode == DS4_THINK_MAX. It now appends the prefix for the context-adjusted tier, which is a no-op for the unprefixed tiers. - eval auto-context and the downgrade warnings treat ULTRA like MAX for the context floor and name the tier actually used.
ds4_think_set_effort_min_context() replaces the compile-time-only DS4_THINK_MAX_MIN_CONTEXT read, and --think-effort-min-ctx N is wired into ds4, ds4-agent, ds4-eval and ds4-server. The default is unchanged at DeepSeeks recommended 393216, so behaviour is identical unless the flag is passed. Process-wide by design: it is a property of the deployment, not of a request, and every endpoint must apply the same floor. Set during argument parsing, before anything is served. The REPL help line no longer hardcodes 393216, since the floor can now move.
test_reasoning_effort_mapping() now asserts:
- the full deepseek table (minimal/low/medium -> high, high/xhigh ->
max, max -> ultra, none -> none) and that it is the default;
- the legacy table, unchanged from before the patch;
- rejection of unknown strings ("banana", "", "ultra", NULL) in both
maps, so the HTTP 400 path is still reachable;
- ULTRA is a real tier: enabled, named, and with a prefix distinct from
MAX and starting with the em dash form of the 0731 string;
- think_mode_from_enabled() passes tiers through (the landmine);
- the gate steps down ONE tier and the floor is runtime-settable.
New test_render_think_ultra_prompt_prefix() renders an ULTRA prompt end
to end. It fails if either silent landmine returns: a missed
ds4_think_mode_enabled() closes </think>, and a collapsing
think_mode_from_enabled() emits the MAX text. It also checks
rendered_chat_system_region() strips the ULTRA preamble.
README gains the ds4-tier / DeepSeek-name mapping table, the wire
effort table for both maps, and the one-step downgrade rule. Help text
drops the hardcoded 393216 where the floor is now configurable.
parse_int_arg() rejects v <= 0, so the zero value that disables the gate exit(2)d before reaching the (dead) v < 0 check. Use parse_nonneg_int_arg and cover the zero case in the unit test.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up to #653, where @sleepless said he didn't mind if I opened this.
The problem
ds4 carries one reasoning-effort prefix constant, and its text is byte-identical to
DeepSeek's
highstring. DeepSeek-V4-Flash-0731 defines three tiers in its referenceencoder (
encoding/encoding_dsv4.py,REASONING_EFFORT_PROMPTS):low(the emptystring),
high, and a genuinely newmax. So every ds4 tier sits one notch below its ownlabel, and 0731's
max— the tier the published DeepSWE numbers were produced at — isunreachable in any configuration, at any context length, with any flag.
noneminimal/low/medium/high/xhighlowmaxhighmaxByte-exact, if it helps you verify your own build:
DS4_THINK_MAX(== DeepSeekhigh)f7a24f3b3050d4a8…DS4_THINK_ULTRA(== DeepSeekmax)53fb31b8392ec5b4…Both are copied verbatim from
REASONING_EFFORT_PROMPTS. The ULTRA string contains an emdash (U+2014) after "Beyond maximum" — it is part of the tokenised prompt, so it must not
be normalised to a hyphen. I re-checked both hashes against this branch's
ds4.cafterrebasing, precisely because that is the byte a rebase would eat.
The design: append a fourth tier, don't renumber the three
This is the part I'd most like reviewed, because it is the whole reason this isn't a
three-line patch.
Preview's
maxis 0731'shighstring. So any fixed remap that makesmaxmean0731's
maxnecessarily sends preview users a prefix that checkpoint never saw. That isthe breakage @sleepless described in #653 as the reason he implemented a shift locally and
never pushed it.
Appending avoids it.
DS4_THINK_ULTRAcarries 0731's realmaxprefix; the three existingenumerators keep their names and their values, so preview behaviour is bit-for-bit
unchanged. Renaming instead of appending would also have been a trap on its own: the
existing names are spelled out in ~120 literals across
ds4_server.c/ds4_cli.c/ds4_agent.c/ds4_eval.cand the tests, and a rename silently re-points every one.Selection is one flag:
legacyreproduces today's wire behaviour exactly, so it is a one-flag revert rather thana rebuild. An env var would do as well — the shape matters more than the mechanism. Also
included:
--think-ultra//think-ultrato reach the tier from the CLI and the REPL, and--think-effort-min-ctxto make the 393216-token floor runtime-settable (it accepts 0 todisable the gate).
One behaviour change worth calling out: the context gate now steps down one tier
(ULTRA → MAX → HIGH) instead of collapsing straight to HIGH, so asking for the top tier in
a small context still reasons at the tier below rather than at no prefix at all.
Three things that bite silently — none produces a compiler warning
These will catch any implementation of this, not just mine, which is why I'd rather state
them than just hand over a diff.
ds4_think_mode_enabled()is an==list. Omit your new tier and thinking issilently disabled for it at ~25 call sites — no error, the model just stops thinking.
think_mode_from_enabled()doeseffort == MAX ? MAX : HIGH, which destroys anytier above MAX. A new top tier collapses straight back down unless this is updated.
default:.A new enum value silently drops its effort text there. In this branch ULTRA saturates at
GLM's "Max"; someone with a GLM5.2 checkpoint should sanity-check that choice, as I
can't.
Testing
Rebased onto
ae504c963eand built and tested there, not on the old base — the earlierversion of this work sat on
80ebbc39and I didn't want to offer something untestedagainst current
main.mainre-checked immediately before the rebase: no commit touches thereasoning-effort tiers.
main, not around it:mainhas since moved prefix emission into
chat_push_think_prefix(), moved the legacy/completionspath ontorender_chat_prompt_text_for_syntax(), and reworked the REPL totrack a
think_prefix_posinstead of assuming the prefix sits at transcript index 1(which matters for GLM, whose BOS sequence pushes an extra token). Each of those is
upstream's structure kept, with the tier table layered in.
make cuda-sparkon a DGX Spark / GB10 (sm_121a, CUDA 13) — clean.12 of 13 groups OK.
serverpasses for both effort maps and the new tier;long-context,tool-call-qualityandstreaming-decode-prefill-correctnessall pass.think-tool-recovery— which is think-tool-recovery: model EOS-es after forced</think>injection — test fails with both official Flash GGUFs #675, and you closed it in51a1c14fa few hours after the commit this branch is based on. So it is not thischange: on 0731 the forced think-close injects correctly and then the first sampled token
is EOS, so the parser sees
calls=0, and it reproduces byte-identically on unpatched80ebbc39. I'd expect it to pass on currentmainnow, but I have not re-run the suitethere, so I'd rather say that than imply a result I don't have.
ds4_test --server,ds4_agent_test,ds4-eval --self-test-extractors,q4k-dot-test) all pass.branch: on a 121 GiB GB10 the suite only completes with the aligned-artifact paths
disabled (
DS4_CUDA_MOE_NO_IQ2_ALIGNED=1 DS4_CUDA_MOE_NO_Q2K_ALIGNED=1 DS4_CUDA_Q8_NO_ALIGNED=1). At default settingsds4_testis OOM-killed, because loadingthis GGUF builds ~68.6 GiB of aligned CUDA artifacts on top of the ~90.9 GiB mmap — the
memory model in CUDA OOM regression on DGX Spark (GB10) #585. Stock
mainwith no patch does the same thing, at the same figure.ae504c963ebecause that is what it was built and suite-testedon, and I didn't want to move it underneath its own evidence.
mainhas advanced 24commits since. I re-checked all of them: none touches the reasoning-effort tiers —
fe2d3b0cis the only one that even mentions a tier symbol, and only as a caller ofds4_think_mode_enabled(), whose two new call sites this patch's updated predicatealready covers. It merges into current
maincleanly — I tested the merge, not justthe diff. Happy to rebase onto the tip and re-run the suite there if you'd prefer it
that way.
Prior art, and a correction to something I said earlier
fabiopili's PR #5 to theEntrpi/ds4fork (merged into that fork'sbatched-servingbranch on 2026-08-02) reaches the same diagnosis and the same mechanism independently — prefixes from
REASONING_EFFORT_PROMPTS,injected ahead of the system message, empty string for
low— and cites a community vLLMrecipe for this checkpoint arriving at an identical gate condition. Three implementations on
two unrelated engines agreeing on the mechanism is better evidence than any of them alone.
It does not corroborate the design here, and I'd rather say so than overclaim: that PR
renumbers (it inserts
DS4_THINK_LOWbetweenNONEandHIGH, which shifts theordinals of
HIGHandMAXup by one, and rewrites the call sites to match). It also keepsthe 393216-token gate, widening it to cover both prefixed tiers. That is a perfectly good
choice in a 0731-only fork, and it is exactly the choice upstream can't make without
breaking the preview checkpoint. It's a fair datapoint that renumbering is what
everyone reaches for first, and part of why I think the append variant is worth the extra
enumerator here.
Happy to split this up, drop the
--think-effort-min-ctxcommit, or change the flagspelling — the part I care about is that
maxstops meaninghigh.