模型微调数据集格式转换报错,多数不是“格式选错了”这么一件事,而是几种问题叠在一起:源字段名和目标框架要求不一致、逐行 JSON 不合法、文件编码或空行干扰、对话模板与训练框架不匹配。先判断报错发生在哪一步,再决定是改转换脚本还是回退改数据,比反复重跑转换更省时间。
建议先不动原始文件,复制一份抽样数据到临时目录,确认三件事:源数据是 alpaca、sharegpt 还是 messages 结构;目标训练框架要求哪些字段名和 role 顺序;数据是否已经按模型 chat template 组织。转换用脚本做字段映射,不要用编辑器批量替换;转换后依次通过逐行 JSON 解析、字段类型与空值校验、用目标 tokenizer 渲染一条样本这三项检查,再全量执行。若训练框架自带的对话模板与数据中 role 顺序冲突,优先按框架模板重新构造,不要手工拼提示词。
先确认当前场景和目标
判断这个问题是否成立,先看报错出现的阶段。不同阶段的报错指向完全不同的处理方向,混在一起改容易越改越乱。
- 读文件阶段:出现
JSONDecodeError、Expecting value、Extra data,说明某一行不是合法 JSON,通常是多行字符串未转义、行尾多余逗号、或整个文件是 JSON 数组而不是 JSONL。 - 字段映射阶段:出现
KeyError、NoneType参与拼接,说明脚本假设的字段名和实际数据不一致,比如脚本找instruction,数据里叫question或prompt。 - 分词或模板阶段:报模板不存在、role 不认识、序列长度异常,说明数据的对话结构与 tokenizer 的 chat template 不匹配。
- 训练启动阶段:报 shape 不匹配、label 全为 -100,通常不是格式转换本身的问题,而是转换后字段嵌套层级或标签位置不对。
同时要确认目标格式。常见的三类是:单轮指令式(instruction/input/output)、多轮对话式(messages 里放 role 和 content)、以及带 system 与工具调用的扩展结构。训练框架对字段名的要求差异很大,需要结合你实际使用的框架配置确认,不要照抄别处的字段名。
按这个顺序处理
第一步先把原始数据设为只读,另存一份样本文件用于调试,转换脚本输出到新路径。这样任何一次转换失败都能直接重跑,不会污染源数据。

第二步做显式字段映射,不依赖自动推断。下面是一个把单轮指令数据转成 messages 结构的通用骨架,字段名按你的实际数据替换:
import json
def to_messages(row):
user = (row.get('instruction') or '').strip()
extra = (row.get('input') or '').strip()
if extra:
user = user + '\n\n' + extra
answer = (row.get('output') or '').strip()
if not user or not answer:
return None
return {'messages': [
{'role': 'user', 'content': user},
{'role': 'assistant', 'content': answer},
]}
with open('raw.jsonl', encoding='utf-8') as fin, \
open('converted.jsonl', 'w', encoding='utf-8') as fout:
for i, line in enumerate(fin, 1):
line = line.strip().lstrip('\ufeff')
if not line:
continue
try:
row = json.loads(line)
except json.JSONDecodeError as e:
print('line', i, 'bad json:', e)
continue
item = to_messages(row)
if item is None:
print('line', i, 'empty field')
continue
fout.write(json.dumps(item, ensure_ascii=False) + '\n')
第三步处理容易被忽略的输入细节:文件开头带 BOM 会让第一行解析失败,用 lstrip('\ufeff') 或 encoding='utf-8-sig' 处理;字段里含未转义的换行和引号,必须由 JSON 序列化函数生成,不要手工拼接字符串;跳过空行但不静默丢弃,把行号打出来便于回头核对。
第四步在小样本上先过 tokenizer,确认模板能渲染出来,再跑全量。多轮对话数据要检查 role 顺序,system 是否放在首位,连续两条同 role 是否需要合并。

改完后怎么验证
验证不要只看脚本有没有抛异常,要看出入是否对得上。可以先做这几项:
- 行数核对:转换前后的有效样本数是否一致,被跳过的行是否都有日志记录,逐条确认是可以丢弃的脏数据。
- 逐行复解析:用
json.loads再读一遍输出文件,确认每一行都合法,这是最便宜也最能拦住格式问题的检查。 - 字段类型与空值检查:遍历样本,确认 messages 是列表、每条含 role 和 content、content 是非空字符串,没有 None 混进去。
- 模板渲染抽检:用目标 tokenizer 的
apply_chat_template渲染一两条,肉眼看一下特殊标记、分隔符、system 段落位置是否符合预期。 - 训练前空跑:把训练配置的步数压到极小,或用单个 batch 走一遍前向,确认能正常读出样本并进入计算。
预期信号是:输出文件可以稳定逐行解析,样本数与有效源数据一致,渲染文本里没有多余的转义字符和空 assistant 段,训练脚本能正常加载第一条数据而不报字段错误。如果渲染结果出现 role 标记错位或内容为空,说明模板或字段映射仍需要调整,先回到上一步,不要直接跑全量训练。
容易忽略的边界
字段名兼容性是最常见的坑。同一份数据在不同训练框架里可能要求 conversations、messages 或纯 text 字段,转换前需要结合所用框架的文档和配置项确认,不要假设字段名通用。涉及工具调用、多模态的样本,各框架的结构差异更大,通常不适合套用同一套转换脚本。

长样本截断属于静默失败:转换不报错,但超出最大长度的部分被截掉,标签跟着丢。建议在转换阶段统计一下长度分布,超长样本单独标记,而不是直接截断。
权限和路径也要确认:数据目录如果是只读挂载,脚本写临时文件或输出文件会失败;容器内外的路径映射不一致时,报错信息里出现的路径可能根本不存在。
回退建议:转换脚本纳入版本管理,原始数据保留只读副本,输出分批生成。一旦发现字段映射错误,直接改脚本重跑,不在已转换文件上做二次修补。若训练框架版本变化导致字段解析行为改变,先用手工构造的两三条样本对照验证,再决定是全量重转还是只调整配置。