【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:
1 parent
a0c8fdbd86
commit
14da93c155
4 files changed
+361
No files matched your search
@@ -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
|
||||
@@ -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},
|
||||
|
||||
@@ -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()
|
||||
Reference in new issue
Block a user