Qwen3.8-27B 的 thinking 默认开着:怎么按请求关掉

2026-08-16

翻 Qwen3.8-27B 这个权重仓的时候,最容易被跳过的文件是 chat_template.jinja。它只有 170 行,不像 config.json 那样一眼看得出是「模型结构」,但和「思考模式」有关的三个控制变量——enable_thinkingreasoning_effortpreserve_thinking——在这个仓库里的唯一落点就是它

这一点是可以数出来的。截至 2026-08-16 我们采集时,对仓内 8 个文本文件逐词统计:reasoning_efforttokenizer_config.json 命中 6 次、在 chat_template.jinja 命中 6 次,其余文件 0;preserve_thinking 是 2 次 / 2 次;enable_thinking 是 4 次 / 4 次。而 tokenizer_config.jsonchat_template 字段与 chat_template.jinja 是同一份内容——两者长度同为 8,952 字符、md5 同为 519239a4908bb1f805bbce5fa8c8a242。也就是说这两处命中其实是同一份模板。generation_config.jsonconfig.jsonpreprocessor_config.jsonvideo_preprocessor_config.json 里,这三个词一个都没有。

所以「关掉 thinking」这件事,在本仓的边界内,是模板变量的取值问题,不是模型权重里的某个开关。下面沿着模板的分支条件走一遍。

默认开着这件事,写在哪几处

model card 的 Highlights 里有一条 Flexible Thinking Control(README.md:28),自述思考模式默认开启、可以按请求关闭,推理深度用 reasoning_effort 调,历史消息的推理上下文靠 preserve_thinking 保留。API Usage 开头还有一个 > [!Important] 提示框(README.md:245-247),写模型默认以 thinking 模式运行,在最终回复前生成由 <think>\n...</think>\n\n 标记的思考内容。Instruct(Non-Thinking)Mode 一节开头(README.md:425)又重复了一次:默认会先思考再回答。

这三处都是 model card 自述。落到模板里,「默认开」体现为一个判定写法——chat_template.jinja:46

{%- if enable_thinking is undefined or enable_thinking is true %}

不传这个变量(undefined)和显式传 true,走的是同一个分支。

关掉的两种传法

model card 给的关闭方式分两套,而且它自己在两处都特意标了 Note。下面这几行都是照抄 model card 里的示例,属于 model card 口径:我们没有下载权重、没有部署过这个模型、也没有调用过任何一个接口,实际是否生效请以官方最新说明与你所用框架的文档为准。

开源框架侧(README.md:454-459 的 Instruct 示例),enable_thinking 包在 extra_bodychat_template_kwargs 里:

extra_body={"top_k": 20, "chat_template_kwargs": {"enable_thinking": False}}

同一个示例里另外三行是 temperature=0.7top_p=0.8presence_penalty=1.5

Qwen Cloud 侧(README.md:465-466 的 Note)写的是:如果用的是 Qwen Cloud 的 API,除了改 model,要直接传 "enable_thinking": False,而不是包在 "chat_template_kwargs" 里。preserve_thinking 那一节末尾(README.md:492-493)有一条同构的 Note。顺带一提,Qwen Cloud 上 Qwen3.8-27B 的托管版本,按 README.md:15-16 的自述是 coming soon,尚未上线。

对照 Text-Only 示例(README.md:291-303)还能看出第三件事:enable_thinkingpreserve_thinkingextra_body.chat_template_kwargs 里,而 reasoning_effortcreate()顶层参数,三者位置并不一致。至于这个顶层参数在 vLLM、SGLang 这些框架里最终怎么映射回模板的 kwargs,本仓的文件里没有说明,我们也没有去查框架源码。

关掉之后,模板少渲染了什么

这是本篇真正想说的部分。enable_thinking 为 false 时,模板有两处输出发生变化。

第一处:45-56 行整个推理档位解析块不执行。

{%- set reasoning_instructions = '' %}
{%- if enable_thinking is undefined or enable_thinking is true %}
    {%- set resolved_reasoning_effort = reasoning_effort|default('xhigh') %}
    {%- if resolved_reasoning_effort not in ('xhigh', 'medium', 'low') %}
        {{- raise_exception('Unexpected reasoning effort ' ~ reasoning_effort ~ '. Supported types are xhigh (default), medium, and low.') }}
    {%- endif %}
    {%- if resolved_reasoning_effort == 'xhigh' %}
        {%- set reasoning_instructions = 'Reasoning effort is set to xhigh. ...' %}
    {%- elif resolved_reasoning_effort == 'low' %}
        {%- set reasoning_instructions = 'Reasoning effort is set to low. ...' %}
    {%- endif %}
{%- endif %}

chat_template.jinja:52:54 那两段英文提示词在模板里是完整的长句,上面代码块用省略号做了截断,完整文本以仓库文件原文为准。)

reasoning_instructions 在 45 行被初始化为空串,只有进了 46 行这个 if 才可能被赋值。而这段文本会去哪里?看 57-87 行:有 tools 时,它被放在 <|im_start|>system\n 之后、# Tools 固定文案之前chat_template.jinja:59-62);没有 tools 时,它被拼在首条 system 消息正文前面(80 行),如果首条 system 正文为空、或者第一条压根不是 system,模板会单独插一条只含这段提示词的 system 消息(81-85 行)。

换句话说,推理档位是以一条 system 提示词的形式注入到 prompt 里的。关掉 thinking,这段英文提示词就不会出现在渲染结果里。顺带被跳过的还有 48-49 行那个合法值校验:reasoning_effort 传了 xhigh/medium/low 之外的值本来会直接抛 Unexpected reasoning effort ...,而这个校验本身也被包在 46 行的 if 里面。

第二处:163-170 行的生成提示,预填的字符串换了。

{%- if add_generation_prompt %}
    {{- '<|im_start|>assistant\n' }}
    {%- if enable_thinking is defined and enable_thinking is false %}
        {{- '<think>\n\n</think>\n\n' }}
    {%- else %}
        {{- '<think>\n' }}
    {%- endif %}
{%- endif %}

默认路径预填的是 <think>\n——生成起点落在思考块内部enable_thinking 明确为 false 时,预填的是一个完整且为空的 <think>\n\n</think>\n\n

这里有个细节值得单独记一笔:两处判定条件的写法不一样。46 行是 enable_thinking is undefined or enable_thinking is true,165 行是 enable_thinking is defined and enable_thinking is false。前者用「未定义或为真」进入思考分支,后者用「已定义且为假」进入空思考块分支。我们只记录这两处的字面条件,不推断它们在各种取值下的后果。

关掉之后没变的部分

同样值得说清楚的是哪些地方不受这个开关影响,否则很容易把两件事记混。

消息主循环在 102-162 行,它对 assistant 消息的处理是这样的:先从 message.reasoning_content 取思考内容,而且必须是字符串才采用、随后 |trim,否则当空串(111-115 行);接着由 116 行的 preserve_thinking 条件决定这条历史消息渲染成带 <think>...</think> 的形式还是只有正文。这一段的判定里没有出现 enable_thinking。也就是说,本轮不让它思考,和历史消息里已有的思考块要不要保留,是两个独立的开关。

工具那一侧也一样。57-68 行的 <tools>...</tools> 系统提示、<tool_call> / <function=...> / <parameter=...> 的调用格式说明与四条 <IMPORTANT> 规则,是由 tools 变量是否可迭代决定的;121-145 行 assistant 侧 tool_calls 的序列化、147-158 行把 tool 角色渲染成 <tool_response> 并以 user 角色承载,也都与思考开关无关。这里还有一处口径差异可以记一笔:tool_call 这个词在 README.md 全文的命中数是 0,整套工具调用协议的描述只存在于 chat_template.jinja 里。

再补一个容易想当然的点:<think></think>tokenizer_config.jsonadded_tokens_decoder 里确实有条目(id 248068、248069,起于 tokenizer_config.json:196:205),但它们的 "special" 字段是 false,而且不在 269 行 additional_special_tokens 列出的那 13 个 token 里——那 13 个是 <|im_start|><|im_end|><|vision_start|> 这一系。同样标 false 的还有 <tool_call><tool_response> 系列。这只是词表元数据的字面事实,我们没有核实过真实词表文件(tokenizer.jsonvocab.jsonmerges.txt 不在我们读到的 8 个文本文件里)。

