19 Mar, 2026

3 commits

  • 推理”,不再是先按原始输入条数切块。也就是说,如果 100 条请求分句后变成
    150 个 segments,batch_size=64 时会按 64 + 64 + 22
    三批推理,推理完再按原始分句计划合并并还原成 100 条返回。这个改动在
    local_seq2seq.py (line 241) 和 local_ctranslate2.py (line 391)。
    
    日志这边也补上了两层你要的关键信息:
    
    分句摘要日志:Translation segmentation
    summary,会打印输入条数、非空条数、发生分句的输入数、总 segments
    数、当前 batch_size、每条输入分成多少段的统计,见 local_seq2seq.py (line
    216) 和 local_ctranslate2.py (line 366)。
    每个预测批次日志:Translation inference
    batch,会打印第几批、总批数、该批 segment
    数、长度统计、首条预览。CTranslate2 另外还会打印 Translation model batch
    detail,补充 token 长度和 max_decoding_length,见 local_ctranslate2.py
    (line 294)。
    我也补了测试,覆盖了“分句后再
    batching”和“日志中有分句摘要与每批推理日志”,在
    test_translation_local_backends.py (line 358)。
    tangwang
     
  • 改动:
    
    新增分句与预算工具:translation/text_splitter.py
    接入 HF 本地后端:translation/backends/local_seq2seq.py (line 157)
    接入 CT2 本地后端:translation/backends/local_ctranslate2.py (line 301)
    补了测试:tests/test_translation_local_backends.py
    我先把代码里实际限制梳理了一遍,关键配置在 config/config.yaml (line
    133):
    
    nllb-200-distilled-600m: max_input_length=256,max_new_tokens=64,并且是
    ct2_decoding_length_mode=source +
    extra=8。现在按这个配置计算出的保守输入预算是 56 token。
    opus-mt-zh-en:
    max_input_length=256,max_new_tokens=256。现在保守输入预算是 248 token。
    opus-mt-en-zh: 同上,也是 248 token。
    这版分句策略是:
    
    先按强边界切:。!?!?;;…、换行、英文句号
    不够再按弱边界切:,,、::()()[]【】/|
    再不够才按空白切
    最后才做 token 预算下的硬切
    超长时会“分句翻译后再回拼”,中文目标语言默认无空格回拼,英文等默认按空格回拼,尽量别切太碎
    验证:
    
    python3 -m compileall translation
    tests/test_translation_local_backends.py 已通过
    tangwang
     
  • 中采用了最优T4配置:ct2_inter_threads=2、ct2_max_queued_batches=16、ct2_batch_type=examples。该设置使NLLB获得了显著更优的在线式性能,同时大致保持了大批次吞吐量不变。我没有将相同配置应用于两个Marian模型,因为聚焦式报告显示了复杂的权衡:opus-mt-zh-en
    在保守默认配置下更为均衡,而 opus-mt-en-zh 虽然获得了吞吐量提升,但在
    c=8 时尾延迟波动较大。
    我还将部署/配置经验记录在 /data/saas-search/translation/README.md
    中,并在 /data/saas-search/docs/TODO.txt
    中标记了优化结果。关键实践要点现已记录如下:使用CT2 +
    float16,保持单worker,将NLLB的 inter_threads 设为2、max_queued_batches
    设为16,在T4上避免使用
    inter_threads=4(因为这会损害高批次吞吐量),除非区分在线/离线配置,否则保持Marian模型的默认配置保守。
    tangwang
     

18 Mar, 2026

