mirror of
https://github.com/Alibaba-Quark/LiveAvatar.git
synced 2026-08-28 14:56:01 +08:00
229 lines
8.1 KiB
Python
229 lines
8.1 KiB
Python
from regex import F
|
|
from torch.distributed.fsdp.wrap import transformer_auto_wrap_policy
|
|
from torch.distributed.fsdp.wrap import size_based_auto_wrap_policy
|
|
from torch.distributed.fsdp.wrap import ModuleWrapPolicy
|
|
from torch.distributed.fsdp import (
|
|
FullStateDictConfig,
|
|
StateDictType,
|
|
MixedPrecision,
|
|
ShardingStrategy,
|
|
FullyShardedDataParallel as FSDP
|
|
)
|
|
from torchvision.utils import make_grid
|
|
from datetime import timedelta, datetime
|
|
import torch.distributed as dist
|
|
from omegaconf import OmegaConf
|
|
from functools import partial
|
|
import numpy as np
|
|
import random
|
|
import torch
|
|
import os
|
|
|
|
|
|
def launch_distributed_job(backend: str = "nccl"):
|
|
rank = int(os.environ["RANK"])
|
|
local_rank = int(os.environ["LOCAL_RANK"])
|
|
world_size = int(os.environ["WORLD_SIZE"])
|
|
host = os.environ["MASTER_ADDR"]
|
|
port = int(os.environ["MASTER_PORT"])
|
|
|
|
if ":" in host: # IPv6
|
|
init_method = f"tcp://[{host}]:{port}"
|
|
else: # IPv4
|
|
init_method = f"tcp://{host}:{port}"
|
|
dist.init_process_group(rank=rank, world_size=world_size, backend=backend,
|
|
init_method=init_method, timeout=timedelta(minutes=30))
|
|
torch.cuda.set_device(local_rank)
|
|
|
|
|
|
def set_seed(seed: int, deterministic: bool = False):
|
|
"""
|
|
Helper function for reproducible behavior to set the seed in `random`, `numpy`, `torch`.
|
|
|
|
Args:
|
|
seed (`int`):
|
|
The seed to set.
|
|
deterministic (`bool`, *optional*, defaults to `False`):
|
|
Whether to use deterministic algorithms where available. Can slow down training.
|
|
"""
|
|
random.seed(seed)
|
|
np.random.seed(seed)
|
|
torch.manual_seed(seed)
|
|
torch.cuda.manual_seed_all(seed)
|
|
|
|
if deterministic:
|
|
torch.use_deterministic_algorithms(True)
|
|
|
|
|
|
def init_logging_folder(args):
|
|
import wandb
|
|
date = str(datetime.now()).replace(" ", "-").replace(":", "-")
|
|
output_path = os.path.join(
|
|
args.output_path,
|
|
f"{date}_seed{args.seed}"
|
|
)
|
|
os.makedirs(output_path, exist_ok=False)
|
|
|
|
os.makedirs(args.output_path, exist_ok=True)
|
|
wandb.login(host=args.wandb_host, key=args.wandb_key)
|
|
run = wandb.init(config=OmegaConf.to_container(args, resolve=True), dir=args.output_path, **
|
|
{"mode": "online", "entity": args.wandb_entity, "project": args.wandb_project})
|
|
wandb.run.log_code(".")
|
|
wandb.run.name = args.wandb_name
|
|
print(f"run dir: {run.dir}")
|
|
wandb_folder = run.dir
|
|
os.makedirs(wandb_folder, exist_ok=True)
|
|
|
|
return output_path, wandb_folder
|
|
|
|
|
|
def fsdp_wrap(module, sharding_strategy="full", mixed_precision=False, wrap_strategy="size", min_num_params=int(5e7), transformer_module=None):
|
|
if mixed_precision:
|
|
mixed_precision_policy = MixedPrecision(
|
|
param_dtype=torch.bfloat16,
|
|
reduce_dtype=torch.float32,
|
|
buffer_dtype=torch.float32,
|
|
cast_forward_inputs=False
|
|
)
|
|
else:
|
|
mixed_precision_policy = None
|
|
|
|
if wrap_strategy == "transformer":
|
|
auto_wrap_policy = partial(
|
|
transformer_auto_wrap_policy,
|
|
transformer_layer_cls=transformer_module
|
|
)
|
|
elif wrap_strategy == "size":
|
|
auto_wrap_policy = partial(
|
|
size_based_auto_wrap_policy,
|
|
min_num_params=min_num_params
|
|
)
|
|
else:
|
|
raise ValueError(f"Invalid wrap strategy: {wrap_strategy}")
|
|
|
|
os.environ["NCCL_CROSS_NIC"] = "1"
|
|
|
|
sharding_strategy = {
|
|
"full": ShardingStrategy.FULL_SHARD,
|
|
"hybrid_full": ShardingStrategy.HYBRID_SHARD,
|
|
"hybrid_zero2": ShardingStrategy._HYBRID_SHARD_ZERO2,
|
|
"no_shard": ShardingStrategy.NO_SHARD,
|
|
}[sharding_strategy]
|
|
|
|
module = FSDP(
|
|
module,
|
|
auto_wrap_policy=auto_wrap_policy,
|
|
sharding_strategy=sharding_strategy,
|
|
mixed_precision=mixed_precision_policy,
|
|
device_id=torch.cuda.current_device(),
|
|
limit_all_gathers=True,
|
|
sync_module_states=False # Load ckpt on rank 0 and sync to other ranks
|
|
)
|
|
return module
|
|
|
|
|
|
def cycle(dl):
|
|
while True:
|
|
for data in dl:
|
|
yield data
|
|
|
|
|
|
def fsdp_state_dict(model):
|
|
fsdp_fullstate_save_policy = FullStateDictConfig(
|
|
offload_to_cpu=True, rank0_only=True
|
|
)
|
|
with FSDP.state_dict_type(
|
|
model, StateDictType.FULL_STATE_DICT, fsdp_fullstate_save_policy
|
|
):
|
|
checkpoint = model.state_dict()
|
|
|
|
return checkpoint
|
|
|
|
|
|
def barrier():
|
|
if dist.is_initialized():
|
|
dist.barrier()
|
|
|
|
|
|
def prepare_for_saving(tensor, fps=16, caption=None):
|
|
import wandb
|
|
# Convert range [-1, 1] to [0, 1]
|
|
tensor = (tensor * 0.5 + 0.5).clamp(0, 1).detach()
|
|
|
|
if tensor.ndim == 4:
|
|
# Assuming it's an image and has shape [batch_size, 3, height, width]
|
|
tensor = make_grid(tensor, 4, padding=0, normalize=False)
|
|
return wandb.Image((tensor * 255).cpu().numpy().astype(np.uint8), caption=caption)
|
|
elif tensor.ndim == 5:
|
|
# Assuming it's a video and has shape [batch_size, num_frames, 3, height, width]
|
|
return wandb.Video((tensor * 255).cpu().numpy().astype(np.uint8), fps=fps, format="webm", caption=caption)
|
|
else:
|
|
raise ValueError("Unsupported tensor shape for saving. Expected 4D (image) or 5D (video) tensor.")
|
|
|
|
def validate_tensor(tensor, expected_shape, expected_dtype, expected_device):
|
|
"""验证单个tensor的形状、dtype和device"""
|
|
try:
|
|
if not isinstance(tensor, torch.Tensor):
|
|
raise Exception("tensor is not a torch.Tensor")
|
|
if tensor.shape != expected_shape:
|
|
raise Exception("tensor shape is not expected")
|
|
if tensor.dtype != expected_dtype:
|
|
raise Exception("tensor dtype is not expected")
|
|
if tensor.device != expected_device:
|
|
raise Exception("tensor device is not expected")
|
|
except Exception as e:
|
|
# import pdb;pdb.set_trace()
|
|
print(f"error: {e}")
|
|
return False
|
|
return True
|
|
|
|
|
|
def check_input(noise_input,timestep,arg_c):
|
|
"""
|
|
验证输入参数的tensor是否符合预期规格
|
|
|
|
Args:
|
|
arg_c: 包含各种tensor的字典
|
|
|
|
Returns:
|
|
bool: 所有验证通过返回True,否则返回False
|
|
"""
|
|
try:
|
|
bs = noise_input.__len__() # batch_size
|
|
noise_input = noise_input[0] # [c,f,h,w]
|
|
f = noise_input.shape[1] # frames
|
|
h = noise_input.shape[2] # height
|
|
w = noise_input.shape[3] # width
|
|
motion_frames_rgb = arg_c["motion_frames"][0]
|
|
motion_frames_latent = arg_c["motion_frames"][1]
|
|
|
|
expected_dtype = noise_input.dtype
|
|
expected_device = noise_input.device
|
|
|
|
validation_configs = [
|
|
("cond_states", (bs, 16, f, h, w), True),
|
|
("ref_latents", (bs, 16,1, h, w), True),
|
|
("motion_latents", (bs, 16, motion_frames_latent, h, w), False),
|
|
("audio_input", (bs, 25, 1024, 4*f), True),#数据集里 1的部分在 motion_frames,后面都是 4n
|
|
("context", (bs, 512, 4096), True),
|
|
]
|
|
if validate_tensor(timestep, (bs, f), expected_dtype, expected_device) is False:
|
|
print(f"input timestep, shape {timestep.shape}, dtype {timestep.dtype}, device {timestep.device} is not valid")
|
|
print(f"expected shape {bs, f}, expected dtype {expected_dtype}, expected device {expected_device}")
|
|
return False
|
|
|
|
for key, expected_shape, is_required in validation_configs:
|
|
if key in arg_c:
|
|
if not validate_tensor(arg_c[key], expected_shape, expected_dtype, expected_device):
|
|
print(f"input {key}, shape {arg_c[key].shape}, dtype {arg_c[key].dtype}, device {arg_c[key].device} is not valid")
|
|
print(f"expected shape {expected_shape}, expected dtype {expected_dtype}, expected device {expected_device}")
|
|
return False
|
|
elif is_required:
|
|
print(f"input {key} is required, but not found")
|
|
return False
|
|
print(f"input {key} is required, but not found")
|
|
except Exception as e:
|
|
print(f"error: {e}")
|
|
return False
|
|
|
|
return True |