La couche située sous la pile de service. Toute opération de modèle finit par s’exécuter sous forme de noyaux GPU, et la qualité des noyaux décide de la part réellement utilisée du matériel que vous avez payé. Vous écrirez rarement des noyaux vous-même, mais lire cette couche explique pourquoi les systèmes au-dessus se comportent comme ils le font.
Comment un GPU exécute le travail
Modèle d’exécution
Les GPU exécutent des milliers de threads par groupes synchrones (warps), organisés en blocs et ordonnancés sur des streaming multiprocessors. Les threads d’un warp partagent un même flux d’instructions : les branches divergentes se sérialisent donc et gaspillent des voies. Garder assez de warps résidents par SM (l’occupancy) est ce qui masque la latence mémoire.
Hiérarchie mémoire
Les registres, puis la petite mémoire partagée rapide sur puce et les caches, puis la grande HBM hors puce. L’écart de bande passante entre mémoire sur puce et hors puce se compte en ordres de grandeur : la performance d’un noyau est donc surtout une question de rareté des franchissements de cette frontière et de bonne coalescence des accès.
Tensor cores
Des unités dédiées de multiplication-accumulation matricielle fournissent l’essentiel des FLOPS d’un GPU moderne, à des précisions réduites (FP16/BF16, FP8, INT8). Les noyaux qui n’alimentent pas les tensor cores, ou qui utilisent des précisions absentes du matériel, laissent la majeure partie de la puce inactive.
Limité par le calcul ou par la mémoire
Chaque noyau est limité par le débit arithmétique ou par la bande passante mémoire, selon celui qui s’épuise en premier. Le prefill d’un LLM est gourmand en calcul ; le decode est gourmand en bande passante. Savoir de quel côté de la ligne se situe une opération vous dit si plus de FLOPS ou moins de mouvements de données va aider.
Ce que fait réellement l’optimisation de noyaux
Fusion : fusionner des opérations adjacentes (matmul + biais + activation, sous-étapes d’attention) en un seul noyau pour que les résultats intermédiaires restent sur puce au lieu de faire des allers-retours par la HBM, et le coût de lancement par noyau disparaît.
Tuilage : traiter les données par blocs dimensionnés pour la mémoire partagée, de sorte que chaque tuile chargée soit réutilisée de nombreuses fois avant d’être évincée.
Disposition et précision : organiser les tenseurs pour des accès coalescés et choisir des précisions que les tensor cores exécutent nativement.
Les points chauds des LLM : les noyaux de multiplication matricielle, d’attention, de normalisation et d’activation dominent ; un moteur de service est, en première approximation, un planificateur enroulé autour d’une poignée de tels noyaux.
Le travail sur les noyaux complète le niveau système : le batching continu ou le prefix caching changent le travail effectué, les noyaux changent la vitesse d’exécution de chaque unité de travail. Voir optimisation du service pour le premier point.
FlashAttention, l’exemple canonique
L’attention standard matérialise une matrice de scores N x N en HBM : le trafic mémoire croît donc quadratiquement avec la longueur de séquence et l’opération devient limitée par la bande passante. FlashAttention restructure le calcul : elle fait circuler des tuiles des matrices de requêtes, de clés et de valeurs à travers la mémoire sur puce, maintient la normalisation softmax de façon incrémentale à mesure que les tuiles arrivent, et n’écrit jamais la matrice de scores complète. Même résultat mathématique, une fraction du trafic mémoire, et c’est ce qui a rendu le service en long contexte praticable.
Les versions successives ont été réajustées pour chaque génération de matériel (meilleur partitionnement du travail, pipelines de tensor cores asynchrones, chemins en basse précision), et la technique est désormais livrée dans PyTorch et dans tous les grands moteurs de service. La leçon se généralise : les plus grands gains sur les noyaux viennent d’une restructuration du calcul autour de la hiérarchie mémoire, pas d’un micro-réglage des instructions.
L’échelle des outils
Classée par contrôle croissant et coût croissant. Commencez en haut ; ne descendez que lorsque le profilage prouve que le niveau supérieur ne suffit pas.
1. Bibliothèques constructeurcuBLAS, cuDNN et compagnie : des opérations standard préréglées pour chaque architecture. Excellentes sur leur périmètre ; pas de fusion entre opérations, et spécifiques à NVIDIA.
2. Compilateurstorch.compile, XLA, TVM : ils prennent le graphe du modèle et génèrent automatiquement des noyaux fusionnés et réglés. Gains importants pour peu d’effort sur les architectures standard ; les opérations inédites peuvent sortir du chemin rapide.
3. DSL de noyauxTriton et similaires : écrire des noyaux sur mesure dans un langage proche de Python au niveau de la tuile, pendant que le compilateur gère les détails bas niveau. Le bon compromis pour des variantes d’attention personnalisées et des opérations fusionnées sans expertise CUDA complète.
4. Noyaux écrits à la mainCUDA (ou ROCm) avec un contrôle explicite de la mémoire et des warps, exactement comme FlashAttention est construite. Performance maximale, forte expertise, vraie charge de maintenance, dépendance au matériel.
5. Piles de portabilitéDes écosystèmes plus récents (par exemple Mojo/MAX et d’autres travaux fondés sur MLIR) visent une base de code de noyaux unique pour tous les constructeurs ; pesez leur maturité face à la dépendance qu’ils suppriment.
Éléments de décision : à quel point vos opérateurs sont standard, ce que vaut la performance maximale, quel matériel vous devez prendre en charge, et si la force de votre équipe est les compilateurs ou les noyaux. Profilez d’abord ; le goulot d’étranglement n’est souvent pas là où l’intuition le place.