Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

【PaddlePaddle Hackathon 4】add paddle set_value op #15888

Merged
merged 22 commits into from
Jun 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
180 changes: 180 additions & 0 deletions src/frontends/paddle/src/op/set_value.cpp
Original file line number Diff line number Diff line change
@@ -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
ilya-lavrenov marked this conversation as resolved.
Show resolved Hide resolved

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")) {
meiyang-intel marked this conversation as resolved.
Show resolved Hide resolved
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 changes: 2 additions & 0 deletions src/frontends/paddle/src/op_table.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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},
Expand Down
9 changes: 9 additions & 0 deletions src/frontends/paddle/tests/op_fuzzy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand Down
Original file line number Diff line number Diff line change
@@ -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

meiyang-intel marked this conversation as resolved.
Show resolved Hide resolved
paddle_set_value("set_value_dynamic2", data, value, set_value7, dtype, starts, ends, steps, is_dynamic=True)

if __name__ == "__main__":
main()