Download code_snapshot/sample_ddp.py from BlueSourceJY/SiT-Complementary: direct link, hf CLI and curl.
- Browser
- Download file 10.7 kB
-
https://huggingface.co/BlueSourceJY/SiT-Complementary/resolve/main/code_snapshot/sample_ddp.py
- Command line
-
hf download hf://BlueSourceJY/SiT-Complementary/code_snapshot/sample_ddp.py
-
curl -L -o sample_ddp.py https://huggingface.co/BlueSourceJY/SiT-Complementary/resolve/main/code_snapshot/sample_ddp.py
10.7 kB
| # This source code is licensed under the license found in the | |
| # LICENSE file in the root directory of this source tree. | |
| """ | |
| Samples a large number of images from a pre-trained SiT model using DDP. | |
| Subsequently saves a .npz file that can be used to compute FID and other | |
| evaluation metrics via the ADM repo: https://github.com/openai/guided-diffusion/tree/main/evaluations | |
| For a simple single-GPU/CPU sampling script, see sample.py. | |
| """ | |
| import importlib | |
| import os | |
| import torch | |
| import torch.distributed as dist | |
| MODEL_MODULE_NAME = os.environ.get("SIT_MODEL_MODULE", "models") | |
| model_module = importlib.import_module(MODEL_MODULE_NAME) | |
| SiT_models = model_module.SiT_models | |
| MODEL_IMPLEMENTATION_PATH = os.path.realpath(model_module.__file__) | |
| from download import find_model | |
| from transport import create_transport, Sampler | |
| from diffusers.models import AutoencoderKL | |
| from train_utils import parse_ode_args, parse_sde_args, parse_transport_args | |
| from tqdm import tqdm | |
| from PIL import Image | |
| import numpy as np | |
| import math | |
| import argparse | |
| import sys | |
| def create_npz_from_sample_folder(sample_dir, num=50_000): | |
| """ | |
| Builds a single .npz file from a folder of .png samples. | |
| """ | |
| samples = [] | |
| for i in tqdm(range(num), desc="Building .npz file from samples"): | |
| sample_pil = Image.open(f"{sample_dir}/{i:06d}.png") | |
| sample_np = np.asarray(sample_pil).astype(np.uint8) | |
| samples.append(sample_np) | |
| samples = np.stack(samples) | |
| assert samples.shape == (num, samples.shape[1], samples.shape[2], 3) | |
| npz_path = f"{sample_dir}.npz" | |
| np.savez(npz_path, arr_0=samples) | |
| print(f"Saved .npz file to {npz_path} [shape={samples.shape}].") | |
| return npz_path | |
| def main(mode, args): | |
| """ | |
| Run sampling. | |
| """ | |
| torch.backends.cuda.matmul.allow_tf32 = args.tf32 # True: fast but may lead to some small numerical differences | |
| assert torch.cuda.is_available(), "Sampling with DDP requires at least one GPU. sample.py supports CPU-only usage" | |
| expected_model_module = os.environ.get("SIT_EXPECTED_MODEL_MODULE") | |
| if expected_model_module and MODEL_MODULE_NAME != expected_model_module: | |
| raise RuntimeError( | |
| f"Expected model module {expected_model_module!r}, but loaded " | |
| f"{MODEL_MODULE_NAME!r} from {MODEL_IMPLEMENTATION_PATH}" | |
| ) | |
| torch.set_grad_enabled(False) | |
| # Setup DDP: | |
| dist.init_process_group("nccl") | |
| rank = dist.get_rank() | |
| device = rank % torch.cuda.device_count() | |
| seed = args.global_seed * dist.get_world_size() + rank | |
| torch.manual_seed(seed) | |
| torch.cuda.set_device(device) | |
| print(f"Starting rank={rank}, seed={seed}, world_size={dist.get_world_size()}.") | |
| if rank == 0: | |
| print(f"Model implementation: {MODEL_MODULE_NAME} ({MODEL_IMPLEMENTATION_PATH})") | |
| if args.ckpt is None: | |
| assert args.model == "SiT-XL/2", "Only SiT-XL/2 models are available for auto-download." | |
| assert args.image_size in [256, 512] | |
| assert args.num_classes == 1000 | |
| assert args.image_size == 256, "512x512 models are not yet available for auto-download." # remove this line when 512x512 models are available | |
| learn_sigma = args.image_size == 256 | |
| else: | |
| # train.py constructs custom checkpoints with the model default | |
| # learn_sigma=True, so preserve that architecture for strict loading. | |
| learn_sigma = True | |
| # Load model: | |
| latent_size = args.image_size // 8 | |
| model = SiT_models[args.model]( | |
| input_size=latent_size, | |
| num_classes=args.num_classes, | |
| learn_sigma=learn_sigma, | |
| ).to(device) | |
| # Auto-download a pre-trained model or load a custom SiT checkpoint from train.py: | |
| ckpt_path = args.ckpt or f"SiT-XL-2-{args.image_size}x{args.image_size}.pt" | |
| state_dict = find_model(ckpt_path) | |
| model.load_state_dict(state_dict) | |
| model.eval() # important! | |
| transport = create_transport( | |
| args.path_type, | |
| args.prediction, | |
| args.loss_weight, | |
| args.train_eps, | |
| args.sample_eps | |
| ) | |
| sampler = Sampler(transport) | |
| if mode == "ODE": | |
| if args.likelihood: | |
| assert args.cfg_scale == 1, "Likelihood is incompatible with guidance" | |
| sample_fn = sampler.sample_ode_likelihood( | |
| sampling_method=args.sampling_method, | |
| num_steps=args.num_sampling_steps, | |
| atol=args.atol, | |
| rtol=args.rtol, | |
| ) | |
| else: | |
| sample_fn = sampler.sample_ode( | |
| sampling_method=args.sampling_method, | |
| num_steps=args.num_sampling_steps, | |
| atol=args.atol, | |
| rtol=args.rtol, | |
| reverse=args.reverse | |
| ) | |
| elif mode == "SDE": | |
| sample_fn = sampler.sample_sde( | |
| sampling_method=args.sampling_method, | |
| diffusion_form=args.diffusion_form, | |
| diffusion_norm=args.diffusion_norm, | |
| last_step=args.last_step, | |
| last_step_size=args.last_step_size, | |
| num_steps=args.num_sampling_steps, | |
| ) | |
| vae = AutoencoderKL.from_pretrained(f"stabilityai/sd-vae-ft-{args.vae}").to(device) | |
| assert args.cfg_scale >= 1.0, "In almost all cases, cfg_scale be >= 1.0" | |
| using_cfg = args.cfg_scale > 1.0 | |
| # Create folder to save samples: | |
| model_string_name = args.model.replace("/", "-") | |
| ckpt_string_name = os.path.basename(args.ckpt).replace(".pt", "") if args.ckpt else "pretrained" | |
| if mode == "ODE": | |
| folder_name = f"{model_string_name}-{ckpt_string_name}-" \ | |
| f"cfg-{args.cfg_scale}-{args.per_proc_batch_size}-"\ | |
| f"{mode}-{args.num_sampling_steps}-{args.sampling_method}" | |
| elif mode == "SDE": | |
| folder_name = f"{model_string_name}-{ckpt_string_name}-" \ | |
| f"cfg-{args.cfg_scale}-{args.per_proc_batch_size}-"\ | |
| f"{mode}-{args.num_sampling_steps}-{args.sampling_method}-"\ | |
| f"{args.diffusion_form}-{args.last_step}-{args.last_step_size}" | |
| sample_folder_dir = f"{args.sample_dir}/{folder_name}" | |
| if rank == 0: | |
| os.makedirs(sample_folder_dir, exist_ok=True) | |
| print(f"Saving .png samples at {sample_folder_dir}") | |
| dist.barrier() | |
| # Figure out how many samples we need to generate on each GPU and how many iterations we need to run: | |
| n = args.per_proc_batch_size | |
| global_batch_size = n * dist.get_world_size() | |
| # To make things evenly-divisible, we'll sample a bit more than we need and then discard the extra samples: | |
| num_samples = len([name for name in os.listdir(sample_folder_dir) if (os.path.isfile(os.path.join(sample_folder_dir, name)) and ".png" in name)]) | |
| total_samples = int(math.ceil(args.num_fid_samples / global_batch_size) * global_batch_size) | |
| if rank == 0: | |
| print(f"Total number of images that will be sampled: {total_samples}") | |
| assert total_samples % dist.get_world_size() == 0, "total_samples must be divisible by world_size" | |
| samples_needed_this_gpu = int(total_samples // dist.get_world_size()) | |
| assert samples_needed_this_gpu % n == 0, "samples_needed_this_gpu must be divisible by the per-GPU batch size" | |
| iterations = int(samples_needed_this_gpu // n) | |
| done_iterations = int( int(num_samples // dist.get_world_size()) // n) | |
| pbar = range(iterations) | |
| pbar = tqdm(pbar) if rank == 0 else pbar | |
| total = 0 | |
| for i in pbar: | |
| # Sample inputs: | |
| z = torch.randn(n, model.in_channels, latent_size, latent_size, device=device) | |
| y = torch.randint(0, args.num_classes, (n,), device=device) | |
| # Setup classifier-free guidance: | |
| if using_cfg: | |
| z = torch.cat([z, z], 0) | |
| y_null = torch.tensor([1000] * n, device=device) | |
| y = torch.cat([y, y_null], 0) | |
| model_kwargs = dict(y=y, cfg_scale=args.cfg_scale) | |
| model_fn = model.forward_with_cfg | |
| else: | |
| model_kwargs = dict(y=y) | |
| model_fn = model.forward | |
| samples = sample_fn(z, model_fn, **model_kwargs)[-1] | |
| if using_cfg: | |
| samples, _ = samples.chunk(2, dim=0) # Remove null class samples | |
| samples = vae.decode(samples / 0.18215).sample | |
| samples = torch.clamp(127.5 * samples + 128.0, 0, 255).permute(0, 2, 3, 1).to("cpu", dtype=torch.uint8).numpy() | |
| # Save samples to disk as individual .png files | |
| for i, sample in enumerate(samples): | |
| index = i * dist.get_world_size() + rank + total | |
| Image.fromarray(sample).save(f"{sample_folder_dir}/{index:06d}.png") | |
| total += global_batch_size | |
| dist.barrier() | |
| # Make sure all processes have finished saving their samples before attempting to convert to .npz | |
| dist.barrier() | |
| if rank == 0: | |
| create_npz_from_sample_folder(sample_folder_dir, args.num_fid_samples) | |
| print("Done.") | |
| dist.barrier() | |
| dist.destroy_process_group() | |
| if __name__ == "__main__": | |
| parser = argparse.ArgumentParser() | |
| if len(sys.argv) < 2: | |
| print("Usage: program.py <mode> [options]") | |
| sys.exit(1) | |
| mode = sys.argv[1] | |
| assert mode[:2] != "--", "Usage: program.py <mode> [options]" | |
| assert mode in ["ODE", "SDE"], "Invalid mode. Please choose 'ODE' or 'SDE'" | |
| parser.add_argument("--model", type=str, choices=list(SiT_models.keys()), default="SiT-XL/2") | |
| parser.add_argument("--vae", type=str, choices=["ema", "mse"], default="ema") | |
| parser.add_argument("--sample-dir", type=str, default="samples") | |
| parser.add_argument("--per-proc-batch-size", type=int, default=64) | |
| parser.add_argument("--num-fid-samples", type=int, default=50_000) | |
| parser.add_argument("--image-size", type=int, choices=[256, 512], default=256) | |
| parser.add_argument("--num-classes", type=int, default=1000) | |
| parser.add_argument("--cfg-scale", type=float, default=1.0) | |
| parser.add_argument("--num-sampling-steps", type=int, default=250) | |
| parser.add_argument("--global-seed", type=int, default=0) | |
| parser.add_argument("--tf32", action=argparse.BooleanOptionalAction, default=True, | |
| help="By default, use TF32 matmuls. This massively accelerates sampling on Ampere GPUs.") | |
| parser.add_argument("--ckpt", type=str, default=None, | |
| help="Optional path to a SiT checkpoint (default: auto-download a pre-trained SiT-XL/2 model).") | |
| parse_transport_args(parser) | |
| if mode == "ODE": | |
| parse_ode_args(parser) | |
| # Further processing for ODE | |
| elif mode == "SDE": | |
| parse_sde_args(parser) | |
| # Further processing for SDE | |
| args = parser.parse_known_args()[0] | |
| main(mode, args) | |