5 · La spécialisation des warps¶
Tous les warps d'un bloc exécutent le même code depuis 2006. La programmation Hopper casse cette symétrie : certains warps ne font que déplacer des données, d'autres ne font que calculer. C'est le motif architectural central des noyaux modernes — et celui des megakernels.
5.1 Le modèle homogène et sa limite¶
Dans un noyau classique, chaque warp fait la même chose :
__global__ void homogene() {
for (int t = 0; t < N; ++t) {
charger(t); // tous les warps chargent
__syncthreads();
calculer(t); // tous les warps calculent
__syncthreads();
}
}
Deux problèmes.
1. Les ressources sont sous-utilisées alternativement. Pendant la phase de chargement, les tensor cores sont inactifs. Pendant le calcul, les unités load/store le sont.
2. Les registres sont dimensionnés pour le pire cas. Le compilateur alloue assez de registres pour couvrir les deux phases dans chaque warp, alors qu'aucun warp n'a besoin des deux simultanément.
5.2 Le modèle spécialisé¶
__global__ void specialise() {
int wg = threadIdx.x / 128; // identifiant du warp group
if (wg == 0) {
// ── PRODUCTEUR ──
// Registres : peu (juste des adresses)
for (int t = 0; t < N; ++t) {
vide[t % E].wait();
emettre_tma(t, plein[t % E]);
}
} else {
// ── CONSOMMATEUR ──
// Registres : beaucoup (accumulateurs)
for (int t = 0; t < N; ++t) {
plein[t % E].wait();
wgmma(acc, tampon[t % E]);
vide[t % E].arrive();
}
}
}
Les deux branches ne se rejoignent jamais. C'est de la divergence assumée à l'échelle du warp, ce qui ne coûte rien : la divergence n'est chère qu'à l'intérieur d'un warp.
Le gain sur les registres¶
CUDA offre un mécanisme dédié pour redistribuer les registres entre warp groups :
setmaxnreg.dec.sync.aligned.u32 24; // producteur : rendre des registres
setmaxnreg.inc.sync.aligned.u32 240; // consommateur : en prendre
En CUDA C++ (Hopper, sm_90a) :
if (wg == 0) {
asm volatile("setmaxnreg.dec.sync.aligned.u32 %0;\n" :: "n"(24));
// producteur
} else {
asm volatile("setmaxnreg.inc.sync.aligned.u32 %0;\n" :: "n"(240));
// consommateur
}
Un producteur qui n'a besoin que de 24 registres en rend 200 aux consommateurs. Sur un bloc de 3 warp groups (1 producteur + 2 consommateurs), cela permet des accumulateurs beaucoup plus grands, donc des tuiles plus grandes, donc une intensité arithmétique supérieure.
Pourquoi c'est le motif gagnant sur Hopper
Il aligne trois choses :
- TMA est mono-thread : un seul warp suffit à alimenter tout le bloc ;
wgmmaest asynchrone : le consommateur peut lancer plusieurs MMA puis attendre ;mbarriercompte des octets : la synchronisation producteur → consommateur est exacte.
Les trois mécanismes de Hopper ont été conçus pour ce motif.
5.3 Les deux schémas canoniques¶
Ping-pong¶
Deux warp groups consommateurs travaillent sur des tuiles de sortie différentes, décalés d'une demi-période.
temps →
WG1 : [MMA tuile A][épilogue A][MMA tuile C][épilogue C]
WG2 : [MMA tuile B][épilogue B][MMA tuile D]
TC : ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ ← jamais inactifs
Adapté quand l'épilogue est coûteux (activation, quantification, écriture avec transformation).
Cooperative¶
Les deux warp groups collaborent sur la même tuile de sortie, chacun traitant la moitié des lignes.
WG1 : [MMA lignes 0-63 ][épilogue 0-63 ]
WG2 : [MMA lignes 64-127][épilogue 64-127]
Adapté aux grandes tuiles où la mémoire partagée ne permettrait pas deux tuiles simultanées.
CUTLASS nomme ces variantes sm90_gemm_tma_warpspecialized_pingpong et
..._cooperative. Le choix se fait par mesure ; il n'y a pas de règle absolue.
5.4 La forme générale : quatre rôles¶
Dans les noyaux les plus sophistiqués (megakernels compris), on trouve jusqu'à quatre rôles.
| Rôle | Ce qu'il fait | Ressource utilisée |
|---|---|---|
| Loader | émet les copies TMA global → partagé | unités TMA |
| Compute | exécute les MMA | tensor cores |
| Storer | écrit les résultats, gère la communication inter-GPU | unités load/store, NVLink |
| Scheduler | distribue le travail, gère les dépendances | atomiques, mémoire |
Le megakernel de débit de Hazy Research utilise exactement cette décomposition : des threads loader, compute et storer spécialisés au sein de chaque SM, « permettant le pipelining d'instructions — charger les poids de l'opération suivante pendant qu'on termine la courante ». Gain mesuré : 2 à 6 %.
Le gain paraît modeste, mais il s'ajoute à deux autres niveaux de recouvrement :
| Niveau | Mécanisme | Gain mesuré |
|---|---|---|
| Dans un SM | threads loader/compute/storer | 2-6 % |
| Entre SM | file de travail globale, entrelacement d'instructions d'opérations différentes | 14,2 % (lot 8 192, vs tourniquet) |
| Entre GPU | threads storer dédiés à la communication asynchrone | inclus dans les 22 % totaux |
Résultat final : 23 468 jetons/s contre 19 170 pour SGLang sur Llama-70B en tensor-parallèle sur 8 H100, soit +22 %.
5.5 Le lien avec les megakernels¶
C'est ici que la partie 4 rejoint la partie 8.
Un megakernel est la spécialisation des warps portée à l'échelle du GPU entier :
| Concept | Dans une GEMM | Dans un megakernel |
|---|---|---|
| Unité de travail | une tuile | une « instruction » / « tâche » |
| Producteur | 1 warp group émettant TMA | threads loader de chaque SM |
| Consommateur | 2 warp groups en ping-pong | threads compute |
| Tampon | mémoire partagée circulaire, E étages | pages de mémoire partagée (16 ou 32 Ko) |
| Synchronisation | mbarrier locale au bloc |
compteurs en mémoire globale |
| Ordonnancement | statique (blockIdx → tuile) |
file de travail dynamique + SM ordonnanceurs |
Mirage MPK pousse la logique jusqu'à dédier des SM entiers au rôle d'ordonnanceur : 16 SM ordonnanceurs et 104/128/144 SM workers selon la carte (A100/H100/B200).
L'idée à emporter de ce chapitre
La spécialisation des warps n'est pas une astuce d'optimisation. C'est un changement de modèle de programmation : on passe d'un modèle SPMD (tout le monde fait la même chose) à un modèle de flux de données (chacun son rôle, communication par tampons).
Les megakernels sont ce modèle appliqué à un réseau de neurones entier.
5.6 Le support dans les outils¶
Écrire cela à la main est pénible. L'état du support en 2026 :
| Outil | Support de la spécialisation |
|---|---|
| CUTLASS C++ | complet, c'est son architecture native (CollectiveMma) |
| CuTe DSL | complet, en Python |
| ThunderKittens | oui, via des templates de « workers » |
| Triton | en cours — tl.async_task et des passes automatiques ; voir la feuille de route PyTorch |
| Gluon | oui, c'est un de ses arguments : « expose layouts, shared memory, warp specialization » |
| Mojo | oui, via les structured kernels |
| HIP / AMD | manuel ; pas d'équivalent asynchrone à wgmma |
Le fait que Triton ait dû ajouter la spécialisation des warps — et qu'OpenAI ait créé Gluon pour l'exposer explicitement — est le meilleur indicateur de son importance : le modèle de tuiles automatique de Triton ne suffisait plus pour atteindre le pic sur Hopper.
Résumé du chapitre¶
À retenir
- La spécialisation des warps donne des rôles différents aux warps d'un même bloc : producteur (TMA), consommateur (MMA), storer, ordonnanceur.
- La divergence entre warps est gratuite ; seule celle à l'intérieur d'un warp coûte.
setmaxnregredistribue les registres : le producteur en rend, le consommateur en prend. C'est ce qui permet de grandes tuiles.- Deux schémas : ping-pong (tuiles différentes, bon si l'épilogue est coûteux) et cooperative (même tuile, bon si elle est grande).
- Le recouvrement se fait à trois niveaux : dans un SM (2-6 %), entre SM (14,2 %), entre GPU. Cumulés : +22 % sur SGLang.
- Un megakernel est ce modèle appliqué au GPU entier, avec des compteurs
globaux à la place des
mbarrierlocales.
Vérifiez que vous avez compris¶
Pourquoi la divergence entre warp groups est-elle gratuite alors que la divergence intra-warp coûte cher ?
Parce que l'unité d'ordonnancement est le warp, pas le bloc. Chaque warp a son propre flux d'instructions et est planifié indépendamment par l'ordonnanceur de sa partition.
Quand deux warps exécutent des branches différentes, ils occupent simplement des créneaux d'exécution différents — c'est le fonctionnement normal. Quand deux threads d'un même warp divergent, le matériel doit exécuter les deux chemins successivement avec des masques, puisqu'il n'y a qu'un seul flux d'instructions par warp.
Pourquoi le gain de la spécialisation intra-SM (2-6 %) est-il si faible comparé au gain inter-SM (14,2 %) ?
Parce qu'ils corrigent des problèmes d'échelles différentes.
La spécialisation intra-SM récupère les bulles courtes : les quelques dizaines de cycles où un SM attend une donnée. Avec un pipeline déjà profond, ces bulles sont rares.
L'ordonnancement inter-SM corrige le déséquilibre de charge : certains SM finissent leur travail bien avant d'autres et restent inactifs. À lot 8 192, avec des opérations hétérogènes (GEMM compute-bound, attention memory-bound), ce déséquilibre est structurel et coûte beaucoup plus.
C'est cohérent avec l'analyse générale : les frontières et les déséquilibres coûtent plus que les micro-inefficacités. C'est le raisonnement même des megakernels.
Vous portez un noyau warp-specialized de H100 vers MI300X. Quelles difficultés ?
Quatre, au minimum :
- Pas de TMA. Le producteur doit calculer ses adresses à la main, ce qui consomme des registres et des instructions — le gain principal de la spécialisation disparaît en partie.
MFMAest synchrone. Le consommateur ne peut pas lancer plusieurs MMA puis attendre ; il faut structurer le recouvrement différemment.- Pas de
setmaxnreg. L'allocation de registres est uniforme sur le bloc. - Wavefront de 64. Toute la granularité change.
En pratique, les noyaux AMD performants utilisent un autre motif : le streaming continu des poids vers le LDS avec des hints non-temporels, tel que décrit par Kog. Le portage n'est pas une traduction, c'est une reconception.
Chapitre suivant : 6 · CUDA Tile
Sources de ce chapitre¶
- Deep Dive on CUTLASS Ping-Pong GEMM Kernel, PyTorch blog
- Efficient GEMM in CUDA, CUTLASS documentation
- Warp Specialization in Triton: Design and Roadmap, PyTorch blog
- Hazy Research, We Bought the Whole GPU, So We're Damn Well Going to Use the Whole GPU — les trois niveaux de recouvrement et leurs gains chiffrés.
- Mirage Persistent Kernel, arXiv:2512.22219 — workers et schedulers.
- Gluon Overview, Triton documentation