diff --git a/CLAUDE.md b/CLAUDE.md index d14cab2cf4..ff41d385d9 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -82,7 +82,7 @@ uv venv && uv sync --extra dev --group docs pre-commit install # Run servers -ng_run "+config_paths=[resources_servers/example_single_tool_call/configs/example_single_tool_call.yaml,responses_api_models/vllm_model/configs/vllm_model.yaml]" +ng_run "+config_paths=[resources_servers/example_single_tool_call/configs/example_single_tool_call.yaml,responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml]" # Run tests for a specific server (creates .venv per server, installs deps, runs pytest) # First run is slow. Use skip_venv_if_present config or place a .venv to skip venv creation. diff --git a/benchmarks/aalcr/README.md b/benchmarks/aalcr/README.md index 52a0d823d0..7c80ec3960 100644 --- a/benchmarks/aalcr/README.md +++ b/benchmarks/aalcr/README.md @@ -6,7 +6,7 @@ ng_prepare_benchmark "+config_paths=[$config_paths]" # Run ```bash -config_paths="responses_api_models/vllm_model/configs/vllm_model.yaml,\ +config_paths="responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml,\ benchmarks/aalcr/config.yaml" ng_e2e_collect_rollouts \ "+config_paths=[${config_paths}]" \ diff --git a/benchmarks/aalcr/config.yaml b/benchmarks/aalcr/config.yaml index 390ef39e57..8129d27605 100644 --- a/benchmarks/aalcr/config.yaml +++ b/benchmarks/aalcr/config.yaml @@ -1,7 +1,7 @@ -# Refer to the params in responses_api_models/local_vllm_model/configs/Qwen/Qwen3-235B-A22B-Instruct-2507-FP8.yaml +# Refer to the params in responses_api_models/vllm_server/configs/Qwen/Qwen3-235B-A22B-Instruct-2507-FP8.yaml Qwen3-235B-A22B-Instruct-2507-FP8: responses_api_models: - vllm_model: + vllm_endpoint: entrypoint: app.py model: Qwen3-235B-A22B-Instruct-2507-FP8 replace_developer_role_with_system: true diff --git a/benchmarks/aime24-x/README.md b/benchmarks/aime24-x/README.md index e59d3bfde3..64d746900a 100644 --- a/benchmarks/aime24-x/README.md +++ b/benchmarks/aime24-x/README.md @@ -36,7 +36,7 @@ python benchmarks/aime24-x/prepare.py --prompt_language en ## Quickstart ```bash -ng_run "+config_paths=[benchmarks/aime24-x/config.yaml,responses_api_models/vllm_model/configs/vllm_model.yaml]" +ng_run "+config_paths=[benchmarks/aime24-x/config.yaml,responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml]" ``` Then in another shell: diff --git a/benchmarks/aime25-x/README.md b/benchmarks/aime25-x/README.md index 1ac1453ff1..0d0f377e77 100644 --- a/benchmarks/aime25-x/README.md +++ b/benchmarks/aime25-x/README.md @@ -36,7 +36,7 @@ python benchmarks/aime25-x/prepare.py --prompt_language en ## Quickstart ```bash -ng_run "+config_paths=[benchmarks/aime25-x/config.yaml,responses_api_models/vllm_model/configs/vllm_model.yaml]" +ng_run "+config_paths=[benchmarks/aime25-x/config.yaml,responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml]" ``` Then in another shell: diff --git a/benchmarks/aime26/README.md b/benchmarks/aime26/README.md index a7d0c401d6..6a334cf7e6 100644 --- a/benchmarks/aime26/README.md +++ b/benchmarks/aime26/README.md @@ -11,7 +11,7 @@ ng_prepare_benchmark "+config_paths=[benchmarks/aime26/config.yaml]" ## Run servers ```bash -config_paths="responses_api_models/vllm_model/configs/vllm_model.yaml,benchmarks/aime26/config.yaml" +config_paths="responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml,benchmarks/aime26/config.yaml" ng_run "+config_paths=[$config_paths]" ``` diff --git a/benchmarks/answer-judge/README.md b/benchmarks/answer-judge/README.md index 3313ae000c..feb36913cf 100644 --- a/benchmarks/answer-judge/README.md +++ b/benchmarks/answer-judge/README.md @@ -16,7 +16,7 @@ here is the same deterministic `Judgement: Yes/No` parsing used by Skills' ng_prepare_benchmark "+config_paths=[benchmarks/answer-judge/config.yaml]" # Running servers -config_paths="responses_api_models/vllm_model/configs/vllm_model.yaml,\ +config_paths="responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml,\ benchmarks/answer-judge/config.yaml" ng_run "+config_paths=[$config_paths]" diff --git a/benchmarks/apex_shortlist/README.md b/benchmarks/apex_shortlist/README.md index 2ced13a9f8..40b2d7594e 100644 --- a/benchmarks/apex_shortlist/README.md +++ b/benchmarks/apex_shortlist/README.md @@ -35,7 +35,7 @@ Writes `data/apex_shortlist_benchmark.jsonl` with one row per problem: ## Running servers ```bash -config_paths="responses_api_models/vllm_model/configs/vllm_model.yaml,\ +config_paths="responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml,\ benchmarks/apex_shortlist/config.yaml" ng_run "+config_paths=[$config_paths]" ``` diff --git a/benchmarks/arena_hard/README.md b/benchmarks/arena_hard/README.md index 55e75997b5..655996fd05 100644 --- a/benchmarks/arena_hard/README.md +++ b/benchmarks/arena_hard/README.md @@ -31,7 +31,7 @@ to pick the standard judge prompt. ng_prepare_benchmark "+config_paths=[benchmarks/arena_hard/config.yaml]" # Running servers -config_paths="responses_api_models/vllm_model/configs/vllm_model.yaml,\ +config_paths="responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml,\ benchmarks/arena_hard/config.yaml" ng_run "+config_paths=[$config_paths]" diff --git a/benchmarks/arena_hard_v2/README.md b/benchmarks/arena_hard_v2/README.md index e88a228525..b78cc916e6 100644 --- a/benchmarks/arena_hard_v2/README.md +++ b/benchmarks/arena_hard_v2/README.md @@ -34,7 +34,7 @@ repo, joins by `uid`, and emits one row per question with `question`, ng_prepare_benchmark "+config_paths=[benchmarks/arena_hard_v2/config.yaml]" # Running servers -config_paths="responses_api_models/vllm_model/configs/vllm_model.yaml,\ +config_paths="responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml,\ benchmarks/arena_hard_v2/config.yaml" ng_run "+config_paths=[$config_paths]" diff --git a/benchmarks/asr_leaderboard/README.md b/benchmarks/asr_leaderboard/README.md index 7326651f22..39d7a699af 100644 --- a/benchmarks/asr_leaderboard/README.md +++ b/benchmarks/asr_leaderboard/README.md @@ -32,7 +32,7 @@ Downloads the 8 ESB subsets (~tens of GB of FLAC) and writes ## Running servers ```bash -config_paths="responses_api_models/vllm_model/configs/vllm_model.yaml,\ +config_paths="responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml,\ benchmarks/asr_leaderboard/config.yaml" ng_run "+config_paths=[$config_paths]" ``` diff --git a/benchmarks/bigcodebench/README.md b/benchmarks/bigcodebench/README.md index 0722b2eaa9..df3c08543b 100644 --- a/benchmarks/bigcodebench/README.md +++ b/benchmarks/bigcodebench/README.md @@ -16,7 +16,7 @@ the `full` split (~1140 problems) is `bigcode/bigcodebench@v0.1.4`. ng_prepare_benchmark "+config_paths=[benchmarks/bigcodebench/config.yaml]" # Running servers -config_paths="responses_api_models/vllm_model/configs/vllm_model.yaml,\ +config_paths="responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml,\ benchmarks/bigcodebench/config.yaml" ng_run "+config_paths=[$config_paths]" diff --git a/benchmarks/birdbench/README.md b/benchmarks/birdbench/README.md index ba7708c352..2027e00ef2 100644 --- a/benchmarks/birdbench/README.md +++ b/benchmarks/birdbench/README.md @@ -22,7 +22,7 @@ and writes `data/birdbench_benchmark.jsonl`. Each row has ## Running servers ```bash -config_paths="responses_api_models/vllm_model/configs/vllm_model.yaml,\ +config_paths="responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml,\ benchmarks/birdbench/config.yaml" ng_run "+config_paths=[$config_paths]" ``` diff --git a/benchmarks/browsecomp/README.md b/benchmarks/browsecomp/README.md index 7d7d2ef9dd..7fd7b1676a 100644 --- a/benchmarks/browsecomp/README.md +++ b/benchmarks/browsecomp/README.md @@ -8,7 +8,7 @@ browsecomp_benchmark_resources_server: exclude_domains_file_path: ??? Qwen3-235B-A22B-Instruct-2507-FP8: responses_api_models: - vllm_model: + vllm_endpoint: base_url: ??? api_key: ??? ``` @@ -23,7 +23,7 @@ ng_prepare_benchmark "+config_paths=[$config_paths]" ```bash WANDB_PROJECT= EXPERIMENT_NAME= -config_paths="responses_api_models/vllm_model/configs/vllm_model.yaml,\ +config_paths="responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml,\ benchmarks/browsecomp/config.yaml" ng_e2e_collect_rollouts \ "+config_paths=[${config_paths}]" \ diff --git a/benchmarks/browsecomp/config.yaml b/benchmarks/browsecomp/config.yaml index d000c1d090..d1ed15fa31 100644 --- a/benchmarks/browsecomp/config.yaml +++ b/benchmarks/browsecomp/config.yaml @@ -1,7 +1,7 @@ -# Refer to the params in responses_api_models/local_vllm_model/configs/Qwen/Qwen3-235B-A22B-Instruct-2507-FP8.yaml +# Refer to the params in responses_api_models/vllm_server/configs/Qwen/Qwen3-235B-A22B-Instruct-2507-FP8.yaml Qwen3-235B-A22B-Instruct-2507-FP8: responses_api_models: - vllm_model: + vllm_endpoint: entrypoint: app.py model: Qwen3-235B-A22B-Instruct-2507-FP8 replace_developer_role_with_system: true @@ -11,7 +11,7 @@ Qwen3-235B-A22B-Instruct-2507-FP8: policy_model_no_interleaved_reasoning: _copy: policy_model responses_api_models: - vllm_model: + vllm_endpoint: uses_interleaved_reasoning: false browsecomp_benchmark_resources_server: diff --git a/benchmarks/finance_sec_search/README.md b/benchmarks/finance_sec_search/README.md index 5a43b6c9fb..d73e7f5fe4 100644 --- a/benchmarks/finance_sec_search/README.md +++ b/benchmarks/finance_sec_search/README.md @@ -47,7 +47,7 @@ JSONL to `data/`. ## Running servers ```bash -config_paths="responses_api_models/vllm_model/configs/vllm_model.yaml,\ +config_paths="responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml,\ benchmarks/finance_sec_search/config_no_web_search.yaml" ng_run "+config_paths=[$config_paths]" ``` diff --git a/benchmarks/flores200/README.md b/benchmarks/flores200/README.md index 8f770cbedd..bed2795944 100644 --- a/benchmarks/flores200/README.md +++ b/benchmarks/flores200/README.md @@ -41,7 +41,7 @@ only advertised on multi-node SLURM deployments via NeMo-Skills' override and rely on corpus-BLEU only: ```bash -config_paths="responses_api_models/vllm_model/configs/vllm_model.yaml,\ +config_paths="responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml,\ benchmarks/flores200/config.yaml" ng_run "+config_paths=[$config_paths]" \ "++flores200_wmt_translation_resources_server.resources_servers.wmt_translation.compute_comet=false" @@ -102,7 +102,7 @@ ns nemo_gym_rollouts \ --server_nodes 2 \ --server_args "--tensor-parallel-size 8 --data-parallel-size 1 --data-parallel-size-local 1 --data-parallel-backend ray --distributed-executor-backend ray --api-server-count 1 --reasoning-parser deepseek_r1 --trust-remote-code --dtype auto --enforce-eager" \ --model "$MODEL" \ - --config_paths "benchmarks/flores200/config.yaml,responses_api_models/vllm_model/configs/vllm_model.yaml" \ + --config_paths "benchmarks/flores200/config.yaml,responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml" \ --input_file benchmarks/flores200/data/flores200_devtest_benchmark.jsonl \ --output_dir /workspace/flores200_smoke \ --expname flores200_smoke \ diff --git a/benchmarks/frontierscience_olympiad/README.md b/benchmarks/frontierscience_olympiad/README.md index 645a646e64..d75b9d29f6 100644 --- a/benchmarks/frontierscience_olympiad/README.md +++ b/benchmarks/frontierscience_olympiad/README.md @@ -39,7 +39,7 @@ example, the original Skills configuration uses `o3-mini-2025-01-31` via ng_prepare_benchmark "+config_paths=[benchmarks/frontierscience_olympiad/config.yaml]" # Running servers -config_paths="responses_api_models/vllm_model/configs/vllm_model.yaml,\ +config_paths="responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml,\ benchmarks/frontierscience_olympiad/config.yaml" ng_run "+config_paths=[$config_paths]" diff --git a/benchmarks/gdpval/README.md b/benchmarks/gdpval/README.md index b2e50937f8..c99bba3d78 100644 --- a/benchmarks/gdpval/README.md +++ b/benchmarks/gdpval/README.md @@ -19,7 +19,7 @@ ng_prepare_benchmark "+config_paths=[benchmarks/gdpval/config.yaml]" Each deliverable is scored 0–1 against the task rubric. ```bash -config_paths="responses_api_models/vllm_model/configs/vllm_model.yaml,\ +config_paths="responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml,\ benchmarks/gdpval/config.yaml" ng_e2e_collect_rollouts \ "+config_paths=[${config_paths}]" \ diff --git a/benchmarks/gdpval/config.yaml b/benchmarks/gdpval/config.yaml index 6b827e1800..5527540125 100644 --- a/benchmarks/gdpval/config.yaml +++ b/benchmarks/gdpval/config.yaml @@ -3,7 +3,7 @@ # Run: # ng_prepare_benchmark "+config_paths=[benchmarks/gdpval/config.yaml]" # ng_e2e_collect_rollouts \ -# "+config_paths=[responses_api_models/vllm_model/configs/vllm_model.yaml,benchmarks/gdpval/config.yaml]" \ +# "+config_paths=[responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml,benchmarks/gdpval/config.yaml]" \ # ++split=benchmark \ # ++output_jsonl_fpath=results/gdpval.jsonl # diff --git a/benchmarks/global-piqa/README.md b/benchmarks/global-piqa/README.md index 73516ff902..dc4b5bb717 100644 --- a/benchmarks/global-piqa/README.md +++ b/benchmarks/global-piqa/README.md @@ -19,7 +19,7 @@ server. ng_prepare_benchmark "+config_paths=[benchmarks/global-piqa/config.yaml]" # Running servers -config_paths="responses_api_models/vllm_model/configs/vllm_model.yaml,\ +config_paths="responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml,\ benchmarks/global-piqa/config.yaml" ng_run "+config_paths=[$config_paths]" diff --git a/benchmarks/gpqa-x/README.md b/benchmarks/gpqa-x/README.md index f50c9a8a69..01aa9a7106 100644 --- a/benchmarks/gpqa-x/README.md +++ b/benchmarks/gpqa-x/README.md @@ -37,7 +37,7 @@ python benchmarks/gpqa-x/prepare.py --prompt_language en ## Quickstart ```bash -ng_run "+config_paths=[benchmarks/gpqa-x/config.yaml,responses_api_models/vllm_model/configs/vllm_model.yaml]" +ng_run "+config_paths=[benchmarks/gpqa-x/config.yaml,responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml]" ``` Then in another shell: diff --git a/benchmarks/gpqa/README.md b/benchmarks/gpqa/README.md index 1801de0c52..aadbb35d4c 100644 --- a/benchmarks/gpqa/README.md +++ b/benchmarks/gpqa/README.md @@ -16,10 +16,10 @@ This benchmark uses the `mcqa` resource server with the `mcqa_simple_agent`. ng_prepare_benchmark "+config_paths=[benchmarks/gpqa/config.yaml]" # Start servers -ng_run "+config_paths=[benchmarks/gpqa/config.yaml,responses_api_models/vllm_model/configs/vllm_model.yaml]" +ng_run "+config_paths=[benchmarks/gpqa/config.yaml,responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml]" # Collect rollouts ng_collect_rollouts \ - "+config_paths=[benchmarks/gpqa/config.yaml,responses_api_models/vllm_model/configs/vllm_model.yaml]" \ + "+config_paths=[benchmarks/gpqa/config.yaml,responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml]" \ +output_jsonl_fpath=results/gpqa.jsonl ``` diff --git a/benchmarks/graphwalks/README.md b/benchmarks/graphwalks/README.md index 526a6e9152..fa9e9e370a 100644 --- a/benchmarks/graphwalks/README.md +++ b/benchmarks/graphwalks/README.md @@ -47,7 +47,7 @@ python benchmarks/graphwalks/prepare.py \ ## Start environment ```bash -ng_run "+config_paths=[benchmarks/graphwalks/config.yaml,responses_api_models/vllm_model/configs/vllm_model.yaml]" +ng_run "+config_paths=[benchmarks/graphwalks/config.yaml,responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml]" ``` ## Collect rollouts diff --git a/benchmarks/gsm8k/README.md b/benchmarks/gsm8k/README.md index d9a425048d..aed131d538 100644 --- a/benchmarks/gsm8k/README.md +++ b/benchmarks/gsm8k/README.md @@ -16,7 +16,7 @@ the expected answer is integer-valued), then renames `problem` -> ng_prepare_benchmark "+config_paths=[benchmarks/gsm8k/config.yaml]" # Running servers -config_paths="responses_api_models/vllm_model/configs/vllm_model.yaml,\ +config_paths="responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml,\ benchmarks/gsm8k/config.yaml" ng_run "+config_paths=[$config_paths]" diff --git a/benchmarks/hendrycks_math/README.md b/benchmarks/hendrycks_math/README.md index 1b51db38f5..5b5d739658 100644 --- a/benchmarks/hendrycks_math/README.md +++ b/benchmarks/hendrycks_math/README.md @@ -15,7 +15,7 @@ applies Skills' renames (`answer` -> `expected_answer`, `question` -> ng_prepare_benchmark "+config_paths=[benchmarks/hendrycks_math/config.yaml]" # Running servers -config_paths="responses_api_models/vllm_model/configs/vllm_model.yaml,\ +config_paths="responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml,\ benchmarks/hendrycks_math/config.yaml" ng_run "+config_paths=[$config_paths]" diff --git a/benchmarks/hle/README.md b/benchmarks/hle/README.md index 61d83fcb28..e6a898f9df 100644 --- a/benchmarks/hle/README.md +++ b/benchmarks/hle/README.md @@ -34,7 +34,7 @@ Downloads `cais/hle`, filters to text-only questions, and writes ## Running servers ```bash -config_paths="responses_api_models/vllm_model/configs/vllm_model.yaml,\ +config_paths="responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml,\ benchmarks/hle/config.yaml" ng_run "+config_paths=[$config_paths]" ``` diff --git a/benchmarks/hmmt_feb25/README.md b/benchmarks/hmmt_feb25/README.md index 067dbbabb1..8a9b35cbc8 100644 --- a/benchmarks/hmmt_feb25/README.md +++ b/benchmarks/hmmt_feb25/README.md @@ -37,7 +37,7 @@ Start the benchmark's servers (inherits `math_with_judge` in symbolic-only mode plus a vLLM model server — adjust the model config to match your deployment): ``` -ng_run "+config_paths=[benchmarks/hmmt_feb25/config.yaml,responses_api_models/vllm_model/configs/vllm_model.yaml]" +ng_run "+config_paths=[benchmarks/hmmt_feb25/config.yaml,responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml]" ``` In a separate shell, collect rollouts against the full 30-problem set. `num_repeats` diff --git a/benchmarks/hmmt_nov25/README.md b/benchmarks/hmmt_nov25/README.md index 36cddd227f..22535ea65f 100644 --- a/benchmarks/hmmt_nov25/README.md +++ b/benchmarks/hmmt_nov25/README.md @@ -26,7 +26,7 @@ few-shots). Start vLLM with the `--reasoning-parser` that matches your model (e.g. `deepseek_r1` for models with a `` convention; the parser name is declared in -`responses_api_models/local_vllm_model/configs/nvidia/*.yaml`). Without +`responses_api_models/vllm_server/configs/nvidia/*.yaml`). Without one, `math_with_judge` may extract intermediate expressions from truncated rollouts, and Skills' `parse_reasoning=True` default diverges on the same inputs. @@ -38,7 +38,7 @@ on the same inputs. ng_prepare_benchmark "+config_paths=[benchmarks/hmmt_nov25/config.yaml]" # Running servers -config_paths="responses_api_models/vllm_model/configs/vllm_model.yaml,\ +config_paths="responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml,\ benchmarks/hmmt_nov25/config.yaml" ng_run "+config_paths=[$config_paths]" diff --git a/benchmarks/hotpotqa_closedbook/README.md b/benchmarks/hotpotqa_closedbook/README.md index 2f145701a4..78da1f23e8 100644 --- a/benchmarks/hotpotqa_closedbook/README.md +++ b/benchmarks/hotpotqa_closedbook/README.md @@ -25,7 +25,7 @@ ng_prepare_benchmark "+config_paths=[benchmarks/hotpotqa_closedbook/config.yaml] ## Run servers ```bash -config_paths="responses_api_models/vllm_model/configs/vllm_model.yaml,benchmarks/hotpotqa_closedbook/config.yaml" +config_paths="responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml,benchmarks/hotpotqa_closedbook/config.yaml" ng_run "+config_paths=[$config_paths]" ``` diff --git a/benchmarks/human_eval/README.md b/benchmarks/human_eval/README.md index ade0190a0b..ce013b148a 100644 --- a/benchmarks/human_eval/README.md +++ b/benchmarks/human_eval/README.md @@ -23,7 +23,7 @@ holds the dataset definition + prompt + prepare script. ng_prepare_benchmark "+config_paths=[benchmarks/human_eval/config.yaml]" # Running servers -config_paths="responses_api_models/vllm_model/configs/vllm_model.yaml,\ +config_paths="responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml,\ benchmarks/human_eval/config.yaml" ng_run "+config_paths=[$config_paths]" diff --git a/benchmarks/human_eval_infilling/README.md b/benchmarks/human_eval_infilling/README.md index b5a301e830..6195b54798 100644 --- a/benchmarks/human_eval_infilling/README.md +++ b/benchmarks/human_eval_infilling/README.md @@ -40,7 +40,7 @@ holds only the dataset definition + prompt + prepare script. ng_prepare_benchmark "+config_paths=[benchmarks/human_eval_infilling/config.yaml]" # Running servers -config_paths="responses_api_models/vllm_model/configs/vllm_model.yaml,\ +config_paths="responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml,\ benchmarks/human_eval_infilling/config.yaml" ng_run "+config_paths=[$config_paths]" diff --git a/benchmarks/ifbench/README.md b/benchmarks/ifbench/README.md index 55c5c333c2..7fa0340e6d 100644 --- a/benchmarks/ifbench/README.md +++ b/benchmarks/ifbench/README.md @@ -21,7 +21,7 @@ ng_prepare_benchmark "+config_paths=[benchmarks/ifbench/config.yaml]" ```bash ng_e2e_collect_rollouts \ - "+config_paths=[benchmarks/ifbench/config.yaml,responses_api_models/vllm_model/configs/vllm_model.yaml]" \ + "+config_paths=[benchmarks/ifbench/config.yaml,responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml]" \ ++output_jsonl_fpath=results/benchmarks/ifbench.jsonl \ ++overwrite_metrics_conflicts=true \ ++split=benchmark \ diff --git a/benchmarks/ifeval/README.md b/benchmarks/ifeval/README.md index 891b537178..8bcb1375b4 100644 --- a/benchmarks/ifeval/README.md +++ b/benchmarks/ifeval/README.md @@ -13,7 +13,7 @@ ng_prepare_benchmark "+config_paths=[benchmarks/ifeval/config.yaml]" ## Running servers ```bash -config_paths="responses_api_models/vllm_model/configs/vllm_model.yaml,\ +config_paths="responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml,\ benchmarks/ifeval/config.yaml" ng_run "+config_paths=[$config_paths]" ``` diff --git a/benchmarks/imo_answerbench/README.md b/benchmarks/imo_answerbench/README.md index a2aaf420b4..7a3888f0de 100644 --- a/benchmarks/imo_answerbench/README.md +++ b/benchmarks/imo_answerbench/README.md @@ -13,7 +13,7 @@ ng_prepare_benchmark "+config_paths=[benchmarks/imo_answerbench/config.yaml]" ## Run servers ```bash -config_paths="responses_api_models/vllm_model/configs/vllm_model.yaml,benchmarks/imo_answerbench/config.yaml" +config_paths="responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml,benchmarks/imo_answerbench/config.yaml" ng_run "+config_paths=[$config_paths]" ``` diff --git a/benchmarks/imo_gradingbench/README.md b/benchmarks/imo_gradingbench/README.md index de46a420b8..7464759001 100644 --- a/benchmarks/imo_gradingbench/README.md +++ b/benchmarks/imo_gradingbench/README.md @@ -36,7 +36,7 @@ details. ng_prepare_benchmark "+config_paths=[benchmarks/imo_gradingbench/config.yaml]" # Running servers -config_paths="responses_api_models/vllm_model/configs/vllm_model.yaml,\ +config_paths="responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml,\ benchmarks/imo_gradingbench/config.yaml" ng_run "+config_paths=[$config_paths]" diff --git a/benchmarks/imo_proofbench/README.md b/benchmarks/imo_proofbench/README.md index 3bf4908726..8a863ef31c 100644 --- a/benchmarks/imo_proofbench/README.md +++ b/benchmarks/imo_proofbench/README.md @@ -13,7 +13,7 @@ ng_prepare_benchmark "+config_paths=[benchmarks/imo_proofbench/config.yaml]" ## Run servers ```bash -config_paths="responses_api_models/vllm_model/configs/vllm_model.yaml,\ +config_paths="responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml,\ benchmarks/imo_proofbench/config.yaml" ng_run "+config_paths=[$config_paths]" \ +judge_base_url=https://generativelanguage.googleapis.com/v1beta/openai \ diff --git a/benchmarks/ioi/README.md b/benchmarks/ioi/README.md index 54028ecfef..ad0c735b1c 100644 --- a/benchmarks/ioi/README.md +++ b/benchmarks/ioi/README.md @@ -51,7 +51,7 @@ ng_prepare_data +config_paths=[benchmarks/ioi/config.yaml] \ +output_dirpath=benchmarks/ioi/data \ +mode=benchmark_preparation -ng_run +config_paths=[benchmarks/ioi/config.yaml,responses_api_models/vllm_model/configs/vllm_model.yaml] +ng_run +config_paths=[benchmarks/ioi/config.yaml,responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml] ng_collect_rollouts +agent_name=ioi_simple_agent \ +input_jsonl_fpath=benchmarks/ioi/data/ioi24_benchmark.jsonl \ diff --git a/benchmarks/labbench2_vlm/README.md b/benchmarks/labbench2_vlm/README.md index c506b91c15..311db687e3 100644 --- a/benchmarks/labbench2_vlm/README.md +++ b/benchmarks/labbench2_vlm/README.md @@ -19,7 +19,7 @@ the agent. The benchmark chains in `resources_servers/labbench2_vlm/configs/judge_model_openai.yaml`, which targets an OpenAI-compatible hosted endpoint. To use a different judge (e.g. a local vLLM), drop that chain and supply your own `responses_api_models` -instance named `judge_model` — see `responses_api_models/vllm_model/configs/vllm_model.yaml` +instance named `judge_model` — see `responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml` for the vLLM form. Credentials go in `env.yaml` at the **repository root** (the parser loads diff --git a/benchmarks/librispeech_pc/README.md b/benchmarks/librispeech_pc/README.md index a80b5a4019..999c0a8aa8 100644 --- a/benchmarks/librispeech_pc/README.md +++ b/benchmarks/librispeech_pc/README.md @@ -43,7 +43,7 @@ and writes the JSONL into `benchmarks/librispeech_pc/data/`. ## Running servers ```bash -config_paths="responses_api_models/vllm_model/configs/vllm_model.yaml,\ +config_paths="responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml,\ benchmarks/librispeech_pc/config.yaml" ng_run "+config_paths=[$config_paths]" ``` diff --git a/benchmarks/livecodebench-x/README.md b/benchmarks/livecodebench-x/README.md index eaefaf4e0e..24b8ccb0a9 100644 --- a/benchmarks/livecodebench-x/README.md +++ b/benchmarks/livecodebench-x/README.md @@ -64,7 +64,7 @@ python benchmarks/livecodebench-x/prepare.py --prompt_language en ## Quickstart ```bash -ng_run "+config_paths=[benchmarks/livecodebench-x/config.yaml,responses_api_models/vllm_model/configs/vllm_model.yaml]" +ng_run "+config_paths=[benchmarks/livecodebench-x/config.yaml,responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml]" ``` Then in another shell: @@ -72,7 +72,7 @@ Then in another shell: ```bash mkdir -p results/livecodebench-x ng_collect_rollouts \ - "+config_paths=[benchmarks/livecodebench-x/config.yaml,responses_api_models/vllm_model/configs/vllm_model.yaml]" \ + "+config_paths=[benchmarks/livecodebench-x/config.yaml,responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml]" \ +agent_name=livecodebench-x_code_gen_simple_agent \ +input_jsonl_fpath=benchmarks/livecodebench-x/data/livecodebench-x_benchmark.jsonl \ +output_jsonl_fpath=results/livecodebench-x/rollouts.jsonl \ diff --git a/benchmarks/longbench_v2/README.md b/benchmarks/longbench_v2/README.md index 61fbd756ba..d4308e452a 100644 --- a/benchmarks/longbench_v2/README.md +++ b/benchmarks/longbench_v2/README.md @@ -48,7 +48,7 @@ ng_prepare_benchmark "+config_paths=[benchmarks/longbench_v2/config.yaml]" ng_prepare_benchmark "+config_paths=[benchmarks/longbench_v2/config_n3_1m.yaml]" # Running servers -config_paths="responses_api_models/vllm_model/configs/vllm_model.yaml,\ +config_paths="responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml,\ benchmarks/longbench_v2/config.yaml" ng_run "+config_paths=[$config_paths]" diff --git a/benchmarks/longcodebench/README.md b/benchmarks/longcodebench/README.md index 60192ab366..6310e61726 100644 --- a/benchmarks/longcodebench/README.md +++ b/benchmarks/longcodebench/README.md @@ -42,7 +42,7 @@ ng_prepare_benchmark "+config_paths=[benchmarks/longcodebench/config.yaml]" ng_prepare_benchmark "+config_paths=[benchmarks/longcodebench/config_n3_1m.yaml]" # Running servers -config_paths="responses_api_models/vllm_model/configs/vllm_model.yaml,\ +config_paths="responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml,\ benchmarks/longcodebench/config.yaml" ng_run "+config_paths=[$config_paths]" diff --git a/benchmarks/m_arena_hard/README.md b/benchmarks/m_arena_hard/README.md index 062d8e1688..04e1729191 100644 --- a/benchmarks/m_arena_hard/README.md +++ b/benchmarks/m_arena_hard/README.md @@ -51,7 +51,7 @@ ng_prepare_benchmark "+config_paths=[benchmarks/m_arena_hard/config.yaml]" python benchmarks/m_arena_hard/prepare.py --baseline-file path/to/baselines.jsonl # Running servers -config_paths="responses_api_models/vllm_model/configs/vllm_model.yaml,\ +config_paths="responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml,\ benchmarks/m_arena_hard/config.yaml" ng_run "+config_paths=[$config_paths]" diff --git a/benchmarks/m_arena_hard_v2/README.md b/benchmarks/m_arena_hard_v2/README.md index bbf7c38208..91f5901eaa 100644 --- a/benchmarks/m_arena_hard_v2/README.md +++ b/benchmarks/m_arena_hard_v2/README.md @@ -58,7 +58,7 @@ python benchmarks/m_arena_hard_v2/prepare.py \ ng_prepare_benchmark "+config_paths=[benchmarks/m_arena_hard_v2/config.yaml]" # Running servers -config_paths="responses_api_models/vllm_model/configs/vllm_model.yaml,\ +config_paths="responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml,\ benchmarks/m_arena_hard_v2/config.yaml" ng_run "+config_paths=[$config_paths]" diff --git a/benchmarks/math-500/README.md b/benchmarks/math-500/README.md index fdebaa0226..e9460f8edd 100644 --- a/benchmarks/math-500/README.md +++ b/benchmarks/math-500/README.md @@ -18,7 +18,7 @@ resource server. ng_prepare_benchmark "+config_paths=[benchmarks/math-500/config.yaml]" # Running servers -config_paths="responses_api_models/vllm_model/configs/vllm_model.yaml,\ +config_paths="responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml,\ benchmarks/math-500/config.yaml" ng_run "+config_paths=[$config_paths]" diff --git a/benchmarks/mbpp/README.md b/benchmarks/mbpp/README.md index 45859a3c69..2d5dba5b43 100644 --- a/benchmarks/mbpp/README.md +++ b/benchmarks/mbpp/README.md @@ -25,7 +25,7 @@ Verification runs in the `evalplus` resource server (shared with ng_prepare_benchmark "+config_paths=[benchmarks/mbpp/config.yaml]" # Running servers -config_paths="responses_api_models/vllm_model/configs/vllm_model.yaml,\ +config_paths="responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml,\ benchmarks/mbpp/config.yaml" ng_run "+config_paths=[$config_paths]" diff --git a/benchmarks/minif2f/README.md b/benchmarks/minif2f/README.md index 6569256f2b..c04982e167 100644 --- a/benchmarks/minif2f/README.md +++ b/benchmarks/minif2f/README.md @@ -30,7 +30,7 @@ and set `NEMO_SKILLS_SANDBOX_HOST` / `NEMO_SKILLS_SANDBOX_PORT` before starting the server. ```bash -config_paths="responses_api_models/vllm_model/configs/vllm_model.yaml,\ +config_paths="responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml,\ benchmarks/minif2f/config.yaml" ng_run "+config_paths=[$config_paths]" ``` diff --git a/benchmarks/mmlu-redux/README.md b/benchmarks/mmlu-redux/README.md index 5c47f85fbc..42e458abfc 100644 --- a/benchmarks/mmlu-redux/README.md +++ b/benchmarks/mmlu-redux/README.md @@ -18,7 +18,7 @@ Migrates NeMo Skills' `mmlu-redux` benchmark to Gym on top of the shared ng_prepare_benchmark "+config_paths=[benchmarks/mmlu-redux/config.yaml]" # Running servers -config_paths="responses_api_models/vllm_model/configs/vllm_model.yaml,\ +config_paths="responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml,\ benchmarks/mmlu-redux/config.yaml" ng_run "+config_paths=[$config_paths]" diff --git a/benchmarks/mmlu/README.md b/benchmarks/mmlu/README.md index 6987bf93a3..41d3819951 100644 --- a/benchmarks/mmlu/README.md +++ b/benchmarks/mmlu/README.md @@ -17,7 +17,7 @@ resource server. ng_prepare_benchmark "+config_paths=[benchmarks/mmlu/config.yaml]" # Running servers -config_paths="responses_api_models/vllm_model/configs/vllm_model.yaml,\ +config_paths="responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml,\ benchmarks/mmlu/config.yaml" ng_run "+config_paths=[$config_paths]" diff --git a/benchmarks/mmlu_pro/README.md b/benchmarks/mmlu_pro/README.md index a61aa8880c..ffe60abb96 100644 --- a/benchmarks/mmlu_pro/README.md +++ b/benchmarks/mmlu_pro/README.md @@ -16,10 +16,10 @@ This benchmark uses the `mcqa` resource server with the `mcqa_simple_agent`. ng_prepare_benchmark "+config_paths=[benchmarks/mmlu_pro/config.yaml]" # Start servers -ng_run "+config_paths=[benchmarks/mmlu_pro/config.yaml,responses_api_models/vllm_model/configs/vllm_model.yaml]" +ng_run "+config_paths=[benchmarks/mmlu_pro/config.yaml,responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml]" # Collect rollouts ng_collect_rollouts \ - "+config_paths=[benchmarks/mmlu_pro/config.yaml,responses_api_models/vllm_model/configs/vllm_model.yaml]" \ + "+config_paths=[benchmarks/mmlu_pro/config.yaml,responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml]" \ +output_jsonl_fpath=results/mmlu_pro.jsonl ``` diff --git a/benchmarks/mmlu_prox/README.md b/benchmarks/mmlu_prox/README.md index 35676c43a0..6db934900d 100644 --- a/benchmarks/mmlu_prox/README.md +++ b/benchmarks/mmlu_prox/README.md @@ -16,10 +16,10 @@ This benchmark uses the `mcqa` resource server with the `mcqa_simple_agent`. ng_prepare_benchmark "+config_paths=[benchmarks/mmlu_prox/config.yaml]" # Start servers -ng_run "+config_paths=[benchmarks/mmlu_prox/config.yaml,responses_api_models/vllm_model/configs/vllm_model.yaml]" +ng_run "+config_paths=[benchmarks/mmlu_prox/config.yaml,responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml]" # Collect rollouts ng_collect_rollouts \ - "+config_paths=[benchmarks/mmlu_prox/config.yaml,responses_api_models/vllm_model/configs/vllm_model.yaml]" \ + "+config_paths=[benchmarks/mmlu_prox/config.yaml,responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml]" \ +output_jsonl_fpath=results/mmlu_prox.jsonl ``` diff --git a/benchmarks/mmmlu/README.md b/benchmarks/mmmlu/README.md index bae74b2969..dce9e40704 100644 --- a/benchmarks/mmmlu/README.md +++ b/benchmarks/mmmlu/README.md @@ -17,7 +17,7 @@ resource server. ng_prepare_benchmark "+config_paths=[benchmarks/mmmlu/config.yaml]" # Running servers -config_paths="responses_api_models/vllm_model/configs/vllm_model.yaml,\ +config_paths="responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml,\ benchmarks/mmmlu/config.yaml" ng_run "+config_paths=[$config_paths]" diff --git a/benchmarks/mobench/README.md b/benchmarks/mobench/README.md index b32706e6a1..e3bca0efe6 100644 --- a/benchmarks/mobench/README.md +++ b/benchmarks/mobench/README.md @@ -29,7 +29,7 @@ and set `NEMO_SKILLS_SANDBOX_HOST` / `NEMO_SKILLS_SANDBOX_PORT` before starting the server. ```bash -config_paths="responses_api_models/vllm_model/configs/vllm_model.yaml,\ +config_paths="responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml,\ benchmarks/mobench/config.yaml" ng_run "+config_paths=[$config_paths]" ``` diff --git a/benchmarks/mrcr/README.md b/benchmarks/mrcr/README.md index d31ae8ec0c..3d91da38d3 100644 --- a/benchmarks/mrcr/README.md +++ b/benchmarks/mrcr/README.md @@ -45,7 +45,7 @@ ng_prepare_benchmark "+config_paths=[benchmarks/mrcr/config_n3_1m.yaml]" ## Start environment ```bash -ng_run "+config_paths=[benchmarks/mrcr/config.yaml,responses_api_models/vllm_model/configs/vllm_model.yaml]" +ng_run "+config_paths=[benchmarks/mrcr/config.yaml,responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml]" ``` ## Collect rollouts diff --git a/benchmarks/musan/README.md b/benchmarks/musan/README.md index 4ef8ee8a9d..b10f321800 100644 --- a/benchmarks/musan/README.md +++ b/benchmarks/musan/README.md @@ -53,7 +53,7 @@ points at `/data/musan//audio/musan__NNNNNN.wav` by default ## Running servers ```bash -config_paths="responses_api_models/vllm_model/configs/vllm_model.yaml,\ +config_paths="responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml,\ benchmarks/musan/config.yaml" ng_run "+config_paths=[$config_paths]" ``` diff --git a/benchmarks/nemotron_3_ultra/README.md b/benchmarks/nemotron_3_ultra/README.md index 7f95ce688c..e7eeae303a 100644 --- a/benchmarks/nemotron_3_ultra/README.md +++ b/benchmarks/nemotron_3_ultra/README.md @@ -28,7 +28,7 @@ tavily_search_resources_server: exclude_domains_file_path: ??? Qwen3-235B-A22B-Instruct-2507-FP8: responses_api_models: - vllm_model: + vllm_endpoint: model: ??? # The actual judge model needs to be Qwen/Qwen3-235B-A22B-Instruct-2507-FP8, but the name will probably differ based on endpoint. base_url: ??? api_key: ??? diff --git a/benchmarks/nemotron_3_ultra/judge_local_endpoints.yaml b/benchmarks/nemotron_3_ultra/judge_local_endpoints.yaml index 6741e3276a..65aa47c56e 100644 --- a/benchmarks/nemotron_3_ultra/judge_local_endpoints.yaml +++ b/benchmarks/nemotron_3_ultra/judge_local_endpoints.yaml @@ -1,12 +1,12 @@ config_paths: -- responses_api_models/local_vllm_model/configs/Qwen/Qwen3-235B-A22B-Instruct-2507-FP8.yaml +- responses_api_models/vllm_server/configs/Qwen/Qwen3-235B-A22B-Instruct-2507-FP8.yaml # Spin up judge models locally too # Used in browsecomp Qwen3-235B-A22B-Instruct-2507-FP8: responses_api_models: - _delete_key: vllm_model - local_vllm_model: + _delete_key: vllm_endpoint + vllm_server: vllm_serve_kwargs: data_parallel_size: 2 tensor_parallel_size: 4 @@ -14,7 +14,7 @@ Qwen3-235B-A22B-Instruct-2507-FP8: # Used in XSTest allenai_wildguard_model_server: responses_api_models: - local_vllm_model: + vllm_server: entrypoint: app.py model: allenai/wildguard return_token_id_information: false diff --git a/benchmarks/nemotron_3_ultra/judge_remote_endpoints.yaml b/benchmarks/nemotron_3_ultra/judge_remote_endpoints.yaml index 96ee2b7b87..64ed4b4f01 100644 --- a/benchmarks/nemotron_3_ultra/judge_remote_endpoints.yaml +++ b/benchmarks/nemotron_3_ultra/judge_remote_endpoints.yaml @@ -1,8 +1,8 @@ -# Refer to the params in responses_api_models/local_vllm_model/configs/Qwen/Qwen3-235B-A22B-Instruct-2507-FP8.yaml +# Refer to the params in responses_api_models/vllm_server/configs/Qwen/Qwen3-235B-A22B-Instruct-2507-FP8.yaml # Used in browsecomp Qwen3-235B-A22B-Instruct-2507-FP8: responses_api_models: - vllm_model: + vllm_endpoint: entrypoint: app.py model: ??? base_url: ??? @@ -14,8 +14,8 @@ Qwen3-235B-A22B-Instruct-2507-FP8: # Used in XSTest allenai_wildguard_model_server: responses_api_models: - _delete_key: local_vllm_model - vllm_model: + _delete_key: vllm_server + vllm_endpoint: entrypoint: app.py model: ??? base_url: ??? diff --git a/benchmarks/nemotron_3_ultra/ultra_local_endpoint.yaml b/benchmarks/nemotron_3_ultra/ultra_local_endpoint.yaml index dfe319117a..dd7516adfc 100644 --- a/benchmarks/nemotron_3_ultra/ultra_local_endpoint.yaml +++ b/benchmarks/nemotron_3_ultra/ultra_local_endpoint.yaml @@ -1,5 +1,5 @@ config_paths: -- responses_api_models/local_vllm_model/configs/nvidia/nemotron_3_ultra_dev_nemorl_gb200.yaml +- responses_api_models/vllm_server/configs/nvidia/nemotron_3_ultra_dev_nemorl_gb200.yaml # We bump up the connector per host limit since this is a larger benchmark suite global_aiohttp_connector_limit_per_host: 16384 diff --git a/benchmarks/nemotron_3_ultra/ultra_remote_endpoint.yaml b/benchmarks/nemotron_3_ultra/ultra_remote_endpoint.yaml index 30171547f4..793b888b43 100644 --- a/benchmarks/nemotron_3_ultra/ultra_remote_endpoint.yaml +++ b/benchmarks/nemotron_3_ultra/ultra_remote_endpoint.yaml @@ -1,5 +1,5 @@ config_paths: -- responses_api_models/vllm_model/configs/vllm_model.yaml +- responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml # We bump up the connector per host limit since this is a larger benchmark suite global_aiohttp_connector_limit_per_host: 16384 @@ -7,7 +7,7 @@ global_aiohttp_connector_limit_per_host: 16384 # We bump up the number of workers since we need higher concurrency for this larger benchmark suite policy_model: responses_api_models: - vllm_model: + vllm_endpoint: num_workers: 4 sequential_reasoning_allowed: false @@ -15,6 +15,6 @@ policy_model: policy_model_reasoning_off: _copy: policy_model responses_api_models: - vllm_model: + vllm_endpoint: chat_template_kwargs: enable_thinking: false diff --git a/benchmarks/numb3rs/README.md b/benchmarks/numb3rs/README.md index 206fea0aff..40e5b44d80 100644 --- a/benchmarks/numb3rs/README.md +++ b/benchmarks/numb3rs/README.md @@ -64,7 +64,7 @@ combined `benchmarks/numb3rs/data/numb3rs_benchmark.jsonl`. ## Running servers ```bash -config_paths="responses_api_models/vllm_model/configs/vllm_model.yaml,\ +config_paths="responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml,\ benchmarks/numb3rs/config.yaml" ng_run "+config_paths=[$config_paths]" ``` diff --git a/benchmarks/omniscience/README.md b/benchmarks/omniscience/README.md index 725375140e..f69a1fe1bb 100644 --- a/benchmarks/omniscience/README.md +++ b/benchmarks/omniscience/README.md @@ -14,7 +14,7 @@ ng_prepare_data +benchmark=omniscience # Run benchmark ng_collect_rollouts +benchmark=omniscience \ - "+config_paths=[responses_api_models/vllm_model/configs/vllm_model.yaml]" \ + "+config_paths=[responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml]" \ +output_jsonl_fpath=results/omniscience_rollouts.jsonl ``` diff --git a/benchmarks/physics/README.md b/benchmarks/physics/README.md index 99057c0e23..2b1804664b 100644 --- a/benchmarks/physics/README.md +++ b/benchmarks/physics/README.md @@ -24,7 +24,7 @@ transformation Skills uses for the multi-part answers, and writes ## Running servers ```bash -config_paths="responses_api_models/vllm_model/configs/vllm_model.yaml,\ +config_paths="responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml,\ benchmarks/physics/config.yaml" ng_run "+config_paths=[$config_paths]" ``` diff --git a/benchmarks/polymath/README.md b/benchmarks/polymath/README.md index d1606eab3a..5fdd36d0b0 100644 --- a/benchmarks/polymath/README.md +++ b/benchmarks/polymath/README.md @@ -50,7 +50,7 @@ Start the servers (inherits the `polymath` resources server in symbolic-only mode plus a vLLM model server): ``` -config_paths="responses_api_models/vllm_model/configs/vllm_model.yaml,\ +config_paths="responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml,\ benchmarks/polymath/config.yaml" ng_run "+config_paths=[$config_paths]" ``` diff --git a/benchmarks/proof-arena-judge/README.md b/benchmarks/proof-arena-judge/README.md index 19ea6e9456..0e83f16fe7 100644 --- a/benchmarks/proof-arena-judge/README.md +++ b/benchmarks/proof-arena-judge/README.md @@ -20,7 +20,7 @@ Adds the `proof-arena-judge` benchmark to Gym. ng_prepare_benchmark "+config_paths=[benchmarks/proof-arena-judge/config.yaml]" # Running servers -config_paths="responses_api_models/vllm_model/configs/vllm_model.yaml,\ +config_paths="responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml,\ benchmarks/proof-arena-judge/config.yaml" ng_run "+config_paths=[$config_paths]" diff --git a/benchmarks/proof_bench_judge/README.md b/benchmarks/proof_bench_judge/README.md index 0320517a22..43a997eec8 100644 --- a/benchmarks/proof_bench_judge/README.md +++ b/benchmarks/proof_bench_judge/README.md @@ -37,7 +37,7 @@ details. ng_prepare_benchmark "+config_paths=[benchmarks/proof_bench_judge/config.yaml]" # Running servers -config_paths="responses_api_models/vllm_model/configs/vllm_model.yaml,\ +config_paths="responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml,\ benchmarks/proof_bench_judge/config.yaml" ng_run "+config_paths=[$config_paths]" diff --git a/benchmarks/proofnet/README.md b/benchmarks/proofnet/README.md index cc80c352eb..cd3090c800 100644 --- a/benchmarks/proofnet/README.md +++ b/benchmarks/proofnet/README.md @@ -28,7 +28,7 @@ and set `NEMO_SKILLS_SANDBOX_HOST` / `NEMO_SKILLS_SANDBOX_PORT` before starting the server. ```bash -config_paths="responses_api_models/vllm_model/configs/vllm_model.yaml,\ +config_paths="responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml,\ benchmarks/proofnet/config.yaml" ng_run "+config_paths=[$config_paths]" ``` diff --git a/benchmarks/putnam_bench/README.md b/benchmarks/putnam_bench/README.md index 924b067a5d..78506f6c03 100644 --- a/benchmarks/putnam_bench/README.md +++ b/benchmarks/putnam_bench/README.md @@ -28,7 +28,7 @@ and set `NEMO_SKILLS_SANDBOX_HOST` / `NEMO_SKILLS_SANDBOX_PORT` before starting the server. ```bash -config_paths="responses_api_models/vllm_model/configs/vllm_model.yaml,\ +config_paths="responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml,\ benchmarks/putnam_bench/config.yaml" ng_run "+config_paths=[$config_paths]" ``` diff --git a/benchmarks/ruler/README.md b/benchmarks/ruler/README.md index 61a9257340..77d749e96c 100644 --- a/benchmarks/ruler/README.md +++ b/benchmarks/ruler/README.md @@ -11,7 +11,7 @@ ng_prepare_benchmark "+config_paths=[$config_paths]" # Run ```bash -config_paths="responses_api_models/vllm_model/configs/vllm_model.yaml,\ +config_paths="responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml,\ benchmarks/ruler/config_nemotron_3_256k.yaml" ng_e2e_collect_rollouts \ "+config_paths=[${config_paths}]" \ diff --git a/benchmarks/simpleqa/README.md b/benchmarks/simpleqa/README.md index 8bd24dde26..497c0aed9b 100644 --- a/benchmarks/simpleqa/README.md +++ b/benchmarks/simpleqa/README.md @@ -40,7 +40,7 @@ ng_prepare_benchmark "+config_paths=[benchmarks/simpleqa/config.yaml]" ## Running servers ```bash -config_paths="responses_api_models/vllm_model/configs/vllm_model.yaml,\ +config_paths="responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml,\ benchmarks/simpleqa/config.yaml" ng_run "+config_paths=[$config_paths]" ``` diff --git a/benchmarks/speed-bench/README.md b/benchmarks/speed-bench/README.md index fc7f68db90..8536ad5cdc 100644 --- a/benchmarks/speed-bench/README.md +++ b/benchmarks/speed-bench/README.md @@ -57,7 +57,7 @@ ng_prepare_benchmark "+config_paths=[benchmarks/speed-bench/config_qualitative.y # ngram speculative decoding into vllm_serve_kwargs.speculative_config. # To use a different target model, swap this for any local_vllm_model # config that includes a `speculative_config:` block. -config_paths="responses_api_models/local_vllm_model/configs/Qwen/Qwen3-30B-A3B-Instruct-2507-ngram-specdec.yaml,\ +config_paths="responses_api_models/vllm_server/configs/Qwen/Qwen3-30B-A3B-Instruct-2507-ngram-specdec.yaml,\ benchmarks/speed-bench/config_qualitative.yaml" ng_run "+config_paths=[$config_paths]" \ +policy_model=Qwen3-30B-A3B-Instruct-2507-ngram-specdec diff --git a/benchmarks/spider2_lite/README.md b/benchmarks/spider2_lite/README.md index cdcd7e03f3..3be2e59457 100644 --- a/benchmarks/spider2_lite/README.md +++ b/benchmarks/spider2_lite/README.md @@ -6,7 +6,7 @@ ng_prepare_benchmark "+config_paths=[$config_paths]" # Run ```bash -config_paths="responses_api_models/vllm_model/configs/vllm_model.yaml,\ +config_paths="responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml,\ benchmarks/spider2_lite/config.yaml" ng_e2e_collect_rollouts \ "+config_paths=[${config_paths}]" \ diff --git a/benchmarks/supergpqa/README.md b/benchmarks/supergpqa/README.md index ff3392f728..f520dc8376 100644 --- a/benchmarks/supergpqa/README.md +++ b/benchmarks/supergpqa/README.md @@ -17,7 +17,7 @@ server. ng_prepare_benchmark "+config_paths=[benchmarks/supergpqa/config.yaml]" # Running servers -config_paths="responses_api_models/vllm_model/configs/vllm_model.yaml,\ +config_paths="responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml,\ benchmarks/supergpqa/config.yaml" ng_run "+config_paths=[$config_paths]" diff --git a/benchmarks/ugphysics/README.md b/benchmarks/ugphysics/README.md index a620490c21..16e763791c 100644 --- a/benchmarks/ugphysics/README.md +++ b/benchmarks/ugphysics/README.md @@ -64,7 +64,7 @@ message. ng_prepare_benchmark "+config_paths=[benchmarks/ugphysics/config.yaml]" # Running servers -config_paths="responses_api_models/vllm_model/configs/vllm_model.yaml,\ +config_paths="responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml,\ benchmarks/ugphysics/config.yaml" ng_run "+config_paths=[$config_paths]" diff --git a/benchmarks/wmt24pp/README.md b/benchmarks/wmt24pp/README.md index 92022c3ecf..c744e149ad 100644 --- a/benchmarks/wmt24pp/README.md +++ b/benchmarks/wmt24pp/README.md @@ -32,7 +32,7 @@ runs disable COMET via Hydra override and rely on corpus-BLEU only; xCOMET scoring still works end-to-end on the cluster path: ```bash -config_paths="responses_api_models/vllm_model/configs/vllm_model.yaml,\ +config_paths="responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml,\ benchmarks/wmt24pp/config.yaml" ng_run "+config_paths=[$config_paths]" \ "++wmt24pp_wmt_translation_resources_server.resources_servers.wmt_translation.compute_comet=false" @@ -137,7 +137,7 @@ ns nemo_gym_rollouts \ --server_nodes 2 \ --server_args "--tensor-parallel-size 8 --data-parallel-size 1 --data-parallel-size-local 1 --data-parallel-backend ray --distributed-executor-backend ray --api-server-count 1 --reasoning-parser deepseek_r1 --trust-remote-code --dtype auto --enforce-eager" \ --model "$MODEL" \ - --config_paths "benchmarks/wmt24pp/config.yaml,responses_api_models/vllm_model/configs/vllm_model.yaml" \ + --config_paths "benchmarks/wmt24pp/config.yaml,responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml" \ --input_file benchmarks/wmt24pp/data/wmt24pp_benchmark.jsonl \ --output_dir /workspace/wmt24pp_smoke \ --expname wmt24pp_smoke \ diff --git a/benchmarks/xstest/README.md b/benchmarks/xstest/README.md index 0e8372cc26..e994362db6 100644 --- a/benchmarks/xstest/README.md +++ b/benchmarks/xstest/README.md @@ -9,7 +9,7 @@ ng_prepare_benchmark "+config_paths=[$config_paths]" # Run ```bash -config_paths="responses_api_models/vllm_model/configs/vllm_model.yaml,\ +config_paths="responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml,\ benchmarks/xstest/config.yaml" ng_e2e_collect_rollouts \ "+config_paths=[${config_paths}]" \ diff --git a/nemo_gym/model_server_rename.py b/nemo_gym/model_server_rename.py new file mode 100644 index 0000000000..6affe658da --- /dev/null +++ b/nemo_gym/model_server_rename.py @@ -0,0 +1,128 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Rewrite references to a renamed model server across configs and docs. + +Renaming a model server (RFC M6b / friction #9, e.g. ``vllm_model``→``vllm_endpoint`` or +``local_vllm_model``→``vllm_server``) touches references in many files. This tool rewrites the two +*textual* reference forms that are safe to transform mechanically: + +1. ``config_paths`` / dotted-override **directory paths**: ``responses_api_models//...`` + → ``responses_api_models//...``. +2. The server's **config key** under ``responses_api_models:`` — a line ``:`` whose + parent is ``responses_api_models:`` — renamed to ``:``. + +It deliberately does NOT touch Python imports (``from responses_api_models..app import ...``) +or move directories — those are done as part of the dir-move/compat-shim step, not by a text pass. +Run via ``python -m nemo_gym.model_server_rename [--dry-run]``. +""" + +import argparse +import re +import sys +from pathlib import Path +from typing import List, Tuple + + +_PARENT_KEY = "responses_api_models:" + + +def rename_references(text: str, old: str, new: str) -> Tuple[str, int]: + """Rewrite directory-path and config-key references from ``old`` to ``new`` in ``text``. + + Returns the rewritten text and the number of substitutions made. Path references + (``responses_api_models/``) are rewritten anywhere; the bare ``:`` key is rewritten + only when it is indented directly under a ``responses_api_models:`` line, so unrelated keys + that merely share the name are left alone. + """ + count = 0 + path_pattern = re.compile(rf"(responses_api_models/){re.escape(old)}(?=[/\"'\],\s]|$)") + + out_lines: List[str] = [] + parent_indent: int = -1 # indent of the most recent `responses_api_models:` line, else -1 + key_pattern = re.compile(rf"^(?P\s*){re.escape(old)}:\s*$") + # `_delete_key: ` removes an inherited server block by name (the key == the dir name), so it + # tracks the rename. Only rewritten when scoped directly under a `responses_api_models:` block. + delete_key_pattern = re.compile(rf"^(?P\s*)_delete_key:\s*{re.escape(old)}\s*$") + + for line in text.split("\n"): + new_line, n = path_pattern.subn(rf"\1{new}", line) + count += n + + stripped = new_line.strip() + indent = len(new_line) - len(new_line.lstrip()) + key_match = key_pattern.match(new_line) + delete_match = delete_key_pattern.match(new_line) + if key_match is not None and parent_indent >= 0 and indent == parent_indent + 2: + new_line = f"{key_match.group('indent')}{new}:" + count += 1 + elif delete_match is not None and parent_indent >= 0 and indent == parent_indent + 2: + new_line = f"{delete_match.group('indent')}_delete_key: {new}" + count += 1 + + # Track whether we're directly under a `responses_api_models:` block. + if stripped == _PARENT_KEY: + parent_indent = indent + elif stripped and indent <= parent_indent: + parent_indent = -1 + + out_lines.append(new_line) + + return "\n".join(out_lines), count + + +def rename_file(path: Path, old: str, new: str, dry_run: bool = False) -> int: + """Rewrite a single file in place. Returns the number of substitutions.""" + original = path.read_text() + rewritten, count = rename_references(original, old, new) + if count and not dry_run: + path.write_text(rewritten) + return count + + +def _iter_files(paths: List[Path]): # pragma: no cover + for path in paths: + if path.is_dir(): + for pattern in ("*.yaml", "*.yml", "*.md", "*.sh"): + yield from sorted(path.rglob(pattern)) + else: + yield path + + +def main() -> None: # pragma: no cover + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("old", help="Current model-server name, e.g. local_vllm_model") + parser.add_argument("new", help="New model-server name, e.g. vllm_server") + parser.add_argument("paths", nargs="+", type=Path, help="Files or directories to rewrite.") + parser.add_argument("--dry-run", action="store_true", help="Report what would change without writing.") + args = parser.parse_args() + + total_files = 0 + total = 0 + for path in _iter_files(args.paths): + n = rename_file(path, args.old, args.new, dry_run=args.dry_run) + if n: + total_files += 1 + total += n + verb = "would rewrite" if args.dry_run else "rewrote" + print(f"{verb} {n} ref(s): {path}") + + verb = "Would rewrite" if args.dry_run else "Rewrote" + print(f"{verb} {total} reference(s) across {total_files} file(s).") + if args.dry_run and total: + sys.exit(1) + + +if __name__ == "__main__": # pragma: no cover + main() diff --git a/resources_servers/arc_agi/README.md b/resources_servers/arc_agi/README.md index 08987a922b..0795f4fd41 100644 --- a/resources_servers/arc_agi/README.md +++ b/resources_servers/arc_agi/README.md @@ -51,7 +51,7 @@ uv sync ### Start ARC-AGI environment (we can reuse the same one for ARC-AGI-1 and 2): ```bash -ng_run "+config_paths=[resources_servers/arc_agi/configs/arc_agi.yaml,responses_api_models/vllm_model/configs/vllm_model.yaml]" +ng_run "+config_paths=[resources_servers/arc_agi/configs/arc_agi.yaml,responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml]" ``` diff --git a/resources_servers/arena_judge/README.md b/resources_servers/arena_judge/README.md index 83caf6521f..7bd619674e 100644 --- a/resources_servers/arena_judge/README.md +++ b/resources_servers/arena_judge/README.md @@ -47,7 +47,7 @@ Each JSONL row must carry the following top-level fields (pydantic ```bash # Running servers -config_paths="responses_api_models/vllm_model/configs/vllm_model.yaml,\ +config_paths="responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml,\ resources_servers/arena_judge/configs/arena_judge.yaml" ng_run "+config_paths=[$config_paths]" diff --git a/resources_servers/asr_with_pc/README.md b/resources_servers/asr_with_pc/README.md index 87430f1115..e9e5bc425b 100644 --- a/resources_servers/asr_with_pc/README.md +++ b/resources_servers/asr_with_pc/README.md @@ -50,7 +50,7 @@ workaround until the schema is extended. ## Running servers ```bash -config_paths="responses_api_models/vllm_model/configs/vllm_model.yaml,\ +config_paths="responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml,\ resources_servers/asr_with_pc/configs/asr_with_pc.yaml" ng_run "+config_paths=[$config_paths]" ``` diff --git a/resources_servers/aviary/README.md b/resources_servers/aviary/README.md index 83307c67de..83a0deafce 100644 --- a/resources_servers/aviary/README.md +++ b/resources_servers/aviary/README.md @@ -23,7 +23,7 @@ Run the GSM8K Aviary resources server together with a model config: ```bash config_paths="resources_servers/aviary/configs/gsm8k_aviary.yaml,\ -responses_api_models/vllm_model/configs/vllm_model.yaml" +responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml" ng_run "+config_paths=[$config_paths]" ``` @@ -49,7 +49,7 @@ Once the dataset server is running and is accessible at a specific URL, update y ```bash config_paths="resources_servers/aviary/configs/bbh_remote.yaml,\ -responses_api_models/vllm_model/configs/vllm_model.yaml" +responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml" ng_run "+config_paths=[$config_paths]" ``` @@ -74,7 +74,7 @@ env: nemo_gym: # This is passed into NeMo-Gym as the initial_global_config_dict is_trajectory_collection: false # Set this to true to enable trajectory collection (no training). You may also want to increase `policy.generation.vllm_cfg.gpu_memory_utilization` config_paths: - - responses_api_models/vllm_model/configs/vllm_model_for_training.yaml # Required! And it must be *for_training + - responses_api_models/vllm_endpoint/configs/vllm_model_for_training.yaml # Required! And it must be *for_training - resources_servers/aviary/configs/bbh_remote.yaml ``` @@ -99,7 +99,7 @@ cd /path/to/gym/directory And then bring up NeMo-Gym: ```bash config_paths="resources_servers/aviary/configs/bbh_bundled.yaml,\ -responses_api_models/vllm_model/configs/vllm_model.yaml" +responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml" ng_run "+config_paths=[$config_paths]" ``` ```bash @@ -160,7 +160,7 @@ env: nemo_gym: # This is passed into NeMo-Gym as the initial_global_config_dict is_trajectory_collection: false # Set this to true to enable trajectory collection (no training). You may also want to increase `policy.generation.vllm_cfg.gpu_memory_utilization` config_paths: - - responses_api_models/vllm_model/configs/vllm_model_for_training.yaml # Required! And it must be *for_training + - responses_api_models/vllm_endpoint/configs/vllm_model_for_training.yaml # Required! And it must be *for_training - resources_servers/aviary/configs/bbh_bundled.yaml ``` Following this, you can export `SETUP_COMMAND` to a snippet installing enroot prior to launching your `ray.sub` command: diff --git a/resources_servers/bigcodebench/README.md b/resources_servers/bigcodebench/README.md index 7a177cd7ef..175d0c683d 100644 --- a/resources_servers/bigcodebench/README.md +++ b/resources_servers/bigcodebench/README.md @@ -23,7 +23,7 @@ starts are instant. ```bash # Running servers -config_paths="responses_api_models/vllm_model/configs/vllm_model.yaml,\ +config_paths="responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml,\ resources_servers/bigcodebench/configs/bigcodebench.yaml" ng_run "+config_paths=[$config_paths]" diff --git a/resources_servers/bird_sql/README.md b/resources_servers/bird_sql/README.md index 8921249e3a..f51d574430 100644 --- a/resources_servers/bird_sql/README.md +++ b/resources_servers/bird_sql/README.md @@ -34,7 +34,7 @@ ensure_bird_sql() ### Running servers ```bash -config_paths="responses_api_models/vllm_model/configs/vllm_model.yaml,\ +config_paths="responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml,\ resources_servers/bird_sql/configs/bird_sql.yaml" ng_run "+config_paths=[$config_paths]" ``` diff --git a/resources_servers/blackjack/README.md b/resources_servers/blackjack/README.md index 54fbff84ad..c757c4c814 100644 --- a/resources_servers/blackjack/README.md +++ b/resources_servers/blackjack/README.md @@ -9,7 +9,7 @@ Example data provided in `data/example.jsonl` (system prompt only, no verifier_m ## Run ```bash -ng_run "+config_paths=[resources_servers/blackjack/configs/blackjack.yaml,responses_api_models/vllm_model/configs/vllm_model.yaml]" +ng_run "+config_paths=[resources_servers/blackjack/configs/blackjack.yaml,responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml]" ``` ## Data diff --git a/resources_servers/browsecomp_advanced_harness/README.md b/resources_servers/browsecomp_advanced_harness/README.md index 8b2917f32e..11f0365fe1 100644 --- a/resources_servers/browsecomp_advanced_harness/README.md +++ b/resources_servers/browsecomp_advanced_harness/README.md @@ -30,7 +30,7 @@ judge_model_name: Qwen/Qwen3-235B-A22B-Instruct-2507 ```bash # If you want to run with browsecomp benchmark instead of the example samples, need to change the datasets part to the one like `benchmarks/browsecomp/config.yaml` in `resources_servers/browsecomp_advanced_harness/configs/browsecomp_advanced_harness.yaml` config_paths="resources_servers/browsecomp_advanced_harness/configs/browsecomp_advanced_harness.yaml,\ -responses_api_models/vllm_model/configs/vllm_model.yaml" +responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml" ng_run "+config_paths=[${config_paths}]" ``` diff --git a/resources_servers/browsecomp_advanced_harness/configs/browsecomp_advanced_harness.yaml b/resources_servers/browsecomp_advanced_harness/configs/browsecomp_advanced_harness.yaml index 3cd61957ae..3ede58f162 100644 --- a/resources_servers/browsecomp_advanced_harness/configs/browsecomp_advanced_harness.yaml +++ b/resources_servers/browsecomp_advanced_harness/configs/browsecomp_advanced_harness.yaml @@ -18,7 +18,7 @@ browsecomp_advanced_harness_resources_server: judge_model: responses_api_models: - vllm_model: + vllm_endpoint: entrypoint: app.py base_url: ${search_judge_model_base_url} api_key: ${search_judge_model_api_key} diff --git a/resources_servers/circle_click/README.md b/resources_servers/circle_click/README.md index 3fcfe8819f..2cd400ebd7 100644 --- a/resources_servers/circle_click/README.md +++ b/resources_servers/circle_click/README.md @@ -12,7 +12,7 @@ policy_model_name: Qwen/Qwen3-VL-8B-Instruct ```bash vllm serve Qwen/Qwen3-VL-8B-Instruct -tp 8 --enable-auto-tool-choice --tool-call-parser hermes & -ng_run "+config_paths=[resources_servers/circle_click/configs/circle_click.yaml,responses_api_models/vllm_model/configs/vllm_model.yaml]" & +ng_run "+config_paths=[resources_servers/circle_click/configs/circle_click.yaml,responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml]" & ng_collect_rollouts +agent_name=circle_click_simple_agent +input_jsonl_fpath=resources_servers/circle_click/data/example.jsonl +output_jsonl_fpath=resources_servers/circle_click/data/example_rollouts.jsonl +limit=1 ``` diff --git a/resources_servers/circle_count/README.md b/resources_servers/circle_count/README.md index 5d567b04dc..39533ff660 100644 --- a/resources_servers/circle_count/README.md +++ b/resources_servers/circle_count/README.md @@ -19,7 +19,7 @@ policy_model_name: Qwen/Qwen3-VL-8B-Instruct ```bash vllm serve Qwen/Qwen3-VL-8B-Instruct -tp 8 --enable-auto-tool-choice --tool-call-parser hermes & -ng_run "+config_paths=[resources_servers/circle_count/configs/circle_count.yaml,responses_api_models/vllm_model/configs/vllm_model.yaml]" & +ng_run "+config_paths=[resources_servers/circle_count/configs/circle_count.yaml,responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml]" & ng_collect_rollouts +agent_name=circle_count_simple_agent +input_jsonl_fpath=resources_servers/circle_count/data/example.jsonl +output_jsonl_fpath=resources_servers/circle_count/data/example_rollouts.jsonl +limit=1 ``` diff --git a/resources_servers/code_fim/README.md b/resources_servers/code_fim/README.md index 809548160e..c04b0035c6 100644 --- a/resources_servers/code_fim/README.md +++ b/resources_servers/code_fim/README.md @@ -58,7 +58,7 @@ the reward; pass@k / majority@k are computed by the metrics layer. ```bash # Running servers -config_paths="responses_api_models/vllm_model/configs/vllm_model.yaml,\ +config_paths="responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml,\ resources_servers/code_fim/configs/code_fim.yaml" ng_run "+config_paths=[$config_paths]" diff --git a/resources_servers/competitive_coding_challenges/README.md b/resources_servers/competitive_coding_challenges/README.md index ade3fcf957..394bc5a6e7 100644 --- a/resources_servers/competitive_coding_challenges/README.md +++ b/resources_servers/competitive_coding_challenges/README.md @@ -13,7 +13,7 @@ Example override from NeMo RL: ```bash uv run python examples/nemo_gym/run_grpo_nemo_gym.py --config path/to/grpo.yaml \ - env.nemo_gym.config_paths='[responses_api_models/vllm_model/configs/vllm_model_for_training.yaml,resources_servers/competitive_coding_challenges/configs/competitive_coding_challenges.yaml,responses_api_agents/simple_agent/configs/simple_agent.yaml]' + env.nemo_gym.config_paths='[responses_api_models/vllm_endpoint/configs/vllm_model_for_training.yaml,resources_servers/competitive_coding_challenges/configs/competitive_coding_challenges.yaml,responses_api_agents/simple_agent/configs/simple_agent.yaml]' ``` Please see the resource server yaml for settings that can be modified such as: diff --git a/resources_servers/cvdp/README.md b/resources_servers/cvdp/README.md index 67db483f47..551f7e250b 100644 --- a/resources_servers/cvdp/README.md +++ b/resources_servers/cvdp/README.md @@ -123,7 +123,7 @@ apt install -y ./apptainer_1.3.1_amd64.deb ### Step 1 — Start servers ```bash -ng_run "+config_paths=[resources_servers/cvdp/configs/cvdp.yaml,responses_api_models/vllm_model/configs/vllm_model.yaml]" +ng_run "+config_paths=[resources_servers/cvdp/configs/cvdp.yaml,responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml]" ``` ### Step 2 — Run rollout collection @@ -138,7 +138,7 @@ ng_collect_rollouts \ +num_repeats=5 \ +num_samples_in_parallel=4 \ "+responses_create_params={max_output_tokens: 4096, temperature: 0.2, top_p: 0.7}" \ - "+config_paths=[resources_servers/cvdp/configs/cvdp.yaml,responses_api_models/vllm_model/configs/vllm_model.yaml]" + "+config_paths=[resources_servers/cvdp/configs/cvdp.yaml,responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml]" "+resume_from_cache=True" ``` diff --git a/resources_servers/ether0/README.md b/resources_servers/ether0/README.md index f84ab52ccf..3893e0808a 100644 --- a/resources_servers/ether0/README.md +++ b/resources_servers/ether0/README.md @@ -27,7 +27,7 @@ Start servers and collect rollouts ```bash # start vllm and nemo gym servers vllm serve futurehouse/ether0 & -ng_run "+config_paths=[resources_servers/ether0/configs/ether0.yaml,responses_api_models/vllm_model/configs/vllm_model.yaml]" & +ng_run "+config_paths=[resources_servers/ether0/configs/ether0.yaml,responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml]" & # wait for above to be ready ng_collect_rollouts \ diff --git a/resources_servers/evalplus/README.md b/resources_servers/evalplus/README.md index d7b6186b00..e9da1a90a2 100644 --- a/resources_servers/evalplus/README.md +++ b/resources_servers/evalplus/README.md @@ -46,7 +46,7 @@ compute pass@k for each separately. ```bash # Running servers -config_paths="responses_api_models/vllm_model/configs/vllm_model.yaml,\ +config_paths="responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml,\ resources_servers/evalplus/configs/evalplus.yaml" ng_run "+config_paths=[$config_paths]" diff --git a/resources_servers/example_multi_turn_gymnasium/README.md b/resources_servers/example_multi_turn_gymnasium/README.md index 68d1930157..ba16e12151 100644 --- a/resources_servers/example_multi_turn_gymnasium/README.md +++ b/resources_servers/example_multi_turn_gymnasium/README.md @@ -15,7 +15,7 @@ Example data provided in `data/example.jsonl`. ## Run ```bash -ng_run "+config_paths=[resources_servers/example_multi_turn_gymnasium/configs/example_multi_turn_gymnasium.yaml,responses_api_models/vllm_model/configs/vllm_model.yaml]" +ng_run "+config_paths=[resources_servers/example_multi_turn_gymnasium/configs/example_multi_turn_gymnasium.yaml,responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml]" ``` ## Collect rollouts diff --git a/resources_servers/finance_sec_search/README.md b/resources_servers/finance_sec_search/README.md index 6c7afdb064..d6bacc9425 100644 --- a/resources_servers/finance_sec_search/README.md +++ b/resources_servers/finance_sec_search/README.md @@ -194,7 +194,7 @@ Launch a vLLM-compatible model server (e.g. Qwen3-30B-A3B) so the policy and jud With a local vLLM model server: ```bash -config_paths="responses_api_models/vllm_model/configs/vllm_model.yaml,resources_servers/finance_sec_search/configs/finance_sec_search.yaml" +config_paths="responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml,resources_servers/finance_sec_search/configs/finance_sec_search.yaml" ng_run "+config_paths=[$config_paths]" ``` diff --git a/resources_servers/format_verification/README.md b/resources_servers/format_verification/README.md index 978983216f..c8cda07d42 100644 --- a/resources_servers/format_verification/README.md +++ b/resources_servers/format_verification/README.md @@ -62,7 +62,7 @@ ng_collect_rollouts \ ### Freeform Formatting ```bash -config_paths="responses_api_models/vllm_model/configs/vllm_model_for_training.yaml,\ +config_paths="responses_api_models/vllm_endpoint/configs/vllm_model_for_training.yaml,\ resources_servers/format_verification/configs/freeform_formatting.yaml" ng_prepare_data "+config_paths=[${config_paths}]" \ +output_dirpath=data/format_verification_freeform/ \ @@ -72,7 +72,7 @@ ng_prepare_data "+config_paths=[${config_paths}]" \ ### Citation Format ```bash -config_paths="responses_api_models/vllm_model/configs/vllm_model_for_training.yaml,\ +config_paths="responses_api_models/vllm_endpoint/configs/vllm_model_for_training.yaml,\ resources_servers/format_verification/configs/citation_format.yaml" ng_prepare_data "+config_paths=[${config_paths}]" \ +output_dirpath=data/format_verification_citation/ \ diff --git a/resources_servers/frontierscience_judge/README.md b/resources_servers/frontierscience_judge/README.md index a335ff5ddc..4cd6b3a2e4 100644 --- a/resources_servers/frontierscience_judge/README.md +++ b/resources_servers/frontierscience_judge/README.md @@ -24,7 +24,7 @@ judge's full text). ```bash # Running servers -config_paths="responses_api_models/vllm_model/configs/vllm_model.yaml,\ +config_paths="responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml,\ resources_servers/frontierscience_judge/configs/frontierscience_judge.yaml" ng_run "+config_paths=[$config_paths]" diff --git a/resources_servers/gdpval/README.md b/resources_servers/gdpval/README.md index a82cd07ecd..16fd272325 100644 --- a/resources_servers/gdpval/README.md +++ b/resources_servers/gdpval/README.md @@ -15,7 +15,7 @@ Canonical entry point is the benchmark at `benchmarks/gdpval/`: ```bash ng_prepare_benchmark "+config_paths=[benchmarks/gdpval/config.yaml]" ng_e2e_collect_rollouts \ - "+config_paths=[responses_api_models/vllm_model/configs/vllm_model.yaml,benchmarks/gdpval/config.yaml]" \ + "+config_paths=[responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml,benchmarks/gdpval/config.yaml]" \ ++split=benchmark ``` diff --git a/resources_servers/genrm_compare/README.md b/resources_servers/genrm_compare/README.md index 0cd8c0cd86..b18c230710 100644 --- a/resources_servers/genrm_compare/README.md +++ b/resources_servers/genrm_compare/README.md @@ -288,7 +288,7 @@ See `examples/genrm_grpo_example.yaml` for complete configuration. - **GenRM Model**: `responses_api_models/genrm_model/` - Response API model with custom roles (`response_1`, `response_2`, `principle`); default config uses server name `genrm_model` (local vLLM) - **Comparison Strategies**: `comparison_strategies.py` (in this package) - Strategy infrastructure -- **Base VLLM Model**: `responses_api_models/vllm_model/` - Generic model (unchanged) +- **Base VLLM Model**: `responses_api_models/vllm_endpoint/` - Generic model (unchanged) - **Type Definitions**: `nemo_gym/openai_utils.py` - Custom role type support - **Rollout Collection**: `nemo_gym/rollout_collection.py` - Integrates comparison strategies - **Design Doc**: `docs/design_notes/genrm_reward_model_refactoring.md` diff --git a/resources_servers/gpqa_diamond/README.md b/resources_servers/gpqa_diamond/README.md index b039946429..f494e75c80 100644 --- a/resources_servers/gpqa_diamond/README.md +++ b/resources_servers/gpqa_diamond/README.md @@ -1,116 +1,116 @@ -# GPQA-Diamond Resources Server - -## Overview - -This resources server evaluates GPQA-Diamond multiple-choice responses with a -GPQA-specific verifier built on top of `resources_servers/mcqa`. - -- Task type: single-turn MCQ -- Domain: `knowledge` -- Dataset prompt format: final line `Answer: LETTER` -- Also accepted by the verifier: `\boxed{X}` and custom regex extraction via - `template_metadata.output_regex` -- Grading mode: `strict_single_letter_boxed` - -## Server Composition - -Use GPQA-Diamond with: - -- `responses_api_agents/simple_agent` -- `responses_api_models/*` (typically `policy_model`) -- `resources_servers/gpqa_diamond` - -The server verifies the model response and returns reward `1.0` for exact -letter match against `expected_answer`, else `0.0`. - -Answer extraction priority is: - -1. `template_metadata.output_regex` if present -2. GPQA fallback parsing of the final answer from `\boxed{...}` or `Answer: X` - -## Dataset Format - -Each JSONL row follows the MCQA request schema: - -- `responses_create_params.input[0].content`: user prompt containing question + options -- `options`: list of letter-to-text maps, e.g. `[{"A": "..."}, {"B": "..."}]` -- `expected_answer`: one of `A/B/C/D` -- `grading_mode`: `strict_single_letter_boxed` -- `template_metadata`: optional per-row metadata, including optional `output_regex` -- `metadata`: passthrough metadata (`explanation`, `subset_for_metrics`, `difficulty`) -- `uuid`: unique row id - -See `data/example.jsonl` for concrete examples. - -Notes: - -- The generated GPQA prompt asks the model to finish with `Answer: LETTER`. -- Although dataset rows keep `grading_mode: strict_single_letter_boxed` for - compatibility with the shared MCQA schema, the GPQA server's custom fallback - parser accepts both `Answer: X` and boxed final answers. - -## Preprocessing Raw GPQA-Diamond - -Full train data is not stored in this repo. - -`dataset_preprocess.py` downloads the GPQA Diamond split from -`Idavidrein/gpqa` on HuggingFace, writes a normalized raw dump, and then writes -the Gym-formatted training set into `data/`. - -From the repository root: - -```bash -python3 resources_servers/gpqa_diamond/dataset_preprocess.py -``` - -This generates: - -- `resources_servers/gpqa_diamond/data/diamond_raw.jsonl` -- `resources_servers/gpqa_diamond/data/train.jsonl` - -`data/example.jsonl` is a curated repo artifact and is not modified by the -preprocess script. There is currently no `validation.jsonl` for this resources -server. - -## Example Usage - -Using a local Nemotron 3 model with `local_vllm_model`: - -```bash -config_paths="responses_api_agents/simple_agent/configs/simple_agent.yaml,responses_api_models/local_vllm_model/configs/nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B-BF16.yaml,resources_servers/gpqa_diamond/configs/gpqa_diamond.yaml" -ng_run "+config_paths=[${config_paths}]" \ - '++policy_model=${inherit_from:NVIDIA-Nemotron-3-Nano-30B-A3B-BF16}' \ - +simple_agent.responses_api_agents.simple_agent.resources_server.name=gpqa_diamond \ - "++NVIDIA-Nemotron-3-Nano-30B-A3B-BF16.responses_api_models.local_vllm_model.vllm_serve_kwargs.mamba_ssm_cache_dtype=float32" \ - "++NVIDIA-Nemotron-3-Nano-30B-A3B-BF16.responses_api_models.local_vllm_model.vllm_serve_kwargs.enable_prefix_caching=False" -``` - -Generic example with `openai_model`: - -```bash -config_paths="responses_api_agents/simple_agent/configs/simple_agent.yaml,\ -responses_api_models/openai_model/configs/openai_model.yaml,\ -resources_servers/gpqa_diamond/configs/gpqa_diamond.yaml" - -ng_run "+config_paths=[$config_paths]" \ - +simple_agent.responses_api_agents.simple_agent.resources_server.name=gpqa_diamond - -ng_collect_rollouts \ - +agent_name=simple_agent \ - +input_jsonl_fpath=resources_servers/gpqa_diamond/data/example.jsonl \ - +output_jsonl_fpath=resources_servers/gpqa_diamond/data/example_rollouts.jsonl \ - +limit=3 -``` - -`ng_collect_rollouts` also writes sidecar files next to `output_jsonl_fpath`, matching -the same pattern as `test_rollouts*`: - -- `*_materialized_inputs.jsonl` -- `*_reward_profiling.jsonl` -- `*_agent_metrics.json` - -## Licensing - -Code: Apache 2.0 -Configured train dataset license metadata: MIT - +# GPQA-Diamond Resources Server + +## Overview + +This resources server evaluates GPQA-Diamond multiple-choice responses with a +GPQA-specific verifier built on top of `resources_servers/mcqa`. + +- Task type: single-turn MCQ +- Domain: `knowledge` +- Dataset prompt format: final line `Answer: LETTER` +- Also accepted by the verifier: `\boxed{X}` and custom regex extraction via + `template_metadata.output_regex` +- Grading mode: `strict_single_letter_boxed` + +## Server Composition + +Use GPQA-Diamond with: + +- `responses_api_agents/simple_agent` +- `responses_api_models/*` (typically `policy_model`) +- `resources_servers/gpqa_diamond` + +The server verifies the model response and returns reward `1.0` for exact +letter match against `expected_answer`, else `0.0`. + +Answer extraction priority is: + +1. `template_metadata.output_regex` if present +2. GPQA fallback parsing of the final answer from `\boxed{...}` or `Answer: X` + +## Dataset Format + +Each JSONL row follows the MCQA request schema: + +- `responses_create_params.input[0].content`: user prompt containing question + options +- `options`: list of letter-to-text maps, e.g. `[{"A": "..."}, {"B": "..."}]` +- `expected_answer`: one of `A/B/C/D` +- `grading_mode`: `strict_single_letter_boxed` +- `template_metadata`: optional per-row metadata, including optional `output_regex` +- `metadata`: passthrough metadata (`explanation`, `subset_for_metrics`, `difficulty`) +- `uuid`: unique row id + +See `data/example.jsonl` for concrete examples. + +Notes: + +- The generated GPQA prompt asks the model to finish with `Answer: LETTER`. +- Although dataset rows keep `grading_mode: strict_single_letter_boxed` for + compatibility with the shared MCQA schema, the GPQA server's custom fallback + parser accepts both `Answer: X` and boxed final answers. + +## Preprocessing Raw GPQA-Diamond + +Full train data is not stored in this repo. + +`dataset_preprocess.py` downloads the GPQA Diamond split from +`Idavidrein/gpqa` on HuggingFace, writes a normalized raw dump, and then writes +the Gym-formatted training set into `data/`. + +From the repository root: + +```bash +python3 resources_servers/gpqa_diamond/dataset_preprocess.py +``` + +This generates: + +- `resources_servers/gpqa_diamond/data/diamond_raw.jsonl` +- `resources_servers/gpqa_diamond/data/train.jsonl` + +`data/example.jsonl` is a curated repo artifact and is not modified by the +preprocess script. There is currently no `validation.jsonl` for this resources +server. + +## Example Usage + +Using a local Nemotron 3 model with `local_vllm_model`: + +```bash +config_paths="responses_api_agents/simple_agent/configs/simple_agent.yaml,responses_api_models/vllm_server/configs/nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B-BF16.yaml,resources_servers/gpqa_diamond/configs/gpqa_diamond.yaml" +ng_run "+config_paths=[${config_paths}]" \ + '++policy_model=${inherit_from:NVIDIA-Nemotron-3-Nano-30B-A3B-BF16}' \ + +simple_agent.responses_api_agents.simple_agent.resources_server.name=gpqa_diamond \ + "++NVIDIA-Nemotron-3-Nano-30B-A3B-BF16.responses_api_models.local_vllm_model.vllm_serve_kwargs.mamba_ssm_cache_dtype=float32" \ + "++NVIDIA-Nemotron-3-Nano-30B-A3B-BF16.responses_api_models.local_vllm_model.vllm_serve_kwargs.enable_prefix_caching=False" +``` + +Generic example with `openai_model`: + +```bash +config_paths="responses_api_agents/simple_agent/configs/simple_agent.yaml,\ +responses_api_models/openai_model/configs/openai_model.yaml,\ +resources_servers/gpqa_diamond/configs/gpqa_diamond.yaml" + +ng_run "+config_paths=[$config_paths]" \ + +simple_agent.responses_api_agents.simple_agent.resources_server.name=gpqa_diamond + +ng_collect_rollouts \ + +agent_name=simple_agent \ + +input_jsonl_fpath=resources_servers/gpqa_diamond/data/example.jsonl \ + +output_jsonl_fpath=resources_servers/gpqa_diamond/data/example_rollouts.jsonl \ + +limit=3 +``` + +`ng_collect_rollouts` also writes sidecar files next to `output_jsonl_fpath`, matching +the same pattern as `test_rollouts*`: + +- `*_materialized_inputs.jsonl` +- `*_reward_profiling.jsonl` +- `*_agent_metrics.json` + +## Licensing + +Code: Apache 2.0 +Configured train dataset license metadata: MIT + diff --git a/resources_servers/graphwalks/README.md b/resources_servers/graphwalks/README.md index 5f11f4de55..a76046785a 100644 --- a/resources_servers/graphwalks/README.md +++ b/resources_servers/graphwalks/README.md @@ -25,7 +25,7 @@ https://github.com/NVIDIA-NeMo/Skills/blob/main/nemo_skills/evaluation/evaluator ## Start environment ```bash -ng_run "+config_paths=[resources_servers/graphwalks/configs/graphwalks.yaml,responses_api_models/vllm_model/configs/vllm_model.yaml]" +ng_run "+config_paths=[resources_servers/graphwalks/configs/graphwalks.yaml,responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml]" ``` ## Collect example rollouts diff --git a/resources_servers/imo_gradingbench/README.md b/resources_servers/imo_gradingbench/README.md index 18a5e910db..6874b63fd5 100644 --- a/resources_servers/imo_gradingbench/README.md +++ b/resources_servers/imo_gradingbench/README.md @@ -68,7 +68,7 @@ grade. ```bash # Running servers -config_paths="responses_api_models/vllm_model/configs/vllm_model.yaml,\ +config_paths="responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml,\ resources_servers/imo_gradingbench/configs/imo_gradingbench.yaml" ng_run "+config_paths=[$config_paths]" diff --git a/resources_servers/imo_proofbench_judge/README.md b/resources_servers/imo_proofbench_judge/README.md index 37ee65a27a..566d8b77bf 100644 --- a/resources_servers/imo_proofbench_judge/README.md +++ b/resources_servers/imo_proofbench_judge/README.md @@ -22,7 +22,7 @@ Use ``benchmarks/imo_proofbench/`` for the IMO-ProofBench dataset. ## Running servers ```bash -config_paths="responses_api_models/vllm_model/configs/vllm_model.yaml,\ +config_paths="responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml,\ resources_servers/imo_proofbench_judge/configs/imo_proofbench_judge.yaml" ng_run "+config_paths=[$config_paths]" \ +judge_base_url=https://generativelanguage.googleapis.com/v1beta/openai \ diff --git a/resources_servers/indirect_prompt_injection/configs/nemotron_nano_2gpu.yaml b/resources_servers/indirect_prompt_injection/configs/nemotron_nano_2gpu.yaml index e8aab2e48a..d34540f339 100644 --- a/resources_servers/indirect_prompt_injection/configs/nemotron_nano_2gpu.yaml +++ b/resources_servers/indirect_prompt_injection/configs/nemotron_nano_2gpu.yaml @@ -1,6 +1,6 @@ policy_model: responses_api_models: - local_vllm_model: + vllm_server: entrypoint: app.py model: nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B-BF16 return_token_id_information: false diff --git a/resources_servers/inverse_if/README.md b/resources_servers/inverse_if/README.md index 3bcdf63bcd..b0885a2f33 100644 --- a/resources_servers/inverse_if/README.md +++ b/resources_servers/inverse_if/README.md @@ -9,7 +9,7 @@ Evaluates model responses on the **Inverse IF** (Instruction Following) benchmar ng_test +entrypoint=resources_servers/inverse_if # 2. Start servers (in terminal 1) -config_paths="resources_servers/inverse_if/configs/inverse_if.yaml,responses_api_models/vllm_model/configs/vllm_model.yaml" +config_paths="resources_servers/inverse_if/configs/inverse_if.yaml,responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml" ng_run "+config_paths=[${config_paths}]" # 3. Collect rollouts on example data (in terminal 2) @@ -117,7 +117,7 @@ Tests cover: 1. **Start servers**: ```bash - config_paths="resources_servers/inverse_if/configs/inverse_if.yaml,responses_api_models/vllm_model/configs/vllm_model.yaml" + config_paths="resources_servers/inverse_if/configs/inverse_if.yaml,responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml" ng_run "+config_paths=[${config_paths}]" ``` diff --git a/resources_servers/jailbreak_detection/configs/safety_judge_model.yaml b/resources_servers/jailbreak_detection/configs/safety_judge_model.yaml index ba5c183935..4c6dc42bc5 100644 --- a/resources_servers/jailbreak_detection/configs/safety_judge_model.yaml +++ b/resources_servers/jailbreak_detection/configs/safety_judge_model.yaml @@ -1,6 +1,6 @@ safety_judge_model: responses_api_models: - vllm_model: + vllm_endpoint: entrypoint: app.py base_url: http://:/v1 api_key: diff --git a/resources_servers/labbench2_vlm/README.md b/resources_servers/labbench2_vlm/README.md index 426a8d4cf8..b74c2c33f4 100644 --- a/resources_servers/labbench2_vlm/README.md +++ b/resources_servers/labbench2_vlm/README.md @@ -154,7 +154,7 @@ python3 resources_servers/labbench2_vlm/prepare_data.py --example # also popula The judge model lives in a separate config (`configs/judge_model_openai.yaml`) so a user who wants a non-OpenAI judge (e.g. a local vLLM) can drop that file and supply their own `responses_api_models` instance named `judge_model` (see -`responses_api_models/vllm_model/configs/vllm_model.yaml` for the vLLM form). +`responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml` for the vLLM form). Credentials go in `env.yaml` at the **repository root** (the parser loads `$CWD/env.yaml` first, then falls back to `$PARENT_DIR/env.yaml`). The file diff --git a/resources_servers/longmt_eval/README.md b/resources_servers/longmt_eval/README.md index de5584b38e..d687e0bc2b 100644 --- a/resources_servers/longmt_eval/README.md +++ b/resources_servers/longmt_eval/README.md @@ -158,7 +158,7 @@ rollouts file. ```bash # Start servers (smoke-test mode — no GPU needed for the verifier) -ng_run "+config_paths=[resources_servers/longmt_eval/configs/longmt_eval.yaml,responses_api_models/vllm_model/configs/vllm_model.yaml]" \ +ng_run "+config_paths=[resources_servers/longmt_eval/configs/longmt_eval.yaml,responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml]" \ "++longmt_eval.resources_servers.longmt_eval.compute_segale=false" & # Collect rollouts — also writes results/longmt_eval_rollouts_materialized_inputs.jsonl diff --git a/resources_servers/math_proof_judgement/README.md b/resources_servers/math_proof_judgement/README.md index 3959e44044..bcc6801cdf 100644 --- a/resources_servers/math_proof_judgement/README.md +++ b/resources_servers/math_proof_judgement/README.md @@ -66,7 +66,7 @@ parser name that matches your model's reasoning tokens (see `vllm serve ```bash # Running servers -config_paths="responses_api_models/vllm_model/configs/vllm_model.yaml,\ +config_paths="responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml,\ resources_servers/math_proof_judgement/configs/math_proof_judgement.yaml" ng_run "+config_paths=[$config_paths]" diff --git a/resources_servers/math_with_autograder/README.md b/resources_servers/math_with_autograder/README.md index 100c400146..2445916920 100644 --- a/resources_servers/math_with_autograder/README.md +++ b/resources_servers/math_with_autograder/README.md @@ -35,7 +35,7 @@ existing math_with_judge consumers. ## Run servers ```bash -config_paths="responses_api_models/vllm_model/configs/vllm_model.yaml,resources_servers/math_with_autograder/configs/math_with_autograder.yaml,resources_servers/math_with_autograder/configs/judge_gptoss20b.yaml" +config_paths="responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml,resources_servers/math_with_autograder/configs/math_with_autograder.yaml,resources_servers/math_with_autograder/configs/judge_gptoss20b.yaml" ng_run "+config_paths=[$config_paths]" ``` diff --git a/resources_servers/math_with_judge/configs/math_with_local_judge.yaml b/resources_servers/math_with_judge/configs/math_with_local_judge.yaml index 54d6727c7e..0ec82a3b61 100644 --- a/resources_servers/math_with_judge/configs/math_with_local_judge.yaml +++ b/resources_servers/math_with_judge/configs/math_with_local_judge.yaml @@ -11,7 +11,7 @@ math_with_judge: domain: math qwen3_30ba3b_instruct_2507_model_server: responses_api_models: - local_vllm_model: + vllm_server: entrypoint: app.py model: Qwen/Qwen3-30B-A3B-Instruct-2507 return_token_id_information: false diff --git a/resources_servers/mrcr/README.md b/resources_servers/mrcr/README.md index d067f11789..ffff62f8bb 100644 --- a/resources_servers/mrcr/README.md +++ b/resources_servers/mrcr/README.md @@ -27,7 +27,7 @@ always fails. ## Start environment ```bash -ng_run "+config_paths=[resources_servers/mrcr/configs/mrcr.yaml,responses_api_models/vllm_model/configs/vllm_model.yaml]" +ng_run "+config_paths=[resources_servers/mrcr/configs/mrcr.yaml,responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml]" ``` ## Collect example rollouts diff --git a/resources_servers/multichallenge/README.md b/resources_servers/multichallenge/README.md index 6e877d9421..34ee17c34f 100644 --- a/resources_servers/multichallenge/README.md +++ b/resources_servers/multichallenge/README.md @@ -9,7 +9,7 @@ Evaluates model responses on the **MultiChallenge** benchmark using an LLM judge ng_test +entrypoint=resources_servers/multichallenge # 2. Start servers (in terminal 1) -config_paths="resources_servers/multichallenge/configs/multichallenge.yaml,responses_api_models/vllm_model/configs/vllm_model.yaml" +config_paths="resources_servers/multichallenge/configs/multichallenge.yaml,responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml" ng_run "+config_paths=[${config_paths}]" # 3. Collect rollouts on example data (in terminal 2) @@ -105,7 +105,7 @@ Tests cover: 1. **Start servers**: ```bash - config_paths="resources_servers/multichallenge/configs/multichallenge.yaml,responses_api_models/vllm_model/configs/vllm_model.yaml" + config_paths="resources_servers/multichallenge/configs/multichallenge.yaml,responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml" ng_run "+config_paths=[${config_paths}]" ``` diff --git a/resources_servers/newton_bench/README.md b/resources_servers/newton_bench/README.md index 75e6749a72..314c1d9f08 100644 --- a/resources_servers/newton_bench/README.md +++ b/resources_servers/newton_bench/README.md @@ -91,7 +91,7 @@ vllm serve \ ### Launch servers ```bash -config_paths="resources_servers/newton_bench/configs/newton_bench.yaml,responses_api_models/vllm_model/configs/vllm_model.yaml" +config_paths="resources_servers/newton_bench/configs/newton_bench.yaml,responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml" ng_run "+config_paths=[${config_paths}]" ``` diff --git a/resources_servers/ns_tools/README.md b/resources_servers/ns_tools/README.md index 571b764e92..442cf30ea2 100644 --- a/resources_servers/ns_tools/README.md +++ b/resources_servers/ns_tools/README.md @@ -10,7 +10,7 @@ The following are example commands for running this resources server with the si ```bash config_paths="resources_servers/ns_tools/configs/ns_tools.yaml, \ resources_servers/math_with_judge/configs/math_with_judge.yaml, \ -responses_api_models/vllm_model/configs/vllm_model.yaml" +responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml" ng_run "+config_paths=[$config_paths]" \ +policy_base_url="http://localhost:8000/v1" \ +policy_model_name="Qwen/Qwen3-8B" \ diff --git a/resources_servers/ns_tools/configs/ns_tools.yaml b/resources_servers/ns_tools/configs/ns_tools.yaml index 3f35ec1dc0..d8b492d531 100644 --- a/resources_servers/ns_tools/configs/ns_tools.yaml +++ b/resources_servers/ns_tools/configs/ns_tools.yaml @@ -6,7 +6,7 @@ # USAGE: # ng_run "+config_paths=[resources_servers/ns_tools/configs/ns_tools.yaml,\ # resources_servers/math_with_judge/configs/math_with_judge.yaml,\ -# responses_api_models/vllm_model/configs/vllm_model.yaml]" +# responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml]" ns_tools: resources_servers: diff --git a/resources_servers/physics_judge/README.md b/resources_servers/physics_judge/README.md index 9444dbe3ed..aae84a2ef0 100644 --- a/resources_servers/physics_judge/README.md +++ b/resources_servers/physics_judge/README.md @@ -32,7 +32,7 @@ existing math_with_judge consumers (`aime24`, `aime25`, `gsm8k`, ## Running servers ```bash -config_paths="responses_api_models/vllm_model/configs/vllm_model.yaml,\ +config_paths="responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml,\ resources_servers/physics_judge/configs/physics_judge.yaml,\ resources_servers/physics_judge/configs/judge_openai.yaml" ng_run "+config_paths=[$config_paths]" diff --git a/resources_servers/polymath/README.md b/resources_servers/polymath/README.md index 8bae6bd138..5d43a0a94c 100644 --- a/resources_servers/polymath/README.md +++ b/resources_servers/polymath/README.md @@ -30,7 +30,7 @@ PolyMath additions are at the metric-aggregation layer: ## Running servers ```bash -config_paths="responses_api_models/vllm_model/configs/vllm_model.yaml,\ +config_paths="responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml,\ resources_servers/polymath/configs/polymath.yaml" ng_run "+config_paths=[$config_paths]" ``` diff --git a/resources_servers/reasoning_gym/README.md b/resources_servers/reasoning_gym/README.md index 41ac8f2cc9..5386dbfe6f 100644 --- a/resources_servers/reasoning_gym/README.md +++ b/resources_servers/reasoning_gym/README.md @@ -82,7 +82,7 @@ policy_model_name: nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B-BF16 ## Launch nemo gym servers ```bash -ng_run "+config_paths=[resources_servers/reasoning_gym/configs/reasoning_gym.yaml,responses_api_models/vllm_model/configs/vllm_model.yaml]" +ng_run "+config_paths=[resources_servers/reasoning_gym/configs/reasoning_gym.yaml,responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml]" ``` ## Collect rollouts diff --git a/resources_servers/simpleqa/README.md b/resources_servers/simpleqa/README.md index 0267583de9..cbeb85320c 100644 --- a/resources_servers/simpleqa/README.md +++ b/resources_servers/simpleqa/README.md @@ -39,7 +39,7 @@ the reasoning trace is split off before the response reaches this server. ## Running servers ```bash -config_paths="responses_api_models/vllm_model/configs/vllm_model.yaml,\ +config_paths="responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml,\ resources_servers/simpleqa/configs/simpleqa.yaml" ng_run "+config_paths=[$config_paths]" \ +simpleqa.resources_servers.simpleqa.judge_model_server.name=policy_model diff --git a/resources_servers/speed_bench/README.md b/resources_servers/speed_bench/README.md index 132b6783cb..c078e0e894 100644 --- a/resources_servers/speed_bench/README.md +++ b/resources_servers/speed_bench/README.md @@ -86,7 +86,7 @@ records `spec_decode_unavailable: true` on every row and A ready-to-use demo config that bakes ngram speculative decoding into a `local_vllm_model` lives at -[`responses_api_models/local_vllm_model/configs/Qwen/Qwen3-30B-A3B-Instruct-2507-ngram-specdec.yaml`](../../responses_api_models/local_vllm_model/configs/Qwen/Qwen3-30B-A3B-Instruct-2507-ngram-specdec.yaml). +[`responses_api_models/vllm_server/configs/Qwen/Qwen3-30B-A3B-Instruct-2507-ngram-specdec.yaml`](../../responses_api_models/vllm_server/configs/Qwen/Qwen3-30B-A3B-Instruct-2507-ngram-specdec.yaml). The relevant block to copy into your own model config is: ```yaml @@ -107,7 +107,7 @@ For an EAGLE3 / MTP setup with a paired draft model, see ```bash # Running servers — uses the demo local_vllm_model config above (drop in # your own model config to swap targets; just keep the speculative_config block). -config_paths="responses_api_models/local_vllm_model/configs/Qwen/Qwen3-30B-A3B-Instruct-2507-ngram-specdec.yaml,\ +config_paths="responses_api_models/vllm_server/configs/Qwen/Qwen3-30B-A3B-Instruct-2507-ngram-specdec.yaml,\ resources_servers/speed_bench/configs/speed_bench.yaml,\ responses_api_agents/speed_bench_agent/configs/speed_bench_agent.yaml" ng_run "+config_paths=[$config_paths]" \ diff --git a/resources_servers/spider2_lite/scripts/run_eval.sh b/resources_servers/spider2_lite/scripts/run_eval.sh index e80e78c2b9..43ed0e40de 100755 --- a/resources_servers/spider2_lite/scripts/run_eval.sh +++ b/resources_servers/spider2_lite/scripts/run_eval.sh @@ -39,7 +39,7 @@ fi ROLLOUTS_OUT="results/${SLUG}_${LABEL}.jsonl" MATERIALIZED_OUT="results/${SLUG}_${LABEL}_materialized_inputs.jsonl" PROFILED_OUT="results/${SLUG}_${LABEL}_reward_profiling.jsonl" -CONFIGS="resources_servers/spider2_lite/configs/spider2_lite.yaml,responses_api_models/vllm_model/configs/vllm_model.yaml" +CONFIGS="resources_servers/spider2_lite/configs/spider2_lite.yaml,responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml" mkdir -p results diff --git a/resources_servers/structeval/README.md b/resources_servers/structeval/README.md index 6534ab7ab1..1548b0668d 100644 --- a/resources_servers/structeval/README.md +++ b/resources_servers/structeval/README.md @@ -28,7 +28,7 @@ reward = 0.2 * render_score + 0.8 * key_validation_score ### Running servers ```bash -config_paths="responses_api_models/vllm_model/configs/vllm_model.yaml,\ +config_paths="responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml,\ resources_servers/structeval/configs/structeval_nonrenderable.yaml" ng_run "+config_paths=[${config_paths}]" ``` diff --git a/resources_servers/structured_outputs/README.md b/resources_servers/structured_outputs/README.md index 50fa7d0c9e..76274039c7 100644 --- a/resources_servers/structured_outputs/README.md +++ b/resources_servers/structured_outputs/README.md @@ -74,7 +74,7 @@ For v4 tool-call structured outputs, use `structured_outputs_v4.yaml` and the v4 simple agent. The config routes through a non-executing agent because the emitted function call is the final answer, not an action to execute: ```bash -config_paths="responses_api_models/vllm_model/configs/vllm_model.yaml,\ +config_paths="responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml,\ resources_servers/structured_outputs/configs/structured_outputs_v4.yaml" ng_run "+config_paths=[${config_paths}]" ``` @@ -108,7 +108,7 @@ ng_prepare_data "+config_paths=[${config_paths}]" \ ### Version 2 [260310] (JSON, YAML, XML) ```bash # prepare -config_paths="responses_api_models/vllm_model/configs/vllm_model_for_training.yaml,\ +config_paths="responses_api_models/vllm_endpoint/configs/vllm_model_for_training.yaml,\ resources_servers/structured_outputs/configs/structured_outputs_json_yaml_xml_v1.yaml" ng_prepare_data "+config_paths=[${config_paths}]" \ +output_dirpath=data/structured_outputs/ \ @@ -118,7 +118,7 @@ ng_prepare_data "+config_paths=[${config_paths}]" \ ### Version 3 [260409] (JSON, YAML, XML, TOML, CSV) ```bash -config_paths="responses_api_models/vllm_model/configs/vllm_model_for_training.yaml,\ +config_paths="responses_api_models/vllm_endpoint/configs/vllm_model_for_training.yaml,\ resources_servers/structured_outputs/configs/structured_outputs_v3.yaml" ng_prepare_data "+config_paths=[${config_paths}]" \ +output_dirpath=data/structured_outputs_v3/ \ @@ -139,7 +139,7 @@ The uploaded GitLab dataset is: Prepare the v4 training data with: ```bash -config_paths="responses_api_models/vllm_model/configs/vllm_model_for_training.yaml,\ +config_paths="responses_api_models/vllm_endpoint/configs/vllm_model_for_training.yaml,\ resources_servers/structured_outputs/configs/structured_outputs_v4.yaml" ng_prepare_data "+config_paths=[${config_paths}]" \ +output_dirpath=data/structured_outputs_v4_tool_call/ \ diff --git a/resources_servers/structured_outputs/misc/data_generation/structured_outputs_v4/vllm-tool-schema-compatibility.md b/resources_servers/structured_outputs/misc/data_generation/structured_outputs_v4/vllm-tool-schema-compatibility.md index 244997d438..221e9283fa 100644 --- a/resources_servers/structured_outputs/misc/data_generation/structured_outputs_v4/vllm-tool-schema-compatibility.md +++ b/resources_servers/structured_outputs/misc/data_generation/structured_outputs_v4/vllm-tool-schema-compatibility.md @@ -12,7 +12,7 @@ that compatibility choice explicitly in the generated data. The v4 rows are intended to test tool-call behavior against the serving stack we actually run. If the data contains tool schemas that vanilla vLLM/Outlines cannot compile, rollout collection fails before the model can generate. Hiding -that in `responses_api_models/vllm_model/app.py` would make the shared wrapper +that in `responses_api_models/vllm_endpoint/app.py` would make the shared wrapper behave differently from vanilla vLLM and would affect unrelated environments. Some schemas can be valid JSON Schema, and even reasonable OpenAPI-style diff --git a/resources_servers/tavily_search/README.md b/resources_servers/tavily_search/README.md index b4edfa0497..35e473814b 100644 --- a/resources_servers/tavily_search/README.md +++ b/resources_servers/tavily_search/README.md @@ -40,7 +40,7 @@ ng_download_dataset_from_gitlab \ +output_fpath=resources_servers/tavily_search/data/sft_samples/sft_samples_validation.jsonl config_paths="resources_servers/tavily_search/configs/tavily_search_judge_vllm_model.yaml,\ -responses_api_models/vllm_model/configs/vllm_model.yaml" +responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml" ng_run "+config_paths=[${config_paths}]" ``` diff --git a/resources_servers/tavily_search/configs/tavily_search_judge_vllm_model.yaml b/resources_servers/tavily_search/configs/tavily_search_judge_vllm_model.yaml index d5b9d87544..560a081198 100644 --- a/resources_servers/tavily_search/configs/tavily_search_judge_vllm_model.yaml +++ b/resources_servers/tavily_search/configs/tavily_search_judge_vllm_model.yaml @@ -17,7 +17,7 @@ tavily_search_resources_server: value: Measure agentic search capability judge_model: responses_api_models: - vllm_model: + vllm_endpoint: entrypoint: app.py base_url: ${search_judge_model_base_url} api_key: ${search_judge_model_api_key} diff --git a/resources_servers/ugphysics_judge/README.md b/resources_servers/ugphysics_judge/README.md index 095d9bd2a2..80c0a015fa 100644 --- a/resources_servers/ugphysics_judge/README.md +++ b/resources_servers/ugphysics_judge/README.md @@ -58,7 +58,7 @@ swap it for any other `responses_api_models/*` config that exposes a ```bash # Running servers -config_paths="responses_api_models/vllm_model/configs/vllm_model.yaml,\ +config_paths="responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml,\ resources_servers/ugphysics_judge/configs/ugphysics_judge.yaml,\ benchmarks/ugphysics/judge_gptoss20b.yaml" ng_run "+config_paths=[$config_paths]" diff --git a/resources_servers/wmt_translation/README.md b/resources_servers/wmt_translation/README.md index a649cbb04b..ca8ab23fac 100644 --- a/resources_servers/wmt_translation/README.md +++ b/resources_servers/wmt_translation/README.md @@ -70,7 +70,7 @@ For an end-to-end SLURM run with COMET enabled, see the ```bash # Running servers (BLEU-only locally; flip compute_comet=true on cluster) -config_paths="responses_api_models/vllm_model/configs/vllm_model.yaml,\ +config_paths="responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml,\ resources_servers/wmt_translation/configs/wmt_translation.yaml" ng_run "+config_paths=[$config_paths]" \ "++wmt_translation.resources_servers.wmt_translation.compute_comet=false" diff --git a/resources_servers/xlam_fc/README.md b/resources_servers/xlam_fc/README.md index 06978e9718..05de319a32 100644 --- a/resources_servers/xlam_fc/README.md +++ b/resources_servers/xlam_fc/README.md @@ -9,7 +9,7 @@ python resources_servers/xlam_fc/generate_dataset.py ``` ```bash -config_paths="responses_api_models/vllm_model/configs/vllm_model.yaml,\ +config_paths="responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml,\ resources_servers/xlam_fc/configs/xlam_fc.yaml" ng_run "+config_paths=[$config_paths]" ``` diff --git a/resources_servers/xstest/README.md b/resources_servers/xstest/README.md index 11ef09bf23..38898cb0a3 100644 --- a/resources_servers/xstest/README.md +++ b/resources_servers/xstest/README.md @@ -100,7 +100,7 @@ contrast_privacy ### Example usage ```bash # For chat completions endpoints (vLLM, NIM, etc.): -ng_run "+config_paths=[resources_servers/xstest/configs/xstest.yaml,responses_api_models/vllm_model/configs/vllm_model.yaml]" +ng_run "+config_paths=[resources_servers/xstest/configs/xstest.yaml,responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml]" # For OpenAI Responses API endpoints: # ng_run "+config_paths=[resources_servers/xstest/configs/xstest.yaml,responses_api_models/openai_model/configs/openai_model.yaml]" diff --git a/resources_servers/xstest/configs/xstest.yaml b/resources_servers/xstest/configs/xstest.yaml index 60d016ea0c..4a310527e8 100644 --- a/resources_servers/xstest/configs/xstest.yaml +++ b/resources_servers/xstest/configs/xstest.yaml @@ -18,7 +18,7 @@ xstest_resources_server: judge_output_format: wildguard allenai_wildguard_model_server: responses_api_models: - local_vllm_model: + vllm_server: entrypoint: app.py model: allenai/wildguard return_token_id_information: false diff --git a/responses_api_agents/browsecomp_agent/app.py b/responses_api_agents/browsecomp_agent/app.py index b9a32efa50..26f445480e 100644 --- a/responses_api_agents/browsecomp_agent/app.py +++ b/responses_api_agents/browsecomp_agent/app.py @@ -55,7 +55,7 @@ NeMoGymResponseUsage, ) from nemo_gym.server_utils import get_response_json, raise_for_status -from responses_api_models.vllm_model.app import VLLMConverter +from responses_api_models.vllm_endpoint.app import VLLMConverter class BrowsecompAgentConfig(BaseResponsesAPIAgentConfig): diff --git a/responses_api_agents/harbor_agent/README.md b/responses_api_agents/harbor_agent/README.md index 46e62cdcd0..821329b0f9 100644 --- a/responses_api_agents/harbor_agent/README.md +++ b/responses_api_agents/harbor_agent/README.md @@ -161,7 +161,7 @@ Training config example: ```yaml policy_model: responses_api_models: - vllm_model: + vllm_endpoint: entrypoint: app.py base_url: ${policy_base_url} api_key: ${policy_api_key} @@ -201,7 +201,7 @@ Then start NeMo Gym: ```bash config_paths="responses_api_agents/harbor_agent/configs/harbor_agent.yaml,\ -responses_api_models/vllm_model/configs/vllm_model_for_training.yaml" +responses_api_models/vllm_endpoint/configs/vllm_model_for_training.yaml" ng_run "+config_paths=[${config_paths}]" ``` @@ -277,7 +277,7 @@ export DAYTONA_API_KEY= ``` Then add the policy model server settings to repo-root `env.yaml`, using the -same keys consumed by `responses_api_models/vllm_model/configs/vllm_model.yaml`: +same keys consumed by `responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml`: ```yaml policy_base_url: @@ -289,7 +289,7 @@ Then follow the same Harbor-agent workflow with the Daytona config: ```bash config_paths="responses_api_agents/harbor_agent/configs/harbor_agent_daytona.yaml,\ -responses_api_models/vllm_model/configs/vllm_model.yaml" +responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml" ng_run "+config_paths=[${config_paths}]" ``` @@ -370,7 +370,7 @@ without involving NeMo Gym's model-server routing. Pass `chat_template_kwargs` to the tokenize endpoint. -**`Gym/responses_api_models/vllm_model/app.py`** — the `/tokenize` endpoint must +**`Gym/responses_api_models/vllm_endpoint/app.py`** — the `/tokenize` endpoint must receive `chat_template_kwargs` (e.g., `truncate_history_thinking: false`) to match the tokenization used during chat completion. Without this, the tokenize call uses the template's default `truncate_history_thinking=True`, which strips reasoning from diff --git a/responses_api_agents/harbor_agent/custom_agents/llms/nemo_gym_llm.py b/responses_api_agents/harbor_agent/custom_agents/llms/nemo_gym_llm.py index 5815ae8814..9d51f489f5 100644 --- a/responses_api_agents/harbor_agent/custom_agents/llms/nemo_gym_llm.py +++ b/responses_api_agents/harbor_agent/custom_agents/llms/nemo_gym_llm.py @@ -284,7 +284,7 @@ def _build_extra_chat_params(self, responses_create_params: dict[str, Any]) -> d if not responses_create_params: return {} - from responses_api_models.vllm_model.app import VLLMConverter + from responses_api_models.vllm_endpoint.app import VLLMConverter params_for_conversion = {key: value for key, value in responses_create_params.items() if key != "input"} params_for_conversion["input"] = [] diff --git a/responses_api_agents/langgraph_agent/README.md b/responses_api_agents/langgraph_agent/README.md index 99585f5786..b750ae4f50 100644 --- a/responses_api_agents/langgraph_agent/README.md +++ b/responses_api_agents/langgraph_agent/README.md @@ -9,7 +9,7 @@ Please note that agents such as parallel thinking which produce non-monotonicall ## Quick Start ```bash -ng_run "+config_paths=[resources_servers/reasoning_gym/configs/reflection_agent.yaml,responses_api_models/vllm_model/configs/vllm_model.yaml]" +ng_run "+config_paths=[resources_servers/reasoning_gym/configs/reflection_agent.yaml,responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml]" ``` ```bash diff --git a/responses_api_agents/stirrup_agent/README.md b/responses_api_agents/stirrup_agent/README.md index 8ad50604ae..dac3661435 100644 --- a/responses_api_agents/stirrup_agent/README.md +++ b/responses_api_agents/stirrup_agent/README.md @@ -189,7 +189,7 @@ server (`resources_servers/gdpval`). Drive it from the benchmark config: ```bash ng_e2e_collect_rollouts \ - "+config_paths=[responses_api_models/vllm_model/configs/vllm_model.yaml,benchmarks/gdpval/config.yaml]" \ + "+config_paths=[responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml,benchmarks/gdpval/config.yaml]" \ ++split=benchmark \ ++output_jsonl_fpath=results/gdpval_compare.jsonl \ ++gdpval_resources_server.resources_servers.gdpval.reward_mode=comparison \ diff --git a/responses_api_agents/swe_agents/README.md b/responses_api_agents/swe_agents/README.md index 8a8b7efc7b..265fd01387 100644 --- a/responses_api_agents/swe_agents/README.md +++ b/responses_api_agents/swe_agents/README.md @@ -253,11 +253,11 @@ policy_model_name: Qwen/Qwen3-Coder-30B-A3B-Instruct ```bash # OpenHands single-prompt config_paths="responses_api_agents/swe_agents/configs/swebench_openhands.yaml,\ -responses_api_models/vllm_model/configs/vllm_model.yaml" +responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml" # Or full prompt × agent-class × tool-name diversity config_paths="responses_api_agents/swe_agents/configs/swebench_multi_tools.yaml,\ -responses_api_models/vllm_model/configs/vllm_model.yaml" +responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml" ng_run "+config_paths=[$config_paths]" \ +swe_agents.responses_api_agents.swe_agents.container_formatter=/lustre/xxx/images/swe-bench/swebench_sweb.eval.x86_64.\{instance_id\}.sif \ diff --git a/responses_api_agents/swe_agents/app.py b/responses_api_agents/swe_agents/app.py index f7a54acfdf..752fc93c2d 100644 --- a/responses_api_agents/swe_agents/app.py +++ b/responses_api_agents/swe_agents/app.py @@ -57,7 +57,7 @@ NeMoGymResponseCreateParamsNonStreaming, ) from nemo_gym.profiling import Profiler -from responses_api_models.vllm_model.app import VLLMConverter, split_responses_input_output_items +from responses_api_models.vllm_endpoint.app import VLLMConverter, split_responses_input_output_items ######################################## diff --git a/responses_api_agents/tau2/app.py b/responses_api_agents/tau2/app.py index d16db9c78b..06cbbf0c4c 100644 --- a/responses_api_agents/tau2/app.py +++ b/responses_api_agents/tau2/app.py @@ -42,7 +42,7 @@ NeMoGymResponseCreateParamsNonStreaming, ) from nemo_gym.server_utils import get_server_url, is_nemo_gym_fastapi_entrypoint -from responses_api_models.vllm_model.app import VLLMConverter, split_responses_input_output_items +from responses_api_models.vllm_endpoint.app import VLLMConverter, split_responses_input_output_items from tau2.data_model.simulation import SimulationRun, TextRunConfig from tau2.data_model.tasks import Task from tau2.evaluator.evaluator import EvaluationType diff --git a/responses_api_agents/verifiers_agent/README.md b/responses_api_agents/verifiers_agent/README.md index 14a8f4a541..b76aa80542 100644 --- a/responses_api_agents/verifiers_agent/README.md +++ b/responses_api_agents/verifiers_agent/README.md @@ -21,7 +21,7 @@ policy_model_name: "Qwen/Qwen3-4B-Instruct-2507" ``` # start nemo gym servers -ng_run "+config_paths=[responses_api_agents/verifiers_agent/configs/acereason-math.yaml,responses_api_models/vllm_model/configs/vllm_model.yaml]" +ng_run "+config_paths=[responses_api_agents/verifiers_agent/configs/acereason-math.yaml,responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml]" # generate a rollout ng_collect_rollouts \ @@ -98,7 +98,7 @@ deactivate source .venv/bin/activate # start nemo gym servers -ng_run "+config_paths=[responses_api_agents/verifiers_agent/configs/ascii-tree.yaml,responses_api_models/vllm_model/configs/vllm_model.yaml]" +ng_run "+config_paths=[responses_api_agents/verifiers_agent/configs/ascii-tree.yaml,responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml]" # generate a rollout ng_collect_rollouts \ diff --git a/responses_api_models/azure_openai_model/app.py b/responses_api_models/azure_openai_model/app.py index 5211a96d29..8c5931774b 100644 --- a/responses_api_models/azure_openai_model/app.py +++ b/responses_api_models/azure_openai_model/app.py @@ -30,7 +30,7 @@ NeMoGymResponse, NeMoGymResponseCreateParamsNonStreaming, ) -from responses_api_models.vllm_model.app import VLLMConverter +from responses_api_models.vllm_endpoint.app import VLLMConverter class AzureOpenAIModelServerConfig(BaseResponsesAPIModelConfig): diff --git a/responses_api_models/azure_openai_model/tests/test_app.py b/responses_api_models/azure_openai_model/tests/test_app.py index 0af2e118d3..d7235d0414 100644 --- a/responses_api_models/azure_openai_model/tests/test_app.py +++ b/responses_api_models/azure_openai_model/tests/test_app.py @@ -123,7 +123,7 @@ async def test_responses(self, monkeypatch: MonkeyPatch) -> None: monkeypatch.setattr("responses_api_models.azure_openai_model.app.uuid4", lambda: FakeUUID()) monkeypatch.setattr("responses_api_models.azure_openai_model.app.time", lambda: FIXED_TIME) - monkeypatch.setattr("responses_api_models.vllm_model.app.uuid4", lambda: FakeUUID()) + monkeypatch.setattr("responses_api_models.vllm_endpoint.app.uuid4", lambda: FakeUUID()) mock_response_data = NeMoGymChatCompletion( id="chtcmpl-123", diff --git a/responses_api_models/genrm_model/README.md b/responses_api_models/genrm_model/README.md index 5926179464..9a0ad4df4a 100644 --- a/responses_api_models/genrm_model/README.md +++ b/responses_api_models/genrm_model/README.md @@ -36,5 +36,5 @@ pytest tests/ ## Related -- Base local vLLM: `responses_api_models/local_vllm_model/` +- Base local vLLM: `responses_api_models/vllm_server/` - GenRM Compare server: `resources_servers/genrm_compare/` diff --git a/responses_api_models/genrm_model/app.py b/responses_api_models/genrm_model/app.py index bbcc15cb4e..0bdf8e5559 100644 --- a/responses_api_models/genrm_model/app.py +++ b/responses_api_models/genrm_model/app.py @@ -25,11 +25,11 @@ from fastapi import Request -from responses_api_models.local_vllm_model.app import ( +from responses_api_models.vllm_endpoint.app import VLLMConverter +from responses_api_models.vllm_server.app import ( LocalVLLMModel, LocalVLLMModelConfig, ) -from responses_api_models.vllm_model.app import VLLMConverter class GenRMModelMixin: diff --git a/responses_api_models/local_vllm_model/__init__.py b/responses_api_models/local_vllm_model/__init__.py index e69de29bb2..3159bfe656 100644 --- a/responses_api_models/local_vllm_model/__init__.py +++ b/responses_api_models/local_vllm_model/__init__.py @@ -0,0 +1,14 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/responses_api_models/local_vllm_model/app.py b/responses_api_models/local_vllm_model/app.py index 2583af2009..9287d5e72b 100644 --- a/responses_api_models/local_vllm_model/app.py +++ b/responses_api_models/local_vllm_model/app.py @@ -12,245 +12,12 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -import sys -from argparse import Namespace -from pathlib import Path -from time import sleep -from typing import Any, Dict, List, Optional, Tuple, Union +import warnings -import ray -import requests -from pydantic import BaseModel, Field -from ray import available_resources, cluster_resources -from ray.util.placement_group import PlacementGroup -from ray.util.scheduling_strategies import PlacementGroupSchedulingStrategy -from requests.exceptions import ConnectionError -from vllm.entrypoints.openai.api_server import ( - FlexibleArgumentParser, - cli_env_setup, - make_arg_parser, - validate_parsed_serve_args, -) -from nemo_gym.global_config import ( - DISALLOWED_PORTS_KEY_NAME, - find_open_port, - get_global_config_dict, - get_hf_token, +warnings.warn( + "responses_api_models.local_vllm_model is renamed to responses_api_models.vllm_server; update your imports.", + DeprecationWarning, + stacklevel=2, ) -from responses_api_models.local_vllm_model.local_vllm_model_actor import LocalVLLMModelActor -from responses_api_models.vllm_model.app import VLLMModel, VLLMModelConfig - - -class LocalVLLMModelConfig(VLLMModelConfig): - # We inherit these configs from VLLMModelConfig, but they are set to optional since they will be set later on after we spin up a model endpoint. - base_url: Union[str, List[str]] = Field(default_factory=list) - # Not used on local deployments - api_key: str = "dummy" # pragma: allowlist secret - - hf_home: Optional[str] = None - vllm_serve_kwargs: Dict[str, Any] - vllm_serve_env_vars: Dict[str, str] - - ray_worker_py_executable: str = sys.executable - - show_vllm_engine_stats: bool = False - debug: bool = False - - def model_post_init(self, context): - # Default to the .cache/huggingface in this directory. - if not self.hf_home: - current_directory = Path.cwd() - self.hf_home = str(current_directory / ".cache" / "huggingface") - - return super().model_post_init(context) - - -class GetInnerVLLMConfigResponse(BaseModel): - base_url: List[str] - api_key: str - model: str - - -class LocalVLLMModel(VLLMModel): - config: LocalVLLMModelConfig - - _local_vllm_model_actor: LocalVLLMModelActor - - def setup_webserver(self): - print("Starting vLLM server. This will take a few minutes...") - self.start_vllm_server() - - app = super().setup_webserver() - - # This route is only used to support LocalVLLMModelProxy - app.get("/get_inner_vllm_config")(self.get_inner_vllm_config) - - return app - - async def get_inner_vllm_config(self) -> GetInnerVLLMConfigResponse: - return GetInnerVLLMConfigResponse( - base_url=self.config.base_url, - api_key=self.config.api_key, - model=self.config.model, - ) - - def get_cache_dir(self) -> str: - # We need to reconstruct the cache dir as HF does it given HF_HOME. See https://github.com/huggingface/huggingface_hub/blob/b2723cad81f530e197d6e826f194c110bf92248e/src/huggingface_hub/constants.py#L146 - return str(Path(self.config.hf_home) / "hub") - - def _configure_vllm_serve(self) -> Tuple[Namespace, Dict[str, str]]: - server_args = self.config.vllm_serve_kwargs - - port = find_open_port(disallowed_ports=get_global_config_dict()[DISALLOWED_PORTS_KEY_NAME]) - cache_dir = self.get_cache_dir() - server_args = server_args | { - "model": self.config.model, - "host": "0.0.0.0", # Must be 0.0.0.0 for cross-node communication. - "port": port, - "distributed_executor_backend": "ray", - "data_parallel_backend": "ray", - "download_dir": cache_dir, - } - - env_vars = {"HF_HUB_ENABLE_HF_TRANSFER": "1"} - # vLLM accepts a `hf_token` parameter but it's not used everywhere. We need to set HF_TOKEN environment variable here. - maybe_hf_token = get_hf_token() - if maybe_hf_token: - env_vars["HF_TOKEN"] = maybe_hf_token - - env_vars.update(self.config.vllm_serve_env_vars) - - assert "VLLM_RAY_DP_PACK_STRATEGY" in env_vars, ( - f"Please provide a value for `VLLM_RAY_DP_PACK_STRATEGY` for `{self.config.name}`" - ) - assert server_args.get("data_parallel_size") - assert server_args.get("tensor_parallel_size") - assert server_args.get("pipeline_parallel_size") - - # With our vLLM patches, this assert is no longer necessary - # Ray backend only works if dp_size > 1 - # assert server_args.get("data_parallel_size") is None or server_args.get("data_parallel_size") > 1, ( - # "Ray backend only works with data parallel size > 1!" - # ) - - # With our vLLM patches, this is no longer necessary for people to set. - server_args["data_parallel_size_local"] = 1 - - # TODO multi-node model instances still need to be properly supported - # We get a vLLM error: Exception: Error setting CUDA_VISIBLE_DEVICES: local range: [0, 16) base value: "0,1,2,3,4,5,6,7" - if env_vars.get("VLLM_RAY_DP_PACK_STRATEGY") == "span": - # Unset this flag since it's set by default using span - server_args.pop("data_parallel_size_local", None) - - cli_env_setup() - parser = FlexibleArgumentParser(description="vLLM OpenAI-Compatible RESTful API server.") - parser = make_arg_parser(parser) - final_args = parser.parse_args(namespace=Namespace(**server_args)) - validate_parsed_serve_args(final_args) - - # @bxyu-nvidia: TODO remove, specific to Nemotron 3 Ultra vLLM version - # this return_routed_experts argument isn't present in 0.17.0, so this must be from 0.16.x - final_args.return_routed_experts = final_args.enable_return_routed_experts - - if self.config.debug: - env_vars_to_print = env_vars.copy() - if "HF_TOKEN" in env_vars_to_print: - env_vars_to_print["HF_TOKEN"] = "****" - print(f"""Final vLLM serve arguments: {final_args} -Environment variables: {env_vars_to_print}""") - - return final_args, env_vars - - def _select_vllm_server_head_node(self, server_args: Namespace, env_vars: Dict[str, str]) -> PlacementGroup: - """ - Our LocalVLLMModelActor Ray actor scheduling strategy is as follows: - 1. We estimate the size of a single placement group vLLM will make using TP * PP - 2. We pre-maturely create one placement group of this size which will server as the master node for the vLLM instance - 3. This placement group is also provided on input to the LocalVLLMModelActor, which will schedule (DP - 1) additional placement groups of size TP * PP - """ - # This mirrors the placement group logic above - pack_strategy = env_vars["VLLM_RAY_DP_PACK_STRATEGY"] - if pack_strategy in ("strict", "fill"): - placement_strategy = "STRICT_PACK" - else: - placement_strategy = "PACK" - - device_str = "GPU" - device_bundle = [{device_str: 1.0}] - world_size = server_args.pipeline_parallel_size * server_args.tensor_parallel_size - bundles = device_bundle * world_size + [{"CPU": 1.0}] - head_node_placement_group = ray.util.placement_group( - name=f"{self.config.name}_dp_rank_0", - strategy=placement_strategy, - bundles=bundles, - ) - ray.get(head_node_placement_group.ready()) - - return head_node_placement_group - - def start_vllm_server(self) -> None: - # If base_url is already set, skip local launch — connect to external server. - if self.config.base_url: - print(f"External base_url configured: {self.config.base_url}. Skipping local vLLM launch.") - self._post_init() - return - - if self.config.debug: - print(f"""Currently available Ray cluster resources: {available_resources()} -Total Ray cluster resources: {cluster_resources()}""") - - server_args, env_vars = self._configure_vllm_serve() - head_node_placement_group = self._select_vllm_server_head_node(server_args, env_vars) - - pythonpath = str(Path(__file__).parent.parent.parent) - if self.config.debug: - print(f"Using PYTHONPATH={pythonpath}") - - self._local_vllm_model_actor = LocalVLLMModelActor.options( - scheduling_strategy=PlacementGroupSchedulingStrategy( - placement_group=head_node_placement_group, - ), - runtime_env=dict( - py_executable=self.config.ray_worker_py_executable, - env_vars={ - "RAY_EXPERIMENTAL_NOSET_CUDA_VISIBLE_DEVICES": "1", - "PYTHONPATH": pythonpath, - **env_vars, - }, - ), - ).remote( - head_node_placement_group=head_node_placement_group, - server_args=server_args, - env_vars=env_vars, - server_name=self.config.name, - debug=self.config.debug, - show_vllm_engine_stats=self.config.show_vllm_engine_stats, - ) - - self.config.base_url = [ray.get(self._local_vllm_model_actor.base_url.remote())] - - # Reset clients after base_url config - self._post_init() - - self.await_server_ready() - - def await_server_ready(self) -> None: - poll_count = 0 - while True: - is_alive = ray.get(self._local_vllm_model_actor.is_alive.remote()) - assert is_alive, f"{self.config.name} LocalVLLMModel server spinup failed, see the error logs above!" - - try: - requests.get(url=f"{self.config.base_url[0]}/models") - return - except ConnectionError: - if poll_count % 10 == 0: # Print every 30s - print(f"Waiting for {self.config.name} LocalVLLMModel server to spinup...") - - poll_count += 1 - sleep(3) - - -if __name__ == "__main__": - LocalVLLMModel.run_webserver() +from responses_api_models.vllm_server.app import * # noqa: E402,F401,F403 diff --git a/responses_api_models/local_vllm_model/local_vllm_model_actor.py b/responses_api_models/local_vllm_model/local_vllm_model_actor.py index 6fb1c8c00e..171cb6a00b 100644 --- a/responses_api_models/local_vllm_model/local_vllm_model_actor.py +++ b/responses_api_models/local_vllm_model/local_vllm_model_actor.py @@ -12,349 +12,4 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - -# Try a separate file here to ease on the imports -import asyncio -import sys -from argparse import Namespace -from threading import Thread -from typing import Dict - -import ray -from ray.util.placement_group import PlacementGroup - - -def _vllm_asyncio_task(server_args: Namespace): - from vllm.entrypoints.openai.api_server import run_server - - asyncio.run(run_server(server_args)) - - -@ray.remote -class LocalVLLMModelActor: - def __init__( - self, - head_node_placement_group: PlacementGroup, - server_args: Namespace, - env_vars: Dict[str, str], - server_name: str, - debug: bool, - show_vllm_engine_stats: bool, - ) -> None: - from os import environ - - self.head_node_placement_group = head_node_placement_group - self.server_args = server_args - self.env_vars = env_vars - self.server_name = server_name - self.debug = debug - self.show_vllm_engine_stats = show_vllm_engine_stats - - self.env_vars.pop("CUDA_VISIBLE_DEVICES", None) - - node_ip = ray._private.services.get_node_ip_address() - self._base_url = f"http://{node_ip}:{self.server_args.port}/v1" - print(f"Spinning up local vLLM server at {self._base_url}", file=sys.stderr) - - # vLLM doesn't expose a config for this yet, so we need to pass via environment variable. - self.env_vars["VLLM_DP_MASTER_IP"] = node_ip # This is the master node. - - self._patch_signal_handler() - self._patch_uvicorn_logger() - self._maybe_patch_engine_stats() - self._patch_create_dp_placement_groups() - self._patch_init_data_parallel() - - for k, v in self.env_vars.items(): - environ[k] = v - - self.server_thread = Thread(target=_vllm_asyncio_task, args=(server_args,), daemon=True) - self.server_thread.start() - - def _patch_signal_handler(self) -> None: - # Pass through signal setting not allowed in threads. - # See https://github.com/vllm-project/vllm/blob/275de34170654274616082721348b7edd9741d32/vllm/entrypoints/launcher.py#L94 - # This may be vLLM version specific! - # - # api_server.py uses `from vllm.entrypoints.launcher import serve_http`, - # so we must patch the name in api_server's namespace (not launcher's). - - import signal - from asyncio import get_running_loop - - import vllm.entrypoints.openai.api_server as api_server - - original_serve_http = api_server.serve_http - - def new_serve_http(*args, **kwargs): - loop = get_running_loop() - loop.add_signal_handler = lambda *args, **kwargs: None - - return original_serve_http(*args, **kwargs) - - api_server.serve_http = new_serve_http - - # Patch signal as well. - signal.signal = lambda *args, **kwargs: None - - def _patch_uvicorn_logger(self) -> None: - from logging import Filter as LoggingFilter - from logging import LogRecord, getLogger - - print( - "Adding a uvicorn logging filter so that the logs aren't spammed with 200 OK messages. This is to help errors pop up better and filter out noise." - ) - - class No200Filter(LoggingFilter): - def filter(self, record: LogRecord) -> bool: - msg = record.getMessage() - return not msg.strip().endswith("200") - - uvicorn_logger = getLogger("uvicorn.access") - uvicorn_logger.addFilter(No200Filter()) - - def _maybe_patch_engine_stats(self) -> None: - from logging import ERROR, StreamHandler - - from vllm.v1.metrics.loggers import logger as metrics_logger - - if self.debug or self.show_vllm_engine_stats: - print("vLLM metrics logger will display engine stats.") - handler = StreamHandler(sys.stderr) - metrics_logger.addHandler(handler) - else: - print( - f"Setting vLLM metrics logger for {self.server_name} to ERROR which will not print engine stats. This helps declutter the logs. Use `debug` for LocalVLLMModel to see them." - ) - metrics_logger.setLevel(ERROR) - - def _patch_init_data_parallel(self) -> None: - from vllm.v1.engine.core import DPEngineCoreProc, logger - - def new_init_data_parallel(self, vllm_config): - # Configure GPUs and stateless process group for data parallel. - dp_rank = vllm_config.parallel_config.data_parallel_rank - dp_size = vllm_config.parallel_config.data_parallel_size - local_dp_rank = vllm_config.parallel_config.data_parallel_rank_local - - # This allows the vLLM DP Ray flow to be run even with a single DP instance. - # assert dp_size > 1 - - assert local_dp_rank is not None - assert 0 <= local_dp_rank <= dp_rank < dp_size - - if vllm_config.kv_transfer_config is not None: - # modify the engine_id and append the local_dp_rank to it to ensure - # that the kv_transfer_config is unique for each DP rank. - vllm_config.kv_transfer_config.engine_id = ( - f"{vllm_config.kv_transfer_config.engine_id}_dp{local_dp_rank}" - ) - logger.debug( - "Setting kv_transfer_config.engine_id to %s", - vllm_config.kv_transfer_config.engine_id, - ) - - self.dp_rank = dp_rank - self.dp_group = vllm_config.parallel_config.stateless_init_dp_group() - - DPEngineCoreProc._init_data_parallel = new_init_data_parallel - - def _patch_create_dp_placement_groups(self) -> None: - head_node_placement_group = self.head_node_placement_group - - from ray.util.placement_group import PlacementGroup - from vllm.v1.engine.utils import ( - CoreEngineActorManager, - current_platform, - envs, - logger, - ) - - ######################################## - # The logic below is an exact copy of CoreEngineActorManager.create_dp_placement_groups - # Except in places where we specify it differs - ######################################## - def new_create_dp_placement_groups(vllm_config): - """ - Create placement groups for data parallel. - """ - - import ray - from ray._private.state import available_resources_per_node, total_resources_per_node - - logger.info("Creating placement groups for data parallel") - dp_master_ip = vllm_config.parallel_config.data_parallel_master_ip - dp_size = vllm_config.parallel_config.data_parallel_size - dp_size_local = vllm_config.parallel_config.data_parallel_size_local - - available_resources = available_resources_per_node() - - """ - START Patch colocated placement group logic - - When running multiple local vLLM model instances on the same node, the placement group logic will error with the following since multiple placement groups are now on the same node. - - (LocalVLLMModelActor pid=504531) (APIServer pid=504531) File "responses_api_models/local_vllm_model/.venv/lib/python3.12/site-packages/vllm/v1/engine/utils.py", line 858, in launch_core_engines - (LocalVLLMModelActor pid=504531) (APIServer pid=504531) engine_actor_manager = CoreEngineActorManager( - (LocalVLLMModelActor pid=504531) (APIServer pid=504531) ^^^^^^^^^^^^^^^^^^^^^^^ - (LocalVLLMModelActor pid=504531) (APIServer pid=504531) File "responses_api_models/local_vllm_model/.venv/lib/python3.12/site-packages/vllm/v1/engine/utils.py", line 300, in __init__ - (LocalVLLMModelActor pid=504531) (APIServer pid=504531) CoreEngineActorManager.create_dp_placement_groups(vllm_config) - (LocalVLLMModelActor pid=504531) (APIServer pid=504531) File "responses_api_models/local_vllm_model/.venv/lib/python3.12/site-packages/vllm/v1/engine/utils.py", line 467, in create_dp_placement_groups - (LocalVLLMModelActor pid=504531) (APIServer pid=504531) assert len(node_ip_keys) == 1, ( - (LocalVLLMModelActor pid=504531) (APIServer pid=504531) ^^^^^^^^^^^^^^^^^^^^^^ - (LocalVLLMModelActor pid=504531) (APIServer pid=504531) AssertionError: Zero or multiple node IP keys found in node resources: ['node:10.65.9.15_group_a036a448bf98d155cd0d6a8991f902000000', 'node:10.65.9.15_group_1_8786b4bfb840f7ba7af007e7e41602000000', 'node:10.65.9.15', 'node:10.65.9.15_group_8786b4bfb840f7ba7af007e7e41602000000', 'node:10.65.9.15_group_1_a036a448bf98d155cd0d6a8991f902000000', 'node:10.65.9.15_group_0_8786b4bfb840f7ba7af007e7e41602000000', 'node:10.65.9.15_group_0_a036a448bf98d155cd0d6a8991f902000000'] - """ - - for node_hex_id, node_resources in list(available_resources.items()): - available_resources[node_hex_id] = { - resource_id: resource - for resource_id, resource in node_resources.items() - if "_group_" not in resource_id - } - - """ - END Patch colocated placement group logic - """ - - world_size = vllm_config.parallel_config.world_size - """ - START Use our initial placement group - """ - placement_groups: list[PlacementGroup] = [head_node_placement_group] - local_dp_ranks: list[int] = [0] - """ - END Use our initial placement group - """ - - dp_master_ip_key = f"node:{dp_master_ip}" - nodes = sorted(available_resources.values(), key=lambda x: dp_master_ip_key not in x) - assert len(nodes) > 0, "No nodes with resources found in Ray cluster." - assert dp_master_ip_key in nodes[0], ( - "The DP master node (ip: %s) is missing or dead", - dp_master_ip, - ) - device_str = current_platform.ray_device_key - - n_node_devices: list[int] = [ - int(node_resources[device_str]) for node_resources in nodes if device_str in node_resources - ] - """ - START Account for cases when the initial placement groups we create i.e. DP == 1 are already sufficient - """ - # Original code: - # assert n_node_devices, f"No {device_str} found in Ray cluster." - - # Modified code: - if dp_size == 1: - total_nodes = total_resources_per_node().values() - total_n_node_devices: list[int] = [ - int(node_resources[device_str]) for node_resources in total_nodes if device_str in node_resources - ] - max_device_per_node = max(total_n_node_devices) - else: - assert n_node_devices, f"No {device_str} found in Ray cluster." - max_device_per_node = max(n_node_devices) - """ - END Account for cases when the initial placement groups we create i.e. DP == 1 are already sufficient - """ - - pack_strategy = envs.VLLM_RAY_DP_PACK_STRATEGY - _supported_pack_strategies = ("strict", "fill", "span") - if pack_strategy not in _supported_pack_strategies: - raise ValueError( - f"{envs.VLLM_RAY_DP_PACK_STRATEGY} is not supported. " - "Make sure to set `VLLM_RAY_DP_PACK_STRATEGY` " - f"to one of {_supported_pack_strategies}" - ) - - all2all_backend = vllm_config.parallel_config.all2all_backend - if pack_strategy == "fill" and ( - all2all_backend == "deepep_high_throughput" or all2all_backend == "deepep_low_latency" - ): - raise ValueError( - "DeepEP kernels require EP ranks [0,7] (same for [8,15], ...) " - "to be on the same node, but VLLM_RAY_DP_PACK_STRATEGY=fill " - "does not guarantee that. " - "Please use VLLM_RAY_DP_PACK_STRATEGY=strict instead." - ) - - if pack_strategy in ("strict", "fill"): - placement_strategy = "STRICT_PACK" - else: - placement_strategy = "PACK" - assert world_size > max_device_per_node, ( - f"World size {world_size} is smaller than the " - "maximum number of devices per node " - f"{max_device_per_node}. Make sure to set " - "`VLLM_RAY_DP_PACK_STRATEGY` to `strict` or `fill`" - ) - - # if we need multiple nodes per dp group, we require for now that - # available nodes are homogenous - if dp_size == 1: - assert set(total_n_node_devices) == {max_device_per_node}, f"Nodes are not homogenous, {nodes}" - else: - assert set(n_node_devices) == {max_device_per_node}, f"Nodes are not homogenous, {nodes}" - assert world_size % max_device_per_node == 0, ( - f"For multi-node data parallel groups, world_size ({world_size}) must " - f"be a multiple of number of devices per node ({max_device_per_node})." - ) - """ - START Fix required GPU compute necessary calculation given we already reserve one placement group - """ - # Original code: - # assert len(n_node_devices) * max_device_per_node >= world_size * dp_size, ( - - # Modified code: - assert len(n_node_devices) * max_device_per_node >= world_size * (dp_size - 1), ( - f"Not enough total available nodes ({len(n_node_devices)}) " - f"and devices per node ({max_device_per_node}) " - f"to satisfy required world size {world_size} and data parallel size " - f"{dp_size}" - ) - """ - END Fix required GPU compute necessary calculation given we already reserve one placement group - """ - assert dp_size_local == 1, ( - f"data-parallel-size-local {dp_size_local} should be set as the " - "default (1) for VLLM_RAY_DP_PACK_STRATEGY=span. " - "The actual data-parallel-size-local will be auto determined." - ) - - for _ in range(dp_size - 1): - bundles = [{device_str: 1.0}] * world_size + [{"CPU": 1.0}] - - pg_name = f"{self.server_name}_dp_rank_{len(placement_groups)}" - pg = ray.util.placement_group( - name=pg_name, - strategy=placement_strategy, - bundles=bundles, - ) - - placement_groups.append(pg) - local_dp_ranks.append(0) - - if len(placement_groups) < dp_size: - raise ValueError( - f"Not enough resources to allocate {dp_size} " - "placement groups, only created " - f"{len(placement_groups)} placement groups. " - "Available resources: " - f"{available_resources}" - ) - assert len(placement_groups) == dp_size, ( - f"Created {len(placement_groups)} DP placement groups, expected {dp_size}" - ) - assert len(local_dp_ranks) == dp_size, ( - f"local_dp_ranks length {len(local_dp_ranks)} does not match expected {dp_size}" - ) - - return placement_groups, local_dp_ranks - - CoreEngineActorManager.create_dp_placement_groups = new_create_dp_placement_groups - - def base_url(self) -> str: - return self._base_url - - def is_alive(self) -> bool: - return self.server_thread.is_alive() +from responses_api_models.vllm_server.local_vllm_model_actor import * # noqa: F401,F403 diff --git a/responses_api_models/local_vllm_model_proxy/README.md b/responses_api_models/local_vllm_model_proxy/README.md index d4e57d6dd0..c805f9e0a0 100644 --- a/responses_api_models/local_vllm_model_proxy/README.md +++ b/responses_api_models/local_vllm_model_proxy/README.md @@ -3,7 +3,7 @@ # End-to-end test with GPT OSS 20B reasoning high ```bash -config_paths="responses_api_models/local_vllm_model/configs/openai/gpt-oss-20b-reasoning-high.yaml,\ +config_paths="responses_api_models/vllm_server/configs/openai/gpt-oss-20b-reasoning-high.yaml,\ responses_api_models/local_vllm_model_proxy/configs/local_vllm_model_proxy.yaml" ng_run "+config_paths=[${config_paths}]" \ ++policy_model_proxy.responses_api_models.local_vllm_model_proxy.model_server.name=gpt-oss-20b-reasoning-high \ diff --git a/responses_api_models/local_vllm_model_proxy/app.py b/responses_api_models/local_vllm_model_proxy/app.py index e07104e1a5..9f7f639dde 100644 --- a/responses_api_models/local_vllm_model_proxy/app.py +++ b/responses_api_models/local_vllm_model_proxy/app.py @@ -24,7 +24,7 @@ from nemo_gym.config_types import ModelServerRef from nemo_gym.global_config import get_first_server_config_dict -from responses_api_models.vllm_model.app import VLLMModel, VLLMModelConfig +from responses_api_models.vllm_endpoint.app import VLLMModel, VLLMModelConfig class LocalVLLMModelProxyServerConfig(VLLMModelConfig): diff --git a/responses_api_models/vllm_model/README.md b/responses_api_models/vllm_endpoint/README.md similarity index 100% rename from responses_api_models/vllm_model/README.md rename to responses_api_models/vllm_endpoint/README.md diff --git a/responses_api_models/local_vllm_model/tests/__init__.py b/responses_api_models/vllm_endpoint/__init__.py similarity index 100% rename from responses_api_models/local_vllm_model/tests/__init__.py rename to responses_api_models/vllm_endpoint/__init__.py diff --git a/responses_api_models/vllm_endpoint/app.py b/responses_api_models/vllm_endpoint/app.py new file mode 100644 index 0000000000..4eace05d0b --- /dev/null +++ b/responses_api_models/vllm_endpoint/app.py @@ -0,0 +1,977 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import base64 +import json +import os +import re +from copy import deepcopy +from time import time +from typing import Any, ClassVar, Dict, List, Optional, Tuple, Union +from uuid import uuid4 + +from aiohttp.client_exceptions import ClientResponseError +from fastapi import Request +from pydantic import BaseModel, Field + +from nemo_gym.base_responses_api_model import ( + BaseResponsesAPIModelConfig, + Body, + SimpleResponsesAPIModel, +) +from nemo_gym.openai_utils import ( + RESPONSES_TO_TRAIN, + NeMoGymAsyncOpenAI, + NeMoGymChatCompletion, + NeMoGymChatCompletionAssistantMessageForTrainingParam, + NeMoGymChatCompletionAssistantMessageParam, + NeMoGymChatCompletionCreateParamsNonStreaming, + NeMoGymChatCompletionDeveloperMessageParam, + NeMoGymChatCompletionMessage, + NeMoGymChatCompletionMessageParam, + NeMoGymChatCompletionMessageToolCallFunctionParam, + NeMoGymChatCompletionMessageToolCallParam, + NeMoGymChatCompletionSystemMessageParam, + NeMoGymChatCompletionToolMessageParam, + NeMoGymChatCompletionToolParam, + NeMoGymChatCompletionUserMessageParam, + NeMoGymChoice, + NeMoGymEasyInputMessage, + NeMoGymFunctionCallOutput, + NeMoGymFunctionDefinition, + NeMoGymResponse, + NeMoGymResponseCreateParamsNonStreaming, + NeMoGymResponseFunctionToolCall, + NeMoGymResponseInputTokensDetails, + NeMoGymResponseOutputItem, + NeMoGymResponseOutputMessage, + NeMoGymResponseOutputText, + NeMoGymResponseOutputTokensDetails, + NeMoGymResponseReasoningItem, + NeMoGymResponseUsage, + NeMoGymSummary, + TokenIDLogProbMixin, +) +from nemo_gym.server_utils import SESSION_ID_KEY, is_nemo_gym_fastapi_entrypoint + + +class VLLMModelConfig(BaseResponsesAPIModelConfig): + base_url: Union[str, List[str]] + api_key: str + model: str + return_token_id_information: bool + + uses_reasoning_parser: bool + uses_interleaved_reasoning: bool = True + replace_developer_role_with_system: bool = False + + # Whether or not the model can generate a reasoning output, and called again to produce additional reasoning output. + sequential_reasoning_allowed: bool = True + + # As of Feb 2026, we default this to False since majority of open source models aren't responses native with the exception of GPT-OSS + is_responses_native: bool = False + + chat_template_kwargs: Optional[Dict[str, Any]] = None + + # Corresponds to the extra_body of OpenAI Client. + extra_body: Optional[Dict[str, Any]] = None + + default_headers: Dict[str, str] = Field(default_factory=dict) + # Optional prefix for resolving relative ``metadata.audio_path`` (or + # entries in ``metadata.audio_paths``) against. Absolute paths are used + # as-is. When unset, relative paths raise. Audio is always inlined as a + # ``data:audio/;base64,...`` URI at request time — keeps the JSONL + # small without depending on vLLM's ``--allowed-local-media-path``. + audio_root: Optional[str] = None + + def model_post_init(self, context): + if isinstance(self.base_url, str): + self.base_url = [self.base_url] + return super().model_post_init(context) + + +class VLLMModel(SimpleResponsesAPIModel): + config: VLLMModelConfig + + def get_converter(self) -> "VLLMConverter": + """Return the converter used for Responses API <-> Chat Completions mapping. + + Override in subclasses (e.g. GenRMModel) to use a specialized converter. + """ + return VLLMConverter( + return_token_id_information=self.config.return_token_id_information, + uses_reasoning_parser=self.config.uses_reasoning_parser, + ) + + def model_post_init(self, context): + self._post_init() + return super().model_post_init(context) + + def _post_init(self) -> None: + self._clients = [ + NeMoGymAsyncOpenAI( + base_url=base_url, + api_key=self.config.api_key, + default_headers=self.config.default_headers, + ) + for base_url in self.config.base_url + ] + + self._session_id_to_client: Dict[str, NeMoGymAsyncOpenAI] = dict() + + self._converter = self.get_converter() + + async def responses( + self, request: Request, body: NeMoGymResponseCreateParamsNonStreaming = Body() + ) -> NeMoGymResponse: + if self.config.is_responses_native: + return await self._responses_native(request, body) + + # Response Create Params -> Chat Completion Create Params + chat_completion_create_params = self._converter.responses_to_chat_completion_create_params(body) + body.model = self.config.model + + # Chat Completion Create Params -> Chat Completion + chat_completion_response = await self.chat_completions(request, chat_completion_create_params) + + choice = chat_completion_response.choices[0] + + response_output = self._converter.postprocess_chat_response(choice) + response_output_dicts = [item.model_dump() for item in response_output] + + usage = None + if chat_completion_response.usage: + usage = NeMoGymResponseUsage( + input_tokens=chat_completion_response.usage.prompt_tokens, + input_tokens_details=NeMoGymResponseInputTokensDetails(cached_tokens=0), + output_tokens=chat_completion_response.usage.completion_tokens, + output_tokens_details=NeMoGymResponseOutputTokensDetails(reasoning_tokens=0), + total_tokens=chat_completion_response.usage.prompt_tokens + + chat_completion_response.usage.completion_tokens, + ) + + incomplete_details = None + if choice.finish_reason == "length": + incomplete_details = {"reason": "max_output_tokens"} + elif choice.finish_reason == "content_filter": + incomplete_details = {"reason": "content_filter"} + + # Chat Completion -> Response + return NeMoGymResponse( + id=f"resp_{uuid4().hex}", + created_at=int(time()), + model=body.model, + object="response", + output=response_output_dicts, + tool_choice=body.tool_choice if "tool_choice" in body else "auto", + parallel_tool_calls=body.parallel_tool_calls, + tools=body.tools, + temperature=body.temperature, + top_p=body.top_p, + background=body.background, + max_output_tokens=body.max_output_tokens, + max_tool_calls=body.max_tool_calls, + previous_response_id=body.previous_response_id, + prompt=body.prompt, + reasoning=body.reasoning, + service_tier=body.service_tier, + text=body.text, + top_logprobs=body.top_logprobs, + truncation=body.truncation, + metadata=body.metadata, + instructions=body.instructions, + user=body.user, + incomplete_details=incomplete_details, + usage=usage, + ) + + async def _responses_native( + self, request: Request, body: NeMoGymResponseCreateParamsNonStreaming + ) -> NeMoGymResponse: + """ + The following config parameters are effectively no-ops with Responses native models: + - uses_reasoning_parser: bool (Not applicable) + """ + # The following parameters could be supported, but have not been supported yet for Responses-native models: + if self.config.return_token_id_information: + raise NotImplementedError + if self.config.replace_developer_role_with_system: + raise NotImplementedError + if not self.config.sequential_reasoning_allowed: + raise NotImplementedError + + body_dict = body.model_dump(exclude_unset=True) + body_dict["model"] = self.config.model + if self.config.chat_template_kwargs: + body_dict["chat_template_kwargs"] = deepcopy(self.config.chat_template_kwargs) + if self.config.extra_body: + body_dict = self.config.extra_body | body_dict + + client = self._resolve_client(request) + response_dict = await client.create_response(**body_dict) + + return NeMoGymResponse.model_validate(response_dict) + + # Mapping from common audio file extensions to MIME subtypes used in the + # ``data:audio/;base64,...`` URI. vLLM-side decoders inspect the + # subtype to pick a backend (libsndfile, ffmpeg, …); guessing wrong would + # silently mis-decode, so we keep the table conservative and raise on + # unknown extensions instead of falling back to ``wav``. + _AUDIO_EXT_TO_MIME: ClassVar[Dict[str, str]] = { + ".wav": "wav", + ".flac": "flac", + ".mp3": "mpeg", + ".m4a": "mp4", + ".ogg": "ogg", + ".opus": "opus", + } + + def _resolve_audio_path_to_url(self, audio_path: str) -> str: + """Turn an ``audio_path`` reference into a ``data:audio/...;base64`` URI. + + Reads the file and inlines it as a base64 data URI at request time + — same strategy NeMo Skills' ``VLLMMultimodalModel.content_text_to_list`` + uses (read once per request, hand vLLM a self-contained content + block). Keeps the on-disk JSONL small without requiring any vLLM + server-side flag. + + Relative paths are resolved against ``config.audio_root``; without + it, relative paths raise so the failure mode is loud rather than + silently reading from the server CWD. + """ + if os.path.isabs(audio_path): + resolved = audio_path + elif self.config.audio_root: + resolved = os.path.join(self.config.audio_root, audio_path) + else: + raise ValueError( + f"metadata.audio_path={audio_path!r} is relative but VLLMModelConfig.audio_root " + "is unset. Set audio_root in the model config or use absolute paths." + ) + + if not os.path.isfile(resolved): + raise FileNotFoundError(f"metadata.audio_path resolved to {resolved!r}, which does not exist.") + + ext = os.path.splitext(resolved)[1].lower() + mime = self._AUDIO_EXT_TO_MIME.get(ext) + if mime is None: + raise ValueError( + f"Unsupported audio extension {ext!r} for {resolved!r}. Supported: {sorted(self._AUDIO_EXT_TO_MIME)}." + ) + with open(resolved, "rb") as f: + encoded = base64.b64encode(f.read()).decode("ascii") + return f"data:audio/{mime};base64,{encoded}" + + def _preprocess_chat_completion_create_params(self, request: Request, body_dict: Dict[str, Any]) -> Dict[str, Any]: + """Preprocess the body dict before issuing a chat completion request. + + Subclasses can override this to apply model-specific transformations + (e.g. role remapping, extra sampling params). The base implementation + handles the features driven by ``VLLMModelConfig``. + + Args: + request: The originating FastAPI request (available for session / + client resolution if needed by subclasses). + body_dict: Mutable dict produced by ``body.model_dump(exclude_unset=True)``. + + Returns: + The (possibly mutated) ``body_dict`` that will be forwarded to + ``client.create_chat_completion``. + """ + if self.config.replace_developer_role_with_system: + for message_dict in body_dict["messages"]: + if message_dict.get("role") == "developer": + message_dict["role"] = "system" + + body_dict["model"] = self.config.model + + chat_template_kwargs = {} + if self.config.chat_template_kwargs: + chat_template_kwargs = deepcopy(self.config.chat_template_kwargs) + + metadata = body_dict.get("metadata", dict()) + + # Merge global config chat_template_kwargs with per-request overrides in metadata (e.g. per-sample reasoning on/off) + metadata_chat_template_kwargs_str = metadata.get("chat_template_kwargs", "{}") + chat_template_kwargs.update(json.loads(metadata_chat_template_kwargs_str)) + + if chat_template_kwargs: + body_dict["chat_template_kwargs"] = chat_template_kwargs + + # Merge global config extra_body with per-request overrides from metadata + extra_body = {} + if self.config.extra_body: + extra_body = deepcopy(self.config.extra_body) + + metadata_extra_body_str = metadata.get("extra_body", "{}") + extra_body.update(json.loads(metadata_extra_body_str)) + + if self.config.return_token_id_information: + body_dict |= dict( + logprobs=True, + # Typically passed via OpenAI client extra_body. + return_tokens_as_token_ids=True, + # TODO add this when NeMo RL upgrades to vLLM 0.10.2 support for prompt token ids + # For prompt and generation token IDs + # return_token_ids=True, + # For prompt token IDs + # prompt_logprobs=0, + ) + + if self.config.uses_reasoning_parser: + for message_dict in body_dict["messages"]: + if message_dict.get("role") != "assistant" or "content" not in message_dict: + continue + + content = message_dict["content"] + if isinstance(content, str): + reasoning_matches, remaining_content = self._converter._extract_reasoning_from_content(content) + message_dict["content"] = remaining_content + if reasoning_matches and self.config.uses_interleaved_reasoning: + message_dict["reasoning_content"] = reasoning_matches[0] + + # TODO when NeMo RL migrates to vLLM>=0.16.0, remove the reasoning_content support above. + # Starting with vLLM 0.16.0, the `reasoning_content` field has been deprecated in favor of just `reasoning` + message_dict["reasoning"] = reasoning_matches[0] + elif isinstance(content, list): + reasoning_content = None + for content_item_dict in content: + reasoning_matches, remaining_content = self._converter._extract_reasoning_from_content( + content_item_dict["text"] + ) + assert reasoning_content is None or not reasoning_matches, ( + f"Found multiple reasoning matches in a single assistant message content item list!\nMessage: {message_dict}" + ) + + # Even though we set the reasoning content already here, we still loop through all the content item dicts for the assert above. + content_item_dict["text"] = remaining_content + if reasoning_matches and self.config.uses_interleaved_reasoning: + message_dict["reasoning_content"] = reasoning_matches[0] + # See the TODO wrt reasoning_content above + message_dict["reasoning"] = reasoning_matches[0] + elif not content: + # No content or content None is a no-op + pass + else: + raise NotImplementedError + + if extra_body: + body_dict = extra_body | body_dict + + # Audio sidechannel: rows can carry audio on + # ``responses_create_params.metadata`` via three mutually exclusive + # keys, all spliced as ``audio_url`` content blocks into the most + # recent user message before forwarding to vLLM Chat Completions: + # + # * ``audio_data`` — a single pre-built ``data:audio/...;base64,`` + # URI inlined into the JSONL. Self-contained; + # no audio root needed at request time. + # * ``audio_path`` — a single file path; resolved against + # ``config.audio_root`` and encoded to a data + # URI at request time. + # * ``audio_paths`` — list of file paths; each encoded and spliced + # in order. Mirrors NeMo Skills' ``audios`` + # multi-clip schema. + # + # OpenAI's Responses API content union has no audio variant (audio + # types exist as orphans in the SDK but aren't members of + # ``ResponseInputContentParam``), so audio rows can't ride in + # ``input.content`` directly — the metadata-sidechannel hop lets + # audio benchmarks carry audio without a Gym schema change. + # + # Audio is placed BEFORE text in the content list (some audio + # models care). No-op when none of the three keys are present, so + # non-audio benchmarks are unaffected. + audio_keys_present = [k for k in ("audio_data", "audio_path", "audio_paths") if metadata.get(k)] + if len(audio_keys_present) > 1: + raise ValueError( + f"metadata audio keys are mutually exclusive — got {audio_keys_present}. " + "Set exactly one of audio_data / audio_path / audio_paths per row." + ) + + audio_urls: List[str] = [] + if metadata.get("audio_data"): + audio_urls.append(metadata["audio_data"]) + metadata.pop("audio_data", None) + elif metadata.get("audio_path"): + audio_urls.append(self._resolve_audio_path_to_url(metadata["audio_path"])) + metadata.pop("audio_path", None) + elif metadata.get("audio_paths"): + paths = metadata["audio_paths"] + if not isinstance(paths, list): + raise ValueError(f"metadata.audio_paths must be a list, got {type(paths).__name__}.") + audio_urls.extend(self._resolve_audio_path_to_url(p) for p in paths) + metadata.pop("audio_paths", None) + + if audio_urls: + if not metadata and "metadata" in body_dict: + body_dict.pop("metadata", None) + + audio_blocks = [{"type": "audio_url", "audio_url": {"url": url}} for url in audio_urls] + messages = body_dict.get("messages", []) or [] + for msg in reversed(messages): + if msg.get("role") != "user": + continue + content = msg.get("content") + if isinstance(content, str): + msg["content"] = audio_blocks + [{"type": "text", "text": content}] + elif isinstance(content, list): + msg["content"] = audio_blocks + list(content) + else: + # ``None`` / unexpected shape — replace with a fresh content list + msg["content"] = list(audio_blocks) + break + else: + # No user message found — create one with just the audio blocks. + body_dict.setdefault("messages", []).append({"role": "user", "content": list(audio_blocks)}) + + return body_dict + + async def chat_completions( + self, request: Request, body: NeMoGymChatCompletionCreateParamsNonStreaming = Body() + ) -> NeMoGymChatCompletion: + body_dict = body.model_dump(exclude_unset=True) + body_dict = self._preprocess_chat_completion_create_params(request, body_dict) + + client = self._resolve_client(request) + + if not self.config.sequential_reasoning_allowed: + last_message = body_dict["messages"][-1] + if last_message["role"] == "assistant" and not (last_message["content"] or last_message.get("tool_calls")): + res = self._create_empty_chat_completion() + res.choices[0].finish_reason = "content_filter" + return res + + try: + chat_completion_dict = await client.create_chat_completion(**body_dict) + except ClientResponseError as e: + """ + Example messages for out of context length: + + 1. https://github.com/vllm-project/vllm/blob/685c99ee77b4818dcdd15b30fe0e0eff0d5d22ec/vllm/entrypoints/openai/serving_engine.py#L914 + ```json + {"object":"error","message":"This model\'s maximum context length is 32768 tokens. However, you requested 32818 tokens in the messages, Please reduce the length of the messages. None","type":"BadRequestError","param":null,"code":400} + ``` + 2. https://github.com/vllm-project/vllm/blob/685c99ee77b4818dcdd15b30fe0e0eff0d5d22ec/vllm/entrypoints/openai/serving_engine.py#L940 + 3. https://github.com/vllm-project/vllm/blob/685c99ee77b4818dcdd15b30fe0e0eff0d5d22ec/vllm/entrypoints/openai/serving_engine.py#L948 + 4. https://github.com/vllm-project/vllm/blob/685c99ee77b4818dcdd15b30fe0e0eff0d5d22ec/vllm/sampling_params.py#L463 + """ + result_content_str = e.response_content.decode() + + is_out_of_context_length = e.status == 400 and ( + "context length" in result_content_str or "max_tokens" in result_content_str + ) + if is_out_of_context_length: + res = self._create_empty_chat_completion() + res.choices[0].finish_reason = "length" + return res + else: + raise e + + choice_dict = chat_completion_dict["choices"][0] + if self.config.uses_reasoning_parser: + # See the TODO wrt reasoning_content above + reasoning_content = choice_dict["message"].get("reasoning_content") or choice_dict["message"].get( + "reasoning" + ) + if reasoning_content: + choice_dict["message"].pop("reasoning_content", None) + # See the TODO wrt reasoning_content above + choice_dict["message"].pop("reasoning", None) + + # We wrap this here in think tags for Gym's sake and to return a valid OpenAI Chat Completions response. + choice_dict["message"]["content"] = self._converter._wrap_reasoning_in_think_tags( + [reasoning_content] + ) + (choice_dict["message"].get("content") or "") + else: + # See the TODO wrt reasoning_content above + assert not (choice_dict["message"].get("reasoning_content") or choice_dict["message"].get("reasoning")), ( + f"NeMo Gym server `{self.config.name}` config has explicitly been set to not use a reasoning parser i.e. `uses_reasoning_parser: false`. Please do not use a reasoning parser in your vLLM endpoint, or fix the `{self.config.name}` server config!" + ) + + if self.config.return_token_id_information and "prompt_token_ids" not in choice_dict["message"]: + log_probs = choice_dict["logprobs"]["content"] + generation_log_probs = [log_prob["logprob"] for log_prob in log_probs] + + """ + START TODO remove this when NeMo RL upgrades to vLLM 0.10.2 support for prompt token ids + """ + # Looks like `"token_id:151667"` + generation_token_ids = [log_prob["token"].removeprefix("token_id:") for log_prob in log_probs] + + # The tokenize endpoint doesn't accept any sampling parameters + # The only relevant params are model, messages, and tools. + # + # IMPORTANT: pass through chat-template knobs (e.g. enable_thinking) + # when tokenizing, otherwise `prompt_token_ids` (and therefore logged + # `prompt_str`) can be built with different chat template settings than + # the actual generation request. + tokenize_body_dict = dict() + for key in ("model", "messages", "tools", "chat_template_kwargs"): + if key in body_dict: + tokenize_body_dict[key] = body_dict[key] + + # The base url has /v1 at the end but vLLM's tokenize endpoint does not have v1, hence the .. + tokenize_response = await client.create_tokenize(**tokenize_body_dict) + """ + END + """ + + message_dict = choice_dict["message"] + message_dict.update( + dict( + # TODO add this when NeMo RL upgrades to vLLM 0.10.2 support for prompt token ids + # prompt_token_ids=chat_completion_dict["prompt_token_ids"], + prompt_token_ids=tokenize_response["tokens"], + # generation_token_ids=choice_dict["token_ids"], + generation_token_ids=generation_token_ids, + generation_log_probs=generation_log_probs, + ) + ) + + # Clean the duplicated information + choice_dict.pop("logprobs") + # TODO add this when NeMo RL upgrades to vLLM 0.10.2 support for prompt token ids + # chat_completion_dict.pop("prompt_token_ids") + # choice_dict.pop("token_ids") + + return NeMoGymChatCompletion.model_validate(chat_completion_dict) + + def _create_empty_chat_completion(self) -> NeMoGymChatCompletion: + return NeMoGymChatCompletion( + id="chtcmpl-123", + object="chat.completion", + created=int(time()), + model=self.config.model, + choices=[ + NeMoGymChoice( + index=0, + finish_reason="stop", + message=NeMoGymChatCompletionMessage( + role="assistant", + content=None, + tool_calls=None, + ), + ) + ], + ) + + def _resolve_client(self, request: Request) -> NeMoGymAsyncOpenAI: + session_id = request.session[SESSION_ID_KEY] + if session_id not in self._session_id_to_client: + # There is probably a better way to select the endpoint for this request. But this will do for now. + client_idx = len(self._session_id_to_client) % len(self._clients) + client = self._clients[client_idx] + self._session_id_to_client[session_id] = client + client = self._session_id_to_client[session_id] + + return client + + +class VLLMConverterResponsesToChatCompletionsState(BaseModel): + return_token_id_information: bool + + messages: List[NeMoGymChatCompletionMessageParam] = Field(default_factory=list) + + # We are mapping from Response input items to chat completions messages, which is many to one. + # Our state will accumulate the reasoning, chat, and tool calls for assistant messages. + content_buffer: str = "" # Buffer for reasoning and chat + tool_calls_buffer: List[NeMoGymChatCompletionMessageToolCallParam] = Field(default_factory=list) + + # Will only be populated if return_token_id_information is True. + token_information: Optional[TokenIDLogProbMixin] = None + + def flush_assistant(self) -> None: + if not (self.content_buffer or self.tool_calls_buffer): + return + + shared_params = dict( + content=self.content_buffer or None, + role="assistant", + tool_calls=self.tool_calls_buffer, + ) + + # We check here that self.token_information is non-empty since it's possible that some assistant messages are entirely inputs and are not generated by the model in this trajectory. + if self.return_token_id_information and self.token_information: + message = NeMoGymChatCompletionAssistantMessageForTrainingParam( + **shared_params, + **self.token_information.model_dump(), + ) + else: + message = NeMoGymChatCompletionAssistantMessageParam(**shared_params) + + self.messages.append(message) + + self.content_buffer = "" + self.tool_calls_buffer = [] + + +class VLLMConverter(BaseModel): + return_token_id_information: bool + uses_reasoning_parser: bool = True + + # ======================================================= + # Reasoning handling. This may change across models and model families + # ======================================================= + + THINK_TAG_PATTERN: ClassVar = re.compile(r"(.*?)", re.DOTALL) + + @staticmethod + def _wrap_reasoning_in_think_tags(texts: List[str]) -> str: + return "".join(f"{t}" for t in texts if t) + + @classmethod + def _parse_think_tags(cls, content: str) -> Tuple[List[str], str]: + # Extract reasoning content from between tags. + matches = cls.THINK_TAG_PATTERN.findall(content) + # Remove reasoning from main content + cleaned = cls.THINK_TAG_PATTERN.sub("", content) + return matches, cleaned + + # ======================================================= + # Response create params to Chat Completion create params + # ======================================================= + + def responses_to_chat_completion_create_params( + self, + responses_create_params: NeMoGymResponseCreateParamsNonStreaming, + ) -> NeMoGymChatCompletionCreateParamsNonStreaming: + responses_create_params = responses_create_params.model_dump(exclude_unset=True) + + # Tracks messages including reasoning for each respective message type helper function + state = VLLMConverterResponsesToChatCompletionsState( + return_token_id_information=self.return_token_id_information + ) + + # Input can be a string. Wrap in a ResponseInput-like + response_input = responses_create_params["input"] + if isinstance(response_input, str): + wrapped_input = { + "content": [ + { + "text": response_input, + "type": "input_text", + } + ], + "role": "user", + "type": "message", + } + input_messages = [wrapped_input] + else: + input_messages = responses_create_params.pop("input", []) + + for m in input_messages: + if not m.get("type") and m.get("role"): + m["type"] = "message" + + match m["type"]: + case "message": + self._format_message(m, state) + case "reasoning": + self._format_reasoning(m, state) + case "function_call": + self._format_function_call(m, state) + case "function_call_output": + self._format_function_call_output(m, state) + case _: # pragma: no cover + raise NotImplementedError(f"Unsupported message type: {m}") + + if self.return_token_id_information and m.get("prompt_token_ids"): + state.token_information = TokenIDLogProbMixin( + prompt_token_ids=m["prompt_token_ids"], + generation_token_ids=m["generation_token_ids"], + generation_log_probs=m["generation_log_probs"], + ) + + state.flush_assistant() + + model = responses_create_params.pop("model", None) + if model is not None: + responses_create_params["model"] = model + + # The corresponding parameter to `max_output_tokens`` is `max_tokens` + max_output_tokens = responses_create_params.pop("max_output_tokens", None) + if max_output_tokens is not None: + responses_create_params["max_tokens"] = max_output_tokens + + tools = responses_create_params.pop("tools", None) + if tools: + responses_create_params["tools"] = [] + for tool_dict in tools: + tool_dict = tool_dict.copy() + tool_dict.pop("type", None) + + # As of vLLM 0.17.1, vLLM Chat Completions does not accept this `strict` parameter on tool definitions that OpenAI accepts. + tool_dict.pop("strict", None) + responses_create_params["tools"].append( + NeMoGymChatCompletionToolParam(type="function", function=NeMoGymFunctionDefinition(**tool_dict)) + ) + + chat_completion_create_params = NeMoGymChatCompletionCreateParamsNonStreaming( + messages=state.messages, + **responses_create_params, + ) + + return chat_completion_create_params + + def _format_function_call_output( + self, + m: dict, + state: VLLMConverterResponsesToChatCompletionsState, + ) -> None: + state.flush_assistant() + + assert "call_id" in m + converted = NeMoGymChatCompletionToolMessageParam( + content=m["output"], + role="tool", + tool_call_id=m["call_id"], + ) + state.messages.append(converted) + + def _format_message( + self, + m: dict, + state: VLLMConverterResponsesToChatCompletionsState, + ) -> None: + content = m["content"] + + if isinstance(content, list) and m["role"] != "assistant": + converted_parts = [] + for part_param in content: + match part_param["type"]: + case "input_text": + converted_parts.append({"type": "text", "text": part_param["text"]}) + case "input_image": + image_url = part_param.get("image_url", "") + detail = part_param.get("detail", "auto") + converted_parts.append( + {"type": "image_url", "image_url": {"url": image_url, "detail": detail}} + ) + case _: + raise NotImplementedError(f"Unsupported part param type: {part_param['type']}") + content = converted_parts + m["content"] = content + + match m["role"]: + case "assistant": + # Handle reasoning + final_content = "" + if isinstance(m["content"], list): + content_str = "".join([part.get("text", "") for part in m["content"]]) + final_content += content_str + elif isinstance(m["content"], str): + final_content += m["content"] + else: + raise NotImplementedError( + f"Expected m['content'] to be str or list[dict], but got {type(m['content']).__name__!r}: {m['content']!r}" + ) + + converted = [] + state.content_buffer += final_content + case "user": + state.flush_assistant() + converted = [ + NeMoGymChatCompletionUserMessageParam( + content=content, + role="user", + ) + ] + # TODO: Revisit this in case we need separate handling. Not all chat templates may support the 'developer' role. + case "system": + state.flush_assistant() + converted = [ + NeMoGymChatCompletionSystemMessageParam( + content=content, + role="system", + ) + ] + case "developer": + state.flush_assistant() + converted = [ + NeMoGymChatCompletionDeveloperMessageParam( + content=content, + role="developer", + ) + ] + case _: # pragma: no cover + raise NotImplementedError(f"Unrecognized role for message: `{m['role']}`") + + state.messages.extend(converted) + + def _format_reasoning( + self, + m: dict, + state: VLLMConverterResponsesToChatCompletionsState, + ) -> None: + """ + Collects text from 'reasoning' messages in responses api and appends it to a buffer. + + This is done to group together one (or multiple) reasoning message(s) into a single, + cohesive block, later prepending it to a subsequent assistant message. + See: https://github.com/NVIDIA-NeMo/Gym/blob/main/docs/how-to-faq.md#faq-openai-responses-vs-chat-completions-api for an example of reasoning in responses api. + """ + if "summary" in m and m["summary"]: + texts = [s["text"] for s in m["summary"]] + state.content_buffer += self._wrap_reasoning_in_think_tags(texts) + + def _format_function_call( + self, + m: dict, + state: VLLMConverterResponsesToChatCompletionsState, + ) -> None: + assert "call_id" in m + tool_call = NeMoGymChatCompletionMessageToolCallParam( + id=m["call_id"], + function=NeMoGymChatCompletionMessageToolCallFunctionParam( + arguments=m["arguments"], + name=m["name"], + ), + type="function", + ) + state.tool_calls_buffer.append(tool_call) + + # ======================================================= + # Chat Completion to Response + # ======================================================= + + def postprocess_chat_response(self, choice: NeMoGymChoice) -> List[NeMoGymResponseOutputItem]: + return self.postprocess_assistant_message_dict(choice.message.model_dump()) + + def postprocess_assistant_message_dict(self, message_dict: Dict[str, Any]) -> List[NeMoGymResponseOutputItem]: + response_output = [] + + content = message_dict.get("content") or "" + if self.uses_reasoning_parser: + reasoning_matches, content = self._extract_reasoning_from_content(content) + else: + reasoning_matches = [] + if reasoning_matches: + reasoning_item = NeMoGymResponseReasoningItem( + id=f"rs_{uuid4().hex}", + type="reasoning", + summary=[ + NeMoGymSummary(text=reasoning_text, type="summary_text") for reasoning_text in reasoning_matches + ], + status="completed", + ) + response_output.append(reasoning_item) + + tool_calls_raw = message_dict.get("tool_calls", []) or [] + # We need to return at least one output item. When the model decides to just stop with no chat or tool calls + # We just add an output item with empty or null content here. This is prevalent e.g. in the case of base models that may not be the most reliable since they have not been instruction tuned. + has_empty_output = not (response_output or tool_calls_raw) + + if content or has_empty_output: + response_output.append( + NeMoGymResponseOutputMessage( + id=f"msg_{uuid4().hex}", + role=message_dict.get("role"), + content=[ + NeMoGymResponseOutputText( + type="output_text", + text=content, + annotations=[], + ) + ], + status="completed", + type="message", + ) + ) + + for tc in tool_calls_raw: + assert "id" in tc + response_output.append( + NeMoGymResponseFunctionToolCall( + name=tc["function"]["name"], + arguments=tc["function"]["arguments"], + call_id=tc["id"], + type="function_call", + status="completed", + id=tc["id"], + ) + ) + + # `"prompt_token_ids" in raw_message`: sometimes the model endpoint may go out of context length, in which case we return an empty response + # In these cases, there are no token id information provided. + if self.return_token_id_information and "prompt_token_ids" in message_dict: + last_response_output_item = response_output[-1] + train_cls = RESPONSES_TO_TRAIN[last_response_output_item.__class__] + response_output[-1] = train_cls( + **last_response_output_item.model_dump(), + prompt_token_ids=message_dict["prompt_token_ids"], + generation_token_ids=message_dict["generation_token_ids"], + generation_log_probs=message_dict["generation_log_probs"], + ) + + return response_output + + def _extract_reasoning_from_content(self, content: str) -> Tuple[List[str], str]: + # TODO: Currently only parses reasoning wrapped in ... tags. + # Maybe parameterize to support other model formats in the future. + return self._parse_think_tags(content) + + def chat_completions_messages_to_responses_items( + self, messages: List[Dict[str, Any]] + ) -> List[NeMoGymResponseOutputItem]: + output_items = [] + + for message in messages: + role = message["role"] + if role in ("user", "system", "developer"): + # vLLM may return None content + if message["content"] is None: + message["content"] = "" + output_items.append(NeMoGymEasyInputMessage.model_validate(message)) + elif role == "assistant": + output_items.extend(self.postprocess_assistant_message_dict(message)) + elif role == "tool": + output_items.append( + NeMoGymFunctionCallOutput( + call_id=message["tool_call_id"], + output=message["content"], + status="completed", + ) + ) + else: + raise NotImplementedError(f"Unrecognized role: {role}!") + + return output_items + + +def split_responses_input_output_items( + items: List[NeMoGymResponseOutputItem], +) -> Tuple[List[NeMoGymResponseOutputItem], List[NeMoGymResponseOutputItem]]: + if not items: + return [], [] + + for i, item in enumerate(items): + if ( + getattr(item, "role", None) == "assistant" + or getattr(item, "type", None) + in { + "reasoning", + "reasoning_item", + } + or getattr(item, "type", None) in ("function_call",) + ): + break + + return items[:i], items[i:] + + +if __name__ == "__main__": + VLLMModel.run_webserver() +elif is_nemo_gym_fastapi_entrypoint(__file__): + app = VLLMModel.run_webserver() # noqa: F401 diff --git a/responses_api_models/vllm_endpoint/client.py b/responses_api_models/vllm_endpoint/client.py new file mode 100644 index 0000000000..582e2bc212 --- /dev/null +++ b/responses_api_models/vllm_endpoint/client.py @@ -0,0 +1,101 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from asyncio import run + +from nemo_gym.server_utils import ServerClient + + +server_client = ServerClient.load_from_global_config() + + +async def main(): + task_1a = await server_client.post( + server_name="policy_model", + url_path="/v1/responses", + json={"input": [{"role": "user", "content": "hello"}]}, + ) + task_1b = await server_client.post( + server_name="policy_model", + url_path="/v1/responses", + json={ + "input": [ + {"role": "user", "content": "what's it like in sf?"}, + ], + "tools": [ + { + "type": "function", + "name": "get_weather", + "description": "", + "parameters": { + "type": "object", + "properties": { + "city": { + "type": "string", + "description": "", + }, + }, + "required": ["city"], + "additionalProperties": False, + }, + "strict": True, + } + ], + }, + ) + task_2a = await server_client.post( + server_name="policy_model", + url_path="/v1/chat/completions", + json={ + "messages": [{"role": "user", "content": "hello"}], + }, + ) + task_2b = await server_client.post( + server_name="policy_model", + url_path="/v1/chat/completions", + json={ + "messages": [ + {"role": "user", "content": "what's it like in sf?"}, + ], + "tools": [ + { + "type": "function", + "function": { + "name": "get_weather", + "description": "", + "parameters": { + "type": "object", + "properties": { + "city": { + "type": "string", + "description": "", + }, + }, + "required": ["city"], + "additionalProperties": False, + }, + "strict": True, + }, + } + ], + }, + ) + print(await task_1a.json()) + print(await task_1b.json()) + print(await task_2a.json()) + print(await task_2b.json()) + + +if __name__ == "__main__": + run(main()) diff --git a/responses_api_models/vllm_model/configs/vllm_model.yaml b/responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml similarity index 95% rename from responses_api_models/vllm_model/configs/vllm_model.yaml rename to responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml index 98dc6be426..67e8122469 100644 --- a/responses_api_models/vllm_model/configs/vllm_model.yaml +++ b/responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml @@ -1,6 +1,6 @@ policy_model: responses_api_models: - vllm_model: + vllm_endpoint: entrypoint: app.py base_url: ${policy_base_url} api_key: ${policy_api_key} diff --git a/responses_api_models/vllm_model/configs/vllm_model_for_training.yaml b/responses_api_models/vllm_endpoint/configs/vllm_model_for_training.yaml similarity index 92% rename from responses_api_models/vllm_model/configs/vllm_model_for_training.yaml rename to responses_api_models/vllm_endpoint/configs/vllm_model_for_training.yaml index 70727036c7..c693057d54 100644 --- a/responses_api_models/vllm_model/configs/vllm_model_for_training.yaml +++ b/responses_api_models/vllm_endpoint/configs/vllm_model_for_training.yaml @@ -1,6 +1,6 @@ policy_model: responses_api_models: - vllm_model: + vllm_endpoint: entrypoint: app.py base_url: ${policy_base_url} api_key: ${policy_api_key} diff --git a/responses_api_models/vllm_model/pyproject.toml b/responses_api_models/vllm_endpoint/pyproject.toml similarity index 100% rename from responses_api_models/vllm_model/pyproject.toml rename to responses_api_models/vllm_endpoint/pyproject.toml diff --git a/responses_api_models/vllm_model/tests/__init__.py b/responses_api_models/vllm_endpoint/tests/__init__.py similarity index 100% rename from responses_api_models/vllm_model/tests/__init__.py rename to responses_api_models/vllm_endpoint/tests/__init__.py diff --git a/responses_api_models/vllm_model/tests/round_trip_test_data.json b/responses_api_models/vllm_endpoint/tests/round_trip_test_data.json similarity index 100% rename from responses_api_models/vllm_model/tests/round_trip_test_data.json rename to responses_api_models/vllm_endpoint/tests/round_trip_test_data.json diff --git a/responses_api_models/vllm_model/tests/test_app.py b/responses_api_models/vllm_endpoint/tests/test_app.py similarity index 98% rename from responses_api_models/vllm_model/tests/test_app.py rename to responses_api_models/vllm_endpoint/tests/test_app.py index 729e5c044e..48253ecfd3 100644 --- a/responses_api_models/vllm_model/tests/test_app.py +++ b/responses_api_models/vllm_endpoint/tests/test_app.py @@ -53,7 +53,7 @@ NeMoGymSummary, ) from nemo_gym.server_utils import ServerClient -from responses_api_models.vllm_model.app import ( +from responses_api_models.vllm_endpoint.app import ( VLLMConverter, VLLMModel, VLLMModelConfig, @@ -820,8 +820,8 @@ def test_responses_multistep(self, monkeypatch: MonkeyPatch): mock_method, ) - monkeypatch.setattr("responses_api_models.vllm_model.app.time", lambda: FIXED_TIME) - monkeypatch.setattr("responses_api_models.vllm_model.app.uuid4", lambda: FakeUUID()) + monkeypatch.setattr("responses_api_models.vllm_endpoint.app.time", lambda: FIXED_TIME) + monkeypatch.setattr("responses_api_models.vllm_endpoint.app.uuid4", lambda: FakeUUID()) request_body = NeMoGymResponseCreateParamsNonStreaming( input=input_messages, @@ -977,8 +977,8 @@ def test_responses_multiturn(self, monkeypatch: MonkeyPatch): "chat_completions", mock_method, ) - monkeypatch.setattr("responses_api_models.vllm_model.app.time", lambda: FIXED_TIME) - monkeypatch.setattr("responses_api_models.vllm_model.app.uuid4", lambda: FakeUUID()) + monkeypatch.setattr("responses_api_models.vllm_endpoint.app.time", lambda: FIXED_TIME) + monkeypatch.setattr("responses_api_models.vllm_endpoint.app.uuid4", lambda: FakeUUID()) request_body = NeMoGymResponseCreateParamsNonStreaming( input=input_messages, @@ -1233,8 +1233,8 @@ def test_responses_multistep_multiturn(self, monkeypatch: MonkeyPatch): "chat_completions", mock_method, ) - monkeypatch.setattr("responses_api_models.vllm_model.app.time", lambda: FIXED_TIME) - monkeypatch.setattr("responses_api_models.vllm_model.app.uuid4", lambda: FakeUUID()) + monkeypatch.setattr("responses_api_models.vllm_endpoint.app.time", lambda: FIXED_TIME) + monkeypatch.setattr("responses_api_models.vllm_endpoint.app.uuid4", lambda: FakeUUID()) request_body = NeMoGymResponseCreateParamsNonStreaming( input=input_messages, @@ -1378,8 +1378,8 @@ def test_responses_e2e( app = server.setup_webserver() client = TestClient(app) - monkeypatch.setattr("responses_api_models.vllm_model.app.uuid4", lambda: FakeUUID()) - monkeypatch.setattr("responses_api_models.vllm_model.app.time", lambda: FIXED_TIME) + monkeypatch.setattr("responses_api_models.vllm_endpoint.app.uuid4", lambda: FakeUUID()) + monkeypatch.setattr("responses_api_models.vllm_endpoint.app.time", lambda: FIXED_TIME) responses_create_params = NeMoGymResponseCreateParamsNonStreaming(input=single_input) @@ -1762,8 +1762,8 @@ def test_responses_reasoning_parser(self, monkeypatch: MonkeyPatch): mock_method, ) - monkeypatch.setattr("responses_api_models.vllm_model.app.time", lambda: FIXED_TIME) - monkeypatch.setattr("responses_api_models.vllm_model.app.uuid4", lambda: FakeUUID()) + monkeypatch.setattr("responses_api_models.vllm_endpoint.app.time", lambda: FIXED_TIME) + monkeypatch.setattr("responses_api_models.vllm_endpoint.app.uuid4", lambda: FakeUUID()) request_body = NeMoGymResponseCreateParamsNonStreaming( input=input_messages, @@ -2220,8 +2220,8 @@ def test_responses_reasoning_parser_reasoning(self, monkeypatch: MonkeyPatch): mock_method, ) - monkeypatch.setattr("responses_api_models.vllm_model.app.time", lambda: FIXED_TIME) - monkeypatch.setattr("responses_api_models.vllm_model.app.uuid4", lambda: FakeUUID()) + monkeypatch.setattr("responses_api_models.vllm_endpoint.app.time", lambda: FIXED_TIME) + monkeypatch.setattr("responses_api_models.vllm_endpoint.app.uuid4", lambda: FakeUUID()) request_body = NeMoGymResponseCreateParamsNonStreaming( input=input_messages, @@ -2554,8 +2554,8 @@ def test_responses_sequential_reasoning_allowed_False(self, monkeypatch: MonkeyP tools=[], ) - monkeypatch.setattr("responses_api_models.vllm_model.app.time", lambda: FIXED_TIME) - monkeypatch.setattr("responses_api_models.vllm_model.app.uuid4", lambda: FakeUUID()) + monkeypatch.setattr("responses_api_models.vllm_endpoint.app.time", lambda: FIXED_TIME) + monkeypatch.setattr("responses_api_models.vllm_endpoint.app.uuid4", lambda: FakeUUID()) response = client.post( "/v1/responses", @@ -2693,9 +2693,9 @@ def test_chat_completion_to_responses_postprocessing( ChatCompletion output -> Response output """ - monkeypatch.setattr("responses_api_models.vllm_model.app.uuid4", lambda: FakeUUID()) + monkeypatch.setattr("responses_api_models.vllm_endpoint.app.uuid4", lambda: FakeUUID()) - monkeypatch.setattr("responses_api_models.vllm_model.app.time", lambda: FIXED_TIME) + monkeypatch.setattr("responses_api_models.vllm_endpoint.app.time", lambda: FIXED_TIME) choice = mock_chat_completion.choices[0] @@ -2723,8 +2723,8 @@ def test_extract_reasoning_from_content(self): assert main_content_none == "Just plain content here." def test_postprocess_chat_response_multiple_reasoning_items(self, monkeypatch: MonkeyPatch): - monkeypatch.setattr("responses_api_models.vllm_model.app.uuid4", lambda: FakeUUID()) - monkeypatch.setattr("responses_api_models.vllm_model.app.time", lambda: FIXED_TIME) + monkeypatch.setattr("responses_api_models.vllm_endpoint.app.uuid4", lambda: FakeUUID()) + monkeypatch.setattr("responses_api_models.vllm_endpoint.app.time", lambda: FIXED_TIME) raw_model_response = ( "I need to check the user's order ID." @@ -3016,7 +3016,7 @@ def test_round_trip_chat_completions_return_token_id_information(self) -> None: assert expected_output == chat_completion_create_params.model_dump() def test_whitespace_round_trip_chat_completions(self, monkeypatch: MonkeyPatch) -> None: - monkeypatch.setattr("responses_api_models.vllm_model.app.uuid4", lambda: FakeUUID()) + monkeypatch.setattr("responses_api_models.vllm_endpoint.app.uuid4", lambda: FakeUUID()) message = NeMoGymChatCompletionMessage( content=" \n \n I'm thinking \n \n \n \n I'm chatting! \n \n ", diff --git a/responses_api_models/vllm_model/__init__.py b/responses_api_models/vllm_model/__init__.py index e69de29bb2..3159bfe656 100644 --- a/responses_api_models/vllm_model/__init__.py +++ b/responses_api_models/vllm_model/__init__.py @@ -0,0 +1,14 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/responses_api_models/vllm_model/app.py b/responses_api_models/vllm_model/app.py index 4eace05d0b..010d77b35d 100644 --- a/responses_api_models/vllm_model/app.py +++ b/responses_api_models/vllm_model/app.py @@ -12,966 +12,12 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -import base64 -import json -import os -import re -from copy import deepcopy -from time import time -from typing import Any, ClassVar, Dict, List, Optional, Tuple, Union -from uuid import uuid4 +import warnings -from aiohttp.client_exceptions import ClientResponseError -from fastapi import Request -from pydantic import BaseModel, Field -from nemo_gym.base_responses_api_model import ( - BaseResponsesAPIModelConfig, - Body, - SimpleResponsesAPIModel, +warnings.warn( + "responses_api_models.vllm_model is renamed to responses_api_models.vllm_endpoint; update your imports.", + DeprecationWarning, + stacklevel=2, ) -from nemo_gym.openai_utils import ( - RESPONSES_TO_TRAIN, - NeMoGymAsyncOpenAI, - NeMoGymChatCompletion, - NeMoGymChatCompletionAssistantMessageForTrainingParam, - NeMoGymChatCompletionAssistantMessageParam, - NeMoGymChatCompletionCreateParamsNonStreaming, - NeMoGymChatCompletionDeveloperMessageParam, - NeMoGymChatCompletionMessage, - NeMoGymChatCompletionMessageParam, - NeMoGymChatCompletionMessageToolCallFunctionParam, - NeMoGymChatCompletionMessageToolCallParam, - NeMoGymChatCompletionSystemMessageParam, - NeMoGymChatCompletionToolMessageParam, - NeMoGymChatCompletionToolParam, - NeMoGymChatCompletionUserMessageParam, - NeMoGymChoice, - NeMoGymEasyInputMessage, - NeMoGymFunctionCallOutput, - NeMoGymFunctionDefinition, - NeMoGymResponse, - NeMoGymResponseCreateParamsNonStreaming, - NeMoGymResponseFunctionToolCall, - NeMoGymResponseInputTokensDetails, - NeMoGymResponseOutputItem, - NeMoGymResponseOutputMessage, - NeMoGymResponseOutputText, - NeMoGymResponseOutputTokensDetails, - NeMoGymResponseReasoningItem, - NeMoGymResponseUsage, - NeMoGymSummary, - TokenIDLogProbMixin, -) -from nemo_gym.server_utils import SESSION_ID_KEY, is_nemo_gym_fastapi_entrypoint - - -class VLLMModelConfig(BaseResponsesAPIModelConfig): - base_url: Union[str, List[str]] - api_key: str - model: str - return_token_id_information: bool - - uses_reasoning_parser: bool - uses_interleaved_reasoning: bool = True - replace_developer_role_with_system: bool = False - - # Whether or not the model can generate a reasoning output, and called again to produce additional reasoning output. - sequential_reasoning_allowed: bool = True - - # As of Feb 2026, we default this to False since majority of open source models aren't responses native with the exception of GPT-OSS - is_responses_native: bool = False - - chat_template_kwargs: Optional[Dict[str, Any]] = None - - # Corresponds to the extra_body of OpenAI Client. - extra_body: Optional[Dict[str, Any]] = None - - default_headers: Dict[str, str] = Field(default_factory=dict) - # Optional prefix for resolving relative ``metadata.audio_path`` (or - # entries in ``metadata.audio_paths``) against. Absolute paths are used - # as-is. When unset, relative paths raise. Audio is always inlined as a - # ``data:audio/;base64,...`` URI at request time — keeps the JSONL - # small without depending on vLLM's ``--allowed-local-media-path``. - audio_root: Optional[str] = None - - def model_post_init(self, context): - if isinstance(self.base_url, str): - self.base_url = [self.base_url] - return super().model_post_init(context) - - -class VLLMModel(SimpleResponsesAPIModel): - config: VLLMModelConfig - - def get_converter(self) -> "VLLMConverter": - """Return the converter used for Responses API <-> Chat Completions mapping. - - Override in subclasses (e.g. GenRMModel) to use a specialized converter. - """ - return VLLMConverter( - return_token_id_information=self.config.return_token_id_information, - uses_reasoning_parser=self.config.uses_reasoning_parser, - ) - - def model_post_init(self, context): - self._post_init() - return super().model_post_init(context) - - def _post_init(self) -> None: - self._clients = [ - NeMoGymAsyncOpenAI( - base_url=base_url, - api_key=self.config.api_key, - default_headers=self.config.default_headers, - ) - for base_url in self.config.base_url - ] - - self._session_id_to_client: Dict[str, NeMoGymAsyncOpenAI] = dict() - - self._converter = self.get_converter() - - async def responses( - self, request: Request, body: NeMoGymResponseCreateParamsNonStreaming = Body() - ) -> NeMoGymResponse: - if self.config.is_responses_native: - return await self._responses_native(request, body) - - # Response Create Params -> Chat Completion Create Params - chat_completion_create_params = self._converter.responses_to_chat_completion_create_params(body) - body.model = self.config.model - - # Chat Completion Create Params -> Chat Completion - chat_completion_response = await self.chat_completions(request, chat_completion_create_params) - - choice = chat_completion_response.choices[0] - - response_output = self._converter.postprocess_chat_response(choice) - response_output_dicts = [item.model_dump() for item in response_output] - - usage = None - if chat_completion_response.usage: - usage = NeMoGymResponseUsage( - input_tokens=chat_completion_response.usage.prompt_tokens, - input_tokens_details=NeMoGymResponseInputTokensDetails(cached_tokens=0), - output_tokens=chat_completion_response.usage.completion_tokens, - output_tokens_details=NeMoGymResponseOutputTokensDetails(reasoning_tokens=0), - total_tokens=chat_completion_response.usage.prompt_tokens - + chat_completion_response.usage.completion_tokens, - ) - - incomplete_details = None - if choice.finish_reason == "length": - incomplete_details = {"reason": "max_output_tokens"} - elif choice.finish_reason == "content_filter": - incomplete_details = {"reason": "content_filter"} - - # Chat Completion -> Response - return NeMoGymResponse( - id=f"resp_{uuid4().hex}", - created_at=int(time()), - model=body.model, - object="response", - output=response_output_dicts, - tool_choice=body.tool_choice if "tool_choice" in body else "auto", - parallel_tool_calls=body.parallel_tool_calls, - tools=body.tools, - temperature=body.temperature, - top_p=body.top_p, - background=body.background, - max_output_tokens=body.max_output_tokens, - max_tool_calls=body.max_tool_calls, - previous_response_id=body.previous_response_id, - prompt=body.prompt, - reasoning=body.reasoning, - service_tier=body.service_tier, - text=body.text, - top_logprobs=body.top_logprobs, - truncation=body.truncation, - metadata=body.metadata, - instructions=body.instructions, - user=body.user, - incomplete_details=incomplete_details, - usage=usage, - ) - - async def _responses_native( - self, request: Request, body: NeMoGymResponseCreateParamsNonStreaming - ) -> NeMoGymResponse: - """ - The following config parameters are effectively no-ops with Responses native models: - - uses_reasoning_parser: bool (Not applicable) - """ - # The following parameters could be supported, but have not been supported yet for Responses-native models: - if self.config.return_token_id_information: - raise NotImplementedError - if self.config.replace_developer_role_with_system: - raise NotImplementedError - if not self.config.sequential_reasoning_allowed: - raise NotImplementedError - - body_dict = body.model_dump(exclude_unset=True) - body_dict["model"] = self.config.model - if self.config.chat_template_kwargs: - body_dict["chat_template_kwargs"] = deepcopy(self.config.chat_template_kwargs) - if self.config.extra_body: - body_dict = self.config.extra_body | body_dict - - client = self._resolve_client(request) - response_dict = await client.create_response(**body_dict) - - return NeMoGymResponse.model_validate(response_dict) - - # Mapping from common audio file extensions to MIME subtypes used in the - # ``data:audio/;base64,...`` URI. vLLM-side decoders inspect the - # subtype to pick a backend (libsndfile, ffmpeg, …); guessing wrong would - # silently mis-decode, so we keep the table conservative and raise on - # unknown extensions instead of falling back to ``wav``. - _AUDIO_EXT_TO_MIME: ClassVar[Dict[str, str]] = { - ".wav": "wav", - ".flac": "flac", - ".mp3": "mpeg", - ".m4a": "mp4", - ".ogg": "ogg", - ".opus": "opus", - } - - def _resolve_audio_path_to_url(self, audio_path: str) -> str: - """Turn an ``audio_path`` reference into a ``data:audio/...;base64`` URI. - - Reads the file and inlines it as a base64 data URI at request time - — same strategy NeMo Skills' ``VLLMMultimodalModel.content_text_to_list`` - uses (read once per request, hand vLLM a self-contained content - block). Keeps the on-disk JSONL small without requiring any vLLM - server-side flag. - - Relative paths are resolved against ``config.audio_root``; without - it, relative paths raise so the failure mode is loud rather than - silently reading from the server CWD. - """ - if os.path.isabs(audio_path): - resolved = audio_path - elif self.config.audio_root: - resolved = os.path.join(self.config.audio_root, audio_path) - else: - raise ValueError( - f"metadata.audio_path={audio_path!r} is relative but VLLMModelConfig.audio_root " - "is unset. Set audio_root in the model config or use absolute paths." - ) - - if not os.path.isfile(resolved): - raise FileNotFoundError(f"metadata.audio_path resolved to {resolved!r}, which does not exist.") - - ext = os.path.splitext(resolved)[1].lower() - mime = self._AUDIO_EXT_TO_MIME.get(ext) - if mime is None: - raise ValueError( - f"Unsupported audio extension {ext!r} for {resolved!r}. Supported: {sorted(self._AUDIO_EXT_TO_MIME)}." - ) - with open(resolved, "rb") as f: - encoded = base64.b64encode(f.read()).decode("ascii") - return f"data:audio/{mime};base64,{encoded}" - - def _preprocess_chat_completion_create_params(self, request: Request, body_dict: Dict[str, Any]) -> Dict[str, Any]: - """Preprocess the body dict before issuing a chat completion request. - - Subclasses can override this to apply model-specific transformations - (e.g. role remapping, extra sampling params). The base implementation - handles the features driven by ``VLLMModelConfig``. - - Args: - request: The originating FastAPI request (available for session / - client resolution if needed by subclasses). - body_dict: Mutable dict produced by ``body.model_dump(exclude_unset=True)``. - - Returns: - The (possibly mutated) ``body_dict`` that will be forwarded to - ``client.create_chat_completion``. - """ - if self.config.replace_developer_role_with_system: - for message_dict in body_dict["messages"]: - if message_dict.get("role") == "developer": - message_dict["role"] = "system" - - body_dict["model"] = self.config.model - - chat_template_kwargs = {} - if self.config.chat_template_kwargs: - chat_template_kwargs = deepcopy(self.config.chat_template_kwargs) - - metadata = body_dict.get("metadata", dict()) - - # Merge global config chat_template_kwargs with per-request overrides in metadata (e.g. per-sample reasoning on/off) - metadata_chat_template_kwargs_str = metadata.get("chat_template_kwargs", "{}") - chat_template_kwargs.update(json.loads(metadata_chat_template_kwargs_str)) - - if chat_template_kwargs: - body_dict["chat_template_kwargs"] = chat_template_kwargs - - # Merge global config extra_body with per-request overrides from metadata - extra_body = {} - if self.config.extra_body: - extra_body = deepcopy(self.config.extra_body) - - metadata_extra_body_str = metadata.get("extra_body", "{}") - extra_body.update(json.loads(metadata_extra_body_str)) - - if self.config.return_token_id_information: - body_dict |= dict( - logprobs=True, - # Typically passed via OpenAI client extra_body. - return_tokens_as_token_ids=True, - # TODO add this when NeMo RL upgrades to vLLM 0.10.2 support for prompt token ids - # For prompt and generation token IDs - # return_token_ids=True, - # For prompt token IDs - # prompt_logprobs=0, - ) - - if self.config.uses_reasoning_parser: - for message_dict in body_dict["messages"]: - if message_dict.get("role") != "assistant" or "content" not in message_dict: - continue - - content = message_dict["content"] - if isinstance(content, str): - reasoning_matches, remaining_content = self._converter._extract_reasoning_from_content(content) - message_dict["content"] = remaining_content - if reasoning_matches and self.config.uses_interleaved_reasoning: - message_dict["reasoning_content"] = reasoning_matches[0] - - # TODO when NeMo RL migrates to vLLM>=0.16.0, remove the reasoning_content support above. - # Starting with vLLM 0.16.0, the `reasoning_content` field has been deprecated in favor of just `reasoning` - message_dict["reasoning"] = reasoning_matches[0] - elif isinstance(content, list): - reasoning_content = None - for content_item_dict in content: - reasoning_matches, remaining_content = self._converter._extract_reasoning_from_content( - content_item_dict["text"] - ) - assert reasoning_content is None or not reasoning_matches, ( - f"Found multiple reasoning matches in a single assistant message content item list!\nMessage: {message_dict}" - ) - - # Even though we set the reasoning content already here, we still loop through all the content item dicts for the assert above. - content_item_dict["text"] = remaining_content - if reasoning_matches and self.config.uses_interleaved_reasoning: - message_dict["reasoning_content"] = reasoning_matches[0] - # See the TODO wrt reasoning_content above - message_dict["reasoning"] = reasoning_matches[0] - elif not content: - # No content or content None is a no-op - pass - else: - raise NotImplementedError - - if extra_body: - body_dict = extra_body | body_dict - - # Audio sidechannel: rows can carry audio on - # ``responses_create_params.metadata`` via three mutually exclusive - # keys, all spliced as ``audio_url`` content blocks into the most - # recent user message before forwarding to vLLM Chat Completions: - # - # * ``audio_data`` — a single pre-built ``data:audio/...;base64,`` - # URI inlined into the JSONL. Self-contained; - # no audio root needed at request time. - # * ``audio_path`` — a single file path; resolved against - # ``config.audio_root`` and encoded to a data - # URI at request time. - # * ``audio_paths`` — list of file paths; each encoded and spliced - # in order. Mirrors NeMo Skills' ``audios`` - # multi-clip schema. - # - # OpenAI's Responses API content union has no audio variant (audio - # types exist as orphans in the SDK but aren't members of - # ``ResponseInputContentParam``), so audio rows can't ride in - # ``input.content`` directly — the metadata-sidechannel hop lets - # audio benchmarks carry audio without a Gym schema change. - # - # Audio is placed BEFORE text in the content list (some audio - # models care). No-op when none of the three keys are present, so - # non-audio benchmarks are unaffected. - audio_keys_present = [k for k in ("audio_data", "audio_path", "audio_paths") if metadata.get(k)] - if len(audio_keys_present) > 1: - raise ValueError( - f"metadata audio keys are mutually exclusive — got {audio_keys_present}. " - "Set exactly one of audio_data / audio_path / audio_paths per row." - ) - - audio_urls: List[str] = [] - if metadata.get("audio_data"): - audio_urls.append(metadata["audio_data"]) - metadata.pop("audio_data", None) - elif metadata.get("audio_path"): - audio_urls.append(self._resolve_audio_path_to_url(metadata["audio_path"])) - metadata.pop("audio_path", None) - elif metadata.get("audio_paths"): - paths = metadata["audio_paths"] - if not isinstance(paths, list): - raise ValueError(f"metadata.audio_paths must be a list, got {type(paths).__name__}.") - audio_urls.extend(self._resolve_audio_path_to_url(p) for p in paths) - metadata.pop("audio_paths", None) - - if audio_urls: - if not metadata and "metadata" in body_dict: - body_dict.pop("metadata", None) - - audio_blocks = [{"type": "audio_url", "audio_url": {"url": url}} for url in audio_urls] - messages = body_dict.get("messages", []) or [] - for msg in reversed(messages): - if msg.get("role") != "user": - continue - content = msg.get("content") - if isinstance(content, str): - msg["content"] = audio_blocks + [{"type": "text", "text": content}] - elif isinstance(content, list): - msg["content"] = audio_blocks + list(content) - else: - # ``None`` / unexpected shape — replace with a fresh content list - msg["content"] = list(audio_blocks) - break - else: - # No user message found — create one with just the audio blocks. - body_dict.setdefault("messages", []).append({"role": "user", "content": list(audio_blocks)}) - - return body_dict - - async def chat_completions( - self, request: Request, body: NeMoGymChatCompletionCreateParamsNonStreaming = Body() - ) -> NeMoGymChatCompletion: - body_dict = body.model_dump(exclude_unset=True) - body_dict = self._preprocess_chat_completion_create_params(request, body_dict) - - client = self._resolve_client(request) - - if not self.config.sequential_reasoning_allowed: - last_message = body_dict["messages"][-1] - if last_message["role"] == "assistant" and not (last_message["content"] or last_message.get("tool_calls")): - res = self._create_empty_chat_completion() - res.choices[0].finish_reason = "content_filter" - return res - - try: - chat_completion_dict = await client.create_chat_completion(**body_dict) - except ClientResponseError as e: - """ - Example messages for out of context length: - - 1. https://github.com/vllm-project/vllm/blob/685c99ee77b4818dcdd15b30fe0e0eff0d5d22ec/vllm/entrypoints/openai/serving_engine.py#L914 - ```json - {"object":"error","message":"This model\'s maximum context length is 32768 tokens. However, you requested 32818 tokens in the messages, Please reduce the length of the messages. None","type":"BadRequestError","param":null,"code":400} - ``` - 2. https://github.com/vllm-project/vllm/blob/685c99ee77b4818dcdd15b30fe0e0eff0d5d22ec/vllm/entrypoints/openai/serving_engine.py#L940 - 3. https://github.com/vllm-project/vllm/blob/685c99ee77b4818dcdd15b30fe0e0eff0d5d22ec/vllm/entrypoints/openai/serving_engine.py#L948 - 4. https://github.com/vllm-project/vllm/blob/685c99ee77b4818dcdd15b30fe0e0eff0d5d22ec/vllm/sampling_params.py#L463 - """ - result_content_str = e.response_content.decode() - - is_out_of_context_length = e.status == 400 and ( - "context length" in result_content_str or "max_tokens" in result_content_str - ) - if is_out_of_context_length: - res = self._create_empty_chat_completion() - res.choices[0].finish_reason = "length" - return res - else: - raise e - - choice_dict = chat_completion_dict["choices"][0] - if self.config.uses_reasoning_parser: - # See the TODO wrt reasoning_content above - reasoning_content = choice_dict["message"].get("reasoning_content") or choice_dict["message"].get( - "reasoning" - ) - if reasoning_content: - choice_dict["message"].pop("reasoning_content", None) - # See the TODO wrt reasoning_content above - choice_dict["message"].pop("reasoning", None) - - # We wrap this here in think tags for Gym's sake and to return a valid OpenAI Chat Completions response. - choice_dict["message"]["content"] = self._converter._wrap_reasoning_in_think_tags( - [reasoning_content] - ) + (choice_dict["message"].get("content") or "") - else: - # See the TODO wrt reasoning_content above - assert not (choice_dict["message"].get("reasoning_content") or choice_dict["message"].get("reasoning")), ( - f"NeMo Gym server `{self.config.name}` config has explicitly been set to not use a reasoning parser i.e. `uses_reasoning_parser: false`. Please do not use a reasoning parser in your vLLM endpoint, or fix the `{self.config.name}` server config!" - ) - - if self.config.return_token_id_information and "prompt_token_ids" not in choice_dict["message"]: - log_probs = choice_dict["logprobs"]["content"] - generation_log_probs = [log_prob["logprob"] for log_prob in log_probs] - - """ - START TODO remove this when NeMo RL upgrades to vLLM 0.10.2 support for prompt token ids - """ - # Looks like `"token_id:151667"` - generation_token_ids = [log_prob["token"].removeprefix("token_id:") for log_prob in log_probs] - - # The tokenize endpoint doesn't accept any sampling parameters - # The only relevant params are model, messages, and tools. - # - # IMPORTANT: pass through chat-template knobs (e.g. enable_thinking) - # when tokenizing, otherwise `prompt_token_ids` (and therefore logged - # `prompt_str`) can be built with different chat template settings than - # the actual generation request. - tokenize_body_dict = dict() - for key in ("model", "messages", "tools", "chat_template_kwargs"): - if key in body_dict: - tokenize_body_dict[key] = body_dict[key] - - # The base url has /v1 at the end but vLLM's tokenize endpoint does not have v1, hence the .. - tokenize_response = await client.create_tokenize(**tokenize_body_dict) - """ - END - """ - - message_dict = choice_dict["message"] - message_dict.update( - dict( - # TODO add this when NeMo RL upgrades to vLLM 0.10.2 support for prompt token ids - # prompt_token_ids=chat_completion_dict["prompt_token_ids"], - prompt_token_ids=tokenize_response["tokens"], - # generation_token_ids=choice_dict["token_ids"], - generation_token_ids=generation_token_ids, - generation_log_probs=generation_log_probs, - ) - ) - - # Clean the duplicated information - choice_dict.pop("logprobs") - # TODO add this when NeMo RL upgrades to vLLM 0.10.2 support for prompt token ids - # chat_completion_dict.pop("prompt_token_ids") - # choice_dict.pop("token_ids") - - return NeMoGymChatCompletion.model_validate(chat_completion_dict) - - def _create_empty_chat_completion(self) -> NeMoGymChatCompletion: - return NeMoGymChatCompletion( - id="chtcmpl-123", - object="chat.completion", - created=int(time()), - model=self.config.model, - choices=[ - NeMoGymChoice( - index=0, - finish_reason="stop", - message=NeMoGymChatCompletionMessage( - role="assistant", - content=None, - tool_calls=None, - ), - ) - ], - ) - - def _resolve_client(self, request: Request) -> NeMoGymAsyncOpenAI: - session_id = request.session[SESSION_ID_KEY] - if session_id not in self._session_id_to_client: - # There is probably a better way to select the endpoint for this request. But this will do for now. - client_idx = len(self._session_id_to_client) % len(self._clients) - client = self._clients[client_idx] - self._session_id_to_client[session_id] = client - client = self._session_id_to_client[session_id] - - return client - - -class VLLMConverterResponsesToChatCompletionsState(BaseModel): - return_token_id_information: bool - - messages: List[NeMoGymChatCompletionMessageParam] = Field(default_factory=list) - - # We are mapping from Response input items to chat completions messages, which is many to one. - # Our state will accumulate the reasoning, chat, and tool calls for assistant messages. - content_buffer: str = "" # Buffer for reasoning and chat - tool_calls_buffer: List[NeMoGymChatCompletionMessageToolCallParam] = Field(default_factory=list) - - # Will only be populated if return_token_id_information is True. - token_information: Optional[TokenIDLogProbMixin] = None - - def flush_assistant(self) -> None: - if not (self.content_buffer or self.tool_calls_buffer): - return - - shared_params = dict( - content=self.content_buffer or None, - role="assistant", - tool_calls=self.tool_calls_buffer, - ) - - # We check here that self.token_information is non-empty since it's possible that some assistant messages are entirely inputs and are not generated by the model in this trajectory. - if self.return_token_id_information and self.token_information: - message = NeMoGymChatCompletionAssistantMessageForTrainingParam( - **shared_params, - **self.token_information.model_dump(), - ) - else: - message = NeMoGymChatCompletionAssistantMessageParam(**shared_params) - - self.messages.append(message) - - self.content_buffer = "" - self.tool_calls_buffer = [] - - -class VLLMConverter(BaseModel): - return_token_id_information: bool - uses_reasoning_parser: bool = True - - # ======================================================= - # Reasoning handling. This may change across models and model families - # ======================================================= - - THINK_TAG_PATTERN: ClassVar = re.compile(r"(.*?)", re.DOTALL) - - @staticmethod - def _wrap_reasoning_in_think_tags(texts: List[str]) -> str: - return "".join(f"{t}" for t in texts if t) - - @classmethod - def _parse_think_tags(cls, content: str) -> Tuple[List[str], str]: - # Extract reasoning content from between tags. - matches = cls.THINK_TAG_PATTERN.findall(content) - # Remove reasoning from main content - cleaned = cls.THINK_TAG_PATTERN.sub("", content) - return matches, cleaned - - # ======================================================= - # Response create params to Chat Completion create params - # ======================================================= - - def responses_to_chat_completion_create_params( - self, - responses_create_params: NeMoGymResponseCreateParamsNonStreaming, - ) -> NeMoGymChatCompletionCreateParamsNonStreaming: - responses_create_params = responses_create_params.model_dump(exclude_unset=True) - - # Tracks messages including reasoning for each respective message type helper function - state = VLLMConverterResponsesToChatCompletionsState( - return_token_id_information=self.return_token_id_information - ) - - # Input can be a string. Wrap in a ResponseInput-like - response_input = responses_create_params["input"] - if isinstance(response_input, str): - wrapped_input = { - "content": [ - { - "text": response_input, - "type": "input_text", - } - ], - "role": "user", - "type": "message", - } - input_messages = [wrapped_input] - else: - input_messages = responses_create_params.pop("input", []) - - for m in input_messages: - if not m.get("type") and m.get("role"): - m["type"] = "message" - - match m["type"]: - case "message": - self._format_message(m, state) - case "reasoning": - self._format_reasoning(m, state) - case "function_call": - self._format_function_call(m, state) - case "function_call_output": - self._format_function_call_output(m, state) - case _: # pragma: no cover - raise NotImplementedError(f"Unsupported message type: {m}") - - if self.return_token_id_information and m.get("prompt_token_ids"): - state.token_information = TokenIDLogProbMixin( - prompt_token_ids=m["prompt_token_ids"], - generation_token_ids=m["generation_token_ids"], - generation_log_probs=m["generation_log_probs"], - ) - - state.flush_assistant() - - model = responses_create_params.pop("model", None) - if model is not None: - responses_create_params["model"] = model - - # The corresponding parameter to `max_output_tokens`` is `max_tokens` - max_output_tokens = responses_create_params.pop("max_output_tokens", None) - if max_output_tokens is not None: - responses_create_params["max_tokens"] = max_output_tokens - - tools = responses_create_params.pop("tools", None) - if tools: - responses_create_params["tools"] = [] - for tool_dict in tools: - tool_dict = tool_dict.copy() - tool_dict.pop("type", None) - - # As of vLLM 0.17.1, vLLM Chat Completions does not accept this `strict` parameter on tool definitions that OpenAI accepts. - tool_dict.pop("strict", None) - responses_create_params["tools"].append( - NeMoGymChatCompletionToolParam(type="function", function=NeMoGymFunctionDefinition(**tool_dict)) - ) - - chat_completion_create_params = NeMoGymChatCompletionCreateParamsNonStreaming( - messages=state.messages, - **responses_create_params, - ) - - return chat_completion_create_params - - def _format_function_call_output( - self, - m: dict, - state: VLLMConverterResponsesToChatCompletionsState, - ) -> None: - state.flush_assistant() - - assert "call_id" in m - converted = NeMoGymChatCompletionToolMessageParam( - content=m["output"], - role="tool", - tool_call_id=m["call_id"], - ) - state.messages.append(converted) - - def _format_message( - self, - m: dict, - state: VLLMConverterResponsesToChatCompletionsState, - ) -> None: - content = m["content"] - - if isinstance(content, list) and m["role"] != "assistant": - converted_parts = [] - for part_param in content: - match part_param["type"]: - case "input_text": - converted_parts.append({"type": "text", "text": part_param["text"]}) - case "input_image": - image_url = part_param.get("image_url", "") - detail = part_param.get("detail", "auto") - converted_parts.append( - {"type": "image_url", "image_url": {"url": image_url, "detail": detail}} - ) - case _: - raise NotImplementedError(f"Unsupported part param type: {part_param['type']}") - content = converted_parts - m["content"] = content - - match m["role"]: - case "assistant": - # Handle reasoning - final_content = "" - if isinstance(m["content"], list): - content_str = "".join([part.get("text", "") for part in m["content"]]) - final_content += content_str - elif isinstance(m["content"], str): - final_content += m["content"] - else: - raise NotImplementedError( - f"Expected m['content'] to be str or list[dict], but got {type(m['content']).__name__!r}: {m['content']!r}" - ) - - converted = [] - state.content_buffer += final_content - case "user": - state.flush_assistant() - converted = [ - NeMoGymChatCompletionUserMessageParam( - content=content, - role="user", - ) - ] - # TODO: Revisit this in case we need separate handling. Not all chat templates may support the 'developer' role. - case "system": - state.flush_assistant() - converted = [ - NeMoGymChatCompletionSystemMessageParam( - content=content, - role="system", - ) - ] - case "developer": - state.flush_assistant() - converted = [ - NeMoGymChatCompletionDeveloperMessageParam( - content=content, - role="developer", - ) - ] - case _: # pragma: no cover - raise NotImplementedError(f"Unrecognized role for message: `{m['role']}`") - - state.messages.extend(converted) - - def _format_reasoning( - self, - m: dict, - state: VLLMConverterResponsesToChatCompletionsState, - ) -> None: - """ - Collects text from 'reasoning' messages in responses api and appends it to a buffer. - - This is done to group together one (or multiple) reasoning message(s) into a single, - cohesive block, later prepending it to a subsequent assistant message. - See: https://github.com/NVIDIA-NeMo/Gym/blob/main/docs/how-to-faq.md#faq-openai-responses-vs-chat-completions-api for an example of reasoning in responses api. - """ - if "summary" in m and m["summary"]: - texts = [s["text"] for s in m["summary"]] - state.content_buffer += self._wrap_reasoning_in_think_tags(texts) - - def _format_function_call( - self, - m: dict, - state: VLLMConverterResponsesToChatCompletionsState, - ) -> None: - assert "call_id" in m - tool_call = NeMoGymChatCompletionMessageToolCallParam( - id=m["call_id"], - function=NeMoGymChatCompletionMessageToolCallFunctionParam( - arguments=m["arguments"], - name=m["name"], - ), - type="function", - ) - state.tool_calls_buffer.append(tool_call) - - # ======================================================= - # Chat Completion to Response - # ======================================================= - - def postprocess_chat_response(self, choice: NeMoGymChoice) -> List[NeMoGymResponseOutputItem]: - return self.postprocess_assistant_message_dict(choice.message.model_dump()) - - def postprocess_assistant_message_dict(self, message_dict: Dict[str, Any]) -> List[NeMoGymResponseOutputItem]: - response_output = [] - - content = message_dict.get("content") or "" - if self.uses_reasoning_parser: - reasoning_matches, content = self._extract_reasoning_from_content(content) - else: - reasoning_matches = [] - if reasoning_matches: - reasoning_item = NeMoGymResponseReasoningItem( - id=f"rs_{uuid4().hex}", - type="reasoning", - summary=[ - NeMoGymSummary(text=reasoning_text, type="summary_text") for reasoning_text in reasoning_matches - ], - status="completed", - ) - response_output.append(reasoning_item) - - tool_calls_raw = message_dict.get("tool_calls", []) or [] - # We need to return at least one output item. When the model decides to just stop with no chat or tool calls - # We just add an output item with empty or null content here. This is prevalent e.g. in the case of base models that may not be the most reliable since they have not been instruction tuned. - has_empty_output = not (response_output or tool_calls_raw) - - if content or has_empty_output: - response_output.append( - NeMoGymResponseOutputMessage( - id=f"msg_{uuid4().hex}", - role=message_dict.get("role"), - content=[ - NeMoGymResponseOutputText( - type="output_text", - text=content, - annotations=[], - ) - ], - status="completed", - type="message", - ) - ) - - for tc in tool_calls_raw: - assert "id" in tc - response_output.append( - NeMoGymResponseFunctionToolCall( - name=tc["function"]["name"], - arguments=tc["function"]["arguments"], - call_id=tc["id"], - type="function_call", - status="completed", - id=tc["id"], - ) - ) - - # `"prompt_token_ids" in raw_message`: sometimes the model endpoint may go out of context length, in which case we return an empty response - # In these cases, there are no token id information provided. - if self.return_token_id_information and "prompt_token_ids" in message_dict: - last_response_output_item = response_output[-1] - train_cls = RESPONSES_TO_TRAIN[last_response_output_item.__class__] - response_output[-1] = train_cls( - **last_response_output_item.model_dump(), - prompt_token_ids=message_dict["prompt_token_ids"], - generation_token_ids=message_dict["generation_token_ids"], - generation_log_probs=message_dict["generation_log_probs"], - ) - - return response_output - - def _extract_reasoning_from_content(self, content: str) -> Tuple[List[str], str]: - # TODO: Currently only parses reasoning wrapped in ... tags. - # Maybe parameterize to support other model formats in the future. - return self._parse_think_tags(content) - - def chat_completions_messages_to_responses_items( - self, messages: List[Dict[str, Any]] - ) -> List[NeMoGymResponseOutputItem]: - output_items = [] - - for message in messages: - role = message["role"] - if role in ("user", "system", "developer"): - # vLLM may return None content - if message["content"] is None: - message["content"] = "" - output_items.append(NeMoGymEasyInputMessage.model_validate(message)) - elif role == "assistant": - output_items.extend(self.postprocess_assistant_message_dict(message)) - elif role == "tool": - output_items.append( - NeMoGymFunctionCallOutput( - call_id=message["tool_call_id"], - output=message["content"], - status="completed", - ) - ) - else: - raise NotImplementedError(f"Unrecognized role: {role}!") - - return output_items - - -def split_responses_input_output_items( - items: List[NeMoGymResponseOutputItem], -) -> Tuple[List[NeMoGymResponseOutputItem], List[NeMoGymResponseOutputItem]]: - if not items: - return [], [] - - for i, item in enumerate(items): - if ( - getattr(item, "role", None) == "assistant" - or getattr(item, "type", None) - in { - "reasoning", - "reasoning_item", - } - or getattr(item, "type", None) in ("function_call",) - ): - break - - return items[:i], items[i:] - - -if __name__ == "__main__": - VLLMModel.run_webserver() -elif is_nemo_gym_fastapi_entrypoint(__file__): - app = VLLMModel.run_webserver() # noqa: F401 +from responses_api_models.vllm_endpoint.app import * # noqa: E402,F401,F403 diff --git a/responses_api_models/vllm_model/client.py b/responses_api_models/vllm_model/client.py index 582e2bc212..4cc98b9db3 100644 --- a/responses_api_models/vllm_model/client.py +++ b/responses_api_models/vllm_model/client.py @@ -12,90 +12,4 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -from asyncio import run - -from nemo_gym.server_utils import ServerClient - - -server_client = ServerClient.load_from_global_config() - - -async def main(): - task_1a = await server_client.post( - server_name="policy_model", - url_path="/v1/responses", - json={"input": [{"role": "user", "content": "hello"}]}, - ) - task_1b = await server_client.post( - server_name="policy_model", - url_path="/v1/responses", - json={ - "input": [ - {"role": "user", "content": "what's it like in sf?"}, - ], - "tools": [ - { - "type": "function", - "name": "get_weather", - "description": "", - "parameters": { - "type": "object", - "properties": { - "city": { - "type": "string", - "description": "", - }, - }, - "required": ["city"], - "additionalProperties": False, - }, - "strict": True, - } - ], - }, - ) - task_2a = await server_client.post( - server_name="policy_model", - url_path="/v1/chat/completions", - json={ - "messages": [{"role": "user", "content": "hello"}], - }, - ) - task_2b = await server_client.post( - server_name="policy_model", - url_path="/v1/chat/completions", - json={ - "messages": [ - {"role": "user", "content": "what's it like in sf?"}, - ], - "tools": [ - { - "type": "function", - "function": { - "name": "get_weather", - "description": "", - "parameters": { - "type": "object", - "properties": { - "city": { - "type": "string", - "description": "", - }, - }, - "required": ["city"], - "additionalProperties": False, - }, - "strict": True, - }, - } - ], - }, - ) - print(await task_1a.json()) - print(await task_1b.json()) - print(await task_2a.json()) - print(await task_2b.json()) - - -if __name__ == "__main__": - run(main()) +from responses_api_models.vllm_endpoint.client import * # noqa: F401,F403 diff --git a/responses_api_models/local_vllm_model/README.md b/responses_api_models/vllm_server/README.md similarity index 54% rename from responses_api_models/local_vllm_model/README.md rename to responses_api_models/vllm_server/README.md index 6fbbbe6989..6eac651c41 100644 --- a/responses_api_models/local_vllm_model/README.md +++ b/responses_api_models/vllm_server/README.md @@ -6,7 +6,7 @@ Run this on a single GPU node! Set tensor_parallel_size * data_parallel_size to ```bash config_paths="resources_servers/example_single_tool_call/configs/example_single_tool_call.yaml,\ -responses_api_models/local_vllm_model/configs/nano_v3_single_node.yaml" +responses_api_models/vllm_server/configs/nano_v3_single_node.yaml" ng_run "+config_paths=[${config_paths}]" \ ++policy_model.responses_api_models.local_vllm_model.vllm_serve_kwargs.tensor_parallel_size=4 \ ++policy_model.responses_api_models.local_vllm_model.vllm_serve_kwargs.data_parallel_size=2 \ @@ -27,15 +27,15 @@ python responses_api_agents/simple_agent/client.py # E2E sanity testing See the following scripts: - 1 node - - responses_api_models/local_vllm_model/test_scripts/1_node/1_instance_1x8.sh - - responses_api_models/local_vllm_model/test_scripts/1_node/1_instance_2x4.sh - - responses_api_models/local_vllm_model/test_scripts/1_node/2_instances_1x4.sh + - responses_api_models/vllm_server/test_scripts/1_node/1_instance_1x8.sh + - responses_api_models/vllm_server/test_scripts/1_node/1_instance_2x4.sh + - responses_api_models/vllm_server/test_scripts/1_node/2_instances_1x4.sh - 2 nodes - - responses_api_models/local_vllm_model/test_scripts/2_nodes/2_instances_1x8.sh - - responses_api_models/local_vllm_model/test_scripts/2_nodes/2_instances_2x4.sh - - [Not supported yet] responses_api_models/local_vllm_model/test_scripts/2_nodes/1_instance_1x16.sh + - responses_api_models/vllm_server/test_scripts/2_nodes/2_instances_1x8.sh + - responses_api_models/vllm_server/test_scripts/2_nodes/2_instances_2x4.sh + - [Not supported yet] responses_api_models/vllm_server/test_scripts/2_nodes/1_instance_1x16.sh - 4 nodes - - [Not supported yet] responses_api_models/local_vllm_model/test_scripts/4_nodes/1_instance_2x16.sh - - [Not supported yet] responses_api_models/local_vllm_model/test_scripts/4_nodes/2_instances_1x16.sh + - [Not supported yet] responses_api_models/vllm_server/test_scripts/4_nodes/1_instance_2x16.sh + - [Not supported yet] responses_api_models/vllm_server/test_scripts/4_nodes/2_instances_1x16.sh - 8 nodes - - [Not supported yet] responses_api_models/local_vllm_model/test_scripts/8_nodes/2_instances_2x16.sh + - [Not supported yet] responses_api_models/vllm_server/test_scripts/8_nodes/2_instances_2x16.sh diff --git a/responses_api_models/vllm_server/__init__.py b/responses_api_models/vllm_server/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/responses_api_models/vllm_server/app.py b/responses_api_models/vllm_server/app.py new file mode 100644 index 0000000000..778cdd5a34 --- /dev/null +++ b/responses_api_models/vllm_server/app.py @@ -0,0 +1,256 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import sys +from argparse import Namespace +from pathlib import Path +from time import sleep +from typing import Any, Dict, List, Optional, Tuple, Union + +import ray +import requests +from pydantic import BaseModel, Field +from ray import available_resources, cluster_resources +from ray.util.placement_group import PlacementGroup +from ray.util.scheduling_strategies import PlacementGroupSchedulingStrategy +from requests.exceptions import ConnectionError +from vllm.entrypoints.openai.api_server import ( + FlexibleArgumentParser, + cli_env_setup, + make_arg_parser, + validate_parsed_serve_args, +) + +from nemo_gym.global_config import ( + DISALLOWED_PORTS_KEY_NAME, + find_open_port, + get_global_config_dict, + get_hf_token, +) +from responses_api_models.vllm_endpoint.app import VLLMModel, VLLMModelConfig +from responses_api_models.vllm_server.local_vllm_model_actor import LocalVLLMModelActor + + +class LocalVLLMModelConfig(VLLMModelConfig): + # We inherit these configs from VLLMModelConfig, but they are set to optional since they will be set later on after we spin up a model endpoint. + base_url: Union[str, List[str]] = Field(default_factory=list) + # Not used on local deployments + api_key: str = "dummy" # pragma: allowlist secret + + hf_home: Optional[str] = None + vllm_serve_kwargs: Dict[str, Any] + vllm_serve_env_vars: Dict[str, str] + + ray_worker_py_executable: str = sys.executable + + show_vllm_engine_stats: bool = False + debug: bool = False + + def model_post_init(self, context): + # Default to the .cache/huggingface in this directory. + if not self.hf_home: + current_directory = Path.cwd() + self.hf_home = str(current_directory / ".cache" / "huggingface") + + return super().model_post_init(context) + + +class GetInnerVLLMConfigResponse(BaseModel): + base_url: List[str] + api_key: str + model: str + + +class LocalVLLMModel(VLLMModel): + config: LocalVLLMModelConfig + + _local_vllm_model_actor: LocalVLLMModelActor + + def setup_webserver(self): + print("Starting vLLM server. This will take a few minutes...") + self.start_vllm_server() + + app = super().setup_webserver() + + # This route is only used to support LocalVLLMModelProxy + app.get("/get_inner_vllm_config")(self.get_inner_vllm_config) + + return app + + async def get_inner_vllm_config(self) -> GetInnerVLLMConfigResponse: + return GetInnerVLLMConfigResponse( + base_url=self.config.base_url, + api_key=self.config.api_key, + model=self.config.model, + ) + + def get_cache_dir(self) -> str: + # We need to reconstruct the cache dir as HF does it given HF_HOME. See https://github.com/huggingface/huggingface_hub/blob/b2723cad81f530e197d6e826f194c110bf92248e/src/huggingface_hub/constants.py#L146 + return str(Path(self.config.hf_home) / "hub") + + def _configure_vllm_serve(self) -> Tuple[Namespace, Dict[str, str]]: + server_args = self.config.vllm_serve_kwargs + + port = find_open_port(disallowed_ports=get_global_config_dict()[DISALLOWED_PORTS_KEY_NAME]) + cache_dir = self.get_cache_dir() + server_args = server_args | { + "model": self.config.model, + "host": "0.0.0.0", # Must be 0.0.0.0 for cross-node communication. + "port": port, + "distributed_executor_backend": "ray", + "data_parallel_backend": "ray", + "download_dir": cache_dir, + } + + env_vars = {"HF_HUB_ENABLE_HF_TRANSFER": "1"} + # vLLM accepts a `hf_token` parameter but it's not used everywhere. We need to set HF_TOKEN environment variable here. + maybe_hf_token = get_hf_token() + if maybe_hf_token: + env_vars["HF_TOKEN"] = maybe_hf_token + + env_vars.update(self.config.vllm_serve_env_vars) + + assert "VLLM_RAY_DP_PACK_STRATEGY" in env_vars, ( + f"Please provide a value for `VLLM_RAY_DP_PACK_STRATEGY` for `{self.config.name}`" + ) + assert server_args.get("data_parallel_size") + assert server_args.get("tensor_parallel_size") + assert server_args.get("pipeline_parallel_size") + + # With our vLLM patches, this assert is no longer necessary + # Ray backend only works if dp_size > 1 + # assert server_args.get("data_parallel_size") is None or server_args.get("data_parallel_size") > 1, ( + # "Ray backend only works with data parallel size > 1!" + # ) + + # With our vLLM patches, this is no longer necessary for people to set. + server_args["data_parallel_size_local"] = 1 + + # TODO multi-node model instances still need to be properly supported + # We get a vLLM error: Exception: Error setting CUDA_VISIBLE_DEVICES: local range: [0, 16) base value: "0,1,2,3,4,5,6,7" + if env_vars.get("VLLM_RAY_DP_PACK_STRATEGY") == "span": + # Unset this flag since it's set by default using span + server_args.pop("data_parallel_size_local", None) + + cli_env_setup() + parser = FlexibleArgumentParser(description="vLLM OpenAI-Compatible RESTful API server.") + parser = make_arg_parser(parser) + final_args = parser.parse_args(namespace=Namespace(**server_args)) + validate_parsed_serve_args(final_args) + + # @bxyu-nvidia: TODO remove, specific to Nemotron 3 Ultra vLLM version + # this return_routed_experts argument isn't present in 0.17.0, so this must be from 0.16.x + final_args.return_routed_experts = final_args.enable_return_routed_experts + + if self.config.debug: + env_vars_to_print = env_vars.copy() + if "HF_TOKEN" in env_vars_to_print: + env_vars_to_print["HF_TOKEN"] = "****" + print(f"""Final vLLM serve arguments: {final_args} +Environment variables: {env_vars_to_print}""") + + return final_args, env_vars + + def _select_vllm_server_head_node(self, server_args: Namespace, env_vars: Dict[str, str]) -> PlacementGroup: + """ + Our LocalVLLMModelActor Ray actor scheduling strategy is as follows: + 1. We estimate the size of a single placement group vLLM will make using TP * PP + 2. We pre-maturely create one placement group of this size which will server as the master node for the vLLM instance + 3. This placement group is also provided on input to the LocalVLLMModelActor, which will schedule (DP - 1) additional placement groups of size TP * PP + """ + # This mirrors the placement group logic above + pack_strategy = env_vars["VLLM_RAY_DP_PACK_STRATEGY"] + if pack_strategy in ("strict", "fill"): + placement_strategy = "STRICT_PACK" + else: + placement_strategy = "PACK" + + device_str = "GPU" + device_bundle = [{device_str: 1.0}] + world_size = server_args.pipeline_parallel_size * server_args.tensor_parallel_size + bundles = device_bundle * world_size + [{"CPU": 1.0}] + head_node_placement_group = ray.util.placement_group( + name=f"{self.config.name}_dp_rank_0", + strategy=placement_strategy, + bundles=bundles, + ) + ray.get(head_node_placement_group.ready()) + + return head_node_placement_group + + def start_vllm_server(self) -> None: + # If base_url is already set, skip local launch — connect to external server. + if self.config.base_url: + print(f"External base_url configured: {self.config.base_url}. Skipping local vLLM launch.") + self._post_init() + return + + if self.config.debug: + print(f"""Currently available Ray cluster resources: {available_resources()} +Total Ray cluster resources: {cluster_resources()}""") + + server_args, env_vars = self._configure_vllm_serve() + head_node_placement_group = self._select_vllm_server_head_node(server_args, env_vars) + + pythonpath = str(Path(__file__).parent.parent.parent) + if self.config.debug: + print(f"Using PYTHONPATH={pythonpath}") + + self._local_vllm_model_actor = LocalVLLMModelActor.options( + scheduling_strategy=PlacementGroupSchedulingStrategy( + placement_group=head_node_placement_group, + ), + runtime_env=dict( + py_executable=self.config.ray_worker_py_executable, + env_vars={ + "RAY_EXPERIMENTAL_NOSET_CUDA_VISIBLE_DEVICES": "1", + "PYTHONPATH": pythonpath, + **env_vars, + }, + ), + ).remote( + head_node_placement_group=head_node_placement_group, + server_args=server_args, + env_vars=env_vars, + server_name=self.config.name, + debug=self.config.debug, + show_vllm_engine_stats=self.config.show_vllm_engine_stats, + ) + + self.config.base_url = [ray.get(self._local_vllm_model_actor.base_url.remote())] + + # Reset clients after base_url config + self._post_init() + + self.await_server_ready() + + def await_server_ready(self) -> None: + poll_count = 0 + while True: + is_alive = ray.get(self._local_vllm_model_actor.is_alive.remote()) + assert is_alive, f"{self.config.name} LocalVLLMModel server spinup failed, see the error logs above!" + + try: + requests.get(url=f"{self.config.base_url[0]}/models") + return + except ConnectionError: + if poll_count % 10 == 0: # Print every 30s + print(f"Waiting for {self.config.name} LocalVLLMModel server to spinup...") + + poll_count += 1 + sleep(3) + + +if __name__ == "__main__": + LocalVLLMModel.run_webserver() diff --git a/responses_api_models/local_vllm_model/configs/Qwen/Qwen3-235B-A22B-Instruct-2507-FP8.yaml b/responses_api_models/vllm_server/configs/Qwen/Qwen3-235B-A22B-Instruct-2507-FP8.yaml similarity index 98% rename from responses_api_models/local_vllm_model/configs/Qwen/Qwen3-235B-A22B-Instruct-2507-FP8.yaml rename to responses_api_models/vllm_server/configs/Qwen/Qwen3-235B-A22B-Instruct-2507-FP8.yaml index b743c8e089..e1583fed66 100644 --- a/responses_api_models/local_vllm_model/configs/Qwen/Qwen3-235B-A22B-Instruct-2507-FP8.yaml +++ b/responses_api_models/vllm_server/configs/Qwen/Qwen3-235B-A22B-Instruct-2507-FP8.yaml @@ -1,6 +1,6 @@ Qwen3-235B-A22B-Instruct-2507-FP8: responses_api_models: - local_vllm_model: + vllm_server: entrypoint: app.py model: Qwen/Qwen3-235B-A22B-Instruct-2507-FP8 return_token_id_information: false diff --git a/responses_api_models/local_vllm_model/configs/Qwen/Qwen3-30B-A3B-Instruct-2507-ngram-specdec.yaml b/responses_api_models/vllm_server/configs/Qwen/Qwen3-30B-A3B-Instruct-2507-ngram-specdec.yaml similarity index 99% rename from responses_api_models/local_vllm_model/configs/Qwen/Qwen3-30B-A3B-Instruct-2507-ngram-specdec.yaml rename to responses_api_models/vllm_server/configs/Qwen/Qwen3-30B-A3B-Instruct-2507-ngram-specdec.yaml index 845abc061c..0b5e2f07e4 100644 --- a/responses_api_models/local_vllm_model/configs/Qwen/Qwen3-30B-A3B-Instruct-2507-ngram-specdec.yaml +++ b/responses_api_models/vllm_server/configs/Qwen/Qwen3-30B-A3B-Instruct-2507-ngram-specdec.yaml @@ -17,7 +17,7 @@ # is exactly the 4-line `speculative_config:` entry below. Qwen3-30B-A3B-Instruct-2507-ngram-specdec: responses_api_models: - local_vllm_model: + vllm_server: entrypoint: app.py model: Qwen/Qwen3-30B-A3B-Instruct-2507 return_token_id_information: false diff --git a/responses_api_models/local_vllm_model/configs/Qwen/Qwen3-30B-A3B-Instruct-2507.yaml b/responses_api_models/vllm_server/configs/Qwen/Qwen3-30B-A3B-Instruct-2507.yaml similarity index 98% rename from responses_api_models/local_vllm_model/configs/Qwen/Qwen3-30B-A3B-Instruct-2507.yaml rename to responses_api_models/vllm_server/configs/Qwen/Qwen3-30B-A3B-Instruct-2507.yaml index 1de32862be..10b0e4fe31 100644 --- a/responses_api_models/local_vllm_model/configs/Qwen/Qwen3-30B-A3B-Instruct-2507.yaml +++ b/responses_api_models/vllm_server/configs/Qwen/Qwen3-30B-A3B-Instruct-2507.yaml @@ -1,6 +1,6 @@ Qwen3-30B-A3B-Instruct-2507: responses_api_models: - local_vllm_model: + vllm_server: entrypoint: app.py model: Qwen/Qwen3-30B-A3B-Instruct-2507 return_token_id_information: false diff --git a/responses_api_models/local_vllm_model/configs/Qwen/Qwen3-30B-A3B-Thinking-2507.yaml b/responses_api_models/vllm_server/configs/Qwen/Qwen3-30B-A3B-Thinking-2507.yaml similarity index 98% rename from responses_api_models/local_vllm_model/configs/Qwen/Qwen3-30B-A3B-Thinking-2507.yaml rename to responses_api_models/vllm_server/configs/Qwen/Qwen3-30B-A3B-Thinking-2507.yaml index dfc4a643ff..d3700239b9 100644 --- a/responses_api_models/local_vllm_model/configs/Qwen/Qwen3-30B-A3B-Thinking-2507.yaml +++ b/responses_api_models/vllm_server/configs/Qwen/Qwen3-30B-A3B-Thinking-2507.yaml @@ -1,6 +1,6 @@ Qwen3-30B-A3B-Thinking-2507: responses_api_models: - local_vllm_model: + vllm_server: entrypoint: app.py model: Qwen/Qwen3-30B-A3B-Thinking-2507 return_token_id_information: false diff --git a/responses_api_models/local_vllm_model/configs/Qwen/Qwen3.5-122B-A10B.yaml b/responses_api_models/vllm_server/configs/Qwen/Qwen3.5-122B-A10B.yaml similarity index 98% rename from responses_api_models/local_vllm_model/configs/Qwen/Qwen3.5-122B-A10B.yaml rename to responses_api_models/vllm_server/configs/Qwen/Qwen3.5-122B-A10B.yaml index ea79d2f2c0..0697f11d4b 100644 --- a/responses_api_models/local_vllm_model/configs/Qwen/Qwen3.5-122B-A10B.yaml +++ b/responses_api_models/vllm_server/configs/Qwen/Qwen3.5-122B-A10B.yaml @@ -1,6 +1,6 @@ Qwen3_5-122B-A10B: responses_api_models: - local_vllm_model: + vllm_server: entrypoint: app.py model: Qwen/Qwen3.5-122B-A10B return_token_id_information: false diff --git a/responses_api_models/local_vllm_model/configs/Qwen/Qwen3.5-27B.yaml b/responses_api_models/vllm_server/configs/Qwen/Qwen3.5-27B.yaml similarity index 98% rename from responses_api_models/local_vllm_model/configs/Qwen/Qwen3.5-27B.yaml rename to responses_api_models/vllm_server/configs/Qwen/Qwen3.5-27B.yaml index 88747bf2b8..620a85b3a5 100644 --- a/responses_api_models/local_vllm_model/configs/Qwen/Qwen3.5-27B.yaml +++ b/responses_api_models/vllm_server/configs/Qwen/Qwen3.5-27B.yaml @@ -1,6 +1,6 @@ Qwen3_5-27B: responses_api_models: - local_vllm_model: + vllm_server: entrypoint: app.py model: Qwen/Qwen3.5-27B return_token_id_information: false diff --git a/responses_api_models/local_vllm_model/configs/Qwen/Qwen3.5-35B-A3B.yaml b/responses_api_models/vllm_server/configs/Qwen/Qwen3.5-35B-A3B.yaml similarity index 98% rename from responses_api_models/local_vllm_model/configs/Qwen/Qwen3.5-35B-A3B.yaml rename to responses_api_models/vllm_server/configs/Qwen/Qwen3.5-35B-A3B.yaml index eaba31db6d..cbd3460ae6 100644 --- a/responses_api_models/local_vllm_model/configs/Qwen/Qwen3.5-35B-A3B.yaml +++ b/responses_api_models/vllm_server/configs/Qwen/Qwen3.5-35B-A3B.yaml @@ -1,6 +1,6 @@ Qwen3_5-35B-A3B: responses_api_models: - local_vllm_model: + vllm_server: entrypoint: app.py model: Qwen/Qwen3.5-35B-A3B return_token_id_information: false diff --git a/responses_api_models/local_vllm_model/configs/nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B-BF16.yaml b/responses_api_models/vllm_server/configs/nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B-BF16.yaml similarity index 98% rename from responses_api_models/local_vllm_model/configs/nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B-BF16.yaml rename to responses_api_models/vllm_server/configs/nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B-BF16.yaml index ddbd2a6fe6..1e51b9e190 100644 --- a/responses_api_models/local_vllm_model/configs/nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B-BF16.yaml +++ b/responses_api_models/vllm_server/configs/nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B-BF16.yaml @@ -1,6 +1,6 @@ NVIDIA-Nemotron-3-Nano-30B-A3B-BF16: responses_api_models: - local_vllm_model: + vllm_server: entrypoint: app.py model: nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B-BF16 return_token_id_information: false diff --git a/responses_api_models/local_vllm_model/configs/nvidia/nemotron_3_ultra_dev_nemorl_gb200.yaml b/responses_api_models/vllm_server/configs/nvidia/nemotron_3_ultra_dev_nemorl_gb200.yaml similarity index 99% rename from responses_api_models/local_vllm_model/configs/nvidia/nemotron_3_ultra_dev_nemorl_gb200.yaml rename to responses_api_models/vllm_server/configs/nvidia/nemotron_3_ultra_dev_nemorl_gb200.yaml index ff64a17cb5..ba416d24dd 100644 --- a/responses_api_models/local_vllm_model/configs/nvidia/nemotron_3_ultra_dev_nemorl_gb200.yaml +++ b/responses_api_models/vllm_server/configs/nvidia/nemotron_3_ultra_dev_nemorl_gb200.yaml @@ -2,7 +2,7 @@ # This config by default assumes DP4, requiring 4 GPU nodes on GB200 (each node has 4 GPUs) nemotron_3_ultra_dev: responses_api_models: - local_vllm_model: + vllm_server: entrypoint: app.py model: ??? return_token_id_information: false diff --git a/responses_api_models/local_vllm_model/configs/openai/gpt-oss-120b-reasoning-high.yaml b/responses_api_models/vllm_server/configs/openai/gpt-oss-120b-reasoning-high.yaml similarity index 98% rename from responses_api_models/local_vllm_model/configs/openai/gpt-oss-120b-reasoning-high.yaml rename to responses_api_models/vllm_server/configs/openai/gpt-oss-120b-reasoning-high.yaml index 21ed3ce03a..68d36334f9 100644 --- a/responses_api_models/local_vllm_model/configs/openai/gpt-oss-120b-reasoning-high.yaml +++ b/responses_api_models/vllm_server/configs/openai/gpt-oss-120b-reasoning-high.yaml @@ -1,6 +1,6 @@ gpt-oss-120b-reasoning-high: responses_api_models: - local_vllm_model: + vllm_server: entrypoint: app.py model: openai/gpt-oss-120b return_token_id_information: false diff --git a/responses_api_models/local_vllm_model/configs/openai/gpt-oss-120b-reasoning-low.yaml b/responses_api_models/vllm_server/configs/openai/gpt-oss-120b-reasoning-low.yaml similarity index 98% rename from responses_api_models/local_vllm_model/configs/openai/gpt-oss-120b-reasoning-low.yaml rename to responses_api_models/vllm_server/configs/openai/gpt-oss-120b-reasoning-low.yaml index 16b2a3247c..06a9e9fb4c 100644 --- a/responses_api_models/local_vllm_model/configs/openai/gpt-oss-120b-reasoning-low.yaml +++ b/responses_api_models/vllm_server/configs/openai/gpt-oss-120b-reasoning-low.yaml @@ -1,6 +1,6 @@ gpt-oss-120b-reasoning-low: responses_api_models: - local_vllm_model: + vllm_server: entrypoint: app.py model: openai/gpt-oss-120b return_token_id_information: false diff --git a/responses_api_models/local_vllm_model/configs/openai/gpt-oss-120b-reasoning-medium.yaml b/responses_api_models/vllm_server/configs/openai/gpt-oss-120b-reasoning-medium.yaml similarity index 98% rename from responses_api_models/local_vllm_model/configs/openai/gpt-oss-120b-reasoning-medium.yaml rename to responses_api_models/vllm_server/configs/openai/gpt-oss-120b-reasoning-medium.yaml index d71a25617c..7957773ce6 100644 --- a/responses_api_models/local_vllm_model/configs/openai/gpt-oss-120b-reasoning-medium.yaml +++ b/responses_api_models/vllm_server/configs/openai/gpt-oss-120b-reasoning-medium.yaml @@ -1,6 +1,6 @@ gpt-oss-120b-reasoning-medium: responses_api_models: - local_vllm_model: + vllm_server: entrypoint: app.py model: openai/gpt-oss-120b return_token_id_information: false diff --git a/responses_api_models/local_vllm_model/configs/openai/gpt-oss-20b-reasoning-high.yaml b/responses_api_models/vllm_server/configs/openai/gpt-oss-20b-reasoning-high.yaml similarity index 98% rename from responses_api_models/local_vllm_model/configs/openai/gpt-oss-20b-reasoning-high.yaml rename to responses_api_models/vllm_server/configs/openai/gpt-oss-20b-reasoning-high.yaml index eeedeb2ff8..4b727879d7 100644 --- a/responses_api_models/local_vllm_model/configs/openai/gpt-oss-20b-reasoning-high.yaml +++ b/responses_api_models/vllm_server/configs/openai/gpt-oss-20b-reasoning-high.yaml @@ -1,6 +1,6 @@ gpt-oss-20b-reasoning-high: responses_api_models: - local_vllm_model: + vllm_server: entrypoint: app.py model: openai/gpt-oss-20b return_token_id_information: false diff --git a/responses_api_models/local_vllm_model/configs/local_vllm_model.yaml b/responses_api_models/vllm_server/configs/vllm_server.yaml similarity index 98% rename from responses_api_models/local_vllm_model/configs/local_vllm_model.yaml rename to responses_api_models/vllm_server/configs/vllm_server.yaml index 09ff537d96..961910918f 100644 --- a/responses_api_models/local_vllm_model/configs/local_vllm_model.yaml +++ b/responses_api_models/vllm_server/configs/vllm_server.yaml @@ -5,7 +5,7 @@ # gym eval run --benchmark aime24 --model-type local_vllm_model --model Qwen/Qwen3-30B-A3B-Instruct-2507 policy_model: responses_api_models: - local_vllm_model: + vllm_server: entrypoint: app.py model: ${policy_model_name} return_token_id_information: false diff --git a/responses_api_models/vllm_server/local_vllm_model_actor.py b/responses_api_models/vllm_server/local_vllm_model_actor.py new file mode 100644 index 0000000000..6fb1c8c00e --- /dev/null +++ b/responses_api_models/vllm_server/local_vllm_model_actor.py @@ -0,0 +1,360 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Try a separate file here to ease on the imports +import asyncio +import sys +from argparse import Namespace +from threading import Thread +from typing import Dict + +import ray +from ray.util.placement_group import PlacementGroup + + +def _vllm_asyncio_task(server_args: Namespace): + from vllm.entrypoints.openai.api_server import run_server + + asyncio.run(run_server(server_args)) + + +@ray.remote +class LocalVLLMModelActor: + def __init__( + self, + head_node_placement_group: PlacementGroup, + server_args: Namespace, + env_vars: Dict[str, str], + server_name: str, + debug: bool, + show_vllm_engine_stats: bool, + ) -> None: + from os import environ + + self.head_node_placement_group = head_node_placement_group + self.server_args = server_args + self.env_vars = env_vars + self.server_name = server_name + self.debug = debug + self.show_vllm_engine_stats = show_vllm_engine_stats + + self.env_vars.pop("CUDA_VISIBLE_DEVICES", None) + + node_ip = ray._private.services.get_node_ip_address() + self._base_url = f"http://{node_ip}:{self.server_args.port}/v1" + print(f"Spinning up local vLLM server at {self._base_url}", file=sys.stderr) + + # vLLM doesn't expose a config for this yet, so we need to pass via environment variable. + self.env_vars["VLLM_DP_MASTER_IP"] = node_ip # This is the master node. + + self._patch_signal_handler() + self._patch_uvicorn_logger() + self._maybe_patch_engine_stats() + self._patch_create_dp_placement_groups() + self._patch_init_data_parallel() + + for k, v in self.env_vars.items(): + environ[k] = v + + self.server_thread = Thread(target=_vllm_asyncio_task, args=(server_args,), daemon=True) + self.server_thread.start() + + def _patch_signal_handler(self) -> None: + # Pass through signal setting not allowed in threads. + # See https://github.com/vllm-project/vllm/blob/275de34170654274616082721348b7edd9741d32/vllm/entrypoints/launcher.py#L94 + # This may be vLLM version specific! + # + # api_server.py uses `from vllm.entrypoints.launcher import serve_http`, + # so we must patch the name in api_server's namespace (not launcher's). + + import signal + from asyncio import get_running_loop + + import vllm.entrypoints.openai.api_server as api_server + + original_serve_http = api_server.serve_http + + def new_serve_http(*args, **kwargs): + loop = get_running_loop() + loop.add_signal_handler = lambda *args, **kwargs: None + + return original_serve_http(*args, **kwargs) + + api_server.serve_http = new_serve_http + + # Patch signal as well. + signal.signal = lambda *args, **kwargs: None + + def _patch_uvicorn_logger(self) -> None: + from logging import Filter as LoggingFilter + from logging import LogRecord, getLogger + + print( + "Adding a uvicorn logging filter so that the logs aren't spammed with 200 OK messages. This is to help errors pop up better and filter out noise." + ) + + class No200Filter(LoggingFilter): + def filter(self, record: LogRecord) -> bool: + msg = record.getMessage() + return not msg.strip().endswith("200") + + uvicorn_logger = getLogger("uvicorn.access") + uvicorn_logger.addFilter(No200Filter()) + + def _maybe_patch_engine_stats(self) -> None: + from logging import ERROR, StreamHandler + + from vllm.v1.metrics.loggers import logger as metrics_logger + + if self.debug or self.show_vllm_engine_stats: + print("vLLM metrics logger will display engine stats.") + handler = StreamHandler(sys.stderr) + metrics_logger.addHandler(handler) + else: + print( + f"Setting vLLM metrics logger for {self.server_name} to ERROR which will not print engine stats. This helps declutter the logs. Use `debug` for LocalVLLMModel to see them." + ) + metrics_logger.setLevel(ERROR) + + def _patch_init_data_parallel(self) -> None: + from vllm.v1.engine.core import DPEngineCoreProc, logger + + def new_init_data_parallel(self, vllm_config): + # Configure GPUs and stateless process group for data parallel. + dp_rank = vllm_config.parallel_config.data_parallel_rank + dp_size = vllm_config.parallel_config.data_parallel_size + local_dp_rank = vllm_config.parallel_config.data_parallel_rank_local + + # This allows the vLLM DP Ray flow to be run even with a single DP instance. + # assert dp_size > 1 + + assert local_dp_rank is not None + assert 0 <= local_dp_rank <= dp_rank < dp_size + + if vllm_config.kv_transfer_config is not None: + # modify the engine_id and append the local_dp_rank to it to ensure + # that the kv_transfer_config is unique for each DP rank. + vllm_config.kv_transfer_config.engine_id = ( + f"{vllm_config.kv_transfer_config.engine_id}_dp{local_dp_rank}" + ) + logger.debug( + "Setting kv_transfer_config.engine_id to %s", + vllm_config.kv_transfer_config.engine_id, + ) + + self.dp_rank = dp_rank + self.dp_group = vllm_config.parallel_config.stateless_init_dp_group() + + DPEngineCoreProc._init_data_parallel = new_init_data_parallel + + def _patch_create_dp_placement_groups(self) -> None: + head_node_placement_group = self.head_node_placement_group + + from ray.util.placement_group import PlacementGroup + from vllm.v1.engine.utils import ( + CoreEngineActorManager, + current_platform, + envs, + logger, + ) + + ######################################## + # The logic below is an exact copy of CoreEngineActorManager.create_dp_placement_groups + # Except in places where we specify it differs + ######################################## + def new_create_dp_placement_groups(vllm_config): + """ + Create placement groups for data parallel. + """ + + import ray + from ray._private.state import available_resources_per_node, total_resources_per_node + + logger.info("Creating placement groups for data parallel") + dp_master_ip = vllm_config.parallel_config.data_parallel_master_ip + dp_size = vllm_config.parallel_config.data_parallel_size + dp_size_local = vllm_config.parallel_config.data_parallel_size_local + + available_resources = available_resources_per_node() + + """ + START Patch colocated placement group logic + + When running multiple local vLLM model instances on the same node, the placement group logic will error with the following since multiple placement groups are now on the same node. + + (LocalVLLMModelActor pid=504531) (APIServer pid=504531) File "responses_api_models/local_vllm_model/.venv/lib/python3.12/site-packages/vllm/v1/engine/utils.py", line 858, in launch_core_engines + (LocalVLLMModelActor pid=504531) (APIServer pid=504531) engine_actor_manager = CoreEngineActorManager( + (LocalVLLMModelActor pid=504531) (APIServer pid=504531) ^^^^^^^^^^^^^^^^^^^^^^^ + (LocalVLLMModelActor pid=504531) (APIServer pid=504531) File "responses_api_models/local_vllm_model/.venv/lib/python3.12/site-packages/vllm/v1/engine/utils.py", line 300, in __init__ + (LocalVLLMModelActor pid=504531) (APIServer pid=504531) CoreEngineActorManager.create_dp_placement_groups(vllm_config) + (LocalVLLMModelActor pid=504531) (APIServer pid=504531) File "responses_api_models/local_vllm_model/.venv/lib/python3.12/site-packages/vllm/v1/engine/utils.py", line 467, in create_dp_placement_groups + (LocalVLLMModelActor pid=504531) (APIServer pid=504531) assert len(node_ip_keys) == 1, ( + (LocalVLLMModelActor pid=504531) (APIServer pid=504531) ^^^^^^^^^^^^^^^^^^^^^^ + (LocalVLLMModelActor pid=504531) (APIServer pid=504531) AssertionError: Zero or multiple node IP keys found in node resources: ['node:10.65.9.15_group_a036a448bf98d155cd0d6a8991f902000000', 'node:10.65.9.15_group_1_8786b4bfb840f7ba7af007e7e41602000000', 'node:10.65.9.15', 'node:10.65.9.15_group_8786b4bfb840f7ba7af007e7e41602000000', 'node:10.65.9.15_group_1_a036a448bf98d155cd0d6a8991f902000000', 'node:10.65.9.15_group_0_8786b4bfb840f7ba7af007e7e41602000000', 'node:10.65.9.15_group_0_a036a448bf98d155cd0d6a8991f902000000'] + """ + + for node_hex_id, node_resources in list(available_resources.items()): + available_resources[node_hex_id] = { + resource_id: resource + for resource_id, resource in node_resources.items() + if "_group_" not in resource_id + } + + """ + END Patch colocated placement group logic + """ + + world_size = vllm_config.parallel_config.world_size + """ + START Use our initial placement group + """ + placement_groups: list[PlacementGroup] = [head_node_placement_group] + local_dp_ranks: list[int] = [0] + """ + END Use our initial placement group + """ + + dp_master_ip_key = f"node:{dp_master_ip}" + nodes = sorted(available_resources.values(), key=lambda x: dp_master_ip_key not in x) + assert len(nodes) > 0, "No nodes with resources found in Ray cluster." + assert dp_master_ip_key in nodes[0], ( + "The DP master node (ip: %s) is missing or dead", + dp_master_ip, + ) + device_str = current_platform.ray_device_key + + n_node_devices: list[int] = [ + int(node_resources[device_str]) for node_resources in nodes if device_str in node_resources + ] + """ + START Account for cases when the initial placement groups we create i.e. DP == 1 are already sufficient + """ + # Original code: + # assert n_node_devices, f"No {device_str} found in Ray cluster." + + # Modified code: + if dp_size == 1: + total_nodes = total_resources_per_node().values() + total_n_node_devices: list[int] = [ + int(node_resources[device_str]) for node_resources in total_nodes if device_str in node_resources + ] + max_device_per_node = max(total_n_node_devices) + else: + assert n_node_devices, f"No {device_str} found in Ray cluster." + max_device_per_node = max(n_node_devices) + """ + END Account for cases when the initial placement groups we create i.e. DP == 1 are already sufficient + """ + + pack_strategy = envs.VLLM_RAY_DP_PACK_STRATEGY + _supported_pack_strategies = ("strict", "fill", "span") + if pack_strategy not in _supported_pack_strategies: + raise ValueError( + f"{envs.VLLM_RAY_DP_PACK_STRATEGY} is not supported. " + "Make sure to set `VLLM_RAY_DP_PACK_STRATEGY` " + f"to one of {_supported_pack_strategies}" + ) + + all2all_backend = vllm_config.parallel_config.all2all_backend + if pack_strategy == "fill" and ( + all2all_backend == "deepep_high_throughput" or all2all_backend == "deepep_low_latency" + ): + raise ValueError( + "DeepEP kernels require EP ranks [0,7] (same for [8,15], ...) " + "to be on the same node, but VLLM_RAY_DP_PACK_STRATEGY=fill " + "does not guarantee that. " + "Please use VLLM_RAY_DP_PACK_STRATEGY=strict instead." + ) + + if pack_strategy in ("strict", "fill"): + placement_strategy = "STRICT_PACK" + else: + placement_strategy = "PACK" + assert world_size > max_device_per_node, ( + f"World size {world_size} is smaller than the " + "maximum number of devices per node " + f"{max_device_per_node}. Make sure to set " + "`VLLM_RAY_DP_PACK_STRATEGY` to `strict` or `fill`" + ) + + # if we need multiple nodes per dp group, we require for now that + # available nodes are homogenous + if dp_size == 1: + assert set(total_n_node_devices) == {max_device_per_node}, f"Nodes are not homogenous, {nodes}" + else: + assert set(n_node_devices) == {max_device_per_node}, f"Nodes are not homogenous, {nodes}" + assert world_size % max_device_per_node == 0, ( + f"For multi-node data parallel groups, world_size ({world_size}) must " + f"be a multiple of number of devices per node ({max_device_per_node})." + ) + """ + START Fix required GPU compute necessary calculation given we already reserve one placement group + """ + # Original code: + # assert len(n_node_devices) * max_device_per_node >= world_size * dp_size, ( + + # Modified code: + assert len(n_node_devices) * max_device_per_node >= world_size * (dp_size - 1), ( + f"Not enough total available nodes ({len(n_node_devices)}) " + f"and devices per node ({max_device_per_node}) " + f"to satisfy required world size {world_size} and data parallel size " + f"{dp_size}" + ) + """ + END Fix required GPU compute necessary calculation given we already reserve one placement group + """ + assert dp_size_local == 1, ( + f"data-parallel-size-local {dp_size_local} should be set as the " + "default (1) for VLLM_RAY_DP_PACK_STRATEGY=span. " + "The actual data-parallel-size-local will be auto determined." + ) + + for _ in range(dp_size - 1): + bundles = [{device_str: 1.0}] * world_size + [{"CPU": 1.0}] + + pg_name = f"{self.server_name}_dp_rank_{len(placement_groups)}" + pg = ray.util.placement_group( + name=pg_name, + strategy=placement_strategy, + bundles=bundles, + ) + + placement_groups.append(pg) + local_dp_ranks.append(0) + + if len(placement_groups) < dp_size: + raise ValueError( + f"Not enough resources to allocate {dp_size} " + "placement groups, only created " + f"{len(placement_groups)} placement groups. " + "Available resources: " + f"{available_resources}" + ) + assert len(placement_groups) == dp_size, ( + f"Created {len(placement_groups)} DP placement groups, expected {dp_size}" + ) + assert len(local_dp_ranks) == dp_size, ( + f"local_dp_ranks length {len(local_dp_ranks)} does not match expected {dp_size}" + ) + + return placement_groups, local_dp_ranks + + CoreEngineActorManager.create_dp_placement_groups = new_create_dp_placement_groups + + def base_url(self) -> str: + return self._base_url + + def is_alive(self) -> bool: + return self.server_thread.is_alive() diff --git a/responses_api_models/local_vllm_model/pyproject.toml b/responses_api_models/vllm_server/pyproject.toml similarity index 100% rename from responses_api_models/local_vllm_model/pyproject.toml rename to responses_api_models/vllm_server/pyproject.toml diff --git a/responses_api_models/local_vllm_model/scripts/launch_vllm_server.sh b/responses_api_models/vllm_server/scripts/launch_vllm_server.sh similarity index 92% rename from responses_api_models/local_vllm_model/scripts/launch_vllm_server.sh rename to responses_api_models/vllm_server/scripts/launch_vllm_server.sh index aee4be64b2..ff95babf65 100755 --- a/responses_api_models/local_vllm_model/scripts/launch_vllm_server.sh +++ b/responses_api_models/vllm_server/scripts/launch_vllm_server.sh @@ -27,7 +27,7 @@ hostname -i cd 3rdparty/Gym-workspace/Gym source .venv/bin/activate -ng_run "+config_paths=[responses_api_models/local_vllm_model/configs/openai/gpt-oss-20b-reasoning-high.yaml]" \ +ng_run "+config_paths=[responses_api_models/vllm_server/configs/openai/gpt-oss-20b-reasoning-high.yaml]" \ ++use_absolute_ip=true EOF diff --git a/responses_api_models/local_vllm_model/scripts/nvidia_smi_cluster.py b/responses_api_models/vllm_server/scripts/nvidia_smi_cluster.py similarity index 100% rename from responses_api_models/local_vllm_model/scripts/nvidia_smi_cluster.py rename to responses_api_models/vllm_server/scripts/nvidia_smi_cluster.py diff --git a/responses_api_models/local_vllm_model/setup.py b/responses_api_models/vllm_server/setup.py similarity index 100% rename from responses_api_models/local_vllm_model/setup.py rename to responses_api_models/vllm_server/setup.py diff --git a/responses_api_models/local_vllm_model/test_scripts/1_node/1_instance_1x8.sh b/responses_api_models/vllm_server/test_scripts/1_node/1_instance_1x8.sh similarity index 87% rename from responses_api_models/local_vllm_model/test_scripts/1_node/1_instance_1x8.sh rename to responses_api_models/vllm_server/test_scripts/1_node/1_instance_1x8.sh index 2f8b297a0c..902037bb30 100755 --- a/responses_api_models/local_vllm_model/test_scripts/1_node/1_instance_1x8.sh +++ b/responses_api_models/vllm_server/test_scripts/1_node/1_instance_1x8.sh @@ -13,5 +13,5 @@ # See the License for the specific language governing permissions and # limitations under the License. -config_paths="responses_api_models/local_vllm_model/configs/openai/gpt-oss-20b-reasoning-high.yaml" +config_paths="responses_api_models/vllm_server/configs/openai/gpt-oss-20b-reasoning-high.yaml" ng_run "+config_paths=[${config_paths}]" diff --git a/responses_api_models/local_vllm_model/test_scripts/1_node/1_instance_2x4.sh b/responses_api_models/vllm_server/test_scripts/1_node/1_instance_2x4.sh similarity index 90% rename from responses_api_models/local_vllm_model/test_scripts/1_node/1_instance_2x4.sh rename to responses_api_models/vllm_server/test_scripts/1_node/1_instance_2x4.sh index 325db92f43..319a838998 100755 --- a/responses_api_models/local_vllm_model/test_scripts/1_node/1_instance_2x4.sh +++ b/responses_api_models/vllm_server/test_scripts/1_node/1_instance_2x4.sh @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -config_paths="responses_api_models/local_vllm_model/configs/openai/gpt-oss-20b-reasoning-high.yaml" +config_paths="responses_api_models/vllm_server/configs/openai/gpt-oss-20b-reasoning-high.yaml" ng_run "+config_paths=[${config_paths}]" \ ++gpt-oss-20b-reasoning-high.responses_api_models.local_vllm_model.vllm_serve_kwargs.data_parallel_size=2 \ ++gpt-oss-20b-reasoning-high.responses_api_models.local_vllm_model.vllm_serve_kwargs.tensor_parallel_size=4 diff --git a/responses_api_models/local_vllm_model/test_scripts/1_node/2_instances_1x4.sh b/responses_api_models/vllm_server/test_scripts/1_node/2_instances_1x4.sh similarity index 83% rename from responses_api_models/local_vllm_model/test_scripts/1_node/2_instances_1x4.sh rename to responses_api_models/vllm_server/test_scripts/1_node/2_instances_1x4.sh index 0816162d8f..dd647c0fa0 100755 --- a/responses_api_models/local_vllm_model/test_scripts/1_node/2_instances_1x4.sh +++ b/responses_api_models/vllm_server/test_scripts/1_node/2_instances_1x4.sh @@ -13,8 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -config_paths="responses_api_models/local_vllm_model/configs/openai/gpt-oss-20b-reasoning-high.yaml,\ -responses_api_models/local_vllm_model/configs/openai/gpt-oss-120b-reasoning-high.yaml" +config_paths="responses_api_models/vllm_server/configs/openai/gpt-oss-20b-reasoning-high.yaml,\ +responses_api_models/vllm_server/configs/openai/gpt-oss-120b-reasoning-high.yaml" ng_run "+config_paths=[${config_paths}]" \ ++gpt-oss-20b-reasoning-high.responses_api_models.local_vllm_model.vllm_serve_kwargs.tensor_parallel_size=4 \ ++gpt-oss-120b-reasoning-high.responses_api_models.local_vllm_model.vllm_serve_kwargs.tensor_parallel_size=4 diff --git a/responses_api_models/local_vllm_model/test_scripts/2_nodes/1_instance_1x16.sh b/responses_api_models/vllm_server/test_scripts/2_nodes/1_instance_1x16.sh similarity index 91% rename from responses_api_models/local_vllm_model/test_scripts/2_nodes/1_instance_1x16.sh rename to responses_api_models/vllm_server/test_scripts/2_nodes/1_instance_1x16.sh index 17dc2d4106..84d3e64ec0 100755 --- a/responses_api_models/local_vllm_model/test_scripts/2_nodes/1_instance_1x16.sh +++ b/responses_api_models/vllm_server/test_scripts/2_nodes/1_instance_1x16.sh @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -config_paths="responses_api_models/local_vllm_model/configs/openai/gpt-oss-20b-reasoning-high.yaml" +config_paths="responses_api_models/vllm_server/configs/openai/gpt-oss-20b-reasoning-high.yaml" ng_run "+config_paths=[${config_paths}]" \ ++gpt-oss-20b-reasoning-high.responses_api_models.local_vllm_model.vllm_serve_kwargs.data_parallel_size=1 \ ++gpt-oss-20b-reasoning-high.responses_api_models.local_vllm_model.vllm_serve_kwargs.tensor_parallel_size=16 \ diff --git a/responses_api_models/local_vllm_model/test_scripts/2_nodes/2_instances_1x8.sh b/responses_api_models/vllm_server/test_scripts/2_nodes/2_instances_1x8.sh similarity index 79% rename from responses_api_models/local_vllm_model/test_scripts/2_nodes/2_instances_1x8.sh rename to responses_api_models/vllm_server/test_scripts/2_nodes/2_instances_1x8.sh index a9f9d950db..f6420ce1ea 100755 --- a/responses_api_models/local_vllm_model/test_scripts/2_nodes/2_instances_1x8.sh +++ b/responses_api_models/vllm_server/test_scripts/2_nodes/2_instances_1x8.sh @@ -13,6 +13,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -config_paths="responses_api_models/local_vllm_model/configs/openai/gpt-oss-20b-reasoning-high.yaml,\ -responses_api_models/local_vllm_model/configs/openai/gpt-oss-120b-reasoning-high.yaml" +config_paths="responses_api_models/vllm_server/configs/openai/gpt-oss-20b-reasoning-high.yaml,\ +responses_api_models/vllm_server/configs/openai/gpt-oss-120b-reasoning-high.yaml" ng_run "+config_paths=[${config_paths}]" diff --git a/responses_api_models/local_vllm_model/test_scripts/2_nodes/2_instances_2x4.sh b/responses_api_models/vllm_server/test_scripts/2_nodes/2_instances_2x4.sh similarity index 86% rename from responses_api_models/local_vllm_model/test_scripts/2_nodes/2_instances_2x4.sh rename to responses_api_models/vllm_server/test_scripts/2_nodes/2_instances_2x4.sh index 7fdadd58df..6bd54dae0e 100755 --- a/responses_api_models/local_vllm_model/test_scripts/2_nodes/2_instances_2x4.sh +++ b/responses_api_models/vllm_server/test_scripts/2_nodes/2_instances_2x4.sh @@ -13,8 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -config_paths="responses_api_models/local_vllm_model/configs/openai/gpt-oss-20b-reasoning-high.yaml,\ -responses_api_models/local_vllm_model/configs/openai/gpt-oss-120b-reasoning-high.yaml" +config_paths="responses_api_models/vllm_server/configs/openai/gpt-oss-20b-reasoning-high.yaml,\ +responses_api_models/vllm_server/configs/openai/gpt-oss-120b-reasoning-high.yaml" ng_run "+config_paths=[${config_paths}]" \ ++gpt-oss-20b-reasoning-high.responses_api_models.local_vllm_model.vllm_serve_kwargs.data_parallel_size=2 \ ++gpt-oss-20b-reasoning-high.responses_api_models.local_vllm_model.vllm_serve_kwargs.tensor_parallel_size=4 \ diff --git a/responses_api_models/local_vllm_model/test_scripts/4_nodes/1_instance_2x16.sh b/responses_api_models/vllm_server/test_scripts/4_nodes/1_instance_2x16.sh similarity index 91% rename from responses_api_models/local_vllm_model/test_scripts/4_nodes/1_instance_2x16.sh rename to responses_api_models/vllm_server/test_scripts/4_nodes/1_instance_2x16.sh index c1ac4f0f1e..a9d5a7de27 100755 --- a/responses_api_models/local_vllm_model/test_scripts/4_nodes/1_instance_2x16.sh +++ b/responses_api_models/vllm_server/test_scripts/4_nodes/1_instance_2x16.sh @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -config_paths="responses_api_models/local_vllm_model/configs/openai/gpt-oss-20b-reasoning-high.yaml" +config_paths="responses_api_models/vllm_server/configs/openai/gpt-oss-20b-reasoning-high.yaml" ng_run "+config_paths=[${config_paths}]" \ ++gpt-oss-20b-reasoning-high.responses_api_models.local_vllm_model.vllm_serve_kwargs.data_parallel_size=2 \ ++gpt-oss-20b-reasoning-high.responses_api_models.local_vllm_model.vllm_serve_kwargs.tensor_parallel_size=16 \ diff --git a/responses_api_models/local_vllm_model/test_scripts/4_nodes/2_instances_1x16.sh b/responses_api_models/vllm_server/test_scripts/4_nodes/2_instances_1x16.sh similarity index 86% rename from responses_api_models/local_vllm_model/test_scripts/4_nodes/2_instances_1x16.sh rename to responses_api_models/vllm_server/test_scripts/4_nodes/2_instances_1x16.sh index ff814bf040..50a46ae9fe 100755 --- a/responses_api_models/local_vllm_model/test_scripts/4_nodes/2_instances_1x16.sh +++ b/responses_api_models/vllm_server/test_scripts/4_nodes/2_instances_1x16.sh @@ -13,8 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -config_paths="responses_api_models/local_vllm_model/configs/openai/gpt-oss-20b-reasoning-high.yaml,\ -responses_api_models/local_vllm_model/configs/openai/gpt-oss-120b-reasoning-high.yaml" +config_paths="responses_api_models/vllm_server/configs/openai/gpt-oss-20b-reasoning-high.yaml,\ +responses_api_models/vllm_server/configs/openai/gpt-oss-120b-reasoning-high.yaml" ng_run "+config_paths=[${config_paths}]" \ ++gpt-oss-20b-reasoning-high.responses_api_models.local_vllm_model.vllm_serve_kwargs.tensor_parallel_size=16 \ ++gpt-oss-20b-reasoning-high.responses_api_models.local_vllm_model.vllm_serve_env_vars.VLLM_RAY_DP_PACK_STRATEGY=span \ diff --git a/responses_api_models/local_vllm_model/test_scripts/8_nodes/2_instances_2x16.sh b/responses_api_models/vllm_server/test_scripts/8_nodes/2_instances_2x16.sh similarity index 88% rename from responses_api_models/local_vllm_model/test_scripts/8_nodes/2_instances_2x16.sh rename to responses_api_models/vllm_server/test_scripts/8_nodes/2_instances_2x16.sh index 0dbe98b9e1..0fe0d1564b 100755 --- a/responses_api_models/local_vllm_model/test_scripts/8_nodes/2_instances_2x16.sh +++ b/responses_api_models/vllm_server/test_scripts/8_nodes/2_instances_2x16.sh @@ -13,8 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -config_paths="responses_api_models/local_vllm_model/configs/openai/gpt-oss-20b-reasoning-high.yaml,\ -responses_api_models/local_vllm_model/configs/openai/gpt-oss-120b-reasoning-high.yaml" +config_paths="responses_api_models/vllm_server/configs/openai/gpt-oss-20b-reasoning-high.yaml,\ +responses_api_models/vllm_server/configs/openai/gpt-oss-120b-reasoning-high.yaml" ng_run "+config_paths=[${config_paths}]" \ ++gpt-oss-20b-reasoning-high.responses_api_models.local_vllm_model.vllm_serve_kwargs.data_parallel_size=2 \ ++gpt-oss-20b-reasoning-high.responses_api_models.local_vllm_model.vllm_serve_kwargs.tensor_parallel_size=16 \ diff --git a/responses_api_models/vllm_server/tests/__init__.py b/responses_api_models/vllm_server/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/responses_api_models/local_vllm_model/tests/test_app.py b/responses_api_models/vllm_server/tests/test_app.py similarity index 93% rename from responses_api_models/local_vllm_model/tests/test_app.py rename to responses_api_models/vllm_server/tests/test_app.py index 1fc7e0f826..89a3f23655 100644 --- a/responses_api_models/local_vllm_model/tests/test_app.py +++ b/responses_api_models/vllm_server/tests/test_app.py @@ -19,9 +19,9 @@ from vllm import platforms from vllm.platforms import resolve_obj_by_qualname -import responses_api_models.local_vllm_model.app +import responses_api_models.vllm_server.app from nemo_gym.global_config import DISALLOWED_PORTS_KEY_NAME, DictConfig -from responses_api_models.local_vllm_model.app import LocalVLLMModel, LocalVLLMModelConfig +from responses_api_models.vllm_server.app import LocalVLLMModel, LocalVLLMModelConfig class TestApp: @@ -48,7 +48,7 @@ def test_sanity_start_vllm_server(self, monkeypatch) -> None: get_global_config_dict_mock = MagicMock() get_global_config_dict_mock.return_value = DictConfig({DISALLOWED_PORTS_KEY_NAME: []}) monkeypatch.setattr( - responses_api_models.local_vllm_model.app, + responses_api_models.vllm_server.app, "get_global_config_dict", get_global_config_dict_mock, ) diff --git a/tests/unit_tests/test_cli_main.py b/tests/unit_tests/test_cli_main.py index 9f1f5cb7e4..5378712c57 100644 --- a/tests/unit_tests/test_cli_main.py +++ b/tests/unit_tests/test_cli_main.py @@ -473,10 +473,10 @@ def test_local_vllm_deployment_flow(self, monkeypatch: MonkeyPatch) -> None: # The deployment invocation: select the local vLLM server type and pass the checkpoint to serve via --model. _, overrides = _dispatch_for( monkeypatch, - ["eval", "run", "--model-type", "local_vllm_model", "--model", "Qwen/Qwen3-8B"], + ["eval", "run", "--model-type", "vllm_server", "--model", "Qwen/Qwen3-8B"], ) paths, others = _split_overrides(overrides) - assert paths == {str(WORKING_DIR / "responses_api_models/local_vllm_model/configs/local_vllm_model.yaml")} + assert paths == {str(WORKING_DIR / "responses_api_models/vllm_server/configs/vllm_server.yaml")} assert others == {"+policy_model_name=Qwen/Qwen3-8B"} def test_short_alias_on_env_run(self, monkeypatch: MonkeyPatch) -> None: @@ -617,8 +617,11 @@ class TestAssetSelectors: ["env", "run", "--model-type", "openai_model"], "responses_api_models/openai_model/configs/openai_model.yaml", ), - # model-server/vllm.mdx: responses_api_models/vllm_model/configs/vllm_model.yaml - (["env", "run", "--model-type", "vllm_model"], "responses_api_models/vllm_model/configs/vllm_model.yaml"), + # model-server/vllm.mdx: responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml + ( + ["env", "run", "--model-type", "vllm_endpoint"], + "responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml", + ), ], ) def test_name_resolves_to_config_path(self, monkeypatch: MonkeyPatch, argv, expected_config) -> None: @@ -642,12 +645,14 @@ def test_quickstart_resource_server_plus_model(self, monkeypatch: MonkeyPatch) - def test_gpqa_benchmark_plus_model(self, monkeypatch: MonkeyPatch) -> None: # benchmarks/gpqa/README.md: - # ng_run "+config_paths=[benchmarks/gpqa/config.yaml,responses_api_models/vllm_model/configs/vllm_model.yaml]" - _, overrides = _dispatch_for(monkeypatch, ["eval", "run", "--benchmark", "gpqa", "--model-type", "vllm_model"]) + # ng_run "+config_paths=[benchmarks/gpqa/config.yaml,responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml]" + _, overrides = _dispatch_for( + monkeypatch, ["eval", "run", "--benchmark", "gpqa", "--model-type", "vllm_endpoint"] + ) paths, others = _split_overrides(overrides) assert paths == { str(WORKING_DIR / "benchmarks/gpqa/config.yaml"), - str(WORKING_DIR / "responses_api_models/vllm_model/configs/vllm_model.yaml"), + str(WORKING_DIR / "responses_api_models/vllm_endpoint/configs/vllm_endpoint.yaml"), } assert others == set() diff --git a/tests/unit_tests/test_model_server_rename.py b/tests/unit_tests/test_model_server_rename.py new file mode 100644 index 0000000000..8c685eac69 --- /dev/null +++ b/tests/unit_tests/test_model_server_rename.py @@ -0,0 +1,103 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from pathlib import Path + +from nemo_gym.model_server_rename import rename_file, rename_references + + +OLD, NEW = "local_vllm_model", "vllm_server" + + +class TestRenameReferences: + def test_rewrites_config_paths_directory(self) -> None: + text = "config_paths:\n- responses_api_models/local_vllm_model/configs/Qwen/Qwen3.5-27B.yaml\n" + out, count = rename_references(text, OLD, NEW) + assert count == 1 + assert "responses_api_models/vllm_server/configs/Qwen/Qwen3.5-27B.yaml" in out + assert "local_vllm_model" not in out + + def test_rewrites_dotted_override_path(self) -> None: + text = "++policy_model.responses_api_models.local_vllm_model.vllm_serve_kwargs.x=1\n" + out, count = rename_references(text, OLD, NEW) + # The path token responses_api_models/ isn't present (dotted form); the bare key under a + # parent isn't either, so a dotted-attr reference is left untouched (handled at dir-move time). + assert count == 0 + assert out == text + + def test_rewrites_config_key_under_responses_api_models(self) -> None: + text = "policy_model:\n responses_api_models:\n local_vllm_model:\n entrypoint: app.py\n" + out, count = rename_references(text, OLD, NEW) + assert count == 1 + assert " vllm_server:" in out + assert "local_vllm_model:" not in out + + def test_rewrites_delete_key_directive_under_responses_api_models(self) -> None: + text = "k:\n responses_api_models:\n _delete_key: local_vllm_model\n vllm_server:\n x: 1\n" + out, count = rename_references(text, OLD, NEW) + assert " _delete_key: vllm_server" in out + assert "_delete_key: local_vllm_model" not in out + assert count == 1 + + def test_does_not_rewrite_same_name_key_elsewhere(self) -> None: + # A key named like the server but NOT under responses_api_models: must be left alone. + text = "some_block:\n local_vllm_model:\n foo: bar\n" + out, count = rename_references(text, OLD, NEW) + assert count == 0 + assert out == text + + def test_rewrites_both_path_and_key_together(self) -> None: + text = ( + "config_paths:\n- responses_api_models/local_vllm_model/configs/x.yaml\n" + "policy_model:\n responses_api_models:\n local_vllm_model:\n entrypoint: app.py\n" + ) + out, count = rename_references(text, OLD, NEW) + assert count == 2 + assert "responses_api_models/vllm_server/configs/x.yaml" in out + assert " vllm_server:" in out + + def test_does_not_touch_python_import_path(self) -> None: + # Imports use dotted module paths, not slash paths; left to the dir-move step. + text = "from responses_api_models.local_vllm_model.app import LocalVLLMModel\n" + out, count = rename_references(text, OLD, NEW) + assert count == 0 + assert out == text + + def test_no_references_is_unchanged(self) -> None: + text = "config_paths:\n- responses_api_models/openai_model/configs/openai_model.yaml\n" + out, count = rename_references(text, OLD, NEW) + assert count == 0 + assert out == text + + def test_key_block_scope_resets_after_dedent(self) -> None: + # After leaving the responses_api_models block, a matching key must not be rewritten. + text = "a:\n responses_api_models:\n local_vllm_model:\n x: 1\nb:\n local_vllm_model:\n y: 2\n" + out, count = rename_references(text, OLD, NEW) + assert count == 1 # only the one under responses_api_models: + assert "b:\n local_vllm_model:" in out + + +class TestRenameFile: + def test_rewrites_in_place(self, tmp_path: Path) -> None: + f = tmp_path / "config.yaml" + f.write_text("config_paths:\n- responses_api_models/local_vllm_model/configs/x.yaml\n") + assert rename_file(f, OLD, NEW) == 1 + assert "vllm_server" in f.read_text() + + def test_dry_run_does_not_write(self, tmp_path: Path) -> None: + f = tmp_path / "config.yaml" + original = "config_paths:\n- responses_api_models/local_vllm_model/configs/x.yaml\n" + f.write_text(original) + assert rename_file(f, OLD, NEW, dry_run=True) == 1 + assert f.read_text() == original