Skip to content

Add --duration flag to perf for time-budgeted benchmarking#1168

Open
xieofxie wants to merge 3 commits into
mainfrom
hualxie/perf_add_duration
Open

Add --duration flag to perf for time-budgeted benchmarking#1168
xieofxie wants to merge 3 commits into
mainfrom
hualxie/perf_add_duration

Conversation

@xieofxie

Copy link
Copy Markdown
Contributor

Summary

Adds a --duration <seconds> flag to winml perf. When set, the benchmark runs for a wall-clock time budget instead of a fixed --iterations count. After warmup, inference loops until the duration elapses (always running at least one benchmark iteration so stats are never empty).

This is ideal with --monitor, whose PDH hardware counters need time to emit real utilization data — a small fixed iteration count often finishes before the counters produce meaningful samples.

winml perf -m model.onnx --device npu --monitor --duration 15

Details

  • New --duration FLOAT option (must be > 0), plumbed through BenchmarkConfig.duration.
  • _benchmark_indices() helper centralizes the loop: warmup first, then either a fixed iteration count (default) or a timed loop. Used by the single-model, monitored, and per-module paths.
  • Mutually exclusive with --op-tracing (op-tracing runs its own fixed, small iteration count) — rejected with a clear UsageError.
  • Live --monitor chart shows elapsed/total time progress (Time: 12.3/30s) in duration mode instead of an iteration counter.
  • Accurate iteration reporting: in duration mode the reported iteration count reflects the actual number of timed samples rather than the unused --iterations default. For per-module runs (where each module runs a different count under a shared budget), the count is recorded per instance and the top-level value is null; both single-model and module reports also record duration_sec.
  • --duration is added to the genai "ignored flags" warning (genai benchmarks by token generation).

Tests

  • --duration help/forwarding/default, --op-tracing conflict, non-positive rejection.
  • _benchmark_indices iteration mode, timed budget, and at-least-one-iteration guarantee (deterministic via a faked clock).
  • LiveMonitorDisplay time-based vs iteration-based progress.
  • benchmark_info.iterations reports configured count without duration and actual sample count with duration.

All perf suites pass (test_perf_cli, test_perf_module, test_perf_composite, test_ep_monitor): 200 passed, 2 NPU-only skips; ruff clean.

Run the benchmark for a wall-clock budget (seconds) instead of a fixed
--iterations count. After warmup, inference loops until the duration
elapses (always at least one benchmark iteration). Ideal with --monitor,
whose PDH counters need time to emit real utilization data. Rejected with
--op-tracing, which runs its own fixed iteration count.

Works across the single-model, monitored, and per-module paths. The live
--monitor chart shows elapsed/total time progress in duration mode. The
reported iteration count reflects the actual timed sample count instead of
the unused --iterations default.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@xieofxie
xieofxie requested a review from a team as a code owner July 22, 2026 08:41
@xieofxie

Copy link
Copy Markdown
Contributor Author

Needed for #1149

@DingmaomaoBJTU DingmaomaoBJTU left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Took a pass through this — the overall approach looks good. The warmup→timed-loop split, the at-least-one-iteration guarantee, the --op-tracing conflict, and the genai ignored-flag warning are all correct, and the tests cover the important paths.

A few minor nits (none blocking):

perf.py L732-734logger.info("Running benchmark: %d iterations + %d warmup", ...) still logs the fixed iterations count in --duration mode, so it reads e.g. "Running benchmark: 100 iterations + 10 warmup" for a run that's actually time-bounded. Might be worth branching to log the time budget there.

Two more inline below.

Comment thread src/winml/modelkit/commands/perf.py Outdated
Comment thread src/winml/modelkit/commands/_live_chart.py Outdated
Address PR review nits:
- Log duration/warmup (not iterations) at benchmark start in duration mode.
- _run_simple_loop now logs elapsed/total seconds in duration mode instead
  of a meaningless iteration count over total_iterations.
- LiveMonitorDisplay reads the loop's shared _BenchmarkClock start instead of
  self-stamping on first update(), so the time progress bar tracks the exact
  budget the loop stops on rather than lagging by one inference.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@xieofxie

Copy link
Copy Markdown
Contributor Author

Thanks for the review @DingmaomaoBJTU. Addressed all three nits in 6b3f744:

  1. Info log — the "[3] Run benchmark" log now reports %gs duration + %d warmup in duration mode instead of the iteration count.
  2. _run_simple_loop debug progress — logs an elapsed/total-seconds readout in duration mode instead of the meaningless i/total_iterations count (resolved inline).
  3. Live chart clock — added a shared _BenchmarkClock stamped by _benchmark_indices at the warmup boundary; the display reads it instead of self-stamping, so the time bar tracks the exact budget the loop stops on (resolved inline).

Comment thread src/winml/modelkit/commands/perf.py Fixed
The duration-mode loop in _run_simple_loop does not use the yielded index,
so name it _ to satisfy CodeQL's unused-loop-variable check.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

@DingmaomaoBJTU DingmaomaoBJTU left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found one termination bug and one runtime-dispatch inconsistency in the new flag handling; details inline.

)
@click.option(
"--duration",
type=click.FloatRange(min=0, min_open=True),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Click FloatRange accepts non-finite values because comparisons such as nan <= 0 are false. That value reaches _benchmark_indices, where elapsed >= duration_sec is permanently false, so winml perf --duration nan never terminates; inf likewise creates an unbounded run. Please reject non-finite durations (for example with math.isfinite) and add a CLI regression test.

# --duration replaces the fixed iteration count with a wall-clock budget.
# Op-tracing runs its own fixed, small iteration count, so the two are
# mutually exclusive.
if duration is not None and op_tracing:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This validation runs before the winml-genai dispatch, but both --duration and --op-tracing are registered in _GENAI_IGNORED_FLAGS. Passing either alone warns and continues, while passing both now fails before the ignored-flag warning. Please move the conflict check to the WinML path (after the GenAI early return), or explicitly exclude the GenAI runtime, so ignored options remain consistently non-fatal.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants