NVIDIA nvmath-python是一个旨在连接Python科学计算社区与NVIDIA CUDA-X数学库的开发工具。它让Python用户无需改变现有工作流程,即可享受CUDA-X在常见数学运算中带来的性能优势。根据所调用的API类型,运算既可在CPU上执行,也可在支持CUDA的GPU,乃至分布式多GPU、多节点系统上运行。
随着nvmath-python v1.0正式发布,本文将深入介绍该库的设计理念与独特功能——涵盖从单CPU或单GPU到多GPU、多节点规模的数学运算加速能力。nvmath-python是对CUDA及NVPL数学库的Python抽象封装,底层支持cuFFT、cuBLASLt、cuDSS、cuSPARSE、cuTENSOR、cuBLASMp等多个库。此外,该库还引入了一种全新的稀疏性处理方案——通用稀疏张量(UST),用户可通过领域特定语言自定义适合特定应用场景的稀疏格式,无需手动编写底层实现代码。
安装说明
安装含有复杂原生依赖的Python包往往耗时且容易出错。nvmath-python的安装过程简洁高效,并支持针对不同环境进行定制:
支持pip、conda、uv、pixi等多种包管理器;可选择通过包管理器的依赖解析系统安装全部依赖,也可仅安装最小依赖集,适用于CI/CD流水线或纯CPU环境;可按需选择CPU后端、设备API支持或分布式API;可自由选择NumPy、CuPy或PyTorch等配套数组库(也可同时安装全部)。
设计定位
与NumPy等通用数学库类似,nvmath-python实现了大量适用于工程和科学计算的核心数值运算。但它并非旨在替代通用数组库,也不提供索引、切片或归约等传统功能。
nvmath-python的核心目标是将CUDA-X数学库的完整功能和强大性能引入Python生态,帮助现有数组库和框架更便捷地调用高度优化的GPU加速运算,从而无需依赖底层C/C++接口。
以下示例展示了nvmath-python如何接受NumPy数组作为输入,并返回NumPy数组作为结果:
import numpy as np
import nvmath
m, n, k = 10, 40, 100
a = np.random.randn(m, k)
b = np.random.randn(k, n)
c = nvmath.linalg.advanced.matmul(a, b)
多执行空间支持
nvmath-python支持灵活选择数组库,无论是GPU端的CuPy还是CPU端的NumPy均可兼容。这得益于以下底层支持:
GPU库方面,包含cuBLAS和cuFFT;CPU库方面,支持适用于NVIDIA Grace或ARM v8处理器的NVPL,以及适用于x86主机的Intel MKL;分布式库方面,支持cuBLASMp、cuSOLVERMp和cuFFTMp。
这一特性极大简化了CPU与GPU之间的代码迁移,并支持结合CPU与GPU执行的混合与分布式工作流。以下代码说明了nvmath-python如何同时支持多种内存和执行空间:
import cupy as cp
import numpy as np
import nvmath
N = 2048
a_gpu = cp.random.randn(N) + 1j * cp.random.randn(N)
a_cpu = np.random.randn(N) + 1j * np.random.randn(N)
c_gpu = nvmath.fft.fft(a_gpu)
c_cpu = nvmath.fft.fft(a_cpu)
每次调用的FFT执行空间会根据输入张量(a_gpu或a_cpu)自动推断,当然用户也可以手动指定执行空间。
通用API与专用API
nvmath-python的API大致分为两类:通用API和专用API。
通用API提供统一的用户体验,兼容多种执行空间、内存空间和操作数类型,类似于功能广泛的多用途工具,但可配置项仅限于各类场景共同支持的基础功能。专用API则针对特定操作范围提供全面的配置选项,能够充分发挥硬件性能,可能受限于特定硬件平台。
以矩阵乘法为例:advanced模块中的高级矩阵乘法专门针对GPU上的稠密操作数实现了D=f(AB+C)这一复合运算,提供充分挖掘硬件效率所需的全部配置选项;而通用矩阵乘法API则兼容CPU和GPU上的稠密与结构化操作数,但仅提供适用于更广泛场景的通用配置子集。
最优选择完全取决于具体使用场景:当某项运算成为计算瓶颈、需要针对特定硬件进行优化时,专用API是理想之选;而对于非性能关键型任务,或无需深度定制的场景,通用API更为适合。所有专用API均位于advanced子模块中,以便与通用API明确区分。
日志记录与数据流追踪
该库与Python标准库中的logging模块完整集成,支持在调试、信息、警告和错误等多个层级记录计算细节。
通过日志输出,用户可以直观了解操作数在内存与执行空间之间的流转情况。例如,当操作数来自CPU内存但在GPU上执行时,日志会明确提示可能存在的数据传输开销。nvmath-python会在可能的情况下自动选择最优执行空间,以最大限度降低数据传输代价,用户也可通过execution关键字参数手动指定所需的执行空间。
内核融合与性能提升
以链式调用方式实现D=f(αA·B+βC)这类运算时,若底层原语的算术强度较低,效率会明显不足。一个典型案例是对高瘦矩阵执行GEMM运算:
D=αA·B+βC
以下代码对比了CuPy与nvmath-python在高瘦矩阵上执行GEMM的方式:
import cupy as cp
import nvmath
m, n, k = 10_000_000, 40, 10
a = cp.random.randn(m, k, dtype=cp.float32)
b = cp.random.randn(k, n, dtype=cp.float32)
c = cp.random.randn(m, n, dtype=cp.float32)
alpha, beta = 1.5, 0.5
d1 = alpha * cp.matmul(a, b) + beta * c # 多个内核
d2 = nvmath.linalg.advanced.matmul(a, b, c=c, alpha=alpha, beta=beta) # 单个内核
融合的复合运算相比NumPy风格的API在性能上具有显著优势。nvmath-python底层依托cuBLASLt库,支持即时内核融合,是提升算术强度的有效手段之一。
有状态API与自动调优
上述示例均采用了nvmath-python的函数式(无状态)API,这是一种便捷的单次调用接口,但其内部包含耗时的准备逻辑,称为规划阶段。此外,准备阶段还可能包含自动调优的开销,与实际执行数学运算的执行阶段相互独立。
性能说明:NVIDIA CUDA-X数学库通过启发式算法来选择最优实现方案。由于针对特定问题规模、数据布局或数据类型优化的专用内核存在多个候选项,自动调优会迭代遍历各内核选项、测量其性能并选出最佳方案,因此该阶段可能较为耗时。
在深度学习等需要对不同输入重复执行相同操作的工作负载中,通过创建并复用计划对象,可以将规划开销分摊到多次执行中。nvmath-python的基于类(有状态)API正是为此场景而设计。
以下示例展示了如何使用有状态API进行矩阵乘法(含RELU_BIAS epilog),在一批矩阵上分阶段执行规划、自动调优和计算:
import nvmath
from nvmath.linalg.advanced import MatmulEpilog
import cupy as cp
feed_count = 10
batch_size = 1024
m, n, k = 1024, 1024, 1024
a = cp.random.rand(batch_size, m, k, dtype=cp.float32)
b = cp.random.rand(batch_size, k, n, dtype=cp.float32)
bias = cp.random.rand(batch_size, m, 1, dtype=cp.float32)
with nvmath.linalg.advanced.Matmul(a, b) as mm:
mm.plan(epilog=MatmulEpilog(MatmulEpilog.RELU_BIAS),
epilog_inputs={"bias": bias})
mm.autotune(iterations=5)
for i in range(feed_count):
d = mm.execute()
mm.reset_operands_unchecked(a=d)
与无状态API相比,有状态API通过分摊规划和准备开销显著降低了单次执行的总成本,而自动调优则能在此基础上进一步提升性能。自动调优结果可序列化保存到磁盘,并在新会话中加载复用,避免重复调优。测试表明,内置启发式算法在多数情况下已能选出高性能内核;但对于某些问题规模、数据类型、操作数布局与硬件的特定组合,自动调优仍可带来额外增益。
与Python编译器集成
nvmath-python与numba-cuda等Python编译器深度集成,支持将高性能自定义Python代码即时编译(JIT),并与nvmath-python的运算协同使用。
FFT回调示例
FFT的回调函数以预定义签名的Python函数形式编写,经JIT编译为中间表示后,作为nvmath-python正向或反向FFT的自定义前处理或后处理逻辑。
以高斯滤波器为例,该滤波器对图像进行模糊处理。实现流程为:原始图像 → R2C FFT → 高斯滤波 → C2R iFFT → 滤波结果图像。高斯滤波器的频域形式为H(fx,fy)=exp(-2π?σ?(fx?+fy?)),以下代码展示了具体实现:
from PIL import Image
import nvmath
import cupy as cp
img = cp.asarray(Image.open("your_lovely_dog.jpg").convert("L")) / 255.0
wh = img.shape[0] * img.shape[1]
sigma_value = 20.0
def gaussian_filter(shape, sigma):
fy = cp.fft.fftfreq(shape[0])[:,None]
fx = cp.fft.rfftfreq(shape[1])[None,:]
return cp.exp(-2.0 * cp.pi * cp.pi * sigma * sigma * (fx * fx + fy * fy))
def epilog_impl(data_out, offset, data, filter_data, unused):
data_out[offset] = data * filter_data[offset] / wh
epilog = nvmath.fft.compile_epilog(epilog_impl, "complex64", "complex64")
h_filter = gaussian_filter(img.shape, sigma_value)
img_fft = nvmath.fft.rfft(img, epilog={"ltoir": epilog, "data": h_filter.data.ptr})
filtered_img = nvmath.fft.irfft(img_fft)
设备API与Monte Carlo仿真
nvmath-python还支持在numba-cuda编写的GPU内核中调用设备API,涵盖FFT、GEMM、稠密直接求解器(LU、Cholesky、QR)和随机数生成器(RNG)。以下示例实现了用于Monte Carlo股价仿真的几何布朗运动(GBM),采用nvmath-python的高斯分布随机数生成器,并结合自定义numba-cuda代码将正态分布转换为GBM路径:
from numba import cuda
from nvmath.device import random
import cupy as cp
import math
compiled_rng = random.Compile(cc=None)
rng_seed = 7777
n_time_steps, n_paths = 252, 8192
mu, sigma, s0 = 0.003, 0.027, 100.0
threads_per_block = 32
blocks = n_paths // threads_per_block + bool(n_paths % threads_per_block)
nthreads = threads_per_block * blocks
@cuda.jit(link=compiled_rng.files, extensions=compiled_rng.extension)
def init_rng(states, seed):
idx = cuda.grid(1)
random.init(seed, idx, 0, states[idx])
@cuda.jit(link=compiled_rng.files, extensions=compiled_rng.extension)
def generate_gbm_paths(states, paths, nsteps, mu, sigma, s0):
idx = cuda.grid(1)
if idx >= paths.shape[0]:
return
paths[idx, 0] = s0
for i in range(1, nsteps, 4):
v = random.normal4(states[idx])
vals = v.x, v.y, v.z, v.w
for j in range(i, min(i + 4, nsteps)):
paths[idx, j] = paths[idx, j-1] * math.exp(mu + sigma * vals[j-i])
states = random.StatesPhilox4_32_10(nthreads)
init_rng[blocks, threads_per_block](states, rng_seed)
paths = cp.empty((n_paths, n_time_steps), dtype=cp.float32, order='F')
generate_gbm_paths[blocks, threads_per_block](states, paths, n_time_steps, mu, sigma, s0)
快速上手
nvmath-python在兼顾开发效率的同时,不以牺牲性能为代价,代表了现代数学库设计理念的全新探索。只需一条命令即可开始使用:
pip install nvmath-python[cu13]
更多学习资源包括:详细安装文档、包含代码示例和深度教程的nvmath-python GitHub仓库、NVIDIA加速计算中心的Python进阶训练材料,以及关于通用稀疏张量(UST)的专题文章。
该库凝聚了NVIDIA众多团队成员的心血,核心贡献者包括:Harun Bayraktar、Becca Zandstein、Lukasz Ligowski等数十位工程师。
Q&A
Q1:nvmath-python是什么?和NumPy有什么区别?
A:nvmath-python是NVIDIA推出的Python数学运算库,用于将CUDA-X高性能数学库(如cuFFT、cuBLASLt等)暴露给Python用户。与NumPy不同,nvmath-python不提供索引、切片或归约等通用数组操作,而是专注于在CPU、单GPU乃至多GPU分布式环境下提供高度优化的数学运算加速,并且支持NumPy、CuPy、PyTorch等多种数组库作为输入和输出。
Q2:nvmath-python的有状态API和无状态API有什么区别?什么时候该用哪种?
A:无状态API是单次调用接口,使用方便但每次调用都会执行规划和准备阶段,开销较大。有状态API支持将规划、自动调优和执行拆分为独立阶段,适合需要对不同输入重复执行相同操作的场景(如深度学习训练),可将规划开销分摊到多次执行中。自动调优结果还可序列化保存和跨会话复用。如果某项运算不是性能瓶颈,用无状态API更简洁;如果是高频重复运算,推荐使用有状态API。
Q3:nvmath-python如何安装?支持哪些环境?
A:只需一条命令即可安装:pip install nvmath-python[cu13]。该库支持pip、conda、uv、pixi等多种包管理器,可选择完整依赖安装或最小化安装(适合CI/CD或纯CPU环境),支持选择CPU后端、GPU设备API或分布式API,并兼容NumPy、CuPy、PyTorch等主流数组库。详细安装选项可参考官方安装文档。
