Commit cd6d887e288e6b350d3ea71a263cc0d66503c9a9

Authored by tangwang
1 parent ea118f2b

reranker doc

docs/reranker-共享前缀批量推理.md 0 → 100644
... ... @@ -0,0 +1,460 @@
  1 +
  2 +## 共享前缀+批量后缀
  3 +
  4 +**"共享前缀批量推理"**(Shared-Prefix Batch Inference):
  5 +- 输入结构:`[Prefix A] + [Suffix B1]`、`[Prefix A] + [Suffix B2]`、`[Prefix A] + [Suffix B3]`...
  6 +- 痛点:Prefix A被重复计算成千上万次,浪费算力和时间
  7 +- 理想方案:Prefix A只推理一次,KV Cache复用给所有后缀
  8 +
  9 +---
  10 +
  11 +## 一、顶级推荐方案
  12 +
  13 +### 1. **FlashInfer + Cascade Attention** ⭐ 最强技术
  14 +- **核心创新**:Cascade Inference(级联推理)
  15 +- **性能提升**:相比vLLM的PageAttention,在32K token共享前缀、batch size 256场景下可达 **31倍加速**
  16 +- **原理**:
  17 + - 将Attention计算分解为两个阶段:
  18 + 1. **多查询阶段**:对共享前缀使用Multi-Query Kernel,只计算一次,结果存入Shared Memory
  19 + 2. **批量解码阶段**:对每个唯一后缀使用Batch Decode Kernel
  20 + - 使用结合律算子合并部分Attention状态,类似FlashAttention的分块策略
  21 +- **适用场景**:文档QA、系统提示词复用、RAG批量检索
  22 +- **集成**:已集成到SGLang和vLLM中作为后端
  23 +- **GitHub**: https://github.com/flashinfer-ai/flashinfer
  24 +
  25 +### 2. **SGLang + RadixAttention** ⭐ 最实用框架
  26 +- **核心创新**:RadixTree(基数树)管理KV Cache
  27 +- **自动前缀复用**:无需手动配置,自动识别共享前缀并复用KV Cache
  28 +- **性能**:相比vLLM、LMQL等基线系统,结构化工作负载上可达 **6.4倍吞吐量提升** 和 **3.7倍延迟降低**
  29 +- **关键特性**:
  30 + - **In-Batch Prefix Caching**:同一batch内自动共享前缀(如你的A+B1, A+B2场景)
  31 + - **Multi-Item Scoring (MIS)**:LinkedIn用于推荐排序的优化,将多个候选项合并为单次前向传播
  32 + - **Zero-Overhead CPU Scheduler**:GPU计算时CPU并行准备下一batch,利用率接近100%
  33 +- **特别适合**:Agent系统、工具链、RAG应用
  34 +- **GitHub**: https://github.com/sgl-project/sglang
  35 +
  36 +### 3. **vLLM + Automatic Prefix Caching** ⭐ 最成熟稳定
  37 +- **核心机制**:基于哈希表的块级前缀缓存
  38 +- **工作原理**:
  39 + - 将KV Cache按块(默认16 tokens)哈希
  40 + - 新请求先查哈希表,命中则直接复用,只计算新tokens
  41 + - 使用LRU策略管理缓存 eviction
  42 +- **使用方式**:
  43 + ```python
  44 + from vllm import LLM, SamplingParams
  45 + # 启用prefix caching
  46 + llm = LLM(model="your-model", enable_prefix_caching=True)
  47 +
  48 + # 第一次调用缓存前缀
  49 + outputs = llm.generate(long_prefix + prompt_1, sampling_params)
  50 + # 第二次调用自动命中缓存,prefix部分零计算
  51 + outputs = llm.generate(long_prefix + prompt_2, sampling_params)
  52 + ```
  53 +- **注意事项**:vLLM 0.6.3之前调度器未考虑缓存命中率,高并发长序列场景可能性能下降,建议升级到0.6.5+
  54 +
  55 +---
  56 +
  57 +## 二、其他重要方案
  58 +
  59 +### 4. **TensorRT-LLM + In-Flight Batching**
  60 +- **优势**:NVIDIA官方优化,与FlashInfer深度集成
  61 +- **特性**:
  62 + - 支持Prefix Caching(具体实现闭源,但概念类似vLLM)
  63 + - XQA Kernel(Flash Attention 3变体)优化内存访问
  64 + - 层融合技术减少中间结果存储
  65 +- **性能**:在共享前缀数据集上,吞吐量提升 **~34.7%**,TPOT降低 **~20.9%**
  66 +- **适用**:NVIDIA GPU生产环境,追求极致性能
  67 +
  68 +### 5. **LMDeploy + TurboMind**
  69 +- **定位**:纯C++引擎,消除Python开销
  70 +- **性能**:与SGLang相当,在H100上可达 **~16,200 tok/s**(vLLM为12,553 tok/s)
  71 +- **优化**:支持KV Cache量化(8-bit)、Continuous Batching
  72 +- **适用**:高吞吐生产部署,对延迟敏感的场景
  73 +
  74 +### 6. **Daft + Dynamic Prefix Bucketing**(大数据场景)
  75 +- **创新点**:动态前缀分桶 + 流式Continuous Batching
  76 +- **解决痛点**:
  77 + - 全局排序分组会导致GPU空闲
  78 + - 动态分桶在推理同时进行前缀分组,实现流水线
  79 +- **性能**:128 GPU集群上,20万prompts(1.28亿tokens)处理速度提升 **50.7%**
  80 +- **适用**:大规模离线批处理(如数据标注、合成数据生成)
  81 +
  82 +---
  83 +
  84 +## 三、针对你的具体场景建议
  85 +
  86 +### 场景1:在线服务(RAG检索、实时重排序)
  87 +**推荐栈**:**SGLang** 或 **vLLM + FlashInfer后端**
  88 +
  89 +```python
  90 +# SGLang示例:自动前缀复用
  91 +import sglang as sgl
  92 +
  93 +@sgl.function
  94 +def rerank(s, query, docs):
  95 + # query是共享前缀,docs是批量后缀
  96 + s += "Query: " + query + "\n"
  97 + s += "Document: " + sgl.arg(docs) + "\nRelevance:"
  98 + s += sgl.gen("score", max_tokens=5)
  99 +
  100 +# 批量执行,自动共享query部分的KV Cache
  101 +docs = ["doc1 content", "doc2 content", "doc3 content", ...] # 成千上万个
  102 +state = rerank.run_batch(
  103 + [{"query": "user query", "docs": d} for d in docs],
  104 + max_new_tokens=5
  105 +)
  106 +```
  107 +
  108 +### 场景2:离线批量处理(数据标注、索引构建)
  109 +**推荐栈**:**Daft** 或 **FlashInfer原生API**
  110 +
  111 +```python
  112 +# Daft示例:动态前缀分桶
  113 +import daft
  114 +from daft.functions import prompt
  115 +
  116 +df = daft.from_pydict({
  117 + "query": ["shared query"] * 10000,
  118 + "doc": ["doc1", "doc2", ...] # 不同后缀
  119 +})
  120 +
  121 +df = df.with_column("score",
  122 + prompt(
  123 + df["query"] + "\n" + df["doc"],
  124 + provider="vllm-prefix-caching", # 利用前缀缓存
  125 + model="your-model"
  126 + )
  127 +)
  128 +```
  129 +
  130 +### 场景3:Embedding/Reranker模型(Bi-Encoder/Cross-Encoder)
  131 +**推荐栈**:**Sentence-Transformers优化** + **ONNX/TensorRT**
  132 +
  133 +```python
  134 +# Cross-Encoder批量重排序优化
  135 +from sentence_transformers import CrossEncoder
  136 +import numpy as np
  137 +
  138 +class OptimizedReranker:
  139 + def __init__(self, model_name="cross-encoder/ms-marco-MiniLM-L-6-v2"):
  140 + self.model = CrossEncoder(model_name, max_length=512, device="cuda")
  141 +
  142 + def rerank_batch(self, query, documents, batch_size=32):
  143 + # 构建所有pairs:[query, doc1], [query, doc2], ...
  144 + pairs = [[query, doc] for doc in documents]
  145 +
  146 + # 单次批量推理,自动共享query的编码计算
  147 + scores = self.model.predict(
  148 + pairs,
  149 + batch_size=batch_size,
  150 + convert_to_numpy=True
  151 + )
  152 + return np.argsort(scores)[::-1]
  153 +```
  154 +
  155 +---
  156 +
  157 +## 四、性能优化关键技巧
  158 +
  159 +### 1. **Prompt构造策略**
  160 +- **对齐块边界**:确保共享前缀长度是KV Cache块大小(通常是16或32)的整数倍,避免部分块重计算
  161 +- **静态前缀前置**:将不变的指令、系统提示放在最前面
  162 +
  163 +### 2. **Batch构造策略**
  164 +- **前缀分桶(Prefix Bucketing)**:将相同前缀的请求分到同一batch,最大化缓存命中率
  165 +- **长度排序**:同batch内按后缀长度排序,减少padding浪费
  166 +
  167 +### 3. **硬件/精度优化**
  168 +- **FP16/BF16**:混合精度推理,吞吐量提升1.5-2倍
  169 +- **KV Cache量化**:8-bit量化减少内存带宽压力,提升20%吞吐
  170 +- **CUDA Graphs**:捕获静态计算图,减少CPU开销(适合固定batch size场景)
  171 +
  172 +---
  173 +
  174 +## 五、方案对比总结
  175 +
  176 +| 方案 | 前缀复用方式 | 适用场景 | 性能提升 | 易用性 | 成熟度 |
  177 +|------|-------------|---------|---------|--------|--------|
  178 +| **FlashInfer** | Cascade Attention | 超长前缀(32K+) | 31x | 中(需集成) | 高 |
  179 +| **SGLang** | RadixTree自动缓存 | Agent/RAG/工具链 | 6.4x | 高 | 高 |
  180 +| **vLLM** | 哈希块缓存 | 通用在线服务 | 2-5x | 高 | 极高 |
  181 +| **TensorRT-LLM** | 内部优化 | NVIDIA生产环境 | 1.3-2x | 中 | 高 |
  182 +| **LMDeploy** | TurboMind C++引擎 | 低延迟高吞吐 | 1.3x | 中 | 高 |
  183 +| **Daft** | 动态分桶 | 大规模离线批处理 | 50%+ | 高 | 中 |
  184 +
  185 +---
  186 +
  187 +## 六、推荐实施路径
  188 +
  189 +1. **快速验证**:先用 **vLLM** 开启`enable_prefix_caching=True`验证效果,零代码改动
  190 +2. **性能突破**:切换到 **SGLang**,利用RadixAttention和自动batch优化,获得显著性能提升
  191 +3. **极致优化**:对超长前缀场景,使用 **FlashInfer** 的Cascade Attention内核,或集成到自定义推理引擎中
  192 +
  193 +需要针对具体框架的详细配置代码或部署架构建议吗?
  194 +
  195 +
  196 +
  197 +## 天然支持Prefix Caching的Reranker模型清单
  198 +
  199 +最新版本 (v2 系列):混合架构
  200 +在 BGE-Reranker-v2 中,BAAI 为了提升模型上限,引入了不同的底座:
  201 +
  202 +BGE-Reranker-v2-m3: 依然保持了 Encoder-only 架构(基于 XLM-RoBERTa),支持多语言和长文本,具有极高的性价比。
  203 +
  204 +BGE-Reranker-v2-Gemma/Llama: 采用了 Decoder-only 架构。
  205 +
  206 +这些模型是基于 Gemma-2b 或 Llama-3-8B 等大语言模型(LLM)微调而来的。
  207 +
  208 +虽然原生 LLM 是单向(Causal)注意力的,但在作为 Reranker 使用时,模型通常会通过特殊的 Prompt 引导,并取最后一个 Token 的输出经过一个线性层来计算相关性分数。
  209 +
  210 +
  211 +### 1. **BGE-Reranker-V2/V2.5 系列** ⭐ 强烈推荐
  212 +基于Gemma/MiniCPM等Decoder-only架构,FlagEmbedding官方实现已优化
  213 +
  214 +| 模型 | 架构 | 参数量 | 特点 |
  215 +|------|------|--------|------|
  216 +| `BAAI/bge-reranker-v2-gemma` | Gemma-2B (Decoder-only) | 2B | 多语言强,基础版 |
  217 +| `BAAI/bge-reranker-v2-minicpm-layerwise` | MiniCPM-2B (Decoder-only) | 2B | **支持层选择**,可截断到第24层加速 |
  218 +| `BAAI/bge-reranker-v2.5-gemma2-lightweight` | Gemma2-9B (Decoder-only) | 9B | **Token压缩+层选择**,极致效率 |
  219 +
  220 +**Prefix Caching友好特性** :
  221 +- 输入格式:`[Query A] + [SEP] + [Document B] + [Prompt]`
  222 +- Query部分作为前缀,可被所有Document共享
  223 +- 官方代码中已实现`compute_score_single_gpu`的batch处理,自动对齐长度排序减少padding
  224 +
  225 +**使用示例**:
  226 +```python
  227 +from FlagEmbedding import FlagAutoReranker
  228 +
  229 +# 启用vLLM后端 + Prefix Caching
  230 +reranker = FlagAutoReranker.from_finetuned(
  231 + 'BAAI/bge-reranker-v2-gemma',
  232 + model_class='decoder-only-base',
  233 + use_fp16=True,
  234 + devices=['cuda:0']
  235 +)
  236 +
  237 +# 批量推理:Query自动复用KV Cache
  238 +pairs = [
  239 + ('what is panda?', 'The giant panda is a bear species...'), # A+B1
  240 + ('what is panda?', 'Pandas are popular zoo animals.'), # A+B2 (Query复用)
  241 + ('what is panda?', 'Pandas eat bamboo and live in China.'), # A+B3 (Query复用)
  242 +]
  243 +scores = reranker.compute_score(pairs, batch_size=32)
  244 +```
  245 +
  246 +### 2. **Qwen3-Reranker 系列** ⭐ 国产最强
  247 +基于Qwen3 Decoder-only架构,阿里云官方支持
  248 +
  249 +| 模型 | 架构 | 参数量 | 特点 |
  250 +|------|------|--------|------|
  251 +| `Qwen/Qwen3-Reranker-0.6B` | Qwen3-0.6B (Decoder-only) | 0.6B | 超轻量,快速 |
  252 +| `Qwen/Qwen3-Reranker-4B` | Qwen3-4B (Decoder-only) | 4B | 性能均衡 |
  253 +| `Qwen/Qwen3-Reranker-8B` | Qwen3-8B (Decoder-only) | 8B | 精度最高 |
  254 +
  255 +**架构细节** :
  256 +- **纯Decoder-only架构**:使用因果注意力,天然支持Prefix Caching
  257 +- **输入模板**:
  258 + ```
  259 + <|im_start|>system
  260 + You are a helpful assistant.
  261 + <|im_end|>
  262 + <|im_start|>user
  263 + Query: {query}
  264 + Document: {document}
  265 + Does the document answer the query? Please answer Yes or No.
  266 + <|im_end|>
  267 + <|im_start|>assistant
  268 + Yes
  269 + ```
  270 +- **输出**:只生成"Yes"或"No"的logits,作为相关性分数
  271 +
  272 +**vLLM部署优化** :
  273 +```bash
  274 +# 启动vLLM服务,启用Prefix Caching
  275 +python -m vllm.entrypoints.openai.api_server \
  276 + --model Qwen/Qwen3-Reranker-8B \
  277 + --tensor-parallel-size 1 \
  278 + --dtype half \
  279 + --max-model-len 32768 \
  280 + --enable-prefix-caching # 关键参数!提速40%
  281 +```
  282 +
  283 +### 3. **Jina-Reranker-V3** ⭐ 创新架构
  284 +基于Qwen3-0.6B的Listwise重排序器,支持跨Document注意力
  285 +
  286 +| 模型 | 架构 | 参数量 | 特点 |
  287 +|------|------|--------|------|
  288 +| `jinaai/jina-reranker-v3` | Qwen3-0.6B (Decoder-only) | 0.6B | **Listwise**,单次处理64个docs |
  289 +
  290 +**独特优势** :
  291 +- **"Last but Not Late"交互**:在单个context window中同时处理Query+所有Documents
  292 +- **跨Document注意力**:通过因果注意力实现Document间交互,捕捉相对相关性
  293 +- **Prefix Caching优化**:Query放在序列开头,可被所有Document共享
  294 +
  295 +**输入格式** :
  296 +```xml
  297 +<|im_start|>system
  298 +You are a search relevance expert...
  299 +<|im_end|>
  300 +<|im_start|>user
  301 +Rank the passages based on their relevance to query: [QUERY]
  302 +
  303 +<passage id="1">[DOC_1]<|doc_emb|></passage>
  304 +<passage id="2">[DOC_2]<|doc_emb|></passage>
  305 +...
  306 +<passage id="k">[DOC_k]<|doc_emb|></passage>
  307 +
  308 +<query>[QUERY]<|query_emb|></query>
  309 +<|im_end|>
  310 +```
  311 +
  312 +**性能**:BEIR nDCG@10达61.94,超过Qwen3-Reranker-4B,体积小6倍
  313 +
  314 +### 4. **E5-Mistral / NV-Embed-v2 / SFR-Embedding-Mistral**
  315 +基于Mistral-7B Decoder-only架构的Embedding/Reranker
  316 +
  317 +| 模型 | 架构 | 用途 | 特点 |
  318 +|------|------|------|------|
  319 +| `intfloat/e5-mistral-7b-instruct` | Mistral-7B (Decoder-only) | Embedding | 指令微调,支持多任务 |
  320 +| `nvidia/NV-Embed-v2` | Mistral-7B (Decoder-only) | Embedding | 潜在注意力层优化 |
  321 +| `Salesforce/SFR-Embedding-Mistral` | Mistral-7B (Decoder-only) | Embedding | 长上下文优化 |
  322 +
  323 +**注意**:这些主要是**Embedding模型**(Bi-encoder),但可作为Reranker使用(如通过余弦相似度)。若需Cross-encoder式重排序,需配合其他技术。
  324 +
  325 +### 5. **RankGPT / RankZephyr / RankLLaMA**
  326 +基于LLM的生成式重排序器
  327 +
  328 +| 模型 | 架构 | 特点 |
  329 +|------|------|------|
  330 +| `RankGPT` (GPT-3.5/4) | Decoder-only API | 通过Prompt让LLM判断相关性 |
  331 +| `RankZephyr` | Zephyr-7B (Decoder-only) | 蒸馏自RankGPT,开源可部署 |
  332 +| `RankLLaMA` | LLaMA-2/3 (Decoder-only) | 本地部署,隐私友好 |
  333 +
  334 +**实现方式**:
  335 +- 使用LLM的logits或生成"Yes/No"判断相关性
  336 +- 完全基于Decoder-only架构,天然支持Prefix Caching
  337 +
  338 +---
  339 +
  340 +## 不支持Prefix Caching的Reranker(双向架构)
  341 +
  342 +### ❌ **BGE-Reranker-V1 / BGE-Reranker-Base/Large**
  343 +- **架构**:基于XLM-RoBERTa(Encoder-only,双向注意力)
  344 +- **问题**:Query和Document拼接后`[CLS]` token的表示依赖于整个序列,无法分离缓存
  345 +- **适用场景**:轻量级、短文本,对延迟不敏感
  346 +
  347 +### ❌ **Cross-Encoder (BERT-based)**
  348 +- **架构**:BERT/RoBERTa等Encoder-only模型
  349 +- **问题**:
  350 + - 每个Query-Document对必须**联合编码**
  351 + - 前缀的KV Cache与后续token强耦合,无法复用
  352 + - 计算复杂度O((|Q|+|D|)²),无法分解为O(|Q|²) + O(|D|²)
  353 +
  354 +### ❌ **ColBERT / ColPali / Late Interaction模型**
  355 +- **架构**:基于BERT的双向编码 + 后期MaxSim交互
  356 +- **问题**:
  357 + - **独立编码,但双向注意力**:Query和Document分别编码,但各自内部仍是双向
  358 + - **无法Prefix Cache**:虽然Query可独立编码,但Document的编码不依赖于Query,所以不存在"共享前缀"场景
  359 + - **优化点**:Document可**预计算并离线存储**,Query实时编码,然后做MaxSim
  360 + - **本质区别**:这是"离线预计算"而非"Prefix Caching",适用于Document固定、Query变化的场景
  361 +
  362 +**ColBERT的优化策略** :
  363 +```python
  364 +# ColBERT流程:Document预计算(离线) + Query实时编码(在线)
  365 +document_embeddings = encode_documents(docs) # 离线,一次性
  366 +query_embedding = encode_query(query) # 在线,每次查询
  367 +scores = maxsim(query_embedding, document_embeddings) # 轻量级交互
  368 +```
  369 +
  370 +---
  371 +
  372 +## 实际部署建议
  373 +
  374 +### 场景1:高并发在线服务(Query多变,Document固定)
  375 +**推荐**:**Jina-Reranker-V3** 或 **Qwen3-Reranker + vLLM Prefix Caching**
  376 +
  377 +```python
  378 +# vLLM配置优化
  379 +from vllm import LLM, SamplingParams
  380 +
  381 +llm = LLM(
  382 + model="Qwen/Qwen3-Reranker-8B",
  383 + enable_prefix_caching=True, # 关键!
  384 + max_num_seqs=256, # 高并发
  385 + max_model_len=32768
  386 +)
  387 +
  388 +# 批量构造Prompts:共享Query前缀
  389 +query = "What is the capital of France?"
  390 +docs = ["Paris is the capital...", "France is a country...", "Berlin is the capital of Germany..."]
  391 +
  392 +prompts = [
  393 + f"Query: {query}\nDocument: {doc}\nRelevant:"
  394 + for doc in docs
  395 +]
  396 +
  397 +# vLLM自动识别共享前缀,只计算一次Query的KV Cache
  398 +sampling_params = SamplingParams(max_tokens=1, temperature=0)
  399 +outputs = llm.generate(prompts, sampling_params)
  400 +```
  401 +
  402 +### 场景2:离线批量重排序(Query固定,Document多变)
  403 +**推荐**:**ColBERT / ColPali**(Document预计算策略)
  404 +
  405 +```python
  406 +from rankify.models.reranking import Reranking
  407 +from rankify.dataset.dataset import Document, Question, Context
  408 +
  409 +# ColBERT:Document预计算,Query实时编码
  410 +model = Reranking(method='colbert_ranker', model_name='Colbert')
  411 +
  412 +# Documents已预计算并存储
  413 +document = Document(
  414 + question=Question("What is RAG?"),
  415 + contexts=[Context(text=doc, id=i) for i, doc in enumerate(docs)]
  416 +)
  417 +
  418 +# 只需编码Query,然后MaxSim计算
  419 +model.rank([document])
  420 +```
  421 +
  422 +### 场景3:极致性能 + 高精度
  423 +**推荐**:**BGE-Reranker-V2.5-Gemma2-Lightweight**(Token压缩 + 层选择)
  424 +
  425 +```python
  426 +from FlagEmbedding import LightWeightFlagLLMReranker
  427 +
  428 +reranker = LightWeightFlagLLMReranker(
  429 + 'BAAI/bge-reranker-v2.5-gemma2-lightweight',
  430 + devices=["cuda:0"],
  431 + use_fp16=True
  432 +)
  433 +
  434 +# 综合优化:层截断 + Token压缩
  435 +scores = reranker.compute_score(
  436 + pairs,
  437 + cutoff_layers=[28], # 只用前28层
  438 + compress_ratio=4, # Token压缩4倍
  439 + compress_layers=[24, 40] # 特定层压缩
  440 +)
  441 +```
  442 +
  443 +---
  444 +
  445 +## 总结对比表
  446 +
  447 +| 模型 | 架构 | Prefix Caching | 适用场景 | 性能/效率 |
  448 +|------|------|---------------|---------|----------|
  449 +| **BGE-Reranker-V2/V2.5** | Decoder-only ✅ | ✅ 原生支持 | 多语言、生产环境 | 高 |
  450 +| **Qwen3-Reranker** | Decoder-only ✅ | ✅ vLLM支持 | 中文优先、高精度 | 极高 |
  451 +| **Jina-Reranker-V3** | Decoder-only ✅ | ✅ Listwise优化 | 跨Doc交互、Top-K排序 | 极高 |
  452 +| **E5-Mistral/NV-Embed** | Decoder-only ✅ | ✅ 需配合框架 | Embedding+轻量重排 | 高 |
  453 +| **RankGPT/Zephyr** | Decoder-only ✅ | ✅ API/本地 | 生成式判断 | 中 |
  454 +| **BGE-Reranker-V1** | Encoder-only ❌ | ❌ 不支持 | 轻量、短文本 | 低 |
  455 +| **ColBERT/ColPali** | 双向+后期交互 | ❌ 不适用(预计算替代) | Document固定场景 | 中 |
  456 +
  457 +**最终建议**:
  458 +- 如果追求**Prefix Caching加速** + **高精度**:选择 **Qwen3-Reranker-8B** 或 **BGE-Reranker-V2.5**,配合 **vLLM + FlashInfer** 部署
  459 +- 如果需要**跨Document比较**(Listwise):选择 **Jina-Reranker-V3**
  460 +- 如果**Document固定且量大**:选择 **ColBERT** 预计算策略(虽非Prefix Caching,但效率类似)
