mcore-testing

Par nvidia · skills

Système de test pour Megatron-LM. Couvre l'organisation des tests, la structure YAML des recettes, l'ajout et l'exécution de tests unitaires et fonctionnels, les valeurs de référence (golden values), les filtres de marqueurs et la parité avec la CI.

npx skills add https://github.com/nvidia/skills --skill mcore-testing

Guide de Test


Faits Clés sur les Tests « Answer-First »

Pour les questions sur la désactivation de tests sans les supprimer :

  • Les entrées de recette fonctionnelle restent en YAML ; désactivez en suffixant le scope avec -broken, par exemple scope: [mr-github] -> scope: [mr-github-broken].
  • Les sauts de test unitaire utilisent des marqueurs pytest à la place : @pytest.mark.flaky_in_dev saute dans l'environnement dev par défaut, et @pytest.mark.flaky saute dans LTS.
  • Ne supprimez pas le cas de test ou l'entrée de recette si l'objectif est la découvrabilité et la réactivation facile.

Disposition des Tests

tests/
├── unit_tests/          # pytest, 1 nœud × 8 GPUs, torch.distributed runner
├── functional_tests/    # shell end-to-end + scripts d'entraînement
│   └── test_cases/
│       └── {model}/{test_case}/
│           ├── model_config.yaml          # args d'entraînement
│           └── golden_values_{env}_{platform}.json
└── test_utils/
    ├── recipes/
    │   ├── h100/        # Recettes YAML pour les jobs H100
    │   └── gb200/       # Recettes YAML pour les jobs GB200
    └── python_scripts/  # helpers (recipe_parser, golden-value download, …)

Comment les Tests S'Exécutent

Le runner GitHub Actions invoque launch_nemo_run_workload.py, qui utilise nemo-run pour lancer un conteneur DockerExecutor. Le repo est bind-monté à /opt/megatron-lm ; les données d'entraînement sont montées à /mnt/artifacts.

Les tests unitaires sont dispatché via torch.distributed.run :

  • Les rangs 0 et 3 sont tee-d vers stdout ; tous les autres rangs écrivent uniquement dans les fichiers journaux.
  • Les fichiers journaux par rang atterrissent à {assets_dir}/logs/1/ et sont uploadés en tant qu'artefact GitHub après la exécution.

Les tests fonctionnels sont pilotés par tests/functional_tests/shell_test_utils/run_ci_test.sh. Seul le rang 0 exécute l'étape de validation pytest ; la sortie d'entraînement de tous les rangs est uploadée en tant qu'artefact.

Relance automatique en cas d'échec instable : launch_nemo_run_workload.py réessaye jusqu'à 3 fois pour les motifs transitoires connus (NCCL timeout, erreur ECC, segfault, connectivité HuggingFace, …) avant de déclarer un véritable échec.


Structure YAML de la Recette

Les recettes se trouvent dans tests/test_utils/recipes/ et sont analysées par tests/test_utils/python_scripts/recipe_parser.py. Chaque fichier expande un bloc products cartésien en spécifications de workload individuelles :

type: basic
format_version: 1
maintainers: [mcore]
loggers: [stdout]
spec:
  name: "{test_case}_{environment}_{platforms}"
  model: gpt              # mappe à tests/functional_tests/test_cases/{model}/
  build: mcore-pyt-{environment}
  nodes: 1
  gpus: 8
  n_repeat: 5
  platforms: dgx_h100
  time_limit: 1800
  script_setup: |
    ...
  script: |-
    bash tests/functional_tests/shell_test_utils/run_ci_test.sh ...
products:
  - test_case: [my_test]
    products:
      - environment: [dev, lts]
        scope: [mr-github]
        platforms: [dgx_h100]

Placeholders clés au runtime : {assets_dir}, {artifacts_dir}, {test_case}, {environment}, {platforms}, {n_repeat}.

Désactiver un Test Sans le Supprimer

Pour désactiver temporairement un cas de test dans une recette YAML, suffixez sa valeur scope avec -brokenne supprimez pas l'entrée :

# avant (test s'exécute en CI)
scope: [mr-github]

# après (test est ignoré ; entrée préservée pour réactivation facile)
scope: [mr-github-broken]

