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 StacxTrainer selects a TrainBackend implementation; Megatron and FSDP columns show their internals; both sync weights out to SGLang. StacxTrainer owns the loop · selects backend via --train-backend TrainWorker.init() TrainBackend interface · base.py init train() update_weights() save_model() sleep / wake_up megatron fsdp MegatronBackend backends/megatron.py TP · PP · DP · CP — parallel_state DistributedOptimizer · offload_to_cpu() torch_dist ckpt — load / save / ref-load packed THD · max_tokens_per_gpu FSDPBackend backends/fsdp.py fully_shard · 1-D device mesh (dp,) AdamW · PyTorch DCP checkpoint HF AutoModelForCausalLM grad-accum: skip reduce-scatter update_weights() → NCCL broadcast / CPU shared-mem SGLang rollout engines inference · weight-synced each iteration

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

MethodContract
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

AttributeDescription
modelThe trainable model — a Megatron list[DDP] chunk list, or an FSDP-wrapped HF AutoModelForCausalLM.
ref_modelSeparate frozen reference model. Populated only by FSDP; Megatron uses a weight tag instead (stays None).
optimizer / lr_schedulerMegatron distributed optimizer + its scheduler, or PyTorch AdamW + FSDPLRScheduler.
specializationsDict of role name → ModelSpecialization. No role-string dispatch: the backend just looks up the key.
weight_managerWeightManager for multi-tag backup/restore/switch (actor, ref, teacher, old_actor).
process_groupsProcessGroupManager — Megatron TP/PP/DP/CP groups, or the FSDP 1-D device mesh.
worker_syncWorkerSync for two-sided NCCL exchange syncs between worker groups (used by PPO's value↔policy).
dp/tp/pp/cp _size & _rankParallel 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.pyMegatronBackend, 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.pyFSDPBackend, FSDP v2 on a 1-D data-parallel mesh, HF-native (AutoModelForCausalLMapply_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 — TMSTier 2 — Megatron native defaultTier 3 — PGs only
Mechanismtorch_memory_saver.pause() + destroy PGsoptimizer.offload_to_cpu() + ddp_model.offload_grad_buffers() + destroy PGsDestroy NCCL process groups only
Model weightsPaused to CPU (allocator-level)Stay on GPUStay on GPU
RequiresSTACX_USE_TMS_OFFLOAD=1 + LD_PRELOAD + NCCL_CUMEM_ENABLE=1Megatron distributed optimizer with offload_to_cpuNothing
Selected whenOpt-in onlyOffload 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.

KnobApplies toEffect
--train-backendbothmegatron (default) or fsdp; read in TrainWorker.init().
--tensor-model-parallel-sizeMegatronTP degree; SWE recipes use 4. Paired with --sequence-parallel.
--pipeline-model-parallel-size / --context-parallel-sizeMegatronPP / CP degrees (1 on single node).
MAX_TOKENS_PER_GPU--max-tokens-per-gpuMegatronPer-GPU token budget for dynamic batching. 32768 (4B) / 8192 (8B); lower it for 8B OOM relief.
SGLANG_MEM_FRACTION--sglang-mem-fraction-staticbothSGLang static KV-cache fraction (0.7 / 0.5); a higher fraction leaves less training headroom.
--recompute-granularity / -method / -num-layersMegatronActivation recompute (full / uniform / 1) — trades compute for memory on long trajectories.
STACX_USE_TMS_OFFLOADMegatronOpt into Tier-1 TMS sleep. Requires LD_PRELOAD + NCCL_CUMEM_ENABLE=1; off by default.
ROTARY_BASEMODEL_ARGS_ROTARY_BASE--rotary-baseMegatronRoPE base override; model script is the single emitter. Default 1e6; SWE recipes set 5e6.
--optimizer-cpu-offload / --use-precision-aware-optimizerMegatronOffload optimizer state to CPU; precision-aware master weights.
fsdp_cpu_offloadFSDPAdds CPUOffloadPolicy() — params/grads/optimizer step run on CPU.
--fp16FSDPSwitch param_dtype from bf16 to fp16 in the mixed-precision policy.
--ref-load / --opd-teacher-loadMegatronExtra 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.

Single node · 8 GPUs · Docker — teacher + student containers + host ROCK Teacher container SGLang · Qwen3-Coder-30B-A3B · TP=4 · docker --rm 127.0.0.1:30055 GPU 0 GPU 1 GPU 2 GPU 3 ROCK admin :8080 SWE-Bench sandbox orchestrator sandbox containers (CPU) host process · pulls per-instance images Student container Ray head · Megatron TP=4 trainer + colocated SGLang engine · docker -d · CUDA_VISIBLE_DEVICES=4,5,6,7 Ray head :7796 Megatron TP=4 trainer params + DistributedOptimizer (CPU-offloaded) SGLang student engine KV cache ≤ SGLANG_MEM_FRACTION = 0.7 both roles time-share GPUs 4–7 — 3-tier sleep / wake (§4) GPU 4 GPU 5 GPU 6 GPU 7 Host bind-mounts (docker -v external/…) models/ (ro) ckpts/ results/ logs/ data/ HTTP :30055 — force-decode logprobs (K3) + β-mixed actions ROCK SDK — exec / stdout / patch docker -v: read-write ckpts/results/logs/data · read-only models

Teacher (GPUs 0–3) and student (GPUs 4–7) containers plus host ROCK. The teacher is reused if already healthy.

RoleGPUsProcessWhere set
Teacher0,1,2,3SGLang Qwen3-Coder-30B TP=4 → :30055CUDA_VISIBLE_DEVICES=0,1,2,3 (dagger_4b.sh)
Student4,5,6,7Ray head + Megatron TP=4 + colocated SGLang engineCUDA_VISIBLE_DEVICES=4,5,6,7 (dagger_4b.sh)
ROCK + sandboxesSandbox orchestrator (CPU), pulls per-instance imageshost 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

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 weight-sync paths — trainer → inference server TENSOR — colocated or TP>1 · UpdateWeightFromTensor · SWE default · no cross-node broadcast Megatron trainer backup(actor) CPU weight backup tms.disable() SGLang student engine copy to GPU · update in place DISTRIBUTED — cross-GPU · UpdateWeightFromDistributed Trainer (policy GPUs) NCCL broadcast (per-param) Rollout engine (other GPUs) connect_rollout_engines() builds the NCCL group on new engines DISK — diffusion · DiffusionWeightSyncViaDisk (see Diffusion Pipeline) DP rank 0 save .safetensors transformer/ dir HTTP POST /update_weights_from_disk other ranks barrier · flush_cache=false

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.

resume_state.json (written atomically after every save and on graceful exit; read by the launcher on relaunch):

FieldExampleRole on relaunch
next_rollout_id2--start-rollout-id; completed rollouts skipped
checkpoint_path…/{RUN_TAG}= --save; resume repoints --load here
rollout_ckpt_dir…/rollout_ckptswhere rollout_{N}.pt live
wandb_run_id3f9c1a…--wandb-resume-id, reopens the same run
exit_reason"wall_time"periodic_save / wall_time / completed
resume_count1incremented 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).

Auto-resume across a wall-time boundary wall-time checks — ① pre-rollout · ② mid-rollout (between task batches) · ③ post-training Job 1 Rollout 0train → save iter_* → eval Rollout 1save iter_* → eval Rollout 2in-flight — aborted RESERVE3600 s WALL_TIME_LIMIT 2 ② check trips between task batches → abort, keep partial rollout_2.pt write resume_state.json + flush W&B resume_state.json next_rollout_id: 2 · exit_reason: "wall_time" checkpoint_path: …/${RUN_TAG} wandb_run_id: 3f9c… · resume_count: n+1 completed: false · num_rollout: 10 relaunch (same command) → --start-rollout-id 2 --load = save_dir (latest iter_*) · --wandb-resume-id Job 2 Rollout 0skipped (already saved) Rollout 1skipped Rollout 2regenerated from scratch Rollout 3 → …continues to num_rollout in-flight rollout is not resumed — it restarts; completed rollouts 0–1 are preserved ↑ resume boundary: next_rollout_id = 2

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).

