AI图鉴

AI图鉴首页 › AI 资讯 › 大模型训练全流程实战指南实战篇(十七)— LLamaFatory预训练实战:从语

大模型训练全流程实战指南实战篇(十七)— LLamaFatory预训练实战:从语料到模型

来源:掘金 · 发布时间:10-25 · 由 AI图鉴每小时自动聚合

本文手把手带你用 LLaMA-Factory 完成 LoRA 增量预训练:从领域语料构建、通用数据混合,到参数配置、训练合并与 checkpoint 评测筛选,避开灾难性遗忘等实战坑点,直接可复现。

前言

上篇文章《大模型训练全流程实战指南实战篇(十六)——预训练数据集构建》中,笔者通过结构化切片和质量判别,得到了结构清晰、内容干净、可供LLaMA-Factory使用的专业领域数据集。但严格来说这还不是LLaMA-Factory可直接用于预训练的完整数据文件。

本篇文章将按照“数据准备 → 参数设置 → 启动训练 → 效果查看”四步递进:先说明如何将专业领域数据整理、扩充并配置为LLaMA-Factory可直接使用的完整的预训练数据;再介绍如何使用LLaMA-Factory启动大模型预训练。训练结束后大家将得到一个经过专业知识扩充的大模型,并将它作为后续微调的模型基座。

一、完整的预训练数据准备

1.1 构建专业领域数据集

在将 大模型训练全流程实战指南实战篇(十六)——预训练数据集构建中的内容整理为专业领域数据集之前,笔者先介绍一个小技巧。大家是否还记得在 大模型训练全流程实战指南实战篇(十四)——网络安全大模型数据获取 文章中笔者提到可以使用EasyDataset蒸馏数据集,不过蒸馏得到的是问答对数据。但因为预训练只关心“根据上文预测下一个 token”,并不关心文本是论文还是问答;问答文本同样包含领域术语、问题表述和答案组织逻辑。因此可以将高质量微调数据集中的问题和答案拼接成自然文本,作为预训练数据使用。

大家可以将 EasyDataset 蒸馏得到的数据导出为alpaca格式:

然后可以使用如下代码提取问题和答案,将其拼接为问答对文本:

import json

def alpaca_to_text(item):

q = item.get("instruction", "").strip()

if item.get("input"):

q += "\n" + item["input"].strip()

return f"{q}?{item.get('output', '').strip()}"

接着可以将《大模型训练全流程实战指南实战篇(十六)——预训练数据集构建》处理得到的数据集中的 text 字段提取出来,与蒸馏得到的数据集拼接,得到专业领域预训练数据集:

import json, hashlib

DISTILL = './llamafactory_data/cyber_distill.jsonl'

SRC = "./llamafactory_data/chunks_judged.jsonl"

DST = "./llamafactory_data/cyber_pt.jsonl"

dataset = []

with open(SRC, encoding="utf-8") as fin:

for line in fin:

rec = json.loads(line)

if not rec.get("keep"):

continue

text = rec["text"].strip()

if len(text) < 100:

continue

dataset.append({"text": text})

kept= kept + 1

with open(DISTILL, encoding="utf-8") as f:

data = json.load(f)

for item in data:

text = alpaca_to_text(item)

kept= kept + 1

dataset.append({"text": text})

with open(DST, 'w', encoding="utf-8") as fout:

for item in dataset:

fout.write(json.dumps(item, ensure_ascii=False) + "\n")

print(f"{kept} 条")

执行完成后,本次蒸馏和数据治理得到的数据总计30,847 条。

1.2 引入通用数据集,避免通用能力丧失

专业领域数据集只覆盖网络安全知识,而预训练阶段的知识注入在很大程度上决定了模型的相关能力(大模型训练全流程实战指南工具篇(五)——大模型训练全流程步骤详解与对应工具推荐文章中笔者提过)。如果只用专业领域数据集训练,模型长时间只学习单一分布,原本具备的通用问答和常识知识反而可能下降,这就是所谓的“灾难性遗忘”。因此预训练通常不会使用单一领域语料,而是将通用语料与领域语料混合使用。

那么通用数据集应该配多少?这个问题并没有统一标准,各大公司也鲜有公开。结合笔者日常工作中的尝试,如果使用增量预训练,通用数据集的比例一般略高于专业领域数据集,笔者最常用的比例是通用数据集占全部数据集的 60%~70%,专业领域语料占 30%~40%(可以粗略按条数计算比例)。

那么通用数据集该如何构建?是不是随便爬取一些网页、清洗后即可使用?实际并没有这么简单。通用数据集需要按类别进行分层采样,保证各类数据都有一定比例分布,不同类别数据的配比通常也是各公司的商业机密。笔者在这里分享一个基本思路:先确定 6~10 个大类,再在每个大类中细分为若干小类,并为每个类别设置配额比例,例如计算机 15%、数学 10%、地理/历史/人文各 15%、其余百科网页 30%;最后根据预训练效果动态调整类别比例。

通用数据集的来源比较丰富,目前社区和部分公司已经发布了开源的通用数据集,例如HuggingFace上的维基百科数据;如果需要完整的中文数据集,也可以使用OpenDataLab的MNBVC、北京智源人工智能研究院(BAAI)构建的悟道 WuDaoCorpora 或 CLUECorpus2020 等中文预训练数据集。

下面以悟道 220G 版本(WuDaoCorpora Text,共 59,100,001 条)中文预训练数据集为例,演示如何构建预训练语料。

注意: 悟道数据集的官方数据需要在Science Data Bank申请下载。下面的代码读取的是社区搬运到HuggingFace的 p208p2002/wudao,约 125GB。该数据集被切分为多个parquet分片,每个分片约 100MB。parquet是面向大数据场景的列式压缩存储格式,可理解为“按列存储、压缩后的类 Excel 表格”。

由于悟道数据只能按分片逐个读取,下面代码会先打乱分片顺序,再限制读取的分片数量,避免一次性读取全部几百 GB 数据。核心代码如下:

import os, json, random, hashlib

os.environ.setdefault("HF_ENDPOINT", "https://hf-mirror.com")

from datasets import load_dataset

from huggingface_hub import list_repo_files

random.seed(42)

N = 45610

TARGET = {"百科": 0.40, "博客": 0.25, "科技": 0.10,

"教育": 0.08, "文化": 0.08, "经济": 0.05, "新闻": 0.04}

BAD = ["蜘蛛池", "站群", "seo", "代发", "加微信", "网站优化"]

repo = "p208p2002/wudao"

files = [f for f in list_repo_files(repo, repo_type="dataset")

if f.endswith(".parquet")]

base = f"{os.environ['HF_ENDPOINT']}/datasets/{repo}/resolve/main/"

files = [base + f for f in files]

random.shuffle(files) # 分片按时间切,打乱后才能覆盖不同类别

quota = {k: int(N * v) for k, v in TARGET.items()}

counts, seen = {k: 0 for k in quota}, set()

ds = load_dataset("parquet", data_files=files[:80], split="train", streaming=True)

with open("./llamafactory_data/wudao_pt.jsonl", "w", encoding="utf-8") as fout:

for item in ds:

cat = item.get("dataType", "")

if cat not in quota or counts[cat] >= quota[cat]:

continue

text = f"{item['title']}\n\n{item['content']}".strip()

if len(text) < 200 or any(w in text.lower() for w in BAD):

continue

digest = hashlib.md5(text.encode("utf-8")).hexdigest()

if digest in seen:

continue

seen.add(digest)

counts[cat] += 1

fout.write(json.dumps({"text": text, "category": cat},

ensure_ascii=False) + "\n")

if all(counts[k] >= quota[k] for k in quota):

break

执行结果如下:

1.3 根据数据规模选择训练方式

将 ./llamafactory_data/wudao_pt.jsonl 合并到 ./llamafactory_data/cyber_pt.jsonl,完成预训练数据集制作后,一般还需要统计数据集的 token 数。预训练更关心 token 数而不是条数,不同 token 规模对应的训练策略差别很大:

- 小于 1M(1,000,000)tokens:不建议使用增量预训练,直接进入微调阶段更合适。

- 1M~100M tokens:个人和小团队通常能够制作的合理数据规模,可使用 LoRA 增量预训练。

