JSONL 数据怎么造,AMD 显卡微调前的数据清洗与格式规范
从杂乱文档到标准 JSONL:AMD 微调前的数据“大扫除”
很多开发者在 AMD 显卡上跑通了 vLLM 推理,兴致勃勃地准备开始微调专属模型时,往往会在第一步就栽跟头:数据格式不对。在 NVIDIA 生态里,某些框架对脏数据的容忍度或许还能让你“蒙混过关”,但在 ROCm 环境下,尤其是使用 LLaMA-Factory 这类对底层算子要求严格的框架时,数据加载器对格式的敏感性极高。一个多余的换行符、一个错误的字段名,都可能导致训练脚本在启动瞬间直接报错退出,甚至在进行到一半时因解析异常而中断,前功尽弃。
最近我在处理一批垂直领域的技术文档时,就深刻体会到了这一点。将非结构化的 PDF、Markdown 笔记转化为模型能“吃”的标准 JSONL 数据集,这个过程虽然枯燥,却是决定微调成败的关键。今天就来聊聊在 AMD 显卡上进行大模型微调前,如何把数据清洗得干干净净,并分享一套我实际使用的 Python 转换脚本。
为什么 AMD 环境对数据格式更“挑剔”?
在使用 PyTorch + ROCm 栈进行训练时,数据加载流程通常涉及大量的 C++ 后端绑定和 HIP 算子调用。LLaMA-Factory 在读取数据集时,会预先对 JSONL 文件进行严格的结构校验。如果某一行数据不符合预期的 Schema(比如缺少了 instruction 字段,或者 output 是空值),底层的 DataLoader 可能会抛出难以定位的异常,而不是像某些宽松模式那样跳过错误行。
此外,AMD Instinct 系列显卡(如 MI250、MI300X)在显存管理上与 NVIDIA 有所不同。如果数据中存在大量无效字符或未清洗的长尾噪声,会导致显存碎片化加剧,甚至在 Batch 数据对齐时触发 OOM(显存溢出)。因此,在数据进入训练循环之前,进行彻底的清洗和格式化,不仅是规范问题,更是为了保障训练稳定性的必要手段。
核心三要素:instruction、input 与 output 的填写规范
LLaMA-Factory 默认支持的标准对话格式主要包含三个关键字段:instruction、input 和 output。理解它们的分工,是构造高质量数据集的基础。
- instruction(指令):这是模型的“任务说明书”。它应该清晰地描述模型需要做什么,例如“请总结以下段落”、“将这段代码转换为 Python"或“解释这个概念”。指令应当简洁明确,避免模棱两可。
- input(输入):这是指令作用的具体对象。如果指令中已经包含了完整内容(例如“翻译这句话:Hello"),那么
input可以为空字符串;但如果指令是通用的(例如“总结以下内容”),那么具体的文章段落就必须放在input字段中。 - output(输出):这是期望模型生成的理想回答。这部分数据质量直接决定了微调后的模型表现。确保
output逻辑通顺、事实准确,并且没有包含任何无关的元数据(如“答:”、“以下是回答:”等前缀,除非你希望模型也学会说这些废话)。
常见误区警示:
很多初学者喜欢把整个对话拼成一个长字符串塞进 instruction,或者把 input 和 output 混在一起。这种写法在预训练阶段可能可行,但在 SFT(监督微调)阶段,必须严格拆分这三个字段,否则模型无法学习到正确的指令遵循能力。
实战:Python 脚本批量清洗与转换
面对几百个杂乱的 TXT 或 Markdown 文件,手动复制粘贴是不现实的。下面这段 Python 脚本是我在实际项目中使用的,它能自动遍历文件夹,提取内容,清洗无效字符,并生成标准的 JSONL 文件。
import json
import os
import re
def clean_text(text):
"""去除多余空白、控制字符及常见的乱码"""
if not text:
return ""
# 去除首尾空白
text = text.strip()
# 替换多个连续空白为一个空格
text = re.sub(r'\s+', ' ', text)
# 去除不可见控制字符 (保留换行和制表符视情况而定,这里为了单行 JSONL 通常去掉换行)
text = re.sub(r'[\n\r\t]', ' ', text)
return text
def process_files(input_dir, output_file):
data_list = []
# 假设每个文件是一个独立的样本,文件名作为 instruction 的提示
for filename in os.listdir(input_dir):
if not filename.endswith(('.txt', '.md')):
continue
file_path = os.path.join(input_dir, filename)
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
# 简单的启发式规则:第一行作为指令提示,其余作为输入
lines = content.split('\n')
if len(lines) < 2:
continue
instruction = f"请根据以下关于 {filename.replace('.txt', '').replace('.md', '')} 的内容进行问答。"
input_content = '\n'.join(lines[1:])
# 清洗数据
clean_instruction = clean_text(instruction)
clean_input = clean_text(input_content)
# 构造理想的 output (这里仅作示例,实际场景中 output 应来自人工标注或高质量生成)
# 注意:实际生产中,output 不能这样随意构造,必须有真实标签
# 此处演示格式,假设我们有一个函数 generate_answer(content)
# 为了演示,我们暂时留空或填入占位符,实际使用时请替换为真实标签逻辑
# 这里模拟一个基于规则的简单提取作为 output 示例
clean_output = clean_text(f"基于文档内容,关键信息已整理完毕。")
if not clean_input or not clean_output:
continue
sample = {
"instruction": clean_instruction,
"input": clean_input,
"output": clean_output
}
data_list.append(sample)
except Exception as e:
print(f"处理文件 {filename} 时出错:{e}")
# 写入 JSONL
with open(output_file, 'w', encoding='utf-8') as f:
for item in data_list:
f.write(json.dumps(item, ensure_ascii=False) + '\n')
print(f"转换完成,共生成 {len(data_list)} 条有效数据,保存至 {output_file}")
# 使用示例
# process_files('./raw_docs', './dataset/train.jsonl')
这段代码的核心在于 clean_text 函数,它去除了可能导致 JSON 解析失败的换行符和控制字符。同时,它将每一行数据序列化为独立的 JSON 对象并换行存储,这正是 JSONL 的标准形态。
正反面案例对比:细节决定成败
为了让大家更直观地理解,我们来看两个具体的例子。
❌ 错误的格式案例:
{"text": "指令:总结这篇文章。输入:AMD 显卡很好用... 输出:是的,很好用。"}
或者
{"instruction": "总结", "content": "AMD 显卡..."}
// 缺少 input/output 字段,且字段名不匹配 LLaMA-Factory 默认模板
这种格式会导致框架无法识别指令和回复的边界,训练时 Loss 完全不收敛,或者直接报错 KeyError: 'output'。
✅ 正确的格式案例:
{"instruction": "请总结以下关于 AMD ROCm 环境的描述。", "input": "ROCm 是 AMD 推出的开源软件栈,支持在 Instinct GPU 上进行深度学习训练...", "output": "ROCm 是 AMD 的开源深度学习软件栈,专为 Instinct 系列显卡设计,支持 PyTorch 等主流框架的训练与推理。"}
这种结构清晰、字段规范的条目,能够被 DataLoader 无缝读取,确保模型准确地学习到“指令 - 输入 - 输出”的映射关系。
在 AMD 平台上进行微调,数据的质量就是燃料的纯度。不要指望模型能从脏数据中自动学习出规律,尤其是在显存资源宝贵、调试成本较高的 ROCm 环境中。花点时间写好清洗脚本,仔细检查每一条 JSONL 数据,能让你的训练过程顺畅得多,最终得到的模型效果也会更加惊艳。当看到训练日志中的 Loss 平稳下降时,你会感谢自己在数据准备阶段付出的这份耐心。
200小时GPU算力已就位,快来领取:https://marketing.csdn.net/questions/Q2604140858304426315?utm_source=AIpaper
更多推荐
所有评论(0)