8 commits

  • Implemented CTranslate2 for the three local translation models and
    switched the existing local_nllb / local_marian factories over to it.
    The new runtime lives in local_ctranslate2.py, including HF->CT2
    auto-conversion, float16 compute type mapping, Marian direction
    handling, and NLLB target-prefix decoding. The service wiring is in
    service.py (line 113), and the three model configs now point at explicit
    ctranslate2-float16 dirs in config.yaml (line 133).
    
    I also updated the setup path so this is usable end-to-end:
    ctranslate2>=4.7.0 was added to requirements_translator_service.txt and
    requirements.txt, the download script now supports pre-conversion in
    download_translation_models.py (line 27), and the docs/config examples
    were refreshed in translation/README.md. I installed ctranslate2 into
    .venv-translator, pre-converted all three models, and the CT2 artifacts
    are now already on disk:
    
    models/translation/facebook/nllb-200-distilled-600M/ctranslate2-float16
    models/translation/Helsinki-NLP/opus-mt-zh-en/ctranslate2-float16
    models/translation/Helsinki-NLP/opus-mt-en-zh/ctranslate2-float16
    Verification was solid. python3 -m compileall passed, direct
    TranslationService smoke tests ran successfully in .venv-translator, and
    the focused NLLB benchmark on the local GPU showed a clear win:
    
    batch_size=16: HF 0.347s/batch, 46.1 items/s vs CT2 0.130s/batch, 123.0
    items/s
    batch_size=1: HF 0.396s/request vs CT2 0.126s/request
    One caveat: translation quality on some very short phrases, especially
    opus-mt-en-zh, still looks a bit rough in smoke tests, so I’d run your
    real quality set before fully cutting over. If you want, I can take the
    next step and update the benchmark script/report so you have a fresh
    full CT2 performance report for all three models.
    tangwang
     
  • batch×并发矩阵”彻底分开展示。
    
    改动在这几处:
    
    scripts/benchmark_translation_local_models.py:新增 --suite
    extended,支持
    batch_size=1,4,8,16,32,64、concurrency=1,2,4,8,16,64、以及 batch_size *
    concurrency <= 128
    的组合矩阵;并且单场景模式现在只加载目标模型,load_seconds
    更干净,也支持 --disable-cache。
    translation/README.md:把性能章节拆成了
    batch_sweep、concurrency_sweep、batch x concurrency matrix
    三块,补了这次复测的参数、复现命令和摘要表。
    perf_reports/20260318/translation_local_models/README.md:新增本轮补测摘要。
    完整结果在 translation_local_models_extended_221846.md 和
    translation_local_models_extended_221846.json。
    这次补测的核心结论很明确:
    
    在线单条请求应该看 concurrency_sweep,也就是固定 batch_size=1 的表。
    离线批量吞吐应该看 batch_sweep,4 个方向的最高 raw throughput 都出现在
    batch_size=64,但更均衡的默认值仍更像 batch_size=16。
    当前本地 seq2seq backend
    有单模型锁,提升客户端并发几乎不涨吞吐,主要是把排队时间变成更高的
    p95;所以并发更像“延迟预算”问题,不是“扩容吞吐”手段。
    本轮在线单条里最快的是 opus-mt-zh-en;最慢、且并发放大最明显的是
    nllb-200-distilled-600m en->zh。
    tangwang
     
  • tangwang
     
  • tangwang
     
  • tangwang
     
  • 核心改动在 rerank_client.py (line 99):fuse_scores_and_resort 现在按
    rerank * knn * text 的平滑乘法公式计算,优先从 hit["matched_queries"]
    里取 base_query 和 knn_query,并把 _text_score / _knn_score
    一并写回调试字段。为了让 KNN 也有名字,我给 top-level knn 加了 name:
    "knn_query",见 es_query_builder.py (line 273)。搜索执行时会在 rerank
    窗口内打开 include_named_queries_score,并在显式排序时加上
    track_scores,见 searcher.py (line 400) 和 es_client.py (line 224)。
    tangwang
     
  • 2. 优化缓存,缓存粒度为商品级,每次只对batch中未cache的重新计算;key使用每个商品输入的hash
    tangwang
     
  • tangwang
     

17 Mar, 2026

