Ajouter un Benchmark à NeMo-Gym
Déterminer le Type d'Intégration
Avant de commencer, déterminez quel type de benchmark vous ajoutez :
Benchmark natif — logique de vérification implémentée directement dans un serveur de ressources Gym :
- Le serveur de ressources implémente
verify()avec la logique de récompense - Le serveur agent orchestre les appels du modèle (utilise
simple_agentpour un tour unique, ou un agent personnalisé pour plusieurs tours) - Exemple :
code_gen,instruction_following,math_with_judge
Benchmark externe — encapsulation d'une bibliothèque tierce qui a sa propre orchestration :
- Intégration au niveau du serveur agent (non au serveur de ressources)
- Le endpoint
/runde l'agent encapsule la bibliothèque externe - Pré-traitement du schéma Gym vers l'entrée de la bibliothèque, post-traitement vers
BaseVerifyResponse - Reproduisez d'abord les nombres publiquement rapportés avec le dépôt d'origine, puis à nouveau après l'intégration à Gym
- Ajoutez la dépendance dans
requirements.txt
Flux de Travail
Étape 1 : Structurer le serveur
Exécutez ng_init_resources_server pour générer la structure de répertoires :
ng_init_resources_server +entrypoint=resources_servers/my_benchmark
Cela crée :
resources_servers/my_benchmark/
├── app.py # Modèle de serveur
├── configs/my_benchmark.yaml
├── data/.gitignore
├── tests/test_app.py
├── requirements.txt
└── README.md
Pour les benchmarks externes, créez manuellement le serveur agent sous responses_api_agents/my_agent/ avec la même structure.
Étape 2 : Préparer les données
Convertissez votre dataset source au format JSONL de Gym. Chaque ligne doit avoir responses_create_params.input (format de message OpenAI). Les données de vérification spécifiques à la tâche vont dans verifier_metadata.
{
"responses_create_params": {
"input": [
{"role": "system", "content": "Invite système"},
{"role": "user", "content": "Énoncé du problème"}
]
},
"verifier_metadata": {
"test_cases": [{"input": "...", "expected_output": "..."}],
"task_id": "id_unique"
}
}
Conversion de données : Écrivez les scripts de conversion dans le dépôt source (p. ex. votre dépôt de dataset), non dans NeMo-Gym. Les fichiers de prompt appartiennent aussi au dépôt source. Exception : quand il n'y a pas de dépôt source externe. Voir references/patterns.md § « Data Conversion Script Pattern ».
example.jsonl : Générez 5 entrées pour un test de fumée. Ce fichier est committé directement dans data/example.jsonl.
Datasets train/validation : Téléchargez dans le registre de datasets GitLab — ces données NE DOIVENT PAS être commitées dans git.
ng_upload_dataset_to_gitlab \
+dataset_name=my_benchmark \
+version=0.0.1 \
+input_jsonl_fpath=resources_servers/my_benchmark/data/my_dataset.jsonl
Nécessite les identifiants MLflow dans env.yaml (ou passés via CLI) :
mlflow_tracking_uri: <your-gitlab-mlflow-tracking-uri>
mlflow_tracking_token: <your-gitlab-api-token>
data/.gitignore : Le scaffold génère des motifs par défaut (*train.jsonl, *validation.jsonl, etc.). Si votre nom de fichier ne correspond pas (p. ex. my_eval.jsonl), ajoutez un motif personnalisé (p. ex. *eval.jsonl). Si les données étaient précédemment suivies, exécutez git rm --cached <file>.
Validez vos données :
# Valider les données d'exemple (pour la soumission de PR)
ng_prepare_data "+config_paths=[resources_servers/my_benchmark/configs/my_benchmark.yaml]" \
+output_dirpath=/tmp/prepare +mode=example_validation
# Télécharger et préparer train/validation depuis GitLab
ng_prepare_data "+config_paths=[resources_servers/my_benchmark/configs/my_benchmark.yaml]" \
+output_dirpath=data/my_benchmark +mode=train_preparation +should_download=true +data_source=gitlab
Étape 3 : Implémenter verify()
Modifiez app.py. La méthode verify() reçoit la sortie du modèle + verifier_metadata, retourne une récompense.
Pour les benchmarks d'exécution de code, consultez references/patterns.md § « Subprocess Execution with Ray » et « Resources Server Pattern ».
Règles critiques :
- Retournez
rewardcomme 0,0 ou 1,0 (binaire) - Gérez gracieusement les sorties vides/manquantes du modèle — retournez 0,0, ne plantez pas
- Doit gérer 4k-65k requêtes concurrentes sans planter
- Utilisez
asyncio.Semaphorepour le contrôle de concurrence des sous-processus - Pour les tâches Ray distantes :
result = await future(les futures Ray sont directement attendables). N'appelez jamaisray.get()dans un contexte async. - Décodez la sortie des sous-processus avec
errors="replace" - Supprimez les blocs
<think>/<thinking>avant d'analyser la sortie du modèle (les modèles de réflexion émettent ceux-ci) - Les tests doivent
pytest.mark.skipifquand les outils externes ne sont pas installés - Si le benchmark installe automatiquement son outil (voir Étape 3b), ajoutez un hook
pytest_configuredansconftest.pypour exécuter l'installation avant la collecte de tests —skipifs'évalue au moment de l'import, avant l'exécution des fixtures
Étape 3b : Auto-installer les outils externes (si applicable)
Si le benchmark nécessite un outil externe (compilateur, runtime, etc.), installez-le automatiquement au démarrage du serveur pour que les utilisateurs n'aient pas besoin d'une configuration manuelle. Voir references/patterns.md § « External Tool Auto-Install Pattern ».
Points clés :
- Créez
setup_<tool>.pyavecensure_<tool>()— vérifie PATH, bifurque sursys.platform(brew sur macOS, compilation à partir de la source sur Linux) - Appelez-le dans
model_post_init()avant l'initialisation du sémaphore - Les scripts de compilation doivent être idempotents et installer dans un préfixe local ignoré par git
- Ajoutez un hook
pytest_configuredanstests/conftest.pyqui appelleensure_<tool>()avant la collecte
Étape 4 : Câbler la config YAML
Modifiez configs/my_benchmark.yaml. Définissez l'instance du serveur de ressources et le(s) appairage(s) d'agent. Voir references/patterns.md § « YAML Config Pattern ».
Points clés :
verified: falseest auto-ajouté par le hook pre-commit (définissez àtrueaprès le baseline)licenseest obligatoire pour les datasetstrainetvalidation- L'agent référence le serveur de ressources et le serveur de modèle par nom d'instance
Pour les benchmarks multi-tour, utilisez soit proof_refinement_agent soit créez un agent personnalisé. Voir references/patterns.md § « Agent Patterns ».
Pour les datasets train/validation, ajoutez gitlab_identifier à côté de jsonl_fpath :
datasets:
- name: my_dataset
type: train
jsonl_fpath: resources_servers/my_benchmark/data/my_dataset.jsonl
gitlab_identifier:
dataset_name: my_benchmark
version: 0.0.1
artifact_fpath: my_dataset.jsonl
license: MIT
- name: example
type: example
jsonl_fpath: resources_servers/my_benchmark/data/example.jsonl
Les deux champs doivent coexister : jsonl_fpath est la destination de téléchargement local, gitlab_identifier indique au système d'où faire la récupération. Les datasets example n'ont pas besoin de gitlab_identifier — ils sont committes directement dans git.
Étape 5 : Tester
# Exécuter les tests du serveur (crée un .venv isolé, lent au premier lancement)
ng_test +entrypoint=resources_servers/my_benchmark
# Exécuter les tests de la bibliothèque principale pour vérifier que rien n'a cassé
pytest tests/unit_tests/ -x
La couverture de test doit être >= 95%. Écrivez des tests pour : verify réussit, verify échoue (sortie incorrecte), verify échoue (aucun code extrait), verify échoue (erreur de compilation si applicable), verify timeout.
Étape 6 : Test de fumée end-to-end
# Démarrer les serveurs
ng_run "+config_paths=[resources_servers/my_benchmark/configs/my_benchmark.yaml,responses_api_models/openai_model/configs/openai_model.yaml]"
# Test rapide avec données d'exemple
ng_collect_rollouts +agent_name=my_benchmark_simple_agent \
+input_jsonl_fpath=resources_servers/my_benchmark/data/example.jsonl \
+output_jsonl_fpath=results/example_rollouts.jsonl \
+num_repeats=1 \
"+responses_create_params={max_output_tokens: 16384, temperature: 1.0}"
# Inspectez les résultats
Étape 7 : Baseline (profilage des récompenses)
Exécutez avec plusieurs modèles pour valider la correction. Suite recommandée :
- Votre modèle de politique d'intérêt
- Au moins un modèle instruct open-source (p. ex. Qwen 3 30B A3B Instruct)
- Au moins un modèle de réflexion open-source (p. ex. Qwen 3 30B A3B Thinking)
- Au moins un modèle fermé (p. ex. GPT-5 Nano ou GPT-5)
# Collecter les rollouts
ng_collect_rollouts +agent_name=my_benchmark_simple_agent \
+input_jsonl_fpath=resources_servers/my_benchmark/data/my_dataset.jsonl \
+output_jsonl_fpath=results/rollouts.jsonl \
+num_repeats=5 \
"+responses_create_params={max_output_tokens: 16384, temperature: 1.0}"
# Calculer les taux de passage par tâche
ng_reward_profile +input_jsonl_fpath=resources_servers/my_benchmark/data/my_dataset.jsonl \
+rollouts_jsonl_fpath=results/rollouts.jsonl \
+output_jsonl_fpath=results/profiled.jsonl \
+pass_threshold=1.0
# Agréger les métriques (pass@1 = avg_reward, pass@k à partir de max_reward)
python scripts/print_aggregate_results.py +jsonl_fpath=results/profiled.jsonl
Augmentez num_repeats jusqu'à ce que la variance < 1 % à travers les exécutions sur le même modèle.
Les modèles fermés doivent obtenir un score égal ou supérieur aux modèles open-source. Si ce n'est pas le cas, enquêtez sur les bugs. Inspectez les cas d'échec réels dans le JSONL des rollouts, pas seulement les chiffres agrégés.
Pour les benchmarks externes : reproduisez d'abord les nombres publiés du dépôt d'origine. Puis reproduisez après l'intégration à Gym. Les scores doivent correspondre.
Étape 8 : Pre-commit et PR
pre-commit run --all-files
La première exécution peut échouer car les hooks modifient automatiquement les fichiers (flag verified: false, tableau README). Mettez en scène les changements et exécutez à nouveau.
Définissez verified: true dans la configuration YAML après un baseline réussi. Incluez les liens W&B et les captures d'écran des résultats dans la description de la PR.
Pour éviter de committer des auto-corrections non liées d'autres serveurs, limitez pre-commit à vos fichiers :
pre-commit run --files resources_servers/my_benchmark/**/*
Si les hooks modifient des fichiers dans d'autres répertoires, annulez ces changements :
git checkout -- resources_servers/other_server/
Contraintes
- Utilisez le client OpenAI de NeMo Gym (
nemo_gym/openai_utils.py), non LiteLLM/Anthropic/autre - Utilisez aiohttp, non httpx, pour HTTP async. Tous les appels HTTP async doivent passer par
nemo_gym.server_utils.request()(aiohttp). httpx a un pool de connexions O(n²) qui s'accroche à haute concurrence. Quand vous encapsulez des bibliothèques externes qui utilisent httpx en interne, remplacez leur transport HTTP par un adaptateur aiohttp — voirresources_servers/tavily_search/app.py(TavilySearchAIOHTTPClient) pour le motif etdocs/infrastructure/engineering-notes/aiohttp-vs-httpx.mdpour la justification. - Passez la configuration via la config Gym (YAML), non des variables d'environnement
- Le code doit s'exécuter sur Linux
- Le endpoint
/rundoit être async - Les erreurs d'exécution d'outil ou de sortie de modèle erronée doivent retourner des réponses d'erreur, non planter
- Tous les commits nécessitent une signature DCO (
-s) et une signature cryptographique (-S)
Référence
Pour des motifs de code détaillés, des schémas et des exemples : consultez references/patterns.md.