Training Backends
One TrainBackend interface, two implementations — Megatron-LM (TP/PP/DP/CP) and PyTorch FSDP v2 (HuggingFace-native) — plus the runtime infrastructure they share: Ray placement, weight sync, checkpointing, memory, and logging.
The contract
TrainBackend (base.py): seven abstract methods for setup, training, weight sync, and checkpointing. The loss comes from a specialization, not the backend.
Two backends
MegatronBackend (TP/PP/DP/CP) is the default for scale; FSDPBackend (FSDP v2, HF-native, DP-only) suits smaller runs.
Shared runtime
Ray placement, weight sync to SGLang, torch_dist / DCP checkpoints, 3-tier sleep/wake, and W&B logging (§6).
1 The backend contract
The trainer selects a backend from args.train_backend (ray/train_worker.py) and drives it through one interface. Each step calls the specialization hooks compute_advantages() → prepare_batch() → extract_model_output() → compute_loss().
Backend stack: one interface, two implementations, weights synced to SGLang each iteration.
2 backends/base.py — TrainBackend interface
Seven abstract methods define the surface each backend implements; the rest of base.py is shared machinery.
Abstract surface
| Method | Contract |
|---|---|
init(args, role, with_ref, with_opd_teacher, specialization_cls) | Full setup: distributed groups → model → optimizer → weight sync → checkpoint load → specialization. Returns the rollout id to resume from, or None. |
_setup_optimizer() | Create the optimizer + LR scheduler (Megatron distributed optimizer, or PyTorch AdamW). |
train(rollout_id, rollout_data, specialization_key) | One full iteration: preprocess → ref/teacher/old log probs → advantages → forward/backward/step. Returns a metrics dict. |
update_weights() | Push updated weights to the rollout (SGLang) engines. No-op when no rollout manager is attached (--debug-train-only). |
save_model(rollout_id, force_sync) | Write model + optimizer + LR-scheduler state in the backend's checkpoint format. |
sleep(tags) / wake_up(tags) | Offload GPU state before inference / restore it before training (colocated GPU sharing). |
Shared concretes
forward_only() (no-grad log-probs), maybe_compute_ref/teacher_log_probs() (frozen model for FSDP, weight-tag switch for Megatron), preprocess_rollout_data() (DP shard + move to CUDA), and backup/restore/switch_weights(tag) via the WeightManager.
Key attributes
| Attribute | Description |
|---|---|
model | The trainable model — a Megatron list[DDP] chunk list, or an FSDP-wrapped HF AutoModelForCausalLM. |
ref_model | Separate frozen reference model. Populated only by FSDP; Megatron uses a weight tag instead (stays None). |
optimizer / lr_scheduler | Megatron distributed optimizer + its scheduler, or PyTorch AdamW + FSDPLRScheduler. |
specializations | Dict of role name → ModelSpecialization. No role-string dispatch: the backend just looks up the key. |
weight_manager | WeightManager for multi-tag backup/restore/switch (actor, ref, teacher, old_actor). |
process_groups | ProcessGroupManager — Megatron TP/PP/DP/CP groups, or the FSDP 1-D device mesh. |
worker_sync | WorkerSync for two-sided NCCL exchange syncs between worker groups (used by PPO's value↔policy). |
dp/tp/pp/cp _size & _rank | Parallel dimensions. FSDP populates only dp_*; the rest stay 1/0. |
Out of scope
Advantage estimation, KL, clipping → specialization. Rollout logic, task scheduling, sandbox → rollout/environment.
3 Backends — megatron.py & fsdp.py
backends/megatron.py — MegatronBackend, the default, built on slime + rl_engine/utils/megatron.py: TP/PP/DP/CP parallelism, a CPU-offloaded distributed optimizer, packed sequences, and torch_dist checkpoints. Diffusion models take an experimental _setup_diffusion_model() path (see Algorithm Recipes). The flags it reads:
--tensor-model-parallel-size 4 --sequence-parallel # TP; PP/CP stay 1 on a single node
--optimizer adam --optimizer-cpu-offload --use-precision-aware-optimizer
--use-dynamic-batch-size --max-tokens-per-gpu 32768 # packed batches (4B; 8192 for 8B)
--recompute-granularity full # activation recompute
--hf-checkpoint <HF dir> --load <torch_dist dir> --save <ckpt root>
--ref-load / --opd-teacher-load <torch_dist> # KL reference / frozen teacher weights
backends/fsdp.py — FSDPBackend, FSDP v2 on a 1-D data-parallel mesh, HF-native (AutoModelForCausalLM → apply_fsdp2), PyTorch-DCP checkpoints. No TP/PP; with_opd_teacher unsupported.
--train-backend fsdp # select the backend (default: megatron)
--hf-checkpoint <HF dir> # the only required model path
--grad-clip 1.0 --fp16 # bf16 default; optional fp16 / CPU offload
Both drive the same spec.compute_loss(); full flag set in the knobs below.
4 Memory: Sleep / Wake
FSDP: CPU offload via rl_engine/utils/memory.py. Megatron — three tiers:
| Tier 1 — TMS | Tier 2 — Megatron native default | Tier 3 — PGs only | |
|---|---|---|---|
| Mechanism | torch_memory_saver.pause() + destroy PGs | optimizer.offload_to_cpu() + ddp_model.offload_grad_buffers() + destroy PGs | Destroy NCCL process groups only |
| Model weights | Paused to CPU (allocator-level) | Stay on GPU | Stay on GPU |
| Requires | STACX_USE_TMS_OFFLOAD=1 + LD_PRELOAD + NCCL_CUMEM_ENABLE=1 | Megatron distributed optimizer with offload_to_cpu | Nothing |
| Selected when | Opt-in only | Offload APIs present (the norm) | Fallback |
Tier 2 is the production default on A100 80 GB.
Selection logic (MegatronBackend.sleep()):
if self._tms_available: # Tier 1
destroy_process_groups(); torch_memory_saver.pause()
elif self._can_megatron_offload(): # Tier 2 (default)
ddp_model.offload_grad_buffers(); optimizer.offload_to_cpu(); destroy_process_groups()
else: # Tier 3
destroy_process_groups()
5 Backend knobs
Full STACX_* catalog: Getting Started / scripts/train/swe/ENV_REFERENCE.md.
| Knob | Applies to | Effect |
|---|---|---|
--train-backend | both | megatron (default) or fsdp; read in TrainWorker.init(). |
--tensor-model-parallel-size | Megatron | TP degree; SWE recipes use 4. Paired with --sequence-parallel. |
--pipeline-model-parallel-size / --context-parallel-size | Megatron | PP / CP degrees (1 on single node). |
MAX_TOKENS_PER_GPU → --max-tokens-per-gpu | Megatron | Per-GPU token budget for dynamic batching. 32768 (4B) / 8192 (8B); lower it for 8B OOM relief. |
SGLANG_MEM_FRACTION → --sglang-mem-fraction-static | both | SGLang static KV-cache fraction (0.7 / 0.5); a higher fraction leaves less training headroom. |
--recompute-granularity / -method / -num-layers | Megatron | Activation recompute (full / uniform / 1) — trades compute for memory on long trajectories. |
STACX_USE_TMS_OFFLOAD | Megatron | Opt into Tier-1 TMS sleep. Requires LD_PRELOAD + NCCL_CUMEM_ENABLE=1; off by default. |
ROTARY_BASE → MODEL_ARGS_ROTARY_BASE → --rotary-base | Megatron | RoPE base override; model script is the single emitter. Default 1e6; SWE recipes set 5e6. |
--optimizer-cpu-offload / --use-precision-aware-optimizer | Megatron | Offload optimizer state to CPU; precision-aware master weights. |
fsdp_cpu_offload | FSDP | Adds CPUOffloadPolicy() — params/grads/optimizer step run on CPU. |
--fp16 | FSDP | Switch param_dtype from bf16 to fp16 in the mixed-precision policy. |
--ref-load / --opd-teacher-load | Megatron | Extra checkpoints loaded into ref / teacher weight tags. |
6 Runtime infrastructure
Model-agnostic plumbing shared by every recipe, in rl_engine/trainer/infra/ and rl_engine/train.py.
Single-node topology
Two Docker containers plus a host ROCK process share one 8-GPU node. Teacher and student never share GPUs; inside the student, the Megatron trainer and its SGLang engine do, via --colocate.
Teacher (GPUs 0–3) and student (GPUs 4–7) containers plus host ROCK. The teacher is reused if already healthy.
| Role | GPUs | Process | Where set |
|---|---|---|---|
| Teacher | 0,1,2,3 | SGLang Qwen3-Coder-30B TP=4 → :30055 | CUDA_VISIBLE_DEVICES=0,1,2,3 (dagger_4b.sh) |
| Student | 4,5,6,7 | Ray head + Megatron TP=4 + colocated SGLang engine | CUDA_VISIBLE_DEVICES=4,5,6,7 (dagger_4b.sh) |
| ROCK + sandboxes | — | Sandbox orchestrator (CPU), pulls per-instance images | host process on :8080; workers pinned to GPUs 4–7 in the cluster-layout JSON |
Ray placement & runtime env
rl_engine/utils/ray.py · rl_engine/trainer/factory.py · dagger_4b.sh
create_placement_groups(args)re-exports fromslime.ray.placement_group;build_gpu_allocations()maps slime keys to trainer roles ("policy"→pgs["actor"]; value/reference/reward groups only for multi-role recipes).--colocateputs the actor placement group and the rollout SGLang engine on the same 4 GPUs.
Ray socket path length limit
Ray's plasma-store socket path must fit in 107 bytes. The launcher hashes RUN_TAG to a short suffix:
RAY_TMP_SUFFIX=$(echo "${RUN_TAG}" | md5sum | cut -c1-8)
export RAY_TMP_DIR=/tmp/r_${RAY_TMP_SUFFIX} # e.g. /tmp/r_3f9c1a20
Process groups
rl_engine/trainer/infra/process_groups.py
ProcessGroupManager is a backend-aware query wrapper over an already-initialized torch.distributed. Megatron's _initialize_megatron() builds independent TP/PP/CP/DP groups via mpu.initialize_model_parallel(); FSDP reports tp=pp=cp=1, dp=world_size over group.WORLD.
Weight sync — sync primitive details
rl_engine/trainer/infra/inference_weight_sync.py
Three sync paths. The SWE recipe uses the tensor lane: use_tensor_path = colocate or tp_size > 1.
WeightManager — multi-tag backup
infra/weight_manager.py. Snapshots named weight sets ("actor", "ref", "teacher", "old_actor") and switch()es between them on one GPU set. GRPO restores a frozen "ref" from --ref-load as its in-Megatron KL anchor; the DAgger/OPD teacher is a separate SGLang server, not a tag.
WorkerSync — inject vs. exchange
infra/worker_data_sync.py. Key-driven tensor exchange between worker pools — inject via the Ray object store (dpo_distributed, online_reward), exchange via direct NCCL (PPO actor↔critic). Single-role SWE recipes declare none.
distributed_masked_whiten()
infra/distributed.py. Whitens advantages with global DP-group statistics via an all-reduce of masked sum / sum-of-squares / count (Bessel-corrected), avoiding an OOM-prone gather. Used by GRPO.
Checkpointing & auto-resume
rl_engine/train.py · backends/megatron.py · dagger_4b.sh
Re-running the same launch command resumes with the same weights, optimizer state, and W&B run.
save_model()writes atorch_distcheckpoint intoiter_{N:07d}/sharded across TP ranks — ~53 GB per 4B checkpoint (bf16 + fp32 Adam state), pruned to the newest--save-max-to-keepdirs.- Each rollout is pickled to
rollout_ckpts/rollout_{N}.ptso a resumed job skips regeneration.
resume_state.json (written atomically after every save and on graceful exit; read by the launcher on relaunch):
| Field | Example | Role on relaunch |
|---|---|---|
next_rollout_id | 2 | → --start-rollout-id; completed rollouts skipped |
checkpoint_path | …/{RUN_TAG} | = --save; resume repoints --load here |
rollout_ckpt_dir | …/rollout_ckpts | where rollout_{N}.pt live |
wandb_run_id | 3f9c1a… | → --wandb-resume-id, reopens the same run |
exit_reason | "wall_time" | periodic_save / wall_time / completed |
resume_count | 1 | incremented each relaunch; logged to W&B |
Eval is gated behind 2 × wall_time_reserve so a run is never killed mid-eval. Defaults: WALL_TIME_LIMIT 7 d, WALL_TIME_RESERVE 10 min (SWE scripts set 1 h).
Relaunch reads resume_state.json, skips completed rollouts, reopens the W&B run. In-flight rollouts restart from scratch.
Memory management additions
Beyond sleep/wake (§4), two more mechanisms manage the colocated engine's memory:
Rollout offload
With --offload-rollout, the driver wraps rollout-engine memory via rollout_manager.offload() / onload_weights() / onload_kv().
Platform awareness
PlatformCapabilities (infra/platform_detect.py) sets memory_offload_effective = False on unified-memory GPUs (GH200, GB200), where CPU offload doesn't free device memory.
Logging & monitoring
infra/logging.py · infra/profiler.py · train.py
init_tracking(args) initializes W&B; --wandb-resume-id reopens the same run on resume. Ray workers can't reach W&B, so the driver logs from the main process (eval goes to a secondary run).
| Key | Meaning | Source |
|---|---|---|
dagger/beta | β at this rollout (teacher/student execution mix) | train.py |
train/ce_mean | CE loss on teacher-executed action tokens | mixed_loss_dagger.py |
train/k3_mean | K3 reverse-KL on student-executed action tokens | |
train/teacher_action_frac | fraction of action tokens the teacher executed | |
train/global_step | rollout_id × passes + pass_idx — per-pass x-axis | train.py |
resume/count, resume/mode_* | relaunch counter + fresh/rollout/training mode | train.py |
perf/train_time, perf/pass_time | per-rollout and per-pass wall time | train.py |
7 Infrastructure knobs & source map
Infra knobs only — algorithm knobs (β/κ, K3, teacher sampling) live on Algorithm Recipes; full catalog in scripts/train/swe/ENV_REFERENCE.md.
| Env / flag | Default | Effect |
|---|---|---|
WALL_TIME_LIMIT → --wall-time-limit | 604800 | Wall-clock budget (s); graceful save + exit before it. |
WALL_TIME_RESERVE → --wall-time-reserve | 600 | Head-room reserved for the final checkpoint save. SWE scripts set 3600. |
SAVE_INTERVAL → --save-interval | 1 | Save a checkpoint every N rollouts. |
SAVE_MAX_TO_KEEP → --save-max-to-keep | NUM_ITERS (GRPO: 3) | Retain only the newest K iter_* dirs (~53 GB each). |
EVAL_INTERVAL → --eval-interval | 1 | Eval cadence; skipped if within 2×reserve of the limit. |
START_ROLLOUT_ID → --start-rollout-id | from resume_state.json | First rollout to run; earlier ones are skipped on resume. |
RAY_TMP_DIR | /tmp/r_<8hex> | Short hashed temp dir to stay under Ray's 107-byte socket limit. |
TEACHER_PORT / ROCK_PORT | 30055 / 8080 | Teacher SGLang and ROCK admin service ports. |
Source map
| File | Provides |
|---|---|
| rl_engine/train.py | Driver loop, wall-time checks, resume_state.json, multi-pass reshuffle, checkpoint cleanup. |
| rl_engine/utils/ray.py | Re-exports create_placement_groups and TrainRayActor from slime. |
| rl_engine/trainer/factory.py | infer_algorithm, build_gpu_allocations, create_trainer. |
| infra/process_groups.py | ProcessGroupManager — Megatron MPU vs. FSDP WORLD. |
| infra/inference_weight_sync.py | create_weight_updater, DiffusionWeightSyncViaDisk. |
| infra/weight_manager.py | WeightManager — multi-tag backup / restore / switch. |
| infra/worker_data_sync.py | WorkerSync — key-driven inject/exchange NCCL groups. |
| infra/distributed.py | distributed_masked_whiten — global advantage whitening. |
| infra/optimizer.py | create_optimizer, create_lr_scheduler, clip_grad_norm. |
| infra/profiler.py · infra/logging.py | TrainingProfiler, timers; W&B / metric helpers. |
| infra/platform_detect.py | PlatformCapabilities — unified-memory / TE detection. |
| backends/megatron.py | save_model, update_weights, 3-tier sleep/wake_up. |