30 Mar, 2026

1 commit

  • …nt.py)、[search/searcher.py](/data/saas-search/search/searcher.py)、[frontend/static/js/app.js](/data/saas-search/frontend/static/js/app.js)
    以及
    [tests/test_rerank_client.py](/data/saas-search/tests/test_rerank_client.py)。
    
    主要修复内容如下:
    - 精排现依据融合阶段得分进行排序,而非仅依据原始的 `fine_score`。
    - 最终重排不再依赖独立的 `fine_scores`
      数组(该数组在精排排序后可能产生同步偏差),而是直接读取命中结果附带的
    `_fine_score`。
    -
    精排与最终重排现均通过同一计算路径生成融合调试信息,该路径同时也决定实际排序结果,从而保证记录逻辑与生产逻辑保持一致。
    -
    调试信息载荷更加清晰:精排和最终重排阶段都会暴露融合输入/因子以及规范的
    `fusion_summary`,前端界面现在会渲染该摘要信息。
    
    主要问题:阶段逻辑重复且存在并行的数据通道:一个通道用于计算排序,另一个通道用于组装调试字段,还有第三个通道用于传递辅助数组。这造成了潜在的差异风险。本次重构通过将阶段得分作为唯一事实来源,并让调试/前端直接消费其输出而非事后重构,降低了该风险。
    
    验证结果:
    - `./.venv/bin/python -m pytest -q tests/test_rerank_client.py
      tests/test_search_rerank_window.py`
    - `./.venv/bin/python -m py_compile search/rerank_client.py
      search/searcher.py`
    
    结果:`22 passed`。
    
    当前的主流程:
    
    1. Query 解析
    2. ES 召回
    3. 粗排:只用 ES 内部文本/KNN 信号
    4. 款式 SKU 选择 + title suffix
    5. 精排:轻量 reranker + 文本/KNN 融合
    6. 最终 rerank:重 reranker + fine score + 文本/KNN 融合
    7. 分页、补全字段、格式化返回
    
    主控代码在 [searcher.py](/data/saas-search/search/searcher.py),打分与
    rerank 细节在
    [rerank_client.py](/data/saas-search/search/rerank_client.py),配置定义在
    [schema.py](/data/saas-search/config/schema.py) 和
    [config.yaml](/data/saas-search/config/config.yaml)。
    
    **先看入口怎么决定走哪条路**
    在 [searcher.py:348](/data/saas-search/search/searcher.py#L348)
    开始,`search()` 先读租户语言、开关、窗口大小。
    关键判断在 [searcher.py:364](/data/saas-search/search/searcher.py#L364)
    到 [searcher.py:372](/data/saas-search/search/searcher.py#L372):
    
    - `rerank_window` 现在是 80,见
      [config.yaml:256](/data/saas-search/config/config.yaml#L256)
    - `coarse_rank.input_window` 是 700,`output_window` 是 240,见
      [config.yaml:231](/data/saas-search/config/config.yaml#L231)
    - `fine_rank.input_window` 是 240,`output_window` 是 80,见
      [config.yaml:245](/data/saas-search/config/config.yaml#L245)
    
    所以如果请求满足 `from_ + size <= rerank_window`,就进入完整漏斗:
    - ES 实际取前 `700`
    - 粗排后留 `240`
    - 精排后留 `80`
    - 最终 rerank 也只处理这 `80`
    - 最后再做分页切片
    
    如果请求页超出 80,就不走后面的多阶段漏斗,直接按 ES 原逻辑返回。
    tangwang
     

27 Mar, 2026

8 commits


26 Mar, 2026

2 commits


24 Mar, 2026

2 commits

  • 上面一版实现,性能上完全无法接受。因此进行了一轮策略简化
    
    style_sku_prepare_hits阶段耗时太长。请根据需求,思考优化的方法,给出性能优化的方案。
    1.
    _select_by_embedding,有缓存吗,option_value的值是有限的,之前已经算过的,就不用再算了。不仅仅是embedding相似的结果,整个option_value的匹配结果,是有包含、还是没包含,相似度多少,都不用重新计算。比如之前已经有一个sku的某个属性值叫做“卡其色”,已经算出来是否文本匹配了,那么不需要再去做文本匹配。如果已经算出来向量的相似度,那么不需要再去取向量以及计算相似度。
    2. 匹配可以适当的优化:
    匹配流程简化:
    1)找到第一个文本匹配的,如果有直接匹配成功。不需要考虑匹配多个的情况。
    2)如果全部都没有匹配,那么进行embedding筛选。
    
    匹配规则:
    option_name的匹配,直接看规范化后的option_name是不是意图维度的泛化词之一(比如颜色、color、colour),如果没有匹配的,现在应该是把所有维度都算上,这样匹配成本和比较成本太高了,去掉这些逻辑,这种情况不需要加后缀、不需要选择sku。
    ption_value的匹配。意图检测的时候,有匹配的query中的命中的词,这个词被包含在属性值中,那么就算匹配。属性值被包含在query(包括翻译文本)中,也算匹配。提高匹配的覆盖率。
    
    3.
    这一阶段得到sku选择的结果即可(选中的sku的id,也可以为空值表示没找到匹配成功的,这种情况不需要拼接title后缀给重排输入),但是不用着急做image_url的替换和sku的置顶。等最后填充的时候判断有选中sku的时候直接做替换和置顶即可。
    请你思考如何进行设计,提高性能的时候不带来复杂度的提升,可以适当的重构以降低修改后的代码行数。
    @search/sku_intent_selector.py @query/style_intent.py
    tangwang
     
  • 2, 漏了一些重要的stage,比如「款式意图 SKU
    预筛选(StyleSkuSelector.prepare_hits)」,补上这个stage
    tangwang
     

23 Mar, 2026

6 commits


22 Mar, 2026

3 commits


21 Mar, 2026

3 commits


20 Mar, 2026

7 commits


19 Mar, 2026

6 commits

  • tangwang
     
  • tangwang
     
  • tangwang
     
  • tangwang
     
  • The instability is very likely real overload, but `lsof -i :6005 | wc -l
    = 75` alone does not prove it. What does matter is the live shape of the
    service: it is a single `uvicorn` worker on port `6005`, and the code
    had one shared process handling both text and image requests, with image
    work serialized behind a single lock. Under bursty image traffic,
    requests could pile up and sit blocked with almost no useful tracing,
    which matches the “only blocking observed” symptom.
    
    now adds persistent log files, request IDs, per-request
    request/response/failure logs, text microbatch dispatch logs, health
    stats with active/rejected counts, and explicit overload admission
    control. New knobs are `TEXT_MAX_INFLIGHT`, `IMAGE_MAX_INFLIGHT`, and
    `EMBEDDING_OVERLOAD_STATUS_CODE`. Startup output now shows those limits
    and log paths in
    [scripts/start_embedding_service.sh](/data/saas-search/scripts/start_embedding_service.sh#L80).
    I also added focused tests in
    [tests/test_embedding_service_limits.py](/data/saas-search/tests/test_embedding_service_limits.py#L1).
    
    What this means operationally:
    - Text and image are still in one process, so this is not the final
      architecture.
    - But image spikes will now be rejected quickly once the image lane is
      full instead of sitting around and consuming the worker pool.
    - Logs will now show each request, each rejection, each microbatch
      dispatch, backend time, response time, and request ID.
    
    Verification:
    - Passed: `.venv/bin/python -m pytest -q
      tests/test_embedding_service_limits.py`
    - I also ran a wider test command, but 3 failures came from pre-existing
      drift in
    [tests/test_embedding_pipeline.py](/data/saas-search/tests/test_embedding_pipeline.py#L95),
    where the tests still monkeypatch `embeddings.text_encoder.redis.Redis`
    even though
    [embeddings/text_encoder.py](/data/saas-search/embeddings/text_encoder.py#L1)
    no longer imports `redis` that way.
    
    已把 CLIP_AS_SERVICE 的默认模型切到
    ViT-L-14,并把这套配置收口成可变更的统一入口了。现在默认值在
    embeddings/config.py (line 29) 的 CLIP_AS_SERVICE_MODEL_NAME,当前为
    CN-CLIP/ViT-L-14;scripts/start_cnclip_service.sh (line 37)
    会自动读取这个配置,不再把默认模型写死在脚本里,同时支持
    CNCLIP_MODEL_NAME 和 --model-name
    临时覆盖。scripts/start_embedding_service.sh (line 29) 和
    embeddings/server.py (line 425)
    也补了模型信息输出,方便排查实际连接的配置。
    
    文档也一起更新了,重点在 docs/CNCLIP_SERVICE说明文档.md (line 62) 和
    embeddings/README.md (line
    58):现在说明的是“以配置为准、可覆盖”的机制,而不是写死某个模型名;相关总结文档和内部说明也同步改成了配置驱动表述。
    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

2 commits

  • 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