Eval suites (HARNESS-03)

rollout-harness-eval bundles three scorers that mirror lm-evaluation-harness at a pinned version (LM_EVAL_VERSION = "lm-eval-harness-v0.4.9", cited in the scorer rustdoc). The pin removes scoring ambiguity: published MMLU/GSM8K conventions diverge, so the authoritative convention is declared and version-pinned.

MMLU — report both acc and acc_norm (D-EVAL-03)

  • acc — predicted answer = argmax over the four choice continuations' total log-likelihood; correct iff it matches the gold letter (A–D). Raw exact-match, temperature = 0.
  • acc_norm — same argmax, but each choice's log-likelihood is length-normalized before the argmax.

Both are reported (lm-eval's headline pair). Prompt format follows lm-eval's mmlu task default (A./B./C./D. + Answer:).

IFEval — strict, non-language (D-EVAL-04)

Pure-Rust strict checks for the verifiable non-language constraints (word / sentence count, JSON format, bullet count, keyword existence/frequency, case, placeholders). Language-detection constraints (language:*) are SKIPPED — no langdetect/langid dependency — and a load-time warning fires: "IFEval: skipping N language-detection constraints (unsupported in v1.1)". A prompt whose instructions are all skipped is excluded from both strict denominators; a partially-skipped prompt is scored on the remainder. Reported metrics: inst_strict_acc, prompt_strict_acc.

GSM8K — #### numeric extraction (D-EVAL-04)

Gold answer = the number after the final #### (strip ,/$). Model answer = the last match of the lm-eval gsm8k filter regex (-?[$0-9.,]{2,})|(-?[0-9]+) in the generation. Score = exact numeric equivalence, temperature = 0.

Datasets: offline-default (D-EVAL-01)

HF_OFFLINE=1 is the test default. Loaders read vendored SHA-pinned 10-row parquet fixtures under crates/rollout-harness-eval/tests/fixtures/, so eval_score_matches_lm_eval_harness (≤1% parity) and eval_loader_works_with_no_network run on every commit with zero network. Each fixture has a hardcoded blake3 constant that fails loudly on drift.

Online runs (HF_OFFLINE=0) download full splits via hf-hub (pure-Rust, rustls — never openssl) and persist to the v1.0 ObjectStore under a ContentId hash-checked cache. google/IFEval has stricter anonymous rate limits — set HF_TOKEN for full-split runs.

Eval as a WorkQueue job (D-EVAL-05)

One example = one queue item, reusing the Phase-6 rollout-coordinator::work_item CAS state machine (Pending → Running → Done{result_id}, single-winner via try_claim/try_complete). The item id is blake3(suite, version, idx, model_id) — idempotent, so re-enqueue is a no-op. Per-task determinism comes from a fixed seeded order + temperature = 0; MockEvalBackend makes the whole path GPU-free.

The aggregate EvalReport is persisted to the eval_reports storage namespace (postcard row) and content-addressed as a full blob in the object store.

This is eval execution as a job — NOT the eval gate (pause training → eval → continue/stop), which lands with HARNESS-04 in v1.2.