Files
paddlepaddle--paddle/paddle/phi/infermeta/spmd_rules/conv3d.cc
T
2026-07-13 12:40:42 +08:00

289 lines
13 KiB
C++

/* Copyright (c) 2025 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. */
#include "paddle/phi/infermeta/spmd_rules/conv3d.h"
#include "glog/logging.h"
#include "paddle/phi/core/distributed/auto_parallel/dist_attr.h"
#include "paddle/phi/core/distributed/auto_parallel/inferspmd_utils.h"
#include "paddle/phi/core/distributed/auto_parallel/utils.h"
#include "paddle/phi/core/enforce.h"
#include "paddle/phi/infermeta/spmd_rules/utils.h"
namespace phi {
namespace distributed {
SpmdInfo Conv3dInferSpmdBase(const DistMetaTensor& input,
const DistMetaTensor& filter,
const std::string& data_format) {
// Step0: verify input args based on conv3d logic
VLOG(4) << "step 0: verify input args based on conv3d logic";
auto original_input_shape = vectorize(input.dims());
auto original_filter_shape = vectorize(filter.dims());
int input_ndim = static_cast<int>(original_input_shape.size());
int filter_ndim = static_cast<int>(original_filter_shape.size());
const auto& input_dist_attr_src = input.dist_attr();
const auto& filter_dist_attr_src = filter.dist_attr();
std::vector<int64_t> input_dims_mapping = input_dist_attr_src.dims_mapping();
std::vector<int64_t> filter_dims_mapping =
filter_dist_attr_src.dims_mapping();
PADDLE_ENFORCE_EQ(
input_ndim,
5,
common::errors::InvalidArgument("The Tensor Input's rank must be 5 in "
"conv3d, for NCDHW or NDHWC format."
"But now it's [%d]",
input_ndim));
PADDLE_ENFORCE_EQ(
filter_ndim,
5,
common::errors::InvalidArgument(
"The Tensor Filter's rank must be 5 in conv3d, for MCDHW format."
"But now it's [%d]",
filter_ndim));
PADDLE_ENFORCE_EQ(input_ndim,
input_dims_mapping.size(),
common::errors::InvalidArgument(
"The Tensor Input's rank [%d] and Input's "
"dims_mapping size [%d] are not matched.",
input_ndim,
input_dims_mapping.size()));
PADDLE_ENFORCE_EQ(filter_ndim,
filter_dims_mapping.size(),
common::errors::InvalidArgument(
"The Tensor Filter's rank [%d] and Filter's "
"dims_mapping size [%d] are not matched.",
filter_ndim,
filter_dims_mapping.size()));
// todo: NCDHW or NDHWC check, check channel logic, input's channel
// dims_mapping must be equal to filter's channel dims_mapping
int input_channel_dim = (data_format == "NCDHW") ? 1 : 4;
int filter_channel_dim = 1;
PADDLE_ENFORCE_EQ(input_dims_mapping[input_channel_dim],
filter_dims_mapping[filter_channel_dim],
common::errors::InvalidArgument(
"The Input channel's dims mapping must be equal to "
"filter channel's dims mapping in conv3d. "
"When shard channel dim to a mesh (multiple cards), "
"each card will compute partial output, "
"otherwise, mark channel dim as replicate, each card "
"will compute complete output. "
"But now the Input channel's dims mapping is [%d], and "
"the filter channel's dims mapping is [%d].",
input_dims_mapping[input_channel_dim],
filter_dims_mapping[filter_channel_dim]));
VLOG(6) << "Conv3D InferForward Inputs: "
<< "Input shape: [" << str_join(original_input_shape)
<< "], input_dims_mapping: [" << str_join(input_dims_mapping)
<< "], Input data format: [" << data_format << "]; Filter shape: ["
<< str_join(original_filter_shape) << "], filter_dims_mapping: ["
<< str_join(filter_dims_mapping) << "]; ";
// Step1: build Einsum Notation
// todo: check output notation, how to deal with the "Input DHW, Filter DHW
// and Output DHW"...
VLOG(4) << "step 1: build Einsum Notation";
std::string input_axes = (data_format == "NCDHW") ? "ncdhw" : "ndhwc";
std::string filter_axes = "mcdhw";
std::string output_axes = "nmdhw";
// Step2: sharding propagation
VLOG(4) << "step 2: sharding propagation";
// Step2.1: merge input sharding
std::pair<std::string, std::vector<int64_t>> input_pair(input_axes,
input_dims_mapping);
std::pair<std::string, std::vector<int64_t>> filter_pair(filter_axes,
filter_dims_mapping);
auto axis_to_dim_map = ShardingMergeForTensors({input_pair, filter_pair});
// Step2.2: infer output dims mapping
TensorDistAttr output_dist_attr_dst =
CopyTensorDistAttrForOutput(input_dist_attr_src);
output_dist_attr_dst.set_dims_mapping(
GetDimsMappingForAxes(output_axes, axis_to_dim_map));
// Step2.3: update input dims mapping
TensorDistAttr input_dist_attr_dst =
CopyTensorDistAttrForOutput(input_dist_attr_src);
TensorDistAttr filter_dist_attr_dst =
CopyTensorDistAttrForOutput(filter_dist_attr_src);
input_dist_attr_dst.set_dims_mapping(
GetDimsMappingForAxes(input_axes, axis_to_dim_map));
filter_dist_attr_dst.set_dims_mapping(
GetDimsMappingForAxes(filter_axes, axis_to_dim_map));
// Step2.4: Handle Partial
// Step2.4.1 Output Partial
std::vector<int64_t> partial_on_dims =
ResoluteOutputPartialDimension(axis_to_dim_map, output_axes);
output_dist_attr_dst.set_partial_status(partial_on_dims);
VLOG(4) << "Conv3DSPMDRule InferForward: "
<< "Einsum notation: [" << input_axes << "," << filter_axes << " --> "
<< output_axes << "]. " << std::endl;
LogInputDistAttr(
"Input", original_input_shape, input_dist_attr_src, input_dist_attr_dst);
LogInputDistAttr("Filter",
original_filter_shape,
filter_dist_attr_src,
filter_dist_attr_dst);
LogOutputDistAttr("Output", output_dist_attr_dst);
VLOG(4) << std::endl;
return {{input_dist_attr_dst, filter_dist_attr_dst}, {output_dist_attr_dst}};
}
SpmdInfo Conv3dGradInferSpmdBase(const DistMetaTensor& input,
const DistMetaTensor& filter,
const DistMetaTensor& output_grad,
const std::string& data_format) {
auto check_channel_dist_attr =
[&](const phi::distributed::TensorDistAttr& input_dist_attr,
const phi::distributed::TensorDistAttr& filter_dist_attr,
const phi::distributed::TensorDistAttr& output_grad_dist_attr) {
int input_channel_dim = (data_format == "NCDHW") ? 1 : 4;
int filter_channel_dim = 1;
if (output_grad_dist_attr.is_partial()) {
std::set<int64_t> partial_dims = output_grad_dist_attr.partial_dims();
PADDLE_ENFORCE_EQ(
partial_dims.size(),
1,
common::errors::InvalidArgument(
"For conv3d output, only support partial on channel_dim for "
"output, "
"which means shard on channel_dim for input and filter."
"But now the output is partial on [%d] dims.",
partial_dims.size()));
int64_t partial_dim = *partial_dims.begin();
auto input_dims_mapping = input_dist_attr.dims_mapping();
auto filter_dims_mapping = filter_dist_attr.dims_mapping();
if (input_dims_mapping[input_channel_dim] == partial_dim &&
filter_dims_mapping[filter_channel_dim] == partial_dim) {
return true;
}
}
return false;
};
auto input_dist_attr_src = input.dist_attr();
auto filter_dist_attr_src = filter.dist_attr();
auto output_grad_dist_attr_src = output_grad.dist_attr();
std::string input_axes = (data_format == "NCDHW") ? "ncdhw" : "ndhwc";
std::string filter_axes = "mcdhw";
std::string output_axes = "nmdhw";
std::pair<std::string, std::vector<int64_t>> input_pair(
input_axes, input_dist_attr_src.dims_mapping());
std::pair<std::string, std::vector<int64_t>> filter_pair(
filter_axes, filter_dist_attr_src.dims_mapping());
std::pair<std::string, std::vector<int64_t>> output_grad_pair(
output_axes, output_grad_dist_attr_src.dims_mapping());
// input_grad_dist, copy n_dim and merge m_dim
auto axis_to_dim_map_1 =
ShardingMergeForTensors({filter_pair, output_grad_pair});
TensorDistAttr input_grad_dist_attr_dst =
GetReplicatedDistAttr(input_dist_attr_src);
input_grad_dist_attr_dst.set_dims_mapping(
GetDimsMappingForAxes(input_axes, axis_to_dim_map_1));
// handle partial for input_grad
std::vector<int64_t> partial_on_m_dim =
ResoluteOutputPartialDimension(axis_to_dim_map_1, input_axes);
input_grad_dist_attr_dst.set_partial_status(partial_on_m_dim);
TensorDistAttr filter_dist_attr_dst =
CopyTensorDistAttrForOutput(filter_dist_attr_src);
filter_dist_attr_dst.set_dims_mapping(
GetDimsMappingForAxes(filter_axes, axis_to_dim_map_1));
// filter_grad_dist, copy m_dim and merge n_dim
auto axis_to_dim_map_2 =
ShardingMergeForTensors({input_pair, output_grad_pair});
TensorDistAttr filter_grad_dist_attr_dst =
GetReplicatedDistAttr(filter_dist_attr_src);
filter_grad_dist_attr_dst.set_dims_mapping(
GetDimsMappingForAxes(filter_axes, axis_to_dim_map_2));
// handle partial for filter_grad
std::vector<int64_t> partial_on_n_dim =
ResoluteOutputPartialDimension(axis_to_dim_map_2, filter_axes);
filter_grad_dist_attr_dst.set_partial_status(partial_on_n_dim);
TensorDistAttr input_dist_attr_dst =
CopyTensorDistAttrForOutput(input_dist_attr_src);
input_dist_attr_dst.set_dims_mapping(
GetDimsMappingForAxes(input_axes, axis_to_dim_map_2));
// output_grad
auto axis_to_dim_map_3 =
ShardingMergeForTensors({input_pair, filter_pair, output_grad_pair});
TensorDistAttr output_grad_dist_attr_dst =
CopyTensorDistAttrForOutput(output_grad_dist_attr_src);
output_grad_dist_attr_dst.set_dims_mapping(
GetDimsMappingForAxes(output_axes, axis_to_dim_map_3));
// process channel_dim, handle partial
int input_channel_dim = (data_format == "NCDHW") ? 1 : 4;
int filter_channel_dim = 1;
if (check_channel_dist_attr(input_dist_attr_src,
filter_dist_attr_src,
output_grad_dist_attr_src)) {
int partial_mesh_dim = *output_grad_dist_attr_src.partial_dims().begin();
std::vector<int64_t> input_grad_dims_mapping_dst =
input_grad_dist_attr_dst.dims_mapping();
input_grad_dims_mapping_dst[input_channel_dim] = partial_mesh_dim;
input_grad_dist_attr_dst.set_dims_mapping(input_grad_dims_mapping_dst);
std::vector<int64_t> filter_grad_dims_mapping_dst =
filter_grad_dist_attr_dst.dims_mapping();
filter_grad_dims_mapping_dst[filter_channel_dim] = partial_mesh_dim;
filter_grad_dist_attr_dst.set_dims_mapping(filter_grad_dims_mapping_dst);
output_grad_dist_attr_dst.set_partial_status(
std::vector<int64_t>({partial_mesh_dim}));
}
return {
{input_dist_attr_dst, filter_dist_attr_dst, output_grad_dist_attr_dst},
{input_grad_dist_attr_dst, filter_grad_dist_attr_dst}};
}
SpmdInfo Conv3dInferSpmd(const DistMetaTensor& input,
const DistMetaTensor& filter,
const std::vector<int>& strides,
const std::vector<int>& paddings,
const std::string& padding_algorithm,
int groups,
const std::vector<int>& dilations,
const std::string& data_format) {
return Conv3dInferSpmdBase(input, filter, data_format);
}
SpmdInfo Conv3dGradInferSpmd(const DistMetaTensor& input,
const DistMetaTensor& filter,
const DistMetaTensor& output_grad,
const std::vector<int>& strides,
const std::vector<int>& paddings,
const std::string& padding_algorithm,
int groups,
const std::vector<int>& dilations,
const std::string& data_format) {
return Conv3dGradInferSpmdBase(input, filter, output_grad, data_format);
}
} // namespace distributed
} // namespace phi