nvlabs-sana / scripts /inference.py
imw34531's picture
Upload folder using huggingface_hub
87e21d1 verified
# Copyright 2024 NVIDIA CORPORATION & AFFILIATES
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
import argparse
import json
import os
import re
import subprocess
import tarfile
import time
import warnings
from dataclasses import dataclass, field
# from datetime import datetime
from typing import List, Optional
import pyrallis
import torch
from termcolor import colored
from torchvision.utils import save_image
from tqdm import tqdm
warnings.filterwarnings("ignore") # ignore warning
from diffusion import DPMS, FlowEuler, SASolverSampler
from diffusion.data.datasets.utils import *
from diffusion.model.builder import build_model, get_tokenizer_and_text_encoder, get_vae, vae_decode
from diffusion.model.utils import prepare_prompt_ar
from diffusion.utils.config import SanaConfig
from diffusion.utils.logger import get_root_logger
from tools.download import find_model
def set_env(seed=0, latent_size=256):
torch.manual_seed(seed)
torch.set_grad_enabled(False)
for _ in range(30):
torch.randn(1, 4, latent_size, latent_size)
def get_dict_chunks(data, bs):
keys = []
for k in data:
keys.append(k)
if len(keys) == bs:
yield keys
keys = []
if keys:
yield keys
def create_tar(data_path):
tar_path = f"{data_path}.tar"
with tarfile.open(tar_path, "w") as tar:
tar.add(data_path, arcname=os.path.basename(data_path))
print(f"Created tar file: {tar_path}")
return tar_path
def delete_directory(exp_name):
if os.path.exists(exp_name):
subprocess.run(["rm", "-r", exp_name], check=True)
print(f"Deleted directory: {exp_name}")
@torch.inference_mode()
def visualize(config, args, model, items, bs, sample_steps, cfg_scale, pag_scale=1.0):
if isinstance(items, dict):
get_chunks = get_dict_chunks
else:
from diffusion.data.datasets.utils import get_chunks
generator = torch.Generator(device=device).manual_seed(args.seed)
tqdm_desc = f"{save_root.split('/')[-1]} Using GPU: {args.gpu_id}: {args.start_index}-{args.end_index}"
for chunk in tqdm(list(get_chunks(items, bs)), desc=tqdm_desc, unit="batch", position=args.gpu_id, leave=True):
# data prepare
prompts, hw, ar = (
[],
torch.tensor([[args.image_size, args.image_size]], dtype=torch.float, device=device).repeat(bs, 1),
torch.tensor([[1.0]], device=device).repeat(bs, 1),
)
if bs == 1:
prompt = data_dict[chunk[0]]["prompt"] if dict_prompt else chunk[0]
prompt_clean, _, hw, ar, custom_hw = prepare_prompt_ar(prompt, base_ratios, device=device, show=False)
latent_size_h, latent_size_w = (
(int(hw[0, 0] // config.vae.vae_downsample_rate), int(hw[0, 1] // config.vae.vae_downsample_rate))
if args.image_size == 1024
else (latent_size, latent_size)
)
prompts.append(prompt_clean.strip())
else:
for data in chunk:
prompt = data_dict[data]["prompt"] if dict_prompt else data
prompts.append(prepare_prompt_ar(prompt, base_ratios, device=device, show=False)[0].strip())
latent_size_h, latent_size_w = latent_size, latent_size
# check exists
save_file_name = f"{chunk[0]}.jpg" if dict_prompt else f"{prompts[0][:100]}.jpg"
save_path = os.path.join(save_root, save_file_name)
if os.path.exists(save_path):
# make sure the noise is totally same
torch.randn(bs, config.vae.vae_latent_dim, latent_size, latent_size, device=device, generator=generator)
continue
# prepare text feature
if not config.text_encoder.chi_prompt:
max_length_all = config.text_encoder.model_max_length
prompts_all = prompts
else:
chi_prompt = "\n".join(config.text_encoder.chi_prompt)
prompts_all = [chi_prompt + prompt for prompt in prompts]
num_chi_prompt_tokens = len(tokenizer.encode(chi_prompt))
max_length_all = (
num_chi_prompt_tokens + config.text_encoder.model_max_length - 2
) # magic number 2: [bos], [_]
caption_token = tokenizer(
prompts_all, max_length=max_length_all, padding="max_length", truncation=True, return_tensors="pt"
).to(device)
select_index = [0] + list(range(-config.text_encoder.model_max_length + 1, 0))
caption_embs = text_encoder(caption_token.input_ids, caption_token.attention_mask)[0][:, None][
:, :, select_index
]
emb_masks = caption_token.attention_mask[:, select_index]
null_y = null_caption_embs.repeat(len(prompts), 1, 1)[:, None]
# start sampling
with torch.no_grad():
n = len(prompts)
z = torch.randn(
n,
config.vae.vae_latent_dim,
latent_size,
latent_size,
device=device,
generator=generator,
dtype=weight_dtype,
)
model_kwargs = dict(data_info={"img_hw": hw, "aspect_ratio": ar}, mask=emb_masks)
if args.sampling_algo == "dpm-solver":
dpm_solver = DPMS(
model.forward_with_dpmsolver,
condition=caption_embs,
uncondition=null_y,
cfg_scale=cfg_scale,
model_kwargs=model_kwargs,
)
samples = dpm_solver.sample(
z,
steps=sample_steps,
order=2,
skip_type="time_uniform",
method="multistep",
)
elif args.sampling_algo == "sa-solver":
sa_solver = SASolverSampler(model.forward_with_dpmsolver, device=device)
samples = sa_solver.sample(
S=25,
batch_size=n,
shape=(config.vae.vae_latent_dim, latent_size_h, latent_size_w),
eta=1,
conditioning=caption_embs,
unconditional_conditioning=null_y,
unconditional_guidance_scale=cfg_scale,
model_kwargs=model_kwargs,
)[0]
elif args.sampling_algo == "flow_euler":
flow_solver = FlowEuler(
model, condition=caption_embs, uncondition=null_y, cfg_scale=cfg_scale, model_kwargs=model_kwargs
)
samples = flow_solver.sample(
z,
steps=sample_steps,
)
elif args.sampling_algo == "flow_dpm-solver":
dpm_solver = DPMS(
model,
condition=caption_embs,
uncondition=null_y,
guidance_type=guidance_type,
cfg_scale=cfg_scale,
pag_scale=pag_scale,
pag_applied_layers=pag_applied_layers,
model_type="flow",
model_kwargs=model_kwargs,
schedule="FLOW",
interval_guidance=args.interval_guidance,
)
samples = dpm_solver.sample(
z,
steps=sample_steps,
order=2,
skip_type="time_uniform_flow",
method="multistep",
flow_shift=flow_shift,
)
else:
raise ValueError(f"{args.sampling_algo} is not defined")
samples = samples.to(weight_dtype)
samples = vae_decode(config.vae.vae_type, vae, samples)
torch.cuda.empty_cache()
os.umask(0o000)
for i, sample in enumerate(samples):
save_file_name = f"{chunk[i]}.jpg" if dict_prompt else f"{prompts[i][:100]}.jpg"
save_path = os.path.join(save_root, save_file_name)
# logger.info(f"Saving path: {save_path}")
save_image(sample, save_path, nrow=1, normalize=True, value_range=(-1, 1))
def get_args():
parser = argparse.ArgumentParser()
parser.add_argument("--config", type=str, help="config")
return parser.parse_known_args()[0]
@dataclass
class SanaInference(SanaConfig):
config: Optional[str] = "configs/sana_config/1024ms/Sana_1600M_img1024.yaml" # config
model_path: Optional[str] = "hf://Efficient-Large-Model/Sana_1600M_1024px/checkpoints/Sana_1600M_1024px.pth"
work_dir: str = "output/inference"
version: str = "sigma"
txt_file: str = "asset/samples_mini.txt"
json_file: Optional[str] = None
sample_nums: int = 100_000
bs: int = 1
cfg_scale: float = 4.5
pag_scale: float = 1.0
sampling_algo: str = "flow_dpm-solver"
seed: int = 0
dataset: str = "custom"
step: int = -1
add_label: str = ""
tar_and_del: bool = False
exist_time_prefix: str = ""
gpu_id: int = 0
custom_image_size: Optional[int] = None
start_index: int = 0
end_index: int = 30_000
interval_guidance: List[float] = field(default_factory=lambda: [0, 1])
ablation_selections: Optional[List[float]] = None
ablation_key: Optional[str] = None
debug: bool = False
if_save_dirname: bool = False
if __name__ == "__main__":
args = get_args()
config = args = pyrallis.parse(config_class=SanaInference, config_path=args.config)
args.image_size = config.model.image_size
if args.custom_image_size:
args.image_size = args.custom_image_size
print(f"custom_image_size: {args.image_size}")
set_env(args.seed, args.image_size // config.vae.vae_downsample_rate)
device = "cuda" if torch.cuda.is_available() else "cpu"
logger = get_root_logger()
# only support fixed latent size currently
latent_size = args.image_size // config.vae.vae_downsample_rate
max_sequence_length = config.text_encoder.model_max_length
pe_interpolation = config.model.pe_interpolation
micro_condition = config.model.micro_condition
flow_shift = config.scheduler.flow_shift
pag_applied_layers = config.model.pag_applied_layers
guidance_type = "classifier-free_PAG"
assert (
isinstance(args.interval_guidance, list)
and len(args.interval_guidance) == 2
and args.interval_guidance[0] <= args.interval_guidance[1]
)
args.interval_guidance = [max(0, args.interval_guidance[0]), min(1, args.interval_guidance[1])]
sample_steps_dict = {"dpm-solver": 20, "sa-solver": 25, "flow_dpm-solver": 20, "flow_euler": 28}
sample_steps = args.step if args.step != -1 else sample_steps_dict[args.sampling_algo]
if config.model.mixed_precision == "fp16":
weight_dtype = torch.float16
elif config.model.mixed_precision == "bf16":
weight_dtype = torch.bfloat16
elif config.model.mixed_precision == "fp32":
weight_dtype = torch.float32
else:
raise ValueError(f"weigh precision {config.model.mixed_precision} is not defined")
logger.info(f"Inference with {weight_dtype}, default guidance_type: {guidance_type}, flow_shift: {flow_shift}")
vae = get_vae(config.vae.vae_type, config.vae.vae_pretrained, device).to(weight_dtype)
tokenizer, text_encoder = get_tokenizer_and_text_encoder(name=config.text_encoder.text_encoder_name, device=device)
null_caption_token = tokenizer(
"", max_length=max_sequence_length, padding="max_length", truncation=True, return_tensors="pt"
).to(device)
null_caption_embs = text_encoder(null_caption_token.input_ids, null_caption_token.attention_mask)[0]
# model setting
pred_sigma = getattr(config.scheduler, "pred_sigma", True)
learn_sigma = getattr(config.scheduler, "learn_sigma", True) and pred_sigma
model_kwargs = {
"pe_interpolation": config.model.pe_interpolation,
"config": config,
"model_max_length": config.text_encoder.model_max_length,
"qk_norm": config.model.qk_norm,
"micro_condition": config.model.micro_condition,
"caption_channels": text_encoder.config.hidden_size,
"y_norm": config.text_encoder.y_norm,
"attn_type": config.model.attn_type,
"ffn_type": config.model.ffn_type,
"mlp_ratio": config.model.mlp_ratio,
"mlp_acts": list(config.model.mlp_acts),
"in_channels": config.vae.vae_latent_dim,
"y_norm_scale_factor": config.text_encoder.y_norm_scale_factor,
"use_pe": config.model.use_pe,
"linear_head_dim": config.model.linear_head_dim,
"pred_sigma": pred_sigma,
"learn_sigma": learn_sigma,
}
model = build_model(
config.model.model, use_fp32_attention=config.model.get("fp32_attention", False), **model_kwargs
).to(device)
# model = build_model(config.model, **model_kwargs).to(device)
logger.info(
f"{model.__class__.__name__}:{config.model.model}, Model Parameters: {sum(p.numel() for p in model.parameters()):,}"
)
logger.info("Generating sample from ckpt: %s" % args.model_path)
state_dict = find_model(args.model_path)
if "pos_embed" in state_dict["state_dict"]:
del state_dict["state_dict"]["pos_embed"]
missing, unexpected = model.load_state_dict(state_dict["state_dict"], strict=False)
logger.warning(f"Missing keys: {missing}")
logger.warning(f"Unexpected keys: {unexpected}")
model.eval().to(weight_dtype)
base_ratios = eval(f"ASPECT_RATIO_{args.image_size}_TEST")
args.sampling_algo = (
args.sampling_algo
if ("flow" not in args.model_path or args.sampling_algo == "flow_dpm-solver")
else "flow_euler"
)
if args.work_dir is None:
work_dir = (
f"/{os.path.join(*args.model_path.split('/')[:-2])}"
if args.model_path.startswith("/")
else os.path.join(*args.model_path.split("/")[:-2])
)
img_save_dir = os.path.join(str(work_dir), "vis")
else:
img_save_dir = args.work_dir
logger.info(colored(f"Saving images at {img_save_dir}", "green"))
dict_prompt = args.json_file is not None
if dict_prompt:
data_dict = json.load(open(args.json_file))
items = list(data_dict.keys())
else:
with open(args.txt_file) as f:
items = [item.strip() for item in f.readlines()]
logger.info(f"Eval first {min(args.sample_nums, len(items))}/{len(items)} samples")
items = items[: max(0, args.sample_nums)]
items = items[max(0, args.start_index) : min(len(items), args.end_index)]
match = re.search(r".*epoch_(\d+).*step_(\d+).*", args.model_path)
epoch_name, step_name = match.groups() if match else ("unknown", "unknown")
os.umask(0o000)
os.makedirs(img_save_dir, exist_ok=True)
logger.info(f"Sampler {args.sampling_algo}")
def create_save_root(args, dataset, epoch_name, step_name, sample_steps, guidance_type):
save_root = os.path.join(
img_save_dir,
# f"{datetime.now().date() if args.exist_time_prefix == '' else args.exist_time_prefix}_"
f"{dataset}_epoch{epoch_name}_step{step_name}_scale{args.cfg_scale}"
f"_step{sample_steps}_size{args.image_size}_bs{args.bs}_samp{args.sampling_algo}"
f"_seed{args.seed}_{str(weight_dtype).split('.')[-1]}",
)
if args.pag_scale != 1.0:
save_root = save_root.replace(f"scale{args.cfg_scale}", f"scale{args.cfg_scale}_pagscale{args.pag_scale}")
if flow_shift != 1.0:
save_root += f"_flowshift{flow_shift}"
if guidance_type != "classifier-free":
save_root += f"_{guidance_type}"
if args.interval_guidance[0] != 0 and args.interval_guidance[1] != 1:
save_root += f"_intervalguidance{args.interval_guidance[0]}{args.interval_guidance[1]}"
save_root += f"_imgnums{args.sample_nums}" + args.add_label
return save_root
def guidance_type_select(default_guidance_type, pag_scale, attn_type):
guidance_type = default_guidance_type
if not (pag_scale > 1.0 and attn_type == "linear"):
logger.info("Setting back to classifier-free")
guidance_type = "classifier-free"
return guidance_type
dataset = "MJHQ-30K" if args.json_file and "MJHQ-30K" in args.json_file else args.dataset
if args.ablation_selections and args.ablation_key:
for ablation_factor in args.ablation_selections:
setattr(args, args.ablation_key, eval(ablation_factor))
print(f"Setting {args.ablation_key}={eval(ablation_factor)}")
sample_steps = args.step if args.step != -1 else sample_steps_dict[args.sampling_algo]
guidance_type = guidance_type_select(guidance_type, args.pag_scale, config.model.attn_type)
save_root = create_save_root(args, dataset, epoch_name, step_name, sample_steps, guidance_type)
os.makedirs(save_root, exist_ok=True)
if args.if_save_dirname and args.gpu_id == 0:
# save at work_dir/metrics/tmp_xxx.txt for metrics testing
with open(f"{work_dir}/metrics/tmp_{dataset}_{time.time()}.txt", "w") as f:
print(f"save tmp file at {work_dir}/metrics/tmp_{dataset}_{time.time()}.txt")
f.write(os.path.basename(save_root))
logger.info(f"Inference with {weight_dtype}, guidance_type: {guidance_type}, flow_shift: {flow_shift}")
visualize(
config=config,
args=args,
model=model,
items=items,
bs=args.bs,
sample_steps=sample_steps,
cfg_scale=args.cfg_scale,
pag_scale=args.pag_scale,
)
else:
guidance_type = guidance_type_select(guidance_type, args.pag_scale, config.model.attn_type)
logger.info(f"Inference with {weight_dtype}, guidance_type: {guidance_type}, flow_shift: {flow_shift}")
save_root = create_save_root(args, dataset, epoch_name, step_name, sample_steps, guidance_type)
os.makedirs(save_root, exist_ok=True)
if args.if_save_dirname and args.gpu_id == 0:
# save at work_dir/metrics/tmp_xxx.txt for metrics testing
with open(f"{work_dir}/metrics/tmp_{dataset}_{time.time()}.txt", "w") as f:
print(f"save tmp file at {work_dir}/metrics/tmp_{dataset}_{time.time()}.txt")
f.write(os.path.basename(save_root))
if args.debug:
items = [
'a blackboard wrote text "Hello World"'
'Text" Super Dad Mode ON", t shirt design, This is a graffiti-style image.The letters are surrounded by a playful, abstract design of paw prints and pet-related shapes, such as a heart-shaped bone and a cat-whisker-shaped element.',
'"NR Beauty Hair" logo para peluqueria, product, typography, fashion, painting',
'Text"Goblins gone wild.", The text is written in an elegant, vintage-inspired font and each letter in the text showed in different colors.',
"An awe-inspiring 3D render of the mahir Olympics logo, set against the backdrop of a fiery, burning Olympic flame. The flames dance and intertwine to form the iconic Olympic rings and typography, while the Eiffel Tower stands tall in the distance. The cinematic-style poster is rich in color and detail, evoking a sense of excitement and anticipation for the upcoming games., ukiyo-e, vibrant, cinematic, 3d render, typography, poster",
'Cute cartoon back style of a couple, wearing a black t shirts , she have long hair with the name "C". He have staright hair and light beard with the name "J"white color,heart snowy atmosphere, typography, 3d render, portrait photography, fashion',
'A captivating 3D render of a whimsical, colorful scene, featuring the word "Muhhh" spelled out in vibrant, floating balloons. The wordmark hovers above a lush, emerald green field. A charming, anthropomorphic rabbit with a wide smile and twinkling eyes hops alongside the balloon letters. The background showcases a serene, dreamy sky with soft pastel hues, creating an overall atmosphere of joy, enchantment, and surrealism. The 3D render is a stunning illustration that blends fantasy and realism effortlessly., illustration, 3d render',
'create a logo for a company named "FUN"',
"A stunningly realistic image of an Asian woman sitting on a plush sofa, completely engrossed in a book. She is wearing cozy loungewear and has headphones on, indicating her desire for a serene and quiet environment. In one hand, she holds a can of water, providing a refreshing sensation. The adjacent table features an array of snacks and books, adding to the cozy ambiance of the scene. The room is filled with natural light streaming through vibrantly decorated windows, and tasteful decorations contribute to the overall relaxing and soothing atmosphere.",
'A captivating 3D logo illustration of the name "ANGEL" in a romantic and enchanting Follow my Page poster design. The lettering is adorned with a majestic, shimmering crown encrusted with intricate gemstones. Swirling pink and purple patterns, reminiscent of liquid or air, surround the crown, with beautiful pink flowers in full bloom and bud adorning the design. Heart-shaped decorations enhance the romantic ambiance, and a large, iridescent butterfly with intricate wings graces the right side of the crown. The muted purple background contrasts with the bright and lively elements within the composition, creating a striking visual effect. The 3D rendering showcases the intricate details and depth of the design, making it a truly mesmerizing piece of typography, 3D render, and illustration art., illustration, typography, poster, 3d render',
'A human wearing a T-shirt with Text "NVIDIA" and logo',
'Logo with text "Hi"',
]
visualize(
config=config,
args=args,
model=model,
items=items,
bs=args.bs,
sample_steps=sample_steps,
cfg_scale=args.cfg_scale,
pag_scale=args.pag_scale,
)
if args.tar_and_del:
create_tar(save_root)
delete_directory(save_root)
print(
colored(f"Sana inference has finished. Results stored at ", "green"),
colored(f"{img_save_dir}", attrs=["bold"]),
".",
)