From c79d530ad387399ea2cbb97d7f9eb771d694711f Mon Sep 17 00:00:00 2001 From: Yancey Date: Fri, 19 Jan 2018 10:22:29 +0800 Subject: [PATCH] Add split selected rows op (#7604) * add split selected rows op * update comment * add grad check * registry cuda kernel * fix ci failed --- paddle/operators/split_selected_rows_op.cc | 114 ++++++++++++++++ paddle/operators/split_selected_rows_op.cu | 19 +++ paddle/operators/split_selected_rows_op.h | 58 ++++++++ .../tests/test_split_selected_rows_op.py | 128 ++++++++++++++++++ 4 files changed, 319 insertions(+) create mode 100644 paddle/operators/split_selected_rows_op.cc create mode 100644 paddle/operators/split_selected_rows_op.cu create mode 100644 paddle/operators/split_selected_rows_op.h create mode 100644 python/paddle/v2/fluid/tests/test_split_selected_rows_op.py diff --git a/paddle/operators/split_selected_rows_op.cc b/paddle/operators/split_selected_rows_op.cc new file mode 100644 index 0000000000..d9a023987b --- /dev/null +++ b/paddle/operators/split_selected_rows_op.cc @@ -0,0 +1,114 @@ +/* Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserve. + +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 "paddle/operators/split_selected_rows_op.h" + +namespace paddle { +namespace operators { + +class SplitSelectedRowsOpMaker : public framework::OpProtoAndCheckerMaker { + public: + SplitSelectedRowsOpMaker(OpProto *proto, OpAttrChecker *op_checker) + : OpProtoAndCheckerMaker(proto, op_checker) { + AddInput("X", "The input SelectedRows."); + AddOutput("Out", "The outputs of input SelectedRows.").AsDuplicable(); + AddAttr>("rows_sections", "Rows section for output.") + .SetDefault(std::vector({})); + AddAttr>("height_sections", + "Height for each output SelectedRows.") + .SetDefault(std::vector({})); + + AddComment(R"DOC( +Split a SelectedRows with a specified rows section. +height_sections is only needed when need to split the dims of the original tensor. + +Example: + Input: + X.rows = {0, 7, 5} + X.height = 12 + Attr: + rows_sections = {1, 2} + height_sections = {} + Out: + out0.rows = {0} + out0.height = 12 + out1.rows = {7, 5} + out2.height = 12 + +)DOC"); + } +}; + +class SplitSelectedRowsOp : public framework::OperatorWithKernel { + public: + using framework::OperatorWithKernel::OperatorWithKernel; + + void InferShape(framework::InferShapeContext *ctx) const override { + PADDLE_ENFORCE(ctx->HasInput("X"), "SplitSelectedRowsOp must has input X."); + PADDLE_ENFORCE(ctx->HasOutputs("Out"), + "SplitSelectedRowsOp must has output Out."); + + std::vector height_sections = + ctx->Attrs().Get>("height_sections"); + std::vector rows_sections = + ctx->Attrs().Get>("rows_sections"); + PADDLE_ENFORCE_EQ( + rows_sections.size(), ctx->Outputs("Out").size(), + "The size of rows section should be the same with Outputs size."); + int64_t n = ctx->Outputs("Out").size(); + + std::vector outs_dims; + outs_dims.reserve(n); + + // make output dims + for (int64_t i = 0; i < n; ++i) { + auto dims = ctx->GetInputDim("X"); + if (height_sections.size()) { + PADDLE_ENFORCE_EQ( + height_sections.size(), static_cast(n), + "The size of height section should be the same with height" + " section size."); + dims[0] = height_sections[i]; + } + outs_dims.push_back(dims); + } + ctx->SetOutputsDim("Out", outs_dims); + } +}; + +class SplitSelectedRowsGradMaker : public framework::SingleGradOpDescMaker { + public: + using framework::SingleGradOpDescMaker::SingleGradOpDescMaker; + + protected: + std::unique_ptr Apply() const override { + auto *grad_op = new framework::OpDesc(); + grad_op->SetType("sum"); + grad_op->SetInput("X", OutputGrad("Out")); + grad_op->SetOutput("Out", InputGrad("X")); + grad_op->SetAttrMap(Attrs()); + return std::unique_ptr(grad_op); + } +}; + +} // namespace operators +} // namespace paddle + +namespace ops = paddle::operators; +REGISTER_OPERATOR(split_selected_rows, ops::SplitSelectedRowsOp, + ops::SplitSelectedRowsOpMaker, + ops::SplitSelectedRowsGradMaker); +REGISTER_OP_CPU_KERNEL( + split_selected_rows, + ops::SplitSelectedRowsOpKernel); diff --git a/paddle/operators/split_selected_rows_op.cu b/paddle/operators/split_selected_rows_op.cu new file mode 100644 index 0000000000..983285480f --- /dev/null +++ b/paddle/operators/split_selected_rows_op.cu @@ -0,0 +1,19 @@ +/* Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserve. + +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 "paddle/operators/split_selected_rows_op.h" +namespace ops = paddle::operators; +REGISTER_OP_CUDA_KERNEL( + split_selected_rows, + ops::SplitSelectedRowsOpKernel); diff --git a/paddle/operators/split_selected_rows_op.h b/paddle/operators/split_selected_rows_op.h new file mode 100644 index 0000000000..1cae53f1af --- /dev/null +++ b/paddle/operators/split_selected_rows_op.h @@ -0,0 +1,58 @@ +/* Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserve. + +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 +#include "paddle/framework/op_registry.h" + +namespace paddle { +namespace operators { + +template +class SplitSelectedRowsOpKernel : public framework::OpKernel { + public: + void Compute(const framework::ExecutionContext& ctx) const override { + auto* x = ctx.Input("X"); + auto outs = ctx.MultiOutput("Out"); + + auto rows_sections = ctx.Attr>("rows_sections"); + auto height_sections = ctx.Attr>("height_sections"); + + int64_t n = outs.size(); + int offset = 0; + + for (int64_t i = 0; i < n; ++i) { + framework::Vector out_rows; + for (int64_t j = 0; j < rows_sections[i]; ++j) { + out_rows.push_back(x->rows()[offset + j]); + } + + auto& out = outs[i]; + auto x_dims = x->GetCompleteDims(); + x_dims[0] = rows_sections[i]; + out->mutable_value()->mutable_data(x_dims, ctx.GetPlace()); + framework::Copy(x->value().Slice(offset, rows_sections[i] + offset), + x->place(), ctx.device_context(), out->mutable_value()); + outs[i]->set_rows(out_rows); + if (height_sections.size()) { + outs[i]->set_height(height_sections[i]); + } + offset += rows_sections[i]; + } + } +}; + +} // namespace operators +} // namespace paddle diff --git a/python/paddle/v2/fluid/tests/test_split_selected_rows_op.py b/python/paddle/v2/fluid/tests/test_split_selected_rows_op.py new file mode 100644 index 0000000000..a6cc4f6c6d --- /dev/null +++ b/python/paddle/v2/fluid/tests/test_split_selected_rows_op.py @@ -0,0 +1,128 @@ +# Copyright (c) 2018 PaddlePaddle Authors. All Rights Reserve. +# +#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. +import unittest +import paddle.v2.fluid.core as core +import numpy as np +from paddle.v2.fluid.op import Operator + + +class TestSpliteSelectedRows(unittest.TestCase): + def get_places(self): + places = [core.CPUPlace()] + if core.is_compile_gpu(): + places.append(core.CUDAPlace(0)) + return places + + def test_check_output(self): + for place in self.get_places(): + self.check_with_place(place) + + def test_check_grad(self): + for place in self.get_places(): + self.check_grad_with_place(place) + + def check_with_place(self, place): + scope = core.Scope() + rows = [0, 5, 7, 4] + height = 10 + row_numel = 2 + + # initialize input variable X + x = scope.var('X').get_selected_rows() + x.set_rows(rows) + x.set_height(height) + np_array = np.ones((len(rows), row_numel)).astype("float32") + np_array[0, 0] = 2.0 + np_array[2, 1] = 4.0 + x_tensor = x.get_tensor() + x_tensor.set(np_array, place) + + rows_sections = [2, 2] + height_sections = [] + + # initialize output variables [out0, out1] + out0 = scope.var('out0').get_selected_rows() + out1 = scope.var('out1').get_selected_rows() + + # expected output selected rows + expected_out0_rows = [0, 5] + expected_out1_rows = [7, 4] + expected_height = height + + op = Operator( + "split_selected_rows", + X="X", + Out=["out0", "out1"], + rows_sections=rows_sections, + height_sections=height_sections) + + op.run(scope, place) + + self.assertEqual(out0.rows(), expected_out0_rows) + self.assertEqual(out1.rows(), expected_out1_rows) + + self.assertEqual(out0.height(), expected_height) + self.assertEqual(out1.height(), expected_height) + + self.assertAlmostEqual(2.0, np.array(out0.get_tensor())[0, 0]) + self.assertAlmostEqual(4.0, np.array(out1.get_tensor())[0, 1]) + + def check_grad_with_place(self, place): + scope = core.Scope() + height = 10 + row_numel = 2 + + # attr + rows_sections = [2, 2] + height_sections = [] + + # initialize input variable X + out0_grad = scope.var("out0@GRAD").get_selected_rows() + rows0 = [0, 5] + out0_grad.set_rows(rows0) + out0_grad.set_height(height) + out0_grad_tensor = out0_grad.get_tensor() + np_array = np.ones((len(rows0), row_numel)).astype("float32") + np_array[0, 0] = 2.0 + out0_grad_tensor.set(np_array, place) + + out1_grad = scope.var("out1@GRAD").get_selected_rows() + rows1 = [7, 5] + out1_grad.set_rows(rows1) + out1_grad.set_height(height) + out1_grad_tensor = out1_grad.get_tensor() + np_array = np.ones((len(rows1), row_numel)).astype("float32") + np_array[0, 1] = 4.0 + out1_grad_tensor.set(np_array, place) + + x_grad = scope.var("X@GRAD").get_selected_rows() + + grad_op = Operator( + "sum", + X=["out0@GRAD", "out1@GRAD"], + Out="X@GRAD", + rows_sections=rows_sections, + height_sections=height_sections) + + grad_op.run(scope, place) + + self.assertEqual(x_grad.rows(), rows0 + rows1) + self.assertEqual(x_grad.height(), height) + + self.assertAlmostEqual(2.0, np.array(x_grad.get_tensor())[0, 0]) + self.assertAlmostEqual(4.0, np.array(x_grad.get_tensor())[2, 1]) + + +if __name__ == "__main__": + unittest.main() -- GitLab