---
tags: [CANN]
doc_type: knowledge-note
返回目录: "[[计算机系/昇腾与CANN/大模型与算子专题学习导航|大模型与算子专题学习导航]]"
上一篇: "[[计算机系/昇腾与CANN/异构计算基础与CANN架构整理|异构计算基础与CANN架构整理]]"
---

# CANN软件栈与Torch NPU实践

CANN 连接上层 AI 框架与昇腾硬件。理解它可以从一次 `torch.matmul` 出发：输入怎样成为 Tensor，框架怎样选择实现，运行时怎样提交任务，NPU 怎样完成计算。

Host/Device、SPMD 与内存层级见 [异构计算基础与 CANN 架构](异构计算基础与CANN架构整理.md)；本篇集中解释 Tensor 契约、组件边界和框架侧实践。

## 1. 计算图与算子

![计算图和基本算子](.assets/CANN推理专题/stack-13.png)

图中的节点描述运算，边表达 Tensor 和依赖。`Conv1`、`Conv2` 可以是不同节点的名字，但都属于 Conv2D 类型。同一种逻辑运算可以根据输入类型、布局与硬件选择不同实现。

静态计算图常用有向无环结构表达一次前向中的数据依赖；带控制流的程序还可能包含循环、分支和子图，不能把所有程序都简单等同一张固定 DAG。

## 2. Tensor：值与描述一起构成接口

| 属性 | 作用 | 例子 |
| --- | --- | --- |
| shape | 逻辑维度大小 | `(2,3)` |
| dtype | 每个元素的解释方式 | float16、float32、int8 |
| device | 所在设备 | CPU、NPU |
| stride/layout | 索引如何映射为地址 | 行主序、NCHW、NHWC、分形布局 |
| name | 图或框架内部标识 | 输入节点名称；并非所有 Tensor 都要求全局唯一字符串名 |

标量的 shape 是 `()`，零个维度但有一个元素；`(0,)` 表示长度为零的一维张量。`[1,2,3]` 为 `(3,)`，`[[1,2],[3,4]]` 为 `(2,2)`。

shape `(5,6)` 中 `axis=0` 指长度为 5 的维，沿它求和会消去这维，得到长度为 6 的结果。`axis=-1` 表示最后一维，而不是“把元素倒过来处理”。

![NCHW 与 NHWC 的存储顺序](.assets/CANN推理专题/stack-17.png)

N、C、H、W 分别代表批量、通道、高和宽。连续 NCHW 中 W 通常变化最快；连续 NHWC 中 C 通常变化最快。`reshape` 改变解释方式并不自动等价于真正的维度转置，非连续视图还要考虑 stride。矩阵分形格式见 [矩阵算子与数据布局](Ascend%20C矩阵算子与数据布局.md)。

例如连续 NHWC 张量 `(4,20,20,3)` 表示 4 张高宽均为 20、各有 3 个通道的图像。四重循环分别遍历 `n,h,w,c`，元素的线性偏移为 `((n*20+h)*20+w)*3+c`，整个张量包含 4800 个元素。这个例子把 shape、轴和存储顺序联系起来：相同的一串数，使用不同的维度解释会得到不同含义。

## 3. 软件栈各层做什么

![CANN 软件栈](.assets/CANN推理专题/stack-33.png)

| 层 | 职责 |
| --- | --- |
| 框架适配 | torch_npu 等把框架操作连接到 NPU 后端 |
| 算子与领域库 | 提供数学、神经网络、视觉、Transformer、BLAS/FFT 等实现 |
| 通信库 | HCCL 等提供集合通信，相关传输库提供跨设备数据交换能力 |
| 图引擎 GE | 图表达、编译优化、资源规划与执行 |
| 算子编程与编译 | Ascend C、PyPTO 等表达计算，毕昇编译器生成目标代码 |
| Runtime | 设备、内存、stream、event 与任务提交管理 |
| Driver | 与设备及操作系统交互的底层支撑 |

HCCL 的 AllReduce 与 HIXL 等传输能力处于不同抽象层，不能只因都“搬数据”就认为接口可以互换。库名称、交付包和原生格式支持需要与使用版本对应。

![一次框架矩阵乘的执行链](.assets/CANN推理专题/stack-41.png)

`a.npu()` 将 Tensor 放到 NPU；矩阵乘通过后端选择适合的算子实现，再由运行时提交。普通 Eager 调用不一定经过 GE 整图编译；图模式才会按所选后端捕获、转换或编译。也不是每次调用都重新编译 Kernel，很多实现已预编译或缓存。

这里的“搬到 NPU”是移动到设备内存，不是上传到云端；NPU 可以就在本机。是否能异步传输以及与计算重叠，取决于接口参数、主机内存条件和同步安排，不能仅凭 `.npu()` 这个写法判断。

![CANN 与 CUDA 生态的功能对照](.assets/CANN推理专题/stack-42.png)

这类对照用于找到学习入口，不代表 API、内存模型和工具行为一一等价。把 `.cuda()` 换成 `.npu()` 可以覆盖部分普通 Tensor 代码，但 CUDA 自定义算子、专用库、图捕获和分布式通信仍可能需要适配。

## 4. 编程抽象与硬件代际

Ascend C 更直接暴露内存、搬运、计算和同步；PyPTO 的 Tensor 层侧重计算表达，Tile 层开放更细的分块、缓冲和调度控制。越高层的抽象通常越容易组合，越低层则提供更多控制空间，但性能仍由具体实现与输入决定。

A2/A3 与 Ascend 950 的可用路径不同。950 的寄存器矢量计算、SIMT 以及部分 AIC/AIV 直连路径，不能套用到 A2/A3 的 GM 中转方案。具体核数、容量和类型支持以目标型号为准，避免将产品族名称当作一套固定规格。

超节点通过高速互联扩展协作范围，但远端访问仍有拓扑和通信成本。互联语境中的 UB（Unified Bus）与 AIV 片上 UB（Unified Buffer）是不同概念；GB/s 和 Gb/s 也相差 8 倍，比较单双向带宽时必须统一口径。

## 5. 最小 Torch NPU 运算

```python
import torch
import torch_npu

assert torch.npu.is_available()
print(torch.npu.device_count())
print(torch.npu.get_device_name(0))

x = torch.full((3, 3), 2.0, dtype=torch.float32)
y = torch.full((3, 3), 3.0, dtype=torch.float32)
z = x.npu().square() + y.npu()
torch.testing.assert_close(z.cpu(), x.square() + y)
```

这里期望每个输出为 7，因为输入明确设为 x=2、y=3。环境检查失败时，先检查驱动、设备可见性、CANN 与 torch_npu 版本和初始化环境，不要先修改计算公式。

## 6. 如何公平比较 CPU 与 NPU

先区分“纯计算耗时”和“含数据搬运的端到端耗时”。纯计算测试应先把数据放到目标设备，预热后再计时，并等待异步任务完成。

```python
from time import perf_counter

def elapsed_npu(fn, repeats=20):
    for _ in range(5):
        fn()
    torch.npu.synchronize()
    start = perf_counter()
    for _ in range(repeats):
        result = fn()
    torch.npu.synchronize()
    return (perf_counter() - start) / repeats, result

n = 2048
a = torch.randn(n, n)
b = torch.randn(n, n)
a_npu, b_npu = a.npu(), b.npu()
seconds, result = elapsed_npu(lambda: a_npu @ b_npu)
```

CPU 计时不需要 NPU 同步，但应明确线程数、dtype 和相同数学任务。输出按适当数值容差比较，不应因为执行顺序不同就要求所有浮点结果逐位相同。

建议比较 N=128、512、2048，观察小规模启动开销与大规模计算吞吐的变化；再用 B=8、32、128 的 `torch.bmm` 对比逐矩阵循环，检查批处理是否减少调用与调度开销。

图像高斯模糊可以用 5×5 卷积核实现。RGB 图像逐通道模糊要正确设置分组卷积或通道布局，同时固定边界 padding。卷积规模较小、数据搬运占比高时，加速比未必像大矩阵乘一样明显。

入门实践仓：[CANN Learning Hub](https://gitcode.com/cann/cann-learning-hub/tree/master/quick_start)。
