2026-10-23 18:09:59 +08:00
2026-10-23 18:09:59 +08:00
2026-10-23 18:09:59 +08:00
2026-10-23 18:09:59 +08:00
2026-10-23 18:09:59 +08:00
2026-10-23 18:09:59 +08:00
2026-10-23 18:09:59 +08:00
2026-10-23 18:09:59 +08:00
2026-10-23 18:09:59 +08:00

local-remarker

本地重排(rerank)服务。零 API Key、文档不出内网,对外提供 SiliconFlow 兼容接口。

起因是在给 PandaWiki 做本地化部署时, 「重排序模型」被卡住了,一共有四个独立的阻塞点:

# 阻塞点 实测结论
1 Ollama 不提供重排 Ollama 0.40.8 的 /api/rerank、/v1/rerank 都是纯 404
2 调用方把路径写死 PandaWiki 的 raglite 只认 <base_url>/v1/services/rerank/text(SilhouFlow 格式)
3 装不上 torch download.pytorch.org/whl/cpu 与阿里云 pytorch-wheels 下载均 20 秒零进度
4 官方模型站不可达 registry.ollama.ai 的 blob 下载完全卡死,hf-mirror.com 连不上

最终方案是用 ONNX Runtime,绕开 torch。ModelScope 上 Xenova/bge-reranker-base 自带量化 ONNX,于是第 3、4 两个阻塞点同时消失 —— 整条链路只依赖 ModelScope 一个国内源,实测 15~25 MB/s。

快速开始

docker compose up -d

首次启动会下载约 290MB 模型到 ./models,日志形如:

[local-remarker] 模型目录: /models
  [model_int8.onnx] 需要下载 ...
  [model_int8.onnx] 266.0MB / 266.0MB (100%)
  [model_int8.onnx] 校验通过
[local-remarker] 模型准备完成

之后无论重启还是重建容器,都不会再产生下载流量。

服务监听 63001:

curl -s localhost:63001/health
# {"status":"ok","model":"bge-reranker-base"}

curl -s -X POST localhost:63001/v1/services/rerank/text \
  -H 'Content-Type: application/json' \
  -d '{
    "model": "bge-reranker-base",
    "query": "如何部署 PandaWiki 知识库",
    "documents": [
      "PandaWiki 支持通过 Docker 部署,数据保存在 postgres 与 minio",
      "今天股市行情如何,上证指数涨跌",
      "部署时需要先配置 embedding 模型,否则无法创建知识库"
    ],
    "top_n": 3,
    "return_documents": true
  }'

设计要点

模型不在镜像里,启动时按需下载

download_model.py 在 entrypoint 里跑,做三件事:

  1. 检查 —— 每个文件比对 大小 + sha256,全部通过就立刻返回,零网络请求
  2. 续传 —— 下载落到 .part 文件,重试时带 Range 头从断点继续
  3. 原子替换 —— 下载完先校验 sha256,通过才 os.replace 成正式文件, 避免中断留下的半个文件被当成有效模型加载

模型仓库、版本、下载源都是环境变量,换模型不用重新 build。

路径兜底

调用方填的 base_url 可能是 http://host:8000,也可能是 http://host:8000/v1, 框架在其后拼接路径后可能出现 /v1/v1/services/rerank/text 之类的变体。所以除了 显式注册常见路径,还加了一个兜底路由:路径中含 rerank 或以 /text 结尾的 POST 一律按重排处理;无关路径照常返回 404,不会误吞流量。

打分逻辑

遵循 bge-reranker 系列的惯例:把 (query, document) 拼成句对批量推理, 取 logits[:, 0] 作为相关性分数 —— 用的是原始 logit,不过 sigmoid, 然后降序取 top_n。

为什么选 int8

宿主机是 i7-1165G7,带 AVX512-VNNI,int8 推理能直接吃到该指令集加速。 266MB 的 int8 vs 1061MB 的 fp32,精度损失很小但速度和体积都明显更优。

同仓库可选:model_quantized.onnx (266MB)、model_fp16.onnx (531MB)、 model.onnx (1061MB)。换法见 docker-compose.yml 里的注释。

接口

POST /v1/services/rerank/text

兼容 SiliconFlow,请求:

{
  "model": "bge-reranker-base",
  "query": "查询词",
  "documents": ["片段1", "片段2"],
  "top_n": 5,
  "return_documents": false
}

响应:

{
  "id": "…",
  "object": "rerank.result",
  "model": "bge-reranker-base",
  "results": [
    {"index": 0, "relevance_score": 6.8775}
  ],
  "usage": {"total_tokens": 44},
  "meta": {"latency_ms": 18}
}

relevance_score 是原始 logit,无固定取值范围,只保证相对大小关系正确。 需要 0~1 区间的话调用方自行过 sigmoid。

其它

  • GET /health —— 存活探测
  • GET /info —— 当前模型、路径、输入张量名
  • 同样接受 POST /services/rerank/text、/v1/rerank、/rerank、/rank

配置

全部写在 docker-compose.yml 的 environment 里,改完 docker compose up -d 生效。常用的几个:

变量 默认 说明
PORT(宿主机侧) 63001 compose 的 ports,映射到容器 8000
MODEL_DIR /models 容器内模型目录
MODEL_REPO Xenova/bge-reranker-base 换模型改这里
MODEL_BASE ModelScope 换下载源改这里
ONNX_FILE model_int8.onnx 换量化档位改这里
MAX_LEN 512 截断长度,长文档可调大(吃内存)
BATCH_SIZE 16 每次推理的句对数
WORKERS 1 调大前先看内存,每个 worker 各加载一份模型

实测性能

i7-1165G7 / 8 逻辑核 / 15GB 内存:

场景 耗时
3 个文档重排 14~18 ms
镜像大小 401 MB
模型大小 290 MB

已知限制

  • 单 worker:默认 --workers 1,每个 worker 会各自加载一份模型(约 400MB 内存)。 高并发场景应调 WORKERS,同时注意内存翻倍。
  • 不鉴权:不校验 api_key。放到非受信网络需要自行加反向代理。
  • 仅 CPU:未做 GPU 加速,也没接 TensorRT/ONNXRuntime-GPU。
  • 只支持 ONNX:换 safetensors 格式的模型需要调整 download_model.py 的 MANIFEST 并改用 torch 运行时。

文件

local-remarker/
├── app.py                 FastAPI 服务本体
├── download_model.py      模型检查/下载/校验(幂等,支持续传)
├── entrypoint.sh          先下模型,再起 uvicorn
├── requirements.txt       运行时依赖(无 torch)
├── Dockerfile
├── docker-compose.yml     参数全部写在这里
└── .gitignore
S
Description
No description provided
Readme
37 KiB
Languages
Python 88.1%
Dockerfile 8.2%
Shell 3.7%