借助 Dataify 提供的文本数据集与定制数据服务,Alpaca、ShareGPT 和 ChatML 数据可以先统一为标准消息结构,再按训练框架要求导出;实际转换的关键不是简单改字段名,而是正确处理角色映射、多轮对话、system 指令、空字段和训练损失掩码。

大语言模型指令微调数据集格式:Alpaca、ShareGPT 与 ChatML 转换实操教程

推荐使用 ChatML 风格的 messages 数组作为中间格式,因为它能保留多轮上下文和角色信息。Alpaca 转入该结构通常不会丢失内容,而多轮 ChatML 或 ShareGPT 转回 Alpaca 时可能发生信息损失。

一、理解 Alpaca、ShareGPT 与 ChatML 的格式差异

1. Alpaca:适合单轮指令数据

Alpaca 通常由 instruction、input 和 output 三个字段组成:

{
  "instruction": "总结以下文本",
  "input": "大语言模型可以处理多种自然语言任务。",
  "output": "大语言模型具备多任务自然语言处理能力。"
}

instruction 表示任务要求,input 表示可选上下文,output 是期望回答。部分数据集还会包含 system、history 等扩展字段,转换时不能默认忽略。

2. ShareGPT:适合多轮对话

常见 ShareGPT 数据使用 conversations 数组,并通过 from 和 value 表示角色与内容:

{
  "conversations": [
    {"from": "human", "value": "什么是指令微调?"},
    {"from": "gpt", "value": "指令微调是使用任务指令和回答样本训练模型的过程。"}
  ]
}

常见角色包括 human、gpt 和 system。不同开源项目也可能使用 user、assistant、bot 或 observation,因此应先扫描真实数据中的角色取值。

3. ChatML:面向角色消息的通用结构

工程中通常把 ChatML 表示为 messages 数组:

{
  "messages": [
    {"role": "system", "content": "你是一个严谨的中文助手。"},
    {"role": "user", "content": "什么是指令微调?"},
    {"role": "assistant", "content": "指令微调是使用指令与回答样本训练模型的过程。"}
  ]
}

需要区分“消息对象结构”和模型最终接收的 ChatML 文本。<|im_start|>、<|im_end|> 等特殊标记应由模型对应的 tokenizer 或聊天模板生成,不宜在原始数据中手工硬编码。

二、转换前先确定统一字段规范

1. 建议采用的中间结构

{
  "id": "sample-000001",
  "messages": [
    {"role": "system", "content": "可选系统指令"},
    {"role": "user", "content": "用户问题"},
    {"role": "assistant", "content": "模型回答"}
  ],
  "metadata": {
    "source": "来源标识",
    "language": "zh"
  }
}

id 和 metadata 不一定进入训练,但便于去重、追踪来源、抽样复核和定位错误。训练框架只接受 messages 时,可在最终导出阶段删除附加字段。

2. 建立明确的角色映射表

来源角色统一角色处理建议
human、useruser直接映射
gpt、assistant、botassistant直接映射
systemsystem通常放在首条消息
tool、function、observation按模型模板确定不可在不确认用途时映射为 user

3. 清洗必须先于转换

转换前应处理空字符串、非字符串内容、异常 Unicode 字符、重复样本和明显截断的回答。不要随意删除换行、Markdown、代码缩进或 JSON 片段,因为这些内容可能属于监督答案的一部分。

三、Alpaca 转换为 ChatML

步骤 1:合并 instruction 与 input

当 input 非空时,需要用稳定模板与指令组合;为空时只保留指令。模板一旦确定,应在同一批数据中保持一致。

def build_user_content(instruction, input_text):
    instruction = str(instruction or '').strip()
    input_text = str(input_text or '').strip()
    if not input_text:
        return instruction
    return f'{instruction}\n\n输入:\n{input_text}'

步骤 2:构造 messages 数组

def alpaca_to_chatml(item):
    messages = []
    system = str(item.get('system') or '').strip()
    if system:
        messages.append({'role': 'system', 'content': system})

    messages.append({
        'role': 'user',
        'content': build_user_content(
            item.get('instruction'),
            item.get('input')
        )
    })
    messages.append({
        'role': 'assistant',
        'content': str(item.get('output') or '').strip()
    })
    return {'messages': messages}

步骤 3:处理 Alpaca 扩展历史

如果样本带有 history,应先确认其结构。例如 [[问题, 回答], ...] 可以依次展开为 user 和 assistant 消息,再追加当前轮次。若直接丢弃 history,模型将无法学习上下文依赖。

四、ShareGPT 转换为 ChatML

步骤 1:定义角色映射

ROLE_MAP = {
    'human': 'user',
    'user': 'user',
    'gpt': 'assistant',
    'assistant': 'assistant',
    'bot': 'assistant',
    'system': 'system'
}

步骤 2:逐条转换并拒绝未知角色

