模型微调数据集格式转换报错怎么解决

文章导读
模型微调数据集格式转换报错,多数不是“格式选错了”这么一件事,而是几种问题叠在一起:源字段名和目标框架要求不一致、逐行 JSON 不合法、文件编码或空行干扰、对话模板与训练框架不匹配。先判断报错发生在哪一步,再决定是改转换脚本还是回退改数据,比反复重跑转换更省时间。
📋 目录
  1. A 先确认当前场景和目标
  2. B 按这个顺序处理
  3. C 改完后怎么验证
  4. D 容易忽略的边界
A A

模型微调数据集格式转换报错,多数不是“格式选错了”这么一件事,而是几种问题叠在一起:源字段名和目标框架要求不一致、逐行 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 是否需要合并。

模型微调数据集格式转换报错怎么解决

改完后怎么验证

验证不要只看脚本有没有抛异常,要看出入是否对得上。可以先做这几项:

  1. 行数核对:转换前后的有效样本数是否一致,被跳过的行是否都有日志记录,逐条确认是可以丢弃的脏数据。
  2. 逐行复解析:用 json.loads 再读一遍输出文件,确认每一行都合法,这是最便宜也最能拦住格式问题的检查。
  3. 字段类型与空值检查:遍历样本,确认 messages 是列表、每条含 role 和 content、content 是非空字符串,没有 None 混进去。
  4. 模板渲染抽检:用目标 tokenizer 的 apply_chat_template 渲染一两条,肉眼看一下特殊标记、分隔符、system 段落位置是否符合预期。
  5. 训练前空跑:把训练配置的步数压到极小,或用单个 batch 走一遍前向,确认能正常读出样本并进入计算。

预期信号是:输出文件可以稳定逐行解析,样本数与有效源数据一致,渲染文本里没有多余的转义字符和空 assistant 段,训练脚本能正常加载第一条数据而不报字段错误。如果渲染结果出现 role 标记错位或内容为空,说明模板或字段映射仍需要调整,先回到上一步,不要直接跑全量训练。

容易忽略的边界

字段名兼容性是最常见的坑。同一份数据在不同训练框架里可能要求 conversations、messages 或纯 text 字段,转换前需要结合所用框架的文档和配置项确认,不要假设字段名通用。涉及工具调用、多模态的样本,各框架的结构差异更大,通常不适合套用同一套转换脚本。

模型微调数据集格式转换报错怎么解决

长样本截断属于静默失败:转换不报错,但超出最大长度的部分被截掉,标签跟着丢。建议在转换阶段统计一下长度分布,超长样本单独标记,而不是直接截断。

权限和路径也要确认:数据目录如果是只读挂载,脚本写临时文件或输出文件会失败;容器内外的路径映射不一致时,报错信息里出现的路径可能根本不存在。

回退建议:转换脚本纳入版本管理,原始数据保留只读副本,输出分批生成。一旦发现字段映射错误,直接改脚本重跑,不在已转换文件上做二次修补。若训练框架版本变化导致字段解析行为改变,先用手工构造的两三条样本对照验证,再决定是全量重转还是只调整配置。