Compare commits

..
Author SHA1 Message Date
Josh Hawkins ea268533e5 keep valid listen values and refuse to start nginx when the ports collide 2026-10-18 13:59:38 -05:00
Josh Hawkins 777b33f849 fall back to default listen ports when either is invalid or they collide 2026-10-18 13:49:43 -05:00
Josh Hawkins f08e4aac69 always create go2rtc.yaml after removing it and don't let opt_in_out stop frigate
The go2rtc else-branch was only reachable when a regular file was planted between rm and the test, and then loaded that file as go2rtc's config. create_config.py now runs unconditionally and its exclusive create fails closed. opt_in_out runs after s6-svc -O, so a failure there was terminal under errexit.
2026-10-18 12:35:45 -05:00
Josh Hawkins 2d632f4b70 accept hostnames, wildcards, and padded values in networking.listen
The stricter parser broke localhost:5000, *:5000, and values with surrounding whitespace that the old int() parsing accepted. parse_listen_address now strips the value and accepts RFC 1123 hostnames and *, still rebuilding the rendered value from the parsed parts. The ListenConfig validator runs in before mode, because pydantic coerced true to port 1 while get_nginx_settings.py rejected it and fell back to 5000, so the auth internal port and nginx disagreed.
2026-10-18 12:35:45 -05:00
Josh Hawkins 2f3338f950 tighten comments 2026-10-18 12:19:43 -05:00
Josh Hawkins f650ccb3b5 validate networking.listen values before rendering them into nginx config
ListenConfig and get_nginx_settings.py only checked that the text after the last colon parsed as an int, and tempio rendered the raw string into the listen directive, so a value like "5000; location /x/ { alias /; } #:5000" injected nginx directives. Both now go through parse_listen_address, which accepts only a port, IPv4:port, or [IPv6]:port with a 1 to 65535 port and rebuilds the value from the parsed parts. get_nginx_settings.py falls back to the default port on an invalid value so nginx still serves the safe-mode UI.
2026-10-18 12:15:30 -05:00
Josh Hawkins 19d3047fb9 run root python helpers in isolated mode and keep HOME=/config for non-root containers
The previous commit only set HOME=/config when dropping from root, so a container started with --user lost it. The other root-run helpers in the go2rtc, nginx, and certsync scripts now use python3 -I too, so they never load user site-packages or PYTHON* variables.
2026-10-18 12:11:59 -05:00
Josh Hawkins 5d81da8c00 don't follow a planted symlink when writing /dev/shm/go2rtc.yaml
After the first start the go2rtc user owned /dev/shm/go2rtc.yaml and could swap it for a dangling symlink. The run script's -f test was false for a dangling link, so the link survived, and root's create_config.py then created the link target and the chown handed it to go2rtc. The run script now removes any existing entry, create_config.py creates the file with O_EXCL and O_NOFOLLOW at mode 0640, and the chown uses -h.
2026-10-18 12:07:17 -05:00
Josh Hawkins 72183983b0 keep /config out of the root environment in the frigate run script
The script exported HOME=/config and prepended /config/.local/lib to LD_LIBRARY_PATH while still root, then ran python3, ffmpeg, grep, and opt_in_out as root. The runtime user owns /config, so it could plant a .pth file in the user site-packages or a library on the loader path and get code run as root on the next start. Root helpers now run with root's environment (and python3 -I), and HOME, LD_LIBRARY_PATH, and PATH reach only the dropped process through env on the s6-setuidgid line. opt_in_out runs as the runtime user, so the openvino marker no longer gets written into /config by root.
2026-10-18 12:06:35 -05:00
24 changed files with 757 additions and 820 deletions
+1 -1
View File
@@ -1,2 +1,2 @@
numpy numpy
openvino >= 2026.4.1 openvino >= 2026.2.0
+4 -4
View File
@@ -23,7 +23,7 @@ pandas == 3.0.*
peewee == 4.5.* peewee == 4.5.*
peewee_migrate == 2.3.* peewee_migrate == 2.3.*
psutil == 7.2.* psutil == 7.2.*
pydantic == 2.14.* pydantic == 2.13.*
git+https://github.com/fbcotter/py3nvml#egg=py3nvml git+https://github.com/fbcotter/py3nvml#egg=py3nvml
tzdata == 2026.5 tzdata == 2026.5
pyzmq == 27.2.* pyzmq == 27.2.*
@@ -42,16 +42,16 @@ opencv-python-headless == 4.11.0.*
opencv-contrib-python == 4.11.0.* opencv-contrib-python == 4.11.0.*
scipy == 1.17.* scipy == 1.17.*
# OpenVino & ONNX # OpenVino & ONNX
openvino == 2026.4.* openvino == 2025.4.*
onnxruntime == 1.30.* onnxruntime == 1.30.*
# Embeddings # Embeddings
transformers == 5.19.* transformers == 5.19.*
# Generative AI # Generative AI
google-genai == 2.31.* google-genai == 2.29.*
ollama == 0.6.* ollama == 0.6.*
openai == 3.26.* openai == 3.26.*
# push notifications # push notifications
py-vapid == 1.9.6 py-vapid == 1.9.4
pywebpush == 2.5.* pywebpush == 2.5.*
# alpr # alpr
pyclipper == 1.4.* pyclipper == 1.4.*
+1 -1
View File
@@ -1 +1 @@
scikit-build == 0.19.* scikit-build == 0.18.*
@@ -36,8 +36,8 @@ else
lefile="/config/tls/fullchain.pem" lefile="/config/tls/fullchain.pem"
fi fi
tls_enabled=`python3 /usr/local/nginx/get_nginx_settings.py | jq -r .tls.enabled` tls_enabled=`python3 -I /usr/local/nginx/get_nginx_settings.py | jq -r .tls.enabled`
listen_external_port=`python3 /usr/local/nginx/get_nginx_settings.py | jq -r .listen.external_port` listen_external_port=`python3 -I /usr/local/nginx/get_nginx_settings.py | jq -r .listen.external_port`
while true while true
do do
@@ -11,30 +11,13 @@ if [[ "$(id -u)" -eq 0 ]]; then
fi fi
fi fi
# /root survives s6-setuidgid and breaks cache writes after the drop; set
# before opt_in_out so the opt-out marker lands where the service will look
if [[ "$runs_as_root" -eq 0 ]]; then
export HOME=/config
fi
# detector runtimes installed at first start (pip install --user) live under
# $HOME/.local; the dynamic loader only reads LD_LIBRARY_PATH at exec time
export LD_LIBRARY_PATH="${LD_LIBRARY_PATH:+${LD_LIBRARY_PATH}:}${HOME}/.local/lib"
export PATH="${PATH}:${HOME}/.local/bin"
# opt out of openvino telemetry
if [ -e /usr/local/bin/opt_in_out ]; then
/usr/local/bin/opt_in_out --opt_out > /dev/null 2>&1
fi
# Logs should be sent to stdout so that s6 can collect them
# Tell S6-Overlay not to restart this service # Tell S6-Overlay not to restart this service
s6-svc -O . s6-svc -O .
# Runs as root with root's HOME and LD_LIBRARY_PATH; -I skips user site-packages
function set_libva_version() { function set_libva_version() {
local ffmpeg_path local ffmpeg_path
ffmpeg_path=$(python3 /usr/local/ffmpeg/get_ffmpeg_path.py) ffmpeg_path=$(python3 -I /usr/local/ffmpeg/get_ffmpeg_path.py)
LIBAVFORMAT_VERSION_MAJOR=$("$ffmpeg_path" -version | grep -Po "libavformat\W+\K\d+") LIBAVFORMAT_VERSION_MAJOR=$("$ffmpeg_path" -version | grep -Po "libavformat\W+\K\d+")
export LIBAVFORMAT_VERSION_MAJOR export LIBAVFORMAT_VERSION_MAJOR
} }
@@ -42,14 +25,32 @@ function set_libva_version() {
echo "[INFO] Preparing Frigate..." echo "[INFO] Preparing Frigate..."
set_libva_version set_libva_version
# HOME=/root survives s6-setuidgid, so any run that is not root gets
# HOME=/config explicitly. The runtime user owns /config, so these paths go
# only to the service process, never to root. Detector runtimes installed at
# first start (pip install --user) live under $HOME/.local.
run_as=()
runtime_home="${HOME}"
if [[ "$runs_as_root" -eq 0 ]]; then
runtime_home=/config
if [[ "$(id -u)" -eq 0 ]]; then
run_as=(s6-setuidgid frigate)
fi
fi
run_as+=(env
"HOME=${runtime_home}"
"LD_LIBRARY_PATH=${LD_LIBRARY_PATH:+${LD_LIBRARY_PATH}:}${runtime_home}/.local/lib"
"PATH=${PATH}:${runtime_home}/.local/bin")
# opt out of openvino telemetry; the marker lands under the service's HOME
if [ -e /usr/local/bin/opt_in_out ]; then
"${run_as[@]}" /usr/local/bin/opt_in_out --opt_out > /dev/null 2>&1 || true
fi
echo "[INFO] Starting Frigate..." echo "[INFO] Starting Frigate..."
cd /opt/frigate || echo "[ERROR] Failed to change working directory to /opt/frigate" cd /opt/frigate || echo "[ERROR] Failed to change working directory to /opt/frigate"
# Replace the bash process with the Frigate process, redirecting stderr to stdout # Replace the bash process with the Frigate process, redirecting stderr to stdout
exec 2>&1 exec 2>&1
if [[ "$(id -u)" -ne 0 || "$runs_as_root" -eq 1 ]]; then exec "${run_as[@]}" python3 -u -m frigate
exec python3 -u -m frigate
else
exec s6-setuidgid frigate python3 -u -m frigate
fi
@@ -59,38 +59,33 @@ function get_ip_and_port_from_supervisor() {
function set_libva_version() { function set_libva_version() {
local ffmpeg_path local ffmpeg_path
ffmpeg_path=$(python3 /usr/local/ffmpeg/get_ffmpeg_path.py) ffmpeg_path=$(python3 -I /usr/local/ffmpeg/get_ffmpeg_path.py)
LIBAVFORMAT_VERSION_MAJOR=$("$ffmpeg_path" -version | grep -Po "libavformat\W+\K\d+") LIBAVFORMAT_VERSION_MAJOR=$("$ffmpeg_path" -version | grep -Po "libavformat\W+\K\d+")
export LIBAVFORMAT_VERSION_MAJOR export LIBAVFORMAT_VERSION_MAJOR
} }
set_libva_version set_libva_version
if [[ -f "/dev/shm/go2rtc.yaml" ]]; then # create_config.py creates the file exclusively and fails if anything
echo "[INFO] Removing stale config from last run..." # reappears at this path
rm /dev/shm/go2rtc.yaml rm -f /dev/shm/go2rtc.yaml
echo "[INFO] Preparing new go2rtc config..."
if [[ -n "${SUPERVISOR_TOKEN:-}" ]]; then
# Running as a Home Assistant Add-on, infer the IP address and port
get_ip_and_port_from_supervisor
fi fi
if [[ ! -f "/dev/shm/go2rtc.yaml" ]]; then python3 -I /usr/local/go2rtc/create_config.py
echo "[INFO] Preparing new go2rtc config..."
if [[ -n "${SUPERVISOR_TOKEN:-}" ]]; then
# Running as a Home Assistant Add-on, infer the IP address and port
get_ip_and_port_from_supervisor
fi
python3 /usr/local/go2rtc/create_config.py
else
echo "[WARNING] Unable to remove existing go2rtc config. Changes made to your frigate config file may not be recognized. Please remove the /dev/shm/go2rtc.yaml from your docker host manually."
fi
# HomeKit persistence. The helper is symlink-safe; hand off to go2rtc only when dropping. # HomeKit persistence. The helper is symlink-safe; hand off to go2rtc only when dropping.
readonly homekit_config_path="/config/go2rtc_homekit.yml" readonly homekit_config_path="/config/go2rtc_homekit.yml"
if [[ "$(id -u)" -eq 0 && "$runs_as_root" -eq 0 ]]; then if [[ "$(id -u)" -eq 0 && "$runs_as_root" -eq 0 ]]; then
python3 /usr/local/go2rtc/prepare_homekit.py "${homekit_config_path}" --chown python3 -I /usr/local/go2rtc/prepare_homekit.py "${homekit_config_path}" --chown
chown go2rtc:go2rtc /dev/shm/go2rtc.yaml 2>/dev/null || true chown -h go2rtc:go2rtc /dev/shm/go2rtc.yaml 2>/dev/null || true
else else
python3 /usr/local/go2rtc/prepare_homekit.py "${homekit_config_path}" python3 -I /usr/local/go2rtc/prepare_homekit.py "${homekit_config_path}"
fi fi
readonly config_path="/config" readonly config_path="/config"
@@ -124,7 +124,7 @@ fi
mkdir -p /etc/letsencrypt/www 2>/dev/null || true mkdir -p /etc/letsencrypt/www 2>/dev/null || true
# nginx settings are read once; both templates consume them # nginx settings are read once; both templates consume them
nginx_settings=$(python3 /usr/local/nginx/get_nginx_settings.py) nginx_settings=$(python3 -I /usr/local/nginx/get_nginx_settings.py)
# build templates for optional FRIGATE_BASE_PATH environment variable # build templates for optional FRIGATE_BASE_PATH environment variable
echo "$nginx_settings" | \ echo "$nginx_settings" | \
@@ -198,9 +198,13 @@ if config.get("birdseye", {}).get("restream", False):
else: else:
go2rtc_config["streams"] = {"birdseye": ffmpeg_cmd} go2rtc_config["streams"] = {"birdseye": ffmpeg_cmd}
# Write go2rtc_config to /dev/shm/go2rtc.yaml # The run script removes the old file first. Exclusive create never follows a
with open("/dev/shm/go2rtc.yaml", "w") as f: # link the go2rtc user planted in sticky /dev/shm. The config contains camera
# credentials, so it is not world-readable.
fd = os.open(
"/dev/shm/go2rtc.yaml",
os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW,
0o640,
)
with os.fdopen(fd, "w") as f:
yaml.dump(go2rtc_config, f) yaml.dump(go2rtc_config, f)
# config contains camera credentials; do not leave it world-readable
os.chmod("/dev/shm/go2rtc.yaml", 0o640)
@@ -8,7 +8,7 @@ from typing import Any
from ruamel.yaml import YAML from ruamel.yaml import YAML
sys.path.insert(0, "/opt/frigate") sys.path.insert(0, "/opt/frigate")
from frigate.util.config import find_config_file from frigate.util.config import find_config_file, parse_listen_address
sys.path.remove("/opt/frigate") sys.path.remove("/opt/frigate")
@@ -35,20 +35,29 @@ ipv6_config: dict[str, Any] = networking_config.get("ipv6", {})
ipv6_config.setdefault("enabled", False) ipv6_config.setdefault("enabled", False)
listen_config: dict[str, Any] = networking_config.get("listen", {}) listen_config: dict[str, Any] = networking_config.get("listen", {})
listen_config.setdefault("internal", 5000)
listen_config.setdefault("external", 8971)
# handle case where internal port is a string with ip:port # Frigate's own validation runs elsewhere, and a bad value here must not stop nginx
internal_port = listen_config["internal"] # from serving the safe-mode UI. Valid values are kept so a fallback never widens
if type(internal_port) is str: # the unauthenticated internal listener beyond the configured address.
internal_port = int(internal_port.split(":")[-1]) for key, default in (("internal", 5000), ("external", 8971)):
listen_config["internal_port"] = internal_port try:
value, port = parse_listen_address(listen_config.get(key, default))
except ValueError:
print(
f"[WARN] Invalid networking.listen.{key}, using {default}",
file=sys.stderr,
)
value, port = default, default
# handle case where external port is a string with ip:port listen_config[key] = value
external_port = listen_config["external"] listen_config[f"{key}_port"] = port
if type(external_port) is str:
external_port = int(external_port.split(":")[-1]) if listen_config["internal_port"] == listen_config["external_port"]:
listen_config["external_port"] = external_port print(
"[ERROR] networking.listen.internal and external share a port",
file=sys.stderr,
)
sys.exit(1)
base_path = os.environ.get("FRIGATE_BASE_PATH", "") base_path = os.environ.get("FRIGATE_BASE_PATH", "")
+50 -11
View File
@@ -2,9 +2,32 @@
# https://askubuntu.com/questions/972516/debian-frontend-environment-variable # https://askubuntu.com/questions/972516/debian-frontend-environment-variable
ARG DEBIAN_FRONTEND=noninteractive ARG DEBIAN_FRONTEND=noninteractive
ARG ROCM=1
ARG HSA_OVERRIDE_GFX_VERSION ARG HSA_OVERRIDE_GFX_VERSION
ARG HSA_OVERRIDE ARG HSA_OVERRIDE
#######################################################################
FROM wget AS rocm
ARG ROCM
RUN apt update -qq && \
apt install -y wget gpg && \
wget -O rocm.deb https://repo.radeon.com/amdgpu-install/7.2.3/ubuntu/jammy/amdgpu-install_7.2.3.70203-1_all.deb && \
apt install -y ./rocm.deb && \
apt update && \
apt install -qq -y rocm
RUN mkdir -p /opt/rocm-dist/opt/rocm-$ROCM/lib
RUN cd /opt/rocm-$ROCM/lib && \
cp -dpr libMIOpen*.so* libamd*.so* libhip*.so* libhsa*.so* libmigraphx*.so* librocm*.so* librocblas*.so* libroctracer*.so* librocsolver*.so* librocfft*.so* librocprofiler*.so* libroctx*.so* librocroller.so* /opt/rocm-dist/opt/rocm-$ROCM/lib/ && \
mkdir -p /opt/rocm-dist/opt/rocm-$ROCM/lib/migraphx/lib && \
cp -dpr migraphx/lib/* /opt/rocm-dist/opt/rocm-$ROCM/lib/migraphx/lib
RUN cd /opt/rocm-dist/opt/ && ln -s rocm-$ROCM rocm
RUN mkdir -p /opt/rocm-dist/etc/ld.so.conf.d/
RUN echo /opt/rocm/lib|tee /opt/rocm-dist/etc/ld.so.conf.d/rocm.conf
####################################################################### #######################################################################
FROM deps AS deps-prelim FROM deps AS deps-prelim
@@ -19,35 +42,51 @@ RUN echo "deb http://deb.debian.org/debian trixie main" > /etc/apt/sources.list.
rm -f /etc/apt/sources.list.d/trixie.list && \ rm -f /etc/apt/sources.list.d/trixie.list && \
rm -rf /var/lib/apt/lists/* rm -rf /var/lib/apt/lists/*
WORKDIR /opt/frigate
COPY --from=rootfs / /
RUN wget -q https://bootstrap.pypa.io/get-pip.py -O get-pip.py \ RUN wget -q https://bootstrap.pypa.io/get-pip.py -O get-pip.py \
&& sed -i 's/args.append("setuptools")/args.append("setuptools==77.0.3")/' get-pip.py \ && sed -i 's/args.append("setuptools")/args.append("setuptools==77.0.3")/' get-pip.py \
&& python3 get-pip.py "pip" --break-system-packages && python3 get-pip.py "pip" --break-system-packages
RUN python3 -m pip config set global.break-system-packages true RUN python3 -m pip config set global.break-system-packages true
# ROCm installs from AMD's wheels ahead of Frigate's code so code changes
# reuse this layer, and is pruned in the same layer so the removed files never
# reach the image. /opt/rocm links to the runtime for rocminfo and HIP headers.
COPY docker/rocm/requirements-wheels-rocm.txt /requirements.txt COPY docker/rocm/requirements-wheels-rocm.txt /requirements.txt
RUN --mount=type=bind,source=docker/rocm/prune_rocm.sh,target=/deps/prune_rocm.sh \ RUN pip3 uninstall -y onnxruntime \
pip3 uninstall -y onnxruntime \ && pip3 install -r /requirements.txt
&& pip3 install --no-cache-dir -r /requirements.txt \
&& ROCM_SDK_CORE="$(python3 -c 'import _rocm_sdk_core, os; print(os.path.dirname(_rocm_sdk_core.__file__))')" \
&& /deps/prune_rocm.sh "$(dirname "${ROCM_SDK_CORE}")" \
&& ln -s "${ROCM_SDK_CORE}" /opt/rocm
WORKDIR /opt/frigate #######################################################################
COPY --from=rootfs / / FROM scratch AS rocm-dist
ARG ROCM
# Copy HIP headers required for MIOpen JIT (BuildHip) / HIPRTC at runtime
COPY --from=rocm /opt/rocm-${ROCM}/include/ /opt/rocm-${ROCM}/include/
COPY --from=rocm /opt/rocm-$ROCM/bin/rocminfo /opt/rocm-$ROCM/bin/migraphx-driver /opt/rocm-$ROCM/bin/
# Copy MIOpen database files for gfx10xx, gfx11xx, and gfx12xx only (RDNA2/RDNA3/RDNA4)
COPY --from=rocm /opt/rocm-$ROCM/share/miopen/db/*gfx10* /opt/rocm-$ROCM/share/miopen/db/
COPY --from=rocm /opt/rocm-$ROCM/share/miopen/db/*gfx11* /opt/rocm-$ROCM/share/miopen/db/
COPY --from=rocm /opt/rocm-$ROCM/share/miopen/db/*gfx12* /opt/rocm-$ROCM/share/miopen/db/
# Copy rocBLAS library files for gfx10xx, gfx11xx, and gfx12xx only
COPY --from=rocm /opt/rocm-$ROCM/lib/rocblas/library/*gfx10* /opt/rocm-$ROCM/lib/rocblas/library/
COPY --from=rocm /opt/rocm-$ROCM/lib/rocblas/library/*gfx11* /opt/rocm-$ROCM/lib/rocblas/library/
COPY --from=rocm /opt/rocm-$ROCM/lib/rocblas/library/*gfx12* /opt/rocm-$ROCM/lib/rocblas/library/
COPY --from=rocm /opt/rocm-dist/ /
####################################################################### #######################################################################
FROM deps-prelim AS rocm-prelim-hsa-override0 FROM deps-prelim AS rocm-prelim-hsa-override0
ENV MIGRAPHX_DISABLE_MIOPEN_FUSION=1 ENV MIGRAPHX_DISABLE_MIOPEN_FUSION=1
ENV MIGRAPHX_DISABLE_SCHEDULE_PASS=1 ENV MIGRAPHX_DISABLE_SCHEDULE_PASS=1
ENV MIGRAPHX_DISABLE_REDUCE_FUSION=1 ENV MIGRAPHX_DISABLE_REDUCE_FUSION=1
ENV MIGRAPHX_ENABLE_HIPRTC_WORKAROUNDS=1
ENV MIOPEN_CUSTOM_CACHE_DIR=/config/model_cache/migraphx ENV MIOPEN_CUSTOM_CACHE_DIR=/config/model_cache/migraphx
ENV MIOPEN_USER_DB_PATH=/config/model_cache/migraphx ENV MIOPEN_USER_DB_PATH=/config/model_cache/migraphx
ENV AMD_COMGR_CACHE=1 ENV AMD_COMGR_CACHE=1
ENV AMD_COMGR_CACHE_DIR=/config/model_cache/migraphx ENV AMD_COMGR_CACHE_DIR=/config/model_cache/migraphx
COPY --from=rocm-dist / /
RUN ldconfig
####################################################################### #######################################################################
FROM rocm-prelim-hsa-override0 as rocm-prelim-hsa-override1 FROM rocm-prelim-hsa-override0 as rocm-prelim-hsa-override1
-63
View File
@@ -1,63 +0,0 @@
#!/bin/bash
# Remove the parts of AMD's ROCm wheels that MIGraphX inference never loads:
# compilers, debuggers, profilers, management tools, and the math libraries
# (sparse, solver, FFT, random, RCCL, image processing) outside MIGraphX's
# dependencies. Runs in the same layer as the install so the files never
# reach the image.
set -euo pipefail
site_packages="$1"
core="${site_packages}/_rocm_sdk_core"
libraries="${site_packages}/_rocm_sdk_libraries"
# rocminfo stays for checking that the GPU is visible in the container
find "${core}/bin" -mindepth 1 -maxdepth 1 \
! -name rocminfo ! -name rocm_agent_enumerator -exec rm -rf {} +
# hiprtc compiles MIGraphX's kernels through comgr, which needs libLLVM,
# libclang-cpp, the clang resource headers, and the amdgcn device libraries
rm -rf "${core}/lib/llvm/bin" "${core}/lib/llvm/include"
find "${core}/lib/llvm/lib" -mindepth 1 -maxdepth 1 \
! -name 'libLLVM.so*' ! -name 'libclang-cpp.so*' ! -name clang \
-exec rm -rf {} +
rm -rf "${core}"/lib/llvm/lib/clang/*/lib
rm -rf \
"${core}/lib/host-math" \
"${core}"/lib/python3* \
"${core}/lib/rocprofiler-sdk" \
"${core}"/lib/librocprofiler-sdk*.so* \
"${core}"/lib/librocprof-trace-decoder.so* \
"${core}"/lib/librocm-dbgapi.so* \
"${core}"/lib/librocm-debug-agent.so* \
"${core}"/lib/libamd_smi*.so* \
"${core}"/lib/libgoamdsmi_shim64.so* \
"${core}"/lib/librocdecode.so* \
"${core}"/lib/librocjpeg.so* \
"${core}"/lib/librocdxg.so* \
"${core}"/lib/librocjitsu_*.so* \
"${core}"/lib/libhsa_hotswap_rocjitsu.so* \
"${core}"/lib/*.a
rm -rf \
"${libraries}"/lib/librpp.so* \
"${libraries}"/lib/librocsparse.so* \
"${libraries}"/lib/libhipsparse.so* \
"${libraries}"/lib/libhipsparselt.so* \
"${libraries}/lib/rocsparse" \
"${libraries}"/lib/librocsolver.so* \
"${libraries}"/lib/libhipsolver.so* \
"${libraries}"/lib/libhipblas.so* \
"${libraries}"/lib/librocrand.so* \
"${libraries}"/lib/libhiprand.so* \
"${libraries}"/lib/librocfft.so* \
"${libraries}"/lib/libhipfft.so* \
"${libraries}"/lib/libhipfftw.so* \
"${libraries}"/lib/librccl.so* \
"${libraries}"/.kpack/sparse_lib_*.kpack \
"${libraries}"/.kpack/solver_lib_*.kpack \
"${libraries}"/.kpack/rand_lib_*.kpack \
"${libraries}"/.kpack/fft_lib_*.kpack \
"${libraries}"/.kpack/rccl_lib_*.kpack
+1 -19
View File
@@ -1,19 +1 @@
# ROCm 10.1.0, MIGraphX, and its ONNX Runtime plugin provider from AMD's wheels. onnxruntime-migraphx @ https://github.com/NickM-27/frigate-onnxruntime-rocm/releases/download/v7.2.3-1/onnxruntime_migraphx-1.24.4-cp311-cp311-linux_x86_64.whl
onnxruntime == 1.29.*
onnxruntime-ep-migraphx @ https://stable.repo.amd.com/rocm/onnxruntime/whl-next/onnxruntime-ep-migraphx/onnxruntime_ep_migraphx-1.0.0%2Brocm10.1.0-cp311-cp311-manylinux_2_28_x86_64.whl
migraphx-libs @ https://stable.repo.amd.com/rocm/migraphx/whl-next/migraphx-libs/migraphx_libs-2.18.0%2Brocm10.1.0-py3-none-manylinux_2_28_x86_64.whl
rocm-sdk-core @ https://stable.repo.amd.com/rocm/whl-next/rocm-sdk-core/rocm_sdk_core-10.1.0-py3-none-linux_x86_64.whl
rocm-sdk-libraries @ https://stable.repo.amd.com/rocm/whl-next/rocm-sdk-libraries/rocm_sdk_libraries-10.1.0-py3-none-linux_x86_64.whl
# Kernels for each supported GPU target. MIGraphX has no gfx1031-gfx1036 kernels,
# so those GPUs run as gfx1030 with HSA_OVERRIDE_GFX_VERSION=10.3.0
rocm-sdk-device-gfx1030 @ https://stable.repo.amd.com/rocm/whl-next/rocm-sdk-device-gfx1030/rocm_sdk_device_gfx1030-10.1.0-py3-none-linux_x86_64.whl
rocm-sdk-device-gfx1100 @ https://stable.repo.amd.com/rocm/whl-next/rocm-sdk-device-gfx1100/rocm_sdk_device_gfx1100-10.1.0-py3-none-linux_x86_64.whl
rocm-sdk-device-gfx1101 @ https://stable.repo.amd.com/rocm/whl-next/rocm-sdk-device-gfx1101/rocm_sdk_device_gfx1101-10.1.0-py3-none-linux_x86_64.whl
rocm-sdk-device-gfx1102 @ https://stable.repo.amd.com/rocm/whl-next/rocm-sdk-device-gfx1102/rocm_sdk_device_gfx1102-10.1.0-py3-none-linux_x86_64.whl
rocm-sdk-device-gfx1103 @ https://stable.repo.amd.com/rocm/whl-next/rocm-sdk-device-gfx1103/rocm_sdk_device_gfx1103-10.1.0-py3-none-linux_x86_64.whl
rocm-sdk-device-gfx1150 @ https://stable.repo.amd.com/rocm/whl-next/rocm-sdk-device-gfx1150/rocm_sdk_device_gfx1150-10.1.0-py3-none-linux_x86_64.whl
rocm-sdk-device-gfx1151 @ https://stable.repo.amd.com/rocm/whl-next/rocm-sdk-device-gfx1151/rocm_sdk_device_gfx1151-10.1.0-py3-none-linux_x86_64.whl
rocm-sdk-device-gfx1152 @ https://stable.repo.amd.com/rocm/whl-next/rocm-sdk-device-gfx1152/rocm_sdk_device_gfx1152-10.1.0-py3-none-linux_x86_64.whl
rocm-sdk-device-gfx1153 @ https://stable.repo.amd.com/rocm/whl-next/rocm-sdk-device-gfx1153/rocm_sdk_device_gfx1153-10.1.0-py3-none-linux_x86_64.whl
rocm-sdk-device-gfx1200 @ https://stable.repo.amd.com/rocm/whl-next/rocm-sdk-device-gfx1200/rocm_sdk_device_gfx1200-10.1.0-py3-none-linux_x86_64.whl
rocm-sdk-device-gfx1201 @ https://stable.repo.amd.com/rocm/whl-next/rocm-sdk-device-gfx1201/rocm_sdk_device_gfx1201-10.1.0-py3-none-linux_x86_64.whl
+11
View File
@@ -1,3 +1,6 @@
variable "ROCM" {
default = "7.2.3"
}
variable "HSA_OVERRIDE_GFX_VERSION" { variable "HSA_OVERRIDE_GFX_VERSION" {
default = "" default = ""
} }
@@ -5,6 +8,12 @@ variable "HSA_OVERRIDE" {
default = "1" default = "1"
} }
target wget {
dockerfile = "docker/main/Dockerfile"
platforms = ["linux/amd64"]
target = "wget"
}
target deps { target deps {
dockerfile = "docker/main/Dockerfile" dockerfile = "docker/main/Dockerfile"
platforms = ["linux/amd64"] platforms = ["linux/amd64"]
@@ -21,10 +30,12 @@ target rocm {
dockerfile = "docker/rocm/Dockerfile" dockerfile = "docker/rocm/Dockerfile"
contexts = { contexts = {
deps = "target:deps", deps = "target:deps",
wget = "target:wget",
rootfs = "target:rootfs" rootfs = "target:rootfs"
} }
platforms = ["linux/amd64"] platforms = ["linux/amd64"]
args = { args = {
ROCM = ROCM,
HSA_OVERRIDE_GFX_VERSION = HSA_OVERRIDE_GFX_VERSION, HSA_OVERRIDE_GFX_VERSION = HSA_OVERRIDE_GFX_VERSION,
HSA_OVERRIDE = HSA_OVERRIDE HSA_OVERRIDE = HSA_OVERRIDE
} }
+18 -26
View File
@@ -394,22 +394,6 @@ Note that the labelmap uses a subset of the complete COCO label set that has onl
Support for AMD GPUs is provided using the [ONNX detector](#onnx). In order to utilize the AMD GPU for object detection use a frigate docker image with `-rocm` suffix, for example `ghcr.io/blakeblackshear/frigate:stable-rocm`. Support for AMD GPUs is provided using the [ONNX detector](#onnx). In order to utilize the AMD GPU for object detection use a frigate docker image with `-rocm` suffix, for example `ghcr.io/blakeblackshear/frigate:stable-rocm`.
### Supported GPUs {#supported-gpus-rocm}
The `-rocm` image includes ROCm support for these GPU chipsets, which work without any chipset override:
| Chipset | Example GPUs |
| -------------------------------------------- | ------------------------------------------------------------------------ |
| gfx1030 (RDNA2) | Radeon RX 6800 / 6800 XT / 6900 XT / 6950 XT, Radeon PRO W6800 |
| gfx1100, gfx1101, gfx1102 (RDNA3) | Radeon RX 7900 / 7800 / 7700 / 7600 series, Radeon PRO W7000 series |
| gfx1103 (RDNA3 integrated) | Radeon 780M / 760M in Ryzen 7040, 8040, 8000G, and 200 series processors |
| gfx1150, gfx1151, gfx1152, gfx1153 (RDNA3.5) | Integrated GPUs in Ryzen AI 300 / 400 series and Ryzen AI Max processors |
| gfx1200, gfx1201 (RDNA4) | Radeon RX 9060 / 9070 series, Radeon AI PRO R9700 |
Other RDNA2 GPUs (gfx1031 through gfx1036), such as the Radeon RX 6700 / 6600 / 6500 series and the Radeon 680M / 660M integrated GPUs, run on the gfx1030 support by [overriding the chipset](#docker-settings-for-overriding-the-gpu-chipset) to `10.3.0`.
Older GPUs, including RDNA1 (Radeon RX 5000 series) and Vega based dedicated and integrated GPUs, are not supported.
### Docker settings for GPU access ### Docker settings for GPU access
ROCm needs access to the `/dev/kfd` and `/dev/dri` devices. When docker or frigate is not run under root then also `video` (and possibly `render` and `ssl/_ssl`) groups should be added. ROCm needs access to the `/dev/kfd` and `/dev/dri` devices. When docker or frigate is not run under root then also `video` (and possibly `render` and `ssl/_ssl`) groups should be added.
@@ -436,10 +420,19 @@ For reference on recommended settings see [running ROCm/pytorch in Docker](https
### Docker settings for overriding the GPU chipset ### Docker settings for overriding the GPU chipset
A GPU whose chipset isn't in the [supported GPUs](#supported-gpus-rocm) list can run on the support for a closely related chipset by setting the `HSA_OVERRIDE_GFX_VERSION` environment variable. RDNA2 GPUs other than gfx1030 (gfx1031 through gfx1036) need this, using `10.3.0`: Your GPU might work just fine without any special configuration but in many cases they need manual settings. AMD/ROCm software stack comes with a limited set of GPU drivers and for newer or missing models you will have to override the chipset version to an older/generic version to get things working.
Also AMD/ROCm does not "officially" support integrated GPUs. It still does work with most of them just fine but requires special settings. One has to configure the `HSA_OVERRIDE_GFX_VERSION` environment variable. See the [ROCm bug report](https://github.com/ROCm/ROCm/issues/1743) for context and examples.
For the rocm frigate build there is some automatic detection:
- gfx1031 -> 10.3.0
- gfx1103 -> 11.0.0
If you have something else you might need to override the `HSA_OVERRIDE_GFX_VERSION` at Docker launch. Suppose the version you want is `10.0.0`, then you should configure it from command line as:
```bash ```bash
$ docker run -e HSA_OVERRIDE_GFX_VERSION=10.3.0 \ $ docker run -e HSA_OVERRIDE_GFX_VERSION=10.0.0 \
... ...
``` ```
@@ -450,17 +443,16 @@ services:
frigate: frigate:
... ...
environment: environment:
HSA_OVERRIDE_GFX_VERSION: "10.3.0" HSA_OVERRIDE_GFX_VERSION: "10.0.0"
``` ```
GPUs in the supported list don't need an override. An override that was set for the Radeon 780M / 760M (gfx1103) on an earlier version of Frigate is no longer needed and can be removed. Figuring out what version you need can be complicated as you can't tell the chipset name and driver from the AMD brand name.
You can't tell the chipset from the AMD brand name, so check it in the container: - first make sure that rocm environment is running properly by running `/opt/rocm/bin/rocminfo` in the frigate container -- it should list both the CPU and the GPU with their properties
- find the chipset version you have (gfxNNN) from the output of the `rocminfo` (see below)
- first make sure that the ROCm environment is running properly by running `/opt/rocm/bin/rocminfo` in the Frigate container. It should list both the CPU and the GPU with their properties - use a search engine to query what `HSA_OVERRIDE_GFX_VERSION` you need for the given gfx name ("gfxNNN ROCm HSA_OVERRIDE_GFX_VERSION")
- find the chipset version you have (gfxNNN) from the output of `rocminfo` (see below) - override the `HSA_OVERRIDE_GFX_VERSION` with relevant value
- if the chipset isn't in the supported list, set `HSA_OVERRIDE_GFX_VERSION` to the closest supported chipset, written as major.minor.stepping (for example gfx1030 is `10.3.0`) - if things are not working check the frigate docker logs
- if things are not working check the Frigate docker logs
#### Figuring out if AMD/ROCm is working and found your GPU #### Figuring out if AMD/ROCm is working and found your GPU
+2 -2
View File
@@ -72,7 +72,7 @@ Frigate supports multiple different detectors that work on different types of ha
**AMD** **AMD**
- [ROCm](#rocm---amd-gpu): ROCm can run on AMD discrete and recent integrated GPUs to provide efficient object detection - [ROCm](#rocm---amd-gpu): ROCm can run on AMD Discrete GPUs to provide efficient object detection
- [Supports limited model architectures](../../configuration/object_detectors#amdrocm-gpu-detector) - [Supports limited model architectures](../../configuration/object_detectors#amdrocm-gpu-detector)
- Runs best on discrete AMD GPUs - Runs best on discrete AMD GPUs
- <CommunityBadge /> [XDNA2 (Ryzen AI)](#amd-xdna2): AMD XDNA2 NPU (sub-watt power AI/ML processor separate to the GPU) inside Strix and other "AI" branded AMD platforms - <CommunityBadge /> [XDNA2 (Ryzen AI)](#amd-xdna2): AMD XDNA2 NPU (sub-watt power AI/ML processor separate to the GPU) inside Strix and other "AI" branded AMD platforms
@@ -240,7 +240,7 @@ Apple Silicon can not run within a container, so a ZMQ proxy is utilized to comm
### ROCm - AMD GPU ### ROCm - AMD GPU
With the [ROCm](../configuration/object_detectors.md#amdrocm-gpu-detector) detector Frigate can take advantage of many AMD discrete and integrated GPUs, see the [supported GPUs](../configuration/object_detectors.md#supported-gpus-rocm). With the [ROCm](../configuration/object_detectors.md#amdrocm-gpu-detector) detector Frigate can take advantage of many discrete AMD GPUs.
| Name | YOLOv9 Inference Time | YOLO-NAS Inference Time | RF-DETR Inference Time | | Name | YOLOv9 Inference Time | YOLO-NAS Inference Time | RF-DETR Inference Time |
| -------------- | --------------------------- | ------------------------- | ---------------------- | | -------------- | --------------------------- | ------------------------- | ---------------------- |
+12 -11
View File
@@ -1,18 +1,12 @@
from pydantic import Field, model_validator from pydantic import Field, field_validator, model_validator
from frigate.util.config import parse_listen_address
from .base import FrigateBaseModel from .base import FrigateBaseModel
__all__ = ["IPv6Config", "ListenConfig", "NetworkingConfig"] __all__ = ["IPv6Config", "ListenConfig", "NetworkingConfig"]
def parse_listen_port(value: int | str) -> int:
"""Return the port number from a bare port or an "address:port" value."""
if isinstance(value, str):
return int(value.split(":")[-1])
return value
class IPv6Config(FrigateBaseModel): class IPv6Config(FrigateBaseModel):
enabled: bool = Field( enabled: bool = Field(
default=False, default=False,
@@ -33,13 +27,20 @@ class ListenConfig(FrigateBaseModel):
description="External listening port for Frigate (default 8971).", description="External listening port for Frigate (default 8971).",
) )
# Runs before pydantic coerces a bool to an int port, which
# get_nginx_settings.py rejects when it reads the raw YAML
@field_validator("internal", "external", mode="before")
@classmethod
def validate_listen(cls, value: int | str) -> int | str:
return parse_listen_address(value)[0]
@property @property
def internal_port(self) -> int: def internal_port(self) -> int:
return parse_listen_port(self.internal) return parse_listen_address(self.internal)[1]
@property @property
def external_port(self) -> int: def external_port(self) -> int:
return parse_listen_port(self.external) return parse_listen_address(self.external)[1]
@model_validator(mode="after") @model_validator(mode="after")
def validate_distinct_ports(self) -> "ListenConfig": def validate_distinct_ports(self) -> "ListenConfig":
+11 -71
View File
@@ -10,7 +10,6 @@ from typing import Any
import numpy as np import numpy as np
import onnxruntime as ort import onnxruntime as ort
from frigate.const import MODEL_CACHE_DIR
from frigate.util.model import get_ort_providers from frigate.util.model import get_ort_providers
from frigate.util.rknn_converter import auto_convert_model, is_rknn_compatible from frigate.util.rknn_converter import auto_convert_model, is_rknn_compatible
@@ -56,9 +55,6 @@ _PROVIDER_LABELS = {
LIGHTER_ANE_EP_NAME = "LighterANE" LIGHTER_ANE_EP_NAME = "LighterANE"
LIGHTER_ANE_LIBRARY = "/usr/lib/lighter/liblighter_ane_ep.so" 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]: def get_lighter_ane_devices() -> list[Any]:
"""Get the Neural Engine devices lighter's provider offers, registering it once. """Get the Neural Engine devices lighter's provider offers, registering it once.
@@ -87,42 +83,6 @@ def get_lighter_ane_devices() -> list[Any]:
return devices 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: def is_arm64_platform() -> bool:
"""Check if we're running on an ARM platform.""" """Check if we're running on an ARM platform."""
machine = platform.machine().lower() machine = platform.machine().lower()
@@ -796,41 +756,12 @@ def get_optimized_runner(
ONNXModelRunner(session, model_type=model_type), 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) providers, options = get_ort_providers(device == "CPU", device, **kwargs)
if providers[0] == "CPUExecutionProvider": if providers[0] == "CPUExecutionProvider":
# In the default image, ONNXRuntime is used so we will only get CPUExecutionProvider # In the default image, ONNXRuntime is used so we will only get CPUExecutionProvider
# In other images we will get CUDA which is preferred over OpenVINO, while # In other images we will get CUDA / ROCm which are preferred over OpenVINO
# MIGraphX in the ROCm image is already handled above # There is currently no way to prioritize OpenVINO over CUDA / ROCm in these images
# There is currently no way to prioritize OpenVINO over CUDA in these images
if device != "CPU" and is_openvino_gpu_npu_available(): if device != "CPU" and is_openvino_gpu_npu_available():
return _record_runner( return _record_runner(
model_path, model_path,
@@ -859,6 +790,15 @@ 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( return _record_runner(
model_path, model_path,
model_type, model_type,
+67
View File
@@ -36,6 +36,73 @@ class TestListenConfig(unittest.TestCase):
self.assertEqual(listen.internal_port, 5001) self.assertEqual(listen.internal_port, 5001)
self.assertEqual(listen.external_port, 8971) self.assertEqual(listen.external_port, 8971)
def test_valid_listen_values_are_rebuilt(self):
cases = {
5000: 5000,
"5000": 5000,
"127.0.0.1:5000": "127.0.0.1:5000",
"0.0.0.0:5000": "0.0.0.0:5000",
"[::1]:5000": "[::1]:5000",
"[::]:5000": "[::]:5000",
"[2001:DB8:0:0::1]:5000": "[2001:db8::1]:5000",
" 5000 ": 5000,
"5000\n": 5000,
" 127.0.0.1:5000 ": "127.0.0.1:5000",
"localhost:5000": "localhost:5000",
"frigate-host.lan:5000": "frigate-host.lan:5000",
"*:5000": "*:5000",
}
for value, expected in cases.items():
with self.subTest(value=value):
listen = ListenConfig(internal=value)
self.assertEqual(listen.internal, expected)
self.assertEqual(listen.internal_port, 5000)
def test_invalid_listen_values_rejected(self):
cases = [
"5000; location /x/ { alias /; autoindex on; } #:5000",
"127.0.0.1:5000; include /etc/passwd",
"5000 ssl",
"5000;",
"::1:5000",
"[127.0.0.1]:5000",
"[fe80::1%eth0]:5000",
"local host:5000",
"localhost;:5000",
"local{host}:5000",
"host#:5000",
"'localhost':5000",
'"localhost":5000',
"-host:5000",
"host-:5000",
"host..lan:5000",
"**:5000",
"*.lan:5000",
"unix:/tmp/sock:5000",
"5000 5001",
":5000",
"127.0.0.1:",
"127.0.0.1:+5000",
0,
65536,
-1,
]
for value in cases:
with self.subTest(value=value):
with self.assertRaises(ValidationError):
ListenConfig(internal=value)
def test_bool_listen_value_rejected(self):
# get_nginx_settings.py sees the raw bool and falls back to the default,
# so accepting it as port 1 would split nginx and the auth port check
for value in (True, False):
with self.subTest(value=value):
with self.assertRaises(ValidationError):
ListenConfig(internal=value)
if __name__ == "__main__": if __name__ == "__main__":
unittest.main(verbosity=2) unittest.main(verbosity=2)
+1 -215
View File
@@ -1,13 +1,12 @@
"""Tests for the device each model runner reports after loading.""" """Tests for the device each model runner reports after loading."""
import os import os
import sys
import tempfile import tempfile
import threading import threading
import unittest import unittest
from unittest.mock import MagicMock, patch from unittest.mock import MagicMock, patch
from frigate.detectors import detection_runners, hardware from frigate.detectors import detection_runners
from frigate.detectors.detection_runners import ( from frigate.detectors.detection_runners import (
CudaGraphRunner, CudaGraphRunner,
ONNXModelRunner, ONNXModelRunner,
@@ -18,7 +17,6 @@ from frigate.detectors.detection_runners import (
record_loaded_device, record_loaded_device,
snapshot_loaded_devices, snapshot_loaded_devices,
) )
from frigate.util import model as model_util
class TestRunnerDeviceName(unittest.TestCase): class TestRunnerDeviceName(unittest.TestCase):
@@ -319,215 +317,3 @@ class TestLighterANE(unittest.TestCase):
get_optimized_runner("/models/arcface.onnx", "CPU", "arcface") get_optimized_runner("/models/arcface.onnx", "CPU", "arcface")
ane.assert_not_called() 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}])
+45
View File
@@ -1,8 +1,10 @@
"""configuration utils.""" """configuration utils."""
import asyncio import asyncio
import ipaddress
import logging import logging
import os import os
import re
import shutil import shutil
from functools import cache from functools import cache
from typing import Any from typing import Any
@@ -126,6 +128,49 @@ def redact_credential(obj: dict[str, Any], key: str) -> None:
obj.pop(key, None) obj.pop(key, None)
LISTEN_HOSTNAME = re.compile(
r"[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?"
r"(?:\.[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?)*"
)
def parse_listen_address(value: int | str) -> tuple[int | str, int]:
"""Validate a networking.listen value and rebuild it from its parsed parts.
Accepts a port, "host:port", "*:port", or "[IPv6]:port", where host is an
IPv4 address or an RFC 1123 hostname. nginx receives the rebuilt value
verbatim, so anything else raises ValueError.
Returns:
The normalized value (an int for a bare port) and the port
"""
if isinstance(value, bool) or not isinstance(value, int | str):
raise ValueError(f"invalid listen value: {value!r}")
host, sep, port_text = str(value).strip().rpartition(":")
if not re.fullmatch(r"[0-9]{1,5}", port_text) or not (1 <= int(port_text) <= 65535):
raise ValueError(f"invalid listen port: {value!r}")
port = int(port_text)
if not sep:
return port, port
if host.startswith("[") and host.endswith("]"):
address = ipaddress.IPv6Address(host[1:-1])
if address.scope_id:
raise ValueError(f"listen address must not have a scope ID: {value!r}")
return f"[{address}]:{port}", port
if host != "*" and not LISTEN_HOSTNAME.fullmatch(host):
raise ValueError(f"invalid listen address: {value!r}")
return f"{host}:{port}", port
def find_config_file() -> str: def find_config_file() -> str:
"""Return the path of the config file to use. """Return the path of the config file to use.
+9 -3
View File
@@ -385,9 +385,15 @@ def get_ort_providers(
} }
) )
elif provider == "MIGraphXExecutionProvider": elif provider == "MIGraphXExecutionProvider":
# MIGraphX is a plugin provider, which ONNX Runtime can't create by migraphx_cache_dir = os.path.join(MODEL_CACHE_DIR, "migraphx")
# name, so get_optimized_runner selects it through its devices os.makedirs(migraphx_cache_dir, exist_ok=True)
continue
providers.append(provider)
options.append(
{
"migraphx_model_cache_dir": migraphx_cache_dir,
}
)
elif provider == "CPUExecutionProvider": elif provider == "CPUExecutionProvider":
providers.append(provider) providers.append(provider)
options.append( options.append(
+426 -305
View File
File diff suppressed because it is too large Load Diff
+23 -22
View File
@@ -23,28 +23,28 @@
"dependencies": { "dependencies": {
"@cycjimmy/jsmpeg-player": "^6.1.2", "@cycjimmy/jsmpeg-player": "^6.1.2",
"@hookform/resolvers": "^5.9.1", "@hookform/resolvers": "^5.9.1",
"@radix-ui/react-alert-dialog": "^1.2.1", "@radix-ui/react-alert-dialog": "^1.1.24",
"@radix-ui/react-aspect-ratio": "^1.2.0", "@radix-ui/react-aspect-ratio": "^1.1.16",
"@radix-ui/react-checkbox": "^1.4.1", "@radix-ui/react-checkbox": "^1.3.12",
"@radix-ui/react-collapsible": "^1.2.1", "@radix-ui/react-collapsible": "^1.1.21",
"@radix-ui/react-context-menu": "^2.4.1", "@radix-ui/react-context-menu": "^2.3.8",
"@radix-ui/react-dialog": "^1.2.0", "@radix-ui/react-dialog": "^1.2.0",
"@radix-ui/react-dropdown-menu": "^2.2.1", "@radix-ui/react-dropdown-menu": "^2.1.25",
"@radix-ui/react-hover-card": "^1.2.1", "@radix-ui/react-hover-card": "^1.1.24",
"@radix-ui/react-label": "^2.1.17", "@radix-ui/react-label": "^2.1.16",
"@radix-ui/react-popover": "^1.3.1", "@radix-ui/react-popover": "^1.2.0",
"@radix-ui/react-progress": "^1.2.1", "@radix-ui/react-progress": "^1.1.17",
"@radix-ui/react-radio-group": "^1.5.1", "@radix-ui/react-radio-group": "^1.4.8",
"@radix-ui/react-scroll-area": "^1.4.0", "@radix-ui/react-scroll-area": "^1.3.0",
"@radix-ui/react-select": "^2.4.1", "@radix-ui/react-select": "^2.3.8",
"@radix-ui/react-separator": "^1.2.0", "@radix-ui/react-separator": "^1.1.16",
"@radix-ui/react-slider": "^1.6.1", "@radix-ui/react-slider": "^1.5.0",
"@radix-ui/react-slot": "1.5.0", "@radix-ui/react-slot": "1.4.0",
"@radix-ui/react-switch": "^1.4.1", "@radix-ui/react-switch": "^1.3.8",
"@radix-ui/react-tabs": "^1.2.1", "@radix-ui/react-tabs": "^1.1.22",
"@radix-ui/react-toggle": "^1.2.1", "@radix-ui/react-toggle": "^1.1.19",
"@radix-ui/react-toggle-group": "^1.2.1", "@radix-ui/react-toggle-group": "^1.1.20",
"@radix-ui/react-tooltip": "^1.4.1", "@radix-ui/react-tooltip": "^1.3.0",
"@rjsf/core": "^6.10.1", "@rjsf/core": "^6.10.1",
"@rjsf/shadcn": "^6.10.1", "@rjsf/shadcn": "^6.10.1",
"@rjsf/utils": "^6.10.1", "@rjsf/utils": "^6.10.1",
@@ -127,6 +127,7 @@
}, },
"overrides": { "overrides": {
"@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-compose-refs": "1.1.2",
"@radix-ui/react-slot": "1.5.0" "@radix-ui/react-popper": "1.2.8",
"@radix-ui/react-slot": "1.4.0"
} }
} }
@@ -210,7 +210,7 @@ export function VideoPreview({
}, [playerRef]); }, [playerRef]);
const onProgressHover = useCallback( const onProgressHover = useCallback(
(event: React.MouseEvent<HTMLElement>) => { (event: React.MouseEvent<HTMLDivElement>) => {
if (!sliderRef.current) { if (!sliderRef.current) {
return; return;
} }
@@ -401,7 +401,7 @@ export function InProgressPreview({
); );
const onProgressHover = useCallback( const onProgressHover = useCallback(
(event: React.MouseEvent<HTMLElement>) => { (event: React.MouseEvent<HTMLDivElement>) => {
if (!sliderRef.current || !previewFrames) { if (!sliderRef.current || !previewFrames) {
return; return;
} }