Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion deps/renderers
2 changes: 1 addition & 1 deletion deps/verifiers
15 changes: 15 additions & 0 deletions src/prime_rl/trainer/rl/broadcast/nccl.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ def broadcast_state_dict(state_dict: dict[str, Tensor], communicator: PyNcclComm
# Flatten all tensors and concatenate
flat_tensors = [value.flatten() for _, value in items]
concatenated = torch.cat(flat_tensors)
if not concatenated.is_cuda:
# fsdp_cpu_offload keeps parameters on CPU; NCCL requires device tensors,
# so stage the (per-layer, per-dtype) buffer through CUDA transiently.
concatenated = concatenated.cuda()
communicator.broadcast(concatenated, src=0)
del concatenated
# Clean up individual tensors
Expand Down Expand Up @@ -164,6 +168,17 @@ def broadcast_weights(self, model: nn.Module, step: int) -> None:
def _resolve_dtensors(self, state_dict: dict[str, Tensor]) -> dict[str, Tensor]:
for key, value in list(state_dict.items()):
if isinstance(value, DTensor):
local = value.to_local()
if local.device.type != "cuda":
# FSDP CPU offload keeps local shards on CPU (pinned); gathering
# them there would run over Gloo (hours for 100B+ params). Stage
# the shard to CUDA asynchronously and rebuild the DTensor on the
# same (CUDA) mesh so full_tensor() gathers over NCCL. The bf16
# cast happens on GPU, after the copy.
local = local.to("cuda", non_blocking=True)
value = DTensor.from_local(
local, device_mesh=value.device_mesh, placements=value.placements, run_check=False
)
state_dict[key] = cast(DTensor, value.to(self.dtype)).full_tensor()
return state_dict

Expand Down
Loading