Files
2026-07-13 12:40:42 +08:00

691 lines
25 KiB
Python

# Copyright (c) 2023 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.
import argparse
import dist_api_gen
import yaml
from backward_api_gen import BackwardAPI
from dist_api_gen import DistForwardAPI
######################
# Code Gen Templates #
######################
MAIN_DIST_BRANCH_TEMPLATE = """
// Auto Parallel condition
if (run_auto_parallel) {{
// 1. InferSpmd (Infer DistAttr of Inputs&Outputs){}
// 2. Create Temporary Output & Prepare Dist and Dense Output{}
// 3. Infer DistTensor's Global Shape{}\n
// 4. Set Output Dist Attr For Default Impl{}\n
if (rank_is_in_current_mesh) {{
// 5. Select Kernel{}
// 6. Reshard Input{}\n
// 7. PrepareData (DataTransform & Prepare Dense Input){}
// 8. RecordOpInfoSupplement{}
// 9. Infer Local DenseTensor Meta{}
// 10. DenseTensor Kernel Call{}
// 11. Fallback{}
}}
// 12. Reshard Kernel Output to API output{}\n
// 13. Return
{}
}}
"""
# 1. Create API Outputs
SINGLE_OUT_CREATION_TEMPLATE_NO_SPMD = """
auto dist_out = SetKernelDistOutput({});
auto dense_out = dist_out->unsafe_mutable_value();
"""
SINGLE_OUT_CREATION_TEMPLATE_WITH_SPMD = """
std::shared_ptr<phi::distributed::DistTensor> shared_dist_out =
CreateKernelDistOutput({}, !rank_is_in_current_mesh, spmd_info.second[0]);
phi::distributed::DistTensor* dist_out = shared_dist_out.get();
phi::DenseTensor* dense_out = nullptr;
if (dist_out) {{
dense_out = dist_out->unsafe_mutable_value();
if (dense_out && !rank_is_in_current_mesh && !dist_out->defined()) {{
*dense_out = phi::DenseTensor(
std::make_shared<phi::Allocation>(nullptr, 0, phi::distributed::GetDefaultPlace()),
phi::DenseTensorMeta());
}}
}}
"""
SINGLE_OUT_CREATION_TEMPLATE = """
std::shared_ptr<phi::distributed::DistTensor> shared_dist_out =
CreateKernelDistOutput({}, !rank_is_in_current_mesh);
phi::distributed::DistTensor* dist_out = shared_dist_out.get();
phi::DenseTensor* dense_out = nullptr;
if (dist_out) {{
dense_out = dist_out->unsafe_mutable_value();
if (dense_out && !rank_is_in_current_mesh && !dist_out->defined()) {{
*dense_out = phi::DenseTensor(
std::make_shared<phi::Allocation>(nullptr, 0, phi::distributed::GetDefaultPlace()),
phi::DenseTensorMeta());
}}
}}
"""
VECTOR_OUT_CREATION_TEMPLATE_WITH_NO_SPMD = """
auto dist_out = SetKernelDistOutput({name});
std::vector<phi::DenseTensor*> dense_out(dist_out.size(), nullptr);
for (size_t i=0; i<dist_out.size(); i++) {{
if (dist_out[i]) {{
dense_out[i] = dist_out[i]->unsafe_mutable_value();
if (dense_out[i] && !rank_is_in_current_mesh && !dist_out[i]->defined()) {{
*dense_out[i] = phi::DenseTensor(
std::make_shared<phi::Allocation>(nullptr, 0, phi::distributed::GetDefaultPlace()),
phi::DenseTensorMeta());
}}
}}
}}
"""
VECTOR_OUT_CREATION_TEMPLATE_WITH_SPMD = """
auto shared_dist_out = CreateKernelDistOutput({name}, !rank_is_in_current_mesh, spmd_info.second[0]);
std::vector<phi::distributed::DistTensor*> dist_out;
for(auto& e: shared_dist_out){{
dist_out.push_back(e.get());
}}
std::vector<phi::DenseTensor*> dense_out(dist_out.size(), nullptr);
for (size_t i=0; i<dist_out.size(); i++) {{
if (dist_out[i]) {{
dense_out[i] = dist_out[i]->unsafe_mutable_value();
if (dense_out[i] && !rank_is_in_current_mesh && !dist_out[i]->defined()) {{
*dense_out[i] = phi::DenseTensor(
std::make_shared<phi::Allocation>(nullptr, 0, phi::distributed::GetDefaultPlace()),
phi::DenseTensorMeta());
}}
}}
}}
"""
VECTOR_OUT_CREATION_TEMPLATE = """
auto shared_dist_out = CreateKernelDistOutput({name}, !rank_is_in_current_mesh);
std::vector<phi::distributed::DistTensor*> dist_out;
for(auto& e: shared_dist_out){{
dist_out.push_back(e.get());
}}
std::vector<phi::DenseTensor*> dense_out(dist_out.size(), nullptr);
for (size_t i=0; i<dist_out.size(); i++) {{
if (dist_out[i]) {{
dense_out[i] = dist_out[i]->unsafe_mutable_value();
if (dense_out[i] && !rank_is_in_current_mesh && !dist_out[i]->defined()) {{
*dense_out[i] = phi::DenseTensor(
std::make_shared<phi::Allocation>(nullptr, 0, phi::distributed::GetDefaultPlace()),
phi::DenseTensorMeta());
}}
}}
}}
"""
INPLACE_OUT_CREATION_TEMPLATE = """
*{} = {};
"""
MULTI_SINGLE_OUT_CREATION_TEMPLATE_NO_SPMD = """
auto dist_out_{idx} = SetKernelDistOutput({name});
auto dense_out_{idx} = dist_out_{idx} ? dist_out_{idx}->unsafe_mutable_value() : nullptr;
if (dense_out_{idx} && !rank_is_in_current_mesh && !dist_out_{idx}->defined()) {{
*dense_out_{idx} = phi::DenseTensor(
std::make_shared<phi::Allocation>(nullptr, 0, phi::distributed::GetDefaultPlace()),
phi::DenseTensorMeta());
}}
"""
MULTI_SINGLE_OUT_CREATION_TEMPLATE_WITH_SPMD = """
std::shared_ptr<phi::distributed::DistTensor> shared_dist_out_{idx} =
CreateKernelDistOutput({name}, !rank_is_in_current_mesh, spmd_info.second[{idx}]);
phi::distributed::DistTensor* dist_out_{idx} = shared_dist_out_{idx}.get();
phi::DenseTensor* dense_out_{idx} = dist_out_{idx} ? dist_out_{idx}->unsafe_mutable_value() : nullptr;
if (dense_out_{idx} && !rank_is_in_current_mesh && !dist_out_{idx}->defined()) {{
*dense_out_{idx} = phi::DenseTensor(
std::make_shared<phi::Allocation>(nullptr, 0, phi::distributed::GetDefaultPlace()),
phi::DenseTensorMeta());
}}
"""
MULTI_SINGLE_OUT_CREATION_TEMPLATE = """
std::shared_ptr<phi::distributed::DistTensor> shared_dist_out_{idx} =
CreateKernelDistOutput({name}, !rank_is_in_current_mesh);
phi::distributed::DistTensor* dist_out_{idx} = shared_dist_out_{idx}.get();
phi::DenseTensor* dense_out_{idx} = dist_out_{idx} ? dist_out_{idx}->unsafe_mutable_value() : nullptr;
if (dense_out_{idx} && !rank_is_in_current_mesh && !dist_out_{idx}->defined()) {{
*dense_out_{idx} = phi::DenseTensor(
std::make_shared<phi::Allocation>(nullptr, 0, phi::distributed::GetDefaultPlace()),
phi::DenseTensorMeta());
}}
"""
MULTI_VECTOR_OUT_CREATION_TEMPLATE = """
auto dist_out_{i} = SetKernelDistOutput({name});
std::vector<phi::DenseTensor*> dense_out_{i}(dist_out_{i}.size(), nullptr);
for (size_t i = 0; i < dist_out_{i}.size(); i++) {{
if (dist_out_{i}[i]) {{
dense_out_{i}[i] = const_cast<phi::DenseTensor*>(&dist_out_{i}[i]->value());
if (dense_out_{i}[i] && !rank_is_in_current_mesh && !dist_out_{i}[i]->defined()) {{
*dense_out_{i}[i]= phi::DenseTensor(
std::make_shared<phi::Allocation>(nullptr, 0, phi::distributed::GetDefaultPlace()),
phi::DenseTensorMeta());
}}
}}
}}
"""
# 9. Reshard Output
RESHARD_SINGLE_OUTPUT_TEMPLATE = """
ReshardKernelOutputToApiOutput(dev_ctx, shared_dist_out, {}, "{}");"""
RESHARD_MULTI_SINGLE_OUTPUT_TEMPLATE = """
ReshardKernelOutputToApiOutput(dev_ctx, shared_dist_out_{}, {}, "{}");"""
RESHARD_VECTOR_OUTPUT_TEMPLATE = """
ReshardKernelOutputToApiOutput(dev_ctx, shared_dist_out, {}, "{}");"""
NONEED_TO_RESHARD_OUTPUT_TEMPLATE = """
// API `{}` does not need to reshard output."""
SET_LOCAL_SHAPE_TEMPLATE = """
{meta_tensor}.set_dims(phi::make_ddim(local_shape));"""
class DistBackwardAPI(DistForwardAPI, BackwardAPI):
def __init__(self, backward_item_yaml):
BackwardAPI.__init__(self, backward_item_yaml)
self.forward_config = backward_item_yaml['forward']
self.init_dist_api_members()
# override DistForwardAPI's method
def generate_output_creation_code(self) -> str:
# backward api only need to generate kernel outputs
output_num = len(self.outputs['types'])
output_creation_code = ""
output_creation_code += "\n phi::DeviceContext* dev_ctx = nullptr;"
if output_num == 1:
self.dist_output_args.append('dist_out')
self.dense_output_args.append('dense_out')
if self.outputs['types'][0] == 'Tensor':
if self.infer_meta['spmd_rule'] is not None:
output_creation_code += (
SINGLE_OUT_CREATION_TEMPLATE_WITH_SPMD.format(
self.outputs['names'][0]
)
)
elif self.generate_general_infer_spmd is True:
output_creation_code += SINGLE_OUT_CREATION_TEMPLATE.format(
self.outputs['names'][0]
)
else:
output_creation_code += (
SINGLE_OUT_CREATION_TEMPLATE_NO_SPMD.format(
self.outputs['names'][0]
)
)
elif self.outputs['types'][0] == 'std::vector<Tensor>':
if self.infer_meta['spmd_rule'] is not None:
output_creation_code += (
VECTOR_OUT_CREATION_TEMPLATE_WITH_SPMD.format(
name=self.outputs['names'][0]
)
)
elif self.generate_general_infer_spmd is True:
output_creation_code += VECTOR_OUT_CREATION_TEMPLATE.format(
name=self.outputs['names'][0]
)
else:
output_creation_code += (
VECTOR_OUT_CREATION_TEMPLATE_WITH_NO_SPMD.format(
name=self.outputs['names'][0]
)
)
else:
self.vector_output_size_assertion_check()
elif output_num > 1:
for i, out_type in enumerate(self.outputs['types']):
self.dist_output_args.append(f'dist_out_{i}')
self.dense_output_args.append(f'dense_out_{i}')
if out_type == 'Tensor':
if self.infer_meta['spmd_rule'] is not None:
output_creation_code += (
MULTI_SINGLE_OUT_CREATION_TEMPLATE_WITH_SPMD.format(
name=self.outputs['names'][i], idx=i
)
)
elif self.generate_general_infer_spmd is True:
output_creation_code += (
MULTI_SINGLE_OUT_CREATION_TEMPLATE.format(
name=self.outputs['names'][i], idx=i
)
)
else:
output_creation_code += (
MULTI_SINGLE_OUT_CREATION_TEMPLATE_NO_SPMD.format(
name=self.outputs['names'][i], idx=i
)
)
elif out_type == 'std::vector<Tensor>':
output_creation_code += (
MULTI_VECTOR_OUT_CREATION_TEMPLATE.format(
i=i, name=self.outputs['names'][i]
)
)
else:
self.vector_output_size_assertion_check()
else:
raise ValueError(
f"{self.api} : Output error: the output should not be empty."
)
return output_creation_code
def generate_bw_infer_local_shape_code(self, need_kernel=False):
arg_name = self.infer_meta['local_shape']
assert arg_name in self.outputs['names'], (
f"Auto Parallel will calculate local_shape for {arg_name} "
f"in {self.api}, but {arg_name} is not found in its outputs."
)
_, fw_inputs, fw_attrs, fw_outputs = self.parse_forward_config(
self.forward_config
)
# shape_type = self.attrs['attr_info'][shape_name][0]
# out_name = self.dist_output_args[0]
dist_out_name = self.dist_output_args[
self.outputs['names'].index(arg_name)
]
shape_type = self.get_shape_type(fw_attrs['attr_info'])
return_code = dist_api_gen.CALCULATE_LOCAL_SHAPE_TEMPLATE.format(
out_name=dist_out_name,
out_dist_attr=(
"PADDLE_GET_CONST(phi::distributed::TensorDistAttr, spmd_info.second[0]);"
if self.infer_meta['spmd_rule']
else f"phi::distributed::TensorDistAttr(common::vectorize({dist_out_name}->dims()))"
),
dtype=shape_type,
op_name=self.kernel['func'][0],
)
if need_kernel:
return (
dist_api_gen.CALCULATE_LOCAL_SHAPE_KERNEL_TEMPLATE.format(
out_grad_dist_attr=(
"PADDLE_GET_CONST(phi::distributed::TensorDistAttr, spmd_info.first[1]);"
if self.infer_meta['spmd_rule']
else "phi::distributed::TensorDistAttr(common::vectorize(out_grad.dims()))"
),
dtype=shape_type,
op_name=self.kernel['func'][0],
)
+ return_code
)
return return_code
def generate_infer_meta_code(self) -> str:
(
infer_meta_func_code,
input_args_code,
output_decl_code,
output_args_code,
) = self.generate_infer_meta_func_and_args_code()
infer_meta_code = ""
if self.infer_meta['global_shape'] is not None:
for i, out_name in enumerate(self.outputs['names']):
if out_name == self.infer_meta[
'global_shape'
] and self.need_to_generate_code_for_inplace_impl(i):
infer_meta_code += dist_api_gen.SET_DIMS_TEMPLATE.format(
dst=self.dist_output_args[i],
src=(
self.dist_output_args[i] + '_tmp'
if i > 0
else self.dist_output_args[i]
),
)
infer_meta_code = (
infer_meta_code
+ dist_api_gen.INFER_META_TEMPLATE.format(
infer_meta_func_code, input_args_code, output_args_code
)
)
# TODO(GhostScreaming): kernel like reshape need calculate local_shape
if self.infer_meta['local_shape'] is not None:
if (
self.kernel['param'] is not None
and self.infer_meta['local_shape'] not in self.kernel['param']
):
infer_meta_code += self.generate_bw_infer_local_shape_code()
else:
infer_meta_code += self.generate_bw_infer_local_shape_code(
need_kernel=True
)
infer_meta_code += SET_LOCAL_SHAPE_TEMPLATE.format(
meta_tensor="meta_" + self.dense_output_args[0]
)
return output_decl_code + infer_meta_code
# override DistForwardAPI's method
def generate_return_code(self) -> str:
return "return;"
# override BaseAPI's method
def get_api_func_name(self):
return self.api
# override BaseAPI's method
# The method lookup order are: (DistBackwardAPI.__mro__)
# <class '__main__.DistBackwardAPI'>,
# <class 'dist_api_gen.DistForwardAPI'>,
# <class 'api_gen.ForwardAPI'>,
# <class 'backward_api_gen.BackwardAPI'>,
# <class 'api_base.BaseAPI'>,
# <class 'object'>
# if don't override it, the ForwardAPI's gene_output will be called
def gene_output(
self,
out_dtype_list,
out_tensor_type_list=None,
code_indent='',
inplace_flag=False,
):
return BackwardAPI.gene_output(
self,
out_dtype_list,
out_tensor_type_list,
code_indent,
inplace_flag,
)
# override BaseAPI's method
def get_return_type(self, inplace_flag=False):
return BackwardAPI.get_return_type(self)
# override BaseAPI's method
def gene_return_code(self):
return ""
# override BaseAPI's method
def gene_api_declaration(
self, grad_flag=False, append_predefined_out=False
) -> str:
return BackwardAPI.gene_api_declaration(
self, grad_flag=grad_flag, append_predefined_out=not grad_flag
)
def generate_reshard_output_code(self):
reshard_output_code = ""
if self.generate_infer_spmd is True:
output_num = len(self.outputs['types'])
if output_num == 1:
if self.outputs['types'][0] == 'Tensor':
reshard_output_code += (
RESHARD_SINGLE_OUTPUT_TEMPLATE.format(
self.outputs['names'][0], self.outputs['names'][0]
)
)
elif self.outputs['types'][0] == 'std::vector<Tensor>':
reshard_output_code += (
RESHARD_VECTOR_OUTPUT_TEMPLATE.format(
self.outputs['names'][0], self.outputs['names'][0]
)
)
else:
self.vector_output_size_assertion_check()
elif output_num > 1:
for i, out_type in enumerate(self.outputs['types']):
if out_type == 'Tensor':
reshard_output_code += (
RESHARD_MULTI_SINGLE_OUTPUT_TEMPLATE.format(
i,
self.outputs['names'][i],
self.outputs['names'][i],
)
)
else:
self.vector_output_size_assertion_check()
else:
raise ValueError(
f"{self.api} : Output error: the output should not be empty."
)
else:
reshard_output_code += NONEED_TO_RESHARD_OUTPUT_TEMPLATE.format(
self.kernel['func'][0]
)
# do nothing
pass
return reshard_output_code
def generate_auto_parallel_branch(self) -> str:
# if no tensor input, do not generate auto parallel branch
if len(self.inputs['names']) == 0:
return ""
infer_spmd_code = self.generate_infer_spmd_code()
output_creation_code = self.generate_output_creation_code()
infer_global_shape_code = self.generate_infer_global_shape_code()
output_dist_attr_setting = self.generate_output_dist_attr_setting()
kernel_selection_code = self.generate_kernel_selection_code()
reshard_input_code = self.generate_reshard_input_code()
(
prepare_data_code,
input_name_tensor_map,
) = self.generate_prepare_data_code()
record_op_info_supplement_code = (
self.generate_record_op_info_supplement(
input_name_tensor_map, ' ', True
)
)
infer_meta_code = self.generate_infer_meta_code()
kernel_call_code = self.generate_kernel_call_code(is_forward=False)
fallback_code = self.generate_fallback_code()
reshard_output_code = self.generate_reshard_output_code()
return_code = self.generate_return_code()
return MAIN_DIST_BRANCH_TEMPLATE.format(
infer_spmd_code,
output_creation_code,
infer_global_shape_code,
output_dist_attr_setting,
kernel_selection_code,
reshard_input_code,
prepare_data_code,
record_op_info_supplement_code,
infer_meta_code,
kernel_call_code,
fallback_code,
reshard_output_code,
return_code,
)
def header_include():
return """
#include <tuple>
#include "paddle/phi/api/include/tensor.h"
#include "paddle/phi/common/scalar.h"
#include "paddle/phi/common/int_array.h"
#include "paddle/utils/optional.h"
"""
def source_include(header_file_path, fw_header_file_path):
return f"""
#include "{header_file_path}"
#include <memory>
#include "glog/logging.h"
#include "paddle/common/flags.h"
#include "paddle/phi/api/lib/api_custom_impl.h"
#include "paddle/phi/api/lib/api_gen_utils.h"
#include "paddle/phi/api/lib/data_transform.h"
#include "paddle/phi/api/lib/kernel_dispatch.h"
#include "paddle/phi/common/type_traits.h"
#include "paddle/phi/core/kernel_registry.h"
#include "{fw_header_file_path}"
#include "paddle/phi/infermeta/backward.h"
#include "paddle/phi/infermeta/unary.h"
#include "paddle/phi/infermeta/fusion.h"
#include "paddle/phi/api/profiler/event_tracing.h"
#include "paddle/phi/api/profiler/supplement_tracing.h"
#if defined(PADDLE_WITH_NCCL) || defined(PADDLE_WITH_RCCL)
#include "paddle/phi/core/distributed/comm_context_manager.h"
#include "paddle/phi/core/distributed/nccl_comm_context.h"
#elif defined(PADDLE_WITH_XPU_BKCL)
#include "paddle/phi/core/distributed/comm_context_manager.h"
#include "paddle/phi/core/distributed/bkcl_comm_context.h"
#elif defined(PADDLE_WITH_CUSTOM_DEVICE)
#include "paddle/phi/core/distributed/comm_context_manager.h"
#include "paddle/phi/core/distributed/xccl_comm_context.h"
#endif
#ifdef PADDLE_WITH_DISTRIBUTE
#include "paddle/phi/core/distributed/store/store_utils.h"
#include "paddle/phi/infermeta/spmd_rules/rules.h"
#include "paddle/phi/core/distributed/auto_parallel/reshard/reshard_utils.h"
#endif
PD_DECLARE_bool(conv2d_disable_cudnn);
COMMON_DECLARE_int32(low_precision_op_list);
COMMON_DECLARE_bool(benchmark);
"""
def backward_api_namespace():
return (
"""
namespace paddle {
namespace experimental {
""",
"""
} // namespace experimental
} // namespace paddle
""",
)
def generate_backward_api(
backward_yaml_path,
is_fused_backward_yaml,
header_file_path,
source_file_path,
):
bw_apis = []
for each_api_yaml in backward_yaml_path:
with open(each_api_yaml, 'r') as f:
api_list = yaml.load(f, Loader=yaml.FullLoader)
if api_list:
bw_apis.extend(api_list)
header_file = open(header_file_path, 'w')
source_file = open(source_file_path, 'w')
namespace = backward_api_namespace()
header_file.write("#pragma once\n")
header_file.write(header_include())
header_file.write(namespace[0])
include_header_file = (
"paddle/phi/api/backward/fused_backward_api_base.h"
if is_fused_backward_yaml
else "paddle/phi/api/backward/backward_api_base.h"
)
include_fw_header_file = (
"paddle/phi/api/include/fused_api.h"
if is_fused_backward_yaml
else "paddle/phi/api/include/api.h"
)
source_file.write(
source_include(include_header_file, include_fw_header_file)
)
source_file.write(namespace[0])
# not all fused ops support dygraph
if is_fused_backward_yaml is True:
new_bw_apis = [
bw_api
for bw_api in bw_apis
if "support_dygraph_mode" in bw_api
and bw_api["support_dygraph_mode"] is True
]
bw_apis = new_bw_apis
for bw_api in bw_apis:
dist_bw_api = DistBackwardAPI(bw_api)
header_file.write(dist_bw_api.gene_api_declaration())
if is_fused_backward_yaml is True:
source_file.write(dist_bw_api.gene_api_code())
else:
source_file.write(dist_bw_api.gene_api_code())
header_file.write(namespace[1])
source_file.write(namespace[1])
header_file.close()
source_file.close()
def main():
parser = argparse.ArgumentParser(
description='Generate PaddlePaddle C++ backward API files'
)
parser.add_argument(
'--backward_yaml_path',
help='path to backward yaml file',
nargs='+',
default=['paddle/phi/ops/yaml/backward.yaml'],
)
parser.add_argument(
'--is_fused_backward_yaml',
help='flag of fused backward yaml',
action='store_true',
)
parser.add_argument(
'--backward_header_path',
help='output of generated backward header code file',
default='paddle/phi/api/backward/backward_api_base.h',
)
parser.add_argument(
'--backward_source_path',
help='output of generated backward source code file',
default='paddle/phi/api/lib/backward_api_base.cc',
)
options = parser.parse_args()
backward_yaml_path = options.backward_yaml_path
is_fused_backward_yaml = options.is_fused_backward_yaml
header_file_path = options.backward_header_path
source_file_path = options.backward_source_path
generate_backward_api(
backward_yaml_path,
is_fused_backward_yaml,
header_file_path,
source_file_path,
)
if __name__ == '__main__':
main()