CUDA-oxide: Nvidias offizieller Rust-zu-CUDA-Compiler
(nvlabs.github.io)- cuda-oxide ist ein experimenteller Compiler, mit dem sich SIMT-GPU-Kernel in sicherheitsnahen, idiomatischen Rust schreiben lassen und der Standard-Rust-Code direkt zu PTX kompiliert
- Es wird ausschließlich Rust verwendet, ohne DSL oder Bindings zu Fremdsprachen; vorausgesetzt werden Kenntnisse zu Ownership, Traits und Generics, und für das Async-Kapitel auch Wissen über
.await - v0.1.0 ist ein frühes Alpha-Release, daher sollte man mit Bugs, unvollständigen Funktionen und API-Breaking-Changes rechnen
- Das Beispiel wird mit
cargo oxide run vecaddausgeführt; die Funktion#[kernel]innerhalb von#[cuda_module]führt mitthread::index_1d()eine Vektoraddition aus #[cuda_module]bindet Device-Artefakte in das Host-Binary ein und erzeugt typspezifizierte Loader sowie kernelbezogene Ausführungsmethoden
Verwendung und generierter Code
-
Schnellstart
- Nach Erfüllen der Installationsvoraussetzungen wird das Beispiel mit
cargo oxide run vecaddgebaut und ausgeführt - Die Installationsanleitung steht unter prerequisites
- Das Beispiel definiert die Funktion
vecaddmit#[kernel]innerhalb eines#[cuda_module]-Moduls, ermittelt den Index mitthread::index_1d()und schreibta[i] + b[i]inDisjointSlice<f32> - Auf der Host-Seite werden
CudaContext::new(0), der Default-Stream undkernels::load(&ctx)verwendet; der Kernel wird mitDeviceBuffer::from_host,DeviceBuffer::<f32>::zeroedundLaunchConfig::for_num_elems(1024)ausgeführt - Das Ausführungsergebnis wird mit
c.to_host_vec(&stream)geholt undresult[0] == 3.0geprüft
- Nach Erfüllen der Installationsvoraussetzungen wird das Beispiel mit
-
Verhalten von
#[cuda_module]#[cuda_module]bindet die erzeugten Device-Artefakte in das Host-Binary ein- Es erzeugt eine typisierte Funktion
kernels::loadsowie kernelbezogene Ausführungsmethoden - Wenn bestimmte Sidecar-Artefakte geladen oder benutzerdefinierter Ausführungscode erstellt werden müssen, können weiterhin die Low-Level-APIs
load_kernel_moduleundcuda_launch!verwendet werden
Voraussetzungen und Ausrichtung
- cuda-oxide zielt darauf ab, GPU-Kernel mit Rusts Typsystem und Ownership-Modell zu schreiben, wobei Sicherheit ein Ziel erster Ordnung ist
- Da es bei GPUs subtile Besonderheiten gibt, sollte man the safety model lesen
- Es ist keine DSL, sondern ein benutzerdefiniertes
rustc-Codegen-Backend, das reines Rust zu PTX kompiliert - Unterstützt wird asynchrone Ausführung, bei der GPU-Arbeit als Graph verzögert ausgeführter
DeviceOperations aufgebaut, in einen Stream-Pool eingeplant und per.awaitauf Ergebnisse gewartet wird - Vorausgesetzt wird Vertrautheit mit Rusts Ownership, Traits und Generics; die späteren Kapitel zu asynchroner GPU-Programmierung erfordern außerdem Kenntnisse zu
async/.awaitund Runtimes wie tokio - Als Referenzmaterial werden The Rust Programming Language, Rust by Example, Async Book bereitgestellt
- Das Release v0.1.0 befindet sich in einer frühen Alpha-Phase; mit Bugs, unvollständigen Funktionen und API-Breaking-Changes ist zu rechnen
1 Kommentare
Hacker-News-Kommentare
Ich bin vor allem gespannt, wie sich die Build-Zeiten vergleichen. Die meisten Rust-CUDA-Crates verlassen sich auf CMake- oder nvcc-Aufrufe, wodurch das Kompilieren schmerzhaft langsam werden kann
Ich habe erst letzte Woche beim Profiling von Build-Zeiten gesehen, dass Tools wie sccache durch Artefakt-Caching die Rebuild-Zeiten stark verkürzen können, aber die Kosten benutzerdefinierter nvcc-Aufrufe bleiben trotzdem. Zum Beispiel ruft auch candle von Hugging Face bei der Kernel-Kompilierung benutzerdefinierte nvcc-Befehle auf: https://arpadvoros.com/posts/2026/05/05/speeding-up-rust-whi...
Dass die meisten Rust-CUDA-Crates CMake oder nvcc aufrufen und deshalb langsam kompilieren, habe ich persönlich nicht so stark erlebt. Wenn man sich das
cuda_setup-Crate ansieht, das ich für die Verarbeitung von Build-Skripten erstellt habe: Das ist nur ein einfachesbuild.rs, es wird also nur neu kompiliert, wenn sich Dateien ändern, und verglichen mit dem Rust-CPU-Code ist die Kompilierzeit sehr geringDas wäre wirklich schön, aber ich vermute eher, dass es ein komplementäres Werkzeug ist. Ich frage mich auch, wodurch sich cuda-oxide eigentlich abhebt, abgesehen davon, dass NVIDIA die vollständige Kontrolle darüber hat
Tile IR ist etwas höherstufig und daher viel einfacher als Ziel zu verwenden; man verliert eigentlich nur bei Dingen wie Epilog-Fusion
[1] https://docs.nvidia.com/cuda/tile-ir/
[2] https://developer.nvidia.com/cuda/tile
Ich halte das Schreiben von GPU-Kernels für inhärent unsicher. Wegen der Funktionsweise der Hardware und weil man ständig bis ans Limit optimieren muss, ist es einfach zu schwer, dafür eine sichere Sprache zu bauen
cudaFree-Aufrufen zu arbeitenZweitens: Während C++-
void*-Argumente nur ein Pointer-Array sind und lediglich die Anzahl validiert wird, erzwingt man hier die Kernel-Argumente mitcuda_launch!Drittens geht es um Alias-Probleme bei veränderbaren Schreibzugriffen. In C++ wird auch Code kompiliert, in dem zwei oder mehr Threads mit demselben
inachout[i]schreiben, aberDisjointSliceundThreadIndexhaben keine öffentlichen Konstruktoren, und man soll nur die APIs https://github.com/NVlabs/cuda-oxide/blob/2a03dfd9d5f3ecba52...index_1d,index_2d,index_2d_runtimeverwendenViertens kann man in C++
std::stringoder praktisch jeden POD percuda memcpykopieren und damit den Zustand beschädigen, während hier nurDisjointSlice, Skalare und Closures akzeptiert werden https://nvlabs.github.io/cuda-oxide/gpu-programming/memory-a...Mehr Details stehen unter https://nvlabs.github.io/cuda-oxide/gpu-safety/the-safety-mo... und https://nvlabs.github.io/cuda-oxide/gpu-programming/memory-a.... Es fängt natürlich nicht alles ab, aber es scheint viel mehr Leitplanken gegen undefiniertes Verhalten zu geben als bei rohen
.cu-DateienOb es eine gute Sprache für GPU-Programmierung ist, muss sich noch zeigen, aber es würde mich nicht überraschen, wenn man eine halbwegs vernünftige DSL-artige API bauen kann, mit der man sicheren Code schreibt und gleichzeitig all die seltsamen GPU-spezifischen Dinge nutzt. Ist CUDA am Ende nicht genau so etwas?
Für sichere, aber parallele Aufgaben, die sich nicht leicht in Rusts
Send/Sync-Modell einfügen lassen, braucht es etwas unbeholfene KonstruktionenEs wäre gut, wenn sich Speicher- oder Ownership-Modell mit wenig Reibung nutzen ließen. Aber wenn dadurch die Nutzungserfahrung deutlich schlechter wird, will ich das auch nicht
Die Messlatte ist für mich das, was Cudarc aktuell macht: relativ wenig Eingriff in die Speicherverwaltung, eine imperative Syntax als FFI-Wrapper und ein paar Zeilen Build-Skript, die nvcc aufrufen, wenn sich der Kernel ändert
Zur Einordnung: Ich mag Slang ziemlich gern
[0]: https://shader-slang.org/
Zum Beispiel Descriptor Sets, Ressourcenregister oder Dispatch-Beschränkungen
Shader-Sprachen sind funktional außerdem benutzerfreundlicher. Und NVIDIA setzt Slang bereits produktiv ein; diese Leute werden ihre Shader-Pipeline kaum in Rust neu schreiben
Alles, was ich dazu finden kann, ist Folgendes
https://www.adacore.com/case-studies/nvidia-adoption-of-spar...
https://www.youtube.com/watch?v=2YoPoNx3L5E
Ich wollte das ignorieren und trotzdem die Doku lesen, aber gerade als es mit dem benutzerdefinierten IR interessant zu werden begann, kam so ein Satz wie „eine MLIR-Implementierung in C++ mit etwas TableGen dazu, einem Build-System, das das gesamte LLVM kompiliert, und Debugging-Sessions, die einen die eigene Berufswahl hinterfragen lassen“, und danach fiel es mir schwer, diese Branche noch ernst zu nehmen
Das sieht nach genau dem erwartbaren dogfooding eines KI-Hype-Unternehmens aus
cuda-oxide mache den häufigen Fall „ein Thread schreibt ein Element“ strukturell sicher, verlange für seltenere Fälle wie Shared Memory, Warp Shuffles oder Hardware-Intrinsics
unsafemit dokumentierten Verträgen und lasse ganz vorn liegende Features wie TMA, Tensor Cores oder Kommunikation auf Cluster-Ebene vollständig manuell, entsprechend der Hardware-KomplexitätAber das wirkt nicht besonders Rust-typisch. In Rust baut man neue sichere Abstraktionen, wenn bestehende für ein Problem nicht gut passen. Rust for Linux ist dafür ein Beispiel
Wenn es nicht sicher ist, fragt man sich, wozu man dann Rust verwendet. Es ist in Ordnung, Leuten, die das letzte Quäntchen Leistung herausholen wollen, eine unsichere API zu geben, aber das sollte nicht der Standard sein
Das erinnert mich an User-Space-Bibliotheken für APIs wie
io_uringoder Vulkan. Dafür sichere APIs zu entwerfen, ist ziemlich schwierig, und es gab tatsächlich auch unsounde VersucheGleiches gilt für die Serialisierungs-/Byte-Barriere dazwischen
Zum Beispiel frage ich mich, ob Bounds Checks bei Arrays zusätzlichen Registerverbrauch verursachen und dadurch die Kernel-Konkurrenzfähigkeit verringern könnten