30天 LLM+RL+Agent 成长计划(day3)

张开发
2026/4/20 20:39:45 15 分钟阅读

分享文章

30天 LLM+RL+Agent 成长计划(day3)
RLHF微调之DPO复制可直接运行 DPO (Direct Preference Optimization) 训练示例脚本 本脚本演示如何使用 DPO 技术微调大语言模型让模型学习特定的对话风格。 DPO 核心思想 - 不需要训练奖励模型 - 直接使用偏好数据chosen vs rejected优化模型 - 让模型学会什么回答更好 硬件要求 - 显卡NVIDIA RTX 4060 12GB 或更高 - 显存约 8-10GB使用 4-bit 量化 - 存储约 5GB模型文件 训练结果 依赖库 - torch - transformers - trl (Transformer Reinforcement Learning) - peft (Parameter Efficient Fine-Tuning) - datasets - modelscope (国内模型下载加速) importosimporttorchfromdatasetsimportload_dataset,Datasetfrommodelscopeimportsnapshot_downloadfromtransformersimportAutoModelForCausalLM,AutoTokenizer,BitsAndBytesConfig,TrainingArgumentsfromtrl.trainer.dpo_trainerimportDPOTrainerfromtrl.trainer.dpo_configimportDPOConfigfrompeftimportLoraConfig,PeftModel# 1. 环境与模型准备 步骤1下载并加载预训练模型 - 使用 ModelScope 加速下载国内镜像 - 选择 Qwen2.5-1.5B-Instruct 模型轻量级适合本地训练 - 使用 4-bit 量化减少显存占用 print(正在从 ModelScope 下载模型...)# 自动从魔搭下载模型到本地缓存目录# Qwen2.5-1.5B 约 3GB适合个人电脑运行model_dirsnapshot_download(qwen/Qwen2.5-1.5B-Instruct)# 4-bit 量化配置QLoRA 技术# 目的将模型权重从 16-bit 压缩到 4-bit大幅减少显存使用# 原理使用 NF4 (Normal Float 4) 量化类型保持模型精度bnb_configBitsAndBytesConfig(load_in_4bitTrue,# 启用 4-bit 量化bnb_4bit_quant_typenf4,# 使用 NF4 量化类型精度更高bnb_4bit_compute_dtypetorch.bfloat16,# 计算时使用 bfloat16平衡速度和精度)# 加载分词器Tokenizer# Tokenizer 负责将文本转换为模型可理解的数字序列tokenizerAutoTokenizer.from_pretrained(model_dir)# 设置填充标记为结束标记EOS token# 原因生成模型通常使用 EOS 作为序列结束标记tokenizer.pad_tokentokenizer.eos_token# 加载预训练模型# device_mapauto自动分配模型层到可用设备GPU/CPU# trust_remote_codeTrue允许执行模型仓库中的自定义代码modelAutoModelForCausalLM.from_pretrained(model_dir,quantization_configbnb_config,# 应用 4-bit 量化配置device_mapauto,# 自动设备映射trust_remote_codeTrue# 信任远程代码Qwen模型需要)# 训练时必须关闭 KV Cache# 原因KV Cache 用于加速推理但训练时需要重新计算梯度model.config.use_cacheFalse# 1.5 测试查看训练前的模型输出 步骤2测试原始模型的输出风格 - 使用一个测试问题 - 观察模型在训练前的回答风格通常是标准的 AI 助手风格 - 作为对比基准 # 设定测试问题模拟日常对话场景test_prompt嘿忙啥呢晚上出来撸串不# 获取原始模型Base Model的回答print(\n--- [训练前原始 Qwen2.5 机器人风格] ---\n)print(test_prompt)# 构建消息格式OpenAI 风格的消息列表messages[{role:user,content:test_prompt}]# 应用聊天模板将消息列表转换为模型输入格式# tokenizeFalse返回字符串不转换为 token IDs# add_generation_promptTrue添加生成提示告诉模型需要生成回复texttokenizer.apply_chat_template(messages,tokenizeFalse,add_generation_promptTrue)# 将文本转换为模型输入张量# return_tensorspt返回 PyTorch 张量inputstokenizer([text],return_tensorspt).to(model.device)# 生成回复不计算梯度节省显存withtorch.no_grad():outputsmodel.generate(**inputs,max_new_tokens100)# 解码输出将 token IDs 转换回文本# split(assistant\n)[-1]提取 assistant 的回复部分print(\n原始模型回复)print(tokenizer.decode(outputs[0],skip_special_tokensTrue).split(assistant\n)[-1])# 嗨我最近在学习新的编程语言希望未来能做一些有趣的项目。晚上有空的话可以一起吃个烤串怎么样# 2. 准备 DPO 数据集 步骤3构建 DPO 训练数据集 DPO 数据集格式要求 - prompt: 用户的提问或指令 - chosen: 偏好的回答希望模型学习的风格 - rejected: 不喜欢的回答希望模型避免的风格 数据构建原则 1. chosen 和 rejected 对应同一个 prompt 2. chosen 应该比 rejected 更符合期望的风格 3. 数据量不需要很大几十到几百条即可 print(正在构造本地演示数据集跳过 Hugging Face 下载...)# 构造示例数据集# 目标让模型学习更自然、亲切的对话风格摒弃生硬的 AI 腔调mock_data{prompt:[你吃了吗,今天天气怎么样,我想去蹦迪你去吗,老板叫我加班好烦啊。,你会写代码吗],chosen:[# 期望的风格随性、亲切、有情感还没呢正打算去弄点烧烤你要一起不,外面阴沉沉的感觉要下雨出门记得带伞哈。,走起啊今晚不醉不归我早就想去晃荡一下了。,害打工人实惨。摸摸头等加完班请你喝奶茶补补偿。,略懂略懂不过要是太难的逻辑我也得抓瞎咱们一起研究呗。],rejected:[# 不喜欢的风格生硬、机械、过于正式作为一个人工智能助手我不需要进食。,根据天气预报显示今日局部地区有阵雨建议携带雨具。,我不建议进行嘈杂的娱乐活动这不利于身心健康。,加班是职场中常见的现象建议您合理安排时间保持积极心态。,我可以为您提供多种编程语言的代码示例请问您需要什么]}# 将字典转换为 Hugging Face Dataset 格式# Dataset 格式便于后续处理和训练datasetDataset.from_dict(mock_data)print(本地数据集构造完成)# 3. 配置 LoRA 和 DPO 步骤4配置 LoRA 和 DPO 训练参数 LoRA (Low-Rank Adaptation) - 只训练少量参数适配器冻结原模型 - 大幅减少训练时间和显存需求 - 训练后的权重可以合并回原模型或单独保存 DPO 训练参数 - beta控制偏好学习的强度通常 0.1-0.5 - 其他参数类似标准监督学习 # LoRA 配置# 原理在原始权重矩阵旁添加低秩矩阵进行微调peft_configLoraConfig(r8,# LoRA 秩低秩矩阵的维度越大表达能力越强lora_alpha16,# 缩放因子控制 LoRA 权重的影响程度# 目标模块指定哪些层添加 LoRA 适配器# q_proj, k_proj, v_proj, o_proj注意力层的投影矩阵# gate_proj, up_proj, down_projMLP 层的投影矩阵target_modules[q_proj,v_proj,k_proj,o_proj,gate_proj,up_proj,down_proj],lora_dropout0.05,# Dropout 率防止过拟合biasnone,# 是否训练偏置项task_typeCAUSAL_LM,# 任务类型因果语言模型生成任务)# DPO 训练配置dpo_configDPOConfig(output_dir./dpo_qwen_results,# 训练结果保存目录per_device_train_batch_size1,# 每个设备的批次大小显存限制设为1gradient_accumulation_steps4,# 梯度累积步数每4步更新一次参数learning_rate5e-5,# 学习率控制参数更新幅度lr_scheduler_typecosine,# 学习率调度余弦退火逐渐降低学习率max_steps50,# 最大训练步数演示用实际可更大save_steps50,# 每50步保存一次检查点logging_steps5,# 每5步记录一次日志bf16True,# 使用 bfloat16 混合精度训练gradient_checkpointingTrue,# 梯度检查点节省显存但速度稍慢remove_unused_columnsFalse,# 保留所有列DPO 需要多列数据beta0.1,# DPO 温度参数控制偏好学习的强度max_length1024,# 最大序列长度)# 初始化 DPO 训练器# DPOTrainer 自动处理偏好数据的损失计算dpo_trainerDPOTrainer(modelmodel,# 要训练的模型ref_modelNone,# 参考模型None 表示使用当前模型作为参考argsdpo_config,# 训练配置train_datasetdataset,# 训练数据集processing_classtokenizer,# 分词器新版 TRL 使用 processing_classpeft_configpeft_config,# LoRA 配置)# 4. 执行训练 步骤5开始 DPO 训练 训练过程 1. 对每个样本计算 chosen 和 rejected 的 log 概率 2. 计算偏好损失DPO 损失函数 3. 反向传播更新 LoRA 参数 4. 重复直到达到 max_steps 训练时间 - RTX 4060 12GB约 5-10 分钟50步 print(开始 DPO 训练...)dpo_trainer.train()# 保存微调后的模型权重LoRA 适配器# 注意保存的是适配器权重不是完整模型dpo_trainer.save_model(./dpo_qwen_final)print(训练完成模型已保存至 ./dpo_qwen_final)# 5. 测试训练后的模型 步骤6加载训练后的模型并测试 对比训练前后的差异 - 训练前标准 AI 助手风格正式、机械 - 训练后学习到的个人风格自然、亲切 # 加载微调后的 LoRA 适配器权重# PeftModel将适配器权重合并到基础模型print(\n--- [加载你的数字人分身权重...] ---)modelPeftModel.from_pretrained(model,./dpo_qwen_final)# 获取微调后Fine-tuned的回答print(\n--- [训练后你的个人风格分身] ---)withtorch.no_grad():outputsmodel.generate(**inputs,max_new_tokens100)print(tokenizer.decode(outputs[0],skip_special_tokensTrue).split(assistant\n)[-1])# 嗨晚上出来撸串挺好的你有时间的话可以来我家。不过我得先回家做饭了不然今天就吃不了了。 总结 本脚本演示了完整的 DPO 微调流程 1. 加载预训练模型使用 4-bit 量化节省显存 2. 测试原始模型的输出风格 3. 构建偏好数据集chosen vs rejected 4. 配置 LoRA 和 DPO 训练参数 5. 执行 DPO 训练 6. 测试训练后的模型效果 关键概念 - DPO直接偏好优化无需奖励模型 - LoRA参数高效微调只训练少量参数 - 4-bit 量化大幅减少显存占用 扩展建议 1. 增加更多训练数据几百到几千条 2. 调整 beta 参数0.1-0.5 之间尝试 3. 增加训练步数100-500步 4. 尝试不同的 LoRA 秩r4, 8, 16, 32 5. 合并 LoRA 权重到基础模型便于部署

更多文章