CUDA-oxide : le compilateur officiel Rust-to-CUDA de Nvidia
(nvlabs.github.io)- cuda-oxide est un compilateur expérimental qui permet d’écrire des kernels GPU SIMT en Rust idiomatique, proche de la sécurité, et de compiler directement du code Rust standard en PTX
- Il utilise uniquement Rust, sans DSL ni bindings vers un langage externe, et suppose une compréhension de l’ownership, des traits et des génériques ; la section async requiert aussi des connaissances sur
.await - La v0.1.0 est une version alpha initiale, il faut donc s’attendre à des bugs, des fonctionnalités incomplètes et des changements d’API cassants
- L’exemple s’exécute avec
cargo oxide run vecadd, et la fonction#[kernel]à l’intérieur de#[cuda_module]effectue une addition de vecteurs avecthread::index_1d() #[cuda_module]intègre les artifacts device dans le binaire hôte, et génère un chargeur typé ainsi que des méthodes d’exécution par kernel
Mode d’utilisation et code généré
-
Démarrage rapide
- Une fois les prérequis d’installation remplis, l’exemple se compile et s’exécute avec
cargo oxide run vecadd - Les instructions d’installation se trouvent dans prerequisites
- L’exemple définit la fonction
#[kernel]vecadddans un module#[cuda_module], récupère l’index avecthread::index_1d()et écrita[i] + b[i]dansDisjointSlice<f32> - Côté hôte, il utilise
CudaContext::new(0), le flux par défaut,kernels::load(&ctx), puis exécute le kernel avecDeviceBuffer::from_host,DeviceBuffer::<f32>::zeroedetLaunchConfig::for_num_elems(1024) - Le résultat est récupéré avec
c.to_host_vec(&stream)et vérifié avecresult[0] == 3.0
- Une fois les prérequis d’installation remplis, l’exemple se compile et s’exécute avec
-
Fonctionnement de
#[cuda_module]#[cuda_module]intègre les artifacts device générés dans le binaire hôte- Il génère une fonction typée
kernels::loadet des méthodes d’exécution pour chaque kernel - Lorsqu’il faut charger un artifact sidecar spécifique ou créer un code d’exécution personnalisé, les API de plus bas niveau
load_kernel_moduleetcuda_launch!restent utilisables
Prérequis et orientation
- cuda-oxide vise à permettre l’écriture de kernels GPU à l’aide du système de types de Rust et de son modèle d’ownership, avec la sécurité comme objectif de premier plan
- Les GPU ayant des subtilités propres, il est nécessaire de lire the safety model
- Il ne s’agit pas d’un DSL, mais d’un backend personnalisé de génération de code
rustcqui compile du Rust pur en PTX - Il prend en charge une exécution asynchrone en composant le travail GPU sous forme de graphe de
DeviceOperationà exécution différée, planifié sur un pool de streams, puis en attendant le résultat avec.await - Il part du principe que vous êtes à l’aise avec l’ownership, les traits et les génériques de Rust ; les chapitres suivants sur la programmation GPU async demandent aussi des connaissances sur
async/.awaitet des runtimes comme tokio - Les références proposées sont The Rust Programming Language, Rust by Example, Async Book
- La version v0.1.0 est une release alpha initiale, et il faut s’attendre à des bugs, des fonctionnalités incomplètes et des changements d’API cassants
1 commentaires
Commentaires sur Hacker News
Je suis surtout curieux de voir comment ça se comparera sur les temps de build. La plupart des crates Rust CUDA reposent sur CMake ou des appels à nvcc, ce qui peut rendre la compilation péniblement lente
Justement, la semaine dernière, en profilant des temps de build, j’ai vu que des outils comme sccache peuvent fortement réduire les temps de recompilation via le cache des artefacts, mais le coût des appels nvcc personnalisés reste là. Par exemple, candle de Hugging Face appelle aussi une commande nvcc personnalisée pour compiler les kernels : https://arpadvoros.com/posts/2026/05/05/speeding-up-rust-whi...
Personnellement, je n’ai pas tant souffert que ça du fait que la plupart des crates Rust CUDA appellent CMake ou nvcc et compilent lentement. Si on regarde la crate
cuda_setup, conçue pour gérer les scripts de build, ce n’est qu’un simplebuild.rs, donc ça ne recompile que lorsque les fichiers changent, et le temps de compilation reste très faible par rapport au code Rust côté CPUCe serait vraiment bien, mais personnellement je pense que ce sera plutôt un complément. Je me demande aussi ce qui différencie cuda-oxide, au-delà du fait qu’il soit entièrement contrôlé par NVIDIA
Tile IR est un peu plus haut niveau, donc bien plus facile à cibler, au prix de pertes surtout sur des points comme la fusion d’épilogue
[1] https://docs.nvidia.com/cuda/tile-ir/
[2] https://developer.nvidia.com/cuda/tile
J’ai l’impression qu’écrire des kernels GPU est intrinsèquement non sûr. Entre le fonctionnement du matériel et le besoin constant d’optimiser à l’extrême, ça semble trop difficile d’en faire un langage sûr
cudaFree, ça gère les use-after-free et la sémantique de dropEnsuite, là où en C++ les arguments
void*ne sont qu’un tableau de pointeurs dont on ne vérifie que le nombre, ici les arguments du kernel sont imposés viacuda_launch!Troisièmement, il y a le problème de l’aliasing lors des écritures mutables. En C++, un code où plusieurs threads écrivent dans
out[i]avec le mêmeicompile quand même, maisDisjointSliceetThreadIndexn’ont pas de constructeurs publics, et on est limité aux API https://github.com/NVlabs/cuda-oxide/blob/2a03dfd9d5f3ecba52...index_1d,index_2d,index_2d_runtimeQuatrièmement, en C++, on peut faire un
cuda memcpyd’unstd::stringou d’à peu près n’importe quel POD et corrompre son état, alors qu’ici seulsDisjointSlice, les scalaires et les closures sont acceptés https://nvlabs.github.io/cuda-oxide/gpu-programming/memory-a...Plus de détails ici : https://nvlabs.github.io/cuda-oxide/gpu-safety/the-safety-mo... et https://nvlabs.github.io/cuda-oxide/gpu-programming/memory-a.... Bien sûr, ça ne capture pas tout, mais on dirait que ça fournit bien plus de garde-fous contre le comportement indéfini que des fichiers
.cubrutsReste à voir si c’est un langage pratique pour programmer des GPU, mais je ne serais pas surpris qu’on puisse construire une API un peu DSL qui permette d’écrire du code sûr tout en exploitant toutes les bizarreries propres aux GPU. Au fond, CUDA n’est-il pas déjà un peu cela ?
Pour les tâches sûres mais parallèles qui s’intègrent mal dans le modèle
Send/Syncde Rust, il a fallu accepter une certaine maladresseSi on peut profiter du modèle mémoire ou de propriété avec peu de friction, tant mieux. Mais si cela rend l’expérience d’usage beaucoup plus pénible, je n’en veux pas
Pour moi, la référence, c’est ce que fait actuellement Cudarc. La gestion mémoire n’est pas très envahissante, et on a essentiellement une syntaxe impérative qui encapsule la FFI avec quelques lignes de script de build qui appellent nvcc quand le kernel change
Pour info, j’aime bien Slang
[0]: https://shader-slang.org/
Par exemple les descriptor sets, les registres de ressources ou les limites de dispatch
Les langages de shading sont aussi plus conviviaux sur le plan des fonctionnalités. Et puis NVIDIA utilise déjà Slang en production, donc ces équipes ne vont probablement pas réécrire leurs pipelines de shaders en Rust
Je n’ai trouvé que ceci
https://www.adacore.com/case-studies/nvidia-adoption-of-spar...
https://www.youtube.com/watch?v=2YoPoNx3L5E
J’ai quand même essayé d’ignorer ça pour lire la documentation, et au moment où ça commençait à devenir intéressant avec l’IR personnalisé, je suis tombé sur une phrase du style “une implémentation MLIR, c’est du C++ avec du TableGen, un système de build qui oblige à compiler tout LLVM, et des sessions de débogage qui vous font remettre en question vos choix de carrière”, et là j’ai eu du mal à continuer à prendre ce secteur au sérieux
Ça ressemble exactement au bon niveau de dogfooding qu’on peut attendre d’une entreprise en plein battage autour de l’IA
cuda-oxide rend structurellement sûr le cas courant où “un thread écrit un élément”, et impose du
unsafeavec des contrats documentés pour les cas plus rares comme la mémoire partagée, les warp shuffles ou les intrinsics matérielles, tandis que les fonctionnalités de pointe comme TMA, les tensor cores ou la communication au niveau cluster restent entièrement manuelles afin de refléter la complexité du matérielMais ce n’est pas très dans l’esprit de Rust. En Rust, quand les abstractions existantes ne collent pas bien au problème, on crée de nouvelles abstractions sûres. Rust for Linux en est un exemple
Si ce n’est pas sûr, je me demande quel est l’intérêt d’utiliser Rust. Fournir des API unsafe à ceux qui doivent arracher le dernier pour cent de performance, d’accord, mais cela ne devrait pas être le comportement par défaut
Ça me fait penser aux bibliothèques en espace utilisateur pour des API comme
io_uringou Vulkan. Concevoir des API sûres pour ce genre de choses est assez difficile, et il y a effectivement eu des tentatives qui n’étaient pas soundIdem pour la barrière de sérialisation / de bytes entre les deux
Par exemple, je me demande si les vérifications de limites de tableau pourraient entraîner une utilisation supplémentaire de registres et ainsi réduire la concurrence du kernel