NVIDIA nvmath-python 是一个库,旨在弥合 Python 科学社区与 NVIDIA CUDA-X 数学库之间的差距。它使 Python 用户能够在不中断现有工作流程的情况下,使用 CUDA-X 性能执行常见的数学运算。根据 API,操作可以在 CPU、支持 CUDA 的 GPU 或分布式多 GPU、多节点系统上运行。
nvmath-python v1.0 版本
随着 nvmath-python v1.0 的全面推出,本文将探讨该库的设计和用于加速从 CPU 或单个 GPU 到多 GPU、多节点规模的数学运算的独特功能。nvmath-python 是 CUDA 和 NVPL 数学库 (例如 cuFFT、cuBLASLt、cuDSS、cuSPARSE、cuTENSOR、cuBLASMp 等) 上的 Python 抽象层。通用稀疏张量 (UST) 是一种新型稀疏方法,使用户能够通过特定领域语言创建自己独特的应用优化稀疏格式,而无需在代码中实现该格式。
快速灵活的安装
安装具有复杂原生依赖项的 Python 包可能是一种耗时且令人沮丧的经历。nvmath-python 可以快速安装,并且可以针对不同的环境进行定制。
- 选择软件包管理器,例如 pip、conda、uv 或 pixi。
- 您可以选择通过包管理器的依赖项解决系统安装所有必需的依赖项,或执行最低限度安装,这在 CI/ CD 或 CPU 环境等场景中很有用。
- 选择 CPU 后端、设备 API 支持或分布式 API。
- 选择要使用的配套数组库,例如 NumPy、CuPy 或 PyTorch (或所有这些库) 。有关可用选项,请参阅详细的安装指南。
对现有数组库的有用补充
与 NumPy 等其他数学库一样,nvmath-python 实现了在许多工程和科学计算应用中有用的核心数值运算。但是,它并不打算取代通用数组库或提供索引、切片或归约等传统功能。
相反,nvmath-python 专注于在 Python 中展示 CUDA-X 数学库的全部功能和功能,使现有数组库和框架更容易使用高度优化的 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) # a is a NumPy array
b = np.random.randn(k, n) # b is a NumPy array
c = nvmath.linalg.advanced.matmul(a, b) # c is also a NumPy array
选择显存和执行空间
选择数组库的灵活性适用于 GPU 库 (例如 CuPy) 和 CPU 库 (例如 NumPy) 。这是可行的,因为 nvmath-python 由以下内容提供支持:
- GPU 库,如 cuBLAS 和 cuFFT。
- CPU 库,例如适用于 NVIDIA Grace 或任何 ARM v8 CPU 的 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
nvmath-python 中的 API 大致分为两类:充当灵活的多工具 (宽但浅) 的通用 API,以及设计为精确、专用工具 (窄和深) 的专用 API。
通用 API 专注于跨各种执行和内存空间以及操作数类型提供统一的用户体验,但是它们将可配置性限制为在其广泛范围内共享的基准常见特征。同时,专用 API 提供了一整套专为窄操作范围设计的功能和配置,并且可能仅限于特定硬件。
例如,高级矩阵乘法专门针对 GPU 上的密集操作数实现了合成运算 \(\scriptstyle \mathbf{D}=f(\mathbf{A}\mathbf{B}+\mathbf{C})\),并提供了尽可能提高硬件效率所需的各种配置。相反,通用矩阵乘法 API 可适应 CPU 和 GPU 执行空间中的密集和结构化操作数,但仅提供适用于更广泛范围的通用选项子集。
最佳选择完全取决于具体用例:当操作成为计算瓶颈,需要特定于硬件的优化或访问不同功能时,专用 API 是理想选择。同时,通用 API 更适合对性能不要求严苛或无需专门定制的任务。所有专用 API 均位于 advanced 子模块内,以保持与通用 API 的区别。
使用 nvmath-python 记录日志
该库提供了与日志记录模块中的 Python 标准库日志记录器的集成,用于在各个级别 (调试、信息、警告和错误) 捕获计算详细信息。
以下示例说明了内存和执行空间之间的数据流 (使用高级 matmul) 。
import numpy as np
import nvmath
import logging
logging.basicConfig(level=logging.INFO,
format="%(asctime)s %(levelname)-8s %(message)s", force=True)
logging.disable(logging.NOTSET)
m, n, k = 8000, 2000, 4000
a_cpu = np.random.randn(m, k).astype(np.float32)
b_cpu = np.random.randn(k, n).astype(np.float32)
d_cpu = nvmath.linalg.advanced.matmul(a_cpu, b_cpu)
生成的输出如下所示:
2025-09-18 14:53:32,166 INFO = SPECIFICATION PHASE =
2025-09-18 14:53:32,167 INFO The data type of operand A is 'float32', and that of operand B is 'float32'.
2025-09-18 14:53:32,168 INFO The input operands' memory space is cpu, and the execution space is on device 0.
...
请注意显示操作数来源和使用位置的记录。这表明内存和执行空间之间的数据传输可能成本高昂。现在,使用 fft 等通用 API 运行类似实验,说明内存和执行空间之间的数据流。
import numpy as np
import nvmath
import logging
logging.basicConfig(level=logging.INFO,
format="%(asctime)s %(levelname)-8s %(message)s", force=True)
logging.disable(logging.NOTSET)
N = 10000
e_cpu = (np.random.randn(N) + 1j * np.random.randn(N)).astype(np.complex64)
r_cpu = nvmath.fft.fft(e_cpu)
日志记录输出如下所示:
2025-09-18 15:46:22,295 INFO The FFT type is C2C.
2025-09-18 15:46:22,295 INFO The input data type is complex64, and the result data type is complex64.
2025-09-18 15:46:22,296 INFO The specified FFT axes are (0,).
2025-09-18 15:46:22,297 INFO The input tensor's memory space is cpu, and the execution space is cpu, with device cpu.
2025-09-18 15:46:22,298 INFO The specified stream for the FFT ctor is None.
...
请注意,执行空间与输入的内存空间相同。在可能的情况下,nvmath-python 会选择执行空间,以尽可能减少数据传输用度。用户可以通过向 API 提供 execution 关键字参数来自由选择所需的执行空间。
为什么合成操作很重要
像 \(\scriptstyle \mathbf{D}=f(\alpha\mathbf{A}\cdot\mathbf{B}+\beta\mathbf{C})\) 这样具有纯 NumPy 式 API 的操作在许多用例中都能正常工作。但是,当底层基元运算的算术强度较低时,将其链接为一系列调用是低效的。一个值得注意的例子是,在计算 GEMM 时,\(\scriptstyle \mathbf{A}\) 是一个纤细细高的矩阵:
\(\scriptstyle \mathbf{D}=\alpha\mathbf{A}\cdot\mathbf{B}+\beta\mathbf{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 # Multiple kernels
d2 = nvmath.linalg.advanced.matmul(a, b, c=c, alpha=alpha, beta=beta) # Single kernel
图 1 显示,与类似于 NumPy 的 API 相比,融合复合运算可带来可衡量的优势。

由于底层 cuBLASLt 库能够实现即时内核融合,nvmath-python 的性能要好得多。它是提高算术强度的有效技术之一。
使用有状态 API 分摊准备成本
之前的所有示例都利用了 nvmath-python 的函数形式或无状态 API。这是一种便捷的单次调用 API,涉及耗时的准备逻辑,称为规划阶段。此外,准备成本还可能包括自动调整成本。它不同于执行阶段,后者在规划/ 自动调整后执行所请求的数学运算。
性能说明
NVIDIA CUDA-X 数学库采用启发式方法来确定可产生最佳性能的特定实现。针对特定的问题规模、布局或数据类型进行优化的专用内核有多种选择。在硬件、工作负载和其他因素的特定组合下,哪种内核运行效果最佳并不总是显而易见。自动调整旨在通过迭代内核选项、测量其性能并选择最佳选项来覆盖默认内核选择。因此,自动调整阶段可能非常耗时。
在深度学习等工作负载中,相同的操作可能会在不同的输入下重复运行。跨执行创建和重复使用计划可分摊其规划成本。nvmath-python 基于类或有状态的 API 支持此工作流。
以下示例说明了在批量大小为 batch_size 的矩阵 a 和 b 上使用 matmul (带有 RELU_BIAS epilog) 的类形式 API,并对 bias 进行偏差处理。先验矩阵乘法的结果是下一个矩阵乘法中的操作数,并且有 feed_count 运算。除了规划之外,它还执行自动调整阶段。
以下代码说明了如何将规划、自动调整和执行作为不同阶段使用有状态 API。
import nvmath
from nvmath.linalg.advanced import MatmulEpilog
import cupy as cp
feed_count = 10 # The operation feed count.
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:
# 1. Planning phase
mm.plan(epilog=MatmulEpilog(MatmulEpilog.RELU_BIAS),
epilog_inputs={"bias": bias})
# 2. Autotuning phase
mm.autotune(iterations=5)
# 3. Execution phase.
for i in range(feed_count):
d = mm.execute()
# The result of the previous MM is the operand `a` of the next MM, so use
# reset_operands_unchecked() to reset the `a` operand.
mm.reset_operands_unchecked(a=d)

图 2 显示计算成本如何随执行次数而变化。精细虚线表示使用 nvmath-python 无状态 API 的成本。粗虚线显示切换到有状态 API 后成本降低的情况,虚线显示自动调整带来的额外性能提升。有状态 API 会分摊规范和准备成本,而无状态 API 会在每次执行期间产生这些成本。自动调整的优势可以扩展到所有会话,因为可以将自动调整的计划序列化到磁盘并加载到新会话中。
图 3 显示,内置启发式算法通常可以在不进行自动调整的情况下选择高性能内核。但是,自动调整会受益于问题大小、数据类型、操作数布局、硬件和其他因素的某些组合。在测试配置中,NVIDIA RTX A6000 的速度提升幅度最大,而 NVIDIA B200 无需自动调整即可达到峰值性能。
部署说明
另一个示例是执行一次调优,然后在多个同构系统中部署经过调优的计划,以便对不需要重复重新规划的数据执行类似的运算。如需更深入地了解,请参阅 nvmath-python GitHub 存储库中的 example14_autotune.py、example15_manual_tuning.py 和 example16_reuse_algorithms.py。

自定义内核与 nvmath-python 融合
nvmath-python 与 numba-cuda 等 Python 编译器集成,使高性能自定义 Python 代码能够及时编译 (JIT) ,并与 nvmath-python 操作一起使用。
自定义 FFT 回调
FFT 的回调函数被写成具有预定义签名的 Python 函数,并通过 JIT 将其编译为中间表示,随后用作 nvmath-python 的正向或反向 FFT 的自定义序幕或结语。

高斯滤波器示例
为说明起见,我们实施了高斯滤波器,该滤波器会对原始图像应用模糊处理。以下代码段使用 PIL 库加载图像,然后将其转换为灰度[0,1]图像,并将其转换为 CuPy ndarray。对于图像过滤,我们实现了 img+ R2C FFT+ Gaussian 滤波器+ C2R iFFT+ filtered_img 链。高斯滤波器为 \(\scriptstyle G(x,y)=\exp\left(-\frac{x^2+y^2}{2\sigma^2}\right)\),在频域中也是 Gaussian \(\scriptstyle H(f_x,f_y)=\exp\left(-2\pi^2\sigma^2(f_x^2+f_y^2)\right)\)。
以下代码展示了如何使用 nvmath-python FFT 和自定义回调函数应用高斯图像过滤器:
from PIL import Image
import nvmath
import cupy as cp
img = cp.asarray(Image.open("your_lovely_dog.jpg").convert("L")) / 255.0 # Gray[0,1]
wh = img.shape[0] * image.shape[1] # We must normalize by the image area
sigma_value = 20.0 # Filter size
# Implement Gaussian filter in the frequency domain
def gaussian_filter(shape, sigma):
fy = cp.fft.fftfreq(shape[0])[:,None] # Column
fx = cp.fft.rfftfreq(shape[1])[None,:] # Row
return = cp.exp(-2.0 * cp.pi * cp.pi * sigma * sigma * (fx * fx + fy * fy))
# Implement FFT epilog wrapper with the pre-defined signature
def epilog_impl(data_out, offset, data, filter_data, unused): # Epilog to be compiled
data_out[offset] = data * filter_data[offset] / wh
# Compile epilog to LTO-IR targeting the current CUDA device
epilog = nvmath.fft.compile_epilog(epilog_impl, "complex64", "complex64")
# Compute R2C FFT using nvmath-python with the compiled epilog
h_filter = gaussian_filter(img.shape, sigma)
img_fft = nvmath.fft.rfft(image, epilog={"ltoir": epilog, "data": h_filter.data.ptr})
# Compute C2R inverse FFT using nvmath-python
filtered_img = nvmath.fft.irfft(img_fft) # Visualize or save as you want
使用 nvmath-python 调用自定义 numba-cuda 核函数
第二种常用场景是在使用 numba-cuda 编写的 GPU 核函数中调用 nvmath-python 设备 API。nvmath-python 支持用于 FFT、GEMM、密集直接求解器 ( LU、Cholesky、QR) 和 RNG 的设备 API。以下示例展示了针对蒙特卡洛股价模拟的几何布朗运动 (GBM) 的实现。它使用 nvmath-python 的随机数生成器进行高斯分布,以及将正态分布转换为 GBM 蒙特卡罗路径的自定义 numba-cuda 代码:
from numba import cuda
from nvmath.device import random
import cupy as cp
import math
# Pre-compile the RNGs into IR to use alongside other device code
compiled_rng = random.Compile(cc=None)
# GBM parameters
rng_seed = 7777
n_time_steps, n_paths = 252, 8192
mu, sigma, s0 = 0.003, 0.027, 100.0
# Set up CUDA kernel launch configuration
threads_per_block = 32
blocks = n_paths // threads_per_block + bool(n_paths % threads_per_block)
nthreads = threads_per_block * blocks
# RNG initialization kernel
@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])
# GBM path generation kernel
@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
# Consume 4 normal variates at a time for better throughput
for i in range(1, nsteps, 4):
v = random.normal4(states[idx]) # Returned as float32x4 type
vals = v.x, v.y, v.z, v.w # Decompose into a tuple of float32
for j in range(i, min(i + 4, nsteps)): # Process a chunk of 4 time steps
paths[idx, j] = paths[idx, j - 1] * math.exp(mu + sigma * vals[j - i])
# Initialize RNG
states = random.StatesPhilox4_32_10(nthreads)
init_rng[blocks, threads_per_block](states, rng_seed)
# Generate GBM paths on GPU
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)
generate_gbm_paths 中的每个运算都具有较低的算术强度,这使得基于主机 API 的实现效率低下。将这些操作与 numba-cuda 和 nvmath-python 设备 API 融合在一起至关重要。
开始使用 nvmath-python
nvmath-python 专为提高工作效率而设计,且不会影响性能,它重塑了现代数学库的设计。开始使用一条简单的命令:
pip install nvmath-python[cu13]
其他资源包括:
- 包含详细设置说明的安装文档
- nvmath-python GitHub 资源库,包括代码示例和深度教程 Notebook
- NVIDIA 加速计算中心中的扩展 Python 训练材料
- 之前的博文,在 nvmath-python 中使用通用稀疏张量简化稀疏深度学习
- 最新新闻和公告的 nvmath-python 产品页面
致谢
该库是 NVIDIA 许多人努力的结果,包括:
Harun Bayraktar、Becca Zandstein、Lukasz Ligowski、Aart Bik、Yevhenii Havrylko、Juan Galvez、Daniel Ching、Mark Olah、Yang Gao、Szymon Karpinski、Kamil Tokarski、Francesco Rizzi、Jakub Ligowski、Marcin Rogowski、Robbie Jensen、Artem Amogolonov、Sushma Kini、Rachna Pandey、Graham Markall、Michael Yh Wang、Bradley Dice、Liam Zhang、Jack Cui、Chang Liu、Qi Xia、Feng Cheng、Ruilin Tian、Zan Xu、Alm