Débogage de la précision d'AutoDeploy
Où cette skill s'applique
Ce fichier fait partie de trtllm-agent-toolkit. Les chemins tels que tensorrt_llm/, tests/, et
examples/auto_deploy/ sont relatifs à un checkout source de TensorRT-LLM sur la machine de l'utilisateur,
non au repository du plugin.
Skills connexes dans ce plugin
trtllm-agent-toolkit:ad-graph-dump— inspecter les snapshots de graphes FX par transformation quand la Phase 2 suggère qu'une transformation a été appliquée incorrectement ou corrompt les activations.trtllm-agent-toolkit:ad-conf-check— vérifier que les paramètres de précision ou de configuration (FP8, sharding, prefill par chunks, etc.) ont bien été appliqués à l'exécution avant d'attribuer un écart de précision à un kernel ou un bug de poids.
Input : nom du modèle, score de précision en échec, score de référence, tâche d'évaluation (ex. MMLU, GSM8K). Output : cause première identifiée, reproducer minimal, et correctif de code.
Évaluation de la situation
Avant le débogage, confirmez :
- Quel est le score de référence ? Provient-il d'un test backend PyTorch, d'un leaderboard publié, ou défini manuellement ?
- Quelle est l'ampleur de l'écart ? Un écart de 1-2 % peut être du bruit statistique ; un écart de 5 %+ est un vrai bug.
- Le framework d'évaluation lui-même est-il suspect ? Lancez la même évaluation sur le backend PyTorch pour valider le framework avant de blâmer AutoDeploy.
Abréviations
- AD — AutoDeploy, TRT-LLM avec le backend
_autodeploy - PT — PyTorch, TRT-LLM avec le backend
pytorch(déploiement manuel)
Phase 0 — Valider le framework de test
Lancez le test équivalent du backend PyTorch sur le même modèle et la même tâche d'évaluation. Si PT échoue aussi ou obtient un score inférieur aux attentes, le problème se trouve dans le framework d'évaluation (format de prompt, chat template, paramètres d'échantillonnage), non spécifique à AD.
Éléments clés à vérifier dans le framework d'évaluation :
- Format de prompt /
apply_chat_template: l'évaluateur envoie-t-il des prompts bruts ou applique-t-il un chat template ? La relation est bidirectionnelle pour les modèles de raisonnement/chat :- Appliquer
apply_chat_templateà un prompt few-shot concaténé (sansfewshot_as_multiturn) effondre les exemples en un single turn mal formé et peut produire 0 % de précision. - Omettre
apply_chat_templatepour un modèle chat-first peut être tout aussi faux. Pour les modèles chat sur des benchmarks few-shot, considérez siapply_chat_template=Trueassocié avecfewshot_as_multiturn=Trueest approprié — ce dernier transforme chaque exemple few-shot en un échange utilisateur/assistant explicite avant l'application du template. (Référence : correction de précision de Qwen3.5-MoE danstest_llm_api_autodeploy.py.)
- Appliquer
max_output_lenpour les tâches de génération : pour les benchmarks où le modèle doit générer une chaîne de raisonnement complète avant la réponse (ex. GSM8K avec un modèle de raisonnement), leMAX_OUTPUT_LENpar défaut peut tronquer la réponse avant que la réponse finale soit atteinte. Envisagez de l'augmenter (ex. 512) si les sorties semblent coupées. C'est différent du plafonnage demax_tokenspour les tâches de classification comme MMLU où vous voulez empêcher les longues générations.max_tokenspour les tâches de classification : doit être plafonné (ex. 2 pour MMLU) pour empêcher le modèle de générer une chaîne de raisonnement complète.- Chemin du dataset : confirmez que
LLM_MODELS_ROOTest défini correctement et que le répertoire du dataset existe.
Si PT réussit : le framework est correct. Passez à la Phase 1.
Phase 1 — Diagnostic rapide avec un petit échantillon
Écrivez un script de diagnostic autonome qui :
- Charge le modèle AD directement via
from tensorrt_llm._torch.auto_deploy import LLM as AutoDeployLLM - Reproduit le exact format de prompt utilisé par l'évaluateur (pas une variante simplifiée), y compris les exemples few-shot s'il y en a
- Lance ~50-100 échantillons
- Affiche par échantillon
(ref, output, correct)et la précision globale
Critique : reproduisez le format exact du prompt de l'évaluateur. S'écarter — par exemple, utiliser un prompt 0-shot quand l'évaluateur utilise 5-shot — peut faire générer aux modèles de réflexion des « OK » ou d'autres réponses méta au lieu de la réponse attendue, rendant les résultats non interprétables. Vérifiez que le premier prompt affiché correspond à ce que l'évaluateur envoie.
Sources d'évaluateur typiques :
tensorrt_llm/evaluate/mmlu.py— format 5-shot avec exemples devtensorrt_llm/evaluate/gsm8k.py— few-shot avec références CoTtests/integration/defs/accuracy/accuracy_core.py—MAX_INPUT_LEN,MAX_OUTPUT_LEN,NUM_SAMPLESpar tâche
Phase 2 — Classer le modèle d'erreur
À partir de la sortie du diagnostic, déterminez ce que le modèle génère :
| Modèle de sortie | Cause première probable |
|---|---|
| Cohérent mais lettre / réponse systématiquement fausse | Bug de précision numérique (attention, kernel FP8, corruption de poids) |
| Génère du texte méta ("L'utilisateur veut...", "La réponse est...", "Laisse-moi réfléchir...") | Problème de format de prompt — modèle non amorcé pour répondre directement |
| Affiche une chaîne vide ou EOS immédiatement | Garbage dans le KV cache (cache non initialisé, débordement d'échelle), ou end_id correspondant au premier token |
| Tokens complètement aléatoires / charabia | Transformation appliquée incorrectement, hook de chargement manquant ou appliqué deux fois, poids corrompus |
| Correct sur les sujets faciles, faux sur les sujets difficiles | Bug de précision numérique subtil (mismatch de kernel FP8, échelle d'attention fausse) |
| NaN dans les logits, surtout au prefill | La transformation du graphe FX a produit un nœud sans métadonnées de shape — activez AD_DUMP_GRAPHS_DIR et cherchez les nœuds manquant meta["val"] ; souvent causé par une closure Python opaque à l'intérieur d'une transformation |
Réussit à world_size=1, échoue à world_size>1 |
Bug de sharding — voir Phase 4c |
Phase 3 — Isolation de la configuration
Réduisez l'environnement à sa forme la plus simple, puis réactivez les composants un par un jusqu'à ce que la régression réapparaisse.
Étape 1 — Réduire à une configuration minimale :
Quand c'est possible, réduisez la complexité sur chaque axe, en relançant le diagnostic de la Phase 1 après chaque changement :
- Supprimez le sharding ou réduisez à TP=1 / GPU unique
- Désactivez le multi-streaming
- Désactivez les passes de transformation non-défaut dans le fichier YAML (
enabled: false) - Revenez au
compile_backendtorch-simple: AutoDeploy supporte actuellement deux backends —torch-cudagraph(CUDA graphs, le paramètre de production typique) ettorch-simple(pas de CUDA graphs, considérablement plus lent). Si le modèle est configuré avectorch-cudagraph, revenez àtorch-simpleet vérifiez si le problème de précision persiste. Notez que le débit plus lent rendra la boucle de validation plus longue. Si la précision se rétablit àtorch-simple, la capture ou la relecture du graphe CUDA est suspecte.
Si le problème disparaît quand un composant est supprimé, ce composant est suspect — notez-le et passez à l'Étape 2 en le ciblant. Si le problème persiste même à la config minimale, le bug se trouve dans un chemin core (chargement de poids, attention, KV cache) — passez à la Phase 4.
Étape 2 — Réactivez un composant à la fois :
À partir de la configuration réduite qui reproduit encore le problème, réactivez les composants suspects individuellement — un par diagnostic run. Arrêtez dès que la précision baisse : le dernier composant réactivé est la pass ou le backend fautif. Portez cette découverte en Phase 4 pour investiguer la cause première.
Phase 4 — Investigation de la cause première
Cette phase contient des chemins d'investigation ciblés pour les catégories de causes premières connues. Ajoutez des étapes spécifiques au modèle ou au modèle d'erreur ici au fur et à mesure qu'elles sont découvertes.
4a — Précision du modèle quantifié
Si le modèle en échec est quantifié (ex. FP8, NVFP4), vérifiez d'abord si le problème se trouve dans la quantification elle-même ou dans le chemin du kernel quantifié :
Étape 1 — Testez une baseline non-quantifiée.
Demandez à l'utilisateur une version non-quantifiée (BF16/FP16) du même modèle. Lancez le diagnostic de la Phase 1
dessus avec une configuration identique (même compile_backend, même TP, même format d'eval).
- Si le modèle non-quantifié échoue aussi : le bug ne concerne pas la quantification — le problème se trouve dans une pass de transformation, l'implémentation d'attention, ou le chargement de poids. Continuez avec l'isolation de la Phase 3 sur le modèle non-quantifié.
- Si le modèle non-quantifié réussit : l'écart de précision est introduit par la quantification ou le chemin du kernel quantifié. Passez à l'Étape 2.
Étape 2 — Classification des suspects.
Quand la quantification est confirmée comme source, les causes probables sont (dans un ordre approximatif de sévérité) :
| Suspect | Symptôme | Comment isoler |
|---|---|---|
| Échelle manquante pendant la dequantification | Logits proches de zéro ou astronomiquement grands ; perte de précision catastrophique (≈ aléatoire ou pire) | Loggez quelques logits bruts ; ils seront sauvagement hors de portée |
| Échelle inversée (multipliée au lieu de divisée, ou inversement) | Similaire à catastrophique ; sorties de tokens plausibles mais systématiquement fausses | Même inspection de logits ; comparez les valeurs d'échelle du checkpoint vs ce que le kernel reçoit |
| Calcul d'échelle de bloc incorrect | Dégradation majeure mais pas catastrophique ; typiquement 5–20 % sous la référence non-quantifiée | Comparez les échelles par-bloc contre un quantificateur de référence sur quelques tenseurs de poids |
.to(dtype) utilisé au lieu de .view(dtype) pour la réinterprétation de format compacté |
Valeurs d'échelle ou de poids fausses sans erreur — .to() convertit les valeurs numériquement tandis que .view() réinterprète les bits bruts |
Grep la transformation de quantification pour .to( sur des tenseurs de poids/échelle quantifiés de types compactés (FP4, FP8) ; l'intention est la réinterprétation au niveau bit, qui nécessite .view() |
| Bug du kernel quantifié (accumulation fausse, cast faux) | Non-catastrophique ; peut être dépendant de l'input ou de la shape | Étape 3 ci-dessous |
Étape 3 — Isolez les kernels quantifiés via fake quantization.
Le pipeline de transformation d'AutoDeploy a un chemin fake-quantization intégré qui implémente exactement Q→DQ→high-precision-matmul. Comprendre les deux étapes aide :
-
Étape 1 (
pattern_matcher) : Remplace les nœudsnn.Linearpartorch.ops.auto_deploy.torch_fake_quant_fp8_linear/torch_fake_quant_nvfp4_linearetc. Ces ops quantifient l'input, dequantifient immédiatement à la fois l'input et le poids en BF16/FP16, puis lancent untorch.matmulstandard. Les échelles sont exercées mais toute l'arithmétique se fait en haute précision. Implémentation :tensorrt_llm/_torch/auto_deploy/custom_ops/quantization/torch_quant.py, lignes 178–286. -
Étape 2 (
post_load_fusion) :fuse_fp8_linear,fuse_nvfp4_linear, etfuse_finegrained_fp8_lineartransformations remplacent les ops fake-quant par des kernels optimisés basse précision. C'est là qu'un bug de kernel serait introduit.
Pour lancer l'inférence en mode fake-quantization (contournant les kernels basse précision), ajoutez
ce qui suit au fichier YAML config que le test utilise déjà (passé via --config ou
--extra_llm_api_options) :
transforms:
fuse_fp8_linear:
enabled: false
fuse_nvfp4_linear:
enabled: false
fuse_finegrained_fp8_linear:
enabled: false
# Pour les modèles MoE ajoutez aussi :
fuse_fp8_moe:
enabled: false
fuse_finegrained_fp8_moe:
enabled: false
fuse_nvfp4_moe:
enabled: false
S'il n'y a pas de fichier config existant, créez-en un avec seulement le contenu ci-dessus et passez-le via
--extra_llm_api_options /path/to/fake_quant_debug.yaml. La clé transforms mappe directement à
LlmArgs.transforms ; toute transformation non listée hérite sa valeur par défaut de
tensorrt_llm/_torch/auto_deploy/config/default.yaml.
Si la précision se rétablit avec fake quantization, le kernel quantifié (non les échelles) est le bug. Si la précision est toujours fausse, les échelles ou les données de poids sont les culprits probables.
4b — Suppositions codées en dur du wrapper de kernel
Symptôme : sortie cohérent mais systématiquement fausse sur une famille de modèles spécifique (ou une variante au sein d'une famille), tandis qu'un modèle structurellement similaire va bien.
Modèle de cause première : Le kernel C++ ou son wrapper Python a une constante où il devrait lire depuis la config du modèle ou depuis le tensor réel. Deux formes courantes :
- Valeur de config codée en dur : un wrapper de kernel suppose une valeur par défaut au lieu de la lire depuis la config HF chargée à l'exécution.
- Stride/shape du tensor codés en dur : un kernel C++ suppose une disposition mémoire spécifique qui diffère de ce qu'AutoDeploy passe actuellement. Le kernel lit alors silencieusement les mauvaises locations mémoire.
Comment investiguer :
- Identifiez quel kernel est dispatché pour l'op en échec (cherchez le point d'entrée Python du op dans
tensorrt_llm/_torch/auto_deploy/). - Comparez chaque constante ou défaut dans l'appel du kernel wrapper contre le champ correspondant
dans la config HF du modèle (
config.json). Signalez toute valeur qui n'est pas lue depuis la config. - Pour les bugs de stride : affichez les strides actuels des tensors passés au kernel et comparez contre ce que le kernel attend (vérifiez la source du kernel C++ pour les suppositions de stride ou les paramètres).
- Si un mismatch est trouvé, le correctif est de passer la config ou la propriété du tensor en paramètre explicite plutôt que d'utiliser une constante codée en dur.
4c — Précision liée au sharding (world_size > 1)
Première étape : reproduisez le problème à world_size=1. Si la précision se rétablit, le bug se trouve dans le chemin
sharding. S'il échoue à world_size=1 aussi, le sharding n'en est pas la cause — retournez à la Phase 3.
Pour lancer à world_size=1, définissez world_size: 1 dans la config YAML du modèle ou passez
--extra_llm_api_options avec world_size: 1.
Modèles de bugs de sharding connus (vérifiez dans l'ordre) :
| Suspect | Symptôme | Comment isoler |
|---|---|---|
| Mauvaise stratégie d'allreduce | Sorties non-déterministes ou dépendantes du rank ; peut apparaître seulement à TP≥4 | Définissez allreduce_strategy: NCCL dans la config de transformation sharding ; la valeur par défaut AUTO a causé des problèmes de correctness dans le passé |
Double all_reduce dans MoE |
Sortie MoE doublée en magnitude ; précision catastrophique | Inspectez le graphe exporté ; il devrait y avoir exactement un all_reduce après la somme des sorties d'experts routés et partagés, non un par branche |
| Head reshape avec mauvais stride après TP | Garbage de sortie d'attention à TP>1, correct à TP=1 | Les reshapes qui utilisent des head counts concrets depuis torch.export deviennent faux après que TP divise la dimension head ; ceux-ci doivent utiliser torch.ops.auto_deploy.view avec tp_scaled_dim |
| Sharding d'une projection qui ne doit pas être shardée | Projections de gating dim-1 ou projections latentes shardées → résultats faux | Vérifiez tp_mode sur les petites projections de sortie (ex. routeur MoE, latent q_a/kv_a MLA) ; ils doivent être "none" |
| Suppression de paramètres imbriqués cassant le chargement de poids | Certains poids manquants après sharding, silencieusement par défaut à zéro ou aléatoire | Si le sharding supprime les params du module parent et les params enfants sont recherchés par l'ancien chemin, le hook de chargement peut silencieusement les sauter |
Validation d'une correction de sharding :
Si la taille du modèle le permet, lancez-le à world_size=1 (baseline), puis world_size=2, puis le world_size
cible. Si la précision est correcte à TP=1 et TP=2 mais fausse à TP=8, le bug est probablement une supposition de divisibilité
de head count (head dim doit être divisible par le degré TP). S'il est faux à tout TP>1, c'est un bug de sharding
structurel (missing allreduce, mauvais point de split, mauvais stride).
Phase 5 — Décomposition par sujet / catégorie
Quand le score global est plus bas que prévu mais pas catastrophiquement faux, regardez les décompositions par sujet ou catégorie dans les logs d'éval. Modèles à chercher :
| Modèle | Implication |
|---|---|
| Tous les sujets uniformément ~N % sous la référence | Perte de précision uniforme — suspect kernel FP8 ou échelle d'attention |
| Sujets spécifiques proches de 25 % (aléatoire pour QCM 4-choix) | Ces sujets ont une erreur systématique — suspect longueur du sujet ou prefill par chunks |
| Sujets faciles corrects, sujets difficiles faux | Sensibilité proche de la limite — suspect erreur numérique subtile |
| Erreurs corrélées au sujet | Corrélation de longueur de prompt — vérifiez le comportement de troncature |
Pour les tâches QCM comme MMLU, l'aléatoire est 25 %. Les sujets obtenant 25-35 % peuvent être réellement difficiles pour le modèle même dans le backend PT — vérifiez contre les scores PT par-sujet avant de conclure un bug spécifique à AD.
Phase 6 — Ablation itérative
Une fois qu'une hypothèse est formée, vérifiez-la en basculant un changement à la fois et en relançant le diagnostic (50-100 échantillons suffisent pour des écarts de 5 %+).
Chaque ablation devrait être un diagnostic run séparé. Ne batchinez pas plusieurs hypothèses en un run — cela rend les résultats ambigus.
Anti-patterns
- N'utilisez pas des prompts 0-shot pour déboguer un évaluateur 5-shot. Les réponses méta ("OK", "La réponse est...") d'un run 0-shot sont un artefact de format de prompt, non un bug d'inférence AD.
- N'invoquez pas
torchrunpour les tests AD. L'API LLM d'AD lance les workers MPI en interne ;torchrunajoute une seconde couche d'init distribuée qui provoque des deadlocks. - Ne surchargez pas
LLM_MODELS_ROOT. S'il est déjà défini dans l'environnement (CI le définit à/path/to/llm-models), le désactiver ou le surcharger casse les lookups de dataset. Vérifiezecho $LLM_MODELS_ROOTavant d'assumer qu'il doit être défini. - Ne baissez pas le seuil de référence comme un "correctif". La valeur de référence doit être validée contre le backend PT avant d'être acceptée. Si PT échoue aussi, réexaminez le framework, non le seuil.
- N'appliquez pas le même hook de chargement deux fois. Si un hook convertit interleaved → NeoX, l'appliquer à nouveau corrompt les poids (ce n'est pas idempotent). Vérifiez le git log pour les reverts/restores avant d'ajouter un hook qui pourrait déjà exister ailleurs dans la chaîne d'appels.
Garder cette skill à jour
Chaque fois que cette skill est utilisée et que la session de débogage découvre une nouvelle cause première ou un modèle d'erreur non encore décrit ici, mettez à jour la skill avant de fermer la session.
Où ajouter les nouvelles découvertes :
-
Phase 2 — Classer le modèle d'erreur : ajoutez une nouvelle ligne à la table si la session a révélé un mapping symptôme → cause première non déjà listée. Gardez la colonne "Modèle de sortie" observable (quelque chose de visible dans la sortie du diagnostic), et la colonne "Cause première probable" actionnelle (pointe vers une étape suivante concrète ou une sous-section de Phase 4).
-
Phase 4 — Investigation de la cause première : ajoutez les étapes d'investigation sous la sous-section existante la plus appropriée (4a quantification, 4b suppositions du wrapper de kernel, 4c sharding). Si la découverte ne correspond à aucune sous-section existante, créez-en une numérotée séquentiellement (4d, 4e, …). Chaque sous-section devrait suivre la même structure : symptôme, modèle de cause première, et une procédure d'investigation numérotée.
Ce qui vaut la peine de capturer :
- Une cause première non déjà représentée en Phase 4.
- Un modèle de symptôme permettant une classification antérieure (Phase 2).
- Une condition de configuration ou d'environnement qui reproduit ou masque le bug (Phase 3).
- Un nouvel anti-pattern découvert pendant la session.
Ce qui ne vaut pas la peine de capturer :
- Des quirks spécifiques au modèle sans potentiel de généralisation.
- Des découvertes qui dupliquent ce qui est déjà écrit.
- Des workarounds qui cachent un bug plutôt que de l'identifier.