CUDA Rust — désormais avec deux voies pour le développement natif de noyaux GPU en Rust

  • PublicPublic
  • 09 Sep, 2026
CUDA Rust — désormais avec deux voies pour le développement natif de noyaux GPU en Rust

En septembre 2026, NVIDIA a annoncé qu'elle misait sur le développement GPU natif en Rust. CUDA C++ et CUDA Python sont des chaînes d'outils matures et de niveau industriel, mais la couche système de l'IA — moteurs d'inférence, infrastructure de service, pilotes et runtimes d'agents — est sans cesse réécrite et de plus en plus souvent écrite en Rust, qui détecte des classes entières d'erreurs dès la compilation, sans sacrifier les performances. NVIDIA fait elle-même partie de ce basculement : le pilote Nova Linux est écrit en Rust, NVIDIA Dynamo repose sur un cœur Rust, et NVTX dispose de liaisons Rust. L'exception restait le noyau GPU, car les noyaux pouvaient être lancés depuis Rust, mais il fallait généralement les écrire dans un autre langage. CUDA Rust comble ce fossé, en permettant d'écrire des noyaux en Rust et de les compiler nativement en PTX, plutôt que d'envelopper du code provenant d'autres sources.

Il existe deux voies pour utiliser Rust, correspondant aux deux voies que CUDA lui-même possède déjà. SIMT est le modèle familier de CUDA C++ ou de numba-cuda : vous décrivez ce que fait un thread et vous en lancez des milliers. Tile est un modèle de programmation plus récent, également disponible en C++ et en Python, où vous décrivez ce que fait un tile de données, le compilateur Tile IR s'occupant de tout le reste. Le guide de NVIDIA conseille de se tourner d'abord vers Tile, car le compilateur décide lui-même de la façon dont les tiles s'appliquent à chaque architecture, et votre code source reste libre de choix spécifiques à l'architecture ; vous descendez vers SIMT lorsque vous avez besoin de ce niveau de contrôle ou que vous voulez gérer vous-même la mémoire et les threads. Le choix du langage est séparé du choix du modèle, et l'interopérabilité inter-langages est prévue, de sorte qu'opter pour Rust ne vous enferme pas hors des autres frontends.

La voie SIMT est cuda-oxide, un backend codegen personnalisé de rustc. Il intercepte la compilation, fait passer les fonctions #[kernel] par Rust MIR, le framework Pliron IR et LLVM IR jusqu'à PTX, et confie tout le reste au backend standard. Les dialectes GPU au-dessus de Pliron appartiennent à NVIDIA, et chaque transformation reste en Rust jusqu'à ce que le backend LLVM standard prenne le relais. Les prérequis incluent Linux, un GPU avec une compute capability 8.0 ou supérieure, un CUDA toolkit 12.x ou plus récent, clang avec ses en-têtes libclang et la toolchain nightly épinglée. La sous-commande Cargo cargo-oxide pilote la compilation, et cargo oxide doctor vérifie la toolchain. Le projet est scaffoldé via cargo oxide new, et le premier cargo oxide run compile le backend codegen, de sorte que les lancements suivants réutilisent le cache.

La voie Tile est cutile-rs, qui opère un niveau plus haut : vous effectuez des calculs sur des tiles, et non sur des scalaires. Chaque bloc de tiles exécute le corps du noyau une fois comme un unique thread logique sur un sous-tenseur de données, et le compilateur décide du nombre de threads GPU réels qui le soutiennent. La macro #[cutile::module] intègre l'AST du noyau dans le binaire hôte et le compile en JIT via CUDA Tile IR au premier accès au noyau. Les prérequis sont plus légers que pour la voie SIMT : un GPU avec une compute capability 8.0 ou supérieure, CUDA 13.3, Rust stable 1.89 ou plus récent et Linux, sans toolchain nightly et sans LLVM propre. Comme cutile est publié, vous lancez simplement cargo add cutile, sans rien cloner.

Les deux voies avancent le même argument de sécurité concernant la mémoire. Dans l'exemple SIMT, le tampon de sortie mutable est un DisjointSlice, un type spécialement conçu qui donne à chaque thread un accès exclusif à son propre élément, car &mut [f32] a la mauvaise forme quand chaque thread aurait besoin du même emprunt mutable. Le lancement est vérifié, et non tenu pour acquis : la déclaration #[launch_contract] est validée face à LaunchConfig1D et aux limites réelles du périphérique, et les noyaux sans contrat n'exposent que des méthodes de lancement unsafe brutes. Du côté Tile, le partitionnement donne à chaque bloc de tiles un sous-tenseur inscriptible qu'aucun autre bloc ne peut chevaucher, et cette exclusivité est exactement ce que &mut garantit déjà. Passer le tampon de sortie d'un noyau comme l'une de ses propres entrées ne compile pas, ce qui attrape les courses d'aliasing qui, sinon, passeraient les tests puis surgiraient en production.