Commit 208e079afcaceab40b086e3c1dbb0c3140dd8cc3

Authored by tangwang
1 parent af827ce9

TODO

@@ -4,6 +4,59 @@ @@ -4,6 +4,59 @@
4 4
5 5
6 6
  7 +融合打分:
  8 +1. 融合公式
  9 +def fuse_scores(self, context: SearchContext) -> None:
  10 + for result in context.results:
  11 + # 计算文本相关性分数
  12 + text_score = (
  13 + result.es_base_score * 0.5 +
  14 + result.es_phrase_score * 0.3 +
  15 + result.es_keywords_score * 0.2
  16 + )
  17 +
  18 + # 最终融合分数
  19 + result.fused_score = (
  20 + (result.rerank_text + 0.00001) ** 1.0 *
  21 + (result.rerank_boost + 0.1) ** 1.0 *
  22 + (result.es_knn_score + 0.6) ** 0.2 *
  23 + (text_score + 0.1) ** 0.77
  24 + )
  25 +
  26 +2. matched_queries
  27 +
  28 + # Add debug information if matched_queries is present and from_search_debug is True
  29 + if 'matched_queries' in hit:
  30 + # Extract individual scores from matched_queries
  31 + matched_queries = hit['matched_queries']
  32 + if context.from_search_debug:
  33 + result.matched_queries = matched_queries
  34 + if isinstance(matched_queries, dict):
  35 + result.es_knn_score = matched_queries.get('knn_query', 0.0)
  36 + result.es_phrase_score = matched_queries.get('phrase_query', 0.0)
  37 + result.es_base_score = matched_queries.get('base_query', 0.0)
  38 + result.es_keywords_score = matched_queries.get('keywords_query', 0.0)
  39 + result.es_tags_score = matched_queries.get('tags_query', 0.0)
  40 + elif isinstance(matched_queries, list):
  41 + for query in matched_queries:
  42 + if query == 'knn_query':
  43 + result.es_knn_score = 1.0
  44 + elif query == 'phrase_query':
  45 + result.es_phrase_score = 1.0
  46 + elif query == 'base_query':
  47 + result.es_base_score = 1.0
  48 + elif query == 'keywords_query':
  49 + result.es_keywords_score = 1.0
  50 + elif query == 'tags_query':
  51 + result.es_tags_score = 1.0
  52 +
  53 +es_query["track_scores"] = True
  54 +为了获取 需要打开 track_scores ? 不一定
  55 +Track scores
  56 +When sorting on a field, scores are not computed. By setting track_scores to true, scores will still be computed and tracked.
  57 +
  58 +
  59 +
7 60
8 61
9 翻译的health很慢 62 翻译的health很慢
@@ -67,10 +120,38 @@ translation: @@ -67,10 +120,38 @@ translation:
67 120
68 121
69 122
  123 +翻译,增加facebook/nllb-200-distilled-600M
  124 +https://blog.csdn.net/qq_42746084/article/details/154947534
  125 +https://huggingface.co/facebook/nllb-200-distilled-600M
  126 +
  127 +
  128 +店铺的语言:英语能占到80%,所以专门增加一个en-zh的
  129 +https://huggingface.co/Helsinki-NLP/opus-mt-zh-en
  130 +https://huggingface.co/Helsinki-NLP/opus-mt-en-zh
  131 +
  132 +
  133 +opus-mt-zh-en
  134 +
  135 +from transformers import AutoTokenizer, AutoModelForSeq2SeqLM
  136 +model_name = "./models/opus-mt-en-zh"
  137 +tokenizer = AutoTokenizer.from_pretrained(model_name)
  138 +model = AutoModelForSeq2SeqLM.from_pretrained(model_name)
  139 +data = 'test'
  140 +encoded = tokenizer([data], return_tensors="pt")
  141 +translation = model.generate(**encoded)
  142 +result = tokenizer.batch_decode(translation, skip_special_tokens=True)[0]
  143 +print(result)
  144 +
70 145
71 146
72 147
73 148
  149 +Qwen3-Reranker-4B-GGUF
  150 +https://modelscope.cn/models/dengcao/Qwen3-Reranker-4B-GGUF/summary
  151 +1. 要确定选择哪种量化方式
  152 +2. 确定提示词
  153 +
  154 +
