Skip to content

feat(rollout): add SGLang native group sampling - #269

Open
overloadedHenry wants to merge 4 commits into
redai-studio:mainfrom
overloadedHenry:feat/sglang-native-group-sampling
Open

feat(rollout): add SGLang native group sampling#269
overloadedHenry wants to merge 4 commits into
redai-studio:mainfrom
overloadedHenry:feat/sglang-native-group-sampling

Conversation

@overloadedHenry

@overloadedHenry overloadedHenry commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

标题

feat(rollout): 使用 SGLang 原生采样生成 prompt group

基于 branch 5b23011 开发

close #268

变更概述

本 PR 为标准 group-RM rollout 增加一条默认关闭的 native group sampling 路径。

对于同一个 prompt/image 的 8 条 sample,Relax 不再发送 8 个独立 /generate 请求,而是发送一个 sampling_params.n=8 的请求。SGLang 只需 prefill 一次公共多模态前缀,然后将其展开为 8 条 decode 分支。

原有逐 sample 路径继续作为 fallback,native 功能默认关闭。

具体改动

  • 新增 --sglang-native-group-sampling CLI 参数。
  • 为同质、first-turn、训练阶段的 group-RM sample 增加严格 eligibility 检查。
  • 增加单请求 native generation,并严格校验返回列表的类型与数量。
  • Baseline 与 native 路径复用相同的 SGLang token、logprob、文本和状态处理逻辑。
  • 保留 consistent-hashing/sticky routing header。
  • Native 多模态 payload 增加 batch-size-one 外层维度,避免多图 prompt 在 n>1 时被误判为多个 batch entry。
  • Native 开启时自动加入 --group-rm
  • Native 开启时 router policy 默认使用 round_robin;可以通过 SGLANG_ROUTER_POLICY 显式覆盖。
  • Native 关闭时不自动加入 --group-rm,保持训练脚本原有默认行为。
  • 增加 CPU 异步与接口契约测试,覆盖完整 native 请求路径和 launcher 激活规则。

兼容与回退条件

以下情况不会使用 native group sampling:

  • Evaluation
  • Group 少于 2 条 sample
  • 未启用 group RM
  • Partial 或续跑 rollout
  • 确定性推理
  • OPD 或 routing replay
  • Slime router
  • LoRA adapter mode
  • Custom generate function
  • Group 内 prompt 不同,或多模态输入不是同一个对象
  • Sample 已有生成 token、response 或 loss mask
  • Sample 状态不兼容

这些情况继续使用现有逐 sample 路径。

启动脚本激活规则

训练脚本现在使用以下规则:

SGLANG_NATIVE_GROUP_SAMPLING != 1
  -> 不增加 --group-rm
  -> 仅当用户显式设置 SGLANG_ROUTER_POLICY 时覆盖路由策略

SGLANG_NATIVE_GROUP_SAMPLING = 1
  -> 增加 --group-rm
  -> 增加 --sglang-native-group-sampling
  -> 默认增加 --sglang-router-policy round_robin
  -> SGLANG_ROUTER_POLICY 可覆盖 round_robin

之所以默认使用 round-robin,是因为 native 会把每个 rollout 的 HTTP 请求数从约 512 降至 64。请求数变少、单请求 decode 工作量变大后,cache-aware 路由更容易产生长尾负载失衡。

Cache-aware 问题的发现与修复过程

本 PR 的路由默认值来自实际 A/B 排查,不是预先假设。

第一阶段:验证 native 机制是否命中

Baseline 四轮共发送 2048 个 /generate 请求,即每轮约 512 个独立 sample 请求。开启 native n=8 后,四轮请求数降到 256,即每轮约 64 个 prompt group 请求。

请求数严格接近 512 -> 64,同时每轮仍然产出 64 groups / 512 samples、零 abort,说明 native 合并和输出回填都已生效。

第二阶段:发现 cache-aware 下性能反而下降

最初只开启 native,保持默认/cache-aware 路由。稳态结果为:

  • 平均生成时间:76.7 -> 82.7 秒,增加 7.8%;
  • Response-token throughput:9,678.52 -> 8,848.82 token/s,下降 8.6%;
  • 平均 response length:1449.26 -> 1428.72,只下降约 1.4%,不足以解释性能回退。

这说明请求合并已经成功,但系统出现了新的瓶颈。

进一步统计 SGLang worker 访问日志:

