README.md 17.1 KB

Reranker 模块

请求示例docs/QUICKSTART.md §3.5。扩展规范见 docs/DEVELOPER_GUIDE.md §7。部署与调优实战见 reranker/DEPLOYMENT_AND_TUNING.mdggml-org/Qwen3-Reranker-0.6B-Q8_0-GGUF 的专项接入与调优结论见 reranker/GGUF_0_6B_INSTALL_AND_TUNING.md


Reranker 服务提供统一的 /rerank API,支持可插拔后端(BGE、Qwen3-vLLM、Qwen3-Transformers、Qwen3-GGUF、DashScope 云重排)。调用方通过 HTTP 访问,不关心具体后端。

当前结论

在当前项目的线上形态里,首选后端是 qwen3_vllm_score次选后端是 qwen3_vllm

原因不是“LLM.score() 理论上更高级”,而是这轮优化后,qwen3_vllm_score 在当前硬件和依赖栈上形成了一套更干净、更稳定、也更快的组合:

  • 模型:Qwen/Qwen3-Reranker-0.6B
  • GPU:Tesla T4 16GB
  • CUDA:12.8
  • PyTorch:2.10.0+cu128
  • vLLM-score 环境:vllm==0.18.0
  • attention:由 vLLM 运行时自动选择后端实现;在已验证的 T4 栈上日志可见 FLASHINFER

这次经验沉淀的核心结论有 4 条:

  1. qwen3_vllm_score 的 attention 实现由 vLLM 在运行时按 GPU 与版本自动选择
  2. 在已验证栈(T4 + vLLM 0.18.x 等)上,日志可见选用 FLASHINFER 等由运行时选定的路径。
  3. 无论 score 还是 generate,真正有价值的优化点都不是 prompt 小改,而是: 去重、按 doc 长度排序分批、合适的 infer_batch_size、合理的 max_model_len、前缀缓存、隔离 venv 与运行时缓存目录。
  4. 本项目当前统一把 instruction_format 配成 standard。代码仍兼容两种格式,但它不是本轮性能优化的重点,也不是推荐继续投入精力的方向

后端总览

后端 当前定位 结论
qwen3_vllm_score 主推荐 走 vLLM LLM.score()pooling / classify 路径:对每条 (query, doc) 直接产出相关分,不经 causal LM 的整步 generate。相对 qwen3_vllmgenerate(max_tokens=1) + yes/no 的 logprob 推导),省去每对样本上大词表 softmax / 采样约束那一层的常规开销,语义与 cross-encoder 式 rerank 更一致;在当前栈与 T4 上延迟表现最好
qwen3_vllm 次推荐 稳定、成熟、好排障,是很好的 fallback 和对照组
qwen3_transformers 兼容方案
qwen3_transformers_packed 特定场景方案 T可能实现还有问题,没调好
qwen3_gguf / qwen3_gguf_06b 低显存 / 功能兜底 更适合资源受限场景,不适合作为当前主在线方案
dashscope_rerank 云服务方案 运维简单,但依赖外部服务和网络

目录与入口

  • reranker/server.py:FastAPI 服务,启动时按配置加载一个后端
  • reranker/backends/:后端实现与工厂
    • backends/__init__.pyget_rerank_backend(name, config)
    • backends/qwen3_vllm_score.py:当前最优的本地 GPU reranker
    • backends/qwen3_vllm.py:次优的本地 GPU reranker
    • backends/qwen3_transformers.py:Transformers 基线实现
    • backends/qwen3_transformers_packed.py:packed 推理实现
    • backends/qwen3_gguf.py:GGUF + llama.cpp 后端
    • backends/dashscope_rerank.py:DashScope 云端重排后端
  • scripts/setup_reranker_venv.sh:按后端创建独立 venv
  • scripts/start_reranker.sh:启动 reranker 服务
  • scripts/smoke_qwen3_vllm_score_backend.pyqwen3_vllm_score 本地 smoke
  • scripts/benchmark_reranker_random_titles.py:随机标题压测脚本
  • scripts/run_reranker_vllm_instruction_benchmark.sh:历史矩阵脚本

环境基线

当前验证环境:

  • GPU:Tesla T4 16GB
  • Driver / CUDA:570.158.01 / 12.8
  • Python:3.12.3
  • torch2.10.0+cu128
  • transformers4.51+
  • qwen3_vllm_score 环境:vllm==0.18.0
  • qwen3_vllm 环境:vllm>=0.8.5

