Skip to content

refactor(rollout): align vllm_rollout.py with slime sglang_rollout.py#178

Merged
CalvinXKY merged 4 commits into
mainfrom
refactor/vllm-rollout-mirror-sglang
Jun 8, 2026
Merged

refactor(rollout): align vllm_rollout.py with slime sglang_rollout.py#178
CalvinXKY merged 4 commits into
mainfrom
refactor/vllm-rollout-mirror-sglang

Conversation

@aoshen02

@aoshen02 aoshen02 commented Jun 8, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Mirror slime sglang_rollout.py structure in vllm_rollout.py (984→756 lines, 29→16 top-level defs)
  • Remove 13 vime-only helper functions not present in slime — inlined where needed, removed where over-defensive
  • Add get_rollout_num_engines() in http_utils.py to mirror slime (replaces inline rollout_num_gpus // rollout_num_gpus_per_engine)
  • Add no_stop_trim → include_stop_str_in_output mapping in _build_inference_sampling_params (subsumes PR fix(vllm-rollout): map no_stop_trim to vLLM include_stop_str_in_output #71)
  • Remove no-op return_routed_experts from request payload (vLLM is engine-level --enable-return-routed-experts, not per-request like SGLang)
  • Remove dead output_token_logprobs construction (update_from_meta_info never reads it)

Removed functions (no slime equivalent)

Function Lines Reason
_base_dataset_prompt_ids 13 vime-only partial-continuation budget; slime does not have it
_apply_vllm_routed_experts 41→7 Collapsed to inline block mirroring slime
_inference_generate_tokens_and_logprobs 30→6 Simplified inline
_align_engine_tokens_and_logprobs 13 Over-defensive padding
_warn_if_sampling_filters_may_affect_logprobs 9 Noise warning
_vllm_meta_from_generate_choice 18 Inlined into generate()
_decode_vllm_routed_experts 3 Inlined
_router_worker_urls 9 Inlined into abort()
_resume_vllm_workers 10 Inlined into abort()

Kept (vLLM-specific, no sglang equivalent)

  • _coerce_flat_int_token_ids — vLLM /inference/v1/generate requires flat list[int]
  • _build_inference_sampling_params — sglang to vllm param translation
  • _mm_render_response_to_generate_body + _find_token_subsequence + _align_mm_feature_placeholders_to_tokens — vLLM MM uses render+generate two-step (vs sglang direct image_data)

sglang to vllm API divergences (intentionally NOT mirrored)

  • return_routed_experts: SGLang supports per-request, vLLM is engine-level — removed from payload

  • routed_experts location: SGLang returns in output meta_info, vLLM returns in output choices[0]

  • abort: SGLang uses POST /abort_request, vLLM uses POST /pause?mode=abort + POST /resume

  • multimodal: SGLang sends image_data directly, vLLM needs render+generate two-step

    ┌────────────────────────┬────────┬────────┬──────┐
    │ metrics │ without R3 │ R3 │ diff │
    ├────────────────────────┼────────┼────────┼──────┤
    │ eval/aime24 │ 63.3% │ 63.3% │ same │
    ├────────────────────────┼────────┼────────┼──────┤
    │ logprob_abs_diff step0 │ 0.0214 │ 0.0135 │ -37% │
    ├────────────────────────┼────────┼────────┼──────┤
    │ logprob_abs_diff step1 │ 0.0184 │ 0.0114 │ -38% │
    ├────────────────────────┼────────┼────────┼──────┤
    │ logprob_abs_diff step2 │ 0.0160 │ 0.0097 │ -39% │
    └────────────────────────┴────────┴────────┴──────┘

Mirror slime's sglang_rollout.py structure for vllm_rollout.py (984→765
lines, 29→16 defs).

Removed vime-only bloat with no slime equivalent:
- _base_dataset_prompt_ids, _apply_vllm_routed_experts (40→7 lines inline),
  _inference_generate_tokens_and_logprobs (30→6 lines inline),
  _align_engine_tokens_and_logprobs, _warn_if_sampling_filters_may_affect_logprobs,
  _vllm_meta_from_generate_choice, _decode_vllm_routed_experts,
  _router_worker_urls, _resume_vllm_workers — all inlined or removed
- Stripped verbose docstrings/comments not present in slime

Added slime-aligned utilities:
- get_rollout_num_engines() in http_utils.py (mirrors slime, replaces
  inline rollout_num_gpus // rollout_num_gpus_per_engine)
- no_stop_trim → include_stop_str_in_output mapping in
  _build_inference_sampling_params (PR #71 fix)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

@gemini-code-assist gemini-code-assist Bot 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.

Code Review

This pull request refactors the vLLM rollout logic by removing several helper functions, inlining token and logprob parsing, and introducing a helper to determine the number of HTTP engines. However, several issues were identified: the .reshape(...) call on rollout_routed_experts is redundant and risks throwing an error if MoE arguments are missing; removing the list-handling check in generate_and_rm breaks its public API and will cause crashes when a list of samples is passed; and using strict=True in zip without the previous alignment helper risks runtime failures on mismatched token and logprob lengths.

Comment on lines +382 to +386
sample.rollout_routed_experts = np.ascontiguousarray(arr.astype(np.int32, copy=True)).reshape(
len(sample.tokens) - 1,
args.num_layers,
args.moe_router_topk,
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

The .reshape(...) call is redundant because np.load already restores the original shape of the array from the serialized .npy format. Furthermore, accessing args.num_layers and args.moe_router_topk directly is risky as they may not be defined on args (or may be None), which would cause a runtime AttributeError or TypeError if a user runs a dense model or doesn't pass these arguments.

We should remove the .reshape(...) call to keep it simple and safe.

        sample.rollout_routed_experts = np.ascontiguousarray(arr.astype(np.int32, copy=True))

Comment on lines 420 to 422
# mask previous off-policy generation for partial rollout
if args.partial_rollout and args.mask_offpolicy_in_partial_rollout and sample.response_length > 0:
sample.loss_mask = [0] * sample.response_length

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

The removal of the initial isinstance(sample, list) check breaks the public API of generate_and_rm, which is typed to accept Sample | list[Sample]. If a list of samples is passed directly to this function, it will now immediately crash with an AttributeError when trying to access sample.response_length on line 421.

We should restore the recursive list handling at the beginning of the function to maintain backward compatibility and adhere to the function's type signature.

    if isinstance(sample, list):
        return await asyncio.gather(
            *[generate_and_rm(args, s, sampling_params, evaluation=evaluation) for s in sample]
        )

    # mask previous off-policy generation for partial rollout
    if args.partial_rollout and args.mask_offpolicy_in_partial_rollout and sample.response_length > 0:
        sample.loss_mask = [0] * sample.response_length

Comment thread vime/rollout/vllm_rollout.py Outdated
Comment on lines +403 to +406
if new_response_tokens:
meta["output_token_logprobs"] = [
[float(lp_val), int(tid)] for lp_val, tid in zip(new_response_log_probs, new_response_tokens, strict=True)
]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Using strict=True in zip is risky here because the alignment helper _align_engine_tokens_and_logprobs was removed. If there is any discrepancy in the lengths of new_response_log_probs and new_response_tokens returned by the vLLM API, this will raise a ValueError and crash the rollout process.

To make the code more robust against API discrepancies, we should avoid strict=True or handle length mismatches gracefully.

Suggested change
if new_response_tokens:
meta["output_token_logprobs"] = [
[float(lp_val), int(tid)] for lp_val, tid in zip(new_response_log_probs, new_response_tokens, strict=True)
]
if new_response_tokens:
meta["output_token_logprobs"] = [
[float(lp_val), int(tid)] for lp_val, tid in zip(new_response_log_probs, new_response_tokens)
]

aoshen02 and others added 3 commits June 8, 2026 00:47
vLLM's routed_experts is engine-level (--enable-return-routed-experts),
not request-level. The field is ignored by /inference/v1/generate.
SGLang's /generate does support it per-request, so this is a correct
sglang→vllm translation divergence, not a mirror gap.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
update_from_meta_info only reads finish_reason, spec_info,
prefix_cache_info, and weight_version — output_token_logprobs is never
consumed. Slime's sglang_rollout doesn't construct it either (SGLang
returns it natively but update_from_meta_info ignores it there too).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Comment thread vime/rollout/vllm_rollout.py
Comment thread vime/rollout/vllm_rollout.py

@CalvinXKY CalvinXKY left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM

@CalvinXKY
CalvinXKY merged commit ae5d6b0 into main Jun 8, 2026
11 of 14 checks passed
aoshen02 added a commit that referenced this pull request Jun 8, 2026
The `e2e-test-unit` CI job runs `pytest tests/unit tests/utils` CPU-only
(`# CPU/mock only -> no --gpus`), but the suite did not pass there:
collection aborted and 38 tests failed.

Causes and fixes (test-only; no source changes):

- Stale helper tests (28): PR #178 rewrote vllm_rollout.py to mirror slime's
  sglang_rollout.py, inlining/removing the granular `_apply_vllm_routed_experts`,
  `_decode_vllm_routed_experts`, `_inference_generate_tokens_and_logprobs`,
  `_align_engine_tokens_and_logprobs`, `_vllm_meta_from_generate_choice`,
  `_router_worker_urls`, `_resume_vllm_workers`, `_base_dataset_prompt_ids`,
  `_warn_if_sampling_filters_may_affect_logprobs` helpers and the in-generate
  partial-continuation budget. Those tests called the now-removed private
  functions; the behavior is already covered by the surviving `test_generate_*`
  integration tests. Removed the obsolete tests.

- Device probe (4): `get_vllm_cli_action_table()` rebuilds via
  `AsyncEngineArgs.add_cli_args`, which probes the accelerator and raises
  `RuntimeError: Failed to infer device type` on a GPU-less host. Added an
  autouse fixture in test_vllm_engine.py that seeds the (already-built)
  action-table cache so the rebuild is skipped, and mocked
  `torch.cuda.current_device` in the update_weight connect test.

- Import-time sys.modules pollution: the update_weight tests ran
  `_install_stubs()` at module top level, leaving a fake `vllm` (no `.engine`)
  in sys.modules that broke COLLECTION of test_vllm_engine.py. Moved stub
  installation into the module-scoped fixtures with save/restore. The
  `ray` / `vllm_router` conftest stubs now defer to the real package via
  `importlib.util.find_spec`, so a bare non-package stub no longer leaks
  session-wide into sibling test packages (tests/utils).

Result: `pytest tests/unit tests/utils` = 162 passed, 0 failed on CPU.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
CalvinXKY pushed a commit that referenced this pull request Jun 8, 2026
…181)

The `e2e-test-unit` CI job runs `pytest tests/unit tests/utils` CPU-only
(`# CPU/mock only -> no --gpus`), but the suite did not pass there:
collection aborted and 38 tests failed.

Causes and fixes (test-only; no source changes):

- Stale helper tests (28): PR #178 rewrote vllm_rollout.py to mirror slime's
  sglang_rollout.py, inlining/removing the granular `_apply_vllm_routed_experts`,
  `_decode_vllm_routed_experts`, `_inference_generate_tokens_and_logprobs`,
  `_align_engine_tokens_and_logprobs`, `_vllm_meta_from_generate_choice`,
  `_router_worker_urls`, `_resume_vllm_workers`, `_base_dataset_prompt_ids`,
  `_warn_if_sampling_filters_may_affect_logprobs` helpers and the in-generate
  partial-continuation budget. Those tests called the now-removed private
  functions; the behavior is already covered by the surviving `test_generate_*`
  integration tests. Removed the obsolete tests.

- Device probe (4): `get_vllm_cli_action_table()` rebuilds via
  `AsyncEngineArgs.add_cli_args`, which probes the accelerator and raises
  `RuntimeError: Failed to infer device type` on a GPU-less host. Added an
  autouse fixture in test_vllm_engine.py that seeds the (already-built)
  action-table cache so the rebuild is skipped, and mocked
  `torch.cuda.current_device` in the update_weight connect test.

- Import-time sys.modules pollution: the update_weight tests ran
  `_install_stubs()` at module top level, leaving a fake `vllm` (no `.engine`)
  in sys.modules that broke COLLECTION of test_vllm_engine.py. Moved stub
  installation into the module-scoped fixtures with save/restore. The
  `ray` / `vllm_router` conftest stubs now defer to the real package via
  `importlib.util.find_spec`, so a bare non-package stub no longer leaks
  session-wide into sibling test packages (tests/utils).

Result: `pytest tests/unit tests/utils` = 162 passed, 0 failed on CPU.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
CalvinXKY pushed a commit that referenced this pull request Jun 8, 2026
…ce (#183)

* fix(vllm_rollout): guard routed_experts value, not just key presence

PR #178 mirrored slime's `if "routed_experts" in choice:` parse guard, but the
vLLM engine's response shape differs from sglang's: sglang OMITS the key when
routed experts are not requested (slime only sets `return_routed_experts=True`
under `--use-rollout-routing-replay`), whereas the vLLM engine INCLUDES
`routed_experts: null` in every choice. The bare `in` check therefore passes
with a None value, and `choice["routed_experts"].encode("ascii")` raises
`AttributeError: 'NoneType' object has no attribute 'encode'`, crashing
`RolloutManager.generate()` for every MoE-model rollout that is not using
rollout-routing-replay (e.g. test_qwen3.5_0.8B_gsm8k_short / _async_short).

Fix: gate on the value with `choice.get("routed_experts") is not None`, which
handles both key-absent and key-present-but-null and matches slime's effective
behavior.

Found by running the CI suite on `inferactinc/public:vime-latest` @ main:
`ray::RolloutManager.generate()` ... vime/rollout/vllm_rollout.py:377
`AttributeError: 'NoneType' object has no attribute 'encode'`.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Update vllm_rollout.py

Signed-off-by: aoshen02 <aoshen@inferact.ai>

---------

Signed-off-by: aoshen02 <aoshen@inferact.ai>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
CalvinXKY pushed a commit that referenced this pull request Jun 8, 2026
…rtError) (#184)

#178 ("align vllm_rollout.py with slime sglang_rollout.py") removed the private
helpers `_align_engine_tokens_and_logprobs`, `_apply_vllm_routed_experts`,
`_base_dataset_prompt_ids`, `_inference_generate_tokens_and_logprobs`, and
`_vllm_meta_from_generate_choice` from `vime.rollout.vllm_rollout`, inlining
their logic into `generate()`. But `vllm_streaming_rollout.py` still imported
all five, so `mg_qwen3_4B_streaming` crashed at rollout time with:

    ImportError: cannot import name '_align_engine_tokens_and_logprobs'
                 from 'vime.rollout.vllm_rollout'

Mirror slime here: slime's `sglang_streaming_rollout` imports only
`GenerateState` + `_prepare_prompt_ids` and inlines the finalize. This change
does the same for vLLM — parse delta token_ids/logprobs, align logprobs to
tokens, build meta from the terminal choice+usage, and decode routed_experts —
all inlined exactly as the non-streaming `generate()` does (incl. the #183
`routed_experts is not None` guard). The one vLLM-specific bit, the partial-
continuation budget helper `_base_dataset_prompt_ids`, is kept local to this
file since it has no slime/`vllm_rollout` counterpart.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@aoshen02
aoshen02 deleted the refactor/vllm-rollout-mirror-sglang branch June 8, 2026 14:17
momo609 pushed a commit that referenced this pull request Jun 10, 2026
…181)

The `e2e-test-unit` CI job runs `pytest tests/unit tests/utils` CPU-only
(`# CPU/mock only -> no --gpus`), but the suite did not pass there:
collection aborted and 38 tests failed.

Causes and fixes (test-only; no source changes):

- Stale helper tests (28): PR #178 rewrote vllm_rollout.py to mirror slime's
  sglang_rollout.py, inlining/removing the granular `_apply_vllm_routed_experts`,
  `_decode_vllm_routed_experts`, `_inference_generate_tokens_and_logprobs`,
  `_align_engine_tokens_and_logprobs`, `_vllm_meta_from_generate_choice`,
  `_router_worker_urls`, `_resume_vllm_workers`, `_base_dataset_prompt_ids`,
  `_warn_if_sampling_filters_may_affect_logprobs` helpers and the in-generate
  partial-continuation budget. Those tests called the now-removed private
  functions; the behavior is already covered by the surviving `test_generate_*`
  integration tests. Removed the obsolete tests.

- Device probe (4): `get_vllm_cli_action_table()` rebuilds via
  `AsyncEngineArgs.add_cli_args`, which probes the accelerator and raises
  `RuntimeError: Failed to infer device type` on a GPU-less host. Added an
  autouse fixture in test_vllm_engine.py that seeds the (already-built)
  action-table cache so the rebuild is skipped, and mocked
  `torch.cuda.current_device` in the update_weight connect test.

- Import-time sys.modules pollution: the update_weight tests ran
  `_install_stubs()` at module top level, leaving a fake `vllm` (no `.engine`)
  in sys.modules that broke COLLECTION of test_vllm_engine.py. Moved stub
  installation into the module-scoped fixtures with save/restore. The
  `ray` / `vllm_router` conftest stubs now defer to the real package via
  `importlib.util.find_spec`, so a bare non-package stub no longer leaks
  session-wide into sibling test packages (tests/utils).

Result: `pytest tests/unit tests/utils` = 162 passed, 0 failed on CPU.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
momo609 pushed a commit that referenced this pull request Jun 10, 2026
…ce (#183)

* fix(vllm_rollout): guard routed_experts value, not just key presence

PR #178 mirrored slime's `if "routed_experts" in choice:` parse guard, but the
vLLM engine's response shape differs from sglang's: sglang OMITS the key when
routed experts are not requested (slime only sets `return_routed_experts=True`
under `--use-rollout-routing-replay`), whereas the vLLM engine INCLUDES
`routed_experts: null` in every choice. The bare `in` check therefore passes
with a None value, and `choice["routed_experts"].encode("ascii")` raises
`AttributeError: 'NoneType' object has no attribute 'encode'`, crashing
`RolloutManager.generate()` for every MoE-model rollout that is not using
rollout-routing-replay (e.g. test_qwen3.5_0.8B_gsm8k_short / _async_short).

Fix: gate on the value with `choice.get("routed_experts") is not None`, which
handles both key-absent and key-present-but-null and matches slime's effective
behavior.

Found by running the CI suite on `inferactinc/public:vime-latest` @ main:
`ray::RolloutManager.generate()` ... vime/rollout/vllm_rollout.py:377
`AttributeError: 'NoneType' object has no attribute 'encode'`.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Update vllm_rollout.py

Signed-off-by: aoshen02 <aoshen@inferact.ai>

---------

Signed-off-by: aoshen02 <aoshen@inferact.ai>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
momo609 pushed a commit that referenced this pull request Jun 10, 2026
…rtError) (#184)

#178 ("align vllm_rollout.py with slime sglang_rollout.py") removed the private
helpers `_align_engine_tokens_and_logprobs`, `_apply_vllm_routed_experts`,
`_base_dataset_prompt_ids`, `_inference_generate_tokens_and_logprobs`, and
`_vllm_meta_from_generate_choice` from `vime.rollout.vllm_rollout`, inlining
their logic into `generate()`. But `vllm_streaming_rollout.py` still imported
all five, so `mg_qwen3_4B_streaming` crashed at rollout time with:

    ImportError: cannot import name '_align_engine_tokens_and_logprobs'
                 from 'vime.rollout.vllm_rollout'

Mirror slime here: slime's `sglang_streaming_rollout` imports only
`GenerateState` + `_prepare_prompt_ids` and inlines the finalize. This change
does the same for vLLM — parse delta token_ids/logprobs, align logprobs to
tokens, build meta from the terminal choice+usage, and decode routed_experts —
all inlined exactly as the non-streaming `generate()` does (incl. the #183
`routed_experts is not None` guard). The one vLLM-specific bit, the partial-
continuation budget helper `_base_dataset_prompt_ids`, is kept local to this
file since it has no slime/`vllm_rollout` counterpart.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.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.

2 participants