74 155
75 156
76 查看翻译的缓存情况 157 查看翻译的缓存情况
@@ -114,26 +195,17 @@ HOST:10.200.16.14 / localhost @@ -114,26 +195,17 @@ HOST:10.200.16.14 / localhost
114 用户名密码:saas:4hOaLaf41y2VuI8y 195 用户名密码:saas:4hOaLaf41y2VuI8y
115 196
116 197
117 -你安装过nvidia-container-toolkit吗  
118 -现在有一些开源的推理引擎对向量化模型和重排模型支持的比较好,我们这块也正好要单独拎出来,因此想改造下。  
119 -已决策:embedding 先统一为 Qwen3-Embedding-0.6B(Sentence Transformers,本地 6005),后续若要独立高并发服务优先评估 TEI。  
120 -最好以 docker 方式部署,让 gpu 对 docker 可见需要 nvidia-container-toolkit,  
121 -我试了多种方法安装 nvidia-container-toolkit 都失败了 198 +安装 nvidia-container-toolkit (done)
122 https://mirrors.aliyun.com/github/releases/NVIDIA/nvidia-container-toolkit/ 199 https://mirrors.aliyun.com/github/releases/NVIDIA/nvidia-container-toolkit/
123 https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/index.html 200 https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/index.html
124 201
125 202
126 -qwen3-embedding  
127 -qwen3-reranker  
128 - 203 +qwen3-embedding、qwen3-reranker (done)
129 选一个推理引擎,相比于我自己直接调 sentence-transformers,主要是多进程和负载均衡、连续批处理,比较有用 204 选一个推理引擎,相比于我自己直接调 sentence-transformers,主要是多进程和负载均衡、连续批处理,比较有用
130 当前结论:embedding 场景优先 TEI;vLLM 更偏向生成式与 rerank 场景。 205 当前结论:embedding 场景优先 TEI;vLLM 更偏向生成式与 rerank 场景。
131 206
132 207
133 208
134 -  
135 -  
136 -  
137 混用 大模型 使用:hunyuan-turbos-latest 209 混用 大模型 使用:hunyuan-turbos-latest
138 混元 OpenAI 兼容接口相关调用示例:https://cloud.tencent.com/document/product/1729/111007 210 混元 OpenAI 兼容接口相关调用示例:https://cloud.tencent.com/document/product/1729/111007
139 211
@@ -144,11 +216,11 @@ hunyuan翻译:使用模型 hunyuan-translation @@ -144,11 +216,11 @@ hunyuan翻译:使用模型 hunyuan-translation
144 https://cloud.tencent.com/document/product/1729/113395#4.-.E7.A4.BA.E4.BE.8B 216 https://cloud.tencent.com/document/product/1729/113395#4.-.E7.A4.BA.E4.BE.8B
145 217
146 218
147 -  
148 谷歌翻译 基础版:https://docs.cloud.google.com/translate/docs/reference/rest/v2/translate 219 谷歌翻译 基础版:https://docs.cloud.google.com/translate/docs/reference/rest/v2/translate
149 220
150 221
151 222
  223 +
152 阿里云 百炼模型 现在使用的apikey是国内的。 224 阿里云 百炼模型 现在使用的apikey是国内的。
153 各地域的 Base URL 和对应的 API Key 是绑定的。 225 各地域的 Base URL 和对应的 API Key 是绑定的。
154 226
docs/工作总结-微服务性能优化与架构.md 0 → 100644
@@ -0,0 +1,252 @@ @@ -0,0 +1,252 @@
  1 +# 工作总结:微服务性能优化与架构
  2 +
  3 +本文档汇总**三个微服务的性能优化**、**架构设计**及**性能测试基线**,便于汇报与后续复现。
  4 +
  5 +---
  6 +
  7 +## 一、完成三个微服务的性能优化
  8 +
  9 +### 1. 文本向量(Embedding)
  10 +
  11 +**调研与选型**:对 TEI(Text Embeddings Inference)、vLLM 与原有 SentenceTransformers 方案进行对比评估。
  12 +
  13 +| 方案 | 特点 | 结论 |
  14 +|------|------|------|
  15 +| **SentenceTransformers** | 本地 Python 进程,单机推理,无独立服务化 | 此前用于 Qwen3-Embedding-0.6B,扩展性与运维性一般 |
  16 +| **vLLM** | 高吞吐推理框架,更适合生成式与重排混合场景 | 纯 embedding 场景通常不作为首选 |
  17 +| **TEI** | HuggingFace 官方 embedding 专用推理服务,Docker 部署 | **当前最优选型** |
  18 +
  19 +**当前方案**:以 **TEI** 为文本向量后端,模型为 `Qwen/Qwen3-Embedding-0.6B`,embedding 服务(端口 **6005**)将 `POST /embed/text` 请求转发至 TEI(默认端口 **8080**)。
  20 +
  21 +**具体配置与脚本**:
  22 +- **配置**:`config/config.yaml` → `services.embedding.backend: "tei"`,`services.embedding.backends.tei.base_url: "http://127.0.0.1:8080"`、`model_id: "Qwen/Qwen3-Embedding-0.6B"`、`timeout_sec: 20`。
  23 +- **启动**:`scripts/start_tei_service.sh`(Docker 容器);环境变量:`TEI_DEVICE=cuda`(默认)、`TEI_PORT=8080`、`TEI_MODEL_ID=Qwen/Qwen3-Embedding-0.6B`、`TEI_VERSION=1.9`、`TEI_MAX_BATCH_TOKENS=4096`、`TEI_MAX_CLIENT_BATCH_SIZE=24`、`TEI_DTYPE=float16`;T4 自动选镜像 `ghcr.io/huggingface/text-embeddings-inference:turing-1.9`。
  24 +- **编排**:`./scripts/service_ctl.sh start tei` 或 `start embedding`(embedding 会校验 TEI `/health` 后再就绪)。
  25 +
  26 +**工程化收益**:
  27 +- **独立服务**:TEI 以 Docker 容器运行,与主程序 `.venv` 解耦;embedding 使用 `.venv-embedding`,便于独立扩缩容与升级。
  28 +- **协议统一**:与 `local_st`(SentenceTransformers)可插拔,通过 `config.yaml` 的 `services.embedding.backend` 或环境变量 `EMBEDDING_BACKEND` 切换。
  29 +- **T4 适配**:按 GPU 架构自动选择镜像(T4 用 `turing-*`,Ampere+ 用 `cuda-*`);`TEI_MAX_BATCH_TOKENS`、`TEI_MAX_CLIENT_BATCH_SIZE`、`TEI_DTYPE` 已按短文本场景调优。
  30 +- **健康检查与编排**:`service_ctl.sh` 对 tei 校验容器存在且 `GET http://127.0.0.1:8080/health` 成功;embedding 启动前会等待 TEI 健康。
  31 +
  32 +详见:`docs/TEI_SERVICE说明文档.md`、`docs/QUICKSTART.md` §4。
  33 +
  34 +---
  35 +
  36 +### 2. 重排(Reranker)
  37 +
  38 +**后端**:vLLM 部署 **Qwen3-Reranker-0.6B**,对外提供 `POST /rerank`(query + docs → scores),端口 **6007**。
  39 +
  40 +**针对 T4 的优化(已落地配置)**:
  41 +- **精度**:`dtype: "float16"`,降低显存与计算量。
  42 +- **Prefix Caching**:`enable_prefix_caching: true`,对重复前缀(如相同 query)做缓存,减少重复计算。
  43 +- **CUDA 图**:`enforce_eager: false`(默认),利用 vLLM 的 CUDA graph 降低 kernel 启动开销。
  44 +- **按文档长度分批**:`sort_by_doc_length: true`,请求内先按文档长度排序再按 `infer_batch_size` 分批推理,减少 padding 浪费;`length_sort_mode: "char"`(更快,短文本推荐)或 `"token"`(更精确)。
  45 +- **参数搜索结论**:在 T4、1000-doc 口径下对 `infer_batch_size` 做了 24/32/48/64 对比;**单请求延迟(c=1)** 上 `infer_batch_size=64` 最优,故当前默认 `infer_batch_size: 64`;`max_model_len: 256` 满足 query+doc 短文本场景;`gpu_memory_utilization: 0.36` 与 T4 16GB 匹配。
  46 +
  47 +**具体配置**(`config/config.yaml` → `services.rerank.backends.qwen3_vllm`):
  48 +```yaml
  49 +model_name: "Qwen/Qwen3-Reranker-0.6B"
  50 +engine: "vllm"
  51 +max_model_len: 256
  52 +tensor_parallel_size: 1
  53 +gpu_memory_utilization: 0.36
  54 +dtype: "float16"
  55 +enable_prefix_caching: true
  56 +enforce_eager: false
  57 +infer_batch_size: 64
  58 +sort_by_doc_length: true
  59 +length_sort_mode: "char"
  60 +instruction: "Given a shopping query, rank product titles by relevance"
  61 +```
  62 +环境变量覆盖:`RERANK_BACKEND`、`RERANKER_SERVICE_URL`、`RERANK_VLLM_INFER_BATCH_SIZE`、`RERANK_VLLM_SORT_BY_DOC_LENGTH` 等。启停:`./scripts/service_ctl.sh start reranker`,健康:`curl -sS http://127.0.0.1:6007/health`。
  63 +
  64 +详见:`reranker/DEPLOYMENT_AND_TUNING.md`、`reranker/README.md`。
  65 +
  66 +---
  67 +
  68 +### 3. 图片向量(Image Embedding)
  69 +
  70 +**方案**:**clip-as-service**(CN-CLIP,ViT-H-14),由独立服务提供图片向量化能力。
  71 +
  72 +**具体内容**:
  73 +- **端口**:clip-as-service 默认 **51000**(`CNCLIP_PORT`);文本走 TEI(8080),图片走 clip-as-service。
  74 +- **API**:embedding 服务(6005)统一暴露 `POST /embed/text` 与 `POST /embed/image`;图片请求由 `embeddings/server.py` 按配置调用实现 `ImageEncoderProtocol` 的后端(clip-as-service 或本地 CN-CLIP)。
  75 +- **环境与启停**:CN-CLIP 使用独立虚拟环境 `.venv-cnclip`;启动 `scripts/start_cnclip_service.sh`,或 `./scripts/service_ctl.sh start cnclip`;设备可通过 `CNCLIP_DEVICE=cuda`(默认)或 `cpu` 指定。
  76 +- **配置**:图片后端在 `config/config.yaml` 的 `services.embedding` 下配置(若存在 image 相关 backend);clip-as-service 的 flow 配置在 `third-party/clip-as-service/server/torch-flow-temp.yml`。
  77 +
  78 +详见:`docs/CNCLIP_SERVICE说明文档.md`、`embeddings/README.md`。
  79 +
  80 +---
  81 +
  82 +### 4. 翻译(Translation)
  83 +
  84 +**背景**:原使用 DeepL,后迁移至 **qwen-mt**(如 `qwen-mt-flash`)。qwen-mt 云端限速约 **RPM=60(每分钟 60 请求)**,此前未做大商品量压测,未暴露问题;高并发索引或查询场景下易触限。
  85 +
  86 +**当前方案**:
  87 +- **迁移至 qwen-flash**:在配置中将翻译改为 **LLM provider + qwen-flash 模型**,由 DashScope 兼容 API 调用,可配置化切换。
  88 +- **可配置化(具体配置)**:
  89 + - **入口**:`config/config.yaml` → `services.translation`;`provider: "llm"` 时使用 `providers.llm`,`model: "qwen-flash"`,`timeout_sec: 30`,`base_url` 可选(为空则用 `DASHSCOPE_BASE_URL`);环境变量 `DASHSCOPE_API_KEY` 注入 Key。
  90 + - **Provider 取值**:`provider` 可为 `http`(走翻译服务 6006)、`qwen-mt`(直连 qwen-mt-flush 等)、`deepl`(DeepL API)、`llm`(对话模型 qwen-flash 等);工厂函数 `providers/translation.py` 的 `create_translation_provider(query_config)` 根据 `get_translation_config()` 解析结果返回对应实现。
  91 + - **调用位置**:QueryParser(`query/query_parser.py`)与 Indexer(`indexer/incremental_service.py`、`indexer/indexing_utils.py`)均通过 `create_translation_provider(...)` 获取实例,不写死 URL 或模型名。
  92 +- **缓存**:`services.translation.cache` 支持 `key_prefix: "trans:v2"`、`ttl_seconds`、`sliding_expiration` 等,翻译结果写 Redis,减轻重复请求对限速的影响。
  93 +- **场景支撑**:在线索引(indexer)与 query 请求(QueryParser)共用同一套 provider 配置;可按环境或租户通过修改 `config.yaml` 或环境变量切换 provider/model。
  94 +- **待配合**:**金伟侧对索引侧翻译调用做流量控制**(限流/排队/批量聚合),避免索引高峰打满 qwen 限速,影响在线 query 翻译。
  95 +
  96 +---
  97 +
  98 +### 5. 内容理解字段(支撑 Suggest)
  99 +
  100 +**能力**:支持根据商品标题批量生成 **qanchors**(锚文本)、**semantic_attributes**、**tags**,供索引与 suggest 使用。
  101 +
  102 +**具体内容**:
  103 +- **接口**:`POST /indexer/enrich-content`(Indexer 服务端口 **6004**)。请求体为 `items` 数组,每项含 `spu_id`、`title`(必填)及可选多语言标题等;单次请求最多 **50 条**,建议批量调用。响应 `results` 与 `items` 一一对应,每项含 `spu_id`、`qanchors`(按语言键,如 `qanchors.zh`、`qanchors.en`,逗号分隔短语)、`semantic_attributes`、`tags`。
  104 +- **索引侧**:微服务组合方式下,调用方先拿不含 qanchors/tags 的 doc,再调用本接口补齐后写入 ES 的 `qanchors.{lang}` 等字段;索引 transformer(`indexer/document_transformer.py`、`indexer/product_annotator.py`)内也可在构建 doc 时调用内容理解逻辑,写入 `qanchors.{lang}`。
  105 +- **Suggest 侧**:`suggestion/builder.py` 从 ES 商品索引读取 `_source: ["id", "spu_id", "title", "qanchors"]`,对 `qanchors.{lang}` 用 `_split_qanchors` 拆成词条,以 `source="qanchor"` 加入候选,排序时 `qanchor` 权重大于纯 title(`add_product("qanchor", ...)`);suggest 配置中 `sources: ["query_log", "qanchor"]` 表示候选来源包含 qanchor。
  106 +- **实现与依赖**:内容理解内部使用大模型(需 `DASHSCOPE_API_KEY`),支持多语言与 Redis 缓存(如 `product_anchors`);逻辑与 `indexer/product_annotator` 一致。
  107 +
  108 +**状态**:内容理解字段已接入索引与 suggest 链路;依赖内容理解(qanchors/tags)的**全量数据尚未全部完成一轮**,后续需持续跑满并校验效果。
  109 +
  110 +详见:`indexer/ANCHORS_AND_SEMANTIC_ATTRIBUTES.md`、`docs/搜索API对接指南.md`(内容理解接口)、`api/routes/indexer.py`(enrich-content 路由)。
  111 +
  112 +---
  113 +
  114 +## 二、架构
  115 +
  116 +### 1. Provider 与动态选择翻译
  117 +
  118 +- **设计**:参考 `docs/系统设计文档.md`、`docs/DEVELOPER_GUIDE.md`,翻译/向量/重排均采用 **Provider + Backend** 解耦;配置单一来源为 `config/config.yaml` 的 `services` 块,环境变量可覆盖。
  119 +- **翻译(具体实现)**:
  120 + - **工厂**:`providers/translation.py` 的 `create_translation_provider(query_config)`;内部调用 `config/services_config.get_translation_config()` 得到 `provider` 与 `providers.<name>` 参数。
  121 + - **分支**:`provider in ("qwen-mt", "direct", "local", "inprocess")` → 使用 `query/qwen_mt_translate.py` 的 `Translator`(model 如 qwen-mt-flush);`provider == "http"` 或 `"service"` → `HttpTranslationProvider`(base_url 为翻译服务 6006,model 如 qwen);`provider == "llm"` → `query/llm_translate.py` 的 `LLMTranslatorProvider`(model 如 qwen-flash,base_url 可选);`provider == "deepl"` → `query/deepl_provider.py` 的 `DeepLProvider`。
  122 + - **调用方**:`query/query_parser.py`(搜索前翻译)、`indexer/incremental_service.py`、`indexer/indexing_utils.py`(索引时翻译)均通过上述工厂获取实例,不写死 URL 或模型名。
  123 +- **效果**:仅改 `config.yaml` 的 `services.translation.provider` 及对应 `providers.<name>` 即可切换 DeepL、qwen-mt、qwen-flash(llm)、HTTP 翻译服务等。
  124 +
  125 +### 2. 服务的监控与拉起机制
  126 +
  127 +- **脚本**:`scripts/service_ctl.sh` 统一负责各服务的生命周期与监控;依赖 `scripts/lib/load_env.sh` 与项目根目录 `.env`。
  128 +- **服务与端口**:
  129 + - 核心:backend **6002**、indexer **6004**、frontend **6003**。
  130 + - 可选:embedding **6005**、translator **6006**、reranker **6007**、tei **8080**、cnclip **51000**。
  131 + - 端口可由环境变量覆盖:`API_PORT`、`INDEXER_PORT`、`FRONTEND_PORT`、`EMBEDDING_PORT`、`TRANSLATION_PORT`、`RERANKER_PORT`、`TEI_PORT`、`CNCLIP_PORT`。
  132 +- **命令**:
  133 + - `./scripts/service_ctl.sh start [service...]` 或 `start all`(all = tei cnclip embedding translator reranker backend indexer frontend,按依赖顺序);`stop`、`restart` 同参数;`status` 默认列出所有服务。
  134 + - 启动时:backend/indexer/frontend/embedding/translator/reranker 会写 pid 到 `logs/<service>.pid`,并执行 `wait_for_health`(GET `http://127.0.0.1:<port>/health`);reranker 健康重试 90 次,其余 30 次;TEI 校验 Docker 容器存在且 `/health` 成功;cnclip 无 HTTP 健康则仅校验进程/端口。
  135 +- **监控常驻**:
  136 + - `./scripts/service_ctl.sh monitor-start <targets>` 启动后台监控进程,将 targets 写入 `logs/service-monitor.targets`,pid 写入 `logs/service-monitor.pid`,日志追加到 `logs/service-monitor.log`。
  137 + - 轮询间隔 `MONITOR_INTERVAL_SEC` 默认 **10** 秒;连续 **3** 次(`MONITOR_FAIL_THRESHOLD`)健康失败则触发重启;重启冷却 `MONITOR_RESTART_COOLDOWN_SEC` 默认 **30** 秒;每小时最多重启 `MONITOR_MAX_RESTARTS_PER_HOUR` 默认 **6** 次;超限时调用 `scripts/wechat_alert.py` 告警(若存在)。
  138 +- **日志**:各服务按日滚动到 `logs/<service>-<date>.log`,通过 `scripts/daily_log_router.sh` 与 `LOG_RETENTION_DAYS`(默认 30)控制保留。
  139 +
  140 +详见:`scripts/service_ctl.sh` 内注释及 `docs/Usage-Guide.md`。
  141 +
  142 +### 3. Suggest 索引与增量
  143 +
  144 +- **当前**:suggest 索引由**全量脚本触发**,入口为 `main.py build-suggestions`,或封装脚本 `scripts/build_suggestions.sh <tenant_id> [args...]`。
  145 + - **全量示例**:`./scripts/build_suggestions.sh 162 --mode full --days 30 --publish-alias`(重建建议索引,近 30 天数据,并发布别名)。
  146 + - **增量示例**:`./scripts/build_suggestions.sh 162 --mode incremental --overlap-minutes 30`(按 watermark 增量更新);脚本内部调用 `main.py build-suggestions --tenant-id <id> ...`。
  147 + - 构建逻辑在 `suggestion/builder.py` 的 `SuggestionIndexBuilder`:从 ES 商品索引(含 `title`、`qanchors`)与查询日志等拉取数据,写入 versioned 建议索引并切换 alias。
  148 +- **尚未完成的“增量机制”**:指**自动/事件驱动的增量**(如商品变更或日志写入时自动刷新建议索引);当前 incremental 模式为“按 watermark 再跑一次构建”,仍为脚本主动触发,非持续增量流水线。
  149 +- **依赖**:suggest 候选依赖商品侧 **内容理解字段**(qanchors/tags);`sources: ["query_log", "qanchor"]` 表示候选来自查询日志与 qanchor;当前内容理解未全量跑完一轮,suggest 数据会随全量重建逐步完善。
  150 +
  151 +详见:`suggestion/builder.py`、`suggestion/ARCHITECTURE_V2.md`、`main.py`(build-suggestions 子命令)。
  152 +
  153 +---
  154 +
  155 +## 三、性能测试报告摘要
  156 +
  157 +以下数据来自 `docs/性能测试报告.md`,测试时间 **2026-03-12**,环境:**8 vCPU**(Intel Xeon Platinum 8255C @ 2.50GHz)、**约 15Gi 可用内存**;租户 **162** 文档数约 **53**(search/suggest/rerank 与文档规模相关)。压测工具:`scripts/perf_api_benchmark.py`,场景×并发矩阵,每档 **20s**。
  158 +
  159 +**复现命令(四场景×四并发)**:
  160 +```bash
  161 +cd /data/saas-search
  162 +.venv/bin/python scripts/perf_api_benchmark.py \
  163 + --scenario backend_search,backend_suggest,embed_text,rerank \
  164 + --concurrency-list 1,5,10,20 \
  165 + --duration 20 \
  166 + --tenant-id 162 \
  167 + --backend-base http://127.0.0.1:6002 \
  168 + --embedding-base http://127.0.0.1:6005 \
  169 + --translator-base http://127.0.0.1:6006 \
  170 + --reranker-base http://127.0.0.1:6007 \
  171 + --output perf_reports/2026-03-12/perf_matrix_report.json
  172 +```
  173 +执行前需启动服务:`./scripts/service_ctl.sh start embedding translator reranker backend`,并通过各端口 `/health` 检查。Reranker 单独 386-docs 口径复现见性能测试报告 §7.4 与 §13。
  174 +
  175 +### 3.1 向量化服务(embed_text)
  176 +
  177 +| 并发 | 请求数 | 成功率 | 吞吐(RPS) | Avg(ms) | P95(ms) | Max(ms) |
  178 +|-----:|-------:|-------:|----------:|--------:|--------:|--------:|
  179 +| 1 | 966 | 100% | 48.27 | 20.63 | 23.41 | 49.80 |
  180 +| 5 | 1796 | 100% | 89.57 | 55.55 | 69.62 | 109.84 |
  181 +| 10 | 2095 | 100% | 104.42 | 95.22 | 117.66 | 152.48 |
  182 +| 20 | 2393 | 100% | 118.70 | 167.37 | 212.21 | 318.70 |
  183 +
  184 +**结论**:随并发提升吞吐持续增长,延迟平滑上升,扩展性较好。
  185 +
  186 +---
  187 +
  188 +### 3.2 Reranker(rerank)
  189 +
  190 +口径:query 固定 `wireless mouse`,每次请求 **386 docs**,句长 15–40 词随机(从 1000 词池采样);配置 `rerank_window=384`。复现命令:
  191 +```bash
  192 +.venv/bin/python scripts/perf_api_benchmark.py \
  193 + --scenario rerank --duration 20 --concurrency-list 1,5,10,20 --timeout 60 \
  194 + --rerank-dynamic-docs --rerank-doc-count 386 --rerank-vocab-size 1000 \
  195 + --rerank-sentence-min-words 15 --rerank-sentence-max-words 40 \
  196 + --rerank-query "wireless mouse" --rerank-seed 20260312 \
  197 + --reranker-base http://127.0.0.1:6007 \
  198 + --output perf_reports/2026-03-12/rerank_realistic/rerank_386docs.json
  199 +```
  200 +
  201 +| 并发 | 请求数 | 成功率 | 吞吐(RPS) | Avg(ms) | P95(ms) | Max(ms) |
  202 +|-----:|-------:|-------:|----------:|--------:|--------:|--------:|
  203 +| 1 | 14 | 100% | 0.67 | 1498.64 | 1799.25 | 2160.96 |
  204 +| 5 | 15 | 100% | 0.62 | 8011.99 | 9725.61 | 9726.02 |
  205 +| 10 | 20 | 100% | 0.61 | 16217.12| 18043.05| 18050.04|
  206 +| 20 | 20 | 100% | 0.60 | 33252.35| 33456.74| 33480.14|
  207 +
  208 +**结论**:在 386 docs/请求 的真实口径下,吞吐约 **0.6 rps**,延迟随并发明显上升(并发 20 时 P95 约 33.5s),是当前整条链路的**最重瓶颈**。与 DashScope 云后端对比:单并发 vLLM 延迟更优;并发 5+ 时云后端吞吐更高(约 1.3x–6x),在线高并发场景可优先考虑云后端。
  209 +
  210 +---
  211 +
  212 +### 3.3 搜索(backend_search)
  213 +
  214 +| 并发 | 请求数 | 成功率 | 吞吐(RPS) | Avg(ms) | P95(ms) | Max(ms) |
  215 +|-----:|-------:|-------:|----------:|--------:|--------:|--------:|
  216 +| 1 | 160 | 100% | 7.98 | 124.89 | 228.06 | 345.49 |
  217 +| 5 | 161 | 100% | 7.89 | 628.91 | 1271.49| 1441.02|
  218 +| 10 | 181 | 100% | 8.78 | 1129.23| 1295.88| 1330.96|
  219 +| 20 | 161 | 100% | 7.63 | 2594.00| 4706.44| 4783.05|
  220 +
  221 +**结论**:吞吐约 **8 rps** 平台化,延迟随并发上升明显,符合“检索 + 向量 + 重排”重链路特征。多租户补测(文档数 500–10000,见报告 §12)表明:文档数越大,RPS 下降、延迟升高;tenant 0(10000 doc)在并发 20 出现部分 ReadTimeout(成功率 59.02%),需注意 timeout 与容量规划;补测命令示例:`for t in 0 1 2 3 4; do .venv/bin/python scripts/perf_api_benchmark.py --scenario backend_search --concurrency-list 1,5,10,20 --duration 20 --tenant-id $t --output perf_reports/2026-03-12/search_tenant_matrix/tenant_${t}.json; done`。
  222 +
  223 +---
  224 +
  225 +### 3.4 Suggest(backend_suggest)
  226 +
  227 +| 并发 | 请求数 | 成功率 | 吞吐(RPS) | Avg(ms) | P95(ms) | Max(ms) |
  228 +|-----:|-------:|-------:|----------:|--------:|--------:|--------:|
  229 +| 1 | 3502 | 100% | 175.09 | 5.68 | 8.70 | 15.98 |
  230 +| 5 | 4168 | 100% | 208.10 | 23.93 | 36.93 | 59.53 |
  231 +| 10 | 4152 | 100% | 207.25 | 48.05 | 59.45 | 127.20 |
  232 +| 20 | 4190 | 100% | 208.99 | 95.20 | 110.74 | 181.37 |
  233 +
  234 +**结论**:吞吐高且稳定(**约 200+ rps**),对并发友好,与 search/rerank 相比压力较小。
  235 +
  236 +---
  237 +
  238 +## 四、总结
  239 +
  240 +| 维度 | 要点 |
  241 +|------|------|
  242 +| **Embedding** | TEI 替代 SentenceTransformers/vLLM 作为文本向量后端,兼顾性能与工程化(Docker、配置化、T4 调优);图片向量由 clip-as-service 承担。 |
  243 +| **Reranker** | vLLM + Qwen3-Reranker-0.6B,针对 T4 做 float16、prefix caching、CUDA 图、按长度分批及 batch/长度参数搜索;高并发场景可选用 DashScope 云后端。 |
  244 +| **翻译** | 因 qwen-mt 限速(RPM≈60),迁移至可配置的 qwen-flash 等方案,支撑在线索引与 query;需金伟侧对索引做流量控制。 |
  245 +| **内容理解** | 提供 qanchors/tags 等字段生成接口,支撑 suggest 与检索增强;全量一轮尚未完全跑满。 |
  246 +| **架构** | Provider 动态选择翻译;service_ctl 统一监控与拉起;suggest 目前全量脚本触发,增量待做。 |
  247 +| **性能基线** | 向量化扩展性良好;reranker 为整链瓶颈(386 docs 约 0.6 rps);search 约 8 rps;suggest 约 200+ rps。 |
  248 +
  249 +**关键文件与复现**:
  250 +- 配置:`config/config.yaml`(services、rerank、query_config)、`.env`(端口与 API Key)。
  251 +- 脚本:`scripts/service_ctl.sh`(启停与监控)、`scripts/perf_api_benchmark.py`(压测)、`scripts/build_suggestions.sh`(suggest 构建)。
  252 +- 完整步骤与多租户/rerank 对比见:`docs/性能测试报告.md`。
docs/性能测试报告.md
@@ -154,7 +154,7 @@ cd /data/saas-search @@ -154,7 +154,7 @@ cd /data/saas-search
154 154
155 ### 7.4 Reranker(rerank) 155 ### 7.4 Reranker(rerank)
156 156
157 -测试方法(本节已按新口径重跑) 157 +测试方法
158 - `query` 固定为 `wireless mouse` 158 - `query` 固定为 `wireless mouse`
159 - 每次请求 `docs=386` 159 - 每次请求 `docs=386`
160 - 从 `1000` 个候选单词中随机采样,先随机句长 `15-40`,再生成每条 doc 句子 160 - 从 `1000` 个候选单词中随机采样,先随机句长 `15-40`,再生成每条 doc 句子