Files
BGfilter/DEPLOY_ZH.md
lhk229 43797eedeb Split logging: prose event stream vs structured JSONL access stream
Rule: bulk per-request data never lands in the prose log; everything with
analysis value goes to a structured file.

- Event stream (stderr, text): startup summary, warnings, tracebacks --
  what supervisord/journald already captures. Human-readable, low volume.
- Access stream: one JSONL record per request (plus a "startup" meta
  record with the instance config) written to BGFILTER_ACCESS_LOG,
  app-rotated 50MB x 5; falls back to stderr when unset (dev).
  Failed requests carry a truncated `exc` copy so the access file is
  self-contained; the full traceback stays in the event stream, joined
  by request_id.
- BGFILTER_LOG_FORMAT retired: format is now a property of the stream,
  not a global switch.
- uvicorn runs with --no-access-log everywhere (deploy script + unit
  examples): its prose per-request lines duplicated a subset of ours.
- deploy_autodl.sh --log-dir now provisions both files; docs updated
  (EN+ZH: env table, AutoDL section, run/systemd examples).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 13:40:33 +00:00

26 KiB
Raw Permalink Blame History

将 BgFilter 部署到 Linux 服务器

English version: DEPLOY.md

本指南涵盖在 Linux 服务器上运行 BgFilter HTTP 服务(POST /remove-background、 GET /healthz)所需的全部步骤。默认按 CPU 部署(推荐默认);涉及 GPU 的地方会单独标注。

服务入口是 app.py(uvicorn app:app)。API 契约和算法细节见 README.md。

下面第 1–12 节是手动 CPU 部署(systemd + nginx,推荐默认)。如果你部署在 AutoDL GPU 盒子上,走第 0 节的一键脚本更快;两者是各自独立的部署路线。


0. 快速路径:AutoDL 一键部署(GPU)

在 AutoDL GPU 实例上,用仓库自带的一键脚本部署,无需手动装依赖 / 配 systemd:

# 假设仓库已 checkout 到实例(脚本不会 git pull),使用 server-edition 分支
cd /root/BGfilter-server            # 你的实际路径
scripts/deploy_autodl.sh --token <TOKEN>

脚本幂等地完成:装依赖 + 拉模型权重 + 按 CUDA 版本装对应 CuPy(GPU 前景合成)

  • 起一个 supervisord 实例(崩溃自动重启)+ 往 ~/.bashrc 写交互登录自启, 最后启动服务并校验 /healthz。

参数:

参数 默认 说明
--token <TOKEN> 无 鉴权 token,设置/更新后 /remove-background 要求 header X-Genarrative-Image-Token。省略则沿用已有 token(重跑改别的参数不会误关鉴权);只有首次无 token 才开放。
--open — 显式删除 token、以开放模式运行(无鉴权)。
--port <PORT> 6006 容器内监听端口(对应 AutoDL 的公网映射端口)。
--config <cfg> configs/gpu.yaml 流水线配置。
--log-dir <dir> ~/autodl-tmp 日志目录(默认 AutoDL 持久数据盘——容器重置不丢,/tmp 会丢)。含 bgfilter.log(散文事件日志)和 bgfilter-access.jsonl(结构化每请求日志,应用自轮转)。

前提:仓库已 checkout(脚本不 git pull)、root 用户、torch/CUDA 由 AutoDL 镜像提供、 python 在 /root/miniconda3。

