From a4f077b128e96de679a777565a7cf0ce630ff72f Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Thu, 4 Jun 2026 13:48:58 -0500 Subject: [PATCH] Miscellaneous fixes (#23394) * serialize OpenVINO inference per process to prevent concurrent-inference segfault * clean up * add max scaling meta to login page * add more detect section field messages * fix icon layout in settings field messages * tweak edit icon color --- frigate/detectors/detection_runners.py | 29 +++--- web/login.html | 2 +- web/public/locales/en/views/settings.json | 6 +- .../config-form/ConfigFieldMessage.tsx | 8 +- .../config-form/ConfigMessageBanner.tsx | 6 +- .../config-form/section-configs/detect.ts | 91 +++++++++++++++++-- .../components/filter/CameraGroupSelector.tsx | 4 +- 7 files changed, 114 insertions(+), 32 deletions(-) diff --git a/frigate/detectors/detection_runners.py b/frigate/detectors/detection_runners.py index c89cd8b44f..ee465b3d51 100644 --- a/frigate/detectors/detection_runners.py +++ b/frigate/detectors/detection_runners.py @@ -15,6 +15,9 @@ from frigate.util.rknn_converter import auto_convert_model, is_rknn_compatible logger = logging.getLogger(__name__) +# Process-wide lock serializing all OpenVINO compile/inference calls +_OPENVINO_LOCK = threading.Lock() + def is_arm64_platform() -> bool: """Check if we're running on an ARM platform.""" @@ -326,19 +329,17 @@ class OpenVINOModelRunner(BaseModelRunner): except Exception as e: logger.debug(f"NPU_TURBO not supported by driver: {e}") - # Compile model - self.compiled_model = self.ov_core.compile_model( - model=model_path, device_name=device - ) + # Compile model under the shared lock + with _OPENVINO_LOCK: + self.compiled_model = self.ov_core.compile_model( + model=model_path, device_name=device + ) + + # Create reusable inference request + self.infer_request = self.compiled_model.create_infer_request() - # Create reusable inference request - self.infer_request = self.compiled_model.create_infer_request() self.input_tensor: ov.Tensor | None = None - # Thread lock to prevent concurrent inference (needed for JinaV2 which shares - # one runner between text and vision embeddings called from different threads) - self._inference_lock = threading.Lock() - if not self.complex_model: try: input_shape = self.compiled_model.inputs[0].get_shape() @@ -382,9 +383,11 @@ class OpenVINOModelRunner(BaseModelRunner): Returns: List of output tensors """ - # Lock prevents concurrent access to infer_request - # Needed for JinaV2: genai thread (text) + embeddings thread (vision) - with self._inference_lock: + # Shared lock serializes inference across every OpenVINO runner in this + # process — both the shared-runner JinaV2 case (genai text thread + + # embeddings vision thread) and distinct runners running on separate + # threads (e.g. the ArcFace face-model build vs the LPR detector). + with _OPENVINO_LOCK: from frigate.embeddings.types import EnrichmentModelTypeEnum if self.model_type in [EnrichmentModelTypeEnum.arcface.value]: diff --git a/web/login.html b/web/login.html index fc0fb551e3..f83243d023 100644 --- a/web/login.html +++ b/web/login.html @@ -3,7 +3,7 @@ - + Frigate ; + return ; case "warning": - return ; + return ; case "error": - return ; + return ; default: - return ; + return ; } } diff --git a/web/src/components/config-form/ConfigMessageBanner.tsx b/web/src/components/config-form/ConfigMessageBanner.tsx index 919d337c15..8e701947a0 100644 --- a/web/src/components/config-form/ConfigMessageBanner.tsx +++ b/web/src/components/config-form/ConfigMessageBanner.tsx @@ -18,11 +18,11 @@ const severityVariantMap: Record< function SeverityIcon({ severity }: { severity: MessageSeverity }) { switch (severity) { case "info": - return ; + return ; case "warning": - return ; + return ; case "error": - return ; + return ; } } diff --git a/web/src/components/config-form/section-configs/detect.ts b/web/src/components/config-form/section-configs/detect.ts index 74d170edc6..9176c49a74 100644 --- a/web/src/components/config-form/section-configs/detect.ts +++ b/web/src/components/config-form/section-configs/detect.ts @@ -11,8 +11,12 @@ const detect: SectionConfigOverrides = { condition: (ctx) => ctx.level === "camera" && ctx.formData?.enabled === false, }, + ], + fieldMessages: [ { key: "detect-resolution-not-multiple-of-four", + field: "width", + position: "before", messageKey: "configMessages.detect.resolutionShouldBeMultipleOfFour", severity: "warning", condition: (ctx) => { @@ -23,8 +27,59 @@ const detect: SectionConfigOverrides = { return isEvenButNotFour(width) || isEvenButNotFour(height); }, }, + { + key: "detect-global-resolution-multiple-cameras", + field: "width", + position: "before", + messageKey: "configMessages.detect.globalResolutionMultipleCameras", + severity: "info", + condition: (ctx) => { + if (ctx.level !== "global") return false; + const width = ctx.formData?.width as number | null | undefined; + const height = ctx.formData?.height as number | null | undefined; + if (typeof width !== "number" && typeof height !== "number") { + return false; + } + const cameraCount = Object.keys(ctx.fullConfig?.cameras ?? {}).length; + return cameraCount > 1; + }, + }, + { + key: "detect-resolution-high", + field: "width", + position: "before", + messageKey: "configMessages.detect.resolutionHigh", + severity: "warning", + condition: (ctx) => { + const width = ctx.formData?.width as number | null | undefined; + const height = ctx.formData?.height as number | null | undefined; + if (typeof width !== "number" || typeof height !== "number") { + return false; + } + return Math.min(width, height) > 1080; + }, + }, + { + key: "detect-square-resolution", + field: "width", + position: "before", + messageKey: "configMessages.detect.squareResolution", + severity: "warning", + condition: (ctx) => { + const width = ctx.formData?.width as number | null | undefined; + const height = ctx.formData?.height as number | null | undefined; + return ( + typeof width === "number" && + typeof height === "number" && + width > 0 && + width === height + ); + }, + }, { key: "detect-aspect-ratio-mismatch", + field: "width", + position: "before", messageKey: "configMessages.detect.aspectRatioMismatch", severity: "warning", condition: (ctx) => { @@ -55,8 +110,6 @@ const detect: SectionConfigOverrides = { return Math.abs(newRatio - savedRatio) > 0.01; }, }, - ], - fieldMessages: [ { key: "fps-greater-than-five", field: "fps", @@ -71,6 +124,31 @@ const detect: SectionConfigOverrides = { return detectFps != null && streamFps != null && detectFps > 5; }, }, + { + key: "max-frames-set", + field: "stationary.max_frames", + messageKey: "configMessages.detect.maxFramesSet", + severity: "warning", + position: "after", + condition: (ctx) => { + const stationary = ctx.formData?.stationary as + | { + max_frames?: { + default?: number | null; + objects?: Record; + } | null; + } + | null + | undefined; + const maxFrames = stationary?.max_frames; + if (!maxFrames) return false; + return ( + typeof maxFrames.default === "number" || + (maxFrames.objects != null && + Object.keys(maxFrames.objects).length > 0) + ); + }, + }, ], fieldOrder: [ "enabled", @@ -81,9 +159,9 @@ const detect: SectionConfigOverrides = { "max_disappeared", "annotation_offset", "stationary", - "interval", - "threshold", - "max_frames", + "stationary.interval", + "stationary.threshold", + "stationary.max_frames", ], restartRequired: [], fieldGroups: { @@ -129,9 +207,6 @@ const detect: SectionConfigOverrides = { "max_disappeared", "annotation_offset", "stationary", - "interval", - "threshold", - "max_frames", ], advancedFields: [], }, diff --git a/web/src/components/filter/CameraGroupSelector.tsx b/web/src/components/filter/CameraGroupSelector.tsx index 8f32846fc1..151eb6d044 100644 --- a/web/src/components/filter/CameraGroupSelector.tsx +++ b/web/src/components/filter/CameraGroupSelector.tsx @@ -283,7 +283,7 @@ export function CameraGroupSelector({ className }: CameraGroupSelectorProps) { afterSelect?.(); }} > - + , ); } @@ -376,7 +376,7 @@ export function CameraGroupSelector({ className }: CameraGroupSelectorProps) { onMouseEnter={() => showTooltip("edit")} onMouseLeave={() => showTooltip(undefined)} > - +