spark-training-gotchas

Par wshobson · agents

Vérifiez en amont et diagnostiquez les dix modes de défaillance connus pour l'entraînement ML sur NVIDIA DGX Spark. À utiliser lorsqu'un run d'entraînement sur DGX Spark ne démarre pas, provoque des OOM en dessous de la limite de 128 Go, ralentit en cours d'exécution, ou avant tout job d'entraînement de plusieurs heures sur GB10.

npx skills add https://github.com/wshobson/agents --skill spark-training-gotchas

Pièges de formation Spark

Le chip GB10 de DGX Spark (Grace Blackwell, SM121, 128 GB de mémoire unifiée, aarch64) présente dix modes de défaillance récurrents touchant au lancement, à la mémoire, aux thermiques, à la bande passante et à la précision. Chacun est numéroté G1–G10 pour être vérifiable par numéro — la numérotation est structurante pour les outils qui exécutent ces vérifications. Lisez ceci avant une longue exécution, pas après six heures.

Quand utiliser cette compétence

  • Une exécution de formation échoue au démarrage, avec une erreur d'import ou un segfault qui ne pointe pas vers la vraie cause.
  • Une exécution OOM alors que nvidia-smi affiche encore de la marge disponible.
  • Le débit se dégrade en cours d'exécution qui avait bien commencé.
  • Avant tout travail multi-heures ou multi-epochs sur GB10.
  • Avant de câbler deux Sparks ensemble, avant de choisir une stratégie de parallélisme.
  • Avant de choisir entre FP8 et NVFP4 pour une exécution hébergée sur Spark.

Référence rapide des problèmes courants

# Symptôme Solution
G1 symbole indéfini / segfault wheel cu130 ou conteneur
G2 mauvais backend flash-attn utilisé skip pip build; monkeypatch sur NGC
G3 OOM malgré marge disponible drop page cache
G4 baisse débit / reboot attendre ~100W cap soutenu
G5 étape memory-bound lente budget 180–192 GB/s
G6 cache évincé en cours d'exécution un serveur GPU à la fois
G7 NVFP4 plus lent que FP8 rester FP8 sauf sm_121a
G8 playbook échoue complètement vérifier problèmes upstream
G9 env casse après install utiliser un conteneur
G10 TP 2-Spark se bloque DDP/FSDP uniquement, jamais TP

Les dix pièges

G1 : Incompatibilité ABI CUDA 12/13

  • SYMPTÔME : ImportError: undefined symbol nommant une fonction CUDA, ou segfault au premier appel .cuda().
  • CAUSE : la plupart des wheels PyPI lient libcudart.so.12 ; Spark embarque CUDA 13. pip ne vérifier jamais l'ABI CUDA, donc cela n'apparaît qu'à l'import ou au premier lancement de kernel.
  • VÉRIFICATION : references/gotcha-checks.md G1 — le tag de build CUDA du wheel.
  • SOLUTION : réinstallez depuis download.pytorch.org/whl/cu130 ou utilisez un conteneur compatible.

G2 : flash-attn — Skip pip Build, Surveiller l'auto-détection Unsloth

  • SYMPTÔME : pip install flash-attn échoue/se bloque toujours. Unsloth peut aussi entraîner silencieusement flash-attn sur un SDPA explicitement demandé.
  • CAUSE : pas de wheel aarch64/sm_121 pour pip brut — mais les conteneurs NGC livrent un flash-attn SM121 fonctionnant, et Unsloth l'auto-préfère, supprimant attn_implementation="sdpa".
  • VÉRIFICATION : references/gotcha-checks.md G2 — flash-attn est-il déjà présent et fonctionnant.
  • SOLUTION : pip brut — skip flash-attn, utilisez SDPA (inchangé). Sur NGC — le seul override fiable est le monkeypatch dans references/gotcha-checks.md G2.

G3 : OOM UMA sous 128 GB

  • SYMPTÔME : OOM lors du chargement/entraînement du modèle tandis que nvidia-smi rapporte toujours de la mémoire libre sous le cap 128 GB — ou, sur certaines configurations, [N/A] purement et simplement au lieu d'un nombre.
  • CAUSE : mmap et l'allocateur CUDA double-comptent les pages lors du chargement safetensors ; QLoRA peut OOM plus tôt que bf16 puisque la dédéquantification ajoute des allocations transitoires.
  • VÉRIFICATION : references/gotcha-checks.md G3 — lisez free -g et /proc/meminfo, pas nvidia-smi.
  • SOLUTION : videz le cache de pages avec sync; echo 3 > /proc/sys/vm/drop_caches — nécessite root, une réinitialisation entre exécutions, pas une étape en cours d'entraînement.

G4 : Limitation thermique

  • SYMPTÔME : le débit baisse en cours d'exécution multi-heures, ou le serveur redémarre spontanément sous charge soutenue.
  • CAUSE : la consommation électrique soutenue plafonne autour de 100W face à la figure nominale de 240W ; les longues exécutions heurtent ce plafond et limiteront ou, parfois, redémarreront.
  • VÉRIFICATION : references/gotcha-checks.md G4 — échantillonnez nvidia-smi --query-gpu=temperature.gpu,power.draw.
  • SOLUTION : si la puissance plafonne sous 240W tandis que la température grimpe, traitez la limitation comme cause ; améliorez le refroidissement ou limitez la durée d'exécution.

