Prhub

#28143 fix(health): make health-check rid unique across tokenizer workers

原始 PR 作者 whybeyoung 合并时间 2026-06-14 15:01 文件变更 2 提交数 2 评论 2 代码增减 +9 / -3

执行摘要

修复健康检查 ID 跨进程重复 bug

Issue #28142 报告了在 --tokenizer-worker-num 8 的多 worker 模式下,健康检查频繁抛出 KeyError: 'HEALTH_CHECK_1781301554.5131953'。根本原因是 time.time() 精度不足以区分同一时刻多个 worker 的请求,导致 rid 碰撞,进而使 BatchTokenIDOutput 中出现重复 rid,DetokenizerManager 无法找到对应的 decode status 而崩溃。

值得立即合并。改动小、风险低、针对一个已复现的生产 bug。可以作为多 worker 部署的最佳实践参考——始终使用 uuid 或 pid + counter 保证请求 ID 跨进程唯一。

讨论亮点

无实质性 review 讨论。自动化 bot gemini-code-assist[bot] 仅做了总结性评论,没有提出修改意见。第二位提交者 hnyls2002 在第二次 commit 中将最初的 os.getpid() 方案改为了 uuid.uuid4().hex,并补充了 encode_server.py 的覆盖。

实现拆解

这是一个极简的两步修复:

  1. 在 http_server.py 中替换 rid 生成(第 610→613 行):在 /health/health_generate 端点中,将 rid = f"{HEALTH_CHECK_RID_PREFIX}_{time.time()}" 改为 rid = f"{HEALTH_CHECK_RID_PREFIX}_{uuid.uuid4().hex}",并添加说明注释。新增了 import uuid。改动量仅 +4/-1。
  2. 在 encode_server.py 中同步修复(第 2554→2555 行 和 第 3761→3762 行):同样的 rid 生成方式替换,覆盖 DP worker 内部健康编码(_dp_worker_health_encode)和 encode dispatcher 侧的 health_generate 函数。新增了 import uuid。改动量 +5/-2。

没有引入配置、测试或文档变更。

文件 模块 状态 重要度
python/sglang/srt/entrypoints/http_server.py HTTP 入口 modified 5.3
python/sglang/srt/disaggregation/encode_server.py 编码服务器 modified 5.4

关键符号

health_generate _dp_worker_health_encode

关键源码片段

python/sglang/srt/entrypoints/http_server.py dependency-wiring

主入口 HTTP server,是健康检查端点的核心实现,修复直接影响所有 `/health` 和 `/health_generate` 请求的 rid 唯一性。

# python/sglang/srt/entrypoints/http_server.pyimport uuid # 新增导入@app.get("/health")
@app.get("/health_generate")
async def health_generate(request: Request) -> Response:
    """
    Check the health of the inference server by sending a special request
    to generate one token.
    """
    # ... 前置检查省略 ...
​
    sampling_params = {"max_new_tokens": 1, "temperature": 0.0}
    # uuid 保证 rid 在多个 tokenizer worker 间唯一;
    # 纯 time.time() 在同一纳秒可能碰撞,
    # 导致共享的 DetokenizerManager 抛 KeyError。
    rid = f"{HEALTH_CHECK_RID_PREFIX}_{uuid.uuid4().hex}"
​
    if _global_state.tokenizer_manager.is_generation:
        gri = GenerateReqInput(
            rid=rid,
            input_ids=[0],
            sampling_params=sampling_params,
            log_metrics=False,
        )
        # ... 分解模式配置 ...
    else:
        gri = EmbeddingReqInput(
            rid=rid,
            input_ids=[0],
            sampling_params=sampling_params,
            log_metrics=False,
        )
    # ... 后续不变 ...
python/sglang/srt/disaggregation/encode_server.py dependency-wiring

分解模式下的 encode server,包含 DP worker 内部健康编码和 dispatcher 端的健康检查,两处都需要唯一 rid。

# python/sglang/srt/disaggregation/encode_server.pyimport uuid # 新增导入async def _dp_worker_health_encode(enc: MMEncoder) -> None:
    """DP worker 内部的健康探测,执行一次 dummy encode。"""
    # ... 跳过忙碌 worker 和模态选择 ...
​
    # uuid 保证不同 worker 进程的 rid 不会碰撞;
    # bare time.time() 在并发时可能产生重复。
    req_id = f"{HEALTH_CHECK_RID_PREFIX}_{uuid.uuid4().hex}"
    try:
        _, _, _, error_msg, error_code = await enc.encode(
            mm_items=mm_items,
            modality=modality,
            req_id=req_id,
            num_parts=1,
            part_idx=0,
        )
    finally:
        enc.embedding_to_send.pop(req_id, None)
    # ... 错误处理 ...# dispatcher 侧的 /health_generate 端点同样替换
# req_id = f"{HEALTH_CHECK_RID_PREFIX}_{uuid.uuid4().hex}"

评论区精华

没有提炼出高价值讨论线程

当前评论区没有形成足够清晰的争议点或结论,后续有更多讨论时会体现在这里。

风险与影响

风险极低。uuid4 碰撞概率在可忽略范围内,且 rid 仅用于内部路由和 decode status 查找,不依赖单调性或可读性。唯一潜在风险是 uuid 生成相比单调递增 ID 或 f"{pid}_{time_ns()}" 可能带来微小的额外开销,但在健康检查低频场景下完全可接受。返回的 Response 内容和逻辑流程未变。

直接修复了多 tokenizer worker 模式下健康检查的稳定性问题,影响用户为所有使用 --tokenizer-worker-num > 1 的部署,包括 DP 分解(disaggregation)场景。对其他单 worker 配置无影响。修复本身不涉及模型推理路径,不触发回归。

低风险

关联 Issue

#28142 [Bug] KeyError:'HEALTH_CHECK_1781301554.5131953'

完整报告

参与讨论