★★ `enable_thinking` 的三态:True、False 与那个「须谨慎」的 None

2026-08-09

本文所有事实以 hiyouga/LlamaFactory 官方仓库 2026-08-09 的内容为准,来源是 data/README.mdsrc/llamafactory/hparams/data_args.py。我们没有安装、训练或部署过任何模型。

enable_thinking 在配置里长得像个布尔开关,但它实际上有三个合法取值:TrueFalseNone。前两个决定”自动补出来的空 CoT 放在哪一侧、算不算损失”,第三个是一条官方自己标了”须谨慎”的分叉路。把它当成普通的 true/false 来填,是这一带最容易踩的坑之一。

这篇不讲该设成哪个——官方没给通用值,我也不打算替你拍。这篇只把三个取值各自的语义、它在参数体系里坐哪一层、以及一条几乎所有人第一次都会漏掉的硬约束讲清楚。

先定位:它是数据侧参数,不是模型侧

enable_thinking 定义在 src/llamafactory/hparams/data_args.py 里,默认值是 True

这个”它在哪一层”的问题值得先钉死,因为它决定了你该去哪儿改、以及改了会牵动谁。和它同在 data_args 这一层的还有 template(默认 None)、cutoff_len(默认 2048)、train_on_prompt(默认 False)、mask_history(默认 False)、preserve_thinking(默认 False)等等。也就是说,enable_thinking 与”哪部分文本参与损失计算”的那几个开关是同族的,不是模型加载参数,也不是 LoRA 那一族的微调超参。

顺带说一句,preserve_thinking 默认 False,这是另一个名字里带 thinking 的字段,跟 enable_thinking 不是一回事,别在配置里混着抄。本文只讲 enable_thinking

空 CoT 是从哪冒出来的

要理解三态,得先知道有个东西会被”自动补”进来。

data/README.md 的原文规则是:**如果模型有推理能力(例如 Qwen3),但数据集不含思维链(CoT),框架会自动给数据补一个空 CoT。**注意这一步的触发条件是”模型有推理能力 + 数据集不含 CoT”,跟 enable_thinking 取什么值无关:补是先发生的,enable_thinking 决定的是这个空 CoT 被放到哪一侧去。

顺带一个小观察:这条 TIP 的原文里,主语写的是带连字符的旧名 LLaMA-Factory。仓库当前的真实路径是 hiyouga/LlamaFactory,PyPI 包名是 llamafactory,但仓库内部新旧两种写法是并存的。这里只陈述这个差异,改名的时间和原因我们没有核实,不做推断。

那 CoT 在数据里长什么样?data/README.md 在监督微调那一节给了写法:对推理模型,CoT 需要放在模型响应里,形如 <think>cot</think>output。也就是说,判断你的数据”含不含 CoT”,看的是 output 这一列(alpaca 格式下)里有没有这个结构,而不是你有没有另开一列。

TrueFalse:区别只有两件事

原文这两句非常紧凑,展开成表就一目了然:

取值空 CoT 被加到哪里损失计算
True(慢思考,默认值模型响应计入损失计算
False(快思考)用户提示词忽略它

读这张表的关键在于把两列连起来看:空 CoT 落在响应侧且计入损失,就意味着它落在了模型要学的那部分文本里;落在提示词侧且损失计算忽略它,就是把它当成输入的一部分、不参与学习。“慢思考""快思考”这两个说法本身也是原文给这两个取值贴的标签,至于训出来的模型最终会表现成什么样,文档没写,我们也没有训练过任何模型来验证,这里不往下推。

我要在这里停住。再往前一步”所以你的场景该选哪个”,就超出仓库文档给的依据了。data/README.md 没有给”什么数据配什么值”的推荐表,我们也没有训练过任何模型来对比效果,所以这个选择取决于你的数据形态和推理侧的配置,官方没给通用值。

那条最容易漏的硬约束:训练与推理必须一致

原文一句话:训练与推理时 enable_thinking 必须保持一致。

这条约束单独拎出来说,是因为它的失效方式很隐蔽——它不会在训练阶段报错。训练时你在数据侧配了一个值,训完把权重端起来做推理时,推理侧的配置是另一套文件、可能由另一个人维护,两边对不上的时候没有任何东西会拦你。

要说明的是,data/README.md 把这句”必须保持一致”是写给 enable_thinking 的,我们不替它扩大到别的字段。但同在 data_args 这一层、同样会影响文本怎么被组织起来的 template(默认 None),值得你顺手一起管:训练 YAML 里写了什么,推理那份配置里就照抄什么,别靠记忆。至于 template 本身的取值体系,我们另有一篇专门讲。

None:官方自己标了”须谨慎”的那一档

第三态是这篇标题里的重点。原文的表述是:**如果你想让含 CoT 的数据走慢思考、不含 CoT 的数据走快思考,可以把 enable_thinking 设为 None。**紧接着原文补了一句:这个特性相对复杂,使用需谨慎(原文 should be used with caution)。

拆开看,None 的语义是”按数据逐条决定”,而不是”关闭”。TrueFalse 是对整个数据集统一施加一种处理,None 则是把决定权交给每一条数据自身的形态——这条含 CoT 就按慢思考走,那条不含就按快思考走。

它适用的场景其实很明确:你手上是一份混合数据,一部分标注了思维链、一部分没有,而你不想为了统一格式去改数据。

但”官方自己写了须谨慎”这句限定必须原样带上。它是文档作者留的余地,我们没有跑过任何一次训练来验证复杂在哪里,所以这里只转述,不解释、不加码、也不告诉你”其实还好”。你要用它,就自己承担这份文档级的提示。

动手前先确认一件事:你的数据到底含不含 CoT

上面所有分支都建立在同一个前提上——你知道自己的数据含不含 CoT。而这个前提,很多人是凭印象的。

判定动作很简单:按 <think> 这个标记,去数你响应列里有多少条命中。写成一个小脚本比在命令行里拼一行更省事,因为 Windows 和 Linux/macOS 的引号规则不一样,一行命令抄过来经常在引号上翻车。存成 count_cot.py

import json

path = "<你的数据文件>.json"          # 换成你自己的路径
column = "output"                    # alpaca 格式下模型响应默认落在这一列

with open(path, "r", encoding="utf-8") as f:
    rows = json.load(f)

hit = sum(1 for r in rows if "<think>" in str(r.get(column, "")))
print(f"total={len(rows)} with_cot={hit} without_cot={len(rows) - hit}")

跑法两边一样,差别只在提示符:

# Linux / macOS
python3 count_cot.py
:: Windows
python count_cot.py

(这段脚本和两条跑法是通用命令行常识,不是仓库文档里的内容;<think> 这个标记来自 data/README.md 给的 <think>cot</think>output 写法。)

数出来是三种结果,对应三条不同的路:全部命中、全部落空、部分命中。前两种是”整份数据同质”,第三种才是原文描述的 None 那条分叉路所针对的情形。先把这个数字拿到手,再回头看该配哪个值,比先纠结参数高效得多。

为什么这篇要提 columns 里的两行

上面脚本里写死了 column = "output",这个默认值不是我瞎填的,而是 dataset_info.json 规格里定下的:columnsresponse 字段默认就是 outputprompt 默认是 instruction。同一份规格里 formatting 默认是 alpaca

这三行跟本篇直接相关,所以拎出来说——enable_thinking 谈的”模型响应”和”用户提示词”,落到你的数据文件上就是这两列;如果你在 dataset description 里把 response 映射成了别的列名,脚本里那个 column 也要跟着换。至于 dataset_info.json 的全部字段、以及四个数据来源字段的优先级覆盖链,我们另有一篇专门讲,这里不整表照搬。

ShareGPT 格式那一侧不走 output 列,消息放在 conversations 里、角色由 role_tagcontent_tag 决定,默认分别是 fromvalue,且 gptfunction 这两个角色的内容会被模型学习。所以你用的是 sharegpt 格式的话,上面那个脚本要按 conversations 结构改写,不能照抄。

什么情况说明问题不出在这里

排查文章最容易省掉、也最值得留的一节:

  • 资源相关的训练失败enable_thinking 在文档里的语义只涉及”空 CoT 放哪一侧、算不算损失”,不涉及资源占用,这类问题该去看的是硬件侧和 cutoff_len 这类长度参数(cutoff_len 默认 2048);显存到底够不够,我们没有依据替你判断;
  • 数据集根本加载不进来:那是 dataset_info.json 的配置问题,比如 dataset_dir(默认 "data")指错了、或者你压根没在 dataset_info.json 里加那段 dataset description——原文写得很硬,用自定义数据集时这一步是必须的;
  • 你的基座模型本来就没有推理能力:自动补空 CoT 那条规则的前提是”模型有推理能力(例如 Qwen3)“,前提不成立,这一整套行为就不在你的路径上;
  • 两侧配置本来就一致、数据也同质:那么 enable_thinking 保持默认的 True 就是当前行为,问题多半在别处,别在这个字段上反复试。

最后再说一次这篇的底线:True / False / None 三个取值的语义是文档写死的,可以照抄;“你该选哪个”文档没写,我们也没有实测依据,所以不给推荐值。能替你省时间的,是把”训练推理必须一致”这条约束、和”先数一遍数据里有没有 <think>”这个动作变成习惯。


本文依据 LlamaFactory 官方仓库(github.com/hiyouga/LlamaFactory)的 README、data/README.mdexamples/ 下的配置与 src/llamafactory/hparams/ 的参数定义整理,核对日 2026-08-09。本文内容为仓库源码与文档口径,我们没有安装、训练或部署过任何模型,文中显存数字均为官方标注的估算值(README 原文标 * estimated)而非实测占用。参数与默认值随版本变动,请以 llamafactory-cli train -h 的实际输出为准。

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