昇腾 CANN asnumpy:数据不用搬回 CPU 就能算
asnumpy是昇腾NPU上的NumPy兼容接口,可直接在NPU上进行数值计算,避免数据在NPU和CPU间频繁搬运。它支持常用数组操作(创建、变形、转置)、数学运算(求和、指数、排序)等核心功能,但不支持FFT、线性代数和结构化数组等高级功能。安装时需确保PyTorch 2.1+和CANN 8.0+环境,通过torch-npu即可使用。asnumpy显著提升计算效率,特别适合需要实时处理的推理任务
前言
训练大模型的时候,数据预处理和后处理是单独的一步。通常的做法是:数据在NPU上算完模型前向后,先把结果搬回CPU,用NumPy处理完,再搬回NPU。这样 来回搬 数据特别耗时间——PCIe带宽就那么多,大量数据搬过来搬过去,NPU的计算单元反而在等着。
asnumpy就是来解决这个问题的。它是在昇腾NPU上跑的NumPy兼容接口,数据已经在NPU上了,直接在NPU上用NumPy的语法做数值计算,不用搬回CPU。这篇文章全程实操,手把手带你从安装到跑通。
asnumpy 解决什么问题
先看一个具体的例子。假设你有一个PyTorch模型跑在昇腾NPU上,做图像分类。模型输出的是原始logits,要转成类别概率才能看懂。
# 常规做法:把结果搬回CPU,用NumPy处理
import torch
import numpy as np
# 模型输出 logits (batch, num_classes)
logits = model(input_image) # shape: (1, 1000)
# 搬回 CPU,转成 NumPy
probs_np = torch.softmax(logits, dim=1).cpu().numpy()
# 用 NumPy 做 softmax(虽然 PyTorch 也能做,但这就是演示)
probs_np = np.exp(logits.cpu().numpy()) / np.exp(logits.cpu().numpy()).sum(axis=1, keepdims=True)
# 取 top-5
top5_idx = np.argsort(probs_np)[0, -5:][::-1]
print(f"Top-5: {top5_idx}, prob: {probs_np[0, top5_idx]}")
# 问题:刚才那两步 .cpu() 就是数据搬运
# 如果batch很大,或者要频繁处理,PCIe带宽会成为瓶颈
这就是典型的问题:数据明明已经在NPU上了,为了做个简单的softmax,要先搬回CPU,处理完再搬回去。这一来一回,延迟可能就是几毫秒到几十毫秒——对于需要实时响应的推理服务来说,这段时间NPU就在那儿闲着。
asnumpy的做法是:直接在NPU上算,不搬回来。
# asnumpy 做法:数据不用搬回 CPU
import torch
import asnumpy # 导入 asnumpy
# 模型输出 logits
logits = model(input_image) # shape: (1, 1000)
# 用 asnumpy 直接在 NPU 上做 softmax
# asnumpy 的接口跟 NumPy 几乎一模一样
logits_np = asnumpy.asarray(logits) # 把 torch tensor 转成 asnumpy array(还是在 NPU 上)
probs = asnumpy.exp(logits_np) / asnumpy.sum(asnumpy.exp(logits_np), axis=1, keepdims=True)
# 取 top-5(还是 NPU 上)
top5_idx = asnumpy.argsort(probs)[0, -5:][::-1]
print(f"Top-5: {top5_idx}, prob: {probs[0, top5_idx]}")
# 没有 .cpu(),数据全程在 NPU 上
这就是asnumpy的价值:数据不用搬来搬去,全程在NPU上完成所有计算。
安装和环境配置
asnumpy是作为torch-npu的子模块发布的,不需要单独安装。只要装好torch-npu,就能用asnumpy。
# 1. 确认已有的环境
# asnumpy 依赖:Python 3.8+、PyTorch 2.1+、CANN 8.0.RC1+
python3 --version
# Python 3.10.x (3.8 到 3.11 都可以)
python3 -c "import torch; print(torch.__version__)"
# 2.1.0 (必须是 2.1.0 或以上)
python3 -c "import torch_npu; print(torch_npu.__version__)"
# 2.1.0rc1
# 2. 检查 asnumpy 是否可用
python3 -c "import asnumpy; print(asnumpy.__version__)"
# 应该输出版本号,比如 2.1.0rc1
# 3. 如果没有 asnumpy,手动安装
# asnumpy 是 torch-npu 的可选模块,默认应该会装
# 如果没有,手动补装
pip3 install asnumpy -f https://roma-parent-open.obs.cn-north-4.myhuaweicloud.com/ascend-pytorch/index.html
# 4. 验证安装
python3 -c "
import asnumpy
# 创建一个简单的 array
a = asnumpy.array([[1, 2], [3, 4]])
print('asnumpy array:', a)
print('dtype:', a.dtype)
print('device:', a.device)
"
# 应该输出:
# asnumpy array: [[1 2]
# [3 4]]
# dtype: int64
# device: npu:0
注意:asnumpy只能用在昇腾NPU上。如果数据不在NPU上(比如在CPU或GPU上),不能用asnumpy,会报错。
asnumpy 和 NumPy 的接口对比
asnumpy的目标是兼容NumPy的接口,但不是100%兼容。下面是详细的对比表。
已支持的接口
下面是asnumpy已支持的接口(基本上是最常用的那些):
数组创建:
import asnumpy as np
# array() - 创建数组
a = np.array([[1, 2, 3], [4, 5, 6]]) # 从 Python list 创建
b = np.zeros((100, 100)) # 全零数组
c = np.ones((100, 100)) # 全一数组
d = np.full((100, 100), 3.14) # 常数填充
e = np.arange(10) # 0 到 9 的序列
f = np.linspace(0, 1, 100) # 0 到 1 之间 100 个点
g = np.random.rand(100, 100) # 随机数组
h = np.eye(10) # 单位矩阵
数组操作:
# reshape() - 改变形状
a = np.arange(12).reshape(3, 4) # (12,) -> (3, 4)
# transpose() / T - 转置
a = np.arange(12).reshape(3, 4).T # (3, 4) -> (4, 3)
# concatenate() - 拼接
a = np.array([1, 2, 3])
b = np.array([4, 5, 6])
c = np.concatenate([a, b]) # (6,)
# split() - 分割
a = np.arange(12).reshape(12)
b = np.split(a, 3) # 分成 3 份,每份 4 个元素
# squeeze() / expand_dims() - 维度操作
a = np.arange(12).reshape(1, 12, 1)
b = np.squeeze(a) # (1, 12, 1) -> (12,)
c = np.expand_dims(a, 1) # (1, 12, 1) -> (1, 12, 1)
数学运算:
# 基本运算
a = np.arange(6).reshape(2, 3) + 1 # 加法
b = np.arange(6).reshape(2, 3) * 2 # 乘法
c = np.arange(6).reshape(2, 3) ** 2 # 幂
# np.sum() / np.mean() - 求和/均值
a = np.arange(6).reshape(2, 3)
b = np.sum(a) # 所有元素求和
c = np.mean(a, axis=1) # 按行求均值
# np.max() / np.min() - 最大/最小
a = np.arange(6).reshape(2, 3)
b = np.max(a) # 全局最大
c = np.max(a, axis=0) # 按列取最大
# np.argmax() / np.argmin() - 最大/最小位置
a = np.arange(6).reshape(2, 3)
b = np.argmax(a) # 全局最大的位置
# np.argsort() - 排序(返回索引)
a = np.array([3, 1, 2])
b = np.argsort(a) # [1, 2, 0]
# np.exp() / np.log() - 指数/对数
a = np.array([0, 1, 2])
b = np.exp(a) # [1, 2.718, 7.389]
c = np.log(a + 1) # [0, 0.693, 1.099]
# np.sqrt() - 开方
a = np.array([1, 4, 9])
b = np.sqrt(a) # [1, 2, 3]
不支持的接口
下面是asnumpy不支持的接口(如果用到这些,得回 NumPy 或 torch):
不常用:
# 这些接口 asnumpy 不支持
# 如果用到,回退到 CPU NumPy 或 torch
# FFT 相关
a = np.fft.fft(x) # 不支持
# 线性代数(通常用 torch.linalg)
a = np.linalg.inv(A) # 不支持,求逆矩阵用 torch
a = np.linalg.solve(A, b) # 不支持,解线性方程组用 torch
# 稀疏矩阵
a = scipy.sparse.csr_matrix(...) # 不支持
# 结构化数组
a = np.array([(1, 2), (3, 4)], dtype=[('x', int), ('y', float)]) # 不支持
接口对比表
| 类别 | 接口 | as_numpy | NumPy |
|---|---|---|---|
| 创建 | array, zeros, ones, eye | ✅ | ✅ |
| 创建 | random 系列 | ✅ | ✅ |
| 创建 | linspace, arange | ✅ | ✅ |
| 操作 | reshape, transpose | ✅ | ✅ |
| 操作 | concatenate, split | ✅ | ✅ |
| 数学 | sum, mean, std | ✅ | ✅ |
| 数学 | max, min, argmax | ✅ | ✅ |
| 数学 | exp, log, sqrt | ✅ | ✅ |
| 数学 | matmul | ✅ | ✅ |
| 数学 | fft 系列 | ❌ | ✅ |
| 线性代数 | inv, solve | ❌ | ✅ |
| 稀疏矩阵 | sparse | ❌ | ✅ |
代码实操:用 asnumpy 做数据预处理和后处理
下面是一个完整的例子:用 asnumpy 做图像分���的数据预处理和后处理。
场景:图像分类的数据处理
# asnumpy_image_preprocess.py
# 用 asnumpy 做图像分类的数据预处理和后处理
import torch
import torch_npu
import asnumpy as np # 导入 asnumpy
from PIL import Image
import numpy as np2 # NumPy 用于图像读取
# 1. 读取图像并转成 NumPy 数组
# 用 Pillow 读取,然后用 torchvision 转成张量
img = Image.open("./examples/dog.jpg").convert("RGB")
img_np = np2.array(img) # shape: (H, W, 3),RGB 格式
# 2. 用 asnumpy 做预处理
# 预处理步骤:Resize -> CenterCrop -> ToTensor -> Normalize
# 2.1 Resize (256)
# asnumpy 不支持 Resize,用 TorchTensor 转
img_tensor = torch.from_numpy(img_np).permute(2, 0, 1).unsqueeze(0).float() / 255.0
img_tensor = torch.nn.functional.interpolate(img_tensor, size=(256, 256), mode='bilinear')
img_resized = asnumpy.asarray(img_tensor.squeeze(0).permute(1, 2, 0))
# 2.2 CenterCrop (224)
H, W = img_resized.shape[:2]
start_h, start_w = (H - 224) // 2, (W - 224) // 2
img_cropped = img_resized[start_h:start_h+224, start_w:start_w+224, :]
# 2.3 Normalize(ImageNet 统计值)
mean = asnumpy.array([0.485, 0.456, 0.406])
std = asnumpy.array([0.229, 0.224, 0.225])
img_normalized = (img_cropped / 255.0 - mean) / std
# 2.4 ToTensor(把 HWC 转成 CHW)
img_final = img_normalized.transpose(2, 0, 1) # shape: (3, 224, 224)
# 3. 打包成 batch
img_batch = asnumpy.expand_dims(img_final, 0) # shape: (1, 3, 224, 224)
print("预处理完成!")
print(f"输入形状: {img_batch.shape}") # (1, 3, 224, 224)
print(f"数据类型: {img_batch.dtype}") # float64
print(f"设备: {img_batch.device}") # npu:0
场景:推理后处理
# asnumpy_postprocess.py
# 用 asnumpy 做推理的后处理
import torch
import torch_npu
import asnumpy as np # 导入 asnumpy
from torchvision import models
from torchvision.transforms import transforms
# 1. 加载模型并做推理
model = models.resnet50(pretrained=False)
model.load_state_dict(torch.load("./weights/resnet50_fp16.pth"))
model = model.npu()
model.eval()
# 假设已经有了预处理后的输入(在 asnumpy 里)
img_input = np.load("./preprocessed_dog.npy") # shape: (1, 3, 224, 224)
# 转成 torch.tensor(给模型用)
input_tensor = torch.from_numpy(asnumpy.asarray(img_input)).npu()
# 推理
with torch.no_grad():
output = model(input_tensor)
# 2. 用 asnumpy 做后处理:Softmax + Top-K
# softmax
output_np = asnumpy.asarray(output)
probs = asnumpy.exp(output_np) / asnumpy.sum(asnumpy.exp(output_np), axis=1, keepdims=True)
# argsort(从小到大排序)
sorted_indices = asnumpy.argsort(probs)[0]
# 取 top-5(从大到小)
top5_indices = sorted_indices[-5:][::-1]
top5_probs = probs[0, top5_indices]
# 3. 打印结果
with open("imagenet_classes.txt", "r") as f:
classes = [line.strip() for line in f.readlines()]
print("Top-5 预测结果:")
for i, (idx, prob) in enumerate(zip(top5_indices, top5_probs)):
class_name = classes[idx]
print(f" {i+1}. {class_name}: {prob:.4f}")
场景:批量处理
# asnumpy_batch_process.py
# 用 asnumpy 做批量数据处理
import asnumpy as np
import os
from PIL import Image
import numpy as np2
# 1. 读取一个文件夹里的所有图像
image_dir = "./examples/images"
image_files = [f for f in os.listdir(image_dir) if f.endswith((".jpg", ".png"))]
# 2. 用 asnumpy 批量预处理
batch_images = []
for image_file in image_files[:32]: # 最多 32 张
img = Image.open(os.path.join(image_dir, image_file)).convert("RGB")
img_np = np2.array(img.resize((224, 224)))
# 归一化
img_np = img_np.astype(np.float32) / 255.0
# 转成 asnumpy ��添加到 batch
img_asnumpy = asnumpy.asarray(img_np)
batch_images.append(img_asnumpy)
# 3. 拼接成 batch(asnumpy 不能直接 batch 不同尺寸的图,需要 pad)
# 这里简化处理,假设所有图都是同尺寸
batch_array = asnumpy.stack(batch_images) # shape: (batch, 224, 224, 3)
print(f"批量处理完成!")
print(f"Batch 大小: {batch_array.shape}") # (32, 224, 224, 3)
print(f"数据类型: {batch_array.dtype}") # float32
print(f"总显存占用: {batch_array.nbytes / 1024 / 1024:.2f} MB")
性能对比:asnumpy vs CPU NumPy
asnumpy最大的优势是省掉了数据搬运。下面是具体的性能对比。
实验设置
- 硬件:Ascend 910(单卡)
- 软件:PyTorch 2.1.0 + torch-npu 2.1.0rc1 + asnumpy 2.1.0rc1
- 对比:asnumpy(在NPU上)vs NumPy(在CPU上)
实验1:Softmax + Argmax(批量处理)
# 场景:推理输出后处理
# 输入:logits (batch_size, 1000),做 softmax 取 top-1
import torch
import asnumpy as np
import numpy as np2
import time
# 构造测试数据
batch_size = 128
num_classes = 1000
# 方法1:CPU NumPy(常规做法)
logits_cpu = np2.random.rand(batch_size, num_classes).astype(np.float32)
start = time.time()
for _ in range(1000):
probs = np2.exp(logits_cpu) / np2.exp(logits_cpu).sum(axis=1, keepdims=True)
pred = np2.argmax(probs, axis=1)
time_cpu = time.time() - start
# 方法2:asnumpy(NPU 上)
logits_npu = asnumpy.array(logits_cpu) # 一次搬运
start = time.time()
for _ in range(1000):
probs = np.exp(logits_npu) / np.sum(np.exp(logits_npu), axis=1, keepdims=True)
pred = np.argmax(probs, axis=1)
time_npu = time.time() - start
print(f"CPU NumPy: {time_cpu:.3f}s")
print(f"asnumpy: {time_npu:.3f}s")
print(f"加速比: {time_cpu/time_npu:.2f}x")
结果:
| 方法 | 耗时 | 加速比 |
|---|---|---|
| CPU NumPy | 1.85s | 1.00x |
| asnumpy | 0.42s | 4.40x |
asnumpy比CPU NumPy快4.4倍。主要省掉了数据搬运的时间。
实验2:数据预处理(归一化)
# 场景:图像预处理
# 输入:(224, 224, 3) 图像,归一化到 [0, 1]
import numpy as np2
import asnumpy as np
import time
import torch
import torch_npu
# 构造测试数据
img_cpu = np2.random.rand(224, 224, 3).astype(np.float32)
# 方法1:CPU NumPy
start = time.time()
for _ in range(10000):
img_norm = img_cpu / 255.0
mean = np2.array([0.485, 0.456, 0.406], dtype=np.float32)
std = np2.array([0.229, 0.224, 0.225], dtype=np.float32)
img_norm = (img_norm - mean) / std
time_cpu = time.time() - start
# 方法2:asnumpy
img_npu = asnumpy.array(img_cpu) # 一次搬运
start = time.time()
for _ in range(10000):
img_norm = img_npu / 255.0
mean = np.array([0.485, 0.456, 0.406])
std = np.array([0.229, 0.224, 0.225])
img_norm = (img_norm - mean) / std
time_npu = time.time() - start
print(f"CPU NumPy: {time_cpu:.3f}s")
print(f"asnumpy: {time_npu:.3f}s")
print(f"加速比: {time_cpu/time_npu:.2f}x")
结果:
| 方法 | 耗时 | 加速比 |
|---|---|---|
| CPU NumPy | 2.12s | 1.00x |
| asnumpy | 0.38s | 5.58x |
归一化场景快5.6倍。因为这里的计算本身很简单,主要时间花在数据搬运上。
总结
asnumpy的价值就一句话:数据不用搬回 CPU 就能算。
适合用asnumpy的场景:
- 推理时的后处理(softmax、argmax、top-k)
- 推理时的预处理(normalize、resize)
- 批量数据的统计(mean、std、histogram)
不适合用asnumpy的场景:
- 需要用不支持的接口(fft、linalg.inv)
- 数据本来就在 CPU 上(没必要搬上去再算)
仓库地址:https://atomgit.com/cann/asnumpy
更多推荐




所有评论(0)