2026年9月,NVIDIA宣布将全力投入原生Rust语言的GPU编程支持。CUDA C++和CUDA Python已经是成熟的企业级工具链,而NVIDIA将在2027年及以后持续发展和完善CUDA Rust。

AI系统层涵盖推理引擎、服务基础设施、驱动程序和智能体运行时,随着模型和技术的变化,这一层始终在快速迭代。其中越来越多的部分正在用Rust编写,因为Rust能在编译阶段就捕获整类错误,同时不牺牲性能。

NVIDIA参与这一转变出于同样的原因。Nova Linux驱动程序就是用Rust编写的。NVIDIA Dynamo基于Rust核心构建。NVTX也提供了Rust绑定。

GPU内核则是个例外。你可以从Rust中启动内核,但内核本身往往必须用其他语言编写。

NVIDIA CUDA Rust填补了这一空白。GPU内核现在可以用Rust编写,直接原生编译为PTX,而不是对其他语言代码的封装。

使用Rust有两条路径,对应CUDA自身的两种编程模型。SIMT是你已经在CUDA C++或numba-cuda中使用的模型,你指定一个线程执行什么操作,然后启动成千上万个线程。Tile则是一种较新的编程模型,同样在C++和Python中可用。所有这些前端都让你描述一个数据块(tile)该做什么,剩下的工作由Tile IR编译器完成。

在选择构建基础时,应优先考虑Tile。编译器会决定如何将tile映射到各个架构上,因此你的源代码不会包含特定架构的选择,只有当你需要那种控制权,或想自行管理内存和线程时,才需要降级到SIMT。

选择哪种语言是与选择哪种模型完全独立的问题。使用最适合你现有技术栈的CUDA接口。下面介绍的两个项目适用于该技术栈是Rust的情况。我们计划支持跨语言互操作,因此这个选择不会将你锁定在其他语言之外。

下面展示的是同一个内核在两条技术路径上的实现,执行1024个浮点数的逐元素加法。两者都是完整的程序,都能运行,并打印相同的结果,你可以并排阅读它们,看看有哪些变化。

SIMT路径:cuda-oxide

cuda-oxide是一个自定义的rustc代码生成后端。它拦截编译过程,将带有#[kernel]标记的函数通过Rust MIR、社区的Pliron IR框架、LLVM IR一路转换为PTX,其余部分则交给标准后端处理。Pliron之上的GPU方言是我们自己开发的。所有方言和转换过程都保留在Rust中,直到标准LLVM后端接管为止。

你需要Linux系统、计算能力8.0或更高的GPU、CUDA工具包(12.x或更新版本)、带有libclang头文件的clang,以及固定版本的nightly工具链。cargo oxide doctor命令可以检查所有这些依赖项,包括可选的系统LLVM。安装cargo-oxide这个驱动构建过程的Cargo子命令:

cargo +nightly-2026-04-03 install --git https://github.com/NVlabs/cuda-oxide.git cargo-oxide

然后搭建项目并运行。该模板是一个完整的向量加法程序:

cargo oxide new vecadd_demo

cd vecadd_demo

cargo oxide doctor

cargo oxide run

第一次运行cargo oxide run会构建代码生成后端,因此预计需要一段时间。之后的运行会复用缓存。

程序会打印出"PASSED: all 1024 elements correct"(通过:全部1024个元素正确)。这就是完成这项工作的完整程序,与cargo oxide new生成的内容一致,此处添加了注释。

主机代码和设备代码存放在同一个文件中,用一条命令构建,不需要单独的内核crate。

首先阅读内核签名,因为它承载了整个安全性论证。a和b是普通的共享切片,每个线程都可读取。c是一个DisjointSlice<f32>类型,这种类型让每个线程独享其自身元素的访问权限,且仅限于此元素。之所以需要这种设计,是因为&mut [f32]并不适合这项任务——每个线程都需要相同的&mut引用,而Rust会正确地拒绝这种做法。DisjointSlice将这一个可变借用拆分成每个线程各自的片段。

thread::index_1d()返回的是一个索引类型,而非普通整数,c.get_mut(idx)也只接受这种类型。你得到的返回值是一个Option,因此越界情况是你需要处理的一个分支,而不是日后才会发现的内存错误。

启动过程是经过检查的,而非全凭信任。#[launch_contract]声明了该内核以一维方式索引,使用256线程的块。prepare_vecadd会根据这一声明和实际设备限制来验证你的LaunchConfig1D,并返回安全的vecadd方法所需的证明。没有契约声明的内核只暴露原始的unsafe启动方法,因为一个裸露的LaunchConfig完全无法说明它要启动的是什么内核。

Tile路径:cutile-rs

cutile-rs的工作层级更高一级。你在tile(数据块)而非标量上执行计算。每个tile block将内核体作为单一逻辑线程运行一次,处理一个子张量的数据,而由编译器决定用多少个实际GPU线程来支撑它。#[cutile::module]宏会将内核的AST嵌入主机二进制文件中,并在内核首次被需要时通过CUDA Tile IR(NVIDIA的tile级编译器IR)进行即时编译。

其依赖要求比SIMT路径更轻量。你只需要计算能力8.0或更高的GPU、CUDA 13.3、稳定版Rust 1.89或更新版本,以及Linux系统,不需要nightly工具链,也不需要自行配置LLVM。

cutile已经发布,因此无需克隆代码:

cargo new vecadd_demo

cd vecadd_demo

cargo add cutile

以下是为tile编写的同样的逐元素加法程序。将其粘贴到src/main.rs中并运行cargo run。

PASSED: all 1024 elements correct

Tile路径在稳定版Rust上得到了同样的结果,其签名也做出了同样的安全性论证。这次没有DisjointSlice。分区(partitioning)操作只对可变张量是必要的,它给每个tile block分配一个可写的子张量,其他任何tile block都不能与之重叠。这种排他性正是&mut本身所保证的。

输入形状中的-1是一个占位符而非具体尺寸。这个维度会在启动时从张量中读取,因此形状可以变化而无需重新编译。

主机端代码中最有意思的一行是.partition([128]),它同时完成了三项工作。首先,它使排他性成为现实:每个tile拥有自己128个元素的数据块,其他tile无法触碰。其次,它确定了启动的几何结构,因为1024除以128等于8个tile组成的网格。

网格大小是从分区推导出来的,而不是单独计算后再与内核的索引方式进行核对。它还提供了B值,这个值在调用处从未被显式写出,因为启动器会从分区中读取tile宽度。这也是为什么一个&mut输出必须先经过分区才能被传递的原因。

再来看启动调用返回的内容。你在主机上调用的add函数,是宏生成的启动器,而非上面的设备函数本身。它取得了所有三个张量的所有权,并在GPU完成计算后将它们以元组形式返回。这就是.first()的作用——从中取出输出结果。

在调用.sync_on(&stream)之前,什么都不会运行。此前的一切都只是惰性描述,只是被记录下来,而非提交执行。这包括ones、zeros、内核调用,乃至拷贝回主机的操作。整个程序是一条链,只有一个同步点。

编译器能捕获什么

两种内核对内存做出了相同的声明:输入是共享的,输出则只属于唯一的写入者。它们的区别仅在于做出这一声明的层级,以及是否需要一个专门设计的类型来实现这一声明。

这一点很重要,因为成千上万个线程会以无法保证的顺序访问相同的缓冲区。当其中两个线程访问同一地址且其中一个在写入时,执行顺序会决定最终结果。这类错误很少能按需复现,往往在通过测试后才会在生产环境中崩溃。

将SIMT内核的输出缓冲区同时作为其自身的输入传入,无法通过编译,无论该内核实际上是否会发生竞争:

error[E0502]: cannot borrow `c_dev` as mutable because it is also borrowed as immutable

Tile一侧同样的别名问题同样无法通过编译:

error[E0382]: use of moved value: `z`

