CoolFace
Apppublic

gracekim0513/Step1X-Edit

sourceHugging Faceapache-2.0updated 1y agoView on Hugging Face
0likes
app.py500 linesDownload Raw Back to root
1import argparse2import datetime3import json 4import itertools5import math6import os7import spaces8import time9from pathlib import Path10 11 12import gradio as gr13import numpy as np14import torch15from einops import rearrange, repeat16from huggingface_hub import snapshot_download17from PIL import Image, ImageOps18from safetensors.torch import load_file19from torchvision.transforms import functional as F20from tqdm import tqdm 21 22import sampling23from modules.autoencoder import AutoEncoder24from modules.conditioner import Qwen25VL_7b_Embedder as Qwen2VLEmbedder25from modules.model_edit import Step1XParams, Step1XEdit26from diffusers import StableDiffusionPipeline27 28pipe = StableDiffusionPipeline.from_pretrained(29    "stabilityai/stable-diffusion-2",30    cache_dir="/tmp",  # 작게 제한된 디렉토리 사용31    local_files_only=False32)33 34print("TORCH_CUDA", torch.cuda.is_available())35 36examples = [37["examples 2/meme.jpg", "turn into an illustration in studio ghibli style",("examples 2/meme.jpg","examples 2/ghibli_meme.jpg"),],38["examples 2/celeb_meme.jpg", "replace the gray blazer with a leather jacket",("examples 2/celeb_meme.jpg","examples 2/leather.jpg")],39["examples 2/cookie.png", "remove the cookie",("examples 2/cookie.png","examples 2/no_cookie.png")],40["examples 2/poster_orig.jpg", "replace 'lambs' with 'llamas'",("examples 2/poster_orig.jpg","examples 2/poster.jpg")],41]42 43def generate_examples(init_image, prompt):44    return inference(prompt, init_image, seed=-1, size_level=512)45 46 47def load_state_dict(model, ckpt_path, device="cuda", strict=False, assign=True):48    if Path(ckpt_path).suffix == ".safetensors":49        state_dict = load_file(ckpt_path, device)50    else:51        state_dict = torch.load(ckpt_path, map_location="cpu")52 53    missing, unexpected = model.load_state_dict(54        state_dict, strict=strict, assign=assign55    )56    if len(missing) > 0 and len(unexpected) > 0:57        print(f"Got {len(missing)} missing keys:\n\t" + "\n\t".join(missing))58        print("\n" + "-" * 79 + "\n")59        print(f"Got {len(unexpected)} unexpected keys:\n\t" + "\n\t".join(unexpected))60    elif len(missing) > 0:61        print(f"Got {len(missing)} missing keys:\n\t" + "\n\t".join(missing))62    elif len(unexpected) > 0:63        print(f"Got {len(unexpected)} unexpected keys:\n\t" + "\n\t".join(unexpected))64    return model65 66 67def load_models(68    dit_path=None,69    ae_path=None,70    qwen2vl_model_path=None,71    device="cuda",72    max_length=256,73    dtype=torch.bfloat16,74):75    qwen2vl_encoder = Qwen2VLEmbedder(76        qwen2vl_model_path,77        device=device,78        max_length=max_length,79        dtype=dtype,80    )81 82    with torch.device("meta"):83        ae = AutoEncoder(84            resolution=256,85            in_channels=3,86            ch=128,87            out_ch=3,88            ch_mult=[1, 2, 4, 4],89            num_res_blocks=2,90            z_channels=16,91            scale_factor=0.3611,92            shift_factor=0.1159,93        )94 95        step1x_params = Step1XParams(96            in_channels=64,97            out_channels=64,98            vec_in_dim=768,99            context_in_dim=4096,100            hidden_size=3072,101            mlp_ratio=4.0,102            num_heads=24,103            depth=19,104            depth_single_blocks=38,105            axes_dim=[16, 56, 56],106            theta=10_000,107            qkv_bias=True,108        )109        dit = Step1XEdit(step1x_params)110 111    ae = load_state_dict(ae, ae_path)112    dit = load_state_dict(113        dit, dit_path114    )115 116    dit = dit.to(device=device, dtype=dtype)117    ae = ae.to(device=device, dtype=torch.float32)118 119    return ae, dit, qwen2vl_encoder120 121 122class ImageGenerator:123    def __init__(124        self,125        dit_path=None,126        ae_path=None,127        qwen2vl_model_path=None,128        device="cuda",129        max_length=640,130        dtype=torch.bfloat16,131    ) -> None:132        self.device = torch.device(device)133        self.ae, self.dit, self.llm_encoder = load_models(134            dit_path=dit_path,135            ae_path=ae_path,136            qwen2vl_model_path=qwen2vl_model_path,137            max_length=max_length,138            dtype=dtype,139        )140        self.ae = self.ae.to(device=self.device, dtype=torch.float32)141        self.dit = self.dit.to(device=self.device, dtype=dtype)142        self.llm_encoder = self.llm_encoder.to(device=self.device, dtype=dtype)143    144    def to_cuda(self):145        self.ae.to(device='cuda', dtype=torch.float32)146        self.dit.to(device='cuda', dtype=torch.bfloat16)147        self.llm_encoder.to(device='cuda', dtype=torch.bfloat16)148 149    def prepare(self, prompt, img, ref_image, ref_image_raw):150        bs, _, h, w = img.shape151        bs, _, ref_h, ref_w = ref_image.shape152 153        assert h == ref_h and w == ref_w154 155        if bs == 1 and not isinstance(prompt, str):156            bs = len(prompt)157        elif bs >= 1 and isinstance(prompt, str):158            prompt = [prompt] * bs159 160        img = rearrange(img, "b c (h ph) (w pw) -> b (h w) (c ph pw)", ph=2, pw=2)161        ref_img = rearrange(ref_image, "b c (ref_h ph) (ref_w pw) -> b (ref_h ref_w) (c ph pw)", ph=2, pw=2)162        if img.shape[0] == 1 and bs > 1:163            img = repeat(img, "1 ... -> bs ...", bs=bs)164            ref_img = repeat(ref_img, "1 ... -> bs ...", bs=bs)165 166        img_ids = torch.zeros(h // 2, w // 2, 3)167 168        img_ids[..., 1] = img_ids[..., 1] + torch.arange(h // 2)[:, None]169        img_ids[..., 2] = img_ids[..., 2] + torch.arange(w // 2)[None, :]170        img_ids = repeat(img_ids, "h w c -> b (h w) c", b=bs)171 172        ref_img_ids = torch.zeros(ref_h // 2, ref_w // 2, 3)173 174        ref_img_ids[..., 1] = ref_img_ids[..., 1] + torch.arange(ref_h // 2)[:, None]175        ref_img_ids[..., 2] = ref_img_ids[..., 2] + torch.arange(ref_w // 2)[None, :]176        ref_img_ids = repeat(ref_img_ids, "ref_h ref_w c -> b (ref_h ref_w) c", b=bs)177 178        if isinstance(prompt, str):179            prompt = [prompt]180 181        txt, mask = self.llm_encoder(prompt, ref_image_raw)182 183        txt_ids = torch.zeros(bs, txt.shape[1], 3)184 185        img = torch.cat([img, ref_img.to(device=img.device, dtype=img.dtype)], dim=-2)186        img_ids = torch.cat([img_ids, ref_img_ids], dim=-2)187 188 189        return {190            "img": img,191            "mask": mask,192            "img_ids": img_ids.to(img.device),193            "llm_embedding": txt.to(img.device),194            "txt_ids": txt_ids.to(img.device),195        }196 197    @staticmethod198    def process_diff_norm(diff_norm, k):199        pow_result = torch.pow(diff_norm, k)200 201        result = torch.where(202            diff_norm > 1.0,203            pow_result,204            torch.where(diff_norm < 1.0, torch.ones_like(diff_norm), diff_norm),205        )206        return result207 208    def denoise(209        self,210        img: torch.Tensor,211        img_ids: torch.Tensor,212        llm_embedding: torch.Tensor,213        txt_ids: torch.Tensor,214        timesteps: list[float],215        cfg_guidance: float = 4.5,216        mask=None,217        show_progress=False,218        timesteps_truncate=1.0,219    ):220        if show_progress:221            pbar = tqdm(itertools.pairwise(timesteps), desc='denoising...')222        else:223            pbar = itertools.pairwise(timesteps)224        for t_curr, t_prev in pbar:225            if img.shape[0] == 1 and cfg_guidance != -1:226                img = torch.cat([img, img], dim=0)227            t_vec = torch.full(228                (img.shape[0],), t_curr, dtype=img.dtype, device=img.device229            )230 231            txt, vec = self.dit.connector(llm_embedding, t_vec, mask)232 233 234            pred = self.dit(235                img=img,236                img_ids=img_ids,237                txt=txt,238                txt_ids=txt_ids,239                y=vec,240                timesteps=t_vec,241            )242 243            if cfg_guidance != -1:244                cond, uncond = (245                    pred[0 : pred.shape[0] // 2, :],246                    pred[pred.shape[0] // 2 :, :],247                )248                if t_curr > timesteps_truncate:249                    diff = cond - uncond250                    diff_norm = torch.norm(diff, dim=(2), keepdim=True)251                    pred = uncond + cfg_guidance * (252                        cond - uncond253                    ) / self.process_diff_norm(diff_norm, k=0.4)254                else:255                    pred = uncond + cfg_guidance * (cond - uncond)256            tem_img = img[0 : img.shape[0] // 2, :] + (t_prev - t_curr) * pred257            img_input_length = img.shape[1] // 2258            img = torch.cat(259                [260                tem_img[:, :img_input_length],261                img[ : img.shape[0] // 2, img_input_length:],262                ], dim=1263            )264 265        return img[:, :img.shape[1] // 2]266 267    @staticmethod268    def unpack(x: torch.Tensor, height: int, width: int) -> torch.Tensor:269        return rearrange(270            x,271            "b (h w) (c ph pw) -> b c (h ph) (w pw)",272            h=math.ceil(height / 16),273            w=math.ceil(width / 16),274            ph=2,275            pw=2,276        )277 278    @staticmethod279    def load_image(image):280        from PIL import Image281 282        if isinstance(image, np.ndarray):283            image = torch.from_numpy(image).permute(2, 0, 1).float() / 255.0284            image = image.unsqueeze(0)285            return image286        elif isinstance(image, Image.Image):287            image = F.to_tensor(image.convert("RGB"))288            image = image.unsqueeze(0)289            return image290        elif isinstance(image, torch.Tensor):291            return image292        elif isinstance(image, str):293            image = F.to_tensor(Image.open(image).convert("RGB"))294            image = image.unsqueeze(0)295            return image296        else:297            raise ValueError(f"Unsupported image type: {type(image)}")298 299    def output_process_image(self, resize_img, image_size):300        res_image = resize_img.resize(image_size)301        return res_image302    303    def input_process_image(self, img, img_size=512):304        # 1. 打开图片305        w, h = img.size306        r = w / h 307 308        if w > h:309            w_new = math.ceil(math.sqrt(img_size * img_size * r))310            h_new = math.ceil(w_new / r)311        else:312            h_new = math.ceil(math.sqrt(img_size * img_size / r))313            w_new = math.ceil(h_new * r)314        h_new = math.ceil(h_new) // 16 * 16315        w_new = math.ceil(w_new) // 16 * 16316 317        img_resized = img.resize((w_new, h_new))318        return img_resized, img.size319 320    @torch.inference_mode()321    def generate_image(322        self,323        prompt,324        negative_prompt,325        ref_images,326        num_steps,327        cfg_guidance,328        seed,329        num_samples=1,330        init_image=None,331        image2image_strength=0.0,332        show_progress=False,333        size_level=512,334    ):335        assert num_samples == 1, "num_samples > 1 is not supported yet."336        ref_images_raw, img_info = self.input_process_image(ref_images, img_size=size_level)337        338        width, height = ref_images_raw.width, ref_images_raw.height339 340 341        ref_images_raw = self.load_image(ref_images_raw)342        ref_images_raw = ref_images_raw.to(self.device)343        # print(f'self.ae, self.dit device: {self.ae.device}, {self.dit.device}')344        ref_images = self.ae.encode(ref_images_raw.to(self.device) * 2 - 1)345 346        seed = int(seed)347        seed = torch.Generator(device="cpu").seed() if seed < 0 else seed348 349        t0 = time.perf_counter()350 351        if init_image is not None:352            init_image = self.load_image(init_image)353            init_image = init_image.to(self.device)354            init_image = torch.nn.functional.interpolate(init_image, (height, width))355            init_image = self.ae.encode(init_image.to() * 2 - 1)356        357        x = torch.randn(358            num_samples,359            16,360            height // 8,361            width // 8,362            device=self.device,363            dtype=torch.bfloat16,364            generator=torch.Generator(device=self.device).manual_seed(seed),365        )366 367        timesteps = sampling.get_schedule(368            num_steps, x.shape[-1] * x.shape[-2] // 4, shift=True369        )370 371        if init_image is not None:372            t_idx = int((1 - image2image_strength) * num_steps)373            t = timesteps[t_idx]374            timesteps = timesteps[t_idx:]375            x = t * x + (1.0 - t) * init_image.to(x.dtype)376 377        x = torch.cat([x, x], dim=0)378        ref_images = torch.cat([ref_images, ref_images], dim=0)379        ref_images_raw = torch.cat([ref_images_raw, ref_images_raw], dim=0)380        inputs = self.prepare([prompt, negative_prompt], x, ref_image=ref_images, ref_image_raw=ref_images_raw)381 382        x = self.denoise(383            **inputs,384            cfg_guidance=cfg_guidance,385            timesteps=timesteps,386            show_progress=show_progress,387            timesteps_truncate=1.0,388        )389        x = self.unpack(x.float(), height, width)390        with torch.autocast(device_type=self.device.type, dtype=torch.bfloat16):391            x = self.ae.decode(x)392            x = x.clamp(-1, 1)393            x = x.mul(0.5).add(0.5)394 395        t1 = time.perf_counter()396        print(f"Done in {t1 - t0:.1f}s.")397        images_list = []398        for img in x.float():399            images_list.append(self.output_process_image(F.to_pil_image(img), img_info))400        return images_list401 402 403# 模型仓库ID(如:"bert-base-uncased")404model_repo = "stepfun-ai/Step1X-Edit"405# 本地保存路径406model_path = "./model_weights"407os.makedirs(model_path, exist_ok=True)408 409 410# 下载模型(包括所有文件)411snapshot_download(412    repo_id=model_repo,413    local_dir=model_path,414    local_dir_use_symlinks=False  # 避免使用符号链接415)416 417 418image_edit = ImageGenerator(419    ae_path=os.path.join(model_path, 'vae.safetensors'),420    dit_path=os.path.join(model_path, "step1x-edit-i1258.safetensors"),421    qwen2vl_model_path='Qwen/Qwen2.5-VL-7B-Instruct',422    max_length=640,423)424 425 426 427@spaces.GPU(duration=240)428def inference(prompt, ref_images, seed, size_level):429    start_time = time.time()430 431    if seed == -1:432        import random 433        random_seed = random.randint(0, 2**32 - 1)434    else:435        random_seed = seed436 437    image_edit.to_cuda()438 439    inference_func = image_edit.generate_image440    441    image = inference_func(442        prompt,443        negative_prompt="",444        ref_images=ref_images.convert('RGB'),445        num_samples=1,446        num_steps=28,447        cfg_guidance=6.0,448        seed=random_seed,449        show_progress=True,450        size_level=size_level,451    )[0]452    453    print(f"Time taken: {time.time() - start_time:.2f} seconds")454    return (ref_images, image), random_seed455 456with gr.Blocks() as demo:457    gr.Markdown(458        """459        # Step1X-Edit460        """461    )462    with gr.Row():463        with gr.Column():464            prompt = gr.Textbox(465                label="编辑指令 prompt",466                value='Remove the person from the image.',467            )468            init_image = gr.Image(label="Input Image", type='pil')469 470            random_seed = gr.Number(label="Random Seed", value=-1, minimum=-1)471 472            size_level = gr.Number(label="size level (recommend 512, 768, 1024, min 512)", value=512, minimum=512, maximum=1024)473 474            generate_btn = gr.Button("Generate")475 476        with gr.Column():477            output_image = gr.ImageSlider(label="Generated Image", type="pil", image_mode='RGB')478            output_random_seed = gr.Textbox(label="Used Seed", lines=5)479    from functools import partial480    generate_btn.click(481        fn=inference,482        inputs=[483            prompt, 484            init_image,485            random_seed,486            size_level,487        ],488        outputs=[output_image, output_random_seed],489    )490 491    gr.Examples(492        examples,493        inputs=[init_image, prompt],494        outputs=[output_image, output_random_seed],495        fn=generate_examples,496        cache_examples=True497        )498 499demo.launch()500