baseline:     218, 236, 249, 253, 260, 263, 271, 298  (CV ≈ 8.7%)
native/cache:  24,  24,  26,  27,  29,  29,  30,  67  (CV ≈ 41.9%)

Native/cache-aware 中有一个 engine 收到 67 个完整 group,而最少的 engine 只有 24 个。一个 native group 又包含 8 条 decode,因此这种请求数倾斜会被放大为明显的 decode 长尾。

第三阶段:为什么转向 round-robin

Native n=8 已经在单个请求内部完成公共 prefix 复用,各 group 之间又是不同的 prompt/image,所以跨请求 cache affinity 不再是主要收益来源。此时更重要的是把 64 个粗粒度 decode group 均匀铺到 8 个 engine。

从实验现象推断,cache-aware 的请求级 cache/load 信号没有充分计入一个 n=8 请求背后的 8 条 decode 工作量;而 round-robin 不依赖这个估计,可以直接保证 group 数量均匀。

因此先采用最小改动:保持 native n=8 不变,只把路由切到 round-robin。如果仍然存在严重的 group 内长度长尾,再考虑拆成两个 n=4 请求,在 prefix 复用和调度粒度之间折中。本轮 round-robin 已经解决主要问题,无需拆分 n=4

切换后的四轮请求分布为 31–33,CV 降至约 1.6%。稳态 response-token throughput 相对 baseline 提升 14.3%,生成墙钟时间降低 12.6%。

自动化测试

Native 专项测试:

24 passed

相邻 rollout 和 argument 回归:

64 passed, 2 deselected

两个 deselected 是当前沙箱中会阻塞的既有 Deepeyes permit 集成测试,不涉及本改动。

以下静态检查全部通过:

  • Ruff check
  • Ruff format check
  • py_compile
  • 训练脚本 bash -n
  • git diff --check

完整命令、验收矩阵和实验日志见 /root/paddlejob/workspace/env_run/research/TASK24_PR_TEST.md

8-GPU A/B 实验

实验配置:

  • 模型:Qwen3-VL-4B-Instruct
  • 数据集:multimodal-open-r1-8k-verified
  • 硬件:8 × H800,actor/rollout colocate
  • 每轮:64 prompts × 8 samples
  • 每个方案运行 4 个 rollout,稳态统计排除 rollout 0
  • 实验中的 baseline 与 native 均启用 --group-rm,避免 reward 调用时机成为额外变量
方案 平均生成时间 平均 response length Response 吞吐 平均 step time
Baseline 76.7 s 1449.26 9,678.52 token/s 160.19 s
Native,默认路由 82.7 s 1428.72 8,848.82 token/s 169.41 s
Native,round-robin 67.0 s 1447.90 11,064.55 token/s 152.51 s

Native + round-robin 相对 baseline:

  • Response-token throughput:+14.3%
  • 生成墙钟时间:-12.6%
  • 端到端 step time:-4.8%
  • 平均 response length 差异:-0.094%
  • Abort:0

请求负载方面,默认路由的每卡请求数范围为 24–67、CV 约 41.9%;round-robin 后收敛到 31–33、CV 约 1.6%。该结果是启动脚本把 round-robin 作为 native 默认策略的直接依据。

风险与限制

  • 本功能依赖 SGLang 非流式 parallel-sampling 返回契约;代码会严格验证响应形状,出现不匹配时直接失败,不静默吞错。
  • 当前没有直接记录 native/fallback group 数量,机制是否命中通过 worker 请求日志确认。
  • Group 共享请求时间会写入每条 sample,不能把 per-sample timing mean 当作请求总成本求和。
  • 多图 payload 修复是在性能实验后加入的;已通过 SGLang 真实 request normalizer 的 CPU 契约测试,但尚未单独跑多图 GPU 实验。
  • 工作树中的 length-filter cache 改动与本 PR 无关,不应包含在本 PR 中。

检查清单

  • 功能默认关闭。
  • Native 关闭时保持 launcher 原默认行为。
  • Native 开启时自动启用 group RM 与均衡路由。
  • Router policy 允许显式覆盖。
  • 保留现有 fallback 路径。
  • 严格校验输出数量。
  • 单测覆盖 native 请求和 launcher 契约。
  • 真实 8-GPU smoke 与 A/B 成功。
  • 性能结果按 response token 归一化,并报告 response length。

