Spaces:
Configuration error
Configuration error
# 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}") | |
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] | |
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"]), | |
".", | |
) | |