★ human 在奇数位、gpt 在偶数位:ShareGPT 的位置约束
本文所有事实以
hiyouga/LlamaFactory官方仓库 2026-08-09 的内容为准,主要出处是data/README.md。我们没有安装、训练或部署过任何模型,文中数字与规则均为仓库文档与源码里写着的值。
把一份多轮对话数据转成 LlamaFactory 的 ShareGPT 格式,绝大多数人只关心两件事:字段名对不对、formatting 填没填 sharegpt。但 data/README.md 在 Sharegpt Format 那一节的正文里,还夹着一句纯文字的位置规则——它不在 JSON 规格表里,也不是某个参数的默认值,就是一句散文。跳过它,你的数据在字段层面完全合法,在角色顺序上却不符合文档写的形态。
这篇只讲这一条规则:它的文档口径是什么、边界在哪、怎么在自己的数据上核对、以及它跟 tags 那套改名机制的关系。至于 dataset_info.json 的全部字段、enable_thinking 的三态行为,我们另有专门篇目讲。
原文到底怎么写的
data/README.md 的 Sharegpt Format → Supervised Fine-Tuning Dataset 一节,示例数据集标的是 glaive_toolcall_en_demo.json。这一节先说了 ShareGPT 相对 alpaca 的差别:它允许数据集有更多角色,例如 human、gpt、observation、function,这些角色以对象列表的形式放在 conversations 列里。
紧接着的那句就是本文的主角:human 与 observation 必须出现在奇数位置,gpt 与 function 必须出现在偶数位置,以及一句同样重要的补充:gpt 和 function 会被模型学习。
两个细节值得先钉住:
第一,这条约束是以一句散文写在文档正文里的,不像 formatting 那样是个有枚举取值、能在配置里直接比对的字段——所以它不会在你检查 dataset_info.json 时自己跳出来。我们没有跑过任何一次数据预处理,因此不推断违反这条规则时程序会不会报错、会静默处理还是会抛异常:文档把它写成硬性约束,我们就照文档的口径当硬性约束对待,运行时行为不替它补。
第二,这句话把四个角色分成了两组:human / observation 一组(奇数位),gpt / function 一组(偶数位)。这个分组不是随口列的,它对应的是”谁在提供输入”和”谁在产出内容”这条界线——而后半句”gpt 和 function 会被模型学习”正好落在偶数位这一组上。
拿官方示例数一遍下标
README 给的那段示例 JSON,照抄如下:
[
{
"conversations": [
{ "from": "human", "value": "user instruction" },
{ "from": "function_call", "value": "tool arguments" },
{ "from": "observation", "value": "tool result" },
{ "from": "gpt", "value": "model response" }
],
"system": "system prompt (optional)",
"tools": "tool description (optional)"
}
]
按从 1 起数的自然序号对一遍:
| 序号 | 奇/偶 | from 的值 | 规则里属于哪一组 |
|---|---|---|---|
| 1 | 奇 | human | human / observation |
| 2 | 偶 | function_call | gpt / function |
| 3 | 奇 | observation | human / observation |
| 4 | 偶 | gpt | gpt / function |
四条全部对得上。这里要注意一处用词上的不一致:规则那句话里写的角色名是 function,而示例 JSON 里 from 的实际取值是 function_call,tags 规格里 function_tag 的默认值也是 function_call。两处写法不同,我们只陈述这个差异,不推断哪个是笔误、也不推断作者的意图;对着数据核对时,以 tags 里那份默认值为准去理解字段的实际取值。
另外提醒一点:这里说的”位置”是 conversations 这个数组内部的序号,不是整份 JSON 文件里第几条样本。程序里遍历时数组下标通常从 0 起,按下标看就是”偶数下标 = 奇数位置”。这个一错就全错,写校验代码前先把起点定死。
还有一个原文没有覆盖的角落:这段示例里 system 和 tools 是和 conversations 平级的独立列,并没有出现在消息列表内部,所以它们不参与奇偶位的计数。而后面 OpenAI 格式那一节里,system 是作为消息列表的第一条出现的。README 在写位置约束那句时只点了 human、observation、gpt、function 四个角色,没有说 system 消息算不算占一个位置,我们也不替它补这个结论。
怎么在自己的数据上核对
规则本身很短,麻烦的是几万条数据里挑出不合规的那几条。给一段判定动作,按顺序做:
第一步,先确认你的数据确实走 ShareGPT 分支。 看 dataset_info.json 里这个数据集条目的 formatting 字段——它的默认值是 alpaca,只能取 {alpaca, sharegpt}。没显式写成 sharegpt 的话,你这份对话数据根本没走到位置约束这一层,得先解决 formatting。
第二步,确认消息列表读的是哪一列。 columns 里 messages 的默认值是 conversations。你的文件如果用的是别的列名,得在 columns.messages 里写清楚。
第三步,确认角色字段名与取值。 tags 只对 sharegpt 格式生效,7 个字段的默认值分别是:
| 字段 | 默认值 | 含义(README 原文口径) |
|---|---|---|
role_tag | from | 消息里表示身份的 key |
content_tag | value | 消息里表示内容的 key |
user_tag | human | role_tag 取该值表示用户 |
assistant_tag | gpt | role_tag 取该值表示助手 |
observation_tag | observation | role_tag 取该值表示工具结果 |
function_tag | function_call | role_tag 取该值表示函数调用 |
system_tag | system | role_tag 取该值表示 system prompt,可覆盖 system 列 |
第四步,按下标扫一遍。 下面这段是我们按 README 那句规则写的自查示意(存成 check_roles.py),不是官方脚本,我们也没有运行过,请自己核对逻辑再用:
import json
with open("data.json", encoding="utf-8") as f: # Windows 上务必显式写 encoding
rows = json.load(f)
ODD = {"human", "observation"} # 应出现在奇数位
EVEN = {"gpt", "function_call"} # 应出现在偶数位
for i, row in enumerate(rows):
for j, msg in enumerate(row["conversations"], start=1): # 从 1 起数
role = msg["from"]
ok = (role in ODD) if j % 2 == 1 else (role in EVEN)
if not ok:
print(f"sample {i}, position {j}: {role}")
命令行侧 Windows 与 Linux/macOS 的差异,按你自己的环境来:
:: Windows
python check_roles.py
# Linux / macOS
python3 check_roles.py
encoding="utf-8" 那一行在 Windows 上尤其别省——Windows 的默认文本编码历来不是 UTF-8,中文数据集读进来容易直接崩在解码这一步。这属于通用的 Python 使用常识,不是 LlamaFactory 官方文档里的内容。
第五步,什么情况说明不是这个原因。 如果扫出来 0 条不合规,那你遇到的问题就跟位置约束无关,别继续在这里打转——先回去查 formatting 有没有生效、columns.messages 指的列对不对、dataset_info.json 是不是放在 dataset_dir 下(默认值是 ./data),以及文件类型在不在允许范围内(json、jsonl、csv、parquet、arrow)。
位置约束和”谁被学习”是同一件事的两面
README 那句话的后半截容易被当成附注读过去:gpt 和 function 会被模型学习。把它和奇偶分组放在一起看,按文档写的这两句合起来读,偶数位对应的是”会被学习的那部分”,奇数位对应的是”不在这句话覆盖范围内的输入侧角色”。也就是说,角色摆错位不只是顺序难看,按文档的口径它改变的是哪一组内容落在”会被模型学习”的那一侧。至于程序实际怎么处理,我们没跑过,不替它下结论。
顺带一个可以对照的锚点:alpaca 格式那边,history 列是由字符串二元组组成的列表,README 明写监督微调时历史里的 response 也会被模型学习。两种格式在”哪部分进损失”这件事上都写了明文规则,都不是靠直觉能猜对的。
数据侧还有两个与”哪部分参与损失”相关的开关,hparams/data_args.py 里 train_on_prompt 默认 False、mask_history 默认 False。这里只交代字段名和默认值——它们的实现细节我们没有读过,该不该改、改成什么取决于你的任务和数据,官方没给通用值,我们不编调参建议。
一条明确的能力边界
ShareGPT 格式的 Pre-training Dataset 那一节,README 正文只有一句话:尚不支持,请使用 alpaca 格式。
这是这一整套数据格式文档里少见的、写得毫不含糊的边界。所以如果你的场景是继续预训练,转 ShareGPT 这件事本身就不用做了,位置约束也就无从谈起——预训练那一侧走 alpaca 格式,我们另有一篇专门讲。
把两种格式的小节结构摆在一起看也有意思:alpaca 与 sharegpt 各有 7 个小节,几乎一一对应(监督微调、预训练、偏好、KTO,以及图像/视频/音频三类多模态),差别只有两处——sharegpt 多了一节 OpenAI Format,而 sharegpt 的预训练那一节写的是”尚不支持”。
OpenAI 格式:位置约束不变,只是角色改了名
README 对 OpenAI 格式的定位写得很直白:它只是 sharegpt 格式的一个特例,其中第一条消息可以是 system prompt。对应的 dataset description 里 formatting 仍然填 sharegpt,真正变化的是 tags 那几行——这套机制本身我们另有一篇专门讲,这里只取与位置约束直接相关的部分:
"tags": {
"role_tag": "role",
"content_tag": "content",
"user_tag": "user",
"assistant_tag": "assistant",
"system_tag": "system"
}
把这几行和前面那份默认值表逐行对一遍,tags 的作用就一目了然:role_tag 从 from 改成 role、content_tag 从 value 改成 content、user_tag 从 human 改成 user、assistant_tag 从 gpt 改成 assistant。所谓”支持 OpenAI 格式”,在配置层面就是这四行改名。
对本文这条规则的启示是:角色的名字是可配的,角色的分组和位置约束是规则层面的东西。你的数据里 from 写的是 user 还是 human、是 assistant 还是 gpt,都可以通过 tags 对上;但”输入角色在奇数位、产出角色在偶数位”这件事,改 tags 是改不掉的。转数据时先想清楚哪一层归 tags 管、哪一层归顺序管,能省掉一轮返工。
一句话收尾
这条规则的成本很低——写十几行代码扫一遍就能确认;漏掉它的代价却不在报错里,而在你以为数据没问题、却和文档写的形态对不上。转 ShareGPT 之前先数一遍下标,比训练完再回来查便宜得多。
本文依据 LlamaFactory 官方仓库(github.com/hiyouga/LlamaFactory)的 README、data/README.md、examples/ 下的配置与 src/llamafactory/hparams/ 的参数定义整理,核对日 2026-08-09。本文内容为仓库源码与文档口径,我们没有安装、训练或部署过任何模型,文中显存数字均为官方标注的估算值(README 原文标 * estimated)而非实测占用。参数与默认值随版本变动,请以 llamafactory-cli train -h 的实际输出为准。