昇腾服务器测试lora微调
垂域模型微调实践手册(昇腾 NPU + LLaMA-Factory + LoRA)
记录一次完整的"先行测试"过程:从环境搭建到 LoRA 微调(SFT)验证生效,以及继续预训练(CPT)与交付的完整路线。
实验环境:2 × Ascend 910B3(64G/卡),基座模型 Qwen2.5-7B-Instruct(最终目标 32B/72B,流程相同)。
⚠️ 说明:本文为博客公开版,所有服务器路径、用户名等隐私信息已替换为通用占位路径(如/data/models、/data/finetune),技术内容与操作步骤不受影响。
一、技术路线与核心概念
1.1 架构选择:单基座 + 多 LoRA 适配器
┌─ 政务适配器(几百 MB ~ 2GB)
Qwen2.5-72B ────┼─ 医疗适配器 ← 按请求热切换,vLLM --enable-lora
(存一份) └─ 法律适配器
- LoRA:冻结基座权重,只训练旁路低秩矩阵(A、B),可训练参数约为全量的 0.1%~1%。
- 一个适配器只对应生成它的那个基座,不可跨基座混用。
- 不合并即可部署(挂载模式);合并(merge)只是打包方式切换,不是能力升级,数学上与挂载逐比特等价。
1.2 概念辨析
| 阶段 | 吃什么数据 | 改变模型什么 | 产出 |
|---|---|---|---|
| 预训练(Pretrain) | 海量无标注语料 | 一切,从零形成基座 | 完整模型(巨头游戏,中小团队不做) |
继续预训练(CPT,stage: pt) | 领域纯文本(GB 级) | “知道什么”、语言分布、术语 | 适配器(ΔW₁) |
监督微调(SFT,stage: sft) | 指令问答对(千条级) | “怎么答”、格式、风格、任务套路 | 适配器(ΔW₂) |
| 合并(export) | — | 无,仅打包 | 基座 + 适配器的完整模型(仅交付时做一次) |
关键认知:
- SFT 改变"怎么说话",CPT 改变"知道什么";CPT 后风格几乎不变,但内容更"懂行"。
- CPT 判断成败看领域知识生成质量(术语准不准),不能只看问答风格。
- 严格来说 CPT 应从 Base 版基座(非 Instruct)开始;练手阶段用 Instruct 版跑通流程即可,正式大成本训练时再切 Base。
- bitsandbytes(QLoRA 的核心库)只支持 CUDA,昇腾 NPU 用不了;但 7B bf16 LoRA 双卡 128G 完全不需要量化。
1.3 CPT → SFT 串行训练(垂域标准打法)
基座(一份,不动)
│ CPT 训练 → CPT适配器(ΔW₁)
│ adapter_name_or_path 接着练 → 最终适配器(ΔW₂,已融合知识与风格)
└ 交付时:export 合并"基座 + 最终适配器" → 完整模型(仅此一次合并)
- 不存在"基座 + CPT适配器 + SFT适配器"三合一:SFT 是在 CPT 适配器的同一对 LoRA 矩阵上继续更新。
- 全量微调才必须先合并;LoRA 路线全程不用合并。
二、环境与资源
2.1 镜像
| 用途 | 镜像 | 说明 |
|---|---|---|
| 训练 | quay.io/ascend/llamafactory:latest-910b-ubuntu | 官方镜像,自带 CANN + torch_npu + LLaMA-Factory(0.9.6.dev0,位于容器 /app) |
| 推理 | quay.io/ascend/vllm-ascend:v0.13.0 | 已有镜像;注意 v0.13 + LoRA 图捕获有 bug,需 --enforce-eager(见 5.4) |
2.2 目录约定(宿主机与容器内路径保持一致,免去路径换算)
| 宿主机路径 | 内容 |
|---|---|
/data/models/ | LLaMA-Factory 源码(含 data/)、基座模型 Qwen2.5-7B-Instruct/ |
/data/finetune/logs/ | 训练日志 |
/data/finetune/output/ | 训练产物(适配器) |
2.3 训练容器启动(常驻,名为 llamafactory-train)
docker run -itd \
--name llamafactory-train \
-p 9950-9955:9950-9955 \
--device /dev/davinci0 \
--device /dev/davinci1 \
--device /dev/davinci_manager \
--device /dev/devmm_svm \
--device /dev/hisi_hdc \
-v /usr/local/dcmi:/usr/local/dcmi \
-v /usr/local/bin/npu-smi:/usr/local/bin/npu-smi \
-v /usr/local/Ascend/driver:/usr/local/Ascend/driver \
-v /usr/local/Ascend/driver/version.info:/usr/local/Ascend/driver/version.info \
-v /usr/local/Ascend/add-ons:/usr/local/Ascend/add-ons \
-v /etc/ascend_install.info:/etc/ascend_install.info \
-v /data/models:/data/models \
-v /data/finetune:/data/finetune \
--shm-size 16g \
quay.io/ascend/llamafactory:latest-910b-ubuntu \
bash
注意:
- 只用 0、1 两张卡,训练与推理容器错峰使用(推理容器运行时先停训练相关进程)。
- 容器内 LLaMA-Factory 在
/app(镜像自带,只读);统一使用宿主机挂载的那份(可读写,数据集在里面):cd /data/models/LLaMA-Factory。
2.4 容器内环境自检(进容器后必做)
docker exec -it llamafactory-train bash
# ① NPU 是否可见
npu-smi info
# ② torch_npu 环境(+cpu 后缀是正常的,看 is_available)
python -c "import torch, torch_npu; print(torch.__version__, torch.npu.is_available())"
# ③ 模型能否加载上 NPU(决定性验证)
python -c "from transformers import AutoModelForCausalLM; import torch; m = AutoModelForCausalLM.from_pretrained('/data/models/Qwen2.5-7B-Instruct', torch_dtype=torch.bfloat16, device_map='npu:0'); print('NPU 加载成功')"
# ④ 训练命令可用
llamafactory-cli version
三、数据准备
3.1 SFT 数据格式(alpaca,指令问答对)
文件:LLaMA-Factory/data/vertical_demo.json(72 条练手数据,政务/医疗/法律各 24 条;内容为演示用模拟数据,知识不保真,仅用于跑通流程)。
[
{"instruction": "请解释什么是行政许可,并举一个政务办理中的例子。", "input": "", "output": "行政许可是指行政机关根据……"},
{"instruction": "请将以下政务大厅的口头答复改写为规范的政务回复文书。", "input": "口头答复:你这个材料不对啊……", "output": "【政务答复】您好,经审核……"}
]
instruction:任务描述;input:可选的输入材料(没有就空字符串);output:标准答案。- 真实项目:数据质量决定成败(工作量约占 70%)。格式、长度分布可参考练手文件。
3.2 CPT 数据格式(纯文本)
每行一段、无结构,LLaMA-Factory 只要求一个 text 字段。仓库自带练手语料:data/c4_demo.jsonl(已注册为 c4_demo)。真实项目需 GB 级领域语料(法规全文、公文、病历等)。
3.3 注册数据集
编辑 LLaMA-Factory/data/dataset_info.json,在 JSON 中加一个条目(花名册,惰性加载,不引用就无影响):
"vertical_demo": {
"file_name": "vertical_demo.json",
"columns": {
"prompt": "instruction",
"query": "input",
"response": "output"
}
}
机制:yaml 的 dataset: 参数"点名" → dataset_info.json 查"名字→文件"映射 → 加载数据文件。
新增数据集三步走:放文件进 data/ → dataset_info.json 加条目 → yaml 写 dataset: 名字。
在线数据集(ms_hub_url,如 alpaca_zh、belle_*)写名字即可自动下载,国内加 export USE_MODELSCOPE=1。
四、SFT 训练流程(已验证跑通)
4.1 训练配置 sft_demo.yaml
存于 /data/finetune/sft_demo.yaml:
### model
model_name_or_path: /data/models/Qwen2.5-7B-Instruct
### method
stage: sft
do_train: true
finetuning_type: lora
lora_rank: 8
lora_target: all
### dataset
dataset: vertical_demo
template: qwen
cutoff_len: 2048
overwrite_cache: true
preprocessing_num_workers: 4
### output
output_dir: /data/finetune/output/sft_demo
logging_steps: 5
save_steps: 100
plot_loss: true
overwrite_output_dir: true
### train
per_device_train_batch_size: 4
gradient_accumulation_steps: 4
learning_rate: 1.0e-4
num_train_epochs: 3.0
lr_scheduler_type: cosine
warmup_ratio: 0.1
bf16: true
gradient_checkpointing: true
参数直觉:
lora_rank: 8+lora_target: all(全部 7 类线性层)→ 可训练参数 0.26%(约 2000 万)。- 72 条数据配 3 epoch(数据太少,1 epoch 学不出可感知效果);真实数据一般 1~3 epoch,以验证集效果为准。
- SFT 学习率
1e-4;CPT 用更低的2e-5(CPT 调的是知识分布,步子大容易洗坏基座)。
4.2 启动训练(训练容器内)
cd /data/models/LLaMA-Factory
nohup env ASCEND_RT_VISIBLE_DEVICES=0,1 FORCE_TORCHRUN=1 llamafactory-cli train /data/finetune/sft_demo.yaml > /data/finetune/logs/sft_demo.log 2>&1 &
tail -f /data/finetune/logs/sft_demo.log
时间分布经验:启动(分布式初始化、数据 tokenize、权重加载 76 亿参数)约 1~2 分钟,72 条数据实际训练约 20 秒。日志长时间只有进度条没有 loss 行是正常现象,不是卡死。
4.3 日志判读
日志三段式:启动段 → 训练段(进度条 + 周期 loss 行)→ 结束段。
成功标志(三者齐全):
***** Running training *****
{'loss': '1.393', 'grad_norm': '0.8497', 'learning_rate': '6.913e-05', 'epoch': '1.889'}
Training completed. → train metrics → Saving model checkpoint to .../sft_demo
- 健康 loss:从 2 上下缓慢下降;平均 1.3 左右属正常量级。
- LoRA 挂载确认行:
Found linear modules: gate_proj,q_proj,up_proj,v_proj,k_proj,down_proj,o_proj。
危险信号(任一中招即有问题):
grep -iE "error|traceback|exception" xxx.log
grep -i "out of memory\|OOM" xxx.log
Traceback、RuntimeError、out of memory、loss: nan、进度条停在某处且 NPU 利用率归零。
无害噪音(见到可忽略):can not use command: npu-smi info(容器内监控工具未挂,不影响训练)、warmup_ratio is deprecated、Dropping the following result、CANN/HDK 32 padding 警告。
4.4 产物
output/sft_demo/
├── adapter_model.safetensors ← 最终交付物:LoRA 权重(几十 MB)
├── adapter_config.json ← LoRA 配置
├── tokenizer_config.json 等 ← 分词器文件(保留)
├── training_loss.png ← loss 曲线
├── trainer_log.jsonl ← 逐步 loss 记录
└── checkpoint-9/ ← 中途存档(不用于部署,可删)
版本管理习惯:每次阶段切换/大改前 cp -r 备份适配器目录(小文件,秒级);练砸了从备份重新出发,不用重训。
五、推理验证:vLLM 挂载适配器
5.1 前置
推理用独立容器(与训练错峰,共用 0、1 卡)。训练后启动推理容器前,确认训练进程已结束、npu-smi info 显存已释放。
5.2 临时推理容器(测试用完即删)
docker run -itd \
--name tempdemo \
--rm \
--device /dev/davinci0 \
--device /dev/davinci1 \
--device /dev/davinci_manager \
--device /dev/devmm_svm \
--device /dev/hisi_hdc \
-v /usr/local/dcmi:/usr/local/dcmi \
-v /usr/local/bin/npu-smi:/usr/local/bin/npu-smi \
-v /usr/local/Ascend/driver/lib64:/usr/local/Ascend/driver/lib64 \
-v /usr/local/Ascend/driver/version.info:/usr/local/Ascend/driver/version.info \
-v /etc/ascend_install.info:/etc/ascend_install.info \
-v /data/models:/models:ro \
-v /data/finetune:/data/finetune:ro \
-p 8000:8000 \
--shm-size 16g \
quay.io/ascend/vllm-ascend:v0.13.0 \
bash
--rm:stop 即自动删除,无痕清理。生产环境的推理容器按现有 vllm 脚本规范创建。
5.3 启动 vLLM 服务(容器内)
docker exec -it tempdemo bash
# 确认适配器可见
ls /data/finetune/output/sft_demo/adapter_model.safetensors
vllm serve /models/Qwen2.5-7B-Instruct \
--served-model-name qwen \
--enable-lora \
--lora-modules vertical=/data/finetune/output/sft_demo \
--max-model-len 8192 \
--tensor-parallel-size 2 \
--enforce-eager
--lora-modules 别名=适配器目录:目录指到sft_demo这一层即可;别名(示例为vertical)是客户端model参数里用的名字。- 基座也用
--served-model-name起了名(qwen),于是一个服务同时暴露"裸基座"和"挂适配器"两个入口,对比测试极方便。 - 多个适配器并列挂载:
--lora-modules gov=路径1 medical=路径2 legal=路径3。
5.4 已知坑:vllm-ascend v0.13 + LoRA 图捕获失败
现象:启动到 Capturing CUDA graphs 33/40 左右崩溃,根因日志为昇腾 LoRA 算子 sgmv_shrink 执行失败 → capture_end ... error code is 507903。
规避:加 --enforce-eager(跳过图录制)。代价是吞吐略降,功能不受影响。
后续可试本地已有的 vllm-ascend:v0.14.0rc1 镜像,验证新版是否修复。
5.5 验证适配器生效(两个测试)
# 测试一:训练集原题,对比基座 vs 适配器的风格差异
curl http://localhost:8000/v1/chat/completions -H "Content-Type: application/json" \
-d '{"model": "qwen", "messages": [{"role": "user", "content": "请解释什么是行政许可"}], "temperature": 0.1}'
curl http://localhost:8000/v1/chat/completions -H "Content-Type: application/json" \
-d '{"model": "vertical","messages": [{"role": "user", "content": "请解释什么是行政许可"}], "temperature": 0.1}'
# 测试二:训练集没有的变体题,验证学到的是"模式"而非"背题"
curl http://localhost:8000/v1/chat/completions -H "Content-Type: application/json" \
-d '{"model": "vertical","messages": [{"role": "user", "content": "请用一句话向老年人解释什么是行政复议"}], "temperature": 0.1}'
实测结果(练手数据):
- 原题:基座输出 251 token、Markdown 列表式长文;适配器输出 78 token、单段连贯文字——与训练数据形态一致,挂载生效。
- 未见题:“行政复议是老百姓认为行政机关作出的决定不公平时,可以向上级机关提出重新审议的申请。”——一句话、通俗口吻、知识正确,风格迁移确认。
判断标准:测试一看"挂载机制通"(风格变化),测试二看"学到模式"(未见题风格一致)。
5.6 收尾
docker stop tempdemo # --rm 生效,容器自动删除
docker ps -a | grep tempdemo # 确认无残留
六、CPT(继续预训练)流程
6.1 与 SFT 的三个差异
stage: pt(配置层面唯一的本质区别);- 数据集换纯文本语料(如内置
c4_demo;真实项目用 GB 级领域语料); - 学习率更低(
2e-5),防止洗坏基座能力。
6.2 练手配置 cpt_demo.yaml
### model
model_name_or_path: /data/models/Qwen2.5-7B-Instruct
### method
stage: pt
do_train: true
finetuning_type: lora
lora_rank: 8
lora_target: all
### dataset
dataset: c4_demo
template: qwen
cutoff_len: 2048
overwrite_cache: true
preprocessing_num_workers: 4
### output
output_dir: /data/finetune/output/cpt_demo
logging_steps: 5
save_steps: 100
plot_loss: true
overwrite_output_dir: true
### train
per_device_train_batch_size: 4
gradient_accumulation_steps: 4
learning_rate: 2.0e-5
num_train_epochs: 1.0
lr_scheduler_type: cosine
warmup_ratio: 0.1
bf16: true
gradient_checkpointing: true
6.3 CPT 的效果与验证
- CPT 适配器装上后问答风格不会有可见变化(它没学过指令格式),效果体现在:领域术语使用、法规引用、文本的"专业味"。
- 验证方式:生成领域文本看质量;或作为 6.4 串行训练的中间步骤。
- pt 阶段 loss 形态与 SFT 不同(初值、下降节奏),首次跑时留意对比。
6.4 串行训练:CPT 之上继续 SFT(标准路线)
# sft_after_cpt.yaml 的关键两行
model_name_or_path: /data/models/Qwen2.5-7B-Instruct
adapter_name_or_path: /data/finetune/output/cpt_demo # 从CPT适配器出发
stage: sft
finetuning_type: lora
# 其余同 sft_demo.yaml
- 产物是一个融合了 CPT+SFT 的最终适配器;部署、交付都只面对这一个文件。
- 开始 SFT 前
cp -r output/cpt_demo output/cpt_demo_bak留底。 - 正式项目最佳实践:
Qwen2.5-7B-Base(非 Instruct)→ CPT → SFT。
七、合并与交付(仅在交付时执行)
llamafactory-cli export \
--model_name_or_path /data/models/Qwen2.5-7B-Instruct \
--adapter_name_or_path /data/finetune/output/最终适配器目录 \
--template qwen \
--finetuning_type lora \
--export_dir /data/finetune/output/交付模型目录
- 产出自包含完整模型(7B 约 15GB,72B 约 145GB),对方免配 LoRA 挂载。
- 适用场景:对外交付、长期冻结的版本、推理框架不支持 LoRA。
- 开发/内部部署阶段保持适配器形态(基座一份 + 适配器小文件,按场景切换)。
八、问题排查速查表
| 现象 | 定位 | 处理 |
|---|---|---|
npu-smi: command not found(容器内) | 监控工具未挂载 | 仅影响容器内监控;宿主机执行 npu-smi info 即可,或补挂 -v /usr/local/bin/npu-smi:/usr/local/bin/npu-smi 与 -v /usr/local/dcmi:/usr/local/dcmi |
torch 版本显示 x.x.x+cpu | 正常 | torch_npu 即此形态,以 torch.npu.is_available() 为准 |
| CANN/HDK 32 padding 警告 | 信息性提示 | 忽略 |
| 日志只有进度条没有 loss 行,“像卡死” | 启动阶段耗时长 | 正常现象;看 NPU 利用率确认在干活 |
vLLM 启动在 Capturing CUDA graphs 崩溃,sgmv_shrink / error 507903 | vllm-ascend 0.13 + LoRA 图捕获 bug | 加 --enforce-eager;后续试 v0.14.0rc1 镜像 |
| 训练启动报找不到数据集 | dataset_info.json 未注册或 file_name 不对 | 核对名字、文件路径、JSON 逗号 |
| 容器启动后 NPU 不可用 | 设备节点没挂全 | 对照 2.3 检查五个 --device |
| 启动容器报资源不足 | 两张卡被旧进程占用 | 停旧容器/进程,npu-smi info 确认显存释放 |
九、后续路线
- 数据建设(主战场,约 70% 工作量):垂域指令数据(SFT)+ 领域语料(CPT)。模型是"不知道"(缺知识,需 CPT)还是"知道但答不好"(只需 SFT),先用一批真实业务问题测基座确定短板。
- 每个新场景 = 重复三步:数据注册 → 复制 yaml 改
dataset:和output_dir:→ 训练 → vLLM 加一条--lora-modules挂载。 - 规模化:7B 流程原样搬到 32B/72B(双卡 910B 训 72B 需多卡并行方案另行规划);正式 CPT 考虑 Base 版基座 + GB 级语料。
- 性能:试 vllm-ascend 0.14 去掉
--enforce-eager;多适配器并发按 vLLM multi-LoRA serving 调优。
附录 A:一次完整 SFT 的命令清单(复现用)
# 宿主机
docker pull quay.io/ascend/llamafactory:latest-910b-ubuntu
# (训练容器已创建则跳过)按 2.3 启动 llamafactory-train
docker exec -it llamafactory-train bash
# 容器内:环境自检(2.4 四步)→ 确认数据与注册(三章)→ 配置 yaml(4.1)
cd /data/models/LLaMA-Factory
nohup env ASCEND_RT_VISIBLE_DEVICES=0,1 FORCE_TORCHRUN=1 llamafactory-cli train /data/finetune/sft_demo.yaml > /data/finetune/logs/sft_demo.log 2>&1 &
tail -f /data/finetune/logs/sft_demo.log # 看 loss;Ctrl+C 只退出查看,不影响训练
# 训练完成后:退出训练容器,按 5.2 起临时推理容器,5.3 起服务,5.5 两组对比测试,5.6 清理
备忘:
docker pull报 access denied 多半是 registry 命名空间写错(ModelScope 正确为registry.cn-hangzhou.aliyuncs.com/modelscope-repo/modelscope:...);quay.io/ascend在本机已验证可用。
鲲鹏昇腾开发者社区是面向全社会开放的“联接全球计算开发者,聚合华为+生态”的社区,内容涵盖鲲鹏、昇腾资源,帮助开发者快速获取所需的知识、经验、软件、工具、算力,支撑开发者易学、好用、成功,成为核心开发者。
更多推荐

所有评论(0)