diff --git a/app.py b/app.py index ce814c0..af431c8 100644 --- a/app.py +++ b/app.py @@ -210,7 +210,8 @@ async def remove_background( normalized_color = _normalize_screen_color(screen_color) normalized_seg = _normalize_seg_model(seg_model) or _manager.default_seg_model normalized_cc = _normalize_cross_check(cross_check) - effective_cc = normalized_cc if normalized_cc is not None else _manager.cross_check_enabled + # Honors the lane lock (a locked cpu-fast box reports/runs "off" even on cross_check=on). + effective_cc = _manager.effective_cross_check(normalized_cc) rgb = _decode_image(await file.read()) diff --git a/bgfilter/service.py b/bgfilter/service.py index 29bb967..f7cb2cf 100644 --- a/bgfilter/service.py +++ b/bgfilter/service.py @@ -66,6 +66,20 @@ class PipelineManager: def cross_check_enabled(self) -> bool: return self._base.cross_check.enabled + def effective_cross_check(self, requested: bool | None) -> bool: + """Resolve the cross-check state for a request, honoring the lane lock. + + When ``cross_check.lock`` is set (e.g. the cpu-fast lane) the base default + is authoritative and a per-request override is ignored -- so a locked + off-lane never loads the HR-matting model or risks an OOM, and never errors + on a ``cross_check=on`` request. Otherwise the request value wins when + given, else the base default applies. + """ + base = self._base.cross_check + if base.lock: + return base.enabled + return requested if requested is not None else base.enabled + def _get_runner(self) -> ViTMatteRunner: if self._runner is None: with self._load_lock: @@ -135,8 +149,9 @@ class PipelineManager: self._base, screen_color=screen_color, seg_backend=seg_model, - # None = keep the service default; True/False = per-request override. - cross_check=cross_check, + # Honor the lane lock: a locked lane ignores the per-request override + # (a cpu-fast box never runs cross-check, even on cross_check=on). + cross_check=self.effective_cross_check(cross_check), ) runner = ( self._get_runner() diff --git a/bgfilter/settings.py b/bgfilter/settings.py index cedde26..ee28fa1 100644 --- a/bgfilter/settings.py +++ b/bgfilter/settings.py @@ -80,6 +80,11 @@ class CrossCheckSettings: # that chroma, segmentation and the primary matte all read as foreground; # already-soft wisps and dark hair are exempt by construction. enabled: bool = True + # Lock the cross-check state to ``enabled`` and ignore per-request overrides. + # A memory-constrained lane (e.g. cpu-fast) sets ``enabled: false, lock: true`` + # so a request with ``cross_check=on`` is silently honoured as off instead of + # loading the HR-matting model and risking an OOM. No error is raised. + lock: bool = False model_name: str = "ZhengPeng7/BiRefNet_HR-matting" input_size: int = 2048 # Reuse the cross-check forward as the segmentation mask: one HR-matting diff --git a/configs/cpu-fast.yaml b/configs/cpu-fast.yaml index 0ee16f6..bbbe838 100644 --- a/configs/cpu-fast.yaml +++ b/configs/cpu-fast.yaml @@ -29,3 +29,5 @@ segmentation: cross_check: enabled: false # no second-opinion HR-matting pass (the big cost/mem sink) + lock: true # ignore per-request cross_check=on so a request can't + # force the HR-matting load and OOM this low-memory lane