Skip to content

[Sync][G] streaming rollout + retool example (slime #1921/#1861) - #147

Merged
CalvinXKY merged 3 commits into
sync/slime-mega-Afrom
sync/slime-mega-G
Jun 7, 2026
Merged

[Sync][G] streaming rollout + retool example (slime #1921/#1861)#147
CalvinXKY merged 3 commits into
sync/slime-mega-Afrom
sync/slime-mega-G

Conversation

@aoshen02

@aoshen02 aoshen02 commented Jun 4, 2026

Copy link
Copy Markdown
Collaborator

PR-G — streaming + retool examples (RFC #107). Stacked on PR-A (#145) — base is sync/slime-mega-A so the diff is G-only.

Included

  • #1921 streamingvime/rollout/vllm_streaming_rollout.py + tests/test_qwen3_4B_streaming_partial_rollout.py. Translates the sglang streaming path to vLLM's /inference/v1/generate SSE. Load-bearing translation: sglang streams cumulative output_token_logprobs (overwrite per chunk); vLLM SSE sends delta token_ids+logprobs per chunk → accumulate (+=) and re-decode accumulated tokens each chunk (multi-byte-safe), finalize via _vllm_meta_from_generate_choice. routed_experts applied only from the terminal choice. MM raises NotImplementedError (the two-call render→generate flow doesn't fit a single stream). Streaming file is the reviewed sync/slime-pr-1921 (draft [Rollout] Add streaming vLLM rollout (port slime #1921) #117) design.
  • #1861 retool → re-added examples/retool/generate_with_retool.py (vime lacked examples/retool/) from the slime base + #1861's multi-turn/retry-desync hardening (if not meta_info.get("output_token_logprobs") guard after routing through the vLLM field map).

Notes / follow-ups

  • Retool example is partial: it depends on a sibling tool_sandbox.py not yet ported (out of #1861's scope; vime never had examples/retool/). The example won't run end-to-end until that lands — flagged for a follow-up.
  • Two small correctness guards vs slime in retool (reviewed): post-loop status won't clobber a budget-driven TRUNCATED; finish_type tracked locally since vLLM responses are per-call.

CI

  • test_qwen3_4B_streaming_partial_rollout registered in the e2e-test-megatron matrix (.j2 + generated .yml). py_compile clean.

🤖 Generated with Claude Code


Fidelity ledger (audited 2026-06-04)

op detail
Ported #1861 retool multi-turn/retry hardening ; #1921 streaming example
Translated (sglang→vllm) streaming meta_info from sglang cumulative form → vLLM delta-accumulation
Dropped/Deferred none
Verified green on rebuilt FCBAGDE stack

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

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 introduces a custom tool-enabled generation script, a streaming vLLM rollout implementation to handle mid-generation aborts, and a corresponding CI smoke test. The reviewer identified two critical issues: first, a global newline replacement in the tool-call JSON parser that can break structural newlines and cause decoding errors, which can be resolved by using json.loads(..., strict=False); second, several vulnerabilities in the streaming finalization logic, including a lack of exception handling for stream disconnects, incorrect status assignment on stream failure, and potential aborted status overwrites.

Comment on lines +130 to +133
json_str = tool_call_match.group(1)
# Replace newlines in string values with \n
json_str = json_str.replace("\n", "\\n")
tool_call_data = json.loads(json_str)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

Replacing all newlines (\n) with escaped newlines (\\n) globally on the JSON string breaks structural newlines in formatted JSON, leading to JSONDecodeError during json.loads. Use json.loads(json_str, strict=False) instead, which is the standard way to allow raw control characters (such as newlines) inside JSON string values without breaking structural whitespace.

Suggested change
json_str = tool_call_match.group(1)
# Replace newlines in string values with \n
json_str = json_str.replace("\n", "\\n")
tool_call_data = json.loads(json_str)
json_str = tool_call_match.group(1)
tool_call_data = json.loads(json_str, strict=False)

Comment on lines +147 to +232
with trace_span(
sample, "vllm_inference_generate_stream", attrs={"max_new_tokens": params["max_new_tokens"]}
) as span:
async with client.stream("POST", url, json=payload, headers=headers) as response:
response.raise_for_status()
async for raw_line in response.aiter_lines():
if not raw_line or not raw_line.startswith("data:"):
continue
data_str = raw_line[len("data:") :].strip()
if not data_str or data_str == "[DONE]":
continue
try:
chunk = json.loads(data_str)
except json.JSONDecodeError:
logger.warning("vllm_streaming: skipping non-JSON chunk: %r", data_str[:120])
continue

choices = chunk.get("choices") or []
if not choices:
# usage-only / keepalive chunk
if chunk.get("usage"):
last_usage = chunk["usage"]
continue
choice = choices[0]
last_choice = choice
if chunk.get("usage"):
last_usage = chunk["usage"]
if choice.get("finish_reason"):
finish_reason = choice["finish_reason"]

# Each streamed choice carries only this chunk's *delta* tokens
# (GenerateResponseStreamChoice), so accumulate.
delta_tokens, delta_log_probs = _inference_generate_tokens_and_logprobs(choice)
if delta_tokens:
call_tokens += delta_tokens
call_log_probs += delta_log_probs

# Surface partial state on the sample immediately. If the outer
# abort path cuts us, whatever we've written so far is what
# survives. Decode the *accumulated* tokens (not the per-chunk
# delta) so multi-token characters straddling a chunk boundary
# decode correctly.
sample.tokens = base_tokens + call_tokens
sample.response = base_response + (
state.tokenizer.decode(call_tokens, skip_special_tokens=skip_decode) if call_tokens else ""
)
sample.response_length = base_response_length + len(call_tokens)
sample.rollout_log_probs = base_log_probs + call_log_probs
if base_loss_mask is not None:
assert args.partial_rollout and args.mask_offpolicy_in_partial_rollout
sample.loss_mask = base_loss_mask + [1] * len(call_tokens)

if state.aborted:
break

if finish_reason and last_choice is not None:
span.update(build_vllm_meta_trace_attrs({"choices": [last_choice], "usage": last_usage}))

if finish_reason and last_choice is not None:
# Finalize exactly like the non-streaming path: align logprobs to tokens,
# rebuild meta + output_token_logprobs, then let Sample own status.
new_response_tokens, new_response_log_probs = _align_engine_tokens_and_logprobs(call_tokens, call_log_probs)

meta = _vllm_meta_from_generate_choice(args, last_choice, last_usage)
if new_response_tokens:
meta["output_token_logprobs"] = [
[float(lp), int(tid)] for lp, tid in zip(new_response_log_probs, new_response_tokens, strict=True)
]

sample.tokens = base_tokens + new_response_tokens
sample.response = base_response + (
state.tokenizer.decode(new_response_tokens, skip_special_tokens=skip_decode) if new_response_tokens else ""
)
sample.response_length = base_response_length + len(new_response_tokens)
sample.rollout_log_probs = base_log_probs + new_response_log_probs
if base_loss_mask is not None:
assert args.partial_rollout and args.mask_offpolicy_in_partial_rollout
sample.loss_mask = base_loss_mask + [1] * len(new_response_tokens)

sample.update_from_meta_info(args, meta)
# MoE routing replay (when requested) ships on the terminal choice.
_apply_vllm_routed_experts(args, sample, last_choice)
elif state.aborted:
sample.status = Sample.Status.ABORTED

return sample

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

There are three critical issues in the streaming finalization logic:

  1. Lack of Exception Handling: Any transient network error or premature stream disconnect will raise an exception and crash the entire rollout task. Wrapping the stream block in a try...except block ensures robustness.
  2. Incorrect Status on Stream Failure: If the stream fails or disconnects prematurely without being aborted, the sample status remains PENDING. It should be marked as FAILED to prevent training on incomplete data.
  3. Aborted Status Overwrite: If state.aborted is True, the sample status must be set to ABORTED even if finish_reason is set, to ensure proper recycling of the sample.
    try:
        with trace_span(
            sample, "vllm_inference_generate_stream", attrs={"max_new_tokens": params["max_new_tokens"]}
        ) as span:
            async with client.stream("POST", url, json=payload, headers=headers) as response:
                response.raise_for_status()
                async for raw_line in response.aiter_lines():
                    if not raw_line or not raw_line.startswith("data:"):
                        continue
                    data_str = raw_line[len("data:") :].strip()
                    if not data_str or data_str == "[DONE]":
                        continue
                    try:
                        chunk = json.loads(data_str)
                    except json.JSONDecodeError:
                        logger.warning("vllm_streaming: skipping non-JSON chunk: %r", data_str[:120])
                        continue

                    choices = chunk.get("choices") or []
                    if not choices:
                        # usage-only / keepalive chunk
                        if chunk.get("usage"):
                            last_usage = chunk["usage"]
                        continue
                    choice = choices[0]
                    last_choice = choice
                    if chunk.get("usage"):
                        last_usage = chunk["usage"]
                    if choice.get("finish_reason"):
                        finish_reason = choice["finish_reason"]

                    # Each streamed choice carries only this chunk's *delta* tokens
                    # (GenerateResponseStreamChoice), so accumulate.
                    delta_tokens, delta_log_probs = _inference_generate_tokens_and_logprobs(choice)
                    if delta_tokens:
                        call_tokens += delta_tokens
                        call_log_probs += delta_log_probs

                    # Surface partial state on the sample immediately. If the outer
                    # abort path cuts us, whatever we've written so far is what
                    # survives. Decode the *accumulated* tokens (not the per-chunk
                    # delta) so multi-token characters straddling a chunk boundary
                    # decode correctly.
                    sample.tokens = base_tokens + call_tokens
                    sample.response = base_response + (
                        state.tokenizer.decode(call_tokens, skip_special_tokens=skip_decode) if call_tokens else ""
                    )
                    sample.response_length = base_response_length + len(call_tokens)
                    sample.rollout_log_probs = base_log_probs + call_log_probs
                    if base_loss_mask is not None:
                        assert args.partial_rollout and args.mask_offpolicy_in_partial_rollout
                        sample.loss_mask = base_loss_mask + [1] * len(call_tokens)

                    if state.aborted:
                        break

            if finish_reason and last_choice is not None:
                span.update(build_vllm_meta_trace_attrs({"choices": [last_choice], "usage": last_usage}))
    except Exception as e:
        logger.warning("vllm_streaming: stream failed with exception: %r", e)

    if state.aborted:
        sample.status = Sample.Status.ABORTED
    elif finish_reason and last_choice is not None:
        # Finalize exactly like the non-streaming path: align logprobs to tokens,
        # rebuild meta + output_token_logprobs, then let Sample own status.
        new_response_tokens, new_response_log_probs = _align_engine_tokens_and_logprobs(call_tokens, call_log_probs)

        meta = _vllm_meta_from_generate_choice(args, last_choice, last_usage)
        if new_response_tokens:
            meta["output_token_logprobs"] = [
                [float(lp), int(tid)] for lp, tid in zip(new_response_log_probs, new_response_tokens, strict=True)
            ]

        sample.tokens = base_tokens + new_response_tokens
        sample.response = base_response + (
            state.tokenizer.decode(new_response_tokens, skip_special_tokens=skip_decode) if new_response_tokens else ""
        )
        sample.response_length = base_response_length + len(new_response_tokens)
        sample.rollout_log_probs = base_log_probs + new_response_log_probs
        if base_loss_mask is not None:
            assert args.partial_rollout and args.mask_offpolicy_in_partial_rollout
            sample.loss_mask = base_loss_mask + [1] * len(new_response_tokens)

        sample.update_from_meta_info(args, meta)
        # MoE routing replay (when requested) ships on the terminal choice.
        _apply_vllm_routed_experts(args, sample, last_choice)
    else:
        sample.status = Sample.Status.FAILED

aoshen02 and others added 3 commits June 6, 2026 23:06
…ang->vLLM

Port THUDM/slime PR-G examples to vime's vLLM rollout:
- #1921 streaming: vime/rollout/vllm_streaming_rollout.py consumes the vLLM
  /inference/v1/generate SSE stream. Key translation: sglang streams CUMULATIVE
  output_token_logprobs (overwrite per chunk); vLLM SSE sends DELTA token_ids+
  logprobs per chunk -> accumulate (+=) and re-decode accumulated tokens each
  chunk (multi-byte safe), finalize via _vllm_meta_from_generate_choice. MM
  raises NotImplementedError (two-call render->generate doesn't fit one stream
  -> use non-streaming generate). + CI test test_qwen3_4B_streaming_partial_rollout.
  (streaming file is the reviewed sync/slime-pr-1921 / draft #117 design.)
- #1861 retool: re-add examples/retool/generate_with_retool.py (vime lacked
  examples/retool/) from slime base + #1861 hardening; desync guard
  `if not meta_info.get("output_token_logprobs")`. NOTE: depends on sibling
  tool_sandbox.py (not yet ported -> follow-up; example partial until then).

Based on PR-A (#145) branch (stacked).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: aoshen02 <aoshen@inferact.ai>
…tim)

generate_with_retool.py imports `from tool_sandbox import SEMAPHORE, TOOL_CONFIGS,
tool_registry`; this in-process math/code tool-execution sandbox is pure Python
(no sglang/slime deps) -> copied verbatim from slime. Completes the retool example
import (was the flagged missing sibling).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: aoshen02 <aoshen@inferact.ai>
…-dance)

Streaming only changes how output is returned (SSE deltas vs one JSON); the
image render (input prep: images→features) is identical to the non-streaming
path. Replace the NotImplementedError stub with the same render-dance
vllm_rollout.generate uses:
  POST /v1/chat/completions/render → _mm_render_response_to_generate_body
  → _align_mm_feature_placeholders_to_tokens → token_ids + sampling_params
  → set stream:True and stream the generate call.

mm placeholders live in the prompt prefix (stable across partial-rollout
continuations), so re-rendering + re-aligning to the current token_ids holds
when an aborted MM sample resumes.

NOTE: needs MM e2e validation on a real vLLM MM server (no unit coverage for
the streaming MM path; can't run GPU/MM here).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@aoshen02
aoshen02 force-pushed the sync/slime-mega-G branch from ef1e28a to d52c36d Compare June 6, 2026 23:24

@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 68183e8 into sync/slime-mega-A Jun 7, 2026
0 of 2 checks passed
aoshen02 added a commit that referenced this pull request Jun 7, 2026
* [Sync][G] streaming rollout + retool example (slime #1921/#1861), sglang->vLLM

Port THUDM/slime PR-G examples to vime's vLLM rollout:
- #1921 streaming: vime/rollout/vllm_streaming_rollout.py consumes the vLLM
  /inference/v1/generate SSE stream. Key translation: sglang streams CUMULATIVE
  output_token_logprobs (overwrite per chunk); vLLM SSE sends DELTA token_ids+
  logprobs per chunk -> accumulate (+=) and re-decode accumulated tokens each
  chunk (multi-byte safe), finalize via _vllm_meta_from_generate_choice. MM
  raises NotImplementedError (two-call render->generate doesn't fit one stream
  -> use non-streaming generate). + CI test test_qwen3_4B_streaming_partial_rollout.
  (streaming file is the reviewed sync/slime-pr-1921 / draft #117 design.)
- #1861 retool: re-add examples/retool/generate_with_retool.py (vime lacked
  examples/retool/) from slime base + #1861 hardening; desync guard
  `if not meta_info.get("output_token_logprobs")`. NOTE: depends on sibling
  tool_sandbox.py (not yet ported -> follow-up; example partial until then).

Based on PR-A (#145) branch (stacked).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: aoshen02 <aoshen@inferact.ai>

* [Sync][G] add retool tool_sandbox.py (in-process tool registry, verbatim)

generate_with_retool.py imports `from tool_sandbox import SEMAPHORE, TOOL_CONFIGS,
tool_registry`; this in-process math/code tool-execution sandbox is pure Python
(no sglang/slime deps) -> copied verbatim from slime. Completes the retool example
import (was the flagged missing sibling).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: aoshen02 <aoshen@inferact.ai>

* feat(mega-G): support multimodal samples in streaming rollout (render-dance)

Streaming only changes how output is returned (SSE deltas vs one JSON); the
image render (input prep: images→features) is identical to the non-streaming
path. Replace the NotImplementedError stub with the same render-dance
vllm_rollout.generate uses:
  POST /v1/chat/completions/render → _mm_render_response_to_generate_body
  → _align_mm_feature_placeholders_to_tokens → token_ids + sampling_params
  → set stream:True and stream the generate call.

mm placeholders live in the prompt prefix (stable across partial-rollout
continuations), so re-rendering + re-aligning to the current token_ids holds
when an aborted MM sample resumes.

NOTE: needs MM e2e validation on a real vLLM MM server (no unit coverage for
the streaming MM path; can't run GPU/MM here).

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

---------

Signed-off-by: aoshen02 <aoshen@inferact.ai>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
aoshen02 added a commit that referenced this pull request Jun 7, 2026
* [Sync][G] streaming rollout + retool example (slime #1921/#1861), sglang->vLLM

Port THUDM/slime PR-G examples to vime's vLLM rollout:
- #1921 streaming: vime/rollout/vllm_streaming_rollout.py consumes the vLLM
  /inference/v1/generate SSE stream. Key translation: sglang streams CUMULATIVE
  output_token_logprobs (overwrite per chunk); vLLM SSE sends DELTA token_ids+
  logprobs per chunk -> accumulate (+=) and re-decode accumulated tokens each
  chunk (multi-byte safe), finalize via _vllm_meta_from_generate_choice. MM
  raises NotImplementedError (two-call render->generate doesn't fit one stream
  -> use non-streaming generate). + CI test test_qwen3_4B_streaming_partial_rollout.
  (streaming file is the reviewed sync/slime-pr-1921 / draft #117 design.)
- #1861 retool: re-add examples/retool/generate_with_retool.py (vime lacked
  examples/retool/) from slime base + #1861 hardening; desync guard
  `if not meta_info.get("output_token_logprobs")`. NOTE: depends on sibling
  tool_sandbox.py (not yet ported -> follow-up; example partial until then).

Based on PR-A (#145) branch (stacked).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: aoshen02 <aoshen@inferact.ai>

* [Sync][G] add retool tool_sandbox.py (in-process tool registry, verbatim)

generate_with_retool.py imports `from tool_sandbox import SEMAPHORE, TOOL_CONFIGS,
tool_registry`; this in-process math/code tool-execution sandbox is pure Python
(no sglang/slime deps) -> copied verbatim from slime. Completes the retool example
import (was the flagged missing sibling).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: aoshen02 <aoshen@inferact.ai>

* feat(mega-G): support multimodal samples in streaming rollout (render-dance)

Streaming only changes how output is returned (SSE deltas vs one JSON); the
image render (input prep: images→features) is identical to the non-streaming
path. Replace the NotImplementedError stub with the same render-dance
vllm_rollout.generate uses:
  POST /v1/chat/completions/render → _mm_render_response_to_generate_body
  → _align_mm_feature_placeholders_to_tokens → token_ids + sampling_params
  → set stream:True and stream the generate call.

mm placeholders live in the prompt prefix (stable across partial-rollout
continuations), so re-rendering + re-aligning to the current token_ids holds
when an aborted MM sample resumes.

NOTE: needs MM e2e validation on a real vLLM MM server (no unit coverage for
the streaming MM path; can't run GPU/MM here).

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

---------

Signed-off-by: aoshen02 <aoshen@inferact.ai>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
CalvinXKY pushed a commit that referenced this pull request Jun 7, 2026
…ariable global batch + group_ids (slime #1926/#1930/#1933/#1941/#1959/#1965/#1969/#1984/#1962) (#145)

* [Sync][A] rollout data-model: micro-batch sched rollout-side + variable global batch + group_ids (slime #1926/#1930/#1933/#1941/#1959/#1965/#1969/#1984/#1962)

Port THUDM/slime mega-PR A (rollout data-model + train-split):
- #1926 move micro-batch scheduling train->rollout side (dp_schedule.py, first-fit packing)
- #1930+#1933 variable global batch size + per-token-loss reduction fix (cp_utils
  reduce_train_step_metrics / get_sum_of_sample_mean sample_denoms; step_global_batch_size
  denominator). CP-invariance + rollout==train equality validated by test_metric_report_dist
  / test_loss_cp_invariance (57/57 multi-proc gloo across CP/DP in {1,2,4}); test_dp_schedule 8/8.
- #1959 forge_load replay (vime/rollout/forge_load.py, --load-forge-rollout-data)
- #1965 group_ids fall back to range(), not sample.index
- #1969 --save-hf for raw mode (hf_checkpoint_saver.py)
- #1941 multi-sample fanout test + helpers (sglang_rollout->vllm_rollout)
- #1984 rename rollout_ids->group_ids (NON-agent files; agent half deferred to PR-D)
- #1962 lint (forge_load)

sglang->vLLM translated per arg-map (--sglang-*->--vllm-*, vllm_speculative_config).
NOTE: test_sample depends on args.vllm_speculative_config existing -> relies on PR-C #1938
getattr guard (stack order F+C+B+A satisfies this). py_compile clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: aoshen02 <aoshen@inferact.ai>

* test(A): port test_hf_checkpoint_saver + wire A numeric tests into cpu CI job

Oracle found A#145 added the data-model numeric tests + hf_checkpoint_saver
impl but did NOT wire them into the cpu CI job (e2e-test-plugin-contracts),
and the hf_checkpoint_saver unit test (slime #1969) was never ported.

- port tests/utils/test_hf_checkpoint_saver.py (slime->vime)
- add dp_schedule/cp_utils/metric_report{,_dist}/loss_cp_invariance/sample
  + utils/test_hf_checkpoint_saver to the cpu job; regen pr-test.yml

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

* [Sync][A] revert rollout_id->group_id rename (port slime #2013)

slime #2013 reverts #1984 (the rollout_id->group_id rename) because it
conflicts with internal tooling. A #145 ported #1984, so mirror the revert to
stay byte-faithful and realign with main + post-#2013 slime:
  group_id -> rollout_id, group_ids -> rollout_ids,
  group_mask_sums -> rollout_mask_sums, num_groups_in_rollout ->
  num_rollouts_in_rollout, and drop the Sample.rollout_id deprecation guard
  (__getattribute__/__setattr__ + import warnings) #1984 had added.

Per-file churn matches slime #2013 exactly (actor/data/loss/model/rollout/
_fanout_test_helpers/forge_load/dp_schedule/types + 3 tests). The unrelated
compute_pass_rate(num_groups=...) kwarg is left as-is (slime keeps it too).

NOTE: #2013 also reverts D-side files not present on A
(examples/coding_agent_rl/*, vime/agent/trajectory.py, docs .../agent.md, the
group_id sections of docs customization.md + multi_agent stamping). Those must
get the same revert when D #148 is rebased onto this A.

* [Sync][G] streaming rollout + retool example (slime #1921/#1861) (#147)

* [Sync][G] streaming rollout + retool example (slime #1921/#1861), sglang->vLLM

Port THUDM/slime PR-G examples to vime's vLLM rollout:
- #1921 streaming: vime/rollout/vllm_streaming_rollout.py consumes the vLLM
  /inference/v1/generate SSE stream. Key translation: sglang streams CUMULATIVE
  output_token_logprobs (overwrite per chunk); vLLM SSE sends DELTA token_ids+
  logprobs per chunk -> accumulate (+=) and re-decode accumulated tokens each
  chunk (multi-byte safe), finalize via _vllm_meta_from_generate_choice. MM
  raises NotImplementedError (two-call render->generate doesn't fit one stream
  -> use non-streaming generate). + CI test test_qwen3_4B_streaming_partial_rollout.
  (streaming file is the reviewed sync/slime-pr-1921 / draft #117 design.)
- #1861 retool: re-add examples/retool/generate_with_retool.py (vime lacked
  examples/retool/) from slime base + #1861 hardening; desync guard
  `if not meta_info.get("output_token_logprobs")`. NOTE: depends on sibling
  tool_sandbox.py (not yet ported -> follow-up; example partial until then).

Based on PR-A (#145) branch (stacked).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: aoshen02 <aoshen@inferact.ai>

* [Sync][G] add retool tool_sandbox.py (in-process tool registry, verbatim)

generate_with_retool.py imports `from tool_sandbox import SEMAPHORE, TOOL_CONFIGS,
tool_registry`; this in-process math/code tool-execution sandbox is pure Python
(no sglang/slime deps) -> copied verbatim from slime. Completes the retool example
import (was the flagged missing sibling).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: aoshen02 <aoshen@inferact.ai>

* feat(mega-G): support multimodal samples in streaming rollout (render-dance)

Streaming only changes how output is returned (SSE deltas vs one JSON); the
image render (input prep: images→features) is identical to the non-streaming
path. Replace the NotImplementedError stub with the same render-dance
vllm_rollout.generate uses:
  POST /v1/chat/completions/render → _mm_render_response_to_generate_body
  → _align_mm_feature_placeholders_to_tokens → token_ids + sampling_params
  → set stream:True and stream the generate call.

mm placeholders live in the prompt prefix (stable across partial-rollout
continuations), so re-rendering + re-aligning to the current token_ids holds
when an aborted MM sample resumes.

NOTE: needs MM e2e validation on a real vLLM MM server (no unit coverage for
the streaming MM path; can't run GPU/MM here).

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

---------

Signed-off-by: aoshen02 <aoshen@inferact.ai>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* [Sync][D] coding_agent_rl: agent-in-sandbox RL subsystem (slime #1923/#1956/#1960/#1954/#1957/#1958/#1963/#1979/#1981/#1982/#1961) (#148)

* [Sync][D] coding_agent_rl: agent-in-sandbox RL subsystem (slime #1923/#1956/#1960/#1954/#1957/#1958/#1963/#1979/#1981/#1982/#1961), sglang->vLLM

Add the agent-in-sandbox RL subsystem vime lacked. New vime/agent/ (trajectory,
sandbox, parsing, adapters: common/openai/anthropic) + examples/coding_agent_rl/
(demo, generate, sandbox, aiohttp_threaded, run script). Copied slime's final
state (all 11 D PRs applied) + translated.

3 sglang->vLLM seams:
- adapters/common.py: call_sglang_generate -> call_engine_generate; POST /generate
  -> /inference/v1/generate; body input_ids->token_ids + max_new_tokens->max_tokens
  + drop return_logprob + add logprobs:1 + model=; response output_token_logprobs
  -> choices[0].token_ids + logprobs.content[].logprob (via canonical field map);
  per-rid /abort_request -> task.cancel() + connection teardown on cancel.
- coding_agent_rl/generate.py: sglang_router_ip/port -> vllm_router_ip/port.
- parsing.py: NO change (ReasoningParser/FunctionCallParser lazy; coding_agent_rl
  defaults to XML fallback).

#1984 agent-half rename (rollout_id->group_id in trajectory.py) already present
(copied slime HEAD; consistent with PR-A's Sample.group_id). EAGLE spec left
commented (needs draft-model path). Based on PR-A (#145) (stacked).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: aoshen02 <aoshen@inferact.ai>

* test(D): port agent adapter/trajectory tests + wire agent-adapter CI job

Oracle found D#148's agent subsystem (vime/agent/adapters + trajectory) shipped
without its tests, and the cpu CI job had no agent-adapter coverage.

- port tests/test_agent_{adapters,sdk_adapters,trajectory}.py (slime->vime:
  slime.agent->vime.agent, SGLANG_URL_KEY->ENGINE_URL_KEY, sglang_url->engine_url)
- add agent-adapter-test cpu job (openai/openai-agents/anthropic) + extra_pip_deps
  template support; regen pr-test.yml

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

* ci(D): don't wire test_agent_adapters yet (3/14 mock sglang /generate schema)

11/14 pass; the 3 (uses_sglang_tokens_for_training_segment x2, generate_
posts_input_ids_and_extracts_logprobs) assert against sglang's meta_info.
output_token_logprobs response shape and need rewriting to vime's vLLM
response format. File stays ported; trajectory + sdk_adapters wired+green.

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

* fix(D): clean .j2 (drop invalid python comment, regen pr-test.yml)

prior commit left a '#' comment inside the jinja dict literal -> regen
failed -> pr-test.yml stale (still wired the 3 red sglang-schema cases).
Remove comment; rationale lives in commit msg + OVERNIGHT_REPORT.

* fix(mega-D): translate agent-adapter sglang→vllm, not engine/杂名

Audit finding (bidirectional translation rule): the agent adapters had
translated slime's `sglang` identifiers/prose to generic `engine`/`rollout
engine` aliases instead of `vllm` specifically. Per the ground principle
(vime = slime modulo sglang→vllm, 1:1), fix to vllm:

  SGLANG_URL_KEY  → VLLM_URL_KEY        (AppKey "sglang_url"→"vllm_url")
  sglang_url      → vllm_url
  call_sglang_generate → call_vllm_generate
  (new helper)    → _vllm_sampling_body (was _engine_sampling_body)
  "sglang upstream {status}" → "vllm upstream {status}"
  docstrings "SGLang /generate" → "vLLM /inference/v1/generate"

Also wire the 3 previously-untranslated test cases in test_agent_adapters.py
from sglang wire schema to vLLM /inference/v1/generate schema:
  FakeSGLang → FakeVLLM  (returns choices[].token_ids + logprobs.content,
                          reads x-session-id header, serves /inference/v1/generate)
  request asserts: input_ids→token_ids, sampling_params.max_new_tokens→max_tokens,
                   return_logprob→sampling_params.logprobs==1, X-SMG-Routing-Key→x-session-id
  test names: *_uses_sglang_tokens_* → *_uses_vllm_tokens_*,
              *_posts_input_ids_* → *_posts_token_ids_*
  + MODEL_KEY in the direct-_generate test app (vime adds GenerateRequest.model).

14/14 test_agent_adapters pass.

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

* fix(mega-D): drop the upstream `model` passthrough (mirror slime; vLLM doesn't need it)

Audit (vime-added field, no slime counterpart): the adapter had added a `model`
param + MODEL_KEY + `payload["model"]=model` sending model upstream to
/inference/v1/generate. Source check: vLLM `GenerateRequest.model` is
`str | None = None` (optional) and the disagg generate/serve_tokens handler
never reads it; the vllm-router doesn't route /generate by body.model. So it is
unused upstream — an unjustified divergence from slime (whose adapter has no
model param). Remove it to mirror slime exactly.

Kept: the OpenAI-response echo `body.get("model", "vime-actor")` — slime has the
identical line (slime-actor). Test no longer needs MODEL_KEY. 14/14 pass.

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

* ci(mega-D): wire test_agent_adapters.py into agent-adapter-test (was a dead test)

The agent-adapter-test job ran test_agent_sdk_adapters.py + test_agent_trajectory.py
but NOT test_agent_adapters.py — so the 14 adapter cases (incl. the 3 sglang→vLLM
schema ports) never ran in CI. slime wires test_agent_adapters.py in its
agent-adapter-test matrix; add it here (yml + j2). Deps (openai/anthropic/aiohttp)
already installed by this job.

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

* [Sync][D] revert rollout_id->group_id in agent files (port slime #2013, D-side)

Rebased D's own commits onto the updated A #145 (which now carries slime #2013).
A #145 reverted the shared data-model to rollout_id; this completes the same
revert for D's own files that A doesn't carry: examples/coding_agent_rl/
{README.md,generate.py} and vime/agent/trajectory.py. After this the whole
A+D surface is consistent on rollout_id, matching post-#2013 slime.

* fix(D): translate engine_url -> vllm_url at AnthropicAdapter call site (coding_agent_rl)

The adapter modules (agent/adapters/{anthropic,common,openai}.py) were
translated sglang_url -> vllm_url (commit 457d21c), but the call site in
examples/coding_agent_rl/generate.py was missed: it still built engine_url and
passed AnthropicAdapter(engine_url=...). Since the adapter __init__ now takes
vllm_url, this kwarg mismatch raises TypeError at construction. Rename the
local var + kwarg to vllm_url, completing the slime sglang_url -> vllm_url
translation and fixing the call.

---------

Signed-off-by: aoshen02 <aoshen@inferact.ai>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Signed-off-by: aoshen02 <aoshen@inferact.ai>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@aoshen02
aoshen02 deleted the sync/slime-mega-G branch June 8, 2026 14:17
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