CUDA-oxide: Nvidia公式のRust-to-CUDAコンパイラ
(nvlabs.github.io)- cuda-oxideは、安全性を重視したイディオマティックなRustでSIMT GPUカーネルを記述し、標準的なRustコードをPTXへ直接コンパイルする実験的コンパイラ
- DSLや異言語バインディングなしでRustのみを使用し、所有権・トレイト・ジェネリクスの理解が前提で、asyncの章では
.awaitの知識も必要 - v0.1.0は初期アルファリリースのため、バグ、未完成機能、APIの破壊的変更を想定する必要がある
- 例は
cargo oxide run vecaddで実行し、#[cuda_module]内の#[kernel]関数がthread::index_1d()でベクトル加算を行う #[cuda_module]はデバイスアーティファクトをホストバイナリに含め、型付きローダーとカーネルごとの実行メソッドを生成する
使い方と生成コード
-
クイックスタート
- インストール前提条件を満たしたうえで、
cargo oxide run vecaddでサンプルをビルドして実行する - インストール案内はprerequisitesにある
- サンプルは
#[cuda_module]モジュール内に#[kernel]関数vecaddを定義し、thread::index_1d()でインデックスを取得してa[i] + b[i]をDisjointSlice<f32>に書き込む - ホスト側では
CudaContext::new(0)、デフォルトストリーム、kernels::load(&ctx)を使い、DeviceBuffer::from_host、DeviceBuffer::<f32>::zeroed、LaunchConfig::for_num_elems(1024)でカーネルを実行する - 実行結果を
c.to_host_vec(&stream)で取得し、result[0] == 3.0を確認する
- インストール前提条件を満たしたうえで、
-
#[cuda_module]の動作#[cuda_module]は生成されたデバイスアーティファクトをホストバイナリに含める- 型付きの
kernels::load関数とカーネルごとの実行メソッドを生成する - 特定のsidecarアーティファクトを読み込んだり、カスタム実行コードを作成したりする必要がある場合は、低レベルの
load_kernel_moduleとcuda_launch!APIも引き続き利用できる
前提と方向性
- cuda-oxideは、Rustの型システムと所有権モデルでGPUカーネルを記述することを目指し、安全性を最優先の目標にしている
- GPUには微妙な点があるため、the safety modelを読む必要がある
- DSLではなく、純粋なRustをPTXへコンパイルするカスタム
rustcコード生成バックエンドである - GPU処理を遅延実行される
DeviceOperationグラフとして構成し、ストリームプールにスケジューリングして、.awaitで結果を待つ非同期実行をサポートする - Rustの所有権、トレイト、ジェネリクスに習熟していることを前提とし、その後のasync GPUプログラミングの章では
async/.awaitやtokioのようなランタイムの知識も必要になる - 参考資料としてThe Rust Programming Language、Rust by Example、Async Bookが提供されている
- v0.1.0リリースは初期アルファ段階であり、バグ、未完成機能、APIの破壊的変更を想定する必要がある
1件のコメント
Hacker Newsのコメント
特にビルド時間がどう比較されるのか気になる。多くのRust CUDAクレートはCMakeやnvccの呼び出しに依存していて、コンパイルがつらいほど遅くなることがある
ちょうど先週ビルド時間をプロファイリングしていて、sccacheのようなツールが生成物キャッシュで再ビルド時間を大きく減らせるのを見たが、それでもカスタムnvcc呼び出しのコストは残る。たとえばHugging Faceのcandleもカーネルコンパイルでカスタムnvccコマンドを呼んでいる: https://arpadvoros.com/posts/2026/05/05/speeding-up-rust-whi...
Rust CUDAクレートの大半がCMakeやnvccを呼ぶせいでコンパイルが遅い、という点は個人的にはあまり経験していない。ビルドスクリプトを処理するために作った
cuda_setupクレートを見ると、単なるbuild.rsなのでファイルが変わった時だけ再コンパイルされるし、RustのCPU側コードと比べればコンパイル時間はごく小さいそうなれば本当に良いが、個人的にはおそらく補完材に近いのではないかと思う。cuda-oxideを差別化する要素が何なのか、そしてNVIDIAが完全にコントロールしているという点以上に何があるのかも気になる
Tile IRはもう少し高水準なので、ターゲットにするのはずっと簡単で、エピローグ融合のようなところでだけ不利になる
[1] https://docs.nvidia.com/cuda/tile-ir/
[2] https://developer.nvidia.com/cuda/tile
GPUカーネルを書くことは本質的に安全ではないと思う。ハードウェアの動作の仕方や、常に限界まで最適化しなければならないという点のせいで、安全な言語を作るのはあまりにも難しい
cudaFreeを手動で呼ぶ方式とは違って、use-after-free とdropセマンティクスを扱える第二に、C++の
void*引数はポインタ配列で個数しか検証しないが、ここではcuda_launch!でカーネル引数を強制している第三に、可変書き込みのエイリアシング問題だ。C++では2つ以上のスレッドが同じ
iでout[i]に書くコードもコンパイルできるが、DisjointSliceとThreadIndexには公開コンストラクタがなく、https://github.com/NVlabs/cuda-oxide/blob/2a03dfd9d5f3ecba52... のindex_1d,index_2d,index_2d_runtimeAPIだけを使うようになっている第四に、C++では
std::stringや事実上どんなPODでもcuda memcpyして状態を壊せるが、ここではDisjointSlice、スカラー、クロージャしか受け付けない https://nvlabs.github.io/cuda-oxide/gpu-programming/memory-a...詳しくは https://nvlabs.github.io/cuda-oxide/gpu-safety/the-safety-mo... と https://nvlabs.github.io/cuda-oxide/gpu-programming/memory-a... にある。もちろん全部を捕まえられるわけではないが、生の
.cuファイルよりは未定義動作を防ぐガードレールをずっと多く与えているように見えるGPUプログラミングに向いた言語かどうかはまだ分からないが、GPU特有の奇妙な要素をすべて活用しつつ安全なコードを書くための、そこそこまともなDSL風APIを作れても驚かない。CUDAも結局はそういうものではないかと思う
Rustの
Send/Syncモデルに簡単には収めにくかった、安全ではあるが並列的な処理には、少し不格好さが必要になるメモリモデルや所有権モデルを低い摩擦で活用できるなら良い。だが、そのせいで使い勝手が大きく悪くなるなら、そういうやり方は望まない
基準線は今のCudarcのやり方だと思う。メモリ管理はあまり介入せず、FFIを包んだ命令型の文法と、カーネルが変わった時にnvccを呼ぶビルドスクリプトが数行ある程度だ
ちなみにSlangはかなり気に入っている
[0]: https://shader-slang.org/
たとえばディスクリプタセット、リソースレジスタ、ディスパッチ制限などだ
シェーディング言語は機能面でもよりユーザーフレンドリーだ。そのうえNVIDIAはすでにSlangを本番環境で使っており、その人たちがシェーダパイプラインをRustで書き直すことはないだろう
見つけられるのは以下の内容だけだ
https://www.adacore.com/case-studies/nvidia-adoption-of-spar...
https://www.youtube.com/watch?v=2YoPoNx3L5E
それでも無視してドキュメントを読もうとしたが、カスタムIRが出てきて面白くなりかけた瞬間、「MLIRの実装はC++にTableGenを添えたもので、LLVM全体をコンパイルしなければならないビルドシステムと、キャリア選択を疑いたくなるようなデバッグセッションがある」といった調子の文を見て、もうこの業界を真面目に受け取る気がなくなった
これはAI過剰宣伝企業に期待される、まさに自社製品の利用に見える
cuda-oxideは、よくある「1スレッドが1要素を書く」というケースを構造的に安全にし、共有メモリ・ワープシャッフル・ハードウェア組み込み関数のような珍しいケースでは、文書化された契約付きの
unsafeを要求し、TMA・テンソルコア・クラスタレベル通信のような最先端機能は、ハードウェアの複雑さに合わせて完全手動のままにしているというだがこれはあまりRustらしくない。Rustでは既存の抽象化が問題にうまく合わないなら、新しい安全な抽象化を作る。Rust for Linuxがその例だ
安全でないのなら、Rustを使う理由は何なのか疑問だ。最後の性能を絞り出したい人にunsafe APIを提供するのは構わないが、それがデフォルトであってはならない
io_uringやVulkanのようなAPIのユーザー空間ライブラリと比較してしまう。そうしたもの向けの安全なAPI設計はかなり難しく、実際soundではない試みもあるその間のシリアライズ/バイト境界も同様だ
たとえば配列境界チェックが追加のレジスタ使用を招いて、カーネルの同時実行性を下げる可能性があるのか気になる