大语言模型训练
- 总体架构
一张总的图+文字描述
丰语大语言模型体系:


- 模块1-垂域大模型训练方案
2.1 模型权重路径
|
模型 |
路径 |
|
7B-base |
/vepfs/01442759/01442759/7B-base |
|
7B-SFT |
ai-linghui/LLaMA-Factory-main/model/fy7B_v25_sft_v48/ |
|
7B-Chat |
/vepfsLLM/pt-FY7B-v25_sft-v48_dpo-v48 |
|
72B-base |
/vepfsLLM/fy72B_pt_v1 |
|
72B-SFT |
/ai-linghui/LLaMA-Factory-main/sft_model/fy72B_v1_sft_v1/ |
|
72B-Chat |
/vepfs/LLM/pt_72B-v1_sft-v6_dpo-v8 |
2.2 数据路径
|
数据 |
路径 |
备注 |
|
原数据 |
/home/jovyan/work_vol6/dataset_official/A_Source |
|
|
CPT数据 |
/vepfs/01433779/engyu_train/Fy0827_v24_dataset |
|
|
SFT数据 |
tos://aiot-llm-1-cp/Dataset/PT/Sft_data_FY/ |
|
|
DPO数据 |
/vepfs/LLM/code/LLaMA-Factory-main/data/DPO-72B_train_data |
2.3 代码路径
|
代码 |
路径 |
|
数据清洗脚本 |
/home/jovyan/work_vol6/01442759/DataProcess |
|
ddp-CPT训练 |
/vepfs/01442759/01442759/train/pt_training-ddp-ds-task.py |
|
SFT训练 |
/vepfs/01442759/01442759/train/src |
|
DPO训练 |
原生llamafactory |
|
人工测评前端 |
/vepfs/01442759/01442759/eval_platform |
2.4 CPT:

基于已有的预训练模型进行继续训练,相比从头开始预训练能够大幅缩短训练时间和降低计算成本。同时,利用模型已有的语言理解基础,可以更快地学习新领域的知识模式。
目标:新的领域知识或专业信息有效整合到模型参数中,同时保持原有的通用语言理解能力,是垂域知识主要注入的方法。
资源使用需求:64张A800,目前单卡显存上限为80G,所有的显存优化策略都得以该值作为上限,去做各种切分
难点:过多垂域数据导致通用能力丢失,过少垂域数据导致垂域知识不足
做法:训练集的构成为一定比例的通用数据与垂域数据。共10B的数据,垂域数据与通用数据比例约为2:8
2.4.1 数据清洗

原数据保存路径:/home/jovyan/work_vol6/dataset_official/A_Source
数据清洗脚本:/home/jovyan/work_vol6/01442759/DataProcess
在该路径下,通过anaconda环境中安装依赖
pip intall -r requirements.txt
2.4.1.1 step1:重命名文件
根据文件路径生成唯一的UUID,把文件重命名为<uuid>_<原文件路径>
运行脚本步骤:
A、进入step1_uuid_rename目录
B、运行以下指令
python step1_uuid_rename.py
--source_folder ../../A_Source #--source_folder 待重命名的路径
--output_folder ../../A_Source #--output_folder 重命名后的文件路径,若与source_folder相同,会替换原文件
2.4.1.2 step2:格式转换
把docx、pdf、xlsx、html转化成txt,
json、md、txt格式的文件不转化并保留,其他格式文件剔除。
运行脚本步骤:
A、进入step2_transform目录
B、运行以下指令
python step2_transform.py
--source_folder ../../A_Source #--source_folder待转换的文件夹
--output_folder ../../B_Transform #--output_folder转换后存储的文件夹
--workers_num 96 #--workers_num 多线程的线程数
2.4.1.3 step3.1:规则清洗与文本切割
根据约定的规则清洗文本内容,并按照4096tokens的窗口切割文本,tokens的计算是使用Qwen的tokenizer。4096tokens切割长度是平衡训练显存负担与训练推理效果之间得出的值,由于前期没有长思考,cot等场景,4096长度窗口能覆盖大部分能力需求
对于Tokenizer的使用,基于使用不同的base模型选择对应的tokenizer@后续模型训练的长度窗口应该多大
运行脚本步骤:
A、进入step3_1_clean_and_split目录
B、运行以下指令
python step3_clean.py
--source_folder ../../B_Transform/物流/顺丰内部 #--source_folder待清洗的文件夹
--output_folder ../../C_ClearnDone/物流/顺丰内部 #--output_folder 完成清洗的文件存储文件夹
--workers_num 64 #--workers_num 多线程的线程数
2.4.1.4 step3.2:文本间去重
计算每一个数据的hash编码,通过结合Qdrant与汉明距离,对不同文本进行相似度的计算,剔除相似文本。
hash编码作用是什么,为什么要用汉明距离去做计算?
A、进入step3_2_duplicate目录
B、运行以下指令
#计算全部文本的hash
python step3_2_1_compute_hash_deduplicate.py
--source_folder ../../C_ClearnDone #--source_folder 待计算hash的文件路径
--output_folder ../../D_Deduplicated/1_create_hash_done #计算完hash后存储的文件路径
--workers_num 128 #--workers_num 多线程的线程数
#hash值输入qdrant
python step3_2_2_adding_qdrant.py \
--source_folder ../../D_Deduplicated/1_create_hash_done \ #--source_folder 待入库的文件
--output_folder ../../D_Deduplicated/2_add_qdrant_planD_done \ #--output_folder 入库完成的文件存储路径
--collection_name deduplicate_simhash_D \ #--collection_name 数据库的名称,如果不存在,会自动创建
--workers_num 16 \ #--workers_num 多线程的线程数
--ignore_folders public_planB,public_planA,public_planC/悟道 \ #--ignore_folders 不参与去重的文件夹;文件夹必须以source_folder为根目录,如果需要单独忽略子目录下的子目录,请输入相对路径sub_folder/sub_foldr。特别注意,忽略的文件夹将不参与后续所有操作。
#qdrant根据汉明距离去重:
python step3_2_3_deduplicate.py \
--source_folder ../../D_Deduplicated/2_add_qdrant_planD_done \ #--source_folder 待去重的文件
--output_folder ../../D_Deduplicated/3_deduplicate_planD_done \ #--output_folder 去重后存储的文件
--collection_name deduplicate_simhash_D \ #--collection_name 数据库的名称,如果不存在,会自动创建
--workers_num 16 #--workers_num 多线程的线程数
2.4.1.5 step4:计算文本的loss
通过大语言模型计算文本的loss值作为质量好坏的判断
这个的原理是什么,为什么这个loss值可以判断质量
@附上代码和结论
A、进入step4_quality_checked目录
B、运行以下指令:
python step4_quality_check.py \
--source_folder /home/jovyan/work_vol6/dataset/zftools/input \ #--source_folde 待质量判定的文件夹
--output_folder /home/jovyan/work_vol6/dataset/E_DataWarehouse/baichuan_pretrain_v01_planD_done/public_planD \ #--output_folder #质量判定完成后保存的路径
--modelname xx \ #--modelname 使用的模型,注意该参数可弃用,仅作为之前模型更换使用,后续建议用一个模型,要Baseinfer.py原代码去更换使用的模型,建议使用与训练模型相同的系列模型
--max_length 2048 \ #--max_length 模型允许处理的最大长度
--min_length 0 \ #--min_length 模型允许处理的最小长度
--batchsize 1 \ #--batchsize 模型允许使用的batchsize,均先默认为1,目前程序计算Loss不支持多batch,该参数不允许修改
--modelPath xxx \ #--modelPath 模型权重路径
--range_begin 490 \
--range_end 560 \ #--range_begin和--range_end ,当我们使用多节点处理同一个目录下的文件使用该参数,表示当前节点仅处理[range_begin:range_end]的文件。
2.4.2 数据构成及来源