- 100M~1B(1,000,000,000)tokens:大型团队和中型公司能够制作的合理数据规模,建议使用 LoRA 增量预训练。

- 1B tokens 以上:面向企业级大模型,建议直接进行全参数预训练。

那么如何测算当前数据拥有的token数?在《大模型训练全流程实战指南基础篇(二)——大模型文件结构解读与原理解析》中提到,模型原始文件中包含一个名为tokenizer.json的分词器文件。可以先将./llamafactory_data/cyber_pt.jsonl中的text字段读出来,利用大模型的分词器统计token数:

from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen2.5-0.5B")

total, docs = 0, 0

with open('./llamafactory_data/cyber_pt.jsonl', encoding="utf-8") as f:

batch = []

for line in f:

text = (json.loads(line).get("text") or "").strip()

if not text:

continue

batch.append(text)

docs += 1

if len(batch) == 500:

enc = tokenizer(batch, add_special_tokens=False, return_length=True)

total += sum(enc["length"])

batch = []

if batch:

enc = tokenizer(batch, add_special_tokens=False, return_length=True)

total += sum(enc["length"])

print(f"{docs} 条,{total / 1e6:.2f}M tokens")

经统计,本次得到的预训练数据集共 76,457 条数据,约 926M tokens。根据这一数据规模,本次预训练将采用 LoRA 增量预训练。

1.4 在 LLamaFactory中注册数据集

数据集制作完成后,下一步需要将其注册到LLaMA-Factory中。笔者在《大模型训练全流程实战指南工具篇(九)——LLamaFactory大模型训练工具使用指南》中介绍过如何使用 LLaMA-Factory 进行大模型微调;预训练流程与之类似,第一步同样是注册数据集。

首先,将 cyber_pt.jsonl 放到 LlamaFactory/data/ 下:

然后,在LlamaFactory/data/dataset_info.json中新增如下配置:

"cyber_pt": {

"file_name": "cyber_pt.jsonl",

"columns": {

"prompt": "text"

}

}

其中columns的key是LLaMA-Factory内部字段名,value是文件中真实的列名。预训练内部使用prompt字段承载正文,因此需要将 text 映射到 prompt。

二、LLamaFactory 进行 预训练

LLaMA-Factory 通过“命令行 + 配置文件”的方式启动训练。可以说,只要配置文件编写正确,预训练就成功了一半。本次笔者基于 Qwen3-32B 模型进行预训练。

注意: Qwen3-32B 模型的训练一般需要 80~160 GB 显存,约合 1~2 张 A100 或 H100。

2.1 编写配置文件

在LLaMA-Factory目录下新建 params文件夹,并在其中新建 cyber_pt.yaml,写入如下配置:

### model(包括模型权重路径)

model_name_or_path: ~/Qwen3-32B

trust_remote_code: true

### method

stage: pt

do_train: true

finetuning_type: lora

lora_rank: 8

lora_alpha: 8

lora_target: all

### dataset

dataset: cyber_pt

cutoff_len: 4096

overwrite_cache: true

preprocessing_num_workers: 16

### output

output_dir: ~/saves/qwen3-32b/lora/pretrain

logging_steps: 10

save_steps: 50

plot_loss: true

overwrite_output_dir: true

### train

per_device_train_batch_size: 1

gradient_accumulation_steps: 8

learning_rate: 5.0e-5

num_train_epochs: 20.0

lr_scheduler_type: cosine

warmup_ratio: 0.05

bf16: true

gradient_checkpointing: true

group_by_length: true

ddp_timeout: 180000000

相关参数的详细说明,笔者已经在《大模型训练全流程实战指南工具篇(十)——小白也能懂的大模型训练参数万字详解》中介绍过,如有遗忘可以回顾。这里重点说明几个关键参数:stage 设为 pt,表示进行预训练;finetuning_type 设为 lora,表示采用 LoRA 增量预训练;lora_rank / lora_alpha 设置为 8/8,在预训练阶段保持 1:1,且数值不宜设置过高,因为还需要兼顾模型原有的通用能力;num_train_epochs 虽然设置为 20,但并不一定要选择最后一个checkpoint,完全可以选择中间某个checkpoint进行合并。

