[Sync][G] streaming rollout + retool example (slime #1921/#1861) - #147
Conversation
There was a problem hiding this comment.
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.
| 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) |
There was a problem hiding this comment.
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.
| 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) |
| 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 |
There was a problem hiding this comment.
There are three critical issues in the streaming finalization logic:
- 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...exceptblock ensures robustness. - Incorrect Status on Stream Failure: If the stream fails or disconnects prematurely without being aborted, the sample status remains
PENDING. It should be marked asFAILEDto prevent training on incomplete data. - Aborted Status Overwrite: If
state.abortedisTrue, the sample status must be set toABORTEDeven iffinish_reasonis 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.FAILED8803d18 to
68d40fa
Compare
c7d04c5 to
ef1e28a
Compare
…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>
ef1e28a to
d52c36d
Compare
* [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][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>
…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>
PR-G — streaming + retool examples (RFC #107). Stacked on PR-A (#145) — base is
sync/slime-mega-Aso the diff is G-only.Included
vime/rollout/vllm_streaming_rollout.py+tests/test_qwen3_4B_streaming_partial_rollout.py. Translates the sglang streaming path to vLLM's/inference/v1/generateSSE. Load-bearing translation: sglang streams cumulativeoutput_token_logprobs(overwrite per chunk); vLLM SSE sends deltatoken_ids+logprobsper chunk → accumulate (+=) and re-decode accumulated tokens each chunk (multi-byte-safe), finalize via_vllm_meta_from_generate_choice.routed_expertsapplied only from the terminal choice. MM raisesNotImplementedError(the two-call render→generate flow doesn't fit a single stream). Streaming file is the reviewedsync/slime-pr-1921(draft [Rollout] Add streaming vLLM rollout (port slime #1921) #117) design.examples/retool/generate_with_retool.py(vime lackedexamples/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
tool_sandbox.pynot yet ported (out of #1861's scope; vime never hadexamples/retool/). The example won't run end-to-end until that lands — flagged for a follow-up.TRUNCATED;finish_typetracked locally since vLLM responses are per-call.CI
test_qwen3_4B_streaming_partial_rolloutregistered in the e2e-test-megatron matrix (.j2+ generated.yml). py_compile clean.🤖 Generated with Claude Code
Fidelity ledger (audited 2026-06-04)
meta_infofrom sglang cumulative form → vLLM delta-accumulation