Prhub

#29436 feat: first-class session identity in SGLang

原始 PR 作者 ishandhanani 合并时间 2026-06-28 22:16 文件变更 24 提交数 4 评论 6 代码增减 +136 / -64

执行摘要

引入顶层 session_id 作为稳定请求标识

PR body 指出:需要一种稳定的请求身份标识来驱动 session radix cache,而不必依赖显式的 session_params。通过顶层 session_id 简化用户交互,并且保持现有 session_params 路径不变。

值得精读,尤其是 session 身份与 session_params 解耦的设计,以及 radix session 隐式启用模式。注意兼容性迁移。

讨论亮点

无实质 review 讨论,作者自行合并。CI 全部通过。

实现拆解

  1. io_struct.pyGenerateReqInputTokenizedGenerateReqInput 中添加可选 session_id 字段,并在 normalize_batch_and_arguments 中增加互斥校验。
  2. 在 Rust gRPC 请求映射 request_utils.rsbuild_text_generate_dictbuild_generate_dict 中,当 session_id 存在时插入到请求字典。
  3. scheduler.pyhandle_generate_request 中,将 radix_native_session 的判断条件从 session_params.id 改为顶层 session_id,移除了对 session_params 的校验,并将 session_id 传入 Req 对象。同时调整 open_sessionclose_session 逻辑:open_session 只转发给 session_controller(传统路径),close_session 如果启用 radix cache 则调用 release_radix_session,否则调用 session_controller.close
  4. session_radix_cache.py 中移除 register_session 方法,将 release_session 重命名为 release_radix_session,并在 _tag_session_leaf 中加入 enable_session_radix_cache 守卫。
  5. 在 Engine、OpenAI、HTTP Server 等 entrypoint 添加 session_id 参数传递。
  6. 新增测试用例覆盖禁用缓存时不标记、传统 release 不影响 radix session、close tombstone 阻止晚期 finish 等场景。
文件 模块 状态 重要度
python/sglang/srt/managers/scheduler.py 调度器 modified 7.24
python/sglang/srt/mem_cache/session_radix_cache.py 缓存层 modified 7.39
python/sglang/srt/managers/io_struct.py 请求结构 modified 5.97
rust/sglang-grpc/src/utils/request_utils.rs gRPC 处理 modified 6.99
test/manual/core/test_session_radix_cache.py 缓存层 modified 7.07

关键符号

_tag_session_leaf release_radix_session handle_generate_request build_text_generate_dict build_generate_dict

关键源码片段

python/sglang/srt/managers/scheduler.py core-logic

核心调度逻辑,修改 radix_native_session 判断条件并移除对 session_params 的校验,将 session_id 传入 Req。

def handle_generate_request(self, recv_req: TokenizedGenerateReqInput):
    # 从 session_params.id 获取传统 session id
    session_id = (
        recv_req.session_params.id if recv_req.session_params is not None else None
    )
    # Radix-native sessions use only the top-level session_id.
    radix_native_session = (
        recv_req.session_id is not None
        and self.server_args.enable_session_radix_cache
    )
​
    if session_id is None or radix_native_session:
        # Normal non-session request, or a radix-native session request
        # ... (mm inputs, bootstrap port, etc.)
        req = Req(
            recv_req.rid,
            recv_req.input_text,
            recv_req.input_ids,
            recv_req.sampling_params,
            return_logprob=recv_req.return_logprob,
            top_logprobs_num=recv_req.top_logprobs_num,
            token_ids_logprob=recv_req.token_ids_logprob,
            stream=recv_req.stream,
            lora_id=recv_req.lora_id,
            session_id=recv_req.session_id, # 将顶层 session_id 传入 Req
            # ... other fields
        )
    else:
        # Legacy session request with session_params
        # ...
python/sglang/srt/mem_cache/session_radix_cache.py core-logic

Radix session 核心逻辑,修改了标记和释放语义。

def _tag_session_leaf(self, req: Req, radix_key, node=None) -> None:
    # 将请求的 session_id 标记到叶子节点(仅当 radix cache 启用时)
    if not self.enable_session_radix_cache:
        return # 缓存未启用,不执行任何操作
    self._ensure_session_radix_state()
    sid = getattr(req, 'session_id', None)
    if sid is None or sid in self._closed_session_ids:
        return
    if node is None:
        logger.warning(
            '_tag_session_leaf 被调用时未提供 node,将回退到 match_prefix'
        )
        node = self.match_prefix(MatchPrefixParams(key=radix_key)).last_device_node
    if node is not None and node is not self.root_node:
        session_ids = getattr(node, 'session_ids', None)
        if session_ids is None:
            session_ids = set()
            node.session_ids = session_ids
        session_ids.add(sid)
        self._session_leaves[sid].add(node)def release_radix_session(self, session_id: str) -> int:
    # 关闭 session:从每个标记的叶子节点中移除该 session 的持有关系
    # 仅当节点不再被任何 session 持有且满足回收条件时才释放物理 KV 块
    self._ensure_session_radix_state()
    self._remember_closed_session(session_id)
    indexed = self._session_leaves.pop(session_id, set())
    freed = 0
    for leaf in indexed:
        if session_id not in getattr(leaf, 'session_ids', set()):
            continue
        node = leaf
        while True:
            session_ids = getattr(node, 'session_ids', None)
            if session_ids is not None:
                session_ids.discard(session_id)
                if not session_ids:
                    delattr(node, 'session_ids')
            # 停止条件:根节点、锁定的节点、有子节点的节点、不可逐出的节点、或被其他 session 持用的节点
            if (
                node is self.root_node
                or node.lock_ref != 0
                or len(node.children) != 0
                or node not in self.evictable_leaves
                or getattr(node, 'session_ids', None)
            ):
                break
            parent = node.parent
            self.token_to_kv_pool_allocator.free(node.value)
            self._delete_leaf(node)
            freed += 1
            node = parent
    logger.info('released radix session %s: %d tokens freed', session_id, freed)
    return freed

评论区精华

整体评审 other

无实质讨论,作者自行合并。

结论:无 · closed

风险与影响

  1. 兼容性风险:新增 session_idsession_params 互斥约束,可能中断同时使用两者的旧请求。
  2. 行为变化:启用 enable_session_radix_cache 后,携带 session_id 的请求会自动开启 radix session,不再需要 /open_session,可能影响依赖显式打开的客户端。
  3. 废弃 register_session 方法:任何外部调用会报错,需迁移。

影响范围中等:修改了请求定义(io_struct)、调度器核心逻辑、gRPC 映射、多个 entrypoint 和测试。对使用 session radix cache 的用户体验简化明显;对仅用传统 session 的用户无影响。

session_id 与 session_params 互斥 隐式开启 radix session 移除 register_session 方法

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论