Fully automated SWE-bench Verified runs against locally-hosted LLMs.
Given a YAML config file, this tool:
- Downloads the model (GGUF via
huggingface_hubfor llama.cpp; HF snapshot for vLLM) - Starts the backend server (llama.cpp or vLLM)
- Runs Harbor (with OpenHands by default) against the dataset
- Stops the server
- Collects pass/fail results from Harbor's verifier output
Sweep mode lets you test multiple quantizations (or other parameter variations) of the same model in a single config, with an automatic comparison table at the end.
- Python 3.11+
- uv (for
uvx harbor) - Docker (for Harbor agent containers)
- llama.cpp binary (
llama-server) in PATH — or specify path in local config - OR: vLLM installed (
pip install vllm) for the vLLM backend
git clone https://github.com/yourname/auto-bench
cd auto-bench
uv sync# Validate a config
uv run auto-bench validate configs/smoke-test.jsonnet
# Download model only (no inference)
uv run auto-bench download configs/smoke-test.jsonnet
# Run the full pipeline (download → server → agent → evaluate)
uv run auto-bench run configs/smoke-test.jsonnet
# Run inference only, skip evaluation
uv run auto-bench run configs/smoke-test.jsonnet --skip-eval
# Start backend server and block until Ctrl+C (useful for manual testing)
uv run auto-bench serve configs/smoke-test.jsonnet
# Run a quantization sweep
uv run auto-bench run configs/llama3.1-8b-quant-sweep.jsonnet
# Resume a partially-completed sweep (specify directory)
uv run auto-bench run configs/llama3.1-8b-quant-sweep.jsonnet --resume-from results/sweep_llama3.1-8b-quant-sweep_20260428_153012
# Resume the most recent sweep automatically
uv run auto-bench run configs/llama3.1-8b-quant-sweep.jsonnet --resumeAll commands accept --local / -l to specify a local config file (default: ~/.config/auto-bench/local.yaml).
Resuming a sweep requires its saved compiled JSON to be structurally equivalent to the current Jsonnet output. The resume aborts before running any entries if the file is missing, invalid, or different.
Configs are split into two files:
| File | Purpose | Committed to repo? |
|---|---|---|
| Experiment YAML | What to run: model, dataset, sampling, agent settings | Yes |
~/.config/auto-bench/local.yaml |
How to run: host, ports, HF token, backend paths | No |
This keeps experiment configs portable across machines while keeping machine-specific settings out of version control.
Create this file on each machine you run auto-bench on. All fields are optional.
# Network settings
# host: address the server binds on, from the host's perspective (default: 172.17.0.1)
# docker_gateway: address containers use to reach the host (default: 172.17.0.1)
# These are separate because you may bind on 127.0.0.1 locally but containers
# need to reach the host via the Docker bridge IP.
# Linux (Docker Engine): 172.17.0.1 (default)
# macOS / Docker Desktop: host.docker.internal
host: 127.0.0.1
docker_gateway: "172.17.0.1"
port: 8080 # override port (default: 8080 for llamacpp, 8000 for vllm)
startup_timeout: 300 # seconds to wait for backend /health endpoint
# HuggingFace token for gated models
hf_token: hf_xxxxxxxxxxxxxxxxxxxx
# Backend-specific overrides (merged with experiment config)
llamacpp:
binary: /usr/local/bin/llama-server # path to llama-server binary
n_gpu_layers: all # int, "auto", or "all"
parallel: 4 # concurrent request slots
vllm:
dtype: bfloat16
gpu_memory_utilization: 0.95
tensor_parallel_size: 2
openai:
base_url: https://api.openai.com/v1
api_key: sk-xxxxxxxxxxxxxxxxxxxxname: my-run # run name (used for output dirs)
dataset: SWE-bench/SWE-bench_Verified # HF dataset (default: Verified)
split: test # dataset split
instance_ids: [] # optional: only run specific instances
output_dir: results # where to write outputs
backend_type: llamacpp # llamacpp | vllm | openai
remove_downloaded_models: false # delete model after run to free disk
model:
name: my-model # logical name in predictions JSONL
source: huggingface # huggingface | local
repo_id: bartowski/Llama-3.1-8B-GGUF # HF repo
filename: Llama-3.1-8B-Q4_K_M.gguf # GGUF filename (llamacpp only)
revision: main # branch/tag/commit
local_path: null # for source: local
allow_patterns: null # vLLM: files to include (e.g. ["*.safetensors"])
ignore_patterns: null # vLLM: files to exclude
# Optional: sweep across multiple quantizations / variants
sweep:
- label: Q4_K_M
filename: Llama-3.1-8B-Q4_K_M.gguf
- label: Q8_0
filename: Llama-3.1-8B-Q8_0.gguf
sampling: # optional per-entry sampling overrides
temperature: 0.5
overrides: # arbitrary deep-merged overrides
backend_options:
ctx_size: 65536
backend_options: # experiment-level context-length settings
ctx_size: null # llamacpp --ctx-size (null = model default)
sampling:
temperature: 0.0
top_p: 1.0
max_tokens: -1 # -1 = model/server default
top_k: null
min_p: null
presence_penalty: null
repetition_penalty: null
extra: {} # forwarded verbatim to API extra_body
agent:
agent: openhands # Harbor agent name
env: docker # Harbor execution environment
attempts: 1 # -k (attempts per instance)
limit: null # -l (max instances from dataset; null = all)
trials: 1 # -n (concurrent trials)
setup_multiplier: 10.0 # --agent-setup-timeout-multiplier
agent_timeout_multiplier: 1.0 # --agent-timeout-multiplier
max_retries: 0 # -r (max retry attempts per trial)
agent_kwargs: [] # --ak key="value" entries
# openhands version/python_version auto-injected
agent_env: [] # extra --ae KEY=VALUE entries
extra_args: [] # extra Harbor CLI flags
evaluation:
run_evaluation: true # set false to skip result collection
perplexity:
enabled: true # measure WikiText-2 perplexity after starting server
dataset: wikitext
dataset_name: wikitext-2-raw-v1
split: test
max_chars: 40000 # max characters of text to evaluate
chunk_chars: 4096 # per-request chunk size (chars)
stride_chars: 2048 # stride between chunks (50% overlap)Each run creates a timestamped directory under output_dir:
results/
└── my-run_20240428_153012/
├── my-run.jsonnet # original experiment config
├── my-run.json # compiled experiment config (before local merge)
├── run_meta.json # perplexity and runtime saved for --resume
└── jobs/ # Harbor output
└── <instance-id>/
└── verifier/
└── reward.txt # "1" = resolved, "0" = failed
Sweep runs collect entries under a parent sweep_{name}_{timestamp}/ directory:
results/
└── sweep_llama3.1-8b-quant-sweep_20240428_153012/
├── llama3.1-8b-quant-sweep.jsonnet # original sweep config
├── llama3.1-8b-quant-sweep.json # compiled sweep (before local merge)
├── summary.md # comparison table (updated after each entry)
├── llama3.1-8b-quant-sweep-Q4_K_M_20240428_153012/
│ ├── run_meta.json
│ └── jobs/...
└── llama3.1-8b-quant-sweep-Q8_0_20240428_160512/
├── run_meta.json
└── jobs/...
- Create
auto_bench/backends/mybackend.pysubclassingBackend - Implement
download(),start(),stop(),model_name - Add the backend type to
BackendConfig.typeinconfig.py - Register it in
runner.py:make_backend()