两个示例都在编译期捕获了经典的别名错误,但它们划定界限的位置不同。cuda-oxide在每次启动调用时进行检查,而cutile-rs的所有权跟踪贯穿整个启动边界,这是两者中更强的一种保证。

Tile不会给你任何需要操心的共享内存或线程索引,因为编译器同时掌控了这两者。一个tile block就是单一逻辑线程,因此不存在需要竞争的多个线程。这正是它天生安全的原因,但这也是你所付出的代价。SIMT保留了那种控制权,而如今在SIMT中使用共享内存仍需要unsafe。共享内存是快速SIMT内核的基石,因此让这条路径变得安全是我们正在推进的工作。

两个项目目前的进展

这两个项目都处于早期阶段,都还没有达到生产就绪的水平。cuda-oxide目前是早期alpha版本。cutile-rs进展更靠前一些,已发布在crates.io上,并已在NVIDIA之外得到应用,包括HuggingFace的Grout推理引擎和mistral.rs。功能覆盖尚不完整,API也会持续变动。如果你发现了粗糙之处,我们希望听到你的反馈。

Cargo和crates生态系统给人的印象是上手很容易。而GPU编程历史上一直恰恰相反,缩短这一差距正是我们工作的一部分。SIMT路径目前仍需要固定版本的nightly工具链,而这正是我们希望不再要求用户配置的东西。

Rust在GPU上的应用并非新鲜事。在我们之前,这个领域已经有出色的工作,并且仍在与我们并行推进。cuda-oxide指南中的生态系统附录说明了我们相对于Rust-GPU、rust-cuda、CubeCL等项目所处的位置,我们也一直在与rust-cuda的维护者合作,共同推进这两个项目的成熟。

真正新的是我们为此投入的工程力量,以及对未来方向的清晰规划。

你现在可以做的事

运行SIMT示例:在cuda-oxide中先执行cargo oxide new,再执行cargo oxide run。

运行Tile示例:克隆cutile-rs,然后执行cargo run -p cutile-examples --example hello_world。

阅读文档:cuda-oxide指南和cuTile Rust文档。

阅读论文:《GPU上的无畏并发》(Fearless Concurrency on the GPU)。

提交问题:在cuda-oxide或cutile-rs上告诉我们哪里出了问题,缺少了什么。

加入讨论:在两个仓库的GitHub Discussions中交流,或加入cuda-oxide的Discord。

参加演讲:Melih Elibol将于2026年9月8日至11日在蒙特利尔举行的RustConf 2026大会上发表题为"GPU上的无畏并发"的演讲。NVIDIA还会有其他员工出席,如果你也在现场,欢迎来找我们!

动手试试这些项目,和我们一起参与开发。现在还是早期阶段,一切都是开放的,你现在构建的东西将塑造接下来的发展方向。

Rust社区

NVIDIA很高兴能与Rust社区携手,共同推进原生Rust GPU编程的发展。像rust-cuda、rust-gpu和cudarc这样的项目率先开创了GPU与Rust的结合,这些项目背后的人们,包括VectorWare团队,一直在持续影响着我们对自身工作的思考方式,我们也将继续与Rust社区一起构建这一未来。

Q&A

Q1:CUDA Rust是什么?

A:CUDA Rust是NVIDIA推出的原生GPU编程支持,让开发者可以直接用Rust语言编写GPU内核,并原生编译为PTX,无需依赖其他语言代码的封装。

Q2:cuda-oxide和cutile-rs有什么区别?

A:cuda-oxide对应SIMT编程模型,需要指定单个线程的操作并启动大量线程,需要nightly工具链;cutile-rs对应Tile编程模型,以数据块为单位编程,由编译器决定线程映射,可在稳定版Rust上运行,安全性更高但控制粒度较粗。

Q3:这两个项目现在能用于生产环境吗?

A:目前都不建议用于生产环境。cuda-oxide处于早期alpha阶段,cutile-rs进展更靠前,已发布在crates.io并被HuggingFace的Grout推理引擎、mistral.rs等项目使用,但功能覆盖仍不完整,API也会持续变动。

NVIDIA