执行摘要
- 一句话:增强请求转储的鲁棒性与可配置性
- 推荐动作:建议精读
tokenizer_manager.py 中的 dump_requests、_dump_data_to_file 和 dump_requests_before_crash 方法,理解 Pickle 回退的设计模式。值得关注的设计决策是:在出错时只丢弃 server_args 而非整体放弃,这是一种优雅的降级方案。
功能与动机
PR body 明确指出:在 --trust-remote-code + MoE 模型下,ServerArgs.get_model_config() 懒加载的 ModelConfig 中的 hf_config 处于动态命名空间 transformers_modules.* 下,无法安全 Pickle,导致 _dump_data_to_file 和 dump_requests_before_crash 留下空/损坏的 .pkl 文件。同时,--enable-routing-replay 和 --return-hidden-states 会在 meta_info 中注入 base64 编码的 routed_experts 和 hidden_states,这些数据在回放工具中并不使用。
实现拆解
- Pickle 安全回退:在
tokenizer_manager.py 的 _dump_data_to_file 和 dump_requests_before_crash 两个方法中,将 pickle.dump 包裹在 try/except 中;捕获 Exception 后,将 server_args 字段置为 None 重新尝试序列化,确保请求数据至少能保留。
- meta_info 键过滤:在
TokenizerManager 的 init_request_logging_and_dumping 中新增实例属性 dump_requests_exclude_meta_keys,默认值为 ['routed_experts', 'hidden_states']。在 dump_requests 方法中,若 meta_info 包含这些键,则创建浅拷贝副本去除它们,不修改原 out_dict。
- 数据契约扩展:在
io_struct.py 的 ConfigureLoggingReq dataclass 中新增可选字段 dump_requests_exclude_meta_keys: Optional[List[str]],使得可通过 API 动态配置。
- CLI 入口更新:在
configure_logging.py 中新增 --dump-requests-exclude-meta-keys 参数,支持以逗号分隔的字符串输入,空字符串表示保留所有键;若未设置则使用服务端默认值。
关键文件:
python/sglang/srt/managers/tokenizer_manager.py(模块 管理器;类别 source;类型 core-logic;符号 init_request_logging_and_dumping, configure_logging, dump_requests, _dump_data_to_file): 核心改动所在:修改了转储相关的三个方法,实现了 Pickle 安全回退和 meta_info 键过滤。
python/sglang/srt/managers/configure_logging.py(模块 CLI 工具;类别 source;类型 core-logic): CLI 入口,新增 --dump-requests-exclude-meta-keys 参数,支持逗号分隔字符串。
python/sglang/srt/managers/io_struct.py(模块 数据结构;类别 source;类型 core-logic;符号 ConfigureLoggingReq): 数据契约更新:ConfigureLoggingReq 新增 dump_requests_exclude_meta_keys 字段。
关键符号:TokenizerManager.dump_requests, TokenizerManager._dump_data_to_file, TokenizerManager.dump_requests_before_crash, TokenizerManager.configure_logging
关键源码片段
python/sglang/srt/managers/tokenizer_manager.py
核心改动所在:修改了转储相关的三个方法,实现了 Pickle 安全回退和 meta_info 键过滤。
# python/sglang/srt/managers/tokenizer_manager.py (partial)
def dump_requests(self, state: ReqState, out_dict: dict):
# 如果配置了排除键且 meta_info 是 dict,则创建过滤后的副本
if self.dump_requests_exclude_meta_keys and isinstance(
out_dict.get("meta_info"), dict
):
exclude = self.dump_requests_exclude_meta_keys
if any(k in out_dict["meta_info"] for k in exclude):
filtered_meta = {
k: v
for k, v in out_dict["meta_info"].items()
if k not in exclude
}
# 不修改原 out_dict,仅对副本做过滤
out_dict = {**out_dict, "meta_info": filtered_meta}
self.dump_request_list.append(
(state.obj, time.time(), out_dict, state.rid)
)
if len(self.dump_request_list) >= self.dump_requests_threshold:
self._dump_data_to_file()
def _dump_data_to_file(self, ...):
...
def background_task():
os.makedirs(os.path.dirname(filename), exist_ok=True)
with open(filename, "wb") as f:
try:
pickle.dump(to_dump_with_server_args, f)
except Exception as e:
# ServerArgs 在 --trust-remote-code 下可能因动态
# transformers_modules 命名空间而不可 pickle。
# 降级:丢弃 server_args 仅保留请求数据。
logger.error(
f"Failed to pickle dump with server_args: {e!r}; "
"retrying without server_args"
)
f.seek(0)
f.truncate()
to_dump_with_server_args["server_args"] = None
pickle.dump(to_dump_with_server_args, f)
asyncio.create_task(asyncio.to_thread(background_task))
def dump_requests_before_crash(self, ...):
...
with open(filename, "wb") as f:
try:
pickle.dump(data_to_dump_with_server_args, f)
except Exception as e:
# 同上,降级策略一致
logger.error(...)
f.seek(0)
f.truncate()
data_to_dump_with_server_args["server_args"] = None
pickle.dump(data_to_dump_with_server_args, f)
python/sglang/srt/managers/configure_logging.py
CLI 入口,新增 --dump-requests-exclude-meta-keys 参数,支持逗号分隔字符串。
# python/sglang/srt/managers/configure_logging.py (partial)
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--url", type=str, default="http://localhost:30000")
parser.add_argument("--log-requests", action="store_true")
parser.add_argument("--log-requests-level", type=int, default=3)
parser.add_argument("--dump-requests-folder", type=str, default="/tmp/sglang_request_dump")
parser.add_argument("--dump-requests-threshold", type=int, default=1000)
parser.add_argument(
"--dump-requests-exclude-meta-keys",
type=str,
default=None,
help=(
"Comma-separated meta_info keys to strip from each dumped request "
"(e.g. 'routed_experts,hidden_states'). Pass an empty string to "
"keep all keys. If not set, the server default is used."
),
)
args = parser.parse_args()
payload = {
"log_requests": args.log_requests,
"log_requests_level": args.log_requests_level,
"dump_requests_folder": args.dump_requests_folder,
"dump_requests_threshold": args.dump_requests_threshold,
}
# 仅当用户显式指定时才覆盖服务端默认值
if args.dump_requests_exclude_meta_keys is not None:
payload["dump_requests_exclude_meta_keys"] = [
k.strip()
for k in args.dump_requests_exclude_meta_keys.split(",")
if k.strip()
]
response = requests.post(args.url + "/configure_logging", json=payload)
assert response.status_code == 200
评论区精华
Review 中 ispobock 提出了关于注释风格的改进建议,建议遵循“默认无注释,仅对非显而易见的 WHY 写注释”的原则。ByronHsu 表示赞同,并在第二个 commit 中精简了原有过多的描述性注释,仅保留了关于 pickle 回退原因的说明。
- 注释风格优化 (style): ByronHsu 接受建议,在第二个 commit 中移除了过量的描述性注释。
风险与影响
关联脉络
- PR #24861 [Utils] Refactor device cache emptying: 同为 tokenizer_manager 涉及的工具函数重构,虽然功能不同,但属于同一模块的持续改进。
- PR #24768 [PrefillDelayer] support NCCL all-gather for cross-DP info sync: 同样是调度器相关的性能与稳定性改进,且涉及跨 DP 通信,属于同一次迭代周期内的调度层优化。
参与讨论