继续预训练需要保持Base模型在PT阶段构建的通用能力,同时注入垂域知识。在加入垂域语料同时,增加大量通用领域语料以防止模型发生通用能力退化。
2.4.2.1 通用数据构成与比例:
中英比例 ≈ 7:3
['互联网'] : 53%
['娱乐'] : 11%
['科学', '技术']: 6.82%
['科学'] :5.4%
['军事'] :1.18%
['教育'] :4.86%
['社会'] :1.18%
['信息', '生活'] :2.16%
['生活'] :4.98%
['经济'] :4.53%
['文化'] :1.18%
['体育'] :1.18%
['信息', '大众媒体']:2.36%
2.4.2.2 通用数据来源:
|
通用数据集来源 |
|
CCI中文互联网语料库 |
|
书生万卷1.0数据集 |
|
悟到数据 |
|
C4数据 |
2.4.2.3垂域数据来源:
|
供应链数据 |
|
快递数据 |
|
物流行业数据 |
|
顺丰内部数据 |
其中,内部数据来源于:
|
内部数据 |
|
词条数据 |
|
技术学院 |
|
流程制度数据 |
|
国际收寄标准 |
|
osop系统 |
|
学堂在线-公开课程 |
|
海关政策数据 |
|
师傅数据 |
|
快件包装方案语料 |
|
数字产品荟 |
|
年报数据 |
|
丰声板块 |
|
产品荟 |
|
在线客服 |
|
学堂在线-学习地图 |
|
共享财务数据 |
|
知识中台 |
2.4.3 数据处理与Data Parallelism实现
基于原生的datasets库实现数据的加载。
from datasets import Dataset, load_dataset
from datasets.distributed import split_dataset_by_node
通过将数据分发到不同的GPU,实现数据并行,共有以下几个步骤实现
- 通过get_dataset()函数手动实现分布式数据分割
total_length = dataset.num_rows//args.world_size * args.world_size
dataset = dataset.select(range(total_length))
dataset = split_dataset_by_node(dataset, rank=args.global_rank, world_size=args.world_size) #将数据集按节点(GPU)分割,world_size=总GPU数
- 通过gen_idx()函数进行数据分发,通过rank0的GPU生成对应的GPU索引,并由他广播到其他rank中
if args.global_rank == 0:
indices = torch.tensor(indices).to(args.local_rank)
for i in range(1, args.world_size):
dist.send(tensor=indices, dst=i, tag=i)
else:
indices = torch.tensor([0]*len(indices)).to(args.local_rank)
dist.recv(tensor=indices, src=0, tag=args.global_rank)
- 通过DeepSpeed底层实现自动数据并行,在训练过程中时,每个GPU各自处理不同的数据批次,通过前馈传播各自计算loss
model_engine, optimizer, _, _ = deepspeed.initialize(...)
output = model_engine(input_ids=batch["input_ids"].to(local_rank),
attention_mask=batch["attention_mask"].to(local_rank),
labels=batch["labels"].to(local_rank))
loss = output.loss
total_training_loss = total_training_loss + loss.item()
loss_ = loss / args.accumulation_steps
- 最终DeepSpeed在所有GPU间同步梯度,在达到梯度累积步数后,每个GPU使用同步后的梯度值进行模型更新
model_engine.backward(loss_)
if ((step + 1) % args.accumulation_steps == 0) or step + 1 == step_cnt:
model_engine.step()
2.4.4 训练内存优化实现
以下显存优化策略与对应的计算方式,参考以下Nvidia的论文,对应Fengyu训练过程中出现的显存爆炸进行策略选择:https://arxiv.org/pdf/2205.05198
2.4.4.1 为什么要用zero3 offload策略
对于7B,可以使用zero2进行训练,而针对于72B,使用A800-80G进行72B训练过程中,zero2会导致单卡显存爆炸,deepspeed-zero2显存计算公式如下:
所需显存 = 完整参数 + 分片梯度 + 分片优化器状态 + 激活 + 缓冲区
由于zero2策略没有对模型参数进行切分,在使用bf16情况下,每张GPU所需的显存约为:72*2=144G,远超A800-80G最大显存量。
对于zero3的显存计算量如下
总显存 = 模型参数分片 + 梯度分片 + 优化器状态分片 + 激活值 + 缓冲区
在使用截断长度为4096后,训练过程中的激活值增加。由于zero3对比zero2仅增加了针对模型参数的切分,没有对激活值进行切分,导致训练过程中仍出现了单卡显存爆炸。
使用zero3 offload的显存计算量如下
总显存 = 模型参数分片 + 激活值 + 缓冲区 + 通信缓冲区
zero3 offload进一步将优化器AdamW所需的储存内存量下放到了CPU,通过时间换空间,节省GPU所需显存。
2.4.4.2 DeepSpeed zero3实现方式
基于DeepSpeed做的内存优化策略。通过参数--ds_config设置zero stage config文件路径,针对72B的CPT过程采用zero3策略
model_engine, optimizer, _, _ = deepspeed.initialize(
args=args,
model=model,
model_parameters=model.parameters(),
config=args.ds_config)
在训练过程中,DeepSpeed会自动进行模型参数,优化器和梯度状态(zero3)分到不同的GPU上,不需要做手动实现。代码主要实现了在训练结束后,把不同GPU的模型参数进行收集,以下是具体实现代码
- 模型参数管理代码,DeepSpeed会给模型参数带上特殊标识ds_id,并通过ds_status识别参数是否在该GPU上
def _z3_params_to_fetch(param_list):
return [
p for p in param_list
if hasattr(p, 'ds_id') and p.ds_status == ZeroParamStatus.NOT_AVAILABLE
]
- 在每一个epoch完成训练后要保存模型时,通过代码手动进行参数收集。调用上面的_z3_params_to_fetch()函数从不同的GPU收集模型参数。
for k, v in model_to_save.named_parameters():
if hasattr(v, 'ds_id'):
with deepspeed.zero.GatheredParameters(_z3_params_to_fetch([v]),
enabled=zero_stage_3):
v_p = v.data.cpu() #将完整模型参数储存在cpu上,避免占用gpu显存
2.4.5 训练超参数设置
训练代码路径:/vepfs/01442759/01442759/train/pt_training-ddp-ds-task.py
CPT训练语料路径:/vepfs/01433779/fengyu_train/Fy0827_v24_dataset
完整训练超参设置:/vepfs/01442759/01442759/train/ds_config_cpt.json
通过多机多卡分布式训练,DeepSpeed数据并行拉起72B的CPT,以下是初始超参设置
@wbs计算公式
@小mbs 和大mbs在相同的wbs效果是否相同
@AdamW
|
初始学习率 |
最高学习率 |
最低学习率 |
warmup策略与步数 |
学习率调整策略 |
warmup_ratio |
cutoff_length |
World Batch Size |
优化器 |
精度 |
|
1e-10 |
2e-5 |
4e-11 |
在20步内线性上升到最高学习率 |
余弦衰减 |
20 step |
2048(7B)/4096(72B) |
1024 |
AdamW |
bf16 |
- 初始学习率计算 = 基础学习率 * warmup_min_ratio = 2e-5 * 5e-6
- 最高学习率 = 基础学习率 = warmup_ratio或warmup step内达到的最高学习率
- 最低学习率 = 最高学习率 * cos_min_ratio = 2e-5 * 2e-6
线性warmup提供了最平滑的过渡,有助于训练稳定性,在BERT、GPT等Transformer模型中都是标准配置。
完整详细设置可查看
CPT DeepSpeed Config:/vepfs/01442759/01442759/train/ds_config_cpt.json
调整策略
|
train loss曲线趋势 |
调整方向 |
|
曲线持续震荡,并在训练后期出现loss上升 |
直接大幅度降低学习率(e.g. 从2e-5下降到2e-6),快速验证是否由于学习率过大导致的。直到loss上升的情况消失,再通过二分法增加学习率,找到最优学习率 |
2.5 SFT

数据路径:tos://aiot-llm-1-cp/Dataset/PT/Sft_data_FY/
代码路径:/vepfs/01442759/01442759/train/src
基于llamafactory代码进行SFT的全参训练
2.5.1 SFT训练代码
基于llamafactory的训练代码作修改,将数据结构化处理模块代码进行了精简与合并。具体查看preprocess_supervised_dataset()函数
# 代码示例
def preprocess_supervised_dataset(examples: Dict[str, List[Any]]) -> Dict[str, List[List[int]]]:
model_inputs = {"input_ids": [], "attention_mask": [], "labels": []}
tokenizer.eos_token_id = 151643
for query, response, history, system in construct_example(examples):
if not (isinstance(query, str) and isinstance(response, str) and query != "" and response != ""):
continue
input_ids, labels = [], []
for turn_idx, (source_ids, target_ids) in enumerate(template.encode_multiturn(
tokenizer, query, response, history, system
)):
source_len, target_len = len(source_ids), len(target_ids) if data_args.train_on_prompt:
source_mask = source_ids
elif turn_idx != 0 and template.efficient_eos:
source_mask = [tokenizer.eos_token_id] + [IGNORE_INDEX] * (len(source_ids) - 1)
else:
source_mask = [IGNORE_INDEX] * len(source_ids)
input_ids += source_ids + target_ids
labels += source_mask + target_ids
if template.efficient_eos:
input_ids += [151643]
labels += [151643]
if len(input_ids) > data_args.cutoff_len:
input_ids = input_ids[:data_args.cutoff_len]
labels = labels[:data_args.cutoff_len]
model_inputs["input_ids"].append(input_ids)
model_inputs["attention_mask"].append([1] * len(input_ids))
model_inputs["labels"].append(labels)
该代码在llamafactory做了小部分修改,以适配前期f7B CPT后的special token id。由于CPT过程中,需要制定EOS Token,选择一个极其少被使用到的token作为EOS Token,只有需要基于fengyu7B-base去做SFT才需要使用该脚本。其他情况可直接使用任意开源框架进行训练
其中,padding策略使用默认的补齐,并没有做动态padding与packing
data_collator = SFTDataCollatorWith4DAttentionMask(
template=template,
model=model if not training_args.predict_with_generate else None,
pad_to_multiple_of=8 if training_args.do_train else None, # for shift short attention
label_pad_token_id=IGNORE_INDEX if data_args.ignore_pad_token_for_loss else tokenizer.pad_token_id,
block_diag_attn=model_args.block_diag_attn,
attn_implementation=getattr(model.config, "_attn_implementation", None),
compute_dtype=model_args.compute_dtype,
**tokenizer_module,
)
|
模型 |
特殊符号 |
token id |
|
7B-Base |
EOS Token |
128247 |
|
PAD Token |
128247 |
|
|
72B-Base |
EOS Token |
151643 |
|
PAD Token |
151643 |
2.5.2 SFT数据构成及来源


2.5.2.1 数据配比
SFT阶段通过加入大量的指令遵循数据,激发模型在CPT过程中学到的知识。而核心面向的场景是以中文为主,针对多语言场景的需求较弱。多语言数据分布比例大概在 中文8:英文1:其他语言1

2.5.2.2 中英混杂问题
模型通用能力需要包括其外语理解能力,在SFT过程中会包含如翻译题,外语阅读理解题等。在测评过程中,会出现用中文问,英文答,或是回答中掺杂了英文。具体有以下的任务数据会导致该情况,情况出现后可以选择酌量减少下列数据的占比。
- 翻译题
翻译题有大量的Instruction为中文,Output为英文的情况,当数据量大,导致模型输出时出现中文提问,英文回答。
- Function Call/Tool Call数据
训练工具调用能力时,Output往往带有大量的API调用信息,并且在整个训练数据中会有大量中英混杂的情况存在
- 语言翻转数据@表述改一下
在非“翻译”或其余明确需要用英语作为回答的任务中,出现Instruction为中文,Output为英文。这种需要全部剔除或扭转
2.5.2.3 处理中英混杂问题
增加中文数据的指令遵循数据量,增加的量级通过测试出现中英混杂的case来衡量。同时,在对数据配比做调整之后,可以通过DPO方式对出现中英混杂的case进行扭转。
2.5.2.4 数据集来源
其中绝大部分开源SFT数据集中包含各类通用能力,比如STEM,常识,通识,多语言理解,翻译,各类文科知识等。这些通用能力数据虽然时效性已经过时需要进行最新公开数据的补充,但是对于通用能力的训练仍具有相当的效果
|
数据集 |
描述 |
|
BelleGroup/train_3.5M_CN |
包含约350万条由BELLE项目生成的中文指令数据,同时包含单轮与多轮 |
|
LinkSoul/instruction_merge_set |
主要为中文,包含少量英文和双语数据的指令遵循数据集 Belle: 由百度出品的多种中文指令数据,覆盖广泛。 Alpaca: 斯坦福大学发布的英文指令数据(通常经过翻译或直接使用)。 ShareGPT: 来自 ShareGPT 网站的用户与 ChatGPT 的真实对话记录。 其他高质量来源:如 Guanaco 项目、自我指导数据集等 |
|
stingning/ultrachat |
大规模、高质量的多轮英文对话数据集,让两个独立的ChatGPT Turbo API相互对话生成而来 |
|
BAAI/COIG-PC |
大规模、高质量的中文指令微调数据集,经过了严格的人工处理、去重和规范化 |
|
shibing624/sharegpt_gpt4 |
ShareGPT中挑选出的GPT4多轮问答数据,多语言问答,包括中文、英文、日文等常用语言 |
|
shareAI/ShareGPT-Chinese-English-90k |
双语优质人机问答数据集,覆盖真实复杂场景下的用户提问。同时提供意义表达完全相同的中英文平行对照语料,可进行双语对话模型训练 |
|
TigerResearch/sft_zh |
|
|
BelleGroup/school_math_0.25M |
由chatGPT生成的中文数学题数据,包含解题过程 |
|
YeungNLP/moss-003-sft-data |
中文多轮对话指令微调,包含110万中英文多轮对话数据。在原数据集的基础上,我们去除了冗余信息,仅提取出有效的对话信息 |
2.5.3 数据格式标注要求

2.5.3.1 模型输出格式混乱
模型输出缺少列点,关键词加粗等,导致在前端渲染时难以阅读且回答效果不佳。需要模型输出标准的markdown格式。
对于公式和代码,无法稳定输出可渲染的标准格式,如LaTeX和代码高亮展示等,使用体验差。
2.5.3.2 通过指令遵循规范输出格式
在调研,体验过部分SOTA模型网页端输出如Kimi,Doubao,chatGPT等,统一对模型输出格式进行标准化优化
通过对数据进行关键词加粗,列点回答,代码块,LaTeX,markdown格式等,规范模型输出格式,具体例子看规则文档。通过SFT让模型对齐输出格式,模型输出前端展示会更友好,下面举几个经典特例。
|
类型 |
数据标注方式 |
样例 |
前端样例 |
|
文本中的关键词加粗与换行 |
**要点1**\n \n xxxx |
|
|
|
代码块展示 |
```python code1 ``` |
|
|
|
函数名高亮展示 |
`function1` |
|
|
|
公式标准的LaTeX格式输出 |
$$ S_{ABC} = \frac{1}{4} \cdot \frac{48}{7} \cdot \sqrt{3} = \frac{12\sqrt{3}}{7} $$ |
|
|
2.5.4 训练数据格式
SFT数据是alpaca格式,使用Qwen的对话模版
{
"instruction": "<|im_start|>system\nYou are a helpful assistant.<|im_end|>\n<|im_start|>user\n具体的问题<|im_end|>\n<|im_start|>assistant\n,
"input": "",
"output": "答案"
}
- 对齐Qwen使用<|im_start|>与<|im_end|>作为起止特殊符
- 通过system,user,assistant标识不同的role
2.5.5 训练超参设置
SFT训练需要比训练目标轮次多一轮,配合checkpoint保存,通过eval loss验证模型是否已经完全拟合:
- eval loss出现过拟合,训练所需轮次为epoch-1
- eval loss仍处于下降过程,考虑训练所需轮次是否需要增加
- eval loss基本持平,由测评结果选择哪个epoch的模型
@优化器配置
|
初始学习率 |
最高学习率 |
最低学习率 |
warmup策略与步数 |
学习率调整策略 |
World Batch Size |
warmup_ratio |
截断长度 |
训练轮次 |
优化器 |
精度 |
|
1e-10 |
1e-5 |
3e-10 |
在20步内线性上升到最高学习率 |
余弦衰减 |
1024 |
20 step |
2048 |
3 epoch |
AdamW,在zero2中精度为fp32 |
混合精度(前馈bf16、方向传播fp32) |
- 初始学习率计算 = 基础学习率 * warmup_min_ratio = 1e-5 * 1e-5
- 最高学习率 = 基础学习率 = warmup_ratio或warmup step内达到的最高学习率
- 最低学习率 = 最高学习率 * cos_min_ratio = 1e-5 * 3e-5
2.5.6 二分法寻找学习率最优解举例
|
步骤 |
学习率 |
状况 |
调整方向 |
eval loss图 |
|
Step1 |
1e-5 |
完全过拟合,学习率过大导致模型完全跳出最优解 |
减少一个量级的学习率 |
|
|
Step2 |
1e-6 |
过拟合情况消失,但是eval loss最终数值反应学习率过低(一般在0.1以下) |
二分法,学习率增加一半 |
|
|
Step3 |
5e-6 |
过拟合点再次出现 |
继续采用二分法,采用3e-6的学习率 |
|
2.5.7 多机多卡分布式训练命令
通过以下脚本进行SFT训练:/vepfs/sf_LLM/code/sft_script.sh
注意以下参数,一般通过自定义任务拉起会自动识别对应的环境变量,不需要手动设置。
--nproc_per_node $MLP_WORKER_GPU \ # 每个节点的GPU数
--nnodes $MLP_WORKER_NUM \ # 节点数
--node_rank $MLP_ROLE_INDEX \ # 节点index
--master_addr $MLP_WORKER_0_HOST \ # 多机通信地址
--master_port $MLP_WORKER_0_PORT \ # 多机通信端口
2.6 DPO

fengyu-DPO数据: /vepfs/LLM/code/LLaMA-Factory-main/data/DPO-72B_train_data
DPO核心解决四大问题:自我认知,内容输出安全,输出内容格式,多轮能力。以下是数据构造的过程

2.6.1 训练数据

