Prhub

#33593 [RL] Expose top-p-only sampling masks

原始 PR 作者 nanjiangwill 合并时间 2026-08-15 07:40 文件变更 6 提交数 2 评论 5 代码增减 +68 / -16

执行摘要

支持 top-p-only 采样掩码并新增 chat 接口暴露

现有请求准入要求有限 top_k,排除了常见的 RL rollout 配置 top_k=-1top_p<1,尽管 top-p 截断定义了可重放的采样支持;同时 /v1/chat/completions 未暴露该原语,而 Miles 等 RL 工作流同时使用 /generate/v1/chat/completions(PR body 原话)。

值得精读。它展示了如何以最小改动(3 处源码 + 3 处测试)扩展一个既有原语的请求面,并通过拆分后续 PR 控制性能风险。关注 sampling_mask_max_top_k 对批处理的影响,以及 #34037 的后续落地。

讨论亮点

JustinTong0323 在 scheduler.py 的评审中指出准入放宽的性能隐患:

Allowing TOP_K_ALL here makes any top-p-only mask request set sampling_mask_max_top_k to TOP_K_ALL; Sampler._compute_sampling_mask_from_probs then full-sorts every row in the decode batch and materializes all kept IDs on CPU before filtering return_sampling_masks, so one opt-in request can impose full-vocabulary work on unrelated co-batched requests.

并补充 top_p < 1 不是大小边界(平坦 logits 可保留约 top_p * vocab_size,接近 1 的值可能 float32 舍入为 1.0)。作者 nanjiangwill 回应:

the performance concern is valid...But that is a general issue with the primitive from #27408...I split the row filtering and size bound into #34037

最终 JustinTong0323 批准,确认本 PR 的准入扩展与 #34037 的通用加固方向一致。

实现拆解

1. 放宽 /generate 准入

python/sglang/srt/managers/scheduler.pyhandle_generate_request 中,将原有的 req.sampling_params.top_k == TOP_K_ALL 拒绝条件改为计算 uses_top_k_or_top_p_truncationtop_k != TOP_K_ALLtop_p < 1.0),并在不满足时以新错误消息拒绝。这样 top_k=-1 配合 top_p<1 的配置可进入掩码返回链路,同时完整词汇表请求仍被拦截。

2. 新增 chat 请求 schema 字段

python/sglang/srt/entrypoints/openai/protocol.pyChatCompletionRequest 中新增 return_sampling_mask: bool = False,默认关闭,避免改变现有请求行为。

3. 聊天请求验证与转发

python/sglang/srt/entrypoints/openai/serving_chat.py 中,_validate_request 增加约束:return_sampling_mask 必须伴随 return_meta_info=true,否则返回错误;_convert_to_internal_requestrequest.return_sampling_mask 转发到 GenerateReqInput.return_sampling_mask,复用既有采样掩码输出链路。

4. 测试配套

更新 test/registered/sampling/test_sampling_mask.py,新增 /generate/v1/chat/completions 的 top-p-only 掩码端到端用例(_TOP_P_SMALL=1e-5 保证严格截断),并新增完整词汇表拒绝用例;更新 test_serving_chat.pytest_protocol.py 覆盖 schema 默认值、验证与转换。GPU 端到端用例注册到 CI,本地协议与 serving-chat 单元套件 135 项通过。

文件 模块 状态 重要度
python/sglang/srt/managers/scheduler.py 调度器 modified 6.41
test/registered/sampling/test_sampling_mask.py 采样掩码 modified 6.7
python/sglang/srt/entrypoints/openai/serving_chat.py 聊天服务 modified 5.23
python/sglang/srt/entrypoints/openai/protocol.py 协议 modified 4.35
test/registered/unit/entrypoints/openai/test_serving_chat.py 聊天服务 modified 4.8
test/registered/unit/entrypoints/openai/test_protocol.py 协议 modified 3.11

关键符号

handle_generate_request _validate_request _convert_to_internal_request test_generate_returns_top_p_only_sampling_mask test_chat_completions_returns_top_p_only_sampling_mask test_generate_rejects_full_vocabulary_sampling_mask test_validate_request_rejects_sampling_mask_without_meta_info

关键源码片段

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

核心准入逻辑变更:将采样掩码请求从仅限有限 top_k 扩展为 top_k 有限或 top_p<1,是本次功能的关键开关。

