Files
paddlepaddle--paddle/paddle/fluid/imperative/layout_transformer.h
T
2026-07-13 12:40:42 +08:00

475 lines
18 KiB
C++

// Copyright (c) 2022 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.
#pragma once
#include "paddle/common/errors.h"
#include "paddle/fluid/imperative/layout_autotune.h"
#include "paddle/fluid/imperative/tracer.h"
#include "paddle/fluid/imperative/var_helper.h"
#include "paddle/phi/core/enforce.h"
#include "paddle/phi/core/framework/framework.pb.h"
#include "paddle/phi/core/tensor_utils.h"
namespace paddle {
namespace imperative {
template <typename VarType>
void SetOutDataLayout(std::shared_ptr<VarType> var,
const phi::DataLayout layout) {
if (var != nullptr && var->Var().IsInitialized()) {
paddle::imperative::SetDataLayout(var, layout);
// set out_tensor's layout
if (var->MutableVar()->IsInitialized()) {
paddle::framework::Variable* tmp_var = var->MutableVar();
auto* out = tmp_var->GetMutable<DenseTensor>();
auto meta =
phi::DenseTensorUtils::GetMutableMeta(static_cast<DenseTensor*>(out));
meta->layout = layout;
}
}
}
template <typename VarType>
std::shared_ptr<VarType> TraceTransposeOp(
const std::shared_ptr<VarType>& var,
const DataLayout layout,
const std::shared_ptr<paddle::imperative::Tracer>& tracer) {
std::vector<int> axis;
if (layout == DataLayout::NHWC) {
axis = {0, 2, 3, 1};
} else if (layout == DataLayout::NCHW) {
axis = {0, 3, 1, 2};
} else {
axis = {0, 1, 2, 3};
}
paddle::imperative::NameVarMap<VarType> ins = {{"X", {var}}};
auto out =
std::shared_ptr<VarType>(new VarType(tracer->GenerateUniqueName()));
auto x_shape =
std::shared_ptr<VarType>(new VarType(tracer->GenerateUniqueName()));
paddle::imperative::NameVarMap<VarType> outs = {{"Out", {out}},
{"XShape", {x_shape}}};
paddle::framework::AttributeMap attrs = {{"axis", axis}};
tracer->TraceOp("transpose2", ins, outs, std::move(attrs));
paddle::imperative::SetDataLayout(out, layout);
VLOG(4) << "Transpose " << paddle::imperative::GetNameFromVar(var) << "["
<< common::DataLayoutToString(paddle::imperative::GetDataLayout(var))
<< "]"
<< " to " << paddle::imperative::GetNameFromVar(out) << "["
<< common::DataLayoutToString(paddle::imperative::GetDataLayout(out))
<< "]";
return out;
}
template <typename VarType>
class LayoutTransformer {
public:
explicit LayoutTransformer(const std::string& type) : type_(type) {}
virtual ~LayoutTransformer() {}
LayoutTransformer(const LayoutTransformer&) = delete;
LayoutTransformer& operator=(const LayoutTransformer&) = delete;
virtual paddle::imperative::NameVarMap<VarType> Apply(
const paddle::imperative::NameVarMap<VarType>& ins,
const paddle::imperative::NameVarMap<VarType>& outs,
paddle::framework::AttributeMap* attrs,
const std::shared_ptr<paddle::imperative::Tracer>& tracer) {
VLOG(3) << "Optimize Layout agnostic op: " << type_;
auto in_layout = DataLayout::UNDEFINED;
for (auto& pair : ins) {
for (auto& var : pair.second) {
// Once the any input is desired layout, we set in_layout is desired
// layout.
if (in_layout == DataLayout::UNDEFINED) {
in_layout = paddle::imperative::GetDataLayout(var);
}
if (var != nullptr && (paddle::imperative::GetDataLayout(var) ==
LayoutAutoTune::Instance().GetDesiredLayout())) {
in_layout = LayoutAutoTune::Instance().GetDesiredLayout();
break;
}
}
}
VLOG(3) << "Optimize Layout agnostic op: " << type_ << " "
<< common::DataLayoutToString(in_layout);
if (in_layout != DataLayout::UNDEFINED) {
SetVarsLayout(outs, in_layout);
}
return ins;
}
// Set inputs, outputs and attributes to be optimized for the transposer.
// Those may respectively be a subset of the corresponding original argument
// of the operator.
void SetArguments(const std::vector<std::string>& ins,
const std::vector<std::string>& outs,
const std::vector<std::string>& attrs) {
ins_ = ins;
outs_ = outs;
attrs_ = attrs;
}
// Set the variables's layout to the specified layout.
// If outs_ is not specified, it means all outputs of the operator
// will be considered. Otherwise, it only set layout for the specified output.
void SetVarsLayout(const paddle::imperative::NameVarMap<VarType>& outs,
DataLayout layout) const {
bool not_in_out = true;
if (!outs_.empty()) {
for (auto& name : outs_) {
if (outs.find(name) != outs.end()) {
auto out_vars = outs.at(name);
for (auto& var : out_vars) {
if (var != nullptr) {
paddle::imperative::SetOutDataLayout(var, layout);
}
}
not_in_out = false;
}
}
}
if (not_in_out) {
for (auto& pair : outs) {
for (auto& var : pair.second) {
if (var != nullptr) {
paddle::imperative::SetOutDataLayout(var, layout);
}
}
}
}
}
const std::vector<std::string>& Inputs() const { return ins_; }
const std::vector<std::string>& Outputs() const { return outs_; }
const std::vector<std::string>& Attributes() const { return attrs_; }
const std::string& Type() { return type_; }
protected:
std::string type_{};
std::vector<std::string> ins_{};
std::vector<std::string> outs_{};
std::vector<std::string> attrs_{};
};
/*
* Both functionality and performance are affected by data layout.
* Such as operators with data_format attribute.
*/
template <typename VarType>
class HeavilyLayoutSensitiveOpTransformer : public LayoutTransformer<VarType> {
public:
explicit HeavilyLayoutSensitiveOpTransformer(const std::string& type)
: LayoutTransformer<VarType>(type) {}
paddle::imperative::NameVarMap<VarType> Apply(
const paddle::imperative::NameVarMap<VarType>& ins,
const paddle::imperative::NameVarMap<VarType>& outs,
paddle::framework::AttributeMap* attrs,
const std::shared_ptr<paddle::imperative::Tracer>& tracer) {
VLOG(3) << "Optimize heavily layout sensitive op " << this->Type();
paddle::imperative::NameVarMap<VarType> new_ins(ins);
// Step 1: Adjust the data_layout attr to the desired layout
auto desired_layout = LayoutAutoTune::Instance().GetDesiredLayout();
std::string desired_layout_str = common::DataLayoutToString(
LayoutAutoTune::Instance().GetDesiredLayout());
if (attrs->find("data_format") != attrs->end() &&
PADDLE_GET_CONST(std::string, (*attrs)["data_format"]) !=
desired_layout_str) {
VLOG(4) << "Origin layout attr: "
<< PADDLE_GET_CONST(std::string, (*attrs)["data_format"])
<< ", Desired layout attr: " << desired_layout_str;
(*attrs)["data_format"] = desired_layout_str;
} else if (attrs->find("data_layout") != attrs->end() &&
PADDLE_GET_CONST(std::string, (*attrs)["data_layout"]) !=
desired_layout_str) {
VLOG(4) << "Origin layout attr: "
<< PADDLE_GET_CONST(std::string, (*attrs)["data_layout"])
<< ", Desired layout attr: " << desired_layout_str;
(*attrs)["data_layout"] = desired_layout_str;
}
// Step 2: Transpose the specified input for Op and set the transposed var's
// layout.
for (auto& name : this->Inputs()) {
if (new_ins.find(name) != new_ins.end()) {
auto& in_vars = new_ins[name];
for (auto& var : in_vars) {
if (var != nullptr &&
paddle::imperative::GetDataLayout(var) != desired_layout) {
var = TraceTransposeOp(var, desired_layout, tracer);
}
}
}
}
// Step 3: Set the Op's layout sensitive outs var.
this->SetVarsLayout(outs, desired_layout);
return new_ins;
}
};
/*
* The functionality may be affected layout transformation before them.
* Such as operators with axis attribute.
*/
template <typename VarType>
class LightlyLayoutSensitiveOpTransformer : public LayoutTransformer<VarType> {
public:
explicit LightlyLayoutSensitiveOpTransformer(const std::string& type)
: LayoutTransformer<VarType>(type) {}
paddle::imperative::NameVarMap<VarType> Apply(
const paddle::imperative::NameVarMap<VarType>& ins,
const paddle::imperative::NameVarMap<VarType>& outs,
paddle::framework::AttributeMap* attrs,
const std::shared_ptr<paddle::imperative::Tracer>& tracer) {
VLOG(3) << "Optimize lightly layout sensitive op " << this->Type();
paddle::imperative::NameVarMap<VarType> new_ins(ins);
// If input's layout is not tuned, transformation is unnecessary.
// If input's layout is already tuned, it will be transformed back to NCHW.
// TODO(zhangting): The op of this type should be adapted to the previous
// operator output data layout. Currently only a few operators are
// supported, and transposers need to be carefully designed to ensure that
// they do not cause exceptions.
auto desired_layout = LayoutAutoTune::Instance().GetDesiredLayout();
for (auto& pair : new_ins) {
for (auto& var : pair.second) {
if (var != nullptr) {
VLOG(3) << "Tune the layout from "
<< common::DataLayoutToString(
paddle::imperative::GetDataLayout(var))
<< " to "
<< common::DataLayoutToString(
LayoutAutoTune::Instance().GetDesiredLayout());
}
if (var != nullptr &&
paddle::imperative::GetDataLayout(var) == desired_layout &&
desired_layout == DataLayout::NHWC) {
// Set layout to UNDEFINED so that TransposeOpTransformer do
// NHWC->NCHW transformation.
var = TraceTransposeOp(var, DataLayout::UNDEFINED, tracer);
}
}
}
return new_ins;
}
};
template <typename VarType>
class ElementwiseOpTransformer
: public LightlyLayoutSensitiveOpTransformer<VarType> {
public:
explicit ElementwiseOpTransformer(const std::string& type)
: LightlyLayoutSensitiveOpTransformer<VarType>(type) {}
paddle::imperative::NameVarMap<VarType> Apply(
const paddle::imperative::NameVarMap<VarType>& ins,
const paddle::imperative::NameVarMap<VarType>& outs,
paddle::framework::AttributeMap* attrs,
const std::shared_ptr<paddle::imperative::Tracer>& tracer) {
// [Why we need the this?]
// The Elementwise Ops has a axis attr, it is to support broadcast.
// When bias_attr of Conv is not false, the elementwise_add will be
// appended, and the axis will be set to the channel dimension.
// If the axis is set to the channel dimension, the attr transformation
// is necessary. Otherwise, it will fall back to the
// LayoutTransformer::Apply.
auto& in1_vars = ins.at("X")[0];
auto& in2_vars = ins.at("Y")[0];
auto in_layout = paddle::imperative::GetDataLayout(in1_vars);
// for conv's bias
if (attrs->find("axis") != attrs->end() &&
PADDLE_GET_CONST(int, (*attrs)["axis"]) != -1) {
if (in_layout == DataLayout::NHWC) {
(*attrs)["axis"] = 3;
} else if (in_layout == DataLayout::NCHW) {
(*attrs)["axis"] = 1;
}
this->SetVarsLayout(outs, in_layout);
return ins;
} else {
auto in2_layout = paddle::imperative::GetDataLayout(in2_vars);
if (in_layout == in2_layout) {
this->SetVarsLayout(outs, in_layout);
return ins;
}
return LightlyLayoutSensitiveOpTransformer<VarType>::Apply(
ins, outs, attrs, tracer);
}
}
};
template <typename VarType>
class TransposeOpTransformer
: public LightlyLayoutSensitiveOpTransformer<VarType> {
public:
explicit TransposeOpTransformer(const std::string& type)
: LightlyLayoutSensitiveOpTransformer<VarType>(type) {}
paddle::imperative::NameVarMap<VarType> Apply(
const paddle::imperative::NameVarMap<VarType>& ins,
const paddle::imperative::NameVarMap<VarType>& outs,
paddle::framework::AttributeMap* attrs,
const std::shared_ptr<paddle::imperative::Tracer>& tracer) {
VLOG(3) << "Optimize lightly layout sensitive op " << this->Type();
// When the input layout is the desired format, it means that there
// is a transpose layer in the network, it is better to transpose
// the result to the original format.
// Instead of actually inserting a transpose Op, we fuse the inserted
// transpose Op with the current transpose Op by transforming 'axis' attr.
auto& in_var = ins.at("X")[0];
auto var_layout = paddle::imperative::GetDataLayout(in_var);
auto desired_layout = LayoutAutoTune::Instance().GetDesiredLayout();
if (var_layout == desired_layout && desired_layout == DataLayout::NHWC) {
auto axis = PADDLE_GET_CONST(std::vector<int>, (*attrs)["axis"]);
// NHWC->NCHW, permutation will be set as follows.
std::vector<int> perm = {0, 3, 1, 2};
// fuse the transpose Ops by transforming axis.
std::vector<int> fusion_axis = {
perm[axis[0]], perm[axis[1]], perm[axis[2]], perm[axis[3]]};
(*attrs)["axis"] = fusion_axis;
}
return ins;
}
};
template <typename VarType>
class FlattenOpTransformer
: public LightlyLayoutSensitiveOpTransformer<VarType> {
public:
explicit FlattenOpTransformer(const std::string& type)
: LightlyLayoutSensitiveOpTransformer<VarType>(type) {}
paddle::imperative::NameVarMap<VarType> Apply(
const paddle::imperative::NameVarMap<VarType>& ins,
const paddle::imperative::NameVarMap<VarType>& outs,
paddle::framework::AttributeMap* attrs,
const std::shared_ptr<paddle::imperative::Tracer>& tracer) {
VLOG(3) << "Optimize lightly layout sensitive op " << this->Type();
// Flatten the C, H, W dimensions will not affect functionality.
// So transformation is unnecessary. But in other cases, it needs to
// fall back to the LightlyLayoutSensitiveOpTransformer.
auto start_axis = PADDLE_GET_CONST(int, (*attrs)["start_axis"]);
auto stop_axis = PADDLE_GET_CONST(int, (*attrs)["stop_axis"]);
if (paddle::imperative::GetDataLayout(ins.at("X")[0]) ==
LayoutAutoTune::Instance().GetDesiredLayout() &&
start_axis == 1 && stop_axis == 3) {
return ins;
} else {
return LightlyLayoutSensitiveOpTransformer<VarType>::Apply(
ins, outs, attrs, tracer);
}
}
};
template <typename VarType>
class ArgmaxOpTransformer
: public LightlyLayoutSensitiveOpTransformer<VarType> {
public:
explicit ArgmaxOpTransformer(const std::string& type)
: LightlyLayoutSensitiveOpTransformer<VarType>(type) {}
paddle::imperative::NameVarMap<VarType> Apply(
const paddle::imperative::NameVarMap<VarType>& ins,
const paddle::imperative::NameVarMap<VarType>& outs,
paddle::framework::AttributeMap* attrs,
const std::shared_ptr<paddle::imperative::Tracer>& tracer) {
VLOG(3) << "Optimize lightly layout sensitive op " << this->Type();
auto& in_var = ins.at("X")[0];
auto var_layout = paddle::imperative::GetDataLayout(in_var);
bool keep_dims = PADDLE_GET_CONST(bool, (*attrs)["keepdims"]);
if (keep_dims) {
if (var_layout != DataLayout::UNDEFINED) {
std::vector<int> perm_nhwc = {0, 3, 1, 2};
std::vector<int> perm_nchw = {0, 2, 3, 1};
auto perm = var_layout == DataLayout::NHWC ? perm_nhwc : perm_nchw;
switch (AttrTypeID((*attrs)["axis"])) {
case paddle::framework::proto::AttrType::INT: {
auto axis = PADDLE_GET_CONST(int, (*attrs)["axis"]);
(*attrs)["axis"] = static_cast<int>(perm[axis]);
break;
}
case paddle::framework::proto::AttrType::LONG: {
auto axis = PADDLE_GET_CONST(int64_t, (*attrs)["axis"]);
(*attrs)["axis"] = static_cast<int64_t>(perm[axis]);
break;
}
default:
VLOG(4) << "The data_type of axis is Error, axis must be int or "
"int64, bug got "
<< (AttrTypeID((*attrs)["axis"]));
}
}
this->SetVarsLayout(outs, var_layout);
return ins;
}
return LightlyLayoutSensitiveOpTransformer<VarType>::Apply(
ins, outs, attrs, tracer);
}
};
template <typename VarType>
class ConcatOpTransformer
: public LightlyLayoutSensitiveOpTransformer<VarType> {
public:
explicit ConcatOpTransformer(const std::string& type)
: LightlyLayoutSensitiveOpTransformer<VarType>(type) {}
paddle::imperative::NameVarMap<VarType> Apply(
const paddle::imperative::NameVarMap<VarType>& ins,
const paddle::imperative::NameVarMap<VarType>& outs,
paddle::framework::AttributeMap* attrs,
const std::shared_ptr<paddle::imperative::Tracer>& tracer) {
VLOG(3) << "Optimize lightly layout sensitive op " << this->Type();
auto& in_var = ins.at("X")[0];
auto var_layout = paddle::imperative::GetDataLayout(in_var);
bool need_transpose = false;
for (auto& pair : ins) {
for (auto& var : pair.second) {
if (var != nullptr &&
(paddle::imperative::GetDataLayout(var) != var_layout)) {
need_transpose = true;
break;
}
}
}
if (need_transpose) {
return LightlyLayoutSensitiveOpTransformer<VarType>::Apply(
ins, outs, attrs, tracer);
}
if (var_layout != DataLayout::UNDEFINED) {
std::vector<int> perm_nhwc = {0, 3, 1, 2};
std::vector<int> perm_nchw = {0, 2, 3, 1};
auto perm = var_layout == DataLayout::NHWC ? perm_nhwc : perm_nchw;
auto axis = PADDLE_GET_CONST(int, (*attrs)["axis"]);
(*attrs)["axis"] = static_cast<int>(perm[axis]);
}
auto axis = PADDLE_GET_CONST(int, (*attrs)["axis"]);
VLOG(3) << "Optimize lightly layout sensitive op axis" << axis;
this->SetVarsLayout(outs, var_layout);
return ins;
}
};
} // namespace imperative
} // namespace paddle