Nvidia announces native GPU programming in Rust

来源:HackerNews

Nvidia 宣布原生支持 Rust 编写 GPU 内核:CUDA Rust 的两条技术路线

背景与概述

长期以来,GPU 编程几乎等同于 CUDA C/C++。尽管 CUDA 生态在性能与工具链上极为成熟,但其语言绑定始终局限于 C、C++ 以及 Fortran 等传统系统语言。随着 Rust 在系统编程领域的快速崛起——尤其是在操作系统、嵌入式和高性能计算场景中的广泛应用——开发者社区对“用 Rust 写 GPU 内核”的呼声日益高涨。

过去,社区曾通过 rust-cuda、cust、accel 等第三方项目尝试打通 Rust 与 CUDA 之间的壁垒,但这些方案大多依赖非官方的 LLVM 后端或手写绑定,存在维护成本高、版本滞后、与官方工具链脱节等问题。Nvidia 此次在官方开发者博客上发布《Introducing CUDA Rust: Two Tracks for Writing GPU Kernels》,标志着 GPU 巨头正式将 Rust 纳入其原生编程语言版图。

这一动作的意义不仅在于多了一种语言选择,更在于它释放出一个明确信号:Rust 正在从“系统层”向“加速计算层”渗透,而 Nvidia 愿意为此提供官方支持。对于中国开发者而言,这意味着未来在 AI 基础设施、自动驾驶、科学计算等领域,可以用更安全、更现代的语言栈来编写高性能 GPU 代码。

核心内容

根据 Nvidia 官方博客的表述,CUDA Rust 并非单一方案,而是提供了两条并行的技术路线,分别面向不同的使用场景与成熟度需求。

路线一:cuda-oxide——基于 rustc 代码生成后端的原生集成

第一条路线名为 cuda-oxide,其核心思路是将 CUDA 的代码生成能力直接接入 Rust 编译器 rustc 的后端。开发者可以用纯 Rust 编写 GPU 内核,编译器在编译阶段将 Rust 的 MIR(中级中间表示)或 LLVM IR 转换为 PTX(Parallel Thread Execution)或 SASS,从而生成可在 Nvidia GPU 上执行的二进制代码。

这条路线追求的是“原生体验”:无需手写 C++ 胶水代码,类型系统、所有权模型和生命周期检查都能在内核代码中延续。它更适合追求长期可维护性和安全性的项目。

路线二:cuda-bindings——面向现有 CUDA 生态的 Rust 绑定

第二条路线是 cuda-bindings,它并不试图重新实现代码生成,而是为现有的 CUDA Runtime API、Driver API 以及部分库(如 cuBLAS、cuDNN)提供高质量的 Rust FFI 绑定。开发者仍然用 CUDA C++ 编写内核,但主机端(host)的调度、��存管理和错误处理可以用 Rust 完成。

这条路线成熟度更高、落地更快,适合那些已有大量 CUDA C++ 内核资产、但希望用 Rust 重构主机端逻辑的团队。

两条路线的定位差异

维度cuda-oxidecuda-bindings
内核语言纯 RustCUDA C++
成熟度实验性/早期相对成熟
迁移成本需重写内核仅重写主机端
安全性全栈 Rust 安全保障主机端安全,内核端仍依赖 C++
适用场景新项目、长期演进存量项目、渐进式改造

与社区方案的对比

与 rust-cuda 等社区项目相比,官方方案的最大优势在于工具链一致性和版本同步。社区项目往往需要锁定特定的 nightly Rust 版本,且难以跟上 CUDA 新特性的发布节奏。官方介入后,开发者可以期待更稳定的 ABI、更完善的文档以及更及时的 bug 修复。

对中国开发者的现实意义

在国内,AI 芯片与 GPU 加速计算正处于高速发展期,但底层软件栈长期依赖 C/C++,人才门槛高、内存安全问题频发。CUDA Rust 的出现,为国内团队提供了一条“用 Rust 写加速计算”的官方路径,尤其适合对可靠性和安全性要求极高的自动驾驶、金融风控和科学计算场景。

技术分析

从技术实现角度看,cuda-oxide 的关键挑战在于如何将 Rust 的所有权与借用检查语义映射到 GPU 的 SIMT(单指令多线程)执行模型上。GPU 内核中常见的共享内存、线程束同步、原子操作等概念,在 Rust 中并没有直接对应物。因此,Nvidia 很可能通过提供一组 #[kernel] 属性宏和 unsafe 边界来桥接两种语义。

一个典型的 Rust GPU 内核可能长这样:

use cuda_rust::prelude::*;

#[kernel]
fn vector_add(a: &[f32], b: &[f32], c: &mut [f32]) {
    let idx = thread_idx_x() + block_idx_x() * block_dim_x();
    if idx < a.len() as u32 {
        c[idx] = a[idx] + b[idx];
    }
}

主机端则通过 Rust 的异步运行时或 cuda-bindings 来调度:

let a = DeviceBuffer::from_slice(&host_a)?;
let b = DeviceBuffer::from_slice(&host_b)?;
let mut c = DeviceBuffer::zeros(host_a.len())?;
vector_add::launch(&a, &b, &mut c, grid, block)?;

而 cuda-bindings 则更接近传统 FFI 风格,通过 unsafe 块调用 cuLaunchKernel 等 API,同时用 Rust 的 Result 类型封装 CUDA 错误码,避免 C 风格错误处理带来的隐患。

从架构上看,两条路线并非互斥,而是可以混合使用:用 cuda-bindings 管理主机端资源,用 cuda-oxide 编写新内核,逐步替换旧有 C++ 内核。这种渐进式策略,正是 Nvidia 降低迁移门槛的巧妙之处。

实践建议

对于想要尝鲜的开发者,建议按以下步骤上手:

  1. 评估现有资产:如果项目已有大量 CUDA C++ 内核,优先选择 cuda-bindings,先重构主机端逻辑,验证 Rust 与 CUDA 的互操作性。
  2. 从 `cuda-bindings` 起步:安装官方提供的 crate,编写一个简单的向量加法示例,熟悉 DeviceBuffer、Stream 等抽象。
  3. 小范围试点 `cuda-oxide`:选择计算密集但逻辑简单的内核(如逐元素运算、归约)用 Rust 重写,对比性能与开发体验。
  4. 关注工具链版本:由于 cuda-oxide 仍处于早期阶段,建议锁定官方推荐的 Rust 版本,避免 nightly 特性带来的不稳定性。
  5. 参与社区反馈:Nvidia 开发者论坛和 GitHub 仓库是获取最新进展和提交 issue 的主要渠道,国内开发者可积极反馈中文场景下的需求。

总结

Nvidia 官方支持 Rust 编写 GPU 内核,是 Rust 生态向加速计算领域迈出的标志性一步。cuda-oxide 与 cuda-bindings 两条路线分别代表了“原生重构”与“渐进绑定”两种哲学,既照顾了存量项目的迁移现实,也为新项目提供了全栈安全的可能。尽管当前成熟度仍有待提升,但官方背书本身已足以让这一方向值得持续关注。对于中国开发者而言,尽早熟悉 CUDA Rust,或许就是抢占下一代高性能计算语言红利的关键一步。