Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
46 changes: 31 additions & 15 deletions src/winml/modelkit/commands/perf.py
Original file line number Diff line number Diff line change
Expand Up @@ -233,8 +233,28 @@ def _resolve_ep_monitor(
device_norm = (device or "").lower()

if op_tracing:
from ..session.monitor.openvino_monitor import OpenVinoMonitor
from ..session.monitor.qnn_monitor import QNNMonitor

if ep_norm == "openvino":
if op_tracing != "basic":
raise RuntimeError("OpenVINO op-tracing currently supports only level 'basic'.")
if device_norm not in ("cpu", "npu"):
raise RuntimeError(
"OpenVINO op-tracing currently supports only --device cpu or --device npu."
)
OpenVinoMonitor.validate_runtime_version()
if not OpenVinoMonitor.is_available():
raise RuntimeError(
"Op-tracing --ep openvino requested but OpenVINO is not available "
"on this system."
)
return OpenVinoMonitor(
level="basic",
output_dir=output_dir,
device=cast("Literal['cpu', 'npu']", device_norm),
)

qnn_available: bool | None = None

def is_qnn_available() -> bool:
Expand All @@ -260,7 +280,8 @@ def is_qnn_available() -> bool:
)

raise RuntimeError(
f"Op-tracing not available for EP {ep!r} on device {device!r}. Supported EPs: qnn."
f"Op-tracing not available for EP {ep!r} on device {device!r}. "
"Supported EPs: qnn, openvino (basic on cpu/npu)."
)

# Proof-of-execution monitors (no op-tracing)
Expand Down Expand Up @@ -328,27 +349,20 @@ def _get_ep_device_binding(
candidates = [
candidate
for candidate in ep_device.ep.devices
if provider_device is None
or candidate.device_type.lower() == provider_device
if provider_device is None or candidate.device_type.lower() == provider_device
]
if not candidates:
return None, provider_device
candidate_options = [
(
candidate,
{
str(key): str(value)
for key, value in candidate.ort_handle.ep_options.items()
},
{str(key): str(value) for key, value in candidate.ort_handle.ep_options.items()},
)
for candidate in candidates
]
advertised_keys = {
key for _, options in candidate_options for key in options
}
advertised_keys = {key for _, options in candidate_options for key in options}
selected_options = {
str(key): str(value)
for key, value in device.ort_handle.ep_options.items()
str(key): str(value) for key, value in device.ort_handle.ep_options.items()
}
device_overrides = {
key: str(value)
Expand Down Expand Up @@ -1767,9 +1781,7 @@ def _perf_modules(
device=effective_monitor_device,
ep_name=cast("EPName | None", session.ep_name),
adapter_luid=adapter_luid,
adapter_device=(
adapter_device if adapter_luid is not None else None
),
adapter_device=(adapter_device if adapter_luid is not None else None),
)

if hw_ctx:
Expand Down Expand Up @@ -2149,10 +2161,13 @@ def _print_save_to_footer(
console: Console,
*,
profiling_csv: str | None,
profiling_json: str | None = None,
) -> None:
"""Print the raw profiling artifact path after the op-trace report."""
if profiling_csv:
console.print(f"[dim]Profiling CSV:[/dim] {profiling_csv}")
if profiling_json:
console.print(f"[dim]Profiling JSON:[/dim] {profiling_json}")


@dataclass
Expand Down Expand Up @@ -3464,6 +3479,7 @@ def perf(
_print_save_to_footer(
console,
profiling_csv=profiling_csv,
profiling_json=trace_result.artifacts.get("profile"),
)
else:
if json_mode:
Expand Down
14 changes: 14 additions & 0 deletions src/winml/modelkit/session/monitor/ep_monitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@
from pathlib import Path
from typing import Self

import onnxruntime as ort

from .op_metrics import OpTraceResult


Expand Down Expand Up @@ -78,6 +80,11 @@ class WinMLEPMonitor(ABC):
#: hooks return empty dicts.
ep_name: ClassVar[str | None] = None

#: Whether the monitor mutates native ``SessionOptions`` attributes through
#: :meth:`configure_session_options` and therefore needs a dedicated
#: inference session for the perf window.
configures_session_options: ClassVar[bool] = False

def __init_subclass__(cls, **kwargs: Any) -> None:
"""Reject subclasses that try to shadow load-bearing class vars.

Expand Down Expand Up @@ -110,6 +117,13 @@ def get_provider_options(self) -> dict[str, str]:
"""
return {}

def configure_session_options(self, session_options: ort.SessionOptions) -> None: # noqa: B027
"""Apply monitor-specific native ``SessionOptions`` attributes.

Default: no-op. This complements :meth:`get_session_options`, which is
limited to string-valued ``add_session_config_entry`` settings.
"""

def set_onnx_op_types(self, onnx_op_types: dict[str, str]) -> None: # noqa: B027 - intentional no-op default; op-tracing monitors override
"""Inject the ONNX ``node.name -> node.op_type`` map.

Expand Down
Loading
Loading