| import io |
| import logging |
| import time |
| from typing import Optional |
|
|
| import torch |
| import torch.distributed as dist |
|
|
| logger = logging.getLogger(__name__) |
|
|
|
|
| def init_gloo(master_ip: str, port: int, rank: int, world_size: int) -> None: |
| dist.init_process_group( |
| backend="gloo", |
| init_method=f"tcp://{master_ip}:{port}", |
| rank=rank, |
| world_size=world_size, |
| ) |
| logger.info( |
| f"[rank {rank}] GLOO ready — world_size={world_size} master={master_ip}:{port}" |
| ) |
|
|
|
|
| def diloco_outer_step( |
| model: torch.nn.Module, |
| ref_state: dict, |
| velocity: Optional[dict], |
| outer_lr: float = 0.7, |
| outer_momentum: float = 0.9, |
| ) -> dict: |
| t0 = time.time() |
|
|
| params = list(model.parameters()) |
| flat = torch.cat([p.data.view(-1).float() for p in params]) |
| dist.all_reduce(flat, op=dist.ReduceOp.AVG) |
| offset = 0 |
| for p in params: |
| n = p.data.numel() |
| p.data.copy_(flat[offset : offset + n].view_as(p.data).to(p.dtype)) |
| offset += n |
| dist.barrier() |
|
|
| avg_state = {k: v.clone() for k, v in model.state_dict().items()} |
|
|
| if velocity is None: |
| velocity = { |
| k: torch.zeros_like(ref_state[k], dtype=torch.float32) for k in ref_state |
| } |
|
|
| new_state = {} |
| for k in ref_state: |
| ref = ref_state[k].float() |
| avg = avg_state[k].float() |
| pseudo_grad = ref - avg |
|
|
| v_k = outer_momentum * velocity[k] + pseudo_grad |
| new_params = ref - outer_lr * (pseudo_grad + outer_momentum * v_k) |
|
|
| new_state[k] = new_params.to(ref_state[k].dtype) |
| velocity[k] = v_k |
|
|
| model.load_state_dict(new_state, strict=True) |
|
|
| elapsed = time.time() - t0 |
| logger.info(f"GLOO outer sync done in {elapsed:.1f}s") |
| return velocity |
|
|
|
|
| def save_outer_checkpoint( |
| s3_client, |
| bucket: Optional[str], |
| run_name: str, |
| outer_step: int, |
| inner_step: int, |
| node_rank: int, |
| model: torch.nn.Module, |
| optimizer_state: dict, |
| velocity: Optional[dict], |
| ) -> None: |
| if s3_client is None or not bucket: |
| return |
| ckpt = { |
| "outer_step": outer_step, |
| "inner_step": inner_step, |
| "model": {k: v.cpu() for k, v in model.state_dict().items()}, |
| "optimizer": optimizer_state, |
| "velocity": {k: v.cpu() for k, v in velocity.items()} if velocity else None, |
| } |
| buf = io.BytesIO() |
| torch.save(ckpt, buf) |
| key = f"{run_name}/checkpoints/node_{node_rank:04d}/latest.pt" |
| s3_client.put_object(Bucket=bucket, Key=key, Body=buf.getvalue()) |
| logger.info( |
| f"[rank {node_rank}] checkpoint outer_step={outer_step} → s3://{bucket}/{key}" |
| ) |
|
|
|
|
| def load_latest_checkpoint( |
| s3_client, |
| bucket: Optional[str], |
| run_name: str, |
| node_rank: int, |
| ) -> Optional[dict]: |
| if s3_client is None or not bucket: |
| return None |
| key = f"{run_name}/checkpoints/node_{node_rank:04d}/latest.pt" |
| try: |
| obj = s3_client.get_object(Bucket=bucket, Key=key) |
| ckpt = torch.load( |
| io.BytesIO(obj["Body"].read()), map_location="cpu", weights_only=False |
| ) |
| logger.info(f"[rank {node_rank}] resumed from inner_step={ckpt['inner_step']}") |
| return ckpt |
| except Exception: |
| return None |
|
|