[Bugfix] stabilize actor offload wake/sleep flow (follow-up to #48) - #53
[Bugfix] stabilize actor offload wake/sleep flow (follow-up to #48)#53CalvinXKY wants to merge 1 commit into
Conversation
There was a problem hiding this comment.
Code Review
This pull request updates the weight synchronization logic to use a dedicated Gloo process group (_ipc_slot_group) covering the entire engine GPU slot, rather than relying on the Megatron tensor parallel group. It also introduces corresponding unit tests and minor lifecycle fixes in the actor backend. However, a critical issue was identified where dist.new_group is called within a rank-conditional block; because it is a collective operation, this will lead to a distributed deadlock. All ranks must invoke dist.new_group collectively.
| slot_ranks = list(range(start, end)) | ||
| self._ipc_slot_group = dist.new_group(ranks=slot_ranks, backend="gloo") |
There was a problem hiding this comment.
In PyTorch distributed, dist.new_group is a collective call that must be invoked by all ranks in the default process group (the entire world), even if they are not going to be members of the new group.
Calling dist.new_group inside the rank-conditional block if start <= rank < end: means only a subset of ranks will execute it, which will cause a distributed hang/deadlock during initialization when there are multiple colocated engines or when some ranks do not belong to the current engine's GPU slot.
To fix this, you should create the Gloo group collectively for all ranks outside of the conditional block:
for i, engine in enumerate(self._colocated_engines):
start = colocate_gpu_offsets[i]
end = start + colocate_gpu_counts[i]
slot_ranks = list(range(start, end))
# Every rank in the default group must call new_group collectively
slot_group = dist.new_group(ranks=slot_ranks, backend="gloo")
rank = dist.get_rank()
if start <= rank < end:
self._ipc_engine = engine
self._ipc_engine_slot_start = start
self._ipc_engine_slot_end = end
self._ipc_slot_group = slot_group
# First global rank in the engine GPU slot (not Megatron TP rank 0).
if rank == start:
self._ipc_engine_coordinator = True90cd38c to
5810073
Compare
knlnguyen1802
left a comment
There was a problem hiding this comment.
LGTM, thanks for the fix
|
I don't see slime& miles do these modification, wonder if the error is just because of experiment setting problem. |
Sounds reasonable. I'll reproduce it. |
|
Closure Note
|

Summary
Follow-up to #48. This PR keeps the actor-side stability fix on top of #48's IPC contract/coordinator/gather changes.
Scope in this PR:
slime/backends/megatron_utils/actor.pyself._switch_model("actor"))Related IPC correctness is handled in #48 (coordinator = slot-start rank, slot-wide gloo gather, single-RPC version-with-data).
Test case:
tests/test_qwen3_30B_A3B.pyon A800, 8× colocate, Megatron TP=4,rollout-num-gpus-per-engine=8.Failure mode
We hit a chain of failures while bringing up colocated IPC weight sync for Qwen3-30B-A3B. Below are the observed symptoms, NFS log paths, and key excerpts.
0) Checkpoint load appeared stuck (NFS mount)
Symptom: training looked hung at Megatron load:
Cause:
/root/Qwen3-30B-A3B_torch_distwas a symlink to NFS (/data/nfs_87/xky/models/...), so ~57Gtorch_distwas read over the mount.Mitigation (operational, not in this PR): rsync checkpoint to container local disk (
/root/local_models/Qwen3-30B-A3B_torch_dist) before training.Log:
/data/nfs_87/xky/logs/test_qwen3_30B_A3B_20260527_074737.log1) Duplicate
start_weight_updatein colocate IPC pathSymptom:
Root cause: for one 8-GPU vLLM engine with Megatron TP=4, coordinator gating used
tp_rank == 0. Both global rank 0 and rank 4 could enter the coordinator path and callstart_weight_updatetwice.Fix: #48 - gate coordinator on
rank == slot_start(lowest global rank in the engine GPU slot).Log:
/data/nfs_87/xky/logs/test_qwen3_30B_A3B_20260527_025345.log(around line 1823)2) Missing IPC handles during tensor transfer
Symptom:
Root cause: IPC payloads were gathered only within the Megatron TP subgroup (4 ranks), but the 8-GPU vLLM engine needs handles from the full engine slot (8 ranks).
Fix: #48 - build per-slot gloo group and
all_gather_objectacross the full slot, not TP subgroup.Log:
/data/nfs_87/xky/logs/test_qwen3_30B_A3B_20260527_031623.log(line 1809)3)
torch_memory_savercrash after first successful weight syncSymptom: Ray actor workers died right after
update_weightsreached 100%:Context: first IPC sync completed (
Update weights: 100%|...| 115/115,finish_weight_update200 OK), then crash during offload/sleep transition after rollout 0 train.Fix: this PR (
actor.py) - stabilize sleep/wake: clear routing replay, avoid unconditional PG destroy on colocate sleep, restore actor model tag after wake-up.Log:
/data/nfs_87/xky/logs/test_qwen3_30B_A3B_20260527_080824.log(lines 2823, 2853)4) Ray host-memory kill during second
update_weightsSymptom:
Context: not GPU OOM - Ray killed workers when host RAM crossed the default 95% threshold during the second post-train weight sync.
Mitigation (operational): raise
RAY_memory_usage_threshold=0.99when starting isolated Ray in the run script.Log:
/data/nfs_87/xky/logs/test_qwen3_30B_A3B_20260527_092114.log(line 2700)Fix (this PR)
slime/backends/megatron_utils/actor.pyRoutingReplaybefore offload sleep when--use-routing-replaydestroy_process_groups()in sleep when non-colocate critic reconnect path applieswake_up(), callself._switch_model("actor")so the active model tag is restoredWhy actor.py change is needed (with #48)
#48 fixes IPC coordinator selection and slot-wide handle gather. Even after IPC sync succeeds, colocate +
offload_trainstill needs a stable actor sleep/wake path. Without these actor adjustments we saw intermittenttorch_memory_saver/ActorDiedErrorcrashes after successful weight updates.Test plan
pre-commit run --all-filespython -m pytest tests/unit/backends/megatron_utils/update_weight/test_update_weight_from_tensor.py -q- 7 passedSuccessful run after fixes:
Log:
/data/nfs_87/xky/logs/test_qwen3_30B_A3B_20260527_094318.log