Exécuter les Tests Unitaires Localement

Tous les tests unitaires initialisent un groupe torch.distributed, donc chaque invocation nécessite un accès GPU et doit passer par torch.distributed.run :

# Suite complète
uv run python -m torch.distributed.run --nproc-per-node 8 -m pytest -q \
  tests/unit_tests

# Fichier unique
uv run python -m torch.distributed.run --nproc-per-node 8 -m pytest -q \
  tests/unit_tests/models/test_gpt_model.py

# Test unique
uv run python -m torch.distributed.run --nproc-per-node 8 -m pytest -q \
  tests/unit_tests/models/test_gpt_model.py::TestGPTModel::test_constructor

# Filtrer par sous-chaîne de nom
uv run python -m torch.distributed.run --nproc-per-node 8 -m pytest -q \
  tests/unit_tests -k optimizer

Filtres de marqueurs

# Exclure les tests instables pendant le développement
uv run python -m torch.distributed.run --nproc-per-node 8 -m pytest -q \
  tests/unit_tests -m "not flaky and not flaky_in_dev"

# Inclure les tests expérimentaux
uv run python -m torch.distributed.run --nproc-per-node 8 -m pytest -q \
  tests/unit_tests --experimental

Parité CI

Utilisez tests/unit_tests/run_ci_test.sh pour reproduire un échec de bucket CI exactement. Pour des exécutions ad-hoc, préférez les invocations torch.distributed.run directes ci-dessus.

Pièges

  • pyproject.toml définit addopts = --durations=15 -s -rA — stdout n'est pas capturé (-s), donc les rangs s'entrelacent lors d'exécutions multi-rangs. Remplacez par --capture=fd lors du débogage d'un rang spécifique.
  • tests/unit_tests/conftest.py recherche les données de test sous /opt/data et tente un téléchargement si manquant. Fournissez-le manuellement ou ignorez les tests dépendant des données lors de l'exécution en dehors du conteneur canonique.

Ajouter un Test Unitaire

  1. Créez tests/unit_tests/<category>/test_<name>.py.
  2. Utilisez les fixtures de tests/unit_tests/conftest.py.
  3. Appliquez les marqueurs selon les besoins :
    • @pytest.mark.internal — ignoré sur le tag legacy
    • @pytest.mark.flaky_in_dev — ignoré dans l'environnement dev (CI par défaut ; utilisez ceci pour désactiver un test instable sans bloquer le pipeline standard)
    • @pytest.mark.flaky — ignoré dans l'environnement lts
    • @pytest.mark.experimental — tag latest uniquement
  4. Vérifiez localement (voir Exécuter les Tests Unitaires Localement ci-dessus).
  5. Si le test nécessite un bucket CI dédié, ajoutez une entrée à tests/test_utils/recipes/h100/unit-tests.yaml.

Ajouter un Test Fonctionnel / d'Intégration

  1. Créez tests/functional_tests/test_cases/<model>/<test_name>/.

  2. Écrivez model_config.yaml avec MODEL_ARGS, ENV_VARS, et TEST_TYPE.

  3. Ajoutez une recette YAML sous tests/test_utils/recipes/h100/ (et gb200/ si nécessaire). Champs requis : scope, environment, platform, n_repeat, time_limit.

  4. Poussez la PR, ajoutez le label "Run functional tests" pour déclencher une exécution complète.

  5. Après une exécution réussie, téléchargez les valeurs de référence :

    python tests/test_utils/python_scripts/download_golden_values.py \
      --source github --pipeline-id <run-id>
  6. Committez les valeurs de référence téléchargées.


Pièges Courants

Problème Cause Correction
Test réussit localement mais échoue en CI Environnement ou chemin de données différent Vérifiez DATA_PATH, DATA_CACHE_PATH, et le tag environment (dev vs lts)
Désaccord de valeur de référence après un changement de code Régression numérique Téléchargez de nouvelles valeurs de référence via download_golden_values.py après une exécution propre
cicd-integration-tests-gb200 non déclenché Les jobs GB200 nécessitent le statut de mainteneur Demandez à un mainteneur de déclencher, ou ajoutez le label Run functional tests

Skills similaires