独立 venv 约定:

  • qwen3_vllm -> .venv-reranker
  • qwen3_vllm_score -> .venv-reranker-score
  • qwen3_transformers -> .venv-reranker-transformers
  • qwen3_transformers_packed -> .venv-reranker-transformers-packed
  • qwen3_gguf -> .venv-reranker-gguf
  • qwen3_gguf_06b -> .venv-reranker-gguf-06b
  • bge -> .venv-reranker-bge
  • dashscope_rerank -> .venv-reranker-dashscope

这样做不是形式主义,而是因为:

  • 不同后端的 CUDA / vLLM / llama.cpp 依赖耦合很深,混装后更难定位性能和兼容性问题
  • qwen3_vllm_score 和 qwen3_vllm 分了两个环境,是因为qwen3_vllm_score使用了vllm 0.18,但是后面经过测试两者性能相同。所以其实可以共用一个环境。不过没有动力合并回去。

安装与部署

1. 创建后端环境

qwen3_vllm_score

./scripts/setup_reranker_venv.sh qwen3_vllm_score

qwen3_vllm

./scripts/setup_reranker_venv.sh qwen3_vllm

2. 基础检查

nvidia-smi
./.venv-reranker-score/bin/python -c "import torch, vllm; print(torch.cuda.is_available(), torch.cuda.get_device_name(0), vllm.__version__)"
./.venv-reranker/bin/python -c "import torch, vllm; print(torch.cuda.is_available(), torch.cuda.get_device_name(0), vllm.__version__)"

3. 启动服务

./scripts/start_reranker.sh

scripts/start_reranker.sh 做了几件对性能和稳定性都很关键的事:

  • 自动选择当前 backend 对应的独立 venv
  • 为 vLLM / triton / torch.compile 指定独立缓存目录
  • 把后端 venv 的 bin 放到 PATH 前面

最后这一点很重要。对 qwen3_vllm_score 来说,T4 上 vLLM 自动选择 FLASHINFER 时,首次 JIT 需要 ninja,而 ninja 是装在对应 venv 里的。如果裸跑一个没有正确 PATH 的 Python 进程,就可能出现“环境明明装了,worker 里却找不到编译工具”的问题。

4. Smoke

PYTHONPATH=. ./.venv-reranker-score/bin/python scripts/smoke_qwen3_vllm_score_backend.py --gpu-memory-utilization 0.2

如果显卡上还有别的重进程,gpu_memory_utilization 可以临时调小或调大做排查;smoke 本身建议单独跑,不要和大压测并发。

当前最优方案:qwen3_vllm_score

它为什么是当前最优

qwen3_vllm_score.py 的优势,来自这几个组合在一起:

  1. 使用 vLLM 的 LLM.score()(pooling / classify),对 (query, doc) 直接打分,而非借 generate 在整词表上走最后一步分布再抠 yes/no——省掉那一类路径上的常规算力与模板绕路。
  2. 使用独立的 .venv-reranker-score,把 vllm==0.18.0 固定下来,避免和其他后端互相污染。
  3. attention 后端由 vLLM 按 GPU 与版本自动选择config.yaml 里与 rerank 相关的调参集中在批量、长度、缓存、显存占比等项。
  4. 在已验证的 T4 依赖栈上,运行时通常选用 FLASHINFER(见服务日志);与 FlashAttention 2 等路径的取舍由 vLLM 内部策略完成。
  5. 服务层保留高杠杆优化: 全局去重、按 doc 长度排序、分批推理、前缀缓存、单进程锁保护。

关键实现点

qwen3_vllm_score.py 里值得关注的地方:

  • runner / convert 保持 auto:走 pooling / classifyLLM.score() 的推荐接法(vLLM 0.17+)
  • hf_overrides:把原始 Qwen3 reranker 权重按官方要求映射到 Qwen3ForSequenceClassification
  • LLM(...) 仅使用本后端所需的模型与并行等参数;attention 后端由 vLLM 内部按运行环境选用
  • deduplicate_with_positions(...):先去重,再回填原始顺序
  • sort_by_doc_length:减少 padding 浪费
  • infer_batch_size:控制服务层分批
  • enable_prefix_caching:对重复前缀场景有收益
  • self._infer_lock:避免当前进程模型下并发调用破坏 vLLM engine 稳定性

