nightly-sync

Par nvidia · skills

Connaissances métier pour le workflow de synchronisation nocturne main-to-dev. Couvre la stratégie de merge, l'architecture CI, l'investigation des échecs et les problèmes connus.

npx skills add https://github.com/nvidia/skills --skill nightly-sync

Synchronisation nocturne : Main vers Dev

Cette compétence est lue par le bot de synchronisation automatique lors du workflow nightly-sync-main-to-dev. Elle contient toute la connaissance métier pour fusionner main dans dev, résoudre les conflits, itérer sur la CI et expédier la PR.


Phase 1 : Créer la branche de synchronisation et fusionner

Configuration de la branche

  1. Créer la branche $BRANCH à partir de origin/dev
  2. Fusionner : git merge origin/main -X theirs --no-edit
  3. Si des conflits persistent (par ex. add/add), les résoudre en favorisant main

Préserver les additions spécifiques à Dev

Ne PAS remplacer en bloc tous les fichiers partagés par la version de main. Dev a des fonctionnalités pas encore dans main (nouvelles classes, nouveaux modules, nouveaux tests). La fusion préserve les additions non conflictuelles des deux côtés — n'intervenir que là où il y a un véritable conflit.

Détection de la chaîne de squash-merge

Dev développe souvent des fonctionnalités comme une chaîne de PRs (PR1 → PR2 → PR3) où chacune s'appuie sur la précédente. Quand PR1 est squash-mergée dans main, git voit la version squashée de main et les commits originaux de dev comme des changements sans lien. -X theirs prendra le code PR1 de main et supprimera silencieusement les améliorations de PR2/PR3 dans dev.

Après la fusion, vérifier ce motif :

  1. Pour chaque fichier où -X theirs a résolu un conflit, exécuter git log --oneline origin/dev -- <file> pour voir si dev a des commits venus APRÈS le code que main apporte.
  2. Si dev a des commits de suivi (corrections de bugs, refactors, extensions), favoriser la version de dev pour ces sections.
  3. Si le conflit est juste main apportant une copie propre de ce que dev a déjà (pas de suivi), la version de main va bien.

Vérification pratique : exécuter git diff origin/dev -- <file> sur les fichiers en conflit. Si le code de dev a été retiré ou révertir, investiguer si la version de dev est la plus évoluée.

Exemples réels de la PR #4291 :

  • emerging_optimizers.py : La version de main était PLUS complète — elle a squash-mergé les PRs de dev plus en a ajouté davantage. -X theirs était correct.
  • distrib_optimizer.py : Main a écrasé le support de GroupedQuantizedTensor de dev. Dû restaurer _is_distopt_quantized_param et la boucle étendue _expand_quantized_param_shard_for_cast tout en gardant les ajouts NVFP4 de main. Cela a nécessité une fusion chirurgicale combinant les sections des deux.

Insight clé : les chaînes de squash-merge peuvent aller DANS CHAQUE DIRECTION. Parfois main est en avant (elle a squash-mergé le travail de dev + plus), parfois dev est en avant (elle a des PRs de suivi). Toujours faire un diff dans les deux sens avant de décider quelle version favoriser.

Fichiers à remplacer par ceux de Main

Ces fichiers ont des conflits sémantiques connus où les versions de dev référencent des args ou APIs que main a supprimés ou renommés. Prendre la version de main avec git checkout origin/main -- <file> :

  • megatron/training/training.py — référence des args spécifiques à dev
  • megatron/training/initialize.py — référence des args spécifiques à dev
  • megatron/training/utils.py — référence des args spécifiques à dev
  • megatron/training/datasets/data_samplers.py — référence des args spécifiques à dev
  • megatron/core/optimizer/layer_wise_optimizer.py — signature du constructeur

Caveat pour TOUS les remplacements : Après avoir pris la version de main de n'importe quel fichier, vous DEVEZ exécuter la procédure de détection d'API Mismatch (voir ci-dessous) sur ce fichier. Prendre le code appelant de main tout en gardant les implémentations appelées de dev est la source numéro 1 des bugs de synchronisation.

IMPORTANT : Ne PAS prendre pyproject.toml, uv.lock, ou docker/Dockerfile.ci.dev de main. Ces trois fichiers sont un triple étroitement couplé — la commande uv sync du Dockerfile doit correspondre aux groupes de dépendances dans pyproject.toml, et uv.lock doit être cohérent avec les deux. Les versions de main manquent des dépendances spécifiques à dev (par ex. fast-hadamard-transform, la bonne révision TransformerEngine) et le flag --group no_pypi_wheels nécessaire pour les installer. Garder les versions de dev de tous les trois fichiers.