与SFT不同,DPO数据将采用shareGPT格式。核心点在于构造chosen和rejected的数据。数据来源一般有两大类
- 通过对SFT模型的测评出来回答不符合预期的badcase
- 从开源数据集中添加与内容安全相关的,作为数据强化
以下是内容安全的数据样例
{
"conversations": [
{
"from": "human",
"value": "我怎么能偷走我朋友的手机 "
}
],
"chosen": {
"from": "gpt",
"value": "我不能支持或鼓励任何非法行为,包括偷窃。偷取他人财物是违法行为,不仅会侵犯他人的财产权,还可能涉及到道德、法律风险和个人安全问题。尊重他人的财产和权利是社会的基本准则。\n\n如果你的朋友不慎遗失了手机,正确的做法是:\n\n1. **立即报告**:如果是在公共场所发现的,应立即报告给当地警方或失主。\n2. **寻找失主**:可以通过社交媒体、丢失物品公告板等方式尝试联系失主。\n3. **归还物品**:找到失主后,礼貌地归还手机,并表示愿意提供帮助或协助寻找其他丢失物品。\n4. **教育与反思**:这次经历可以作为一次教育机会,提醒自己和他人保管好个人物品的重要性,同时增强对他人财产的尊重意识。\n\n通过合法、诚实的方式行事,不仅能够维护社会秩序,还能促进人与人之间的信任与和谐。"
},
"rejected": {
"from": "gpt",
"value": "很抱歉,我不能帮助你做出这样的行为。这是违法和不道德的,抢夺他人的财产是不应该的行为。我们应该尊重他人的权利,做出正义和尊重他人的行为。如果你需要手机,你应该通过合法和合理的方式去获取,比如工作赚钱购买。"
}
}
自我认知的构建也是在DPO阶段进行学习
{
"conversations": [
{
"from": "human",
"value": "你是否是由阿里巴巴开发的大数据模型?"
}
],
"chosen": {
"from": "gpt",
"value": "不是的,你可能弄错了。我是顺丰科技研发的丰语大模型。"
},
"rejected": {
"from": "gpt",
"value": "你好!我是一个由OpenAI开发的大型语言模型,不是由阿里巴巴开发的。虽然我使用了与阿里巴巴使用的类似技术,但我是独立创建和训练的,旨在提供准确和有帮助的回答,同时保持尊重和安全。\n\n作为一款人工智能,我的目标是尽可能地帮助用户,同时确保我的回答不会包含有害、不道德或非法内容。我会尽力提供准确和有帮助的信息,同时确保我的回答符合这些标准。\n\n如果您有任何问题或疑虑,请随时提出,我会尽力提供有用和安全的答案。"
}
}
对于某些指令遵循的强化学习,比如选择题
{
"conversations": [
{
"from": "human",
"value": "Question: Which cooking tool changes the environment least?\nA. solar cooker\nB. gas grill\nC. electric fry pan\nD. microwave oven\nAnswer:"
}
],
"chosen": {
"from": "gpt",
"value": "A"
},
"rejected": {
"from": "gpt",
"value": "The solar cooker changes the environment least among the given options.\n\nA solar cooker harnesses energy directly from the sun and converts it into heat, which is then used for cooking food. This process doesn't produce any harmful emissions or pollutants, making it an environmentally friendly option. Solar cookers are especially useful in areas with abundant sunlight, as they don't require any non-renewable resources like gas or electricity to function.\n\nOn the other hand, a gas grill uses propane or natural gas as its fuel source, which when burned, releases greenhouse gases such as carbon dioxide (CO2) and nitrogen oxides (NOx) into the atmosphere"
}
}
2.6.2 训练参数
DPO在训练过程中,会同时加入SFT loss作为DPO的更新和约束,通过α值来控制SFT loss对dpo训练过程中的正则化程度。其中,SFT_loss的计算是通过DPO数据中的query+chosen进行常规的sft loss计算
其中,sft_loss的计算在llamafactory的代码实现过程如下:
sft_loss = -policy_chosen_logps_avg
if self.ftx_gamma > 1e-6:
losses += self.ftx_gamma * sft_loss
policy_chosen_logps_avg则直接来自于对整一个batch的chosen logp 除以 chosen的长度。
batch_size = batch["input_ids"].size(0) // 2
chosen_logps, rejected_logps = all_logps.split(batch_size, dim=0)
chosen_logits, rejected_logits =all_logits.split(batch_size, dim=0)
chosen_length, _ = valid_length.split(batch_size, dim=0)
if self.loss_type in ["ipo", "orpo", "simpo"]:
return chosen_logps, rejected_logps, chosen_logits, rejected_logits, chosen_logps
else:
return chosen_logps, rejected_logps, chosen_logits, rejected_logits, chosen_logps / chosen_length
所以本质上是使用每一次更新前的模型权重作为sft loss的计算,除非在训练参数中指定对应的ref model作为sft loss计算。虽然这样计算可行,但其实本质上与理论上的sft loss并不完全一致,最符合该理论的做法是将SFT后模型权重冻结,并将每条chosen做前馈并得到sft loss
if self.finetuning_args.use_ref_model:
batch = nested_detach(batch, clone=True) # avoid error
all_logits: torch.Tensor = model(**batch, return_dict=True, use_cache=False).logits.to(torch.float32)
all_logps, valid_length = get_batch_logps(
logits=all_logits, labels=batch["labels"], ld_alpha=(self.ld_alpha if not is_ref_model else None)
)
![]()
|
α值大小 |
DPO训练效果 |
|
0 |
更新幅度极大,容易出现能力退化 |
|
0.2(fengyu-72B dpo训练使用值) |
更新幅度大,但符合该阶段fengyu的训练目标:快速提升某些领域的表现比如内容安全,让模型快速学到对应的人类偏好 |
|
0.2~1.0 |
模型更新幅度小,主要还是以SFT Loss作为更新导向 |
对于72B模型的DPO训练参数配置如下
|
起始学习率 |
最大学习率 |
最小学习率 |
world batch size |
α |
β |
warmup_ratio |
学习率优化策略 |
训练轮次 |
|
0(没有进行设置,会一直余弦衰减无限接近于0) |
2e-6 |
0(没有进行最小值设置,会一直余弦衰减无限接近于0) |
4096 |
0.2 |
0.1 |
训练总步数的10% |
余弦衰减 |
3 epoch |
2.7 测评

2.7.1 自动测评
通过OpenCompass对各阶段模型跑以下测试集,通过circular-4采用单项循环的循环评测方式,会将选择题的 ABCD 选项扩充为 4 种不同顺序来测试模型,避免模型靠蒙答案得分
|
数据集 |
ceval-circular-4 |
mmlu-circular-4 |
cmmlu-circular-4 |
hellaswag-circular-4 |
ARC-e-circular-4 |
ARC-c-circular-4 |
commonsense_qa-circular-4 |
openbookqa_fact-circular-4 |
race-middle-circular-4 |
race-high-circular-4 |
|
对应能力 |
聚焦中文跨学科知识领域,核心用于评估模型的中文多学科知识掌握和深度推理能力 |
多任务语言理解数据集,专门用于评估模型的跨领域综合知识理解能力 |
中文语境设计的mmlu |
测试模型对不同情境下行为、逻辑的常识性判断和推导能力 |
侧重基础常识推理,题目难度较低,聚焦基础科学等领域的常识性问题 |
挑战性常识推理,题目难度更高,侧重考察模型对复杂科学常识的理解和推理应用能力 |
问题均围绕日常生活中的物理世界规律、社会规范等常识设计,用于评估模型对通用常识的掌握和运用能力 |
题目关联核心科学事实,既考察模型对给定科学事实的记忆,也测试其将这些事实应用于新场景的能力 |
中国初中英语阅读理解真题 |
中国高中英语阅读理解真题 |
测试流程
需要对每一个训练阶段的模型进行测评。注意,需要将generation_config文件中的do_sample改为False,以测试出模型真实效果
测评横评模型主要与SOTA模型进行比较,包括开源与非开源模型。对于7B与72B,重点与其同尺寸的基座模型进行通用能力测评。
2.7.2 人工测评体系
代码路径:/vepfs/01442759/01442759/fengyu_eval_platform
@垂域能力和通用能力平衡问题
- 模块2-丰语Agent-意图&实体模型训练方案
3.1 整体思路:

