Skip to main content
Miles-diffusion trains LoRA adapters on the FSDP actor and syncs them to sglang-diffusion rollout engines each iteration. The rollout engine has no PEFT layers — weights arrive either as merged base tensors or as raw lora_A / lora_B pairs for local merge. For colocated LoRA training, prefer IPC merge — push only lora_A / lora_B and let the rollout engine merge locally:
--lora-rank / --lora-alpha vary by recipe (e.g. SD3 uses 32/64; some others use 64/128). Without --lora-ipc-weight-sync, LoRA still trains but merges on the train side and pushes full merged weights (§3). IPC merge requires --colocate. Without --colocate (separate train and rollout GPU pools, e.g. async training), leave out --lora-ipc-weight-sync: LoRA always pushes only lora_A / lora_B over NCCL, and the rollout engine merges them the same way.

2. Key flags

--lora-ipc-weight-sync requires --use-lora, and --colocate whenever weights are synced (any run without --train-only or --debug-rollout-only). Without colocation, CUDA IPC handles cannot cross the train/rollout process boundary; LoRA then always syncs lora_A / lora_B over NCCL, and the flag is rejected.
LoRA target modules default from the model family’s TrainPipelineConfig.lora_target_modules. For SD3, see SD3 model guide.

3. Weight-sync strategies

Selection logic in miles/backends/fsdp_utils/actor.py: rollout_merges_lora(args) in miles/utils/arguments.py is the single check for the last two rows; the engine’s LoRA target modules, the merge precision and the adapter checks (§5) all follow it. Implementation: miles/backends/fsdp_utils/diffusion_update_weight_utils.py.

Full-weight sync (no LoRA)

FSDP shards are all-gathered into FlattenedTensorBucket objects, serialized via CUDA IPC, and sent to the rollout engine’s update_weights_from_tensor(load_format="flattened_bucket").

Train-side merge (LoRA, no IPC)

For each base layer with adapters, the updater computes:
on the fly, strips PEFT key prefixes, and pushes standard weight names that sglang-d expects (e.g. transformer_blocks.0.attn.to_q.weight).

NCCL sync (no --colocate)

Trainer rank 0 and every rollout engine GPU join one NCCL group (trainer rank 0, engine i from rank 1 + i × --rollout-num-gpus-per-engine). For each bucket, rank 0 broadcasts the tensors while the engines receive them through update_weights_from_distributed. This needs an sglang build with distributed weight updates for diffusion engines.

Rollout-side LoRA merge (IPC or NCCL)

DiffusionUpdateWeightFromTensorLoRAIPC (IPC) and DiffusionUpdateWeightLoRADistributed (NCCL) share this path:
  1. FSDPHfWeightIterator.iter_hf_adapter_weights() turns each LoRA module into one unit, its lora_A and lora_B, named by to_hf_name() (e.g. transformer_blocks.0.attn.to_q.lora_A). Fused families such as H3 keep these diffusers names; sglang-d lora_merge applies param_names_mapping and the disk-load FFN swap.
  2. FSDP shard all-gather → pack_units_by_size() packs units into buckets of up to --update-weight-buffer-size (most recipes use 2 GB; a unit is never split, so one larger than that ships as its own bucket) → CUDA IPC or NCCL broadcast.
  3. Rollout engine receives weight_update_mode="lora_merge" with lora_alpha and lora_rank.
Bucket packing: every updater packs units, not individual tensors. When adding the next unit would reach or exceed --update-weight-buffer-size, the current bucket is flushed first; the whole unit then starts the next bucket. A unit is never split across buckets, so a lora_A / lora_B pair always arrives together. For a family whose rollout fuses projections into one layer (H3 to_q/k/v, Qwen-Image and Cosmos3 add_q/k/v_proj), the adapters of those projections form one atomic update group and share a bucket too: sglang-d’s lora_merge replaces a fused layer’s LoRA with the sections one request carries. Constant: LORA_WEIGHT_UPDATE_MODE = "lora_merge". Rollout-side merge precision is controlled by environment variable SGLANG_DIFFUSION_LORA_MERGE_FP32:
  • "1" when --diffusion-forward-dtype fp32
  • "0" otherwise (no fp32 upcast: the engine merges in its own weight dtype, e.g. bf16)
Set automatically in RolloutManager when spawning engines that merge LoRA. On the first few syncs, rank 0 logs lines like:
After FSDP all-gather, IPC buckets are collected on the gather-src rank only, and that rank calls the rollout engine; over NCCL, trainer rank 0 broadcasts. If sync stalls or VRAM grows across rollouts, check trainer logs for LoRA weight sync lines and Ray worker stderr under ~/.ray/session_latest/logs/.

4. Internals

5. Limitations

  • Rollout-side merge expresses only uniform standard LoRA — adapters from --lora-adapter-path with rsLoRA or per-layer rank/alpha are rejected for it. They need --colocate without --lora-ipc-weight-sync (train-side merge), which has no NCCL path yet.
  • One adapter per trained component — every component in --update-weight-target-module gets its own adapter, all from one set of --lora-* flags per job.
  • FSDP backend — LoRA weight sync is implemented for the FSDP diffusion actor; Megatron LLM LoRA (in upstream Miles) uses a separate path.