CANN asnumpy:在 NPU 上跑 NumPy 工作负载

文章目录
前言
你写了几年科学计算代码,全是 import numpy as np,张量创建、矩阵乘法、随机数生成——这些操作在你的 CPU 上跑得好好的,突然老板说"搬到昇腾 NPU 上试试"。你搜了一圈,发现昇腾 CANN 生态里有个叫 asnumpy 的东西,号称"在 NPU 上跑 NumPy 工作负载"。
别被名字骗了——asnumpy 不是 NumPy。它是一个兼容层,让你不用重写代码,就能把原来在 CPU 上跑的 NumPy 风格操作搬到昇腾 NPU 上执行。打个比方:你在一个城市住了十年,每个路口都熟,现在换了个新城市。asnumpy 就是那个给你画了一份"老路名→新路名"对照表的向导——你不需要重新学认路,跟着走就行。
为什么需要 NumPy 兼容层?
科学计算领域的代码资产是巨大的。从天文学的星系模拟到生物信息学的基因测序,从金融工程的期权定价到物理仿真的流体计算,全球数百万研究人员用 NumPy 积累了十几年的代码。这些代码不是想扔就能扔的。
问题来了:昇腾 NPU 的算力很强,但生态里缺一个让科学计算用户无门槛进入的入口。用户不可能为了用 NPU 去学 PyTorch、学 MindSpore——他们只是想把手头的计算跑快一点。asnumpy 就是为这个场景设计的:不要求用户改变编程习惯,不要求重写算法逻辑,只是把执行后端从 CPU 换成 NPU。
这就好像你有一把瑞士军刀,现在告诉你"换个刀片能用更大力气",但用法完全不用变。asnumpy 做的就是这件事。
它在 CANN 五层架构里的什么位置?
昇腾 CANN 的五层架构从下到上分别是:基础层、执行层、编译层、服务层、语言层。asnumpy 不在这五层里面——它属于五层之上的应用链,也就是第三层的便捷工具层。
为什么放在这个位置?让我们看看其他几层在干什么:
- 基础层和执行层负责最底层的硬件抽象和算子执行
- 编译层做图优化和调度
- 服务层提供推理服务框架
- 语言层提供 Python、C++ 等编程接口
asnumpy 做的事情完全不同:它是面向终端用户的胶水层。它不自己实现算子,也不做编译优化,它做的是把 NumPy 风格的 API 调用翻译成底层算子库的调用。比如你要算矩阵乘,asnumpy 会在底下帮你调 ops-blas;要算三角函数、指数运算,它会去调 ops-math。就像你点外卖,你只管在 app 上点"宫保鸡丁",后厨是哪个师傅炒的你不用管——asnumpy 就是那个外卖 app,ops-math 和 ops-blas 是后厨。
核心能力:覆盖哪些 NumPy API?
asnumpy 目前支持的 NumPy API 可以分成几大类,每一类都对应底层的算子仓库:
ndarray 创建与管理
import asnumpy as anp
# 在 NPU 显存上创建各种类型的数组
a = anp.zeros((1024, 1024), dtype=anp.float32)
b = anp.ones((512, 512), dtype=anp.float16)
c = anp.full((256, 256), 3.14159)
d = anp.eye(1024) # 单位矩阵
# 从 Python 列表或 NumPy 数组导入
data = [[1, 2, 3], [4, 5, 6]]
e = anp.array(data, dtype=anp.float32)
# 查看张量形状和设备
print(f"Shape: {e.shape}, Dtype: {e.dtype}")
区别在于:这些张量住在 NPU 显存上,不在系统内存里。整个计算过程不需要 CPU 介入。
数学运算(底层调用 ops-math)
# 基础数学函数 - 底层走 ops-math
x = anp.random.randn(1000, 1000)
y = anp.sin(x) # 正弦
z = anp.cos(x) # 余弦
w = anp.exp(z) # 指数
v = anp.log(w) # 对数
u = anp.sqrt(v) # 平方根
# 元素级运算
result = anp.power(x, 2) # x^2
result = anp.abs(x) # 绝对值
result = anp.clip(x, -1, 1) # 截断
ops-math 提供了 conversion 类(张量形态变换)、math 类(基础数学运算)、random 类(随机数生成)。asnumpy 里的 anp.sin()、anp.exp()、anp.random.randn() 这些操作,最终都走到这里。
线性代数(底层调用 ops-blas)
# 矩阵运算 - 底层走 ops-blas
A = anp.random.randn(2048, 1024)
B = anp.random.randn(1024, 512)
C = anp.dot(A, B) # 矩阵乘法
D = anp.matmul(A, B) # 另一种写法
# 向量运算
v1 = anp.random.randn(512)
v2 = anp.random.randn(512)
dot_product = anp.dot(v1, v2) # 点积
# 范数计算
norm = anp.linalg.norm(A) # Frobenius 范数
ops-blas 是高性能线性代数计算库,anp.dot()、anp.matmul() 这些矩阵运算的幕后功臣。
线性代数求解器
# 解线性方程组 Ax = b
A = anp.array([[3, 1], [1, 2]], dtype=anp.float32)
b = anp.array([9, 8], dtype=anp.float32)
x = anp.linalg.solve(A, b)
# SVD 分解
U, s, Vh = anp.linalg.svd(A)
# 特征值分解
eigenvalues, eigenvectors = anp.linalg.eigh(A)
FFT 频域变换
# 快速傅里叶变换 - 科学计算常见操作
signal = anp.random.randn(1024)
fft_result = anp.fft.fft(signal) # 一维 FFT
ifft_result = anp.fft.ifft(fft_result) # 逆变换
# 二维 FFT(图像处理常用)
image = anp.random.randn(512, 512)
fft_2d = anp.fft.fft2(image)
典型使用场景:三层递进
第一层:像用 NumPy 一样创建张量
import asnumpy as anp
# 在 NPU 显存上创建一个张量
a = anp.zeros((1024, 1024))
# 数据默认驻留在 NPU 显存里,不需要手动搬运
看到没?跟 NumPy 的用法几乎一模一样。区别在于 a 这个张量住在 NPU 显存上,不在系统内存里。
第二层:科学计算的基本操作直接上 NPU
b = anp.random.randn(1024, 1024)
c = anp.dot(a, b) # 矩阵乘法 → 底层调 ops-blas
d = anp.exp(c) # 指数运算 → 底层调 ops-math
e = anp.mean(d, axis=0) # 求均值 → 底层调 ops-math
每一行代码在 CPU 上用 NumPy 也能写,但现在全部在 NPU 上跑。数据从头到尾没离开过显存,省掉了 CPU 和 NPU 之间来回搬运的功夫。
第三层:传统 ML 算法的迁移
很多科研人员和工程师手头有大量基于 NumPy 的传统机器学习代码——PCA、SVD、KMeans 这些算法自己用 NumPy 写的。这些代码要迁移到昇腾 NPU 上,重写成本很高。asnumpy 的价值就在这里:
# 一段经典的 PCA 实现,原来跑在 CPU 上
def pca(X, n_components):
# 中心化
X_centered = X - anp.mean(X, axis=0)
# 协方差矩阵
cov = anp.dot(X_centered.T, X_centered) / X.shape[0]
# 特征分解
eigenvalues, eigenvectors = anp.linalg.eigh(cov)
# 取前 n 个主成分
idx = anp.argsort(eigenvalues)[::-1]
return eigenvectors[:, idx[:n_components]]
# 把 X 换成 asnumpy 张量,剩下的不用改
X_npu = anp.array(X_cpu)
components = pca(X_npu, 50)
代码逻辑一行没改,计算从 CPU 搬到了 NPU。这就是 asnumpy 定位的典型场景:科学计算用户和传统 ML 迁移。
性能对比:数据搬运的代价
为什么一定要在 NPU 上跑?关键在于数据搬运。如果数据在 CPU 和 NPU 之间反复移动,异步传输的开销可能抵消 NPU 计算带来的收益。asnumpy 的设计让数据从头到尾留在 NPU 显存里:
# 传统做法:CPU 计算
import numpy as np
a_np = np.random.randn(4096, 4096)
b_np = np.dot(a_np, a_np) # CPU 计算,可能很慢
# asnumpy 做法:NPU 计算
import asnumpy as anp
a_npu = anp.random.randn(4096, 4096) # 直接在 NPU 创建
b_npu = anp.dot(a_npu, a_npu) # 全程 NPU 计算
# 数据不需要在 CPU 和 NPU 之间搬运
对于大矩阵运算,这个差异是明显的。
和 CANN 其他仓库怎么配合?
asnumpy 不是一个人在战斗。它的上游是三个算子仓库:
- ops-math——提供 conversion 类(张量形态变换)、math 类(基础数学运算)、random 类(随机数生成)。asnumpy 里
anp.sin()、anp.exp()、anp.random.randn()这些操作,最终都走到这里。 - ops-blas——高性能线性代数计算。
anp.dot()、anp.matmul()这些矩阵运算的幕后功臣。 - ascend-boost-comm——算子公共平台,南向对接算子库,北向支撑加速库,负责 M×N 的算子复用。
有一个很容易搞混的点:asnumpy 和 ops-nn 的关系。它们不是竞争关系,而是互补的。ops-nn 覆盖的是神经网络域——Conv2D、LayerNorm、GELU 这些深度学习算子;asnumpy 覆盖的是 NumPy 域——张量创建、数学运算、线性代数这些科学计算操作。你做深度学习用 ops-nn,做科学计算用 asnumpy,各管各的赛道。
它们的关系可以这样理解:
# 深度学习场景 → ops-nn
import AscendBridge
conv = AscendBridge.Conv2d(in_channels, out_channels, kernel_size)
output = conv(input)
# 科学计算场景 → asnumpy
import asnumpy as anp
result = anp.dot(matrix_a, matrix_b)
谁适合用 asnumpy?
两类人:
- 科学计算用户——物理仿真、金融建模、生物信息学,手头大量 NumPy 代码想在昇腾 NPU 上加速跑。
- 传统 ML 迁移——用 NumPy 手写的 PCA、SVD、聚类算法等,要搬上昇腾但不打算全部重写成 PyTorch。
如果你已经在用 PyTorch 做深度学习,那其实不需要 asnumpy——直接用 PyTorch + ops-nn + ops-transformer 这条链路更合适。asnumpy 解决的是另一个问题:让不会用深度学习框架的人,也能用上昇腾 NPU 的算力。
环境准备与快速验证
要使用 asnumpy,需要满足以下环境条件:
# 1. 确认昇腾 NPU 驱动已安装
npu-smi
# 2. 确认 CANN 已安装(需要对应版本)
python -c "import acl"
# 无报错说明 ACL 就绪
# 3. 安装 asnumpy(从 atomgit 拉取或使用预装版本)
pip install asnumpy
简单验证脚本:
import asnumpy as anp
# 创建一个简单张量验证环境
a = anp.ones((100, 100))
b = anp.dot(a, a)
print(f"Result shape: {b.shape}")
print("NPU 计算验证通过!")
想动手试试?
去 https://atomgit.com/cann/asnumpy 看仓库里的 README 和示例代码。先把环境搭好(需要一台带昇腾 NPU 的机器和对应版本的 CANN),然后试着把你手头最简单的 NumPy 脚本改一下 import——把 import numpy as np 换成 import asnumpy as anp,看看能不能直接跑起来。大概率是可以的。跑通了再往复杂场景走。
asnumpy 的目标很简单:让每一个会用 NumPy 的人,都能直接用上昇腾 NPU 的算力,不需要学新框架,不需要改算法逻辑。这就是它存在的价值。
更多推荐




所有评论(0)