Add EP and hardware device type to Windows ML telemetry (#28477)
## Problem
Windows ML engineers need telemetry that answers: **"Which Execution
Providers and hardware device types are apps using for inference, and
how much?"**
Today, the inference telemetry has these gaps:
| Event | Gap |
|---|---|
| SessionCreation | No hardware device type (CPU/GPU/NPU), no vendor ID.
Fires once — falls out of the 24h pipeline join window for long-lived
sessions. |
| RuntimePerf | No EP type, no hardware device — only session_id,
requires a join back to SessionCreation. |
| ExecutionProviderEvent | Only fires for DML. Irrelevant for
QNN/OpenVINO/etc. |
The new Windows ML EP plugin platform (OrtEpDevice / OrtEpFactory /
OrtHardwareDevice) already has all the hardware metadata we need; we
just weren't surfacing it.
## What this PR does
### 1. New `EpDeviceUsage` ETW event
Emitted once per `(EP, hardware device)` tuple at session init and on
every `RuntimePerf` heartbeat (plus a destructor flush). Each event is
self-contained:
| Field | Example |
|---|---|
| executionProviderType | `QNNExecutionProvider` |
| hardwareDeviceType | `NPU` / `GPU` / `CPU` / `FPGA` / `UNKNOWN` |
| hardwareVendorId / hardwareDeviceId | `0x5143` / `0x0901` (PCI IDs) |
| hardwareVendor | `Qualcomm` |
| epVendor | `Qualcomm` |
| assignedNodeCount | `89` (count after graph partitioning) |
| totalRunsSinceLast / totalRunDurationSinceLast | session-level run
counters |
This gives downstream consumers a trivial `GROUP BY
executionProviderType, hardwareDeviceType` without needing to join back
to `SessionCreation`. Works for long-lived sessions that span past the
24h pipeline join window.
### 2. `SessionCreation` enrichment
Added `hardwareDeviceTypes` and `hardwareVendorIds` (comma-separated,
positionally aligned with the existing `executionProviderIds`). Bumped
`schemaVersion` 0 -> 1.
## Implementation notes
- `LogEpDeviceUsage` added to the `Telemetry` interface with a no-op
default; `WindowsTelemetry` implements it via TraceLogging under the
existing `Microsoft.ML.ONNXRuntime` provider (no new provider GUID).
- `InferenceSession::PopulateEpDeviceInfo` runs after graph
partitioning. For EPs created via the V2 path
(`AppendExecutionProvider_V2` / `SetEpSelectionPolicy` /
`RegisterExecutionProviderLibrary`) it pulls full hardware metadata from
`IExecutionProvider::GetEpDevices()`. For legacy EPs it falls back to
`IExecutionProvider::GetDevice()` (OrtDevice type + vendor ID; no PCI
device ID).
- Heartbeat block in `Run()` and destructor flush in `~InferenceSession`
both emit `LogEpDeviceUsage` per entry.
## Testing
- Debug build with Ninja: clean build (1636 targets)
- `onnxruntime_test_all` (full suite): **1571 passed, 0 failed**, 3
skipped (CUDA-EP-gated, environment)
- No memory leaks reported
## Compatibility
- No public C API surface changes.
- `Telemetry::LogSessionCreation` virtual gains two `const std::string&`
parameters — all in-tree overrides are updated.
- `LogEpDeviceUsage` has a no-op default, so non-Windows platforms are
unaffected.
---------
Co-authored-by: Angela Serrano Brummett <angelser@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>