Prhub

#36006 refactor(disagg): register SGLANG_ENCODER_MM_LOAD_WORKERS in Envs

原始 PR 作者 ShangmingCai 合并时间 2026-08-23 12:47 文件变更 2 提交数 1 评论 3 代码增减 +2 / -2

执行摘要

将编码器 I/O 线程数环境变量注册进 Envs 描述符

PR body 指出 EncoderPreprocessor.__init__ 中两个相邻线程池初始化语句,一个遵循 env-var 约定(envs.SGLANG_ENCODER_PREPROC_WORKERS.get()),一个没有(int(os.environ.get("SGLANG_ENCODER_MM_LOAD_WORKERS", 4)))。按项目约定,sglang 自有的 SGLANG_* 变量都应注册在 Envs 类并通过描述符读取;裸 os.environ 读取对描述符 API 不可见,无法用 .get() 读取,更实际的是测试无法 .override() 它。作者还专门核实两个 knob 不是重复配置——preproc_executor 是 CPU 预处理池,io_executor 是媒体加载 I/O 池——因此不做合并。

值得快速浏览,不必精读。PR 本身改动极小且论证充分(等价性表格 + 静态检查),作为 disaggregation 系列清理的收尾没有惊喜;更值得关注的是其 PR body 中识别的 SGLANG_VLM_CACHE_SIZE_MB 40 倍默认值差异——这是一个真实存在的配置一致性隐患,建议跟进单独修复。可借鉴的设计习惯是:坚持所有 SGLANG_* 变量注册进 Envs 并用描述符读取,使变量对测试可见、可 override、可审计,裸读环境变量会让默认值漂移变得不可见。

讨论亮点

该 PR 无任何 review comments(0 条),技术交锋为零。值得记录的讨论信息集中在 issue 评论与 PR body:作者发起 /rerun-test test/registered/disaggregation/test_epd_disaggregation.py,CI 机器人在 4-gpu-h100 上确认该测试通过(PR Test Base/Extra 此前为 ❌ 状态,AMD ROCm 7.2 仍在跑)。PR body 中作者主动澄清了两点:一是两个 worker 配置名相似但职责不同(预处理 vs I/O),不做合并;二是 SGLANG_TORCH_PROFILER_DIRASCEND_MF_* 等裸读被有意保留(仓库级既有模式或供应商前缀变量,另案处理)。最有价值的发现是 SGLANG_VLM_CACHE_SIZE_MB 的 40 倍默认值差异,作者说明 "correcting it changes real behavior in either direction",并主动提出可单独开 PR 处理。

实现拆解

  1. 注册环境变量python/sglang/srt/environ.py):在 Envs 类的 "Encoder pipeline and disaggregation" 区块中,紧挨 SGLANG_ENCODER_PREPROC_WORKERS = EnvInt(8) 之后新增 SGLANG_ENCODER_MM_LOAD_WORKERS = EnvInt(4),默认值与原先内联默认一致,避免行为漂移。
  2. 迁移读取点python/sglang/srt/disaggregation/encoder/preprocessor.py):EncoderPreprocessor.__init__io_executormax_workersint(os.environ.get("SGLANG_ENCODER_MM_LOAD_WORKERS", 4)) 改为 envs.SGLANG_ENCODER_MM_LOAD_WORKERS.get()。这是该文件对 os 的唯一使用,因此删除 import osenvs 此前已导入),同时清掉一个孤儿导入。
  3. 等价性与静态检查验证:PR 用表格论证三种可达状态行为完全一致——变量未设置时均得 4;设置为合法整数 "16" 时均得 16.override(3) 是描述符新增能力(裸读不可达)。固定版本 ruff(F401/F821/UP037)、blackisort 全部通过,其中 F401 正是用于确认删除 import os 后无残留引用。
  4. 测试与部署配套:PR 明确不新增测试(无可测行为变化),也未提供 accuracy/speed 数据(不涉及模型前向)。编码路径未在本地验证(需要 GPU 与在线 encode server),由 CI 的 EPD 测试覆盖,重跑 test/registered/disaggregation/test_epd_disaggregation.py 通过。
  5. 遗留问题识别(不随本 PR 修复):PR body 指出 disaggregation/encoder/server.py 裸读 SGLANG_VLM_CACHE_SIZE_MB 且内联默认值为 4096,而 Envs 中注册的默认值为 100——变量未设置时 encode server 的缓存尺寸是全系统其余部分的 40 倍。作者有意不在本 PR 修正,因为任何方向的修正都会改变真实行为,需要由当初设定该缓存大小的 owner 决策。
文件 模块 状态 重要度
python/sglang/srt/disaggregation/encoder/preprocessor.py 编码器 modified 5.16
python/sglang/srt/environ.py 环境配置 modified 4.35

关键符号

EncoderPreprocessor.__init__

关键源码片段

python/sglang/srt/disaggregation/encoder/preprocessor.py dependency-wiring

核心变更点:io_executor 线程池大小从裸 os.environ 读取改为 Envs 描述符读取,并删除该文件唯一的 os 导入。