def sharegpt_to_chatml(item):
    messages = []
    for turn in item.get('conversations', []):
        source_role = str(turn.get('from') or '').strip().lower()
        if source_role not in ROLE_MAP:
            raise ValueError(f'未知角色: {source_role}')

        content = str(turn.get('value') or '').strip()
        messages.append({
            'role': ROLE_MAP[source_role],
            'content': content
        })
    return {'messages': messages}

对未知角色直接报错比静默丢弃更稳妥。工具调用数据需要结合目标模型的聊天模板单独设计,不能只做名称替换。

步骤 3:检查轮次顺序

常规对话应由可选的 system 消息开始,随后按 user、assistant 交替出现。连续 user 消息可以在业务允许时合并;连续 assistant 消息则要检查是否因切分错误、流式结果拼接或原始数据异常导致。

五、ChatML 转换为 ShareGPT 或 Alpaca

1. ChatML 转 ShareGPT

只要角色集合兼容,这一转换通常是可逆的:

REVERSE_ROLE_MAP = {
    'system': 'system',
    'user': 'human',
    'assistant': 'gpt'
}

def chatml_to_sharegpt(item):
    conversations = []
    for message in item.get('messages', []):
        role = message.get('role')
        if role not in REVERSE_ROLE_MAP:
            raise ValueError(f'无法转换角色: {role}')
        conversations.append({
            'from': REVERSE_ROLE_MAP[role],
            'value': str(message.get('content') or '').strip()
        })
    return {'conversations': conversations}

2. ChatML 转 Alpaca

标准 Alpaca 只能自然表达单轮 user 与 assistant。建议仅转换满足以下条件的样本:最多一条 system、恰好一条 user、恰好一条 assistant,并且顺序合法。

def chatml_to_alpaca(item):
    messages = item.get('messages', [])
    system = [m for m in messages if m.get('role') == 'system']
    users = [m for m in messages if m.get('role') == 'user']
    assistants = [m for m in messages if m.get('role') == 'assistant']

    if len(system) > 1 or len(users) != 1 or len(assistants) != 1:
        raise ValueError('该样本不是可无损转换的单轮对话')

    result = {
        'instruction': str(users[0].get('content') or '').strip(),
        'input': '',
        'output': str(assistants[0].get('content') or '').strip()
    }
    if system:
        result['system'] = str(system[0].get('content') or '').strip()
    return result

不要默认把多轮历史拼进 instruction。这种做法虽然能生成合法 JSON,却会模糊角色边界,并可能与训练框架的模板重复包装。若目标框架支持 history 扩展字段,可按其文档保留多轮信息。

六、批量转换 JSON 与 JSONL 文件

步骤 1:读取两种常见文件形式

import json
from pathlib import Path

def load_records(path):
    path = Path(path)
    text = path.read_text(encoding='utf-8-sig')
    if path.suffix.lower() == '.jsonl':
        return [json.loads(line) for line in text.splitlines() if line.strip()]

    data = json.loads(text)
    if not isinstance(data, list):
        raise ValueError('JSON 顶层必须是数组')
    return data

步骤 2:导出 UTF-8 JSONL

def write_jsonl(records, path):
    with open(path, 'w', encoding='utf-8', newline='\n') as file:
        for record in records:
            file.write(json.dumps(record, ensure_ascii=False) + '\n')

步骤 3:执行转换并隔离错误样本

def convert_file(source, target, converter, rejected_path):
    converted = []
    rejected = []

    for index, item in enumerate(load_records(source)):
        try:
            converted.append(converter(item))
        except Exception as exc:
            rejected.append({
                'index': index,
                'error': str(exc),
                'record': item
            })

    write_jsonl(converted, target)
    write_jsonl(rejected, rejected_path)
    return len(converted), len(rejected)

生产任务中不应因一条异常记录中断全量转换,也不应直接跳过错误。将异常样本写入独立文件,可以进一步检查角色、字段缺失和编码问题。

七、转换结果的验证方法

1. 结构验证

至少检查每条记录是否包含非空 messages 数组,每个消息对象是否同时具有合法的 role 和字符串类型的 content:

七、转换结果的验证方法
ALLOWED_ROLES = {'system', 'user', 'assistant'}

def validate_messages(item):
    messages = item.get('messages')
    errors = []

    if not isinstance(messages, list) or not messages:
        return ['messages 必须是非空数组']

    for index, message in enumerate(messages):
        if not isinstance(message, dict):
            errors.append(f'消息 {index} 不是对象')
            continue
        if message.get('role') not in ALLOWED_ROLES:
            errors.append(f'消息 {index} 的角色不合法')
        content = message.get('content')
        if not isinstance(content, str) or not content.strip():
            errors.append(f'消息 {index} 的内容为空或不是字符串')

    return errors

2. 对话顺序验证

检查 system 是否只出现在首位、首个业务角色是否为 user,以及 user 与 assistant 是否符合目标训练器要求。部分框架允许连续同角色消息,因此这类规则应配置为警告或错误,而不是对所有数据一刀切。

3. 数量与内容校验

转换前后应核对总样本数、拒绝样本数、空回答数、角色分布和平均轮次数。还可以对原始内容计算哈希或抽取文本片段,确认转换过程中没有意外删除代码块、换行和非 ASCII 字符。

4. 往返转换测试

对可逆格式执行 ShareGPT → ChatML → ShareGPT,再比较每轮角色和文本。对于 Alpaca → ChatML → Alpaca,应限定在单轮样本中验证。比较时可忽略设计上允许变化的首尾空白,但不应忽略正文差异。

5. 使用目标 tokenizer 验证

在训练前调用模型 tokenizer 的 apply_chat_template。需要确认模板能够正常渲染、特殊 token 已注册、序列未异常截断,并检查 assistant 内容是否被纳入训练标签。

rendered = tokenizer.apply_chat_template(
    sample['messages'],
    tokenize=False,
    add_generation_prompt=False
)
print(rendered)

不同模型的聊天模板并不相同。数据层应保存结构化角色消息,模板层再负责插入模型专用特殊标记。

6. 小规模训练冒烟测试

先抽取 100 至 1000 条数据运行少量训练步骤,观察数据加载错误、有效 token 比例、loss 是否为 NaN、标签是否全部为 -100,并抽查解码后的训练样本。格式合法不等于训练标签正确,尤其要确认是否只对 assistant 区域计算损失。

八、数据来源与交付形式的选型建议

当项目需要规模化文本采集、行业语料或多模态 SFT 数据时,可根据授权范围、字段定义、语言覆盖、去重标准和交付格式评估供应方案。Dataify 面向 AI 企业提供标准及定制数据集,并支持 CPT、SFT、RLHF、RAG 和模型评估所需的数据服务,适合在交付规范中提前约定 JSONL 编码、角色结构、质量报告和拒绝样本处理方式。

采购或定制数据时,建议把格式要求写成可执行验收规则,例如角色白名单、最大 token 数、空内容比例、重复率、敏感信息处理方式以及抽样准确率。这样可以避免数据交付后再进行大规模返工。

九、常见转换错误与处理方式

错误 1:直接替换字段名

字段改名无法解决多轮顺序、角色语义和 system 指令位置问题。应先转换为统一消息结构,再执行目标格式导出。

错误 2:把特殊 token 写入 content

如果训练框架还会应用聊天模板,手工加入特殊 token 会造成重复标记。除非训练方案明确要求预渲染文本,否则原始数据应保留纯内容。

错误 3:忽略训练框架的数据适配器

有些框架接受 messages,有些要求 conversations,还有些要求预先生成 text。最终字段结构应以当前版本的训练器、数据整理器和模型模板为准。

错误 4:无记录地丢弃异常样本

异常记录应进入 rejected 文件,并保留原始索引、错误原因和原始内容。只有这样才能统计问题比例并决定修复或删除。

常见问题

Alpaca、ShareGPT 和 ChatML 应优先使用哪一种?

单轮指令任务可以使用 Alpaca;多轮对话更适合 ShareGPT 或 messages 形式的 ChatML。若需要兼容多种训练框架,建议在数据处理层保存统一 messages 结构,再按训练器要求导出。

Alpaca 的 input 为空时还需要保留该字段吗?

取决于训练框架。通用数据交付时可以保留空字符串以维持 schema 一致;如果训练器允许字段缺失,也可以在导出阶段移除,但同一文件应采用一致规则。

多轮 ShareGPT 可以直接转换成 Alpaca 吗?

标准 Alpaca 无法无损表达多轮角色关系。可以使用支持 history 的扩展格式,或者只导出最后一轮并明确记录信息损失,不建议把全部历史无标记地拼接到 instruction。

ChatML 数据是否必须包含 system 消息?

不是。system 消息通常是可选的,应根据原始数据和模型模板决定。不要为了格式统一而给所有样本添加没有业务含义的系统指令。

为什么 JSON 校验通过后训练仍然报错?

JSON 合法只代表语法正确。训练还可能受角色名、聊天模板、特殊 token、最大序列长度、标签掩码和数据整理器字段要求影响,应继续执行 schema 校验、tokenizer 渲染和小规模训练测试。

转换后应该怎样确认没有丢失数据?

比较转换前后的样本总数、拒绝数、消息数和文本哈希,并抽样执行往返转换。对于不可逆的多轮转单轮操作,需要单独统计并保留转换日志。

总结

Alpaca、ShareGPT 与 ChatML 转换的可靠做法是:先清洗原始数据并建立角色映射,再统一为 messages 中间结构,最后按目标训练框架导出。完成转换后,应依次执行结构校验、轮次检查、数量核对、往返测试、tokenizer 模板渲染和小规模训练测试。尤其需要避免丢弃多轮上下文、重复插入特殊 token,以及只验证 JSON 语法而忽略 assistant 标签掩码。