swag-train-bundle / Transfer_and_Test /src /train_controlnet_two_condition.py
DancingNow's picture
Add files using upload-large-folder tool
923fdff verified
Raw History Blame Contribute Delete
9.37 kB
from __future__ import annotations
import argparse
import os
import random
from pathlib import Path
import numpy as np
import torch
import torch.distributed as dist
from torch.nn.parallel import DistributedDataParallel
from torch.utils.data import DataLoader
from torch.utils.data.distributed import DistributedSampler
from tqdm import tqdm
from config import load_config, resolve_path
from diffusion import create_diffusion
from two_condition_dataset import TwoConditionDataset
from models_controlnet_two_condition import TwoConditionControlNetSWaG
# This script is bundled directly under <bundle>/src on the server.
ROOT = Path(__file__).resolve().parents[1]
def distributed_context() -> tuple[int, int, int]:
world_size = int(os.environ.get("WORLD_SIZE", "1"))
rank = int(os.environ.get("RANK", "0"))
local_rank = int(os.environ.get("LOCAL_RANK", "0"))
if world_size > 1:
dist.init_process_group("nccl")
return rank, local_rank, world_size
def save_checkpoint(path, model, optimizer, epoch, step, config, include_optimizer: bool):
source = model.module if isinstance(model, DistributedDataParallel) else model
payload = {
"model": source.state_dict(),
"epoch": epoch,
"step": step,
"config": config,
}
if include_optimizer:
payload["optimizer"] = optimizer.state_dict()
temporary = path.with_suffix(".tmp")
torch.save(payload, temporary)
temporary.replace(path)
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--config", required=True)
parser.add_argument("--smoke", action="store_true")
parser.add_argument("--target-epoch", type=int, default=0)
parser.add_argument("--resume", type=Path, default=None)
args = parser.parse_args()
if not torch.cuda.is_available():
raise RuntimeError("CUDA is required")
rank, local_rank, world_size = distributed_context()
device = torch.device("cuda", local_rank)
torch.cuda.set_device(device)
config_path = Path(args.config).resolve()
config = load_config(config_path)
training = config["training"]
seed = int(training.get("seed", 0)) + rank
random.seed(seed)
np.random.seed(seed)
torch.manual_seed(seed)
torch.cuda.manual_seed_all(seed)
output = resolve_path(ROOT, config["output"]["directory"])
checkpoint_dir = output / "checkpoints"
if rank == 0:
checkpoint_dir.mkdir(parents=True, exist_ok=True)
(output / "config.yaml").write_text(config_path.read_text(encoding="utf-8"), encoding="utf-8")
if world_size > 1:
dist.barrier()
dataset = TwoConditionDataset(
resolve_path(ROOT, config["data"]["train_h5"]),
cache_in_memory=bool(training.get("cache_in_memory", False)),
)
global_batch = int(training["global_batch_size"])
if global_batch % world_size:
raise ValueError(f"global_batch_size must be divisible by world_size ({world_size})")
local_batch = global_batch // world_size
sampler = DistributedSampler(dataset, shuffle=True, seed=int(training.get("seed", 0))) if world_size > 1 else None
loader = DataLoader(
dataset,
batch_size=local_batch,
shuffle=sampler is None,
sampler=sampler,
num_workers=int(training.get("num_workers", 0)),
pin_memory=True,
drop_last=True,
)
model = TwoConditionControlNetSWaG(**config["model"])
start_epoch = 0
step = 0
resume_checkpoint = args.resume.resolve() if args.resume else None
if resume_checkpoint is not None:
checkpoint = torch.load(resume_checkpoint, map_location="cpu", weights_only=False)
model.load_state_dict(checkpoint["model"], strict=True)
start_epoch = int(checkpoint.get("epoch", 0))
step = int(checkpoint.get("step", 0))
else:
checkpoint_path = resolve_path(ROOT, training["base_checkpoint"])
checkpoint = torch.load(checkpoint_path, map_location="cpu", weights_only=False)
base_state = checkpoint["model"]
result = model.load_state_dict(base_state, strict=False)
allowed_missing = (
"p_embedder.",
"s_embedder.",
"condition_mlp.",
"control_input_zero.",
"control_mid_input_zero.",
"control_large_input_zero.",
"control_blocks.",
"control_zero.",
"control_mid_zero.",
"control_large_zero.",
)
unexpected = list(result.unexpected_keys)
missing = [key for key in result.missing_keys if not key.startswith(allowed_missing)]
if unexpected or missing:
raise RuntimeError(f"checkpoint mismatch: unexpected={unexpected[:8]} missing={missing[:8]}")
model.copy_backbone_blocks_to_control()
train_prefixes = (
"p_embedder.",
"s_embedder.",
"condition_mlp.",
"control_input_zero.",
"control_mid_input_zero.",
"control_large_input_zero.",
"control_blocks.",
"control_zero.",
"control_mid_zero.",
"control_large_zero.",
)
for name, parameter in model.named_parameters():
parameter.requires_grad_(name.startswith(train_prefixes))
trainable = [parameter for parameter in model.parameters() if parameter.requires_grad]
if rank == 0:
print(f"resume_checkpoint={resume_checkpoint}")
print(f"world_size={world_size} global_batch={global_batch} per_gpu_batch={local_batch}")
print(f"control_depth={model.control_depth}")
print(f"trainable_parameters={sum(p.numel() for p in trainable):,}")
print(f"frozen_parameters={sum(p.numel() for p in model.parameters() if not p.requires_grad):,}")
model.to(device)
if world_size > 1:
model = DistributedDataParallel(model, device_ids=[local_rank], output_device=local_rank)
optimizer = torch.optim.AdamW(
trainable,
lr=float(training["learning_rate"]),
weight_decay=float(training.get("weight_decay", 0.0)),
)
diffusion = create_diffusion(timestep_respacing="", **config["diffusion"])
use_amp = bool(training.get("use_amp", True))
if hasattr(torch, "amp") and hasattr(torch.amp, "GradScaler"):
scaler = torch.amp.GradScaler("cuda", enabled=use_amp)
else:
scaler = torch.cuda.amp.GradScaler(enabled=use_amp)
if resume_checkpoint is not None and "optimizer" in checkpoint:
optimizer.load_state_dict(checkpoint["optimizer"])
elif resume_checkpoint is not None:
print("WARNING: resume checkpoint has no optimizer state; optimizer restarts fresh.")
del checkpoint
epochs = 1 if args.smoke else (int(args.target_epoch) if args.target_epoch else int(training["epochs"]))
if epochs < start_epoch:
raise ValueError(f"target epoch {epochs} is before checkpoint epoch {start_epoch}")
optimizer.zero_grad(set_to_none=True)
for epoch in range(start_epoch, epochs):
if sampler is not None:
sampler.set_epoch(epoch)
model.train()
total_loss = 0.0
for batch_index, (waveforms, labels) in enumerate(
tqdm(
loader,
desc=f"epoch {epoch + 1}/{epochs}",
disable=rank != 0 or not bool(training.get("progress_bar", True)),
)
):
waveforms = waveforms.to(device, non_blocking=True)
labels = labels.to(device, non_blocking=True)
timesteps = torch.randint(
0, diffusion.num_timesteps, (waveforms.shape[0],), device=device
)
with torch.autocast("cuda", enabled=use_amp, dtype=torch.float16):
loss = diffusion.training_losses(
model, waveforms, timesteps, model_kwargs={"labels": labels}
)["loss"].mean()
scaler.scale(loss).backward()
max_grad_norm = float(training.get("max_grad_norm", 0.0))
if max_grad_norm > 0:
scaler.unscale_(optimizer)
torch.nn.utils.clip_grad_norm_(trainable, max_grad_norm)
scaler.step(optimizer)
scaler.update()
optimizer.zero_grad(set_to_none=True)
step += 1
total_loss += float(loss.detach())
if args.smoke:
break
batches = 1 if args.smoke else len(loader)
mean_loss = torch.tensor(total_loss / batches, device=device)
if world_size > 1:
dist.all_reduce(mean_loss, op=dist.ReduceOp.SUM)
mean_loss /= world_size
if rank == 0:
print(f"epoch={epoch + 1} loss={mean_loss.item():.6f}")
interval = int(config["output"].get("checkpoint_every_epochs", 10))
if rank == 0 and not args.smoke and ((epoch + 1) % interval == 0 or epoch + 1 == epochs):
save_checkpoint(
checkpoint_dir / f"checkpoint_epoch_{epoch + 1:05d}.pt",
model,
optimizer,
epoch + 1,
step,
config,
include_optimizer=bool(config["output"].get("save_optimizer_state", True)),
)
if world_size > 1:
dist.barrier()
dataset.close()
if world_size > 1:
dist.destroy_process_group()
if __name__ == "__main__":
main()