DRAFT: feat(config): consolidate all durations and deadlines into one Timeouts model#409
DRAFT: feat(config): consolidate all durations and deadlines into one Timeouts model#409viraatc wants to merge 5 commits into
Conversation
…ts model One frozen pydantic model (config/timeouts.py, mounted at settings.timeouts) now owns every global time knob: min/max duration, per-phase drain deadlines, service-ready deadline, metrics drain budget, and the whole-run watchdog. - BenchmarkConfig.timeout was a silent no-op consumed nowhere; it is now settings.timeouts.run_timeout_s (--timeout alias preserved): a real whole-run watchdog that SIGTERMs the metrics aggregator (INTERRUPTED final snapshot), stops the session, and exits non-zero. A fired watchdog can never produce a COMPLETE report (locked by integration test). - DrainConfig deleted; drain fields moved to settings.timeouts with CLI aliases unchanged. metrics_drain_timeout_s normalized to None=unlimited (was 0=unlimited); the aggregator argv boundary still speaks 0. - metrics_tokenizer_workers is not a timeout: moved to settings.metrics (new MetricsConfig block). - min/max_duration_ms (+ suffix parsing + cross-validator) moved from RuntimeConfig to Timeouts; RuntimeSettings resolves them to plain values at setup, so nothing in the hot path reads pydantic. - ServiceLauncher.terminate_all() added (graceful SIGTERM counterpart to kill_all) for the watchdog path. - Templates regenerated; examples/docs/tests migrated (YAML keys moved, no back-compat shims per repo convention). Example run_timeout_s values were dropped rather than carried over: the old top-level timeout was inert, and enforcing stale values would abort valid runs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
MLCommons CLA bot All contributors have signed the MLCommons CLA ✍️ ✅ |
There was a problem hiding this comment.
Code Review
This pull request refactors the configuration schema by centralizing all global durations, deadlines, and timeouts into a new frozen Pydantic model Timeouts (accessible via settings.timeouts). This separates workload durations from failure-handling deadlines. Additionally, a whole-run watchdog (run_timeout_s) has been introduced to gracefully abort stuck runs, signaling managed subprocesses via SIGTERM to write an interrupted final snapshot before exiting non-zero. All configuration templates, examples, and tests have been updated to align with this new schema, and new integration tests have been added to verify the watchdog behavior. No review comments were provided, so there is no feedback to address.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
…on semantics - performance_drain_timeout_s default 240.0 -> None (wait indefinitely), matching the accuracy drain default. - min/max_duration_ms help/description now state they bound the whole performance phase (single phase; not per-dataset) and never bound warmup/accuracy. Templates regenerated. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
schema.py (1415 lines) now holds only BenchmarkConfig/EndpointConfig and re-exports the full schema surface; models move to sibling modules: enums.py, audit.py, model_params.py, datasets.py, settings.py (timeouts.py already existed). Import sites are unchanged — config.schema remains the single import point. Dead code deleted: SystemDefaults (DEFAULT_TIMEOUT had no consumers; DEFAULT_METRIC inlined into rulesets/mlcommons/rules.py) and the unused TEMPLATE_TYPE_MAP. regenerate-templates pre-commit hook now watches the new modules. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Findings from the review council (Codex review + adversary council), all verified against the code before fixing: - Watchdog now stays armed through the unbounded metrics drain: it was cancelled right after session.run, so run_timeout_s could not bound a stuck aggregator drain (wait_for_exit(None)). Cancelled after services exit instead. - Watchdog SIGTERMs only the metrics aggregator (ServiceLauncher.terminate with module suffix, replacing terminate_all): SIGTERMing the event logger dropped its buffered events.jsonl tail; the logger flushes on the ENDED event, which session.stop() still delivers. - A timed-out run skips accuracy scoring in finalize: phases that never started KeyError in scorer init and partial phases would yield misleading subset scores. Artifacts are still salvaged. - Teardown race no longer skips finalization: if session.run raises after the watchdog fired, fall through with an empty SessionResult so result_summary.json (INTERRUPTED, complete=false) is always written; run_benchmark raises the timeout ExecutionError after finalize. - run_audit maps a watchdog fire to ExecutionError naming the timeout instead of the Ctrl-C KeyboardInterrupt path (exit 130). - MetricsConfig gets cyclopts.Parameter(name='*') matching sibling settings blocks (flat --tokenizer-workers + --metrics-tokenizer-workers). - Stale drain-key name fixed in session.py docstring. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
| # (launched once from top-level model_params), so a per-dataset override would | ||
| # desync ISL/OSL/TTFT/TPOT accounting without changing what is measured. Rejected | ||
| # as generation_config_override keys — they are per-run/identity, not per-dataset. | ||
| _METRICS_DECOUPLED_OVERRIDE_KEYS = frozenset({"name", "streaming", "tokenizer_name"}) |
…entinels min_duration_ms and n_samples_to_issue are now mutually exclusive (Settings validator): the sample count is either an explicit count or derived from QPS x duration — previously an explicit count silently won and the configured duration was dead weight. - min_duration_ms: int | None = None. Omitting both knobs issues the dataset once. BEHAVIOR CHANGE: a bare config (neither set) previously derived qps x 600s worth of samples; it now runs the dataset once. The 0 = 'all dataset samples' sentinel is gone (0 now rejected). - max_duration_ms: int | None = None, replacing the 0 = 'no limit' sentinel; the argv-boundary 0->None conversion in RuntimeSettings is deleted (internal RuntimeSettings stays permissive for programmatic callers passing 0). - Examples/docs/templates migrated: dropped min_duration where an explicit count is set (it was silently ignored), dropped --duration 0 usage, fixed compare_with_vllm.py double-cap (it set max_duration_ms from the same value it passed as --timeout, capping one budget via two mechanisms), corrected CLI_QUICK_REFERENCE/LOCAL_TESTING stale defaults and key locations. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
What does this PR do?
Consolidates every global time knob into one frozen pydantic model —
Timeoutsin the newsrc/inference_endpoint/config/timeouts.py, mounted atsettings.timeouts— gives the previously dead--timeoutflag real semantics, splits the 1400-lineconfig/schema.pyinto focused modules, and makes the sample-count knobs mutually exclusive.The headline bug this fixes
BenchmarkConfig.timeout(--timeout, top-leveltimeout:YAML key) was consumed nowhere — a silent no-op. Several example YAMLs set it believing it bounded the run. It is nowsettings.timeouts.run_timeout_s: a real whole-run watchdog. When it fires, it SIGTERMs the metrics aggregator (whose handler writes an INTERRUPTEDfinal_snapshot.json), stops the session, andrun_benchmarkraisesExecutionError(non-zero exit) after finalization — a fired watchdog can never yield a COMPLETE report and always leavesresult_summary.jsonwithcomplete: false. Locked bytests/integration/commands/test_run_timeout.py.The one model
All CLI flag aliases are unchanged. Only YAML key locations and auto-generated dotted flags moved.
Sample count: explicit XOR duration-derived (new)
runtime.n_samples_to_issue(--num-samples) andtimeouts.min_duration_ms(--duration) are now mutually exclusive (Settings validator). Previously an explicit count silently won and the configured duration was dead weight — several examples carried both. Omitting both issues the dataset once, which also deletes the oldmin_duration_ms: 0= "all samples" andmax_duration_ms: 0= "no limit" sentinels (0 is now rejected loudly).qps × 600sworth of samples; it now runs the dataset once.performance_drain_timeout_sdefault is now None (unbounded). A default run has no time backstop on a wedged endpoint — setrun_timeout_sfor unattended runs.metrics_drain_timeout_s: 0/min_duration_ms: 0/max_duration_ms: 0now error loudly.run_timeout_svalues were dropped, not carried over (old top-leveltimeout:was inert; enforcing stale values would hard-kill valid runs). Also fixedcompare_with_vllm.py, which capped the same budget twice viamax_duration_msand--timeout.schema.py split
config/schema.pynow owns onlyBenchmarkConfig/EndpointConfigand re-exports the full schema surface (import sites unchanged). Models moved toconfig/enums.py,audit.py,model_params.py,datasets.py,settings.py(+ existingtimeouts.py). Dead code deleted:SystemDefaults(DEFAULT_TIMEOUThad zero consumers),TEMPLATE_TYPE_MAP.Design invariants (multi-model design review: Codex/Opus/Grok/Gemini)
run_timeout_snever derives per-stage deadlines (no remaining-budget propagation).min/max_duration_msbound the whole performance phase (single phase; not per-dataset);max_duration_msremains the sole perf ceiling.turn_timeout_sstays dataset-scoped; worker lifecycle timeouts stay onsettings.client.*.Multi-AI review hardening
A Codex review + adversary-council pass found and fixed: watchdog now covers the unbounded metrics drain; watchdog SIGTERMs only the aggregator (event logger kept its buffered
events.jsonl); timed-out runs skip accuracy scoring (artifacts still salvaged); teardown race can no longer skipresult_summary.json;run_auditmaps a watchdog fire toExecutionErrorinstead of the Ctrl-C exit-130 path.Type of change
Testing
tests/unit: 1522 passed, 5 skipped; integration command suites green (incl. fulltest_cli.pye2e)--helpverified aliases unchangedChecklist
🤖 Generated with Claude Code