442 lines
16 KiB
C++
442 lines
16 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/control_flow_api.h"
|
|
|
|
#include <Python.h>
|
|
#include <pybind11/chrono.h>
|
|
#include <pybind11/complex.h>
|
|
#include <pybind11/functional.h>
|
|
#include <pybind11/stl.h>
|
|
#include <unordered_set>
|
|
#include <vector>
|
|
|
|
#include "paddle/common/ddim.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/utils/general_functions.h"
|
|
#include "paddle/fluid/platform/enforce.h"
|
|
#include "paddle/phi/common/data_type.h"
|
|
#include "paddle/phi/common/place.h"
|
|
#include "paddle/pir/include/core/block.h"
|
|
#include "paddle/pir/include/core/operation.h"
|
|
#include "paddle/pir/include/core/program.h"
|
|
#include "paddle/pir/include/core/value.h"
|
|
#include "paddle/pir/include/dialect/control_flow/ir/cf_op.h"
|
|
|
|
#include "paddle/fluid/pybind/python_callable_registry.h"
|
|
|
|
namespace py = pybind11;
|
|
using paddle::dialect::ApiBuilder;
|
|
using paddle::dialect::AssertOp;
|
|
using paddle::dialect::HasElementsOp;
|
|
using paddle::dialect::IfOp;
|
|
using paddle::dialect::PyLayerOp;
|
|
using paddle::dialect::WhileOp;
|
|
using paddle::pybind::PyIfOp;
|
|
using paddle::pybind::PyWhileOp;
|
|
using pir::Block;
|
|
using pir::Builder;
|
|
using pir::CombineOp;
|
|
using pir::Operation;
|
|
using pir::Program;
|
|
using pir::StackCreateOp;
|
|
using pir::TuplePopOp;
|
|
using pir::TuplePushOp;
|
|
using pir::Type;
|
|
using pir::Value;
|
|
using pir::YieldOp;
|
|
using pybind11::return_value_policy;
|
|
namespace {
|
|
|
|
void BindIfOp(py::module* m) {
|
|
m->def("build_if_op", [](Value cond) {
|
|
return PyIfOp(ApiBuilder::Instance().GetBuilder()->Build<IfOp>(
|
|
cond, std::vector<Type>{}));
|
|
});
|
|
m->def("build_if_op", [](const std::vector<Value>& cond) {
|
|
auto& builder = ApiBuilder::Instance().GetBuilder();
|
|
auto new_cond = builder->Build<CombineOp>(cond).out();
|
|
return PyIfOp(builder->Build<IfOp>(new_cond, std::vector<Type>{}));
|
|
});
|
|
py::class_<PyIfOp> if_op(*m, "IfOp", R"DOC(
|
|
The PyIfOp is a encapsulation of IfOp. Compared with ifOp, it provides an additional 'update_output' interface.
|
|
The 'update_output' interface will construct a new IfOp operation to replace its underlying IfOp. In the process, the original
|
|
IfOp will be destroyed. In order to avoid the risk of memory used in python side, We encapsulate PyIfOp to python api.
|
|
)DOC");
|
|
if_op.def("true_block", &PyIfOp::true_block, return_value_policy::reference)
|
|
.def("false_block", &PyIfOp::false_block, return_value_policy::reference)
|
|
.def("cond", &PyIfOp::cond)
|
|
.def("update_output", &PyIfOp::UpdateOutput)
|
|
.def("as_operation", &PyIfOp::operation, return_value_policy::reference)
|
|
.def("results", [](PyIfOp& self) -> py::list {
|
|
py::list op_list;
|
|
for (uint32_t i = 0; i < self->num_results(); i++) {
|
|
op_list.append(static_cast<pir::Value>(self.result(i)));
|
|
}
|
|
return op_list;
|
|
});
|
|
}
|
|
|
|
void BindPyLayerOp(py::module* m) {
|
|
m->def("build_pylayer_op", [](const std::vector<Value>& inputs) {
|
|
return ApiBuilder::Instance().GetBuilder()->Build<PyLayerOp>(
|
|
inputs, std::vector<Type>{}, -1);
|
|
});
|
|
py::class_<PyLayerOp> pylayer_op(*m, "PyLayerOp", R"DOC(
|
|
TODO(MarioLulab): Add some docs for pd_op.pylayer
|
|
)DOC");
|
|
pylayer_op
|
|
.def("forward_block",
|
|
&PyLayerOp::forward_block,
|
|
return_value_policy::reference)
|
|
.def("update_output", &PyLayerOp::UpdateOutput)
|
|
.def("update_input", &PyLayerOp::UpdateInput)
|
|
.def(
|
|
"as_operation", &PyLayerOp::operation, return_value_policy::reference)
|
|
.def("id",
|
|
[](PyLayerOp& self) -> uint64_t { return self.operation()->id(); })
|
|
.def("results",
|
|
[](PyLayerOp& self) -> py::list {
|
|
py::list op_list;
|
|
for (uint32_t i = 0; i < self->num_results(); i++) {
|
|
op_list.append(self.result(i));
|
|
}
|
|
return op_list;
|
|
})
|
|
.def("register_backward_function", [](PyLayerOp& self, py::object func) {
|
|
uint64_t unique_id = self.operation()->id();
|
|
VLOG(2) << "register backward function for op id: " << unique_id;
|
|
paddle::pybind::PythonCallableRegistrar::GetInstance().Register(
|
|
unique_id, func);
|
|
self.operation()->set_attribute(
|
|
"backward_function_id",
|
|
pir::Int32Attribute::get(pir::IrContext::Instance(), unique_id));
|
|
});
|
|
}
|
|
|
|
void BindWhileOp(py::module* m) {
|
|
m->def("build_while_op", [](Value cond, py::list loop_vars) -> PyWhileOp {
|
|
std::vector<Value> loop_values;
|
|
for (auto var : loop_vars) {
|
|
loop_values.push_back(var.cast<Value>());
|
|
}
|
|
return PyWhileOp(
|
|
ApiBuilder::Instance().GetBuilder()->Build<WhileOp>(cond, loop_values));
|
|
});
|
|
py::class_<PyWhileOp> while_op(*m, "WhileOp", R"DOC(
|
|
WhileOp in python api.
|
|
)DOC");
|
|
while_op.def("body", &PyWhileOp::body, return_value_policy::reference)
|
|
.def(
|
|
"as_operation", &PyWhileOp::operation, return_value_policy::reference)
|
|
.def("block_arguments",
|
|
&WhileOp::block_args,
|
|
return_value_policy::reference)
|
|
.def("optimize_update", &PyWhileOp::OptimizeUpdate)
|
|
.def("add_extra_input", &PyWhileOp::AddExtraInput);
|
|
}
|
|
|
|
void BindAssertOp(py::module* m) {
|
|
m->def("build_assert_op",
|
|
[](Value cond, const std::vector<Value>& data, int64_t summarize) {
|
|
auto data_combine_op =
|
|
ApiBuilder::Instance().GetBuilder()->Build<pir::CombineOp>(data);
|
|
return ApiBuilder::Instance().GetBuilder()->Build<AssertOp>(
|
|
cond, data_combine_op.out(), summarize);
|
|
});
|
|
py::class_<AssertOp> assert_op(*m, "AssertOp", R"DOC(
|
|
AssertOp in python api.
|
|
)DOC");
|
|
assert_op.def(
|
|
"as_operation", &AssertOp::operation, return_value_policy::reference);
|
|
}
|
|
|
|
void BindTuplePopOp(py::module* m) {
|
|
py::class_<TuplePopOp> tuple_pop_op(*m, "TuplePopOp", R"DOC(
|
|
TuplePopOp in python api.
|
|
)DOC");
|
|
tuple_pop_op
|
|
.def("as_operation",
|
|
&TuplePopOp::operation,
|
|
return_value_policy::reference)
|
|
.def("pop_all_values",
|
|
[](TuplePopOp& self) -> py::list {
|
|
py::list res;
|
|
for (size_t i = 0; i < self.num_results(); ++i) {
|
|
res.append(self.result(i));
|
|
}
|
|
return res;
|
|
})
|
|
.def(
|
|
"tuple_size", &TuplePopOp::tuple_size, return_value_policy::reference)
|
|
.def("outlet_element",
|
|
&TuplePopOp::outlet_element,
|
|
return_value_policy::reference);
|
|
}
|
|
|
|
void BuildPipeForPyLayer(Block* block, const std::vector<pir::Value>& values) {
|
|
PADDLE_ENFORCE_NOT_NULL(
|
|
block,
|
|
common::errors::InvalidArgument(
|
|
"The block used to hook local value can't be nullptr"));
|
|
auto& builder = *(ApiBuilder::Instance().GetBuilder());
|
|
Program* program = block->parent_program();
|
|
PADDLE_ENFORCE_NOT_NULL(
|
|
program,
|
|
common::errors::InvalidArgument(
|
|
"The block used to hook local value must belong to a program"));
|
|
|
|
auto original_position = builder.insertion_point();
|
|
|
|
builder.SetInsertionPointToStart(program->block());
|
|
auto inlet = builder.Build<StackCreateOp>().inlet();
|
|
auto iter = block->end();
|
|
if (!block->empty() && block->back().isa<YieldOp>()) {
|
|
--iter;
|
|
}
|
|
builder.set_insertion_point(block, iter);
|
|
builder.Build<TuplePushOp>(inlet, values);
|
|
builder.set_insertion_point(original_position);
|
|
}
|
|
|
|
Value BuildHasElementsOp(Operation& fwd_op) { // NOLINT
|
|
PADDLE_ENFORCE(fwd_op.isa<WhileOp>(),
|
|
common::errors::PreconditionNotMet(
|
|
"param op of BuildHasElementsOp must be while op."));
|
|
auto fwdop = fwd_op.dyn_cast<WhileOp>();
|
|
TuplePushOp push_op;
|
|
for (auto iter = fwdop.body().rbegin(); iter != fwdop.body().rend(); ++iter) {
|
|
if (iter->isa<TuplePushOp>()) {
|
|
push_op = iter->dyn_cast<TuplePushOp>();
|
|
PADDLE_ENFORCE_EQ(push_op.container().use_empty(),
|
|
false,
|
|
common::errors::InvalidArgument(
|
|
"The last container in forward while op must used "
|
|
"after construct while_grad op"));
|
|
break;
|
|
}
|
|
}
|
|
auto new_cond = ApiBuilder::Instance()
|
|
.GetBuilder()
|
|
->Build<HasElementsOp>(push_op.container())
|
|
.out();
|
|
return new_cond;
|
|
}
|
|
|
|
void BuildPipeForBlock(Block* block) {
|
|
PADDLE_ENFORCE_NOT_NULL(
|
|
block,
|
|
common::errors::InvalidArgument(
|
|
"The block used to hook local value can't be nullptr"));
|
|
auto& builder = *(ApiBuilder::Instance().GetBuilder());
|
|
Program* program = block->parent_program();
|
|
PADDLE_ENFORCE_NOT_NULL(
|
|
program,
|
|
common::errors::InvalidArgument(
|
|
"The block used to hook local value must belong to a program"));
|
|
|
|
auto original_position = builder.insertion_point();
|
|
|
|
builder.SetInsertionPointToStart(program->block());
|
|
auto inlet = builder.Build<StackCreateOp>().inlet();
|
|
auto iter = block->end();
|
|
if (!block->empty() && block->back().isa<YieldOp>()) {
|
|
--iter;
|
|
}
|
|
std::vector<Value> local_values;
|
|
for (auto arg_value : block->args()) {
|
|
local_values.push_back(arg_value);
|
|
}
|
|
for (auto& op : *block) {
|
|
for (auto result_value : op.results()) {
|
|
local_values.push_back(result_value);
|
|
}
|
|
}
|
|
builder.set_insertion_point(block, iter);
|
|
builder.Build<TuplePushOp>(inlet, local_values);
|
|
builder.set_insertion_point(original_position);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
namespace paddle::pybind {
|
|
PyIfOp::PyIfOp(IfOp if_op) : IfOp(if_op) {
|
|
PADDLE_ENFORCE_NOT_NULL(
|
|
if_op,
|
|
common::errors::InvalidArgument(
|
|
"The if_op used to construct PyIfOp can't be nullptr"));
|
|
}
|
|
|
|
void PyIfOp::UpdateOutput() {
|
|
PADDLE_ENFORCE_NOT_NULL(
|
|
operation_,
|
|
common::errors::InvalidArgument(
|
|
"The if_op in PyIfOp used to update output can't be nullptr"));
|
|
auto block = parent();
|
|
PADDLE_ENFORCE_NOT_NULL(block,
|
|
common::errors::InvalidArgument(
|
|
"The parent block of if_op which used to update "
|
|
"output can't be nullptr"));
|
|
Block::Iterator iter = **this;
|
|
Builder builder(ir_context(), false);
|
|
auto new_if_op = builder.Build<IfOp>(
|
|
cond(), true_region().TakeBack(), false_region().TakeBack());
|
|
block->Assign(iter, new_if_op);
|
|
IfOp::operator=(new_if_op);
|
|
operation_->Verify();
|
|
}
|
|
|
|
PyWhileOp::PyWhileOp(WhileOp while_op) : WhileOp(while_op) {
|
|
PADDLE_ENFORCE_NOT_NULL(
|
|
operation_,
|
|
common::errors::InvalidArgument(
|
|
"The while_op used to construct PyWhileOp can't be nullptr"));
|
|
}
|
|
|
|
void PyWhileOp::AddExtraInput(const pir::Value& value) {
|
|
extra_inputs_.push_back(value);
|
|
}
|
|
|
|
std::vector<Value> PyWhileOp::OptimizeUpdate() {
|
|
PADDLE_ENFORCE_NOT_NULL(operation_,
|
|
common::errors::InvalidArgument(
|
|
"The while_op in PyWhileOp used to remove unused "
|
|
"loop vars can't be nullptr"));
|
|
auto parent_block = parent();
|
|
PADDLE_ENFORCE_NOT_NULL(
|
|
parent_block,
|
|
common::errors::InvalidArgument(
|
|
"The parent block of while_op which used to remove "
|
|
"unused loop vars can't be nullptr"));
|
|
|
|
// Skip verify if operation has extra inputs
|
|
if (extra_inputs_.empty()) {
|
|
operation_->Verify();
|
|
}
|
|
auto& body_block = body();
|
|
auto yield_op = body_block.back().dyn_cast<YieldOp>();
|
|
auto operand_num = operation_->num_operands();
|
|
|
|
PADDLE_ENFORCE_EQ(
|
|
operand_num - 1 + extra_inputs_.size(),
|
|
body_block.args().size(),
|
|
common::errors::InvalidArgument("The number of operands in while_op and "
|
|
"the number of args in body block "
|
|
"should be equal."));
|
|
bool no_change = extra_inputs_.empty();
|
|
std::vector<size_t> index_vec;
|
|
std::vector<Value> res, new_input, new_yield_val{yield_op.operand_source(0)};
|
|
for (uint32_t i = 0; i < num_results(); ++i) {
|
|
res.push_back(result(i));
|
|
}
|
|
for (size_t operand_index = 1u, arg_index = 0u; operand_index < operand_num;
|
|
++operand_index, ++arg_index) {
|
|
if (!body_block.arg(arg_index).type().isa<pir::DenseTensorType>()) {
|
|
continue;
|
|
}
|
|
|
|
auto l_type =
|
|
body_block.arg(arg_index).type().dyn_cast<pir::DenseTensorType>();
|
|
auto r_type = yield_op.operand_source(operand_index)
|
|
.type()
|
|
.dyn_cast<pir::DenseTensorType>();
|
|
if (l_type.dims().size() == r_type.dims().size() &&
|
|
l_type.dims() != r_type.dims()) {
|
|
VLOG(4) << "while op input " << operand_index
|
|
<< " has dynamic shape, origin shape is: " << l_type.dims()
|
|
<< "new shape is: " << r_type.dims();
|
|
auto dim = common::ComputeCompatibleDim(l_type.dims(), r_type.dims());
|
|
auto new_type = pir::DenseTensorType::get(operation_->ir_context(),
|
|
l_type.dtype(),
|
|
dim,
|
|
l_type.data_layout(),
|
|
l_type.lod(),
|
|
l_type.offset());
|
|
body_block.arg(arg_index).set_type(new_type);
|
|
yield_op.operand_source(operand_index).set_type(new_type);
|
|
result(arg_index).set_type(new_type);
|
|
VLOG(4) << "change shape as: " << new_type.dims();
|
|
}
|
|
}
|
|
|
|
for (size_t operand_index = 1u, arg_index = 0u; operand_index < operand_num;
|
|
++operand_index) {
|
|
operand_source(operand_index).set_type(body_block.arg(arg_index).type());
|
|
if (yield_op.operand_source(operand_index) == body_block.arg(arg_index)) {
|
|
body_block.arg(arg_index).ReplaceAllUsesWith(
|
|
operand_source(operand_index));
|
|
body_block.EraseArg(arg_index);
|
|
no_change = false;
|
|
res[operand_index - 1u] = operand_source(operand_index);
|
|
} else {
|
|
new_input.push_back(operand_source(operand_index));
|
|
index_vec.push_back(operand_index - 1u);
|
|
new_yield_val.push_back(yield_op.operand_source(operand_index));
|
|
++arg_index;
|
|
}
|
|
}
|
|
for (size_t extra_input_idx = 0u; extra_input_idx < extra_inputs_.size();
|
|
++extra_input_idx) {
|
|
new_input.push_back(extra_inputs_[extra_input_idx]);
|
|
new_yield_val.push_back(
|
|
yield_op.operand_source(operand_num + extra_input_idx));
|
|
}
|
|
if (no_change) return res;
|
|
Block::Iterator iter = **this;
|
|
Builder builder(ir_context(), false);
|
|
auto new_while_op = builder.Build<WhileOp>(cond(), new_input, false);
|
|
new_while_op->region(0).swap(std::move(operation_->region(0)));
|
|
parent_block->Assign(iter, new_while_op);
|
|
WhileOp::operator=(new_while_op);
|
|
body_block.pop_back();
|
|
builder.SetInsertionPointToBlockEnd(&body_block);
|
|
builder.Build<YieldOp>(new_yield_val);
|
|
operation_->Verify();
|
|
for (size_t result_index = 0;
|
|
result_index < num_results() - extra_inputs_.size();
|
|
++result_index) {
|
|
res[index_vec[result_index]] = result(result_index);
|
|
}
|
|
for (size_t i = 0; i < extra_inputs_.size(); ++i) {
|
|
res.push_back(result(num_results() - extra_inputs_.size() + i));
|
|
}
|
|
return res;
|
|
}
|
|
|
|
void BindControlFlowApi(py::module* m) {
|
|
m->def("get_used_external_value",
|
|
[](const Operation& op) { return pir::GetUsedExternalValue(op); });
|
|
m->def("get_used_external_value",
|
|
[](const Block& block) { return pir::GetUsedExternalValue(block); });
|
|
m->def("build_pipe_for_block", BuildPipeForBlock);
|
|
m->def("build_pipe_for_pylayer", BuildPipeForPyLayer);
|
|
m->def("cf_has_elements", BuildHasElementsOp);
|
|
m->def("cf_yield", [](py::list inputs) {
|
|
std::vector<Value> input_values;
|
|
for (auto input : inputs) {
|
|
input_values.push_back(input.cast<Value>());
|
|
}
|
|
ApiBuilder::Instance().GetBuilder()->Build<YieldOp>(input_values);
|
|
});
|
|
BindIfOp(m);
|
|
BindWhileOp(m);
|
|
BindAssertOp(m);
|
|
BindPyLayerOp(m);
|
|
BindTuplePopOp(m);
|
|
}
|
|
|
|
} // namespace paddle::pybind
|