You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This project provides a unified workbench for evaluating language models throughout the model development loop.
Features:
Registry of benchmark tasks and composable suites, with named variants for few-shot settings, formatting, and scoring (e.g. humaneval:3shot:bpb).
Support for inference via vLLM, SGLang, LiteLLM for commercial APIs, and a mock provider for dry runs and debugging.
Harness abstraction that separates execution policy from task definition, so any task can be run baseline or tool-augmented without modification.
Multi-turn agentic evaluation with tool calling, scaffolds, and sandboxed environments via Docker, Podman, or Modal.
LLM-as-judge scoring with auxiliary providers, including locally served judge models.
Aggregate and instance-level prediction storage.
Inspection tooling for viewing instances, formatted prompts, token arrays, and model responses.
To contribute, start with CONTRIBUTING.md. The reasoning behind the
design is in docs/design.md, and AGENTS.md holds the
instructions for coding agents.
Quick Start
This project uses uv with a checked-in uv.lock
for reproducible builds. To get started, sync the repo with uv, browse the
available tasks and suites, and preview a run with the built-in mock provider.
Run Your First Eval
# Install uv if not already installed
curl -LsSf https://astral.sh/uv/install.sh | sh
# Install Python 3.12 if your machine does not already have it
uv python install 3.12
# Install dependencies + the package (editable) from the lockfile.# The default groups (`dev` + `vllm`) are installed automatically, which# pulls in storage, beaker, hf, and the vLLM inference provider. vLLM# deps are marked Linux-only via PEP 508 markers, so this works on macOS# too — no extra flags needed.
uv sync --frozen
# Install pre-commit hooks
make setup
# To update the lockfile after changing pyproject.toml
uv lock
# Add an optional extra on top of the defaults (e.g. agents, litellm)
uv sync --frozen --extra agents
# `openhands` conflicts with vllm — opt out of the vllm group when using it
uv sync --frozen --no-group vllm --extra openhands
# Browse a few suites
uv run olmo-eval suite inspect mmlu
uv run olmo-eval suite inspect gpqa
uv run olmo-eval suite inspect olmobase:code
# Preview a run without loading a model
uv run olmo-eval run -m mock -t gsm8k --dry-run
# Preview another run with a different task spec
uv run olmo-eval run -m mock -t humaneval:3shot:bpb --dry-run
Key Concepts
The evaluation framework is built around these core abstractions:
Abstraction
Description
Task
Benchmark specification defining dataset slice, request construction, and scoring logic
Suite
Benchmark collection that composes tasks and/or nested suites and defines result aggregation
Harness
Execution runtime around the inference provider, tools, scaffolds, and runtime behavior
Formatter
Prompt renderer from an instance and few-shot context to an LM request
Scorer
Per-example evaluator from model output to raw score or judgment
Metric
Dataset-level aggregator over per-example scores
Tasks
Tasks define how to load data, format prompts, and score outputs. Register with @register:
Suites support different strategies for combining task results:
Strategy
Description
AVERAGE
Simple average of all task scores (default)
WEIGHTED_AVERAGE
Average of all task scores, each weighted by the task's instance count
AVERAGE_OF_AVERAGES
Average over child suite averages (equal weight per child)
DISPLAY_ONLY
Display child results without computing suite average
GAP
Two tasks, reference then companion: both primary scores and the companion minus the reference
NONE
No suite score: tasks report individually, and each nested suite reports its own aggregate
Average of Averages Example:
fromolmo_eval.evals.suitesimportSuite, AggregationStrategy, register# Nested suite with 3 tasksmultilingual_code=Suite(
name="multilingual_code",
tasks=("mbpp_python", "mbpp_java", "mbpp_rust"),
aggregation=AggregationStrategy.AVERAGE,
)
# Parent suite using average of averagesregister(Suite(
name="code_eval",
tasks=(
"humaneval", # Single task (score: 0.80)multilingual_code, # Nested suite with 3 tasks (scores: 0.40, 0.50, 0.60)
),
aggregation=AggregationStrategy.AVERAGE_OF_AVERAGES,
))
# Results:# - humaneval: 0.80# - mu
10BC8
ltilingual_code average: (0.40 + 0.50 + 0.60) / 3 = 0.50## AVERAGE_OF_AVERAGES: (0.80 + 0.50) / 2 = 0.65# vs AVERAGE: (0.80 + 0.40 + 0.50 + 0.60) / 4 = 0.575
Note: AVERAGE_OF_AVERAGES gives each child equal weight regardless of how many tasks it contains.
Weighted Average Example:
register(Suite(
name="mmlu_pro",
tasks=("mmlu_pro_math", "mmlu_pro_history"), # 1351 and 381 questionsaggregation=AggregationStrategy.WEIGHTED_AVERAGE,
))
# With scores of 0.30 (math) and 0.45 (history):# WEIGHTED_AVERAGE: (0.30 * 1351 + 0.45 * 381) / 1732 = 0.333# vs AVERAGE: (0.30 + 0.45) / 2 = 0.375
WEIGHTED_AVERAGE matches the instance-weighted "micro" average that oe-eval reports for some suites. It weights each task by the number of instances that task scored.
A weighted suite reports its weighted mean or no score at all. If a contributing task has no instance count, the suite aggregate is omitted and the runner logs which tasks were missing, rather than publishing an unweighted mean under the same suite name. Instance counts are required when a result is stored, so this only affects results written before that check existed.
Gap Example:
register(Suite(
name="omega:dev",
tasks=("omega_500:hillclimb", "omega_500_out"), # in-distribution, then held-outaggregation=AggregationStrategy.GAP,
))
# With scores of 0.60 (in) and 0.45 (out):# primary_score: reference 0.60, companion 0.45 (the suite's primary), gap -0.15
A gap suite retains its definition in suite_metrics, but is omitted from summary unless both tasks scored on the same primary metric. It can be nested only in a NONE suite, which reports it under its own name: a gap is not a score to average.
The held-out companion score is the value to rank and optimize. The gap is a diagnostic guard, shown in percentage points in the log summary; reducing the reference score never improves the suite's primary score. In metrics.json, suite_metrics["omega:dev"]["primary_score"] retains reference, companion, and gap; summary selects the companion.
Formatters
Formatters convert instances into LM requests. See olmo_eval.common.formatters for available options.
fromolmo_eval.common.formattersimportMultipleChoiceFormatter, ChatFormatter# Multiple choice with logprob scoringformatter=MultipleChoiceFormatter(template="Q: {question}\n\nA:")
# Chat-based formattingformatter=ChatFormatter(system_prompt="You are a helpful assistant.")
Scorers
Scorers compute a score for each instance/output pair. See olmo_eval.common.scorers for available options.
Metrics aggregate scores across responses. See olmo_eval.common.metrics for available options.
fromolmo_eval.common.metricsimportAccuracyMetric, F1Metricfromolmo_eval.common.scorersimportExactMatchScorer, F1Scorer# Mean accuracymetric=AccuracyMetric(scorer=ExactMatchScorer)
# Mean F1 scoremetric=F1Metric(scorer=F1Scorer)
Model Presets
Pre-configured model settings in olmo_eval/common/constants/models.py:
fromolmo_eval.common.constantsimportget_model_presets# Returns dict of preset name -> ModelConfigpresets=get_model_presets()
# {# "llama3.1-8b": ModelConfig(model="meta-llama/Meta-Llama-3.1-8B"),# "olmo-2-7b": ModelConfig(model="allenai/OLMo-2-1124-7B"),# ...# }
Harness
A Harness is the runtime orchestration layer for an evaluation run. It combines the primary inference provider with execution policy such as system prompts, tools, auxiliary providers, sandboxing, metrics collection, and an optional scaffold for multi-turn control. This lets the same task run in plain, tool-using, or scaffolded modes without changing the task definition.
Key concept: Any task can be run with or without tools—that's determined by the Harness configuration, not the task definition. This allows comparing baseline vs tool-augmented performance on the same task.
Using Harness via CLI
# Run task without tools or a scaffold (baseline)
uv run olmo-eval run -m llama3.1-8b -t simpleqa:judge
# Run task with search tools via harness preset
uv run olmo-eval run -m llama3.1-8b -t simpleqa:judge --harness dr_tulu
# Use a custom harness config file
uv run olmo-eval run -m llama3.1-8b -t simpleqa:judge --harness-config ./my_harness.yaml
HarnessConfig
Configuration for a harness:
fromolmo_eval.harnessimportHarnessConfig, ProviderConfig, get_harness_presetfromolmo_eval.harness.tools.searchimport (
semantic_scholar_search,
serper_web_search,
serper_fetch_page,
)
# Get a presetconfig=get_harness_preset("dr_tulu")
# Or create custom config with toolsconfig=HarnessConfig(
name="my_harness",
provider=ProviderConfig(model="gpt-4o", kind="litellm"),
tools=(semantic_scholar_search, serper_web_search, serper_fetch_page),
system_prompt="You are a helpful assistant with search tools.",
max_turns=10,
max_concurrency=8,
scaffold="openai_agents",
required_secrets=("S2_API_KEY", "SERPER_API_KEY"),
)
Field
Type
Default
Description
name
str
Required
Harness identifier
provider
ProviderConfig
ProviderConfig()
Model provider configuration
tools
tuple[Tool | str, ...]
()
Tool instances or registered tool names
system_prompt
str | None
None
System prompt to inject
tool_choice
str
"auto"
Tool selection mode (auto, none, required)
scaffold
str | None
None
Execution scaffold (e.g., openai_agents)
max_turns
int | None
None
Max turns for multi-turn execution
max_concurrency
int | None
None
Concurrent executions
scoring_concurrency
int | None
None
Max concurrent scoring operations
sandboxes
tuple[SandboxConfig, ...]
()
Sandbox configurations for isolated tool execution
The failure budget: fraction of a task's instances allowed to produce no output or have no scored output before the task is marked failed, its metrics are withheld and the run exits non-zero (default 0.05). 1.0 never fails a run on partial failures; a task with nothing scored still fails
required_secrets
tuple[str, ...]
()
Required environment variables
Scaffolds
Scaffolds define how the Harness executes multi-turn requests with tool calling. A scaffold handles the agentic loop: calling the model, executing tools, and feeding results back.
# List available scaffolds
uv run olmo-eval scaffolds
When to use a scaffold:
For multi-turn execution with harness.run(), you must specify a scaffold
For single-turn generation with harness.generate(), no scaffold is needed
# Multi-turn execution requires a scaffoldconfig=HarnessConfig(
name="my_agent",
provider=ProviderConfig(model="gpt-4o", kind="litellm"),
tools=(semantic_scholar_search, serper_web_search),
scaffold="openai_agents", # Required for run()
)
harness=Harness(config)
result=awaitharness.run(request) # Uses the scaffold# Single-turn generation works without a scaffoldconfig=HarnessConfig(
name="simple",
provider=ProviderConfig(model="gpt-4o", kind="litellm"),
)
harness=Harness(config)
outputs=harness.generate(requests) # No scaffold needed
Inference Metrics
Harness configurations can include MetricsConfig to collect inference performance metrics during evaluation. A run without --harness or --harness-config collects them with the default MetricsConfig(), writing metrics/<provider>_<model>-inference.jsonl in the output directory, as the built-in presets do:
fromolmo_eval.harnessimportHarnessConfig, ProviderConfigfromolmo_eval.inference.metricsimportMetricsConfigconfig=HarnessConfig(
name="with_metrics",
provider=ProviderConfig(model="llama3.1-8b", kind="vllm_server"),
metrics=MetricsConfig(
enabled=True,
reporters=("file", "db"), # Save to file and databasecollect_vllm_server=True, # Poll vLLM server /metrics endpoint
),
)
Visualizing Metrics:
# Plot metrics from database (requires at least one filter)
uv run olmo-eval metrics plot -G my-benchmark-group
uv run olmo-eval metrics plot -m OLMo-3 --metric throughput
# Show statistics table without interactive plots
uv run olmo-eval metrics plot -e experiment_123 --stats-only
When using the db reporter, metrics are stored in a PostgreSQL database (default name: olmo_eval_metrics). You must configure your own database connection using the OLMO_EVAL_DB_* environment variables (see Database Configuration).
Auxiliary Providers and Local Judge Models
Some tasks or custom scorers use LLM-as-judge scoring, where a separate model evaluates responses. The auxiliary_providers configuration lets you specify additional inference providers for scoring or judging. Harness overrides must come immediately after --harness, while task overrides like limit=... must come after -t.
URL for external servers (when not spawning locally)
scoring_concurrency
Number of concurrent scoring requests
Defining Tools
Tools combine schema (for the LLM) and implementation (for execution) in a single definition:
fromolmo_eval.harnessimporttool, registered_tool# Option 1: @tool decorator (local use)@tool(description="Search the web for information")asyncdefweb_search(query: str) ->str:
"""Search implementation."""returnawaitsearch_api(query)
# Option 2: @registered_tool decorator (global registry, for cross-process use)@registered_tool(description="Fetch a webpage")asyncdeffetch_page(url: str) ->str:
"""Fetch implementation."""returnawaitfetch_url(url)
Tools are automatically registered when using @registered_tool, making them available by name in HarnessConfig.
Custom Harness Config File
Create a YAML file for custom harness configurations:
# my_harness.yamlname: custom_searchtool_names:
- semantic_scholar_snippet_search
- serper_google_webpage_searchsystem_prompt: | You are a research assistant with web search capabilities. Use search tools to find accurate information before answering.max_turns: 15max_concurrency: 4required_secrets:
- S2_API_KEY
- SERPER_API_KEY
uv run olmo-eval run -m llama3.1-8b -t simpleqa:judge --harness-config my_harness.yaml
Programmatic Usage
fromolmo_eval.harnessimportHarness, HarnessConfig, ProviderConfig, get_harness_presetfromolmo_eval.harness.tools.searchimport (
semantic_scholar_search,
serper_web_search,
)
# Create harness with preset and provider overrideconfig=get_harness_preset("dr_tulu").with_provider(
ProviderConfig(model="meta-llama/Llama-3.1-8B-Instruct", kind="vllm")
)
harness=Harness(config)
# Or create from scratchconfig=HarnessConfig(
name="my_harness",
provider=ProviderConfig(model="gpt-4o", kind="litellm"),
tools=(semantic_scholar_search, serper_web_search),
system_prompt="You are a helpful assistant.",
scaffold="openai_agents",
)
harness=Harness(config)
# Multi-turn execution with tool callingresult=awaitharness.run(request, sampling_params)
print(result.trajectory) # Shows all turns including tool callsprint(result.final_output) # Final model response
Adding New Tasks
This section explains how to create new evaluation tasks.
Quick Start: Minimal Task Example
A multiple-choice task scored by the log-likelihood of each answer choice:
"""Example: a minimal multiple-choice task scored by log-likelihood."""fromcollections.abcimportIteratorfromtypingimportAnyfromolmo_eval.common.formattersimportMultipleChoiceFormatterfromolmo_eval.common.metricsimportLogprobMCAccuracyMetricfromolmo_eval.common.typesimportInstance, LMRequestfromolmo_eval.dataimportDataSourcefromolmo_eval.evals.tasks.commonimportTask, register@register("my_task")classMyTask(Task):
"""MMLU abstract algebra, scored by the highest-likelihood answer choice."""# Pin the revision so the task's data cannot change underneath stored results.data_source=DataSource(
path="cais/mmlu",
subset="abstract_algebra",
split="test",
revision="c30699e8356da336a370243923dbaf21066bb9fe",
)
formatter=MultipleChoiceFormatter(
template="Question: {question}",
choice_template=" {choice}",
prompt_suffix="\nAnswer:",
)
metrics= (LogprobMCAccuracyMetric(),)
@propertydefinstances(self) ->Iterator[Instance]:
# Loads the configured data source, calls process_doc, and caches the result.yieldfromself._load_instances_cached()
defprocess_doc(self, doc: dict[str, Any], index: int=0) ->Instance|None:
returnInstance(
question=doc["question"],
choices=tuple(doc["choices"]),
gold_answer=doc["choices"][doc["answer"]],
metadata={"id": f"my_task_{index}", "gold_idx": doc["answer"]},
)
defformat_request(self, instance: Instance) ->LMRequest:
assertself.config.formatterisnotNonereturnself.config.formatter.format(instance, self.get_fewshot())
Class attributes that match TaskConfig fields (data_source, formatter, metrics,
and so on) become the task's default configuration. Put the module in
src/olmo_eval/evals/tasks/ and it is registered on import. Check it with
uv run olmo-eval task inspect my_task --request -n 1 and
uv run olmo-eval run -m mock -t my_task -o limit=5. Generation tasks use a chat or
completion formatter instead, extract an answer in extract_answer, and score it with a
metric such as AccuracyMetric(scorer=ExactMatchScorer).
Task Class Overview
Method
Required
Purpose
instances
Yes
Property that yields Instance objects; usually yield from self._load_instances_cached()
format_request(instance)
Yes
Converts an Instance into an LMRequest for the model
process_doc(doc, index)
When using _load_instances*
Converts a raw document into an Instance, or None to skip it
extract_answer(output)
No
Extracts the answer from LMOutput; defaults to config.answer_extractor or the raw text
_build_fewshot()
No
Override to customize few-shot example loading
score_responses(...)
No
Override to customize scoring logic
compute_metrics(...)
No
Override to customize metric computation
TaskConfig Reference
Commonly used fields. TaskConfig in src/olmo_eval/evals/tasks/common/base.py
documents the rest.
Field
Type
Default
Description
name
str
Required
Task identifier used in CLI
data_source
DataSource | str | None
None
Dataset source (HuggingFace, S3, GCS, local, or URI string)
formatter
Formatter | None
None
Request formatter
metrics
tuple[Metric, ...]
()
Evaluation metrics (scorers are inferred from metrics)
num_fewshot
int
0
Number of few-shot examples
limit
int | None
None
Max instances to evaluate
split
Split
Split.TEST
Dataset split to use
primary_metric
MetricName | Metric | None
None
Primary metric for ranking (defaults to single metric if only one)
sampling_params
SamplingParams | None
None
Default sampling parameters for this task
answer_extractor
Callable[[str], str] | None
None
Function applied to output text by the default extract_answer
response_parser
str
unset
Set harmony to score only structured Harmony final-channel text
strip_thinking
bool
False
Drop <think>...</think> traces before scoring
dependencies
list[str] | None
None
Runtime packages to install (e.g., ["pkg==1.0"])
judge_model
str | None
None
Judge model for LLM-as-judge scoring
For inline vLLM, or a provider that returns raw Harmony delimiters, use
-o response_parser=harmony. Inline vLLM preserves the generated Harmony token
boundaries before its default decoder removes special tokens, saving them in
text_with_special_tokens for auditing or re-scoring. Saved predictions also
record response_parser_found: false means no delimited transcript was found
(including an already parsed plain answer). If a known structured decode cannot
be aligned with the stopped completion, explicit parsing raises a scoring error;
the diagnostic and raw text remain in saved predictions.
This opt-in changes the task hash, keeps the original decoded generation in original_text,
and scores an empty answer when a Harmony analysis trace has no final channel.
It accepts the documented <|start|>/<|channel|>/<|message|> delimiters of the gpt-oss
tokenizer. Already parsed plain text passes through. Decoding with special tokens removed loses message boundaries:
analysis...assistantfinal... cannot be parsed reliably; retain special tokens or
use a provider's native Harmony reasoning parser before evaluating.
Generation diagnostics
Generation diagnostics count outputs, including each sample of an instance.
cap_hit counts known length finish reasons and is a lower bound when
finish_reason_unknown is nonzero. unclosed_think detects emitted <think>
tags without a closing tag in the original generation. empty counts empty
scoring text and excludes tool calls. Tasks that strip or transform text inside
extraction, without changing LMOutput.text, set
output.metadata[SCORING_TEXT_EMPTY_KEY] from that scoring text before parsing;
a nonempty answer that fails parsing is still nonempty.
Data Sources
Tasks can load data from multiple sources using DataSource:
<
10BC8
div class="highlight highlight-source-python notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="from olmo_eval.data import DataSource
# HuggingFace datasets - specify path, subset, and split
DataSource(path="cais/mmlu", subset="abstract_algebra", split="test")
# Using URI string (alternative syntax)
DataSource.from_uri("hf://cais/mmlu?subset=abstract_algebra&split=test")
# Without subset (for datasets that don't have subsets)
DataSource(path="openai_humaneval", split="test")
# With specific data files and revision
DataSource(path="my-org/dataset", data_files="data/test.jsonl", revision="v1.0")
# Local JSONL files
DataSource(path="/path/to/dataset.jsonl")
# S3
DataSource(path="s3://my-bucket/datasets/data.jsonl")
# GCS
DataSource(path="gs://my-bucket/datasets/data.parquet")">
fromolmo_eval.dataimportDataSource# HuggingFace datasets - specify path, subset, and splitDataSource(path="cais/mmlu", subset="abstract_algebra", split="test")
# Using URI string (alternative syntax)DataSource.from_uri("hf://cais/mmlu?subset=abstract_algebra&split=test")
# Without subset (for datasets that don't have subsets)DataSource(path="openai_humaneval", split="test")
# With specific data files and revisionDataSource(path="my-org/dataset", data_files="data/test.jsonl", revision="v1.0")
# Local JSONL filesDataSource(path="/path/to/dataset.jsonl")
# S3DataSource(path="s3://my-bucket/datasets/data.jsonl")
# GCSDataSource(path="gs://my-bucket/datasets/data.parquet")
DataSource Fields:
Field
Type
Default
Description
path
str
Required
Dataset path (HuggingFace repo, S3/GCS URI, or local path)
Tasks with Multiple Subsets (like MMLU with 57 subjects):
# Base class with shared logicclassMMLUTask(Task):
...
# Register each subset - the subset is specified in DataSource@register("mmlu_anatomy")classMMLUAnatomy(MMLUTask):
data_source=DataSource(path="cais/mmlu", subset="anatomy", split="test")
@register("mmlu_physics")classMMLUPhysics(MMLUTask):
data_source=DataSource(path="cais/mmlu", subset="high_school_physics", split="test")
Adding Variants
Variants modify how a task is formatted/scored (e.g., :mc, :bpb):
fromolmo_eval.evals.tasks.commonimportregister_variant# Register after task is definedregister_variant("my_task", "bpb", formatter=PPLFormatter(), metrics=(BPBMetricByteAvg(scorer=BitsPerByteScorer),))
Variants can also encode configuration presets (e.g., :3shot, :zero):
Usage: uv run olmo-eval run -m llama3.1-8b -t humaneval:3shot:bpb
Tool-Augmented Evaluation
olmo-eval supports evaluating models with tool use through the Harness abstraction. This enables comparing baseline model performance against tool-augmented performance on the same tasks.
The Harness is the preferred way to add tools to evaluations. It separates tool configuration from task definition, allowing any task to be run with or without tools:
# Baseline evaluation (no tools)
uv run olmo-eval run -m llama3.1-8b -t simpleqa:judge
# Same task with search tools
uv run olmo-eval run -m llama3.1-8b -t simpleqa:judge --harness dr_tulu
See the Harness section above for full documentation on:
Creating custom harness configurations
Defining tools with the @tool decorator
Programmatic usage
Function Calling (BFCL)
The Berkeley Function Calling Leaderboard v3 single-turn categories are
registered as bfcl_* tasks, reformulated as a completion so a pretrained
model with no chat template can be measured on them. See
src/olmo_eval/evals/tasks/bfcl/README.md
for the task list, suites, exemplar settings, and what is not implemented.
uv run olmo-eval run -m my-base-model -t bfcl
Multimodal Evaluation
olmo-eval evaluates vision-language models on image benchmarks. Tasks attach images
to LMRequest.images and the provider decides how to render them, so the task
definition is the same regardless of which multimodal provider runs it.
Image-QA primary metrics are all 0-1, so molmo2_imageqa averages them.
dense_caption reports on a 0-100 scale, so molmo2_imageqa_caption is display-only
and computes no cross-task average. Pointing tasks score point-in-mask
precision/recall/f1 by maximum bipartite matching rather than VQA-style answers,
which is why they are a separate suite. Multi-image tasks attach a list of images
per instance (capped at 20, matching the mm_olmo eval config) and score multiple
choice answers by MMMU-style option-letter parsing; besides the primary all
accuracy each task reports per-category breakdowns (MuirBench's 12 task types,
BLINK's 14 subtasks, MMIU's 7 relationship types plus image-count buckets).
Document-OCR tasks hand the model one page image and grade the markdown (or JSON)
it writes back, each with its benchmark's official scoring:
olmocr_bench — olmOCR-bench:
1,403 PDF pages and 7,019 unit tests (text presence/absence, reading order, table
cells, rendered math). Tests run through the official olmocr scorer; overall is
the mean of the eight category pass rates (0-1). Math tests render with KaTeX in a
headless Chromium, which is installed on first use.
cc_ocr_multi_scene — the multi-scene OCR track of
CC-OCR: 2,750 scene-text, document and
web images in English and Chinese. The primary macro_f1 is the official track score
(0-1): per-image F1 of the word (Chinese: character) multiset, averaged within and then
across the 13 sub-datasets.
omnidocbench — OmniDocBench v1.6:
1,651 pages scored by the pinned official evaluator, which runs in a virtualenv of its
own (needs git and uv). overall is the leaderboard's 0-100 number, which is why
the ocr suite is display-only. Its formula metric (CDM) renders LaTeX; the task sets
up the toolchain itself at job start, so it runs in one step anywhere (see
OmniDocBench formula scoring). omnidocbench_v15
is the 1,355-page v1.5 release with its own evaluator; the two leaderboards are not
comparable.
English-only variants keep the benchmarks' official scoring on a subset:
cc_ocr_multi_scene_en runs the 8 English sub-datasets (2,000 images) and averages over
those, and each OmniDocBench version has an _en variant (omnidocbench_en,
omnidocbench_v15_en) that runs and scores only the pages annotated english (755 of
v1.6's pages, 628 of v1.5's). Molmo2 models read English only, so the _en variants (suite
ocr_en) are the default setting for evaluating them. The plain names run every language,
as the leaderboards do, for multilingual models; olmocr_bench is English throughout and
has no variant.
By default each OCR task sends its benchmark's own instruction, for instruction-tuned
checkpoints. A stage-1 checkpoint (-o prompt_templates=none -o system_prompt_style=style_and_length_v2) gets the OCR style tag it was trained on, alone:
olmocr: for olmocr_bench and OmniDocBench, textocr: for CC-OCR.
The OCR datasets are downloaded from the Hugging Face Hub at pinned revisions rather
than read from $MOLMO_DATA_DIR.
Setup
The multimodal providers live behind the hf and olmo_core_vlm extras, and the
benchmarks' own scoring dependencies (pycocotools, scipy) behind vision, which
every provider needs:
Beaker jobs install each task's scoring dependencies automatically.
Image data is read from a read-only tree — loaders raise rather than build
caches, so the data must already be staged:
export MOLMO_DATA_DIR=/weka/oe-training-default/mm-olmo # this is the default
A missing data root fails at task setup with the variable named, rather than
reading a nonexistent path (the Ai2 defaults are tracked for removal in #381).
Images live under $MOLMO_DATA_DIR/torch_datasets/. Manifests that recorded
absolute paths on another machine are re-anchored under the current root
automatically.
Four tasks call the OpenAI API and need OPENAI_API_KEY or OPENROUTER_API_KEY
(see OpenAI models through OpenRouter), so the
molmo2_imageqa suite needs one of them as a whole:
math_vista defaults to the official gpt-4-0613 answer extraction. Use the
math_vista:offline variant for an API-free heuristic instead.
charxiv_descriptive and charxiv_reasoning run the official gpt-4o grader.
dense_caption runs a gpt-4o recall+consistency judge. Judge responses are
cached, so the key is only needed on a cache miss.
The launcher resolves the key as the user-scoped beaker secret
{beaker-username}_OPENAI_API_KEY, or {beaker-username}_OPENROUTER_API_KEY when
that is missing. If neither exists, the launch fails up front and prints the
beaker secret write command to run.
Running
# Released HuggingFace checkpoint, full image-QA suite
uv run olmo-eval run -m molmo2-4b -t molmo2_imageqa
# A single benchmark
uv run olmo-eval run -m molmo2-4b -t mmmu_pro
# Pointing benchmarks
uv run olmo-eval run -m molmo2-4b -t molmo2_pointing
# Multi-image benchmarks
uv run olmo-eval run -m molmo2-4b -t molmo2_multiimage
# Inspect the suite without loading a model
uv run olmo-eval suite inspect molmo2_imageqa
The molmo2-4b preset runs allenai/Molmo2-4B with dtype=float32,
autocast_dtype=bfloat16 and max_crops=24 (8 per image in multi-image prompts),
matching the reference evaluation numerics.
OmniDocBench formula scoring (CDM)
omnidocbench, omnidocbench_v15 and their _en variants score formulas with CDM,
which renders every formula through LaTeX and ImageMagick. Before inference each task
installs whatever part of that toolchain is missing or at another version, then renders
a test formula through its version's own CDM template. The whole evaluation, CDM
included, finishes in the same job; no separate rescoring step is needed. Setup takes
about 5-9 minutes on a fresh machine.
The versions are the ones each release documents, because CDM follows the renderer
(Ghostscript 10.02 instead of 9.55 moves a formula score by about 0.2 points):
v1.6 (omnidocbench)
v1.5 (omnidocbench_v15)
LaTeX
TeX Live 2025, pdflatex + CJK
TeX Live 2025, xelatex + xeCJK
Font
gkai (TeX Live arphic)
Source Han Sans SC
ImageMagick
7.1.1-47
7.1.1-47
Ghostscript
9.55.0
9.55.0
Other
Node.js 16.13.1 (KaTeX tokenizer)
What the setup needs:
Network access: TeX Live 2025 comes from the frozen tlnet-final repository in the
TeX historic archive, ImageMagick and Ghostscript from their GitHub releases, the font
from Adobe's release, and Node.js from nodejs.org. Each download is checked against its
pinned SHA-256, and a mismatch stops the task.
Linux x86_64, where the installed binaries run. Elsewhere, put the toolchain on
PATH yourself.
apt (root, as in Beaker jobs), for the system libraries the ImageMagick AppImage
links against and for fontconfig.
TeX Live, ImageMagick and Ghostscript already on PATH at exactly these versions are
used as they are, as is any node; anything else is installed alongside, first on
PATH for the evaluation.
If a download fails or the test formula does not render, the task stops before inference
with the error, rather than scoring every formula zero. On a machine without network
access or root, put the pinned versions on PATH first (the task then uses them as they
are) or point OMNIDOCBENCH_CDM_DIR at a toolchain installed elsewhere.
Environment variables:
Variable
Default
Purpose
OMNIDOCBENCH_CDM_DIR
~/.cache/olmo_eval/omnidocbench/cdm
Where the toolchain is installed; point several runs at one shared install to reuse it
OMNIDOCBENCH_EVAL_DIR
~/.cache/olmo_eval/omnidocbench
Where each version's evaluator checkout and virtualenv are provisioned
The job log shows the toolchain setup (Running: ...install-tl..., tlmgr install ...),
then inference, then one overall for the task.
Providers
huggingface handles image-text-to-text models via AutoProcessor and
AutoModelForImageTextToText. Pass multimodal=True in the provider kwargs (the
molmo2-4b preset does this for you).
olmo_core_vlm runs OLMo-core MultimodalLM checkpoints in three on-disk
formats: raw trainer saves (config.json + model_and_optim/), consolidated
safetensors exports, and mm_olmo trainer checkpoints, which are config-translated
and key-remapped at load time. It drives its own decode loop because OLMo-core's
vision branch has no multimodal generation module.
Prefer provider.kind=olmo_core_vlm explicitly — remote jobs then install the
matching olmo_core_vlm extra. provider.kind=olmo_core also detects
multimodal checkpoints and reroutes (with a warning), but on Beaker that route
installs the text extra and fails at worker init:
uv run olmo-eval run \
--harness default -o provider.kind=olmo_core \
-m /weka/path/to/multimodal-checkpoint \
-t molmo2_imageqa
-o must follow --harness or -t, so the provider override needs an explicit
--harness default ahead of it.
A single image is cropped to the checkpoint's training max_crops
(-o provider.max_crops=N), and each image of a multi-image prompt to its
max_multi_image_crops (8 when the checkpoint names none;
-o provider.max_multi_image_crops=N), as mm_olmo does. The context window is
64,000 tokens, the length mm_olmo's eval_molmo2.py evaluates at, or the
checkpoint's training window if that is longer (-o provider.max_model_len=N); a
prompt longer than the window fails its instance rather than being truncated.
mm_olmo evaluates the released Molmo2 models at 24 crops per single image, so pass
-o provider.max_crops=24 to compare against their published numbers; document QA
is the most crop-sensitive.
No released ai2-olmo-core ships the multimodal classes (olmo_core.nn.vision,
MultimodalLM) yet, so the olmo_core_vlm extra pins OLMo-core's vision branch
by commit. It is declared conflicting with the text olmo_core extra — the two
cannot be installed together — and beaker jobs install it automatically for
provider.kind=olmo_core_vlm, so no package override is needed:
Once a release includes the multimodal classes, the extras collapse into one and
the pin goes away.
Adding a multimodal task
Image-QA tasks subclass ImageQATask (evals/vision/tasks/image_qa.py),
which resolves images from instance.metadata["image_path"] or
instance.metadata["image"] (a PIL image or a zero-arg callable returning one —
use load_instance_image from evals/vision/data/images.py for either form) and anchors data reads under
$MOLMO_DATA_DIR. Pointing tasks subclass PointingTask
(evals/vision/tasks/pointing.py). Multi-image tasks subclass
MultiImageQATask (evals/vision/tasks/multi_image.py), which resolves
instance.metadata["images"] — a callable returning the image list, or a sequence
of PIL images / zero-arg callables / file paths — and sends them all on one
request. Registration and variants work exactly as described in
Adding New Tasks.
Hill-Climb Dev Suite
hillclimb:dev runs LiveCodeBench lite, the OMEGA in/out pair, IFEval, IFBench
and GPQA main together. It has no cross-domain aggregate; each task reports
its score and omega:dev reports its held-out score and gap.
uv run olmo-eval run -m <checkpoint> -t hillclimb:dev \
--harness codex_python \
-o provider.kind=vllm -o provider.max_model_len=36864 \
-o sandboxes.0.inject_swerex=true
LiveCodeBench requires the sandboxed harness; see missing-sandbox tracking (#423). Set the same model context limit
for every checkpoint you compare; OMEGA has a 32,768-token generation cap.
Post-Training Dev and Test Suites
posttrain:tiny:dev, posttrain:tiny:test, posttrain:small:dev and
posttrain:small:test follow the Olmo eval tracker: its Track column is dev, its
Publish column is test. Use dev suites for hill-climbing and checkpoint selection;
read test suites only for release reports. No task is in both suites of one size.
None has a cross-domain aggregate. Dev leaves out four Track rows. GPQA is read in neither
variant: at 32,768 output tokens about 60% of a reasoning model's generations stop
mid-thought and score at chance, so the entry measures the cutoff more than the model;
MMLU:cot guards knowledge instead. HELMET's recall tasks repeat RULER+, and its QA tasks
are chat requests capped at 10 tokens, so a model that reasons first is scored on where the
cap cut it. CTC's 64K tasks report heterogeneous metrics with no aggregate, and its own
README says a reasoning model needs more than their direct-answer budgets.
Small dev also reads the tracker's agentic Track rows. They are
external evals, which a suite cannot hold, so each runs as its own
olmo-eval run-external -e <name> job beside the suite: openthoughts_tblite
(OpenThoughts-TBLite, 100 tasks) and terminal_bench_2 (Terminal-Bench 2.1, 89 tasks) for
the Terminal-Bench row, and swe_bench_verified. Tiny leaves them out: the tracker
deprioritizes them there.
They mix short tasks with RULER+ at 64K to 256K, LiveCodeBench (sandboxed harness,
as above) and LLM-judged safety and Arena-Hard tasks (see
local judge models). Sampled tasks run at the ledger's setting:
Olmo 3.5 Tiny's suggested temperature 1.0 and top_p 0.95 (Small uses them too, assumed from
Tiny), with 32,768 output tokens, through _t10_32k variants that keep each task's sample
count. Without the fixed budget, most of these tasks would generate up to whatever context
they are served at. RULER+ runs greedy in completion mode with
direct-answer budgets, and BFCL keeps its own settings. Do not override sampling or
max_tokens for the whole run. Serve sampled tasks with a context of at least their
longest prompt plus 32,768 tokens: the server refuses any request that does not fit (at
34,816, every prompt over 2,048 tokens), and only when the request reaches it.
Small dev is two suites, run as separate jobs. MT-Bench and Multi-IF need the
multi_turn_chat scaffold (--harness multi_turn_chat), which drops tool calls and has no
sandbox, so they are posttrain:small:dev:multi_turn; the rest is posttrain:small:dev.
The suites name no provider, but the engine is part of what a score measures: compare
scores only between runs on the same provider, and record it with each run. When the rest
of a suite runs on SGLang, BFCL needs its own job with provider.kind=vllm_server, the
only provider with tool calls. LiveCodeBench (livecodebench:lite in dev,
livecodebench_hidden in test) needs a sandbox harness, whose default image is CUDA 12.8
and so cannot install SGLang (SGLang); with --image set to the CUDA 13
evaluator image (Beaker 01M632ZP67Y4WMV060J74FSC3R) it runs on SGLang, and so does the
WildGuard judge. In Small test, Arena-Hard v2 also needs vLLM: it fits its output budget to the context,
which SGLang refuses.
Every suite has WildGuard-judged safety tasks, so a launch needs the wg_judge overrides
(-o auxiliary_providers.wg_judge.kind=vllm_server -o auxiliary_providers.wg_judge.model=allenai/wildguard -o auxiliary_providers.wg_judge.kwargs.add_bos_token=true), and the test suites also need the
sr_judge overrides for StrongREJECT; safety.py has both. Without them the run generates
every item and then fails at scoring. Each local judge gets its own GPUs on top of the
model's: at least 2 GPUs for dev and 3 for test, at tensor parallel 1.
Querying Results
Evaluation results can be stored in PostgreSQL and queried via the CLI.
Basic Queries
# Query by experiment ID
uv run olmo-eval results query --experiment exp_001
# Query by model
uv run olmo-eval results query --model llama3.1-8b
# Query by task (shows comparison matrix)
uv run olmo-eval results query --task mmlu --task gsm8k
# Query by experiment group
uv run olmo-eval results query -G my-benchmark-group --format json
# Combine filters
uv run olmo-eval results query --model llama3.1-8b --task mmlu --format json
Instance-Level Predictions
Include --instances to retrieve instance-level predictions:
# Get instances for an experiment
uv run olmo-eval results query --experiment exp_001 --task mmlu --instances --format json
# Paginate through large result sets using keyset pagination
uv run olmo-eval results query --task mmlu --instances --limit 1000 --format json
# Get next page using last_id from previous response
uv run olmo-eval results query --task mmlu --instances --limit 1000 --after-id 1000 --format json
The password is automatically fetched from AWS Secrets Manager on first connection.
This requires AWS credentials configured (via ~/.aws/credentials or environment variables).
All Environment Variables
Variable
Default
Description
OLMO_EVAL_DB_HOST
localhost
Database host
OLMO_EVAL_DB_PORT
5432
Database port
OLMO_EVAL_DB_NAME
olmo_eval
Database name
OLMO_EVAL_DB_USER
postgres
Database user
OLMO_EVAL_DB_PASSWORD
-
Database password (use this OR OLMO_EVAL_DB_SECRET_ARN)
OLMO_EVAL_DB_SECRET_ARN
-
AWS Secrets Manager ARN for password (fetched on auth failure)
Advanced Usage
Multi-GPU and Tool-Augmented Evaluation
# Basic evaluation
uv run olmo-eval run -m llama3.1-8b -t mmlu -t gsm8k -t arc_easy
# Large models with multi-GPU tensor parallelism
uv run olmo-eval run -m llama3.1-70b -t mmlu --num-gpus 4
# Refresh Hugging Face cache before loading a remote model
uv run olmo-eval run -m allenai/OLMo-2-1124-7B -t mmlu --force-download-model
# Tool-augmented evaluation with harness
uv run olmo-eval run -m llama3.1-8b -t simpleqa:judge --harness dr_tulu
Resuming Interrupted Runs
With --resume, a run records each instance as it finishes. Restarted on the same
output directory, it skips the instances an earlier attempt finished and writes the same
predictions and metrics as an uninterrupted run.
uv run olmo-eval run -m llama3.1-8b -t gsm8k -O results/ --resume # interrupted
uv run olmo-eval run -m llama3.1-8b -t gsm8k -O results/ --resume # continues
uv run olmo-eval run -m llama3.1-8b -t gsm8k -O results/ --fresh # discards progress
Flag
Description
--resume
Record progress under <output_dir>/progress/ and continue from any already there
--fresh
Delete the recorded progress of this run's tasks and model before the run; cannot be combined with --resume
Without either flag, a run records nothing and ignores progress it finds, with a warning.
Beaker task launches default to --resume and use --resume-volume ai2/olmo-eval
by default. Replacement jobs mount the same WEKA directory directly. Use
--resume-volume <org>/<name> to select a different native volume.
Beaker results are the same with or without resume. The eval writes predictions,
requests, metrics, the manifest and logs to /results as it runs, in the same layout
and at the same moments. Only the progress records and the run's identity (its manifest
and upload secret) go to the volume, and a completed run deletes them.
To run without progress recovery, pass --no-resume (YAML: resume: false). This
needs no resume-volume claim and records no recovery progress. Preempted or retried
attempts start over. --resume explicitly enables
recovery and overrides resume: false from YAML.
Before the first resumable launch in a workspace, grant it read-write access to the volume:
This requires a volume-capable Beaker CLI (v1.5.346 is qualified). Claims are managed
once per workspace; the launcher never changes access. If a claim is missing or
read-only, submission stops with the workspace-specific grant command, permission
requirements, and the --no-resume alternative. Every resumable submission gets its own
UUID directory under /eval-cache/tmp-7d/ by default. Set
--resume-retention-days 14 (YAML: resume_retention_days: 14) to use tmp-14d
and retain inactive recovery files for fourteen days. Any positive whole number of
days is accepted. Replacement jobs reuse their original UUID and retention path and take
an exclusive run lock. The job log names the run's WEKA recovery directory.
Grant read-write claims only to workspaces whose submitters are mutually trusted.
Progress records can contain serialized Python objects; checksums detect corruption
and do not authenticate writers.
Active runs refresh every recovery file's modification time hourly. After the selected
retention period of inactivity (seven days by default), files are eligible for WEKA
prune deletion. Actual deletion depends on the volume's prune schedule. Results are
unaffected: they are in Beaker results and upload to the dashboard normally. Expired,
missing, or corrupt committed progress stops recovery with an error. Shared-storage
progress flushes every five seconds and on graceful shutdown. A completion marker
prevents a replacement from repeating a successfully finished run when its results
came along; a rerun whose results lack them runs the eval again under the same run ID. External evals do
not support --resume-volume. See the implementation and validation plan.
What is recorded.progress/<model>/run.segments/ (on the volume under the Beaker
resume supervisor) holds one stream for the run: a
header per task, then one entry per finished instance with its outputs, scores and a
hash of its prepared request. Prompts are rebuilt from the task, not stored. A
generation's per-token logprobs are left out unless one of the task's metrics scores
from them (Scorer.reads_logprobs); its sum_logits and num_tokens are kept. Each
finished task also gets a <task>_<hash>.final.json. Entries reach the disk every two
minutes. The progress/ directory is never uploaded. A run that completes deletes
the records of its own tasks only.
What must match. The record format, task spec and hash, model hash and revision,
the harness settings that affect outputs, the inference engine version (such as
vllm==0.11.2), the git commit, the uncommitted changes to tracked files (a hash of
git diff HEAD; untracked files are not covered) and, for SGLang, the resolved runtime.
Editing code after an interruption therefore stops a resume; revert the edits, pass
--fresh, or use another output directory. A run that starts from a dirty tree warns
that resuming will need the same changes. Without git (an installed package), the
commit and changes are not compared. New untracked files, such as a new scorer module,
are not covered either; git add -N <file> includes one. A difference
stops the run with an error naming the setting; pass --fresh or use another output
directory. Where the run executes (GPU count, cluster, tensor parallelism, concurrency)
may differ and only logs a warning. An instance whose prepared request, gold
answer or metadata changed is run again.
Sharing an output directory. Runs of different tasks may share one. Each run
keeps the others' records when it completes and logs which tasks it is keeping. Only one
run at a time may record for a model: progress/<model>/.lock names the holder (host,
PID, start time, Beaker job). A second run fails with an error naming it. A lock whose
process is gone from this host is taken over; on Beaker, a job takes over any lock in its
own /results (the predecessor's, copied there) and any lock left by an earlier attempt of
the same job. In a shared directory (-O /weka/...), another job's lock is treated as any
lock from another host. For a lock from another host outside Beaker,
delete the file once that run is gone.
Failures. An instance is recorded only when an uninterrupted run would keep its
outcome: a scored response, or a failure the request itself caused, such as a context
overflow. Connection and server errors, judge or sandbox outages, and results that
arrive after SIGTERM are not recorded, so the resumed run tries them again. A task that
fails or exceeds its failure budget is computed again from its recorded instances.
Results return per request. While recording, providers that send requests one at
a time (vllm_server, litellm) return each result when it finishes, not when its
batch does. In-process engines (vllm, sglang, hf, olmo_core, olmo_core_vlm) return a batch at once; to
record them more often, lower the batch size with
--harness default -o batching.chunk_size=16.
Dashboard status. A --resume run stopped by SIGTERM, as a preemption or a canceled
Beaker job stops it, shows as interrupted instead of failed. The resumed run keeps
the run ID and shows as running again, then finishes with its final status. A run that
nobody resumes shows as stale, like a stale running run, after the same delay; olmo-eval results upload on its directory uploads
it as finished, with the status its saved results support. Other crashes, Ctrl-C, and
runs without --resume are still reported as failed.
Testing.OLMO_EVAL_DEBUG_EXIT_AT=0.5 makes a --resume run send itself SIGTERM
once half of its instances are recorded, which ends it as a preemption does (exit code
143).
Debugging and Inspection
olmo-eval provides tools for inspecting tasks, requests, and responses at various stages of evaluation.
Task Inspection (uv run olmo-eval task inspect)
Inspect task instances without running evaluation:
# View raw instance data
uv run olmo-eval task inspect arc_easy
# View multiple instances
uv run olmo-eval task inspect arc_easy -n 5 --skip 10
# View the LM request that will be sent to the model
uv run olmo-eval task inspect arc_easy:mc --request
# View formatted prompt with chat template applied
uv run olmo-eval task inspect humaneval -T meta-llama/Llama-3.1-8B-Instruct --formatted
# View tokenized representation
uv run olmo-eval task inspect humaneval -T meta-llama/Llama-3.1-8B-Instruct --tokens
# Export as JSON for programmatic use
uv run olmo-eval task inspect arc_easy --json
Option
Description
-n, --count
Number of instances to display
-s, --skip
Number of instances to skip
--instance
Show instance details (default if no other flags)
--request
Show the LM request
-T, --tokenizer
Tokenizer for formatting/tokenization
--formatted
Show prompt after template applied (requires -T)
--tokens
Show token array (requires -T)
--max-tokens
Max tokens to display (0 for no limit)
--max-chars
Max chars for formatted prompt (0 for no limit)
--max-string-length
Max chars for instance field values (0 for no limit)
--json
Output as JSON
Runtime Inspection Flags
Inspect data during evaluation runs with uv run olmo-eval run:
# Enable all inspection flags at once
uv run olmo-eval run -m llama3.1-8b -t mmlu --inspect
# Or use individual flags for specific inspection
uv run olmo-eval run -m llama3.1-8b -t mmlu --inspect-instance --inspect-request
# Inspect the response after model generation
uv run olmo-eval run -m llama3.1-8b -t mmlu --inspect-response
# Combine multiple inspection flags
uv run olmo-eval run -m llama3.1-8b -t mmlu \
--inspect-instance \
--inspect-request \
--inspect-response
Flag
Description
--inspect
Enable all inspection flags below
--inspect-instance
Print the first instance of each task before running
--inspect-request
Print the first LM request before model generation
--inspect-formatted
Show formatted prompt (after chat template applied)
--inspect-tokens
Show token array before evaluation
--inspect-response
Print the first response after model generation
Mock Provider for Testing
Use the mock provider to test inspection tools without loading a real model:
# Quick inspection without vLLM or PyTorch
uv run olmo-eval run -m mock -t humaneval:3shot:bpb --inspect-request
# Dry run with mock to preview configuration
uv run olmo-eval run -m mock -t mmlu --dry-run
External Evals
External evals are standalone evaluations that run outside the normal task pipeline.
Use them when a benchmark already comes with its own harness, verifier, or environment
and does not fit cleanly into the usual task formatter/scorer flow. They are a good fit
for agent-style benchmarks like terminal_bench_2, tau2_bench, and asta_bench
that need sandbox orchestration, benchmark-specific setup, or end-to-end execution
against an external repo or runner.
Defining an External Eval
SandboxedExternalEval runs a benchmark inside one container. This sketch runs a
benchmark's own CLI against the model under evaluation and reads its score file:
Benchmark modules under src/olmo_eval/evals/external/benchmarks/ are registered
automatically. See benchmarks/tau2/eval.py for a complete implementation, including how
a sandboxed benchmark reaches a locally served model.
Running External Evals
# List available external evals
uv run olmo-eval external-evals
# Run a built-in external eval
uv run olmo-eval run-external -e tau2_bench --model llama3.1-8b -a domain=airline -a num_tasks=1
ExternalEvalResult
External evals return structured results:
Field
Type
Description
name
str
Eval identifier
metrics
dict[str, float]
Evaluation metrics
metadata
dict
Additional metadata
success
bool
Whether the eval completed successfully
error
str | None
Error message if failed
duration_seconds
float | None
Execution time
raw_output
str | None
Raw stdout/stderr from the evaluation
predictions
list[dict] | None
Instance-level predictions
Sandboxes
Sandboxes provide isolated execution environments for code execution, tool use, and external evals.
Configuration
fromolmo_eval.harness.sandboximportSandboxConfig, SandboxMode, Capabilityconfig=SandboxConfig(
image="python:3.12-slim",
mode=SandboxMode.DOCKER,
command_timeout=30.0,
startup_timeout=60.0,
instances=4, # Run 4 parallel executorsworking_dir="/workspace",
environment=(("MY_VAR", "value"),),
volumes=(("/host/path", "/container/path"),),
capabilities=Capability.BASH|Capability.PYTHON, # Union of frozensets
)
SandboxConfig Fields
Commonly used fields. SandboxConfig in src/olmo_eval/harness/sandbox/config.py
documents the rest, including Modal and SWE-ReX options.
Field
Type
Default
Description
image
str
Required
Container image
mode
SandboxMode
Required
LOCAL, DOCKER, or MODAL
capabilities
frozenset[str]
Capability.DEFAULT
Capabilities this sandbox provides, such as Capability.BASH or Capability.PYTHON
instances
int | None
None
Executors to create; None lets the caller allocate capacity, otherwise one executor
container_runtime
str
"podman"
"docker" or "podman"
command_timeout
float
30.0
Default timeout per command (seconds)
working_dir
str
"/workspace"
Working directory in the container
environment
tuple[tuple[str, str], ...]
()
Environment variables as (name, value) pairs
volumes
tuple[tuple[str, str], ...]
()
Volume mounts as (host_path, container_path) pairs
Using SandboxManager
The SandboxManager manages multiple executors and routes each command to an executor
with the required capabilities:
importasynciofromolmo_eval.harness.sandboximportCapability, SandboxConfig, SandboxManager, SandboxModeconfigs= [
SandboxConfig(image="python:3.12", mode=SandboxMode.DOCKER, capabilities=Capability.PYTHON, instances=2),
SandboxConfig(image="ubuntu:22.04", mode=SandboxMode.DOCKER, capabilities=Capability.BASH),
]
manager=SandboxManager(configs, owner="my-scorer")
awaitmanager.start()
# Run on an executor that provides the python capabilityoutput=awaitmanager.execute("python -c 'print(1)'", capabilities=Capability.PYTHON)
# Commands that need the same capability are spread across matching executorsoutputs=awaitasyncio.gather(*[
manager.execute(command, capabilities=Capability.PYTHON) forcommandincommands
])
# execute_command and execute_code return an ExecutionResult with exit code and outputresult=awaitmanager.execute_code("print('hello')", capabilities=Capability.PYTHON)
awaitmanager.stop()
Sandbox Modes
Mode
Description
LOCAL
Run commands locally (development only)
DOCKER
Run in Docker/Podman containers
MODAL
Run on Modal cloud platform
Launching on Beaker
olmo-eval includes built-in support for launching evaluation jobs on Beaker.
Installation
The beaker extra is included in the default dev group, so a plain
uv sync --frozen is enough. If you previously opted out of the default
groups, re-enable it with:
For tasks or custom scorers that use a named auxiliary judge provider, you can run a local judge model alongside the main model. Put harness overrides immediately after --harness, then put task overrides after -t.
Tasks can include an optional @priority suffix to set different priorities per task.
Tasks with different priorities will be launched as separate Beaker experiments:
# Mixed priorities - creates separate experiments per priority level
uv run olmo-eval beaker launch -n "eval-suite" -m llama3.1-8b \
-t "mmlu@high" \
-t "gsm8k@normal" \
-t "arc_easy@low" \
--cluster h100 \
-w "ai2/olmo-eval-debug" \
-B "ai2/oe-other"# Creates 3 experiments:# eval-suite-high: runs mmlu at high priority# eval-suite-normal: runs gsm8k at normal priority# eval-suite-low: runs arc_easy at low priority# With task variants (@ comes after the task spec)
uv run olmo-eval beaker launch -n "eval" -m llama3.1-8b -t "arc_easy:mc@high" \
--cluster h100 \
-w "ai2/olmo-eval-debug" \
-B "ai2/oe-other"# Tasks without @priority use the config file priority (default: normal)
Experiment Groups
Groups logically organize experiments for management and result retrieval:
# Launch with grouping
uv run olmo-eval beaker launch -n "benchmark-v1" --group "benchmark-2024" \
-m llama3.1-8b -m olmo-2-7b \
-t mmlu -t gsm8k -t hellaswag \
--cluster h100 \
-w "ai2/olmo-eval-debug" \
-B "ai2/oe-other"# Creates experiment and adds it to "benchmark-2024" group# Check group status and results
uv run olmo-eval beaker group info benchmark-2024
# Show detailed task info
uv run olmo-eval beaker group info benchmark-2024 --verbose
# Wait for completion and export as CSV
uv run olmo-eval beaker group info benchmark-2024 --wait --format csv > results.csv
# Export as JSON
uv run olmo-eval beaker group info benchmark-2024 --format json
# Watch experiment logs
uv run olmo-eval beaker watch -e <experiment-id># Cancel all experiments in a group
uv run olmo-eval beaker group cancel benchmark-2024
# List groups in a workspace
uv run olmo-eval beaker group list -w <workspace>
Inference Provider Configuration
Docker images do NOT include inference providers (vllm, transformers, litellm) by default.
Each model must resolve to a provider configuration, either from a built-in model preset or from harness overrides.
Experiment name (auto-generated from model/tasks if not provided)
--model
-m
required
Model name or HuggingFace path (can specify multiple)
--task
-t
required
Task name with optional @priority suffix (can specify multiple)
--harness
-H
none
Harness preset name
--override
-o
none
Override for preceding -t or -H (can specify multiple)
--cluster
-c
required
Cluster alias (h100, a100, aus) or full name
--gpus
-G
auto
Number of GPUs (defaults to 1 for GPU providers, 0 otherwise)
--max-gpus-per-node
8
Maximum GPUs per node (tasks split if exceeded)
--priority
-p
normal
Job priority (low, normal, high, urgent)
--preemptible
true
Allow preemption (deprecated by Beaker; prefer --min-runtime); --no-preemptible means --min-runtime 8h
--min-runtime
none
Minimum runtime before Beaker may preempt the job (e.g., 2h)
--timeout
-T
24h
Job timeout (e.g., 24h, 30m)
--retries
-r
none
Number of retries on failure
--workspace
-w
required
Beaker workspace
--budget
-B
required
Beaker budget
--image
-I
default
Custom Beaker image
--group
-g
auto
Add experiments to Beaker group(s) (auto-generated if not specified)
--external-eval
-E
none
External evaluation name(s) to run instead of tasks
--eval-arg
-A
none
Arguments for external evals (key=value)
--provider-kwarg
-K
none
Provider kwargs for external evals (key=value)
--force-download-model
false
Refresh Hugging Face model/tokenizer cache before loading
--uv-cache-dir
default
UV cache directory for package downloads
--weka-bucket
none
Extra Weka bucket to mount at /weka/<bucket>, on top of oe-training-default, oe-eval-default and oe-adapt-default (can specify multiple)
--resume / --no-resume
enabled
Recover interrupted task progress; opt out to run without a resume-volume claim
--resume-volume
ai2/olmo-eval
Native Beaker volume for resumable task state; requires a read-write workspace claim
--resume-retention-days
7
Positive whole number of days to retain inactive recovery files
--dry-run
-d
false
Print spec without launching
--yes
-y
false
Skip confirmation prompt
--follow/--no-follow
true
Follow logs after launch
--secret-env
none
Map Beaker secret to env var (SECRET:VAR)
--aws-credentials
auto
Inject AWS credentials (auto-detected from s3:// paths)
--gcp-credentials
auto
Inject GCP credentials (auto-detected from gs:// model paths)
--store
false
Persist results to configured database
Preemption
By default, Beaker can preempt a job at any time and requeues it afterward. The requeued
job continues from the instances the preempted one recorded (see
Resuming Interrupted Runs). Use --min-runtime (or
min_runtime: in a config file) to protect a job from preemption:
How long the protection lasts depends on the cluster's scheduling policy. Where the
policy makes jobs interruptible after their min runtime, the job is protected for that
long, then can be preempted and is requeued. On other clusters, any min runtime
protects the job for its whole run.
Beaker accepts 5m to 8h, and no more than the cluster's maximum task timeout.
Clusters that require an allocation for protected work reject a min runtime unless
your workspace has an allocation there.
min_runtime cannot be combined with preemptible in the CLI or in a config file.
When the CLI sets --min-runtime or --preemptible/--no-preemptible, it replaces
both config file fields.
External evals (-E) take --min-runtime from the CLI only; they do not read the
config file.
--no-preemptible (and preemptible: false) is the same as --min-runtime 8h. The
launcher sends the min runtime and leaves preemptible unset, so Beaker keeps
auto-resume on. Sending preemptible=False to Beaker would also turn auto-resume off,
and on clusters that make jobs interruptible after the min runtime, a preemption after
8h would end the experiment with no replacement job.
Per-Task Overrides
Use the -o/--override flag to apply configuration overrides to the preceding -t:
Task overrides accept TaskConfig fields and SamplingParams fields (sampling shortcuts
such as temperature or max_tokens). Any other key after -t is rejected; harness keys
belong after --harness.
Kind
Example
Number
-o limit=100, -o num_fewshot=5
Sampling shortcut
-o temperature=0.6, -o max_tokens=512
Boolean
-o strip_thinking=true
List (JSON)
-o 'dependencies=["pkg1==1.0", "pkg2"]'
Harness, nested
--harness default -o provider.max_model_len=16384
Quote values that contain brackets, braces, or spaces so the shell passes them through
unchanged.
OpenAI models through OpenRouter
Anything that needs OPENAI_API_KEY also accepts an
OpenRouter key in OPENROUTER_API_KEY (or
OPEN_ROUTER_KEY). This covers LLM
judges, the OpenAI models in the model registry (gpt-4o, gpt-4o-mini, ...),
and the OpenAI models that external evals such as tau2 and DeepScholar call.
When both keys are set, the OpenAI key is used.
With only an OpenRouter key, requests go to OpenRouter's OpenAI-compatible
endpoint, and OpenAI model names are sent as OpenRouter slugs (gpt-4o becomes
openai/gpt-4o). Judge response caches are still keyed by the OpenAI model name,
so existing caches keep working.
Locally, export the key:
export OPENROUTER_API_KEY=sk-or-...
On Beaker, store it as a user-scoped secret. A task that requires
OPENAI_API_KEY uses {beaker-username}_OPENROUTER_API_KEY when
{beaker-username}_OPENAI_API_KEY does not exist:
External evals run third-party code in a sandbox. There, the OpenRouter key is
exported as both OPENROUTER_API_KEY and OPENAI_API_KEY, and OPENAI_BASE_URL
points the OpenAI SDK at OpenRouter. olmo-eval rewrites the model names it passes
to tau2, DeepScholar and AstaBench, but it cannot rewrite names hardcoded inside a benchmark,
such as AstaBench's scorer models. OpenRouter resolves most bare OpenAI names
(gpt-4o-mini, o3-mini-2025-01-31), but rejects dated snapshots it does not list,
such as gpt-4-0613.
The openai_agents harness compacts long agent histories through OpenAI's
responses.compact endpoint. OpenRouter does not serve that endpoint, so with
only an OpenRouter key the harness skips compaction, logs a warning and keeps
the full history.
Secret Environment Overrides
By default, Beaker secrets are mapped using the pattern {username}_{ENV_VAR} (e.g., ai2-tylerm_OPENAI_API_KEY).
Use --secret-env to override this with a custom Beaker secret name:
# Use a team-shared secret instead of your personal secret
uv run olmo-eval beaker launch -n "eval" -m gpt-4o -t mmlu \
--secret-env team-openai-key:OPENAI_API_KEY \
--cluster h100 \
-w "ai2/olmo-eval-debug" \
-B "ai2/oe-other"# Multiple secret overrides
uv run olmo-eval beaker launch -n "eval" -m gpt-4o -t simpleqa:judge \
--harness dr_tulu \
--secret-env team-openai-key:OPENAI_API_KEY \
--secret-env shared-serper-key:SERPER_API_KEY \
--secret-env shared-s2-key:S2_API_KEY \
--cluster h100 \
-w "ai2/olmo-eval-debug" \
-B "ai2/oe-other"
Format: BEAKER_SECRET_NAME:ENV_VAR_NAME
This is useful for:
Using team-shared API keys instead of personal secrets
Testing with different credential sets
Sharing jobs that use organization-level secrets
YAML Configuration
For complex or reusable configurations, use YAML config files with the --config/-f option.
CLI arguments override values from the config file.
# Run from config file
uv run olmo-eval beaker launch -f eval_config.yaml --dry-run
# Override specific values
uv run olmo-eval beaker launch -f eval_config.yaml --gpus 4
# Replace the config's model list from the CLI
uv run olmo-eval beaker launch -f eval_config.yaml -m olmo-2-7b
Native Beaker volume for resumable task state (default: ai2/olmo-eval)
resume_retention_days
int
no
Days to retain inactive recovery files (default: 7; must be positive)
beaker_image
string
no
Container image to use (config-only)
groups
list
no
Beaker groups to add experiments to
CLI options override config values. -m and -t replace the config's lists rather than
adding to them.
See examples/beaker/configs/ for more configuration examples.
Cluster Aliases
# List clusters with utilization
uv run olmo-eval beaker clusters
# Only show cluster aliases
uv run olmo-eval beaker clusters --aliases
Programmatic API
fromolmo_eval.launchimportBeakerJobConfig, BeakerLauncherconfig=BeakerJobConfig(
name="eval-llama3-mmlu",
command=["uv", "run", "olmo-eval", "run", "-m", "llama3.1-8b", "-t", "mmlu"],
cluster="h100",
workspace="<workspace>",
budget="<budget>", # optional when the workspace has a bound budgetnum_gpus=1,
)
launcher=BeakerLauncher()
experiment=launcher.launch(config)
print(f"Launched: {launcher.beaker.experiment.url(experiment)}")
Docker Image Management
Docker images provide the runtime environment (Python, PyTorch, CUDA) but do NOT include:
Source code - Gantry mounts your git repository at runtime
Inference providers - Installed at job startup from each model's resolved provider config
This approach allows you to:
Use any git commit without rebuilding images
Keep images small and cacheable
Building Images
Images are tagged with CUDA and PyTorch versions: cu{version}-trc{version}-{arch}
# Build with defaults
./scripts/build_image.sh
# Specific CUDA + PyTorch version
./scripts/build_image.sh --cuda-version 12.8.1 --torch-version 2.10.0
# Production build
./scripts/build_image.sh --platform linux/amd64
# See supported CUDA+PyTorch pairs
./scripts/build_image.sh --help
Supported CUDA versions: 12.6.1, 12.8.0, 12.8.1, 12.9.1, 13.0.2
PyTorch version: Configurable via --torch-versionConfiguration: See scripts/build_config.sh
Pushing Images
# Push most recent build
./scripts/beaker/push_beaker_image.sh
# Preview without pushing
./scripts/beaker/push_beaker_image.sh --dry-run
The script auto-detects the image name from the tag (e.g., olmo-eval-cu1281-trc2100-amd64)
What's in the Image
The image contains:
Python 3.12 (via uv)
PyTorch with CUDA support
Base Python dependencies from uv.lock
System dependencies (git, uv, ca-certificates)
The image does NOT contain:
olmo-eval source code (provided by gantry at runtime)
Storage backends like boto3, psycopg (installed at job startup if needed)
Inference providers like vllm, transformers, litellm (installed at job startup)
For the SGLang provider, provider.kind=sglang selects a compatible shared
CUDA 13 / PyTorch 2.13 base and installs the pinned sglang extra (including the
Olmo model plugin) at startup. Explicit image overrides take precedence. See the
SGLang setup guide for local installation, checkpoint registration
and a small validation command.
Installing Inference Providers at Runtime
Inference providers are NOT baked into images. They are installed at job startup from the resolved provider configuration for each model:
# In config filemodels:
- llama3.1-8b
- gpt-4o
# Or force the provider kind via a harness override
uv run olmo-eval beaker launch -n "eval" \
--harness default \
-o provider.kind=vllm_server \
-m llama3.1-8b \
-t mmlu \
--cluster h100 \
-w "ai2/olmo-eval-debug" \
-B "ai2/oe-other"# Manual installation inside container
uv pip install -e '.[vllm]'# includes vllm[runai]
For raw OLMo-core checkpoints, force the provider kind through the harness and
use provider.package when you need a specific ai2-olmo-core install:
The OLMo-core source URL is normalized to install the
ai2-olmo-core[torchao,transformers] distribution, so branch overrides replace
the bundled OLMo-core dependency instead of installing a separate repo-named
package.
Task-Specific Dependencies
Tasks can declare runtime dependencies that are installed at job startup (see Tasks). Dependencies are automatically merged, deduplicated, and installed after the inference provider.
You can also add or override dependencies via the CLI:
# Add dependencies to a task via -o flag
uv run olmo-eval beaker launch -n "eval" -m llama3.1-8b \
-t humaneval:3shot:bpb -o 'dependencies=["code-sandbox==1.0", "git+https://github.com/user/repo@v2.0"]' \
--cluster h100 \
-w "ai2/olmo-eval-debug" \
-B "ai2/oe-other"# Dependencies from multiple tasks are merged
uv run olmo-eval beaker launch -n "eval" -m llama3.1-8b \
-t humaneval:3shot:bpb -o 'dependencies=["pkg1"]' \
-t mbpp:3shot:bpb -o 'dependencies=["pkg2"]' \
--cluster h100 \
-w "ai2/olmo-eval-debug" \
-B "ai2/oe-other"