17 KiB
将 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. 前置条件
- 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-fastlane(§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)
作为"第二意见",清理发丝间残留的背景色。CLI 和 HTTP 服务都默认开启,而且开了
reuse-as-seg 之后,在 birefnet 后端这同一次前向还兼作分割掩码——它是替换了 primary
分割模型,而不是额外叠加。因此必须备好这个模型(上面方案 A 或 B):
- ⚠️ 离线服务器(
HF_HUB_OFFLINE=1)必须在第一个请求前就备好它,否则每个默认请求都会 失败——运行时它无法联网下载模型。 - 服务级关闭:
BGFILTER_CROSS_CHECK=0;或按请求覆盖:表单字段cross_check(true/false)。 - 服务启动时会随暖机集一起预载它;在 birefnet 后端,冗余的 primary 分割模型会被跳过。
墙内下载(国内网络)
若直连 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_PRELOAD |
1 |
启动时预加载默认模型(首个请求不用冷加载) |
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 |
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 关闭分块(走原始一次性注意力)。 |
7. 运行服务
python -m uvicorn app:app --host 127.0.0.1 --port 18083 --workers 1
- 必须保留
--workers 1。 每个 worker 各加载一整套模型,多 worker 会成倍占内存 (8 GB 机器会 OOM)。服务已用全局锁串行化推理。 --host 127.0.0.1只对本机开放(面向同机调用方)。跨机访问见 §10。
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=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
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
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。 切勿把无鉴权的服务裸暴露到公网。
11. 容量与性能
- CPU 吞吐:
cpu-fastlane 约 14–15 秒/张(1.5 MP)(base ViTMatte + 一个 BiRefNet@1024,分块注意力)。默认 lane 更慢——cross-check 会多跑一次 HR-matting@2048 前向。请求被全局锁串行,单实例按每分钟几张估算。 - 要提吞吐:上 GPU(
BGFILTER_DEVICE=cuda,装 CUDA 版 torch),大约快一个数量级; 和/或多实例 + 负载均衡(每个都--workers 1)。 - 内存:分块注意力(
attn_query_chunk)把 ViTMatte 的 O(N²) 瞬时尖峰削平,于是峰值由 稠密计算而非注意力矩阵决定。cpu-fastlane 实测峰值 约 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:0apt 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。 |