基于潜在一致性的文生图优化

问题陈述:
AIGC 时代,包括 Stable Diffusion 和 DALL-E 3 等基于扩散模型的文生图模型受到了广泛关注。扩散模型通过向训练数据添加噪声,然后逆转这一过程来生成高质量图像。然而,扩散模型生成图片需要进行多步采样,这一过程相对较慢,增加了推理成本。缓慢的多步采样问题是部署这类模型时的主要瓶颈。

英特尔黑客松比赛旨在由参赛者使用 Stable Diffusion 在目标英特尔硬件上完成部署优化及指定的图片生成工作。根据指定的输入输出要求,优化指定文生图模型在端侧设备上的 Pipeline性能,在保证生图效果的情况下,降低pipeline端到端延迟,降低pipeline峰值内存占用。这对于AIGC时代文生图推理成本问题解决有极大的促进意义。

初始解决方案:
我们团队的初始解决方案为基于潜在一致性的文生图优化,针对上述问题,OpenAI 的宋飏博士在今年提出的一致性模型(Consistency Model,CM)为解决上述问题提供了一个思路。一致性模型被指出在设计上具有单步生成的能力,展现出极大的加速扩散模型的生成的潜力。然而,由于一致性模型局限于无条件图片生成,导致包括文生图、图生图等在内的许多实际应用还难以享受这一模型的潜在优势。

LCM的核心创新在于其潜在一致性架构。这一架构通过在潜空间进行图像处理,显著减少了所需处理的数据量,从而大幅提升了图像生成的速度。这种方法不仅加快了生成速度,还保持了图像的高分辨率和细节质量。

在性能方面,LCM展现出了卓越的效率。据实验数据显示,LCM在图像生成过程中,所需的算力比传统模型减少了约50%。这一显著的性能提升,使得LCM在处理大规模图像数据时更加高效。同时,LCM在图像质量上也有所提升,其出图效果已经大幅优于许多现有模型。

LCM 强调的是不要再一步步迭代了, 直接追求“一步到位”。LCM 是在 Consistency Models 的基础上引入了 Lantent Space (潜空间),进一步压缩需要处理的数据量,从而实现超快速的图像推理合成。只需要 2-4 步即可完成!

我想,针对于该赛题,该方案是最好的方法,可以看下基于潜在一致性模型生成的一些图片,首先是4步效果图:
在这里插入图片描述
然后是2步效果图:
在这里插入图片描述
1步效果图:
在这里插入图片描述
可以看出,基于潜在一致性的文生图,不仅推理速度方面有了提升,而且在相当少的推理步数下,推理出来的图片质量也是十分不错的。

然而目前官网只提供了Dreamshaper-V7和LCM-SDXL,2 款可以在Stable Diffusion中使用的模型,并且需要安装 LCM 插件。
我们需要先安装以下库:

pip install --upgrade diffusers  # make sure to use at least diffusers >= 0.22
pip install transformers accelerate

然后运行以下代码即可:

from diffusers import DiffusionPipeline
import torch

pipe = DiffusionPipeline.from_pretrained("SimianLuo/LCM_Dreamshaper_v7")

# To save GPU memory, torch.float16 can be used, but it may compromise image quality.
pipe.to(torch_device="cpu", torch_dtype=torch.float32)

prompt = "Self-portrait oil painting, a beautiful cyborg with golden hair, 8k"

# Can be set to 1~50 steps. LCM support fast inference even <= 4 steps. Recommend: 1~8 steps.
num_inference_steps = 4 

images = pipe(prompt=prompt, num_inference_steps=num_inference_steps, guidance_scale=8.0, lcm_origin_steps=50, output_type="pil").images

多线程优化:
在此次优化任务中,我们还特意使用到了多线程,多线程可以显著提高性能和吞吐量,对于此次任务,我觉得它最大的好处就是并行处理,如果你需要生成大量的图像,并且每张图像的生成是独立的,那么多线程可以同时处理多个生成任务。这样可以利用多核心CPU的能力,减少总体的等待时间,从而提高效率。

但是在进行的过程中也是遇到了些许问题,如RuntimeError: Infer Request is busy,在多线程环境中,如果多个线程尝试同时使用同一个模型对象进行推理,就可能发生这种情况。OpenVINO™ 是一个高性能的深度学习推理引擎,但是当使用它的 Python API 时,需要确保对模型的推理请求是线程安全的。
刚开始,我们增加了一个线程锁,虽然解决了推理报错的问题,但是增加线程锁会严重影响并行处理的效率,代码如下:

import threading
import os
from PIL import Image
import ipywidgets as widgets
from transformers import CLIPTokenizer

core = ov.Core()

device = 'GPU.1'

ov_config = {"INFERENCE_PRECISION_HINT": "f32"} if device != "CPU" else {}

text_enc = core.compile_model(TEXT_ENCODER_OV_PATH, device)
unet_model = core.compile_model(UNET_OV_PATH, device)
vae_decoder = core.compile_model(VAE_DECODER_OV_PATH, device, ov_config)
vae_encoder = core.compile_model(VAE_ENCODER_OV_PATH, device, ov_config)

scheduler = LMSDiscreteScheduler.from_config(conf)
tokenizer = CLIPTokenizer.from_pretrained('clip-vit-large-patch14')

# 创建一个锁对象
lock = threading.Lock()

ov_pipe = OVStableDiffusionPipeline(
    tokenizer=tokenizer,
    text_encoder=text_enc,
    unet=unet_model,
    vae_encoder=vae_encoder,
    vae_decoder=vae_decoder,
    scheduler=scheduler
)


def generate(prompt, negative_prompt, seed, num_steps):  
    result = ov_pipe(  
        prompt,  
        negative_prompt=negative_prompt,  
        num_inference_steps=int(num_steps),  
        seed=seed,  
    )  
    return result["sample"][0]  

def save_image(image, file_path):  
    # 确保目录存在  
    os.makedirs(os.path.dirname(file_path), exist_ok=True)  
      
    # 保存图像  
    image.save(file_path, format="PNG", optimize=True)

def generate_and_save_image(prompt, negative_prompt, seed, num_steps, image_dir, i):  
    # 使用锁来确保线程安全
    with lock:
        image = generate(prompt, negative_prompt, seed + i, num_steps)  
    image = image.resize((512, 512))  
    file_path = os.path.join(image_dir, f"image_{i}.png")  
    save_image(image, file_path)

def generate_and_save_images_multithreaded(prompt, negative_prompt, seed, num_steps, image_dir, num_images=20):  
    threads = []
    for i in range(num_images):  
        thread = threading.Thread(target=generate_and_save_image, args=(prompt, negative_prompt, seed, num_steps, image_dir, i))
        threads.append(thread)
        thread.start()
    
    for thread in threads:
        thread.join()  # 等待所有线程完成

prompt = "a photo of an astronaut riding a horse on mars"
negative_prompt = "low resolution, blurry"
seed = 42
num_inference_steps = 20
output_dir = "output_images5"

# 使用多线程函数生成图片  
generate_and_save_images_multithreaded(prompt, negative_prompt, seed, num_inference_steps, output_dir)

考虑到并行效率问题,我们最终摒弃了该方法,测试了线程池与队列、异步推理等方法。

from openvino.inference_engine import IECore, StatusCode
import os
from PIL import Image
import ipywidgets as widgets
from transformers import CLIPTokenizer

core = ov.Core()
device = 'GPU.1'
ov_config = {"INFERENCE_PRECISION_HINT": "f32"} if device != "CPU" else {}

text_enc = core.compile_model(TEXT_ENCODER_OV_PATH, device)
unet_model = core.compile_model(UNET_OV_PATH, device)
vae_decoder = core.compile_model(VAE_DECODER_OV_PATH, device, ov_config)
vae_encoder = core.compile_model(VAE_ENCODER_OV_PATH, device, ov_config)


scheduler = PNDMScheduler.from_config(conf)
tokenizer = CLIPTokenizer.from_pretrained('clip-vit-large-patch14')



ov_pipe = OVStableDiffusionPipeline(
    tokenizer=tokenizer,
    text_encoder=text_enc,
    unet=unet_model,
    vae_encoder=vae_encoder,
    vae_decoder=vae_decoder,
    scheduler=scheduler
)

def generate(prompt, negative_prompt, seed, num_steps):  
    result = ov_pipe(  
        prompt,  
        negative_prompt=negative_prompt,  
        num_inference_steps=int(num_steps),  
        seed=seed,  
    )  
    return result["sample"][0]  

def save_image(image, file_path):  
    # 确保目录存在  
    os.makedirs(os.path.dirname(file_path), exist_ok=True)  
      
    # 保存图像  
    image.save(file_path, format="PNG", optimize=True)

def generate_and_save_image(prompt, negative_prompt, seed, num_steps, image_dir, i):  
    # 创建推理请求
    text_enc_infer_req = text_enc_exec_net.create_infer_request()
    unet_infer_req = unet_exec_net.create_infer_request()
    vae_encoder_infer_req = vae_encoder_exec_net.create_infer_request()
    vae_decoder_infer_req = vae_decoder_exec_net.create_infer_request()
    
    # 准备输入数据
    input_blob = next(iter(text_enc.input_info))
    output_blob = next(iter(text_enc.outputs))
    text_enc_input_data = {"input_ids": tokenizer.encode(prompt)}
    
    # 启动异步推理
    text_enc_infer_req.start_async(request_id=0, inputs=text_enc_input_data)
    status = text_enc_infer_req.wait()
    
    if status == StatusCode.OK:
        text_enc_output_data = text_enc_infer_req.outputs[output_blob]
        
        # 准备输入数据
        input_blob = next(iter(unet_model.input_info))
        output_blob = next(iter(unet_model.outputs))
        unet_input_data = {"input": image}
        
        # 启动异步推理
        unet_infer_req.start_async(request_id=0, inputs=unet_input_data)
        
        # 等待推理完成
        status = unet_infer_req.wait()
        
        if status == StatusCode.OK:
            unet_output_data = unet_infer_req.outputs[output_blob]
            
            # 准备输入数据
            input_blob = next(iter(vae_encoder.input_info))
            output_blob = next(iter(vae_encoder.outputs))
            vae_encoder_input_data = {"input": unet_output_data, "text_embed": text_enc_output_data}
            
            # 启动异步推理
            vae_encoder_infer_req.start_async(request_id=0, inputs=vae_encoder_input_data)
            
            # 等待推理完成
            status = vae_encoder_infer_req.wait()
            
            if status == StatusCode.OK:
                vae_encoder_output_data = vae_encoder_infer_req.outputs[output_blob]

                # 准备输入数据
                input_blob = next(iter(vae_decoder.input_info))
                output_blob = next(iter(vae_decoder.outputs))
                vae_decoder_input_data = {"z": vae_encoder_output_data, "text_embed": text_enc_output_data}

                # 启动异步推理
                vae_decoder_infer_req.start_async(request_id=0, inputs=vae_decoder_input_data)

                # 等待推理完成
                status = vae_decoder_infer_req.wait()

                if status == StatusCode.OK:
                    vae_decoder_output_data = vae_decoder_infer_req.outputs[output_blob]

                    # 获取推理结果
                    image = Image.fromarray(vae_decoder_output_data[0].transpose((1, 2, 0)))
                    image = image.resize((512, 512))  
                    file_path = os.path.join(image_dir, f"image_{i}.png")  
                    save_image(image, file_path)
                else:
                    print("VAE decoder inference failed.")
            else:
                print("VAE encoder inference failed.")
        else:
            print("Unet inference failed.")
    else:
        print("Text encoder inference failed.")

def generate_and_save_images_multithreaded(prompt, negative_prompt, seed, num_steps, image_dir, num_images=20, max_workers=5):  
    with ThreadPoolExecutor(max_workers=max_workers) as executor:
        futures = [executor.submit(generate_and_save_image, prompt, negative_prompt, seed, num_steps, image_dir, i) for i in range(num_images)]
        for future in futures:
            future.result()  # 等待每个线程任务完成


prompt = "a photo of an astronaut riding a horse on mars"
negative_prompt = "low resolution, blurry"
seed = 42
num_inference_steps = 20
output_dir = "output_images5"

# 使用多线程函数生成图片  
generate_and_save_images_multithreaded(prompt, negative_prompt, seed, num_inference_steps, output_dir)
import threading
import os,random
from PIL import Image
import ipywidgets as widgets
from transformers import CLIPTokenizer
import copy
core = ov.Core()

device = 'GPU.1'

ov_config = {"INFERENCE_PRECISION_HINT": "f32"} if device != "CPU" else {}

text_enc = core.compile_model(TEXT_ENCODER_OV_PATH, device)
unet_model = core.compile_model(UNET_OV_PATH, device)
vae_decoder = core.compile_model(VAE_DECODER_OV_PATH, device, ov_config)
vae_encoder = core.compile_model(VAE_ENCODER_OV_PATH, device, ov_config)

scheduler = LMSDiscreteScheduler.from_config(conf)
tokenizer = CLIPTokenizer.from_pretrained('clip-vit-large-patch14')


ov_pipe = OVStableDiffusionPipeline(
    tokenizer=tokenizer,
    text_encoder=text_enc,
    unet=unet_model,
    vae_encoder=vae_encoder,
    vae_decoder=vae_decoder,
    scheduler=scheduler
)
def generate_and_save_images(prompt, negative_prompt, seed, num_steps, image_dir): 
    if not os.path.exists(image_dir):
        os.makedirs(image_dir)
    infer_request_curr = ov_pipe
    infer_request_curr_result=infer_request_curr(
        prompt,  
        negative_prompt=negative_prompt,  
        num_inference_steps=int(num_steps),  
        seed=seed[0],  
    )
    for inx,i in enumerate(range(len(seed))): 
        infer_request_next = ov_pipe
        infer_request_next_result=infer_request_next(
        prompt,  
        negative_prompt=negative_prompt,  
        num_inference_steps=int(num_steps),  
        seed=seed[inx],  
    )

        image = infer_request_curr_result["sample"][0].resize((512, 512))
        # 保存图片到文件  
        file_path = os.path.join(image_dir, f"image_{inx-1}.png")
        image.save(file_path, format="PNG", optimize=True)
      
        infer_request_curr, infer_request_next = infer_request_next, infer_request_curr
        infer_request_curr_result,infer_request_next_result = infer_request_next_result,infer_request_curr_result

prompt = "a photo of an astronaut riding a horse on mars"
negative_prompt = "low resolution, blurry"
seed = 42
num_inference_steps = 20
output_dir = "output_images6"
generate_numbers = 20
seed_li = random_numbers = random.sample(range(1, 100), generate_numbers)
generate_and_save_images(prompt, negative_prompt, seed_li, num_inference_steps, output_dir)

OneAPI—Intel® Distribution for Python*

  • Intel® Distribution for Python*是一个为Python语言提供的发行版,旨在优化Python在Intel硬件上的性能。它包含了Python解释器以及一系列常用的Python库和工具,可以帮助用户更高效地开发Python应用程序。

  • 该发行版提供了对Intel硬件特性的支持,例如多核处理器、向量指令集等,从而提高了Python代码的执行效率。此外,它还提供了对并行计算和分布式计算的支持,可以帮助用户更好地利用多核处理器和集群资源进行高性能计算。

  • Intel® Distribution for Python*还包含了一些专门为数据科学和机器学习应用优化的库,例如NumPy、Pandas、SciPy和Scikit-learn等。这些库可以帮助用户更轻松地进行数据处理、模型训练和预测等操作。

  • Intel® Distribution for Python*是一个针对Python语言的优化发行版,可以帮助用户更高效地开发Python应用程序,并充分利用Intel硬件的特性进行高性能计算。通过使用该python发行版本,我们可以进一步优化模型推理速度。

在本地进行以下命令安装:
首先使用conda update conda更新所有anaconda包。

然后使用conda config --add channels intel 向Conda配置中添加一个新的频道(channel),名为"intel"。Conda是一个流行的Python包管理工具,用于安装和管理Python包和环境。

最后使用conda create -n idp intelpython3_core python=3.9进行安装。

安装完成后使用conda activate idp进入该虚拟环境。
在这里插入图片描述
然后基于此环境来安装我们所需要的文生图依赖库。

调度器优化:
初次之外,我们还将调度器进行了替换,使用PNDMScheduler调度器,该调度器有更快的推理速度,可以更好的优化文生图性能,减少文生图时间延迟。

    def prepare_latents(self, image:PIL.Image.Image = None, latent_timestep:torch.Tensor = None):
        """
        Function for getting initial latents for starting generation
        
        Parameters:
            image (PIL.Image.Image, *optional*, None):
                Input image for generation, if not provided randon noise will be used as starting point
            latent_timestep (torch.Tensor, *optional*, None):
                Predicted by scheduler initial step for image generation, required for latent image mixing with nosie
        Returns:
            latents (np.ndarray):
                Image encoded in latent space
        """
        latents_shape = (1, 4, self.height // 8, self.width // 8)
        noise = np.random.randn(*latents_shape).astype(np.float32)
        if image is None:
            # if we use LMSDiscreteScheduler, let's make sure latents are mulitplied by sigmas
            if isinstance(self.scheduler, PNDMScheduler):
                noise = noise * self.scheduler.alphas[0].numpy()
            return noise, {}
        input_image, meta = preprocess(image)
        latents = self.vae_encoder(input_image)[self._vae_e_output]
        latents = latents * 0.18215
        latents = self.scheduler.add_noise(torch.from_numpy(latents), torch.from_numpy(noise), latent_timestep).numpy()
        return latents, meta

推理结果:
在这里插入图片描述

  • 20
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值