12 commits

  • tangwang
     
  • tangwang
     
  • tangwang
     
  • tangwang
     
  • tangwang
     
  • tangwang
     
  • 多个独立翻译能力”重构。现在业务侧不再把翻译当 provider
    选型,QueryParser 和 indexer 统一通过 6006 的 translator service client
    调用;真正的能力选择、启用开关、model + scene 路由,都收口到服务端和新的
    translation/ 目录里了。
    
    这次的核心改动在
    config/services_config.py、providers/translation.py、api/translator_app.py、config/config.yaml
    和新的 translation/service.py。配置从旧的
    services.translation.provider/providers 改成了 service_url +
    default_model + default_scene + capabilities,每个能力可独立
    enabled;服务端新增了统一的 backend 管理与懒加载,真实实现集中到
    translation/backends/qwen_mt.py、translation/backends/llm.py、translation/backends/deepl.py,旧的
    query/qwen_mt_translate.py、query/llm_translate.py、query/deepl_provider.py
    只保留兼容导出。接口上,/translate 现在标准支持 scene,context
    作为兼容别名继续可用,健康检查会返回默认模型、默认场景和已启用能力。
    tangwang
     
  • 2. 抽象出可复用的 embedding Redis 缓存类(图文共用)
    
    详细:
    1. embedding 缓存改为 BF16 存 Redis(读回恢复 FP32)
    关键行为(按你给的流程落地)
    写入前:FP32 embedding →(normalize_embeddings=True 时)L2 normalize →
    转 BF16 → bytes(2字节/维,大端) → redis.setex
    读取后:redis.get bytes → BF16 → 恢复 FP32(np.float32 向量)
    变更点
    新增 embeddings/bf16.py
    提供 float32_to_bf16 / bf16_to_float32
    encode_embedding_for_redis():FP32 → BF16 → bytes
    decode_embedding_from_redis():bytes → BF16 → FP32
    l2_normalize_fp32():按需归一化
    修改 embeddings/text_encoder.py
    Redis value 从 pickle.dumps(np.ndarray) 改为 BF16 bytes
    缓存 key 改为包含 normalize 标记:{prefix}:{n0|n1}:{query}(避免
    normalize 开关不同却共用缓存)
    修改 tests/test_embedding_pipeline.py
    cache hit 用例改为写入 BF16 bytes,并使用新
    key:embedding:n1:cached-text
    修改 docs/缓存与Redis使用说明.md
    embedding 缓存的 Key/Value 格式更新为 BF16 bytes + n0/n1
    修改 scripts/redis/redis_cache_health_check.py
    embedding pattern 不再硬编码 embedding:*,改为读取
    REDIS_CONFIG["embedding_cache_prefix"]
    value 预览从 pickle 解码改为 BF16 解码后展示 dim/bytes/dtype
    自检
    在激活环境后跑过 BF16 编解码往返 sanity check:bytes
    长度、维度恢复正常;归一化向量读回后范数接近 1(会有 BF16 量化误差)。
    
    2. 抽象出可复用的 embedding Redis 缓存类(图文共用)
    新增
    embeddings/redis_embedding_cache.py:RedisEmbeddingCache
    统一 Redis 初始化(读 REDIS_CONFIG)
    统一 BF16 bytes 编解码(复用 embeddings/bf16.py)
    统一过期策略:写入 setex(expire_time),命中读取后 expire(expire_time)
    滑动过期刷新 TTL
    统一异常/坏数据处理:解码失败或向量非 1D/为空/含 NaN/Inf 会删除该 key
    并当作 miss
    已接入复用
    文本 embeddings/text_encoder.py
    用 self.cache = RedisEmbeddingCache(key_prefix=..., namespace="")
    key 仍是:{prefix}:{query}
    图片 embeddings/image_encoder.py
    用 self.cache = RedisEmbeddingCache(key_prefix=..., namespace="image")
    key 仍是:{prefix}:image:{url_or_path}
    tangwang
     
  • tangwang
     
  • tangwang
     
  • tangwang
     
  • - Rename indexer/product_annotator.py to indexer/product_enrich.py and remove CSV-based CLI entrypoint, keeping only in-memory analyze_products API
    - Introduce dedicated product_enrich logging with separate verbose log file for full LLM requests/responses
    - Change indexer and /indexer/enrich-content API wiring to use indexer.product_enrich instead of indexer.product_annotator, updating tests and docs accordingly
    - Switch translate_prompts to share SUPPORTED_INDEX_LANGUAGES from tenant_config_loader and reuse that mapping for language code → display name
    - Remove hard SUPPORTED_LANGS constraint from LLM content-enrichment flow, driving languages directly from tenant/indexer configuration
    - Redesign LLM prompt generation to support multi-round, multi-language tables: first round in English, subsequent rounds translate the entire table (headers + cells) into target languages using English instructions
    tangwang
     

16 Mar, 2026

4 commits


14 Mar, 2026

1 commit


13 Mar, 2026

10 commits


12 Mar, 2026

2 commits