ad-accuracy-debug

Par nvidia · skills

Déboguez les régressions de précision d'AutoDeploy par rapport à un score de référence (backend PyTorch ou baseline publiée). À utiliser lorsque le score d'évaluation d'un modèle AutoDeploy est significativement inférieur à la référence et que la cause racine est inconnue.

npx skills add https://github.com/nvidia/skills --skill ad-accuracy-debug

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 :

  1. Quel est le score de référence ? Provient-il d'un test backend PyTorch, d'un leaderboard publié, ou défini manuellement ?
  2. 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.
  3. 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é (sans fewshot_as_multiturn) effondre les exemples en un single turn mal formé et peut produire 0 % de précision.
    • Omettre apply_chat_template pour un modèle chat-first peut être tout aussi faux. Pour les modèles chat sur des benchmarks few-shot, considérez si apply_chat_template=True associé avec fewshot_as_multiturn=True est 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 dans test_llm_api_autodeploy.py.)
  • max_output_len pour 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), le MAX_OUTPUT_LEN par 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 de max_tokens pour les tâches de classification comme MMLU où vous voulez empêcher les longues générations.
  • max_tokens pour 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_ROOT est 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 :

  1. Charge le modèle AD directement via from tensorrt_llm._torch.auto_deploy import LLM as AutoDeployLLM
  2. 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
  3. Lance ~50-100 échantillons
  4. 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 dev
  • tensorrt_llm/evaluate/gsm8k.py — few-shot avec références CoT
  • tests/integration/defs/accuracy/accuracy_core.pyMAX_INPUT_LEN, MAX_OUTPUT_LEN, NUM_SAMPLES par 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_backend torch-simple : AutoDeploy supporte actuellement deux backends — torch-cudagraph (CUDA graphs, le paramètre de production typique) et torch-simple (pas de CUDA graphs, considérablement plus lent). Si le modèle est configuré avec torch-cudagraph, revenez à torch-simple et 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œuds nn.Linear par torch.ops.auto_deploy.torch_fake_quant_fp8_linear / torch_fake_quant_nvfp4_linear etc. Ces ops quantifient l'input, dequantifient immédiatement à la fois l'input et le poids en BF16/FP16, puis lancent un torch.matmul standard. 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, et fuse_finegrained_fp8_linear transformations 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 :

  1. 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/).
  2. 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.
  3. 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).
  4. 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 torchrun pour les tests AD. L'API LLM d'AD lance les workers MPI en interne ; torchrun ajoute 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érifiez echo $LLM_MODELS_ROOT avant 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.

Skills similaires