Upgrade to ROCm 10.1 (#24708)

* Update to ROCm 10.1

* Update docs for new setup
This commit is contained in:
Nicolas Mowen
2026-10-18 12:13:32 -05:00
committed by GitHub
parent 893542f4ca
commit d7bb780ff8
9 changed files with 410 additions and 103 deletions
+71 -11
View File
@@ -10,6 +10,7 @@ from typing import Any
import numpy as np
import onnxruntime as ort
from frigate.const import MODEL_CACHE_DIR
from frigate.util.model import get_ort_providers
from frigate.util.rknn_converter import auto_convert_model, is_rknn_compatible
@@ -55,6 +56,9 @@ _PROVIDER_LABELS = {
LIGHTER_ANE_EP_NAME = "LighterANE"
LIGHTER_ANE_LIBRARY = "/usr/lib/lighter/liblighter_ane_ep.so"
# AMD's MIGraphX plugin execution provider, installed in the ROCm image
MIGRAPHX_EP_NAME = "MIGraphXExecutionProvider"
def get_lighter_ane_devices() -> list[Any]:
"""Get the Neural Engine devices lighter's provider offers, registering it once.
@@ -83,6 +87,42 @@ def get_lighter_ane_devices() -> list[Any]:
return devices
def get_migraphx_devices() -> list[Any]:
"""Get the AMD GPU devices the MIGraphX provider offers, registering it once.
Returns:
The provider's ONNX Runtime devices, or an empty list outside the ROCm
image or without an AMD GPU
"""
# Import here to avoid circular imports
from frigate.detectors.hardware import detect_amd_gpu
try:
import onnxruntime_ep_migraphx
except ImportError:
return []
# ONNX Runtime finds the provider's GPUs in the same /sys/class/drm entries,
# so an AMD GPU here means the provider lists it once registered
if detect_amd_gpu() is None:
return []
devices = [d for d in ort.get_ep_devices() if d.ep_name == MIGRAPHX_EP_NAME]
if not devices:
try:
ort.register_execution_provider_library(
MIGRAPHX_EP_NAME, onnxruntime_ep_migraphx.get_library_path()
)
except Exception as e:
logger.warning("Failed to load the MIGraphX provider: %s", e)
return []
devices = [d for d in ort.get_ep_devices() if d.ep_name == MIGRAPHX_EP_NAME]
return devices
def is_arm64_platform() -> bool:
"""Check if we're running on an ARM platform."""
machine = platform.machine().lower()
@@ -756,12 +796,41 @@ def get_optimized_runner(
ONNXModelRunner(session, model_type=model_type),
)
if (
device != "CPU"
and not ONNXModelRunner.is_migraphx_complex_model(model_type)
and (migraphx_devices := get_migraphx_devices())
):
migraphx_cache_dir = os.path.join(MODEL_CACHE_DIR, "migraphx/ort")
os.makedirs(migraphx_cache_dir, exist_ok=True)
sess_options = get_ort_session_options(model_type) or ort.SessionOptions()
# only the first AMD GPU is used, matching detect_amd_gpu
sess_options.add_provider_for_devices(
migraphx_devices[:1], {"cache_dir": migraphx_cache_dir}
)
try:
session = ort.InferenceSession(model_path, sess_options=sess_options)
except Exception as e:
logger.warning(
"Failed to load %s with MIGraphX, using the default providers: %s",
model_path,
e,
)
else:
return _record_runner(
model_path,
model_type,
ONNXModelRunner(session, model_type=model_type),
)
providers, options = get_ort_providers(device == "CPU", device, **kwargs)
if providers[0] == "CPUExecutionProvider":
# In the default image, ONNXRuntime is used so we will only get CPUExecutionProvider
# In other images we will get CUDA / ROCm which are preferred over OpenVINO
# There is currently no way to prioritize OpenVINO over CUDA / ROCm in these images
# In other images we will get CUDA which is preferred over OpenVINO, while
# MIGraphX in the ROCm image is already handled above
# There is currently no way to prioritize OpenVINO over CUDA in these images
if device != "CPU" and is_openvino_gpu_npu_available():
return _record_runner(
model_path,
@@ -790,15 +859,6 @@ def get_optimized_runner(
),
)
if (
providers
and providers[0] == "MIGraphXExecutionProvider"
and ONNXModelRunner.is_migraphx_complex_model(model_type)
):
# Don't use MIGraphX for models that are not supported
providers.pop(0)
options.pop(0)
return _record_runner(
model_path,
model_type,
+215 -1
View File
@@ -1,12 +1,13 @@
"""Tests for the device each model runner reports after loading."""
import os
import sys
import tempfile
import threading
import unittest
from unittest.mock import MagicMock, patch
from frigate.detectors import detection_runners
from frigate.detectors import detection_runners, hardware
from frigate.detectors.detection_runners import (
CudaGraphRunner,
ONNXModelRunner,
@@ -17,6 +18,7 @@ from frigate.detectors.detection_runners import (
record_loaded_device,
snapshot_loaded_devices,
)
from frigate.util import model as model_util
class TestRunnerDeviceName(unittest.TestCase):
@@ -317,3 +319,215 @@ class TestLighterANE(unittest.TestCase):
get_optimized_runner("/models/arcface.onnx", "CPU", "arcface")
ane.assert_not_called()
class TestMIGraphX(unittest.TestCase):
"""AMD's MIGraphX plugin provider, selected through its devices in the ROCm image."""
def setUp(self):
loaded_devices.clear()
self.root = tempfile.TemporaryDirectory()
self.addCleanup(self.root.cleanup)
for patcher in (
patch.object(hardware, "detect_amd_gpu", return_value=MagicMock()),
patch.object(detection_runners, "MODEL_CACHE_DIR", self.root.name),
patch.object(detection_runners, "is_rknn_compatible", return_value=False),
patch.object(detection_runners, "get_lighter_ane_devices", return_value=[]),
):
patcher.start()
self.addCleanup(patcher.stop)
self.plugin = MagicMock()
self.plugin.get_library_path.return_value = "/site/libmigraphx-ep.so"
def _device(self) -> MagicMock:
device = MagicMock()
device.ep_name = "MIGraphXExecutionProvider"
return device
def test_no_devices_outside_the_rocm_image(self):
with (
patch.dict(sys.modules, {"onnxruntime_ep_migraphx": None}),
patch.object(
detection_runners.ort, "register_execution_provider_library"
) as register,
):
self.assertEqual(detection_runners.get_migraphx_devices(), [])
self.assertEqual(detection_runners.get_migraphx_devices(), [])
register.assert_not_called()
def test_no_devices_without_an_amd_gpu(self):
with (
patch.dict(sys.modules, {"onnxruntime_ep_migraphx": self.plugin}),
patch.object(hardware, "detect_amd_gpu", return_value=None),
patch.object(detection_runners.ort, "get_ep_devices") as get_ep_devices,
patch.object(
detection_runners.ort, "register_execution_provider_library"
) as register,
):
self.assertEqual(detection_runners.get_migraphx_devices(), [])
get_ep_devices.assert_not_called()
register.assert_not_called()
def test_the_provider_is_registered_once(self):
device = self._device()
other = MagicMock()
other.ep_name = "CPUExecutionProvider"
with (
patch.dict(sys.modules, {"onnxruntime_ep_migraphx": self.plugin}),
patch.object(
detection_runners.ort,
"get_ep_devices",
side_effect=[[other], [other, device], [other, device]],
),
patch.object(
detection_runners.ort, "register_execution_provider_library"
) as register,
):
self.assertEqual(detection_runners.get_migraphx_devices(), [device])
self.assertEqual(detection_runners.get_migraphx_devices(), [device])
register.assert_called_once_with(
"MIGraphXExecutionProvider", "/site/libmigraphx-ep.so"
)
def test_a_provider_that_will_not_load_is_skipped(self):
with (
patch.dict(sys.modules, {"onnxruntime_ep_migraphx": self.plugin}),
patch.object(detection_runners.ort, "get_ep_devices", return_value=[]),
patch.object(
detection_runners.ort,
"register_execution_provider_library",
side_effect=RuntimeError("libamdhip64.so.7: cannot open shared object"),
),
self.assertLogs(detection_runners.logger, level="WARNING"),
):
self.assertEqual(detection_runners.get_migraphx_devices(), [])
def test_the_session_runs_on_the_first_gpu(self):
device = self._device()
session = MagicMock()
session.get_providers.return_value = [
"MIGraphXExecutionProvider",
"CPUExecutionProvider",
]
options = MagicMock()
with (
patch.object(
detection_runners,
"get_migraphx_devices",
return_value=[device, self._device()],
),
patch.object(
detection_runners, "get_ort_session_options", return_value=options
),
patch.object(
detection_runners.ort, "InferenceSession", return_value=session
) as inference_session,
):
runner = get_optimized_runner("/models/yolo.onnx", "AUTO", "yolo-generic")
cache_dir = os.path.join(self.root.name, "migraphx/ort")
options.add_provider_for_devices.assert_called_once_with(
[device], {"cache_dir": cache_dir}
)
inference_session.assert_called_once_with(
"/models/yolo.onnx", sess_options=options
)
self.assertTrue(os.path.isdir(cache_dir))
self.assertIsInstance(runner, ONNXModelRunner)
self.assertEqual(
loaded_devices["/models/yolo.onnx"], ("yolo-generic", "MIGraphX")
)
def test_a_model_migraphx_cannot_load_uses_the_default_providers(self):
session = MagicMock()
session.get_providers.return_value = ["CPUExecutionProvider"]
with (
patch.object(
detection_runners,
"get_migraphx_devices",
return_value=[self._device()],
),
patch.object(
detection_runners,
"get_ort_providers",
return_value=(["CPUExecutionProvider"], [{}]),
),
patch.object(
detection_runners, "is_openvino_gpu_npu_available", return_value=False
),
patch.object(
detection_runners.ort,
"InferenceSession",
side_effect=[RuntimeError("compile failed"), session],
) as inference_session,
patch.object(
detection_runners, "get_ort_session_options", return_value=MagicMock()
),
self.assertLogs(detection_runners.logger, level="WARNING"),
):
runner = get_optimized_runner("/models/arcface.onnx", "AUTO", "arcface")
self.assertEqual(inference_session.call_count, 2)
self.assertIsInstance(runner, ONNXModelRunner)
self.assertEqual(loaded_devices["/models/arcface.onnx"], ("arcface", "CPU"))
def test_unsupported_models_and_cpu_models_skip_migraphx(self):
for model_path, device, model_type in [
("/models/paddleocr.onnx", "AUTO", "paddleocr"),
("/models/dfine.onnx", "AUTO", "dfine"),
("/models/yolo.onnx", "CPU", "yolo-generic"),
]:
session = MagicMock()
session.get_providers.return_value = ["CPUExecutionProvider"]
with (
self.subTest(model_type=model_type, device=device),
patch.object(
detection_runners,
"get_migraphx_devices",
return_value=[self._device()],
) as migraphx,
patch.object(
detection_runners,
"get_ort_providers",
return_value=(["CPUExecutionProvider"], [{}]),
),
patch.object(
detection_runners,
"is_openvino_gpu_npu_available",
return_value=False,
),
patch.object(
detection_runners.ort, "InferenceSession", return_value=session
),
patch.object(
detection_runners, "get_ort_session_options", return_value=None
),
):
get_optimized_runner(model_path, device, model_type)
migraphx.assert_not_called()
self.assertEqual(loaded_devices[model_path], (model_type, "CPU"))
def test_migraphx_is_never_requested_by_name(self):
with patch.object(
model_util.ort,
"get_available_providers",
return_value=[
"AzureExecutionProvider",
"CPUExecutionProvider",
"MIGraphXExecutionProvider",
],
):
providers, options = model_util.get_ort_providers()
self.assertEqual(providers, ["CPUExecutionProvider"])
self.assertEqual(options, [{"enable_cpu_mem_arena": False}])
+3 -9
View File
@@ -385,15 +385,9 @@ def get_ort_providers(
}
)
elif provider == "MIGraphXExecutionProvider":
migraphx_cache_dir = os.path.join(MODEL_CACHE_DIR, "migraphx")
os.makedirs(migraphx_cache_dir, exist_ok=True)
providers.append(provider)
options.append(
{
"migraphx_model_cache_dir": migraphx_cache_dir,
}
)
# MIGraphX is a plugin provider, which ONNX Runtime can't create by
# name, so get_optimized_runner selects it through its devices
continue
elif provider == "CPUExecutionProvider":
providers.append(provider)
options.append(