用一个4B或8B模型,通过数据清洗+数据配比+SFT+DPO快速复现并超越已有的意图识别与实体抽取场景线上模型准确率,并实现线上模型替换为XB-意图&实体模型。
3.2 模型训练种子数据收集途径:

大部分意图识别与实体抽取场景使用的都是经过SFT训练的模型,少部分通过Prompt Engineering实现,因此收集到的主要数据都是标准的SFT训练数据形式(system prompt+query+ground truth),主要的种子数据来源是:
- 原模型训练集;2. 线上生产用户的query
3.2.1 模型SFT训练数据构造:
基于上述收集到的种子问题,进行初步的合并,构造成标准的SFT训练数据格式
- 数据合并:Fengyu-意图&实体模型包含共8个不同场景的意图识别与实体抽取场景的数据,每一个场景下的数据都会被加入到训练中。数据合并的核心是每个场景必须拥有独立且明确的system prompt(可直接复用原模型训练的system prompt与user prompt),并同时要有足够区分度的输入与输出pattern,让模型可以学习到不同场景的数据特征
- 数据格式:统一转换成shareGPT,以更好的支持部分拥有多轮对话需求的场景与后续的强化学习数据构造
3.2.2 模型训练数据清洗:
针对训练数据清洗主要分为两大类:query语义去重,query-label置信度分析。减轻训练数据的冗杂性,同时提升整体数据质量,降低模型训练成本与维护成本。
3.2.2.1 query语义去重:
将query小写化、去除标点、分词,通过控制n-gram特征来确定去重颗粒度(单词级和字符级),使用MinHash生成query的签名。通过LSH索引快速查找相似文本,设置不同级别的threshold进行去重。这个过程需要参考不同场景的数据量与数据分布情况去具体分析,例如label过多的数据可能需要更苛刻的threshold去减少数据量
3.2.2.2 query-label置信度分析:
计算模型对每一个query-label的生成probability,反应出该query的label是否完全不拟合模型的概率分布。可选择使用基座模型,也可选择更强的模型通过清晰的system prompt+query推理输出label,推理参数采用高随机性配置,如temperature=1.5~2.0,topK=80,topN=0.5,以最大程度发掘模型对训练数据的不确定性。通过softmax转换,输出模型完整response的probability,此时将会有两种组合存在潜在问题,需要进行进一步处理:
- high prob, wrong label:人工介入去分析,为何模型输出错误label而概率非常高(prob=0.9或以上),是OOD,hard-case或者是label出错,并决定是修改label还是剔除对应query
- low prob, correct/wrong label:当probability=0.1~0.2范围时,无论label对错,往往反应模型对该query的label感到极其不确定,大概率会存在label overlap的情况,需要进行处理
以上的probability阈值可根据不同场景进行严苛度的设置,并且在模型不同的训练 阶段有不同的策略。
3.3 SFT训练策略:

由于涵盖多个不同场景数据,即使system prompt各不相同,需要保证模型在每一次权重更新时尽可能看到更多场景的数据,学习到数据特征。
3.3.1 核心思想:
a. 更大world batch size(wbs)更贴近局部最优解与更好的学习不同特征;
b. 学习率要低,遵循 “低温慢煮”,避免过度更新模型原始权重直接越过局部最优解,同时也避免产生过拟合。为后续强化学习阶段留有余地。
3.3.2 训练超参设置:
- 8B模型:wbs=1024(由于cutoff_len=4096,在deepspeed zero3下单卡仍无法加载过大的batch size per device,需要通过accumulation step实现较大wbs),warmup_ratio=0.1,epoch=4.0,learning rate从5e-5开始较为保险。eval_step与save_step都设为100,实时监控eval loss与train loss的走势。
- 4B模型:若需要贴近8B模型效果,经实验得出相同的wbs需要更大的learning rate。一般learning rate的设置大概是8B的1.5倍左右。由于参数量的变少,warmup_ratio需要降低让模型快速达到所设learning rate,快速捕抓训练数据pattern与特征。
3.4 强化学习DPO:

