CUDA Python 1.0:稳定 API、统一基础、完整平台访问
多年来,Python 开发者要用 GPU 只有两条路:要么把 NVIDIA CUDA C++ 学到能写扩展的程度,配好构建工具链,再维护回 Python 的绑定——大多数人不会这么做;要么往上借力,让现成库代劳,也就是 PyTorch、CuPy 或 RAPIDS。
Python GPU 生态的繁荣靠的就是第二条路,但它有天花板。一旦你需要的功能上层库没有暴露出来,就又回到了第一条路。
由于各个库各自接入 CUDA,让两个库在同一份数据上协作并不容易:CuPy 分配了一块 GPU 内存,cuDF 要在同一流上直接处理这块内存、不做拷贝,需要怎么做?答案是借助互操作协议,并仔细追踪内存归属。
在 CUDA 13.3 中,我们发布了 CUDA Python 1.0——一套让你从 Python 访问完整 CUDA 平台的库与工具。Python 现在已成为使用 CUDA 平台的官方支持途径。
这次一同交付的内容包括:
cuda.core1.0.0:以 Python 风格访问 CUDA runtimecuda.compute1.0.0:CCCL 的并行算法,可从 Python 调用cuda.bindings13.3.0:与 CUDA C API 一一对应的底层绑定,版本与 CUDA Toolkit 保持一致cuda-pathfinder:定位环境中安装的 CUDA 组件nvmath-python1.0:NVIDIA 的数学库 Python 版,在独立发布轨道上遵循同样的稳定性承诺
CUDA Python 1.0 是一个里程碑名称,而不是你要敲进 pip 的版本号;各组件独立发版,上面那些版本号不一致是有意为之。
其中影响最深的是 cuda.core。它把 CUDA 的基本词汇(设备、流、缓冲区)变成一组普通 Python 对象,而这件事的意义远不止方便:它为 Python 里的所有 GPU 库提供了共同的基础,可以在此之上构建、协作并共享资源。这个思路贯穿了本文余下的部分。
CUDA 1.0:语义化版本
CUDA Python 1.0 不是重写,也不是新产品。这些库大多已经存在并迭代了一段时间。1.0 带来的变化是一项承诺:语义化版本。
具体来说意味着:
- 破坏性 API 变更只发生在主版本中
- 次版本会加入新功能
- 补丁版本用于修复缺陷
- 任何计划移除的公开 API 会先在某个次版本中标记为废弃,并给出明确的替代方案
如果你曾因为不确定 API 能否在下一次升级中存活而对基于某个库构建犹豫不决,那这条保证就是本次发布的核心信息。CUDA Python 将继续跟进每一版新发布的 CUDA 功能,从此拥有可预测的版本管理和废弃规则。
一套基础取代百家实现
要理解 1.0 版本带来了什么,不妨先回顾一下它之前的状态。
过去,从 Python 调用 CUDA 意味着在多种绑定层之间做出选择。它们各自由不同项目维护,各自覆盖 API 的不同片段,对流、设备、内存分配等概念也各有各的理解。写应用时,你只能继承依赖项碰巧选用的那一层;写库时,要么沿用别人的实现,要么自己再造一个,于是生态系统中又多了一份。这正是如今发生改变的地方。
现在,从 Python 访问 CUDA 有了一条由 NVIDIA 官方维护的唯一通道。从 CUDA 13.3 起,CUDA Python 与 C++ 享有同等的一等公民地位,NVIDIA 承诺将持续维持二者之间的功能完整性对等。Python 已成为使用 CUDA 平台的受支持方式。
由此带来的实际收益是:各个库之间可以真正组合,而不仅仅是并存。一个 Numba kernel 和一次 cuda.compute 调用可以在同一条流上操作同一个 GPU buffer,因为它们都没有自带私有的 CUDA 层。对象能够跨越库边界传递,因为底层它们就是同一类对象。这是对"共享"问题比任何交换协议都更简洁的回答。
这也改变了谁能使用高级平台能力的格局。以 green contexts 为例——它把 GPU 的流式多处理器划分为多个分区,使延迟敏感型 kernel 与吞吐型 kernel 互不干扰——此前,每个对此感兴趣 的库都需要独立完成绑定并暴露该功能。如今它只需落地到 cuda.core 一次,所有基于 cuda.core 构建的项目便都能使用它。
如果你开发面向 CUDA 的库,那么真正改变你日常工作的是:可以把精力放在让库与众不同的部分,而不是重复实现别人早已写好的底层。 如果你写的是应用,这种好处会经过一层间接传递——当你的依赖项都收敛到同一套基础设施上时,你就会感受到。
心智模型:一个基础,三层堆叠
CUDA Python 是一组库的合集,从 Python 端覆盖整个 CUDA 生态:底层驱动和运行时绑定、并行算法、数学库、通信库以及 kernel 编写工具。下方的图 1 展示了它们之间的层次关系,最清晰的阅读方式是从下往上看。
运行时系统是所有其他模块所依赖的基础:设备管理、内存分配、流与同步、CUDA graphs 以及 JIT 编译。
cuda-pathfinder 听起来平平无奇,但只要回想一下 Python GPU 社区曾经花了多少时间去排查一个进程究竟加载了哪个 CUDA 运行时,就能明白它的价值所在。
再往上是 CUDA 库:为 NVIDIA 调优的主机端与设备端库提供的 Pythonic 接口。cuda.compute 把 CCCL 中可在主机端调用的并行算法带了过来;同样升级到 1.0 的 nvmath-python 把数学库带了过来,包括主机端 API、设备端 API 以及底层绑定;NCCL4Py 和 NVSHMEM4P 则把通信库 NCCL 和 NVSHMEM 带了过来。
它们都没有重新实现底层的 CUDA 层。它们使用的是同一个 cuda.core 中的缓冲区、设备和流——和直接调用时一样。因此例如 NVSHMEM 的对称内存返回给你时就是一个 cuda.core 缓冲区。这套共享的基础不只是给生态的指引,NVIDIA 自己的库也是建立在它之上的。
最顶层是内核编写,供那些想亲手写 GPU 代码的人使用。numba-cuda 是面向 Python 内核的 SIMT 语言,与它并列的还有两种较新的领域特定语言:用于块模型的 CUDA tile 语言 cutile-python,以及面向 Tensor Core 的 CUTLASS 语言 cuteDSL。
有两件事值得说明:
第一,你从问题所对应的层级入手即可,不必把整套都学完。很多高效的用户始终停留在库层级,从未离开。图中的层级并不是一份课程大纲。
第二,这些层级并不是一个统一版本号管理的产品。每个组件按各自的节奏演进,其中一些部件——包括某些较新的内核编写语言——仍然是实验性的,尚未纳入 1.0 的语义化版本保障;不过随着它们趋于稳定,目标是让它们同样受到这些承诺的约束。在把生产环境的依赖固定在某个组件之前,这一点值得了解。
三种入门路径
几乎每个人接触 CUDA Python 时都会带着三个问题之一,每个问题分别指向图中的不同层级。下面按你需要承担的工作量由少到多依次列出,而不是按它们在图中的位置。
我只想用现成的优化算法:cuda.compute
最快的捷径通常根本不是自己写内核,而是调用已有的、由专业人士长期调优过的内核。
cuda.compute 把 CUDA Core Compute Libraries(CCCL)的并行算法以宿主机可调用的方式带到 Python 中:排序、扫描、归约、变换、去重、直方图、top-k,等等。这些算法与高性能 C++ CUDA 代码背后使用的是同一套实现,从 Python 这边看,它们只是作用在已有 GPU 数组上的普通函数调用。很大一部分数值计算工作其实都是这些模式的组合。由于 cuda.compute 会针对你实际运行的 GPU 做编译,同一段调用在下一代硬件上无需修改即可照常工作。
1.0 版本还让这些 API 表达力更强:现在你可以用普通的 Python 函数(包括 lambda 表达式)来自定义算法的行为。
- 适用场景:你的问题可以拆解成常见的并行模式,并且希望用最少的代码拿到结果。
我想用 Python 写自己的内核:Numba
有时你的逻辑找不到现成的算法可套用。这时你可以自己写内核,而且仍然可以用 Python 来写。
Numba 把 Python 的一个子集编译成 GPU 内核:你只需在一个函数上加上装饰器,Numba 就会从中生成 GPU 代码。你所写的是一个基于 CUDA SIMT 模型的内核,表达的是单个线程的工作,然后由 GPU 在成千上万个线程上并发执行。完成这种思维方式的转换是你需要学习的主要东西,而这比去学 C++ 和一套构建系统要小得多。
Numba CUDA MLIR 是一个基于 MLIR 和现代 NVVM 工具链构建的、与 Numba 兼容的新内核生成器。它保留了你已经熟知的编程模型,同时替换掉底层的编译器,带来更快的 JIT 预热编译和更低的内核启动延迟;对于大多数代码来说,迁移到它只需要改一行 import。它比 1.0 的其他组件更新,尚未纳入同样的语义化版本承诺范围。
- 适用场景:你的计算任务不适合用现成的算法,需要直接控制每个线程的行为。
我需要 driver 和 runtime API:cuda.core、cuda.bindings
最底层是两个把 CUDA 本身交到你手中的包,其中 cuda.core 达到 1.0 是本次发布的重头戏。
cuda.core 是对 CUDA runtime 的 Pythonic 接口,涵盖设备、流、程序、链接器、内存资源以及图(graphs),并支持 CUDA C++ 的运行时编译,让内核可以从源码直接跑到运行起来,无需单独的构建步骤。重点在于Pythonic:资源是真正的 Python 对象,失败时抛出异常而不是返回错误码。由于它使用标准的 CUDA 上下文,因此可以与 Python GPU 生态中的其他组件共享设备、流和内存——这样你写的内核可以直接作用于 CuPy 数组或 PyTorch 张量,而无需复制数据。
1.0 版本把过去几个发布周期逐步稳定下来的 API 整合为一套受官方支持的统一接口,并新增了三项值得关注的能力:
- Green contexts(绿上下文):将一块 GPU 的 SM 划分为互不重叠的子集,如前所述,让对延迟敏感的内核免受同进程中长时运行的吞吐型内核干扰。
- Process checkpointing(进程检查点):对运行中进程的完整 CUDA 状态打快照,以便稍后恢复。
- Inter-process sharing (IPC,进程间共享):在不经过主机内存拷贝的前提下,让多个进程共享 GPU 显存。
在这一层之下,cuda.bindings 提供了与 CUDA 主机 API 一一对应的底层绑定,覆盖从 Driver 和 Runtime 到编译器、链接器及其周边系统库。它跟随 CUDA Toolkit 同步发版。cuda.core 追求 Python 体验的优雅,而 cuda.bindings 追求 API 覆盖的完备:只要 C API 里有的,Python 里就能调到。
- 使用场景:你正在构建一个 GPU 库,或把 CUDA 集成进现有工具链时,就用
cuda.bindings;写纯 Python 代码、追求开发效率时,用cuda.core。
生态已经在向它靠拢
统一底座是否好用,看谁在用它最有说服力。前面已经提到,NVIDIA 自己的通信库和数学库全部基于 cuda.core 对象。更广泛的生态也在跟进:CuPy 因此获得了更简洁的构建流程和更轻、更快的模块加载;PyTorch 在其 CUDA 发行版里也已经依赖 cuda.bindings。
每多一个库迁移到这层公共底座,你的依赖图中就少一层私有绑定——版本冲突更少、诡异的互操作 Bug 更少,两个库在「当前到底处于哪个 CUDA 上下文」上产生分歧的情况也更少。
而且由于各组件各自走语义化版本号,单独依赖其中一个并不会把其余组件的频繁变更一起带进来。
上手
一行命令装齐 CUDA Python 全家桶:
pip install cuda-python cuda-cccl numba-cuda-mlir[cu13]
以上内容覆盖了前面提到的所有 CUDA Python 组件,以及基于 MLIR 的 Numba 后端。可以通过 pip install nvmath-python[cu13] 单独安装 nvmath-python。唯一的系统要求是安装最新的 NVIDIA 驱动,通常不需要单独安装 CUDA Toolkit。
如果你正在决定从何处入手:
- 如果你做的是数据科学工作,可能根本不需要下沉到这么底层。RAPIDS 库已经覆盖了这个领域:cuDF 加速了 pandas、Polars 和 Apache Spark,nx-cugraph 为 NetworkX 提供了支持
- 如果需要优化算法,从
cuda.compute开始 - 如果想用 Python 编写自己的 kernel,从 Numba 或 Numba CUDA MLIR 开始
- 如果需要访问底层驱动和运行时 API,从
cuda.core和cuda.bindings开始
此外,CUDA Python 文档和NVIDIA/cuda-python 仓库提供了安装指南、API 参考和示例,而NVIDIA Accelerated Computing Hub汇集了更广泛的 GPU 计算学习资料。
1.0 里程碑最棒的一点在于,这个选择不再是一个高风险的决策。挑选与你眼前问题匹配的层级即可,因为底层的基础是稳定的。
致谢
CUDA Python 1.0 凝聚了 CUDA Python 产品和工程团队多年来的心血,他们设计并构建了本文所介绍的这些库。同时也感谢 NVIDIA 内部各位审阅者的反馈,让这次发布和本文都更加完善,还要感谢那些提交 issue、测试预发布版本、并帮助打磨 API 的开源贡献者们——正是有了他们的努力,我们现在才能承诺对这些 API 提供长期支持。