9月8日、NVIDIAが「Introducing CUDA Rust: Two Tracks for Writing GPU Kernels」と題した記事を公開した。この記事では、RustでGPUカーネルを直接記述するための公式フレームワーク「CUDA Rust」と、その2つのプログラミングモデルについて詳しく紹介されている。
RustでGPUカーネルが書けるようになった
Rustの採用はNVIDIA内部でも進んでいる。Nova LinuxドライバーはRustで書かれ、推論インフラのNVIDIA DynamoもRustコアで構築されている。しかしGPUカーネル——実際にGPU上で動く演算の核心部分——だけは例外だった。Rustからカーネルを起動することはできても、カーネル本体は別言語(主にCUDA C++)で書く必要があった。
CUDA Rustはそのギャップを埋める。 RustでGPUカーネルを直接記述し、PTX(NVIDIAのGPU向け中間表現。C++やRustから生成されたコードをGPUが実行できる形式に変換する仮想ISA)にネイティブコンパイルできる。他言語コードのラッパーではない。
RustによるGPUプログラミング自体は新しい取り組みではなく、Rust-GPU、rust-cuda、CubeCLなどのコミュニティ主導プロジェクトが先行している。NVIDIAが今このタイミングで公式フレームワークを投入した背景には、AIワークロードの爆発的拡大に伴うGPUカーネル開発者層の拡大への必要性と、メモリ安全性への業界全体の関心の高まりがある。NVIDIAはrust-cudaのメンテナーとも協力しながら開発を進めており、既存エコシステムとの棲み分けはcuda-oxideのエコシステム付録に整理されている。
2つのトラック:SIMTとTile
CUDA Rustには2つのプログラミングモデルが用意されている。これは「1つに統合する」のではなく意図的な設計だ。GPUプログラミングには本来、低レベルなスレッド制御が必要な場面と、それを抽象化して生産性を上げたい場面の両方があり、1つのモデルでどちらも最適に扱うのは難しい。
SIMTモデルは従来のCUDA C++と同じ考え方だ。1スレッドが何をするかを記述し、それを数千並列で走らせる。Tileモデルは新しいアプローチで、「データの塊(タイル)」単位で処理を記述する。実際のスレッド配置はコンパイラが決定するため、アーキテクチャ固有の記述が不要になる。
NVIDIAの推奨は「まずTileを選べ」だ。アーキテクチャをまたいだポータビリティが高く、スレッド管理やメモリ管理を自前でやる必要がある場合のみSIMTに降りる、という使い分けを想定している。
SIMTトラック:cuda-oxide
cuda-oxideは、Rustのカスタムコード生成バックエンドとして動作する。#[kernel]アトリビュートを付けた関数をRustのMIR(Mid-level Intermediate Representation:RustコンパイラがLLVMに渡す前に生成する中間表現)経由でPTXにコンパイルし、ホスト側コードは通常のRustバックエンドが処理する。
動作要件はLinux、compute capability 8.0以上のGPU、CUDA Toolkit 12.x以上、clangとそのヘッダ、そしてピン留めされたnightlyツールチェーンだ。
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 run
初回ビルドはコード生成バックエンドのビルドも含むため時間がかかるが、以降はキャッシュが使われる。
生成されたテンプレートが実行するのは1,024要素のベクター加算で、PASSED: all 1024 elements correctと出力される。カーネルのシグネチャが安全性の要を担っている点が特徴的だ。
pub fn vecadd(a: &[f32], b: &[f32], mut c: DisjointSlice<f32>) {
let idx = thread::index_1d();
let idx_raw = idx.get();
if let Some(c_elem) = c.get_mut(idx) {
*c_elem = a[idx_raw] + b[idx_raw];
}
}
入力のaとbは全スレッドが読める通常のスライス。出力のcは**DisjointSlice<f32>**という専用型で、各スレッドに自分の要素だけへの排他アクセスを与える。&mut [f32]を使えないのは、複数スレッドが同じ&mutを持つことをRustが正しく拒否するからだ。DisjointSliceはその1つの可変借用をスレッドごとに分割する。
また#[launch_contract]でカーネルの起動条件を宣言しておくと、prepare_vecaddが実際のデバイス制限と照合して検証済みトークンを返す。これを安全な起動メソッドが要求する仕組みで、コントラクトのないカーネルはunsafeな起動メソッドしか持たない。
Tileトラック:cutile-rs
cutile-rsはSIMTトラックより一段高い抽象レベルで動作する。スカラーではなくタイル単位で演算を記述し、#[cutile::module]マクロがカーネルのASTをホストバイナリに埋め込み、初回起動時にCUDA Tile IR経由でJITコンパイルする。
要件はSIMTトラックより軽い。 nightlyツールチェーンもLLVMも不要で、安定版Rust 1.89以上、CUDA 13.3、compute capability 8.0以上のGPUとLinuxがあれば動く。crates.ioに公開済みなのでリポジトリをクローンする必要もない。なお、SIMTトラックがCUDA 12.x系を要求するのに対しTileトラックがCUDA 13.3を要求するのは、Tile IRが新しいランタイムAPIに依存しているためだ。
cargo new vecadd_demo
cargo add cutile
同じベクター加算をTileで書くとこうなる:
#[cutile::entry()]
fn add<const B: i32>(
z: &mut Tensor<f32, { [B] }>,
x: &Tensor<f32, { [-1] }>,
y: &Tensor<f32, { [-1] }>,
) {
let tx = load_tile_like(x, z);
let ty = load_tile_like(y, z);
z.store(tx + ty);
}
ホスト側では.partition([128])が3つの仕事を同時に行う。各タイルへの排他的所有権の付与、グリッドサイズの確定(1,024÷128=8タイル)、そしてテンプレートパラメータBの供給だ。
もう一つの特徴は遅延実行だ。ones、zeros、カーネル呼び出し、ホストへのコピーバックまで、.sync_on(&stream)が呼ばれるまで何も実際には実行されない。
cutile-rsはすでにNVIDIA外部でも採用されている。HuggingFaceの推論エンジンGrout(HuggingFaceが開発するRust製LLM推論サーバー)と、人気のRust製LLM推論ライブラリmistral.rsがそれにあたる。リリース直後から実プロジェクトへの採用が進んでいることは、APIの成熟度を示す一つの指標だ。
コンパイラが検出する競合バグ
GPUプログラミングで最も厄介なのはメモリの競合(レースコンディション)だ。何千ものスレッドが保証なき順序で同じバッファにアクセスするとき、2スレッドが同じアドレスを読み書きすると結果が不定になる。こうしたバグは再現性が低く、テストをすり抜けて本番で発症しやすい。
CUDA Rustはこれをコンパイル時に検出する。SIMTトラックで出力バッファを入力と入力の両方に渡そうとすると:
error[E0502]: cannot borrow `c_dev` as mutable because it is also borrowed as immutable
Tileトラックで同じことをしようとすると:
error[E0382]: use of moved value: `z`
どちらも古典的なエイリアシングのミスをコンパイル時に止める。 Tileトラックはさらに強く、テンソルの所有権が起動境界をまたいで追跡されるため、GPUが実行中のテンソルを誤って再利用することも防がれる。
現状と位置づけ
両プロジェクトともまだ初期段階でプロダクション対応ではない。cuda-oxideはアーリーアルファ。cutile-rsはcrates.io公開済みで前述の外部プロジェクトに採用されているが、APIは変わりうる。
SIMTトラックがまだpinned nightlyを要求している点は、NVIDIAも課題と認識しており解消を目指している。NVIDIAが公式としてRustによるGPUカーネル開発を支援する姿勢を明確にしたことは、エコシステム全体にとって大きな転換点だ。
詳細はIntroducing CUDA Rust: Two Tracks for Writing GPU Kernelsを参照していただきたい。