0 461 \ No newline at end of file
... ...
docs/搜索API对接指南.md
... ... @@ -148,6 +148,17 @@ curl -X POST &quot;http://120.76.41.98:6002/search/&quot; \
148 148  
149 149 - **端点**: `POST /search/`
150 150 - **描述**: 执行文本搜索查询,支持多语言、布尔表达式、过滤器和分面搜索
  151 +- **租户标识**:`tenant_id` 通过 HTTP 请求头 **`X-Tenant-ID`** 传递(推荐);也可通过 URL query 参数 **`tenant_id`** 传递。**不要放在请求体中。**
  152 +
  153 +**请求示例(推荐)**:
  154 +```python
  155 +url = f"{base_url.rstrip('/')}/search/"
  156 +headers = {
  157 + "Content-Type": "application/json",
  158 + "X-Tenant-ID": "162", # 租户ID,必填
  159 +}
  160 +response = requests.post(url, headers=headers, json={"query": "芭比娃娃"})
  161 +```
151 162  
152 163 ### 3.2 请求参数
153 164  
... ... @@ -605,6 +616,7 @@ curl &quot;http://localhost:6002/search/instant?q=玩具&amp;size=5&quot;
605 616  
606 617 - **端点**: `GET /search/{doc_id}`
607 618 - **描述**: 根据文档 ID 获取单个商品详情,用于点击结果后的详情页或排查问题。
  619 +- **租户标识**:同 [3.1](#31-接口信息),通过请求头 `X-Tenant-ID` 或 query 参数 `tenant_id` 传递。
608 620  
609 621 #### 路径参数
610 622  
... ... @@ -630,7 +642,8 @@ curl &quot;http://localhost:6002/search/instant?q=玩具&amp;size=5&quot;
630 642 #### 请求示例
631 643  
632 644 ```bash
633   -curl "http://localhost:6002/search/12345"
  645 +curl "http://localhost:6002/search/12345" -H "X-Tenant-ID: 162"
  646 +# 或使用 query 参数:curl "http://localhost:6002/search/12345?tenant_id=162"
634 647 ```
635 648  
636 649 ---
... ... @@ -1287,6 +1300,8 @@ curl -X GET &quot;http://localhost:6004/indexer/health&quot;
1287 1300  
1288 1301 ## 常见场景示例
1289 1302  
  1303 +以下示例仅展示**请求体**(body);实际调用时请加上请求头 `X-Tenant-ID: <租户ID>`(或 URL 参数 `tenant_id`),参见 [3.1 接口信息](#31-接口信息)。
  1304 +
1290 1305 ### 7.1 基础搜索与排序
1291 1306  
1292 1307 **按价格从低到高排序**:
... ...
indexer/document_transformer.py
... ... @@ -348,7 +348,8 @@ class SPUDocumentTransformer:
348 348 if pd.notna(spu_row.get('image_src')):
349 349 image_src = str(spu_row['image_src'])
350 350 if not image_src.startswith('http'):
351   - image_src = f"//{image_src}" if image_src.startswith('//') else image_src
  351 + # 仅当尚未是协议相对 URL 时才补 "//",避免 "//host" 变成 "////host"
  352 + image_src = f"//{image_src}" if not image_src.startswith('//') else image_src
352 353 doc['image_url'] = image_src
353 354  
354 355 def _process_skus(
... ...