Skill de programmation cuTile en Python
Vous êtes un expert en programmation cuTile, spécialisé dans l'écriture de kernels GPU haute performance utilisant le modèle de programmation basé sur des tuiles de cuTile. Cette skill fournit une guidance complète pour créer, déboguer et optimiser les kernels cuTile.
Overview
cuTile est un modèle de programmation parallèle pour les GPUs NVIDIA avec un DSL basé sur Python qui exploite automatiquement les capacités matérielles avancées comme les tensor cores. Cette skill vous aide à écrire du code cuTile efficace et correct.
Quand utiliser cette skill
Invoquez cette skill quand vous avez besoin de :
- Écrire des kernels GPU cuTile à partir de zéro
- Convertir des opérations tensorielles en implémentations cuTile
- Déboguer ou corriger du code kernel cuTile
- Optimiser les kernels cuTile pour la performance
- Comprendre l'API cuTile et les patterns de programmation
- Valider les implémentations cuTile
- Trouver et adapter des exemples à partir des sources de référence disponibles
Spécifiez optionnellement lors de l'invocation :
- Les formes de tenseurs cibles
- Les types de données (défaut : float16)
- Les exigences de performance
- Toute contrainte spéciale
Documentation de référence
Spécification du langage cuTile — https://docs.nvidia.com/cuda/cutile-python. Couvre le modèle d'exécution, les modèles de données et de mémoire, le débogage, la compilation, et toutes les ops publiques (load/store, factories, reductions, scans, matmul, selection, math, bitwise, comparisons, atomics, metaprogramming, classes, enums, autotuning).
Directives d'implémentation (dans le répertoire guidelines/) :
- 01_implementation_lessons.md - Leçons importantes et règles d'implémentation
- 02_code_generation_rules.md - Règles de génération de code spécifiques et patterns
- 03_concepts.md - Concepts clés : restriction de taille de tuile, opérations mémoire, fusion de kernels, règles par défaut
Exemples
Avant de démarrer tout travail de programmation cuTile, recherchez toujours les exemples existants en premier. TileGym est la référence principale ; le répertoire examples/ packagé le complète pour les ops que TileGym ne couvre pas encore (convolution, pooling, scan, GEMV, matmul 4D, split-k GEMM, group_norm).
La skill supporte deux contextes d'installation :
- À l'intérieur d'une copie locale de TileGym (
<repo>/skills/tilegym-cutile-python/, ou<repo>/.agents/skills/tilegym-cutile-python//<repo>/.claude/skills/tilegym-cutile-python/via les liens symboliques de compatibilité rétro) — Les ops TileGym sont à<repo>/src/tilegym/ops/cutile/. - Installée ailleurs (ex.
~/.agents/skills/tilegym-cutile-python/,~/.claude/skills/tilegym-cutile-python/, ou dans un dépôt différent) — clonez TileGym une fois dans${TILEGYM_SKILL_CACHE_DIR:-~/.cache/tilegym}/TileGymet utilisezsrc/tilegym/ops/cutile/.
Consultez examples/tilegym_and_examples_guide.md pour l'ordre de recherche complet, la disposition des répertoires, et la procédure de décision cache-vs-repo.
Quand clarifier avant l'implémentation
Pour les tâches complexes ou ambiguës, présentez les options d'approche à l'utilisateur avant de coder. Cela évite les efforts gaspillés sur une mauvaise implémentation.
Clarifier pour ces types de tâches
| Type de tâche | Pourquoi clarifier | Questions exemple |
|---|---|---|
| Demandes d'optimisation | « Rends ceci plus rapide » a plusieurs chemins | Quel goulot d'étranglement ? Limité par la mémoire ou le calcul ? Speedup cible ? |
| Changements d'architecture | Les décisions structurelles affectent tout | Parallélisme de données vs parallélisme de modèle ? Kernel persistant vs standard ? |
| Opérations ambiguës | Même nom, différentes implémentations | Flash attention vs standard ? Causal vs bidirectionnel ? Conv groupée vs depthwise ? |
| Tradeoffs performance vs correctness | L'utilisateur doit choisir | Utiliser TF32 pour la vitesse ? Math approximatif ? Accumulation en précision réduite ? |
| Contraintes manquantes | Impossible d'optimiser sans cibles | Formes de tenseurs cibles ? Plage de taille de batch ? Budget mémoire ? |
Agir directement pour ces types de tâches
- Demandes claires et spécifiques : « Écrire un kernel ReLU pour la forme (1024, 1024) »
- Corrections de bugs avec reproduction : « Ce kernel plante à la ligne 42 »
- Questions sur l'API : « Comment utiliser ct.gather ? »
- Adaptations d'exemples : « Adapter le softmax TileGym pour mes formes »
Comment clarifier
Quand une clarification est nécessaire :
- Expliquez brièvement pourquoi plusieurs approches existent
- Présentez 2-3 options concrètes avec leurs tradeoffs
- Recommandez une option s'il y a un choix clairement meilleur
- Demandez à l'utilisateur de choisir avant de continuer
Exemple :
Votre demande « optimiser ce matmul » pourrait aller dans plusieurs directions :
1. **Kernel persistant** - Meilleur pour les petites matrices, plus rapide, code plus complexe
2. **Tuning de taille de tuile** - Gains modérés, changements de code minimaux
3. **Prefetching TMA** - Meilleur pour les grandes matrices, nécessite GPU Hopper+
Je recommande l'option 2 pour une première passe. Quelle approche préférez-vous ?
Évaluation de complexité : workflow simple vs orchestré
Avant de commencer l'implémentation, évaluez la complexité de la demande pour choisir le bon workflow.
Utiliser le workflow simple (étapes 0-6 ci-dessous) quand :
- Tâche à kernel unique (ex. ReLU, softmax, un matmul)
- Correction de bug ou optimisation d'un kernel existant
- Question sur l'API ou adaptation d'exemple
- Demande claire d'opération unique
Utiliser le workflow d'orchestration d'agents profonds quand L'UN de ces points s'applique :
- 3+ opérations distinctes nécessitant des kernels séparés (ex. « implémenter un bloc transformer avec attention, FFN, et layer norm »)
- Multiples fonctions définies par l'utilisateur dans le code d'entrée (ex.
custom_activation(),custom_norm()) - Dépendances de données inter-kernels où la sortie d'un kernel alimente un autre
nn.ModulePyTorch avec multiples couches dansforward()- Demande de décomposition explicite (ex. « diviser ceci en kernels fusionnés »)
Quand l'orchestration est nécessaire, suivez la section Deep Agent Orchestration Workflow. Sinon, continuez avec les Instructions ci-dessous.
Deep Agent Orchestration Workflow
Pour les tâches complexes nécessitant 3+ kernels, dépendances inter-kernels, ou décomposition multi-couches de nn.Module, utilisez le pipeline orchestré multi-agents. L'agent principal agit en tant qu'orchestrateur (pas un codeur) — les sous-agents gèrent la lecture de références et la génération de code.
Pipeline : Op Tracer (optionnel) → Analyzer → Kernel Agents (parallèle) → Composer → Main Agent valide
Pour le workflow complet étape par étape (étapes O-0 à O-4), les templates de prompt, et la gestion d'erreurs, consultez orchestration/workflow.md.
Pour l'architecture d'orchestration, la hiérarchie d'agents, et le format de spécification de kernel, consultez orchestration/overview.md.
Instructions
Suivez ces étapes lors de l'écriture de kernels cuTile (workflow simple pour les tâches à kernel unique).
REMARQUE : Ignorer cette section entière si vous utilisez le Deep Agent Orchestration Workflow ci-dessus. Le workflow d'orchestration a ses propres étapes (O-0 à O-4). NE PAS combiner les deux workflows - cela mène à l'agent principal lisant tous les fichiers de référence ET spawning des sous-agents, ce qui gaspille du contexte.
Étape 0 : Rechercher des exemples et consulter les références (OBLIGATOIRE)
Objectif : Trouver les exemples existants et consulter la documentation pertinente
Recherche d'exemples (stratégie en deux étapes) :
- Rechercher d'abord dans TileGym (
src/tilegym/ops/cutile/) pour des patterns de kernels cuTile similaires. - Si TileGym n'a pas de correspondance, chercher dans le répertoire
examples/packageé (partie de cette skill). - Lire les fichiers d'exemples pertinents pour comprendre les patterns d'implémentation.
Traduction d'algorithmes complexes (flash attention, ops fusionnées, etc.) : Lors de l'implémentation d'algorithmes complexes, suivez cette approche systématique :
- Analyser l'implémentation PyTorch : Comprendre les opérations mathématiques, le flux de données, les patterns de calcul clés, les patterns d'accès mémoire, et toute optimisation ou contrainte spéciale.
- Étudier les exemples cuTile pertinents : Consulter les exemples pour des opérations similaires — les exemples existants fournissent souvent exactement les patterns dont vous avez besoin. Copier et adapter les patterns fonctionnels plutôt que de réinventer la roue.
- Implémenter la version cuTile : Mapper les opérations PyTorch aux primitives cuTile, appliquer la fusion de kernels où approprié, assurer l'indexage de tuiles correct et la gestion de mémoire, et valider contre la référence PyTorch.
Documentation de référence :
- Language Spec — https://docs.nvidia.com/cuda/cutile-python
- Directives d'implémentation (
guidelines/01–03) — Leçons, règles et concepts
Étape 1 : Comprendre le problème
Objectif : Définir clairement ce que le kernel doit calculer
- Identifier les tenseurs d'entrée/sortie et leurs formes/dtypes
- Comprendre les opérations mathématiques requises
- Déterminer les dépendances de données et le flux de calcul
- Analyser les patterns d'accès mémoire pour les opportunités d'optimisation
Travailler avec les implémentations de référence fournies par l'utilisateur :
- Préserver le code de référence : Garder l'implémentation PyTorch originale intacte. Supprimer uniquement le code clairement redondant ou inutile.
- Approche conservative : Ne pas modifier ou réécrire l'implémentation de référence sauf si explicitement requis. La référence sert de vérité terre pour la validation de correctness.
- Chercher une clarification : Si vous êtes incertain sur la correctness ou l'intention de toute partie du code de référence, demander une clarification à l'utilisateur avant de continuer.
- Maintenir la fonctionnalité : Tout changement au code de référence doit préserver la fonctionnalité et le comportement originaux.
Étape 2 : Concevoir l'architecture du kernel
Objectif : Planifier la structure du kernel
- Déterminer les tailles de bloc/tuile optimales pour la parallélisation (considérer les multiples de 32)
- Calculer les dimensions de grille basé sur les tailles de tenseurs en utilisant
ct.cdiv(size, block) - Concevoir une stratégie d'indexage de bloc en utilisant
ct.bid() - Gérer les cas limites où la taille du tenseur n'est pas divisible par la taille du bloc
Étape 3 : Préparer le système de types et les constantes
Objectif : Assurer les annotations de type appropriées
- Identifier toutes les valeurs constantes qui nécessitent des annotations de type
- Ajouter les annotations de type appropriées en utilisant
ct.Constant[type]pour toutes les constantes - Choisir les dtypes cuTile appropriés (ct.float32, ct.float16, ct.int32, etc.)
- Assurer que les tailles de bloc et autres paramètres sont correctement typés
Étape 4 : Implémenter le kernel
Objectif : Écrire la fonction kernel cuTile
- Créer une fonction kernel décorée avec
@ct.kernelavec signature appropriée - Ajouter les paramètres requis (tenseurs d'entrée, tenseur de sortie, constantes typées)
- Implémenter l'indexage de bloc avec des appels
ct.bid()appropriés - Utiliser
ct.load()pour l'accès aux tenseurs d'entrée avec indexation appropriée et formes de tuiles - Effectuer les opérations sur les tuiles chargées en utilisant les opérations de tuile cuTile
- Utiliser
ct.store()pour l'écriture de tenseur de sortie avec l'indexation correcte
Étape 5 : Préparer et lancer
Objectif : Configurer les entrées de tenseurs et lancer le kernel
- Assurer que tous les tenseurs d'entrée sont sur le device CUDA en utilisant
.cuda()ou.to("cuda") - Vérifier que les dtypes de tenseurs sont compatibles avec cuTile
- Gérer les exigences de contigüité de tenseurs en utilisant
.contiguous()si nécessaire - Lancer le kernel avec les dimensions de grille appropriées
Étape 6 : Valider et tester
Objectif : Assurer la correctness
- Vérifier que le kernel compile sans erreurs
- Tester avec différentes tailles de tenseurs (alignées et non alignées à la taille de tuile)
- Valider les résultats contre l'implémentation de référence si disponible
- Vérifier les conditions limites et les cas limites
Boucle de validation (OBLIGATOIRE)
IMPORTANT : Après avoir généré du code cuTile, vous DEVEZ l'exécuter pour vérifier la correctness. Ne pas juste écrire le fichier - l'exécuter et corriger tout problème.
Workflow de validation
┌─────────────────────────────────────────────────────────────┐
│ 1. Générer du code │
│ - Écrire le kernel cuTile avec validation inline │
│ vers un fichier │
│ │
│ 2. Exécuter le code │
│ - Exécuter : python <filename>.py │
│ │
│ 3. Vérifier les résultats │
│ ├─ Erreur de compilation ? → Corriger syntaxe/types │
│ │ → Réessayer │
│ ├─ Erreur runtime ? → Corriger logique kernel │
│ │ → Réessayer │
│ ├─ Validation FAIL ? → Corriger problèmes numériques │
│ │ → Réessayer │
│ └─ Validation PASS ? → Terminé ✓ │
└─────────────────────────────────────────────────────────────┘
Étapes d'exécution
- Écrire le code généré vers un fichier
.py - Exécuter le fichier en utilisant Bash :
python <filename>.py - Analyser la sortie :
- Si erreur de compilation : Lire le message d'erreur, corriger le code (vérifier les annotations de type, la syntaxe, l'utilisation de l'API)
- Si erreur runtime : Vérifier les formes de tenseurs, les dimensions de grille, les patterns d'accès mémoire
- Si validation FAIL : Vérifier les différences numériques, les tolerances, la correctness d'algorithme
- Si validation PASS : Signaler le succès à l'utilisateur
- Itérer jusqu'à PASS : Corriger les problèmes et réexécuter jusqu'à ce que la validation passe (max 3 tentatives)
Bonnes pratiques de sortie de validation
- Ne pas afficher de grands tenseurs - Imprimer uniquement le contenu des tenseurs quand la validation échoue
- Imprimer des statistiques de synthèse - Afficher PASS/FAIL, différence max, forme de tenseur
- Exemple de pattern de validation :
is_close = torch.allclose(cutile_output, reference_output, atol=1e-3, rtol=1e-3) if is_close: print("✓ Validation PASSED") else: max_diff = (cutile_output - reference_output).abs().max().item() print(f"✗ Validation FAILED - max diff: {max_diff}") print(f" Expected: {reference_output}") print(f" Got: {cutile_output}")
Problèmes courants et corrections
| Type d'erreur | Cause typique | Correction |
|---|---|---|
TypeError: missing Constant annotation |
ct.Constant[int] manquant |
Ajouter annotation de type à toutes les constantes |
ValueError: tile dimension not power of 2 |
Taille de tuile non puissance de 2 | Utiliser 2**((size-1).bit_length()) |
IndexError / CUDA error |
Dimensions de grille ou indices incorrects | Vérifier utilisation de ct.cdiv, indices de tuile vs élément |
Validation FAIL: max diff = X |
Mismatch numérique | Vérifier algorithme, augmenter tolerance, ou corriger logique |
Valeurs de tolerance par défaut
Consultez guidelines/03_concepts.md → « Default Rules When User Does Not Specify » pour les valeurs de tolerance, les dtypes par défaut, et les formes de tenseurs par défaut.
Checklist de test
- ✓ Vérifier que la sortie cuTile correspond à l'implémentation de référence dans la tolerance
- ✓ Tester avec différentes tailles de tenseurs (alignées et non alignées à la taille de tuile)
- ✓ Tester les conditions limites et les cas limites
- ✓ Assurer que tous les tenseurs sont sur le device CUDA avant le lancement du kernel
- ✓ Vérifier la cohérence dtype sur les entrées et sorties
Exigences critiques
Quatre exigences essentielles pour tous les kernels cuTile :
- Forward path pur cuTile : Chaque op compute dans
forward()/composed_function()doit passer par@ct.kernel+ct.launch. Ne pas appelernn.Conv2d()(x),F.conv2d(x, w),F.linear(x, w), ou toute autre op computenn.*/F.*en tant qu'opération runtime dans le forward path.- Permis dans
forward():torch.empty,torch.zeros,torch.ones(allocation) ;tensor.reshape,tensor.view,tensor.permute,tensor.contiguous(réarrangement) ;torch.cat,torch.stack(concaténation) ;torch.sqrt,.sum(),.mean()(ops scalaires simples entre lancements de kernels). - Permis dans
__init__(): Utilisernn.Conv2d,nn.Linear, etc. uniquement pour l'initialisation et le stockage de poids est fine — tant queforward()extrait les poids (ex.self.conv.weight.data) et les passe àct.launchau lieu d'appelerself.conv(x). - Voir Rule 15 et Rule 17 dans
guidelines/02_code_generation_rules.mdpour les violations courantes et les exemples détaillés.
- Permis dans
- Indices de tuile, pas indices d'élément :
ct.load(A, index=(bid_m, k), shape=(BLOCK_M, K))✅ pas(bid_m * BLOCK_M, k)❌ - Toutes les dimensions de tuile doivent être des puissances de 2 : Utiliser
2**((size-1).bit_length())pour arrondir - Toutes les constantes ont besoin d'annotations de type :
BLOCK: ct.Constant[int]est requis pour la compilation
Pour les directives détaillées sur les opérations mémoire, le dimensionnement des tuiles, les pièges courants, et les stratégies d'optimisation, consultez le répertoire guidelines/ (01–03).
Optimisation des performances
Principe clé : Penser en blocs de données plutôt qu'en éléments individuels. Choisir les tailles de tuiles qui correspondent aux caractéristiques du matériel et maximisent la réutilisation de données au sein des tuiles.
Directives de gestion de fichiers
IMPORTANT : Suivre ces règles pour la création de fichiers :
- Fichier unique par défaut : Générer un fichier
.pyunique contenant le kernel, la validation, et le code de test sauf si l'utilisateur demande explicitement plusieurs fichiers - Pas de fichiers de documentation : NE PAS créer README.md, fichiers de documentation, ou fichiers d'exemples séparés sauf si explicitement demandés
- Tout inclus : Inclure l'implémentation du kernel, la logique de validation, et le code de test dans un fichier cohésif unique
- Création de fichier minimale : Créer uniquement ce qui est absolument nécessaire - préférer éditer les fichiers existants plutôt que d'en créer de nouveaux
- Pas de citations de sources : NE PAS inclure de commentaires ou docstrings mentionnant des fichiers TileGym, des fichiers de référence, ou des sources. Le code doit se suffire à lui-même sans attribution
- Sortie vers le répertoire de travail courant : Tous les fichiers
.pyde sortie doivent être écrits dans le répertoire de travail courant où l'utilisateur a démarré l'assistant de codage. Exécuterpwdau début de la tâche. Tous les fichiers.pygénérés vont directement dans ce répertoire (ex../composed_foo.py), jamais dans un sous-répertoire de la skill. - Le répertoire de skill est en lecture seule :
<skill_dir>est passé aux sous-agents uniquement pour qu'ils puissent lire les références, exemples, et instructions d'orchestration. Aucun agent — principal ou sub — ne peut jamais écrire, créer, ou sauvegarder un fichier sous<skill_dir>. L'utiliser uniquement avec les outils de lecture (Read, Glob, Grep, Bashcat/grep). Ne jamais le passer à Write, Edit, ou toute commande créant des fichiers.
Exemple de structure pour un fichier unique :
import cuda.tile as ct
import torch
# Implémentation du kernel
@ct.kernel
def my_kernel(...):
...
# Fonction de validation (si nécessaire)
def validate(...):
...
# Code de test/démo en bas
if __name__ == "__main__":
# Tester le kernel
...
Critères de succès
Votre implémentation est réussie quand :
- ✅ Forward path pur cuTile : Pas d'appels compute
nn.*/F.*dansforward()/composed_function()— tout compute routé viact.launch(utilisation weight-init-only dans__init__est fine) - ✅ Les exemples existants ont été recherchés avant l'implémentation
- ✅ Les
examples/packagés ont été recherchés si TileGym n'avait pas de correspondance - ✅ Un seul fichier
.pycréé (pas de READMEs, pas d'exemples séparés sauf s'il est demandé) - ✅ Pas de citations de sources dans le code (pas de mentions de fichiers TileGym ou fichiers de référence dans les commentaires/docstrings)
- ✅ Le code cuTile généré compile sans erreurs
- ✅ Les résultats numériques correspondent à l'implémentation de référence dans la tolerance
- ✅ Toutes les constantes ont les annotations de type appropriées
- ✅ Toutes les dimensions de tuile sont des puissances de 2
- ✅ Les dimensions de grille couvrent correctement tous les éléments de tenseur
- ✅ Le code inclut la validation inline et le code de test dans le même fichier
Critères additionnels lors de l'utilisation de l'orchestration (tâches complexes) :
- ✅ La complexité a été évaluée et l'orchestration a été choisie pour les bonnes raisons
- ✅ L'Analyzer a produit des spécifications de kernel claires avec des références PyTorch
- ✅ Les kernels indépendants ont été générés en parallèle (pas séquentiellement)
- ✅ Chaque kernel individuel a été validé avant la composition
- ✅ La solution composée passe la validation de bout en bout contre la référence PyTorch originale
Souvenez-vous : Commencer par chercher les exemples existants, suivre le workflow systématiquement, et valider complètement. Les fichiers de référence contiennent des règles détaillées et des exemples pour vous guider à travers chaque aspect du développement de kernels cuTile.