Skip to content

feat(gym): steer the tail of a rollout step to a faster server group - #8

Open
aoshen02 wants to merge 1 commit into
integration/group-routingfrom
feat/tail-route-worker-group
Open

feat(gym): steer the tail of a rollout step to a faster server group#8
aoshen02 wants to merge 1 commit into
integration/group-routingfrom
feat/tail-route-worker-group

Conversation

@aoshen02

Copy link
Copy Markdown
Owner

base 为什么是一条集成分支

这个特性同时依赖两个仍未合并的上游 PR:

base 分支 integration/group-routing = NVIDIA-NeMo#3520 的 head + 取自 NVIDIA-NeMo#3663nemo_gym.py 单个文件
不做完整 merge,是因为 main 自 NVIDIA-NeMo#3520 分叉以来对 grpo.py 已漂移 422 行,合并等于替 NVIDIA-NeMo#3520 重做 rebase
—— 那是 NVIDIA-NeMo#3520 自己的事,不该混进这个 PR。NVIDIA-NeMo#3520 不碰 nemo_gym.py,所以直接取文件是安全的。

因此本 PR 的 diff 只包含长尾引导这一层,不重复任何已开 PR 的内容。
真正上游化需要等 NVIDIA-NeMo#3520NVIDIA-NeMo#3663 先落地。

做什么

一个 rollout step 的墙钟由最慢的那个样本决定,所以最后百分之几的样本拖着整步,
而此时绝大多数引擎已经空闲。当某一比例的样本完成后,把剩下的引导到指定的 server group
(通常是小而快的低延迟池),让高吞吐池排空。

  • tail_route_threshold(比例,不设即关闭)与 tail_route_group
  • replica 注册到 router 时带上 labels: {"group": <name>},来自生成后端的 URL→组 映射。
    没有分组的后端照旧不带标签注册
  • 给 Gym 的信号是哨兵文件(Gym 日志目录下,通过 NEMO_GYM_TAIL_ROUTE_SENTINEL 导出)。
    vllm_model 会起多个 uvicorn worker、彼此无共享内存,而翻转发生在 step 中途 ——
    文件是唯一能让它们同时观察到的东西。每步开始时移除
  • max_concurrent_rollouts 是前提而非优化。引导只能作用于尚未发出的请求;
    没有并发上限时,阈值触发的那一刻整批请求早已在途,没有任何东西可引导
  • _validate_tail_route 让配置错误在 setup 阶段失败而不是静默降级:阈值不在 (0,1]、
    router_url、没有 server groups、组名未知、或 policy_model 未配 worker_group_header

配套改动

需要同时具备:

  • vllm-project/router:x-worker-group 分组过滤(否则标签无人读)
  • NVIDIA-NeMo/Gym vllm_model:读哨兵文件并在出站请求上打该 header

测试

没有单元测试。 这个机制只在「真实 router + 多个引擎组 + 完整一步」的组合下才显现,
纯 Python 层能测的部分测不到它真正的失败模式。改为端到端验证
(9B / 3 节点 / 4 个 TP1 引擎 / 128 会话 / consistent_hash / 阈值 0.95):

翻转触发       122/128 = ceil(0.95 × 128)          精确
翻转后         高吞吐组冻结在 2428 请求
               低延迟组独自消化剩余 693 个
外溢           332 条路由决策中 1 条(0.3%),落在翻转那一秒的边界上
前缀命中率     95.7%,与不做引导的对照跑一致
HTTP 500       0
抢占           0

作为对照,同一套配置在缺少分组过滤修复的旧 router上,翻转后外溢 31.5%,
且单个 session 在两组之间来回抖动。

静态检查:ruff check 全过、ruff format 干净、pyrefly 错误数与 base 持平(122,未新增)。


AI 辅助完成。所有改动已逐行 review,验证方式与结果如上。

A rollout step finishes when its slowest sample does, so the last few percent
of samples set the step's wall clock while most of the fleet sits idle. Once
a configurable fraction of a step has finished, steer what remains to a named
server group -- typically a small, low-latency pool -- and let the throughput
pool drain.

- `tail_route_threshold` (fraction, unset disables) and `tail_route_group`.
- Replicas are now registered at the router with `labels: {"group": <name>}`,
  taken from the generation backend's URL-to-group map. Backends without
  groups register unlabelled, as before.
- The signal to Gym is a sentinel file under the Gym log dir, exported as
  NEMO_GYM_TAIL_ROUTE_SENTINEL. vllm_model runs several uvicorn workers with
  no shared memory and the flip happens mid-step, so a file is the only thing
  they all observe at once. It is removed at the start of every step.
- `max_concurrent_rollouts` caps rollouts in flight. Steering can only reach
  requests that have not been dispatched yet, so without a cap the whole batch
  is already in flight when the threshold trips and there is nothing left to
  steer. This is a prerequisite, not an optimization.
- `_validate_tail_route` fails setup rather than degrading silently: threshold
  outside (0,1], no router_url, no server groups, an unknown group name, or a
  policy_model with no worker_group_header configured.

No unit tests: the mechanism only shows up across a real router, several
engine groups and a full step. Verified end to end instead (9B, 3 nodes,
4 TP1 engines, 128 sessions, consistent_hash, threshold 0.95):

- flip fired at 122/128 = ceil(0.95 * 128)
- after the flip the throughput group froze at 2428 requests while the
  low-latency group absorbed the remaining 693
- spillover to the wrong group: 1 of 332 routing decisions (0.3%), on the
  boundary second of the flip
- prefix cache hit rate 95.7%, unchanged from a run without steering
- HTTP 500s: 0, preemptions: 0

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: aoshen02 <aoshen524@gmail.com>
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.

1 participant