3625 lines
142 KiB
C++
3625 lines
142 KiB
C++
// 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.
|
|
|
|
#include "paddle/fluid/pybind/pir.h"
|
|
#include <Python.h>
|
|
#include <algorithm>
|
|
#include <iterator>
|
|
#include <memory>
|
|
#include <sstream>
|
|
#include <string>
|
|
#include <unordered_map>
|
|
#include <unordered_set>
|
|
#include <utility>
|
|
#include "paddle/common/enforce.h"
|
|
#include "paddle/common/errors.h"
|
|
#include "paddle/common/flags.h"
|
|
#include "paddle/fluid/framework/executor.h"
|
|
#include "paddle/fluid/framework/ir/pass.h"
|
|
#include "paddle/fluid/framework/program_desc.h"
|
|
#include "paddle/fluid/framework/tensor_util.h"
|
|
#include "paddle/fluid/ir_adaptor/translator/program_translator.h"
|
|
#include "paddle/fluid/ir_adaptor/translator/translate.h"
|
|
#include "paddle/fluid/ir_adaptor/translator/utils.h"
|
|
#include "paddle/fluid/pir/dialect/distributed/ir/dist_attribute.h"
|
|
#include "paddle/fluid/pir/dialect/distributed/ir/dist_dialect.h"
|
|
#include "paddle/fluid/pir/dialect/distributed/ir/dist_tools.h"
|
|
#include "paddle/fluid/pir/dialect/distributed/ir/dist_type.h"
|
|
#include "paddle/fluid/pir/dialect/kernel/ir/kernel_type.h"
|
|
#include "paddle/fluid/pir/dialect/operator/interface/op_yaml_info.h"
|
|
#include "paddle/fluid/pir/dialect/operator/ir/api_builder.h"
|
|
#include "paddle/fluid/pir/dialect/operator/ir/control_flow_op.h"
|
|
#include "paddle/fluid/pir/dialect/operator/ir/manual_pylayer_op.h"
|
|
#include "paddle/fluid/pir/dialect/operator/ir/op_attribute.h"
|
|
#include "paddle/fluid/pir/dialect/operator/ir/op_dialect.h"
|
|
#include "paddle/fluid/pir/dialect/operator/ir/op_type.h"
|
|
#include "paddle/fluid/pir/dialect/operator/ir/pd_api.h"
|
|
#include "paddle/fluid/pir/dialect/operator/ir/pd_op.h"
|
|
#include "paddle/fluid/pir/dialect/operator/trait/inplace.h"
|
|
#include "paddle/fluid/pir/dialect/operator/utils/op_yaml_info_parser.h"
|
|
#include "paddle/fluid/pir/dialect/operator/utils/shape_analysis_utils.h"
|
|
#include "paddle/fluid/pir/dialect/operator/utils/utils.h"
|
|
#include "paddle/fluid/pir/drr/include/drr_pattern_base.h"
|
|
#include "paddle/fluid/pir/serialize_deserialize/include/ir_serialize.h"
|
|
#include "paddle/fluid/pir/transforms/general/common_subexpression_elimination_pass.h"
|
|
#include "paddle/fluid/pir/transforms/general/dead_code_elimination_pass.h"
|
|
#include "paddle/fluid/pir/transforms/gpu/fused_bn_add_act_pass.h"
|
|
#include "paddle/fluid/pir/transforms/passes.h"
|
|
#include "paddle/fluid/pir/utils/general_functions.h"
|
|
#include "paddle/fluid/pir/utils/name_analysis.h"
|
|
#include "paddle/fluid/pybind/control_flow_api.h"
|
|
#include "paddle/fluid/pybind/eager_utils.h"
|
|
#include "paddle/fluid/pybind/pir_utils.h"
|
|
#include "paddle/fluid/pybind/pybind_variant_caster.h"
|
|
#include "paddle/fluid/pybind/size.h"
|
|
#include "paddle/phi/common/data_type.h"
|
|
#include "paddle/phi/common/place.h"
|
|
#include "paddle/phi/core/distributed/auto_parallel/process_mesh.h"
|
|
#include "paddle/phi/core/enforce.h"
|
|
#include "paddle/pir/include/core/attribute.h"
|
|
#include "paddle/pir/include/core/block.h"
|
|
#include "paddle/pir/include/core/builtin_attribute.h"
|
|
#include "paddle/pir/include/core/builtin_op.h"
|
|
#include "paddle/pir/include/core/ir_mapping.h"
|
|
#include "paddle/pir/include/core/ir_printer.h"
|
|
#include "paddle/pir/include/core/operation.h"
|
|
#include "paddle/pir/include/core/program.h"
|
|
#include "paddle/pir/include/core/type.h"
|
|
#include "paddle/pir/include/core/value.h"
|
|
#include "paddle/pir/include/core/visitors.h"
|
|
#include "paddle/pir/include/dialect/control_flow/ir/cf_dialect.h"
|
|
#include "paddle/pir/include/dialect/control_flow/ir/cf_op.h"
|
|
#include "paddle/pir/include/dialect/shape/ir/shape_attribute.h"
|
|
#include "paddle/pir/include/dialect/shape/ir/shape_dialect.h"
|
|
#include "paddle/pir/include/dialect/shape/transforms/shape_optimization_pass.h"
|
|
#include "paddle/pir/include/dialect/shape/utils/original_attributes_filter.h"
|
|
#include "paddle/pir/include/pass/pass.h"
|
|
#include "paddle/pir/include/pass/pass_manager.h"
|
|
#include "paddle/pir/include/pass/pass_registry.h"
|
|
#include "pybind11/stl.h"
|
|
|
|
#ifdef PADDLE_WITH_CINN
|
|
#include "paddle/ap/include/paddle/hlir/op_dialect.h"
|
|
#include "paddle/ap/include/paddle/pass/add_pcc_pass.h"
|
|
#include "paddle/cinn/hlir/dialect/operator/ir/op_dialect.h"
|
|
#include "paddle/cinn/hlir/dialect/operator/transforms/add_cinn_pass.h"
|
|
#include "paddle/cinn/hlir/dialect/operator/transforms/check_infer_symbolic_util.h"
|
|
#include "paddle/cinn/hlir/dialect/operator/transforms/pir_to_py_code_converter.h"
|
|
#include "paddle/cinn/hlir/dialect/operator/transforms/reduce_as_to_sum_pass.h"
|
|
#include "paddle/cinn/hlir/dialect/operator/transforms/specify_input_dynamic_dim_util.h"
|
|
#include "paddle/cinn/hlir/framework/pir_compiler.h"
|
|
#include "paddle/pir/include/dialect/shape/utils/shape_analysis.h"
|
|
#endif
|
|
|
|
using paddle::dialect::ApiBuilder;
|
|
using paddle::dialect::DenseTensorArrayType;
|
|
using paddle::dialect::DenseTensorType;
|
|
using paddle::dialect::DistDenseTensorType;
|
|
using paddle::dialect::DistTypeInterface;
|
|
using paddle::dialect::IfOp;
|
|
using paddle::dialect::PrintOp;
|
|
using paddle::dialect::PyLayerOp;
|
|
using paddle::dialect::SelectedRowsType;
|
|
using paddle::dialect::SparseCooTensorType;
|
|
using paddle::dialect::SparseCsrTensorType;
|
|
using paddle::dialect::WhileOp;
|
|
using pir::TuplePopOp;
|
|
|
|
using paddle::dialect::IntArrayAttribute;
|
|
using paddle::dialect::OperationDistAttribute;
|
|
using paddle::dialect::PlaceAttribute;
|
|
using paddle::dialect::TensorDistAttribute;
|
|
using pir::ArrayAttribute;
|
|
using pir::Attribute;
|
|
using pir::Block;
|
|
using pir::BlockArgument;
|
|
using pir::BoolAttribute;
|
|
using pir::CloneOptions;
|
|
using pir::Int32Attribute;
|
|
using pir::Int64Attribute;
|
|
using pir::IrContext;
|
|
using pir::IrMapping;
|
|
using pir::Operation;
|
|
using pir::OpOperand;
|
|
using pir::OpResult;
|
|
using pir::Pass;
|
|
using pir::PassManager;
|
|
using pir::Program;
|
|
using pir::StrAttribute;
|
|
using pir::Type;
|
|
using pir::Value;
|
|
using pir::VectorType;
|
|
using pybind11::return_value_policy;
|
|
|
|
namespace name_analysis = pir::utils::name_analysis;
|
|
|
|
COMMON_DECLARE_bool(print_ir);
|
|
|
|
namespace paddle {
|
|
namespace pybind {
|
|
|
|
PyTypeObject *g_ir_value_pytype = nullptr;
|
|
namespace py = pybind11;
|
|
|
|
void BindOpsAPI(pybind11::module *module);
|
|
|
|
Value FakeValue() {
|
|
// create a fake value to simplify `ForwardBackwardSplit`.
|
|
return Value(nullptr);
|
|
}
|
|
|
|
bool IsFakeValue(const Value &value) {
|
|
// create a fake value to simplify `ForwardBackwardSplit`.
|
|
return value.impl() == nullptr || !value.type();
|
|
}
|
|
|
|
inline int64_t GetProgramInt64Attr(const std::shared_ptr<Program> &program,
|
|
const std::string &attr_name,
|
|
int64_t default_value = 0) {
|
|
auto op = program->module_op();
|
|
if (op->HasAttribute(attr_name)) {
|
|
auto val = op->attribute(attr_name).dyn_cast<pir::Int64Attribute>().data();
|
|
return val;
|
|
} else {
|
|
return default_value;
|
|
}
|
|
}
|
|
|
|
inline void SetProgramInt64Attr(std::shared_ptr<Program> program,
|
|
const std::string &attr_name,
|
|
int64_t value) {
|
|
auto op = program->module_op();
|
|
op->set_attribute(attr_name,
|
|
pir::Int64Attribute::get(IrContext::Instance(), value));
|
|
}
|
|
|
|
std::string GetValueInfo(Value v) {
|
|
if (v.impl() == nullptr) {
|
|
return "nullptr value";
|
|
}
|
|
std::stringstream ss;
|
|
if (auto op_result = v.dyn_cast<OpResult>()) {
|
|
ss << "define_op_name=" << op_result.owner()->name();
|
|
ss << ", index=" << op_result.index();
|
|
} else if (auto arg = v.dyn_cast<BlockArgument>()) {
|
|
if (arg.is_kwarg()) {
|
|
ss << "keyword block_arg, keyword = " << arg.keyword();
|
|
} else {
|
|
ss << "position block_arg, index = " << arg.index();
|
|
}
|
|
}
|
|
if (!v.type()) {
|
|
ss << ", dtype=<<NULL TYPE>>";
|
|
} else {
|
|
ss << ", dtype=" << v.type();
|
|
if (v.type().isa<paddle::dialect::AllocatedDenseTensorType>()) {
|
|
ss << ", place="
|
|
<< v.type()
|
|
.dyn_cast<paddle::dialect::AllocatedDenseTensorType>()
|
|
.place();
|
|
}
|
|
}
|
|
auto stop_gradient = v.attribute<BoolAttribute>(kAttrStopGradients);
|
|
if (stop_gradient && !stop_gradient.data()) {
|
|
ss << ", stop_gradient=False";
|
|
} else {
|
|
ss << ", stop_gradient=True";
|
|
}
|
|
return ss.str();
|
|
}
|
|
|
|
py::object Clone(const Program &self, IrMapping *p_mapper = nullptr) {
|
|
IrMapping mapper;
|
|
if (p_mapper == nullptr) {
|
|
p_mapper = &mapper;
|
|
}
|
|
auto src_obj = py::cast(self);
|
|
auto new_obj = py::cast(self.Clone(*p_mapper));
|
|
for (auto item : src_obj.attr("__dict__").cast<py::dict>()) {
|
|
new_obj.attr(item.first.cast<std::string>().c_str()) = item.second;
|
|
}
|
|
return new_obj;
|
|
}
|
|
|
|
bool SomeInSet(const std::vector<Value> &vec, const std::set<Value> &set) {
|
|
for (auto &v : vec) {
|
|
if (set.find(v) != set.end()) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
Value AppendDataOp(pir::Block *block,
|
|
const Value &value,
|
|
const std::string &name,
|
|
const Operation &origin_op) {
|
|
IrContext *ctx = IrContext::Instance();
|
|
auto op_info = ctx->GetRegisteredOpInfo(paddle::dialect::DataOp::name());
|
|
pir::AttributeMap attribute_map = {
|
|
{"name", StrAttribute::get(ctx, name)},
|
|
{"shape",
|
|
paddle::dialect::IntArrayAttribute::get(
|
|
ctx, phi::IntArray(phi::vectorize(GetValueDims(value))))},
|
|
{"dtype",
|
|
paddle::dialect::DataTypeAttribute::get(ctx, pir::GetValueDtype(value))},
|
|
{"place", PlaceAttribute::get(ctx, Place())}};
|
|
std::vector<pir::Type> output_types{value.type()};
|
|
Operation *operation =
|
|
Operation::Create({}, attribute_map, output_types, op_info);
|
|
|
|
block->insert(origin_op, operation);
|
|
return operation->result(0);
|
|
}
|
|
std::vector<Value> GetRealOpInputs(Operation *op) {
|
|
if (op->isa<paddle::dialect::IfOp>() ||
|
|
op->isa<paddle::dialect::PyLayerOp>()) {
|
|
return pir::GetUsedExternalValue(*op);
|
|
} else if (op->isa<paddle::dialect::WhileOp>()) {
|
|
paddle::dialect::WhileOp whileop = op->dyn_cast<paddle::dialect::WhileOp>();
|
|
auto value_vector = op->operands_source();
|
|
auto value_vector2 = pir::GetUsedExternalValue(whileop.body());
|
|
value_vector.insert(
|
|
value_vector.end(), value_vector2.begin(), value_vector2.end());
|
|
return value_vector;
|
|
} else {
|
|
return op->operands_source();
|
|
}
|
|
}
|
|
/*
|
|
Variables in input_vars will be the pruned program's inputs,
|
|
and variables in output_vars will be the pruned program's outputs.
|
|
Therefore, the pruning logic includes replacing the input of
|
|
input_vars with the data op, and then preserving all connected
|
|
ops starting from output_vars.
|
|
|
|
Note: The returned program is the original program.
|
|
If you do not want the original program to be modified,
|
|
please pass in a cloned result.
|
|
*/
|
|
void PruneWithInput(const std::vector<Value> &input_vars,
|
|
const std::vector<Value> &output_vars,
|
|
Program *prog) {
|
|
auto global_block = prog->block();
|
|
std::vector<Value> new_input_vars;
|
|
if (!input_vars.empty()) {
|
|
std::vector<Value> new_input_vars;
|
|
for (uint64_t idx = 0; idx < input_vars.size(); idx++) {
|
|
auto input = input_vars[idx];
|
|
auto origin_op = input.defining_op();
|
|
std::string name = name_analysis::TryGetValueFirstName(input).value_or(
|
|
"input_" + std::to_string(idx));
|
|
auto new_input = AppendDataOp(global_block, input, name, *origin_op);
|
|
input.ReplaceAllUsesWith(new_input);
|
|
new_input_vars.push_back(new_input);
|
|
}
|
|
}
|
|
VLOG(6) << "program after add new feed op = " << *prog;
|
|
auto total_ops_list = global_block->ops();
|
|
std::vector<Operation *> total_ops(total_ops_list.begin(),
|
|
total_ops_list.end());
|
|
std::vector<bool> intersection_op_flags(total_ops.size(), true);
|
|
std::set<Value> output_vars_set(output_vars.begin(), output_vars.end());
|
|
for (uint32_t index = total_ops.size() - 1; index != (uint32_t)(-1);
|
|
--index) {
|
|
auto op = total_ops[index];
|
|
auto op_results = op->results();
|
|
if (SomeInSet(op_results, output_vars_set)) {
|
|
for (auto &operand : GetRealOpInputs(op)) {
|
|
output_vars_set.insert(operand);
|
|
}
|
|
} else {
|
|
VLOG(6) << "delete op " << index << ", name is " << op->name();
|
|
intersection_op_flags[index] = false;
|
|
}
|
|
}
|
|
|
|
std::set<Value> input_vars_set(new_input_vars.begin(), new_input_vars.end());
|
|
std::vector<Operation *> remove_ops;
|
|
for (uint32_t index = total_ops.size() - 1; index != (uint32_t)(-1);
|
|
--index) {
|
|
auto op = total_ops[index];
|
|
if (!intersection_op_flags[index]) {
|
|
auto op_results = op->results();
|
|
if (!input_vars_set.empty() && SomeInSet(op_results, input_vars_set)) {
|
|
PADDLE_THROW(common::errors::InvalidArgument(
|
|
"The input_var create by: '{%s}' is not involved in the "
|
|
"output_vars calculation. "
|
|
"Please remove it from input_vars.",
|
|
op->name()));
|
|
}
|
|
global_block->erase(*op);
|
|
}
|
|
}
|
|
}
|
|
|
|
void SetIsTestAttr(const std::shared_ptr<Program> &prog) {
|
|
for (auto &op : prog->block()->ops()) {
|
|
if (op->HasAttribute("is_test")) {
|
|
op->set_attribute("is_test",
|
|
pir::BoolAttribute::get(IrContext::Instance(), true));
|
|
}
|
|
}
|
|
}
|
|
|
|
using ComputeReturnType = std::variant<float,
|
|
double,
|
|
int32_t,
|
|
int64_t,
|
|
bool,
|
|
std::string,
|
|
std::vector<int32_t>,
|
|
std::vector<int64_t>,
|
|
std::vector<float>,
|
|
DataType,
|
|
Place>;
|
|
ComputeReturnType CastPyObjectToAny(const pybind11::object &obj,
|
|
const std::string &type_name) {
|
|
static const std::unordered_map<
|
|
std::string,
|
|
std::function<ComputeReturnType(const pybind11::object &)>>
|
|
type_casters = {
|
|
{"float",
|
|
[](const pybind11::object &obj) { return obj.cast<float>(); }},
|
|
{"double",
|
|
[](const pybind11::object &obj) { return obj.cast<double>(); }},
|
|
{"int32",
|
|
[](const pybind11::object &obj) { return obj.cast<int32_t>(); }},
|
|
{"int64",
|
|
[](const pybind11::object &obj) { return obj.cast<int64_t>(); }},
|
|
{"bool",
|
|
[](const pybind11::object &obj) { return obj.cast<bool>(); }},
|
|
{"string",
|
|
[](const pybind11::object &obj) { return obj.cast<std::string>(); }},
|
|
{"vector<int32>",
|
|
[](const pybind11::object &obj) {
|
|
return obj.cast<std::vector<int32_t>>();
|
|
}},
|
|
{"vector<int64>",
|
|
[](const pybind11::object &obj) {
|
|
return obj.cast<std::vector<int64_t>>();
|
|
}},
|
|
{"vector<float>",
|
|
[](const pybind11::object &obj) {
|
|
return obj.cast<std::vector<float>>();
|
|
}},
|
|
{"datatype",
|
|
[](const pybind11::object &obj) { return obj.cast<DataType>(); }},
|
|
{"place",
|
|
[](const pybind11::object &obj) { return obj.cast<Place>(); }}};
|
|
|
|
auto it = type_casters.find(type_name);
|
|
if (it == type_casters.end()) {
|
|
throw std::runtime_error("Unsupported type: " + type_name);
|
|
}
|
|
return it->second(obj);
|
|
}
|
|
|
|
void BindProgram(py::module *m) {
|
|
static int64_t global_prog_seed = 0;
|
|
py::class_<Program, std::shared_ptr<Program>> program(
|
|
*m, "Program", py::dynamic_attr(), R"DOC(
|
|
Create Python Program. Program is an abstraction of model structure, divided into
|
|
computational graphs and weights. The Program has a main block that stores the computational
|
|
graphs.
|
|
|
|
A set of Program usually contains startup program and main program.
|
|
A startup program is set to contain some initial work, eg. initialize the ``Parameter``, and the main
|
|
program will contain the network structure and vars for train.
|
|
|
|
A set of Program can be used for test or train, in train program ,
|
|
Paddle will contain all content to build a train network, in test
|
|
program Paddle will prune some content which is irrelevant to test, eg.
|
|
backward ops and vars.
|
|
|
|
**Notes**:
|
|
**we have** :ref:`api_paddle_static_default_startup_program` **and** :ref:`api_paddle_static_default_main_program`
|
|
**by default, a pair of them will shared the parameters. The** :ref:`api_paddle_static_default_startup_program` **only run once to initialize parameters,**
|
|
:ref:`api_paddle_static_default_main_program` **run in every mini batch and adjust the weights.**
|
|
|
|
|
|
Returns:
|
|
Program: An empty Program.
|
|
|
|
Examples:
|
|
.. code-block:: pycon
|
|
|
|
>>> import paddle
|
|
>>> import paddle.static as static
|
|
|
|
>>> paddle.enable_static()
|
|
|
|
>>> main_program = static.Program()
|
|
>>> startup_program = static.Program()
|
|
>>> with static.program_guard(main_program=main_program, startup_program=startup_program):
|
|
... x = static.data(name="x", shape=[-1, 784], dtype='float32')
|
|
... y = static.data(name="y", shape=[-1, 1], dtype='int32')
|
|
... z = static.nn.fc(name="fc", x=x, size=10, activation="relu")
|
|
>>> print("main program is: {}".format(main_program))
|
|
>>> print("start up program is: {}".format(startup_program))
|
|
)DOC");
|
|
program
|
|
.def(py::init([]() {
|
|
auto prog = std::make_shared<Program>(IrContext::Instance());
|
|
SetProgramInt64Attr(prog, "random_seed", global_prog_seed);
|
|
return prog;
|
|
}))
|
|
.def("__str__",
|
|
[](const std::shared_ptr<Program> &self) {
|
|
std::ostringstream print_stream;
|
|
self->Print(print_stream);
|
|
return print_stream.str();
|
|
})
|
|
.def("__repr__",
|
|
[](const std::shared_ptr<Program> &self) {
|
|
std::ostringstream print_stream;
|
|
self->Print(print_stream);
|
|
return print_stream.str();
|
|
})
|
|
.def("parameters_num",
|
|
[](const std::shared_ptr<Program> &self) {
|
|
return self->parameters_num();
|
|
})
|
|
.def("set_is_test_attr",
|
|
[](const std::shared_ptr<Program> &self) { SetIsTestAttr(self); })
|
|
.def("set_parameters_from",
|
|
[](const std::shared_ptr<Program> &self,
|
|
const std::shared_ptr<Program> &other) {
|
|
self->set_parameters(other->parameters());
|
|
})
|
|
.def(
|
|
"global_block",
|
|
[](std::shared_ptr<Program> self) { return self->block(); },
|
|
return_value_policy::reference)
|
|
.def("clone", [](Program &self) { return Clone(self); })
|
|
.def("clone",
|
|
[](Program &self, IrMapping &ir_mapper) {
|
|
return Clone(self, &ir_mapper);
|
|
})
|
|
.def(
|
|
"copy_to_block",
|
|
[](std::shared_ptr<Program> self,
|
|
pir::IrMapping &mapper,
|
|
Block *block) { return self->CopyToBlock(mapper, block); },
|
|
return_value_policy::reference)
|
|
.def("list_vars",
|
|
[](std::shared_ptr<Program> self) {
|
|
py::list vars;
|
|
for (auto op : self->block()->ops()) {
|
|
for (auto var : op->results()) {
|
|
vars.append(var);
|
|
}
|
|
}
|
|
return vars;
|
|
})
|
|
.def("_list_named_vars",
|
|
[](std::shared_ptr<Program> self) {
|
|
return name_analysis::GetAllNamedValues(*self);
|
|
})
|
|
.def(
|
|
"global_block",
|
|
[](const std::shared_ptr<Program> &self) { return self->block(); },
|
|
return_value_policy::reference)
|
|
.def_property(
|
|
"random_seed",
|
|
[](const std::shared_ptr<Program> &self) {
|
|
return GetProgramInt64Attr(self, "random_seed", 0);
|
|
},
|
|
[](std::shared_ptr<Program> self, int64_t random_seed) {
|
|
SetProgramInt64Attr(self, "random_seed", random_seed);
|
|
})
|
|
.def_property(
|
|
"_seed",
|
|
[](const std::shared_ptr<Program> &self) {
|
|
return GetProgramInt64Attr(self, "random_seed", 0);
|
|
},
|
|
[](std::shared_ptr<Program> self, int64_t random_seed) {
|
|
SetProgramInt64Attr(self, "random_seed", random_seed);
|
|
})
|
|
.def("global_seed",
|
|
[](std::shared_ptr<Program> self, int64_t random_seed) {
|
|
global_prog_seed = random_seed;
|
|
SetProgramInt64Attr(self, "random_seed", random_seed);
|
|
})
|
|
.def_property_readonly(
|
|
"num_blocks",
|
|
[](const std::shared_ptr<Program> &self) {
|
|
size_t num_blocks = 0;
|
|
auto top_level_op = self->module_op();
|
|
for (size_t i = 0; i < top_level_op->num_regions(); ++i) {
|
|
auto ®ion = top_level_op->region(i);
|
|
num_blocks += region.size();
|
|
}
|
|
return num_blocks;
|
|
})
|
|
.def_property_readonly(
|
|
"blocks",
|
|
[](const std::shared_ptr<Program> &self) {
|
|
// Note: We only return global block currently.
|
|
py::list op_list;
|
|
op_list.append(self->block());
|
|
return op_list;
|
|
},
|
|
return_value_policy::reference)
|
|
.def(
|
|
"get_value_by_op_id",
|
|
[](Program &self, py::object op_ids) {
|
|
std::vector<int> op_ids_list;
|
|
if (py::isinstance<py::int_>(op_ids)) {
|
|
op_ids_list.push_back(op_ids.cast<int>());
|
|
} else if (py::isinstance<py::list>(op_ids)) {
|
|
for (auto item : op_ids) {
|
|
op_ids_list.push_back(item.cast<int>());
|
|
}
|
|
} else {
|
|
PADDLE_THROW(
|
|
"Invalid op_ids format. Please provide either a single "
|
|
"integer or a list of integers.");
|
|
}
|
|
|
|
std::list<Operation *> all_ops = self.block()->get_recursive_ops();
|
|
std::vector<Value> value_list;
|
|
|
|
for (auto op : all_ops) {
|
|
if (std::find(op_ids_list.begin(), op_ids_list.end(), op->id()) !=
|
|
op_ids_list.end()) {
|
|
for (auto value : op->results()) {
|
|
value_list.push_back(value);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (value_list.empty()) {
|
|
PADDLE_THROW(
|
|
"Can't find the corresponding opresult from the op ids");
|
|
}
|
|
return value_list;
|
|
})
|
|
.def("get_output_value_by_name",
|
|
[](Program &self, const std::string &name) {
|
|
return name_analysis::GetOutputValueByName(self, name);
|
|
})
|
|
.def("get_parameter_value_by_name",
|
|
[](Program &self, const std::string &name) {
|
|
return name_analysis::GetParameterValueByName(self, name);
|
|
})
|
|
.def("get_all_parameter_values",
|
|
[](Program &self) {
|
|
return name_analysis::GetAllParameterValues(self);
|
|
})
|
|
.def("num_ops", [](Program &self) { return self.num_ops(); })
|
|
.def("_state_dict",
|
|
[](std::shared_ptr<Program> self,
|
|
const std::string &mode = "all",
|
|
const framework::Scope &scope = framework::Scope()) {
|
|
std::unordered_map<std::string, DenseTensor> state_dict_all;
|
|
std::unordered_map<std::string, DenseTensor> state_dict_param;
|
|
std::unordered_map<std::string, DenseTensor> state_dict_opt;
|
|
for (auto op : self->block()->ops()) {
|
|
for (auto var : op->results()) {
|
|
auto is_persistable =
|
|
var.attribute<BoolAttribute>(kAttrIsPersistable);
|
|
if (is_persistable && is_persistable.data()) {
|
|
if (var.defining_op()->isa<pir::ParameterOp>()) {
|
|
std::string var_name =
|
|
name_analysis::GetValueFirstName(var);
|
|
auto tensor =
|
|
scope.FindVar(var_name)->GetMutable<DenseTensor>();
|
|
state_dict_param[var_name] = *tensor;
|
|
state_dict_all[var_name] = *tensor;
|
|
} else if (var.defining_op()
|
|
->isa<paddle::dialect::DataOp>()) {
|
|
std::string var_name =
|
|
name_analysis::GetValueFirstName(var);
|
|
auto tensor =
|
|
scope.FindVar(var_name)->GetMutable<DenseTensor>();
|
|
state_dict_opt[var_name] = *tensor;
|
|
state_dict_all[var_name] = *tensor;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (mode == "all") {
|
|
return state_dict_all;
|
|
} else if (mode == "param") {
|
|
return state_dict_param;
|
|
} else if (mode == "opt") {
|
|
return state_dict_opt;
|
|
} else {
|
|
PADDLE_THROW(common::errors::InvalidArgument(
|
|
"The mode is not supported."));
|
|
}
|
|
})
|
|
.def(
|
|
"set_state_dict",
|
|
[](std::shared_ptr<Program> self,
|
|
const std::unordered_map<std::string, DenseTensor> &state_dict,
|
|
const framework::Scope &scope = framework::Scope(),
|
|
bool copy_tensor = false) {
|
|
for (auto item : state_dict) {
|
|
auto var = scope.FindVar(item.first);
|
|
if (var == nullptr) {
|
|
PADDLE_THROW(common::errors::NotFound(
|
|
"The variable %s is not found.", item.first));
|
|
} else {
|
|
if (copy_tensor) {
|
|
auto *mutable_tensor = var->GetMutable<DenseTensor>();
|
|
paddle::framework::TensorCopy(
|
|
item.second, item.second.place(), mutable_tensor);
|
|
} else {
|
|
*var->GetMutable<DenseTensor>() = item.second;
|
|
}
|
|
}
|
|
}
|
|
},
|
|
py::arg("state_dict"),
|
|
py::arg("scope"),
|
|
py::arg("copy_tensor") = false)
|
|
.def(
|
|
"_prune",
|
|
[](Program &self, std::vector<Value> output_vars) {
|
|
std::vector<Value> input_vars;
|
|
PruneWithInput(input_vars, output_vars, &self);
|
|
return &self;
|
|
},
|
|
py::arg("targets"),
|
|
"A description for the _prune method")
|
|
.def(
|
|
"_prune_with_input",
|
|
[](Program &self,
|
|
std::vector<Value> input_vars,
|
|
std::vector<Value> output_vars) {
|
|
PruneWithInput(input_vars, output_vars, &self);
|
|
return &self;
|
|
},
|
|
py::arg("feeded_vars"),
|
|
py::arg("targets"))
|
|
.def("_sync_with_cpp", [](const std::shared_ptr<Program> &self) {
|
|
// It's not need _sync_with_cpp in pir, but it's necessary in old static
|
|
// graph. Add empty function to avoid python call error.
|
|
});
|
|
}
|
|
|
|
void RefreshOpStopgradients(Operation *op) {
|
|
if (op->num_operands() == 0 || op->isa<pir::ParameterOp>() ||
|
|
op->isa<paddle::dialect::UniformOp>()) {
|
|
return;
|
|
} else if (op->isa<pir::SliceOp>()) {
|
|
op->dyn_cast<pir::SliceOp>().RefreshStopGradients();
|
|
} else if (op->isa<pir::SplitOp>()) {
|
|
op->dyn_cast<pir::SplitOp>().RefreshStopGradients();
|
|
} else {
|
|
RefreshStopGradientsDefaultly(op);
|
|
}
|
|
}
|
|
|
|
void BindBlock(py::module *m) {
|
|
py::class_<Block> block(*m, "Block", R"DOC(
|
|
In IR, a Block has a list of Operation and can represent a sub computational graph.
|
|
|
|
Notes:
|
|
The constructor of Block should not be invoked directly. You can
|
|
use `Program.block()` to get a block.
|
|
)DOC");
|
|
block.def("empty", &Block::empty)
|
|
.def(
|
|
"__str__",
|
|
[](Block &self) {
|
|
std::ostringstream print_stream;
|
|
pir::IrPrinter printer(print_stream);
|
|
printer.PrintBlock(self);
|
|
return print_stream.str();
|
|
},
|
|
return_value_policy::reference)
|
|
.def(
|
|
"front",
|
|
[](Block &self) { return &self.front(); },
|
|
return_value_policy::reference)
|
|
.def(
|
|
"back",
|
|
[](Block &self) { return &self.back(); },
|
|
return_value_policy::reference)
|
|
.def_property_readonly(
|
|
"parent_op",
|
|
[](Block &self) { return self.GetParentOp(); },
|
|
return_value_policy::reference)
|
|
.def_property_readonly(
|
|
"program",
|
|
[](Block &self) { return self.GetParentOp()->GetParentProgram(); },
|
|
return_value_policy::reference)
|
|
.def_property_readonly(
|
|
"parent_block",
|
|
[](Block &self) { return self.GetParentOp()->GetParent(); },
|
|
return_value_policy::reference)
|
|
.def_property_readonly("ops",
|
|
[](Block &self) -> py::list {
|
|
py::list op_list;
|
|
for (auto &op : self) {
|
|
op_list.append(&op);
|
|
}
|
|
return op_list;
|
|
})
|
|
.def("num_ops", [](Block &self) { return self.num_ops(); })
|
|
.def(
|
|
"__enter__",
|
|
[](Block &self) -> Block & {
|
|
ApiBuilder::Instance().PushInsertionPoint();
|
|
ApiBuilder::Instance().SetInsertionPointToBlockEnd(&self);
|
|
return self;
|
|
},
|
|
return_value_policy::reference)
|
|
.def("__exit__",
|
|
[](Block &self, py::object, py::object, py::object) {
|
|
ApiBuilder::Instance().LoadInsertionPoint();
|
|
})
|
|
.def("__len__", [](Block &self) { return self.size(); })
|
|
.def("args", &Block::args, return_value_policy::reference)
|
|
.def("kwargs", &Block::kwargs, return_value_policy::reference)
|
|
.def("add_arg", &Block::AddArg)
|
|
.def("add_kwarg", &Block::AddKwarg)
|
|
.def("erase_kwarg", &Block::EraseKwarg)
|
|
.def("get_values_by_op_idx",
|
|
[](Block &self, const py::list &op_idxs) -> py::list {
|
|
py::list value_list;
|
|
auto it = self.begin();
|
|
std::set<int> idxs_set;
|
|
for (py::handle item : op_idxs) {
|
|
idxs_set.insert(item.cast<int>());
|
|
}
|
|
for (int i = 0; it != self.end(); ++i, ++it) {
|
|
if (idxs_set.find(i) != idxs_set.end()) {
|
|
for (uint32_t j = 0; j < it->num_results(); ++j) {
|
|
value_list.append(static_cast<Value>(it->result(j)));
|
|
}
|
|
}
|
|
}
|
|
return value_list;
|
|
})
|
|
.def("remove_op",
|
|
[](Block &self, const Operation &op) { self.erase(op); })
|
|
.def(
|
|
"move_op",
|
|
[](Block &self, Operation *op, uint32_t offset) {
|
|
Block::Iterator position = self.begin();
|
|
std::advance(position, offset);
|
|
op->MoveTo(&self, position);
|
|
},
|
|
R"DOC(
|
|
Move an op to a specific position (block.begin() + offset).
|
|
|
|
Args:
|
|
op (pir.Operation): the operator to be moved.
|
|
offset (uint32_t) : offset relative to the begin of the block
|
|
|
|
Returns:
|
|
None
|
|
|
|
)DOC")
|
|
.def(
|
|
"move_op_to_block_end",
|
|
[](Block &self, Operation *op) { op->MoveTo(&self, self.end()); },
|
|
R"DOC(
|
|
Move an op to the end of the block.
|
|
|
|
Args:
|
|
op (pir.Operation): The operator to be moved.
|
|
|
|
Returns:
|
|
None
|
|
|
|
)DOC")
|
|
.def("all_parameters",
|
|
[](Block &self) -> py::list {
|
|
py::list param_list;
|
|
for (auto &op : self) {
|
|
if (op.name() == "builtin.parameter" &&
|
|
op.HasAttribute(kAttrIsPersistable)) {
|
|
auto attrs = op.attribute(kAttrIsPersistable)
|
|
.dyn_cast<pir::ArrayAttribute>()
|
|
.AsVector();
|
|
for (uint32_t i = 0; i < attrs.size(); i++) {
|
|
bool is_persistable =
|
|
attrs[i].dyn_cast<pir::BoolAttribute>().data();
|
|
if (is_persistable) {
|
|
param_list.append(static_cast<Value>(op.result(i)));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return param_list;
|
|
})
|
|
.def("refresh_stopgradient",
|
|
[](Block &self) {
|
|
for (auto &op : self) {
|
|
RefreshOpStopgradients(&op);
|
|
}
|
|
})
|
|
.def("_sync_with_cpp", [](const Block &self) {
|
|
// It's not need _sync_with_cpp in pir, but it's necessary in old static
|
|
// graph. Add empty function to avoid python call error.
|
|
});
|
|
}
|
|
|
|
void BindIrMapping(py::module *m) {
|
|
py::class_<IrMapping> ir_mapping(*m, "IrMapping");
|
|
ir_mapping.def(py::init<>())
|
|
.def("look_up",
|
|
[](IrMapping &self, Value from) { return self.Lookup(from); })
|
|
.def("has", [](IrMapping &self, Value from) { return self.Has(from); })
|
|
.def("add",
|
|
[](IrMapping &self, Value from, Value to) {
|
|
self.Add<Value>(from, to);
|
|
})
|
|
.def("size",
|
|
[](IrMapping &self) { return self.GetMutableMap<Value>().size(); });
|
|
}
|
|
|
|
void BindCloneOptions(py::module *m) {
|
|
py::class_<CloneOptions> clone_options(*m, "CloneOptions");
|
|
clone_options.def(
|
|
"__init__",
|
|
[](CloneOptions &self,
|
|
bool clone_regions,
|
|
bool clone_operands,
|
|
bool clone_successors) {
|
|
new (&self)
|
|
CloneOptions(clone_regions, clone_operands, clone_successors);
|
|
},
|
|
return_value_policy::reference);
|
|
}
|
|
|
|
void BindOperation(py::module *m) {
|
|
py::class_<Operation> op(*m, "Operation", R"DOC(
|
|
In IR, all the operation are represented by Operation, and Operation
|
|
is regarded as a build in an instruction of a Block. Users can call
|
|
python api to describe their neural network.
|
|
|
|
Notes:
|
|
The constructor of operator should not be invoked directly. Use
|
|
python api, for example: paddle.mean for building mean operation.
|
|
|
|
)DOC");
|
|
op.def("name", &Operation::name)
|
|
.def("get_parent_block",
|
|
&Operation::GetParent,
|
|
return_value_policy::reference)
|
|
.def("num_operands", &Operation::num_operands)
|
|
.def("num_results", &Operation::num_results)
|
|
.def("num_regions", &Operation::num_regions)
|
|
.def("id", &Operation::id)
|
|
.def("operand", &Operation::operand)
|
|
.def("result",
|
|
[](Operation &self, uint32_t index) {
|
|
return static_cast<Value>(self.result(index));
|
|
})
|
|
.def("operand_source", &Operation::operand_source)
|
|
.def("operands", &Operation::operands)
|
|
.def("results",
|
|
[](Operation &self) -> py::list {
|
|
py::list value_list;
|
|
for (uint32_t i = 0; i < self.num_results(); i++) {
|
|
value_list.append(static_cast<Value>(self.result(i)));
|
|
}
|
|
return value_list;
|
|
})
|
|
.def(
|
|
"blocks",
|
|
[](Operation &self) { return &self.blocks(); },
|
|
return_value_policy::reference)
|
|
.def("has_attr", &Operation::HasAttribute)
|
|
.def("str_attr",
|
|
[](Operation &self, const std::string &attr_name) -> py::object {
|
|
auto str_attr = self.attribute<StrAttribute>(attr_name);
|
|
if (str_attr) {
|
|
return py::cast(str_attr.AsString());
|
|
} else {
|
|
return py::cast<py::none>(Py_None);
|
|
}
|
|
})
|
|
.def("int_attr",
|
|
[](Operation &self, const std::string &attr_name) -> py::object {
|
|
auto int_attr = self.attribute<Int32Attribute>(attr_name);
|
|
if (int_attr) {
|
|
return py::cast(int_attr.data());
|
|
} else {
|
|
return py::cast<py::none>(Py_None);
|
|
}
|
|
})
|
|
.def("set_bool_attr",
|
|
[](Operation &self, std::string &attr_name, bool flag) {
|
|
self.set_attribute(
|
|
attr_name,
|
|
pir::BoolAttribute::get(IrContext::Instance(), flag));
|
|
})
|
|
.def("set_int_array_attr",
|
|
[](Operation &self,
|
|
std::string &attr_name,
|
|
const std::vector<int64_t> &val) {
|
|
auto attr = IntArrayAttribute::get(IrContext::Instance(),
|
|
phi::IntArray(val));
|
|
self.set_attribute(attr_name, attr);
|
|
})
|
|
.def("set_str_array_attr",
|
|
[](Operation &self,
|
|
std::string &attr_name,
|
|
const std::vector<std::string> &val) {
|
|
std::vector<Attribute> val_attr;
|
|
for (auto &str : val) {
|
|
val_attr.emplace_back(
|
|
StrAttribute::get(IrContext::Instance(), str));
|
|
}
|
|
auto attr =
|
|
pir::ArrayAttribute::get(IrContext::Instance(), val_attr);
|
|
self.set_attribute(attr_name, attr);
|
|
})
|
|
.def("set_str_attr",
|
|
[](Operation &self, std::string &attr_name, std::string &val) {
|
|
self.set_attribute(attr_name,
|
|
StrAttribute::get(IrContext::Instance(), val));
|
|
})
|
|
.def("set_int_attr",
|
|
[](Operation &self, std::string &attr_name, const int &val) {
|
|
self.set_attribute(
|
|
attr_name,
|
|
pir::Int32Attribute::get(IrContext::Instance(), val));
|
|
})
|
|
.def("erase_attr",
|
|
[](Operation &self, std::string &attr_name) {
|
|
self.erase_attribute(attr_name);
|
|
})
|
|
.def("attrs",
|
|
[](Operation &self) -> py::dict {
|
|
py::dict attrs_dict;
|
|
for (auto &pair : self.attributes()) {
|
|
// SymbolAttribute is only used in PIR, no need to pass to Python
|
|
if (pair.second.isa<pir::shape::SymbolAttribute>()) continue;
|
|
if (pair.first == kAttrOpDistAttr) {
|
|
attrs_dict[pair.first.c_str()] =
|
|
pair.second.dyn_cast<OperationDistAttribute>();
|
|
} else {
|
|
if (pair.second.isa<pir::FloatAttribute>()) {
|
|
VLOG(2) << "The value is stored with float32 precision, "
|
|
"which may cause precision issues for higher "
|
|
"precision requirements.";
|
|
}
|
|
attrs_dict[pair.first.c_str()] =
|
|
paddle::dialect::GetAttributeData(pair.second);
|
|
}
|
|
}
|
|
return attrs_dict;
|
|
})
|
|
|
|
.def("copy_attrs_from",
|
|
[](Operation &self, Operation &other) {
|
|
for (auto &pair : other.attributes()) {
|
|
self.set_attribute(pair.first, pair.second);
|
|
}
|
|
})
|
|
.def("set_execution_stream",
|
|
[](Operation &self, const std::string &exe_stream) {
|
|
self.set_attribute(
|
|
"execution_stream",
|
|
StrAttribute::get(IrContext::Instance(), exe_stream));
|
|
})
|
|
.def("set_scheduling_priority",
|
|
[](Operation &self, int64_t priority) {
|
|
self.set_attribute(
|
|
"scheduling_priority",
|
|
pir::Int64Attribute::get(IrContext::Instance(), priority));
|
|
})
|
|
.def("operands_source",
|
|
[](Operation &self) -> py::list {
|
|
py::list op_list;
|
|
for (uint32_t i = 0; i < self.num_operands(); i++) {
|
|
op_list.append(self.operand_source(i));
|
|
}
|
|
return op_list;
|
|
})
|
|
.def("get_input_names",
|
|
[](Operation &self) -> py::list {
|
|
if (self.HasInterface<paddle::dialect::OpYamlInfoInterface>() ==
|
|
false) {
|
|
PADDLE_THROW(common::errors::InvalidArgument(
|
|
"Currently, we can only get input names of Operation that "
|
|
"has OpYamlInfoInterface"));
|
|
}
|
|
|
|
py::list op_list;
|
|
paddle::dialect::OpYamlInfoInterface yaml_interface =
|
|
self.dyn_cast<paddle::dialect::OpYamlInfoInterface>();
|
|
auto inputs_info = std::get<0>(yaml_interface.GetOpInfo());
|
|
for (auto &input_info : inputs_info) {
|
|
op_list.append(input_info.name);
|
|
}
|
|
return op_list;
|
|
})
|
|
.def("get_attr_names",
|
|
[](Operation &self) -> py::list {
|
|
py::list op_list;
|
|
paddle::dialect::OpYamlInfoInterface yaml_interface =
|
|
self.dyn_cast<paddle::dialect::OpYamlInfoInterface>();
|
|
auto attrs_info = std::get<1>(yaml_interface.GetOpInfo());
|
|
for (auto &attr_info : attrs_info) {
|
|
op_list.append(attr_info.name);
|
|
}
|
|
return op_list;
|
|
})
|
|
.def("get_output_names",
|
|
[](Operation &self) -> py::list {
|
|
py::list op_list;
|
|
paddle::dialect::OpYamlInfoInterface yaml_interface =
|
|
self.dyn_cast<paddle::dialect::OpYamlInfoInterface>();
|
|
auto outputs_info = std::get<2>(yaml_interface.GetOpInfo());
|
|
for (auto &output_info : outputs_info) {
|
|
op_list.append(output_info.name);
|
|
}
|
|
return op_list;
|
|
})
|
|
.def("get_output_intermediate_status",
|
|
[](Operation &self) -> py::list {
|
|
py::list op_list;
|
|
paddle::dialect::OpYamlInfoInterface yaml_interface =
|
|
self.dyn_cast<paddle::dialect::OpYamlInfoInterface>();
|
|
auto outputs_info = std::get<2>(yaml_interface.GetOpInfo());
|
|
for (auto &output_info : outputs_info) {
|
|
op_list.append(output_info.intermediate);
|
|
}
|
|
return op_list;
|
|
})
|
|
.def("get_input_grad_semantics",
|
|
[](Operation &self) -> py::list {
|
|
if (self.HasInterface<paddle::dialect::OpYamlInfoInterface>() ==
|
|
false) {
|
|
PADDLE_THROW(common::errors::InvalidArgument(
|
|
"Currently, we can only get input grad semantics of "
|
|
"Operation that "
|
|
"has OpYamlInfoInterface"));
|
|
}
|
|
py::list op_list;
|
|
paddle::dialect::OpYamlInfoInterface yaml_interface =
|
|
self.dyn_cast<paddle::dialect::OpYamlInfoInterface>();
|
|
auto inputs_grad_info = std::get<0>(yaml_interface.GetOpInfo());
|
|
for (auto &input_grad_info : inputs_grad_info) {
|
|
op_list.append(input_grad_info.with_grad_semantic);
|
|
}
|
|
return op_list;
|
|
})
|
|
.def("replace_all_uses_with",
|
|
[](Operation &self, const std::vector<Value> &values) {
|
|
self.ReplaceAllUsesWith(values);
|
|
})
|
|
.def("as_if_op",
|
|
[](Operation &self) { return PyIfOp(self.dyn_cast<IfOp>()); })
|
|
.def("as_pylayer_op",
|
|
[](Operation &self) -> PyLayerOp {
|
|
auto pylayer_op = self.dyn_cast<PyLayerOp>();
|
|
if (!pylayer_op) {
|
|
PADDLE_THROW(common::errors::InvalidArgument(
|
|
"Can't cast non-pylayer_op type Operation to PyLayerOp."));
|
|
}
|
|
return pylayer_op;
|
|
})
|
|
.def("as_while_op",
|
|
[](Operation &self) { return PyWhileOp(self.dyn_cast<WhileOp>()); })
|
|
.def(
|
|
"as_tuple_pop_op",
|
|
[](Operation &self) -> TuplePopOp {
|
|
auto tuple_pop_op = self.dyn_cast<TuplePopOp>();
|
|
if (!tuple_pop_op) {
|
|
PADDLE_THROW(common::errors::InvalidArgument(
|
|
"Can't cast non-tuple_pop_op type Operation to TuplePopOp."));
|
|
}
|
|
return tuple_pop_op;
|
|
})
|
|
.def("__repr__",
|
|
|
|
[](Operation &self) {
|
|
std::ostringstream print_stream;
|
|
print_stream << "Operation(";
|
|
self.Print(print_stream);
|
|
print_stream << ")";
|
|
return print_stream.str();
|
|
})
|
|
.def(
|
|
"clone",
|
|
[](Operation &self, IrMapping &ir_mapping, CloneOptions options) {
|
|
auto op = self.Clone(ir_mapping, options);
|
|
return ApiBuilder::Instance().GetBuilder()->Insert(op);
|
|
},
|
|
return_value_policy::reference)
|
|
.def("erase", &Operation::Erase)
|
|
.def("move_before",
|
|
[](Operation &self, Operation &other) {
|
|
self.MoveTo(other.GetParent(), Block::Iterator{other});
|
|
})
|
|
.def_property(
|
|
"callstack",
|
|
[](Operation &self) -> py::list {
|
|
py::list callstack_list;
|
|
if (!self.HasAttribute(paddle::framework::OpProtoAndCheckerMaker::
|
|
OpCreationCallstackAttrName())) {
|
|
return callstack_list;
|
|
}
|
|
pir::Attribute op_callstack = self.attribute<pir::Attribute>(
|
|
paddle::framework::OpProtoAndCheckerMaker::
|
|
OpCreationCallstackAttrName());
|
|
PADDLE_ENFORCE(op_callstack.isa<pir::ArrayAttribute>(),
|
|
common::errors::PreconditionNotMet(
|
|
"The callstack of operation `%s` should be an "
|
|
"array attribute.",
|
|
self.name()));
|
|
auto op_callstack_array_attr =
|
|
op_callstack.dyn_cast<pir::ArrayAttribute>();
|
|
for (size_t i = 0; i < op_callstack_array_attr.size(); ++i) {
|
|
PADDLE_ENFORCE(
|
|
op_callstack_array_attr.at(i).isa<StrAttribute>(),
|
|
common::errors::PreconditionNotMet(
|
|
"The callstack info of operation `%s` should be array of "
|
|
"string attribute.",
|
|
self.name()));
|
|
callstack_list.append(op_callstack_array_attr.at(i)
|
|
.dyn_cast<StrAttribute>()
|
|
.AsString());
|
|
}
|
|
return callstack_list;
|
|
},
|
|
[](Operation &self,
|
|
const std::vector<std::string> &callstack) -> void {
|
|
std::vector<pir::Attribute> op_callstack_infos;
|
|
for (auto str : callstack) {
|
|
op_callstack_infos.push_back(
|
|
StrAttribute::get(IrContext::Instance(), str));
|
|
}
|
|
|
|
self.set_attribute(paddle::framework::OpProtoAndCheckerMaker::
|
|
OpCreationCallstackAttrName(),
|
|
pir::ArrayAttribute::get(IrContext::Instance(),
|
|
op_callstack_infos));
|
|
})
|
|
.def_property(
|
|
"dist_attr",
|
|
[](Operation &self) -> py::object {
|
|
if (self.HasAttribute(kAttrOpDistAttr)) {
|
|
return py::cast(
|
|
self.attribute<OperationDistAttribute>(kAttrOpDistAttr));
|
|
} else {
|
|
return py::cast<py::none>(Py_None);
|
|
}
|
|
},
|
|
[](Operation &self, OperationDistAttribute op_dist_attr) {
|
|
self.set_attribute(kAttrOpDistAttr, op_dist_attr);
|
|
})
|
|
.def_property(
|
|
"op_role",
|
|
[](Operation &self) -> py::object {
|
|
auto int_attr = self.attribute<Int32Attribute>("op_role");
|
|
if (int_attr) {
|
|
return py::cast(int_attr.data());
|
|
} else {
|
|
return py::cast(-1);
|
|
}
|
|
},
|
|
[](Operation &self, const int &op_role) {
|
|
self.set_attribute(
|
|
"op_role", Int32Attribute::get(IrContext::Instance(), op_role));
|
|
})
|
|
.def_property(
|
|
"chunk_id",
|
|
[](Operation &self) -> py::object {
|
|
auto int_attr = self.attribute<Int32Attribute>("chunk_id");
|
|
if (int_attr) {
|
|
return py::cast(int_attr.data());
|
|
} else {
|
|
return py::cast(-1);
|
|
}
|
|
},
|
|
[](Operation &self, const int &chunk_id) {
|
|
self.set_attribute(
|
|
"chunk_id",
|
|
Int32Attribute::get(IrContext::Instance(), chunk_id));
|
|
})
|
|
.def("is_no_need_buffer",
|
|
[](Operation &self, const Value &operand_source) -> bool {
|
|
paddle::dialect::OpYamlInfoInterface op_info_interface =
|
|
self.dyn_cast<paddle::dialect::OpYamlInfoInterface>();
|
|
std::unique_ptr<paddle::dialect::OpYamlInfoParser> info_parser(
|
|
nullptr);
|
|
if (op_info_interface) {
|
|
info_parser =
|
|
std::make_unique<paddle::dialect::OpYamlInfoParser>(
|
|
op_info_interface.GetOpInfo(),
|
|
paddle::dialect::IsLegacyOp(self.name()));
|
|
auto &no_need_buffer_ids = info_parser->NoNeedBufferIds();
|
|
for (auto no_need_buffer_id : no_need_buffer_ids) {
|
|
if (operand_source == self.operand_source(no_need_buffer_id)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
});
|
|
py::class_<Operation::BlockContainer> block_container(
|
|
*m, "Operation_BlockContainer", R"DOC(
|
|
The Operation_BlockContainer only use to walk all blocks in the operation.
|
|
)DOC");
|
|
block_container.def(
|
|
"__iter__",
|
|
[](Operation::BlockContainer &self) {
|
|
return py::make_iterator(self.begin(), self.end());
|
|
},
|
|
py::keep_alive<0, 1>());
|
|
}
|
|
|
|
py::str Value2String(Value self) {
|
|
std::ostringstream print_stream;
|
|
print_stream << "Value(";
|
|
print_stream << GetValueInfo(self);
|
|
print_stream << ")";
|
|
return print_stream.str();
|
|
}
|
|
|
|
const phi::DDim &GetTensorDims(Type type) {
|
|
if (!type) {
|
|
PADDLE_THROW(common::errors::InvalidArgument(
|
|
"The type used to get dims is nullptr."));
|
|
}
|
|
if (auto dense_type = type.dyn_cast<DenseTensorType>()) {
|
|
return dense_type.dims();
|
|
} else if (auto select_rows_type = type.dyn_cast<SelectedRowsType>()) {
|
|
return select_rows_type.dims();
|
|
} else if (auto sparse_coo_tensor_type =
|
|
type.dyn_cast<SparseCooTensorType>()) {
|
|
return sparse_coo_tensor_type.dims();
|
|
} else if (auto sparse_csr_tensor_type =
|
|
type.dyn_cast<SparseCsrTensorType>()) {
|
|
return sparse_csr_tensor_type.dims();
|
|
} else if (auto dense_array_type = type.dyn_cast<DenseTensorArrayType>()) {
|
|
return dense_array_type.dims();
|
|
} else {
|
|
PADDLE_THROW(common::errors::InvalidArgument(
|
|
"Currently, we can only get shape for dense and select rows type."));
|
|
}
|
|
}
|
|
const phi::DDim &GetValueDims(Value value) {
|
|
return GetTensorDims(value.type());
|
|
}
|
|
|
|
Value apply(Value self, py::object func) {
|
|
py::gil_scoped_acquire gil;
|
|
auto stop_gradient = self.attribute<BoolAttribute>(kAttrStopGradients);
|
|
if (stop_gradient && !stop_gradient.data()) {
|
|
PADDLE_THROW(common::errors::Unavailable(
|
|
"Cannot apply function on a tensor that required gradient."));
|
|
}
|
|
PyObject *py_func = func.release().ptr();
|
|
Py_INCREF(py_func);
|
|
PyObject *res = nullptr;
|
|
try {
|
|
py::object obj = py::cast(self);
|
|
PyObject *tmp_self = obj.release().ptr();
|
|
Py_INCREF(tmp_self);
|
|
res = PyObject_CallFunctionObjArgs(py_func, tmp_self, nullptr);
|
|
Py_DECREF(tmp_self);
|
|
} catch (std::exception &e) {
|
|
PADDLE_THROW(common::errors::Unavailable(
|
|
"Apply function of Tensor raises an exception: %s.", e.what()));
|
|
} catch (...) {
|
|
PADDLE_THROW(common::errors::Fatal(
|
|
"Apply function of Tensor raises an unknown exception."));
|
|
}
|
|
if (res == Py_None) {
|
|
return self;
|
|
}
|
|
auto out = CastPyArg2Value(res, "", 0, false);
|
|
Py_DECREF(py_func);
|
|
Py_DECREF(res);
|
|
return out;
|
|
}
|
|
|
|
#define DEF_VALUE_BOOL_PROPERTY(name) \
|
|
def_property( \
|
|
name, \
|
|
[](Value self) { \
|
|
auto bool_data = self.attribute<BoolAttribute>(name); \
|
|
return bool_data && bool_data.data(); \
|
|
}, \
|
|
[](Value self, bool bool_data) { \
|
|
self.set_attribute( \
|
|
name, BoolAttribute::get(IrContext::Instance(), bool_data)); \
|
|
})
|
|
|
|
#define DEF_VALUE_STOP_GRADIENT_PROPERTY(name) \
|
|
def_property( \
|
|
name, \
|
|
[](Value self) { \
|
|
auto bool_data = self.attribute<BoolAttribute>(name); \
|
|
return !bool_data || bool_data.data(); \
|
|
}, \
|
|
[](Value self, bool bool_data) { \
|
|
self.set_attribute( \
|
|
name, BoolAttribute::get(IrContext::Instance(), bool_data)); \
|
|
})
|
|
|
|
#define DEF_VALUE_POINTER_PROPERTY(name) \
|
|
def_property( \
|
|
name, \
|
|
[](Value self) -> py::object { \
|
|
auto prop_ptr = self.property(name); \
|
|
if (!prop_ptr) { \
|
|
return py::cast<py::none>(Py_None); \
|
|
} \
|
|
auto py_data = reinterpret_cast<PyObject *>(prop_ptr); \
|
|
py::object obj = \
|
|
py::reinterpret_borrow<py::object>(py::handle(py_data)); \
|
|
return obj; \
|
|
}, \
|
|
[](Value self, py::object obj) { \
|
|
pir::PropertiesDeleter deleter = [](void *python_obj) { \
|
|
Py_DECREF(python_obj); \
|
|
}; \
|
|
PyObject *pointer_data = obj.release().ptr(); \
|
|
pir::Property value_property(reinterpret_cast<void *>(pointer_data), \
|
|
deleter); \
|
|
self.set_property(name, value_property); \
|
|
})
|
|
|
|
void BindValue(py::module *m) {
|
|
py::class_<Value> value(*m,
|
|
"Value",
|
|
R"DOC(
|
|
Value class represents the SSA value in the IR system. It is a directed edge
|
|
and a base class.
|
|
|
|
Notes:
|
|
The constructor of Value should not be invoked directly. Value can be automatically constructed
|
|
when build network.
|
|
|
|
)DOC");
|
|
g_ir_value_pytype = reinterpret_cast<PyTypeObject *>(value.ptr());
|
|
value.def(py::init<>())
|
|
.def(py::init([](Value value) { return value; }))
|
|
.def_property_readonly(
|
|
"block",
|
|
[](Value self) {
|
|
if (auto op_result = self.dyn_cast<OpResult>()) {
|
|
return op_result.owner()->GetParent();
|
|
}
|
|
return self.dyn_cast<BlockArgument>().owner();
|
|
},
|
|
return_value_policy::reference)
|
|
.def_property_readonly(
|
|
"id",
|
|
[](Value self) {
|
|
if (self.impl() == nullptr) {
|
|
PADDLE_THROW(common::errors::InvalidArgument(
|
|
"Currently, we can only get id of Value whose impl "
|
|
"is not nullptr"));
|
|
} else {
|
|
std::stringstream ss;
|
|
ss << std::hex << self.impl();
|
|
return ss.str();
|
|
}
|
|
})
|
|
.def_property(
|
|
"name",
|
|
[](Value self) -> std::string {
|
|
return name_analysis::GetValueFirstName(self);
|
|
},
|
|
[](Value self, const std::string &name) {
|
|
name_analysis::SetValueName(self, name);
|
|
})
|
|
.def_property_readonly(
|
|
"has_name",
|
|
[](Value self) {
|
|
return name_analysis::TryGetValueFirstName(self).has_value();
|
|
})
|
|
// Return all Maybe names of given Value, for example:
|
|
// DataOp("var_1") -> %0 -> shadow_output("output_2")
|
|
// Return ["var_1", "output_2"]
|
|
.def_property_readonly("_names",
|
|
[](Value self) -> py::list {
|
|
std::vector<std::string> names =
|
|
name_analysis::GetValueAllNames(self);
|
|
return py::cast(names);
|
|
})
|
|
.def_property(
|
|
"shape",
|
|
[](Value self) {
|
|
auto array = phi::vectorize(GetValueDims(self));
|
|
auto ptr =
|
|
Paddle_Size_NewFromInt64Array(array.data(), array.size());
|
|
if (!ptr) {
|
|
throw py::error_already_set();
|
|
}
|
|
|
|
return py::reinterpret_steal<py::object>(ptr);
|
|
},
|
|
[](Value self, const std::vector<int> &shape) {
|
|
PADDLE_THROW(common::errors::InvalidArgument(
|
|
"can't set shape when building static graph"));
|
|
})
|
|
.def_property(
|
|
"_local_shape",
|
|
[](Value self) {
|
|
if (!self.type().isa<DistDenseTensorType>()) {
|
|
PADDLE_THROW(common::errors::InvalidArgument(
|
|
"_local_shape is only for distdense tensor."));
|
|
}
|
|
return phi::vectorize(
|
|
self.type().dyn_cast<DistDenseTensorType>().local_ddim());
|
|
},
|
|
[](Value self, const std::vector<int> &shape) {
|
|
PADDLE_THROW(common::errors::InvalidArgument(
|
|
"can't set _local_shape when building static graph"));
|
|
})
|
|
.def_property(
|
|
"dtype",
|
|
[](Value self) { return pir::GetValueDtype(self); },
|
|
[](Value self, DataType dtype) {
|
|
PADDLE_THROW(common::errors::InvalidArgument(
|
|
"can't set dtype when building static graph"));
|
|
})
|
|
.def_property(
|
|
"place_attr",
|
|
[](Value self) -> Place {
|
|
auto place_attr = self.attribute<PlaceAttribute>("place");
|
|
return place_attr ? place_attr.data() : Place();
|
|
},
|
|
[](Value self, const Place &place) {
|
|
// auto place = CastPyArg2Place(place_obj.release().ptr(), 1);
|
|
auto place_attr =
|
|
dialect::PlaceAttribute::get(IrContext::Instance(), place);
|
|
self.set_attribute("place", place_attr);
|
|
})
|
|
.def("initialized",
|
|
[](Value self) {
|
|
if (self.impl() == nullptr || self.type().storage() == nullptr) {
|
|
return false;
|
|
} else {
|
|
return true;
|
|
}
|
|
})
|
|
.DEF_VALUE_STOP_GRADIENT_PROPERTY("stop_gradient")
|
|
.DEF_VALUE_BOOL_PROPERTY("trainable")
|
|
.DEF_VALUE_BOOL_PROPERTY("persistable")
|
|
.DEF_VALUE_BOOL_PROPERTY("need_clip")
|
|
.DEF_VALUE_BOOL_PROPERTY("is_distributed")
|
|
.DEF_VALUE_BOOL_PROPERTY("is_parameter")
|
|
.DEF_VALUE_POINTER_PROPERTY("optimize_attr")
|
|
.DEF_VALUE_POINTER_PROPERTY("regularizer")
|
|
.DEF_VALUE_POINTER_PROPERTY("do_model_average")
|
|
.def("all_used_ops",
|
|
[](Value &self) -> py::list {
|
|
py::list op_list;
|
|
for (auto it = self.use_begin(); it != self.use_end(); ++it) {
|
|
op_list.append(it.owner());
|
|
}
|
|
return op_list;
|
|
})
|
|
.def("all_used_ops_in_same_block",
|
|
[](Value &self) -> py::list {
|
|
py::list op_list;
|
|
for (auto it = self.use_begin(); it != self.use_end(); ++it) {
|
|
Operation *used_op = it.owner();
|
|
while (used_op->GetParent() != self.defining_op()->GetParent() &&
|
|
used_op->GetParent()->GetParentOp()) {
|
|
used_op = used_op->GetParent()->GetParentOp();
|
|
}
|
|
op_list.append(used_op);
|
|
}
|
|
return op_list;
|
|
})
|
|
.def(
|
|
"get_defining_op",
|
|
[](Value self) -> Operation * { return self.defining_op(); },
|
|
return_value_policy::reference)
|
|
.def("type", &Value::type)
|
|
.def("index",
|
|
[](Value self) -> uint32_t {
|
|
if (auto op_result = self.dyn_cast<OpResult>()) {
|
|
return op_result.index();
|
|
} else if (auto arg = self.dyn_cast<BlockArgument>()) {
|
|
if (!arg.is_kwarg()) {
|
|
return arg.index();
|
|
}
|
|
}
|
|
PADDLE_THROW(common::errors::InvalidArgument(
|
|
"only support accessing index from op_result or positional "
|
|
"block arg."));
|
|
})
|
|
.def("is_dense_tensor_type",
|
|
[](Value self) { return self.type().isa<DenseTensorType>(); })
|
|
.def("is_selected_row_type",
|
|
[](Value self) { return self.type().isa<SelectedRowsType>(); })
|
|
.def("is_sparse_coo_tensor_type",
|
|
[](Value self) { return self.type().isa<SparseCooTensorType>(); })
|
|
.def("is_sparse_csr_tensor_type",
|
|
[](Value self) { return self.type().isa<SparseCsrTensorType>(); })
|
|
.def("is_dense_tensor_array_type",
|
|
[](Value self) { return self.type().isa<DenseTensorArrayType>(); })
|
|
.def("is_dist_dense_tensor_type",
|
|
[](Value self) { return self.type().isa<DistDenseTensorType>(); })
|
|
.def("value_assign", [](Value &self, Value value) { self = value; })
|
|
.def("replace_all_uses_with",
|
|
[](Value self, Value value) { self.ReplaceAllUsesWith(value); })
|
|
.def("replace_grad_users_with",
|
|
[](Value self,
|
|
Value value,
|
|
std::unordered_set<Operation *> &grad_ops) {
|
|
for (auto it = self.use_begin(); it != self.use_end();) {
|
|
auto use_op = it.owner();
|
|
if (grad_ops.find(use_op) != grad_ops.end()) {
|
|
(it++)->set_source(value);
|
|
} else {
|
|
it++;
|
|
}
|
|
}
|
|
})
|
|
.def("set_type", [](Value self, Type type) { self.set_type(type); })
|
|
.def("first_use", &Value::first_use, return_value_policy::reference)
|
|
.def("has_one_use", &Value::HasOneUse)
|
|
.def("use_empty", &Value::use_empty)
|
|
.def("apply", &apply)
|
|
.def("is_same", &Value::operator==)
|
|
.def("hash", [](Value self) { return std::hash<Value>{}(self); })
|
|
.def("element_size",
|
|
[](Value self) { return phi::SizeOf(pir::GetValueDtype(self)); })
|
|
.def(
|
|
"stride",
|
|
[](Value self, py::object dim_obj = py::none()) {
|
|
const auto &dims = paddle::pybind::GetValueDims(self);
|
|
std::vector<int64_t> strides;
|
|
|
|
int64_t step = 1;
|
|
for (int i = static_cast<int>(dims.size()) - 1; i >= 0; --i) {
|
|
strides.insert(strides.begin(), step);
|
|
step *= dims[i];
|
|
}
|
|
|
|
if (dim_obj.is_none()) {
|
|
return py::cast(strides);
|
|
}
|
|
|
|
int dim = py::cast<int>(dim_obj);
|
|
dim = dim < 0 ? dim + static_cast<int>(dims.size()) : dim;
|
|
|
|
PADDLE_ENFORCE_EQ(dim >= 0 && dim < static_cast<int>(dims.size()),
|
|
true,
|
|
common::errors::InvalidArgument(
|
|
"Dimension out of range (expected to be in "
|
|
"range of [%d, %d], "
|
|
"but got %d)",
|
|
-static_cast<int>(dims.size()),
|
|
static_cast<int>(dims.size()) - 1,
|
|
dim));
|
|
|
|
return py::cast(strides[dim]);
|
|
},
|
|
py::arg("dim") = py::none())
|
|
.def("_rename", &name_analysis::RenameValue)
|
|
.def("_has_only_one_name",
|
|
[](Value self) -> bool {
|
|
return name_analysis::HasOnlyOneValueName(self);
|
|
})
|
|
.def("detach",
|
|
[](Value self) {
|
|
auto share_data_op =
|
|
ApiBuilder::Instance()
|
|
.GetBuilder()
|
|
->Build<paddle::dialect::ShareData_Op>(self);
|
|
auto out = share_data_op.out();
|
|
out.set_attribute(kAttrStopGradients,
|
|
BoolAttribute::get(IrContext::Instance(), true));
|
|
return out;
|
|
})
|
|
.def("__repr__", &Value2String)
|
|
.def("is_combine",
|
|
[](Value self) { return self.type().isa<pir::VectorType>(); })
|
|
.def("is_dist",
|
|
[](Value self) { return self.type().isa<DistTypeInterface>(); })
|
|
// The function will calculate the new local shape based on the global
|
|
// shape and the dist_attr argument.
|
|
.def("update_dist_attr",
|
|
[](Value &self, Attribute dist_attr) {
|
|
self.set_type(dialect::CvtToPirDistType(self.type(), dist_attr));
|
|
})
|
|
.def("is_coalesced",
|
|
[](Value self) {
|
|
auto sparse_coo_tensor_type =
|
|
self.type().dyn_cast<SparseCooTensorType>();
|
|
if (sparse_coo_tensor_type) {
|
|
return sparse_coo_tensor_type.coalesced();
|
|
} else {
|
|
PADDLE_THROW(common::errors::InvalidType(
|
|
"Method is_coalesced only support sparse coo tensor."));
|
|
}
|
|
})
|
|
.def_property_readonly(
|
|
"process_mesh",
|
|
[](Value &self) -> py::object {
|
|
auto type = self.type();
|
|
if (auto dist_type = type.dyn_cast<DistTypeInterface>()) {
|
|
return py::cast(dist_type.tensor_dist_attr()
|
|
.process_mesh_attr()
|
|
.process_mesh());
|
|
} else {
|
|
return py::cast<py::none>(Py_None);
|
|
}
|
|
})
|
|
.def("_clone",
|
|
[](Value self) {
|
|
// Return a new value owned by python side
|
|
return self;
|
|
})
|
|
.def("sparse_dim",
|
|
[](Value self) -> int32_t {
|
|
auto op_result = self.dyn_cast<OpResult>();
|
|
Operation *operation = op_result.owner();
|
|
if (self.type().isa<SparseCooTensorType>() &&
|
|
operation->name() == "pd_op.sparse_coo_tensor_sp") {
|
|
std::vector<Value> sources = operation->operands_source();
|
|
Value non_zero_indices = sources[1];
|
|
return phi::vectorize(GetValueDims(non_zero_indices))[0];
|
|
} else if (self.type().isa<SparseCsrTensorType>()) {
|
|
PADDLE_THROW(common::errors::InvalidType(
|
|
"SparseCsrTensor is unsupported in pir mode."));
|
|
} else {
|
|
return 0;
|
|
}
|
|
})
|
|
.def("dense_dim", [](Value self) -> int32_t {
|
|
auto op_result = self.dyn_cast<OpResult>();
|
|
Operation *operation = op_result.owner();
|
|
if (self.type().isa<SparseCooTensorType>() &&
|
|
operation->name() == "pd_op.sparse_coo_tensor_sp") {
|
|
std::vector<Value> sources = operation->operands_source();
|
|
Value non_zero_indices = sources[1];
|
|
int32_t dims = phi::vectorize(GetValueDims(self)).size();
|
|
return dims - phi::vectorize(GetValueDims(non_zero_indices))[0];
|
|
} else if (self.type().isa<SparseCsrTensorType>()) {
|
|
PADDLE_THROW(common::errors::InvalidType(
|
|
"SparseCsrTensor is unsupported in pir mode."));
|
|
} else {
|
|
return phi::vectorize(GetValueDims(self)).size();
|
|
}
|
|
});
|
|
}
|
|
|
|
void BindOpOperand(py::module *m) {
|
|
py::class_<OpOperand> op_operand(*m,
|
|
"OpOperand",
|
|
R"DOC(
|
|
OpOperand class represents the op_operand (input) of operation.
|
|
|
|
Notes:
|
|
The constructor of OpOperand should not be invoked directly. OpOperand can be automatically constructed
|
|
when build network.
|
|
|
|
)DOC");
|
|
op_operand.def("source", [](OpOperand &self) { return self.source(); })
|
|
.def("set_source",
|
|
[](OpOperand &self, Value *value) {
|
|
value ? self.set_source(*value) : self.set_source(nullptr);
|
|
})
|
|
.def("owner", &OpOperand::owner, return_value_policy::reference)
|
|
.def("index", &OpOperand::index);
|
|
}
|
|
|
|
bool GetValueBoolAttr(Value value, const std::string &attr_name) {
|
|
auto bool_attr = value.attribute<BoolAttribute>(attr_name);
|
|
return !bool_attr || bool_attr.data();
|
|
}
|
|
|
|
std::string GetAttrsMapJson(Operation *op) {
|
|
if (!op) {
|
|
PADDLE_THROW(common::errors::InvalidArgument(
|
|
"Operation pointer cannot be nullptr."));
|
|
}
|
|
auto attributes = op->attributes();
|
|
pir::ProgramWriter writer(1, false);
|
|
auto attrs_map_info = writer.GetAttributesMapJson(op->attributes()).dump();
|
|
return attrs_map_info;
|
|
}
|
|
|
|
pir::AttributeMap ConvertAttrsToAttributeMap(py::dict attrs) {
|
|
IrContext *ctx = IrContext::Instance();
|
|
pir::AttributeMap attrs_map;
|
|
|
|
for (auto item : attrs) {
|
|
std::string key = py::cast<std::string>(item.first);
|
|
py::handle value = item.second;
|
|
|
|
if (py::isinstance<py::bool_>(value)) {
|
|
attrs_map[key] = pir::BoolAttribute::get(ctx, py::cast<bool>(value));
|
|
} else if (py::isinstance<py::float_>(value)) {
|
|
attrs_map[key] = pir::FloatAttribute::get(ctx, py::cast<float>(value));
|
|
} else if (py::isinstance<py::str>(value)) {
|
|
attrs_map[key] =
|
|
pir::StrAttribute::get(ctx, py::cast<std::string>(value));
|
|
} else if (py::isinstance<py::list>(value)) {
|
|
py::list list_value = py::cast<py::list>(value);
|
|
std::vector<pir::Attribute> attr_list;
|
|
if (list_value.size() > 0) {
|
|
auto first_elem = list_value[0];
|
|
if (py::isinstance<py::bool_>(first_elem)) {
|
|
for (auto elem : list_value) {
|
|
attr_list.push_back(
|
|
pir::BoolAttribute::get(ctx, py::cast<bool>(elem)));
|
|
}
|
|
} else if (py::isinstance<py::str>(first_elem)) {
|
|
for (auto elem : list_value) {
|
|
attr_list.push_back(
|
|
pir::StrAttribute::get(ctx, py::cast<std::string>(elem)));
|
|
}
|
|
} else if (py::isinstance<py::int_>(first_elem)) {
|
|
for (auto elem : list_value) {
|
|
int64_t val = py::cast<int64_t>(elem);
|
|
attr_list.push_back(pir::Int64Attribute::get(ctx, val));
|
|
}
|
|
} else {
|
|
PADDLE_THROW(common::errors::InvalidArgument(
|
|
"Unsupported list element type, key: %s", key));
|
|
}
|
|
}
|
|
attrs_map[key] = pir::ArrayAttribute::get(ctx, attr_list);
|
|
} else {
|
|
PADDLE_THROW(common::errors::InvalidArgument(
|
|
"Unsupported attribute type, key: %s", key));
|
|
}
|
|
}
|
|
return attrs_map;
|
|
}
|
|
|
|
std::string GetAttrsMapJson(py::dict attrs) {
|
|
pir::AttributeMap attrs_map = ConvertAttrsToAttributeMap(attrs);
|
|
pir::ProgramWriter writer(1, false);
|
|
return writer.GetAttributesMapJson(attrs_map).dump();
|
|
}
|
|
|
|
std::string GetTypeJson(Operation *op, bool is_input) {
|
|
if (!op) {
|
|
PADDLE_THROW(
|
|
common::errors::InvalidArgument("Operation pointer cannot be nullptr"));
|
|
}
|
|
pir::ProgramWriter writer(1, false);
|
|
std::stringstream type_info_ss;
|
|
if (is_input) {
|
|
for (auto operand : op->operands_source()) {
|
|
type_info_ss << (writer.GetTypeJson(operand.type()).dump())
|
|
<< '\n'; // use '\n' as separator
|
|
}
|
|
} else {
|
|
for (auto result : op->results()) {
|
|
type_info_ss << (writer.GetTypeJson(result.type()).dump())
|
|
<< '\n'; // use '\n' as separator
|
|
}
|
|
}
|
|
return type_info_ss.str();
|
|
}
|
|
|
|
std::string GetInputsTypeJson(Operation *op) { return GetTypeJson(op, true); }
|
|
|
|
std::string GetOutputsTypeJson(Operation *op) { return GetTypeJson(op, false); }
|
|
|
|
void BindType(py::module *m) {
|
|
py::class_<Type> ir_type(*m, "Type");
|
|
ir_type.def("__eq__", &Type::operator==)
|
|
.def_property(
|
|
"shape",
|
|
[](Type self) { return phi::vectorize(GetTensorDims(self)); },
|
|
[](Type self, const std::vector<int> &shape) {
|
|
PADDLE_THROW(common::errors::InvalidArgument(
|
|
"can't set shape when building static graph"));
|
|
})
|
|
.def_property(
|
|
"dtype",
|
|
[](Type self) { return GetTensorDtype(self); },
|
|
[](Type self, DataType dtype) {
|
|
PADDLE_THROW(common::errors::InvalidArgument(
|
|
"can't set dtype when building static graph"));
|
|
})
|
|
.def_property(
|
|
"_local_shape",
|
|
[](Type self) {
|
|
if (!self.isa<DistDenseTensorType>()) {
|
|
PADDLE_THROW(common::errors::InvalidArgument(
|
|
"_local_shape is only for distdense tensor."));
|
|
}
|
|
return phi::vectorize(
|
|
self.dyn_cast<DistDenseTensorType>().local_ddim());
|
|
},
|
|
[](Type self, const std::vector<int> &shape) {
|
|
PADDLE_THROW(common::errors::InvalidArgument(
|
|
"can't set _local_shape when building static graph"));
|
|
})
|
|
.def("as_vec_type",
|
|
[](Type self) -> py::object {
|
|
if (auto vec_type = self.dyn_cast<VectorType>()) {
|
|
return py::cast(vec_type);
|
|
}
|
|
return py::cast<py::none>(Py_None);
|
|
})
|
|
.def("as_dist_type",
|
|
[](Type &self) -> py::object {
|
|
if (auto dist_type = self.dyn_cast<DistTypeInterface>()) {
|
|
return py::cast(dist_type);
|
|
}
|
|
return py::cast<py::none>(Py_None);
|
|
})
|
|
.def("__str__", [](Type &self) {
|
|
std::ostringstream print_stream;
|
|
print_stream << self;
|
|
return print_stream.str();
|
|
});
|
|
|
|
m->def("create_shaped_type",
|
|
[](Type &type, const std::vector<int64_t> &shape) -> Type {
|
|
if (type.isa<DenseTensorType>()) {
|
|
DenseTensorType src_type = type.dyn_cast<DenseTensorType>();
|
|
DenseTensorType dst_type =
|
|
DenseTensorType::get(IrContext::Instance(),
|
|
src_type.dtype(),
|
|
phi::make_ddim(shape),
|
|
src_type.data_layout(),
|
|
src_type.lod(),
|
|
src_type.offset());
|
|
return dst_type;
|
|
} else if (type.isa<SelectedRowsType>()) {
|
|
SelectedRowsType src_type = type.dyn_cast<SelectedRowsType>();
|
|
SelectedRowsType dst_type =
|
|
SelectedRowsType::get(IrContext::Instance(),
|
|
src_type.dtype(),
|
|
phi::make_ddim(shape),
|
|
src_type.data_layout(),
|
|
src_type.lod(),
|
|
src_type.offset());
|
|
return dst_type;
|
|
} else {
|
|
PADDLE_THROW(common::errors::InvalidArgument(
|
|
"Currently, we can only set shape for dense tensor"));
|
|
}
|
|
});
|
|
}
|
|
void BindVectorType(py::module *m) {
|
|
py::class_<VectorType, Type> vec_type(*m, "VectorType");
|
|
vec_type.def("as_list", &VectorType::data);
|
|
m->def("create_vec_type", [](std::vector<Type> &types) {
|
|
return VectorType::get(IrContext::Instance(), types);
|
|
});
|
|
}
|
|
void BindAttribute(py::module *m) {
|
|
py::class_<Attribute> ir_attr(*m, "Attribute", py::module_local());
|
|
ir_attr.def(py::init<>())
|
|
.def("__bool__", [](Attribute &self) { return static_cast<bool>(self); })
|
|
.def("__eq__", &Attribute::operator==)
|
|
.def("__str__",
|
|
[](Attribute &self) {
|
|
std::ostringstream print_stream;
|
|
print_stream << self;
|
|
return print_stream.str();
|
|
})
|
|
.def("as_tensor_dist_attr",
|
|
[](Attribute &self) -> py::object {
|
|
if (auto dist_attr = self.dyn_cast<TensorDistAttribute>()) {
|
|
return py::cast(dist_attr);
|
|
}
|
|
return py::cast<py::none>(Py_None);
|
|
})
|
|
.def("as_array_attr", [](Attribute &self) -> py::object {
|
|
if (auto array_attr = self.dyn_cast<ArrayAttribute>()) {
|
|
return py::cast(array_attr);
|
|
}
|
|
return py::cast<py::none>(Py_None);
|
|
});
|
|
py::class_<ArrayAttribute, Attribute> array_attr(*m, "ArrayAttribute");
|
|
array_attr.def("__len__", [](ArrayAttribute &self) { return self.size(); })
|
|
.def("__getitem__",
|
|
[](ArrayAttribute &self, int idx) { return self.at(idx); });
|
|
}
|
|
|
|
struct PyInsertionPoint {
|
|
pir::InsertionPoint value;
|
|
};
|
|
void BindInsertionPoint(pybind11::module *m) {
|
|
py::class_<PyInsertionPoint> ir_insertion_point(*m, "InsertionPoint", R"DOC(
|
|
InsertionPoint class represents the insertion point in the Builder.)DOC");
|
|
ir_insertion_point
|
|
.def(
|
|
"next",
|
|
[](PyInsertionPoint &self) -> Operation & {
|
|
if (self.value.second == self.value.first->end()) {
|
|
PADDLE_THROW(common::errors::InvalidArgument(
|
|
"The insertion point is already at the end and can't call "
|
|
"next()."));
|
|
}
|
|
return *(self.value.second++);
|
|
},
|
|
return_value_policy::reference)
|
|
.def(
|
|
"prev",
|
|
[](PyInsertionPoint &self) -> Operation & {
|
|
if (self.value.second == self.value.first->begin()) {
|
|
PADDLE_THROW(common::errors::InvalidArgument(
|
|
"The insertion point is already at the begin and can't call "
|
|
"prev()."));
|
|
}
|
|
return *(--self.value.second);
|
|
},
|
|
return_value_policy::reference)
|
|
.def(
|
|
"get_operation",
|
|
[](PyInsertionPoint &self) -> Operation & {
|
|
if (self.value.second == self.value.first->begin()) {
|
|
PADDLE_THROW(common::errors::InvalidArgument(
|
|
"The insertion point is already at the begin."));
|
|
} else if (self.value.second == self.value.first->end()) {
|
|
PADDLE_THROW(common::errors::InvalidArgument(
|
|
"The insertion point is already at the end."));
|
|
}
|
|
return *(self.value.second);
|
|
},
|
|
return_value_policy::reference)
|
|
.def(
|
|
"block",
|
|
[](PyInsertionPoint &self) { return self.value.first; },
|
|
return_value_policy::reference);
|
|
}
|
|
|
|
template <typename F, typename S>
|
|
void range_block_do(const Block *block,
|
|
std::pair<size_t, size_t> range,
|
|
F fn,
|
|
S skip_fn) {
|
|
auto [start, end] = range;
|
|
if (start >= end) {
|
|
return;
|
|
}
|
|
auto it = block->begin();
|
|
std::advance(it, start);
|
|
for (size_t i = start; i < end && it != block->end(); ++i, ++it) {
|
|
if (skip_fn(it)) {
|
|
continue;
|
|
}
|
|
fn(it);
|
|
}
|
|
}
|
|
|
|
template <typename F>
|
|
void range_block_do(const Block *block, std::pair<size_t, size_t> range, F fn) {
|
|
range_block_do(block, range, fn, [](Operation *op) { return false; });
|
|
}
|
|
|
|
std::map<int, int> GetOpInplaceInfo(const Operation *op) {
|
|
std::map<int, int> inplace_info;
|
|
if (!op->HasTrait<paddle::dialect::InplaceTrait>()) {
|
|
return inplace_info;
|
|
}
|
|
IrContext *ctx = IrContext::Instance();
|
|
std::string op_name = op->name();
|
|
if (op->attributes().count("op_name")) {
|
|
op_name =
|
|
op->attributes().at("op_name").dyn_cast<StrAttribute>().AsString();
|
|
}
|
|
|
|
pir::OpInfo op_info = ctx->GetRegisteredOpInfo(op_name);
|
|
paddle::dialect::OpYamlInfoParser yaml_parser(
|
|
op_info.GetInterfaceImpl<paddle::dialect::OpYamlInfoInterface>()
|
|
->get_op_info_(op_name),
|
|
paddle::dialect::IsLegacyOp(op_name));
|
|
|
|
for (size_t i = 0; i < op->num_results(); ++i) {
|
|
std::string value_name = yaml_parser.OutputNames()[i];
|
|
if (yaml_parser.HasInplace(value_name)) {
|
|
const std::string &inplace_name = yaml_parser.InplaceName(value_name);
|
|
inplace_info[i] = yaml_parser.InputName2Id().at(inplace_name);
|
|
}
|
|
if (yaml_parser.HasView(value_name)) {
|
|
const std::string &view_name = yaml_parser.ViewName(value_name);
|
|
inplace_info[i] = yaml_parser.InputName2Id().at(view_name);
|
|
}
|
|
}
|
|
|
|
return inplace_info;
|
|
}
|
|
|
|
std::pair<std::vector<Value>, std::unordered_set<Value>> AnalysisMiddleVariable(
|
|
const Program &program,
|
|
const std::vector<Value> &forward_inputs,
|
|
const std::vector<Value> &backward_outputs,
|
|
const std::pair<size_t, size_t> &forward_range,
|
|
const std::pair<size_t, size_t> &backward_range) {
|
|
std::vector<Value> middle_values;
|
|
|
|
std::unordered_set<Value> backward_used_values;
|
|
std::unordered_set<Value> x_or_param(forward_inputs.begin(),
|
|
forward_inputs.end());
|
|
for (const auto &value : backward_outputs) {
|
|
backward_used_values.insert(value);
|
|
}
|
|
range_block_do(
|
|
program.block(), backward_range, [&backward_used_values](Operation *op) {
|
|
pir::Walk(op, [&](Operation *inner_op) {
|
|
for (auto &t : inner_op->operands()) {
|
|
backward_used_values.insert(t.source());
|
|
}
|
|
});
|
|
});
|
|
|
|
range_block_do(
|
|
program.block(),
|
|
forward_range,
|
|
[&middle_values, &backward_used_values, &x_or_param](Operation *op) {
|
|
pir::Walk(op, [&](Operation *inner_op) {
|
|
for (auto &t : inner_op->results()) {
|
|
auto v = Value(t.Value::impl());
|
|
if (backward_used_values.count(v) && !x_or_param.count(v)) {
|
|
middle_values.push_back(v);
|
|
}
|
|
}
|
|
});
|
|
});
|
|
return std::make_pair(middle_values, backward_used_values);
|
|
}
|
|
|
|
void mapping_value(const std::vector<Value> &origin,
|
|
const std::unordered_map<Value, Value> &value_map,
|
|
std::vector<Value> &out) { // NOLINT
|
|
std::transform(origin.begin(),
|
|
origin.end(),
|
|
std::back_inserter(out),
|
|
[&value_map](const Value &v) {
|
|
if (v.impl() == nullptr) return Value(nullptr);
|
|
if (!value_map.count(v)) {
|
|
VLOG(2) << "mapping value found v is not exist. may not "
|
|
"used by backward program.";
|
|
return Value(nullptr);
|
|
}
|
|
return value_map.at(v);
|
|
});
|
|
}
|
|
|
|
using SplitedProgram = std::vector<std::shared_ptr<Program>>;
|
|
using SplitedAttribute = std::map<std::string, std::vector<Value>>;
|
|
using SplitedResult = std::pair<SplitedProgram, SplitedAttribute>;
|
|
|
|
static auto GetNoNeedBufferValue(const pir::Block *whole_block,
|
|
std::pair<size_t, size_t> range) {
|
|
// filter no need buffer values.
|
|
std::unordered_set<Value> need_buffer_values;
|
|
std::unordered_set<Value> no_need_buffer_values;
|
|
range_block_do(whole_block, range, [&need_buffer_values](Operation *op) {
|
|
// NOTE(SigureMo): We should process the CombineOp in it's users.
|
|
if (op->isa<pir::CombineOp>()) {
|
|
return;
|
|
}
|
|
if (op->HasInterface<paddle::dialect::OpYamlInfoInterface>() == false) {
|
|
// not a OpYamlInfoInterface, can't have no_need_buffer.
|
|
for (const auto &operand : op->operands_source()) {
|
|
need_buffer_values.insert(operand);
|
|
}
|
|
} else {
|
|
auto opinfo =
|
|
op->dyn_cast<paddle::dialect::OpYamlInfoInterface>().GetOpInfo();
|
|
int counter = 0;
|
|
for (const auto &op_input_info : std::get<0>(opinfo)) {
|
|
auto value = op->operand_source(counter);
|
|
if (!op_input_info.no_need_buffer) {
|
|
need_buffer_values.insert(value);
|
|
if (!IsFakeValue(value) && value.defining_op() &&
|
|
value.defining_op()->isa<pir::CombineOp>()) {
|
|
for (const auto &combine_value :
|
|
value.defining_op()->operands_source()) {
|
|
need_buffer_values.insert(combine_value);
|
|
}
|
|
}
|
|
}
|
|
counter += 1;
|
|
}
|
|
}
|
|
});
|
|
range_block_do(
|
|
whole_block,
|
|
range,
|
|
[&need_buffer_values, &no_need_buffer_values](const Operation *op) {
|
|
for (const auto &operand : op->operands_source()) {
|
|
if (need_buffer_values.count(operand) == 0) {
|
|
no_need_buffer_values.insert(operand);
|
|
}
|
|
}
|
|
});
|
|
return std::vector<Value>(no_need_buffer_values.begin(),
|
|
no_need_buffer_values.end());
|
|
}
|
|
|
|
using ValueMap = std::pair<std::vector<Value>, std::vector<Value>>;
|
|
std::pair<std::shared_ptr<Program>, ValueMap> CloneProgram(
|
|
const Program &program) {
|
|
// Limitation of this function:
|
|
// 1. don't support Parameters.
|
|
pir::IrMapping mapper;
|
|
auto cloned_program = program.Clone(mapper);
|
|
std::vector<Value> associated_array_key, associated_array_value;
|
|
for (auto &pair : mapper.GetMap<Value>()) {
|
|
associated_array_key.push_back(pair.first);
|
|
associated_array_value.push_back(pair.second);
|
|
}
|
|
return std::make_pair(
|
|
cloned_program,
|
|
std::make_pair(associated_array_key, associated_array_value));
|
|
}
|
|
|
|
void AppendPrintOp(Program *program,
|
|
const Value &value,
|
|
int first_n,
|
|
std::string message,
|
|
int summarize,
|
|
bool print_tensor_name,
|
|
bool print_tensor_type,
|
|
bool print_tensor_shape,
|
|
bool print_tensor_layout,
|
|
bool print_tensor_lod,
|
|
std::string print_phase,
|
|
bool is_forward,
|
|
int start_point) {
|
|
std::unordered_set<std::string> print_phase_set{
|
|
"FORWARD", "BACKWARD", "BOTH"};
|
|
if (!print_phase_set.count(print_phase)) {
|
|
PADDLE_THROW(common::errors::InvalidArgument(
|
|
"The attribute 'print_phase' must be one of 'FORWARD', 'BACKWARD', "
|
|
"'BOTH' but got '%s'.",
|
|
print_phase));
|
|
}
|
|
IrContext *ctx = IrContext::Instance();
|
|
auto op_info = ctx->GetRegisteredOpInfo(paddle::dialect::PrintOp::name());
|
|
pir::AttributeMap attribute_map = {
|
|
{"first_n", Int32Attribute::get(ctx, first_n)},
|
|
{"message", StrAttribute::get(ctx, message)},
|
|
{"summarize", Int32Attribute::get(ctx, summarize)},
|
|
{"print_tensor_name", BoolAttribute::get(ctx, print_tensor_name)},
|
|
{"print_tensor_type", BoolAttribute::get(ctx, print_tensor_type)},
|
|
{"print_tensor_shape", BoolAttribute::get(ctx, print_tensor_shape)},
|
|
{"print_tensor_layout", BoolAttribute::get(ctx, print_tensor_layout)},
|
|
{"print_tensor_lod", BoolAttribute::get(ctx, print_tensor_lod)},
|
|
{"print_phase", StrAttribute::get(ctx, print_phase)},
|
|
{"is_forward", BoolAttribute::get(ctx, is_forward)},
|
|
};
|
|
std::vector<pir::Type> output_types{value.type()};
|
|
Operation *operation =
|
|
Operation::Create({value}, attribute_map, output_types, op_info);
|
|
|
|
auto block = value.defining_op()->GetParent();
|
|
auto position = block->begin();
|
|
std::advance(position, start_point);
|
|
if (position == block->end()) {
|
|
block->push_back(operation);
|
|
} else {
|
|
block->insert(position, operation);
|
|
}
|
|
}
|
|
|
|
void AppendPrintOps(Program *program,
|
|
const std::vector<Value> &values,
|
|
int first_n,
|
|
std::string message,
|
|
int summarize,
|
|
bool print_tensor_name,
|
|
bool print_tensor_type,
|
|
bool print_tensor_shape,
|
|
bool print_tensor_layout,
|
|
bool print_tensor_lod,
|
|
std::string print_phase,
|
|
bool is_forward,
|
|
int start_point) {
|
|
int counter = 0;
|
|
std::unordered_set<Value> added_values;
|
|
for (const auto &value : values) {
|
|
if (!added_values.count(value)) {
|
|
AppendPrintOp(program,
|
|
value,
|
|
first_n,
|
|
message,
|
|
summarize,
|
|
print_tensor_name,
|
|
print_tensor_type,
|
|
print_tensor_shape,
|
|
print_tensor_layout,
|
|
print_tensor_lod,
|
|
print_phase,
|
|
is_forward,
|
|
start_point + counter);
|
|
++counter;
|
|
added_values.insert(value);
|
|
}
|
|
}
|
|
}
|
|
|
|
void AppendShadowOutput(Program *program,
|
|
const Value &value,
|
|
const std::string &name,
|
|
size_t start_point) {
|
|
IrContext *ctx = IrContext::Instance();
|
|
auto op_info = ctx->GetRegisteredOpInfo(pir::ShadowOutputOp::name());
|
|
pir::AttributeMap attribute_map = {
|
|
{"output_name", StrAttribute::get(ctx, name)},
|
|
};
|
|
Operation *operation = Operation::Create({value}, attribute_map, {}, op_info);
|
|
auto position = program->block()->begin();
|
|
std::advance(position, start_point);
|
|
if (position == program->block()->end()) {
|
|
program->block()->push_back(operation);
|
|
} else {
|
|
program->block()->insert(position, operation);
|
|
}
|
|
}
|
|
|
|
int AppendShadowOutputs(Program *program,
|
|
const std::vector<Value> &outputs,
|
|
int start_point,
|
|
std::string name_prefix) {
|
|
int counter = 0;
|
|
std::unordered_set<Value> added_value;
|
|
for (const auto &value : outputs) {
|
|
if (!added_value.count(value) || IsFakeValue(value)) {
|
|
std::string shadow_output_name =
|
|
name_analysis::TryGetValueFirstName(value).value_or(
|
|
name_prefix + std::to_string(counter));
|
|
AppendShadowOutput(
|
|
program, value, shadow_output_name, start_point + counter);
|
|
counter += 1;
|
|
added_value.insert(value);
|
|
}
|
|
}
|
|
// return the inserted op.
|
|
return counter;
|
|
}
|
|
|
|
SplitedResult SplitForwardBackward(
|
|
const Program &program,
|
|
const std::vector<Value> &forward_inputs,
|
|
const std::vector<Value> &forward_params,
|
|
const std::vector<Value> &forward_outputs,
|
|
const std::vector<Value> &forward_inputs_grads,
|
|
const std::vector<Value> &forward_params_grads,
|
|
const std::vector<Value> &forward_outputs_grads,
|
|
const std::pair<size_t, size_t> &forward_range,
|
|
const std::pair<size_t, size_t> &backward_range) {
|
|
std::vector<Value> forward_in_out_values;
|
|
for (auto &v :
|
|
std::vector({&forward_inputs, &forward_outputs, &forward_params})) {
|
|
forward_in_out_values.insert(
|
|
forward_in_out_values.end(), v->begin(), v->end());
|
|
}
|
|
std::vector<Value> backward_out_values;
|
|
for (auto &v : std::vector({&forward_inputs_grads, &forward_params_grads})) {
|
|
backward_out_values.insert(backward_out_values.end(), v->begin(), v->end());
|
|
}
|
|
|
|
std::vector<Value> fx, fp, fm, fo, bx, bp, bm, bo_g, bx_g, bp_g, bo;
|
|
std::vector<Value> no_need_buffer_values;
|
|
IrContext *ctx = IrContext::Instance();
|
|
auto forward_program = std::make_shared<Program>(ctx);
|
|
auto backward_program = std::make_shared<Program>(ctx);
|
|
std::vector<Value> middle_values;
|
|
std::unordered_set<Value> backward_used_values;
|
|
std::tie(middle_values, backward_used_values) =
|
|
AnalysisMiddleVariable(program,
|
|
forward_in_out_values,
|
|
backward_out_values,
|
|
forward_range,
|
|
backward_range);
|
|
|
|
pir::Block &backward_block = *backward_program->block();
|
|
bool has_backward = forward_inputs_grads.size() > 0 ||
|
|
forward_params_grads.size() > 0 ||
|
|
forward_outputs_grads.size() > 0;
|
|
|
|
// forward program construct.
|
|
VLOG(4) << "start create forward program.";
|
|
pir::IrMapping forward_mapper;
|
|
auto clone_options = pir::CloneOptions::All();
|
|
range_block_do(
|
|
program.block(),
|
|
forward_range,
|
|
[&forward_mapper, &forward_program, &clone_options](Operation *op) {
|
|
auto *cloned_op = op->Clone(forward_mapper, clone_options);
|
|
forward_program->block()->push_back(cloned_op);
|
|
},
|
|
// Skip the ShadowOutputOp.
|
|
/*skip_fn=*/[](Operation *op) { return op->isa<pir::ShadowOutputOp>(); });
|
|
auto &forward_value_map = forward_mapper.GetMutableMap<Value>();
|
|
|
|
// backward program construct.
|
|
// Step1. insert data op for inputs_values and middle_values
|
|
pir::IrMapping backward_mapper;
|
|
auto &backward_value_map = backward_mapper.GetMutableMap<Value>();
|
|
|
|
auto create_output_fn = [&ctx](
|
|
const std::unordered_map<Value, Value> &value_map,
|
|
const std::shared_ptr<Program> &program,
|
|
const std::string &prefix) {
|
|
auto counter = std::make_shared<size_t>(0);
|
|
return [&ctx, &value_map, &program, &prefix, counter](const Value &v) {
|
|
// NOTE(SigureMo): Ensure counter++ executed in each iteration.
|
|
auto default_name = prefix + std::to_string((*counter)++);
|
|
if (v.impl() == nullptr) {
|
|
return;
|
|
}
|
|
const Value &new_value = value_map.at(v);
|
|
std::string shadow_output_name =
|
|
name_analysis::TryGetValueFirstName(new_value).value_or(default_name);
|
|
auto op_info = ctx->GetRegisteredOpInfo(pir::ShadowOutputOp::name());
|
|
pir::AttributeMap attribute_map = {
|
|
{"output_name", StrAttribute::get(ctx, shadow_output_name)},
|
|
};
|
|
Operation *operation =
|
|
Operation::Create({new_value}, attribute_map, {}, op_info);
|
|
program->block()->push_back(operation);
|
|
};
|
|
};
|
|
|
|
VLOG(4) << "start create forward outputs, inserting shadow_output ops.";
|
|
std::for_each(
|
|
middle_values.begin(),
|
|
middle_values.end(),
|
|
create_output_fn(forward_value_map, forward_program, "middle_"));
|
|
std::for_each(
|
|
forward_outputs.begin(),
|
|
forward_outputs.end(),
|
|
create_output_fn(forward_value_map, forward_program, "output_"));
|
|
|
|
auto create_kwarg_fn = [&backward_block,
|
|
&backward_used_values,
|
|
&backward_value_map,
|
|
&forward_value_map](const std::string &prefix) {
|
|
auto counter = std::make_shared<size_t>(0);
|
|
return [&backward_block,
|
|
&backward_used_values,
|
|
&backward_value_map,
|
|
&forward_value_map,
|
|
&prefix,
|
|
counter](const Value &v) {
|
|
// NOTE(SigureMo): Ensure counter++ executed in each iteration.
|
|
auto default_name = prefix + std::to_string((*counter)++);
|
|
if (v && !backward_value_map.count(v) &&
|
|
(backward_used_values.count(v))) {
|
|
backward_value_map[v] = backward_block.AddKwarg(
|
|
name_analysis::TryGetValueFirstName(forward_value_map[v])
|
|
.value_or(default_name),
|
|
v.type());
|
|
}
|
|
};
|
|
};
|
|
|
|
if (has_backward) {
|
|
VLOG(4) << "start create backward inputs, creating keyword argument.";
|
|
VLOG(4) << "Create keyword argument for backward program: fo";
|
|
std::for_each(forward_outputs.begin(),
|
|
forward_outputs.end(),
|
|
create_kwarg_fn("output_"));
|
|
VLOG(4) << "Create keyword argument for backward program: fx";
|
|
std::for_each(forward_inputs.begin(),
|
|
forward_inputs.end(),
|
|
create_kwarg_fn("input_"));
|
|
VLOG(4) << "Create keyword argument for backward program: fp";
|
|
std::for_each(forward_params.begin(),
|
|
forward_params.end(),
|
|
create_kwarg_fn("param_"));
|
|
VLOG(4) << "Create keyword argument for backward program: fm";
|
|
std::for_each(
|
|
middle_values.begin(), middle_values.end(), create_kwarg_fn("middle_"));
|
|
VLOG(4) << "Create keyword argument for backward program: fo_g";
|
|
std::for_each(forward_outputs_grads.begin(),
|
|
forward_outputs_grads.end(),
|
|
create_kwarg_fn("output_grad_"));
|
|
VLOG(4) << "Create keyword argument for backward program end.";
|
|
}
|
|
|
|
// Step2. copy backward ops .
|
|
VLOG(4) << "start copy backward ops";
|
|
range_block_do(
|
|
program.block(),
|
|
backward_range,
|
|
[&backward_mapper, &backward_program, &clone_options](Operation *op) {
|
|
auto *cloned_op = op->Clone(backward_mapper, clone_options);
|
|
backward_program->block()->push_back(cloned_op);
|
|
},
|
|
// Skip the ShadowOutputOp.
|
|
/*skip_fn=*/[](Operation *op) { return op->isa<pir::ShadowOutputOp>(); });
|
|
VLOG(4) << "start create backward outputs, inserting shadow_output ops.";
|
|
if (has_backward) {
|
|
std::for_each(
|
|
forward_inputs_grads.begin(),
|
|
forward_inputs_grads.end(),
|
|
create_output_fn(backward_value_map, backward_program, "input_grad_"));
|
|
std::for_each(
|
|
forward_params_grads.begin(),
|
|
forward_params_grads.end(),
|
|
create_output_fn(backward_value_map, backward_program, "param_grad_"));
|
|
}
|
|
|
|
VLOG(4) << "forward_value_map.size() is " << forward_value_map.size();
|
|
VLOG(4) << "backward_value_map.size() is " << backward_value_map.size();
|
|
if (FLAGS_print_ir) {
|
|
std::ostringstream print_stream;
|
|
print_stream << "ForwardProgram is :\n";
|
|
forward_program->Print(print_stream);
|
|
print_stream << "BackwardProgram is:\n";
|
|
backward_program->Print(print_stream);
|
|
std::cout << "Splited Program (fwd | bwd): \n"
|
|
<< print_stream.str() << std::endl;
|
|
}
|
|
|
|
// construct all attributes we needed.
|
|
|
|
mapping_value(middle_values, forward_value_map, fm); // write 'fm'
|
|
mapping_value(middle_values, backward_value_map, bm); // write 'bm'
|
|
mapping_value(forward_inputs, forward_value_map, fx); // write 'fx'
|
|
mapping_value(forward_inputs, backward_value_map, bx); // write 'bx'
|
|
mapping_value(forward_params, forward_value_map, fp); // write 'fp'
|
|
mapping_value(forward_params, backward_value_map, bp); // write 'bp'
|
|
mapping_value(forward_outputs, forward_value_map, fo); // write 'fo'
|
|
mapping_value(
|
|
forward_inputs_grads, backward_value_map, bx_g); // write 'bx_g'
|
|
mapping_value(
|
|
forward_params_grads, backward_value_map, bp_g); // write 'bp_g'
|
|
mapping_value(
|
|
forward_outputs_grads, backward_value_map, bo_g); // write 'bo_g'
|
|
mapping_value(forward_outputs, backward_value_map, bo); // write 'bo'
|
|
mapping_value(GetNoNeedBufferValue(program.block(), backward_range),
|
|
forward_value_map,
|
|
no_need_buffer_values); // write 'no_need_buffers'
|
|
|
|
std::map<std::string, std::vector<Value>> attr = {
|
|
{"fx", fx},
|
|
{"fp", fp},
|
|
{"fm", fm},
|
|
{"fo", fo},
|
|
{"bx", bx},
|
|
{"bp", bp},
|
|
{"bm", bm},
|
|
{"bo_g", bo_g},
|
|
{"bx_g", bx_g},
|
|
{"bp_g", bp_g},
|
|
{"no_need_buffers", no_need_buffer_values},
|
|
{"bo", bo}};
|
|
std::vector<std::shared_ptr<Program>> programs = {forward_program,
|
|
backward_program};
|
|
return std::make_pair(programs, attr);
|
|
}
|
|
|
|
pir::Type CreateSelectedRowsTypeByDenseTensor(pir::Type dense_tensor_type) {
|
|
if (dense_tensor_type.isa<DenseTensorType>()) {
|
|
DenseTensorType type = dense_tensor_type.dyn_cast<DenseTensorType>();
|
|
return SelectedRowsType::get(IrContext::Instance(),
|
|
type.dtype(),
|
|
type.dims(),
|
|
type.data_layout(),
|
|
type.lod(),
|
|
type.offset());
|
|
} else {
|
|
PADDLE_THROW(common::errors::InvalidArgument(
|
|
"Currently, input is not a dense tensor type."));
|
|
}
|
|
}
|
|
|
|
pir::Type CreateDistDenseTensorTypeByDenseTensor(
|
|
const pir::Type &gdense_tensor_type,
|
|
const std::vector<int> &lshape,
|
|
const phi::distributed::ProcessMesh &mesh,
|
|
const std::vector<int64_t> &dims_mapping) {
|
|
if (gdense_tensor_type.isa<DenseTensorType>()) {
|
|
DenseTensorType type = gdense_tensor_type.dyn_cast<DenseTensorType>();
|
|
paddle::flat_hash_map<int64_t, phi::ReduceType> partial_status;
|
|
paddle::dialect::TensorDistAttribute tensor_dist_attr =
|
|
paddle::dialect::TensorDistAttribute::get(
|
|
IrContext::Instance(), mesh, dims_mapping, partial_status);
|
|
return DistDenseTensorType::get(
|
|
IrContext::Instance(), type, tensor_dist_attr, phi::make_ddim(lshape));
|
|
} else {
|
|
PADDLE_THROW(common::errors::InvalidArgument(
|
|
"Currently, input is not a dense tensor type are not supported."));
|
|
}
|
|
}
|
|
|
|
static void inline CreateVariableIfNotExist(
|
|
const std::vector<Value> &var_list,
|
|
framework::Scope *scope,
|
|
const framework::Executor *exe = nullptr) {
|
|
size_t len = var_list.size();
|
|
|
|
for (size_t i = 0; i < len; ++i) {
|
|
Value value = var_list[i];
|
|
std::string para_name = name_analysis::GetValueFirstName(value);
|
|
auto var = scope->FindVar(para_name);
|
|
if (var == nullptr) {
|
|
PADDLE_ENFORCE_NOT_NULL(exe,
|
|
common::errors::InvalidArgument(
|
|
"Parameter not Initialized, "
|
|
"Please set argument [executor] not None "
|
|
"or run startup program first"));
|
|
var = scope->Var(para_name);
|
|
auto *tensor_temp = var->GetMutable<DenseTensor>();
|
|
tensor_temp->Resize(
|
|
common::make_ddim(phi::vectorize(GetValueDims(value))));
|
|
phi::DeviceContextPool &pool = phi::DeviceContextPool::Instance();
|
|
const phi::DeviceContext *dev_ctx = nullptr;
|
|
dev_ctx = pool.Get(exe->GetPlace());
|
|
dev_ctx->Alloc(tensor_temp, pir::GetValueDtype(value));
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
void BindUtils(pybind11::module *m) {
|
|
m->def("create_loaded_parameter", CreateVariableIfNotExist);
|
|
m->def("clone_program", CloneProgram);
|
|
m->def("get_op_inplace_info", GetOpInplaceInfo);
|
|
m->def("split_program", SplitForwardBackward);
|
|
m->def("append_shadow_outputs", AppendShadowOutputs);
|
|
m->def("append_shadow_output", AppendShadowOutput);
|
|
m->def("append_print", AppendPrintOp);
|
|
m->def("append_prints", AppendPrintOps);
|
|
m->def("fake_value", FakeValue);
|
|
m->def("get_fake_value_name",
|
|
[]() -> std::string { return paddle::framework::kFakeVarName; });
|
|
m->def("is_fake_value", IsFakeValue);
|
|
m->def("get_current_insertion_point", []() -> PyInsertionPoint {
|
|
return {ApiBuilder::Instance().GetCurrentInsertionPoint()};
|
|
});
|
|
m->def("set_insertion_point", [](const PyInsertionPoint &insertion_point) {
|
|
ApiBuilder::Instance().SetInsertionPoint(insertion_point.value);
|
|
});
|
|
m->def("set_insertion_point",
|
|
[](Operation *op) { ApiBuilder::Instance().SetInsertionPoint(op); });
|
|
m->def("set_insertion_point_after", [](Operation *op) {
|
|
ApiBuilder::Instance().SetInsertionPointAfter(op);
|
|
});
|
|
m->def("set_insertion_point_to_block_end", [](Block *block) {
|
|
ApiBuilder::Instance().SetInsertionPointToBlockEnd(block);
|
|
});
|
|
m->def("reset_insertion_point_to_start",
|
|
[]() { ApiBuilder::Instance().ResetInsertionPointToStart(); });
|
|
m->def("reset_insertion_point_to_end",
|
|
[]() { ApiBuilder::Instance().ResetInsertionPointToEnd(); });
|
|
m->def("set_chunk_id",
|
|
[](int chunk_id) { ApiBuilder::Instance().SetChunkId(chunk_id); });
|
|
m->def("get_chunk_id", []() { return ApiBuilder::Instance().GetChunkId(); });
|
|
m->def("set_op_role",
|
|
[](int op_role) { ApiBuilder::Instance().SetOpRole(op_role); });
|
|
m->def("get_op_role", []() { return ApiBuilder::Instance().GetOpRole(); });
|
|
m->def("set_comp_op_name", [](std::string comp_op_name) {
|
|
ApiBuilder::Instance().SetCompOpName(comp_op_name);
|
|
});
|
|
m->def("get_comp_op_name",
|
|
[]() { return ApiBuilder::Instance().GetCompOpName(); });
|
|
m->def("register_paddle_dialect", []() {
|
|
IrContext::Instance()
|
|
->GetOrRegisterDialect<paddle::dialect::OperatorDialect>();
|
|
});
|
|
m->def("register_dist_dialect", []() {
|
|
IrContext::Instance()->GetOrRegisterDialect<paddle::dialect::DistDialect>();
|
|
});
|
|
m->def("create_selected_rows_type_by_dense_tensor",
|
|
CreateSelectedRowsTypeByDenseTensor);
|
|
m->def("create_dist_dense_tensor_type_by_dense_tensor",
|
|
CreateDistDenseTensorTypeByDenseTensor);
|
|
m->def(
|
|
"translate_to_pir",
|
|
[](const ::paddle::framework::ProgramDesc &legacy_program) {
|
|
std::shared_ptr<Program> ret =
|
|
paddle::TranslateLegacyProgramToProgram(legacy_program);
|
|
return ret;
|
|
},
|
|
R"DOC(
|
|
Convert Fluid Program to New IR Program.
|
|
|
|
Args:
|
|
|
|
legacy_program (ProgramDesc): The Fluid Program that will be converted.
|
|
|
|
Returns:
|
|
Program: The New IR Program
|
|
|
|
Raises:
|
|
PreconditionNotMet: If legacy_program has multi block will raise error.
|
|
|
|
Examples:
|
|
.. code-block:: pycon
|
|
|
|
>>> import os
|
|
>>> # Paddle will remove this flag in the next version
|
|
>>> pir_flag = 'FLAGS_enable_pir_in_executor'
|
|
>>> os.environ[pir_flag] = 'True'
|
|
|
|
>>> import paddle
|
|
>>> from paddle import pir
|
|
>>> paddle.enable_static()
|
|
|
|
>>> x = paddle.randn([4, 4])
|
|
>>> main_program, start_program = (
|
|
... paddle.static.Program(),
|
|
... paddle.static.Program(),
|
|
... )
|
|
|
|
>>> with paddle.static.program_guard(main_program, start_program):
|
|
... x_s = paddle.static.data('x', [4, 4], x.dtype)
|
|
... x_s.stop_gradient = False
|
|
... y_s = paddle.matmul(x_s, x_s)
|
|
... z_s = paddle.add(y_s, y_s)
|
|
... k_s = paddle.tanh(z_s)
|
|
>>> pir_program = pir.translate_to_pir(main_program.desc)
|
|
|
|
>>> print(pir_program)
|
|
{
|
|
(%0) = "pd_op.data" () {dtype:(pd_op.DataType)float32,is_persistable:[false],name:"x",place:(pd_op.Place)Place(undefined:0),shape:(pd_op.IntArray)[4,4],stop_gradient:[false]} : () -> builtin.tensor<4x4xf32>
|
|
(%1) = "pd_op.matmul" (%0, %0) {is_persistable:[false],stop_gradient:[false],transpose_x:false,transpose_y:false} : (builtin.tensor<4x4xf32>, builtin.tensor<4x4xf32>) -> builtin.tensor<4x4xf32>
|
|
(%2) = "pd_op.add" (%1, %1) {is_persistable:[false],stop_gradient:[false]} : (builtin.tensor<4x4xf32>, builtin.tensor<4x4xf32>) -> builtin.tensor<4x4xf32>
|
|
(%3) = "pd_op.tanh" (%2) {is_persistable:[false],stop_gradient:[false]} : (builtin.tensor<4x4xf32>) -> builtin.tensor<4x4xf32>
|
|
}
|
|
|
|
|
|
)DOC");
|
|
m->def(
|
|
"check_unregistered_ops",
|
|
[](const framework::ProgramDesc &legacy_program) {
|
|
IrContext *ctx = IrContext::Instance();
|
|
return paddle::translator::CheckUnregisteredOperation(ctx,
|
|
legacy_program);
|
|
},
|
|
R"DOC(
|
|
Check unregistered operators in paddle dialect.
|
|
|
|
Args:
|
|
legacy_program (ProgramDesc): The Fluid Program that need checked.
|
|
Returns:
|
|
list[str] : List of unregistered operators in paddle dialect, the name is expressed by origin op name.
|
|
)DOC");
|
|
m->def(
|
|
"translate_to_pir_with_param_map",
|
|
[](const framework::ProgramDesc &legacy_program) {
|
|
auto ir_ctx = IrContext::Instance();
|
|
auto program = std::make_shared<pir::Program>(ir_ctx);
|
|
translator::ProgramTranslator program_translator(&legacy_program,
|
|
program.get());
|
|
program_translator.Translate();
|
|
return std::make_pair(program, program_translator.VarDesc2Value());
|
|
},
|
|
R"DOC(
|
|
Convert Fluid Program to New IR Program and get the mappings of VarDesc -> Value.
|
|
|
|
Args:
|
|
|
|
legacy_program (ProgramDesc): The Fluid Program that will be converted.
|
|
|
|
Returns:
|
|
Program: The New IR Program
|
|
dict[str, Value]: Mapping between VarDesc(by name) and Value.
|
|
|
|
Raises:
|
|
PreconditionNotMet: If legacy_program has multi block will raise error.
|
|
|
|
Examples:
|
|
.. code-block:: pycon
|
|
|
|
>>> import os
|
|
>>> # Paddle will remove this flag in the next version
|
|
>>> pir_flag = 'FLAGS_enable_pir_in_executor'
|
|
>>> os.environ[pir_flag] = 'True'
|
|
|
|
>>> import paddle
|
|
>>> from paddle import pir
|
|
>>> paddle.enable_static()
|
|
|
|
>>> x = paddle.randn([4, 4])
|
|
>>> main_program, start_program = (
|
|
... paddle.static.Program(),
|
|
... paddle.static.Program(),
|
|
... )
|
|
|
|
>>> with paddle.static.program_guard(main_program, start_program):
|
|
... x_s = paddle.static.data('x', [4, 4], x.dtype)
|
|
... x_s.stop_gradient = False
|
|
... y_s = paddle.matmul(x_s, x_s)
|
|
... z_s = paddle.add(y_s, y_s)
|
|
... k_s = paddle.tanh(z_s)
|
|
>>> pir_program, mappings = pir.translate_to_pir_with_param_map(main_program.desc)
|
|
|
|
>>> print(pir_program)
|
|
{
|
|
(%0) = "pd_op.data" () {dtype:(pd_op.DataType)float32,is_persistable:[false],name:"x",place:(pd_op.Place)Place(undefined:0),shape:(pd_op.IntArray)[4,4],stop_gradient:[false]} : () -> builtin.tensor<4x4xf32>
|
|
(%1) = "pd_op.matmul" (%0, %0) {is_persistable:[false],stop_gradient:[false],transpose_x:false,transpose_y:false} : (builtin.tensor<4x4xf32>, builtin.tensor<4x4xf32>) -> builtin.tensor<4x4xf32>
|
|
(%2) = "pd_op.add" (%1, %1) {is_persistable:[false],stop_gradient:[false]} : (builtin.tensor<4x4xf32>, builtin.tensor<4x4xf32>) -> builtin.tensor<4x4xf32>
|
|
(%3) = "pd_op.tanh" (%2) {is_persistable:[false],stop_gradient:[false]} : (builtin.tensor<4x4xf32>) -> builtin.tensor<4x4xf32>
|
|
}
|
|
|
|
>>> print(mappings)
|
|
{'matmul_v2_0.tmp_0': [Value(define_op_name=pd_op.matmul, index=0, dtype=builtin.tensor<4x4xf32>)], 'x': [Value(define_op_name=pd_op.data, index=0, dtype=builtin.tensor<4x4xf32>)], 'tanh_0.tmp_0': [Value(define_op_name=pd_op.tanh, index=0, dtype=builtin.tensor<4x4xf32>)], 'elementwise_add_0': [Value(define_op_name=pd_op.add, index=0, dtype=builtin.tensor<4x4xf32>)]}
|
|
)DOC");
|
|
m->def("clear_cinn_compilation_cache", []() {
|
|
#ifdef PADDLE_WITH_CINN
|
|
pybind11::gil_scoped_release release;
|
|
VLOG(4) << "clear CINN CompilationCache and free BackendResource.";
|
|
cinn::hlir::framework::CompilationCache::Instance().Clear();
|
|
#endif
|
|
});
|
|
|
|
m->def("cinn_compilation_cache_size", []() {
|
|
#ifdef PADDLE_WITH_CINN
|
|
pybind11::gil_scoped_release release;
|
|
VLOG(4) << "clear CINN CompilationCache and free BackendResource.";
|
|
return cinn::hlir::framework::CompilationCache::Instance().Size();
|
|
#endif
|
|
});
|
|
m->def("get_attrs_map_json",
|
|
py::overload_cast<Operation *>(&GetAttrsMapJson),
|
|
py::arg("op"));
|
|
m->def("get_attrs_map_json",
|
|
py::overload_cast<py::dict>(&GetAttrsMapJson),
|
|
py::arg("attrs"));
|
|
m->def("get_inputs_type_json",
|
|
&GetInputsTypeJson,
|
|
"Get operation input types as JSON string.");
|
|
m->def("get_outputs_type_json",
|
|
&GetOutputsTypeJson,
|
|
"Get operation output types as JSON string.");
|
|
}
|
|
|
|
namespace {
|
|
|
|
void ApplyCinnPass(Program &program) { // NOLINT
|
|
#ifdef PADDLE_WITH_CINN
|
|
auto CreatePassManager = [&]() -> std::shared_ptr<pir::PassManager> {
|
|
IrContext *ctx = IrContext::Instance();
|
|
ctx->GetOrRegisterDialect<paddle::dialect::OperatorDialect>();
|
|
ctx->GetOrRegisterDialect<cinn::dialect::OperatorDialect>();
|
|
ctx->GetOrRegisterDialect<ap::dialect::OperatorDialect>();
|
|
ctx->GetOrRegisterDialect<pir::shape::ShapeDialect>();
|
|
auto pass_manager = std::make_shared<pir::PassManager>(ctx);
|
|
if (FLAGS_print_ir && VLOG_IS_ON(4)) {
|
|
pass_manager->EnableIRPrinting();
|
|
}
|
|
auto &shape_analysis = pir::ShapeAnalysisManager::Instance().Get(&program);
|
|
pass_manager->SetValueReplacedHook([&](Value from, Value to) {
|
|
shape_analysis.ShareShapeOrData(from, to);
|
|
});
|
|
return pass_manager;
|
|
};
|
|
cinn::dialect::ir::ApplyCinnPass(&program, CreatePassManager);
|
|
#else
|
|
PADDLE_THROW(common::errors::Unimplemented(
|
|
"Currently we only support CINN Pass for Pir under @to_static, please "
|
|
"compile PaddlePaddle with CINN"));
|
|
#endif
|
|
}
|
|
|
|
void ApplyPccPass(Program &program) { // NOLINT
|
|
#ifdef PADDLE_WITH_CINN
|
|
auto CreatePassManager = [&]() -> std::shared_ptr<pir::PassManager> {
|
|
IrContext *ctx = IrContext::Instance();
|
|
ctx->GetOrRegisterDialect<paddle::dialect::OperatorDialect>();
|
|
ctx->GetOrRegisterDialect<cinn::dialect::OperatorDialect>();
|
|
ctx->GetOrRegisterDialect<ap::dialect::OperatorDialect>();
|
|
ctx->GetOrRegisterDialect<pir::shape::ShapeDialect>();
|
|
auto pass_manager = std::make_shared<pir::PassManager>(ctx);
|
|
if (FLAGS_print_ir && VLOG_IS_ON(4)) {
|
|
pass_manager->EnableIRPrinting();
|
|
}
|
|
auto &shape_analysis = pir::ShapeAnalysisManager::Instance().Get(&program);
|
|
pass_manager->SetValueReplacedHook([&](Value from, Value to) {
|
|
shape_analysis.ShareShapeOrData(from, to);
|
|
});
|
|
return pass_manager;
|
|
};
|
|
ap::paddle::ApplyPccPass(&program, CreatePassManager);
|
|
#else
|
|
PADDLE_THROW(common::errors::Unimplemented(
|
|
"Currently we only support CINN Pass for Pir under @to_static, please "
|
|
"compile PaddlePaddle with CINN"));
|
|
#endif
|
|
}
|
|
|
|
void CheckInferSymbolicIfNeed(Program &program) { // NOLINT
|
|
#ifdef PADDLE_WITH_CINN
|
|
auto CreatePassManager = [&]() -> std::shared_ptr<pir::PassManager> {
|
|
IrContext *ctx = IrContext::Instance();
|
|
ctx->GetOrRegisterDialect<paddle::dialect::OperatorDialect>();
|
|
ctx->GetOrRegisterDialect<cinn::dialect::OperatorDialect>();
|
|
ctx->GetOrRegisterDialect<pir::shape::ShapeDialect>();
|
|
auto pass_manager = std::make_shared<pir::PassManager>(ctx);
|
|
if (FLAGS_print_ir) {
|
|
pass_manager->EnableIRPrinting();
|
|
}
|
|
return pass_manager;
|
|
};
|
|
cinn::dialect::ir::CheckInferSymbolicIfNeed(&program, CreatePassManager);
|
|
#else
|
|
// Do nothing.
|
|
#endif
|
|
}
|
|
|
|
} // namespace
|
|
|
|
void InferSymbolicShapePass(
|
|
std::shared_ptr<pir::PassManager> &pass_manager, // NOLINT
|
|
pir::Program &program) { // NOLINT
|
|
IrContext *ctx = IrContext::Instance();
|
|
ctx->GetOrRegisterDialect<pir::shape::ShapeDialect>();
|
|
pir::OriginalAttributesFilter::Instance().SetOriginalAttributesMap(
|
|
paddle::dialect::GetAllOpOriginalAttributes());
|
|
pass_manager->AddPass(pir::CreateShapeOptimizationPass());
|
|
}
|
|
|
|
std::shared_ptr<Program> ApplyCommonSubexpressionEliminationPass(
|
|
std::shared_ptr<Program> program) {
|
|
pir::PassManager pm(IrContext::Instance(), 2);
|
|
pm.AddPass(pir::CreateCommonSubexpressionEliminationPass());
|
|
pm.Run(program.get());
|
|
if (FLAGS_print_ir) {
|
|
std::cout
|
|
<< "IR After CommonSubexpressionEliminationPass -------------------"
|
|
<< std::endl;
|
|
std::cout << *program << std::endl;
|
|
}
|
|
return program;
|
|
}
|
|
|
|
void ApplyReduceAsToSumPass(
|
|
std::shared_ptr<pir::PassManager> &pass_manager, // NOLINT
|
|
pir::Program &program) { // NOLINT
|
|
#ifdef PADDLE_WITH_CINN
|
|
pass_manager->AddPass(cinn::dialect::ir::CreateReduceAsToSumPass());
|
|
pass_manager->AddPass(pir::CreateDeadCodeEliminationPass());
|
|
#else
|
|
PADDLE_THROW(common::errors::Unimplemented(
|
|
"Currently we only support ReduceAsToSumPass Pass for Pir under "
|
|
"@to_static, please "
|
|
"compile PaddlePaddle with CINN"));
|
|
#endif
|
|
}
|
|
|
|
std::shared_ptr<Program> ApplyFusedBnAddActPass(
|
|
std::shared_ptr<Program> program) {
|
|
pir::PassManager pm(IrContext::Instance(), 3);
|
|
pm.AddPass(pir::CreateFusedBnAddActPass());
|
|
pm.Run(program.get());
|
|
if (FLAGS_print_ir) {
|
|
std::cout << "IR After FusedBnAddActPass -------------------" << std::endl;
|
|
std::cout << *program << std::endl;
|
|
}
|
|
return program;
|
|
}
|
|
|
|
void BindIrPass(pybind11::module *m) {
|
|
m->def("apply_cinn_pass", ApplyCinnPass);
|
|
m->def("apply_pcc_pass", ApplyPccPass);
|
|
m->def("check_infer_symbolic_if_need", CheckInferSymbolicIfNeed);
|
|
m->def("infer_symbolic_shape_pass", InferSymbolicShapePass);
|
|
m->def("apply_cse_pass", ApplyCommonSubexpressionEliminationPass);
|
|
m->def("apply_bn_add_act_pass", ApplyFusedBnAddActPass);
|
|
m->def("reduce_as_sum_pass", ApplyReduceAsToSumPass);
|
|
|
|
py::class_<Pass, std::shared_ptr<Pass>> pass(*m,
|
|
"Pass",
|
|
R"DOC(
|
|
Pass class.
|
|
|
|
)DOC");
|
|
pass.def("name", &Pass::name)
|
|
.def("opt_level",
|
|
[](const Pass &self) { return self.pass_info().opt_level; })
|
|
.def("dependents",
|
|
[](const Pass &self) { return self.pass_info().dependents; });
|
|
}
|
|
|
|
void BindPassManager(pybind11::module *m) {
|
|
py::class_<PassManager, std::shared_ptr<PassManager>> pass_manager(
|
|
*m,
|
|
"PassManager",
|
|
R"DOC(
|
|
A class that manages all passes.
|
|
|
|
)DOC");
|
|
pass_manager
|
|
.def(py::init([](uint8_t opt_level) {
|
|
return std::make_unique<PassManager>(IrContext::Instance(),
|
|
opt_level);
|
|
}),
|
|
py::arg("opt_level") = 2)
|
|
.def("add_pass",
|
|
[](PassManager &self,
|
|
const std::string &pass_name,
|
|
const std::unordered_map<std::string, py::object> attrs = {}) {
|
|
auto pass = pir::PassRegistry::Instance().Get(pass_name);
|
|
for (const auto &attr : attrs) {
|
|
if (py::isinstance<py::str>(attr.second)) {
|
|
pass->Set(attr.first,
|
|
new std::string(attr.second.cast<std::string>()));
|
|
} else if (py::isinstance<py::bool_>(attr.second)) {
|
|
pass->Set(attr.first, new bool(attr.second.cast<bool>()));
|
|
} else if (py::isinstance<py::int_>(attr.second)) {
|
|
pass->Set(attr.first, new int(attr.second.cast<int>()));
|
|
} else if (py::isinstance<py::float_>(attr.second)) {
|
|
pass->Set(attr.first, new float(attr.second.cast<float>()));
|
|
} else if (py::isinstance<framework::Scope>(attr.second)) {
|
|
pass->SetNotOwned(attr.first,
|
|
attr.second.cast<framework::Scope *>());
|
|
} else if (py::isinstance<GPUPlace>(attr.second)) {
|
|
pass->Set(attr.first, new Place(attr.second.cast<GPUPlace>()));
|
|
} else {
|
|
PADDLE_THROW(common::errors::InvalidArgument(
|
|
"The pass attr is not supported this type."));
|
|
}
|
|
}
|
|
self.AddPass(std::move(pass));
|
|
})
|
|
.def("register_pass",
|
|
[](PassManager &self,
|
|
const std::string &pass_name,
|
|
std::shared_ptr<paddle::drr::DrrPatternContext> pattern_ctx) {
|
|
using AutoFinalPass =
|
|
paddle::drr::AutoDrrPass<paddle::drr::AutoDrrPattern>;
|
|
// Instead of using static PassRegistrar which may cause lifetime
|
|
// issues during program termination, directly register the pass to
|
|
// PassRegistry. This approach provides better control over object
|
|
// lifetime management and avoids potential segmentation faults
|
|
// during static destruction.
|
|
self.AddPass(
|
|
std::make_unique<AutoFinalPass>(pass_name, pattern_ctx));
|
|
})
|
|
.def("passes",
|
|
[](PassManager &self) {
|
|
std::vector<std::string> pass_names;
|
|
for (const auto &pass : self.passes()) {
|
|
pass_names.emplace_back(pass->name());
|
|
}
|
|
return pass_names;
|
|
})
|
|
.def("run", [](PassManager &self, Program *p) { self.Run(p); })
|
|
.def("empty", &PassManager::empty)
|
|
.def("clear", &PassManager::clear)
|
|
.def("enable_ir_printing",
|
|
[](PassManager &self) { self.EnableIRPrinting(); })
|
|
.def("enable_print_statistics",
|
|
[](PassManager &self) { self.EnablePrintStatistics(); });
|
|
}
|
|
|
|
void BindDrrPatternContext(pybind11::module *m) {
|
|
// bind NormalAttribute
|
|
pybind11::class_<drr::NormalAttribute> normal_attribute(*m,
|
|
"NormalAttribute");
|
|
|
|
// bind ComputeAttribute
|
|
pybind11::class_<drr::ComputeAttribute> compute_attribute(*m,
|
|
"ComputeAttribute");
|
|
|
|
// bind Tensor
|
|
pybind11::class_<drr::Tensor> tensor(*m,
|
|
"Tensor",
|
|
R"DOC(
|
|
register Tensor for DRR.
|
|
)DOC");
|
|
|
|
// bind Op
|
|
pybind11::class_<drr::Op> op(*m,
|
|
"Op",
|
|
R"DOC(
|
|
Represents an operation in the DRR framework.
|
|
)DOC");
|
|
op.def(
|
|
"__call__",
|
|
[](drr::Op *self,
|
|
const std::vector<drr::Tensor> &input_tensors,
|
|
const std::vector<drr::Tensor> &output_tensors) {
|
|
std::vector<const drr::Tensor *> input_ptrs;
|
|
std::vector<const drr::Tensor *> output_ptrs;
|
|
|
|
for (const auto &t : input_tensors) {
|
|
input_ptrs.push_back(&t);
|
|
}
|
|
for (const auto &t : output_tensors) {
|
|
output_ptrs.push_back(&t);
|
|
}
|
|
(*self)(input_ptrs, output_ptrs);
|
|
},
|
|
pybind11::arg("input_tensors"),
|
|
pybind11::arg("output_tensors"),
|
|
"Call the operation with an input tensor and return the output tensor.");
|
|
|
|
// bind DrrPatternContext
|
|
pybind11::class_<drr::DrrPatternContext,
|
|
std::shared_ptr<drr::DrrPatternContext>>
|
|
drr_pattern_context(*m,
|
|
"DrrPatternContext",
|
|
R"DOC(
|
|
A class that manages DRR (Dynamic Rewrite Rule) pattern context.
|
|
|
|
)DOC");
|
|
drr_pattern_context.def(pybind11::init<>())
|
|
.def("SourcePattern", &drr::DrrPatternContext::SourcePattern);
|
|
|
|
// bind drr::SourcePattern
|
|
pybind11::class_<drr::SourcePattern, std::shared_ptr<drr::SourcePattern>>
|
|
source_pattern(*m,
|
|
"SourcePattern",
|
|
R"DOC(
|
|
Represents a source pattern for matching in the DRR framework.
|
|
|
|
)DOC");
|
|
source_pattern.def("ResultPattern", &drr::SourcePattern::ResultPattern)
|
|
.def(
|
|
"Op",
|
|
[](drr::SourcePattern &self,
|
|
const std::string &op_type,
|
|
const std::unordered_map<std::string, drr::Attribute> &attributes =
|
|
{}) { return self.Op(op_type, attributes); },
|
|
pybind11::return_value_policy::reference_internal,
|
|
pybind11::arg("op_type"),
|
|
pybind11::arg("attributes") =
|
|
std::unordered_map<std::string, drr::Attribute>())
|
|
.def(
|
|
"Tensor",
|
|
[](drr::SourcePattern &self, const std::string &name) {
|
|
return self.Tensor(name);
|
|
},
|
|
pybind11::return_value_policy::reference_internal,
|
|
pybind11::arg("name"))
|
|
.def(
|
|
"InputNoneTensor",
|
|
[](drr::ResultPattern &self) { return self.InputNoneTensor(); },
|
|
pybind11::return_value_policy::reference_internal)
|
|
.def(
|
|
"OutputNoneTensor",
|
|
[](drr::ResultPattern &self) { return self.OutputNoneTensor(); },
|
|
pybind11::return_value_policy::reference_internal)
|
|
.def(
|
|
"Attr",
|
|
[](drr::SourcePattern &self, const std::string &attr_name) {
|
|
return self.Attr(attr_name);
|
|
},
|
|
pybind11::return_value_policy::reference_internal,
|
|
pybind11::arg("attr_name"))
|
|
.def("AddConstraint",
|
|
[](drr::SourcePattern &self, const pybind11::function &py_func) {
|
|
// wrap pyfunction -> cpp function
|
|
paddle::drr::ConstraintFunction cpp_func =
|
|
[py_func](const paddle::drr::MatchContext &context) -> bool {
|
|
try {
|
|
pybind11::object py_context = pybind11::cast(context);
|
|
pybind11::object result = py_func(py_context);
|
|
|
|
bool ret = result.cast<bool>();
|
|
return ret;
|
|
} catch (const pybind11::error_already_set &e) {
|
|
std::cerr << "Python error in AddConstraint callback: "
|
|
<< e.what() << std::endl;
|
|
throw;
|
|
}
|
|
};
|
|
self.AddConstraint(cpp_func);
|
|
})
|
|
.def("AddPostProcess",
|
|
[](drr::SourcePattern &self, const pybind11::function &py_func) {
|
|
// wrap pyfunction -> cpp function
|
|
paddle::drr::PostProcessFunction cpp_func =
|
|
[py_func](const paddle::drr::MatchContext &context) -> void {
|
|
try {
|
|
pybind11::object py_context = pybind11::cast(context);
|
|
py_func(py_context);
|
|
} catch (const pybind11::error_already_set &e) {
|
|
std::cerr << "Python error in AddPostProcess callback: "
|
|
<< e.what() << std::endl;
|
|
throw;
|
|
}
|
|
};
|
|
self.AddPostProcess(cpp_func);
|
|
});
|
|
|
|
// bind MatchContext
|
|
pybind11::class_<drr::MatchContext, std::shared_ptr<drr::MatchContext>>
|
|
match_context(*m,
|
|
"MatchContext",
|
|
R"DOC(
|
|
Represents the context of a match in the DRR framework.
|
|
)DOC");
|
|
match_context
|
|
.def(
|
|
"Tensor",
|
|
[](drr::MatchContext &self, std::string &tensor_name) -> Value {
|
|
return self.Tensor(tensor_name);
|
|
},
|
|
pybind11::return_value_policy::reference_internal,
|
|
pybind11::arg("tensor_name"))
|
|
// Attr
|
|
.def(
|
|
"StrAttr",
|
|
[](drr::MatchContext &self, const std::string &value_name) {
|
|
return self.Attr<std::string>(value_name);
|
|
},
|
|
pybind11::arg("value_name"))
|
|
.def(
|
|
"BoolAttr",
|
|
[](drr::MatchContext &self, const std::string &value_name) {
|
|
return self.Attr<bool>(value_name);
|
|
},
|
|
pybind11::arg("value_name"))
|
|
.def(
|
|
"Int32Attr",
|
|
[](drr::MatchContext &self, const std::string &value_name) {
|
|
return self.Attr<int32_t>(value_name);
|
|
},
|
|
pybind11::arg("value_name"))
|
|
.def(
|
|
"Int64Attr",
|
|
[](drr::MatchContext &self, const std::string &value_name) {
|
|
return self.Attr<int64_t>(value_name);
|
|
},
|
|
pybind11::arg("value_name"))
|
|
.def(
|
|
"Float32Attr",
|
|
[](drr::MatchContext &self, const std::string &value_name) {
|
|
return self.Attr<float>(value_name);
|
|
},
|
|
pybind11::arg("value_name"))
|
|
.def(
|
|
"DoubleAttr",
|
|
[](drr::MatchContext &self, const std::string &value_name) {
|
|
return self.Attr<double>(value_name);
|
|
},
|
|
pybind11::arg("value_name"))
|
|
.def(
|
|
"VectorInt32Attr",
|
|
[](drr::MatchContext &self, const std::string &value_name) {
|
|
return self.Attr<std::vector<int32_t>>(value_name);
|
|
},
|
|
pybind11::arg("value_name"))
|
|
.def(
|
|
"VectorInt64Attr",
|
|
[](drr::MatchContext &self, const std::string &value_name) {
|
|
return self.Attr<std::vector<int64_t>>(value_name);
|
|
},
|
|
pybind11::arg("value_name"))
|
|
.def(
|
|
"VectorFloat32Attr",
|
|
[](drr::MatchContext &self, const std::string &value_name) {
|
|
return self.Attr<std::vector<int32_t>>(value_name);
|
|
},
|
|
pybind11::arg("value_name"))
|
|
.def(
|
|
"DataTypeAttr",
|
|
[](drr::MatchContext &self, const std::string &value_name) {
|
|
return self.Attr<DataType>(value_name);
|
|
},
|
|
pybind11::arg("value_name"))
|
|
.def(
|
|
"PlaceAttr",
|
|
[](drr::MatchContext &self, const std::string &value_name) {
|
|
return self.Attr<Place>(value_name);
|
|
},
|
|
pybind11::arg("value_name"));
|
|
|
|
// bind drr::ResultPattern
|
|
pybind11::class_<drr::ResultPattern, std::shared_ptr<drr::ResultPattern>>
|
|
result_pattern(*m,
|
|
"ResultPattern",
|
|
R"DOC(
|
|
Represents a result pattern for matching in the DRR framework
|
|
|
|
)DOC");
|
|
|
|
result_pattern
|
|
.def(
|
|
"Op",
|
|
[](drr::ResultPattern &self,
|
|
const std::string &op_type,
|
|
const std::unordered_map<std::string, drr::Attribute> &attributes =
|
|
{}) { return self.Op(op_type, attributes); },
|
|
pybind11::return_value_policy::reference_internal,
|
|
pybind11::arg("op_type"),
|
|
pybind11::arg("attributes") =
|
|
std::unordered_map<std::string, drr::Attribute>())
|
|
.def(
|
|
"InputNoneTensor",
|
|
[](drr::ResultPattern &self) { return self.InputNoneTensor(); },
|
|
pybind11::return_value_policy::reference_internal)
|
|
.def(
|
|
"OutputNoneTensor",
|
|
[](drr::ResultPattern &self) { return self.OutputNoneTensor(); },
|
|
pybind11::return_value_policy::reference_internal)
|
|
.def(
|
|
"Tensor",
|
|
[](drr::ResultPattern &self, const std::string &name) {
|
|
return self.Tensor(name);
|
|
},
|
|
pybind11::return_value_policy::reference_internal,
|
|
pybind11::arg("name"))
|
|
// Attr
|
|
.def(
|
|
"StrAttr",
|
|
[](drr::ResultPattern &self, const std::string &value) {
|
|
return self.StrAttr(value);
|
|
},
|
|
pybind11::arg("value"))
|
|
.def(
|
|
"BoolAttr",
|
|
[](drr::ResultPattern &self, bool value) {
|
|
return self.BoolAttr(value);
|
|
},
|
|
pybind11::arg("value"))
|
|
.def(
|
|
"Int32Attr",
|
|
[](drr::ResultPattern &self, int32_t value) {
|
|
return self.Int32Attr(value);
|
|
},
|
|
pybind11::arg("value"))
|
|
.def(
|
|
"Int64Attr",
|
|
[](drr::ResultPattern &self, int64_t value) {
|
|
return self.Int64Attr(value);
|
|
},
|
|
pybind11::arg("value"))
|
|
.def(
|
|
"Float32Attr",
|
|
[](drr::ResultPattern &self, float value) {
|
|
return self.Float32Attr(value);
|
|
},
|
|
pybind11::arg("value"))
|
|
.def(
|
|
"DoubleAttr",
|
|
[](drr::ResultPattern &self, double value) {
|
|
return self.DoubleAttr(value);
|
|
},
|
|
pybind11::arg("value"))
|
|
.def(
|
|
"VectorInt32Attr",
|
|
[](drr::ResultPattern &self, const std::vector<int32_t> &value) {
|
|
return self.VectorInt32Attr(value);
|
|
},
|
|
pybind11::arg("value"))
|
|
.def(
|
|
"VectorInt64Attr",
|
|
[](drr::ResultPattern &self, const std::vector<int64_t> &value) {
|
|
return self.VectorInt64Attr(value);
|
|
},
|
|
pybind11::arg("value"))
|
|
.def(
|
|
"VectorFloat32Attr",
|
|
[](drr::ResultPattern &self, const std::vector<float> &value) {
|
|
return self.VectorFloatAttr(value);
|
|
},
|
|
pybind11::arg("value"))
|
|
.def(
|
|
"DataTypeAttr",
|
|
[](drr::ResultPattern &self, const std::string &value) {
|
|
return self.DataTypeAttr(value);
|
|
},
|
|
pybind11::arg("value"))
|
|
.def(
|
|
"PlaceAttr",
|
|
[](drr::ResultPattern &self, const std::string &value) {
|
|
return self.PlaceAttr(value);
|
|
},
|
|
pybind11::arg("value"))
|
|
.def(
|
|
"DataLayoutAttr",
|
|
[](drr::ResultPattern &self, const std::string &value) {
|
|
return self.DataLayoutAttr(value);
|
|
},
|
|
pybind11::arg("value"))
|
|
.def(
|
|
"ComputeAttr",
|
|
[](drr::ResultPattern &self, pybind11::function py_func) {
|
|
paddle::drr::AttrComputeFunc cpp_func =
|
|
[py_func](
|
|
const paddle::drr::MatchContext &context) -> std::any {
|
|
try {
|
|
pybind11::object py_context = pybind11::cast(context);
|
|
pybind11::object py_result = py_func(py_context);
|
|
pybind11::tuple result_tuple =
|
|
py_result.cast<pybind11::tuple>();
|
|
pybind11::object result = result_tuple[0];
|
|
std::string type_name = result_tuple[1].cast<std::string>();
|
|
auto any_result = CastPyObjectToAny(result, type_name);
|
|
|
|
return std::visit(
|
|
[](auto &&value) -> std::any { return std::any(value); },
|
|
any_result);
|
|
} catch (const pybind11::error_already_set &e) {
|
|
std::cerr << "Python error in ComputeAttr callback: "
|
|
<< e.what() << std::endl;
|
|
throw;
|
|
}
|
|
};
|
|
return self.ComputeAttr(cpp_func);
|
|
},
|
|
pybind11::arg("py_func"));
|
|
|
|
m->def("value_is_persistable",
|
|
[](const Value &value) { return pir::ValueIsPersistable(value); });
|
|
}
|
|
|
|
void BindShapeOrDataDimExprs(pybind11::module *m) {
|
|
py::class_<symbol::ShapeOrDataDimExprs,
|
|
std::shared_ptr<symbol::ShapeOrDataDimExprs>>
|
|
shape_or_data_dim_exprs(*m, "ShapeOrDataDimExprs", R"DOC(
|
|
A class that store the shape or data of value.
|
|
)DOC");
|
|
shape_or_data_dim_exprs
|
|
.def("shape",
|
|
&symbol::ShapeOrDataDimExprs::shape,
|
|
return_value_policy::reference)
|
|
.def("data",
|
|
&symbol::ShapeOrDataDimExprs::data,
|
|
return_value_policy::reference)
|
|
.def(
|
|
"is_equal",
|
|
[](symbol::ShapeOrDataDimExprs &self,
|
|
std::vector<int64_t> expect_shape,
|
|
std::vector<int64_t> expect_data) -> bool {
|
|
VLOG(3) << "Start compare shape and data.";
|
|
|
|
const auto &CompareFunc =
|
|
[&](const std::vector<int64_t> &expect,
|
|
const std::vector<symbol::DimExpr> &actual,
|
|
const std::string &compare_type) -> bool {
|
|
const auto PrintExpectAndActual = [&](const std::string &prefix) {
|
|
std::ostringstream sout;
|
|
sout << prefix << " expect: [";
|
|
std::copy(expect.begin(),
|
|
expect.end(),
|
|
std::ostream_iterator<int64_t>(sout, ","));
|
|
sout << "]" << std::endl;
|
|
|
|
sout << prefix << " actual:" << actual << std::endl;
|
|
LOG(ERROR) << sout.str();
|
|
};
|
|
|
|
if (actual.size() != expect.size()) {
|
|
LOG(ERROR) << compare_type << " expect size " << expect.size()
|
|
<< " is not equal to actual size " << actual.size()
|
|
<< " . The detailed information is as follows:";
|
|
PrintExpectAndActual(compare_type);
|
|
return false;
|
|
} else if (actual.empty()) {
|
|
return true;
|
|
}
|
|
|
|
for (size_t i = 0; i < actual.size(); i++) {
|
|
if (!actual.at(i).isa<int64_t>()) {
|
|
PrintExpectAndActual(compare_type);
|
|
PADDLE_THROW(common::errors::InvalidArgument(
|
|
"In OpTest, only supports cases where the type of "
|
|
"DimExpr "
|
|
"is int64_t."));
|
|
return false;
|
|
}
|
|
if (actual.at(i) != expect.at(i)) {
|
|
LOG(ERROR)
|
|
<< compare_type << " expect[" << i
|
|
<< "]: " << expect.at(i) << " is not equal to actual["
|
|
<< i << "]: " << actual.at(i)
|
|
<< " . The detailed information is as follows:";
|
|
PrintExpectAndActual(compare_type);
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
};
|
|
|
|
// compare shape
|
|
const std::vector<symbol::DimExpr> &actual_shape = self.shape();
|
|
const bool shape_status =
|
|
CompareFunc(expect_shape, actual_shape, "shape");
|
|
// compare data
|
|
const std::optional<std::vector<symbol::DimExpr>> &actual_data_ =
|
|
self.data();
|
|
if (actual_data_.has_value()) {
|
|
PADDLE_ENFORCE_LE(actual_shape.size(),
|
|
1,
|
|
common::errors::Unimplemented(
|
|
"Now data dim expr is not supported for "
|
|
"multi-dim shape."));
|
|
const std::vector<symbol::DimExpr> actual_data =
|
|
actual_data_.value();
|
|
const bool data_status =
|
|
CompareFunc(expect_data, actual_data, "data");
|
|
return shape_status && data_status;
|
|
}
|
|
return shape_status;
|
|
},
|
|
py::arg("expect_shape"),
|
|
py::arg("expect_data") = py::list());
|
|
}
|
|
|
|
void BindShapeConstraintIRAnalysis(pybind11::module *m) {
|
|
m->def(
|
|
"get_shape_constraint_ir_analysis",
|
|
[](const pir::Program *program) -> pir::ShapeConstraintIRAnalysis & {
|
|
return pir::ShapeAnalysisManager::Instance().Get(program);
|
|
},
|
|
return_value_policy::reference);
|
|
m->def("all_ops_defined_symbol_infer",
|
|
[](const pir::Program *program) -> bool {
|
|
// check that all ops have defined the InferSymbolicShapeInterface
|
|
bool flag = true;
|
|
for (Operation &op : *(program->block())) {
|
|
pir::InferSymbolicShapeInterface infer_interface =
|
|
op.dyn_cast<pir::InferSymbolicShapeInterface>();
|
|
if (!infer_interface) {
|
|
LOG(ERROR) << "The op: " << op.name()
|
|
<< " does not implement InferSymbolicShapeInterface.";
|
|
flag = false;
|
|
}
|
|
}
|
|
return flag;
|
|
});
|
|
#ifdef PADDLE_WITH_CINN
|
|
m->def(
|
|
"bind_symbolic_constraints",
|
|
[](pir::Program *program, const py::handle &constraints) -> void {
|
|
// Check input is sequence
|
|
PADDLE_ENFORCE_EQ(
|
|
py::isinstance<py::sequence>(constraints),
|
|
true,
|
|
common::errors::InvalidArgument(
|
|
"constraints for SOT symbolic variables must be a sequence."));
|
|
|
|
const py::sequence constraints_seq =
|
|
py::cast<py::sequence>(constraints);
|
|
if (py::len(constraints_seq) == 0) {
|
|
return;
|
|
}
|
|
|
|
// Process constraints
|
|
std::vector<std::tuple<std::string,
|
|
std::tuple<int64_t,
|
|
std::optional<int64_t>,
|
|
std::optional<int64_t>>>>
|
|
raw_constraints;
|
|
|
|
for (size_t idx = 0; idx < constraints_seq.size(); ++idx) {
|
|
const auto &constraint = constraints_seq[idx];
|
|
|
|
// Check constraint item is tuple
|
|
PADDLE_ENFORCE_EQ(
|
|
py::isinstance<py::tuple>(constraint),
|
|
true,
|
|
common::errors::InvalidArgument("Constraint[%zu] must be a tuple "
|
|
"of (name, dimension_triplet).",
|
|
idx));
|
|
|
|
const py::tuple constraint_tuple = py::cast<py::tuple>(constraint);
|
|
|
|
// Check tuple has 2 elements
|
|
PADDLE_ENFORCE_EQ(
|
|
constraint_tuple.size(),
|
|
2,
|
|
common::errors::InvalidArgument(
|
|
"Constraint[%zu] must have exactly 2 elements (got %zu).",
|
|
idx,
|
|
constraint_tuple.size()));
|
|
|
|
// Check and get input spec name
|
|
const py::handle name_handle = constraint_tuple[0];
|
|
|
|
PADDLE_ENFORCE_EQ(
|
|
py::isinstance<py::str>(name_handle),
|
|
true,
|
|
common::errors::InvalidArgument(
|
|
"Constraint[%zu][0] must be a string (got %s)",
|
|
idx,
|
|
py::str(name_handle.get_type()).cast<std::string>().c_str()));
|
|
const std::string input_spec_name =
|
|
py::cast<std::string>(name_handle);
|
|
|
|
// Check and get dimension triplet
|
|
const py::handle triplet_handle = constraint_tuple[1];
|
|
PADDLE_ENFORCE_EQ(py::isinstance<py::tuple>(triplet_handle),
|
|
true,
|
|
common::errors::InvalidArgument(
|
|
"Constraint[%zu][1] must be a tuple.", idx));
|
|
|
|
const py::tuple triplet = py::cast<py::tuple>(triplet_handle);
|
|
PADDLE_ENFORCE_EQ(
|
|
triplet.size(),
|
|
3,
|
|
common::errors::InvalidArgument(
|
|
"Constraint[%zu][1] must have 3 elements (got %zu).",
|
|
idx,
|
|
triplet.size()));
|
|
|
|
// Validate and convert elements
|
|
auto convert_optional = [idx](const py::handle &h,
|
|
int pos) -> std::optional<int64_t> {
|
|
if (h.is_none()) return std::nullopt;
|
|
|
|
PADDLE_ENFORCE_EQ(
|
|
py::isinstance<py::int_>(h),
|
|
true,
|
|
"Constraint[%zu][1][%d] must be int or None (got %s).",
|
|
idx,
|
|
pos,
|
|
py::str(h.get_type()).cast<std::string>().c_str());
|
|
return py::cast<int64_t>(h);
|
|
};
|
|
|
|
// Check dim_idx
|
|
PADDLE_ENFORCE_EQ(
|
|
py::isinstance<py::int_>(triplet[0]),
|
|
true,
|
|
common::errors::InvalidArgument(
|
|
"Constraint[%zu][1][0] (dim_idx) must be int (got %s).",
|
|
idx,
|
|
py::str(triplet[0].get_type()).cast<std::string>().c_str()));
|
|
const int64_t dim_idx = py::cast<int64_t>(triplet[0]);
|
|
|
|
// Convert min/max with position info
|
|
std::optional<int64_t> min_val = convert_optional(triplet[1], 1);
|
|
std::optional<int64_t> max_val = convert_optional(triplet[2], 2);
|
|
|
|
// Add to constraints
|
|
raw_constraints.emplace_back(
|
|
std::move(input_spec_name),
|
|
std::make_tuple(dim_idx, min_val, max_val));
|
|
}
|
|
|
|
::cinn::dialect::ir::SpecifyInputDynamicDimFromPython(program,
|
|
raw_constraints);
|
|
},
|
|
py::arg("program"),
|
|
py::arg("constraints").noconvert());
|
|
#endif
|
|
|
|
py::class_<pir::ShapeConstraintIRAnalysis,
|
|
std::shared_ptr<pir::ShapeConstraintIRAnalysis>>
|
|
shape_constraint_ir_analysis(*m, "ShapeConstraintIRAnalysis", R"DOC(
|
|
A class that store the shape information of all operators.
|
|
)DOC");
|
|
shape_constraint_ir_analysis
|
|
.def("get_shape_or_data_for_var",
|
|
&pir::ShapeConstraintIRAnalysis::GetShapeOrDataForValue,
|
|
return_value_policy::reference)
|
|
.def("set_shape_or_data_for_var",
|
|
&pir::ShapeConstraintIRAnalysis::SetShapeOrDataForValue)
|
|
.def("register_symbol_cstr_from_shape_analysis",
|
|
&pir::ShapeConstraintIRAnalysis::
|
|
RegisterSymbolConstraintFromShapeAnalysis);
|
|
}
|
|
|
|
void BindPir(pybind11::module *module) {
|
|
auto ir_module = module->def_submodule("pir");
|
|
BindProgram(&ir_module);
|
|
BindBlock(&ir_module);
|
|
BindValue(&ir_module);
|
|
BindIrMapping(&ir_module);
|
|
BindCloneOptions(&ir_module);
|
|
BindOperation(&ir_module);
|
|
BindOpOperand(&ir_module);
|
|
BindType(&ir_module);
|
|
BindVectorType(&ir_module);
|
|
BindAttribute(&ir_module);
|
|
BindInsertionPoint(&ir_module);
|
|
BindUtils(&ir_module);
|
|
BindIrPass(&ir_module);
|
|
BindPassManager(&ir_module);
|
|
BindControlFlowApi(&ir_module);
|
|
BindShapeOrDataDimExprs(&ir_module);
|
|
BindShapeConstraintIRAnalysis(&ir_module);
|
|
auto ops_modules = ir_module.def_submodule("ops");
|
|
BindOpsAPI(&ops_modules);
|
|
BindDrrPatternContext(&ir_module);
|
|
}
|
|
|
|
} // namespace pybind
|
|
} // namespace paddle
|