Copilot AI lite review requested due to automatic review settings August 13, 2026 09:23

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds an opt-in rollout path that uses SGLang’s native parallel sampling (sampling_params.n = group_size) to generate an entire prompt group via a single /generate request, improving multimodal prefix reuse and reducing request overhead while keeping the existing per-sample fallback behavior.

Changes:

  • Add --sglang-native-group-sampling CLI flag and launcher wiring to couple native sampling with --group-rm and a balanced default router policy.
  • Implement native group sampling eligibility checks and the single-request generation/mapping path in sglang_rollout.py, reusing existing token/logprob/status handling.
  • Add unit tests covering eligibility, payload shaping (including multimodal batch-of-one), output contract validation, routing key behavior, and launcher activation rules.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
relax/engine/rollout/sglang_rollout.py Adds native group-sampling request path, eligibility gating, and refactors shared token/logprob application logic.
relax/utils/arguments.py Introduces the --sglang-native-group-sampling rollout CLI flag (default off).
scripts/training/multimodal/run-qwen3-vl-4B-8xgpu.sh Enables env-driven activation that also sets --group-rm and defaults router policy to round_robin for native mode.
tests/engine/rollout/test_sglang_native_group_sampling.py Adds contract and behavior tests for native group sampling and launcher rules.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread relax/utils/arguments.py
Comment on lines +712 to +720
parser.add_argument(
"--sglang-native-group-sampling",
action="store_true",
default=False,
help=(
"Use one SGLang /generate request with sampling_params.n equal to the prompt group size "
"for standard first-turn group-RM rollouts."
),
)
Comment thread relax/engine/rollout/sglang_rollout.py Outdated
Comment on lines +829 to +832
if _native_group_sampling_eligible(args, group, evaluation):
group = await _generate_native_group(args, state, group, sampling_params)
else:
tasks = []
@xiaoliang0601

Copy link
Copy Markdown
Contributor

补充下正确性测试吧,跑 baseline 和 experiment,看看那些指标(loss、grad norm、reward 等等)是否正确。

@xiaoliang0601

Copy link
Copy Markdown
Contributor

别的我没啥问题了。

@overloadedHenry

Copy link
Copy Markdown
Contributor Author

@xiaoliang0601
first-50-steps

我将默认的multimodal reasoning 数据清洗后放大分辨率,以证明该方案在 prefix 阶段的优势。此方案在多图大分辨率 OCR-VLM 训练中具有一定的优势。
由于图像 token 过多,训练稍微有些缓慢。后续会补上更多 step 的数据。目前训练相对稳定。

如代码需要修改,或 CLI 参数需要对齐沟通,请告诉我。

# 🐛 Bug Fix

## Prevent pre-encoded media leak into sample dicts

- Wrap the group generate branch in try/finally so the temporary
  pre-encoded media attributes are always dropped before samples reach
  Sample.to_dict(), covering the per-sample fallback, abort and error
  paths alike
- Previously the base64 media blobs could leak into the data buffer
  via __dict__ on early returns

## Keep aborted groups eligible for the native path

- Revert tokens, rollout_tokens and multimodal_train_inputs to their
  pre-call state when the request is aborted before any output, so
  retried groups no longer silently degrade to per-sample requests

## Exclude speculative decoding from native eligibility

- The draft/verify path is not validated with parallel sampling, so
  keep the per-sample fanout when a speculative algorithm is set

## Add visibility for the native/fallback decision

- Log once when native group sampling activates and warn once when the
  flag is enabled but a training group is ineligible
- Warn at startup when flag combinations make every group ineligible

---

# ♻️ Refactor

## Remove dead code and document semantics

- Drop the unreachable routed_experts branch: the native payload never
  requests it and routing replay is excluded by eligibility
- Document group-shared timing duplication and session-permit
  concurrency semantics

---

# ✅ Tests

## Cover abort, cleanup and eligibility edge cases

- Mid-flight abort maps n outputs to aborted samples with partial
  tokens retained
- Abort before dispatch restores a fresh, still-eligible group
- Pre-encoded media is stripped on success and on fallback failure
- Speculative decoding joins the incompatible-modes matrix
- Ineligible-group warning fires only once
Copilot AI review requested due to automatic review settings August 14, 2026 13:39

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@overloadedHenry

Copy link
Copy Markdown
Contributor Author
step-105

由于rollout 引擎卡死导致 baseline 实验重启,重启后baseline 模型行为在前 50step 迅速坍缩,仅供参考,如时间充裕会重新再测。

Copilot AI review requested due to automatic review settings August 16, 2026 17:47

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@xiaoliang0601