G5 : Plafond de bande passante

  • SYMPTÔME : les charges memory-bound, les boucles RL heavy-decode notamment, plafonnent bien en dessous du débit attendu.
  • CAUSE : 273 GB/s est un plafond spec, pas soutenu ; la bande passante mesurée tourne 180–192 GB/s.
  • VÉRIFICATION : references/gotcha-checks.md G5 — temps d'étape observé vs plage mesurée, pas spec.
  • SOLUTION : budgétez la bande passante 180–192 GB/s ; révisez un plan construit sur la figure 273 GB/s.

G6 : Contention de ressources UMA globale

  • SYMPTÔME : le KV cache/poids d'un processus se font évincer en cours d'exécution silencieusement, pas d'OOM dans ses propres logs.
  • CAUSE : la mémoire unifiée est un pool global ; un processus sans cap ou près de capacité entre en compétition avec tout autre et peut l'évincer. Une petite charge bornée ne le fait pas — un LoRA <4 GB coexiste bien aux côtés d'une vLLM cappée à gpu-memory-utilization<=0.5.
  • VÉRIFICATION : references/gotcha-checks.md G6 — autres processus résidents GPU et s'ils sont cappés.
  • SOLUTION : la règle un-gros-job s'applique aux charges sans cap ou près de capacité — cappez ou arrêtez d'abord les serveurs sans rapport. Une petite charge cappée ne doit pas s'arrêter.

G7 : NVFP4 plus lent que FP8 sur SM121

  • SYMPTÔME : basculer une charge inference de FP8 à NVFP4 sur Spark la ralentit, ne l'accélère pas.
  • CAUSE : SM121 manque cvt.e2m1x2 sauf si les kernels ciblent sm_121a ; NVFP4 tourne ~32% plus lentement sans cela.
  • VÉRIFICATION : references/gotcha-checks.md G7 — les rapports de capacité montrent (12, 1) ; le build cible-t-il sm_121a ?
  • SOLUTION : restez sur FP8 sauf si le build cible sm_121a.

G8 : Playbooks officiels périmés

  • SYMPTÔME : suivre un playbook DGX Spark officiel échoue toujours, sans configuration locale erronée l'expliquant.
  • CAUSE : les playbooks officiels ont livré cassés avant ; la pile évolue plus vite que les docs.
  • VÉRIFICATION : references/gotcha-checks.md G8 — les problèmes récents du repo playbook.
  • SOLUTION : vérifiez github.com/NVIDIA/dgx-spark-playbooks issues avant de faire confiance à une recette pour une exécution coûteuse.

G9 : Conteneur d'abord, pas pip brut

  • SYMPTÔME : un environnement pip brut qui fonctionnait hier casse après une pip install sans rapport, ou deux environnements "identiques" se comportent différemment.
  • CAUSE : pip brut laisse Triton, xformers et transformers dériver indépendamment ; rien ne les épingle à la cible SM121 de GB10.
  • VÉRIFICATION : references/gotcha-checks.md G9 — conteneur ou pip brut ?
  • SOLUTION : préférez un conteneur NGC (voir spark-environment-setup pour guidance de tag) ou le conteneur Unsloth. Si pip brut est inévitable, suivez l'ordre d'install NVIDIA, incluant --no-deps sur Unsloth.

G10 : Dual-Spark est DDP/FSDP uniquement

  • SYMPTÔME : un lancement tensor-parallel sur deux Sparks se bloque, tourne beaucoup plus lent qu'un single-Spark, ou erreurs.
  • CAUSE : ConnectX-7 est assez rapide pour la sync gradient/paramètre (DDP, FSDP) mais trop fin pour le trafic fin-grained du TP.
  • VÉRIFICATION : references/gotcha-checks.md G10 — la stratégie de parallélisme configurée.
  • SOLUTION : sur setup deux-Spark, choisissez DDP ou FSDP, jamais tensor parallelism — le TP est single-node uniquement ici.

Triage rapide

Les vérifications les moins chères à exécuter avant toute chose :

python3 -c "import torch; print(torch.version.cuda)"  # attendre 13.x (G1); les builds NGC n'ont pas le tag +cu130 — ce n'est pas une défaillance
import torch; print(torch.cuda.get_device_capability())  # attendre (12, 1) (G7)
{ [ -f /.dockerenv -o -f /run/.containerenv ] || grep -qE 'docker|containerd' /proc/1/cgroup; } 2>/dev/null && echo container || echo unknown  # G9

assets/preflight.sh exécute G1, G3, G4, G7, G9 et produit une ligne de sortie par piège dans un format fixe : le G-number d'abord, puis PASS/FAIL/WARN où automatisable, SKIP si indisponible, ou INFO: pour une lecture brute (G3, G4). Commandes complètes : references/gotcha-checks.md. Voir aussi spark-environment-setup pour l'environnement assumé fonctionnant.

Skills similaires