Source code for openstef_foundation_models.inference.provider_selection

# SPDX-FileCopyrightText: 2025 Contributors to the OpenSTEF project <openstef@lfenergy.org>
#
# SPDX-License-Identifier: MPL-2.0

"""Metadata-driven execution-provider selection.

Which ONNX Runtime execution provider is fastest — and even which is *usable* —
depends on both the host (Apple CoreML vs NVIDIA CUDA/TensorRT vs CPU) and on
properties of the checkpoint itself (precision, whether its graph has static
shapes). This module keeps that knowledge in **one replaceable component** rather
than scattered platform ``if``-ladders:

* :class:`HostCapabilities` carries the host facts as injectable data, with a
  single impure :meth:`HostCapabilities.detect` classmethod.
* :class:`ProviderPolicy` is the port; :class:`DefaultProviderPolicy` is the
  adapter that maps ``(checkpoint, host)`` to an ordered provider chain. Users
  with exotic hardware implement their own policy.

Importing this module requires ONNX Runtime (the ``[cpu]`` or ``[gpu]`` extra)
and raises :class:`MissingExtraError` if it is missing.
"""

import platform
from typing import Literal, Protocol, Self

from pydantic import Field

from openstef_core.base_model import BaseConfig
from openstef_core.exceptions import MissingExtraError
from openstef_foundation_models.inference.providers import (
    CoreMLProvider,
    CpuProvider,
    CudaProvider,
    ExecutionProvider,
)
from openstef_foundation_models.models.checkpoint import CheckpointMetadata

try:
    import onnxruntime as ort
except ImportError as e:
    raise MissingExtraError("onnxruntime", "openstef-foundation-models", install_extra="cpu") from e


[docs] class HostCapabilities(BaseConfig): """Execution-relevant facts about the host, captured as injectable data. Passing host facts into a policy (rather than having the policy call ``platform.system()`` itself) keeps selection a pure function of its inputs, so it can be unit-tested by constructing a fake host. """ model_config = BaseConfig.model_config | {"frozen": True} platform: str = Field( description="OS identifier, lower-cased (e.g. 'darwin', 'linux', 'windows').", ) available_providers: frozenset[str] = Field( description="Execution provider names ONNX Runtime reports as available on this host.", )
[docs] @classmethod def detect(cls) -> Self: """Detect the host's capabilities from the platform and ONNX Runtime. This is the one impure call in the selection path; it is isolated here so the policy stays a pure function of injected facts. Returns: The detected host capabilities. """ return cls( platform=platform.system().lower(), available_providers=frozenset(ort.get_available_providers()), )
[docs] class ProviderPolicy(Protocol): """Port mapping a checkpoint and host to an ordered execution-provider chain. Implement this to encode selection rules for hardware the default policy does not cover; pass the implementation to the backend or :class:`~openstef_foundation_models.presets.forecasting_workflow.OnnxBackendConfig`. A policy-selected chain is enforced *gracefully*: ONNX Runtime silently drops accelerators it cannot initialize and falls back to CPU, and a policy chain such as ``[CoreML, CPU]`` realizing CoreML is the intended outcome, so a warning is logged only if it falls all the way to CPU. A chain the caller passes explicitly is enforced *strictly* instead: any requested accelerator that is not realized raises. """
[docs] def select(self, metadata: CheckpointMetadata, host: HostCapabilities) -> list[ExecutionProvider]: """Return the ordered provider chain to try for *metadata* on *host*.""" ...
[docs] class DefaultProviderPolicy(BaseConfig): """Default policy mapping ``(checkpoint precision/shape, host)`` to a provider chain. Each rule encodes a *measured* hardware conclusion; see the design doc ``design-docs/0001`` and the provider benchmark for the rationale. The chain is ordered preferred-first with CPU as the final fallback. """ kind: Literal["default"] = Field(default="default", description="Discriminator tag for the policy type.")
[docs] def select( # instance method to satisfy the ProviderPolicy protocol, though stateless here self, metadata: CheckpointMetadata, host: HostCapabilities ) -> list[ExecutionProvider]: """Select an ordered provider chain for *metadata* on *host*. Args: metadata: The checkpoint's metadata (precision, static-shape-ness). host: The detected host capabilities. Returns: An ordered execution-provider chain, preferred-first, CPU last. """ cuda_ok = "CUDAExecutionProvider" in host.available_providers coreml_ok = "CoreMLExecutionProvider" in host.available_providers and metadata.static_shapes # int8 (QDQ) runs fast on CPU; CoreML cannot accelerate the quantized ops, # so it is skipped entirely. CUDA int8 is fine when a GPU is present. if metadata.precision == "int8": return [CudaProvider(), CpuProvider()] if cuda_ok else [CpuProvider()] # macOS: a static-shape fp16/fp32 graph runs on CoreML, but only on the GPU # (MLComputeUnits=ALL/ANE triggers a multi-minute Neural-Engine compile for # no inference win — measured). if host.platform == "darwin" and coreml_ok: return [CoreMLProvider(compute_units="CPUAndGPU"), CpuProvider()] # NVIDIA: CUDA with a CPU fallback. TensorRT stays opt-in (engine-build cost # and fp16 caveats), so the default never selects it. if cuda_ok: return [CudaProvider(), CpuProvider()] return [CpuProvider()]
__all__ = [ "DefaultProviderPolicy", "HostCapabilities", "ProviderPolicy", ]