5.5. 多模型融合

对于 Poplar 来说, 模型会先编译成在 IPU 上可执行的二进制 executable, 然后加载到 IPU 中执行. 由于 Poplar 目前只支持同一时刻加载一个 executable, 如果在一颗 IPU 上执行多个模型, 会造成反复加载不同的 executable, 从而导致性能急剧下降.

模型融合功能, 是在编译阶段, 将多个用户模型融合成一个模型, 每个模型作为融合后模型的一个子图, 子图和子图之间通过分支算子隔离. 在运行时, 通过输入模型索引来控制运行哪个子图, 可以大幅减少模型切换的延时.

../_images/model_fusion.png

本教程将展示如何在 PopRT 中实现模型融合并运行. 在阅读本教程之前, 假设读者已经了解以下主题:

5.5.1. 实现 PopRT 模型融合

目前该特性只支持通过 yaml 配置文件的方式来启动, 用户需要提供 yaml 格式的配置文件, 文件内定义有需要融合的 onnx 模型以及相关参数.

yaml 文件示例如下:

Listing 5.5 config.yaml
 1output_dir: './'
 2output_model: 'executable'
 3export_popef: True
 4max_tensor_size: -1
 5ipu_version: 'ipu21'
 6model_fusion:
 7  - input_model: 'model0.onnx'
 8    input_shape: ['X=1,2', 'Y=1,2']
 9    precision: 'fp32'
10
11  - input_model: 'model1.onnx'
12    input_shape: ['X=1,1']
13    precision: 'fp16'

可以在 model_fusion 中为各模型配置独立的参数, 但请注意目前模型融合只支持两个模型. model_fusion 之外的参数是全局参数,它们会对每个待融合模型生效.

例如, 示例中的 max_tensor_size = -1, 因为它定义在全局参数中, 所以会作用于所有模型.

融合后的模型需要通过 PopRT Runtime 来运行, 因此, 必须设置 export_popefTrue 以生成 PopEF.

此外, 为了保证后续推理代码的运行, 需要根据运行的设备, 正确设置 ipu_version, 如 C600 平台, 需要设置 ipu21.

模型融合代码示例如下:

Listing 5.6 compile.py
 1# Copyright (c) 2023 Graphcore Ltd. All rights reserved.
 2import os
 3
 4import numpy as np
 5import onnx
 6
 7from onnx import TensorProto, checker, helper, mapping
 8
 9
10def create_model0(opset_version=11):
11    g0_dtype = TensorProto.FLOAT
12    g0_add = helper.make_node("Add", ["X", "Y"], ["Z"])
13    g0_reshape = helper.make_node("Reshape", ["Z", "C"], ["O"])
14    g0 = helper.make_graph(
15        [g0_add, g0_reshape],
16        'graph',
17        [
18            helper.make_tensor_value_info("X", g0_dtype, (1, 2)),
19            helper.make_tensor_value_info("Y", g0_dtype, (1, 2)),
20        ],
21        [
22            helper.make_tensor_value_info("O", g0_dtype, (2,)),
23        ],
24    )
25
26    g0_const_type = TensorProto.INT64
27    g0_const = helper.make_tensor(
28        "C",
29        g0_const_type,
30        (1,),
31        vals=np.array([2], dtype=mapping.TENSOR_TYPE_TO_NP_TYPE[g0_const_type])
32        .flatten()
33        .tobytes(),
34        raw=True,
35    )
36    g0.initializer.append(g0_const)
37    m0 = helper.make_model(g0, opset_imports=[helper.make_opsetid("", opset_version)])
38    checker.check_model(m0)
39    return m0
40
41
42def create_model1(opset_version=11):
43    g1_dtype = TensorProto.FLOAT16
44    g1_concat = helper.make_node("Concat", ["X", "C"], ["O"], axis=1)
45    g1 = helper.make_graph(
46        [g1_concat],
47        'graph',
48        [
49            helper.make_tensor_value_info("X", g1_dtype, (1, 1)),
50        ],
51        [
52            helper.make_tensor_value_info("O", g1_dtype, (1, 3)),
53        ],
54    )
55
56    g1_const = helper.make_tensor(
57        "C",
58        g1_dtype,
59        (1, 2),
60        vals=np.array([[1.5, 2.0]], dtype=mapping.TENSOR_TYPE_TO_NP_TYPE[g1_dtype])
61        .flatten()
62        .tobytes(),
63        raw=True,
64    )
65    g1.initializer.append(g1_const)
66    m1 = helper.make_model(g1, opset_imports=[helper.make_opsetid("", opset_version)])
67    checker.check_model(m1)
68    return m1
69
70
71def create_onnx(opset):
72    model0 = create_model0(opset)
73    model1 = create_model1(opset)
74
75    onnx.save(model0, "model0.onnx")
76    onnx.save(model1, "model1.onnx")
77
78
79if __name__ == '__main__':
80    abs_path = os.path.abspath(os.path.dirname(__file__))
81    if os.getcwd() != abs_path:
82        raise RuntimeError(f"Please run program in {abs_path}")
83
84    create_onnx(opset=11)
85
86    cmd = "poprt \
87            --config_yaml config.yaml "
88
89    os.system(cmd)

