Skip to content

fix(token-capture): use next-turn prefill routes at R3 turn boundaries - #4579

Closed
pjin-nvidia wants to merge 1 commit into
mainfrom
pjin/routing-replay-v2-fix-20260928
Closed

pjin-nvidia wants to merge 1 commit into
mainfrom
pjin/routing-replay-v2-fix-20260928

Conversation

@pjin-nvidia

@pjin-nvidia pjin-nvidia commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

With token capture, each turn of a multi-turn rollout is a separate vLLM call. A call never runs its last generated token back through the model, so its aligned routes carry only a placeholder for that token (arange(topk) at every layer, not the -1 missing-route sentinel), and Router Replay forced those experts in the Megatron forward passes. Every later position attends to that token, so each intermediate turn boundary was trained with a route that inference never used. The next call's prefill computes the real route. The non-TQ NeMo-Gym path already splices it in (nemo_rl/environments/nemo_gym.py); this does the same for TQ token capture, for both direct route assembly and token_capture.defer_routed_experts_to_policy.

  • Capture: a token_in call that generated tokens also returns the prefill route of the previous turn's last token (routed_experts_boundary, [1, layers, topk]) and its absolute index (routed_experts_boundary_index = prev_len - 1). The Gym adapter copies both into the call's extras, so the extras digest commits them. Token IDs, masks, logprobs, and delta lengths are unchanged.
  • Staging: TQTokenSink checks the sidecar (token_in child, index == prev_len - 1, same dtype and per-token shape as the delta routes) and stores it in a routed_experts_boundary column registered on the R3 staging partition; the index stays in the extras metadata. Only rows with a boundary write the column, and only those rows read it.
  • Assembly: route plans move to schema v3 with per-span boundary_token_index and repair_previous_token. The reassembler allows a repair only along the receipt's selected chain and only between two spans that both generated tokens, so abandoned retries and response order cannot change a parent. The repair edits the assembled tensor, never the parent's staged routes. A missing or corrupt sidecar follows the existing failure policy: direct assembly rejects the rollout; deferred assembly uses native routing for it. The trajectory's final token keeps its placeholder.
  • Compatibility: v2 plans and rows staged without a boundary remain readable; the latter keep the placeholder and log a warning. With R3 and token capture, the vLLM worker fails at startup on a Gym adapter without boundary support, data-plane checkpoints record routed_experts_boundary_schema_version, and resuming a native TQ checkpoint without it fails at startup. Staging-schema migration is not implemented: a restored Mooncake controller would register the new column lazily while producers are live.
  • docs/guides/router-replay.md describes the behavior and upgrade path.
  • Tests cover capture -> TQ -> direct/deferred replay for 1-3 turns (int8/int16 routes, all -1 boundaries), retry selection, tampered or missing sidecars, a data-plane checkpoint round trip, legacy rows, v2/v3 plan decoding, the Gym adapter gate, and checkpoint metadata/restore checks.

Bumps Gym to ad46d9f6b: main's pin 17abf2ddc plus "feat(capture): commit the vLLM turn-boundary route in call extras".

Rebased onto VLM media capture (#4191): the staging partition registers the boundary column next to the media columns, stage() derives the media checksum and the boundary index from the same metadata JSON, and fetch_for_finalization() verifies media metadata before parsing the boundary index from it.

Also see: NVIDIA-NeMo/Gym#4201

With token capture, each turn of a multi-turn rollout is a separate vLLM
call. A call never runs its last generated token back through the
model, so its aligned routes carry only a placeholder for that token
(arange(topk) at every layer, not the -1 missing-route sentinel), and
Router Replay forced those experts in the Megatron forward passes.
Every later position attends to that token, so each intermediate turn
boundary was trained with a route that inference never used. The next
call's prefill computes the real route. The non-TQ NeMo-Gym path
already splices it in (nemo_rl/environments/nemo_gym.py); this does the
same for TQ token capture, for both direct route assembly and
token_capture.defer_routed_experts_to_policy.

- Capture: a token_in call that generated tokens also returns the
  prefill route of the previous turn's last token
  (routed_experts_boundary, [1, layers, topk]) and its absolute index
  (routed_experts_boundary_index = prev_len - 1). The Gym adapter
  copies both into the call's extras, so the extras digest commits
  them. Token IDs, masks, logprobs, and delta lengths are unchanged.
- Staging: TQTokenSink checks the sidecar (token_in child, index ==
  prev_len - 1, same dtype and per-token shape as the delta routes) and
  stores it in a routed_experts_boundary column registered on the R3
  staging partition; the index stays in the extras metadata. Only rows
  with a boundary write the column, and only those rows read it.
- Assembly: route plans move to schema v3 with per-span
  boundary_token_index and repair_previous_token. The reassembler
  allows a repair only along the receipt's selected chain and only
  between two spans that both generated tokens, so abandoned retries
  and response order cannot change a parent. The repair edits the
  assembled tensor, never the parent's staged routes. A missing or
  corrupt sidecar follows the existing failure policy: direct assembly
  rejects the rollout; deferred assembly uses native routing for it.
  The trajectory's final token keeps its placeholder.
- Compatibility: v2 plans and rows staged without a boundary remain
  readable; the latter keep the placeholder and log a warning. With R3
  and token capture, the vLLM worker fails at startup on a Gym adapter
  without boundary support, data-plane checkpoints record
  routed_experts_boundary_schema_version, and resuming a native TQ
  checkpoint without it fails at startup. Staging-schema migration is
  not implemented: a restored Mooncake controller would register the
  new column lazily while producers are live.
- docs/guides/router-replay.md describes the behavior and upgrade path.
- Tests cover capture -> TQ -> direct/deferred replay for 1-3 turns
  (int8/int16 routes, all -1 boundaries), retry selection, tampered or
  missing sidecars, a data-plane checkpoint round trip, legacy rows,
  v2/v3 plan decoding, the Gym adapter gate, and checkpoint
  metadata/restore checks.

Bumps Gym to ad46d9f6b: main's pin 17abf2ddc plus "feat(capture):
commit the vLLM turn-boundary route in call extras".

Rebased onto VLM media capture (#4191): the staging partition registers
the boundary column next to the media columns, stage() derives the
media checksum and the boundary index from the same metadata JSON, and
fetch_for_finalization() verifies media metadata before parsing the
boundary index from it.

Signed-off-by: Peter Jin <pjin@nvidia.com>
@copy-pr-bot

copy-pr-bot Bot commented Oct 8, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@github-actions github-actions Bot added the Documentation Improvements or additions to documentation label Oct 8, 2026
@github-actions

github-actions Bot commented Oct 8, 2026

Copy link
Copy Markdown

✅ Submodule Fast-Forward Check Results

Check based on commit: 9dce0ff (PR #4579 from pjin/routing-replay-v2-fix-20260928)

✅ Submodules that are properly updated:

Gym: ✅ PR branch is ahead of main branch (fast-forward)

All submodule changes look good! ✨

@github-actions

github-actions Bot commented Oct 8, 2026

Copy link
Copy Markdown

✅ Submodule Fast-Forward Check Results

Check based on commit: e39475d (PR #4579 from pjin/routing-replay-v2-fix-20260928)

✅ Submodules that are properly updated:

Gym: ✅ PR branch is ahead of main branch (fast-forward)

All submodule changes look good! ✨

@pjin-nvidia

pjin-nvidia commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor Author

closing, merged #4610

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

Labels

Documentation Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant