kylinfires's picture
Sync main: low-VRAM live test results (624x360)
b9fc164 verified
Raw History Blame Contribute Delete
3.16 kB
"""Shared loss and optimizer handling for distributed training."""
import torch
import torch.distributed as dist
from torch.distributed.tensor import DTensor
from torch.distributed.fsdp import FSDPModule
from src.distributed.communications import all_ranks_finite
def build_token_timesteps(timesteps: torch.Tensor, latents: torch.Tensor, model) -> torch.Tensor:
"""Repeat each patch-frame timestep across its spatial tokens."""
patch_time, patch_height, patch_width = model.config.patch_size
frames, height, width = latents.shape[-3:]
spatial_tokens = (height // patch_height) * (width // patch_width)
return timesteps[:, ::patch_time].unsqueeze(-1).expand(-1, -1, spatial_tokens).reshape(
timesteps.shape[0], (frames // patch_time) * spatial_tokens,
)
def synchronize_batch_frames(batch: dict, device: torch.device) -> dict:
"""Keep chunk-by-chunk forward counts equal when ranks draw different buckets."""
if not dist.is_initialized() or dist.get_world_size() == 1:
return batch
frames = torch.tensor(batch["pixel"].shape[3], dtype=torch.int64, device=device)
dist.all_reduce(frames, op=dist.ReduceOp.MIN)
return {**batch, "pixel": batch["pixel"][:, :, :, :int(frames.item())]}
def backward_if_finite(loss: torch.Tensor, logger, context: str, *, model=None) -> bool:
if not all_ranks_finite(loss):
# A skipped backward leaves FSDP's root parameters unsharded. Restore
# the optimizer's sharded parameter views without dropping prior grads.
if model is not None:
for module in reversed(list(model.modules())):
if isinstance(module, FSDPModule):
module.reshard()
logger.warning(f"{context}: non-finite loss on at least one rank; skipping backward on all ranks.")
return False
loss.backward()
return True
def step_optimizer(optimizer, scheduler, max_grad_norm, successful_backwards: int, logger):
"""Skip empty/non-finite updates and advance the scheduler only after a step."""
if successful_backwards == 0:
optimizer.zero_grad(set_to_none=True)
return None
grad_norm = torch.nn.utils.clip_grad_norm_(
[parameter for group in optimizer.param_groups for parameter in group["params"]],
max_norm=float("inf") if max_grad_norm is None else max_grad_norm,
)
if isinstance(grad_norm, DTensor):
grad_norm = grad_norm.full_tensor()
if all_ranks_finite(grad_norm):
optimizer.step()
scheduler.step()
else:
logger.warning("Non-finite gradient norm on at least one rank; skipping optimizer and scheduler on all ranks.")
optimizer.zero_grad(set_to_none=True)
return grad_norm
def log_bucket_batch_sizes(cfg, world_size: int, logger) -> None:
buckets = list(cfg.bucket_configs or [])
sizes = sorted({bucket[0] for bucket in buckets})
logger.info(f"Per-rank bucket batch sizes: {sizes}")
logger.info(
"Effective batch size per update = sum of rank-local bucket batch sizes "
f"across {world_size} ranks and {cfg.gradient_accumulation_steps} accumulation steps."
)