两个 AutoDL 固有的坑(脚本管不了):

  • 公网 URL 映射(如 6006 → https://...:8443)要在 AutoDL 网页控制台里设,脚本不负责。
  • 容器重启后服务不会自动起来——AutoDL 没有 systemd / 开机钩子,自启写在 ~/.bashrc 里, 需要开一个交互式 shell(SSH / JupyterLab 终端) 才会触发拉起。

服务管理(supervisorctl 与 python 同目录;socket 在 ~/bgfilter-supervisor.sock, supervisord 配置在 ~/supervisord.conf):

S="/root/miniconda3/bin/supervisorctl -c $HOME/supervisord.conf"
$S status bgfilter      # 状态 + 运行时长
$S restart bgfilter     # 重启(如改了 configs/*.yaml 之后)
$S stop bgfilter        # 停服务(supervisord 本身继续跑)
$S start bgfilter       # 再启动
$S shutdown             # 全部停掉,包括 supervisord 自己

tail -f ~/autodl-tmp/bgfilter.log            # 事件日志:启动/告警/堆栈(散文)
tail -f ~/autodl-tmp/bgfilter-access.jsonl   # 访问日志:每请求一条 JSON 记录
tail -f ~/supervisord.log                    # supervisord 自己的日志(服务起不来时看)

若 supervisord 本身没在跑——容器刚重启还没开过 shell,或报 socket 残留错误——照 ~/.bashrc 自启的方式手动拉起:

rm -f ~/bgfilter-supervisor.sock ~/supervisord.pid
/root/miniconda3/bin/supervisord -c ~/supervisord.conf

改 ~/supervisord.conf 本身(端口、日志路径等)的方式是重跑部署脚本——它会重新生成 该文件并做 reread + update + restart。

改了 configs/*.yaml(比如开 cross_check.reuse_as_seg)后,用上面的 restart bgfilter 让服务重新加载配置——配置只在启动时读入,没有请求级开关。


1. 前置条件

  • Linux x86-64(在 Ubuntu 上验证过;其它主流发行版同理)。
  • 推荐 Python 3.11–3.13。更新的版本(如 3.14)也可能可用,但部分 ML 包可能还没发布对应 wheel——尽量选一个 torch / onnxruntime / opencv / pymatting 都有预编译 wheel 的版本。
  • git,以及 venv 模块(Debian/Ubuntu 上是 python3-venv)。
  • 磁盘空间:Python 环境约 3 GB + 模型权重约 1 GB。
  • 一次性联网即可(用于装依赖和下模型);服务跑起来后可完全离线。
sudo apt update
sudo apt install -y python3-venv python3-pip git

2. 获取代码

sudo mkdir -p /opt/genarrative-image-host/bgfilter-service
sudo chown "$USER" /opt/genarrative-image-host/bgfilter-service
cd /opt/genarrative-image-host/bgfilter-service
git clone <repo-url> .            # 或把仓库拷到这里;使用 server-edition 分支

3. Python 环境

python3 -m venv .venv
. .venv/bin/activate
python -m pip install -U pip

4. 安装依赖(CPU)

先从 CPU wheel 源装 PyTorch——否则 pip 会拉默认的 CUDA 构建,附带约 2–3 GB 的 nvidia-* 包,CPU 机器完全用不上:

pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu

requirements.txt 里已经固定用 opencv-python-headless(GUI 构建依赖 libGL.so.1 等无头服务器没有的库),所以无需改动。直接装其余依赖——此时 torch/torchvision 已满足,不会再拉 CUDA:

pip install -r requirements.txt

验证整套依赖可导入、且 torch 是 CPU 构建(cuda= None):

python -c "import torch,cv2,fastapi,transformers,pymatting,onnxruntime,kornia,timm; \
print('torch', torch.__version__, 'cuda=', torch.version.cuda)"

在墙内,给第二条 pip install 加上 -i https://pypi.tuna.tsinghua.edu.cn/simple 提速。

5. 准备模型权重

默认用到三个模型:hustvl/vitmatte-base-composition-1k(抠图)、 ZhengPeng7/BiRefNet(默认分割)、skytnt/anime-seg(可选分割)。 第四个模型 ZhengPeng7/BiRefNet_HR-matting(约 425 MB)只有在启用 cross-check(交叉校验) 否决时才需要——见本节末尾说明。

一键方式(推荐):跑仓库自带脚本(无参数),经 hf-mirror 把所有模型下到 models/ (绕过代理、禁用 Xet、已存在的文件夹自动跳过)。跑完主 app 即离线全功能可用:

python scripts/fetch_weights.py

下面两种手动放法用于更精细的控制(比如共享 HF 缓存、只下部分模型),二选一。

方案 A —— HuggingFace 缓存 + 离线模式(推荐)

先下载到一个缓存目录,之后离线运行:

export HF_HOME=/opt/genarrative-image-host/bgfilter-service/model-cache/hf
hf download hustvl/vitmatte-base-composition-1k
hf download ZhengPeng7/BiRefNet
hf download skytnt/anime-seg
hf download ZhengPeng7/BiRefNet_HR-matting   # 仅在使用 cross-check 时(见下)

运行时把 HF_HOME 指向同一路径,并设 HF_HUB_OFFLINE=1 TRANSFORMERS_OFFLINE=1(见 §6)。 也可以在另一台机器下好,把 hub/ 目录整个拷过来。

方案 B —— 权重作为普通文件夹放进项目

把每个模型放进 models/<repo末段名>/,加载器会直接用它(不走 HF 缓存、不联网)。 没放对应文件夹时会回退到 repo id,所以这是可选功能。

文件夹名必须和 repo 末段名完全一致——加载器靠这个找到它 (ZhengPeng7/BiRefNet → models/BiRefNet)。各模型需要的文件:

<项目根目录>/
└── models/
    ├── vitmatte-base-composition-1k/     # ViTMatte 抠图模型
    │   ├── config.json
    │   ├── preprocessor_config.json
    │   └── pytorch_model.bin             # 约 369 MB
    ├── BiRefNet/                         # 默认分割模型(trust_remote_code)
    │   ├── config.json
    │   ├── BiRefNet_config.py            # 自定义代码——trust_remote_code 必需
    │   ├── birefnet.py                   # 自定义代码——trust_remote_code 必需
    │   └── model.safetensors             # 约 424 MB
    ├── anime-seg/                        # 可选分割模型(seg_model=anime-seg)
    │   └── isnetis.onnx                  # 约 168 MB
    └── BiRefNet_HR-matting/              # 可选——仅用于 cross-check 否决
        ├── config.json
        ├── BiRefNet_config.py            # 自定义代码——trust_remote_code 必需
        ├── birefnet.py                   # 自定义代码——trust_remote_code 必需
        └── model.safetensors             # 约 425 MB

说明:

  • ViTMatte:需要 config.json、preprocessor_config.json 和权重 (pytorch_model.bin;若有 model.safetensors 也可)。
  • BiRefNet:两个 .py 文件是必需的——trust_remote_code 会执行它们来构建模型。 和 config.json + model.safetensors 放在一起。(repo 里的 handler.py / requirements.txt / README.md 用不到。)
  • anime-seg:只读 isnetis.onnx,其它都不需要。
  • anime-seg 和 BiRefNet_HR-matting 都是可选的——不用 seg_model=anime-seg / cross-check 否决时,各自的文件夹可以不放。
  • cpu-fast lane(§6)只加载 ViTMatte + BiRefNet;8 GB 机器用这条 lane 时, anime-seg 和 BiRefNet_HR-matting 都可以完全不放。 (scripts/fetch_weights.py 仍会把 4 个都下齐,以保证完整功能。)

最省事的填充方式是用 hf download(它也会顺带拉几个用不到的文件如 README.md,无害):

hf download hustvl/vitmatte-base-composition-1k --local-dir models/vitmatte-base-composition-1k
hf download ZhengPeng7/BiRefNet                 --local-dir models/BiRefNet
hf download skytnt/anime-seg                     --local-dir models/anime-seg
hf download ZhengPeng7/BiRefNet_HR-matting       --local-dir models/BiRefNet_HR-matting  # 仅 cross-check

用 BGFILTER_WEIGHTS_DIR 可改基目录。models/ 已被 gitignore。

cross-check 否决(默认开启)

管线会再跑一个 matting 模型(ZhengPeng7/BiRefNet_HR-matting,约 425 MB)作为独立的 "第二意见"——它做什么、为什么,见 README_ZH.md → 管线。部署侧要点:

  • CLI 和 HTTP 服务都默认开启,因此必须备好这个模型(上面方案 A 或 B)。 ⚠️ 离线服务器(HF_HUB_OFFLINE=1)必须在第一个请求前就备好它,否则每个默认请求都失败。
  • 服务级关闭:BGFILTER_CROSS_CHECK=0;按请求覆盖:表单字段 cross_check。 开启时服务启动即预载。
  • 配置里 cross_check.reuse_as_seg: true(默认关)让这次前向兼作分割掩码: 少载一个模型、CPU 快约 20 秒/张、GPU 省约 1 GB 显存。仅配置文件可设——改动需重启。

背景模式(flat vs complex)

background_mode=complex(非纯色/实景背景)不需要额外权重——复用已部署的 BiRefNet 分割器 + ViTMatte,跳过色键相关阶段。cross-check 否决在 complex 模式下 默认关闭(传 cross_check=on 启用)。字段语义与响应头见 README_ZH.md → HTTP 服务。

墙内下载(国内网络)

若直连 HuggingFace 被墙,用镜像 并绕过本地代理、禁用 Xet——这是稳定可用的组合:

export HF_ENDPOINT=https://hf-mirror.com   # 国内镜像
export NO_PROXY='*'                        # 绕过代理;镜像是国内直连
export HF_HUB_DISABLE_XET=1                 # 这些 repo 是 Xet 存储;强制走经典 HTTP

这样能避开两个坑:(1) 镜像 + 走海外的代理会让镜像把 resolve 又重定向回 huggingface.co,新版 huggingface_hub 会因此报 FileMetadataError; (2) 装了 hf-xet 时 Xet 下载路径在此环境下会瞬间失败。

6. 配置(环境变量)

变量 默认 作用
BGFILTER_CONFIG configs/default.yaml 管线配置文件
BGFILTER_DEVICE cpu 同时覆盖 model 和 segmentation 的 device(GPU 用 cuda)
BGFILTER_MAX_IMAGE_PIXELS 4194304 超过则返回 413(约 4 MP)
BGFILTER_ACCESS_LOG — 结构化访问日志的路径(JSONL,每请求一条记录 + 一条 startup 元记录;应用自轮转,50 MB × 5)。字段:request_id、参数、尺寸、分阶段耗时(t_queue/fetch/decode/process/total_ms)、状态码、失败时截断的 exc;该 id 同时以 X-Request-ID 响应头返回。不设置 = 记录回退到 stderr(开发用)。散文事件日志(启动/告警/堆栈)始终走 stderr 由 supervisord/journald 捕获——每请求数据绝不落在散文流里。
BGFILTER_LOG_LEVEL INFO 根日志级别(事件流)。
BGFILTER_FETCH_MAX_BYTES 26214400 image_url 模式:下载体上限(约 25 MB)→ 413。无 SSRF 过滤——务必开鉴权 / 调用方可信。
BGFILTER_FETCH_TIMEOUT 15 image_url 模式:fetch 读超时(秒;连接超时固定 5 秒)。出站 fetch 并发固定为 2 × cpu_workers(硬顶 64)。
BGFILTER_PRELOAD 1 启动时预加载默认模型(首个请求不用冷加载)
BGFILTER_CROSS_CHECK 配置(on) 强制开/关 cross-check 否决(0 关闭;需 BiRefNet_HR-matting)
BGFILTER_CPU_WORKERS 配置 server.cpu_workers(1) 请求流水线的线程池大小。1 = 严格串行(一次一个请求)。>1 允许这么多请求并发,重叠各自的 CPU 段,同时用一把 GPU 互斥锁串行化模型前向——让 GPU 别闲着。CPU 为瓶颈时(如关 cross-check)收益最大。可写进配置的 server: 段,或用此 env 变量按机器覆盖。
BGFILTER_GPU_CONCURRENCY 配置 server.gpu_concurrency(1) 允许几个请求同时进入 GPU 前向。1 = 单流(安全)。2 让两次前向在 SM 上共同调度(batch-1 喂不满 SM),提升 GPU 瓶颈下的吞吐——但并发激活会成倍占显存,按 权重 + N × 单次前向峰值 ≤ 显存 来定(32GB 卡上 2 在 2048 cross-check 时峰值约 19GB;≤16GB 卡保持 1)。需 cpu_workers>1 才有意义。配置键 server.gpu_concurrency,此 env 变量覆盖它。
BGFILTER_AUTH_TOKEN — 若设置,/remove-background 要求 header X-Genarrative-Image-Token 等于它(常量时间比较;否则 401)。不设 = 开放(鉴权交给前置代理)。/healthz 始终开放。见 §10。
BGFILTER_WEIGHTS_DIR models 本地权重文件夹的查找目录(方案 B)
HF_HOME — HF 缓存位置(方案 A)
HF_HUB_OFFLINE / TRANSFORMERS_OFFLINE — 权重缓存好后,生产设为 1
OMP_NUM_THREADS / MKL_NUM_THREADS — 限制 CPU 线程数(如 4)

配置文件:default.yaml 与 cpu-fast lane

BGFILTER_CONFIG 决定用哪个管线配置。仓库自带两个:

  • configs/default.yaml —— 完整画质。cross-check 否决默认开(额外加载 BiRefNet_HR-matting@2048 作第二意见),边缘最好。较重:大图时峰值工作集轻松到 两位数 GB,需要内存宽裕的机器(或 GPU)。
  • configs/cpu-fast.yaml —— 精简 CPU lane,为 8 GB 机器调校。fp32、只用 BiRefNet 分割、无 cross-check、无 anime-seg、分块注意力(attn_query_chunk: 512)。只加载 两个权重(base ViTMatte + BiRefNet)。实测峰值工作集:2048×1152(2.4 MP)约 5.6 GB、 2048×2048(4.2 MP)约 7.1 GB;约 14–15 秒/张(1.5 MP)。

≤8 GB 的服务器请设 BGFILTER_CONFIG=configs/cpu-fast.yaml。 默认 lane + 大图 正是把 ViTMatte 注意力瞬时内存推到 OOM 的组合——worker 被 kill(SIGKILL),前面的 nginx 返回 502,而应用日志显示 DefaultCPUAllocator: can't allocate memory。

有两个模型旋钮放在配置文件里(model: 段),不是环境变量:

配置项 默认 作用
model.precision fp32 fp32 或 bf16。bf16 仅在有原生内核的硬件上启用(CUDA,或带 AVX512-BF16/AMX 的 CPU);否则告警并回退 fp32。也可在 segmentation: / cross_check: 下按模型单独设。
model.attn_query_chunk 2048 ViTMatte 全局注意力的 query 行分块。调小(如 512)以几个百分点的额外算力换掉 O(N²) 内存瞬时尖峰;输出逐比特相同。设 0 关闭分块(走原始一次性注意力)。

despill 默认关闭(所有 lane,含 cpu-fast)。它沿背景色调方向压前景色度,没有位置/语义护栏,所以主体与背景同色相时(如蓝背景前的蓝西装)会被褪色/偏色(约 ΔE 35)。仅当"去边缘溢色"比"同色系保真"更重要时,才在配置里 despill: {enabled: true} 单独开启。

7. 运行服务

BGFILTER_ACCESS_LOG=logs/bgfilter-access.jsonl \
python -m uvicorn app:app --host 127.0.0.1 --port 18083 --workers 1 --no-access-log
  • 必须保留 --workers 1。 每个 worker 各加载一整套模型,多 worker 会成倍占内存 (8 GB 机器会 OOM)。服务已用全局锁串行化推理。
  • --host 127.0.0.1 只对本机开放(面向同机调用方)。跨机访问见 §10。
  • --no-access-log 关掉 uvicorn 自己的散文版每请求日志——它是应用结构化访问记录 (§6 BGFILTER_ACCESS_LOG)的子集、纯重复,且每请求数据不该进散文流。

8. 作为 systemd 服务运行

/etc/systemd/system/genarrative-bgfilter.service:

[Unit]
Description=Genarrative BgFilter background removal service
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
WorkingDirectory=/opt/genarrative-image-host/bgfilter-service
Environment=HF_HOME=/opt/genarrative-image-host/bgfilter-service/model-cache/hf
Environment=HF_HUB_OFFLINE=1
Environment=TRANSFORMERS_OFFLINE=1
Environment=HF_HUB_DISABLE_XET=1
Environment=BGFILTER_CONFIG=configs/default.yaml
Environment=BGFILTER_DEVICE=cpu
Environment=BGFILTER_MAX_IMAGE_PIXELS=4194304
Environment=BGFILTER_PRELOAD=1
Environment=BGFILTER_ACCESS_LOG=/opt/genarrative-image-host/bgfilter-service/logs/bgfilter-access.jsonl
Environment=OMP_NUM_THREADS=4
Environment=MKL_NUM_THREADS=4
ExecStart=/opt/genarrative-image-host/bgfilter-service/.venv/bin/uvicorn app:app --host 127.0.0.1 --port 18083 --workers 1 --no-access-log
Restart=on-failure
RestartSec=5
MemoryMax=8G
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=full
ReadWritePaths=/opt/genarrative-image-host

[Install]
WantedBy=multi-user.target

≤8 GB 机器请把配置那行改成精简 lane: Environment=BGFILTER_CONFIG=configs/cpu-fast.yaml(见 §6)——默认 lane 大图会 OOM。

若用方案 B(本地 models/ 文件夹)而非 HF 缓存,去掉 HF_HOME 和离线那几行—— 那种权重不需要缓存也不联网。

sudo systemctl daemon-reload
sudo systemctl enable --now genarrative-bgfilter
sudo systemctl status genarrative-bgfilter
journalctl -u genarrative-bgfilter -f          # 等到 "Application startup complete"

9. 验证

curl -s http://127.0.0.1:18083/healthz
# {"ok":true,"service":"bgfilter","version":"0.1.0","defaultSegModel":"birefnet","device":"cpu"}

curl -sS -F "file=@input.png" -F "screen_color=#CFEFFF" \
  http://127.0.0.1:18083/remove-background -o out.png

# 非纯色/实景背景(无色键;此模式 cross-check 默认关)
curl -sS -F "file=@scene.jpg" -F "background_mode=complex" \
  http://127.0.0.1:18083/remove-background -o out.png

10. 对外暴露

单元里绑的是 127.0.0.1——面向同机调用方(例如 Genarrative Rust BFF,通过 GENARRATIVE_EDITOR_BACKGROUND_REMOVAL_BASE_URL 调用)。要从其它主机访问:

  • 反向代理(推荐): 前面放 nginx/caddy,终止 TLS、加限流,转发到 127.0.0.1:18083。
  • 直接暴露: 把 --host 改成 0.0.0.0,并用防火墙限制来源 IP。 切勿把无鉴权的服务裸暴露到公网(见下方鉴权)。

鉴权

app 层 token 鉴权是可选开启,由 BGFILTER_AUTH_TOKEN 控制:

  • 设置它 → /remove-background 要求 header X-Genarrative-Image-Token: <token> (常量时间比较);缺失或错误返回 401。/healthz 保持开放。当 app 被直接暴露 (--host 0.0.0.0、或云端端口映射无代理)时用这个。
  • 不设置 → app 开放(fail-open),并在启动时打一条 WARNING。仅当前置代理 已经鉴权时才可这样——例如生产机 nginx 自己校验 X-Genarrative-Image-Token, 其后的 app 就无需 token。

header 名和 nginx 层一致,所以调用方只换 base URL + token 值,请求结构不变:

curl -H "X-Genarrative-Image-Token: <token>" \
  -F "file=@input.png" \
  https://<host>/remove-background -o out.png

11. 容量与性能

  • CPU 吞吐:cpu-fast lane 约 14–15 秒/张(1.5 MP)(base ViTMatte + 一个 BiRefNet@1024,分块注意力)。默认 lane 更慢——cross-check 会多跑一次 HR-matting@2048 前向。请求被全局锁串行,单实例按每分钟几张估算。

  • 默认 lane,工作站 CPU 参考(Ryzen 9700X,原生 bf16,32 GB;cross-check 开且 reuse_as_seg 开,分块注意力):

    输入 精度 warm / 每图 峰值内存
    1024×1536 fp32 ~45 s —
    1024×1536 bf16 ~33 s —
    2048×2048 bf16 ~53 s 11.4 GB(批处理)、8.1 GB(单图)

    不开 reuse_as_seg 要加上主 BiRefNet@1024 前向(每图约 +20 s)。内存上限来自 cross-check 的 HR 前向(无论图多大都固定 input_size 2048)。分块注意力之前, 同一个 2048² bf16 run 峰值 25.2 GB;完全未优化的 fp32 估计要 55–60 GB。无原生 bf16 的 CPU(如 Zen 2)自动回落 fp32——这类机器上内存或延迟紧张时优先 --no-cross-check。

  • 要提吞吐:上 GPU(BGFILTER_DEVICE=cuda,装 CUDA 版 torch),大约快一个数量级; 和/或多实例 + 负载均衡(每个都 --workers 1)。

  • GPU 部署(实测,RTX 5070 Ti 16 GB,完整管线——cross-check 开,bf16): 约 1–2 秒/张(4 MP 也只要 2 秒),峰值 VRAM 分配约 10.9 GB(开 reuse_as_seg 为 10.1 GB)。真正决定「装不装得下」的是分配器的 reserved 占用。bgfilter 在 Linux/WSL2 上会默认设置 PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (在 torch 加载前;原生 Windows 不支持该选项;显式设过的环境变量优先)。 实测效果:reserved 15.3 GB → 12.0 GB(开 reuse;默认不开为 13.3 GB),且还 快约 10%——完整管线在 16 GB 卡上即使旁边挂着桌面会话也有约 2–3.5 GB 余量。 没有 expandable segments(如原生 Windows)时 reserved 约 15.3–16.2 GB:在 16 GB 卡上从很悬到直接溢出。另外 bf16 是 16 GB 可行的前提——fp32 的 reserved 约 17 GB,直接溢出。

  • 内存:分块注意力(attn_query_chunk)把 ViTMatte 的 O(N²) 瞬时尖峰削平,于是峰值由 稠密计算而非注意力矩阵决定。cpu-fast lane 实测峰值 约 5.6 GB@2.4MP / 7.1 GB@4.2MP—— 配合默认 4 MP 输入上限,MemoryMax=8G 对单 CPU worker 是稳妥上限。默认 lane(cross-check 开) 需要更多余量。

  • 让 RSS 保持平稳(glibc)。 每处理完一张图,服务会调用 glibc 的 malloc_trim(0) (bgfilter/memtune.py)把 BiRefNet@2048 前向释放的大块激活还给 OS——否则 ptmalloc 会把它们留在 arena 里,RSS 会随请求逐步抬高。想进一步降内存并提升 CPU 吞吐,可以 预载 jemalloc(PyTorch 对 CPU 推理的推荐),并让它激进归还脏页:

    # 放进 systemd unit 的 [Service] 段
    Environment=LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so.2
    Environment=MALLOC_CONF=background_thread:true,dirty_decay_ms:0,muzzy_decay_ms:0
    

    (apt install libjemalloc2;tcmalloc 用 libtcmalloc_minimal.so.4 同理。) jemalloc/tcmalloc 自己管理归还,此时 malloc_trim 会自动空转。若无法预载分配器, 在 unit 环境里设 MALLOC_ARENA_MAX=2、MALLOC_TRIM_THRESHOLD_=0 也能抑制 ptmalloc 滞留。

12. 故障排查

现象 原因 / 解决
pip 拉了 nvidia-*/cuda-*(巨大) §4 里漏了 CPU 源。用 --index-url https://download.pytorch.org/whl/cpu 装 torch。
import cv2 报 ImportError: libGL.so.1 在无 GUI 机器上用了 opencv-python。换成 opencv-python-headless(§4)。
建 venv 时 ensurepip is not available 装 python3-venv(§1)。
下载时 FileMetadataError: ... not on huggingface.co 镜像与代理冲突。绕过代理(NO_PROXY='*')并设 HF_HUB_DISABLE_XET=1(§5)。
LocalEntryNotFoundError、0 字节 Xet 下载路径失败;设 HF_HUB_DISABLE_XET=1。
503 service not ready 启动时模型还在加载;等 "Application startup complete"。
刚设 BGFILTER_CROSS_CHECK 后(离线)报 500 inference failed BiRefNet_HR-matting 没备好;下到缓存/models/(§5)。
负载下 OOM / 被 kill worker 多于一个,或 MemoryMax 太低。保持 --workers 1。
大图时 worker 被 kill(SIGKILL)/ 返回 502,日志显示 DefaultCPUAllocator: can't allocate memory 大图触发 ViTMatte OOM。改用 BGFILTER_CONFIG=configs/cpu-fast.yaml(§6),和/或调小 BGFILTER_MAX_IMAGE_PIXELS。
大文件上传在代理层被 413 拒 nginx client_max_body_size(默认 1 MB)——在 server 块里调大(如 client_max_body_size 12m;);这和应用的 BGFILTER_MAX_IMAGE_PIXELS 是两道独立限制。
400 could not auto-detect background colour 输入没有干净的纯色边框;传 screen_color=#RRGGBB。