2 · Définition et généalogie¶
Ce qu'est exactement un megakernel, ce qu'il n'est pas, et d'où vient l'idée.
2.1 La définition¶
Définition
Un megakernel est un noyau GPU unique et persistant qui exécute l'intégralité d'un programme tensoriel — typiquement une passe avant de modèle — en remplaçant les frontières de noyau par des dépendances à grain fin exprimées en mémoire globale.
Quatre propriétés le caractérisent.
1. Un seul lancement. Le modèle entier est exécuté par un unique appel
<<<grille, bloc>>>.
2. Persistance. La grille est dimensionnée pour que tous les blocs soient simultanément résidents — typiquement un bloc par SM. Le noyau ne se termine qu'à la fin du travail.
3. Dépendances fines. Les dépendances entre opérations sont exprimées par des compteurs, des sémaphores ou des sentinelles en mémoire globale, à une granularité bien plus fine que « tout le noyau précédent ».
4. Hétérogénéité interne. Un même noyau exécute des opérations différentes — normalisations, GEMM, attention, communication — selon un mécanisme de dispatch interne.
2.2 Ce que ce n'est pas¶
Trois confusions fréquentes.
Ce n'est pas de la simple fusion de noyaux¶
La fusion combine des opérations qu'un bloc peut calculer seul. Un megakernel franchit la barrière de la synchronisation inter-blocs.
| Fusion | Megakernel | |
|---|---|---|
| Portée | ce qu'un bloc peut faire | le modèle entier |
| Synchronisation | __syncthreads() |
compteurs globaux |
| Dispatch | aucun | interpréteur |
| Nombre de lancements | réduit | un |
Ce n'est pas un CUDA Graph¶
Un CUDA Graph reste une séquence de noyaux avec leurs barrières et leurs bulles. Il ne supprime que le surcoût CPU.
Ce n'est pas un noyau persistant ordinaire¶
Un noyau persistant classique (une GEMM avec ordonnanceur de tuiles, par exemple) exécute une seule opération avec une file de travail. Un megakernel exécute plusieurs opérations différentes, avec des dépendances entre elles.
La distinction est de degré autant que de nature : le noyau persistant est la brique, le megakernel est ce qu'on en fait.
2.3 La généalogie¶
2012 ── Persistent Threads (Gupta, Stuart, Owens, UC Davis)
│ Le concept fondateur : garder les blocs résidents,
│ consommer une file de travail.
│ Quatre cas d'usage identifiés :
│ · synchronisation CPU-GPU
│ · équilibrage de charge / parallélisme irrégulier
│ · localité producteur-consommateur
│ · synchronisation globale
│
2017 ── Cooperative Groups (CUDA 9)
│ grid.sync() officialise la barrière inter-blocs
│
2018 ── Persistent RNN, Persistent kernels temps réel
│ Premiers usages en apprentissage profond
│
2020 ── cp.async (Ampere)
│ Copies asynchrones : le pipelining devient praticable
│
2022 ── TMA, clusters, wgmma (Hopper)
│ Les briques d'un pipeline profond sont réunies
│
2023 ── FlashAttention, Stream-K, tile schedulers
│ Les noyaux persistants deviennent standard pour UNE opération
│
2025 ── LOOK MA, NO BUBBLES (Hazy Research, mai)
│ Premier megakernel de modèle complet.
│ Llama-1B en moins d'une milliseconde sur H100.
│
2025 ── Megakernel tensor-parallèle (Hazy Research, sept.)
│ Version orientée débit, 8 GPU, +22 % sur SGLang
│
2025 ── Mirage Persistent Kernel (CMU et al., déc.)
│ Premier COMPILATEUR de megakernel
│
2026 ── Event Tensor (avr.) · Fleet (avr.) · Ada-MK (mai)
│ AutoMegaKernel (juin)
│ Dynamisme, chiplets, production, synthèse automatique
▼
2.4 Les persistent threads, la source¶
Le papier fondateur est celui de Gupta, Stuart et Owens (UC Davis, 2012), A Study of Persistent Threads Style GPU Programming for GPGPU Workloads.
Sa contribution : caractériser formellement un style de programmation où l'on « circonscrit le noyau logique dans une boucle, de sorte que cette boucle continue de tourner tant qu'il reste des éléments à traiter ».
__global__ void persistant(FileTravail* file) {
while (true) {
int tache = file->prochaine(); // atomicAdd sur un compteur
if (tache < 0) break; // plus de travail
traiter(tache);
}
}
Les auteurs identifient quatre cas d'usage, et il est frappant de constater que les megakernels de 2025-2026 les exploitent tous les quatre simultanément :
| Cas d'usage (2012) | Dans un megakernel (2026) |
|---|---|
| Synchronisation CPU-GPU | le CPU pousse des instructions, le GPU les consomme |
| Équilibrage de charge | file de travail dynamique entre SM |
| Localité producteur-consommateur | activations gardées en mémoire partagée |
| Synchronisation globale | compteurs de dépendances |
Leur conclusion mérite d'être citée : l'approche PT « peut atteindre jusqu'à un ordre de grandeur d'accélération sur les noyaux non-PT, mais peut aussi entraîner une perte de performance dans de nombreux cas ».
C'est-à-dire : la technique est puissante et pas universellement gagnante. C'est exactement le message du chapitre 9, et il n'a pas changé en quatorze ans.
2.5 L'anatomie générale¶
Tous les megakernels publiés partagent la même structure, avec des variations de vocabulaire.
┌──────────────────────────────────────────────────────────────────┐
│ MEGAKERNEL │
│ │
│ CPU : construit la liste d'instructions / le graphe de tâches │
│ │ │
│ ▼ │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ SM 0 SM 1 … SM 147 │ │
│ │ ┌────────┐ ┌────────┐ ┌────────┐ │ │
│ │ │loader │ │loader │ │loader │ │ │
│ │ │compute │ │compute │ │compute │ │ │
│ │ │storer │ │storer │ │storer │ │ │
│ │ └────────┘ └────────┘ └────────┘ │ │
│ │ │ │ │ │ │
│ │ └────────────────┴───────────────────────────┘ │ │
│ │ mémoire partagée paginée │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ Mémoire globale : compteurs de dépendances, files de tâches, │
│ activations intermédiaires │
└──────────────────────────────────────────────────────────────────┘
Le vocabulaire selon les travaux :
| Concept | Hazy Research | Mirage MPK | Event Tensor |
|---|---|---|---|
| Unité de travail | instruction | tâche (task) | tâche tuilée |
| Synchronisation | compteurs | événements (events) | event tensor |
| Représentation | liste par SM | ttGraph (SM-level graph) | tenseur d'événements |
| Répartition | interpréteur + file | workers + schedulers | statique + dynamique |
| Mémoire partagée | pages de 16 Ko | pages de 32 Ko | paginée |
2.6 Les cinq mécanismes indispensables¶
Quel que soit le travail, cinq briques sont nécessaires.
1. La persistance¶
Une grille dimensionnée pour la résidence complète :
int blocs_par_sm;
cudaOccupancyMaxActiveBlocksPerMultiprocessor(&blocs_par_sm, megakernel,
THREADS, SMEM);
int nb_sm;
cudaDeviceGetAttribute(&nb_sm, cudaDevAttrMultiProcessorCount, 0);
megakernel<<<nb_sm * blocs_par_sm, THREADS, SMEM>>>(...);
Dépasser cette taille provoque un interblocage.
2. Le dispatch d'instructions¶
while (true) {
Instruction ins = prochaine_instruction();
if (ins.opcode == FIN) break;
switch (ins.opcode) {
case RMSNORM_QKV_ROPE: executer<RmsNormQkvRope>(ins); break;
case ATTENTION: executer<Attention>(ins); break;
case O_PROJ: executer<OProj>(ins); break;
// ...
}
}
Le switch divergent, mais pas coûteux
Ce switch porte sur un opcode uniforme dans tout le bloc : tous les
threads d'un SM exécutent la même instruction. Il n'y a donc aucune
divergence intra-warp.
L'hétérogénéité est entre SM, ce qui est gratuit. C'est le point souligné en Domaines 6, §6.4 : la divergence bien organisée ne coûte rien.
3. Les compteurs de dépendance¶
// Producteur, à la fin de son travail
__threadfence(); // rendre les données visibles
if (thread_representant) {
atomicAdd(&compteurs[ins.compteur_sortie], 1);
}
// Consommateur, avant de commencer
if (thread_representant) {
while (atomicAdd(&compteurs[ins.compteur_entree], 0) < ins.valeur_cible) {
__nanosleep(20); // réduire la contention
}
}
__syncthreads();
4. L'allocateur de mémoire partagée¶
La mémoire partagée du SM devient un tas géré par pages, puisqu'un seul bloc persistant l'occupe pendant toute l'exécution et que les instructions successives ont des besoins différents.
5. L'ordonnancement¶
Statique (chaque SM a sa liste), dynamique (file de travail globale), ou hybride.
2.7 Le lien avec la spécialisation des warps¶
Un megakernel est, structurellement, la spécialisation des warps portée à l'échelle du GPU entier.
| Concept | Dans une GEMM Hopper | Dans un megakernel |
|---|---|---|
| Unité de travail | une tuile | une instruction |
| Producteur | 1 warp group émettant TMA | threads loader |
| Consommateur | 2 warp groups wgmma |
threads compute |
| Épilogue | dans le consommateur | threads storer |
| Tampon | mémoire partagée circulaire | pages de mémoire partagée |
| Synchronisation | mbarrier (locale au bloc) |
compteurs globaux |
| Ordonnancement | statique | file de travail + SM ordonnanceurs |
C'est pourquoi la partie 4 était un prérequis : un megakernel est un noyau warp-specialized dont l'horizon est le modèle entier plutôt qu'une tuile.
Résumé du chapitre¶
À retenir
- Un megakernel : un seul lancement, persistant, avec des dépendances fines et une hétérogénéité interne.
- Ce n'est ni de la fusion simple (qui reste dans un bloc), ni un CUDA Graph (qui garde les frontières), ni un noyau persistant ordinaire (qui n'a qu'une opération).
- L'idée vient des persistent threads de Gupta, Stuart et Owens (2012), dont les quatre cas d'usage sont tous exploités simultanément par les megakernels modernes.
- Cinq mécanismes indispensables : persistance, dispatch, compteurs, allocateur de mémoire partagée, ordonnancement.
- Le
switchd'opcode ne coûte rien : il est uniforme dans le bloc, l'hétérogénéité est entre SM. - Un megakernel est la spécialisation des warps à l'échelle du GPU.
Vérifiez que vous avez compris¶
Pourquoi la persistance est-elle obligatoire, et pas seulement souhaitable ?
Parce que la synchronisation inter-blocs par compteurs interbloque si tous les blocs ne sont pas résidents.
Scénario : le bloc A attend le compteur que le bloc B doit incrémenter. Si B n'a pas encore été lancé — parce qu'aucun SM ne s'est libéré, et qu'aucun ne se libérera puisque A attend — le programme se fige.
C'est pourquoi tout megakernel dimensionne sa grille par
cudaOccupancyMaxActiveBlocksPerMultiprocessor × nb_SM, et pourquoi
cudaLaunchCooperativeKernel impose la même contrainte.
En quoi le mécanisme de MPK diffère-t-il de simples compteurs ?
MPK introduit une notion d'événement avec des propriétés structurelles fortes.
Dans son graphe (le ttGraph), « tâches et événements alternent : chaque
tâche n'a que des arêtes sortantes vers des événements déclencheurs et des
arêtes entrantes depuis des événements de dépendance ». Après
normalisation, chaque tâche a au plus un événement de dépendance et un
événement déclencheur.
Cette régularité permet au compilateur d'appliquer des optimisations — fusion d'événements par ensembles de successeurs et de prédécesseurs, linéarisation par parcours en largeur pour que les tâches déclenchées par le même événement occupent des indices contigus.
Autrement dit : ce sont des compteurs, avec une structure algébrique qui les rend optimisables par un compilateur. C'est la différence entre écrire un megakernel à la main et en générer un.
Le papier de 2012 dit que les persistent threads « peuvent aussi entraîner une perte de performance dans de nombreux cas ». Lesquels ?
Quatre catégories, toutes toujours valables :
- Quand le travail est régulier et abondant. L'ordonnanceur matériel équilibre déjà parfaitement ; la file logicielle n'ajoute que du coût atomique.
- Quand l'occupancy en souffre. Un noyau persistant consomme des registres et de la mémoire partagée pour son état d'ordonnancement, ce qui réduit le nombre de warps résidents.
- Quand la contention atomique domine. Avec de nombreux blocs sondant la même file, les atomiques se sérialisent.
- Quand le noyau devient trop gros. Pression sur les registres, spilling, temps de compilation, et perte de la spécialisation par opération.
Ces quatre points sont exactement ceux du chapitre 9.
Chapitre suivant : 3 · L'interpréteur sur GPU
Sources de ce chapitre¶
- Gupta, Stuart, Owens, A Study of Persistent Threads Style GPU Programming for GPGPU Workloads, InPar 2012 — eScholarship · présentation GTC 2012
- Hazy Research, Look Ma, No Bubbles!
- Mirage Persistent Kernel, arXiv:2512.22219 — structure du ttGraph, normalisation, fusion d'événements.
- Event Tensor, arXiv:2604.13327
- 15-779 Lecture 7: Advanced CUDA Programming — Mega-Kernel, CMU (Zhihao Jia)