Attention 与算力路径(现状)

  • vLLM 根据 GPU 算力架构当前 wheel 中的实现(如随发行版提供的 flashinfer 等)自动选用 attention 路径。
  • Tesla T4(sm_75 + vLLM 0.18.x 的已验证环境中,服务日志中可见选用 FLASHINFER
  • 最佳实践:性能调优放在 max_model_leninfer_batch_sizegpu_memory_utilization、去重、长度排序、prefix cache 等服务可见参数上。与 400 docs 量级相关的稳态 HTTP 数字见 perf_reports/reranker_vllm_instruction/2026-03-25/RESULTS.md(主表方法论 + Addendumqwen3_vllm_score 补充行)。

推荐配置

当前项目统一使用 standard,README 也按这个基线描述:

services:
  rerank:
    backend: "qwen3_vllm_score"
    backends:
      qwen3_vllm_score:
        model_name: "Qwen/Qwen3-Reranker-0.6B"
        use_original_qwen3_hf_overrides: true
        engine: "vllm"
        max_model_len: 256
        tensor_parallel_size: 1
        gpu_memory_utilization: 0.20
        dtype: "float16"
        enable_prefix_caching: true
        enforce_eager: false
        infer_batch_size: 100
        sort_by_doc_length: true
        instruction_format: standard
        instruction: "Rank products by query with category & style match prioritized"

优点

  • 当前本地 GPU 方案里性能最好
  • attention 由 vLLM 在引擎内统一决策;仓库侧配置只覆盖批量、长度、缓存、显存等,实现路径短
  • score / classify 路径与 rerank 任务对齐;相对 generate + 词表 logprob 少一层常规开销
  • 服务层优化(去重、排序分批、缓存)与后端解耦清晰,易维护

缺点

  • 依赖更新的 vLLM 栈,升级时要重新验证
  • 首次启动会经历 compile / JIT / graph capture,冷启动偏慢
  • 对环境完整性更敏感,尤其是 CUDA、worker 进程和 ninja

次优方案:qwen3_vllm

它为什么仍然很有价值

qwen3_vllm.py 是当前最好的次优方案,不只是“备用”,而是一个很重要的稳定对照组。

它走的是:

  • causal LM
  • generate(max_tokens=1)
  • 只允许输出 yes/no
  • 用最后一步 logprobs 反推出相关性分数

这条路径的优点是工程上非常稳:

  • 行为更容易理解
  • 更容易和 Hugging Face tokenizer 对齐
  • 排查问题时更直观
  • 在一些旧版本 vLLM 或其他 GPU 组合上,表现可能仍然很好

它为什么排在第二

它不是当前第一名,主要不是因为模型差,而是路径更“绕”:

  • 要先走 chat template
  • 要自己维护 yes/no token
  • 要做一次短 decode
  • 要从 logprobs 里手工算概率

也就是说,qwen3_vllm 的打分是“借 generate 模式实现 rerank”,而不是原生 score 路径。它依然有效,但从结构上不如 qwen3_vllm_score 直接。

关键实现点

  • AutoTokenizer.apply_chat_template(...)
  • SamplingParams(max_tokens=1, allowed_token_ids=[yes, no])
  • generate(...) 后从最后一步 logprobs 计算 yes/no 概率
  • 同样具备去重、按长度排序、分批推理、前缀缓存、单进程锁等优化

推荐配置

services:
  rerank:
    backends:
      qwen3_vllm:
        model_name: "Qwen/Qwen3-Reranker-0.6B"
        engine: "vllm"
        max_model_len: 256
        tensor_parallel_size: 1
        gpu_memory_utilization: 0.20
        dtype: "float16"
        enable_prefix_caching: true
        enforce_eager: false
        infer_batch_size: 100
        sort_by_doc_length: true
        instruction_format: standard
        instruction: "Rank products by query with category & style match prioritized"

优点

  • 路径成熟,易理解,易排障
  • 作为 fallback 很合适
  • qwen3_vllm_score 共用很多服务层优化经验

缺点

  • 不是原生 reranker score 路径
  • qwen3_vllm_score 多一层 tokenizer / generate / logprob 推导成本
  • 当前环境下性能略逊

这轮优化里真正有价值的方法

下面这些是跨后端都值得保留的经验,优先级高于 prompt 微调。

1. 全局去重

对 doc 先做全局去重,再按原始索引回填,是收益最高、风险最低的优化之一。

适用原因:

  • 商品标题、变体标题、重复 SKU 文案很常见
  • 去重不会改变 API 契约
  • 能直接减少模型真实推理次数

2. 按 doc 长度排序再分批

sort_by_doc_length: true 建议保持开启。

原因:

  • 同一批里长度更接近,padding 更少
  • infer_batch_size 较大时收益更明显
  • 实现成本低,行为稳定

当前实现里长度估计采用字符长度近似,这已经足够实用。没有必要为了这一层再引入额外 tokenizer 计算开销。

3. infer_batch_size 作为核心调参项

对当前业务形态,infer_batch_size 是最值得扫的参数。

建议:

  • 先固定其他参数,再扫 64 / 80 / 96 / 100 / 128
  • 看的是单请求延迟和稳定性,不只是吞吐
  • 不要只拿一次结果下结论,至少 warm-up 后 repeat 5 次

4. max_model_len 不要盲目开大

当前场景是短 query + 商品标题/短描述,不需要把 max_model_len 拉得很高。

经验:

  • 160 适合做对比实验
  • 256 更像当前线上保守值
  • 再往上加,对当前场景通常是成本大于收益

5. enable_prefix_caching

建议开启。

原因:

  • 一个请求里通常是同一个 query 对很多 doc
  • 前缀共享明显
  • vLLM 在这类场景里能吃到 prefix cache 的收益

6. enforce_eager

建议:

  • 线上常规运行:false
  • smoke / 排障 / 显存紧张时:可临时 true

因为:

  • false 时可使用 compile / graph capture,稳态性能更好
  • true 时启动更直接,问题更容易定位

7. 独立 venv + 独立运行时缓存

这不是“环境洁癖”,而是性能优化的一部分。

收益:

  • 避免不同 vLLM 版本互相污染
  • compile / triton / flashinfer 缓存可复用
  • 便于精确复现实验结果

性能数据应该怎么看

perf_reports/reranker_vllm_instruction/2026-03-25/RESULTS.md 建议按三部分读:

  • 方法论:脚本、预热、--seed、HTTP 客户端延迟与 /health 核对等(可复用于后续复跑)。
  • 主表qwen3_vllm / qwen3_vllm_score × instruction_format 的矩阵基线(固定 max_model_len 等条件见该文)。
  • Addendum:同一方法下对 qwen3_vllm_score 当前实现的补充测数(含 compact/standard),便于与主表对照同一指标口径

对外结论应基于:当前代码 revision文档中注明的 max_model_len / GPU 占用、尽量避免与大压测或其他 GPU 重进程并发时的样本。

benchmark 建议流程

推荐流程:

  1. 确认目标 backend 已切换到正确配置
  2. ./scripts/start_reranker.sh
  3. curl http://127.0.0.1:6007/health
  4. 跑 benchmark 脚本
  5. 保存 JSON 和 Markdown 结果
  6. 记录当时的 GPU 占用情况和 nvidia-smi

重点观察:

  • 单请求延迟
  • 稳态均值
  • 波动大小
  • 冷启动与热启动差异
  • 是否有显存竞争导致的异常样本

常见问题

1. 为什么第一次启动很慢

因为第一次会叠加:

  • 模型加载
  • torch.compile
  • CUDA graph capture
  • flashinfer / triton JIT

这不是异常。看性能时要区分冷启动和稳态。

2. 为什么 smoke 有时会 OOM

常见原因不是参数本身,而是:

  • GPU 上同时还有 embedding / translator / 其他 vLLM 进程
  • smoke 和 benchmark 并发跑
  • gpu_memory_utilization 设得不适合当前剩余显存

处理方式:

  • 先单独跑 smoke
  • nvidia-smi
  • 适当调整 gpu_memory_utilization

3. qwen3_vllm_score 的 attention 要在哪里调

由 vLLM 在运行时按 GPU 与版本自动选择;与延迟和稳定性更直接相关、且建议在仓库里动的,是 max_model_leninfer_batch_sizegpu_memory_utilization、去重、排序分批、prefix cache 等。

代码阅读建议

如果要快速理解当前主线实现,建议按这个顺序读:

  1. reranker/backends/qwen3_vllm_score.py
  2. reranker/backends/qwen3_vllm.py
  3. scripts/start_reranker.sh
  4. scripts/setup_reranker_venv.sh
  5. config/config.yaml 里的 services.rerank.backends.*

阅读重点:

  • 后端如何构造 prompt(instruction_format compact / standard)
  • 后端调用 score() 还是 generate(),以及是否经过整词表上的最后一步分布
  • qwen3_vllm_scoreLLM(...) 传了哪些字段(模型、并行、dtype、缓存等),以及 attention 如何由 vLLM 内部承接
  • 服务层去重 / 排序 / 分批 / 回填怎么做

最终建议

如果你的目标是“当前仓库在 T4 上的在线 reranker 最优落地”,建议直接遵循下面这条线:

  • 主后端:qwen3_vllm_score
  • 模型:Qwen/Qwen3-Reranker-0.6B
  • 配置:instruction_formatstandard 为项目统一基线;细调优先放在批量与长度相关项
  • attention:由 vLLM 运行时自动选择;调参见 max_model_len / infer_batch_size / gpu_memory_utilization
  • 关键参数:max_model_leninfer_batch_sizegpu_memory_utilization
  • 服务层优化:去重、长度排序、分批、prefix cache
  • 工程约束:独立 venv、正确 PATH、缓存目录隔离、单独 smoke、完整 benchmark 归档