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

177 lines
5.9 KiB
Markdown

# local-remarker
本地重排(rerank)服务。零 API Key、文档不出内网,对外提供 SiliconFlow 兼容接口。
起因是在给 [PandaWiki](https://github.com/chaitin/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。
## 快速开始
```bash
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**:
```bash
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,请求:
```json
{
"model": "bge-reranker-base",
"query": "查询词",
"documents": ["片段1", "片段2"],
"top_n": 5,
"return_documents": false
}
```
响应:
```json
{
"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
```