
# pipeline代码适配
onnx pipeline的主要作用是将onnx模型进行一系列编排，并在onnx Runtime上按照编排顺序执行。因此，需要将转换得到的mindir模型按照相同的逻辑进行编排，并在MindSpore Lite上执行。只需要将原始onnx的pipeline中涉及到onnx模型初始化及推理的接口替换为MindSpore Lite的接口即可。
MindSpore Lite提供了Python、C++以及JAVA三种应用开发接口。此处以Python接口为例，介绍如何使用MindSpore Lite Python API构建并推理Stable Diffusion模型，更多信息请参考[MindSpore Lite应用开发](https://www.mindspore.cn/lite/docs/zh-CN/r2.3.1/use/cloud_infer/runtime_python.html)。
以官方onnx pipeline代码为例，其提供的onnx pipeline代码路径在"[${diffusers}/pipelines/stable_diffusion/pipeline_onnx_stable_diffusion_img2img.py](https://github.com/huggingface/diffusers/blob/main/src/diffusers/pipelines/stable_diffusion/pipeline_onnx_stable_diffusion_img2img.py)"，其中${diffusers}表示diffusers包的安装路径，可以通过pip进行查看。
```
# shell
pip show diffusers
```
#### 修改代码依赖
新建并进入/home_host/work/pipeline目录。
```
mkdir -p /home_host/work/pipeline
cd /home_host/work/pipeline
```
将onnx pipeline依赖的图生图源码"pipeline_onnx_stable_diffusion_img2img.py"复制到该目录下，名称改为"pipeline_onnx_stable_diffusion_img2img_mslite.py"，以便与源文件名称区分。但是这样也会导致无法正确找到源码中相对路径下的依赖，需要将对于diffusers包内的相对路径修改为绝对路径的形式。
图1代码依赖修改前与修改后   
![](https://support.huaweicloud.com/bestpractice-modelarts/figure/zh-cn_image_0000001731995812.png "点击放大")
将推理代码"modelarts-ascend/examples/AIGC/stable_diffusion/onnx_pipeline.py"也复制一份到该目录，名称改为"mslite_pipeline.py"，迁移后的推理代码中的pipeline需要修改为从复制的onnx pipeline文件导入：
```
# onnx_pipeline.py
from pipeline_onnx_stable_diffusion_img2img_mslite import OnnxStableDiffusionImg2ImgPipeline
```
#### 模型初始化
使用MindSpore Lite进行推理时一般需要先设置目标设备的上下文信息，然后构建推理模型，获取输入数据，模型预测并得到最终的结果。一个基础的推理框架写法如下所示：
```
# base_mslite_demo.py
import mindspore_lite as mslite
# 设置目标设备上下文为Ascend，指定device_id为0。
context = mslite.Context()
context.target = ["ascend"]
context.ascend.device_id = 0
# 构建模型。
model = mslite.Model()
model.build_from_file("./model.mindir", mslite.ModelType.MINDIR, context)
# 输入数据到Device侧，针对于多输入场景可以通过list来指定输入。
in_data = [np.array(data1), np.array(data2)]
inputs = model.get_inputs()
for i, _inputs in enumerate(inputs):
    _input.set_data_from_numpy(in_data[i])
# 前向推理，并将结果从device侧传到host侧。
outputs = model.predict(inputs)
outputs = [output.get_data_to_numpy() for output in outputs]
# 后处理...
```
为了同时兼容onnx模型和mindir模型都能够在适配后的pipeline中运行，需要对于Model进行封装。MsliteModel各参数模型说明已给出，根据模型初始化参数设置当前模型使用onnx模型（运行在CPU上）或mindir模型（运行在昇腾设备上），也能够方便进行精度的校验。
```
# mslite_model_proxy.py
import os
import mindspore_lite as mslite
class MsliteModel:
    def __init__(self, model_path, model_name='ms model', device_type='ascend', use_ascend=True,
                 onnx_runtime_model=None, get_shape=False, resize_shape=False) -> None:
        """
        mindir模型代理类
        Args:
            model_path: mindir文件路径
            model_name: 模型名称
            device_type: 设备类型
            use_ascend: 是否使用Ascend
            onnx_runtime_model: onnx模型对象
            get_shape: 是否获取模型shape信息、输入数据shape信息
            resize_shape: resize shape开关，分档模型需开启
        """
        print('model_path:{}'.format(model_path))
        self.model_name = model_name
        self.context = MsliteModel.init_context(device_type)
        self.model = mslite.Model()
        self.model.build_from_file(model_path, mslite.ModelType.MINDIR, self.context)
        self.ms_inputs = self.model.get_inputs()
        self.use_ascend = use_ascend
        self.onnx_runtime_model = onnx_runtime_model
        self.get_shape = get_shape
        self.resize_shape = resize_shape
    @staticmethod
    def init_context(device_type='ascend'):
        context = mslite.Context()
        context.target = [device_type]
        context.ascend.device_id = int(os.getenv('DEVICE_ID') or 0)
        context.cpu.thread_num = 1 if device_type == 'ascend' else 32
        context.cpu.thread_affinity_mode = 2
        return context
    def __call__(self, **kwargs):
        if not self.use_ascend:
            return self.onnx_runtime_model(**kwargs)
        inputs = list(kwargs.values())
        if len(inputs) <= 0:
            raise Exception('get tensor input info failed')
        ms_input = self.model.get_inputs()
        if self.get_shape:
            print(f'{self.model_name} shape info:')
            for index, val in enumerate(self.model.get_inputs()):
                print(f'{self.model_name}: param{index} shape -> {val.shape}')
            shapes = [list(input.shape) for input in inputs]
            print(f"inputs: input_shape -> {shapes}")
        if self.resize_shape:
            self.model.resize(ms_input, [list(input.shape) for input in inputs])
        for index, val in enumerate(ms_input):
            val.set_data_from_numpy(inputs[index])
        outputs = self.model.predict(ms_input)
        outputs = [output.get_data_to_numpy() for output in outputs]
        return outputs
```
适配MindSpore Lite Runtime到onnx pipeline，首先需要初始化MindSpore LiteModel对象，通过在OnnxStableDiffusionImg2ImgPipeline中增加mindir模型初始化函数，然后在pipeline类的__init__方法调用该函数，在pipeline初始化的时候直接初始化模型。您可以参照如下样例，通过修改use_ascend修改该模型是否使用mindir运行，也可以编写代码通过环境变量指定。
```
# pipeline_onnx_stable_diffusion_img2img_mslite.py
class OnnxStableDiffusionImg2ImgPipeline(DiffusionPipeline):
    ...
    def mslite_modules_init(self):
        self.text_encoder_ms = MsliteModel(
            model_path=os.environ['TEXT_ENCODER_PATH'], 
            model_name='text_encoder', use_ascend=True, 
            onnx_runtime_model=self.text_encoder)
        self.vae_encoder_ms = MsliteModel(
            model_path=os.environ['VAE_ENCODER_PATH'], 
            model_name='vae_encoder', use_ascend=True, 
            onnx_runtime_model=self.vae_encoder)
        self.unet_ms = MsliteModel(model_path=os.environ['UNET_PATH'], 
                                   model_name='unet', use_ascend=True, 
                                   onnx_runtime_model=self.unet)
        self.vae_decoder_ms = MsliteModel(model_path=os.environ['VAE_DECODER_PATH'], 
                                        model_name='vae_decoder', use_ascend=True, 
                                        onnx_runtime_model=self.vae_decoder)
        self.safety_checker_ms = MsliteModel(model_path=os.environ['SAFETY_CHECKER_PATH'], 
                                            model_name='safety_checker', use_ascend=True, 
                                            onnx_runtime_model=self.safety_checker)
    ...
```
#### 模型推理适配
完成模型初始化后，您需要将onnx模型推理的代码等价替换为对应的mindir模型推理接口。以vae_encoder模型为例，在pipeline代码中查找vae_encoder推理调用的地方，然后修改为对应的MindSpore Lite版本的推理接口模型。
- 使用MindSpore Lite Runtime接口替换onnx Runtime接口。
  ```
  # pipeline_onnx_stable_diffusion_img2img_mslite.py
  …
  # onnx模型
  # init_latents = self.vae_encoder(sample=image)[0]
  # ----------------修改点-----------------
  # mslite模型
  init_latents = self.vae_encoder_ms(sample=image)[0]
  ...
  ```
  
- 替换内嵌模型。
  ```
  # pipeline_onnx_stable_diffusion_img2img_mslite.py
  …
  # onnx模型
  # image = np.concatenate([self.vae_decoder(latent_sample=latents[i : i + 1])[0] for i in range(latents.shape[0])])
  # ----------------修改点-----------------
  # mslite模型
  image = np.concatenate([self.vae_decoder_ms(latent_sample=latents[i : i + 1])[0] for i in range(latents.shape[0])])
  ...
  ```
  
修改后的文件参考[Gitee代码库](https://gitee.com/ModelArts/modelarts-ascend/tree/master/examples/AIGC/stable_diffusion)中的如下两个文件：
- pipeline_onnx_stable_diffusion_img2img_mslite.py
- mslite_model_proxy.py
 
#### 运行pipeline代码
pipeline代码如下：
```
# mslite_pipeline.py
import os
import requests
import torch
import numpy as np
from PIL import Image
from io import BytesIO
from pipeline_onnx_stable_diffusion_img2img_mslite import OnnxStableDiffusionImg2ImgPipeline
def setup_seed(seed):
    torch.manual_seed(seed)
    torch.cuda.manual_seed_all(seed)
    np.random.seed(seed)
    torch.backends.cudnn.deterministic = True
setup_seed(0)
# 指定mindir和onnx模型路径。
mindir_dir = "/home_host/work/static_shape_convert/mindir_models"
onnx_model_path = "/home_host/work/runwayml/onnx_models"
os.environ['DEVICE_ID'] = "0"
os.environ['TEXT_ENCODER_PATH'] = f"{mindir_dir}/text_encoder.mindir"
os.environ['VAE_ENCODER_PATH'] = f"{mindir_dir}/vae_encoder.mindir"
os.environ['UNET_PATH'] = f"{mindir_dir}/unet_graph.mindir"
os.environ['VAE_DECODER_PATH'] = f"{mindir_dir}/vae_decoder.mindir"
os.environ['SAFETY_CHECKER_PATH'] = f"{mindir_dir}/safety_checker.mindir"
pipe = OnnxStableDiffusionImg2ImgPipeline.from_pretrained(onnx_model_path,
       torch_dtype=torch.float32).to("cpu")
url = "https://raw.githubusercontent.com/CompVis/stable-diffusion/main/assets/stable-samples/img2img/sketch-mountains-input.jpg"
response = requests.get(url, verify=False)
init_image = Image.open(BytesIO(response.content)).convert("RGB")
init_image = init_image.resize((512, 512))
prompt = "A fantasy landscape, trending on artstation"
images = pipe(prompt=prompt, image=init_image, strength=0.75, guidance_scale=7.5).images
images[0].save("fantasy_landscape_npu.png")
```
在运行pipeline时，默认的加速卡为0号卡，当机器有多人使用时，可能存在资源占用而无法正常运行的情况，可以通过环境变量指定加速卡ID，如指定5号卡进行执行。
```
# mslite_pipeline.py
…
os.environ['DEVICE_ID'] = "5"
…
```
最后执行python脚本进行推理：
```
# shell
python mslite_pipeline.py
```
图2执行推理脚本   
![](https://support.huaweicloud.com/bestpractice-modelarts/figure/zh-cn_image_0000001778915825.png "点击放大")
图3MindSpore Lite pipeline输出的结果图片   
![](https://support.huaweicloud.com/bestpractice-modelarts/figure/zh-cn_image_0000001731836672.png "点击放大")
