diff --git a/DEPLOY.md b/DEPLOY.md index bc0ff92..02e135b 100644 --- a/DEPLOY.md +++ b/DEPLOY.md @@ -158,20 +158,20 @@ Override the base directory with `BGFILTER_WEIGHTS_DIR`. `models/` is gitignored ### Cross-check veto (optional, off by default in the service) -The pipeline can run a second, high-resolution matting model -(`ZhengPeng7/BiRefNet_HR-matting`, ~425 MB) as a "second opinion" that only clears -background residue trapped between hair strands. In the **HTTP service it is OFF by -default** and lazy-loaded, so you don't need this model unless you turn it on: +The pipeline runs a second, high-resolution matting model +(`ZhengPeng7/BiRefNet_HR-matting`, ~425 MB) as a "second opinion" that clears +background residue trapped between hair strands. It is **ON by default** (both CLI +and HTTP service) and, with reuse-as-seg, that same forward doubles as the +segmentation mask on the birefnet backend — so it replaces the primary seg model +rather than adding to it. **You must provision this model** (Option A or B above): -- Enable service-wide with `BGFILTER_CROSS_CHECK=1`, or per request with the - `cross_check=true` form field. -- ⚠️ **On an offline server (`HF_HUB_OFFLINE=1`) you must provision - `BiRefNet_HR-matting` first** (Option A or B above) — otherwise the first - cross-check request fails, because it cannot download the model at runtime. -- It adds one extra 2048-px inference (~35 s/image on CPU), roughly doubling latency. -- Note the config default (`configs/default.yaml`) has `cross_check.enabled: true`, - which the **CLI** honours; only the HTTP service forces it off unless - `BGFILTER_CROSS_CHECK=1`. +- ⚠️ **On an offline server (`HF_HUB_OFFLINE=1`) it must be present before the + first request** — otherwise every default request fails, since it cannot + download the model at runtime. +- Turn it off service-wide with `BGFILTER_CROSS_CHECK=0`, or override per request + with the `cross_check` form field (`true`/`false`). +- The service preloads it at startup with the rest of the warm set; on the + birefnet backend the redundant primary segmenter is skipped. ### Downloading behind a firewall (CN networks) @@ -196,7 +196,7 @@ redirect back to `huggingface.co`, which recent `huggingface_hub` rejects with | `BGFILTER_DEVICE` | `cpu` | Overrides **both** model and segmentation device (`cuda` for GPU) | | `BGFILTER_MAX_IMAGE_PIXELS` | `4194304` | Reject larger inputs with `413` (~4 MP) | | `BGFILTER_PRELOAD` | `1` | Load default models at startup (first request isn't cold) | -| `BGFILTER_CROSS_CHECK` | `0` | Enable the cross-check veto (needs `BiRefNet_HR-matting`; ~2× latency) | +| `BGFILTER_CROSS_CHECK` | config (`on`) | Force the cross-check veto on/off (`0` disables; needs `BiRefNet_HR-matting`) | | `BGFILTER_WEIGHTS_DIR` | `models` | Where local weight folders are looked up (Option B) | | `HF_HOME` | — | HF cache location (Option A) | | `HF_HUB_OFFLINE` / `TRANSFORMERS_OFFLINE` | — | Set to `1` in production once weights are cached | diff --git a/DEPLOY_ZH.md b/DEPLOY_ZH.md index 847544e..f93cfd0 100644 --- a/DEPLOY_ZH.md +++ b/DEPLOY_ZH.md @@ -147,18 +147,17 @@ hf download ZhengPeng7/BiRefNet_HR-matting --local-dir models/BiRefNet_HR- 用 `BGFILTER_WEIGHTS_DIR` 可改基目录。`models/` 已被 gitignore。 -### cross-check 否决(可选,服务里默认关闭) +### cross-check 否决(默认开启) -管线可以再跑一个高分辨率 matting 模型(`ZhengPeng7/BiRefNet_HR-matting`,约 425 MB) -作为"第二意见",专门清理发丝间残留的背景色。在 **HTTP 服务里默认是关的**、且懒加载, -所以不开启就不需要这个模型: +管线会再跑一个高分辨率 matting 模型(`ZhengPeng7/BiRefNet_HR-matting`,约 425 MB) +作为"第二意见",清理发丝间残留的背景色。**CLI 和 HTTP 服务都默认开启**,而且开了 +reuse-as-seg 之后,在 birefnet 后端这同一次前向还兼作分割掩码——它是**替换**了 primary +分割模型,而不是额外叠加。因此**必须备好这个模型**(上面方案 A 或 B): -- 服务级开启:`BGFILTER_CROSS_CHECK=1`;或按请求:表单字段 `cross_check=true`。 -- ⚠️ **离线服务器(`HF_HUB_OFFLINE=1`)必须先备好 `BiRefNet_HR-matting`**(上面方案 A 或 B), - 否则第一个 cross-check 请求会失败——运行时它无法联网下载模型。 -- 每张图多一次 2048 分辨率推理(CPU 上约 35s/张),延迟大约翻倍。 -- 注意:配置默认(`configs/default.yaml`)里 `cross_check.enabled: true`,**CLI 会遵循它**; - 只有 HTTP 服务强制关闭,除非设了 `BGFILTER_CROSS_CHECK=1`。 +- ⚠️ **离线服务器(`HF_HUB_OFFLINE=1`)必须在第一个请求前就备好它**,否则每个默认请求都会 + 失败——运行时它无法联网下载模型。 +- 服务级关闭:`BGFILTER_CROSS_CHECK=0`;或按请求覆盖:表单字段 `cross_check`(`true`/`false`)。 +- 服务启动时会随暖机集一起预载它;在 birefnet 后端,冗余的 primary 分割模型会被跳过。 ### 墙内下载(国内网络) @@ -182,7 +181,7 @@ export HF_HUB_DISABLE_XET=1 # 这些 repo 是 Xet 存储;强 | `BGFILTER_DEVICE` | `cpu` | 同时覆盖 model 和 segmentation 的 device(GPU 用 `cuda`)| | `BGFILTER_MAX_IMAGE_PIXELS` | `4194304` | 超过则返回 `413`(约 4 MP)| | `BGFILTER_PRELOAD` | `1` | 启动时预加载默认模型(首个请求不用冷加载)| -| `BGFILTER_CROSS_CHECK` | `0` | 启用 cross-check 否决(需 `BiRefNet_HR-matting`;延迟约 ×2)| +| `BGFILTER_CROSS_CHECK` | 配置(`on`)| 强制开/关 cross-check 否决(`0` 关闭;需 `BiRefNet_HR-matting`)| | `BGFILTER_WEIGHTS_DIR` | `models` | 本地权重文件夹的查找目录(方案 B)| | `HF_HOME` | — | HF 缓存位置(方案 A)| | `HF_HUB_OFFLINE` / `TRANSFORMERS_OFFLINE` | — | 权重缓存好后,生产设为 `1` | diff --git a/README.md b/README.md index 4d01f9f..94efd49 100644 --- a/README.md +++ b/README.md @@ -217,13 +217,13 @@ Config comes from `BGFILTER_CONFIG` (defaults to `configs/default.yaml` when pre caps input size (default ~4MP → `413`); `BGFILTER_PRELOAD=1` loads the default models at startup. Run a single worker (`--workers 1`) — each worker loads its own copy of the models. -Unlike the CLI, the service defaults the **cross-model veto to off** — it adds one -extra large-model inference (~35 s/image on CPU, 14 s → 49 s total). Enable it -per request with the `cross_check` form field, or flip the service default with -`BGFILTER_CROSS_CHECK=1` (worth it on GPU or when hair-gap residue matters more -than latency); `/healthz` reports the default as `crossCheck`. The cross-check -model is lazy-loaded: it is never preloaded at startup, so the first request that -enables it also pays the one-time model load (~1 GB) on top of the inference. +Like the CLI, the service runs the **cross-model veto by default** (config +default: on). With reuse-as-seg it replaces the primary seg forward rather than +adding to it, so it is not the latency hit it once was; its HR-matting model is +preloaded with the rest of the warm set (and, on the birefnet backend, the now- +redundant primary segmenter is skipped at startup). Set `BGFILTER_CROSS_CHECK=0` +to turn it off service-wide, or use the per-request `cross_check` form field to +override either way; `/healthz` reports the default as `crossCheck`. ## Quality Check diff --git a/app.py b/app.py index d1b8ff5..fece2f1 100644 --- a/app.py +++ b/app.py @@ -62,12 +62,14 @@ def _build_settings(): if device: # Covers both settings.model.device and settings.segmentation.device. settings = override_settings(settings, device=device) - # The cross-model veto adds one extra large-model inference (~35 s/image on - # CPU), so the service defaults it OFF regardless of the config default; - # opt back in with BGFILTER_CROSS_CHECK=1. Other cross_check params (model, - # thresholds) still come from the config. - cross_check = os.environ.get("BGFILTER_CROSS_CHECK", "0").strip().lower() in ("1", "true", "yes", "on") - settings = override_settings(settings, cross_check=cross_check) + # The cross-model veto is default behaviour (config default: on). With + # reuse-as-seg it replaces the primary seg forward rather than adding to it, + # so the earlier CPU-cost concern no longer applies. BGFILTER_CROSS_CHECK + # can still force it either way; unset = honour the config. + cc_env = os.environ.get("BGFILTER_CROSS_CHECK") + if cc_env is not None and cc_env.strip() != "": + cross_check = cc_env.strip().lower() in ("1", "true", "yes", "on") + settings = override_settings(settings, cross_check=cross_check) return settings diff --git a/bgfilter/service.py b/bgfilter/service.py index 20c5364..1ce15df 100644 --- a/bgfilter/service.py +++ b/bgfilter/service.py @@ -38,10 +38,20 @@ class PipelineManager: if preload: if base_settings.model.matting_method == "vitmatte": self._get_runner() - if base_settings.segmentation.enabled: + # Cross-check is default behaviour, so its HR-matting model is part of + # the warm set. With reuse-as-seg on the birefnet backend that same + # forward also serves as the seg mask, so the primary segmenter is + # never used on the default path -- only preload it when reuse won't + # cover segmentation (reuse off, or a non-birefnet default backend). + if base_settings.cross_check.enabled: + self._get_cross_checker() + reuse_covers_seg = ( + base_settings.cross_check.enabled + and base_settings.cross_check.reuse_as_seg + and base_settings.segmentation.backend == "birefnet" + ) + if base_settings.segmentation.enabled and not reuse_covers_seg: self._get_segmenter(base_settings.segmentation.backend) - # The cross-check model is deliberately NOT preloaded: it is opt-in - # per request, so the first request that enables it pays the load. @property def default_seg_model(self) -> str: