diff --git a/docs/翻译模块说明.md b/docs/翻译模块说明.md deleted file mode 100644 index 6ff372b..0000000 --- a/docs/翻译模块说明.md +++ /dev/null @@ -1,145 +0,0 @@ -# 翻译模块 - -**快速上手**:见 `docs/QUICKSTART.md` 第 3.4 节。 - -## 环境变量 - -```bash -# Qwen(默认) -DASHSCOPE_API_KEY=sk-xxx - -# DeepL -DEEPL_AUTH_KEY=xxx -``` - -> **重要限速说明(Qwen 机翻)** -> 当前默认的 Qwen 翻译后端使用 `qwen-mt-flash` 云端模型,**官方限速较低,约 RPM=60(每分钟约 60 请求)**。 -> - 推荐通过 Redis 翻译缓存复用结果,避免对相同文本重复打云端 -> - 高并发场景需要在调用端做限流 / 去抖,或改为离线批量翻译 -> - 如需更高吞吐,可考虑 DeepL 或自建翻译服务 - -## 配置模型 - -翻译已改为“一个翻译服务 + 多种翻译能力”的结构: - -- 业务侧(`QueryParser` / indexer)统一调用 `http://127.0.0.1:6006` -- 服务内按 `services.translation.capabilities` 加载并管理各翻译能力 -- 已启用 capability 统一注册,后端实例按首次调用懒加载,避免多个本地模型在启动阶段一次性占满显存 -- `config.yaml` 只保留部署相关配置;scene 规则、语言码映射、prompt 模板、模型方向约束等翻译域知识统一收口在 `translation/` 内部 -- 每种能力独立配置 `enabled`、`model`、`base_url/api_url`、`timeout`、本地模型运行参数等部署项 -- 每种能力显式声明 `backend` 类型,例如 `qwen_mt`、`llm`、`deepl`、`local_nllb`、`local_marian` -- `service_url`、`default_model`、`default_scene` 只从 `config/config.yaml` 读取,不再接受环境变量静默覆盖 -- 外部接口通过 `model + scene` 指定本次使用哪种能力、哪个场景 - -配置入口在 `config/config.yaml -> services.translation` - -## 本地模型部署 - -本仓库已内置 3 个本地机翻 capability: - -- `nllb-200-distilled-600m` -- `opus-mt-zh-en` -- `opus-mt-en-zh` - -推荐流程: - -1. 创建独立运行环境:`./scripts/setup_translator_venv.sh` -2. 下载本地模型:`./.venv-translator/bin/python scripts/download_translation_models.py --all-local` -3. 在 `config/config.yaml` 中把对应 capability 的 `enabled` 改为 `true` -4. 启动服务:`./scripts/start_translator.sh` - -默认模型目录: - -- `models/translation/facebook/nllb-200-distilled-600M` -- `models/translation/Helsinki-NLP/opus-mt-zh-en` -- `models/translation/Helsinki-NLP/opus-mt-en-zh` - -说明: - -- 目前只支持 3 个标准 scene:`general`、`sku_name`、`ecommerce_search_query` -- `nllb-200-distilled-600m` 支持多语,但依赖明确的 `source_lang` -- 两个 OPUS 模型分别只支持 `zh -> en` 与 `en -> zh` -- 本地模型建议单 worker 运行,避免重复加载占用显存 - -## HTTP 接口契约(translator service,端口 6006) - -服务默认监听 `http://localhost:6006`,提供: - -- `POST /translate`: 文本翻译(支持所有已启用 capability) -- `GET /health`: 健康检查 - -### `POST /translate` - -**请求体**: - -```json -{ - "text": "商品名称", - "target_lang": "en", - "source_lang": "zh", - "model": "qwen-mt", - "scene": "sku_name" -} -``` - -- `text` 支持两种形式: - - 单条:`string` - - 批量:`string[]`(等长返回,顺序对应) - -**响应体**(单条): - -```json -{ - "text": "商品名称", - "target_lang": "en", - "source_lang": "zh", - "translated_text": "Product name", - "status": "success", - "model": "qwen-mt", - "scene": "sku_name" -} -``` - -**响应体**(批量): - -```json -{ - "text": ["商品名称1", "商品名称2"], - "target_lang": "en", - "source_lang": "zh", - "translated_text": ["Product name 1", null], - "status": "success", - "model": "qwen-mt", - "scene": "sku_name" -} -``` - -批量模式下,**单条失败用 `null` 占位**(即 `translated_text[i] = null`),保证长度与顺序一一对应,避免部分失败导致整批报错。 - -说明: - -- `scene` 是标准字段 -- `prompt` 不属于外部接口;LLM prompt 由 translator service 内部根据 `scene` 生成 -- `model` 只能选择已在 `services.translation.capabilities` 中启用的能力 -- `/health` 会返回 `default_model`、`default_scene`、`enabled_capabilities` 与 `loaded_models` - ---- - -## 开发者接口约定(代码调用) - -代码侧(如 query/indexer)通过 `translation.create_translation_client()` 获取实例并调用 `translate()`; - -### 输入输出Shape - -- `translate(text=...)` 支持: - - **单条**:`text: str` → 返回 `Optional[str]` - - **批量**:`text: List[str]` → 返回 `List[Optional[str]]` -- **批量语义**:返回列表必须与输入 **等长且顺序对应**;某条翻译失败时,对应位置为 `None`(HTTP JSON 中表现为 `null`)。 - -### 批量能力标识(supports_batch) - -服务客户端与服务内后端都可以暴露 `supports_batch`。若后端不支持批量,服务端会逐条拆分并保持 shape。 - -为便于上层(如 `api/translator_app.py`)做最优调用,client / backend 可暴露: - -- `supports_batch: bool`(property) diff --git a/perf_reports/20260317/translation_local_models/README.md b/perf_reports/20260317/translation_local_models/README.md index bc1bf96..f347d60 100644 --- a/perf_reports/20260317/translation_local_models/README.md +++ b/perf_reports/20260317/translation_local_models/README.md @@ -13,6 +13,7 @@ Environment: Method: - `opus-mt-zh-en` and `opus-mt-en-zh` were benchmarked on the full dataset using their configured production settings. - `nllb-200-distilled-600m` was benchmarked on a `500`-row subset after optimization. +- `nllb-200-distilled-600m` was also benchmarked with `batch_size=1` on a `100`-row subset to approximate online query translation latency. - This report only keeps the final optimized results and final deployment recommendation. - Quality was intentionally not evaluated; this is a performance-only report. @@ -54,6 +55,34 @@ What did not become the final recommendation: | `nllb-200-distilled-600m` | `zh -> en` | `cuda` | 500 | 7.3397 | 25.9577 | 19.26 | 51.915 | 832.64 | 1263.01 | | `nllb-200-distilled-600m` | `en -> zh` | `cuda` | 500 | 7.4152 | 42.0405 | 11.89 | 84.081 | 1093.87 | 2107.44 | +## Single-Request Latency + +To model online search query translation, we reran NLLB with `batch_size=1`. In this mode, batch latency is request latency. + +| Model | Direction | Rows | Load s | Translate s | Avg req ms | Req p50 ms | Req p95 ms | Req max ms | Items/s | +|---|---|---:|---:|---:|---:|---:|---:|---:|---:| +| `nllb-200-distilled-600m` | `zh -> en` | 100 | 6.8390 | 32.1909 | 321.909 | 292.54 | 624.12 | 819.67 | 3.11 | +| `nllb-200-distilled-600m` | `en -> zh` | 100 | 6.8249 | 54.2470 | 542.470 | 481.61 | 1171.71 | 1751.85 | 1.84 | + +Command used: + +```bash +./.venv-translator/bin/python scripts/benchmark_translation_local_models.py \ + --single \ + --model nllb-200-distilled-600m \ + --source-lang zh \ + --target-lang en \ + --column title_cn \ + --scene sku_name \ + --batch-size 1 \ + --limit 100 +``` + +Takeaways for online use: +- `batch_size=1` can be treated as single-request latency for the current service path. +- `zh -> en` is materially faster than `en -> zh` on this machine. +- NLLB is usable for online query translation, but it is not a low-latency model by search-serving standards. + ## NLLB Resource Reality The common online claim that this model uses only about `1.25GB` in `float16` is best understood as a rough weight-size level, not end-to-end runtime memory. diff --git a/translation/README.md b/translation/README.md index 275a8e7..c3b7d63 100644 --- a/translation/README.md +++ b/translation/README.md @@ -286,6 +286,12 @@ results = translator.translate( ) ``` +接口 shape 约定: +- `translate(text="...")` 返回 `Optional[str]` +- `translate(text=[...])` 返回 `List[Optional[str]]` +- 批量模式始终保持“等长、同序返回”;某条失败时对应位置为 `None` +- backend/client 可通过 `supports_batch` 暴露是否支持原生批量;服务端会在必要时自动逐条拆分并保持返回 shape 不变 + ## 8. 具体实现说明 ### 8.1 Qwen-MT @@ -329,26 +335,51 @@ results = translator.translate( 模型信息: - Hugging Face 名称:`facebook/nllb-200-distilled-600M` +- 简介:多语种翻译:覆盖约 200 种语言。作为NLLB-200系列的蒸馏版本,该模型通过知识蒸馏技术将原130亿参数模型压缩至600M,同时保持了80%以上的翻译质量。 - 本地目录:`models/translation/facebook/nllb-200-distilled-600M` - 当前磁盘占用:约 `2.4G` - 模型类型:多语种 Seq2Seq 机器翻译模型 - 来源:Meta NLLB(No Language Left Behind)系列的 600M 蒸馏版 -- 目标:用一个模型覆盖大规模多语言互译,而不是只服务某一个固定语言对 - 结构特点: - Transformer encoder-decoder 架构 - 12 层 encoder + 12 层 decoder - `d_model=1024` - - 多头注意力,适合多语统一建模 - 通过 `source_lang + forced_bos_token_id` 控制翻译方向 - 语言标识采用 `language_script` 形式,例如 `eng_Latn`、`zho_Hans` + - 改良 encoder-decoder(含嵌入层缩放 `scale_embedding`、相对位置等) + +核心配置如下: + +| 配置项 | 参数值 | 备注 | +| --- | --- | --- | +| 隐藏层维度(`d_model`) | 1024 | | +| 编码器 / 解码器层数 | 12 / 12 | | +| 注意力头数 | 16 | | +| FFN 维度 | 4096 | | +| 词表大小 | 256,206 | 多语统一词表 | +| 最大序列长度 | 1024 tokens | 满足长文本翻译 | + +`config.json` 片段(示意): + +```json +{ + "d_model": 1024, + "encoder_layers": 12, + "decoder_layers": 12, + "attention_dropout": 0.1, + "use_cache": true, + "torch_dtype": "float32", + "max_length": 200 +} +``` 模型定位: - 优势是多语覆盖面广,一个模型可以支撑很多语言方向 - 劣势是相较于 Marian 这种双语专用模型,推理更重、延迟更高 -- 在我们当前业务里,它更适合“多语覆盖优先”的场景,不适合拿来和专用中英模型拼极致吞吐 +- 更适合做**索引翻译**(离线 / 批量),不建议作为在线 query 翻译的默认方案 显存占用情况: -- 600M模型半float16权重约1.25G,推理时会叠加 CUDA context、allocator reserve、激活张量、batch、输入长度、生成长度等开销 +- 600M 模型半精度(float16)权重约 `~1.25G`;推理还会叠加 CUDA context、allocator reserve、激活张量、batch、输入/生成长度等开销 - 当前这台 `Tesla T4` 上,优化后的实际运行峰值大约在 `2.8-3.0 GiB` 当前实现特点: @@ -358,6 +389,25 @@ results = translator.translate( - 语言码映射定义在 [`translation/languages.py`](/data/saas-search/translation/languages.py) - 当前 T4 推荐配置:`device=cuda`、`torch_dtype=float16`、`batch_size=16`、`max_new_tokens=64`、`attn_implementation=sdpa` +当前实现已经利用的优化: +- 已做批量分块:`translate()` 会按 capability 的 `batch_size` 分批进入模型 +- 已做动态 padding:tokenizer 使用 `padding=True`、`truncation=True` +- 已传入 `attention_mask`:由 tokenizer 生成并随 `generate()` 一起送入模型 +- 已设置方向控制:NLLB 通过 `tokenizer.src_lang` 和 `forced_bos_token_id` 指定语言对 +- 已启用推理态:`torch.inference_mode()` + `model.eval()` +- 已启用半精度和更优注意力实现:当前配置为 `float16 + sdpa` +- 已关闭高开销搜索:默认 `num_beams=1`,更接近线上低延迟设置 + +和你给出的批处理示例对照: +- 核心思路已经覆盖,现有实现与 `tokenizer(batch) -> model.generate(...) -> batch_decode(...)` 一致 +- 差异在于服务端额外做了语言校验、统一 chunking、输入长度约束和单条/批量 shape 保持 +- “预计算 attention mask” 目前没有单独缓存层;现状是每个 batch 在 tokenizer 阶段实时生成 `attention_mask`,这也是 HF 常规推理路径 + +优化空间(按场景): +- **线上 query**:优先补测 `batch_size=1` 的真实延迟与 tail latency,而不是继续拉大 batch。 +- **离线批量**:可再尝试更激进的 batching / 长度分桶 / 独立批处理队列(吞吐更高,但会增加在线尾延迟风险)。 +- **进一步降显存 / 提速**:可评估 `ctranslate2` / int8;当前仓库尚未引入该运行栈。 + ### 8.5 `opus-mt-zh-en` 实现文件: @@ -483,6 +533,29 @@ cd /data/saas-search --scene sku_name ``` +单条请求延迟复现: + +```bash +./.venv-translator/bin/python scripts/benchmark_translation_local_models.py \ + --single \ + --model nllb-200-distilled-600m \ + --source-lang zh \ + --target-lang en \ + --column title_cn \ + --scene sku_name \ + --batch-size 1 \ + --limit 100 +``` + +说明: +- 对当前脚本和本地 backend 来说,“单条请求”可以直接等价理解为 `batch_size=1` +- 此时脚本里的 `batch_latency_*`,就可以直接视为“单次请求延迟”指标 +- 线上搜索 query 翻译更应该关注这组数据,而不是大 batch 吞吐 + +当前单条请求实测(`Tesla T4`,`limit=100`): +- `nllb-200-distilled-600m zh->en`:p50 约 `292.54 ms`,p95 约 `624.12 ms`,平均约 `321.91 ms` +- `nllb-200-distilled-600m en->zh`:p50 约 `481.61 ms`,p95 约 `1171.71 ms`,平均约 `542.47 ms` + 当前压测环境: - GPU:`Tesla T4 16GB` - Python env:`.venv-translator` @@ -511,6 +584,9 @@ NLLB 性能优化经验: - 起作用的优化点 4:`attn_implementation=sdpa` - 对当前 PyTorch + T4 环境有效 - 配合半精度和较合理 batch size 后,整体延迟进一步下降 +- 已有但不需要单独开关的点:`attention_mask` + - 当前实现会在 tokenizer 阶段自动生成并传入 `generate()` + - 它属于标准推理路径,不是一个额外的“高级优化开关” 为什么最终没有采用其它方案: @@ -569,7 +645,7 @@ NLLB 性能优化经验: ## 13. 相关文档 -- [`docs/翻译模块说明.md`](/data/saas-search/docs/翻译模块说明.md) +- [`docs/翻译模块说明.md`](/data/saas-search/docs/翻译模块说明.md)(已收口到本 README,保留为跳转页) - [`docs/QUICKSTART.md`](/data/saas-search/docs/QUICKSTART.md) - [`docs/DEVELOPER_GUIDE.md`](/data/saas-search/docs/DEVELOPER_GUIDE.md) - [`docs/搜索API对接指南.md`](/data/saas-search/docs/搜索API对接指南.md) diff --git a/translation/backends/local_seq2seq.py b/translation/backends/local_seq2seq.py index b5109cd..187a395 100644 --- a/translation/backends/local_seq2seq.py +++ b/translation/backends/local_seq2seq.py @@ -93,7 +93,7 @@ class LocalSeq2SeqTranslationBackend: def _model_kwargs(self) -> Dict[str, object]: kwargs: Dict[str, object] = {} if self.torch_dtype is not None: - kwargs["dtype"] = self.torch_dtype + kwargs["torch_dtype"] = self.torch_dtype kwargs["low_cpu_mem_usage"] = True if self.attn_implementation: kwargs["attn_implementation"] = self.attn_implementation @@ -134,7 +134,10 @@ class LocalSeq2SeqTranslationBackend: max_length=self.max_input_length, **tokenizer_kwargs, ) - encoded = {key: value.to(self.device) for key, value in encoded.items()} + encoded = { + key: value.to(self.device, non_blocking=self.device.startswith("cuda")) + for key, value in encoded.items() + } generate_kwargs = self._build_generate_kwargs(source_lang, target_lang) input_ids = encoded.get("input_ids") if input_ids is not None and "max_length" not in generate_kwargs: -- libgit2 0.21.2