The reasoning budget derived from reasoning_effort never applied to Mistral
models. gpttype_adapter.cpp picks the think delimiters from a switch on the
model architecture, and mistral3 has no case, so it falls back to <think> /
</think>. Those are not vocabulary tokens for Ministral-3, so TokenizeString
returns more than one token each, the expected_start/end_tokens guard clears
all three vectors, and apply_reasoning_budget() returns at its first if.
The parameter is accepted, converted and passed down to the sampler, then
dropped on a size check, with nothing logged.
Adding the mistral3 case arms the budget. [THINK] and [/THINK] are single
vocabulary tokens (ids 34 and 35 on Ministral-3), so the size guard passes.
The thinkformats entry is a separate fix for a separate defect: without it the
thinking block was never split out, so it leaked into content with its [THINK]
marker still in it, instead of going to reasoning_content.
Measured on Ministral-3-14B-Reasoning-2512 (IQ4_XS, ctx 8192, --jinja), 5 real
prompts x 3 samples per cell, max_tokens 3000 (so a 750-token budget at "low"):
reasoning_effort thinking words before thinking words after
none 311 - 2314 7 (the forced-close phrase)
low 340 - 2255 521 - 574
Forced closes: 0/15 before, 14/15 after at "low" and 15/15 at "none". Three
samples per cell because this model's variance at temperature 0.7 spans a
factor of 4 on an identical payload — a single sample per cell cannot tell an
effect from noise.
No regression on a non-reasoning mistral3 model: Ministral-3-8B-Instruct with
reasoning_effort "low" returns finish_reason "stop", a normal answer and zero
forced closes, since apply_reasoning_budget() bails out when the start marker
never appears.
DEEPSEEK4 SLOP Explanation. (Tested and is working correctly after build)
fix mtmd: missing text_len in gpttype_adapter.cpp aggregate init
Commit 4114ba18b ("mtmd: fix silent prompt truncation on embedded NUL
(#25548)") added a `text_len` field to `mtmd_input_text`, changing the
struct layout from:
{ text, add_special, parse_special }
to:
{ text, text_len, add_special, parse_special }
The aggregate initialization in `gpttype_adapter.cpp` was never updated
to account for the new field. With C++ aggregate init, the positional
arguments shifted:
mtmd_input_text inp_txt = {
mtmd_default_marker(), // text = "<__media__>"
/* add_special */ false, // text_len = 0 <-- BUG
/* parse_special */ true, // add_special = true
};
// parse_special is zero-initialized
Because text_len was 0, `input_text.assign(text->text, text->text_len)`
in the mtmd_tokenizer constructor produced an empty string.
`split_text()` on an empty string returned an empty vector, yielding
zero media markers in the text -- but one bitmap was still provided,
triggering the error:
mtmd_tokenize: error: number of media markers in text (0) does not
match number of bitmaps (1)
Fix by explicitly capturing the marker pointer and passing its length.