3. NPU矩阵乘法加速
任务目标
在NPU上实现一个固定尺寸(64×64)的矩阵乘法 C = A × B,并与CPU上的同等实现进行耗时对比,验证NPU加速效果。本案例聚焦于NPU算子调用与数据搬运流程,不涉及自定义算子开发。
环境与依赖
- 硬件:搭载NPU的AI加速卡(如昇腾310P/910)
- 软件:CANN Toolkit 6.0+,Python 3.8+
- 依赖库:
numpy(用于CPU对比与数据生成)、acl(CANN Python接口)
关键完整代码
import numpy as np
import time
import acl
# 矩阵尺寸
N = 64
def cpu_matmul(a, b):
"""CPU参考实现"""
return np.dot(a, b)
def npu_matmul(a, b):
"""NPU实现:使用CANN的MatMul算子"""
# 初始化
ret = acl.init()
assert ret == 0, f"acl.init failed: {ret}"
ret = acl.rt.set_device(0)
assert ret == 0, f"set_device failed: {ret}"
context, ret = acl.rt.create_context(0)
assert ret == 0, f"create_context failed: {ret}"
# 准备数据(转为fp16以匹配NPU常用精度)
a_fp16 = a.astype(np.float16)
b_fp16 = b.astype(np.float16)
# 分配NPU设备内存
a_buffer, ret = acl.rt.malloc(a_fp16.nbytes, acl.const.MEM_MALLOC_NORMAL_ONLY)
b_buffer, ret = acl.rt.malloc(b_fp16.nbytes, acl.const.MEM_MALLOC_NORMAL_ONLY)
c_buffer, ret = acl.rt.malloc(N * N * 2, acl.const.MEM_MALLOC_NORMAL_ONLY) # fp16输出
# 数据拷贝到设备
a_np = np.frombuffer(a_buffer, dtype=np.float16).reshape(N, N)
b_np = np.frombuffer(b_buffer, dtype=np.float16).reshape(N, N)
a_np[:] = a_fp16
b_np[:] = b_fp16
# 创建MatMul算子描述
matmul_desc = acl.ops.matmul.create_desc()
acl.ops.matmul.set_desc(matmul_desc, acl.ops.matmul.DESC_INPUT_A, 0, a_fp16.shape, acl.ops.matmul.FORMAT_ND, acl.ops.matmul.DTYPE_FP16)
acl.ops.matmul.set_desc(matmul_desc, acl.ops.matmul.DESC_INPUT_B, 0, b_fp16.shape, acl.ops.matmul.FORMAT_ND, acl.ops.matmul.DTYPE_FP16)
acl.ops.matmul.set_desc(matmul_desc, acl.ops.matmul.DESC_OUTPUT_C, 0, (N, N), acl.ops.matmul.FORMAT_ND, acl.ops.matmul.DTYPE_FP16)
# 执行MatMul
stream, ret = acl.rt.create_stream()
ret = acl.ops.matmul.exec(stream, matmul_desc, [a_buffer], [b_buffer], [c_buffer])
assert ret == 0, f"matmul exec failed: {ret}"
acl.rt.synchronize_stream(stream)
# 拷贝回主机
c_result = np.zeros((N, N), dtype=np.float16)
c_np = np.frombuffer(c_buffer, dtype=np.float16).reshape(N, N)
c_result[:] = c_np
# 清理资源
acl.rt.free(a_buffer)
acl.rt.free(b_buffer)
acl.rt.free(c_buffer)
acl.ops.matmul.destroy_desc(matmul_desc)
acl.rt.destroy_stream(stream)
acl.rt.destroy_context(context)
acl.rt.reset_device(0)
acl.finalize()
return c_result.astype(np.float32)
if __name__ == "__main__":
# 生成随机输入
np.random.seed(42)
A = np.random.rand(N, N).astype(np.float32)
B = np.random.rand(N, N).astype(np.float32)
# CPU执行与计时
t0 = time.time()
c_cpu = cpu_matmul(A, B)
t_cpu = time.time() - t0
# NPU执行与计时(首次调用含初始化,取第二次执行时间)
npu_matmul(A, B) # 预热
t0 = time.time()
c_npu = npu_matmul(A, B)
t_npu = time.time() - t0
# 验证正确性(容差1e-2,因fp16精度)
max_diff = np.max(np.abs(c_cpu - c_npu))
print(f"CPU耗时: {t_cpu*1000:.2f} ms")
print(f"NPU耗时: {t_npu*1000:.2f} ms")
print(f"最大误差: {max_diff:.4f}")
print(f"加速比: {t_cpu/t_npu:.2f}x")
assert max_diff < 1e-2, "NPU结果与CPU差异过大"
print("验证通过")
运行方式
# 在已配置CANN环境的NPU服务器上执行
python3 npu_matmul_demo.py
验证方法与判定标准
- 正确性验证:程序自动比较NPU输出与CPU参考结果,最大误差小于0.01即判定计算正确(fp16精度下合理容差)。
- 性能验证:观察输出的CPU耗时与NPU耗时,加速比大于1即表明NPU对该尺寸矩阵乘法有加速效果。对于64×64矩阵,通常可观察到2~10倍加速。
- 通过条件:程序打印“验证通过”且加速比>1,说明NPU矩阵乘法实现正确且达到加速目的。
常见问题
- 初始化失败:确认已安装CANN且当前用户有NPU设备访问权限(通常需root或加入
HwHiAiUser用户组)。 - 内存不足:64×64矩阵仅需约24KB设备内存,若失败请检查NPU显存占用或驱动状态。
- 精度误差偏大:若误差超过0.01,可改用fp32执行NPU计算(需确认硬件支持),或增大容差至0.05。