当前位置:   article > 正文

用 OpenVINO™ 在 Intel 13th Gen CPU 上运行 SDXL-Turbo 文本图像生成模型_openvino sdxl

openvino sdxl

作者:英特尔创新大使 卢雨畋

本文基于 13th Gen Intel(R) Core(TM) i5-13490F 型号 CPU 验证,对于量化后模型,你只需要在 16G 的笔记本电脑上就可体验生成过程(最佳体验为32G内存)。

SDXL-Turbo是一个快速的生成式文本到图像模型,可以通过单次网络评估从文本提示中合成逼真的图像。SDXL-Turbo采用了一种称为Adversarial Diffusion Distillation (ADD)的新型训练方法(详见技术报告),该方法可以在1到4个步骤中对大规模基础图像扩散模型进行采样,并保持高质量的图像。通过最新版本(2023.2)OpenVINO™ 工具套件的强大推理能力及 NNCF 的高效神经网络压缩能力,我们能够在两秒内实现 SDXL-Turbo 图像的高速、高质量生成。

## 环境安装

在开始之前,我们需要安装所有环境依赖:

  1. %pip install --extra-index-url https://download.pytorch.org/whl/cpu \
  2. torch transformers diffusers nncf optimum-intel gradio openvino==2023.2.0 onnx "git+https://github.com/huggingface/optimum-intel.git"

## 下载、转换模型

首先我们要把 huggingface 下载的原始模型转化为 OpenVINO IR,以便后续的 NNCF 工具链进行量化工作。转换完成后你将得到对应的 text_encode、unet、vae模型。

  1. from pathlib import Path
  2. model_dir = Path("./sdxl_vino_model")
  3. sdxl_model_id = "stabilityai/sdxl-turbo"
  4. skip_convert_model = model_dir.exists()

  1. import os
  2. if not skip_convert_model:
  3.     # 设置下载路径到当前文件夹,并加速下载
  4.     os.environ['HF_ENDPOINT'] = 'https://hf-mirror.com'
  5.     os.system(f'optimum-cli export openvino --model {sdxl_model_id} --task stable-diffusion-xl {model_dir} --fp16')
  6. os.environ['HF_ENDPOINT'] = 'https://hf-mirror.com'
  7. tae_id = "madebyollin/taesdxl"
  8. save_path = './taesdxl'
  9. os.system(f'huggingface-cli download --resume-download {tae_id} --local-dir {save_path}')


 

  1. import torch
  2. import openvino as ov
  3. from diffusers import AutoencoderTiny
  4. import gc
  5. class VAEEncoder(torch.nn.Module):
  6.     def __init__(self, vae):
  7.         super().__init__()
  8.         self.vae = vae
  9.     def forward(self, sample):
  10.         return self.vae.encode(sample)
  11.    
  12. class VAEDecoder(torch.nn.Module):
  13.     def __init__(self, vae):
  14.         super().__init__()
  15.         self.vae = vae
  16.     def forward(self, latent_sample):
  17.         return self.vae.decode(latent_sample)
  18. def convert_tiny_vae(save_path, output_path):
  19.     tiny_vae = AutoencoderTiny.from_pretrained(save_path)
  20.     tiny_vae.eval()
  21.     vae_encoder = VAEEncoder(tiny_vae)
  22.     ov_model = ov.convert_model(vae_encoder, example_input=torch.zeros((1,3,512,512)))
  23.     ov.save_model(ov_model, output_path / "vae_encoder/openvino_model.xml")
  24.     tiny_vae.save_config(output_path / "vae_encoder")
  25.     vae_decoder = VAEDecoder(tiny_vae)
  26.     ov_model = ov.convert_model(vae_decoder, example_input=torch.zeros((1,4,64,64)))
  27.     ov.save_model(ov_model, output_path / "vae_decoder/openvino_model.xml")
  28.     tiny_vae.save_config(output_path / "vae_decoder")    
  29. convert_tiny_vae(save_path, model_dir)

## 从文本到图像生成

现在,我们就可以进行文本到图像的生成了,我们使用优化后的 openvino pipeline 加载转换后的模型文件并推理;只需要指定一个文本输入,就可以生成我们想要的图像结果。

  1. from optimum.intel.openvino import OVStableDiffusionXLPipeline
  2. device='AUTO'  # 这里直接指定AUTO,可以写成CPU
  3. model_dir = "./sdxl_vino_model"
  4. text2image_pipe = OVStableDiffusionXLPipeline.from_pretrained(model_dir, device=device)
  1. import numpy as np
  2. prompt = "cute cat"
  3. image = text2image_pipe(prompt, num_inference_steps=1, height=512, width=512, guidance_scale=0.0, generator=np.random.RandomState(987)).images[0]
  4. image.save("cat.png")
  5. image

  1. # 清除资源占用
  2. import gc
  3. del text2image_pipe
  4. gc.collect()

## 从图片到图片生成

我们还可以实现从图片到图片的扩散模型生成,将刚才产出的文生图图片进行二次图像生成即可。

  1. from optimum.intel import OVStableDiffusionXLImg2ImgPipeline
  2. model_dir = "./sdxl_vino_model"
  3. device='AUTO'  # 'CPU'
  4. image2image_pipe = OVStableDiffusionXLImg2ImgPipeline.from_pretrained(model_dir, device=device)
  1.     Compiling the vae_decoder to AUTO ...
  2.     Compiling the unet to AUTO ...
  3.     Compiling the vae_encoder to AUTO ...
  4.     Compiling the text_encoder_2 to AUTO ...
  5.     Compiling the text_encoder to AUTO ...
  1. photo_prompt = "a cute cat with bow tie"
  2. photo_image = image2image_pipe(photo_prompt, image=image, num_inference_steps=2, generator=np.random.RandomState(511), guidance_scale=0.0, strength=0.5).images[0]
  3. photo_image.save("cat_tie.png")
  4. photo_image

## 量化

NNCF(Neural Network Compression Framework)是一款神经网络压缩框架,通过对OpenVINO IR格式模型的压缩与量化巍以便更好的提升模型在 Intel 设备上部署的推理性能。

[NNCF](https://github.com/openvinotoolkit/nncf/) 通过在模型图中添加量化层,并使用训练数据集的子集来微调这些额外的量化层的参数,实现了后训练量化。量化后的权重结果将是 INT8 而不是 FP32/FP16,从而加快了模型的推理速度。

根据 SDXL-Turbo Model 的结构,UNet 模型占据了整个流水线执行时间的重要部分。现在我们将展示如何使用  [NNCF](https://github.com/openvinotoolkit/nncf/)  对 UNet 部分进行优化,以减少计算成本并加快流水线速度。至于其余部分不需要量化,因为并不能显著提高推理性能,但可能会导致准确性的大幅降低。

量化过程包含以下步骤:

- 为量化创建一个校准数据集。

- 运行 nncf.quantize() 来获取量化模型。

- 使用 openvino.save_model() 函数保存 INT8 模型。

注:由于量化需要一定的硬件资源(64G以上的内存),之后我直接附上了量化后的模型,你可以直接下载使用。

  1. from pathlib import Path
  2. import openvino as ov
  3. from optimum.intel.openvino import OVStableDiffusionXLPipeline
  4. import os
  5. core = ov.Core()
  6. model_dir = Path("./sdxl_vino_model")
  7. UNET_INT8_OV_PATH = model_dir / "optimized_unet" / "openvino_model.xml"
  8. import datasets
  9. import numpy as np
  10. from tqdm import tqdm
  11. from transformers import set_seed
  12. from typing import Any, Dict, List
  13. set_seed(1)
  14. class CompiledModelDecorator(ov.CompiledModel):
  15.     def __init__(self, compiled_model: ov.CompiledModel, data_cache: List[Any] = None):
  16.         super().__init__(compiled_model)
  17.         self.data_cache = data_cache if data_cache else []
  18.     def __call__(self, *args, **kwargs):
  19.         self.data_cache.append(*args)
  20.         return super().__call__(*args, **kwargs)
  21. def collect_calibration_data(pipe, subset_size: int) -> List[Dict]:
  22.     original_unet = pipe.unet.request
  23.     pipe.unet.request = CompiledModelDecorator(original_unet)
  24.     dataset = datasets.load_dataset("conceptual_captions", split="train").shuffle(seed=42)
  25.     # Run inference for data collection
  26.     pbar = tqdm(total=subset_size)
  27.     diff = 0
  28.     for batch in dataset:
  29.         prompt = batch["caption"]
  30.         if len(prompt) > pipe.tokenizer.model_max_length:
  31.             continue
  32.         _ = pipe(
  33.             prompt,
  34.             num_inference_steps=1,
  35.             height=512,
  36.             width=512,
  37.             guidance_scale=0.0,
  38.             generator=np.random.RandomState(987)
  39.         )
  40.         collected_subset_size = len(pipe.unet.request.data_cache)
  41.         if collected_subset_size >= subset_size:
  42.             pbar.update(subset_size - pbar.n)
  43.             break
  44.         pbar.update(collected_subset_size - diff)
  45.         diff = collected_subset_size
  46.     calibration_dataset = pipe.unet.request.data_cache
  47.     pipe.unet.request = original_unet
  48.     return calibration_dataset
  1. if not UNET_INT8_OV_PATH.exists():
  2.     text2image_pipe = OVStableDiffusionXLPipeline.from_pretrained(model_dir)
  3.     unet_calibration_data = collect_calibration_data(text2image_pipe, subset_size=200)
  1. import nncf
  2. from nncf.scopes import IgnoredScope
  3. UNET_OV_PATH = model_dir / "unet" / "openvino_model.xml"
  4. if not UNET_INT8_OV_PATH.exists():
  5.     unet = core.read_model(UNET_OV_PATH)
  6.     quantized_unet = nncf.quantize(
  7.         model=unet,
  8.         model_type=nncf.ModelType.TRANSFORMER,
  9.         calibration_dataset=nncf.Dataset(unet_calibration_data),
  10.         ignored_scope=IgnoredScope(
  11.             names=[
  12.                 "__module.model.conv_in/aten::_convolution/Convolution",
  13.                 "__module.model.up_blocks.2.resnets.2.conv_shortcut/aten::_convolution/Convolution",
  14.                 "__module.model.conv_out/aten::_convolution/Convolution"
  15.             ],
  16.         ),
  17.     )
  18.     ov.save_model(quantized_unet, UNET_INT8_OV_PATH)

## 运行量化后模型

由于量化 unet 的过程需要的内存可能比较大,且耗时较长,我提前导出了量化后 unet 模型,此处给出下载地址:

链接: https://pan.baidu.com/s/1WMAsgFFkKKp-EAS6M1wK1g 提取码: psta

下载后解压到目标文件夹 `sdxl_vino_model` 即可运行量化后的 int8 unet 模型。

从文本到图像生成

  1. from pathlib import Path
  2. import openvino as ov
  3. from optimum.intel.openvino import OVStableDiffusionXLPipeline
  4. import numpy as np
  5. core = ov.Core()
  6. model_dir = Path("./sdxl_vino_model")
  7. UNET_INT8_OV_PATH = model_dir / "optimized_unet" / "openvino_model.xml"
  8. int8_text2image_pipe = OVStableDiffusionXLPipeline.from_pretrained(model_dir, compile=False)
  9. int8_text2image_pipe.unet.model = core.read_model(UNET_INT8_OV_PATH)
  10. int8_text2image_pipe.unet.request = None
  11. prompt = "cute cat"
  12. image = int8_text2image_pipe(prompt, num_inference_steps=1, height=512, width=512, guidance_scale=0.0, generator=np.random.RandomState(987)).images[0]
  13. display(image)
  1.     Compiling the text_encoder to CPU ...
  2.     Compiling the text_encoder_2 to CPU ...
  3.       0%|          | 0/1 [00:00<?, ?it/s]
  4.     Compiling the unet to CPU ...
  5.     Compiling the vae_decoder to CPU ...

  1. import gc
  2. del int8_text2image_pipe
  3. gc.collect()

从图片到图片生成

  1. from optimum.intel import OVStableDiffusionXLImg2ImgPipeline
  2. int8_image2image_pipe = OVStableDiffusionXLImg2ImgPipeline.from_pretrained(model_dir, compile=False)
  3. int8_image2image_pipe.unet.model = core.read_model(UNET_INT8_OV_PATH)
  4. int8_image2image_pipe.unet.request = None
  5. photo_prompt = "a cute cat with bow tie"
  6. photo_image = int8_image2image_pipe(photo_prompt, image=image, num_inference_steps=2, generator=np.random.RandomState(511), guidance_scale=0.0, strength=0.5).images[0]
  7. display(photo_image)
  1.     Compiling the text_encoder to CPU ...
  2.     Compiling the text_encoder_2 to CPU ...
  3.     Compiling the vae_encoder to CPU ...
  4.       0%|          | 0/1 [00:00<?, ?it/s]
  5.     Compiling the unet to CPU ...
  6.     Compiling the vae_decoder to CPU ...

我们可以对比量化后的 unet 模型大小减少,可以看到量化对模型大小的压缩是非常显著的

  1. from pathlib import Path
  2. model_dir = Path("./sdxl_vino_model")
  3. UNET_OV_PATH = model_dir / "unet" / "openvino_model.xml"
  4. UNET_INT8_OV_PATH = model_dir / "optimized_unet" / "openvino_model.xml"
  5. fp16_ir_model_size = UNET_OV_PATH.with_suffix(".bin").stat().st_size / 1024
  6. quantized_model_size = UNET_INT8_OV_PATH.with_suffix(".bin").stat().st_size / 1024
  7. print(f"FP16 model size: {fp16_ir_model_size:.2f} KB")
  8. print(f"INT8 model size: {quantized_model_size:.2f} KB")
  9. print(f"Model compression rate: {fp16_ir_model_size / quantized_model_size:.3f}")
  1.     FP16 model size: 5014578.27 KB
  2.     INT8 model size: 2513501.39 KB
  3.     Model compression rate: 1.995

运行下列代码可以对量化前后模型推理速度进行简单比较,我们可以发现速度几乎加速了一倍,NNCF 使我们在CPU上生成一张图的时间缩短到两秒之内:

  1. FP16 pipeline latency: 3.148
  2. INT8 pipeline latency: 1.558
  3. Text-to-Image generation speed up: 2.020
  1. import time
  2. def calculate_inference_time(pipe):
  3.     inference_time = []
  4.     for prompt in ['cat']*10:
  5.         start = time.perf_counter()
  6.         _ = pipe(
  7.             prompt,
  8.             num_inference_steps=1,
  9.             guidance_scale=0.0,
  10.             generator=np.random.RandomState(23)
  11.         ).images[0]
  12.         end = time.perf_counter()
  13.         delta = end - start
  14.         inference_time.append(delta)
  15.     return np.median(inference_time)
  1. int8_latency = calculate_inference_time(int8_text2image_pipe)
  2. text2image_pipe = OVStableDiffusionXLPipeline.from_pretrained(model_dir)
  3. fp_latency = calculate_inference_time(text2image_pipe)
  4. print(f"FP16 pipeline latency: {fp_latency:.3f}")
  5. print(f"INT8 pipeline latency: {int8_latency:.3f}")
  6. print(f"Text-to-Image generation speed up: {fp_latency / int8_latency:.3f}")

## 可交互前端demo

最后,为了方便推理使用,这里附上了 gradio 前端运行 demo,你可以利用他轻松生成你想要生成的图像,并尝试不同组合。

  1. import gradio as gr
  2. from pathlib import Path
  3. import openvino as ov
  4. import numpy as np
  5. core = ov.Core()
  6. model_dir = Path("./sdxl_vino_model")
  7. # 如果你只有量化前模型,请使用这个地址并注释 optimized_unet 地址:
  8. # UNET_PATH = model_dir / "unet" / "openvino_model.xml"
  9. UNET_PATH = model_dir / "optimized_unet" / "openvino_model.xml"
  10. from optimum.intel.openvino import OVStableDiffusionXLPipeline
  11. text2image_pipe = OVStableDiffusionXLPipeline.from_pretrained(model_dir)
  12. text2image_pipe.unet.model = core.read_model(UNET_PATH)
  13. text2image_pipe.unet.request = core.compile_model(text2image_pipe.unet.model)
  14. def generate_from_text(text, seed, num_steps, height, width):
  15.     result = text2image_pipe(text, num_inference_steps=num_steps, guidance_scale=0.0, generator=np.random.RandomState(seed), height=height, width=width).images[0]
  16.     return result
  17. with gr.Blocks() as demo:
  18.     with gr.Column():
  19.         positive_input = gr.Textbox(label="Text prompt")
  20.         with gr.Row():
  21.             seed_input = gr.Number(precision=0, label="Seed", value=42, minimum=0)
  22.             steps_input = gr.Slider(label="Steps", value=1, minimum=1, maximum=4, step=1)
  23.             height_input = gr.Slider(label="Height", value=512, minimum=256, maximum=1024, step=32)
  24.             width_input = gr.Slider(label="Width", value=512, minimum=256, maximum=1024, step=32)
  25.             btn = gr.Button()
  26.         out = gr.Image(label="Result (Quantized)" , type="pil", width=512)
  27.         btn.click(generate_from_text, [positive_input, seed_input, steps_input, height_input, width_input], out)
  28.         gr.Examples([
  29.             ["cute cat", 999],
  30.             ["underwater world coral reef, colorful jellyfish, 35mm, cinematic lighting, shallow depth of field,  ultra quality, masterpiece, realistic", 89],
  31.             ["a photo realistic happy white poodle dog ​​playing in the grass, extremely detailed, high res, 8k, masterpiece, dynamic angle", 1569],
  32.             ["Astronaut on Mars watching sunset, best quality, cinematic effects,", 65245],
  33.             ["Black and white street photography of a rainy night in New York, reflections on wet pavement", 48199]
  34.         ], [positive_input, seed_input])
  35. try:
  36.     demo.launch(debug=True)
  37. except Exception:
  38.     demo.launch(share=True, debug=True)

## 总结

利用最新版本的 OpenVINO™ 优化,我们可以很容易实现在家用设备上高效推理图像生成 AI 的能力,加速生成式 AI 在世纪场景下的落地应用;欢迎您与我们一同体验 OpenVINO™ 与 NNCF 在生成式 AI 场景上的强大威力。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/2023面试高手/article/detail/395240
推荐阅读
相关标签
  

闽ICP备14008679号