From 9d2c77e6262eae63a150425ab24402660c091bfb Mon Sep 17 00:00:00 2001 From: Yang Yang Date: Tue, 19 Dec 2017 05:12:53 +0000 Subject: [PATCH 01/49] parallel_do skeleton pass compile --- paddle/operators/CMakeLists.txt | 2 + paddle/operators/parallel_do_op.cc | 147 +++++++++++++++++++++++++++++ paddle/operators/recurrent_op.cc | 20 ++-- paddle/operators/while_op.cc | 20 ++-- 4 files changed, 169 insertions(+), 20 deletions(-) create mode 100644 paddle/operators/parallel_do_op.cc diff --git a/paddle/operators/CMakeLists.txt b/paddle/operators/CMakeLists.txt index 5aaaf99332..fae2fc3c25 100644 --- a/paddle/operators/CMakeLists.txt +++ b/paddle/operators/CMakeLists.txt @@ -185,6 +185,7 @@ set(DEPS_OPS cond_op cross_entropy_op recurrent_op + parallel_do_op softmax_with_cross_entropy_op softmax_op sequence_softmax_op @@ -256,6 +257,7 @@ op_library(lstm_op DEPS sequence2batch lstm_compute) op_library(conv_transpose_op DEPS vol2col) op_library(gru_op DEPS sequence2batch gru_compute) op_library(recurrent_op SRCS recurrent_op.cc DEPS executor) +op_library(parallel_do_op SRCS parallel_do_op.cc DEPS executor) # FIXME(typhoonzero): save/load depends lodtensor serialization functions op_library(save_op DEPS lod_tensor) diff --git a/paddle/operators/parallel_do_op.cc b/paddle/operators/parallel_do_op.cc new file mode 100644 index 0000000000..a2a12cfdf0 --- /dev/null +++ b/paddle/operators/parallel_do_op.cc @@ -0,0 +1,147 @@ +/* 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 +#include "paddle/framework/executor.h" +#include "paddle/framework/op_registry.h" +#include "paddle/framework/operator.h" + +namespace paddle { +namespace operators { + +constexpr char kInputs[] = "inputs"; +constexpr char kParameters[] = "parameters"; +constexpr char kPlaces[] = "places"; +constexpr char kParallelBlock[] = "parallel_block"; +constexpr char kOutputs[] = "outputs"; +constexpr char kParallelScopes[] = "parallel_scopes"; +// #define GRAD_SUFFIX "@GRAD" +// constexpr char kInputGrads[] = "inputs" GRAD_SUFFIX; +// constexpr char kOutputGrads[] = "outputs" GRAD_SUFFIX; +// constexpr char kParamGrads[] = "parameters" GRAD_SUFFIX; + +using ParallelScopeVar = std::vector; +using OperatorBase = framework::OperatorBase; + +class ParallelDoOp : public OperatorBase { + public: + ParallelDoOp(const std::string &type, + const framework::VariableNameMap &inputs, + const framework::VariableNameMap &outputs, + const framework::AttributeMap &attrs) + : OperatorBase(type, inputs, outputs, attrs) {} + + void Run(const framework::Scope &scope, + const platform::DeviceContext &dev_ctx) const override { + // create scope + // copy parameters + } +}; + +class ParallelDoGradOp : public OperatorBase { + public: + ParallelDoGradOp(const std::string &type, + const framework::VariableNameMap &inputs, + const framework::VariableNameMap &outputs, + const framework::AttributeMap &attrs) + : OperatorBase(type, inputs, outputs, attrs) {} + + void Run(const framework::Scope &scope, + const platform::DeviceContext &dev_ctx) const override {} +}; + +class ParallelDoOpProtoMaker : public framework::OpProtoAndCheckerMaker { + public: + ParallelDoOpProtoMaker(framework::OpProto *proto, + framework::OpAttrChecker *op_checker) + : OpProtoAndCheckerMaker(proto, op_checker) { + AddInput(kInputs, "").AsDuplicable(); + AddInput(kParameters, "").AsDuplicable(); + AddInput(kPlaces, ""); + AddOutput(kOutputs, "").AsDuplicable(); + AddOutput(kParallelScopes, ""); + AddAttr(kParallelBlock, ""); + AddComment(R"DOC( +ParallelDo Operator. +)DOC"); + } +}; + +class ParallelDoGradOpDescMaker : public framework::SingleGradOpDescMaker { + public: + using framework::SingleGradOpDescMaker::SingleGradOpDescMaker; + + protected: + virtual std::unique_ptr Apply() const { + PADDLE_THROW("Not Implemented"); + auto *grad = new framework::OpDescBind(); + grad->SetType("recurrent_grad"); + for (auto &input_param : this->InputNames()) { + grad->SetInput(input_param, this->Input(input_param)); + grad->SetOutput(framework::GradVarName(input_param), + this->InputGrad(input_param)); + } + + for (auto &output_param : this->OutputNames()) { + if (output_param == kParallelScopes) { + grad->SetInput(output_param, this->Output(output_param)); + grad->SetInput(framework::GradVarName(output_param), + this->Output(output_param)); + } else { + grad->SetInput(output_param, this->Output(output_param)); + grad->SetInput(framework::GradVarName(output_param), + this->OutputGrad(output_param)); + } + } + grad->SetAttrMap(this->Attrs()); + grad->SetBlockAttr(kParallelBlock, *grad_block_[0]); + + return std::unique_ptr(grad); + } +}; + +class ParallelDoGradOpShapeInference : public framework::InferShapeBase { + public: + void operator()(framework::InferShapeContext *ctx) const override { + PADDLE_THROW("Not Implemented"); + // std::vector input{kInputs}; + // std::vector output{kOutputs}; + // for (auto &s : input) { + // PADDLE_ENFORCE(ctx->HasInputs(s)); + // PADDLE_ENFORCE(ctx->HasOutputs(framework::GradVarName(s)), + // "Cannot find the gradient variable %s", + // framework::GradVarName(s)); + // } + // for (auto &s : output) { + // PADDLE_ENFORCE(ctx->HasInputs(s)); + // } + // for (auto &s : input) { + // ctx->SetOutputsDim(framework::GradVarName(s), ctx->GetInputsDim(s)); + // } + // if (ctx->HasInputs(kParameters)) { + // PADDLE_ENFORCE(ctx->HasOutputs(framework::GradVarName(kParameters))); + // ctx->SetOutputsDim(framework::GradVarName(kParameters), + // ctx->GetInputsDim(kParameters)); + // } + } +}; + +} // namespace operators +} // namespace paddle + +REGISTER_OPERATOR(parallel_do, paddle::operators::ParallelDoOp, + paddle::operators::ParallelDoOpProtoMaker, + paddle::operators::ParallelDoGradOpDescMaker); +REGISTER_OPERATOR(parallel_do_grad, paddle::operators::ParallelDoGradOp, + paddle::operators::ParallelDoGradOpShapeInference); diff --git a/paddle/operators/recurrent_op.cc b/paddle/operators/recurrent_op.cc index 29f9163643..82ac5a27f7 100644 --- a/paddle/operators/recurrent_op.cc +++ b/paddle/operators/recurrent_op.cc @@ -22,10 +22,10 @@ constexpr char kInputs[] = "inputs"; constexpr char kInitialStates[] = "initial_states"; constexpr char kParameters[] = "parameters"; constexpr char kOutputs[] = "outputs"; -constexpr char kStepScopes[] = "step_scopes"; +constexpr char kParallelScopes[] = "step_scopes"; constexpr char kExStates[] = "ex_states"; constexpr char kStates[] = "states"; -constexpr char kStepBlock[] = "step_block"; +constexpr char kParallelBlock[] = "step_block"; constexpr char kReverse[] = "reverse"; constexpr char kIsTrain[] = "is_train"; #define GRAD_SUFFIX "@GRAD" @@ -234,7 +234,7 @@ class RecurrentOp : public RecurrentBase { auto reverse = Attr(kReverse); framework::Executor executor(dev_ctx); - auto *block = Attr(kStepBlock); + auto *block = Attr(kParallelBlock); auto *program = block->Program(); for (size_t i = 0; i < seq_len; ++i) { @@ -295,7 +295,7 @@ class RecurrentOp : public RecurrentBase { private: StepScopes CreateStepScopes(const framework::Scope &scope, size_t seq_len) const { - auto *var = scope.FindVar(Output(kStepScopes)); + auto *var = scope.FindVar(Output(kParallelScopes)); PADDLE_ENFORCE(var != nullptr); return StepScopes(scope, var->GetMutable(), Attr(kIsTrain), seq_len); @@ -317,7 +317,7 @@ class RecurrentGradOp : public RecurrentBase { auto reverse = Attr(kReverse); framework::Executor executor(dev_ctx); - auto *block = Attr(kStepBlock); + auto *block = Attr(kParallelBlock); auto *program = block->Program(); for (size_t step_id = 0; step_id < seq_len; ++step_id) { @@ -465,7 +465,7 @@ class RecurrentGradOp : public RecurrentBase { private: StepScopes CreateStepScopes(const framework::Scope &scope, size_t seq_len) const { - auto *var = scope.FindVar(Input(kStepScopes)); + auto *var = scope.FindVar(Input(kParallelScopes)); PADDLE_ENFORCE(var != nullptr); return StepScopes(scope, var->GetMutable(), Attr(kIsTrain), seq_len, true /*is_backward*/); @@ -510,7 +510,7 @@ class RecurrentOpProtoMaker : public framework::OpProtoAndCheckerMaker { AddOutput(kOutputs, "The output sequence of RNN. The sequence length must be same.") .AsDuplicable(); - AddOutput(kStepScopes, + AddOutput(kParallelScopes, "StepScopes contain all local variables in each time step."); AddAttr>(kExStates, string::Sprintf( @@ -523,7 +523,7 @@ The ex-state means the state value in the ex-timestep or the previous time step string::Sprintf( "The state variable names. [%s, %s, %s] must be the same order", kExStates, kStates, kInitStateGrads)); - AddAttr(kStepBlock, + AddAttr(kParallelBlock, "The step block inside RNN"); AddAttr(kReverse, R"DOC(Calculate RNN reversely or not. By default reverse=False @@ -576,7 +576,7 @@ class RecurrentGradOpDescMaker : public framework::SingleGradOpDescMaker { } for (auto &output_param : this->OutputNames()) { - if (output_param == kStepScopes) { + if (output_param == kParallelScopes) { grad->SetInput(output_param, this->Output(output_param)); grad->SetInput(framework::GradVarName(output_param), this->Output(output_param)); @@ -587,7 +587,7 @@ class RecurrentGradOpDescMaker : public framework::SingleGradOpDescMaker { } } grad->SetAttrMap(this->Attrs()); - grad->SetBlockAttr(kStepBlock, *grad_block_[0]); + grad->SetBlockAttr(kParallelBlock, *grad_block_[0]); return std::unique_ptr(grad); } diff --git a/paddle/operators/while_op.cc b/paddle/operators/while_op.cc index b8e44bcc5a..f2b917b0fc 100644 --- a/paddle/operators/while_op.cc +++ b/paddle/operators/while_op.cc @@ -25,9 +25,9 @@ namespace operators { using StepScopeVar = std::vector; using LoDTensor = framework::LoDTensor; -constexpr char kStepBlock[] = "step_block"; +constexpr char kParallelBlock[] = "step_block"; constexpr char kCondition[] = "Condition"; -constexpr char kStepScopes[] = "StepScopes"; +constexpr char kParallelScopes[] = "StepScopes"; constexpr char kParameters[] = "X"; constexpr char kParamGrads[] = "X@GRAD"; constexpr char kOutputs[] = "Out"; @@ -46,11 +46,11 @@ class WhileOp : public framework::OperatorBase { PADDLE_ENFORCE_EQ(cond.dims(), paddle::framework::make_ddim({1})); framework::Executor executor(dev_ctx); - auto *block = Attr(kStepBlock); + auto *block = Attr(kParallelBlock); auto *program = block->Program(); auto step_scopes = - scope.FindVar(Output(kStepScopes))->GetMutable(); + scope.FindVar(Output(kParallelScopes))->GetMutable(); while (cond.data()[0]) { auto ¤t_scope = scope.NewScope(); @@ -78,11 +78,11 @@ class WhileOpMaker : public framework::OpProtoAndCheckerMaker { "A set of variables, which will be assigned with values " "generated by the operators inside the block of While Op.") .AsDuplicable(); - AddOutput(kStepScopes, + AddOutput(kParallelScopes, "(StepScopeVar) A vector of local scope, which size equals the " "step number of While Op. The i'th scope storages temporary " "variables generated in the i'th step."); - AddAttr(kStepBlock, + AddAttr(kParallelBlock, "The step block inside WhileOp"); AddComment(R"DOC( )DOC"); @@ -99,11 +99,11 @@ class WhileGradOp : public framework::OperatorBase { void Run(const framework::Scope &scope, const platform::DeviceContext &dev_ctx) const override { framework::Executor executor(dev_ctx); - auto *block = Attr(kStepBlock); + auto *block = Attr(kParallelBlock); auto *program = block->Program(); auto *step_scopes = - scope.FindVar(Input(kStepScopes))->GetMutable(); + scope.FindVar(Input(kParallelScopes))->GetMutable(); auto outside_og_names = Inputs(framework::GradVarName(kOutputs)); auto inside_og_names = @@ -272,9 +272,9 @@ class WhileGradOpDescMaker : public framework::SingleGradOpDescMaker { std::copy(extra_inputs.begin(), extra_inputs.end(), extra_inputs_list.begin()); grad->SetInput(framework::GradVarName(kOutputs), extra_inputs_list); - grad->SetInput(kStepScopes, Output(kStepScopes)); + grad->SetInput(kParallelScopes, Output(kParallelScopes)); grad->SetAttrMap(this->Attrs()); - grad->SetBlockAttr(kStepBlock, *grad_block_[0]); + grad->SetBlockAttr(kParallelBlock, *grad_block_[0]); // record the original output gradient names, since the gradient name of // while operator could be renamed. grad->SetAttr("original_output_grad", extra_inputs_list); From 973aec2c51ad50b4d8d29971be84c8b91c84af64 Mon Sep 17 00:00:00 2001 From: Yang Yang Date: Tue, 19 Dec 2017 05:46:27 +0000 Subject: [PATCH 02/49] modify block name --- paddle/operators/parallel_do_op.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paddle/operators/parallel_do_op.cc b/paddle/operators/parallel_do_op.cc index a2a12cfdf0..3ab4bd3df2 100644 --- a/paddle/operators/parallel_do_op.cc +++ b/paddle/operators/parallel_do_op.cc @@ -25,7 +25,7 @@ constexpr char kParameters[] = "parameters"; constexpr char kPlaces[] = "places"; constexpr char kParallelBlock[] = "parallel_block"; constexpr char kOutputs[] = "outputs"; -constexpr char kParallelScopes[] = "parallel_scopes"; +constexpr char kParallelScopes[] = "sub_block"; // #define GRAD_SUFFIX "@GRAD" // constexpr char kInputGrads[] = "inputs" GRAD_SUFFIX; // constexpr char kOutputGrads[] = "outputs" GRAD_SUFFIX; From aea5ccca703817f41b8fa0e258eb45ea7d0c4f29 Mon Sep 17 00:00:00 2001 From: Yang Yang Date: Tue, 19 Dec 2017 06:03:39 +0000 Subject: [PATCH 03/49] revise typo --- paddle/operators/recurrent_op.cc | 18 +++++++++--------- paddle/operators/while_op.cc | 18 +++++++++--------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/paddle/operators/recurrent_op.cc b/paddle/operators/recurrent_op.cc index e9d4b449fd..232d926f7b 100644 --- a/paddle/operators/recurrent_op.cc +++ b/paddle/operators/recurrent_op.cc @@ -22,7 +22,7 @@ constexpr char kInputs[] = "inputs"; constexpr char kInitialStates[] = "initial_states"; constexpr char kParameters[] = "parameters"; constexpr char kOutputs[] = "outputs"; -constexpr char kParallelScopes[] = "step_scopes"; +constexpr char kStepScopes[] = "step_scopes"; constexpr char kExStates[] = "ex_states"; constexpr char kStates[] = "states"; constexpr char kStepBlock[] = "sub_block"; @@ -234,7 +234,7 @@ class RecurrentOp : public RecurrentBase { auto reverse = Attr(kReverse); framework::Executor executor(dev_ctx); - auto *block = Attr(kParallelBlock); + auto *block = Attr(kStepBlock); auto *program = block->Program(); for (size_t i = 0; i < seq_len; ++i) { @@ -295,7 +295,7 @@ class RecurrentOp : public RecurrentBase { private: StepScopes CreateStepScopes(const framework::Scope &scope, size_t seq_len) const { - auto *var = scope.FindVar(Output(kParallelScopes)); + auto *var = scope.FindVar(Output(kStepScopes)); PADDLE_ENFORCE(var != nullptr); return StepScopes(scope, var->GetMutable(), Attr(kIsTrain), seq_len); @@ -317,7 +317,7 @@ class RecurrentGradOp : public RecurrentBase { auto reverse = Attr(kReverse); framework::Executor executor(dev_ctx); - auto *block = Attr(kParallelBlock); + auto *block = Attr(kStepBlock); auto *program = block->Program(); for (size_t step_id = 0; step_id < seq_len; ++step_id) { @@ -465,7 +465,7 @@ class RecurrentGradOp : public RecurrentBase { private: StepScopes CreateStepScopes(const framework::Scope &scope, size_t seq_len) const { - auto *var = scope.FindVar(Input(kParallelScopes)); + auto *var = scope.FindVar(Input(kStepScopes)); PADDLE_ENFORCE(var != nullptr); return StepScopes(scope, var->GetMutable(), Attr(kIsTrain), seq_len, true /*is_backward*/); @@ -510,7 +510,7 @@ class RecurrentOpProtoMaker : public framework::OpProtoAndCheckerMaker { AddOutput(kOutputs, "The output sequence of RNN. The sequence length must be same.") .AsDuplicable(); - AddOutput(kParallelScopes, + AddOutput(kStepScopes, "StepScopes contain all local variables in each time step."); AddAttr>(kExStates, string::Sprintf( @@ -523,7 +523,7 @@ The ex-state means the state value in the ex-timestep or the previous time step string::Sprintf( "The state variable names. [%s, %s, %s] must be the same order", kExStates, kStates, kInitStateGrads)); - AddAttr(kParallelBlock, + AddAttr(kStepBlock, "The step block inside RNN"); AddAttr(kReverse, R"DOC(Calculate RNN reversely or not. By default reverse=False @@ -576,7 +576,7 @@ class RecurrentGradOpDescMaker : public framework::SingleGradOpDescMaker { } for (auto &output_param : this->OutputNames()) { - if (output_param == kParallelScopes) { + if (output_param == kStepScopes) { grad->SetInput(output_param, this->Output(output_param)); grad->SetInput(framework::GradVarName(output_param), this->Output(output_param)); @@ -587,7 +587,7 @@ class RecurrentGradOpDescMaker : public framework::SingleGradOpDescMaker { } } grad->SetAttrMap(this->Attrs()); - grad->SetBlockAttr(kParallelBlock, *grad_block_[0]); + grad->SetBlockAttr(kStepBlock, *grad_block_[0]); return std::unique_ptr(grad); } diff --git a/paddle/operators/while_op.cc b/paddle/operators/while_op.cc index af992da5b0..9a092a570f 100644 --- a/paddle/operators/while_op.cc +++ b/paddle/operators/while_op.cc @@ -27,7 +27,7 @@ using LoDTensor = framework::LoDTensor; constexpr char kStepBlock[] = "sub_block"; constexpr char kCondition[] = "Condition"; -constexpr char kParallelScopes[] = "StepScopes"; +constexpr char kStepScopes[] = "StepScopes"; constexpr char kParameters[] = "X"; constexpr char kParamGrads[] = "X@GRAD"; constexpr char kOutputs[] = "Out"; @@ -46,11 +46,11 @@ class WhileOp : public framework::OperatorBase { PADDLE_ENFORCE_EQ(cond.dims(), paddle::framework::make_ddim({1})); framework::Executor executor(dev_ctx); - auto *block = Attr(kParallelBlock); + auto *block = Attr(kStepBlock); auto *program = block->Program(); auto step_scopes = - scope.FindVar(Output(kParallelScopes))->GetMutable(); + scope.FindVar(Output(kStepScopes))->GetMutable(); while (cond.data()[0]) { auto ¤t_scope = scope.NewScope(); @@ -78,11 +78,11 @@ class WhileOpMaker : public framework::OpProtoAndCheckerMaker { "A set of variables, which will be assigned with values " "generated by the operators inside the block of While Op.") .AsDuplicable(); - AddOutput(kParallelScopes, + AddOutput(kStepScopes, "(StepScopeVar) A vector of local scope, which size equals the " "step number of While Op. The i'th scope storages temporary " "variables generated in the i'th step."); - AddAttr(kParallelBlock, + AddAttr(kStepBlock, "The step block inside WhileOp"); AddComment(R"DOC( )DOC"); @@ -99,11 +99,11 @@ class WhileGradOp : public framework::OperatorBase { void Run(const framework::Scope &scope, const platform::DeviceContext &dev_ctx) const override { framework::Executor executor(dev_ctx); - auto *block = Attr(kParallelBlock); + auto *block = Attr(kStepBlock); auto *program = block->Program(); auto *step_scopes = - scope.FindVar(Input(kParallelScopes))->GetMutable(); + scope.FindVar(Input(kStepScopes))->GetMutable(); auto outside_og_names = Inputs(framework::GradVarName(kOutputs)); auto inside_og_names = @@ -272,9 +272,9 @@ class WhileGradOpDescMaker : public framework::SingleGradOpDescMaker { std::copy(extra_inputs.begin(), extra_inputs.end(), extra_inputs_list.begin()); grad->SetInput(framework::GradVarName(kOutputs), extra_inputs_list); - grad->SetInput(kParallelScopes, Output(kParallelScopes)); + grad->SetInput(kStepScopes, Output(kStepScopes)); grad->SetAttrMap(this->Attrs()); - grad->SetBlockAttr(kParallelBlock, *grad_block_[0]); + grad->SetBlockAttr(kStepBlock, *grad_block_[0]); // record the original output gradient names, since the gradient name of // while operator could be renamed. grad->SetAttr("original_output_grad", extra_inputs_list); From b2ee91903daedcd7a2c0eda9096da588811dacae Mon Sep 17 00:00:00 2001 From: Yang Yang Date: Tue, 19 Dec 2017 08:56:03 +0000 Subject: [PATCH 04/49] add parallel_do test --- paddle/operators/parallel_do_op.cc | 4 +- python/paddle/v2/fluid/layers/control_flow.py | 122 +++++++++++++++--- 2 files changed, 107 insertions(+), 19 deletions(-) diff --git a/paddle/operators/parallel_do_op.cc b/paddle/operators/parallel_do_op.cc index 3ab4bd3df2..4c026c2239 100644 --- a/paddle/operators/parallel_do_op.cc +++ b/paddle/operators/parallel_do_op.cc @@ -23,9 +23,9 @@ namespace operators { constexpr char kInputs[] = "inputs"; constexpr char kParameters[] = "parameters"; constexpr char kPlaces[] = "places"; -constexpr char kParallelBlock[] = "parallel_block"; +constexpr char kParallelBlock[] = "sub_block"; constexpr char kOutputs[] = "outputs"; -constexpr char kParallelScopes[] = "sub_block"; +constexpr char kParallelScopes[] = "sub_scopes"; // #define GRAD_SUFFIX "@GRAD" // constexpr char kInputGrads[] = "inputs" GRAD_SUFFIX; // constexpr char kOutputGrads[] = "outputs" GRAD_SUFFIX; diff --git a/python/paddle/v2/fluid/layers/control_flow.py b/python/paddle/v2/fluid/layers/control_flow.py index dc6c0e7f51..4791d74970 100644 --- a/python/paddle/v2/fluid/layers/control_flow.py +++ b/python/paddle/v2/fluid/layers/control_flow.py @@ -5,12 +5,12 @@ from tensor import assign, fill_constant import contextlib __all__ = [ - 'split_lod_tensor', 'merge_lod_tensor', 'BlockGuard', 'StaticRNNGuard', - 'StaticRNNMemoryLink', 'WhileGuard', 'While', 'lod_rank_table', - 'max_sequence_len', 'topk', 'lod_tensor_to_array', 'array_to_lod_tensor', - 'increment', 'array_write', 'create_array', 'less_than', 'array_read', - 'shrink_memory', 'array_length', 'IfElse', 'DynamicRNN', 'ConditionalBlock', - 'StaticRNN' + 'split_lod_tensor', 'merge_lod_tensor', 'BlockGuard', + 'BlockGuardWithCompletion', 'StaticRNNMemoryLink', 'WhileGuard', 'While', + 'lod_rank_table', 'max_sequence_len', 'topk', 'lod_tensor_to_array', + 'array_to_lod_tensor', 'increment', 'array_write', 'create_array', + 'less_than', 'array_read', 'shrink_memory', 'array_length', 'IfElse', + 'DynamicRNN', 'ConditionalBlock', 'StaticRNN', 'ParallelDo' ] @@ -67,29 +67,117 @@ class BlockGuard(object): return True -class StaticRNNGuard(BlockGuard): +class ParallelDo(object): """ - StaticRNNGuard class. + ParallelDo class. - StaticRNNGuard class is used to create a StaticRNN block in a program. + ParallelDo class is used to create a ParallelDo. + """ + + def __init__(self, places, name=None): + self.helper = LayerHelper("parallel_do", name=name) + self.inputs = [] + self.places = places + self.outputs = [] + self.status = StaticRNN.BEFORE_RNN_BLOCK + + def do(self): + return BlockGuardWithCompletion(self) + + def parent_block(self): + prog = self.helper.main_program + parent_idx = prog.current_block().parent_idx + assert parent_idx >= 0 + parent_block = prog.block(parent_idx) + return parent_block + + def __call__(self, *args, **kwargs): + if self.status != StaticRNN.AFTER_RNN_BLOCK: + raise ValueError("RNN output can only be retrieved after rnn block") + if len(self.outputs) == 0: + raise ValueError("RNN has no output") + elif len(self.outputs) == 1: + return self.outputs[0] + else: + return self.outputs + + def read_input(self, var): + self.inputs.append(var) + + def write_output(self, var): + self.outputs.append(var) + + def get_parameters(self): + main_program = self.helper.main_program + current_block = main_program.current_block() + parent_block = self.parent_block() + + local_inputs = set() + + for op in current_block.ops: + for oname in op.output_names: + for out_var_name in op.output(oname): + local_inputs.add(out_var_name) + + for var in self.inputs: + local_inputs.add(var.name) + + params = list() + for op in current_block.ops: + for iname in op.input_names: + for in_var_name in op.input(iname): + if in_var_name not in local_inputs: + params.append(in_var_name) + + return [parent_block.var(name) for name in params] + + def complete_op(self): + main_program = self.helper.main_program + current_block = main_program.current_block() + parent_block = self.parent_block() + + step_scope = parent_block.create_var( + type=core.VarDesc.VarType.STEP_SCOPES) + + inputs = [parent_block.var(i.name) for i in self.inputs] + + parent_block.append_op( + type='parallel_do', + inputs={ + 'inputs': inputs, + 'parameters': self.get_parameters(), + 'places': self.places + }, + outputs={'outputs': self.outputs, + 'step_scopes': [step_scope]}, + attrs={'sub_block': current_block}) + + +class BlockGuardWithCompletion(BlockGuard): + """ + BlockGuardWithCompletion class. + + BlockGuardWithCompletion class is used to create an op with a block in a program. """ def __init__(self, rnn): - if not isinstance(rnn, StaticRNN): - raise TypeError("StaticRNNGuard takes a StaticRNN") - super(StaticRNNGuard, self).__init__(rnn.helper.main_program) + if not (isinstance(rnn, StaticRNN) or isinstance(rnn, ParallelDo)): + raise TypeError( + "BlockGuardWithCompletion takes a StaticRNN or ParallelDo") + super(BlockGuardWithCompletion, self).__init__(rnn.helper.main_program) self.rnn = rnn def __enter__(self): self.rnn.status = StaticRNN.IN_RNN_BLOCK - return super(StaticRNNGuard, self).__enter__() + return super(BlockGuardWithCompletion, self).__enter__() def __exit__(self, exc_type, exc_val, exc_tb): if exc_type is not None: return False self.rnn.status = StaticRNN.AFTER_RNN_BLOCK - self.rnn.complete_rnn_op() - return super(StaticRNNGuard, self).__exit__(exc_type, exc_val, exc_tb) + self.rnn.complete_op() + return super(BlockGuardWithCompletion, self).__exit__(exc_type, exc_val, + exc_tb) class StaticRNNMemoryLink(object): @@ -135,7 +223,7 @@ class StaticRNN(object): self.seq_len = None def step(self): - return StaticRNNGuard(self) + return BlockGuardWithCompletion(self) def _assert_in_rnn_block_(self, method): if self.status != StaticRNN.IN_RNN_BLOCK: @@ -251,7 +339,7 @@ class StaticRNN(object): else: return self.outputs - def complete_rnn_op(self): + def complete_op(self): main_program = self.helper.main_program rnn_block = main_program.current_block() parent_block = self.parent_block() From 2f56d4b3d4a048b98a19872a6f69f1b8c7cf29a0 Mon Sep 17 00:00:00 2001 From: Yang Yang Date: Tue, 19 Dec 2017 10:54:45 +0000 Subject: [PATCH 05/49] forward pass compile time --- paddle/operators/parallel_do_op.cc | 6 ++-- python/paddle/v2/fluid/framework.py | 3 +- python/paddle/v2/fluid/layers/control_flow.py | 3 +- .../paddle/v2/fluid/tests/test_parallel_op.py | 33 +++++++++++++++++++ 4 files changed, 41 insertions(+), 4 deletions(-) create mode 100644 python/paddle/v2/fluid/tests/test_parallel_op.py diff --git a/paddle/operators/parallel_do_op.cc b/paddle/operators/parallel_do_op.cc index 4c026c2239..bde59c7e7a 100644 --- a/paddle/operators/parallel_do_op.cc +++ b/paddle/operators/parallel_do_op.cc @@ -23,9 +23,11 @@ namespace operators { constexpr char kInputs[] = "inputs"; constexpr char kParameters[] = "parameters"; constexpr char kPlaces[] = "places"; -constexpr char kParallelBlock[] = "sub_block"; + constexpr char kOutputs[] = "outputs"; -constexpr char kParallelScopes[] = "sub_scopes"; +constexpr char kParallelScopes[] = "parallel_scopes"; + +constexpr char kParallelBlock[] = "sub_block"; // #define GRAD_SUFFIX "@GRAD" // constexpr char kInputGrads[] = "inputs" GRAD_SUFFIX; // constexpr char kOutputGrads[] = "outputs" GRAD_SUFFIX; diff --git a/python/paddle/v2/fluid/framework.py b/python/paddle/v2/fluid/framework.py index bf0cd275b6..14e0734331 100644 --- a/python/paddle/v2/fluid/framework.py +++ b/python/paddle/v2/fluid/framework.py @@ -424,7 +424,8 @@ class Operator(object): self.desc.check_attrs() no_kernel_op_set = { 'feed', 'fetch', 'save', 'load', 'recurrent', - 'rnn_memory_helper_grad', 'conditional_block', 'while' + 'rnn_memory_helper_grad', 'conditional_block', 'while', + 'parallel_do' } if type not in no_kernel_op_set: self.desc.infer_var_type(self.block.desc) diff --git a/python/paddle/v2/fluid/layers/control_flow.py b/python/paddle/v2/fluid/layers/control_flow.py index 4791d74970..09ab9726d1 100644 --- a/python/paddle/v2/fluid/layers/control_flow.py +++ b/python/paddle/v2/fluid/layers/control_flow.py @@ -103,6 +103,7 @@ class ParallelDo(object): def read_input(self, var): self.inputs.append(var) + return var def write_output(self, var): self.outputs.append(var) @@ -149,7 +150,7 @@ class ParallelDo(object): 'places': self.places }, outputs={'outputs': self.outputs, - 'step_scopes': [step_scope]}, + 'parallel_scopes': [step_scope]}, attrs={'sub_block': current_block}) diff --git a/python/paddle/v2/fluid/tests/test_parallel_op.py b/python/paddle/v2/fluid/tests/test_parallel_op.py new file mode 100644 index 0000000000..1e64303284 --- /dev/null +++ b/python/paddle/v2/fluid/tests/test_parallel_op.py @@ -0,0 +1,33 @@ +import unittest + +import paddle.v2.fluid.layers as layers +import paddle.v2.fluid as fluid +from paddle.v2.fluid.framework import Program +from paddle.v2.fluid.executor import Executor +from paddle.v2.fluid.backward import append_backward_ops +import numpy as np +import paddle.v2.fluid.core as core + + +class ParallelOpTest(unittest.TestCase): + def setUp(self): + x = layers.data( + shape=[2, 3, 4], dtype='float32', name='x', append_batch_size=False) + + places = fluid.default_main_program().global_block().create_var() + pd = layers.ParallelDo(places=places) + + with pd.do(): + data = pd.read_input(x) + hidden = layers.fc(input=data, size=7) + pd.write_output(hidden) + data = pd() + print data + print fluid.default_main_program() + + def test_forward(self): + pass + + +if __name__ == '__main__': + unittest.main() From f899150e0a45f97e836f02ad989e30c0f337310c Mon Sep 17 00:00:00 2001 From: Yang Yang Date: Thu, 21 Dec 2017 08:24:17 +0000 Subject: [PATCH 06/49] pass forward runtime --- paddle/framework/backward.cc | 3 +- paddle/framework/lod_tensor.cc | 25 ++++ paddle/framework/lod_tensor.h | 3 + paddle/framework/operator.cc | 20 ++- paddle/framework/tensor.h | 11 ++ paddle/operators/parallel_do_op.cc | 132 ++++++++++++------ python/paddle/v2/fluid/layers/control_flow.py | 13 +- .../paddle/v2/fluid/tests/test_parallel_op.py | 18 ++- 8 files changed, 177 insertions(+), 48 deletions(-) diff --git a/paddle/framework/backward.cc b/paddle/framework/backward.cc index faf6e60cbd..358ddae083 100644 --- a/paddle/framework/backward.cc +++ b/paddle/framework/backward.cc @@ -429,7 +429,8 @@ std::vector> MakeBlockBackward( VLOG(5) << "Making backward " << (*it)->Type() << " op"; std::vector> op_grads; - if ((*it)->Type() == "recurrent" || (*it)->Type() == "while") { + if ((*it)->Type() == "recurrent" || (*it)->Type() == "while" || + (*it)->Type() == "parallel_do") { int step_block_idx = (*it)->GetBlockAttr("sub_block"); BlockDescBind* backward_block = CreateStepBlock( program_desc, no_grad_vars, grad_to_var, step_block_idx); diff --git a/paddle/framework/lod_tensor.cc b/paddle/framework/lod_tensor.cc index fdf6de4bab..4198847ad0 100644 --- a/paddle/framework/lod_tensor.cc +++ b/paddle/framework/lod_tensor.cc @@ -314,5 +314,30 @@ void DeserializeFromStream(std::istream &is, LoDTensor *tensor) { } } +void LoDTensor::MergeLoDTensor( + const std::vector &lod_tensors, platform::Place place) { + PADDLE_ENFORCE(platform::is_cpu_place(place)); + PADDLE_ENFORCE(!lod_tensors.empty()); + + framework::DDim new_dim = lod_tensors[0]->dims(); + std::type_index new_type = lod_tensors[0]->type(); + for (auto *lod : lod_tensors) { + PADDLE_ENFORCE(new_dim == lod->dims()); + PADDLE_ENFORCE(new_type == lod->type()); + PADDLE_ENFORCE(platform::is_cpu_place(lod->place())); + } + new_dim[0] *= lod_tensors.size(); + Resize(new_dim); + + auto *dst_ptr = reinterpret_cast(mutable_data(place, new_type)); + for (auto *src : lod_tensors) { + auto size = src->numel() * SizeOfType(src->type()); + memory::Copy(boost::get(place), dst_ptr, + boost::get(src->place()), + src->data(), size); + dst_ptr += size; + } +} + } // namespace framework } // namespace paddle diff --git a/paddle/framework/lod_tensor.h b/paddle/framework/lod_tensor.h index 9411c96aea..989d8c1c4f 100644 --- a/paddle/framework/lod_tensor.h +++ b/paddle/framework/lod_tensor.h @@ -144,6 +144,9 @@ class LoDTensor : public Tensor { */ void ShrinkInLevel(size_t level, size_t elem_begin, size_t elem_end); + void MergeLoDTensor(const std::vector& lod_tensors, + platform::Place place); + private: LoD lod_; }; diff --git a/paddle/framework/operator.cc b/paddle/framework/operator.cc index e83d754783..26bc646753 100644 --- a/paddle/framework/operator.cc +++ b/paddle/framework/operator.cc @@ -179,10 +179,13 @@ static const Tensor* GetTensorFromVar(const Variable* var) { const Tensor* t = nullptr; if (var->IsType()) { t = &(var->Get()); + } else if (var->IsType()) { + t = &(var->Get()); } else if (var->IsType()) { t = &(var->Get().value()); } else { - PADDLE_THROW("Variable type must be LoDTensor/SelectedRows."); + PADDLE_THROW("Variable type_id %s, expect LoDTensor/SelectedRows.", + var->Type().name()); } return t; } @@ -191,10 +194,13 @@ static Tensor* GetMutableTensorFromVar(Variable* var) { Tensor* t = nullptr; if (var->IsType()) { t = var->GetMutable(); + } else if (var->IsType()) { + t = var->GetMutable(); } else if (var->IsType()) { t = var->GetMutable()->mutable_value(); } else { - PADDLE_THROW("Variable type must be LoDTensor/SelectedRows."); + PADDLE_THROW("Variable type_id %s, expect LoDTensor/SelectedRows.", + var->Type().name()); } return t; } @@ -359,10 +365,13 @@ class RuntimeInferShapeContext : public InferShapeContext { Variable* var = scope_.FindVar(name); if (var->IsType()) { return var->Get().dims(); + } else if (var->IsType()) { + return var->Get().dims(); } else if (var->IsType()) { return var->Get().GetCompleteDims(); } else { - PADDLE_THROW("Variable type must be LoDTensor/SelectedRows."); + PADDLE_THROW("Variable %s type_id %s, expect LoDTensor/SelectedRows.", + name, var->Type().name()); } } @@ -370,10 +379,13 @@ class RuntimeInferShapeContext : public InferShapeContext { Variable* var = scope_.FindVar(name); if (var->IsType()) { var->GetMutable()->Resize(dim); + } else if (var->IsType()) { + var->GetMutable()->Resize(dim); } else if (var->IsType()) { var->GetMutable()->set_height(dim[0]); } else { - PADDLE_THROW("Variable type must be LoDTensor/SelectedRows."); + PADDLE_THROW("Variable %s type_id %s, expect LoDTensor/SelectedRows.", + name, var->Type().name()); } } diff --git a/paddle/framework/tensor.h b/paddle/framework/tensor.h index 6a0c5133c9..837f63c706 100644 --- a/paddle/framework/tensor.h +++ b/paddle/framework/tensor.h @@ -55,6 +55,8 @@ class Tensor { template inline const T* data() const; + inline void switch_place(platform::Place new_place); + /** * @brief Return a pointer to mutable memory block. * @note If not exist, then allocation. @@ -183,6 +185,15 @@ class Tensor { size_t offset_; }; +inline void Tensor::switch_place(platform::Place new_place) { + if (holder_->place() == new_place) { + return; + } + + // TODO(tonyyang-svail): do memcpy here. + PADDLE_THROW("Not Implemented"); +} + } // namespace framework } // namespace paddle diff --git a/paddle/operators/parallel_do_op.cc b/paddle/operators/parallel_do_op.cc index bde59c7e7a..c0c1de7369 100644 --- a/paddle/operators/parallel_do_op.cc +++ b/paddle/operators/parallel_do_op.cc @@ -13,9 +13,11 @@ limitations under the License. */ #include +#include "chunk_eval_op.h" #include "paddle/framework/executor.h" #include "paddle/framework/op_registry.h" #include "paddle/framework/operator.h" +#include "paddle/platform/place.h" namespace paddle { namespace operators { @@ -28,10 +30,6 @@ constexpr char kOutputs[] = "outputs"; constexpr char kParallelScopes[] = "parallel_scopes"; constexpr char kParallelBlock[] = "sub_block"; -// #define GRAD_SUFFIX "@GRAD" -// constexpr char kInputGrads[] = "inputs" GRAD_SUFFIX; -// constexpr char kOutputGrads[] = "outputs" GRAD_SUFFIX; -// constexpr char kParamGrads[] = "parameters" GRAD_SUFFIX; using ParallelScopeVar = std::vector; using OperatorBase = framework::OperatorBase; @@ -46,21 +44,66 @@ class ParallelDoOp : public OperatorBase { void Run(const framework::Scope &scope, const platform::DeviceContext &dev_ctx) const override { - // create scope - // copy parameters - } -}; + auto *block = Attr(kParallelBlock); + auto *program = block->Program(); -class ParallelDoGradOp : public OperatorBase { - public: - ParallelDoGradOp(const std::string &type, - const framework::VariableNameMap &inputs, - const framework::VariableNameMap &outputs, - const framework::AttributeMap &attrs) - : OperatorBase(type, inputs, outputs, attrs) {} + // TODO(tonyyang-svail): get places from input + std::vector places; + places.emplace_back(platform::CPUPlace()); + places.emplace_back(platform::CPUPlace()); - void Run(const framework::Scope &scope, - const platform::DeviceContext &dev_ctx) const override {} + std::vector sub_scopes; + for (int place_idx = 0; place_idx < places.size(); ++place_idx) { + VLOG(3) << "Run " << place_idx; + + sub_scopes.push_back(&scope.NewScope()); + + auto &place = places[place_idx]; + auto *cur_scope = sub_scopes[place_idx]; + + // copy parameter + if (dev_ctx.GetPlace() != place) { + PADDLE_THROW("Not Implemented"); + } + + // feed input + for (auto &argu : Inputs(kInputs)) { + auto *var = scope.FindVar(argu); + const auto &tensor = var->Get(); + if (!tensor.lod().empty()) { + PADDLE_THROW("Disable parallel lod for now"); + } else { + PADDLE_ENFORCE(tensor.dims()[0] % places.size() == 0, + "Batch size should be divided by places size"); + int begin = place_idx * tensor.dims()[0] / places.size(); + int end = (place_idx + 1) * tensor.dims()[0] / places.size(); + auto feed_tensor = tensor.Slice(begin, end); + feed_tensor.switch_place(place); + + auto *cur_var = cur_scope->Var(argu); + auto *cur_tensor = cur_var->GetMutable(); + *cur_tensor = feed_tensor; + } + } + + // execute + auto executor = framework::Executor(place); + executor.Run(*program, cur_scope, block->ID(), + false /*create_local_scope*/); + } + + // merge output + for (auto &o_name : Outputs(kOutputs)) { + std::vector lod_tensors; + for (auto *sub_scope : sub_scopes) { + lod_tensors.push_back(&sub_scope->FindVar(o_name)->Get()); + } + + auto *lod_tensor_to_be_merged = + scope.FindVar(o_name)->GetMutable(); + lod_tensor_to_be_merged->MergeLoDTensor(lod_tensors, dev_ctx.GetPlace()); + } + } }; class ParallelDoOpProtoMaker : public framework::OpProtoAndCheckerMaker { @@ -80,16 +123,28 @@ ParallelDo Operator. } }; +class ParallelDoGradOp : public OperatorBase { + public: + ParallelDoGradOp(const std::string &type, + const framework::VariableNameMap &inputs, + const framework::VariableNameMap &outputs, + const framework::AttributeMap &attrs) + : OperatorBase(type, inputs, outputs, attrs) {} + + void Run(const framework::Scope &scope, + const platform::DeviceContext &dev_ctx) const override {} +}; + class ParallelDoGradOpDescMaker : public framework::SingleGradOpDescMaker { public: using framework::SingleGradOpDescMaker::SingleGradOpDescMaker; protected: virtual std::unique_ptr Apply() const { - PADDLE_THROW("Not Implemented"); auto *grad = new framework::OpDescBind(); - grad->SetType("recurrent_grad"); + grad->SetType("parallel_do_grad"); for (auto &input_param : this->InputNames()) { + LOG(INFO) << input_param; grad->SetInput(input_param, this->Input(input_param)); grad->SetOutput(framework::GradVarName(input_param), this->InputGrad(input_param)); @@ -116,26 +171,25 @@ class ParallelDoGradOpDescMaker : public framework::SingleGradOpDescMaker { class ParallelDoGradOpShapeInference : public framework::InferShapeBase { public: void operator()(framework::InferShapeContext *ctx) const override { - PADDLE_THROW("Not Implemented"); - // std::vector input{kInputs}; - // std::vector output{kOutputs}; - // for (auto &s : input) { - // PADDLE_ENFORCE(ctx->HasInputs(s)); - // PADDLE_ENFORCE(ctx->HasOutputs(framework::GradVarName(s)), - // "Cannot find the gradient variable %s", - // framework::GradVarName(s)); - // } - // for (auto &s : output) { - // PADDLE_ENFORCE(ctx->HasInputs(s)); - // } - // for (auto &s : input) { - // ctx->SetOutputsDim(framework::GradVarName(s), ctx->GetInputsDim(s)); - // } - // if (ctx->HasInputs(kParameters)) { - // PADDLE_ENFORCE(ctx->HasOutputs(framework::GradVarName(kParameters))); - // ctx->SetOutputsDim(framework::GradVarName(kParameters), - // ctx->GetInputsDim(kParameters)); - // } + std::vector input{kParameters, kInputs}; + std::vector output{kOutputs}; + for (auto &s : input) { + PADDLE_ENFORCE(ctx->HasInputs(s)); + PADDLE_ENFORCE(ctx->HasOutputs(framework::GradVarName(s)), + "Cannot find the gradient variable %s", + framework::GradVarName(s)); + } + for (auto &s : output) { + PADDLE_ENFORCE(ctx->HasInputs(s)); + } + for (auto &s : input) { + ctx->SetOutputsDim(framework::GradVarName(s), ctx->GetInputsDim(s)); + } + if (ctx->HasInputs(kParameters)) { + PADDLE_ENFORCE(ctx->HasOutputs(framework::GradVarName(kParameters))); + ctx->SetOutputsDim(framework::GradVarName(kParameters), + ctx->GetInputsDim(kParameters)); + } } }; diff --git a/python/paddle/v2/fluid/layers/control_flow.py b/python/paddle/v2/fluid/layers/control_flow.py index 09ab9726d1..aafecdafa2 100644 --- a/python/paddle/v2/fluid/layers/control_flow.py +++ b/python/paddle/v2/fluid/layers/control_flow.py @@ -140,7 +140,18 @@ class ParallelDo(object): step_scope = parent_block.create_var( type=core.VarDesc.VarType.STEP_SCOPES) + self.outputs = [ + parent_block.create_var( + name=o.name, + shape=o.shape, + dtype=o.dtype, + lod_level=o.lod_level, + persistable=o.persistable, + stop_gradient=o.stop_gradient) for o in self.outputs + ] + inputs = [parent_block.var(i.name) for i in self.inputs] + outputs = [parent_block.var(o.name) for o in self.outputs] parent_block.append_op( type='parallel_do', @@ -149,7 +160,7 @@ class ParallelDo(object): 'parameters': self.get_parameters(), 'places': self.places }, - outputs={'outputs': self.outputs, + outputs={'outputs': outputs, 'parallel_scopes': [step_scope]}, attrs={'sub_block': current_block}) diff --git a/python/paddle/v2/fluid/tests/test_parallel_op.py b/python/paddle/v2/fluid/tests/test_parallel_op.py index 1e64303284..61126cc9da 100644 --- a/python/paddle/v2/fluid/tests/test_parallel_op.py +++ b/python/paddle/v2/fluid/tests/test_parallel_op.py @@ -12,7 +12,11 @@ import paddle.v2.fluid.core as core class ParallelOpTest(unittest.TestCase): def setUp(self): x = layers.data( - shape=[2, 3, 4], dtype='float32', name='x', append_batch_size=False) + shape=[-1, 3, 4], + dtype='float32', + name='x', + append_batch_size=False, + stop_gradient=False) places = fluid.default_main_program().global_block().create_var() pd = layers.ParallelDo(places=places) @@ -22,8 +26,16 @@ class ParallelOpTest(unittest.TestCase): hidden = layers.fc(input=data, size=7) pd.write_output(hidden) data = pd() - print data - print fluid.default_main_program() + loss = layers.mean(x=data) + append_backward_ops(loss) + + exe = fluid.Executor(fluid.CPUPlace()) + exe.run(fluid.default_startup_program()) + exe.run(fluid.default_main_program(), + feed={ + x.name: np.random.uniform(0.1, 0.6, + (2, 3, 4)).astype("float32") + }) def test_forward(self): pass From f879ef23c3d2b364d5c590ff7b59ec8d16ccfb34 Mon Sep 17 00:00:00 2001 From: Yang Yang Date: Fri, 22 Dec 2017 03:51:11 +0000 Subject: [PATCH 07/49] pass forward backward runtime --- paddle/framework/lod_tensor.cc | 39 +++++++++++ paddle/framework/lod_tensor.h | 3 + paddle/operators/elementwise_op.h | 5 ++ paddle/operators/parallel_do_op.cc | 106 +++++++++++++++++++++-------- 4 files changed, 124 insertions(+), 29 deletions(-) diff --git a/paddle/framework/lod_tensor.cc b/paddle/framework/lod_tensor.cc index 4198847ad0..beb2edee23 100644 --- a/paddle/framework/lod_tensor.cc +++ b/paddle/framework/lod_tensor.cc @@ -314,6 +314,45 @@ void DeserializeFromStream(std::istream &is, LoDTensor *tensor) { } } +std::vector LoDTensor::SplitLoDTensor( + const std::vector places) const { + check_memory_size(); + // PADDLE_ENFORCE(lod().empty() || (lod().size() == 1 && lod()[0].empty()) + // , "Disable parallel lod for now"); + PADDLE_ENFORCE(lod().empty(), "Disable parallel lod for now"); + PADDLE_ENFORCE(dims()[0] % places.size() == 0, + "Batch size should be divided by places size"); + + std::vector lods; + for (int place_idx = 0; place_idx < places.size(); ++place_idx) { + int begin = place_idx * dims()[0] / places.size(); + int end = (place_idx + 1) * dims()[0] / places.size(); + auto src = Slice(begin, end); + + LoDTensor dst; + dst.Resize(src.dims()); + auto &dst_place = places[place_idx]; + auto dst_ptr = dst.mutable_data(dst_place, src.type()); + + // TODO(tonyyang-svail): + // change the following to framework::CopyFrom + auto src_place = src.place(); + auto src_ptr = src.data(); + auto size = src.numel() * SizeOfType(src.type()); + if (platform::is_cpu_place(src_place) && + platform::is_cpu_place(dst_place)) { + memory::Copy(boost::get(dst_place), dst_ptr, + boost::get(src_place), src_ptr, size); + } else { + PADDLE_THROW("Not Implemented"); + } + + lods.emplace_back(dst); + } + + return lods; +} + void LoDTensor::MergeLoDTensor( const std::vector &lod_tensors, platform::Place place) { PADDLE_ENFORCE(platform::is_cpu_place(place)); diff --git a/paddle/framework/lod_tensor.h b/paddle/framework/lod_tensor.h index 989d8c1c4f..fae36892f0 100644 --- a/paddle/framework/lod_tensor.h +++ b/paddle/framework/lod_tensor.h @@ -144,6 +144,9 @@ class LoDTensor : public Tensor { */ void ShrinkInLevel(size_t level, size_t elem_begin, size_t elem_end); + std::vector SplitLoDTensor( + const std::vector places) const; + void MergeLoDTensor(const std::vector& lod_tensors, platform::Place place); diff --git a/paddle/operators/elementwise_op.h b/paddle/operators/elementwise_op.h index ea533503e4..b8bbdb1e2a 100644 --- a/paddle/operators/elementwise_op.h +++ b/paddle/operators/elementwise_op.h @@ -34,6 +34,8 @@ class ElementwiseOp : public framework::OperatorWithKernel { auto x_dim = ctx->GetInputDim("X"); auto y_dim = ctx->GetInputDim("Y"); + LOG(INFO) << x_dim; + LOG(INFO) << y_dim; PADDLE_ENFORCE_GE(x_dim.size(), y_dim.size(), "Rank of first input must >= rank of second input."); ctx->SetOutputDim("Out", x_dim); @@ -118,6 +120,9 @@ class ElementwiseOpGrad : public framework::OperatorWithKernel { auto x_dims = ctx->GetInputDim("X"); auto y_dims = ctx->GetInputDim("Y"); auto out_dims = ctx->GetInputDim(framework::GradVarName("Out")); + LOG(INFO) << x_dims; + LOG(INFO) << y_dims; + LOG(INFO) << out_dims; PADDLE_ENFORCE_GE(x_dims.size(), y_dims.size(), "Rank of first input must >= rank of second input."); diff --git a/paddle/operators/parallel_do_op.cc b/paddle/operators/parallel_do_op.cc index c0c1de7369..b15d171b9b 100644 --- a/paddle/operators/parallel_do_op.cc +++ b/paddle/operators/parallel_do_op.cc @@ -13,11 +13,9 @@ limitations under the License. */ #include -#include "chunk_eval_op.h" + #include "paddle/framework/executor.h" #include "paddle/framework/op_registry.h" -#include "paddle/framework/operator.h" -#include "paddle/platform/place.h" namespace paddle { namespace operators { @@ -31,10 +29,31 @@ constexpr char kParallelScopes[] = "parallel_scopes"; constexpr char kParallelBlock[] = "sub_block"; -using ParallelScopeVar = std::vector; +// using ParallelScopeVar = std::vector; +using LoDTensor = framework::LoDTensor; using OperatorBase = framework::OperatorBase; -class ParallelDoOp : public OperatorBase { +void SplitTensorAndMoveTensorToScopes( + const framework::Scope &scope, + const std::vector &sub_scopes, + const std::vector &places, + const std::vector &names) { + for (auto &argu : names) { + auto *var = scope.FindVar(argu); + const auto &tensor = var->Get(); + auto lod_tensors = tensor.SplitLoDTensor(places); + + for (auto &lod : lod_tensors) { + LOG(INFO) << lod.dims(); + } + + for (int i = 0; i < sub_scopes.size(); ++i) { + *sub_scopes[i]->Var(argu)->GetMutable() = lod_tensors[i]; + } + } +} + +class ParallelDoOp : public framework::OperatorBase { public: ParallelDoOp(const std::string &type, const framework::VariableNameMap &inputs, @@ -52,11 +71,18 @@ class ParallelDoOp : public OperatorBase { places.emplace_back(platform::CPUPlace()); places.emplace_back(platform::CPUPlace()); - std::vector sub_scopes; + auto &sub_scopes = *scope.FindVar(Output(kParallelScopes)) + ->GetMutable>(); + // std::vector sub_scopes; for (int place_idx = 0; place_idx < places.size(); ++place_idx) { - VLOG(3) << "Run " << place_idx; - sub_scopes.push_back(&scope.NewScope()); + } + + SplitTensorAndMoveTensorToScopes(scope, sub_scopes, places, + Inputs(kInputs)); + + for (int place_idx = 0; place_idx < places.size(); ++place_idx) { + VLOG(3) << "Run " << place_idx; auto &place = places[place_idx]; auto *cur_scope = sub_scopes[place_idx]; @@ -66,26 +92,6 @@ class ParallelDoOp : public OperatorBase { PADDLE_THROW("Not Implemented"); } - // feed input - for (auto &argu : Inputs(kInputs)) { - auto *var = scope.FindVar(argu); - const auto &tensor = var->Get(); - if (!tensor.lod().empty()) { - PADDLE_THROW("Disable parallel lod for now"); - } else { - PADDLE_ENFORCE(tensor.dims()[0] % places.size() == 0, - "Batch size should be divided by places size"); - int begin = place_idx * tensor.dims()[0] / places.size(); - int end = (place_idx + 1) * tensor.dims()[0] / places.size(); - auto feed_tensor = tensor.Slice(begin, end); - feed_tensor.switch_place(place); - - auto *cur_var = cur_scope->Var(argu); - auto *cur_tensor = cur_var->GetMutable(); - *cur_tensor = feed_tensor; - } - } - // execute auto executor = framework::Executor(place); executor.Run(*program, cur_scope, block->ID(), @@ -132,7 +138,49 @@ class ParallelDoGradOp : public OperatorBase { : OperatorBase(type, inputs, outputs, attrs) {} void Run(const framework::Scope &scope, - const platform::DeviceContext &dev_ctx) const override {} + const platform::DeviceContext &dev_ctx) const override { + auto *block = Attr(kParallelBlock); + auto *program = block->Program(); + + auto &sub_scopes = scope.FindVar(Input(kParallelScopes)) + ->Get>(); + + // TODO(tonyyang-svail): get places from input + std::vector places; + places.emplace_back(platform::CPUPlace()); + places.emplace_back(platform::CPUPlace()); + + // feed output@grad + SplitTensorAndMoveTensorToScopes(scope, sub_scopes, places, + Inputs(framework::GradVarName(kOutputs))); + + for (auto &s : Inputs(framework::GradVarName(kOutputs))) { + LOG(INFO) << s; + LOG(INFO) << scope.FindVar(s)->Get().dims(); + for (auto *sub_scope : sub_scopes) { + LOG(INFO) << sub_scope->FindVar(s)->Get().dims(); + } + } + // exe run + for (int place_idx = 0; place_idx < places.size(); ++place_idx) { + VLOG(3) << "Run " << place_idx; + + auto &place = places[place_idx]; + auto *cur_scope = sub_scopes[place_idx]; + + // copy parameter + if (dev_ctx.GetPlace() != place) { + PADDLE_THROW("Not Implemented"); + } + + // execute + auto executor = framework::Executor(place); + executor.Run(*program, cur_scope, block->ID(), + false /*create_local_scope*/); + } + + // merge grad + } }; class ParallelDoGradOpDescMaker : public framework::SingleGradOpDescMaker { From 55b17c11710a595dc64ec9123012f329c6a36d62 Mon Sep 17 00:00:00 2001 From: Yibing Liu Date: Tue, 26 Dec 2017 12:28:45 +0000 Subject: [PATCH 08/49] Add the parsing part for the profiling tool --- paddle/platform/profiler.cc | 59 ++++++++++++++++++++++++++++++++ paddle/platform/profiler.h | 12 ++++++- paddle/platform/profiler_test.cc | 22 ++++++++++++ 3 files changed, 92 insertions(+), 1 deletion(-) diff --git a/paddle/platform/profiler.cc b/paddle/platform/profiler.cc index 40b34b732c..3d95097048 100644 --- a/paddle/platform/profiler.cc +++ b/paddle/platform/profiler.cc @@ -13,6 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. */ #include "paddle/platform/profiler.h" +#include namespace paddle { namespace platform { @@ -70,5 +71,63 @@ std::vector> DisableProfiler() { return result; } +void PushEvent(const std::string name, const platform::DeviceContext* dev_ctx) { + GetEventList().Record(EventKind::kPushRange, std::move(name), kThreadId, + dev_ctx); +} + +void PopEvent(const std::string name, const platform::DeviceContext* dev_ctx) { + GetEventList().Record(EventKind::kPopRange, std::move(name), kThreadId, + dev_ctx); +} + +void ParseEvents(std::vector> events) { + std::map> events_table; + for (size_t i = 0; i < events.size(); i++) { + std::list pushed_events; + for (size_t j = 0; j < events[i].size(); j++) { + if (events[i][j].kind() == "push") { + pushed_events.push_back(events[i][j]); + } + if (events[i][j].kind() == "pop") { + std::list::reverse_iterator rit = pushed_events.rbegin(); + while (rit->name() != events[i][j].name() && + rit != pushed_events.rend()) { + ++rit; + } + if (rit != pushed_events.rend()) { + Event pushed_event = *rit; + double cpu_time = rit->CpuElapsedUs(events[i][j]); + double cuda_time = 0; +#ifdef PADDLE_WITH_CUDA + cuda_time = rit->CudaElapsedUs(events[i][j]); +#endif + if (events_table.find(rit->name()) == events_table.end()) { + events_table[rit->name()] = std::make_tuple(1, cpu_time, cuda_time); + } else { + std::get<0>(events_table[rit->name()]) += 1; + std::get<1>(events_table[rit->name()]) += cpu_time; + std::get<2>(events_table[rit->name()]) += cuda_time; + } + // remove the start marker from the list + pushed_events.erase((++rit).base()); + } else { + std::cout << "Warning: can not find the start marker of event " + << events[i][j].name(); + } + } + } + } + // output events table + std::cout << "\nEvents\t\tCalls\t\tTotal CPU time\t\tTotal GPU time\n"; + for (std::map>::iterator it = + events_table.begin(); + it != events_table.end(); ++it) { + std::cout << it->first << "\t\t" << std::get<0>(it->second) << "\t\t" + << std::get<1>(it->second) << "\t\t" << std::get<2>(it->second) + << std::endl; + } +} + } // namespace platform } // namespace paddle diff --git a/paddle/platform/profiler.h b/paddle/platform/profiler.h index 2242635024..5f21ff8c1c 100644 --- a/paddle/platform/profiler.h +++ b/paddle/platform/profiler.h @@ -173,25 +173,35 @@ inline void Mark(const std::string name, GetEventList().Record(EventKind::kMark, std::move(name), kThreadId, dev_ctx); } +void PushEvent(const std::string name, + const platform::DeviceContext* dev_ctx = nullptr); + +void PopEvent(const std::string name, + const platform::DeviceContext* dev_ctx = nullptr); + struct RecordEvent { explicit RecordEvent(const std::string name, platform::DeviceContext* dev_ctx = nullptr) { if (kState == ProfilerState::kDisabled) return; dev_ctx_ = dev_ctx; + name_ = name; GetEventList().Record(EventKind::kPushRange, std::move(name), kThreadId, dev_ctx_); } ~RecordEvent() { if (kState == ProfilerState::kDisabled) return; - GetEventList().Record(EventKind::kPopRange, std::string(), kThreadId, + GetEventList().Record(EventKind::kPopRange, std::move(name_), kThreadId, dev_ctx_); } platform::DeviceContext* dev_ctx_; + std::string name_; }; void EnableProfiler(ProfilerState state); std::vector> DisableProfiler(); +void ParseEvents(std::vector>); + } // namespace platform } // namespace paddle diff --git a/paddle/platform/profiler_test.cc b/paddle/platform/profiler_test.cc index 5bd0a9d859..b2f1dea465 100644 --- a/paddle/platform/profiler_test.cc +++ b/paddle/platform/profiler_test.cc @@ -67,8 +67,29 @@ TEST(RecordEvent, RecordEvent) { #endif EnableProfiler(state); + /* Usage 1: + * PushEvent(evt_name, dev_ctx); + * ... + * code to time + * ... + * PopEvent(evt_name, dev_ctx); + */ for (int i = 1; i < 5; ++i) { std::string name = "op_" + std::to_string(i); + PushEvent(name, dev_ctx); + int counter = 1; + while (counter != i * 1000) counter++; + PopEvent(name, dev_ctx); + } + + /* Usage 2: + * { + * RecordEvent record_event(name, dev_ctx); + * ... + * } + */ + for (int i = 1; i < 5; ++i) { + std::string name = "evs_op_" + std::to_string(i); RecordEvent record_event(name, dev_ctx); int counter = 1; while (counter != i * 1000) counter++; @@ -77,6 +98,7 @@ TEST(RecordEvent, RecordEvent) { int cuda_startup_count = 0; int start_profiler_count = 0; int stop_profiler_count = 0; + ParseEvents(events); for (size_t i = 0; i < events.size(); ++i) { for (size_t j = 0; j < events[i].size(); ++j) { if (events[i][j].name() == "_cuda_startup_") ++cuda_startup_count; From cb0b81f97b5791a29aab73797abfa2820b89f8a3 Mon Sep 17 00:00:00 2001 From: Yang Yang Date: Wed, 3 Jan 2018 07:21:40 +0000 Subject: [PATCH 09/49] add << lodtensor --- paddle/framework/lod_tensor.cc | 16 ++++++++++++++++ paddle/framework/lod_tensor.h | 1 + paddle/operators/parallel_do_op.cc | 5 +++-- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/paddle/framework/lod_tensor.cc b/paddle/framework/lod_tensor.cc index beb2edee23..dcde3705d6 100644 --- a/paddle/framework/lod_tensor.cc +++ b/paddle/framework/lod_tensor.cc @@ -43,6 +43,22 @@ std::ostream &operator<<(std::ostream &os, const LoD &lod) { return os; } +std::ostream &operator<<(std::ostream &os, const LoDTensor &t) { + PADDLE_ENFORCE(platform::is_cpu_place(t.place())); + PADDLE_ENFORCE(t.type().hash_code() == typeid(float).hash_code()); + + os << "dim: " << t.dims() << "\n"; + os << "lod: " << t.lod() << "\n"; + + // only print first ten elements + int64_t size = t.numel() < 10 ? t.numel() : 10; + for (int64_t i = 0; i < size; ++i) { + os << t.data()[i] << " "; + } + + return os; +} + LoD SliceLevels(const LoD &in, size_t level_begin, size_t level_end) { LoD new_lod; new_lod.reserve(level_end - level_begin); diff --git a/paddle/framework/lod_tensor.h b/paddle/framework/lod_tensor.h index fae36892f0..7e44ffca3b 100644 --- a/paddle/framework/lod_tensor.h +++ b/paddle/framework/lod_tensor.h @@ -58,6 +58,7 @@ using Vector = thrust::host_vector< using LoD = std::vector>; std::ostream& operator<<(std::ostream& os, const LoD& lod); +std::ostream& operator<<(std::ostream& os, const LoDTensor& t); /* * Slice levels from a LoD. diff --git a/paddle/operators/parallel_do_op.cc b/paddle/operators/parallel_do_op.cc index b15d171b9b..fd48c1b54c 100644 --- a/paddle/operators/parallel_do_op.cc +++ b/paddle/operators/parallel_do_op.cc @@ -156,11 +156,12 @@ class ParallelDoGradOp : public OperatorBase { for (auto &s : Inputs(framework::GradVarName(kOutputs))) { LOG(INFO) << s; - LOG(INFO) << scope.FindVar(s)->Get().dims(); + LOG(INFO) << scope.FindVar(s)->Get(); for (auto *sub_scope : sub_scopes) { - LOG(INFO) << sub_scope->FindVar(s)->Get().dims(); + LOG(INFO) << sub_scope->FindVar(s)->Get(); } } + // exe run for (int place_idx = 0; place_idx < places.size(); ++place_idx) { VLOG(3) << "Run " << place_idx; From 72652845b63bec4308e04fb112a338a607d87284 Mon Sep 17 00:00:00 2001 From: tensor-tang Date: Wed, 3 Jan 2018 16:53:57 +0800 Subject: [PATCH 10/49] add MKLDNNDeviceContext --- paddle/platform/device_context.cc | 64 +++++++++++++++++++++++++++++++ paddle/platform/device_context.h | 63 ++++++++++++++++++++++++++++++ 2 files changed, 127 insertions(+) diff --git a/paddle/platform/device_context.cc b/paddle/platform/device_context.cc index ea07f2e002..9d72569f51 100644 --- a/paddle/platform/device_context.cc +++ b/paddle/platform/device_context.cc @@ -176,5 +176,69 @@ cudnnHandle_t CUDNNDeviceContext::cudnn_handle() const { return cudnn_handle_; } #endif +#ifdef PADDLE_WITH_MKLDNN +MKLDNNDeviceContext::MKLDNNDeviceContext(CPUPlace place) + : CPUDeviceContext(place), ready_(false) { + stream_.reset(new mkldnn::stream(mkldnn::stream::kind::eager)); + engine_.reset(new mkldnn::engine(mkldnn::engine::cpu, 0)); +} + +template +void MKLDNNDeviceContext::AddElement(const std::string& op_key, + const T& value) { + if (GetElement(op_key)) { + return; + } + GetElementPool().emplace(op_key, value); +} + +template +const T MKLDNNDeviceContext::GetElement(const std::string& op_key) const { + auto it = GetElementPool().find(op_key); + return it == GetElementPool().end() ? nullptr : it->second; +} + +template <> +const std::unordered_map>& +MKLDNNDeviceContext::GetElementPool() const { + return memory_pool_; +} + +template <> +const std::unordered_map>& +MKLDNNDeviceContext::GetElementPool() const { + return primitive_pool_; +} + +template <> +const std::unordered_map>& +MKLDNNDeviceContext::GetElementPool() const { + return primitive_desc_pool_; +} + +void MKLDNNDeviceContext::Execute(bool block) { + if (pipeline_.empty()) { + return; + } + ResetStream(); + stream_->submit(pipeline_).wait(block); + ready_ = false; + pipeline_.clear(); +} + +void MKLDNNDeviceContext::ResetStream() { + if (ready_) { + return; + } + // TODO(TJ): change me when mkldnn have specific method to reset this state + stream_.reset(new mkldnn::stream(mkldnn::stream::kind::eager)); + ready_ = true; +} + +#endif + } // namespace platform } // namespace paddle diff --git a/paddle/platform/device_context.h b/paddle/platform/device_context.h index 2b366e6383..faabb8575e 100644 --- a/paddle/platform/device_context.h +++ b/paddle/platform/device_context.h @@ -21,6 +21,10 @@ limitations under the License. */ #define EIGEN_USE_GPU #endif +#ifdef PADDLE_WITH_MKLDNN +#include "mkldnn.hpp" +#endif + #include "paddle/platform/enforce.h" #include "paddle/platform/place.h" #include "unsupported/Eigen/CXX11/Tensor" @@ -117,6 +121,65 @@ class CUDNNDeviceContext : public CUDADeviceContext { #endif +#ifdef PADDLE_WITH_MKLDNN +using MKLDNNStream = mkldnn::stream; +using MKLDNNEngine = mkldnn::engine; +using MKLDNNMemory = mkldnn::memory; +using MKLDNNPrimitive = mkldnn::primitive; +using MKLDNNPrimitiveDesc = mkldnn::handle; + +typedef std::shared_ptr MKLDNNEnginePtr; +typedef std::shared_ptr MKLDNNMemoryPtr; +typedef std::shared_ptr MKLDNNPrimitivePtr; +typedef std::shared_ptr MKLDNNPrimitiveDescPtr; +class MKLDNNDeviceContext : public CPUDeviceContext { + public: + explicit MKLDNNDeviceContext(CPUPlace place); + virtual ~MKLDNNDeviceContext(); + + /* \brief Add new element: memory, primitive or primitive desc */ + template + void AddElement(const std::string& op_key, const T& value); + + /* \brief Get existed element: memory, primitive or primitive desc */ + template + const T GetElement(const std::string& op_key) const; + + /* \brief Get element pool: memory, primitive or primitive desc pool */ + template + const std::unordered_map>& + GetElementPool() const; + + /* \brief Get the active engine */ + const MKLDNNEnginePtr GetEngine() const { return engine_; } + + /* \brief Submit primitive to pipeline */ + void Submit(const MKLDNNPrimitivePtr& p) { pipeline_.push_back(*p); } + + /*! \brief Execute all submitted primitives in pipeline */ + void Execute(bool block = true); + + protected: + /*! \brief Reset the stream to prepare next exectue */ + void ResetStream(); + + private: + std::unordered_map> + memory_pool_; + std::unordered_map> + primitive_pool_; + std::unordered_map> + primitive_desc_pool_; + std::vector pipeline_; + std::unique_ptr stream_; + MKLDNNEnginePtr engine_; + bool ready_; +}; +#endif + /*! \brief device context pool singleton */ class DeviceContextPool { public: From 03091ccbffcba56d2cc88b60b1c826398149db21 Mon Sep 17 00:00:00 2001 From: tensor-tang Date: Wed, 3 Jan 2018 16:55:13 +0800 Subject: [PATCH 11/49] add mkldnn_helper --- paddle/platform/device_context.h | 12 +---------- paddle/platform/mkldnn_helper.h | 34 ++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 11 deletions(-) create mode 100644 paddle/platform/mkldnn_helper.h diff --git a/paddle/platform/device_context.h b/paddle/platform/device_context.h index faabb8575e..68abe937b1 100644 --- a/paddle/platform/device_context.h +++ b/paddle/platform/device_context.h @@ -22,7 +22,7 @@ limitations under the License. */ #endif #ifdef PADDLE_WITH_MKLDNN -#include "mkldnn.hpp" +#include "paddle/platform/mkldnn_helper.h" #endif #include "paddle/platform/enforce.h" @@ -122,16 +122,6 @@ class CUDNNDeviceContext : public CUDADeviceContext { #endif #ifdef PADDLE_WITH_MKLDNN -using MKLDNNStream = mkldnn::stream; -using MKLDNNEngine = mkldnn::engine; -using MKLDNNMemory = mkldnn::memory; -using MKLDNNPrimitive = mkldnn::primitive; -using MKLDNNPrimitiveDesc = mkldnn::handle; - -typedef std::shared_ptr MKLDNNEnginePtr; -typedef std::shared_ptr MKLDNNMemoryPtr; -typedef std::shared_ptr MKLDNNPrimitivePtr; -typedef std::shared_ptr MKLDNNPrimitiveDescPtr; class MKLDNNDeviceContext : public CPUDeviceContext { public: explicit MKLDNNDeviceContext(CPUPlace place); diff --git a/paddle/platform/mkldnn_helper.h b/paddle/platform/mkldnn_helper.h new file mode 100644 index 0000000000..2649e4a5f3 --- /dev/null +++ b/paddle/platform/mkldnn_helper.h @@ -0,0 +1,34 @@ +/* Copyright (c) 2017 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 "mkldnn.hpp" + +namespace paddle { +namespace platform { + +using MKLDNNStream = mkldnn::stream; +using MKLDNNEngine = mkldnn::engine; +using MKLDNNMemory = mkldnn::memory; +using MKLDNNPrimitive = mkldnn::primitive; +using MKLDNNPrimitiveDesc = mkldnn::handle; + +typedef std::shared_ptr MKLDNNEnginePtr; +typedef std::shared_ptr MKLDNNMemoryPtr; +typedef std::shared_ptr MKLDNNPrimitivePtr; +typedef std::shared_ptr MKLDNNPrimitiveDescPtr; + +} // namespace platform +} // namespace paddle From 31fda46cf4e936df006d102d16829cb1cea68dca Mon Sep 17 00:00:00 2001 From: tensor-tang Date: Wed, 3 Jan 2018 17:25:00 +0800 Subject: [PATCH 12/49] fix mkldnn deps --- paddle/platform/CMakeLists.txt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/paddle/platform/CMakeLists.txt b/paddle/platform/CMakeLists.txt index 8c4803b973..44f6d85cd1 100644 --- a/paddle/platform/CMakeLists.txt +++ b/paddle/platform/CMakeLists.txt @@ -21,10 +21,16 @@ ELSE() set(GPU_CTX_DEPS) ENDIF() +IF(WITH_MKLDNN) + set(MKLDNN_CTX_DEPS mkldnn) +ELSE() + set(MKLDNN_CTX_DEPS) +ENDIF() + # memcpy deoends on device_context, here add deps individually for # avoiding cycle dependencies cc_library(device_context SRCS device_context.cc DEPS memory buddy_allocator - system_allocator memory_block meta_data meta_cache place eigen3 ${GPU_CTX_DEPS}) + system_allocator memory_block meta_data meta_cache place eigen3 ${GPU_CTX_DEPS} ${MKLDNN_CTX_DEPS}) nv_test(device_context_test SRCS device_context_test.cu DEPS device_context gpu_info) nv_test(cudnn_helper_test SRCS cudnn_helper_test.cc DEPS dynload_cuda) From 6004a2ed4f74b864b8ff886d20e18891ac0a8ef3 Mon Sep 17 00:00:00 2001 From: Yang Yang Date: Wed, 3 Jan 2018 09:38:13 +0000 Subject: [PATCH 13/49] add copy skeleton --- paddle/operators/parallel_do_op.cc | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/paddle/operators/parallel_do_op.cc b/paddle/operators/parallel_do_op.cc index fd48c1b54c..6ac480b576 100644 --- a/paddle/operators/parallel_do_op.cc +++ b/paddle/operators/parallel_do_op.cc @@ -47,7 +47,7 @@ void SplitTensorAndMoveTensorToScopes( LOG(INFO) << lod.dims(); } - for (int i = 0; i < sub_scopes.size(); ++i) { + for (size_t i = 0; i < sub_scopes.size(); ++i) { *sub_scopes[i]->Var(argu)->GetMutable() = lod_tensors[i]; } } @@ -73,15 +73,14 @@ class ParallelDoOp : public framework::OperatorBase { auto &sub_scopes = *scope.FindVar(Output(kParallelScopes)) ->GetMutable>(); - // std::vector sub_scopes; - for (int place_idx = 0; place_idx < places.size(); ++place_idx) { + for (size_t place_idx = 0; place_idx < places.size(); ++place_idx) { sub_scopes.push_back(&scope.NewScope()); } SplitTensorAndMoveTensorToScopes(scope, sub_scopes, places, Inputs(kInputs)); - for (int place_idx = 0; place_idx < places.size(); ++place_idx) { + for (size_t place_idx = 0; place_idx < places.size(); ++place_idx) { VLOG(3) << "Run " << place_idx; auto &place = places[place_idx]; @@ -163,17 +162,12 @@ class ParallelDoGradOp : public OperatorBase { } // exe run - for (int place_idx = 0; place_idx < places.size(); ++place_idx) { + for (size_t place_idx = 0; place_idx < places.size(); ++place_idx) { VLOG(3) << "Run " << place_idx; auto &place = places[place_idx]; auto *cur_scope = sub_scopes[place_idx]; - // copy parameter - if (dev_ctx.GetPlace() != place) { - PADDLE_THROW("Not Implemented"); - } - // execute auto executor = framework::Executor(place); executor.Run(*program, cur_scope, block->ID(), @@ -181,6 +175,21 @@ class ParallelDoGradOp : public OperatorBase { } // merge grad + for (auto &s : Outputs(framework::GradVarName(kParameters))) { + LOG(INFO) << s; + // std::string s_buf = s + "@BUF"; + // auto *t_buf = sub_scopes[0]->Var(s_buf)->GetMutable(); + for (size_t place_idx = 1; place_idx < places.size(); ++place_idx) { + LOG(INFO) << place_idx; + LOG(INFO) << sub_scopes[place_idx]->FindVar(s)->Get(); + // Copy grad[i] to grad_buf[0] + + // sum_op + } + + // Copy grad[0] to grad + // auto *t = scope.FindVar(s)->GetMutable(); + } } }; From 8ee17e965f8ff488c6ea4a0437653b504ccab9a2 Mon Sep 17 00:00:00 2001 From: Yang Yang Date: Wed, 3 Jan 2018 10:46:38 +0000 Subject: [PATCH 14/49] pass sgd at first iter --- paddle/operators/parallel_do_op.cc | 23 +++++++++++++------ .../paddle/v2/fluid/tests/test_parallel_op.py | 3 ++- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/paddle/operators/parallel_do_op.cc b/paddle/operators/parallel_do_op.cc index b067e3bda4..a81ddb25c4 100644 --- a/paddle/operators/parallel_do_op.cc +++ b/paddle/operators/parallel_do_op.cc @@ -185,18 +185,27 @@ class ParallelDoGradOp : public OperatorBase { // merge grad for (auto &s : Outputs(framework::GradVarName(kParameters))) { LOG(INFO) << s; - // std::string s_buf = s + "@BUF"; - // auto *t_buf = sub_scopes[0]->Var(s_buf)->GetMutable(); + + auto &t = sub_scopes[0]->FindVar(s)->Get(); + LOG(INFO) << t; + + std::string s_buf = s + "@BUF"; + auto *t_buf = sub_scopes[0]->Var(s_buf)->GetMutable(); + for (size_t place_idx = 1; place_idx < places.size(); ++place_idx) { + auto &tt = sub_scopes[place_idx]->FindVar(s)->Get(); LOG(INFO) << place_idx; - LOG(INFO) << sub_scopes[place_idx]->FindVar(s)->Get(); - // Copy grad[i] to grad_buf[0] + LOG(INFO) << tt; + framework::CopyFrom(tt, places[0], t_buf); - // sum_op + auto sum_op = framework::OpRegistry::CreateOp( + "sum", {{"X", {s, s_buf}}}, {{"Out", {s}}}, + framework::AttributeMap{}); + sum_op->Run(*sub_scopes[0], place); } - // Copy grad[0] to grad - // auto *t = scope.FindVar(s)->GetMutable(); + LOG(INFO) << t; + framework::CopyFrom(t, place, scope.FindVar(s)->GetMutable()); } } }; diff --git a/python/paddle/v2/fluid/tests/test_parallel_op.py b/python/paddle/v2/fluid/tests/test_parallel_op.py index eec546107f..c39040869d 100644 --- a/python/paddle/v2/fluid/tests/test_parallel_op.py +++ b/python/paddle/v2/fluid/tests/test_parallel_op.py @@ -27,7 +27,8 @@ class ParallelOpTest(unittest.TestCase): pd.write_output(hidden) data = pd() loss = layers.mean(x=data) - append_backward(loss) + sgd_optimizer = fluid.optimizer.SGD(learning_rate=0.001) + sgd_optimizer.minimize(loss) exe = fluid.Executor(fluid.CPUPlace()) exe.run(fluid.default_startup_program()) From 7411df34b8c2c1c21b0158489e5659d74284e9f3 Mon Sep 17 00:00:00 2001 From: Yang Yang Date: Wed, 3 Jan 2018 11:08:49 +0000 Subject: [PATCH 15/49] add multi thread --- paddle/operators/parallel_do_op.cc | 45 ++++++++++++------- .../paddle/v2/fluid/tests/test_parallel_op.py | 4 +- 2 files changed, 31 insertions(+), 18 deletions(-) diff --git a/paddle/operators/parallel_do_op.cc b/paddle/operators/parallel_do_op.cc index a81ddb25c4..c32884f8c2 100644 --- a/paddle/operators/parallel_do_op.cc +++ b/paddle/operators/parallel_do_op.cc @@ -12,6 +12,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +#include #include #include "paddle/framework/executor.h" @@ -44,7 +45,7 @@ void SplitTensorAndMoveTensorToScopes( auto lod_tensors = tensor.SplitLoDTensor(places); for (auto &lod : lod_tensors) { - LOG(INFO) << lod.dims(); + VLOG(3) << lod.dims(); } for (size_t i = 0; i < sub_scopes.size(); ++i) { @@ -84,6 +85,7 @@ class ParallelDoOp : public framework::OperatorBase { SplitTensorAndMoveTensorToScopes(scope, sub_scopes, places, Inputs(kInputs)); + std::vector workers; for (size_t place_idx = 0; place_idx < places.size(); ++place_idx) { VLOG(3) << "Run " << place_idx; @@ -96,9 +98,14 @@ class ParallelDoOp : public framework::OperatorBase { } // execute - auto executor = framework::Executor(place); - executor.Run(*program, cur_scope, block->ID(), - false /*create_local_scope*/); + workers.push_back(std::thread([program, cur_scope, place, block] { + auto executor = framework::Executor(place); + executor.Run(*program, cur_scope, block->ID(), + false /*create_local_scope*/); + })); + } + for (auto &worker : workers) { + worker.join(); } // merge output @@ -162,14 +169,15 @@ class ParallelDoGradOp : public OperatorBase { Inputs(framework::GradVarName(kOutputs))); for (auto &s : Inputs(framework::GradVarName(kOutputs))) { - LOG(INFO) << s; - LOG(INFO) << scope.FindVar(s)->Get(); + VLOG(3) << s; + VLOG(3) << scope.FindVar(s)->Get(); for (auto *sub_scope : sub_scopes) { - LOG(INFO) << sub_scope->FindVar(s)->Get(); + VLOG(3) << sub_scope->FindVar(s)->Get(); } } // exe run + std::vector workers; for (size_t place_idx = 0; place_idx < places.size(); ++place_idx) { VLOG(3) << "Run " << place_idx; @@ -177,25 +185,30 @@ class ParallelDoGradOp : public OperatorBase { auto *cur_scope = sub_scopes[place_idx]; // execute - auto executor = framework::Executor(place); - executor.Run(*program, cur_scope, block->ID(), - false /*create_local_scope*/); + workers.push_back(std::thread([program, cur_scope, place, block] { + auto executor = framework::Executor(place); + executor.Run(*program, cur_scope, block->ID(), + false /*create_local_scope*/); + })); + } + for (auto &worker : workers) { + worker.join(); } // merge grad for (auto &s : Outputs(framework::GradVarName(kParameters))) { - LOG(INFO) << s; + VLOG(3) << s; auto &t = sub_scopes[0]->FindVar(s)->Get(); - LOG(INFO) << t; + VLOG(3) << t; std::string s_buf = s + "@BUF"; auto *t_buf = sub_scopes[0]->Var(s_buf)->GetMutable(); for (size_t place_idx = 1; place_idx < places.size(); ++place_idx) { auto &tt = sub_scopes[place_idx]->FindVar(s)->Get(); - LOG(INFO) << place_idx; - LOG(INFO) << tt; + VLOG(3) << place_idx; + VLOG(3) << tt; framework::CopyFrom(tt, places[0], t_buf); auto sum_op = framework::OpRegistry::CreateOp( @@ -204,7 +217,7 @@ class ParallelDoGradOp : public OperatorBase { sum_op->Run(*sub_scopes[0], place); } - LOG(INFO) << t; + VLOG(3) << t; framework::CopyFrom(t, place, scope.FindVar(s)->GetMutable()); } } @@ -219,7 +232,7 @@ class ParallelDoGradOpDescMaker : public framework::SingleGradOpDescMaker { auto *grad = new framework::OpDesc(); grad->SetType("parallel_do_grad"); for (auto &input_param : this->InputNames()) { - LOG(INFO) << input_param; + VLOG(3) << input_param; grad->SetInput(input_param, this->Input(input_param)); grad->SetOutput(framework::GradVarName(input_param), this->InputGrad(input_param, false)); diff --git a/python/paddle/v2/fluid/tests/test_parallel_op.py b/python/paddle/v2/fluid/tests/test_parallel_op.py index c39040869d..2788f4e519 100644 --- a/python/paddle/v2/fluid/tests/test_parallel_op.py +++ b/python/paddle/v2/fluid/tests/test_parallel_op.py @@ -12,7 +12,7 @@ import paddle.v2.fluid.core as core class ParallelOpTest(unittest.TestCase): def setUp(self): x = layers.data( - shape=[-1, 3, 4], + shape=[-1, 30, 40], dtype='float32', name='x', append_batch_size=False, @@ -35,7 +35,7 @@ class ParallelOpTest(unittest.TestCase): exe.run(fluid.default_main_program(), feed={ x.name: np.random.uniform(0.1, 0.6, - (2, 3, 4)).astype("float32") + (20, 30, 40)).astype("float32") }) def test_forward(self): From b0ba2b061e5e130b8ef6648705a6a9cadd2c3b7b Mon Sep 17 00:00:00 2001 From: tensor-tang Date: Wed, 3 Jan 2018 18:46:58 +0800 Subject: [PATCH 16/49] fix typo --- paddle/platform/device_context.h | 1 - paddle/platform/mkldnn_helper.h | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/paddle/platform/device_context.h b/paddle/platform/device_context.h index 68abe937b1..44f3ac1d39 100644 --- a/paddle/platform/device_context.h +++ b/paddle/platform/device_context.h @@ -125,7 +125,6 @@ class CUDNNDeviceContext : public CUDADeviceContext { class MKLDNNDeviceContext : public CPUDeviceContext { public: explicit MKLDNNDeviceContext(CPUPlace place); - virtual ~MKLDNNDeviceContext(); /* \brief Add new element: memory, primitive or primitive desc */ template diff --git a/paddle/platform/mkldnn_helper.h b/paddle/platform/mkldnn_helper.h index 2649e4a5f3..07a9c362c5 100644 --- a/paddle/platform/mkldnn_helper.h +++ b/paddle/platform/mkldnn_helper.h @@ -14,7 +14,7 @@ limitations under the License. */ #pragma once -#include "mkldnn.hpp" +#include namespace paddle { namespace platform { From 0a8775cc5dff90264dd663498e8ba8f86d8b660d Mon Sep 17 00:00:00 2001 From: tensor-tang Date: Wed, 3 Jan 2018 23:37:53 +0800 Subject: [PATCH 17/49] fix shape_inference deps --- paddle/framework/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paddle/framework/CMakeLists.txt b/paddle/framework/CMakeLists.txt index b4458eb955..79a09986cd 100644 --- a/paddle/framework/CMakeLists.txt +++ b/paddle/framework/CMakeLists.txt @@ -38,7 +38,7 @@ device_context) cc_library(op_proto_maker SRCS op_proto_maker.cc DEPS framework_proto attribute) cc_test(op_proto_maker_test SRCS op_proto_maker_test.cc DEPS op_proto_maker) cc_library(op_info SRCS op_info.cc DEPS attribute framework_proto) -cc_library(shape_inference SRCS shape_inference.cc DEPS ddim attribute) +cc_library(shape_inference SRCS shape_inference.cc DEPS ddim attribute device_context) cc_library(operator SRCS operator.cc DEPS op_info device_context tensor scope glog shape_inference data_transform) cc_test(operator_test SRCS operator_test.cc DEPS operator op_registry init) From 5bf5650dcf26f437346bc4dedb40a0dbd7138732 Mon Sep 17 00:00:00 2001 From: tensor-tang Date: Thu, 4 Jan 2018 10:08:56 +0800 Subject: [PATCH 18/49] generate mkldnn dummy target for static deps --- cmake/external/mkldnn.cmake | 16 ++++++++++++---- paddle/operators/tensor.save | Bin 462 -> 466 bytes 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/cmake/external/mkldnn.cmake b/cmake/external/mkldnn.cmake index 5d24caebdc..b67c559fdf 100644 --- a/cmake/external/mkldnn.cmake +++ b/cmake/external/mkldnn.cmake @@ -63,9 +63,17 @@ ExternalProject_Add( -DMKLROOT:PATH=${MKLML_ROOT} ) -ADD_LIBRARY(mkldnn SHARED IMPORTED GLOBAL) -SET_PROPERTY(TARGET mkldnn PROPERTY IMPORTED_LOCATION ${MKLDNN_LIB}) -ADD_DEPENDENCIES(mkldnn ${MKLDNN_PROJECT}) +ADD_LIBRARY(shared_mkldnn SHARED IMPORTED GLOBAL) +SET_PROPERTY(TARGET shared_mkldnn PROPERTY IMPORTED_LOCATION ${MKLDNN_LIB}) +ADD_DEPENDENCIES(shared_mkldnn ${MKLDNN_PROJECT}) MESSAGE(STATUS "MKLDNN library: ${MKLDNN_LIB}") add_definitions(-DPADDLE_WITH_MKLDNN) -LIST(APPEND external_project_dependencies mkldnn) +LIST(APPEND external_project_dependencies shared_mkldnn) + +# generate a static dummy target to track mkldnn dependencies +# for cc_library(xxx SRCS xxx.c DEPS mkldnn) +SET(dummyfile ${CMAKE_CURRENT_BINARY_DIR}/mkldnn_dummy.c) +FILE(WRITE ${dummyfile} "const char * dummy = \"${dummyfile}\";") +ADD_LIBRARY(mkldnn STATIC ${dummyfile}) +TARGET_LINK_LIBRARIES(mkldnn ${MKLDNN_LIB} ${MKLML_LIB} ${MKLML_IOMP_LIB}) +ADD_DEPENDENCIES(mkldnn ${MKLDNN_PROJECT}) diff --git a/paddle/operators/tensor.save b/paddle/operators/tensor.save index c24308a7d0131b84c28c0a9857cce4949afb2091..de6cd5a66a26e9e8f2bee245cf17ec190c5c1e05 100644 GIT binary patch delta 13 UcmX@de2JNnVe)K7;|=o|0UwzJSpWb4 delta 11 Scmcb_e2#ge$vno1ECv7@nFH_u From fccbc2fc2816c7a8665cbe228f28dd4ffdb25ef4 Mon Sep 17 00:00:00 2001 From: Yang Yang Date: Thu, 4 Jan 2018 02:56:05 +0000 Subject: [PATCH 19/49] licence update --- paddle/operators/parallel_do_op.cc | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/paddle/operators/parallel_do_op.cc b/paddle/operators/parallel_do_op.cc index c32884f8c2..348356f28d 100644 --- a/paddle/operators/parallel_do_op.cc +++ b/paddle/operators/parallel_do_op.cc @@ -1,16 +1,16 @@ /* 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 +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 + 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. */ +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 #include From 97dc451f4a069c816722f37a79cc53e972b7e0dc Mon Sep 17 00:00:00 2001 From: Yang Yang Date: Thu, 4 Jan 2018 03:06:12 +0000 Subject: [PATCH 20/49] clean up --- paddle/framework/operator.cc | 8 -------- paddle/operators/elementwise_op.h | 5 ----- 2 files changed, 13 deletions(-) diff --git a/paddle/framework/operator.cc b/paddle/framework/operator.cc index 60f31635ac..9e3ef5f4ca 100644 --- a/paddle/framework/operator.cc +++ b/paddle/framework/operator.cc @@ -182,8 +182,6 @@ static const Tensor* GetTensorFromVar(const Variable* var) { const Tensor* t = nullptr; if (var->IsType()) { t = &(var->Get()); - } else if (var->IsType()) { - t = &(var->Get()); } else if (var->IsType()) { t = &(var->Get().value()); } else { @@ -197,8 +195,6 @@ static Tensor* GetMutableTensorFromVar(Variable* var) { Tensor* t = nullptr; if (var->IsType()) { t = var->GetMutable(); - } else if (var->IsType()) { - t = var->GetMutable(); } else if (var->IsType()) { t = var->GetMutable()->mutable_value(); } else { @@ -362,8 +358,6 @@ class RuntimeInferShapeContext : public InferShapeContext { Variable* var = scope_.FindVar(name); if (var->IsType()) { return var->Get().dims(); - } else if (var->IsType()) { - return var->Get().dims(); } else if (var->IsType()) { return var->Get().GetCompleteDims(); } else { @@ -376,8 +370,6 @@ class RuntimeInferShapeContext : public InferShapeContext { Variable* var = scope_.FindVar(name); if (var->IsType()) { var->GetMutable()->Resize(dim); - } else if (var->IsType()) { - var->GetMutable()->Resize(dim); } else if (var->IsType()) { var->GetMutable()->set_height(dim[0]); } else { diff --git a/paddle/operators/elementwise_op.h b/paddle/operators/elementwise_op.h index 4128a3b1dc..f308ee05e1 100644 --- a/paddle/operators/elementwise_op.h +++ b/paddle/operators/elementwise_op.h @@ -34,8 +34,6 @@ class ElementwiseOp : public framework::OperatorWithKernel { auto x_dim = ctx->GetInputDim("X"); auto y_dim = ctx->GetInputDim("Y"); - LOG(INFO) << x_dim; - LOG(INFO) << y_dim; PADDLE_ENFORCE_GE(x_dim.size(), y_dim.size(), "Rank of first input must >= rank of second input."); ctx->SetOutputDim("Out", x_dim); @@ -119,9 +117,6 @@ class ElementwiseOpGrad : public framework::OperatorWithKernel { auto x_dims = ctx->GetInputDim("X"); auto y_dims = ctx->GetInputDim("Y"); auto out_dims = ctx->GetInputDim(framework::GradVarName("Out")); - LOG(INFO) << x_dims; - LOG(INFO) << y_dims; - LOG(INFO) << out_dims; PADDLE_ENFORCE_GE(x_dims.size(), y_dims.size(), "Rank of first input must >= rank of second input."); From f9ef6d15190604e4a0780e76c7254a7875e7352e Mon Sep 17 00:00:00 2001 From: peterzhang2029 Date: Thu, 4 Jan 2018 16:11:50 +0800 Subject: [PATCH 21/49] init --- paddle/operators/adagrad_op.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/paddle/operators/adagrad_op.h b/paddle/operators/adagrad_op.h index 0d77dbcbac..667c1939a2 100644 --- a/paddle/operators/adagrad_op.h +++ b/paddle/operators/adagrad_op.h @@ -47,8 +47,8 @@ class AdagradOpKernel : public framework::OpKernel { *ctx.Input("Grad")); auto moment = framework::EigenVector::Flatten( *ctx.Input("Moment")); - auto lr = framework::EigenVector::Flatten( - *ctx.Input("LearningRate")); + auto* learning_rate = ctx.Input("LearningRate"); + auto* lr = learning_rate->data(); auto param_out = framework::EigenVector::Flatten(*param_out_tensor); auto moment_out = framework::EigenVector::Flatten(*moment_out_tensor); @@ -57,7 +57,7 @@ class AdagradOpKernel : public framework::OpKernel { moment_out.device(*place) = moment + grad * grad; Eigen::DSizes m_dsize(moment_out_tensor->numel()); param_out.device(*place) = - param - lr.broadcast(m_dsize) * grad / (moment_out.sqrt() + epsilon); + param - lr[0] * grad / (moment_out.sqrt() + epsilon); } else if (grad_var->IsType()) { auto* param_tensor = ctx.Input("Param"); PADDLE_ENFORCE_EQ(param_tensor, param_out_tensor); From c10d5d2aa16b23cc45e01cc399bfa8fbfd39e387 Mon Sep 17 00:00:00 2001 From: peterzhang2029 Date: Thu, 4 Jan 2018 16:50:18 +0800 Subject: [PATCH 22/49] refine code style --- paddle/operators/adagrad_op.h | 1 + 1 file changed, 1 insertion(+) diff --git a/paddle/operators/adagrad_op.h b/paddle/operators/adagrad_op.h index 667c1939a2..be234cf4c4 100644 --- a/paddle/operators/adagrad_op.h +++ b/paddle/operators/adagrad_op.h @@ -47,6 +47,7 @@ class AdagradOpKernel : public framework::OpKernel { *ctx.Input("Grad")); auto moment = framework::EigenVector::Flatten( *ctx.Input("Moment")); + auto* learning_rate = ctx.Input("LearningRate"); auto* lr = learning_rate->data(); From ebc616b0f9bb72d17668478b70a0df8a0a979771 Mon Sep 17 00:00:00 2001 From: emailweixu Date: Thu, 4 Jan 2018 11:08:07 -0800 Subject: [PATCH 23/49] Fix comment to layers.fc x_num_col_dims => num_flatten_dims --- python/paddle/v2/fluid/layers/nn.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/python/paddle/v2/fluid/layers/nn.py b/python/paddle/v2/fluid/layers/nn.py index 1c1c09dd28..417afbecae 100644 --- a/python/paddle/v2/fluid/layers/nn.py +++ b/python/paddle/v2/fluid/layers/nn.py @@ -64,14 +64,14 @@ def fc(input, is flattened: the first `num_flatten_dims` dimensions will be flatten to form the first dimension of the final matrix (height of the - matrix), and the rest `rank(X) - num_col_dims` + matrix), and the rest `rank(X) - num_flatten_dims` dimensions are flattened to form the second dimension of the final matrix (width of the matrix). For example, suppose `X` is a 6-dimensional tensor with a shape [2, 3, 4, 5, 6], and - `x_num_col_dims` = 3. Then, the flattened matrix + `num_flatten_dims` = 3. Then, the flattened matrix will have a shape [2 x 3 x 4, 5 x 6] = [24, 30]. - By default, `x_num_col_dims` is set to 1. + By default, `num_flatten_dims` is set to 1. param_attr(ParamAttr|list): The parameter attribute for learnable parameters/weights of the fully connected layer. From 0f4410755fa2fd333aaf1f009388773b0c7bbd6c Mon Sep 17 00:00:00 2001 From: Yibing Liu Date: Fri, 5 Jan 2018 03:38:12 +0000 Subject: [PATCH 24/49] Confirm the contents in profiling report --- paddle/platform/profiler.cc | 52 ++++++++++++++++++++++---------- paddle/platform/profiler.h | 1 + paddle/platform/profiler_test.cc | 6 ++++ 3 files changed, 43 insertions(+), 16 deletions(-) diff --git a/paddle/platform/profiler.cc b/paddle/platform/profiler.cc index 64b8bd1485..f11c87f4df 100644 --- a/paddle/platform/profiler.cc +++ b/paddle/platform/profiler.cc @@ -3,7 +3,7 @@ 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 @@ -13,6 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. */ #include "paddle/platform/profiler.h" +#include #include namespace paddle { @@ -183,7 +184,8 @@ void PopEvent(const std::string& name, DeviceContext* dev_ctx) { void ParseEvents(std::vector>& events) { // Event name :: counts :: ave :: min :: max :: total - std::map> events_table; + std::map> + events_table; for (size_t i = 0; i < events.size(); i++) { std::list pushed_events; for (size_t j = 0; j < events[i].size(); j++) { @@ -197,18 +199,28 @@ void ParseEvents(std::vector>& events) { ++rit; } if (rit != pushed_events.rend()) { - Event pushed_event = *rit; - double cpu_time = rit->CpuElapsedUs(events[i][j]); - double cuda_time = 0; #ifdef PADDLE_WITH_CUDA - cuda_time = rit->CudaElapsedUs(events[i][j]); + double event_time = rit->CudaElapsedUs(events[i][j]); +#else + double event_time = rit->CpuElapsedUs(events[i][j]); #endif - if (events_table.find(rit->name()) == events_table.end()) { - events_table[rit->name()] = std::make_tuple(1, cpu_time, cuda_time); + std::string event_name = + "thread" + std::to_string(rit->thread_id()) + "::" + rit->name(); + if (events_table.find(event_name) == events_table.end()) { + events_table[event_name] = + std::make_tuple(1, event_time, event_time, event_time, 0); } else { - std::get<0>(events_table[rit->name()]) += 1; - std::get<1>(events_table[rit->name()]) += cpu_time; - std::get<2>(events_table[rit->name()]) += cuda_time; + std::get<0>(events_table[event_name]) += 1; + // total time + std::get<1>(events_table[event_name]) += event_time; + // min time + if (std::get<2>(events_table[event_name]) > event_time) { + std::get<2>(events_table[event_name]) = event_time; + } + // max time + if (std::get<3>(events_table[event_name]) < event_time) { + std::get<3>(events_table[event_name]) = event_time; + } } // remove the start marker from the list pushed_events.erase((++rit).base()); @@ -220,13 +232,21 @@ void ParseEvents(std::vector>& events) { } } // output events table - std::cout << "\nEvents\t\tCalls\t\tTotal CPU time\t\tTotal GPU time\n"; - for (std::map>::iterator it = + std::cout << std::setw(20) << "Events" << std::setw(10) << "Calls" + << std::setw(10) << "Total" << std::setw(10) << "Min" + << std::setw(10) << "Max" << std::setw(10) << "Ave" << std::endl; + for (std::map>::iterator it = events_table.begin(); it != events_table.end(); ++it) { - std::cout << it->first << "\t\t" << std::get<0>(it->second) << "\t\t" - << std::get<1>(it->second) << "\t\t" << std::get<2>(it->second) - << std::endl; + // average time + std::get<4>(it->second) = std::get<1>(it->second) / std::get<0>(it->second); + std::cout << std::setw(20) << it->first << std::setw(10) + << std::get<0>(it->second) << std::setw(10) + << std::get<1>(it->second) << std::setw(10) + << std::get<2>(it->second) << std::setw(10) + << std::get<3>(it->second) << std::setw(10) + << std::get<4>(it->second) << std::endl; } } diff --git a/paddle/platform/profiler.h b/paddle/platform/profiler.h index eb36355f86..eb176421a9 100644 --- a/paddle/platform/profiler.h +++ b/paddle/platform/profiler.h @@ -33,6 +33,7 @@ class Event { std::string kind() const; std::string name() const { return name_; } + uint32_t thread_id() const { return thread_id_; } bool has_cuda() const { return has_cuda_; } #ifdef PADDLE_WITH_CUDA diff --git a/paddle/platform/profiler_test.cc b/paddle/platform/profiler_test.cc index ff5e658f5b..7966b35a16 100644 --- a/paddle/platform/profiler_test.cc +++ b/paddle/platform/profiler_test.cc @@ -94,6 +94,12 @@ TEST(RecordEvent, RecordEvent) { int counter = 1; while (counter != i * 1000) counter++; } + for (int i = 1; i < 5; ++i) { + std::string name = "evs_op_" + std::to_string(i); + RecordEvent record_event(name, dev_ctx); + int counter = 1; + while (counter != i * 1000) counter++; + } std::vector> events = paddle::platform::DisableProfiler(); int cuda_startup_count = 0; int start_profiler_count = 0; From 2d94eca8a1f707830cabb6eb0afe8879c376ac5c Mon Sep 17 00:00:00 2001 From: Yibing Liu Date: Fri, 5 Jan 2018 05:45:45 +0000 Subject: [PATCH 25/49] Format profiling report --- paddle/platform/profiler.cc | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/paddle/platform/profiler.cc b/paddle/platform/profiler.cc index f11c87f4df..52312d5a57 100644 --- a/paddle/platform/profiler.cc +++ b/paddle/platform/profiler.cc @@ -186,6 +186,7 @@ void ParseEvents(std::vector>& events) { // Event name :: counts :: ave :: min :: max :: total std::map> events_table; + size_t max_name_width = 0; for (size_t i = 0; i < events.size(); i++) { std::list pushed_events; for (size_t j = 0; j < events[i].size(); j++) { @@ -206,6 +207,7 @@ void ParseEvents(std::vector>& events) { #endif std::string event_name = "thread" + std::to_string(rit->thread_id()) + "::" + rit->name(); + max_name_width = std::max(max_name_width, event_name.size()); if (events_table.find(event_name) == events_table.end()) { events_table[event_name] = std::make_tuple(1, event_time, event_time, event_time, 0); @@ -232,21 +234,24 @@ void ParseEvents(std::vector>& events) { } } // output events table - std::cout << std::setw(20) << "Events" << std::setw(10) << "Calls" - << std::setw(10) << "Total" << std::setw(10) << "Min" - << std::setw(10) << "Max" << std::setw(10) << "Ave" << std::endl; + std::cout.setf(std::ios::left); + const int data_width = 12; + std::cout << std::setw(max_name_width + 4) << "Event" << std::setw(data_width) + << "Calls" << std::setw(data_width) << "Total" + << std::setw(data_width) << "Min." << std::setw(data_width) + << "Max." << std::setw(data_width) << "Ave." << std::endl; for (std::map>::iterator it = events_table.begin(); it != events_table.end(); ++it) { // average time std::get<4>(it->second) = std::get<1>(it->second) / std::get<0>(it->second); - std::cout << std::setw(20) << it->first << std::setw(10) - << std::get<0>(it->second) << std::setw(10) - << std::get<1>(it->second) << std::setw(10) - << std::get<2>(it->second) << std::setw(10) - << std::get<3>(it->second) << std::setw(10) - << std::get<4>(it->second) << std::endl; + std::cout << std::setw(max_name_width + 4) << it->first + << std::setw(data_width) << std::get<0>(it->second) + << std::setw(data_width) << std::get<1>(it->second) + << std::setw(data_width) << std::get<2>(it->second) + << std::setw(data_width) << std::get<3>(it->second) + << std::setw(data_width) << std::get<4>(it->second) << std::endl; } } From 8496b2e41b2654a0904d2d46b547bea7c1df1eca Mon Sep 17 00:00:00 2001 From: Yang Yu Date: Fri, 5 Jan 2018 14:32:55 +0800 Subject: [PATCH 26/49] Refine parallel_do --- paddle/framework/lod_tensor.cc | 8 +++---- paddle/operators/parallel_do_op.cc | 38 ++++++++++++++++-------------- python/paddle/v2/fluid/backward.py | 1 + 3 files changed, 25 insertions(+), 22 deletions(-) diff --git a/paddle/framework/lod_tensor.cc b/paddle/framework/lod_tensor.cc index 6853b7ee5f..ef85ed69db 100644 --- a/paddle/framework/lod_tensor.cc +++ b/paddle/framework/lod_tensor.cc @@ -270,10 +270,10 @@ std::vector LoDTensor::SplitLoDTensor( "Batch size should be divided by places size"); std::vector lods; - for (int place_idx = 0; place_idx < places.size(); ++place_idx) { - int begin = place_idx * dims()[0] / places.size(); - int end = (place_idx + 1) * dims()[0] / places.size(); - auto src = Slice(begin, end); + for (size_t place_idx = 0; place_idx < places.size(); ++place_idx) { + size_t begin = place_idx * dims()[0] / places.size(); + size_t end = (place_idx + 1) * dims()[0] / places.size(); + auto src = Slice(static_cast(begin), static_cast(end)); LoDTensor dst; dst.Resize(src.dims()); diff --git a/paddle/operators/parallel_do_op.cc b/paddle/operators/parallel_do_op.cc index 348356f28d..077245cd83 100644 --- a/paddle/operators/parallel_do_op.cc +++ b/paddle/operators/parallel_do_op.cc @@ -12,23 +12,23 @@ 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 #include #include "paddle/framework/executor.h" #include "paddle/framework/op_registry.h" +#include "paddle/framework/threadpool.h" namespace paddle { namespace operators { -constexpr char kInputs[] = "inputs"; -constexpr char kParameters[] = "parameters"; -constexpr char kPlaces[] = "places"; +static constexpr char kInputs[] = "inputs"; +static constexpr char kParameters[] = "parameters"; +static constexpr char kPlaces[] = "places"; -constexpr char kOutputs[] = "outputs"; -constexpr char kParallelScopes[] = "parallel_scopes"; +static constexpr char kOutputs[] = "outputs"; +static constexpr char kParallelScopes[] = "parallel_scopes"; -constexpr char kParallelBlock[] = "sub_block"; +static constexpr char kParallelBlock[] = "sub_block"; // using ParallelScopeVar = std::vector; using LoDTensor = framework::LoDTensor; @@ -85,7 +85,8 @@ class ParallelDoOp : public framework::OperatorBase { SplitTensorAndMoveTensorToScopes(scope, sub_scopes, places, Inputs(kInputs)); - std::vector workers; + std::vector> workers; + workers.reserve(places.size()); for (size_t place_idx = 0; place_idx < places.size(); ++place_idx) { VLOG(3) << "Run " << place_idx; @@ -93,26 +94,27 @@ class ParallelDoOp : public framework::OperatorBase { auto *cur_scope = sub_scopes[place_idx]; // copy parameter - if (dev_ctx.GetPlace() != place) { + // some version of boost lacks != for boost::variant + if (!(dev_ctx.GetPlace() == place)) { PADDLE_THROW("Not Implemented"); } - // execute - workers.push_back(std::thread([program, cur_scope, place, block] { - auto executor = framework::Executor(place); + workers.emplace_back(framework::Async([program, cur_scope, place, block] { + framework::Executor executor(place); executor.Run(*program, cur_scope, block->ID(), false /*create_local_scope*/); })); } for (auto &worker : workers) { - worker.join(); + worker.wait(); } // merge output for (auto &o_name : Outputs(kOutputs)) { std::vector lod_tensors; + lod_tensors.reserve(sub_scopes.size()); for (auto *sub_scope : sub_scopes) { - lod_tensors.push_back(&sub_scope->FindVar(o_name)->Get()); + lod_tensors.emplace_back(&sub_scope->FindVar(o_name)->Get()); } auto *lod_tensor_to_be_merged = @@ -177,7 +179,7 @@ class ParallelDoGradOp : public OperatorBase { } // exe run - std::vector workers; + std::vector> workers; for (size_t place_idx = 0; place_idx < places.size(); ++place_idx) { VLOG(3) << "Run " << place_idx; @@ -185,14 +187,14 @@ class ParallelDoGradOp : public OperatorBase { auto *cur_scope = sub_scopes[place_idx]; // execute - workers.push_back(std::thread([program, cur_scope, place, block] { - auto executor = framework::Executor(place); + workers.emplace_back(framework::Async([program, cur_scope, place, block] { + framework::Executor executor(place); executor.Run(*program, cur_scope, block->ID(), false /*create_local_scope*/); })); } for (auto &worker : workers) { - worker.join(); + worker.wait(); } // merge grad diff --git a/python/paddle/v2/fluid/backward.py b/python/paddle/v2/fluid/backward.py index ac60bf5436..88fe19da5e 100644 --- a/python/paddle/v2/fluid/backward.py +++ b/python/paddle/v2/fluid/backward.py @@ -205,6 +205,7 @@ def _append_backward_ops_(target, # Getting op's corresponding grad_op grad_op_desc, op_grad_to_var = core.get_grad_op_desc( op.desc, no_grad_dict[block.idx], grad_sub_block_list) + grad_op_descs.extend(grad_op_desc) grad_to_var.update(op_grad_to_var) From 9c7cea81c8407ace7db46d781df5123fce60bd66 Mon Sep 17 00:00:00 2001 From: tensor-tang Date: Fri, 5 Jan 2018 14:50:27 +0800 Subject: [PATCH 27/49] follow comments, use unique_ptr and remove unused file --- paddle/operators/tensor.save | Bin 466 -> 0 bytes paddle/platform/device_context.cc | 4 ++-- paddle/platform/device_context.h | 6 +++--- paddle/platform/mkldnn_helper.h | 9 +++++---- 4 files changed, 10 insertions(+), 9 deletions(-) delete mode 100644 paddle/operators/tensor.save diff --git a/paddle/operators/tensor.save b/paddle/operators/tensor.save deleted file mode 100644 index de6cd5a66a26e9e8f2bee245cf17ec190c5c1e05..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 466 zcmYMqg-%035CzbsSSj{!hvM$;?(XjY{|}rckWO+YlRGzr5b}tV{oT3spQrw!{M;)P zxznj6lVoBTo3Rxe1r!n+#iS`A7E39koC+#QsG^!0YN?~11{!IinHE}Uqn!@2bkapP zJ@nE?KLZRh#4sa_GR8O)OftnZGt4r_JPRzc#4;h9qTyn)VH{5c^Jr6wc#4|6v^2R$KeDcLNKi(h{!T(op_key)) { return; } - GetElementPool().emplace(op_key, value); + GetElementPool().emplace(op_key, std::move(value)); } template -const T MKLDNNDeviceContext::GetElement(const std::string& op_key) const { +const T& MKLDNNDeviceContext::GetElement(const std::string& op_key) const { auto it = GetElementPool().find(op_key); return it == GetElementPool().end() ? nullptr : it->second; } diff --git a/paddle/platform/device_context.h b/paddle/platform/device_context.h index 44f3ac1d39..21a5ed1835 100644 --- a/paddle/platform/device_context.h +++ b/paddle/platform/device_context.h @@ -132,7 +132,7 @@ class MKLDNNDeviceContext : public CPUDeviceContext { /* \brief Get existed element: memory, primitive or primitive desc */ template - const T GetElement(const std::string& op_key) const; + const T& GetElement(const std::string& op_key) const; /* \brief Get element pool: memory, primitive or primitive desc pool */ template @@ -140,7 +140,7 @@ class MKLDNNDeviceContext : public CPUDeviceContext { GetElementPool() const; /* \brief Get the active engine */ - const MKLDNNEnginePtr GetEngine() const { return engine_; } + const MKLDNNEngine& engine() const { return *engine_; } /* \brief Submit primitive to pipeline */ void Submit(const MKLDNNPrimitivePtr& p) { pipeline_.push_back(*p); } @@ -163,7 +163,7 @@ class MKLDNNDeviceContext : public CPUDeviceContext { std::hash> primitive_desc_pool_; std::vector pipeline_; - std::unique_ptr stream_; + MKLDNNStreamPtr stream_; MKLDNNEnginePtr engine_; bool ready_; }; diff --git a/paddle/platform/mkldnn_helper.h b/paddle/platform/mkldnn_helper.h index 07a9c362c5..cd52a8b4c4 100644 --- a/paddle/platform/mkldnn_helper.h +++ b/paddle/platform/mkldnn_helper.h @@ -25,10 +25,11 @@ using MKLDNNMemory = mkldnn::memory; using MKLDNNPrimitive = mkldnn::primitive; using MKLDNNPrimitiveDesc = mkldnn::handle; -typedef std::shared_ptr MKLDNNEnginePtr; -typedef std::shared_ptr MKLDNNMemoryPtr; -typedef std::shared_ptr MKLDNNPrimitivePtr; -typedef std::shared_ptr MKLDNNPrimitiveDescPtr; +typedef std::unique_ptr MKLDNNStreamPtr; +typedef std::unique_ptr MKLDNNEnginePtr; +typedef std::unique_ptr MKLDNNMemoryPtr; +typedef std::unique_ptr MKLDNNPrimitivePtr; +typedef std::unique_ptr MKLDNNPrimitiveDescPtr; } // namespace platform } // namespace paddle From dd09d8b2bfe68ff3a0df6a95fc6fd751434fa9cf Mon Sep 17 00:00:00 2001 From: peterzhang2029 Date: Fri, 5 Jan 2018 15:08:48 +0800 Subject: [PATCH 28/49] fix gpu error --- paddle/operators/adagrad_op.h | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/paddle/operators/adagrad_op.h b/paddle/operators/adagrad_op.h index be234cf4c4..66f5b0f449 100644 --- a/paddle/operators/adagrad_op.h +++ b/paddle/operators/adagrad_op.h @@ -47,9 +47,7 @@ class AdagradOpKernel : public framework::OpKernel { *ctx.Input("Grad")); auto moment = framework::EigenVector::Flatten( *ctx.Input("Moment")); - auto* learning_rate = ctx.Input("LearningRate"); - auto* lr = learning_rate->data(); auto param_out = framework::EigenVector::Flatten(*param_out_tensor); auto moment_out = framework::EigenVector::Flatten(*moment_out_tensor); @@ -57,8 +55,16 @@ class AdagradOpKernel : public framework::OpKernel { moment_out.device(*place) = moment + grad * grad; Eigen::DSizes m_dsize(moment_out_tensor->numel()); - param_out.device(*place) = - param - lr[0] * grad / (moment_out.sqrt() + epsilon); + if (platform::is_cpu_place(ctx.GetPlace())) { + auto* lr = learning_rate->data(); + param_out.device(*place) = + param - lr[0] * grad / (moment_out.sqrt() + epsilon); + } else { + auto lr = framework::EigenVector::Flatten(*learning_rate); + param_out.device(*place) = + param - + lr.broadcast(m_dsize) * grad / (moment_out.sqrt() + epsilon); + } } else if (grad_var->IsType()) { auto* param_tensor = ctx.Input("Param"); PADDLE_ENFORCE_EQ(param_tensor, param_out_tensor); From 5593858dd95a5921fbd938fcc8d7bb8003a2fdbf Mon Sep 17 00:00:00 2001 From: dzhwinter Date: Fri, 5 Jan 2018 15:19:11 +0800 Subject: [PATCH 29/49] Feature/use cudnn (#7141) * "add c++ side kernel selection" * "add multiple kernel op test" * "kernel selection only support cudnn" * "better formatter" * "small fix with UseCPU" * "depends on change interface Get(Place, Library)" * "fix CI" * "fix python cudnn test" * "leave the register cudnn op to another PR" * "fix CI" * "use all kernel by default" * "fix CI" --- paddle/framework/CMakeLists.txt | 3 +- paddle/framework/data_transform.cc | 24 ++++ paddle/framework/init.cc | 3 +- paddle/framework/op_registry_test.cc | 135 +++++++++++++++++- paddle/framework/operator.cc | 95 ++++++++++-- paddle/framework/operator.h | 32 ++++- paddle/operators/conv_cudnn_op.cu.cc | 5 +- paddle/operators/conv_op.h | 13 ++ paddle/pybind/const_value.cc | 5 - paddle/pybind/pybind.cc | 6 + python/paddle/v2/fluid/framework.py | 4 - .../paddle/v2/fluid/tests/test_conv2d_op.py | 8 ++ 12 files changed, 301 insertions(+), 32 deletions(-) diff --git a/paddle/framework/CMakeLists.txt b/paddle/framework/CMakeLists.txt index fb8c9ab96d..528e45b510 100644 --- a/paddle/framework/CMakeLists.txt +++ b/paddle/framework/CMakeLists.txt @@ -73,8 +73,7 @@ cc_test(var_type_inference_test SRCS var_type_inference_test.cc DEPS op_registry cc_library(selected_rows SRCS selected_rows.cc DEPS tensor) cc_test(selected_rows_test SRCS selected_rows_test.cc DEPS selected_rows) - -cc_library(init SRCS init.cc DEPS gflags device_context place stringpiece) +cc_library(init SRCS init.cc DEPS gflags device_context place stringpiece operator) cc_test(init_test SRCS init_test.cc DEPS init) cc_test(op_kernel_type_test SRCS op_kernel_type_test.cc DEPS place device_context framework_proto) diff --git a/paddle/framework/data_transform.cc b/paddle/framework/data_transform.cc index ac6e40a3ae..6b17809688 100644 --- a/paddle/framework/data_transform.cc +++ b/paddle/framework/data_transform.cc @@ -37,6 +37,28 @@ auto KernelNHWC = OpKernelType(proto::DataType::FP64, platform::CPUPlace(), auto KernelNCHW = OpKernelType(proto::DataType::FP64, platform::CPUPlace(), DataLayout::kNCHW, LibraryType::kPlain); +// TODO(dzhwinter): Only for testing multiple op kernel. +// Dummy transform function for library_type +// should be removed. +auto KernelPlain = OpKernelType(proto::DataType::FP32, platform::CUDAPlace(0), + DataLayout::kAnyLayout, LibraryType::kPlain); + +auto KernelCUDNN = OpKernelType(proto::DataType::FP32, platform::CUDAPlace(0), + DataLayout::kAnyLayout, LibraryType::kCUDNN); + +void DummyTrans(const platform::DeviceContext* ctx, + const KernelTypePair& kernel_pair, const Variable& in, + Variable* out) { + PADDLE_ENFORCE(in.IsType(), "Only Support Tensor transform!."); + PADDLE_ENFORCE( + platform::places_are_same_class(kernel_pair.first.place_, + kernel_pair.second.place_), + "TransDataType Only Support DataType transform on same place!"); + auto src = in.Get(); + auto* dst = out->GetMutable(); + *dst = src; +} + void TransDataType(const platform::DeviceContext* ctx, const KernelTypePair& kernel_pair, const Variable& in, Variable* out) { @@ -121,6 +143,8 @@ std::vector NCHW2NHWC = {0, 2, 3, 1}; } REGISTER_DATA_TRANSFORM_FN(f::KernelFP32, f::KernelFP64, f::TransDataType); +REGISTER_DATA_TRANSFORM_FN(f::KernelPlain, f::KernelCUDNN, f::DummyTrans); +REGISTER_DATA_TRANSFORM_FN(f::KernelCUDNN, f::KernelPlain, f::DummyTrans); REGISTER_DATA_TRANSFORM_FN(f::KernelNHWC, f::KernelNCHW, std::bind(f::TransDataLayout, NHWC2NCHW, std::placeholders::_1, diff --git a/paddle/framework/init.cc b/paddle/framework/init.cc index 3bea8f3d0a..7ec8d18b0e 100644 --- a/paddle/framework/init.cc +++ b/paddle/framework/init.cc @@ -15,6 +15,7 @@ limitations under the License. */ #include #include "paddle/framework/init.h" +#include "paddle/framework/operator.h" #include "paddle/platform/device_context.h" #include "paddle/platform/place.h" #include "paddle/string/piece.h" @@ -24,7 +25,6 @@ namespace framework { std::once_flag gflags_init_flag; -// TODO(qijun) move init gflags to init.cc void InitGflags(std::vector &argv) { std::call_once(gflags_init_flag, [&]() { int argc = argv.size(); @@ -72,6 +72,7 @@ bool InitDevices(const std::vector &devices) { LOG(WARNING) << "Not specified CPU device, create CPU by Default."; } platform::DeviceContextPool::Init(places); + framework::UseALL(); return true; } diff --git a/paddle/framework/op_registry_test.cc b/paddle/framework/op_registry_test.cc index cef530c6e6..a286925bbe 100644 --- a/paddle/framework/op_registry_test.cc +++ b/paddle/framework/op_registry_test.cc @@ -12,13 +12,16 @@ See the License for the specific language governing permissions and limitations under the License. */ -#include "paddle/framework/op_registry.h" +#include #include +#include "paddle/framework/op_registry.h" + namespace pd = paddle::framework; namespace paddle { namespace framework { + class CosineOp : public OperatorBase { public: using OperatorBase::OperatorBase; @@ -252,7 +255,6 @@ TEST(OperatorRegistrar, CPU) { op->Run(scope, cpu_place); } -#ifdef PADDLE_WITH_CUDA TEST(OperatorRegistrar, CUDA) { paddle::framework::proto::OpDesc op_desc; paddle::platform::CUDAPlace cuda_place(0); @@ -263,4 +265,131 @@ TEST(OperatorRegistrar, CUDA) { op->Run(scope, cuda_place); } -#endif + +static int op_test_value = 0; + +using paddle::platform::DeviceContext; +using paddle::platform::CPUDeviceContext; +using paddle::platform::CUDADeviceContext; + +namespace paddle { +namespace framework { + +class OpWithMultiKernelTest : public OperatorWithKernel { + public: + using OperatorWithKernel::OperatorWithKernel; + + protected: + void InferShape(InferShapeContext* ctx) const override {} + + framework::OpKernelType GetActualKernelType( + const framework::ExecutionContext& ctx) const override { + return framework::OpKernelType(proto::DataType::FP32, ctx.device_context()); + } + + framework::OpKernelType GetExpectedKernelType( + const framework::OpKernelType& kernel) const override { + return framework::OpKernelType(kernel.data_type_, platform::CUDAPlace(0), + kernel.data_layout_, + framework::LibraryType::kCUDNN); + } +}; + +template +class OpMultiKernelTest : public paddle::framework::OpKernel { + public: + void Compute(const paddle::framework::ExecutionContext& ctx) const; +}; + +template +class OpMultiKernelTest + : public paddle::framework::OpKernel { + public: + void Compute(const paddle::framework::ExecutionContext& ctx) const { + ++op_test_value; + } +}; + +template +class OpMultiKernelTest + : public paddle::framework::OpKernel { + public: + void Compute(const paddle::framework::ExecutionContext& ctx) const { + --op_test_value; + } +}; + +template +class OpMultiKernelTest2 : public paddle::framework::OpKernel { + public: + void Compute(const paddle::framework::ExecutionContext& ctx) const; +}; + +template +class OpMultiKernelTest2 + : public paddle::framework::OpKernel { + public: + void Compute(const paddle::framework::ExecutionContext& ctx) const { + op_test_value += 10; + } +}; + +template +class OpMultiKernelTest2 + : public paddle::framework::OpKernel { + public: + void Compute(const paddle::framework::ExecutionContext& ctx) const { + op_test_value -= 10; + } +}; + +} // namespace framework +} // namespace paddle + +REGISTER_OP_WITHOUT_GRADIENT(op_with_multi_kernel, + paddle::framework::OpWithMultiKernelTest, + paddle::framework::OpKernelTestMaker); +REGISTER_OP_KERNEL( + op_with_multi_kernel, CPU, paddle::platform::CPUPlace, + paddle::framework::OpMultiKernelTest); +REGISTER_OP_KERNEL( + op_with_multi_kernel, MKLDNN, paddle::platform::CPUPlace, + paddle::framework::OpMultiKernelTest2); +REGISTER_OP_KERNEL( + op_with_multi_kernel, CUDA, paddle::platform::CUDAPlace, + paddle::framework::OpMultiKernelTest); +REGISTER_OP_KERNEL( + op_with_multi_kernel, CUDNN, paddle::platform::CUDAPlace, + paddle::framework::OpMultiKernelTest2); + +TEST(OperatorRegistrar, OpWithMultiKernel) { + paddle::framework::proto::OpDesc op_desc; + paddle::platform::CUDAPlace cuda_place(0); + paddle::platform::CPUPlace cpu_place; + paddle::framework::Scope scope; + + op_desc.set_type("op_with_multi_kernel"); + auto op = paddle::framework::OpRegistry::CreateOp(op_desc); + + // use all available kernels + paddle::framework::UseALL(); + op->Run(scope, cuda_place); + EXPECT_EQ(op_test_value, -10); + + // remove cuda kernels + paddle::framework::UseCPU(); + op->Run(scope, cpu_place); + + EXPECT_EQ(op_test_value, -9); + + // add cuda kernels + paddle::framework::UseCUDA(); + op->Run(scope, cuda_place); + + EXPECT_EQ(op_test_value, -10); + + // use cudnn kernel + paddle::framework::UseCUDNN(); + op->Run(scope, cuda_place); + EXPECT_EQ(op_test_value, -20); +} diff --git a/paddle/framework/operator.cc b/paddle/framework/operator.cc index fc7091f1c8..70a9c4b555 100644 --- a/paddle/framework/operator.cc +++ b/paddle/framework/operator.cc @@ -11,6 +11,7 @@ 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 #include #include @@ -25,6 +26,53 @@ limitations under the License. */ namespace paddle { namespace framework { +std::vector> kKernelPriority; + +void UseCPU() { + kKernelPriority.clear(); + /*Plain CPU*/ + auto pair0 = std::make_tuple(platform::CPUPlace(), LibraryType::kPlain); + kKernelPriority.insert(kKernelPriority.begin(), pair0); +} + +void UseMKLDNN() { + UseCPU(); +#if PADDLE_WITH_MKLML + { + /*MKLDNN Kernel*/ + auto pair0 = std::make_tuple(platform::CPUPlace(), LibraryType::kMKLDNN); + kKernelPriority.insert(kKernelPriority.begin(), pair0); + } +#endif +} + +void UseCUDA() { + UseMKLDNN(); +#if PADDLE_WITH_CUDA + /*Plain GPU*/ + auto pair0 = std::make_tuple(platform::CUDAPlace(0), LibraryType::kPlain); + kKernelPriority.insert(kKernelPriority.begin(), pair0); +#endif +} + +void UseCUDNN() { + UseCUDA(); +#if PADDLE_WITH_CUDA + if (platform::dynload::HasCUDNN()) { + /*CUDNN Kernel*/ + auto pair0 = std::make_tuple(platform::CUDAPlace(0), LibraryType::kCUDNN); + kKernelPriority.insert(kKernelPriority.begin(), pair0); + } +#endif +} + +void UseALL() { + UseCPU(); + UseMKLDNN(); + UseCUDA(); + UseCUDNN(); +} + std::string OperatorBase::Input(const std::string& name) const { auto& ins = Inputs(name); PADDLE_ENFORCE_LE(ins.size(), 1UL, @@ -402,6 +450,12 @@ const platform::DeviceContext* GetDeviceContext( } } +const platform::DeviceContext* GetDeviceContext( + const framework::OpKernelType& kernel) { + platform::DeviceContextPool& pool = platform::DeviceContextPool::Instance(); + return pool.Get(kernel.place_); +} + void OperatorWithKernel::Run(const Scope& scope, const platform::Place& place) const { RuntimeInferShapeContext infer_shape_ctx(*this, scope); @@ -422,13 +476,8 @@ void OperatorWithKernel::Run(const Scope& scope, ExecutionContext ctx(*this, scope, *dev_ctx); auto actual_kernel_key = GetActualKernelType(ctx); - auto expected_kernel_key = GetExpectedKernelType(actual_kernel_key); - auto kernel_iter = kernels.find(expected_kernel_key); - if (kernel_iter == kernels.end()) { - PADDLE_THROW("The operator %s does not support %s", type_, - expected_kernel_key); - } + auto expected_kernel_key = GetExpectedKernelType(actual_kernel_key); if (actual_kernel_key == expected_kernel_key) { PADDLE_ENFORCE_EQ(actual_kernel_key.place_, expected_kernel_key.place_, @@ -436,9 +485,24 @@ void OperatorWithKernel::Run(const Scope& scope, "CPU and other devices. For example, multi-GPU model " "parallelism will failed."); } else { + // find the best key candidate + const DataTransformFnMap& trans_map = DataTransformFnMap::Instance(); + for (auto& candidate : kKernelPriority) { + auto candidate_key = + OpKernelType(actual_kernel_key.data_type_, std::get<0>(candidate), + actual_kernel_key.data_layout_, std::get<1>(candidate)); + + auto candidate_pair = std::make_pair(actual_kernel_key, candidate_key); + if ((actual_kernel_key == candidate_key) || + (kernels.count(candidate_key) && + trans_map.GetNullable(candidate_pair))) { + expected_kernel_key = candidate_key; + break; + } + } + auto kernel_pair = std::make_pair(actual_kernel_key, expected_kernel_key); - const DataTransformFn* trans_fun = - DataTransformFnMap::Instance().GetNullable(kernel_pair); + const DataTransformFn* trans_fun = trans_map.GetNullable(kernel_pair); if (trans_fun) { auto input_vars = this->InputVars(); // TODO(qijun) filter the input vars that do not need to be transformed @@ -471,7 +535,20 @@ void OperatorWithKernel::Run(const Scope& scope, } } - kernel_iter->second->Compute(ctx); + VLOG(10) << "Actual kernel: " << actual_kernel_key + << "Expected kernel: " << expected_kernel_key; + + auto kernel_iter = kernels.find(expected_kernel_key); + + if (kernel_iter == kernels.end()) { + PADDLE_THROW("The operator %s does not support %s", type_, + expected_kernel_key); + } + + auto* expected_dev_ctx = GetDeviceContext(expected_kernel_key); + ExecutionContext expected_ctx(*this, scope, *expected_dev_ctx); + + kernel_iter->second->Compute(expected_ctx); } OpKernelType OperatorWithKernel::GetActualKernelType( diff --git a/paddle/framework/operator.h b/paddle/framework/operator.h index d0a9b643d5..1f5a4af58c 100644 --- a/paddle/framework/operator.h +++ b/paddle/framework/operator.h @@ -17,6 +17,7 @@ limitations under the License. */ #include #include #include +#include #include #include @@ -52,10 +53,33 @@ constexpr char kGradVarSuffix[] = "@GRAD"; /// Variables with this suffix are supposed to be filled up with zeros. constexpr char kZeroVarSuffix[] = "@ZERO"; -// define some kernel hint -const std::string kUseCPU = "use_cpu"; -const std::string kUseCUDNN = "use_cudnn"; -const std::string kUseMKLDNN = "use_mkldnn"; +// define some kernel priority +extern std::vector> kKernelPriority; + +/** + * @brief Use cpu kernel only + */ +void UseCPU(); + +/** + * @brief Perfer MKLDNN kernel than Plain CPU kernel + */ +void UseMKLDNN(); + +/** + * @brief Perfer CUDA kernel than Plain CPU kernel + */ +void UseCUDA(); + +/** + * @brief Perfer cudnn kernel than Plain CUDA kernel + */ +void UseCUDNN(); + +/** + * @brief Use all available kernels + */ +void UseALL(); inline std::string GradVarName(const std::string& var_name) { return var_name + kGradVarSuffix; diff --git a/paddle/operators/conv_cudnn_op.cu.cc b/paddle/operators/conv_cudnn_op.cu.cc index 0aa7dd48ca..0c5ed3e4e8 100644 --- a/paddle/operators/conv_cudnn_op.cu.cc +++ b/paddle/operators/conv_cudnn_op.cu.cc @@ -315,10 +315,7 @@ class CudnnConvGradOpKernel : public framework::OpKernel { } // namespace operators } // namespace paddle -REGISTER_OP_KERNEL(conv2d, CUDNN, paddle::platform::CUDAPlace, - paddle::operators::CudnnConvOpKernel, - paddle::operators::CudnnConvOpKernel); - +// TODO(dzhwinter) : below register should be removed REGISTER_OP_CUDA_KERNEL(conv2d_cudnn, paddle::operators::CudnnConvOpKernel, paddle::operators::CudnnConvOpKernel); diff --git a/paddle/operators/conv_op.h b/paddle/operators/conv_op.h index 83786e2329..fe3c0bc930 100644 --- a/paddle/operators/conv_op.h +++ b/paddle/operators/conv_op.h @@ -62,12 +62,25 @@ class ConvOp : public framework::OperatorWithKernel { public: using framework::OperatorWithKernel::OperatorWithKernel; void InferShape(framework::InferShapeContext* ctx) const override; + framework::OpKernelType GetExpectedKernelType( + const framework::OpKernelType& kernel) const override { + return framework::OpKernelType(kernel.data_type_, platform::CUDAPlace(0), + kernel.data_layout_, + framework::LibraryType::kCUDNN); + } }; class ConvOpGrad : public framework::OperatorWithKernel { public: using framework::OperatorWithKernel::OperatorWithKernel; void InferShape(framework::InferShapeContext* ctx) const override; + + framework::OpKernelType GetExpectedKernelType( + const framework::OpKernelType& kernel) const override { + return framework::OpKernelType(kernel.data_type_, platform::CUDAPlace(0), + kernel.data_layout_, + framework::LibraryType::kCUDNN); + } }; template diff --git a/paddle/pybind/const_value.cc b/paddle/pybind/const_value.cc index 761635aa5e..b13ad42ea2 100644 --- a/paddle/pybind/const_value.cc +++ b/paddle/pybind/const_value.cc @@ -23,11 +23,6 @@ void BindConstValue(pybind11::module& m) { m.def("kTempVarName", [] { return framework::kTempVarName; }); m.def("kGradVarSuffix", [] { return framework::kGradVarSuffix; }); m.def("kZeroVarSuffix", [] { return framework::kZeroVarSuffix; }); - - // for kernel_hint key - m.def("kUseCPU", [] { return framework::kUseCPU; }); - m.def("kUseCUDNN", [] { return framework::kUseCUDNN; }); - m.def("kUseMKLDNN", [] { return framework::kUseMKLDNN; }); } } // namespace pybind diff --git a/paddle/pybind/pybind.cc b/paddle/pybind/pybind.cc index 364db62cba..5d170c66e9 100644 --- a/paddle/pybind/pybind.cc +++ b/paddle/pybind/pybind.cc @@ -430,6 +430,12 @@ All parameter, weight, gradient are variables in Paddle. m.def("init_glog", framework::InitGLOG); m.def("init_devices", &framework::InitDevices); + m.def("use_cpu", framework::UseCPU); + m.def("use_mkldnn", framework::UseMKLDNN); + m.def("use_cuda", framework::UseCUDA); + m.def("use_cudnn", framework::UseCUDNN); + m.def("use_all", framework::UseALL); + m.def("is_compile_gpu", IsCompileGPU); m.def("set_feed_variable", framework::SetFeedVariable); m.def("get_fetch_variable", framework::GetFetchVariable); diff --git a/python/paddle/v2/fluid/framework.py b/python/paddle/v2/fluid/framework.py index b66a8bce5f..7340dd23d1 100644 --- a/python/paddle/v2/fluid/framework.py +++ b/python/paddle/v2/fluid/framework.py @@ -17,10 +17,6 @@ TEMP_VAR_NAME = core.kTempVarName() GRAD_VAR_SUFFIX = core.kGradVarSuffix() ZERO_VAR_SUFFIX = core.kZeroVarSuffix() -USE_CPU = core.kUseCPU() -USE_CUDNN = core.kUseMKLDNN() -USE_MKLDNN = core.kUseMKLDNN() - def grad_var_name(var_name): """ diff --git a/python/paddle/v2/fluid/tests/test_conv2d_op.py b/python/paddle/v2/fluid/tests/test_conv2d_op.py index e82e3ab0c9..958300e655 100644 --- a/python/paddle/v2/fluid/tests/test_conv2d_op.py +++ b/python/paddle/v2/fluid/tests/test_conv2d_op.py @@ -1,5 +1,7 @@ import unittest import numpy as np + +import paddle.v2.fluid.core as core from op_test import OpTest @@ -47,6 +49,7 @@ def conv2d_forward_naive(input, filter, group, conv_param): class TestConv2dOp(OpTest): def setUp(self): + core.use_cuda() self.init_op_type() self.init_group() self.init_dilation() @@ -167,26 +170,31 @@ class TestWithDilation(TestConv2dOp): #----------------Conv2dCudnn---------------- class TestCudnn(TestConv2dOp): def init_op_type(self): + core.use_cudnn() self.op_type = "conv2d_cudnn" class TestCudnnWithPad(TestWithPad): def init_op_type(self): + core.use_cudnn() self.op_type = "conv2d_cudnn" class TestCudnnWithStride(TestWithStride): def init_op_type(self): + core.use_cudnn() self.op_type = "conv2d_cudnn" class TestCudnnWithGroup(TestWithGroup): def init_op_type(self): + core.use_cudnn() self.op_type = "conv2d_cudnn" class TestCudnnWith1x1(TestWith1x1): def init_op_type(self): + core.use_cudnn() self.op_type = "conv2d_cudnn" From 298977c4e4c8ec8a13a8580c3bde5a7771d91ffe Mon Sep 17 00:00:00 2001 From: wangmeng28 Date: Fri, 5 Jan 2018 15:36:18 +0800 Subject: [PATCH 30/49] Fix doc of ParameterAttribute --- python/paddle/trainer_config_helpers/attrs.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/python/paddle/trainer_config_helpers/attrs.py b/python/paddle/trainer_config_helpers/attrs.py index ecba871910..e6f87ce61b 100644 --- a/python/paddle/trainer_config_helpers/attrs.py +++ b/python/paddle/trainer_config_helpers/attrs.py @@ -58,12 +58,12 @@ def is_compatible_with(x, Type): class HookAttribute(object): """ - Hook Attribute object. As a member of ParameterAttribute class, the hook is an auxiliary operation that occurs + Hook Attribute object. As a member of ParameterAttribute class, the hook is an auxiliary operation that occurs during training process of a layer with parameters, such as img_conv layer, fc layer. - :param type: Hook type, currently supported types: + :param type: Hook type, currently supported types: 'pruning' : user specify a sparsity_ratio before training started, and the - network will prune the parameters based on the sparsity_ratio. + network will prune the parameters based on the sparsity_ratio. eg: The definition of Hook object can be hk = HookAttribute('pruning', 0.6) The specific usage can be paddle.layer.img_conv(input=img, filter_size=3, num_channels=3, num_filters=64, @@ -71,10 +71,10 @@ class HookAttribute(object): The pruning details can be found https://arxiv.org/pdf/1506.02626.pdf :type type: string - :param sparsity_ratio: Must be specified if hook type is 'pruning', + :param sparsity_ratio: Must be specified if hook type is 'pruning', it represents the ratio of the zero elements to be set by the Parameter. :type sparsity_ratio: float or None - + """ def __init__(self, type, sparsity_ratio=None): @@ -130,10 +130,12 @@ class ParameterAttribute(object): :param sparse_update: Enable sparse update for this parameter. It will enable both local and remote sparse update. :type sparse_update: bool + :param update_hooks: A HookAttribute object. + :type update_hooks: HookAttribute :param initializer: If not None, it should be a callable object which accepts a parameter name and returns numpy array for the initial value of the parameter - :param initializer: callable object + :type initializer: callable object """ def __init__(self, From 0aa03a822e17e905b75e2df5a6cadc31c4159cfc Mon Sep 17 00:00:00 2001 From: Yibing Liu Date: Fri, 5 Jan 2018 08:54:35 +0000 Subject: [PATCH 31/49] Enable sorting the profiling result by different keys --- paddle/platform/profiler.cc | 124 +++++++++++++++++++++++-------- paddle/platform/profiler.h | 17 ++++- paddle/platform/profiler_test.cc | 31 ++++---- 3 files changed, 124 insertions(+), 48 deletions(-) diff --git a/paddle/platform/profiler.cc b/paddle/platform/profiler.cc index 52312d5a57..a35d6e94f0 100644 --- a/paddle/platform/profiler.cc +++ b/paddle/platform/profiler.cc @@ -182,47 +182,88 @@ void PopEvent(const std::string& name, DeviceContext* dev_ctx) { dev_ctx); } -void ParseEvents(std::vector>& events) { - // Event name :: counts :: ave :: min :: max :: total - std::map> - events_table; +void ParseEvents(std::vector>& events, + EventSortingKey sorted_by) { + // Output header information + std::cout << "------------------------->" + << " Profiling Report " + << "<-------------------------" + << "\n\n"; +#ifdef PADDLE_WITH_CUDA + std::cout << "Place: GPU" << std::endl; +#else + std::cout << "Place: CPU" << std::endl; +#endif + std::cout << "Time unit: ms" << std::endl; + std::string sort_domain = "event end time"; + switch (sorted_by) { + case EventSortingKey::kCalls: + sort_domain = "number of calls"; + break; + case EventSortingKey::kTotal: + sort_domain = "total time"; + break; + case EventSortingKey::kMin: + sort_domain = "minimum time"; + break; + case EventSortingKey::kMax: + sort_domain = "maximum time"; + break; + case EventSortingKey::kAve: + sort_domain = "average time"; + break; + default: + if (sorted_by != EventSortingKey::kDefault) { + std::cout << "Warning: unkown sorting key. "; + sorted_by = EventSortingKey::kDefault; + } + } + std::cout << "Sorted by " << sort_domain + << " in descending order in the same thread\n\n"; + + // Parse events + std::vector> events_table; size_t max_name_width = 0; for (size_t i = 0; i < events.size(); i++) { std::list pushed_events; + std::vector event_items; + std::unordered_map event_idx; + for (size_t j = 0; j < events[i].size(); j++) { if (events[i][j].kind() == "push") { pushed_events.push_back(events[i][j]); - } - if (events[i][j].kind() == "pop") { + } else if (events[i][j].kind() == "pop") { std::list::reverse_iterator rit = pushed_events.rbegin(); while (rit->name() != events[i][j].name() && rit != pushed_events.rend()) { ++rit; } if (rit != pushed_events.rend()) { +// get event time in ms #ifdef PADDLE_WITH_CUDA - double event_time = rit->CudaElapsedUs(events[i][j]); + double event_time = rit->CudaElapsedUs(events[i][j]) / 1000.0; #else - double event_time = rit->CpuElapsedUs(events[i][j]); + double event_time = rit->CpuElapsedUs(events[i][j]) / 1000.0; #endif std::string event_name = "thread" + std::to_string(rit->thread_id()) + "::" + rit->name(); max_name_width = std::max(max_name_width, event_name.size()); - if (events_table.find(event_name) == events_table.end()) { - events_table[event_name] = - std::make_tuple(1, event_time, event_time, event_time, 0); + if (event_idx.find(event_name) == event_idx.end()) { + event_idx[event_name] = event_items.size(); + EventItem event_item = {event_name, 1, event_time, + event_time, event_time, event_time}; + event_items.push_back(event_item); } else { - std::get<0>(events_table[event_name]) += 1; + int index = event_idx[event_name]; + event_items[index].calls += 1; // total time - std::get<1>(events_table[event_name]) += event_time; + event_items[index].total_time += event_time; // min time - if (std::get<2>(events_table[event_name]) > event_time) { - std::get<2>(events_table[event_name]) = event_time; - } + event_items[index].min_time = + std::min(event_time, event_items[index].min_time); // max time - if (std::get<3>(events_table[event_name]) < event_time) { - std::get<3>(events_table[event_name]) = event_time; - } + event_items[index].max_time = + std::max(event_time, event_items[index].max_time); } // remove the start marker from the list pushed_events.erase((++rit).base()); @@ -232,6 +273,29 @@ void ParseEvents(std::vector>& events) { } } } + // average time + for (auto& item : event_items) { + item.ave_time = item.total_time / item.calls; + } + // sort + if (sorted_by != EventSortingKey::kDefault) { + std::sort(event_items.begin(), event_items.end(), + [&](EventItem& a, EventItem& b) { + switch (sorted_by) { + case EventSortingKey::kCalls: + return a.calls > b.calls; + case EventSortingKey::kTotal: + return a.total_time > b.total_time; + case EventSortingKey::kMin: + return a.min_time > b.min_time; + case EventSortingKey::kMax: + return a.max_time > b.max_time; + default: + return a.ave_time > b.ave_time; + } + }); + } + events_table.push_back(event_items); } // output events table std::cout.setf(std::ios::left); @@ -240,18 +304,16 @@ void ParseEvents(std::vector>& events) { << "Calls" << std::setw(data_width) << "Total" << std::setw(data_width) << "Min." << std::setw(data_width) << "Max." << std::setw(data_width) << "Ave." << std::endl; - for (std::map>::iterator it = - events_table.begin(); - it != events_table.end(); ++it) { - // average time - std::get<4>(it->second) = std::get<1>(it->second) / std::get<0>(it->second); - std::cout << std::setw(max_name_width + 4) << it->first - << std::setw(data_width) << std::get<0>(it->second) - << std::setw(data_width) << std::get<1>(it->second) - << std::setw(data_width) << std::get<2>(it->second) - << std::setw(data_width) << std::get<3>(it->second) - << std::setw(data_width) << std::get<4>(it->second) << std::endl; + for (size_t i = 0; i < events_table.size(); ++i) { + for (size_t j = 0; j < events_table[i].size(); ++j) { + EventItem& event_item = events_table[i][j]; + std::cout << std::setw(max_name_width + 4) << event_item.name + << std::setw(data_width) << event_item.calls + << std::setw(data_width) << event_item.total_time + << std::setw(data_width) << event_item.min_time + << std::setw(data_width) << event_item.max_time + << std::setw(data_width) << event_item.ave_time << std::endl; + } } } diff --git a/paddle/platform/profiler.h b/paddle/platform/profiler.h index eb176421a9..7504aff5cd 100644 --- a/paddle/platform/profiler.h +++ b/paddle/platform/profiler.h @@ -117,7 +117,22 @@ void EnableProfiler(ProfilerState state); // event_lists, event_lists[i][j] represents the j-th Event of i-th thread. std::vector> DisableProfiler(); -void ParseEvents(std::vector>&); +// The information of each event given in the profiling report +struct EventItem { + std::string name; + int calls; + double total_time; + double min_time; + double max_time; + double ave_time; +}; + +// Candidate keys to sort the profiling report +enum EventSortingKey { kDefault, kCalls, kTotal, kMin, kMax, kAve }; + +// Parse the event list and output the profiling report +void ParseEvents(std::vector>&, + EventSortingKey sorted_by = EventSortingKey::kDefault); } // namespace platform } // namespace paddle diff --git a/paddle/platform/profiler_test.cc b/paddle/platform/profiler_test.cc index 7966b35a16..68b75e7063 100644 --- a/paddle/platform/profiler_test.cc +++ b/paddle/platform/profiler_test.cc @@ -55,6 +55,7 @@ TEST(RecordEvent, RecordEvent) { using paddle::platform::EventKind; using paddle::platform::RecordEvent; using paddle::platform::ProfilerState; + using paddle::platform::EventSortingKey; ProfilerState state = ProfilerState::kCPU; DeviceContext* dev_ctx = nullptr; @@ -70,22 +71,26 @@ TEST(RecordEvent, RecordEvent) { /* Usage 1: * PushEvent(evt_name, dev_ctx); * ... - * code to time + * code to analyze * ... * PopEvent(evt_name, dev_ctx); */ - for (int i = 1; i < 5; ++i) { - std::string name = "op_" + std::to_string(i); - PushEvent(name, dev_ctx); - int counter = 1; - while (counter != i * 1000) counter++; - PopEvent(name, dev_ctx); + for (int loop = 0; loop < 3; ++loop) { + for (int i = 1; i < 5; ++i) { + std::string name = "op_" + std::to_string(i); + PushEvent(name, dev_ctx); + int counter = 1; + while (counter != i * 1000) counter++; + PopEvent(name, dev_ctx); + } } /* Usage 2: * { * RecordEvent record_event(name, dev_ctx); * ... + * code to analyze + * ... * } */ for (int i = 1; i < 5; ++i) { @@ -94,19 +99,13 @@ TEST(RecordEvent, RecordEvent) { int counter = 1; while (counter != i * 1000) counter++; } - for (int i = 1; i < 5; ++i) { - std::string name = "evs_op_" + std::to_string(i); - RecordEvent record_event(name, dev_ctx); - int counter = 1; - while (counter != i * 1000) counter++; - } std::vector> events = paddle::platform::DisableProfiler(); + // Will remove from test before merging + ParseEvents(events, EventSortingKey::kTotal); + int cuda_startup_count = 0; int start_profiler_count = 0; int stop_profiler_count = 0; - - ParseEvents(events); - for (size_t i = 0; i < events.size(); ++i) { for (size_t j = 0; j < events[i].size(); ++j) { if (events[i][j].name() == "_cuda_startup_") ++cuda_startup_count; From d7e56847c7063c0281e13cfda0cc6ba78761a618 Mon Sep 17 00:00:00 2001 From: Yibing Liu Date: Fri, 5 Jan 2018 09:04:27 +0000 Subject: [PATCH 32/49] fix typos --- paddle/platform/profiler_test.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/paddle/platform/profiler_test.cc b/paddle/platform/profiler_test.cc index 68b75e7063..d4fd4c8850 100644 --- a/paddle/platform/profiler_test.cc +++ b/paddle/platform/profiler_test.cc @@ -71,7 +71,7 @@ TEST(RecordEvent, RecordEvent) { /* Usage 1: * PushEvent(evt_name, dev_ctx); * ... - * code to analyze + * code to be analyzed * ... * PopEvent(evt_name, dev_ctx); */ @@ -89,7 +89,7 @@ TEST(RecordEvent, RecordEvent) { * { * RecordEvent record_event(name, dev_ctx); * ... - * code to analyze + * code to be analyzed * ... * } */ From 5a0a4617536aa4c79a30f195f88653794fe98678 Mon Sep 17 00:00:00 2001 From: Yibing Liu Date: Fri, 5 Jan 2018 09:57:28 +0000 Subject: [PATCH 33/49] Make time calc funcs return ms instead of us --- paddle/platform/profiler.cc | 13 ++++++------- paddle/platform/profiler.h | 4 ++-- paddle/platform/profiler_test.cc | 8 ++++---- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/paddle/platform/profiler.cc b/paddle/platform/profiler.cc index a35d6e94f0..ea16e56076 100644 --- a/paddle/platform/profiler.cc +++ b/paddle/platform/profiler.cc @@ -74,11 +74,11 @@ std::string Event::kind() const { PADDLE_THROW("Unknown EventKind."); } -double Event::CpuElapsedUs(const Event& e) const { - return (e.cpu_ns_ - cpu_ns_) / (1000.0); +double Event::CpuElapsedMs(const Event& e) const { + return (e.cpu_ns_ - cpu_ns_) / (1000000.0); } -double Event::CudaElapsedUs(const Event& e) const { +double Event::CudaElapsedMs(const Event& e) const { #ifdef PADDLE_WITH_CUDA PADDLE_ENFORCE(e.has_cuda() && has_cuda()); PADDLE_ENFORCE(e.device() == device()); @@ -86,7 +86,7 @@ double Event::CudaElapsedUs(const Event& e) const { PADDLE_ENFORCE(cudaEventSynchronize(e.event())); float ms; PADDLE_ENFORCE(cudaEventElapsedTime(&ms, event_, e.event())); - return ms * 1000.0; + return ms; #else PADDLE_THROW("CUDA is not enabled"); #endif @@ -239,11 +239,10 @@ void ParseEvents(std::vector>& events, ++rit; } if (rit != pushed_events.rend()) { -// get event time in ms #ifdef PADDLE_WITH_CUDA - double event_time = rit->CudaElapsedUs(events[i][j]) / 1000.0; + double event_time = rit->CudaElapsedMs(events[i][j]); #else - double event_time = rit->CpuElapsedUs(events[i][j]) / 1000.0; + double event_time = rit->CpuElapsedMs(events[i][j]); #endif std::string event_name = "thread" + std::to_string(rit->thread_id()) + "::" + rit->name(); diff --git a/paddle/platform/profiler.h b/paddle/platform/profiler.h index 7504aff5cd..d51ec64432 100644 --- a/paddle/platform/profiler.h +++ b/paddle/platform/profiler.h @@ -41,8 +41,8 @@ class Event { int device() const { return device_; } #endif - double CpuElapsedUs(const Event& e) const; - double CudaElapsedUs(const Event& e) const; + double CpuElapsedMs(const Event& e) const; + double CudaElapsedMs(const Event& e) const; private: EventKind kind_; diff --git a/paddle/platform/profiler_test.cc b/paddle/platform/profiler_test.cc index d4fd4c8850..3800f3914f 100644 --- a/paddle/platform/profiler_test.cc +++ b/paddle/platform/profiler_test.cc @@ -26,7 +26,7 @@ TEST(Event, CpuElapsedTime) { counter++; } Event stop_event(EventKind::kPopRange, "test", 0, nullptr); - EXPECT_GT(start_event.CpuElapsedUs(stop_event), 0); + EXPECT_GT(start_event.CpuElapsedMs(stop_event), 0); } #ifdef PADDLE_WITH_CUDA @@ -45,7 +45,7 @@ TEST(Event, CudaElapsedTime) { counter++; } Event stop_event(EventKind::kPopRange, "test", 0, dev_ctx); - EXPECT_GT(start_event.CudaElapsedUs(stop_event), 0); + EXPECT_GT(start_event.CudaElapsedMs(stop_event), 0); } #endif @@ -114,9 +114,9 @@ TEST(RecordEvent, RecordEvent) { if (events[i][j].name() == "push") { EXPECT_EQ(events[i][j + 1].name(), "pop"); #ifdef PADDLE_WITH_CUDA - EXPECT_GT(events[i][j].CudaElapsedUs(events[i][j + 1]), 0); + EXPECT_GT(events[i][j].CudaElapsedMs(events[i][j + 1]), 0); #else - EXPECT_GT(events[i][j].CpuElapsedUs(events[i][j + 1]), 0); + EXPECT_GT(events[i][j].CpuElapsedMs(events[i][j + 1]), 0); #endif } } From 643ff03fbc5e100176070760c9c73f11a695caf3 Mon Sep 17 00:00:00 2001 From: Yancey Date: Fri, 5 Jan 2018 18:51:49 +0800 Subject: [PATCH 34/49] capi package (#7237) --- paddle/scripts/docker/build.sh | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/paddle/scripts/docker/build.sh b/paddle/scripts/docker/build.sh index 92039ec6b0..f1e244772f 100644 --- a/paddle/scripts/docker/build.sh +++ b/paddle/scripts/docker/build.sh @@ -193,6 +193,25 @@ EOF EOF } +function gen_capi_package() { + if [[ ${WITH_C_API} == "ON" ]]; then + install_prefix="/paddle/build/capi_output" + rm -rf $install_prefix + + make DESTDIR="$install_prefix" install + + if [[ ${WITH_MKL:-OFF} == "ON" ]]; then + find ./third_party/install -name 'libmklml_gnu.so' -exec cp {} $install_prefix/usr/local/lib \; + find ./third_party/install -name 'libmklml_intel.so' -exec cp {} $install_prefix/usr/local/lib \; + cp -P ./third_party/install/mkldnn/lib/* $install_prefix/usr/local/lib/ + fi + + find ./third_party/install -name 'libiomp5.so' -exec cp {} $install_prefix/usr/local/lib \; + cd $install_prefix/usr/local + ls | egrep -v "^Found.*item$" | xargs tar /paddle/build/paddle.tgz + fi +} + set -xe cmake_gen ${PYTHON_ABI:-""} @@ -200,6 +219,11 @@ run_build run_test gen_docs gen_dockerfile - -printf "If you need to install PaddlePaddle in develop docker image," -printf "please make install or pip install build/python/dist/*.whl.\n" +gen_capi_package + +if [[ ${WITH_C_API:-OFF} == "ON" ]]; then + printf "PaddlePaddle C-API libraries was generated on build/paddle.tgz\n" +else + printf "If you need to install PaddlePaddle in develop docker image," + printf "please make install or pip install build/python/dist/*.whl.\n" +fi From 5ab271821b54bf7933976cf3f44ccf4546e43991 Mon Sep 17 00:00:00 2001 From: tensor-tang Date: Fri, 5 Jan 2018 22:18:11 +0800 Subject: [PATCH 35/49] fix crash when generating c-api package --- paddle/scripts/docker/build.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paddle/scripts/docker/build.sh b/paddle/scripts/docker/build.sh index f1e244772f..12fd7b1bda 100644 --- a/paddle/scripts/docker/build.sh +++ b/paddle/scripts/docker/build.sh @@ -208,7 +208,7 @@ function gen_capi_package() { find ./third_party/install -name 'libiomp5.so' -exec cp {} $install_prefix/usr/local/lib \; cd $install_prefix/usr/local - ls | egrep -v "^Found.*item$" | xargs tar /paddle/build/paddle.tgz + ls | egrep -v "^Found.*item$" | xargs tar -cf /paddle/build/paddle.tgz fi } From df3b250c7000269489522fcaa1f62e18e2f44ee8 Mon Sep 17 00:00:00 2001 From: Yibing Liu Date: Fri, 5 Jan 2018 14:52:03 +0000 Subject: [PATCH 36/49] Fix bad_alloc bug & refine code in profiler --- paddle/platform/profiler.cc | 150 ++++++++++++++++--------------- paddle/platform/profiler.h | 4 + paddle/platform/profiler_test.cc | 6 +- 3 files changed, 88 insertions(+), 72 deletions(-) diff --git a/paddle/platform/profiler.cc b/paddle/platform/profiler.cc index ea16e56076..abb1e6a89c 100644 --- a/paddle/platform/profiler.cc +++ b/paddle/platform/profiler.cc @@ -15,12 +15,16 @@ limitations under the License. */ #include "paddle/platform/profiler.h" #include #include +#include "gflags/gflags.h" +#include "glog/logging.h" namespace paddle { namespace platform { // The profiler state, the initial value is ProfilerState::kDisabled static ProfilerState g_state = ProfilerState::kDisabled; +// To record which timer the profiler used, CUDA or CPU. +static std::string g_profiler_place = ""; // The thread local event list only can be accessed by the specific thread // The thread index of each thread static thread_local int32_t g_thread_id; @@ -45,10 +49,7 @@ inline uint64_t GetTimeInNsec() { Event::Event(EventKind kind, std::string name, uint32_t thread_id, DeviceContext* dev_ctx) - : kind_(kind), - name_(std::move(name)), - thread_id_(thread_id), - has_cuda_(false) { + : kind_(kind), name_(name), thread_id_(thread_id), has_cuda_(false) { #ifdef PADDLE_WITH_CUDA auto* cuda_dev_ctx = static_cast(dev_ctx); if (cuda_dev_ctx) { @@ -115,22 +116,27 @@ inline EventList& GetEventList() { } void Mark(const std::string& name, DeviceContext* dev_ctx) { - GetEventList().Record(EventKind::kMark, std::move(name), g_thread_id, - dev_ctx); + GetEventList().Record(EventKind::kMark, name, g_thread_id, dev_ctx); +} + +void PushEvent(const std::string& name, DeviceContext* dev_ctx) { + GetEventList().Record(EventKind::kPushRange, name, g_thread_id, dev_ctx); +} + +void PopEvent(const std::string& name, DeviceContext* dev_ctx) { + GetEventList().Record(EventKind::kPopRange, name, g_thread_id, dev_ctx); } RecordEvent::RecordEvent(const std::string& name, DeviceContext* dev_ctx) { if (g_state == ProfilerState::kDisabled) return; dev_ctx_ = dev_ctx; name_ = name; - GetEventList().Record(EventKind::kPushRange, std::move(name), g_thread_id, - dev_ctx_); + PushEvent(name_, dev_ctx_); } RecordEvent::~RecordEvent() { if (g_state == ProfilerState::kDisabled) return; - GetEventList().Record(EventKind::kPopRange, std::move(name_), g_thread_id, - dev_ctx_); + PopEvent(name_, dev_ctx_); } void EnableProfiler(ProfilerState state) { @@ -141,6 +147,7 @@ void EnableProfiler(ProfilerState state) { "The profiling state should be disabled when calling ", "EnableProfiler."); g_state = state; + g_profiler_place = (g_state == ProfilerState::kCUDA) ? "CUDA" : "CPU"; #ifdef PADDLE_WITH_CUDA if (g_state == ProfilerState::kCUDA) { // Generate some dummy evenets first to reduce the startup overhead. @@ -172,56 +179,8 @@ std::vector> DisableProfiler() { return result; } -void PushEvent(const std::string& name, DeviceContext* dev_ctx) { - GetEventList().Record(EventKind::kPushRange, std::move(name), g_thread_id, - dev_ctx); -} - -void PopEvent(const std::string& name, DeviceContext* dev_ctx) { - GetEventList().Record(EventKind::kPopRange, std::move(name), g_thread_id, - dev_ctx); -} - void ParseEvents(std::vector>& events, EventSortingKey sorted_by) { - // Output header information - std::cout << "------------------------->" - << " Profiling Report " - << "<-------------------------" - << "\n\n"; -#ifdef PADDLE_WITH_CUDA - std::cout << "Place: GPU" << std::endl; -#else - std::cout << "Place: CPU" << std::endl; -#endif - std::cout << "Time unit: ms" << std::endl; - std::string sort_domain = "event end time"; - switch (sorted_by) { - case EventSortingKey::kCalls: - sort_domain = "number of calls"; - break; - case EventSortingKey::kTotal: - sort_domain = "total time"; - break; - case EventSortingKey::kMin: - sort_domain = "minimum time"; - break; - case EventSortingKey::kMax: - sort_domain = "maximum time"; - break; - case EventSortingKey::kAve: - sort_domain = "average time"; - break; - default: - if (sorted_by != EventSortingKey::kDefault) { - std::cout << "Warning: unkown sorting key. "; - sorted_by = EventSortingKey::kDefault; - } - } - std::cout << "Sorted by " << sort_domain - << " in descending order in the same thread\n\n"; - - // Parse events std::vector> events_table; size_t max_name_width = 0; for (size_t i = 0; i < events.size(); i++) { @@ -234,19 +193,19 @@ void ParseEvents(std::vector>& events, pushed_events.push_back(events[i][j]); } else if (events[i][j].kind() == "pop") { std::list::reverse_iterator rit = pushed_events.rbegin(); - while (rit->name() != events[i][j].name() && - rit != pushed_events.rend()) { + while (rit != pushed_events.rend() && + rit->name() != events[i][j].name()) { ++rit; } + if (rit != pushed_events.rend()) { -#ifdef PADDLE_WITH_CUDA - double event_time = rit->CudaElapsedMs(events[i][j]); -#else - double event_time = rit->CpuElapsedMs(events[i][j]); -#endif + double event_time = (g_state == ProfilerState::kCUDA) + ? rit->CudaElapsedMs(events[i][j]) + : rit->CpuElapsedMs(events[i][j]); std::string event_name = "thread" + std::to_string(rit->thread_id()) + "::" + rit->name(); max_name_width = std::max(max_name_width, event_name.size()); + if (event_idx.find(event_name) == event_idx.end()) { event_idx[event_name] = event_items.size(); EventItem event_item = {event_name, 1, event_time, @@ -264,11 +223,13 @@ void ParseEvents(std::vector>& events, event_items[index].max_time = std::max(event_time, event_items[index].max_time); } + // remove the start marker from the list pushed_events.erase((++rit).base()); } else { - std::cout << "Warning: can not find the start marker of event " - << events[i][j].name(); + LOG(WARNING) << "Cannot find the push marker of event \'" + << events[i][j].name() + << "\', which will be ignored in profiling report."; } } } @@ -294,19 +255,65 @@ void ParseEvents(std::vector>& events, } }); } + events_table.push_back(event_items); + // To check whether there are events with `push` but without `pop` + std::list::reverse_iterator rit = pushed_events.rbegin(); + while (rit != pushed_events.rend()) { + if (rit->kind() == "push") { + LOG(WARNING) << "Cannot find the pop marker of event \'" << rit->name() + << "\', which will be ignored in profiling report."; + } + ++rit; + } } - // output events table + + // Print report + PrintProfilingReport(events_table, sorted_by, max_name_width + 4, 12); +} + +void PrintProfilingReport(std::vector>& events_table, + EventSortingKey sorted_by, const size_t name_width, + const size_t data_width) { + if (g_profiler_place == "") return; + // Output header information + std::cout << "\n------------------------->" + << " Profiling Report " + << "<-------------------------\n\n"; + std::cout << "Place: " << g_profiler_place << std::endl; + std::cout << "Time unit: ms" << std::endl; + std::string sort_domain = "event end time"; + switch (sorted_by) { + case EventSortingKey::kCalls: + sort_domain = "number of calls"; + break; + case EventSortingKey::kTotal: + sort_domain = "total time"; + break; + case EventSortingKey::kMin: + sort_domain = "minimum time"; + break; + case EventSortingKey::kMax: + sort_domain = "maximum time"; + break; + case EventSortingKey::kAve: + sort_domain = "average time"; + break; + default: + break; + } + std::cout << "Sorted by " << sort_domain + << " in descending order in the same thread\n\n"; + // Output events table std::cout.setf(std::ios::left); - const int data_width = 12; - std::cout << std::setw(max_name_width + 4) << "Event" << std::setw(data_width) + std::cout << std::setw(name_width) << "Event" << std::setw(data_width) << "Calls" << std::setw(data_width) << "Total" << std::setw(data_width) << "Min." << std::setw(data_width) << "Max." << std::setw(data_width) << "Ave." << std::endl; for (size_t i = 0; i < events_table.size(); ++i) { for (size_t j = 0; j < events_table[i].size(); ++j) { EventItem& event_item = events_table[i][j]; - std::cout << std::setw(max_name_width + 4) << event_item.name + std::cout << std::setw(name_width) << event_item.name << std::setw(data_width) << event_item.calls << std::setw(data_width) << event_item.total_time << std::setw(data_width) << event_item.min_time @@ -314,6 +321,7 @@ void ParseEvents(std::vector>& events, << std::setw(data_width) << event_item.ave_time << std::endl; } } + std::cout << std::endl; } } // namespace platform diff --git a/paddle/platform/profiler.h b/paddle/platform/profiler.h index d51ec64432..f97a586787 100644 --- a/paddle/platform/profiler.h +++ b/paddle/platform/profiler.h @@ -134,5 +134,9 @@ enum EventSortingKey { kDefault, kCalls, kTotal, kMin, kMax, kAve }; void ParseEvents(std::vector>&, EventSortingKey sorted_by = EventSortingKey::kDefault); +// Print results +void PrintProfilingReport(std::vector>& events_table, + EventSortingKey sorted_by, const size_t name_width, + const size_t data_width); } // namespace platform } // namespace paddle diff --git a/paddle/platform/profiler_test.cc b/paddle/platform/profiler_test.cc index 3800f3914f..13dea713c7 100644 --- a/paddle/platform/profiler_test.cc +++ b/paddle/platform/profiler_test.cc @@ -99,8 +99,12 @@ TEST(RecordEvent, RecordEvent) { int counter = 1; while (counter != i * 1000) counter++; } + + // Bad Usage: + PushEvent("event_without_pop", dev_ctx); + PopEvent("event_without_push", dev_ctx); std::vector> events = paddle::platform::DisableProfiler(); - // Will remove from test before merging + // Will remove parsing-related code from test later ParseEvents(events, EventSortingKey::kTotal); int cuda_startup_count = 0; From 11ed2f2f93edd955cbb1d253d1c7fbeda3048191 Mon Sep 17 00:00:00 2001 From: tensor-tang Date: Fri, 5 Jan 2018 23:04:57 +0800 Subject: [PATCH 37/49] package right mkldnn and mklml libs if enabled in capi --- cmake/external/mkldnn.cmake | 5 +++++ cmake/external/mklml.cmake | 4 ++++ paddle/scripts/docker/build.sh | 9 --------- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/cmake/external/mkldnn.cmake b/cmake/external/mkldnn.cmake index b67c559fdf..471de3858a 100644 --- a/cmake/external/mkldnn.cmake +++ b/cmake/external/mkldnn.cmake @@ -77,3 +77,8 @@ FILE(WRITE ${dummyfile} "const char * dummy = \"${dummyfile}\";") ADD_LIBRARY(mkldnn STATIC ${dummyfile}) TARGET_LINK_LIBRARIES(mkldnn ${MKLDNN_LIB} ${MKLML_LIB} ${MKLML_IOMP_LIB}) ADD_DEPENDENCIES(mkldnn ${MKLDNN_PROJECT}) + +IF(WITH_C_API) + INSTALL(FILES ${MKLDNN_LIB} DESTINATION lib) +ENDIF() + diff --git a/cmake/external/mklml.cmake b/cmake/external/mklml.cmake index 20dbc32a73..15a07ea3da 100644 --- a/cmake/external/mklml.cmake +++ b/cmake/external/mklml.cmake @@ -66,3 +66,7 @@ ADD_LIBRARY(mklml SHARED IMPORTED GLOBAL) SET_PROPERTY(TARGET mklml PROPERTY IMPORTED_LOCATION ${MKLML_LIB}) ADD_DEPENDENCIES(mklml ${MKLML_PROJECT}) LIST(APPEND external_project_dependencies mklml) + +IF(WITH_C_API) + INSTALL(FILES ${MKLML_LIB} ${MKLML_IOMP_LIB} DESTINATION lib) +ENDIF() diff --git a/paddle/scripts/docker/build.sh b/paddle/scripts/docker/build.sh index 12fd7b1bda..e70d04d901 100644 --- a/paddle/scripts/docker/build.sh +++ b/paddle/scripts/docker/build.sh @@ -197,16 +197,7 @@ function gen_capi_package() { if [[ ${WITH_C_API} == "ON" ]]; then install_prefix="/paddle/build/capi_output" rm -rf $install_prefix - make DESTDIR="$install_prefix" install - - if [[ ${WITH_MKL:-OFF} == "ON" ]]; then - find ./third_party/install -name 'libmklml_gnu.so' -exec cp {} $install_prefix/usr/local/lib \; - find ./third_party/install -name 'libmklml_intel.so' -exec cp {} $install_prefix/usr/local/lib \; - cp -P ./third_party/install/mkldnn/lib/* $install_prefix/usr/local/lib/ - fi - - find ./third_party/install -name 'libiomp5.so' -exec cp {} $install_prefix/usr/local/lib \; cd $install_prefix/usr/local ls | egrep -v "^Found.*item$" | xargs tar -cf /paddle/build/paddle.tgz fi From 6de20496511f6cd7a032291721392f4ea74a3b7d Mon Sep 17 00:00:00 2001 From: tensor-tang Date: Sat, 6 Jan 2018 00:45:17 +0800 Subject: [PATCH 38/49] use the mkldnn shared lib so.0 --- cmake/external/mkldnn.cmake | 10 +++++++++- python/CMakeLists.txt | 4 ++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/cmake/external/mkldnn.cmake b/cmake/external/mkldnn.cmake index 471de3858a..89fc34796a 100644 --- a/cmake/external/mkldnn.cmake +++ b/cmake/external/mkldnn.cmake @@ -78,7 +78,15 @@ ADD_LIBRARY(mkldnn STATIC ${dummyfile}) TARGET_LINK_LIBRARIES(mkldnn ${MKLDNN_LIB} ${MKLML_LIB} ${MKLML_IOMP_LIB}) ADD_DEPENDENCIES(mkldnn ${MKLDNN_PROJECT}) +# copy the real so.0 lib to install dir +# it can be directly contained in wheel or capi +SET(MKLDNN_SHARED_LIB ${MKLDNN_INSTALL_DIR}/libmkldnn.so.0) +ADD_CUSTOM_COMMAND(OUTPUT ${MKLDNN_SHARED_LIB} + COMMAND cp ${MKLDNN_LIB} ${MKLDNN_SHARED_LIB} + DEPENDS mkldnn) +ADD_CUSTOM_TARGET(mkldnn_shared_lib ALL DEPENDS ${MKLDNN_SHARED_LIB}) + IF(WITH_C_API) - INSTALL(FILES ${MKLDNN_LIB} DESTINATION lib) + INSTALL(FILES ${MKLDNN_SHARED_LIB} DESTINATION lib) ENDIF() diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index 6f589e9169..36919ab00b 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -29,8 +29,8 @@ if(WITH_MKLML) endif() if(WITH_MKLDNN) - list(APPEND MKL_SHARED_LIBS "${MKLDNN_LIB}" "${MKLDNN_LIB}.0") - list(APPEND MKL_DEPENDS mkldnn) + list(APPEND MKL_SHARED_LIBS "${MKLDNN_SHARED_LIB}") + list(APPEND MKL_DEPENDS mkldnn mkldnn_shared_lib) endif() if(WITH_GPU) From 787d1d36d00568672e99abbb3fb594af27607b30 Mon Sep 17 00:00:00 2001 From: xuwei06 Date: Fri, 5 Jan 2018 18:22:14 -0800 Subject: [PATCH 39/49] Fix the output of conditional_block --- python/paddle/v2/fluid/layers/control_flow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/paddle/v2/fluid/layers/control_flow.py b/python/paddle/v2/fluid/layers/control_flow.py index acc22bef98..eacaf7a8e8 100644 --- a/python/paddle/v2/fluid/layers/control_flow.py +++ b/python/paddle/v2/fluid/layers/control_flow.py @@ -897,7 +897,7 @@ class ConditionalBlock(object): out_list = [ parent_block.var(var_name) for var_name in parent_block.vars - if var_name not in intermediate + if var_name in intermediate ] step_scope = parent_block.create_var( From 0c2af3a50eb1ee61e38ec94122c38f3b08e595a6 Mon Sep 17 00:00:00 2001 From: typhoonzero Date: Sat, 6 Jan 2018 13:02:50 +0800 Subject: [PATCH 40/49] update releasing doc --- doc/design/releasing_process.md | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/doc/design/releasing_process.md b/doc/design/releasing_process.md index 14c081ea84..102420d0ed 100644 --- a/doc/design/releasing_process.md +++ b/doc/design/releasing_process.md @@ -7,11 +7,9 @@ PaddlePaddle每次发新的版本,遵循以下流程: 1. 从`develop`分支派生出新的分支,分支名为`release/版本号`。例如,`release/0.10.0` 1. 将新分支的版本打上tag,tag为`版本号rc.Patch号`。第一个tag为`0.10.0rc1`,第二个为`0.10.0rc2`,依次类推。 1. 对这个版本的提交,做如下几个操作: + * 使用Regression Test List作为检查列表,测试本次release的正确性。 + * 如果失败,记录下所有失败的例子,在这个`release/版本号`分支中,修复所有bug后,Patch号加一,到第二步 * 修改`python/setup.py.in`中的版本信息,并将`istaged`字段设为`True`。 - * 编译这个版本的Docker发行镜像,发布到dockerhub。如果失败,修复Docker编译镜像问题,Patch号加一,返回第二步 - * 编译这个版本的Ubuntu Deb包。如果失败,修复Ubuntu Deb包编译问题,Patch号加一,返回第二步。 - * 使用Regression Test List作为检查列表,测试Docker镜像/ubuntu安装包的功能正确性 - * 如果失败,记录下所有失败的例子,在这个`release/版本号`分支中,修复所有bug后,Patch号加一,返回第二步 * 编译这个版本的python wheel包,并发布到pypi。 * 由于pypi.python.org目前遵循[严格的命名规范PEP 513](https://www.python.org/dev/peps/pep-0513),在使用twine上传之前,需要重命名wheel包中platform相关的后缀,比如将`linux_x86_64`修改成`manylinux1_x86_64`。 * pypi上的package名称为paddlepaddle和paddlepaddle_gpu,如果要上传GPU版本的包,需要修改build/python/setup.py中,name: "paddlepaddle_gpu"并重新打包wheel包:`python setup.py bdist_wheel`。 @@ -21,8 +19,8 @@ PaddlePaddle每次发新的版本,遵循以下流程: pip install twine twine upload dist/[package to upload] ``` + * 编译这个版本的Docker发行镜像,发布到dockerhub。如果失败,修复Docker编译镜像问题,Patch号加一,返回第二步 1. 第三步完成后,将`release/版本号`分支合入master分支,并删除`release/版本号`分支。将master分支的合入commit打上tag,tag为`版本号`。同时再将`master`分支合入`develop`分支。最后删除`release/版本号`分支。 -1. 编译master分支的Docker发行镜像,发布到dockerhub。编译ubuntu的deb包,发布到github release页面 1. 协同完成Release Note的书写 @@ -31,6 +29,30 @@ PaddlePaddle每次发新的版本,遵循以下流程: * `release/版本号`分支一旦建立,一般不允许再从`develop`分支合入`release/版本号`。这样保证`release/版本号`分支功能的封闭,方便测试人员测试PaddlePaddle的行为。 * 在`release/版本号`分支存在的时候,如果有bugfix的行为,需要将bugfix的分支同时merge到`master`, `develop`和`release/版本号`这三个分支。 +## 发布whl包到pypi + +使用[PaddlePaddle CI](https://paddleci.ngrok.io/project.html?projectId=Manylinux1&tab=projectOverview) +完成自动化二进制编译,参考下图,选择需要发布的版本(通常包含一个CPU版本和一个GPU版本),点击"run"右侧的"..."按钮,可以 +弹出下面的选择框,在第二个tab (Changes)里选择需要发布的分支,这里选择0.11.0,然后点击"Run Build"按钮。等待编译完成后 +可以在此页面的"Artifacts"下拉框中找到生成的3个二进制文件,分别对应CAPI,`cp27m`和`cp27mu`的版本。然后按照上述的方法 +使用`twine`工具上传即可。 + + + +* 注:CI环境使用 https://github.com/PaddlePaddle/buildtools 这里的DockerImage作为编译环境以支持更多的Linux + 发型版,如果需要手动编译,也可以使用这些镜像。这些镜像也可以从 https://hub.docker.com/r/paddlepaddle/paddle_manylinux_devel/tags/ 下载得到。 +* pypi不支持覆盖上传,所以一个版本号的whl包发布之后,不可以更改。下一个whl包需要更新版本号才可以上传。 + +## 发布Docker镜像 + +上述PaddlePaddle CI编译whl完成后会自动将Docker镜像push到DockerHub,所以,发布Docker镜像只需要对自动push的镜像打上 +版本号对应的tag即可: + +1. 进入 https://hub.docker.com/r/paddlepaddle/paddle/tags/ 查看latest tag的更新时间是否在上述编译whl包完成后是否最新。 +1. 执行 `docker pull paddlepaddle/paddle:[latest tag]`,latest tag可以是latest或latest-gpu等。 +1. 执行 `docker tag paddlepaddle/paddle:[latest tag] paddlepaddle/paddle:[version]` +1. 执行 `docker push paddlepaddle/paddle:[version]` + ## PaddlePaddle 分支规范 PaddlePaddle开发过程使用[git-flow](http://nvie.com/posts/a-successful-git-branching-model/)分支规范,并适应github的特性做了一些区别。 From 7a4f3be9f3f44781f789b60620a77f3857a6cd15 Mon Sep 17 00:00:00 2001 From: Yibing Liu Date: Sat, 6 Jan 2018 06:14:48 +0000 Subject: [PATCH 41/49] Fix profiler place bug --- paddle/platform/profiler.cc | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/paddle/platform/profiler.cc b/paddle/platform/profiler.cc index abb1e6a89c..239df23128 100644 --- a/paddle/platform/profiler.cc +++ b/paddle/platform/profiler.cc @@ -181,6 +181,7 @@ std::vector> DisableProfiler() { void ParseEvents(std::vector>& events, EventSortingKey sorted_by) { + if (g_profiler_place == "") return; std::vector> events_table; size_t max_name_width = 0; for (size_t i = 0; i < events.size(); i++) { @@ -199,7 +200,7 @@ void ParseEvents(std::vector>& events, } if (rit != pushed_events.rend()) { - double event_time = (g_state == ProfilerState::kCUDA) + double event_time = (g_profiler_place == "CUDA") ? rit->CudaElapsedMs(events[i][j]) : rit->CpuElapsedMs(events[i][j]); std::string event_name = @@ -224,7 +225,7 @@ void ParseEvents(std::vector>& events, std::max(event_time, event_items[index].max_time); } - // remove the start marker from the list + // remove the push marker from the list pushed_events.erase((++rit).base()); } else { LOG(WARNING) << "Cannot find the push marker of event \'" @@ -257,13 +258,11 @@ void ParseEvents(std::vector>& events, } events_table.push_back(event_items); - // To check whether there are events with `push` but without `pop` + // log warning if there are events with `push` but without `pop` std::list::reverse_iterator rit = pushed_events.rbegin(); while (rit != pushed_events.rend()) { - if (rit->kind() == "push") { - LOG(WARNING) << "Cannot find the pop marker of event \'" << rit->name() - << "\', which will be ignored in profiling report."; - } + LOG(WARNING) << "Cannot find the pop marker of event \'" << rit->name() + << "\', which will be ignored in profiling report."; ++rit; } } @@ -275,7 +274,6 @@ void ParseEvents(std::vector>& events, void PrintProfilingReport(std::vector>& events_table, EventSortingKey sorted_by, const size_t name_width, const size_t data_width) { - if (g_profiler_place == "") return; // Output header information std::cout << "\n------------------------->" << " Profiling Report " From b865545a00f23dc3e05ec8b04f5ec938c30e820f Mon Sep 17 00:00:00 2001 From: typhoonzero Date: Sun, 7 Jan 2018 22:46:16 +0800 Subject: [PATCH 42/49] follow comments --- doc/design/ci_build_whl.png | Bin 0 -> 287162 bytes doc/design/releasing_process.md | 8 ++++---- 2 files changed, 4 insertions(+), 4 deletions(-) create mode 100644 doc/design/ci_build_whl.png diff --git a/doc/design/ci_build_whl.png b/doc/design/ci_build_whl.png new file mode 100644 index 0000000000000000000000000000000000000000..232762b82a9ae3e979a1f38a7beb715c87438f40 GIT binary patch literal 287162 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%0A&-&{?FF24yJ8Go;#1U~d$&ISRCpipW0 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

}6Qu`2pGC0PaQd6Amn%i&@2OXQ#z*hy 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�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 Date: Sun, 7 Jan 2018 17:52:08 -0800 Subject: [PATCH 43/49] Modify inference.cc to run example without pickletools (#7262) --- paddle/inference/inference.cc | 23 ++++++++--------------- python/paddle/v2/fluid/io.py | 5 +++++ 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/paddle/inference/inference.cc b/paddle/inference/inference.cc index 48a51efcd2..49e39358e8 100644 --- a/paddle/inference/inference.cc +++ b/paddle/inference/inference.cc @@ -38,23 +38,16 @@ void InferenceEngine::LoadInferenceModel( LOG(INFO) << "program_desc_str's size: " << program_desc_str.size(); // PicklingTools cannot parse the vector of strings correctly. #else - // program_desc_str - // the inference.model is stored by following python codes: - // inference_program = fluid.io.get_inference_program(predict) - // model_filename = "recognize_digits_mlp.inference.model/inference.model" - // with open(model_filename, "w") as f: - // program_str = inference_program.desc.serialize_to_string() - // f.write(struct.pack('q', len(program_str))) - // f.write(program_str) - std::string model_filename = dirname + "/inference.model"; + std::string model_filename = dirname + "/__model__.dat"; LOG(INFO) << "loading model from " << model_filename; - std::ifstream fs(model_filename, std::ios_base::binary); - int64_t size = 0; - fs.read(reinterpret_cast(&size), sizeof(int64_t)); - LOG(INFO) << "program_desc_str's size: " << size; + std::ifstream inputfs(model_filename, std::ios::in | std::ios::binary); std::string program_desc_str; - program_desc_str.resize(size); - fs.read(&program_desc_str[0], size); + inputfs.seekg(0, std::ios::end); + program_desc_str.resize(inputfs.tellg()); + inputfs.seekg(0, std::ios::beg); + LOG(INFO) << "program_desc_str's size: " << program_desc_str.size(); + inputfs.read(&program_desc_str[0], program_desc_str.size()); + inputfs.close(); #endif program_ = new framework::ProgramDesc(program_desc_str); GenerateLoadProgram(dirname); diff --git a/python/paddle/v2/fluid/io.py b/python/paddle/v2/fluid/io.py index 926327b70c..c63567601a 100644 --- a/python/paddle/v2/fluid/io.py +++ b/python/paddle/v2/fluid/io.py @@ -212,6 +212,11 @@ def save_inference_model(dirname, "fetch_var_names": fetch_var_names }, f, -1) + # Save only programDesc of inference_program in binary format + # in another file: __model__.dat + with open(model_file_name + ".dat", "wb") as fp: + fp.write(inference_program.desc.serialize_to_string()) + save_params(executor, dirname, main_program) From 05c08214e33cadac54966250b47b7903d175c404 Mon Sep 17 00:00:00 2001 From: yangyaming Date: Mon, 8 Jan 2018 10:50:40 +0800 Subject: [PATCH 44/49] Bug fix when inserting fill_zeros_like_op. --- python/paddle/v2/fluid/backward.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/python/paddle/v2/fluid/backward.py b/python/paddle/v2/fluid/backward.py index 88fe19da5e..66a7f73757 100644 --- a/python/paddle/v2/fluid/backward.py +++ b/python/paddle/v2/fluid/backward.py @@ -7,7 +7,7 @@ __all__ = ['append_backward'] def _rename_arg_(op_descs, old_name, new_name, begin_idx=None, end_idx=None): """ - Traverse all ops in op_descs[begin_idx : end_idx], + Traverse all ops in op_descs[begin_idx : end_idx], if any op has inputs/outputs named "old_name", rename it as 'new_name' """ if begin_idx is None: @@ -162,7 +162,7 @@ def _remove_no_grad_branch_(op_descs, no_grad_set): if core.grad_var_suffix() in arg and arg in no_grad_set: to_insert.append((_create_op_desc_("fill_zeros_like", { "X": [_strip_grad_suffix_(arg)] - }, {"Y": [arg]}, {}), idx)) + }, {"Out": [arg]}, {}), idx)) map(lambda p: op_descs.insert(p[1], p[0]), reversed(to_insert)) @@ -182,7 +182,7 @@ def _append_backward_ops_(target, target(Variable): the target variable of forward pass block(Block): the block where forward ops are target_block(Block): the block which is going to hold new generated grad ops - no_grad_dict(dict): + no_grad_dict(dict): key(int) block index val(set) a set of varibale names. These varibales have no gradient grad_to_var(dict)(output argument): @@ -276,8 +276,8 @@ def append_backward(loss, parameter_list=None, no_grad_set=None): loss(Variable): The variable generated by cost function. parameter_list(list): Parameters that need to be updated by optimizer. If None, it means all parameters need to be updated. - no_grad_set(set): Variables that have no gradients in Block 0. - If None, the set will be generated inside the function and + no_grad_set(set): Variables that have no gradients in Block 0. + If None, the set will be generated inside the function and contains all variables with `step_gradient=True` from all blocks. Return: From 691b5cac6164f6db6b421b6c809e6163afdea534 Mon Sep 17 00:00:00 2001 From: Siddharth Goyal Date: Sun, 7 Jan 2018 19:31:14 -0800 Subject: [PATCH 45/49] Fix equation for gru op (#7274) --- python/paddle/v2/fluid/layers/nn.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/python/paddle/v2/fluid/layers/nn.py b/python/paddle/v2/fluid/layers/nn.py index cc32a0a19a..7feb479d2e 100644 --- a/python/paddle/v2/fluid/layers/nn.py +++ b/python/paddle/v2/fluid/layers/nn.py @@ -243,18 +243,21 @@ def gru_unit(input, r_t & = actGate(xr_{t} + W_r h_{t-1} + b_r) - ch_t & = actNode(xc_t + W_c dot(r_t, h_{t-1}) + b_c) + m_t & = actNode(xm_t + W_c dot(r_t, h_{t-1}) + b_m) - h_t & = dot((1-u_t), ch_{t-1}) + dot(u_t, h_t) + h_t & = dot((1-u_t), m_t) + dot(u_t, h_{t-1}) The inputs of gru unit includes :math:`z_t`, :math:`h_{t-1}`. In terms of the equation above, the :math:`z_t` is split into 3 parts - - :math:`xu_t`, :math:`xr_t` and :math:`xc_t`. This means that in order to + :math:`xu_t`, :math:`xr_t` and :math:`xm_t`. This means that in order to implement a full GRU unit operator for an input, a fully connected layer has to be applied, such that :math:`z_t = W_{fc}x_t`. - This layer has three outputs :math:`h_t`, :math:`dot(r_t, h_{t - 1})` - and concatenation of :math:`u_t`, :math:`r_t` and :math:`ch_t`. + The terms :math:`u_t` and :math:`r_t` represent the update and reset gates + of the GRU cell. Unlike LSTM, GRU has one lesser gate. However, there is + an intermediate candidate hidden output, which is denoted by :math:`m_t`. + This layer has three outputs :math:`h_t`, :math:`dot(r_t, h_{t-1})` + and concatenation of :math:`u_t`, :math:`r_t` and :math:`m_t`. Args: input (Variable): The fc transformed input value of current step. From 8814bec0c5275036e50f9bf12ceb67648419d04f Mon Sep 17 00:00:00 2001 From: emailweixu Date: Sun, 7 Jan 2018 19:54:54 -0800 Subject: [PATCH 46/49] Show argument dimensions with operator::DebugStringEx (#7268) This can make it easier to locate error. --- paddle/framework/executor.cc | 2 +- paddle/framework/operator.cc | 19 ++++++++++++++++++- paddle/framework/operator.h | 5 ++++- paddle/operators/net_op.cc | 6 +++--- paddle/operators/net_op.h | 3 ++- 5 files changed, 28 insertions(+), 7 deletions(-) diff --git a/paddle/framework/executor.cc b/paddle/framework/executor.cc index bf1f0471cc..844d98916e 100644 --- a/paddle/framework/executor.cc +++ b/paddle/framework/executor.cc @@ -111,7 +111,7 @@ void Executor::Run(const ProgramDesc& pdesc, Scope* scope, int block_id, for (auto& op_desc : block.AllOps()) { auto op = paddle::framework::OpRegistry::CreateOp(*op_desc); - VLOG(3) << op->DebugString(); + VLOG(3) << op->DebugStringEx(local_scope); op->Run(*local_scope, place_); if (FLAGS_check_nan_inf) { for (auto& vname : op->OutputVars(true)) { diff --git a/paddle/framework/operator.cc b/paddle/framework/operator.cc index b9dcf16da5..4ef0c2523c 100644 --- a/paddle/framework/operator.cc +++ b/paddle/framework/operator.cc @@ -73,6 +73,17 @@ void UseALL() { UseCUDNN(); } +static DDim GetDims(const Scope& scope, const std::string& name) { + Variable* var = scope.FindVar(name); + if (var->IsType()) { + return var->Get().dims(); + } else if (var->IsType()) { + return var->Get().GetCompleteDims(); + } else { + return DDim({-1}); + } +} + std::string OperatorBase::Input(const std::string& name) const { auto& ins = Inputs(name); PADDLE_ENFORCE_LE(ins.size(), 1UL, @@ -105,7 +116,7 @@ const std::vector& OperatorBase::Outputs( return it->second; } -std::string OperatorBase::DebugString() const { +std::string OperatorBase::DebugStringEx(const Scope* scope) const { std::stringstream ss; ss << "Op(" << type_ << "), inputs:{"; for (auto it = inputs_.begin(); it != inputs_.end();) { @@ -113,6 +124,9 @@ std::string OperatorBase::DebugString() const { ss << input.first << "["; for (size_t i = 0; i < input.second.size(); ++i) { ss << input.second[i]; + if (scope) { + ss << "(" << GetDims(*scope, input.second[i]) << ")"; + } if (i != input.second.size() - 1) { ss << ", "; } @@ -129,6 +143,9 @@ std::string OperatorBase::DebugString() const { ss << output.first << "["; for (size_t i = 0; i < output.second.size(); ++i) { ss << output.second[i]; + if (scope) { + ss << "(" << GetDims(*scope, output.second[i]) << ")"; + } if (i != output.second.size() - 1) { ss << ", "; } diff --git a/paddle/framework/operator.h b/paddle/framework/operator.h index 1f5a4af58c..800397c077 100644 --- a/paddle/framework/operator.h +++ b/paddle/framework/operator.h @@ -108,7 +108,10 @@ class OperatorBase { return boost::get(attrs_.at(name)); } - virtual std::string DebugString() const; + /// if scope is not null, also show dimensions of arguments + virtual std::string DebugStringEx(const Scope* scope) const; + + std::string DebugString() const { return DebugStringEx(nullptr); } /// Net will call this function to Run an op. virtual void Run(const Scope& scope, const platform::Place& place) const = 0; diff --git a/paddle/operators/net_op.cc b/paddle/operators/net_op.cc index 78b5e27678..03302f5cbf 100644 --- a/paddle/operators/net_op.cc +++ b/paddle/operators/net_op.cc @@ -56,11 +56,11 @@ void NetOp::CompleteAddOp(bool calc) { std::copy(output_set.begin(), output_set.end(), std::back_inserter(outputs)); } -std::string NetOp::DebugString() const { +std::string NetOp::DebugStringEx(const framework::Scope* scope) const { std::ostringstream os; - os << OperatorBase::DebugString() << std::endl; + os << OperatorBase::DebugStringEx(scope) << std::endl; for (auto& op : ops_) { - std::istringstream is(op->DebugString()); + std::istringstream is(op->DebugStringEx(scope)); for (std::string line; std::getline(is, line);) { os << " " << line << std::endl; } diff --git a/paddle/operators/net_op.h b/paddle/operators/net_op.h index 85d0153b32..b24042f5ef 100644 --- a/paddle/operators/net_op.h +++ b/paddle/operators/net_op.h @@ -106,7 +106,8 @@ class NetOp : public framework::OperatorBase { void CompleteAddOp(bool calculate = true); - std::string DebugString() const override; + std::string DebugStringEx( + const framework::Scope* scope = nullptr) const override; bool IsNetOp() const override; std::vector OutputVars(bool has_intermediate) const override; From d09503b2bef5b134b50e7a9069426276bcf73762 Mon Sep 17 00:00:00 2001 From: Yibing Liu Date: Mon, 8 Jan 2018 04:29:57 +0000 Subject: [PATCH 47/49] Remove the redundant switch case statement --- paddle/platform/profiler.cc | 80 +++++++++++++++++++------------------ paddle/platform/profiler.h | 2 +- 2 files changed, 43 insertions(+), 39 deletions(-) diff --git a/paddle/platform/profiler.cc b/paddle/platform/profiler.cc index 239df23128..2562b2b5f0 100644 --- a/paddle/platform/profiler.cc +++ b/paddle/platform/profiler.cc @@ -182,6 +182,44 @@ std::vector> DisableProfiler() { void ParseEvents(std::vector>& events, EventSortingKey sorted_by) { if (g_profiler_place == "") return; + + std::string sorted_domain; + std::function sorted_func; + switch (sorted_by) { + case EventSortingKey::kCalls: + sorted_domain = "number of calls"; + sorted_func = [](EventItem& a, EventItem& b) { + return a.calls > b.calls; + }; + break; + case EventSortingKey::kTotal: + sorted_domain = "total time"; + sorted_func = [](EventItem& a, EventItem& b) { + return a.total_time > b.total_time; + }; + break; + case EventSortingKey::kMin: + sorted_domain = "minimum time"; + sorted_func = [](EventItem& a, EventItem& b) { + return a.min_time > b.min_time; + }; + break; + case EventSortingKey::kMax: + sorted_domain = "maximum time"; + sorted_func = [](EventItem& a, EventItem& b) { + return a.max_time > b.max_time; + }; + break; + case EventSortingKey::kAve: + sorted_domain = "average time"; + sorted_func = [](EventItem& a, EventItem& b) { + return a.ave_time > b.ave_time; + }; + break; + default: + sorted_domain = "event end time"; + } + std::vector> events_table; size_t max_name_width = 0; for (size_t i = 0; i < events.size(); i++) { @@ -240,21 +278,7 @@ void ParseEvents(std::vector>& events, } // sort if (sorted_by != EventSortingKey::kDefault) { - std::sort(event_items.begin(), event_items.end(), - [&](EventItem& a, EventItem& b) { - switch (sorted_by) { - case EventSortingKey::kCalls: - return a.calls > b.calls; - case EventSortingKey::kTotal: - return a.total_time > b.total_time; - case EventSortingKey::kMin: - return a.min_time > b.min_time; - case EventSortingKey::kMax: - return a.max_time > b.max_time; - default: - return a.ave_time > b.ave_time; - } - }); + std::sort(event_items.begin(), event_items.end(), sorted_func); } events_table.push_back(event_items); @@ -268,11 +292,11 @@ void ParseEvents(std::vector>& events, } // Print report - PrintProfilingReport(events_table, sorted_by, max_name_width + 4, 12); + PrintProfilingReport(events_table, sorted_domain, max_name_width + 4, 12); } void PrintProfilingReport(std::vector>& events_table, - EventSortingKey sorted_by, const size_t name_width, + std::string& sorted_domain, const size_t name_width, const size_t data_width) { // Output header information std::cout << "\n------------------------->" @@ -280,27 +304,7 @@ void PrintProfilingReport(std::vector>& events_table, << "<-------------------------\n\n"; std::cout << "Place: " << g_profiler_place << std::endl; std::cout << "Time unit: ms" << std::endl; - std::string sort_domain = "event end time"; - switch (sorted_by) { - case EventSortingKey::kCalls: - sort_domain = "number of calls"; - break; - case EventSortingKey::kTotal: - sort_domain = "total time"; - break; - case EventSortingKey::kMin: - sort_domain = "minimum time"; - break; - case EventSortingKey::kMax: - sort_domain = "maximum time"; - break; - case EventSortingKey::kAve: - sort_domain = "average time"; - break; - default: - break; - } - std::cout << "Sorted by " << sort_domain + std::cout << "Sorted by " << sorted_domain << " in descending order in the same thread\n\n"; // Output events table std::cout.setf(std::ios::left); diff --git a/paddle/platform/profiler.h b/paddle/platform/profiler.h index f97a586787..6df48ef880 100644 --- a/paddle/platform/profiler.h +++ b/paddle/platform/profiler.h @@ -136,7 +136,7 @@ void ParseEvents(std::vector>&, // Print results void PrintProfilingReport(std::vector>& events_table, - EventSortingKey sorted_by, const size_t name_width, + std::string& sorted_domain, const size_t name_width, const size_t data_width); } // namespace platform } // namespace paddle From ea0280b4a17ae3862c127bcdda1e7900b5ba1e11 Mon Sep 17 00:00:00 2001 From: Yibing Liu Date: Mon, 8 Jan 2018 04:33:46 +0000 Subject: [PATCH 48/49] Remove unused included header gflags --- paddle/platform/profiler.cc | 1 - 1 file changed, 1 deletion(-) diff --git a/paddle/platform/profiler.cc b/paddle/platform/profiler.cc index 2562b2b5f0..4283724d28 100644 --- a/paddle/platform/profiler.cc +++ b/paddle/platform/profiler.cc @@ -15,7 +15,6 @@ limitations under the License. */ #include "paddle/platform/profiler.h" #include #include -#include "gflags/gflags.h" #include "glog/logging.h" namespace paddle { From 0f353ab46ed75ab99213f5ee38fcd1867838ef5f Mon Sep 17 00:00:00 2001 From: Qiao Longfei Date: Mon, 8 Jan 2018 13:10:44 +0800 Subject: [PATCH 49/49] cpu gpu transform function (#7191) * add rename guard * add device_data_transform * add device_data_transform_test * modify GetExpectedKernelType * update operator.run * support test test_label_semantic_roles * optimize code * optimize code * rename GetActualKernelType to GetExpectedKernelType * fix chunk_eval_op and device_data_transform_test * add is_same_place to place * optimize code, refine rename_guard * refine rename guard, add GetKernelTypeForVar * optimize code * add some log * rename guard * use sub scope to create var * fix compile * add IsInitialized for Tensor * add VarIsTensor * fix op_registry_test * test * tmp disable priority * restore switch_kernel.md * code clean --- paddle/framework/CMakeLists.txt | 7 +- paddle/framework/data_transform.cc | 33 ++++ paddle/framework/data_transform.h | 8 + paddle/framework/device_data_transform.cc | 46 +++++ paddle/framework/device_data_transform.h | 27 +++ .../framework/device_data_transform_test.cu | 168 ++++++++++++++++++ paddle/framework/op_registry_test.cc | 22 +-- paddle/framework/operator.cc | 151 +++++----------- paddle/framework/operator.h | 7 +- paddle/framework/operator_test.cc | 3 +- paddle/framework/scope.cc | 1 + paddle/framework/scope.h | 2 +- paddle/framework/tensor.h | 2 + paddle/framework/tensor_impl.h | 2 + paddle/framework/variable.h | 2 + paddle/operators/accuracy_op.cc | 2 +- paddle/operators/auc_op.cc | 2 +- paddle/operators/batch_norm_op.cc | 2 +- paddle/operators/chunk_eval_op.cc | 4 +- paddle/operators/chunk_eval_op.h | 13 +- paddle/operators/compare_op.cc | 4 +- paddle/operators/conv_op.h | 13 -- paddle/operators/crf_decoding_op.cc | 10 +- paddle/operators/crf_decoding_op.h | 3 - paddle/operators/cross_entropy_op.cc | 4 +- .../fill_constant_batch_size_like_op.cc | 2 +- paddle/operators/gather_op.cc | 4 +- paddle/operators/gaussian_random_op.cc | 2 +- paddle/operators/linear_chain_crf_op.cc | 4 +- paddle/operators/lod_reset_op.cc | 4 +- paddle/operators/logical_op.cc | 4 +- paddle/operators/lookup_table_op.cc | 4 +- paddle/operators/lstm_op.cc | 4 +- paddle/operators/multiplex_op.cc | 4 +- paddle/operators/nce_op.cc | 4 +- paddle/operators/pool_with_index_op.cc | 4 +- paddle/operators/positive_negative_pair_op.cc | 2 +- paddle/operators/precision_recall_op.cc | 2 +- paddle/operators/roi_pool_op.cc | 4 +- paddle/operators/scatter_op.cc | 4 +- paddle/operators/sequence_pool_op.cc | 2 +- paddle/operators/sequence_slice_op.cc | 4 +- .../softmax_with_cross_entropy_op.cc | 4 +- paddle/operators/sum_op.cc | 2 +- paddle/operators/uniform_random_op.cc | 2 +- paddle/operators/unpool_op.cc | 4 +- paddle/platform/place.cc | 12 ++ paddle/platform/place.h | 1 + .../tests/book/test_label_semantic_roles.py | 8 +- 49 files changed, 429 insertions(+), 200 deletions(-) create mode 100644 paddle/framework/device_data_transform.cc create mode 100644 paddle/framework/device_data_transform.h create mode 100644 paddle/framework/device_data_transform_test.cu diff --git a/paddle/framework/CMakeLists.txt b/paddle/framework/CMakeLists.txt index 3967a40136..46439c2d27 100644 --- a/paddle/framework/CMakeLists.txt +++ b/paddle/framework/CMakeLists.txt @@ -32,7 +32,9 @@ cc_test(threadpool_test SRCS threadpool_test.cc DEPS threadpool) cc_library(scope SRCS scope.cc DEPS glog threadpool) cc_test(scope_test SRCS scope_test.cc DEPS scope) -cc_library(data_transform SRCS data_transform.cc DEPS math_function tensor framework_proto) +cc_library(device_data_transform SRCS device_data_transform.cc DEPS tensor) + +cc_library(data_transform SRCS data_transform.cc DEPS math_function tensor framework_proto selected_rows device_data_transform) cc_test(data_transform_test SRCS data_transform_test.cc DEPS data_transform device_context) cc_library(attribute SRCS attribute.cc DEPS framework_proto) @@ -77,3 +79,6 @@ cc_library(init SRCS init.cc DEPS gflags device_context place stringpiece operat cc_test(init_test SRCS init_test.cc DEPS init) cc_test(op_kernel_type_test SRCS op_kernel_type_test.cc DEPS place device_context framework_proto) + +nv_test(device_data_transform_test SRCS device_data_transform_test.cu + DEPS operator op_registry init math_function) diff --git a/paddle/framework/data_transform.cc b/paddle/framework/data_transform.cc index 6b17809688..55825c5b7d 100644 --- a/paddle/framework/data_transform.cc +++ b/paddle/framework/data_transform.cc @@ -14,7 +14,9 @@ limitations under the License. */ #include #include "paddle/framework/data_transform.h" +#include "paddle/framework/device_data_transform.h" #include "paddle/framework/lod_tensor.h" +#include "paddle/framework/selected_rows.h" #include "paddle/platform/device_context.h" namespace paddle { @@ -25,6 +27,37 @@ DataTransformFnMap& DataTransformFnMap::Instance() { return data_transform_map; } +Tensor* DataTransform(const OpKernelType& expected_kernel_type, + const OpKernelType& kernel_type_for_var, + const Tensor& input_tensor) { + Tensor* out = nullptr; + if (!platform::is_same_place(kernel_type_for_var.place_, + expected_kernel_type.place_)) { + out = DeviceTransform(input_tensor, expected_kernel_type.place_); + } + PADDLE_ENFORCE_NOT_NULL(out, "out should not be null"); + return out; +} + +void CopyVariableWithTensor(const Variable& in_var, const Tensor& tensor, + Variable& out_var) { + if (in_var.IsType()) { + auto& in_lod_tensor = in_var.Get(); + auto* tran_lod_tensor = out_var.GetMutable(); + tran_lod_tensor->set_lod(in_lod_tensor.lod()); + tran_lod_tensor->set_layout(in_lod_tensor.layout()); + tran_lod_tensor->ShareDataWith(tensor); + } else if (in_var.IsType()) { + auto& in_selected_rows = in_var.Get(); + auto* trans_selected_rows = out_var.GetMutable(); + trans_selected_rows->set_height(in_selected_rows.height()); + trans_selected_rows->set_rows(in_selected_rows.rows()); + trans_selected_rows->mutable_value()->ShareDataWith(tensor); + } else { + PADDLE_THROW("unknown var type"); + } +} + auto KernelFP32 = OpKernelType(proto::DataType::FP32, platform::CPUPlace(), DataLayout::kNHWC, LibraryType::kPlain); diff --git a/paddle/framework/data_transform.h b/paddle/framework/data_transform.h index 56ebc80f43..42fc5f4d7e 100644 --- a/paddle/framework/data_transform.h +++ b/paddle/framework/data_transform.h @@ -19,6 +19,7 @@ limitations under the License. */ #include #include "paddle/framework/op_kernel_type.h" +#include "paddle/framework/selected_rows.h" #include "paddle/framework/tensor.h" #include "paddle/framework/variable.h" #include "paddle/operators/math/math_function.h" @@ -49,6 +50,13 @@ struct KernelTypePairHash { } }; +Tensor* DataTransform(const OpKernelType& expected_kernel_type, + const OpKernelType& kernel_type_for_var, + const Tensor& input_tensor); + +void CopyVariableWithTensor(const Variable& in_var, const Tensor& tensor, + Variable& out_var); + template struct CastDataTypeFunctor { HOSTDEVICE inline OutType operator()(InType in) const { diff --git a/paddle/framework/device_data_transform.cc b/paddle/framework/device_data_transform.cc new file mode 100644 index 0000000000..4f9b7e96a2 --- /dev/null +++ b/paddle/framework/device_data_transform.cc @@ -0,0 +1,46 @@ +/* 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/framework/device_data_transform.h" + +namespace paddle { +namespace framework { + +static const platform::DeviceContext* GetDeviceContext( + const platform::Place& src_place, const platform::Place& dst_place) { + platform::DeviceContextPool& pool = platform::DeviceContextPool::Instance(); + + if (platform::is_gpu_place(src_place) && platform::is_cpu_place(dst_place)) { + return pool.Get(src_place); + } else if (platform::is_cpu_place(src_place) && + platform::is_gpu_place(dst_place)) { + return pool.Get(dst_place); + } else { + PADDLE_THROW( + "Currently, model parallelism is only supported between CPU and CUDA"); + } +} + +Tensor* DeviceTransform(const Tensor& in, const platform::Place& dst_place) { + VLOG(3) << "DeviceTransform in, src_place " << in.place() + << " dst_place: " << dst_place; + Tensor* out = new Tensor(); + auto* dev_ctx = GetDeviceContext(in.place(), dst_place); + dev_ctx->Wait(); + CopyFrom(in, dst_place, *dev_ctx, out); + dev_ctx->Wait(); + return out; +} + +} // namespace framework +} // namespace paddle diff --git a/paddle/framework/device_data_transform.h b/paddle/framework/device_data_transform.h new file mode 100644 index 0000000000..bebf0d1b32 --- /dev/null +++ b/paddle/framework/device_data_transform.h @@ -0,0 +1,27 @@ +/* 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 "paddle/framework/lod_tensor.h" +#include "paddle/framework/tensor.h" +#include "paddle/framework/tensor_util.h" +#include "paddle/platform/device_context.h" + +namespace paddle { +namespace framework { + +Tensor* DeviceTransform(const Tensor& in, const platform::Place& dst_place); + +} // namespace framework +} // namespace paddle diff --git a/paddle/framework/device_data_transform_test.cu b/paddle/framework/device_data_transform_test.cu new file mode 100644 index 0000000000..e9100053d5 --- /dev/null +++ b/paddle/framework/device_data_transform_test.cu @@ -0,0 +1,168 @@ +/* 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 "gtest/gtest.h" + +#include "paddle/framework/init.h" +#include "paddle/framework/lod_tensor.h" +#include "paddle/framework/op_info.h" +#include "paddle/framework/op_registry.h" +#include "paddle/operators/elementwise_op_function.h" +#include "paddle/operators/math/math_function.h" +#include "paddle/platform/device_context.h" + +namespace paddle { +namespace framework { + +template +struct AddFunctor { + inline HOSTDEVICE T operator()(T a, T b) const { return a + b; } +}; + +class OpKernelTestProtoAndCheckerMaker : public OpProtoAndCheckerMaker { + public: + OpKernelTestProtoAndCheckerMaker(OpProto* proto, OpAttrChecker* op_checker) + : OpProtoAndCheckerMaker(proto, op_checker) { + AddInput("input", "input1 of test op"); + AddOutput("output", "output of test op"); + AddAttr("use_gpu", "force to use gpu kernel").SetDefault(false); + AddComment("This is test op"); + } +}; + +class TestOpWithKernel : public OperatorWithKernel { + public: + using OperatorWithKernel::OperatorWithKernel; + + protected: + void InferShape(framework::InferShapeContext* ctx) const override {} + OpKernelType GetExpectedKernelType( + const ExecutionContext& ctx) const override { + if (Attr("use_gpu")) { + VLOG(3) << "force use gpu kernel"; + return OpKernelType(proto::DataType::FP32, platform::CUDAPlace(0)); + } else { + VLOG(3) << "use default kernel"; + return OpKernelType(proto::DataType::FP32, + ctx.Input("input")->place()); + } + } +}; + +template +class TestKernel : public OpKernel { + public: + void Compute(const ExecutionContext& ctx) const { + std::cout << ctx.op().DebugString() << std::endl; + + const Tensor* input = ctx.Input("input"); + + std::cout << "input place:" << input->place() << std::endl; + auto* output = ctx.Output("output"); + output->Resize(input->dims()); + output->mutable_data(ctx.GetPlace()); + + operators::TransformFunctor, T, DeviceContext> functor( + input, input, output, ctx.template device_context(), + AddFunctor()); + functor.Run(); + } +}; + +} // namespace framework +} // namespace paddle + +REGISTER_OP_WITHOUT_GRADIENT( + test_op, paddle::framework::TestOpWithKernel, + paddle::framework::OpKernelTestProtoAndCheckerMaker); +REGISTER_OP_CPU_KERNEL( + test_op, + paddle::framework::TestKernel); +REGISTER_OP_CUDA_KERNEL( + test_op, + paddle::framework::TestKernel); + +static void BuildVar(const std::string& param_name, + std::initializer_list arguments, + paddle::framework::proto::OpDesc::Var* var) { + var->set_parameter(param_name); + for (auto& arg_name : arguments) { + *var->mutable_arguments()->Add() = arg_name; + } +} + +TEST(Operator, CPUtoGPU) { + using namespace paddle::framework; + using namespace paddle::platform; + + ASSERT_EQ(InitDevices({"CPU", "GPU:0"}), true); + + paddle::framework::Scope scope; + paddle::platform::CPUPlace cpu_place; + + // create an op to run on CPU + paddle::framework::proto::OpDesc cpu_op_desc; + cpu_op_desc.set_type("test_op"); + BuildVar("input", {"IN1"}, cpu_op_desc.add_inputs()); + BuildVar("output", {"OUT1"}, cpu_op_desc.add_outputs()); + + auto cpu_op = paddle::framework::OpRegistry::CreateOp(cpu_op_desc); + // prepare input + auto* in_t = scope.Var("IN1")->GetMutable(); + auto* src_ptr = in_t->mutable_data({2, 3}, CPUPlace()); + for (int i = 0; i < 2 * 3; ++i) { + src_ptr[i] = static_cast(i); + } + + // get output + auto* output = scope.Var("OUT1"); + cpu_op->Run(scope, cpu_place); + + auto* output_ptr = output->Get().data(); + for (int i = 0; i < 2 * 3; ++i) { + ASSERT_EQ(output_ptr[i], static_cast(i) * 2); + } + + // create an op to run on GPU + paddle::framework::proto::OpDesc gpu_op_desc; + gpu_op_desc.set_type("test_op"); + BuildVar("input", {"OUT1"}, gpu_op_desc.add_inputs()); + BuildVar("output", {"OUT2"}, gpu_op_desc.add_outputs()); + + auto attr = gpu_op_desc.mutable_attrs()->Add(); + attr->set_name("use_gpu"); + attr->set_type(paddle::framework::proto::AttrType::BOOLEAN); + attr->set_b(true); + + auto gpu_op = paddle::framework::OpRegistry::CreateOp(gpu_op_desc); + + paddle::platform::CUDAPlace cuda_place(0); + // get output + auto* output2 = scope.Var("OUT2"); + gpu_op->Run(scope, cuda_place); + + // auto* output2_ptr = output2->Get().data(); + DeviceContextPool& pool = DeviceContextPool::Instance(); + auto dev_ctx = pool.Get(cuda_place); + + paddle::framework::Tensor output_tensor; + CopyFrom(output2->Get(), paddle::platform::CPUPlace(), *dev_ctx, + &output_tensor); + + dev_ctx->Wait(); + float* output2_ptr = output_tensor.data(); + for (int i = 0; i < 2 * 3; ++i) { + ASSERT_EQ(output2_ptr[i], static_cast(i) * 4); + } +} diff --git a/paddle/framework/op_registry_test.cc b/paddle/framework/op_registry_test.cc index a286925bbe..f7a10ada80 100644 --- a/paddle/framework/op_registry_test.cc +++ b/paddle/framework/op_registry_test.cc @@ -218,7 +218,7 @@ class OpWithKernelTest : public OperatorWithKernel { protected: void InferShape(InferShapeContext* ctx) const override {} - framework::OpKernelType GetActualKernelType( + framework::OpKernelType GetExpectedKernelType( const framework::ExecutionContext& ctx) const override { return framework::OpKernelType(proto::DataType::FP32, ctx.device_context()); } @@ -282,16 +282,11 @@ class OpWithMultiKernelTest : public OperatorWithKernel { protected: void InferShape(InferShapeContext* ctx) const override {} - framework::OpKernelType GetActualKernelType( - const framework::ExecutionContext& ctx) const override { - return framework::OpKernelType(proto::DataType::FP32, ctx.device_context()); - } - framework::OpKernelType GetExpectedKernelType( - const framework::OpKernelType& kernel) const override { - return framework::OpKernelType(kernel.data_type_, platform::CUDAPlace(0), - kernel.data_layout_, - framework::LibraryType::kCUDNN); + const framework::ExecutionContext& ctx) const override { + return framework::OpKernelType( + proto::DataType::FP32, platform::CUDAPlace(0), DataLayout::kAnyLayout, + framework::LibraryType::kCUDNN); } }; @@ -371,6 +366,7 @@ TEST(OperatorRegistrar, OpWithMultiKernel) { op_desc.set_type("op_with_multi_kernel"); auto op = paddle::framework::OpRegistry::CreateOp(op_desc); + // TODO(qiao) add priority back // use all available kernels paddle::framework::UseALL(); op->Run(scope, cuda_place); @@ -380,16 +376,16 @@ TEST(OperatorRegistrar, OpWithMultiKernel) { paddle::framework::UseCPU(); op->Run(scope, cpu_place); - EXPECT_EQ(op_test_value, -9); + EXPECT_EQ(op_test_value, -20); // add cuda kernels paddle::framework::UseCUDA(); op->Run(scope, cuda_place); - EXPECT_EQ(op_test_value, -10); + EXPECT_EQ(op_test_value, -30); // use cudnn kernel paddle::framework::UseCUDNN(); op->Run(scope, cuda_place); - EXPECT_EQ(op_test_value, -20); + EXPECT_EQ(op_test_value, -40); } diff --git a/paddle/framework/operator.cc b/paddle/framework/operator.cc index 4ef0c2523c..adc85b1049 100644 --- a/paddle/framework/operator.cc +++ b/paddle/framework/operator.cc @@ -14,11 +14,10 @@ limitations under the License. */ #include #include -#include #include "paddle/framework/data_transform.h" +#include "paddle/framework/device_data_transform.h" #include "paddle/framework/executor.h" -#include "paddle/framework/lod_tensor_array.h" #include "paddle/framework/operator.h" #include "paddle/framework/shape_inference.h" #include "paddle/framework/var_type.h" @@ -243,6 +242,10 @@ void OperatorBase::GenerateTemporaryNames() { } } +static bool VarIsTensor(const Variable* var) { + return var->IsType() || var->IsType(); +} + static const Tensor* GetTensorFromVar(const Variable* var) { const Tensor* t = nullptr; if (var->IsType()) { @@ -453,30 +456,6 @@ class RuntimeInferShapeContext : public InferShapeContext { const Scope& scope_; }; -const platform::DeviceContext* GetDeviceContext( - framework::KernelTypePair& kernel_pair) { - auto& actual_kernel_key = kernel_pair.first; - auto& expected_kernel_key = kernel_pair.second; - platform::DeviceContextPool& pool = platform::DeviceContextPool::Instance(); - - if (platform::is_gpu_place(actual_kernel_key.place_) && - platform::is_cpu_place(expected_kernel_key.place_)) { - return pool.Get(actual_kernel_key.place_); - } else if (platform::is_cpu_place(actual_kernel_key.place_) && - platform::is_gpu_place(expected_kernel_key.place_)) { - return pool.Get(expected_kernel_key.place_); - } else { - PADDLE_THROW( - "Currently, model parallelism is only supported between CPU and CUDA"); - } -} - -const platform::DeviceContext* GetDeviceContext( - const framework::OpKernelType& kernel) { - platform::DeviceContextPool& pool = platform::DeviceContextPool::Instance(); - return pool.Get(kernel.place_); -} - void OperatorWithKernel::Run(const Scope& scope, const platform::Place& place) const { RuntimeInferShapeContext infer_shape_ctx(*this, scope); @@ -492,94 +471,43 @@ void OperatorWithKernel::Run(const Scope& scope, "There are no kernels which are registered in the %s operator.", type_); } - // check if op[type] have kernel for kernel_key - OpKernelMap& kernels = kernels_iter->second; - ExecutionContext ctx(*this, scope, *dev_ctx); - auto actual_kernel_key = GetActualKernelType(ctx); - - auto expected_kernel_key = GetExpectedKernelType(actual_kernel_key); - - if (actual_kernel_key == expected_kernel_key) { - PADDLE_ENFORCE_EQ(actual_kernel_key.place_, expected_kernel_key.place_, - "Currently, model parallelism is only supported between " - "CPU and other devices. For example, multi-GPU model " - "parallelism will failed."); - } else { - // find the best key candidate - const DataTransformFnMap& trans_map = DataTransformFnMap::Instance(); - for (auto& candidate : kKernelPriority) { - auto candidate_key = - OpKernelType(actual_kernel_key.data_type_, std::get<0>(candidate), - actual_kernel_key.data_layout_, std::get<1>(candidate)); - - auto candidate_pair = std::make_pair(actual_kernel_key, candidate_key); - if ((actual_kernel_key == candidate_key) || - (kernels.count(candidate_key) && - trans_map.GetNullable(candidate_pair))) { - expected_kernel_key = candidate_key; - break; - } - } - - auto kernel_pair = std::make_pair(actual_kernel_key, expected_kernel_key); - const DataTransformFn* trans_fun = trans_map.GetNullable(kernel_pair); - if (trans_fun) { - auto input_vars = this->InputVars(); - // TODO(qijun) filter the input vars that do not need to be transformed - - // filter vars that has been transformed - std::vector need_trans; - for (auto var_name : input_vars) { - auto var_name_trans = - var_name + framework::KernelTypeToString(expected_kernel_key); - if (!scope.FindVar(var_name_trans)) { - const_cast(scope).Var(var_name_trans); - need_trans.push_back(var_name); - } - } - - if (!need_trans.empty()) { - auto trans_dev_ctx = GetDeviceContext(kernel_pair); - - // Wait for transform starting - dev_ctx->Wait(); - - for (auto var_name : need_trans) { - (*trans_fun)(trans_dev_ctx, kernel_pair, *(scope.FindVar(var_name)), - scope.FindVar(var_name + framework::KernelTypeToString( - expected_kernel_key))); + auto expected_kernel_key = this->GetExpectedKernelType(ctx); + + Scope& new_scope = scope.NewScope(); + + for (auto& var_name_item : this->Inputs()) { + for (auto& var_name : var_name_item.second) { + auto* var = scope.FindVar(var_name); + if (var && VarIsTensor(var)) { + auto* tensor_in = GetTensorFromVar(var); + if (tensor_in->IsInitialized()) { + auto kernel_type_for_var = this->GetKernelTypeForVar( + var_name_item.first, *tensor_in, expected_kernel_key); + if (kernel_type_for_var != expected_kernel_key) { + auto out_var_names = OutputVars(true); + if (std::find(out_var_names.begin(), out_var_names.end(), + var_name) != out_var_names.end()) { + PADDLE_THROW( + "var %s is both input and output, " + "does not support transform", + var_name); + } + VLOG(3) << "need to do transform for var " << var_name; + auto* trans_var = new_scope.Var(var_name); + auto* out = DataTransform(expected_kernel_key, kernel_type_for_var, + *tensor_in); + CopyVariableWithTensor(*var, *out, *trans_var); + } } - // Wait for data transform finishing - trans_dev_ctx->Wait(); } } } - VLOG(10) << "Actual kernel: " << actual_kernel_key - << "Expected kernel: " << expected_kernel_key; - + OpKernelMap& kernels = kernels_iter->second; auto kernel_iter = kernels.find(expected_kernel_key); - if (kernel_iter == kernels.end()) { - PADDLE_THROW("The operator %s does not support %s", type_, - expected_kernel_key); - } - - auto* expected_dev_ctx = GetDeviceContext(expected_kernel_key); - ExecutionContext expected_ctx(*this, scope, *expected_dev_ctx); - - kernel_iter->second->Compute(expected_ctx); -} - -OpKernelType OperatorWithKernel::GetActualKernelType( - const ExecutionContext& ctx) const { - return OpKernelType(IndicateDataType(ctx), ctx.GetPlace()); -} - -OpKernelType OperatorWithKernel::GetExpectedKernelType( - const OpKernelType& actual_kernel_type) const { - return actual_kernel_type; + kernel_iter->second->Compute(ExecutionContext(*this, new_scope, *dev_ctx)); } proto::DataType OperatorWithKernel::IndicateDataType( @@ -611,5 +539,16 @@ proto::DataType OperatorWithKernel::IndicateDataType( return static_cast(data_type); } +OpKernelType OperatorWithKernel::GetExpectedKernelType( + const ExecutionContext& ctx) const { + return OpKernelType(IndicateDataType(ctx), ctx.GetPlace()); +} + +OpKernelType OperatorWithKernel::GetKernelTypeForVar( + const std::string& var_name, const Tensor& tensor, + const OpKernelType& expected_kernel_type) const { + return OpKernelType(expected_kernel_type.data_type_, tensor.place()); +} + } // namespace framework } // namespace paddle diff --git a/paddle/framework/operator.h b/paddle/framework/operator.h index 800397c077..d5feb59864 100644 --- a/paddle/framework/operator.h +++ b/paddle/framework/operator.h @@ -408,9 +408,10 @@ class OperatorWithKernel : public OperatorBase { } protected: - virtual OpKernelType GetActualKernelType(const ExecutionContext& ctx) const; - virtual OpKernelType GetExpectedKernelType( - const OpKernelType& actual_kernel_type) const; + virtual OpKernelType GetExpectedKernelType(const ExecutionContext& ctx) const; + virtual OpKernelType GetKernelTypeForVar( + const std::string& var_name, const Tensor& tensor, + const OpKernelType& expected_kernel_type) const; private: // indicate kernel DataType by input data. Defaultly all input data must be diff --git a/paddle/framework/operator_test.cc b/paddle/framework/operator_test.cc index 4d38a7ada9..d002f3f238 100644 --- a/paddle/framework/operator_test.cc +++ b/paddle/framework/operator_test.cc @@ -114,7 +114,8 @@ class OpWithKernelTest : public OperatorWithKernel { protected: void InferShape(framework::InferShapeContext* ctx) const override {} - OpKernelType GetActualKernelType(const ExecutionContext& ctx) const override { + OpKernelType GetExpectedKernelType( + const ExecutionContext& ctx) const override { return OpKernelType(proto::DataType::FP32, ctx.GetPlace()); } }; diff --git a/paddle/framework/scope.cc b/paddle/framework/scope.cc index 4e80e3d974..2bd0ac8f5a 100644 --- a/paddle/framework/scope.cc +++ b/paddle/framework/scope.cc @@ -109,6 +109,7 @@ std::string Scope::Rename(const std::string& origin_name) const { Rename(origin_name, var_name); return var_name; } + Variable* Scope::FindVarLocally(const std::string& name) const { auto it = vars_.find(name); if (it != vars_.end()) return it->second; diff --git a/paddle/framework/scope.h b/paddle/framework/scope.h index 10143326df..a1da81cc79 100644 --- a/paddle/framework/scope.h +++ b/paddle/framework/scope.h @@ -75,9 +75,9 @@ class Scope { // Rename variable to a new name and return the new name std::string Rename(const std::string& origin_name) const; - private: Variable* FindVarLocally(const std::string& name) const; + private: // Call Scope::NewScope for a sub-scope. explicit Scope(Scope const* parent) : parent_(parent) {} diff --git a/paddle/framework/tensor.h b/paddle/framework/tensor.h index 02b125cbbe..4aaa29d794 100644 --- a/paddle/framework/tensor.h +++ b/paddle/framework/tensor.h @@ -55,6 +55,8 @@ class Tensor { template inline const T* data() const; + inline bool IsInitialized() const; + inline void switch_place(platform::Place new_place); /** diff --git a/paddle/framework/tensor_impl.h b/paddle/framework/tensor_impl.h index 6c6f298edc..1340c5e485 100644 --- a/paddle/framework/tensor_impl.h +++ b/paddle/framework/tensor_impl.h @@ -84,6 +84,8 @@ inline const T* Tensor::data() const { reinterpret_cast(holder_->ptr()) + offset_); } +inline bool Tensor::IsInitialized() const { return holder_ != nullptr; } + template inline T* Tensor::data() { check_memory_size(); diff --git a/paddle/framework/variable.h b/paddle/framework/variable.h index e5a94759f9..36b76fb196 100644 --- a/paddle/framework/variable.h +++ b/paddle/framework/variable.h @@ -32,6 +32,8 @@ class Variable { return *static_cast(holder_->Ptr()); } + bool IsInitialized() const { return holder_ != nullptr; } + template T* GetMutable() { if (!IsType()) { diff --git a/paddle/operators/accuracy_op.cc b/paddle/operators/accuracy_op.cc index d7baa6e905..8e8a3c7dd3 100644 --- a/paddle/operators/accuracy_op.cc +++ b/paddle/operators/accuracy_op.cc @@ -53,7 +53,7 @@ class AccuracyOp : public framework::OperatorWithKernel { } protected: - framework::OpKernelType GetActualKernelType( + framework::OpKernelType GetExpectedKernelType( const framework::ExecutionContext &ctx) const override { return framework::OpKernelType( framework::ToDataType(ctx.Input("Out")->type()), diff --git a/paddle/operators/auc_op.cc b/paddle/operators/auc_op.cc index c16bc11931..b6494f9509 100644 --- a/paddle/operators/auc_op.cc +++ b/paddle/operators/auc_op.cc @@ -39,7 +39,7 @@ class AucOp : public framework::OperatorWithKernel { } protected: - framework::OpKernelType GetActualKernelType( + framework::OpKernelType GetExpectedKernelType( const framework::ExecutionContext &ctx) const override { return framework::OpKernelType( framework::ToDataType(ctx.Input("Out")->type()), diff --git a/paddle/operators/batch_norm_op.cc b/paddle/operators/batch_norm_op.cc index dd7b038b00..0e984c38ba 100644 --- a/paddle/operators/batch_norm_op.cc +++ b/paddle/operators/batch_norm_op.cc @@ -306,7 +306,7 @@ class BatchNormGradOp : public framework::OperatorWithKernel { } protected: - framework::OpKernelType GetActualKernelType( + framework::OpKernelType GetExpectedKernelType( const framework::ExecutionContext &ctx) const override { const auto *var = ctx.InputVar(framework::GradVarName("Y")); if (var == nullptr) { diff --git a/paddle/operators/chunk_eval_op.cc b/paddle/operators/chunk_eval_op.cc index a040404266..44f667aead 100644 --- a/paddle/operators/chunk_eval_op.cc +++ b/paddle/operators/chunk_eval_op.cc @@ -55,10 +55,10 @@ class ChunkEvalOp : public framework::OperatorWithKernel { } protected: - framework::OpKernelType GetActualKernelType( + framework::OpKernelType GetExpectedKernelType( const framework::ExecutionContext &ctx) const override { return framework::OpKernelType(framework::proto::DataType::FP32, - ctx.device_context()); + platform::CPUPlace()); } }; diff --git a/paddle/operators/chunk_eval_op.h b/paddle/operators/chunk_eval_op.h index 74ab435c86..300aff90c0 100644 --- a/paddle/operators/chunk_eval_op.h +++ b/paddle/operators/chunk_eval_op.h @@ -145,6 +145,7 @@ class ChunkEvalKernel : public framework::OpKernel { context.Attr>("excluded_chunk_types").end()); auto* inference = context.Input("Inference"); + auto place = inference->place(); auto* label = context.Input("Label"); auto* precision = context.Output("Precision"); auto* recall = context.Output("Recall"); @@ -155,15 +156,15 @@ class ChunkEvalKernel : public framework::OpKernel { const int64_t* inference_data = inference->data(); const int64_t* label_data = label->data(); - T* precision_data = precision->mutable_data(context.GetPlace()); - T* racall_data = recall->mutable_data(context.GetPlace()); - T* f1_data = f1->mutable_data(context.GetPlace()); + T* precision_data = precision->mutable_data(place); + T* racall_data = recall->mutable_data(place); + T* f1_data = f1->mutable_data(place); int64_t* num_infer_chunks_data = - num_infer_chunks->mutable_data(context.GetPlace()); + num_infer_chunks->mutable_data(place); int64_t* num_label_chunks_data = - num_label_chunks->mutable_data(context.GetPlace()); + num_label_chunks->mutable_data(place); int64_t* num_correct_chunks_data = - num_correct_chunks->mutable_data(context.GetPlace()); + num_correct_chunks->mutable_data(place); *num_infer_chunks_data = 0; *num_label_chunks_data = 0; *num_correct_chunks_data = 0; diff --git a/paddle/operators/compare_op.cc b/paddle/operators/compare_op.cc index 44665b7872..daa2c193b4 100644 --- a/paddle/operators/compare_op.cc +++ b/paddle/operators/compare_op.cc @@ -66,9 +66,9 @@ class CompareOp : public framework::OperatorWithKernel { using framework::OperatorWithKernel::OperatorWithKernel; protected: - framework::OpKernelType GetActualKernelType( + framework::OpKernelType GetExpectedKernelType( const framework::ExecutionContext &ctx) const override { - framework::OpKernelType kt = OperatorWithKernel::GetActualKernelType(ctx); + framework::OpKernelType kt = OperatorWithKernel::GetExpectedKernelType(ctx); // CompareOp kernel's device type is decided by input tensor place kt.place_ = ctx.Input("X")->place(); return kt; diff --git a/paddle/operators/conv_op.h b/paddle/operators/conv_op.h index fe3c0bc930..83786e2329 100644 --- a/paddle/operators/conv_op.h +++ b/paddle/operators/conv_op.h @@ -62,25 +62,12 @@ class ConvOp : public framework::OperatorWithKernel { public: using framework::OperatorWithKernel::OperatorWithKernel; void InferShape(framework::InferShapeContext* ctx) const override; - framework::OpKernelType GetExpectedKernelType( - const framework::OpKernelType& kernel) const override { - return framework::OpKernelType(kernel.data_type_, platform::CUDAPlace(0), - kernel.data_layout_, - framework::LibraryType::kCUDNN); - } }; class ConvOpGrad : public framework::OperatorWithKernel { public: using framework::OperatorWithKernel::OperatorWithKernel; void InferShape(framework::InferShapeContext* ctx) const override; - - framework::OpKernelType GetExpectedKernelType( - const framework::OpKernelType& kernel) const override { - return framework::OpKernelType(kernel.data_type_, platform::CUDAPlace(0), - kernel.data_layout_, - framework::LibraryType::kCUDNN); - } }; template diff --git a/paddle/operators/crf_decoding_op.cc b/paddle/operators/crf_decoding_op.cc index 024e1d061a..30626028c1 100644 --- a/paddle/operators/crf_decoding_op.cc +++ b/paddle/operators/crf_decoding_op.cc @@ -120,17 +120,11 @@ class CRFDecodingOp : public framework::OperatorWithKernel { } protected: - framework::OpKernelType GetActualKernelType( + framework::OpKernelType GetExpectedKernelType( const framework::ExecutionContext& ctx) const override { return framework::OpKernelType( framework::ToDataType(ctx.Input("Emission")->type()), - ctx.device_context()); - } - - framework::OpKernelType GetExpectedKernelType( - const framework::OpKernelType& actual_kernel_type) const override { - return framework::OpKernelType(actual_kernel_type.data_type_, - platform::CPUPlace()); + platform::CPUPlace()); } }; } // namespace operators diff --git a/paddle/operators/crf_decoding_op.h b/paddle/operators/crf_decoding_op.h index f6827b7b11..ce2f4e6622 100644 --- a/paddle/operators/crf_decoding_op.h +++ b/paddle/operators/crf_decoding_op.h @@ -28,9 +28,6 @@ template class CRFDecodingOpKernel : public framework::OpKernel { public: void Compute(const framework::ExecutionContext& ctx) const override { - PADDLE_ENFORCE(platform::is_cpu_place(ctx.GetPlace()), - "The crf_decoding operator can only run on CPU."); - auto* emission_weights = ctx.Input("Emission"); auto* transition_weights = ctx.Input("Transition"); auto* label = ctx.Input("Label"); diff --git a/paddle/operators/cross_entropy_op.cc b/paddle/operators/cross_entropy_op.cc index fe39cb481a..7abd5b1c61 100644 --- a/paddle/operators/cross_entropy_op.cc +++ b/paddle/operators/cross_entropy_op.cc @@ -51,7 +51,7 @@ class CrossEntropyOp : public framework::OperatorWithKernel { protected: // Explicitly set that the data type of computation kernel of cross_entropy // is determined by its input "X". - framework::OpKernelType GetActualKernelType( + framework::OpKernelType GetExpectedKernelType( const framework::ExecutionContext& ctx) const override { return framework::OpKernelType( framework::ToDataType(ctx.Input("X")->type()), @@ -101,7 +101,7 @@ class CrossEntropyGradientOp : public framework::OperatorWithKernel { protected: // Explicitly set that the data type of computation kernel of cross_entropy // is determined by its input "X". - framework::OpKernelType GetActualKernelType( + framework::OpKernelType GetExpectedKernelType( const framework::ExecutionContext& ctx) const override { return framework::OpKernelType( framework::ToDataType(ctx.Input("X")->type()), diff --git a/paddle/operators/fill_constant_batch_size_like_op.cc b/paddle/operators/fill_constant_batch_size_like_op.cc index 852ecdfe45..c74a5b6ced 100644 --- a/paddle/operators/fill_constant_batch_size_like_op.cc +++ b/paddle/operators/fill_constant_batch_size_like_op.cc @@ -49,7 +49,7 @@ class FillConstantBatchSizeLikeOp : public framework::OperatorWithKernel { } protected: - framework::OpKernelType GetActualKernelType( + framework::OpKernelType GetExpectedKernelType( const framework::ExecutionContext &ctx) const override { return framework::OpKernelType( static_cast(ctx.Attr("dtype")), diff --git a/paddle/operators/gather_op.cc b/paddle/operators/gather_op.cc index 45e9d8df70..597fdad079 100644 --- a/paddle/operators/gather_op.cc +++ b/paddle/operators/gather_op.cc @@ -40,7 +40,7 @@ class GatherOp : public framework::OperatorWithKernel { } protected: - framework::OpKernelType GetActualKernelType( + framework::OpKernelType GetExpectedKernelType( const framework::ExecutionContext& ctx) const override { return framework::OpKernelType( framework::ToDataType(ctx.Input("X")->type()), @@ -57,7 +57,7 @@ class GatherGradOp : public framework::OperatorWithKernel { } protected: - framework::OpKernelType GetActualKernelType( + framework::OpKernelType GetExpectedKernelType( const framework::ExecutionContext& ctx) const override { return framework::OpKernelType( framework::ToDataType(ctx.Input("X")->type()), diff --git a/paddle/operators/gaussian_random_op.cc b/paddle/operators/gaussian_random_op.cc index 9ed493a7d0..2dca05760e 100644 --- a/paddle/operators/gaussian_random_op.cc +++ b/paddle/operators/gaussian_random_op.cc @@ -60,7 +60,7 @@ class GaussianRandomOp : public framework::OperatorWithKernel { } protected: - framework::OpKernelType GetActualKernelType( + framework::OpKernelType GetExpectedKernelType( const framework::ExecutionContext& ctx) const override { return framework::OpKernelType( static_cast(ctx.Attr("dtype")), diff --git a/paddle/operators/linear_chain_crf_op.cc b/paddle/operators/linear_chain_crf_op.cc index 666207ea07..975e394c78 100644 --- a/paddle/operators/linear_chain_crf_op.cc +++ b/paddle/operators/linear_chain_crf_op.cc @@ -183,7 +183,7 @@ class LinearChainCRFOp : public framework::OperatorWithKernel { protected: // Explicitly set that the data type of computation kernel of linear_chain_crf // is determined by its input "Emission". - framework::OpKernelType GetActualKernelType( + framework::OpKernelType GetExpectedKernelType( const framework::ExecutionContext& ctx) const override { return framework::OpKernelType( framework::ToDataType(ctx.Input("Emission")->type()), @@ -242,7 +242,7 @@ class LinearChainCRFGradOp : public framework::OperatorWithKernel { protected: // Explicitly set that the data type of output of the linear_chain_crf_grad // operator is determined by its input: gradients of LogLikelihood. - framework::OpKernelType GetActualKernelType( + framework::OpKernelType GetExpectedKernelType( const framework::ExecutionContext& ctx) const override { return framework::OpKernelType( framework::ToDataType( diff --git a/paddle/operators/lod_reset_op.cc b/paddle/operators/lod_reset_op.cc index f3c0badf2a..3d7b15edcf 100644 --- a/paddle/operators/lod_reset_op.cc +++ b/paddle/operators/lod_reset_op.cc @@ -38,7 +38,7 @@ class LoDResetOp : public framework::OperatorWithKernel { } protected: - framework::OpKernelType GetActualKernelType( + framework::OpKernelType GetExpectedKernelType( const framework::ExecutionContext &ctx) const override { return framework::OpKernelType( framework::ToDataType(ctx.Input("X")->type()), @@ -97,7 +97,7 @@ class LoDResetGradOp : public framework::OperatorWithKernel { } protected: - framework::OpKernelType GetActualKernelType( + framework::OpKernelType GetExpectedKernelType( const framework::ExecutionContext &ctx) const override { return framework::OpKernelType( framework::ToDataType(ctx.Input("X")->type()), diff --git a/paddle/operators/logical_op.cc b/paddle/operators/logical_op.cc index 7417192479..fedd325cf4 100644 --- a/paddle/operators/logical_op.cc +++ b/paddle/operators/logical_op.cc @@ -99,9 +99,9 @@ class LogicalOp : public framework::OperatorWithKernel { using framework::OperatorWithKernel::OperatorWithKernel; protected: - framework::OpKernelType GetActualKernelType( + framework::OpKernelType GetExpectedKernelType( const framework::ExecutionContext &ctx) const override { - framework::OpKernelType kt = OperatorWithKernel::GetActualKernelType(ctx); + framework::OpKernelType kt = OperatorWithKernel::GetExpectedKernelType(ctx); // LogicalOp kernel's device type is decided by input tensor place kt.place_ = ctx.Input("X")->place(); return kt; diff --git a/paddle/operators/lookup_table_op.cc b/paddle/operators/lookup_table_op.cc index 6e5cbd6f8c..bb03def439 100644 --- a/paddle/operators/lookup_table_op.cc +++ b/paddle/operators/lookup_table_op.cc @@ -41,7 +41,7 @@ class LookupTableOp : public framework::OperatorWithKernel { } protected: - framework::OpKernelType GetActualKernelType( + framework::OpKernelType GetExpectedKernelType( const framework::ExecutionContext& ctx) const override { return framework::OpKernelType( framework::ToDataType(ctx.Input("W")->type()), @@ -98,7 +98,7 @@ class LookupTableOpGrad : public framework::OperatorWithKernel { } protected: - framework::OpKernelType GetActualKernelType( + framework::OpKernelType GetExpectedKernelType( const framework::ExecutionContext& ctx) const override { return framework::OpKernelType( framework::ToDataType(ctx.Input("W")->type()), diff --git a/paddle/operators/lstm_op.cc b/paddle/operators/lstm_op.cc index b8fcec0f29..3b90b64b4e 100644 --- a/paddle/operators/lstm_op.cc +++ b/paddle/operators/lstm_op.cc @@ -92,7 +92,7 @@ class LSTMOp : public framework::OperatorWithKernel { } protected: - framework::OpKernelType GetActualKernelType( + framework::OpKernelType GetExpectedKernelType( const framework::ExecutionContext& ctx) const override { return framework::OpKernelType( framework::ToDataType(ctx.Input("Input")->type()), @@ -260,7 +260,7 @@ class LSTMGradOp : public framework::OperatorWithKernel { } protected: - framework::OpKernelType GetActualKernelType( + framework::OpKernelType GetExpectedKernelType( const framework::ExecutionContext& ctx) const override { return framework::OpKernelType( framework::ToDataType(ctx.Input("Input")->type()), diff --git a/paddle/operators/multiplex_op.cc b/paddle/operators/multiplex_op.cc index 11e047b5d5..78263da2fb 100644 --- a/paddle/operators/multiplex_op.cc +++ b/paddle/operators/multiplex_op.cc @@ -51,7 +51,7 @@ class MultiplexOp : public framework::OperatorWithKernel { } protected: - framework::OpKernelType GetActualKernelType( + framework::OpKernelType GetExpectedKernelType( const framework::ExecutionContext& ctx) const override { return framework::OpKernelType( framework::ToDataType(ctx.MultiInput("X")[0]->type()), @@ -102,7 +102,7 @@ class MultiplexGradOp : public framework::OperatorWithKernel { } protected: - framework::OpKernelType GetActualKernelType( + framework::OpKernelType GetExpectedKernelType( const framework::ExecutionContext& ctx) const override { return framework::OpKernelType( framework::ToDataType(ctx.MultiInput("X")[0]->type()), diff --git a/paddle/operators/nce_op.cc b/paddle/operators/nce_op.cc index d39ca87d53..84ba3ead2b 100644 --- a/paddle/operators/nce_op.cc +++ b/paddle/operators/nce_op.cc @@ -63,7 +63,7 @@ class NCEOp : public framework::OperatorWithKernel { } protected: - framework::OpKernelType GetActualKernelType( + framework::OpKernelType GetExpectedKernelType( const framework::ExecutionContext& ctx) const override { return framework::OpKernelType( framework::ToDataType(ctx.Input("Input")->type()), @@ -166,7 +166,7 @@ class NCEOpGrad : public framework::OperatorWithKernel { } protected: - framework::OpKernelType GetActualKernelType( + framework::OpKernelType GetExpectedKernelType( const framework::ExecutionContext& ctx) const override { return framework::OpKernelType( framework::ToDataType(ctx.Input("Input")->type()), diff --git a/paddle/operators/pool_with_index_op.cc b/paddle/operators/pool_with_index_op.cc index 76c5123527..1d31d813af 100644 --- a/paddle/operators/pool_with_index_op.cc +++ b/paddle/operators/pool_with_index_op.cc @@ -69,7 +69,7 @@ class MaxPoolWithIndexOp : public framework::OperatorWithKernel { } protected: - framework::OpKernelType GetActualKernelType( + framework::OpKernelType GetExpectedKernelType( const framework::ExecutionContext &ctx) const override { return framework::OpKernelType( framework::ToDataType(ctx.Input("X")->type()), @@ -90,7 +90,7 @@ class MaxPoolWithIndexOpGrad : public framework::OperatorWithKernel { } protected: - framework::OpKernelType GetActualKernelType( + framework::OpKernelType GetExpectedKernelType( const framework::ExecutionContext &ctx) const override { return framework::OpKernelType( framework::ToDataType(ctx.Input("X")->type()), diff --git a/paddle/operators/positive_negative_pair_op.cc b/paddle/operators/positive_negative_pair_op.cc index a6b23c995b..5aa5167dbb 100644 --- a/paddle/operators/positive_negative_pair_op.cc +++ b/paddle/operators/positive_negative_pair_op.cc @@ -85,7 +85,7 @@ class PositiveNegativePairOp : public framework::OperatorWithKernel { } protected: - framework::OpKernelType GetActualKernelType( + framework::OpKernelType GetExpectedKernelType( const framework::ExecutionContext &ctx) const override { return framework::OpKernelType( framework::ToDataType(ctx.Input("Score")->type()), diff --git a/paddle/operators/precision_recall_op.cc b/paddle/operators/precision_recall_op.cc index c5753147ef..f1598d53ca 100644 --- a/paddle/operators/precision_recall_op.cc +++ b/paddle/operators/precision_recall_op.cc @@ -80,7 +80,7 @@ class PrecisionRecallOp : public framework::OperatorWithKernel { } protected: - framework::OpKernelType GetActualKernelType( + framework::OpKernelType GetExpectedKernelType( const framework::ExecutionContext &ctx) const override { return framework::OpKernelType( framework::ToDataType(ctx.Input("MaxProbs")->type()), diff --git a/paddle/operators/roi_pool_op.cc b/paddle/operators/roi_pool_op.cc index ef1804d976..a7351f11c5 100644 --- a/paddle/operators/roi_pool_op.cc +++ b/paddle/operators/roi_pool_op.cc @@ -68,7 +68,7 @@ class ROIPoolOp : public framework::OperatorWithKernel { } protected: - framework::OpKernelType GetActualKernelType( + framework::OpKernelType GetExpectedKernelType( const framework::ExecutionContext& ctx) const override { return framework::OpKernelType( framework::ToDataType(ctx.Input("X")->type()), @@ -89,7 +89,7 @@ class ROIPoolGradOp : public framework::OperatorWithKernel { } protected: - framework::OpKernelType GetActualKernelType( + framework::OpKernelType GetExpectedKernelType( const framework::ExecutionContext& ctx) const override { return framework::OpKernelType( framework::ToDataType(ctx.Input("X")->type()), diff --git a/paddle/operators/scatter_op.cc b/paddle/operators/scatter_op.cc index 806dccc6ca..b653348906 100644 --- a/paddle/operators/scatter_op.cc +++ b/paddle/operators/scatter_op.cc @@ -49,7 +49,7 @@ class ScatterOp : public framework::OperatorWithKernel { } protected: - framework::OpKernelType GetActualKernelType( + framework::OpKernelType GetExpectedKernelType( const framework::ExecutionContext& ctx) const override { return framework::OpKernelType( framework::ToDataType(ctx.Input("Ref")->type()), @@ -68,7 +68,7 @@ class ScatterGradOp : public framework::OperatorWithKernel { } protected: - framework::OpKernelType GetActualKernelType( + framework::OpKernelType GetExpectedKernelType( const framework::ExecutionContext& ctx) const override { return framework::OpKernelType( framework::ToDataType(ctx.Input("Ref")->type()), diff --git a/paddle/operators/sequence_pool_op.cc b/paddle/operators/sequence_pool_op.cc index aea98744d8..34e1a12591 100644 --- a/paddle/operators/sequence_pool_op.cc +++ b/paddle/operators/sequence_pool_op.cc @@ -107,7 +107,7 @@ class SequencePoolGradOp : public framework::OperatorWithKernel { } protected: - framework::OpKernelType GetActualKernelType( + framework::OpKernelType GetExpectedKernelType( const framework::ExecutionContext& ctx) const override { return framework::OpKernelType( framework::ToDataType(ctx.Input("X")->type()), diff --git a/paddle/operators/sequence_slice_op.cc b/paddle/operators/sequence_slice_op.cc index 98bd885490..f79106ff0f 100644 --- a/paddle/operators/sequence_slice_op.cc +++ b/paddle/operators/sequence_slice_op.cc @@ -48,7 +48,7 @@ class SequenceSliceOp : public framework::OperatorWithKernel { } protected: - framework::OpKernelType GetActualKernelType( + framework::OpKernelType GetExpectedKernelType( const framework::ExecutionContext& ctx) const override { return framework::OpKernelType( framework::ToDataType(ctx.Input("X")->type()), @@ -69,7 +69,7 @@ class SequenceSliceGradOp : public framework::OperatorWithKernel { } protected: - framework::OpKernelType GetActualKernelType( + framework::OpKernelType GetExpectedKernelType( const framework::ExecutionContext& ctx) const override { return framework::OpKernelType( framework::ToDataType(ctx.Input("X")->type()), diff --git a/paddle/operators/softmax_with_cross_entropy_op.cc b/paddle/operators/softmax_with_cross_entropy_op.cc index 41e65b701e..7135780c92 100644 --- a/paddle/operators/softmax_with_cross_entropy_op.cc +++ b/paddle/operators/softmax_with_cross_entropy_op.cc @@ -118,7 +118,7 @@ class SoftmaxWithCrossEntropyOp : public framework::OperatorWithKernel { } protected: - framework::OpKernelType GetActualKernelType( + framework::OpKernelType GetExpectedKernelType( const framework::ExecutionContext& ctx) const override { return framework::OpKernelType( framework::ToDataType(ctx.Input("Logits")->type()), @@ -159,7 +159,7 @@ class SoftmaxWithCrossEntropyOpGrad : public framework::OperatorWithKernel { } protected: - framework::OpKernelType GetActualKernelType( + framework::OpKernelType GetExpectedKernelType( const framework::ExecutionContext& ctx) const override { return framework::OpKernelType( framework::ToDataType( diff --git a/paddle/operators/sum_op.cc b/paddle/operators/sum_op.cc index b86e826642..a4c08430d8 100644 --- a/paddle/operators/sum_op.cc +++ b/paddle/operators/sum_op.cc @@ -53,7 +53,7 @@ class SumOp : public framework::OperatorWithKernel { } protected: - framework::OpKernelType GetActualKernelType( + framework::OpKernelType GetExpectedKernelType( const framework::ExecutionContext& ctx) const override { auto x_vars = ctx.MultiInputVar("X"); if (x_vars[0]->IsType()) { diff --git a/paddle/operators/uniform_random_op.cc b/paddle/operators/uniform_random_op.cc index 4d5dd86cb8..3a314bdb9b 100644 --- a/paddle/operators/uniform_random_op.cc +++ b/paddle/operators/uniform_random_op.cc @@ -63,7 +63,7 @@ class UniformRandomOp : public framework::OperatorWithKernel { } protected: - framework::OpKernelType GetActualKernelType( + framework::OpKernelType GetExpectedKernelType( const framework::ExecutionContext& ctx) const override { return framework::OpKernelType( static_cast(ctx.Attr("dtype")), diff --git a/paddle/operators/unpool_op.cc b/paddle/operators/unpool_op.cc index aeed9679b2..50cee11a7a 100644 --- a/paddle/operators/unpool_op.cc +++ b/paddle/operators/unpool_op.cc @@ -71,7 +71,7 @@ int OutputSize(int input_size, int ksize, int padding, int stride) { class UnpoolOp : public framework::OperatorWithKernel { protected: - framework::OpKernelType GetActualKernelType( + framework::OpKernelType GetExpectedKernelType( const framework::ExecutionContext& ctx) const override { return framework::OpKernelType( framework::ToDataType(ctx.Input("X")->type()), @@ -110,7 +110,7 @@ class UnpoolOp : public framework::OperatorWithKernel { class UnpoolOpGrad : public framework::OperatorWithKernel { protected: - framework::OpKernelType GetActualKernelType( + framework::OpKernelType GetExpectedKernelType( const framework::ExecutionContext& ctx) const override { return framework::OpKernelType( framework::ToDataType(ctx.Input("X")->type()), diff --git a/paddle/platform/place.cc b/paddle/platform/place.cc index 249527e3e1..f05260ccac 100644 --- a/paddle/platform/place.cc +++ b/paddle/platform/place.cc @@ -51,6 +51,18 @@ bool places_are_same_class(const Place &p1, const Place &p2) { return p1.which() == p2.which(); } +bool is_same_place(const Place &p1, const Place &p2) { + if (places_are_same_class(p1, p2)) { + if (is_cpu_place(p1)) { + return true; + } else { + return boost::get(p1) == boost::get(p2); + } + } else { + return false; + } +} + std::ostream &operator<<(std::ostream &os, const Place &p) { detail::PlacePrinter printer(os); boost::apply_visitor(printer, p); diff --git a/paddle/platform/place.h b/paddle/platform/place.h index 76b5c502cc..ba32dd3be6 100644 --- a/paddle/platform/place.h +++ b/paddle/platform/place.h @@ -61,6 +61,7 @@ const CPUPlace default_cpu(); bool is_gpu_place(const Place &); bool is_cpu_place(const Place &); bool places_are_same_class(const Place &, const Place &); +bool is_same_place(const Place &, const Place &); std::ostream &operator<<(std::ostream &, const Place &); diff --git a/python/paddle/v2/fluid/tests/book/test_label_semantic_roles.py b/python/paddle/v2/fluid/tests/book/test_label_semantic_roles.py index 8acd470c5e..74ca56182c 100644 --- a/python/paddle/v2/fluid/tests/book/test_label_semantic_roles.py +++ b/python/paddle/v2/fluid/tests/book/test_label_semantic_roles.py @@ -4,6 +4,7 @@ import numpy as np import paddle.v2 as paddle import paddle.v2.dataset.conll05 as conll05 import paddle.v2.fluid as fluid +import time word_dict, verb_dict, label_dict = conll05.get_dict() word_dict_len = len(word_dict) @@ -160,7 +161,8 @@ def main(): paddle.reader.shuffle( paddle.dataset.conll05.test(), buf_size=8192), batch_size=BATCH_SIZE) - place = fluid.CPUPlace() + #place = fluid.CPUPlace() + place = fluid.CUDAPlace(0) feeder = fluid.DataFeeder( feed_list=[ word, ctx_n2, ctx_n1, ctx_0, ctx_p1, ctx_p2, predicate, mark, target @@ -174,6 +176,7 @@ def main(): embedding_param.set( load_parameter(conll05.get_embedding(), word_dict_len, word_dim), place) + start_time = time.time() batch_id = 0 for pass_id in xrange(PASS_NUM): chunk_evaluator.reset(exe) @@ -191,6 +194,9 @@ def main(): f1_score) + " pass_precision:" + str( pass_precision) + " pass_recall:" + str(pass_recall) + " pass_f1_score:" + str(pass_f1_score)) + if batch_id != 0: + print("second per batch: " + str((time.time() - start_time) + / batch_id)) # exit early for CI exit(0)