【PaddlePaddle Hackathon 4】add paddle set_value op (#15888)

* add set_value op

* Support for tensor input

* fix shape error

* refactor for dynamic shape

* update process of target_value_shape and add comments

* support arbitrary steps

* fix

* fix ends_node

* fix and add test cases

* fix error when slice operation return maximum number in int32

* remove redundant function call

* update for minus step

* add constraints for minus inputs

---------

Co-authored-by: mei, yang <yang.mei@intel.com>
This commit is contained in:
Asthestarsfalllandmei, yang authored and GitHub committed 2023-06-05 05:06:05 +02:00
1 parent a0c8fdbd86
commit 14da93c155
4 files changed
+361

No files matched your search

+180
View File
@@ -0,0 +1,180 @@
// // Copyright (C) 2018-2023 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//
#include "default_opset.hpp"
#include "openvino/frontend/paddle/node_context.hpp"
#define INT_MAX 2147483647
namespace ov {
namespace frontend {
namespace paddle {
namespace op {
std::shared_ptr<Node> handle_minus_index(const OutputVector& node, const Output<Node>& dim) {
const auto new_node = std::make_shared<default_opset::Concat>(node, 0);
return new_node;
}
std::shared_ptr<Node> handle_minus_index(const std::vector<int64_t>& node, const Output<Node>& dim) {
const auto new_node = default_opset::Constant::create(element::i64, {node.size()}, node);
return new_node;
}
// std::shared_ptr<Node> handle_maximum_index(Output<Node>& node, const Output<Node>& update_node) {
// const auto maximum_node = default_opset::Constant::create(element::i64, {1}, {INT_MAX});
// const auto mask = std::make_shared<default_opset::Equal>(node, maximum_node);
// return std::make_shared<default_opset::Select>(mask, update_node, node);
// }
bool is_contain_minus(const std::vector<int64_t> vec) {
for (int64_t i : vec) {
if (i < 0)
return true;
}
return false;
}
NamedOutputs set_value(const NodeContext& node) {
auto input_node = node.get_input("Input");
auto value_node = node.get_input("ValueTensor");
PADDLE_OP_CHECK(node, (input_node.get_partial_shape().rank().is_static()), "rank must be static");
const auto dims = static_cast<int64_t>(input_node.get_partial_shape().rank().get_length());
const auto axes = node.get_attribute<std::vector<int64_t>>("axes");
// const auto input_shape_ = input_node.get_partial_shape().get_shape();
// auto input_shape = default_opset::Constant::create(element::i64, {input_shape_.size()}, input_shape_);
auto input_shape = std::make_shared<default_opset::ShapeOf>(input_node);
Output<Node> starts_node, ends_node, steps_node, starts, ends, steps;
// The following process is:
// Given:
// input_data: shape(5, 6, 7, 8, 9)
// update_value: shape(1, 6, 3, 3)
// operation: input_data[:, :, 2: 7: 2, -4: -1] = update_value
// axes = [2, 3]
// starts = [2, -4]
// ends = [7, -1]
// steps = [2, 1]
// Our process is:
// 1. Get axes [2, 3], get shape of input [5, 6, 7, 8, 9], select dimension from shape by axes: [7, 8].
// 2. Get starts [2, -4] and ends [3, -1]. Process minus starts and ends. starts: [2, 4], ends: [7, 7].
// 3. Calculate starts_node, ends_node and steps_node
// 1. Create `starts node` filled with 0. Update `starts` to `starts_node` according to axes.
// starts_node[axes[i]] = starts[i] for i in axes.size
// starts_node: [0, 0, 0, 0, 0] -> [0, 0, 2, 4, 0].
// 2. Update `ends` to `input_shape` according to axes.
// input_shape[axes[i]] = ends[i] for i in axes.size
// ends_node: [5, 6, 7, 8, 9] -> [5, 6, 7, 7, 9].
// 3. Create `steps_node` filled with 1. Update `steps' to `steps_node` according to axes.
// steps_node[axes[i]] = steps[i] for i in axes.size
// steps_node: [1, 1, 1, 1, 1] -> [1, 1, 2, 1, 1].
// 4. Calculate and broadcast update_value to corresponding shape: [5, 6, 5, 3, 9].
// 1. Calculate `end - start`: [5, 3].
// 2. Calculate `(end - start) / abs(step)`: [2.5, 3]
// 3. Calculate `ceil((end - start) / step)`: [3, 3]
// 2. Use `ScatterNDUpdate` to get `target_value_shape` for input_shape: [5, 6, 7, 8, 9] -> [5, 6, 3, 3, 9].
// 3. Broadcast from [1, 6, 3, 3] to [5, 6, 3, 3, 9]
// 5. Create a range_node filled with number from 0 to numel.
// 1. Reshape it to input_shape.
// 2. Use `StridedSlice` to get those indices which are about to be updated.
// 6. Flatten input, update_value and sliced_range.
// 7. Use `ScatterUpdate` update update_value into input_data.
// 8. Reshape input to original input_shape.
const auto axes_node = default_opset::Constant::create(element::i64, {axes.size(), 1}, axes);
const auto spec_dim_node = std::make_shared<default_opset::GatherND>(input_shape, axes_node);
const auto zero_node = default_opset::Constant::create(element::i64, Shape{}, {0});
const auto one_node = default_opset::Constant::create(element::i64, Shape{}, {1});
const auto dim_node = default_opset::Constant::create(element::i64, Shape{}, {dims});
const auto reshape_flatten = default_opset::Constant::create(ov::element::i64, {1}, {-1});
const auto slice_shape = default_opset::Constant::create(ov::element::i64, {1, 1}, {-1});
// get positive starts ends and steps
if (node.has_input("StartsTensorList") && node.has_input("StepsTensorList") && node.has_input("EndsTensorList")) {
starts = handle_minus_index(node.get_ng_inputs("StartsTensorList"), spec_dim_node);
ends = handle_minus_index(node.get_ng_inputs("EndsTensorList"), spec_dim_node);
steps = std::make_shared<default_opset::Concat>(node.get_ng_inputs("StepsTensorList"), 0);
} else if (node.has_attribute("starts") && node.has_attribute("steps") && node.has_attribute("ends")) {
const auto start_vec = node.get_attribute<std::vector<int64_t>>("starts");
const auto ends_vec = node.get_attribute<std::vector<int64_t>>("ends");
const auto step_vec = node.get_attribute<std::vector<int64_t>>("steps");
if (is_contain_minus(start_vec) || is_contain_minus(ends_vec) || is_contain_minus(step_vec)) {
PADDLE_OP_CHECK(node, (false), "Currently not support minus start, ends and steps!");
}
starts = handle_minus_index(start_vec, spec_dim_node);
ends = handle_minus_index(ends_vec, spec_dim_node);
steps = default_opset::Constant::create(element::i64, {step_vec.size()}, step_vec);
} else
PADDLE_OP_CHECK(node, (false), "Invalid arguments!");
// for unsepcified end: x[::2], end will be 2147483647
// ends = handle_maximum_index(ends, spec_dim_node);
// 3.1 get starts node
starts_node =
default_opset::Constant::create(element::i64, {static_cast<size_t>(dims)}, std::vector<int64_t>(dims));
starts_node = std::make_shared<default_opset::ScatterNDUpdate>(starts_node, axes_node, starts);
// 3.2 get ends node
ends_node = std::make_shared<default_opset::ScatterNDUpdate>(input_shape, axes_node, ends);
// 3.3 get steps node
steps_node =
default_opset::Constant::create(element::i64, {static_cast<size_t>(dims)}, std::vector<int64_t>(dims, 1));
steps_node = std::make_shared<default_opset::ScatterNDUpdate>(steps_node, axes_node, steps);
// 4.get target value shape
// 4.1 end - start
Output<Node> value_shape_update_node = std::make_shared<default_opset::Subtract>(ends, starts);
// 4.2 ( end - start ) / step
value_shape_update_node = std::make_shared<default_opset::Convert>(value_shape_update_node, element::f32);
// We don't need to process the the minus number in steps
// if step < 0, end - start < 0, (end - start) / step > 0
steps = std::make_shared<default_opset::Convert>(steps, element::f32);
value_shape_update_node = std::make_shared<default_opset::Divide>(value_shape_update_node, steps);
// 4.3 ceil(( end - start ) / step)
value_shape_update_node = std::make_shared<default_opset::Ceiling>(value_shape_update_node);
value_shape_update_node = std::make_shared<default_opset::Convert>(value_shape_update_node, element::i64);
// 4.4 update
const auto value_target_shape =
std::make_shared<default_opset::ScatterNDUpdate>(input_shape, axes_node, value_shape_update_node);
// 4.5 broadcast
value_node = std::make_shared<default_opset::Broadcast>(value_node, value_target_shape);
// get total number of elements
const auto numel_node = std::make_shared<default_opset::ReduceProd>(input_shape, zero_node);
// generate indices from 0 to numel - 1
Output<Node> range_node = std::make_shared<default_opset::Range>(zero_node, numel_node, one_node, element::i64);
// reshape to input_shape
range_node = std::make_shared<default_opset::Reshape>(range_node, input_shape, true);
// slice range node, get the indices that to be updated
Output<Node> sliced_range_node = std::make_shared<default_opset::StridedSlice>(range_node,
starts_node,
ends_node,
steps_node,
std::vector<int64_t>(dims),
std::vector<int64_t>(dims));
// flatten input, upadte_value and sliced_range_node
input_node = std::make_shared<default_opset::Reshape>(input_node, reshape_flatten, true);
sliced_range_node = std::make_shared<default_opset::Reshape>(sliced_range_node, reshape_flatten, true);
value_node = std::make_shared<default_opset::Reshape>(value_node, reshape_flatten, true);
// update value to input according to sliced_range_node
sliced_range_node = std::make_shared<default_opset::Unsqueeze>(sliced_range_node, one_node);
input_node = std::make_shared<default_opset::ScatterNDUpdate>(input_node, sliced_range_node, value_node);
// reshape to original shape
return node.default_single_output_mapping({std::make_shared<default_opset::Reshape>(input_node, input_shape, true)},
{"Out"});
};
} // namespace op
} // namespace paddle
} // namespace frontend
} // namespace ov
+2
View File
@@ -94,6 +94,7 @@ OP_CONVERTER(rnn);
OP_CONVERTER(roi_align);
OP_CONVERTER(scale);
OP_CONVERTER(select_input);
OP_CONVERTER(set_value);
OP_CONVERTER(shape);
OP_CONVERTER(slice);
OP_CONVERTER(softmax);
@@ -217,6 +218,7 @@ std::map<std::string, CreatorFunction> get_supported_ops() {
{"roi_align", op::roi_align},
{"scale", op::scale},
{"select_input", op::select_input},
{"set_value", op::set_value},
{"shape", op::shape},
{"slice", op::slice},
{"softmax", op::softmax},
+9
View File
@@ -464,6 +464,15 @@ static const std::vector<std::string> models{
std::string("scale_bias_before_int64"),
std::string("scale_tensor_bias_after"),
std::string("scale_tensor_bias_before"),
std::string("set_value1"),
std::string("set_value2"),
std::string("set_value3"),
std::string("set_value4"),
std::string("set_value5"),
// std::string("set_value6"),
// std::string("set_value7"),
// std::string("set_value_dynamic1"),
std::string("set_value_dynamic2"),
std::string("shape"),
std::string("sigmoid"),
std::string("silu_static_test1"),
@@ -0,0 +1,170 @@
# Copyright (C) 2018-2023 Intel Corporation
# SPDX-License-Identifier: Apache-2.0
import sys
#
# set_value paddle model generator
#
import numpy as np
import paddle
from save_model import saveModel
def concat(data):
data = [np.expand_dims(d, 0) for d in data]
return np.concatenate(data, axis=0)
def paddle_set_value(name: str, x, value, callback, dtype, starts=None, ends=None, steps=None, is_dynamic=False):
paddle.enable_static()
with paddle.static.program_guard(paddle.static.Program(), paddle.static.Program()):
node_x = paddle.static.data(name="x", shape=x.shape, dtype=dtype)
value_shape = (0,) if isinstance(value, (int, float)) else value.shape
value_shape = (-1,) * len(value_shape) if is_dynamic else value_shape
node_v = paddle.static.data(name="v", shape=value_shape, dtype=dtype)
cpu = paddle.static.cpu_places(1)
exe = paddle.static.Executor(cpu[0])
# startup program will call initializer to initialize the parameters.
exe.run(paddle.static.default_startup_program())
feed = {"x": x, "v": value}
inputs = [x, value]
if starts is None:
out = callback(paddle.clone(node_x), node_v)
else:
input_starts = concat(starts)
input_ends = concat(ends)
input_steps = concat(steps)
node_starts = paddle.assign(input_starts)
node_ends = paddle.assign(input_ends)
node_steps = paddle.assign(input_steps)
out = callback(paddle.clone(node_x), node_v, node_starts, node_ends, node_steps)
outs = exe.run(feed=feed, fetch_list=[out])
saveModel(name, exe, feedkeys=list(feed.keys()), fetchlist=[out], inputs=inputs, outputs=[outs[0]], target_dir=sys.argv[1])
def build_slice(starts, ends, steps) -> list:
outs = []
for st, ed, step in zip(starts, ends, steps):
outs.append(slice(st, ed, step))
return outs
def generate_data(data, dtype):
return [np.array(d).astype(dtype) for d in data]
def main():
shape = (2, 3, 4, 5)
dtype = "float32"
data = np.random.random(shape).astype(dtype)
value = np.array([0]).astype(dtype)
def set_value1(x, value):
x[1:2, 0:4:2, 2:4] = value
return x
paddle_set_value("set_value1", data, value, set_value1, dtype)
shape = (5, 3, 1)
dtype = "float32"
data = np.random.random(shape).astype("float32")
value = np.random.random((3, 3, 1)).astype(dtype)
def set_value2(x, value):
x[2:5] = value
return x
paddle_set_value("set_value2", data, value, set_value2, dtype)
shape = (10, 2, 5)
dtype = "int32"
data = np.random.randint(0, 5, shape).astype(dtype)
value = np.random.randint(0, 2, (10, 2, 3)).astype(dtype)
def set_value3(x, value):
x[:, :, 1:4] = value
return x
paddle_set_value("set_value3", data, value, set_value3, dtype)
shape = (10, 2, 5)
dtype = "float32"
data = np.random.randn(*shape).astype(dtype)
value = np.random.randn(3, 1, 1).astype(dtype)
starts = generate_data([-4, 0, 1], np.int64)
ends = generate_data([-1, 1, 3], np.int64)
steps = generate_data([1, 1, 1], np.int64)
def set_value4(x, value, *slice):
x[build_slice(*slice)] = value
return x
paddle_set_value("set_value4", data, value, set_value4, dtype, starts, ends, steps)
shape = (10, 5)
dtype = "int32"
data = np.random.randint(0, 5, shape).astype(dtype)
value = np.random.randint(0, 2, (1, )).astype(dtype)
starts = generate_data([-4], np.int64)
ends = generate_data([-1], np.int64)
steps = generate_data([1], np.int64)
def set_value5(x, value, *slice):
x[build_slice(*slice)] = value
return x
paddle_set_value("set_value5", data, value, set_value5, dtype, starts, ends, steps)
# shape = (17, 19)
# dtype = "float32"
# data = np.random.randn(*shape).astype(dtype)
# value = np.random.randn(1).astype(dtype)
# def set_value_step(x, value):
# x[::4, 2:-1:5] = value
# return x
# paddle_set_value("set_value6", data, value, set_value_step, dtype)
# shape = (7, 9)
# dtype = "int32"
# data = np.random.randint(0, 5, shape).astype(dtype)
# value = np.random.randint(0, 2, (1, )).astype(dtype)
# def set_value_step1(x, value):
# x[::2, 2:7:5] = value
# return x
# paddle_set_value("set_value7", data, value, set_value_step1, dtype)
# shape = (10, 5)
# dtype = "float32"
# data = np.random.randint(0, 5, shape).astype(dtype)
# value = np.random.randint(0, 2, (10, 3)).astype(dtype)
# def set_value6(x, value):
# x[:, -4:-1] = value
# return x
# paddle_set_value("set_value_dynamic1", data, value, set_value6, dtype, is_dynamic=True)
shape = (10, 5)
dtype = "int32"
data = np.random.randint(0, 5, shape).astype(dtype)
value = np.random.randint(0, 2, (1, )).astype(dtype)
starts = generate_data([-4], np.int64)
ends = generate_data([-1], np.int64)
steps = generate_data([1], np.int64)
def set_value7(x, value, *slice):
x[build_slice(*slice)] = value
return x
paddle_set_value("set_value_dynamic2", data, value, set_value7, dtype, starts, ends, steps, is_dynamic=True)
if __name__ == "__main__":
main()