/* Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved. 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 #include #include "paddle/fluid/operators/reduce_ops/reduce_op_function.h" namespace paddle { namespace operators { #define HANDLE_DIM(NDIM, RDIM) \ if (ndim == NDIM && rdim == RDIM) { \ ReduceFunctor( \ context.template device_context(), *input, output, \ dims, keep_dim); \ } template class ReduceKernel : public framework::OpKernel { public: void Compute(const framework::ExecutionContext& context) const override { bool reduce_all = context.Attr("reduce_all"); auto* input = context.Input("X"); auto* output = context.Output("Out"); output->mutable_data(context.GetPlace()); auto dims = context.Attr>("dim"); bool keep_dim = context.Attr("keep_dim"); if (reduce_all) { // Flatten and reduce 1-D tensor auto x = EigenVector::Flatten(*input); auto out = EigenScalar::From(*output); auto& place = *context.template device_context().eigen_device(); auto reduce_dim = Eigen::array({{0}}); Functor functor; functor(place, &x, &out, reduce_dim); } else { int ndim = input->dims().size(); int rdim = dims.size(); // comments for accelerating compiling temporarily. // HANDLE_DIM(6, 5); // HANDLE_DIM(6, 4); // HANDLE_DIM(6, 3); // HANDLE_DIM(6, 2); // HANDLE_DIM(6, 1); // HANDLE_DIM(5, 4); // HANDLE_DIM(5, 3); // HANDLE_DIM(5, 2); // HANDLE_DIM(5, 1); HANDLE_DIM(4, 3); HANDLE_DIM(4, 2); HANDLE_DIM(4, 1); HANDLE_DIM(3, 2); HANDLE_DIM(3, 1); HANDLE_DIM(2, 1); HANDLE_DIM(1, 1); } } }; template class ReduceGradKernel : public framework::OpKernel { public: void Compute(const framework::ExecutionContext& context) const override { bool reduce_all = context.Attr("reduce_all"); auto dims = context.Attr>("dim"); auto* input0 = context.Input("X"); auto* input1 = context.Input("Out"); auto* input2 = context.Input(framework::GradVarName("Out")); auto* output = context.Output(framework::GradVarName("X")); output->mutable_data(context.GetPlace()); // NOTE: EigenTensor::From() uses tensor->data() // if op has NoNeedBufferVarsInferer, the corresponding kNoNeedBufferX or // kNoNeedBufferY should set true // and use fake var that has same dims. if (kNoNeedBufferX) { input0 = output; } if (kNoNeedBufferY) { input1 = input2; } // NOTE(dengkaipeng): Out is unnecessary in some reduce kernel and // not be set as Input in grad Maker, use Out_grad to replace here if (!input1) input1 = input2; if (reduce_all) { auto x = EigenVector::Flatten(*input0); auto x_reduce = EigenVector::From(*input1); auto x_reduce_grad = EigenVector::From(*input2); auto x_grad = EigenVector::Flatten(*output); auto& place = *context.template device_context().eigen_device(); auto broadcast_dim = Eigen::array({{static_cast(input0->numel())}}); Functor functor; functor(place, &x, &x_reduce, &x_grad, &x_reduce_grad, broadcast_dim, broadcast_dim[0]); } else { int rank = input0->dims().size(); switch (rank) { case 1: ReduceGradFunctor( context.template device_context(), *input0, *input1, *input2, output, dims); break; case 2: ReduceGradFunctor( context.template device_context(), *input0, *input1, *input2, output, dims); break; case 3: ReduceGradFunctor( context.template device_context(), *input0, *input1, *input2, output, dims); break; case 4: ReduceGradFunctor( context.template device_context(), *input0, *input1, *input2, output, dims); break; case 5: ReduceGradFunctor( context.template device_context(), *input0, *input1, *input2, output, dims); break; case 6: ReduceGradFunctor( context.template device_context(), *input0, *input1, *input2, output, dims); break; } } } }; class ReduceOp : public framework::OperatorWithKernel { public: using framework::OperatorWithKernel::OperatorWithKernel; void InferShape(framework::InferShapeContext* ctx) const override { PADDLE_ENFORCE(ctx->HasInput("X"), "Input(X) of ReduceOp should not be null."); PADDLE_ENFORCE(ctx->HasOutput("Out"), "Output(Out) of ReduceOp should not be null."); auto x_dims = ctx->GetInputDim("X"); auto x_rank = x_dims.size(); PADDLE_ENFORCE_LE(x_rank, 6, "ShapeError: The input tensor X's dimensions of Reduce " "should be less equal than 6. But received X's " "dimensions = %d, X's shape = [%s].", x_rank, x_dims); auto dims = ctx->Attrs().Get>("dim"); PADDLE_ENFORCE_GT( dims.size(), 0, "ShapeError: The input dim dimensions of Reduce " "shoud be greater than 0. But received the dim dimesions of Reduce " " = %d", dims.size()); for (size_t i = 0; i < dims.size(); ++i) { PADDLE_ENFORCE_LT(dims[i], x_rank, "ShapeError: The reduce dim index %d should be in the " "range [-dimension(X), dimension(X)]." "which dimesion = %d, But received dim index = %d", i, x_rank, dims[i]); if (dims[i] < 0) dims[i] = x_rank + dims[i]; } sort(dims.begin(), dims.end()); bool reduce_all = ctx->Attrs().Get("reduce_all"); bool keep_dim = ctx->Attrs().Get("keep_dim"); if (reduce_all) { if (keep_dim) ctx->SetOutputDim( "Out", framework::make_ddim(std::vector(x_rank, 1))); else ctx->SetOutputDim("Out", {1}); } else { auto dims_vector = vectorize(x_dims); if (keep_dim) { for (size_t i = 0; i < dims.size(); ++i) { dims_vector[dims[i]] = 1; } } else { const int kDelFlag = -2; for (size_t i = 0; i < dims.size(); ++i) { dims_vector[dims[i]] = kDelFlag; } dims_vector.erase( remove(dims_vector.begin(), dims_vector.end(), kDelFlag), dims_vector.end()); } if (!keep_dim && dims_vector.size() == 0) { dims_vector.push_back(1); } auto out_dims = framework::make_ddim(dims_vector); ctx->SetOutputDim("Out", out_dims); if (dims.size() > 0 && dims[0] != 0) { // Only pass LoD when not reducing on the first dim. ctx->ShareLoD("X", /*->*/ "Out"); } } } }; class ReduceGradOp : public framework::OperatorWithKernel { public: using framework::OperatorWithKernel::OperatorWithKernel; void InferShape(framework::InferShapeContext* ctx) const override { PADDLE_ENFORCE(ctx->HasInput("X"), "Input(X) should not be null."); PADDLE_ENFORCE(ctx->HasInput(framework::GradVarName("Out")), "Input(Out@GRAD) should not be null."); auto x_dims = ctx->GetInputDim("X"); auto x_rank = x_dims.size(); PADDLE_ENFORCE_LE(x_rank, 6, "Tensors with rank at most 6 are supported."); auto dims = ctx->Attrs().Get>("dim"); for (size_t i = 0; i < dims.size(); ++i) { PADDLE_ENFORCE_LT(dims[i], x_rank, "ShapeError: The reduce dim index %d should be in the " "range [-dimension(X), dimension(X)]." "which dimesion = %d, But received dim index = %d", i, x_rank, dims[i]); if (dims[i] < 0) dims[i] = x_rank + dims[i]; } sort(dims.begin(), dims.end()); auto x_grad_name = framework::GradVarName("X"); if (ctx->HasOutput(x_grad_name)) { ctx->SetOutputDim(x_grad_name, x_dims); ctx->ShareLoD("X", /*->*/ x_grad_name); } } protected: framework::OpKernelType GetExpectedKernelType( const framework::ExecutionContext& ctx) const override { return framework::OpKernelType( ctx.Input(framework::GradVarName("Out"))->type(), ctx.GetPlace()); } }; class ReduceOpMaker : public framework::OpProtoAndCheckerMaker { public: void Make() final { AddInput("X", "(Tensor) The input tensor. Tensors with rank at most 6 are " "supported."); AddOutput("Out", "(Tensor) The result tensor."); AddAttr>( "dim", "(list, default {0}) The dimensions to reduce. " "Must be in the range [-rank(input), rank(input)). " "If `dim[i] < 0`, the dims[i] to reduce is `rank + dims[i]`. " "Note that reducing on the first dim will make the LoD info lost.") .SetDefault({0}); AddAttr("keep_dim", "(bool, default false) " "If true, retain the reduced dimension with length 1.") .SetDefault(false); AddAttr("reduce_all", "(bool, default false) " "If true, output a scalar reduced along all dimensions.") .SetDefault(false); AddComment(string::Sprintf(R"DOC( %s Operator. This operator computes the %s of input tensor along the given dimension. The result tensor has 1 fewer dimension than the input unless keep_dim is true. If reduce_all is true, just reduce along all dimensions and output a scalar. )DOC", GetOpType(), GetName())); } protected: virtual std::string GetName() const = 0; virtual std::string GetOpType() const = 0; }; } // namespace operators } // namespace paddle namespace ops = paddle::operators; #define REGISTER_REDUCE_OP(op_name) \ class __##op_name##Maker__ : public ops::ReduceOpMaker { \ protected: \ virtual std::string GetName() const { return #op_name; } \ virtual std::string GetOpType() const { return "Reduce " #op_name; } \ }; \ REGISTER_OPERATOR(op_name, ops::ReduceOp, __##op_name##Maker__, \ paddle::framework::DefaultGradOpDescMaker); \ REGISTER_OPERATOR(op_name##_grad, ops::ReduceGradOp) #define REGISTER_REDUCE_OP_WITHOUT_GRAD(op_name) \ class __##op_name##Maker__ : public ops::ReduceOpMaker { \ protected: \ virtual std::string GetName() const { return #op_name; } \ virtual std::string GetOpType() const { return "Reduce " #op_name; } \ }; \ REGISTER_OPERATOR(op_name, ops::ReduceOp, __##op_name##Maker__, \ paddle::framework::EmptyGradOpMaker);