プログラミング
Nvidia、RustでのネイティブGPUプログラミングを発表
Nvidia announces native GPU programming in Rust (developer.nvidia.com)
要約
Nvidiaは、GPUカーネルをRustで直接記述できる2つの新しい方法を発表しました。cuda-oxideはSIMTモデルを、cutile-rsはタイルベースモデルをサポートし、どちらもコンパイル時のメモリ安全性を提供します。これにより、Rustの安全性とパフォーマンスをGPU開発にもたらします。
全文翻訳
開発者ツール&テクニック
English
한국어
中文
CUDA Rustの紹介:GPUカーネルを記述するための2つのトラック
2026年9月8日
Sri Koundinyan、Melih Elibol、Jonathan Bentz著
いいね
議論(0)
L
T
F
R
E
AI生成の要約
cuda-oxideは、Pliron IRフレームワークとLLVMを使用して、Rustで記述されたSIMTスタイルのGPUカーネルを直接PTXにコンパイルするカスタムrustcコードジェネレータバックエンドを提供します。
cutile-rsは、コンパイラがCUDA Tile IR JITコンパイルを通じてスレッドマッピングとメモリレイアウトを管理する、安定版RustでのタイルベースGPUプログラミングを可能にします。
両方のプロジェクトはコンパイル時にメモリ安全性を強制します。cuda-oxideはDisjointSliceとlaunch contractsを使用してエイリアシングを防ぎ、cutile-rsはテンソルパーティショニングと所有権を使用して排他的アクセスを保証します。
cuda-oxideは、ピン留めされたナイトリーツールチェーンとLLVMを必要としますが、cutile-rsはCUDA 13.3とカスタムLLVMなしで、安定版Rust 1.89+で動作します。
cutile-rsはcrates.ioで公開されており、HuggingFaceのGrout推論エンジンとmistral.rsですでに使用されていますが、cuda-oxideは初期アルファ版のままです。
NVIDIAは、CUDA Rust、CUDA C++、CUDA Python間の言語間相互運用性をサポートする予定であり、フロントエンドの選択が開発者を他のエコシステムから締め出すことを防ぎます。
次のステップ
cuda-oxideでSIMTサンプルを実行するには、プロジェクトをスキャフォールディングしてcargo oxide runを実行します。
cutile-rsでタイルサンプルを実行するには、リポジトリをクローンしてhello_worldサンプルを実行します。
詳細なガイドとAPIリファレンスのために、cuda-oxideブックとcuTile Rustドキュメントをお読みください。
バグや不足している機能を報告するには、cuda-oxideまたはcutile-rsに問題を提起してください。
両方のリポジトリのcuda-oxide DiscordまたはGitHub Discussionsに参加して、コミュニティと交流してください。
NVIDIA Nemotronによって提供されています。AI生成コンテンツは情報を不完全に要約する場合があります。重要な情報を確認してください。詳細はこちらをご覧ください。
2026年9月、NVIDIAはRustでのネイティブGPUプログラミングに注力することを発表しました。CUDA C++とCUDA Pythonは成熟したエンタープライズグレードのツールチェーンであり、NVIDIAは2027年以降もCUDA Rustを成長させ、成熟させていきます。
AIのシステムレイヤーは、推論エンジン、サービングインフラストラクチャ、ドライバー、エージェントランタイムを網羅しており、モデルやテクニックの変化に伴って常に変化しています。その多くがRustで書かれており、パフォーマンスを犠牲にすることなくコンパイル時に多くの種類のバグを検出します。NVIDIAも同じ理由でそのシフトの一部となっています。Nova LinuxドライバーはRustで書かれています。NVIDIA DynamoはRustコア上に構築されています。NVTXにはRustバインディングがあります。GPUカーネルは例外です。Rustからカーネルを起動できますが、カーネル自体はしばしば別の言語で書く必要があります。NVIDIA CUDA Rustはこのギャップを埋めます。GPUカーネルはRustで記述し、ネイティブにPTXにコンパイルでき、どこか他の場所からのコードのラッパーではありません。
Rustを使用するには2つのトラックがあり、CUDA自体の2つのトラックに対応しています。SIMTは、CUDA C++やnumba-cudaで既に記述しているモデルです。1つのスレッドが何をするかを指定し、数千のスレッドを起動します。タイルは、C++やPythonでも利用可能な新しいプログラミングモデルです。これらのフロントエンドはすべて、1つのタイルのデータが何をするかを言うことができ、Tile IRコンパイラが残りを処理します。構築するものを選択する際には、まずタイルを選択してください。コンパイラが各アーキテクチャにタイルをどのようにマッピングするかを決定するため、ソースコードにアーキテクチャ固有の選択をエンコードする必要がなく、その制御が必要な場合やメモリとスレッドを自分で管理したい場合はSIMTにダウングレードします。
どの言語を選択するかは、どのモデルを選択するかとは別の問題です。既に持っているスタックに最適なCUDAエクスポージャーを使用してください。以下の2つのプロジェクトは、そのスタックがRustである場合のためのものです。言語間の相互運用性をサポートする予定ですので、選択によって他の言語から締め出されることはありません。
以下は、各トラックでの同じカーネルであり、1,024個の浮動小数点数の要素ごとの加算を実行します。どちらも完全なプログラムであり、どちらも実行され、同じ行を出力するため、並べて読んで変更点を確認できます。
SIMTトラック:cuda-oxide
cuda-oxideはカスタムrustcコードジェネレータバックエンドです。コンパイルをインターセプトし、#[kernel]関数をRust MIR、コミュニティPliron IRフレームワーク、およびLLVM IRを経由してPTXにルーティングし、残りは標準バックエンドに渡します。Pliron上のGPUダイアレクトは私たちのものです。ダイアレクトとすべての変換は、標準のLLVMバックエンドが引き継ぐまでRust内に留まります。Linux、コンピュート能力8.0以上のGPU、CUDAツールキット(12.x以降)、clangとそのlibclangヘッダー、およびピン留めされたナイトリーツールチェーンが必要です。cargo oxide doctorは、オプションのシステムLLVMを含むすべてをチェックします。
ビルドを駆動するCargoサブコマンドであるcargo-oxideをインストールします。
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はコードジェネレータバックエンドをビルドするため、時間がかかることを予想してください。後続の実行はキャッシュを再利用します。
1024個すべての要素が正しい:PASSEDと表示されます。これは、cargo oxide newが書き込んだものとまったく同じで、コメントが追加された完全なプログラムです。
use cuda_device::{kernel, launch_bounds, launch_contract, thread, DisjointSlice};
use cuda_host::cuda_module;
use cuda_core::{CudaContext, DeviceBuffer, LaunchConfig1D};
// === DEVICE CODE - everything in here is compiled to PTX ===
// The macro also generates the host-side API used further down:
// `load`, `prepare_vecadd`, and the safe `vecadd` launch method.
#[cuda_module]
mod kernels {
use super::*;
#[kernel] // GPU entry point
#[launch_bounds(256)] // max threads per block; lets the compiler budget registers
#[launch_contract(domain = 1, block = (256, 1, 1))] // indexes in 1-D, 256-thread blocks
pub fn vecadd(a: &[f32], b: &[f32], mut c: DisjointSlice<f32>) {
let idx = thread::index_1d();
let idx_raw = idx.get(); // the plain usize, for reading the inputs
if let Some(c_elem) = c.get_mut(idx) {
*c_elem = a[idx_raw] + b[idx_raw];
}
}
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
// === HOST SETUP - device, stream, and buffers ===
let ctx = CudaContext::new(0)?;
let stream = ctx.default_stream();
const N: usize = 1024;
let a_host: Vec<f32> = (0..N).map(|i| i as f32).collect();
let b_host: Vec<f32> = (0..N).map(|i| (i * 2) as f32).collect();
let a_dev = DeviceBuffer::from_host(&stream, &a_host)?;
let b_dev = DeviceBuffer::from_host(&stream, &b_host)?;
let mut c_dev = DeviceBuffer::<f32>::zeroed(&stream, N)?;
// === LOAD, PREPARE, LAUNCH ===
// SAFETY: this package owns the embedded device bundle produced for the
// kernels module above.
let module = unsafe { kernels::load(&ctx)? };
// 4 blocks of 256 threads, 0 bytes of dynamic shared memory. `prepare_vecadd`
// checks that against the contract above and against the live device limits.
// The safe `vecadd` below takes that token where a raw config would go.
let prepared = module.prepare_vecadd(LaunchConfig1D::new((N as u32).div_ceil(256), 256, 0))?;
module.vecadd(&stream, &prepared, &a_dev, &b_dev, &mut c_dev)?;
// === READ BACK AND VERIFY ===
// Copies down and synchronizes, so the launch has finished by the time
// `c_host` can be read.
let c_host = c_dev.to_host_vec(&stream)?;
let errors = (0..N)
.filter(|&i| (c_host[i] - (a_host[i] + b_host[i])).abs() > 1e-5)
.count();
if errors == 0 {
println!("PASSED: all {} elements correct", N);
} else {
eprintln!("FAILED: {} errors", errors);
std::process::exit(1);
}
Ok(())
}
ホストコードとデバイスコードは1つのファイルに共存し、1つのコマンドでビルドされ、別のカーネルクレートを必要としません。カーネルシグネチャを最初に読みます。なぜなら、それは全体の安全性に関する議論を担っているからです。aとbは通常の共有スライスであり、すべてのスレッドが読み取ることができます。cはDisjointSlice<f32>であり、各スレッドにその要素への排他的アクセスを渡し、それ以外は何もしません。これは、&mut [f32]がこのジョブに適さない形状であるためです。すべてのスレッドが同じ&mutを必要としますが、Rustはそれを正しく拒否します。DisjointSliceは、その1つのミュータブルな借用をスレッドごとの断片に分割します。thread::index_