Skip to content

reasoning-effort: add a fourth tier so 0731's max is actually reachable - #686

Open
rell666 wants to merge 6 commits into
antirez:mainfrom
rell666:effort-tiers-0731-head
Open

reasoning-effort: add a fourth tier so 0731's max is actually reachable#686
rell666 wants to merge 6 commits into
antirez:mainfrom
rell666:effort-tiers-0731-head

Conversation

@rell666

@rell666 rell666 commented Aug 4, 2026

Copy link
Copy Markdown

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 high string
. DeepSeek-V4-Flash-0731 defines three tiers in its reference
encoder (encoding/encoding_dsv4.py, REASONING_EFFORT_PROMPTS): low (the empty
string), high, and a genuinely new max. So every ds4 tier sits one notch below its own
label, and 0731's max — the tier the published DeepSWE numbers were produced at — is
unreachable in any configuration, at any context length, with any flag.

sent to ds4 prefix emitted today = DeepSeek tier
none — (thinking off) non-thinking
minimal / low / medium / high / xhigh nothing low
max "Absolute maximum…" (476 B) high
unreachable max

Byte-exact, if it helps you verify your own build:

prefix bytes sha256
existing DS4_THINK_MAX (== DeepSeek high) 476 f7a24f3b3050d4a8…
new DS4_THINK_ULTRA (== DeepSeek max) 528 53fb31b8392ec5b4…

Both are copied verbatim from REASONING_EFFORT_PROMPTS. The ULTRA string contains an em
dash (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.c after
rebasing, 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 max is 0731's high string. So any fixed remap that makes max mean
0731's max necessarily sends preview users a prefix that checkpoint never saw. That is
the breakage @sleepless described in #653 as the reason he implemented a shift locally and
never pushed it.

Appending avoids it. DS4_THINK_ULTRA carries 0731's real max prefix; the three existing
enumerators 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.c and the tests, and a rename silently re-points every one.

Selection is one flag:

--reasoning-effort-map {deepseek|legacy}     # default: deepseek

legacy reproduces today's wire behaviour exactly, so it is a one-flag revert rather than
a rebuild. An env var would do as well — the shape matters more than the mechanism. Also
included: --think-ultra / /think-ultra to reach the tier from the CLI and the REPL, and
--think-effort-min-ctx to make the 393216-token floor runtime-settable (it accepts 0 to
disable 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.

  1. ds4_think_mode_enabled() is an == list. Omit your new tier and thinking is
    silently disabled for it at ~25 call sites — no error, the model just stops thinking.
  2. think_mode_from_enabled() does effort == MAX ? MAX : HIGH, which destroys any
    tier above MAX. A new top tier collapses straight back down unless this is updated.
  3. The GLM path renders effort as a system message, and its switch has no 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 ae504c963e and built and tested there, not on the old base — the earlier
version of this work sat on 80ebbc39 and I didn't want to offer something untested
against current main.

  • Upstream main re-checked immediately before the rebase: no commit touches the
    reasoning-effort tiers.
  • Conflicts were real and were resolved towards current main, not around it: main
    has since moved prefix emission into chat_push_think_prefix(), moved the legacy
    /completions path onto render_chat_prompt_text_for_syntax(), and reworked the REPL to
    track a think_prefix_pos instead 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.
  • Built make cuda-spark on a DGX Spark / GB10 (sm_121a, CUDA 13) — clean.
  • Full suite against a real DeepSeek-V4-Flash-0731 GGUF (imatrix checkpoint 202100):
    12 of 13 groups OK. server passes for both effort maps and the new tier;
    long-context, tool-call-quality and streaming-decode-prefill-correctness all pass.
  • The one failure is 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 in
    51a1c14f a few hours after the commit this branch is based on
    . So it is not this
    change: 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 unpatched
    80ebbc39. I'd expect it to pass on current main now, but I have not re-run the suite
    there, so I'd rather say that than imply a result I don't have.
  • Unit suites (ds4_test --server, ds4_agent_test, ds4-eval --self-test-extractors,
    q4k-dot-test) all pass.
  • One environmental caveat, and it is an upstream issue rather than a property of this
    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 settings ds4_test is OOM-killed, because loading
    this 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 main with no patch does the same thing, at the same figure.
  • On the base: this sits on ae504c963e because that is what it was built and suite-tested
    on, and I didn't want to move it underneath its own evidence. main has advanced 24
    commits since. I re-checked all of them: none touches the reasoning-effort tiers
    fe2d3b0c is the only one that even mentions a tier symbol, and only as a caller of
    ds4_think_mode_enabled(), whose two new call sites this patch's updated predicate
    already covers. It merges into current main cleanly — I tested the merge, not just
    the 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 the Entrpi/ds4 fork (merged into that fork's batched-serving
branch 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 vLLM
recipe 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_LOW between NONE and HIGH, which shifts the
ordinals of HIGH and MAX up by one, and rewrites the call sites to match). It also keeps
the 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-ctx commit, or change the flag
spelling — the part I care about is that max stops meaning high.

Mark (agent) 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.
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.

1 participant