Make cross-check and reuse-as-seg the service default

Cross-check + reuse-as-seg are already the config/CLI default; align the HTTP
service with them instead of forcing cross-check off. The earlier off-by-default
was to avoid the extra inference, but reuse-as-seg now makes that forward double
as the seg mask (birefnet backend) -- it replaces the primary seg model rather
than adding to it, so the cost concern is gone.

- app.py: honour the config default (on); BGFILTER_CROSS_CHECK still forces
  either way, unset = config.
- service.py preload: warm the cross-check HR-matting model, and skip the now-
  redundant primary segmenter when reuse covers segmentation.
- README / DEPLOY(_ZH): document cross-check as default-on, BiRefNet_HR-matting
  as required (not optional), BGFILTER_CROSS_CHECK=0 to disable.

Verified: service defaults cross_check on, env forces both ways, preload picks
cross-checker and skips the primary segmenter.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-06 16:11:43 +08:00
parent c19a35de80
commit ad445611ad
5 changed files with 52 additions and 41 deletions
+14 -14
View File
@@ -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 |
+10 -11
View File
@@ -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 的 deviceGPU 用 `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` |
+7 -7
View File
@@ -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
+8 -6
View File
@@ -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
+13 -3
View File
@@ -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: