- Un projet de démonstration a été dévoilé montrant qu’une base de code unique écrite en Rust fonctionne sur toutes les principales plateformes GPU et CPU, notamment CUDA, Vulkan (SPIR-V), Metal, DirectX 12, WebGPU et CPU
- La programmation GPU classique est complexe et entraîne des duplications, car elle nécessite l’usage de langages distincts comme GLSL, HLSL, etc. ; cette démonstration prend au contraire en charge toutes les cibles GPU uniquement avec du code Rust pur
- Elle combine des implémentations majeures comme Rust GPU (SPIR-V), Rust CUDA (NVVM IR) et Naga (couche de traduction entre langages GPU) ; le même algorithme de tri bitonique fonctionne ainsi sur le CPU et sur tous les GPU, tandis que les fonctionnalités du langage Rust comme
no_std, la compilation conditionnelle, les newtypes, les enums et les traits s’appliquent telles quelles au code GPU
- Même s’il reste des points à améliorer, comme l’intégration officielle dans rustc, le débogage et la cohérence des API, il s’agit d’une tentative qui pourrait marquer un tournant pour le calcul cross-platform sur GPU
Concrétisation du calcul GPU cross-platform basé sur Rust
Présentation du projet et intérêt
- Une seule base de code Rust permet d’exécuter le même code de kernel sur CUDA (NVIDIA), Vulkan (SPIR-V), Metal (Apple), DirectX 12 (Windows), WebGPU (navigateur) et CPU
- Sans utiliser de langage dédié aux shaders ou aux kernels (GLSL, HLSL, etc.), il devient possible d’effectuer les mêmes opérations sur GPU et CPU uniquement avec du code Rust pur
- Cela réduit fortement la duplication de code et la complexité entre GPU et CPU, tout en apportant à la programmation GPU les avantages de l’écosystème Rust et du langage lui-même (sûreté des types, tests, documentation, gestion de build, etc.)
Contexte
- La programmation GPU traditionnelle impose l’usage de langages de shader spécialisés selon chaque plateforme (par ex. GLSL, HLSL, MSL, WGSL, etc.)
- Cela sépare le code CPU et GPU, entraîne de la duplication logique et accroît la complexité du développement
- Pour y répondre, la communauté Rust poursuit une approche consistant à compiler du Rust standard vers des cibles GPU
- Rust GPU : compile du code Rust en SPIR-V et l’exécute sur Vulkan et les GPU compatibles SPIR-V
- Rust CUDA : compile du code Rust vers l’IR CUDA de NVIDIA (NVVM IR, PTX) pour l’exécuter sur CUDA
- Naga : couche intermédiaire permettant les conversions entre divers langages GPU (WGSL, SPIR-V, GLSL, MSL, HLSL), principalement utilisée dans le projet wgpu. Elle assure la portabilité des backends
- Chaque projet ayant démarré de manière indépendante avec des API et des structures différentes, c’est grâce à une collaboration récente que l’exécution GPU d’une base de code commune a pu être réalisée
Mode d’implémentation
- Dans la démonstration, l’algorithme de tri bitonique est implémenté sous la forme d’un unique code Rust, exécuté à l’identique sur tous les GPU et sur le CPU
- Terminologie des principaux composants
- Host : code Rust exécuté sur le CPU (démarrage du travail)
- Device : GPU/CPU sur lequel le kernel s’exécute réellement
- Driver API : API bas niveau de communication avec le périphérique, comme CUDA, Vulkan, Metal, DX12, etc.
- Backend : abstraction en Rust au-dessus des Driver API (cust, ash, wgpu, etc.)
- Les feature flags de Rust et les combinaisons de cibles permettent de choisir le backend et de contrôler la build
- Exemple : des options de build indépendantes sont fournies pour chaque backend, comme wgpu, ash ou cuda
- Il est possible de construire plusieurs backends dans un même binaire et de les sélectionner dynamiquement à l’exécution
Flux de compilation des kernels
- Selon la cible et les features, des binaires d’exécution GPU (SPIR-V, PTX, etc.) sont générés à la compilation puis embarqués dans les fichiers objet
- À l’exécution, les kernels embarqués sont chargés puis convertis, notamment via Naga, vers le format requis par chaque plateforme avant d’être lancés
- Exemples :
- macOS : conversion de SPIR-V vers MSL → exécution sur Metal
- Windows : conversion de SPIR-V vers HLSL → exécution sur DX12
- Linux/Android : SPIR-V → exécution sur Vulkan
- CUDA : compilation de PTX en SASS, envoi au GPU puis exécution
Techniques de codage GPU adaptées à Rust
-
Prise en charge de no_std
- Les GPU ne disposant pas de support OS, l’usage de
no_std en Rust est indispensable
- Comme l’écosystème Rust de base vise déjà nativement des environnements sans OS, comme l’embarqué, le firmware ou le kernel, les GPU peuvent eux aussi être pris en charge sans « mode spécial » distinct, selon l’approche standard de Rust
-
Compilation conditionnelle
- La combinaison des attributs
cfg et des feature flags permet d’écrire de façon claire et concise le code différenciant les plateformes ainsi que les chemins GPU/CPU
- L’IDE et le compilateur comprennent l’ensemble des chemins de code, ce qui améliore la fiabilité et la productivité
-
Utilisation des newtypes
- Les erreurs possibles sur GPU liées aux index implicites ou au mapping de structures peuvent être évitées au niveau du type grâce aux newtypes
- L’attribut
#[repr(transparent)] permet cette abstraction de type sans surcoût réel
-
Enum et représentations sûres
- Au lieu d’utiliser des magic numbers, Rust permet d’employer des enums ; avec
#[repr(u32)], on garantit un mapping correct vers les entiers natifs
- Le pattern matching et l’exhaustivité du traitement des cas permettent d’écrire un code plus sûr
- Il faut toutefois être prudent lors des échanges de valeurs d’enum via des buffers partagés entre CPU et GPU, car toutes les valeurs doivent correspondre à des enums valides
-
Algorithmes génériques basés sur les traits
- Les traits permettent d’abstraire les opérations GPU communes — comparaison, conversion, calcul, etc. — pour divers types de valeurs
- En définissant clairement les trait bounds dans les algorithmes génériques, on concilie sûreté des types et performance
-
#[inline] et optimisation des performances
- L’usage de
#[inline] aide à faire disparaître les couches d’abstraction dans le résultat compilé
- La conception vise à éviter tout coût lié à l’abstraction, ce qui est important compte tenu des contraintes propres aux GPU (performance, pile limitée, etc.)
-
Composition de structs et regroupement sémantique
- Des paramètres GPU complexes sont regroupés dans des
struct par unité de sens, afin d’assurer la sûreté des types et d’éviter l’explosion du nombre de paramètres
- Le pattern du smart constructor permet d’écarter les états invalides dès la compilation
-
Contrôle de la disposition mémoire et #[repr(C)]
- Pour assurer la compatibilité des données avec le GPU, la disposition des
struct est explicitement définie avec #[repr(C)]
- Le besoin d’un support futur du langage pour automatiser, par exemple, le padding selon les GPU est également mentionné
-
Pattern matching
- Comme extension du concept de switch/case, il permet de traiter clairement tous les branchements et états dans le code GPU
- Le compilateur peut alors vérifier les chemins de code et optimiser les performances
-
Fonctions génériques
- Elles permettent d’implémenter une même logique pour plusieurs types, indépendamment du type de donnée
- Les trait bounds, la monomorphisation, etc. améliorent la maintenabilité et la facilité de test
-
Macros derive
- Elles automatisent l’implémentation de traits adaptés au GPU comme
Copy, Clone, Debug, PartialEq, Pod, etc.
- Cela renforce la sûreté et réduit le boilerplate
-
Système de modules et gestion des workspaces
- Le système de packages/modules de Rust permet de structurer séparément le code hôte, les kernels, les types et les sources propres à chaque backend
- Les Cargo workspaces et les workspace dependencies assurent la cohérence des dépendances entre crates et facilitent la maintenance
-
Formatage, linting et documentation
- Le code GPU peut être géré exactement comme le reste du code Rust avec les outils standard tels que rustfmt et clippy, ce qui permet de conserver une qualité homogène
- Les doc comments et
cargo doc permettent aussi de documenter le code GPU
-
Scripts de build
- Via
build.rs de Cargo, il est possible d’automatiser la build des kernels selon les feature flags et leur embarquement dans le binaire
-
Tests unitaires et productivité de développement
- Le code de kernel GPU peut également être testé sur CPU, ce qui facilite le développement et la détection de bugs
- Il est possible d’utiliser des outils traditionnels comme le debug par
println, gdb ou lldb
- Les kernels Vulkan peuvent aussi être testés en CI via des pilotes logiciels comme SwiftShader ou lavapipe
- Les tests, la mesure de couverture de code et les property-based tests s’intègrent facilement avec des outils tiers
Expérience développeur encore incomplète et défis restants
- Les backends GPU n’étant pas intégrés officiellement au compilateur Rust, il faut utiliser des backends de génération de code séparés (
spirv, nvvm) et figer une version nightly
- La cible CUDA dépend de LLVM 7.1 de NVIDIA, ce qui nécessite une build distincte sur les distributions Linux récentes
- L’expérience de build et de débogage des kernels reste limitée, avec des problèmes de traçage des erreurs et d’informations de debug insuffisantes
- Les API de Rust GPU et Rust CUDA ainsi que les noms de bibliothèques standard diffèrent, ce qui peut prêter à confusion
- À long terme, il sera nécessaire de renforcer la cohérence et l’intégration orientées GPU dans l’ensemble du langage Rust et de son écosystème
Participation de la communauté et avenir
- L’exécution d’un même code Rust sur toutes les grandes plateformes GPU est désormais devenue réalité
- Les prochaines étapes concernent l’amélioration de la build et du débogage, l’élargissement de la prise en charge du langage Rust et des API, ainsi que le tuning des performances
- Les développeurs souhaitant participer ou contribuer peuvent se référer aux dépôts GitHub de rust-gpu et rust-cuda
1 commentaires
Commentaires sur Hacker News
Le simple fait que cette technique soit possible est vraiment impressionnant
Mais mon cas d’usage consiste à exécuter sur du matériel client arbitraire, donc j’ai tendance à ne pas faire confiance à toutes les couches d’abstraction construites au-dessus des API GPU
Le but est d’exploiter au maximum les détails bas niveau du GPU, et les approches qui considèrent ces détails comme une nuisance finissent par entraîner des bugs et des pertes de performances
Parce que chaque cible est significativement différente
Pour surmonter cela, je pense qu’un système similaire devrait être fourni directement par les constructeurs
Mais comme les positions entre fournisseurs ne font toujours pas consensus, les différences entre plateformes semblent rester importantes
Il y a bien des exceptions comme Angle dans certains cas, mais même là, la stabilité n’est obtenue qu’au prix de limitations fonctionnelles, avec au final une perte de performances
Cela dit, le fait que des approches comme la compilation conditionnelle soient possibles aide clairement
Rust étant un langage système, on peut avoir autant de contrôle qu’on le souhaite
Nous prévoyons de refléter les détails et les API des GPU dans le langage et dans les bibliothèques core/std, et d’exposer les fonctionnalités du GPU et des pilotes via le système
cfg()(auteur)
Je pense exactement pareil
Je suis toujours prudent à l’idée de construire quelque chose de commercial sur des couches d’abstraction, des adaptateurs ou des couches de traduction dont on ne sait pas si elles auront assez de support à l’avenir
On est presque en 2025, et j’ai toujours l’impression qu’on a désespérément besoin d’un standard ouvert pris en charge par tous les fournisseurs et donnant accès à toutes les capacités du matériel GPU moderne
Vu la situation actuelle, le fait que Nvidia, qui a construit la barrière logicielle à l’entrée la plus puissante, siège à la tête de Khronos me paraît assez révélateur
Comme vous semblez très intéressé par les performances, j’aimerais poser la question par curiosité
De mon point de vue, l’état actuel du monde GPU ressemble beaucoup à l’ancien monde CPU
Pour les CPU, on avait une architecture de compilateur en trois niveaux, avec optimisation dans une couche intermédiaire puis génération de code adapté à chaque matériel dans la couche finale
L’avantage de cette structure, c’est que la couche d’abstraction dure longtemps, et que le compilateur devient plus intelligent avec le temps
Je me demande si une structure comparable est possible côté GPU
Ou bien si la diversité des GPU est trop grande pour que ce soit possible ou économiquement viable, ou encore si c’est évidemment la direction à prendre mais qu’on n’y est pas encore techniquement
C’est tout à fait juste
Je ne vois pas très bien en quoi faire tourner Rust sur des GPU Nvidia serait vraiment préférable au code CUDA existant
J’entends l’intérêt d’ajouter de l’abstraction, mais au final cela donne une impression de « faire un peu tout et n’importe quoi »
En réalité, tout est abstraction
CUDA aussi n’est au fond qu’une abstraction qui unifie conceptuellement des matériels pourtant complètement différents
Je développe des applications audio natives, et ici chaque cycle de calcul compte
Et j’ai besoin d’une API de calcul complète, pas seulement de shaders graphiques
Je me demande si le pipeline "Rust -> WebGPU -> SPIR-V -> MSL -> Metal" est solide du point de vue des performances
Il y a tellement d’étapes de transformation que cela paraît fragile et imprévisible
C’est pareil pour "... -> Vulkan -> MoltenVk -> ..."
À l’inverse, "Julia -> Metal" saute MSL et peut exploiter directement des optimisations spécifiques à Apple Silicon, comme la mémoire unifiée
L’innovation ici, ce n’est pas le langage de shader, mais le fait d’utiliser un langage de programmation complet comme Rust
Rust prend en charge de nombreuses fonctionnalités comme les newtypes, les traits, les macros, etc.
Avec rust-gpu, il n’est pas obligatoire de passer par la couche WebGPU
rust-gpu est un backend de génération de code du compilateur
L’architecture permet de compiler directement le MIR de Rust en SPIRV
« Le pipeline "Rust -> WebGPU -> SPIR-V -> MSL -> Metal" est-il solide en matière de performances ? »
Fondamentalement, c’est une idée proche de la manière dont Apple utilise les optimisations Clang pour le GPU
SPIR-V est une représentation intermédiaire, comme l’IR utilisé dans LLVM, donc elle peut être optimisée pour chaque système
En théorie, on peut cibler avec une seule base de code tous les GPU de rastérisation pris en charge
La pile Julia -> Metal est en revanche moins portable
Pour un développeur limité à une seule plateforme, comme pour un plugin audio, ce n’est pas très important, mais pour des sociétés multiplateformes comme u-he ou Spectrasonics, un pipeline plus complexe basé sur SPIR-V peut être plus attractif
Pour le calcul numérique et les optimisations qui suivent, Julia est bien plus adapté que Rust, qui est un langage système
Quand on regarde la matrice de compatibilité de Rust-CUDA, on voit qu’il y a très peu de demande pour Rust dans la programmation CUDA
La plupart des fonctionnalités de CUDA que les gens apprécient sont absentes, et s’il y avait une vraie demande, on verrait davantage de progrès
Les programmeurs CUDA ne semblent pas très enclins à utiliser Rust
https://github.com/Rust-GPU/Rust-CUDA/blob/main/guide/src/features.md
Même quand j’ai du code que j’aimerais faire tourner sur GPU, toute la programmation GPU est tellement pénible que je finis par renoncer
Le vrai usage de rust-gpu est peut-être de transformer des développeurs CPU en développeurs GPU, même au prix d’un certain sacrifice sur les performances
Si on est déjà à l’aise avec le GPU et qu’on maîtrise cuda/vulkan/metal/dx, on ne verra probablement pas un grand intérêt dans ce type d’outil
Je suis un simple développeur web, donc c’est peut-être une question idiote, mais je n’ai jamais fait de programmation GPU
Je me demande si WebGPU, en tant qu’API unique compatible avec tous les backends GPU, ne résout pas déjà tous ces problèmes
WebGPU semble aussi être l’un des backends pris en charge, donc si c’est le cas, cela ne revient-il pas à empiler une abstraction sur une abstraction existante pour finir par appeler un backend GPU natif ?
Non
WebGPU est une API qui permet au CPU de contrôler le GPU afin d’exécuter des shaders et d’autres tâches graphiques, comme D3D, Vulkan ou SDL GPU
Rust-GPU est un langage permettant d’écrire le code shader réellement exécuté sur le GPU, à la manière de HLSL, GLSL ou WGSL
À l’époque où Microsoft avait de l’influence, il y avait DirectX
Mais aujourd’hui, je ne sais pas trop dans quelle mesure les fabricants de GPU implémentent chacun des API dédiées à leurs propres technologies
Il y a toutes sortes de fonctionnalités particulières comme DLSS, MFG, RTX, etc.
Si j’étais le méchant d’un dessin animé, je pourrais volontairement rendre les API existantes lentes et ne fournir rapidement que de nouvelles API propriétaires plus rapides
Je précise que je suis moi aussi développeur web, donc je ne maîtrise pas vraiment le sujet, mais au moins les LLM vont apprendre ce genre de choses
Je vois plutôt WebGPU comme une API du plus petit dénominateur commun
Par exemple, l’éditeur Zed cible directement Metal sur Mac
Et puis chacun a sa propre idée de ce que signifie « commun »
Comme avec OpenGL contre Vulkan, les acteurs les plus puissants cherchent à faire de leur propre écosystème — CUDA, Metal, DirectX, etc. — le standard du marché
Si c’était vraiment aussi simple, CUDA ne serait pas aujourd’hui le fossé défensif aussi puissant de Nvidia
Ce projet repose en grande partie sur les efforts autour de l’implémentation WebGPU
wgpu-rsWebGPU n’est pas optimal pour les applications natives
Il a été conçu à partir d’anciennes versions de Vulkan, surtout pré-RTX, et depuis les API véritablement natives ont beaucoup évolué
C’est encore assez brut, mais j’ai vraiment du mal à croire que ce soit possible
Si ce genre de progrès continue, j’ai l’impression qu’il y a un vrai potentiel pour briser la forte dépendance aux fournisseurs dans le logiciel GPU et permettre une concurrence réelle entre fabricants de matériel
J’imagine qu’un jour on pourrait écrire des modèles de machine learning en Rust et les exécuter à la fois sur Nvidia et AMD
Bien sûr, pour obtenir les meilleures performances, il faudra du code spécialisé pour chaque fournisseur, mais ça relève de l’optimisation
Malgré tout, on pourrait avoir des kernels portables fonctionnant en multiplateforme
Il existe un framework de machine learning en Rust appelé https://burn.dev, avec plusieurs backends comme CUDA et ROCm
Cela vaut le coup d’y jeter un œil
Un avenir où l’on écrit des modèles de machine learning en Rust pour les exécuter à la fois sur Nvidia et AMD me paraît difficile à imaginer dans les dix prochaines années
En pratique, tout l’écosystème, avec jax et torch notamment, repose sur Python
Faire basculer tous les développeurs du secteur vers des outils Rust semble presque inimaginable
Si on compte les couches d’abstraction
cust,ash,wgpuwgpuet autresCela fait au moins six niveaux de complexité cachée
Je me demande s’il est réellement possible de traverser toutes ces couches tout en conservant, en performances, les spécificités de chaque plateforme
Ce que fait rust-gpu, au fond, c’est compiler en SPIRV, c’est-à-dire l’IR de Vulkan
Donc les couches 2 et 3 peuvent être ignorées ou placées en parallèle
On peut aussi réutiliser tels quels pour le développement de shaders GPU les outils de l’écosystème Rust, comme
cargo,cargo test,cargo clippyetrust-analyzerEn fait, je pense que la difficulté de la programmation GPU ne vient pas du fait que les architectures GPU seraient trop extraterrestres, mais du fait que l’écosystème entier est verrouillé par les fournisseurs et freiné par de vieux outils
La démo ressemble clairement à une machine de Rube Goldberg assez complexe, mais c’est parce que c’est la première fois que ce genre de chose devient possible
Avec le temps, cela deviendra plus naturel et plus intégré
Et un autre avantage de l’écosystème Rust, c’est qu’on peut travailler à un niveau d’abstraction aussi élevé ou aussi concret qu’on le souhaite
Par exemple, on peut utiliser des fonctionnalités spécifiques à une plateforme via
std::arch, ou même écrire de l’assembleurOn peut aussi remplacer l’allocateur ou le gestionnaire de panique, et quand la fonctionnalité externally implemented items arrivera, cela offrira encore plus de flexibilité pour manier les couches d’abstraction comme on le souhaite
Bonne remarque
Mais les couches 4 à 6 existent toujours aussi bien pour les shaders que pour le code CUDA
Quant aux couches 1 et 3, elles sont en réalité simplement remplacées par d’autres couches, surtout en multiplateforme
Même si ce projet Rust ajoute une couche d’abstraction, cela n’en fait qu’une de plus
Et en tant que personne qui travaille concrètement sur les couches 4 à 6, je peux confirmer qu’il y a énormément de complexité cachée à l’intérieur
Honnêtement, il y a même encore plus de couches :P
En pratique, la plupart des utilisateurs ne manipuleront au plus que les couches (3) ou (4)
Cela n’ajoute donc pas réellement une énorme étape supplémentaire
D’ailleurs, il y a aussi d’autres couches d’abstraction au-dessus du niveau 6
Le firmware et la microarchitecture implémentent l’ensemble d’instructions tel que nous nous le représentons
Je ne pense pas que ce soit très différent du fait d’avoir des compilateurs et des runtimes distincts selon les architectures CPU
Il y a aussi des conventions d’appel différentes, des questions d’endianness, etc., et au niveau matériel on trouve également firmware et microcode
Le fait que des crates existantes en
no_std+no allocpuissent tourner sur GPU presque sans modification est vraiment impressionnantJ’ai l’impression que cela ouvre énormément d’idées d’applications
C’est vraiment remarquable
Il existe déjà énormément de projets GPU autour de Rust
Celui-ci semble plus proche d’une abstraction de bas niveau que burn, et burn est lui-même plus bas niveau que candle
Ce qui manque maintenant, ce serait peut-être d’ajouter des backends comme naga aux projets situés au-dessus
On a l’impression que tout le monde construit quelque chose sur des bases différentes, mais cela vient peut-être du fait que le travail sur naga est relativement récent
Je voudrais aussi ajouter que burn se concentre sur le support des plateformes
Mais en regardant, le seul backend qui utilise naga semble être wgpu
Donc au final, est-ce qu’utiliser simplement wgpu ne suffit pas ?
En résumé, c’est soit wgpu/ash (vulkan, metal), soit cuda
Petit ajout : un autre crate proche de cet effort
https://github.com/tracel-ai/cubecl
[0]: https://github.com/tracel-ai/burn
[1]: https://github.com/huggingface/candle/
On y trouve également des informations sur CubeCL
Je me demande si c’est vraiment du « Rust » qui s’exécute sur le GPU
En survolant un peu le code, on dirait plutôt une structure qui repose au final sur un langage de shader, avec au-dessus une syntaxe Rust remplie de macros de programmation
La programmation GPU est tellement différente qu’elle demande, selon moi, une attention particulière
Ajouter cette abstraction peut rendre certaines optimisations impossibles
Ce projet me réjouit vraiment
J’ai l’impression qu’il aide énormément les développeurs qui ne veulent pas être pris dans les guerres de plateformes
Des exemples comme https://github.com/cogentcore/webgpu sont aussi intéressants
J’utilise golang et je veux simplement pouvoir tirer parti du GPU sur toutes les plateformes, donc ce genre de travail permet d’utiliser le GPU partout
Merci vraiment à Rust