# NLLB T4 Product-Name Tuning Summary

测试脚本：
- [`benchmarks/translation/benchmark_nllb_t4_tuning.py`](/data/saas-search/benchmarks/translation/benchmark_nllb_t4_tuning.py)

本轮报告：
- Markdown：[`nllb_t4_tuning_003608.md`](/data/saas-search/perf_reports/20260318/nllb_t4_product_names_ct2/nllb_t4_tuning_003608.md)
- JSON：[`nllb_t4_tuning_003608.json`](/data/saas-search/perf_reports/20260318/nllb_t4_product_names_ct2/nllb_t4_tuning_003608.json)

相关报告：
- 基线扩展报告：[`../translation_local_models/README.md`](/data/saas-search/perf_reports/20260318/translation_local_models/README.md)
- CT2 扩展报告：[`../translation_local_models_ct2/README.md`](/data/saas-search/perf_reports/20260318/translation_local_models_ct2/README.md)
- CT2 聚焦调优报告：[`../translation_local_models_ct2_focus/README.md`](/data/saas-search/perf_reports/20260318/translation_local_models_ct2_focus/README.md)

测试时间：
- `2026-03-19`

环境：
- GPU：`Tesla T4 16GB`
- Python env：`.venv-translator`
- Torch / Transformers：`2.10.0+cu128 / 5.3.0`
- CTranslate2：`4.7.1`

## Scope

这轮不再做全模型矩阵，只盯住 `facebook/nllb-200-distilled-600M` 的商品标题场景：

- `high batch + low concurrency`
  - `batch=64`
  - `concurrency=1`
- `high concurrency + low batch`
  - `batch=1`
  - `concurrency=64`

对比的核心变体：

- `ct2_default_fixed64`
  - `ct2_inter_threads=1`
  - `ct2_max_queued_batches=0`
  - `ct2_batch_type=examples`
  - `max_new_tokens=64`
- `ct2_prev_t4_fixed64`
  - `ct2_inter_threads=2`
  - `ct2_max_queued_batches=16`
  - `ct2_batch_type=examples`
  - `max_new_tokens=64`
- `ct2_best_t4_dynamic`
  - `ct2_inter_threads=4`
  - `ct2_max_queued_batches=32`
  - `ct2_batch_type=examples`
  - `max_new_tokens=64`
  - `ct2_decoding_length_mode=source`
  - `ct2_decoding_length_extra=8`
  - `ct2_decoding_length_min=32`
- `ct2_fixed48_experiment`
  - `ct2_inter_threads=3`
  - `ct2_max_queued_batches=16`
  - `ct2_batch_type=examples`
  - `max_new_tokens=48`

## Recommendation

最终推荐的 T4 线上默认配置：

- `ct2_inter_threads=4`
- `ct2_max_queued_batches=32`
- `ct2_batch_type=examples`
- `max_new_tokens=64`
- `ct2_decoding_length_mode=source`
- `ct2_decoding_length_extra=8`
- `ct2_decoding_length_min=32`

为什么是这组：

- 相比 `ct2_default_fixed64`，在线 `batch=1, concurrency=64` 收益明显。
- 相比上一轮 `ct2_prev_t4_fixed64`，还有一段稳定增益。
- bulk `batch=64` 基本持平，没有出现明显回退。
- 它比 `max_new_tokens=48` 更保守，质量风险更低。

## Key Results

`nllb zh -> en`

| Variant | Bulk items/s | Bulk p95 ms | Online items/s | Online p95 ms |
|---|---:|---:|---:|---:|
| `ct2_default_fixed64` | `121.32` | `589.47` | `13.12` | `1682.25` |
| `ct2_prev_t4_fixed64` | `121.0` | `592.16` | `15.97` | `1401.59` |
| `ct2_best_t4_dynamic` | `120.29` | `595.79` | `16.97` | `1353.38` |

解读：

- 相比默认 CT2 配置，在线吞吐 `13.12 -> 16.97 items/s`，提升约 `29.3%`
- 在线 `p95` 从 `1682.25 -> 1353.38 ms`，下降约 `19.6%`
- bulk 基本持平，说明这组参数更像“白拿线上收益”

`nllb en -> zh`

| Variant | Bulk items/s | Bulk p95 ms | Online items/s | Online p95 ms |
|---|---:|---:|---:|---:|
| `ct2_default_fixed64` | `95.99` | `701.53` | `6.47` | `3533.98` |
| `ct2_prev_t4_fixed64` | `95.46` | `706.05` | `7.93` | `2922.29` |
| `ct2_best_t4_dynamic` | `95.06` | `707.54` | `8.61` | `2751.74` |

解读：

- 相比默认 CT2 配置，在线吞吐 `6.47 -> 8.61 items/s`，提升约 `33.1%`
- 在线 `p95` 从 `3533.98 -> 2751.74 ms`，下降约 `22.1%`
- 相比上一轮固定 `64` 配置，在线吞吐还有约 `8.6%` 提升

## Quality Notes

自动化报告里的逐条 exact-match 对比，不适合作为这轮的最终质量结论：

- CT2 在不同服务实例之间，即使是同样的固定 `64` 配置，也会出现少量措辞差异。
- 因此这轮是否推广，主要看“有没有明显截断风险”，不是看跨实例是否逐字一致。

这轮真正用于决策的人工 spot-check 结论：

- `ct2_fixed48_experiment`：
  - `zh->en`：`98/100` 与固定 `64` 一致
  - `en->zh`：`77/100` 与固定 `64` 一致
  - 已看到明显长标题截断，不适合作为线上默认
- `ct2_decoding_length_mode=source(+8,min=32)`：
  - 在控制变量的 spot-check 中，`zh->en` 与 `en->zh` 都能保持 `100/100`
  - 说明动态上限比直接把 `max_new_tokens` 砍到 `48` 更稳
- `ct2_decoding_length_mode=source(+4,min=24)`：
  - 更快一点，但 `en->zh` 已出现少量长标题截断
  - 可以保留为 latency-first 备选，不作为默认

## Variables That Helped

- `CTranslate2 + float16`
  - 这是基础前提，没有它后面的大部分优化都不成立
- `ct2_inter_threads`
  - NLLB 在 T4 上确实能吃到更多 GPU-side 并发
  - `4` 比 `2` 继续有收益
- `ct2_max_queued_batches`
  - 和 `inter_threads` 配合后能继续压在线延迟
  - `32` 比 `16` 还有一点增益，但幅度已经不大
- 动态解码上限
  - 这是本轮最关键的新结论
  - 保留 `64` 的安全上限，但按 source length 收紧短标题的 decode 上限，能明显改善线上场景

## Variables That Did Not Become Defaults

- `ct2_batch_type=tokens`
  - 没有带来稳定收益，当前项目保留 `examples`
- 直接把 `max_new_tokens` 改成 `48`
  - 虽然速度很好，但质量风险太明显
- 更激进的动态策略 `source(+4,min=24)`
  - 还能更快，但已经开始伤长标题

## Deployment Tasks Worth Keeping

- 本地 NLLB 继续使用 `CTranslate2 + float16`
- 单 worker 部署，避免重复加载模型
- 显式保留 `batch_size=16`
- 在线默认使用动态解码上限，而不是盲目缩短 `max_new_tokens`
- 调优文档里明确记录：
  - `inter_threads`
  - `max_queued_batches`
  - `batch_type`
  - `max_new_tokens`
  - 动态解码策略

## Next Step

如果下一轮还要继续压线上尾延迟，优先顺序建议是：

1. 服务级微批处理队列
2. 商品标题按长度分桶
3. 在保持当前动态 decode 策略的前提下，继续评估 `int8_float16`
