👋 教程说明
适合谁看?
- ✅ 有基础Python编程经验
- ✅ 会使用Linux命令行(终端)
- ✅ 有一台带多张GPU的服务器
- ❌ 不需要深度学习专家级别的知识
你将学到什么?
本教程将手把手教你如何用 单机多卡(4×GPU) 并行训练的方式,把通用的 Qwen3 大模型微调成专业的"算命助手"。
时间投入:
- 环境配置: 30分钟 - 1小时
- 数据准备: 30分钟
- 训练时间: 3-8小时(自动运行)
- 总时长: 约半天到一天
🚀 本教程专注于:单机多卡 (4×GPU) 并行训练
硬件配置: 4×15GB 显存(总共60GB)
核心特点:
- ✅ 多卡并行: 使用 DDP/DeepSpeed 实现 4 卡并行训练
- ✅ 训练加速: 相比单卡提速 3-4 倍
- ✅ QLoRA + 4bit 量化: 每卡显存占用 ~12GB
- ✅ batch_size = 1/卡: 4卡总batch = 4,有效batch = 32
- ✅ gradient_checkpointing: 节省显存必备
- ✅ max_seq_length = 1024: 适合15GB显存
- ✅ 8bit 优化器: 进一步节省显存
单机多卡 vs 单机单卡:
- 单机单卡启动:
python train.py(仅用1个GPU)- 单机多卡启动:
torchrun --nproc_per_node=4 train.py(用4个GPU)本教程配置为单机多卡! 如需单卡训练,请使用
python直接启动。
📚 术语速查表(遇到不懂的词就来这里查)
基础概念
微调 (Fine-tuning)
在预训练模型的基础上,用特定领域的数据继续训练,让模型学会特定任务。就像给一个已经会说话的AI老师,教它专门的算命知识。
单机多卡 (Multi-GPU on Single Node)
一台服务器里装了多张显卡(GPU),让它们同时工作来加速训练。本教程使用4张GPU并行训练,速度提升3-4倍。
显存 (VRAM)
GPU自带的内存,用来存放模型和数据。本教程配置为每张卡15GB,总共60GB。
DDP (DistributedDataParallel)
PyTorch自带的多卡并行训练方法,自动将数据分配到多张卡上并行处理,然后同步梯度。
DeepSpeed
微软开源的深度学习训练加速工具,提供ZeRO优化技术,比DDP更省显存。
训练相关
QLoRA (Quantized LoRA)
一种参数高效的微调方法,结合了量化和低秩适配。只训练少量参数(几百万)而非全部参数(70亿),显存占用减少80%+。
- 比喻: 不装修整栋楼(全量微调),只改造几个房间(LoRA),还用便宜材料(Q=4bit量化)
batch_size
每次训练时同时处理多少条数据。本教程设置为每卡1条,4卡并行 = 总batch 4。
gradient_accumulation
梯度累积。累积多个小batch的梯度再更新参数,效果等于大batch但不占额外显存。本教程累积8步,有效batch = 4×8 = 32。
gradient_checkpointing
梯度检查点。牺牲20%训练时间来节省30-40%显存。15GB显存必须开启。
epoch
把所有训练数据过一遍叫1个epoch。本教程训练3个epochs = 把数据学3遍。
learning_rate
学习率。控制模型参数更新的步长。太大容易不稳定,太小训练太慢。本教程使用2e-4。
⚡ 快速开始(适合有经验的用户)
新手请跳过这部分,从第1章开始按步骤操作。
如果你已经配置好环境和数据,可以直接使用以下命令启动单机4卡训练:
# 方式1: 使用 torchrun(推荐,最简单)
torchrun --nproc_per_node=4 train_qwen3_qlora.py
# 方式2: 使用 accelerate(更灵活)
accelerate launch --multi_gpu --num_processes=4 train_qwen3_qlora.py
# 方式3: 使用 DeepSpeed(最省显存,高级)
deepspeed --num_gpus=4 train_qwen3_qlora.py --deepspeed ds_config.json
🚨 重要: 必须使用上述多卡启动命令(而不是 python train.py),否则只会使用单卡!
目录
1. 显存计算与模型选择
1.1 你的硬件配置
- GPU数量: 4块 GPU
- 单卡显存: 15GB(60GB ÷ 4)
- 总显存: 60GB
- 适用场景: 单卡15GB显存,需要特别优化才能微调7B模型
1.2 显存消耗计算公式
微调时的显存消耗主要包括:
总显存 = 模型参数 + 梯度 + 优化器状态 + 激活值 + 其他开销
详细计算:
以 Qwen3-7B 为例(推荐)
| 项目 | 计算方式 | FP16/BF16 | INT8 | INT4 |
|---|---|---|---|---|
| 模型参数 | 7B × 2 bytes | 14 GB | 7 GB | 3.5 GB |
| 梯度 | 7B × 2 bytes | 14 GB | 14 GB | 14 GB |
| 优化器状态 (AdamW) | 7B × 8 bytes | 28 GB | 28 GB | 28 GB |
| 激活值 (batch=4) | 估算 | ~8 GB | ~6 GB | ~4 GB |
| 其他开销 | 估算 | ~4 GB | ~3 GB | ~2 GB |
| 单卡总计 | 68 GB | 58 GB | 51.5 GB |
1.3 模型选择建议
| 模型 | 参数量 | QLoRA显存需求 | 是否适合你 |
|---|---|---|---|
| Qwen3-1.8B | 1.8B | ~8GB/卡 | ✅ 最安全选择 |
| Qwen3-7B | 7B | ~12GB/卡(4卡并行) | ✅ 推荐(单机多卡配置) |
| Qwen3-14B | 14B | ~20GB/卡 | ❌ 显存不足 |
单机多卡配置结论:
- 首选: Qwen3-7B + QLoRA + 4卡并行(batch_size=1/卡,总batch=4)
- 保险: Qwen3-1.8B + QLoRA + 4卡并行(显存充裕,可增加batch_size=2/卡)
- 关键: 使用 DDP (DistributedDataParallel) 或 DeepSpeed 实现多卡并行训练
1.4 QLoRA 原理(小白版)
想象一下:
- 全量微调:把整栋房子(70亿参数)全部重新装修 → 太贵
- LoRA:只装修几个房间(几百万参数)→ 省钱
- QLoRA:用更便宜的材料(INT4)装修几个房间 → 超省钱
QLoRA 能节省多少显存?
- 全量微调:需要 ~68GB/卡(你的15GB显存装不下)
- QLoRA:需要 ~12GB/卡(节省 80%+,刚好适合你的15GB)
单机多卡(4×15GB)优化策略:
- 使用 QLoRA + 4bit 量化
- 每卡 batch_size 设置为 1(4卡并行 = 总batch 4)
- 开启 gradient_checkpointing(用时间换空间)
- 最大序列长度限制在 1024
- 使用 DeepSpeed ZeRO-2 将优化器状态分散到多卡
- 使用 DDP 或 torchrun 启动多卡训练
2. 环境配置
2.1 系统要求
硬件要求:
- GPU:4张GPU,每张显存15GB以上(如RTX 4090、A100等)
- CPU:16核以上
- 内存:64GB以上
- 硬盘空间:至少 100GB(模型约14GB + 数据 + 输出)
软件要求:
- 操作系统:Ubuntu 20.04/22.04 或 Windows 11 + WSL2
- Python:3.10+
- CUDA:12.1+
- 驱动:NVIDIA Driver 525+
检查你的硬件:
# 1. 检查GPU(应该看到4张卡)
nvidia-smi
# 2. 检查CUDA版本
nvcc --version
# 3. 检查Python版本
python --version
2.2 创建虚拟环境
为什么需要虚拟环境?
- 隔离依赖,避免版本冲突
- 方便管理和删除
- 推荐使用conda
# 如果没有conda,先安装Miniconda
# wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh
# bash Miniconda3-latest-Linux-x86_64.sh
# 创建 conda 环境
conda create -n qwen3_finetune python=3.10 -y
# 激活环境(每次训练前都要运行)
conda activate qwen3_finetune
# 确认Python版本
python --version # 应显示 Python 3.10.x
2.3 安装核心依赖
安装顺序很重要! 按以下顺序安装,避免版本冲突。
第1步: 安装PyTorch(深度学习框架)
# 根据你的CUDA版本选择
# CUDA 12.1版本(推荐):
pip install torch==2.2.0 torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121
# 验证安装
python -c "import torch; print('PyTorch:', torch.__version__); print('CUDA可用:', torch.cuda.is_available())"
# 期望输出: PyTorch: 2.2.0, CUDA可用: True
如果CUDA不可用,检查:
- NVIDIA驱动是否正确安装
- CUDA toolkit是否安装
- PyTorch版本是否匹配CUDA版本
第2步: 安装训练相关库
# 一次性安装所有依赖(推荐)
pip install transformers==4.38.0 \
accelerate==0.27.0 \
peft==0.9.0 \
bitsandbytes==0.42.0 \
datasets==2.17.0 \
trl==0.7.10 \
sentencepiece \
tensorboard
# 可选: DeepSpeed(高级优化,新手可跳过)
pip install deepspeed==0.13.1
各个库的作用:
transformers: Hugging Face的模型库,加载Qwen3模型accelerate: 简化多GPU训练配置peft: LoRA/QLoRA实现bitsandbytes: 4bit/8bit量化,节省显存datasets: 数据加载和处理trl: Transformer强化学习训练工具sentencepiece: 分词器tensorboard: 训练过程可视化
2.4 验证安装
创建测试脚本 test_env.py:
"""
环境验证脚本
检查所有依赖是否正确安装,GPU是否可用
"""
import torch
import transformers
from peft import LoraConfig
import bitsandbytes as bnb
print("=" * 60)
print("环境验证")
print("=" * 60)
print(f"✅ PyTorch版本: {torch.__version__}")
print(f"✅ Transformers版本: {transformers.__version__}")
print(f"✅ CUDA可用: {torch.cuda.is_available()}")
print(f"✅ GPU数量: {torch.cuda.device_count()}")
print()
# 检查GPU信息
gpu_count = torch.cuda.device_count()
if gpu_count == 4:
print("🎉 完美! 检测到4张GPU")
for i in range(gpu_count):
gpu_name = torch.cuda.get_device_name(i)
gpu_mem = torch.cuda.get_device_properties(i).total_memory / 1024**3
print(f" GPU {i}: {gpu_name} ({gpu_mem:.1f}GB)")
elif gpu_count > 0:
print(f"⚠️ 警告: 检测到{gpu_count}张GPU,本教程需要4张")
print(" 可以继续,但需要修改启动命令中的--nproc_per_node参数")
for i in range(gpu_count):
gpu_name = torch.cuda.get_device_name(i)
gpu_mem = torch.cuda.get_device_properties(i).total_memory / 1024**3
print(f" GPU {i}: {gpu_name} ({gpu_mem:.1f}GB)")
else:
print("❌ 错误: 没有检测到GPU!")
print(" 请检查NVIDIA驱动和CUDA安装")
print()
print("=" * 60)
运行测试:
python test_env.py
期望输出示例:
============================================================
环境验证
============================================================
✅ PyTorch版本: 2.2.0
✅ Transformers版本: 4.38.0
✅ CUDA可用: True
✅ GPU数量: 4
🎉 完美! 检测到4张GPU
GPU 0: NVIDIA GeForce RTX 4090 (24.0GB)
GPU 1: NVIDIA GeForce RTX 4090 (24.0GB)
GPU 2: NVIDIA GeForce RTX 4090 (24.0GB)
GPU 3: NVIDIA GeForce RTX 4090 (24.0GB)
============================================================
✅ 如果看到类似输出,说明环境配置成功! 可以继续下一步。
❌ 如果出现错误:
- GPU数量不是4: 检查
nvidia-smi是否显示4张卡 - CUDA不可用: 重新安装PyTorch,确保版本匹配
- Import错误: 重新安装对应的库
3. 数据集准备
3.1 数据格式说明
为什么需要准备数据? 微调就像教AI说话,你需要给它"例句"学习。数据质量直接决定了微调效果。
使用什么格式? 推荐使用 Alpaca 格式(JSON格式,简单易懂):
[
{
"instruction": "请帮我算一下今年的运势",
"input": "我是1990年出生的,属马",
"output": "根据您的生肖属马,1990年出生属庚午年。今年2026年丙午年,您正值本命年..."
},
{
"instruction": "我想知道我的事业运",
"input": "我在做销售工作",
"output": "从您的描述来看,销售工作需要良好的人际关系..."
}
]
字段说明:
instruction: 用户的问题主题(必需)input: 用户的具体输入信息(可以为空)output: 模型应该给出的回答(必需)
数据量建议:
- 最少: 100条(能跑通,效果一般)
- 推荐: 500-1000条(效果较好)
- 理想: 5000+条(效果很好)
- 本教程提供6条示例,仅用于演示流程
3.2 创建示例数据集
创建文件 data/fortune_telling_train.json:
[
{
"instruction": "请帮我算一下今年的运势",
"input": "我是1990年出生的,属马",
"output": "根据您的生肖属马,1990年出生属庚午年。今年2026年丙午年,您正值本命年,需要特别注意。整体运势来看:\n\n**事业运**: 本命年事业会有一些波动,建议保守稳健,不宜大幅变动\n**财运**: 正财尚可,偏财需谨慎,避免高风险投资\n**感情**: 已婚者注意沟通,单身者桃花运一般\n**健康**: 注意肠胃和心血管健康,定期体检\n\n化解建议: 可佩戴红绳,多行善事,保持积极心态。"
},
{
"instruction": "我想知道我的事业运",
"input": "我在做销售工作,最近业绩不太好",
"output": "从事销售工作需要良好的人际关系和沟通能力。根据您目前的情况分析:\n\n**近期状况**: 可能处于低谷期,这是正常的周期性波动\n**改善建议**:\n1. 调整心态,保持积极乐观\n2. 加强客户关系维护,重视老客户\n3. 学习新的销售技巧和产品知识\n4. 注意与同事合作,团队力量很重要\n\n**吉日选择**: 建议在每月初三、初八、十三、十八、廿三、廿八拜访重要客户\n**幸运色**: 红色、橙色能提升您的气场"
},
{
"instruction": "帮我看看感情运势",
"input": "我是单身,想知道什么时候能遇到对的人",
"output": "关于您的感情运势,我为您分析如下:\n\n**桃花运**: 今年上半年桃花运较旺,特别是春季(3-5月)和秋季(9-11月)\n**理想对象**: 可能会在工作场合或朋友聚会中遇到,注意身边的机会\n**建议行动**:\n1. 多参加社交活动,扩大社交圈\n2. 保持良好的外在形象和内在修养\n3. 不要过于挑剔,给彼此了解的机会\n4. 可以请朋友介绍,熟人介绍成功率更高\n\n**注意事项**: 避免网恋和闪婚,需要时间真正了解对方\n**幸运方位**: 东南方向,可以多往这个方向活动"
},
{
"instruction": "我想问问财运如何",
"input": "最近想投资股票",
"output": "关于财运和投资,我给您以下建议:\n\n**整体财运**: 今年正财运稳定,偏财运需谨慎\n**投资建议**: \n- ⚠️ **风险提示**: 今年不是大规模投资的好时机\n- 如果一定要投资,建议小额分散投资\n- 避免借钱投资或加杠杆操作\n- 重点关注稳健型投资,如蓝筹股、基金定投\n\n**财运提升方法**:\n1. 专注本职工作,提升专业能力\n2. 开源节流,建立储蓄习惯\n3. 可在家中财位摆放绿植或鱼缸\n4. 避免冲动消费,理性规划财务\n\n**吉日**: 农历每月初一、十五适合重要财务决策"
},
{
"instruction": "帮我看看健康运",
"input": "最近总感觉累,睡眠也不好",
"output": "您的健康状况需要关注,从您的描述来看:\n\n**主要问题**: 疲劳和睡眠质量差可能与压力和作息有关\n**健康建议**:\n1. **作息调整**: 每晚11点前入睡,保证7-8小时睡眠\n2. **运动**: 每周至少3次有氧运动,如慢跑、游泳\n3. **饮食**: 多吃新鲜蔬果,减少油腻和辛辣食物\n4. **心理**: 学会释放压力,可以尝试冥想或瑜伽\n\n**重点关注**: \n- 肝脏和脾胃健康\n- 定期体检,特别是血压血糖\n- 避免熬夜和过度劳累\n\n**养生建议**:\n- 早上喝温水,晚上泡脚\n- 可以喝一些菊花茶、枸杞茶养肝明目\n- 保持良好心态,心情愉悦最重要"
},
{
"instruction": "我想知道考试运",
"input": "下个月有个重要考试",
"output": "关于您的考试运势,我为您分析:\n\n**考试运势**: 整体运势尚可,但需要努力准备\n**准备建议**:\n1. **时间规划**: 制定详细复习计划,每天坚持\n2. **重点突破**: 抓住重点和难点,不要面面俱到\n3. **模拟练习**: 多做真题和模拟题,熟悉题型\n4. **劳逸结合**: 注意休息,保持良好状态\n\n**考前准备**:\n- 考前一周减少熬夜,保证睡眠\n- 考试当天穿红色或黄色衣物\n- 提前踩点,避免迟到慌张\n- 保持平常心,相信自己的努力\n\n**吉时**: 上午9-11点(巳时)精神状态最佳\n**助运物**: 可随身携带红绳或文昌符\n\n记住: 运势只是辅助,真正的成功来自于踏实的努力!"
}
]
3.3 数据集划分
为什么要划分数据?
- 训练集(90%): 用来训练模型
- 验证集(10%): 用来评估训练效果,防止过拟合
创建数据处理脚本 prepare_data.py:
import json
import random
def split_dataset(input_file, train_ratio=0.9):
"""将数据集划分为训练集和验证集"""
# 读取数据
with open(input_file, 'r', encoding='utf-8') as f:
data = json.load(f)
# 打乱数据
random.seed(42)
random.shuffle(data)
# 划分数据
split_idx = int(len(data) * train_ratio)
train_data = data[:split_idx]
val_data = data[split_idx:]
# 保存
with open('data/train.json', 'w', encoding='utf-8') as f:
json.dump(train_data, f, ensure_ascii=False, indent=2)
with open('data/val.json', 'w', encoding='utf-8') as f:
json.dump(val_data, f, ensure_ascii=False, indent=2)
print(f"数据划分完成:")
print(f" 训练集: {len(train_data)} 条")
print(f" 验证集: {len(val_data)} 条")
if __name__ == "__main__":
split_dataset('data/fortune_telling_train.json')
运行脚本:
mkdir -p data
# 先创建上面的 fortune_telling_train.json
python prepare_data.py
3.4 数据质量要求
为了获得好的微调效果,建议:
- 数量: 至少 500-1000 条高质量对话
- 多样性: 覆盖不同算命场景(事业、感情、财运、健康等)
- 长度: 每条回复 100-500 字为佳
- 风格: 保持统一的回复风格和专业度
4. 单机多卡微调代码
4.1 代码结构说明
训练代码主要包含以下几个部分:
- 参数配置 (
ScriptArguments): 设置模型、数据、训练参数 - 模型加载 (
load_model_and_tokenizer): 加载Qwen3模型并应用QLoRA - 数据处理 (
preprocess_dataset): 将JSON数据转换为模型输入格式 - 训练器设置 (
TrainingArguments+Trainer): 配置训练参数并启动训练
关键配置说明(针对单机多卡):
# 单机多卡的关键配置
per_device_train_batch_size = 1 # 每张卡batch=1(15GB显存限制)
# 4卡并行 = 总batch 4
gradient_accumulation_steps = 8 # 累积8步更新一次
# 有效batch = 1×4卡×8步 = 32
ddp_find_unused_parameters = False # DDP多卡训练必须设置
device_map = "auto" # 自动分配模型到多卡
# DDP模式下会自动处理
4.2 完整训练脚本
创建 train_qwen3_qlora.py:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Qwen3 单机多卡 QLoRA 微调脚本
适用于 4×RTX 4090 (24GB × 4)
"""
import os
import json
import torch
from dataclasses import dataclass, field
from typing import Optional
from transformers import (
AutoModelForCausalLM,
AutoTokenizer,
BitsAndBytesConfig,
TrainingArguments,
Trainer,
DataCollatorForSeq2Seq,
)
from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training
from datasets import load_dataset
import logging
# 设置日志
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
@dataclass
class ScriptArguments:
"""训练参数配置"""
# 模型参数
model_name: str = field(
default="Qwen/Qwen2.5-7B-Instruct", # 或 Qwen/Qwen3-7B
metadata={"help": "预训练模型名称或路径"}
)
# 数据参数
train_file: str = field(
default="data/train.json",
metadata={"help": "训练数据文件"}
)
val_file: str = field(
default="data/val.json",
metadata={"help": "验证数据文件"}
)
# LoRA 参数
lora_r: int = field(default=64, metadata={"help": "LoRA 秩"})
lora_alpha: int = field(default=16, metadata={"help": "LoRA alpha"})
lora_dropout: float = field(default=0.05, metadata={"help": "LoRA dropout"})
# 量化参数
use_4bit: bool = field(default=True, metadata={"help": "使用 4bit 量化"})
# 训练参数(单机多卡配置)
output_dir: str = field(default="./output", metadata={"help": "输出目录"})
num_train_epochs: int = field(default=3, metadata={"help": "训练轮数"})
per_device_train_batch_size: int = field(default=1, metadata={"help": "每卡训练 batch size(单机多卡:1/卡 × 4卡 = 4)"})
gradient_accumulation_steps: int = field(default=8, metadata={"help": "梯度累积步数(有效batch=4×8=32)"})
learning_rate: float = field(default=2e-4, metadata={"help": "学习率"})
max_seq_length: int = field(default=1024, metadata={"help": "最大序列长度(15GB显存建议1024)"})
gradient_checkpointing: bool = field(default=True, metadata={"help": "梯度检查点(节省显存)"})
# 其他
seed: int = field(default=42, metadata={"help": "随机种子"})
def create_bnb_config(use_4bit: bool = True):
"""创建 BitsAndBytes 量化配置"""
if use_4bit:
return BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_use_double_quant=True, # 双重量化,进一步节省显存
bnb_4bit_quant_type="nf4", # 使用 NF4 量化
bnb_4bit_compute_dtype=torch.bfloat16, # 计算精度
)
else:
return BitsAndBytesConfig(
load_in_8bit=True,
)
def load_model_and_tokenizer(script_args: ScriptArguments):
"""加载模型和分词器"""
logger.info(f"加载模型: {script_args.model_name}")
# BitsAndBytes 配置
bnb_config = create_bnb_config(script_args.use_4bit)
# 加载模型(多卡配置)
model = AutoModelForCausalLM.from_pretrained(
script_args.model_name,
quantization_config=bnb_config,
device_map="auto", # 自动分配到多卡(DDP模式下会自动处理)
trust_remote_code=True,
torch_dtype=torch.bfloat16,
)
# 准备模型用于 k-bit 训练
model = prepare_model_for_kbit_training(model)
# 开启梯度检查点(节省显存,15GB显存必须开启)
if script_args.gradient_checkpointing:
model.gradient_checkpointing_enable()
# 加载分词器
tokenizer = AutoTokenizer.from_pretrained(
script_args.model_name,
trust_remote_code=True,
padding_side="right", # 重要:padding 放在右侧
)
# 设置 pad_token
if tokenizer.pad_token is None:
tokenizer.pad_token = tokenizer.eos_token
# LoRA 配置
lora_config = LoraConfig(
r=script_args.lora_r,
lora_alpha=script_args.lora_alpha,
target_modules=[
"q_proj", "k_proj", "v_proj", "o_proj", # attention
"gate_proj", "up_proj", "down_proj", # MLP
],
lora_dropout=script_args.lora_dropout,
bias="none",
task_type="CAUSAL_LM",
)
# 应用 LoRA
model = get_peft_model(model, lora_config)
model.print_trainable_parameters() # 打印可训练参数量
return model, tokenizer
def format_instruction(sample):
"""格式化指令数据"""
return f"""<|im_start|>system
你是一位专业的算命师傅,精通八字、生肖、风水等传统命理学。请根据用户的问题,提供专业、详细且富有洞察力的解答。<|im_end|>
<|im_start|>user
{sample['instruction']}
{sample['input']}<|im_end|>
<|im_start|>assistant
{sample['output']}<|im_end|>"""
def preprocess_dataset(dataset, tokenizer, max_seq_length):
"""预处理数据集"""
def tokenize_function(examples):
# 格式化文本
texts = [format_instruction(ex) for ex in examples]
# 分词
model_inputs = tokenizer(
texts,
max_length=max_seq_length,
truncation=True,
padding=False, # 不在这里 padding,使用 DataCollator
)
# labels 与 input_ids 相同(自回归语言模型)
model_inputs["labels"] = model_inputs["input_ids"].copy()
return model_inputs
# 将单个样本转换为批处理格式
def prepare_batch(examples):
batch = {key: [ex[key] for ex in examples] for key in examples[0].keys()}
return tokenize_function(batch)
# 处理数据集
processed_dataset = []
for example in dataset:
processed_dataset.append(example)
# 使用 map 批处理
tokenized = []
batch_size = 1000
for i in range(0, len(processed_dataset), batch_size):
batch = processed_dataset[i:i+batch_size]
result = prepare_batch(batch)
for j in range(len(batch)):
tokenized.append({
'input_ids': result['input_ids'][j],
'attention_mask': result['attention_mask'][j],
'labels': result['labels'][j],
})
return tokenized
def main():
"""主函数"""
# 参数配置
script_args = ScriptArguments()
# 设置随机种子
torch.manual_seed(script_args.seed)
# 加载模型和分词器
model, tokenizer = load_model_and_tokenizer(script_args)
# 加载数据集
logger.info("加载数据集...")
train_dataset = load_dataset('json', data_files=script_args.train_file, split='train')
val_dataset = load_dataset('json', data_files=script_args.val_file, split='train')
logger.info(f"训练集大小: {len(train_dataset)}")
logger.info(f"验证集大小: {len(val_dataset)}")
# 预处理数据
logger.info("预处理数据...")
train_dataset = preprocess_dataset(train_dataset, tokenizer, script_args.max_seq_length)
val_dataset = preprocess_dataset(val_dataset, tokenizer, script_args.max_seq_length)
# Data Collator
data_collator = DataCollatorForSeq2Seq(
tokenizer=tokenizer,
padding=True,
return_tensors="pt",
)
# 训练参数(单机多卡配置)
training_args = TrainingArguments(
output_dir=script_args.output_dir,
num_train_epochs=script_args.num_train_epochs,
per_device_train_batch_size=script_args.per_device_train_batch_size, # 1/卡
per_device_eval_batch_size=script_args.per_device_train_batch_size,
gradient_accumulation_steps=script_args.gradient_accumulation_steps, # 有效batch=1×4×8=32
learning_rate=script_args.learning_rate,
logging_steps=10,
save_steps=100,
eval_steps=100,
save_total_limit=3,
load_best_model_at_end=True,
evaluation_strategy="steps",
warmup_steps=100,
bf16=True, # 使用 bfloat16
gradient_checkpointing=script_args.gradient_checkpointing, # 梯度检查点
ddp_find_unused_parameters=False, # DDP 多卡训练必须设置
dataloader_num_workers=2, # 减少内存占用
remove_unused_columns=False,
report_to="tensorboard",
logging_dir=f"{script_args.output_dir}/logs",
optim="paged_adamw_8bit", # 使用8bit优化器进一步节省显存
# 多卡训练会自动启用 DDP
)
# 创建 Trainer
trainer = Trainer(
model=model,
args=training_args,
train_dataset=train_dataset,
eval_dataset=val_dataset,
data_collator=data_collator,
)
# 开始训练
logger.info("开始训练...")
trainer.train()
# 保存模型
logger.info("保存模型...")
trainer.save_model(f"{script_args.output_dir}/final_model")
tokenizer.save_pretrained(f"{script_args.output_dir}/final_model")
logger.info("训练完成!")
if __name__ == "__main__":
main()
4.2 配置文件
创建 config/training_config.json(可选):
{
"model_name": "Qwen/Qwen2.5-7B-Instruct",
"train_file": "data/train.json",
"val_file": "data/val.json",
"output_dir": "./output",
"num_train_epochs": 3,
"per_device_train_batch_size": 1,
"gradient_accumulation_steps": 8,
"learning_rate": 2e-4,
"max_seq_length": 1024,
"lora_r": 64,
"lora_alpha": 16,
"lora_dropout": 0.05,
"use_4bit": true,
"gradient_checkpointing": true,
"seed": 42
}
重要说明(单机多卡配置):
per_device_train_batch_size: 1- 每卡batch设为1,4卡并行 = 总batch 4gradient_accumulation_steps: 8- 累积8步,有效batch = 4×8 = 32max_seq_length: 1024- 序列长度限制在1024gradient_checkpointing: true- 必须开启,节省30-40%显存- 使用
torchrun或accelerate启动多卡训练
5. 训练启动与监控
5.1 单机多卡 vs 单机单卡
关键区别:
| 特性 | 单机单卡 | 单机多卡 (本教程) |
|---|---|---|
| 启动方式 | python train.py | torchrun --nproc_per_node=4 train.py |
| 并行方式 | 无并行 | DDP (DistributedDataParallel) |
| batch_size | 1/卡 = 1 | 1/卡 × 4卡 = 4 |
| 训练速度 | 1x | 3-4x (接近线性加速) |
| 有效batch | 1×8=8 | 4×8=32 |
| 适用场景 | 调试、小数据集 | 生产训练、大数据集 |
为什么要用单机多卡?
- 速度提升: 4卡并行训练速度提升3-4倍
- batch size增大: 有效batch从8增加到32,训练更稳定
- 资源利用: 充分利用所有GPU,提高硬件利用率
5.2 单机多卡启动命令
🚨 最重要的事: 必须使用以下多卡启动命令,否则只会用单卡!
方式一:使用 torchrun(推荐,最简单)
# 激活环境(每次训练前都要做)
conda activate qwen3_finetune
# 进入项目目录
cd /path/to/your/project
# 启动4卡训练
torchrun --nproc_per_node=4 train_qwen3_qlora.py
# 参数说明:
# --nproc_per_node=4 表示使用4个进程(对应4张GPU)
# 如果你有8张卡,改成 --nproc_per_node=8
指定特定GPU:
# 只使用GPU 0,1,2,3(默认就是这样)
CUDA_VISIBLE_DEVICES=0,1,2,3 torchrun --nproc_per_node=4 train_qwen3_qlora.py
# 如果想用GPU 4,5,6,7
CUDA_VISIBLE_DEVICES=4,5,6,7 torchrun --nproc_per_node=4 train_qwen3_qlora.py
启动成功的标志:
Setting OMP_NUM_THREADS environment variable for each process to be 1
[INFO] Setting ds_accelerator to cuda (auto detect)
加载模型: Qwen/Qwen2.5-7B-Instruct
...
trainable params: 41,943,040 || all params: 7,615,616,000 || trainable%: 0.55%
方式二:使用 accelerate(更灵活的多卡配置)
# 首先配置 accelerate(选择分布式训练选项)
accelerate config
# 配置示例:
# - 选择 "multi-GPU"
# - GPU数量: 4
# - 混合精度: bf16
# - 是否使用DeepSpeed: 否(或选择ZeRO-2)
# 然后启动多卡训练
accelerate launch --multi_gpu --num_processes=4 train_qwen3_qlora.py
方式三:使用 DeepSpeed(单机多卡高级优化)
DeepSpeed ZeRO可以进一步优化多卡训练的显存使用:
创建 ds_config.json:
{
"train_batch_size": "auto",
"train_micro_batch_size_per_gpu": "auto",
"gradient_accumulation_steps": "auto",
"gradient_clipping": 1.0,
"zero_optimization": {
"stage": 2, # ZeRO-2: 将优化器状态分散到4张卡
"offload_optimizer": {
"device": "cpu", # 进一步将优化器卸载到CPU(可选)
"pin_memory": true
},
"allgather_partitions": true,
"allgather_bucket_size": 5e8,
"reduce_scatter": true,
"reduce_bucket_size": 5e8,
"overlap_comm": true, # 重叠通信和计算,提高多卡效率
"contiguous_gradients": true
},
"fp16": {
"enabled": false
},
"bf16": {
"enabled": true
},
"zero_allow_untested_optimizer": true
}
启动命令(单机4卡):
# 使用DeepSpeed启动单机多卡训练
deepspeed --num_gpus=4 train_qwen3_qlora.py --deepspeed ds_config.json
# 或指定特定GPU
CUDA_VISIBLE_DEVICES=0,1,2,3 deepspeed --num_gpus=4 train_qwen3_qlora.py --deepspeed ds_config.json
DeepSpeed优势(单机多卡):
- ZeRO-2: 将优化器状态均分到4张卡,每卡节省75%优化器显存
- ZeRO-3: 进一步将模型参数也分散(适合更大模型)
- 通信优化: 自动优化多卡间的梯度同步
5.3 训练监控
TensorBoard 监控
# 在另一个终端运行
tensorboard --logdir=./output/logs --port=6006
# 然后在浏览器访问: http://localhost:6006
实时显存监控(多卡)
# 在另一个终端运行,监控所有GPU
watch -n 1 nvidia-smi
# 预期看到4张卡都在工作,显存使用均衡
# GPU 0: 12GB / 15GB
# GPU 1: 12GB / 15GB
# GPU 2: 12GB / 15GB
# GPU 3: 12GB / 15GB
训练脚本监控
创建 monitor.py:
import time
import subprocess
def monitor_training():
"""监控训练进度"""
while True:
# GPU 信息
result = subprocess.run(
['nvidia-smi', '--query-gpu=index,memory.used,memory.total,utilization.gpu', '--format=csv,noheader,nounits'],
capture_output=True,
text=True
)
print("\n" + "="*60)
print(f"时间: {time.strftime('%Y-%m-%d %H:%M:%S')}")
print("="*60)
for line in result.stdout.strip().split('\n'):
gpu_id, mem_used, mem_total, util = line.split(', ')
print(f"GPU {gpu_id}: {mem_used}MB / {mem_total}MB ({util}%)")
time.sleep(5)
if __name__ == "__main__":
try:
monitor_training()
except KeyboardInterrupt:
print("\n监控结束")
5.4 预期训练时间(单机多卡 vs 单卡)
以 Qwen3-7B + QLoRA 为例(针对你的4×15GB配置):
| 数据量 | 训练轮数 | 单卡时间 | 4卡并行时间 | 加速比 |
|---|---|---|---|---|
| 500条 | 3 epochs | ~12-16小时 | ~3-4小时 | 4x |
| 1000条 | 3 epochs | ~24-32小时 | ~6-8小时 | 4x |
| 5000条 | 3 epochs | ~120-160小时 | ~30-40小时 | 4x |
单机多卡优势明显!
进一步提速建议:
- 使用 Qwen3-1.8B(快3-4倍): 4卡训练1000条数据仅需1.5-2小时
- 减少 max_seq_length 到 512(快30-40%)
- 使用更少的 lora_r(如32代替64,快20%)
- 确保使用 Flash Attention 2(提升10-20%)
6. 常见问题解决
遇到问题不要慌!大部分问题都有标准解决方案。
6.1 显存不足(OOM)
错误症状:
RuntimeError: CUDA out of memory. Tried to allocate XX GB (GPU 0; 15.00 GB total capacity...)
训练开始没多久就崩溃,或者启动时就报错。
15GB显存专用解决方案(按优先级排序):
# ✅ 方案 1: 确认已开启所有优化(最重要)
gradient_checkpointing: True # 必须开启
use_4bit: True # 必须开启
per_device_train_batch_size: 1 # 已经是最小
# ✅ 方案 2: 减小序列长度(最有效)
max_seq_length: 1024 → 512 # 可节省50%显存
# ✅ 方案 3: 减小 LoRA 秩
lora_r: 64 → 32 # 可节省20-30%显存
lora_r: 32 → 16 # 极端情况
# ✅ 方案 4: 使用 8bit 优化器(代码中已配置)
optim: "paged_adamw_8bit" # 确保启用
# ✅ 方案 5: 使用 DeepSpeed ZeRO-3
# 修改 ds_config.json 中 stage: 2 → 3
# 可将模型参数也分散到多卡
# ⚠️ 方案 6: 降级到更小的模型
# 如果以上都不行,使用 Qwen3-1.8B(只需6-8GB)
检查命令:
# 训练前检查显存使用
nvidia-smi
# 如果基础占用超过2GB,先清理:
python -c "import torch; torch.cuda.empty_cache()"
6.2 多卡不工作(重要!)
问题: 只有一张卡在工作,其他卡闲置
诊断方法:
# 训练时运行 nvidia-smi,检查是否所有GPU都在使用
watch -n 1 nvidia-smi
# 如果只有GPU 0在工作,说明多卡没有启动
解决方案(按优先级):
- 确认使用了多卡启动命令
# ❌ 错误:单卡启动
python train_qwen3_qlora.py
# ✅ 正确:多卡启动
torchrun --nproc_per_node=4 train_qwen3_qlora.py
- 检查 GPU 可见性
# 检查可见GPU
echo $CUDA_VISIBLE_DEVICES
# 如果未设置或错误,手动设置
export CUDA_VISIBLE_DEVICES=0,1,2,3
- 确认训练参数配置
# 在 TrainingArguments 中确认
training_args = TrainingArguments(
...
ddp_find_unused_parameters=False, # DDP必须设置
...
)
- 检查进程数量
# 训练启动后,检查进程数
ps aux | grep train_qwen3_qlora.py
# 应该看到4个Python进程(每个GPU一个)
6.3 训练很慢
优化方案:
# 1. 启用混合精度训练
bf16=True
# 2. 增加数据加载器线程
dataloader_num_workers=4
# 3. 启用梯度检查点(节省显存但会稍慢)
gradient_checkpointing=True
# 4. 使用 Flash Attention 2
pip install flash-attn --no-build-isolation
# 在模型加载时:
model = AutoModelForCausalLM.from_pretrained(
model_name,
attn_implementation="flash_attention_2",
...
)
6.4 Loss 不下降
可能原因及解决:
# 1. 学习率过大或过小
learning_rate: 2e-4 # 尝试 1e-4 到 5e-4
# 2. 数据格式问题
# 检查数据是否正确格式化,labels 是否正确
# 3. 增加训练轮数
num_train_epochs: 3 → 5
# 4. 调整 warmup
warmup_steps: 100 → 200
6.5 模型输出乱码
解决方案:
# 1. 检查 tokenizer padding_side
tokenizer.padding_side = "right"
# 2. 检查是否正确设置 pad_token
if tokenizer.pad_token is None:
tokenizer.pad_token = tokenizer.eos_token
# 3. 增加训练数据量和质量
6.6 推理测试脚本
创建 inference_test.py:
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
from peft import PeftModel
def load_model(base_model_path, lora_path):
"""加载微调后的模型"""
tokenizer = AutoTokenizer.from_pretrained(base_model_path, trust_remote_code=True)
model = AutoModelForCausalLM.from_pretrained(
base_model_path,
torch_dtype=torch.bfloat16,
device_map="auto",
trust_remote_code=True,
)
# 加载 LoRA 权重
model = PeftModel.from_pretrained(model, lora_path)
model = model.merge_and_unload() # 合并 LoRA 权重
return model, tokenizer
def chat(model, tokenizer, instruction, input_text=""):
"""对话函数"""
prompt = f"""<|im_start|>system
你是一位专业的算命师傅,精通八字、生肖、风水等传统命理学。请根据用户的问题,提供专业、详细且富有洞察力的解答。<|im_end|>
<|im_start|>user
{instruction}
{input_text}<|im_end|>
<|im_start|>assistant
"""
inputs = tokenizer(prompt, return_tensors="pt").to(model.device)
outputs = model.generate(
**inputs,
max_new_tokens=512,
temperature=0.7,
top_p=0.9,
repetition_penalty=1.1,
do_sample=True,
)
response = tokenizer.decode(outputs[0], skip_special_tokens=True)
# 提取 assistant 的回复
response = response.split("<|im_start|>assistant")[-1].strip()
return response
if __name__ == "__main__":
# 加载模型
print("加载模型中...")
model, tokenizer = load_model(
base_model_path="Qwen/Qwen2.5-7B-Instruct",
lora_path="./output/final_model"
)
# 测试
print("\n测试开始:\n")
questions = [
("请帮我算一下今年的运势", "我是1992年出生的,属猴"),
("我想知道我的事业运", ""),
("最近感情不顺利", ""),
]
for instruction, input_text in questions:
print(f"问题: {instruction} {input_text}")
response = chat(model, tokenizer, instruction, input_text)
print(f"回答: {response}\n")
print("-" * 60 + "\n")
运行测试:
python inference_test.py
附录
A. 完整项目结构
qwen3-fortune-teller/
├── data/
│ ├── fortune_telling_train.json # 原始数据
│ ├── train.json # 训练集
│ └── val.json # 验证集
├── config/
│ ├── training_config.json # 训练配置
│ └── ds_config.json # DeepSpeed 配置
├── scripts/
│ ├── train_qwen3_qlora.py # 训练脚本
│ ├── prepare_data.py # 数据准备
│ ├── inference_test.py # 推理测试
│ └── monitor.py # 监控脚本
├── output/ # 输出目录
│ ├── checkpoint-*/ # 检查点
│ ├── final_model/ # 最终模型
│ └── logs/ # 日志
├── requirements.txt
└── README.md
B. requirements.txt
torch==2.2.0
transformers==4.38.0
accelerate==0.27.0
peft==0.9.0
bitsandbytes==0.42.0
datasets==2.17.0
trl==0.7.10
sentencepiece
deepspeed==0.13.1
tensorboard
wandb
flash-attn
C. 参考资源
- Qwen 官方文档: github.com/QwenLM/Qwen
- Hugging Face 文档: huggingface.co/docs
- PEFT 文档: github.com/huggingface…
- DeepSpeed 文档: www.deepspeed.ai/
🎉 总结
恭喜你完成了这份教程!
你已经学会了什么
✅ 理解核心概念: 微调、QLoRA、单机多卡、DDP等 ✅ 环境配置: 从零开始搭建Python + PyTorch + GPU训练环境 ✅ 数据准备: 理解数据格式,准备训练和验证数据 ✅ 单机多卡训练: 使用4张GPU并行训练,提速3-4倍 ✅ 监控调试: 使用TensorBoard监控训练,解决常见问题 ✅ 模型测试: 加载微调后的模型进行推理测试
完整工作流回顾
1. 环境配置 (30分钟)
├── 安装conda环境
├── 安装PyTorch和相关库
└── 验证GPU可用性
2. 数据准备 (30分钟)
├── 创建JSON格式数据
├── 划分训练集和验证集
└── 检查数据质量
3. 启动训练 (3-8小时)
├── 使用torchrun启动4卡训练
├── 监控训练进度和显存
└── 等待训练完成
4. 测试模型 (15分钟)
├── 加载微调后的模型
├── 进行推理测试
└── 评估效果
下一步建议
初级阶段(完成第一次微调):
- ✅ 收集更多高质量数据(至少500-1000条)
- ✅ 增加训练轮数(3 → 5 epochs)
- ✅ 尝试不同的learning_rate(1e-4, 2e-4, 5e-4)
中级阶段(优化训练效果): 4. 📊 使用更大的模型(Qwen3-14B) 5. 🎯 调整LoRA参数(lora_r: 32, 64, 128) 6. 🔧 尝试DeepSpeed ZeRO-3进一步优化
高级阶段(生产部署): 7. 🚀 使用vLLM加速推理 8. 🌐 部署为API服务(FastAPI) 9. 💡 使用DPO (Direct Preference Optimization)进一步优化 10. 📈 搭建Web界面(Gradio/Streamlit)
重要提醒
单机多卡的核心命令(记住它!):
# ❌ 错误 - 只用单卡
python train_qwen3_qlora.py
# ✅ 正确 - 使用4卡
torchrun --nproc_per_node=4 train_qwen3_qlora.py
显存不够时的应对策略:
- 减小 max_seq_length: 1024 → 512
- 减小 lora_r: 64 → 32
- 使用更小的模型: Qwen3-7B → Qwen3-1.8B
学习资源
- 📘 Qwen官方文档: github.com/QwenLM/Qwen
- 📘 Hugging Face文档: huggingface.co/docs
- 📘 PEFT (LoRA)文档: github.com/huggingface…
- 📘 DeepSpeed文档: www.deepspeed.ai/
- 📘 小白版教程: 见同目录下的
Qwen3单机多卡微调完整教程-小白版.md
遇到问题?
- 先查看本教程的常见问题解决章节
- 检查术语速查表,确认概念理解正确
- 查看错误信息,大部分错误都有明确提示
- 搜索GitHub Issues,很可能有人遇到过类似问题
祝你训练顺利! 🚀
记住:数据质量 > 模型大小 > 训练时间
好的微调效果来自于高质量的数据,而不是盲目增加模型规模。先从500-1000条精心准备的数据开始,观察效果,再逐步扩展。