lora_A /
lora_B pairs for local merge.
1. Recommended flags
For colocated LoRA training, prefer IPC merge — push onlylora_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 target modules default from the model family’s
TrainPipelineConfig.lora_target_modules. For SD3, see
SD3 model guide.
3. Weight-sync strategies
Selection logic inmiles/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 intoFlattenedTensorBucket 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: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:
FSDPHfWeightIterator.iter_hf_adapter_weights()turns each LoRA module into one unit, its lora_A and lora_B, named byto_hf_name()(e.g.transformer_blocks.0.attn.to_q.lora_A). Fused families such as H3 keep these diffusers names; sglang-dlora_mergeappliesparam_names_mappingand the disk-load FFN swap.- 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. - Rollout engine receives
weight_update_mode="lora_merge"withlora_alphaandlora_rank.
--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)
RolloutManager when spawning engines that merge LoRA.
On the first few syncs, rank 0 logs lines like:
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-pathwith rsLoRA or per-layer rank/alpha are rejected for it. They need--colocatewithout--lora-ipc-weight-sync(train-side merge), which has no NCCL path yet. - One adapter per trained component — every component in
--update-weight-target-modulegets 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.

