简介:这份《基于Qwen2.5-7B-Instruct的大模型微调实战指南》面向具备机器学习基础、希望独立跑通大模型微调流程的研究人员与开发者,围绕在AutoDL平台租用4090 GPU、从HuggingFace镜像获取Qwen2.5-7B-Instruct、借助LLaMA-Factory完成LoRA微调这一主线展开,重点解决环境配置繁琐、模型下载缓慢、训练参数与数据集配置不清晰等实际问题。资料以单份PDF手册形式交付,压缩包约2.28MB,内含1个PDF文件,便于在本地或移动端随时查阅,也可作为实验过程中的对照清单。目前已有12513人学习,说明其在微调入门与实操场景中具备较高参考价值。内容覆盖环境搭建、模型下载、数据集添加、显存占用查看及wandb实验记录等环节,并配有平台操作截图与命令示例,读者可据此搭建可复现的训练环境,理解关键参数含义,掌握训练过程监控与实验结果追踪方法,并积累常见配置问题的排查思路。
1. 为什么选 Qwen2.5-7B-Instruct 配 4090:一张卡能跑通的微调方案
很多人第一次做大模型微调,卡住的不是算法,而是显存和环境的组合:模型下到一半数据盘满了,依赖装到一半版本冲突了,训练跑到 step 30 显存炸了。Qwen2.5-7B-Instruct 适合当第一份练手权重——7B 的体量在 24GB 显存的 4090 上做 LoRA 刚好留有余量,全量微调不够,LoRA 够,4bit 量化后更宽裕。这份实操手册走的就是这条路:在 AutoDL 开一台 4090、社区镜像省掉装环境的时间、hf-mirror 拉权重、LLaMA-Factory 可视化界面点参数、wandb 记曲线。适合有 Python 和 Transformer 基础、手里已经攒了几百上千条问答数据、想把它们真正训进模型里的开发者;如果你只是想调 API 换个提示词,这份东西对你没用。
2. AutoDL 开一台 4090:镜像选型、实例配置与两条连接通道
2.1 为什么是 4090 而不是 A100:显存账要先算清楚
选卡这件事,我被问过太多次,答案基本取决于你走哪条微调路线。Qwen2.5-7B-Instruct 的 bf16 权重约 15GB 上下,分片存成几个 safetensors 文件。如果你做全量微调,训练态要额外扛梯度、优化器状态和激活值,24GB 显存连权重都放不下,起步就得 80GB 级别。LoRA 只训练旁路的低秩矩阵,主干冻结,显存主要被权重和激活值吃掉,4090 的 24GB 在cutoff_len=2048、batch_size=1、开梯度检查点的配置下能稳定跑住,QLoRA 把主干量化到 4bit 之后余量更大。
把常见组合列一下,心里有个数:
| 微调方式 | 权重占用 | 训练额外占用 | 24GB 4090 可行性 |
|---|---|---|---|
| 全量 SFT(bf16) | 约 15GB | 梯度+优化器约 45GB 起 | 不可行 |
| LoRA(bf16 主干) | 约 15GB | 激活+临时约 4~7GB | 可行,需控 cutoff_len |
| QLoRA(4bit 主干) | 约 5GB | 激活+临时约 3~5GB | 宽裕,可加大 batch |
| LoRA + 长上下文(4096+) | 约 15GB | 激活显著上升 | 需 batch=1 + 梯度检查点 |
A100 当然更稳,但按小时计费的成本是 4090 的数倍,对一个还在验证数据质量的实验来说不划算。我的习惯是:先用 4090 把数据和流程跑通,确认 loss 曲线和人工抽检都正常,再决定要不要换大卡加 batch 提速。反过来先上大卡,往往是花了几倍的钱在调一个格式有错的数据集。
2.2 创建实例:镜像选对了能省掉半天
流程本身不复杂,容易出错的是镜像和磁盘。注册账号、按要求完成实名认证后,进算力市场挑 4090,创建实例时镜像那一栏不要选基础镜像,直接选社区镜像里的agiclass/fine-tuning-lab/finetune-lab-v8并取最新版本。这类预置镜像已经把 PyTorch、CUDA、 transformers 这一套依赖配好,省掉你自己跟版本冲突搏斗的时间。后面 LLaMA-Factory 仍然要单独装,但底座是干净的。
创建时还要注意磁盘布局,这是新手最容易忽略的地方:
| 路径 | 性质 | 容量量级 | 放什么 |
|---|---|---|---|
/root(系统盘) | 随实例 | 几十 GB | 代码、conda 环境、临时脚本 |
/root/autodl-tmp(数据盘) | 随实例 | 数百 GB | 模型权重、数据集、训练输出 |
/root/autodl-fs(如有) | 可选挂载 | 按需 | 跨实例共享的文件 |
7B 权重解压后 15GB 左右,加上训练时的 checkpoint 和导出产物,系统盘那几十 GB 很快见底。所以从第一条命令开始,工作目录就固定在/root/autodl-tmp,不要图省事在/root下面 clone 模型。
实例状态变成「运行中」就开始计费了,不用的时候点关机,下次开机再继续。开机后先确认一下驱动和环境:
# 确认卡被正确挂载,驱动版本和显存都能看到 nvidia-smi # 看磁盘余量,重点看 /root/autodl-tmp 这一行的 Avail df -h # 确认预装环境里 torch 能识别 CUDA python -c "import torch; print(torch.__version__, torch.version.cuda, torch.cuda.is_available())"nvidia-smi的输出里重点看三处:右上角 CUDA Version 与镜像匹配情况、显存总量是否为 24564MiB 左右、进程列表是否为空。df -h用来提前发现数据盘没挂上或者被历史文件占满的情况。最后那行 Python 命令返回True才说明软件栈是通的,如果返回False,后面装多少依赖都是白费。
2.3 SSH 与 VSCode Remote-SSH:两种连法各有适用场景
连接信息在实例详情页能看到,包括 host、端口、用户名(一般是 root)和密码。Windows 上我常用 FinalShell,Mac 上 Termius 更顺手,本质都是标准 SSH 加一个 SFTP 面板。命令行版本长这样:
# 端口和 host 从实例详情页复制,不要写死 ssh -p 12345 root@region-xx.autodl.com # 需要传数据集时用 sftp,或者直接在本地用图形化 SFTP 面板拖 sftp -P 12345 root@region-xx.autodl.com-p后面是平台分配的端口,不是默认 22;-P是大写,sftp 的参数和 ssh 不一样,这点坑过不少人。密码登录成功后建议顺手配一个本地公钥,后面 VSCode 反复重连会舒服很多。
VSCode 走另一条路:扩展市场装 Remote - SSH,在~/.ssh/config里写一段主机配置,之后左侧远程资源管理器里直接点连接,整个远端目录就像本地工程一样打开。好处是编辑数据集 JSON、改 yaml 配置不用来回传文件,坏处是断线重连偶尔会卡,训练跑起来之后建议还是用 SSH 终端挂着tmux或nohup,别把训练进程绑在 VSCode 的终端会话上。
WebUI 的端口转发也是靠这条通道:VSCode 连上之后会自动把远端的 7860 或 6006 转发到本地,浏览器直接开http://localhost:6006。用纯 SSH 客户端的话,要去 AutoDL 控制台的「自定义服务」里开对应端口,两者选一个就行。
2.4 目录约定与第一次开机检查清单
开机后我固定跑一遍这几件事,两分钟,能省掉后面一堆莫名其妙的问题。第一,确认当前在哪个 conda 环境,预置镜像可能默认激活了一个,conda info --envs看一眼。第二,把工作目录切到/root/autodl-tmp并建好models、datasets、outputs三个子目录,后面所有路径都往这里落。第三,pip config list确认 pip 源指向国内镜像,装 LLaMA-Factory 依赖时速度差别很明显。第四,用nvidia-smi -l 1挂一分钟,确认空载显存占用正常,没有别的进程偷偷占卡。
# 建好固定的工作目录结构,后面所有路径都基于它 mkdir -p /root/autodl-tmp/{models,datasets,outputs} cd /root/autodl-tmp # 确认 conda 环境列表和 pip 源 conda info --envs pip config list # 空载观察一分钟,确认没有残留进程占显存 nvidia-smi -l 1目录结构定死之后,配置里写路径就不会东一个西一个。models放原始权重,datasets放自定义数据,outputs放 LoRA 检查点和合并后的模型,出问题时按目录排查,比在一堆散落路径里翻要快得多。
3. 权重与框架落地:hf-mirror 拉模型、LLaMA-Factory 装依赖
3.1 用 hf-mirror 拉 Qwen2.5-7B-Instruct:LFS、分片与 .git 清理
HuggingFace 直连的下载速度在国内极不稳定,十几 GB 的权重可能下一晚上还断在 60%。hf-mirror 是一个镜像入口,把仓库地址里的域名换掉即可,仓库路径保持不变,所以Qwen/Qwen2.5-7B-Instruct这段不用改。
cd /root/autodl-tmp/models # 大文件走 git-lfs,必须先装钩子再 clone,否则拉下来是一堆指针文件 git lfs install # 用镜像域名替换 huggingface.co,仓库路径保持一致 git clone https://hf-mirror.com/Qwen/Qwen2.5-7B-Instruct cd Qwen2.5-7B-Instruct # .git 里存着完整的 LFS 对象副本,等于把权重存了两份,务必清掉 rm -rf .git # 核对关键文件是否齐 ls -lhgit lfs install是全局钩子,只跑一次;如果跳过这步,clone 下来的是几百字节的 LFS 指针文本,加载模型时会报 safetensors 解析失败。rm -rf .git这一步是血泪经验——LFS 会在.git/lfs/objects里保留一份完整对象,7B 权重等于占掉 30GB,数据盘不够大的实例会直接写满。清理完检查目录里应该有config.json、generation_config.json、tokenizer.json、tokenizer_config.json和若干个model-0000x-of-0000y.safetensors,缺任何一个都别急着往下走。
如果 git clone 中途断了,重试时先删掉不完整的目录再拉,别在残骸上续,LFS 的断点续传行为不够可靠。另一个更稳的备选是官方 CLI:
# 备选方案:走 HF 官方下载器,支持断点续传,适合网络抖动大的时候 export HF_ENDPOINT=https://hf-mirror.com pip install -U huggingface_hub huggingface-cli download Qwen/Qwen2.5-7B-Instruct \ --local-dir /root/autodl-tmp/models/Qwen2.5-7B-Instruct \ --resume-downloadHF_ENDPOINT这一行是让 huggingface_hub 走镜像,--local-dir指定落盘位置,--resume-download让中断之后能接着下。两种方式选一种即可,不要混着用,否则会出现同一个模型两份半成品。
3.2 LLaMA-Factory 克隆与 conda 环境:为什么锁 Python 3.10
LLaMA-Factory 是目前上手成本最低的微调框架之一,命令行和 WebUI 共用一套配置,训练、推理、导出、评测都能通过llamafactory-cli的不同动作参数切换。装它的关键在独立 conda 环境,不要往预置镜像的 base 环境里灌,版本冲突之后很难回滚。
cd /root/autodl-tmp git clone https://github.com/hiyouga/LLaMA-Factory.git # 单独建环境,Python 3.10 是目前依赖兼容性最稳的版本 conda create -n llama_factory python=3.10 -y conda activate llama_factory cd LLaMA-Factory # -e 是可编辑安装,改源码立即生效;extras 里带上 torch 和 metrics 依赖 pip install -e '.[torch,metrics]'python=3.10不是随便写的,3.9 太老有些新依赖装不上,3.12 又会让部分编译型依赖找不到 wheel,在 4090 这种新卡上跑起来容易出问题。pip install -e '.[torch,metrics]'里的方括号是 extras 语法,shell 里必须加引号,否则会被当成通配符;torch这个 extra 会把匹配 CUDA 版本的 PyTorch 一并拉下来,metrics是评测要用的。整个安装过程几分钟到十几分钟,取决于 pip 源速度。
装完必须验证,别默认它一定成功:
# 版本号能打出来说明入口脚本已注册 llamafactory-cli version # 确认 torch 在虚拟环境里仍然能识别 CUDA python -c "import torch; print(torch.__version__, torch.cuda.is_available())"如果llamafactory-cli提示 command not found,八成是 conda 环境没激活或者-e安装中断过,重新conda activate llama_factory再装一遍。torch.cuda.is_available()返回False则说明 conda 环境里装的是 CPU 版 torch,需要按镜像的 CUDA 版本重装对应 wheel。
3.3 启动 webui 与端口转发:llamafactory-cli 的动作参数
llamafactory-cli的设计是「一个入口 + 一个动作参数」,动作决定这次要干什么。常用的几个:
| 动作参数 | 用途 |
|---|---|
version | 打印版本信息,装完先跑这个 |
train | 命令行启动训练,吃 yaml 配置 |
chat | 命令行推理,用于快速验证模型 |
export | 合并 LoRA 权重并导出完整模型 |
api | 起一个兼容 OpenAI 协议的接口服务 |
eval | 用 mmlu 等标准数据集做评测 |
webchat | 只开推理页面,不加载训练面板 |
webui | 开完整可视化面板,含训练、预测、对话、导出 |
启动 WebUI:
# 在 LLaMA-Factory 目录下,激活环境后执行 conda activate llama_factory cd /root/autodl-tmp/LLaMA-Factory # 指定端口,方便在 AutoDL 自定义服务里映射 GRADIO_SERVER_PORT=6006 llamafactory-cli webuiGRADIO_SERVER_PORT=6006是因为 AutoDL 自定义服务映射的是 6006 端口,Gradio 默认走 7860,不改的话在控制台点开是空的。用 VSCode 的话不设这个变量也行,端口转发会自动处理 7860。启动后终端会打印一个本地地址,浏览器打开看到 LlamaBoard 的四个页签(训练、评估预测、对话、导出)就算成功。
3.4 三个上线前必须核对的版本点
同一份代码在不同镜像上表现可能完全不同,出问题时先怀疑环境。我固定核对三件事:nvidia-smi里的驱动版本和 CUDA 版本、torch.version.cuda报告的 CUDA 版本、以及 LLaMA-Factory 自身的版本号。三者之间是有对应关系的,驱动版本过低会导致新一点的 torch 直接报 CUDA driver version is insufficient。另外那个预置镜像里的 conda 环境名可能是finetune之类,如果 LLaMA-Factory 装在自己的llama_factory环境里,注意别在两个环境之间来回切换搞混。
4. WebUI 里的一次完整微调:参数映射、数据集注册与显存观测
4.1 界面上六个输入项,对应的是配置里的哪些字段
WebUI 顶部那些输入框不是摆设,每一个都直接映射到 yaml 字段,理解映射关系才能在出问题时改配置而不是乱点。
| 界面项 | 对应配置字段 | 填写要点 |
|---|---|---|
| 模型名称 | model_name_or_path的别名 | 填Qwen2.5-7B-Instruct,只影响展示和模板匹配 |
| 模型路径 | model_name_or_path | 填本地绝对路径,即 3.1 下载的位置 |
| 微调方法 | finetuning_type | 默认lora,显存紧就改qlora |
| 检查点路径 | adapter_name_or_path | 首次训练留空,续训时指向上一轮输出 |
| 数据集 | dataset | 下拉里选已注册的数据集名,可多选混合 |
| 训练轮次 | num_train_epochs | 默认 20 偏大,小数据集 3~5 更常见 |
「模型名称」这一栏如果下拉里搜不到 Qwen2.5,手输Qwen2.5-7B-Instruct也可以,它会按名字匹配对话模板。模板选错是新手最常见的翻车点:Qwen 系列有自己的 chat template,用成 llama 或者 alpaca 的模板,训练 loss 能降,但推理时输出格式全乱。所以哪怕界面上自动匹配了,也去「高级设置」里确认一眼template字段是不是qwen。
微调方法选 LoRA 之后,还会看到lora_rank、lora_alpha、lora_dropout、lora_target这几个进阶参数。经验值是 rank 8 到 16 起步,alpha 取 rank 的两倍,dropout 0.05,target 选all让注意力层和 MLP 层都挂上适配器。数据量小的时候 rank 别贪大,32 以上在小数据集上很容易过拟合,训出来的模型说话变得很机械。
4.2 自定义数据集注册:改错文件是最高频的错误
LLaMA-Factory 的数据集不是丢进 data 目录就能用,必须在注册文件里登记一条。这里有个容易踩的坑:手册里写的路径是data/c4_demo.json,但在框架当前版本里,实际生效的注册文件叫dataset_info.json,改错文件的表现就是界面上死活刷不出你的数据集名,也不报错。
{ "my_qa_data": { "file_name": "my_qa_data.json", "formatting": "sharegpt", "columns": { "messages": "messages" }, "tags": { "role_tag": "role", "content_tag": "content", "user_tag": "user", "assistant_tag": "assistant" } } }这段 JSON 的每个字段都有讲究。键名my_qa_data就是界面上显示的数据集名,随便起但必须唯一。file_name是相对data目录的文件名,不是绝对路径。formatting决定解析方式,sharegpt对应多轮对话结构,alpaca对应 instruction/input/output 三字段结构,选错了会在数据预处理阶段直接报 key error。columns里的映射是告诉框架去哪个字段取对话内容。数据文件本身长这样:
[ { "messages": [ {"role": "user", "content": "帮我把这句话改得更正式:这事儿明天再说"}, {"role": "assistant", "content": "该事项拟于明日另行商议。"} ] } ]放好文件、改好注册表之后,回 WebUI 刷新页面,下拉里能搜到my_qa_data才算成功。多选题还有一个「预览」按钮,点开能看到 tokenize 之后的实际输入,这一步非常值得做,很多模板问题在预览里一眼就能看出来。
4.3 4090 上的稳妥参数组合:先跑通,再谈效率
小数据集的 LoRA 微调,参数不用调得很花,稳比快重要。下面这套是我在 24GB 4090 上反复用过的起点:
model_name_or_path: /root/autodl-tmp/models/Qwen2.5-7B-Instruct stage: sft do_train: true finetuning_type: lora lora_rank: 16 lora_alpha: 32 lora_dropout: 0.05 lora_target: all template: qwen dataset: my_qa_data cutoff_len: 2048 max_samples: 10000 overwrite_cache: true preprocessing_num_workers: 8 per_device_train_batch_size: 1 gradient_accumulation_steps: 8 learning_rate: 1.0e-4 num_train_epochs: 3 lr_scheduler_type: cosine warmup_ratio: 0.1 bf16: true gradient_checkpointing: true logging_steps: 10 save_steps: 200 save_total_limit: 3 val_size: 0.1 eval_steps: 100 output_dir: /root/autodl-tmp/outputs/qwen25-lora-v1逐项说一下取值逻辑。cutoff_len: 2048是显存和样本完整性的平衡点,超过这个长度的样本会被截断,如果你的数据里有长文档摘要类任务,得往上提到 4096 并相应把 batch 压到 1。per_device_train_batch_size: 1配gradient_accumulation_steps: 8,等效 batch 是 8,显存占用按 1 算,这是 24GB 卡的常规做法。learning_rate: 1.0e-4是 LoRA 的量级,全量微调要降到 1e-5 到 2e-5,差一个数量级,混用会直接训崩。bf16: true在 4090 上支持良好,比 fp16 更不容易出梯度溢出。gradient_checkpointing: true用时间换显存,慢 20% 左右但能避免 OOM。save_total_limit: 3是保命的,不设的话每个 checkpoint 都留着,几轮下来数据盘就满了。
val_size: 0.1会从数据里切 10% 做验证集,配合eval_steps就能在 wandb 里看到两条曲线。数据量少于 200 条时这个比例要谨慎,切出来的验证集太小,eval loss 抖动会非常大,参考价值有限。
4.4 显存与吞吐观测:三种看法的区别
训练跑起来之后,别只盯着进度条。nvidia-smi有三种用法,用途不一样:
# 单次快照,看当前时刻的显存占用和进程 nvidia-smi # 每秒刷新,适合观察启动瞬间的显存爬升 nvidia-smi -l 1 # 每 5 秒刷新,适合长时间挂着的训练,输出更干净 watch -n 5 nvidia-smi启动阶段用-l 1,能清楚看到显存从 0 爬到 20GB 左右的过程,如果爬过 23GB 还没停,基本就要 OOM 了,赶紧Ctrl+C改参数。训练稳定后用watch -n 5,重点看 GPU-Util 是不是长期在 85% 以上,如果显存占满但利用率只有 30%,说明卡在数据加载上,把preprocessing_num_workers调高或者提前做一次overwrite_cache预处理。功耗和温度也顺带看一眼,4090 长时间满载温度上到 80 度以上会降频,吞吐会掉。
想看更精确的显存数值,可以用查询模式:
# 只要显存数字,方便脚本里判断 nvidia-smi --query-gpu=memory.used,memory.total,utilization.gpu \ --format=csv -l 5--query-gpu后面跟字段名,--format=csv输出成表格,-l 5每 5 秒一次。这个组合比watch nvidia-smi轻,适合挂在另一个终端里长期观察。
5. 避坑与排查:微调链路上最容易翻车的 5 个点
5.1 磁盘与下载:数据盘被吃满的两种典型场景
现象:训练跑到一半报No space left on device,或者 clone 模型到最后几个分片时卡死。
原因:第一种是.git目录没清,LFS 对象和实际权重各占一份,15GB 的模型吃掉 30GB。第二种是save_steps设得太小又没设save_total_limit,LoRA checkpoint 虽然单个只有几十到几百 MB,但几十个累积起来同样可观,再加上导出时的合并模型,数据盘见底很快。
解决:clone 完立刻rm -rf .git;训练配置里强制写save_total_limit: 3;导出目录和训练输出目录分开放,导出完把中间 checkpoint 删掉。养成习惯,每次开训前df -h看一眼 Avail,留出至少 20GB 余量。
5.2 数据集在 WebUI 下拉里刷不出来
现象:JSON 文件明明放进data目录了,刷新页面还是找不到,也不报错。
原因:三种可能——改错了注册文件(改成了c4_demo.json而不是dataset_info.json);file_name写成了绝对路径或者带了data/前缀;JSON 本身语法错误,比如末尾多了逗号,框架解析失败后静默跳过。还有一种隐蔽情况是文件名大小写和注册表里不一致,Linux 下是区分大小写的。
解决:先python -m json.tool data/dataset_info.json验证语法;确认file_name是纯文件名;确认数据集键名没有和内置数据集重名。改完必须重启llamafactory-cli webui,热刷新不一定生效。
5.3 训练刚启动就 CUDA out of memory
现象:进度条还没动,直接抛 OOM,日志里显示尝试分配几个 GB 失败。
原因:cutoff_len给到 4096 以上、per_device_train_batch_size大于 1、gradient_checkpointing没开,三者叠加在 24GB 卡上必炸。另一个常被忽略的是preprocessing_num_workers开太大,多进程预处理时每个进程都会占一部分显存。
解决:按batch_size=1→gradient_checkpointing=true→cutoff_len降到 2048 → 换qlora的顺序逐级降配,每改一项重跑一次,找到临界点。preprocessing_num_workers控制在 8 以内。改完还是炸,就在启动前nvidia-smi确认没有别的进程占着卡。
5.4 loss 卡在 2.x 不降,或者推理时输出复读
现象:训练 3 轮,loss 从 2.4 降到 2.3 就不动了;拿去推理,模型反复输出同一句话或者把用户问题原样复述。
原因:最常见的不是学习率问题,而是对话模板不匹配。数据是 sharegpt 格式但template选成了 alpaca,模型学到的输入结构和推理时框架拼出来的结构对不上。其次是学习率量级错了,LoRA 用了 1e-5 这种全量微调的取值,几乎学不动。数据量少于 100 条时也会出现这种情况,模型没足够样本可学。
解决:先确认template: qwen;LoRA 学习率回到 1e-4 量级;数据量太少就补数据或者把num_train_epochs提到 5 到 8 并观察 eval loss 是否开始回升(过拟合信号)。还有一个低成本检查手段:用训练集里的一条样本直接喂给训练后的模型,如果它能复现标准答案,说明是推理侧模板问题而不是训练失败。
5.5 wandb 面板空白,训练日志卡在「wandb: waiting」
现象:勾选了外部记录面板,训练正常跑,但 wandb 网页上什么都没有,终端反复打印等待连接的日志。
原因:没执行wandb login导致没有凭据;或者网络到 wandb 服务端不通,进程一直在重试,甚至拖慢训练启动。
解决:先在终端wandb login粘贴 API key;网络不稳时改用离线模式export WANDB_MODE=offline,训练照常记录到本地wandb目录,跑完再wandb sync上传。yaml 里report_to写成wandb而环境里没装 wandb 包也会静默失败,pip install wandb补上。离线模式下本地目录同样占空间,长时间实验记得清理。
6. wandb 记录与 LoRA 导出:把 N 次实验变成一条可比曲线
6.1 接入 wandb 的两种姿势
图形界面里勾选「启用外部记录面板」最省事,但它依赖 yaml 里已经有对应字段。我习惯直接在配置里写死,可复现性更好:
report_to: wandb project: qwen25-lora-sft run_name: lora-r16-lr1e4-cutoff2048 logging_steps: 10project是 wandb 里的项目名,同一个任务的所有实验放一起;run_name建议把关键超参编进去,lora-r16-lr1e4-cutoff2048这种命名在面板上排开时,一眼就知道哪条曲线对应哪组参数。配合wandb login或WANDB_MODE=offline,训练启动后终端会打印一个 run 链接。
6.2 曲线怎么读:三种典型形态
| train loss | eval loss | 判断 | 处理 |
|---|---|---|---|
| 持续下降 | 同步下降 | 正常收敛 | 继续,观察拐点 |
| 持续下降 | 先降后升 | 过拟合 | 减小 epoch、加数据、降 rank |
| 几乎不动 | 几乎不动 | 学习率或模板有问题 | 查 template、调学习率量级 |
| 剧烈震荡 | 剧烈震荡 | batch 太小或学习率过大 | 加梯度累积、降 lr |
eval loss 的绝对值不重要,趋势才重要。小数据集上 eval loss 抖 0.1 很正常,看的是三五次评估的整体走向。我一般会在 wandb 里把不同 run 的 eval loss 叠在一张图上,横轴 step 对齐,这样比较才有意义。
6.3 导出与推理验证
训练满意之后,把 LoRA 适配器合并回主干,导出成可以直接加载的完整模型:
# 合并 LoRA 并导出,adapter 路径指向训练输出目录 llamafactory-cli export \ --model_name_or_path /root/autodl-tmp/models/Qwen2.5-7B-Instruct \ --adapter_name_or_path /root/autodl-tmp/outputs/qwen25-lora-v1 \ --template qwen \ --finetuning_type lora \ --export_dir /root/autodl-tmp/outputs/qwen25-merged \ --export_size 4--export_size 4表示按 4GB 分片,方便后续上传或搬到显存更小的机器。导出完成后用一段短脚本验证:
from transformers import AutoModelForCausalLM, AutoTokenizer import torch path = "/root/autodl-tmp/outputs/qwen25-merged" tok = AutoTokenizer.from_pretrained(path, trust_remote_code=True) model = AutoModelForCausalLM.from_pretrained( path, torch_dtype=torch.bfloat16, device_map="auto" ) msgs = [{"role": "user", "content": "帮我把这句话改得更正式:这事儿明天再说"}] text = tok.apply_chat_template(msgs, tokenize=False, add_generation_prompt=True) out = model.generate(**tok(text, return_tensors="pt").to(model.device), max_new_tokens=128, temperature=0.1) print(tok.decode(out[0], skip_special_tokens=True))关键是apply_chat_template这一句,它保证推理时的输入拼接和训练时完全一致,手写 prompt 很容易在 system 段或结束符上差一个 token,输出就会明显变差。
6.4 一个我每次都会走的固定动作
导出之后我一定做三件事:拿训练集里挑 5 条、验证集挑 5 条、再手写 3 条分布外的问题,一起喂给合并后的模型,人工过一遍输出。这一步不省,因为 loss 好看但输出跑偏的情况我遇到过不止一次——最常见的原因是训练时数据里混进了格式不统一的样本,loss 被平均掉了。从那以后我每次训练前都强制走一遍数据格式校验脚本,把字段缺失、内容为空、角色顺序不对的样本先剔干净再开跑,比训完再回来返工省太多时间。希望帮到你。
本文还有配套的精品资源,点击获取