Commit f09b512e authored by baojun's avatar baojun Committed by Scott Cyphers

Add partial slice fused op for pdpd use (#3759)

* add partial slice op placeholder

* fprop implemented

* add a dynamic ut

* implement bprop

* support decrease axis

* add bprop ut

* address comment

* use descriptive var name
parent 1d36daea
......@@ -350,6 +350,8 @@ set (SRC
op/fused/mvn.hpp
op/fused/normalize_l2.cpp
op/fused/normalize_l2.hpp
op/fused/partial_slice.cpp
op/fused/partial_slice.hpp
op/fused/prelu.cpp
op/fused/prelu.hpp
op/fused/rnn_cell.cpp
......
......@@ -142,6 +142,7 @@ namespace ngraph
#include "ngraph/op/fused/matmul.hpp"
#include "ngraph/op/fused/mvn.hpp"
#include "ngraph/op/fused/normalize_l2.hpp"
#include "ngraph/op/fused/partial_slice.hpp"
#include "ngraph/op/fused/prelu.hpp"
#include "ngraph/op/fused/rnn_cell.hpp"
#include "ngraph/op/fused/scale_shift.hpp"
......
//*****************************************************************************
// Copyright 2017-2019 Intel Corporation
//
// 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 <cmath>
#include <numeric>
#include "ngraph/builder/make_constant.hpp"
#include "ngraph/op/constant.hpp"
#include "ngraph/op/fused/partial_slice.hpp"
#include "ngraph/op/replace_slice.hpp"
#include "ngraph/op/reshape.hpp"
#include "ngraph/op/slice.hpp"
using namespace std;
using namespace ngraph;
constexpr NodeTypeInfo op::PartialSlice::type_info;
constexpr NodeTypeInfo op::PartialSliceBackprop::type_info;
op::PartialSlice::PartialSlice(const Output<Node>& data,
const AxisVector& axes,
const std::vector<int64_t>& lower_bounds,
const std::vector<int64_t>& upper_bounds,
const AxisVector& decrease_axes)
: FusedOp({data})
, m_axes(axes)
, m_lower_bounds(lower_bounds)
, m_upper_bounds(upper_bounds)
, m_decrease_axes(decrease_axes)
{
constructor_validate_and_infer_types();
}
// All input shape should be static by this point
NodeVector op::PartialSlice::decompose_op() const
{
const PartialShape& data_pshape = get_input_partial_shape(0);
if (data_pshape.is_dynamic())
{
throw ngraph_error("Data needs to have static shape to decompose");
}
auto data = input_value(0);
auto data_shape = data.get_shape();
auto axes = get_axes();
auto starts = get_lower_bounds();
auto ends = get_upper_bounds();
auto decrease_axes = get_decrease_axes();
Coordinate ng_start, ng_end;
int axis_length, start, end;
for (size_t i = 0; i < data_shape.size(); ++i)
{
ng_start.push_back(0);
ng_end.push_back(data_shape[i]);
}
for (size_t i = 0; i < axes.size(); ++i)
{
axis_length = data_shape[axes[i]];
start = starts[i] < 0 ? (starts[i] + axis_length) : starts[i];
end = ends[i] < 0 ? (ends[i] + axis_length) : ends[i];
start = max(start, 0);
end = max(end, 0);
start = min(start, axis_length);
end = min(end, axis_length);
start = min(start, end);
ng_start[axes[i]] = start;
ng_end[axes[i]] = end;
}
auto sliced = std::make_shared<op::Slice>(data, ng_start, ng_end);
auto out_shape = sliced->get_shape();
Shape out_reshape_shape{};
if (decrease_axes.size() > 0)
{
auto new_out_shape = out_shape;
for (size_t i = 0; i < decrease_axes.size(); ++i)
{
int idx = decrease_axes[i];
NGRAPH_CHECK(out_shape[idx] == 1, "Decrease dim should be 1");
new_out_shape[idx] = 0;
}
for (size_t i = 0; i < out_shape.size(); ++i)
{
if (new_out_shape[i] != 0)
{
out_reshape_shape.push_back(out_shape[i]);
}
}
if (out_reshape_shape.size() == 0)
{
out_reshape_shape.push_back(1);
}
}
else
{
out_reshape_shape = out_shape;
}
auto out =
std::make_shared<op::Reshape>(sliced, get_default_order(out_shape), out_reshape_shape);
return {out};
}
shared_ptr<Node> op::PartialSlice::copy_with_new_args(const NodeVector& new_args) const
{
if (new_args.size() != 1)
{
throw ngraph_error("Incorrect number of new arguments");
}
return make_shared<PartialSlice>(
new_args.at(0), m_axes, m_lower_bounds, m_upper_bounds, m_decrease_axes);
}
void op::PartialSlice::pre_validate_and_infer_types()
{
element::Type input_element_type = get_input_element_type(0);
NODE_VALIDATION_CHECK(this,
input_element_type.is_dynamic() || input_element_type.is_real(),
"Argument element type must be f16, bf16, f32, f64 or dynamic (got ",
input_element_type,
").");
}
void op::PartialSlice::generate_adjoints(autodiff::Adjoints& adjoints, const NodeVector& deltas)
{
throw ngraph_error("op::PartialSlice::generate_adjoints function is not implemented yet");
}
op::PartialSliceBackprop::PartialSliceBackprop(const Output<Node>& data,
const Output<Node>& dout,
const AxisVector& axes,
const std::vector<int64_t>& lower_bounds,
const std::vector<int64_t>& upper_bounds)
: FusedOp({data, dout})
, m_axes(axes)
, m_lower_bounds(lower_bounds)
, m_upper_bounds(upper_bounds)
{
constructor_validate_and_infer_types();
}
// All input shape should be static by this point
NodeVector op::PartialSliceBackprop::decompose_op() const
{
const PartialShape& data_pshape = get_input_partial_shape(0);
if (data_pshape.is_dynamic())
{
throw ngraph_error("Data needs to have static shape to decompose");
}
auto data = input_value(0);
auto dout = input_value(1);
auto data_shape = data.get_shape();
auto axes = get_axes();
auto starts = get_lower_bounds();
auto ends = get_upper_bounds();
Coordinate ng_start, ng_end;
int axis_length, start, end;
auto reshape = data_shape;
for (size_t i = 0; i < data_shape.size(); ++i)
{
ng_start.push_back(0);
ng_end.push_back(data_shape[i]);
}
for (size_t i = 0; i < axes.size(); ++i)
{
axis_length = data_shape[axes[i]];
start = starts[i] < 0 ? (starts[i] + axis_length) : starts[i];
end = ends[i] < 0 ? (ends[i] + axis_length) : ends[i];
start = max(start, 0);
end = max(end, 0);
start = min(start, axis_length);
end = min(end, axis_length);
start = min(start, end);
ng_start[axes[i]] = start;
ng_end[axes[i]] = end;
reshape[axes[i]] = end - start;
}
auto dout_reshape =
std::make_shared<op::Reshape>(dout, get_default_order(dout.get_shape()), reshape);
std::shared_ptr<ngraph::Node> mask =
op::Constant::create(dout.get_element_type(), data_shape, {0});
auto din = std::make_shared<op::ReplaceSlice>(mask, dout_reshape, ng_start, ng_end);
return {din};
}
shared_ptr<Node> op::PartialSliceBackprop::copy_with_new_args(const NodeVector& new_args) const
{
if (new_args.size() != 2)
{
throw ngraph_error("Incorrect number of new arguments");
}
return make_shared<PartialSliceBackprop>(
new_args.at(0), new_args.at(1), m_axes, m_lower_bounds, m_upper_bounds);
}
void op::PartialSliceBackprop::pre_validate_and_infer_types()
{
element::Type input_element_type = get_input_element_type(0);
NODE_VALIDATION_CHECK(this,
input_element_type.is_dynamic() || input_element_type.is_real(),
"Argument element type must be f16, bf16, f32, f64 or dynamic (got ",
input_element_type,
").");
}
//*****************************************************************************
// Copyright 2017-2019 Intel Corporation
//
// 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.
//*****************************************************************************
#pragma once
#include "ngraph/node.hpp"
#include "ngraph/op/op.hpp"
#include "ngraph/op/util/fused_op.hpp"
namespace ngraph
{
namespace op
{
/// \brief pdpd slice op
///
class PartialSlice : public ngraph::op::util::FusedOp
{
public:
NGRAPH_API
static constexpr NodeTypeInfo type_info{"PartialSlice", 0};
const NodeTypeInfo& get_type_info() const override { return type_info; }
PartialSlice() = default;
/// \brief Constructs an PartialSlice operation.
///
/// \param data Input tensor
/// \param axes Axes that lower and upper bounds apply to
/// \param lower_bounds Starting indices of corresponding axis in `axes`
/// \param upper_bounds Ending indices of corresponding axis in `axes`
/// \param decrease_axis Axes to be dropped (dimension will be one)
PartialSlice(const Output<Node>& data,
const AxisVector& axes,
const std::vector<int64_t>& lower_bounds,
const std::vector<int64_t>& upper_bounds,
const AxisVector& decrease_axes);
virtual NodeVector decompose_op() const override;
void pre_validate_and_infer_types() override;
virtual std::shared_ptr<Node>
copy_with_new_args(const NodeVector& new_args) const override;
const AxisVector& get_axes() const { return m_axes; }
const std::vector<int64_t>& get_lower_bounds() const { return m_lower_bounds; }
const std::vector<int64_t>& get_upper_bounds() const { return m_upper_bounds; }
const AxisVector& get_decrease_axes() const { return m_decrease_axes; }
protected:
virtual void generate_adjoints(autodiff::Adjoints& adjoints,
const NodeVector& deltas) override;
private:
AxisVector m_axes;
std::vector<int64_t> m_lower_bounds;
std::vector<int64_t> m_upper_bounds;
AxisVector m_decrease_axes;
};
/// \brief pdpd slice backprop
///
class PartialSliceBackprop : public ngraph::op::util::FusedOp
{
public:
NGRAPH_API
static constexpr NodeTypeInfo type_info{"PartialSliceBackprop", 0};
const NodeTypeInfo& get_type_info() const override { return type_info; }
PartialSliceBackprop() = default;
/// \brief Constructs an PartialSliceBackprop operation.
///
/// \param data Input tensor
/// \param dout Onput tensor from fprop
/// \param axes Axes that lower and upper bounds apply to
/// \param lower_bounds Starting indices of corresponding axis in `axes`
/// \param upper_bounds Ending indices of corresponding axis in `axes`
PartialSliceBackprop(const Output<Node>& data,
const Output<Node>& dout,
const AxisVector& axes,
const std::vector<int64_t>& lower_bounds,
const std::vector<int64_t>& upper_bounds);
virtual NodeVector decompose_op() const override;
void pre_validate_and_infer_types() override;
virtual std::shared_ptr<Node>
copy_with_new_args(const NodeVector& new_args) const override;
const AxisVector& get_axes() const { return m_axes; }
const std::vector<int64_t>& get_lower_bounds() const { return m_lower_bounds; }
const std::vector<int64_t>& get_upper_bounds() const { return m_upper_bounds; }
private:
AxisVector m_axes;
std::vector<int64_t> m_lower_bounds;
std::vector<int64_t> m_upper_bounds;
};
}
}
......@@ -44,6 +44,8 @@ NGRAPH_OP(LSTMSequence, ngraph::op)
NGRAPH_OP(MatMul, ngraph::op)
NGRAPH_OP(MVN, ngraph::op)
NGRAPH_OP(NormalizeL2, ngraph::op)
NGRAPH_OP(PartialSlice, ngraph::op)
NGRAPH_OP(PartialSliceBackprop, ngraph::op)
NGRAPH_OP(PRelu, ngraph::op)
NGRAPH_OP(RNNCell, ngraph::op)
NGRAPH_OP(ScaleShift, ngraph::op)
......
......@@ -81,6 +81,7 @@ void runtime::Executable::validate(const vector<std::shared_ptr<runtime::Tensor>
for (size_t i = 0; i < results.size(); i++)
{
if (outputs[i]->get_element_type().is_static() &&
results[i]->get_element_type().is_static() &&
results[i]->get_element_type() != outputs[i]->get_element_type())
{
stringstream ss;
......
......@@ -86,6 +86,7 @@
#include "ngraph/op/fused/matmul.hpp"
#include "ngraph/op/fused/mvn.hpp"
#include "ngraph/op/fused/normalize_l2.hpp"
#include "ngraph/op/fused/partial_slice.hpp"
#include "ngraph/op/fused/prelu.hpp"
#include "ngraph/op/fused/rnn_cell.hpp"
#include "ngraph/op/fused/scale_shift.hpp"
......@@ -1839,6 +1840,25 @@ shared_ptr<Node> JSONDeserializer::deserialize_node(json node_js)
node = make_shared<op::Parameter>(element_type, read_partial_shape(shape), cacheable);
break;
}
case OP_TYPEID::PartialSlice:
{
auto axes = node_js.at("axes").get<vector<size_t>>();
auto lower_bounds = node_js.at("lower_bounds").get<vector<int64_t>>();
auto upper_bounds = node_js.at("upper_bounds").get<vector<int64_t>>();
auto decrease_axes = node_js.at("decrease_axes").get<vector<size_t>>();
node = make_shared<op::PartialSlice>(
args[0], axes, lower_bounds, upper_bounds, decrease_axes);
break;
}
case OP_TYPEID::PartialSliceBackprop:
{
auto axes = node_js.at("axes").get<vector<size_t>>();
auto lower_bounds = node_js.at("lower_bounds").get<vector<int64_t>>();
auto upper_bounds = node_js.at("upper_bounds").get<vector<int64_t>>();
node = make_shared<op::PartialSliceBackprop>(
args[0], args[1], axes, lower_bounds, upper_bounds);
break;
}
case OP_TYPEID::Passthrough:
{
std::vector<json> outputs_js = node_js.at("output_shapes");
......@@ -3183,6 +3203,23 @@ json JSONSerializer::serialize_node(const Node& n)
node["element_type"] = write_element_type(tmp->get_element_type());
break;
}
case OP_TYPEID::PartialSlice:
{
auto tmp = dynamic_cast<const op::PartialSlice*>(&n);
node["axes"] = tmp->get_axes();
node["lower_bounds"] = tmp->get_lower_bounds();
node["upper_bounds"] = tmp->get_upper_bounds();
node["decrease_axes"] = tmp->get_decrease_axes();
break;
}
case OP_TYPEID::PartialSliceBackprop:
{
auto tmp = dynamic_cast<const op::PartialSliceBackprop*>(&n);
node["axes"] = tmp->get_axes();
node["lower_bounds"] = tmp->get_lower_bounds();
node["upper_bounds"] = tmp->get_upper_bounds();
break;
}
case OP_TYPEID::Passthrough:
{
auto tmp = static_cast<const op::Passthrough*>(&n);
......
......@@ -310,6 +310,7 @@ set(MULTI_TEST_SRC
backend/one_hot.in.cpp
backend/pad.in.cpp
backend/parameter_as_output.in.cpp
backend/partial_slice.in.cpp
backend/pool.in.cpp
backend/power.in.cpp
backend/product.in.cpp
......
//*****************************************************************************
// Copyright 2017-2019 Intel Corporation
//
// 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 <algorithm>
#include <cinttypes>
#include <cmath>
#include <cstdlib>
#include <random>
#include <string>
// clang-format off
#ifdef ${BACKEND_NAME}_FLOAT_TOLERANCE_BITS
#define DEFAULT_FLOAT_TOLERANCE_BITS ${BACKEND_NAME}_FLOAT_TOLERANCE_BITS
#endif
#ifdef ${BACKEND_NAME}_DOUBLE_TOLERANCE_BITS
#define DEFAULT_DOUBLE_TOLERANCE_BITS ${BACKEND_NAME}_DOUBLE_TOLERANCE_BITS
#endif
// clang-format on
#include "gtest/gtest.h"
#include "ngraph/ngraph.hpp"
#include "util/all_close.hpp"
#include "util/all_close_f.hpp"
#include "util/autodiff/numeric_compare.hpp"
#include "util/ndarray.hpp"
#include "util/test_control.hpp"
#include "util/test_tools.hpp"
using namespace std;
using namespace ngraph;
static string s_manifest = "${MANIFEST}";
NGRAPH_TEST(${BACKEND_NAME}, partial_slice_static)
{
Shape shape_x{2, 3, 2};
auto x = make_shared<op::Parameter>(element::f32, shape_x);
AxisVector axes{0, 1};
vector<int64_t> lower_bounds{1, 0};
vector<int64_t> upper_bounds{2, 2};
AxisVector decrease_axes{};
auto f = make_shared<Function>(
make_shared<op::PartialSlice>(x, axes, lower_bounds, upper_bounds, decrease_axes),
ParameterVector{x});
auto backend = runtime::Backend::create("${BACKEND_NAME}");
// Create some tensors for input/output
auto t_x = backend->create_tensor(element::f32, shape_x);
vector<float> v_x{0.f, 1.f, 2.f, 3.f, 4.f, 5.f, 6.f, 7.f, 8.f, 9.f, 10.f, 11.f};
copy_data(t_x, v_x);
auto t_r = backend->create_tensor(element::f32, Shape{1, 2, 2});
auto handle = backend->compile(f);
handle->call_with_validate({t_r}, {t_x});
vector<float> v_r{6.f, 7.f, 8.f, 9.f};
ASSERT_EQ(t_r->get_shape(), (Shape{1, 2, 2}));
EXPECT_TRUE(test::all_close_f(v_r, read_vector<float>(t_r)));
}
NGRAPH_TEST(${BACKEND_NAME}, partial_slice_partial_shape)
{
auto pshape_x = PartialShape{Dimension::dynamic(), 3, Dimension::dynamic()};
auto x = make_shared<op::Parameter>(element::f32, pshape_x);
AxisVector axes{0, 1};
vector<int64_t> lower_bounds{1, 0};
vector<int64_t> upper_bounds{2, 2};
AxisVector decrease_axes{};
auto f = make_shared<Function>(
make_shared<op::PartialSlice>(x, axes, lower_bounds, upper_bounds, decrease_axes),
ParameterVector{x});
auto backend = runtime::Backend::create("${BACKEND_NAME}", true);
// Create some tensors for input/output
Shape shape_x{2, 3, 2};
auto t_x = backend->create_tensor(element::f32, shape_x);
vector<float> v_x{0.f, 1.f, 2.f, 3.f, 4.f, 5.f, 6.f, 7.f, 8.f, 9.f, 10.f, 11.f};
copy_data(t_x, v_x);
auto t_r = backend->create_dynamic_tensor(element::f32, PartialShape::dynamic());
auto handle = backend->compile(f);
handle->call_with_validate({t_r}, {t_x});
vector<float> v_r{6.f, 7.f, 8.f, 9.f};
ASSERT_EQ(t_r->get_shape(), (Shape{1, 2, 2}));
EXPECT_TRUE(test::all_close_f(v_r, read_vector<float>(t_r)));
}
NGRAPH_TEST(${BACKEND_NAME}, partial_slice_unkown_rank)
{
auto pshape_x = PartialShape::dynamic();
auto x = make_shared<op::Parameter>(element::f32, pshape_x);
AxisVector axes{0, 1};
vector<int64_t> lower_bounds{1, 0};
vector<int64_t> upper_bounds{2, 2};
AxisVector decrease_axes{};
auto f = make_shared<Function>(
make_shared<op::PartialSlice>(x, axes, lower_bounds, upper_bounds, decrease_axes),
ParameterVector{x});
auto backend = runtime::Backend::create("${BACKEND_NAME}", true);
// Create some tensors for input/output
Shape shape_x{2, 3, 2};
auto t_x = backend->create_tensor(element::f32, shape_x);
vector<float> v_x{0.f, 1.f, 2.f, 3.f, 4.f, 5.f, 6.f, 7.f, 8.f, 9.f, 10.f, 11.f};
copy_data(t_x, v_x);
auto t_r = backend->create_dynamic_tensor(element::f32, PartialShape::dynamic());
auto handle = backend->compile(f);
handle->call_with_validate({t_r}, {t_x});
vector<float> v_r{6.f, 7.f, 8.f, 9.f};
ASSERT_EQ(t_r->get_shape(), (Shape{1, 2, 2}));
EXPECT_TRUE(test::all_close_f(v_r, read_vector<float>(t_r)));
}
NGRAPH_TEST(${BACKEND_NAME}, partial_slice_bprop_unkown_rank)
{
auto pshape_x = PartialShape::dynamic();
auto pshape_dout = PartialShape::dynamic();
auto x = make_shared<op::Parameter>(element::f32, pshape_x);
auto dout = make_shared<op::Parameter>(element::f32, pshape_dout);
AxisVector axes{0, 1};
vector<int64_t> lower_bounds{1, 0};
vector<int64_t> upper_bounds{2, 2};
auto f = make_shared<Function>(
make_shared<op::PartialSliceBackprop>(x, dout, axes, lower_bounds, upper_bounds),
ParameterVector{x, dout});
auto backend = runtime::Backend::create("${BACKEND_NAME}", true);
// Create some tensors for input/output
Shape shape_x{2, 3, 2};
Shape shape_dout{1, 2, 2};
auto t_x = backend->create_tensor(element::f32, shape_x);
auto t_dout = backend->create_tensor(element::f32, shape_dout);
vector<float> v_x{0.f, 1.f, 2.f, 3.f, 4.f, 5.f, 6.f, 7.f, 8.f, 9.f, 10.f, 11.f};
vector<float> v_dout{6.f, 7.f, 8.f, 9.f};
copy_data(t_x, v_x);
copy_data(t_dout, v_dout);
auto t_r = backend->create_dynamic_tensor(element::f32, PartialShape::dynamic());
auto handle = backend->compile(f);
handle->call_with_validate({t_r}, {t_x, t_dout});
vector<float> v_r{0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 6.f, 7.f, 8.f, 9.f, 0.f, 0.f};
ASSERT_EQ(t_r->get_shape(), (Shape{2, 3, 2}));
EXPECT_TRUE(test::all_close_f(v_r, read_vector<float>(t_r)));
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment