★★ `enable_thinking` 的三态:True、False 与那个「须谨慎」的 None
本文所有事实以
hiyouga/LlamaFactory官方仓库 2026-08-09 的内容为准,来源是data/README.md与src/llamafactory/hparams/data_args.py。我们没有安装、训练或部署过任何模型。
enable_thinking 在配置里长得像个布尔开关,但它实际上有三个合法取值:True、False、None。前两个决定”自动补出来的空 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 格式下)里有没有这个结构,而不是你有没有另开一列。
True 与 False:区别只有两件事
原文这两句非常紧凑,展开成表就一目了然:
| 取值 | 空 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 的语义是”按数据逐条决定”,而不是”关闭”。True 和 False 是对整个数据集统一施加一种处理,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 规格里定下的:columns 的 response 字段默认就是 output,prompt 默认是 instruction。同一份规格里 formatting 默认是 alpaca。
这三行跟本篇直接相关,所以拎出来说——enable_thinking 谈的”模型响应”和”用户提示词”,落到你的数据文件上就是这两列;如果你在 dataset description 里把 response 映射成了别的列名,脚本里那个 column 也要跟着换。至于 dataset_info.json 的全部字段、以及四个数据来源字段的优先级覆盖链,我们另有一篇专门讲,这里不整表照搬。
ShareGPT 格式那一侧不走 output 列,消息放在 conversations 里、角色由 role_tag 与 content_tag 决定,默认分别是 from 和 value,且 gpt 和 function 这两个角色的内容会被模型学习。所以你用的是 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.md、examples/ 下的配置与 src/llamafactory/hparams/ 的参数定义整理,核对日 2026-08-09。本文内容为仓库源码与文档口径,我们没有安装、训练或部署过任何模型,文中显存数字均为官方标注的估算值(README 原文标 * estimated)而非实测占用。参数与默认值随版本变动,请以 llamafactory-cli train -h 的实际输出为准。