From 03a02d695670ad4b989d8e167a494ae7ea6778b9 Mon Sep 17 00:00:00 2001 From: Yi Wang Date: Thu, 7 Dec 2017 15:54:45 -0800 Subject: [PATCH 01/87] Update deisgn doc --- doc/design/concurrent_programming.md | 161 +++++++++++++++++++++++++++ 1 file changed, 161 insertions(+) create mode 100644 doc/design/concurrent_programming.md diff --git a/doc/design/concurrent_programming.md b/doc/design/concurrent_programming.md new file mode 100644 index 0000000000..0fd37bd697 --- /dev/null +++ b/doc/design/concurrent_programming.md @@ -0,0 +1,161 @@ +# Design Doc: Concurrent Programming with Fluid + +With PaddlePaddle Fluid, users describe a program other than a model. The program is a [`ProgramDesc`](https://github.com/PaddlePaddle/Paddle/blob/develop/paddle/framework/framework.proto) protobuf message. TensorFlow/MxNet/Caffe2 applications generate protobuf messages too, but their protobuf messages represent the model, a graph of operators, but not the program that trains/uses the model. + +Many know that when we program TensorFlow, we can specify the device on which each operator runs. This allows us to create a concurrent/parallel AI application. An interesting questions is **how does a `ProgramDesc` represents a concurrent program?** + +The answer relies on the fact that a `ProgramDesc` is similar to an abstract syntax tree (AST) that describes a program. So users just program a concurrent program that they do with any concurrent programming language, e.g., [Go](https://golang.org). + +## An Analogy + +The following table compares concepts in Fluid and Go + +| Go | Fluid | +|----|-------| +|user-defined functions | [layers](https://github.com/PaddlePaddle/Paddle/tree/develop/python/paddle/v2/fluid) | +| control-flow and built-in functions | [intrinsics/operators](https://github.com/PaddlePaddle/Paddle/tree/develop/paddle/operators) | +| goroutines, channels | [class ThreadPool](https://github.com/PaddlePaddle/Paddle/tree/develop/paddle/framework/thread_pool.h) | +| runtime | [class Executor](https://github.com/PaddlePaddle/Paddle/blob/develop/paddle/framework/executor.h) | + +## An Example Concurrent Program + +To review all above concepts in an example, let us take a simple program and writes its distributed version. + +Suppose that we want to parallelize a naive Fluid program (written in Go and calling Fluid's Go binding) that multiplies two tensors. + +```go +import "fluid" + +func paddlepaddle() { + X = fluid.read(...) + W = fluid.Tensor(...) + Y = fluid.mult(X, W) +} +``` + +Please be aware that the Fluid's Go binding provides the default `main` function, which calls the `paddlepaddle` function, which, in this case, is defined in above program and creates the following `ProgramDesc` message. + +```protobuf +message ProgramDesc { + block[0] = Block { + vars = [X, W, Y], + ops = [ + read(output = X) + assign(input = ..., output = W) + mult(input = {X, W}, output = Y) + ], + } +} +``` + +Then, the default `main` function calls `fluid.run()`, which creates an instance of the [`class Executor`](https://github.com/PaddlePaddle/Paddle/blob/develop/paddle/framework/executor.h) and calls `Executor.Run(block[0])`, where `block[0]` is the first and only block defined in above `ProgramDesc` message. + +The default `main` function is defined as follows: + +```go +func main() { + paddlepaddle() + fluid.run() +} +``` + +## The Concurrent Version + +By parallelizing the above program, we could support very big tensor X by splitting into small pieces {x_1, x_2, ...} and sent each piece to worker process/node for parallel multiplication. + +In this case, we can write a transpiler that takes a `ProgramDesc` message that represents the above example program and outputs two `ProgramDesc` messages, one for running on the master process/node, and the other one for worker processes/nodes. + +### The Master Program + +The master program could look like the following: + +```protobuf +message ProgramDesc { + block[0] = Block { + vars = [X, L, Y], + ops = [ + read(output = X) + kube_get_workers_addrs(output = L) + Y = tensor_array(len(L)) + parallel_for(input = X, output = Y, + attrs = {L, block_id(1)}) # referring to block 1 + ] + } + + block[1] = Block { + vars = [x, y, index], + ops = [ + slice(input = [X, index], output = x) # index is initialized by parallel_for + send(input = x, attrs = L[index]) + recv(outputs = y, attrs = L[index]) + assign(input = y, output = Y[index]) + ] + } +} +``` + +The equivalent Fluid program (calling the Go binding) is: + +```go +func main() { //// block 0 + X = fluid.read(...) + L = fluid.k8s.get_worker_addrs() + Y = fluid.tensor_array(len(L)) + fluid.parallel_for(X, L, + func(index int) { //// block 1 + x = X[index] + fluid.send(L[index], x) + y = fluid.recv(L[index]) + Y[index] = y + }) +} +``` + +An explanation of the above program: + +- `fluid.k8s` is a package that provides access to Kubernetes API. +- `fluid.k8s.get_worker_addrs` returns the list of IP and ports of all pods of the current job except for the current one (the master pod). +- `fluid.tensor_array` creates a [tensor array](https://github.com/PaddlePaddle/Paddle/blob/develop/paddle/framework/lod_tensor_array.h). `fluid.parallel_for` creates a `ParallelFor` intrinsic, which, when executed, + + 1. creates `len(L)` scopes, each for the concurrent running of the sub-block (block 1 in this case), and initializes a variable named "index" in the scope to an integer value in the range `[0, len(L)-1]`, and + 2. creates `len(L)` threads by calling into the `ThreadPool` singleton, each thread + 1. creates an Executor instance, and + 2. calls `Executor.Run(block)`, where `block` is block 1 as explained above. + +### The Worker Program + +The worker program looks like + +```go +func main() { + W = Tensor(...) + x = fluid.listen_and_do( + fluid.k8s.self_addr(), + func(input Tensor) { + output = fluid.mult(input, W) + }) +} +``` + +where + +- `fluid.listen_and_do` creates a `ListenAndDo` intrinsic, which, when executed, + 1. listens on the current pod's IP address, as returned by `fliud.k8s.self_addr()`, + 2. once a connection is established, + 1. creates a scope of two parameters, "input" and "output", + 2. reads a [Fluid variable](https://github.com/PaddlePaddle/Paddle/blob/develop/paddle/framework/variable.h) and saves it into "input", + 3. creates an Executor instance and calls `Executor.Run(block)`, where the block is generated by running the lambda specified as the second parameter of `fluid.listen_and_do`. + +## Summarization + +From the above example, we see that: + +1. Fluid enables the imperative programming paradigm by: + 1. letting users describe a program, but not a model (a sequence of layers, or a graph of operators), and + 2. call the `fluid.run` function that runs the program implicitly. +1. The program is described as a `ProgramDesc` protobuf message. +2. Function `Executor.Run` takes a block, instead of a `ProgramDesc`, as its parameter. +3. `fluid.run` calls `Executor.Run` to run the first block in the `ProgramDesc` message. +4. `Executor.Run`'s implementation is extremely simple -- it doesn't plan the execution nor create threads; instead, it runs on the current thread and execute intrinsics/operators' `Run` method sequentially as they appear in the `Block.ops` array. +5. Intrinsics/operators' `Run` method might create threads. For example, the `ListenAndDo` operator creates a thread to handle each incoming request. +6. Threads are not necessarily OS thread; instead, they could be [green threads](https://en.wikipedia.org/wiki/Green_threads) managed by ThreadPool. Multiple green threads might run on the same OS thread. An example green threads is Go's [goroutines](https://tour.golang.org/concurrency/1). From 6a6d433cbed63f83924d5f03810a211a501618d0 Mon Sep 17 00:00:00 2001 From: Yi Wang Date: Thu, 7 Dec 2017 16:36:14 -0800 Subject: [PATCH 02/87] Update block parent-kid relationship --- doc/design/concurrent_programming.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/design/concurrent_programming.md b/doc/design/concurrent_programming.md index 0fd37bd697..afc65e831d 100644 --- a/doc/design/concurrent_programming.md +++ b/doc/design/concurrent_programming.md @@ -83,6 +83,7 @@ message ProgramDesc { } block[1] = Block { + parent = 0, vars = [x, y, index], ops = [ slice(input = [X, index], output = x) # index is initialized by parallel_for @@ -121,6 +122,7 @@ An explanation of the above program: 2. creates `len(L)` threads by calling into the `ThreadPool` singleton, each thread 1. creates an Executor instance, and 2. calls `Executor.Run(block)`, where `block` is block 1 as explained above. +1. Please be aware that block 1 is a sub-block of block 0, so ops in block 1 could refer to variables defined in block 0. ### The Worker Program From 2bdd3e43fa86d614b8235eab55182e15ead98743 Mon Sep 17 00:00:00 2001 From: Liu Yiqun Date: Thu, 7 Dec 2017 09:09:32 +0000 Subject: [PATCH 03/87] Update the version of openblas. --- cmake/external/openblas.cmake | 8 ++------ paddle/math/tests/CMakeLists.txt | 4 +++- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/cmake/external/openblas.cmake b/cmake/external/openblas.cmake index 97857a686b..da25574793 100644 --- a/cmake/external/openblas.cmake +++ b/cmake/external/openblas.cmake @@ -30,23 +30,21 @@ IF(NOT ${CBLAS_FOUND}) CACHE FILEPATH "openblas library." FORCE) SET(OPENBLAS_CC "${CMAKE_C_COMPILER} -Wno-unused-but-set-variable -Wno-unused-variable") + SET(OPENBLAS_COMMIT "v0.2.20") IF(CMAKE_CROSSCOMPILING) SET(OPTIONAL_ARGS HOSTCC=${HOST_C_COMPILER}) GET_FILENAME_COMPONENT(CROSS_SUFFIX ${CMAKE_C_COMPILER} DIRECTORY) SET(CROSS_SUFFIX ${CROSS_SUFFIX}/) IF(ANDROID) - # arm_soft_fp_abi branch of OpenBLAS to support softfp - # https://github.com/xianyi/OpenBLAS/tree/arm_soft_fp_abi - SET(OPENBLAS_COMMIT "b5c96fcfcdc82945502a2303116a64d89985daf5") IF(ANDROID_ABI MATCHES "^armeabi(-v7a)?$") + # use softfp SET(OPTIONAL_ARGS ${OPTIONAL_ARGS} TARGET=ARMV7 ARM_SOFTFP_ABI=1 USE_THREAD=0) ELSEIF(ANDROID_ABI STREQUAL "arm64-v8a") SET(OPTIONAL_ARGS ${OPTIONAL_ARGS} TARGET=ARMV8 BINARY=64 USE_THREAD=0) ENDIF() ELSEIF(IOS) IF(CMAKE_OSX_ARCHITECTURES MATCHES "arm64") - SET(OPENBLAS_COMMIT "b5c96fcfcdc82945502a2303116a64d89985daf5") SET(OPENBLAS_CC "${OPENBLAS_CC} ${CMAKE_C_FLAGS} -isysroot ${CMAKE_OSX_SYSROOT}") SET(OPENBLAS_CC "${OPENBLAS_CC} -arch arm64") SET(OPTIONAL_ARGS ${OPTIONAL_ARGS} TARGET=ARMV8 BINARY=64 USE_THREAD=0 CROSS_SUFFIX=${CROSS_SUFFIX}) @@ -56,14 +54,12 @@ IF(NOT ${CBLAS_FOUND}) ENDIF() ELSEIF(RPI) # use hardfp - SET(OPENBLAS_COMMIT "v0.2.20") SET(OPTIONAL_ARGS ${OPTIONAL_ARGS} TARGET=ARMV7 USE_THREAD=0) ENDIF() ELSE() IF(APPLE) SET(OPENBLAS_CC "${CMAKE_C_COMPILER} -isysroot ${CMAKE_OSX_SYSROOT}") ENDIF() - SET(OPENBLAS_COMMIT "v0.2.20") SET(OPTIONAL_ARGS "") IF(CMAKE_SYSTEM_PROCESSOR MATCHES "^x86(_64)?$") SET(OPTIONAL_ARGS DYNAMIC_ARCH=1 NUM_THREADS=64) diff --git a/paddle/math/tests/CMakeLists.txt b/paddle/math/tests/CMakeLists.txt index 215bac1271..41c1bdf785 100644 --- a/paddle/math/tests/CMakeLists.txt +++ b/paddle/math/tests/CMakeLists.txt @@ -34,4 +34,6 @@ add_simple_unittest(test_FPException) add_simple_unittest(test_GpuProfiler) add_simple_unittest(test_BaseMatrix) add_simple_unittest(test_Matrix) -cc_test(test_float16 SRCS test_float16.cpp) +if(WITH_ARM_FP16) + cc_test(test_float16 SRCS test_float16.cpp) +endif() From 9d2c77e6262eae63a150425ab24402660c091bfb Mon Sep 17 00:00:00 2001 From: Yang Yang Date: Tue, 19 Dec 2017 05:12:53 +0000 Subject: [PATCH 04/87] 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 05/87] 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 06/87] 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 07/87] 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 08/87] 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 d13d787bcdc324ec5d31a1043742ea31061c00dc Mon Sep 17 00:00:00 2001 From: Liu Yiqun Date: Thu, 21 Dec 2017 15:24:42 +0800 Subject: [PATCH 09/87] Refine the cross-compiling documentations. --- doc/mobile/cross_compiling_for_android_cn.md | 27 +++++++++++++------- doc/mobile/cross_compiling_for_android_en.md | 20 ++++++++++----- doc/mobile/cross_compiling_for_ios_en.md | 6 ++--- 3 files changed, 35 insertions(+), 18 deletions(-) diff --git a/doc/mobile/cross_compiling_for_android_cn.md b/doc/mobile/cross_compiling_for_android_cn.md index 424d7718c6..ae24ced770 100644 --- a/doc/mobile/cross_compiling_for_android_cn.md +++ b/doc/mobile/cross_compiling_for_android_cn.md @@ -1,8 +1,9 @@ # Android平台编译指南 用户可通过如下两种方式,交叉编译Android平台上适用的PaddlePaddle库: -- 基于Docker容器的编译方式 -- 基于Linux交叉编译环境的编译方式 + +- [基于Docker容器的编译方式](#基于docker容器的编译方式) +- [基于Linux交叉编译环境的编译方式](#基于linux交叉编译环境的编译方式) ## 基于Docker容器的编译方式 Docker能在所有主要操作系统(包括Linux,Mac OS X和Windows)上运行,因此,使用基于Docker容器的编译方式,用户可在自己熟悉的开发平台上编译Android平台上适用的PaddlePaddle库。 @@ -16,6 +17,12 @@ $ cd Paddle $ docker build -t username/paddle-android:dev . -f Dockerfile.android ``` +用户也可以使用PaddlePaddle提供的官方开发镜像: + +```bash +$ docker pull paddlepaddle/paddle:latest-dev-android +``` + ### 编译PaddlePaddle C-API库 构建好开发镜像后,即可使用开发镜像来编译Android版PaddlePaddle C-API库。 Android的Docker开发镜像向用户提供两个可配置的参数: @@ -41,23 +48,25 @@ Android的Docker开发镜像向用户提供两个可配置的参数: ANDROID_API - >= 21 + >= 16 21 - 编译`armeabi-v7a`,`Android API 21`的PaddlePaddle库 + ```bash $ docker run -it --rm -v $PWD:/paddle -e "ANDROID_ABI=armeabi-v7a" -e "ANDROID_API=21" username/paddle-android:dev ``` - 编译`arm64-v8a`,`Android API 21`的PaddlePaddle库 + ```bash $ docker run -it --rm -v $PWD:/paddle -e "ANDROID_ABI=arm64-v8a" -e "ANDROID_API=21" username/paddle-android:dev ``` -执行上述`docker run`命令时,容器默认执行[paddle/scripts/docker/build_android.sh](https://github.com/PaddlePaddle/Paddle/blob/develop/paddle/scripts/docker/build_android.sh)脚本。该脚本中记录了交叉编译Android版PaddlePaddle库常用的CMake配置,并且会根据`ANDROID_ABI`和`ANDROID_API`自动构建独立工具链、进行编译和安装。由于arm64架构要求Android API不小于21。因此当`ANDROID_ABI=arm64-v8a`,`ANDROID_API<21`时,Docker容器中将默认使用`Android API 21`的编译工具链。用户可以参考下文**配置交叉编译参数**章节,根据个人的需求修改定制Docker容器所执行的脚本。编译安装结束之后,PaddlePaddle的C-API库将被安装到`$PWD/install_android`目录,所依赖的第三方库同时也被安装到`$PWD/install_android/third_party`目录。 +执行上述`docker run`命令时,容器默认执行[paddle/scripts/docker/build_android.sh](https://github.com/PaddlePaddle/Paddle/blob/develop/paddle/scripts/docker/build_android.sh)脚本。该脚本中记录了交叉编译Android版PaddlePaddle库常用的CMake配置,并且会根据`ANDROID_ABI`和`ANDROID_API`自动构建独立工具链、进行编译和安装。由于arm64架构要求Android API不小于21。因此当`ANDROID_ABI=arm64-v8a`,`ANDROID_API<21`时,Docker容器中将默认使用`Android API 21`的编译工具链。用户可以参考下文[配置交叉编译参数](#配置交叉编译参数)章节,根据个人的需求修改定制Docker容器所执行的脚本。编译安装结束之后,PaddlePaddle的C-API库将被安装到`$PWD/install_android`目录,所依赖的第三方库同时也被安装到`$PWD/install_android/third_party`目录。 ## 基于Linux交叉编译环境的编译方式 本文档将以Linux x86-64平台为例,介绍交叉编译Android平台上适用的PaddlePaddle库的方法和步骤。 @@ -83,6 +92,7 @@ your/path/to/android-ndk-r14b-linux-x86_64/build/tools/make-standalone-toolchain 此命令将在`your/path/to/arm_standalone_toolchain`目录生成一套独立编译工具链,面向架构为32位ARM架构,支持的最小的Android API级别为21,支持编译器`arm-linux-androideabi-gcc (GCC) 4.9`和`clang 3.8`。 - 构建`arm64-v8a`、 `Android API 21`的独立工具链: + ```bash your/path/to/android-ndk-r14b-linux-x86_64/build/tools/make-standalone-toolchain.sh \ --arch=arm64 --platform=android-21 --install-dir=your/path/to/arm64_standalone_toolchain @@ -90,14 +100,12 @@ your/path/to/android-ndk-r14b-linux-x86_64/build/tools/make-standalone-toolchain 此命令将在`your/path/to/arm64_standalone_toolchain`目录生成一套独立编译工具链,面向架构为64位ARM64架构,支持的最小Android API级别为21,支持编译器`arm-linux-androideabi-gcc (GCC) 4.9`和`clang 3.8`。 -注意:**PaddlePaddle要求使用的编译工具链所支持的Android API级别不小于21**。 - ### 配置交叉编译参数 CMake系统对交叉编译提供了支持[cmake-toolchains](https://cmake.org/cmake/help/v3.0/manual/cmake-toolchains.7.html#cross-compiling)。为了简化cmake配置,PaddlePaddle为交叉编译提供了工具链配置文档[cmake/cross_compiling/android.cmake](https://github.com/PaddlePaddle/Paddle/blob/develop/cmake/cross_compiling/android.cmake),以提供一些默认的编译器和编译参数相关配置。注意,从CMake 3.7版本开始,CMake官方对Android平台的交叉编译提供了通用的支持。PaddlePaddle若检测到用户使用的CMake版本不低于3.7时,将会将用户传进来的配置参数传递CMake系统,交由CMake系统本身来处理。有关参数配置的详细说明见[cmake-toolchains](https://cmake.org/cmake/help/v3.7/manual/cmake-toolchains.7.html#cross-compiling)。 交叉编译Android版本的PaddlePaddle库时,有一些必须配置的参数: -- `CMAKE_SYSTEM_NAME`,CMake编译的目标平台,必须设置为`Android`。在设置`CMAKE_SYSTEM_NAME=Android`后,PaddlePaddle的CMake系统才认为是在交叉编译Android系统的版本,并自动编译宿主机版protoc可执行文件、目标机版protobuf库、以及Android所需`arm_soft_fp_abi`分支的目标机版OpenBLAS库。此外,还会强制设置一些PaddlePaddle参数的值(`WITH_GPU=OFF`、`WITH_AVX=OFF`、`WITH_PYTHON=OFF`、`WITH_RDMA=OFF`)。 +- `CMAKE_SYSTEM_NAME`,CMake编译的目标平台,必须设置为`Android`。在设置`CMAKE_SYSTEM_NAME=Android`后,PaddlePaddle的CMake系统才认为是在交叉编译Android系统的版本,并自动编译PaddlePaddle所需的所有第三方库。此外,还会强制设置一些PaddlePaddle参数的值(`WITH_GPU=OFF`、`WITH_AVX=OFF`、`WITH_PYTHON=OFF`、`WITH_RDMA=OFF`、`WITH_MKL=OFF`、`WITH_GOLANG=OFF`)。 - `WITH_C_API`,必须设置为`ON`。在Android平台上只支持使用C-API来预测。 - `WITH_SWIG_PY`,必须设置为`OFF`。在Android平台上不支持通过swig调用来训练或者预测。 @@ -119,7 +127,7 @@ Android平台可选配置参数: 其他配置参数: - `USE_EIGEN_FOR_BLAS`,是否使用Eigen库进行矩阵计算。可设置`ON/OFF`,默认值为`OFF`。 -- `HOST_C/CXX_COMPILER`,宿主机的C/C++编译器。在编译宿主机版protoc可执行文件和目标机版OpenBLAS库时需要用到。默认设置成环境变量`CC`的值;若环境变量`CC`没有设置,则设置成`cc`编译器。 +- `HOST_C/CXX_COMPILER`,宿主机的C/C++编译器。在编译宿主机版protoc可执行文件和目标机版OpenBLAS库时需要用到。默认设置成环境变量`CC/CXX`的值;若环境变量`CC/CXX`没有设置,则设置成`cc/c++`编译器。 常用的cmake配置如下: @@ -147,9 +155,10 @@ cmake -DCMAKE_SYSTEM_NAME=Android \ .. ``` -用户还可根据自己的需求设置其他编译参数。比如希望最小化生成的库的大小,可以设置`CMAKE_BUILD_TYPE`为`MinSizeRel`;若希望最快的执行速度,则可设置`CMAKE_BUILD_TYPE`为`Release`。亦可以通过手动设置`CMAKE_C/CXX_FLAGS_MINSIZEREL/RELEASE`来影响PaddlePaddle的编译过程。 +用户还可根据自己的需求设置其他编译参数。比如希望最小化生成的库的大小,可以设置`CMAKE_BUILD_TYPE`为`MinSizeRel`;若希望最快的执行速度,则可设置`CMAKE_BUILD_TYPE`为`Release`。亦可以通过手动设置`CMAKE_C/CXX_FLAGS`来影响PaddlePaddle的编译过程。 **性能TIPS**,为了达到最快的计算速度,在CMake参数配置上,有以下建议: + - 设置`CMAKE_BUILD_TYPE`为`Release` - 使用`clang`编译工具链 - `armeabi-v7a`时,设置`USE_EIGEN_BLAS=ON`,使用Eigen进行矩阵计算;`arm64-v8a`时,设置`USE_EIGEN_FOR_BLAS=OFF`,使用OpenBLAS进行矩阵计算 diff --git a/doc/mobile/cross_compiling_for_android_en.md b/doc/mobile/cross_compiling_for_android_en.md index 26858581fc..0cf50181df 100644 --- a/doc/mobile/cross_compiling_for_android_en.md +++ b/doc/mobile/cross_compiling_for_android_en.md @@ -1,6 +1,9 @@ # Build PaddlePaddle for Android -There are two approaches to build PaddlePaddle for Android: using Docker and on Linux without Docker. +There are two approaches to build PaddlePaddle for Android: + +- [Cross-Compiling Using Docker](#cross-compiling-using-docker) +- [Cross-Compiling on Linux](#cross-compiling-on-linux) ## Cross-Compiling Using Docker @@ -16,6 +19,12 @@ $ cd Paddle $ docker build -t paddle:dev-android . -f Dockerfile.android ``` +Users can directly use the published Docker image. + +```bash +$ docker pull paddlepaddle/paddle:latest-dev-android +``` + ### Build the Inference Library We can run the Docker image we just created to build the inference library of PaddlePaddle for Android using the command below: @@ -47,7 +56,7 @@ The Docker image accepts two arguments `ANDROID_ABI` and `ANDROID_API`: ANDROID_API - >= 21 + >= 16 21 @@ -93,15 +102,13 @@ Android NDK includes everything we need to build the [*standalone toolchain*](ht The generated standalone toolchain will be in `your/path/to/arm64_standalone_toolchain`. -**Please be aware that the minimum level of Android API required by PaddlePaddle is 21.** - ### Cross-Compiling Arguments CMake supports [choosing the toolchain](https://cmake.org/cmake/help/v3.0/manual/cmake-toolchains.7.html#cross-compiling). PaddlePaddle provides [`android.cmake`](https://github.com/PaddlePaddle/Paddle/blob/develop/cmake/cross_compiling/android.cmake), which configures the Android cross-compiling toolchain for CMake. `android.cmake` is not required for CMake >= 3.7, which support Android cross-compiling. PaddlePaddle detects the CMake version, for those newer than 3.7, it uses [the official version](https://cmake.org/cmake/help/v3.7/manual/cmake-toolchains.7.html#cross-compiling). Some other CMake arguments you need to know: -- `CMAKE_SYSTEM_NAME` must be `Android`. This tells PaddlePaddle's CMake system to cross-compile third-party dependencies. This also changes some other CMake arguments like `WITH_GPU=OFF`, `WITH_AVX=OFF`, `WITH_PYTHON=OFF`, and `WITH_RDMA=OFF`. +- `CMAKE_SYSTEM_NAME` must be `Android`. This tells PaddlePaddle's CMake system to cross-compile third-party dependencies. This also changes some other CMake arguments like `WITH_GPU=OFF`, `WITH_AVX=OFF`, `WITH_PYTHON=OFF`, `WITH_RDMA=OFF`, `WITH_MKL=OFF` and `WITH_GOLANG=OFF`. - `WITH_C_API` must be `ON`, to build the C-based inference library for Android. - `WITH_SWIG_PY` must be `OFF` because the Android platform doesn't support SWIG-based API. @@ -123,7 +130,7 @@ Some Android-specific arguments: Other useful arguments: - `USE_EIGEN_FOR_BLAS`: indicates if using Eigen. Could be `ON` or `OFF`, defaults to `OFF`. -- `HOST_C/CXX_COMPILER`: specifies the host compiler, which is used to build the host-specific protoc and target-specific OpenBLAS. It defaults to the value of the environment variable `CC`, or `cc`. +- `HOST_C/CXX_COMPILER`: specifies the host compiler, which is used to build the host-specific protoc and target-specific OpenBLAS. It defaults to the value of the environment variable `CC/C++`, or `cc/c++`. Some frequent configurations for your reference: @@ -158,6 +165,7 @@ There are some other arguments you might want to configure. - `CMAKE_BUILD_TYPE-Release` optimizes the runtime performance. Our own tip for performance optimization to use clang and Eigen or OpenBLAS: + - `CMAKE_BUILD_TYPE=Release` - `ANDROID_TOOLCHAIN=clang` - `USE_EIGEN_BLAS=ON` for `armeabi-v7a`, or `USE_EIGEN_FOR_BLAS=OFF` for `arm64-v8a`. diff --git a/doc/mobile/cross_compiling_for_ios_en.md b/doc/mobile/cross_compiling_for_ios_en.md index aa390cd61f..19bfe86c51 100644 --- a/doc/mobile/cross_compiling_for_ios_en.md +++ b/doc/mobile/cross_compiling_for_ios_en.md @@ -1,4 +1,4 @@ -# PaddlePaddle Compiling Guide for iOS +# Build PaddlePaddle for iOS This tutorial will walk you through cross compiling the PaddlePaddle library for iOS from the source in MacOS. @@ -98,7 +98,7 @@ You can set other compiling parameters for your own need. I.E. if you are trying - set `CMAKE_BUILD_TYPE` with `Release` - set `IOS_USE_VECLIB_FOR_BLAS` with `ON` -## Compile and install +## Build and install After CMake, run following commands, PaddlePaddle will download the compile 3rd party dependencies, compile and install PaddlePaddle inference library. @@ -109,7 +109,7 @@ $ make install Please Note: if you compiled PaddlePaddle in the source directory for other platforms, do remove `third_party` and `build` directory within the source with `rm -rf` to ensure that all the 3rd party libraries dependencies and PaddlePaddle is newly compiled with current CMake configuration. -`your/path/to/install` directory will have following directories after `compile` and `install`: +`your/path/to/install` directory will have following directories after `make install`: - `include`, contains all the C-API header files. - `lib`, contains PaddlePaddle C-API static library. From f899150e0a45f97e836f02ad989e30c0f337310c Mon Sep 17 00:00:00 2001 From: Yang Yang Date: Thu, 21 Dec 2017 08:24:17 +0000 Subject: [PATCH 10/87] 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 11/87] 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 12/87] 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 9b3f2c39f2de4ea0f67dc151c3b59647334c3c3d Mon Sep 17 00:00:00 2001 From: Liu Yiqun Date: Thu, 28 Dec 2017 02:29:58 +0000 Subject: [PATCH 13/87] Add a simple example for fluid to do inference in C++ code. --- CMakeLists.txt | 6 +- cmake/external/eigen.cmake | 2 +- cmake/external/openblas.cmake | 2 +- cmake/generic.cmake | 6 +- paddle/CMakeLists.txt | 1 + paddle/framework/op_desc.cc | 2 +- paddle/framework/var_desc.cc | 2 +- paddle/framework/var_desc.h | 2 +- paddle/inference/CMakeLists.txt | 36 ++++ paddle/inference/example.cc | 56 +++++ paddle/inference/inference.cc | 203 ++++++++++++++++++ paddle/inference/inference.h | 50 +++++ paddle/pybind/protobuf.cc | 2 +- python/paddle/v2/fluid/io.py | 2 +- .../tests/book/test_recognize_digits_mlp.py | 5 + 15 files changed, 365 insertions(+), 12 deletions(-) create mode 100644 paddle/inference/CMakeLists.txt create mode 100644 paddle/inference/example.cc create mode 100644 paddle/inference/inference.cc create mode 100644 paddle/inference/inference.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 5df83499d5..00996cb7ed 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,8 +20,10 @@ set(PADDLE_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}) include(system) project(paddle CXX C Go) -message(STATUS "CXX compiler: " ${CMAKE_CXX_COMPILER} ", version: " ${CMAKE_CXX_COMPILER_VERSION}) -message(STATUS "C compiler: " ${CMAKE_C_COMPILER} ", version: " ${CMAKE_C_COMPILER_VERSION}) +message(STATUS "CXX compiler: ${CMAKE_CXX_COMPILER}, version: " + "${CMAKE_CXX_COMPILER_ID} ${CMAKE_CXX_COMPILER_VERSION}") +message(STATUS "C compiler: ${CMAKE_C_COMPILER}, version: " + "${CMAKE_C_COMPILER_ID} ${CMAKE_C_COMPILER_VERSION}") find_package(Sphinx) if(NOT CMAKE_CROSSCOMPILING) diff --git a/cmake/external/eigen.cmake b/cmake/external/eigen.cmake index 96fc886a34..c4712f19eb 100644 --- a/cmake/external/eigen.cmake +++ b/cmake/external/eigen.cmake @@ -19,7 +19,7 @@ ExternalProject_Add( if (${CMAKE_VERSION} VERSION_LESS "3.3.0") set(dummyfile ${CMAKE_CURRENT_BINARY_DIR}/eigen3_dummy.c) - file(WRITE ${dummyfile} "const char * dummy_eigen3 = \"${dummyfile}\";") + file(WRITE ${dummyfile} "const char *dummy_eigen3 = \"${dummyfile}\";") add_library(eigen3 STATIC ${dummyfile}) else() add_library(eigen3 INTERFACE) diff --git a/cmake/external/openblas.cmake b/cmake/external/openblas.cmake index 97857a686b..d8dc79c19e 100644 --- a/cmake/external/openblas.cmake +++ b/cmake/external/openblas.cmake @@ -113,7 +113,7 @@ INCLUDE_DIRECTORIES(${CBLAS_INC_DIR}) # FIXME(gangliao): generate cblas target to track all high performance # linear algebra libraries for cc_library(xxx SRCS xxx.c DEPS cblas) SET(dummyfile ${CMAKE_CURRENT_BINARY_DIR}/cblas_dummy.c) -FILE(WRITE ${dummyfile} "const char * dummy = \"${dummyfile}\";") +FILE(WRITE ${dummyfile} "const char *dummy_cblas = \"${dummyfile}\";") ADD_LIBRARY(cblas STATIC ${dummyfile}) TARGET_LINK_LIBRARIES(cblas ${CBLAS_LIBRARIES}) diff --git a/cmake/generic.cmake b/cmake/generic.cmake index 66c8e3ad7e..585db019d5 100644 --- a/cmake/generic.cmake +++ b/cmake/generic.cmake @@ -120,7 +120,7 @@ function(merge_static_libs TARGET_NAME) DEPENDS ${libs}) # Generate dummy staic lib - file(WRITE ${target_SRCS} "const char *dummy = \"${target_SRCS}\";") + file(WRITE ${target_SRCS} "const char *dummy_${TARGET_NAME} = \"${target_SRCS}\";") add_library(${TARGET_NAME} STATIC ${target_SRCS}) target_link_libraries(${TARGET_NAME} ${libs_deps}) @@ -160,7 +160,7 @@ function(merge_static_libs TARGET_NAME) DEPENDS ${libs} ${target_OBJS}) # Generate dummy staic lib - file(WRITE ${target_SRCS} "const char *dummy = \"${target_SRCS}\";") + file(WRITE ${target_SRCS} "const char *dummy_${TARGET_NAME} = \"${target_SRCS}\";") add_library(${TARGET_NAME} STATIC ${target_SRCS}) target_link_libraries(${TARGET_NAME} ${libs_deps}) @@ -324,7 +324,7 @@ function(go_library TARGET_NAME) ) # Add dummy code to support `make target_name` under Terminal Command - file(WRITE ${dummyfile} "const char * dummy = \"${dummyfile}\";") + file(WRITE ${dummyfile} "const char *dummy_${TARGET_NAME} = \"${dummyfile}\";") if (go_library_SHARED OR go_library_shared) add_library(${TARGET_NAME} SHARED ${dummyfile}) else() diff --git a/paddle/CMakeLists.txt b/paddle/CMakeLists.txt index 7d2becbdd7..4a98ede278 100644 --- a/paddle/CMakeLists.txt +++ b/paddle/CMakeLists.txt @@ -24,6 +24,7 @@ else() add_subdirectory(framework) add_subdirectory(operators) add_subdirectory(pybind) + add_subdirectory(inference) endif() if(WITH_SWIG_PY) diff --git a/paddle/framework/op_desc.cc b/paddle/framework/op_desc.cc index 781bbb4c19..f9b82180df 100644 --- a/paddle/framework/op_desc.cc +++ b/paddle/framework/op_desc.cc @@ -64,7 +64,7 @@ class CompileTimeInferShapeContext : public InferShapeContext { PADDLE_ENFORCE_EQ(in_var->GetType(), proto::VarDesc::LOD_TENSOR, "The %d-th output of Output(%s) must be LoDTensor.", j, out); - out_var->SetLoDLevel(in_var->GetLodLevel()); + out_var->SetLoDLevel(in_var->GetLoDLevel()); } bool IsRuntime() const override; diff --git a/paddle/framework/var_desc.cc b/paddle/framework/var_desc.cc index 7d002b9ea0..aeab18d721 100644 --- a/paddle/framework/var_desc.cc +++ b/paddle/framework/var_desc.cc @@ -52,7 +52,7 @@ void VarDesc::SetLoDLevel(int32_t lod_level) { } } -int32_t VarDesc::GetLodLevel() const { +int32_t VarDesc::GetLoDLevel() const { switch (desc_.type()) { case proto::VarDesc::LOD_TENSOR: return desc_.lod_tensor().lod_level(); diff --git a/paddle/framework/var_desc.h b/paddle/framework/var_desc.h index 4fd2abe7fb..fc482c4674 100644 --- a/paddle/framework/var_desc.h +++ b/paddle/framework/var_desc.h @@ -76,7 +76,7 @@ class VarDesc { void SetLoDLevel(int32_t lod_level); - int32_t GetLodLevel() const; + int32_t GetLoDLevel() const; proto::VarDesc::VarType GetType() const; diff --git a/paddle/inference/CMakeLists.txt b/paddle/inference/CMakeLists.txt new file mode 100644 index 0000000000..73c875409a --- /dev/null +++ b/paddle/inference/CMakeLists.txt @@ -0,0 +1,36 @@ +set(FLUID_CORE_MODULES + backward proto_desc paddle_memory executor prune init ${GLOB_OP_LIB}) + +cc_library(paddle_fluid_api + SRCS inference.cc + DEPS ${FLUID_CORE_MODULES}) + +# Merge all modules into a simgle static library +cc_library(paddle_fluid DEPS paddle_fluid_api ${FLUID_CORE_MODULES}) + +# ptools +# just for testing, we may need to change the storing format for inference_model +# and move the dependent of pickle. +# download from http://www.picklingtools.com/ +# build in the C++ sub-directory, using command +# make -f Makefile.Linux libptools.so +set(PTOOLS_LIB) +set(PTOOLS_ROOT $ENV{PTOOLS_ROOT} CACHE PATH "Folder contains PicklingTools") +find_path(PTOOLS_INC_DIR chooseser.h PATHS ${PTOOLS_ROOT}/C++) +find_library(PTOOLS_SHARED_LIB NAMES ptools PATHS ${PTOOLS_ROOT}/C++) +if(PTOOLS_INC_DIR AND PTOOLS_SHARED_LIB) + add_definitions(-DPADDLE_USE_PTOOLS) + set(PTOOLS_LIB ptools) + message(STATUS "Found PicklingTools: ${PTOOLS_SHARED_LIB}") + add_library(${PTOOLS_LIB} SHARED IMPORTED GLOBAL) + set_property(TARGET ${PTOOLS_LIB} PROPERTY IMPORTED_LOCATION ${PTOOLS_SHARED_LIB}) + include_directories(${PTOOLS_ROOT}/C++) + include_directories(${PTOOLS_ROOT}/C++/opencontainers_1_8_5/include) + add_definitions(-DOC_NEW_STYLE_INCLUDES) # used in ptools +endif() + +add_executable(example example.cc) +target_link_libraries(example + -Wl,--start-group -Wl,--whole-archive paddle_fluid + -Wl,--no-whole-archive -Wl,--end-group + ${PTOOLS_LIB}) diff --git a/paddle/inference/example.cc b/paddle/inference/example.cc new file mode 100644 index 0000000000..30cdd96327 --- /dev/null +++ b/paddle/inference/example.cc @@ -0,0 +1,56 @@ +/* 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 +#include "paddle/inference/inference.h" + +int main(int argc, char* argv[]) { + std::string dirname = + "/home/work/liuyiqun/PaddlePaddle/Paddle/paddle/inference/" + "recognize_digits_mlp.inference.model"; + std::vector feed_var_names = {"x"}; + std::vector fetch_var_names = {"fc_2.tmp_2"}; + paddle::InferenceEngine* desc = new paddle::InferenceEngine(); + desc->LoadInferenceModel(dirname, feed_var_names, fetch_var_names); + + paddle::framework::LoDTensor input; + srand(time(0)); + float* input_ptr = + input.mutable_data({1, 784}, paddle::platform::CPUPlace()); + for (int i = 0; i < 784; ++i) { + input_ptr[i] = rand() / (static_cast(RAND_MAX)); + } + + std::vector feeds; + feeds.push_back(input); + std::vector fetchs; + desc->Execute(feeds, fetchs); + + for (size_t i = 0; i < fetchs.size(); ++i) { + auto dims_i = fetchs[i].dims(); + std::cout << "dims_i:"; + for (int j = 0; j < dims_i.size(); ++j) { + std::cout << " " << dims_i[j]; + } + std::cout << std::endl; + std::cout << "result:"; + float* output_ptr = fetchs[i].data(); + for (int j = 0; j < paddle::framework::product(dims_i); ++j) { + std::cout << " " << output_ptr[j]; + } + std::cout << std::endl; + } + return 0; +} diff --git a/paddle/inference/inference.cc b/paddle/inference/inference.cc new file mode 100644 index 0000000000..ebfdcd7456 --- /dev/null +++ b/paddle/inference/inference.cc @@ -0,0 +1,203 @@ +/* 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 "inference.h" +#include +#include "paddle/framework/executor.h" +#include "paddle/framework/feed_fetch_method.h" +#include "paddle/framework/init.h" +#include "paddle/framework/scope.h" + +#ifdef PADDLE_USE_PTOOLS +#include "chooseser.h" +#endif + +namespace paddle { + +void InferenceEngine::LoadInferenceModel( + const std::string& dirname, + const std::vector& feed_var_names, + const std::vector& fetch_var_names) { +#ifdef PADDLE_USE_PTOOLS + std::string model_filename = dirname + "/__model__"; + LOG(INFO) << "Using PicklingTools, loading model from " << model_filename; + Val v; + LoadValFromFile(model_filename.c_str(), v, SERIALIZE_P0); + std::string program_desc_str = v["program_desc_str"]; + 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"; + 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::string program_desc_str; + program_desc_str.resize(size); + fs.read(&program_desc_str[0], size); +#endif + program_ = new framework::ProgramDesc(program_desc_str); + GenerateLoadProgram(dirname); + + if (feed_var_names.empty() || fetch_var_names.empty()) { + LOG(FATAL) << "Please specify the feed_var_names and fetch_var_names."; + } + feed_var_names_ = feed_var_names; + fetch_var_names_ = fetch_var_names; + PrependFeedOp(); + AppendFetchOp(); +} + +bool InferenceEngine::IsParameter(const framework::VarDesc* var) { + if (var->Persistable()) { + // There are many unreachable variables in the program + for (size_t i = 0; i < program_->Size(); ++i) { + const framework::BlockDesc& block = program_->Block(i); + for (auto* op : block.AllOps()) { + for (auto input_argument_name : op->InputArgumentNames()) { + if (input_argument_name == var->Name()) { + return true; + } + } + } + } + } + return false; +} + +void InferenceEngine::GenerateLoadProgram(const std::string& dirname) { + framework::BlockDesc* global_block = program_->MutableBlock(0); + + load_program_ = new framework::ProgramDesc(); + framework::BlockDesc* load_block = load_program_->MutableBlock(0); + for (auto* var : global_block->AllVars()) { + if (IsParameter(var)) { + LOG(INFO) << "parameter's name: " << var->Name(); + + // framework::VarDesc new_var = *var; + framework::VarDesc* new_var = load_block->Var(var->Name()); + new_var->SetShape(var->Shape()); + new_var->SetDataType(var->GetDataType()); + new_var->SetType(var->GetType()); + new_var->SetLoDLevel(var->GetLoDLevel()); + new_var->SetPersistable(true); + + // append_op + framework::OpDesc* op = load_block->AppendOp(); + op->SetType("load"); + op->SetOutput("Out", {new_var->Name()}); + op->SetAttr("file_path", {dirname + "/" + new_var->Name()}); + op->CheckAttrs(); + } + } +} + +void InferenceEngine::PrependFeedOp() { + if (!program_) { + LOG(FATAL) << "Please initialize the program_ first."; + } + + framework::BlockDesc* global_block = program_->MutableBlock(0); + + // create_var + framework::VarDesc* feed_var = global_block->Var("feed"); + feed_var->SetType(framework::proto::VarDesc::FEED_MINIBATCH); + feed_var->SetPersistable(true); + + // prepend feed_op + for (size_t i = 0; i < feed_var_names_.size(); ++i) { + std::string var_name = feed_var_names_[i]; + LOG(INFO) << "feed var's name: " << var_name; + + // prepend_op + framework::OpDesc* op = global_block->PrependOp(); + op->SetType("feed"); + op->SetInput("X", {"feed"}); + op->SetOutput("Out", {var_name}); + op->SetAttr("col", {static_cast(i)}); + op->CheckAttrs(); + } +} + +void InferenceEngine::AppendFetchOp() { + if (!program_) { + LOG(FATAL) << "Please initialize the program_ first."; + } + + framework::BlockDesc* global_block = program_->MutableBlock(0); + + // create_var + framework::VarDesc* fetch_var = global_block->Var("fetch"); + fetch_var->SetType(framework::proto::VarDesc::FETCH_LIST); + fetch_var->SetPersistable(true); + + // append fetch_op + for (size_t i = 0; i < fetch_var_names_.size(); ++i) { + std::string var_name = fetch_var_names_[i]; + LOG(INFO) << "fetch var's name: " << var_name; + + // append_op + framework::OpDesc* op = global_block->AppendOp(); + op->SetType("fetch"); + op->SetInput("X", {var_name}); + op->SetOutput("Out", {"fetch"}); + op->SetAttr("col", {static_cast(i)}); + op->CheckAttrs(); + } +} + +void InferenceEngine::Execute(const std::vector& feeds, + std::vector& fetchs) { + if (!program_ || !load_program_) { + LOG(FATAL) << "Please initialize the program_ and load_program_ first."; + } + + if (feeds.size() < feed_var_names_.size()) { + LOG(FATAL) << "Please feed " << feed_var_names_.size() << " input Tensors."; + } + + auto* place = new platform::CPUPlace(); + framework::InitDevices({"CPU"}); + framework::Executor* executor = new framework::Executor(*place); + framework::Scope* scope = new framework::Scope(); + + executor->Run(*load_program_, scope, 0, true, true); + + // set_feed_variable + for (size_t i = 0; i < feed_var_names_.size(); ++i) { + framework::SetFeedVariable(scope, feeds[i], "feed", i); + } + + executor->Run(*program_, scope, 0, true, true); + + // get_fetch_variable + fetchs.resize(fetch_var_names_.size()); + for (size_t i = 0; i < fetch_var_names_.size(); ++i) { + fetchs[i] = framework::GetFetchVariable(*scope, "fetch", i); + } + + delete place; + delete scope; + delete executor; +} +} // namespace paddle diff --git a/paddle/inference/inference.h b/paddle/inference/inference.h new file mode 100644 index 0000000000..a3f3ef4b44 --- /dev/null +++ b/paddle/inference/inference.h @@ -0,0 +1,50 @@ +/* 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/block_desc.h" +#include "paddle/framework/lod_tensor.h" +#include "paddle/framework/program_desc.h" + +namespace paddle { + +class InferenceEngine { +public: + InferenceEngine() : program_(nullptr), load_program_(nullptr) {} + ~InferenceEngine() { + delete program_; + delete load_program_; + } + + void LoadInferenceModel(const std::string& dirname, + const std::vector& feed_var_names, + const std::vector& fetch_var_names); + void Execute(const std::vector& feeds, + std::vector& fetchs); + +private: + bool IsParameter(const framework::VarDesc* var); + void GenerateLoadProgram(const std::string& dirname); + void PrependFeedOp(); + void AppendFetchOp(); + +private: + framework::ProgramDesc* program_; + framework::ProgramDesc* load_program_; + std::vector feed_var_names_; + std::vector fetch_var_names_; +}; + +} // namespace paddle diff --git a/paddle/pybind/protobuf.cc b/paddle/pybind/protobuf.cc index 07292d47e9..564a370001 100644 --- a/paddle/pybind/protobuf.cc +++ b/paddle/pybind/protobuf.cc @@ -216,7 +216,7 @@ void BindVarDsec(py::module &m) { .def("set_dtype", &VarDesc::SetDataType) .def("shape", &VarDesc::Shape, py::return_value_policy::reference) .def("dtype", &VarDesc::GetDataType, py::return_value_policy::reference) - .def("lod_level", &VarDesc::GetLodLevel) + .def("lod_level", &VarDesc::GetLoDLevel) .def("set_lod_level", &VarDesc::SetLoDLevel) .def("type", &VarDesc::GetType) .def("set_type", &VarDesc::SetType) diff --git a/python/paddle/v2/fluid/io.py b/python/paddle/v2/fluid/io.py index 69a732fc45..78b8d97b2e 100644 --- a/python/paddle/v2/fluid/io.py +++ b/python/paddle/v2/fluid/io.py @@ -36,7 +36,7 @@ def save_vars(executor, dirname, main_program=None, vars=None, predicate=None): :param executor: executor that save variable :param dirname: directory path :param main_program: program. If vars is None, then filter all variables in this - program which fit `predicate`. Default g_program. + program which fit `predicate`. Default default_main_program. :param predicate: The Predicate describes a callable that returns a variable as a bool. If it returns true, the variables will be saved. :param vars: variables need to be saved. If specify vars, program & predicate diff --git a/python/paddle/v2/fluid/tests/book/test_recognize_digits_mlp.py b/python/paddle/v2/fluid/tests/book/test_recognize_digits_mlp.py index fc073f6be8..51bfe2973d 100644 --- a/python/paddle/v2/fluid/tests/book/test_recognize_digits_mlp.py +++ b/python/paddle/v2/fluid/tests/book/test_recognize_digits_mlp.py @@ -14,6 +14,7 @@ hidden1 = fluid.layers.fc(input=image, param_attr=fluid.ParamAttr( regularizer=regularizer, clip=fluid.clip.ClipByValue(10))) + hidden2 = fluid.layers.fc(input=hidden1, size=64, act='relu', @@ -73,5 +74,9 @@ for pass_id in range(PASS_NUM): + " test_acc=" + str(test_pass_acc)) if test_pass_acc > 0.7: + fluid.io.save_inference_model( + "./recognize_digits_mlp.inference.model/", ["x"], [predict], + exe) exit(0) + exit(1) From 0c5202cbb562b7d070c807d38de5b54db06833b4 Mon Sep 17 00:00:00 2001 From: Yang Yu Date: Tue, 2 Jan 2018 14:42:27 +0800 Subject: [PATCH 14/87] Tiny enhance of while_op --- paddle/operators/while_op.cc | 42 ++++++++++++++++++------------------ 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/paddle/operators/while_op.cc b/paddle/operators/while_op.cc index 728ef60794..65d827e0e0 100644 --- a/paddle/operators/while_op.cc +++ b/paddle/operators/while_op.cc @@ -25,12 +25,12 @@ namespace operators { using StepScopeVar = std::vector; using LoDTensor = framework::LoDTensor; -constexpr char kStepBlock[] = "sub_block"; -constexpr char kCondition[] = "Condition"; -constexpr char kStepScopes[] = "StepScopes"; -constexpr char kParameters[] = "X"; -constexpr char kParamGrads[] = "X@GRAD"; -constexpr char kOutputs[] = "Out"; +static constexpr char kStepBlock[] = "sub_block"; +static constexpr char kCondition[] = "Condition"; +static constexpr char kStepScopes[] = "StepScopes"; +static constexpr char kX[] = "X"; +static constexpr char kXGRAD[] = "X@GRAD"; +static constexpr char kOutputs[] = "Out"; class WhileOp : public framework::OperatorBase { public: @@ -67,7 +67,7 @@ class WhileOpMaker : public framework::OpProtoAndCheckerMaker { public: WhileOpMaker(OpProto *proto, OpAttrChecker *op_checker) : OpProtoAndCheckerMaker(proto, op_checker) { - AddInput(kParameters, + AddInput(kX, "A set of variables, which are required by operators inside the " "block of While Op.") .AsDuplicable(); @@ -158,8 +158,8 @@ class WhileGradOp : public framework::OperatorBase { executor.Run(*program, *cur_scope_iter, block->ID(), false); - auto &pg_names = Outputs(kParamGrads); - auto &p_names = Inputs(kParameters); + auto &pg_names = Outputs(kXGRAD); + auto &p_names = Inputs(kX); PADDLE_ENFORCE_EQ(pg_names.size(), p_names.size()); for (size_t param_id = 0; param_id < pg_names.size(); ++param_id) { if (pg_names[param_id] == framework::kEmptyVarName) { @@ -213,11 +213,11 @@ class WhileGradOpDescMaker : public framework::SingleGradOpDescMaker { std::unique_ptr Apply() const override { auto *grad = new framework::OpDesc(); grad->SetType("while_grad"); - grad->SetInput(kParameters, Input(kParameters)); + grad->SetInput(kX, Input(kX)); // Not all of IGs will be generated by inner gradient operators of while op. // Ignore IGs that is not generated by the inside block. - auto igs = InputGrad(kParameters, /*do not drop empty gradient*/ false); + auto igs = InputGrad(kX, /*do not drop empty gradient*/ false); std::unordered_set all_outs; for (size_t i = 0; i < grad_block_[0]->OpSize(); ++i) { for (auto &oname : grad_block_[0]->Op(i)->OutputArgumentNames()) { @@ -231,7 +231,7 @@ class WhileGradOpDescMaker : public framework::SingleGradOpDescMaker { } } - grad->SetOutput(framework::GradVarName(kParameters), igs); + grad->SetOutput(framework::GradVarName(kX), igs); grad->SetInput(kOutputs, Output(kOutputs)); @@ -240,7 +240,7 @@ class WhileGradOpDescMaker : public framework::SingleGradOpDescMaker { std::unordered_set block_ins; auto *fwd_block = this->grad_block_[0]->ParentBlock(); { - for (auto &p : Input(kParameters)) { + for (auto &p : Input(kX)) { block_ins.insert(p); } for (auto &o : Output(kOutputs)) { @@ -288,8 +288,8 @@ class WhileGradOpVarTypeInference : public framework::VarTypeInference { public: void operator()(const framework::OpDesc &op_desc, framework::BlockDesc *block) const override { - auto p_names = op_desc.Input(kParameters); - auto pg_names = op_desc.Output(framework::GradVarName(kParameters)); + auto p_names = op_desc.Input(kX); + auto pg_names = op_desc.Output(framework::GradVarName(kX)); for (size_t i = 0; i < p_names.size(); ++i) { auto &p_var = detail::Ref(block->FindVarRecursive(p_names[i])); @@ -307,21 +307,21 @@ class WhileGradOpVarTypeInference : public framework::VarTypeInference { class WhileGradOpShapeInference : public framework::InferShapeBase { public: void operator()(framework::InferShapeContext *ctx) const override { - ctx->HasInputs(kParameters); - ctx->HasOutputs(framework::GradVarName(kParameters)); + ctx->HasInputs(kX); + ctx->HasOutputs(framework::GradVarName(kX)); ctx->HasInputs(kOutputs); ctx->HasInputs(framework::GradVarName(kOutputs)); - auto p_names = ctx->Inputs(kParameters); - auto pg_names = ctx->Outputs(kParamGrads); - auto var_types = ctx->GetInputsVarType(kParameters); + auto p_names = ctx->Inputs(kX); + auto pg_names = ctx->Outputs(kXGRAD); + auto var_types = ctx->GetInputsVarType(kX); std::vector names_to_set; std::vector dims_to_set; for (size_t i = 0; i < p_names.size(); ++i) { if (pg_names[i] == framework::kEmptyVarName) { continue; } - auto dims = ctx->GetInputsElementDim(kParameters, i); + auto dims = ctx->GetInputsElementDim(kX, i); if (var_types[i] == framework::proto::VarDesc::LOD_TENSOR) { names_to_set.push_back(pg_names[i]); dims_to_set.push_back(dims); From f3851fe58dbae3d5d6a450af76b97fb49aa4f4ba Mon Sep 17 00:00:00 2001 From: Luo Tao Date: Tue, 2 Jan 2018 21:18:26 +0800 Subject: [PATCH 15/87] auto pybind when *_op.cc contains several operators --- paddle/operators/CMakeLists.txt | 83 +++++---------------------------- 1 file changed, 11 insertions(+), 72 deletions(-) diff --git a/paddle/operators/CMakeLists.txt b/paddle/operators/CMakeLists.txt index 9f603474de..467963f666 100644 --- a/paddle/operators/CMakeLists.txt +++ b/paddle/operators/CMakeLists.txt @@ -71,74 +71,11 @@ function(op_library TARGET) file(APPEND ${pybind_file} "USE_OP(less_than);\nUSE_OP(equal);\n") endif() - # conv_op contains several operators - if ("${TARGET}" STREQUAL "conv_op") - set(pybind_flag 1) - # It's enough to just adding one operator to pybind - file(APPEND ${pybind_file} "USE_OP(conv2d);\n") - endif() - - # conv_cudnn_op contains several operators - if ("${TARGET}" STREQUAL "conv_cudnn_op") - set(pybind_flag 1) - # It's enough to just adding one operator to pybind - file(APPEND ${pybind_file} "USE_OP(conv2d_cudnn);\n") - endif() - - # pool_op contains several operators - if ("${TARGET}" STREQUAL "pool_op") - set(pybind_flag 1) - # It's enough to just adding one operator to pybind - file(APPEND ${pybind_file} "USE_OP(pool2d);\n") - endif() - - # pool_cudnn_op contains several operators - if ("${TARGET}" STREQUAL "pool_cudnn_op") - set(pybind_flag 1) - # It's enough to just adding one operator to pybind - file(APPEND ${pybind_file} "USE_OP(pool2d_cudnn);\n") - endif() - if ("${TARGET}" STREQUAL "logical_op") set(pybind_flag 1) file(APPEND ${pybind_file} "USE_OP(logical_and);\n") endif() - # pool_with_index_op contains several operators - if ("${TARGET}" STREQUAL "pool_with_index_op") - set(pybind_flag 1) - # It's enough to just adding one operator to pybind - file(APPEND ${pybind_file} "USE_OP(max_pool2d_with_index);\n") - endif() - - # conv_transpose_op contains several operators - if ("${TARGET}" STREQUAL "conv_transpose_op") - set(pybind_flag 1) - # It's enough to just adding one operator to pybind - file(APPEND ${pybind_file} "USE_OP(conv2d_transpose);\n") - endif() - - # conv_transpose_cudnn_op contains two operators - if ("${TARGET}" STREQUAL "conv_transpose_cudnn_op") - set(pybind_flag 1) - # It's enough to just adding one operator to pybind - file(APPEND ${pybind_file} "USE_OP(conv2d_transpose_cudnn);\n") - endif() - - # save_restore_op contains several operators - if ("${TARGET}" STREQUAL "save_restore_op") - set(pybind_flag 1) - # It's enough to just adding one operator to pybind - file(APPEND ${pybind_file} "USE_NO_KERNEL_OP(save);\n") - endif() - - # activation_op contains several operators - if ("${TARGET}" STREQUAL "activation_op") - set(pybind_flag 1) - # It's enough to just adding one operator to pybind - file(APPEND ${pybind_file} "USE_OP(sigmoid);\n") - endif() - # nccl_op contains several operators if ("${TARGET}" STREQUAL "nccl_op") set(pybind_flag 1) @@ -146,21 +83,24 @@ function(op_library TARGET) file(APPEND ${pybind_file} "USE_CUDA_ONLY_OP(ncclAllReduce);\n") endif() - # reduce_op contains several operators - if ("${TARGET}" STREQUAL "reduce_op") - set(pybind_flag 1) - # It's enough to just adding one operator to pybind - file(APPEND ${pybind_file} "USE_OP(reduce_sum);\n") - endif() - if ("${TARGET}" STREQUAL "tensor_array_read_write_op") set(pybind_flag 1) file(APPEND ${pybind_file} "USE_NO_KERNEL_OP(read_from_array);\nUSE_NO_KERNEL_OP(write_to_array);\n") endif() + file(READ ${TARGET}.cc TARGET_CONTENT) + # It's enough to just adding one operator to pybind + string(REGEX MATCH "REGISTER_OP\\(.*REGISTER_OP\\(" multi_register "${TARGET_CONTENT}") + string(REGEX MATCH "REGISTER_OP\\([a-z0-9_]*," one_register "${multi_register}") + if (one_register STREQUAL "") + string(REPLACE "_op" "" TARGET "${TARGET}") + else () + string(REPLACE "REGISTER_OP(" "" TARGET "${one_register}") + string(REPLACE "," "" TARGET "${TARGET}") + endif() + # pybind USE_NO_KERNEL_OP # HACK: if REGISTER_OP_CPU_KERNEL presents the operator must have kernel - file(READ ${TARGET}.cc TARGET_CONTENT) string(REGEX MATCH "REGISTER_OP_CPU_KERNEL" regex_result "${TARGET_CONTENT}") string(REPLACE "_op" "" TARGET "${TARGET}") if (${pybind_flag} EQUAL 0 AND regex_result STREQUAL "") @@ -171,7 +111,6 @@ function(op_library TARGET) # pybind USE_CPU_ONLY_OP list(LENGTH cu_srcs cu_srcs_len) list(LENGTH cu_cc_srcs cu_cc_srcs_len) - if (${pybind_flag} EQUAL 0 AND ${cu_srcs_len} EQUAL 0 AND ${cu_cc_srcs_len} EQUAL 0) file(APPEND ${pybind_file} "USE_CPU_ONLY_OP(${TARGET});\n") set(pybind_flag 1) From e4e95beedc3cabd73e3d37faf6c6d95c96f955df Mon Sep 17 00:00:00 2001 From: Luo Tao Date: Tue, 2 Jan 2018 21:21:42 +0800 Subject: [PATCH 16/87] manually pybind some specific operators --- paddle/operators/CMakeLists.txt | 35 +++++++++------------------------ 1 file changed, 9 insertions(+), 26 deletions(-) diff --git a/paddle/operators/CMakeLists.txt b/paddle/operators/CMakeLists.txt index 467963f666..df737ed9b0 100644 --- a/paddle/operators/CMakeLists.txt +++ b/paddle/operators/CMakeLists.txt @@ -61,32 +61,12 @@ function(op_library TARGET) ${op_common_deps}) endif() - # net_op doesn't need pybind - if ("${TARGET}" STREQUAL "net_op") - set(pybind_flag 1) - endif() - - if ("${TARGET}" STREQUAL "compare_op") - set(pybind_flag 1) - file(APPEND ${pybind_file} "USE_OP(less_than);\nUSE_OP(equal);\n") - endif() - - if ("${TARGET}" STREQUAL "logical_op") - set(pybind_flag 1) - file(APPEND ${pybind_file} "USE_OP(logical_and);\n") - endif() - - # nccl_op contains several operators - if ("${TARGET}" STREQUAL "nccl_op") - set(pybind_flag 1) - # It's enough to just adding one operator to pybind - file(APPEND ${pybind_file} "USE_CUDA_ONLY_OP(ncclAllReduce);\n") - endif() - - if ("${TARGET}" STREQUAL "tensor_array_read_write_op") - set(pybind_flag 1) - file(APPEND ${pybind_file} "USE_NO_KERNEL_OP(read_from_array);\nUSE_NO_KERNEL_OP(write_to_array);\n") - endif() + # net_op doesn't need pybind, others will be pybind manually + foreach(manual_pybind_op "net_op" "compare_op" "logical_op" "nccl_op" "tensor_array_read_write_op") + if ("${TARGET}" STREQUAL "${manual_pybind_op}") + set(pybind_flag 1) + endif() + endforeach() file(READ ${TARGET}.cc TARGET_CONTENT) # It's enough to just adding one operator to pybind @@ -127,6 +107,7 @@ add_subdirectory(nccl) if(WITH_GPU) op_library(nccl_op DEPS nccl_common) + file(APPEND ${pybind_file} "USE_CUDA_ONLY_OP(ncclAllReduce);\n") else() set(DEPS_OPS ${DEPS_OPS} nccl_op) endif() @@ -177,6 +158,8 @@ list(REMOVE_ITEM GENERAL_OPS ${DEPS_OPS}) foreach(src ${GENERAL_OPS}) op_library(${src}) endforeach() +file(APPEND ${pybind_file} "USE_OP(less_than);\nUSE_OP(logical_and);\nUSE_NO_KERNEL_OP(read_from_array);\n") + set(GLOB_OP_LIB ${OP_LIBRARY} CACHE INTERNAL "Global OP library") From f0e797e5b70bf098b407f0ef4983b2bd8f853609 Mon Sep 17 00:00:00 2001 From: yangyaming Date: Wed, 3 Jan 2018 14:12:15 +0800 Subject: [PATCH 17/87] Doc fix and enhancement for lstm_unit python wrapper. --- python/paddle/v2/fluid/layers/nn.py | 126 +++++++++++++++------------- 1 file changed, 66 insertions(+), 60 deletions(-) diff --git a/python/paddle/v2/fluid/layers/nn.py b/python/paddle/v2/fluid/layers/nn.py index 55d8bf8a8a..1a2019d1f2 100644 --- a/python/paddle/v2/fluid/layers/nn.py +++ b/python/paddle/v2/fluid/layers/nn.py @@ -151,7 +151,7 @@ def embedding(input, size, is_sparse=False, param_attr=None, dtype='float32'): Args: input(Variable): Input to the function - size(tuple|list|None): Shape of the look up table parameter + size(tuple|list|None): Shape of the look up table parameter is_sparse(bool): Boolean flag that specifying whether the input is sparse param_attr(ParamAttr): Parameters for this layer dtype(np.dtype|core.DataType|str): The type of data : float32, float_16, int etc @@ -366,9 +366,9 @@ def cross_entropy(input, label, **kwargs): 1) One-hot cross-entropy: `soft_label = False`, `Label[i, 0]` indicates the class index for sample i: - + .. math:: - + Y[i] = -\log(X[i, Label[i]]) 2) Soft-label cross-entropy: @@ -386,15 +386,15 @@ def cross_entropy(input, label, **kwargs): As a special case of 2), when each row of 'label' has only one non-zero element which is equal to 1, soft-label cross-entropy degenerates to a one-hot cross-entropy with one-hot label representation. - + Args: - input (Variable|list): a 2-D tensor with shape [N x D], where N is the - batch size and D is the number of classes. This input is a probability + input (Variable|list): a 2-D tensor with shape [N x D], where N is the + batch size and D is the number of classes. This input is a probability computed by the previous operator, which is almost always the result of a softmax operator. - label (Variable|list): the ground truth which is a 2-D tensor. When - `soft_label` is set to `False`, `label` is a tensor with shape - [N x 1]. When `soft_label` is set to `True`, `label` is a + label (Variable|list): the ground truth which is a 2-D tensor. When + `soft_label` is set to `False`, `label` is a tensor with shape + [N x 1]. When `soft_label` is set to `True`, `label` is a tensor with shape [N x D]. soft_label (bool, via `**kwargs`): a flag indicating whether to interpretate the given labels as soft labels, default `False`. @@ -403,7 +403,7 @@ def cross_entropy(input, label, **kwargs): A 2-D tensor with shape [N x 1], the cross entropy loss. Raises: - `ValueError`: 1) the 1st dimension of `input` and `label` are not equal; 2) when \ + `ValueError`: 1) the 1st dimension of `input` and `label` are not equal; 2) when \ `soft_label == True`, and the 2nd dimension of `input` and `label` are not \ equal; 3) when `soft_label == False`, and the 2nd dimension of `label` is not 1. @@ -727,9 +727,9 @@ def conv2d(input, def sequence_pool(input, pool_type, **kwargs): """ - This function add the operator for sequence pooling. - It pools features of all time-steps of each instance, and is applied - on top of the input using pool_type mentioned in the parameters. + This function add the operator for sequence pooling. + It pools features of all time-steps of each instance, and is applied + on top of the input using pool_type mentioned in the parameters. It supports four pool_type: @@ -758,7 +758,7 @@ def sequence_pool(input, pool_type, **kwargs): Args: input(variable): The input variable which is a LoDTensor. - pool_type (string): The pooling type of sequence_pool. + pool_type (string): The pooling type of sequence_pool. It supports average, sum, sqrt and max. Returns: @@ -768,7 +768,7 @@ def sequence_pool(input, pool_type, **kwargs): .. code-block:: python - x = fluid.layers.data(name='x', shape=[7, 1], + x = fluid.layers.data(name='x', shape=[7, 1], dtype='float32', lod_level=1) avg_x = fluid.layers.sequence_pool(input=x, pool_type='average') sum_x = fluid.layers.sequence_pool(input=x, pool_type='sum') @@ -816,7 +816,7 @@ def sequence_first_step(input, **kwargs): .. code-block:: python - x = fluid.layers.data(name='x', shape=[7, 1], + x = fluid.layers.data(name='x', shape=[7, 1], dtype='float32', lod_level=1) x_first_step = fluid.layers.sequence_first_step(input=x) """ @@ -849,7 +849,7 @@ def sequence_last_step(input, **kwargs): .. code-block:: python - x = fluid.layers.data(name='x', shape=[7, 1], + x = fluid.layers.data(name='x', shape=[7, 1], dtype='float32', lod_level=1) x_last_step = fluid.layers.sequence_last_step(input=x) """ @@ -1168,25 +1168,26 @@ def lstm_unit(x_t, .. math:: - i_t & = \sigma(W_{x_i}x_{t} + W_{h_i}h_{t-1} + W_{c_i}c_{t-1} + b_i) + i_t & = \sigma(W_{x_i}x_{t} + W_{h_i}h_{t-1} + b_i) - f_t & = \sigma(W_{x_f}x_{t} + W_{h_f}h_{t-1} + W_{c_f}c_{t-1} + b_f) + f_t & = \sigma(W_{x_f}x_{t} + W_{h_f}h_{t-1} + b_f) - c_t & = f_tc_{t-1} + i_t tanh (W_{x_c}x_t+W_{h_c}h_{t-1} + b_c) + c_t & = f_tc_{t-1} + i_t tanh (W_{x_c}x_t + W_{h_c}h_{t-1} + b_c) - o_t & = \sigma(W_{x_o}x_{t} + W_{h_o}h_{t-1} + W_{c_o}c_t + b_o) + o_t & = \sigma(W_{x_o}x_{t} + W_{h_o}h_{t-1} + b_o) h_t & = o_t tanh(c_t) - The inputs of lstm unit includes :math:`x_t`, :math:`h_{t-1}` and - :math:`c_{t-1}`. The implementation separates the linear transformation - and non-linear transformation apart. Here, we take :math:`i_t` as an - example. The linear transformation is applied by calling a `fc` layer and - the equation is: + The inputs of lstm unit include :math:`x_t`, :math:`h_{t-1}` and + :math:`c_{t-1}`. The 2nd dimensions of :math:`h_{t-1}` and :math:`c_{t-1}` + should be same. The implementation separates the linear transformation and + non-linear transformation apart. Here, we take :math:`i_t` as an example. + The linear transformation is applied by calling a `fc` layer and the + equation is: .. math:: - L_{i_t} = W_{x_i}x_{t} + W_{h_i}h_{t-1} + W_{c_i}c_{t-1} + b_i + L_{i_t} = W_{x_i}x_{t} + W_{h_i}h_{t-1} + b_i The non-linear transformation is applied by calling `lstm_unit_op` and the equation is: @@ -1213,14 +1214,15 @@ def lstm_unit(x_t, Raises: ValueError: The ranks of **x_t**, **hidden_t_prev** and **cell_t_prev**\ not be 2 or the 1st dimensions of **x_t**, **hidden_t_prev** \ - and **cell_t_prev** not be the same. + and **cell_t_prev** not be the same or the 2nd dimensions of \ + **hidden_t_prev** and **cell_t_prev** not be the same. Examples: .. code-block:: python x_t = fluid.layers.fc(input=x_t_data, size=10) - prev_hidden = fluid.layers.fc(input=prev_hidden_data, size=20) + prev_hidden = fluid.layers.fc(input=prev_hidden_data, size=30) prev_cell = fluid.layers.fc(input=prev_cell_data, size=30) hidden_value, cell_value = fluid.layers.lstm_unit(x_t=x_t, hidden_t_prev=prev_hidden, @@ -1239,7 +1241,11 @@ def lstm_unit(x_t, if x_t.shape[0] != hidden_t_prev.shape[0] or x_t.shape[ 0] != cell_t_prev.shape[0]: - raise ValueError("The 1s dimension of x_t, hidden_t_prev and " + raise ValueError("The 1s dimensions of x_t, hidden_t_prev and " + "cell_t_prev must be the same.") + + if hidden_t_prev.shape[1] != cell_t_prev.shape[1]: + raise ValueError("The 2nd dimensions of hidden_t_prev and " "cell_t_prev must be the same.") if bias_attr is None: @@ -1268,17 +1274,17 @@ def lstm_unit(x_t, def reduce_sum(input, dim=None, keep_dim=False): """ - Computes the sum of tensor elements over the given dimension. + Computes the sum of tensor elements over the given dimension. Args: input (Variable): The input variable which is a Tensor or LoDTensor. - dim (int|None): The dimension along which the sum is performed. If - :attr:`None`, sum all elements of :attr:`input` and return a - Tensor variable with a single element, otherwise must be in the - range :math:`[-rank(input), rank(input))`. If :math:`dim < 0`, + dim (int|None): The dimension along which the sum is performed. If + :attr:`None`, sum all elements of :attr:`input` and return a + Tensor variable with a single element, otherwise must be in the + range :math:`[-rank(input), rank(input))`. If :math:`dim < 0`, the dimension to reduce is :math:`rank + dim`. - keep_dim (bool): Whether to reserve the reduced dimension in the - output Tensor. The result tensor will have one fewer dimension + keep_dim (bool): Whether to reserve the reduced dimension in the + output Tensor. The result tensor will have one fewer dimension than the :attr:`input` unless :attr:`keep_dim` is true. Returns: @@ -1312,17 +1318,17 @@ def reduce_sum(input, dim=None, keep_dim=False): def reduce_mean(input, dim=None, keep_dim=False): """ - Computes the mean of tensor elements over the given dimension. + Computes the mean of tensor elements over the given dimension. Args: input (Variable): The input variable which is a Tensor or LoDTensor. - dim (int|None): The dimension along which the mean is computed. If - :attr:`None`, compute the mean over all elements of :attr:`input` - and return a Tensor variable with a single element, otherwise - must be in the range :math:`[-rank(input), rank(input))`. If + dim (int|None): The dimension along which the mean is computed. If + :attr:`None`, compute the mean over all elements of :attr:`input` + and return a Tensor variable with a single element, otherwise + must be in the range :math:`[-rank(input), rank(input))`. If :math:`dim < 0`, the dimension to reduce is :math:`rank + dim`. - keep_dim (bool): Whether to reserve the reduced dimension in the - output Tensor. The result tensor will have one fewer dimension + keep_dim (bool): Whether to reserve the reduced dimension in the + output Tensor. The result tensor will have one fewer dimension than the :attr:`input` unless :attr:`keep_dim` is true. Returns: @@ -1356,22 +1362,22 @@ def reduce_mean(input, dim=None, keep_dim=False): def reduce_max(input, dim=None, keep_dim=False): """ - Computes the maximum of tensor elements over the given dimension. + Computes the maximum of tensor elements over the given dimension. Args: input (Variable): The input variable which is a Tensor or LoDTensor. - dim (int|None): The dimension along which the maximum is computed. - If :attr:`None`, compute the maximum over all elements of - :attr:`input` and return a Tensor variable with a single element, - otherwise must be in the range :math:`[-rank(input), rank(input))`. + dim (int|None): The dimension along which the maximum is computed. + If :attr:`None`, compute the maximum over all elements of + :attr:`input` and return a Tensor variable with a single element, + otherwise must be in the range :math:`[-rank(input), rank(input))`. If :math:`dim < 0`, the dimension to reduce is :math:`rank + dim`. - keep_dim (bool): Whether to reserve the reduced dimension in the - output Tensor. The result tensor will have one fewer dimension + keep_dim (bool): Whether to reserve the reduced dimension in the + output Tensor. The result tensor will have one fewer dimension than the :attr:`input` unless :attr:`keep_dim` is true. Returns: Variable: The reduced Tensor variable. - + Examples: .. code-block:: python @@ -1400,22 +1406,22 @@ def reduce_max(input, dim=None, keep_dim=False): def reduce_min(input, dim=None, keep_dim=False): """ - Computes the minimum of tensor elements over the given dimension. + Computes the minimum of tensor elements over the given dimension. Args: input (Variable): The input variable which is a Tensor or LoDTensor. - dim (int|None): The dimension along which the minimum is computed. - If :attr:`None`, compute the minimum over all elements of - :attr:`input` and return a Tensor variable with a single element, - otherwise must be in the range :math:`[-rank(input), rank(input))`. + dim (int|None): The dimension along which the minimum is computed. + If :attr:`None`, compute the minimum over all elements of + :attr:`input` and return a Tensor variable with a single element, + otherwise must be in the range :math:`[-rank(input), rank(input))`. If :math:`dim < 0`, the dimension to reduce is :math:`rank + dim`. - keep_dim (bool): Whether to reserve the reduced dimension in the - output Tensor. The result tensor will have one fewer dimension + keep_dim (bool): Whether to reserve the reduced dimension in the + output Tensor. The result tensor will have one fewer dimension than the :attr:`input` unless :attr:`keep_dim` is true. Returns: Variable: The reduced Tensor variable. - + Examples: .. code-block:: python From d6ec9630473712bf0a61b121030369b63a9996b8 Mon Sep 17 00:00:00 2001 From: yangyaming Date: Wed, 3 Jan 2018 14:20:33 +0800 Subject: [PATCH 18/87] Minor correction. --- python/paddle/v2/fluid/layers/nn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/paddle/v2/fluid/layers/nn.py b/python/paddle/v2/fluid/layers/nn.py index 1a2019d1f2..09b71cc371 100644 --- a/python/paddle/v2/fluid/layers/nn.py +++ b/python/paddle/v2/fluid/layers/nn.py @@ -1241,7 +1241,7 @@ def lstm_unit(x_t, if x_t.shape[0] != hidden_t_prev.shape[0] or x_t.shape[ 0] != cell_t_prev.shape[0]: - raise ValueError("The 1s dimensions of x_t, hidden_t_prev and " + raise ValueError("The 1st dimensions of x_t, hidden_t_prev and " "cell_t_prev must be the same.") if hidden_t_prev.shape[1] != cell_t_prev.shape[1]: From c0f6f492bcc86dcb2a5702332915852734884b9a Mon Sep 17 00:00:00 2001 From: yangyaming Date: Wed, 3 Jan 2018 14:24:31 +0800 Subject: [PATCH 19/87] Add shape info for arguments. --- 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 09b71cc371..5442cce494 100644 --- a/python/paddle/v2/fluid/layers/nn.py +++ b/python/paddle/v2/fluid/layers/nn.py @@ -1199,9 +1199,9 @@ def lstm_unit(x_t, This layer has two outputs including :math:`h_t` and :math:`o_t`. Args: - x_t (Variable): The input value of current step. - hidden_t_prev (Variable): The hidden value of lstm unit. - cell_t_prev (Variable): The cell value of lstm unit. + x_t (Variable): The input value of current step, a 2-D tensor. + hidden_t_prev (Variable): The hidden value of lstm unit, a 2-D tensor. + cell_t_prev (Variable): The cell value of lstm unit, a 2-D tensor. forget_bias (float): The forget bias of lstm unit. param_attr (ParamAttr): The attributes of parameter weights, used to set initializer, name etc. From 5974c1b76e400da3b6f3e1dd8884fb006d48cc59 Mon Sep 17 00:00:00 2001 From: Luo Tao Date: Wed, 3 Jan 2018 15:09:24 +0800 Subject: [PATCH 20/87] refine comments in CMakelists.txt of operator --- paddle/operators/CMakeLists.txt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/paddle/operators/CMakeLists.txt b/paddle/operators/CMakeLists.txt index df737ed9b0..a0b61640e5 100644 --- a/paddle/operators/CMakeLists.txt +++ b/paddle/operators/CMakeLists.txt @@ -61,7 +61,7 @@ function(op_library TARGET) ${op_common_deps}) endif() - # net_op doesn't need pybind, others will be pybind manually + # Define operators that don't need pybind here. foreach(manual_pybind_op "net_op" "compare_op" "logical_op" "nccl_op" "tensor_array_read_write_op") if ("${TARGET}" STREQUAL "${manual_pybind_op}") set(pybind_flag 1) @@ -69,7 +69,8 @@ function(op_library TARGET) endforeach() file(READ ${TARGET}.cc TARGET_CONTENT) - # It's enough to just adding one operator to pybind + # It's enough to just adding one operator to pybind. + # And for detail pybind information, please see paddle/pybind/pybind.h. string(REGEX MATCH "REGISTER_OP\\(.*REGISTER_OP\\(" multi_register "${TARGET_CONTENT}") string(REGEX MATCH "REGISTER_OP\\([a-z0-9_]*," one_register "${multi_register}") if (one_register STREQUAL "") From cb0b81f97b5791a29aab73797abfa2820b89f8a3 Mon Sep 17 00:00:00 2001 From: Yang Yang Date: Wed, 3 Jan 2018 07:21:40 +0000 Subject: [PATCH 21/87] 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 90a5a55a6c68585707d012346ff055fbe534eba3 Mon Sep 17 00:00:00 2001 From: Yang Yu Date: Wed, 3 Jan 2018 15:22:24 +0800 Subject: [PATCH 22/87] Expose some activations --- python/paddle/v2/fluid/layers/ops.py | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/python/paddle/v2/fluid/layers/ops.py b/python/paddle/v2/fluid/layers/ops.py index d2ff6841a3..23fe13f9bb 100644 --- a/python/paddle/v2/fluid/layers/ops.py +++ b/python/paddle/v2/fluid/layers/ops.py @@ -1,9 +1,24 @@ from ..registry import register_layer -__all__ = [ - 'mean', 'mul', 'dropout', 'reshape', 'sigmoid', 'scale', 'transpose', - 'sigmoid_cross_entropy_with_logits', 'elementwise_add', 'elementwise_div', - 'elementwise_sub', 'elementwise_mul', 'clip', 'abs', 'sequence_softmax' + +__activations__ = [ + 'abs', 'tanh', 'sigmoid', 'relu', 'sqrt', 'ceil', 'floor', 'log', 'round' ] +__all__ = [ + 'mean', + 'mul', + 'dropout', + 'reshape', + 'scale', + 'transpose', + 'sigmoid_cross_entropy_with_logits', + 'elementwise_add', + 'elementwise_div', + 'elementwise_sub', + 'elementwise_mul', + 'clip', + 'sequence_softmax', +] + __activations__ + for _OP in set(__all__): globals()[_OP] = register_layer(_OP) From 60fecce43db68281112a91198d85a79a972f03f9 Mon Sep 17 00:00:00 2001 From: yangyaming Date: Wed, 3 Jan 2018 15:20:00 +0800 Subject: [PATCH 23/87] Fix unit test for lstm_unit. --- python/paddle/v2/fluid/layers/nn.py | 9 ++++++--- python/paddle/v2/fluid/tests/test_layers.py | 4 ++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/python/paddle/v2/fluid/layers/nn.py b/python/paddle/v2/fluid/layers/nn.py index 5442cce494..1c1c09dd28 100644 --- a/python/paddle/v2/fluid/layers/nn.py +++ b/python/paddle/v2/fluid/layers/nn.py @@ -1199,9 +1199,12 @@ def lstm_unit(x_t, This layer has two outputs including :math:`h_t` and :math:`o_t`. Args: - x_t (Variable): The input value of current step, a 2-D tensor. - hidden_t_prev (Variable): The hidden value of lstm unit, a 2-D tensor. - cell_t_prev (Variable): The cell value of lstm unit, a 2-D tensor. + x_t (Variable): The input value of current step, a 2-D tensor with shape + M x N, M for batch size and N for input size. + hidden_t_prev (Variable): The hidden value of lstm unit, a 2-D tensor + with shape M x S, M for batch size and S for size of lstm unit. + cell_t_prev (Variable): The cell value of lstm unit, a 2-D tensor with + shape M x S, M for batch size and S for size of lstm unit. forget_bias (float): The forget bias of lstm unit. param_attr (ParamAttr): The attributes of parameter weights, used to set initializer, name etc. diff --git a/python/paddle/v2/fluid/tests/test_layers.py b/python/paddle/v2/fluid/tests/test_layers.py index 9d2dcca56d..77f0f11f1b 100644 --- a/python/paddle/v2/fluid/tests/test_layers.py +++ b/python/paddle/v2/fluid/tests/test_layers.py @@ -177,8 +177,8 @@ class TestBook(unittest.TestCase): name='x_t_data', shape=[10, 10], dtype='float32') x_t = layers.fc(input=x_t_data, size=10) prev_hidden_data = layers.data( - name='prev_hidden_data', shape=[10, 20], dtype='float32') - prev_hidden = layers.fc(input=prev_hidden_data, size=20) + name='prev_hidden_data', shape=[10, 30], dtype='float32') + prev_hidden = layers.fc(input=prev_hidden_data, size=30) prev_cell_data = layers.data( name='prev_cell', shape=[10, 30], dtype='float32') prev_cell = layers.fc(input=prev_cell_data, size=30) From 059096741e85d8595b1d6767de936afb051d7f06 Mon Sep 17 00:00:00 2001 From: Yang Yu Date: Wed, 3 Jan 2018 16:09:30 +0800 Subject: [PATCH 24/87] Add init glog --- paddle/framework/init.cc | 4 ++++ paddle/framework/init.h | 2 ++ paddle/pybind/pybind.cc | 1 + python/paddle/v2/fluid/__init__.py | 25 +++++++++++++------------ 4 files changed, 20 insertions(+), 12 deletions(-) diff --git a/paddle/framework/init.cc b/paddle/framework/init.cc index 682cff168d..1980023372 100644 --- a/paddle/framework/init.cc +++ b/paddle/framework/init.cc @@ -75,5 +75,9 @@ bool InitDevices(const std::vector &devices) { return true; } +void InitGLOG(const std::string &prog_name) { + google::InitGoogleLogging(prog_name.c_str()); +} + } // namespace framework } // namespace paddle diff --git a/paddle/framework/init.h b/paddle/framework/init.h index 33907f9eb0..9c84a03ded 100644 --- a/paddle/framework/init.h +++ b/paddle/framework/init.h @@ -22,6 +22,8 @@ namespace framework { void InitGflags(std::vector &argv); +void InitGLOG(const std::string &prog_name); + bool InitDevices(const std::vector &devices); } // namespace framework diff --git a/paddle/pybind/pybind.cc b/paddle/pybind/pybind.cc index 04485ce7c1..364db62cba 100644 --- a/paddle/pybind/pybind.cc +++ b/paddle/pybind/pybind.cc @@ -427,6 +427,7 @@ All parameter, weight, gradient are variables in Paddle. m.def("unique_integer", UniqueIntegerGenerator); m.def("init_gflags", framework::InitGflags); + m.def("init_glog", framework::InitGLOG); m.def("init_devices", &framework::InitDevices); m.def("is_compile_gpu", IsCompileGPU); diff --git a/python/paddle/v2/fluid/__init__.py b/python/paddle/v2/fluid/__init__.py index 225b41c504..b00892d91a 100644 --- a/python/paddle/v2/fluid/__init__.py +++ b/python/paddle/v2/fluid/__init__.py @@ -1,23 +1,23 @@ # import all class inside framework into fluid module -import framework -from framework import * -# import all class inside executor into fluid module -import executor -from executor import * +from core import LoDTensor -import io +import backward +import clip import evaluator +# import all class inside executor into fluid module +import executor +import framework import initializer +import io import layers import nets import optimizer -import backward import regularizer -from param_attr import ParamAttr from data_feeder import DataFeeder -from core import LoDTensor, CPUPlace, CUDAPlace from distribute_transpiler import DistributeTranspiler -import clip +from executor import * +from framework import * +from param_attr import ParamAttr Tensor = LoDTensor __all__ = framework.__all__ + executor.__all__ + [ @@ -27,7 +27,7 @@ __all__ = framework.__all__ + executor.__all__ + [ ] -def __read_gflags_from_env__(): +def __bootstrap__(): """ Enable reading gflags from environment variables. @@ -41,6 +41,7 @@ def __read_gflags_from_env__(): read_env_flags.append('fraction_of_gpu_memory_to_use') core.init_gflags([sys.argv[0]] + ["--tryfromenv=" + ",".join(read_env_flags)]) + core.init_glog(sys.argv[0]) if core.is_compile_gpu(): core.init_devices(["CPU", "GPU:0"]) @@ -48,4 +49,4 @@ def __read_gflags_from_env__(): core.init_devices(["CPU"]) -__read_gflags_from_env__() +__bootstrap__() From 0c16f4f6029871c1fadd97b60e93feba2990e75c Mon Sep 17 00:00:00 2001 From: Yang Yu Date: Wed, 3 Jan 2018 16:18:10 +0800 Subject: [PATCH 25/87] Update --- python/paddle/v2/fluid/__init__.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/python/paddle/v2/fluid/__init__.py b/python/paddle/v2/fluid/__init__.py index b00892d91a..e115529665 100644 --- a/python/paddle/v2/fluid/__init__.py +++ b/python/paddle/v2/fluid/__init__.py @@ -1,23 +1,23 @@ # import all class inside framework into fluid module -from core import LoDTensor - -import backward -import clip -import evaluator +import framework +from framework import * # import all class inside executor into fluid module import executor -import framework -import initializer +from executor import * + import io +import evaluator +import initializer import layers import nets import optimizer +import backward import regularizer +from param_attr import ParamAttr from data_feeder import DataFeeder +from core import LoDTensor, CPUPlace, CUDAPlace from distribute_transpiler import DistributeTranspiler -from executor import * -from framework import * -from param_attr import ParamAttr +import clip Tensor = LoDTensor __all__ = framework.__all__ + executor.__all__ + [ From 2b3d94691bd9a2cf5ddadce1bb9e7227b0f891da Mon Sep 17 00:00:00 2001 From: Yang Yu Date: Wed, 3 Jan 2018 16:38:49 +0800 Subject: [PATCH 26/87] Update init.cc --- paddle/framework/init.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/paddle/framework/init.cc b/paddle/framework/init.cc index 1980023372..3bea8f3d0a 100644 --- a/paddle/framework/init.cc +++ b/paddle/framework/init.cc @@ -77,6 +77,7 @@ bool InitDevices(const std::vector &devices) { void InitGLOG(const std::string &prog_name) { google::InitGoogleLogging(prog_name.c_str()); + google::InstallFailureSignalHandler(); } } // namespace framework From 72652845b63bec4308e04fb112a338a607d87284 Mon Sep 17 00:00:00 2001 From: tensor-tang Date: Wed, 3 Jan 2018 16:53:57 +0800 Subject: [PATCH 27/87] 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 28/87] 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 63e3150772557e311e32914c44c0f22420ecb52d Mon Sep 17 00:00:00 2001 From: Yang Yu Date: Wed, 3 Jan 2018 17:13:35 +0800 Subject: [PATCH 29/87] Update code --- paddle/framework/lod_tensor.cc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/paddle/framework/lod_tensor.cc b/paddle/framework/lod_tensor.cc index 7b6dc09bdb..92b3d7fccd 100644 --- a/paddle/framework/lod_tensor.cc +++ b/paddle/framework/lod_tensor.cc @@ -177,6 +177,9 @@ void AppendLoD(LoD *lod, const LoD &lod_length) { lod->empty() || lod->size() == lod_length.size(), "The lod_length should has the same size with the appended lod."); if (lod->empty()) { + for (size_t i = 0; i < lod_length.size(); ++i) { + lod->emplace_back(1, 0); // size = 1, value = 0; + } *lod = LoD(lod_length.size(), std::vector({0})); } for (size_t i = 0; i < lod->size(); ++i) { From 31fda46cf4e936df006d102d16829cb1cea68dca Mon Sep 17 00:00:00 2001 From: tensor-tang Date: Wed, 3 Jan 2018 17:25:00 +0800 Subject: [PATCH 30/87] 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 5b3cf4ee61482b44c89ac8ebe9cf656e9d6fac7c Mon Sep 17 00:00:00 2001 From: Liu Yiqun Date: Wed, 3 Jan 2018 17:26:22 +0800 Subject: [PATCH 31/87] Use gflags to parse arguments from command-line. --- paddle/inference/example.cc | 41 +++++++++++++++++++++++++++-------- paddle/inference/inference.cc | 1 - 2 files changed, 32 insertions(+), 10 deletions(-) diff --git a/paddle/inference/example.cc b/paddle/inference/example.cc index 30cdd96327..9711b20e6f 100644 --- a/paddle/inference/example.cc +++ b/paddle/inference/example.cc @@ -14,16 +14,37 @@ limitations under the License. */ #include #include +#include "gflags/gflags.h" #include "paddle/inference/inference.h" -int main(int argc, char* argv[]) { - std::string dirname = - "/home/work/liuyiqun/PaddlePaddle/Paddle/paddle/inference/" - "recognize_digits_mlp.inference.model"; - std::vector feed_var_names = {"x"}; - std::vector fetch_var_names = {"fc_2.tmp_2"}; - paddle::InferenceEngine* desc = new paddle::InferenceEngine(); - desc->LoadInferenceModel(dirname, feed_var_names, fetch_var_names); +DEFINE_string(dirname, "", "Directory of the inference model."); +DEFINE_string(feed_var_names, "", "Names of feeding variables"); +DEFINE_string(fetch_var_names, "", "Names of fetching variables"); + +int main(int argc, char** argv) { + google::ParseCommandLineFlags(&argc, &argv, true); + if (FLAGS_dirname.empty() || FLAGS_feed_var_names.empty() || + FLAGS_fetch_var_names.empty()) { + // Example: + // ./example --dirname=recognize_digits_mlp.inference.model + // --feed_var_names="x" + // --fetch_var_names="fc_2.tmp_2" + std::cout << "Usage: ./example --dirname=path/to/your/model " + "--feed_var_names=x --fetch_var_names=y" + << std::endl; + exit(1); + } + + std::cout << "FLAGS_dirname: " << FLAGS_dirname << std::endl; + std::cout << "FLAGS_feed_var_names: " << FLAGS_feed_var_names << std::endl; + std::cout << "FLAGS_fetch_var_names: " << FLAGS_fetch_var_names << std::endl; + + std::string dirname = FLAGS_dirname; + std::vector feed_var_names = {FLAGS_feed_var_names}; + std::vector fetch_var_names = {FLAGS_fetch_var_names}; + + paddle::InferenceEngine* engine = new paddle::InferenceEngine(); + engine->LoadInferenceModel(dirname, feed_var_names, fetch_var_names); paddle::framework::LoDTensor input; srand(time(0)); @@ -36,7 +57,7 @@ int main(int argc, char* argv[]) { std::vector feeds; feeds.push_back(input); std::vector fetchs; - desc->Execute(feeds, fetchs); + engine->Execute(feeds, fetchs); for (size_t i = 0; i < fetchs.size(); ++i) { auto dims_i = fetchs[i].dims(); @@ -52,5 +73,7 @@ int main(int argc, char* argv[]) { } std::cout << std::endl; } + + delete engine; return 0; } diff --git a/paddle/inference/inference.cc b/paddle/inference/inference.cc index ebfdcd7456..48a51efcd2 100644 --- a/paddle/inference/inference.cc +++ b/paddle/inference/inference.cc @@ -94,7 +94,6 @@ void InferenceEngine::GenerateLoadProgram(const std::string& dirname) { if (IsParameter(var)) { LOG(INFO) << "parameter's name: " << var->Name(); - // framework::VarDesc new_var = *var; framework::VarDesc* new_var = load_block->Var(var->Name()); new_var->SetShape(var->Shape()); new_var->SetDataType(var->GetDataType()); From 5a4367bb16c14fb039dbeedcfe44051663ea77d0 Mon Sep 17 00:00:00 2001 From: Yang Yu Date: Wed, 3 Jan 2018 17:33:16 +0800 Subject: [PATCH 32/87] Update --- paddle/operators/activation_op.h | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/paddle/operators/activation_op.h b/paddle/operators/activation_op.h index 0885f7c570..88c3d1c597 100644 --- a/paddle/operators/activation_op.h +++ b/paddle/operators/activation_op.h @@ -15,6 +15,7 @@ limitations under the License. */ #pragma once #include "paddle/framework/eigen.h" #include "paddle/framework/op_registry.h" +#include "paddle/operators/detail/safe_ref.h" namespace paddle { namespace operators { @@ -26,12 +27,16 @@ class ActivationKernel using T = typename Functor::ELEMENT_TYPE; void Compute(const framework::ExecutionContext& context) const override { - auto* X = context.Input("X"); - auto* Out = context.Output("Out"); - Out->mutable_data(context.GetPlace()); - - auto x = framework::EigenVector::Flatten(*X); - auto out = framework::EigenVector::Flatten(*Out); + auto& X = detail::Ref(context.Input("X"), + "Cannot get input tensor X, variable name = %s", + context.op().Input("X")); + + auto& Out = detail::Ref(context.Output("Out"), + "Cannot get output tensor Out, variable name = %s", + context.op().Output("Out")); + Out.mutable_data(context.GetPlace()); + auto x = framework::EigenVector::Flatten(X); + auto out = framework::EigenVector::Flatten(Out); auto* place = context.template device_context().eigen_device(); Functor functor; From 6004a2ed4f74b864b8ff886d20e18891ac0a8ef3 Mon Sep 17 00:00:00 2001 From: Yang Yang Date: Wed, 3 Jan 2018 09:38:13 +0000 Subject: [PATCH 33/87] 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 34/87] 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 35/87] 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 2d2b633282523c494a99e02da092c87da0c87dc0 Mon Sep 17 00:00:00 2001 From: Luo Tao Date: Wed, 3 Jan 2018 19:53:22 +0800 Subject: [PATCH 36/87] add more comments in CMakelists.txt of operator --- paddle/framework/op_registry.h | 4 ++-- paddle/operators/CMakeLists.txt | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/paddle/framework/op_registry.h b/paddle/framework/op_registry.h index bdaa259181..d75c0233e8 100644 --- a/paddle/framework/op_registry.h +++ b/paddle/framework/op_registry.h @@ -37,8 +37,8 @@ class Registrar { public: // In our design, various kinds of classes, e.g., operators and kernels, // have their corresponding registry and registrar. The action of - // registration is in the constructor of a global registrar variable, which, - // however, are not used in the code that calls package framework, and would + // registration is in the constructor of a global registrar variable, which + // are not used in the code that calls package framework, and would // be removed from the generated binary file by the linker. To avoid such // removal, we add Touch to all registrar classes and make USE_OP macros to // call this method. So, as long as the callee code calls USE_OP, the global diff --git a/paddle/operators/CMakeLists.txt b/paddle/operators/CMakeLists.txt index a0b61640e5..77b52eb176 100644 --- a/paddle/operators/CMakeLists.txt +++ b/paddle/operators/CMakeLists.txt @@ -68,9 +68,10 @@ function(op_library TARGET) endif() endforeach() + # The registration of USE_OP, please refer to paddle/framework/op_registry.h. + # Note that it's enough to just adding one operator to pybind in a *_op.cc file. + # And for detail pybind information, please see generated paddle/pybind/pybind.h. file(READ ${TARGET}.cc TARGET_CONTENT) - # It's enough to just adding one operator to pybind. - # And for detail pybind information, please see paddle/pybind/pybind.h. string(REGEX MATCH "REGISTER_OP\\(.*REGISTER_OP\\(" multi_register "${TARGET_CONTENT}") string(REGEX MATCH "REGISTER_OP\\([a-z0-9_]*," one_register "${multi_register}") if (one_register STREQUAL "") From 19541468b6a99b57a3ef130fba841fac721b75c8 Mon Sep 17 00:00:00 2001 From: dzhwinter Date: Wed, 3 Jan 2018 22:04:35 +0800 Subject: [PATCH 37/87] "fix frigled test gradient of rnn" (#7166) * "fix frigled test gradient of rnn" * "fix based on comments" --- paddle/gserver/tests/test_LayerGrad.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/paddle/gserver/tests/test_LayerGrad.cpp b/paddle/gserver/tests/test_LayerGrad.cpp index a2f07937b8..ba83667ebc 100644 --- a/paddle/gserver/tests/test_LayerGrad.cpp +++ b/paddle/gserver/tests/test_LayerGrad.cpp @@ -1472,7 +1472,8 @@ TEST(Layer, RecurrentLayer) { for (auto reversed : {false, true}) { config.layerConfig.set_reversed(reversed); config.testState = !reversed; - testLayerGrad(config, "recurrent", 50, /* trans= */ false, useGpu); + testLayerGrad( + config, "recurrent", 50, /* trans= */ false, useGpu, false, 1.0); } } } @@ -1494,7 +1495,8 @@ TEST(Layer, LstmLayer) { for (auto reversed : {false, true}) { config.layerConfig.set_reversed(reversed); config.testState = !reversed; - testLayerGrad(config, "lstmemory", 100, /* trans= */ false, useGpu); + testLayerGrad( + config, "lstmemory", 100, /* trans= */ false, useGpu, false, 0.02); } } for (auto useGpu : {true}) { From b0ba2b061e5e130b8ef6648705a6a9cadd2c3b7b Mon Sep 17 00:00:00 2001 From: tensor-tang Date: Wed, 3 Jan 2018 18:46:58 +0800 Subject: [PATCH 38/87] 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 39/87] 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 40/87] 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 41/87] 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 42/87] 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 042f3524d2327075e0d88993e8c75bb80e8d8f69 Mon Sep 17 00:00:00 2001 From: tensor-tang Date: Thu, 4 Jan 2018 11:04:27 +0800 Subject: [PATCH 43/87] add flag use_mkl_packed --- paddle/trainer/TrainerConfigHelper.cpp | 2 ++ paddle/utils/Flags.cpp | 6 ++++++ paddle/utils/Flags.h | 1 + python/paddle/v2/__init__.py | 2 ++ 4 files changed, 11 insertions(+) diff --git a/paddle/trainer/TrainerConfigHelper.cpp b/paddle/trainer/TrainerConfigHelper.cpp index a0a365aa0b..2b68d89e48 100644 --- a/paddle/trainer/TrainerConfigHelper.cpp +++ b/paddle/trainer/TrainerConfigHelper.cpp @@ -29,6 +29,7 @@ DECLARE_bool(with_gpu); DECLARE_bool(parallel_nn); DECLARE_string(config_args); DECLARE_bool(use_mkldnn); +DECLARE_bool(use_mkl_packed); const char *kConfigParserModuleName = "paddle.trainer.config_parser"; const char *kConfigParserFuncName = "parse_config_and_serialize"; @@ -46,6 +47,7 @@ TrainerConfigHelper::TrainerConfigHelper(const std::string &configFilePath) << ",with_cost=" << FLAGS_with_cost << ",use_gpu=" << FLAGS_use_gpu << ",parallel_nn=" << FLAGS_parallel_nn << ",use_mkldnn=" << FLAGS_use_mkldnn + << ",use_mkl_packed=" << FLAGS_use_mkl_packed << ",cudnn_version=" << hl_get_cudnn_lib_version(); if (!FLAGS_config_args.empty()) { configArgs << "," << FLAGS_config_args; diff --git a/paddle/utils/Flags.cpp b/paddle/utils/Flags.cpp index 9a7dc0e356..10db81cbda 100644 --- a/paddle/utils/Flags.cpp +++ b/paddle/utils/Flags.cpp @@ -27,6 +27,12 @@ DEFINE_bool(use_mkldnn, false, "Default still keep use CPU training"); DEFINE_bool(use_mkldnn, false, "Only support CPU training"); #endif +#ifdef PADDLE_WITH_MKLML +DEFINE_bool(use_mkl_packed, true, "Default use MKL Packed Optimization"); +#else +DEFINE_bool(use_mkl_packed, false, "Whether to use MKL Packed Optimization"); +#endif + DEFINE_bool(parallel_nn, false, "Whether to use multi-threads to calculate one neural network." diff --git a/paddle/utils/Flags.h b/paddle/utils/Flags.h index 1832bb515e..b64295bca0 100644 --- a/paddle/utils/Flags.h +++ b/paddle/utils/Flags.h @@ -41,3 +41,4 @@ DECLARE_string(predict_file); DECLARE_bool(prev_batch_state); DECLARE_string(init_model_path); DECLARE_bool(use_mkldnn); +DECLARE_bool(use_mkl_packed); diff --git a/python/paddle/v2/__init__.py b/python/paddle/v2/__init__.py index 70f61e8499..0de417df2c 100644 --- a/python/paddle/v2/__init__.py +++ b/python/paddle/v2/__init__.py @@ -135,6 +135,8 @@ def init(**kwargs): cp.g_command_config_args['use_gpu'] = kwargs['use_gpu'] if 'use_mkldnn' in kwargs: cp.g_command_config_args['use_mkldnn'] = kwargs['use_mkldnn'] + if 'use_mkl_packed' in kwargs: + cp.g_command_config_args['use_mkl_packed'] = kwargs['use_mkl_packed'] assert 'parallel_nn' not in kwargs, ("currently 'parallel_nn' is not " "supported in v2 APIs.") From d3f867e776c9ff4c56401ca005257c091cfa6c31 Mon Sep 17 00:00:00 2001 From: tensor-tang Date: Thu, 4 Jan 2018 11:12:16 +0800 Subject: [PATCH 44/87] enable mkl_packed_recurrent python interface --- python/paddle/trainer/config_parser.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/python/paddle/trainer/config_parser.py b/python/paddle/trainer/config_parser.py index 239fe4204b..4fdf409021 100644 --- a/python/paddle/trainer/config_parser.py +++ b/python/paddle/trainer/config_parser.py @@ -3622,8 +3622,13 @@ class ConcatenateLayer2(LayerBase): @config_layer('recurrent') class RecurrentLayer(LayerBase): + layer_type = 'recurrent' + def __init__(self, name, inputs, reversed=False, bias=True, **xargs): - super(RecurrentLayer, self).__init__(name, 'recurrent', 0, inputs, + use_mkl_packed = bool( + int(g_command_config_args.get("use_mkl_packed", 0))) + self.layer_type = 'mkl_packed_recurrent' if use_mkl_packed else 'recurrent' + super(RecurrentLayer, self).__init__(name, self.layer_type, 0, inputs, **xargs) config_assert(len(self.inputs) == 1, 'RecurrentLayer must have 1 input') input_layer = self.get_input_layer(0) From a893f156527942d9172d51ab6662748b7000d5bc Mon Sep 17 00:00:00 2001 From: dzhwinter Date: Thu, 4 Jan 2018 13:30:51 +0800 Subject: [PATCH 45/87] fix layout transform (#7149) * "fix typo" * "fix based on comments" * "follow gogle style" * "fix based on comemnts" --- paddle/framework/data_transform.cc | 36 +++++++++++++++++++------ paddle/framework/data_transform.h | 8 +++--- paddle/framework/data_transform_test.cc | 16 +++++++++-- 3 files changed, 47 insertions(+), 13 deletions(-) diff --git a/paddle/framework/data_transform.cc b/paddle/framework/data_transform.cc index 9d6a842442..ac6e40a3ae 100644 --- a/paddle/framework/data_transform.cc +++ b/paddle/framework/data_transform.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 "paddle/framework/data_transform.h" #include "paddle/framework/lod_tensor.h" @@ -74,26 +75,28 @@ void TransDataType(const platform::DeviceContext* ctx, } } -void TransDataLayout(const platform::DeviceContext* ctx, +void TransDataLayout(const std::vector& axis, + const platform::DeviceContext* ctx, const KernelTypePair& kernel_pair, const Variable& in, Variable* out) { - PADDLE_ENFORCE(in.IsType(), "Only Support Tensor transform!."); + 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!"); + "TransDataLayout only support DataLayout transform on same place!"); + PADDLE_ENFORCE(kernel_pair.first.data_type_ == kernel_pair.second.data_type_, + "TransDataLayout only support Datatype are same!"); auto src = in.Get(); auto* dst = out->GetMutable(); PADDLE_ENFORCE(arity(src.dims()) == 4, "Input Arity Only Suppport 4!"); - auto src_dim = src.dims(); - dst->Resize(src_dim); auto place = kernel_pair.second.place_; CopyFrom(src, place, *ctx, dst); - const std::vector axis = {0, 2, 3, 1}; + auto src_dim = src.dims(); std::vector dst_dim; + dst_dim.resize(axis.size()); for (size_t i = 0; i < axis.size(); i++) { dst_dim[i] = src_dim[axis[i]]; @@ -102,7 +105,7 @@ void TransDataLayout(const platform::DeviceContext* ctx, dst->Resize(make_ddim(dst_dim)); auto src_type = kernel_pair.first.data_type_; - framework::VisitDataType(src_type, CastDataLayout(src, dst, ctx, axis)); + framework::VisitDataType(src_type, CastDataLayout(ctx, axis, src, dst)); dst->set_layout(kernel_pair.second.data_layout_); } @@ -111,5 +114,22 @@ void TransDataLayout(const platform::DeviceContext* ctx, } // namespace paddle namespace f = paddle::framework; + +namespace { +std::vector NHWC2NCHW = {0, 3, 1, 2}; +std::vector NCHW2NHWC = {0, 2, 3, 1}; +} + REGISTER_DATA_TRANSFORM_FN(f::KernelFP32, f::KernelFP64, f::TransDataType); -REGISTER_DATA_TRANSFORM_FN(f::KernelNHWC, f::KernelNCHW, f::TransDataLayout); +REGISTER_DATA_TRANSFORM_FN(f::KernelNHWC, f::KernelNCHW, + std::bind(f::TransDataLayout, NHWC2NCHW, + std::placeholders::_1, + std::placeholders::_2, + std::placeholders::_3, + std::placeholders::_4)); +REGISTER_DATA_TRANSFORM_FN(f::KernelNCHW, f::KernelNHWC, + std::bind(f::TransDataLayout, NCHW2NHWC, + std::placeholders::_1, + std::placeholders::_2, + std::placeholders::_3, + std::placeholders::_4)); diff --git a/paddle/framework/data_transform.h b/paddle/framework/data_transform.h index 9abb3c99bf..56ebc80f43 100644 --- a/paddle/framework/data_transform.h +++ b/paddle/framework/data_transform.h @@ -73,6 +73,7 @@ struct CastDataType { auto numel = in_.numel(); auto* in_end = in_begin + numel; auto* out_begin = out_->mutable_data(place); + if (platform::is_cpu_place(place)) { platform::Transform trans; auto* context = static_cast(ctx_); @@ -86,9 +87,9 @@ struct CastDataType { }; struct CastDataLayout { - CastDataLayout(const framework::Tensor& in, framework::Tensor* out, - const platform::DeviceContext* ctx, - const std::vector& axis) + CastDataLayout(const platform::DeviceContext* ctx, + const std::vector& axis, const framework::Tensor& in, + framework::Tensor* out) : in_(in), out_(out), ctx_(ctx), axis_(axis) {} const framework::Tensor in_; framework::Tensor* out_; @@ -98,6 +99,7 @@ struct CastDataLayout { template void operator()() { auto place = ctx_->GetPlace(); + if (platform::is_cpu_place(place)) { operators::math::Transpose trans4; auto* context = static_cast(ctx_); diff --git a/paddle/framework/data_transform_test.cc b/paddle/framework/data_transform_test.cc index 8665b6248f..edd305fd17 100644 --- a/paddle/framework/data_transform_test.cc +++ b/paddle/framework/data_transform_test.cc @@ -106,7 +106,7 @@ TEST(DataTransform, Register) { ASSERT_EQ(test_value, 2); } -TEST(DataTransform, Layout) { +TEST(DataTransform, DataLayout) { using namespace paddle::framework; using namespace paddle::platform; @@ -127,7 +127,19 @@ TEST(DataTransform, Layout) { } Tensor dst = out.Get(); - EXPECT_TRUE(dst.layout() != src->layout()); + + EXPECT_TRUE(dst.layout() == DataLayout::kNCHW); + EXPECT_TRUE(dst.dims() == make_ddim({2, 2, 3, 1})); + + { + auto kernel1 = GenFromBit({1, 0, 1, 0}); + auto kernel2 = GenFromBit({1, 0, 0, 0}); + auto pair0 = std::make_pair(kernel1, kernel2); + instance.Get(pair0)(ctx, pair0, out, &in); + } + + EXPECT_TRUE(src->layout() == DataLayout::kNHWC); + EXPECT_TRUE(src->dims() == make_ddim({2, 3, 1, 2})); } TEST(DataTransform, DataType) { From 8ae84a5745591a2937d3004831050f8fe77ce1ef Mon Sep 17 00:00:00 2001 From: Yang Yu Date: Thu, 4 Jan 2018 14:47:35 +0800 Subject: [PATCH 46/87] Async to drop kid --- paddle/framework/scope.cc | 4 +++- paddle/framework/threadpool.h | 9 +++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/paddle/framework/scope.cc b/paddle/framework/scope.cc index 0c01d605bc..4e80e3d974 100644 --- a/paddle/framework/scope.cc +++ b/paddle/framework/scope.cc @@ -17,6 +17,7 @@ limitations under the License. */ #include // for unique_ptr #include // for call_once #include "glog/logging.h" +#include "paddle/framework/threadpool.h" #include "paddle/string/printf.h" namespace paddle { @@ -87,7 +88,8 @@ void Scope::DeleteScope(Scope* scope) { auto it = std::find(this->kids_.begin(), this->kids_.end(), scope); PADDLE_ENFORCE(it != this->kids_.end(), "Cannot find %p as kid scope", scope); this->kids_.erase(it); - delete scope; + // Make delete async. + Async([scope] { delete scope; }); } void Scope::Rename(const std::string& origin_name, diff --git a/paddle/framework/threadpool.h b/paddle/framework/threadpool.h index bcd8190755..c644e7d296 100644 --- a/paddle/framework/threadpool.h +++ b/paddle/framework/threadpool.h @@ -159,5 +159,14 @@ class ThreadPool { std::condition_variable completed_; }; +// Run a function asynchronously. +// NOTE: The function must return void. If the function need to return a value, +// you can use lambda to capture a value pointer. +template +std::future Async(Callback callback, ARGS... args) { + return ThreadPool::GetInstance()->Run( + [&] { callback(std::forward(args)...); }); +}; + } // namespace framework } // namespace paddle From e138bcf45080a0288293197195d453283c1396bf Mon Sep 17 00:00:00 2001 From: Yang Yu Date: Thu, 4 Jan 2018 15:16:14 +0800 Subject: [PATCH 47/87] Update cmake of scope --- paddle/framework/CMakeLists.txt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/paddle/framework/CMakeLists.txt b/paddle/framework/CMakeLists.txt index b4458eb955..fb8c9ab96d 100644 --- a/paddle/framework/CMakeLists.txt +++ b/paddle/framework/CMakeLists.txt @@ -26,7 +26,10 @@ nv_test(lod_tensor_gpu_test SRCS lod_tensor_test.cu DEPS lod_tensor) cc_test(variable_test SRCS variable_test.cc) -cc_library(scope SRCS scope.cc DEPS glog) +cc_library(threadpool SRCS threadpool.cc) +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) @@ -70,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(threadpool SRCS threadpool.cc) -cc_test(threadpool_test SRCS threadpool_test.cc DEPS threadpool) + cc_library(init SRCS init.cc DEPS gflags device_context place stringpiece) cc_test(init_test SRCS init_test.cc DEPS init) From 7e10b8181804295ddecf9b10201bb12e2d88f745 Mon Sep 17 00:00:00 2001 From: Yang Yu Date: Thu, 4 Jan 2018 15:17:53 +0800 Subject: [PATCH 48/87] Fix style check --- paddle/framework/threadpool.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paddle/framework/threadpool.h b/paddle/framework/threadpool.h index 31c12e9e1f..3ac345851c 100644 --- a/paddle/framework/threadpool.h +++ b/paddle/framework/threadpool.h @@ -165,7 +165,7 @@ class ThreadPool { template std::future Async(Callback callback) { return ThreadPool::GetInstance()->Run(callback); -}; +} } // namespace framework } // namespace paddle From f9ef6d15190604e4a0780e76c7254a7875e7352e Mon Sep 17 00:00:00 2001 From: peterzhang2029 Date: Thu, 4 Jan 2018 16:11:50 +0800 Subject: [PATCH 49/87] 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 3b5e4e0a83e0229a70a8ef232d41f01e5c965896 Mon Sep 17 00:00:00 2001 From: tensor-tang Date: Thu, 4 Jan 2018 16:15:52 +0800 Subject: [PATCH 50/87] default disable use_mkl_packed --- paddle/utils/Flags.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/paddle/utils/Flags.cpp b/paddle/utils/Flags.cpp index 10db81cbda..ea47cf23eb 100644 --- a/paddle/utils/Flags.cpp +++ b/paddle/utils/Flags.cpp @@ -28,9 +28,10 @@ DEFINE_bool(use_mkldnn, false, "Only support CPU training"); #endif #ifdef PADDLE_WITH_MKLML -DEFINE_bool(use_mkl_packed, true, "Default use MKL Packed Optimization"); -#else +// TODO(TJ): change to true when fully confirmed DEFINE_bool(use_mkl_packed, false, "Whether to use MKL Packed Optimization"); +#else +DEFINE_bool(use_mkl_packed, false, "Not to use MKL Packed Optimization"); #endif DEFINE_bool(parallel_nn, From c10d5d2aa16b23cc45e01cc399bfa8fbfd39e387 Mon Sep 17 00:00:00 2001 From: peterzhang2029 Date: Thu, 4 Jan 2018 16:50:18 +0800 Subject: [PATCH 51/87] 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 b585c93ab013f0c0fd860f1cee0b0cc4e4760398 Mon Sep 17 00:00:00 2001 From: Yang Yu Date: Thu, 4 Jan 2018 17:36:52 +0800 Subject: [PATCH 52/87] Default use one thread in fluid --- python/paddle/v2/fluid/__init__.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/python/paddle/v2/fluid/__init__.py b/python/paddle/v2/fluid/__init__.py index e115529665..5e01b87198 100644 --- a/python/paddle/v2/fluid/__init__.py +++ b/python/paddle/v2/fluid/__init__.py @@ -1,3 +1,4 @@ +from __future__ import print_function # import all class inside framework into fluid module import framework from framework import * @@ -36,6 +37,24 @@ def __bootstrap__(): """ import sys import core + import os + + try: + num_threads = int(os.getenv('OMP_NUM_THREADS', '1')) + except ValueError: + num_threads = 1 + + if num_threads > 1: + print( + 'WARNING: OMP_NUM_THREADS set to {0}, not 1. The computation ' + 'speed will not be optimized if you use data parallel. It will ' + 'fail if this PaddlePaddle binary is compiled with OpenBlas since' + ' OpenBlas does not support multi-threads.'.format(num_threads), + file=sys.stderr) + print('PLEASE USE OMP_NUM_THREADS WISELY.', file=sys.stderr) + + os.environ['OMP_NUM_THREADS'] = str(num_threads) + read_env_flags = ['use_pinned_memory', 'check_nan_inf'] if core.is_compile_gpu(): read_env_flags.append('fraction_of_gpu_memory_to_use') From a4024a5f3dc852bc955f8c4c7aa30750d8da4c6f Mon Sep 17 00:00:00 2001 From: dzhwinter Date: Thu, 4 Jan 2018 18:36:51 +0800 Subject: [PATCH 53/87] "remove cudnn devicecontext" (#7207) * "remove cudnndevicecontext" * "remove unused init code" * "fix hash functions" --- doc/design/support_new_device.md | 14 ++------------ paddle/framework/library_type.h | 4 ++++ paddle/framework/op_kernel_type.h | 13 ++++++------- paddle/platform/device_context.cc | 26 +++++++++----------------- paddle/platform/device_context.h | 14 +------------- paddle/platform/device_context_test.cu | 15 --------------- python/paddle/v2/fluid/executor.py | 7 ------- 7 files changed, 22 insertions(+), 71 deletions(-) diff --git a/doc/design/support_new_device.md b/doc/design/support_new_device.md index f54b2b3694..4c5f10e2ec 100644 --- a/doc/design/support_new_device.md +++ b/doc/design/support_new_device.md @@ -48,8 +48,8 @@ Fluid uses class [DeviceContext](https://github.com/PaddlePaddle/Paddle/blob/dev ``` - /-> CPUDeviceContext --> MKLDeviceContext -DeviceContext ----> CUDADeviceContext --> CUDNNDeviceContext + /-> CPUDeviceContext +DeviceContext ----> CUDADeviceContext \-> FPGADeviceContext ``` @@ -79,16 +79,6 @@ private: }; ``` -- CUDNNDeviceContext - -``` -class CUDNNDeviceContext : public CUDADeviceContext { - private: - cudnnHandle_t cudnn_handle_; -}; -``` - - ### Memory and Tensor diff --git a/paddle/framework/library_type.h b/paddle/framework/library_type.h index 7707799cae..1e30848354 100644 --- a/paddle/framework/library_type.h +++ b/paddle/framework/library_type.h @@ -13,6 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. */ #pragma once +#include namespace paddle { namespace framework { @@ -41,6 +42,9 @@ inline std::string LibraryTypeToString(const LibraryType& library_type) { inline LibraryType StringToLibraryType(const char* ctype) { std::string s(ctype); + for (size_t i = 0; i < s.size(); ++i) { + s[i] = toupper(s[i]); + } if (s == std::string("PLAIN")) { return LibraryType::kPlain; } else if (s == std::string("MKLDNN")) { diff --git a/paddle/framework/op_kernel_type.h b/paddle/framework/op_kernel_type.h index b06002096f..053897784c 100644 --- a/paddle/framework/op_kernel_type.h +++ b/paddle/framework/op_kernel_type.h @@ -26,13 +26,12 @@ namespace framework { struct OpKernelType { struct Hash { size_t operator()(const OpKernelType& key) const { - int place = key.place_.which() + (1 << LEFT_SHIFT); - int data_type = - static_cast(key.data_type_) + (1 << (LEFT_SHIFT + 1)); - int data_layout = - static_cast(key.data_layout_) + (1 << (LEFT_SHIFT + 2)); - int library_type = - static_cast(key.library_type_) + (1 << (LEFT_SHIFT + 3)); + int place = key.place_.which(); + int data_type = static_cast(key.data_type_) << LEFT_SHIFT; + int data_layout = static_cast(key.data_layout_) << (LEFT_SHIFT * 2); + int library_type = static_cast(key.library_type_) + << (LEFT_SHIFT * 3); + std::hash hasher; return hasher(place + data_type + data_layout + library_type); } diff --git a/paddle/platform/device_context.cc b/paddle/platform/device_context.cc index ea07f2e002..4bf643e048 100644 --- a/paddle/platform/device_context.cc +++ b/paddle/platform/device_context.cc @@ -127,15 +127,21 @@ CUDADeviceContext::CUDADeviceContext(CUDAPlace place) : place_(place) { eigen_device_.reset(new Eigen::GpuDevice(eigen_stream_.get())); PADDLE_ENFORCE(dynload::cublasCreate(&cublas_handle_)); PADDLE_ENFORCE(dynload::cublasSetStream(cublas_handle_, stream_)); - PADDLE_ENFORCE(dynload::cudnnCreate(&cudnn_handle_)); - PADDLE_ENFORCE(dynload::cudnnSetStream(cudnn_handle_, stream_)); + if (dynload::HasCUDNN()) { + PADDLE_ENFORCE(dynload::cudnnCreate(&cudnn_handle_)); + PADDLE_ENFORCE(dynload::cudnnSetStream(cudnn_handle_, stream_)); + } else { + cudnn_handle_ = nullptr; + } } CUDADeviceContext::~CUDADeviceContext() { SetDeviceId(place_.device); Wait(); PADDLE_ENFORCE(dynload::cublasDestroy(cublas_handle_)); - PADDLE_ENFORCE(dynload::cudnnDestroy(cudnn_handle_)); + if (cudnn_handle_ != nullptr) { + PADDLE_ENFORCE(dynload::cudnnDestroy(cudnn_handle_)); + } eigen_stream_.reset(); eigen_device_.reset(); PADDLE_ENFORCE(cudaStreamDestroy(stream_)); @@ -160,20 +166,6 @@ cudnnHandle_t CUDADeviceContext::cudnn_handle() const { return cudnn_handle_; } cudaStream_t CUDADeviceContext::stream() const { return stream_; } -CUDNNDeviceContext::CUDNNDeviceContext(CUDAPlace place) - : CUDADeviceContext(place) { - PADDLE_ENFORCE(dynload::cudnnCreate(&cudnn_handle_)); - PADDLE_ENFORCE(dynload::cudnnSetStream(cudnn_handle_, stream())); -} - -CUDNNDeviceContext::~CUDNNDeviceContext() { - SetDeviceId(boost::get(GetPlace()).device); - Wait(); - PADDLE_ENFORCE(dynload::cudnnDestroy(cudnn_handle_)); -} - -cudnnHandle_t CUDNNDeviceContext::cudnn_handle() const { return cudnn_handle_; } - #endif } // namespace platform diff --git a/paddle/platform/device_context.h b/paddle/platform/device_context.h index 2b366e6383..609ea4bd3a 100644 --- a/paddle/platform/device_context.h +++ b/paddle/platform/device_context.h @@ -103,18 +103,6 @@ struct DefaultDeviceContextType { using TYPE = CUDADeviceContext; }; -class CUDNNDeviceContext : public CUDADeviceContext { - public: - explicit CUDNNDeviceContext(CUDAPlace place); - virtual ~CUDNNDeviceContext(); - - /*! \brief Return cudnn handle in the device context. */ - cudnnHandle_t cudnn_handle() const; - - private: - cudnnHandle_t cudnn_handle_; -}; - #endif /*! \brief device context pool singleton */ @@ -151,7 +139,7 @@ class DeviceContextPool { struct Hash { std::hash hash_; size_t operator()(const platform::Place& place) const { - int pre_hash = place.which() + (1 << LEFT_SHIFT); + int pre_hash = place.which() << LEFT_SHIFT; if (platform::is_gpu_place(place)) { pre_hash += boost::get(place).GetDeviceId(); } diff --git a/paddle/platform/device_context_test.cu b/paddle/platform/device_context_test.cu index ca10cf3463..767fe9b24a 100644 --- a/paddle/platform/device_context_test.cu +++ b/paddle/platform/device_context_test.cu @@ -49,21 +49,6 @@ TEST(Device, CUDADeviceContext) { } } -TEST(Device, CUDNNDeviceContext) { - using paddle::platform::CUDNNDeviceContext; - using paddle::platform::CUDAPlace; - if (paddle::platform::dynload::HasCUDNN()) { - int count = paddle::platform::GetCUDADeviceCount(); - for (int i = 0; i < count; ++i) { - CUDNNDeviceContext* device_context = new CUDNNDeviceContext(CUDAPlace(i)); - cudnnHandle_t cudnn_handle = device_context->cudnn_handle(); - ASSERT_NE(nullptr, cudnn_handle); - ASSERT_NE(nullptr, device_context->stream()); - delete device_context; - } - } -} - TEST(Device, DeviceContextPool) { using paddle::platform::DeviceContextPool; using paddle::platform::CUDADeviceContext; diff --git a/python/paddle/v2/fluid/executor.py b/python/paddle/v2/fluid/executor.py index 1d6c594b41..1b2075dcd5 100644 --- a/python/paddle/v2/fluid/executor.py +++ b/python/paddle/v2/fluid/executor.py @@ -65,13 +65,6 @@ class Executor(object): p.set_place(each) act_places.append(p) - # TODO(dzhwinter) : consider that our fluid tests all written in - # CUDAPlace(gpu_id), this will be changed in the future - if core.is_compile_gpu(): - core.init_devices(["CPU", "GPU:0"]) - else: - core.init_devices(["CPU"]) - # TODO(dzhwinter) : only use the first place self.executor = core.Executor(act_places[0]) self.places = places From c7bd77792edd5d5e9b33c98ba2c99d9f5c3e6188 Mon Sep 17 00:00:00 2001 From: Liu Yiqun Date: Thu, 4 Jan 2018 11:59:48 +0000 Subject: [PATCH 54/87] Support the link of inference library on mac. --- paddle/inference/CMakeLists.txt | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/paddle/inference/CMakeLists.txt b/paddle/inference/CMakeLists.txt index 73c875409a..8437b2b219 100644 --- a/paddle/inference/CMakeLists.txt +++ b/paddle/inference/CMakeLists.txt @@ -30,7 +30,18 @@ if(PTOOLS_INC_DIR AND PTOOLS_SHARED_LIB) endif() add_executable(example example.cc) -target_link_libraries(example - -Wl,--start-group -Wl,--whole-archive paddle_fluid - -Wl,--no-whole-archive -Wl,--end-group - ${PTOOLS_LIB}) +if(APPLE) + set(OPTIONAL_LINK_FLAGS) + if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang" OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "AppleClang") + set(OPTIONAL_LINK_FLAGS "-undefined dynamic_lookup") + endif() + target_link_libraries(example + -Wl,-force_load paddle_fluid + ${OPTIONAL_LINK_FLAGS} + ${PTOOLS_LIB}) +else() + target_link_libraries(example + -Wl,--start-group -Wl,--whole-archive paddle_fluid + -Wl,--no-whole-archive -Wl,--end-group + ${PTOOLS_LIB}) +endif() From 040dc59b0f3558baee90627936716823569700d5 Mon Sep 17 00:00:00 2001 From: Yang Yu Date: Thu, 4 Jan 2018 21:57:16 +0800 Subject: [PATCH 55/87] Correctly handle image operators --- paddle/operators/batch_norm_op.cc | 3 ++- paddle/operators/conv_op.cc | 7 +++---- paddle/operators/pool_op.cc | 1 + 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/paddle/operators/batch_norm_op.cc b/paddle/operators/batch_norm_op.cc index 98db28ddee..dd7b038b00 100644 --- a/paddle/operators/batch_norm_op.cc +++ b/paddle/operators/batch_norm_op.cc @@ -64,7 +64,7 @@ class BatchNormOp : public framework::OperatorWithKernel { PADDLE_ENFORCE(x_dims.size() >= 2 && x_dims.size() <= 5, "Input X must have 2 to 5 dimensions."); - const int C = + const int64_t C = (data_layout == DataLayout::kNCHW ? x_dims[1] : x_dims[x_dims.size() - 1]); @@ -78,6 +78,7 @@ class BatchNormOp : public framework::OperatorWithKernel { ctx->SetOutputDim("VarianceOut", {C}); ctx->SetOutputDim("SavedMean", {C}); ctx->SetOutputDim("SavedVariance", {C}); + ctx->ShareLoD("X", "Y"); } }; diff --git a/paddle/operators/conv_op.cc b/paddle/operators/conv_op.cc index e65a5dce52..ad84524e17 100644 --- a/paddle/operators/conv_op.cc +++ b/paddle/operators/conv_op.cc @@ -44,14 +44,12 @@ void ConvOp::InferShape(framework::InferShapeContext* ctx) const { paddings.size(), strides.size(), "Conv paddings dimension and Conv strides dimension should be the same."); - int input_channels = in_dims[1]; - PADDLE_ENFORCE_EQ(input_channels, filter_dims[1] * groups, + PADDLE_ENFORCE_EQ(in_dims[1], filter_dims[1] * groups, "The number of input channels should be equal to filter " "channels * groups."); - int output_channels = filter_dims[0]; PADDLE_ENFORCE_EQ( - output_channels % groups, 0, + filter_dims[0] % groups, 0, "The number of output channels should be divided by groups."); std::vector output_shape({in_dims[0], filter_dims[0]}); @@ -66,6 +64,7 @@ void ConvOp::InferShape(framework::InferShapeContext* ctx) const { dilations[i], paddings[i], strides[i])); } ctx->SetOutputDim("Output", framework::make_ddim(output_shape)); + ctx->ShareLoD("Input", "Output"); } Conv2DOpMaker::Conv2DOpMaker(OpProto* proto, OpAttrChecker* op_checker) diff --git a/paddle/operators/pool_op.cc b/paddle/operators/pool_op.cc index 50057eb648..d3cf5fa638 100644 --- a/paddle/operators/pool_op.cc +++ b/paddle/operators/pool_op.cc @@ -58,6 +58,7 @@ void PoolOp::InferShape(framework::InferShapeContext *ctx) const { OutputSizePool(in_x_dims[i + 2], ksize[i], paddings[i], strides[i])); } ctx->SetOutputDim("Out", framework::make_ddim(output_shape)); + ctx->ShareLoD("X", "Out"); } void PoolOpGrad::InferShape(framework::InferShapeContext *ctx) const { From ebc616b0f9bb72d17668478b70a0df8a0a979771 Mon Sep 17 00:00:00 2001 From: emailweixu Date: Thu, 4 Jan 2018 11:08:07 -0800 Subject: [PATCH 56/87] 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 f3c42f607c91f7fe6c9c3b04151baab60e92c16c Mon Sep 17 00:00:00 2001 From: Siddharth Goyal Date: Thu, 4 Jan 2018 13:52:04 -0800 Subject: [PATCH 57/87] Add doc for gru_unit op (in fluid) (#7151) * Add squared error layers doc * Add doc for gru_unit * Remove cdot which isn't supported * Update layers.rst * Update layers.rst (minor) --- doc/api/v2/fluid/layers.rst | 6 ++++ python/paddle/v2/fluid/layers/nn.py | 48 ++++++++++++++++++++++------- 2 files changed, 43 insertions(+), 11 deletions(-) diff --git a/doc/api/v2/fluid/layers.rst b/doc/api/v2/fluid/layers.rst index 004ee2d8c8..a7c8670f66 100644 --- a/doc/api/v2/fluid/layers.rst +++ b/doc/api/v2/fluid/layers.rst @@ -307,6 +307,12 @@ sequence_expand :noindex: +gru_unit +-------- +.. autofunction:: paddle.v2.fluid.layers.gru_unit + :noindex: + + lstm_unit --------- .. autofunction:: paddle.v2.fluid.layers.lstm_unit diff --git a/python/paddle/v2/fluid/layers/nn.py b/python/paddle/v2/fluid/layers/nn.py index 1c1c09dd28..6883630ac6 100644 --- a/python/paddle/v2/fluid/layers/nn.py +++ b/python/paddle/v2/fluid/layers/nn.py @@ -236,21 +236,47 @@ def gru_unit(input, activation='tanh', gate_activation='sigmoid'): """ - GRUUnit Operator implements partial calculations of the GRU unit as following: + GRU unit layer. The equation of a gru step is: - $$ - update \ gate: u_t = actGate(xu_t + W_u * h_{t-1} + b_u) \\ - reset \ gate: r_t = actGate(xr_t + W_r * h_{t-1} + b_r) \\ - output \ candidate: {h}_t = actNode(xc_t + W_c * dot(r_t, h_{t-1}) + b_c) \\ - output: h_t = dot((1 - u_t), h_{t-1}) + dot(u_t, {h}_t) - $$ + .. math:: + u_t & = actGate(xu_{t} + W_u h_{t-1} + b_u) + + 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) + + h_t & = dot((1-u_t), ch_{t-1}) + dot(u_t, h_t) - which is same as one time step of GRU Operator. + 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 + 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`. + + Args: + input (Variable): The fc transformed input value of current step. + hidden (Variable): The hidden value of lstm unit from previous step. + size (integer): The input dimension value. + weight (ParamAttr): The weight parameters for gru unit. Default: None + bias (ParamAttr): The bias parameters for gru unit. Default: None + activation (string): The activation type for cell (actNode). Default: 'tanh' + gate_activation (string): The activation type for gates (actGate). Default: 'sigmoid' + + Returns: + tuple: The hidden value, reset-hidden value and gate values. + + Examples: + + .. code-block:: python - @note To implement the complete GRU unit, fully-connected operator must be - used before to feed xu, xr and xc as the Input of GRUUnit operator. + # assuming we have x_t_data and prev_hidden of size=10 + x_t = fluid.layers.fc(input=x_t_data, size=30) + hidden_val, r_h_val, gate_val = fluid.layers.gru_unit(input=x_t, + hidden = prev_hidden) - TODO(ChunweiYan) add more document here """ activation_dict = dict( identity=0, From 0f4410755fa2fd333aaf1f009388773b0c7bbd6c Mon Sep 17 00:00:00 2001 From: Yibing Liu Date: Fri, 5 Jan 2018 03:38:12 +0000 Subject: [PATCH 58/87] 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 7508d52b3c532208d14b96465084107288e2f461 Mon Sep 17 00:00:00 2001 From: QI JUN Date: Fri, 5 Jan 2018 13:03:08 +0800 Subject: [PATCH 59/87] add memory optimization design doc (#7206) --- doc/design/images/control_flow_graph.png | Bin 0 -> 85311 bytes doc/design/images/dataflow_equations.png | Bin 0 -> 23064 bytes doc/design/images/deep_learning.png | Bin 0 -> 40605 bytes doc/design/memory_optimization.md | 217 +++++++++++++++++++++++ 4 files changed, 217 insertions(+) create mode 100644 doc/design/images/control_flow_graph.png create mode 100644 doc/design/images/dataflow_equations.png create mode 100644 doc/design/images/deep_learning.png create mode 100644 doc/design/memory_optimization.md diff --git a/doc/design/images/control_flow_graph.png b/doc/design/images/control_flow_graph.png new file mode 100644 index 0000000000000000000000000000000000000000..3579998e58d07abc50bd3332128d4733a391cb3b GIT binary patch literal 85311 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

