Files
paddlepaddle--paddle/python/paddle/distributed/fleet/utils/hybrid_parallel_inference.py
T
2026-07-13 12:40:42 +08:00

843 lines
33 KiB
Python

# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from collections import defaultdict
import numpy as np
# (TODO: GhostScreaming) It will be removed later.
from paddle.base import core
from paddle.distributed import fleet
from paddle.framework import Block, Program, in_dynamic_mode
class HybridParallelInferenceHelper:
"""
A helper class to split program for inference with hybrid parallelism.
Args:
startup_program (Program): the startup program.
main_program (Program): the main program.
num_mp (int): number of model parallel degree. Default ``1``.
num_pp (int): number of pipeline parallel degree. Default ``1``.
micro_batch_size (int): number of micro batch size. Default ``1``.
beam_size (int): number of beam search size. Default ``1``.
init_comm (bool): whether if initialize communication group. Default ``True``.
role_maker (RoleMakerBase or subclass): user custom define RoleMakerBase.
If ``role_maker==None``, then use PaddleCloudRoleMaker. Default ``None``.
Returns:
None.
Write Paradigm:
.. code-block:: text
:name: text-example1
>>> # doctest: +REQUIRES(env:DISTRIBUTED, env:GPU)
>>> import paddle
>>> # while op pattern
>>> with paddle.base.device_guard(f'{device}:all'):
... # init global cond
... max_len = paddle.full(shape=[1], dtype="int64", fill_value=10)
... step_idx = paddle.full(shape=[1], dtype="int64", fill_value=0)
... cond_int = paddle.full(shape=[1], dtype="int64", fill_value=0, name="cond_int")
... cond = layers.cast(step_idx < max_len, dtype="bool")
... while_op = layers.While(cond, is_test=True)
... # init global lod_tensor_array for generation task
... arr = paddle.tensor.array_write(data, step_idx)
>>> with while_op.block():
... with paddle.base.device_guard(f'{device}:all'):
... # read data from global lod_tensor_array
... element_in_arr = paddle.tensor.array_read(array=arr, i=step_idx)
... # write placeholder data to global lod_tensor_array,
... # it need for send_v2 of lod_tensor_array
... paddle.increment(x=step_idx, value=1.0)
... paddle.tensor.array_write(element_in_arr, i=step_idx, array=arr)
... with paddle.base.device_guard(f'{device}:0'):
... pass # some code
... with paddle.base.device_guard(f'{device}:1'):
... pass # some code
... with paddle.base.device_guard(f'{device}:{num_pp - 1}'):
... # generate some data in while block and write to global lod_tensor_array
... # that they are read in next while step.
... # we will using send_v2 to send global lod_tensor_array to other pipeline and sync
... paddle.tensor.array_write(other_var, i=step_idx, array=arr)
... # update cond and assign to cond_int, we will sync cond_int
... layers.assign(layers.cast(cond, dtype="int32"), cond_int)
... with paddle.base.device_guard(f'{model._device}:all'):
... # the code below must at end of while block and exists in device:all
... layers.assign(layers.cast(cond_int, dtype='bool'), cond)
>>> with paddle.base.device_guard(f'{model._device}:all'):
... # use a empty lod_tensor_array to clear lod_tensor_array
... layers.assign(layers.create_array(data.dtype), arr)
Examples:
.. code-block:: pycon
:name: code-example1
>>> # doctest: +REQUIRES(env:DISTRIBUTED, env:GPU)
>>> import os
>>> import numpy as np
>>> import paddle
>>> import paddle.distributed.fleet as fleet
>>> from paddle.distributed.fleet.utils import hybrid_parallel_inference
>>> paddle.enable_static()
>>> nranks = int(os.getenv("PADDLE_TRAINERS_NUM", 1))
>>> rank = int(os.getenv("PADDLE_TRAINER_ID", 0))
>>> dev_id = int(os.getenv("FLAGS_selected_gpus", 0))
>>> main_program = paddle.static.Program()
>>> startup_program = paddle.static.Program()
>>> if nranks > 1:
... dist_strategy = fleet.DistributedStrategy()
... dist_strategy.without_graph_optimization = True
... fleet.init(is_collective=True, strategy=dist_strategy)
>>> device = "gpu"
>>> with paddle.static.program_guard(main_program, startup_program):
... with paddle.base.device_guard(f'{device}:0'):
... X = paddle.static.data(name='X', shape=[None, 2], dtype='float32')
... with paddle.base.device_guard(f'{device}:all'):
... max_len = paddle.full(shape=[1], dtype="int64", fill_value=5, name="n")
... step_idx = paddle.full(shape=[1], dtype="int64", fill_value=0, name="i")
... data = paddle.tensor.array_write(X, step_idx)
... cond_int = paddle.full(shape=[1], dtype="int64", fill_value=0, name="cond_int")
... cond = paddle.less_than(x=step_idx, y=max_len)
... while_op = paddle.static.nn.control_flow.While(cond, is_test=True)
... with while_op.block():
... with paddle.base.device_guard(f'{device}:all'):
... input = paddle.tensor.array_read(array=data, i=step_idx)
... paddle.increment(x=step_idx, value=1.0)
... paddle.tensor.array_write(input, i=step_idx, array=data)
... with paddle.base.device_guard(f'{device}:0'):
... param_attr = paddle.ParamAttr(initializer=paddle.nn.initializer.Constant(1.0))
... weight1 = paddle.static.create_parameter(shape=[2, 5], dtype='float32', attr=param_attr, is_bias=False)
... hidden1 = paddle.matmul(input, weight1)
... with paddle.base.device_guard(f'{device}:1'):
... param_attr = paddle.ParamAttr(initializer=paddle.nn.initializer.Constant(2.0))
... weight2 = paddle.static.create_parameter(shape=[5, 2], dtype='float32', attr=param_attr, is_bias=False)
... hidden2 = paddle.matmul(hidden1, weight2)
... paddle.tensor.array_write(hidden2, i=step_idx, array=data)
... # update cond and assign to cond_int, we will sync cond_int
... paddle.assign(paddle.less_than(x=step_idx, y=max_len), cond)
... paddle.assign(paddle.cast(cond, dtype="int32"), cond_int)
... with paddle.base.device_guard(f'{device}:all'):
... # the code below must at end of while block and exists in device:all
... paddle.assign(paddle.cast(cond_int, dtype='bool'), cond)
... with paddle.base.device_guard(f'{device}:all'):
... out = paddle.tensor.create_array(data.dtype)
... paddle.assign(data, out)
... with paddle.base.device_guard(f'{device}:all'):
... # use a empty lod_tensor_array to clear lod_tensor_array
... paddle.assign(paddle.tensor.create_array(data.dtype), data)
>>> helper = hybrid_parallel_inference.HybridParallelInferenceHelper(
... startup_program,
... main_program,
... micro_batch_size=2,
... num_pp=2,
... init_comm=nranks > 1,
... )
>>> helper.gen_infer_program(['array_write_0.out'], ['cond_int.tmp_0'])
>>> exe = paddle.static.Executor(paddle.CUDAPlace(dev_id))
>>> exe.run(startup_program)
>>> np.random.seed(2333)
>>> for step in range(5):
... init_data = np.random.uniform(low=0.0, high=1.0, size=[2, 2]).astype('float32')
... [res] = exe.run(main_program, feed={"X": init_data}, fetch_list=[out])
... print('-------- step', step, ' --------')
... print(res)
"""
def __init__(
self,
startup_program,
main_program,
num_mp=1,
num_pp=1,
micro_batch_size=1,
beam_size=1,
init_comm=True,
role_maker=None,
):
assert isinstance(startup_program, Program)
assert isinstance(main_program, Program)
self._device = None
if core.is_compiled_with_cuda():
self._device = "gpu"
assert self._device, "Only gpu are supported."
assert not in_dynamic_mode(), "Only static graph mode is supported."
op_maker = core.op_proto_and_checker_maker
self._op_role = op_maker.OpRole
self._op_role_key = op_maker.kOpRoleAttrName()
self._op_device_key = op_maker.kOpDeviceAttrName()
self._param_device_map = {}
self._pipeline_pair = []
self._pipeline_pair_in_while = []
self._pp_ring_map = {}
self.ring_id = 20 # Just a magic number
self.micro_batch_size = micro_batch_size
self.beam_size = beam_size
self.init_comm = init_comm
self._output_var_to_op = None
self._input_var_to_op = None
self._main_program = main_program
self._startup_program = startup_program
if role_maker is None:
self.role_maker = fleet.base.role_maker.PaddleCloudRoleMaker(
is_collective=True
)
else:
if isinstance(role_maker, fleet.base.role_maker.RoleMakerBase):
assert role_maker._is_collective
self.role_maker = role_maker
# communication_group info
self.mp_ring_id = 0
self.global_ring_id = 1
self.endpoints = self.role_maker._get_trainer_endpoints()
self.current_endpoint = self.endpoints[self.role_maker._worker_index()]
self.rank = self.role_maker._worker_index()
self.nranks = self.role_maker._worker_num()
assert num_mp * num_pp == self.nranks
self.num_pp = num_pp
self.num_mp = num_mp
# global ring info
self.global_endpoints = self.endpoints
self.global_rank = self.rank
self.global_nranks = self.nranks
arr = np.arange(0, self.num_pp * self.num_mp).reshape(
[self.num_pp, self.num_mp]
)
ipp, imp = np.where(arr == self.rank)
ipp = ipp[0]
imp = imp[0]
self.mp_group = arr[ipp, :]
self.pp_group = arr[:, imp]
self._stage = ipp
def _init_communication_group(self):
dev_ids = []
for pair in self._pipeline_pair:
prev_id, cur_id = pair
if prev_id not in dev_ids:
dev_ids.append(prev_id)
if cur_id not in dev_ids:
dev_ids.append(cur_id)
num_pp = len(dev_ids)
num_pp = max(1, num_pp)
assert num_pp == self.num_pp, (
f'num_pp: {num_pp}, self.num_pp: {self.num_pp}'
)
collective_helper = fleet.meta_optimizers.common.CollectiveHelper(
self.role_maker, wait_port=False
)
# Create global rings
collective_helper._init_communicator(
self._startup_program,
self.current_endpoint,
self.global_endpoints,
self.global_rank,
self.global_ring_id,
True,
self.global_ring_id,
True,
)
# Create mp rings
if self.num_mp > 1:
mp_endpoints = [self.endpoints[mp_idx] for mp_idx in self.mp_group]
mp_rank = next(
idx
for idx, mp_idx in enumerate(self.mp_group)
if mp_idx == self.rank
)
collective_helper._init_communicator(
self._startup_program,
self.current_endpoint,
mp_endpoints,
mp_rank,
self.mp_ring_id,
True,
self.global_ring_id,
True,
)
# Create pipeline rings
if self.num_pp > 1:
for pair in self._pipeline_pair:
pair_key = pair[0] * 1000 + pair[1]
ring_id = self._pp_ring_map[pair_key]
first_node = self.pp_group[pair[0]]
second_node = self.pp_group[pair[1]]
if self.rank != first_node and self.rank != second_node:
collective_helper._init_communicator(
self._startup_program,
None,
None,
None,
None,
False,
self.global_ring_id,
True,
)
continue
pipeline_endpoints = [
self.endpoints[first_node],
self.endpoints[second_node],
]
pipeline_rank = 0 if self.rank == first_node else 1
collective_helper._init_communicator(
self._startup_program,
self.current_endpoint,
pipeline_endpoints,
pipeline_rank,
ring_id,
False,
self.global_ring_id,
True,
)
def _get_input_output_info(self, block):
'''
Get info of op input and output.
'''
# A map from output var to op which generate it.
output_var_to_op = defaultdict(list)
# A map from var to op which takes it as input.
input_var_to_op = defaultdict(list)
for index, op in enumerate(block.ops):
for var_name in op.input_arg_names:
input_var_to_op[var_name].append([op, index])
for var_name in op.output_arg_names:
output_var_to_op[var_name].append([op, index])
return output_var_to_op, input_var_to_op
def _update_param_device_map(self):
"""
Get the device info for parameters.
"""
params = [param.name for param in self._main_program.all_parameters()]
for each_block in self._main_program.blocks:
for op in each_block.ops:
for var_name in op.input_arg_names:
if (
var_name not in params
or var_name in self._param_device_map
):
continue
device = op.attr(self._op_device_key)
self._param_device_map[var_name] = device
def _split_program(self, program, stage, block_idx):
"""
Split a program and get the one with the given pipeline stage.
Args:
stage (int): pipeline stage
block_idx (int): block index
Returns:
used_var_names (set): used var names in block_idx block
"""
used_var_names = set()
block = program.block(block_idx)
op_idx = 0
for op in list(block.ops):
op_stage = op.attr(self._op_device_key).split(':')[1]
# Copy ops whose op_device set to "gpu:all" to all sections.
if op_stage == "all" or int(op_stage) == stage:
op_idx += 1
if op.type == "while":
sub_block_id = int(op.attr('sub_block').id)
sub_used_var_names = self._split_program(
program, stage, sub_block_id
)
used_var_names.update(sub_used_var_names)
input_idxs = []
input_arg_names = op.input("X")
for i, name in enumerate(input_arg_names):
if name not in sub_used_var_names:
input_idxs.append(i)
if len(input_idxs) > 0:
for i in reversed(input_idxs):
input_arg_names.pop(i)
op.desc.set_input("X", input_arg_names)
output_idxs = []
output_arg_names = op.output("Out")
for i, name in enumerate(output_arg_names):
if name not in sub_used_var_names:
output_idxs.append(i)
if len(output_idxs) > 0:
for i in reversed(output_idxs):
output_arg_names.pop(i)
op.desc.set_output("Out", output_arg_names)
for var_name in op.input_arg_names + op.output_arg_names:
used_var_names.add(var_name)
else:
block._remove_op(op_idx)
for var_name in list(block.vars.keys()):
if var_name not in used_var_names:
block._remove_var(var_name)
return used_var_names
# def _find_post_op(self, index, var_name):
# """
# Find the post op that has variable named var_name as input.
# """
# # bugfix for uniform hybrid parallelism
# if '.cast_fp32' in var_name:
# var_name = var_name.replace('.cast_fp32', '')
# if '.cast_fp16' in var_name:
# var_name = var_name.replace('.cast_fp16', '')
# post_ops = self._input_var_to_op[var_name]
# if post_ops == None: return None
# result_op = None
# for post_op, post_idx in reversed(post_ops):
# if post_idx > index:
# result_op = post_op
# break
# return result_op
def _find_prev_op(self, index, var_name):
"""
Find the previous op of op with index that outputs
variable named var_name.
"""
prev_ops = self._output_var_to_op[var_name]
if prev_ops is None:
return None
result_op = None
for prev_op, prev_idx in reversed(prev_ops):
if prev_idx < index:
result_op = prev_op
break
return result_op
def _add_op_device_attr(self, block):
"""
Add op_device attribute for ops in block that have
not that attribute set.
Args:
block (Block): the block to process.
"""
assert isinstance(block, Block)
# Ops should be copied to all pipeline stages.
device_all_ops = [
"create_py_reader",
"read",
"create_double_buffer_reader",
"while",
]
for op in block.ops:
if op.type in device_all_ops:
# We use "gpu:all" to represent an op should be put on all
# pipeline stages, such as read ops. Note that: "gpu:all"
# is only used by pipeline as an indicator.
op._set_attr(self._op_device_key, self._device + ":all")
if op.type == "while":
sub_block_id = op.attr('sub_block').id
sub_block = block.program.block(sub_block_id)
self._add_op_device_attr(sub_block)
def _check_validation(self, block):
"""
Check whether ops in a block have both the op_device and the
op_role attributes set.
"""
assert isinstance(block, Block)
pre_stage_id = None
for op in block.ops:
assert op.has_attr(self._op_role_key), (
f"{op.type} has no {self._op_role_key} set ."
)
op_role = op.attr(self._op_role_key)
assert op_role == int(self._op_role.Forward), (
"Only forward is supported for inference."
)
if not op._has_kernel(op.type):
assert op.type in [
"while",
"conditional_block",
], "The only supported op without kernel is while."
sub_block_id = op.attr('sub_block').id
sub_block = block.program.block(sub_block_id)
self._check_validation(sub_block)
assert op.has_attr(self._op_device_key), (
f"{op.type} has no {self._op_device_key} set."
)
device = op.attr(self._op_device_key)
assert device, f"{op.type} has no {self._op_device_key} set."
if device.split(':')[1] == "all":
continue
dev_type = device.split(':')[0]
assert dev_type == self._device
stage_id = int(device.split(':')[1])
pre_stage_id = stage_id
def _insert_sendrecv_ops_for_boundaries(self, block, is_while_block):
"""
Insert a pair of send and recv ops for every two
consecutive ops on different devices.
"""
# A map from var to device where op takes it as input,
# avoiding multiple send and recv ops.
input_var_to_device = {}
extra_index_info = {
'index': 0,
}
for index, op in enumerate(list(block.ops)):
cur_device = op.attr(self._op_device_key)
if cur_device.split(':')[-1] == "all":
continue
for var_name in op.input_arg_names:
if not block.has_var(var_name) and block._find_var_recursive(
var_name
):
continue
var = block.var(var_name)
# skip data var
if var.is_data:
continue
prev_device = None
generate_ops = self._output_var_to_op.get(var_name)
if generate_ops is None:
if var_name not in self._param_device_map:
continue
prev_device = self._param_device_map[var_name]
prev_op = self._find_prev_op(index, var_name)
if not prev_device:
prev_device = (
prev_op.attr(self._op_device_key) if prev_op else None
)
if prev_device is None or prev_device.split(":")[-1] == "all":
continue
if prev_device == cur_device:
continue
if var_name not in input_var_to_device:
input_var_to_device[var_name] = []
if (cur_device, prev_device) in input_var_to_device[var_name]:
continue
assert self._device == cur_device.split(':')[0], (
"More than one device type found."
)
device_type = cur_device.split(':')[0] + ':'
def _insert_send_recv(cur_id, prev_id):
assert cur_id > prev_id
cur_dev = device_type + str(cur_id)
prev_dev = device_type + str(prev_id)
if (cur_dev, prev_dev) in input_var_to_device[var_name]:
return
if cur_id - prev_id > 1:
_insert_send_recv(cur_id - 1, prev_id)
_insert_send_recv(cur_id, cur_id - 1)
input_var_to_device[var_name].append(
(cur_dev, prev_dev)
)
return
assert cur_id - prev_id == 1
input_var_to_device[var_name].append((cur_dev, prev_dev))
op_role = op.attr(self._op_role_key)
var = block.vars[var_name]
pair = (prev_id, cur_id)
if (
is_while_block
and pair not in self._pipeline_pair_in_while
):
self._pipeline_pair_in_while.append(pair)
# 1000 is just a magic number
pair_key = prev_id * 1000 + cur_id
if pair not in self._pipeline_pair:
self._pipeline_pair.append(pair)
self._pp_ring_map[pair_key] = self.ring_id
ring_id = self.ring_id
self.ring_id += 1
else:
ring_id = self._pp_ring_map[pair_key]
block._insert_op_without_sync(
index=index + extra_index_info['index'],
type='send_v2',
inputs={'X': var},
attrs={
self._op_device_key: prev_dev,
self._op_role_key: op_role,
'use_calc_stream': True,
'peer': 1,
'ring_id': ring_id,
},
)
extra_index_info['index'] += 1
var_shape = list(var.shape)
if var_shape[0] < 0:
if is_while_block:
var_shape[0] = (
self.micro_batch_size * self.beam_size
)
else:
var_shape[0] = self.micro_batch_size
block._insert_op_without_sync(
index=index + extra_index_info['index'],
type='recv_v2',
outputs={'Out': [var]},
attrs={
'out_shape': var_shape,
'dtype': var.dtype,
self._op_device_key: cur_dev,
self._op_role_key: op_role,
'use_calc_stream': True,
'peer': 0,
'ring_id': ring_id,
},
)
extra_index_info['index'] += 1
_insert_send_recv(
int(cur_device.split(':')[1]),
int(prev_device.split(':')[1]),
)
block._sync_with_cpp()
def _insert_sendrecv_ops_in_while_block(
self,
block,
sync_in_while_lastpp2firstpp_var_names,
sync_in_while_var_names,
stage,
):
dev_ids = []
for pair in self._pipeline_pair_in_while:
prev_id, cur_id = pair
if prev_id not in dev_ids:
dev_ids.append(prev_id)
if cur_id not in dev_ids:
dev_ids.append(cur_id)
if len(dev_ids) == 0:
return
first_id = min(dev_ids)
last_id = max(dev_ids)
assert len(block.ops) > 2, (
"It must have more than 2 ops in while sub block, "
"layers.assign(layers.cast(cond_int, dtype='bool'), cond) must at end of while block, "
"because nccl cannot send bool dtype var"
)
index = len(block.ops) - 2
for prev_id in dev_ids:
if prev_id == cur_id:
continue
assert cur_id > prev_id
pair = (prev_id, cur_id)
# 1000 is just a magic number
pair_key = prev_id * 1000 + cur_id
if pair not in self._pipeline_pair:
self._pipeline_pair.append(pair)
self._pp_ring_map[pair_key] = self.ring_id
ring_id = self.ring_id
self.ring_id += 1
else:
ring_id = self._pp_ring_map[pair_key]
if cur_id == last_id and prev_id == first_id:
var_names = (
sync_in_while_lastpp2firstpp_var_names
+ sync_in_while_var_names
)
else:
var_names = sync_in_while_var_names
for var_name in var_names:
var = block._var_recursive(var_name)
if stage == cur_id:
block._insert_op_without_sync(
index=index,
type='send_v2',
inputs={'X': var},
attrs={
self._op_device_key: self._device
+ ':'
+ str(cur_id),
self._op_role_key: int(self._op_role.Forward),
'use_calc_stream': True,
'peer': 0,
'ring_id': ring_id,
},
)
else:
var_shape = list(var.shape)
print(var_name)
if len(var.shape) > 0:
var_shape[0] = (
self.micro_batch_size
if var_shape[0] < 0
else var_shape[0]
)
block._insert_op_without_sync(
index=index,
type='recv_v2',
outputs={'Out': [var]},
attrs={
'out_shape': var_shape,
'dtype': var.dtype,
self._op_device_key: self._device
+ ':'
+ str(prev_id),
self._op_role_key: int(self._op_role.Forward),
'use_calc_stream': True,
'peer': 1,
'ring_id': ring_id,
},
)
index += 1
block._sync_with_cpp()
def _get_while_block(self):
"""
Get the while sub-block.
"""
main_block = self._main_program.global_block()
num_while = 0
sub_block_id = None
for op in main_block.ops:
assert num_while < 2, "More than one while op found."
if op.type == 'while':
sub_block_id = op.attr('sub_block').id
num_while += 1
if sub_block_id:
return op, self._main_program.block(sub_block_id)
return None, None
def gen_infer_program(
self,
sync_in_while_lastpp2firstpp_var_names=None,
sync_in_while_var_names=None,
debug=False,
):
"""
Generate inference program.
Params:
sync_in_while_lastpp2firstpp_var_names (list(str)): the vars in the last pipeline
that need to send var to first pipeline and exclude bool dtype var
sync_in_while_var_names (list(str)): the vars sync among all pipeline in while block
e.g cond. Note that cond cannot be bool dtype.
debug (bool): the flag indicate debug
"""
main_block = self._main_program.global_block()
startup_block = self._startup_program.global_block()
if debug:
with open('main_program.txt', 'w') as f:
f.write(str(self._main_program))
with open('startup_program.txt', 'w') as f:
f.write(str(self._startup_program))
# step1: add op_device attribute for all ops
self._add_op_device_attr(startup_block)
self._check_validation(startup_block)
self._add_op_device_attr(main_block)
self._check_validation(main_block)
# step2: add send/recv ops
self._update_param_device_map()
# step2.1: add send/recv for main_block
out_var_to_op, in_var_to_op = self._get_input_output_info(main_block)
self._output_var_to_op = out_var_to_op
self._input_var_to_op = in_var_to_op
self._insert_sendrecv_ops_for_boundaries(main_block, False)
# step2.2: add send/recv for while_block
while_op, while_block = self._get_while_block()
if while_block:
out_var_to_op, in_var_to_op = self._get_input_output_info(
while_block
)
self._output_var_to_op = out_var_to_op
self._input_var_to_op = in_var_to_op
self._insert_sendrecv_ops_for_boundaries(while_block, True)
self._insert_sendrecv_ops_in_while_block(
while_block,
sync_in_while_lastpp2firstpp_var_names,
sync_in_while_var_names,
self._stage,
)
# step3: split programs
self._split_program(self._startup_program, self._stage, 0)
self._split_program(self._main_program, self._stage, 0)
if debug:
with open(f'main_program.txt.{self.rank}', 'w') as f:
f.write(str(self._main_program))
with open(f'startup_program.txt.{self.rank}', 'w') as f:
f.write(str(self._startup_program))
if self.init_comm:
self._init_communication_group()