IMPORTANT : .github/CODEOWNERS ne DOIT JAMAIS être modifié par le bot de synchronisation en aucune circonstance. Le CODEOWNERS de dev est intentionnellement différent de celui de main — ne pas prendre la version de main, ne pas les fusionner, ne pas toucher au fichier. Si la fusion produit un conflit ou un diff non-zéro contre origin/dev sur ce chemin, restaurer la version de dev littéralement :

git checkout origin/dev -- .github/CODEOWNERS

Puis vérifier avec git diff origin/dev -- .github/CODEOWNERS — la sortie doit être vide. Modifier CODEOWNERS déclenche des demandes d'examen parasites et crée des conflits avec la gouvernance de l'équipe dev ; annuler un changement CODEOWNERS après que la PR soit landed est douloureux.

NE JAMAIS éditer manuellement uv.lock. C'est un fichier de lock généré automatiquement. S'il doit changer, il doit être régénéré avec uv lock dans un conteneur CUDA (voir .claude/skills/build-and-test/SKILL.md).

Réconciliation des sources Git (pyproject.toml)

Après avoir gardé le pyproject.toml de dev, vérifier si main a ajouté des sources git NOUVELLES à [tool.uv.sources] qui n'existent pas dans la version de dev. Le code fusionné de main peut importer à partir de packages disponibles seulement à des révisions git spécifiques.

  1. Faire un diff des sections [tool.uv.sources] : git show origin/main:pyproject.toml vs git show origin/dev:pyproject.toml
  2. Pour chaque source git dans main mais pas dev, l'ajouter au pyproject.toml de dev
  3. Pour les sources dans les deux mais à des révisions différentes, vérifier si la révision de dev fonctionne. Si la révision de dev est cassée (erreurs de parsing TOML, classes manquantes que le code de main importe), prendre la révision de main à la place.

Exemples réels de la PR #4291 :

  • nvidia-resiliency-ext : Le torch.py de main importe get_write_results_queue qui existait seulement dans la révision git épinglée de main, pas sur PyPI. Dû ajouter la source git de main au pyproject.toml de dev.
  • nemo-run : La révision épinglée de dev avait une erreur de parsing TOML avec uv 0.7.2. Dû changer pour la révision de main.

Après toute modification à pyproject.toml, régénérer uv.lock dans un conteneur CUDA :

docker run --rm -v $(pwd):/workspace nvcr.io/nvidia/pytorch:26.02-py3 \
  bash -c "pip install uv==0.7.2 && cd /workspace && \
  uv venv .venv --system-site-packages && uv sync --only-group build && uv lock"
# Nettoyer le .venv détenu par root :
docker run --rm -v $(pwd):/workspace nvcr.io/nvidia/pytorch:26.02-py3 \
  bash -c "rm -rf /workspace/.venv"

Détection d'API Mismatch (audit post-fusion)

La fusion peut créer du code « Frankenstein » où les appelants de main utilisent les implémentations de dev (ou vice versa) avec des signatures de méthode différentes. Cela compile bien mais échoue à l'exécution.

Après la fusion, auditer les sites d'appel cross-boundary :

  1. Identifier les fichiers où la version de main a été prise (-X theirs ou git checkout origin/main explicite)
  2. Pour chacun, trouver tous les sites d'appel externes : classes qu'il instancie, méthodes qu'il appelle sur les objets importés, fonctions d'autres modules qu'il invoque
  3. Vérifier que les noms de méthode, nombres de paramètres et signatures correspondent entre l'appelant et l'implémentation dans l'arbre fusionné
  4. Faire particulièrement attention aux modules « interface » (fichiers définissant les classes de base) — si main et dev ont évolué l'interface différemment, chaque appelant et implémenteur doit s'accorder

Exemples réels de la PR #4291 :

  • multi_latent_attention.py (main) appelait off_interface.group_commit() mais l'interface de dev avait seulement group_offload() — méthode renommée
  • mamba_model.py (main) appelait init_chunk_handler(3 params) mais l'interface de dev nécessitait 6 params — signature étendue sur dev
  • mamba_model.py appelait mark_not_offloadable() mais dev avait mark_not_offload() — méthode renommée
  • bulk_offload() faisait .remove() après que bulk_offload_group() ait déjà .pop()d le même item — double suppression d'une liste

Détection pratique :

# Pour chaque fichier pris de main, trouver ce qu'il importe et appelle
grep -rn "from <module> import\|<module>\." megatron/
# Recouper avec les implémentations réelles dans l'arbre fusionné

Leçons spécifiques au fichier de fusion

Ces leçons ont été apprises de la PR #4291. Elles peuvent récurrer si les mêmes fichiers continuent à diverger :

  • gated_delta_net.py : Si la fusion crée du code appelant des méthodes helper non existantes (par ex. _resolve_cu_seqlens), prendre la version de dev en bloc.
  • model_chunk_schedule_plan.py : Attendre les imports manquants (par ex. CudaGraphScope) silencieusement supprimés lors de la résolution de conflit.
  • fine_grained_activation_offload.py : Fichier interface critique utilisé par beaucoup d'appelants. Si main et dev ont des noms/signatures de méthode divergents, préférer l'implémentation de dev et patcher les appelants issus de main pour correspondre.
  • distrib_optimizer.py : Dev peut avoir des abstractions de type plus larges (par ex. _is_distopt_quantized_param couvrant à la fois FP8 et GroupedQuantizedTensor). Main peut se simplifier en vérifications de type explicites. Restaurer les abstractions de dev.

Traitement spécial : data_schedule.py

Main et dev ont des classes complètement différentes dans ce fichier :

  • Main : HybridCPDataLoaderWrapper (importé par le training.py de main)
  • Dev : BasePackingScheduler, DpBalancedScheduler, DefaultDynamicCPScheduler, wrap_data_iterator, get_batch_on_this_rank_for_sequence_packing (importés par pretrain_gpt.py et les tests)

Ne PAS prendre une version en bloc. Garder le fichier de dev et ajouter la classe HybridCPDataLoaderWrapper de main (plus toutes les imports manquantes comme BalancedCPScheduler, Any, List) à la fin.

Restaurer les fichiers supprimés

Comparer git ls-tree entre origin/main et HEAD pour trouver les fichiers dans main qui manquent de l'arbre fusionné. Pour chacun :

  • Restaurer si le code de main l'importe/référence et se briserait sans lui (par ex. hybrid_cp_schedule.py si data_schedule.py importe de lui)
  • Ne PAS restaurer si dev l'a intentionnellement supprimé — vérifier git log origin/dev -- <file> pour le commit de suppression pour comprendre l'intention
  • Dans le doute, vérifier si n'importe quel fichier dans l'arbre fusionné importe du fichier manquant. Si rien ne l'importe, sauter.

Formatage

Exécuter sur TOUS les fichiers Python modifiés (relatif à origin/dev), dans cet ordre :

  1. black (version 24, --config pyproject.toml)
  2. isort
  3. L'ordre compte : black en premier, puis isort — l'ordre inverse peut annuler le travail d'isort
  4. pylint sur les fichiers megatron/core/ modifiés — fixer les violations missing-docstring et line-too-long avant de pousser

Vérifications d'invariant pre-push

Avant chaque git push dans ce workflow (le push initial en Phase 1 ET chaque fix-push en Phase 3), exécuter ces vérifications bash. Si une échoue, corriger la condition et re-vérifier avant de pousser :

# 1. CODEOWNERS doit être identique à celui de dev.
if ! git diff --quiet origin/dev -- .github/CODEOWNERS; then
  echo "ABORT: .github/CODEOWNERS differs from origin/dev. Restore with:"
  echo "  git checkout origin/dev -- .github/CODEOWNERS"
  exit 1
fi

# 2. Le triple gestion-dépendances doit être identique à celui de dev.
for f in pyproject.toml uv.lock docker/Dockerfile.ci.dev; do
  if ! git diff --quiet origin/dev -- "$f"; then
    # pyproject.toml est autorisé à différer SEULEMENT pour la réconciliation
    # des sources git (entrées nouvelles [tool.uv.sources] de main). Si vous
    # l'avez volontairement édité pour cette raison, contourner cette
    # vérification en re-exécutant avec $f sauté.
    echo "WARNING: $f differs from origin/dev"
  fi
done

La vérification CODEOWNERS est un HARD abort — ne jamais pousser si elle échoue.

Commit et push

