5.10. 使用 Manual Sharding
PopRT Manual Sharding 支持通过用户提供的切分点将模型划分成不同的子图, 实现模型并行和流水线并行.
5.10.2. Pipelining / 流水线并行
PopRT 支持根据用户的提供的切分点将 ONNX graph 切分成不同的流水线阶段, 实现流水线并行, 提高 Throughput.
参考: Pipelining 原理说明
Note
使用流水线并行, 需要基于模型并行并对 PopRT backend options 进行如下设置:
options.enable_pipelining = True
options.batches_per_step = 流水线阶段数量的整数倍
5.10.3. Manual Sharding 流程
PopRT Manual Sharding 基于 ONNX node 对 ONNX graph 进行切分, 切分点可以是任意的 ONNX node.
ONNX graph 中 nodes 是按拓扑排序的顺序排列的. PopRT Manual Sharding 首先对用户设置的切分点进行拓扑排序.
遍历切分点, 以切分点为起点向输入方向遍历 ONNX graph, 将遍历到的所有 ONNX node 放入一个子图中, 如果遇到 node 没有输入 node 或 node 已经设置了切分信息则停止该分支的遍历.
遍历完整后将得到子图, 以 ONNX attribute 的方式对子图设置切分信息:
__ipu_number
指定模型并行中每个子图对应的设备序号__pipeline_stage
指定流水线并行中每个子图对应的流水线阶段.
Note
通常不同的切分点对应不同的设备序号和流水线阶段, 但同一个设备上可以有多个子图和多个流水线阶段.
根据切分点设置切分信息后, 剩余没有设置切分信息的 node 将被自动设置:
__ipu_number
将被设置为当前已设置的最大设备序号 + 1.__pipeline_stage
将被设置为当前已设置的最大流水线阶段 + 1.
5.10.4. 配置 Manual Sharding
Manual Sharding 有两种配置方法, 一种是通过 PopRT CLI, 另一种是通过 poprt.converter.Sharder
API.
通过 PopRT CLI 配置 Manual Sharding
通过 yaml 文件指定切分点名称, 设备序号和流水线阶段:
1-
2 node: resnetv17_stage1__plus0
3 device: 0
4 stage: 0
5-
6 node: resnetv17_stage4_batchnorm2_fwd
7 device: 1
8 stage: 1
9-
10 node: resnetv17_stage4__plus0
11 device: 2
12 stage: 2
通过 PopRT CLI 中通过
--manual_sharding_config
来配置切分信息:
poprt \
--input_model model.onnx \
--manual_sharding_config shard.yaml
通过 PopRT CLI 中
--only_manual_sharding
来确定是否仅对input_model
进行 Manual Sharding, 默认不设置.不设置
--only_manual_sharding
表示对input_model
进行 Convert 优化后再进行 Manual Sharding.设置
--only_manual_sharding
表示对input_model
仅进行 Manual Sharding, 仅支持--input_model
,--output_model
,--output_dir
和--manual_sharding_config
, 其它参数无效.
poprt \
--input_model model.onnx \
--manual_sharding_config shard.yaml \
--only_manual_sharding
通过 poprt.converter.Sharder
API 配置 Manual Sharding
sharding_info = {
"resnetv17_stage1__plus0": 0,
"resnetv17_stage4_batchnorm2_fwd": 1,
"resnetv17_stage4__plus0: 2,
}
pipelining_info = {
"resnetv17_stage1__plus0": 0,
"resnetv17_stage4_batchnorm2_fwd": 1,
"resnetv17_stage4__plus0: 2,
}
sharded_model = poprt.converter.Sharder(
sharding_info=sharding_info,
pipelining_info=pipelining_info
).run(converted_model)
Note
设置
--only_manual_sharding
的 CLI 或 使用poprt.converter.Sharder
API 需要保证 ONNX graph 中每一个 node 都有unique name
.不设置
--only_manual_sharding
的 CLI 无需保证 ONNX graph 中每一个 node 都有unique name
, Convert 优化过程会保证每一个 node 都有unique name
.
5.10.5. 示例
下面是一个简单的 Manual Sharding 的 example:
以 ResNet50 为例.
1# Copyright (c) 2023 Graphcore Ltd. All rights reserved.
2
3import datetime
4
5import numpy as np
6import onnx
7import requests
8
9from poprt import runtime
10from poprt.compiler import Compiler, CompilerOptions
11from poprt.converter import Sharder
12
13
14def load_model():
15 # Download model
16 url = 'https://github.com/onnx/models/raw/main/vision/classification/resnet/model/resnet50-v1-7.onnx'
17 response = requests.get(url)
18 if response.status_code == 200:
19 model = onnx.load_model_from_string(response.content)
20 else:
21 raise Exception(
22 f"Failed to download model with status_code {response.status_code}"
23 )
24 return model
25
26
27def manual_sharding(model):
28 # Fix the batch size to 1
29 model.graph.input[0].type.tensor_type.shape.dim[0].dim_value = 1
30
31 # Sharding and pipelining info
32 sharding_info = {
33 "resnetv17_stage1__plus0": 0,
34 "resnetv17_stage4_batchnorm2_fwd": 1,
35 "resnetv17_stage4__plus0": 2,
36 }
37 pipelining_info = {
38 "resnetv17_stage1__plus0": 0,
39 "resnetv17_stage4_batchnorm2_fwd": 1,
40 "resnetv17_stage4__plus0": 2,
41 }
42 model = Sharder(sharding_info=sharding_info, pipelining_info=pipelining_info).run(
43 model
44 )
45
46 return model
47
48
49def compile(model):
50 # Compile the model with backend options
51 model_bytes = model.SerializeToString()
52 outputs = [o.name for o in model.graph.output]
53
54 options = CompilerOptions()
55 options.ipu_version = runtime.DeviceManager().ipu_hardware_version()
56 # Sharding into 4 IPUs
57 options.num_ipus = 4
58 # Enable Sharding and Pipelining
59 options.enable_pipelining = True
60 options.virtual_graph_mode = "manual"
61 options.batches_per_step = 16
62
63 executable = Compiler.compile(model_bytes, outputs, options)
64 runner_config = runtime.RuntimeConfig()
65 runner_config.timeout_ns = datetime.timedelta(microseconds=0)
66 runner = runtime.Runner(executable, runner_config)
67 return runner
68
69
70def run(runner):
71 inputs_info = runner.get_execute_inputs()
72 outputs_info = runner.get_execute_outputs()
73
74 inputs = {}
75 for i in inputs_info:
76 inputs[i.name] = np.ones(i.shape, dtype=i.numpy_data_type())
77
78 outputs = {}
79 for o in outputs_info:
80 outputs[o.name] = np.zeros(o.shape, dtype=o.numpy_data_type())
81
82 runner.execute(inputs, outputs)
83
84
85if __name__ == '__main__':
86 model = load_model()
87 model = manual_sharding(model)
88 runner = compile(model)
89 run(runner)