Prhub

#29296 [EPD][BugFix] Fix encoder health check with global cache TP

原始 PR 作者 cccccya 合并时间 2026-07-01 11:04 文件变更 1 提交数 3 评论 2 代码增减 +30 / -15

执行摘要

修复 EPD 全局缓存 TP 下编码器健康检查挂起问题

当启用 --enable-mm-global-cachetp-size > 1 时,编码器的健康检查会挂起,因为非零 TP rank 的假请求走 encode_request() 路径,该路径在全局缓存下可能进入 rank 0 未参与的缓存协调逻辑。此外,语言服务器会因连续失败而驱逐编码器 URL,导致编码器陷入僵死状态。

该 PR 是一个典型的小范围 bugfix,逻辑清晰,改动集中。值得关注的是其修复思路——通过识别请求类型并路由到不同的处理路径,解决了分布式环境下控制流不一致导致的死锁问题。适合作为理解 EPD 架构中健康检查与请求分发机制的参考。

讨论亮点

无实质讨论。仅在提交过程中有两次自动合并 main 分支的 merge commit,review 仅包含 gemini-code-assist 的自动摘要和两位 reviewer 的简单批准(LGTM)。

实现拆解

  1. 重构请求分发逻辑:将 run_encoder 中的内联 if-elif 分支提取为独立的 _handle_encoder_worker_request 函数,使请求处理更清晰,并为后续新增分支做准备。
  2. 新增健康检查专用分支:在 _handle_encoder_worker_request 中增加一个 elif 分支,通过检查 request["req_id"].startswith(HEALTH_CHECK_RID_PREFIX) 来识别健康检查请求。
  3. 路由到 encoder.encode():健康检查请求直接调用 encoder.encode(),传递所有必要字段(mm_itemsmodalityreq_idnum_partspart_idxhashes)。这与 rank 0 的 /health 端点行为一致,绕过了可能挂起的缓存协调逻辑。
  4. 保留常规路径:非健康检查的常规编码请求仍走 encoder.encode_request(),保持原有逻辑不变。
文件 模块 状态 重要度
python/sglang/srt/disaggregation/encode_server.py 编码器 modified 6.4

关键符号

_handle_encoder_worker_request

关键源码片段

python/sglang/srt/disaggregation/encode_server.py core-logic

唯一变更文件,重构了请求分发逻辑并新增健康检查专用分支。

# 从 run_encoder 中提取的请求分发函数,统一处理所有请求类型
async def _handle_encoder_worker_request(encoder: MMEncoder, request):
    if isinstance(request, ProfileReq):
        # Profiling 请求,直接处理 start/stop
        if request.req_type == ProfileReqType.START_PROFILE:
            if encoder.profiler is None:
                encoder.profiler = EncoderProfiler(encoder.rank)
            encoder.profiler.start(request)
        else:
            encoder.profiler.stop()
    elif isinstance(request, dict) and request.get("type") == "batch_encode":
        # 批量编码请求
        await encoder.batch_encode(
            request["requests"],
            Modality.from_str(request["modality"]),
        )
    elif (
        isinstance(request, dict)
        and isinstance(request.get("req_id"), str)
        and request["req_id"].startswith(HEALTH_CHECK_RID_PREFIX)
    ):
        # 健康检查请求:直接调用 encode(),避免走 encode_request() 路径
        # 该路径在全局缓存 + TP > 1 时会进入缓存协调逻辑导致死锁
        await encoder.encode(
            mm_items=request["mm_items"],
            modality=Modality.from_str(request["modality"]),
            req_id=request["req_id"],
            num_parts=request["num_parts"],
            part_idx=request["part_idx"],
            hashes=request.get("hashes"),
        )
    else:
        # 默认路径:普通编码请求
        await encoder.encode_request(request, Modality.from_str(request["modality"]))

评论区精华

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

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

风险与影响

此修改在单个文件的核心路径上进行,引入的风险较低。主要关注点在于:新分支仅通过 req_id 的前缀判断,如果未来有其他请求也使用 HEALTH_CHECK_RID_PREFIX 前缀但需要不同处理,可能产生混淆。但当前设计明确,与 rank 0 的健康检查处理一致,回归风险很小。

仅影响编码器服务器在 TP 分布式环境下的健康检查行为。修复后,健康检查在任何 TP 规模下均能正常工作,不会因缓存协调逻辑而挂起。对普通编码请求无影响。

核心路径变更 缺少测试覆盖

关联 Issue

未识别关联 Issue

当前没有检测到明确关联的 Issue 链接,后续同步到相关引用后会出现在这里。

完整报告

参与讨论