顺手会踩到的三件事

medium 档在模板里没有对应的提示词分支。 README.md:260mediumxhighlow 并列成三档之一,描述是 balancing accuracy and speedchat_template.jinja:48 的合法值集合里也确实有 medium,传它不会抛异常。但 51-55 行只有 xhighlow 两个分支会给 reasoning_instructions 赋值,选 medium 时它保持 45 行设的空串。两处的粒度不一样,这里只陈述差异,位置分别是 README.md:260chat_template.jinja:48-55

preserve_thinkingenable_thinking 不是一回事,别一起关。 前者的唯一实现处在 chat_template.jinja:116,条件是 preserve_thinking is undefined or preserve_thinking is true or loop.index0 > ns.last_query_index,管的是历史 assistant 消息要不要带 <think>;后者管的是本轮的档位提示词与生成提示前缀。ns.last_query_index 由 88-98 行算出,指「最后一条正文不是纯 <tool_response> 包裹的 user 消息」的下标。model card 在 README.md:474preserve_thinking=False 描述为「只保留最新一条 user 消息的 thinking 块」,措辞与模板里的字面条件不同,两处位置都在上面,我们不做等价性判断。

Instruct 模式那组采样参数,在配置文件里找不到。 README.md:503 建议 non-thinking 模式用 temperature=0.7top_p=0.80top_k=20min_p=0.0presence_penalty=1.5repetition_penalty=1.0。而 generation_config.json 全文只有 202 字节、13 行、7 个键:bos_token_id 248044、do_sample true、eos_token_id [248046, 248044]pad_token_id 248044、temperature 1.0、top_k 20、top_p 0.95。这组值对应的是 model card 里 Thinking Mode 那一组(README.md:502 同为 1.0 / 0.95 / 20),仓里没有第二组。min_ppresence_penaltyrepetition_penalty 三个词在仓内配置文件里的 grep 命中数都是 0。model card 自己也在 README.md:255 提示过,采样参数的支持情况因推理框架而异。

你可以自己核的几步

不需要下载权重,仓里的文本文件就够:

  1. 打开 chat_template.jinja,直接看 45-56 行和 163-170 行这两段,确认 enable_thinking 只在这两处参与判定;
  2. json.loadtokenizer_config.jsonchat_template 字段,和 chat_template.jinja 的全文比一次长度与 md5,确认是同一份(我们比出来是 8952 / 8952、md5 相同,两者都不以换行结尾);
  3. generation_config.jsonconfig.jsonpreprocessor_config.jsonvideo_preprocessor_config.json 里 grep 这三个变量名,确认命中为 0——这一步决定了「关 thinking」到底该往哪儿传参;
  4. 如果传参之后行为和预期不符,先确认自己走的是开源框架侧还是 Qwen Cloud 侧的传法,两者在 README.md:466README.md:493 有明确区分。

有一件事这个仓库回答不了:模板渲染出来的 prompt 具体长什么样。我们没有跑过 Jinja 渲染,也没有加载过模型,上面所有结论都停留在「模板源码的分支条件」这一层。要看渲染结果,得自己用 apply_chat_template 跑一遍。

延伸阅读


本文依据 Hugging Face 仓库 Qwen/Qwen3.8-27B 的 model card 与随仓配置文件 (config.jsongeneration_config.jsonpreprocessor_config.jsonchat_template.jinja 等)整理, 核对日 2026-08-16,对应仓库快照 1d4bf0f。 本文内容为 model card 与配置文件口径,我们没有下载权重、没有部署、也没有推理过这个模型, 因此不涉及生成质量、推理速度与显存占用的任何描述;文中所有评测数字均为 model card 自述,我们没有复现。 模型仓库内容随上游更新而变动,请以官方最新说明为准。

想系统学会用 AI?报名体系课或加入会员,照着学、照着用。