Prhub

#34999 [Engine] Freeze GC after server warmup

原始 PR 作者 merrymercy 合并时间 2026-08-17 13:41 文件变更 1 提交数 2 评论 2 代码增减 +21 / -0

执行摘要

warmup 后冻结 GC,减少请求期暂停

PR body 指出:启动期间创建的长期存活静态对象仍会被后续 generation-2 垃圾回收收集,从而在请求阶段引入暂停(request-time pauses)。因此希望在 warmup 完成后调用已有的 /freeze_gc 端点,让 tokenizer、scheduler 和 detokenizer 进程冻结其静态对象图,降低运行时 GC 开销。

该 PR 值得精读,其"失败不阻塞启动"的降级设计值得借鉴。建议后续补充一个端到端冒烟测试,验证 warmup 后 /freeze_gc 被调用且失败时不影响服务就绪;同时关注 freeze_gc 的内存语义,确认运行时新增静态对象不会导致泄漏。

讨论亮点

唯一的一条 review 评论来自作者 merrymercy 本人,针对内联的冻结代码建议:

wrap this into a separate function and call it in a single-line

该建议在第二个 commit(Refactor post-warmup GC freeze)中得到落实,把内联逻辑抽成 _freeze_gc_after_server_warmup 并在调用处简化为一行,属于代码组织层面的重构,无实质设计争议。

实现拆解

实现分为以下步骤:

  1. 在 http_server.py 的 _execute_server_warmup 之后新增 _freeze_gc_after_server_warmup 函数,负责向 server_args.url() + "/freeze_gc" 发送 POST 请求,并设置 timeout=10 与 TLS 验证(server_args.ssl_verify())。
  2. 请求认证使用 server_args.admin_api_key or server_args.api_key,通过 Authorization: Bearer 头传递;未配置密钥时不带认证头。
  3. 在 _wait_and_warmup 的 warmup 逻辑之后、"The server is fired up and ready to roll!" 日志之前调用该函数,保证无论是否正常执行 warmup(包括 skip_server_warmup 或 elastic joiner 模式)都会尝试冻结 GC。
  4. 异常处理仅捕获 requests.exceptions.RequestException,记录 warning 日志但不中断启动流程,符合"优化非必需"的设计。
  5. 测试与配置配套:未新增测试文件,作者仅执行了 py_compile 验证;未引入新的配置项。
文件 模块 状态 重要度
python/sglang/srt/entrypoints/http_server.py 服务入口 modified 6.83

关键符号

_freeze_gc_after_server_warmup

关键源码片段

python/sglang/srt/entrypoints/http_server.py core-logic

唯一变更文件,在服务启动公共路径 _wait_and_warmup 中注入 GC 冻结逻辑,影响所有 HTTP 部署的启动行为与请求期 GC 暂停。

def _freeze_gc_after_server_warmup(server_args: ServerArgs):
    # 服务 warmup 完成后冻结 GC,让启动期创建的静态对象跳过后续 gen2 回收,
    # 避免请求阶段出现不必要的 GC 暂停。
    # 通过 /freeze_gc 同时冻结 scheduler 与 detokenizer 进程。
    freeze_key = server_args.admin_api_key or server_args.api_key
    freeze_headers = {}
    if freeze_key:
        # 携带 Bearer token 的请求,认证方式与服务器启动参数保持一致。
        freeze_headers["Authorization"] = f"Bearer {freeze_key}"
    try:
        res = requests.post(
            server_args.url() + "/freeze_gc",
            headers=freeze_headers,
            timeout=10,
            verify=server_args.ssl_verify(),
        )
        res.raise_for_status()
    except requests.exceptions.RequestException:
        # GC 冻结属于优化路径,失败只记录告警,不阻塞服务启动。
        logger.warning("post-warmup freeze_gc failed", exc_info=True)
​
​
def _wait_and_warmup(
    server_args: ServerArgs,
    launch_callback: Optional[Callable[[], None]] = None,
    execute_warmup_func: Callable = _execute_server_warmup,
):
    # 在服务标记为 ready 之前统一触发一次 GC 冻结。
    _freeze_gc_after_server_warmup(server_args)
    # The server is ready for requests
    logger.info("The server is fired up and ready to roll!")

评论区精华

将 GC 冻结逻辑抽成独立函数 style

merrymercy 评论:"wrap this into a separate function and call it in a single-line",要求把内联的 freeze_gc 调用封装为单独函数并一行调用。

结论:第二个 commit "Refactor post-warmup GC freeze" 落实该建议,新增 _freeze_gc_after_server_warmup 并将调用简化为一行。 · 已解决

风险与影响

  1. 核心启动路径变更:_wait_and_warmup 是所有 HTTP 服务启动的公共路径,即使 skip_server_warmup 或 elastic joiner 模式也会触发一次同步 POST 请求;若 /freeze_gc 处理缓慢,最多延迟服务就绪 10 秒(超时后仅告警)。
  2. 端点契约依赖:当前实现假定 /freeze_gc 端点存在;若旧版本或 rust-server 模式下未实现该端点,请求会失败并被捕获,但后续若端点行为变化(如返回非 RequestException 异常)可能改变失败容忍度。
  3. GC 冻结的内存语义:冻结静态对象图后,运行时若继续产生新的长期存活对象,可能不再被 GC 回收,存在内存增长风险;该语义取决于 freeze_gc 的实现,PR 中未详细说明。
  4. 缺少自动化测试:21 行新增逻辑无直接测试覆盖,回归依赖手工验证。

对用户:预期减少请求阶段的 GC 暂停,改善长时运行服务的尾部延迟。对系统:所有 sglang server 启动流程都会多一次内部 HTTP 调用,失败不影响启动,但会新增一条 warning 日志。对团队:/freeze_gc 端点成为启动契约的一部分,后续需保持其稳定性和可用性;改动集中在单个文件,影响面清晰。

核心启动路径变更 缺少测试覆盖 依赖 /freeze_gc 端点契约

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论