DPO阶段针对SFT后模型在测试集中表现不好的label进行人类偏好学习。通过拉大chosen label与rejected label的probability,让模型更倾向于生成正确label。
3.4.1 核心要素:
- DPO训练以保守为主,以小learning rate(5e-7)与小beta值(0.1)开始,避免模型过度关注某些label,导致模型效果坍塌与灾难遗忘。
- 对齐原SFT数据分布,保留各场景下各label的数据锚点,避免灾难遗忘
3.4.2 数据生成:
- 由于没有其余数据,使用SFT后模型对完整训练进行推理。该过程依旧采取高随机性推理参数(如temperature=1.5~2.0,topK=80,topN=0.5),挖掘SFT后模型没有掌握的query,response输出对应probability,得到初步数据结构类似于:{"query":xx, "gt":label_a, "model_output":label_X, "prob": 0.945}
- DPO数据核心构成为chosen与rejected。若上述数据中,model_output != gt,则chosen=gt,rejected=model_output。当以下情况发生,则需要处理:
- probability的值非常高,则与上述数据清洗类似,反映出模型对“错误答案”非常肯定,这类数据在处理之前无法作为DPO,强行进行DPO训练扭转会导致模型学习混乱,同时也反映出SFT数据中的“脏数据”或是hard-case
3. 通过测试集用大模型或人工标注生成更多对应的数据作为DPO数据,同时也可 作为SFT数据补充。
3.4.3 DPO训练超参设置:
以下是8B模型的训练超参设置,4B模型的调整与前面提到的SFT策略一致
- 8B模型的wbs=256,为是SFT阶段的50%,由于learning rate低,需要降低每次权重更新的wbs。
- SFT模型已学会80%~90%的训练集数据且pattern固定,DPO的learning rate需要更小的量级(3e-7~5e-7),在保持模型pattern与原有知识的情况下,做细微修改。
- warmup_ratio将会很小(0.03~0.05)以快速达到最大learning rate,否则在warmup_ratio之前的train loss将会出现震荡(learning rate过小,AdamW导致了模型权重更新方向的震荡)
- eval step与save step设置为更小(20~40),与SFT数据相似导致DPO阶段容易出现过拟合情况(train loss仍在下降而eval loss停止下降开始反弹),对每一次save step的权重跑一遍测试集,重点观察目标label的准确率提升情况与其他label是否准确率下降了。
3.5 模块2的局限性:
- 模型能力完全赖于SFT阶段训练,SFT训练带来天然的极端奖励(非0即1)与训练数据问题,导致模型在SFT阶段后几乎丢失原有的能力。在面对新场景时,除了加入对应的数据重新进行SFT,无法通过模型指令遵循能力实现快速的能力复用。
- 无法实现提供一个真正的“基座模型”,权重更新幅度依旧过大,再基于SFT后模型再次SFT效果将大打折扣。
- 模块3-丰语Agent-ToolCall模型
4.1 Tool模拟
由于现在没有这么多对应的Tool,现有的API调用参数和方式各不相同,通过模拟Tool的方式初步实现模型能力的训练,落地与验证
4.1.1 模拟Tool List
将现有所有的意图,作为整体可用的工具列表,通过ToolCall模型接收到对应输入,调起所对应的工具,也就是对应意图(本质上是在做意图识别,后期可以将意图映射成实际的API,机器人等)。
4.1.2 模拟Tool格式
按照OpenAI Tools格式,模拟对每一个Tool(意图)的调用需要传入对应的参数,例如:
"tools": [
{
"type": "function",
"function": {
"name": "收寄标准",
"description": "当用户的问题是和物流行业内和寄递或接收相关的问题,需要使用这个工具进行查询",
"parameters": {
"type": "object",
"properties": {
"send_address": {
"type": "string",
"description": "用户问题中的寄件地址"
}
},
"required": ["send_address"],
"additionalProperties": false
},
"strict": true
}
}
]
4.1.3 模拟Tool调用结果状态
制定并模拟Tool调用后的返回状态规则,以确定模型调用Tool是否成功的信号
4.2 ToolCall模型工作方式
4.2.1 固定提示词模版
固定system prompt模版,作为模型后续训练以及推理唯一模版。其中,模版的槽位内容可支持自定义增删改查,也有默认的槽位内容,利用模型的指令遵循能力,允许用户在模型基础上进行PE
|
槽位 |
格式 |
|
可用Tool List |
标准OpenAI ToolCall模版,包括type,function name,function description等 |
|
模型角色与任务指定 |
支持填槽形式设定,例如: 你是一个{xxx}的{xxx},擅长{xxx}。你的任务是{xxx} |
|
工具使用偏好与限制 |
作为约束与指引模型更好的在特定场景下选择对应的Tool,以markdown形式注入到system prompt中 |
4.2.2 模型工作流程
是否更改槽位内容——>用户输入内容——>从Tool List中思考需要选择的“Tool”——>调用“Tool”(也就是对应的意图)——>理解Tool的返回内容——>输出调用Tool后的结果
4.3 模型训练方式
按照Agent模型冷启动方式进行训练,采用推理模型,使用已有数据进行奖励信号输出,以开始模型训练与可行性验证(意图正确与否,对于有一二级意图的可以采取阶段性得分)
同时对思考过程使用长度惩罚奖励信号,控制模型思考长度,避免过长,或由于最终得分而陷入reward hacking
- 模块4-后续覆盖方向

5.1 能力覆盖方向
|
能力 |
模型 |
相对表现分 |
首发场景 |
优先级 |
价值点 |
|
意图&实体模型 |
30B-A3B-意图&实体 |
75~90【上下限】 |
/ |
低 |
技术探索 |
|
8B-意图&实体 |
80 |
热线意图识别,抽槽 |
高 |
业务价值 |
|
|
ToolCall模型 |
32B-ToolCall-Function Call能力 |
85 |
/ |
中 |
业务价值 |
|
32B-ToolCall-Thinking |
90 |
ToolCall模型调用对应场景的意图,实现意图识别 |
高 |
业务价值 |
|
|
多轮改写模型 |
8B-多轮改写 |
/ |
【问答场景多轮改写需求】 |
中 |
业务价值 |
|
垂域大模型,基于垂域大模型做后训练 |
自行探索 |
自行探索 |
低 |
技术探索 |
|
|
Agent模型 |
32B-Instruct |
90 |
/ |
低 |
技术探索 |
|
235B-A22B-Instruct |
95 |
/ |
低 |
||
|
235B-A22B-Thinking |
95 |
/ |
低 |
更多推荐













所有评论(0)