Repository navigation
Add speed-bench benchmark and resource server - #1232
Merged
bxyu-nvidia merged 12 commits intoMay 8, 2026
Merged
Conversation
Adds the SPEED-Bench speculative-decoding benchmark to NeMo Gym, mirroring nemo_skills' speed-bench: - resources_servers/speed_bench/ scrapes vLLM `vllm:spec_decode_*` Prometheus counters before/after generation. verify() returns the running aggregate (acceptance length / rate / per-position rates) per task; compute_metrics surfaces the headline values from the latest-completing task. SGLang scrape is stubbed (NotImplementedError). - responses_api_agents/speed_bench_agent/ replays multi-turn rows one user turn at a time, matching Skills' SpecdecGenerationTask. Single-turn rows collapse to a single model call (same shape as simple_agent). - benchmarks/speed-bench/ chains the resources server and agent. prepare.py imports Skills' _resolve_external_data via file path so the 14-source interpolation logic doesn't have to be reproduced. Default configs are qualitative + throughput_2k; --config all is supported. - Unit tests cover the Prometheus parser, running-delta math, metrics URL resolution, compute_metrics aggregation, and the verify() async path with a stubbed scrape. Signed-off-by: gwarmstrong <gwarmstrong@users.noreply.github.com>
ng_prepare_benchmark requires `prompt_config` for type: benchmark datasets. speed-bench bakes the multi-turn user-message list into responses_create_params.input in prepare.py, so no prompt template is needed — set prompt_config: null explicitly (matching the mrcr pattern) so the schema validates. Signed-off-by: gwarmstrong <gwarmstrong@users.noreply.github.com>
speed_bench_agent_default in responses_api_agents/speed_bench_agent/configs/ speed_bench_agent.yaml referenced `speed_bench_resources_server`, but the benchmark config.yaml's `_inherit_from: speed_bench_resources_server` re-registers it as `speed_bench_speed_bench_resources_server` and the original name disappears from the registry. The standalone agent config is unused by the benchmark flow — `speed_bench_simple_agent` in the resources server's own config already wires up the agent's entrypoint. Signed-off-by: gwarmstrong <gwarmstrong@users.noreply.github.com>
ng_prepare_benchmark requires exactly one dataset per agent. Define a separate `speed_bench_<config>_simple_agent` per benchmark config rather than stacking both datasets under one agent. Updates the Gym run script and benchmark README to point at the new qualitative agent name. Signed-off-by: gwarmstrong <gwarmstrong@users.noreply.github.com>
ng_prepare_benchmark calls prepare() with no args and asserts the returned path matches benchmark_config.dataset.jsonl_fpath. With multiple configs, each agent needs its own prepare module that returns its own path. Refactors: - prepare.py: now prepares the qualitative split (returns one Path). - prepare_throughput_2k.py: prepares the throughput_2k split. - _prepare_common.py: shared resolver+row-conversion helpers. config.yaml's throughput_2k agent now points at prepare_throughput_2k.py. Signed-off-by: gwarmstrong <gwarmstrong@users.noreply.github.com>
importlib.import_module('benchmarks.speed-bench.prepare') doesn't add the
benchmark dir to sys.path (and the dotted name has a hyphen anyway), so
'from _prepare_common import ...' fails with ModuleNotFoundError.
Prepend the script directory to sys.path inside each prepare module so
the sibling helper resolves.
Signed-off-by: gwarmstrong <gwarmstrong@users.noreply.github.com>
Mirrors Skills' fetch_sglang_spec_decode_metrics +
compute_sglang_spec_decode_delta:
- _parse_sglang_metrics(): extracts sglang:spec_accept_{length,rate} gauges
and sglang:{num_requests_total, generation_tokens_total} counters.
- _weighted_delta_average(): generic helper to recover a benchmark-window
average from two running-average gauges.
- _compute_sglang_running_delta(): emits acceptance_length, acceptance_rate,
num_drafts (~delta_requests), draft_tokens (~delta_gen_tokens),
accepted_tokens (delta_gen_tokens * ar_fraction). Per-position rates are
not exposed by SGLang — empty list, matching Skills.
- verify() and _scrape_metrics() pick the SGLang path when
server_type_for_metrics == 'sglang'.
7 new unit tests cover the parser, the weighted-delta arithmetic edge
cases (first window, no traffic), the full sglang delta computation, and
an end-to-end verify() exercise of the SGLang code path. 26 tests passing
total (was 19).
Skills also supports an SGLang per-request metrics-file path
(--export-metrics-to-file) — that's left as future work; Prometheus delta
is sufficient for parity comparisons.
Signed-off-by: gwarmstrong <gwarmstrong@users.noreply.github.com>
Mirrors Skills' BaseMetrics._add_std_metrics for SpecdecMetrics' five score keys (acceptance_length, acceptance_rate, num_drafts, draft_tokens, accepted_tokens). When tasks have >1 rollouts each (e.g. +num_repeats=N), compute_metrics() now emits four variance estimators per key: - spec_<key>_avg — mean across all (task, rollout) pairs - spec_<key>_std_dev_across_runs — std-dev of per-run averages - spec_<key>_std_err_across_runs — std_dev / sqrt(num_repeats) - spec_<key>_avg_sample_std_dev — mean of per-task std-devs Single-seed runs (the default speed-bench shape on each side) skip this block, matching Skills' max_k=1 behaviour. get_key_metrics() additionally surfaces the headline std_err_across_runs for acceptance_length and acceptance_rate so a reader can see at a glance whether a Skills↔Gym Δ is within sampling noise. Implementation uses stdlib statistics + math (no numpy dep). Edge cases covered by tests: - single-rollout / empty tasks → empty dict - all-identical values → zero std-dev (avoids stdev's NaN at n=1) - tasks with None values for a key → dropped per-key - uneven k across tasks → uses min(k) (Skills enforces equality; we relax for robustness against partial outputs) 8 new unit tests, 34 passing total (was 26). Signed-off-by: gwarmstrong <gwarmstrong@users.noreply.github.com>
- nemo_gym/config_types.py: add 'NVIDIA Evaluation Dataset License Agreement' to the dataset license whitelist. SPEED-Bench is a publicly hosted NVIDIA dataset under exactly that license; without the literal, ng_prepare_data fails Pydantic validation. - resources_servers/speed_bench/data/example_metrics.json: generated by ng_prepare_data ... +mode=example_validation (CI artifact). - resources_servers/speed_bench/data/example.jsonl: regenerated via example_validation; agent_ref field now stamped per row (canonical shape for type=example datasets). - responses_api_models/local_vllm_model/configs/Qwen/Qwen3-30B-A3B-Instruct-2507-ngram-specdec.yaml: ready-to-use local_vllm_model config that bakes ngram speculative decoding into vllm_serve_kwargs.speculative_config — cheapest path to a working spec-decode-enabled vLLM for speed-bench. - Both READMEs reference the demo config and document that the model server MUST be launched with --speculative-config (or the equivalent vllm_serve_kwargs block) — without it the resources server records spec_decode_unavailable: true on every row. Signed-off-by: gwarmstrong <gwarmstrong@users.noreply.github.com>
…etadata - Adds resources_servers/speed_bench/data/example_rollouts.jsonl: 5 real rollouts produced by ng_collect_rollouts on a vLLM-with-ngram-spec-dec cluster job against example.jsonl. Required CI artifact per the add-benchmark / migrate-benchmark skills. - Fixes a Pydantic-schema bug: SpeedBenchVerifyRequest now declares verifier_metadata: Optional[Dict[str, Any]] explicitly so the per-row src_id / source / speed_config / num_turns / sub_category fields the prepare.py emits round-trip through verify() into rollouts.jsonl. Without this, the debug_compare_specdec.py src_id-based matching loses every row. The committed example_rollouts.jsonl was produced before the verifier_metadata fix landed (its rows have null verifier_metadata), which is fine for CI validation — future runs will populate it. Signed-off-by: gwarmstrong <gwarmstrong@users.noreply.github.com>
Gym's BenchmarkConfig.from_initial_config_dict asserts exactly one type:benchmark dataset per config.yaml file (nemo_gym/benchmarks.py:83). Our prior config.yaml carried two — qualitative + throughput_2k — which caused tests/unit_tests/test_benchmarks.py::TestListBenchmarks::test_lists_found_benchmarks to fail in CI with: AssertionError: Expected 1 benchmark dataset for config /home/runner/work/Gym/Gym/benchmarks/speed-bench/config.yaml, but found 2! Split into per-config files following the ruler pattern: - benchmarks/speed-bench/config_qualitative.yaml - benchmarks/speed-bench/config_throughput_2k.yaml Each carries exactly one benchmark dataset entry. README + recipe scripts updated to reference the per-config paths explicitly. list_benchmarks() globs **/config.yaml literally, so neither file is auto-discovered — same as ruler's config_nemotron_3_256k.yaml. Users opt into a config explicitly via +config_paths=. Signed-off-by: gwarmstrong <gwarmstrong@users.noreply.github.com>
…pecdec-fixmerge Signed-off-by: gwarmstrong <gwarmstrong@users.noreply.github.com> # Conflicts: # README.md
bxyu-nvidia
approved these changes
May 8, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Add speed-bench benchmark and resource server
Migrates the SPEED-Bench speculative-decoding throughput benchmark from NeMo Skills into NeMo Gym. Verification reads the model server's Prometheus
/metricscounters before and after generation to derive acceptance length (AL) and acceptance rate (AR) — there is no notion of answer correctness; the metrics are server-cumulative properties of the run window.Includes
resources_servers/speed_bench/— new resource server that scrapes vLLMvllm:spec_decode_*(or SGLangsglang:spec_accept_*) Prometheus counters across the benchmark window and returns running aggregates per task.nemo_skills/inference/eval/specdec.py::fetch_vllm_spec_decode_metrics.sglang:spec_accept_length,sglang:spec_accept_rate) and the request/generation-token counters — mirrors Skills'compute_sglang_spec_decode_delta.responses_api_agents/speed_bench_agent/— new fixed-replay multi-turn agent.simple_agentsends a list of messages in one model call; SPEED-Bench'squalitativeconfig has 185/880 multi-turn rows where Skills'SpecdecGenerationTaskplays each user turn separately so the model sees its own intermediate replies. The new agent reproduces that turn-by-turn replay.simple_agent).benchmarks/speed-bench/— qualitative + throughput_2k configs.ng_prepare_benchmark's "exactly one dataset per agent" assertion is satisfied).prepare.pyandprepare_throughput_2k.pyreuse Skills'_resolve_external_datahelper (loaded by file path) so the 14-source interpolation logic isn't duplicated.responses_create_params.inputis a list of{"role": "user", "content": "<turn>"}messages with no interspersed assistants —speed_bench_agentplays them sequentially at rollout time.responses_api_models/local_vllm_model/configs/Qwen/Qwen3-30B-A3B-Instruct-2507-ngram-specdec.yaml— ready-to-use local_vllm_model config that bakes ngram speculative decoding intovllm_serve_kwargs.speculative_config. Drop it into+config_paths=[…]alongside the speed_bench config and you have a working spec-decode-enabled vLLM out of the box.nemo_gym/config_types.py— adds"NVIDIA Evaluation Dataset License Agreement"to the dataset license whitelist (SPEED-Bench is hosted under exactly that license; without the literal,ng_prepare_datafails Pydantic validation).Validated against NeMo Skills
Two side-by-side runs on a single 8 × H100 node, Qwen3-30B-A3B target, vLLM ngram speculative decoding (
num_speculative_tokens=3,prompt_lookup_max=5,prompt_lookup_min=2),temperature=1.0,top_p=0.95,max_output_tokens=1024, single seed each side. (Single seed because spec-decode metrics are server-cumulative on a single benchmark window — splitting into multiple seeds would change the semantic, not just produce variance estimates.)Both spec-decode quality metrics are within sampling noise on both configs; data-prep parity (row counts,
src_idsequences, multi-turn row counts) is byte-equivalent. The natural drop from qualitative→throughput_2k (AL 1.77→1.68) reflects ngram lookup having a lower hit rate on long padded prompts; both pipelines see the drop identically.Deferred
throughput_{1k,8k,16k,32k}configs:prepare.pyknows how to prepare them (--config throughput_8ketc.) but onlyqualitativeandthroughput_2kwere validated end-to-end.--export-metrics-to-file) is not ported — the Prometheus delta gives matching headline numbers and is sufficient for parity.get_key_metrics()headline.Notes for reviewers
--speculative-config '{"method": "ngram", ...}'or thevllm_serve_kwargs.speculative_config:block in the demolocal_vllm_modelconfig). Without it, every rollout recordsspec_decode_unavailable: trueand AL/AR are null. Both READMEs document this prominently.server_type_for_metrics: sglangin the resources server config and ensure the model server is SGLang. Unit tests cover the parser, weighted-delta arithmetic, and the verify-time code path; missing piece is end-to-end SGLang validation.