Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 10 additions & 10 deletions src/winml/modelkit/analyze/runtime_checker/ep_checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
import onnx
import onnxruntime as ort

from ...utils.native_stderr import suppress_native_warnings


if TYPE_CHECKING:
from collections.abc import Sequence
Expand All @@ -31,8 +33,7 @@
def build_skip_check_result_for_rules_all_nodes_compile_run_pass(
self,
onnx_model: onnx.ModelProto,
) -> dict[str, Any] | None:
...
) -> dict[str, Any] | None: ...

Check notice

Code scanning / CodeQL

Statement has no effect Note

This statement has no effect.


class EPChecker:
Expand All @@ -46,9 +47,7 @@
# EP/device combinations that are known to leak resources/state across many
# sequential checks inside a single worker process. Running each case in an
# isolated process avoids "first case passes, later cases fail" behavior.
EPS_REQUIRING_CASE_ISOLATION_BY_DEVICE: ClassVar[
dict[str, set[ort.OrtHardwareDeviceType]]
] = {
EPS_REQUIRING_CASE_ISOLATION_BY_DEVICE: ClassVar[dict[str, set[ort.OrtHardwareDeviceType]]] = {
"OpenVINOExecutionProvider": {ort.OrtHardwareDeviceType.NPU},
}

Expand Down Expand Up @@ -176,11 +175,12 @@
input_args: dict[str, Any],
) -> dict[str, Any]:
"""Test model execution with execution provider."""
session = ort.InferenceSession(
path_or_bytes,
self._get_sess_options(),
provider_options=self._provider_options,
)
with suppress_native_warnings():
session = ort.InferenceSession(
path_or_bytes,
self._get_sess_options(),
provider_options=self._provider_options,
)
# inputs = self._generate_inputs(session)
graph_input_names = {inp.name for inp in session.get_inputs()}
inputs = {k: v for k, v in input_args.items() if k in graph_input_names}
Expand Down
2 changes: 1 addition & 1 deletion src/winml/modelkit/commands/_ep_arg.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

import click

from ..session import VALID_SOURCE_TAGS
from ..ep_path import VALID_SOURCE_TAGS


def split_ep_at_source(value: str) -> tuple[str, str | None]:
Expand Down
54 changes: 47 additions & 7 deletions src/winml/modelkit/commands/_live_chart.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@

# Display refresh rate (frames per second)
_REFRESH_FPS = 5
_PANEL_HORIZONTAL_OVERHEAD = 4
_PLOTEXT_HORIZONTAL_OVERHEAD = 21
_MIN_CHART_WIDTH = 20


class _OmittedDeviceKind:
Expand Down Expand Up @@ -97,9 +100,43 @@ def __init__(
self._chart_height = chart_height
self._poll_interval_s = poll_interval_ms / 1000.0
self._live: Any = None
self._console: Console | None = None
# Track the last rendered panel for transient=False final display
self._last_panel: Any = None

def _panel_content_width(self) -> int | None:
"""Return the Rich panel content width available in the current console."""
width = getattr(self._console, "width", None)
if not isinstance(width, int) or width <= _PANEL_HORIZONTAL_OVERHEAD:
return None
return width - _PANEL_HORIZONTAL_OVERHEAD

def _effective_chart_width(self) -> int:
"""Clamp plotext's canvas width so its full output fits in the panel."""
content_width = self._panel_content_width()
if content_width is None:
return self._chart_width
available_chart_width = content_width - _PLOTEXT_HORIZONTAL_OVERHEAD
return min(self._chart_width, max(_MIN_CHART_WIDTH, available_chart_width))

def _pack_status_cells(self, cells: list[str]) -> list[str]:
"""Pack status cells into as few panel-safe lines as possible."""
if not cells:
return []
max_width = self._panel_content_width()
separator = " | "
lines: list[str] = []
current = f" {cells[0]}"
for cell in cells[1:]:
candidate = f"{current}{separator}{cell}"
if max_width is not None and len(candidate) > max_width:
lines.append(current)
current = f" {cell}"
else:
current = candidate
lines.append(current)
return lines

def _resolved_device_label(self) -> str:
"""Return the display label for the requested or resolved device."""
return self._adapter_label if self._show_adapter else self._device
Expand All @@ -125,9 +162,10 @@ def _aggregate_gpu_label(self) -> str:
def __enter__(self) -> LiveMonitorDisplay:
from rich.live import Live

self._console = Console(stderr=True)
self._live = Live(
refresh_per_second=_REFRESH_FPS,
console=Console(stderr=True),
console=self._console,
transient=False, # Keep last frame visible in scrollback
)
self._live.__enter__()
Expand Down Expand Up @@ -279,7 +317,7 @@ def _render_chart(
plt.xlim(x_min, x_max)
plt.xlabel("Time (s)")

plt.plotsize(self._chart_width, self._chart_height)
plt.plotsize(self._effective_chart_width(), self._chart_height)

from rich.console import Group
from rich.text import Text
Expand Down Expand Up @@ -358,7 +396,9 @@ def _render_status(

# Row 1: Progress
pct_cell = f"{bar} {pct:.0%}"
row1 = f" {pct_cell:<30}| {progress} | Device: {self._resolved_device_label()}"
row1_lines = self._pack_status_cells(
[pct_cell, progress, f"Device: {self._resolved_device_label()}"]
)

# Row 2: Compute (unified now/avg format across all three)
adapter_label_text = self._selected_adapter_label()
Expand All @@ -369,19 +409,19 @@ def _render_status(
row2_cells = [cpu_cell, gpu_cell]
if self._show_adapter:
row2_cells.insert(0, adapter_cell)
row2 = " " + "| ".join(f"{cell:<28}" for cell in row2_cells)
row2_lines = self._pack_status_cells(row2_cells)

# Row 3: Memory
ram_cell = f"Sys Mem: {ram_mb:.0f} MB"
mem_cell = f"Device Mem: {memory_local_mb:.0f}/{memory_shared_mb:.0f} MB (local/shared)"
row3 = f" {ram_cell:<24}| {mem_cell}"
row3_lines = self._pack_status_cells([ram_cell, mem_cell])

# Row 4: Inference
lat_cell = f"Latency: {latency_ms:.2f} ms"
thr_cell = f"Throughput: ~{throughput:.0f} smp/s"
row4 = f" {lat_cell:<24}| {thr_cell}"
row4_lines = self._pack_status_cells([lat_cell, thr_cell])

return f"{row1}\n{row2}\n{row3}\n{row4}"
return "\n".join([*row1_lines, *row2_lines, *row3_lines, *row4_lines])

def print_final_snapshot(
self,
Expand Down
24 changes: 16 additions & 8 deletions src/winml/modelkit/commands/perf.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@

from ..utils import cli as cli_utils
from ..utils.constants import ACCELERATOR_DEVICE_TYPES, EPName, EPNameOrAlias
from ..utils.logging import configure_logging
from ..utils.logging import configure_logging, suppress_huggingface_warning_logs
from ..utils.model_input import ModelInputKind, classify_model_input
from ..utils.native_stderr import suppress_native_warnings
from ._ep_arg import EpAtSourceParamType
from ._live_chart import LiveMonitorDisplay
from ._pre_bench import print_pre_bench_block


Expand Down Expand Up @@ -1966,6 +1966,8 @@ def _run_monitored_loop(
duration_sec: float | None = None,
) -> None:
"""Run the benchmark iteration loop with live hardware monitoring."""
from ._live_chart import LiveMonitorDisplay

# In duration mode the display and the loop share one benchmark-phase clock
# so the progress bar tracks the same budget the loop stops on.
clock = _BenchmarkClock() if duration_sec is not None else None
Expand Down Expand Up @@ -2658,14 +2660,20 @@ def perf(
if not model:
raise click.UsageError("A model is required via -m/--model.")

# Merge top-level -v/-q with subcommand-level flags before any model
# resolution that can touch Hugging Face Hub and emit warning records.
verbose, quiet = cli_utils.resolve_verbosity(ctx, verbose, quiet)
configure_logging(verbosity=verbose, quiet=quiet)

# Hub-hosted ONNX (e.g. ``onnx-community/sam3-tracker-ONNX/onnx/...``)
# is downloaded once and treated as a local .onnx path thereafter.
# Must run BEFORE the ``Path(hf_model).suffix == ".onnx"`` check below
# so a Hub ref is not mistaken for a missing local file.
# ``normalize_model_arg`` returns ``str | None`` per its signature;
# the ``or model`` keeps the narrowed ``str`` type for downstream use.
try:
hf_model: str = cli_utils.normalize_model_arg(model) or model
with suppress_huggingface_warning_logs(verbosity=verbose, quiet=quiet):
hf_model: str = cli_utils.normalize_model_arg(model) or model
except Exception as e:
raise click.ClickException(f"Failed to resolve Hub-hosted ONNX path {model!r}: {e}") from e
model = hf_model
Expand Down Expand Up @@ -2713,10 +2721,6 @@ def perf(
elif "execution_provider" in cc:
ep = (cc["execution_provider"], None)

# Merge top-level -v/-q with subcommand-level flags so either position works.
verbose, quiet = cli_utils.resolve_verbosity(ctx, verbose, quiet)
configure_logging(verbosity=verbose, quiet=quiet)

# Runtime EP provider options (e.g. QNN htp_performance_mode) forwarded to
# the inference session for both HF model IDs and ONNX file inputs.
ep_provider_options = cli_utils.parse_ep_options(ep_options)
Expand Down Expand Up @@ -3038,7 +3042,11 @@ def perf(
console.print(f"[dim]Loading model:[/dim] {hf_model}")

benchmark = PerfBenchmark(config)
result = benchmark.run()
with (
suppress_huggingface_warning_logs(verbosity=verbose, quiet=quiet),
suppress_native_warnings(),
):
result = benchmark.run()

# Composite models (e.g. CLIP/SigLIP dual-encoders) have no single ORT
# session; each sub-model is benchmarked individually and reported as
Expand Down
4 changes: 3 additions & 1 deletion src/winml/modelkit/compiler/stages/compile.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
resolve_device,
)
from ...utils.constants import ORT_SESSION_COMPILER
from ...utils.native_stderr import suppress_native_warnings
from ..configs import WinMLCompileConfig
from .base import BaseStage

Expand Down Expand Up @@ -173,7 +174,8 @@ def _compile_shared_context(self, context: CompileContext) -> None:

if context.use_inference_session:
session_options.add_session_config_entry("ep.context_file_path", str(ctx_path))
session = ort.InferenceSession(str(model_path), sess_options=session_options)
with suppress_native_warnings():
session = ort.InferenceSession(str(model_path), sess_options=session_options)
try:
context.session = session
context.log(f"Actual providers: {session.get_providers()}")
Expand Down
16 changes: 15 additions & 1 deletion src/winml/modelkit/ep_path.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,28 @@
from importlib import metadata
from pathlib import Path
from types import MappingProxyType
from typing import Any
from typing import Any, Final

from packaging.version import InvalidVersion, Version


logger = logging.getLogger(__name__)


# Canonical EPSource origin tags accepted by ``--ep <name>@<source>`` and
# ``EPDeviceTarget(source=...)``.
VALID_SOURCE_TAGS: Final[frozenset[str]] = frozenset(

Check notice

Code scanning / CodeQL

Unused global variable Note

The global variable 'VALID_SOURCE_TAGS' is not used.
{
"bundled",
"pypi",
"nuget",
"msix",
"winml-catalog",
"directory",
}
)


# ---------------------------------------------------------------------------
# Canonical EP metadata registry.
# ---------------------------------------------------------------------------
Expand Down
26 changes: 14 additions & 12 deletions src/winml/modelkit/eval/mask_generation_evaluator.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@

import numpy as np

from ..utils.native_stderr import suppress_native_warnings
from .base_evaluator import WinMLEvaluator


Expand Down Expand Up @@ -294,18 +295,19 @@ def _load_sessions(self) -> tuple[Any, Any]:
sess_opts = ort.SessionOptions()
sess_opts.graph_optimization_level = ort.GraphOptimizationLevel.ORT_DISABLE_ALL

enc = ort.InferenceSession(
paths[self._ENCODER_ROLE],
sess_options=sess_opts,
providers=providers,
provider_options=provider_options,
)
dec = ort.InferenceSession(
paths[self._DECODER_ROLE],
sess_options=sess_opts,
providers=providers,
provider_options=provider_options,
)
with suppress_native_warnings():
enc = ort.InferenceSession(
paths[self._ENCODER_ROLE],
sess_options=sess_opts,
providers=providers,
provider_options=provider_options,
)
dec = ort.InferenceSession(
paths[self._DECODER_ROLE],
sess_options=sess_opts,
providers=providers,
provider_options=provider_options,
)
logger.info(" encoder providers: %s", enc.get_providers())
logger.info(" decoder providers: %s", dec.get_providers())
return enc, dec
Expand Down
8 changes: 5 additions & 3 deletions src/winml/modelkit/optim/pipes/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from typing import TYPE_CHECKING, Any, ClassVar

from ...onnx import load_onnx, save_onnx
from ...utils.native_stderr import suppress_native_warnings
from .base import BasePipe, OptimizationError, PipeConfig, caps_dict


Expand Down Expand Up @@ -568,9 +569,10 @@ def process(self, model: onnx.ModelProto, config: ORTGraphPipeConfig) -> onnx.Mo

# Create session to trigger optimization
try:
_ = ort.InferenceSession(
str(input_file), sess_opts, providers=["CPUExecutionProvider"]
)
with suppress_native_warnings():
_ = ort.InferenceSession(
str(input_file), sess_opts, providers=["CPUExecutionProvider"]
)
except Exception as e:
raise OptimizationError(
f"ONNX Runtime optimization failed: {e}",
Expand Down
13 changes: 7 additions & 6 deletions src/winml/modelkit/pattern/op_input_gen/op_input_gen.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from onnx.defs import OpSchema

from ...onnx import ONNXDomain, SupportedONNXType
from ...utils.native_stderr import suppress_native_warnings
from ...utils.result_sanitizer import sanitize_check_result_payload
from ..utils import get_op_input_properties
from .qdq_gen import QDQGenerator
Expand Down Expand Up @@ -1542,6 +1543,7 @@ def check_on_ep(
isolate_case_execution = ep_checker.needs_case_isolation()

with ResilientRunner(capture_output=capture_output, timeout_sec=60) as runner:

def _run_ep_check(
fn: Callable[[Any, Any], dict[str, Any]],
model_bytes: bytes,
Expand Down Expand Up @@ -1663,9 +1665,7 @@ def _dry_run_result() -> dict[str, Any]:
f"all<{compile_count['all']}>"
f"{Style.RESET_ALL}"
)
if (
not compile_success and save_failed_model
) or save_model:
if (not compile_success and save_failed_model) or save_model:
if save_dir is None:
save_dir = (
Path(model_output_dir)
Expand Down Expand Up @@ -1918,9 +1918,10 @@ def _run_op_on_cpu(self, kwargs: dict[str, Any], tags: dict[str, Any]) -> Any:
model = self._create_model(kwargs, is_constant_map, output_dtypes, qdq_types=qdq_types)

# Create inference session
sess = ort.InferenceSession(
model.SerializeToString(),
)
with suppress_native_warnings():
sess = ort.InferenceSession(
model.SerializeToString(),
)

input_dict = {k: v for k, v in kwargs.items() if k not in self.op_attribute_names}
input_dict = self.create_input_dict(input_dict, qdq_types=qdq_types)
Expand Down
Loading
Loading