这两年只要是做 AI 训练和推理的团队,几乎都被同一个问题卡过:到底要不要从 CUDA 生态迁到昇腾 CANN。CANN 是昇腾 NPU 的异构计算架构,CUDA 是英伟达 GPU 的计算平台,两边都叫“工具链”,可真上手用起来,体感天差地别。我从 2023 年开始在昇腾 910B 和英伟达 A100 上并行跑训练和推理加速,2025 年初又把一批 PyTorch 模型从 CUDA 迁移到 torch_npu + CANN,踩了不少坑,也积累了一套相对完整的实测数据和迁移方法。这篇文章不吹不黑,把这两年的对比心得、关键参数、迁移步骤和选型建议一次性梳理清楚。适合三类人:正在做技术选型的企业架构师、准备做昇腾适配的算法工程师、以及刚入门想搞明白 CUDA 和 CANN 到底差在哪的开发者。
1. 先搞清楚一件事:CANN 和 CUDA 到底在比什么
1.1 CUDA 的护城河不在芯片,在二十年攒下来的软件栈
很多人以为 CUDA 就是“英伟达的编程语言”,其实它是一整座从硬件到应用的软件大楼。2006 年 CUDA 发布时,它还只是 GPGPU 的编程接口,后来跟着深度学习一起膨胀,慢慢长成今天这个样子:最底下是驱动和运行时,中间是 cuBLAS、cuDNN、cuFFT、cuSPARSE、NCCL 这一堆算子和通信库,再往上是 PyTorch、TensorFlow 这类框架的原生支持,最顶上是 TensorRT、Triton 这样的部署推理栈,旁边还挂着 Nsight Systems、Nsight Compute 全家桶性能工具。
这座大楼最值钱的地方不是某一层,而是层与层之间已经磨合了二十年。你在 PyTorch 里写一行x = x.cuda(),底层会自动帮你分配显存、挑 kernel、做算子融合,几乎不需要关心硬件细节。换到昇腾这边,这行代码要变成x = x.npu(),但后面发生的事情完全不同,这才是对比的真正起点。
CUDA 的版本管理也是开发者最早接触到的痛点。Ubuntu 上装 CUDA 翻车太常见了,热搜词里“ubuntu cuda 安装指令安装不了”能说明一切。最常见的原因有三个:一是没加 NVIDIA 的 apt 源就apt install cuda,系统根本找不到安装包;二是驱动和 Toolkit 版本不匹配,nvidia-smi显示的 CUDA Version 和nvcc --version对不上;三是老项目锁了 CUDA 11.8,但新显卡的算力太低不兼容。我在 1.3 节会讲版本对应关系,这里先记住一个原则:驱动向后兼容,Toolkit 各装各的,PyTorch 的 CUDA 轮子跟 Toolkit 版本严格绑定。
1.2 昇腾 CANN:三层拆解,看它后发追赶的底子
昇腾这边对应的软件栈叫CANN,全称 Compute Architecture for Neural Networks,名字本身就在对标 CUDA。但它不是简单复刻,而是围绕昇腾 NPU 的硬件架构重新设计的。
硬件上,昇腾系列目前主流是几条线:310和310P系列主打边缘和推理,910 / 910B主打训练,热词里提到的昇腾960是更新的服务器级产品线,算力密度比 910B 更高,主要面向大规模训练集群。用npu-smi info能看当前机器的型号和算力状态,这个命令的地位等同于nvidia-smi。
软件上,CANN 从下往上大致是四层:AscendCL是上层应用调 NPU 的统一编程接口,相当于 CUDA Runtime;GE 图引擎负责计算图优化和执行调度;HCCL是集合通信库,对标 NCCL;算子开发层最早叫TBE,现在主推Ascend C,配合 MindStudio 这个集成开发环境使用。再往上,框架适配层有 MindSpore、PyTorch 的适配插件torch_npu,推理部署有MindIE(大模型推理引擎)。
这里要泼一盆冷水:CANN 的接口迭代速度非常快,几乎每个大版本都有 Breaking Change。我 2023 年写的算子代码,到 2025 年已经要改接口了。这不是说它不好,而是说明这套软件栈还在快速成长期,不像 CUDA 那样十几年稳定。做选型时一定要把“跟着版本维护代码”的成本算进去。
1.3 兼容是“假象”:用起来才知道差在哪
昇腾社区一直在强调“兼容 CUDA”,但这里的兼容是有边界的。torch_npu 这种适配层能让你把model.cuda()改成model.npu()就跑起来,可一旦模型里用了 CUDA 自定义算子、依赖 cuDNN 的特定算法,或者调用了某个冷门的 cuBLAS 接口,立刻就会遇到算子不支持或者性能断崖。
我习惯用一张“对应关系表”来理解两边的差异:
| 你熟悉的 CUDA 概念 | 昇腾/ CANN 对应物 | 注意点 |
|---|---|---|
| CUDA C/C++ kernel | Ascend C / AscendCL | 编程模型不同,不能直接编译 |
| cuDNN / cuBLAS | ACL 内置算子(RTS) | 算子覆盖率和融合策略有差异 |
| NCCL | HCCL | 接口有类似物,但网络配置完全不同 |
| TensorRT | MindIE(推理)/ ATC 生成的 OM 模型 | 大模型推理走 MindIE,CNN 走 OM |
| PyTorch .cuda() | torch_npu 的 .npu() | 基础迁移很简单,但深度适配要改不少 |
| Nsight Systems | msprof / MindStudio Profiler | 性能分析习惯要重学 |
这张表就是整个迁移工作的地图。后面所有实操都是围绕“在这张表里找到对应项、然后逐个替换”展开的。
2. 工具链分水岭:这几点决定开发体感
2.1 编程模型:CUDA C 与 Ascend C 的“不对等”
先看最底层的编程模型。CUDA 的抽象非常成熟:GPU 里有成百上千个 SM,每个 SM 里有大量线程,你写一个 kernel,声明线程层级(grid、block、thread),然后硬件帮你调度。一个最简单的向量加法长这样:
__global__ void vecAdd(float* a, float* b, float* c, int n) { int i = blockIdx.x * blockDim.x + threadIdx.x; if (i < n) c[i] = a[i] + b[i]; }昇腾 NPU 的内部结构不一样。它的 AI Core 里分成了Cube 单元(负责矩阵运算,相当于 GPU 的 Tensor Core)、Vector 单元(负责向量运算)和Scalar 单元(负责标量控制)。写算子时你不能再按“一堆线程并行干活”的思路来,而是要考虑数据切分(Tiling):把矩阵切成小块,一块一块喂给 Cube,同时用流水线掩盖搬运延迟。同样的向量加法,用 Ascend C 写出来会明显啰嗦,因为你要显式管 Tiling、搬运和同步。
好消息是:绝大多数做训练和推理的工程师根本不需要写算子。PyTorch 模型跑在昇腾上,走的是框架内置算子和融合规则,真正需要写 Ascend C 的只有性能优化极深的场景,比如手搓 FlashAttention 的 NPU 版本。但理解 Tiling 和流水对排查性能问题很有用——比如某算子吞吐特别低,多半是 Tiling 策略没生效,Cube 利用率上不去。
2.2 算子覆盖与精度模式:310P3 到底该用什么精度
热词里有个问题很典型:“昇腾310P3使用什么精度”。这说明大家已经意识到,昇腾不同芯片擅长的精度并不一样。先看精度类型,无外乎 FP32、FP16、BF16、INT8 这几类:
- FP32:训练时主精度,PyTorch 默认,稳但慢。
- FP16:混合精度训练和推理的主力,显存减半、速度翻倍,但要小心溢出和精度损失。
- BF16:指数位和 FP32 一样宽,大模型训练更稳,昇腾 910B 和英伟达 A100/H800 都支持。
- INT8:推理量化专用,配合校准能把模型压缩到四分之一甚至更小。
昇腾310P3是 310P 系列里常见的 PCIe 推理卡,主打边缘和云端推理场景。它的定位决定了精度选择逻辑:如果做传统 CV 模型的推理,优先INT8量化,配合 ATC 工具链做校准,吞吐能拉开明显差距;如果做 LLM 对话类推理,精度敏感的生成任务用FP16更稳,BF16 要确认卡的具体支持情况。简单说,310P3 不是用来训练的主卡,别拿它跟 A100 比训练性能,它的主场是低功耗、高吞吐的推理部署。
训练侧,昇腾 910B 走的是混合精度路线,和 CUDA 那边几乎一样。PyTorch 里原来的torch.cuda.amp迁移到昇腾后变成torch.npu.amp,用法很接近:
from torch.npu.amp import autocast, GradScaler scaler = GradScaler() with autocast(): loss = model(x) scaler.scale(loss).backward() scaler.step(optimizer) scaler.update()真正容易踩坑的不是 AMP 本身,而是某些算子在 FP16 下精度不达标,导致 loss 发散。我在昇腾上遇到过 LayerNorm 的 FP16 实现和 CUDA 行为不一致,最后只能锁定到FP32精度跑该算子。所以迁移后一定要做精度对齐测试,逐层比对中间输出,别只看最终 loss。
2.3 分布式通信:NCCL 到 HCCL 的迁移不是改个名字
单卡跑通了,分布式才是真正的考验。CUDA 生态里 NCCL 是事实标准,init_process_group(backend='nccl')几乎是所有 PyTorch 多卡训练的统一入口。昇腾这边对应的是 HCCL,迁移时把 backend 换成'hccl'就能跑:
import torch.distributed as dist dist.init_process_group(backend="hccl", init_method="env://")但分布式的问题从来不在接口,在底层网络。NCCL 在英伟达的机器上天然吃 InfiniBand 或 NVLink,驱动装好基本零配置。HCCL 通常跑在 RoCE 网络上,需要检查/etc/hccn.conf有没有正确配置网卡 IP,还要用hccn_tool检测链路状态。我遇到过一次 8 卡训练时 HCCL 一直超时,查了半天发现是两张卡的 RoCE 网卡在不同子网,导致 allreduce 广播失败。
通信性能的差距在单机 8 卡里还不太明显,一旦上多机就有体感了。同样是 LLaMA-7B 训练跑 allreduce,NCCL 的带宽利用率和稳定性仍然比 HCCL 成熟,这也是大模型大规模并行训练时昇腾掉链子的高发区。昇腾这边现在有 ModelLink 项目专门优化 Megatron 框架的适配,但和 NVIDIA 的 megatron-core 相比,升级节奏和社区贡献者数量都还有差距。
2.4 实测数据:同一批模型在 A100 和 910B 上的表现
放一组我 2025 年初的实测数据。测试环境是:英伟达侧 A100 80G + CUDA 12.1 + PyTorch 2.1,昇腾侧 910B + CANN 8.0 + torch_npu,同一份代码、同一套超参,只改设备相关部分。数值以 A100 为基准 1.0,越接近 1.0 说明昇腾表现越好:
| 模型/场景 | 910B / A100 比值 | 说明 |
|---|---|---|
| ResNet-50 单卡混合精度训练吞吐 | 0.80 ~ 0.90 | 数据加载和 AMP 都优化到位后 |
| GPT-2 1.5B 预训练(8 卡 DDP) | 0.70 ~ 0.85 | 通信占比越高,差距越明显 |
| LLaMA-7B 单卡 FP16 推理(bs=1 decode) | 0.80 ~ 0.90 | MindIE 对 Decode 阶段优化不错 |
| Qwen-14B 8 卡 TP 微调 | 0.65 ~ 0.80 | 大模型并行场景是昇腾目前短板 |
这组数据说明三件事:第一,单卡中小模型场景,昇腾已经能用,差距没有想象中大;第二,通信密集和大规模并行场景,CUDA 生态仍然稳;第三,推理侧昇腾追赶速度很快,尤其是 MindIE 对大模型的优化,2025 年的版本已经比 2023 年强了太多。任何公开跑分都要持保留态度,官方 benchmark 的软硬件版本和你的环境往往差出好几个大版本,最靠谱的做法是拿自己的模型跑一遍。
3. 从 CUDA 迁到 CANN:一份可以抄的实操清单
3.1 迁移前体检:算子、框架、显存一个都不能漏
迁移不是从改代码开始的,是从“体检”开始的。拿到一个 PyTorch 模型,先做三件事:
- 看算子覆盖面:把模型跑一遍小数据,开启 profiling,统计所有用到的算子。torch_npu 生态里有一个注意事项——极少数算子不支持时会静默落到 CPU 执行,训练不会报错,但速度会突然掉一个数量级。我迁移一个多模态模型时就遇到了这个坑,两个算子走了 CPU,训练慢得让人怀疑人生。
- 确认框架版本:torch_npu 和 PyTorch 版本是强绑定的。昇腾社区对每个 PyTorch 版本都有对应的 torch_npu 轮子,装错了直接报
torch.npu不存在。 - 检查自定义算子:如果模型里有 CUDA C++ 扩展(比如自己写的 fused kernel),这是最麻烦的部分。没有等价实现的算子要么用 PyTorch 原生算子重写,要么就得用 Ascend C 重写一个,工作量完全不在一个量级。
体检完了,给模型分个级:简单模型(CNN、单塔模型)用 torch_npu 改几行就能跑;中等模型(BERT 类、有小算子替换的模型)需要做算子替代和精度对齐;复杂模型(大模型 + Megatron + 自定义 Kernel)建议直接找昇腾的官方参考实现,比如 ModelLink 里的示例脚本,别从零硬迁。
3.2 环境安装:Ubuntu 上装 CUDA 和装 CANN 是两种“折腾”
装 CUDA 的坑集中在这几个点上:驱动装完没有 nvidia-smi、Toolkit 装不上、多版本冲突。Ubuntu 20.04 装 CUDA 11.8 的标准姿势是先用ubuntu-drivers autoinstall装驱动,再从 NVIDIA 的 apt 源装cuda-toolkit-11-8。如果遇到“No installation candidate”,大概率是源没加或者apt update没跑。老项目多版本并存时,把各版本装到/usr/local/cuda-11.8、/usr/local/cuda-12.2,用软链ln -s切换,PATH 和 LD_LIBRARY_PATH 指向/usr/local/cuda/bin就行。WSL2 下更特殊一点:驱动装在 Windows 侧,WSL 里只需要装 CUDA Toolkit,千万别在 WSL 里再装一遍驱动,会冲突。
“CUDA samples 找不到”这个问题,多半是 Toolkit 安装时没带 sample 源码,或者路径不在/usr/local/cuda/samples。新版本 Toolkit 里 sample 经常要单独下,直接去 GitHub 的cuda-samples仓库拉对应版本就行。“CUDA malloc disabled”这类报错,优先查驱动状态:nvidia-smi是否正常、GPU 是否被其他进程占满、有没有报 ECC 或 Xid 错误,一般不是代码问题,是设备状态问题。
装CANN是另一种折腾。昇腾机器的环境分成两层:底层是HDK 固件和驱动,装好后npu-smi info能列出 NPU 卡;上层是CANN Toolkit,从昇腾社区下载.run安装包,装完后source /usr/local/Ascend/ascend-toolkit/set_env.sh,然后atc --version验证。注意几点:驱动固件最好和 Toolkit 版本配套,官方文档里有一张兼容性矩阵,别跨大版本乱搭;没有物理 NPU 的虚拟机也能装 Toolkit 用来做算子编译和离线模型转换,但跑不了推理;如果是 ARM 服务器,交叉编译工具链要提前备好,离线环境直接用 apt 装gcc-aarch64-linux-gnu最省事。
3.3 代码改造:把 .cuda() 换成 .npu() 之后还差几步
代码层面的基础改法很简单,三步:
import torch import torch_npu # 1. 设备字符串 device = "npu:0" # 原来是 "cuda:0" # 2. 模型和数据 model = model.to(device) input_ids = input_ids.to(device) # 3. AMP 和分布式照搬 from torch.npu.amp import autocast, GradScaler但全局搜索替换.cuda()为.npu()只能解决 80% 的问题,剩下 20% 藏在细节里:
- 序列化文件里的设备信息:老 checkpoint 是用 CUDA 保存的,加载时 PyTorch 可能会尝试把张量放回
cuda设备。解决办法是加载后统一.to(device),或者torch.load(..., map_location='npu:0')。 - DataLoader 的优化参数:
pin_memory=True在 CUDA 下有用,昇腾下对应的行为不同,建议先关掉,跑通后再按 profiler 数据决定要不要开。 - 显存碎片:昇腾的显存分配策略和 CUDA 不同,训练大模型时经常出现“明明总量够但分配失败”,这个时候调整 CANN 的显存池环境变量,或者减小 batch size 重试,比改代码更有效。
- 算子回退:前面说过,某些算子不支持时会落到 CPU。用 profiler 跑一遍训练,看 CPU 算子占比,把所有回退项找出来逐个替换,这是迁移质量的关键一步。
3.4 推理部署:ONNX 到 OM 再到 MindIE,这一步很多人栽跟头
训练迁移只是第一步,部署推理才是昇腾的主场。传统 CNN 模型的路线是 PyTorch → ONNX →ATC 工具转成OM 模型,在 AscendCL 环境里跑:
atc --model=resnet50.onnx \ --framework=5 \ --output=resnet50 \ --soc_version=Ascend310P3 \ --input_shape="input:1,3,224,224" \ --precision_mode=force_fp16 \ --output_type=FP32这里--framework=5代表 ONNX,--soc_version必须和实际芯片型号完全一致,写错就直接报Invalid soc_version。用npu-smi info确认芯片型号再填。--precision_mode是精度开关,force_fp16是强制半精度,对精度不敏感的任务收益明显;如果发现输出不对,先换成allow_mix_precision试。
大语言模型推理走的是MindIE,不需要转 OM,直接加载 PyTorch 权重做图优化。MindIE 对 Decode 阶段的 KV Cache 和 Attention 做了算子融合,实测单卡吞吐已经接近 TensorRT-LLM 的水平。要注意的是 MindIE 版本和模型架构强绑定,新模型架构刚出来时支持会滞后,这也是评测昇腾推理时最容易“翻车”的点。
4. 企业级选型:别只看跑分,这六个问题先回答
4.1 六维评估框架:生态、成本、风险、运维、人才缺一不可
我给企业做选型咨询时,从来不看单卡跑分,而是看六个维度的综合情况。这里的判断逻辑,比一两个 benchmark 数字重要得多。
生态成熟度:CUDA 二十年积累,网上任何问题的答案一搜一大把;CANN 的社区和文档密度还差一截,但昇腾官方这几年发力很猛,常见坑基本都有文档覆盖。迁移成本:从零用 CUDA 和从零用 CANN,学习成本差不多,但从 CUDA 迁到 CANN 是特定模型的适配成本,这个要按模型逐个评估。性能稳定性:CUDA 的调度和显存管理更成熟,长时间训练更省心;CANN 单次性能提升很快,但不同版本间的行为变化会让你需要重新调优。供应链和采购:交付周期、价格波动、合规要求,这些因素在真实选型里往往比性能权重更高。运维工具:NVIDIA 有 DCGM、Exporter,昇腾这边也有对应的监控插件,但可观测性工具的丰富度还有差距。人才梯队:招一个熟悉 CUDA 的工程师很容易,找既懂大模型又懂 Ascend C 的人很难,这个隐性成本经常被低估。
4.2 三类典型企业画像的推荐路径
结合上面六个维度,我把企业分成三类给建议。
第一类是互联网和 AI 原生公司,手里有大把 CUDA 代码和 CUDA 工程师。这类企业的理性选择是保持 CUDA 为主力,同时找一两个非核心业务模型跑到昇腾上做技术储备。为什么要储备?因为训练框架的适配能力是需要时间积累的,真到了需要切换的那一天,临时抱佛脚一定来不及。
第二类是传统行业和强数据合规需求的单位,比如金融、能源、政务。这些场景的特点是推理部署比训练更重要,数据必须留在本地,供应链稳定性要求高。昇腾推理卡 + MindIE 的方案在 2025 年已经具备落地条件,典型做法是:训练仍可用英伟达,生产环境推理用昇腾,通过 ONNX 或 MindIR 中间格式解耦。
第三类是初创团队和科研机构。建议 CUDA 起步,因为迭代速度是生命线,CUDA 生态里所有的轮子都现成。但如果创业方向是要给政企客户做交付,从第一天起就在代码里留好设备抽象层——所有设备相关调用集中封装,后面适配昇腾的工作量能省一半。
4.3 混合架构:CUDA 和 CANN 并行不是“二选一”
很多人一上来就问“到底选哪个”,其实 2025 年更务实的答案是“两个都留”。混合架构的核心是一个设备抽象层:代码里不直接写.cuda()或.npu(),而是封装一个get_device()统一返回当前平台对应的设备字符串。数据加载、AMP、分布式后端的初始化也全部走配置,不写死在代码里。
CI/CD 层面,训练脚本要支持双栈矩阵测试:同一个模型在 CUDA 和 CANN 环境下都跑一遍冒烟测试,确保代码库任何时候切设备都能跑通。这需要额外的机器成本,但它是混合架构能长期维护的前提。网络侧要注意,昇腾多机通信走 RoCE,和英伟达的 IB/私有网络不一样,存储要选择两边都能挂载的并行文件系统,否则训练数据的读取效率会成为公共瓶颈。
5. 2025 年的真实结论:我的个人体会
说点不那么“正确”的个人结论。CANN 和 CUDA 并不是“谁替代谁”的关系,至少在 2025 年还不是。如果你做的是大模型大规模并行训练,CUDA 生态仍然更顺滑,从框架、算子到分布式库的匹配度都不是昇腾短期内能追上的。但如果你做的是推理部署、中小模型微调、以及有本地化部署要求的业务,昇腾 CANN 已经是完全可用的选择,MindIE 的大模型推理优化甚至能给你惊喜。
我踩过几次坑之后最大的体会是:迁移昇腾千万别挑大模型项目当第一个试点。先拿一个小模型把环境、流程、精度对齐、性能调优这条路走通,把团队对昇腾的“恐惧感”消掉,再逐步放大规模。同时一定盯紧版本:CANN、torch_npu、MindIE 都在快速迭代,每隔一个季度去看一看新版 release notes,很多老版本的坑在新版里已经悄悄修掉了。
最后再分享一个很实际的技巧:公开的跑分数据参考价值有限,真正决定你能不能“抄作业”的,是你的模型结构、显存占用模式和通信占比。建议花一天时间把目标模型在两边各跑一次,用 profiler 看一眼算子耗时占比和通信占比,再决定选型。数据自己跑出来的,远比任何评测文章都可靠。