2lgXYv@-%QDC?iiVxEc}XZsaKW-@ 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 NxpwDj)fIcp{{g{6PO$(0 literal 0 HcmV?d00001 diff --git a/doc/design/images/deep_learning.png b/doc/design/images/deep_learning.png new file mode 100644 index 0000000000000000000000000000000000000000..026becc4d94e01e407dacb2a5314a0e5723334ff GIT binary patch literal 40605 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�*v>KdtmW{i2{ic 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 6 and 5 --> 2, and *succ[5]* = {2, 6}. The in-edges of 2 are 5 --> 2 and 1 --> 2, and *pred[2]* = {1, 5}. + +- Uses and Defs + +An assignmemt to a variable or temporary defines that variable. An occurence of a variable on the right-hand side of an assginment(or in other expressions) uses the variable. We can speak the *def* of a variable as the set of graph nodes that define it; or the *def* of a graph node as the set of variables that it defines; and the similarly for the *use* of a variable or graph node. In former control flow graph, *def(3)* = {c}, *use(3)* = {b, c}. + +- Liveness + +A variable is *live* on an edge if there is a directed path from that edge to a *use* of the variable that does not go through any *def*. A variable is *live-in* at a node if it is live on any of the in-edges of that node; it is *live-out* at a node if it is live on any of the out-edges of the node. + + +The calcution of liveness can be solved by iteration until a fixed pointer is reached. Following is the recursive formula: + +![](images/dataflow_equations.png) + +### Memory optimization transpiler + +At last, we take basic strategy and liveness analysis techniques learning from compilers to implement our memory optimization transpiler. + +#### add in-place attribute + +In-place is a built-in attribute of an operator. Since we treat in-place and other operators differently, we have to add an in-place attribute for every operator. + + +#### contruct control flow graph + +Following is the ProgramDesc protobuf of [machine translation](https://github.com/PaddlePaddle/Paddle/blob/develop/python/paddle/v2/fluid/tests/book/test_machine_translation.py) example. + +- Block0: + +``` +lookup_table +mul +... +while(sub-block idx 1) +... +array_to_lod_tensor +cross_entropy +... +while_grad(sub-block idx 2) +read_from_array +array_to_lod_tensor +... +``` + +- Block1 + +``` +read_from_array +read_from_array +... +write_to_array +increment +write_to_array +less_than +``` + +- Block2 + +``` +read_from_array +increment +... +write_to_array +write_to_array +``` + +We can transfer all the operators and variables in ProgramDesc to build a control flow graph. + +```python +class ControlFlowGraph(object): + def __init__(self, Program): + self._sucessors = defaultdict(set) + self._presucessors = defaultdict(set) + self._uses = defaultdict(set) + self._defs = defaultdict(set) + self._live_in = defaultdict(set) + self._live_out = defaultdict(set) + self._program = Program + + def build(self): + pass + + def dataflow_analysis(self): + pass + + def memory_optimization(self): + pass + + def get_program(self): + return self._program +``` + +#### make dataflow analysis + +We follow guide from compilers and try to solve the dataflow equation to get liveness of every variable. If the live-in of an operator node is different from the live-out, then we can make memory sharing. + +For example: + +``` +a = op1(b, c); +d = op2(a) +e = op3(d, f) +``` + +The dataflow analysis result is: + +``` +live_in(op1) = {b, c, f} +live_out(op1) = {a, f} + +live_in(op2) = {a, f} +live_out(op2) = {d, f} + +live_in(op3) = {d, f} +live_out(op3) = {} +``` + +After op1, we can process variable b and variable c; After op2, we can process variable a. After op3, we can process variable d and variable f. + +#### memory sharing policy + +A memory pool will be mantained in the stage of memory optimization. Each operator node will be scanned to determine memory optimization is done or not. If an operator satifies the requirement, following policy will be taken to handle input/output variables. + +``` +if op.support_inplace(): + i --> pool + pool --> o +else: + pool --> o + i --> pool +``` + + + +## Reference + +- [Lecture Notes From Artificial Intelligence Is The New Electricity By Andrew Ng](https://manavsehgal.com/lecture-notes-from-artificial-intelligence-is-the-new-electricity-by-andrew-ng-4712dcbf26e5) +- Modern compiler implementation in ML, by Andrew W. Appel +- [Optimizing Memory Consumption in Deep learning](https://mxnet.incubator.apache.org/architecture/note_memory.html) From e5fe8935fb8aedd8b7ecd63136bba56e02dbd96c Mon Sep 17 00:00:00 2001 From: Yancey Date: Fri, 5 Jan 2018 13:41:34 +0800 Subject: [PATCH 60/87] send_recv variables (#7161) * send_recv variable * delete unused logs * fix ci failed * update * resize tensor before tensor copy * add selectedrows unit test * check rows --- paddle/framework/lod_tensor.cc | 7 +- paddle/framework/lod_tensor.h | 3 +- paddle/framework/lod_tensor_test.cc | 2 +- paddle/framework/selected_rows.cc | 6 +- paddle/framework/selected_rows.h | 3 +- paddle/framework/selected_rows_test.cc | 4 +- paddle/framework/tensor_util.h | 57 ++++++--- paddle/framework/tensor_util_test.cc | 6 +- paddle/framework/var_type.h | 4 +- paddle/operators/detail/recv_impl.cc | 19 +-- paddle/operators/detail/send_impl.cc | 18 +-- paddle/operators/detail/send_recv.proto | 18 +-- paddle/operators/detail/send_recv_impl.h | 57 ++++++++- paddle/operators/load_op.cc | 2 +- paddle/operators/recv_op.cc | 13 +- paddle/operators/send_recv_op_test.cc | 148 +++++++++++++++++------ 16 files changed, 249 insertions(+), 118 deletions(-) diff --git a/paddle/framework/lod_tensor.cc b/paddle/framework/lod_tensor.cc index 92b3d7fccd..8f6944c241 100644 --- a/paddle/framework/lod_tensor.cc +++ b/paddle/framework/lod_tensor.cc @@ -217,9 +217,10 @@ void SerializeToStream(std::ostream &os, const LoDTensor &tensor, SerializeToStream(os, static_cast(tensor), dev_ctx); } -void DeserializeFromStream(std::istream &is, LoDTensor *tensor) { +void DeserializeFromStream(std::istream &is, LoDTensor *tensor, + const platform::DeviceContext &dev_ctx) { { - // the 1st field, unit32_t version for SelectedRows + // the 1st field, unit32_t version for LoDTensor uint32_t version; is.read(reinterpret_cast(&version), sizeof(version)); PADDLE_ENFORCE_EQ(version, 0U, "Only version 0 is supported"); @@ -240,7 +241,7 @@ void DeserializeFromStream(std::istream &is, LoDTensor *tensor) { } } // the 3st filed, Tensor - DeserializeFromStream(is, static_cast(tensor)); + DeserializeFromStream(is, static_cast(tensor), dev_ctx); } } // namespace framework diff --git a/paddle/framework/lod_tensor.h b/paddle/framework/lod_tensor.h index 147db3ab08..d0b6befffe 100644 --- a/paddle/framework/lod_tensor.h +++ b/paddle/framework/lod_tensor.h @@ -208,7 +208,8 @@ void AppendLoD(LoD* lod, const LoD& lod_length); */ void SerializeToStream(std::ostream& os, const LoDTensor& tensor, const platform::DeviceContext& dev_ctx); -void DeserializeFromStream(std::istream& is, LoDTensor* tensor); +void DeserializeFromStream(std::istream& is, LoDTensor* tensor, + const platform::DeviceContext& dev_ctx); } // namespace framework } // namespace paddle diff --git a/paddle/framework/lod_tensor_test.cc b/paddle/framework/lod_tensor_test.cc index 0747c8db53..0868c1f6e6 100644 --- a/paddle/framework/lod_tensor_test.cc +++ b/paddle/framework/lod_tensor_test.cc @@ -132,7 +132,7 @@ TEST_F(LoDTensorTester, SerializeAndDeserialize) { std::ostringstream oss; SerializeToStream(oss, lod_tensor_, cpu_ctx); std::istringstream iss(oss.str()); - DeserializeFromStream(iss, &dst_tensor); + DeserializeFromStream(iss, &dst_tensor, cpu_ctx); float* dst_ptr = dst_tensor.mutable_data(platform::CPUPlace()); for (int i = 0; i < kLodTensorSize; ++i) { EXPECT_EQ(dst_ptr[i], i); diff --git a/paddle/framework/selected_rows.cc b/paddle/framework/selected_rows.cc index 82adfa7123..3b3e60177a 100644 --- a/paddle/framework/selected_rows.cc +++ b/paddle/framework/selected_rows.cc @@ -37,8 +37,8 @@ void SerializeToStream(std::ostream& os, const SelectedRows& selected_rows, SerializeToStream(os, selected_rows.value(), dev_ctx); } -void DeserializeFromStream(std::istream& is, SelectedRows* selected_rows) { - auto tensor = *selected_rows->mutable_value(); +void DeserializeFromStream(std::istream& is, SelectedRows* selected_rows, + const platform::DeviceContext& dev_ctx) { { // the 1st field, unit32_t version for SelectedRows uint32_t version; @@ -62,7 +62,7 @@ void DeserializeFromStream(std::istream& is, SelectedRows* selected_rows) { selected_rows->set_height(height); } // the 4st field, tensor which contains the data - DeserializeFromStream(is, &tensor); + DeserializeFromStream(is, selected_rows->mutable_value(), dev_ctx); } } // namespace framework diff --git a/paddle/framework/selected_rows.h b/paddle/framework/selected_rows.h index 699e392688..30d3dfc1e8 100644 --- a/paddle/framework/selected_rows.h +++ b/paddle/framework/selected_rows.h @@ -66,7 +66,8 @@ class SelectedRows { */ void SerializeToStream(std::ostream& os, const SelectedRows& selected_rows, const platform::DeviceContext& dev_ctx); -void DeserializeFromStream(std::istream& is, SelectedRows* selected_rows); +void DeserializeFromStream(std::istream& is, SelectedRows* selected_rows, + const platform::DeviceContext& dev_ctx); } // namespace framework } // namespace paddle diff --git a/paddle/framework/selected_rows_test.cc b/paddle/framework/selected_rows_test.cc index 75487c4010..8ff3fb6a97 100644 --- a/paddle/framework/selected_rows_test.cc +++ b/paddle/framework/selected_rows_test.cc @@ -51,10 +51,12 @@ TEST_F(SelectedRowsTester, SerializeAndDeseralize) { SerializeToStream(oss, *selected_rows_, cpu_ctx); std::istringstream iss(oss.str()); - DeserializeFromStream(iss, &dst_tensor); + DeserializeFromStream(iss, &dst_tensor, cpu_ctx); ASSERT_EQ(selected_rows_->rows(), dst_tensor.rows()); ASSERT_EQ(selected_rows_->height(), dst_tensor.height()); + ASSERT_EQ(selected_rows_->value().dims(), dst_tensor.value().dims()); + ASSERT_EQ(selected_rows_->GetCompleteDims(), dst_tensor.GetCompleteDims()); } } // namespace framework diff --git a/paddle/framework/tensor_util.h b/paddle/framework/tensor_util.h index 6a21f8db1e..5ac13cba4d 100644 --- a/paddle/framework/tensor_util.h +++ b/paddle/framework/tensor_util.h @@ -270,7 +270,23 @@ inline void SerializeToStream(std::ostream& os, const Tensor& tensor, } } -inline void DeserializeFromStream(std::istream& is, Tensor* tensor) { +struct DeserializedDataFunctor { + DeserializedDataFunctor(void** buf, Tensor* tensor, + const platform::Place& place) + : buf_(buf), tensor_(tensor), place_(place) {} + + template + void operator()() { + *buf_ = tensor_->mutable_data(place_); + } + + void** buf_; + Tensor* tensor_; + platform::Place place_; +}; + +inline void DeserializeFromStream(std::istream& is, Tensor* tensor, + const platform::DeviceContext& dev_ctx) { uint32_t version; is.read(reinterpret_cast(&version), sizeof(version)); PADDLE_ENFORCE_EQ(version, 0U, "Only version 0 is supported"); @@ -289,27 +305,28 @@ inline void DeserializeFromStream(std::istream& is, Tensor* tensor) { dims.reserve(static_cast(desc.dims().size())); std::copy(desc.dims().begin(), desc.dims().end(), std::back_inserter(dims)); tensor->Resize(framework::make_ddim(dims)); - void* buf; - platform::Place cpu = platform::CPUPlace(); - // TODO(Yancey1989): use VisiterDataType instead of DataType switch - switch (desc.data_type()) { - case proto::FP32: - buf = tensor->mutable_data(cpu); - break; - case proto::FP64: - buf = tensor->mutable_data(cpu); - break; - case proto::INT32: - buf = tensor->mutable_data(cpu); - break; - case proto::INT64: - buf = tensor->mutable_data(cpu); - break; - default: - PADDLE_THROW("DataType %d not supported", desc.data_type()); + auto ctx = platform::CPUDeviceContext(); + if (platform::is_gpu_place(dev_ctx.GetPlace())) { +#ifdef PADDLE_WITH_CUDA + Tensor cpu_tensor; + cpu_tensor.Resize(framework::make_ddim(dims)); + framework::VisitDataType( + desc.data_type(), + DeserializedDataFunctor(&buf, &cpu_tensor, ctx.GetPlace())); + is.read(static_cast(buf), cpu_tensor.memory_size()); + auto cpu_place = new platform::CPUPlace(); + framework::CopyFrom(cpu_tensor, *cpu_place, dev_ctx, tensor); + delete cpu_place; +#else + PADDLE_THROW("Unexpected branch"); +#endif + } else { + framework::VisitDataType( + desc.data_type(), + DeserializedDataFunctor(&buf, tensor, ctx.GetPlace())); + is.read(static_cast(buf), tensor->memory_size()); } - is.read(static_cast(buf), tensor->memory_size()); } } diff --git a/paddle/framework/tensor_util_test.cc b/paddle/framework/tensor_util_test.cc index 0dc5166fca..15cd2bd09c 100644 --- a/paddle/framework/tensor_util_test.cc +++ b/paddle/framework/tensor_util_test.cc @@ -270,11 +270,12 @@ TEST(Tensor, SerializeAndDeserialize) { SerializeToStream(oss, src_tensor, cpu_ctx); std::istringstream iss(oss.str()); - DeserializeFromStream(iss, &dst_tensor); + DeserializeFromStream(iss, &dst_tensor, cpu_ctx); int* dst_ptr = dst_tensor.mutable_data(platform::CPUPlace()); for (int i = 0; i < 5; ++i) { ASSERT_EQ(dst_ptr[i], array[i]); } + ASSERT_EQ(dst_tensor.dims(), src_tensor.dims()); delete place; } #ifdef PADDLE_WITH_CUDA @@ -292,13 +293,12 @@ TEST(Tensor, SerializeAndDeserialize) { SerializeToStream(oss, gpu_tensor, gpu_ctx); std::istringstream iss(oss.str()); - DeserializeFromStream(iss, &dst_tensor); + DeserializeFromStream(iss, &dst_tensor, gpu_ctx); int* dst_ptr = dst_tensor.mutable_data(platform::CPUPlace()); for (int i = 0; i < 6; ++i) { ASSERT_EQ(dst_ptr[i], array[i]); } - delete gpu_place; } #endif diff --git a/paddle/framework/var_type.h b/paddle/framework/var_type.h index 0e6ea8dc69..5b7a08a087 100644 --- a/paddle/framework/var_type.h +++ b/paddle/framework/var_type.h @@ -17,6 +17,8 @@ limitations under the License. */ #include "paddle/framework/lod_rank_table.h" #include "paddle/framework/lod_tensor.h" #include "paddle/framework/lod_tensor_array.h" +#include "paddle/framework/selected_rows.h" +#include "paddle/framework/variable.h" namespace paddle { namespace framework { @@ -35,7 +37,7 @@ inline proto::VarDesc::VarType ToVarType(std::type_index type) { } template -inline void VisitVarType(const Variable& var, Visitor visitor) { +inline void VisitVarType(const framework::Variable& var, Visitor visitor) { switch (ToVarType(var.Type())) { case proto::VarDesc_VarType_LOD_TENSOR: visitor(var.Get()); diff --git a/paddle/operators/detail/recv_impl.cc b/paddle/operators/detail/recv_impl.cc index b746f9df46..319404e56a 100644 --- a/paddle/operators/detail/recv_impl.cc +++ b/paddle/operators/detail/recv_impl.cc @@ -21,14 +21,9 @@ namespace detail { Status SendRecvServerImpl::SendVariable(ServerContext *context, const VariableMessage *in_var, VoidMessage *out_var) { - // TODO(typhoonzero): support different variable types. - std::istringstream iss(in_var->serialized()); - framework::LoDTensor t; - framework::DeserializeFromStream(iss, &t); - TensorWithName tensor_with_name = - std::make_pair(in_var->varname(), std::move(t)); - - var_recv_queue_.Push(std::move(tensor_with_name)); + MessageWithName msg_with_name = + std::make_pair(in_var->varname(), std::move(*in_var)); + var_recv_queue_.Push(std::move(msg_with_name)); return Status::OK; } @@ -37,14 +32,8 @@ Status SendRecvServerImpl::GetVariable(ServerContext *context, VariableMessage *out_var) { std::string get_var_name = in_var->varname(); auto *var = scope_->FindVar(get_var_name); - auto tensor = var->Get(); - std::ostringstream oss; - framework::SerializeToStream(oss, tensor, platform::CPUDeviceContext()); - std::string *varname = out_var->mutable_varname(); - *varname = get_var_name; - std::string *serialized = out_var->mutable_serialized(); - *serialized = oss.str(); + SerializeToMessage(get_var_name, var, platform::CPUDeviceContext(), out_var); return Status::OK; } diff --git a/paddle/operators/detail/send_impl.cc b/paddle/operators/detail/send_impl.cc index a812fcf39b..ae85cf2cec 100644 --- a/paddle/operators/detail/send_impl.cc +++ b/paddle/operators/detail/send_impl.cc @@ -27,14 +27,8 @@ bool RPCClient::SendVariable(const framework::Scope& scope, auto ctx = platform::CPUDeviceContext(); auto* var = scope.FindVar(inname); PADDLE_ENFORCE(var); - // TODO(typhoonzero): support SelectedRows - PADDLE_ENFORCE(var->IsType(), - "Only support LoDTensor, %s has wrong type", inname); - const framework::LoDTensor& tensor = var->Get(); - std::ostringstream oss; - framework::SerializeToStream(oss, tensor, ctx); - msg.set_varname(inname); - msg.set_serialized(oss.str()); + SerializeToMessage(inname, var, ctx, &msg); + Status status = stub_->SendVariable(&context, msg, &out_msg); if (!status.ok()) { LOG(ERROR) << "gRPC error: " << status.error_message(); @@ -50,19 +44,15 @@ bool RPCClient::GetVariable(const framework::Scope& scope, call_msg.set_varname(outname); auto ctx = platform::CPUDeviceContext(); Status status = stub_->GetVariable(&context, call_msg, &ret_msg); + auto* outvar = scope.FindVar(outname); if (!status.ok()) { LOG(ERROR) << "gRPC error: " << status.error_message(); return false; } std::istringstream iss(ret_msg.serialized()); + DeserializeFromMessage(ret_msg, ctx, outvar); - framework::LoDTensor ret_tensor; - framework::DeserializeFromStream(iss, &ret_tensor); - auto* outvar = scope.FindVar(outname); - framework::LoDTensor* out_tensor = outvar->GetMutable(); - // FIXME(typhoonzero): do not copy. - framework::CopyFrom(ret_tensor, ctx.GetPlace(), ctx, out_tensor); return true; } diff --git a/paddle/operators/detail/send_recv.proto b/paddle/operators/detail/send_recv.proto index 95c8e70898..f141c755ce 100644 --- a/paddle/operators/detail/send_recv.proto +++ b/paddle/operators/detail/send_recv.proto @@ -1,7 +1,6 @@ -/* 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. +/* 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 @@ -13,7 +12,6 @@ See the License for the specific language governing permissions and limitations under the License. */ syntax = "proto3"; - package sendrecv; service SendRecvService { @@ -29,12 +27,18 @@ service SendRecvService { // VariableMessage is serialized paddle variable message. // It can be: -// Tensor // LoDTensor // SelectedRows +enum VarType { + LOD_TENSOR = 0; + SELECTED_ROWS = 1; +} + message VariableMessage { string varname = 1; - bytes serialized = 2; + // TODO(Yancey1989): reference framework::proto::VarDesc::VarType + VarType type = 2; + bytes serialized = 3; } message VoidMessage {} diff --git a/paddle/operators/detail/send_recv_impl.h b/paddle/operators/detail/send_recv_impl.h index 47f730f7ae..1fe54f1f05 100644 --- a/paddle/operators/detail/send_recv_impl.h +++ b/paddle/operators/detail/send_recv_impl.h @@ -14,10 +14,10 @@ limitations under the License. */ #pragma once -#include "paddle/framework/data_type.h" #include "paddle/framework/lod_tensor.h" #include "paddle/framework/scope.h" #include "paddle/framework/selected_rows.h" +#include "paddle/framework/var_type.h" #include "paddle/operators/detail/simple_block_queue.h" #include "paddle/operators/detail/send_recv.grpc.pb.h" @@ -44,7 +44,7 @@ namespace paddle { namespace operators { namespace detail { -typedef std::pair TensorWithName; +typedef std::pair MessageWithName; class SendRecvServerImpl final : public SendRecvService::Service { public: @@ -60,13 +60,13 @@ class SendRecvServerImpl final : public SendRecvService::Service { void Done(); void SetScope(framework::Scope *scope) { scope_ = scope; }; - const TensorWithName Get() { return this->var_recv_queue_.Pop(); } + const MessageWithName Get() { return this->var_recv_queue_.Pop(); } - void Push(const TensorWithName &msg) { this->var_recv_queue_.Push(msg); } + void Push(const MessageWithName &msg) { this->var_recv_queue_.Push(msg); } private: // received variable from RPC, operators fetch variable from this queue. - SimpleBlockQueue var_recv_queue_; + SimpleBlockQueue var_recv_queue_; framework::Scope *scope_; // condition of the sub program std::mutex mutex_; @@ -89,6 +89,53 @@ class RPCClient { std::unique_ptr stub_; }; +inline void SerializeToMessage(const std::string &name, + const framework::Variable *var, + const platform::DeviceContext &ctx, + VariableMessage *msg) { + msg->set_varname(name); + std::ostringstream oss; + switch (framework::ToVarType(var->Type())) { + case framework::proto::VarDesc_VarType_LOD_TENSOR: + msg->set_type(sendrecv::VarType::LOD_TENSOR); + framework::SerializeToStream(oss, var->Get(), ctx); + break; + case framework::proto::VarDesc_VarType_SELECTED_ROWS: + msg->set_type(sendrecv::VarType::SELECTED_ROWS); + framework::SerializeToStream(oss, var->Get(), + ctx); + break; + default: { + PADDLE_THROW("Serialize does not support type: %s", + typeid(var->Type()).name()); + break; + } + } + msg->set_serialized(oss.str()); +} + +inline void DeserializeFromMessage(const VariableMessage &msg, + const platform::DeviceContext &ctx, + framework::Variable *var) { + using namespace paddle::framework::proto; + std::istringstream iss(msg.serialized()); + switch (msg.type()) { + case sendrecv::VarType::LOD_TENSOR: + DeserializeFromStream(iss, var->GetMutable(), ctx); + break; + case sendrecv::VarType::SELECTED_ROWS: { + DeserializeFromStream(iss, var->GetMutable(), + ctx); + break; + } + default: { + PADDLE_THROW("Deserialize does not support type: %s", + typeid(var->Type()).name()); + break; + } + } +} + } // namespace detail } // namespace operators } // namespace paddle diff --git a/paddle/operators/load_op.cc b/paddle/operators/load_op.cc index 08b972a233..7f551f101f 100644 --- a/paddle/operators/load_op.cc +++ b/paddle/operators/load_op.cc @@ -38,10 +38,10 @@ class LoadOp : public framework::OperatorBase { out_var_name); auto *tensor = out_var->GetMutable(); - DeserializeFromStream(fin, tensor); platform::DeviceContextPool &pool = platform::DeviceContextPool::Instance(); auto &dev_ctx = *pool.Get(place); + DeserializeFromStream(fin, tensor, dev_ctx); if (platform::is_gpu_place(place)) { // copy CPU to GPU diff --git a/paddle/operators/recv_op.cc b/paddle/operators/recv_op.cc index 322f8571cf..82fceb3da7 100644 --- a/paddle/operators/recv_op.cc +++ b/paddle/operators/recv_op.cc @@ -60,7 +60,7 @@ class RecvOp : public framework::OperatorBase { } void Stop() override { - detail::TensorWithName term_msg; + detail::MessageWithName term_msg; term_msg.first = LISTEN_TERMINATE_MESSAGE; rpc_service_->Push(term_msg); rpc_server_->Shutdown(); @@ -94,7 +94,7 @@ class RecvOp : public framework::OperatorBase { // the gradient arrives, just add suffix 0~n then average the gradient. for (size_t i = 0; i < param_count * trainer_count; ++i) { // blocking get one var from client. - const detail::TensorWithName &v = rpc_service_->Get(); + const detail::MessageWithName &v = rpc_service_->Get(); auto grad_var_name = v.first; if (grad_var_name == LISTEN_TERMINATE_MESSAGE) { exit_flag = true; @@ -121,11 +121,10 @@ class RecvOp : public framework::OperatorBase { } auto *var = recv_scope.Var(grad_var_name); - auto *tensor = var->GetMutable(); - // FIXME(typhoonzero): do not copy - platform::DeviceContextPool &pool = platform::DeviceContextPool::Get(); - auto &dev_ctx = *pool.Borrow(dev_place); - framework::CopyFrom(v.second, dev_place, dev_ctx, tensor); + platform::DeviceContextPool &pool = + platform::DeviceContextPool::Instance(); + auto &dev_ctx = *pool.Get(dev_place); + detail::DeserializeFromMessage(v.second, dev_ctx, var); } if (exit_flag) { break; diff --git a/paddle/operators/send_recv_op_test.cc b/paddle/operators/send_recv_op_test.cc index 108e2dec6b..fa94424bf9 100644 --- a/paddle/operators/send_recv_op_test.cc +++ b/paddle/operators/send_recv_op_test.cc @@ -20,22 +20,27 @@ limitations under the License. */ #include "paddle/framework/op_registry.h" #include "paddle/framework/operator.h" #include "paddle/framework/program_desc.h" +#include "paddle/operators/math/math_function.h" +#include "paddle/operators/math/selected_rows_functor.h" #include "paddle/string/printf.h" USE_NO_KERNEL_OP(send); USE_NO_KERNEL_OP(recv); USE_OP(sum); +namespace f = paddle::framework; +namespace p = paddle::platform; +namespace m = paddle::operators::math; + // global for simplicity. -std::unique_ptr recv_op; +std::unique_ptr recv_op; -void InitTensorsInScope(paddle::framework::Scope &scope, - paddle::platform::CPUPlace &place) { - paddle::platform::CPUDeviceContext ctx(place); +void InitTensorsInScope(f::Scope &scope, p::CPUPlace &place) { + p::CPUDeviceContext ctx(place); for (int i = 0; i < 2; ++i) { auto var_name = paddle::string::Sprintf("x%d", i); auto var = scope.Var(var_name); - auto tensor = var->GetMutable(); + auto tensor = var->GetMutable(); tensor->Resize({10, 10}); float *expect = tensor->mutable_data(place); for (int64_t i = 0; i < tensor->numel(); ++i) { @@ -44,21 +49,53 @@ void InitTensorsInScope(paddle::framework::Scope &scope, } auto out_var = scope.Var("Out"); - auto out_tensor = out_var->GetMutable(); + auto out_tensor = out_var->GetMutable(); out_tensor->Resize({10, 10}); out_tensor->mutable_data(place); // allocate } -void AddOp(const std::string &type, - const paddle::framework::VariableNameMap &inputs, - const paddle::framework::VariableNameMap &outputs, - paddle::framework::AttributeMap attrs, - paddle::framework::BlockDesc *block) { +void InitSelectedRowsInScope(f::Scope &scope, p::CPUPlace &place) { + p::CPUDeviceContext ctx(place); + int64_t height = 10; + int64_t row_numel = 10; + m::SetConstant set_one; + // init x0 + std::vector rows0{0, 4, 7}; + auto x0_var = scope.Var("x0"); + auto x0 = x0_var->GetMutable(); + x0->set_rows(rows0); + x0->set_height(height); + auto x0_value = x0->mutable_value(); + x0_value->mutable_data( + f::make_ddim({static_cast(rows0.size()), row_numel}), place); + set_one(ctx, x0_value, 1.0); + + // init x1 + std::vector rows1{2, 9}; + auto x1_var = scope.Var("x1"); + auto x1 = x1_var->GetMutable(); + x1->set_rows(rows1); + x1->set_height(height); + auto x1_value = x1->mutable_value(); + x1_value->mutable_data( + f::make_ddim({static_cast(rows1.size()), row_numel}), place); + set_one(ctx, x1_value, 1.0); + + auto out_var = scope.Var("Out"); + auto out = out_var->GetMutable(); + auto out_value = out->mutable_value(); + out->set_height(height); + out_value->mutable_data(f::make_ddim({5, 10}), place); +} + +void AddOp(const std::string &type, const f::VariableNameMap &inputs, + const f::VariableNameMap &outputs, f::AttributeMap attrs, + f::BlockDesc *block) { // insert output for (auto kv : outputs) { for (auto v : kv.second) { auto var = block->Var(v); - var->SetDataType(paddle::framework::proto::DataType::FP32); + var->SetDataType(f::proto::DataType::FP32); } } @@ -74,58 +111,99 @@ void AddOp(const std::string &type, op->SetAttrMap(attrs); } -void StartServerNet() { - paddle::framework::Scope scope; - paddle::platform::CPUPlace place; - InitTensorsInScope(scope, place); +void StartServerNet(bool is_sparse) { + f::Scope scope; + p::CPUPlace place; + if (is_sparse) { + InitSelectedRowsInScope(scope, place); + } else { + InitTensorsInScope(scope, place); + } // sub program run in recv_op, for simple test we use sum - paddle::framework::ProgramDesc program; - paddle::framework::BlockDesc *block = program.MutableBlock(0); + f::ProgramDesc program; + f::BlockDesc *block = program.MutableBlock(0); // X for server side tensors, RX for received tensers, must be of same shape. - AddOp("sum", {{"X", {"x0", "x1"}}}, {{"Out", {"x0"}}}, {}, block); + AddOp("sum", {{"X", {"x0", "x1"}}}, {{"Out", {"Out"}}}, {}, block); - paddle::framework::AttributeMap attrs; + f::AttributeMap attrs; attrs.insert({"endpoint", std::string("127.0.0.1:6174")}); - attrs.insert({"ParamList", std::vector({"x0"})}); + attrs.insert({"ParamList", std::vector({"Out"})}); attrs.insert({"GradList", std::vector({"x1"})}); std::string program_proto; PADDLE_ENFORCE(program.Proto()->SerializeToString(&program_proto)); attrs.insert({"OptimizeProgram", program_proto}); - recv_op = paddle::framework::OpRegistry::CreateOp("recv", {{"RX", {"x1"}}}, - {}, attrs); + recv_op = f::OpRegistry::CreateOp("recv", {{"RX", {"x1"}}}, {}, attrs); recv_op->Run(scope, place); } -TEST(SendRecvOp, CPU) { - std::thread server_thread(StartServerNet); - sleep(5); // wait server to start +TEST(SendRecvOp, CPUDense) { + std::thread server_thread(StartServerNet, false); + sleep(3); // wait server to start // local net - paddle::framework::Scope scope; - paddle::platform::CPUPlace place; + f::Scope scope; + p::CPUPlace place; InitTensorsInScope(scope, place); - paddle::framework::AttributeMap attrs; + f::AttributeMap attrs; attrs.insert({"endpoints", std::vector({"127.0.0.1:6174"})}); attrs.insert({"epmap", std::vector({"127.0.0.1:6174"})}); - auto send_op = paddle::framework::OpRegistry::CreateOp( - "send", {{"X", {"x1"}}}, {{"Out", {"x0"}}}, attrs); + auto send_op = f::OpRegistry::CreateOp("send", {{"X", {"x1"}}}, + {{"Out", {"Out"}}}, attrs); send_op->Run(scope, place); auto in_var = scope.Var("x1"); - auto tensor = in_var->GetMutable(); + auto tensor = in_var->GetMutable(); float *expected = tensor->data(); - auto out_var = scope.Var("x0"); - auto target = out_var->GetMutable(); + auto out_var = scope.Var("Out"); + auto target = out_var->GetMutable(); // x1 * 2 == x0 EXPECT_NE(target->memory_size(), size_t(0)); float *actual = target->data(); for (int64_t i = 0; i < target->numel(); ++i) { EXPECT_EQ(expected[i] * 2, actual[i]); } + recv_op->Stop(); + server_thread.join(); + recv_op.reset(nullptr); +} +TEST(SendRecvOp, CPUSparse) { + std::thread server_thread(StartServerNet, true); + sleep(3); // wait server to start + // local net + f::Scope scope; + p::CPUPlace place; + p::CPUDeviceContext ctx(place); + InitSelectedRowsInScope(scope, place); + f::AttributeMap attrs; + attrs.insert({"endpoints", std::vector({"127.0.0.1:6174"})}); + attrs.insert({"epmap", std::vector({"127.0.0.1:6174"})}); + auto send_op = f::OpRegistry::CreateOp("send", {{"X", {"x1"}}}, + {{"Out", {"Out"}}}, attrs); + send_op->Run(scope, place); + + auto x0 = scope.Var("x0")->GetMutable(); + auto x1 = scope.Var("x1")->GetMutable(); + auto out = scope.Var("Out")->GetMutable(); + auto actual = out->mutable_value(); + + std::unique_ptr expect{new f::SelectedRows()}; + auto expect_value = expect->mutable_value(); + expect_value->mutable_data(f::make_ddim({5, 10}), place); + + m::SelectedRowsAdd add_functor; + add_functor(ctx, *x0, *x1, expect.get()); + + EXPECT_EQ(actual->numel(), expect_value->numel()); + EXPECT_EQ(out->rows().size(), x0->rows().size() + x1->rows().size()); + + for (int64_t i = 0; i < expect_value->numel(); ++i) { + EXPECT_EQ(expect_value->mutable_data(place)[i], + actual->mutable_data(place)[i]); + } recv_op->Stop(); server_thread.join(); - // recv_op.reset(); + recv_op.reset(); } From 2d94eca8a1f707830cabb6eb0afe8879c376ac5c Mon Sep 17 00:00:00 2001 From: Yibing Liu Date: Fri, 5 Jan 2018 05:45:45 +0000 Subject: [PATCH 61/87] 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 62/87] 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 63/87] 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 64/87] 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 65/87] 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 66/87] 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 67/87] 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 68/87] 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 69/87] 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 70/87] 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 e21923543a1667bb5655db0316090bf5cafec735 Mon Sep 17 00:00:00 2001 From: guosheng Date: Fri, 5 Jan 2018 20:56:43 +0800 Subject: [PATCH 71/87] Enhance reorder_lod_tensor_by_rank_op to support Tensor --- paddle/operators/reduce_op.cc | 1 + .../reorder_lod_tensor_by_rank_op.cc | 33 ++- python/paddle/v2/fluid/layers/control_flow.py | 2 +- .../v2/fluid/tests/test_reorder_lod_tensor.py | 202 +++++++++++++++--- 4 files changed, 196 insertions(+), 42 deletions(-) diff --git a/paddle/operators/reduce_op.cc b/paddle/operators/reduce_op.cc index a3ff4a6ca0..172d28bb3b 100644 --- a/paddle/operators/reduce_op.cc +++ b/paddle/operators/reduce_op.cc @@ -77,6 +77,7 @@ class ReduceGradOp : public framework::OperatorWithKernel { auto x_grad_name = framework::GradVarName("X"); if (ctx->HasOutput(x_grad_name)) { ctx->SetOutputDim(x_grad_name, x_dims); + ctx->ShareLoD("X", /*->*/ x_grad_name); } } }; diff --git a/paddle/operators/reorder_lod_tensor_by_rank_op.cc b/paddle/operators/reorder_lod_tensor_by_rank_op.cc index 8d652ff806..0fa615d874 100644 --- a/paddle/operators/reorder_lod_tensor_by_rank_op.cc +++ b/paddle/operators/reorder_lod_tensor_by_rank_op.cc @@ -88,20 +88,33 @@ class ReorderLoDTensorByRankTableBase : public framework::OperatorBase { std::vector GetAbsoluteOffsetAndLengthByLoDRankTable( const framework::LoDTensor &x) const { std::vector absolute_table; - size_t level = 0; - size_t size = x.lod()[level].size(); - for (size_t i = 0; i < size - 1; ++i) { - auto lod_offset = - framework::GetSubLoDAndAbsoluteOffset(x.lod(), i, i + 1, level); + if (x.lod().empty()) { + // For Tensor without lod, such as the output of sequence_pool_op + size_t size = x.dims()[0]; + absolute_table.reserve(size); + for (size_t i = 0; i < size; ++i) { + absolute_table.emplace_back(); + absolute_table.back().length = 1; + absolute_table.back().offset = i; + } + } else { + size_t level = 0; + size_t size = x.lod()[level].size(); + + for (size_t i = 0; i < size - 1; ++i) { + auto lod_offset = + framework::GetSubLoDAndAbsoluteOffset(x.lod(), i, i + 1, level); - auto &offset = lod_offset.second; + auto &offset = lod_offset.second; - absolute_table.emplace_back(); - absolute_table.back().length = offset.second - offset.first; - absolute_table.back().offset = offset.first; - absolute_table.back().lod = lod_offset.first; + absolute_table.emplace_back(); + absolute_table.back().length = offset.second - offset.first; + absolute_table.back().offset = offset.first; + absolute_table.back().lod = lod_offset.first; + } } + return absolute_table; } diff --git a/python/paddle/v2/fluid/layers/control_flow.py b/python/paddle/v2/fluid/layers/control_flow.py index acc22bef98..bee57eec83 100644 --- a/python/paddle/v2/fluid/layers/control_flow.py +++ b/python/paddle/v2/fluid/layers/control_flow.py @@ -464,7 +464,7 @@ def lod_rank_table(x, level=0): """LoD Rank Table Operator. Given an input variable **x** and a level number of LoD, this layer creates a LodRankTable object. A LoDRankTable object contains a list of bi-element tuples. Each tuple consists of an index and - a length, both of which are int type. Reffering to specified level of LoD, + a length, both of which are int type. Refering to specified level of LoD, the index is the sequence index number and the length representes the sequence length. Please note that the list is ranked in descending order by the length. The following is an example: diff --git a/python/paddle/v2/fluid/tests/test_reorder_lod_tensor.py b/python/paddle/v2/fluid/tests/test_reorder_lod_tensor.py index 7c136f6360..8b79d448e2 100644 --- a/python/paddle/v2/fluid/tests/test_reorder_lod_tensor.py +++ b/python/paddle/v2/fluid/tests/test_reorder_lod_tensor.py @@ -1,46 +1,186 @@ import unittest import paddle.v2.fluid as fluid +import paddle.v2.fluid.core as core import numpy class TestReorderLoDTensor(unittest.TestCase): - def test_reorder(self): - dat = fluid.layers.data(name='input', shape=[1], lod_level=2) + num_seq = 5 + # [name, dim, lod_level] pair indicating data info of source and target + data_desc = (['input', 9, 0], ['ref', 5, 1]) + + @classmethod + def setUpClass(cls): + cls.set_program() + + @classmethod + def set_program(cls): + dat = fluid.layers.data( + name=cls.data_desc[0][0], shape=[cls.data_desc[0][1]]) dat.stop_gradient = False - rank_dat = fluid.layers.data(name='ref', shape=[1], lod_level=1) + rank_dat = fluid.layers.data( + name=cls.data_desc[1][0], shape=[cls.data_desc[1][1]]) table = fluid.layers.lod_rank_table(rank_dat) new_dat = fluid.layers.reorder_lod_tensor_by_rank( x=dat, rank_table=table) - loss = fluid.layers.mean(x=new_dat) + loss = fluid.layers.reduce_sum(new_dat) fluid.backward.append_backward(loss=loss) + cls.fetch_list = [new_dat, cls.data_desc[0][0] + '@GRAD'] + + def run_program(self): + outputs = [] + input_grads = [] + places = [core.CPUPlace()] + if core.is_compile_gpu(): + places.append(core.CUDAPlace(0)) + for place in places: + self.set_inputs(place) + exe = fluid.Executor(place) + output, input_grad = exe.run(fluid.default_main_program(), + feed=self.inputs, + fetch_list=self.fetch_list, + return_numpy=False) + outputs.append(output) + input_grads.append(input_grad) + self.actual_outputs = outputs + self.actual_grads = input_grads + + def set_data(self): + self.data = {} + for desc in self.data_desc: + data_name = desc[0] + data_dim = desc[1] + data_lod_level = desc[2] + data_lod = [] + for i in range(data_lod_level): + lod_level_i = numpy.random.randint( + low=1, + high=5, + size=self.num_seq if i == 0 else lod_level_i[-1]) + lod_level_i = [0] + numpy.cumsum(lod_level_i).tolist() + data_lod.append(lod_level_i) + data_value = numpy.random.random(size=[ + data_lod[-1][-1] if data_lod else self.num_seq, data_dim + ]).astype('float32') + self.data[data_name] = (data_value, data_lod) + + def set_inputs(self, place): + self.inputs = {} + for desc in self.data_desc: + tensor = fluid.Tensor() + tensor.set(self.data[desc[0]][0], place) + if self.data[desc[0]][1]: + tensor.set_lod(self.data[desc[0]][1]) + self.inputs[desc[0]] = tensor + + def reorder(self): + level = 0 + + # compute the rank_table according to ref_lod + ref_lod = self.data[self.data_desc[1][0]][1][level] + rank_table = [] # list of (index, length) + for i in range(len(ref_lod) - 1): + rank_table.append((i, ref_lod[i + 1] - ref_lod[i])) + rank_table = sorted(rank_table, lambda x, y: y[1] - x[1]) + + # compute the input sequence info according to input_lod + input_value, input_lod = self.data[self.data_desc[0][0]] + + input_table = [] # list of (offset, length, sub_lod) + if input_lod: + for i in range(len(input_lod[level]) - 1): + start_idx = i + end_idx = i + 1 + sub_lod = [] + for lod_level_i in input_lod[level:]: + sub_lod_i = [] + for idx in range(start_idx, end_idx): + sub_lod_i.append(lod_level_i[idx + 1] - lod_level_i[ + idx]) + sub_lod.append(sub_lod_i) + start_idx = lod_level_i[start_idx] + end_idx = lod_level_i[end_idx] + input_table.append((start_idx, end_idx - start_idx, sub_lod)) + else: + input_table = [(i, 1, []) for i in range(len(rank_table))] + + # reorder by rank_table + output_value = numpy.zeros_like(input_value) + output_lod = [] + offset = 0 + for index, length in rank_table: + input_seq_start = input_table[index][0] + input_seq_len = input_table[index][1] + input_seq_end = input_seq_start + input_seq_len + output_value[offset:offset + input_seq_len] = input_value[ + input_seq_start:input_seq_end] + offset += input_seq_len + + input_seq_sub_lod = input_table[index][2] + if len(output_lod) == 0: + output_lod = [[0] for i in input_seq_sub_lod] + for i, sub_lod_i in enumerate(input_seq_sub_lod): + for idx_sub in sub_lod_i: + output_lod[i].append(output_lod[i][-1] + idx_sub) + return output_value, output_lod + + def test_reorder_lod_tensor(self): + self.data_desc[0][-1] = 2 # input is lod_tensor + self.set_data() + self.run_program() + # check output + expect_output, expect_output_lod = self.reorder() + for actual_output in self.actual_outputs: + self.assertTrue( + numpy.allclose( + numpy.array(actual_output), expect_output, atol=0.001)) + self.assertEqual(expect_output_lod, actual_output.lod()) + # check gradient + expect_grad = numpy.ones_like(self.data[self.data_desc[0][0]][0]) + expect_grad_lod = self.data[self.data_desc[0][0]][1] + for actual_grad in self.actual_grads: + self.assertTrue( + numpy.allclose( + numpy.array(actual_grad), expect_grad, atol=0.001)) + self.assertEqual(expect_grad_lod, actual_grad.lod()) + + def test_reorder_tensor(self): + self.data_desc[0][-1] = 0 # input is tensor + self.set_data() + self.run_program() + # check output + expect_output, expect_output_lod = self.reorder() + for actual_output in self.actual_outputs: + self.assertTrue( + numpy.allclose( + numpy.array(actual_output), expect_output, atol=0.001)) + self.assertEqual(expect_output_lod, actual_output.lod()) + # check gradient + expect_grad = numpy.ones_like(self.data[self.data_desc[0][0]][0]) + expect_grad_lod = self.data[self.data_desc[0][0]][1] + for actual_grad in self.actual_grads: + self.assertTrue( + numpy.allclose( + numpy.array(actual_grad), expect_grad, atol=0.001)) + self.assertEqual(expect_grad_lod, actual_grad.lod()) + global outputs_from_tensor_implicit_lod + outputs_from_tensor_implicit_lod = self.actual_outputs - cpu = fluid.CPUPlace() - exe = fluid.Executor(cpu) - exe.run(fluid.default_startup_program()) - - ref = fluid.Tensor() - ref_lod = [0, 3, 4, 7, 8, 14] - ref.set_lod([ref_lod]) - - ref.set(numpy.random.random(size=[14, 1]).astype('float32'), cpu) - input = fluid.Tensor() - lod_level_0 = numpy.random.randint(low=1, high=5, size=5) - lod_level_0 = [0] + numpy.cumsum(lod_level_0).tolist() - lod_level_1 = numpy.random.randint(low=1, high=5, size=lod_level_0[-1]) - lod_level_1 = [0] + numpy.cumsum(lod_level_1).tolist() - - input.set_lod([lod_level_0, lod_level_1]) - input.set( - numpy.random.random(size=[lod_level_1[-1], 1]).astype('float32'), - cpu) - - ig = exe.run(fluid.default_main_program(), - feed={'input': input, - 'ref': ref}, - fetch_list=['input@GRAD'], - return_numpy=False)[0] - self.assertAlmostEqual(numpy.array(ig).sum(), 1.0, delta=0.001) - self.assertEqual(input.lod(), ig.lod()) + # compare outputs between LodTensors with explicit and implicit lod + # use the same data but set the input lod explicitly + input_lod = [[ + i for i in range(len(self.data[self.data_desc[0][0]][0]) + 1) + ]] + self.inputs[self.data_desc[0][0]].set_lod(input_lod) + # preserve the output of LodTensor with implicit lod to compare + expect_output = [ + numpy.array(actual_output) for actual_output in self.actual_outputs + ] + self.run_program() + for actual_output in self.actual_outputs: + self.assertTrue( + numpy.allclose( + numpy.array(actual_output), expect_output, atol=0.001)) if __name__ == '__main__': From 5ab271821b54bf7933976cf3f44ccf4546e43991 Mon Sep 17 00:00:00 2001 From: tensor-tang Date: Fri, 5 Jan 2018 22:18:11 +0800 Subject: [PATCH 72/87] 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 73/87] 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 74/87] 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 75/87] 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 76/87] 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 77/87] 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 78/87] 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 79/87] 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 80/87] 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 81/87] 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 82/87] 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 83/87] 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 84/87] 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 85/87] 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 86/87] 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) From e94db381baa469d73806e4921a87daab260f5060 Mon Sep 17 00:00:00 2001 From: dzhwinter Date: Mon, 8 Jan 2018 14:05:45 +0800 Subject: [PATCH 87/87] Feature/add shared layout (#7233) * "reuse ShareLoD with no regret" * "removed base class shareLayout" * "fix CI" --- paddle/framework/data_transform.cc | 5 ++--- paddle/framework/op_desc.cc | 1 + paddle/framework/operator.cc | 19 +++++++++++++++++++ 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/paddle/framework/data_transform.cc b/paddle/framework/data_transform.cc index 55825c5b7d..fed958db15 100644 --- a/paddle/framework/data_transform.cc +++ b/paddle/framework/data_transform.cc @@ -146,9 +146,6 @@ void TransDataLayout(const std::vector& axis, auto* dst = out->GetMutable(); PADDLE_ENFORCE(arity(src.dims()) == 4, "Input Arity Only Suppport 4!"); - auto place = kernel_pair.second.place_; - CopyFrom(src, place, *ctx, dst); - auto src_dim = src.dims(); std::vector dst_dim; @@ -158,6 +155,8 @@ void TransDataLayout(const std::vector& axis, } dst->Resize(make_ddim(dst_dim)); + auto place = kernel_pair.second.place_; + dst->mutable_data(place, src.type()); auto src_type = kernel_pair.first.data_type_; framework::VisitDataType(src_type, CastDataLayout(ctx, axis, src, dst)); diff --git a/paddle/framework/op_desc.cc b/paddle/framework/op_desc.cc index e02e572af2..47c91290e4 100644 --- a/paddle/framework/op_desc.cc +++ b/paddle/framework/op_desc.cc @@ -66,6 +66,7 @@ class CompileTimeInferShapeContext : public InferShapeContext { out); out_var->SetLoDLevel(in_var->GetLoDLevel()); } + bool IsRuntime() const override; protected: diff --git a/paddle/framework/operator.cc b/paddle/framework/operator.cc index adc85b1049..a1f1be5f34 100644 --- a/paddle/framework/operator.cc +++ b/paddle/framework/operator.cc @@ -417,6 +417,25 @@ class RuntimeInferShapeContext : public InferShapeContext { auto in_tensor = in_var->Get(); auto* out_tensor = out_var->GetMutable(); out_tensor->set_lod(in_tensor.lod()); + + // TODO(dzhwinter) : reuse ShareLoD in most operators. + // Need to call ShareLayout explicitly in sequence related ops. + // Shall we have a better method to shared info between in/out Tensor? + out_tensor->set_layout(in_tensor.layout()); + } + + void ShareLayout(const std::string& in, const std::string& out, size_t i = 0, + size_t j = 0) const { + PADDLE_ENFORCE_LT(i, Inputs(in).size()); + PADDLE_ENFORCE_LT(j, Outputs(out).size()); + Variable* in_var = scope_.FindVar(Inputs(in)[i]); + Variable* out_var = scope_.FindVar(Outputs(out)[j]); + if (!in_var->IsType()) return; + PADDLE_ENFORCE(out_var->IsType(), + "The %d-th output of Output(%s) must be LoDTensor.", j, out); + auto in_tensor = in_var->Get(); + auto* out_tensor = out_var->GetMutable(); + out_tensor->set_layout(in_tensor.layout()); } bool IsRuntime() const override { return true; }