# python/sglang/srt/disaggregation/encoder/preprocessor.py
# CPU 侧多模态预处理模块,服务于 EPD 编码器;模块文档注明未来可由 Rust 实现替换。
# 本 PR 只改动线程池初始化的读取方式,其余逻辑不变。import asyncio
import concurrent.futures
import functools
import logging
# import os 已删除 —— 该文件对 os 的唯一使用被描述符读取替换后成孤儿导入
from dataclasses import dataclass
from typing import Callable, List, Optional, Tuple, Unionfrom sglang.srt.environ import envsclass EncoderPreprocessor:
    # __init__ 中两个相邻线程池:一个早已遵循 Envs 约定,一个是本次迁移对象
    def __init__(self, ...):
        ...
        # CPU 预处理线程池:控制 config 校验、HF processor 调用等 CPU 工作并发度
        self.preproc_executor = concurrent.futures.ThreadPoolExecutor(
            max_workers=envs.SGLANG_ENCODER_PREPROC_WORKERS.get()
        )
        # 媒体加载 I/O 线程池(图片 / 视频 / 音频读取):原为裸 os.environ 读取,
        # 现统一走 Envs 描述符 —— 默认值同为 4,且测试可通过
        # envs.SGLANG_ENCODER_MM_LOAD_WORKERS.override(n) 注入确定性数值
        self.io_executor = concurrent.futures.ThreadPoolExecutor(
            max_workers=envs.SGLANG_ENCODER_MM_LOAD_WORKERS.get()
        )
python/sglang/srt/environ.py core-logic

项目 SGLANG_* 环境变量的唯一注册表,新增 SGLANG_ENCODER_MM_LOAD_WORKERS = EnvInt(4),使变量对描述符 API 与测试可见。

# python/sglang/srt/environ.py —— 项目 SGLANG_* 环境变量的唯一注册表。
# 约定:sglang 自有的每个变量都必须注册为 EnvField 描述符,统一经 .get() 读取。class Envs:
    # ===================================================================
    # Encoder pipeline and disaggregation
    # ===================================================================
    SGLANG_ENCODER_GRPC_TIMEOUT_SECS = EnvInt(60)
    # Encoder receiver selection: http|grpc (used by EPD paths).
    SGLANG_ENCODER_MM_RECEIVER_MODE = EnvStr("http")
    SGLANG_ENCODER_RECV_TIMEOUT = EnvFloat(180.0)
    SGLANG_ENCODER_MAX_BATCH_SIZE = EnvInt(8)
    SGLANG_ENCODER_PREPROC_WORKERS = EnvInt(8)
    # 媒体加载 I/O 线程池大小;与 PREPROC_WORKERS(CPU 预处理池)是并列的两个 knob
    SGLANG_ENCODER_MM_LOAD_WORKERS = EnvInt(4)
    # EncoderBootstrapServer health-check tuning. Interval == 0 disables it.
    SGLANG_ENCODER_BOOTSTRAP_HEALTH_CHECK_INTERVAL = EnvFloat(10.0)
    ...

评论区精华

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

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

风险与影响

技术风险很低,但仍有三处需要留意:一是行为等价性依赖 EnvInt.get() 语义——EnvField.get() 对无法解析的值仅告警并返回默认值,与 int() 直接抛 ValueError 存在理论差异,PR 仅验证了两种合法可达状态;二是编码路径未在本地运行(需 GPU 与在线 encode server),改动依赖 CI 覆盖,重跑的 test_epd_disaggregation.py 通过但覆盖深度有限;三是遗留的 SGLANG_VLM_CACHE_SIZE_MB 默认值不一致(encode server 4096 MB vs 系统其余 100 MB)未被本 PR 修复,属于真实存在的配置分歧。此外本次未新增任何测试文件,无行为变化前提下可接受,但后续若调整默认值或解析语义应补上描述符行为的单元测试。

对用户与系统运行无感知影响:线程池大小在三种状态下与原实现完全一致,改动仅发生在 EncoderPreprocessor 构造路径,不触碰模型前向、内核或网络路径。对团队与工程治理而言收益明显——该变量从此可被测试通过 envs.SGLANG_ENCODER_MM_LOAD_WORKERS.override(n) 注入确定性数值,且所有 encoder 相关环境变量读取收敛到 Envs 描述符,与仓库 config 系列重构(#35904–#35910)的理念一致,降低后续迁移与审计成本。影响范围极小:2 个文件、净 +2/-2 行。

编码路径未本地验证(需 GPU 与 encode server) 无新增测试覆盖 行为等价性依赖 EnvInt 解析语义 遗留 VLM 缓存默认值 40 倍差异未修

关联 Issue

#35838 refactor(disagg): remove unreferenced dead code
#35843 refactor(disagg): remove dead build_and_send_encode_request
#35844 refactor(disagg): remove dead get_embedding_port
#35847 refactor(disagg): collapse duplicated branches in get_kv_class
#35886 refactor(disagg): extract _all_reduce_polls helper
#35890 fix(disagg): PD transfer-failure injection was silently inert
#35948 refactor(disagg): hoist duplicated _handle_staging_req into a mixin
#35950 refactor(disagg): drop dead placeholder overrides in Common KV sender/receiver
#35980 refactor(disagg): hoist staging helper imports out of the bootstrap loops

完整报告

参与讨论