2.2 运行命令

配置完成后,在 LlamaFactory-main 目录下执行:

llamafactory-cli train ~/LlamaFactory-main/params/cyber_pt.yaml

日志会按照 logging_steps 打印 loss、learning_rate、epoch 和 grad_norm。同时,由于设置了 plot_loss: true,训练过程会将整体 loss 变化趋势绘制为一张 PNG 图片。本次训练持续了约2天,训练完成后绘制的整体loss曲线如下:

2.3 合并LORA权重

在《大模型训练全流程实战指南工具篇(九)——LLamaFactory大模型训练工具使用指南》中,笔者介绍过,在设定的存储目录下保存的是大模型的LoRA适配器。由于配置文件中设置了output_dir为~/saves/qwen3-32b/lora/pretrain,save_steps 为50,因此每隔50步都会保存一个适配器。接下来需要选择合适的适配器,与Qwen3-32B模型进行合并。

那这么多checkpoint到底应该选择哪个进行合并?从笔者的经验来看,一般选择loss在0.5~0.8之间所对应的checkpoint。可以将这个范围内对应的checkpoint分别与Qwen3-32B模型合并,得到多个模型后分别进行评测,最终选用评测结果最好的checkpoint作为预训练结果。该结果也是下一阶段微调SFT的起点。

将 checkpoint 与原始模型合并同样需要编写配置文件。在配置文件目录下新建 cyber_pt_merge.yaml,写入如下配置:

model_name_or_path: ~/Qwen3-32B

adapter_name_or_path: ~/saves/qwen3-32b/lora/pretrain/checkpoint-6250

template: qwen3_nothink

trust_remote_code: true

export_dir: ~/pretrain/Qwen3_32B_cyber_pt

export_size: 5

export_device: cpu

export_legacy_format: false

由于导出的模型会有 60 多 GB,export_size表示导出的单个.safetensors文件不超过 5 GB。执行如下命令导出模型:

llamafactory-cli export ~/LlamaFactory-main/params/cyber_pt_merge.yaml

三、预训练效果查看

完成上述操作后,大家会得到多个合并后的模型。接下来需要根据各模型的性能选择最终的预训练模型。可以先使用vllm部署合并后的模型,再通过笔者常用的两种方法挑选最合适的模型参数:

vllm serve 合并后模型的绝对路径 \

--served-model-name cyber-pt --max-model-len 4096 --port 6667

3.1 文本续写

挑选30~50个专业领域数据样本,给大模型一个领域开头,让大模型接着向下续写。例如:

from openai import OpenAI

client = OpenAI(base_url="http://localhost:6667/v1", api_key="EMPTY")

resp = client.completions.create(

model="cyber-pt",

prompt="CVE-2021-44228 是 Apache Log4j2 中的一个",

max_tokens=150,

temperature=0.2,

)

print(resp.choices[0].text)

这个环节重点观察术语使用和句子连贯性。可以分别用基座模型和预训练模型跑相同的prompt做对比:如果模型胡言乱语或反复重复同一句话,说明学习率或训练轮次可能过大,应选择更早的checkpoint;如果生成内容与专业领域数据差距较大,说明训练还不充分,应选择更晚的checkpoint。

那么如何判断模型训练后在专业领域的回答是否明显变好?可以采用关键词命中率做粗略评估:先让基座模型(未合并的 Qwen3-32B)跑一遍,再让合并后的模型跑一遍,对比两者的关键词命中率。不要只看训练后关键词的绝对命中率,因为基座模型本身可能已经知道一部分答案,绝对命中率的意义有限。

3.2 评测集评测

可以按照 1:1 的比例,从通用数据集和专业领域数据集中选择几百条数据构建评测集,然后使用《大模型训练全流程实战指南工具篇(十三)——大模型评测实战(数据集评测+自动化评测)》中介绍的EvalScope工具进行评测。这项评测重点看相对变化,而不是绝对分数;两次评测应使用同一套接口和模板。如果领域loss下降但通用集指标下降明显,说明灾难性遗忘较明显,应减少训练轮次、降低学习率或提高通用语料比例;如果两边都没有明显变化,说明这次预训练大概率没有起作用。

注意: 预训练阶段的评测结果是方向性的,不要拿它当最终结论。真正能体现模型价值的评测,要等SFT之后再做

3.3 必要时重新调整参数训练

选择checkpoint本质上是在同一轮训练结果中做低成本筛选。如果多个checkpoint的文本续写和评测集结果都不理想,就不能只盯着checkpoint继续挑,而应回到配置文件和数据集层面,重新调整参数后再训练一轮。

可以从以下几类现象入手判断:

- 领域loss下降,但通用能力退化明显:说明灾难性遗忘较重,可以降低学习率、减少num_train_epochs,或提高通用语料在预训练数据中的比例。

- 领域loss下降不明显,续写也几乎没有改善:说明领域知识注入不足,可以适当提高学习率或增加训练轮次,同时检查专业领域数据是否过少、质量是否达标、cyber_pt注册和prompt映射是否正确。

- loss震荡明显或长期不收敛:可以降低学习率、增大warmup_ratio,并检查batch size、梯度累积步数、bf16和gradient_checkpointing等训练配置是否匹配当前硬件。

- 显存不足或训练频繁中断:可以降低cutoff_len、减小per_device_train_batch_size、增大gradient_accumulation_steps,并确认gradient_checkpointing已开启。

- LoRA容量不足或遗忘明显:可以适当调整 lora_rank / lora_alpha,但不建议一次改动过多,最好保持少量变量做对照实验。

重新训练时,建议每次只调整1~2个关键变量,并记录对应的配置文件、数据比例和评测结果,避免“一次改太多、最后不知道哪个参数起作用”。重训完成后,仍然按照“文本续写 + 评测集评测”的流程重新筛选 checkpoint,最终以评测结果最好的模型为准。

最终,经过评测集评测,笔者在本次训练结果中选定checkpoint-6250作为预训练结果模型;如果后续调整参数重新训练后得到更优结果,则以新一轮评测最优的模型作为下一阶段 SFT 的起点。

以上就是本篇文章的全部内容。本文完整代码可关注笔者同名公众号:大模型真好玩,并私信 大模型训练 免费获取。

四、总结

本篇按照“数据准备 → 参数设置 → 启动训练 → 效果查看”四步,完成了网络安全大模型LoRA增量预训练:数据侧将chunks_judged.jsonl转为{"text": ...}格式,按通用语料约60%~70%专业领域语料约30%~40%的比例混合,并用高质量问答数据补充语料;参数侧给出可用的LoRA预训练配置;训练侧通过一条命令启动训练并合并适配器;评测侧从验证loss、文本续写、领域问答和通用benchmark 等角度确认效果。

至此模型已经“读完了领域教材”,但它还不知道如何将知识组织成有用的回答。下一篇将进入微调数据集的构建:从领域文档出发,如何设计问题、生成答案、进行质量筛选,并转换为 LLaMA-Factory 可直接使用的 SFT 格式。敬请期待~

除大模型训练外,笔者也在同步更新《深入浅出LangChain&LangGraph AI Agent 智能体开发》免费专栏,要说明该专栏适合所有对 LangChain 感兴趣的学习者,无论之前是否接触过 LangChain。该专栏基于笔者在实际项目中的深度使用经验,系统讲解了使用LangChain/LangGraph如何开发智能体,目前已更新 50 讲,并持续补充实战与拓展内容。欢迎感兴趣的同学关注笔者的掘金账号与专栏,也可关注笔者的同名微信公众号大模型真好玩,每期分享涉及的代码均可在公众号私信: LangChain智能体开发免费获取。

本文由 AI图鉴自动抓取并翻译,版权归原作者所有;如需查看原文请点下方链接。

相关 AI 工具

相关免费羊毛

同源资讯

阅读原文(juejin.cn)

标题、摘要与正文均来自该信源的公开页面,版权归原作者所有;本站自动聚合与翻译,仅作出处跳转。

← 返回 AI 资讯列表AI工具图鉴首页

返回 AI图鉴首页 · 提交工具 · 商务合作 · 关于本站 · 591 款 AI 工具分类导航 · 每日 AI 热门项目与动态