Copy link
Copy Markdown
Contributor

第一张图片看起来是正常的;第二张图片的 baseline 曲线看起来有很大问题,后续有再测吗?

@xiaoliang0601

Copy link
Copy Markdown
Contributor

以及你的图片只展示了 reward,clearml 或者日志里面应该也有 输出数量、response token length、rollout_log_probs length、finish_reason、reward、advantage、loss、grad norm 这些指标的对比,也可以一并贴近来。

@overloadedHenry

Copy link
Copy Markdown
Contributor Author

第一张图片看起来正常的;第二张图片的基线曲线看起来有很大问题,后续还有再测吗?

第二轮测试还在进行,目前到 50step 了,这次没有快速坍缩,100step 的时候我更新到这里。

@overloadedHenry

Copy link
Copy Markdown
Contributor Author

还有你的图片只展示了奖励,clearml或者日志里面也应该有输出数量、响应token长度、rollout_log_probs长度、finish_reason、reward、advantage、loss、gradnorm这些指标的对比,也可以一并贴近来。

好的

@overloadedHenry

Copy link
Copy Markdown
Contributor Author
all150_native_vs_restarted_baseline_sglang_pressure all150_native_vs_restarted_baseline_training_signals_simple

此处依旧是旧实验,在进行三轮新实验的过程中,训练会被偶发 SGLang 502 Bad Gateway 干扰重启,于是没有新的完整曲线(新的训练在 30step 和 60step 的时候分别遇到 BUG 重启)。 #282 此处为 BUG 描述和初步解决方案,确认后可提 PR 修复。

@xiaoliang0601

Copy link
Copy Markdown
Contributor

那要不你补充下第一次测试的这些正确性曲线?

@overloadedHenry

Copy link
Copy Markdown
Contributor Author

那要不你补充下第一次测试的这些正确性曲线?

all_runs_over20_training_and_sglang

这些均为我跑过的实验,供参考,曲线的图例已经标明。其中 完整的baseline 只成功跑了一次,其余baseline 的训练均由我上述提到的issue中的问题被迫终止。若此 issue 确认为BUG 且初步解决方案被认同,我可以在后续的开发中fix。

@xiaoliang0601

Copy link
Copy Markdown
Contributor

可以,我觉得先验收通过了;你可以先把 bug 提新的 社区 PR 修了,然后再跑吧……

# 🐛 Bug Fix

## Guard shared multimodal encoding

- Skip group-level pre-encoding when media payloads are empty
- Preserve text-only rollout behavior for empty multimodal mappings

---

# ✅ Tests

## Cover empty media groups

- Verify empty multimodal payloads bypass the encoder
- Confirm reward assignment continues through the fallback path
Copilot AI review requested due to automatic review settings August 18, 2026 12:53
@overloadedHenry
overloadedHenry force-pushed the feat/sglang-native-group-sampling branch from ffbaa3f to 4984fb3 Compare August 18, 2026 12:53

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@overloadedHenry

Copy link
Copy Markdown
Contributor Author
all_runs_over20_training_and_sglang_v2 这是目前跑的对比。其中 Native 和 baseline 都有坍缩的现象,但坍缩的 Native 由于 OOM 没有训完。坍缩的 Native 表现和 坍缩的baseline 一致(完成的 step 内)。同时未坍缩的 Baseline 和未坍缩的 Native表现也几乎一致(完成的 step 内)。后面有需要可以多次实验去收集完整的坍缩的 Native 版本的实验。

@yuanlehome

Copy link
Copy Markdown
Contributor

感谢提交和详细的测试。合入的主要顾虑:1)native 路径依赖 SGLang n>1 group sampling,官方对多模态/投机解码等组合支持有限,导致 fallback 分支多、维护成本高;2)收益场景(多模长输入重复计算)后续计划通过 EPD 解决。建议先不合入。不影响此前验收结论哈~

@overloadedHenry

Copy link
Copy Markdown
Contributor Author

感谢提交和详细的测试。合入的主要顾虑:1)native 路径依赖 SGLang n>1 group sampling,官方对多模态/投机解码等组合支持有限,导致 fallback 分支多、维护成本高;2)收益场景(多模长输入重复计算)后续计划通过 EPD 解决。建议先不合入。不影响此前验收结论哈~

感谢团队的 review。后面有机会继续向团队学习,贡献高质量代码。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

【Task.24】使用 SGLang 原生组采样复用多模态公共前缀

4 participants