Changelog¶
All notable changes to this project are documented here. The format follows Keep a Changelog, and versions follow SemVer (pre-1.0: minor versions may include breaking changes).
[Unreleased]¶
Fixed¶
- Runtime initialization and prefix-cache state now require separate evidence; unknown load timing remains unavailable rather than zero.
- Python 3.10 CI excludes the Python 3.11-only SDLC kit through a development dependency marker while retaining all product tests and lint checks.
- Sandbox verdict integrity and trust boundary (P0). Candidate stdout and stderr no longer determine coding benchmark verdicts or test counts. The sandbox now communicates test execution status out-of-band via a temporary result file verified with a secret token, enforces caller-provided total test counts, and treats premature exits (such as
SystemExit(0)) or forged stdout markers as test failures.
Changed¶
- CSV export appends a
gpu_fit_pctcolumn (byte share of the model resident in GPU memory; empty when the runtime does not report it). Added a model-selection protocol page for 12 GB GPUs. - Moved manual September benchmark investigations into a dated documentation archive, with historical scope and source-artifact availability clearly identified.
- Split the CLI into command modules while preserving flags, help text, entry points, and existing package imports.
- Repo-quality hardening and documentation batch (Phase 12, items 12.1, 12.2, 12.9, 12.10).
- Clarified sandbox execution model and residual risk: docstrings in
benchrig/core/sandbox.pyand documentation indocs/benchmark-suites.mdanddocs/development.mdnow define execution as process-group subprocess isolation rather than OS-level sandboxing, disclosing interpreter-level residual risks. - Documented coding suite scope as Python-only across
README.md,docs/benchmark-suites.md, and newbenchrig/data/scenarios/coding.README.md. - Added "last green run" status badge and workflow link to
README.md. - Streamlined
AGENTS.mdby moving the detailed runtime troubleshooting playbook (§1–§4) intodocs/agent-debugging.mdand registering it inmkdocs.yml. - SDLC scaffolding now comes from
local-sdlc-kit.scripts/sdlc_check.py,.agents/skills/sdlc*,.cursor/rules/sdlc.mdcand.githooks/pre-commitwere hand-maintained copies of the kit's templates (byte-identical apart from cosmetic type-hint formatting); they're now installed and refreshed via thelocal-sdlc-kitdev dependency (local-sdlc-kit-install --update) instead of being hand-edited.AGENTS.mdgained the kit'sDevelopment processandProcess rulessections (previously missing, so the gate wasn't actually referenced from the process doc).scripts/sdlc_check.pyis now excluded fromruff(pyproject.toml) since the kit template targets an older typing style than this repo's lint config. No behavior change: the gate's checks (tests,lint,changelog) andsdlc.tomlare unchanged.
Added¶
- Added opt-in
--cache-probepaired requests with fixed-setting evidence, separate load/prefill/decode/TTFT provenance, and a dedicated Markdown report outside legacy scores. - Added an opt-in, fixture-based tool-use benchmark with strict argument scoring, explicit unsupported coverage, and separate tool-use reports.
- Surface composite weights in Markdown report header (Phase 12, item 12.6). Render active composite scoring weights (
coding={w_code:.2f}, reasoning={w_reas:.2f}, performance={w_perf:.2f}) in the metadata header ofresults/LATEST_SUMMARY.md. Scorecards generated byBenchmarkRunnernow attachcomposite_weights, andgenerate_markdown_reportresolves weights from config, scorecard, or default fallback. - Pin code extraction dedent behavior with regression suite (Phase 12, item 12.8). Added
ExtractPythonCodeDedentTestsintests/test_sandbox.pypinningextract_python_codebehavior: single markdown fences nested under bullet-list margins are dedented to preventIndentationError, multi-fence continuations preserve relative indentation, and zero-fence plain text falls back cleanly. - Numeric-equivalence layer for reasoning ground truth (Phase 12, item 12.3).
evaluate_reasoning_answeraccepts an opt-inevaluatorkeyword:"numeric"isolates the value after aFinal answer:marker (if present) and compares both sides exactly asFraction("42","42.0"," 42 "all match"42");"numeric_text"adds English text-to-number on both sides before the same comparison ("forty-two"matches"42"). The newevaluatorfield onbenchrig/data/scenarios/reasoning.jsonscenarios is wired throughBenchmarkRunner._evaluate_answer; an unknown value emits anevaluator.unknownstructured-log event withscenario_idandevaluator_valuefields and falls back to today's path. - Executable-test evidence and disaggregated coding metrics (Phase 14, item 14.5). Disaggregated
coding suite performance into task-level success (
coding_tasks_passed / coding_task_count,coding_task_pass_rate) and assertion-level success (coding_assertions_passed / coding_assertion_count,coding_pass_rate). A task is only marked passed if 100% of unit test assertions pass. Run records retain per-task evidence (task_id,passed,passed_tests,total_tests,pass_ratio). Markdown reports display both tasks passed and assertion pass rates, and CSV exports include all disaggregated columns (coding_task_pass_rate,coding_tasks_passed,coding_task_count,coding_assertions_passed,coding_assertion_count). Documented indocs/benchmark-suites.md. - Warm-up iterations and KV prefix cache reuse protocol (Phase 14, item 14.4). Added
--warmup-runs N(default 1) CLI flag to run warm-up iterations per scenario before measurement. Warm-up runs are tagged withphase="warmup"and strictly excluded from benchmark summary averages (avg_eval_tok_sec,avg_ttft_sec,composite_score). Stamped records withcache_mode("cold","warm","prefix_cached", or"unverified"), requiring verified backend telemetry for prefix cache hit attribution. Documented warm-up protocol indocs/tutorials/cross-engine-benchmarking.md. - Peak host RSS sampling and fixed-context attribution (Phase 14, item 14.3). Sample host
Resident Set Size (RSS in MB) during inference via
HardwareSamplerwith explicit process coverage qualification (rss_coverage:"client_only"vs"client_and_server"). Run records and scorecards tag memory measurements with fixed-context workload parameters (context_tokens,prompt_tokens,max_output_tokens). Appendedpeak_rss_mb,rss_coverage, andcontext_tokenstoSCORECARD_CSV_COLUMNS. Documented host RSS mechanics indocs/hardware-telemetry.md. - Timing breakdown and provenance reporting (Phase 14, item 14.2). Report TTFT, prefill duration,
and decode duration as separate metrics with explicit provenance tags (
prefill_provenance:"engine"|"client_ttft"|"unavailable"). TTFT is client wall time to first token and is never treated as engine prefill duration. Ollama records engine prefill duration (prompt_eval_duration), while runtimes without engine prefill telemetry setprefill_duration_sec=Nonewith provenance"unavailable"and report effective client prefill speed (prefill_eff_tok_sec = prompt_eval_count / ttft_sec). Appendedprefill_duration_sec,decode_duration_sec, andprefill_provenancetoSCORECARD_CSV_COLUMNSand propagated to scorecards and run records. -
Execution placement and CPU fallback reporting (Phase 14, item 14.1). Distinguish
requested_devicefromobserved_deviceacross Ollama, Foundry Local, ONNX direct, and Prism. When observed placement is not verifiable by the backend, it is recorded as"unknown"(never assumed to run on GPU). When a GPU device was requested but execution observed on CPU,cpu_fallback=Trueis set on per-scenario records and aggregated scorecards. Leaderboards and reports annotate CPU fallbacks (⚠️ CPU Fallback) and unknown devices (❓ Unknown Device) rather than assuming 100% VRAM offload. Appendedrequested_device,observed_device, andcpu_fallbackcolumns toSCORECARD_CSV_COLUMNS. -
eval_tok_sec_flooredboolean flag (Phase 10):FoundryClient/PrismClientset it on every response whose raw eval window is under 1.5 ms (the 0.001 s measurement floor engaged for at least one token; Ollama reports its own duration and is never flagged).runner.pycopies it onto each record and sets the scorecard'seval_tok_sec_floored(true if any record is floored). - Markdown: leaderboard row renders the speed cell with a tilde prefix (
~3000.0 t/s) so operators can distinguish a real 3 000 t/s measurement from a floor-driven ceiling. -
CSV export: new
eval_tok_sec_flooredboolean column appended toSCORECARD_CSV_COLUMNSso downstream pipelines can filter out floor-driven speeds. -
Structured logging on stderr via the new
benchrig.core.loggingmodule (JsonFormatter+RunIdFilter+setup_logging(level, run_id)). One JSON record per log call on stderr, configurable via the upcoming--log-levelflag andBENCHRIG_LOG_LEVELenv var (Phase 11, item 11.1). The currentrich.consoleUX on stdout is unchanged. - New
--log-level {DEBUG,INFO,WARNING,ERROR}CLI flag andBENCHRIG_LOG_LEVELenv var (Phase 11, item 11.2). DefaultWARNINGkeeps stderr silent; setINFOfor one JSON line per HTTP request / retry / capacity wait. A per-invocationrun_id(UUID4) is threaded through every record so an operator can filter a single run out of a noisy shared log.run.started/run.completedevents book-end every benchmark invocation. - New
docs/cli.md§5 "Logging" section documenting the JSON record shape, event table, andjqfiltering example. FoundryClient._make_requestnow emits HTTP lifecycle events (http.request_started/http.response_completed/http.request_failed) around everyrequests.post. The hook is the onePrismClientinherits for its 503-retry path, so this covers every Prism/Foundry HTTP call. Phase 11, item 11.3._post_with_503_retrynow emitsretry.attempted(INFO per retry) andretry.exhausted(WARNING when the budget runs out) withattempt,delay_sec, andreasonfrom the JSON body. The previous_log.infotext-format strings are replaced with structured events; the Python exception path (PrismBusyError) is unchanged. Phase 11, item 11.4.BenchmarkRunner._capacity_exhausted_reasonnow emitscapacity.exhausted(WARNING) when a persistent 503 withinsufficient_resourcesshort-circuits the remaining scenarios. The rich UX_notifyline is unchanged. Phase 11, item 11.5.PrismClient.unload_modelnow emitsunload.completed(DEBUG withok=true) on a successfulPOST /v1/unload, and the same event withok=false,error=…on transport failure or non-2xx. Best-effort contract (always returnsTrue) preserved. Phase 11, item 11.6.OllamaClientnow goes through the same_make_requestinstrumentation hook (http.request_started/http.response_completed/http.request_failedevents) on its generate/unload paths. Phase 11 follow-up — the hook was scoped toFoundryClient/PrismClientin 11.3;OllamaClienthad directrequests.postcalls outside the hook. The shared_logged_requesthelper refactor keeps both paths identical. Existing tests intests/test_ollama_think.pyhad theirtime.perf_counterfixtures widened by 2 positions to account for the new HTTP hook reads.OllamaClient.is_reachable,get_version,list_installed_models,get_running_models,supports_thinking(POST/api/show),pull_model(POST/api/pull) all flow through the instrumented hook. The new_logged_gethelper plus_make_getmethod mirror the POST helper so GET requests carrymethod=GETin the JSON record. Phase 11 follow-up completion.
Changed¶
Fixed¶
[0.2.0] - 2026-09-23¶
Added¶
- CSV export of scorecards (
benchrig.reporting.csv_export.write_scorecards_csv) with a fixed column order (SCORECARD_CSV_COLUMNS). Wired to the upcoming--csv <path>CLI flag (Phase 1, item 1.1). - PNG chart export of scorecards (
benchrig.reporting.charts.write_scorecards_chart) with one bar per scorecard (bar height =composite_score, label =<model> (<runtime>)). Wired to the upcoming--chart <path>CLI flag (Phase 1, item 1.2).matplotlibis imported lazily inside the function so the CLI still loads when the[charts]extra is not installed (spec.md I3). - Markdown report (
benchrig.reporting.markdown.generate_markdown_report) embeds the chart asnear the top and links the CSV as**Attachments:** [CSV](csv_path)near the bottom, but only when those files were produced this run (Phase 1, item 1.3; spec.md I2). - Warning when a benchmark target uses the
generic-cpuexecution provider on a CUDA host (benchrig.core.runtimes.warning_for_provider, wired intobenchrig.cli._warn_slow_provider). Printed once per run before any model is benchmarked; the run continues and exits 0 (Phase 2, item 2.1; spec.md I4). - New scorecard field
vram_baseline_dirty_mbrecords the foreign-process GPU memory at the baseline (MiB, rounded;Noneon macOS / generic CPU;0.0when no foreign process holds the GPU). Surfaced viaBaseHardwareProvider.read_gpu_foreign_memory_mb(own_pids)(NVIDIA: nvidia-smi--query-compute-apps=pid,used_memory) andBenchmarkRunner.measure_vram_baseline(Phase 2, item 2.2). - Regression test pinning that
PrismClient.generatereads the device fromtelemetry.deviceand ignoresexported_for(Phase 4, item 4.2; spec.md I5). - Documentation: new "Tuning the Prism server with environment variables" section in
docs/runtimes.mdlistsPRISM_PREFILL_CHUNK(default1024),PRISM_THREADS(default unset), andPRISM_DEVICE(defaultauto) with their trade-offs (Phase 4, item 4.3). No CLI / code change: the env vars are read by prism-local itself. PrismClientretries 503 with the server-providedRetry-After(capped at 60 s, max 3 retries) via the newbenchrig.core.client._post_with_503_retryhelper. After the budget runs out the result is a clear failure carrying the JSON reason (e.g.server_busy,insufficient_resources); other 5xx are NOT retried. Wired throughFoundryClient._make_requestwith aPrismClient._make_requestoverride. NewPrismBusyErrorexception is raised by the helper itself (Phase 4, item 4.5; spec.md I6).- Live integration test
tests/test_prism_runtime_live.pyexercises the benchrigPrismClient.generatepath end-to-end against a running prism-local 0.2.0+ server (prism serve --port 5272); skipped automatically when the server is not reachable. Verified: items 4.1 (usage + telemetry in last chunk,usage_estimated=False), 4.2 (devicefrom telemetry, notexported_for), and 4.5 (concurrent load retries against the real load-lock). PrismClient.unload_modelnow callsPOST /v1/unloadon the Prism server to actually free the resident ONNX model's VRAM/RAM between models, instead of the inherited Foundry-CLI no-op it used before (Phase 5, item 5.1; spec.md I7). Needs a prism-local build newer thanv0.2.0(~/03-foundy-localcommit6d6467a); best-effort against an older server or a transport error (logged, never raised — never fails a run).FoundryClient.generate()(and soPrismClient,--runtime foundry) now forwardstop_k,repetition_penaltyandstopfrom scenariooptionsinto the/v1/chat/completionspayload when present; they join the existingtemperature/num_predict/top_p/seedwhitelist and were silently dropped before (Phase 6, item 6.1; spec.md I8). Needs a prism-local build withtop_k/repetition_penaltysupport (pastv0.2.0);stophas been accepted by prism-local since beforev0.2.0but benchrig never sent it.FoundryClient.generate()now readsreasoning_contentfrom the response (streaming delta or non-streaming message) and records its length asthinking_chars, matchingOllamaClient's existing field (Phase 6, item 6.2; spec.md I9). Needs a prism-local build with reasoning separation (pastv0.2.0).- Each scenario now prints its
PASS/FAILline as soon as it finishes, instead of the whole suite finishing silently and dumping every line at once (BenchmarkRunner.on_result, called immediately from_run_suite/run_context_suite);benchrig/cli.py::run_benchmarksalso shows an overallrich.progress.Progressbar across every model/run/scenario in the run (Phase 8; spec.md Phase 8). - A failing
codingscenario's result record now keepsextracted_codeand aresponse_excerpt(bounded to the first 400 characters, since a coding prompt asks for code first — unlike reasoning's tail-truncatedanswer_excerpt, where the final answer comes last), so a run that scores 0passed_testscan be diagnosed from the saved JSON alone instead of needing to reproduce it live against the same model (Phase 9, item 9.1; spec.md Phase 9; excerpt direction fixed from an initial tail-truncation during independent review). Passing scenarios are unaffected — the fields are only added on failure.
Changed¶
ttft_secforFoundryClient/PrismClientnow measures time to the first token of either kind (reasoning or content), not only the first answer token; when a response carriesreasoning_content, the first answer token's time is recorded separately asanswer_ttft_sec(Phase 6, item 6.2; spec.md I9). This makes reasoning-modelttft_secnumbers smaller and more accurate, but not directly comparable tottft_secrecorded by an earlier benchrig version for the same model. Responses with noreasoning_contentare unaffected.
Fixed¶
devextra now pulls incharts(dev = [..., "benchrig[charts]"]) sopip install -e ".[dev]"always installsmatplotlib;tests/test_report_charts.pyneeds it and was failing in CI (and any fresh dev setup) because thedevandchartsextras were independent.vram_baseline_dirty_mbis now forwarded from the per-recordhardwaredict into the scorecard root byBenchmarkRunner.compute_model_scorecard(benchrig/core/runner.py). Phase 2 item 2.2 had shipped the per-record field, but the aggregation step dropped it, so every scorecard serialised the field asnull— even on a Linux + NVIDIA host where the value was always0.0(verified by replay: every Sept 22 record carriedvram_baseline_dirty_mb: 0.0in itshardwaredict). New regression testtests/test_hardware_dirty_baseline.py::ScorecardDirtyMBForwardingTestsred-proven before the fix.tests/test_prism_runtime_live.pynow skips both live-test classes cleanly when free VRAM is belowMIN_FREE_VRAM_MB(8 GB), instead of failing confusingly withinsufficient_resources503s on a busy host. The threshold is the 7B model's footprint; the smaller 0.6B model needs ~3 GB but the load-lock race needs both models sized for the same GPU, so the same floor covers both. Skip predicates combine prism reachability (GET /v1/models) withnvidia-smi --query-gpu=memory.free. No code change inbenchrig/.--csv <path>and--chart <path>are now real CLI flags (benchrig/cli.py):save_outputswrites the scorecards CSV / PNG chart when requested and links/embeds them inLATEST_SUMMARY.md(relative to the report's directory). Phase 1 (items 1.1/1.2) had shipped the underlyingbenchrig.reporting.write_scorecards_csv/write_scorecards_chartfunctions and their tests, but never wired the flags or thebenchrig/reporting/__init__.pyexports into the CLI, so--csv/--chartraised "unrecognized arguments".- A model that fails with a persistent
insufficient_resourceserror (does not fit in available VRAM) now stops the rest of its scenarios immediately instead of retrying every remaining scenario in every remaining suite (BenchmarkRunner._capacity_exhausted_reason, Phase 4 item 4.6). A transientserver_busy(queue full) failure is unaffected and still retries per scenario as before. The CLI now also prints why a suite produced no results (⚠ Skipped — model does not fit in available VRAM: ...,benchrig/cli.py::_suite_skip_notice) instead of silently printing the suite header followed by nothing. --chartbar labels (<model> (<runtime>)) are now rotated 30° with right alignment instead of horizontal; with more than a couple of scorecards, or long model names, the horizontal labels ran into each other and became an unreadable strip (found manually from a 6-scorecard chart; Phase 7, item 7.1).- A scenario
optionsvalue that cannot be coerced (e.g.top_k: "many",repetition_penalty: "high") now fails that one scenario via the normal_failure_resultpath instead of raising an uncaughtValueError/TypeErrorout ofFoundryClient.generate()and crashing the whole--runs Nbenchmark (Phase 6 independent review finding, fixed 2026-09-22; spec.md I8). extract_python_codenow dedents a single matched```pythonblock (textwrap.dedent) before stripping it; a fence nested under a markdown list/bullet kept the list's leftover margin on some lines but not others, which could raiseIndentationErrorwhen the extracted code was spliced into the sandbox script (Phase 9, item 9.2; spec.md Phase 9). Deliberately skipped when there is more than one fenced block: a later block can be a continuation fragment (e.g. a method added to a class shown in an earlier fence) whose indentation is relative to that block, not markdown noise, and dedenting it on its own would strip the indentation that keeps it nested (independent review finding, fixed 2026-09-22). Hardening for a plausible failure mode, not a confirmed fix for any specific past run — see spec.md Phase 9 for why the root cause of the run that prompted this is still open.
[0.1.0] - 2026-09-20¶
First PyPI release.
Added¶
- Installable package
benchrig(importbenchrig) with abenchrigconsole script (alsopython -m benchrig). benchrig --version,--configand--scenarios-dir. The defaultconfig.yamland all scenario suites are bundled;./config.yaml,./scenariosor$BENCHRIG_CONFIGoverride them.- Direct ONNX models are looked up in
$BENCHRIG_MODEL_DIRS, then./models, then~/.benchrig/models. - Benchmarking across Ollama, Microsoft Foundry Local and direct ONNX Runtime GenAI (CUDA), with hardware telemetry, sandboxed coding tests, reasoning/speed/context/Polish suites and 1:1 cross-engine comparison reports.
-
CI (Python 3.10-3.13, wheel smoke test) and a trusted-publishing workflow for TestPyPI and PyPI.
-
Prism runtime (
--runtime prism,prism:prefix,prism:config section): benchmark a Prism server (prism serve) next to Ollama, Foundry Local and direct ONNX. SupportsPRISM_API_KEY,--check,--pull-recommended(viaprism pull), 1:1 pairs with aprism:model, and records the device Prism reports.--models installedskips Prism'sollama:proxies. Reports label the runtime as "Prism" and the cross-engine table covers every non-Ollama runtime. - Documentation site (MkDocs Material, deployed to GitHub Pages) with a new Runtimes page; docs files were
renamed to lowercase names (
docs/cli.md,docs/configuration.md, ...).
Changed (measurement methodology; results from earlier versions are not comparable)¶
- Context suite: on Ollama each step is preceded by an untimed request with the same
num_ctx, so the timed request no longer includes model reload (it was about 4 to 7 s per step and dominated TTFT). Prompts are sized from the window (fill_ratio, default 0.75) instead of a fixed multiplier, the real prompt size is recorded (prompt_tokens_actual), and a 1024 step is added. - Prefill: reports use one definition for every runtime (
prefill_eff_tok_per_sec, prompt tokens / time to first token). The engine-reported value stays asprompt_tok_per_sec, andprefill_sourcesays where it came from. - VRAM: GPU memory is read before each model is loaded; scorecards add
vram_baseline_mbandvram_model_mb(peak minus baseline).peak_vram_mbremains the whole-GPU peak. -
Thinking models:
benchmark.thinking_models/thinking_token_multiplierraisenum_predictfor models such asdeepseek-r1on the coding, reasoning and polish suites. Results and reports flag responses that stopped at the token limit (truncated,truncated_runs,finish_reason). -
Repetitions (
--runs N): repetitions after the first send their prompts with a short marker, because Ollama caches the prompt prefix and a repeated prompt measured 0.006 s of prefill instead of 0.12 s. Each repetition uses its own seed (execution_alignment_1to1.seed+ N), andexecution_alignment_1to1(temperature,top_p,seed,context_tokens) is now actually applied as default sampling on every runtime (it was documented but unused); its unused keys were removed. Scorecards report the min-max of the headline metrics over repetitions (runs,spread). - Reasoning suite: six new scenarios (two trains, knights and knaves, distinct-digit numbers, a recurrence, chained percentages,
letter counting), nine in total, with a new
final_answercheck type (the lastFinal answer:line must match in full). Their expected answers are recomputed by independent solvers intests/test_scenarios.py. - Ollama
think: thethinkingstream is read.ttft_secis now the first token of any kind (latency), withanswer_ttft_sec,think_time_secandthinking_charsalongside; a response that never left the thinking phase no longer reports 0.0 s.benchmark.thinksets Ollama'sthinkper suite (speed and context off by default) and is sent only to models Ollama lists as able to think.thinking_token_multiplierdefault 6 -> 12; it also applies withthink: false(deepseek-r1:14b without thinking still reached the base budget). - Context suite: each step hides a fact in the text and asks for it, so it scores whether the model uses the context
(
retrieved,context_retrieval_pct); wrong answers show as FAIL/"missed the fact" instead of ERROR, and failed requests keep theirerror. - After the context suite an untimed request puts the model back at the default context size. It used to leave Ollama at
num_ctx8192, so with--runs Nthe first request of the next repetition paid a model reload (TTFT about 0.8 s instead of 0.06 s). - Prism token counts: results say when the counts are estimates (
usage_estimated, with a note in the reports) instead of silently guessing; Prism now sends exactusageand telemetry in streamed responses (stream_options.include_usage). - Model memory of a runtime that cannot unload: when a model is still loaded at baseline time (Prism), the model-only figure is
-(vram_baseline_dirty) instead of a value that understates the model. - The 1:1 report also lists the min-max spread of repeated runs, and three easier reasoning scenarios (bat and ball, multiples of three or five, letters in one word) bring the suite to twelve.
- Decode speed is weighted by tokens (total tokens / total generation time); the plain mean is kept as
avg_eval_tok_sec_mean. - New figures:
cold_start_sec(first request),gpu_fit_pct(Ollama/api/ps, share of the model in GPU memory) andtokens_per_joule(GPU power), shown in a "Start-up, GPU fit & efficiency" table.
Fixed¶
exact_or_containsmatches numbers as whole numbers:11/60,21/60and1/60were accepted for1/6, and1114.60for114.6.- Reasoning:
regexanswers are matched against the normalized end of the answer (markdown, bullets and line breaks collapsed), so a multi-line or bulletedBox 1: ... Box 2: ...conclusion is accepted (it was rejected because.did not match newlines), while the bundled patterns use bounded gaps and cannot be satisfied by words scattered through the reasoning (an unbounded multi-line match accepted wrong answers). LaTeX answers such as\boxed{\dfrac{1}{6}}now match1/6. A response cut off inside<think>is no longer scored on its reasoning trace (it produced false passes). Results recordanswer_excerptandtruncated_thinking. - 1:1 comparison report: it compares the Ollama scorecard that matches the other model (by name and size tag) instead of the first one in the list, and it only uses that model's own records (it used to include every Ollama model's results, so another model's errors showed up in the scenario breakdown). Speed, context, reasoning and polish scenarios show OK/PASS/FAIL from their own fields instead of "FAIL (0/0)", cut-off answers are marked, and a Prism run is labelled "Prism", not "MS Foundry", in the title and in every "who is faster" cell.
- Coding: code inside
<think>traces is ignored, and a response cut off before its code is complete is reported as truncated instead ofname '...' is not defined. FoundryClientreports the engine that actually serves each model (fromowned_by, or theollama:prefix) instead of always "ONNX Runtime GenAI"; matters for multi-engine servers such as Prism.- With an explicit endpoint (
auto_detect_port: false)load_model/unload_modelno longer call thefoundryCLI (which talks to a different daemon);load_modelreports whether the server lists the model.
Changed¶
- The agent skills and MCP servers moved to local-coders.
core/andreporting/moved tobenchrig.coreandbenchrig.reporting;benchmark.pyis nowbenchrig.cli(runbenchrig ...instead ofpython3 benchmark.py ...).- The CUDA library bootstrap looks in the active environment's
site-packagesinstead of a.venvnext to the script. requirements*.txtreplaced bypip install -e ".[dev]".
SDLC unification — 2026-10-02¶
- Adopt the shared local-sdlc-kit runner and Codex workflow through AGENTS.md and .agents/skills, retaining project-specific checks in sdlc.toml.
- Document independent review, existing operator authorization and truthful reporting of blocked checks.