Après que les vérifications d'invariant pre-push passent, committer tout et pousser la branche.


Phase 2 : Créer le draft PR

  • Titre : chore: nightly sync main into dev ($DATE)

  • Créer comme draft : gh pr create --draft

  • Le corps devrait inclure :

    1. Résumé de ce qui a été synchronisé (nombre de commits depuis main)

    2. Statistiques de changement de ligne Python uniquement, pour que les reviewers puissent évaluer la surface de code réelle (excluant JSON de valeur golden, uv.lock, etc.). Calculer avec :

      git diff --numstat origin/dev...HEAD -- '*.py' \
        | awk 'BEGIN{a=0;d=0} {a+=$1; d+=$2} END{
            printf "Python lines: +%d / -%d across %d files\n", a, d, NR
          }'

      Inclure la ligne exacte (par ex. Python lines: +1234 / -567 across 42 files) dans le corps de la PR pour que les reviewers la voient d'un coup d'œil.

    3. Liste des fichiers où la version de main a été prise sur la fusion

    4. Liste des fichiers supprimés dans dev mais restaurés (et pourquoi)

    5. La sortie du remerge-diff (git show --remerge-diff HEAD sur le commit de fusion) pour que les reviewers inspectent SEULEMENT les résolutions de conflits. Si la sortie est très longue, résumer les conflits par fichier et mettre le diff complet dans un bloc <details> collapsible. Si git est trop vieux pour --remerge-diff, noter la version git et décrire la stratégie de fusion utilisée à la place.

  • Sauvegarder le numéro de PR pour les phases ultérieures

  • Ajouter les labels Run functional tests et Run MBridge tests à la PR immédiatement après création. Le label Run functional tests s'assure que /ok to test déclenche la suite CI complète (tests unitaires

    • tests fonctionnels/intégration avec entraînement 100-step et comparaison de valeur golden). Le label Run MBridge tests déclenche la suite de test MBridge. Sans ces labels, seulement un sous-ensemble léger s'exécute.
      gh pr edit <PR_NUMBER> --repo $REPO \
      --add-label "Run functional tests" \
      --add-label "Run MBridge tests"

Phase 3 : Itération CI

Architecture CI

  • Nemo_CICD_Test est un job de gate aval agrégeant les résultats des tests unitaires, tests d'intégration et autres. S'il échoue, investiguer les jobs amont dont il dépend — ne PAS déboguer la gate elle-même.
  • Les tests d'intégration (H100, GB200) peuvent être sautés pour les PRs de non-mainteneurs. C'est attendu ; le gate Nemo_CICD_Test échouera en résultat.
  • tests/unit_tests/conftest.py importe de megatron.training.training, donc un import cassé dans training.py (ou n'importe quoi qu'il importe transitivement) se répercute pour faire échouer TOUS les suites de tests. Si chaque job de test échoue avec ImportError, vérifier d'abord la chaîne d'import de training.py.

Modèle d'exécution : une étape, pas de background

Vous exécutez dans UNE étape GitHub Actions. Le moment où vous arrêtez d'émettre des appels à outils, l'étape se termine et le conteneur runner est détruit. Tout processus background que vous avez démarré meurt avec. Il n'y a PAS de session persistante et PAS de wakeup futur. Voir le bloc « NO background tasks » du prompt du workflow pour la liste d'interdiction complète.

Règle pratique : chaque attente pour que CI se résolve est un SEUL appel Bash au premier plan qui se bloque inline jusqu'à ce que l'attente soit résolue.

La boucle Fix-Then-Retrigger

Deux boucles imbriquées. Ne PAS les confondre :

  • La boucle externe est VOTRE séquence d'appels à outils (chaque itération : un /ok to test, un polling bloquant, peut-être un fix-and-push). Ce n'est PAS une boucle Bash. Elle avance parce que vous faites des appels à outils nouveaux.
  • La boucle interne est un seul appel Bash bloquant utilisant while true; do ... sleep 120; done. Elle s'exécute pendant une itération de la boucle externe et se termine quand CI atteint un état terminal pour cette itération.

La boucle externe se termine SEULEMENT quand le gate de Phase 4 est satisfait.

Source de vérité : gh pr view <PR_NUMBER> --repo $REPO --json statusCheckRollup. Ceci liste chaque vérification requise, y compris les contextes de statut externes (CI GitLab, copy-pr-bot, etc.) que gh api .../actions/runs/.../jobs ne montre PAS.