KeyMeaningSource
dagger/betaβ at this rollout (teacher/student execution mix)train.py
train/ce_meanCE loss on teacher-executed action tokensmixed_loss_dagger.py
train/k3_meanK3 reverse-KL on student-executed action tokens
train/teacher_action_fracfraction of action tokens the teacher executed
train/global_steprollout_id × passes + pass_idx — per-pass x-axistrain.py
resume/count, resume/mode_*relaunch counter + fresh/rollout/training modetrain.py
perf/train_time, perf/pass_timeper-rollout and per-pass wall timetrain.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 / flagDefaultEffect
WALL_TIME_LIMIT--wall-time-limit604800Wall-clock budget (s); graceful save + exit before it.
WALL_TIME_RESERVE--wall-time-reserve600Head-room reserved for the final checkpoint save. SWE scripts set 3600.
SAVE_INTERVAL--save-interval1Save a checkpoint every N rollouts.
SAVE_MAX_TO_KEEP--save-max-to-keepNUM_ITERS (GRPO: 3)Retain only the newest K iter_* dirs (~53 GB each).
EVAL_INTERVAL--eval-interval1Eval cadence; skipped if within 2×reserve of the limit.
START_ROLLOUT_ID--start-rollout-idfrom resume_state.jsonFirst 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_PORT30055 / 8080Teacher SGLang and ROCK admin service ports.

Source map

FileProvides
rl_engine/train.pyDriver loop, wall-time checks, resume_state.json, multi-pass reshuffle, checkpoint cleanup.
rl_engine/utils/ray.pyRe-exports create_placement_groups and TrainRayActor from slime.
rl_engine/trainer/factory.pyinfer_algorithm, build_gpu_allocations, create_trainer.
infra/process_groups.pyProcessGroupManager — Megatron MPU vs. FSDP WORLD.
infra/inference_weight_sync.pycreate_weight_updater, DiffusionWeightSyncViaDisk.
infra/weight_manager.pyWeightManager — multi-tag backup / restore / switch.
infra/worker_data_sync.pyWorkerSync — key-driven inject/exchange NCCL groups.
infra/distributed.pydistributed_masked_whiten — global advantage whitening.
infra/optimizer.pycreate_optimizer, create_lr_scheduler, clip_grad_norm.
infra/profiler.py · infra/logging.pyTrainingProfiler, timers; W&B / metric helpers.
infra/platform_detect.pyPlatformCapabilities — unified-memory / TE detection.
backends/megatron.pysave_model, update_weights, 3-tier sleep/wake_up.