前言
我所在团队要预训练一个7B参数的语言模型,预算只够买4张Ascend 910B。原来以为昇腾NPU只能跑推理,没想到torchtitan-npu直接支持大模型预训练------4卡跑7B模型,训练速度1470 tokens/s,两周训完100B tokens。这篇文章是完整的踩坑实录,从环境搭建到性能调优,每一步都记录在案。
torchtitan-npu是Meta TorchTitan的昇腾NPU适配版,核心改动是把CUDA后端替换成CANN后端,上层PyTorch代码零修改。如果你用过TorchTitan在GPU上训模型,迁移到NPU上只需要改一个环境变量。
环境准备
硬件要求
| 组件 | 最低配置 | 推荐配置 |
|---|---|---|
| NPU | 4×Ascend 910 | 8×Ascend 910 |
| CPU | 64核 | 128核 |
| 内存 | 512GB | 1TB |
| 存储 | 2TB NVMe SSD | 4TB NVMe SSD |
| 网络 | RDMA(RoCEv2) | HCCS + RDMA |
⚠️ 踩坑预警:RDMA网卡驱动版本必须跟CANN版本匹配,不然hccl初始化会报HCCL_INIT_ERROR。我用的CANN 8.5 + Mellanox OFED 5.9-0.5.6.0,跑通没问题。
软件安装
bash
# 1. 安装CANN 8.5(必须先装)
chmod +x Ascend-cann-toolkit_8.5.0_linux-x86_64.run
sudo ./Ascend-cann-toolkit_8.5.0_linux-x86_64.run --install
# 2. 安装PyTorch 2.1 + 昇腾NPU版
pip install torch==2.1.0
pip install torch-npu==2.1.0
# 3. 克隆torchtitan-npu
git clone https://atomgit.com/cann/torchtitan-npu.git
cd torchtitan-npu
pip install -r requirements.txt
pip install -e .
Step 1:配置训练参数
torchtitan-npu用YAML配置文件管理所有训练参数,不用改代码:
yaml
# train_config.yaml
model:
name: llama3_7b
dim: 4096
n_layers: 32
n_heads: 32
vocab_size: 128256
max_seq_len: 8192
training:
batch_size: 2 # 每卡batch size
seq_len: 8192
gradient_accumulation_steps: 8
learning_rate: 3e-4
weight_decay: 0.1
max_steps: 100000
parallel:
dp_degree: 4 # 4卡数据并行
tp_degree: 1 # 不用张量并行(7B模型单卡放得下)
dtype: bf16 # BF16混合精度
关键参数说明:
batch_size: 2:每卡2个序列,4卡总共8个序列,梯度累积8步等于全局batch=64dp_degree: 4:4卡数据并行,每张卡跑完整模型dtype: bf16:BF16比FP16训练更稳定(指数位更多,不容易溢出)
Step 2:准备预训练数据
torchtitan-npu要求数据格式是JSONL + tokenizer:
python
# prepare_data.py
import json
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained("meta-llama/Llama-3-7B")
# 把原始文本转成token ID并保存为JSONL
input_file = "raw_corpus.txt"
output_file = "train_data.jsonl"
with open(input_file, "r") as fin, open(output_file, "w") as fout:
for line in fin:
text = line.strip()
if not text:
continue
tokens = tokenizer.encode(text, add_special_tokens=True)
# 截断到max_seq_len
if len(tokens) > 8192:
tokens = tokens[:8192]
record = {"tokens": tokens, "length": len(tokens)}
fout.write(json.dumps(record) + "\n")
⚠️ 踩坑预警:数据文件要放在共享存储(NFS)上,不然4张卡各读一份,存储压力大3倍。RDMA环境推荐用WekaIO或者Lustre。
Step 3:启动训练
bash
# 4卡数据并行训练
torchrun --nproc_per_node=4 \
--nnodes=1 \
--master_port=29500 \
train.py \
--config train_config.yaml \
--data_dir /shared/train_data.jsonl \
--checkpoint_dir /shared/checkpoints \
--log_dir /shared/logs
启动后看日志:
[Step 1] loss=10.82, lr=3e-4, throughput=620 tokens/s, mem=58.2GB/64GB
[Step 10] loss=9.45, lr=3e-4, throughput=620 tokens/s, mem=58.2GB/64GB
[Step 100] loss=7.12, lr=2.7e-4, throughput=620 tokens/s, mem=58.2GB/64GB
620 tokens/s,比预期低不少。下面开始调优。
Step 4:性能调优
调优一:开启FlashAttention-2
FlashAttention-2是ops-transformer仓库提供的高性能Attention算子,把Attention的显存占用从O(N²)降到O(N),同时减少HBM读写次数。
yaml
# train_config.yaml 加一行
model:
use_flash_attention: true # 开启FlashAttention-2
效果:
[Step 100] loss=7.12, throughput=980 tokens/s, mem=42.1GB/64GB
吞吐从620涨到980 tokens/s(+58%),显存从58.2GB降到42.1GB(-28%)。
调优二:调整梯度累积步数
原来gradient_accumulation_steps=8,每8步才做一次梯度同步。改成4步同步一次,减少hccl通信次数:
yaml
training:
gradient_accumulation_steps: 4
batch_size: 4 # 增大per-card batch size(显存够用)
效果:
[Step 100] throughput=1240 tokens/s, mem=55.8GB/64GB
吞吐从980涨到1240 tokens/s(+26%),显存还在64GB以内。
调优三:hccl通信优化
hccl的默认配置不一定最优。4卡HCCS互连场景下,手动指定拓扑可以避免走PCIe:
bash
# 设置hccl环境变量
export HCCL_CONNECT_TIMEOUT=1800 # 连接超时30分钟(初次连接慢)
export HCCL_BUFFSIZE=128 # 通信缓冲区128MB
export HCCL_WHITELIST_DISABLE=1 # 关闭白名单(调试用)
export ASCEND_RT_VISIBLE_DEVICES=0,1,2,3 # 只用4张卡
效果:
[Step 100] throughput=1470 tokens/s
调优结果汇总
| 优化阶段 | 吞吐 (tokens/s) | 显存 (GB) | 提升 |
|---|---|---|---|
| Baseline | 620 | 58.2 | - |
| + FlashAttention-2 | 980 | 42.1 | +58% |
| + 梯度累积+batch调整 | 1240 | 55.8 | +100% |
| + hccl通信优化 | 1470 | 55.8 | +137% |
1470 tokens/s × 2周 = 约100B tokens的训练量,满足7B模型预训练的需求。
完整训练曲线
训练2周后的Loss曲线:
| 训练步数 | Loss | 吞吐 | 备注 |
|---|---|---|---|
| 1,000 | 6.83 | 1470 | 初始快速下降 |
| 10,000 | 4.21 | 1470 | 学到基本语法 |
| 50,000 | 2.87 | 1470 | 学到常识知识 |
| 100,000 | 2.34 | 1470 | 收敛中 |
踩坑实录
坑1:梯度累积步数设太大,Loss会震荡
问题 :一开始设gradient_accumulation_steps=16,全局batch=128,Loss曲线震荡严重(一步跳0.3)。
原因:全局batch太大,每次更新的梯度是128个序列的平均,某个序列的噪声被放大了。
解决方案 :降低到gradient_accumulation_steps=4,全局batch=64,Loss曲线平稳下降。
坑2:RDMA通信偶尔超时
问题 :训练跑到第30步时,hccl报HCCL_INTERNAL_ERROR: timeout,进程挂掉。
原因:RDMA连接在网络抖动时会断开,默认超时时间(300s)不够。
解决方案:
bash
export HCCL_CONNECT_TIMEOUT=1800 # 连接超时改为30分钟
export HCCL_EXEC_TIMEOUT=1800 # 执行超时也改大
坑3:checkpoint保存时训练中断
问题:保存checkpoint时要写大量数据到磁盘(每个checkpoint约28GB),磁盘IO打满,训练进程卡住。
解决方案:用异步保存,训练和保存并行:
yaml
training:
checkpoint:
async_save: true # 异步保存
save_interval: 5000 # 每5000步保存一次
max_keep: 3 # 最多保留3个checkpoint
torchtitan-npu在CANN架构中的位置
torchtitan-npu位于CANN五层架构的应用层,依赖第1层的AscendCL和第2层的ATB:
应用层:torchtitan-npu
↓ 调用
第1层:AscendCL(PyTorch → CANN后端)
↓ 调用
第2层:ATB(Transformer加速库)、ops-transformer(FlashAttention)
↓ 调用
第4层:HCCL(分布式通信)、Runtime(NPU调度)
结尾
NPU上跑大模型预训练不再是梦想,torchtitan-npu让它变成了现实。从620到1470 tokens/s的调优过程告诉我,NPU上做训练的关键不在模型代码,在基础设施------FlashAttention减少计算量、hccl优化减少通信量、显存管理避免OOM。把这些基础设施配好,NPU的训练效率可以跟GPU持平,而硬件成本低30%。
如果你的团队也在评估NPU上做大模型训练,建议从torchtitan-npu的7B预训练开始,4卡就能跑,门槛不高。跑通7B再扩到70B,也就是加卡和调并行策略的事。