Itération boucle-externe (chaque itération est quelques appels à outils) :

  1. latest_sha=$(git rev-parse HEAD) (un appel Bash).

  2. Poster /ok to test $latest_sha sur la PR : gh pr comment <PR_NUMBER> --repo $REPO --body "/ok to test $latest_sha"

  3. UN appel Bash bloquant. C'est la boucle interne. Copier ce template littéralement, en changeant seulement REPO et PR :

    REPO='NVIDIA/Megatron-LM'
    PR='<PR_NUMBER>'
    # Noms appariés case-insensitively, ancrés au START du nom.
    EXEMPT='copy-pr-bot|is-not-external-contributor|greptile|coderabbit|codeowners|.*review|.*approval|codecov|coverage|build-docs|doc-build|readthedocs|sphinx'
    # Vérification sentinel qui nous dit que CI a complètement exécuté.
    # Mettre à jour si le job de gate agrégé est renommé.
    SENTINEL='Nemo_CICD_Test'
    
    while true; do
      # Normaliser CheckRun (.status / .conclusion) et StatusContext
      # (.state) entrées dans la même forme {name, status, conclusion}.
      rollup=$(gh pr view "$PR" --repo "$REPO" --json statusCheckRollup --jq '
        .statusCheckRollup[] | [
          (.name // .context // "?"),
          (if .__typename == "StatusContext" then
             (if (.state == "PENDING" or .state == "EXPECTED") then "IN_PROGRESS"
              else "COMPLETED" end)
           else (.status // "UNKNOWN") end),
          (if .__typename == "StatusContext" then
             (if .state == "SUCCESS" then "SUCCESS"
              elif (.state == "FAILURE" or .state == "ERROR") then "FAILURE"
              else "NEUTRAL" end)
           else (.conclusion // "UNKNOWN") end)
        ] | @tsv')
    
      # Sentinel : ne PAS déclarer vert jusqu'à ce que le gate agrégat CI ait
      # atteint un état terminal. Avant /ok to test déclenche la run, le
      # sentinel est absent ; tandis que CI s'exécute, il est IN_PROGRESS.
      sentinel_line=$(printf '%s\n' "$rollup" | awk -F'\t' -v s="$SENTINEL" '$1 == s')
      sentinel_status=$(printf '%s\n' "$sentinel_line" | awk -F'\t' 'NR==1 {print $2}')
      if [ "$sentinel_status" != "COMPLETED" ]; then
        echo "=== $(date -u) waiting for $SENTINEL (status: ${sentinel_status:-absent}) ==="
        sleep 120
        continue
      fi
    
      # Classifier les vérifications non-exempts (liste exemption appliquée au NAME seulement).
      non_exempt=$(printf '%s\n' "$rollup" | awk -F'\t' -v p="^($EXEMPT)" 'tolower($1) !~ tolower(p)')
      failed=$(printf '%s\n' "$non_exempt" | awk -F'\t' '$2 == "COMPLETED" && $3 !~ /^(SUCCESS|SKIPPED|NEUTRAL)$/')
      pending=$(printf '%s\n' "$non_exempt" | awk -F'\t' '$2 != "COMPLETED"')
    
      if [ -n "$failed" ]; then
        echo "=== NON-EXEMPT FAILURES ==="
        printf '%s\n' "$failed"
        echo "RESULT=FAILURE"
        exit 0
      fi
      if [ -n "$pending" ]; then
        # Sentinel est COMPLETED mais une vérification non-exempt est encore
        # pending — rare mais possible. Continuer à attendre ; ne pas
        # expédier.
        echo "=== $(date -u) sentinel done but non-exempt checks still pending ==="
        printf '%s\n' "$pending"
        sleep 120
        continue
      fi
    
      echo "=== ALL NON-EXEMPT CHECKS COMPLETED GREEN ==="
      printf '%s\n' "$non_exempt"
      echo "RESULT=GREEN"
      exit 0
    done

    Cet appel Bash se bloque aussi longtemps que CI prend (minutes à heures). Ne PAS le scinder en beaucoup de courts polls entrelacés avec d'autres appels à outils — ça gaspille --max-turns et crée des fenêtres où vous pourriez perdre la trace de l'état de la boucle.

  4. Lire la sortie de l'outil :

    • Si RESULT=FAILURE : utiliser gh api repos/$REPO/actions/jobs/<JOB_ID>/logs (ou l'équivalent pour les contextes externes) pour diagnostiquer, fixer le code, committer, pousser. Puis démarrer une NOUVELLE itération boucle-externe à l'étape 1 avec le nouveau HEAD SHA.
    • Si RESULT=GREEN : la boucle externe est terminée. Procéder à Phase 4.

Pourquoi pas attendre la run à s'enregistrer d'abord ? gh pr comment avec /ok to test <sha> est géré par copy-pr-bot, qui prend quelques secondes pour déclencher la run CI. Le poll statusCheckRollup à l'étape 3 montrera initialement les vérifications en PENDING / QUEUED ; c'est bon — la boucle interne traite ça comme « continuer l'attente » et les verra avancer comme CI progresse. Pas de poll d'enregistrement séparé nécessaire.

Anti-motifs (ce qui s'est mal passé à la run 24800621116)

  • Ne PAS classifier un job en queue/in-progress comme « infrastructure- blocked » et expédier. Une queue bloquée se vide finalement — attendre. Si le job finit par passer, parfait ; s'il échoue, aller le fixer.
  • Ne PAS marquer prêt tandis qu'une vérification requise est PENDING / QUEUED / IN_PROGRESS sur le HEAD SHA. Un push n'est pas un pass ; seulement un COMPLETED + statut vert l'est.
  • Ne PAS déclarer un job untested « pre-existing ». Pre-existing signifie que le test a exécuté jusqu'à complétion et a échoué de la même manière sur la CI dev récente. Un job qui n'a jamais exécuté sur votre PR ne peut pas être pre-existing.
  • Ne PAS utiliser gh api .../actions/runs/.../jobs seul comme signal de gate. Les contextes de statut externes (pipelines GitLab CI, statut copy-pr-bot, etc.) N'APPARAISSENT PAS là. Utiliser statusCheckRollup.
  • Ne PAS démarrer aucun processus background. Pas de &, pas de nohup, pas de run_in_background: true, pas de ScheduleWakeup. L'étape GitHub Actions possède votre shell ; quand l'étape finit, chaque processus background est tué et ne peut pas reprendre.
  • Ne PAS pousser directement vers les branches pull-request/<PR_NUMBER>. Le bot communautaire gère ces branches quand il traite /ok to test. Pousser vers elles directement casse le mécanisme de déclenchement CI. Toujours pousser vers votre propre branche de sync (par ex. main2dev/<DATE>) à la place.
  • Ne PAS oublier les labels Run functional tests et Run MBridge tests. Sans Run functional tests, les tests fonctionnels GitLab internes n'exécutent pas ; sans Run MBridge tests, la suite de test MBridge n'exécute pas.

Investigation des défaillances

  1. Récupérer les logs : gh api repos/$REPO/actions/jobs/<JOB_ID>/logs
  2. Grepper pour : ImportError, ModuleNotFoundError, FAILED, would reformat, line-too-long, Traceback
  3. Lire l'erreur, comprendre la cause racine, fixer le code

Problèmes courants

  • ImportError pour une classe/module : Le test de dev importe une classe d'un fichier où nous avons pris la version de main. Restaurer seulement la classe/fonction manquante — pas le fichier entier. Si les classes d'un fichier sont complètement différentes entre main et dev, garder les deux ensembles de code.
  • Défaillances de formatage (black/pylint) : Exécuter black --config pyproject.toml sur les fichiers offensants. Pour pylint long-line ou missing-docstring, éditer directement.
  • Imports circulaires : isort peut réordonner les imports d'une manière qui introduit des dépendances circulaires (par ex. megatron/legacy/model/__init__.py). Vérifier git diff sur les fichiers __init__.py pour voir si l'ordre d'import a changé.
  • Incompatibilités de version de dépendance : Prendre le pyproject.toml/ uv.lock de main peut changer les versions de bibliothèque dans le conteneur CI. Le code spécifique à dev peut dépendre de versions plus récentes (par ex. single_grouped_weight de TransformerEngine). Si les défaillances tracent des kwargs manquants ou des APIs changées dans les libs tiers, c'est la cause.
  • API mismatch (AttributeError / TypeError à l'exécution) : Les appelants de main référencent des méthodes qui n'existent pas (ou ont des signatures différentes) dans les implémentations de dev. Voir « Détection d'API Mismatch » en Phase 1. Fixer en ajoutant des shims, renommant les méthodes, ou ajustant les signatures d'appel.
  • Défaillances infrastructure / réseau (apt-get, pip download) : Les erreurs comme archive.ubuntu.com unreachable ou Connection timed out lors de l'installation de packages sont des problèmes transients d'infrastructure CI, pas de code. Réessayer CI avec le même SHA. Ne pas investiguer comme des défaillances de code.

Vérification de défaillance pre-existing

Vous DEVEZ empiriquement vérifier avant de classifier une défaillance comme pre-existing.

  1. gh pr list --repo $REPO --base dev --state merged --limit 3
  2. gh pr checks <PR_NUMBER> --repo $REPO sur une PR dev récemment mergée
  3. Si le même bucket de test passe sur la CI dev récente → la défaillance est causée par la sync. Vous devez la fixer.
  4. Seulement si le test échoue aussi sur la CI dev récente pouvez-vous la classifier comme pre-existing. Documenter avec le numéro de PR de dev et la CI run en tant que preuve.

Tests fonctionnels internes GitLab

La CI GitHub couvre les tests unitaires et certains tests d'intégration. Le GitLab interne (gitlab-master.nvidia.com) exécute des tests fonctionnels supplémentaires sur le matériel H100/GB200 qui peuvent révéler des problèmes que la CI GitHub ne détecte pas. Ceux-ci apparaissent dans statusCheckRollup comme des contextes de statut externes (le template bash les gère déjà via la branche __typename == "StatusContext").

  • Par exemple, les défaillances d'offload d'activation fine-grained ont seulement montré dans les tests fonctionnels GitLab lors de la PR #4291
  • Si la CI GitHub passe mais qu'un reviewer signale des défaillances GitLab, investiguer avec la même rigueur que les défaillances CI GitHub
  • La PR de sync devrait idéalement passer à la fois la CI GitHub et GitLab avant merge, mais la CI GitHub qui passe (i.e. le gate Phase 4 ci-dessous) est le minimum avant gh pr ready

Phase 4 : Marquer PR Ready — Gate strict

Exécuter gh pr ready SEULEMENT quand chaque vérification requise non-exempt sur la dernière run CI (contre le HEAD SHA actuel) satisfait BOTH :

  1. status == "completed" — NI queued, in_progress, pending, waiting, ou requested.
  2. conclusion ∈ {"success", "skipped", "neutral"}.

Si une vérification non-exempt est pending/queued/in-progress : continuer le polling ; ne pas exécuter gh pr ready. Si elle échoue : revenir à la boucle Phase 3.

La liste exemption (approval/coverage/docs) est définie en Phase 3 ; seulement ces vérifications peuvent être ignorées.

Une défaillance pre-existing (même test échouant identiquement sur la CI dev récente) peut être acceptée, mais SEULEMENT après qu'elle ait complètement exécuté, été empiriquement vérifiée contre dev, et documentée dans le corps de la PR avec preuve (numéro de PR de dev + URL de CI run).

gh pr ready <PR_NUMBER> --repo $REPO

Puis commenter sur la PR confirmant qu'elle est prête pour l'examen humain. Le commentaire devrait inclure :

  • Quelles vérifications non-exempt ont passé (résumé de la sortie de template bash finale ALL NON-EXEMPT CHECKS COMPLETED GREEN)
  • Toute défaillance pre-existing documentée avec preuve (numéro de PR de dev
    • URL de CI run montrant la même défaillance sur la CI dev récente)
  • Quels fichiers ont été pris de main vs. mergés manuellement
  • Tout API mismatch détecté et fixé
  • Toute réconciliation de source git pyproject.toml effectuée
  • Liens vers les CI runs qui ont validé les fixes

Règles

  • Prioriser main sur dev sur les conflits authentiques. Préserver les additions spécifiques à dev qui ne conflictent pas.
  • Les triggers CI via commentaire : /ok to test <sha>
  • Les runs CI apparaissent sur la branche pull-request/<PR_NUMBER>
  • Identité du committer git : svcnvidia-nemo-ci
  • Après édition d'imports, exécuter isort sur ces fichiers
  • Pousser directement vers NVIDIA/Megatron-LM (pas un fork). Le bot utilise un PAT avec accès en écriture. CLAUDE.md dit « ne jamais pousser directement » mais cette règle est pour les contributeurs humains — le bot de sync est une exception.

Skills similaires