Skip to content

[Spike] Sync vs Fully-Async training: Qwen3-4B GRPO on GPU #354

Description

@CalvinXKY

Type: Spike / experiment log
Scope: GPU (A800), non-colocate
Related code / docs:

  • train_async.py + --rollout-function-path vime.rollout.fully_async_rollout.generate_rollout_fully_async
  • Examples: examples/fully_async/
  • Docs: docs/en/examples/qwen3-4B.md (async section)

1. Motivation

When train and rollout are disaggregated (non-colocate), sync GRPO naturally bubbles:
train GPUs wait for rollout, then rollout GPUs wait for train. Official docs already
recommend switching train.pytrain_async.py so the next rollout is generated while
the current batch is trained.

We ran a controlled A/B (same model / data / steps / GPU layout) to answer:

  1. How much wall-clock does the fully-async combo (train_async.py + generate_rollout_fully_async) save vs sync under a matched step budget?
  2. Does accuracy suffer relative to sync, stay flat, or improve?
  3. How should “vime fully async” be described vs common “RL fully async” (stale-weight /
    producer–consumer) designs — and vs train_async.py with the default sync rollout?

2. Terminology note (important)

Term What we mean
train_async.py (default rollout) Outer-loop prefetch / one-step pipeline: while training step N, start generate(N+1); sync that generate before update_weights (default --update-weights-interval 1). Each generate() still finishes a full batch via default vime.rollout.vllm_rollout.generate_rollout.
vime fully_async (this spike) train_async.py + --rollout-function-path vime.rollout.fully_async_rollout.generate_rollout_fully_async. Same outer cadence, plus a continuous rollout worker that keeps generations in flight across generate() calls (long-tail hiding / warm queue). See examples/fully_async/README.md.
RL “fully async” (elsewhere) Train and inference are decoupled: a continuous rollout server fills a buffer; train consumes from the buffer; weight sync is periodic → multi-step stale behavior policy is allowed.

This spike’s Async arm is vime fully_async (both layers), not train_async.py alone.
We did not A/B train_async+default vs fully_async_rollout; isolating that extra gain needs a three-way run.

Relative to a fully decoupled async design, vime’s path still ties weight updates to the step loop (typically ~one-step off-policy under the default interval), rather than allowing arbitrary multi-step staleness.


3. Setup

Item Value
Hardware A800
Model Qwen3-4B
Dataset (train) dapo-math-17k
Method GRPO
Steps 200
TP 2
Actor GPUs 2
Rollout GPUs 2 (disaggregated)
rollout-batch-size / n-samples / gbs 8 / 4 / 32
Eval dapo-math-17k first 500 prompts; vLLM greedy; thinking off; max_tokens=1024
Mode Entry Rollout path
Sync train.py default sync rollout
Async train_async.py vime.rollout.fully_async_rollout.generate_rollout_fully_async

4. Results

4.1 Training speed

Metric Sync Async
step_time ~15.5 s ~6 s
train_wait_time ~10.3 s (~66%) ~0–1 s
train_time (compute) ~5.3 s ~5.3 s
update_weights ~0.3 s ~0.3–0.7 s
Wall clock ~52 min ~20 min
Speedup 1.0× ~2.6×

Async mainly removes the train-wait bubble; per-step train compute stays similar.

4.2 Downstream accuracy (greedy eval, 500 prompts)

Model Accuracy Correct / Total vs Baseline
Baseline (pretrained Qwen3-4B) 16.60% 83 / 500
Sync 17.20% 86 / 500 +0.6 pp
Async 20.60% 103 / 500 +4.0 pp

Rough efficiency (accuracy gain / wall-clock minute): ~0.012 pp/min (sync) vs ~0.20 pp/min (async).

Eval parsing: prefer \boxed{}, then Answer: line, else trailing number.

4.3 Qualitative observations

  1. Async is faster and higher-scoring than sync under this matched setup.
  2. Sync early eval slices (e.g. first 50–100 prompts) dip below baseline (8–9% vs ~12–13%); async does not show the same early dip in our curve.
  3. Same nominal step count ≠ same “data freshness” timeline: async overlaps generation, so the effective exposure schedule differs from sync even when step count matches.

5. References (in-tree)

  • examples/fully_async/README.md
  • examples/fully_async/run-qwen2.5-0.5B-fully-async.sh
  • vime/rollout/fully_async_rollout.py
  • docs/en/examples/qwen3-4B.md — “asynchronous training” note

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions