垂域模型微调实践手册(昇腾 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 的三个差异

  1. stage: pt(配置层面唯一的本质区别);
  2. 数据集换纯文本语料(如内置 c4_demo;真实项目用 GB 级领域语料);
  3. 学习率更低(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 507903vllm-ascend 0.13 + LoRA 图捕获 bug加 --enforce-eager;后续试 v0.14.0rc1 镜像
训练启动报找不到数据集dataset_info.json 未注册或 file_name 不对核对名字、文件路径、JSON 逗号
容器启动后 NPU 不可用设备节点没挂全对照 2.3 检查五个 --device
启动容器报资源不足两张卡被旧进程占用停旧容器/进程,npu-smi info 确认显存释放

九、后续路线

  1. 数据建设(主战场,约 70% 工作量):垂域指令数据(SFT)+ 领域语料(CPT)。模型是"不知道"(缺知识,需 CPT)还是"知道但答不好"(只需 SFT),先用一批真实业务问题测基座确定短板。
  2. 每个新场景 = 重复三步:数据注册 → 复制 yaml 改 dataset: 和 output_dir: → 训练 → vLLM 加一条 --lora-modules 挂载。
  3. 规模化:7B 流程原样搬到 32B/72B(双卡 910B 训 72B 需多卡并行方案另行规划);正式 CPT 考虑 Base 版基座 + GB 级语料。
  4. 性能:试 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 在本机已验证可用。

Logo

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

更多推荐