Download compile.py

此示例会创建上示 yaml 文件需要的 onnx 模型, 并调用 PopRT 读取上述 yaml 进行模型融合, 生成 yaml 中设置的 executable.popef. 编译完成后, 可以通过 popef_dump 来查看融合模型的元数据, 它是后续运行时所需的必要信息.

命令行示例如下:

popef_dump executable.popef

成功运行上述命令后, 终端将会显示此 PopEF 文件的相关信息, 在 Anchors 关键字下, 可以看到编译后静态图的输入/输出名, 类型, 形状等信息, 其中将会有名为 index 的输入, 用于控制子图的选择.

5.5.2. 实现 PopRT Runtime 融合模型推理

要运行 PopRT 模型融合编译得到的 PopEF 文件, 需要编写程序, 通过 PopRT Runtime 进行推理.

程序示例如下:

Listing 5.7 run.py
 1# Copyright (c) 2023 Graphcore Ltd. All rights reserved.
 2import os
 3
 4import numpy as np
 5import numpy.testing as npt
 6
 7from poprt.runtime import ModelRunner, RuntimeConfig
 8
 9if __name__ == '__main__':
10    abs_path = os.path.abspath(os.path.dirname(__file__))
11    if os.getcwd() != abs_path:
12        raise RuntimeError(f"Please run program in {abs_path}")
13
14    popef_path = f"{abs_path}/executable.popef"
15
16    config = RuntimeConfig()
17    config.validate_io_params = False
18    runner = ModelRunner(popef_path, config)
19
20    index = np.array([0], dtype=np.uint32)
21    g0_X = np.ones([1, 2], dtype=np.float32)
22    g0_Y = np.ones([1, 2], dtype=np.float32) * 2
23    g0_O = np.zeros([2], dtype=np.float32)
24
25    runner.execute(
26        {
27            "index": index,
28            "graph0/X": g0_X,
29            "graph0/Y": g0_Y,
30        },
31        {
32            "graph0/O": g0_O,
33        },
34    )
35    npt.assert_array_equal(g0_O, np.ones([2], dtype=np.float32) * 3)
36
37    index = np.array([1], dtype=np.uint32)
38    g1_X = np.zeros([1, 1], dtype=np.float16)
39    g1_O = np.zeros([1, 3], dtype=np.float16)
40
41    runner.execute(
42        {
43            "index": index,
44            "graph1/X": g1_X,
45        },
46        {
47            "graph1/O": g1_O,
48        },
49    )
50    npt.assert_array_equal(g1_O, np.array([[0.0, 1.5, 2.0]], dtype=np.float16))

Download run.py

此示例展示了如何运行前述生成的 PopEF 文件.

第一步, 需要创建 RuntimeConfig 实例, 配置 ModelRunner 的运行参数. 注意请务必设置 validate_io_paramsFalse, 由于融合图不需要为每个输入/输出处理数据, 如设置为 True, 将会引发错误.

第二步, 创建 ModelRunner 实例, 加载 PopEF 文件, 即可将模型加载到 IPU.

第三步, 设置子图的输入/输出, 以及通过 index 指定子图, 如设置为 0 即可指定子图 0.