mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-10-18 20:52:48 +03:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
940aaf191a |
@@ -1,4 +1,4 @@
|
||||
ruff == 0.17.0
|
||||
ruff == 0.16.10
|
||||
|
||||
# types
|
||||
types-peewee == 4.0.*
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
numpy
|
||||
openvino >= 2026.4.1
|
||||
openvino >= 2026.2.0
|
||||
|
||||
@@ -23,7 +23,7 @@ pandas == 3.0.*
|
||||
peewee == 4.5.*
|
||||
peewee_migrate == 2.3.*
|
||||
psutil == 7.2.*
|
||||
pydantic == 2.14.*
|
||||
pydantic == 2.13.*
|
||||
git+https://github.com/fbcotter/py3nvml#egg=py3nvml
|
||||
tzdata == 2026.5
|
||||
pyzmq == 27.2.*
|
||||
@@ -42,16 +42,16 @@ opencv-python-headless == 4.11.0.*
|
||||
opencv-contrib-python == 4.11.0.*
|
||||
scipy == 1.17.*
|
||||
# OpenVino & ONNX
|
||||
openvino == 2026.4.*
|
||||
openvino == 2025.4.*
|
||||
onnxruntime == 1.30.*
|
||||
# Embeddings
|
||||
transformers == 5.19.*
|
||||
# Generative AI
|
||||
google-genai == 2.31.*
|
||||
google-genai == 2.29.*
|
||||
ollama == 0.6.*
|
||||
openai == 3.26.*
|
||||
# push notifications
|
||||
py-vapid == 1.9.6
|
||||
py-vapid == 1.9.4
|
||||
pywebpush == 2.5.*
|
||||
# alpr
|
||||
pyclipper == 1.4.*
|
||||
|
||||
@@ -1 +1 @@
|
||||
scikit-build == 0.19.*
|
||||
scikit-build == 0.18.*
|
||||
|
||||
@@ -61,7 +61,7 @@ do
|
||||
;;
|
||||
esac
|
||||
|
||||
liveprint=`openssl s_client -showcerts -connect 127.0.0.1:$listen_external_port </dev/null 2>&1 | openssl x509 -fingerprint 2>&1 | grep -i fingerprint || echo 'failed'`
|
||||
liveprint=`echo | openssl s_client -showcerts -connect 127.0.0.1:$listen_external_port 2>&1 | openssl x509 -fingerprint 2>&1 | grep -i fingerprint || echo 'failed'`
|
||||
|
||||
case "$liveprint" in
|
||||
*Fingerprint*)
|
||||
|
||||
+50
-11
@@ -2,9 +2,32 @@
|
||||
|
||||
# https://askubuntu.com/questions/972516/debian-frontend-environment-variable
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
ARG ROCM=1
|
||||
ARG HSA_OVERRIDE_GFX_VERSION
|
||||
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
|
||||
|
||||
@@ -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 -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 \
|
||||
&& sed -i 's/args.append("setuptools")/args.append("setuptools==77.0.3")/' get-pip.py \
|
||||
&& python3 get-pip.py "pip" --break-system-packages
|
||||
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
|
||||
RUN --mount=type=bind,source=docker/rocm/prune_rocm.sh,target=/deps/prune_rocm.sh \
|
||||
pip3 uninstall -y onnxruntime \
|
||||
&& 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
|
||||
RUN pip3 uninstall -y onnxruntime \
|
||||
&& pip3 install -r /requirements.txt
|
||||
|
||||
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
|
||||
ENV MIGRAPHX_DISABLE_MIOPEN_FUSION=1
|
||||
ENV MIGRAPHX_DISABLE_SCHEDULE_PASS=1
|
||||
ENV MIGRAPHX_DISABLE_REDUCE_FUSION=1
|
||||
ENV MIGRAPHX_ENABLE_HIPRTC_WORKAROUNDS=1
|
||||
ENV MIOPEN_CUSTOM_CACHE_DIR=/config/model_cache/migraphx
|
||||
ENV MIOPEN_USER_DB_PATH=/config/model_cache/migraphx
|
||||
ENV AMD_COMGR_CACHE=1
|
||||
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
|
||||
|
||||
|
||||
@@ -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 +1 @@
|
||||
# ROCm 10.1.0, MIGraphX, and its ONNX Runtime plugin provider from AMD's wheels.
|
||||
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
|
||||
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
|
||||
@@ -1,3 +1,6 @@
|
||||
variable "ROCM" {
|
||||
default = "7.2.3"
|
||||
}
|
||||
variable "HSA_OVERRIDE_GFX_VERSION" {
|
||||
default = ""
|
||||
}
|
||||
@@ -5,6 +8,12 @@ variable "HSA_OVERRIDE" {
|
||||
default = "1"
|
||||
}
|
||||
|
||||
target wget {
|
||||
dockerfile = "docker/main/Dockerfile"
|
||||
platforms = ["linux/amd64"]
|
||||
target = "wget"
|
||||
}
|
||||
|
||||
target deps {
|
||||
dockerfile = "docker/main/Dockerfile"
|
||||
platforms = ["linux/amd64"]
|
||||
@@ -21,10 +30,12 @@ target rocm {
|
||||
dockerfile = "docker/rocm/Dockerfile"
|
||||
contexts = {
|
||||
deps = "target:deps",
|
||||
wget = "target:wget",
|
||||
rootfs = "target:rootfs"
|
||||
}
|
||||
platforms = ["linux/amd64"]
|
||||
args = {
|
||||
ROCM = ROCM,
|
||||
HSA_OVERRIDE_GFX_VERSION = HSA_OVERRIDE_GFX_VERSION,
|
||||
HSA_OVERRIDE = HSA_OVERRIDE
|
||||
}
|
||||
|
||||
@@ -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`.
|
||||
|
||||
### 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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
$ 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:
|
||||
...
|
||||
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 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
|
||||
- find the chipset version you have (gfxNNN) from the output of `rocminfo` (see below)
|
||||
- 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
|
||||
- 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)
|
||||
- use a search engine to query what `HSA_OVERRIDE_GFX_VERSION` you need for the given gfx name ("gfxNNN ROCm HSA_OVERRIDE_GFX_VERSION")
|
||||
- override the `HSA_OVERRIDE_GFX_VERSION` with relevant value
|
||||
- if things are not working check the frigate docker logs
|
||||
|
||||
#### Figuring out if AMD/ROCm is working and found your GPU
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@ Frigate supports multiple different detectors that work on different types of ha
|
||||
|
||||
**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)
|
||||
- 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
|
||||
@@ -240,7 +240,7 @@ Apple Silicon can not run within a container, so a ZMQ proxy is utilized to comm
|
||||
|
||||
### 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 |
|
||||
| -------------- | --------------------------- | ------------------------- | ---------------------- |
|
||||
|
||||
@@ -297,15 +297,10 @@ async def latest_frame(
|
||||
and request.app.frigate_config.birdseye.enabled
|
||||
and request.app.frigate_config.birdseye.restream
|
||||
):
|
||||
frame = frame_processor.get_current_frame(camera_name)
|
||||
|
||||
if frame is None:
|
||||
return JSONResponse(
|
||||
content={"success": False, "message": "Unable to get valid frame"},
|
||||
status_code=500,
|
||||
)
|
||||
|
||||
frame = cv2.cvtColor(frame, cv2.COLOR_YUV2BGR_I420)
|
||||
frame = cv2.cvtColor(
|
||||
frame_processor.get_current_frame(camera_name),
|
||||
cv2.COLOR_YUV2BGR_I420,
|
||||
)
|
||||
|
||||
height = int(params.height or str(frame.shape[0]))
|
||||
width = int(height * frame.shape[1] / frame.shape[0])
|
||||
|
||||
@@ -322,11 +322,7 @@ class Dispatcher:
|
||||
self.audio_activity.expire_all(payload)
|
||||
|
||||
def handle_update_event_description() -> None:
|
||||
event: Event | None = Event.get_or_none(Event.id == payload["id"])
|
||||
|
||||
if event is None:
|
||||
return
|
||||
|
||||
event: Event = Event.get(Event.id == payload["id"])
|
||||
cast(dict, event.data)["description"] = payload["description"]
|
||||
event.save()
|
||||
self.publish(
|
||||
|
||||
@@ -147,7 +147,6 @@ class RecordConfig(FrigateBaseModel):
|
||||
)
|
||||
expire_interval: int = Field(
|
||||
default=60,
|
||||
ge=1,
|
||||
title="Record cleanup interval",
|
||||
description="Minutes between cleanup passes that remove expired recording segments.",
|
||||
)
|
||||
|
||||
@@ -4,7 +4,6 @@ import logging
|
||||
import os
|
||||
import threading
|
||||
import time
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from peewee import DoesNotExist
|
||||
@@ -194,10 +193,9 @@ class AudioTranscriptionPostProcessor(PostProcessorApi):
|
||||
logger.debug("Recognizer not initialized")
|
||||
return None
|
||||
|
||||
# Save audio data to a temporary wav (faster-whisper expects a file)
|
||||
temp_wav = os.path.join(CACHE_DIR, f"temp_audio_{int(time.time())}.wav") # type: ignore[unreachable]
|
||||
|
||||
try:
|
||||
try: # type: ignore[unreachable]
|
||||
# Save audio data to a temporary wav (faster-whisper expects a file)
|
||||
temp_wav = os.path.join(CACHE_DIR, f"temp_audio_{int(time.time())}.wav")
|
||||
with open(temp_wav, "wb") as f:
|
||||
f.write(audio_data)
|
||||
|
||||
@@ -207,6 +205,8 @@ class AudioTranscriptionPostProcessor(PostProcessorApi):
|
||||
beam_size=5,
|
||||
)
|
||||
|
||||
os.remove(temp_wav)
|
||||
|
||||
# Combine all segment texts
|
||||
text = " ".join(segment.text.strip() for segment in segments)
|
||||
if not text:
|
||||
@@ -222,8 +222,6 @@ class AudioTranscriptionPostProcessor(PostProcessorApi):
|
||||
except Exception as e:
|
||||
logger.error(f"Error transcribing audio: {e}")
|
||||
return None
|
||||
finally:
|
||||
Path(temp_wav).unlink(missing_ok=True)
|
||||
|
||||
def _transcription_wrapper(self, event: dict[str, Any]) -> None:
|
||||
"""Wrapper to run transcription and reset running flag when done."""
|
||||
|
||||
@@ -10,7 +10,6 @@ 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
|
||||
|
||||
@@ -56,9 +55,6 @@ _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.
|
||||
@@ -87,42 +83,6 @@ 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()
|
||||
@@ -796,41 +756,12 @@ 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 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
|
||||
# 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
|
||||
if device != "CPU" and is_openvino_gpu_npu_available():
|
||||
return _record_runner(
|
||||
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(
|
||||
model_path,
|
||||
model_type,
|
||||
|
||||
@@ -547,9 +547,13 @@ class EmbeddingMaintainer(threading.Thread):
|
||||
return
|
||||
|
||||
# Create our own thumbnail based on the bounding box and the frame time
|
||||
yuv_frame = self.frame_manager.get(
|
||||
frame_name, camera_config.frame_shape_yuv, frame_time
|
||||
)
|
||||
try:
|
||||
yuv_frame = self.frame_manager.get(
|
||||
frame_name, camera_config.frame_shape_yuv, frame_time
|
||||
)
|
||||
except FileNotFoundError:
|
||||
logger.debug(f"Frame {frame_name} not found for camera {camera}")
|
||||
pass
|
||||
|
||||
if yuv_frame is None:
|
||||
logger.debug(
|
||||
@@ -812,9 +816,12 @@ class EmbeddingMaintainer(threading.Thread):
|
||||
# no active features that use this data
|
||||
return
|
||||
|
||||
yuv_frame = self.frame_manager.get(
|
||||
frame_name, camera_config.frame_shape_yuv, frame_time
|
||||
)
|
||||
try:
|
||||
yuv_frame = self.frame_manager.get(
|
||||
frame_name, camera_config.frame_shape_yuv, frame_time
|
||||
)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
|
||||
if yuv_frame is None:
|
||||
logger.debug(
|
||||
|
||||
@@ -1000,7 +1000,6 @@ class RecordingMaintainer(threading.Thread):
|
||||
logger.error(f"Unable to convert {cache_path} to {file_path}")
|
||||
if p.stderr:
|
||||
logger.error((await p.stderr.read()).decode("ascii"))
|
||||
Path(file_path).unlink(missing_ok=True)
|
||||
return None
|
||||
else:
|
||||
logger.debug(
|
||||
@@ -1018,7 +1017,7 @@ class RecordingMaintainer(threading.Thread):
|
||||
except OSError:
|
||||
segment_size = 0
|
||||
|
||||
self.drop_segment(cache_path)
|
||||
os.remove(cache_path)
|
||||
|
||||
rand_id = "".join(
|
||||
random.choices(string.ascii_lowercase + string.digits, k=6)
|
||||
@@ -1047,8 +1046,10 @@ class RecordingMaintainer(threading.Thread):
|
||||
}
|
||||
except Exception:
|
||||
logger.exception(f"Unable to store recording segment {cache_path}")
|
||||
Path(cache_path).unlink(missing_ok=True)
|
||||
|
||||
self.drop_segment(cache_path)
|
||||
# clear end_time cache
|
||||
self.end_time_cache.pop(cache_path, None)
|
||||
return None
|
||||
|
||||
def run(self) -> None:
|
||||
|
||||
@@ -543,9 +543,12 @@ class ReviewSegmentMaintainer(threading.Thread):
|
||||
if len(objects) <= pending.frame_active_count:
|
||||
return
|
||||
|
||||
yuv_frame = self.frame_manager.get(
|
||||
frame_name, camera_config.frame_shape_yuv, frame_time
|
||||
)
|
||||
try:
|
||||
yuv_frame = self.frame_manager.get(
|
||||
frame_name, camera_config.frame_shape_yuv, frame_time
|
||||
)
|
||||
except FileNotFoundError:
|
||||
return
|
||||
|
||||
if yuv_frame is None:
|
||||
logger.debug(f"Failed to get frame {frame_name} from SHM")
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import datetime
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
# Mock complex imports before importing maintainer, saving originals so we can
|
||||
@@ -20,7 +17,7 @@ for name in _MOCKED_MODULES:
|
||||
|
||||
# Now import the class under test
|
||||
from frigate.config import FrigateConfig, RetainModeEnum # noqa: E402
|
||||
from frigate.record.maintainer import RecordingMaintainer, SegmentInfo # noqa: E402
|
||||
from frigate.record.maintainer import RecordingMaintainer # noqa: E402
|
||||
|
||||
# Restore original modules (or remove mock if there was no original)
|
||||
for name, orig in _originals.items():
|
||||
@@ -243,86 +240,5 @@ class TestMaintainer(unittest.IsolatedAsyncioTestCase):
|
||||
)
|
||||
|
||||
|
||||
class TestMoveSegment(unittest.IsolatedAsyncioTestCase):
|
||||
def setUp(self):
|
||||
self.tmp = tempfile.TemporaryDirectory()
|
||||
self.record_dir = os.path.join(self.tmp.name, "recordings")
|
||||
self.cache_path = os.path.join(
|
||||
self.tmp.name, "test_cam@20260417150000+0000.mp4"
|
||||
)
|
||||
self.dest_path = os.path.join(
|
||||
self.record_dir, "2026-04-17", "15", "test_cam", "00.00.mp4"
|
||||
)
|
||||
Path(self.cache_path).write_bytes(b"segment")
|
||||
|
||||
config = MagicMock(spec=FrigateConfig)
|
||||
config.cameras = {}
|
||||
config.ffmpeg = MagicMock(ffmpeg_path="ffmpeg")
|
||||
self.maintainer = RecordingMaintainer(config, MagicMock())
|
||||
self.maintainer.end_time_cache = {self.cache_path: MagicMock()}
|
||||
|
||||
def tearDown(self):
|
||||
self.tmp.cleanup()
|
||||
|
||||
async def _move(self, returncode: int = 0) -> tuple[dict | None, AsyncMock]:
|
||||
def fake_exec(*args, **kwargs):
|
||||
# ffmpeg leaves its output behind even when it fails partway
|
||||
Path(args[-1]).write_bytes(b"remuxed")
|
||||
process = MagicMock()
|
||||
process.wait = AsyncMock()
|
||||
process.returncode = returncode
|
||||
process.stderr = None
|
||||
return process
|
||||
|
||||
start = datetime.datetime(2026, 4, 17, 15, 0, 0, tzinfo=datetime.UTC)
|
||||
exec_mock = AsyncMock(side_effect=fake_exec)
|
||||
|
||||
with (
|
||||
patch("frigate.record.maintainer.RECORD_DIR", self.record_dir),
|
||||
patch("frigate.record.maintainer.chown_to_runtime"),
|
||||
patch(
|
||||
"frigate.record.maintainer.asyncio.create_subprocess_exec", exec_mock
|
||||
),
|
||||
):
|
||||
result = await self.maintainer.move_segment(
|
||||
"test_cam",
|
||||
"main",
|
||||
start,
|
||||
start + datetime.timedelta(seconds=10),
|
||||
10.0,
|
||||
self.cache_path,
|
||||
SegmentInfo(0, 0, 0, 0),
|
||||
)
|
||||
|
||||
return result, exec_mock
|
||||
|
||||
async def test_successful_move_clears_cache_entry(self):
|
||||
result, _ = await self._move()
|
||||
|
||||
self.assertIsNotNone(result)
|
||||
self.assertTrue(os.path.exists(self.dest_path))
|
||||
self.assertFalse(os.path.exists(self.cache_path))
|
||||
self.assertNotIn(self.cache_path, self.maintainer.end_time_cache)
|
||||
|
||||
async def test_existing_destination_drops_cache_file(self):
|
||||
os.makedirs(os.path.dirname(self.dest_path))
|
||||
Path(self.dest_path).write_bytes(b"existing")
|
||||
|
||||
result, exec_mock = await self._move()
|
||||
|
||||
self.assertIsNone(result)
|
||||
exec_mock.assert_not_awaited()
|
||||
self.assertFalse(os.path.exists(self.cache_path))
|
||||
self.assertNotIn(self.cache_path, self.maintainer.end_time_cache)
|
||||
|
||||
async def test_failed_move_removes_partial_output_for_retry(self):
|
||||
result, _ = await self._move(returncode=1)
|
||||
|
||||
self.assertIsNone(result)
|
||||
self.assertFalse(os.path.exists(self.dest_path))
|
||||
self.assertTrue(os.path.exists(self.cache_path))
|
||||
self.assertIn(self.cache_path, self.maintainer.end_time_cache)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
"""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, hardware
|
||||
from frigate.detectors import detection_runners
|
||||
from frigate.detectors.detection_runners import (
|
||||
CudaGraphRunner,
|
||||
ONNXModelRunner,
|
||||
@@ -18,7 +17,6 @@ from frigate.detectors.detection_runners import (
|
||||
record_loaded_device,
|
||||
snapshot_loaded_devices,
|
||||
)
|
||||
from frigate.util import model as model_util
|
||||
|
||||
|
||||
class TestRunnerDeviceName(unittest.TestCase):
|
||||
@@ -319,215 +317,3 @@ 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}])
|
||||
|
||||
@@ -41,7 +41,6 @@ from frigate.const import (
|
||||
)
|
||||
from frigate.events.types import EventStateEnum, EventTypeEnum
|
||||
from frigate.models import Event, ReviewSegment, Timeline
|
||||
from frigate.output.birdseye import get_canvas_shape
|
||||
from frigate.ptz.autotrack import PtzAutoTracker
|
||||
from frigate.track.tracked_object import TrackedObject
|
||||
from frigate.util.image import SharedMemoryFrameManager
|
||||
@@ -72,14 +71,6 @@ class TrackedObjectProcessor(threading.Thread):
|
||||
self.camera_states: dict[str, CameraState] = {}
|
||||
self.camera_states_lock = threading.Lock()
|
||||
self.frame_manager = SharedMemoryFrameManager()
|
||||
self.birdseye_yuv_shape: tuple[int, int] | None = None
|
||||
|
||||
if config.birdseye.enabled and config.birdseye.restream:
|
||||
birdseye_width, birdseye_height = get_canvas_shape(
|
||||
config.birdseye.width, config.birdseye.height
|
||||
)
|
||||
self.birdseye_yuv_shape = (birdseye_height * 3 // 2, birdseye_width)
|
||||
|
||||
self.last_motion_detected: dict[str, float] = {}
|
||||
self.ptz_autotracker_thread = ptz_autotracker_thread
|
||||
|
||||
@@ -396,10 +387,10 @@ class TrackedObjectProcessor(threading.Thread):
|
||||
self, camera: str, draw_options: dict[str, Any] = {}
|
||||
) -> np.ndarray | None:
|
||||
if camera == "birdseye":
|
||||
if self.birdseye_yuv_shape is None:
|
||||
return None
|
||||
|
||||
return self.frame_manager.get("birdseye", self.birdseye_yuv_shape)
|
||||
return self.frame_manager.get(
|
||||
"birdseye",
|
||||
(self.config.birdseye.height * 3 // 2, self.config.birdseye.width),
|
||||
)
|
||||
|
||||
camera_state = self.get_camera_state(camera)
|
||||
|
||||
|
||||
@@ -385,9 +385,15 @@ def get_ort_providers(
|
||||
}
|
||||
)
|
||||
elif provider == "MIGraphXExecutionProvider":
|
||||
# MIGraphX is a plugin provider, which ONNX Runtime can't create by
|
||||
# name, so get_optimized_runner selects it through its devices
|
||||
continue
|
||||
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,
|
||||
}
|
||||
)
|
||||
elif provider == "CPUExecutionProvider":
|
||||
providers.append(provider)
|
||||
options.append(
|
||||
|
||||
@@ -559,14 +559,9 @@ class CameraWatchdog(threading.Thread):
|
||||
|
||||
self._send_roles_offline(p["roles"], now)
|
||||
|
||||
if can_restart:
|
||||
p["process"] = start_or_restart_ffmpeg(
|
||||
p["cmd"],
|
||||
self.logger,
|
||||
p["logpipe"],
|
||||
ffmpeg_process=p["process"],
|
||||
)
|
||||
last_restart_time = now
|
||||
p["process"] = start_or_restart_ffmpeg(
|
||||
p["cmd"], self.logger, p["logpipe"], ffmpeg_process=p["process"]
|
||||
)
|
||||
|
||||
if self.detect_process_records_sub and self.config.record.stream_enabled(
|
||||
STREAM_TYPE_SUB
|
||||
|
||||
File diff suppressed because one or more lines are too long
Generated
+1018
-1449
File diff suppressed because it is too large
Load Diff
+26
-25
@@ -23,28 +23,28 @@
|
||||
"dependencies": {
|
||||
"@cycjimmy/jsmpeg-player": "^6.1.2",
|
||||
"@hookform/resolvers": "^5.9.1",
|
||||
"@radix-ui/react-alert-dialog": "^1.2.1",
|
||||
"@radix-ui/react-aspect-ratio": "^1.2.0",
|
||||
"@radix-ui/react-checkbox": "^1.4.1",
|
||||
"@radix-ui/react-collapsible": "^1.2.1",
|
||||
"@radix-ui/react-context-menu": "^2.4.1",
|
||||
"@radix-ui/react-alert-dialog": "^1.1.24",
|
||||
"@radix-ui/react-aspect-ratio": "^1.1.16",
|
||||
"@radix-ui/react-checkbox": "^1.3.12",
|
||||
"@radix-ui/react-collapsible": "^1.1.21",
|
||||
"@radix-ui/react-context-menu": "^2.3.8",
|
||||
"@radix-ui/react-dialog": "^1.2.0",
|
||||
"@radix-ui/react-dropdown-menu": "^2.2.1",
|
||||
"@radix-ui/react-hover-card": "^1.2.1",
|
||||
"@radix-ui/react-label": "^2.1.17",
|
||||
"@radix-ui/react-popover": "^1.3.1",
|
||||
"@radix-ui/react-progress": "^1.2.1",
|
||||
"@radix-ui/react-radio-group": "^1.5.1",
|
||||
"@radix-ui/react-scroll-area": "^1.4.0",
|
||||
"@radix-ui/react-select": "^2.4.1",
|
||||
"@radix-ui/react-separator": "^1.2.0",
|
||||
"@radix-ui/react-slider": "^1.6.1",
|
||||
"@radix-ui/react-slot": "1.5.0",
|
||||
"@radix-ui/react-switch": "^1.4.1",
|
||||
"@radix-ui/react-tabs": "^1.2.1",
|
||||
"@radix-ui/react-toggle": "^1.2.1",
|
||||
"@radix-ui/react-toggle-group": "^1.2.1",
|
||||
"@radix-ui/react-tooltip": "^1.4.1",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.25",
|
||||
"@radix-ui/react-hover-card": "^1.1.24",
|
||||
"@radix-ui/react-label": "^2.1.16",
|
||||
"@radix-ui/react-popover": "^1.2.0",
|
||||
"@radix-ui/react-progress": "^1.1.17",
|
||||
"@radix-ui/react-radio-group": "^1.4.8",
|
||||
"@radix-ui/react-scroll-area": "^1.3.0",
|
||||
"@radix-ui/react-select": "^2.3.8",
|
||||
"@radix-ui/react-separator": "^1.1.16",
|
||||
"@radix-ui/react-slider": "^1.5.0",
|
||||
"@radix-ui/react-slot": "1.4.0",
|
||||
"@radix-ui/react-switch": "^1.3.8",
|
||||
"@radix-ui/react-tabs": "^1.1.22",
|
||||
"@radix-ui/react-toggle": "^1.1.19",
|
||||
"@radix-ui/react-toggle-group": "^1.1.20",
|
||||
"@radix-ui/react-tooltip": "^1.3.0",
|
||||
"@rjsf/core": "^6.10.1",
|
||||
"@rjsf/shadcn": "^6.10.1",
|
||||
"@rjsf/utils": "^6.10.1",
|
||||
@@ -89,7 +89,7 @@
|
||||
"sonner": "^2.0.8",
|
||||
"swr": "^2.5.1",
|
||||
"tailwind-merge": "^2.4.0",
|
||||
"tailwind-scrollbar": "^3.1.0",
|
||||
"tailwind-scrollbar": "^4.0.2",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"use-long-press": "^3.3.0",
|
||||
"vaul": "^1.1.2",
|
||||
@@ -114,19 +114,20 @@
|
||||
"eslint-plugin-react-hooks": "^7.1.1",
|
||||
"eslint-plugin-react-refresh": "^0.5.7",
|
||||
"globals": "^17.13.0",
|
||||
"i18next-cli": "^1.5.11",
|
||||
"i18next-cli": "^1.77.0",
|
||||
"monaco-editor": "^0.52.2",
|
||||
"patch-package": "^8.0.1",
|
||||
"postcss": "^8.5.29",
|
||||
"prettier": "^3.9.9",
|
||||
"prettier-plugin-tailwindcss": "^0.8.1",
|
||||
"tailwindcss": "^3.4.19",
|
||||
"tailwindcss": "^4.3.3",
|
||||
"typescript": "^6.0.3",
|
||||
"typescript-eslint": "^8.71.1",
|
||||
"vite": "^8.3.3"
|
||||
},
|
||||
"overrides": {
|
||||
"@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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -753,6 +753,23 @@ function MSEPlayer({
|
||||
videoRef.current.volume = volume;
|
||||
}, [volume, videoRef]);
|
||||
|
||||
// ensure we disconnect for slower connections
|
||||
|
||||
useEffect(() => {
|
||||
if (wsRef.current?.readyState === WebSocket.OPEN && !playbackEnabled) {
|
||||
if (bufferTimeout) {
|
||||
clearTimeout(bufferTimeout);
|
||||
setBufferTimeout(undefined);
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
if (!playbackEnabled) onDisconnect();
|
||||
}, 10000);
|
||||
}
|
||||
// we know that these deps are correct
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [playbackEnabled]);
|
||||
|
||||
// stats and health samples
|
||||
|
||||
const onHealthSampleRef = useRef(onHealthSample);
|
||||
|
||||
@@ -210,7 +210,7 @@ export function VideoPreview({
|
||||
}, [playerRef]);
|
||||
|
||||
const onProgressHover = useCallback(
|
||||
(event: React.MouseEvent<HTMLElement>) => {
|
||||
(event: React.MouseEvent<HTMLDivElement>) => {
|
||||
if (!sliderRef.current) {
|
||||
return;
|
||||
}
|
||||
@@ -401,7 +401,7 @@ export function InProgressPreview({
|
||||
);
|
||||
|
||||
const onProgressHover = useCallback(
|
||||
(event: React.MouseEvent<HTMLElement>) => {
|
||||
(event: React.MouseEvent<HTMLDivElement>) => {
|
||||
if (!sliderRef.current || !previewFrames) {
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user