Repository navigation
Add a multi-turn GRPO example with tool calling, and port OPSD to the per-sample rollout API - #1023
Open
anyadontfly wants to merge 5 commits into
Open
anyadontfly wants to merge 5 commits into
anyadontfly wants to merge 5 commits into
Conversation
Trains Qwen3 with GRPO on dapo-math-17k while it calls a sandboxed Python interpreter, on the DeepSpeed hybrid engine rollout (deepspeed.runtime.rollout): multi-turn rollout, CUDA-graph decode and multi-turn continuous batching, then a PPO-clip update normalized over all ranks. Includes a Qwen3-0.6B smoke test, a Qwen3-4B single-GPU config and a Qwen3-8B 8-GPU config with launch scripts. Signed-off-by: Puyuan Yao <williamyao034@gmail.com>
- Switch the single-GPU config from the SFT checkpoint to Qwen3-4B-Instruct-2507 (renamed to qwen3_4b_instruct_1gpu.json), with 16 decode rows to fit a 40 GB A100 and AIME evaluation every 100 steps. - Register an sdpa_gqa_decode attention implementation that reads grouped KV heads in place on decode steps. Hugging Face's SDPA copies them whenever there is a mask, which made Qwen3-4B decode about three times slower. - Strip a Markdown fence around the whole code argument, as Qwen3-2507 models write it, and tell the model that only printed output is returned. - Check imports with ast, so `from math import sqrt` is allowed, and return Python's SyntaxError for code that does not parse instead of guessing its imports. - Drop continuous_poll_steps, which the rollout no longer reads, and correct the host memory the single-GPU config needs. Signed-off-by: Puyuan Yao <williamyao034@gmail.com>
tool.show_last_value (off by default) makes the interpreter also return the value of a bare expression on the code's last line, as a notebook cell shows it, and says so in the tool description. Qwen3-4B-Instruct-2507 almost never ends its code with print(), so without it nearly every call returns empty output. Code that ends with print(...) runs as before. Signed-off-by: Puyuan Yao <williamyao034@gmail.com>
The rollout in deepspeed.runtime.rollout now takes and returns lists of per-sample RolloutRecords (stage 1 of RFC #8942). The OPSD trainer builds one record per prompt row from its real tokens and gets the training tensors and the response mask from ResponseTokenBatch.from_rollout, which replaces utils.build_response_mask. main.py passes the student engine as generation_engine. Both rollout benchmarks build their requests from records, and the READMEs say which DeepSpeed the example needs. Signed-off-by: Puyuan Yao <williamyao034@gmail.com>
Drop the sdpa_gqa_decode attention that policy.py registered with Hugging Face, and go back to plain SDPA in the configs. Reading grouped KV heads in place during decode is a rollout concern for DeepSpeed (or Transformers) to handle for every model, not something each example should register. Until then, decode pays Hugging Face's KV-head copy. Signed-off-by: Puyuan Yao <williamyao034@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Depends on deepspeedai/DeepSpeed#9037.
Summary
Adds
training/grpo/, a ReTool-style example that trains Qwen3 with GRPO to solve math problems while calling a Python interpreter across several turns. It is the example for stage 1 of the agentic RL proposal (deepspeedai/DeepSpeed#8942) and is built on the rollout API that deepspeedai/DeepSpeed#9037 adds. Rollout and training share one hybrid engine, so the policy generates with its live training weights, with no separate inference server and no weight sync.That PR changes
RolloutRequestandRolloutBatchfrom tensors to lists of per-sampleRolloutRecords, which breaks the existing OPSD example. This PR ports OPSD to the new API as well.The GRPO example
Each step has three phases:
HybridEngineRolloutsamplesn_samples_per_prompttrajectories per prompt. When the model writes a<tool_call>, the rollout runs the code in the interpreter and appends its output as a tool message, for up tomax_turnsturns. Multi-turn continuous batching and CUDA-graph decode are on in every config.\boxed{}check scores each trajectory +1 or −1, andgroup_advantagesnormalizes rewards within each prompt's group.ResponseTokenBatch.from_rolloutbuilds the targets with a mask over generated tokens only, so prompts, padding and tool output are not trained on.grpo_policy_losscomputes PPO-clip with clip-higher (0.2 / 0.28) and no KL term, aggregated per token (default) or per trajectory.main.pytrainer.pyconfig.pypolicy.pytools.pyreward.pydata.py,scripts/dapo-math-17k,aime-2024) and one launch script per configConfigs:
smoke_qwen3_0.6b.jsonqwen3_4b_instruct_1gpu.jsonqwen3_8b_8gpu.jsonInterpreter. Each call runs in a subprocess with an import allowlist (checked on the parsed code, so
from math import sqrtworks), a pattern filter, a memory limit, a timeout and an output cap. It is not a security boundary, and the README says so. Code wrapped in Markdown fences is unwrapped, code that doesn't compile returns Python's ownSyntaxError, and the tool description asks the model toprint()what it needs.tool.show_last_value(off by default) also returns the value of a bare last-line expression, as a notebook does, for models such as Qwen3-4B-Instruct-2507 that rarely end their code withprint().OPSD port
trainer.pyturns each prompt row's real tokens into aRolloutRecordand getsinput_ids,attention_maskand the response mask fromResponseTokenBatch.from_rollout, which replacesutils.build_response_mask.utils.pyand its tests are removed.main.pypasses the student engine tobuild_rolloutasgeneration_engine.Testing
All on one A100 40 GB, with DeepSpeed from deepspeedai/DeepSpeed#9037 and transformers 5.
show_last_valueshow_last_value, 62 of 82 tool calls return output, against 2 of 108 without itNotes
Decode attention. Hugging Face's SDPA used to repeat each KV head once per query head whenever a mask was passed, which made masked decode of grouped-query models such as Qwen3 about three times slower than needed. huggingface/transformers#49743 fixes this by folding the query heads onto their KV head when decoding. It is merged and should be in the next Transformers release, so this example uses plain
sdpaand does not work around it.