Skill d'Optimisation des Performances Hôte
Automatise la détection et l'optimisation des surcharges côté hôte (CPU) dans le backend PyTorch de TensorRT-LLM.
Quand l'utiliser
- L'utilisation GPU est faible pendant l'inférence (bottleneck CPU suspecté)
- L'utilisateur demande de réduire la surcharge hôte ou la latence CPU
- Optimisation du débit de PyExecutor (requêtes/sec)
- Besoin de profilage ligne par ligne de fonctions Python spécifiques
Détecter un Bottleneck CPU
line_profiler mesure où le temps CPU est dépensé mais pas si le CPU est le bottleneck. Quand l'utilisateur demande une confirmation, ou qu'il n'y a pas de conditions de fin claires pour les optimisations, utilisez nsys (trace système) pour confirmer que le CPU est le facteur limitant :
| Indicateur (depuis nsys) | Seuil | Signification |
|---|---|---|
| Vides GPU entre kernels | >30% du temps d'étape | Le CPU ne peut pas alimenter le GPU assez vite |
Temps API cudaLaunchKernel |
>10% du temps total | Surcharge de lancement de kernel |
Plage NVTX _prepare_tp_inputs |
>50% du temps d'étape | La préparation des entrées domine |
| Utilisation SM du GPU | <60% sans bottleneck comm/sync | Inférence limitée par le CPU |
Si le rapport nsys n'est pas disponible, utilisez une heuristique approximative : si doubler la taille du batch n'augmente pas proportionnellement l'utilisation GPU ou le débit, la surcharge CPU est probablement le bottleneck.
Utiliser les Résultats de la Skill d'Analyse
Si la skill perf-host-analysis a déjà été exécutée, utilisez sa sortie pour ignorer l'étape de confirmation et prioriser les cibles :
- Verdict de détection : Si OUI avec host_prep_confirmed, commencez par
_prepare_tp_inputs. - Triage NVTX (depuis Root Cause) : Le
top_regressing_opsdu bloc de données de transmission mappe les noms de plages NVTX aux fonctions source. Profilez d'abord la fonction avec le plus grand delta absolu. - Triage inter-fonction : Quand la plus grande régression NVTX n'est PAS dans
_prepare_tp_inputs(par ex._fetch_new_requests,broadcast_requests,_update_requests), ciblez directement le fichier source de cette fonction au lieu de défaut vers_prepare_tp_inputs. Voir references/trtllm-nvtx-ranges.md pour la correspondance NVTX-vers-source.
Configuration du Profilage
line_profiler (Méthode Principale)
Variables d'environnement :
TLLM_LINE_PROFILER_ENABLED=True— Activez le profileurTLLM_LINE_PROFILER_PATH— Chemin du fichier de sortieTLLM_LINE_PROFILER_FUNCTIONS— Fonctions supplémentaires à profiler (séparées par des virgules)
Format de spécification de fonction :
# Méthodes de classe : module.path.ClassName.method_name
TLLM_LINE_PROFILER_FUNCTIONS="tensorrt_llm._torch.pyexecutor.model_engine.PyTorchModelEngine._prepare_tp_inputs"
# Fonctions autonomes : module.path::function_name
TLLM_LINE_PROFILER_FUNCTIONS="tensorrt_llm._torch.pyexecutor.sampler::_group_requests_by_strategy_key"
# Fonctions multiples (séparées par des virgules)
TLLM_LINE_PROFILER_FUNCTIONS="module.Class.method1,module.Class.method2"
Affinité CPU (Facteur d'Environnement)
L'affinité des cœurs CPU peut affecter significativement les mesures de surcharge hôte, particulièrement sur les systèmes multi-socket (par ex. B300). Épingler les processus à des cœurs proches du nœud NUMA du GPU réduit la latence d'accès mémoire inter-socket.
- Vérifier l'affinité actuelle :
taskset -p <pid>ounumactl --show - Épingler au nœud NUMA local :
numactl --cpunodebind=<node> --membind=<node> - Impact : Jusqu'à 2x de différence en surcharge hôte sur les systèmes B300
Lors de la comparaison des résultats de profilage entre les exécutions, assurez-vous que l'affinité CPU est cohérente. Ne modifiez pas l'affinité en externe, sauf si l'utilisateur vous demande de le faire pour examiner les effets de cette partie. Documentez le paramètre d'affinité dans le rapport de chaque round s'il varie.
Gestion de l'Espace de Travail et du Suffixe
Chaque exécution de profilage doit avoir un suffixe unique pour suivre la progression entre les rounds :
EXTRA_SUFFIX=round0_baseline bash profile.sh
EXTRA_SUFFIX=round1_eliminate_redundant_iter bash profile.sh
Boucle d'Optimisation Autonome
Exécutez N rounds (3 par défaut) du cycle suivant :
FOR round = 1 to MAX_ROUNDS:
1. PROFILE (avec Drill-Down)
2. ANALYZE (Multi-Option)
3. OPTIMIZE (Apply Change)
4. TEST (Unit Test Validation)
5. VALIDATE (Re-Profile)
6. REPORT
END FOR → FINAL SUMMARY
Phase 1 : PROFILE (avec Drill-Down)
- Exécutez la charge de travail avec le profileur activé
- Parsez la sortie : identifiez les fonctions avec le temps Total le plus élevé et les lignes avec le % Time le plus élevé
- CRITIQUE : Fouillez dans les sous-fonctions pas encore profilees (voir ci-dessous)
Profilage Drill-Down
Le profileur par défaut couvre les fonctions d'exécuteur de haut niveau mais pas toutes les sous-fonctions. Quand une fonction profileée montre la plupart du temps dans un seul sous-appel, vous devez fouiller.
Quand : Une seule ligne consomme >80% du temps d'une fonction en appelant une sous-fonction non profileée :
Line # Hits Time Per Hit % Time Line Contents
==============================================================
2848 4100 59200000000.0 14439024.4 98.7 output = self.model_engine.forward(...)
Comment :
- Identifiez le chemin qualifié complet de la sous-fonction (par ex.
tensorrt_llm._torch.pyexecutor.model_engine.PyTorchModelEngine._prepare_tp_inputs) - Ajoutez-le à
TLLM_LINE_PROFILER_FUNCTIONS - Reprofilez pour obtenir les données au niveau des lignes à l'intérieur
- Analysez maintenant les hotspots internes
Pour les cibles de drill-down courantes, voir references/hot-path-files.md.
Phase 2 : ANALYZE (Multi-Option)
Pour le hotspot choisi :
- Identifiez les hotspots supérieurs par temps absolu (pas seulement %) dans la fonction cible
- Classifiez chaque hotspot par type. Tableau récapitulatif :
| Type | Indicateurs | Sévérité |
|---|---|---|
| SYNC | .item(), .cpu(), synchronize() |
Critique |
| ALLOC | torch.zeros/empty/tensor() dans des boucles, .clone() |
Haute |
| PYLOOP | for x in collection: avec beaucoup d'itérations |
Haute |
| REDUNDANT_ITER | Passages multiples sur la même collection | Haute |
| DEAD_WORK | Construction d'objet dont les résultats sont toujours ignorés | Haute |
| CONTAINER | Recherches Dict/set dans des boucles chaudes | Moyenne |
| FUNCALL | Appels de méthode/propriété répétés | Moyenne |
| GIL | Contention de verrou/queue | Moyenne |
| GC | Pics de latence périodiques, pauses non-déterministes | Basse |
| COMPUTE | Calcul réel (peut ne pas être optimisable) | Basse |
Pour une classification détaillée avec exemples de code, voir references/hotspot-classification.md.
- Proposez 2-4 options d'optimisation dans un tableau :
| Option | Description | Économies Estimées | Risque | Complexité |
|---|---|---|---|---|
| A | ... | ... | Bas/Moyen/Haut | ... |
| B | ... | ... | ... | ... |
- Sélectionnez la meilleure option et expliquez le raisonnement (préférez économies élevées + risque faible)
Pour les modèles d'optimisation par type, voir references/optimization-patterns.md.
Phase 3 : OPTIMIZE (Apply Change)
- Appliquez le changement de code sélectionné avec l'outil Edit
- Une optimisation par round — gardez les changements minimaux et ciblés
- Enregistrez le changement exact (fichier, plage de lignes, avant/après) pour un potentiel rollback
Phase 4 : TEST (Unit Test Validation)
Obligatoire après chaque optimisation. Trouvez et exécutez les UTs associés pour vérifier la correction.
Trouver les tests associés :
# Recherche par nom de fichier modifié
grep -rl "model_engine\|PyTorchModelEngine" tests/unittest/_torch/executor/
# Recherche par nom de fonction modifié
grep -rl "_prepare_tp_inputs\|prepare_inputs" tests/
Exécuter les tests :
# Exécuter un fichier de test spécifique avec arrêt à la première défaillance
pytest tests/unittest/_torch/executor/test_pytorch_model_engine.py -v -x --timeout=120
# Exécuter une méthode de test spécifique
pytest tests/unittest/_torch/executor/test_pytorch_model_engine.py::PyTorchModelEngineTestCase::test_position_id_preparation -v -x
Pour la correspondance complète UT-vers-fichier, voir references/hot-path-files.md.
Si les tests échouent :
- Lisez le message d'erreur
- Rollback immédiatement (
git checkout -- <file>) - Analysez pourquoi l'optimisation a cassé la correction
- Essayez l'option suivante meilleure de la Phase 2
Phase 5 : VALIDATE (Re-Profile)
- Réexécutez le profileur avec la même charge de travail, en utilisant le suffixe
round<N>_<description> - Comparez trois choses :
- Le temps du hotspot cible a-t-il diminué ?
- Le temps Total de la fonction globale a-t-il diminué ?
- Les métriques de benchmark (TPOT, débit) ont-elles amélioré ?
Si une régression est détectée (le temps de fonction a augmenté ou les métriques se sont aggravées) :
- L'« optimisation » a peut-être déclenché un piège CPython — voir references/optimization-patterns.md (section CPython Pitfalls)
- Rollback et essayez l'option suivante meilleure de la Phase 2
Phase 6 : REPORT
Journal pour ce round :
- Numéro du round
- Localisation du hotspot (fichier:ligne) et classification
- Optimisation appliquée (avec résumé du code avant/après)
- Delta de temps : temps Total de la fonction avant → après
- Delta de benchmark : TPOT, débit avant → après
Lecture de la Sortie de Profil
Timer unit: 1e-06 s
Total time: 1.234 s
File: /path/to/file.py
Function: my_function at line 100
Line # Hits Time Per Hit % Time Line Contents
==============================================================
100 def my_function(self):
101 500 890000.0 1780.0 72.1 result = tensor.item()
102 500 234567.0 469.1 19.0 return result
Comment lire efficacement :
- Commencez par le temps Total pour chaque fonction — c'est le budget global
- Triez mentalement les lignes par Time absolu, pas seulement % Time (3% d'une fonction 60s = 1,8s)
- Vérifiez le nombre de Hits pour comprendre les modèles d'itération :
- Hits = 2 × nombre attendu → surcharge de boucle
for x in range(1):(2 hits = entrée + vérif sortie) - Hits ≫ attendu → la ligne est dans une boucle imbriquée
- Hits = 2 × nombre attendu → surcharge de boucle
- Cherchez les modèles répétés : si 10 lignes chacune prennent 3% dans un corps de boucle, la boucle elle-même coûte 30%
Critères d'Arrêt
Arrêtez la boucle d'optimisation quand :
- Limite d'itération atteinte : N rounds complétés (3 par défaut)
- Aucun hotspot actif : Les hotspots supérieurs sont du calcul GPU pur (type COMPUTE)
- Rendements décroissants : < 5% d'amélioration dans les 2 derniers rounds
- Seuil de risque : Les optimisations supplémentaires nécessitent des changements architecturaux (par ex. Cython, struct-of-arrays)
- Défaillances de tests : Impossible de trouver une optimisation qui passe les UTs
Métrique de succès primaire : Débit de benchmark (requêtes/sec ou tokens/sec) tel que mesuré par le script de profilage. Les réductions de temps line_profiler sont des indicateurs avancés, mais le débit est la vérité de base — une accélération au niveau fonction qui n'améliore pas le débit n'est pas une vraie victoire.
Rapport de Synthèse Final
Le rapport final doit inclure :
- Rounds exécutés : Nombre de cycles profile-optimize complétés
- Amélioration cumulative : Réduction totale du temps hôte (pourcentage et absolu)
- Métriques de benchmark : Tableau de comparaison avant/après (TPOT, débit, ITL, E2EL)
- Optimisations appliquées : Liste de changements avec localisation fichier:ligne et classification
- Tentatives échouées : Toute optimisation essayée et annulée (avec pourquoi)
- Hotspots restants : Bottlenecks supérieurs qui n'ont pas pu être optimisés (avec classification)
- Recommandations : Suivi suggéré pour les changements architecturaux si nécessaire
Pour un exemple multi-round concret, voir references/examples.md.
Fichiers de Référence
| Fichier | Contenu |
|---|---|
| references/optimization-patterns.md | Catalogue de modèles par type de hotspot + pièges CPython |
| references/hotspot-classification.md | Indicateurs étendus par type et exemples de code |
| references/hot-path-files.md | Tableaux de fichiers clés, cibles drill-down, correspondance UT |
| references/examples.md | Exemples d'utilisation et walkthrough multi-round |
| trtllm-nvtx-ranges.md | Référence de plage NVTX TRT-LLM (depuis la skill d'analyse) — mappe les noms de plages aux fonctions source |