Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
12 changes: 7 additions & 5 deletions src/prime_rl/trainer/ckpt.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,11 +154,13 @@ def load_state_dict(self, state_dict: dict[str, Any]):

# state_dict is the same dict object that dcp_load held internally; clearing
# it drops the last references to the loaded tensor wrappers so the cuda
# allocator can release whatever blocks it cached during the read.
if has_cpu_offload:
state_dict.clear()
gc.collect()
torch.cuda.empty_cache()
# allocator can release whatever blocks it cached during the read. The
# non-offload path needs this most: its optimizer state is resident on the
# GPU, and under expandable_segments a cached block stays mapped and so is
# unavailable to NCCL and cuBLAS workspaces until empty_cache returns it.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also think this is all more closely related to memory fragmentation. Unconditionally doing clear/collect/empty_cache is more aggressively dropping references and letting the allocator reset its cache before doing further allocations, which may let the newer allocations be less fragmented and reserve less memory.

Do you have the before/after peak allocated memory numbers, too? Would probably be informative.

state_dict.clear()
gc.collect()
torch.cuda.empty_cache()


class CheckpointManager:
Expand Down
6 changes: 6 additions & 0 deletions src/prime_rl/trainer/sft/train.py
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,12 @@ def is_online_eval_step(step: int) -> bool:
if world.is_master:
prune_broadcasts_beyond(config.run_dir, startup_version)
logger.info(f"Broadcasting startup policy weights (v{startup_version}) for online evals")
# The per-layer gather peaks well above the resident weights, and on a resume
# it lands on the pool the checkpoint load just left behind. Release cached
# blocks first, draining pending work so empty_cache cannot free a block out
# from under a running kernel. Mirrors the RL trainer's broadcast.
torch.cuda.synchronize()
torch.cuda.empty_cache()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mikasenghaas did you test without this addition? Just with the ckpt.py edit? I don't believe this is necessary.

Also don't believe calling empty_cache should be able to free any block that's currently used by a kernel.

weight_sender.broadcast(model, startup_version)

logger.info(f"Starting training loop (max_steps={config.max_steps or 'infinite'})")
Expand Down
Loading