# 准入判断:只有 top_k 有限或 top_p < 1 时才允许返回采样掩码。
# top_p < 1 不是严格的大小边界,但这是准入层的可接受近似;
# 行级过滤与硬上限在 #34037 中统一加固。
uses_top_k_or_top_p_truncation = (
    req.sampling_params.top_k != TOP_K_ALL or req.sampling_params.top_p < 1.0
)
if req.return_sampling_mask and not uses_top_k_or_top_p_truncation:
    # 完整词汇表掩码会放大元数据,直接拒绝请求
    error_msg = (
        "return_sampling_mask cannot return the full vocabulary; set "
        "top_p < 1 or a finite top_k."
    )
    req.set_finish_with_abort(error_msg)
    self.init_req_max_new_tokens(req)
    self._add_request_to_queue(req)
    return
test/registered/sampling/test_sampling_mask.py test-coverage

端到端覆盖 top-p-only `/generate` 与 `/v1/chat/completions` 的掩码返回及完整词汇表拒绝,是功能正确性的主要验证。

def test_chat_completions_returns_top_p_only_sampling_mask(self):
    # 通过 /v1/chat/completions 验证 top-p-only 采样掩码返回
    response = requests.post(
        self.base_url + "/v1/chat/completions",
        json={
            "model": self.model,
            "messages": [{"role": "user", "content": "Name a capital city."}],
            "temperature": 1.0,
            "top_p": _TOP_P_SMALL, # 1e-5 保证严格截断
            "max_tokens": _MAX_NEW_TOKENS,
            "ignore_eos": True,
            "return_sampling_mask": True,
            "return_meta_info": True, # return_sampling_mask 的前置要求
            "return_token_ids": True,
        },
        timeout=60,
    )
    self.assertEqual(response.status_code, 200, response.text)
​
    choice = response.json()["choices"][0]
    output_ids = choice["token_ids"]
    meta_info = choice["meta_info"]
    sampling_masks = meta_info["output_token_sampling_mask"]
    sampling_logprobs = meta_info["output_token_sampling_logprobs"]
​
    # mask 与 token 一一对齐,且每个采样 token 都在其 mask 中
    self.assertEqual(len(output_ids), _MAX_NEW_TOKENS)
    self.assertEqual(len(sampling_masks), len(output_ids))
    self.assertEqual(len(sampling_logprobs), len(output_ids))
    for output_id, sampling_mask in zip(output_ids, sampling_masks):
        self.assertIn(output_id, sampling_mask)

评论区精华

top-p-only 掩码的批处理性能与大小上限 性能

JustinTong0323 指出 `sampling_mask_max_top_k` 会被设为 `TOP_K_ALL`,导致 `Sampler._compute_sampling_mask_from_probs` 对整个 decode 批次全排序并物化到 CPU,单请求即可拖累无关请求;且 `top_p < 1` 不是大小边界,接近 1 的 top_p 可能在 float32 中舍入为 1.0 而返回完整词汇表。

结论:nanjiangwill 认可性能问题,但认为这是 #27408 原语的一般问题,已将行过滤与大小上限拆分到 #34037;本 PR 的准入条件是必要且独立的合并单元。JustinTong0323 最终批准。 · 已解决

风险与影响

  • 性能风险(scheduler.py):放宽准入后,top-p-only 请求可能产生接近完整词汇表的 support,Sampler._compute_sampling_mask_from_probs 会对整个 decode 批次逐行全排序并物化到 CPU,单个 opt-in 请求可能拖累同批次无关请求。
  • 正确性风险(scheduler.py):top_p < 1 不是严格大小边界,平坦 logits 下 support 约 top_p * vocab_size,接近 1 的 top_p 可能在 float32 top_ps 张量中舍入为 1.0,从而返回完整词汇表。
  • 兼容性风险(serving_chat.py):新增 return_sampling_mask 必须配合 return_meta_info=true 的校验,用户若未设置会得到 400 错误,属 API 行为变化,需文档同步。
  • 范围风险:/v1/completions 未支持,端点行为不一致,用户可能误用。
  • 用户:Miles 等 RL 工作流可在 top_k=-1, top_p<1 配置下通过 /generate/v1/chat/completions 获取可重放的采样掩码与 logprobs,补齐了 RL rollout 的 replay 支持。
  • 系统:采样掩码准入从有限 top_k 扩展到 top-p 截断,可能增加部分请求的元数据计算与传输开销,但默认关闭(False),且复用既有输出链路。
  • 团队:明确了采样掩码原语(#27408)与策略层准入的职责边界,为 #34037 的行级过滤与大小上限加固提供了独立合并的前置。
性能风险:全批次全排序 准入逻辑变更 潜在完整词汇表掩码 缺少硬大小上限 新增 API 校验约束

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论