Write Your Own Benchmark (BYOB)
Write Your Own Benchmark (BYOB)
Define a complete benchmark with @benchmark + @scorer in under 10 lines.
Minimal Example
Save this as my_bench.py:
Run it:
That is the entire local workflow. Passing the Python file path imports it, lets the
@benchmark decorator register the environment, loads the dataset, formats prompts,
and wires scoring. No package-internal edits or subclass boilerplate required.
How It Works
@benchmarkcreates aByobEnvironment(subclass ofEvalEnvironment) and registers it by name.- On
seed(idx), the environment loads the dataset row, formats the prompt template, and returns aSeedResult. - On
verify(response, expected), it calls your@scorerfunction with aScorerInputand converts the result to aVerifyResult.
Step-by-Step
Step 1: Create the benchmark file
Create benchmarks/my_reasoning.py:
Step 2: Validate
Set your model endpoint once:
Expected output:
Step 3: Run full evaluation
Step 4: Serve for Gym training
Gym training connects at http://hostname:9090.
Step 5: Package for sharing
Generate a Dockerfile for the benchmark module:
Omit --output to build the image directly, and add --push to push it after build.
Decorator Reference
@benchmark parameters
@scorer function signature
The function receives a ScorerInput:
Return a dict. The key "correct" (or "reward") is converted to the numeric reward.
Optionally include "extracted" for the extracted answer string.
Dataset Specs
HuggingFace URIs support split and config query parameters.
Scoring Primitives
Built-in functions you can call from your scorer:
All are importable from the top-level package:
Extension Hooks
prepare_row: Transform dataset rows
Use prepare_row when the raw dataset needs restructuring before prompt formatting.
seed_fn: Fully custom seed
When you need complete control over prompt construction:
Real-World Examples
Most built-in benchmarks use @benchmark + @scorer. See src/nemo_evaluator/benchmarks/ for reference implementations:
Advanced: Subclass EvalEnvironment Directly
For benchmarks that cannot be expressed with decorators (e.g., multi-turn, stateful),
subclass EvalEnvironment:
The decorator path is preferred for single-turn benchmarks. Reserve subclassing for cases that genuinely need it.
Parametrizing a Built-in Benchmark from YAML
Built-in environments registered with @register("<name>") can be tweaked
from a config without a new Python module. BenchmarkConfig accepts a
params: mapping whose entries are forwarded to the environment
constructor, filtered to the arguments it actually declares. Unknown keys
raise a clear TypeError at resolution time.
Scalar values are accepted for list parameters (task_names: workspace-basic-cli)
so the CLI override flag works too: -O benchmarks[0].params.task_names=workspace-basic-cli.
Wrapping a Built-in Benchmark with Extra Pre-Build Steps
When a dataset needs a one-off base image produced from a Dockerfile in a
consumer repo, subclass the relevant environment under
src/nemo_evaluator/benchmarks/ and prepend your own ImageBuildRequest.
See src/nemo_evaluator/benchmarks/nmp_harbor.py for a working reference:
- Subclasses
HarborEnvironmentand registers under@register("nmp_harbor"). - Reads
NMP_REPO(or thenmp_repoparam) and points the dataset at$NMP_REPO/tests/agentic-use. - Overrides
image_build_requests()to prepend a singleImageBuildRequestthat buildsnmp-harbor:latestfrom$NMP_REPO/Dockerfile.harborbefore any per-task image build runs.
The sandbox manager treats the prepended request identically to any other
image build, so the same config works against Docker, ECR/ECS Fargate, etc.
See examples/configs/10_nmp_harbor.yaml.