前言

cann-recipes-train 是昇腾 CANN 开源社区的大模型训练仓库,专门展示如何在昇腾 NPU 上跑通主流大模型的预训练和微调流程。

预训练(Pre-training)和推理(Inference)的区别:

  • 推理:加载别人已经训练好的模型,直接用
  • 预训练:从零开始训练,需要大量数据、算力、时间
  • 微调(Fine-tuning):在预训练模型基础上,用少量数据微调

这篇文章讲微调——在昇腾 NPU 上用 LoRA + QLoRA 技术微调 Qwen2.5-7B,让它学会特定任务(比如写代码、对话、摘要等)。

选微调的原因是:从零预训练需要成百上千张卡,普通团队玩不起,但微调几张卡就能跑,是更实际的选择。

仓库地址:https://atomgit.com/cann/cann-recipes-train

环境要求

硬件

  • Atlas 300T Pro(训练卡,单卡)或 Atlas 800(8 卡)
  • 全量微调 Qwen2.5-7B 需要约 140GB 显存,建议 Atlas 800(8 × 64GB = 512GB)
  • LoRA 微调约需 20GB 显存,Atlas 300T Pro 可跑
  • QLoRA(int4 量化)约需 10GB 显存,Atlas 300I Pro 可跑

软件

CANN Toolkit 8.0+ # 必须
Python 3.10+ # 推荐 3.10
PyTorch 2.1.0+ # 基础框架
torch-npu 2.1.0+ # 昇腾 NPU 后端
transformers 4.36+ # 模型加载
accelerate 0.25+ # 分布式训练
peft 0.7+ # LoRA/QLoRA 框架(关键依赖)
deepspeed 0.12+ # 分布式训练框架
datasets 2.16+ # 数据集加载

第一步:安装依赖

创建独立环境

conda create -n cann-train python=3.10 -y
conda activate cann-train

# 基础依赖
pip install torch==2.1.0 torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu
pip install torch-npu==2.1.0

# 训练相关依赖
pip install transformers==4.36.0 accelerate==0.25.0
pip install peft==0.7.0 deepspeed==0.12.0 datasets==2.16.0
pip install tensorboard peft bitsandbytes # bitsandbytes 用于 QLoRA 量化

克隆 cann-recipes-train

git clone https://atomgit.com/cann/cann-recipes-train.git
cd cann-recipes-train
pip install -e .

⚠️ 坑 1:DeepSpeed 报 NCCL 初始化失败

DeepSpeed 默认用 NCCL(NVIDIA 的通信库),但昇腾 NPU 用的是 HCCL(昇腾的通信库)。需要手动配置:

# 设置 DeepSpeed 使用 HCCL
export NCCL_DEBUG=INFO
export DS_ACCELERATOR=npu # 关键:告诉 DeepSpeed 用 NPU

# 或者在代码里指定
ds_config = {
 "train_batch_size": "auto",
 "gradient_accumulation_steps": "auto",
 "accelerator": {
 "backend": "npu" # 指定用 NPU
 }
}

验证安装

# test_env.py
import torch
import torch_npu
import deepspeed
import peft
from transformers import AutoModel

print(f"✅ torch: {torch.__version__}")
print(f"✅ torch-npu: {torch_npu.__version__}")
print(f"✅ DeepSpeed: {deepspeed.__version__}")
print(f"✅ PEFT: {peft.__version__}")
print(f"✅ NPU 可用: {torch.npu.is_available()}")

# 简单测试:创建一个 NPU 上的张量
x = torch.randn(100, 100).npu()
print(f"✅ NPU 张量创建成功: {x.device}")

运行:

python test_env.py

如果所有检查都通过,环境安装成功。

第二步:准备训练数据

微调需要准备一个高质量的数据集,格式是 JSONL(每行一个 JSON)。

数据格式

{"messages": [
 {"role": "system", "content": "你是一个代码助手,专门回答编程问题。"},
 {"role": "user", "content": "用 Python 写一个斐波那契数列函数"},
 {"role": "assistant", "content": "以下是斐波那契数列的 Python 实现:\n\n```python\ndef fibonacci(n):\n if n <= 0:\n return []\n elif n == 1:\n return [0]\n elif n == 2:\n return [0, 1]\n else:\n fib = [0, 1]\n for i in range(2, n):\n fib.append(fib[i-1] + fib[i-2])\n return fib\n\n# 示例\nprint(fibonacci(10)) # [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]\n```"}
]}

每个样本是一个 JSON,包含 messages 数组,数组里按顺序放 system/user/assistant 的对话。

数据集来源

方式 A:用公开数据集(推荐新手)

from datasets import load_dataset

# 加载 alpaca 数据集(斯坦福的指令微调数据集)
dataset = load_dataset("yahma/alpaca-cleaned")
print(dataset)

# 查看一个样本
print(dataset["train"][0])

方式 B:用自定义数据集

把自己的对话数据转成 JSONL 格式:

import json

# 假设原始数据是 CSV 格式
import pandas as pd
df = pd.read_csv("my_conversations.csv")

# 转成 JSONL
with open("train.jsonl", "w", encoding="utf-8") as f:
 for _, row in df.iterrows():
 sample = {
 "messages": [
 {"role": "system", "content": "你是一个有帮助的助手。"},
 {"role": "user", "content": row["question"]},
 {"role": "assistant", "content": row["answer"]}
 ]
 }
 f.write(json.dumps(sample, ensure_ascii=False) + "\n")

