Skip to content

Commit fc02b1e

Browse files
authored
[CustomOP Inplace] Automap inplace dtype and shape, prepare for vector<Tensor> output (#52214)
* [CustomOP Inplace] Automap inplace dtype and shape, prepare for vector<Tensor> output * delete dtype,shape func of multi_inplace op * [CustomOP Inplace] Automap inplace dtype and shape, support vector<Tensor> output * [CustomOP Inplace] Auto-generate python API for inplace vector<Tensor> output
1 parent 225f1af commit fc02b1e

File tree

5 files changed

+144
-80
lines changed

5 files changed

+144
-80
lines changed

paddle/fluid/pybind/eager_functions.cc

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ typedef SSIZE_T ssize_t;
2222
#endif
2323

2424
#include <string>
25+
#include <unordered_map>
2526
#include <vector>
2627

2728
#include "paddle/fluid/eager/accumulation/accumulation_node.h"
@@ -50,6 +51,7 @@ typedef SSIZE_T ssize_t;
5051
#include "paddle/phi/core/dense_tensor.h"
5152
#include "paddle/phi/core/sparse_coo_tensor.h"
5253
#include "paddle/phi/core/sparse_csr_tensor.h"
54+
#include "paddle/utils/string/string_helper.h"
5355
#include "pybind11/numpy.h"
5456
#include "pybind11/pybind11.h"
5557

@@ -489,6 +491,49 @@ static PyObject* eager_api_jit_function_call(PyObject* self,
489491
EAGER_CATCH_AND_THROW_RETURN_NULL
490492
}
491493

494+
static PyObject* eager_api__get_custom_operator_inplace_reverse_idx(
495+
PyObject* self, PyObject* args, PyObject* kwargs) {
496+
EAGER_TRY
497+
std::string op_type = CastPyArg2AttrString(PyTuple_GET_ITEM(args, 0), 0);
498+
auto meta_info_map = egr::Controller::Instance().GetOpMetaInfoMap();
499+
PADDLE_ENFORCE_NE(meta_info_map.find(op_type),
500+
meta_info_map.end(),
501+
paddle::platform::errors::NotFound(
502+
"Can't find %s in Eager OpMetaInfoMap which should be "
503+
"created by LoadOpMetaInfoAndRegisterOp, please make "
504+
"sure you registered your op first and try again. ",
505+
op_type));
506+
507+
const auto& inputs =
508+
paddle::OpMetaInfoHelper::GetInputs(meta_info_map.at(op_type)[0]);
509+
const auto& outputs =
510+
paddle::OpMetaInfoHelper::GetOutputs(meta_info_map.at(op_type)[0]);
511+
const auto& inplace_map =
512+
paddle::OpMetaInfoHelper::GetInplaceMap(meta_info_map.at(op_type)[0]);
513+
VLOG(7) << "Custom operator " << op_type
514+
<< " get InplaceMap for python, inplace map size = "
515+
<< inplace_map.size();
516+
517+
std::unordered_map<int, int> inplace_idx_map;
518+
for (size_t in_idx = 0; in_idx < inputs.size(); ++in_idx) {
519+
auto& input = inputs[in_idx];
520+
if (inplace_map.find(input) == inplace_map.end()) {
521+
continue;
522+
}
523+
auto out_iter = find(outputs.begin(), outputs.end(), inplace_map.at(input));
524+
PADDLE_ENFORCE(
525+
out_iter != outputs.end(),
526+
phi::errors::NotFound("Can't find the mapped value of %s, please check "
527+
"the input of `Inplace` again and make "
528+
"sure you registered your op accurately. ",
529+
input));
530+
inplace_idx_map[distance(outputs.begin(), out_iter)] = in_idx;
531+
}
532+
533+
return ToPyObject(inplace_idx_map);
534+
EAGER_CATCH_AND_THROW_RETURN_NULL
535+
}
536+
492537
static PyObject* eager_api_run_custom_op(PyObject* self,
493538
PyObject* args,
494539
PyObject* kwargs) {
@@ -1133,6 +1178,11 @@ PyMethodDef variable_functions[] = {
11331178
(PyCFunction)(void (*)(void))eager_api_run_partial_grad,
11341179
METH_VARARGS | METH_KEYWORDS,
11351180
NULL},
1181+
{"_get_custom_operator_inplace_map",
1182+
(PyCFunction)(void (*)(
1183+
void))eager_api__get_custom_operator_inplace_reverse_idx,
1184+
METH_VARARGS | METH_KEYWORDS,
1185+
NULL},
11361186
{"_run_custom_op",
11371187
(PyCFunction)(void (*)(void))eager_api_run_custom_op,
11381188
METH_VARARGS | METH_KEYWORDS,

paddle/fluid/pybind/eager_utils.cc

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -810,6 +810,27 @@ PyObject* ToPyObject(const void* value) {
810810
platform::errors::Fatal("ToPyObject do not support void* with value."));
811811
}
812812

813+
PyObject* ToPyObject(const std::unordered_map<int, int>& value) {
814+
PyObject* dict = PyDict_New();
815+
for (const auto& map_iter : value) {
816+
// Convert Key
817+
PyObject* key = ToPyObject(map_iter.first);
818+
// Convert Value
819+
PyObject* value = ToPyObject(map_iter.second);
820+
821+
if (!key || !value) {
822+
PADDLE_THROW(
823+
platform::errors::Fatal("Unable to convert int to PyObject"));
824+
}
825+
826+
if (PyDict_SetItem(dict, key, value) != 0) {
827+
PADDLE_THROW(
828+
platform::errors::Fatal("Unable to set key:value for py_dict"));
829+
}
830+
}
831+
return dict;
832+
}
833+
813834
PyObject* ToPyObject(
814835
const std::unordered_map<std::string, std::vector<std::string>>& value) {
815836
PyObject* dict = PyDict_New();

paddle/fluid/pybind/eager_utils.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ PyObject* ToPyObject(const phi::SelectedRows* value);
110110
PyObject* ToPyObject(const paddle::framework::proto::VarType::Type& dtype);
111111
PyObject* ToPyObject(const paddle::framework::proto::VarType& type);
112112
PyObject* ToPyObject(const void* value);
113+
PyObject* ToPyObject(const std::unordered_map<int, int>& value);
113114
PyObject* ToPyObject(
114115
const std::unordered_map<std::string, std::vector<std::string>>& value);
115116
PyObject* ToPyObject(const paddle::framework::Vocab& value);

python/paddle/utils/cpp_extension/extension_utils.py

Lines changed: 72 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -950,12 +950,12 @@ def parse_op_info(op_name):
950950
op_proto = OpProtoHolder.instance().get_op_proto(op_name)
951951

952952
in_names = [x.name for x in op_proto.inputs]
953-
out_names = [x.name for x in op_proto.outputs]
954953
attr_names = [
955954
x.name for x in op_proto.attrs if x.name not in DEFAULT_OP_ATTR_NAMES
956955
]
956+
out_names = [x.name for x in op_proto.outputs]
957957

958-
return in_names, out_names, attr_names
958+
return in_names, attr_names, out_names
959959

960960

961961
def _import_module_from_library(module_name, build_directory, verbose=False):
@@ -1038,28 +1038,58 @@ def remove_if_exit(filepath):
10381038
return custom_module
10391039

10401040

1041+
def _gen_output_content(in_names, out_names, inplace_reverse_idx):
1042+
# ' ' * tab space * tab number
1043+
indent = ' ' * 4 * 2
1044+
dynamic_content = ""
1045+
static_content = ""
1046+
for out_idx, out_name in enumerate(out_names):
1047+
in_idx = -1
1048+
if out_idx in inplace_reverse_idx:
1049+
in_idx = inplace_reverse_idx[out_idx]
1050+
if in_idx != -1 and "@VECTOR" in in_names[in_idx]:
1051+
lower_in_names = in_names[in_idx].split("@")[0].lower()
1052+
dynamic_content += f"""
1053+
{indent}outs['{out_name}'] = [core.eager.Tensor() for _ in range(len({lower_in_names}))]
1054+
{indent}ctx.add_outputs(outs['{out_name}'])"""
1055+
static_content += f"""
1056+
{indent}outs['{out_name}'] = [helper.create_variable(dtype='float32') for _ in range(len({lower_in_names}))]"""
1057+
else:
1058+
dynamic_content += f"""
1059+
{indent}outs['{out_name}'] = core.eager.Tensor()
1060+
{indent}ctx.add_outputs(outs['{out_name}'])"""
1061+
static_content += f"""
1062+
{indent}outs['{out_name}'] = helper.create_variable(dtype='float32')"""
1063+
1064+
return dynamic_content, static_content
1065+
1066+
10411067
def _custom_api_content(op_name):
10421068
(
1043-
params_str,
1044-
ins_str,
1045-
attrs_str,
1046-
outs_str,
1069+
params_list,
1070+
ins_map,
1071+
attrs_map,
1072+
outs_list,
10471073
in_names,
1048-
attrs_names,
1074+
attr_names,
1075+
out_names,
1076+
inplace_reverse_idx,
10491077
) = _get_api_inputs_str(op_name)
1050-
lower_in_names = [p.split("@")[0].lower() for p in in_names]
1078+
dynamic_content, static_content = _gen_output_content(
1079+
in_names, out_names, inplace_reverse_idx
1080+
)
1081+
lower_in_list = [p.split("@")[0].lower() for p in in_names]
10511082
API_TEMPLATE = textwrap.dedent(
10521083
"""
10531084
import paddle.fluid.core as core
10541085
from paddle.fluid.core import VarBase, CustomOpKernelContext
10551086
from paddle.fluid.framework import _dygraph_tracer, in_dygraph_mode
10561087
from paddle.fluid.layer_helper import LayerHelper
10571088
1058-
def {op_name}({inputs}):
1089+
def {op_name}({params_list}):
10591090
# prepare inputs and outputs
1060-
attrs = {attrs}
10611091
outs = {{}}
1062-
out_names = {out_names}
1092+
outs_list = {outs_list}
10631093
10641094
# The output variable's dtype use default value 'float32',
10651095
# and the actual dtype of output variable will be inferred in runtime.
@@ -1069,23 +1099,19 @@ def {op_name}({inputs}):
10691099
ctx.add_inputs(i)
10701100
for j in {attr_names}:
10711101
ctx.add_attr(j)
1072-
for out_name in out_names:
1073-
outs[out_name] = core.eager.Tensor()
1074-
ctx.add_outputs(outs[out_name])
1102+
{dynamic_content}
10751103
core.eager._run_custom_op(ctx, "{op_name}", True)
10761104
else:
10771105
ins = {{}}
1078-
for key, value in dict({ins}).items():
1106+
for key, value in dict({ins_map}).items():
10791107
# handle optional inputs
10801108
if value is not None:
10811109
ins[key] = value
10821110
helper = LayerHelper("{op_name}", **locals())
1083-
for out_name in out_names:
1084-
outs[out_name] = helper.create_variable(dtype='float32')
1085-
1086-
helper.append_op(type="{op_name}", inputs=ins, outputs=outs, attrs=attrs)
1111+
{static_content}
1112+
helper.append_op(type="{op_name}", inputs=ins, outputs=outs, attrs={attrs_map})
10871113
1088-
res = [outs[out_name] for out_name in out_names]
1114+
res = [outs[out_name] for out_name in outs_list]
10891115
10901116
return res[0] if len(res)==1 else res
10911117
"""
@@ -1094,13 +1120,15 @@ def {op_name}({inputs}):
10941120
# generate python api file
10951121
api_content = API_TEMPLATE.format(
10961122
op_name=op_name,
1097-
inputs=params_str,
1098-
ins=ins_str,
1099-
attrs=attrs_str,
1123+
params_list=params_list,
1124+
ins_map=ins_map,
1125+
attrs_map=attrs_map,
11001126
# "[x, y, z]""
1101-
in_names="[" + ",".join(lower_in_names) + "]",
1102-
attr_names="[" + ",".join(attrs_names) + "]",
1103-
out_names=outs_str,
1127+
in_names="[" + ",".join(lower_in_list) + "]",
1128+
attr_names="[" + ",".join(attr_names) + "]",
1129+
outs_list=outs_list,
1130+
dynamic_content=dynamic_content,
1131+
static_content=static_content,
11041132
)
11051133

11061134
return api_content
@@ -1132,30 +1160,42 @@ def _get_api_inputs_str(op_name):
11321160
"""
11331161
Returns string of api parameters and inputs dict.
11341162
"""
1135-
in_names, out_names, attr_names = parse_op_info(op_name)
1163+
in_names, attr_names, out_names = parse_op_info(op_name)
11361164
# e.g: x, y, z
11371165
param_names = in_names + attr_names
11381166
# NOTE(chenweihang): we add suffix `@VECTOR` for std::vector<Tensor> input,
11391167
# but the string contains `@` cannot used as argument name, so we split
11401168
# input name by `@`, and only use first substr as argument
1141-
params_str = ','.join([p.split("@")[0].lower() for p in param_names])
1169+
params_list = ','.join([p.split("@")[0].lower() for p in param_names])
11421170
# e.g: {'X': x, 'Y': y, 'Z': z}
1143-
ins_str = "{%s}" % ','.join(
1171+
ins_map = "{%s}" % ','.join(
11441172
[
11451173
"'{}' : {}".format(in_name, in_name.split("@")[0].lower())
11461174
for in_name in in_names
11471175
]
11481176
)
11491177
# e.g: {'num': n}
1150-
attrs_str = "{%s}" % ",".join(
1178+
attrs_map = "{%s}" % ",".join(
11511179
[
11521180
"'{}' : {}".format(attr_name, attr_name.split("@")[0].lower())
11531181
for attr_name in attr_names
11541182
]
11551183
)
11561184
# e.g: ['Out', 'Index']
1157-
outs_str = "[%s]" % ','.join(["'{}'".format(name) for name in out_names])
1158-
return params_str, ins_str, attrs_str, outs_str, in_names, attr_names
1185+
outs_list = "[%s]" % ','.join(["'{}'".format(name) for name in out_names])
1186+
1187+
inplace_reverse_idx = core.eager._get_custom_operator_inplace_map(op_name)
1188+
1189+
return (
1190+
params_list,
1191+
ins_map,
1192+
attrs_map,
1193+
outs_list,
1194+
in_names,
1195+
attr_names,
1196+
out_names,
1197+
inplace_reverse_idx,
1198+
)
11591199

11601200

11611201
def _write_setup_file(

test/custom_op/test_custom_inplace.py

Lines changed: 0 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -40,54 +40,6 @@
4040
verbose=True,
4141
)
4242

43-
# Temporarily assemble custom python API
44-
from paddle.fluid import core
45-
from paddle.fluid.core import CustomOpKernelContext
46-
from paddle.fluid.framework import in_dygraph_mode
47-
from paddle.fluid.layer_helper import LayerHelper
48-
49-
50-
def custom_add_vec(x_vector, y):
51-
# prepare inputs and outputs
52-
attrs = {}
53-
outs = {}
54-
out_names = ["Out@VECTOR"]
55-
56-
# The output variable's dtype use default value 'float32',
57-
# and the actual dtype of output variable will be inferred in runtime.
58-
if in_dygraph_mode():
59-
ctx = CustomOpKernelContext()
60-
for i in [x_vector, y]:
61-
ctx.add_inputs(i)
62-
for out_name in out_names:
63-
outs[out_name] = [core.eager.Tensor() for _ in range(len(x_vector))]
64-
ctx.add_outputs(outs[out_name])
65-
core.eager._run_custom_op(ctx, "custom_add_vec", True)
66-
else:
67-
ins = {}
68-
for key, value in dict({"X@VECTOR": x_vector, "Y": y}).items():
69-
# handle optional inputs
70-
if value is not None:
71-
ins[key] = value
72-
helper = LayerHelper("custom_add_vec", **locals())
73-
for out_name in out_names:
74-
outs[out_name] = [
75-
helper.create_variable(dtype='float32')
76-
for _ in range(len(x_vector))
77-
]
78-
79-
helper.append_op(
80-
type="custom_add_vec", inputs=ins, outputs=outs, attrs=attrs
81-
)
82-
83-
res = [outs[out_name] for out_name in out_names]
84-
85-
return res[0] if len(res) == 1 else res
86-
87-
88-
# Set custom python API manually
89-
custom_inplace.custom_add_vec = custom_add_vec
90-
9143

9244
def inplace_dynamic_add(phi_func, device, dtype, np_x, np_y):
9345
paddle.set_device(device)

0 commit comments

Comments
 (0)