You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Measure model loading memory and prepare GPU baseline counters (#1429)
Model construction can allocate memory before the existing perf
baseline, while missing GPU process counters leave the pre-model delta
unavailable. This ports the loading-memory and GPU-baseline changes from
gim-home/winml-cli-mirror PRs #10 and #11 onto current main.
- Add versioned load-only memory measurements from runtime/device
readiness through model compilation, excluding benchmark inputs and
inference. Keep sampled process peaks and OS lifetime high-water
diagnostics separate. Preserve null counters and signed deltas.
- Prepare absent counters with a model-free D3D12 device on the
explicitly selected GPU, retain it through final sampling, and release
it on success or failure. Connect preparation to the new process tracker
used by the combined port.
- Keep automatic Runtime outputs for older projections and explicit
output requests for newer ones. Include the initializer-shape regression
test; its implementation fix is already upstream.
Compatibility: the outer report remains schema version 2 and
`load_memory.version` is 1. Classic `memory_measurement.schema_version`
is 3 because its structure and legacy-named baseline/delta boundaries
change; documentation describes migration and limitations. Existing
upstream CGIR changes are retained.
Validation on Windows / Python 3.11:
- Affected perf CLI/GenAI/lifecycle, all session unit tests, and
initializer-shape regression: **1,449 passed, 6 existing skips**.
- Final focused memory/GPU integration rerun: **34 passed**; Runtime
compatibility suite: **40 passed**.
- Ruff across `src/` and `tests/`, changed-file formatting, license
headers, and `git diff --check` passed.
- Mypy: **no issues in 464 source files**.
Review follow-up: distinguish configured polling delay from observed
mean/max sampling intervals, including GPU-query overhead. Add native
factory/adapter/device failure-injection tests and idempotent COM
cleanup checks. The updated memory/GPU suite passes all 40 tests.
A fresh-process native smoke test on Qualcomm Adreno X1-85 successfully
created the baseline device on the selected LUID, discovered valid PDH
counters, recorded final observations, and released the retained device.
This validates baseline preparation/cleanup; full model hardware
benchmarks from the source PRs were not rerun.
---------
Co-authored-by: Qiong Wu <120818070+qiowu_microsoft@users.noreply.github.com>
@@ -53,6 +53,81 @@ Both runtime reports include `schema_version: 2` and a `benchmark_info.runtime`
53
53
54
54
When `--memory` is enabled, both `winml-ort` and `ort-genai` reports use the same `memory` field names for shared concepts: RSS baseline, after-compile/load, after-inference, peak, model-load delta, inference/generation delta, and total delta; VRAM local/shared baseline, after-compile/load, after-inference, peak, model-load delta, inference/generation delta, and total delta.
55
55
56
+
### Classic memory lifecycle
57
+
58
+
#### Model loading from a user's perspective
59
+
60
+
`load_memory` is the load-only view (block `version: 1`). It answers how much
61
+
additional memory was observed while this artifact became ready in this
62
+
runtime, and how much remained at readiness. Runtime/EP setup precedes its
63
+
baseline; its endpoint is after session compilation but before allocating
64
+
benchmark inputs, warmup or inference. If the selected path requires online
65
+
compilation, that temporary cost is included because the user must get through
66
+
it to load the model. A precompiled artifact is a different configuration.
67
+
68
+
Each RSS/local/shared block records baseline, absolute peak, absolute ready
69
+
value, `peak_extra_mb` and `ready_extra_mb`, plus the peak method and availability.
70
+
RSS uses the Windows high-water when a **new** process high-water occurs inside
71
+
the load window; otherwise only observed load samples/endpoints can be used.
72
+
GPU peaks are sampled. No inference peak or larger pre-existing process peak
73
+
is substituted into this view. Unknown baselines produce null increments.
74
+
75
+
This is an **observed load footprint**, not a certified minimum device-memory
76
+
capacity: RSS is resident memory, allocator caches can remain, sampling may
77
+
miss short GPU allocations, and unified RAM/GPU counters may overlap. Report
78
+
the load peak and ready increment separately; do not reduce this to parameter
79
+
file size or sum local/shared GPU counters. Already-loaded composite children
80
+
cannot provide this window and return an explicit unavailable status.
81
+
82
+
The enclosing perf document still uses schema version 2; the optional
83
+
`load_memory` block is explicitly versioned. Consumers must support nullable
84
+
memory values and use the recorded scope rather than comparing older deltas
85
+
as if they covered the same interval.
86
+
87
+
For `winml-ort` and `winml-runtime`, RSS baseline is captured **before model
88
+
loading**, including eager session construction and Runtime pipeline creation.
89
+
GPU baseline is captured after resolving the bound EP/adapter but before
90
+
constructing the model. Adapter discovery must not read lazy model properties.
91
+
The added `*_after_load_mb` checkpoint separates loading from the explicit
92
+
compile step. Input generation follows compilation/readiness. `*_after_compile_mb` and `*_after_inference_mb`
93
+
retain their existing names.
94
+
95
+
`memory_measurement` records PID, adapter LUID, lifecycle scope, units and
96
+
per-checkpoint missing reasons. Fields with the legacy `_mb` suffix use MiB
97
+
(bytes / 1,048,576). Deltas are signed process changes, not model-only allocation;
98
+
negative values can reflect released buffers or working-set changes. Library
99
+
imports, build caches and input preparation can contribute to the measured span.
100
+
101
+
`process_memory` separately records sampled RSS/local/shared peaks, actual
102
+
sample counts, interval and duration over loading, compilation and inference.
103
+
These are sampled peaks, not an exact continuous maximum. The configured
104
+
polling delay is not a sampling frequency: `configured_poll_delay_sec` records
105
+
the wait after each observation. `observed_mean_interval_sec` and
106
+
`observed_max_interval_sec` record elapsed time between completed observations
107
+
(null with fewer than two observations). GPU discovery/retries also delay RSS
108
+
sampling; these intervals include that overhead and scheduling delays. The legacy
109
+
`*_checkpoint_peak_mb` remains the maximum of the three original checkpoints,
110
+
and is `null` if any required checkpoint is unavailable.
111
+
112
+
On Windows, `process_memory.os_rss_peak_before_mb` and
113
+
`os_rss_peak_after_mb` also record the OS working-set high-water marks. These
114
+
catch short allocations missed by polling, including native code holding the
115
+
Python GIL. They cover the **process lifetime**, cannot be reset at baseline,
116
+
and do not replace the benchmark-scoped sampled peak. On unsupported systems
117
+
they are `null` with an explicit reason.
118
+
119
+
Missing GPU counters produce `null`, not zero; deltas with missing endpoints
120
+
also remain `null`. A GPU process-memory instance may not exist before model
121
+
allocation, so valid later absolute readings do not establish a zero baseline.
122
+
Local/shared counters can overlap on unified-memory devices and must not be
123
+
summed. GenAI's shared GPU-memory consumer also preserves unavailable values.
124
+
125
+
Composite components are already loaded when measured. Their explicit scope
126
+
is `already_loaded_component_through_inference`; their values include the
127
+
shared process and do not claim an independent component-load footprint.
128
+
`--no-memory` disables this collector. Background sampling adds overhead and
129
+
is separate from the native inference timer.
130
+
56
131
With `--runtime ort-genai`, `winml perf` benchmarks the onnxruntime-genai decoder pipeline rather than a single `session.run()`. The JSON report uses a phase-based schema: `load` contains startup spans, `requests` contains one warmup or timed generation sample per request, `aggregate` summarizes timed requests only, `memory` contains optional RAM/VRAM deltas, and `hw_monitor` contains optional monitor output. The optional `memory` and `hw_monitor` top-level names match the classic `winml-ort` perf report; GenAI keeps `load`/`requests`/`aggregate` instead of classic `latency_ms`/`throughput` because generation has distinct prompt, first-token, and decode phases.
57
132
58
133
For model-ID auto-builds, the selected EP/device must be supported by the model's
@@ -79,34 +154,19 @@ target validation.
79
154
80
155
### Memory measurement contract
81
156
82
-
With --memory, the legacy baseline stays **after the model factory, before
83
-
input generation and explicit session.compile()**, as on main. Existing
84
-
baseline/load/inference/total delta fields and the maximum-of-three checkpoint
85
-
peak keep that boundary. Eager model loading before this baseline is excluded.
86
-
87
-
Single-model runs also take an earlier, separately named before_model_load
88
-
snapshot after device resolution and before the model factory. For each of
89
-
rss, vram_local and vram_shared, additive fields are:
0 commit comments