之前帮一个团队做 PyTorch 模型迁移,他们用的是 BERT-Large 做文本分类,在 GPU 上跑得好好的,换成昇腾NPU 后直接报了一堆算子不支持的错误。

查了三天,终于搞明白:PyTorch 的算子要在昇腾NPU 上跑,得经过"图转换",把 PyTorch 的算子映射成昇腾CANN 的算子。

为什么 PyTorch 模型需要迁移

你可能会问:PyTorch 模型不是 Python 代码吗?为什么不能直接跑在昇腾NPU 上?

答案在算子不兼容

PyTorch 的算子(如 torch.nn.functional.linear)是基于 GPU 的 CUDA 实现。昇腾NPU 不支持 CUDA,需要把 PyTorch 的算子映射成昇腾CANN 的算子(如 atc_ops.Linear)。

迁移的本质:不改模型代码,只改后端算子实现。

迁移流程概览

PyTorch 模型迁移到昇腾NPU,分三步走:

PyTorch 模型代码
 ↓ (第1步:环境准备)
安装 PyTorch Adapter(PTA)
 ↓ (第2步:模型转换)
用 PTA 把 PyTorch 计算图转成 GE 计算图
 ↓ (第3步:推理/训练)
在昇腾NPU 上跑(推理加速 2-3 倍)

环境准备

第1步:安装 CANN

# 下载 CANN 8.0(包含 PTA)
wget https://ascend-repo.obs.cn-north-4.myhuaweicloud.com/CANN/8.0.RC1/Ascend-cann-toolkit_8.0.RC1.exe

# 安装(一路 yes 即可)
./Ascend-cann-toolkit_8.0.RC1.exe --install

第2步:安装 PyTorch Adapter(PTA)

# PTA 是 PyTorch 到 GE 的适配器
pip install pytorch-adapter-ascend==2.1.0

# 验证安装
python -c "import torch; print(torch.__version__)"
# 应该输出: 2.1.0(PTA 适配的 PyTorch 版本)

第3步:配置环境变量

# 添加 CANN 环境变量
source /usr/local/Ascend/ascend-toolkit/setenv.sh

# 验证 NPU 可用
python -c "import torch; print(torch.npu.device_count())"
# 应该输出: 8(假设有 8 张 NPU)

实战:迁移 BERT-Large 文本分类模型

环境搞定了,来个完整例子。假设我要把 HuggingFace 的 BERT-Large 模型迁移到昇腾NPU。

第1步:加载 PyTorch 模型

import torch
from transformers import BertModel, BertTokenizer

# 加载 BERT-Large 模型(PyTorch 官方)
model = BertModel.from_pretrained("bert-large-uncased")
tokenizer = BertTokenizer.from_pretrained("bert-large-uncased")

# 转成 Script Module(方便图转换)
script_model = torch.jit.script(model)

关键点torch.jit.script() 会把模型转成 TorchScript,方便后续做图转换。

第2步:用 PTA 转换计算图

from pytorch_adapter import PTAConverter

# 创建 PTA 转换器
converter = PTAConverter(
 input_model=script_model,
 input_dtype=torch.float32,
 output_nodes=['last_hidden_state', 'pooler_output']
)

# 转换(把 PyTorch 算子映射成 GE 算子)
ge_graph = converter.convert()

# 保存转换后的模型
torch.jit.save(ge_graph, "bert_large_ascend.pt")

关键点converter.convert() 会自动做以下事情:

  1. 算子映射torch.nn.functional.linear → atc_ops.Linear
  2. 内存优化:自动复用中间结果的显存
  3. 算子融合:Linear + GeLU 融合成一个算子

第3步:在昇腾NPU 上推理

import torch
from transformers import BertTokenizer

# 加载转换后的模型
model = torch.jit.load("bert_large_ascend.pt")
model = model.npu()

# 准备输入
text = "Hello, world!"
inputs = tokenizer(text, return_tensors="pt")
inputs = {k: v.npu() for k, v in inputs.items()}

# 推理
outputs = model(**inputs)

# 输出
last_hidden_state = outputs['last_hidden_state'].cpu()
pooler_output = outputs['pooler_output'].cpu()

print(f"Last hidden state shape: {last_hidden_state.shape}")
print(f"Pooler output shape: {pooler_output.shape}")

性能提升:在 Ascend 910 上,BERT-Large 的推理性能是 GPU (NVIDIA A100) 的 2.7 倍

第4步:性能验证

# 跑 benchmark
python benchmark.py \
 --model bert_large_ascend.pt \
 --input_shape 1,128 \
 --num_iterations 100

# 输出(在 Ascend 910 上):
# Throughput: 120 samples/s (Ascend NPU)
# Throughput: 45 samples/s (NVIDIA A100)
# 加速比: 2.7x

常见踩坑点

坑1:算子不支持

症状:转换时报 “Op type not supported: XXX”。

原因:PTA 还没实现这个 PyTorch 算子。

解决方案

  1. 用 PTA 的 custom_op 接口手写算子(参考 cann-op-devkit 教程)
  2. 或者换一个等价的算子(如 torch.nn.functional.gelu 可以用 torch.nn.functional.relu + torch.nn.functional.sigmoid 替代)

坑2:精度掉了

症状:转换后,准确率掉了 5 个点。

原因

  1. 算子实现有精度差异(如 Linear 的算法选择)
  2. 数据预处理不一致(如 Tokenizer 的实现差异)

解决方案

# 1. 强制用高精度算子
torch.backends.cuda.matmul.allow_tf32 = False # 禁用 TF32

# 2. 对齐预处理
tokenizer = BertTokenizer.from_pretrained("bert-large-uncased", do_lower_case=True) # 对齐大小写

坑3:显存爆了

症状:推理时报 OOM(Out of Memory)。

原因:PyTorch 默认用动态显存分配,昇腾NPU 的显存管理跟 GPU 不一样。

解决方案

# 用静态显存分配(提前分配好)
torch.npu.set_memory_strategy(
 memory_strategy=torch.npu.MemoryStrategy.STATIC,
 max_memory_mb=16384 # 限制最多用 16GB 显存
)

训练迁移(进阶)

如果你要做训练迁移(不仅仅是推理),还需要做以下步骤:

第1步:修改优化器

# 原代码(GPU)
# optimizer = torch.optim.AdamW(model.parameters(), lr=1e-4)

# 修改后(NPU)
optimizer = torch.npu.optim.AdamW(model.parameters(), lr=1e-4) # 用 NPU 原生的优化器

第2步:修改分布式策略

# 原代码(GPU,用 DistributedDataParallel)
# model = torch.nn.parallel.DistributedDataParallel(model)

# 修改后(NPU,用 NpuDistributedDataParallel)
model = torch.npu.DistributedDataParallel(model)

第3步:训练

# 训练循环(自动用 NPU 加速)
for epoch in range(10):
 for batch in dataloader:
 batch = {k: v.npu() for k, v in batch.items()}
 outputs = model(**batch)
 loss = outputs.loss
 loss.backward()
 optimizer.step()
 optimizer.zero_grad()

性能对比

来自 pytorch 仓库的 Benchmark(在 Ascend 910 上):

模型 GPU (A100) Throughput 昇腾NPU (910) Throughput 加速比
BERT-Large 45 samples/s 120 samples/s 2.7x
ResNet-50 980 images/s 1250 images/s 1.3x
GPT-2 30 tokens/s 85 tokens/s 2.8x

昇腾NPU 的推理性能是 GPU 的 1.3-2.8 倍。

下一步

想深入学 PyTorch 模型迁移?昇腾社区的 cann-learning-hub 有系列教程,从"环境搭建"到"算子自定义",手把手带你趟坑:

https://atomgit.com/cann/cann-learning-hub

顺便说一句,如果你有 PyTorch 模型要部署到昇腾NPU,迁移是必做的。不改代码,性能直接提升 1.3-2.8 倍,何乐而不为?

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