print(f"转换完成,共 {len(df)} 条数据")

数据预处理

训练前要处理数据(tokenize、转格式):

from transformers import AutoTokenizer
from datasets import load_dataset

# 加载 tokenizer
tokenizer = AutoTokenizer.from_pretrained(
 "Qwen/Qwen2.5-7B-Instruct",
 trust_remote_code=True
)

# 加载数据
dataset = load_dataset("json", data_files="train.jsonl", split="train")

# Tokenize 函数
def tokenize(example):
 # 把 messages 转成文本
 text = tokenizer.apply_chat_template(
 example["messages"],
 tokenize=False,
 add_generation_prompt=False
 )
 # tokenize
 result = tokenizer(
 text,
 truncation=True,
 max_length=2048,
 padding="max_length",
 return_tensors=None
 )
 # labels 就是 input_ids(语言模型的目标)
 result["labels"] = result["input_ids"].copy()
 return result

# 处理数据集
tokenized_dataset = dataset.map(
 tokenize,
 remove_columns=dataset.column_names,
 num_proc=8 # 多进程加速
)

# 保存
tokenized_dataset.save_to_disk("./data/train_tokenized")
print(f"处理完成,共 {len(tokenized_dataset)} 条数据")

⚠️ 坑 2:tokenize 报 “Unknown token” 或 tokenizer 不对齐"

Qwen2.5 的 tokenizer 用特殊的 template 处理对话,如果 template 用错了,模型输出会乱码。

检查 tokenizer 是否有 chat template:

tokenizer = AutoTokenizer.from_pretrained(
 "Qwen/Qwen2.5-7B-Instruct",
 trust_remote_code=True
)

# 检查是否有 chat template
print(tokenizer.chat_template)
# 如果输出 None,说明没加载成功,需要指定 trust_remote_code=True

第三步:配置训练

LoRA 微调配置

LoRA(Low-Rank Adaptation)是一种参数高效微调技术,只训练少量参数(通常是原模型的 0.1-1%),大大降低显存占用。

# lora_config.py
from peft import LoraConfig, get_peft_model, TaskType

# LoRA 配置
lora_config = LoraConfig(
 task_type=TaskType.CAUSAL_LM, # 因果语言模型
 r=16, # LoRA 秩,越大效果越好但参数越多
 lora_alpha=32, # LoRA 缩放因子,通常是 r 的 2 倍
 lora_dropout=0.05, # dropout,防止过拟合
 target_modules=[ # 要加 LoRA 的模块
 "q_proj", "k_proj", "v_proj", # Attention 的 QKV 投影
 "o_proj", # Attention 的输出投影
 "gate_proj", "up_proj", "down_proj" # FFN 层
 ],
 bias="none", # 不训练 bias
)

print("LoRA 配置:")
print(f" 秩 r: {lora_config.r}")
print(f" 可训练参数: {lora_config.lora_alpha / lora_config.r * 100:.1f}%")

DeepSpeed 配置

DeepSpeed ZeRO(Zero Redundancy Optimizer)是一种分布式训练技术,把优化器状态、梯度、参数分片到多张卡上,大幅降低显存占用。

// ds_config.json
{
 "zero_optimization": {
 "stage": 2,
 "offload_optimizer": {
 "device": "cpu",
 "pin_memory": true
 },
 "offload_param": {
 "device": "cpu",
 "pin_memory": true
 },
 "allgather_partitions": true,
 "allgather_bucket_size": 5e7,
 "overlap_comm": true,
 "reduce_scatter": true,
 "reduce_bucket_size": 5e7,
 "contiguous_gradients": true
 },
 "gradient_accumulation_steps": 4,
 "gradient_clipping": 1.0,
 "steps_per_print": 10,
 "train_batch_size": "auto",
 "train_micro_batch_size_per_gpu": "auto",
 "wall_clock_breakdown": false
}

ZeRO-2 在 8 张 Atlas 800 上可以把 Qwen2.5-7B 的全量微调显存从 512GB 降到 ~200GB。

完整训练脚本

# train_qwen_lora.py
import os
import torch
import torch_npu
import deepspeed
from transformers import AutoModelForCausalLM, AutoTokenizer, TrainingArguments
from peft import LoraConfig, get_peft_model, TaskType
from datasets import load_from_disk
from deepspeed import DeepSpeedConfig

# 配置 DeepSpeed
ds_config = DeepSpeedConfig("ds_config.json")

# 加载模型
model_path = "./models/Qwen2.5-7B"
tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=True)

# 加载模型到 NPU
model = AutoModelForCausalLM.from_pretrained(
 model_path,
 torch_dtype=torch.float16,
 device_map="npu:0",
 trust_remote_code=True
)

# 应用 LoRA
lora_config = LoraConfig(
 task_type=TaskType.CAUSAL_LM,
 r=16,
 lora_alpha=32,
 lora_dropout=0.05,
 target_modules=["q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj"],
 bias="none",
)
model = get_peft_model(model, lora_config)
model.print_t
...(truncated)...
Logo

鲲鹏昇腾开发者社区是面向全社会开放的“联接全球计算开发者,聚合华为+生态”的社区,内容涵盖鲲鹏、昇腾资源,帮助开发者快速获取所需的知识、经验、软件、工具、算力,支撑开发者易学、好用、成功,成为核心开发者。

更多推荐