mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-10-18 07:29:45 +00:00
feat(deepx): enforce model_format requirement when ppu is enabled and add integrity checks for driver installation
This commit is contained in:
parent
581b422b2a
commit
8085b24d2f
@ -28,6 +28,13 @@
|
||||
set -euo pipefail
|
||||
|
||||
driver_version="v2.6.0"
|
||||
# Commit the tag above resolves to, pinned so a later retag or repoint of
|
||||
# v2.6.0 upstream can't silently swap in different source between review and
|
||||
# build, before it's compiled and installed as root. DEEPX doesn't sign tags
|
||||
# or publish checksums for this repo, so an immutable commit is the strongest
|
||||
# verification available. Update both together when bumping driver_version:
|
||||
# git ls-remote https://github.com/DEEPX-AI/dx_rt_npu_linux_driver.git "refs/tags/<version>"
|
||||
driver_commit="7074748e7104f470b02f517583abba652b3f05fa"
|
||||
firmware_version="v2.7.4"
|
||||
|
||||
sudo apt-get update
|
||||
@ -39,9 +46,25 @@ if ! lspci -d 1ff4: | grep -q .; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
git clone --depth 1 --branch "${driver_version}" \
|
||||
https://github.com/DEEPX-AI/dx_rt_npu_linux_driver.git
|
||||
cd dx_rt_npu_linux_driver/modules
|
||||
# Fetch the pinned commit directly (GitHub allows fetching a public repo by
|
||||
# SHA, not just by branch/tag) rather than cloning "${driver_version}", so
|
||||
# driver_commit above is what actually determines the source, not a ref that
|
||||
# could move after this script was reviewed.
|
||||
mkdir dx_rt_npu_linux_driver
|
||||
cd dx_rt_npu_linux_driver
|
||||
git init -q
|
||||
git remote add origin https://github.com/DEEPX-AI/dx_rt_npu_linux_driver.git
|
||||
git fetch --depth 1 origin "${driver_commit}"
|
||||
git checkout -q FETCH_HEAD
|
||||
|
||||
fetched_commit=$(git rev-parse HEAD)
|
||||
if [[ "${fetched_commit}" != "${driver_commit}" ]]; then
|
||||
echo "Fetched commit ${fetched_commit} does not match pinned driver_commit ${driver_commit}."
|
||||
echo "Refusing to build unverified driver source."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cd modules
|
||||
|
||||
# --reload unloads any running modules and loads the newly built ones, so the
|
||||
# NPU is usable without a reboot.
|
||||
|
||||
@ -20,6 +20,18 @@ set -euxo pipefail
|
||||
# docs/docs/frigate/installation.md in sync.
|
||||
dxrt_version="v3.4.2"
|
||||
|
||||
# SHA-256 of the .deb pinned above, one per architecture Frigate builds for.
|
||||
# DEEPX doesn't publish checksums or sign this release, so these are computed
|
||||
# once from a vetted download and hardcoded here as the actual integrity
|
||||
# check: a download that doesn't match one of these hashes is rejected before
|
||||
# it reaches apt-get, regardless of whether the mismatch came from the v3.4.2
|
||||
# tag being retargeted or the file being replaced upstream. Recompute both
|
||||
# (`sha256sum libdxrt-bin_*.deb`) when bumping dxrt_version.
|
||||
declare -A dxrt_sha256=(
|
||||
[amd64]="da782deac10acb1c997b52ea6d0160c06f112a68ad5e25035cfc8e16de0a8bdc"
|
||||
[arm64]="161223058938c54efadc581b33499210194da58e55cd5ade332e692ca0d649c2"
|
||||
)
|
||||
|
||||
# The release directory and the package filename both use the bare version,
|
||||
# while the git tag carries a "v".
|
||||
dxrt_release="${dxrt_version#v}"
|
||||
@ -33,6 +45,20 @@ deb_file="/tmp/libdxrt-bin_${dxrt_release}_${deb_arch}.deb"
|
||||
wget -qO "${deb_file}" \
|
||||
"https://raw.githubusercontent.com/DEEPX-AI/dx_rt/${dxrt_version}/release/${dxrt_release}/libdxrt-bin_${dxrt_release}_${deb_arch}.deb"
|
||||
|
||||
expected_sha256="${dxrt_sha256[${deb_arch}]:-}"
|
||||
if [[ -z "${expected_sha256}" ]]; then
|
||||
echo "No pinned SHA-256 for architecture ${deb_arch}; refusing to install an unverified package."
|
||||
exit 1
|
||||
fi
|
||||
actual_sha256=$(sha256sum "${deb_file}" | cut -d' ' -f1)
|
||||
if [[ "${actual_sha256}" != "${expected_sha256}" ]]; then
|
||||
echo "SHA-256 mismatch for ${deb_file}:"
|
||||
echo " expected ${expected_sha256}"
|
||||
echo " actual ${actual_sha256}"
|
||||
echo "Refusing to install a package that doesn't match the pinned checksum."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
apt-get -qq update
|
||||
apt-get -qq install -y "${deb_file}"
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
@ -707,6 +707,8 @@ detectors:
|
||||
|
||||
`ppu` and `model_format` are independent: `ppu` says whether candidate selection already happened on the NPU, and `model_format` says how to read the boxes that come back. Both must match how the model was compiled.
|
||||
|
||||
`model_format` is required whenever `ppu: true` is set. A PPU record has the same fixed-width layout for every variant, so unlike the raw output path there is no shape to infer the layout from — leaving it unset is rejected at startup rather than risking a silent anchor-free/anchor-based mismatch.
|
||||
|
||||
DX-COM cannot compile `yolov10` or `yolov26` models with PPU support, since their heads already produce final detections. Combining either with `ppu: true` is rejected at startup.
|
||||
|
||||
#### Multiple NPUs
|
||||
|
||||
@ -441,7 +441,8 @@ class DeepxDetectorConfig(BaseDetectorConfig):
|
||||
description="Selects how the model's output is decoded. Leave unset to "
|
||||
"infer the layout from the output shape, which cannot tell an "
|
||||
"anchor-based head from an anchor-free one and is only reliable for "
|
||||
"YOLOv8 and newer.",
|
||||
"YOLOv8 and newer. Required when ppu is true, since a PPU record's "
|
||||
"layout is identical for every variant and cannot be inferred.",
|
||||
)
|
||||
score_threshold: float = Field(
|
||||
default=0.25,
|
||||
@ -464,6 +465,25 @@ class DeepxDetectorConfig(BaseDetectorConfig):
|
||||
|
||||
return self
|
||||
|
||||
@model_validator(mode="after")
|
||||
def validate_ppu_requires_model_format(self):
|
||||
"""Reject an unset model_format when ppu is enabled.
|
||||
|
||||
A PPU record is the same fixed-width layout for every variant, so
|
||||
unlike the raw output path there is no shape to infer the layout
|
||||
from. Leaving model_format unset here used to silently default to
|
||||
the anchor-based decoder, which corrupts detections from anchor-free
|
||||
PPU models (e.g. YOLOv8) instead of failing loudly.
|
||||
"""
|
||||
if self.ppu and self.model_format is None:
|
||||
raise ValueError(
|
||||
"model_format must be set when ppu is true. The PPU output "
|
||||
"layout can't be inferred, and guessing wrong silently "
|
||||
"corrupts detections."
|
||||
)
|
||||
|
||||
return self
|
||||
|
||||
|
||||
class Deepx(DetectionApi):
|
||||
type_key = DETECTOR_KEY
|
||||
@ -517,9 +537,9 @@ class Deepx(DetectionApi):
|
||||
def decode(self, outputs: list[np.ndarray]) -> np.ndarray:
|
||||
"""Decode raw or PPU output according to the configured YOLO variant."""
|
||||
if self.ppu:
|
||||
# An unset model_format keeps the original anchor-based behaviour,
|
||||
# which is the only layout this detector decoded before the option
|
||||
# existed.
|
||||
# Config validation guarantees model_format is set to an anchor
|
||||
# or anchor-free format whenever ppu is true, since PPU records
|
||||
# give no other way to tell the two apart.
|
||||
if self.model_format in ANCHOR_FREE_FORMATS:
|
||||
return decode_yolo_ppu_anchor_free(
|
||||
outputs,
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
import unittest
|
||||
|
||||
import numpy as np
|
||||
from pydantic import ValidationError
|
||||
|
||||
from frigate.detectors.plugins.deepx import (
|
||||
ANCHOR_FORMATS,
|
||||
@ -10,6 +11,7 @@ from frigate.detectors.plugins.deepx import (
|
||||
NMS_IN_HEAD_FORMATS,
|
||||
PPU_ANCHORS,
|
||||
PPU_RECORD_SIZE,
|
||||
DeepxDetectorConfig,
|
||||
ModelFormatEnum,
|
||||
decode_yolo_ppu_anchor,
|
||||
decode_yolo_ppu_anchor_free,
|
||||
@ -160,6 +162,29 @@ class TestDeepxModelFormats(unittest.TestCase):
|
||||
self.assertFalse(ANCHOR_FREE_FORMATS & NMS_IN_HEAD_FORMATS)
|
||||
|
||||
|
||||
class TestDeepxDetectorConfig(unittest.TestCase):
|
||||
def test_ppu_without_model_format_is_rejected(self):
|
||||
"""PPU records are fixed-width regardless of variant, so an unset
|
||||
model_format can't be inferred and must not silently fall back to the
|
||||
anchor decoder."""
|
||||
with self.assertRaises(ValidationError):
|
||||
DeepxDetectorConfig(type="deepx", ppu=True)
|
||||
|
||||
def test_ppu_with_anchor_free_model_format_is_accepted(self):
|
||||
config = DeepxDetectorConfig(
|
||||
type="deepx", ppu=True, model_format=ModelFormatEnum.yolov8
|
||||
)
|
||||
|
||||
self.assertEqual(config.model_format, ModelFormatEnum.yolov8)
|
||||
|
||||
def test_raw_without_model_format_is_still_accepted(self):
|
||||
# the raw path falls back to shape-based inference, so this remains
|
||||
# optional when ppu is not set
|
||||
config = DeepxDetectorConfig(type="deepx")
|
||||
|
||||
self.assertIsNone(config.model_format)
|
||||
|
||||
|
||||
class TestDeepxPpuAnchorFreeDecode(unittest.TestCase):
|
||||
def test_reads_the_box_bytes_as_pixel_geometry(self):
|
||||
"""An anchor-free PPU record holds cx, cy, w, h directly in pixels."""
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user