From fbb1b0e4647dfd974fa5bef4670d2d4411f81c37 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Tue, 3 Jan 2017 12:49:05 +0800 Subject: [PATCH 0001/3256] Start Doing C-API for predict. --- paddle/CMakeLists.txt | 2 +- paddle/capi/Arguments.cpp | 49 +++++++++++++++ paddle/capi/CMakeLists.txt | 47 ++++++++++++++ paddle/capi/Matrix.cpp | 61 +++++++++++++++++++ paddle/capi/PaddleCAPI.h | 54 ++++++++++++++++ paddle/capi/PaddleCAPIPrivate.h | 27 ++++++++ paddle/capi/Vector.cpp | 26 ++++++++ paddle/capi/config.h.in | 6 ++ paddle/capi/tests/CMakeLists.txt | 15 +++++ paddle/capi/tests/test_Arguments.cpp | 54 ++++++++++++++++ paddle/capi/tests/test_Matrix.cpp | 33 ++++++++++ paddle/capi/tests/test_Vector.cpp | 11 ++++ paddle/utils/ForceLink.h | 46 ++++++++++++++ paddle/utils/tests/CMakeLists.txt | 8 +++ paddle/utils/tests/test_ClassRegistrar.cpp | 27 ++++++++ .../tests/test_ClassRegistrarGlobals.cpp | 16 +++++ paddle/utils/tests/test_ClassRegistrarLib.cpp | 31 ++++++++++ paddle/utils/tests/test_ClassRegistrarLib.h | 23 +++++++ 18 files changed, 535 insertions(+), 1 deletion(-) create mode 100644 paddle/capi/Arguments.cpp create mode 100644 paddle/capi/CMakeLists.txt create mode 100644 paddle/capi/Matrix.cpp create mode 100644 paddle/capi/PaddleCAPI.h create mode 100644 paddle/capi/PaddleCAPIPrivate.h create mode 100644 paddle/capi/Vector.cpp create mode 100644 paddle/capi/config.h.in create mode 100644 paddle/capi/tests/CMakeLists.txt create mode 100644 paddle/capi/tests/test_Arguments.cpp create mode 100644 paddle/capi/tests/test_Matrix.cpp create mode 100644 paddle/capi/tests/test_Vector.cpp create mode 100644 paddle/utils/ForceLink.h create mode 100644 paddle/utils/tests/test_ClassRegistrar.cpp create mode 100644 paddle/utils/tests/test_ClassRegistrarGlobals.cpp create mode 100644 paddle/utils/tests/test_ClassRegistrarLib.cpp create mode 100644 paddle/utils/tests/test_ClassRegistrarLib.h diff --git a/paddle/CMakeLists.txt b/paddle/CMakeLists.txt index 503024cff33..b3f3b2fbced 100644 --- a/paddle/CMakeLists.txt +++ b/paddle/CMakeLists.txt @@ -8,7 +8,7 @@ add_subdirectory(gserver) add_subdirectory(pserver) add_subdirectory(trainer) add_subdirectory(scripts) - +add_subdirectory(capi) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/setup.py.in ${CMAKE_CURRENT_SOURCE_DIR}/setup.py) diff --git a/paddle/capi/Arguments.cpp b/paddle/capi/Arguments.cpp new file mode 100644 index 00000000000..cf773a65872 --- /dev/null +++ b/paddle/capi/Arguments.cpp @@ -0,0 +1,49 @@ +#include "PaddleCAPI.h" +#include "PaddleCAPIPrivate.h" + +#define cast(v) paddle::capi::cast(v) + +extern "C" { +int PDArgsCreateNone(PD_Arguments* args) { + auto ptr = new paddle::capi::CArguments(); + *args = ptr; + return PD_NO_ERROR; +} + +int PDArgsDestroy(PD_Arguments args) { + if (args == nullptr) return PD_NULLPTR; + delete cast(args); + return PD_NO_ERROR; +} + +int PDArgsGetSize(PD_Arguments args, uint64_t* size) { + if (args == nullptr || size == nullptr) return PD_NULLPTR; + *size = cast(args)->args.size(); + return PD_NO_ERROR; +} + +int PDArgsResize(PD_Arguments args, uint64_t size) { + if (args == nullptr) return PD_NULLPTR; + cast(args)->args.resize(size); + return PD_NO_ERROR; +} + +int PDArgsSetValue(PD_Arguments args, uint64_t ID, PD_Matrix mat) { + if (args == nullptr || mat == nullptr) return PD_NULLPTR; + auto m = paddle::capi::cast(mat); + if (m->mat == nullptr) return PD_NULLPTR; + auto a = cast(args); + if (ID >= a->args.size()) return PD_OUT_OF_RANGE; + a->args[ID].value = m->mat; + return PD_NO_ERROR; +} + +int PDArgsGetValue(PD_Arguments args, uint64_t ID, PD_Matrix mat) { + if (args == nullptr || mat == nullptr) return PD_NULLPTR; + auto m = paddle::capi::cast(mat); + auto a = cast(args); + if (ID >= a->args.size()) return PD_OUT_OF_RANGE; + m->mat = a->args[ID].value; + return PD_NO_ERROR; +} +} diff --git a/paddle/capi/CMakeLists.txt b/paddle/capi/CMakeLists.txt new file mode 100644 index 00000000000..6162267dab7 --- /dev/null +++ b/paddle/capi/CMakeLists.txt @@ -0,0 +1,47 @@ +if (WITH_DOUBLE) + set(PADDLE_FLOAT_TYPE double) +else () + set(PADDLE_FLOAT_TYPE float) +endif() + +configure_file(config.h.in config.h @ONLY) + +set(CAPI_HEADER + PaddleCAPI.h) +set(CAPI_PRIVATE_HEADER + PaddleCAPIPrivate.h) +file(GLOB CAPI_SOURCES *.cpp) + +add_library(paddle_capi SHARED ${CAPI_SOURCES}) + +target_include_directories(paddle_capi PUBLIC ${CMAKE_CURRENT_BINARY_DIR}) +add_dependencies(paddle_capi gen_proto_cpp) + +target_link_libraries(paddle_capi + paddle_gserver + paddle_function + paddle_pserver + paddle_trainer_lib + paddle_network + paddle_math + paddle_utils + paddle_parameter + paddle_proto + paddle_cuda + ${PROTOBUF_LIBRARY} + ${LIBGLOG_LIBRARY} + ${GFLAGS_LIBRARIES} + ${CMAKE_THREAD_LIBS_INIT} + ${CBLAS_LIBS} + ${ZLIB_LIBRARIES} + ${INTERAL_LIBS} + ${CMAKE_DL_LIBS}) + + +set(PADDLE_CAPI_INC_PATH + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}) + +if (WITH_TESTING) + add_subdirectory(tests) +endif() diff --git a/paddle/capi/Matrix.cpp b/paddle/capi/Matrix.cpp new file mode 100644 index 00000000000..71598b1714d --- /dev/null +++ b/paddle/capi/Matrix.cpp @@ -0,0 +1,61 @@ +#include "PaddleCAPI.h" +#include "PaddleCAPIPrivate.h" +#include "hl_cuda.h" + +#define cast(v) paddle::capi::cast(v) +extern "C" { +int PDMatCreate(PD_Matrix* mat, uint64_t height, uint64_t width, bool useGpu) { + auto ptr = new paddle::capi::CMatrix(); + ptr->mat = paddle::Matrix::create(height, width, false, useGpu); + *mat = ptr; + return PD_NO_ERROR; +} + +int PDMatCreateNone(PD_Matrix* mat) { + auto ptr = new paddle::capi::CMatrix(); + *mat = ptr; + return PD_NO_ERROR; +} + +int PDMatDestroy(PD_Matrix mat) { + if (mat == nullptr) return PD_NULLPTR; + auto ptr = cast(mat); + delete ptr; + return PD_NO_ERROR; +} + +int PDMatCopyToRow(PD_Matrix mat, uint64_t rowID, pd_real* rowArray) { + if (mat == nullptr) return PD_NULLPTR; + auto ptr = cast(mat); + if (ptr->mat == nullptr) return PD_NULLPTR; + if (rowID >= ptr->mat->getHeight()) return PD_OUT_OF_RANGE; + paddle::real* buf = ptr->mat->getRowBuf(rowID); + size_t width = ptr->mat->getWidth(); +#ifndef PADDLE_ONLY_CPU + hl_memcpy(buf, rowArray, sizeof(paddle::real) * width); +#else + std::copy(rowArray, rowArray + width, buf); +#endif + return PD_NO_ERROR; +} + +int PDMatGetRow(PD_Matrix mat, uint64_t rowID, pd_real** rawRowBuffer) { + if (mat == nullptr) return PD_NULLPTR; + auto ptr = cast(mat); + if (ptr->mat == nullptr) return PD_NULLPTR; + if (rowID >= ptr->mat->getHeight()) return PD_OUT_OF_RANGE; + *rawRowBuffer = ptr->mat->getRowBuf(rowID); + return PD_NO_ERROR; +} + +int PDMatGetShape(PD_Matrix mat, uint64_t* height, uint64_t* width) { + if (mat == nullptr) return PD_NULLPTR; + if (height != nullptr) { + *height = cast(mat)->mat->getHeight(); + } + if (width != nullptr) { + *width = cast(mat)->mat->getWidth(); + } + return PD_NO_ERROR; +} +} diff --git a/paddle/capi/PaddleCAPI.h b/paddle/capi/PaddleCAPI.h new file mode 100644 index 00000000000..2eff0bc7da2 --- /dev/null +++ b/paddle/capi/PaddleCAPI.h @@ -0,0 +1,54 @@ +#ifndef __PADDLE_PADDLE_CAPI_PADDLECAPI_H_INCLUDED__ +#define __PADDLE_PADDLE_CAPI_PADDLECAPI_H_INCLUDED__ +#include +#include +#include "config.h" +#ifdef __cplusplus +extern "C" { +#endif + +#define PD_NO_ERROR 0 +#define PD_NULLPTR 1 +#define PD_OUT_OF_RANGE 2 +#define PD_UNDEFINED_ERROR -1 + +typedef void* PD_Vector; + +int PDVecCreate(PD_Vector* vec, uint64_t size, bool useGpu); + +int PDVecDestroy(PD_Vector vec); + +int PDVecIsSparse(PD_Vector vec, bool* isSparse); + +typedef void* PD_Matrix; + +int PDMatCreate(PD_Matrix* mat, uint64_t height, uint64_t width, bool useGpu); + +int PDMatDestroy(PD_Matrix mat); + +int PDMatCopyToRow(PD_Matrix mat, uint64_t rowID, pd_real* rowArray); + +int PDMatGetRow(PD_Matrix mat, uint64_t rowID, pd_real** rawRowBuffer); + +int PDMatCreateNone(PD_Matrix* mat); + +int PDMatGetShape(PD_Matrix mat, uint64_t* height, uint64_t* width); + +typedef void* PD_Arguments; + +int PDArgsCreateNone(PD_Arguments* args); + +int PDArgsDestroy(PD_Arguments args); + +int PDArgsGetSize(PD_Arguments args, uint64_t* size); + +int PDArgsResize(PD_Arguments args, uint64_t size); + +int PDArgsSetValue(PD_Arguments args, uint64_t ID, PD_Matrix mat); + +int PDArgsGetValue(PD_Arguments args, uint64_t ID, PD_Matrix mat); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/paddle/capi/PaddleCAPIPrivate.h b/paddle/capi/PaddleCAPIPrivate.h new file mode 100644 index 00000000000..efec60fbb68 --- /dev/null +++ b/paddle/capi/PaddleCAPIPrivate.h @@ -0,0 +1,27 @@ +#include "PaddleCAPI.h" +#include "paddle/math/Matrix.h" +#include "paddle/math/Vector.h" +#include "paddle/parameter/Argument.h" +#pragma once + +namespace paddle { +namespace capi { + +struct CVector { + VectorPtr vec; +}; + +struct CMatrix { + MatrixPtr mat; +}; + +struct CArguments { + std::vector args; +}; + +template +inline T* cast(void* ptr) { + return reinterpret_cast(ptr); +} +} +} diff --git a/paddle/capi/Vector.cpp b/paddle/capi/Vector.cpp new file mode 100644 index 00000000000..10dee7816c3 --- /dev/null +++ b/paddle/capi/Vector.cpp @@ -0,0 +1,26 @@ +#include "PaddleCAPI.h" +#include "PaddleCAPIPrivate.h" + +#define cast(v) paddle::capi::cast(v) +extern "C" { +int PDVecCreate(PD_Vector* vec, uint64_t size, bool useGpu) { + auto ptr = new paddle::capi::CVector(); + ptr->vec = paddle::Vector::create(size, useGpu); + *vec = ptr; + return PD_NO_ERROR; +} +int PDVecDestroy(PD_Vector vec) { + auto v = cast(vec); + v->vec.reset(); + delete v; + return PD_NO_ERROR; +} + +int PDVecIsSparse(PD_Vector vec, bool* isSparse) { + if (isSparse == nullptr || vec == nullptr) { + return PD_NULLPTR; + } + *isSparse = cast(vec)->vec->isSparse(); + return PD_NO_ERROR; +} +} diff --git a/paddle/capi/config.h.in b/paddle/capi/config.h.in new file mode 100644 index 00000000000..32d8a364e0e --- /dev/null +++ b/paddle/capi/config.h.in @@ -0,0 +1,6 @@ +#ifndef __PADDLE_PADDLE_CAPI_CONFIG_H_INCLUDED__ +#define __PADDLE_PADDLE_CAPI_CONFIG_H_INCLUDED__ + +typedef @PADDLE_FLOAT_TYPE@ pd_real; + +#endif diff --git a/paddle/capi/tests/CMakeLists.txt b/paddle/capi/tests/CMakeLists.txt new file mode 100644 index 00000000000..cd6b1d7c627 --- /dev/null +++ b/paddle/capi/tests/CMakeLists.txt @@ -0,0 +1,15 @@ +function(add_capi_unittest TARGET_NAME) + add_executable( + ${TARGET_NAME} + ${ARGN}) + target_link_libraries( + ${TARGET_NAME} + paddle_capi + paddle_test_main + ${GTEST_LIBRARIES}) + target_include_directories(${TARGET_NAME} PUBLIC ${PADDLE_CAPI_INC_PATH}) + add_test(NAME ${TARGET_NAME} COMMAND ${TARGET_NAME}) +endfunction() + +add_capi_unittest(capi_test_mats test_Vector.cpp + test_Matrix.cpp test_Arguments.cpp) diff --git a/paddle/capi/tests/test_Arguments.cpp b/paddle/capi/tests/test_Arguments.cpp new file mode 100644 index 00000000000..c74abd60d1b --- /dev/null +++ b/paddle/capi/tests/test_Arguments.cpp @@ -0,0 +1,54 @@ +#include "PaddleCAPI.h" +#include "gtest/gtest.h" +#include "paddle/utils/ThreadLocal.h" + +static std::vector randomBuffer(size_t bufSize) { + auto& eng = paddle::ThreadLocalRandomEngine::get(); + std::uniform_real_distribution dist(-1.0, 1.0); + std::vector retv; + retv.reserve(bufSize); + for (size_t i = 0; i < bufSize; ++i) { + retv.push_back(dist(eng)); + } + return retv; +} + +TEST(CAPIArguments, create) { + PD_Arguments args; + ASSERT_EQ(PD_NO_ERROR, PDArgsCreateNone(&args)); + uint64_t size; + ASSERT_EQ(PD_NO_ERROR, PDArgsGetSize(args, &size)); + ASSERT_EQ(0UL, size); + ASSERT_EQ(PD_NO_ERROR, PDArgsDestroy(args)); +} + +TEST(CAPIArguments, value) { + PD_Arguments args; + ASSERT_EQ(PD_NO_ERROR, PDArgsCreateNone(&args)); + ASSERT_EQ(PD_NO_ERROR, PDArgsResize(args, 1)); + + PD_Matrix mat; + ASSERT_EQ(PD_NO_ERROR, PDMatCreate(&mat, 128, 64, false)); + for (size_t i = 0; i < 128; ++i) { + std::vector sampleBuf = randomBuffer(64); + PDMatCopyToRow(mat, i, sampleBuf.data()); + } + ASSERT_EQ(PD_NO_ERROR, PDArgsSetValue(args, 0, mat)); + + PD_Matrix val; + ASSERT_EQ(PD_NO_ERROR, PDMatCreateNone(&val)); + + ASSERT_EQ(PD_NO_ERROR, PDArgsGetValue(args, 0, val)); + + for (size_t i = 0; i < 128; ++i) { + pd_real* row1; + pd_real* row2; + + ASSERT_EQ(PD_NO_ERROR, PDMatGetRow(mat, i, &row1)); + ASSERT_EQ(PD_NO_ERROR, PDMatGetRow(val, i, &row2)); + ASSERT_EQ(row1, row2); + } + ASSERT_EQ(PD_NO_ERROR, PDMatDestroy(val)); + ASSERT_EQ(PD_NO_ERROR, PDMatDestroy(mat)); + ASSERT_EQ(PD_NO_ERROR, PDArgsDestroy(args)); +} diff --git a/paddle/capi/tests/test_Matrix.cpp b/paddle/capi/tests/test_Matrix.cpp new file mode 100644 index 00000000000..0f04a468304 --- /dev/null +++ b/paddle/capi/tests/test_Matrix.cpp @@ -0,0 +1,33 @@ +#include "PaddleCAPI.h" +#include "gtest/gtest.h" + +TEST(CAPIMatrix, create) { + PD_Matrix mat; + ASSERT_EQ(PD_NO_ERROR, PDMatCreate(&mat, 128, 32, false)); + std::vector sampleRow; + sampleRow.resize(32); + for (size_t i = 0; i < sampleRow.size(); ++i) { + sampleRow[i] = 1.0 / (i + 1.0); + } + ASSERT_EQ(PD_NO_ERROR, PDMatCopyToRow(mat, 0, sampleRow.data())); + ASSERT_EQ(PD_OUT_OF_RANGE, PDMatCopyToRow(mat, 128, sampleRow.data())); + + pd_real* arrayPtr; + + ASSERT_EQ(PD_NO_ERROR, PDMatGetRow(mat, 0, &arrayPtr)); + for (size_t i = 0; i < sampleRow.size(); ++i) { + ASSERT_NEAR(sampleRow[i], arrayPtr[i], 1e-5); + } + + uint64_t height, width; + ASSERT_EQ(PD_NO_ERROR, PDMatGetShape(mat, &height, &width)); + ASSERT_EQ(128, height); + ASSERT_EQ(32, width); + ASSERT_EQ(PD_NO_ERROR, PDMatDestroy(mat)); +} + +TEST(CAPIMatrix, createNone) { + PD_Matrix mat; + ASSERT_EQ(PD_NO_ERROR, PDMatCreateNone(&mat)); + ASSERT_EQ(PD_NO_ERROR, PDMatDestroy(mat)); +} diff --git a/paddle/capi/tests/test_Vector.cpp b/paddle/capi/tests/test_Vector.cpp new file mode 100644 index 00000000000..dbb987d440a --- /dev/null +++ b/paddle/capi/tests/test_Vector.cpp @@ -0,0 +1,11 @@ +#include "PaddleCAPI.h" +#include "gtest/gtest.h" + +TEST(CAPIVector, create) { + PD_Vector tmp; + ASSERT_EQ(PD_NO_ERROR, PDVecCreate(&tmp, 128, false)); + bool isSparse; + ASSERT_EQ(PD_NO_ERROR, PDVecIsSparse(tmp, &isSparse)); + ASSERT_FALSE(isSparse); + ASSERT_EQ(PD_NO_ERROR, PDVecDestroy(tmp)); +} diff --git a/paddle/utils/ForceLink.h b/paddle/utils/ForceLink.h new file mode 100644 index 00000000000..66005e2992e --- /dev/null +++ b/paddle/utils/ForceLink.h @@ -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. */ + +#pragma once + +/// Declare a force link file ID. It can be enabled by +/// `PADDLE_ENABLE_FORCE_LINK_FILE`. It is +/// +/// Example: +/// +/// In some_file.cpp +/// @code{cpp} +/// static paddle::InitFunction init([]{...}); +/// PADDLE_REGISTER_FORCE_LINK_FILE(some_file) +/// @endcode{cpp} +/// +/// In main.cpp +/// @code{cpp} +/// PADDLE_ENABLE_FORCE_LINK_FILE(some_file); +/// +/// int main() { +/// ... +/// } +/// @endcode{cpp} +/// +/// Then the InitFunction in some_file.cpp can be invoked. +#define PADDLE_REGISTER_FORCE_LINK_FILE(ID) \ + int __paddle_register_force_link_file_##ID##_method__() { return 0; } + +/// Enable a force link file. The file with ID's static variables could +/// be all initialized. +#define PADDLE_ENABLE_FORCE_LINK_FILE(ID) \ + extern int __paddle_register_force_link_file_##ID##_method__(); \ + static int __paddle_register_force_link_file_##ID##_handler__ = \ + __paddle_register_force_link_file_##ID##_method__(); diff --git a/paddle/utils/tests/CMakeLists.txt b/paddle/utils/tests/CMakeLists.txt index 26fafbd1ab3..d9b018ebbb2 100644 --- a/paddle/utils/tests/CMakeLists.txt +++ b/paddle/utils/tests/CMakeLists.txt @@ -15,3 +15,11 @@ if(NOT APPLE) COMMAND ${PROJ_ROOT}/paddle/utils/tests/test_CustomStackTracePrint.sh WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) endif() + +add_library(test_class_registrar_lib STATIC + test_ClassRegistrarLib.cpp + test_ClassRegistrarGlobals.cpp) + +add_simple_unittest(test_ClassRegistrar) +target_link_libraries(test_ClassRegistrar + test_class_registrar_lib) diff --git a/paddle/utils/tests/test_ClassRegistrar.cpp b/paddle/utils/tests/test_ClassRegistrar.cpp new file mode 100644 index 00000000000..c867045cb68 --- /dev/null +++ b/paddle/utils/tests/test_ClassRegistrar.cpp @@ -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. */ + +#include +#include +#include "test_ClassRegistrarLib.h" +// Enable link test_ClassRegistrarLib.cpp +PADDLE_ENABLE_FORCE_LINK_FILE(test_registrar); + +TEST(ClassRegistrar, test) { + std::vector types; + gTestRegistrar_.forEachType( + [&types](const std::string& tp) { types.push_back(tp); }); + ASSERT_EQ(1, types.size()); + ASSERT_EQ("test", types[0]); +} diff --git a/paddle/utils/tests/test_ClassRegistrarGlobals.cpp b/paddle/utils/tests/test_ClassRegistrarGlobals.cpp new file mode 100644 index 00000000000..0f36da137f9 --- /dev/null +++ b/paddle/utils/tests/test_ClassRegistrarGlobals.cpp @@ -0,0 +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 + + 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 "test_ClassRegistrarLib.h" +paddle::ClassRegistrar gTestRegistrar_; diff --git a/paddle/utils/tests/test_ClassRegistrarLib.cpp b/paddle/utils/tests/test_ClassRegistrarLib.cpp new file mode 100644 index 00000000000..27071579f94 --- /dev/null +++ b/paddle/utils/tests/test_ClassRegistrarLib.cpp @@ -0,0 +1,31 @@ +/* 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 "test_ClassRegistrarLib.h" +#include +BaseClass::~BaseClass() {} + +class TestRegistrar : public BaseClass { +public: + TestRegistrar() {} + + virtual ~TestRegistrar() {} +}; + +static paddle::InitFunction init([] { + gTestRegistrar_.registerClass( + "test", []() -> BaseClass* { return new TestRegistrar(); }); +}); + +PADDLE_REGISTER_FORCE_LINK_FILE(test_registrar); diff --git a/paddle/utils/tests/test_ClassRegistrarLib.h b/paddle/utils/tests/test_ClassRegistrarLib.h new file mode 100644 index 00000000000..de2d02e70cc --- /dev/null +++ b/paddle/utils/tests/test_ClassRegistrarLib.h @@ -0,0 +1,23 @@ +/* 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/utils/ClassRegistrar.h" + +class BaseClass { +public: + virtual ~BaseClass(); +}; + +extern paddle::ClassRegistrar gTestRegistrar_; -- GitLab From aa6e25215e906d1ec0fc501849ca19e20abd0a0d Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Wed, 4 Jan 2017 18:02:45 +0800 Subject: [PATCH 0002/3256] Doing C-API --- paddle/capi/CMakeLists.txt | 3 +- paddle/capi/GradientMachine.cpp | 48 +++++++++++++++++++ paddle/capi/Main.cpp | 43 +++++++++++++++++ paddle/capi/PaddleCAPI.h | 25 ++++++++-- paddle/capi/PaddleCAPIPrivate.h | 5 ++ paddle/capi/tests/CMakeLists.txt | 29 +++++++++-- paddle/capi/tests/test_GradientMachine.cpp | 24 ++++++++++ paddle/capi/tests/test_Init.cpp | 0 paddle/capi/tests/vgg_16_cifar.py | 1 + paddle/utils/ForceLink.h | 46 ------------------ paddle/utils/tests/CMakeLists.txt | 8 ---- paddle/utils/tests/test_ClassRegistrar.cpp | 27 ----------- .../tests/test_ClassRegistrarGlobals.cpp | 16 ------- paddle/utils/tests/test_ClassRegistrarLib.cpp | 31 ------------ paddle/utils/tests/test_ClassRegistrarLib.h | 23 --------- 15 files changed, 169 insertions(+), 160 deletions(-) create mode 100644 paddle/capi/GradientMachine.cpp create mode 100644 paddle/capi/Main.cpp create mode 100644 paddle/capi/tests/test_GradientMachine.cpp create mode 100644 paddle/capi/tests/test_Init.cpp create mode 120000 paddle/capi/tests/vgg_16_cifar.py delete mode 100644 paddle/utils/ForceLink.h delete mode 100644 paddle/utils/tests/test_ClassRegistrar.cpp delete mode 100644 paddle/utils/tests/test_ClassRegistrarGlobals.cpp delete mode 100644 paddle/utils/tests/test_ClassRegistrarLib.cpp delete mode 100644 paddle/utils/tests/test_ClassRegistrarLib.h diff --git a/paddle/capi/CMakeLists.txt b/paddle/capi/CMakeLists.txt index 6162267dab7..62e9e5ccef3 100644 --- a/paddle/capi/CMakeLists.txt +++ b/paddle/capi/CMakeLists.txt @@ -35,7 +35,8 @@ target_link_libraries(paddle_capi ${CBLAS_LIBS} ${ZLIB_LIBRARIES} ${INTERAL_LIBS} - ${CMAKE_DL_LIBS}) + ${CMAKE_DL_LIBS} + ${PYTHON_LIBRARIES}) set(PADDLE_CAPI_INC_PATH diff --git a/paddle/capi/GradientMachine.cpp b/paddle/capi/GradientMachine.cpp new file mode 100644 index 00000000000..2969b5f198f --- /dev/null +++ b/paddle/capi/GradientMachine.cpp @@ -0,0 +1,48 @@ +#include "PaddleCAPI.h" +#include "PaddleCAPIPrivate.h" +#include "paddle/gserver/gradientmachines/NeuralNetwork.h" + +#define cast(v) paddle::capi::cast(v) + +enum GradientMatchineCreateMode { + CREATE_MODE_NORMAL = 0, + CREATE_MODE_TESTING = 4 +}; + +namespace paddle { + +class MyNeuralNetwork : public NeuralNetwork { +public: + MyNeuralNetwork(const std::string& name, NeuralNetwork* network) + : NeuralNetwork(name, network) {} +}; + +NeuralNetwork* newCustomNerualNetwork(const std::string& name, + NeuralNetwork* network) { + return new MyNeuralNetwork(name, network); +} +} + +extern "C" { +int PDGradientMachineCreateForPredict(PD_GradiemtMachine* machine, + void* modelConfigProtobuf, + int size) { + if (modelConfigProtobuf == nullptr) return PD_NULLPTR; + paddle::ModelConfig config; + if (!config.ParseFromArray(modelConfigProtobuf, size) || + !config.IsInitialized()) { + return PD_PROTOBUF_ERROR; + } + + auto ptr = new paddle::capi::CGradientMachine(); + ptr->machine.reset(paddle::GradientMachine::create( + config, CREATE_MODE_TESTING, {paddle::PARAMETER_VALUE})); + *machine = ptr; + return PD_NO_ERROR; +} + +int PDGradientMachineDestroy(PD_GradiemtMachine machine) { + delete cast(machine); + return PD_NO_ERROR; +} +} diff --git a/paddle/capi/Main.cpp b/paddle/capi/Main.cpp new file mode 100644 index 00000000000..49606e1f942 --- /dev/null +++ b/paddle/capi/Main.cpp @@ -0,0 +1,43 @@ +#include +#include +#include +#include +#include "PaddleCAPI.h" +#include "PaddleCAPIPrivate.h" +#include "paddle/trainer/TrainerConfigHelper.h" +#include "paddle/utils/Excepts.h" +#include "paddle/utils/PythonUtil.h" + +static void initPaddle(int argc, char** argv) { + paddle::initMain(argc, argv); + paddle::initPython(argc, argv); + feenableexcept(FE_INVALID | FE_DIVBYZERO | FE_OVERFLOW); +} + +extern "C" { +int PDInit(int argc, char** argv) { + std::vector realArgv; + realArgv.reserve(argc + 1); + realArgv.push_back(strdup("")); + for (int i = 0; i < argc; ++i) { + realArgv.push_back(argv[i]); + } + initPaddle(argc + 1, realArgv.data()); + free(realArgv[0]); + return PD_NO_ERROR; +} + +int PDParseTrainerConfigFromFile(char* filename, + void** modelConfigProtobuf, + int* size) { + if (filename == nullptr || modelConfigProtobuf == nullptr || size == nullptr) + return PD_NULLPTR; + paddle::TrainerConfigHelper conf(filename); + if (!conf.getConfig().IsInitialized()) return PD_PROTOBUF_ERROR; + *size = conf.getConfig().ByteSize(); + *modelConfigProtobuf = malloc(*size); + if (!conf.getConfig().SerializeToArray(*modelConfigProtobuf, *size)) + return PD_PROTOBUF_ERROR; + return PD_NO_ERROR; +} +} diff --git a/paddle/capi/PaddleCAPI.h b/paddle/capi/PaddleCAPI.h index 2eff0bc7da2..fa43b3b40bb 100644 --- a/paddle/capi/PaddleCAPI.h +++ b/paddle/capi/PaddleCAPI.h @@ -7,10 +7,13 @@ extern "C" { #endif -#define PD_NO_ERROR 0 -#define PD_NULLPTR 1 -#define PD_OUT_OF_RANGE 2 -#define PD_UNDEFINED_ERROR -1 +typedef enum { + PD_NO_ERROR = 0, + PD_NULLPTR = 1, + PD_OUT_OF_RANGE = 2, + PD_PROTOBUF_ERROR = 3, + PD_UNDEFINED_ERROR = -1, +} PD_Error; typedef void* PD_Vector; @@ -48,6 +51,20 @@ int PDArgsSetValue(PD_Arguments args, uint64_t ID, PD_Matrix mat); int PDArgsGetValue(PD_Arguments args, uint64_t ID, PD_Matrix mat); +typedef void* PD_GradiemtMachine; + +int PDGradientMachineCreateForPredict(PD_GradiemtMachine* machine, + void* modelConfigProtobuf, + int size); + +int PDGradientMachineDestroy(PD_GradiemtMachine machine); + +int PDInit(int argc, char** argv); + +int PDParseTrainerConfigFromFile(char* filename, + void** modelConfigProtobuf, + int* size); + #ifdef __cplusplus } #endif diff --git a/paddle/capi/PaddleCAPIPrivate.h b/paddle/capi/PaddleCAPIPrivate.h index efec60fbb68..07e731f6cd0 100644 --- a/paddle/capi/PaddleCAPIPrivate.h +++ b/paddle/capi/PaddleCAPIPrivate.h @@ -1,4 +1,5 @@ #include "PaddleCAPI.h" +#include "paddle/gserver/gradientmachines/GradientMachine.h" #include "paddle/math/Matrix.h" #include "paddle/math/Vector.h" #include "paddle/parameter/Argument.h" @@ -19,6 +20,10 @@ struct CArguments { std::vector args; }; +struct CGradientMachine { + paddle::GradientMachinePtr machine; +}; + template inline T* cast(void* ptr) { return reinterpret_cast(ptr); diff --git a/paddle/capi/tests/CMakeLists.txt b/paddle/capi/tests/CMakeLists.txt index cd6b1d7c627..e1fa3d6b791 100644 --- a/paddle/capi/tests/CMakeLists.txt +++ b/paddle/capi/tests/CMakeLists.txt @@ -1,15 +1,36 @@ -function(add_capi_unittest TARGET_NAME) +function(add_capi_unittest_without_exec TARGET_NAME) + set(with_test_main ON) + set(sources) + foreach(source_file ${ARGN}) + if (${source_file} STREQUAL "NO_MAIN") + set(with_test_main OFF) + else() + list(APPEND sources ${source_file}) + endif() + endforeach() + add_executable( ${TARGET_NAME} - ${ARGN}) + ${sources}) + + target_link_libraries( ${TARGET_NAME} paddle_capi - paddle_test_main ${GTEST_LIBRARIES}) + + if (with_test_main) + target_link_libraries( + ${TARGET_NAME} paddle_test_main) + endif() target_include_directories(${TARGET_NAME} PUBLIC ${PADDLE_CAPI_INC_PATH}) - add_test(NAME ${TARGET_NAME} COMMAND ${TARGET_NAME}) endfunction() +function(add_capi_unittest TARGET_NAME) + add_capi_unittest_without_exec(${TARGET_NAME} ${ARGN}) + add_test(NAME ${TARGET_NAME} COMMAND ${TARGET_NAME}) +endfunction() add_capi_unittest(capi_test_mats test_Vector.cpp test_Matrix.cpp test_Arguments.cpp) + +add_capi_unittest(capi_test_gradientMachine NO_MAIN test_GradientMachine.cpp) diff --git a/paddle/capi/tests/test_GradientMachine.cpp b/paddle/capi/tests/test_GradientMachine.cpp new file mode 100644 index 00000000000..8c1ea73ae6c --- /dev/null +++ b/paddle/capi/tests/test_GradientMachine.cpp @@ -0,0 +1,24 @@ +#include +#include +#include +#include "PaddleCAPI.h" + +TEST(GradientMachine, load) { + void* buf; + int size; + ASSERT_EQ( + PD_NO_ERROR, + PDParseTrainerConfigFromFile(strdup("./vgg_16_cifar.py"), &buf, &size)); + free(buf); +} + +int main(int argc, char** argv) { + testing::InitGoogleTest(&argc, argv); + std::vector argvs; + argvs.push_back(strdup("--use_gpu=false")); + PDInit((int)argvs.size(), argvs.data()); + for (auto each : argvs) { + free(each); + } + return RUN_ALL_TESTS(); +} diff --git a/paddle/capi/tests/test_Init.cpp b/paddle/capi/tests/test_Init.cpp new file mode 100644 index 00000000000..e69de29bb2d diff --git a/paddle/capi/tests/vgg_16_cifar.py b/paddle/capi/tests/vgg_16_cifar.py new file mode 120000 index 00000000000..81250eefde6 --- /dev/null +++ b/paddle/capi/tests/vgg_16_cifar.py @@ -0,0 +1 @@ +../../../demo/image_classification/vgg_16_cifar.py \ No newline at end of file diff --git a/paddle/utils/ForceLink.h b/paddle/utils/ForceLink.h deleted file mode 100644 index 66005e2992e..00000000000 --- a/paddle/utils/ForceLink.h +++ /dev/null @@ -1,46 +0,0 @@ -/* 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 - -/// Declare a force link file ID. It can be enabled by -/// `PADDLE_ENABLE_FORCE_LINK_FILE`. It is -/// -/// Example: -/// -/// In some_file.cpp -/// @code{cpp} -/// static paddle::InitFunction init([]{...}); -/// PADDLE_REGISTER_FORCE_LINK_FILE(some_file) -/// @endcode{cpp} -/// -/// In main.cpp -/// @code{cpp} -/// PADDLE_ENABLE_FORCE_LINK_FILE(some_file); -/// -/// int main() { -/// ... -/// } -/// @endcode{cpp} -/// -/// Then the InitFunction in some_file.cpp can be invoked. -#define PADDLE_REGISTER_FORCE_LINK_FILE(ID) \ - int __paddle_register_force_link_file_##ID##_method__() { return 0; } - -/// Enable a force link file. The file with ID's static variables could -/// be all initialized. -#define PADDLE_ENABLE_FORCE_LINK_FILE(ID) \ - extern int __paddle_register_force_link_file_##ID##_method__(); \ - static int __paddle_register_force_link_file_##ID##_handler__ = \ - __paddle_register_force_link_file_##ID##_method__(); diff --git a/paddle/utils/tests/CMakeLists.txt b/paddle/utils/tests/CMakeLists.txt index d9b018ebbb2..26fafbd1ab3 100644 --- a/paddle/utils/tests/CMakeLists.txt +++ b/paddle/utils/tests/CMakeLists.txt @@ -15,11 +15,3 @@ if(NOT APPLE) COMMAND ${PROJ_ROOT}/paddle/utils/tests/test_CustomStackTracePrint.sh WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) endif() - -add_library(test_class_registrar_lib STATIC - test_ClassRegistrarLib.cpp - test_ClassRegistrarGlobals.cpp) - -add_simple_unittest(test_ClassRegistrar) -target_link_libraries(test_ClassRegistrar - test_class_registrar_lib) diff --git a/paddle/utils/tests/test_ClassRegistrar.cpp b/paddle/utils/tests/test_ClassRegistrar.cpp deleted file mode 100644 index c867045cb68..00000000000 --- a/paddle/utils/tests/test_ClassRegistrar.cpp +++ /dev/null @@ -1,27 +0,0 @@ -/* 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 "test_ClassRegistrarLib.h" -// Enable link test_ClassRegistrarLib.cpp -PADDLE_ENABLE_FORCE_LINK_FILE(test_registrar); - -TEST(ClassRegistrar, test) { - std::vector types; - gTestRegistrar_.forEachType( - [&types](const std::string& tp) { types.push_back(tp); }); - ASSERT_EQ(1, types.size()); - ASSERT_EQ("test", types[0]); -} diff --git a/paddle/utils/tests/test_ClassRegistrarGlobals.cpp b/paddle/utils/tests/test_ClassRegistrarGlobals.cpp deleted file mode 100644 index 0f36da137f9..00000000000 --- a/paddle/utils/tests/test_ClassRegistrarGlobals.cpp +++ /dev/null @@ -1,16 +0,0 @@ -/* 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 "test_ClassRegistrarLib.h" -paddle::ClassRegistrar gTestRegistrar_; diff --git a/paddle/utils/tests/test_ClassRegistrarLib.cpp b/paddle/utils/tests/test_ClassRegistrarLib.cpp deleted file mode 100644 index 27071579f94..00000000000 --- a/paddle/utils/tests/test_ClassRegistrarLib.cpp +++ /dev/null @@ -1,31 +0,0 @@ -/* 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 "test_ClassRegistrarLib.h" -#include -BaseClass::~BaseClass() {} - -class TestRegistrar : public BaseClass { -public: - TestRegistrar() {} - - virtual ~TestRegistrar() {} -}; - -static paddle::InitFunction init([] { - gTestRegistrar_.registerClass( - "test", []() -> BaseClass* { return new TestRegistrar(); }); -}); - -PADDLE_REGISTER_FORCE_LINK_FILE(test_registrar); diff --git a/paddle/utils/tests/test_ClassRegistrarLib.h b/paddle/utils/tests/test_ClassRegistrarLib.h deleted file mode 100644 index de2d02e70cc..00000000000 --- a/paddle/utils/tests/test_ClassRegistrarLib.h +++ /dev/null @@ -1,23 +0,0 @@ -/* 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/utils/ClassRegistrar.h" - -class BaseClass { -public: - virtual ~BaseClass(); -}; - -extern paddle::ClassRegistrar gTestRegistrar_; -- GitLab From 3fcd81fde757488d03c6d6fcc510c8d2deb9cd17 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Thu, 5 Jan 2017 16:38:31 +0800 Subject: [PATCH 0003/3256] Stash --- CMakeLists.txt | 2 +- cmake/util.cmake | 2 +- paddle/capi/CMakeLists.txt | 5 +++-- paddle/capi/Main.cpp | 14 -------------- paddle/capi/PaddleCAPI.h | 4 ---- paddle/capi/tests/CMakeLists.txt | 1 - paddle/capi/tests/test_GradientMachine.cpp | 15 +++++++++------ 7 files changed, 14 insertions(+), 29 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 65fbbb481c4..bbe47d4ff1d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,7 +26,7 @@ find_package(NumPy REQUIRED) find_package(Threads REQUIRED) find_package(AVX QUIET) find_package(Glog REQUIRED) -find_package(Gflags REQUIRED) +find_package(Gflags COMPONENTS nothreads_static REQUIRED) find_package(GTest) find_package(Sphinx) find_package(Doxygen) diff --git a/cmake/util.cmake b/cmake/util.cmake index 43a56378df0..3b4258654f0 100644 --- a/cmake/util.cmake +++ b/cmake/util.cmake @@ -110,7 +110,7 @@ function(link_paddle_exe TARGET_NAME) ${METRIC_LIBS} ${PROTOBUF_LIBRARY} ${LIBGLOG_LIBRARY} - ${GFLAGS_LIBRARIES} + gflags ${CMAKE_THREAD_LIBS_INIT} ${CBLAS_LIBS} ${ZLIB_LIBRARIES} diff --git a/paddle/capi/CMakeLists.txt b/paddle/capi/CMakeLists.txt index 62e9e5ccef3..80cf2c7fa91 100644 --- a/paddle/capi/CMakeLists.txt +++ b/paddle/capi/CMakeLists.txt @@ -18,7 +18,9 @@ target_include_directories(paddle_capi PUBLIC ${CMAKE_CURRENT_BINARY_DIR}) add_dependencies(paddle_capi gen_proto_cpp) target_link_libraries(paddle_capi + "-Wl,-force_load" paddle_gserver + "-Wl,-force_load" paddle_function paddle_pserver paddle_trainer_lib @@ -30,7 +32,7 @@ target_link_libraries(paddle_capi paddle_cuda ${PROTOBUF_LIBRARY} ${LIBGLOG_LIBRARY} - ${GFLAGS_LIBRARIES} + gflags ${CMAKE_THREAD_LIBS_INIT} ${CBLAS_LIBS} ${ZLIB_LIBRARIES} @@ -38,7 +40,6 @@ target_link_libraries(paddle_capi ${CMAKE_DL_LIBS} ${PYTHON_LIBRARIES}) - set(PADDLE_CAPI_INC_PATH ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/paddle/capi/Main.cpp b/paddle/capi/Main.cpp index 49606e1f942..cc07e2ba4ef 100644 --- a/paddle/capi/Main.cpp +++ b/paddle/capi/Main.cpp @@ -26,18 +26,4 @@ int PDInit(int argc, char** argv) { free(realArgv[0]); return PD_NO_ERROR; } - -int PDParseTrainerConfigFromFile(char* filename, - void** modelConfigProtobuf, - int* size) { - if (filename == nullptr || modelConfigProtobuf == nullptr || size == nullptr) - return PD_NULLPTR; - paddle::TrainerConfigHelper conf(filename); - if (!conf.getConfig().IsInitialized()) return PD_PROTOBUF_ERROR; - *size = conf.getConfig().ByteSize(); - *modelConfigProtobuf = malloc(*size); - if (!conf.getConfig().SerializeToArray(*modelConfigProtobuf, *size)) - return PD_PROTOBUF_ERROR; - return PD_NO_ERROR; -} } diff --git a/paddle/capi/PaddleCAPI.h b/paddle/capi/PaddleCAPI.h index fa43b3b40bb..17a24986718 100644 --- a/paddle/capi/PaddleCAPI.h +++ b/paddle/capi/PaddleCAPI.h @@ -61,10 +61,6 @@ int PDGradientMachineDestroy(PD_GradiemtMachine machine); int PDInit(int argc, char** argv); -int PDParseTrainerConfigFromFile(char* filename, - void** modelConfigProtobuf, - int* size); - #ifdef __cplusplus } #endif diff --git a/paddle/capi/tests/CMakeLists.txt b/paddle/capi/tests/CMakeLists.txt index e1fa3d6b791..e54a53e2935 100644 --- a/paddle/capi/tests/CMakeLists.txt +++ b/paddle/capi/tests/CMakeLists.txt @@ -13,7 +13,6 @@ function(add_capi_unittest_without_exec TARGET_NAME) ${TARGET_NAME} ${sources}) - target_link_libraries( ${TARGET_NAME} paddle_capi diff --git a/paddle/capi/tests/test_GradientMachine.cpp b/paddle/capi/tests/test_GradientMachine.cpp index 8c1ea73ae6c..f07d1e4e729 100644 --- a/paddle/capi/tests/test_GradientMachine.cpp +++ b/paddle/capi/tests/test_GradientMachine.cpp @@ -1,15 +1,18 @@ #include +#include #include #include #include "PaddleCAPI.h" TEST(GradientMachine, load) { - void* buf; - int size; - ASSERT_EQ( - PD_NO_ERROR, - PDParseTrainerConfigFromFile(strdup("./vgg_16_cifar.py"), &buf, &size)); - free(buf); + paddle::TrainerConfigHelper config("./vgg_16_cifar.py"); + std::string buffer; + ASSERT_TRUE(config.getModelConfig().SerializeToString(&buffer)); + PD_GradiemtMachine machine; + + ASSERT_EQ(PD_NO_ERROR, + PDGradientMachineCreateForPredict( + &machine, &buffer[0], (int)buffer.size())); } int main(int argc, char** argv) { -- GitLab From a873a40e508a9a9de87c389a1418c5191f3d3b2e Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Thu, 5 Jan 2017 16:44:33 +0800 Subject: [PATCH 0004/3256] Try to use standard way to import gflags. * See [here](https://gflags.github.io/gflags/#cmake). --- CMakeLists.txt | 2 +- cmake/FindGflags.cmake | 582 ---------------------------------- cmake/util.cmake | 4 +- paddle/api/paddle_ld_flags.py | 8 +- 4 files changed, 5 insertions(+), 591 deletions(-) delete mode 100644 cmake/FindGflags.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 65fbbb481c4..0b7870cde52 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,7 +26,7 @@ find_package(NumPy REQUIRED) find_package(Threads REQUIRED) find_package(AVX QUIET) find_package(Glog REQUIRED) -find_package(Gflags REQUIRED) +find_package(gflags REQUIRED) find_package(GTest) find_package(Sphinx) find_package(Doxygen) diff --git a/cmake/FindGflags.cmake b/cmake/FindGflags.cmake deleted file mode 100644 index 6587089ba38..00000000000 --- a/cmake/FindGflags.cmake +++ /dev/null @@ -1,582 +0,0 @@ -# Ceres Solver - A fast non-linear least squares minimizer -# Copyright 2015 Google Inc. All rights reserved. -# http://ceres-solver.org/ -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, -# this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# * Neither the name of Google Inc. nor the names of its contributors may be -# used to endorse or promote products derived from this software without -# specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -# POSSIBILITY OF SUCH DAMAGE. -# -# Author: alexs.mac@gmail.com (Alex Stewart) -# - -# FindGflags.cmake - Find Google gflags logging library. -# -# This module will attempt to find gflags, either via an exported CMake -# configuration (generated by gflags >= 2.1 which are built with CMake), or -# by performing a standard search for all gflags components. The order of -# precedence for these two methods of finding gflags is controlled by: -# GFLAGS_PREFER_EXPORTED_GFLAGS_CMAKE_CONFIGURATION. -# -# This module defines the following variables: -# -# GFLAGS_FOUND: TRUE iff gflags is found. -# GFLAGS_INCLUDE_DIRS: Include directories for gflags. -# GFLAGS_LIBRARIES: Libraries required to link gflags. -# GFLAGS_NAMESPACE: The namespace in which gflags is defined. In versions of -# gflags < 2.1, this was google, for versions >= 2.1 it is -# by default gflags, although can be configured when building -# gflags to be something else (i.e. google for legacy -# compatibility). -# -# The following variables control the behaviour of this module when an exported -# gflags CMake configuration is not found. -# -# GFLAGS_PREFER_EXPORTED_GFLAGS_CMAKE_CONFIGURATION: TRUE/FALSE, iff TRUE then -# then prefer using an exported CMake configuration -# generated by gflags >= 2.1 over searching for the -# gflags components manually. Otherwise (FALSE) -# ignore any exported gflags CMake configurations and -# always perform a manual search for the components. -# Default: TRUE iff user does not define this variable -# before we are called, and does NOT specify either -# GFLAGS_INCLUDE_DIR_HINTS or GFLAGS_LIBRARY_DIR_HINTS -# otherwise FALSE. -# GFLAGS_INCLUDE_DIR_HINTS: List of additional directories in which to -# search for gflags includes, e.g: /timbuktu/include. -# GFLAGS_LIBRARY_DIR_HINTS: List of additional directories in which to -# search for gflags libraries, e.g: /timbuktu/lib. -# -# The following variables are also defined by this module, but in line with -# CMake recommended FindPackage() module style should NOT be referenced directly -# by callers (use the plural variables detailed above instead). These variables -# do however affect the behaviour of the module via FIND_[PATH/LIBRARY]() which -# are NOT re-called (i.e. search for library is not repeated) if these variables -# are set with valid values _in the CMake cache_. This means that if these -# variables are set directly in the cache, either by the user in the CMake GUI, -# or by the user passing -DVAR=VALUE directives to CMake when called (which -# explicitly defines a cache variable), then they will be used verbatim, -# bypassing the HINTS variables and other hard-coded search locations. -# -# GFLAGS_INCLUDE_DIR: Include directory for gflags, not including the -# include directory of any dependencies. -# GFLAGS_LIBRARY: gflags library, not including the libraries of any -# dependencies. - -# Reset CALLERS_CMAKE_FIND_LIBRARY_PREFIXES to its value when FindGflags was -# invoked, necessary for MSVC. -macro(GFLAGS_RESET_FIND_LIBRARY_PREFIX) - if (MSVC) - set(CMAKE_FIND_LIBRARY_PREFIXES "${CALLERS_CMAKE_FIND_LIBRARY_PREFIXES}") - endif (MSVC) -endmacro(GFLAGS_RESET_FIND_LIBRARY_PREFIX) - -# Called if we failed to find gflags or any of it's required dependencies, -# unsets all public (designed to be used externally) variables and reports -# error message at priority depending upon [REQUIRED/QUIET/] argument. -macro(GFLAGS_REPORT_NOT_FOUND REASON_MSG) - unset(GFLAGS_FOUND) - unset(GFLAGS_INCLUDE_DIRS) - unset(GFLAGS_LIBRARIES) - # Do not use unset, as we want to keep GFLAGS_NAMESPACE in the cache, - # but simply clear its value. - set(GFLAGS_NAMESPACE "" CACHE STRING - "gflags namespace (google or gflags)" FORCE) - - # Make results of search visible in the CMake GUI if gflags has not - # been found so that user does not have to toggle to advanced view. - mark_as_advanced(CLEAR GFLAGS_INCLUDE_DIR - GFLAGS_LIBRARY - GFLAGS_NAMESPACE) - - gflags_reset_find_library_prefix() - - # Note _FIND_[REQUIRED/QUIETLY] variables defined by FindPackage() - # use the camelcase library name, not uppercase. - if (Gflags_FIND_QUIETLY) - message(STATUS "Failed to find gflags - " ${REASON_MSG} ${ARGN}) - elseif (Gflags_FIND_REQUIRED) - message(FATAL_ERROR "Failed to find gflags - " ${REASON_MSG} ${ARGN}) - else() - # Neither QUIETLY nor REQUIRED, use no priority which emits a message - # but continues configuration and allows generation. - message("-- Failed to find gflags - " ${REASON_MSG} ${ARGN}) - endif () - return() -endmacro(GFLAGS_REPORT_NOT_FOUND) - -# Verify that all variable names passed as arguments are defined (can be empty -# but must be defined) or raise a fatal error. -macro(GFLAGS_CHECK_VARS_DEFINED) - foreach(CHECK_VAR ${ARGN}) - if (NOT DEFINED ${CHECK_VAR}) - message(FATAL_ERROR "Ceres Bug: ${CHECK_VAR} is not defined.") - endif() - endforeach() -endmacro(GFLAGS_CHECK_VARS_DEFINED) - -# Use check_cxx_source_compiles() to compile trivial test programs to determine -# the gflags namespace. This works on all OSs except Windows. If using Visual -# Studio, it fails because msbuild forces check_cxx_source_compiles() to use -# CMAKE_BUILD_TYPE=Debug for the test project, which usually breaks detection -# because MSVC requires that the test project use the same build type as gflags, -# which would normally be built in Release. -# -# Defines: GFLAGS_NAMESPACE in the caller's scope with the detected namespace, -# which is blank (empty string, will test FALSE is CMake conditionals) -# if detection failed. -function(GFLAGS_CHECK_GFLAGS_NAMESPACE_USING_TRY_COMPILE) - # Verify that all required variables are defined. - gflags_check_vars_defined( - GFLAGS_INCLUDE_DIR GFLAGS_LIBRARY) - # Ensure that GFLAGS_NAMESPACE is always unset on completion unless - # we explicitly set if after having the correct namespace. - set(GFLAGS_NAMESPACE "" PARENT_SCOPE) - - include(CheckCXXSourceCompiles) - # Setup include path & link library for gflags for CHECK_CXX_SOURCE_COMPILES. - set(CMAKE_REQUIRED_INCLUDES ${GFLAGS_INCLUDE_DIR}) - set(CMAKE_REQUIRED_LIBRARIES ${GFLAGS_LIBRARY} ${GFLAGS_LINK_LIBRARIES}) - # First try the (older) google namespace. Note that the output variable - # MUST be unique to the build type as otherwise the test is not repeated as - # it is assumed to have already been performed. - check_cxx_source_compiles( - "#include - int main(int argc, char * argv[]) { - google::ParseCommandLineFlags(&argc, &argv, true); - return 0; - }" - GFLAGS_IN_GOOGLE_NAMESPACE) - if (GFLAGS_IN_GOOGLE_NAMESPACE) - set(GFLAGS_NAMESPACE google PARENT_SCOPE) - return() - endif() - - # Try (newer) gflags namespace instead. Note that the output variable - # MUST be unique to the build type as otherwise the test is not repeated as - # it is assumed to have already been performed. - set(CMAKE_REQUIRED_INCLUDES ${GFLAGS_INCLUDE_DIR}) - set(CMAKE_REQUIRED_LIBRARIES ${GFLAGS_LIBRARY} ${GFLAGS_LINK_LIBRARIES}) - check_cxx_source_compiles( - "#include - int main(int argc, char * argv[]) { - gflags::ParseCommandLineFlags(&argc, &argv, true); - return 0; - }" - GFLAGS_IN_GFLAGS_NAMESPACE) - if (GFLAGS_IN_GFLAGS_NAMESPACE) - set(GFLAGS_NAMESPACE gflags PARENT_SCOPE) - return() - endif (GFLAGS_IN_GFLAGS_NAMESPACE) -endfunction(GFLAGS_CHECK_GFLAGS_NAMESPACE_USING_TRY_COMPILE) - -# Use regex on the gflags headers to attempt to determine the gflags namespace. -# Checks both gflags.h (contained namespace on versions < 2.1.2) and -# gflags_declare.h, which contains the namespace on versions >= 2.1.2. -# In general, this method should only be used when -# GFLAGS_CHECK_GFLAGS_NAMESPACE_USING_TRY_COMPILE() cannot be used, or has -# failed. -# -# Defines: GFLAGS_NAMESPACE in the caller's scope with the detected namespace, -# which is blank (empty string, will test FALSE is CMake conditionals) -# if detection failed. -function(GFLAGS_CHECK_GFLAGS_NAMESPACE_USING_REGEX) - # Verify that all required variables are defined. - gflags_check_vars_defined(GFLAGS_INCLUDE_DIR) - # Ensure that GFLAGS_NAMESPACE is always undefined on completion unless - # we explicitly set if after having the correct namespace. - set(GFLAGS_NAMESPACE "" PARENT_SCOPE) - - # Scan gflags.h to identify what namespace gflags was built with. On - # versions of gflags < 2.1.2, gflags.h was configured with the namespace - # directly, on >= 2.1.2, gflags.h uses the GFLAGS_NAMESPACE #define which - # is defined in gflags_declare.h, we try each location in turn. - set(GFLAGS_HEADER_FILE ${GFLAGS_INCLUDE_DIR}/gflags/gflags.h) - if (NOT EXISTS ${GFLAGS_HEADER_FILE}) - gflags_report_not_found( - "Could not find file: ${GFLAGS_HEADER_FILE} " - "containing namespace information in gflags install located at: " - "${GFLAGS_INCLUDE_DIR}.") - endif() - file(READ ${GFLAGS_HEADER_FILE} GFLAGS_HEADER_FILE_CONTENTS) - - string(REGEX MATCH "namespace [A-Za-z]+" - GFLAGS_NAMESPACE "${GFLAGS_HEADER_FILE_CONTENTS}") - string(REGEX REPLACE "namespace ([A-Za-z]+)" "\\1" - GFLAGS_NAMESPACE "${GFLAGS_NAMESPACE}") - - if (NOT GFLAGS_NAMESPACE) - gflags_report_not_found( - "Failed to extract gflags namespace from header file: " - "${GFLAGS_HEADER_FILE}.") - endif (NOT GFLAGS_NAMESPACE) - - if (GFLAGS_NAMESPACE STREQUAL "google" OR - GFLAGS_NAMESPACE STREQUAL "gflags") - # Found valid gflags namespace from gflags.h. - set(GFLAGS_NAMESPACE "${GFLAGS_NAMESPACE}" PARENT_SCOPE) - return() - endif() - - # Failed to find gflags namespace from gflags.h, gflags is likely a new - # version, check gflags_declare.h, which in newer versions (>= 2.1.2) contains - # the GFLAGS_NAMESPACE #define, which is then referenced in gflags.h. - set(GFLAGS_DECLARE_FILE ${GFLAGS_INCLUDE_DIR}/gflags/gflags_declare.h) - if (NOT EXISTS ${GFLAGS_DECLARE_FILE}) - gflags_report_not_found( - "Could not find file: ${GFLAGS_DECLARE_FILE} " - "containing namespace information in gflags install located at: " - "${GFLAGS_INCLUDE_DIR}.") - endif() - file(READ ${GFLAGS_DECLARE_FILE} GFLAGS_DECLARE_FILE_CONTENTS) - - string(REGEX MATCH "#define GFLAGS_NAMESPACE [A-Za-z]+" - GFLAGS_NAMESPACE "${GFLAGS_DECLARE_FILE_CONTENTS}") - string(REGEX REPLACE "#define GFLAGS_NAMESPACE ([A-Za-z]+)" "\\1" - GFLAGS_NAMESPACE "${GFLAGS_NAMESPACE}") - - if (NOT GFLAGS_NAMESPACE) - gflags_report_not_found( - "Failed to extract gflags namespace from declare file: " - "${GFLAGS_DECLARE_FILE}.") - endif (NOT GFLAGS_NAMESPACE) - - if (GFLAGS_NAMESPACE STREQUAL "google" OR - GFLAGS_NAMESPACE STREQUAL "gflags") - # Found valid gflags namespace from gflags.h. - set(GFLAGS_NAMESPACE "${GFLAGS_NAMESPACE}" PARENT_SCOPE) - return() - endif() -endfunction(GFLAGS_CHECK_GFLAGS_NAMESPACE_USING_REGEX) - -# ----------------------------------------------------------------- -# By default, if the user has expressed no preference for using an exported -# gflags CMake configuration over performing a search for the installed -# components, and has not specified any hints for the search locations, then -# prefer a gflags exported configuration if available. -if (NOT DEFINED GFLAGS_PREFER_EXPORTED_GFLAGS_CMAKE_CONFIGURATION - AND NOT GFLAGS_INCLUDE_DIR_HINTS - AND NOT GFLAGS_LIBRARY_DIR_HINTS) - message(STATUS "No preference for use of exported gflags CMake configuration " - "set, and no hints for include/library directories provided. " - "Defaulting to preferring an installed/exported gflags CMake configuration " - "if available.") - set(GFLAGS_PREFER_EXPORTED_GFLAGS_CMAKE_CONFIGURATION TRUE) -endif() - -if (GFLAGS_PREFER_EXPORTED_GFLAGS_CMAKE_CONFIGURATION) - # Try to find an exported CMake configuration for gflags, as generated by - # gflags versions >= 2.1. - # - # We search twice, s/t we can invert the ordering of precedence used by - # find_package() for exported package build directories, and installed - # packages (found via CMAKE_SYSTEM_PREFIX_PATH), listed as items 6) and 7) - # respectively in [1]. - # - # By default, exported build directories are (in theory) detected first, and - # this is usually the case on Windows. However, on OS X & Linux, the install - # path (/usr/local) is typically present in the PATH environment variable - # which is checked in item 4) in [1] (i.e. before both of the above, unless - # NO_SYSTEM_ENVIRONMENT_PATH is passed). As such on those OSs installed - # packages are usually detected in preference to exported package build - # directories. - # - # To ensure a more consistent response across all OSs, and as users usually - # want to prefer an installed version of a package over a locally built one - # where both exist (esp. as the exported build directory might be removed - # after installation), we first search with NO_CMAKE_PACKAGE_REGISTRY which - # means any build directories exported by the user are ignored, and thus - # installed directories are preferred. If this fails to find the package - # we then research again, but without NO_CMAKE_PACKAGE_REGISTRY, so any - # exported build directories will now be detected. - # - # To prevent confusion on Windows, we also pass NO_CMAKE_BUILDS_PATH (which - # is item 5) in [1]), to not preferentially use projects that were built - # recently with the CMake GUI to ensure that we always prefer an installed - # version if available. - # - # [1] http://www.cmake.org/cmake/help/v2.8.11/cmake.html#command:find_package - find_package(gflags QUIET - NO_MODULE - NO_CMAKE_PACKAGE_REGISTRY - NO_CMAKE_BUILDS_PATH) - if (gflags_FOUND) - message(STATUS "Found installed version of gflags: ${gflags_DIR}") - else(gflags_FOUND) - # Failed to find an installed version of gflags, repeat search allowing - # exported build directories. - message(STATUS "Failed to find installed gflags CMake configuration, " - "searching for gflags build directories exported with CMake.") - # Again pass NO_CMAKE_BUILDS_PATH, as we know that gflags is exported and - # do not want to treat projects built with the CMake GUI preferentially. - find_package(gflags QUIET - NO_MODULE - NO_CMAKE_BUILDS_PATH) - if (gflags_FOUND) - message(STATUS "Found exported gflags build directory: ${gflags_DIR}") - endif(gflags_FOUND) - endif(gflags_FOUND) - - set(FOUND_INSTALLED_GFLAGS_CMAKE_CONFIGURATION ${gflags_FOUND}) - - # gflags v2.1 - 2.1.2 shipped with a bug in their gflags-config.cmake [1] - # whereby gflags_LIBRARIES = "gflags", but there was no imported target - # called "gflags", they were called: gflags[_nothreads]-[static/shared]. - # As this causes linker errors when gflags is not installed in a location - # on the current library paths, detect if this problem is present and - # fix it. - # - # [1] https://github.com/gflags/gflags/issues/110 - if (gflags_FOUND) - # NOTE: This is not written as additional conditions in the outer - # if (gflags_FOUND) as the NOT TARGET "${gflags_LIBRARIES}" - # condition causes problems if gflags is not found. - if (${gflags_VERSION} VERSION_LESS 2.1.3 AND - NOT TARGET "${gflags_LIBRARIES}") - message(STATUS "Detected broken gflags install in: ${gflags_DIR}, " - "version: ${gflags_VERSION} <= 2.1.2 which defines gflags_LIBRARIES = " - "${gflags_LIBRARIES} which is not an imported CMake target, see: " - "https://github.com/gflags/gflags/issues/110. Attempting to fix by " - "detecting correct gflags target.") - # Ordering here expresses preference for detection, specifically we do not - # want to use the _nothreads variants if the full library is available. - list(APPEND CHECK_GFLAGS_IMPORTED_TARGET_NAMES - gflags-shared gflags-static - gflags_nothreads-shared gflags_nothreads-static) - foreach(CHECK_GFLAGS_TARGET ${CHECK_GFLAGS_IMPORTED_TARGET_NAMES}) - if (TARGET ${CHECK_GFLAGS_TARGET}) - message(STATUS "Found valid gflags target: ${CHECK_GFLAGS_TARGET}, " - "updating gflags_LIBRARIES.") - set(gflags_LIBRARIES ${CHECK_GFLAGS_TARGET}) - break() - endif() - endforeach() - if (NOT TARGET ${gflags_LIBRARIES}) - message(STATUS "Failed to fix detected broken gflags install in: " - "${gflags_DIR}, version: ${gflags_VERSION} <= 2.1.2, none of the " - "imported targets for gflags: ${CHECK_GFLAGS_IMPORTED_TARGET_NAMES} " - "are defined. Will continue with a manual search for gflags " - "components. We recommend you build/install a version of gflags > " - "2.1.2 (or master).") - set(FOUND_INSTALLED_GFLAGS_CMAKE_CONFIGURATION FALSE) - endif() - endif() - endif() - - if (FOUND_INSTALLED_GFLAGS_CMAKE_CONFIGURATION) - message(STATUS "Detected gflags version: ${gflags_VERSION}") - set(GFLAGS_FOUND ${gflags_FOUND}) - set(GFLAGS_INCLUDE_DIR ${gflags_INCLUDE_DIR}) - set(GFLAGS_LIBRARY ${gflags_LIBRARIES}) - - # gflags does not export the namespace in their CMake configuration, so - # use our function to determine what it should be, as it can be either - # gflags or google dependent upon version & configuration. - # - # NOTE: We use the regex method to determine the namespace here, as - # check_cxx_source_compiles() will not use imported targets, which - # is what gflags will be in this case. - gflags_check_gflags_namespace_using_regex() - - if (NOT GFLAGS_NAMESPACE) - gflags_report_not_found( - "Failed to determine gflags namespace using regex for gflags " - "version: ${gflags_VERSION} exported here: ${gflags_DIR} using CMake.") - endif (NOT GFLAGS_NAMESPACE) - else (FOUND_INSTALLED_GFLAGS_CMAKE_CONFIGURATION) - message(STATUS "Failed to find an installed/exported CMake configuration " - "for gflags, will perform search for installed gflags components.") - endif (FOUND_INSTALLED_GFLAGS_CMAKE_CONFIGURATION) -endif(GFLAGS_PREFER_EXPORTED_GFLAGS_CMAKE_CONFIGURATION) - -if (NOT GFLAGS_FOUND) - # Either failed to find an exported gflags CMake configuration, or user - # told us not to use one. Perform a manual search for all gflags components. - - # Handle possible presence of lib prefix for libraries on MSVC, see - # also GFLAGS_RESET_FIND_LIBRARY_PREFIX(). - if (MSVC) - # Preserve the caller's original values for CMAKE_FIND_LIBRARY_PREFIXES - # s/t we can set it back before returning. - set(CALLERS_CMAKE_FIND_LIBRARY_PREFIXES "${CMAKE_FIND_LIBRARY_PREFIXES}") - # The empty string in this list is important, it represents the case when - # the libraries have no prefix (shared libraries / DLLs). - set(CMAKE_FIND_LIBRARY_PREFIXES "lib" "" "${CMAKE_FIND_LIBRARY_PREFIXES}") - endif (MSVC) - - # Search user-installed locations first, so that we prefer user installs - # to system installs where both exist. - list(APPEND GFLAGS_CHECK_INCLUDE_DIRS - /usr/local/include - /usr/local/homebrew/include # Mac OS X - /opt/local/var/macports/software # Mac OS X. - /opt/local/include - /usr/include) - list(APPEND GFLAGS_CHECK_PATH_SUFFIXES - gflags/include # Windows (for C:/Program Files prefix). - gflags/Include ) # Windows (for C:/Program Files prefix). - - list(APPEND GFLAGS_CHECK_LIBRARY_DIRS - /usr/local/lib - /usr/local/homebrew/lib # Mac OS X. - /opt/local/lib - /usr/lib) - list(APPEND GFLAGS_CHECK_LIBRARY_SUFFIXES - gflags/lib # Windows (for C:/Program Files prefix). - gflags/Lib ) # Windows (for C:/Program Files prefix). - - # Search supplied hint directories first if supplied. - find_path(GFLAGS_INCLUDE_DIR - NAMES gflags/gflags.h - PATHS ${GFLAGS_INCLUDE_DIR_HINTS} - ${GFLAGS_CHECK_INCLUDE_DIRS} - PATH_SUFFIXES ${GFLAGS_CHECK_PATH_SUFFIXES}) - if (NOT GFLAGS_INCLUDE_DIR OR - NOT EXISTS ${GFLAGS_INCLUDE_DIR}) - gflags_report_not_found( - "Could not find gflags include directory, set GFLAGS_INCLUDE_DIR " - "to directory containing gflags/gflags.h") - endif (NOT GFLAGS_INCLUDE_DIR OR - NOT EXISTS ${GFLAGS_INCLUDE_DIR}) - - find_library(GFLAGS_LIBRARY NAMES gflags - PATHS ${GFLAGS_LIBRARY_DIR_HINTS} - ${GFLAGS_CHECK_LIBRARY_DIRS} - PATH_SUFFIXES ${GFLAGS_CHECK_LIBRARY_SUFFIXES}) - if (NOT GFLAGS_LIBRARY OR - NOT EXISTS ${GFLAGS_LIBRARY}) - gflags_report_not_found( - "Could not find gflags library, set GFLAGS_LIBRARY " - "to full path to libgflags.") - endif (NOT GFLAGS_LIBRARY OR - NOT EXISTS ${GFLAGS_LIBRARY}) - - # gflags typically requires a threading library (which is OS dependent), note - # that this defines the CMAKE_THREAD_LIBS_INIT variable. If we are able to - # detect threads, we assume that gflags requires it. - find_package(Threads QUIET) - set(GFLAGS_LINK_LIBRARIES ${CMAKE_THREAD_LIBS_INIT}) - # On Windows (including MinGW), the Shlwapi library is used by gflags if - # available. - if (WIN32) - include(CheckIncludeFileCXX) - check_include_file_cxx("shlwapi.h" HAVE_SHLWAPI) - if (HAVE_SHLWAPI) - list(APPEND GFLAGS_LINK_LIBRARIES shlwapi.lib) - endif(HAVE_SHLWAPI) - endif (WIN32) - - # Mark internally as found, then verify. GFLAGS_REPORT_NOT_FOUND() unsets - # if called. - set(GFLAGS_FOUND TRUE) - - # Identify what namespace gflags was built with. - if (GFLAGS_INCLUDE_DIR AND NOT GFLAGS_NAMESPACE) - # To handle Windows peculiarities / CMake bugs on MSVC we try two approaches - # to detect the gflags namespace: - # - # 1) Try to use check_cxx_source_compiles() to compile a trivial program - # with the two choices for the gflags namespace. - # - # 2) [In the event 1) fails] Use regex on the gflags headers to try to - # determine the gflags namespace. Whilst this is less robust than 1), - # it does avoid any interaction with msbuild. - gflags_check_gflags_namespace_using_try_compile() - - if (NOT GFLAGS_NAMESPACE) - # Failed to determine gflags namespace using check_cxx_source_compiles() - # method, try and obtain it using regex on the gflags headers instead. - message(STATUS "Failed to find gflags namespace using using " - "check_cxx_source_compiles(), trying namespace regex instead, " - "this is expected on Windows.") - gflags_check_gflags_namespace_using_regex() - - if (NOT GFLAGS_NAMESPACE) - gflags_report_not_found( - "Failed to determine gflags namespace either by " - "check_cxx_source_compiles(), or namespace regex.") - endif (NOT GFLAGS_NAMESPACE) - endif (NOT GFLAGS_NAMESPACE) - endif (GFLAGS_INCLUDE_DIR AND NOT GFLAGS_NAMESPACE) - - # Make the GFLAGS_NAMESPACE a cache variable s/t the user can view it, and could - # overwrite it in the CMake GUI. - set(GFLAGS_NAMESPACE "${GFLAGS_NAMESPACE}" CACHE STRING - "gflags namespace (google or gflags)" FORCE) - - # gflags does not seem to provide any record of the version in its - # source tree, thus cannot extract version. - - # Catch case when caller has set GFLAGS_NAMESPACE in the cache / GUI - # with an invalid value. - if (GFLAGS_NAMESPACE AND - NOT GFLAGS_NAMESPACE STREQUAL "google" AND - NOT GFLAGS_NAMESPACE STREQUAL "gflags") - gflags_report_not_found( - "Caller defined GFLAGS_NAMESPACE:" - " ${GFLAGS_NAMESPACE} is not valid, not google or gflags.") - endif () - # Catch case when caller has set GFLAGS_INCLUDE_DIR in the cache / GUI and - # thus FIND_[PATH/LIBRARY] are not called, but specified locations are - # invalid, otherwise we would report the library as found. - if (GFLAGS_INCLUDE_DIR AND - NOT EXISTS ${GFLAGS_INCLUDE_DIR}/gflags/gflags.h) - gflags_report_not_found( - "Caller defined GFLAGS_INCLUDE_DIR:" - " ${GFLAGS_INCLUDE_DIR} does not contain gflags/gflags.h header.") - endif (GFLAGS_INCLUDE_DIR AND - NOT EXISTS ${GFLAGS_INCLUDE_DIR}/gflags/gflags.h) - # TODO: This regex for gflags library is pretty primitive, we use lowercase - # for comparison to handle Windows using CamelCase library names, could - # this check be better? - string(TOLOWER "${GFLAGS_LIBRARY}" LOWERCASE_GFLAGS_LIBRARY) - if (GFLAGS_LIBRARY AND - NOT "${LOWERCASE_GFLAGS_LIBRARY}" MATCHES ".*gflags[^/]*") - gflags_report_not_found( - "Caller defined GFLAGS_LIBRARY: " - "${GFLAGS_LIBRARY} does not match gflags.") - endif (GFLAGS_LIBRARY AND - NOT "${LOWERCASE_GFLAGS_LIBRARY}" MATCHES ".*gflags[^/]*") - - gflags_reset_find_library_prefix() - -endif(NOT GFLAGS_FOUND) - -# Set standard CMake FindPackage variables if found. -if (GFLAGS_FOUND) - set(GFLAGS_INCLUDE_DIRS ${GFLAGS_INCLUDE_DIR}) - set(GFLAGS_LIBRARIES ${GFLAGS_LIBRARY} ${GFLAGS_LINK_LIBRARIES}) -endif (GFLAGS_FOUND) - -# Handle REQUIRED / QUIET optional arguments. -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(Gflags DEFAULT_MSG - GFLAGS_INCLUDE_DIRS GFLAGS_LIBRARIES GFLAGS_NAMESPACE) - -# Only mark internal variables as advanced if we found gflags, otherwise -# leave them visible in the standard GUI for the user to set manually. -if (GFLAGS_FOUND) - mark_as_advanced(FORCE GFLAGS_INCLUDE_DIR - GFLAGS_LIBRARY - GFLAGS_NAMESPACE - gflags_DIR) # Autogenerated by find_package(gflags) -endif (GFLAGS_FOUND) diff --git a/cmake/util.cmake b/cmake/util.cmake index 43a56378df0..ab77d9e5def 100644 --- a/cmake/util.cmake +++ b/cmake/util.cmake @@ -110,8 +110,8 @@ function(link_paddle_exe TARGET_NAME) ${METRIC_LIBS} ${PROTOBUF_LIBRARY} ${LIBGLOG_LIBRARY} - ${GFLAGS_LIBRARIES} - ${CMAKE_THREAD_LIBS_INIT} + gflags + ${CMAKE_THREAD_LIBS_INIT} ${CBLAS_LIBS} ${ZLIB_LIBRARIES} ${INTERAL_LIBS} diff --git a/paddle/api/paddle_ld_flags.py b/paddle/api/paddle_ld_flags.py index b4d27b1cc72..152085db847 100644 --- a/paddle/api/paddle_ld_flags.py +++ b/paddle/api/paddle_ld_flags.py @@ -50,7 +50,6 @@ try: self.glog_libs = LIBGLOG_LIBRARY self.with_coverage = PaddleLDFlag.cmake_bool(WITH_COVERALLS) - self.gflags_libs = GFLAGS_LIBRARIES self.gflags_location = GFLAGS_LOCATION self.cblas_libs = CBLAS_LIBRARIES self.curt = CUDA_LIBRARIES @@ -88,7 +87,7 @@ try: "-lpaddle_api", self.normalize_flag(self.protolib), self.normalize_flag(self.glog_libs), - self.normalize_flag(self.gflags_libs), + self.normalize_flag("gflags"), self.normalize_flag(self.zlib), self.normalize_flag(self.thread), self.normalize_flag(self.dl_libs), @@ -114,10 +113,7 @@ try: return cmake_flag elif cmake_flag.startswith("-l"): # normal link command return cmake_flag - elif cmake_flag in [ - "gflags-shared", "gflags-static", "gflags_nothreads-shared", - "gflags_nothreads-static" - ]: # special for gflags + elif cmake_flag == "gflags": # special for gflags assert PaddleLDFlag.cmake_bool(self.gflags_location) return self.gflags_location elif len(cmake_flag) != 0: -- GitLab From fdb64acc50e88ae08d074550de97fa39d151b653 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Thu, 5 Jan 2017 20:54:37 +0800 Subject: [PATCH 0005/3256] add unittest for prediction --- paddle/capi/Arguments.cpp | 52 ++++++++------ paddle/capi/CMakeLists.txt | 25 +------ paddle/capi/GradientMachine.cpp | 62 ++++++++++++++-- paddle/capi/Main.cpp | 2 +- paddle/capi/Matrix.cpp | 28 ++++---- paddle/capi/PaddleCAPI.h | 33 ++++++--- paddle/capi/PaddleCAPIPrivate.h | 24 ++++++- paddle/capi/Vector.cpp | 36 +++++----- paddle/capi/tests/.gitignore | 2 + paddle/capi/tests/CMakeLists.txt | 41 +++-------- paddle/capi/tests/test_Arguments.cpp | 32 +++++---- paddle/capi/tests/test_GradientMachine.cpp | 84 +++++++++++++++++++++- paddle/capi/tests/test_Matrix.cpp | 16 ++--- paddle/capi/tests/test_Vector.cpp | 9 +-- paddle/capi/tests/test_predict_network.py | 13 ++++ paddle/capi/tests/vgg_16_cifar.py | 1 - 16 files changed, 304 insertions(+), 156 deletions(-) create mode 100644 paddle/capi/tests/.gitignore create mode 100644 paddle/capi/tests/test_predict_network.py delete mode 120000 paddle/capi/tests/vgg_16_cifar.py diff --git a/paddle/capi/Arguments.cpp b/paddle/capi/Arguments.cpp index cf773a65872..b983d72bb42 100644 --- a/paddle/capi/Arguments.cpp +++ b/paddle/capi/Arguments.cpp @@ -1,49 +1,61 @@ #include "PaddleCAPI.h" #include "PaddleCAPIPrivate.h" -#define cast(v) paddle::capi::cast(v) +using paddle::capi::cast; + +#define castArg(v) cast(v) +#define castIVec(v) cast(v) extern "C" { int PDArgsCreateNone(PD_Arguments* args) { auto ptr = new paddle::capi::CArguments(); *args = ptr; - return PD_NO_ERROR; + return kPD_NO_ERROR; } int PDArgsDestroy(PD_Arguments args) { - if (args == nullptr) return PD_NULLPTR; - delete cast(args); - return PD_NO_ERROR; + if (args == nullptr) return kPD_NULLPTR; + delete castArg(args); + return kPD_NO_ERROR; } int PDArgsGetSize(PD_Arguments args, uint64_t* size) { - if (args == nullptr || size == nullptr) return PD_NULLPTR; - *size = cast(args)->args.size(); - return PD_NO_ERROR; + if (args == nullptr || size == nullptr) return kPD_NULLPTR; + *size = castArg(args)->args.size(); + return kPD_NO_ERROR; } int PDArgsResize(PD_Arguments args, uint64_t size) { - if (args == nullptr) return PD_NULLPTR; - cast(args)->args.resize(size); - return PD_NO_ERROR; + if (args == nullptr) return kPD_NULLPTR; + castArg(args)->args.resize(size); + return kPD_NO_ERROR; } int PDArgsSetValue(PD_Arguments args, uint64_t ID, PD_Matrix mat) { - if (args == nullptr || mat == nullptr) return PD_NULLPTR; + if (args == nullptr || mat == nullptr) return kPD_NULLPTR; auto m = paddle::capi::cast(mat); - if (m->mat == nullptr) return PD_NULLPTR; - auto a = cast(args); - if (ID >= a->args.size()) return PD_OUT_OF_RANGE; + if (m->mat == nullptr) return kPD_NULLPTR; + auto a = castArg(args); + if (ID >= a->args.size()) return kPD_OUT_OF_RANGE; a->args[ID].value = m->mat; - return PD_NO_ERROR; + return kPD_NO_ERROR; } int PDArgsGetValue(PD_Arguments args, uint64_t ID, PD_Matrix mat) { - if (args == nullptr || mat == nullptr) return PD_NULLPTR; + if (args == nullptr || mat == nullptr) return kPD_NULLPTR; auto m = paddle::capi::cast(mat); - auto a = cast(args); - if (ID >= a->args.size()) return PD_OUT_OF_RANGE; + auto a = castArg(args); + if (ID >= a->args.size()) return kPD_OUT_OF_RANGE; m->mat = a->args[ID].value; - return PD_NO_ERROR; + return kPD_NO_ERROR; +} + +int PDArgsGetIds(PD_Arguments args, uint64_t ID, PD_IVector ids) { + if (args == nullptr || ids == nullptr) return kPD_NULLPTR; + auto iv = castIVec(ids); + auto a = castArg(args); + if (ID >= a->args.size()) return kPD_OUT_OF_RANGE; + iv->vec = a->args[ID].ids; + return kPD_NO_ERROR; } } diff --git a/paddle/capi/CMakeLists.txt b/paddle/capi/CMakeLists.txt index 80cf2c7fa91..93b6b41254d 100644 --- a/paddle/capi/CMakeLists.txt +++ b/paddle/capi/CMakeLists.txt @@ -12,34 +12,11 @@ set(CAPI_PRIVATE_HEADER PaddleCAPIPrivate.h) file(GLOB CAPI_SOURCES *.cpp) -add_library(paddle_capi SHARED ${CAPI_SOURCES}) +add_library(paddle_capi STATIC ${CAPI_SOURCES}) target_include_directories(paddle_capi PUBLIC ${CMAKE_CURRENT_BINARY_DIR}) add_dependencies(paddle_capi gen_proto_cpp) -target_link_libraries(paddle_capi - "-Wl,-force_load" - paddle_gserver - "-Wl,-force_load" - paddle_function - paddle_pserver - paddle_trainer_lib - paddle_network - paddle_math - paddle_utils - paddle_parameter - paddle_proto - paddle_cuda - ${PROTOBUF_LIBRARY} - ${LIBGLOG_LIBRARY} - gflags - ${CMAKE_THREAD_LIBS_INIT} - ${CBLAS_LIBS} - ${ZLIB_LIBRARIES} - ${INTERAL_LIBS} - ${CMAKE_DL_LIBS} - ${PYTHON_LIBRARIES}) - set(PADDLE_CAPI_INC_PATH ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/paddle/capi/GradientMachine.cpp b/paddle/capi/GradientMachine.cpp index 2969b5f198f..ef584ed8d0d 100644 --- a/paddle/capi/GradientMachine.cpp +++ b/paddle/capi/GradientMachine.cpp @@ -27,22 +27,76 @@ extern "C" { int PDGradientMachineCreateForPredict(PD_GradiemtMachine* machine, void* modelConfigProtobuf, int size) { - if (modelConfigProtobuf == nullptr) return PD_NULLPTR; + if (modelConfigProtobuf == nullptr) return kPD_NULLPTR; paddle::ModelConfig config; if (!config.ParseFromArray(modelConfigProtobuf, size) || !config.IsInitialized()) { - return PD_PROTOBUF_ERROR; + return kPD_PROTOBUF_ERROR; } auto ptr = new paddle::capi::CGradientMachine(); ptr->machine.reset(paddle::GradientMachine::create( config, CREATE_MODE_TESTING, {paddle::PARAMETER_VALUE})); *machine = ptr; - return PD_NO_ERROR; + return kPD_NO_ERROR; } int PDGradientMachineDestroy(PD_GradiemtMachine machine) { delete cast(machine); - return PD_NO_ERROR; + return kPD_NO_ERROR; +} + +int PDGradientMachineLoadParameterFromDisk(PD_GradiemtMachine machine, + const char* path) { + auto m = cast(machine); + if (m == nullptr || path == nullptr || m->machine == nullptr) + return kPD_NULLPTR; + m->machine->loadParameters(path); + return kPD_NO_ERROR; +} + +int PDGradientMachineForward(PD_GradiemtMachine machine, + PD_Arguments inArgs, + PD_Arguments outArgs, + bool isTrain) { + auto m = cast(machine); + auto in = paddle::capi::cast(inArgs); + auto out = paddle::capi::cast(outArgs); + if (m == nullptr || in == nullptr || out == nullptr || m->machine == nullptr) + return kPD_NULLPTR; + m->machine->forward( + in->args, &out->args, isTrain ? paddle::PASS_TRAIN : paddle::PASS_TEST); + return kPD_NO_ERROR; +} + +int PDGradientMachineCreateSharedParam(PD_GradiemtMachine origin, + void* modelConfigProtobuf, + int size, + PD_GradiemtMachine* slave) { + auto o = cast(origin); + if (origin == nullptr || slave == nullptr || o->machine == nullptr) { + return kPD_NULLPTR; + } + paddle::ModelConfig config; + if (!config.ParseFromArray(modelConfigProtobuf, size) || + !config.IsInitialized()) { + return kPD_PROTOBUF_ERROR; + } + + std::unique_ptr ptr( + new paddle::capi::CGradientMachine()); + auto nn = paddle::NeuralNetwork::create(config); + nn->init(config, + [&o](int paramId, paddle::Parameter* param) { + auto p = o->machine->getParameters()[paramId]; + param->enableSharedType(paddle::PARAMETER_VALUE, + p->getBuf(paddle::PARAMETER_VALUE)); + + }, + {paddle::PARAMETER_VALUE}, + false); + ptr->machine.reset(nn); + *slave = ptr.release(); + return kPD_NO_ERROR; } } diff --git a/paddle/capi/Main.cpp b/paddle/capi/Main.cpp index cc07e2ba4ef..8cd0104be2e 100644 --- a/paddle/capi/Main.cpp +++ b/paddle/capi/Main.cpp @@ -24,6 +24,6 @@ int PDInit(int argc, char** argv) { } initPaddle(argc + 1, realArgv.data()); free(realArgv[0]); - return PD_NO_ERROR; + return kPD_NO_ERROR; } } diff --git a/paddle/capi/Matrix.cpp b/paddle/capi/Matrix.cpp index 71598b1714d..dc1b4f3379d 100644 --- a/paddle/capi/Matrix.cpp +++ b/paddle/capi/Matrix.cpp @@ -8,27 +8,27 @@ int PDMatCreate(PD_Matrix* mat, uint64_t height, uint64_t width, bool useGpu) { auto ptr = new paddle::capi::CMatrix(); ptr->mat = paddle::Matrix::create(height, width, false, useGpu); *mat = ptr; - return PD_NO_ERROR; + return kPD_NO_ERROR; } int PDMatCreateNone(PD_Matrix* mat) { auto ptr = new paddle::capi::CMatrix(); *mat = ptr; - return PD_NO_ERROR; + return kPD_NO_ERROR; } int PDMatDestroy(PD_Matrix mat) { - if (mat == nullptr) return PD_NULLPTR; + if (mat == nullptr) return kPD_NULLPTR; auto ptr = cast(mat); delete ptr; - return PD_NO_ERROR; + return kPD_NO_ERROR; } int PDMatCopyToRow(PD_Matrix mat, uint64_t rowID, pd_real* rowArray) { - if (mat == nullptr) return PD_NULLPTR; + if (mat == nullptr) return kPD_NULLPTR; auto ptr = cast(mat); - if (ptr->mat == nullptr) return PD_NULLPTR; - if (rowID >= ptr->mat->getHeight()) return PD_OUT_OF_RANGE; + if (ptr->mat == nullptr) return kPD_NULLPTR; + if (rowID >= ptr->mat->getHeight()) return kPD_OUT_OF_RANGE; paddle::real* buf = ptr->mat->getRowBuf(rowID); size_t width = ptr->mat->getWidth(); #ifndef PADDLE_ONLY_CPU @@ -36,26 +36,26 @@ int PDMatCopyToRow(PD_Matrix mat, uint64_t rowID, pd_real* rowArray) { #else std::copy(rowArray, rowArray + width, buf); #endif - return PD_NO_ERROR; + return kPD_NO_ERROR; } int PDMatGetRow(PD_Matrix mat, uint64_t rowID, pd_real** rawRowBuffer) { - if (mat == nullptr) return PD_NULLPTR; + if (mat == nullptr) return kPD_NULLPTR; auto ptr = cast(mat); - if (ptr->mat == nullptr) return PD_NULLPTR; - if (rowID >= ptr->mat->getHeight()) return PD_OUT_OF_RANGE; + if (ptr->mat == nullptr) return kPD_NULLPTR; + if (rowID >= ptr->mat->getHeight()) return kPD_OUT_OF_RANGE; *rawRowBuffer = ptr->mat->getRowBuf(rowID); - return PD_NO_ERROR; + return kPD_NO_ERROR; } int PDMatGetShape(PD_Matrix mat, uint64_t* height, uint64_t* width) { - if (mat == nullptr) return PD_NULLPTR; + if (mat == nullptr) return kPD_NULLPTR; if (height != nullptr) { *height = cast(mat)->mat->getHeight(); } if (width != nullptr) { *width = cast(mat)->mat->getWidth(); } - return PD_NO_ERROR; + return kPD_NO_ERROR; } } diff --git a/paddle/capi/PaddleCAPI.h b/paddle/capi/PaddleCAPI.h index 17a24986718..b848603e8a3 100644 --- a/paddle/capi/PaddleCAPI.h +++ b/paddle/capi/PaddleCAPI.h @@ -8,20 +8,20 @@ extern "C" { #endif typedef enum { - PD_NO_ERROR = 0, - PD_NULLPTR = 1, - PD_OUT_OF_RANGE = 2, - PD_PROTOBUF_ERROR = 3, - PD_UNDEFINED_ERROR = -1, + kPD_NO_ERROR = 0, + kPD_NULLPTR = 1, + kPD_OUT_OF_RANGE = 2, + kPD_PROTOBUF_ERROR = 3, + kPD_UNDEFINED_ERROR = -1, } PD_Error; -typedef void* PD_Vector; +typedef void* PD_IVector; -int PDVecCreate(PD_Vector* vec, uint64_t size, bool useGpu); +int PDIVecCreateNone(PD_IVector* ivec); -int PDVecDestroy(PD_Vector vec); +int PDIVecDestroy(PD_IVector ivec); -int PDVecIsSparse(PD_Vector vec, bool* isSparse); +int PDIVectorGet(PD_IVector ivec, int** buffer); typedef void* PD_Matrix; @@ -51,12 +51,27 @@ int PDArgsSetValue(PD_Arguments args, uint64_t ID, PD_Matrix mat); int PDArgsGetValue(PD_Arguments args, uint64_t ID, PD_Matrix mat); +int PDArgsGetIds(PD_Arguments args, uint64_t ID, PD_IVector ids); + typedef void* PD_GradiemtMachine; int PDGradientMachineCreateForPredict(PD_GradiemtMachine* machine, void* modelConfigProtobuf, int size); +int PDGradientMachineLoadParameterFromDisk(PD_GradiemtMachine machine, + const char* path); + +int PDGradientMachineForward(PD_GradiemtMachine machine, + PD_Arguments inArgs, + PD_Arguments outArgs, + bool isTrain); + +int PDGradientMachineCreateSharedParam(PD_GradiemtMachine origin, + void* modelConfigProtobuf, + int size, + PD_GradiemtMachine* slave); + int PDGradientMachineDestroy(PD_GradiemtMachine machine); int PDInit(int argc, char** argv); diff --git a/paddle/capi/PaddleCAPIPrivate.h b/paddle/capi/PaddleCAPIPrivate.h index 07e731f6cd0..1aae3cedf38 100644 --- a/paddle/capi/PaddleCAPIPrivate.h +++ b/paddle/capi/PaddleCAPIPrivate.h @@ -8,20 +8,40 @@ namespace paddle { namespace capi { -struct CVector { - VectorPtr vec; +enum CType { kIVECTOR = 0, kMATRIX, kARGUMENTS, kGRADIENT_MACHINE }; + +#define STRUCT_HEADER CType type; + +struct CHeader { + STRUCT_HEADER +}; + +struct CIVector { + STRUCT_HEADER + IVectorPtr vec; + + CIVector() : type(kIVECTOR) {} }; struct CMatrix { + STRUCT_HEADER MatrixPtr mat; + + CMatrix() : type(kMATRIX) {} }; struct CArguments { + STRUCT_HEADER std::vector args; + + CArguments() : type(kARGUMENTS) {} }; struct CGradientMachine { + STRUCT_HEADER paddle::GradientMachinePtr machine; + + CGradientMachine() : type(kGRADIENT_MACHINE) {} }; template diff --git a/paddle/capi/Vector.cpp b/paddle/capi/Vector.cpp index 10dee7816c3..2ac795668ff 100644 --- a/paddle/capi/Vector.cpp +++ b/paddle/capi/Vector.cpp @@ -1,26 +1,28 @@ #include "PaddleCAPI.h" #include "PaddleCAPIPrivate.h" -#define cast(v) paddle::capi::cast(v) +using paddle::capi::cast; + extern "C" { -int PDVecCreate(PD_Vector* vec, uint64_t size, bool useGpu) { - auto ptr = new paddle::capi::CVector(); - ptr->vec = paddle::Vector::create(size, useGpu); - *vec = ptr; - return PD_NO_ERROR; + +int PDIVecCreateNone(PD_IVector* ivec) { + if (ivec == nullptr) return kPD_NULLPTR; + auto ptr = new paddle::capi::CIVector(); + *ivec = ptr; + return kPD_NO_ERROR; } -int PDVecDestroy(PD_Vector vec) { - auto v = cast(vec); - v->vec.reset(); - delete v; - return PD_NO_ERROR; + +int PDIVecDestroy(PD_IVector ivec) { + if (ivec == nullptr) return kPD_NULLPTR; + delete cast(ivec); + return kPD_NO_ERROR; } -int PDVecIsSparse(PD_Vector vec, bool* isSparse) { - if (isSparse == nullptr || vec == nullptr) { - return PD_NULLPTR; - } - *isSparse = cast(vec)->vec->isSparse(); - return PD_NO_ERROR; +int PDIVectorGet(PD_IVector ivec, int** buffer) { + if (ivec == nullptr || buffer == nullptr) return kPD_NULLPTR; + auto v = cast(ivec); + if (v->vec == nullptr) return kPD_NULLPTR; + *buffer = v->vec->getData(); + return kPD_NO_ERROR; } } diff --git a/paddle/capi/tests/.gitignore b/paddle/capi/tests/.gitignore new file mode 100644 index 00000000000..7ab6be95e39 --- /dev/null +++ b/paddle/capi/tests/.gitignore @@ -0,0 +1,2 @@ +w +b diff --git a/paddle/capi/tests/CMakeLists.txt b/paddle/capi/tests/CMakeLists.txt index e54a53e2935..d81453982bf 100644 --- a/paddle/capi/tests/CMakeLists.txt +++ b/paddle/capi/tests/CMakeLists.txt @@ -1,35 +1,10 @@ -function(add_capi_unittest_without_exec TARGET_NAME) - set(with_test_main ON) - set(sources) - foreach(source_file ${ARGN}) - if (${source_file} STREQUAL "NO_MAIN") - set(with_test_main OFF) - else() - list(APPEND sources ${source_file}) - endif() - endforeach() - - add_executable( - ${TARGET_NAME} - ${sources}) - - target_link_libraries( - ${TARGET_NAME} - paddle_capi - ${GTEST_LIBRARIES}) - - if (with_test_main) - target_link_libraries( - ${TARGET_NAME} paddle_test_main) - endif() - target_include_directories(${TARGET_NAME} PUBLIC ${PADDLE_CAPI_INC_PATH}) -endfunction() - -function(add_capi_unittest TARGET_NAME) - add_capi_unittest_without_exec(${TARGET_NAME} ${ARGN}) - add_test(NAME ${TARGET_NAME} COMMAND ${TARGET_NAME}) -endfunction() -add_capi_unittest(capi_test_mats test_Vector.cpp +add_unittest(capi_test_mats test_Vector.cpp test_Matrix.cpp test_Arguments.cpp) -add_capi_unittest(capi_test_gradientMachine NO_MAIN test_GradientMachine.cpp) +target_include_directories(capi_test_mats PUBLIC ${PADDLE_CAPI_INC_PATH}) +target_link_libraries(capi_test_mats paddle_capi) +add_unittest(capi_test_gradientMachine test_GradientMachine.cpp) + +target_include_directories(capi_test_gradientMachine PUBLIC + ${PADDLE_CAPI_INC_PATH}) +target_link_libraries(capi_test_gradientMachine paddle_capi) diff --git a/paddle/capi/tests/test_Arguments.cpp b/paddle/capi/tests/test_Arguments.cpp index c74abd60d1b..fe9762deed9 100644 --- a/paddle/capi/tests/test_Arguments.cpp +++ b/paddle/capi/tests/test_Arguments.cpp @@ -15,40 +15,44 @@ static std::vector randomBuffer(size_t bufSize) { TEST(CAPIArguments, create) { PD_Arguments args; - ASSERT_EQ(PD_NO_ERROR, PDArgsCreateNone(&args)); + ASSERT_EQ(kPD_NO_ERROR, PDArgsCreateNone(&args)); uint64_t size; - ASSERT_EQ(PD_NO_ERROR, PDArgsGetSize(args, &size)); + ASSERT_EQ(kPD_NO_ERROR, PDArgsGetSize(args, &size)); ASSERT_EQ(0UL, size); - ASSERT_EQ(PD_NO_ERROR, PDArgsDestroy(args)); + ASSERT_EQ(kPD_NO_ERROR, PDArgsDestroy(args)); } TEST(CAPIArguments, value) { PD_Arguments args; - ASSERT_EQ(PD_NO_ERROR, PDArgsCreateNone(&args)); - ASSERT_EQ(PD_NO_ERROR, PDArgsResize(args, 1)); + ASSERT_EQ(kPD_NO_ERROR, PDArgsCreateNone(&args)); + ASSERT_EQ(kPD_NO_ERROR, PDArgsResize(args, 1)); PD_Matrix mat; - ASSERT_EQ(PD_NO_ERROR, PDMatCreate(&mat, 128, 64, false)); + ASSERT_EQ(kPD_NO_ERROR, PDMatCreate(&mat, 128, 64, false)); for (size_t i = 0; i < 128; ++i) { std::vector sampleBuf = randomBuffer(64); PDMatCopyToRow(mat, i, sampleBuf.data()); } - ASSERT_EQ(PD_NO_ERROR, PDArgsSetValue(args, 0, mat)); + ASSERT_EQ(kPD_NO_ERROR, PDArgsSetValue(args, 0, mat)); PD_Matrix val; - ASSERT_EQ(PD_NO_ERROR, PDMatCreateNone(&val)); + ASSERT_EQ(kPD_NO_ERROR, PDMatCreateNone(&val)); - ASSERT_EQ(PD_NO_ERROR, PDArgsGetValue(args, 0, val)); + ASSERT_EQ(kPD_NO_ERROR, PDArgsGetValue(args, 0, val)); for (size_t i = 0; i < 128; ++i) { pd_real* row1; pd_real* row2; - ASSERT_EQ(PD_NO_ERROR, PDMatGetRow(mat, i, &row1)); - ASSERT_EQ(PD_NO_ERROR, PDMatGetRow(val, i, &row2)); + ASSERT_EQ(kPD_NO_ERROR, PDMatGetRow(mat, i, &row1)); + ASSERT_EQ(kPD_NO_ERROR, PDMatGetRow(val, i, &row2)); ASSERT_EQ(row1, row2); } - ASSERT_EQ(PD_NO_ERROR, PDMatDestroy(val)); - ASSERT_EQ(PD_NO_ERROR, PDMatDestroy(mat)); - ASSERT_EQ(PD_NO_ERROR, PDArgsDestroy(args)); + + PD_IVector ivec; + ASSERT_EQ(kPD_NO_ERROR, PDIVecCreateNone(&ivec)); + ASSERT_EQ(kPD_NO_ERROR, PDIVecDestroy(ivec)); + ASSERT_EQ(kPD_NO_ERROR, PDMatDestroy(val)); + ASSERT_EQ(kPD_NO_ERROR, PDMatDestroy(mat)); + ASSERT_EQ(kPD_NO_ERROR, PDArgsDestroy(args)); } diff --git a/paddle/capi/tests/test_GradientMachine.cpp b/paddle/capi/tests/test_GradientMachine.cpp index f07d1e4e729..63fb47bd27b 100644 --- a/paddle/capi/tests/test_GradientMachine.cpp +++ b/paddle/capi/tests/test_GradientMachine.cpp @@ -1,18 +1,96 @@ #include +#include #include #include #include +#include #include "PaddleCAPI.h" +#include "paddle/utils/ThreadLocal.h" -TEST(GradientMachine, load) { - paddle::TrainerConfigHelper config("./vgg_16_cifar.py"); +static std::vector randomBuffer(size_t bufSize) { + auto& eng = paddle::ThreadLocalRandomEngine::get(); + std::uniform_real_distribution dist(-1.0, 1.0); + std::vector retv; + retv.reserve(bufSize); + for (size_t i = 0; i < bufSize; ++i) { + retv.push_back(dist(eng)); + } + return retv; +} + +TEST(GradientMachine, testPredict) { + paddle::TrainerConfigHelper config("./test_predict_network.py"); std::string buffer; ASSERT_TRUE(config.getModelConfig().SerializeToString(&buffer)); PD_GradiemtMachine machine; - ASSERT_EQ(PD_NO_ERROR, + ASSERT_EQ(kPD_NO_ERROR, PDGradientMachineCreateForPredict( &machine, &buffer[0], (int)buffer.size())); + std::unique_ptr gm( + paddle::GradientMachine::create(config.getModelConfig())); + ASSERT_NE(nullptr, gm); + gm->randParameters(); + gm->saveParameters("./"); + + ASSERT_EQ(kPD_NO_ERROR, + PDGradientMachineLoadParameterFromDisk(machine, "./")); + + PD_GradiemtMachine machineSlave; + ASSERT_EQ(kPD_NO_ERROR, + PDGradientMachineCreateSharedParam( + machine, &buffer[0], (int)buffer.size(), &machineSlave)); + std::swap(machineSlave, machine); + PD_Arguments outArgs; + ASSERT_EQ(kPD_NO_ERROR, PDArgsCreateNone(&outArgs)); + + PD_Arguments inArgs; + ASSERT_EQ(kPD_NO_ERROR, PDArgsCreateNone(&inArgs)); + ASSERT_EQ(kPD_NO_ERROR, PDArgsResize(inArgs, 1)); + PD_Matrix mat; + ASSERT_EQ(kPD_NO_ERROR, PDMatCreate(&mat, 1, 100, false)); + static_assert(std::is_same::value, ""); + + auto data = randomBuffer(100); + pd_real* rowPtr; + ASSERT_EQ(kPD_NO_ERROR, PDMatGetRow(mat, 0, &rowPtr)); + memcpy(rowPtr, data.data(), data.size() * sizeof(pd_real)); + + ASSERT_EQ(kPD_NO_ERROR, PDArgsSetValue(inArgs, 0, mat)); + ASSERT_EQ(kPD_NO_ERROR, + PDGradientMachineForward(machine, inArgs, outArgs, false)); + + uint64_t sz; + ASSERT_EQ(kPD_NO_ERROR, PDArgsGetSize(outArgs, &sz)); + ASSERT_EQ(1UL, sz); + + ASSERT_EQ(kPD_NO_ERROR, PDArgsGetValue(outArgs, 0, mat)); + std::vector paddleInArgs; + std::vector paddleOutArgs; + paddleInArgs.resize(1); + paddleInArgs[0].value = + paddle::Matrix::create(data.data(), 1, 100, false, false); + + gm->forward(paddleInArgs, &paddleOutArgs, paddle::PASS_TEST); + + auto matPaddle = paddleOutArgs[0].value; + + uint64_t height, width; + ASSERT_EQ(kPD_NO_ERROR, PDMatGetShape(mat, &height, &width)); + ASSERT_EQ(matPaddle->getHeight(), height); + ASSERT_EQ(matPaddle->getWidth(), width); + + ASSERT_EQ(kPD_NO_ERROR, PDMatGetRow(mat, 0, &rowPtr)); + for (size_t i = 0; i < width; ++i) { + ASSERT_NEAR(matPaddle->getData()[i], rowPtr[i], 1e-5); + } + + ASSERT_EQ(kPD_NO_ERROR, PDMatDestroy(mat)); + ASSERT_EQ(kPD_NO_ERROR, PDArgsDestroy(inArgs)); + ASSERT_EQ(kPD_NO_ERROR, PDArgsDestroy(outArgs)); + std::swap(machineSlave, machine); + ASSERT_EQ(kPD_NO_ERROR, PDGradientMachineDestroy(machineSlave)); + ASSERT_EQ(kPD_NO_ERROR, PDGradientMachineDestroy(machine)); } int main(int argc, char** argv) { diff --git a/paddle/capi/tests/test_Matrix.cpp b/paddle/capi/tests/test_Matrix.cpp index 0f04a468304..97913f7229f 100644 --- a/paddle/capi/tests/test_Matrix.cpp +++ b/paddle/capi/tests/test_Matrix.cpp @@ -3,31 +3,31 @@ TEST(CAPIMatrix, create) { PD_Matrix mat; - ASSERT_EQ(PD_NO_ERROR, PDMatCreate(&mat, 128, 32, false)); + ASSERT_EQ(kPD_NO_ERROR, PDMatCreate(&mat, 128, 32, false)); std::vector sampleRow; sampleRow.resize(32); for (size_t i = 0; i < sampleRow.size(); ++i) { sampleRow[i] = 1.0 / (i + 1.0); } - ASSERT_EQ(PD_NO_ERROR, PDMatCopyToRow(mat, 0, sampleRow.data())); - ASSERT_EQ(PD_OUT_OF_RANGE, PDMatCopyToRow(mat, 128, sampleRow.data())); + ASSERT_EQ(kPD_NO_ERROR, PDMatCopyToRow(mat, 0, sampleRow.data())); + ASSERT_EQ(kPD_OUT_OF_RANGE, PDMatCopyToRow(mat, 128, sampleRow.data())); pd_real* arrayPtr; - ASSERT_EQ(PD_NO_ERROR, PDMatGetRow(mat, 0, &arrayPtr)); + ASSERT_EQ(kPD_NO_ERROR, PDMatGetRow(mat, 0, &arrayPtr)); for (size_t i = 0; i < sampleRow.size(); ++i) { ASSERT_NEAR(sampleRow[i], arrayPtr[i], 1e-5); } uint64_t height, width; - ASSERT_EQ(PD_NO_ERROR, PDMatGetShape(mat, &height, &width)); + ASSERT_EQ(kPD_NO_ERROR, PDMatGetShape(mat, &height, &width)); ASSERT_EQ(128, height); ASSERT_EQ(32, width); - ASSERT_EQ(PD_NO_ERROR, PDMatDestroy(mat)); + ASSERT_EQ(kPD_NO_ERROR, PDMatDestroy(mat)); } TEST(CAPIMatrix, createNone) { PD_Matrix mat; - ASSERT_EQ(PD_NO_ERROR, PDMatCreateNone(&mat)); - ASSERT_EQ(PD_NO_ERROR, PDMatDestroy(mat)); + ASSERT_EQ(kPD_NO_ERROR, PDMatCreateNone(&mat)); + ASSERT_EQ(kPD_NO_ERROR, PDMatDestroy(mat)); } diff --git a/paddle/capi/tests/test_Vector.cpp b/paddle/capi/tests/test_Vector.cpp index dbb987d440a..907a63bc9e0 100644 --- a/paddle/capi/tests/test_Vector.cpp +++ b/paddle/capi/tests/test_Vector.cpp @@ -2,10 +2,7 @@ #include "gtest/gtest.h" TEST(CAPIVector, create) { - PD_Vector tmp; - ASSERT_EQ(PD_NO_ERROR, PDVecCreate(&tmp, 128, false)); - bool isSparse; - ASSERT_EQ(PD_NO_ERROR, PDVecIsSparse(tmp, &isSparse)); - ASSERT_FALSE(isSparse); - ASSERT_EQ(PD_NO_ERROR, PDVecDestroy(tmp)); + PD_IVector vec; + ASSERT_EQ(kPD_NO_ERROR, PDIVecCreateNone(&vec)); + ASSERT_EQ(kPD_NO_ERROR, PDIVecDestroy(vec)); } diff --git a/paddle/capi/tests/test_predict_network.py b/paddle/capi/tests/test_predict_network.py new file mode 100644 index 00000000000..82ef5cb1a70 --- /dev/null +++ b/paddle/capi/tests/test_predict_network.py @@ -0,0 +1,13 @@ +from paddle.trainer_config_helpers import * + +settings(batch_size=100) + +x = data_layer(name='x', size=100) + +y = fc_layer( + input=x, + size=100, + bias_attr=ParamAttr(name='b'), + param_attr=ParamAttr(name='w')) + +outputs(y) diff --git a/paddle/capi/tests/vgg_16_cifar.py b/paddle/capi/tests/vgg_16_cifar.py deleted file mode 120000 index 81250eefde6..00000000000 --- a/paddle/capi/tests/vgg_16_cifar.py +++ /dev/null @@ -1 +0,0 @@ -../../../demo/image_classification/vgg_16_cifar.py \ No newline at end of file -- GitLab From 873368f8422ea5815e6f0c02df84ec1960438b4a Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Tue, 10 Jan 2017 16:53:32 +0800 Subject: [PATCH 0006/3256] Add style check to target --- paddle/capi/Arguments.cpp | 14 ++++++++++++++ paddle/capi/CMakeLists.txt | 2 ++ paddle/capi/GradientMachine.cpp | 17 +++++++++++++++-- paddle/capi/Main.cpp | 14 ++++++++++++++ paddle/capi/Matrix.cpp | 14 ++++++++++++++ paddle/capi/PaddleCAPI.h | 21 ++++++++++++++++++--- paddle/capi/PaddleCAPIPrivate.h | 18 ++++++++++++++++-- paddle/capi/Vector.cpp | 14 ++++++++++++++ paddle/capi/tests/test_Arguments.cpp | 14 ++++++++++++++ paddle/capi/tests/test_GradientMachine.cpp | 14 ++++++++++++++ paddle/capi/tests/test_Matrix.cpp | 18 ++++++++++++++++-- paddle/capi/tests/test_Vector.cpp | 14 ++++++++++++++ 12 files changed, 165 insertions(+), 9 deletions(-) diff --git a/paddle/capi/Arguments.cpp b/paddle/capi/Arguments.cpp index b983d72bb42..60bdea15ae7 100644 --- a/paddle/capi/Arguments.cpp +++ b/paddle/capi/Arguments.cpp @@ -1,3 +1,17 @@ +/* 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 "PaddleCAPI.h" #include "PaddleCAPIPrivate.h" diff --git a/paddle/capi/CMakeLists.txt b/paddle/capi/CMakeLists.txt index 93b6b41254d..6eb1e9949a4 100644 --- a/paddle/capi/CMakeLists.txt +++ b/paddle/capi/CMakeLists.txt @@ -15,6 +15,8 @@ file(GLOB CAPI_SOURCES *.cpp) add_library(paddle_capi STATIC ${CAPI_SOURCES}) target_include_directories(paddle_capi PUBLIC ${CMAKE_CURRENT_BINARY_DIR}) +add_style_check_target(paddle_capi ${CAPI_SOURCES} ${CAPI_HEADER} + ${CAPI_PRIVATE_HEADER}) add_dependencies(paddle_capi gen_proto_cpp) set(PADDLE_CAPI_INC_PATH diff --git a/paddle/capi/GradientMachine.cpp b/paddle/capi/GradientMachine.cpp index ef584ed8d0d..8299e6442f6 100644 --- a/paddle/capi/GradientMachine.cpp +++ b/paddle/capi/GradientMachine.cpp @@ -1,3 +1,17 @@ +/* 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 "PaddleCAPI.h" #include "PaddleCAPIPrivate.h" #include "paddle/gserver/gradientmachines/NeuralNetwork.h" @@ -21,7 +35,7 @@ NeuralNetwork* newCustomNerualNetwork(const std::string& name, NeuralNetwork* network) { return new MyNeuralNetwork(name, network); } -} +} // namespace paddle extern "C" { int PDGradientMachineCreateForPredict(PD_GradiemtMachine* machine, @@ -91,7 +105,6 @@ int PDGradientMachineCreateSharedParam(PD_GradiemtMachine origin, auto p = o->machine->getParameters()[paramId]; param->enableSharedType(paddle::PARAMETER_VALUE, p->getBuf(paddle::PARAMETER_VALUE)); - }, {paddle::PARAMETER_VALUE}, false); diff --git a/paddle/capi/Main.cpp b/paddle/capi/Main.cpp index 8cd0104be2e..e310eb54047 100644 --- a/paddle/capi/Main.cpp +++ b/paddle/capi/Main.cpp @@ -1,3 +1,17 @@ +/* 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 diff --git a/paddle/capi/Matrix.cpp b/paddle/capi/Matrix.cpp index dc1b4f3379d..db32c945412 100644 --- a/paddle/capi/Matrix.cpp +++ b/paddle/capi/Matrix.cpp @@ -1,3 +1,17 @@ +/* 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 "PaddleCAPI.h" #include "PaddleCAPIPrivate.h" #include "hl_cuda.h" diff --git a/paddle/capi/PaddleCAPI.h b/paddle/capi/PaddleCAPI.h index b848603e8a3..8cd78429f34 100644 --- a/paddle/capi/PaddleCAPI.h +++ b/paddle/capi/PaddleCAPI.h @@ -1,5 +1,19 @@ -#ifndef __PADDLE_PADDLE_CAPI_PADDLECAPI_H_INCLUDED__ -#define __PADDLE_PADDLE_CAPI_PADDLECAPI_H_INCLUDED__ +/* 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. */ + +#ifndef PADDLECAPI_H_ +#define PADDLECAPI_H_ #include #include #include "config.h" @@ -79,4 +93,5 @@ int PDInit(int argc, char** argv); #ifdef __cplusplus } #endif -#endif + +#endif // PADDLECAPI_H_ diff --git a/paddle/capi/PaddleCAPIPrivate.h b/paddle/capi/PaddleCAPIPrivate.h index 1aae3cedf38..bb8baea4e1c 100644 --- a/paddle/capi/PaddleCAPIPrivate.h +++ b/paddle/capi/PaddleCAPIPrivate.h @@ -1,3 +1,17 @@ +/* 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 "PaddleCAPI.h" #include "paddle/gserver/gradientmachines/GradientMachine.h" #include "paddle/math/Matrix.h" @@ -48,5 +62,5 @@ template inline T* cast(void* ptr) { return reinterpret_cast(ptr); } -} -} +} // namespace capi +} // namespace paddle diff --git a/paddle/capi/Vector.cpp b/paddle/capi/Vector.cpp index 2ac795668ff..5b4fe0666cb 100644 --- a/paddle/capi/Vector.cpp +++ b/paddle/capi/Vector.cpp @@ -1,3 +1,17 @@ +/* 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 "PaddleCAPI.h" #include "PaddleCAPIPrivate.h" diff --git a/paddle/capi/tests/test_Arguments.cpp b/paddle/capi/tests/test_Arguments.cpp index fe9762deed9..4a18ffbf474 100644 --- a/paddle/capi/tests/test_Arguments.cpp +++ b/paddle/capi/tests/test_Arguments.cpp @@ -1,3 +1,17 @@ +/* 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 "PaddleCAPI.h" #include "gtest/gtest.h" #include "paddle/utils/ThreadLocal.h" diff --git a/paddle/capi/tests/test_GradientMachine.cpp b/paddle/capi/tests/test_GradientMachine.cpp index 63fb47bd27b..fcade7fb5c9 100644 --- a/paddle/capi/tests/test_GradientMachine.cpp +++ b/paddle/capi/tests/test_GradientMachine.cpp @@ -1,3 +1,17 @@ +/* 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 diff --git a/paddle/capi/tests/test_Matrix.cpp b/paddle/capi/tests/test_Matrix.cpp index 97913f7229f..4192dd6bfb5 100644 --- a/paddle/capi/tests/test_Matrix.cpp +++ b/paddle/capi/tests/test_Matrix.cpp @@ -1,3 +1,17 @@ +/* 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 "PaddleCAPI.h" #include "gtest/gtest.h" @@ -21,8 +35,8 @@ TEST(CAPIMatrix, create) { uint64_t height, width; ASSERT_EQ(kPD_NO_ERROR, PDMatGetShape(mat, &height, &width)); - ASSERT_EQ(128, height); - ASSERT_EQ(32, width); + ASSERT_EQ(128UL, height); + ASSERT_EQ(32UL, width); ASSERT_EQ(kPD_NO_ERROR, PDMatDestroy(mat)); } diff --git a/paddle/capi/tests/test_Vector.cpp b/paddle/capi/tests/test_Vector.cpp index 907a63bc9e0..122f7df176d 100644 --- a/paddle/capi/tests/test_Vector.cpp +++ b/paddle/capi/tests/test_Vector.cpp @@ -1,3 +1,17 @@ +/* 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 "PaddleCAPI.h" #include "gtest/gtest.h" -- GitLab From fe8d5ff39f3c628c294fce0d7f88ee8d5626329e Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Tue, 10 Jan 2017 17:36:12 +0800 Subject: [PATCH 0007/3256] Add WITH_C_API option --- CMakeLists.txt | 2 ++ paddle/CMakeLists.txt | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8f53abacb40..2048796ef3e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -43,6 +43,7 @@ option(WITH_DOC "Compile PaddlePaddle with documentation" OFF) option(ON_COVERALLS "Compile PaddlePaddle with code coverage" OFF) option(COVERALLS_UPLOAD "Package code coverage data to coveralls" OFF) option(ON_TRAVIS "Exclude special unit test on Travis CI" OFF) +option(WITH_C_API "Compile PaddlePaddle with C-API(Prediction)" ON) # CMAKE_BUILD_TYPE if(NOT CMAKE_BUILD_TYPE) @@ -53,6 +54,7 @@ endif() set(THIRD_PARTY_PATH "${PROJ_ROOT}/third_party" CACHE STRING "A path setting third party libraries download & build directories.") + ######################################################################################## include(external/zlib) # download, build, install zlib diff --git a/paddle/CMakeLists.txt b/paddle/CMakeLists.txt index b3f3b2fbced..48905f32373 100644 --- a/paddle/CMakeLists.txt +++ b/paddle/CMakeLists.txt @@ -8,7 +8,9 @@ add_subdirectory(gserver) add_subdirectory(pserver) add_subdirectory(trainer) add_subdirectory(scripts) -add_subdirectory(capi) +if(WITH_C_API) + add_subdirectory(capi) +endif() configure_file(${CMAKE_CURRENT_SOURCE_DIR}/setup.py.in ${CMAKE_CURRENT_SOURCE_DIR}/setup.py) -- GitLab From 06b1a6a6805c43bd1415493c4d28c2f787fb7614 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Wed, 11 Jan 2017 11:39:07 +0800 Subject: [PATCH 0008/3256] Fix unittest --- paddle/capi/tests/CMakeLists.txt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/paddle/capi/tests/CMakeLists.txt b/paddle/capi/tests/CMakeLists.txt index d81453982bf..d73f6b77339 100644 --- a/paddle/capi/tests/CMakeLists.txt +++ b/paddle/capi/tests/CMakeLists.txt @@ -3,8 +3,12 @@ add_unittest(capi_test_mats test_Vector.cpp target_include_directories(capi_test_mats PUBLIC ${PADDLE_CAPI_INC_PATH}) target_link_libraries(capi_test_mats paddle_capi) -add_unittest(capi_test_gradientMachine test_GradientMachine.cpp) + +add_unittest_without_exec(capi_test_gradientMachine test_GradientMachine.cpp) target_include_directories(capi_test_gradientMachine PUBLIC ${PADDLE_CAPI_INC_PATH}) target_link_libraries(capi_test_gradientMachine paddle_capi) +add_test(NAME capi_test_gradientMachine + COMMAND ${PROJ_ROOT}/paddle/.set_python_path.sh -d ${PROJ_ROOT}/python ${CMAKE_CURRENT_BINARY_DIR}/capi_test_gradientMachine + WORKING_DIRECTORY ${PROJ_ROOT}/paddle/capi/tests) -- GitLab From 4fd6888eb1786acca863d8db5e0fdb8fbee15ada Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Wed, 11 Jan 2017 13:11:05 +0800 Subject: [PATCH 0009/3256] C-API for model inference. --- paddle/capi/CMakeLists.txt | 48 ++++++++++++++++++++++++++++++++++---- 1 file changed, 44 insertions(+), 4 deletions(-) diff --git a/paddle/capi/CMakeLists.txt b/paddle/capi/CMakeLists.txt index 6eb1e9949a4..38277915716 100644 --- a/paddle/capi/CMakeLists.txt +++ b/paddle/capi/CMakeLists.txt @@ -4,21 +4,61 @@ else () set(PADDLE_FLOAT_TYPE float) endif() +# config.h used for C-API. It will store Paddle building configuration as a +# header. Make user just include PaddleCAPI.h then can get building +# configuration without explicitly set -DPADDLE_WITH_DOUBLE when building their +# libraries. configure_file(config.h.in config.h @ONLY) -set(CAPI_HEADER - PaddleCAPI.h) -set(CAPI_PRIVATE_HEADER - PaddleCAPIPrivate.h) +# PaddleCAPI.h is the only header we exposed. It currently only used for model +# inference. +set(CAPI_HEADER PaddleCAPI.h) + + +set(CAPI_PRIVATE_HEADER PaddleCAPIPrivate.h) file(GLOB CAPI_SOURCES *.cpp) +# building paddle_capi add_library(paddle_capi STATIC ${CAPI_SOURCES}) target_include_directories(paddle_capi PUBLIC ${CMAKE_CURRENT_BINARY_DIR}) + add_style_check_target(paddle_capi ${CAPI_SOURCES} ${CAPI_HEADER} ${CAPI_PRIVATE_HEADER}) + add_dependencies(paddle_capi gen_proto_cpp) + +# combine all paddle static libraries together, into libpaddle_capi_whole.a +# user should use PaddleCAPI as -lpaddle_capi_whole +set(capi_whole_library libpaddle_capi_whole.a) +add_custom_target(paddle_capi_whole + COMMAND mkdir -p o_files/capi && cd o_files/capi/ && ar -x $ + COMMAND mkdir -p o_files/utils && cd o_files/utils/ && ar -x $ + COMMAND mkdir -p o_files/parameter && cd o_files/parameter/ && ar -x $ + COMMAND mkdir -p o_files/math && cd o_files/math/ && ar -x $ + COMMAND mkdir -p o_files/cuda && cd o_files/cuda/ && ar -x $ + COMMAND mkdir -p o_files/function && cd o_files/function/ && ar -x $ + COMMAND mkdir -p o_files/pserver && cd o_files/pserver/ && ar -x $ + COMMAND mkdir -p o_files/gserver && cd o_files/gserver/ && ar -x $ + COMMAND mkdir -p o_files/proto && cd o_files/proto/ && ar -x $ + COMMAND ar crs ${capi_whole_library} `find ./o_files -name '*.o'` + COMMAND rm -rf o_files + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + DEPENDS paddle_capi paddle_utils paddle_parameter paddle_math + paddle_cuda paddle_function paddle_pserver paddle_gserver + paddle_proto + ) +set_target_properties(paddle_capi_whole + PROPERTIES IMPORTED_LOCATION ${CMAKE_CURRENT_BINARY_DIR}/${capi_whole_library}) + +# install library & headers. +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${capi_whole_library} DESTINATION lib) +install(FILES ${CAPI_HEADER} DESTINATION include/paddle) +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/config.h DESTINATION include/paddle) + + +# this variable used for unittest set(PADDLE_CAPI_INC_PATH ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}) -- GitLab From 005ac1f8496f4c17287b32580778dd94a98324e5 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Wed, 11 Jan 2017 14:12:35 +0800 Subject: [PATCH 0010/3256] Add warning message --- CMakeLists.txt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2048796ef3e..c80c5c03c61 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -43,7 +43,7 @@ option(WITH_DOC "Compile PaddlePaddle with documentation" OFF) option(ON_COVERALLS "Compile PaddlePaddle with code coverage" OFF) option(COVERALLS_UPLOAD "Package code coverage data to coveralls" OFF) option(ON_TRAVIS "Exclude special unit test on Travis CI" OFF) -option(WITH_C_API "Compile PaddlePaddle with C-API(Prediction)" ON) +option(WITH_C_API "Compile PaddlePaddle with C-API(Prediction)" OFF) # CMAKE_BUILD_TYPE if(NOT CMAKE_BUILD_TYPE) @@ -55,6 +55,12 @@ endif() set(THIRD_PARTY_PATH "${PROJ_ROOT}/third_party" CACHE STRING "A path setting third party libraries download & build directories.") +if (WITH_C_API AND WITH_PYTHON) + message(WARNING "It is suggest not embedded a python interpreter in Paddle " + "when using C-API. It will give an unpredictable behavior when using a " + "different Python interpreter from compiling.") +endif() + ######################################################################################## include(external/zlib) # download, build, install zlib -- GitLab From 3bc0d8b2c1892442be03ef4df490870fb9168898 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Wed, 11 Jan 2017 14:13:48 +0800 Subject: [PATCH 0011/3256] Revert unchanged files --- paddle/api/paddle_ld_flags.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/paddle/api/paddle_ld_flags.py b/paddle/api/paddle_ld_flags.py index 59b0657bd0e..ad5dce209bf 100644 --- a/paddle/api/paddle_ld_flags.py +++ b/paddle/api/paddle_ld_flags.py @@ -50,6 +50,7 @@ try: self.glog_libs = GLOG_LIBRARIES self.with_coverage = PaddleLDFlag.cmake_bool(WITH_COVERALLS) + self.gflags_libs = GFLAGS_LIBRARIES self.gflags_location = GFLAGS_LOCATION self.cblas_libs = CBLAS_LIBRARIES self.curt = CUDA_LIBRARIES @@ -87,7 +88,7 @@ try: "-lpaddle_api", self.normalize_flag(self.protolib), self.normalize_flag(self.glog_libs), - self.normalize_flag("gflags"), + self.normalize_flag(self.gflags_libs), self.normalize_flag(self.zlib), self.normalize_flag(self.thread), self.normalize_flag(self.dl_libs), @@ -113,7 +114,10 @@ try: return cmake_flag elif cmake_flag.startswith("-l"): # normal link command return cmake_flag - elif cmake_flag == "gflags": # special for gflags + elif cmake_flag in [ + "gflags-shared", "gflags-static", "gflags_nothreads-shared", + "gflags_nothreads-static" + ]: # special for gflags assert PaddleLDFlag.cmake_bool(self.gflags_location) return self.gflags_location elif len(cmake_flag) != 0: -- GitLab From 987a908f294f9c96c8d7c1def850cd3945e39c65 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Thu, 12 Jan 2017 14:27:24 +0800 Subject: [PATCH 0012/3256] Fix a bug, should be ALL in custom_target --- paddle/capi/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paddle/capi/CMakeLists.txt b/paddle/capi/CMakeLists.txt index 38277915716..172be7b1224 100644 --- a/paddle/capi/CMakeLists.txt +++ b/paddle/capi/CMakeLists.txt @@ -32,7 +32,7 @@ add_dependencies(paddle_capi gen_proto_cpp) # combine all paddle static libraries together, into libpaddle_capi_whole.a # user should use PaddleCAPI as -lpaddle_capi_whole set(capi_whole_library libpaddle_capi_whole.a) -add_custom_target(paddle_capi_whole +add_custom_target(paddle_capi_whole ALL COMMAND mkdir -p o_files/capi && cd o_files/capi/ && ar -x $ COMMAND mkdir -p o_files/utils && cd o_files/utils/ && ar -x $ COMMAND mkdir -p o_files/parameter && cd o_files/parameter/ && ar -x $ -- GitLab From 3b5bed68d0fec8e95eb5a706fd17e80662f27835 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Thu, 12 Jan 2017 15:20:57 +0800 Subject: [PATCH 0013/3256] Add dump binary config --- python/paddle/utils/dump_config.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/python/paddle/utils/dump_config.py b/python/paddle/utils/dump_config.py index 73bf349c467..d1d54b6a295 100644 --- a/python/paddle/utils/dump_config.py +++ b/python/paddle/utils/dump_config.py @@ -20,6 +20,7 @@ __all__ = [] if __name__ == '__main__': whole_conf = False + binary = False if len(sys.argv) == 2: conf = parse_config(sys.argv[1], '') elif len(sys.argv) == 3: @@ -28,6 +29,8 @@ if __name__ == '__main__': conf = parse_config(sys.argv[1], sys.argv[2]) if sys.argv[3] == '--whole': whole_conf = True + elif sys.argv[3] == '--binary': + binary = True else: raise RuntimeError() @@ -36,4 +39,7 @@ if __name__ == '__main__': if whole_conf: print conf else: - print conf.model_config + if binary: + sys.stdout.write(conf.SerializeToString()) + else: + print conf.model_config -- GitLab From 0874a7e866fa15676e60eb13305bffefbbae6fa7 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Thu, 12 Jan 2017 16:48:24 +0800 Subject: [PATCH 0014/3256] Fix typo in API.h --- paddle/capi/CMakeLists.txt | 9 ++++++--- paddle/capi/PaddleCAPI.h | 14 +++++++------- paddle/capi/tests/test_GradientMachine.cpp | 4 ++-- python/paddle/utils/dump_config.py | 3 ++- 4 files changed, 17 insertions(+), 13 deletions(-) diff --git a/paddle/capi/CMakeLists.txt b/paddle/capi/CMakeLists.txt index 172be7b1224..b46fed3a3b4 100644 --- a/paddle/capi/CMakeLists.txt +++ b/paddle/capi/CMakeLists.txt @@ -39,15 +39,18 @@ add_custom_target(paddle_capi_whole ALL COMMAND mkdir -p o_files/math && cd o_files/math/ && ar -x $ COMMAND mkdir -p o_files/cuda && cd o_files/cuda/ && ar -x $ COMMAND mkdir -p o_files/function && cd o_files/function/ && ar -x $ - COMMAND mkdir -p o_files/pserver && cd o_files/pserver/ && ar -x $ COMMAND mkdir -p o_files/gserver && cd o_files/gserver/ && ar -x $ COMMAND mkdir -p o_files/proto && cd o_files/proto/ && ar -x $ + COMMAND mkdir -p o_files/network && cd o_files/network/ && ar -x + +$ + + COMMAND mkdir -p o_files/pserver && cd o_files/pserver/ && ar -x + +$ Date: Thu, 12 Jan 2017 17:05:39 +0800 Subject: [PATCH 0015/3256] Add comments. --- paddle/capi/PaddleCAPI.h | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/paddle/capi/PaddleCAPI.h b/paddle/capi/PaddleCAPI.h index b88254c9303..f2340b8a750 100644 --- a/paddle/capi/PaddleCAPI.h +++ b/paddle/capi/PaddleCAPI.h @@ -21,6 +21,16 @@ limitations under the License. */ extern "C" { #endif +/** + * Paddle C API. It will replace SWIG as Multiple Language API for model + * training & inference. Currently it is only used in model infernece. + * + * NOTE: This is an experimental API, it could be changed. + */ + +/** + * Error Type for Paddle API. + */ typedef enum { kPD_NO_ERROR = 0, kPD_NULLPTR = 1, @@ -29,6 +39,9 @@ typedef enum { kPD_UNDEFINED_ERROR = -1, } PD_Error; +/** + * Int Vector Functions. Return will be a PD_Error type. + */ typedef void* PD_IVector; int PDIVecCreateNone(PD_IVector* ivec); @@ -37,6 +50,9 @@ int PDIVecDestroy(PD_IVector ivec); int PDIVectorGet(PD_IVector ivec, int** buffer); +/** + * Matrix functions. Return will be a PD_Error type. + */ typedef void* PD_Matrix; int PDMatCreate(PD_Matrix* mat, uint64_t height, uint64_t width, bool useGpu); @@ -51,6 +67,10 @@ int PDMatCreateNone(PD_Matrix* mat); int PDMatGetShape(PD_Matrix mat, uint64_t* height, uint64_t* width); +/** + * Arguments functions. Each argument means layer output. Arguments means a + * array of arguemnt. + */ typedef void* PD_Arguments; int PDArgsCreateNone(PD_Arguments* args); @@ -67,6 +87,9 @@ int PDArgsGetValue(PD_Arguments args, uint64_t ID, PD_Matrix mat); int PDArgsGetIds(PD_Arguments args, uint64_t ID, PD_IVector ids); +/** + * @brief GradientMachine means a neural network. + */ typedef void* PD_GradientMachine; int PDGradientMachineCreateForPredict(PD_GradientMachine* machine, @@ -88,6 +111,9 @@ int PDGradientMachineCreateSharedParam(PD_GradientMachine origin, int PDGradientMachineDestroy(PD_GradientMachine machine); +/** + * Initialize Paddle. + */ int PDInit(int argc, char** argv); #ifdef __cplusplus -- GitLab From 64022143d8855a5303133e478f21f9c1106291f0 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Fri, 13 Jan 2017 10:04:19 +0800 Subject: [PATCH 0016/3256] Fix unittest --- .../tests/CMakeLists.txt | 20 ++++++------------- .../tests/configs/run_tests.sh | 14 +++++++------ python/paddle/utils/dump_config.py | 1 - 3 files changed, 14 insertions(+), 21 deletions(-) diff --git a/python/paddle/trainer_config_helpers/tests/CMakeLists.txt b/python/paddle/trainer_config_helpers/tests/CMakeLists.txt index 403aafabe91..6c860fd4970 100644 --- a/python/paddle/trainer_config_helpers/tests/CMakeLists.txt +++ b/python/paddle/trainer_config_helpers/tests/CMakeLists.txt @@ -9,17 +9,9 @@ add_test(NAME test_reset_hook ${PYTHON_EXECUTABLE} ${PROJ_ROOT}/python/paddle/trainer_config_helpers/tests/test_reset_hook.py WORKING_DIRECTORY ${PROJ_ROOT}/python/paddle) -if (PROTOBUF_3) - add_paddle_exe(protobuf_equal - ProtobufEqualMain.cpp) - add_test(NAME test_layerHelpers - COMMAND - ${PROJ_ROOT}/python/paddle/trainer_config_helpers/tests/configs/run_tests.sh ${PYTHON_EXECUTABLE} - ${CMAKE_CURRENT_BINARY_DIR}/protobuf_equal - ) -else() - add_test(NAME test_layerHelpers - COMMAND - ${PROJ_ROOT}/python/paddle/trainer_config_helpers/tests/configs/run_tests.sh ${PYTHON_EXECUTABLE} - ) -endif() +add_paddle_exe(protobuf_equal ProtobufEqualMain.cpp) +add_test(NAME test_layerHelpers + COMMAND + ${PROJ_ROOT}/python/paddle/trainer_config_helpers/tests/configs/run_tests.sh ${PYTHON_EXECUTABLE} + ${CMAKE_CURRENT_BINARY_DIR}/protobuf_equal +) diff --git a/python/paddle/trainer_config_helpers/tests/configs/run_tests.sh b/python/paddle/trainer_config_helpers/tests/configs/run_tests.sh index a37eb6439e6..c8a3b190b19 100755 --- a/python/paddle/trainer_config_helpers/tests/configs/run_tests.sh +++ b/python/paddle/trainer_config_helpers/tests/configs/run_tests.sh @@ -2,16 +2,18 @@ cd `dirname $0` set -e +PYTHON_EXEC=$1 +COMPARE_PROTO_UTIL=$2 protostr=`dirname $0`/protostr files=`ls $protostr | grep -v "unittest"` -./generate_protostr.sh $1 +./generate_protostr.sh ${PYTHON_EXEC} . ./file_list.sh -if [ -z $1 ]; then +if [ -z ${COMPARE_PROTO_UTIL} ]; then for file in $files do base_protostr=$protostr/$file @@ -22,20 +24,20 @@ if [ -z $1 ]; then else for file in ${configs[*]} do - if ! $1 $protostr/$file.protostr $protostr/$file.protostr.unittest; then + if ! ${COMPARE_PROTO_UTIL} $protostr/$file.protostr $protostr/$file.protostr.unittest; then diff $protostr/$file.protostr $protostr/$file.protostr.unittest -u fi - if ! $1 $protostr/$file.protostr $protostr/$file.protostr.non_file_config.unittest; then + if ! ${COMPARE_PROTO_UTIL} $protostr/$file.protostr $protostr/$file.protostr.non_file_config.unittest; then diff $protostr/$file.protostr $protostr/$file.protostr.non_file_config.unittest -u fi done for file in ${whole_configs[*]} do - if ! $1 $protostr/$file.protostr $protostr/$file.protostr.unittest --whole; then + if ! ${COMPARE_PROTO_UTIL} $protostr/$file.protostr $protostr/$file.protostr.unittest --whole; then diff $protostr/$file.protostr $protostr/$file.protostr.unittest -u fi - if ! $1 $protostr/$file.protostr $protostr/$file.protostr.non_file_config.unittest --whole; then + if ! ${COMPARE_PROTO_UTIL} $protostr/$file.protostr $protostr/$file.protostr.non_file_config.unittest --whole; then diff $protostr/$file.protostr $protostr/$file.protostr.non_file_config.unittest -u fi done diff --git a/python/paddle/utils/dump_config.py b/python/paddle/utils/dump_config.py index 2ce83bc8a80..d27af7f7624 100644 --- a/python/paddle/utils/dump_config.py +++ b/python/paddle/utils/dump_config.py @@ -21,7 +21,6 @@ __all__ = [] if __name__ == '__main__': whole_conf = False binary = False - print sys.argv if len(sys.argv) == 2: conf = parse_config(sys.argv[1], '') elif len(sys.argv) == 3: -- GitLab From 30a6f9b39ae6c1c3238001b07faec2c3db6494ec Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Thu, 19 Jan 2017 10:50:25 +0800 Subject: [PATCH 0017/3256] Start doing shared c_api library --- paddle/capi/CMakeLists.txt | 4 ++++ paddle/capi/GradientMachine.cpp | 12 ++++++------ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/paddle/capi/CMakeLists.txt b/paddle/capi/CMakeLists.txt index b46fed3a3b4..b5bf08dc245 100644 --- a/paddle/capi/CMakeLists.txt +++ b/paddle/capi/CMakeLists.txt @@ -55,6 +55,10 @@ add_custom_target(paddle_capi_whole ALL set_target_properties(paddle_capi_whole PROPERTIES IMPORTED_LOCATION ${CMAKE_CURRENT_BINARY_DIR}/${capi_whole_library}) +add_library(paddle_capi_shared SHARED ${CAPI_SOURCES}) +target_include_directories(paddle_capi_shared PUBLIC ${CMAKE_CURRENT_BINARY_DIR}) +link_paddle_exe(paddle_capi_shared) + # install library & headers. install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${capi_whole_library} DESTINATION lib) install(FILES ${CAPI_HEADER} DESTINATION include/paddle) diff --git a/paddle/capi/GradientMachine.cpp b/paddle/capi/GradientMachine.cpp index 8299e6442f6..de3a339fa62 100644 --- a/paddle/capi/GradientMachine.cpp +++ b/paddle/capi/GradientMachine.cpp @@ -38,7 +38,7 @@ NeuralNetwork* newCustomNerualNetwork(const std::string& name, } // namespace paddle extern "C" { -int PDGradientMachineCreateForPredict(PD_GradiemtMachine* machine, +int PDGradientMachineCreateForPredict(PD_GradientMachine* machine, void* modelConfigProtobuf, int size) { if (modelConfigProtobuf == nullptr) return kPD_NULLPTR; @@ -55,12 +55,12 @@ int PDGradientMachineCreateForPredict(PD_GradiemtMachine* machine, return kPD_NO_ERROR; } -int PDGradientMachineDestroy(PD_GradiemtMachine machine) { +int PDGradientMachineDestroy(PD_GradientMachine machine) { delete cast(machine); return kPD_NO_ERROR; } -int PDGradientMachineLoadParameterFromDisk(PD_GradiemtMachine machine, +int PDGradientMachineLoadParameterFromDisk(PD_GradientMachine machine, const char* path) { auto m = cast(machine); if (m == nullptr || path == nullptr || m->machine == nullptr) @@ -69,7 +69,7 @@ int PDGradientMachineLoadParameterFromDisk(PD_GradiemtMachine machine, return kPD_NO_ERROR; } -int PDGradientMachineForward(PD_GradiemtMachine machine, +int PDGradientMachineForward(PD_GradientMachine machine, PD_Arguments inArgs, PD_Arguments outArgs, bool isTrain) { @@ -83,10 +83,10 @@ int PDGradientMachineForward(PD_GradiemtMachine machine, return kPD_NO_ERROR; } -int PDGradientMachineCreateSharedParam(PD_GradiemtMachine origin, +int PDGradientMachineCreateSharedParam(PD_GradientMachine origin, void* modelConfigProtobuf, int size, - PD_GradiemtMachine* slave) { + PD_GradientMachine* slave) { auto o = cast(origin); if (origin == nullptr || slave == nullptr || o->machine == nullptr) { return kPD_NULLPTR; -- GitLab From 510ccfef1cdb0ad32e3f622dbeec280c4928aa26 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Thu, 19 Jan 2017 11:12:00 +0800 Subject: [PATCH 0018/3256] Make Paddle exports the symbols --- cmake/flags.cmake | 1 + paddle/capi/PaddleCAPI.h | 70 ++++++++++++++++++++++------------------ 2 files changed, 40 insertions(+), 31 deletions(-) diff --git a/cmake/flags.cmake b/cmake/flags.cmake index b76852fc6c5..137a27c8fe8 100644 --- a/cmake/flags.cmake +++ b/cmake/flags.cmake @@ -185,3 +185,4 @@ if(CUDA_ARCH) endif() set(CUDA_NVCC_FLAGS ${__arch_flags} ${CUDA_NVCC_FLAGS}) + diff --git a/paddle/capi/PaddleCAPI.h b/paddle/capi/PaddleCAPI.h index f2340b8a750..6ca41311788 100644 --- a/paddle/capi/PaddleCAPI.h +++ b/paddle/capi/PaddleCAPI.h @@ -17,6 +17,11 @@ limitations under the License. */ #include #include #include "config.h" + +// Since we only support linux and macos in compile, always use clang or +// gcc 4.8+. DLL_IMPORT/DLL_EXPORT is as simple as below. +#define PD_API __attribute__((visibility("default"))) + #ifdef __cplusplus extern "C" { #endif @@ -44,28 +49,31 @@ typedef enum { */ typedef void* PD_IVector; -int PDIVecCreateNone(PD_IVector* ivec); +PD_API int PDIVecCreateNone(PD_IVector* ivec); -int PDIVecDestroy(PD_IVector ivec); +PD_API int PDIVecDestroy(PD_IVector ivec); -int PDIVectorGet(PD_IVector ivec, int** buffer); +PD_API int PDIVectorGet(PD_IVector ivec, int** buffer); /** * Matrix functions. Return will be a PD_Error type. */ typedef void* PD_Matrix; -int PDMatCreate(PD_Matrix* mat, uint64_t height, uint64_t width, bool useGpu); +PD_API int PDMatCreate(PD_Matrix* mat, + uint64_t height, + uint64_t width, + bool useGpu); -int PDMatDestroy(PD_Matrix mat); +PD_API int PDMatDestroy(PD_Matrix mat); -int PDMatCopyToRow(PD_Matrix mat, uint64_t rowID, pd_real* rowArray); +PD_API int PDMatCopyToRow(PD_Matrix mat, uint64_t rowID, pd_real* rowArray); -int PDMatGetRow(PD_Matrix mat, uint64_t rowID, pd_real** rawRowBuffer); +PD_API int PDMatGetRow(PD_Matrix mat, uint64_t rowID, pd_real** rawRowBuffer); -int PDMatCreateNone(PD_Matrix* mat); +PD_API int PDMatCreateNone(PD_Matrix* mat); -int PDMatGetShape(PD_Matrix mat, uint64_t* height, uint64_t* width); +PD_API int PDMatGetShape(PD_Matrix mat, uint64_t* height, uint64_t* width); /** * Arguments functions. Each argument means layer output. Arguments means a @@ -73,48 +81,48 @@ int PDMatGetShape(PD_Matrix mat, uint64_t* height, uint64_t* width); */ typedef void* PD_Arguments; -int PDArgsCreateNone(PD_Arguments* args); +PD_API int PDArgsCreateNone(PD_Arguments* args); -int PDArgsDestroy(PD_Arguments args); +PD_API int PDArgsDestroy(PD_Arguments args); -int PDArgsGetSize(PD_Arguments args, uint64_t* size); +PD_API int PDArgsGetSize(PD_Arguments args, uint64_t* size); -int PDArgsResize(PD_Arguments args, uint64_t size); +PD_API int PDArgsResize(PD_Arguments args, uint64_t size); -int PDArgsSetValue(PD_Arguments args, uint64_t ID, PD_Matrix mat); +PD_API int PDArgsSetValue(PD_Arguments args, uint64_t ID, PD_Matrix mat); -int PDArgsGetValue(PD_Arguments args, uint64_t ID, PD_Matrix mat); +PD_API int PDArgsGetValue(PD_Arguments args, uint64_t ID, PD_Matrix mat); -int PDArgsGetIds(PD_Arguments args, uint64_t ID, PD_IVector ids); +PD_API int PDArgsGetIds(PD_Arguments args, uint64_t ID, PD_IVector ids); /** * @brief GradientMachine means a neural network. */ typedef void* PD_GradientMachine; -int PDGradientMachineCreateForPredict(PD_GradientMachine* machine, - void* modelConfigProtobuf, - int size); +PD_API int PDGradientMachineCreateForPredict(PD_GradientMachine* machine, + void* modelConfigProtobuf, + int size); -int PDGradientMachineLoadParameterFromDisk(PD_GradientMachine machine, - const char* path); +PD_API int PDGradientMachineLoadParameterFromDisk(PD_GradientMachine machine, + const char* path); -int PDGradientMachineForward(PD_GradientMachine machine, - PD_Arguments inArgs, - PD_Arguments outArgs, - bool isTrain); +PD_API int PDGradientMachineForward(PD_GradientMachine machine, + PD_Arguments inArgs, + PD_Arguments outArgs, + bool isTrain); -int PDGradientMachineCreateSharedParam(PD_GradientMachine origin, - void* modelConfigProtobuf, - int size, - PD_GradientMachine* slave); +PD_API int PDGradientMachineCreateSharedParam(PD_GradientMachine origin, + void* modelConfigProtobuf, + int size, + PD_GradientMachine* slave); -int PDGradientMachineDestroy(PD_GradientMachine machine); +PD_API int PDGradientMachineDestroy(PD_GradientMachine machine); /** * Initialize Paddle. */ -int PDInit(int argc, char** argv); +PD_API int PDInit(int argc, char** argv); #ifdef __cplusplus } -- GitLab From b18c1268988b8d276f6dd4002a37a70dc2c8ac70 Mon Sep 17 00:00:00 2001 From: liaogang Date: Tue, 14 Feb 2017 22:01:17 +0800 Subject: [PATCH 0019/3256] set macosx deployment version --- cmake/external/python.cmake | 4 ---- cmake/system.cmake | 3 +++ 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/cmake/external/python.cmake b/cmake/external/python.cmake index 6372a9a768e..0accf1a8dd8 100644 --- a/cmake/external/python.cmake +++ b/cmake/external/python.cmake @@ -221,7 +221,3 @@ ENDIF(PYTHONLIBS_FOUND AND PYTHONINTERP_FOUND) INCLUDE_DIRECTORIES(${PYTHON_INCLUDE_DIR}) INCLUDE_DIRECTORIES(${PYTHON_NUMPY_INCLUDE_DIR}) - -MESSAGE("[Paddle] Python Executable: ${PYTHON_EXECUTABLE}") -MESSAGE("[Paddle] Python Include: ${PYTHON_INCLUDE_DIRS}") -MESSAGE("[Paddle] Python Libraries: ${PYTHON_LIBRARIES}") diff --git a/cmake/system.cmake b/cmake/system.cmake index 3e472da7e0b..58ac7dcb94d 100644 --- a/cmake/system.cmake +++ b/cmake/system.cmake @@ -28,6 +28,9 @@ ELSE(WIN32) STRING(REGEX MATCH "[0-9]+.[0-9]+" VERSION "${MACOSX_VERSION}") SET(MACOS_VERSION ${VERSION}) SET(HOST_SYSTEM "macosx") + IF(NOT DEFINED ENV{MACOSX_DEPLOYMENT_TARGET}) + SET(CMAKE_OSX_DEPLOYMENT_TARGET ${MACOS_VERSION}) + ENDIF() ELSE(APPLE) IF(EXISTS "/etc/issue") -- GitLab From d330b6aa02f8817188aeb223572539b429659033 Mon Sep 17 00:00:00 2001 From: liaogang Date: Fri, 24 Feb 2017 17:42:45 +0800 Subject: [PATCH 0020/3256] Mac OSX System configure --- CMakeLists.txt | 7 ++++--- cmake/system.cmake | 4 +++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4b0682c4fe9..1bb18bb23ae 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,17 +14,18 @@ cmake_minimum_required(VERSION 3.0) -project(paddle CXX C) - set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake") set(PROJ_ROOT ${CMAKE_SOURCE_DIR}) +include(system) + +project(paddle CXX C) + find_package(Sphinx) find_package(CUDA QUIET) find_package(Git REQUIRED) find_package(Threads REQUIRED) -include(system) include(simd) ################################ Configurations ####################################### diff --git a/cmake/system.cmake b/cmake/system.cmake index 58ac7dcb94d..8da17744a43 100644 --- a/cmake/system.cmake +++ b/cmake/system.cmake @@ -29,7 +29,9 @@ ELSE(WIN32) SET(MACOS_VERSION ${VERSION}) SET(HOST_SYSTEM "macosx") IF(NOT DEFINED ENV{MACOSX_DEPLOYMENT_TARGET}) - SET(CMAKE_OSX_DEPLOYMENT_TARGET ${MACOS_VERSION}) + # Set cache variable - end user may change this during ccmake or cmake-gui configure. + SET(CMAKE_OSX_DEPLOYMENT_TARGET ${MACOS_VERSION} CACHE STRING + "Minimum OS X version to target for deployment (at runtime); newer APIs weak linked. Set to empty string for default value.") ENDIF() ELSE(APPLE) -- GitLab From 8a1e32d812b9cfd5a33756822c4d0f3051db8672 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Tue, 7 Mar 2017 15:42:54 +0800 Subject: [PATCH 0021/3256] Fix compile error. --- paddle/capi/CMakeLists.txt | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/paddle/capi/CMakeLists.txt b/paddle/capi/CMakeLists.txt index b5bf08dc245..f5827317b0d 100644 --- a/paddle/capi/CMakeLists.txt +++ b/paddle/capi/CMakeLists.txt @@ -41,10 +41,8 @@ add_custom_target(paddle_capi_whole ALL COMMAND mkdir -p o_files/function && cd o_files/function/ && ar -x $ COMMAND mkdir -p o_files/gserver && cd o_files/gserver/ && ar -x $ COMMAND mkdir -p o_files/proto && cd o_files/proto/ && ar -x $ - COMMAND mkdir -p o_files/network && cd o_files/network/ && ar -x - +$ - + COMMAND mkdir -p o_files/pserver && cd o_files/pserver/ && ar -x - +$ + COMMAND mkdir -p o_files/pserver && cd o_files/pserver/ && ar -x $ COMMAND ar crs ${capi_whole_library} `find ./o_files -name '*.o'` COMMAND rm -rf o_files WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} -- GitLab From c32ade74c2bfd5afbece2453f3bf8d967b00a63b Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Tue, 7 Mar 2017 15:54:24 +0800 Subject: [PATCH 0022/3256] Add todo --- paddle/capi/PaddleCAPI.h | 4 ++++ paddle/capi/Vector.cpp | 10 ++++++++++ paddle/capi/tests/test_Vector.cpp | 4 ++++ 3 files changed, 18 insertions(+) diff --git a/paddle/capi/PaddleCAPI.h b/paddle/capi/PaddleCAPI.h index 6ca41311788..4819be3447f 100644 --- a/paddle/capi/PaddleCAPI.h +++ b/paddle/capi/PaddleCAPI.h @@ -55,6 +55,10 @@ PD_API int PDIVecDestroy(PD_IVector ivec); PD_API int PDIVectorGet(PD_IVector ivec, int** buffer); +PD_API int PDIVectorResize(PD_IVector ivec, uint64_t size); + +PD_API int PDIVectorGetSize(PD_IVector ivec, uint64_t* size); + /** * Matrix functions. Return will be a PD_Error type. */ diff --git a/paddle/capi/Vector.cpp b/paddle/capi/Vector.cpp index 5b4fe0666cb..eb3501fb5b8 100644 --- a/paddle/capi/Vector.cpp +++ b/paddle/capi/Vector.cpp @@ -39,4 +39,14 @@ int PDIVectorGet(PD_IVector ivec, int** buffer) { *buffer = v->vec->getData(); return kPD_NO_ERROR; } + +int PDIVectorResize(PD_IVector ivec, uint64_t size) { + // TODO(lizhao): Complete this method. + return 0; +} + +int PDIVectorGetSize(PD_IVector ivec, uint64_t* size) { + // TODO(lizhao): Complete this method. + return 0; +} } diff --git a/paddle/capi/tests/test_Vector.cpp b/paddle/capi/tests/test_Vector.cpp index 122f7df176d..2697e80b92c 100644 --- a/paddle/capi/tests/test_Vector.cpp +++ b/paddle/capi/tests/test_Vector.cpp @@ -18,5 +18,9 @@ limitations under the License. */ TEST(CAPIVector, create) { PD_IVector vec; ASSERT_EQ(kPD_NO_ERROR, PDIVecCreateNone(&vec)); + ASSERT_EQ(kPD_NO_ERROR, PDIVectorResize(vec, 1000)); + uint64_t size; + ASSERT_EQ(kPD_NO_ERROR, PDIVectorGetSize(vec, &size)); + ASSERT_EQ(1000, size); ASSERT_EQ(kPD_NO_ERROR, PDIVecDestroy(vec)); } -- GitLab From 97c64254568b4c68ceb778c1721e36b43e2ad5d1 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Tue, 7 Mar 2017 16:16:46 +0800 Subject: [PATCH 0023/3256] Add some more interfaces --- paddle/capi/Arguments.cpp | 33 +++++++++++++++++++++++++++++++++ paddle/capi/PaddleCAPI.h | 30 ++++++++++++++++++++++++++++++ paddle/capi/Vector.cpp | 13 +++++++++---- 3 files changed, 72 insertions(+), 4 deletions(-) diff --git a/paddle/capi/Arguments.cpp b/paddle/capi/Arguments.cpp index 60bdea15ae7..678a80ac3f4 100644 --- a/paddle/capi/Arguments.cpp +++ b/paddle/capi/Arguments.cpp @@ -72,4 +72,37 @@ int PDArgsGetIds(PD_Arguments args, uint64_t ID, PD_IVector ids) { iv->vec = a->args[ID].ids; return kPD_NO_ERROR; } + +int PDArgsSetIds(PD_Arguments args, uint64_t ID, PD_IVector ids) { + //! TODO(lizhao): Complete this method. + return kPD_UNDEFINED_ERROR; +} + +int PDArgsSetSequenceStartPos(PD_Arguments args, + uint64_t ID, + PD_IVector seqPos) { + //! TODO(lizhao): Complete this method. + return kPD_UNDEFINED_ERROR; +} + +int PDArgsSetSubSequenceStartPos(PD_Arguments args, + uint64_t ID, + PD_IVector subSeqPos) { + //! TODO(lizhao): Complete this method. + return kPD_UNDEFINED_ERROR; +} + +int PDArgsGetSequenceStartPos(PD_Arguments args, + uint64_t ID, + PD_IVector seqPos) { + //! TODO(lizhao): Complete this method. + return kPD_UNDEFINED_ERROR; +} + +int PDArgsGetSubSequenceStartPos(PD_Arguments args, + uint64_t ID, + PD_IVector subSeqPos) { + //! TODO(lizhao): Complete this method. + return kPD_UNDEFINED_ERROR; +} } diff --git a/paddle/capi/PaddleCAPI.h b/paddle/capi/PaddleCAPI.h index 4819be3447f..6eea60ef749 100644 --- a/paddle/capi/PaddleCAPI.h +++ b/paddle/capi/PaddleCAPI.h @@ -51,6 +51,19 @@ typedef void* PD_IVector; PD_API int PDIVecCreateNone(PD_IVector* ivec); +/** + * @brief PDIVectorCreate create a paddle int vector + * @param [out] ivec: output int vector. + * @param [in] array: input array. + * @param [in] size: input array size. + * @param [in] copy: memory copy or just use same memory. True if copy. + * @return PD_Error + */ +PD_API int PDIVectorCreate(PD_IVector* ivec, + int* array, + uint64_t size, + bool copy); + PD_API int PDIVecDestroy(PD_IVector ivec); PD_API int PDIVectorGet(PD_IVector ivec, int** buffer); @@ -99,6 +112,23 @@ PD_API int PDArgsGetValue(PD_Arguments args, uint64_t ID, PD_Matrix mat); PD_API int PDArgsGetIds(PD_Arguments args, uint64_t ID, PD_IVector ids); +PD_API int PDArgsSetIds(PD_Arguments args, uint64_t ID, PD_IVector ids); + +PD_API int PDArgsSetSequenceStartPos(PD_Arguments args, + uint64_t ID, + PD_IVector seqPos); + +PD_API int PDArgsGetSequenceStartPos(PD_Arguments args, + uint64_t ID, + PD_IVector seqPos); + +PD_API int PDArgsSetSubSequenceStartPos(PD_Arguments args, + uint64_t ID, + PD_IVector subSeqPos); + +PD_API int PDArgsGetSubSequenceStartPos(PD_Arguments args, + uint64_t ID, + PD_IVector subSeqPos); /** * @brief GradientMachine means a neural network. */ diff --git a/paddle/capi/Vector.cpp b/paddle/capi/Vector.cpp index eb3501fb5b8..a2e6f3507de 100644 --- a/paddle/capi/Vector.cpp +++ b/paddle/capi/Vector.cpp @@ -26,6 +26,11 @@ int PDIVecCreateNone(PD_IVector* ivec) { return kPD_NO_ERROR; } +int PDIVectorCreate(PD_IVector* ivec, int* array, uint64_t size, bool copy) { + //! TODO(lizhao): Complete this method. + return kPD_UNDEFINED_ERROR; +} + int PDIVecDestroy(PD_IVector ivec) { if (ivec == nullptr) return kPD_NULLPTR; delete cast(ivec); @@ -41,12 +46,12 @@ int PDIVectorGet(PD_IVector ivec, int** buffer) { } int PDIVectorResize(PD_IVector ivec, uint64_t size) { - // TODO(lizhao): Complete this method. - return 0; + //! TODO(lizhao): Complete this method. + return kPD_UNDEFINED_ERROR; } int PDIVectorGetSize(PD_IVector ivec, uint64_t* size) { - // TODO(lizhao): Complete this method. - return 0; + //! TODO(lizhao): Complete this method. + return kPD_UNDEFINED_ERROR; } } -- GitLab From 3519c63034e4eb86c72de218ef67e921b6cc38eb Mon Sep 17 00:00:00 2001 From: livc Date: Thu, 9 Mar 2017 13:51:50 +0800 Subject: [PATCH 0024/3256] complete some functions of c-api. --- paddle/capi/Arguments.cpp | 42 ++++++++++++++++++++++++---- paddle/capi/Vector.cpp | 23 +++++++++++++-- paddle/capi/tests/test_Arguments.cpp | 36 ++++++++++++++++++++++++ paddle/capi/tests/test_Vector.cpp | 11 ++++++-- 4 files changed, 102 insertions(+), 10 deletions(-) diff --git a/paddle/capi/Arguments.cpp b/paddle/capi/Arguments.cpp index 678a80ac3f4..baabd44cc0c 100644 --- a/paddle/capi/Arguments.cpp +++ b/paddle/capi/Arguments.cpp @@ -75,34 +75,66 @@ int PDArgsGetIds(PD_Arguments args, uint64_t ID, PD_IVector ids) { int PDArgsSetIds(PD_Arguments args, uint64_t ID, PD_IVector ids) { //! TODO(lizhao): Complete this method. - return kPD_UNDEFINED_ERROR; + if (args == nullptr || ids == nullptr) return kPD_NULLPTR; + auto iv = paddle::capi::cast(ids); + if (iv->vec == nullptr) return kPD_NULLPTR; + auto a = castArg(args); + if (ID >= a->args.size()) return kPD_OUT_OF_RANGE; + a->args[ID].ids = iv->vec; + return kPD_NO_ERROR; } int PDArgsSetSequenceStartPos(PD_Arguments args, uint64_t ID, PD_IVector seqPos) { //! TODO(lizhao): Complete this method. - return kPD_UNDEFINED_ERROR; + if (args == nullptr || seqPos == nullptr) return kPD_NULLPTR; + auto iv = paddle::capi::cast(seqPos); + if (iv->vec == nullptr) return kPD_NULLPTR; + auto a = castArg(args); + if (ID >= a->args.size()) return kPD_OUT_OF_RANGE; + a->args[ID].sequenceStartPositions = + std::make_shared(iv->vec); + return kPD_NO_ERROR; } int PDArgsSetSubSequenceStartPos(PD_Arguments args, uint64_t ID, PD_IVector subSeqPos) { //! TODO(lizhao): Complete this method. - return kPD_UNDEFINED_ERROR; + if (args == nullptr || subSeqPos == nullptr) return kPD_NULLPTR; + auto iv = paddle::capi::cast(subSeqPos); + if (iv->vec == nullptr) return kPD_NULLPTR; + auto a = castArg(args); + if (ID >= a->args.size()) return kPD_OUT_OF_RANGE; + a->args[ID].sequenceStartPositions = + std::make_shared(iv->vec); + return kPD_NO_ERROR; } int PDArgsGetSequenceStartPos(PD_Arguments args, uint64_t ID, PD_IVector seqPos) { //! TODO(lizhao): Complete this method. - return kPD_UNDEFINED_ERROR; + if (args == nullptr || seqPos == nullptr) return kPD_NULLPTR; + auto iv = castIVec(seqPos); + auto a = castArg(args); + if (ID >= a->args.size()) return kPD_OUT_OF_RANGE; + std::make_shared(iv->vec) = + a->args[ID].sequenceStartPositions; + return kPD_NO_ERROR; } int PDArgsGetSubSequenceStartPos(PD_Arguments args, uint64_t ID, PD_IVector subSeqPos) { //! TODO(lizhao): Complete this method. - return kPD_UNDEFINED_ERROR; + if (args == nullptr || subSeqPos == nullptr) return kPD_NULLPTR; + auto iv = castIVec(subSeqPos); + auto a = castArg(args); + if (ID >= a->args.size()) return kPD_OUT_OF_RANGE; + std::make_shared(iv->vec) = + a->args[ID].sequenceStartPositions; + return kPD_NO_ERROR; } } diff --git a/paddle/capi/Vector.cpp b/paddle/capi/Vector.cpp index a2e6f3507de..38a5fbc00a8 100644 --- a/paddle/capi/Vector.cpp +++ b/paddle/capi/Vector.cpp @@ -28,7 +28,16 @@ int PDIVecCreateNone(PD_IVector* ivec) { int PDIVectorCreate(PD_IVector* ivec, int* array, uint64_t size, bool copy) { //! TODO(lizhao): Complete this method. - return kPD_UNDEFINED_ERROR; + if (ivec == nullptr) return kPD_NULLPTR; + auto ptr = new paddle::capi::CIVector(); + if (copy) { + ptr->vec = paddle::IVector::create(size, false); + ptr->vec->copyFrom(array, size); + } else { + ptr->vec = paddle::IVector::create(array, size, false); + } + *ivec = ptr; + return kPD_NO_ERROR; } int PDIVecDestroy(PD_IVector ivec) { @@ -47,11 +56,19 @@ int PDIVectorGet(PD_IVector ivec, int** buffer) { int PDIVectorResize(PD_IVector ivec, uint64_t size) { //! TODO(lizhao): Complete this method. - return kPD_UNDEFINED_ERROR; + if (ivec == nullptr) return kPD_NULLPTR; + auto v = cast(ivec); + if (v->vec == nullptr) return kPD_NULLPTR; + v->vec->resize(size); + return kPD_NO_ERROR; } int PDIVectorGetSize(PD_IVector ivec, uint64_t* size) { //! TODO(lizhao): Complete this method. - return kPD_UNDEFINED_ERROR; + if (ivec == nullptr) return kPD_NULLPTR; + auto v = cast(ivec); + if (v->vec == nullptr) return kPD_NULLPTR; + *size = v->vec->getSize(); + return kPD_NO_ERROR; } } diff --git a/paddle/capi/tests/test_Arguments.cpp b/paddle/capi/tests/test_Arguments.cpp index 4a18ffbf474..1186d2921ba 100644 --- a/paddle/capi/tests/test_Arguments.cpp +++ b/paddle/capi/tests/test_Arguments.cpp @@ -70,3 +70,39 @@ TEST(CAPIArguments, value) { ASSERT_EQ(kPD_NO_ERROR, PDMatDestroy(mat)); ASSERT_EQ(kPD_NO_ERROR, PDArgsDestroy(args)); } + +TEST(CAPIArguments, ids) { + PD_Arguments args; + ASSERT_EQ(kPD_NO_ERROR, PDArgsCreateNone(&args)); + ASSERT_EQ(kPD_NO_ERROR, PDArgsResize(args, 1)); + + PD_IVector ivec; + int array[3] = {1, 2, 3}; + ASSERT_EQ(kPD_NO_ERROR, PDIVectorCreate(&ivec, array, 3, true)); + ASSERT_EQ(kPD_NO_ERROR, PDArgsSetIds(args, 0, ivec)); + + PD_IVector val; + ASSERT_EQ(kPD_NO_ERROR, PDIVecCreateNone(&val)); + ASSERT_EQ(kPD_NO_ERROR, PDArgsGetIds(args, 0, val)); + ASSERT_EQ(kPD_NO_ERROR, PDIVecDestroy(ivec)); + ASSERT_EQ(kPD_NO_ERROR, PDIVecDestroy(val)); + ASSERT_EQ(kPD_NO_ERROR, PDArgsDestroy(args)); +} + +TEST(CAPIArguments, Sequence) { + PD_Arguments args; + ASSERT_EQ(kPD_NO_ERROR, PDArgsCreateNone(&args)); + ASSERT_EQ(kPD_NO_ERROR, PDArgsResize(args, 1)); + + PD_IVector ivec; + int array[3] = {1, 2, 3}; + ASSERT_EQ(kPD_NO_ERROR, PDIVectorCreate(&ivec, array, 3, true)); + ASSERT_EQ(kPD_NO_ERROR, PDArgsSetSequenceStartPos(args, 0, ivec)); + + PD_IVector val; + ASSERT_EQ(kPD_NO_ERROR, PDIVecCreateNone(&val)); + ASSERT_EQ(kPD_NO_ERROR, PDArgsGetSequenceStartPos(args, 0, val)); + ASSERT_EQ(kPD_NO_ERROR, PDIVecDestroy(ivec)); + ASSERT_EQ(kPD_NO_ERROR, PDIVecDestroy(val)); + ASSERT_EQ(kPD_NO_ERROR, PDArgsDestroy(args)); +} diff --git a/paddle/capi/tests/test_Vector.cpp b/paddle/capi/tests/test_Vector.cpp index 2697e80b92c..547d0ef20d1 100644 --- a/paddle/capi/tests/test_Vector.cpp +++ b/paddle/capi/tests/test_Vector.cpp @@ -17,10 +17,17 @@ limitations under the License. */ TEST(CAPIVector, create) { PD_IVector vec; - ASSERT_EQ(kPD_NO_ERROR, PDIVecCreateNone(&vec)); + int array[3] = {1, 2, 3}; + ASSERT_EQ(kPD_NO_ERROR, PDIVectorCreate(&vec, array, 3, true)); + ASSERT_EQ(kPD_NO_ERROR, PDIVectorCreate(&vec, array, 3, false)); ASSERT_EQ(kPD_NO_ERROR, PDIVectorResize(vec, 1000)); uint64_t size; ASSERT_EQ(kPD_NO_ERROR, PDIVectorGetSize(vec, &size)); - ASSERT_EQ(1000, size); + ASSERT_EQ(kPD_NO_ERROR, PDIVecDestroy(vec)); +} + +TEST(CAPIVector, createNone) { + PD_IVector vec; + ASSERT_EQ(kPD_NO_ERROR, PDIVecCreateNone(&vec)); ASSERT_EQ(kPD_NO_ERROR, PDIVecDestroy(vec)); } -- GitLab From 5a9987a317eef6898f3e0386331bc34174cb0a53 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Thu, 9 Mar 2017 15:30:25 +0800 Subject: [PATCH 0025/3256] Fix bugs in lizhao's code --- paddle/capi/Arguments.cpp | 14 +++++--------- paddle/capi/Vector.cpp | 3 --- paddle/capi/tests/test_Arguments.cpp | 22 +++++++++++++++++++--- 3 files changed, 24 insertions(+), 15 deletions(-) diff --git a/paddle/capi/Arguments.cpp b/paddle/capi/Arguments.cpp index baabd44cc0c..8d00bda3cb9 100644 --- a/paddle/capi/Arguments.cpp +++ b/paddle/capi/Arguments.cpp @@ -87,7 +87,6 @@ int PDArgsSetIds(PD_Arguments args, uint64_t ID, PD_IVector ids) { int PDArgsSetSequenceStartPos(PD_Arguments args, uint64_t ID, PD_IVector seqPos) { - //! TODO(lizhao): Complete this method. if (args == nullptr || seqPos == nullptr) return kPD_NULLPTR; auto iv = paddle::capi::cast(seqPos); if (iv->vec == nullptr) return kPD_NULLPTR; @@ -101,13 +100,12 @@ int PDArgsSetSequenceStartPos(PD_Arguments args, int PDArgsSetSubSequenceStartPos(PD_Arguments args, uint64_t ID, PD_IVector subSeqPos) { - //! TODO(lizhao): Complete this method. if (args == nullptr || subSeqPos == nullptr) return kPD_NULLPTR; auto iv = paddle::capi::cast(subSeqPos); if (iv->vec == nullptr) return kPD_NULLPTR; auto a = castArg(args); if (ID >= a->args.size()) return kPD_OUT_OF_RANGE; - a->args[ID].sequenceStartPositions = + a->args[ID].subSequenceStartPositions = std::make_shared(iv->vec); return kPD_NO_ERROR; } @@ -115,26 +113,24 @@ int PDArgsSetSubSequenceStartPos(PD_Arguments args, int PDArgsGetSequenceStartPos(PD_Arguments args, uint64_t ID, PD_IVector seqPos) { - //! TODO(lizhao): Complete this method. if (args == nullptr || seqPos == nullptr) return kPD_NULLPTR; auto iv = castIVec(seqPos); auto a = castArg(args); if (ID >= a->args.size()) return kPD_OUT_OF_RANGE; - std::make_shared(iv->vec) = - a->args[ID].sequenceStartPositions; + paddle::Argument& arg = a->args[ID]; + iv->vec = arg.sequenceStartPositions->getMutableVector(false); return kPD_NO_ERROR; } int PDArgsGetSubSequenceStartPos(PD_Arguments args, uint64_t ID, PD_IVector subSeqPos) { - //! TODO(lizhao): Complete this method. if (args == nullptr || subSeqPos == nullptr) return kPD_NULLPTR; auto iv = castIVec(subSeqPos); auto a = castArg(args); if (ID >= a->args.size()) return kPD_OUT_OF_RANGE; - std::make_shared(iv->vec) = - a->args[ID].sequenceStartPositions; + paddle::Argument& arg = a->args[ID]; + iv->vec = arg.subSequenceStartPositions->getMutableVector(false); return kPD_NO_ERROR; } } diff --git a/paddle/capi/Vector.cpp b/paddle/capi/Vector.cpp index 38a5fbc00a8..af219255137 100644 --- a/paddle/capi/Vector.cpp +++ b/paddle/capi/Vector.cpp @@ -27,7 +27,6 @@ int PDIVecCreateNone(PD_IVector* ivec) { } int PDIVectorCreate(PD_IVector* ivec, int* array, uint64_t size, bool copy) { - //! TODO(lizhao): Complete this method. if (ivec == nullptr) return kPD_NULLPTR; auto ptr = new paddle::capi::CIVector(); if (copy) { @@ -55,7 +54,6 @@ int PDIVectorGet(PD_IVector ivec, int** buffer) { } int PDIVectorResize(PD_IVector ivec, uint64_t size) { - //! TODO(lizhao): Complete this method. if (ivec == nullptr) return kPD_NULLPTR; auto v = cast(ivec); if (v->vec == nullptr) return kPD_NULLPTR; @@ -64,7 +62,6 @@ int PDIVectorResize(PD_IVector ivec, uint64_t size) { } int PDIVectorGetSize(PD_IVector ivec, uint64_t* size) { - //! TODO(lizhao): Complete this method. if (ivec == nullptr) return kPD_NULLPTR; auto v = cast(ivec); if (v->vec == nullptr) return kPD_NULLPTR; diff --git a/paddle/capi/tests/test_Arguments.cpp b/paddle/capi/tests/test_Arguments.cpp index 1186d2921ba..9357f3a5846 100644 --- a/paddle/capi/tests/test_Arguments.cpp +++ b/paddle/capi/tests/test_Arguments.cpp @@ -89,7 +89,8 @@ TEST(CAPIArguments, ids) { ASSERT_EQ(kPD_NO_ERROR, PDArgsDestroy(args)); } -TEST(CAPIArguments, Sequence) { +template +void testSequenceHelper(T1 setter, T2 getter) { PD_Arguments args; ASSERT_EQ(kPD_NO_ERROR, PDArgsCreateNone(&args)); ASSERT_EQ(kPD_NO_ERROR, PDArgsResize(args, 1)); @@ -97,12 +98,27 @@ TEST(CAPIArguments, Sequence) { PD_IVector ivec; int array[3] = {1, 2, 3}; ASSERT_EQ(kPD_NO_ERROR, PDIVectorCreate(&ivec, array, 3, true)); - ASSERT_EQ(kPD_NO_ERROR, PDArgsSetSequenceStartPos(args, 0, ivec)); + ASSERT_EQ(kPD_NO_ERROR, setter(args, 0, ivec)); PD_IVector val; ASSERT_EQ(kPD_NO_ERROR, PDIVecCreateNone(&val)); - ASSERT_EQ(kPD_NO_ERROR, PDArgsGetSequenceStartPos(args, 0, val)); + ASSERT_EQ(kPD_NO_ERROR, getter(args, 0, val)); + uint64_t size; + ASSERT_EQ(kPD_NO_ERROR, PDIVectorGetSize(val, &size)); + + int* rawBuf; + ASSERT_EQ(kPD_NO_ERROR, PDIVectorGet(val, &rawBuf)); + for (size_t i = 0; i < size; ++i) { + ASSERT_EQ(array[i], rawBuf[i]); + } + ASSERT_EQ(kPD_NO_ERROR, PDIVecDestroy(ivec)); ASSERT_EQ(kPD_NO_ERROR, PDIVecDestroy(val)); ASSERT_EQ(kPD_NO_ERROR, PDArgsDestroy(args)); } + +TEST(CAPIArguments, Sequence) { + testSequenceHelper(PDArgsSetSequenceStartPos, PDArgsGetSequenceStartPos); + testSequenceHelper(PDArgsSetSubSequenceStartPos, + PDArgsGetSubSequenceStartPos); +} -- GitLab From 7bb12fdb50e45b2a98bfd731b2dde105edb64ee0 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Fri, 10 Mar 2017 13:18:48 +0800 Subject: [PATCH 0026/3256] Refactor API follow comments. --- paddle/capi/Arguments.cpp | 44 ++- paddle/capi/GradientMachine.cpp | 28 +- paddle/capi/Main.cpp | 2 +- paddle/capi/Matrix.cpp | 19 +- paddle/capi/PaddleCAPI.h | 303 +++++++++++++++++---- paddle/capi/Vector.cpp | 25 +- paddle/capi/tests/test_Arguments.cpp | 31 +-- paddle/capi/tests/test_GradientMachine.cpp | 9 +- paddle/capi/tests/test_Matrix.cpp | 6 +- paddle/capi/tests/test_Vector.cpp | 6 +- 10 files changed, 314 insertions(+), 159 deletions(-) diff --git a/paddle/capi/Arguments.cpp b/paddle/capi/Arguments.cpp index 8d00bda3cb9..3d60165962d 100644 --- a/paddle/capi/Arguments.cpp +++ b/paddle/capi/Arguments.cpp @@ -21,31 +21,27 @@ using paddle::capi::cast; #define castIVec(v) cast(v) extern "C" { -int PDArgsCreateNone(PD_Arguments* args) { - auto ptr = new paddle::capi::CArguments(); - *args = ptr; - return kPD_NO_ERROR; -} +PD_Arguments PDArgsCreateNone() { return new paddle::capi::CArguments(); } -int PDArgsDestroy(PD_Arguments args) { +PD_Error PDArgsDestroy(PD_Arguments args) { if (args == nullptr) return kPD_NULLPTR; delete castArg(args); return kPD_NO_ERROR; } -int PDArgsGetSize(PD_Arguments args, uint64_t* size) { +PD_Error PDArgsGetSize(PD_Arguments args, uint64_t* size) { if (args == nullptr || size == nullptr) return kPD_NULLPTR; *size = castArg(args)->args.size(); return kPD_NO_ERROR; } -int PDArgsResize(PD_Arguments args, uint64_t size) { +PD_Error PDArgsResize(PD_Arguments args, uint64_t size) { if (args == nullptr) return kPD_NULLPTR; castArg(args)->args.resize(size); return kPD_NO_ERROR; } -int PDArgsSetValue(PD_Arguments args, uint64_t ID, PD_Matrix mat) { +PD_Error PDArgsSetValue(PD_Arguments args, uint64_t ID, PD_Matrix mat) { if (args == nullptr || mat == nullptr) return kPD_NULLPTR; auto m = paddle::capi::cast(mat); if (m->mat == nullptr) return kPD_NULLPTR; @@ -55,7 +51,7 @@ int PDArgsSetValue(PD_Arguments args, uint64_t ID, PD_Matrix mat) { return kPD_NO_ERROR; } -int PDArgsGetValue(PD_Arguments args, uint64_t ID, PD_Matrix mat) { +PD_Error PDArgsGetValue(PD_Arguments args, uint64_t ID, PD_Matrix mat) { if (args == nullptr || mat == nullptr) return kPD_NULLPTR; auto m = paddle::capi::cast(mat); auto a = castArg(args); @@ -64,7 +60,7 @@ int PDArgsGetValue(PD_Arguments args, uint64_t ID, PD_Matrix mat) { return kPD_NO_ERROR; } -int PDArgsGetIds(PD_Arguments args, uint64_t ID, PD_IVector ids) { +PD_Error PDArgsGetIds(PD_Arguments args, uint64_t ID, PD_IVector ids) { if (args == nullptr || ids == nullptr) return kPD_NULLPTR; auto iv = castIVec(ids); auto a = castArg(args); @@ -73,7 +69,7 @@ int PDArgsGetIds(PD_Arguments args, uint64_t ID, PD_IVector ids) { return kPD_NO_ERROR; } -int PDArgsSetIds(PD_Arguments args, uint64_t ID, PD_IVector ids) { +PD_Error PDArgsSetIds(PD_Arguments args, uint64_t ID, PD_IVector ids) { //! TODO(lizhao): Complete this method. if (args == nullptr || ids == nullptr) return kPD_NULLPTR; auto iv = paddle::capi::cast(ids); @@ -84,9 +80,9 @@ int PDArgsSetIds(PD_Arguments args, uint64_t ID, PD_IVector ids) { return kPD_NO_ERROR; } -int PDArgsSetSequenceStartPos(PD_Arguments args, - uint64_t ID, - PD_IVector seqPos) { +PD_Error PDArgsSetSequenceStartPos(PD_Arguments args, + uint64_t ID, + PD_IVector seqPos) { if (args == nullptr || seqPos == nullptr) return kPD_NULLPTR; auto iv = paddle::capi::cast(seqPos); if (iv->vec == nullptr) return kPD_NULLPTR; @@ -97,9 +93,9 @@ int PDArgsSetSequenceStartPos(PD_Arguments args, return kPD_NO_ERROR; } -int PDArgsSetSubSequenceStartPos(PD_Arguments args, - uint64_t ID, - PD_IVector subSeqPos) { +PD_Error PDArgsSetSubSequenceStartPos(PD_Arguments args, + uint64_t ID, + PD_IVector subSeqPos) { if (args == nullptr || subSeqPos == nullptr) return kPD_NULLPTR; auto iv = paddle::capi::cast(subSeqPos); if (iv->vec == nullptr) return kPD_NULLPTR; @@ -110,9 +106,9 @@ int PDArgsSetSubSequenceStartPos(PD_Arguments args, return kPD_NO_ERROR; } -int PDArgsGetSequenceStartPos(PD_Arguments args, - uint64_t ID, - PD_IVector seqPos) { +PD_Error PDArgsGetSequenceStartPos(PD_Arguments args, + uint64_t ID, + PD_IVector seqPos) { if (args == nullptr || seqPos == nullptr) return kPD_NULLPTR; auto iv = castIVec(seqPos); auto a = castArg(args); @@ -122,9 +118,9 @@ int PDArgsGetSequenceStartPos(PD_Arguments args, return kPD_NO_ERROR; } -int PDArgsGetSubSequenceStartPos(PD_Arguments args, - uint64_t ID, - PD_IVector subSeqPos) { +PD_Error PDArgsGetSubSequenceStartPos(PD_Arguments args, + uint64_t ID, + PD_IVector subSeqPos) { if (args == nullptr || subSeqPos == nullptr) return kPD_NULLPTR; auto iv = castIVec(subSeqPos); auto a = castArg(args); diff --git a/paddle/capi/GradientMachine.cpp b/paddle/capi/GradientMachine.cpp index de3a339fa62..ed0cfd88409 100644 --- a/paddle/capi/GradientMachine.cpp +++ b/paddle/capi/GradientMachine.cpp @@ -38,9 +38,9 @@ NeuralNetwork* newCustomNerualNetwork(const std::string& name, } // namespace paddle extern "C" { -int PDGradientMachineCreateForPredict(PD_GradientMachine* machine, - void* modelConfigProtobuf, - int size) { +PD_Error PDGradientMachineCreateForPredict(PD_GradientMachine* machine, + void* modelConfigProtobuf, + int size) { if (modelConfigProtobuf == nullptr) return kPD_NULLPTR; paddle::ModelConfig config; if (!config.ParseFromArray(modelConfigProtobuf, size) || @@ -55,13 +55,13 @@ int PDGradientMachineCreateForPredict(PD_GradientMachine* machine, return kPD_NO_ERROR; } -int PDGradientMachineDestroy(PD_GradientMachine machine) { +PD_Error PDGradientMachineDestroy(PD_GradientMachine machine) { delete cast(machine); return kPD_NO_ERROR; } -int PDGradientMachineLoadParameterFromDisk(PD_GradientMachine machine, - const char* path) { +PD_Error PDGradientMachineLoadParameterFromDisk(PD_GradientMachine machine, + const char* path) { auto m = cast(machine); if (m == nullptr || path == nullptr || m->machine == nullptr) return kPD_NULLPTR; @@ -69,10 +69,10 @@ int PDGradientMachineLoadParameterFromDisk(PD_GradientMachine machine, return kPD_NO_ERROR; } -int PDGradientMachineForward(PD_GradientMachine machine, - PD_Arguments inArgs, - PD_Arguments outArgs, - bool isTrain) { +PD_Error PDGradientMachineForward(PD_GradientMachine machine, + PD_Arguments inArgs, + PD_Arguments outArgs, + bool isTrain) { auto m = cast(machine); auto in = paddle::capi::cast(inArgs); auto out = paddle::capi::cast(outArgs); @@ -83,10 +83,10 @@ int PDGradientMachineForward(PD_GradientMachine machine, return kPD_NO_ERROR; } -int PDGradientMachineCreateSharedParam(PD_GradientMachine origin, - void* modelConfigProtobuf, - int size, - PD_GradientMachine* slave) { +PD_Error PDGradientMachineCreateSharedParam(PD_GradientMachine origin, + void* modelConfigProtobuf, + int size, + PD_GradientMachine* slave) { auto o = cast(origin); if (origin == nullptr || slave == nullptr || o->machine == nullptr) { return kPD_NULLPTR; diff --git a/paddle/capi/Main.cpp b/paddle/capi/Main.cpp index e310eb54047..9314071b4bc 100644 --- a/paddle/capi/Main.cpp +++ b/paddle/capi/Main.cpp @@ -29,7 +29,7 @@ static void initPaddle(int argc, char** argv) { } extern "C" { -int PDInit(int argc, char** argv) { +PD_Error PDInit(int argc, char** argv) { std::vector realArgv; realArgv.reserve(argc + 1); realArgv.push_back(strdup("")); diff --git a/paddle/capi/Matrix.cpp b/paddle/capi/Matrix.cpp index db32c945412..bc25f84344c 100644 --- a/paddle/capi/Matrix.cpp +++ b/paddle/capi/Matrix.cpp @@ -18,27 +18,22 @@ limitations under the License. */ #define cast(v) paddle::capi::cast(v) extern "C" { -int PDMatCreate(PD_Matrix* mat, uint64_t height, uint64_t width, bool useGpu) { +PD_Matrix PDMatCreate(uint64_t height, uint64_t width, bool useGpu) { auto ptr = new paddle::capi::CMatrix(); ptr->mat = paddle::Matrix::create(height, width, false, useGpu); - *mat = ptr; - return kPD_NO_ERROR; + return ptr; } -int PDMatCreateNone(PD_Matrix* mat) { - auto ptr = new paddle::capi::CMatrix(); - *mat = ptr; - return kPD_NO_ERROR; -} +PD_Matrix PDMatCreateNone() { return new paddle::capi::CMatrix(); } -int PDMatDestroy(PD_Matrix mat) { +PD_Error PDMatDestroy(PD_Matrix mat) { if (mat == nullptr) return kPD_NULLPTR; auto ptr = cast(mat); delete ptr; return kPD_NO_ERROR; } -int PDMatCopyToRow(PD_Matrix mat, uint64_t rowID, pd_real* rowArray) { +PD_Error PDMatCopyToRow(PD_Matrix mat, uint64_t rowID, pd_real* rowArray) { if (mat == nullptr) return kPD_NULLPTR; auto ptr = cast(mat); if (ptr->mat == nullptr) return kPD_NULLPTR; @@ -53,7 +48,7 @@ int PDMatCopyToRow(PD_Matrix mat, uint64_t rowID, pd_real* rowArray) { return kPD_NO_ERROR; } -int PDMatGetRow(PD_Matrix mat, uint64_t rowID, pd_real** rawRowBuffer) { +PD_Error PDMatGetRow(PD_Matrix mat, uint64_t rowID, pd_real** rawRowBuffer) { if (mat == nullptr) return kPD_NULLPTR; auto ptr = cast(mat); if (ptr->mat == nullptr) return kPD_NULLPTR; @@ -62,7 +57,7 @@ int PDMatGetRow(PD_Matrix mat, uint64_t rowID, pd_real** rawRowBuffer) { return kPD_NO_ERROR; } -int PDMatGetShape(PD_Matrix mat, uint64_t* height, uint64_t* width) { +PD_Error PDMatGetShape(PD_Matrix mat, uint64_t* height, uint64_t* width) { if (mat == nullptr) return kPD_NULLPTR; if (height != nullptr) { *height = cast(mat)->mat->getHeight(); diff --git a/paddle/capi/PaddleCAPI.h b/paddle/capi/PaddleCAPI.h index 6eea60ef749..94a9fc497f9 100644 --- a/paddle/capi/PaddleCAPI.h +++ b/paddle/capi/PaddleCAPI.h @@ -49,48 +49,115 @@ typedef enum { */ typedef void* PD_IVector; -PD_API int PDIVecCreateNone(PD_IVector* ivec); +/** + * @brief Create an none int vector. It just a handler and store nothing. Used + * to get output from other api. + * @return None int vector. + */ +PD_API PD_IVector PDIVecCreateNone(); /** * @brief PDIVectorCreate create a paddle int vector - * @param [out] ivec: output int vector. - * @param [in] array: input array. - * @param [in] size: input array size. - * @param [in] copy: memory copy or just use same memory. True if copy. + * @param array: input array. + * @param size: input array size. + * @param copy: memory copy or just use same memory. True if copy. + * @param useGPU: True if use GPU * @return PD_Error */ -PD_API int PDIVectorCreate(PD_IVector* ivec, - int* array, - uint64_t size, - bool copy); +PD_API PD_IVector PDIVectorCreate(int* array, + uint64_t size, + bool copy, + bool useGPU); -PD_API int PDIVecDestroy(PD_IVector ivec); +/** + * @brief PDIVecDestroy destory an int vector. + * @param ivec vector to be destoried. + * @return PD_Error + */ +PD_API PD_Error PDIVecDestroy(PD_IVector ivec); -PD_API int PDIVectorGet(PD_IVector ivec, int** buffer); +/** + * @brief PDIVectorGet get raw buffer stored inside this int vector. It could be + * GPU memory if this int vector is stored in GPU. + * @param [in] ivec int vector + * @param [out] buffer the return buffer pointer. + * @return PD_Error + */ +PD_API PD_Error PDIVectorGet(PD_IVector ivec, int** buffer); -PD_API int PDIVectorResize(PD_IVector ivec, uint64_t size); +/** + * @brief PDIVectorResize resize the int vector. + * @param [in] ivec: int vector + * @param [in] size: size to change + * @return PD_Error + */ +PD_API PD_Error PDIVectorResize(PD_IVector ivec, uint64_t size); -PD_API int PDIVectorGetSize(PD_IVector ivec, uint64_t* size); +/** + * @brief PDIVectorGetSize get the size of int vector. + * @param [in] ivec: int vector + * @param [out] size: return size of this int vector. + * @return PD_Error + */ +PD_API PD_Error PDIVectorGetSize(PD_IVector ivec, uint64_t* size); /** * Matrix functions. Return will be a PD_Error type. */ typedef void* PD_Matrix; -PD_API int PDMatCreate(PD_Matrix* mat, - uint64_t height, - uint64_t width, - bool useGpu); +/** + * @brief PDMatCreate Create a dense matrix + * @param height matrix height. + * @param width matrix width + * @param useGpu use GPU of not + * @return Matrix handler + */ +PD_API PD_Matrix PDMatCreate(uint64_t height, uint64_t width, bool useGpu); -PD_API int PDMatDestroy(PD_Matrix mat); +/** + * @brief PDMatDestroy Destroy a matrix. + * @param mat + * @return PD_Error + */ +PD_API PD_Error PDMatDestroy(PD_Matrix mat); -PD_API int PDMatCopyToRow(PD_Matrix mat, uint64_t rowID, pd_real* rowArray); +/** + * @brief PDMatCopyToRow Copy a row to matrix. + * @param mat Target Matrix + * @param rowID Index of row + * @param rowArray Row data. + * @return PD_Error + */ +PD_API PD_Error PDMatCopyToRow(PD_Matrix mat, + uint64_t rowID, + pd_real* rowArray); -PD_API int PDMatGetRow(PD_Matrix mat, uint64_t rowID, pd_real** rawRowBuffer); +/** + * @brief PDMatGetRow Get raw row buffer from matrix + * @param [in] mat Target matrix + * @param [in] rowID Index of row. + * @param [out] rawRowBuffer Row Buffer + * @return PD_Error + */ +PD_API PD_Error PDMatGetRow(PD_Matrix mat, + uint64_t rowID, + pd_real** rawRowBuffer); -PD_API int PDMatCreateNone(PD_Matrix* mat); +/** + * @brief PDMatCreateNone Create None Matrix + * @return + */ +PD_API PD_Matrix PDMatCreateNone(); -PD_API int PDMatGetShape(PD_Matrix mat, uint64_t* height, uint64_t* width); +/** + * @brief PDMatGetShape get the shape of matrix + * @param mat target matrix + * @param height The height of matrix + * @param width The width of matrix + * @return PD_Error + */ +PD_API PD_Error PDMatGetShape(PD_Matrix mat, uint64_t* height, uint64_t* width); /** * Arguments functions. Each argument means layer output. Arguments means a @@ -98,65 +165,185 @@ PD_API int PDMatGetShape(PD_Matrix mat, uint64_t* height, uint64_t* width); */ typedef void* PD_Arguments; -PD_API int PDArgsCreateNone(PD_Arguments* args); - -PD_API int PDArgsDestroy(PD_Arguments args); +/** + * @brief PDArgsCreateNone Create a array of arguments, which size is zero. + * @return Arguemnts + */ +PD_API PD_Arguments PDArgsCreateNone(); -PD_API int PDArgsGetSize(PD_Arguments args, uint64_t* size); +/** + * @brief PDArgsDestroy Destroy the arguments + * @param args arguments to destroy + * @return PD_Error + */ +PD_API PD_Error PDArgsDestroy(PD_Arguments args); -PD_API int PDArgsResize(PD_Arguments args, uint64_t size); +/** + * @brief PDArgsGetSize Get size of arguments array + * @param [in] args arguments array + * @param [out] size array size + * @return PD_Error + */ +PD_API PD_Error PDArgsGetSize(PD_Arguments args, uint64_t* size); -PD_API int PDArgsSetValue(PD_Arguments args, uint64_t ID, PD_Matrix mat); +/** + * @brief PDArgsResize Resize a arguments array. + * @param args arguments array. + * @param size target size of array + * @return PD_Error + */ +PD_API PD_Error PDArgsResize(PD_Arguments args, uint64_t size); -PD_API int PDArgsGetValue(PD_Arguments args, uint64_t ID, PD_Matrix mat); +/** + * @brief PDArgsSetValue Set value matrix of one argument in array, which index + * is `ID`. + * @param args arguments array + * @param ID array index + * @param mat matrix pointer + * @return PD_Error + */ +PD_API PD_Error PDArgsSetValue(PD_Arguments args, uint64_t ID, PD_Matrix mat); -PD_API int PDArgsGetIds(PD_Arguments args, uint64_t ID, PD_IVector ids); +/** + * @brief PDArgsGetValue Get value matrix of one argument in array, which index + * is `ID`. + * @param [in] args arguments array + * @param [in] ID array index + * @param [out] mat matrix pointer + * @return PD_Error + */ +PD_API PD_Error PDArgsGetValue(PD_Arguments args, uint64_t ID, PD_Matrix mat); -PD_API int PDArgsSetIds(PD_Arguments args, uint64_t ID, PD_IVector ids); +/** + * @brief PDArgsGetIds Get the integer vector of one argument in array, which + * index is `ID`. + * @param args arguments array + * @param ID array index + * @param ids integer vector pointer + * @return PD_Error + */ +PD_API PD_Error PDArgsGetIds(PD_Arguments args, uint64_t ID, PD_IVector ids); -PD_API int PDArgsSetSequenceStartPos(PD_Arguments args, - uint64_t ID, - PD_IVector seqPos); +/** + * @brief PDArgsSetIds Set the integer vector of one argument in array, which + * index is `ID`. + * @param [in] args arguments array + * @param [in] ID array index + * @param [out] ids integer vector pointer + * @return PD_Error + */ +PD_API PD_Error PDArgsSetIds(PD_Arguments args, uint64_t ID, PD_IVector ids); -PD_API int PDArgsGetSequenceStartPos(PD_Arguments args, - uint64_t ID, - PD_IVector seqPos); +/** + * @brief PDArgsSetSequenceStartPos Set sequence start position vector of one + * argument in array, which index is `ID`. + * @param args arguments array + * @param ID array index + * @param seqPos sequence position array. + * @return PD_Error + */ +PD_API PD_Error PDArgsSetSequenceStartPos(PD_Arguments args, + uint64_t ID, + PD_IVector seqPos); +/** + * @brief PDArgsGetSequenceStartPos Get sequence start position vector of one + * argument in array, which index is `ID`. + * @param [in] args arguments array + * @param [in] ID array index + * @param [out] seqPos sequence position array + * @return PD_Error + */ +PD_API PD_Error PDArgsGetSequenceStartPos(PD_Arguments args, + uint64_t ID, + PD_IVector seqPos); -PD_API int PDArgsSetSubSequenceStartPos(PD_Arguments args, - uint64_t ID, - PD_IVector subSeqPos); +/** + * @brief PDArgsSetSubSequenceStartPos Set sub-sequence start position vector of + * one argument in array, which index is `ID`. + * @param args arguments array + * @param ID array index + * @param subSeqPos sub-sequence start position array. + * @return PD_Error + */ +PD_API PD_Error PDArgsSetSubSequenceStartPos(PD_Arguments args, + uint64_t ID, + PD_IVector subSeqPos); -PD_API int PDArgsGetSubSequenceStartPos(PD_Arguments args, - uint64_t ID, - PD_IVector subSeqPos); +/** + * @brief PDArgsGetSubSequenceStartPos Get sub-sequence start position vector of + * one argument in array, which index is `ID`. + * @param args arguments array + * @param ID array index + * @param subSeqPos sub-sequence start position array + * @return PD_Error + */ +PD_API PD_Error PDArgsGetSubSequenceStartPos(PD_Arguments args, + uint64_t ID, + PD_IVector subSeqPos); /** * @brief GradientMachine means a neural network. */ typedef void* PD_GradientMachine; -PD_API int PDGradientMachineCreateForPredict(PD_GradientMachine* machine, - void* modelConfigProtobuf, - int size); +/** + * @brief PDGradientMachineCreateForPredict Create a gradient machine used for + * model inference. + * @param [out] machine that used for model inference. + * @param [in] modelConfigProtobuf + * @param [in] size + * @return PD_Error + */ +PD_API PD_Error PDGradientMachineCreateForPredict(PD_GradientMachine* machine, + void* modelConfigProtobuf, + int size); -PD_API int PDGradientMachineLoadParameterFromDisk(PD_GradientMachine machine, - const char* path); +/** + * @brief PDGradientMachineLoadParameterFromDisk Load parameter from disk. + * @param machine Gradient Machine. + * @param path local directory path. + * @return PD_Error + */ +PD_API PD_Error PDGradientMachineLoadParameterFromDisk( + PD_GradientMachine machine, const char* path); -PD_API int PDGradientMachineForward(PD_GradientMachine machine, - PD_Arguments inArgs, - PD_Arguments outArgs, - bool isTrain); +/** + * @brief PDGradientMachineForward Forward a gradient machine + * @param machine Gradient machine + * @param inArgs input arguments + * @param outArgs output arguments + * @param isTrain is train or not + * @return PD_Error + */ +PD_API PD_Error PDGradientMachineForward(PD_GradientMachine machine, + PD_Arguments inArgs, + PD_Arguments outArgs, + bool isTrain); -PD_API int PDGradientMachineCreateSharedParam(PD_GradientMachine origin, - void* modelConfigProtobuf, - int size, - PD_GradientMachine* slave); +/** + * @brief PDGradientMachineCreateSharedParam Create a gradient machine, which + * parameters are shared from another gradient machine. + * @param [in] origin gradient machine + * @param [in] modelConfigProtobuf model config protobuf + * @param [in] size of model config buffer. + * @param [out] slave gradient machine, the output value. + * @return PD_Error + */ +PD_API PD_Error PDGradientMachineCreateSharedParam(PD_GradientMachine origin, + void* modelConfigProtobuf, + int size, + PD_GradientMachine* slave); -PD_API int PDGradientMachineDestroy(PD_GradientMachine machine); +/** + * @brief PDGradientMachineDestroy Destroy a gradient machine + * @param machine that need to destroy + * @return PD_Error + */ +PD_API PD_Error PDGradientMachineDestroy(PD_GradientMachine machine); /** * Initialize Paddle. */ -PD_API int PDInit(int argc, char** argv); +PD_API PD_Error PDInit(int argc, char** argv); #ifdef __cplusplus } diff --git a/paddle/capi/Vector.cpp b/paddle/capi/Vector.cpp index af219255137..514d65fec8f 100644 --- a/paddle/capi/Vector.cpp +++ b/paddle/capi/Vector.cpp @@ -19,33 +19,26 @@ using paddle::capi::cast; extern "C" { -int PDIVecCreateNone(PD_IVector* ivec) { - if (ivec == nullptr) return kPD_NULLPTR; - auto ptr = new paddle::capi::CIVector(); - *ivec = ptr; - return kPD_NO_ERROR; -} +PD_IVector PDIVecCreateNone() { return new paddle::capi::CIVector(); } -int PDIVectorCreate(PD_IVector* ivec, int* array, uint64_t size, bool copy) { - if (ivec == nullptr) return kPD_NULLPTR; +PD_IVector PDIVectorCreate(int* array, uint64_t size, bool copy, bool useGPU) { auto ptr = new paddle::capi::CIVector(); if (copy) { - ptr->vec = paddle::IVector::create(size, false); + ptr->vec = paddle::IVector::create(size, useGPU); ptr->vec->copyFrom(array, size); } else { - ptr->vec = paddle::IVector::create(array, size, false); + ptr->vec = paddle::IVector::create(array, size, useGPU); } - *ivec = ptr; - return kPD_NO_ERROR; + return ptr; } -int PDIVecDestroy(PD_IVector ivec) { +PD_Error PDIVecDestroy(PD_IVector ivec) { if (ivec == nullptr) return kPD_NULLPTR; delete cast(ivec); return kPD_NO_ERROR; } -int PDIVectorGet(PD_IVector ivec, int** buffer) { +PD_Error PDIVectorGet(PD_IVector ivec, int** buffer) { if (ivec == nullptr || buffer == nullptr) return kPD_NULLPTR; auto v = cast(ivec); if (v->vec == nullptr) return kPD_NULLPTR; @@ -53,7 +46,7 @@ int PDIVectorGet(PD_IVector ivec, int** buffer) { return kPD_NO_ERROR; } -int PDIVectorResize(PD_IVector ivec, uint64_t size) { +PD_Error PDIVectorResize(PD_IVector ivec, uint64_t size) { if (ivec == nullptr) return kPD_NULLPTR; auto v = cast(ivec); if (v->vec == nullptr) return kPD_NULLPTR; @@ -61,7 +54,7 @@ int PDIVectorResize(PD_IVector ivec, uint64_t size) { return kPD_NO_ERROR; } -int PDIVectorGetSize(PD_IVector ivec, uint64_t* size) { +PD_Error PDIVectorGetSize(PD_IVector ivec, uint64_t* size) { if (ivec == nullptr) return kPD_NULLPTR; auto v = cast(ivec); if (v->vec == nullptr) return kPD_NULLPTR; diff --git a/paddle/capi/tests/test_Arguments.cpp b/paddle/capi/tests/test_Arguments.cpp index 9357f3a5846..e015b94e12c 100644 --- a/paddle/capi/tests/test_Arguments.cpp +++ b/paddle/capi/tests/test_Arguments.cpp @@ -28,8 +28,7 @@ static std::vector randomBuffer(size_t bufSize) { } TEST(CAPIArguments, create) { - PD_Arguments args; - ASSERT_EQ(kPD_NO_ERROR, PDArgsCreateNone(&args)); + PD_Arguments args = PDArgsCreateNone(); uint64_t size; ASSERT_EQ(kPD_NO_ERROR, PDArgsGetSize(args, &size)); ASSERT_EQ(0UL, size); @@ -37,20 +36,17 @@ TEST(CAPIArguments, create) { } TEST(CAPIArguments, value) { - PD_Arguments args; - ASSERT_EQ(kPD_NO_ERROR, PDArgsCreateNone(&args)); + PD_Arguments args = PDArgsCreateNone(); ASSERT_EQ(kPD_NO_ERROR, PDArgsResize(args, 1)); - PD_Matrix mat; - ASSERT_EQ(kPD_NO_ERROR, PDMatCreate(&mat, 128, 64, false)); + PD_Matrix mat = PDMatCreate(128, 64, false); for (size_t i = 0; i < 128; ++i) { std::vector sampleBuf = randomBuffer(64); PDMatCopyToRow(mat, i, sampleBuf.data()); } ASSERT_EQ(kPD_NO_ERROR, PDArgsSetValue(args, 0, mat)); - PD_Matrix val; - ASSERT_EQ(kPD_NO_ERROR, PDMatCreateNone(&val)); + PD_Matrix val = PDMatCreateNone(); ASSERT_EQ(kPD_NO_ERROR, PDArgsGetValue(args, 0, val)); @@ -63,8 +59,7 @@ TEST(CAPIArguments, value) { ASSERT_EQ(row1, row2); } - PD_IVector ivec; - ASSERT_EQ(kPD_NO_ERROR, PDIVecCreateNone(&ivec)); + PD_IVector ivec = PDIVecCreateNone(); ASSERT_EQ(kPD_NO_ERROR, PDIVecDestroy(ivec)); ASSERT_EQ(kPD_NO_ERROR, PDMatDestroy(val)); ASSERT_EQ(kPD_NO_ERROR, PDMatDestroy(mat)); @@ -72,17 +67,15 @@ TEST(CAPIArguments, value) { } TEST(CAPIArguments, ids) { - PD_Arguments args; - ASSERT_EQ(kPD_NO_ERROR, PDArgsCreateNone(&args)); + PD_Arguments args = PDArgsCreateNone(); ASSERT_EQ(kPD_NO_ERROR, PDArgsResize(args, 1)); PD_IVector ivec; int array[3] = {1, 2, 3}; - ASSERT_EQ(kPD_NO_ERROR, PDIVectorCreate(&ivec, array, 3, true)); + ivec = PDIVectorCreate(array, 3, true, false); ASSERT_EQ(kPD_NO_ERROR, PDArgsSetIds(args, 0, ivec)); - PD_IVector val; - ASSERT_EQ(kPD_NO_ERROR, PDIVecCreateNone(&val)); + PD_IVector val = PDIVecCreateNone(); ASSERT_EQ(kPD_NO_ERROR, PDArgsGetIds(args, 0, val)); ASSERT_EQ(kPD_NO_ERROR, PDIVecDestroy(ivec)); ASSERT_EQ(kPD_NO_ERROR, PDIVecDestroy(val)); @@ -91,17 +84,15 @@ TEST(CAPIArguments, ids) { template void testSequenceHelper(T1 setter, T2 getter) { - PD_Arguments args; - ASSERT_EQ(kPD_NO_ERROR, PDArgsCreateNone(&args)); + PD_Arguments args = PDArgsCreateNone(); ASSERT_EQ(kPD_NO_ERROR, PDArgsResize(args, 1)); PD_IVector ivec; int array[3] = {1, 2, 3}; - ASSERT_EQ(kPD_NO_ERROR, PDIVectorCreate(&ivec, array, 3, true)); + ivec = PDIVectorCreate(array, 3, true, false); ASSERT_EQ(kPD_NO_ERROR, setter(args, 0, ivec)); - PD_IVector val; - ASSERT_EQ(kPD_NO_ERROR, PDIVecCreateNone(&val)); + PD_IVector val = PDIVecCreateNone(); ASSERT_EQ(kPD_NO_ERROR, getter(args, 0, val)); uint64_t size; ASSERT_EQ(kPD_NO_ERROR, PDIVectorGetSize(val, &size)); diff --git a/paddle/capi/tests/test_GradientMachine.cpp b/paddle/capi/tests/test_GradientMachine.cpp index 427b37b085a..acee99bcc4b 100644 --- a/paddle/capi/tests/test_GradientMachine.cpp +++ b/paddle/capi/tests/test_GradientMachine.cpp @@ -55,14 +55,11 @@ TEST(GradientMachine, testPredict) { PDGradientMachineCreateSharedParam( machine, &buffer[0], (int)buffer.size(), &machineSlave)); std::swap(machineSlave, machine); - PD_Arguments outArgs; - ASSERT_EQ(kPD_NO_ERROR, PDArgsCreateNone(&outArgs)); + PD_Arguments outArgs = PDArgsCreateNone(); - PD_Arguments inArgs; - ASSERT_EQ(kPD_NO_ERROR, PDArgsCreateNone(&inArgs)); + PD_Arguments inArgs = PDArgsCreateNone(); ASSERT_EQ(kPD_NO_ERROR, PDArgsResize(inArgs, 1)); - PD_Matrix mat; - ASSERT_EQ(kPD_NO_ERROR, PDMatCreate(&mat, 1, 100, false)); + PD_Matrix mat = PDMatCreate(1, 100, false); static_assert(std::is_same::value, ""); auto data = randomBuffer(100); diff --git a/paddle/capi/tests/test_Matrix.cpp b/paddle/capi/tests/test_Matrix.cpp index 4192dd6bfb5..1d38162add2 100644 --- a/paddle/capi/tests/test_Matrix.cpp +++ b/paddle/capi/tests/test_Matrix.cpp @@ -16,8 +16,7 @@ limitations under the License. */ #include "gtest/gtest.h" TEST(CAPIMatrix, create) { - PD_Matrix mat; - ASSERT_EQ(kPD_NO_ERROR, PDMatCreate(&mat, 128, 32, false)); + PD_Matrix mat = PDMatCreate(128, 32, false); std::vector sampleRow; sampleRow.resize(32); for (size_t i = 0; i < sampleRow.size(); ++i) { @@ -41,7 +40,6 @@ TEST(CAPIMatrix, create) { } TEST(CAPIMatrix, createNone) { - PD_Matrix mat; - ASSERT_EQ(kPD_NO_ERROR, PDMatCreateNone(&mat)); + PD_Matrix mat = PDMatCreateNone(); ASSERT_EQ(kPD_NO_ERROR, PDMatDestroy(mat)); } diff --git a/paddle/capi/tests/test_Vector.cpp b/paddle/capi/tests/test_Vector.cpp index 547d0ef20d1..b3558fe0fdd 100644 --- a/paddle/capi/tests/test_Vector.cpp +++ b/paddle/capi/tests/test_Vector.cpp @@ -18,8 +18,7 @@ limitations under the License. */ TEST(CAPIVector, create) { PD_IVector vec; int array[3] = {1, 2, 3}; - ASSERT_EQ(kPD_NO_ERROR, PDIVectorCreate(&vec, array, 3, true)); - ASSERT_EQ(kPD_NO_ERROR, PDIVectorCreate(&vec, array, 3, false)); + vec = PDIVectorCreate(array, 3, true, false); ASSERT_EQ(kPD_NO_ERROR, PDIVectorResize(vec, 1000)); uint64_t size; ASSERT_EQ(kPD_NO_ERROR, PDIVectorGetSize(vec, &size)); @@ -27,7 +26,6 @@ TEST(CAPIVector, create) { } TEST(CAPIVector, createNone) { - PD_IVector vec; - ASSERT_EQ(kPD_NO_ERROR, PDIVecCreateNone(&vec)); + PD_IVector vec = PDIVecCreateNone(); ASSERT_EQ(kPD_NO_ERROR, PDIVecDestroy(vec)); } -- GitLab From 5ac9c22633d3dacf3b37e56e8680a8255ebe252f Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Fri, 10 Mar 2017 17:10:14 +0800 Subject: [PATCH 0027/3256] Install shared lib --- paddle/capi/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paddle/capi/CMakeLists.txt b/paddle/capi/CMakeLists.txt index f5827317b0d..7fd15e10377 100644 --- a/paddle/capi/CMakeLists.txt +++ b/paddle/capi/CMakeLists.txt @@ -61,7 +61,7 @@ link_paddle_exe(paddle_capi_shared) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${capi_whole_library} DESTINATION lib) install(FILES ${CAPI_HEADER} DESTINATION include/paddle) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/config.h DESTINATION include/paddle) - +install(TARGETS paddle_capi_shared DESTINATION lib) # this variable used for unittest set(PADDLE_CAPI_INC_PATH -- GitLab From fd5c77002bf8c7ad3011271a65c2f9609454e549 Mon Sep 17 00:00:00 2001 From: zhuoyuan Date: Tue, 7 Feb 2017 15:55:46 -0800 Subject: [PATCH 0028/3256] light version of cnn for MNIST --- demo/mnist/light_mnist.py | 71 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 demo/mnist/light_mnist.py diff --git a/demo/mnist/light_mnist.py b/demo/mnist/light_mnist.py new file mode 100644 index 00000000000..271f73cd4d1 --- /dev/null +++ b/demo/mnist/light_mnist.py @@ -0,0 +1,71 @@ +from paddle.trainer_config_helpers import * + +is_predict = get_config_arg("is_predict", bool, False) + +####################Data Configuration ################## + +if not is_predict: + data_dir = './data/' + define_py_data_sources2( + train_list=data_dir + 'train.list', + test_list=data_dir + 'test.list', + module='mnist_provider', + obj='process') + +######################Algorithm Configuration ############# +# settings( +# batch_size=128, +# learning_rate=0.1 / 128.0, +# learning_method=MomentumOptimizer(0.9), +# regularization=L2Regularization(0.0005 * 128)) +settings( + batch_size=50, + learning_rate=0.001, + learning_method=AdamOptimizer()) + +#######################Network Configuration ############# + +data_size = 1 * 28 * 28 +label_size = 10 +img = data_layer(name='pixel', size=data_size) + +# small_vgg is predined in trainer_config_helpers.network +# predict = small_vgg(input_image=img, num_channels=1, num_classes=label_size) + +# light cnn +def light_cnn(input_image, num_channels, num_classes): + def __light__(ipt, num_filter=128, times=1, conv_filter_size=3, dropouts=0, num_channels_=None): + return img_conv_group( + input=ipt, + num_channels=num_channels_, + pool_size=2, + pool_stride=2, + conv_padding=0, + conv_num_filter=[num_filter] * times, + conv_filter_size=conv_filter_size, + conv_act=ReluActivation(), + conv_with_batchnorm=True, + conv_batchnorm_drop_rate=dropouts, + pool_type=MaxPooling()) + + tmp = __light__(input_image, num_filter=128, num_channels_=num_channels) + tmp = __light__(tmp, num_filter=128) + tmp = __light__(tmp, num_filter=128) + tmp = __light__(tmp, num_filter=128, conv_filter_size=1) + + #tmp = img_pool_layer(input=tmp, stride=2, pool_size=2, pool_type=MaxPooling()) + #tmp = dropout_layer(input=tmp, dropout_rate=0.5) + tmp = fc_layer(input=tmp, size = num_classes, act=SoftmaxActivation()) + # tmp = fc_layer(input=tmp, size=512, layer_attr=ExtraAttr(drop_rate=0.5), act=LinearActivation()) + # tmp = batch_norm_layer(input=tmp, act=ReluActivation()) + # return fc_layer(input=tmp, size=num_classes, act=SoftmaxActivation()) + return tmp + +predict = light_cnn(input_image=img, num_channels=1, num_classes=label_size) + +if not is_predict: + lbl = data_layer(name="label", size=label_size) + inputs(img, lbl) + outputs(classification_cost(input=predict, label=lbl)) +else: + outputs(predict) -- GitLab From bc8ba1de750637773052892c5c008f30cd998844 Mon Sep 17 00:00:00 2001 From: zhuoyuan Date: Tue, 7 Feb 2017 16:02:35 -0800 Subject: [PATCH 0029/3256] modify light mnist file --- demo/mnist/light_mnist.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/demo/mnist/light_mnist.py b/demo/mnist/light_mnist.py index 271f73cd4d1..466cc2e2f54 100644 --- a/demo/mnist/light_mnist.py +++ b/demo/mnist/light_mnist.py @@ -53,12 +53,7 @@ def light_cnn(input_image, num_channels, num_classes): tmp = __light__(tmp, num_filter=128) tmp = __light__(tmp, num_filter=128, conv_filter_size=1) - #tmp = img_pool_layer(input=tmp, stride=2, pool_size=2, pool_type=MaxPooling()) - #tmp = dropout_layer(input=tmp, dropout_rate=0.5) tmp = fc_layer(input=tmp, size = num_classes, act=SoftmaxActivation()) - # tmp = fc_layer(input=tmp, size=512, layer_attr=ExtraAttr(drop_rate=0.5), act=LinearActivation()) - # tmp = batch_norm_layer(input=tmp, act=ReluActivation()) - # return fc_layer(input=tmp, size=num_classes, act=SoftmaxActivation()) return tmp predict = light_cnn(input_image=img, num_channels=1, num_classes=label_size) -- GitLab From cf437a89920e66d9193b5386c8223d8089d4f6c2 Mon Sep 17 00:00:00 2001 From: zhuoyuan Date: Tue, 7 Feb 2017 16:11:05 -0800 Subject: [PATCH 0030/3256] modified cnn of mnist light --- demo/mnist/light_mnist.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/demo/mnist/light_mnist.py b/demo/mnist/light_mnist.py index 466cc2e2f54..4e701599817 100644 --- a/demo/mnist/light_mnist.py +++ b/demo/mnist/light_mnist.py @@ -18,10 +18,7 @@ if not is_predict: # learning_rate=0.1 / 128.0, # learning_method=MomentumOptimizer(0.9), # regularization=L2Regularization(0.0005 * 128)) -settings( - batch_size=50, - learning_rate=0.001, - learning_method=AdamOptimizer()) +settings(batch_size=50, learning_rate=0.001, learning_method=AdamOptimizer()) #######################Network Configuration ############# @@ -32,9 +29,15 @@ img = data_layer(name='pixel', size=data_size) # small_vgg is predined in trainer_config_helpers.network # predict = small_vgg(input_image=img, num_channels=1, num_classes=label_size) + # light cnn def light_cnn(input_image, num_channels, num_classes): - def __light__(ipt, num_filter=128, times=1, conv_filter_size=3, dropouts=0, num_channels_=None): + def __light__(ipt, + num_filter=128, + times=1, + conv_filter_size=3, + dropouts=0, + num_channels_=None): return img_conv_group( input=ipt, num_channels=num_channels_, @@ -53,9 +56,10 @@ def light_cnn(input_image, num_channels, num_classes): tmp = __light__(tmp, num_filter=128) tmp = __light__(tmp, num_filter=128, conv_filter_size=1) - tmp = fc_layer(input=tmp, size = num_classes, act=SoftmaxActivation()) + tmp = fc_layer(input=tmp, size=num_classes, act=SoftmaxActivation()) return tmp + predict = light_cnn(input_image=img, num_channels=1, num_classes=label_size) if not is_predict: -- GitLab From e42c3854fa6c6683d52c19d65ca2f5e3833996c4 Mon Sep 17 00:00:00 2001 From: zhuoyuan Date: Wed, 8 Feb 2017 15:49:19 -0800 Subject: [PATCH 0031/3256] follow helin's comments --- demo/mnist/light_mnist.py | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/demo/mnist/light_mnist.py b/demo/mnist/light_mnist.py index 4e701599817..d796a3cc067 100644 --- a/demo/mnist/light_mnist.py +++ b/demo/mnist/light_mnist.py @@ -1,3 +1,17 @@ +# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + from paddle.trainer_config_helpers import * is_predict = get_config_arg("is_predict", bool, False) @@ -13,11 +27,6 @@ if not is_predict: obj='process') ######################Algorithm Configuration ############# -# settings( -# batch_size=128, -# learning_rate=0.1 / 128.0, -# learning_method=MomentumOptimizer(0.9), -# regularization=L2Regularization(0.0005 * 128)) settings(batch_size=50, learning_rate=0.001, learning_method=AdamOptimizer()) #######################Network Configuration ############# @@ -26,11 +35,10 @@ data_size = 1 * 28 * 28 label_size = 10 img = data_layer(name='pixel', size=data_size) -# small_vgg is predined in trainer_config_helpers.network -# predict = small_vgg(input_image=img, num_channels=1, num_classes=label_size) - - # light cnn +# A shallower cnn model: [CNN, BN, ReLU, Max-Pooling] x4 + FC x1 +# Easier to train for mnist dataset and quite efficient +# Final performance is close to deeper ones on tasks such as digital and character classification def light_cnn(input_image, num_channels, num_classes): def __light__(ipt, num_filter=128, -- GitLab From e43f66cd1c2187ca397a903b0dbfe06729f920c7 Mon Sep 17 00:00:00 2001 From: zhuoyuan Date: Wed, 8 Feb 2017 15:54:39 -0800 Subject: [PATCH 0032/3256] after pre-commit --- demo/mnist/light_mnist.py | 1 + 1 file changed, 1 insertion(+) diff --git a/demo/mnist/light_mnist.py b/demo/mnist/light_mnist.py index d796a3cc067..33409054357 100644 --- a/demo/mnist/light_mnist.py +++ b/demo/mnist/light_mnist.py @@ -35,6 +35,7 @@ data_size = 1 * 28 * 28 label_size = 10 img = data_layer(name='pixel', size=data_size) + # light cnn # A shallower cnn model: [CNN, BN, ReLU, Max-Pooling] x4 + FC x1 # Easier to train for mnist dataset and quite efficient -- GitLab From b5288289e1b0029aae88bb4534ca1d1e6a22dac8 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Tue, 21 Mar 2017 11:15:17 +0800 Subject: [PATCH 0033/3256] Rename some API to C-Style --- paddle/capi/Arguments.cpp | 38 ++-- paddle/capi/CMakeLists.txt | 8 +- paddle/capi/GradientMachine.cpp | 28 +-- paddle/capi/Main.cpp | 2 +- paddle/capi/Matrix.cpp | 22 +- paddle/capi/PaddleCAPI.h | 239 ++++++--------------- paddle/capi/Vector.cpp | 17 +- paddle/capi/config.h.in | 4 + paddle/capi/error.h | 15 ++ paddle/capi/matrix.h | 91 ++++++++ paddle/capi/tests/test_Arguments.cpp | 42 ++-- paddle/capi/tests/test_GradientMachine.cpp | 10 +- paddle/capi/tests/test_Matrix.cpp | 17 +- paddle/capi/tests/test_Vector.cpp | 14 +- paddle/capi/vector.h | 89 ++++++++ 15 files changed, 366 insertions(+), 270 deletions(-) create mode 100644 paddle/capi/error.h create mode 100644 paddle/capi/matrix.h create mode 100644 paddle/capi/vector.h diff --git a/paddle/capi/Arguments.cpp b/paddle/capi/Arguments.cpp index 3d60165962d..29aa0858dd8 100644 --- a/paddle/capi/Arguments.cpp +++ b/paddle/capi/Arguments.cpp @@ -23,25 +23,25 @@ using paddle::capi::cast; extern "C" { PD_Arguments PDArgsCreateNone() { return new paddle::capi::CArguments(); } -PD_Error PDArgsDestroy(PD_Arguments args) { +paddle_error PDArgsDestroy(PD_Arguments args) { if (args == nullptr) return kPD_NULLPTR; delete castArg(args); return kPD_NO_ERROR; } -PD_Error PDArgsGetSize(PD_Arguments args, uint64_t* size) { +paddle_error PDArgsGetSize(PD_Arguments args, uint64_t* size) { if (args == nullptr || size == nullptr) return kPD_NULLPTR; *size = castArg(args)->args.size(); return kPD_NO_ERROR; } -PD_Error PDArgsResize(PD_Arguments args, uint64_t size) { +paddle_error PDArgsResize(PD_Arguments args, uint64_t size) { if (args == nullptr) return kPD_NULLPTR; castArg(args)->args.resize(size); return kPD_NO_ERROR; } -PD_Error PDArgsSetValue(PD_Arguments args, uint64_t ID, PD_Matrix mat) { +paddle_error PDArgsSetValue(PD_Arguments args, uint64_t ID, paddle_matrix mat) { if (args == nullptr || mat == nullptr) return kPD_NULLPTR; auto m = paddle::capi::cast(mat); if (m->mat == nullptr) return kPD_NULLPTR; @@ -51,7 +51,7 @@ PD_Error PDArgsSetValue(PD_Arguments args, uint64_t ID, PD_Matrix mat) { return kPD_NO_ERROR; } -PD_Error PDArgsGetValue(PD_Arguments args, uint64_t ID, PD_Matrix mat) { +paddle_error PDArgsGetValue(PD_Arguments args, uint64_t ID, paddle_matrix mat) { if (args == nullptr || mat == nullptr) return kPD_NULLPTR; auto m = paddle::capi::cast(mat); auto a = castArg(args); @@ -60,7 +60,7 @@ PD_Error PDArgsGetValue(PD_Arguments args, uint64_t ID, PD_Matrix mat) { return kPD_NO_ERROR; } -PD_Error PDArgsGetIds(PD_Arguments args, uint64_t ID, PD_IVector ids) { +paddle_error PDArgsGetIds(PD_Arguments args, uint64_t ID, paddle_ivector ids) { if (args == nullptr || ids == nullptr) return kPD_NULLPTR; auto iv = castIVec(ids); auto a = castArg(args); @@ -69,7 +69,7 @@ PD_Error PDArgsGetIds(PD_Arguments args, uint64_t ID, PD_IVector ids) { return kPD_NO_ERROR; } -PD_Error PDArgsSetIds(PD_Arguments args, uint64_t ID, PD_IVector ids) { +paddle_error PDArgsSetIds(PD_Arguments args, uint64_t ID, paddle_ivector ids) { //! TODO(lizhao): Complete this method. if (args == nullptr || ids == nullptr) return kPD_NULLPTR; auto iv = paddle::capi::cast(ids); @@ -80,9 +80,9 @@ PD_Error PDArgsSetIds(PD_Arguments args, uint64_t ID, PD_IVector ids) { return kPD_NO_ERROR; } -PD_Error PDArgsSetSequenceStartPos(PD_Arguments args, - uint64_t ID, - PD_IVector seqPos) { +paddle_error PDArgsSetSequenceStartPos(PD_Arguments args, + uint64_t ID, + paddle_ivector seqPos) { if (args == nullptr || seqPos == nullptr) return kPD_NULLPTR; auto iv = paddle::capi::cast(seqPos); if (iv->vec == nullptr) return kPD_NULLPTR; @@ -93,9 +93,9 @@ PD_Error PDArgsSetSequenceStartPos(PD_Arguments args, return kPD_NO_ERROR; } -PD_Error PDArgsSetSubSequenceStartPos(PD_Arguments args, - uint64_t ID, - PD_IVector subSeqPos) { +paddle_error PDArgsSetSubSequenceStartPos(PD_Arguments args, + uint64_t ID, + paddle_ivector subSeqPos) { if (args == nullptr || subSeqPos == nullptr) return kPD_NULLPTR; auto iv = paddle::capi::cast(subSeqPos); if (iv->vec == nullptr) return kPD_NULLPTR; @@ -106,9 +106,9 @@ PD_Error PDArgsSetSubSequenceStartPos(PD_Arguments args, return kPD_NO_ERROR; } -PD_Error PDArgsGetSequenceStartPos(PD_Arguments args, - uint64_t ID, - PD_IVector seqPos) { +paddle_error PDArgsGetSequenceStartPos(PD_Arguments args, + uint64_t ID, + paddle_ivector seqPos) { if (args == nullptr || seqPos == nullptr) return kPD_NULLPTR; auto iv = castIVec(seqPos); auto a = castArg(args); @@ -118,9 +118,9 @@ PD_Error PDArgsGetSequenceStartPos(PD_Arguments args, return kPD_NO_ERROR; } -PD_Error PDArgsGetSubSequenceStartPos(PD_Arguments args, - uint64_t ID, - PD_IVector subSeqPos) { +paddle_error PDArgsGetSubSequenceStartPos(PD_Arguments args, + uint64_t ID, + paddle_ivector subSeqPos) { if (args == nullptr || subSeqPos == nullptr) return kPD_NULLPTR; auto iv = castIVec(subSeqPos); auto a = castArg(args); diff --git a/paddle/capi/CMakeLists.txt b/paddle/capi/CMakeLists.txt index 7fd15e10377..a2b1929e4b7 100644 --- a/paddle/capi/CMakeLists.txt +++ b/paddle/capi/CMakeLists.txt @@ -12,14 +12,14 @@ configure_file(config.h.in config.h @ONLY) # PaddleCAPI.h is the only header we exposed. It currently only used for model # inference. -set(CAPI_HEADER PaddleCAPI.h) - - +file(GLOB CAPI_HEADERS *.h) set(CAPI_PRIVATE_HEADER PaddleCAPIPrivate.h) +list(REMOVE_ITEM CAPI_HEADERS ${CAPI_PRIVATE_HEADER}) file(GLOB CAPI_SOURCES *.cpp) # building paddle_capi -add_library(paddle_capi STATIC ${CAPI_SOURCES}) +add_library(paddle_capi STATIC ${CAPI_HEADERS} ${CAPI_PRIVATE_HEADER} + ${CAPI_SOURCES}) target_include_directories(paddle_capi PUBLIC ${CMAKE_CURRENT_BINARY_DIR}) diff --git a/paddle/capi/GradientMachine.cpp b/paddle/capi/GradientMachine.cpp index ed0cfd88409..6ca3cf7e4d8 100644 --- a/paddle/capi/GradientMachine.cpp +++ b/paddle/capi/GradientMachine.cpp @@ -38,9 +38,9 @@ NeuralNetwork* newCustomNerualNetwork(const std::string& name, } // namespace paddle extern "C" { -PD_Error PDGradientMachineCreateForPredict(PD_GradientMachine* machine, - void* modelConfigProtobuf, - int size) { +paddle_error PDGradientMachineCreateForPredict(PD_GradientMachine* machine, + void* modelConfigProtobuf, + int size) { if (modelConfigProtobuf == nullptr) return kPD_NULLPTR; paddle::ModelConfig config; if (!config.ParseFromArray(modelConfigProtobuf, size) || @@ -55,13 +55,13 @@ PD_Error PDGradientMachineCreateForPredict(PD_GradientMachine* machine, return kPD_NO_ERROR; } -PD_Error PDGradientMachineDestroy(PD_GradientMachine machine) { +paddle_error PDGradientMachineDestroy(PD_GradientMachine machine) { delete cast(machine); return kPD_NO_ERROR; } -PD_Error PDGradientMachineLoadParameterFromDisk(PD_GradientMachine machine, - const char* path) { +paddle_error PDGradientMachineLoadParameterFromDisk(PD_GradientMachine machine, + const char* path) { auto m = cast(machine); if (m == nullptr || path == nullptr || m->machine == nullptr) return kPD_NULLPTR; @@ -69,10 +69,10 @@ PD_Error PDGradientMachineLoadParameterFromDisk(PD_GradientMachine machine, return kPD_NO_ERROR; } -PD_Error PDGradientMachineForward(PD_GradientMachine machine, - PD_Arguments inArgs, - PD_Arguments outArgs, - bool isTrain) { +paddle_error PDGradientMachineForward(PD_GradientMachine machine, + PD_Arguments inArgs, + PD_Arguments outArgs, + bool isTrain) { auto m = cast(machine); auto in = paddle::capi::cast(inArgs); auto out = paddle::capi::cast(outArgs); @@ -83,10 +83,10 @@ PD_Error PDGradientMachineForward(PD_GradientMachine machine, return kPD_NO_ERROR; } -PD_Error PDGradientMachineCreateSharedParam(PD_GradientMachine origin, - void* modelConfigProtobuf, - int size, - PD_GradientMachine* slave) { +paddle_error PDGradientMachineCreateSharedParam(PD_GradientMachine origin, + void* modelConfigProtobuf, + int size, + PD_GradientMachine* slave) { auto o = cast(origin); if (origin == nullptr || slave == nullptr || o->machine == nullptr) { return kPD_NULLPTR; diff --git a/paddle/capi/Main.cpp b/paddle/capi/Main.cpp index 9314071b4bc..5051dff845a 100644 --- a/paddle/capi/Main.cpp +++ b/paddle/capi/Main.cpp @@ -29,7 +29,7 @@ static void initPaddle(int argc, char** argv) { } extern "C" { -PD_Error PDInit(int argc, char** argv) { +paddle_error PDInit(int argc, char** argv) { std::vector realArgv; realArgv.reserve(argc + 1); realArgv.push_back(strdup("")); diff --git a/paddle/capi/Matrix.cpp b/paddle/capi/Matrix.cpp index bc25f84344c..fe60832d70a 100644 --- a/paddle/capi/Matrix.cpp +++ b/paddle/capi/Matrix.cpp @@ -18,22 +18,28 @@ limitations under the License. */ #define cast(v) paddle::capi::cast(v) extern "C" { -PD_Matrix PDMatCreate(uint64_t height, uint64_t width, bool useGpu) { +paddle_matrix paddle_matrix_create(uint64_t height, + uint64_t width, + bool useGpu) { auto ptr = new paddle::capi::CMatrix(); ptr->mat = paddle::Matrix::create(height, width, false, useGpu); return ptr; } -PD_Matrix PDMatCreateNone() { return new paddle::capi::CMatrix(); } +paddle_matrix paddle_matrix_create_none() { + return new paddle::capi::CMatrix(); +} -PD_Error PDMatDestroy(PD_Matrix mat) { +paddle_error paddle_matrix_destroy(paddle_matrix mat) { if (mat == nullptr) return kPD_NULLPTR; auto ptr = cast(mat); delete ptr; return kPD_NO_ERROR; } -PD_Error PDMatCopyToRow(PD_Matrix mat, uint64_t rowID, pd_real* rowArray) { +paddle_error paddle_matrix_set_row(paddle_matrix mat, + uint64_t rowID, + pd_real* rowArray) { if (mat == nullptr) return kPD_NULLPTR; auto ptr = cast(mat); if (ptr->mat == nullptr) return kPD_NULLPTR; @@ -48,7 +54,9 @@ PD_Error PDMatCopyToRow(PD_Matrix mat, uint64_t rowID, pd_real* rowArray) { return kPD_NO_ERROR; } -PD_Error PDMatGetRow(PD_Matrix mat, uint64_t rowID, pd_real** rawRowBuffer) { +paddle_error paddle_matrix_get_row(paddle_matrix mat, + uint64_t rowID, + pd_real** rawRowBuffer) { if (mat == nullptr) return kPD_NULLPTR; auto ptr = cast(mat); if (ptr->mat == nullptr) return kPD_NULLPTR; @@ -57,7 +65,9 @@ PD_Error PDMatGetRow(PD_Matrix mat, uint64_t rowID, pd_real** rawRowBuffer) { return kPD_NO_ERROR; } -PD_Error PDMatGetShape(PD_Matrix mat, uint64_t* height, uint64_t* width) { +paddle_error paddle_matrix_get_shape(paddle_matrix mat, + uint64_t* height, + uint64_t* width) { if (mat == nullptr) return kPD_NULLPTR; if (height != nullptr) { *height = cast(mat)->mat->getHeight(); diff --git a/paddle/capi/PaddleCAPI.h b/paddle/capi/PaddleCAPI.h index 94a9fc497f9..37dfb13814b 100644 --- a/paddle/capi/PaddleCAPI.h +++ b/paddle/capi/PaddleCAPI.h @@ -17,10 +17,9 @@ limitations under the License. */ #include #include #include "config.h" - -// Since we only support linux and macos in compile, always use clang or -// gcc 4.8+. DLL_IMPORT/DLL_EXPORT is as simple as below. -#define PD_API __attribute__((visibility("default"))) +#include "error.h" +#include "matrix.h" +#include "vector.h" #ifdef __cplusplus extern "C" { @@ -33,132 +32,6 @@ extern "C" { * NOTE: This is an experimental API, it could be changed. */ -/** - * Error Type for Paddle API. - */ -typedef enum { - kPD_NO_ERROR = 0, - kPD_NULLPTR = 1, - kPD_OUT_OF_RANGE = 2, - kPD_PROTOBUF_ERROR = 3, - kPD_UNDEFINED_ERROR = -1, -} PD_Error; - -/** - * Int Vector Functions. Return will be a PD_Error type. - */ -typedef void* PD_IVector; - -/** - * @brief Create an none int vector. It just a handler and store nothing. Used - * to get output from other api. - * @return None int vector. - */ -PD_API PD_IVector PDIVecCreateNone(); - -/** - * @brief PDIVectorCreate create a paddle int vector - * @param array: input array. - * @param size: input array size. - * @param copy: memory copy or just use same memory. True if copy. - * @param useGPU: True if use GPU - * @return PD_Error - */ -PD_API PD_IVector PDIVectorCreate(int* array, - uint64_t size, - bool copy, - bool useGPU); - -/** - * @brief PDIVecDestroy destory an int vector. - * @param ivec vector to be destoried. - * @return PD_Error - */ -PD_API PD_Error PDIVecDestroy(PD_IVector ivec); - -/** - * @brief PDIVectorGet get raw buffer stored inside this int vector. It could be - * GPU memory if this int vector is stored in GPU. - * @param [in] ivec int vector - * @param [out] buffer the return buffer pointer. - * @return PD_Error - */ -PD_API PD_Error PDIVectorGet(PD_IVector ivec, int** buffer); - -/** - * @brief PDIVectorResize resize the int vector. - * @param [in] ivec: int vector - * @param [in] size: size to change - * @return PD_Error - */ -PD_API PD_Error PDIVectorResize(PD_IVector ivec, uint64_t size); - -/** - * @brief PDIVectorGetSize get the size of int vector. - * @param [in] ivec: int vector - * @param [out] size: return size of this int vector. - * @return PD_Error - */ -PD_API PD_Error PDIVectorGetSize(PD_IVector ivec, uint64_t* size); - -/** - * Matrix functions. Return will be a PD_Error type. - */ -typedef void* PD_Matrix; - -/** - * @brief PDMatCreate Create a dense matrix - * @param height matrix height. - * @param width matrix width - * @param useGpu use GPU of not - * @return Matrix handler - */ -PD_API PD_Matrix PDMatCreate(uint64_t height, uint64_t width, bool useGpu); - -/** - * @brief PDMatDestroy Destroy a matrix. - * @param mat - * @return PD_Error - */ -PD_API PD_Error PDMatDestroy(PD_Matrix mat); - -/** - * @brief PDMatCopyToRow Copy a row to matrix. - * @param mat Target Matrix - * @param rowID Index of row - * @param rowArray Row data. - * @return PD_Error - */ -PD_API PD_Error PDMatCopyToRow(PD_Matrix mat, - uint64_t rowID, - pd_real* rowArray); - -/** - * @brief PDMatGetRow Get raw row buffer from matrix - * @param [in] mat Target matrix - * @param [in] rowID Index of row. - * @param [out] rawRowBuffer Row Buffer - * @return PD_Error - */ -PD_API PD_Error PDMatGetRow(PD_Matrix mat, - uint64_t rowID, - pd_real** rawRowBuffer); - -/** - * @brief PDMatCreateNone Create None Matrix - * @return - */ -PD_API PD_Matrix PDMatCreateNone(); - -/** - * @brief PDMatGetShape get the shape of matrix - * @param mat target matrix - * @param height The height of matrix - * @param width The width of matrix - * @return PD_Error - */ -PD_API PD_Error PDMatGetShape(PD_Matrix mat, uint64_t* height, uint64_t* width); - /** * Arguments functions. Each argument means layer output. Arguments means a * array of arguemnt. @@ -174,25 +47,25 @@ PD_API PD_Arguments PDArgsCreateNone(); /** * @brief PDArgsDestroy Destroy the arguments * @param args arguments to destroy - * @return PD_Error + * @return paddle_error */ -PD_API PD_Error PDArgsDestroy(PD_Arguments args); +PD_API paddle_error PDArgsDestroy(PD_Arguments args); /** * @brief PDArgsGetSize Get size of arguments array * @param [in] args arguments array * @param [out] size array size - * @return PD_Error + * @return paddle_error */ -PD_API PD_Error PDArgsGetSize(PD_Arguments args, uint64_t* size); +PD_API paddle_error PDArgsGetSize(PD_Arguments args, uint64_t* size); /** * @brief PDArgsResize Resize a arguments array. * @param args arguments array. * @param size target size of array - * @return PD_Error + * @return paddle_error */ -PD_API PD_Error PDArgsResize(PD_Arguments args, uint64_t size); +PD_API paddle_error PDArgsResize(PD_Arguments args, uint64_t size); /** * @brief PDArgsSetValue Set value matrix of one argument in array, which index @@ -200,9 +73,11 @@ PD_API PD_Error PDArgsResize(PD_Arguments args, uint64_t size); * @param args arguments array * @param ID array index * @param mat matrix pointer - * @return PD_Error + * @return paddle_error */ -PD_API PD_Error PDArgsSetValue(PD_Arguments args, uint64_t ID, PD_Matrix mat); +PD_API paddle_error PDArgsSetValue(PD_Arguments args, + uint64_t ID, + paddle_matrix mat); /** * @brief PDArgsGetValue Get value matrix of one argument in array, which index @@ -210,9 +85,11 @@ PD_API PD_Error PDArgsSetValue(PD_Arguments args, uint64_t ID, PD_Matrix mat); * @param [in] args arguments array * @param [in] ID array index * @param [out] mat matrix pointer - * @return PD_Error + * @return paddle_error */ -PD_API PD_Error PDArgsGetValue(PD_Arguments args, uint64_t ID, PD_Matrix mat); +PD_API paddle_error PDArgsGetValue(PD_Arguments args, + uint64_t ID, + paddle_matrix mat); /** * @brief PDArgsGetIds Get the integer vector of one argument in array, which @@ -220,9 +97,11 @@ PD_API PD_Error PDArgsGetValue(PD_Arguments args, uint64_t ID, PD_Matrix mat); * @param args arguments array * @param ID array index * @param ids integer vector pointer - * @return PD_Error + * @return paddle_error */ -PD_API PD_Error PDArgsGetIds(PD_Arguments args, uint64_t ID, PD_IVector ids); +PD_API paddle_error PDArgsGetIds(PD_Arguments args, + uint64_t ID, + paddle_ivector ids); /** * @brief PDArgsSetIds Set the integer vector of one argument in array, which @@ -230,9 +109,11 @@ PD_API PD_Error PDArgsGetIds(PD_Arguments args, uint64_t ID, PD_IVector ids); * @param [in] args arguments array * @param [in] ID array index * @param [out] ids integer vector pointer - * @return PD_Error + * @return paddle_error */ -PD_API PD_Error PDArgsSetIds(PD_Arguments args, uint64_t ID, PD_IVector ids); +PD_API paddle_error PDArgsSetIds(PD_Arguments args, + uint64_t ID, + paddle_ivector ids); /** * @brief PDArgsSetSequenceStartPos Set sequence start position vector of one @@ -240,22 +121,22 @@ PD_API PD_Error PDArgsSetIds(PD_Arguments args, uint64_t ID, PD_IVector ids); * @param args arguments array * @param ID array index * @param seqPos sequence position array. - * @return PD_Error + * @return paddle_error */ -PD_API PD_Error PDArgsSetSequenceStartPos(PD_Arguments args, - uint64_t ID, - PD_IVector seqPos); +PD_API paddle_error PDArgsSetSequenceStartPos(PD_Arguments args, + uint64_t ID, + paddle_ivector seqPos); /** * @brief PDArgsGetSequenceStartPos Get sequence start position vector of one * argument in array, which index is `ID`. * @param [in] args arguments array * @param [in] ID array index * @param [out] seqPos sequence position array - * @return PD_Error + * @return paddle_error */ -PD_API PD_Error PDArgsGetSequenceStartPos(PD_Arguments args, - uint64_t ID, - PD_IVector seqPos); +PD_API paddle_error PDArgsGetSequenceStartPos(PD_Arguments args, + uint64_t ID, + paddle_ivector seqPos); /** * @brief PDArgsSetSubSequenceStartPos Set sub-sequence start position vector of @@ -263,11 +144,11 @@ PD_API PD_Error PDArgsGetSequenceStartPos(PD_Arguments args, * @param args arguments array * @param ID array index * @param subSeqPos sub-sequence start position array. - * @return PD_Error + * @return paddle_error */ -PD_API PD_Error PDArgsSetSubSequenceStartPos(PD_Arguments args, - uint64_t ID, - PD_IVector subSeqPos); +PD_API paddle_error PDArgsSetSubSequenceStartPos(PD_Arguments args, + uint64_t ID, + paddle_ivector subSeqPos); /** * @brief PDArgsGetSubSequenceStartPos Get sub-sequence start position vector of @@ -275,11 +156,11 @@ PD_API PD_Error PDArgsSetSubSequenceStartPos(PD_Arguments args, * @param args arguments array * @param ID array index * @param subSeqPos sub-sequence start position array - * @return PD_Error + * @return paddle_error */ -PD_API PD_Error PDArgsGetSubSequenceStartPos(PD_Arguments args, - uint64_t ID, - PD_IVector subSeqPos); +PD_API paddle_error PDArgsGetSubSequenceStartPos(PD_Arguments args, + uint64_t ID, + paddle_ivector subSeqPos); /** * @brief GradientMachine means a neural network. */ @@ -291,19 +172,18 @@ typedef void* PD_GradientMachine; * @param [out] machine that used for model inference. * @param [in] modelConfigProtobuf * @param [in] size - * @return PD_Error + * @return paddle_error */ -PD_API PD_Error PDGradientMachineCreateForPredict(PD_GradientMachine* machine, - void* modelConfigProtobuf, - int size); +PD_API paddle_error PDGradientMachineCreateForPredict( + PD_GradientMachine* machine, void* modelConfigProtobuf, int size); /** * @brief PDGradientMachineLoadParameterFromDisk Load parameter from disk. * @param machine Gradient Machine. * @param path local directory path. - * @return PD_Error + * @return paddle_error */ -PD_API PD_Error PDGradientMachineLoadParameterFromDisk( +PD_API paddle_error PDGradientMachineLoadParameterFromDisk( PD_GradientMachine machine, const char* path); /** @@ -312,12 +192,12 @@ PD_API PD_Error PDGradientMachineLoadParameterFromDisk( * @param inArgs input arguments * @param outArgs output arguments * @param isTrain is train or not - * @return PD_Error + * @return paddle_error */ -PD_API PD_Error PDGradientMachineForward(PD_GradientMachine machine, - PD_Arguments inArgs, - PD_Arguments outArgs, - bool isTrain); +PD_API paddle_error PDGradientMachineForward(PD_GradientMachine machine, + PD_Arguments inArgs, + PD_Arguments outArgs, + bool isTrain); /** * @brief PDGradientMachineCreateSharedParam Create a gradient machine, which @@ -326,24 +206,25 @@ PD_API PD_Error PDGradientMachineForward(PD_GradientMachine machine, * @param [in] modelConfigProtobuf model config protobuf * @param [in] size of model config buffer. * @param [out] slave gradient machine, the output value. - * @return PD_Error + * @return paddle_error */ -PD_API PD_Error PDGradientMachineCreateSharedParam(PD_GradientMachine origin, - void* modelConfigProtobuf, - int size, - PD_GradientMachine* slave); +PD_API paddle_error +PDGradientMachineCreateSharedParam(PD_GradientMachine origin, + void* modelConfigProtobuf, + int size, + PD_GradientMachine* slave); /** * @brief PDGradientMachineDestroy Destroy a gradient machine * @param machine that need to destroy - * @return PD_Error + * @return paddle_error */ -PD_API PD_Error PDGradientMachineDestroy(PD_GradientMachine machine); +PD_API paddle_error PDGradientMachineDestroy(PD_GradientMachine machine); /** * Initialize Paddle. */ -PD_API PD_Error PDInit(int argc, char** argv); +PD_API paddle_error PDInit(int argc, char** argv); #ifdef __cplusplus } diff --git a/paddle/capi/Vector.cpp b/paddle/capi/Vector.cpp index 514d65fec8f..4ccb167fec2 100644 --- a/paddle/capi/Vector.cpp +++ b/paddle/capi/Vector.cpp @@ -19,9 +19,14 @@ using paddle::capi::cast; extern "C" { -PD_IVector PDIVecCreateNone() { return new paddle::capi::CIVector(); } +paddle_ivector paddle_ivector_create_none() { + return new paddle::capi::CIVector(); +} -PD_IVector PDIVectorCreate(int* array, uint64_t size, bool copy, bool useGPU) { +paddle_ivector paddle_ivector_create(int* array, + uint64_t size, + bool copy, + bool useGPU) { auto ptr = new paddle::capi::CIVector(); if (copy) { ptr->vec = paddle::IVector::create(size, useGPU); @@ -32,13 +37,13 @@ PD_IVector PDIVectorCreate(int* array, uint64_t size, bool copy, bool useGPU) { return ptr; } -PD_Error PDIVecDestroy(PD_IVector ivec) { +paddle_error paddle_ivector_destroy(paddle_ivector ivec) { if (ivec == nullptr) return kPD_NULLPTR; delete cast(ivec); return kPD_NO_ERROR; } -PD_Error PDIVectorGet(PD_IVector ivec, int** buffer) { +paddle_error paddle_ivector_get(paddle_ivector ivec, int** buffer) { if (ivec == nullptr || buffer == nullptr) return kPD_NULLPTR; auto v = cast(ivec); if (v->vec == nullptr) return kPD_NULLPTR; @@ -46,7 +51,7 @@ PD_Error PDIVectorGet(PD_IVector ivec, int** buffer) { return kPD_NO_ERROR; } -PD_Error PDIVectorResize(PD_IVector ivec, uint64_t size) { +paddle_error paddle_ivector_resize(paddle_ivector ivec, uint64_t size) { if (ivec == nullptr) return kPD_NULLPTR; auto v = cast(ivec); if (v->vec == nullptr) return kPD_NULLPTR; @@ -54,7 +59,7 @@ PD_Error PDIVectorResize(PD_IVector ivec, uint64_t size) { return kPD_NO_ERROR; } -PD_Error PDIVectorGetSize(PD_IVector ivec, uint64_t* size) { +paddle_error paddle_ivector_get_size(paddle_ivector ivec, uint64_t* size) { if (ivec == nullptr) return kPD_NULLPTR; auto v = cast(ivec); if (v->vec == nullptr) return kPD_NULLPTR; diff --git a/paddle/capi/config.h.in b/paddle/capi/config.h.in index 32d8a364e0e..af4e80dea14 100644 --- a/paddle/capi/config.h.in +++ b/paddle/capi/config.h.in @@ -3,4 +3,8 @@ typedef @PADDLE_FLOAT_TYPE@ pd_real; +// Since we only support linux and macos in compile, always use clang or +// gcc 4.8+. DLL_IMPORT/DLL_EXPORT is as simple as below. +#define PD_API __attribute__((visibility("default"))) + #endif diff --git a/paddle/capi/error.h b/paddle/capi/error.h new file mode 100644 index 00000000000..8dbb6d95487 --- /dev/null +++ b/paddle/capi/error.h @@ -0,0 +1,15 @@ +#ifndef __PADDLE_CAPI_ERROR_H__ +#define __PADDLE_CAPI_ERROR_H__ + +/** + * Error Type for Paddle API. + */ +typedef enum { + kPD_NO_ERROR = 0, + kPD_NULLPTR = 1, + kPD_OUT_OF_RANGE = 2, + kPD_PROTOBUF_ERROR = 3, + kPD_UNDEFINED_ERROR = -1, +} paddle_error; + +#endif diff --git a/paddle/capi/matrix.h b/paddle/capi/matrix.h new file mode 100644 index 00000000000..2f6488f3838 --- /dev/null +++ b/paddle/capi/matrix.h @@ -0,0 +1,91 @@ +/* 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. */ + +#ifndef __PADDLE_CAPI_MATRIX_H__ +#define __PADDLE_CAPI_MATRIX_H__ + +#include +#include "config.h" +#include "error.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Matrix functions. Return will be a paddle_error type. + */ +typedef void* paddle_matrix; + +/** + * @brief paddle_matrix_create Create a dense matrix + * @param height matrix height. + * @param width matrix width + * @param useGpu use GPU of not + * @return Matrix handler + */ +PD_API paddle_matrix paddle_matrix_create(uint64_t height, + uint64_t width, + bool useGpu); + +/** + * @brief paddle_matrix_destroy Destroy a matrix. + * @param mat + * @return paddle_error + */ +PD_API paddle_error paddle_matrix_destroy(paddle_matrix mat); + +/** + * @brief paddle_matrix_set_row Set a row to matrix. + * @param mat Target Matrix + * @param rowID Index of row + * @param rowArray Row data. + * @return paddle_error + */ +PD_API paddle_error paddle_matrix_set_row(paddle_matrix mat, + uint64_t rowID, + pd_real* rowArray); + +/** + * @brief PDMatGetRow Get raw row buffer from matrix + * @param [in] mat Target matrix + * @param [in] rowID Index of row. + * @param [out] rawRowBuffer Row Buffer + * @return paddle_error + */ +PD_API paddle_error paddle_matrix_get_row(paddle_matrix mat, + uint64_t rowID, + pd_real** rawRowBuffer); + +/** + * @brief PDMatCreateNone Create None Matrix + * @return + */ +PD_API paddle_matrix paddle_matrix_create_none(); + +/** + * @brief PDMatGetShape get the shape of matrix + * @param mat target matrix + * @param height The height of matrix + * @param width The width of matrix + * @return paddle_error + */ +PD_API paddle_error paddle_matrix_get_shape(paddle_matrix mat, + uint64_t* height, + uint64_t* width); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/paddle/capi/tests/test_Arguments.cpp b/paddle/capi/tests/test_Arguments.cpp index e015b94e12c..92dcf6bf9c5 100644 --- a/paddle/capi/tests/test_Arguments.cpp +++ b/paddle/capi/tests/test_Arguments.cpp @@ -39,14 +39,14 @@ TEST(CAPIArguments, value) { PD_Arguments args = PDArgsCreateNone(); ASSERT_EQ(kPD_NO_ERROR, PDArgsResize(args, 1)); - PD_Matrix mat = PDMatCreate(128, 64, false); + paddle_matrix mat = paddle_matrix_create(128, 64, false); for (size_t i = 0; i < 128; ++i) { std::vector sampleBuf = randomBuffer(64); - PDMatCopyToRow(mat, i, sampleBuf.data()); + paddle_matrix_set_row(mat, i, sampleBuf.data()); } ASSERT_EQ(kPD_NO_ERROR, PDArgsSetValue(args, 0, mat)); - PD_Matrix val = PDMatCreateNone(); + paddle_matrix val = paddle_matrix_create_none(); ASSERT_EQ(kPD_NO_ERROR, PDArgsGetValue(args, 0, val)); @@ -54,15 +54,15 @@ TEST(CAPIArguments, value) { pd_real* row1; pd_real* row2; - ASSERT_EQ(kPD_NO_ERROR, PDMatGetRow(mat, i, &row1)); - ASSERT_EQ(kPD_NO_ERROR, PDMatGetRow(val, i, &row2)); + ASSERT_EQ(kPD_NO_ERROR, paddle_matrix_get_row(mat, i, &row1)); + ASSERT_EQ(kPD_NO_ERROR, paddle_matrix_get_row(val, i, &row2)); ASSERT_EQ(row1, row2); } - PD_IVector ivec = PDIVecCreateNone(); - ASSERT_EQ(kPD_NO_ERROR, PDIVecDestroy(ivec)); - ASSERT_EQ(kPD_NO_ERROR, PDMatDestroy(val)); - ASSERT_EQ(kPD_NO_ERROR, PDMatDestroy(mat)); + paddle_ivector ivec = paddle_ivector_create_none(); + ASSERT_EQ(kPD_NO_ERROR, paddle_ivector_destroy(ivec)); + ASSERT_EQ(kPD_NO_ERROR, paddle_matrix_destroy(val)); + ASSERT_EQ(kPD_NO_ERROR, paddle_matrix_destroy(mat)); ASSERT_EQ(kPD_NO_ERROR, PDArgsDestroy(args)); } @@ -70,15 +70,15 @@ TEST(CAPIArguments, ids) { PD_Arguments args = PDArgsCreateNone(); ASSERT_EQ(kPD_NO_ERROR, PDArgsResize(args, 1)); - PD_IVector ivec; + paddle_ivector ivec; int array[3] = {1, 2, 3}; - ivec = PDIVectorCreate(array, 3, true, false); + ivec = paddle_ivector_create(array, 3, true, false); ASSERT_EQ(kPD_NO_ERROR, PDArgsSetIds(args, 0, ivec)); - PD_IVector val = PDIVecCreateNone(); + paddle_ivector val = paddle_ivector_create_none(); ASSERT_EQ(kPD_NO_ERROR, PDArgsGetIds(args, 0, val)); - ASSERT_EQ(kPD_NO_ERROR, PDIVecDestroy(ivec)); - ASSERT_EQ(kPD_NO_ERROR, PDIVecDestroy(val)); + ASSERT_EQ(kPD_NO_ERROR, paddle_ivector_destroy(ivec)); + ASSERT_EQ(kPD_NO_ERROR, paddle_ivector_destroy(val)); ASSERT_EQ(kPD_NO_ERROR, PDArgsDestroy(args)); } @@ -87,24 +87,24 @@ void testSequenceHelper(T1 setter, T2 getter) { PD_Arguments args = PDArgsCreateNone(); ASSERT_EQ(kPD_NO_ERROR, PDArgsResize(args, 1)); - PD_IVector ivec; + paddle_ivector ivec; int array[3] = {1, 2, 3}; - ivec = PDIVectorCreate(array, 3, true, false); + ivec = paddle_ivector_create(array, 3, true, false); ASSERT_EQ(kPD_NO_ERROR, setter(args, 0, ivec)); - PD_IVector val = PDIVecCreateNone(); + paddle_ivector val = paddle_ivector_create_none(); ASSERT_EQ(kPD_NO_ERROR, getter(args, 0, val)); uint64_t size; - ASSERT_EQ(kPD_NO_ERROR, PDIVectorGetSize(val, &size)); + ASSERT_EQ(kPD_NO_ERROR, paddle_ivector_get_size(val, &size)); int* rawBuf; - ASSERT_EQ(kPD_NO_ERROR, PDIVectorGet(val, &rawBuf)); + ASSERT_EQ(kPD_NO_ERROR, paddle_ivector_get(val, &rawBuf)); for (size_t i = 0; i < size; ++i) { ASSERT_EQ(array[i], rawBuf[i]); } - ASSERT_EQ(kPD_NO_ERROR, PDIVecDestroy(ivec)); - ASSERT_EQ(kPD_NO_ERROR, PDIVecDestroy(val)); + ASSERT_EQ(kPD_NO_ERROR, paddle_ivector_destroy(ivec)); + ASSERT_EQ(kPD_NO_ERROR, paddle_ivector_destroy(val)); ASSERT_EQ(kPD_NO_ERROR, PDArgsDestroy(args)); } diff --git a/paddle/capi/tests/test_GradientMachine.cpp b/paddle/capi/tests/test_GradientMachine.cpp index acee99bcc4b..05a06445c27 100644 --- a/paddle/capi/tests/test_GradientMachine.cpp +++ b/paddle/capi/tests/test_GradientMachine.cpp @@ -59,12 +59,12 @@ TEST(GradientMachine, testPredict) { PD_Arguments inArgs = PDArgsCreateNone(); ASSERT_EQ(kPD_NO_ERROR, PDArgsResize(inArgs, 1)); - PD_Matrix mat = PDMatCreate(1, 100, false); + paddle_matrix mat = paddle_matrix_create(1, 100, false); static_assert(std::is_same::value, ""); auto data = randomBuffer(100); pd_real* rowPtr; - ASSERT_EQ(kPD_NO_ERROR, PDMatGetRow(mat, 0, &rowPtr)); + ASSERT_EQ(kPD_NO_ERROR, paddle_matrix_get_row(mat, 0, &rowPtr)); memcpy(rowPtr, data.data(), data.size() * sizeof(pd_real)); ASSERT_EQ(kPD_NO_ERROR, PDArgsSetValue(inArgs, 0, mat)); @@ -87,16 +87,16 @@ TEST(GradientMachine, testPredict) { auto matPaddle = paddleOutArgs[0].value; uint64_t height, width; - ASSERT_EQ(kPD_NO_ERROR, PDMatGetShape(mat, &height, &width)); + ASSERT_EQ(kPD_NO_ERROR, paddle_matrix_get_shape(mat, &height, &width)); ASSERT_EQ(matPaddle->getHeight(), height); ASSERT_EQ(matPaddle->getWidth(), width); - ASSERT_EQ(kPD_NO_ERROR, PDMatGetRow(mat, 0, &rowPtr)); + ASSERT_EQ(kPD_NO_ERROR, paddle_matrix_get_row(mat, 0, &rowPtr)); for (size_t i = 0; i < width; ++i) { ASSERT_NEAR(matPaddle->getData()[i], rowPtr[i], 1e-5); } - ASSERT_EQ(kPD_NO_ERROR, PDMatDestroy(mat)); + ASSERT_EQ(kPD_NO_ERROR, paddle_matrix_destroy(mat)); ASSERT_EQ(kPD_NO_ERROR, PDArgsDestroy(inArgs)); ASSERT_EQ(kPD_NO_ERROR, PDArgsDestroy(outArgs)); std::swap(machineSlave, machine); diff --git a/paddle/capi/tests/test_Matrix.cpp b/paddle/capi/tests/test_Matrix.cpp index 1d38162add2..1b3b881caee 100644 --- a/paddle/capi/tests/test_Matrix.cpp +++ b/paddle/capi/tests/test_Matrix.cpp @@ -16,30 +16,31 @@ limitations under the License. */ #include "gtest/gtest.h" TEST(CAPIMatrix, create) { - PD_Matrix mat = PDMatCreate(128, 32, false); + paddle_matrix mat = paddle_matrix_create(128, 32, false); std::vector sampleRow; sampleRow.resize(32); for (size_t i = 0; i < sampleRow.size(); ++i) { sampleRow[i] = 1.0 / (i + 1.0); } - ASSERT_EQ(kPD_NO_ERROR, PDMatCopyToRow(mat, 0, sampleRow.data())); - ASSERT_EQ(kPD_OUT_OF_RANGE, PDMatCopyToRow(mat, 128, sampleRow.data())); + ASSERT_EQ(kPD_NO_ERROR, paddle_matrix_set_row(mat, 0, sampleRow.data())); + ASSERT_EQ(kPD_OUT_OF_RANGE, + paddle_matrix_set_row(mat, 128, sampleRow.data())); pd_real* arrayPtr; - ASSERT_EQ(kPD_NO_ERROR, PDMatGetRow(mat, 0, &arrayPtr)); + ASSERT_EQ(kPD_NO_ERROR, paddle_matrix_get_row(mat, 0, &arrayPtr)); for (size_t i = 0; i < sampleRow.size(); ++i) { ASSERT_NEAR(sampleRow[i], arrayPtr[i], 1e-5); } uint64_t height, width; - ASSERT_EQ(kPD_NO_ERROR, PDMatGetShape(mat, &height, &width)); + ASSERT_EQ(kPD_NO_ERROR, paddle_matrix_get_shape(mat, &height, &width)); ASSERT_EQ(128UL, height); ASSERT_EQ(32UL, width); - ASSERT_EQ(kPD_NO_ERROR, PDMatDestroy(mat)); + ASSERT_EQ(kPD_NO_ERROR, paddle_matrix_destroy(mat)); } TEST(CAPIMatrix, createNone) { - PD_Matrix mat = PDMatCreateNone(); - ASSERT_EQ(kPD_NO_ERROR, PDMatDestroy(mat)); + paddle_matrix mat = paddle_matrix_create_none(); + ASSERT_EQ(kPD_NO_ERROR, paddle_matrix_destroy(mat)); } diff --git a/paddle/capi/tests/test_Vector.cpp b/paddle/capi/tests/test_Vector.cpp index b3558fe0fdd..64c19265e3d 100644 --- a/paddle/capi/tests/test_Vector.cpp +++ b/paddle/capi/tests/test_Vector.cpp @@ -16,16 +16,16 @@ limitations under the License. */ #include "gtest/gtest.h" TEST(CAPIVector, create) { - PD_IVector vec; + paddle_ivector vec; int array[3] = {1, 2, 3}; - vec = PDIVectorCreate(array, 3, true, false); - ASSERT_EQ(kPD_NO_ERROR, PDIVectorResize(vec, 1000)); + vec = paddle_ivector_create(array, 3, true, false); + ASSERT_EQ(kPD_NO_ERROR, paddle_ivector_resize(vec, 1000)); uint64_t size; - ASSERT_EQ(kPD_NO_ERROR, PDIVectorGetSize(vec, &size)); - ASSERT_EQ(kPD_NO_ERROR, PDIVecDestroy(vec)); + ASSERT_EQ(kPD_NO_ERROR, paddle_ivector_get_size(vec, &size)); + ASSERT_EQ(kPD_NO_ERROR, paddle_ivector_destroy(vec)); } TEST(CAPIVector, createNone) { - PD_IVector vec = PDIVecCreateNone(); - ASSERT_EQ(kPD_NO_ERROR, PDIVecDestroy(vec)); + paddle_ivector vec = paddle_ivector_create_none(); + ASSERT_EQ(kPD_NO_ERROR, paddle_ivector_destroy(vec)); } diff --git a/paddle/capi/vector.h b/paddle/capi/vector.h new file mode 100644 index 00000000000..a92aeff1642 --- /dev/null +++ b/paddle/capi/vector.h @@ -0,0 +1,89 @@ +/* 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. */ + +#ifndef __PADDLE_CAPI_VECTOR_H__ +#define __PADDLE_CAPI_VECTOR_H__ + +#include +#include +#include "config.h" +#include "error.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Int Vector Functions. Return will be a paddle_error type. + */ +typedef void* paddle_ivector; + +/** + * @brief Create an none int vector. It just a handler and store nothing. Used + * to get output from other api. + * @return None int vector. + */ +PD_API paddle_ivector paddle_ivector_create_none(); + +/** + * @brief paddle_ivector_create create a paddle int vector + * @param array: input array. + * @param size: input array size. + * @param copy: memory copy or just use same memory. True if copy. + * @param useGPU: True if use GPU + * @return paddle_error + */ +PD_API paddle_ivector paddle_ivector_create(int* array, + uint64_t size, + bool copy, + bool useGPU); + +/** + * @brief paddle_ivector_destroy destory an int vector. + * @param ivec vector to be destoried. + * @return paddle_error + */ +PD_API paddle_error paddle_ivector_destroy(paddle_ivector ivec); + +/** + * @brief paddle_ivector_get get raw buffer stored inside this int vector. It + * could be GPU memory if this int vector is stored in GPU. + * @param [in] ivec int vector + * @param [out] buffer the return buffer pointer. + * @return paddle_error + */ +PD_API paddle_error paddle_ivector_get(paddle_ivector ivec, int** buffer); + +/** + * @brief paddle_ivector_resize resize the int vector. + * @param [in] ivec: int vector + * @param [in] size: size to change + * @return paddle_error + */ +PD_API paddle_error paddle_ivector_resize(paddle_ivector ivec, uint64_t size); + +/** + * @brief paddle_ivector_get_size get the size of int vector. + * @param [in] ivec: int vector + * @param [out] size: return size of this int vector. + * @return paddle_error + */ +PD_API paddle_error paddle_ivector_get_size(paddle_ivector ivec, + uint64_t* size); + +#ifdef __cplusplus +} +#endif + +#endif -- GitLab From 0afd5c30a85340fa639d4f2f4805c2c8548dce58 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Tue, 21 Mar 2017 13:27:39 +0800 Subject: [PATCH 0034/3256] Stash --- paddle/capi/Arguments.cpp | 49 +++++++++------- paddle/capi/GradientMachine.cpp | 4 +- paddle/capi/PaddleCAPI.h | 66 +++++++++++----------- paddle/capi/tests/test_Arguments.cpp | 39 ++++++------- paddle/capi/tests/test_GradientMachine.cpp | 16 +++--- 5 files changed, 92 insertions(+), 82 deletions(-) diff --git a/paddle/capi/Arguments.cpp b/paddle/capi/Arguments.cpp index 29aa0858dd8..792b8c58a9a 100644 --- a/paddle/capi/Arguments.cpp +++ b/paddle/capi/Arguments.cpp @@ -21,27 +21,31 @@ using paddle::capi::cast; #define castIVec(v) cast(v) extern "C" { -PD_Arguments PDArgsCreateNone() { return new paddle::capi::CArguments(); } +paddle_arguments paddle_arguments_create_none() { + return new paddle::capi::CArguments(); +} -paddle_error PDArgsDestroy(PD_Arguments args) { +paddle_error paddle_arguments_destroy(paddle_arguments args) { if (args == nullptr) return kPD_NULLPTR; delete castArg(args); return kPD_NO_ERROR; } -paddle_error PDArgsGetSize(PD_Arguments args, uint64_t* size) { +paddle_error paddle_arguments_size(paddle_arguments args, uint64_t* size) { if (args == nullptr || size == nullptr) return kPD_NULLPTR; *size = castArg(args)->args.size(); return kPD_NO_ERROR; } -paddle_error PDArgsResize(PD_Arguments args, uint64_t size) { +paddle_error paddle_arguments_resize(paddle_arguments args, uint64_t size) { if (args == nullptr) return kPD_NULLPTR; castArg(args)->args.resize(size); return kPD_NO_ERROR; } -paddle_error PDArgsSetValue(PD_Arguments args, uint64_t ID, paddle_matrix mat) { +paddle_error paddle_arguments_set_value(paddle_arguments args, + uint64_t ID, + paddle_matrix mat) { if (args == nullptr || mat == nullptr) return kPD_NULLPTR; auto m = paddle::capi::cast(mat); if (m->mat == nullptr) return kPD_NULLPTR; @@ -51,7 +55,9 @@ paddle_error PDArgsSetValue(PD_Arguments args, uint64_t ID, paddle_matrix mat) { return kPD_NO_ERROR; } -paddle_error PDArgsGetValue(PD_Arguments args, uint64_t ID, paddle_matrix mat) { +paddle_error paddle_arguments_value(paddle_arguments args, + uint64_t ID, + paddle_matrix mat) { if (args == nullptr || mat == nullptr) return kPD_NULLPTR; auto m = paddle::capi::cast(mat); auto a = castArg(args); @@ -60,7 +66,9 @@ paddle_error PDArgsGetValue(PD_Arguments args, uint64_t ID, paddle_matrix mat) { return kPD_NO_ERROR; } -paddle_error PDArgsGetIds(PD_Arguments args, uint64_t ID, paddle_ivector ids) { +paddle_error paddle_arguments_ids(paddle_arguments args, + uint64_t ID, + paddle_ivector ids) { if (args == nullptr || ids == nullptr) return kPD_NULLPTR; auto iv = castIVec(ids); auto a = castArg(args); @@ -69,7 +77,9 @@ paddle_error PDArgsGetIds(PD_Arguments args, uint64_t ID, paddle_ivector ids) { return kPD_NO_ERROR; } -paddle_error PDArgsSetIds(PD_Arguments args, uint64_t ID, paddle_ivector ids) { +paddle_error paddle_arguments_set_ids(paddle_arguments args, + uint64_t ID, + paddle_ivector ids) { //! TODO(lizhao): Complete this method. if (args == nullptr || ids == nullptr) return kPD_NULLPTR; auto iv = paddle::capi::cast(ids); @@ -80,9 +90,9 @@ paddle_error PDArgsSetIds(PD_Arguments args, uint64_t ID, paddle_ivector ids) { return kPD_NO_ERROR; } -paddle_error PDArgsSetSequenceStartPos(PD_Arguments args, - uint64_t ID, - paddle_ivector seqPos) { +paddle_error paddle_arguments_set_sequence_start_pos(paddle_arguments args, + uint64_t ID, + paddle_ivector seqPos) { if (args == nullptr || seqPos == nullptr) return kPD_NULLPTR; auto iv = paddle::capi::cast(seqPos); if (iv->vec == nullptr) return kPD_NULLPTR; @@ -93,9 +103,8 @@ paddle_error PDArgsSetSequenceStartPos(PD_Arguments args, return kPD_NO_ERROR; } -paddle_error PDArgsSetSubSequenceStartPos(PD_Arguments args, - uint64_t ID, - paddle_ivector subSeqPos) { +paddle_error paddle_arguments_set_sub_sequence_start_pos( + paddle_arguments args, uint64_t ID, paddle_ivector subSeqPos) { if (args == nullptr || subSeqPos == nullptr) return kPD_NULLPTR; auto iv = paddle::capi::cast(subSeqPos); if (iv->vec == nullptr) return kPD_NULLPTR; @@ -106,9 +115,9 @@ paddle_error PDArgsSetSubSequenceStartPos(PD_Arguments args, return kPD_NO_ERROR; } -paddle_error PDArgsGetSequenceStartPos(PD_Arguments args, - uint64_t ID, - paddle_ivector seqPos) { +paddle_error paddle_arguments_sequence_start_pos(paddle_arguments args, + uint64_t ID, + paddle_ivector seqPos) { if (args == nullptr || seqPos == nullptr) return kPD_NULLPTR; auto iv = castIVec(seqPos); auto a = castArg(args); @@ -118,9 +127,9 @@ paddle_error PDArgsGetSequenceStartPos(PD_Arguments args, return kPD_NO_ERROR; } -paddle_error PDArgsGetSubSequenceStartPos(PD_Arguments args, - uint64_t ID, - paddle_ivector subSeqPos) { +paddle_error paddle_arguments_sub_sequence_start_pos(paddle_arguments args, + uint64_t ID, + paddle_ivector subSeqPos) { if (args == nullptr || subSeqPos == nullptr) return kPD_NULLPTR; auto iv = castIVec(subSeqPos); auto a = castArg(args); diff --git a/paddle/capi/GradientMachine.cpp b/paddle/capi/GradientMachine.cpp index 6ca3cf7e4d8..9f0ffd6599f 100644 --- a/paddle/capi/GradientMachine.cpp +++ b/paddle/capi/GradientMachine.cpp @@ -70,8 +70,8 @@ paddle_error PDGradientMachineLoadParameterFromDisk(PD_GradientMachine machine, } paddle_error PDGradientMachineForward(PD_GradientMachine machine, - PD_Arguments inArgs, - PD_Arguments outArgs, + paddle_arguments inArgs, + paddle_arguments outArgs, bool isTrain) { auto m = cast(machine); auto in = paddle::capi::cast(inArgs); diff --git a/paddle/capi/PaddleCAPI.h b/paddle/capi/PaddleCAPI.h index 37dfb13814b..eea7c3bd05f 100644 --- a/paddle/capi/PaddleCAPI.h +++ b/paddle/capi/PaddleCAPI.h @@ -36,20 +36,21 @@ extern "C" { * Arguments functions. Each argument means layer output. Arguments means a * array of arguemnt. */ -typedef void* PD_Arguments; +typedef void* paddle_arguments; /** - * @brief PDArgsCreateNone Create a array of arguments, which size is zero. + * @brief paddle_arguments_create_none Create a array of arguments, which size + * is zero. * @return Arguemnts */ -PD_API PD_Arguments PDArgsCreateNone(); +PD_API paddle_arguments paddle_arguments_create_none(); /** - * @brief PDArgsDestroy Destroy the arguments + * @brief paddle_arguments_destroy Destroy the arguments * @param args arguments to destroy * @return paddle_error */ -PD_API paddle_error PDArgsDestroy(PD_Arguments args); +PD_API paddle_error paddle_arguments_destroy(paddle_arguments args); /** * @brief PDArgsGetSize Get size of arguments array @@ -57,7 +58,8 @@ PD_API paddle_error PDArgsDestroy(PD_Arguments args); * @param [out] size array size * @return paddle_error */ -PD_API paddle_error PDArgsGetSize(PD_Arguments args, uint64_t* size); +PD_API paddle_error paddle_arguments_size(paddle_arguments args, + uint64_t* size); /** * @brief PDArgsResize Resize a arguments array. @@ -65,7 +67,8 @@ PD_API paddle_error PDArgsGetSize(PD_Arguments args, uint64_t* size); * @param size target size of array * @return paddle_error */ -PD_API paddle_error PDArgsResize(PD_Arguments args, uint64_t size); +PD_API paddle_error paddle_arguments_resize(paddle_arguments args, + uint64_t size); /** * @brief PDArgsSetValue Set value matrix of one argument in array, which index @@ -75,9 +78,9 @@ PD_API paddle_error PDArgsResize(PD_Arguments args, uint64_t size); * @param mat matrix pointer * @return paddle_error */ -PD_API paddle_error PDArgsSetValue(PD_Arguments args, - uint64_t ID, - paddle_matrix mat); +PD_API paddle_error paddle_arguments_set_value(paddle_arguments args, + uint64_t ID, + paddle_matrix mat); /** * @brief PDArgsGetValue Get value matrix of one argument in array, which index @@ -87,9 +90,9 @@ PD_API paddle_error PDArgsSetValue(PD_Arguments args, * @param [out] mat matrix pointer * @return paddle_error */ -PD_API paddle_error PDArgsGetValue(PD_Arguments args, - uint64_t ID, - paddle_matrix mat); +PD_API paddle_error paddle_arguments_value(paddle_arguments args, + uint64_t ID, + paddle_matrix mat); /** * @brief PDArgsGetIds Get the integer vector of one argument in array, which @@ -99,9 +102,9 @@ PD_API paddle_error PDArgsGetValue(PD_Arguments args, * @param ids integer vector pointer * @return paddle_error */ -PD_API paddle_error PDArgsGetIds(PD_Arguments args, - uint64_t ID, - paddle_ivector ids); +PD_API paddle_error paddle_arguments_ids(paddle_arguments args, + uint64_t ID, + paddle_ivector ids); /** * @brief PDArgsSetIds Set the integer vector of one argument in array, which @@ -111,9 +114,9 @@ PD_API paddle_error PDArgsGetIds(PD_Arguments args, * @param [out] ids integer vector pointer * @return paddle_error */ -PD_API paddle_error PDArgsSetIds(PD_Arguments args, - uint64_t ID, - paddle_ivector ids); +PD_API paddle_error paddle_arguments_set_ids(paddle_arguments args, + uint64_t ID, + paddle_ivector ids); /** * @brief PDArgsSetSequenceStartPos Set sequence start position vector of one @@ -123,9 +126,8 @@ PD_API paddle_error PDArgsSetIds(PD_Arguments args, * @param seqPos sequence position array. * @return paddle_error */ -PD_API paddle_error PDArgsSetSequenceStartPos(PD_Arguments args, - uint64_t ID, - paddle_ivector seqPos); +PD_API paddle_error paddle_arguments_set_sequence_start_pos( + paddle_arguments args, uint64_t ID, paddle_ivector seqPos); /** * @brief PDArgsGetSequenceStartPos Get sequence start position vector of one * argument in array, which index is `ID`. @@ -134,9 +136,9 @@ PD_API paddle_error PDArgsSetSequenceStartPos(PD_Arguments args, * @param [out] seqPos sequence position array * @return paddle_error */ -PD_API paddle_error PDArgsGetSequenceStartPos(PD_Arguments args, - uint64_t ID, - paddle_ivector seqPos); +PD_API paddle_error paddle_arguments_sequence_start_pos(paddle_arguments args, + uint64_t ID, + paddle_ivector seqPos); /** * @brief PDArgsSetSubSequenceStartPos Set sub-sequence start position vector of @@ -146,9 +148,8 @@ PD_API paddle_error PDArgsGetSequenceStartPos(PD_Arguments args, * @param subSeqPos sub-sequence start position array. * @return paddle_error */ -PD_API paddle_error PDArgsSetSubSequenceStartPos(PD_Arguments args, - uint64_t ID, - paddle_ivector subSeqPos); +PD_API paddle_error paddle_arguments_set_sub_sequence_start_pos( + paddle_arguments args, uint64_t ID, paddle_ivector subSeqPos); /** * @brief PDArgsGetSubSequenceStartPos Get sub-sequence start position vector of @@ -158,9 +159,8 @@ PD_API paddle_error PDArgsSetSubSequenceStartPos(PD_Arguments args, * @param subSeqPos sub-sequence start position array * @return paddle_error */ -PD_API paddle_error PDArgsGetSubSequenceStartPos(PD_Arguments args, - uint64_t ID, - paddle_ivector subSeqPos); +PD_API paddle_error paddle_arguments_sub_sequence_start_pos( + paddle_arguments args, uint64_t ID, paddle_ivector subSeqPos); /** * @brief GradientMachine means a neural network. */ @@ -195,8 +195,8 @@ PD_API paddle_error PDGradientMachineLoadParameterFromDisk( * @return paddle_error */ PD_API paddle_error PDGradientMachineForward(PD_GradientMachine machine, - PD_Arguments inArgs, - PD_Arguments outArgs, + paddle_arguments inArgs, + paddle_arguments outArgs, bool isTrain); /** diff --git a/paddle/capi/tests/test_Arguments.cpp b/paddle/capi/tests/test_Arguments.cpp index 92dcf6bf9c5..b445b396f3c 100644 --- a/paddle/capi/tests/test_Arguments.cpp +++ b/paddle/capi/tests/test_Arguments.cpp @@ -28,27 +28,27 @@ static std::vector randomBuffer(size_t bufSize) { } TEST(CAPIArguments, create) { - PD_Arguments args = PDArgsCreateNone(); + paddle_arguments args = paddle_arguments_create_none(); uint64_t size; - ASSERT_EQ(kPD_NO_ERROR, PDArgsGetSize(args, &size)); + ASSERT_EQ(kPD_NO_ERROR, paddle_arguments_size(args, &size)); ASSERT_EQ(0UL, size); - ASSERT_EQ(kPD_NO_ERROR, PDArgsDestroy(args)); + ASSERT_EQ(kPD_NO_ERROR, paddle_arguments_destroy(args)); } TEST(CAPIArguments, value) { - PD_Arguments args = PDArgsCreateNone(); - ASSERT_EQ(kPD_NO_ERROR, PDArgsResize(args, 1)); + paddle_arguments args = paddle_arguments_create_none(); + ASSERT_EQ(kPD_NO_ERROR, paddle_arguments_resize(args, 1)); paddle_matrix mat = paddle_matrix_create(128, 64, false); for (size_t i = 0; i < 128; ++i) { std::vector sampleBuf = randomBuffer(64); paddle_matrix_set_row(mat, i, sampleBuf.data()); } - ASSERT_EQ(kPD_NO_ERROR, PDArgsSetValue(args, 0, mat)); + ASSERT_EQ(kPD_NO_ERROR, paddle_arguments_set_value(args, 0, mat)); paddle_matrix val = paddle_matrix_create_none(); - ASSERT_EQ(kPD_NO_ERROR, PDArgsGetValue(args, 0, val)); + ASSERT_EQ(kPD_NO_ERROR, paddle_arguments_value(args, 0, val)); for (size_t i = 0; i < 128; ++i) { pd_real* row1; @@ -63,29 +63,29 @@ TEST(CAPIArguments, value) { ASSERT_EQ(kPD_NO_ERROR, paddle_ivector_destroy(ivec)); ASSERT_EQ(kPD_NO_ERROR, paddle_matrix_destroy(val)); ASSERT_EQ(kPD_NO_ERROR, paddle_matrix_destroy(mat)); - ASSERT_EQ(kPD_NO_ERROR, PDArgsDestroy(args)); + ASSERT_EQ(kPD_NO_ERROR, paddle_arguments_destroy(args)); } TEST(CAPIArguments, ids) { - PD_Arguments args = PDArgsCreateNone(); - ASSERT_EQ(kPD_NO_ERROR, PDArgsResize(args, 1)); + paddle_arguments args = paddle_arguments_create_none(); + ASSERT_EQ(kPD_NO_ERROR, paddle_arguments_resize(args, 1)); paddle_ivector ivec; int array[3] = {1, 2, 3}; ivec = paddle_ivector_create(array, 3, true, false); - ASSERT_EQ(kPD_NO_ERROR, PDArgsSetIds(args, 0, ivec)); + ASSERT_EQ(kPD_NO_ERROR, paddle_arguments_set_ids(args, 0, ivec)); paddle_ivector val = paddle_ivector_create_none(); - ASSERT_EQ(kPD_NO_ERROR, PDArgsGetIds(args, 0, val)); + ASSERT_EQ(kPD_NO_ERROR, paddle_arguments_ids(args, 0, val)); ASSERT_EQ(kPD_NO_ERROR, paddle_ivector_destroy(ivec)); ASSERT_EQ(kPD_NO_ERROR, paddle_ivector_destroy(val)); - ASSERT_EQ(kPD_NO_ERROR, PDArgsDestroy(args)); + ASSERT_EQ(kPD_NO_ERROR, paddle_arguments_destroy(args)); } template void testSequenceHelper(T1 setter, T2 getter) { - PD_Arguments args = PDArgsCreateNone(); - ASSERT_EQ(kPD_NO_ERROR, PDArgsResize(args, 1)); + paddle_arguments args = paddle_arguments_create_none(); + ASSERT_EQ(kPD_NO_ERROR, paddle_arguments_resize(args, 1)); paddle_ivector ivec; int array[3] = {1, 2, 3}; @@ -105,11 +105,12 @@ void testSequenceHelper(T1 setter, T2 getter) { ASSERT_EQ(kPD_NO_ERROR, paddle_ivector_destroy(ivec)); ASSERT_EQ(kPD_NO_ERROR, paddle_ivector_destroy(val)); - ASSERT_EQ(kPD_NO_ERROR, PDArgsDestroy(args)); + ASSERT_EQ(kPD_NO_ERROR, paddle_arguments_destroy(args)); } TEST(CAPIArguments, Sequence) { - testSequenceHelper(PDArgsSetSequenceStartPos, PDArgsGetSequenceStartPos); - testSequenceHelper(PDArgsSetSubSequenceStartPos, - PDArgsGetSubSequenceStartPos); + testSequenceHelper(paddle_arguments_set_sequence_start_pos, + paddle_arguments_sequence_start_pos); + testSequenceHelper(paddle_arguments_set_sub_sequence_start_pos, + paddle_arguments_sub_sequence_start_pos); } diff --git a/paddle/capi/tests/test_GradientMachine.cpp b/paddle/capi/tests/test_GradientMachine.cpp index 05a06445c27..c35432288b4 100644 --- a/paddle/capi/tests/test_GradientMachine.cpp +++ b/paddle/capi/tests/test_GradientMachine.cpp @@ -55,10 +55,10 @@ TEST(GradientMachine, testPredict) { PDGradientMachineCreateSharedParam( machine, &buffer[0], (int)buffer.size(), &machineSlave)); std::swap(machineSlave, machine); - PD_Arguments outArgs = PDArgsCreateNone(); + paddle_arguments outArgs = paddle_arguments_create_none(); - PD_Arguments inArgs = PDArgsCreateNone(); - ASSERT_EQ(kPD_NO_ERROR, PDArgsResize(inArgs, 1)); + paddle_arguments inArgs = paddle_arguments_create_none(); + ASSERT_EQ(kPD_NO_ERROR, paddle_arguments_resize(inArgs, 1)); paddle_matrix mat = paddle_matrix_create(1, 100, false); static_assert(std::is_same::value, ""); @@ -67,15 +67,15 @@ TEST(GradientMachine, testPredict) { ASSERT_EQ(kPD_NO_ERROR, paddle_matrix_get_row(mat, 0, &rowPtr)); memcpy(rowPtr, data.data(), data.size() * sizeof(pd_real)); - ASSERT_EQ(kPD_NO_ERROR, PDArgsSetValue(inArgs, 0, mat)); + ASSERT_EQ(kPD_NO_ERROR, paddle_arguments_set_value(inArgs, 0, mat)); ASSERT_EQ(kPD_NO_ERROR, PDGradientMachineForward(machine, inArgs, outArgs, false)); uint64_t sz; - ASSERT_EQ(kPD_NO_ERROR, PDArgsGetSize(outArgs, &sz)); + ASSERT_EQ(kPD_NO_ERROR, paddle_arguments_size(outArgs, &sz)); ASSERT_EQ(1UL, sz); - ASSERT_EQ(kPD_NO_ERROR, PDArgsGetValue(outArgs, 0, mat)); + ASSERT_EQ(kPD_NO_ERROR, paddle_arguments_value(outArgs, 0, mat)); std::vector paddleInArgs; std::vector paddleOutArgs; paddleInArgs.resize(1); @@ -97,8 +97,8 @@ TEST(GradientMachine, testPredict) { } ASSERT_EQ(kPD_NO_ERROR, paddle_matrix_destroy(mat)); - ASSERT_EQ(kPD_NO_ERROR, PDArgsDestroy(inArgs)); - ASSERT_EQ(kPD_NO_ERROR, PDArgsDestroy(outArgs)); + ASSERT_EQ(kPD_NO_ERROR, paddle_arguments_destroy(inArgs)); + ASSERT_EQ(kPD_NO_ERROR, paddle_arguments_destroy(outArgs)); std::swap(machineSlave, machine); ASSERT_EQ(kPD_NO_ERROR, PDGradientMachineDestroy(machineSlave)); ASSERT_EQ(kPD_NO_ERROR, PDGradientMachineDestroy(machine)); -- GitLab From dbb2047e3b4f2c9c85c8b3f112a736e88b137e53 Mon Sep 17 00:00:00 2001 From: "yi.wu" Date: Wed, 22 Mar 2017 20:29:59 +0800 Subject: [PATCH 0035/3256] cluster design --- doc/design/cluster_design.md | 88 ++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 doc/design/cluster_design.md diff --git a/doc/design/cluster_design.md b/doc/design/cluster_design.md new file mode 100644 index 00000000000..fe92ecfb9d2 --- /dev/null +++ b/doc/design/cluster_design.md @@ -0,0 +1,88 @@ +# Paddle大规模分布式训练设计 + +## 概览 + +## 常见的分布式训练架构 + +深度学习分布式训练的架构如图: + + + +为了完成一个深度学习的训练任务,集群中会运行多个trainer和parameter server,集群会把模型的参 +数分布式的存储在多个parameter server上,trainer完成每个mini-batch数据训练之后会把梯度发送 +给parameter server,parameter server将某个分片的模型参数和梯度执行整合和优化。然后trainer +从所有的parameter server下载模型参数并开始下一轮mini-batch的训练。 + +可以看到,可以进一步的优化以下方面: +1. 模型的参数是保存在parameter server进程的内存中的。在一个训练任务过程中任意一台 + parameter server不能异常退出,否则训练不能继续执行 +1. 不能在一个训练任务中动态的增加Trainer个数或parameter个数 +1. parameter server保存模型参数考虑多个备份防止单点故障 +1. 为了使训练任务至少可以抵御“单点故障”(任意时刻只可能同时有一台服务器故障),模型参数的更新和分发 + 需要保证原子性操作或满足事务性操作 +1. 可以同时调度大量的训练任务和使用模型的应用在一个集群上 +1. 支持训练任务的前置任务和后置任务,支持训练任务的定时调度和对在线流式数据的处理 + +## 模型参数数据备份 +为了实现parameter server集群可以容忍单点故障,必须将每个模型参数的分片在集群中存储多个副本。虽然 +也可以考虑使用校验和的技术减少副本大小,但为了整体系统的简单可靠,优先选择使用副本的方式。 + + + +上图显示了在2台parameter server中实现每个模型参数的分片均保存两个副本的状态。parameter 负责存储 +所有参数分片副本并在etcd中同步每个副本的状态。每个分片的多个副本中同时只有一个处于"master"状态, +处于"master"状态的副本是当前活动的副本。当一台parameter server故障时,集群中剩下的parameter server +会重新选举出新的"master"副本并继续提供服务。 + +用户在启动parameter server是可以指定副本的个数(>=1),副本越多容灾能力越强,越少性能越好。但通常不会 +使用>3个的副本配置。 + +etcd中数据存储格式为: +1. pserver集群状态`[CLUSTER_CHROOT]/pserver_cluster_status` + ```json + { + "cluster_status": "OK|UNHEALTHY|UNKNOWN" + "reason": "", + "nodes": [0,1,2,3] + } + ``` + +1. 每个pserver的状态: [CLUSTER_CHROOT]/pservers/[pserverid] + ```json + { + "id": 0, + "instance": "pserver1", + "status": "up", + "start_time": 1490184573.25, + "sync": true, + } + ``` +1. mini-batch计数器,记录此id对应的parameter server正在执行的mini batch id + [CLUSTER_CHROOT]/pservers/[pserverid]/mini-batch-id +1. parameter分片信息: [CLUSTER_CHROOT]/pshards/[shardid]/[replicaid] + 比如上图显示的分片将生成下面的4个etcd路径: + ```bash + /pshards/0/0 + /pshards/0/1 + /pshards/1/0 + /pshards/1/1 + ``` + 每个replica的信息如下: + ```json + { + "id": 0, + "shardid": 0, + "created": 1490184573.25, + "modified": 1490184573.25, + "status": "master", # indicates the replica is in use + } + ``` + +## 数据一致性 +存在多个副本数据的情况下就需要考虑,多个副本之间的数据一致性。如果使用数据强一致性,则在故障恢复时 +可以获得一个完整的数据集,但每次更新模型参数的性能会下降,因为需要保证多个副本都完全更新之后才算更新 +成功。如果使用异步同步(最终一致性),则在重新选举"master"副本时,可能得到的副本并没有完成数据同步。 + +## 故障恢复 + +## 动态扩容/缩容 -- GitLab From 29b00fadf15a9091bf943cb8a556bb185e14d2f4 Mon Sep 17 00:00:00 2001 From: "yi.wu" Date: Thu, 23 Mar 2017 11:32:44 +0800 Subject: [PATCH 0036/3256] cluster design and thoughts --- doc/design/cluster_design.md | 85 +++++++++++++++++++++++-- doc/design/images/arch.png | Bin 0 -> 165032 bytes doc/design/images/less_trainer.png | Bin 0 -> 61124 bytes doc/design/images/more_trainer.png | Bin 0 -> 32517 bytes doc/design/images/replica.png | Bin 0 -> 59597 bytes doc/design/images/trainer.graffle | Bin 0 -> 4895 bytes doc/design/images/trainer.png | Bin 0 -> 119703 bytes doc/design/images/trainer_data.png | Bin 0 -> 138135 bytes doc/design/images/two_phase_commit.png | Bin 0 -> 49120 bytes 9 files changed, 79 insertions(+), 6 deletions(-) create mode 100644 doc/design/images/arch.png create mode 100644 doc/design/images/less_trainer.png create mode 100644 doc/design/images/more_trainer.png create mode 100644 doc/design/images/replica.png create mode 100644 doc/design/images/trainer.graffle create mode 100644 doc/design/images/trainer.png create mode 100644 doc/design/images/trainer_data.png create mode 100644 doc/design/images/two_phase_commit.png diff --git a/doc/design/cluster_design.md b/doc/design/cluster_design.md index fe92ecfb9d2..fbf25a73f9d 100644 --- a/doc/design/cluster_design.md +++ b/doc/design/cluster_design.md @@ -1,12 +1,13 @@ # Paddle大规模分布式训练设计 ## 概览 +参考[这里](https://github.com/PaddlePaddle/Paddle/pull/1620/files) -## 常见的分布式训练架构 +## 分布式训练架构 -深度学习分布式训练的架构如图: +常见的深度学习分布式训练的架构如图: - + 为了完成一个深度学习的训练任务,集群中会运行多个trainer和parameter server,集群会把模型的参 数分布式的存储在多个parameter server上,trainer完成每个mini-batch数据训练之后会把梯度发送 @@ -27,7 +28,7 @@ 为了实现parameter server集群可以容忍单点故障,必须将每个模型参数的分片在集群中存储多个副本。虽然 也可以考虑使用校验和的技术减少副本大小,但为了整体系统的简单可靠,优先选择使用副本的方式。 - + 上图显示了在2台parameter server中实现每个模型参数的分片均保存两个副本的状态。parameter 负责存储 所有参数分片副本并在etcd中同步每个副本的状态。每个分片的多个副本中同时只有一个处于"master"状态, @@ -79,10 +80,82 @@ etcd中数据存储格式为: ``` ## 数据一致性 -存在多个副本数据的情况下就需要考虑,多个副本之间的数据一致性。如果使用数据强一致性,则在故障恢复时 -可以获得一个完整的数据集,但每次更新模型参数的性能会下降,因为需要保证多个副本都完全更新之后才算更新 +存在多个副本数据的情况下就需要考虑,多个副本之间的数据一致性。如果使用数据强一致性(例如paxos/raft或两段式提交), +则在故障恢复时可以获得一个完整的数据集,但每次更新模型参数的性能会下降,因为需要保证多个副本都完全更新之后才算更新 成功。如果使用异步同步(最终一致性),则在重新选举"master"副本时,可能得到的副本并没有完成数据同步。 +本文档讨论使用两阶段提交实现模型副本数据的更新。 +* 每个副本通常由多个parameter block组成,多个block之间可以并发更新,但更新同一个block需要保证顺序性。 +* 每次需要更新一个block的时候,trainer首先向存放"master"副本的服务器提交“准备更新”请求,"master"副本检查其他副本的状态并创建一个更新事务,然后返回OK。 +* trainer再向"master"发送变化部分的梯度数据和这份数据的id,然后"master"并发的更新本地和其他副本的模型数据,更新成功返回OK,如果有更新失败的节点,则执行"rollback",退回到更新前状态并返回错误代码。 + + + +## 模型数据检查点(Checkpointing) +模型数据检查点,可以在磁盘上保存一份存储在parameter server内存中的模型数据的完整镜像。在一个不可中断并缺少备份的训练任务中,可以通过阶段性的在每个parameter server的本地磁盘保存检查点快照达到容灾的目的,比如每个pass保存一次快照。在出现单点故障时,只需要恢复这台节点,或者将这台节点迁移到另一个节点并启动即可恢复训练任务。 + +## 训练数据的存储和分发 +生产环境中的训练数据集通常体积很大,并被存储在诸如Hadoop HDFS, Ceph, AWS S3之类的分布式存储之上。这些分布式存储服务通常会把数据切割成多个分片分布式的存储在多个节点之上,而多个trainer通常也需要预先完成文件的切割。但通常的方法是从HDFS上将数据拷贝到训练集群,然后切割到多个trainer服务器上,如图(Mount/Copy): + + + +考虑到HDFS实际上已经完成了数据切割的任务,而且如果存在前置的数据预处理任务(Map-Reduce或Spark SQL),这些任务的输出也都存放于HDFS之上,则trainer可以直接调用HDFS LowLevel API,从元数据节点获得每个数据分片存储的位置,直接获得分片。 + +***注:每个数据分片保存多个mini_batch*** + +进一步优化,trainer可以寻找在物理网络拓扑中离自己最近的一个分片副本获取数据。 + +trainer和训练数据分片的均衡: +* 当trainer >= 数据分片: + trainer个数和数据分片个数相同时,可以获得最高的吞吐量。当trainer个数再大于分片数量时,必然有Trainer获取不到数据分片,处于等待状态。但对整体任务运行没有影响,等待的trainer也会消耗很小的资源。 + + + +* 当trainer < 数据分片 + 每个trainer负责多个数据分片,轮询方式完成一个分片训练之后开始下一个分片。 + + + ## 故障恢复 +在通用集群上运行的应用和任务,通常需要有能够自动伸缩的能力,这样在在线集群进行扩容时,可以适当的减小训练任务的资源(进程数/并发数),而不需要直接停止训练任务,修改参数后重新提交任务。 + +然而对于常见的在线服务(比如Web服务,RPC服务等),是可以“无状态”伸缩的,即扩容和缩容只需要增删对应的节点,集群能力就可以自动伸缩,Web服务的每个节点不会维护自身的状态变化和自身的数据,这些数据通常会借由外部的存储或服务完成,如MySQL,Redis等。而对于训练任务来说,每个parameter server都需要保存状态(mini-batch id)和数据(parameters),在增删节点的时候都会涉及到数据重新分布(re-sharding)和处理数据同步的问题。 + +用户只要根据实际训练任务场景,配置parameter server和trainer的初始节点个数,最大节点个数和最小节点个数,模型副本个数,是否开启检查点等配置项,即可配置并启动一个可以容灾的训练集群。具体的过程如下: + +1. 配置parameter server和trainer的初始节点个数、最大节点个数、最小节点个数、模型副本个数、是否开启检查点等配置以及训练任务相关配置。 +1. 启动parameter server和trainer,每个实例会在etcd中注册一个临时节点。这样当某个parameter server或trainer失效是,etcd中的节点会反应这个示例的状态。每个parameter server在所有的parameter server上会使用etcd watcher监听节点的变化状态,已完成后续处理。 +1. parameter server如果开启了检查点,则先判断是否已经存在本地检查点快照数据,如果有,则从快照数据中加载状态和数据,并开始提供服务。如果没有则执行初始化启动步骤。 +1. 提交用户定义的深度学习网络(topology),并根据网络中参数完成pre-sharding,将参数block哈希到512或1024个slot中,每个slot即为一个参数分片。根据实际存在的parameter server个数,将slot和parameter server完成对应的映射,使slot可以平均存储在这些parameter server上。 +1. parameter server开始监听端口并接收数据。每次接收到数据,都使用两段式提交方式同步到所有的副本。如果需要存储检查点,则在同步所有副本之后,保存检查点。 +1. 当故障发生后,parameter server会收到etcd发送的watcher信号,此时将暂停trainer的训练(此时要检查最后一次更新的mini_batch id,如果处于不同步状态,需要执行rollback),执行re-sharding步骤: + 1. 根据现有存活的parameter server的个数,找出丢失master分片的参数slot,重新标记成为master,然后确保集群中一个分片只选择出一个master。 + 2. 重新分布每个slot,将slot平均分布在所有parameter server上,保证负载均衡。 + 3. 重新开始trainer的训练。 + +新增节点的方法类似,此处不再赘述。 ## 动态扩容/缩容 +虽然故障恢复可以提供任意时刻的节点新增和删除仍然可以保证任务正常运行,但通常这样是比较暴力的。为了能graceful的关闭多个节点,parameter server需要提供对应的API接口: + +```python + def resize(n): + # do resize + return success +``` + +接口完成先发送信号暂停训练任务,re-shard数据分片,然后重新开启训练。这样可以避免程序bug导致的数据不同步问题出现。 + +## 性能考虑 +由于每次数据提交都需要完成分片同步,而且在每个pass之后执行检查点保存,必然会带来parameter server性能下降。可以根据不同的场景配置不同的容灾方案。 + +* 测试任务/极短训练任务:如果训练任务在几十分钟或小时级别可以运行完成,可以考虑不开启副本也不开启检查点。 +* 短期训练任务/测试任务:训练任务运行时间如果在数小时或数天范围,可以考虑只使用一个副本(每个slot只保存一份),并开启检查点。在这个时长内出现不可恢复的硬件故障的概率极低。 +* 大型训练任务:训练时间以周或月为单位。建议开启多个副本和检查点。这样可以在任意一个pass停止任务,并重新从这个pass开始训练。或者在通用集群运行时,可以考虑动态扩容和缩容。 + +## 实现考虑 +由于两阶段提交和数据备份同步、选举部分实现比较复杂,可以考虑使用一些开源库函数,比如2pc,raft库等,后期在优化过程中逐步替换。 + + +## TODO: +All-Reduce和Ring的不同设计考虑 diff --git a/doc/design/images/arch.png b/doc/design/images/arch.png new file mode 100644 index 0000000000000000000000000000000000000000..659e340388f63f689f2a0112957f8c9dacf05b20 GIT binary patch literal 165032 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

J8xt_JLB^gDZ59 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%0ABU&`~IFfgZU4)~zV% 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 literal 0 HcmV?d00001 diff --git a/doc/design/images/less_trainer.png b/doc/design/images/less_trainer.png new file mode 100644 index 0000000000000000000000000000000000000000..430f8da83d90b344c0b8cd481d801b9b604ae735 GIT binary patch literal 61124 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ξTV~UbudW_yU={*W+BQ7)J4yU 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.Ksl035 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 literal 0 HcmV?d00001 diff --git a/doc/design/images/replica.png b/doc/design/images/replica.png new file mode 100644 index 0000000000000000000000000000000000000000..74cca9f9b6262dfc0da18696fe0180bbb46bb577 GIT binary patch literal 59597 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 Xc$AaDbHPU#@IQH(o6>nw#=idp!+67i literal 0 HcmV?d00001 diff --git a/doc/design/images/trainer.graffle b/doc/design/images/trainer.graffle new file mode 100644 index 0000000000000000000000000000000000000000..37b3f0b469c14a1eefcd644de16884f87a282841 GIT binary patch literal 4895 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 literal 0 HcmV?d00001 diff --git a/doc/design/images/trainer_data.png b/doc/design/images/trainer_data.png new file mode 100644 index 0000000000000000000000000000000000000000..27ec4c98c4afd5f60228baeeefb8dd0d2ef9ac67 GIT binary patch literal 138135 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 z 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

  • |Q{Vz`&tDs3t{9vLTploku} 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 literal 0 HcmV?d00001 diff --git a/doc/design/images/two_phase_commit.png b/doc/design/images/two_phase_commit.png new file mode 100644 index 0000000000000000000000000000000000000000..ef6f7317bd440cc7d9fe08fcbbf2b7a542f99049 GIT binary patch literal 49120 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 literal 0 HcmV?d00001 -- GitLab From c5eac0ab2d54cded942f555f352e236fba0acfb9 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Fri, 24 Mar 2017 15:38:12 +0800 Subject: [PATCH 0037/3256] Rename API --- paddle/capi/Arguments.cpp | 44 +--- paddle/capi/Main.cpp | 2 +- paddle/capi/PaddleCAPI.h | 215 +----------------- paddle/capi/PaddleCAPIPrivate.h | 16 ++ paddle/capi/arguments.h | 130 +++++++++++ paddle/capi/error.h | 14 ++ ...adientMachine.cpp => gradient_machine.cpp} | 28 +-- paddle/capi/gradient_machine.h | 74 ++++++ paddle/capi/main.h | 19 ++ paddle/capi/tests/test_Arguments.cpp | 20 +- paddle/capi/tests/test_GradientMachine.cpp | 18 +- 11 files changed, 310 insertions(+), 270 deletions(-) create mode 100644 paddle/capi/arguments.h rename paddle/capi/{GradientMachine.cpp => gradient_machine.cpp} (76%) create mode 100644 paddle/capi/gradient_machine.h create mode 100644 paddle/capi/main.h diff --git a/paddle/capi/Arguments.cpp b/paddle/capi/Arguments.cpp index 792b8c58a9a..d9b207af705 100644 --- a/paddle/capi/Arguments.cpp +++ b/paddle/capi/Arguments.cpp @@ -12,8 +12,8 @@ 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 "PaddleCAPI.h" #include "PaddleCAPIPrivate.h" +#include "arguments.h" using paddle::capi::cast; @@ -92,50 +92,26 @@ paddle_error paddle_arguments_set_ids(paddle_arguments args, paddle_error paddle_arguments_set_sequence_start_pos(paddle_arguments args, uint64_t ID, + uint32_t nestedLevel, paddle_ivector seqPos) { if (args == nullptr || seqPos == nullptr) return kPD_NULLPTR; auto iv = paddle::capi::cast(seqPos); if (iv->vec == nullptr) return kPD_NULLPTR; auto a = castArg(args); - if (ID >= a->args.size()) return kPD_OUT_OF_RANGE; - a->args[ID].sequenceStartPositions = - std::make_shared(iv->vec); - return kPD_NO_ERROR; -} - -paddle_error paddle_arguments_set_sub_sequence_start_pos( - paddle_arguments args, uint64_t ID, paddle_ivector subSeqPos) { - if (args == nullptr || subSeqPos == nullptr) return kPD_NULLPTR; - auto iv = paddle::capi::cast(subSeqPos); - if (iv->vec == nullptr) return kPD_NULLPTR; - auto a = castArg(args); - if (ID >= a->args.size()) return kPD_OUT_OF_RANGE; - a->args[ID].subSequenceStartPositions = - std::make_shared(iv->vec); - return kPD_NO_ERROR; + return a->accessSeqPos(ID, nestedLevel, [&iv](paddle::ICpuGpuVectorPtr& ptr) { + ptr = std::make_shared(iv->vec); + }); } paddle_error paddle_arguments_sequence_start_pos(paddle_arguments args, uint64_t ID, + uint32_t nestedLevel, paddle_ivector seqPos) { if (args == nullptr || seqPos == nullptr) return kPD_NULLPTR; - auto iv = castIVec(seqPos); - auto a = castArg(args); - if (ID >= a->args.size()) return kPD_OUT_OF_RANGE; - paddle::Argument& arg = a->args[ID]; - iv->vec = arg.sequenceStartPositions->getMutableVector(false); - return kPD_NO_ERROR; -} - -paddle_error paddle_arguments_sub_sequence_start_pos(paddle_arguments args, - uint64_t ID, - paddle_ivector subSeqPos) { - if (args == nullptr || subSeqPos == nullptr) return kPD_NULLPTR; - auto iv = castIVec(subSeqPos); + auto iv = paddle::capi::cast(seqPos); auto a = castArg(args); - if (ID >= a->args.size()) return kPD_OUT_OF_RANGE; - paddle::Argument& arg = a->args[ID]; - iv->vec = arg.subSequenceStartPositions->getMutableVector(false); - return kPD_NO_ERROR; + return a->accessSeqPos(ID, nestedLevel, [&iv](paddle::ICpuGpuVectorPtr& ptr) { + iv->vec = ptr->getMutableVector(false); + }); } } diff --git a/paddle/capi/Main.cpp b/paddle/capi/Main.cpp index 5051dff845a..7604945de7d 100644 --- a/paddle/capi/Main.cpp +++ b/paddle/capi/Main.cpp @@ -29,7 +29,7 @@ static void initPaddle(int argc, char** argv) { } extern "C" { -paddle_error PDInit(int argc, char** argv) { +paddle_error paddle_init(int argc, char** argv) { std::vector realArgv; realArgv.reserve(argc + 1); realArgv.push_back(strdup("")); diff --git a/paddle/capi/PaddleCAPI.h b/paddle/capi/PaddleCAPI.h index eea7c3bd05f..1e275c5c1fa 100644 --- a/paddle/capi/PaddleCAPI.h +++ b/paddle/capi/PaddleCAPI.h @@ -14,16 +14,6 @@ limitations under the License. */ #ifndef PADDLECAPI_H_ #define PADDLECAPI_H_ -#include -#include -#include "config.h" -#include "error.h" -#include "matrix.h" -#include "vector.h" - -#ifdef __cplusplus -extern "C" { -#endif /** * Paddle C API. It will replace SWIG as Multiple Language API for model @@ -31,203 +21,12 @@ extern "C" { * * NOTE: This is an experimental API, it could be changed. */ - -/** - * Arguments functions. Each argument means layer output. Arguments means a - * array of arguemnt. - */ -typedef void* paddle_arguments; - -/** - * @brief paddle_arguments_create_none Create a array of arguments, which size - * is zero. - * @return Arguemnts - */ -PD_API paddle_arguments paddle_arguments_create_none(); - -/** - * @brief paddle_arguments_destroy Destroy the arguments - * @param args arguments to destroy - * @return paddle_error - */ -PD_API paddle_error paddle_arguments_destroy(paddle_arguments args); - -/** - * @brief PDArgsGetSize Get size of arguments array - * @param [in] args arguments array - * @param [out] size array size - * @return paddle_error - */ -PD_API paddle_error paddle_arguments_size(paddle_arguments args, - uint64_t* size); - -/** - * @brief PDArgsResize Resize a arguments array. - * @param args arguments array. - * @param size target size of array - * @return paddle_error - */ -PD_API paddle_error paddle_arguments_resize(paddle_arguments args, - uint64_t size); - -/** - * @brief PDArgsSetValue Set value matrix of one argument in array, which index - * is `ID`. - * @param args arguments array - * @param ID array index - * @param mat matrix pointer - * @return paddle_error - */ -PD_API paddle_error paddle_arguments_set_value(paddle_arguments args, - uint64_t ID, - paddle_matrix mat); - -/** - * @brief PDArgsGetValue Get value matrix of one argument in array, which index - * is `ID`. - * @param [in] args arguments array - * @param [in] ID array index - * @param [out] mat matrix pointer - * @return paddle_error - */ -PD_API paddle_error paddle_arguments_value(paddle_arguments args, - uint64_t ID, - paddle_matrix mat); - -/** - * @brief PDArgsGetIds Get the integer vector of one argument in array, which - * index is `ID`. - * @param args arguments array - * @param ID array index - * @param ids integer vector pointer - * @return paddle_error - */ -PD_API paddle_error paddle_arguments_ids(paddle_arguments args, - uint64_t ID, - paddle_ivector ids); - -/** - * @brief PDArgsSetIds Set the integer vector of one argument in array, which - * index is `ID`. - * @param [in] args arguments array - * @param [in] ID array index - * @param [out] ids integer vector pointer - * @return paddle_error - */ -PD_API paddle_error paddle_arguments_set_ids(paddle_arguments args, - uint64_t ID, - paddle_ivector ids); - -/** - * @brief PDArgsSetSequenceStartPos Set sequence start position vector of one - * argument in array, which index is `ID`. - * @param args arguments array - * @param ID array index - * @param seqPos sequence position array. - * @return paddle_error - */ -PD_API paddle_error paddle_arguments_set_sequence_start_pos( - paddle_arguments args, uint64_t ID, paddle_ivector seqPos); -/** - * @brief PDArgsGetSequenceStartPos Get sequence start position vector of one - * argument in array, which index is `ID`. - * @param [in] args arguments array - * @param [in] ID array index - * @param [out] seqPos sequence position array - * @return paddle_error - */ -PD_API paddle_error paddle_arguments_sequence_start_pos(paddle_arguments args, - uint64_t ID, - paddle_ivector seqPos); - -/** - * @brief PDArgsSetSubSequenceStartPos Set sub-sequence start position vector of - * one argument in array, which index is `ID`. - * @param args arguments array - * @param ID array index - * @param subSeqPos sub-sequence start position array. - * @return paddle_error - */ -PD_API paddle_error paddle_arguments_set_sub_sequence_start_pos( - paddle_arguments args, uint64_t ID, paddle_ivector subSeqPos); - -/** - * @brief PDArgsGetSubSequenceStartPos Get sub-sequence start position vector of - * one argument in array, which index is `ID`. - * @param args arguments array - * @param ID array index - * @param subSeqPos sub-sequence start position array - * @return paddle_error - */ -PD_API paddle_error paddle_arguments_sub_sequence_start_pos( - paddle_arguments args, uint64_t ID, paddle_ivector subSeqPos); -/** - * @brief GradientMachine means a neural network. - */ -typedef void* PD_GradientMachine; - -/** - * @brief PDGradientMachineCreateForPredict Create a gradient machine used for - * model inference. - * @param [out] machine that used for model inference. - * @param [in] modelConfigProtobuf - * @param [in] size - * @return paddle_error - */ -PD_API paddle_error PDGradientMachineCreateForPredict( - PD_GradientMachine* machine, void* modelConfigProtobuf, int size); - -/** - * @brief PDGradientMachineLoadParameterFromDisk Load parameter from disk. - * @param machine Gradient Machine. - * @param path local directory path. - * @return paddle_error - */ -PD_API paddle_error PDGradientMachineLoadParameterFromDisk( - PD_GradientMachine machine, const char* path); - -/** - * @brief PDGradientMachineForward Forward a gradient machine - * @param machine Gradient machine - * @param inArgs input arguments - * @param outArgs output arguments - * @param isTrain is train or not - * @return paddle_error - */ -PD_API paddle_error PDGradientMachineForward(PD_GradientMachine machine, - paddle_arguments inArgs, - paddle_arguments outArgs, - bool isTrain); - -/** - * @brief PDGradientMachineCreateSharedParam Create a gradient machine, which - * parameters are shared from another gradient machine. - * @param [in] origin gradient machine - * @param [in] modelConfigProtobuf model config protobuf - * @param [in] size of model config buffer. - * @param [out] slave gradient machine, the output value. - * @return paddle_error - */ -PD_API paddle_error -PDGradientMachineCreateSharedParam(PD_GradientMachine origin, - void* modelConfigProtobuf, - int size, - PD_GradientMachine* slave); - -/** - * @brief PDGradientMachineDestroy Destroy a gradient machine - * @param machine that need to destroy - * @return paddle_error - */ -PD_API paddle_error PDGradientMachineDestroy(PD_GradientMachine machine); - -/** - * Initialize Paddle. - */ -PD_API paddle_error PDInit(int argc, char** argv); - -#ifdef __cplusplus -} -#endif +#include "arguments.h" +#include "config.h" +#include "error.h" +#include "gradient_machine.h" +#include "main.h" +#include "matrix.h" +#include "vector.h" #endif // PADDLECAPI_H_ diff --git a/paddle/capi/PaddleCAPIPrivate.h b/paddle/capi/PaddleCAPIPrivate.h index bb8baea4e1c..072e9a37a63 100644 --- a/paddle/capi/PaddleCAPIPrivate.h +++ b/paddle/capi/PaddleCAPIPrivate.h @@ -49,6 +49,22 @@ struct CArguments { std::vector args; CArguments() : type(kARGUMENTS) {} + + template + paddle_error accessSeqPos(uint64_t ID, uint32_t nestedLevel, T callback) { + if (ID >= args.size()) return kPD_OUT_OF_RANGE; + switch (nestedLevel) { + case 0: + callback(args[ID].sequenceStartPositions); + break; + case 1: + callback(args[ID].subSequenceStartPositions); + break; + default: + return kPD_OUT_OF_RANGE; + } + return kPD_NO_ERROR; + } }; struct CGradientMachine { diff --git a/paddle/capi/arguments.h b/paddle/capi/arguments.h new file mode 100644 index 00000000000..3f3594b282b --- /dev/null +++ b/paddle/capi/arguments.h @@ -0,0 +1,130 @@ +#ifndef __PADDLE_CAPI_ARGUMENTS_H__ +#define __PADDLE_CAPI_ARGUMENTS_H__ + +#include +#include "config.h" +#include "error.h" +#include "matrix.h" +#include "vector.h" + +/** + * Arguments functions. Each argument means layer output. Arguments means a + * array of arguemnt. + */ +typedef void* paddle_arguments; + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief paddle_arguments_create_none Create a array of arguments, which size + * is zero. + * @return Arguemnts + */ +PD_API paddle_arguments paddle_arguments_create_none(); + +/** + * @brief paddle_arguments_destroy Destroy the arguments + * @param args arguments to destroy + * @return paddle_error + */ +PD_API paddle_error paddle_arguments_destroy(paddle_arguments args); + +/** + * @brief PDArgsGetSize Get size of arguments array + * @param [in] args arguments array + * @param [out] size array size + * @return paddle_error + */ +PD_API paddle_error paddle_arguments_size(paddle_arguments args, + uint64_t* size); + +/** + * @brief PDArgsResize Resize a arguments array. + * @param args arguments array. + * @param size target size of array + * @return paddle_error + */ +PD_API paddle_error paddle_arguments_resize(paddle_arguments args, + uint64_t size); + +/** + * @brief PDArgsSetValue Set value matrix of one argument in array, which index + * is `ID`. + * @param args arguments array + * @param ID array index + * @param mat matrix pointer + * @return paddle_error + */ +PD_API paddle_error paddle_arguments_set_value(paddle_arguments args, + uint64_t ID, + paddle_matrix mat); + +/** + * @brief PDArgsGetValue Get value matrix of one argument in array, which index + * is `ID`. + * @param [in] args arguments array + * @param [in] ID array index + * @param [out] mat matrix pointer + * @return paddle_error + */ +PD_API paddle_error paddle_arguments_value(paddle_arguments args, + uint64_t ID, + paddle_matrix mat); + +/** + * @brief PDArgsGetIds Get the integer vector of one argument in array, which + * index is `ID`. + * @param args arguments array + * @param ID array index + * @param ids integer vector pointer + * @return paddle_error + */ +PD_API paddle_error paddle_arguments_ids(paddle_arguments args, + uint64_t ID, + paddle_ivector ids); + +/** + * @brief PDArgsSetIds Set the integer vector of one argument in array, which + * index is `ID`. + * @param [in] args arguments array + * @param [in] ID array index + * @param [out] ids integer vector pointer + * @return paddle_error + */ +PD_API paddle_error paddle_arguments_set_ids(paddle_arguments args, + uint64_t ID, + paddle_ivector ids); + +/** + * @brief PDArgsSetSequenceStartPos Set sequence start position vector of one + * argument in array, which index is `ID`. + * @param args arguments array + * @param ID array index + * @param seqPos sequence position array. + * @return paddle_error + */ +PD_API paddle_error +paddle_arguments_set_sequence_start_pos(paddle_arguments args, + uint64_t ID, + uint32_t nestedLevel, + paddle_ivector seqPos); +/** + * @brief PDArgsGetSequenceStartPos Get sequence start position vector of one + * argument in array, which index is `ID`. + * @param [in] args arguments array + * @param [in] ID array index + * @param [out] seqPos sequence position array + * @return paddle_error + */ +PD_API paddle_error paddle_arguments_sequence_start_pos(paddle_arguments args, + uint64_t ID, + uint32_t nestedLevel, + paddle_ivector seqPos); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/paddle/capi/error.h b/paddle/capi/error.h index 8dbb6d95487..6a5907b869f 100644 --- a/paddle/capi/error.h +++ b/paddle/capi/error.h @@ -1,3 +1,17 @@ +/* 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. */ + #ifndef __PADDLE_CAPI_ERROR_H__ #define __PADDLE_CAPI_ERROR_H__ diff --git a/paddle/capi/GradientMachine.cpp b/paddle/capi/gradient_machine.cpp similarity index 76% rename from paddle/capi/GradientMachine.cpp rename to paddle/capi/gradient_machine.cpp index 9f0ffd6599f..ab8e747ae95 100644 --- a/paddle/capi/GradientMachine.cpp +++ b/paddle/capi/gradient_machine.cpp @@ -38,9 +38,8 @@ NeuralNetwork* newCustomNerualNetwork(const std::string& name, } // namespace paddle extern "C" { -paddle_error PDGradientMachineCreateForPredict(PD_GradientMachine* machine, - void* modelConfigProtobuf, - int size) { +paddle_error paddle_gradient_machine_create_for_inference( + paddle_gradient_machine* machine, void* modelConfigProtobuf, int size) { if (modelConfigProtobuf == nullptr) return kPD_NULLPTR; paddle::ModelConfig config; if (!config.ParseFromArray(modelConfigProtobuf, size) || @@ -55,13 +54,13 @@ paddle_error PDGradientMachineCreateForPredict(PD_GradientMachine* machine, return kPD_NO_ERROR; } -paddle_error PDGradientMachineDestroy(PD_GradientMachine machine) { +paddle_error paddle_gradient_machine_destroy(paddle_gradient_machine machine) { delete cast(machine); return kPD_NO_ERROR; } -paddle_error PDGradientMachineLoadParameterFromDisk(PD_GradientMachine machine, - const char* path) { +paddle_error paddle_gradient_machine_load_parameter_from_disk( + paddle_gradient_machine machine, const char* path) { auto m = cast(machine); if (m == nullptr || path == nullptr || m->machine == nullptr) return kPD_NULLPTR; @@ -69,10 +68,10 @@ paddle_error PDGradientMachineLoadParameterFromDisk(PD_GradientMachine machine, return kPD_NO_ERROR; } -paddle_error PDGradientMachineForward(PD_GradientMachine machine, - paddle_arguments inArgs, - paddle_arguments outArgs, - bool isTrain) { +paddle_error paddle_gradient_machine_forward(paddle_gradient_machine machine, + paddle_arguments inArgs, + paddle_arguments outArgs, + bool isTrain) { auto m = cast(machine); auto in = paddle::capi::cast(inArgs); auto out = paddle::capi::cast(outArgs); @@ -83,10 +82,11 @@ paddle_error PDGradientMachineForward(PD_GradientMachine machine, return kPD_NO_ERROR; } -paddle_error PDGradientMachineCreateSharedParam(PD_GradientMachine origin, - void* modelConfigProtobuf, - int size, - PD_GradientMachine* slave) { +paddle_error paddle_gradient_machine_create_shared_param( + paddle_gradient_machine origin, + void* modelConfigProtobuf, + int size, + paddle_gradient_machine* slave) { auto o = cast(origin); if (origin == nullptr || slave == nullptr || o->machine == nullptr) { return kPD_NULLPTR; diff --git a/paddle/capi/gradient_machine.h b/paddle/capi/gradient_machine.h new file mode 100644 index 00000000000..f3cfd67c220 --- /dev/null +++ b/paddle/capi/gradient_machine.h @@ -0,0 +1,74 @@ +#ifndef __PADDLE_CAPI_GRADIENT_MACHINE_H__ +#define __PADDLE_CAPI_GRADIENT_MACHINE_H__ +#include "arguments.h" +#include "config.h" +#include "error.h" + +#ifdef __cplusplus +extern "C" { +#endif +/** + * @brief GradientMachine means a neural network. + */ +typedef void* paddle_gradient_machine; + +/** + * @brief Create a gradient machine used for model inference. + * @param [out] machine that used for model inference. + * @param [in] modelConfigProtobuf + * @param [in] size + * @return paddle_error + */ +PD_API paddle_error paddle_gradient_machine_create_for_inference( + paddle_gradient_machine* machine, void* modelConfigProtobuf, int size); + +/** + * @brief Load parameter from disk. + * @param machine Gradient Machine. + * @param path local directory path. + * @return paddle_error + */ +PD_API paddle_error paddle_gradient_machine_load_parameter_from_disk( + paddle_gradient_machine machine, const char* path); + +/** + * @brief Forward a gradient machine + * @param machine Gradient machine + * @param inArgs input arguments + * @param outArgs output arguments + * @param isTrain is train or not + * @return paddle_error + */ +PD_API paddle_error +paddle_gradient_machine_forward(paddle_gradient_machine machine, + paddle_arguments inArgs, + paddle_arguments outArgs, + bool isTrain); + +/** + * @brief Create a gradient machine, which parameters are shared from another + * gradient machine. + * @param [in] origin gradient machine + * @param [in] modelConfigProtobuf model config protobuf + * @param [in] size of model config buffer. + * @param [out] slave gradient machine, the output value. + * @return paddle_error + */ +PD_API paddle_error +paddle_gradient_machine_create_shared_param(paddle_gradient_machine origin, + void* modelConfigProtobuf, + int size, + paddle_gradient_machine* slave); + +/** + * @brief Destroy a gradient machine + * @param machine that need to destroy + * @return paddle_error + */ +PD_API paddle_error +paddle_gradient_machine_destroy(paddle_gradient_machine machine); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/paddle/capi/main.h b/paddle/capi/main.h new file mode 100644 index 00000000000..814c8cce244 --- /dev/null +++ b/paddle/capi/main.h @@ -0,0 +1,19 @@ +#ifndef __PADDLE_CAPI_MAIN_H__ +#define __PADDLE_CAPI_MAIN_H__ +#include "config.h" +#include "error.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Initialize Paddle. + */ +PD_API paddle_error paddle_init(int argc, char** argv); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/paddle/capi/tests/test_Arguments.cpp b/paddle/capi/tests/test_Arguments.cpp index b445b396f3c..60fa57517fb 100644 --- a/paddle/capi/tests/test_Arguments.cpp +++ b/paddle/capi/tests/test_Arguments.cpp @@ -12,6 +12,7 @@ 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 "PaddleCAPI.h" #include "gtest/gtest.h" #include "paddle/utils/ThreadLocal.h" @@ -109,8 +110,19 @@ void testSequenceHelper(T1 setter, T2 getter) { } TEST(CAPIArguments, Sequence) { - testSequenceHelper(paddle_arguments_set_sequence_start_pos, - paddle_arguments_sequence_start_pos); - testSequenceHelper(paddle_arguments_set_sub_sequence_start_pos, - paddle_arguments_sub_sequence_start_pos); + auto testSequence = [](uint32_t nestedLevel) { + testSequenceHelper(std::bind(paddle_arguments_set_sequence_start_pos, + std::placeholders::_1, + std::placeholders::_2, + nestedLevel, + std::placeholders::_3), + std::bind(paddle_arguments_sequence_start_pos, + std::placeholders::_1, + std::placeholders::_2, + nestedLevel, + std::placeholders::_3)); + }; + for (uint32_t i = 0; i < 2; ++i) { // test seq and sub-seq. + testSequence(i); + } } diff --git a/paddle/capi/tests/test_GradientMachine.cpp b/paddle/capi/tests/test_GradientMachine.cpp index c35432288b4..3e8ba8e9d8f 100644 --- a/paddle/capi/tests/test_GradientMachine.cpp +++ b/paddle/capi/tests/test_GradientMachine.cpp @@ -36,10 +36,10 @@ TEST(GradientMachine, testPredict) { paddle::TrainerConfigHelper config("./test_predict_network.py"); std::string buffer; ASSERT_TRUE(config.getModelConfig().SerializeToString(&buffer)); - PD_GradientMachine machine; + paddle_gradient_machine machine; ASSERT_EQ(kPD_NO_ERROR, - PDGradientMachineCreateForPredict( + paddle_gradient_machine_create_for_inference( &machine, &buffer[0], (int)buffer.size())); std::unique_ptr gm( paddle::GradientMachine::create(config.getModelConfig())); @@ -48,11 +48,11 @@ TEST(GradientMachine, testPredict) { gm->saveParameters("./"); ASSERT_EQ(kPD_NO_ERROR, - PDGradientMachineLoadParameterFromDisk(machine, "./")); + paddle_gradient_machine_load_parameter_from_disk(machine, "./")); - PD_GradientMachine machineSlave; + paddle_gradient_machine machineSlave; ASSERT_EQ(kPD_NO_ERROR, - PDGradientMachineCreateSharedParam( + paddle_gradient_machine_create_shared_param( machine, &buffer[0], (int)buffer.size(), &machineSlave)); std::swap(machineSlave, machine); paddle_arguments outArgs = paddle_arguments_create_none(); @@ -69,7 +69,7 @@ TEST(GradientMachine, testPredict) { ASSERT_EQ(kPD_NO_ERROR, paddle_arguments_set_value(inArgs, 0, mat)); ASSERT_EQ(kPD_NO_ERROR, - PDGradientMachineForward(machine, inArgs, outArgs, false)); + paddle_gradient_machine_forward(machine, inArgs, outArgs, false)); uint64_t sz; ASSERT_EQ(kPD_NO_ERROR, paddle_arguments_size(outArgs, &sz)); @@ -100,15 +100,15 @@ TEST(GradientMachine, testPredict) { ASSERT_EQ(kPD_NO_ERROR, paddle_arguments_destroy(inArgs)); ASSERT_EQ(kPD_NO_ERROR, paddle_arguments_destroy(outArgs)); std::swap(machineSlave, machine); - ASSERT_EQ(kPD_NO_ERROR, PDGradientMachineDestroy(machineSlave)); - ASSERT_EQ(kPD_NO_ERROR, PDGradientMachineDestroy(machine)); + ASSERT_EQ(kPD_NO_ERROR, paddle_gradient_machine_destroy(machineSlave)); + ASSERT_EQ(kPD_NO_ERROR, paddle_gradient_machine_destroy(machine)); } int main(int argc, char** argv) { testing::InitGoogleTest(&argc, argv); std::vector argvs; argvs.push_back(strdup("--use_gpu=false")); - PDInit((int)argvs.size(), argvs.data()); + paddle_init((int)argvs.size(), argvs.data()); for (auto each : argvs) { free(each); } -- GitLab From 58e5b87831a194bb0a1652314b0d2a81a7040bee Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Fri, 24 Mar 2017 15:40:43 +0800 Subject: [PATCH 0038/3256] Add license --- paddle/capi/arguments.h | 14 ++++++++++++++ paddle/capi/gradient_machine.h | 14 ++++++++++++++ paddle/capi/main.h | 14 ++++++++++++++ 3 files changed, 42 insertions(+) diff --git a/paddle/capi/arguments.h b/paddle/capi/arguments.h index 3f3594b282b..1bb6516ea02 100644 --- a/paddle/capi/arguments.h +++ b/paddle/capi/arguments.h @@ -1,3 +1,17 @@ +/* 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. */ + #ifndef __PADDLE_CAPI_ARGUMENTS_H__ #define __PADDLE_CAPI_ARGUMENTS_H__ diff --git a/paddle/capi/gradient_machine.h b/paddle/capi/gradient_machine.h index f3cfd67c220..36c1a2b1b4b 100644 --- a/paddle/capi/gradient_machine.h +++ b/paddle/capi/gradient_machine.h @@ -1,3 +1,17 @@ +/* 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. */ + #ifndef __PADDLE_CAPI_GRADIENT_MACHINE_H__ #define __PADDLE_CAPI_GRADIENT_MACHINE_H__ #include "arguments.h" diff --git a/paddle/capi/main.h b/paddle/capi/main.h index 814c8cce244..893ebcbd58d 100644 --- a/paddle/capi/main.h +++ b/paddle/capi/main.h @@ -1,3 +1,17 @@ +/* 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. */ + #ifndef __PADDLE_CAPI_MAIN_H__ #define __PADDLE_CAPI_MAIN_H__ #include "config.h" -- GitLab From d49c6274ca2f7dafeaa116bc422f7d0c0c67f96b Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Fri, 24 Mar 2017 16:07:05 +0800 Subject: [PATCH 0039/3256] GNU Style API --- paddle/capi/Arguments.cpp | 2 +- paddle/capi/CMakeLists.txt | 4 ++-- paddle/capi/Main.cpp | 4 ++-- paddle/capi/Matrix.cpp | 4 ++-- paddle/capi/Vector.cpp | 4 ++-- paddle/capi/{PaddleCAPI.h => capi.h} | 4 ++-- paddle/capi/{PaddleCAPIPrivate.h => capi_private.h} | 2 +- paddle/capi/gradient_machine.cpp | 4 ++-- paddle/capi/tests/test_Arguments.cpp | 2 +- paddle/capi/tests/test_GradientMachine.cpp | 2 +- paddle/capi/tests/test_Matrix.cpp | 2 +- paddle/capi/tests/test_Vector.cpp | 2 +- 12 files changed, 18 insertions(+), 18 deletions(-) rename paddle/capi/{PaddleCAPI.h => capi.h} (94%) rename paddle/capi/{PaddleCAPIPrivate.h => capi_private.h} (98%) diff --git a/paddle/capi/Arguments.cpp b/paddle/capi/Arguments.cpp index d9b207af705..2954f522c95 100644 --- a/paddle/capi/Arguments.cpp +++ b/paddle/capi/Arguments.cpp @@ -12,8 +12,8 @@ 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 "PaddleCAPIPrivate.h" #include "arguments.h" +#include "capi_private.h" using paddle::capi::cast; diff --git a/paddle/capi/CMakeLists.txt b/paddle/capi/CMakeLists.txt index a2b1929e4b7..1b52a79cebb 100644 --- a/paddle/capi/CMakeLists.txt +++ b/paddle/capi/CMakeLists.txt @@ -13,7 +13,7 @@ configure_file(config.h.in config.h @ONLY) # PaddleCAPI.h is the only header we exposed. It currently only used for model # inference. file(GLOB CAPI_HEADERS *.h) -set(CAPI_PRIVATE_HEADER PaddleCAPIPrivate.h) +set(CAPI_PRIVATE_HEADER capi_private.h) list(REMOVE_ITEM CAPI_HEADERS ${CAPI_PRIVATE_HEADER}) file(GLOB CAPI_SOURCES *.cpp) @@ -59,7 +59,7 @@ link_paddle_exe(paddle_capi_shared) # install library & headers. install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${capi_whole_library} DESTINATION lib) -install(FILES ${CAPI_HEADER} DESTINATION include/paddle) +install(FILES ${CAPI_HEADERS} DESTINATION include/paddle) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/config.h DESTINATION include/paddle) install(TARGETS paddle_capi_shared DESTINATION lib) diff --git a/paddle/capi/Main.cpp b/paddle/capi/Main.cpp index 7604945de7d..7f24561e9aa 100644 --- a/paddle/capi/Main.cpp +++ b/paddle/capi/Main.cpp @@ -16,8 +16,8 @@ limitations under the License. */ #include #include #include -#include "PaddleCAPI.h" -#include "PaddleCAPIPrivate.h" +#include "capi_private.h" +#include "main.h" #include "paddle/trainer/TrainerConfigHelper.h" #include "paddle/utils/Excepts.h" #include "paddle/utils/PythonUtil.h" diff --git a/paddle/capi/Matrix.cpp b/paddle/capi/Matrix.cpp index fe60832d70a..85269e18854 100644 --- a/paddle/capi/Matrix.cpp +++ b/paddle/capi/Matrix.cpp @@ -12,9 +12,9 @@ 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 "PaddleCAPI.h" -#include "PaddleCAPIPrivate.h" +#include "capi_private.h" #include "hl_cuda.h" +#include "matrix.h" #define cast(v) paddle::capi::cast(v) extern "C" { diff --git a/paddle/capi/Vector.cpp b/paddle/capi/Vector.cpp index 4ccb167fec2..564708e963b 100644 --- a/paddle/capi/Vector.cpp +++ b/paddle/capi/Vector.cpp @@ -12,8 +12,8 @@ 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 "PaddleCAPI.h" -#include "PaddleCAPIPrivate.h" +#include "capi_private.h" +#include "vector.h" using paddle::capi::cast; diff --git a/paddle/capi/PaddleCAPI.h b/paddle/capi/capi.h similarity index 94% rename from paddle/capi/PaddleCAPI.h rename to paddle/capi/capi.h index 1e275c5c1fa..4097a1a35a6 100644 --- a/paddle/capi/PaddleCAPI.h +++ b/paddle/capi/capi.h @@ -12,8 +12,8 @@ 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. */ -#ifndef PADDLECAPI_H_ -#define PADDLECAPI_H_ +#ifndef __PADDLE_CAPI_H__ +#define __PADDLE_CAPI_H__ /** * Paddle C API. It will replace SWIG as Multiple Language API for model diff --git a/paddle/capi/PaddleCAPIPrivate.h b/paddle/capi/capi_private.h similarity index 98% rename from paddle/capi/PaddleCAPIPrivate.h rename to paddle/capi/capi_private.h index 072e9a37a63..c7cdbd5f6f3 100644 --- a/paddle/capi/PaddleCAPIPrivate.h +++ b/paddle/capi/capi_private.h @@ -12,7 +12,7 @@ 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 "PaddleCAPI.h" +#include "capi.h" #include "paddle/gserver/gradientmachines/GradientMachine.h" #include "paddle/math/Matrix.h" #include "paddle/math/Vector.h" diff --git a/paddle/capi/gradient_machine.cpp b/paddle/capi/gradient_machine.cpp index ab8e747ae95..6e7740a455b 100644 --- a/paddle/capi/gradient_machine.cpp +++ b/paddle/capi/gradient_machine.cpp @@ -12,8 +12,8 @@ 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 "PaddleCAPI.h" -#include "PaddleCAPIPrivate.h" +#include "gradient_machine.h" +#include "capi_private.h" #include "paddle/gserver/gradientmachines/NeuralNetwork.h" #define cast(v) paddle::capi::cast(v) diff --git a/paddle/capi/tests/test_Arguments.cpp b/paddle/capi/tests/test_Arguments.cpp index 60fa57517fb..e6e4ac9937e 100644 --- a/paddle/capi/tests/test_Arguments.cpp +++ b/paddle/capi/tests/test_Arguments.cpp @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. */ #include -#include "PaddleCAPI.h" +#include "capi.h" #include "gtest/gtest.h" #include "paddle/utils/ThreadLocal.h" diff --git a/paddle/capi/tests/test_GradientMachine.cpp b/paddle/capi/tests/test_GradientMachine.cpp index 3e8ba8e9d8f..b37fe83a30b 100644 --- a/paddle/capi/tests/test_GradientMachine.cpp +++ b/paddle/capi/tests/test_GradientMachine.cpp @@ -18,7 +18,7 @@ limitations under the License. */ #include #include #include -#include "PaddleCAPI.h" +#include "capi.h" #include "paddle/utils/ThreadLocal.h" static std::vector randomBuffer(size_t bufSize) { diff --git a/paddle/capi/tests/test_Matrix.cpp b/paddle/capi/tests/test_Matrix.cpp index 1b3b881caee..162df448d2b 100644 --- a/paddle/capi/tests/test_Matrix.cpp +++ b/paddle/capi/tests/test_Matrix.cpp @@ -12,7 +12,7 @@ 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 "PaddleCAPI.h" +#include "capi.h" #include "gtest/gtest.h" TEST(CAPIMatrix, create) { diff --git a/paddle/capi/tests/test_Vector.cpp b/paddle/capi/tests/test_Vector.cpp index 64c19265e3d..c5c57b7288d 100644 --- a/paddle/capi/tests/test_Vector.cpp +++ b/paddle/capi/tests/test_Vector.cpp @@ -12,7 +12,7 @@ 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 "PaddleCAPI.h" +#include "capi.h" #include "gtest/gtest.h" TEST(CAPIVector, create) { -- GitLab From 3023c652fddae6687728c1a7b75d824682c18860 Mon Sep 17 00:00:00 2001 From: "yi.wu" Date: Fri, 24 Mar 2017 17:48:52 +0800 Subject: [PATCH 0040/3256] update --- doc/design/cluster_design.md | 93 +++++++++++++++++++----------- doc/design/images/replica.png | Bin 59597 -> 179103 bytes doc/design/images/trainer.graffle | Bin 4895 -> 6434 bytes doc/design/images/trainer.png | Bin 119703 -> 145107 bytes 4 files changed, 59 insertions(+), 34 deletions(-) diff --git a/doc/design/cluster_design.md b/doc/design/cluster_design.md index fbf25a73f9d..3555a050a4f 100644 --- a/doc/design/cluster_design.md +++ b/doc/design/cluster_design.md @@ -7,33 +7,34 @@ 常见的深度学习分布式训练的架构如图: - - -为了完成一个深度学习的训练任务,集群中会运行多个trainer和parameter server,集群会把模型的参 -数分布式的存储在多个parameter server上,trainer完成每个mini-batch数据训练之后会把梯度发送 -给parameter server,parameter server将某个分片的模型参数和梯度执行整合和优化。然后trainer -从所有的parameter server下载模型参数并开始下一轮mini-batch的训练。 - -可以看到,可以进一步的优化以下方面: -1. 模型的参数是保存在parameter server进程的内存中的。在一个训练任务过程中任意一台 - parameter server不能异常退出,否则训练不能继续执行 -1. 不能在一个训练任务中动态的增加Trainer个数或parameter个数 -1. parameter server保存模型参数考虑多个备份防止单点故障 -1. 为了使训练任务至少可以抵御“单点故障”(任意时刻只可能同时有一台服务器故障),模型参数的更新和分发 - 需要保证原子性操作或满足事务性操作 -1. 可以同时调度大量的训练任务和使用模型的应用在一个集群上 + + +为了完成一个深度学习的训练任务,集群中会运行多个trainer和parameter server,每个trainer启动时,会先尝试从parameter server集群下载最新的参数,然后以mini-batch为单位读取训练数据集中的一部分数据(Data shard)。在完成这个mini-batch数据的神经网络前馈和反向传播计算后,将参数梯度发送给对应的parameter server。随后trainer开始下一轮计算。 + +每个parameter server保存所有parameter的一个分片(Global model shard),并负责接受所有trainer发送的梯度,完成SGD和优化算法,然后发送更新后的parameter到每个trainer。 + +这样,通过trainer和parameter server的分布式协作,可以完成神经网络的SGD方法的训练。Paddle可以同时支持同步SGD(synchronize SGD)和异步(asynchronize SGD)。 + +在使用同步SGD训练神经网络时,Paddle使用同步屏障(barrier),使梯度的提交和参数的更新按照顺序方式执行。在异步SGD中,则并不会等待所有trainer提交梯度才更新参数,这样极大的提高了计算的并行性:parameter server不之间不相互依赖并行的接收梯度和更新参数,parameter server也不会等待trainer全部都提交梯度之后才开始下一步,trainer之间也不会相互依赖,并行的执行模型的训练。可以看出,虽然异步SGD方式会使参数的更新并不能保证参数的顺序的同步的更新,在任意时间某一台parameter server上保存的参数可能比另一台要更新,这样反而会给参数优化过程带来更多的随机性。在实践中,异步SGD在带来更高效率的同时并没有特别影响算法的准确性。 + +在上面的分布式计算模型中,使用异步SGD比同步SGD可以一定程度的提供训练任务的容灾性。假设在某一时刻,一个trainer进程停止工作,其他的trainer仍然可以完成对部分数据的训练。 + +参考上面所描述的Paddle实现细节,可以进一步的优化以下方面: +1. 目前模型的参数是保存在parameter server进程的内存中的。在同步SGD或异步SGD训练过程中任意一台parameter server不能异常退出,否则参数丢失,训练不能继续执行。需要考虑每个模型分片(model shard)保存多个副本(replica)防止parameter server单点故障。 +1. 不能在一个训练任务中动态的增加或减少Trainer个数或parameter个数(异步SGD是否可以增加Trainer?) +1. 在同步SGD训练过程中,需要保证参数更新满足事务性操作。即可能在更新参数过程中,存放这个参数的shard所在的服务器故障,就需要rollback并重新更新这个参数shard的其他存活副本。 +1. 为了支持大量的训练任务和使用模型的应用在一个集群上,需要支持训练任务节点的伸缩。 1. 支持训练任务的前置任务和后置任务,支持训练任务的定时调度和对在线流式数据的处理 ## 模型参数数据备份 -为了实现parameter server集群可以容忍单点故障,必须将每个模型参数的分片在集群中存储多个副本。虽然 -也可以考虑使用校验和的技术减少副本大小,但为了整体系统的简单可靠,优先选择使用副本的方式。 +为了实现parameter server集群可以容忍单点故障,须将每个模型参数的分片在集群中存储多个副本。虽然也可以考虑使用校验和的技术减少副本大小,但为了整体系统的简单,优先选择使用副本的方式。 - + -上图显示了在2台parameter server中实现每个模型参数的分片均保存两个副本的状态。parameter 负责存储 +上图显示了在3台parameter server中实现每个模型参数的分片均保存两个副本的状态。parameter 负责存储 所有参数分片副本并在etcd中同步每个副本的状态。每个分片的多个副本中同时只有一个处于"master"状态, 处于"master"状态的副本是当前活动的副本。当一台parameter server故障时,集群中剩下的parameter server -会重新选举出新的"master"副本并继续提供服务。 +会重新选举出新的"master"副本并继续提供服务。比如如果parameter server 3故障,仍然可以从parameter server 1和2中找出完整的3个副本。此时虽然性能会临时降低,但可以确保训练任务继续运行,只要有新的parameter server上线,并完成副本的重新分布,就可以恢复原先的集群状态。 用户在启动parameter server是可以指定副本的个数(>=1),副本越多容灾能力越强,越少性能越好。但通常不会 使用>3个的副本配置。 @@ -58,8 +59,6 @@ etcd中数据存储格式为: "sync": true, } ``` -1. mini-batch计数器,记录此id对应的parameter server正在执行的mini batch id - [CLUSTER_CHROOT]/pservers/[pserverid]/mini-batch-id 1. parameter分片信息: [CLUSTER_CHROOT]/pshards/[shardid]/[replicaid] 比如上图显示的分片将生成下面的4个etcd路径: ```bash @@ -80,24 +79,26 @@ etcd中数据存储格式为: ``` ## 数据一致性 -存在多个副本数据的情况下就需要考虑,多个副本之间的数据一致性。如果使用数据强一致性(例如paxos/raft或两段式提交), +存在多个副本数据的情况下就需要考虑多个副本之间的数据一致性。如果使用数据强一致性(例如paxos/raft或两段式提交), 则在故障恢复时可以获得一个完整的数据集,但每次更新模型参数的性能会下降,因为需要保证多个副本都完全更新之后才算更新 成功。如果使用异步同步(最终一致性),则在重新选举"master"副本时,可能得到的副本并没有完成数据同步。 -本文档讨论使用两阶段提交实现模型副本数据的更新。 +本文档讨论使用两阶段提交(2PC)实现模型副本数据的更新。 * 每个副本通常由多个parameter block组成,多个block之间可以并发更新,但更新同一个block需要保证顺序性。 * 每次需要更新一个block的时候,trainer首先向存放"master"副本的服务器提交“准备更新”请求,"master"副本检查其他副本的状态并创建一个更新事务,然后返回OK。 * trainer再向"master"发送变化部分的梯度数据和这份数据的id,然后"master"并发的更新本地和其他副本的模型数据,更新成功返回OK,如果有更新失败的节点,则执行"rollback",退回到更新前状态并返回错误代码。 - + ## 模型数据检查点(Checkpointing) -模型数据检查点,可以在磁盘上保存一份存储在parameter server内存中的模型数据的完整镜像。在一个不可中断并缺少备份的训练任务中,可以通过阶段性的在每个parameter server的本地磁盘保存检查点快照达到容灾的目的,比如每个pass保存一次快照。在出现单点故障时,只需要恢复这台节点,或者将这台节点迁移到另一个节点并启动即可恢复训练任务。 +模型数据检查点,可以在磁盘上保存一份存储在parameter server内存中的模型数据的完整镜像。在一个不可中断并缺少备份的训练任务中,可以通过阶段性的在每个parameter server的 ***本地磁盘/分布式存储挂载点*** 保存检查点快照达到容灾的目的,比如每个pass或每n个mini-batch保存一次快照。在出现单点故障时,只需要恢复这台节点,或者将这台节点迁移到另一个节点并启动即可恢复训练任务。 + +这里需要用户额外注意,在您的实际环境中,训练任务的运行可能会占满trainer和parameter server之间的网络带宽,如果parameter server此时还需要通过网络访问分布式存储以保存快照,可能会造成网络拥塞,而出现阶段性的运行停滞。 ## 训练数据的存储和分发 生产环境中的训练数据集通常体积很大,并被存储在诸如Hadoop HDFS, Ceph, AWS S3之类的分布式存储之上。这些分布式存储服务通常会把数据切割成多个分片分布式的存储在多个节点之上,而多个trainer通常也需要预先完成文件的切割。但通常的方法是从HDFS上将数据拷贝到训练集群,然后切割到多个trainer服务器上,如图(Mount/Copy): - + 考虑到HDFS实际上已经完成了数据切割的任务,而且如果存在前置的数据预处理任务(Map-Reduce或Spark SQL),这些任务的输出也都存放于HDFS之上,则trainer可以直接调用HDFS LowLevel API,从元数据节点获得每个数据分片存储的位置,直接获得分片。 @@ -109,12 +110,12 @@ trainer和训练数据分片的均衡: * 当trainer >= 数据分片: trainer个数和数据分片个数相同时,可以获得最高的吞吐量。当trainer个数再大于分片数量时,必然有Trainer获取不到数据分片,处于等待状态。但对整体任务运行没有影响,等待的trainer也会消耗很小的资源。 - + * 当trainer < 数据分片 每个trainer负责多个数据分片,轮询方式完成一个分片训练之后开始下一个分片。 - + ## 故障恢复 在通用集群上运行的应用和任务,通常需要有能够自动伸缩的能力,这样在在线集群进行扩容时,可以适当的减小训练任务的资源(进程数/并发数),而不需要直接停止训练任务,修改参数后重新提交任务。 @@ -124,12 +125,24 @@ trainer和训练数据分片的均衡: 用户只要根据实际训练任务场景,配置parameter server和trainer的初始节点个数,最大节点个数和最小节点个数,模型副本个数,是否开启检查点等配置项,即可配置并启动一个可以容灾的训练集群。具体的过程如下: 1. 配置parameter server和trainer的初始节点个数、最大节点个数、最小节点个数、模型副本个数、是否开启检查点等配置以及训练任务相关配置。 -1. 启动parameter server和trainer,每个实例会在etcd中注册一个临时节点。这样当某个parameter server或trainer失效是,etcd中的节点会反应这个示例的状态。每个parameter server在所有的parameter server上会使用etcd watcher监听节点的变化状态,已完成后续处理。 +1. 启动parameter server和trainer,每个实例会在etcd中注册一个带TTL(过期时间)的节点,并每隔一段时间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 ={ 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 -- GitLab From 470bbcf9e5bf05a3dc089da07009175f206ab9b8 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Fri, 24 Mar 2017 17:23:53 +0800 Subject: [PATCH 0041/3256] Add example --- paddle/capi/Matrix.cpp | 47 ++++++++- paddle/capi/config.h.in | 2 +- paddle/capi/error.h | 1 + paddle/capi/examples/.gitignore | 2 + paddle/capi/examples/common/common.h | 26 +++++ paddle/capi/examples/dense/CMakeLists.txt | 6 ++ .../capi/examples/dense/convert_protobin.sh | 2 + paddle/capi/examples/dense/main.c | 63 ++++++++++++ paddle/capi/examples/dense/trainer_config.py | 18 ++++ paddle/capi/examples/multi_thread/.gitignore | 73 ++++++++++++++ .../capi/examples/multi_thread/CMakeLists.txt | 8 ++ .../examples/multi_thread/convert_protobin.sh | 1 + paddle/capi/examples/multi_thread/main.c | 96 +++++++++++++++++++ .../examples/multi_thread/trainer_config.py | 1 + paddle/capi/examples/sparse_binary/.gitignore | 73 ++++++++++++++ .../examples/sparse_binary/CMakeLists.txt | 7 ++ .../sparse_binary/convert_protobin.sh | 1 + paddle/capi/examples/sparse_binary/main.c | 64 +++++++++++++ .../examples/sparse_binary/trainer_config.py | 1 + paddle/capi/gradient_machine.cpp | 8 ++ paddle/capi/gradient_machine.h | 3 + paddle/capi/matrix.h | 38 +++++++- paddle/capi/tests/test_Arguments.cpp | 12 +-- paddle/capi/tests/test_GradientMachine.cpp | 12 +-- paddle/capi/tests/test_Matrix.cpp | 4 +- 25 files changed, 550 insertions(+), 19 deletions(-) create mode 100644 paddle/capi/examples/.gitignore create mode 100644 paddle/capi/examples/common/common.h create mode 100644 paddle/capi/examples/dense/CMakeLists.txt create mode 100755 paddle/capi/examples/dense/convert_protobin.sh create mode 100644 paddle/capi/examples/dense/main.c create mode 100644 paddle/capi/examples/dense/trainer_config.py create mode 100644 paddle/capi/examples/multi_thread/.gitignore create mode 100644 paddle/capi/examples/multi_thread/CMakeLists.txt create mode 120000 paddle/capi/examples/multi_thread/convert_protobin.sh create mode 100644 paddle/capi/examples/multi_thread/main.c create mode 120000 paddle/capi/examples/multi_thread/trainer_config.py create mode 100644 paddle/capi/examples/sparse_binary/.gitignore create mode 100644 paddle/capi/examples/sparse_binary/CMakeLists.txt create mode 120000 paddle/capi/examples/sparse_binary/convert_protobin.sh create mode 100644 paddle/capi/examples/sparse_binary/main.c create mode 120000 paddle/capi/examples/sparse_binary/trainer_config.py diff --git a/paddle/capi/Matrix.cpp b/paddle/capi/Matrix.cpp index 85269e18854..d898ebe2612 100644 --- a/paddle/capi/Matrix.cpp +++ b/paddle/capi/Matrix.cpp @@ -39,7 +39,7 @@ paddle_error paddle_matrix_destroy(paddle_matrix mat) { paddle_error paddle_matrix_set_row(paddle_matrix mat, uint64_t rowID, - pd_real* rowArray) { + paddle_real* rowArray) { if (mat == nullptr) return kPD_NULLPTR; auto ptr = cast(mat); if (ptr->mat == nullptr) return kPD_NULLPTR; @@ -56,7 +56,7 @@ paddle_error paddle_matrix_set_row(paddle_matrix mat, paddle_error paddle_matrix_get_row(paddle_matrix mat, uint64_t rowID, - pd_real** rawRowBuffer) { + paddle_real** rawRowBuffer) { if (mat == nullptr) return kPD_NULLPTR; auto ptr = cast(mat); if (ptr->mat == nullptr) return kPD_NULLPTR; @@ -78,3 +78,46 @@ paddle_error paddle_matrix_get_shape(paddle_matrix mat, return kPD_NO_ERROR; } } + +paddle_matrix paddle_matrix_create_sparse( + uint64_t height, uint64_t width, uint64_t nnz, bool isBinary, bool useGpu) { + auto ptr = new paddle::capi::CMatrix(); + ptr->mat = paddle::Matrix::createSparseMatrix( + height, + width, + nnz, + isBinary ? paddle::NO_VALUE : paddle::FLOAT_VALUE, + paddle::SPARSE_CSR, + false, + useGpu); + return ptr; +} + +paddle_error paddle_matrix_sparse_copy_from(paddle_matrix mat, + int* rowArray, + uint64_t rowSize, + int* colArray, + uint64_t colSize, + float* valueArray, + uint64_t valueSize) { + if (mat == nullptr) return kPD_NULLPTR; + auto ptr = cast(mat); + if (rowArray == nullptr || colArray == nullptr || + (valueSize != 0 && valueArray == nullptr) || ptr->mat == nullptr) { + return kPD_NULLPTR; + } + if (auto sparseMat = dynamic_cast(ptr->mat.get())) { + std::vector row(rowSize); + row.assign(rowArray, rowArray + rowSize); + std::vector col(colSize); + col.assign(colArray, colArray + colSize); + std::vector val(valueSize); + if (valueSize) { + val.assign(valueArray, valueArray + valueSize); + } + sparseMat->copyFrom(row, col, val); + return kPD_NO_ERROR; + } else { + return kPD_NOT_SUPPORTED; + } +} diff --git a/paddle/capi/config.h.in b/paddle/capi/config.h.in index af4e80dea14..d205307588e 100644 --- a/paddle/capi/config.h.in +++ b/paddle/capi/config.h.in @@ -1,7 +1,7 @@ #ifndef __PADDLE_PADDLE_CAPI_CONFIG_H_INCLUDED__ #define __PADDLE_PADDLE_CAPI_CONFIG_H_INCLUDED__ -typedef @PADDLE_FLOAT_TYPE@ pd_real; +typedef @PADDLE_FLOAT_TYPE@ paddle_real; // Since we only support linux and macos in compile, always use clang or // gcc 4.8+. DLL_IMPORT/DLL_EXPORT is as simple as below. diff --git a/paddle/capi/error.h b/paddle/capi/error.h index 6a5907b869f..44d8c2040d1 100644 --- a/paddle/capi/error.h +++ b/paddle/capi/error.h @@ -23,6 +23,7 @@ typedef enum { kPD_NULLPTR = 1, kPD_OUT_OF_RANGE = 2, kPD_PROTOBUF_ERROR = 3, + kPD_NOT_SUPPORTED = 4, kPD_UNDEFINED_ERROR = -1, } paddle_error; diff --git a/paddle/capi/examples/.gitignore b/paddle/capi/examples/.gitignore new file mode 100644 index 00000000000..2caa0a5a298 --- /dev/null +++ b/paddle/capi/examples/.gitignore @@ -0,0 +1,2 @@ +*.bin +build-* diff --git a/paddle/capi/examples/common/common.h b/paddle/capi/examples/common/common.h new file mode 100644 index 00000000000..a78522e4a7c --- /dev/null +++ b/paddle/capi/examples/common/common.h @@ -0,0 +1,26 @@ +#ifndef __CAPI_EXAMPLE_COMMON_H__ +#define __CAPI_EXAMPLE_COMMON_H__ +#include +#include + +#define CHECK(stmt) \ + do { \ + paddle_error __err__ = stmt; \ + if (__err__ != kPD_NO_ERROR) { \ + fprintf(stderr, "Invoke paddle error %d \n" #stmt, __err__); \ + exit(__err__); \ + } \ + } while (0) + +void* read_config(const char* filename, long* size) { + FILE* file = fopen(filename, "r"); + if (file == NULL) return NULL; + fseek(file, 0L, SEEK_END); + *size = ftell(file); + fseek(file, 0L, SEEK_SET); + void* buf = malloc(*size); + fread(buf, 1, *size, file); + fclose(file); + return buf; +} +#endif diff --git a/paddle/capi/examples/dense/CMakeLists.txt b/paddle/capi/examples/dense/CMakeLists.txt new file mode 100644 index 00000000000..008a488fd9e --- /dev/null +++ b/paddle/capi/examples/dense/CMakeLists.txt @@ -0,0 +1,6 @@ +project(dense) +cmake_minimum_required(VERSION 2.8) +aux_source_directory(. SRC_LIST) +add_executable(${PROJECT_NAME} ${SRC_LIST}) +set_property(TARGET ${PROJECT_NAME} PROPERTY C_STANDARD 99) +target_link_libraries(${PROJECT_NAME} -lpaddle_capi_shared) diff --git a/paddle/capi/examples/dense/convert_protobin.sh b/paddle/capi/examples/dense/convert_protobin.sh new file mode 100755 index 00000000000..30ffc316ecb --- /dev/null +++ b/paddle/capi/examples/dense/convert_protobin.sh @@ -0,0 +1,2 @@ +#!/bin/bash +python -m paddle.utils.dump_config trainer_config.py '' --binary > trainer_config.bin diff --git a/paddle/capi/examples/dense/main.c b/paddle/capi/examples/dense/main.c new file mode 100644 index 00000000000..4dddd65bbfb --- /dev/null +++ b/paddle/capi/examples/dense/main.c @@ -0,0 +1,63 @@ +#include +#include +#include "../common/common.h" + +#define CONFIG_BIN "./trainer_config.bin" + +int main() { + // Initalize Paddle + char* argv[] = {"--use_gpu=False"}; + CHECK(paddle_init(1, (char**)argv)); + + // Reading config binary file. It is generated by `convert_protobin.sh` + long size; + void* buf = read_config(CONFIG_BIN, &size); + + // Create a gradient machine for inference. + paddle_gradient_machine machine; + CHECK(paddle_gradient_machine_create_for_inference(&machine, buf, (int)size)); + CHECK(paddle_gradient_machine_randomize_param(machine)); + + // Loading parameter. Uncomment the following line and change the directory. + // CHECK(paddle_gradient_machine_load_parameter_from_disk(machine, + // "./some_where_to_params")); + paddle_arguments in_args = paddle_arguments_create_none(); + + // There is only one input of this network. + CHECK(paddle_arguments_resize(in_args, 1)); + + // Create input matrix. + paddle_matrix mat = paddle_matrix_create(/* sample_num */ 1, + /* size */ 784, + /* useGPU */ false); + srand(time(0)); + paddle_real* array; + + // Get First row. + CHECK(paddle_matrix_get_row(mat, 0, &array)); + + for (int i = 0; i < 784; ++i) { + array[i] = rand() / ((float)RAND_MAX); + } + + CHECK(paddle_arguments_set_value(in_args, 0, mat)); + + paddle_arguments out_args = paddle_arguments_create_none(); + CHECK(paddle_gradient_machine_forward(machine, + in_args, + out_args, + /* isTrain */ false)); + paddle_matrix prob = paddle_matrix_create_none(); + + CHECK(paddle_arguments_value(out_args, 0, prob)); + + CHECK(paddle_matrix_get_row(prob, 0, &array)); + + printf("Prob: "); + for (int i = 0; i < 10; ++i) { + printf("%.2f ", array[i]); + } + printf("\n"); + + return 0; +} diff --git a/paddle/capi/examples/dense/trainer_config.py b/paddle/capi/examples/dense/trainer_config.py new file mode 100644 index 00000000000..873ec119e7a --- /dev/null +++ b/paddle/capi/examples/dense/trainer_config.py @@ -0,0 +1,18 @@ +from paddle.trainer_config_helpers import * + +img = data_layer(name='pixel', size=784) + +hidden = fc_layer( + input=img, + size=200, + param_attr=ParamAttr(name='hidden.w'), + bias_attr=ParamAttr(name='hidden.b')) + +prob = fc_layer( + input=hidden, + size=10, + act=SoftmaxActivation(), + param_attr=ParamAttr(name='prob.w'), + bias_attr=ParamAttr(name='prob.b')) + +outputs(prob) diff --git a/paddle/capi/examples/multi_thread/.gitignore b/paddle/capi/examples/multi_thread/.gitignore new file mode 100644 index 00000000000..fab7372d796 --- /dev/null +++ b/paddle/capi/examples/multi_thread/.gitignore @@ -0,0 +1,73 @@ +# This file is used to ignore files which are generated +# ---------------------------------------------------------------------------- + +*~ +*.autosave +*.a +*.core +*.moc +*.o +*.obj +*.orig +*.rej +*.so +*.so.* +*_pch.h.cpp +*_resource.rc +*.qm +.#* +*.*# +core +!core/ +tags +.DS_Store +.directory +*.debug +Makefile* +*.prl +*.app +moc_*.cpp +ui_*.h +qrc_*.cpp +Thumbs.db +*.res +*.rc +/.qmake.cache +/.qmake.stash + +# qtcreator generated files +*.pro.user* + +# xemacs temporary files +*.flc + +# Vim temporary files +.*.swp + +# Visual Studio generated files +*.ib_pdb_index +*.idb +*.ilk +*.pdb +*.sln +*.suo +*.vcproj +*vcproj.*.*.user +*.ncb +*.sdf +*.opensdf +*.vcxproj +*vcxproj.* + +# MinGW generated files +*.Debug +*.Release + +# Python byte code +*.pyc + +# Binaries +# -------- +*.dll +*.exe + diff --git a/paddle/capi/examples/multi_thread/CMakeLists.txt b/paddle/capi/examples/multi_thread/CMakeLists.txt new file mode 100644 index 00000000000..98e411ddc02 --- /dev/null +++ b/paddle/capi/examples/multi_thread/CMakeLists.txt @@ -0,0 +1,8 @@ +project(multi_thread) +cmake_minimum_required(VERSION 2.8) +aux_source_directory(. SRC_LIST) +add_executable(${PROJECT_NAME} ${SRC_LIST}) +find_package (Threads) +set_property(TARGET ${PROJECT_NAME} PROPERTY C_STANDARD 99) +target_link_libraries(${PROJECT_NAME} -lpaddle_capi_shared + ${CMAKE_THREAD_LIBS_INIT}) diff --git a/paddle/capi/examples/multi_thread/convert_protobin.sh b/paddle/capi/examples/multi_thread/convert_protobin.sh new file mode 120000 index 00000000000..3c1b3533523 --- /dev/null +++ b/paddle/capi/examples/multi_thread/convert_protobin.sh @@ -0,0 +1 @@ +../dense/convert_protobin.sh \ No newline at end of file diff --git a/paddle/capi/examples/multi_thread/main.c b/paddle/capi/examples/multi_thread/main.c new file mode 100644 index 00000000000..23f8629765d --- /dev/null +++ b/paddle/capi/examples/multi_thread/main.c @@ -0,0 +1,96 @@ +#include +#include +#include +#include "../common/common.h" + +#define CONFIG_BIN "./trainer_config.bin" +#define NUM_THREAD 1000 +#define NUM_ITER 1000 + +pthread_mutex_t mutex; + +void* thread_main(void* gm_ptr) { + paddle_gradient_machine machine = (paddle_gradient_machine)(gm_ptr); + + for (int iter = 0; iter < NUM_ITER; ++iter) { + paddle_arguments in_args = paddle_arguments_create_none(); + // There is only one input of this network. + CHECK(paddle_arguments_resize(in_args, 1)); + + // Create input matrix. + paddle_matrix mat = paddle_matrix_create(/* sample_num */ 1, + /* size */ 784, + /* useGPU */ false); + + paddle_real* array; + + // Get First row. + CHECK(paddle_matrix_get_row(mat, 0, &array)); + + for (int i = 0; i < 784; ++i) { + array[i] = rand() / ((float)RAND_MAX); + } + + CHECK(paddle_arguments_set_value(in_args, 0, mat)); + + paddle_arguments out_args = paddle_arguments_create_none(); + CHECK(paddle_gradient_machine_forward(machine, + in_args, + out_args, + /* isTrain */ false)); + paddle_matrix prob = paddle_matrix_create_none(); + + CHECK(paddle_arguments_value(out_args, 0, prob)); + + CHECK(paddle_matrix_get_row(prob, 0, &array)); + + pthread_mutex_lock(&mutex); + printf("Prob: "); + for (int i = 0; i < 10; ++i) { + printf("%.2f ", array[i]); + } + printf("\n"); + pthread_mutex_unlock(&mutex); + } + + CHECK(paddle_gradient_machine_destroy(machine)); + return NULL; +} + +int main() { + // Initalize Paddle + char* argv[] = {"--use_gpu=False"}; + CHECK(paddle_init(1, (char**)argv)); + + // Reading config binary file. It is generated by `convert_protobin.sh` + long size; + void* buf = read_config(CONFIG_BIN, &size); + + // Create a gradient machine for inference. + paddle_gradient_machine machine; + CHECK(paddle_gradient_machine_create_for_inference(&machine, buf, (int)size)); + CHECK(paddle_gradient_machine_randomize_param(machine)); + + // Loading parameter. Uncomment the following line and change the directory. + // CHECK(paddle_gradient_machine_load_parameter_from_disk(machine, + // "./some_where_to_params")); + srand(time(0)); + pthread_mutex_init(&mutex, NULL); + + pthread_t threads[NUM_THREAD]; + + for (int i = 0; i < NUM_THREAD; ++i) { + paddle_gradient_machine thread_local_machine; + CHECK(paddle_gradient_machine_create_shared_param( + machine, buf, size, &thread_local_machine)); + pthread_create(&threads[i], NULL, thread_main, thread_local_machine); + } + + for (int i = 0; i < NUM_THREAD; ++i) { + pthread_join(threads[i], NULL); + } + + pthread_mutex_destroy(&mutex); + + return 0; +} diff --git a/paddle/capi/examples/multi_thread/trainer_config.py b/paddle/capi/examples/multi_thread/trainer_config.py new file mode 120000 index 00000000000..70cfb1f7f4c --- /dev/null +++ b/paddle/capi/examples/multi_thread/trainer_config.py @@ -0,0 +1 @@ +../dense/trainer_config.py \ No newline at end of file diff --git a/paddle/capi/examples/sparse_binary/.gitignore b/paddle/capi/examples/sparse_binary/.gitignore new file mode 100644 index 00000000000..fab7372d796 --- /dev/null +++ b/paddle/capi/examples/sparse_binary/.gitignore @@ -0,0 +1,73 @@ +# This file is used to ignore files which are generated +# ---------------------------------------------------------------------------- + +*~ +*.autosave +*.a +*.core +*.moc +*.o +*.obj +*.orig +*.rej +*.so +*.so.* +*_pch.h.cpp +*_resource.rc +*.qm +.#* +*.*# +core +!core/ +tags +.DS_Store +.directory +*.debug +Makefile* +*.prl +*.app +moc_*.cpp +ui_*.h +qrc_*.cpp +Thumbs.db +*.res +*.rc +/.qmake.cache +/.qmake.stash + +# qtcreator generated files +*.pro.user* + +# xemacs temporary files +*.flc + +# Vim temporary files +.*.swp + +# Visual Studio generated files +*.ib_pdb_index +*.idb +*.ilk +*.pdb +*.sln +*.suo +*.vcproj +*vcproj.*.*.user +*.ncb +*.sdf +*.opensdf +*.vcxproj +*vcxproj.* + +# MinGW generated files +*.Debug +*.Release + +# Python byte code +*.pyc + +# Binaries +# -------- +*.dll +*.exe + diff --git a/paddle/capi/examples/sparse_binary/CMakeLists.txt b/paddle/capi/examples/sparse_binary/CMakeLists.txt new file mode 100644 index 00000000000..c8219568890 --- /dev/null +++ b/paddle/capi/examples/sparse_binary/CMakeLists.txt @@ -0,0 +1,7 @@ +project(sparse_binary) +cmake_minimum_required(VERSION 2.8) +aux_source_directory(. SRC_LIST) +add_executable(${PROJECT_NAME} ${SRC_LIST}) +find_package (Threads) +set_property(TARGET ${PROJECT_NAME} PROPERTY C_STANDARD 99) +target_link_libraries(${PROJECT_NAME} -lpaddle_capi_shared) diff --git a/paddle/capi/examples/sparse_binary/convert_protobin.sh b/paddle/capi/examples/sparse_binary/convert_protobin.sh new file mode 120000 index 00000000000..3c1b3533523 --- /dev/null +++ b/paddle/capi/examples/sparse_binary/convert_protobin.sh @@ -0,0 +1 @@ +../dense/convert_protobin.sh \ No newline at end of file diff --git a/paddle/capi/examples/sparse_binary/main.c b/paddle/capi/examples/sparse_binary/main.c new file mode 100644 index 00000000000..c5e653dbc28 --- /dev/null +++ b/paddle/capi/examples/sparse_binary/main.c @@ -0,0 +1,64 @@ +#include +#include +#include "../common/common.h" + +#define CONFIG_BIN "./trainer_config.bin" + +int main() { + // Initalize Paddle + char* argv[] = {"--use_gpu=False"}; + CHECK(paddle_init(1, (char**)argv)); + + // Reading config binary file. It is generated by `convert_protobin.sh` + long size; + void* buf = read_config(CONFIG_BIN, &size); + + // Create a gradient machine for inference. + paddle_gradient_machine machine; + CHECK(paddle_gradient_machine_create_for_inference(&machine, buf, (int)size)); + CHECK(paddle_gradient_machine_randomize_param(machine)); + + // Loading parameter. Uncomment the following line and change the directory. + // CHECK(paddle_gradient_machine_load_parameter_from_disk(machine, + // "./some_where_to_params")); + paddle_arguments in_args = paddle_arguments_create_none(); + + // There is only one input of this network. + CHECK(paddle_arguments_resize(in_args, 1)); + + // Create input matrix. + paddle_matrix mat = paddle_matrix_create_sparse(1, 784, 3, true, false); + srand(time(0)); + paddle_real* array; + int colBuf[] = {9, 93, 109}; + int rowBuf[] = {0, sizeof(colBuf) / sizeof(int)}; + + CHECK(paddle_matrix_sparse_copy_from(mat, + rowBuf, + sizeof(rowBuf) / sizeof(int), + colBuf, + sizeof(colBuf) / sizeof(int), + NULL, + 0)); + + CHECK(paddle_arguments_set_value(in_args, 0, mat)); + + paddle_arguments out_args = paddle_arguments_create_none(); + CHECK(paddle_gradient_machine_forward(machine, + in_args, + out_args, + /* isTrain */ false)); + paddle_matrix prob = paddle_matrix_create_none(); + + CHECK(paddle_arguments_value(out_args, 0, prob)); + + CHECK(paddle_matrix_get_row(prob, 0, &array)); + + printf("Prob: "); + for (int i = 0; i < 10; ++i) { + printf("%.2f ", array[i]); + } + printf("\n"); + + return 0; +} diff --git a/paddle/capi/examples/sparse_binary/trainer_config.py b/paddle/capi/examples/sparse_binary/trainer_config.py new file mode 120000 index 00000000000..70cfb1f7f4c --- /dev/null +++ b/paddle/capi/examples/sparse_binary/trainer_config.py @@ -0,0 +1 @@ +../dense/trainer_config.py \ No newline at end of file diff --git a/paddle/capi/gradient_machine.cpp b/paddle/capi/gradient_machine.cpp index 6e7740a455b..00f76e01523 100644 --- a/paddle/capi/gradient_machine.cpp +++ b/paddle/capi/gradient_machine.cpp @@ -113,3 +113,11 @@ paddle_error paddle_gradient_machine_create_shared_param( return kPD_NO_ERROR; } } + +paddle_error paddle_gradient_machine_randomize_param( + paddle_gradient_machine machine) { + auto m = cast(machine); + if (m == nullptr || m->machine == nullptr) return kPD_NULLPTR; + m->machine->randParameters(); + return kPD_NO_ERROR; +} diff --git a/paddle/capi/gradient_machine.h b/paddle/capi/gradient_machine.h index 36c1a2b1b4b..d7e2dd9bf80 100644 --- a/paddle/capi/gradient_machine.h +++ b/paddle/capi/gradient_machine.h @@ -74,6 +74,9 @@ paddle_gradient_machine_create_shared_param(paddle_gradient_machine origin, int size, paddle_gradient_machine* slave); +PD_API paddle_error +paddle_gradient_machine_randomize_param(paddle_gradient_machine machine); + /** * @brief Destroy a gradient machine * @param machine that need to destroy diff --git a/paddle/capi/matrix.h b/paddle/capi/matrix.h index 2f6488f3838..f15f7f3bbbd 100644 --- a/paddle/capi/matrix.h +++ b/paddle/capi/matrix.h @@ -15,6 +15,7 @@ limitations under the License. */ #ifndef __PADDLE_CAPI_MATRIX_H__ #define __PADDLE_CAPI_MATRIX_H__ +#include #include #include "config.h" #include "error.h" @@ -39,6 +40,18 @@ PD_API paddle_matrix paddle_matrix_create(uint64_t height, uint64_t width, bool useGpu); +/** + * @brief paddle_matrix_create_sparse Create a sparse matrix. + * @param height the matrix height. + * @param width the matrix width. + * @param nnz the number of non-zero elements. + * @param isBinary is binary (either 1 or 0 in matrix) or not. + * @param useGpu is using GPU or not. + * @return paddle_matrix. + */ +PD_API paddle_matrix paddle_matrix_create_sparse( + uint64_t height, uint64_t width, uint64_t nnz, bool isBinary, bool useGpu); + /** * @brief paddle_matrix_destroy Destroy a matrix. * @param mat @@ -55,7 +68,7 @@ PD_API paddle_error paddle_matrix_destroy(paddle_matrix mat); */ PD_API paddle_error paddle_matrix_set_row(paddle_matrix mat, uint64_t rowID, - pd_real* rowArray); + paddle_real* rowArray); /** * @brief PDMatGetRow Get raw row buffer from matrix @@ -66,7 +79,7 @@ PD_API paddle_error paddle_matrix_set_row(paddle_matrix mat, */ PD_API paddle_error paddle_matrix_get_row(paddle_matrix mat, uint64_t rowID, - pd_real** rawRowBuffer); + paddle_real** rawRowBuffer); /** * @brief PDMatCreateNone Create None Matrix @@ -85,6 +98,27 @@ PD_API paddle_error paddle_matrix_get_shape(paddle_matrix mat, uint64_t* height, uint64_t* width); +/** + * @brief paddle_matrix_sparse_copy_from Copy from a CSR format matrix + * @param [out] mat output matrix + * @param [in] rowArray row array. The array slices in column array. + * @param [in] rowSize length of row array. + * @param [in] colArray the column array. It means the non-zero element indices + * in each row. + * @param [in] colSize length of column array. + * @param [in] valueArray the value array. It means the non-zero elemnt values. + * NULL if the matrix is binary. + * @param [in] valueSize length of value array. Zero if the matrix is binary. + * @return paddle_error + */ +PD_API paddle_error paddle_matrix_sparse_copy_from(paddle_matrix mat, + int* rowArray, + uint64_t rowSize, + int* colArray, + uint64_t colSize, + float* valueArray, + uint64_t valueSize); + #ifdef __cplusplus } #endif diff --git a/paddle/capi/tests/test_Arguments.cpp b/paddle/capi/tests/test_Arguments.cpp index e6e4ac9937e..f56391d51e3 100644 --- a/paddle/capi/tests/test_Arguments.cpp +++ b/paddle/capi/tests/test_Arguments.cpp @@ -17,10 +17,10 @@ limitations under the License. */ #include "gtest/gtest.h" #include "paddle/utils/ThreadLocal.h" -static std::vector randomBuffer(size_t bufSize) { +static std::vector randomBuffer(size_t bufSize) { auto& eng = paddle::ThreadLocalRandomEngine::get(); - std::uniform_real_distribution dist(-1.0, 1.0); - std::vector retv; + std::uniform_real_distribution dist(-1.0, 1.0); + std::vector retv; retv.reserve(bufSize); for (size_t i = 0; i < bufSize; ++i) { retv.push_back(dist(eng)); @@ -42,7 +42,7 @@ TEST(CAPIArguments, value) { paddle_matrix mat = paddle_matrix_create(128, 64, false); for (size_t i = 0; i < 128; ++i) { - std::vector sampleBuf = randomBuffer(64); + std::vector sampleBuf = randomBuffer(64); paddle_matrix_set_row(mat, i, sampleBuf.data()); } ASSERT_EQ(kPD_NO_ERROR, paddle_arguments_set_value(args, 0, mat)); @@ -52,8 +52,8 @@ TEST(CAPIArguments, value) { ASSERT_EQ(kPD_NO_ERROR, paddle_arguments_value(args, 0, val)); for (size_t i = 0; i < 128; ++i) { - pd_real* row1; - pd_real* row2; + paddle_real* row1; + paddle_real* row2; ASSERT_EQ(kPD_NO_ERROR, paddle_matrix_get_row(mat, i, &row1)); ASSERT_EQ(kPD_NO_ERROR, paddle_matrix_get_row(val, i, &row2)); diff --git a/paddle/capi/tests/test_GradientMachine.cpp b/paddle/capi/tests/test_GradientMachine.cpp index b37fe83a30b..be7dfadddc3 100644 --- a/paddle/capi/tests/test_GradientMachine.cpp +++ b/paddle/capi/tests/test_GradientMachine.cpp @@ -21,10 +21,10 @@ limitations under the License. */ #include "capi.h" #include "paddle/utils/ThreadLocal.h" -static std::vector randomBuffer(size_t bufSize) { +static std::vector randomBuffer(size_t bufSize) { auto& eng = paddle::ThreadLocalRandomEngine::get(); - std::uniform_real_distribution dist(-1.0, 1.0); - std::vector retv; + std::uniform_real_distribution dist(-1.0, 1.0); + std::vector retv; retv.reserve(bufSize); for (size_t i = 0; i < bufSize; ++i) { retv.push_back(dist(eng)); @@ -60,12 +60,12 @@ TEST(GradientMachine, testPredict) { paddle_arguments inArgs = paddle_arguments_create_none(); ASSERT_EQ(kPD_NO_ERROR, paddle_arguments_resize(inArgs, 1)); paddle_matrix mat = paddle_matrix_create(1, 100, false); - static_assert(std::is_same::value, ""); + static_assert(std::is_same::value, ""); auto data = randomBuffer(100); - pd_real* rowPtr; + paddle_real* rowPtr; ASSERT_EQ(kPD_NO_ERROR, paddle_matrix_get_row(mat, 0, &rowPtr)); - memcpy(rowPtr, data.data(), data.size() * sizeof(pd_real)); + memcpy(rowPtr, data.data(), data.size() * sizeof(paddle_real)); ASSERT_EQ(kPD_NO_ERROR, paddle_arguments_set_value(inArgs, 0, mat)); ASSERT_EQ(kPD_NO_ERROR, diff --git a/paddle/capi/tests/test_Matrix.cpp b/paddle/capi/tests/test_Matrix.cpp index 162df448d2b..71dc2064dd0 100644 --- a/paddle/capi/tests/test_Matrix.cpp +++ b/paddle/capi/tests/test_Matrix.cpp @@ -17,7 +17,7 @@ limitations under the License. */ TEST(CAPIMatrix, create) { paddle_matrix mat = paddle_matrix_create(128, 32, false); - std::vector sampleRow; + std::vector sampleRow; sampleRow.resize(32); for (size_t i = 0; i < sampleRow.size(); ++i) { sampleRow[i] = 1.0 / (i + 1.0); @@ -26,7 +26,7 @@ TEST(CAPIMatrix, create) { ASSERT_EQ(kPD_OUT_OF_RANGE, paddle_matrix_set_row(mat, 128, sampleRow.data())); - pd_real* arrayPtr; + paddle_real* arrayPtr; ASSERT_EQ(kPD_NO_ERROR, paddle_matrix_get_row(mat, 0, &arrayPtr)); for (size_t i = 0; i < sampleRow.size(); ++i) { -- GitLab From 34b3ee31fafae7d0b0ad2977b1ab14094018e2b1 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Sun, 26 Mar 2017 14:06:38 +0800 Subject: [PATCH 0042/3256] Add sequence exampleAdd sequence exampleAdd sequence exampleAdd sequence exampleAdd sequence exampleAdd sequence exampleAdd sequence exampleAdd sequence exampleAdd sequence example --- paddle/capi/examples/sequence/.gitignore | 73 +++++++++++++++++++ paddle/capi/examples/sequence/CMakeLists.txt | 6 ++ .../examples/sequence/convert_protobin.sh | 1 + paddle/capi/examples/sequence/main.c | 63 ++++++++++++++++ .../capi/examples/sequence/trainer_config.py | 13 ++++ 5 files changed, 156 insertions(+) create mode 100644 paddle/capi/examples/sequence/.gitignore create mode 100644 paddle/capi/examples/sequence/CMakeLists.txt create mode 120000 paddle/capi/examples/sequence/convert_protobin.sh create mode 100644 paddle/capi/examples/sequence/main.c create mode 100644 paddle/capi/examples/sequence/trainer_config.py diff --git a/paddle/capi/examples/sequence/.gitignore b/paddle/capi/examples/sequence/.gitignore new file mode 100644 index 00000000000..fab7372d796 --- /dev/null +++ b/paddle/capi/examples/sequence/.gitignore @@ -0,0 +1,73 @@ +# This file is used to ignore files which are generated +# ---------------------------------------------------------------------------- + +*~ +*.autosave +*.a +*.core +*.moc +*.o +*.obj +*.orig +*.rej +*.so +*.so.* +*_pch.h.cpp +*_resource.rc +*.qm +.#* +*.*# +core +!core/ +tags +.DS_Store +.directory +*.debug +Makefile* +*.prl +*.app +moc_*.cpp +ui_*.h +qrc_*.cpp +Thumbs.db +*.res +*.rc +/.qmake.cache +/.qmake.stash + +# qtcreator generated files +*.pro.user* + +# xemacs temporary files +*.flc + +# Vim temporary files +.*.swp + +# Visual Studio generated files +*.ib_pdb_index +*.idb +*.ilk +*.pdb +*.sln +*.suo +*.vcproj +*vcproj.*.*.user +*.ncb +*.sdf +*.opensdf +*.vcxproj +*vcxproj.* + +# MinGW generated files +*.Debug +*.Release + +# Python byte code +*.pyc + +# Binaries +# -------- +*.dll +*.exe + diff --git a/paddle/capi/examples/sequence/CMakeLists.txt b/paddle/capi/examples/sequence/CMakeLists.txt new file mode 100644 index 00000000000..71b73acba7c --- /dev/null +++ b/paddle/capi/examples/sequence/CMakeLists.txt @@ -0,0 +1,6 @@ +project(sequence) +cmake_minimum_required(VERSION 2.8) +aux_source_directory(. SRC_LIST) +add_executable(${PROJECT_NAME} ${SRC_LIST}) +set_property(TARGET ${PROJECT_NAME} PROPERTY C_STANDARD 99) +target_link_libraries(${PROJECT_NAME} -lpaddle_capi_shared) diff --git a/paddle/capi/examples/sequence/convert_protobin.sh b/paddle/capi/examples/sequence/convert_protobin.sh new file mode 120000 index 00000000000..3c1b3533523 --- /dev/null +++ b/paddle/capi/examples/sequence/convert_protobin.sh @@ -0,0 +1 @@ +../dense/convert_protobin.sh \ No newline at end of file diff --git a/paddle/capi/examples/sequence/main.c b/paddle/capi/examples/sequence/main.c new file mode 100644 index 00000000000..7e71bb8b8af --- /dev/null +++ b/paddle/capi/examples/sequence/main.c @@ -0,0 +1,63 @@ +#include +#include +#include "../common/common.h" + +#define CONFIG_BIN "./trainer_config.bin" + +int main() { + // Initalize Paddle + char* argv[] = {"--use_gpu=False"}; + CHECK(paddle_init(1, (char**)argv)); + + // Reading config binary file. It is generated by `convert_protobin.sh` + long size; + void* buf = read_config(CONFIG_BIN, &size); + + // Create a gradient machine for inference. + paddle_gradient_machine machine; + CHECK(paddle_gradient_machine_create_for_inference(&machine, buf, (int)size)); + CHECK(paddle_gradient_machine_randomize_param(machine)); + + // Loading parameter. Uncomment the following line and change the directory. + // CHECK(paddle_gradient_machine_load_parameter_from_disk(machine, + // "./some_where_to_params")); + paddle_arguments in_args = paddle_arguments_create_none(); + + // There is only one input of this network. + CHECK(paddle_arguments_resize(in_args, 1)); + + // Create input ids. + int sentence_ids[] = {83, 48, 20, 84, 394, 853, 64, 53, 64}; + + paddle_ivector sentence = paddle_ivector_create( + sentence_ids, sizeof(sentence_ids) / sizeof(int), false, false); + CHECK(paddle_arguments_set_ids(in_args, 0, sentence)); + + int seq_pos_array[] = {0, sizeof(sentence_ids) / sizeof(int)}; + + paddle_ivector seq_pos = paddle_ivector_create( + seq_pos_array, sizeof(seq_pos_array) / sizeof(int), false, false); + + CHECK(paddle_arguments_set_sequence_start_pos(in_args, 0, 0, seq_pos)); + + paddle_arguments out_args = paddle_arguments_create_none(); + CHECK(paddle_gradient_machine_forward(machine, + in_args, + out_args, + /* isTrain */ false)); + paddle_matrix prob = paddle_matrix_create_none(); + + CHECK(paddle_arguments_value(out_args, 0, prob)); + + paddle_real* array; + + CHECK(paddle_matrix_get_row(prob, 0, &array)); + + printf("Prob: "); + for (int i = 0; i < 2; ++i) { + printf("%.2f ", array[i]); + } + printf("\n"); + + return 0; +} diff --git a/paddle/capi/examples/sequence/trainer_config.py b/paddle/capi/examples/sequence/trainer_config.py new file mode 100644 index 00000000000..6bbc7a909aa --- /dev/null +++ b/paddle/capi/examples/sequence/trainer_config.py @@ -0,0 +1,13 @@ +from paddle.trainer_config_helpers import * + +WORD_DIM = 3000 + +sentence = data_layer(name='sentence', size=WORD_DIM) +sentence_embedding = embedding_layer( + input=sentence, + size=64, + param_attr=ParameterAttribute( + initial_max=1.0, initial_min=0.5)) +lstm = simple_lstm(input=sentence_embedding, size=64) +lstm_last = last_seq(input=lstm) +outputs(fc_layer(input=lstm_last, size=2, act=SoftmaxActivation())) -- GitLab From 852a94f8042d08b817eb38abac7c530bb2833749 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Sun, 26 Mar 2017 14:07:21 +0800 Subject: [PATCH 0043/3256] Add model_inference directory --- paddle/capi/examples/{ => model_inference}/common/common.h | 0 paddle/capi/examples/{ => model_inference}/dense/CMakeLists.txt | 0 .../capi/examples/{ => model_inference}/dense/convert_protobin.sh | 0 paddle/capi/examples/{ => model_inference}/dense/main.c | 0 .../capi/examples/{ => model_inference}/dense/trainer_config.py | 0 .../capi/examples/{ => model_inference}/multi_thread/.gitignore | 0 .../examples/{ => model_inference}/multi_thread/CMakeLists.txt | 0 .../{ => model_inference}/multi_thread/convert_protobin.sh | 0 paddle/capi/examples/{ => model_inference}/multi_thread/main.c | 0 .../examples/{ => model_inference}/multi_thread/trainer_config.py | 0 paddle/capi/examples/{ => model_inference}/sequence/.gitignore | 0 .../capi/examples/{ => model_inference}/sequence/CMakeLists.txt | 0 .../examples/{ => model_inference}/sequence/convert_protobin.sh | 0 paddle/capi/examples/{ => model_inference}/sequence/main.c | 0 .../examples/{ => model_inference}/sequence/trainer_config.py | 0 .../capi/examples/{ => model_inference}/sparse_binary/.gitignore | 0 .../examples/{ => model_inference}/sparse_binary/CMakeLists.txt | 0 .../{ => model_inference}/sparse_binary/convert_protobin.sh | 0 paddle/capi/examples/{ => model_inference}/sparse_binary/main.c | 0 .../{ => model_inference}/sparse_binary/trainer_config.py | 0 20 files changed, 0 insertions(+), 0 deletions(-) rename paddle/capi/examples/{ => model_inference}/common/common.h (100%) rename paddle/capi/examples/{ => model_inference}/dense/CMakeLists.txt (100%) rename paddle/capi/examples/{ => model_inference}/dense/convert_protobin.sh (100%) rename paddle/capi/examples/{ => model_inference}/dense/main.c (100%) rename paddle/capi/examples/{ => model_inference}/dense/trainer_config.py (100%) rename paddle/capi/examples/{ => model_inference}/multi_thread/.gitignore (100%) rename paddle/capi/examples/{ => model_inference}/multi_thread/CMakeLists.txt (100%) rename paddle/capi/examples/{ => model_inference}/multi_thread/convert_protobin.sh (100%) rename paddle/capi/examples/{ => model_inference}/multi_thread/main.c (100%) rename paddle/capi/examples/{ => model_inference}/multi_thread/trainer_config.py (100%) rename paddle/capi/examples/{ => model_inference}/sequence/.gitignore (100%) rename paddle/capi/examples/{ => model_inference}/sequence/CMakeLists.txt (100%) rename paddle/capi/examples/{ => model_inference}/sequence/convert_protobin.sh (100%) rename paddle/capi/examples/{ => model_inference}/sequence/main.c (100%) rename paddle/capi/examples/{ => model_inference}/sequence/trainer_config.py (100%) rename paddle/capi/examples/{ => model_inference}/sparse_binary/.gitignore (100%) rename paddle/capi/examples/{ => model_inference}/sparse_binary/CMakeLists.txt (100%) rename paddle/capi/examples/{ => model_inference}/sparse_binary/convert_protobin.sh (100%) rename paddle/capi/examples/{ => model_inference}/sparse_binary/main.c (100%) rename paddle/capi/examples/{ => model_inference}/sparse_binary/trainer_config.py (100%) diff --git a/paddle/capi/examples/common/common.h b/paddle/capi/examples/model_inference/common/common.h similarity index 100% rename from paddle/capi/examples/common/common.h rename to paddle/capi/examples/model_inference/common/common.h diff --git a/paddle/capi/examples/dense/CMakeLists.txt b/paddle/capi/examples/model_inference/dense/CMakeLists.txt similarity index 100% rename from paddle/capi/examples/dense/CMakeLists.txt rename to paddle/capi/examples/model_inference/dense/CMakeLists.txt diff --git a/paddle/capi/examples/dense/convert_protobin.sh b/paddle/capi/examples/model_inference/dense/convert_protobin.sh similarity index 100% rename from paddle/capi/examples/dense/convert_protobin.sh rename to paddle/capi/examples/model_inference/dense/convert_protobin.sh diff --git a/paddle/capi/examples/dense/main.c b/paddle/capi/examples/model_inference/dense/main.c similarity index 100% rename from paddle/capi/examples/dense/main.c rename to paddle/capi/examples/model_inference/dense/main.c diff --git a/paddle/capi/examples/dense/trainer_config.py b/paddle/capi/examples/model_inference/dense/trainer_config.py similarity index 100% rename from paddle/capi/examples/dense/trainer_config.py rename to paddle/capi/examples/model_inference/dense/trainer_config.py diff --git a/paddle/capi/examples/multi_thread/.gitignore b/paddle/capi/examples/model_inference/multi_thread/.gitignore similarity index 100% rename from paddle/capi/examples/multi_thread/.gitignore rename to paddle/capi/examples/model_inference/multi_thread/.gitignore diff --git a/paddle/capi/examples/multi_thread/CMakeLists.txt b/paddle/capi/examples/model_inference/multi_thread/CMakeLists.txt similarity index 100% rename from paddle/capi/examples/multi_thread/CMakeLists.txt rename to paddle/capi/examples/model_inference/multi_thread/CMakeLists.txt diff --git a/paddle/capi/examples/multi_thread/convert_protobin.sh b/paddle/capi/examples/model_inference/multi_thread/convert_protobin.sh similarity index 100% rename from paddle/capi/examples/multi_thread/convert_protobin.sh rename to paddle/capi/examples/model_inference/multi_thread/convert_protobin.sh diff --git a/paddle/capi/examples/multi_thread/main.c b/paddle/capi/examples/model_inference/multi_thread/main.c similarity index 100% rename from paddle/capi/examples/multi_thread/main.c rename to paddle/capi/examples/model_inference/multi_thread/main.c diff --git a/paddle/capi/examples/multi_thread/trainer_config.py b/paddle/capi/examples/model_inference/multi_thread/trainer_config.py similarity index 100% rename from paddle/capi/examples/multi_thread/trainer_config.py rename to paddle/capi/examples/model_inference/multi_thread/trainer_config.py diff --git a/paddle/capi/examples/sequence/.gitignore b/paddle/capi/examples/model_inference/sequence/.gitignore similarity index 100% rename from paddle/capi/examples/sequence/.gitignore rename to paddle/capi/examples/model_inference/sequence/.gitignore diff --git a/paddle/capi/examples/sequence/CMakeLists.txt b/paddle/capi/examples/model_inference/sequence/CMakeLists.txt similarity index 100% rename from paddle/capi/examples/sequence/CMakeLists.txt rename to paddle/capi/examples/model_inference/sequence/CMakeLists.txt diff --git a/paddle/capi/examples/sequence/convert_protobin.sh b/paddle/capi/examples/model_inference/sequence/convert_protobin.sh similarity index 100% rename from paddle/capi/examples/sequence/convert_protobin.sh rename to paddle/capi/examples/model_inference/sequence/convert_protobin.sh diff --git a/paddle/capi/examples/sequence/main.c b/paddle/capi/examples/model_inference/sequence/main.c similarity index 100% rename from paddle/capi/examples/sequence/main.c rename to paddle/capi/examples/model_inference/sequence/main.c diff --git a/paddle/capi/examples/sequence/trainer_config.py b/paddle/capi/examples/model_inference/sequence/trainer_config.py similarity index 100% rename from paddle/capi/examples/sequence/trainer_config.py rename to paddle/capi/examples/model_inference/sequence/trainer_config.py diff --git a/paddle/capi/examples/sparse_binary/.gitignore b/paddle/capi/examples/model_inference/sparse_binary/.gitignore similarity index 100% rename from paddle/capi/examples/sparse_binary/.gitignore rename to paddle/capi/examples/model_inference/sparse_binary/.gitignore diff --git a/paddle/capi/examples/sparse_binary/CMakeLists.txt b/paddle/capi/examples/model_inference/sparse_binary/CMakeLists.txt similarity index 100% rename from paddle/capi/examples/sparse_binary/CMakeLists.txt rename to paddle/capi/examples/model_inference/sparse_binary/CMakeLists.txt diff --git a/paddle/capi/examples/sparse_binary/convert_protobin.sh b/paddle/capi/examples/model_inference/sparse_binary/convert_protobin.sh similarity index 100% rename from paddle/capi/examples/sparse_binary/convert_protobin.sh rename to paddle/capi/examples/model_inference/sparse_binary/convert_protobin.sh diff --git a/paddle/capi/examples/sparse_binary/main.c b/paddle/capi/examples/model_inference/sparse_binary/main.c similarity index 100% rename from paddle/capi/examples/sparse_binary/main.c rename to paddle/capi/examples/model_inference/sparse_binary/main.c diff --git a/paddle/capi/examples/sparse_binary/trainer_config.py b/paddle/capi/examples/model_inference/sparse_binary/trainer_config.py similarity index 100% rename from paddle/capi/examples/sparse_binary/trainer_config.py rename to paddle/capi/examples/model_inference/sparse_binary/trainer_config.py -- GitLab From 0d73f4c2655bcef0d2c037b9b67f9279ea4fab23 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Sun, 26 Mar 2017 15:41:20 +0800 Subject: [PATCH 0044/3256] Add usage documentation of C-API. --- paddle/capi/examples/README.md | 3 ++ .../capi/examples/model_inference/README.md | 42 +++++++++++++++++++ 2 files changed, 45 insertions(+) create mode 100644 paddle/capi/examples/README.md create mode 100644 paddle/capi/examples/model_inference/README.md diff --git a/paddle/capi/examples/README.md b/paddle/capi/examples/README.md new file mode 100644 index 00000000000..14013e281ff --- /dev/null +++ b/paddle/capi/examples/README.md @@ -0,0 +1,3 @@ +# C-API Example Usage + +* [Model Inference](./model_inference/README.md) diff --git a/paddle/capi/examples/model_inference/README.md b/paddle/capi/examples/model_inference/README.md new file mode 100644 index 00000000000..58e6c83140b --- /dev/null +++ b/paddle/capi/examples/model_inference/README.md @@ -0,0 +1,42 @@ +# Use C-API for Model Inference + +There are several examples in this directory about how to use Paddle C-API for model inference. + +## Convert configuration file to protobuf binary. + +Firstly, the user should convert Paddle's model configuration file into a protobuf binary file. In each example directory, there is a file named `convert_protobin.sh`. It will convert `trainer_config.conf` into `trainer_config.bin`. + +The `convert_protobin.sh` is very simple, just invoke `dump_config` Python module to dump the binary file. The command line usages are: + +```bash +python -m paddle.utils.dump_config YOUR_CONFIG_FILE 'CONFIG_EXTRA_ARGS' --binary > YOUR_CONFIG_FILE.bin +``` + +## Initialize paddle + +```c++ +char* argv[] = {"--use_gpu=False"}; +paddle_init(1, (char**)argv); +``` + +We must initialize global context before we invoke other interfaces in Paddle. The initialize commands just like the `paddle_trainer` command line arguments. `paddle train --help`, will show the list of arguments. The most important argument is `use_gpu` or not. + +## Load network and parameters + +```c +paddle_gradient_machine machine; +paddle_gradient_machine_create_for_inference(&machine, config_file_content, content_size)); +paddle_gradient_machine_load_parameter_from_disk(machine, "./some_where_to_params")); +``` + +The gradient machine is a Paddle concept, which represents a neural network can be forwarded and backward. We can create a gradient machine fo model inference, and load the parameter files from disk. + +Moreover, if we want to inference in multi-thread, we could create a thread local gradient machine which shared the same parameter by using `paddle_gradient_machine_create_shared_param` API. Please reference `multi_thread` as an example. + +## Create input + +The input of a neural network is an `arguments`. The examples in this directory will show how to construct different types of inputs for prediction. Please look at `dense`, `sparse_binary`, `sequence` for details. + +## Get inference + +After invoking `paddle_gradient_machine_forward`, we could get the output of the neural network. The `value` matrix of output arguments will store the neural network output values. If the output is a `SoftmaxActivation`, the `value` matrix are the probabilities of each input samples. The height of output matrix is number of sample. The width is the number of categories. -- GitLab From 66230967502f26fb7c026901ea75abb9d3c3bce7 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Sun, 26 Mar 2017 16:56:57 +0800 Subject: [PATCH 0045/3256] Add Implementation documentation. Also change previous design file, make concept consistant. --- .../{why_plain_c.md => 00.why_plain_c.md} | 12 +- .../01.inference_implementation.md | 106 ++++++++++++++++++ 2 files changed, 112 insertions(+), 6 deletions(-) rename doc/design/multi_language_interface/{why_plain_c.md => 00.why_plain_c.md} (97%) create mode 100644 doc/design/multi_language_interface/01.inference_implementation.md diff --git a/doc/design/multi_language_interface/why_plain_c.md b/doc/design/multi_language_interface/00.why_plain_c.md similarity index 97% rename from doc/design/multi_language_interface/why_plain_c.md rename to doc/design/multi_language_interface/00.why_plain_c.md index a3f41ca7b93..7cb05f3ec01 100644 --- a/doc/design/multi_language_interface/why_plain_c.md +++ b/doc/design/multi_language_interface/00.why_plain_c.md @@ -70,20 +70,20 @@ extern "C" paddle_error paddle_matrix_shape(paddle_matrix matrix, uint64_t *width, uint64_t *height) { - auto m = (paddle::math::matrix*)(matrix); + auto m = (paddle::capi::CMatrix*)(matrix); *width = m->width(); *height = m->height(); } ``` -其中`paddle/math/matrix.hpp`文件内容为: +其中`paddle/capi/CMatrix.hpp`文件内容为: ```cpp namespace paddle { namespace math { -class Matrix { - //... +class CMatrix { + std::shared_ptr mat; }; } // namespace math @@ -113,6 +113,6 @@ class Matrix { | 手写多语言绑定 | 不使用SWIG | 使用SWIG需要多语言绑定的开发人员熟练掌握SWIG配置,社区参与困难。SWIG生成的代码不能保证多语言代码风格的一致性 | -## 简单实现 +## 实现 -TBD +参考[预测接口实现](./01.inference_implementation.md) diff --git a/doc/design/multi_language_interface/01.inference_implementation.md b/doc/design/multi_language_interface/01.inference_implementation.md new file mode 100644 index 00000000000..81dcdd437b0 --- /dev/null +++ b/doc/design/multi_language_interface/01.inference_implementation.md @@ -0,0 +1,106 @@ +# C-API 模型推断实现文档 + +本文档描述Paddle C-API的实现细节。Paddle C-API是多语言API的基础部分。Paddle需要暴露的API很多。先实现模型推断的API,通过模型推断API的实现作为一个样例,来进行讨论。至于为什么需要C-API,请参考[这里](./00.why_plain_c.md)。 + +## 暴露接口原则 + +1. 所有的接口均为C接口。即使用`extern "C"` +2. 除构造某种类型的函数(`paddle_matrix_create`等),其他函数均返回`paddle_error`。且调用时不能抛出异常或出现运行时错误。 +3. 所有类型名为`paddle_类型名`,所有与类型相关的函数,函数名为`paddle_类型名_函数名` +4. 如果某一个Paddle Core概念(GradientMachine/Matrix)需要被暴露到其他语言,那么 + * 为了暴露的接口尽量简单。只暴露概念的接口,而不暴露概念的实现。即暴露`GradientMachine`或者`Matrix`但不暴露`RecurrentGradientMachine`和`CpuSparseMatrix`。 + * 暴露这个概念必要函数。`必要`是指,即完成某一个任务的最少函数。 +5. 不在`capi`接口层做过多封装。 + * 如果某一个Paddle概念必须要暴露,但是又过于琐碎。不在`capi`这一层进行封装,而是直接修改Paddle Core。让Paddle核心中,这一概念不再琐碎。 + + +## 目录结构 + +```text +Paddle + `-- paddle + `-- capi + `-- examples # The example project for C-API. + `-- tests # unittests for C-API + `-- capi.h # C-API header file. + `-- capi_private.h # The shared header file between implementation sources. + `-- matrix.{h, cpp} + `-- gradient_machine.{h, cpp} + `-- ... +``` + + +Paddle的C-API目录结构如上图表所示。这个目录中除了`capi_private.h`之外的所有头文件,均会被安装到include/paddle路径下。C-API生成的二进制文件会被安装到`lib`目录下。即,安装后的目录结构为 + +```text +`-- include + `-- paddle + `-- capi.h + `-- matrix.h + `-- gradient_machine.h + `-- ... +`-- lib + `-- libpaddle_capi_shared.{so, dylib} # In mac, dynamic libary's file name extention is `dylib` + `-- libpaddle_capi_whole.a # static library for all symbols of Paddle. +``` + +## 实现方式 + +下面分别介绍某一类文件的实现方式。 + +### capi.h + +`capi.h`是用户使用C-API时所唯一需要引入的头文件。在`capi.h`中,引入了类型的头文件,`matrix.h`, `gradient_machine.h`。在引入其他类型的头文件时,使用相对路径的引用方式。即`#include "matrix.h"` + +### 具体某种类型的头文件 + +具体某种类型的头文件,即例如`matrix.h`,`gradient_machine.h`等。在这些头文件中,包含了某种类型的类型定义和暴露的全部函数。 + +这个头文件不假设其他文件的引用顺序,即使用户直接引用某种类型的头文件,也不应该报错(虽然不鼓励这样)。如果某一个类型需要引用另一个类型,例如`gradient_machine`需要引用`matrix`,则直接引入另一种类型的头文件,即`#include "matrix.h"`。 + +### capi_private.h + +`capi_prviate.h`是各个实现中共享的头文件,他主要包含了实际暴露的类型结构。在用户使用C-API时,Paddle的类型全部退化成`void *`,即`typedef paddle_matrix void*`。但,对于每种C-API暴露的类型,均是在`capi_private.h`中实现的结构体。 + +```cpp +struct CMatrix { + int type = MatrixType; + std::shared_ptr mat; +}; +``` + +通常,这个结构体包含两个项目。 + +* `type`是一个类型的标志。对于每种类型,type字段均不尽相同。这样,即使C-API接受的类型全是`void *`,我们也可以确定每一个参数的类型。 + + ```cpp + void some_c_api_function(void* some_instance) { + int* type = (int *) some_instance; + switch (*type) { + case MatrixType: + CMatrix* mat = (CMatrix *) some_instance; + ... + ... + } + } + ``` +* 这个结构体中的另一个项目是,Paddle Core中这一类型接口的智能指针(shared_ptr)。 + * 使用智能指针的原因是: 用户可以安全的释放某个C-API的实例,而不必在意Paddle Core是否还在使用这个实例。 + * 例如,用户通过C-API获得了神经网络的参数实例。当用户使用完这个参数后,直接删除这个参数即可。即便Paddle Core中的模型还在使用这个参数,这个参数也不会一并删除。 + +### 具体某种类型的实现文件 + +具体某种类型的实现文件,即`matrix.cpp`, `gradient_machine.cpp`等文件。在这些文件中,使用C++ 11实现了C-API的接口,并且使用`extern "C"`导出这些接口。在实现过程中,对输入参数的安全性进行了必要的判断,并将C-API接口的参数转发给`Paddle Core`。 + +### libpaddle\_capi_shared.{so, dylib} + +`libpaddle_capi_shared`是C-API导出的动态库。这个动态库的连接参数与Paddle的其他二进制(例如`paddle_traienr`)类似。用户可以直接使用这个动态库来引入Paddle C-API。具体使用方法为`-lpaddle_capi_shared`。 + +### libpaddle\_capi_whole.a + +`libpaddle_capi_whole`是C-API导出的静态库。这个静态库包含了Paddle的全部符号。他是将`libpaddle_gserver.a`, `libpaddle_math.a`, `libpaddle_capi.a`等全部静态库中的目标文件全部打包后产生的文件。具体使用方法为`--whole-archive -lpaddle_capi_whole --no-whole-archive`。 + + +### examples + +在样例中,使用`C99`开发了模型预测的样例代码。具体请参考[example/README.md](../../../paddle/capi/examples/README.md)。 -- GitLab From 505d20761f86f75dbe978e607bd9e4f2c37a3b06 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Sun, 26 Mar 2017 17:07:47 +0800 Subject: [PATCH 0046/3256] Add toc --- .../01.inference_implementation.md | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/doc/design/multi_language_interface/01.inference_implementation.md b/doc/design/multi_language_interface/01.inference_implementation.md index 81dcdd437b0..1324bfcaa31 100644 --- a/doc/design/multi_language_interface/01.inference_implementation.md +++ b/doc/design/multi_language_interface/01.inference_implementation.md @@ -2,6 +2,21 @@ 本文档描述Paddle C-API的实现细节。Paddle C-API是多语言API的基础部分。Paddle需要暴露的API很多。先实现模型推断的API,通过模型推断API的实现作为一个样例,来进行讨论。至于为什么需要C-API,请参考[这里](./00.why_plain_c.md)。 +## Table of Contents + * [C-API 模型推断实现文档](#c-api-模型推断实现文档) + * [暴露接口原则](#暴露接口原则) + * [目录结构](#目录结构) + * [实现方式](#实现方式) + * [capi.h](#capih) + * [具体某种类型的头文件](#具体某种类型的头文件) + * [capi_private.h](#capi_privateh) + * [具体某种类型的实现文件](#具体某种类型的实现文件) + * [libpaddle_capi_shared.{so, dylib}](#libpaddle_capi_sharedso-dylib) + * [libpaddle_capi_whole.a](#libpaddle_capi_wholea) + * [examples](#examples) + * [编译选项](#编译选项) + + ## 暴露接口原则 1. 所有的接口均为C接口。即使用`extern "C"` @@ -104,3 +119,13 @@ struct CMatrix { ### examples 在样例中,使用`C99`开发了模型预测的样例代码。具体请参考[example/README.md](../../../paddle/capi/examples/README.md)。 + +## 编译选项 + +C-API的编译选项默认关闭,打开这个编译选项,需要在cmake的时候,设置 + +```bash +cmake ${YOUR_SOURCE_ROOT} -DWITH_C_API=ON -DWITH_PYTHON=OFF -DWITH_SWIG_PY=OFF +``` + +编译C-API的时候推荐Paddle不嵌入Python解释器,也不生成`SWIG`接口,具体原因参考[这里](./00.why_plain_c.md)。 -- GitLab From e7bc8802d99439038079ca5d3835532e3af8f5f9 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Sun, 26 Mar 2017 17:13:06 +0800 Subject: [PATCH 0047/3256] Revert unchanged file. --- cmake/FindGflags.cmake | 582 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 582 insertions(+) create mode 100644 cmake/FindGflags.cmake diff --git a/cmake/FindGflags.cmake b/cmake/FindGflags.cmake new file mode 100644 index 00000000000..6587089ba38 --- /dev/null +++ b/cmake/FindGflags.cmake @@ -0,0 +1,582 @@ +# Ceres Solver - A fast non-linear least squares minimizer +# Copyright 2015 Google Inc. All rights reserved. +# http://ceres-solver.org/ +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# * Neither the name of Google Inc. nor the names of its contributors may be +# used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# +# Author: alexs.mac@gmail.com (Alex Stewart) +# + +# FindGflags.cmake - Find Google gflags logging library. +# +# This module will attempt to find gflags, either via an exported CMake +# configuration (generated by gflags >= 2.1 which are built with CMake), or +# by performing a standard search for all gflags components. The order of +# precedence for these two methods of finding gflags is controlled by: +# GFLAGS_PREFER_EXPORTED_GFLAGS_CMAKE_CONFIGURATION. +# +# This module defines the following variables: +# +# GFLAGS_FOUND: TRUE iff gflags is found. +# GFLAGS_INCLUDE_DIRS: Include directories for gflags. +# GFLAGS_LIBRARIES: Libraries required to link gflags. +# GFLAGS_NAMESPACE: The namespace in which gflags is defined. In versions of +# gflags < 2.1, this was google, for versions >= 2.1 it is +# by default gflags, although can be configured when building +# gflags to be something else (i.e. google for legacy +# compatibility). +# +# The following variables control the behaviour of this module when an exported +# gflags CMake configuration is not found. +# +# GFLAGS_PREFER_EXPORTED_GFLAGS_CMAKE_CONFIGURATION: TRUE/FALSE, iff TRUE then +# then prefer using an exported CMake configuration +# generated by gflags >= 2.1 over searching for the +# gflags components manually. Otherwise (FALSE) +# ignore any exported gflags CMake configurations and +# always perform a manual search for the components. +# Default: TRUE iff user does not define this variable +# before we are called, and does NOT specify either +# GFLAGS_INCLUDE_DIR_HINTS or GFLAGS_LIBRARY_DIR_HINTS +# otherwise FALSE. +# GFLAGS_INCLUDE_DIR_HINTS: List of additional directories in which to +# search for gflags includes, e.g: /timbuktu/include. +# GFLAGS_LIBRARY_DIR_HINTS: List of additional directories in which to +# search for gflags libraries, e.g: /timbuktu/lib. +# +# The following variables are also defined by this module, but in line with +# CMake recommended FindPackage() module style should NOT be referenced directly +# by callers (use the plural variables detailed above instead). These variables +# do however affect the behaviour of the module via FIND_[PATH/LIBRARY]() which +# are NOT re-called (i.e. search for library is not repeated) if these variables +# are set with valid values _in the CMake cache_. This means that if these +# variables are set directly in the cache, either by the user in the CMake GUI, +# or by the user passing -DVAR=VALUE directives to CMake when called (which +# explicitly defines a cache variable), then they will be used verbatim, +# bypassing the HINTS variables and other hard-coded search locations. +# +# GFLAGS_INCLUDE_DIR: Include directory for gflags, not including the +# include directory of any dependencies. +# GFLAGS_LIBRARY: gflags library, not including the libraries of any +# dependencies. + +# Reset CALLERS_CMAKE_FIND_LIBRARY_PREFIXES to its value when FindGflags was +# invoked, necessary for MSVC. +macro(GFLAGS_RESET_FIND_LIBRARY_PREFIX) + if (MSVC) + set(CMAKE_FIND_LIBRARY_PREFIXES "${CALLERS_CMAKE_FIND_LIBRARY_PREFIXES}") + endif (MSVC) +endmacro(GFLAGS_RESET_FIND_LIBRARY_PREFIX) + +# Called if we failed to find gflags or any of it's required dependencies, +# unsets all public (designed to be used externally) variables and reports +# error message at priority depending upon [REQUIRED/QUIET/] argument. +macro(GFLAGS_REPORT_NOT_FOUND REASON_MSG) + unset(GFLAGS_FOUND) + unset(GFLAGS_INCLUDE_DIRS) + unset(GFLAGS_LIBRARIES) + # Do not use unset, as we want to keep GFLAGS_NAMESPACE in the cache, + # but simply clear its value. + set(GFLAGS_NAMESPACE "" CACHE STRING + "gflags namespace (google or gflags)" FORCE) + + # Make results of search visible in the CMake GUI if gflags has not + # been found so that user does not have to toggle to advanced view. + mark_as_advanced(CLEAR GFLAGS_INCLUDE_DIR + GFLAGS_LIBRARY + GFLAGS_NAMESPACE) + + gflags_reset_find_library_prefix() + + # Note _FIND_[REQUIRED/QUIETLY] variables defined by FindPackage() + # use the camelcase library name, not uppercase. + if (Gflags_FIND_QUIETLY) + message(STATUS "Failed to find gflags - " ${REASON_MSG} ${ARGN}) + elseif (Gflags_FIND_REQUIRED) + message(FATAL_ERROR "Failed to find gflags - " ${REASON_MSG} ${ARGN}) + else() + # Neither QUIETLY nor REQUIRED, use no priority which emits a message + # but continues configuration and allows generation. + message("-- Failed to find gflags - " ${REASON_MSG} ${ARGN}) + endif () + return() +endmacro(GFLAGS_REPORT_NOT_FOUND) + +# Verify that all variable names passed as arguments are defined (can be empty +# but must be defined) or raise a fatal error. +macro(GFLAGS_CHECK_VARS_DEFINED) + foreach(CHECK_VAR ${ARGN}) + if (NOT DEFINED ${CHECK_VAR}) + message(FATAL_ERROR "Ceres Bug: ${CHECK_VAR} is not defined.") + endif() + endforeach() +endmacro(GFLAGS_CHECK_VARS_DEFINED) + +# Use check_cxx_source_compiles() to compile trivial test programs to determine +# the gflags namespace. This works on all OSs except Windows. If using Visual +# Studio, it fails because msbuild forces check_cxx_source_compiles() to use +# CMAKE_BUILD_TYPE=Debug for the test project, which usually breaks detection +# because MSVC requires that the test project use the same build type as gflags, +# which would normally be built in Release. +# +# Defines: GFLAGS_NAMESPACE in the caller's scope with the detected namespace, +# which is blank (empty string, will test FALSE is CMake conditionals) +# if detection failed. +function(GFLAGS_CHECK_GFLAGS_NAMESPACE_USING_TRY_COMPILE) + # Verify that all required variables are defined. + gflags_check_vars_defined( + GFLAGS_INCLUDE_DIR GFLAGS_LIBRARY) + # Ensure that GFLAGS_NAMESPACE is always unset on completion unless + # we explicitly set if after having the correct namespace. + set(GFLAGS_NAMESPACE "" PARENT_SCOPE) + + include(CheckCXXSourceCompiles) + # Setup include path & link library for gflags for CHECK_CXX_SOURCE_COMPILES. + set(CMAKE_REQUIRED_INCLUDES ${GFLAGS_INCLUDE_DIR}) + set(CMAKE_REQUIRED_LIBRARIES ${GFLAGS_LIBRARY} ${GFLAGS_LINK_LIBRARIES}) + # First try the (older) google namespace. Note that the output variable + # MUST be unique to the build type as otherwise the test is not repeated as + # it is assumed to have already been performed. + check_cxx_source_compiles( + "#include + int main(int argc, char * argv[]) { + google::ParseCommandLineFlags(&argc, &argv, true); + return 0; + }" + GFLAGS_IN_GOOGLE_NAMESPACE) + if (GFLAGS_IN_GOOGLE_NAMESPACE) + set(GFLAGS_NAMESPACE google PARENT_SCOPE) + return() + endif() + + # Try (newer) gflags namespace instead. Note that the output variable + # MUST be unique to the build type as otherwise the test is not repeated as + # it is assumed to have already been performed. + set(CMAKE_REQUIRED_INCLUDES ${GFLAGS_INCLUDE_DIR}) + set(CMAKE_REQUIRED_LIBRARIES ${GFLAGS_LIBRARY} ${GFLAGS_LINK_LIBRARIES}) + check_cxx_source_compiles( + "#include + int main(int argc, char * argv[]) { + gflags::ParseCommandLineFlags(&argc, &argv, true); + return 0; + }" + GFLAGS_IN_GFLAGS_NAMESPACE) + if (GFLAGS_IN_GFLAGS_NAMESPACE) + set(GFLAGS_NAMESPACE gflags PARENT_SCOPE) + return() + endif (GFLAGS_IN_GFLAGS_NAMESPACE) +endfunction(GFLAGS_CHECK_GFLAGS_NAMESPACE_USING_TRY_COMPILE) + +# Use regex on the gflags headers to attempt to determine the gflags namespace. +# Checks both gflags.h (contained namespace on versions < 2.1.2) and +# gflags_declare.h, which contains the namespace on versions >= 2.1.2. +# In general, this method should only be used when +# GFLAGS_CHECK_GFLAGS_NAMESPACE_USING_TRY_COMPILE() cannot be used, or has +# failed. +# +# Defines: GFLAGS_NAMESPACE in the caller's scope with the detected namespace, +# which is blank (empty string, will test FALSE is CMake conditionals) +# if detection failed. +function(GFLAGS_CHECK_GFLAGS_NAMESPACE_USING_REGEX) + # Verify that all required variables are defined. + gflags_check_vars_defined(GFLAGS_INCLUDE_DIR) + # Ensure that GFLAGS_NAMESPACE is always undefined on completion unless + # we explicitly set if after having the correct namespace. + set(GFLAGS_NAMESPACE "" PARENT_SCOPE) + + # Scan gflags.h to identify what namespace gflags was built with. On + # versions of gflags < 2.1.2, gflags.h was configured with the namespace + # directly, on >= 2.1.2, gflags.h uses the GFLAGS_NAMESPACE #define which + # is defined in gflags_declare.h, we try each location in turn. + set(GFLAGS_HEADER_FILE ${GFLAGS_INCLUDE_DIR}/gflags/gflags.h) + if (NOT EXISTS ${GFLAGS_HEADER_FILE}) + gflags_report_not_found( + "Could not find file: ${GFLAGS_HEADER_FILE} " + "containing namespace information in gflags install located at: " + "${GFLAGS_INCLUDE_DIR}.") + endif() + file(READ ${GFLAGS_HEADER_FILE} GFLAGS_HEADER_FILE_CONTENTS) + + string(REGEX MATCH "namespace [A-Za-z]+" + GFLAGS_NAMESPACE "${GFLAGS_HEADER_FILE_CONTENTS}") + string(REGEX REPLACE "namespace ([A-Za-z]+)" "\\1" + GFLAGS_NAMESPACE "${GFLAGS_NAMESPACE}") + + if (NOT GFLAGS_NAMESPACE) + gflags_report_not_found( + "Failed to extract gflags namespace from header file: " + "${GFLAGS_HEADER_FILE}.") + endif (NOT GFLAGS_NAMESPACE) + + if (GFLAGS_NAMESPACE STREQUAL "google" OR + GFLAGS_NAMESPACE STREQUAL "gflags") + # Found valid gflags namespace from gflags.h. + set(GFLAGS_NAMESPACE "${GFLAGS_NAMESPACE}" PARENT_SCOPE) + return() + endif() + + # Failed to find gflags namespace from gflags.h, gflags is likely a new + # version, check gflags_declare.h, which in newer versions (>= 2.1.2) contains + # the GFLAGS_NAMESPACE #define, which is then referenced in gflags.h. + set(GFLAGS_DECLARE_FILE ${GFLAGS_INCLUDE_DIR}/gflags/gflags_declare.h) + if (NOT EXISTS ${GFLAGS_DECLARE_FILE}) + gflags_report_not_found( + "Could not find file: ${GFLAGS_DECLARE_FILE} " + "containing namespace information in gflags install located at: " + "${GFLAGS_INCLUDE_DIR}.") + endif() + file(READ ${GFLAGS_DECLARE_FILE} GFLAGS_DECLARE_FILE_CONTENTS) + + string(REGEX MATCH "#define GFLAGS_NAMESPACE [A-Za-z]+" + GFLAGS_NAMESPACE "${GFLAGS_DECLARE_FILE_CONTENTS}") + string(REGEX REPLACE "#define GFLAGS_NAMESPACE ([A-Za-z]+)" "\\1" + GFLAGS_NAMESPACE "${GFLAGS_NAMESPACE}") + + if (NOT GFLAGS_NAMESPACE) + gflags_report_not_found( + "Failed to extract gflags namespace from declare file: " + "${GFLAGS_DECLARE_FILE}.") + endif (NOT GFLAGS_NAMESPACE) + + if (GFLAGS_NAMESPACE STREQUAL "google" OR + GFLAGS_NAMESPACE STREQUAL "gflags") + # Found valid gflags namespace from gflags.h. + set(GFLAGS_NAMESPACE "${GFLAGS_NAMESPACE}" PARENT_SCOPE) + return() + endif() +endfunction(GFLAGS_CHECK_GFLAGS_NAMESPACE_USING_REGEX) + +# ----------------------------------------------------------------- +# By default, if the user has expressed no preference for using an exported +# gflags CMake configuration over performing a search for the installed +# components, and has not specified any hints for the search locations, then +# prefer a gflags exported configuration if available. +if (NOT DEFINED GFLAGS_PREFER_EXPORTED_GFLAGS_CMAKE_CONFIGURATION + AND NOT GFLAGS_INCLUDE_DIR_HINTS + AND NOT GFLAGS_LIBRARY_DIR_HINTS) + message(STATUS "No preference for use of exported gflags CMake configuration " + "set, and no hints for include/library directories provided. " + "Defaulting to preferring an installed/exported gflags CMake configuration " + "if available.") + set(GFLAGS_PREFER_EXPORTED_GFLAGS_CMAKE_CONFIGURATION TRUE) +endif() + +if (GFLAGS_PREFER_EXPORTED_GFLAGS_CMAKE_CONFIGURATION) + # Try to find an exported CMake configuration for gflags, as generated by + # gflags versions >= 2.1. + # + # We search twice, s/t we can invert the ordering of precedence used by + # find_package() for exported package build directories, and installed + # packages (found via CMAKE_SYSTEM_PREFIX_PATH), listed as items 6) and 7) + # respectively in [1]. + # + # By default, exported build directories are (in theory) detected first, and + # this is usually the case on Windows. However, on OS X & Linux, the install + # path (/usr/local) is typically present in the PATH environment variable + # which is checked in item 4) in [1] (i.e. before both of the above, unless + # NO_SYSTEM_ENVIRONMENT_PATH is passed). As such on those OSs installed + # packages are usually detected in preference to exported package build + # directories. + # + # To ensure a more consistent response across all OSs, and as users usually + # want to prefer an installed version of a package over a locally built one + # where both exist (esp. as the exported build directory might be removed + # after installation), we first search with NO_CMAKE_PACKAGE_REGISTRY which + # means any build directories exported by the user are ignored, and thus + # installed directories are preferred. If this fails to find the package + # we then research again, but without NO_CMAKE_PACKAGE_REGISTRY, so any + # exported build directories will now be detected. + # + # To prevent confusion on Windows, we also pass NO_CMAKE_BUILDS_PATH (which + # is item 5) in [1]), to not preferentially use projects that were built + # recently with the CMake GUI to ensure that we always prefer an installed + # version if available. + # + # [1] http://www.cmake.org/cmake/help/v2.8.11/cmake.html#command:find_package + find_package(gflags QUIET + NO_MODULE + NO_CMAKE_PACKAGE_REGISTRY + NO_CMAKE_BUILDS_PATH) + if (gflags_FOUND) + message(STATUS "Found installed version of gflags: ${gflags_DIR}") + else(gflags_FOUND) + # Failed to find an installed version of gflags, repeat search allowing + # exported build directories. + message(STATUS "Failed to find installed gflags CMake configuration, " + "searching for gflags build directories exported with CMake.") + # Again pass NO_CMAKE_BUILDS_PATH, as we know that gflags is exported and + # do not want to treat projects built with the CMake GUI preferentially. + find_package(gflags QUIET + NO_MODULE + NO_CMAKE_BUILDS_PATH) + if (gflags_FOUND) + message(STATUS "Found exported gflags build directory: ${gflags_DIR}") + endif(gflags_FOUND) + endif(gflags_FOUND) + + set(FOUND_INSTALLED_GFLAGS_CMAKE_CONFIGURATION ${gflags_FOUND}) + + # gflags v2.1 - 2.1.2 shipped with a bug in their gflags-config.cmake [1] + # whereby gflags_LIBRARIES = "gflags", but there was no imported target + # called "gflags", they were called: gflags[_nothreads]-[static/shared]. + # As this causes linker errors when gflags is not installed in a location + # on the current library paths, detect if this problem is present and + # fix it. + # + # [1] https://github.com/gflags/gflags/issues/110 + if (gflags_FOUND) + # NOTE: This is not written as additional conditions in the outer + # if (gflags_FOUND) as the NOT TARGET "${gflags_LIBRARIES}" + # condition causes problems if gflags is not found. + if (${gflags_VERSION} VERSION_LESS 2.1.3 AND + NOT TARGET "${gflags_LIBRARIES}") + message(STATUS "Detected broken gflags install in: ${gflags_DIR}, " + "version: ${gflags_VERSION} <= 2.1.2 which defines gflags_LIBRARIES = " + "${gflags_LIBRARIES} which is not an imported CMake target, see: " + "https://github.com/gflags/gflags/issues/110. Attempting to fix by " + "detecting correct gflags target.") + # Ordering here expresses preference for detection, specifically we do not + # want to use the _nothreads variants if the full library is available. + list(APPEND CHECK_GFLAGS_IMPORTED_TARGET_NAMES + gflags-shared gflags-static + gflags_nothreads-shared gflags_nothreads-static) + foreach(CHECK_GFLAGS_TARGET ${CHECK_GFLAGS_IMPORTED_TARGET_NAMES}) + if (TARGET ${CHECK_GFLAGS_TARGET}) + message(STATUS "Found valid gflags target: ${CHECK_GFLAGS_TARGET}, " + "updating gflags_LIBRARIES.") + set(gflags_LIBRARIES ${CHECK_GFLAGS_TARGET}) + break() + endif() + endforeach() + if (NOT TARGET ${gflags_LIBRARIES}) + message(STATUS "Failed to fix detected broken gflags install in: " + "${gflags_DIR}, version: ${gflags_VERSION} <= 2.1.2, none of the " + "imported targets for gflags: ${CHECK_GFLAGS_IMPORTED_TARGET_NAMES} " + "are defined. Will continue with a manual search for gflags " + "components. We recommend you build/install a version of gflags > " + "2.1.2 (or master).") + set(FOUND_INSTALLED_GFLAGS_CMAKE_CONFIGURATION FALSE) + endif() + endif() + endif() + + if (FOUND_INSTALLED_GFLAGS_CMAKE_CONFIGURATION) + message(STATUS "Detected gflags version: ${gflags_VERSION}") + set(GFLAGS_FOUND ${gflags_FOUND}) + set(GFLAGS_INCLUDE_DIR ${gflags_INCLUDE_DIR}) + set(GFLAGS_LIBRARY ${gflags_LIBRARIES}) + + # gflags does not export the namespace in their CMake configuration, so + # use our function to determine what it should be, as it can be either + # gflags or google dependent upon version & configuration. + # + # NOTE: We use the regex method to determine the namespace here, as + # check_cxx_source_compiles() will not use imported targets, which + # is what gflags will be in this case. + gflags_check_gflags_namespace_using_regex() + + if (NOT GFLAGS_NAMESPACE) + gflags_report_not_found( + "Failed to determine gflags namespace using regex for gflags " + "version: ${gflags_VERSION} exported here: ${gflags_DIR} using CMake.") + endif (NOT GFLAGS_NAMESPACE) + else (FOUND_INSTALLED_GFLAGS_CMAKE_CONFIGURATION) + message(STATUS "Failed to find an installed/exported CMake configuration " + "for gflags, will perform search for installed gflags components.") + endif (FOUND_INSTALLED_GFLAGS_CMAKE_CONFIGURATION) +endif(GFLAGS_PREFER_EXPORTED_GFLAGS_CMAKE_CONFIGURATION) + +if (NOT GFLAGS_FOUND) + # Either failed to find an exported gflags CMake configuration, or user + # told us not to use one. Perform a manual search for all gflags components. + + # Handle possible presence of lib prefix for libraries on MSVC, see + # also GFLAGS_RESET_FIND_LIBRARY_PREFIX(). + if (MSVC) + # Preserve the caller's original values for CMAKE_FIND_LIBRARY_PREFIXES + # s/t we can set it back before returning. + set(CALLERS_CMAKE_FIND_LIBRARY_PREFIXES "${CMAKE_FIND_LIBRARY_PREFIXES}") + # The empty string in this list is important, it represents the case when + # the libraries have no prefix (shared libraries / DLLs). + set(CMAKE_FIND_LIBRARY_PREFIXES "lib" "" "${CMAKE_FIND_LIBRARY_PREFIXES}") + endif (MSVC) + + # Search user-installed locations first, so that we prefer user installs + # to system installs where both exist. + list(APPEND GFLAGS_CHECK_INCLUDE_DIRS + /usr/local/include + /usr/local/homebrew/include # Mac OS X + /opt/local/var/macports/software # Mac OS X. + /opt/local/include + /usr/include) + list(APPEND GFLAGS_CHECK_PATH_SUFFIXES + gflags/include # Windows (for C:/Program Files prefix). + gflags/Include ) # Windows (for C:/Program Files prefix). + + list(APPEND GFLAGS_CHECK_LIBRARY_DIRS + /usr/local/lib + /usr/local/homebrew/lib # Mac OS X. + /opt/local/lib + /usr/lib) + list(APPEND GFLAGS_CHECK_LIBRARY_SUFFIXES + gflags/lib # Windows (for C:/Program Files prefix). + gflags/Lib ) # Windows (for C:/Program Files prefix). + + # Search supplied hint directories first if supplied. + find_path(GFLAGS_INCLUDE_DIR + NAMES gflags/gflags.h + PATHS ${GFLAGS_INCLUDE_DIR_HINTS} + ${GFLAGS_CHECK_INCLUDE_DIRS} + PATH_SUFFIXES ${GFLAGS_CHECK_PATH_SUFFIXES}) + if (NOT GFLAGS_INCLUDE_DIR OR + NOT EXISTS ${GFLAGS_INCLUDE_DIR}) + gflags_report_not_found( + "Could not find gflags include directory, set GFLAGS_INCLUDE_DIR " + "to directory containing gflags/gflags.h") + endif (NOT GFLAGS_INCLUDE_DIR OR + NOT EXISTS ${GFLAGS_INCLUDE_DIR}) + + find_library(GFLAGS_LIBRARY NAMES gflags + PATHS ${GFLAGS_LIBRARY_DIR_HINTS} + ${GFLAGS_CHECK_LIBRARY_DIRS} + PATH_SUFFIXES ${GFLAGS_CHECK_LIBRARY_SUFFIXES}) + if (NOT GFLAGS_LIBRARY OR + NOT EXISTS ${GFLAGS_LIBRARY}) + gflags_report_not_found( + "Could not find gflags library, set GFLAGS_LIBRARY " + "to full path to libgflags.") + endif (NOT GFLAGS_LIBRARY OR + NOT EXISTS ${GFLAGS_LIBRARY}) + + # gflags typically requires a threading library (which is OS dependent), note + # that this defines the CMAKE_THREAD_LIBS_INIT variable. If we are able to + # detect threads, we assume that gflags requires it. + find_package(Threads QUIET) + set(GFLAGS_LINK_LIBRARIES ${CMAKE_THREAD_LIBS_INIT}) + # On Windows (including MinGW), the Shlwapi library is used by gflags if + # available. + if (WIN32) + include(CheckIncludeFileCXX) + check_include_file_cxx("shlwapi.h" HAVE_SHLWAPI) + if (HAVE_SHLWAPI) + list(APPEND GFLAGS_LINK_LIBRARIES shlwapi.lib) + endif(HAVE_SHLWAPI) + endif (WIN32) + + # Mark internally as found, then verify. GFLAGS_REPORT_NOT_FOUND() unsets + # if called. + set(GFLAGS_FOUND TRUE) + + # Identify what namespace gflags was built with. + if (GFLAGS_INCLUDE_DIR AND NOT GFLAGS_NAMESPACE) + # To handle Windows peculiarities / CMake bugs on MSVC we try two approaches + # to detect the gflags namespace: + # + # 1) Try to use check_cxx_source_compiles() to compile a trivial program + # with the two choices for the gflags namespace. + # + # 2) [In the event 1) fails] Use regex on the gflags headers to try to + # determine the gflags namespace. Whilst this is less robust than 1), + # it does avoid any interaction with msbuild. + gflags_check_gflags_namespace_using_try_compile() + + if (NOT GFLAGS_NAMESPACE) + # Failed to determine gflags namespace using check_cxx_source_compiles() + # method, try and obtain it using regex on the gflags headers instead. + message(STATUS "Failed to find gflags namespace using using " + "check_cxx_source_compiles(), trying namespace regex instead, " + "this is expected on Windows.") + gflags_check_gflags_namespace_using_regex() + + if (NOT GFLAGS_NAMESPACE) + gflags_report_not_found( + "Failed to determine gflags namespace either by " + "check_cxx_source_compiles(), or namespace regex.") + endif (NOT GFLAGS_NAMESPACE) + endif (NOT GFLAGS_NAMESPACE) + endif (GFLAGS_INCLUDE_DIR AND NOT GFLAGS_NAMESPACE) + + # Make the GFLAGS_NAMESPACE a cache variable s/t the user can view it, and could + # overwrite it in the CMake GUI. + set(GFLAGS_NAMESPACE "${GFLAGS_NAMESPACE}" CACHE STRING + "gflags namespace (google or gflags)" FORCE) + + # gflags does not seem to provide any record of the version in its + # source tree, thus cannot extract version. + + # Catch case when caller has set GFLAGS_NAMESPACE in the cache / GUI + # with an invalid value. + if (GFLAGS_NAMESPACE AND + NOT GFLAGS_NAMESPACE STREQUAL "google" AND + NOT GFLAGS_NAMESPACE STREQUAL "gflags") + gflags_report_not_found( + "Caller defined GFLAGS_NAMESPACE:" + " ${GFLAGS_NAMESPACE} is not valid, not google or gflags.") + endif () + # Catch case when caller has set GFLAGS_INCLUDE_DIR in the cache / GUI and + # thus FIND_[PATH/LIBRARY] are not called, but specified locations are + # invalid, otherwise we would report the library as found. + if (GFLAGS_INCLUDE_DIR AND + NOT EXISTS ${GFLAGS_INCLUDE_DIR}/gflags/gflags.h) + gflags_report_not_found( + "Caller defined GFLAGS_INCLUDE_DIR:" + " ${GFLAGS_INCLUDE_DIR} does not contain gflags/gflags.h header.") + endif (GFLAGS_INCLUDE_DIR AND + NOT EXISTS ${GFLAGS_INCLUDE_DIR}/gflags/gflags.h) + # TODO: This regex for gflags library is pretty primitive, we use lowercase + # for comparison to handle Windows using CamelCase library names, could + # this check be better? + string(TOLOWER "${GFLAGS_LIBRARY}" LOWERCASE_GFLAGS_LIBRARY) + if (GFLAGS_LIBRARY AND + NOT "${LOWERCASE_GFLAGS_LIBRARY}" MATCHES ".*gflags[^/]*") + gflags_report_not_found( + "Caller defined GFLAGS_LIBRARY: " + "${GFLAGS_LIBRARY} does not match gflags.") + endif (GFLAGS_LIBRARY AND + NOT "${LOWERCASE_GFLAGS_LIBRARY}" MATCHES ".*gflags[^/]*") + + gflags_reset_find_library_prefix() + +endif(NOT GFLAGS_FOUND) + +# Set standard CMake FindPackage variables if found. +if (GFLAGS_FOUND) + set(GFLAGS_INCLUDE_DIRS ${GFLAGS_INCLUDE_DIR}) + set(GFLAGS_LIBRARIES ${GFLAGS_LIBRARY} ${GFLAGS_LINK_LIBRARIES}) +endif (GFLAGS_FOUND) + +# Handle REQUIRED / QUIET optional arguments. +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(Gflags DEFAULT_MSG + GFLAGS_INCLUDE_DIRS GFLAGS_LIBRARIES GFLAGS_NAMESPACE) + +# Only mark internal variables as advanced if we found gflags, otherwise +# leave them visible in the standard GUI for the user to set manually. +if (GFLAGS_FOUND) + mark_as_advanced(FORCE GFLAGS_INCLUDE_DIR + GFLAGS_LIBRARY + GFLAGS_NAMESPACE + gflags_DIR) # Autogenerated by find_package(gflags) +endif (GFLAGS_FOUND) -- GitLab From ddbb610fd328046ab27e906fe661391485597593 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Mon, 27 Mar 2017 11:02:41 +0800 Subject: [PATCH 0048/3256] Find a bug about recommark. --- doc/design/multi_language_interface/00.why_plain_c.md | 2 +- .../multi_language_interface/01.inference_implementation.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/design/multi_language_interface/00.why_plain_c.md b/doc/design/multi_language_interface/00.why_plain_c.md index 7cb05f3ec01..4004f16daf2 100644 --- a/doc/design/multi_language_interface/00.why_plain_c.md +++ b/doc/design/multi_language_interface/00.why_plain_c.md @@ -115,4 +115,4 @@ class CMatrix { ## 实现 -参考[预测接口实现](./01.inference_implementation.md) +参考[Inference implementation](01.inference_implementation.md) diff --git a/doc/design/multi_language_interface/01.inference_implementation.md b/doc/design/multi_language_interface/01.inference_implementation.md index 1324bfcaa31..9db9ce1834d 100644 --- a/doc/design/multi_language_interface/01.inference_implementation.md +++ b/doc/design/multi_language_interface/01.inference_implementation.md @@ -1,6 +1,6 @@ # C-API 模型推断实现文档 -本文档描述Paddle C-API的实现细节。Paddle C-API是多语言API的基础部分。Paddle需要暴露的API很多。先实现模型推断的API,通过模型推断API的实现作为一个样例,来进行讨论。至于为什么需要C-API,请参考[这里](./00.why_plain_c.md)。 +本文档描述Paddle C-API的实现细节。Paddle C-API是多语言API的基础部分。Paddle需要暴露的API很多。先实现模型推断的API,通过模型推断API的实现作为一个样例,来进行讨论。至于为什么需要C-API,请参考[Why Plain C](./00.why_plain_c.md)。 ## Table of Contents * [C-API 模型推断实现文档](#c-api-模型推断实现文档) @@ -128,4 +128,4 @@ C-API的编译选项默认关闭,打开这个编译选项,需要在cmake的 cmake ${YOUR_SOURCE_ROOT} -DWITH_C_API=ON -DWITH_PYTHON=OFF -DWITH_SWIG_PY=OFF ``` -编译C-API的时候推荐Paddle不嵌入Python解释器,也不生成`SWIG`接口,具体原因参考[这里](./00.why_plain_c.md)。 +编译C-API的时候推荐Paddle不嵌入Python解释器,也不生成`SWIG`接口,具体原因参考[Why Plain C](./00.why_plain_c.md)。 -- GitLab From bb5fb0de594fe3398c64b399957ee3c31fbc4f07 Mon Sep 17 00:00:00 2001 From: "yi.wu" Date: Mon, 27 Mar 2017 20:12:18 +0800 Subject: [PATCH 0049/3256] update --- doc/design/cluster_design.md | 185 +++++++++++----------------- doc/design/images/arch.png | Bin 165032 -> 0 bytes doc/design/images/checkpointing.png | Bin 0 -> 183359 bytes doc/design/images/less_trainer.png | Bin 61124 -> 0 bytes doc/design/images/master.png | Bin 0 -> 166142 bytes doc/design/images/more_trainer.png | Bin 32517 -> 0 bytes doc/design/images/trainer.graffle | Bin 6434 -> 8566 bytes 7 files changed, 72 insertions(+), 113 deletions(-) delete mode 100644 doc/design/images/arch.png create mode 100644 doc/design/images/checkpointing.png delete mode 100644 doc/design/images/less_trainer.png create mode 100644 doc/design/images/master.png delete mode 100644 doc/design/images/more_trainer.png diff --git a/doc/design/cluster_design.md b/doc/design/cluster_design.md index 3555a050a4f..44f0591d77d 100644 --- a/doc/design/cluster_design.md +++ b/doc/design/cluster_design.md @@ -26,145 +26,104 @@ 1. 为了支持大量的训练任务和使用模型的应用在一个集群上,需要支持训练任务节点的伸缩。 1. 支持训练任务的前置任务和后置任务,支持训练任务的定时调度和对在线流式数据的处理 -## 模型参数数据备份 -为了实现parameter server集群可以容忍单点故障,须将每个模型参数的分片在集群中存储多个副本。虽然也可以考虑使用校验和的技术减少副本大小,但为了整体系统的简单,优先选择使用副本的方式。 - - - -上图显示了在3台parameter server中实现每个模型参数的分片均保存两个副本的状态。parameter 负责存储 -所有参数分片副本并在etcd中同步每个副本的状态。每个分片的多个副本中同时只有一个处于"master"状态, -处于"master"状态的副本是当前活动的副本。当一台parameter server故障时,集群中剩下的parameter server -会重新选举出新的"master"副本并继续提供服务。比如如果parameter server 3故障,仍然可以从parameter server 1和2中找出完整的3个副本。此时虽然性能会临时降低,但可以确保训练任务继续运行,只要有新的parameter server上线,并完成副本的重新分布,就可以恢复原先的集群状态。 - -用户在启动parameter server是可以指定副本的个数(>=1),副本越多容灾能力越强,越少性能越好。但通常不会 -使用>3个的副本配置。 - -etcd中数据存储格式为: -1. pserver集群状态`[CLUSTER_CHROOT]/pserver_cluster_status` - ```json - { - "cluster_status": "OK|UNHEALTHY|UNKNOWN" - "reason": "", - "nodes": [0,1,2,3] - } - ``` - -1. 每个pserver的状态: [CLUSTER_CHROOT]/pservers/[pserverid] - ```json - { - "id": 0, - "instance": "pserver1", - "status": "up", - "start_time": 1490184573.25, - "sync": true, - } - ``` -1. parameter分片信息: [CLUSTER_CHROOT]/pshards/[shardid]/[replicaid] - 比如上图显示的分片将生成下面的4个etcd路径: - ```bash - /pshards/0/0 - /pshards/0/1 - /pshards/1/0 - /pshards/1/1 - ``` - 每个replica的信息如下: - ```json - { - "id": 0, - "shardid": 0, - "created": 1490184573.25, - "modified": 1490184573.25, - "status": "master", # indicates the replica is in use - } - ``` - -## 数据一致性 -存在多个副本数据的情况下就需要考虑多个副本之间的数据一致性。如果使用数据强一致性(例如paxos/raft或两段式提交), -则在故障恢复时可以获得一个完整的数据集,但每次更新模型参数的性能会下降,因为需要保证多个副本都完全更新之后才算更新 -成功。如果使用异步同步(最终一致性),则在重新选举"master"副本时,可能得到的副本并没有完成数据同步。 - -本文档讨论使用两阶段提交(2PC)实现模型副本数据的更新。 -* 每个副本通常由多个parameter block组成,多个block之间可以并发更新,但更新同一个block需要保证顺序性。 -* 每次需要更新一个block的时候,trainer首先向存放"master"副本的服务器提交“准备更新”请求,"master"副本检查其他副本的状态并创建一个更新事务,然后返回OK。 -* trainer再向"master"发送变化部分的梯度数据和这份数据的id,然后"master"并发的更新本地和其他副本的模型数据,更新成功返回OK,如果有更新失败的节点,则执行"rollback",退回到更新前状态并返回错误代码。 - - - -## 模型数据检查点(Checkpointing) -模型数据检查点,可以在磁盘上保存一份存储在parameter server内存中的模型数据的完整镜像。在一个不可中断并缺少备份的训练任务中,可以通过阶段性的在每个parameter server的 ***本地磁盘/分布式存储挂载点*** 保存检查点快照达到容灾的目的,比如每个pass或每n个mini-batch保存一次快照。在出现单点故障时,只需要恢复这台节点,或者将这台节点迁移到另一个节点并启动即可恢复训练任务。 +## 模型参数检查点(Checkpointing) +模型数据检查点的实现,可以有效的避免parameter server的单点或多点同时故障。模型参数检查点通过定期向磁盘上保存一份存储在parameter server内存中的模型数据的完整镜像,来保证训练过程可以从中间状态重新启动。在一个不可中断并缺少备份的训练任务中,可以通过阶段性的在每个parameter server的 ***本地磁盘/分布式存储挂载点*** 保存检查点快照达到容灾的目的,比如每个pass或每n个mini-batch保存一次快照。在出现单点故障时,只需要恢复这台节点,或者将这台节点迁移到另一个节点并启动即可恢复训练任务。 + + + +### 快照保存的设计如下: + +前置要求: +* 所有parameter server在etcd上注册自己的id节点为TTL节点`/paddle/pservers/[id]`,并保持心跳。同时使用watcher监听`/paddle/pservers`目录,监听parameter server增加或丢失的消息。 +* 所有trainers在etcd `/paddle/trainers/[id]` 下注册节点。并监听暂停信号:`/paddle/trainers/pause`(监听节点创建和删除),`re-fetch` 信号。trainer在收到pause创建的信号之后,需要保存trainer的reader所读取的文件信息(文件名/文件元数据),和读取的offset到:`/paddle/trainers/[id]`的内容中。 + +程序流程: +1. 满足条件""每个pass或每n个mini-batch"时,parameter server原子写入`/paddle/trainers/pause`暂停所有trainer上传新的梯度 +2. parameter server在etcd服务中创建`/paddle/checkpoints/[snapshot uuid]/[parameter server id]`TTL节点,标识快照开始更新。然后开始向磁盘/存储服务中一个新的文件写入快照数据,并在写入过程中定时更新 etcd的checkpoint TTL节点已保证心跳。 +3. 任意一个parameter server完成检查点更新后,创建etcd目录`/paddle/checkpoints/[snapshot uuid]/finished/[parameter server id]`,写入完成的timestamp。然后检查是否所有的parameter server都完成。如果是,跳到第5步;否则循环等待。 +4. 如果在任意时间点,收到parameter server增加或丢失的消息,则需要回滚整个集群训练过程到上一个检查点: + + * 如果没有处在暂停状态,则暂停所有的参数更新 + * 删除etcd中`/paddle/checkpoints/[snapshot uuid]`的路径,清理没有成功执行的检查点任务。 + * 从etcd中读取检查点的uuid和timestamp,然后解析所有存储在磁盘上的检查点文件(可能有多个文件),判断对应uuid是否相同,如果都不同,则报错退出(FATAL error)。如果有相同的文件,则加载这个检查点文件,并覆盖内存中的参数。 + * 原子性创建etcd节点:`/paddle/trainer/re-fetch` (即多个parameter server不重复创建),通知trainer重新获取参数 + * 删除`/paddle/trainers/pause` 节点,重新开启训练过程,trainer需要从`/paddle/checkpoints/latest`中找到上一个检查点的file和对应的offset,并将reader重新设置到这个位置。 + +5. 尝试获取`/paddle/checkpoints/finish_lock`分布式锁(使用etcd3或者客户端wrapper)。获取锁之后,更新 `/paddle/checkpoints/latest`的内容为最新的checkpoint的uuid,timestamp;从`/paddle/trainers/[id]`中获取file和offset并更新到`/paddle/checkpoints/latest/files/[id]`中;删除每个pserver的上一个snapshot文件;释放锁;删除`/paddle/trainers/pause`节点。 这里需要用户额外注意,在您的实际环境中,训练任务的运行可能会占满trainer和parameter server之间的网络带宽,如果parameter server此时还需要通过网络访问分布式存储以保存快照,可能会造成网络拥塞,而出现阶段性的运行停滞。 -## 训练数据的存储和分发 -生产环境中的训练数据集通常体积很大,并被存储在诸如Hadoop HDFS, Ceph, AWS S3之类的分布式存储之上。这些分布式存储服务通常会把数据切割成多个分片分布式的存储在多个节点之上,而多个trainer通常也需要预先完成文件的切割。但通常的方法是从HDFS上将数据拷贝到训练集群,然后切割到多个trainer服务器上,如图(Mount/Copy): +### ETCD文件一览 +***注:TTL节点表示这个节点在创建者消失时,在TTL时间内也会消失*** - +* `/paddle/pservers/[id]`: TTL节点。id是parameter server的id,保存parameter server的信息。 +* `/paddle/checkpoints/latest`: 最新的checkpoint的信息。json格式保存timestamp, uuid +* `/paddle/checkpoints/latest/files/[trainer id]`: 保存最新的checkpoint对应的每个trainer读取数据的文件和offset +* `/paddle/checkpoints/[snapshot uuid]/[parameter server id]`: TTL节点。uuid是checkpoint生成的唯一snapshot id +* `/paddle/checkpoints/[snapshot uuid]/finished/[parameter server id]`: 同上 +* `/paddle/trainers/[id]`: TTL节点,保存trainer信息。如果发生全局暂停,则节点中以json格式保存trainer正在读取的文件和offset +* `/paddle/trainers/pause`: 控制trainer暂停上传梯度 +* `/paddle/trainers/re-fetch`: 控制trainer重新从parameter server读取参数并覆盖本地参数 -考虑到HDFS实际上已经完成了数据切割的任务,而且如果存在前置的数据预处理任务(Map-Reduce或Spark SQL),这些任务的输出也都存放于HDFS之上,则trainer可以直接调用HDFS LowLevel API,从元数据节点获得每个数据分片存储的位置,直接获得分片。 +## 训练数据的存储和分发 -***注:每个数据分片保存多个mini_batch*** +### 现在的方法 +生产环境中的训练数据集通常体积很大,并被存储在诸如Hadoop HDFS, Ceph, AWS S3之类的分布式存储之上。这些分布式存储服务通常会把数据切割成多个分片分布式的存储在多个节点之上,而多个trainer通常也需要预先完成文件的切割。但通常的方法是从HDFS上将数据拷贝到训练集群,然后切割到多个trainer服务器上,但这样的效率是底下的。如图(Mount/Copy): -进一步优化,trainer可以寻找在物理网络拓扑中离自己最近的一个分片副本获取数据。 + -trainer和训练数据分片的均衡: -* 当trainer >= 数据分片: - trainer个数和数据分片个数相同时,可以获得最高的吞吐量。当trainer个数再大于分片数量时,必然有Trainer获取不到数据分片,处于等待状态。但对整体任务运行没有影响,等待的trainer也会消耗很小的资源。 +### 期望的方法 - +考虑到HDFS实际上已经完成了数据切割的任务,而且如果存在前置的数据预处理任务(Map-Reduce或Spark SQL),这些任务的输出也都存放于HDFS之上,则trainer可以直接调用HDFS LowLevel API,从元数据节点获得每个数据分片存储的位置,直接获得分片。 -* 当trainer < 数据分片 - 每个trainer负责多个数据分片,轮询方式完成一个分片训练之后开始下一个分片。 +***注:每个数据分片保存多个mini_batch*** - +我们将使用如下的设计完成数据分发: -## 故障恢复 -在通用集群上运行的应用和任务,通常需要有能够自动伸缩的能力,这样在在线集群进行扩容时,可以适当的减小训练任务的资源(进程数/并发数),而不需要直接停止训练任务,修改参数后重新提交任务。 + -然而对于常见的在线服务(比如Web服务,RPC服务等),是可以“无状态”伸缩的,即扩容和缩容只需要增删对应的节点,集群能力就可以自动伸缩,Web服务的每个节点不会维护自身的状态变化和自身的数据,这些数据通常会借由外部的存储或服务完成,如MySQL,Redis等。而对于训练任务来说,每个parameter server都需要保存状态(mini-batch id)和数据(parameters),在增删节点的时候都会涉及到数据重新分布(re-sharding)和处理数据同步的问题。 +如图,数据存储在分布式文件系统中,并将预处理之后的文件切割成3个block存储在不同的机器上。在训练任务开始时,master读取这个分布式文件的元数据,并将一个block分配给一个trainer,然后将分配信息写入etcd中。随后trainer从etcd中获取到数据的分配信息并开始执行训练。一个block数据训练完成后,master负责在将新的block分配给一个trainer(途中虚线所示)。 -用户只要根据实际训练任务场景,配置parameter server和trainer的初始节点个数,最大节点个数和最小节点个数,模型副本个数,是否开启检查点等配置项,即可配置并启动一个可以容灾的训练集群。具体的过程如下: +master不会直接发送数据给Trainer而是负责协调训练数据的分配,并以ETCD为协调中心。所以master是一个无状态程序,任务运行过程中,master停止后只需要重新启动即可。 -1. 配置parameter server和trainer的初始节点个数、最大节点个数、最小节点个数、模型副本个数、是否开启检查点等配置以及训练任务相关配置。 -1. 启动parameter server和trainer,每个实例会在etcd中注册一个带TTL(过期时间)的节点,并每隔一段时间(` +|___| |___| |___| + ^ + | +parameter block +需要: + hash to map to +parameter block --------> 128~1024 slots --------> parameter servers +``` -* 测试任务/极短训练任务:如果训练任务在几十分钟或小时级别可以运行完成,可以考虑不开启副本也不开启检查点。 -* 短期训练任务/测试任务:训练任务运行时间如果在数小时或数天范围,可以考虑只使用一个副本(每个slot只保存一份),并开启检查点。在这个时长内出现不可恢复的硬件故障的概率极低。 -* 大型训练任务:训练时间以周或月为单位。建议开启多个副本和检查点。这样可以在任意一个pass停止任务,并重新从这个pass开始训练。或者在通用集群运行时,可以考虑动态扩容和缩容。 +接口完成先发送信号暂停训练任务,保存参数的checkpoint,然后重新开启训练。这样可以避免程序bug导致的数据不同步问题出现。 ## 实现考虑 由于两阶段提交和数据备份同步、选举部分实现比较复杂,可以考虑使用一些开源库函数,比如2pc,raft库等,后期在优化过程中逐步替换。 diff --git a/doc/design/images/arch.png b/doc/design/images/arch.png deleted file mode 100644 index 659e340388f63f689f2a0112957f8c9dacf05b20..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 165032 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

    J8xt_JLB^gDZ59 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%0ABU&`~IFfgZU4)~zV% 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 diff --git a/doc/design/images/checkpointing.png b/doc/design/images/checkpointing.png new file mode 100644 index 0000000000000000000000000000000000000000..c221e8474f90f37e31416cbb19c9452207a0d14c GIT binary patch literal 183359 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�>}C^Zm=nQH~U> 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 ZqXFu^_z{ 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
  • |Q{Vz`&tDs3t{9vLTploku} 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 -- GitLab From 899caa6ddadf5b21341bb12cf1897cb2764fef83 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Wed, 19 Apr 2017 20:29:43 +0800 Subject: [PATCH 0068/3256] Fix Production Docker Image's mirror settings. --- paddle/scripts/docker/build.sh | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/paddle/scripts/docker/build.sh b/paddle/scripts/docker/build.sh index a4b63f90ec8..e4c322bb181 100644 --- a/paddle/scripts/docker/build.sh +++ b/paddle/scripts/docker/build.sh @@ -75,9 +75,8 @@ fi paddle version -# generate production docker image Dockerfile -if [ ${USE_MIRROR} ]; then - MIRROR_UPDATE="sed 's@http:\/\/archive.ubuntu.com\/ubuntu\/@mirror:\/\/mirrors.ubuntu.com\/mirrors.txt@' -i /etc/apt/sources.list && \\" +if [[ -n ${APT_MIRROR} ]]; then + MIRROR_UPDATE="sed -i '${APT_MIRROR}' /etc/apt/sources.list && \\" else MIRROR_UPDATE="\\" fi -- GitLab From 956217887fee6403caec9f4bc047237c8f5b9fcc Mon Sep 17 00:00:00 2001 From: qiaolongfei Date: Wed, 19 Apr 2017 22:08:54 +0800 Subject: [PATCH 0069/3256] support save parameter in trainer --- demo/word2vec/api_train_v2.py | 1 + python/paddle/v2/trainer.py | 14 ++++++++++++++ 2 files changed, 15 insertions(+) diff --git a/demo/word2vec/api_train_v2.py b/demo/word2vec/api_train_v2.py index eb61a7250fb..604adba192e 100644 --- a/demo/word2vec/api_train_v2.py +++ b/demo/word2vec/api_train_v2.py @@ -69,6 +69,7 @@ def main(): def event_handler(event): if isinstance(event, paddle.event.EndIteration): if event.batch_id % 100 == 0: + trainer.save_parameter("output", "batch-" + str(event.batch_id)) result = trainer.test( paddle.batch( paddle.dataset.imikolov.test(word_dict, N), 32)) diff --git a/python/paddle/v2/trainer.py b/python/paddle/v2/trainer.py index 552c6690a60..028f25a0467 100644 --- a/python/paddle/v2/trainer.py +++ b/python/paddle/v2/trainer.py @@ -1,4 +1,6 @@ import collections +import gzip +import os import py_paddle.swig_paddle as api @@ -96,6 +98,18 @@ class SGD(object): self.__gradient_machine__.prefetch(in_args) self.__parameter_updater__.getParametersRemote() + def save_parameter(self, dir_name, file_name): + if not os.path.exists(dir_name): + os.makedirs(dir_name) + param_file_name = dir_name + "/" + file_name + '.tar.gz' + assert not os.path.exists(param_file_name) + self.__parameter_updater__.catchUpWith() + self.__parameter_updater__.apply() + self.__parameter_updater__.getParametersRemote(True, True) + with gzip.open(param_file_name, 'w') as f: + self.__parameters__.to_tar(f) + self.__parameter_updater__.restore() + def train(self, reader, num_passes=1, event_handler=None, feeding=None): """ Training method. Will train num_passes of input data. -- GitLab From 1fa58b005af0b6f09c31af690f05ceaa729732d3 Mon Sep 17 00:00:00 2001 From: typhoonzero Date: Thu, 20 Apr 2017 14:18:46 +0800 Subject: [PATCH 0070/3256] update design --- doc/design/cluster_train/README.md | 8 +++-- doc/design/cluster_train/checkpointing.md | 39 +++------------------ doc/design/cluster_train/data_dispatch.md | 42 ++++++++++++++--------- 3 files changed, 35 insertions(+), 54 deletions(-) diff --git a/doc/design/cluster_train/README.md b/doc/design/cluster_train/README.md index 6a94a82825e..84678481ccc 100644 --- a/doc/design/cluster_train/README.md +++ b/doc/design/cluster_train/README.md @@ -17,12 +17,16 @@ A training job will be created once user asks Paddle cloud to train a model. The 1. the *master process*, which dispatches tasks to 1. one or more *trainer processes*, which run distributed training and synchronize gradients/models via -1. one or more *parameter server processes*, where each holds a shard of the global model. +1. one or more *parameter server processes*, where each holds a shard of the global model, and receive the uploaded gradients from every *trainer process*, so they can run the optimize functions to update their parameters. Their relation is illustrated in the following graph: +By coordinate these processes, paddle can complete the procedure of training neural networks using SGD. Paddle can support both "synchronize SGD" and "asynchronize SGD". + +When training with "sync SGD", paddle parameter servers use barriers to wait for all trainers to finish gradients update. When using "async SGD", parameter servers would not wait for all trainers, so training and parameter optimize will run in parallel. parameter servers will not depend on each other, they will receive the gradients update in parrallel; Also trainers will not depend on each other, run training jobs in parrallel. Using asyc SGD will be faster when training, but parameters on one of the parameter server will be newer than the other, but this will introduce more Randomness. + ### Master Process The master process will: @@ -130,7 +134,7 @@ When the trainer is started by the Kubernetes, it executes the following steps a If trainer's etcd lease expires, it will try set key `/trainer/` again so that the master process can discover the trainer again. -Whenever a trainer fails, the master process is responsible to schedule the failed task back to "todo queue". then kubernetes will try to start the trainer somewhere else, then the recovered trainer will try to fetch new task to continue the training. +When a trainer fails, Kuberentes would try to restart it. The recovered trainer would fetch tasks from the TODO queue and go on training. ### Parameter Server Process diff --git a/doc/design/cluster_train/checkpointing.md b/doc/design/cluster_train/checkpointing.md index df2dc81c86e..0a2682899c4 100644 --- a/doc/design/cluster_train/checkpointing.md +++ b/doc/design/cluster_train/checkpointing.md @@ -1,33 +1,5 @@ -# Paddle大规模分布式训练设计 - -## 概览 -参考[这里](./README.md) - -## 分布式训练架构 - -常见的深度学习分布式训练的架构如图: - - - -为了完成一个深度学习的训练任务,集群中会运行多个trainer和parameter server,每个trainer启动时,会先尝试从parameter server集群下载最新的参数,然后以mini-batch为单位读取训练数据集中的一部分数据(Data shard)。trainer会在训练过程中持续与parameter server通讯,上传计算出来的梯度以及下载最新的模型。 - -每个parameter server保存所有parameter的一个分片(Global model shard),并负责接受所有trainer发送的梯度,完成SGD和优化算法,然后发送更新后的parameter到每个trainer。 - -这样,通过trainer和parameter server的分布式协作,可以完成神经网络的SGD方法的训练。Paddle可以同时支持同步SGD(synchronize SGD)和异步SGD(asynchronize SGD)。 - -在使用同步SGD训练神经网络时,Paddle使用同步屏障(barrier),使梯度的提交和参数的更新按照顺序方式执行。在异步SGD中,则并不会等待所有trainer提交梯度才更新参数,这样极大的提高了计算的并行性:parameter server之间不相互依赖,并行的接收梯度和更新参数,parameter server也不会等待trainer全部都提交梯度之后才开始下一步,trainer之间也不会相互依赖,并行的执行模型的训练。可以看出,虽然异步SGD方式会提高参数更新并行度, 但是并不能保证参数同步更新,在任意时间某一台parameter server上保存的参数可能比另一台要更新,与同步SGD相比,梯度会有噪声。 - -在上面的分布式计算模型中,使用异步SGD比同步SGD可以一定程度的提供训练任务的容灾性。假设在某一时刻,一个trainer进程停止工作,其他的trainer仍然可以完成对部分数据的训练。 - -参考上面所描述的Paddle实现细节,可以进一步的优化以下方面: -1. 目前模型的参数是保存在parameter server进程的内存中的。在同步SGD或异步SGD训练过程中任意一台parameter server不能异常退出,否则参数丢失,训练不能继续执行。需要考虑每个模型分片(model shard)保存多个副本(replica)防止parameter server单点故障。 -1. 不能在一个训练任务中动态的增加或减少Trainer个数或parameter个数(异步SGD是否可以增加Trainer?) -1. 在同步SGD训练过程中,需要保证参数更新满足事务性操作。即可能在更新参数过程中,存放这个参数的shard所在的服务器故障,就需要rollback并重新更新这个参数shard的其他存活副本。 -1. 为了支持大量的训练任务和使用模型的应用在一个集群上,需要支持训练任务节点的伸缩。 -1. 支持训练任务的前置任务和后置任务,支持训练任务的定时调度和对在线流式数据的处理 - ## 模型参数检查点(Checkpointing) -模型数据检查点的实现,可以有效的避免parameter server的单点或多点同时故障。模型参数检查点通过定期向磁盘上保存一份存储在parameter server内存中的模型数据的完整镜像,来保证训练过程可以从中间状态重新启动。在一个不可中断并缺少备份的训练任务中,可以通过阶段性的保存每个parameter server的数据快照(snapshot)到 ***分布式存储服务/分布式存储挂载点*** 达到容灾的目的,比如每隔10分钟或1小时保存最新的快照,并删除更早的快照。在出现单点故障时,只需要恢复这台节点,或者将这台节点迁移到另一个节点并启动即可恢复训练任务。 +模型数据检查点的实现,可以有效的避免parameter server的单点或多点同时故障。模型参数检查点通过定期向磁盘上保存一份存储在parameter server内存中的模型数据的完整镜像,来保证训练过程可以从中间状态重新启动。在一个不可中断并缺少备份的训练任务中,可以通过阶段性的保存每个parameter server的数据快照(snapshot)到 ***分布式存储服务*** 达到容灾的目的,比如每隔10分钟最新的快照,并删除更早的快照。在出现单点故障时,只需要恢复这台节点,或者将这台节点迁移到另一个节点并启动即可恢复训练任务。 @@ -36,18 +8,15 @@ 说明: * parameter server在集群中启动后,自动挂载分布式存储目录,并把快照保存到这个目录下。 -* 所有parameter server和trainer在etcd上注册自己的id节点为TTL节点`/ps/[id]`和`/trainer/[id]`,并保持心跳。 -* ***注:trainer在故障恢复后,master会将失败的task重新分配给恢复的trainer执行。这样会引入更大的随机性。*** * ***注:parameter server在保存检查点时,利用了Linux内核的“写时复制”技术,在fork的进程中保存检查点,原进程可以继续接收trainer的梯度更新请求,而不影响检查点数据的保存。*** -* ***注:每个parameter server的检查点各自独立保存,暂时不考虑多个parameter server同步的保存一个特定时间点的全局检查点,同样会引入随机性。*** - +* ***注:每个parameter server的检查点各自独立保存,暂时不考虑多个parameter server同步的保存一个特定时间点的全局检查点,因为这样做也没法保证消除随机性。*** 检查点保存程序流程: -1. 如果满足条件""每个pass或每n个mini-batch"时,parameter server会`fork`自己,子进程中执行保存检查点任务,父进程继续工作。如果已经有子进程在进行保存检查点工作,则忽略。 +1. 如果满足条件""每个pass或每n个mini-batch"时,parameter server会锁住保存parameter的内存,开始保存检查点。如果已经正在执行保存检查点的任务,则忽略。 2. parameter server生成一个UUID,向指定的目录中一个新的文件(文件名为此UUID)写入快照数据。在快照写入完成后,计算这个文件的MD5 sum。然后在etcd的`/checkpoints/[pserver_id]`中写入json内容:`{"uuid": [UUID], "md5", "MD5 sum", "timestamp": xxxx}`。 3. 删除磁盘目录中不是当前uuid的快照文件。 -4. 关闭fork出来的进程。 +4. 释放对paramters内存的锁定。 这里需要用户额外注意,在您的实际环境中,训练任务的运行可能会占满trainer和parameter server之间的网络带宽,如果parameter server此时还需要通过网络访问分布式存储以保存快照,可能会造成网络拥塞,而出现阶段性的运行停滞。 diff --git a/doc/design/cluster_train/data_dispatch.md b/doc/design/cluster_train/data_dispatch.md index 7bd01cf2d27..ef2baf724d7 100644 --- a/doc/design/cluster_train/data_dispatch.md +++ b/doc/design/cluster_train/data_dispatch.md @@ -7,7 +7,9 @@ * Paddle训练任务 * 在线模型预测服务 - + + +在上图中显示了在一个实际生产环境中的应用(人脸识别)的数据流图。生产环境的日志数据会通过实时流的方式(Kafka)和离线数据的方式(HDFS)存储,并在集群中运行多个分布式数据处理任务,比如流式数据处理(online data process),离线批处理(offline data process)完成数据的预处理,提供给paddle作为训练数据。用于也可以上传labeled data到分布式存储补充训练数据。在paddle之上运行的深度学习训练输出的模型会提供给在线人脸识别的应用使用。 ### 训练数据的存储 @@ -15,7 +17,7 @@ 在Kubernetes上运行的不同的计算框架,可以通过Volume或PersistentVolume挂载存储空间到每个容器中。 -在存储中的共享位置,需要保存PaddlePaddle book中的所有dataset数据,并且可以被提交的job直接使用。 +在GlusterFS存储系统中的公开目录,需要保存一些预置的公开数据集(比如MNIST, BOW, imagenet数据集等),并且可以被提交的job直接使用。 ### 上传训练文件 @@ -25,15 +27,15 @@ paddle upload train_data.list ``` -其中`.list`文件描述了训练数据的文件和对应的label,对于图像类数据,`.list文件`样例如下,每一行包含了图片文件的路径和其label: +其中`.list`文件描述了训练数据的文件和对应的label,对于图像类数据,`.list文件`样例如下,每一行包含了图片文件的路径和其label(用tab分隔开): ``` -/data/image1.jpg 1 -/data/image1.jpg 5 -/data/image1.jpg 2 -/data/image1.jpg 5 -/data/image1.jpg 1 -/data/image1.jpg 8 +./data/image1.jpg 1 +./data/image2.jpg 5 +./data/image3.jpg 2 +./data/image4.jpg 5 +./data/image5.jpg 1 +./data/image6.jpg 8 ... ``` @@ -48,20 +50,26 @@ L' inflation accélérée , mesurée dans la zone euro , est due principale ### 使用reader -使用v2 API编写训练任务是,可以编写如下简单的reader,返回文件中的各列,然后在调用`trainer.train()`时传入,完成训练数据的读取: +用户在使用v2 API编写训练任务时,可以使用paddle内置的reader完成对GlusterFS存储中的训练数据的读取,返回文件中的各列,然后在调用`trainer.train()`时传入,完成训练数据的读取: ```python -def train(): - fp = open("/glusterfs/mount/dir/yourfile_%d.list" % TRAINER_ID, "r") +reader = paddle.dist.reader("dataset-name") +batch_reader = paddle.batch(paddle.dataset.mnist.train(), 128) +trainer.train(batch_reader, ...) +``` - def reader(): - for l in fp: - yield l[:-1].split("\t") +trainer.train内部会获取reader的内容: - return reader ``` +def paddle.train(batch_reader): + r = batch_reader() # create a interator for one pass of data + for batch in r: + # train +``` + +这里面batch是含有128个data instance的mini-batch。每一个data instance会是一个tuple,tuple元素的顺序与`.list`文件文件中每一列的顺序是一致的。每一个data instance会是(raw_image_file_binary_data, label)。其中raw_image_file_binary_data是对应图像文件的没有解码的原始二进制数据,用户需要自己解码。label是文本类型(比如:“1“,”2“),这里用户需要的其实是整形,用户需要自己转换成整形。 ## TODO +### 支持将数据合并成内部的文件格式(key-value),方便sharding与顺序读取 ### 支持用户自定义的数据预处理job -### 支持SSTable格式的key-value数据 -- GitLab From 4acf82941c51c479b1dbad67b3ac894564615e89 Mon Sep 17 00:00:00 2001 From: typhoonzero Date: Thu, 20 Apr 2017 14:19:12 +0800 Subject: [PATCH 0071/3256] add picture --- .../src/paddle-cloud-in-data-center.png | Bin 0 -> 78576 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 doc/design/cluster_train/src/paddle-cloud-in-data-center.png diff --git a/doc/design/cluster_train/src/paddle-cloud-in-data-center.png b/doc/design/cluster_train/src/paddle-cloud-in-data-center.png new file mode 100644 index 0000000000000000000000000000000000000000..da5d1a77562480ad1d886f5f21dbd84001d3d508 GIT binary patch literal 78576 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 ea8{a!Bl Date: Thu, 20 Apr 2017 14:26:37 +0800 Subject: [PATCH 0072/3256] Fix ContextProjectionOpTest --- paddle/function/ContextProjectionOpTest.cpp | 28 +++++++++++---------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/paddle/function/ContextProjectionOpTest.cpp b/paddle/function/ContextProjectionOpTest.cpp index 0f5d6a848d4..1b25172ca5c 100644 --- a/paddle/function/ContextProjectionOpTest.cpp +++ b/paddle/function/ContextProjectionOpTest.cpp @@ -28,11 +28,12 @@ void testMatrixProjectionForward(int context_start, std::max(0, (int)(context_start + context_length - 1)); if (pad == 0) is_padding = false; - FunctionCompare test("ContextProjectionForward", - FuncConfig() - .set("context_length", context_length) - .set("context_start", context_start) - .set("begin_pad", std::max(0, -context_start))); + FunctionCompare test( + "ContextProjectionForward", + FuncConfig() + .set("context_length", context_length) + .set("context_start", context_start) + .set("begin_pad", (size_t)std::max(0, -context_start))); // prepare input arguments test.addSequence(SequenceIdArg(TensorShape{batch_size})); @@ -51,7 +52,7 @@ void testMatrixProjectionForward(int context_start, } void testMatrixProjectionBackward(int context_start, - int context_length, + size_t context_length, bool is_padding, size_t batch_size, size_t input_dim) { @@ -59,13 +60,14 @@ void testMatrixProjectionBackward(int context_start, std::max(0, (int)(context_start + context_length - 1)); if (pad == 0) is_padding = false; - FunctionCompare test("ContextProjectionBackward", - FuncConfig() - .set("context_length", context_length) - .set("context_start", context_start) - .set("begin_pad", std::max(0, -context_start)) - .set("is_padding", is_padding) - .set("total_pad", pad)); + FunctionCompare test( + "ContextProjectionBackward", + FuncConfig() + .set("context_length", context_length) + .set("context_start", context_start) + .set("begin_pad", (size_t)std::max(0, -context_start)) + .set("is_padding", is_padding) + .set("total_pad", pad)); // prepare input arguments test.addSequence(SequenceIdArg(TensorShape{batch_size})); -- GitLab From 2076a92fe444ef4b48fe6be6b064407d71dcfafe Mon Sep 17 00:00:00 2001 From: qijun Date: Thu, 20 Apr 2017 14:27:02 +0800 Subject: [PATCH 0073/3256] fix with_doc build option --- paddle/scripts/docker/build.sh | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/paddle/scripts/docker/build.sh b/paddle/scripts/docker/build.sh index e4c322bb181..b4eea83a5e5 100644 --- a/paddle/scripts/docker/build.sh +++ b/paddle/scripts/docker/build.sh @@ -29,7 +29,7 @@ rm *.deb 2>/dev/null || true cmake .. \ -DCMAKE_BUILD_TYPE=Release \ - -DWITH_DOC=${WITH_DOC:-OFF} \ + -DWITH_DOC=OFF \ -DWITH_GPU=${WITH_GPU:-OFF} \ -DWITH_AVX=${WITH_AVX:-OFF} \ -DWITH_SWIG_PY=ON \ @@ -99,6 +99,18 @@ ADD build/*.deb /usr/local/opt/paddle/deb/ RUN dpkg -i /usr/local/opt/paddle/deb/*.deb && \ rm -f /usr/local/opt/paddle/deb/*.deb && \ paddle version +if [ ${WITH_DOC} == "ON" ]; then + mkdir -p /paddle/build_doc + pushd /paddle/build_doc + cmake .. \ + -DWITH_DOC=ON \ + -DWITH_GPU=OFF \ + -DWITH_AVX=${WITH_AVX:-OFF} \ + -DWITH_SWIG_PY=ON \ + -DWITH_STYLE_CHECK=OFF + make paddle_docs + popd +fi ${CPU_DOCKER_PYTHON_HOME_ENV} ${DOCKERFILE_CUDNN_DSO} ${DOCKERFILE_GPU_ENV} -- GitLab From d75399d5e3493a2a527e9f2cd2b12a642f4280ea Mon Sep 17 00:00:00 2001 From: qijun Date: Thu, 20 Apr 2017 14:30:40 +0800 Subject: [PATCH 0074/3256] add build paddle_docs_cn --- 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 b4eea83a5e5..362f81a949c 100644 --- a/paddle/scripts/docker/build.sh +++ b/paddle/scripts/docker/build.sh @@ -108,7 +108,7 @@ if [ ${WITH_DOC} == "ON" ]; then -DWITH_AVX=${WITH_AVX:-OFF} \ -DWITH_SWIG_PY=ON \ -DWITH_STYLE_CHECK=OFF - make paddle_docs + make paddle_docs paddle_docs_cn popd fi ${CPU_DOCKER_PYTHON_HOME_ENV} -- GitLab From c4ceefa0bc011cb4bb939bd0bdb3db8167257c04 Mon Sep 17 00:00:00 2001 From: hedaoyuan Date: Thu, 20 Apr 2017 14:34:09 +0800 Subject: [PATCH 0075/3256] fix test_matrixCompare --- paddle/math/tests/test_matrixCompare.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/paddle/math/tests/test_matrixCompare.cpp b/paddle/math/tests/test_matrixCompare.cpp index dd19fe516fb..5210fe3fa1f 100644 --- a/paddle/math/tests/test_matrixCompare.cpp +++ b/paddle/math/tests/test_matrixCompare.cpp @@ -235,8 +235,10 @@ TEST(Matrix, unary) { testMatrixTranspose(height, width); testMatrixRotate(height, width); } - // inverse +// inverse +#ifdef PADDLE_USE_LAPACK testMatrixInverse(height); +#endif } } -- GitLab From 8565f94a2e89c2064abc25acd06fcde27c5f0fb8 Mon Sep 17 00:00:00 2001 From: qijun Date: Thu, 20 Apr 2017 14:38:10 +0800 Subject: [PATCH 0076/3256] fix dockerfile typo error --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 97947adf450..49dcc2ad969 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,7 +13,7 @@ ARG WITH_DOC ARG WITH_STYLE_CHECK ENV WOBOQ OFF -ENV WITH_GPU=${WITH_AVX:-OFF} +ENV WITH_GPU=${WITH_GPU:-OFF} ENV WITH_AVX=${WITH_AVX:-ON} ENV WITH_DOC=${WITH_DOC:-OFF} ENV WITH_STYLE_CHECK=${WITH_STYLE_CHECK:-OFF} -- GitLab From f36bfcd430578d8fa7a28905114a00a04be5373b Mon Sep 17 00:00:00 2001 From: qijun Date: Thu, 20 Apr 2017 14:47:21 +0800 Subject: [PATCH 0077/3256] add install doxygen --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 97947adf450..7f85be0d0f1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,7 +27,7 @@ RUN apt-get update && \ apt-get install -y wget unzip tar xz-utils bzip2 gzip coreutils && \ apt-get install -y curl sed grep graphviz libjpeg-dev zlib1g-dev && \ apt-get install -y python-numpy python-matplotlib gcc g++ gfortran && \ - apt-get install -y automake locales clang-format-3.8 swig && \ + apt-get install -y automake locales clang-format-3.8 swig doxygen&& \ apt-get clean -y # git credential to skip password typing -- GitLab From e48ef6886e40453344700197ab49060cb9b0bd14 Mon Sep 17 00:00:00 2001 From: qijun Date: Thu, 20 Apr 2017 14:52:09 +0800 Subject: [PATCH 0078/3256] format code --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 7f85be0d0f1..ed9f95213d7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,7 +27,7 @@ RUN apt-get update && \ apt-get install -y wget unzip tar xz-utils bzip2 gzip coreutils && \ apt-get install -y curl sed grep graphviz libjpeg-dev zlib1g-dev && \ apt-get install -y python-numpy python-matplotlib gcc g++ gfortran && \ - apt-get install -y automake locales clang-format-3.8 swig doxygen&& \ + apt-get install -y automake locales clang-format-3.8 swig doxygen && \ apt-get clean -y # git credential to skip password typing -- GitLab From a85482233515973b445a86fbc839f3b3dc3eb2a6 Mon Sep 17 00:00:00 2001 From: gangliao Date: Thu, 20 Apr 2017 15:05:44 +0800 Subject: [PATCH 0079/3256] Add pip install paddle wheel package in Dockerfile --- paddle/scripts/docker/build.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/paddle/scripts/docker/build.sh b/paddle/scripts/docker/build.sh index e4c322bb181..16f669bd774 100644 --- a/paddle/scripts/docker/build.sh +++ b/paddle/scripts/docker/build.sh @@ -98,6 +98,7 @@ ADD build/*.deb /usr/local/opt/paddle/deb/ # run paddle version to install python packages first RUN dpkg -i /usr/local/opt/paddle/deb/*.deb && \ rm -f /usr/local/opt/paddle/deb/*.deb && \ + pip install /usr/opt/paddle/share/wheels/*.whl && \ paddle version ${CPU_DOCKER_PYTHON_HOME_ENV} ${DOCKERFILE_CUDNN_DSO} -- GitLab From 2f633a6e217f1f4c5e76d4a81e00f93f0033a6bf Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Thu, 20 Apr 2017 15:09:58 +0800 Subject: [PATCH 0080/3256] Merge 3 files into releasing_process.md --- doc/design/releasing_process.md | 58 +++++++++++++++++++ .../01.how_to_release_paddle.md | 22 ------- .../02.paddle_branching_model.md | 16 ----- .../03.regression_test_list.md | 18 ------ 4 files changed, 58 insertions(+), 56 deletions(-) create mode 100644 doc/design/releasing_process.md delete mode 100644 doc/design/releasing_process/01.how_to_release_paddle.md delete mode 100644 doc/design/releasing_process/02.paddle_branching_model.md delete mode 100644 doc/design/releasing_process/03.regression_test_list.md diff --git a/doc/design/releasing_process.md b/doc/design/releasing_process.md new file mode 100644 index 00000000000..3692a5248a3 --- /dev/null +++ b/doc/design/releasing_process.md @@ -0,0 +1,58 @@ +# Paddle发行规范 + +Paddle使用git-flow branching model做分支管理,使用[Semantic Versioning](http://semver.org/)标准表示Paddle版本号。 + +Paddle每次发新的版本,遵循以下流程: + +1. 从`develop`分支派生出新的分支,分支名为`release/版本号`。例如,`release/0.10.0` +2. 将新分支的版本打上tag,tag为`版本号rc.Patch号`。第一个tag为`0.10.0rc1`,第二个为`0.10.0rc2`,依次类推。 +3. 对这个版本的提交,做如下几个操作: + * 编译这个版本的Docker发行镜像,发布到dockerhub。如果失败,修复Docker编译镜像问题,Patch号加一,返回第二步 + * 编译这个版本的Ubuntu Deb包。如果失败,修复Ubuntu Deb包编译问题,Patch号加一,返回第二步。 + * 使用Regression Test List作为检查列表,测试Docker镜像/ubuntu安装包的功能正确性 + * 如果失败,记录下所有失败的例子,在这个`release/版本号`分支中,修复所有bug后,Patch号加一,返回第二步 +4. 第三步完成后,将`release/版本号`分支合入master分支,并删除`release/版本号`分支。将master分支的合入commit打上tag,tag为`版本号`。同时再将`master`分支合入`develop`分支。最后删除`release/版本号`分支。 +5. 编译master分支的Docker发行镜像,发布到dockerhub。编译ubuntu的deb包,发布到github release页面 +6. 协同完成Release Note的书写 + + +需要注意的是: + +* `release/版本号`分支一旦建立,一般不允许再从`develop`分支合入`release/版本号`。这样保证`release/版本号`分支功能的封闭,方便测试人员测试Paddle的行为。 +* 在`release/版本号`分支存在的时候,如果有bugfix的行为,需要将bugfix的分支同时merge到`master`, `develop`和`release/版本号`这三个分支。 + +# Paddle 分支规范 + +Paddle开发过程使用[git-flow](http://nvie.com/posts/a-successful-git-branching-model/)分支规范,并适应github的特性做了一些区别。 + +* Paddle的主版本库遵循[git-flow](http://nvie.com/posts/a-successful-git-branching-model/)分支规范。其中: + * `master`分支为稳定(stable branch)版本分支。每一个`master`分支的版本都是经过单元测试和回归测试的版本。 + * `develop`分支为开发(develop branch)版本分支。每一个`develop`分支的版本都经过单元测试,但并没有经过回归测试。 + * `release/版本号`分支为每一次Release时建立的临时分支。在这个阶段的代码正在经历回归测试。 + +* 其他用户的fork版本库并不需要严格遵守[git-flow](http://nvie.com/posts/a-successful-git-branching-model/)分支规范,但所有fork的版本库的所有分支都相当于特性分支。 + * 建议,开发者fork的版本库使用`develop`分支同步主版本库的`develop`分支 + * 建议,开发者fork的版本库中,再基于`develop`版本fork出自己的功能分支。 + * 当功能分支开发完毕后,向Paddle的主版本库提交`Pull Reuqest`,进而进行代码评审。 + * 在评审过程中,开发者修改自己的代码,可以继续在自己的功能分支提交代码。 + +* BugFix分支也是在开发者自己的fork版本库维护,与功能分支不同的是,BugFix分支需要分别给主版本库的`master`、`develop`与可能有的`release/版本号`分支,同时提起`Pull Request`。 + +# Paddle回归测试列表 + +本列表说明Paddle发版之前需要测试的功能点。 + +## Paddle Book中所有章节 + +Paddle每次发版本首先要保证Paddle Book中所有章节功能的正确性。功能的正确性包括验证Paddle目前的`paddle_trainer`训练和纯使用`Python`训练模型正确性。 + +| | 新手入门章节 | 识别数字 | 图像分类 | 词向量 | 情感分析 | 语意角色标注 | 机器翻译 | 个性化推荐 | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | +| API.V2 + Docker + GPU | | | | | | | | | +| API.V2 + Docker + CPU | | | | | | | | | +| `paddle_trainer` + Docker + GPU | | | | | | | | | +| `paddle_trainer` + Docker + CPU | | | | | | | | | +| API.V2 + Ubuntu + GPU | | | | | | | | | +| API.V2 + Ubuntu + CPU | | | | | | | | | +| `paddle_trainer` + Ubuntu + GPU | | | | | | | | | +| `paddle_trainer` + Ubuntu + CPU | | | | | | | | | diff --git a/doc/design/releasing_process/01.how_to_release_paddle.md b/doc/design/releasing_process/01.how_to_release_paddle.md deleted file mode 100644 index 39faf9504e1..00000000000 --- a/doc/design/releasing_process/01.how_to_release_paddle.md +++ /dev/null @@ -1,22 +0,0 @@ -# Paddle发行规范 - -Paddle使用[git-flow](./02.paddle_branching_model.md) branching model做分支管理,使用[Semantic Versioning](http://semver.org/)标准表示Paddle版本号。 - -Paddle每次发新的版本,遵循以下流程: - -1. 从`develop`分支派生出新的分支,分支名为`release/版本号`。例如,`release/0.10.0` -2. 将新分支的版本打上tag,tag为`版本号rc.Patch号`。第一个tag为`0.10.0rc1`,第二个为`0.10.0rc2`,依次类推。 -3. 对这个版本的提交,做如下几个操作: - * 编译这个版本的Docker发行镜像,发布到dockerhub。如果失败,修复Docker编译镜像问题,Patch号加一,返回第二步 - * 编译这个版本的Ubuntu Deb包。如果失败,修复Ubuntu Deb包编译问题,Patch号加一,返回第二步。 - * 使用[Regression Test List](./03.regression_test_list.md)作为检查列表,测试Docker镜像/ubuntu安装包的功能正确性 - * 如果失败,记录下所有失败的例子,在这个`release/版本号`分支中,修复所有bug后,Patch号加一,返回第二步 -4. 第三步完成后,将`release/版本号`分支合入master分支,并删除`release/版本号`分支。将master分支的合入commit打上tag,tag为`版本号`。同时再将`master`分支合入`develop`分支。最后删除`release/版本号`分支。 -5. 编译master分支的Docker发行镜像,发布到dockerhub。编译ubuntu的deb包,发布到github release页面 -6. 协同完成Release Note的书写 - - -需要注意的是: - -* `release/版本号`分支一旦建立,一般不允许再从`develop`分支合入`release/版本号`。这样保证`release/版本号`分支功能的封闭,方便测试人员测试Paddle的行为。 -* 在`release/版本号`分支存在的时候,如果有bugfix的行为,需要将bugfix的分支同时merge到`master`, `develop`和`release/版本号`这三个分支。 diff --git a/doc/design/releasing_process/02.paddle_branching_model.md b/doc/design/releasing_process/02.paddle_branching_model.md deleted file mode 100644 index 0b2a1bf32f9..00000000000 --- a/doc/design/releasing_process/02.paddle_branching_model.md +++ /dev/null @@ -1,16 +0,0 @@ -# Paddle 分支规范 - -Paddle开发过程使用[git-flow](http://nvie.com/posts/a-successful-git-branching-model/)分支规范,并适应github的特性做了一些区别。 - -* Paddle的主版本库遵循[git-flow](http://nvie.com/posts/a-successful-git-branching-model/)分支规范。其中: - * `master`分支为稳定(stable branch)版本分支。每一个`master`分支的版本都是经过单元测试和回归测试的版本。 - * `develop`分支为开发(develop branch)版本分支。每一个`develop`分支的版本都经过单元测试,但并没有经过回归测试。 - * `release/版本号`分支为每一次Release时建立的临时分支。在这个阶段的代码正在经历回归测试。 - -* 其他用户的fork版本库并不需要严格遵守[git-flow](http://nvie.com/posts/a-successful-git-branching-model/)分支规范,但所有fork的版本库的所有分支都相当于特性分支。 - * 建议,开发者fork的版本库使用`develop`分支同步主版本库的`develop`分支 - * 建议,开发者fork的版本库中,再基于`develop`版本fork出自己的功能分支。 - * 当功能分支开发完毕后,向Paddle的主版本库提交`Pull Reuqest`,进而进行代码评审。 - * 在评审过程中,开发者修改自己的代码,可以继续在自己的功能分支提交代码。 - -* BugFix分支也是在开发者自己的fork版本库维护,与功能分支不同的是,BugFix分支需要分别给主版本库的`master`、`develop`与可能有的`release/版本号`分支,同时提起`Pull Request`。 diff --git a/doc/design/releasing_process/03.regression_test_list.md b/doc/design/releasing_process/03.regression_test_list.md deleted file mode 100644 index 0ff012bc47a..00000000000 --- a/doc/design/releasing_process/03.regression_test_list.md +++ /dev/null @@ -1,18 +0,0 @@ -# Paddle回归测试列表 - -本列表说明Paddle发版之前需要测试的功能点。 - -## Paddle Book中所有章节 - -Paddle每次发版本首先要保证Paddle Book中所有章节功能的正确性。功能的正确性包括验证Paddle目前的`paddle_trainer`训练和纯使用`Python`训练模型正确性。 - -| | 新手入门章节 | 识别数字 | 图像分类 | 词向量 | 情感分析 | 语意角色标注 | 机器翻译 | 个性化推荐 | -| --- | --- | --- | --- | --- | --- | --- | --- | --- | -| API.V2 + Docker + GPU | | | | | | | | | -| API.V2 + Docker + CPU | | | | | | | | | -| `paddle_trainer` + Docker + GPU | | | | | | | | | -| `paddle_trainer` + Docker + CPU | | | | | | | | | -| API.V2 + Ubuntu + GPU | | | | | | | | | -| API.V2 + Ubuntu + CPU | | | | | | | | | -| `paddle_trainer` + Ubuntu + GPU | | | | | | | | | -| `paddle_trainer` + Ubuntu + CPU | | | | | | | | | -- GitLab From 93273a5772d683c814ab98dff84a66aba3cc1f0e Mon Sep 17 00:00:00 2001 From: qijun Date: Thu, 20 Apr 2017 17:08:36 +0800 Subject: [PATCH 0081/3256] fix ipython version --- Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index eaf472221aa..f12be36ceb7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -43,7 +43,8 @@ RUN pip install --upgrade pip && \ pip install -U wheel pillow BeautifulSoup && \ pip install -U docopt PyYAML sphinx && \ pip install -U sphinx-rtd-theme==0.1.9 recommonmark && \ - pip install pre-commit 'requests==2.9.2' 'ipykernel==4.6.0' 'jupyter==1.0.0' + pip install pre-commit 'requests==2.9.2' 'ipython==5.3.0' && \ + pip install 'ipykernel==4.6.0' 'jupyter==1.0.0' RUN curl -sSL https://cmake.org/files/v3.4/cmake-3.4.1.tar.gz | tar -xz && \ cd cmake-3.4.1 && ./bootstrap && make -j `nproc` && make install && \ -- GitLab From e1288616f6e594f853a507dae229f811e77eca2b Mon Sep 17 00:00:00 2001 From: hedaoyuan Date: Thu, 20 Apr 2017 18:56:58 +0800 Subject: [PATCH 0082/3256] Fix compile on raspberry pi --- doc/howto/raspberry/build_for_raspberry.md | 45 ++++++++++++++++++++++ paddle/api/CMakeLists.txt | 2 +- paddle/utils/CpuId.cpp | 11 +++--- 3 files changed, 52 insertions(+), 6 deletions(-) create mode 100644 doc/howto/raspberry/build_for_raspberry.md diff --git a/doc/howto/raspberry/build_for_raspberry.md b/doc/howto/raspberry/build_for_raspberry.md new file mode 100644 index 00000000000..ba23c8457bc --- /dev/null +++ b/doc/howto/raspberry/build_for_raspberry.md @@ -0,0 +1,45 @@ +# 如何构建raspberry pi下运行的paddle + +这里考虑的是交叉编译方式,即在Linux-X86环境下构建raspberry pi下可运行的paddle。 + +## 下载较差编译环境 +``` +git clone https://github.com/raspberrypi/tools +``` +如果host是x86-64环境,选用`arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian-x64`下的作为编译工具。注意,需要系统glibc支持2.14以上。 + + +## 编译第三方库 +Paddle在cmake会在编译paddle的时候自动编译依赖的第三方库,不过openblas和protobuf最好还是在编译paddle之前先编译好,这样可以保证编译paddle的时候更加顺畅。 + +### 编译openblas +``` +git clone https://github.com/xianyi/OpenBLAS.git +make TARGET=ARMV7 HOSTCC=gcc CC=arm-linux-gnueabihf-gcc NOFORTRAN=1 USE_THREAD=0 +``` + +### 编译protobuf +``` +git clone https://github.com/google/protobuf.git +git checkout 9f75c5aa851cd877fb0d93ccc31b8567a6706546 +cmake ../protobuf/cmake \ +-Dprotobuf_BUILD_TESTS=OFF \ +-DCMAKE_CXX_COMPILER=arm-linux-gnueabihf-g++ \ +-DCMAKE_C_COMPILER=arm-linux-gnueabihf-gcc \ +-DCMAKE_POSITION_INDEPENDENT_CODE=ON \ +-DCMAKE_BUILD_TYPE=Release \ +-DCMAKE_INSTALL_LIBDIR=lib +``` +注意:这样编译出来的`libprotobuf.a`和`protoc`都是ARM版本的,而我们需要的是一个x86-64版本的`protoc`,所以需要用host gcc再编译一遍protobuf然后使用其中的`protoc`。 + + +## 编译Paddle +``` +cmake .. -DWITH_GPU=OFF -DWITH_PYTHON=OFF -DWITH_SWIG_PY=OFF \ +-DCMAKE_CXX_COMPILER:FILEPATH=arm-linux-gnueabihf-g++ \ +-DCMAKE_C_COMPILER:FILEPATH=arm-linux-gnueabihf-gcc \ +-DCMAKE_C_FLAGS="-mfpu=neon" \ +-DCMAKE_CXX_FLAGS="-mfpu=neon" \ +-DOPENBLAS_ROOT=openblas \ +-DCMAKE_PREFIX_PATH=protobuf +``` diff --git a/paddle/api/CMakeLists.txt b/paddle/api/CMakeLists.txt index 4d0dacae905..c7993553e35 100644 --- a/paddle/api/CMakeLists.txt +++ b/paddle/api/CMakeLists.txt @@ -26,7 +26,7 @@ FILE(GLOB PY_PADDLE_PYTHON_FILES ${PROJ_ROOT}/paddle/py_paddle/*.py) SET_SOURCE_FILES_PROPERTIES(Paddle.i PROPERTIES CPLUSPLUS ON) SET(CMAKE_SWIG_OUTDIR ${CMAKE_CURRENT_BINARY_DIR}) -SET(CMAKE_CXX_FLAGS "-std=c++11 -fPIC -Wall") +SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -fPIC -Wall") IF(WITH_COVERAGE) SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -O0 -fprofile-arcs -ftest-coverage") ENDIF(WITH_COVERAGE) diff --git a/paddle/utils/CpuId.cpp b/paddle/utils/CpuId.cpp index edd33c45412..5abeeecae8d 100644 --- a/paddle/utils/CpuId.cpp +++ b/paddle/utils/CpuId.cpp @@ -19,19 +19,22 @@ limitations under the License. */ /// for MSVC #define CPUID(info, x) __cpuidex(info, x, 0) -#elif !defined(__ANDROID__) +#else +#if !defined(__arm__) #include - /// for GCC/Clang #define CPUID(info, x) __cpuid_count(x, 0, info[0], info[1], info[2], info[3]) +#endif #endif namespace paddle { SIMDFlags::SIMDFlags() { -#if !defined(__ANDROID__) +#if defined(__arm__) + simd_flags_ = SIMD_NEON; +#else unsigned int cpuInfo[4]; // CPUID: https://en.wikipedia.org/wiki/CPUID // clang-format off @@ -52,8 +55,6 @@ SIMDFlags::SIMDFlags() { CPUID(cpuInfo, 0x80000001); simd_flags_ |= cpuInfo[2] & (1 << 16) ? SIMD_FMA4 : SIMD_NONE; // clang-fotmat on -#else - simd_flags_ = SIMD_NEON; #endif } -- GitLab From e9f0ee07cb9970c82c3d86e405509826cd89dca1 Mon Sep 17 00:00:00 2001 From: qijun Date: Thu, 20 Apr 2017 19:19:44 +0800 Subject: [PATCH 0083/3256] fix build.sh --- paddle/scripts/docker/build.sh | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/paddle/scripts/docker/build.sh b/paddle/scripts/docker/build.sh index 50b083fb398..c1e3f39888f 100644 --- a/paddle/scripts/docker/build.sh +++ b/paddle/scripts/docker/build.sh @@ -42,7 +42,20 @@ if [[ ${RUN_TEST:-OFF} == "ON" ]]; then make coveralls fi make install +pip install /usr/opt/paddle/share/wheels/*.whl +if [ ${WITH_DOC} == "ON" ]; then + mkdir -p /paddle/build_doc + pushd /paddle/build_doc + cmake .. \ + -DWITH_DOC=ON \ + -DWITH_GPU=OFF \ + -DWITH_AVX=${WITH_AVX:-OFF} \ + -DWITH_SWIG_PY=ON \ + -DWITH_STYLE_CHECK=OFF + make paddle_docs paddle_docs_cn + popd +fi # generate deb package for current build # FIXME(typhoonzero): should we remove paddle/scripts/deb ? # FIXME: CPACK_DEBIAN_PACKAGE_DEPENDS removes all dev dependencies, must @@ -100,18 +113,6 @@ RUN dpkg -i /usr/local/opt/paddle/deb/*.deb && \ rm -f /usr/local/opt/paddle/deb/*.deb && \ pip install /usr/opt/paddle/share/wheels/*.whl && \ paddle version -if [ ${WITH_DOC} == "ON" ]; then - mkdir -p /paddle/build_doc - pushd /paddle/build_doc - cmake .. \ - -DWITH_DOC=ON \ - -DWITH_GPU=OFF \ - -DWITH_AVX=${WITH_AVX:-OFF} \ - -DWITH_SWIG_PY=ON \ - -DWITH_STYLE_CHECK=OFF - make paddle_docs paddle_docs_cn - popd -fi ${CPU_DOCKER_PYTHON_HOME_ENV} ${DOCKERFILE_CUDNN_DSO} ${DOCKERFILE_GPU_ENV} -- GitLab From 37db05f915525844340841f80cbc3ee9f4c0e571 Mon Sep 17 00:00:00 2001 From: qijun Date: Thu, 20 Apr 2017 19:36:15 +0800 Subject: [PATCH 0084/3256] fix wrong path of pip install *whl --- paddle/scripts/docker/build.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/paddle/scripts/docker/build.sh b/paddle/scripts/docker/build.sh index c1e3f39888f..6f7b97021da 100644 --- a/paddle/scripts/docker/build.sh +++ b/paddle/scripts/docker/build.sh @@ -42,7 +42,7 @@ if [[ ${RUN_TEST:-OFF} == "ON" ]]; then make coveralls fi make install -pip install /usr/opt/paddle/share/wheels/*.whl +pip install /usr/local/opt/paddle/share/wheels/*.whl if [ ${WITH_DOC} == "ON" ]; then mkdir -p /paddle/build_doc @@ -111,7 +111,7 @@ ADD build/*.deb /usr/local/opt/paddle/deb/ # run paddle version to install python packages first RUN dpkg -i /usr/local/opt/paddle/deb/*.deb && \ rm -f /usr/local/opt/paddle/deb/*.deb && \ - pip install /usr/opt/paddle/share/wheels/*.whl && \ + pip install /usr/local/opt/paddle/share/wheels/*.whl && \ paddle version ${CPU_DOCKER_PYTHON_HOME_ENV} ${DOCKERFILE_CUDNN_DSO} -- GitLab From 3a36f61a3db1db761b621849533213755f004bd0 Mon Sep 17 00:00:00 2001 From: qijun Date: Thu, 20 Apr 2017 19:48:54 +0800 Subject: [PATCH 0085/3256] add build_doc dir to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index ee7c6ec370c..2b30f7938c8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ *.DS_Store build/ +build_doc/ *.user .vscode -- GitLab From 8bd6b749d060ba5da09645517b1d25035fc82bb2 Mon Sep 17 00:00:00 2001 From: "yi.wu" Date: Thu, 20 Apr 2017 21:12:44 +0800 Subject: [PATCH 0086/3256] update reader design --- doc/design/cluster_train/data_dispatch.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/doc/design/cluster_train/data_dispatch.md b/doc/design/cluster_train/data_dispatch.md index ef2baf724d7..cce3deb0047 100644 --- a/doc/design/cluster_train/data_dispatch.md +++ b/doc/design/cluster_train/data_dispatch.md @@ -54,6 +54,7 @@ L' inflation accélérée , mesurée dans la zone euro , est due principale ```python reader = paddle.dist.reader("dataset-name") +trainer.train(reader, ...) batch_reader = paddle.batch(paddle.dataset.mnist.train(), 128) trainer.train(batch_reader, ...) ``` @@ -69,6 +70,14 @@ def paddle.train(batch_reader): 这里面batch是含有128个data instance的mini-batch。每一个data instance会是一个tuple,tuple元素的顺序与`.list`文件文件中每一列的顺序是一致的。每一个data instance会是(raw_image_file_binary_data, label)。其中raw_image_file_binary_data是对应图像文件的没有解码的原始二进制数据,用户需要自己解码。label是文本类型(比如:“1“,”2“),这里用户需要的其实是整形,用户需要自己转换成整形。 +### 实现reader + +reader的实现需要考虑本地训练程序实现之后,可以不修改程序直接提交集群进行分布式训练。要达到这样的目标,需要实现下面的功能: + +paddle会实现内置的默认reader和对公开数据集的reader(如MNIST,BOW等)。这些内置的reader都会被一个`paddle.reader`修饰器修饰。这个修饰器会读取环境变量`PADDLE_TRAIN_LOCAL`,如果是True,则返回只有一个文件的task queue生成器,这个文件就是reader传入的文件。如果是False,则为分布式训练模式(在集群中训练的任务,都会从这个环境变量获得False值),开始读取task queue,获取分布式存储系统中的文件名数据,并返回这个task queue。不同的reader的实现,都要遵守固定的方式:从task queue生成器中逐个获取文件名,完成对不同训练数据的解析。 + +同样用户在实现自己的reader时,也需要使用此修饰器来修饰reader函数。 + ## TODO ### 支持将数据合并成内部的文件格式(key-value),方便sharding与顺序读取 -- GitLab From 91927cc3a20ef805a1fb7dafc7d71a014daade27 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Thu, 20 Apr 2017 21:28:05 +0800 Subject: [PATCH 0087/3256] Change name conventions. --- paddle/capi/Arguments.cpp | 22 +++++++-------- paddle/capi/arguments.h | 27 ++++++++++--------- .../examples/model_inference/dense/main.c | 2 +- .../model_inference/multi_thread/main.c | 2 +- .../examples/model_inference/sequence/main.c | 2 +- .../model_inference/sparse_binary/main.c | 2 +- paddle/capi/tests/test_Arguments.cpp | 8 +++--- paddle/capi/tests/test_GradientMachine.cpp | 4 +-- 8 files changed, 35 insertions(+), 34 deletions(-) diff --git a/paddle/capi/Arguments.cpp b/paddle/capi/Arguments.cpp index 2954f522c95..8b81ec69e60 100644 --- a/paddle/capi/Arguments.cpp +++ b/paddle/capi/Arguments.cpp @@ -31,7 +31,7 @@ paddle_error paddle_arguments_destroy(paddle_arguments args) { return kPD_NO_ERROR; } -paddle_error paddle_arguments_size(paddle_arguments args, uint64_t* size) { +paddle_error paddle_arguments_get_size(paddle_arguments args, uint64_t* size) { if (args == nullptr || size == nullptr) return kPD_NULLPTR; *size = castArg(args)->args.size(); return kPD_NO_ERROR; @@ -55,9 +55,9 @@ paddle_error paddle_arguments_set_value(paddle_arguments args, return kPD_NO_ERROR; } -paddle_error paddle_arguments_value(paddle_arguments args, - uint64_t ID, - paddle_matrix mat) { +paddle_error paddle_arguments_get_value(paddle_arguments args, + uint64_t ID, + paddle_matrix mat) { if (args == nullptr || mat == nullptr) return kPD_NULLPTR; auto m = paddle::capi::cast(mat); auto a = castArg(args); @@ -66,9 +66,9 @@ paddle_error paddle_arguments_value(paddle_arguments args, return kPD_NO_ERROR; } -paddle_error paddle_arguments_ids(paddle_arguments args, - uint64_t ID, - paddle_ivector ids) { +paddle_error paddle_arguments_get_ids(paddle_arguments args, + uint64_t ID, + paddle_ivector ids) { if (args == nullptr || ids == nullptr) return kPD_NULLPTR; auto iv = castIVec(ids); auto a = castArg(args); @@ -103,10 +103,10 @@ paddle_error paddle_arguments_set_sequence_start_pos(paddle_arguments args, }); } -paddle_error paddle_arguments_sequence_start_pos(paddle_arguments args, - uint64_t ID, - uint32_t nestedLevel, - paddle_ivector seqPos) { +paddle_error paddle_arguments_get_sequence_start_pos(paddle_arguments args, + uint64_t ID, + uint32_t nestedLevel, + paddle_ivector seqPos) { if (args == nullptr || seqPos == nullptr) return kPD_NULLPTR; auto iv = paddle::capi::cast(seqPos); auto a = castArg(args); diff --git a/paddle/capi/arguments.h b/paddle/capi/arguments.h index 1bb6516ea02..d71ea26a5d1 100644 --- a/paddle/capi/arguments.h +++ b/paddle/capi/arguments.h @@ -46,13 +46,13 @@ PD_API paddle_arguments paddle_arguments_create_none(); PD_API paddle_error paddle_arguments_destroy(paddle_arguments args); /** - * @brief PDArgsGetSize Get size of arguments array + * @brief paddle_arguments_get_size Get size of arguments array * @param [in] args arguments array * @param [out] size array size * @return paddle_error */ -PD_API paddle_error paddle_arguments_size(paddle_arguments args, - uint64_t* size); +PD_API paddle_error paddle_arguments_get_size(paddle_arguments args, + uint64_t* size); /** * @brief PDArgsResize Resize a arguments array. @@ -83,9 +83,9 @@ PD_API paddle_error paddle_arguments_set_value(paddle_arguments args, * @param [out] mat matrix pointer * @return paddle_error */ -PD_API paddle_error paddle_arguments_value(paddle_arguments args, - uint64_t ID, - paddle_matrix mat); +PD_API paddle_error paddle_arguments_get_value(paddle_arguments args, + uint64_t ID, + paddle_matrix mat); /** * @brief PDArgsGetIds Get the integer vector of one argument in array, which @@ -95,9 +95,9 @@ PD_API paddle_error paddle_arguments_value(paddle_arguments args, * @param ids integer vector pointer * @return paddle_error */ -PD_API paddle_error paddle_arguments_ids(paddle_arguments args, - uint64_t ID, - paddle_ivector ids); +PD_API paddle_error paddle_arguments_get_ids(paddle_arguments args, + uint64_t ID, + paddle_ivector ids); /** * @brief PDArgsSetIds Set the integer vector of one argument in array, which @@ -132,10 +132,11 @@ paddle_arguments_set_sequence_start_pos(paddle_arguments args, * @param [out] seqPos sequence position array * @return paddle_error */ -PD_API paddle_error paddle_arguments_sequence_start_pos(paddle_arguments args, - uint64_t ID, - uint32_t nestedLevel, - paddle_ivector seqPos); +PD_API paddle_error +paddle_arguments_get_sequence_start_pos(paddle_arguments args, + uint64_t ID, + uint32_t nestedLevel, + paddle_ivector seqPos); #ifdef __cplusplus } diff --git a/paddle/capi/examples/model_inference/dense/main.c b/paddle/capi/examples/model_inference/dense/main.c index e03fe748f61..3e6bd528505 100644 --- a/paddle/capi/examples/model_inference/dense/main.c +++ b/paddle/capi/examples/model_inference/dense/main.c @@ -49,7 +49,7 @@ int main() { /* isTrain */ false)); paddle_matrix prob = paddle_matrix_create_none(); - CHECK(paddle_arguments_value(out_args, 0, prob)); + CHECK(paddle_arguments_get_value(out_args, 0, prob)); CHECK(paddle_matrix_get_row(prob, 0, &array)); diff --git a/paddle/capi/examples/model_inference/multi_thread/main.c b/paddle/capi/examples/model_inference/multi_thread/main.c index ab0eb32c582..d7675cd80a5 100644 --- a/paddle/capi/examples/model_inference/multi_thread/main.c +++ b/paddle/capi/examples/model_inference/multi_thread/main.c @@ -38,7 +38,7 @@ void* thread_main(void* gm_ptr) { out_args, /* isTrain */ false)); - CHECK(paddle_arguments_value(out_args, 0, prob)); + CHECK(paddle_arguments_get_value(out_args, 0, prob)); CHECK(paddle_matrix_get_row(prob, 0, &array)); diff --git a/paddle/capi/examples/model_inference/sequence/main.c b/paddle/capi/examples/model_inference/sequence/main.c index 142793cdb3e..50bc0c9201f 100644 --- a/paddle/capi/examples/model_inference/sequence/main.c +++ b/paddle/capi/examples/model_inference/sequence/main.c @@ -47,7 +47,7 @@ int main() { /* isTrain */ false)); paddle_matrix prob = paddle_matrix_create_none(); - CHECK(paddle_arguments_value(out_args, 0, prob)); + CHECK(paddle_arguments_get_value(out_args, 0, prob)); paddle_real* array; diff --git a/paddle/capi/examples/model_inference/sparse_binary/main.c b/paddle/capi/examples/model_inference/sparse_binary/main.c index 776ad878911..8ba67aee560 100644 --- a/paddle/capi/examples/model_inference/sparse_binary/main.c +++ b/paddle/capi/examples/model_inference/sparse_binary/main.c @@ -50,7 +50,7 @@ int main() { /* isTrain */ false)); paddle_matrix prob = paddle_matrix_create_none(); - CHECK(paddle_arguments_value(out_args, 0, prob)); + CHECK(paddle_arguments_get_value(out_args, 0, prob)); CHECK(paddle_matrix_get_row(prob, 0, &array)); diff --git a/paddle/capi/tests/test_Arguments.cpp b/paddle/capi/tests/test_Arguments.cpp index 012901a4916..4792ceb49a7 100644 --- a/paddle/capi/tests/test_Arguments.cpp +++ b/paddle/capi/tests/test_Arguments.cpp @@ -32,7 +32,7 @@ TEST(CAPIArguments, create) { //! TODO(yuyang18): Test GPU Code. paddle_arguments args = paddle_arguments_create_none(); uint64_t size; - ASSERT_EQ(kPD_NO_ERROR, paddle_arguments_size(args, &size)); + ASSERT_EQ(kPD_NO_ERROR, paddle_arguments_get_size(args, &size)); ASSERT_EQ(0UL, size); ASSERT_EQ(kPD_NO_ERROR, paddle_arguments_destroy(args)); } @@ -50,7 +50,7 @@ TEST(CAPIArguments, value) { paddle_matrix val = paddle_matrix_create_none(); - ASSERT_EQ(kPD_NO_ERROR, paddle_arguments_value(args, 0, val)); + ASSERT_EQ(kPD_NO_ERROR, paddle_arguments_get_value(args, 0, val)); for (size_t i = 0; i < 128; ++i) { paddle_real* row1; @@ -78,7 +78,7 @@ TEST(CAPIArguments, ids) { ASSERT_EQ(kPD_NO_ERROR, paddle_arguments_set_ids(args, 0, ivec)); paddle_ivector val = paddle_ivector_create_none(); - ASSERT_EQ(kPD_NO_ERROR, paddle_arguments_ids(args, 0, val)); + ASSERT_EQ(kPD_NO_ERROR, paddle_arguments_get_ids(args, 0, val)); ASSERT_EQ(kPD_NO_ERROR, paddle_ivector_destroy(ivec)); ASSERT_EQ(kPD_NO_ERROR, paddle_ivector_destroy(val)); ASSERT_EQ(kPD_NO_ERROR, paddle_arguments_destroy(args)); @@ -117,7 +117,7 @@ TEST(CAPIArguments, Sequence) { std::placeholders::_2, nestedLevel, std::placeholders::_3), - std::bind(paddle_arguments_sequence_start_pos, + std::bind(paddle_arguments_get_sequence_start_pos, std::placeholders::_1, std::placeholders::_2, nestedLevel, diff --git a/paddle/capi/tests/test_GradientMachine.cpp b/paddle/capi/tests/test_GradientMachine.cpp index 6c8d74c90b2..89aa64608dd 100644 --- a/paddle/capi/tests/test_GradientMachine.cpp +++ b/paddle/capi/tests/test_GradientMachine.cpp @@ -73,10 +73,10 @@ TEST(GradientMachine, testPredict) { paddle_gradient_machine_forward(machine, inArgs, outArgs, false)); uint64_t sz; - ASSERT_EQ(kPD_NO_ERROR, paddle_arguments_size(outArgs, &sz)); + ASSERT_EQ(kPD_NO_ERROR, paddle_arguments_get_size(outArgs, &sz)); ASSERT_EQ(1UL, sz); - ASSERT_EQ(kPD_NO_ERROR, paddle_arguments_value(outArgs, 0, mat)); + ASSERT_EQ(kPD_NO_ERROR, paddle_arguments_get_value(outArgs, 0, mat)); std::vector paddleInArgs; std::vector paddleOutArgs; paddleInArgs.resize(1); -- GitLab From dfd79c8817b74a9599417f9bb48574696b5c2b75 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Thu, 20 Apr 2017 22:34:51 +0800 Subject: [PATCH 0088/3256] Follow comments. --- doc/design/multi_language_interface/00.why_plain_c.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/design/multi_language_interface/00.why_plain_c.md b/doc/design/multi_language_interface/00.why_plain_c.md index 4004f16daf2..a1443093342 100644 --- a/doc/design/multi_language_interface/00.why_plain_c.md +++ b/doc/design/multi_language_interface/00.why_plain_c.md @@ -58,14 +58,14 @@ typedef void* paddle_matrix; typedef int paddle_error; extern "C" -paddle_error paddle_matrix_shape(paddle_matrix matrix, - uint64_t* width, - uint64_t* height); +paddle_error paddle_matrix_get_shape(paddle_matrix matrix, + uint64_t* width, + uint64_t* height); ``` 而在CPP里面实现这个C的接口,文件 `paddle_matrix.cpp` ```cpp -#include "paddle/math/matrix.hpp" +#include "paddle/math/matrix.h" extern "C" paddle_error paddle_matrix_shape(paddle_matrix matrix, uint64_t *width, -- GitLab From ecab029d5bb882b2b75abbc12839201de270ed23 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Fri, 21 Apr 2017 00:12:22 +0800 Subject: [PATCH 0089/3256] Timeout for caching in TravisCI --- .travis.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5a7f45a748a..865e21f046b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,7 +12,6 @@ env: - JOB=DOCS - JOB=BUILD_AND_TEST - JOB=PRE_COMMIT - addons: apt: packages: @@ -49,8 +48,12 @@ before_install: # Paddle is using protobuf 3.1 currently. Protobuf 3.2 breaks the compatibility. So we specify the python # protobuf version. - pip install numpy wheel 'protobuf==3.1' sphinx recommonmark sphinx-rtd-theme==0.1.9 virtualenv pre-commit requests==2.9.2 LinkChecker + - | + function timeout() { perl -e 'alarm shift; exec @ARGV' "$@"; } script: - - paddle/scripts/travis/main.sh + - | + timeout 2580 paddle/scripts/travis/main.sh # 43min timeout + RESULT=$?; if [ $RESULT -eq 0 ] || [ $RESULT -eq 142 ]; then true; else false; fi; notifications: email: on_success: change -- GitLab From 4e0f72e69ae33e23f98dc76af2b4477b9e44b036 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Fri, 21 Apr 2017 14:08:41 +0800 Subject: [PATCH 0090/3256] Typo --- .../multi_language_interface/01.inference_implementation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/design/multi_language_interface/01.inference_implementation.md b/doc/design/multi_language_interface/01.inference_implementation.md index 9db9ce1834d..98202845232 100644 --- a/doc/design/multi_language_interface/01.inference_implementation.md +++ b/doc/design/multi_language_interface/01.inference_implementation.md @@ -109,7 +109,7 @@ struct CMatrix { ### libpaddle\_capi_shared.{so, dylib} -`libpaddle_capi_shared`是C-API导出的动态库。这个动态库的连接参数与Paddle的其他二进制(例如`paddle_traienr`)类似。用户可以直接使用这个动态库来引入Paddle C-API。具体使用方法为`-lpaddle_capi_shared`。 +`libpaddle_capi_shared`是C-API导出的动态库。这个动态库的连接参数与Paddle的其他二进制(例如`paddle_trainer`)类似。用户可以直接使用这个动态库来引入Paddle C-API。具体使用方法为`-lpaddle_capi_shared`。 ### libpaddle\_capi_whole.a -- GitLab From b896b484008df92992008870a8cedd97b320d1a3 Mon Sep 17 00:00:00 2001 From: qijun Date: Fri, 21 Apr 2017 14:29:19 +0800 Subject: [PATCH 0091/3256] correct with_testing option --- paddle/scripts/docker/build.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/paddle/scripts/docker/build.sh b/paddle/scripts/docker/build.sh index 6f7b97021da..83cc30d3c22 100644 --- a/paddle/scripts/docker/build.sh +++ b/paddle/scripts/docker/build.sh @@ -35,11 +35,11 @@ cmake .. \ -DWITH_SWIG_PY=ON \ -DCUDNN_ROOT=/usr/ \ -DWITH_STYLE_CHECK=${WITH_STYLE_CHECK:-OFF} \ - -DON_COVERALLS=${WITH_TEST:-OFF} \ + -DWITH_TESTING=${WITH_TESTING:-OFF} \ -DCMAKE_EXPORT_COMPILE_COMMANDS=ON make -j `nproc` -if [[ ${RUN_TEST:-OFF} == "ON" ]]; then - make coveralls +if [ ${WITH_TESTING:-OFF} == "ON" ] && [ ${RUN_TEST:-OFF} == "ON" ] ; then + make test fi make install pip install /usr/local/opt/paddle/share/wheels/*.whl -- GitLab From d1052de117870f9c4611db49952c8069dfe81e39 Mon Sep 17 00:00:00 2001 From: qijun Date: Fri, 21 Apr 2017 14:35:05 +0800 Subject: [PATCH 0092/3256] add some comments --- paddle/scripts/docker/README.md | 2 +- paddle/scripts/docker/build.sh | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/paddle/scripts/docker/README.md b/paddle/scripts/docker/README.md index 132f8cd8aaf..b45d92507c1 100644 --- a/paddle/scripts/docker/README.md +++ b/paddle/scripts/docker/README.md @@ -94,7 +94,7 @@ docker build -t paddle:dev --build-arg UBUNTU_MIRROR=mirror://mirrors.ubuntu.com Given the development image `paddle:dev`, the following command builds PaddlePaddle from the source tree on the development computer (host): ```bash -docker run --rm -v $PWD:/paddle -e "WITH_GPU=OFF" -e "WITH_AVX=ON" -e "WITH_TEST=OFF" -e "RUN_TEST=OFF" paddle:dev +docker run --rm -v $PWD:/paddle -e "WITH_GPU=OFF" -e "WITH_AVX=ON" -e "WITH_TESTING=OFF" -e "RUN_TEST=OFF" paddle:dev ``` This command mounts the source directory on the host into `/paddle` in the container, so the default entry point of `paddle:dev`, `build.sh`, could build the source code with possible local changes. When it writes to `/paddle/build` in the container, it writes to `$PWD/build` on the host indeed. diff --git a/paddle/scripts/docker/build.sh b/paddle/scripts/docker/build.sh index 83cc30d3c22..1613d970bc4 100644 --- a/paddle/scripts/docker/build.sh +++ b/paddle/scripts/docker/build.sh @@ -44,6 +44,8 @@ fi make install pip install /usr/local/opt/paddle/share/wheels/*.whl +# Since python v2 api import py_paddle module, the generation of paddle docs +# depend on paddle's compilation and installation if [ ${WITH_DOC} == "ON" ]; then mkdir -p /paddle/build_doc pushd /paddle/build_doc -- GitLab From b360dec9db3930cdb11cb284a6f81a83bdb8181e Mon Sep 17 00:00:00 2001 From: typhoonzero Date: Fri, 21 Apr 2017 15:07:17 +0800 Subject: [PATCH 0093/3256] update doc --- doc/design/cluster_train/README.md | 4 ++-- doc/design/cluster_train/checkpointing.md | 14 ++++++------- doc/design/cluster_train/data_dispatch.md | 25 +++++++++++++++-------- 3 files changed, 25 insertions(+), 18 deletions(-) diff --git a/doc/design/cluster_train/README.md b/doc/design/cluster_train/README.md index 84678481ccc..b88a8f382bf 100644 --- a/doc/design/cluster_train/README.md +++ b/doc/design/cluster_train/README.md @@ -23,9 +23,9 @@ Their relation is illustrated in the following graph: -By coordinate these processes, paddle can complete the procedure of training neural networks using SGD. Paddle can support both "synchronize SGD" and "asynchronize SGD". +By coordinating these processes, PaddlePaddle supports use both Synchronize Stochastic Gradient Descent (sync SGD) and Asynchronous Stochastic Gradient Descent (async SGD) to train user-defined neural network topologies. -When training with "sync SGD", paddle parameter servers use barriers to wait for all trainers to finish gradients update. When using "async SGD", parameter servers would not wait for all trainers, so training and parameter optimize will run in parallel. parameter servers will not depend on each other, they will receive the gradients update in parrallel; Also trainers will not depend on each other, run training jobs in parrallel. Using asyc SGD will be faster when training, but parameters on one of the parameter server will be newer than the other, but this will introduce more Randomness. +When training with sync SGD, parameter servers wait for all trainers to finish gradients update and then send the updated parameters to trainers, training can not proceed until the trainer received the updated parameters. This creates a synchronization point between trainers. When training with async SGD, each trainer upload gradient and download new parameters individually, without the synchronization with other trainers. Using asyc SGD will be faster in terms of time per pass, but have more noise in gradient since trainers are likely to have a stale model. ### Master Process diff --git a/doc/design/cluster_train/checkpointing.md b/doc/design/cluster_train/checkpointing.md index 0a2682899c4..a4d09004bde 100644 --- a/doc/design/cluster_train/checkpointing.md +++ b/doc/design/cluster_train/checkpointing.md @@ -1,5 +1,5 @@ -## 模型参数检查点(Checkpointing) -模型数据检查点的实现,可以有效的避免parameter server的单点或多点同时故障。模型参数检查点通过定期向磁盘上保存一份存储在parameter server内存中的模型数据的完整镜像,来保证训练过程可以从中间状态重新启动。在一个不可中断并缺少备份的训练任务中,可以通过阶段性的保存每个parameter server的数据快照(snapshot)到 ***分布式存储服务*** 达到容灾的目的,比如每隔10分钟最新的快照,并删除更早的快照。在出现单点故障时,只需要恢复这台节点,或者将这台节点迁移到另一个节点并启动即可恢复训练任务。 +## 模型参数检查点(Checkpointing) +模型数据检查点的实现,可以有效的避免parameter server的单点或多点同时故障。模型参数检查点通过定期向磁盘上保存一份存储在parameter server内存中的模型数据的完整镜像,来保证训练过程可以从中间状态重新启动。在一个不可中断并缺少备份的训练任务中,可以通过阶段性的保存每个parameter server的数据快照(snapshot)到 ***分布式存储服务*** 达到容灾的目的,比如每隔10分钟最新的快照,并删除更早的快照。在出现单点故障时,只需要恢复这台节点,或者将这台节点迁移到另一个节点并启动即可恢复训练任务。 @@ -13,10 +13,10 @@ 检查点保存程序流程: -1. 如果满足条件""每个pass或每n个mini-batch"时,parameter server会锁住保存parameter的内存,开始保存检查点。如果已经正在执行保存检查点的任务,则忽略。 +1. 如果满足条件"每隔10分钟"时,parameter server会获取parameters内存的`read_lock`,启动一个新的线程开始保存检查点。如果已经正在执行保存检查点的线程,则忽略。由于对parameters的更新需要获取parameters内存的`write_lock`,所以在写入快照的过程中,parameter server会暂停参数更新并等待。 2. parameter server生成一个UUID,向指定的目录中一个新的文件(文件名为此UUID)写入快照数据。在快照写入完成后,计算这个文件的MD5 sum。然后在etcd的`/checkpoints/[pserver_id]`中写入json内容:`{"uuid": [UUID], "md5", "MD5 sum", "timestamp": xxxx}`。 3. 删除磁盘目录中不是当前uuid的快照文件。 -4. 释放对paramters内存的锁定。 +4. 释放对paramters内存的锁定,停止保存检查点的线程。 这里需要用户额外注意,在您的实际环境中,训练任务的运行可能会占满trainer和parameter server之间的网络带宽,如果parameter server此时还需要通过网络访问分布式存储以保存快照,可能会造成网络拥塞,而出现阶段性的运行停滞。 @@ -30,8 +30,8 @@ 1. 开始提供服务 ## TODO List -### 推测执行/加速执行(TODO) -在异构集群中,如果存在某些trainer执行速度过慢会影响整体集群的速度(如图中Trainer 1),此时master将负责启动一个新的Trainer(Accelerate Trainer 2),使用同样的训练数据block。哪个trainer先完成block的训练,则把另一个慢速的kill掉。 +### 推测执行/加速执行(TODO) +在异构集群中,如果存在某些trainer执行速度过慢会影响整体集群的速度(如图中Trainer 1),此时master将负责启动一个新的Trainer(Accelerate Trainer 2),使用同样的训练数据block。哪个trainer先完成block的训练,则把另一个慢速的kill掉。 ### 动态扩容/缩容 目前只考虑动态扩容trainer数量,可以减小系统复杂性。 @@ -42,4 +42,4 @@ * shard: 分片,通常指将一个整体拆分成多份的其中的一份。 * model shard: 将一个神经网络参数拆分成多份,每个shard分别存储在其中一台parameter server之上 * parameter block: 多个parameter block构成一个model shard -* 单点故障: 任意时刻只可能同时有一台服务器故障。由于集群中同时存在两台机器故障的概率极低((平均故障率*平均故障修复时间)^2)只对特殊在线系统考虑两台以上同时故障的容灾。 +* 单点故障: 任意时刻只可能同时有一台服务器故障。由于集群中同时存在两台机器故障的概率极低((平均故障率*平均故障修复时间)^2)只对特殊在线系统考虑两台以上同时故障的容灾。 diff --git a/doc/design/cluster_train/data_dispatch.md b/doc/design/cluster_train/data_dispatch.md index cce3deb0047..b013b14e476 100644 --- a/doc/design/cluster_train/data_dispatch.md +++ b/doc/design/cluster_train/data_dispatch.md @@ -1,7 +1,7 @@ ## 训练数据的存储和分发 ### 流程介绍 -生产环境中的训练数据集通常体积很大,并被存储在诸如Hadoop HDFS, Ceph, AWS S3之类的分布式存储之上。这些分布式存储服务通常会把数据切割成多个分片分布式的存储在多个节点之上。这样就可以在云端执行多种数据类计算任务,包括: +生产环境中的训练数据集通常体积很大,并被存储在诸如Hadoop HDFS,Ceph,AWS S3之类的分布式存储之上。这些分布式存储服务通常会把数据切割成多个分片分布式的存储在多个节点之上。这样就可以在云端执行多种数据类计算任务,包括: * 数据预处理任务 * Paddle训练任务 @@ -9,11 +9,11 @@ -在上图中显示了在一个实际生产环境中的应用(人脸识别)的数据流图。生产环境的日志数据会通过实时流的方式(Kafka)和离线数据的方式(HDFS)存储,并在集群中运行多个分布式数据处理任务,比如流式数据处理(online data process),离线批处理(offline data process)完成数据的预处理,提供给paddle作为训练数据。用于也可以上传labeled data到分布式存储补充训练数据。在paddle之上运行的深度学习训练输出的模型会提供给在线人脸识别的应用使用。 +在上图中显示了在一个实际生产环境中的应用(人脸识别)的数据流图。生产环境的日志数据会通过实时流的方式(Kafka)和离线数据的方式(HDFS)存储,并在集群中运行多个分布式数据处理任务,比如流式数据处理(online data process),离线批处理(offline data process)完成数据的预处理,提供给paddle作为训练数据。用于也可以上传labeled data到分布式存储补充训练数据。在paddle之上运行的深度学习训练输出的模型会提供给在线人脸识别的应用使用。 ### 训练数据的存储 -选择GlusterFS作为训练数据的存储服务(后续的实现考虑HDFS)。 +选择GlusterFS作为训练数据的存储服务(后续的实现考虑HDFS)。 在Kubernetes上运行的不同的计算框架,可以通过Volume或PersistentVolume挂载存储空间到每个容器中。 @@ -39,7 +39,7 @@ paddle upload train_data.list ... ``` -对于文本类训练数据样例如下(机器翻译),一行中包含源语言,目标语言的文本(label): +对于文本类训练数据样例如下(机器翻译),一行中包含源语言,目标语言的文本(label): ``` L' inflation , en Europe , a dérapé sur l' alimentation Food : Where European inflation slipped up @@ -63,22 +63,29 @@ trainer.train内部会获取reader的内容: ``` def paddle.train(batch_reader): - r = batch_reader() # create a interator for one pass of data + r = batch_reader() # create a iterator for one pass of data for batch in r: # train ``` -这里面batch是含有128个data instance的mini-batch。每一个data instance会是一个tuple,tuple元素的顺序与`.list`文件文件中每一列的顺序是一致的。每一个data instance会是(raw_image_file_binary_data, label)。其中raw_image_file_binary_data是对应图像文件的没有解码的原始二进制数据,用户需要自己解码。label是文本类型(比如:“1“,”2“),这里用户需要的其实是整形,用户需要自己转换成整形。 +这里面batch是含有128个data instance的mini-batch。每一个data instance会是一个tuple,tuple元素的顺序与`.list`文件文件中每一列的顺序是一致的。每一个data instance会是(raw_image_file_binary_data, label)。其中raw_image_file_binary_data是对应图像文件的没有解码的原始二进制数据,用户需要自己解码。label是文本类型(比如:“1“,”2“),这里用户需要的其实是整形,用户需要自己转换成整形。 ### 实现reader reader的实现需要考虑本地训练程序实现之后,可以不修改程序直接提交集群进行分布式训练。要达到这样的目标,需要实现下面的功能: -paddle会实现内置的默认reader和对公开数据集的reader(如MNIST,BOW等)。这些内置的reader都会被一个`paddle.reader`修饰器修饰。这个修饰器会读取环境变量`PADDLE_TRAIN_LOCAL`,如果是True,则返回只有一个文件的task queue生成器,这个文件就是reader传入的文件。如果是False,则为分布式训练模式(在集群中训练的任务,都会从这个环境变量获得False值),开始读取task queue,获取分布式存储系统中的文件名数据,并返回这个task queue。不同的reader的实现,都要遵守固定的方式:从task queue生成器中逐个获取文件名,完成对不同训练数据的解析。 +paddle会封装一个在集群中使用的reader: `paddle.dist.reader()`。在集群训练时需要使用这个reader指定要使用的数据集开始训练。用户的训练程序需要按照如下方式初始化reader: -同样用户在实现自己的reader时,也需要使用此修饰器来修饰reader函数。 +```python +if os.getenv("PADDLE_TRAIN_LOCAL"): + reader = my_local_reader("dataset-name") +else: + reader = paddle.dist.reader("dataset-name") +``` + +用户训练程序提交到集群之后,集群会自动设置`PADDLE_TRAIN_LOCAL`环境变量,reader会被配置成集群训练的版本。其中`paddle.dist.reader()`需要从master的队列中获得需要开始执行的训练task,并找到对应的训练数据文件,开始训练任务。如果用户的训练数据源来自于其他服务,比如从集群中的Kafka,zeromq队列读取,也可以根据实际情况实现集群中运行的reader程序。 ## TODO -### 支持将数据合并成内部的文件格式(key-value),方便sharding与顺序读取 +### 支持将数据合并成内部的文件格式(key-value),方便sharding与顺序读取 ### 支持用户自定义的数据预处理job -- GitLab From 4f769a8a1db1af8a748f703387f7d0193b200295 Mon Sep 17 00:00:00 2001 From: Luo Tao Date: Fri, 21 Apr 2017 17:59:33 +0800 Subject: [PATCH 0094/3256] fix sphinx doc bug --- doc/api/v2/config/layer.rst | 215 ++++++------------ .../paddle/trainer_config_helpers/layers.py | 7 +- 2 files changed, 78 insertions(+), 144 deletions(-) diff --git a/doc/api/v2/config/layer.rst b/doc/api/v2/config/layer.rst index 05817ec8545..2a02baf17ba 100644 --- a/doc/api/v2/config/layer.rst +++ b/doc/api/v2/config/layer.rst @@ -11,8 +11,7 @@ Data layer data ---- -.. automodule:: paddle.v2.layer - :members: data +.. autoclass:: paddle.v2.layer.data :noindex: Fully Connected Layers @@ -22,14 +21,12 @@ Fully Connected Layers fc -- -.. automodule:: paddle.v2.layer - :members: fc +.. autoclass:: paddle.v2.layer.fc :noindex: selective_fc ------------ -.. automodule:: paddle.v2.layer - :members: selective_fc +.. autoclass:: paddle.v2.layer.selective_fc :noindex: Conv Layers @@ -37,34 +34,29 @@ Conv Layers conv_operator ------------- -.. automodule:: paddle.v2.layer - :members: conv_operator +.. autoclass:: paddle.v2.layer.conv_operator :noindex: conv_projection --------------- -.. automodule:: paddle.v2.layer - :members: conv_projection +.. autoclass:: paddle.v2.layer.conv_projection :noindex: conv_shift ---------- -.. automodule:: paddle.v2.layer - :members: conv_shift +.. autoclass:: paddle.v2.layer.conv_shift :noindex: img_conv -------- -.. automodule:: paddle.v2.layer - :members: img_conv +.. autoclass:: paddle.v2.layer.img_conv :noindex: .. _api_v2.layer_context_projection: context_projection ------------------ -.. automodule:: paddle.v2.layer - :members: context_projection +.. autoclass:: paddle.v2.layer.context_projection :noindex: Image Pooling Layer @@ -72,20 +64,17 @@ Image Pooling Layer img_pool -------- -.. automodule:: paddle.v2.layer - :members: img_pool +.. autoclass:: paddle.v2.layer.img_pool :noindex: spp --- -.. automodule:: paddle.v2.layer - :members: spp +.. autoclass:: paddle.v2.layer.spp :noindex: maxout ------ -.. automodule:: paddle.v2.layer - :members: maxout +.. autoclass:: paddle.v2.layer.maxout :noindex: Norm Layer @@ -93,26 +82,22 @@ Norm Layer img_cmrnorm ----------- -.. automodule:: paddle.v2.layer - :members: img_cmrnorm +.. autoclass:: paddle.v2.layer.img_cmrnorm :noindex: batch_norm ---------- -.. automodule:: paddle.v2.layer - :members: batch_norm +.. autoclass:: paddle.v2.layer.batch_norm :noindex: sum_to_one_norm --------------- -.. automodule:: paddle.v2.layer - :members: sum_to_one_norm +.. autoclass:: paddle.v2.layer.sum_to_one_norm :noindex: cross_channel_norm ------------------ -.. automodule:: paddle.v2.layer - :members: cross_channel_norm +.. autoclass:: paddle.v2.layer.cross_channel_norm :noindex: Recurrent Layers @@ -120,20 +105,17 @@ Recurrent Layers recurrent --------- -.. automodule:: paddle.v2.layer - :members: recurrent +.. autoclass:: paddle.v2.layer.recurrent :noindex: lstmemory --------- -.. automodule:: paddle.v2.layer - :members: lstmemory +.. autoclass:: paddle.v2.layer.lstmemory :noindex: grumemory --------- -.. automodule:: paddle.v2.layer - :members: grumemory +.. autoclass:: paddle.v2.layer.grumemory :noindex: Recurrent Layer Group @@ -141,38 +123,32 @@ Recurrent Layer Group memory ------ -.. automodule:: paddle.v2.layer - :members: memory +.. autoclass:: paddle.v2.layer.memory :noindex: recurrent_group --------------- -.. automodule:: paddle.v2.layer - :members: recurrent_group +.. autoclass:: paddle.v2.layer.recurrent_group :noindex: lstm_step --------- -.. automodule:: paddle.v2.layer - :members: lstm_step +.. autoclass:: paddle.v2.layer.lstm_step :noindex: gru_step -------- -.. automodule:: paddle.v2.layer - :members: gru_step +.. autoclass:: paddle.v2.layer.gru_step :noindex: beam_search ------------ -.. automodule:: paddle.v2.layer - :members: beam_search +.. autoclass:: paddle.v2.layer.beam_search :noindex: get_output ---------- -.. automodule:: paddle.v2.layer - :members: get_output +.. autoclass:: paddle.v2.layer.get_output :noindex: Mixed Layer @@ -182,59 +158,50 @@ Mixed Layer mixed ----- -.. automodule:: paddle.v2.layer - :members: mixed +.. autoclass:: paddle.v2.layer.mixed :noindex: .. _api_v2.layer_embedding: embedding --------- -.. automodule:: paddle.v2.layer - :members: embedding +.. autoclass:: paddle.v2.layer.embedding :noindex: scaling_projection ------------------ -.. automodule:: paddle.v2.layer - :members: scaling_projection +.. autoclass:: paddle.v2.layer.scaling_projection :noindex: dotmul_projection ----------------- -.. automodule:: paddle.v2.layer - :members: dotmul_projection +.. autoclass:: paddle.v2.layer.dotmul_projection :noindex: dotmul_operator --------------- -.. automodule:: paddle.v2.layer - :members: dotmul_operator +.. autoclass:: paddle.v2.layer.dotmul_operator :noindex: full_matrix_projection ---------------------- -.. automodule:: paddle.v2.layer - :members: full_matrix_projection +.. autoclass:: paddle.v2.layer.full_matrix_projection :noindex: identity_projection ------------------- -.. automodule:: paddle.v2.layer - :members: identity_projection +.. autoclass:: paddle.v2.layer.identity_projection :noindex: table_projection ---------------- -.. automodule:: paddle.v2.layer - :members: table_projection +.. autoclass:: paddle.v2.layer.table_projection :noindex: trans_full_matrix_projection ---------------------------- -.. automodule:: paddle.v2.layer - :members: trans_full_matrix_projection +.. autoclass:: paddle.v2.layer.trans_full_matrix_projection :noindex: Aggregate Layers @@ -244,36 +211,31 @@ Aggregate Layers pooling ------- -.. automodule:: paddle.v2.layer - :members: pooling +.. autoclass:: paddle.v2.layer.pooling :noindex: .. _api_v2.layer_last_seq: last_seq -------- -.. automodule:: paddle.v2.layer - :members: last_seq +.. autoclass:: paddle.v2.layer.last_seq :noindex: .. _api_v2.layer_first_seq: first_seq --------- -.. automodule:: paddle.v2.layer - :members: first_seq +.. autoclass:: paddle.v2.layer.first_seq :noindex: concat ------ -.. automodule:: paddle.v2.layer - :members: concat +.. autoclass:: paddle.v2.layer.concat :noindex: seq_concat ---------- -.. automodule:: paddle.v2.layer - :members: seq_concat +.. autoclass:: paddle.v2.layer.seq_concat :noindex: Reshaping Layers @@ -281,34 +243,29 @@ Reshaping Layers block_expand ------------ -.. automodule:: paddle.v2.layer - :members: block_expand +.. autoclass:: paddle.v2.layer.block_expand :noindex: .. _api_v2.layer_expand: expand ------ -.. automodule:: paddle.v2.layer - :members: expand +.. autoclass:: paddle.v2.layer.expand :noindex: repeat ------ -.. automodule:: paddle.v2.layer - :members: repeat +.. autoclass:: paddle.v2.layer.repeat :noindex: rotate ------ -.. automodule:: paddle.v2.layer - :members: rotate +.. autoclass:: paddle.v2.layer.rotate :noindex: seq_reshape ----------- -.. automodule:: paddle.v2.layer - :members: seq_reshape +.. autoclass:: paddle.v2.layer.seq_reshape :noindex: Math Layers @@ -316,64 +273,54 @@ Math Layers addto ----- -.. automodule:: paddle.v2.layer - :members: addto +.. autoclass:: paddle.v2.layer.addto :noindex: linear_comb ----------- -.. automodule:: paddle.v2.layer - :members: linear_comb +.. autoclass:: paddle.v2.layer.linear_comb :noindex: interpolation ------------- -.. automodule:: paddle.v2.layer - :members: interpolation +.. autoclass:: paddle.v2.layer.interpolation :noindex: bilinear_interp --------------- -.. automodule:: paddle.v2.layer - :members: bilinear_interp +.. autoclass:: paddle.v2.layer.bilinear_interp :noindex: power ----- -.. automodule:: paddle.v2.layer - :members: power +.. autoclass:: paddle.v2.layer.power :noindex: scaling ------- -.. automodule:: paddle.v2.layer - :members: scaling +.. autoclass:: paddle.v2.layer.scaling :noindex: slope_intercept --------------- -.. automodule:: paddle.v2.layer - :members: slope_intercept +.. autoclass:: paddle.v2.layer.slope_intercept :noindex: tensor ------ -.. automodule:: paddle.v2.layer - :members: tensor +.. autoclass:: paddle.v2.layer.tensor :noindex: .. _api_v2.layer_cos_sim: cos_sim ------- -.. automodule:: paddle.v2.layer - :members: cos_sim +.. autoclass:: paddle.v2.layer.cos_sim :noindex: trans ----- -.. automodule:: paddle.v2.layer - :members: trans +.. autoclass:: paddle.v2.layer.trans :noindex: Sampling Layers @@ -381,14 +328,12 @@ Sampling Layers maxid ----- -.. automodule:: paddle.v2.layer - :members: maxid +.. autoclass:: paddle.v2.layer.max_id :noindex: sampling_id ----------- -.. automodule:: paddle.v2.layer - :members: sampling_id +.. autoclass:: paddle.v2.layer.sampling_id :noindex: Slicing and Joining Layers @@ -396,8 +341,7 @@ Slicing and Joining Layers pad ---- -.. automodule:: paddle.v2.layer - :members: pad +.. autoclass:: paddle.v2.layer.pad :noindex: .. _api_v2.layer_costs: @@ -407,80 +351,72 @@ Cost Layers cross_entropy_cost ------------------ -.. automodule:: paddle.v2.layer - :members: cross_entropy_cost +.. autoclass:: paddle.v2.layer.cross_entropy_cost :noindex: cross_entropy_with_selfnorm_cost -------------------------------- -.. automodule:: paddle.v2.layer - :members: cross_entropy_with_selfnorm_cost +.. autoclass:: paddle.v2.layer.cross_entropy_with_selfnorm_cost :noindex: multi_binary_label_cross_entropy_cost ------------------------------------- -.. automodule:: paddle.v2.layer - :members: multi_binary_label_cross_entropy_cost +.. autoclass:: paddle.v2.layer.multi_binary_label_cross_entropy_cost :noindex: huber_cost ---------- -.. automodule:: paddle.v2.layer - :members: huber_cost +.. autoclass:: paddle.v2.layer.huber_cost :noindex: lambda_cost ----------- -.. automodule:: paddle.v2.layer - :members: lambda_cost +.. autoclass:: paddle.v2.layer.lambda_cost + :noindex: + +mse_cost +-------- +.. autoclass:: paddle.v2.layer.mse_cost :noindex: rank_cost --------- -.. automodule:: paddle.v2.layer - :members: rank_cost +.. autoclass:: paddle.v2.layer.rank_cost :noindex: sum_cost --------- -.. automodule:: paddle.v2.layer - :members: sum_cost +.. autoclass:: paddle.v2.layer.sum_cost :noindex: crf --- -.. automodule:: paddle.v2.layer - :members: crf +.. autoclass:: paddle.v2.layer.crf :noindex: crf_decoding ------------ -.. automodule:: paddle.v2.layer - :members: crf_decoding +.. autoclass:: paddle.v2.layer.crf_decoding :noindex: ctc --- -.. automodule:: paddle.v2.layer - :members: ctc +.. autoclass:: paddle.v2.layer.ctc :noindex: warp_ctc -------- -.. automodule:: paddle.v2.layer - :members: warp_ctc +.. autoclass:: paddle.v2.layer.warp_ctc :noindex: nce --- -.. automodule:: paddle.v2.layer - :members: nce +.. autoclass:: paddle.v2.layer.nce :noindex: hsigmoid --------- -.. automodule:: paddle.v2.layer - :members: hsigmoid +.. autoclass:: paddle.v2.layer.hsigmoid :noindex: Check Layer @@ -488,6 +424,5 @@ Check Layer eos --- -.. automodule:: paddle.v2.layer - :members: eos +.. autoclass:: paddle.v2.layer.eos :noindex: diff --git a/python/paddle/trainer_config_helpers/layers.py b/python/paddle/trainer_config_helpers/layers.py index 97db3c2d4c0..f906126d879 100755 --- a/python/paddle/trainer_config_helpers/layers.py +++ b/python/paddle/trainer_config_helpers/layers.py @@ -567,7 +567,7 @@ def dotmul_operator(a=None, b=None, scale=1, **kwargs): DotMulOperator takes two inputs and performs element-wise multiplication: .. math:: - out.row[i] += scale * (x.row[i] .* y.row[i]) + out.row[i] += scale * (a.row[i] .* b.row[i]) where :math:`.*` means element-wise multiplication, and scale is a config scalar, its default value is one. @@ -576,7 +576,7 @@ def dotmul_operator(a=None, b=None, scale=1, **kwargs): .. code-block:: python - op = dotmul_operator(x=layer1, y=layer2, scale=0.5) + op = dotmul_operator(a=layer1, b=layer2, scale=0.5) :param a: Input layer1 :type a: LayerOutput @@ -3689,8 +3689,7 @@ def mse_cost(input, label, weight=None, name=None, layer_attr=None): .. math:: - $\frac{1}{N}\sum_{i=1}^N(t _i- y_i)^2$ - + \frac{1}{N}\sum_{i=1}^N(t_i-y_i)^2 :param name: layer name. :type name: basestring -- GitLab From a278048b35e7690e769c1c84f3eb9972b2a4aa93 Mon Sep 17 00:00:00 2001 From: qijun Date: Fri, 21 Apr 2017 18:56:11 +0800 Subject: [PATCH 0095/3256] find whl path --- 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 1613d970bc4..185aa050777 100644 --- a/paddle/scripts/docker/build.sh +++ b/paddle/scripts/docker/build.sh @@ -113,7 +113,7 @@ ADD build/*.deb /usr/local/opt/paddle/deb/ # run paddle version to install python packages first RUN dpkg -i /usr/local/opt/paddle/deb/*.deb && \ rm -f /usr/local/opt/paddle/deb/*.deb && \ - pip install /usr/local/opt/paddle/share/wheels/*.whl && \ + find /usr/ -name '*paddle-*.whl' | xargs pip install && \ paddle version ${CPU_DOCKER_PYTHON_HOME_ENV} ${DOCKERFILE_CUDNN_DSO} -- GitLab From 42b09b9cac254cf91f8655a75c1ee68bf1ab59ed Mon Sep 17 00:00:00 2001 From: Peng Li Date: Fri, 21 Apr 2017 21:53:09 +0800 Subject: [PATCH 0096/3256] Fix doc error --- python/paddle/trainer_config_helpers/attrs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/paddle/trainer_config_helpers/attrs.py b/python/paddle/trainer_config_helpers/attrs.py index bf020883460..41ad05c3eb4 100644 --- a/python/paddle/trainer_config_helpers/attrs.py +++ b/python/paddle/trainer_config_helpers/attrs.py @@ -196,7 +196,7 @@ class ExtraLayerAttribute(object): `_. :type drop_rate: float - :param device: device ID of layer. device=-1, use CPU. device>0, use GPU. + :param device: device ID of layer. device=-1, use CPU. device>=0, use GPU. The details allocation in parallel_nn please refer to `here `_. -- GitLab From 68c1efdd9c5b0e38893d5034e36a548c6e06a3e6 Mon Sep 17 00:00:00 2001 From: qiaolongfei Date: Sat, 22 Apr 2017 07:39:41 +0800 Subject: [PATCH 0097/3256] fix the bug of use sparse_remote_update with MultiGradientMachine --- paddle/gserver/gradientmachines/MultiGradientMachine.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paddle/gserver/gradientmachines/MultiGradientMachine.cpp b/paddle/gserver/gradientmachines/MultiGradientMachine.cpp index 6ae60102b3e..3159026e6b9 100644 --- a/paddle/gserver/gradientmachines/MultiGradientMachine.cpp +++ b/paddle/gserver/gradientmachines/MultiGradientMachine.cpp @@ -518,7 +518,7 @@ void TrainerThread::computeThread() { backward(); break; case MultiGradientMachine::TASK_COPY_IN_ARGS: - copyInArgs(); + batchSize_ = copyInArgs(); inArgsCopied_ = true; multiMachine_->waitForCopyInArgs(); break; -- GitLab From bcdfe2cd7e827a679eab235ebe592b1c831af679 Mon Sep 17 00:00:00 2001 From: qijun Date: Mon, 24 Apr 2017 10:25:53 +0800 Subject: [PATCH 0098/3256] add docs --- doc/howto/dev/write_docs_cn.rst | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/doc/howto/dev/write_docs_cn.rst b/doc/howto/dev/write_docs_cn.rst index 5051a892304..2420a1c9595 100644 --- a/doc/howto/dev/write_docs_cn.rst +++ b/doc/howto/dev/write_docs_cn.rst @@ -37,17 +37,43 @@ PaddlePaddle的文档构建有直接构建和基于Docker构建两种方式。 直接构建PaddlePaddle的文档 -------------------------- -TBD +因为PaddlePaddle的v2 api文档生成过程依赖于py_paddle Python包,用户需要首先确认py_paddle包已经安装。 + +.. code-block:: bash + + python -c "import py_paddle" + +如果提示错误,那么用户需要在本地编译安装PaddlePaddle,请参考 `源码编译文档 `_ 。 +注意,用户需要安装编译在首次编译安装PaddlePaddle时,请将WITH_DOC选项关闭。在编译安装正确之后,确认py_paddle包已经安装,即可进行下一步操作。 + +如果提示正确,可以执行以下命令编译生成文档,即 + +.. code-block:: bash + + cd TO_YOUR_PADDLE_CLONE_PATH + mkdir build_doc + cd build_doc + cmake .. -DWITH_DOC=ON + make paddle_docs paddle_docs_cn -j `nproc` + +编译完成之后,在build_doc/doc目录之下会生成如下两个子目录\: + +* en 英文文档目录 +* cn 中文文档目录 + +打开浏览器访问对应目录下的index.html即可访问本地文档。 + 如何书写PaddlePaddle的文档 ========================== -TBD +PaddlePaddle文档使用sphix自动生成,用户可以参考sphinx教程进行书写。 如何更新www.paddlepaddle.org文档 ================================ -TBD +目前PaddlePaddle的develop分支的文档是自动触发更新的。用户可以在http://www.paddlepaddle.org/develop/doc_cn/与http://www.paddlepaddle.org/develop/doc/上分别查看最新的中英文文档。 + .. _cmake: https://cmake.org/ -- GitLab From 3dae7c53e5dac7aba75a684c47cfca89e87068c0 Mon Sep 17 00:00:00 2001 From: qijun Date: Mon, 24 Apr 2017 10:33:56 +0800 Subject: [PATCH 0099/3256] update comments --- paddle/scripts/docker/build.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/paddle/scripts/docker/build.sh b/paddle/scripts/docker/build.sh index 185aa050777..73f885c1e70 100644 --- a/paddle/scripts/docker/build.sh +++ b/paddle/scripts/docker/build.sh @@ -44,8 +44,9 @@ fi make install pip install /usr/local/opt/paddle/share/wheels/*.whl -# Since python v2 api import py_paddle module, the generation of paddle docs -# depend on paddle's compilation and installation +# To build documentation, we need to run cmake twice. +# This awkwardness is due to https://github.com/PaddlePaddle/Paddle/issues/1854. +# It also describes a solution. if [ ${WITH_DOC} == "ON" ]; then mkdir -p /paddle/build_doc pushd /paddle/build_doc -- GitLab From 7e2d6e6198f92f1c532daaaf93115617481db634 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Mon, 24 Apr 2017 12:40:04 +0800 Subject: [PATCH 0100/3256] Start release note --- RELEASE.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/RELEASE.md b/RELEASE.md index a8a245ab442..f1588cb15bb 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -1,3 +1,12 @@ +# Release v0.10.0 + +## New Features + +## Improvements + +## Bug Fixes + + # Release v0.9.0 ## New Features: -- GitLab From 35f1dfde72f9be8d9fccdaaf5d328da9f8ffcc36 Mon Sep 17 00:00:00 2001 From: qiaolongfei Date: Mon, 24 Apr 2017 13:58:14 +0800 Subject: [PATCH 0101/3256] chage trainer.save_parameter to trainer.save_parameter_to_tar --- demo/word2vec/api_train_v2.py | 4 ++-- python/paddle/v2/trainer.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/demo/word2vec/api_train_v2.py b/demo/word2vec/api_train_v2.py index 604adba192e..8fea6feeef0 100644 --- a/demo/word2vec/api_train_v2.py +++ b/demo/word2vec/api_train_v2.py @@ -29,7 +29,7 @@ def main(): else: paddle.init( use_gpu=False, - trainer_count=1, + trainer_count=2, port=7164, ports_num=1, ports_num_for_sparse=1, @@ -69,7 +69,7 @@ def main(): def event_handler(event): if isinstance(event, paddle.event.EndIteration): if event.batch_id % 100 == 0: - trainer.save_parameter("output", "batch-" + str(event.batch_id)) + trainer.save_parameter_to_tar("output", "batch-" + str(event.batch_id)) result = trainer.test( paddle.batch( paddle.dataset.imikolov.test(word_dict, N), 32)) diff --git a/python/paddle/v2/trainer.py b/python/paddle/v2/trainer.py index 028f25a0467..220d459525f 100644 --- a/python/paddle/v2/trainer.py +++ b/python/paddle/v2/trainer.py @@ -98,7 +98,7 @@ class SGD(object): self.__gradient_machine__.prefetch(in_args) self.__parameter_updater__.getParametersRemote() - def save_parameter(self, dir_name, file_name): + def save_parameter_to_tar(self, dir_name, file_name): if not os.path.exists(dir_name): os.makedirs(dir_name) param_file_name = dir_name + "/" + file_name + '.tar.gz' -- GitLab From a9b33f38014322126f74fd34a750d4e87a08f783 Mon Sep 17 00:00:00 2001 From: typhoonzero Date: Mon, 24 Apr 2017 14:04:58 +0800 Subject: [PATCH 0102/3256] update --- doc/design/cluster_train/checkpointing.md | 1 - doc/design/cluster_train/data_dispatch.md | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/doc/design/cluster_train/checkpointing.md b/doc/design/cluster_train/checkpointing.md index a4d09004bde..c87ef2c7d26 100644 --- a/doc/design/cluster_train/checkpointing.md +++ b/doc/design/cluster_train/checkpointing.md @@ -8,7 +8,6 @@ 说明: * parameter server在集群中启动后,自动挂载分布式存储目录,并把快照保存到这个目录下。 -* ***注:parameter server在保存检查点时,利用了Linux内核的“写时复制”技术,在fork的进程中保存检查点,原进程可以继续接收trainer的梯度更新请求,而不影响检查点数据的保存。*** * ***注:每个parameter server的检查点各自独立保存,暂时不考虑多个parameter server同步的保存一个特定时间点的全局检查点,因为这样做也没法保证消除随机性。*** 检查点保存程序流程: diff --git a/doc/design/cluster_train/data_dispatch.md b/doc/design/cluster_train/data_dispatch.md index b013b14e476..9f2fc404a09 100644 --- a/doc/design/cluster_train/data_dispatch.md +++ b/doc/design/cluster_train/data_dispatch.md @@ -21,10 +21,10 @@ ### 上传训练文件 -使用下面命令,可以把本地的训练数据上传到存储集群中 +使用下面命令,可以把本地的训练数据上传到存储集群中,并指定上传数据的`dataset-name`: ``` -paddle upload train_data.list +paddle upload train_data.list "dataset-name" ``` 其中`.list`文件描述了训练数据的文件和对应的label,对于图像类数据,`.list文件`样例如下,每一行包含了图片文件的路径和其label(用tab分隔开): -- GitLab From 5ba7f6167aaf378d807e2b4f3c5b0125e5462f5d Mon Sep 17 00:00:00 2001 From: hedaoyuan Date: Mon, 24 Apr 2017 15:14:19 +0800 Subject: [PATCH 0103/3256] follow comment --- doc/howto/raspberry/build_for_raspberry.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/howto/raspberry/build_for_raspberry.md b/doc/howto/raspberry/build_for_raspberry.md index ba23c8457bc..d6888e47ec6 100644 --- a/doc/howto/raspberry/build_for_raspberry.md +++ b/doc/howto/raspberry/build_for_raspberry.md @@ -2,7 +2,7 @@ 这里考虑的是交叉编译方式,即在Linux-X86环境下构建raspberry pi下可运行的paddle。 -## 下载较差编译环境 +## 下载交叉编译环境 ``` git clone https://github.com/raspberrypi/tools ``` -- GitLab From f30e9f417425fdfb393092eee6dd2234d830caba Mon Sep 17 00:00:00 2001 From: qijun Date: Mon, 24 Apr 2017 15:18:04 +0800 Subject: [PATCH 0104/3256] polish doc --- doc/howto/dev/write_docs_cn.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/howto/dev/write_docs_cn.rst b/doc/howto/dev/write_docs_cn.rst index 2420a1c9595..c8ecbc94b95 100644 --- a/doc/howto/dev/write_docs_cn.rst +++ b/doc/howto/dev/write_docs_cn.rst @@ -44,7 +44,7 @@ PaddlePaddle的文档构建有直接构建和基于Docker构建两种方式。 python -c "import py_paddle" 如果提示错误,那么用户需要在本地编译安装PaddlePaddle,请参考 `源码编译文档 `_ 。 -注意,用户需要安装编译在首次编译安装PaddlePaddle时,请将WITH_DOC选项关闭。在编译安装正确之后,确认py_paddle包已经安装,即可进行下一步操作。 +注意,用户在首次编译安装PaddlePaddle时,请将WITH_DOC选项关闭。在编译安装正确之后,请再次确认py_paddle包已经安装,即可进行下一步操作。 如果提示正确,可以执行以下命令编译生成文档,即 -- GitLab From 9e9d456220e1718d0fe2e24a509fa2a9eb324d7c Mon Sep 17 00:00:00 2001 From: qiaolongfei Date: Mon, 24 Apr 2017 15:27:52 +0800 Subject: [PATCH 0105/3256] fix pre-commit check --- demo/word2vec/api_train_v2.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/demo/word2vec/api_train_v2.py b/demo/word2vec/api_train_v2.py index 8fea6feeef0..98ade830cf5 100644 --- a/demo/word2vec/api_train_v2.py +++ b/demo/word2vec/api_train_v2.py @@ -69,7 +69,8 @@ def main(): def event_handler(event): if isinstance(event, paddle.event.EndIteration): if event.batch_id % 100 == 0: - trainer.save_parameter_to_tar("output", "batch-" + str(event.batch_id)) + trainer.save_parameter_to_tar("output", + "batch-" + str(event.batch_id)) result = trainer.test( paddle.batch( paddle.dataset.imikolov.test(word_dict, N), 32)) -- GitLab From 1fd00758acc6c536ef3979a2d9aaf670da7a2fbe Mon Sep 17 00:00:00 2001 From: dangqingqing Date: Mon, 24 Apr 2017 15:31:30 +0800 Subject: [PATCH 0106/3256] SmoothL1 loss --- doc/api/v1/trainer_config_helpers/layers.rst | 6 ++ paddle/gserver/layers/CostLayer.cpp | 2 +- paddle/gserver/layers/CostLayer.h | 10 ++-- paddle/gserver/tests/test_LayerGrad.cpp | 2 +- paddle/math/Matrix.cpp | 30 +++++----- python/paddle/trainer/config_parser.py | 1 + .../paddle/trainer_config_helpers/layers.py | 57 ++++++++++++++++++- .../tests/configs/file_list.sh | 2 +- .../configs/protostr/test_smooth_l1.protostr | 40 +++++++++++++ .../tests/configs/test_smooth_l1.py | 7 +++ 10 files changed, 135 insertions(+), 22 deletions(-) create mode 100644 python/paddle/trainer_config_helpers/tests/configs/protostr/test_smooth_l1.protostr create mode 100644 python/paddle/trainer_config_helpers/tests/configs/test_smooth_l1.py diff --git a/doc/api/v1/trainer_config_helpers/layers.rst b/doc/api/v1/trainer_config_helpers/layers.rst index 24389c2d857..9dec89063ae 100644 --- a/doc/api/v1/trainer_config_helpers/layers.rst +++ b/doc/api/v1/trainer_config_helpers/layers.rst @@ -498,6 +498,12 @@ hsigmoid :members: hsigmoid :noindex: +smooth_l1 +--------- +.. automodule:: paddle.trainer_config_helpers.layers + :members: smooth_l1 + :noindex: + Check Layer ============ diff --git a/paddle/gserver/layers/CostLayer.cpp b/paddle/gserver/layers/CostLayer.cpp index 4ae5b828707..69d5830dd2a 100644 --- a/paddle/gserver/layers/CostLayer.cpp +++ b/paddle/gserver/layers/CostLayer.cpp @@ -217,7 +217,7 @@ void SmoothL1CostLayer::forwardImp(Matrix& output, targetCpu->copyFrom(target); outputCpu->copyFrom(output); labelCpu->copyFrom(*label.value); - targetCpu->smoothL1(*outputCpu, *(labelCpu)); + targetCpu->smoothL1(*outputCpu, *labelCpu); target.copyFrom(*targetCpu); } else { target.smoothL1(output, *label.value); diff --git a/paddle/gserver/layers/CostLayer.h b/paddle/gserver/layers/CostLayer.h index 569a6840f0d..856d1012adc 100644 --- a/paddle/gserver/layers/CostLayer.h +++ b/paddle/gserver/layers/CostLayer.h @@ -91,8 +91,8 @@ public: * * [1] Jacob Devlin, Rabih Zbib, Zhongqiang Huang, Thomas Lamar, * Richard Schwartz, and John Makhoul. Fast and robust neural - * network joint models for statistical machine translation. - * In Proceedings of the ACL 2014 Conference. + * network joint models for statistical machine translation. * In + * Proceedings of the ACL 2014 Conference. */ class MultiClassCrossEntropyWithSelfNorm : public CostLayer { public: @@ -164,9 +164,11 @@ public: * tasks. * \f[ * L = - * (output - label)^2 * 0.5 / -1 < (output - label) < 1 / - * (output - label) - 0.5 / otherwise / + * 0.5 * x^2 if / -1 < |x| < 1 / + * |x| - 0.5 / otherwise / * \f] + * + * x = output - label */ class SmoothL1CostLayer : public CostLayer { public: diff --git a/paddle/gserver/tests/test_LayerGrad.cpp b/paddle/gserver/tests/test_LayerGrad.cpp index 0d7bd8c3b85..1bc5256b67c 100644 --- a/paddle/gserver/tests/test_LayerGrad.cpp +++ b/paddle/gserver/tests/test_LayerGrad.cpp @@ -1685,7 +1685,7 @@ TEST(Layer, smooth_l1) { config.layerConfig.add_inputs(); for (auto useGpu : {false, true}) { - testLayerGrad(config, "smooth_l1", 100, false, useGpu, false, 2.0); + testLayerGrad(config, "smooth_l1", 100, false, useGpu, false); } } diff --git a/paddle/math/Matrix.cpp b/paddle/math/Matrix.cpp index 55a7344495f..6ac61be0bf1 100644 --- a/paddle/math/Matrix.cpp +++ b/paddle/math/Matrix.cpp @@ -3616,17 +3616,18 @@ void CpuMatrix::smoothL1(Matrix& output, Matrix& label) { CHECK_EQ(output.getHeight(), numSamples); CHECK_EQ(label.getWidth(), dim); CHECK_EQ(getWidth(), (size_t)1); - real* out = output.getData(); + real* cost = getData(); + real* out = output.getData(); real* lbl = label.getData(); - for (size_t i = 0; i < numSamples; ++i, out += dim, cost += dim, lbl += dim) { + for (size_t i = 0; i < numSamples; ++i, out += dim, lbl += dim) { for (size_t j = 0; j < dim; ++j) { - cost[j] = std::fabs(out[j] - lbl[j]); - if (cost[j] < 1.0) - cost[j] = 0.5 * cost[j] * cost[j]; + real absVal = std::fabs(out[j] - lbl[j]); + if (absVal < 1.0) + cost[i] += 0.5 * absVal * absVal; else - cost[j] = cost[j] - 0.5; + cost[i] += absVal - 0.5; } } } @@ -3640,17 +3641,20 @@ void CpuMatrix::smoothL1Bp(Matrix& output, Matrix& label) { CHECK_EQ(label.getHeight(), numSamples); CHECK_EQ(output.getHeight(), numSamples); CHECK_EQ(label.getWidth(), dim); - CHECK_EQ(getWidth(), (size_t)1); + CHECK_EQ(getWidth(), dim); + real* out = output.getData(); - real* cost = getData(); real* lbl = label.getData(); + real* grad = getData(); - // f'(x) = x if |x| < 1 - // = sign(x) otherwise - for (size_t i = 0; i < numSamples; ++i, out += dim, cost += dim, lbl += dim) { + for (size_t i = 0; i < numSamples; ++i, out += dim, grad += dim, lbl += dim) { for (size_t j = 0; j < dim; ++j) { - cost[j] = out[j] - lbl[j]; - if (std::fabs(cost[j]) >= 1) cost[j] = (0 < cost[j]) - (cost[j] < 0); + real val = out[j] - lbl[j]; + if (std::fabs(val) < 1) { + grad[j] += val; + } else { + grad[j] += (real(0) < val) - (val < real(0)); + } } } } diff --git a/python/paddle/trainer/config_parser.py b/python/paddle/trainer/config_parser.py index dc89419c40f..32e31fe2c44 100644 --- a/python/paddle/trainer/config_parser.py +++ b/python/paddle/trainer/config_parser.py @@ -2119,6 +2119,7 @@ define_cost('MultiBinaryLabelCrossEntropy', 'multi_binary_label_cross_entropy') define_cost('SoftBinaryClassCrossEntropy', 'soft_binary_class_cross_entropy') define_cost('HuberTwoClass', 'huber') define_cost('SumCost', 'sum_cost') +define_cost('SmoothL1Cost', 'smooth_l1') @config_layer('hsigmoid') diff --git a/python/paddle/trainer_config_helpers/layers.py b/python/paddle/trainer_config_helpers/layers.py index f906126d879..a0432b3966b 100755 --- a/python/paddle/trainer_config_helpers/layers.py +++ b/python/paddle/trainer_config_helpers/layers.py @@ -116,6 +116,7 @@ __all__ = [ 'spp_layer', 'pad_layer', 'eos_layer', + 'smooth_l1_cost', 'layer_support', ] @@ -201,6 +202,7 @@ class LayerType(object): SOFT_BIN_CLASS_CROSS_ENTROPY = "soft_binary_class_cross_entropy" MULTI_BIN_LABEL_CROSS_ENTROPY = "multi_binary_label_cross_entropy" SUM_COST = "sum_cost" + SMOOTH_L1 = "smooth_l1" @staticmethod def is_layer_type(type_name): @@ -5249,8 +5251,6 @@ def multi_binary_label_cross_entropy(input, :type input: LayerOutput :param label: The input label. :type input: LayerOutput - :param type: The type of cost. - :type type: basestring :param name: The name of this layers. It is not necessary. :type name: None|basestring :param coeff: The coefficient affects the gradient in the backward. @@ -5279,3 +5279,56 @@ def multi_binary_label_cross_entropy(input, LayerType.MULTI_BIN_LABEL_CROSS_ENTROPY, parents=[input, label], size=1) + + +@wrap_name_default() +@layer_support() +def smooth_l1_cost(input, label, name=None, layer_attr=None): + """ + This is a L1 loss but more smooth. It requires that the + size of input and label are equal. + + More details can be found by referring to `Fast R-CNN + `_ + + .. math:: + + L = \sum_{i} smooth_{L1}(input_i - label_i) + + in which + + .. math:: + + mooth_{L1}(x) = + \begin{cases} + 0.5x^2& \text{if} |x| < 1 \\ + |x|-0.5& \text{otherwise} + \end{cases} + + .. code-block:: python + + cost = smooth_l1_cost(input=input_layer, + label=label_layer) + + :param input: The input layer. + :type input: LayerOutput + :param label: The input label. + :type input: LayerOutput + :param name: The name of this layers. It is not necessary. + :type name: None|basestring + :param layer_attr: Extra Layer Attribute. + :type layer_attr: ExtraLayerAttribute + :return: LayerOutput object. + :rtype: LayerOutput + """ + assert isinstance(input, LayerOutput) + assert isinstance(label, LayerOutput) + assert input.size == label.size + + Layer( + name=name, + type=LayerType.SMOOTH_L1, + inputs=[input.name, label.name], + **ExtraLayerAttribute.to_kwargs(layer_attr)) + return LayerOutput( + name, LayerType.SMOOTH_L1, parents=[input, label], size=1) diff --git a/python/paddle/trainer_config_helpers/tests/configs/file_list.sh b/python/paddle/trainer_config_helpers/tests/configs/file_list.sh index c9178e3c6a4..ecb2e53364e 100755 --- a/python/paddle/trainer_config_helpers/tests/configs/file_list.sh +++ b/python/paddle/trainer_config_helpers/tests/configs/file_list.sh @@ -5,6 +5,6 @@ last_first_seq test_expand_layer test_ntm_layers test_hsigmoid img_layers img_trans_layers util_layers simple_rnn_layers unused_layers test_cost_layers test_rnn_group shared_fc shared_lstm shared_gru test_cost_layers_with_weight test_spp_layer test_bilinear_interp test_maxout test_bi_grumemory math_ops -test_seq_concat_reshape) +test_seq_concat_reshape test_smooth_l1) export whole_configs=(test_split_datasource) diff --git a/python/paddle/trainer_config_helpers/tests/configs/protostr/test_smooth_l1.protostr b/python/paddle/trainer_config_helpers/tests/configs/protostr/test_smooth_l1.protostr new file mode 100644 index 00000000000..4aa041ea2e1 --- /dev/null +++ b/python/paddle/trainer_config_helpers/tests/configs/protostr/test_smooth_l1.protostr @@ -0,0 +1,40 @@ +type: "nn" +layers { + name: "input" + type: "data" + size: 300 + active_type: "" +} +layers { + name: "label" + type: "data" + size: 300 + active_type: "" +} +layers { + name: "__smooth_l1_cost_0__" + type: "smooth_l1" + size: 1 + active_type: "" + inputs { + input_layer_name: "input" + } + inputs { + input_layer_name: "label" + } + coeff: 1.0 +} +input_layer_names: "input" +input_layer_names: "label" +output_layer_names: "__smooth_l1_cost_0__" +sub_models { + name: "root" + layer_names: "input" + layer_names: "label" + layer_names: "__smooth_l1_cost_0__" + input_layer_names: "input" + input_layer_names: "label" + output_layer_names: "__smooth_l1_cost_0__" + is_recurrent_layer_group: false +} + diff --git a/python/paddle/trainer_config_helpers/tests/configs/test_smooth_l1.py b/python/paddle/trainer_config_helpers/tests/configs/test_smooth_l1.py new file mode 100644 index 00000000000..66629662dd9 --- /dev/null +++ b/python/paddle/trainer_config_helpers/tests/configs/test_smooth_l1.py @@ -0,0 +1,7 @@ +from paddle.trainer_config_helpers import * + +data = data_layer(name='input', size=300) +lbl = data_layer(name='label', size=300) +smooth_l1 = smooth_l1_cost(input=data, label=lbl) + +outputs(smooth_l1) -- GitLab From 22f2519ebac6a30ba46115d14d21f5814bbb2602 Mon Sep 17 00:00:00 2001 From: dangqingqing Date: Mon, 24 Apr 2017 15:34:25 +0800 Subject: [PATCH 0107/3256] refine documents --- python/paddle/trainer_config_helpers/layers.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/python/paddle/trainer_config_helpers/layers.py b/python/paddle/trainer_config_helpers/layers.py index a0432b3966b..56fca13d372 100755 --- a/python/paddle/trainer_config_helpers/layers.py +++ b/python/paddle/trainer_config_helpers/layers.py @@ -5286,10 +5286,7 @@ def multi_binary_label_cross_entropy(input, def smooth_l1_cost(input, label, name=None, layer_attr=None): """ This is a L1 loss but more smooth. It requires that the - size of input and label are equal. - - More details can be found by referring to `Fast R-CNN - `_ + size of input and label are equal. The formula is as follows, .. math:: @@ -5305,6 +5302,9 @@ def smooth_l1_cost(input, label, name=None, layer_attr=None): |x|-0.5& \text{otherwise} \end{cases} + More details can be found by referring to `Fast R-CNN + `_ + .. code-block:: python cost = smooth_l1_cost(input=input_layer, -- GitLab From 44958781150932a27a8ff5747c8caec70113eec2 Mon Sep 17 00:00:00 2001 From: qijun Date: Mon, 24 Apr 2017 16:03:57 +0800 Subject: [PATCH 0108/3256] update docker build doc --- doc/howto/dev/write_docs_cn.rst | 12 ++++-------- paddle/scripts/tools/build_docs/Dockerfile | 7 ------- paddle/scripts/tools/build_docs/build.sh | 13 ------------- paddle/scripts/tools/build_docs/build_docs.sh | 3 +-- 4 files changed, 5 insertions(+), 30 deletions(-) delete mode 100644 paddle/scripts/tools/build_docs/Dockerfile delete mode 100755 paddle/scripts/tools/build_docs/build.sh diff --git a/doc/howto/dev/write_docs_cn.rst b/doc/howto/dev/write_docs_cn.rst index c8ecbc94b95..fb22b473f89 100644 --- a/doc/howto/dev/write_docs_cn.rst +++ b/doc/howto/dev/write_docs_cn.rst @@ -19,19 +19,15 @@ PaddlePaddle的文档构建有直接构建和基于Docker构建两种方式。 .. code-block:: bash cd TO_YOUR_PADDLE_CLONE_PATH - cd paddle/scripts/tools/build_docs - bash build_docs.sh + bash paddle/scripts/tools/build_docs.sh -编译完成后,该目录下会生成如下两个子目录\: +编译完成后,会在当前目录生成两个子目录,build与build_doc,其中build_doc/doc目录之下包含两个子目录\: -* doc 英文文档目录 -* doc_cn 中文文档目录 +* en 英文文档目录 +* cn 中文文档目录 打开浏览器访问对应目录下的index.html即可访问本地文档。 -.. code-block:: bash - - open doc_cn/index.html 直接构建PaddlePaddle的文档 diff --git a/paddle/scripts/tools/build_docs/Dockerfile b/paddle/scripts/tools/build_docs/Dockerfile deleted file mode 100644 index 78dc756bd11..00000000000 --- a/paddle/scripts/tools/build_docs/Dockerfile +++ /dev/null @@ -1,7 +0,0 @@ -FROM paddledev/paddle:cpu-devel-latest -COPY build.sh / -RUN pip install sphinx &&\ - pip install sphinx_rtd_theme &&\ - apt install -y doxygen graphviz &&\ - pip install recommonmark numpy protobuf==2.6.1 -CMD /build.sh diff --git a/paddle/scripts/tools/build_docs/build.sh b/paddle/scripts/tools/build_docs/build.sh deleted file mode 100755 index a23b6e61d45..00000000000 --- a/paddle/scripts/tools/build_docs/build.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash -set -ex - -mkdir -p /build -cd /build -cmake /paddle -DWITH_DOC=ON -make paddle_docs paddle_docs_cn -j `nproc` -mkdir -p /output/doc -mkdir -p /output/doc_cn -cp -r doc/html/* /output/doc/ -cp -r doc_cn/html/* /output/doc_cn/ -cd / -rm -rf /paddle/build diff --git a/paddle/scripts/tools/build_docs/build_docs.sh b/paddle/scripts/tools/build_docs/build_docs.sh index 9f8b80435c8..fb8c26a69ad 100755 --- a/paddle/scripts/tools/build_docs/build_docs.sh +++ b/paddle/scripts/tools/build_docs/build_docs.sh @@ -1,4 +1,3 @@ #!/bin/bash set -e -docker build . -t paddle_build_doc -docker run --rm -v $PWD/../../../../:/paddle -v $PWD:/output paddle_build_doc +docker run --rm -v $PWD:/paddle -e "WITH_GPU=OFF" -e "WITH_AVX=ON" -e "WITH_DOC=ON" paddledev/paddle:dev -- GitLab From 1397521ff2e0a96b0ccac55f99d1a1b778df7a17 Mon Sep 17 00:00:00 2001 From: qijun Date: Mon, 24 Apr 2017 16:13:17 +0800 Subject: [PATCH 0109/3256] format doc rst --- doc/howto/dev/write_docs_cn.rst | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/doc/howto/dev/write_docs_cn.rst b/doc/howto/dev/write_docs_cn.rst index fb22b473f89..0272fd87823 100644 --- a/doc/howto/dev/write_docs_cn.rst +++ b/doc/howto/dev/write_docs_cn.rst @@ -18,8 +18,8 @@ PaddlePaddle的文档构建有直接构建和基于Docker构建两种方式。 .. code-block:: bash - cd TO_YOUR_PADDLE_CLONE_PATH - bash paddle/scripts/tools/build_docs.sh + cd TO_YOUR_PADDLE_CLONE_PATH + bash paddle/scripts/tools/build_docs.sh 编译完成后,会在当前目录生成两个子目录,build与build_doc,其中build_doc/doc目录之下包含两个子目录\: @@ -46,11 +46,11 @@ PaddlePaddle的文档构建有直接构建和基于Docker构建两种方式。 .. code-block:: bash - cd TO_YOUR_PADDLE_CLONE_PATH + cd TO_YOUR_PADDLE_CLONE_PATH mkdir build_doc cd build_doc - cmake .. -DWITH_DOC=ON - make paddle_docs paddle_docs_cn -j `nproc` + cmake .. -DWITH_DOC=ON + make paddle_docs paddle_docs_cn -j `nproc` 编译完成之后,在build_doc/doc目录之下会生成如下两个子目录\: @@ -63,12 +63,13 @@ PaddlePaddle的文档构建有直接构建和基于Docker构建两种方式。 如何书写PaddlePaddle的文档 ========================== -PaddlePaddle文档使用sphix自动生成,用户可以参考sphinx教程进行书写。 +PaddlePaddle文档使用 `sphinx`_ 自动生成,用户可以参考sphinx教程进行书写。 如何更新www.paddlepaddle.org文档 ================================ -目前PaddlePaddle的develop分支的文档是自动触发更新的。用户可以在http://www.paddlepaddle.org/develop/doc_cn/与http://www.paddlepaddle.org/develop/doc/上分别查看最新的中英文文档。 +目前PaddlePaddle的develop分支的文档是自动触发更新的,用户分别查看`中文文档 `_ 和 +`英文文档 `_ 。 -- GitLab From aa7d7e62b6763605ab801f8c254fef47f3fb4e3e Mon Sep 17 00:00:00 2001 From: qijun Date: Mon, 24 Apr 2017 16:22:37 +0800 Subject: [PATCH 0110/3256] fix typo error --- doc/howto/dev/write_docs_cn.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/howto/dev/write_docs_cn.rst b/doc/howto/dev/write_docs_cn.rst index 0272fd87823..dfe6219d8ee 100644 --- a/doc/howto/dev/write_docs_cn.rst +++ b/doc/howto/dev/write_docs_cn.rst @@ -2,7 +2,7 @@ 如何贡献/修改文档 ################## -PaddlePaddle的文档包括英文文档 ``doc`` 和中文文档 ``doc_cn`` 两个部分。文档都是通过 `cmake`_ 驱动 `sphinx`_ 编译生成,生成后的文档分别存储在编译目录的 ``doc`` 和 ``doc_cn`` 两个子目录下。 +PaddlePaddle的文档包括英文文档 ``doc`` 和中文文档 ``doc_cn`` 两个部分。文档都是通过 `cmake`_ 驱动 `sphinx`_ 编译生成,生成后的文档分别存储在编译目录的 ``en`` 和 ``cn`` 两个子目录下。 如何构建PaddlePaddle的文档 @@ -68,8 +68,8 @@ PaddlePaddle文档使用 `sphinx`_ 自动生成,用户可以参考sphinx教程 如何更新www.paddlepaddle.org文档 ================================ -目前PaddlePaddle的develop分支的文档是自动触发更新的,用户分别查看`中文文档 `_ 和 -`英文文档 `_ 。 +目前PaddlePaddle的develop分支的文档是自动触发更新的,用户可以分别查看最新的 `中文文档 `_ 和 +`英文文档 `_ 。 -- GitLab From 6a2776e139b7ba886daaa7ca0026f2f192b30c73 Mon Sep 17 00:00:00 2001 From: qiaolongfei Date: Mon, 24 Apr 2017 16:29:32 +0800 Subject: [PATCH 0111/3256] save_parameter_to_tar to fd --- demo/word2vec/api_train_v2.py | 5 +++-- python/paddle/v2/trainer.py | 9 ++------- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/demo/word2vec/api_train_v2.py b/demo/word2vec/api_train_v2.py index 98ade830cf5..a224951f4d4 100644 --- a/demo/word2vec/api_train_v2.py +++ b/demo/word2vec/api_train_v2.py @@ -1,3 +1,4 @@ +import gzip import math import paddle.v2 as paddle @@ -69,8 +70,8 @@ def main(): def event_handler(event): if isinstance(event, paddle.event.EndIteration): if event.batch_id % 100 == 0: - trainer.save_parameter_to_tar("output", - "batch-" + str(event.batch_id)) + with gzip.open("batch-" + str(event.batch_id), 'w') as f: + trainer.save_parameter_to_tar(f) result = trainer.test( paddle.batch( paddle.dataset.imikolov.test(word_dict, N), 32)) diff --git a/python/paddle/v2/trainer.py b/python/paddle/v2/trainer.py index 220d459525f..6a83ba8533c 100644 --- a/python/paddle/v2/trainer.py +++ b/python/paddle/v2/trainer.py @@ -98,16 +98,11 @@ class SGD(object): self.__gradient_machine__.prefetch(in_args) self.__parameter_updater__.getParametersRemote() - def save_parameter_to_tar(self, dir_name, file_name): - if not os.path.exists(dir_name): - os.makedirs(dir_name) - param_file_name = dir_name + "/" + file_name + '.tar.gz' - assert not os.path.exists(param_file_name) + def save_parameter_to_tar(self, f): self.__parameter_updater__.catchUpWith() self.__parameter_updater__.apply() self.__parameter_updater__.getParametersRemote(True, True) - with gzip.open(param_file_name, 'w') as f: - self.__parameters__.to_tar(f) + self.__parameters__.to_tar(f) self.__parameter_updater__.restore() def train(self, reader, num_passes=1, event_handler=None, feeding=None): -- GitLab From cd7c55aa8c8184cec6f7400c568b389417450382 Mon Sep 17 00:00:00 2001 From: dangqingqing Date: Mon, 24 Apr 2017 16:37:58 +0800 Subject: [PATCH 0112/3256] generate protostr automatically when adding a new test for trainer_config_helpers. --- .../tests/configs/file_list.sh | 2 +- .../tests/configs/generate_protostr.sh | 6 + .../tests/configs/protostr/test_pad.protostr | 120 ++++++++++++++++++ .../tests/configs/test_pad.py | 5 +- 4 files changed, 129 insertions(+), 4 deletions(-) create mode 100644 python/paddle/trainer_config_helpers/tests/configs/protostr/test_pad.protostr diff --git a/python/paddle/trainer_config_helpers/tests/configs/file_list.sh b/python/paddle/trainer_config_helpers/tests/configs/file_list.sh index c9178e3c6a4..164d365c15b 100755 --- a/python/paddle/trainer_config_helpers/tests/configs/file_list.sh +++ b/python/paddle/trainer_config_helpers/tests/configs/file_list.sh @@ -5,6 +5,6 @@ last_first_seq test_expand_layer test_ntm_layers test_hsigmoid img_layers img_trans_layers util_layers simple_rnn_layers unused_layers test_cost_layers test_rnn_group shared_fc shared_lstm shared_gru test_cost_layers_with_weight test_spp_layer test_bilinear_interp test_maxout test_bi_grumemory math_ops -test_seq_concat_reshape) +test_seq_concat_reshape test_pad) export whole_configs=(test_split_datasource) diff --git a/python/paddle/trainer_config_helpers/tests/configs/generate_protostr.sh b/python/paddle/trainer_config_helpers/tests/configs/generate_protostr.sh index ee5961af75e..8a318879630 100755 --- a/python/paddle/trainer_config_helpers/tests/configs/generate_protostr.sh +++ b/python/paddle/trainer_config_helpers/tests/configs/generate_protostr.sh @@ -11,6 +11,9 @@ for conf in ${configs[*]} do echo "Generating " $conf $1 -m paddle.utils.dump_config $conf.py > $protostr/$conf.protostr.unittest + if [ ! -f "$protostr/$conf.protostr" ]; then + cp $protostr/$conf.protostr.unittest $protostr/$conf.protostr + fi cat ${conf}.py |$1 test_config_parser_for_non_file_config.py > $protostr/$conf.protostr.non_file_config.unittest done @@ -18,5 +21,8 @@ for conf in ${whole_configs[*]} do echo "Generating " $conf $1 -m paddle.utils.dump_config $conf.py "" --whole > $protostr/$conf.protostr.unittest + if [ ! -f "$protostr/$conf.protostr" ]; then + cp $protostr/$conf.protostr.unittest $protostr/$conf.protostr + fi cat ${conf}.py |$1 test_config_parser_for_non_file_config.py --whole > $protostr/$conf.protostr.non_file_config.unittest done diff --git a/python/paddle/trainer_config_helpers/tests/configs/protostr/test_pad.protostr b/python/paddle/trainer_config_helpers/tests/configs/protostr/test_pad.protostr new file mode 100644 index 00000000000..15c6ab4dc8e --- /dev/null +++ b/python/paddle/trainer_config_helpers/tests/configs/protostr/test_pad.protostr @@ -0,0 +1,120 @@ +type: "nn" +layers { + name: "data" + type: "data" + size: 2016 + active_type: "" + height: 48 + width: 42 +} +layers { + name: "__conv_0__" + type: "exconv" + size: 32256 + active_type: "" + inputs { + input_layer_name: "data" + input_parameter_name: "___conv_0__.w0" + conv_conf { + filter_size: 3 + channels: 1 + stride: 1 + padding: 1 + groups: 1 + filter_channels: 1 + output_x: 42 + img_size: 42 + caffe_mode: true + filter_size_y: 3 + padding_y: 1 + stride_y: 1 + output_y: 48 + img_size_y: 48 + } + } + bias_parameter_name: "___conv_0__.wbias" + num_filters: 16 + shared_biases: true + height: 48 + width: 42 +} +layers { + name: "__pool_0__" + type: "pool" + size: 8064 + active_type: "" + inputs { + input_layer_name: "__conv_0__" + pool_conf { + pool_type: "max-projection" + channels: 16 + size_x: 2 + stride: 2 + output_x: 21 + img_size: 42 + padding: 0 + size_y: 2 + stride_y: 2 + output_y: 24 + img_size_y: 48 + padding_y: 0 + } + } + height: 24 + width: 21 +} +layers { + name: "__pad_0__" + type: "pad" + size: 14175 + active_type: "" + inputs { + input_layer_name: "__pool_0__" + pad_conf { + image_conf { + channels: 16 + img_size: 21 + img_size_y: 24 + } + pad_c: 2 + pad_c: 3 + pad_h: 1 + pad_h: 2 + pad_w: 3 + pad_w: 1 + } + } + height: 27 + width: 25 +} +parameters { + name: "___conv_0__.w0" + size: 144 + initial_mean: 0.0 + initial_std: 0.471404520791 + initial_strategy: 0 + initial_smart: false +} +parameters { + name: "___conv_0__.wbias" + size: 16 + initial_mean: 0.0 + initial_std: 0.0 + dims: 16 + dims: 1 + initial_strategy: 0 + initial_smart: false +} +input_layer_names: "data" +output_layer_names: "__pad_0__" +sub_models { + name: "root" + layer_names: "data" + layer_names: "__conv_0__" + layer_names: "__pool_0__" + layer_names: "__pad_0__" + input_layer_names: "data" + output_layer_names: "__pad_0__" + is_recurrent_layer_group: false +} + diff --git a/python/paddle/trainer_config_helpers/tests/configs/test_pad.py b/python/paddle/trainer_config_helpers/tests/configs/test_pad.py index bb5f13410db..491e8c8caab 100644 --- a/python/paddle/trainer_config_helpers/tests/configs/test_pad.py +++ b/python/paddle/trainer_config_helpers/tests/configs/test_pad.py @@ -2,7 +2,7 @@ from paddle.trainer_config_helpers import * settings(batch_size=1000, learning_rate=1e-5) -data = data_layer(name='data', size=2304, height=48, width=42) +data = data_layer(name='data', size=2016, height=48, width=42) conv = img_conv_layer( input=data, @@ -13,8 +13,7 @@ conv = img_conv_layer( act=LinearActivation(), bias_attr=True) -pool = img_pool_layer( - input=conv, num_channels=8, pool_size=2, stride=2, pool_type=MaxPooling()) +pool = img_pool_layer(input=conv, pool_size=2, stride=2, pool_type=MaxPooling()) pad = pad_layer(input=pool, pad_c=[2, 3], pad_h=[1, 2], pad_w=[3, 1]) -- GitLab From cb84cbab9dcc6acca30d65de569bc98770edef41 Mon Sep 17 00:00:00 2001 From: qiaolongfei Date: Mon, 24 Apr 2017 16:42:58 +0800 Subject: [PATCH 0113/3256] add .tar.gz suffix to parameter save filename --- demo/word2vec/api_train_v2.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/demo/word2vec/api_train_v2.py b/demo/word2vec/api_train_v2.py index a224951f4d4..c0940f0e56e 100644 --- a/demo/word2vec/api_train_v2.py +++ b/demo/word2vec/api_train_v2.py @@ -70,7 +70,8 @@ def main(): def event_handler(event): if isinstance(event, paddle.event.EndIteration): if event.batch_id % 100 == 0: - with gzip.open("batch-" + str(event.batch_id), 'w') as f: + with gzip.open("batch-" + str(event.batch_id) + ".tar.gz", + 'w') as f: trainer.save_parameter_to_tar(f) result = trainer.test( paddle.batch( -- GitLab From 4d23a942c7da4770b369c346440a6eb9f2b1c456 Mon Sep 17 00:00:00 2001 From: dangqingqing Date: Mon, 24 Apr 2017 16:58:55 +0800 Subject: [PATCH 0114/3256] fix code format --- paddle/gserver/layers/CostLayer.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/paddle/gserver/layers/CostLayer.h b/paddle/gserver/layers/CostLayer.h index 856d1012adc..14c0b33ec1a 100644 --- a/paddle/gserver/layers/CostLayer.h +++ b/paddle/gserver/layers/CostLayer.h @@ -91,8 +91,8 @@ public: * * [1] Jacob Devlin, Rabih Zbib, Zhongqiang Huang, Thomas Lamar, * Richard Schwartz, and John Makhoul. Fast and robust neural - * network joint models for statistical machine translation. * In - * Proceedings of the ACL 2014 Conference. + * network joint models for statistical machine translation. + * In Proceedings of the ACL 2014 Conference. */ class MultiClassCrossEntropyWithSelfNorm : public CostLayer { public: -- GitLab From 6dd90f47c3c290199f659c9e0531ef556a9f5a07 Mon Sep 17 00:00:00 2001 From: dangqingqing Date: Tue, 25 Apr 2017 10:25:21 +0800 Subject: [PATCH 0115/3256] Change the size in test_LayerGrad and pass unit test --- paddle/gserver/tests/test_LayerGrad.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/paddle/gserver/tests/test_LayerGrad.cpp b/paddle/gserver/tests/test_LayerGrad.cpp index 1bc5256b67c..e1e8e7fae7c 100644 --- a/paddle/gserver/tests/test_LayerGrad.cpp +++ b/paddle/gserver/tests/test_LayerGrad.cpp @@ -1679,8 +1679,8 @@ TEST(Layer, smooth_l1) { TestConfig config; config.layerConfig.set_type("smooth_l1"); - config.inputDefs.push_back({INPUT_DATA, "layer_0", 1, 0}); - config.inputDefs.push_back({INPUT_DATA_TARGET, "layer_1", 1, 0}); + config.inputDefs.push_back({INPUT_DATA, "layer_0", 200, 0}); + config.inputDefs.push_back({INPUT_DATA_TARGET, "layer_1", 200, 0}); config.layerConfig.add_inputs(); config.layerConfig.add_inputs(); -- GitLab From 2838491235c1eb85262f41ae45f2dee5c5fe2d72 Mon Sep 17 00:00:00 2001 From: dangqingqing Date: Tue, 25 Apr 2017 11:04:32 +0800 Subject: [PATCH 0116/3256] add smooth_l1 interface to v2 doc. --- doc/api/v2/config/layer.rst | 5 +++++ python/paddle/trainer_config_helpers/layers.py | 8 ++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/doc/api/v2/config/layer.rst b/doc/api/v2/config/layer.rst index 2a02baf17ba..4e3589ebc47 100644 --- a/doc/api/v2/config/layer.rst +++ b/doc/api/v2/config/layer.rst @@ -419,6 +419,11 @@ hsigmoid .. autoclass:: paddle.v2.layer.hsigmoid :noindex: +smooth_l1 +--------- +.. automodule:: paddle.v2.layer.smooth_l1 + :noindex: + Check Layer ============ diff --git a/python/paddle/trainer_config_helpers/layers.py b/python/paddle/trainer_config_helpers/layers.py index 56fca13d372..1796e48f09a 100755 --- a/python/paddle/trainer_config_helpers/layers.py +++ b/python/paddle/trainer_config_helpers/layers.py @@ -116,7 +116,7 @@ __all__ = [ 'spp_layer', 'pad_layer', 'eos_layer', - 'smooth_l1_cost', + 'smooth_l1', 'layer_support', ] @@ -5283,7 +5283,7 @@ def multi_binary_label_cross_entropy(input, @wrap_name_default() @layer_support() -def smooth_l1_cost(input, label, name=None, layer_attr=None): +def smooth_l1(input, label, name=None, layer_attr=None): """ This is a L1 loss but more smooth. It requires that the size of input and label are equal. The formula is as follows, @@ -5307,8 +5307,8 @@ def smooth_l1_cost(input, label, name=None, layer_attr=None): .. code-block:: python - cost = smooth_l1_cost(input=input_layer, - label=label_layer) + cost = smooth_l1(input=input_layer, + label=label_layer) :param input: The input layer. :type input: LayerOutput -- GitLab From f9a5a2c09969e4b8a8973a9bf250090a242998dd Mon Sep 17 00:00:00 2001 From: qijun Date: Tue, 25 Apr 2017 11:21:13 +0800 Subject: [PATCH 0117/3256] follow comments --- doc/howto/dev/write_docs_cn.rst | 10 +++--- paddle/scripts/docker/build.sh | 6 ++++ paddle/scripts/tools/build_docs/build_docs.sh | 36 ++++++++++++++++++- 3 files changed, 46 insertions(+), 6 deletions(-) diff --git a/doc/howto/dev/write_docs_cn.rst b/doc/howto/dev/write_docs_cn.rst index dfe6219d8ee..c8dd3d507f2 100644 --- a/doc/howto/dev/write_docs_cn.rst +++ b/doc/howto/dev/write_docs_cn.rst @@ -16,7 +16,7 @@ PaddlePaddle的文档构建有直接构建和基于Docker构建两种方式。 使用Docker构建PaddlePaddle的文档,需要在系统里先安装好Docker工具包。Docker安装请参考 `Docker的官网 `_ 。安装好Docker之后可以使用源码目录下的脚本构建文档,即 -.. code-block:: bash +.. code-block:: bash cd TO_YOUR_PADDLE_CLONE_PATH bash paddle/scripts/tools/build_docs.sh @@ -35,7 +35,7 @@ PaddlePaddle的文档构建有直接构建和基于Docker构建两种方式。 因为PaddlePaddle的v2 api文档生成过程依赖于py_paddle Python包,用户需要首先确认py_paddle包已经安装。 -.. code-block:: bash +.. code-block:: bash python -c "import py_paddle" @@ -44,7 +44,7 @@ PaddlePaddle的文档构建有直接构建和基于Docker构建两种方式。 如果提示正确,可以执行以下命令编译生成文档,即 -.. code-block:: bash +.. code-block:: bash cd TO_YOUR_PADDLE_CLONE_PATH mkdir build_doc @@ -73,5 +73,5 @@ PaddlePaddle文档使用 `sphinx`_ 自动生成,用户可以参考sphinx教程 -.. _cmake: https://cmake.org/ -.. _sphinx: http://www.sphinx-doc.org/en/1.4.8/ +.. _cmake: https://cmake.org/ +.. _sphinx: http://www.sphinx-doc.org/en/1.4.8/ diff --git a/paddle/scripts/docker/build.sh b/paddle/scripts/docker/build.sh index 73f885c1e70..6fce3b73dcd 100644 --- a/paddle/scripts/docker/build.sh +++ b/paddle/scripts/docker/build.sh @@ -57,7 +57,13 @@ if [ ${WITH_DOC} == "ON" ]; then -DWITH_SWIG_PY=ON \ -DWITH_STYLE_CHECK=OFF make paddle_docs paddle_docs_cn + $DOC_DIR=/paddle/paddle/scripts/tools/build_docs/ + mkdir -p $DOC_DIR/doc + mkdir -p $DOC_DIR/doc_cn + cp -r /paddle/build_doc/doc/doc/en/html/* $DOC_DIR/doc + cp -r /paddle/build_doc/doc/cn/html/* $DOC_DIR/doc_cn popd + rm -rf /paddle/build_doc fi # generate deb package for current build # FIXME(typhoonzero): should we remove paddle/scripts/deb ? diff --git a/paddle/scripts/tools/build_docs/build_docs.sh b/paddle/scripts/tools/build_docs/build_docs.sh index fb8c26a69ad..61db773d88b 100755 --- a/paddle/scripts/tools/build_docs/build_docs.sh +++ b/paddle/scripts/tools/build_docs/build_docs.sh @@ -1,3 +1,37 @@ #!/bin/bash set -e -docker run --rm -v $PWD:/paddle -e "WITH_GPU=OFF" -e "WITH_AVX=ON" -e "WITH_DOC=ON" paddledev/paddle:dev +function usage(){ + echo "usage: build_doc [--help] []" + echo "This script generates doc and doc_cn in the script's directory." + echo "These are common commands used in various situations:" + echo " with_docker build doc and doc_cn with docker" + echo " local build doc and doc_cn locally" +} + + +MYDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +PADDLE_SOURCE_DIR=$MYDIR/../../../../ +case "$1" in + "with_docker") + docker run --rm -v $PADDLE_SOURCE_DIR:/paddle \ + -e "WITH_GPU=OFF" -e "WITH_AVX=ON" -e "WITH_DOC=ON" paddledev/paddle:dev + ;; + "local") + mkdir -p $MYDIR/doc + mkdir -p $MYDIR/doc_cn + mkdir -p $PADDLE_SOURCE_DIR/build_doc + pushd $PADDLE_SOURCE_DIR/build_doc + cmake .. -DWITH_DOC=ON + make paddle_docs paddle_docs_cn + cp -r $PADDLE_SOURCE_DIR/build_doc/doc/en/html/* $MYDIR/doc + cp -r $PADDLE_SOURCE_DIR/build_doc/doc/cn/html/* $MYDIR/doc_cn + popd + rm -rf $PADDLE_SOURCE_DIR/build_doc + ;; + "--help") + usage + ;; + *) + usage + ;; +esac -- GitLab From 63e4e16ced2ad8ccf150fd7f3930d7afdb2ce2c4 Mon Sep 17 00:00:00 2001 From: dangqingqing Date: Tue, 25 Apr 2017 13:50:25 +0800 Subject: [PATCH 0118/3256] add smooth_l1 interface to v2 doc. --- doc/api/v1/trainer_config_helpers/layers.rst | 6 +++--- doc/api/v2/config/layer.rst | 2 +- python/paddle/trainer_config_helpers/layers.py | 14 +++++--------- 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/doc/api/v1/trainer_config_helpers/layers.rst b/doc/api/v1/trainer_config_helpers/layers.rst index 9dec89063ae..75c1b352464 100644 --- a/doc/api/v1/trainer_config_helpers/layers.rst +++ b/doc/api/v1/trainer_config_helpers/layers.rst @@ -498,10 +498,10 @@ hsigmoid :members: hsigmoid :noindex: -smooth_l1 ---------- +smooth_l1_cost +-------------- .. automodule:: paddle.trainer_config_helpers.layers - :members: smooth_l1 + :members: smooth_l1_cost :noindex: Check Layer diff --git a/doc/api/v2/config/layer.rst b/doc/api/v2/config/layer.rst index 4e3589ebc47..0ade7990193 100644 --- a/doc/api/v2/config/layer.rst +++ b/doc/api/v2/config/layer.rst @@ -421,7 +421,7 @@ hsigmoid smooth_l1 --------- -.. automodule:: paddle.v2.layer.smooth_l1 +.. autoclass:: paddle.v2.layer.smooth_l1_cost :noindex: Check Layer diff --git a/python/paddle/trainer_config_helpers/layers.py b/python/paddle/trainer_config_helpers/layers.py index 1796e48f09a..b9e3d264042 100755 --- a/python/paddle/trainer_config_helpers/layers.py +++ b/python/paddle/trainer_config_helpers/layers.py @@ -116,7 +116,7 @@ __all__ = [ 'spp_layer', 'pad_layer', 'eos_layer', - 'smooth_l1', + 'smooth_l1_cost', 'layer_support', ] @@ -5283,7 +5283,7 @@ def multi_binary_label_cross_entropy(input, @wrap_name_default() @layer_support() -def smooth_l1(input, label, name=None, layer_attr=None): +def smooth_l1_cost(input, label, name=None, layer_attr=None): """ This is a L1 loss but more smooth. It requires that the size of input and label are equal. The formula is as follows, @@ -5296,19 +5296,15 @@ def smooth_l1(input, label, name=None, layer_attr=None): .. math:: - mooth_{L1}(x) = - \begin{cases} - 0.5x^2& \text{if} |x| < 1 \\ - |x|-0.5& \text{otherwise} - \end{cases} + smooth_{L1}(x) = \\begin{cases} 0.5x^2& \\text{if} \\ |x| < 1 \\\\ |x|-0.5& \\text{otherwise} \end{cases} More details can be found by referring to `Fast R-CNN `_ .. code-block:: python - cost = smooth_l1(input=input_layer, - label=label_layer) + cost = smooth_l1_cost(input=input_layer, + label=label_layer) :param input: The input layer. :type input: LayerOutput -- GitLab From b4e459b99c17543526f099c81badfdc0fdc8ef42 Mon Sep 17 00:00:00 2001 From: qijun Date: Tue, 25 Apr 2017 13:57:21 +0800 Subject: [PATCH 0119/3256] update wirte_docs_cn.rst --- doc/howto/dev/write_docs_cn.rst | 26 +++++++++---------- paddle/scripts/docker/build.sh | 4 +-- paddle/scripts/tools/build_docs/build_docs.sh | 13 +++++----- 3 files changed, 21 insertions(+), 22 deletions(-) diff --git a/doc/howto/dev/write_docs_cn.rst b/doc/howto/dev/write_docs_cn.rst index c8dd3d507f2..0b93f3fa6a6 100644 --- a/doc/howto/dev/write_docs_cn.rst +++ b/doc/howto/dev/write_docs_cn.rst @@ -2,13 +2,14 @@ 如何贡献/修改文档 ################## -PaddlePaddle的文档包括英文文档 ``doc`` 和中文文档 ``doc_cn`` 两个部分。文档都是通过 `cmake`_ 驱动 `sphinx`_ 编译生成,生成后的文档分别存储在编译目录的 ``en`` 和 ``cn`` 两个子目录下。 +PaddlePaddle的文档包括英文文档 ``doc`` 和中文文档 ``doc_cn`` 两个部分。文档都是通过 `cmake`_ 驱动 `sphinx`_ 编译生成,生成后的文档分别存储在编译目录的 ``doc`` 和 ``doc_cn`` 两个子目录下。 如何构建PaddlePaddle的文档 ========================== -PaddlePaddle的文档构建有直接构建和基于Docker构建两种方式。构建PaddlePaddle文档需要准备的环境相对较复杂,所以我们推荐使用基于Docker来构建PaddlePaddle的文档。 +PaddlePaddle的文档构建有直接构建和基于Docker构建两种方式,我们提供了一个构建脚本build_docs.sh来进行构建。 +PaddlePaddle文档需要准备的环境相对较复杂,所以我们推荐使用基于Docker来构建PaddlePaddle的文档。 使用Docker构建PaddlePaddle的文档 @@ -19,12 +20,13 @@ PaddlePaddle的文档构建有直接构建和基于Docker构建两种方式。 .. code-block:: bash cd TO_YOUR_PADDLE_CLONE_PATH - bash paddle/scripts/tools/build_docs.sh + cd paddle/scripts/tools/build_docs + bash build_docs.sh with_docker -编译完成后,会在当前目录生成两个子目录,build与build_doc,其中build_doc/doc目录之下包含两个子目录\: +编译完成后,会在当前目录生成两个子目录\: -* en 英文文档目录 -* cn 中文文档目录 +* doc 英文文档目录 +* doc_cn 中文文档目录 打开浏览器访问对应目录下的index.html即可访问本地文档。 @@ -47,15 +49,13 @@ PaddlePaddle的文档构建有直接构建和基于Docker构建两种方式。 .. code-block:: bash cd TO_YOUR_PADDLE_CLONE_PATH - mkdir build_doc - cd build_doc - cmake .. -DWITH_DOC=ON - make paddle_docs paddle_docs_cn -j `nproc` + cd paddle/scripts/tools/build_docs + bash build_docs.sh local -编译完成之后,在build_doc/doc目录之下会生成如下两个子目录\: +编译完成之后,会在当前目录生成两个子目录\: -* en 英文文档目录 -* cn 中文文档目录 +* doc 英文文档目录 +* doc_cn 中文文档目录 打开浏览器访问对应目录下的index.html即可访问本地文档。 diff --git a/paddle/scripts/docker/build.sh b/paddle/scripts/docker/build.sh index 6fce3b73dcd..959cdd14f88 100644 --- a/paddle/scripts/docker/build.sh +++ b/paddle/scripts/docker/build.sh @@ -57,10 +57,10 @@ if [ ${WITH_DOC} == "ON" ]; then -DWITH_SWIG_PY=ON \ -DWITH_STYLE_CHECK=OFF make paddle_docs paddle_docs_cn - $DOC_DIR=/paddle/paddle/scripts/tools/build_docs/ + DOC_DIR="/paddle/paddle/scripts/tools/build_docs/" mkdir -p $DOC_DIR/doc mkdir -p $DOC_DIR/doc_cn - cp -r /paddle/build_doc/doc/doc/en/html/* $DOC_DIR/doc + cp -r /paddle/build_doc/doc/en/html/* $DOC_DIR/doc cp -r /paddle/build_doc/doc/cn/html/* $DOC_DIR/doc_cn popd rm -rf /paddle/build_doc diff --git a/paddle/scripts/tools/build_docs/build_docs.sh b/paddle/scripts/tools/build_docs/build_docs.sh index 61db773d88b..99b927b865c 100755 --- a/paddle/scripts/tools/build_docs/build_docs.sh +++ b/paddle/scripts/tools/build_docs/build_docs.sh @@ -9,22 +9,21 @@ function usage(){ } -MYDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" -PADDLE_SOURCE_DIR=$MYDIR/../../../../ case "$1" in "with_docker") - docker run --rm -v $PADDLE_SOURCE_DIR:/paddle \ + docker run --rm -v $PWD/../../../../:/paddle \ -e "WITH_GPU=OFF" -e "WITH_AVX=ON" -e "WITH_DOC=ON" paddledev/paddle:dev ;; "local") - mkdir -p $MYDIR/doc - mkdir -p $MYDIR/doc_cn + mkdir -p doc + mkdir -p doc_cn + PADDLE_SOURCE_DIR=$PWD/../../../../ mkdir -p $PADDLE_SOURCE_DIR/build_doc pushd $PADDLE_SOURCE_DIR/build_doc cmake .. -DWITH_DOC=ON make paddle_docs paddle_docs_cn - cp -r $PADDLE_SOURCE_DIR/build_doc/doc/en/html/* $MYDIR/doc - cp -r $PADDLE_SOURCE_DIR/build_doc/doc/cn/html/* $MYDIR/doc_cn + cp -r $PADDLE_SOURCE_DIR/build_doc/doc/en/html/* doc + cp -r $PADDLE_SOURCE_DIR/build_doc/doc/cn/html/* doc_cn popd rm -rf $PADDLE_SOURCE_DIR/build_doc ;; -- GitLab From 8b997df35e009b6c8d9d14c82b042c01afbc87ba Mon Sep 17 00:00:00 2001 From: dangqingqing Date: Tue, 25 Apr 2017 13:57:03 +0800 Subject: [PATCH 0120/3256] rename --- doc/api/v2/config/layer.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/api/v2/config/layer.rst b/doc/api/v2/config/layer.rst index 0ade7990193..154cfe24432 100644 --- a/doc/api/v2/config/layer.rst +++ b/doc/api/v2/config/layer.rst @@ -419,8 +419,8 @@ hsigmoid .. autoclass:: paddle.v2.layer.hsigmoid :noindex: -smooth_l1 ---------- +smooth_l1_cost +-------------- .. autoclass:: paddle.v2.layer.smooth_l1_cost :noindex: -- GitLab From fe7825bf9bce1688efeb326c79f409d8c7283c66 Mon Sep 17 00:00:00 2001 From: hedaoyuan Date: Tue, 25 Apr 2017 13:59:39 +0800 Subject: [PATCH 0121/3256] follow comments --- doc/howto/raspberry/build_for_raspberry.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/howto/raspberry/build_for_raspberry.md b/doc/howto/raspberry/build_for_raspberry.md index d6888e47ec6..7e843a659f8 100644 --- a/doc/howto/raspberry/build_for_raspberry.md +++ b/doc/howto/raspberry/build_for_raspberry.md @@ -1,6 +1,6 @@ -# 如何构建raspberry pi下运行的paddle +# 如何构建Raspberry pi下运行的PaddlePaddle -这里考虑的是交叉编译方式,即在Linux-X86环境下构建raspberry pi下可运行的paddle。 +这里考虑的是交叉编译方式,即在Linux-x86环境下构建Raspberry pi下可运行的PaddlePaddle。 ## 下载交叉编译环境 ``` @@ -10,9 +10,9 @@ git clone https://github.com/raspberrypi/tools ## 编译第三方库 -Paddle在cmake会在编译paddle的时候自动编译依赖的第三方库,不过openblas和protobuf最好还是在编译paddle之前先编译好,这样可以保证编译paddle的时候更加顺畅。 +cmake编译PaddlePaddle时候会自动下载编译依赖的第三方库,不过openblas和protobuf最好还是在编译PaddlePaddle之前先编译好,这样可以保证编译PaddlePaddle的时候更加顺畅。 -### 编译openblas +### 编译OpenBLAS ``` git clone https://github.com/xianyi/OpenBLAS.git make TARGET=ARMV7 HOSTCC=gcc CC=arm-linux-gnueabihf-gcc NOFORTRAN=1 USE_THREAD=0 -- GitLab From 9e11c20f91117c57a20cdbc49587c8229c5e7274 Mon Sep 17 00:00:00 2001 From: qijun Date: Tue, 25 Apr 2017 14:55:36 +0800 Subject: [PATCH 0122/3256] fix bug --- paddle/scripts/tools/build_docs/build_docs.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paddle/scripts/tools/build_docs/build_docs.sh b/paddle/scripts/tools/build_docs/build_docs.sh index 99b927b865c..00123dcb87d 100755 --- a/paddle/scripts/tools/build_docs/build_docs.sh +++ b/paddle/scripts/tools/build_docs/build_docs.sh @@ -22,9 +22,9 @@ case "$1" in pushd $PADDLE_SOURCE_DIR/build_doc cmake .. -DWITH_DOC=ON make paddle_docs paddle_docs_cn + popd cp -r $PADDLE_SOURCE_DIR/build_doc/doc/en/html/* doc cp -r $PADDLE_SOURCE_DIR/build_doc/doc/cn/html/* doc_cn - popd rm -rf $PADDLE_SOURCE_DIR/build_doc ;; "--help") -- GitLab From c0d0bc4b6a6393e5acdbd79b25b5651cd2d93d4e Mon Sep 17 00:00:00 2001 From: Luo Tao Date: Tue, 25 Apr 2017 15:00:33 +0800 Subject: [PATCH 0123/3256] refine the level of title in k8s_basis_cn.md --- doc/howto/usage/k8s/k8s_basis_cn.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/howto/usage/k8s/k8s_basis_cn.md b/doc/howto/usage/k8s/k8s_basis_cn.md index 6278dacb17a..4c3dc81ed38 100644 --- a/doc/howto/usage/k8s/k8s_basis_cn.md +++ b/doc/howto/usage/k8s/k8s_basis_cn.md @@ -14,7 +14,7 @@ - [*PersistentVolume*](https://kubernetes.io/docs/user-guide/persistent-volumes/): 和[*PersistentVolumeClaim*](https://kubernetes.io/docs/user-guide/persistent-volumes/#persistentvolumeclaims)结合,将外部的存储服务在Kubernetes中描述成为统一的资源形式,便于存储资源管理和Pod引用。 -# 部署Kubernetes集群 +## 部署Kubernetes集群 Kubernetes提供了多种集群部署的方案,本文档内不重复介绍。这里给出集中常见的部署方法: @@ -25,7 +25,7 @@ Kubernetes提供了多种集群部署的方案,本文档内不重复介绍。 可以参考[这个表格](https://kubernetes.io/docs/getting-started-guides/#table-of-solutions)选择适合您的场景的合适方案。 -# 选择存储方案 +## 选择存储方案 容器不会保留在运行时生成的数据,job或者应用程序在容器中运行时生成的数据会在容器销毁时消失。为了完成分布式机器学习训练任务,需要有一个外部的存储服务来保存训练所需数据和训练输出。 常见的可选存储服务包括: @@ -35,9 +35,9 @@ Kubernetes提供了多种集群部署的方案,本文档内不重复介绍。 - [*Ceph*](http://docs.ceph.com/docs/master/): 分布式文件系统,支持rbd,POSIX API接口(ceph fs)和对象存储API,参考[这里](https://kubernetes.io/docs/user-guide/volumes/#rbd)。 - [*MooseFS*](https://moosefs.com/documentation.html): 一个分布式的存储系统。需要先挂载到服务器Node上再通过kubernetes hostPath Volume挂载到容器中。 -# 配置kubectl +## 配置kubectl -## 安装kubectl +### 安装kubectl ``` # OS X curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/darwin/amd64/kubectl @@ -49,7 +49,7 @@ curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s htt curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/windows/amd64/kubectl.exe ``` -## 配置kubectl访问你的kubernetes集群 +### 配置kubectl访问你的kubernetes集群 编辑`~/.kube/config`这个配置文件,修改`Master-IP`的地址。如果使用SSL认证,则需要配置`certificate-authority`和`users`中的用户证书。如果是使用非SSL方式访问(比如通过8080端口),也可以去掉这些证书的配置。 ``` -- GitLab From 99e241ba17a90ed833ce4e3f61829d82983be482 Mon Sep 17 00:00:00 2001 From: qijun Date: Tue, 25 Apr 2017 15:21:38 +0800 Subject: [PATCH 0124/3256] update doc --- doc/howto/dev/write_docs_cn.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/howto/dev/write_docs_cn.rst b/doc/howto/dev/write_docs_cn.rst index 0b93f3fa6a6..d536f53abc0 100644 --- a/doc/howto/dev/write_docs_cn.rst +++ b/doc/howto/dev/write_docs_cn.rst @@ -68,6 +68,7 @@ PaddlePaddle文档使用 `sphinx`_ 自动生成,用户可以参考sphinx教程 如何更新www.paddlepaddle.org文档 ================================ +开发者给PaddlePaddle代码增加的注释以PR的形式提交到github中,提交方式可参见 `贡献文档 `_ 。 目前PaddlePaddle的develop分支的文档是自动触发更新的,用户可以分别查看最新的 `中文文档 `_ 和 `英文文档 `_ 。 -- GitLab From cea2ce509204b62f4ad1de55de0ecf0e21d6ad06 Mon Sep 17 00:00:00 2001 From: livc Date: Tue, 25 Apr 2017 17:39:53 +0800 Subject: [PATCH 0125/3256] pass pre-commit --- doc/howto/dev/contribute_to_paddle_cn.md | 222 +++++++++++++---------- 1 file changed, 131 insertions(+), 91 deletions(-) diff --git a/doc/howto/dev/contribute_to_paddle_cn.md b/doc/howto/dev/contribute_to_paddle_cn.md index ee1b3213eae..87f214e6be9 100644 --- a/doc/howto/dev/contribute_to_paddle_cn.md +++ b/doc/howto/dev/contribute_to_paddle_cn.md @@ -1,130 +1,170 @@ -# 如何贡献代码 - -我们真诚地感谢您的贡献,欢迎通过 GitHub 的 fork 和 pull request 流程来提交代码。 - -## 代码要求 -- 你的代码必须完全遵守 [doxygen](http://www.stack.nl/~dimitri/doxygen/) 的样式。 -- 确保编译器选项 WITH\_STYLE\_CHECK 已打开,并且编译能通过代码样式检查。 -- 所有代码必须具有单元测试。 -- 通过所有单元测试。 - -以下教程将指导您提交代码。 - -## [Fork](https://help.github.com/articles/fork-a-repo/) - -跳转到[PaddlePaddle](https://github.com/PaddlePaddle/Paddle) GitHub首页,然后单击 `Fork` 按钮。 +# Git 开发指南 + +这个指南将完成一次完整的代码贡献流程。 + +## Fork + +首先 Fork ,生成自己目录下的仓库,比如 。 ## 克隆(Clone) -Paddle 目前使用[git流分支模型](http://nvie.com/posts/a-successful-git-branching-model/)进行开发,测试,发行和维护。 -**develop** 是主分支,其他用户分支是特征分支(feature branches)。 -一旦你创建了一个fork,你可以使用你最喜欢的 git 客户端克隆你的仓库(repo)或只是直接在命令行输入: -```shell -# 克隆 fork 到本地 -git clone --branch develop https://github.com/USERNAME/Paddle.git -``` -如果你的仓库不包含 **develop** 分支,你只需自己创建它。 - -```shell -git clone https://github.com/USERNAME/Paddle.git Paddle -cd Paddle -git checkout -b develop # 创建 develop 分支 -git remote add upstream https://github.com/PaddlePaddle/Paddle.git # 添加 upstream 到 baidu/Paddle -git pull upstream develop # 更新 upstream +## 创建本地分支 + +所有的 feature 和 bug_fix 的开发工作都应该在一个新的分支上完成,一般从 `develop` 分支上创建新分支。 + +```bash +# (从当前分支)创建名为 MY_COOL_STUFF_BRANCH 的新分支 +➜ git branch MY_COOL_STUFF_BRANCH + +# 切换到这个分支上 +➜ git checkout MY_COOL_STUFF_BRANCH ``` -然后你可以通过做一个本地开发分支开始开发 +也可以通过 `git checkout -b` 一次性创建并切换分支。 -```shell -git checkout -b MY_COOL_STUFF_BRANCH +```bash +➜ git checkout -b MY_COOL_STUFF_BRANCH ``` -## 使用 `pre-commit` 钩子 +值得注意的是,在 checkout 之前,需要保持当前分支目录 clean,否则会把 untracked 的文件也带到新分支上,这可以通过 `git status` 查看。 + +## 开始开发 -Paddle 开发人员使用 [pre-commit](http://pre-commit.com/) 工具来管理git预提交钩子。 它可以帮助我们格式化源代码(cpp,python),在提交前检查一些基本事宜(每个文件只有一个 EOL -,git 中不要添加大文件)。 `pre-commit`测试是 Travis-CI 中单元测试的一部分,不满足钩子 -的 PR 不能提交代码到 Paddle。 +在本例中,我删除了 README.md 中的一行,并创建了一个新文件。 -你可以通过 `pip install pre-commit` 安装 [pre-commit](http://pre-commit.com/), -目前 Paddle 使用 `clang-format` 来调整C/C++源代码格式。请确保 clang-format 版本在3.8以上。 +通过 `git status` 查看当前状态,这会提示当前目录的一些变化,同时也可以通过 `git diff` 查看文件具体被修改的内容。 -然后只需在 Paddle clone 目录中运行 `pre-commit install` 。当你 -提交你的代码时,pre-commit 钩子会检查本地代码是否存在 -不适合提交的东西,等等。 +```bash +➜ git status +On branch test +Changes not staged for commit: + (use "git add ..." to update what will be committed) + (use "git checkout -- ..." to discard changes in working directory) -## 提交(Commit) + modified: README.md -提交你的代码: +Untracked files: + (use "git add ..." to include in what will be committed) -```shell -# 显示工作树状态 -git status -# 添加修改过的文件 -git add xx -env EDITOR=vim git commit # 你可以用 vim/nano/emacs 写下你的注释 + test + +no changes added to commit (use "git add" and/or "git commit -a") ``` -提交信息的第一行是标题,其他行可以添加一些细节(如果有必要的话)。 -## 保持 Fork 状态最新 +## 提交(commit) + +接下来我们取消对 README.md 文件的改变,然后提交新添加的 test 文件。 -在拉(pull)你的请求(request)之前,你应该从最新的 PaddlePaddle 同步代码。 -为此,你需要首先添加远程(remote): +```bash +➜ git checkout -- README.md +➜ git status +On branch test +Untracked files: + (use "git add ..." to include in what will be committed) -```shell -# 观察当前远程仓库配置 -git remote -v -# 添加上游(upstream)仓库 -git remote add upstream https://github.com/PaddlePaddle/Paddle.git -# 验证新的 upstream -git remote -v + test + +nothing added to commit but untracked files present (use "git add" to track) +➜ git add test ``` -用最新的 upstream 更新你的 fork: +Paddle 使用 [pre-commit](http://pre-commit.com) 完成代码风格检查的自动化,它会在每次 commit 时自动检查代码是否符合规范,并检查一些基本事宜,因此我们首先安装并在当前目录运行它。 -```shell -git pull --rebase upstream develop +```bash +➜ pip install pre-commit +➜ pre-commit install ``` -如果本地没有提交,git 将简单地执行快进。但是,如果你一直在做一些改变(绝大多数情况下不应该),你可能要处理冲突。 -现在,你的本地主分支与上游修改的一致并是最新的。 +Git 每次提交代码,都需要写提交说明,这可以让其他人知道这次提交做了哪些改变,这可以通过`git commit -m` 完成。 + +```bash +➜ git commit -m "add test file" +CRLF end-lines remover...............................(no files to check)Skipped +yapf.................................................(no files to check)Skipped +Check for added large files..............................................Passed +Check for merge conflicts................................................Passed +Check for broken symlinks................................................Passed +Detect Private Key...................................(no files to check)Skipped +Fix End of Files.....................................(no files to check)Skipped +clang-formater.......................................(no files to check)Skipped +[MY_COOL_STUFF_BRANCH c703c041] add test file + 1 file changed, 0 insertions(+), 0 deletions(-) + create mode 100644 233 +``` + +## 保持本地仓库最新 + +在准备发起 Pull Request 之前,需要同步原仓库()最新的代码。 + +首先通过 `git remote` 查看当前远程仓库的名字。 -## 推送(Push)到 GitHub +```bash +➜ git remote +origin +➜ git remote -v +origin https://github.com/USERNAME/Paddle (fetch) +origin https://github.com/USERNAME/Paddle (push) +``` + +这里 origin 是我们 clone 的远程仓库的名字,也就是自己用户名下的 Paddle,接下来我们创建一个原始 Paddle 仓库的远程主机,命名为 upstream。 -```shell -# 在 GitHub 上 push 你的仓库 -git push -u origin MY_COOL_STUFF_BRANCH # 创建远程分支 MY_COOL_STUFF_BRANCH 到 origin. +```bash +➜ git remote add upstream https://github.com/PaddlePaddle/Paddle +➜ git remote +origin +upstream ``` -## 拉取请求(Pull Request) +获取 upstream 的最新代码并更新当前分支。 -转到 GitHub上 你 fork 的页面,选择你的开发分支并单击 **pull request 按钮**。 +```bash +➜ git fetch upstream +➜ git pull --rebase upstream develop +``` -## 使用最新版本更新你的 pull 请求 +## Push 到远程仓库 -在代码审查(code review)期间,由于 baidu/Paddle 中新的提交导致你的 pull 请求可能会失效。如果没有冲突,GitHub允许自动更新。 你可以点击 pull request 页面中的“更新分支(Update Branch)”按钮。 但是如果存在代码冲突,你需要手动进行更新。你需要在本地仓库执行如下命令: +将本地的修改推送到 GitHub 上,也就是 https://github.com/USERNAME/Paddle。 -```shell -git checkout MY_COOL_STUFF_BRANCH -git pull upstream develop -# 你可能需要根据git提示解决冲突 -# 创建并测试你的代码 -git push origin MY_COOL_STUFF_BRANCH +```bash +# 推送到远程仓库 origin 的 MY_COOL_STUFF_BRANCH 分支上 +➜ git push origin MY_COOL_STUFF_BRANCH ``` -现在你的 Pull Request 是最新的了。 -## 修改你的 pull request +## 建立 Issue 并完成 PR + +建立一个 Issue 描述问题,记录它的编号。 + +在 Push 新分支后, https://github.com/USERNAME/Paddle 中会出现新分支提示,点击绿色按钮发起 PR。 + +![](https://ws1.sinaimg.cn/large/9cd77f2egy1fez1jq9mwdj21js04yq3m.jpg) + +选择目标分支: + +![](https://ws1.sinaimg.cn/large/9cd77f2egy1fez1ku4a5vj21am04st9l.jpg) -当根据审阅者的意见修改 pull 请求时,请使用“git commit”而不是“git commit --amend”来提交更改,以便审阅者可以看到新的请求和旧的请求之间的区别。 +在 PR 的说明中,填写 `solve #Issue编号` 可以在这个 PR 被 merge 后,自动关闭对应的 Issue,具体请见 。 -可能的命令是 +接下来等待 review,如果有需要修改的地方,参照上述步骤更新 origin 中的对应分支即可。 -```shell -git checkout MY_COOL_STUFF_BRANCH -git pull upstream develop # 将本地更新到最新的代码库 -# 可能会发生一些冲突 -# 开始开发吧! -env EDITOR=vim git commit # 添加修改日志 -git push origin MY_COOL_STUFF_BRANCH +## 删除远程分支 + +在 PR 被 merge 进主仓库后,我们可以在 PR 的页面删除远程仓库的分支。 + +![](https://ws1.sinaimg.cn/large/9cd77f2egy1fez1pkqohzj217q05c0tk.jpg) + +## 删除本地分支 + +最后,删除本地分支。 + +```bash +# 切换到 develop 分支 +git checkout develop + +# 删除 MY_COOL_STUFF_BRANCH 分支 +git branch -D MY_COOL_STUFF_BRANCH ``` + +至此,我们就完成了一次代码贡献的过程。 -- GitLab From 744fd7a5d3cfdff636150e64726621fedcf5424a Mon Sep 17 00:00:00 2001 From: livc Date: Tue, 25 Apr 2017 17:50:25 +0800 Subject: [PATCH 0126/3256] update --- doc/howto/dev/contribute_to_paddle_cn.md | 25 +++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/doc/howto/dev/contribute_to_paddle_cn.md b/doc/howto/dev/contribute_to_paddle_cn.md index 87f214e6be9..c95d034c989 100644 --- a/doc/howto/dev/contribute_to_paddle_cn.md +++ b/doc/howto/dev/contribute_to_paddle_cn.md @@ -1,18 +1,33 @@ -# Git 开发指南 +# 如何贡献代码 -这个指南将完成一次完整的代码贡献流程。 +我们真诚地感谢您的贡献,欢迎通过 GitHub 的 fork 和 pull request 流程来提交代码。 -## Fork +## 代码要求 +- 你的代码必须完全遵守 [doxygen](http://www.stack.nl/~dimitri/doxygen/) 的样式。 +- 确保编译器选项 WITH\_STYLE\_CHECK 已打开,并且编译能通过代码样式检查。 +- 所有代码必须具有单元测试。 +- 通过所有单元测试。 -首先 Fork ,生成自己目录下的仓库,比如 。 +以下教程将指导您提交代码。 +## [Fork](https://help.github.com/articles/fork-a-repo/) + +跳转到[PaddlePaddle](https://github.com/PaddlePaddle/Paddle) GitHub首页,然后单击 `Fork` 按钮,生成自己目录下的仓库,比如 。 ## 克隆(Clone) +将远程仓库 clone 到本地。 + +```bash +➜ git clone https://github.com/USERNAME/Paddle +➜ cd Paddle +``` ## 创建本地分支 -所有的 feature 和 bug_fix 的开发工作都应该在一个新的分支上完成,一般从 `develop` 分支上创建新分支。 +Paddle 目前使用[Git流分支模型](http://nvie.com/posts/a-successful-git-branching-model/)进行开发,测试,发行和维护。**develop** 是主分支,其他用户分支是特征分支(feature branches)。 + +所有的 feature 和 bug fix 的开发工作都应该在一个新的分支上完成,一般从 `develop` 分支上创建新分支。 ```bash # (从当前分支)创建名为 MY_COOL_STUFF_BRANCH 的新分支 -- GitLab From e4f399048508ec0ec33100e9ec7f5c20ec3c89bf Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Tue, 25 Apr 2017 16:25:24 -0700 Subject: [PATCH 0127/3256] update docker install Chinese version --- .../build_and_install/docker_install_cn.rst | 162 ++++++++++-------- 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/doc/getstarted/build_and_install/docker_install_cn.rst b/doc/getstarted/build_and_install/docker_install_cn.rst index 22db1ef658c..1810dc63485 100644 --- a/doc/getstarted/build_and_install/docker_install_cn.rst +++ b/doc/getstarted/build_and_install/docker_install_cn.rst @@ -7,80 +7,99 @@ PaddlePaddle目前唯一官方支持的运行的方式是Docker容器。因为Do PaddlePaddle发布的docker镜像使用说明 ------------------------------ -对于每一个PaddlePaddle版本,我们都会发布两种Docker镜像:开发镜像、运行镜像。运行镜像包括纯CPU版本和GPU版本以及其对应的非AVX版本。 -我们会在 `dockerhub.com `_ 提供最新的docker镜像,可以在"tags"标签下找到最新的Paddle镜像版本。 +我们把PaddlePaddle的编译环境打包成一个镜像,称为开发镜像,里面涵盖了 +PaddlePaddle需要的所有编译工具。把编译出来的PaddlePaddle也打包成一个镜 +像,称为生产镜像,里面涵盖了PaddlePaddle运行所需的所有环境。每次 +PaddlePaddle发布新版本的时候都会发布对应版本的生产镜像以及开发镜像。运 +行镜像包括纯CPU版本和GPU版本以及其对应的非AVX版本。我们会在 +`dockerhub.com `_ 提供最新 +的docker镜像,可以在"tags"标签下找到最新的Paddle镜像版本。为了方便在国 +内的开发者下载Docker镜像,我们提供了国内的镜像服务器供大家使用。如果您 +在国内,请把文档里命令中的paddlepaddle/paddle替换成 +docker.paddlepaddle.org/paddle。 + 1. 开发镜像::code:`paddlepaddle/paddle:-dev` - 这个镜像包含了Paddle相关的开发工具以及编译和运行环境。用户可以使用开发镜像代替配置本地环境,完成开发,编译,发布, - 文档编写等工作。由于不同的Paddle的版本可能需要不同的依赖和工具,所以如果需要自行配置开发环境需要考虑版本的因素。 - 开发镜像包含了以下工具: - - gcc/clang - - nvcc - - Python - - sphinx - - woboq - - sshd - 很多开发者会使用远程的安装有GPU的服务器工作,用户可以使用ssh登录到这台服务器上并执行 :code:`docker exec`进入开发镜像并开始工作, - 也可以在开发镜像中启动一个SSHD服务,方便开发者直接登录到镜像中进行开发: + 这个镜像包含了Paddle相关的开发工具以及编译和运行环境。用户可以使用开发镜像代替配置本地环境,完成开发,编译,发布, + 文档编写等工作。由于不同的Paddle的版本可能需要不同的依赖和工具,所以如果需要自行配置开发环境需要考虑版本的因素。 + 开发镜像包含了以下工具: + + - gcc/clang + - nvcc + - Python + - sphinx + - woboq + - sshd + 很多开发者会使用远程的安装有GPU的服务器工作,用户可以使用ssh登录到这台服务器上并执行 :code:`docker exec`进入开发镜像并开始工作, + 也可以在开发镜像中启动一个SSHD服务,方便开发者直接登录到镜像中进行开发: + + 以交互容器方式运行开发镜像: + + .. code-block:: bash + + docker run -it --rm paddlepaddle/paddle:-dev /bin/bash + + 或者,可以以后台进程方式运行容器: + + .. code-block:: bash + + docker run -d -p 2202:22 -p 8888:8888 paddledev/paddle:-dev - 以交互容器方式运行开发镜像: + 然后用密码 :code:`root` SSH进入容器: - .. code-block:: bash + .. code-block:: bash - docker run -it --rm paddledev/paddle:-dev /bin/bash + ssh -p 2202 root@localhost - 或者,可以以后台进程方式运行容器: + SSH方式的一个优点是我们可以从多个终端进入容器。比如,一个终端运行vi,另一个终端运行Python。另一个好处是我们可以把PaddlePaddle容器运行在远程服务器上,并在笔记本上通过SSH与其连接。 - .. code-block:: bash +2. 生产镜像:根据CPU、GPU和非AVX区分了如下4个镜像: - docker run -d -p 2202:22 -p 8888:8888 paddledev/paddle:-dev + - GPU/AVX::code:`paddlepaddle/paddle:-gpu` + - GPU/no-AVX::code:`paddlepaddle/paddle:-gpu-noavx` + - CPU/AVX::code:`paddlepaddle/paddle:` + - CPU/no-AVX::code:`paddlepaddle/paddle:-noavx` - 然后用密码 :code:`root` SSH进入容器: + 纯CPU镜像以及GPU镜像都会用到AVX指令集,但是2008年之前生产的旧电脑不支持AVX。以下指令能检查Linux电脑是否支持AVX: - .. code-block:: bash + .. code-block:: bash - ssh -p 2202 root@localhost + if cat /proc/cpuinfo | grep -i avx; then echo Yes; else echo No; fi - SSH方式的一个优点是我们可以从多个终端进入容器。比如,一个终端运行vi,另一个终端运行Python。另一个好处是我们可以把PaddlePaddle容器运行在远程服务器上,并在笔记本上通过SSH与其连接。 + 如果输出是No,就需要选择使用no-AVX的镜像 -2. 运行镜像:根据CPU、GPU和非AVX区分了如下4个镜像: - - GPU/AVX::code:`paddlepaddle/paddle:-gpu` - - GPU/no-AVX::code:`paddlepaddle/paddle:-gpu-noavx` - - CPU/AVX::code:`paddlepaddle/paddle:` - - CPU/no-AVX::code:`paddlepaddle/paddle:-noavx` + 以上方法在GPU镜像里也能用,只是请不要忘记提前在物理机上安装GPU最新驱动。 + 为了保证GPU驱动能够在镜像里面正常运行,我们推荐使用[nvidia-docker](https://github.com/NVIDIA/nvidia-docker)来运行镜像。 - 纯CPU镜像以及GPU镜像都会用到AVX指令集,但是2008年之前生产的旧电脑不支持AVX。以下指令能检查Linux电脑是否支持AVX: + .. code-block:: bash - .. code-block:: bash + nvidia-docker run -it --rm paddledev/paddle:0.10.0rc1-gpu /bin/bash - if cat /proc/cpuinfo | grep -i avx; then echo Yes; else echo No; fi + 注意: 如果使用nvidia-docker存在问题,你也许可以尝试更老的方法,具体如下,但是我们并不推荐这种方法。: - 如果输出是No,就需要选择使用no-AVX的镜像 + .. code-block:: bash - 以上方法在GPU镜像里也能用,只是请不要忘记提前在物理机上安装GPU最新驱动。 - 为了保证GPU驱动能够在镜像里面正常运行,我们推荐使用[nvidia-docker](https://github.com/NVIDIA/nvidia-docker)来运行镜像。 + export CUDA_SO="$(\ls /usr/lib64/libcuda* | xargs -I{} echo '-v {}:{}') $(\ls /usr/lib64/libnvidia* | xargs -I{} echo '-v {}:{}')" + export DEVICES=$(\ls /dev/nvidia* | xargs -I{} echo '--device {}:{}') + docker run ${CUDA_SO} ${DEVICES} -it paddledev/paddle:-gpu - .. code-block:: bash +3. 运行以及发布您的AI程序 - nvidia-docker run -it --rm paddledev/paddle:0.10.0rc1-gpu /bin/bash + 假设您已经完成了一个AI训练的python程序 :code:`a.py`,这个程序是您在开发机上使用开发镜像完成开发。此时您可以运行这个命令在开发机上进行测试运行: - 注意: 如果使用nvidia-docker存在问题,你也许可以尝试更老的方法,具体如下,但是我们并不推荐这种方法。: + .. code-block:: bash - .. code-block:: bash + docker run -it -v $PWD:/work paddle /work/a.py - export CUDA_SO="$(\ls /usr/lib64/libcuda* | xargs -I{} echo '-v {}:{}') $(\ls /usr/lib64/libnvidia* | xargs -I{} echo '-v {}:{}')" - export DEVICES=$(\ls /dev/nvidia* | xargs -I{} echo '--device {}:{}') - docker run ${CUDA_SO} ${DEVICES} -it paddledev/paddle:-gpu + 如果要使用GPU,请运行: -3. 使用运行镜像发布你的AI程序 - 假设您已经完成了一个AI训练的python程序 :code:`a.py`,这个程序是您在开发机上使用开发镜像完成开发。此时您可以运行这个命令在开发机上进行测试运行: + .. code-block:: bash - .. code-block:: bash + nvidia-docker run -it -v $PWD:/work paddle /work/a.py - docker run -it -v $PWD:/work paddle /work/a.py - 这里`a.py`包含的所有依赖假设都可以在Paddle的运行容器中。如果需要包含更多的依赖、或者需要发布您的应用的镜像,可以编写`Dockerfile`使用`FROM paddledev/paddle:` - 创建和发布自己的AI程序镜像。 + 这里`a.py`包含的所有依赖假设都可以在Paddle的运行容器中。如果需要包含更多的依赖、或者需要发布您的应用的镜像,可以编写`Dockerfile`使用`FROM paddledev/paddle:` + 创建和发布自己的AI程序镜像。 运行PaddlePaddle书籍 --------------------- @@ -109,53 +128,44 @@ PaddlePaddle书籍是为用户和开发者制作的一个交互式的Jupyter Nod 开发人员可以在Docker开发镜像中开发PaddlePaddle。这样开发人员可以以一致的方式在不同的平台上工作 - Linux,Mac OS X和Windows。 -1. 构建开发镜像 +1. 制作PaddlePaddle开发镜像 - .. code-block:: bash + PaddlePaddle每次发布新版本都会发布对应的开发镜像供开发者直接使用。这里介绍如生成造这个开发镜像。 + 生成Docker镜像的方式有两个,一个是直接把一个容器转换成镜像,另一个是创建Dockerfile并运行docker build指令按照Dockerfile生成镜像。第一个方法的好处是简单快捷,适合自己实验,可以快速迭代。第二个方法的好处是Dockerfile可以把整个生成流程描述很清楚,其他人很容易看懂镜像生成过程,持续集成系统也可以简单地复现这个过程。我们采用第二个方法。Dockerfile位于PaddlePaddle repo的根目录。生成生产镜像只需要运行: - git clone --recursive https://github.com/PaddlePaddle/Paddle + .. code-block:: bash + + git clone https://github.com/PaddlePaddle/Paddle.git cd Paddle docker build -t paddle:dev . + docker build这个命令的-t指定了生成的镜像的名字,这里我们用paddle:dev。到此,PaddlePaddle开发镜像就被构建完毕了。 - 请注意,默认情况下,:code:`docker build` 不会将源码导入到镜像中并编译它。如果我们想这样做,需要构建完开发镜像,然后执行: - - .. code-block:: bash - - docker run -v $PWD:/paddle -e "WITH_GPU=OFF" -e "WITH_AVX=ON" -e "TEST=OFF" paddle:dev - - -2. 运行开发环境 +2. 制作PaddlePaddle生产镜像 - 当我们编译好了 :code:`paddle:dev`, 我们可以在docker容器里做开发,源代码可以通过挂载本地文件来被载入Docker的开发环境里面: + 生产镜像的生成分为两步,第一步是运行: .. code-block:: bash + + docker run -v $(pwd):/paddle -e "WITH_GPU=OFF" -e "WITH_AVX=OFF" -e "WITH_TEST=ON" paddle:dev - docker run -d -p 2202:22 -v $PWD:/paddle paddle:dev sshd + 以上命令会编译PaddlePaddle,生成运行程序,以及生成创建生产镜像的Dockerfile。所有生成的的文件都在build目录下。“WITH_GPU”控制生成的生产镜像是否支持GPU,“WITH_AVX”控制生成的生产镜像是否支持AVX,”WITH_TEST“控制是否生成单元测试。 - 以上代码会启动一个带有PaddlePaddle开发环境的docker容器,源代码会被挂载到 :code:`/paddle` 。 - - 以上的 :code:`docker run` 命令其实会启动一个在2202端口监听的SSHD服务器。这样,我们就能SSH进入我们的开发容器了: + 第二步是运行: .. code-block:: bash + + docker build -t paddle:prod -f build/Dockerfile . - ssh root@localhost -p 2202 +以上命令会按照生成的Dockerfile把生成的程序拷贝到生产镜像中并做相应的配置,最终生成名为paddle:prod的生产镜像。 -3. 在Docker开发环境中编译与安装PaddlPaddle代码 +3. 运行单元测试 - 当在容器里面的时候,可以用脚本 :code:`paddle/scripts/docker/build.sh` 来编译、安装与测试PaddlePaddle: + 运行以下指令: .. code-block:: bash - - /paddle/paddle/scripts/docker/build.sh - - 以上指令会在 :code:`/paddle/build` 中编译PaddlePaddle。通过以下指令可以运行单元测试: - - .. code-block:: bash - - cd /paddle/build - ctest - + + docker run -it -v $(pwd):/paddle paddle:dev bash -c "cd /paddle/build && ctest" 文档 ---- -- GitLab From 703995aa058664f8222b5d77a2af8956b7d7b2dd Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Tue, 25 Apr 2017 16:55:13 -0700 Subject: [PATCH 0128/3256] update docker install English version --- .../build_and_install/docker_install_en.rst | 75 ++++++++----------- 1 file changed, 32 insertions(+), 43 deletions(-) diff --git a/doc/getstarted/build_and_install/docker_install_en.rst b/doc/getstarted/build_and_install/docker_install_en.rst index 8fb9369e0e8..b6a9dd963c6 100644 --- a/doc/getstarted/build_and_install/docker_install_en.rst +++ b/doc/getstarted/build_and_install/docker_install_en.rst @@ -53,12 +53,20 @@ Docker is simple as long as we understand a few basic concepts: Usage of CPU-only and GPU Images ---------------------------------- -For each version of PaddlePaddle, we release two types of Docker images: -development image and production image. Production image includes -CPU-only version and a CUDA GPU version and their no-AVX versions. We -put the docker images on `dockerhub.com +We package PaddlePaddle's compile environment into a Docker image, +called the develop image, it contains all compiling tools that +PaddlePaddle needs. We package compiled PaddlePaddle program into a +Docker image as well, called the production image, it contains all +runtime environment that running PaddlePaddle needs. For each version +of PaddlePaddle, we release both of them. Production image includes +CPU-only version and a CUDA GPU version and their no-AVX versions. + +We put the docker images on `dockerhub.com `_. You can find the -latest versions under "tags" tab at dockerhub.com +latest versions under "tags" tab at dockerhub.com. If you are in +China, you can use our Docker image registry mirror to speed up the +download process. To use it, please replace all paddlepaddle/paddle in +the commands to docker.paddlepaddle.org/paddle. 1. Production images, this image might have multiple variants: @@ -179,59 +187,40 @@ Develop PaddlePaddle or Train Model Using C++ API We will be using PaddlePaddle development image since it contains all compiling tools and dependencies. -Let's clone PaddlePaddle repo first: +1. Build PaddlePaddle develop image -.. code-block:: bash - - git clone https://github.com/PaddlePaddle/Paddle.git && cd Paddle - -Mount both workspace folder and paddle code folder into docker -container, so we can access them inside docker container. There are -two ways of using PaddlePaddle development docker image: - -- run interactive bash directly + Use following command to build PaddlePaddle develop image: - .. code-block:: bash - - # use nvidia-docker instead of docker if you need to use GPU - docker run -it -v ~/workspace:/workspace -v $(pwd):/paddle paddlepaddle/paddle:0.10.0rc2-dev /bin/bash - # now we are inside docker container + .. code-block:: bash -- or, we can run it as a daemon container + git clone https://github.com/PaddlePaddle/Paddle.git && cd Paddle + docker build -t paddle:dev . - .. code-block:: bash +2. Build PaddlePaddle production image - # use nvidia-docker instead of docker if you need to use GPU - docker run -d -p 2202:22 -p 8888:8888 -v ~/workspace:/workspace -v $(pwd):/paddle paddlepaddle/paddle:0.10.0rc2-dev /usr/sbin/sshd -D + There are two steps for building production image, the first step is to run: - and SSH to this container using password :code:`root`: - - .. code-block:: bash + .. code-block:: bash - ssh -p 2202 root@localhost + docker run -v $(pwd):/paddle -e "WITH_GPU=OFF" -e "WITH_AVX=OFF" -e "WITH_TEST=ON" paddle:dev - An advantage is that we can run the PaddlePaddle container on a - remote server and SSH to it from a laptop. + The above command will compile PaddlePaddle and create a Dockerfile for building production image. All the generated files are in the build directory. "WITH_GPU" controls if the generated production image supports GPU. "WITH_AVX" controls if the generated production image supports AVX. "WITH_TEST" controls if the unit test will be generated. -When developing PaddlePaddle, you can edit PaddlePaddle source code -from outside of docker container using your favoriate editor. To -compile PaddlePaddle, run inside container: + The second step is to run: -.. code-block:: bash + .. code-block:: bash - WITH_GPU=OFF WITH_AVX=ON WITH_TEST=ON bash /paddle/paddle/scripts/docker/build.sh + docker build -t paddle:prod -f build/Dockerfile . -This builds everything about Paddle in :code:`/paddle/build`. And we -can run unit tests there: + The above command will generate the production image by copying the compiled PaddlePaddle program into the image. -.. code-block:: bash +3. Run unit test - cd /paddle/build - ctest + Following command will run unit test: -When training model using C++ API, we can edit paddle program in -~/workspace outside of docker. And build from /workspace inside of -docker. + .. code-block:: bash + + docker run -it -v $(pwd):/paddle paddle:dev bash -c "cd /paddle/build && ctest" PaddlePaddle Book ------------------ -- GitLab From 40f31092a98d71e4065d611b4d4887d5c4a04e79 Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Tue, 25 Apr 2017 17:05:01 -0700 Subject: [PATCH 0129/3256] =?UTF-8?q?change=20PaddlePaddle=E4=B9=A6?= =?UTF-8?q?=E7=B1=8D=20to=20PaddlePaddle=20book,=20rename=20docker=20to=20?= =?UTF-8?q?Docker?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../build_and_install/docker_install_cn.rst | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/doc/getstarted/build_and_install/docker_install_cn.rst b/doc/getstarted/build_and_install/docker_install_cn.rst index 1810dc63485..6e6cafe8b57 100644 --- a/doc/getstarted/build_and_install/docker_install_cn.rst +++ b/doc/getstarted/build_and_install/docker_install_cn.rst @@ -4,7 +4,7 @@ PaddlePaddle的Docker容器使用方式 PaddlePaddle目前唯一官方支持的运行的方式是Docker容器。因为Docker能在所有主要操作系统(包括Linux,Mac OS X和Windows)上运行。 请注意,您需要更改 `Dockers设置 `_ 才能充分利用Mac OS X和Windows上的硬件资源。 -PaddlePaddle发布的docker镜像使用说明 +PaddlePaddle发布的Docker镜像使用说明 ------------------------------ 我们把PaddlePaddle的编译环境打包成一个镜像,称为开发镜像,里面涵盖了 @@ -13,7 +13,7 @@ PaddlePaddle需要的所有编译工具。把编译出来的PaddlePaddle也打 PaddlePaddle发布新版本的时候都会发布对应版本的生产镜像以及开发镜像。运 行镜像包括纯CPU版本和GPU版本以及其对应的非AVX版本。我们会在 `dockerhub.com `_ 提供最新 -的docker镜像,可以在"tags"标签下找到最新的Paddle镜像版本。为了方便在国 +的Docker镜像,可以在"tags"标签下找到最新的Paddle镜像版本。为了方便在国 内的开发者下载Docker镜像,我们提供了国内的镜像服务器供大家使用。如果您 在国内,请把文档里命令中的paddlepaddle/paddle替换成 docker.paddlepaddle.org/paddle。 @@ -101,15 +101,15 @@ docker.paddlepaddle.org/paddle。 这里`a.py`包含的所有依赖假设都可以在Paddle的运行容器中。如果需要包含更多的依赖、或者需要发布您的应用的镜像,可以编写`Dockerfile`使用`FROM paddledev/paddle:` 创建和发布自己的AI程序镜像。 -运行PaddlePaddle书籍 +运行PaddlePaddle Book --------------------- Jupyter Notebook是一个开源的web程序,大家可以通过它制作和分享带有代码、公式、图表、文字的交互式文档。用户可以通过网页浏览文档。 -PaddlePaddle书籍是为用户和开发者制作的一个交互式的Jupyter Nodebook。 -如果您想要更深入了解deep learning,PaddlePaddle书籍一定是您最好的选择。 +PaddlePaddle Book是为用户和开发者制作的一个交互式的Jupyter Nodebook。 +如果您想要更深入了解deep learning,PaddlePaddle Book一定是您最好的选择。 -我们提供可以直接运行PaddlePaddle书籍的docker镜像,直接运行: +我们提供可以直接运行PaddlePaddle Book的Docker镜像,直接运行: .. code-block:: bash -- GitLab From 448130c07fede156f1b8dfdbd15d405687848136 Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Tue, 25 Apr 2017 17:08:23 -0700 Subject: [PATCH 0130/3256] add indentation --- doc/getstarted/build_and_install/docker_install_cn.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/getstarted/build_and_install/docker_install_cn.rst b/doc/getstarted/build_and_install/docker_install_cn.rst index 6e6cafe8b57..e35dc6aaef0 100644 --- a/doc/getstarted/build_and_install/docker_install_cn.rst +++ b/doc/getstarted/build_and_install/docker_install_cn.rst @@ -157,7 +157,7 @@ PaddlePaddle Book是为用户和开发者制作的一个交互式的Jupyter Node docker build -t paddle:prod -f build/Dockerfile . -以上命令会按照生成的Dockerfile把生成的程序拷贝到生产镜像中并做相应的配置,最终生成名为paddle:prod的生产镜像。 + 以上命令会按照生成的Dockerfile把生成的程序拷贝到生产镜像中并做相应的配置,最终生成名为paddle:prod的生产镜像。 3. 运行单元测试 -- GitLab From 45942268b89fed6ab75d69bc115ff5fe675b5a38 Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Tue, 25 Apr 2017 20:56:30 -0700 Subject: [PATCH 0131/3256] make docker build command for building prodction image more precise --- doc/getstarted/build_and_install/docker_install_cn.rst | 2 +- doc/getstarted/build_and_install/docker_install_en.rst | 2 +- paddle/scripts/docker/README.md | 2 +- paddle/scripts/docker/build.sh | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/getstarted/build_and_install/docker_install_cn.rst b/doc/getstarted/build_and_install/docker_install_cn.rst index e35dc6aaef0..da2d4234658 100644 --- a/doc/getstarted/build_and_install/docker_install_cn.rst +++ b/doc/getstarted/build_and_install/docker_install_cn.rst @@ -155,7 +155,7 @@ PaddlePaddle Book是为用户和开发者制作的一个交互式的Jupyter Node .. code-block:: bash - docker build -t paddle:prod -f build/Dockerfile . + docker build -t paddle:prod -f build/Dockerfile ./build 以上命令会按照生成的Dockerfile把生成的程序拷贝到生产镜像中并做相应的配置,最终生成名为paddle:prod的生产镜像。 diff --git a/doc/getstarted/build_and_install/docker_install_en.rst b/doc/getstarted/build_and_install/docker_install_en.rst index b6a9dd963c6..03df4975060 100644 --- a/doc/getstarted/build_and_install/docker_install_en.rst +++ b/doc/getstarted/build_and_install/docker_install_en.rst @@ -210,7 +210,7 @@ compiling tools and dependencies. .. code-block:: bash - docker build -t paddle:prod -f build/Dockerfile . + docker build -t paddle:prod -f build/Dockerfile ./build The above command will generate the production image by copying the compiled PaddlePaddle program into the image. diff --git a/paddle/scripts/docker/README.md b/paddle/scripts/docker/README.md index b45d92507c1..76bc30e59b8 100644 --- a/paddle/scripts/docker/README.md +++ b/paddle/scripts/docker/README.md @@ -119,7 +119,7 @@ Users can specify the following Docker build arguments with either "ON" or "OFF" The following command builds the production image: ```bash -docker build -t paddle -f build/Dockerfile . +docker build -t paddle -f build/Dockerfile ./build ``` This production image is minimal -- it includes binary `paddle`, the shared library `libpaddle.so`, and Python runtime. diff --git a/paddle/scripts/docker/build.sh b/paddle/scripts/docker/build.sh index 959cdd14f88..a750c436dc5 100644 --- a/paddle/scripts/docker/build.sh +++ b/paddle/scripts/docker/build.sh @@ -116,7 +116,7 @@ RUN ${MIRROR_UPDATE} pip install --upgrade pip && \ pip install -U 'protobuf==3.1.0' requests numpy # Use different deb file when building different type of images -ADD build/*.deb /usr/local/opt/paddle/deb/ +ADD *.deb /usr/local/opt/paddle/deb/ # run paddle version to install python packages first RUN dpkg -i /usr/local/opt/paddle/deb/*.deb && \ rm -f /usr/local/opt/paddle/deb/*.deb && \ -- GitLab From 47c4484dd14be0eb18cc9e1ccd2b94a1f012c141 Mon Sep 17 00:00:00 2001 From: livc Date: Wed, 26 Apr 2017 21:24:57 +0800 Subject: [PATCH 0132/3256] update doc as comments --- doc/howto/dev/contribute_to_paddle_cn.md | 104 +++++++++++++++-------- 1 file changed, 69 insertions(+), 35 deletions(-) diff --git a/doc/howto/dev/contribute_to_paddle_cn.md b/doc/howto/dev/contribute_to_paddle_cn.md index c95d034c989..64227352c3b 100644 --- a/doc/howto/dev/contribute_to_paddle_cn.md +++ b/doc/howto/dev/contribute_to_paddle_cn.md @@ -3,8 +3,8 @@ 我们真诚地感谢您的贡献,欢迎通过 GitHub 的 fork 和 pull request 流程来提交代码。 ## 代码要求 -- 你的代码必须完全遵守 [doxygen](http://www.stack.nl/~dimitri/doxygen/) 的样式。 -- 确保编译器选项 WITH\_STYLE\_CHECK 已打开,并且编译能通过代码样式检查。 +- 代码注释请遵守 [Doxygen](http://www.stack.nl/~dimitri/doxygen/) 的样式。 +- 确保编译器选项 `WITH_STYLE_CHECK` 已打开,并且编译能通过代码样式检查。 - 所有代码必须具有单元测试。 - 通过所有单元测试。 @@ -15,7 +15,7 @@ ## 克隆(Clone) -将远程仓库 clone 到本地。 +将远程仓库 clone 到本地: ```bash ➜ git clone https://github.com/USERNAME/Paddle @@ -25,25 +25,30 @@ ## 创建本地分支 -Paddle 目前使用[Git流分支模型](http://nvie.com/posts/a-successful-git-branching-model/)进行开发,测试,发行和维护。**develop** 是主分支,其他用户分支是特征分支(feature branches)。 +Paddle 目前使用[Git流分支模型](http://nvie.com/posts/a-successful-git-branching-model/)进行开发,测试,发行和维护,具体请参考 [Paddle 分支规范](https://github.com/PaddlePaddle/Paddle/blob/develop/doc/design/releasing_process.md#paddle-分支规范)。 所有的 feature 和 bug fix 的开发工作都应该在一个新的分支上完成,一般从 `develop` 分支上创建新分支。 -```bash -# (从当前分支)创建名为 MY_COOL_STUFF_BRANCH 的新分支 -➜ git branch MY_COOL_STUFF_BRANCH +使用 `git checkout -b` 创建并切换到新分支。 -# 切换到这个分支上 -➜ git checkout MY_COOL_STUFF_BRANCH +```bash +➜ git checkout -b my-cool-stuff ``` -也可以通过 `git checkout -b` 一次性创建并切换分支。 +值得注意的是,在 checkout 之前,需要保持当前分支目录 clean,否则会把 untracked 的文件也带到新分支上,这可以通过 `git status` 查看。 + +## 使用 `pre-commit` 钩子 + +Paddle 开发人员使用 [pre-commit](http://pre-commit.com/) 工具来管理 Git 预提交钩子。 它可以帮助我们格式化源代码(C++,Python),在提交(commit)前自动检查一些基本事宜(如每个文件只有一个 EOL,Git 中不要添加大文件等)。 + +`pre-commit`测试是 Travis-CI 中单元测试的一部分,不满足钩子的 PR 不能被提交到 Paddle,首先安装并在当前目录运行它: ```bash -➜ git checkout -b MY_COOL_STUFF_BRANCH +➜ pip install pre-commit +➜ pre-commit install ``` -值得注意的是,在 checkout 之前,需要保持当前分支目录 clean,否则会把 untracked 的文件也带到新分支上,这可以通过 `git status` 查看。 +Paddle 使用 `clang-format` 来调整 C/C++ 源代码格式,请确保 `clang-format` 版本在 3.8 以上。 ## 开始开发 @@ -68,6 +73,36 @@ Untracked files: no changes added to commit (use "git add" and/or "git commit -a") ``` +## 构建和测试 + +编译 PaddlePaddle 的源码以及生成文档需要多种开发工具。为了方便大家,我们的标准开发流程是把这些工具都装进一个Docker image,称为*开发镜像*,通常名字是 `paddle:dev`。然后所有用 `cmake && make` 的地方(比如IDE配置里)都用 `docker run paddle:dev`来代替。 + +如要build这个开发镜像,在源码目录树的根目录中运行: + +```bash +➜ docker build -t paddle:dev . +``` + +随后可以用这个开发镜像开build PaddlePaddle的源码。比如如果要build一个不依赖GPU,但是支持AVX指令集,并且包括unit tests的PaddlePaddle,可以: + +```bash +➜ docker run -v $(pwd):/paddle -e "WITH_GPU=OFF" -e "WITH_AVX=ON" -e "WITH_TEST=ON" paddle:dev +``` + +这个过程除了编译PaddlePaddle为 `./build/libpaddle.so`,并且输出一个 `./build/paddle.deb`文件之外,还会输出一个 `build/Dockerfile`。我们只需要运行下面命令把编译好的PaddlePaddle打包成一个*生产镜像*(`paddle:prod`): + +```bash +➜ docker build -t paddle:prod -f build/Dockerfile . +``` + +如果要运行所有的单元测试,可以用如下命令: + +```bash +➜ docker run -it -v $(pwd):/paddle paddle:dev bash -c "cd /paddle/build && ctest" +``` + +关于构建和测试的更多信息,请参见[这篇文档](https://github.com/PaddlePaddle/Paddle/blob/develop/doc/getstarted/build_and_install/docker_install_cn.rst)。 + ## 提交(commit) 接下来我们取消对 README.md 文件的改变,然后提交新添加的 test 文件。 @@ -85,17 +120,10 @@ nothing added to commit but untracked files present (use "git add" to track) ➜ git add test ``` -Paddle 使用 [pre-commit](http://pre-commit.com) 完成代码风格检查的自动化,它会在每次 commit 时自动检查代码是否符合规范,并检查一些基本事宜,因此我们首先安装并在当前目录运行它。 +Git 每次提交代码,都需要写提交说明,这可以让其他人知道这次提交做了哪些改变,这可以通过`git commit` 完成。 ```bash -➜ pip install pre-commit -➜ pre-commit install -``` - -Git 每次提交代码,都需要写提交说明,这可以让其他人知道这次提交做了哪些改变,这可以通过`git commit -m` 完成。 - -```bash -➜ git commit -m "add test file" +➜ git commit CRLF end-lines remover...............................(no files to check)Skipped yapf.................................................(no files to check)Skipped Check for added large files..............................................Passed @@ -104,7 +132,7 @@ Check for broken symlinks................................................Passed Detect Private Key...................................(no files to check)Skipped Fix End of Files.....................................(no files to check)Skipped clang-formater.......................................(no files to check)Skipped -[MY_COOL_STUFF_BRANCH c703c041] add test file +[my-cool-stuff c703c041] add test file 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 233 ``` @@ -136,7 +164,7 @@ upstream ```bash ➜ git fetch upstream -➜ git pull --rebase upstream develop +➜ git pull upstream develop ``` ## Push 到远程仓库 @@ -144,23 +172,23 @@ upstream 将本地的修改推送到 GitHub 上,也就是 https://github.com/USERNAME/Paddle。 ```bash -# 推送到远程仓库 origin 的 MY_COOL_STUFF_BRANCH 分支上 -➜ git push origin MY_COOL_STUFF_BRANCH +# 推送到远程仓库 origin 的 my-cool-stuff 分支上 +➜ git push origin my-cool-stuff ``` -## 建立 Issue 并完成 PR +## 建立 Issue 并完成 Pull Request -建立一个 Issue 描述问题,记录它的编号。 +建立一个 Issue 描述问题,并记录它的编号。 -在 Push 新分支后, https://github.com/USERNAME/Paddle 中会出现新分支提示,点击绿色按钮发起 PR。 +切换到所建分支,然后点击 `New pull request`。 -![](https://ws1.sinaimg.cn/large/9cd77f2egy1fez1jq9mwdj21js04yq3m.jpg) +screen shot 2017-04-26 at 9 09 28 pm 选择目标分支: -![](https://ws1.sinaimg.cn/large/9cd77f2egy1fez1ku4a5vj21am04st9l.jpg) +screen shot 2017-04-26 at 9 11 52 pm -在 PR 的说明中,填写 `solve #Issue编号` 可以在这个 PR 被 merge 后,自动关闭对应的 Issue,具体请见 。 +在 PR 的描述说明中,填写 `solve #Issue编号` 可以在这个 PR 被 merge 后,自动关闭对应的 Issue,具体请见 。 接下来等待 review,如果有需要修改的地方,参照上述步骤更新 origin 中的对应分支即可。 @@ -168,7 +196,13 @@ upstream 在 PR 被 merge 进主仓库后,我们可以在 PR 的页面删除远程仓库的分支。 -![](https://ws1.sinaimg.cn/large/9cd77f2egy1fez1pkqohzj217q05c0tk.jpg) +screen shot 2017-04-26 at 9 18 24 pm + +也可以使用 `git push origin :分支名` 删除远程分支,如: + +```bash +➜ git push origin :my-cool-stuff +``` ## 删除本地分支 @@ -176,10 +210,10 @@ upstream ```bash # 切换到 develop 分支 -git checkout develop +➜ git checkout develop -# 删除 MY_COOL_STUFF_BRANCH 分支 -git branch -D MY_COOL_STUFF_BRANCH +# 删除 my-cool-stuff 分支 +➜ git branch -D my-cool-stuff ``` 至此,我们就完成了一次代码贡献的过程。 -- GitLab From 0f39da1b4b84177b73f96b0be5db1a4af220bf09 Mon Sep 17 00:00:00 2001 From: Yi Wang Date: Wed, 26 Apr 2017 11:36:46 -0700 Subject: [PATCH 0133/3256] Update author list --- AUTHORS.md | 28 +++++++++++++++++++++++++++ authors | 56 ------------------------------------------------------ 2 files changed, 28 insertions(+), 56 deletions(-) create mode 100644 AUTHORS.md delete mode 100644 authors diff --git a/AUTHORS.md b/AUTHORS.md new file mode 100644 index 00000000000..d5baee2161a --- /dev/null +++ b/AUTHORS.md @@ -0,0 +1,28 @@ +| Github account | name | +|---|---| +| reyoung | Yang Yu | +| gangliao | Gang Liao | +| luotao01 | Tao Luo | +| jacquesqiao | Long-Fei Qiao | +| qingqing01 | Qing-Qing Dang | +| hedaoyuan | Dao-Yuan He | +| wangyang59 | Yang Wang | +| QiJune | Jun Qi | +| tianbingsz | Tian-Bing Xu | +| cxwangyi, yiwangbaidu, wangkuiyi | Yi Wang | +| typhoonzero | Yi Wu | +| backyes | Yan-Fei Wang | +| pengli09 | Peng Li | +| livc | Zhao Li | +| Xreki | Yi-Qun Liu | +| Yancey1989 | Xu Yan | +| emailweixu | Wei Xu | +| wen-bo-yang | Wen-Bo Yang | +| helinwang | He-Lin Wang | +| lcy-seso | Ying Cao | +| Zrachel | Rui-Qing Zhang | +| Haichao-Zhang | Hai-Chao Zhang | +| gongweibao | Wei-Bao Gong | +| lzhao4ever | Liang Zhao | +| zhouxiao-coder | Xiao Zhou | +| lipeng-unisound | Peng Li | diff --git a/authors b/authors deleted file mode 100644 index daac4ec5d81..00000000000 --- a/authors +++ /dev/null @@ -1,56 +0,0 @@ -Cao, Ying -Cheng, Yujuan -Dang, Qingqing -Dong, Tengfei -Du, Dalong -Feng, Shouqiang -Gao, Haoyuan -Han, Baochang -Han, Jinchen -Hao, Nanyu -He, Daoyuan -He, Zhengyan -Hou, Jue -Huang, Chang -Huang, Zhiheng -Hu, Na -Kong, Qi -Liao, Gang -Li, Bo -Li, Jiajie -Li, Jing -Li, Lei -Li, Peng -Liu, Sheng -Liu, Yuan -Li, Yuze -Luo, Heng -Luo, Tao -Lyu, Qin -Mao, Hongyue -Qian, Xiaojun -Qiao, Longfei -Qi, Jun -Qin, Duohao -Shen, Guolong -Shi, Guangchuan -Song, Xiang -Wang, Helin -Wang, Jiang -Wang, Yanfei -Wang, Yi -Wang, Yong -Weng, Renliang -Xu, Tianbing -Xu, Wei -Xu, Xingyu -Yan, Chong -Yan, Chunwei -Yang, Yi -Yu, Yang -Yu, Yinan -Zhang, Jian -Zhang, Ruiqing -Zhang, Weide -Zhao, Liang -Zhou, Jie -- GitLab From bf1320a73222106565ab69c9dd822f36319a3dcb Mon Sep 17 00:00:00 2001 From: xuwei06 Date: Wed, 26 Apr 2017 16:44:09 -0700 Subject: [PATCH 0134/3256] Skip cpplint if source is not changed. Change-Id: Ia29cd3d205e401dc3d04fbaf3cba2b01f2f4f0ca --- cmake/cpplint.cmake | 12 ++++++++---- paddle/scripts/cpplint.py | 12 +++++++++++- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/cmake/cpplint.cmake b/cmake/cpplint.cmake index 38c636b30ed..02a5c0b2c9b 100644 --- a/cmake/cpplint.cmake +++ b/cmake/cpplint.cmake @@ -34,7 +34,7 @@ set(IGNORE_PATTERN # # first argument: target name to attach # rest arguments: source list to check code style. -# +# # NOTE: If WITH_STYLE_CHECK is OFF, then this macro just do nothing. macro(add_style_check_target TARGET_NAME) if(WITH_STYLE_CHECK) @@ -48,13 +48,17 @@ macro(add_style_check_target TARGET_NAME) if(filename MATCHES ${pattern}) message(STATUS "DROP LINT ${filename}") set(LINT OFF) - endif() + endif() endforeach() if(LINT MATCHES ON) - add_custom_command(TARGET ${TARGET_NAME} + get_filename_component(base_filename ${filename} NAME) + set(CUR_GEN ${CMAKE_CURRENT_BINARY_DIR}/${base_filename}.cpplint) + add_custom_command(OUTPUT ${CUR_GEN} PRE_BUILD COMMAND env ${py_env} "${PYTHON_EXECUTABLE}" "${PROJ_ROOT}/paddle/scripts/cpplint.py" - "--filter=${STYLE_FILTER}" ${filename} + "--filter=${STYLE_FILTER}" + "--write-success=${CUR_GEN}" ${filename} + DEPENDS ${filename} WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}) endif() endforeach() diff --git a/paddle/scripts/cpplint.py b/paddle/scripts/cpplint.py index 157ce7b44ac..dff4339ea33 100644 --- a/paddle/scripts/cpplint.py +++ b/paddle/scripts/cpplint.py @@ -58,6 +58,7 @@ _USAGE = """ Syntax: cpplint.py [--verbose=#] [--output=vs7] [--filter=-x,+y,...] [--counting=total|toplevel|detailed] [--root=subdir] [--linelength=digits] + [--write-success=success_status_file] [file] ... The style guidelines this tries to follow are those in @@ -499,6 +500,8 @@ _line_length = 80 # This is set by --extensions flag. _valid_extensions = set(['cc', 'h', 'cpp', 'cu', 'cuh']) +_write_success = None + def ParseNolintSuppressions(filename, raw_line, linenum, error): """Updates the global list of error-suppressions. @@ -6337,7 +6340,7 @@ def ParseArguments(args): try: (opts, filenames) = getopt.getopt(args, '', [ 'help', 'output=', 'verbose=', 'counting=', 'filter=', 'root=', - 'linelength=', 'extensions=' + 'linelength=', 'extensions=', 'write-success=' ]) except getopt.GetoptError: PrintUsage('Invalid arguments.') @@ -6382,6 +6385,9 @@ def ParseArguments(args): _valid_extensions = set(val.split(',')) except ValueError: PrintUsage('Extensions must be comma seperated list.') + elif opt == '--write-success': + global _write_success + _write_success = val if not filenames: PrintUsage('No files were specified.') @@ -6408,6 +6414,10 @@ def main(): ProcessFile(filename, _cpplint_state.verbose_level) _cpplint_state.PrintErrorCounts() + if _cpplint_state.error_count == 0 and _write_success is not None: + with open(_write_success, 'a'): + os.utime(_write_success, None) + sys.exit(_cpplint_state.error_count > 0) -- GitLab From 966e89cd683219000fcd2101fdb2308028c119cf Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Thu, 27 Apr 2017 13:43:06 +0800 Subject: [PATCH 0135/3256] Disable some unit tests when PYTHON or SWIG_PY are not enabled. * When SWIG_PY is not enabled, paddle.v2 package cannot be used. * When PYTHON is not enabled, PyDataProvider cannot be used. Fix issue #1920 --- paddle/trainer/tests/CMakeLists.txt | 19 +++++++++++-------- python/CMakeLists.txt | 9 ++++++--- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/paddle/trainer/tests/CMakeLists.txt b/paddle/trainer/tests/CMakeLists.txt index c5c76a030d9..08b2d8a38e2 100644 --- a/paddle/trainer/tests/CMakeLists.txt +++ b/paddle/trainer/tests/CMakeLists.txt @@ -17,14 +17,17 @@ add_test(NAME test_Trainer WORKING_DIRECTORY ${PROJ_ROOT}/paddle/) ############### test_TrainerOnePass ########################## -add_unittest_without_exec(test_TrainerOnePass - test_TrainerOnePass.cpp) -add_test(NAME test_TrainerOnePass - COMMAND ${PROJ_ROOT}/paddle/.set_python_path.sh -d - ${PROJ_ROOT}/python/:${PROJ_ROOT}/paddle/trainer/tests - ${PROJ_ROOT}/paddle/.set_port.sh -p port ${CMAKE_CURRENT_BINARY_DIR}/test_TrainerOnePass - WORKING_DIRECTORY ${PROJ_ROOT}/paddle/) - +if(WITH_PYTHON) + # only run test_TrainerOnePass when PYTHON is enabled, because train one pass + # is using PyDataProvider2. + add_unittest_without_exec(test_TrainerOnePass + test_TrainerOnePass.cpp) + add_test(NAME test_TrainerOnePass + COMMAND ${PROJ_ROOT}/paddle/.set_python_path.sh -d + ${PROJ_ROOT}/python/:${PROJ_ROOT}/paddle/trainer/tests + ${PROJ_ROOT}/paddle/.set_port.sh -p port ${CMAKE_CURRENT_BINARY_DIR}/test_TrainerOnePass + WORKING_DIRECTORY ${PROJ_ROOT}/paddle/) +endif() ################ test_CompareTwoNets ###################### add_unittest_without_exec(test_CompareTwoNets test_CompareTwoNets.cpp) diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index e7a0895533d..bfa19d5ecc8 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -24,9 +24,12 @@ add_custom_target(paddle_python ALL DEPENDS ${OUTPUT_DIR}/.timestamp) add_subdirectory(paddle/trainer_config_helpers/tests) -add_subdirectory(paddle/v2/tests) -add_subdirectory(paddle/v2/reader/tests) -add_subdirectory(paddle/v2/plot/tests) +if (WITH_SWIG_PY) + # enable v2 API unittest only when paddle swig api is compiled + add_subdirectory(paddle/v2/tests) + add_subdirectory(paddle/v2/reader/tests) + add_subdirectory(paddle/v2/plot/tests) +endif() install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/dist/ DESTINATION opt/paddle/share/wheels -- GitLab From ee1b603e9cdca5cfe7c88bdd9016a56e3b56c509 Mon Sep 17 00:00:00 2001 From: livc Date: Thu, 27 Apr 2017 14:44:22 +0800 Subject: [PATCH 0136/3256] fix typo --- doc/howto/dev/contribute_to_paddle_cn.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/howto/dev/contribute_to_paddle_cn.md b/doc/howto/dev/contribute_to_paddle_cn.md index 64227352c3b..775938612e8 100644 --- a/doc/howto/dev/contribute_to_paddle_cn.md +++ b/doc/howto/dev/contribute_to_paddle_cn.md @@ -188,7 +188,7 @@ upstream screen shot 2017-04-26 at 9 11 52 pm -在 PR 的描述说明中,填写 `solve #Issue编号` 可以在这个 PR 被 merge 后,自动关闭对应的 Issue,具体请见 。 +在 PR 的描述说明中,填写 `resolve #Issue编号` 可以在这个 PR 被 merge 后,自动关闭对应的 Issue,具体请见 。 接下来等待 review,如果有需要修改的地方,参照上述步骤更新 origin 中的对应分支即可。 -- GitLab From 1a2ff73b5895b4460f2045d5f66045e538a1ed6d Mon Sep 17 00:00:00 2001 From: xuwei06 Date: Thu, 27 Apr 2017 15:53:01 -0700 Subject: [PATCH 0137/3256] Change PROJ_ROOT in CMakeList.txt to use CMAKE_CURRENT_SOURCE_DIR --- CMakeLists.txt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 624083b2cee..aa4f1eaff91 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,19 +1,19 @@ # Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserve. -# +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake") -set(PROJ_ROOT ${CMAKE_SOURCE_DIR}) +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake") +set(PROJ_ROOT ${CMAKE_CURRENT_SOURCE_DIR}) include(system) -- GitLab From 29a9118701c813cc5f1e9e54eebde73bf458d9cd Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Thu, 27 Apr 2017 17:47:18 -0700 Subject: [PATCH 0138/3256] update design doc: add how to convert dataset and use converted dataset --- doc/design/cluster_train/data_dispatch.md | 113 ++++++++++++++-------- 1 file changed, 71 insertions(+), 42 deletions(-) diff --git a/doc/design/cluster_train/data_dispatch.md b/doc/design/cluster_train/data_dispatch.md index 9f2fc404a09..a3eb4e28db0 100644 --- a/doc/design/cluster_train/data_dispatch.md +++ b/doc/design/cluster_train/data_dispatch.md @@ -13,79 +13,108 @@ ### 训练数据的存储 -选择GlusterFS作为训练数据的存储服务(后续的实现考虑HDFS)。 +选择CephFS作为训练数据的存储服务。 在Kubernetes上运行的不同的计算框架,可以通过Volume或PersistentVolume挂载存储空间到每个容器中。 -在GlusterFS存储系统中的公开目录,需要保存一些预置的公开数据集(比如MNIST, BOW, imagenet数据集等),并且可以被提交的job直接使用。 +在CephFS存储系统中的公开目录,需要保存一些预置的公开数据集(比如MNIST, BOW, ImageNet数据集等),并且可以被提交的job直接使用。 -### 上传训练文件 +### 文件预处理 -使用下面命令,可以把本地的训练数据上传到存储集群中,并指定上传数据的`dataset-name`: +在数据集可以被训练之前,文件需要预先被转换成PaddlePaddle集群内部的存储格式(SSTable)。我们提供两个转换方式: -``` -paddle upload train_data.list "dataset-name" -``` +- 提供给用户本地转换的库,用户可以编写程序完成转换。 +- 用户可以上传自己的数据集,在集群运行MapReduce job完成转换。 -其中`.list`文件描述了训练数据的文件和对应的label,对于图像类数据,`.list文件`样例如下,每一行包含了图片文件的路径和其label(用tab分隔开): +转换生成的文件名会是以下格式: -``` -./data/image1.jpg 1 -./data/image2.jpg 5 -./data/image3.jpg 2 -./data/image4.jpg 5 -./data/image5.jpg 1 -./data/image6.jpg 8 -... +```text +name_prefix-aaaaa-of-bbbbb ``` -对于文本类训练数据样例如下(机器翻译),一行中包含源语言,目标语言的文本(label): +"aaaaa"和"bbbbb"都是五位的数字,每一个文件是数据集的一个shard,"aaaaa"代表shard的index,"bbbbb"代表这个shard的最大index。 +比如ImageNet这个数据集可能被分成1000个shard,它们的文件名是: +```text +imagenet-00000-of-00999 +imagenet-00001-of-00999 +... +imagenet-00999-of-00999 ``` -L' inflation , en Europe , a dérapé sur l' alimentation Food : Where European inflation slipped up -L' inflation accélérée , mesurée dans la zone euro , est due principalement à l' augmentation rapide des prix de l' alimentation . The skyward zoom in food prices is the dominant force behind the speed up in eurozone inflation . -... +#### 转换库 + +无论是在本地或是云端转换,我们都提供Python的转换库,接口是: +```python +def convert(output_path, reader, num_shards, name_prefix) ``` -### 使用reader +- `output_path`: directory in which output files will be saved. +- `reader`: a [data reader](https://github.com/PaddlePaddle/Paddle/blob/develop/doc/design/reader/README.md#data-reader-interface), from which the convert program will read data instances. +- `num_shards`: the number of shards that the dataset will be partitioned into. +- `name_prefix`: the name prefix of generated files. -用户在使用v2 API编写训练任务时,可以使用paddle内置的reader完成对GlusterFS存储中的训练数据的读取,返回文件中的各列,然后在调用`trainer.train()`时传入,完成训练数据的读取: +`reader`每次输出一个data instance,这个instance可以是单个值,或者用tuple表示的多个值: ```python -reader = paddle.dist.reader("dataset-name") -trainer.train(reader, ...) -batch_reader = paddle.batch(paddle.dataset.mnist.train(), 128) -trainer.train(batch_reader, ...) +yield 1 # 单个值 +yield numpy.random.uniform(-1, 1, size=28*28) # 单个值 +yield numpy.random.uniform(-1, 1, size=28*28), 0 # 多个值 ``` -trainer.train内部会获取reader的内容: +每个值的类型可以是整形、浮点型数据、字符串,或者由它们组成的list,以及numpy.ndarray。如果是其它类型,会被Pickle序列化成字符串。 -``` -def paddle.train(batch_reader): - r = batch_reader() # create a iterator for one pass of data - for batch in r: - # train +### 示例程序 + +#### 使用转换库 + +以下`reader_creator`生成的`reader`每次输出一个data instance,每个data instance包涵两个值:numpy.ndarray类型的值和整型的值: +```python +def reader_creator(): + def reader(): + for i in range(1000): + yield numpy.random.uniform(-1, 1, size=28*28), 0 # 多个值 + return reader ``` -这里面batch是含有128个data instance的mini-batch。每一个data instance会是一个tuple,tuple元素的顺序与`.list`文件文件中每一列的顺序是一致的。每一个data instance会是(raw_image_file_binary_data, label)。其中raw_image_file_binary_data是对应图像文件的没有解码的原始二进制数据,用户需要自己解码。label是文本类型(比如:“1“,”2“),这里用户需要的其实是整形,用户需要自己转换成整形。 +把`reader_creator`生成的`reader`传入`convert`函数即可完成转换: +```python +convert("./", reader_creator(), 100, random_images) +``` -### 实现reader +以上命令会在当前目录下生成100个文件: +```text +random_images-00000-of-00099 +random_images-00001-of-00099 +... +random_images-00099-of-00099 +``` -reader的实现需要考虑本地训练程序实现之后,可以不修改程序直接提交集群进行分布式训练。要达到这样的目标,需要实现下面的功能: +#### 进行训练 -paddle会封装一个在集群中使用的reader: `paddle.dist.reader()`。在集群训练时需要使用这个reader指定要使用的数据集开始训练。用户的训练程序需要按照如下方式初始化reader: +PaddlePaddle提供专用的[data reader creator](https://github.com/PaddlePaddle/Paddle/blob/develop/doc/design/reader/README.md#python-data-reader-design-doc),生成给定SSTable文件对应的data reader。**无论在本地还是在云端,reader的使用方式都是一致的**: ```python -if os.getenv("PADDLE_TRAIN_LOCAL"): - reader = my_local_reader("dataset-name") -else: - reader = paddle.dist.reader("dataset-name") +# ... +reader = paddle.reader.creator.SSTable("/home/random_images-*-of-*") +batch_reader = paddle.batch(paddle.dataset.mnist.train(), 128) +trainer.train(batch_reader, ...) ``` -用户训练程序提交到集群之后,集群会自动设置`PADDLE_TRAIN_LOCAL`环境变量,reader会被配置成集群训练的版本。其中`paddle.dist.reader()`需要从master的队列中获得需要开始执行的训练task,并找到对应的训练数据文件,开始训练任务。如果用户的训练数据源来自于其他服务,比如从集群中的Kafka,zeromq队列读取,也可以根据实际情况实现集群中运行的reader程序。 +以上代码的reader输出的data instance与生成数据集时,reader输出的data instance是一模一样的。 +### 上传训练文件 + +使用下面命令,可以把本地的数据上传到存储集群中。 + +```bash +paddle cp filenames pfs://home/folder/ +``` + +比如,把之前示例中转换完毕的random_images数据集上传到云端的`/home/`可以用以下指令: +```bash +paddle cp random_images-*-of-* pfs://home/ +``` ## TODO -### 支持将数据合并成内部的文件格式(key-value),方便sharding与顺序读取 ### 支持用户自定义的数据预处理job -- GitLab From 2ac8e6a5182d97bf850b8678dde1d2522b650c4e Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Fri, 28 Apr 2017 13:35:17 +0800 Subject: [PATCH 0139/3256] Try to remove ParallelParameter. It seems that is not used. --- paddle/gserver/layers/Layer.h | 8 +- paddle/parameter/ParallelParameter.cpp | 209 --------------------- paddle/parameter/ParallelParameter.h | 244 ------------------------- paddle/parameter/Parameter.cpp | 49 ----- paddle/parameter/Parameter.h | 23 --- 5 files changed, 3 insertions(+), 530 deletions(-) delete mode 100644 paddle/parameter/ParallelParameter.cpp delete mode 100644 paddle/parameter/ParallelParameter.h diff --git a/paddle/gserver/layers/Layer.h b/paddle/gserver/layers/Layer.h index 7c4bea07215..47182c9ecc6 100644 --- a/paddle/gserver/layers/Layer.h +++ b/paddle/gserver/layers/Layer.h @@ -14,20 +14,18 @@ limitations under the License. */ #pragma once -#include #include #include #include "ModelConfig.pb.h" #include "paddle/function/Function.h" +#include "paddle/gserver/activations/ActivationFunction.h" #include "paddle/math/CpuSparseMatrix.h" +#include "paddle/parameter/Argument.h" #include "paddle/parameter/Parameter.h" +#include "paddle/parameter/Weight.h" #include "paddle/utils/ClassRegistrar.h" #include "paddle/utils/Util.h" -#include -#include -#include "paddle/gserver/activations/ActivationFunction.h" - /// Macro for registering a layer type. /// Example: REGISTER_LAYER(crf_error, CRFDecodingErrorLayer); #define REGISTER_LAYER(__type_name, __class_name) \ diff --git a/paddle/parameter/ParallelParameter.cpp b/paddle/parameter/ParallelParameter.cpp deleted file mode 100644 index cea77e5b178..00000000000 --- a/paddle/parameter/ParallelParameter.cpp +++ /dev/null @@ -1,209 +0,0 @@ -/* 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/utils/Logging.h" - -#include "ParallelParameter.h" - -namespace paddle { - -UpdateFunction paramUpdateFunctions[UPDATE_TYPE_NUM] = { - nullptr, // &ParallelParameter::singleUpdate, /* single thread */ - nullptr, // &ParallelParameter::controlUpdate, /* controller thread */ - &ParallelParameter::majorUpdate, /* major thread */ - &ParallelParameter::minorUpdate, /* minor thread */ - - nullptr, /* master */ - &ParallelParameter::slaveUpdate, /* slave */ -}; -ParallelParameterPtr ParallelParameter::create(TrainerRole role, - ParameterPtr localParam, - int asyncCount) { - ParallelParameterPtr ptr = nullptr; - switch (role) { - case TRAINER_ROLE_CONTROL: - case TRAINER_ROLE_MAJOR: - case TRAINER_ROLE_MINOR: - ptr = std::make_shared(role, localParam); - break; - case TRAINER_ROLE_MASTER: - case TRAINER_ROLE_SLAVE: - ptr = std::make_shared(role, asyncCount, localParam); - break; - default: - LOG(FATAL) << "unknown role " << role << "\n"; - } - return ptr; -} -void ParallelParameter::syncUpdate(TrainerRole role, real learnRate) { - if (paramUpdateFunctions[role]) { - (this->*paramUpdateFunctions[role])(learnRate); - } -} - -void SyncParameter::attachControlParam(ParallelParameterPtr controler) { - controlParam_ = controler; -} - -void SyncParameter::attachMajorParam(ParallelParameterPtr partner) { - majorPartners_.push_back(partner); - if (role_ == TRAINER_ROLE_CONTROL) { - localParam_->setSharedCount(majorPartners_.size()); - } - // partnerParam_ = partner; -} - -void SyncParameter::attachMinorParam(ParallelParameterPtr partner, - int deviceId) { - minorPartners_.push_back(partner); - minorDeviceIds_.push_back(deviceId); - // partnerParam_ = partner; -} - -void SyncParameter::waitAllMajorGradReady() { - for (size_t i = 0; i < majorPartners_.size(); i++) { - majorPartners_[i]->waitGradReady(); - partnerParam_ = majorPartners_[i]->getLocalParameter(); - VectorPtr localGrad = localParam_->getBuf(PARAMETER_GRADIENT); - VectorPtr patnrGrad = partnerParam_->getBuf(PARAMETER_GRADIENT); - if (FLAGS_use_gpu) hl_set_device(minorDeviceIds_[i]); - localGrad->add(*patnrGrad); - } -} - -void SyncParameter::synchronizeParamter() { - valueSem_->wait(); - if (role_ == TRAINER_ROLE_MINOR) { - /* copy the value from controller */ - VectorPtr cntrlVec = - (controlParam_->getLocalParameter())->getBuf(PARAMETER_VALUE); - VectorPtr localVec = localParam_->getBuf(PARAMETER_VALUE); - localVec->copyFrom(*cntrlVec); - - /* dispatch the value to major */ - for (size_t i = 0; i < majorPartners_.size(); i++) { - VectorPtr majorVec = - (majorPartners_[i]->getLocalParameter())->getBuf(PARAMETER_VALUE); - majorVec->copyFrom(*localVec); - majorPartners_[i]->postValueReady(); - } - } -} - -void SyncParameter::singleUpdate(real learnRate) { - CHECK(role_ == TRAINER_ROLE_SINGLE); - localParam_->updateWithGradient(learnRate); -} - -void SyncParameter::controlUpdate(const UpdateCallback &callBack) { - CHECK(role_ == TRAINER_ROLE_CONTROL); - CHECK(gradSem_ != NULL && valueSem_ != NULL); - CHECK(majorPartners_.size()); - - /* update */ - if (callBack) { - callBack(localParam_.get()); - localParam_->clearGradient(); - } - - for (size_t i = 0; i < minorPartners_.size(); i++) { - minorPartners_[i]->postValueReady(); - } -} - -void SyncParameter::majorUpdate(real learnRate) { - (void)learnRate; - CHECK(role_ == TRAINER_ROLE_MAJOR); - CHECK(gradSem_ != NULL && valueSem_ != NULL); - CHECK(minorPartners_.size() && controlParam_); - - /* wait the minor-Gradient is ready */ - for (size_t i = 0; i < minorPartners_.size(); i++) { - minorPartners_[i]->waitGradReady(); - partnerParam_ = minorPartners_[i]->getLocalParameter(); - VectorPtr localGrad = localParam_->getBuf(PARAMETER_GRADIENT); - VectorPtr minorGrad = partnerParam_->getBuf(PARAMETER_GRADIENT); - localGrad->add(*minorGrad); - } - - /* notice the controller that the gradient is ready */ - gradSem_->post(); -} - -void SyncParameter::minorUpdate(real learnRate) { - (void)learnRate; - CHECK(role_ == TRAINER_ROLE_MINOR); - CHECK(gradSem_ != NULL && valueSem_ != NULL); - - // notice the major that the gradient is ready - gradSem_->post(); -} - -AsyncParameter::AsyncParameter(TrainerRole role, - int asyncCount, - ParameterPtr localParam) - : ParallelParameter(role, localParam) { - asyncCount_ = asyncCount; - accumCounter_ = 0; - gradientAccum_ = Vector::create(localParam->getSize(), localParam->useGpu()); - gradientAccum_->zeroMem(); -} - -void AsyncParameter::slaveUpdate(real learnRate) { - /* increase the accumCounter_ */ - accumCounter_++; - - /* accumulate the gradient to the buffer */ - VectorPtr grad = localParam_->getBuf(PARAMETER_GRADIENT); - gradientAccum_->add(*grad); - - /* if need to be synchronized with the master */ - if (accumCounter_ == asyncCount_) { - gradSem_->post(); - // accumCounter_ = 0; NOTICE: the upper-function need to reset the counter - } else { // self update - localParam_->updateWithGradient(learnRate); - } - localParam_->clearGradient(); -} - -bool AsyncParameter::masterUpdate(ParallelParameterPtr slaveParam, - const UpdateCallback &callback) { - CHECK(slaveParam && callback); - - /* wait the slave is ready */ - if (!slaveParam->timeWaitGradReady(5)) { - return false; - } - - AsyncParameter *asyncParam = dynamic_cast(slaveParam.get()); - - /* get the accum-gradient to update local parameter */ - VectorPtr slaveVec = asyncParam->getAccum(); - localParam_->getBuf(PARAMETER_GRADIENT)->copyFrom(*slaveVec); - callback(localParam_.get()); - // slaveVec->zeroMem(); - - /* copy the newest parameter-value to the slave */ - slaveVec = (slaveParam->getLocalParameter())->getBuf(PARAMETER_VALUE); - slaveVec->copyFrom(*(localParam_->getBuf(PARAMETER_VALUE))); - - /* release the semphore */ - slaveParam->postValueReady(); - - return true; -} - -} // namespace paddle diff --git a/paddle/parameter/ParallelParameter.h b/paddle/parameter/ParallelParameter.h deleted file mode 100644 index 2e7c18b8084..00000000000 --- a/paddle/parameter/ParallelParameter.h +++ /dev/null @@ -1,244 +0,0 @@ -/* Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserve. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#pragma once - -#include - -#include -#include -#include -#include -#include - -#include "hl_gpu.h" -#include "paddle/math/Vector.h" -#include "paddle/parameter/Parameter.h" -#include "paddle/parameter/ParameterUpdateFunctions.h" -#include "paddle/utils/Common.h" -#include "paddle/utils/Flags.h" -#include "paddle/utils/Locks.h" - -#include "ParameterConfig.pb.h" - -namespace paddle { - -class ParallelParameter; -class SyncParameter; -class AsyncParameter; - -typedef std::shared_ptr ParallelParameterPtr; - -const int UPDATE_TYPE_NUM = 32; - -/** - * TrainRole denotes the role of current training, different roles have - * different jobs. - * - * control, major, minor are three kinds of role to support mutiple GPUs - * parallel SGD training. SM on GPU card has two groups, each group - * consist of a major and a minor. - * - * @param single single GPU card single thread training. - * - * - * @param control current parameter updates via control role, - * not participate in real training. control role is - * responsible for merging all major's gradient and - * update parameter value. - * - * @param major major role paticipates in real training, when local - * gradient is ready, merge its corresponding minor's - * gradient and notify controller: this group's gradient - * is already ready. - * - * @param minor minor role participates in real training, when local - * gradient is ready, only notify its corresponding major. - * In order to maximum apportion jobs, after controller - * updates the paramemter value, each group's minior - * reponses to dispatch the latest model into local and - * major. - */ -enum TrainerRole { - TRAINER_ROLE_SINGLE, - TRAINER_ROLE_CONTROL, - TRAINER_ROLE_MAJOR, - TRAINER_ROLE_MINOR, - TRAINER_ROLE_MASTER, - TRAINER_ROLE_SLAVE -}; -typedef void (ParallelParameter::*UpdateFunction)(real learnRate); - -class ParallelParameter { -public: - static ParallelParameterPtr create(TrainerRole role, - ParameterPtr localParam, - int asyncCount = 1); - - ParallelParameter(TrainerRole role, ParameterPtr localParam) { - role_ = role; - gradSem_.reset(new Semaphore(0)); - valueSem_.reset(new Semaphore(0)); - localParam_ = localParam; - } - - virtual ~ParallelParameter() {} - - ParameterPtr getLocalParameter() { return localParam_; } - bool timeWaitGradReady(int sec) { - struct timespec ts; - ts.tv_nsec = 0; - ts.tv_sec = time(NULL) + sec; - return gradSem_->timeWait(&ts); - } - void waitGradReady() { gradSem_->wait(); } - void postValueReady() { valueSem_->post(); } - - void syncUpdate(TrainerRole role, real learnRate); - - virtual void synchronizeParamter() = 0; - - /** - * for synchronous - */ - virtual void singleUpdate(real learnRate) { (void)learnRate; } - - virtual void controlUpdate(const UpdateCallback& callback) { (void)callback; } - - virtual void majorUpdate(real learnRate) { (void)learnRate; } - - virtual void minorUpdate(real learnRate) { (void)learnRate; } - - /** - * for asynchronous - */ - virtual void slaveUpdate(real learnRate) { (void)learnRate; } - -protected: - TrainerRole role_; - ParameterPtr localParam_; - std::unique_ptr - gradSem_; /// wether the local parameter-gradient is ready - std::unique_ptr - valueSem_; /// wether the local parameter-value is updated -}; - -/** - * this class is designed for multi-threading training. - * - * "Synchronous" means multiple GPUs calculate 1/4 mini-Batch, - * but will get only one gradient - */ -class SyncParameter : public ParallelParameter { -public: - SyncParameter(TrainerRole role, ParameterPtr localParam) - : ParallelParameter(role, localParam) { - controlParam_ = nullptr; - majorPartners_.clear(); - minorPartners_.clear(); - } - ~SyncParameter() { - majorPartners_.clear(); - minorPartners_.clear(); - } - void attachControlParam(ParallelParameterPtr controler); - - void attachMajorParam(ParallelParameterPtr partner); - - void attachMinorParam(ParallelParameterPtr partner, int deviceId); - - void waitAllMajorGradReady(); - - void synchronizeParamter(); - - void singleUpdate(real learnRate); - - void controlUpdate(const UpdateCallback& callback); - - void majorUpdate(real learnRate); - - void minorUpdate(real learnRate); - - std::vector& getMajorPartners() { - return majorPartners_; - } - - std::vector& getMinorPartners() { - return minorPartners_; - } - -private: - // The following variables are used in a multithreaded training situation - // partnerParam_ is local-parameter's partner - // controlParam_ is the controller-thread 's parameter - ParameterPtr partnerParam_; - std::vector majorPartners_; - std::vector minorPartners_; - std::vector minorDeviceIds_; - ParallelParameterPtr controlParam_; -}; - -class AsyncParameter : public ParallelParameter { -public: - AsyncParameter(TrainerRole role, int asyncCount, ParameterPtr localParam); - - void clearCounter() { accumCounter_ = 0; } - - VectorPtr getAccum() { return gradientAccum_; } - - void synchronizeParamter() { - if (accumCounter_ == asyncCount_) { - valueSem_->wait(); - clearCounter(); - gradientAccum_->zeroMem(); - } - } - - /** - * When asynchronous training, update strategy including slave and master. - * - * slave: If in range asyncCount, adopting self-update method. - * If beyond asyncCount, waiting for master to update. - */ - void slaveUpdate(real learnRate); - - /** - * When asynchronous training, update strategy including slave and master. - * - * master: it only polls slaves, do not training data. - * If slave's gradient is ready, fetch it. - * Update master's parameter, then copy it into - * corresponding slave. - */ - bool masterUpdate(ParallelParameterPtr slaveParam, - const UpdateCallback& callback); - -private: - /** - * When asynchronous training, every aysnc trainer needs to - * accumulate a number of batch gradient. - * - * gradientAccum_ is used to save the sum of gradients. - */ - VectorPtr gradientAccum_; - - /// Asynchronous count. - int asyncCount_; - /// Accumulate counter of current gradients. - int accumCounter_; -}; - -typedef std::map ParallelParameterMap; - -} // namespace paddle diff --git a/paddle/parameter/Parameter.cpp b/paddle/parameter/Parameter.cpp index 1ccded81879..b8efabbe2a0 100644 --- a/paddle/parameter/Parameter.cpp +++ b/paddle/parameter/Parameter.cpp @@ -271,55 +271,6 @@ SparsePrefetchRowCpuMatrix* Parameter::getPrefetchMatrix() { return nullptr; } -void Parameter::updateWithGradient(real learningRate) { - sgdUpdate(learningRate * config_.learning_rate(), - config_.momentum(), - config_.decay_rate(), - bufs_[PARAMETER_VALUE].get(), - bufs_[PARAMETER_GRADIENT].get(), - bufs_[PARAMETER_MOMENTUM].get()); -} - -void Parameter::updateWithGradient(real learningRate, - MatrixPtr gradMat, - IVectorPtr t0, - int currentTime, - bool fini) { - SparseRowCpuMatrix* sparseMat = - dynamic_cast(gradMat.get()); - CHECK(sparseMat); - CHECK_EQ(config_.momentum(), 0.0f) - << "not support momentum in sparse input sgd"; - bool useL1 = (config_.decay_rate_l1() != 0.0f); - sparseMat->sgdUpdate(*bufs_[PARAMETER_VALUE], - *t0, - learningRate * config_.learning_rate(), - currentTime, - useL1 ? config_.decay_rate_l1() : config_.decay_rate(), - useL1, - fini); -} - -void Parameter::updateWithGradient(real learningRate, - VectorPtr gradVec, - bool normalUpdate) { - if (normalUpdate) { - sgdUpdate(learningRate * config_.learning_rate(), - config_.momentum(), - config_.decay_rate(), - bufs_[PARAMETER_VALUE].get(), - gradVec.get(), - bufs_[PARAMETER_MOMENTUM].get()); - } else { - size_t size = gradVec->getSize(); - real* mom = bufs_[PARAMETER_MOMENTUM]->getData(); - real* grad = gradVec->getData(); - real* value = bufs_[PARAMETER_VALUE]->getData(); - hl_matrix_add(mom, grad, mom, 1, size, 1.0f, learningRate); - hl_matrix_add(value, grad, value, 1, size, 1.0f, learningRate); - } -} - void Parameter::incUpdate(const UpdateCallback& callback) { // Static parameter is fixed, and does not need to be updated if (isStatic()) { diff --git a/paddle/parameter/Parameter.h b/paddle/parameter/Parameter.h index 72c83367991..36d2b65f3bd 100644 --- a/paddle/parameter/Parameter.h +++ b/paddle/parameter/Parameter.h @@ -223,29 +223,6 @@ public: bool isValueUpdated() const { return updated_; } - /** - * Update bufs_[PARAMETER_VALUE] using bufs_[PARAMETER_GRADIENT] - */ - void updateWithGradient(real learningRate); - - /** - * Update bufs_[PARAMETER_VALUE] using sparse row grad matrix. - * - * @see SparseRowCpuMatrix::sgdUpdate for more information. - */ - void updateWithGradient(real learningRate, - MatrixPtr gradMat, - IVectorPtr t0, - int currentTime, - bool fini = false); - - /** - * This function is used to calculate multiple gpus, but only as a candidate - */ - void updateWithGradient(real learningRate, - VectorPtr grad, - bool normalUpdate = true); - /** * Save parameter value to a file */ -- GitLab From d7d2a4f7004d7fd64bece1a0f74cdd966bb7e8e0 Mon Sep 17 00:00:00 2001 From: Liu Yiqun Date: Fri, 28 Apr 2017 06:35:31 +0000 Subject: [PATCH 0140/3256] Add cross-compiling toolchain for Android. --- CMakeLists.txt | 8 +- cmake/cross_compiling/android.cmake | 171 ++++++++++++++++++++++++++++ cmake/system.cmake | 24 +++- 3 files changed, 194 insertions(+), 9 deletions(-) create mode 100644 cmake/cross_compiling/android.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 624083b2cee..abd07297cb4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,17 +12,13 @@ # See the License for the specific language governing permissions and # limitations under the License +cmake_minimum_required(VERSION 3.0) + set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake") set(PROJ_ROOT ${CMAKE_SOURCE_DIR}) include(system) -if(ANDROID) - cmake_minimum_required(VERSION 3.7) -else() - cmake_minimum_required(VERSION 3.0) -endif() - project(paddle CXX C) find_package(Sphinx) diff --git a/cmake/cross_compiling/android.cmake b/cmake/cross_compiling/android.cmake new file mode 100644 index 00000000000..db5a753cd46 --- /dev/null +++ b/cmake/cross_compiling/android.cmake @@ -0,0 +1,171 @@ +# 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. + +# This is a toolchain file for cross-compiling for Android, and the +# configuration refers to the open-source resposity: +# https://github.com/taka-no-me/android-cmake +# Most of the variables are compatible with that used in +# https://developer.android.com/ndk/guides/cmake.html +# The supported are listed belows: +# +# ANDROID_STANDALONE_TOOLCHAIN +# ANDROID_ABI +# ANDROID_NATIVE_API_LEVEL +# ANDROID_PLATFORM +# ANDROID_ARM_MODE +# ANDROID_ARM_NEON +# +# For CMake >= 3.7.0, all the settings will be delivered to CMake system +# variables to let CMake do the cross-compiling configurations itself. +# More detail of cross-compiling settings +# https://cmake.org/cmake/help/v3.7/manual/cmake-toolchains.7.html + +IF(NOT ANDROID) + return() +ENDIF() + +# check the exist of android standalone toolchain +IF(NOT DEFINED ANDROID_STANDALONE_TOOLCHAIN) + SET(ANDROID_STANDALONE_TOOLCHAIN $ENV{ANDROID_STANDALONE_TOOLCHAIN} + CACHE PATH "Folder holds the standalone toolchain of Android NDK") +ENDIF() +IF(NOT ANDROID_STANDALONE_TOOLCHAIN) + MESSAGE(FATAL_ERROR "Set ANDROID_STANDALONE_TOOLCHAIN to use a standalone toolchain.\n" + "To cross-compile for Android, you need to:\n" + "1. Download an Android NDK from" + " https://developer.android.com/ndk/downloads/index.html\n" + "2. Setup a standalone toolchain" + " https://developer.android.google.cn/ndk/guides/standalone_toolchain.html?hl=zh-cn") +ENDIF() + +IF(DEFINED ANDROID_NATIVE_API_LEVEL) + IF(ANDROID_NATIVE_API_LEVEL MATCHES "^android-[0-9]+$") + STRING(REPLACE "android-" "" CMAKE_SYSTEM_VERSION "${CMAKE_MATCH_0}") + ELSEIF(ANDROID_NATIVE_API_LEVEL MATCHES "^[0-9]+$") + SET(CMAKE_SYSTEM_VERSION ${ANDROID_NATIVE_API_LEVEL}) + ENDIF() + SET(ANDROID_PLATFORM android-${CMAKE_SYSTEM_VERSION}) +ENDIF() + +IF(NOT DEFINED ANDROID_ABI) + SET(ANDROID_ABI "armeabi-v7a") +ENDIF() + +IF(NOT DEFINED ANDROID_ARM_MODE) + SET(ANDROID_ARM_MODE ON) +ENDIF() + +IF(NOT DEFINED ANDROID_ARM_NEON) + SET(ANDROID_ARM_NEON ON) +ENDIF() + +IF(NOT ANDROID_ARM_MODE OR ANDROID_ARM_MODE STREQUAL "thumb") + SET(ANDROID_ARM_MODE OFF) + SET(ANDROID_ARM_MODE_NAME "thumb") +ELSEIF(ANDROID_ARM_MODE OR ANDROID_ARM_MODE STREQUAL "arm") + SET(ANDROID_ARM_MODE ON) + SET(ANDROID_ARM_MODE_NAME "arm") +ENDIF() + +IF(${CMAKE_VERSION} VERSION_LESS "3.7.0") + IF(${CMAKE_VERSION} VERSION_LESS "3.1.0") + SET(CMAKE_SYSTEM_NAME "Linux") + ENDIF() + MESSAGE(WARNING "It is recommended to use CMake >= 3.7.0 (current version: " + "${CMAKE_VERSION}), when cross-compiling for Android.") + + SET(CMAKE_SYSROOT "${ANDROID_STANDALONE_TOOLCHAIN}/sysroot") + + IF(NOT CMAKE_SYSTEM_VERSION) + SET(ANDROID_STANDALONE_TOOLCHAIN_API "") + SET(ANDROID_API_LEVEL_H_REGEX "^[\t ]*#[\t ]*define[\t ]+__ANDROID_API__[\t ]+([0-9]+)") + FILE(STRINGS "${ANDROID_STANDALONE_TOOLCHAIN}/sysroot/usr/include/android/api-level.h" + ANDROID_API_LEVEL_H_CONTENT REGEX "${ANDROID_API_LEVEL_H_REGEX}") + IF(ANDROID_API_LEVEL_H_CONTENT MATCHES "${ANDROID_API_LEVEL_H_REGEX}") + SET(ANDROID_STANDALONE_TOOLCHAIN_API "${CMAKE_MATCH_1}") + ENDIF() + SET(CMAKE_SYSTEM_VERSION ${ANDROID_STANDALONE_TOOLCHAIN_API}) + ENDIF() + + # Toolchain + SET(ANDROID_TOOLCHAIN "gcc") + SET(ANDROID_TOOLCHAIN_ROOT ${ANDROID_STANDALONE_TOOLCHAIN}) + IF(ANDROID_ABI MATCHES "^armeabi(-v7a)?$") + SET(ANDROID_TOOLCHAIN_NAME arm-linux-androideabi) + IF(ANDROID_ABI STREQUAL "armeabi") + SET(CMAKE_SYSTEM_PROCESSOR armv5te) + ELSEIF(ANDROID_ABI STREQUAL "armeabi-v7a") + SET(CMAKE_SYSTEM_PROCESSOR armv7-a) + ENDIF() + ENDIF() + SET(ANDROID_TOOLCHAIN_PREFIX "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_NAME}-") + + SET(CMAKE_C_COMPILER "${ANDROID_TOOLCHAIN_PREFIX}gcc") + SET(CMAKE_CXX_COMPILER "${ANDROID_TOOLCHAIN_PREFIX}g++") + + # Toolchain and ABI specific flags. + SET(ANDROID_COMPILER_FLAGS "-ffunction-sections -fdata-sections -finline-limit=64") + SET(ANDROID_LINKER_FLAGS "-Wl,--gc-sections") + + IF(ANDROID_ABI STREQUAL "armeabi") + LIST(APPEND ANDROID_COMPILER_FLAGS + -march=armv5te + -mtune=xscale + -msoft-float) + ENDIF() + IF(ANDROID_ABI STREQUAL "armeabi-v7a") + LIST(APPEND ANDROID_COMPILER_FLAGS + -march=armv7-a + -mfloat-abi=softfp) + IF(ANDROID_ARM_NEON) + LIST(APPEND ANDROID_COMPILER_FLAGS -mfpu=neon) + ELSE() + LIST(APPEND ANDROID_COMPILER_FLAGS -mfpu=vfpv3-d16) + ENDIF() + LIST(APPEND ANDROID_LINKER_FLAGS -Wl,--fix-cortex-a8) + ENDIF() + + IF(ANDROID_ABI MATCHES "^armeabi(-v7a)?$") + IF(ANDROID_ARM_MODE) + LIST(APPEND ANDROID_COMPILER_FLAGS -marm) + ELSE() + LIST(APPEND ANDROID_COMPILER_FLAGS -mthumb) + ENDIF() + ENDIF() + + STRING(REPLACE ";" " " ANDROID_COMPILER_FLAGS "${ANDROID_COMPILER_FLAGS}") + STRING(REPLACE ";" " " ANDROID_LINKER_FLAGS "${ANDROID_LINKER_FLAGS}") + + SET(CMAKE_C_FLAGS "${ANDROID_COMPILER_FLAGS} ${CMAKE_C_FLAGS}" + CACHE STRING "c flags") + SET(CMAKE_CXX_FLAGS "${ANDROID_COMPILER_FLAGS} ${CMAKE_CXX_FLAGS}" + CACHE STRING "c++ flags") + SET(CMAKE_SHARED_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} ${CMAKE_SHARED_LINKER_FLAGS}" + CACHE STRING "shared linker flags") + + SET(CMAKE_POSITION_INDEPENDENT_CODE TRUE) + SET(CMAKE_EXE_LINKER_FLAGS "-pie -fPIE ${ANDROID_LINKER_FLAGS} ${CMAKE_EXE_LINKER_FLAGS}" + CACHE STRING "executable linker flags") + + MESSAGE(STATUS "Android: Targeting API '${CMAKE_SYSTEM_VERSION}' " + "with architecture '${ANDROID_ARM_MODE_NAME}', " + "ABI '${ANDROID_ABI}', and processor '${CMAKE_SYSTEM_PROCESSOR}'") + MESSAGE(STATUS "System CMAKE_C_FLAGS: " ${CMAKE_C_FLAGS}) + MESSAGE(STATUS "System CMAKE_CXX_FLAGS: " ${CMAKE_CXX_FLAGS}) +ELSE() + SET(CMAKE_ANDROID_STANDALONE_TOOLCHAIN ${ANDROID_STANDALONE_TOOLCHAIN}) + SET(CMAKE_ANDROID_ARCH_ABI ${ANDROID_ABI}) + SET(CMAKE_ANDROID_ARM_MODE ${ANDROID_ARM_MODE}) + SET(CMAKE_ANDROID_ARM_NEON ${ANDROID_ARM_NEON}) +ENDIF() diff --git a/cmake/system.cmake b/cmake/system.cmake index 75a9d8fc256..b34b7436622 100644 --- a/cmake/system.cmake +++ b/cmake/system.cmake @@ -13,9 +13,9 @@ # limitations under the License. # Detects the OS and sets appropriate variables. -# CMAKE_SYSTEM_NAME only give us a coarse-grained name, -# but the name like centos is necessary in some scenes -# to distinguish system for customization. +# CMAKE_SYSTEM_NAME only give us a coarse-grained name of the OS CMake is +# building for, but the host processor name like centos is necessary +# in some scenes to distinguish system for customization. # # for instance, protobuf libs path is /lib64 # on CentOS, but /lib on other systems. @@ -72,12 +72,30 @@ MARK_AS_ADVANCED(HOST_SYSTEM CPU_CORES) MESSAGE(STATUS "Found Paddle host system: ${HOST_SYSTEM}") MESSAGE(STATUS "Found Paddle host system's CPU: ${CPU_CORES} cores") +# configuration for cross-compiling IF(DEFINED CMAKE_SYSTEM_NAME) IF(${CMAKE_SYSTEM_NAME} STREQUAL "Android") SET(ANDROID TRUE) + INCLUDE(cross_compiling/android) ENDIF() ENDIF() +# suffix on different os +IF(WIN32) + SET(SHARED_LIBRARY_SUFFIX ".dll") + SET(STATIC_LIBRARY_SUFFIX ".lib") + SET(EXECUTABLE_SUFFIX ".exe") +ELSE(WIN32) + IF(APPLE) + SET(SHARED_LIBRARY_SUFFIX ".dylib") + ELSE(APPLE) + SET(SHARED_LIBRARY_SUFFIX ".so") + ENDIF(APPLE) + + SET(STATIC_LIBRARY_SUFFIX ".a") + SET(EXECUTABLE_SUFFIX "") +ENDIF(WIN32) + # external dependencies log output SET(EXTERNAL_PROJECT_LOG_ARGS LOG_DOWNLOAD 0 # Wrap download in script to log output -- GitLab From 4725b1c0cbbe08f7055c1ca083d6aff42ae53fa2 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Fri, 28 Apr 2017 14:57:36 +0800 Subject: [PATCH 0141/3256] Fix compile --- paddle/gserver/tests/test_RecurrentGradientMachine.cpp | 1 + paddle/parameter/Argument.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/paddle/gserver/tests/test_RecurrentGradientMachine.cpp b/paddle/gserver/tests/test_RecurrentGradientMachine.cpp index 150850da4d4..4a846397e6c 100644 --- a/paddle/gserver/tests/test_RecurrentGradientMachine.cpp +++ b/paddle/gserver/tests/test_RecurrentGradientMachine.cpp @@ -14,6 +14,7 @@ limitations under the License. */ #include #include +#include #include #include #include diff --git a/paddle/parameter/Argument.cpp b/paddle/parameter/Argument.cpp index 645bf737990..6d9365af2d1 100644 --- a/paddle/parameter/Argument.cpp +++ b/paddle/parameter/Argument.cpp @@ -570,7 +570,7 @@ void Argument::poolSequenceWithStride(const Argument& input, CHECK(input.sequenceStartPositions); CHECK_EQ(input.hasSubseq(), 0UL); - CHECK_GT(stride, 0) << "stride must larger than 0"; + CHECK_GT(stride, 0UL) << "stride must larger than 0"; size_t numSequences = input.getNumSequences(); ICpuGpuVector::resizeOrCreate( sequenceStartPositions, numSequences + 1, false); -- GitLab From c6090035cee2d38fddc01f08ddf73a788b9c6cff Mon Sep 17 00:00:00 2001 From: Liu Yiqun Date: Fri, 28 Apr 2017 06:36:31 +0000 Subject: [PATCH 0142/3256] Support the auto-compiling for host protoc when cross-compiling. --- cmake/cross_compiling/android.cmake | 19 ++++- cmake/external/protobuf.cmake | 105 ++++++++++++++++++---------- 2 files changed, 83 insertions(+), 41 deletions(-) diff --git a/cmake/cross_compiling/android.cmake b/cmake/cross_compiling/android.cmake index db5a753cd46..4dd81bf29be 100644 --- a/cmake/cross_compiling/android.cmake +++ b/cmake/cross_compiling/android.cmake @@ -110,9 +110,22 @@ IF(${CMAKE_VERSION} VERSION_LESS "3.7.0") ENDIF() ENDIF() SET(ANDROID_TOOLCHAIN_PREFIX "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_NAME}-") - - SET(CMAKE_C_COMPILER "${ANDROID_TOOLCHAIN_PREFIX}gcc") - SET(CMAKE_CXX_COMPILER "${ANDROID_TOOLCHAIN_PREFIX}g++") + + IF(EXISTS "${ANDROID_TOOLCHAIN_PREFIX}gcc") + SET(CMAKE_C_COMPILER "${ANDROID_TOOLCHAIN_PREFIX}gcc") + ELSE() + MESSAGE(FATAL_ERROR "Cannot find C compiler: ${ANDROID_TOOLCHAIN_PREFIX}gcc") + ENDIF() + + IF(EXISTS "${ANDROID_TOOLCHAIN_PREFIX}g++") + SET(CMAKE_CXX_COMPILER "${ANDROID_TOOLCHAIN_PREFIX}g++") + ELSE() + MESSAGE(FATAL_ERROR "Cannot find CXX compiler: ${ANDROID_TOOLCHAIN_PREFIX}g++") + ENDIF() + + IF(EXISTS "${ANDROID_TOOLCHAIN_PREFIX}gfortran") + SET(CMAKE_Fortran_COMPILER "${ANDROID_TOOLCHAIN_PREFIX}gfortran") + ENDIF() # Toolchain and ABI specific flags. SET(ANDROID_COMPILER_FLAGS "-ffunction-sections -fdata-sections -finline-limit=64") diff --git a/cmake/external/protobuf.cmake b/cmake/external/protobuf.cmake index 2df042d226a..2fc142de259 100644 --- a/cmake/external/protobuf.cmake +++ b/cmake/external/protobuf.cmake @@ -14,42 +14,41 @@ INCLUDE(ExternalProject) -set(PROTOBUF_VERSION 3.1) -FIND_PACKAGE(Protobuf ${PROTOBUF_VERSION}) +FUNCTION(build_protobuf TARGET_NAME BUILD_FOR_HOST) + SET(PROTOBUF_SOURCES_DIR ${THIRD_PARTY_PATH}/${TARGET_NAME}) + SET(PROTOBUF_INSTALL_DIR ${THIRD_PARTY_PATH}/install/${TARGET_NAME}) -IF(PROTOBUF_FOUND) - EXEC_PROGRAM(${PROTOBUF_PROTOC_EXECUTABLE} ARGS --version OUTPUT_VARIABLE PROTOBUF_VERSION) - STRING(REGEX MATCH "[0-9]+.[0-9]+" PROTOBUF_VERSION "${PROTOBUF_VERSION}") - IF (${PROTOBUF_VERSION} VERSION_LESS "3.1.0") - SET(PROTOBUF_FOUND OFF) - ENDIF() -ENDIF(PROTOBUF_FOUND) - -IF(NOT PROTOBUF_FOUND) - SET(PROTOBUF_SOURCES_DIR ${THIRD_PARTY_PATH}/protobuf) - SET(PROTOBUF_INSTALL_DIR ${THIRD_PARTY_PATH}/install/protobuf) - SET(PROTOBUF_INCLUDE_DIR "${PROTOBUF_INSTALL_DIR}/include" CACHE PATH "protobuf include directory." FORCE) + SET(${TARGET_NAME}_INCLUDE_DIR "${PROTOBUF_INSTALL_DIR}/include" PARENT_SCOPE) + SET(${TARGET_NAME}_LITE_LIBRARY + "${PROTOBUF_INSTALL_DIR}/lib/libprotobuf-lite${STATIC_LIBRARY_SUFFIX}" + PARENT_SCOPE) + SET(${TARGET_NAME}_LIBRARY + "${PROTOBUF_INSTALL_DIR}/lib/libprotobuf${STATIC_LIBRARY_SUFFIX}" + PARENT_SCOPE) + SET(${TARGET_NAME}_PROTOC_LIBRARY + "${PROTOBUF_INSTALL_DIR}/lib/libprotoc${STATIC_LIBRARY_SUFFIX}" + PARENT_SCOPE) + SET(${TARGET_NAME}_PROTOC_EXECUTABLE + "${PROTOBUF_INSTALL_DIR}/bin/protoc${EXECUTABLE_SUFFIX}" + PARENT_SCOPE) - IF(WIN32) - SET(PROTOBUF_LITE_LIBRARY - "${PROTOBUF_INSTALL_DIR}/lib/libprotobuf-lite.lib" CACHE FILEPATH "protobuf lite library." FORCE) - SET(PROTOBUF_LIBRARY - "${PROTOBUF_INSTALL_DIR}/lib/libprotobuf.lib" CACHE FILEPATH "protobuf library." FORCE) - SET(PROTOBUF_PROTOC_LIBRARY - "${PROTOBUF_INSTALL_DIR}/lib/libprotoc.lib" CACHE FILEPATH "protoc library." FORCE) - SET(PROTOBUF_PROTOC_EXECUTABLE "${PROTOBUF_INSTALL_DIR}/bin/protoc.exe" CACHE FILEPATH "protobuf executable." FORCE) - ELSE(WIN32) - SET(PROTOBUF_LITE_LIBRARY - "${PROTOBUF_INSTALL_DIR}/lib/libprotobuf-lite.a" CACHE FILEPATH "protobuf lite library." FORCE) - SET(PROTOBUF_LIBRARY - "${PROTOBUF_INSTALL_DIR}/lib/libprotobuf.a" CACHE FILEPATH "protobuf library." FORCE) - SET(PROTOBUF_PROTOC_LIBRARY - "${PROTOBUF_INSTALL_DIR}/lib/libprotoc.a" CACHE FILEPATH "protoc library." FORCE) - SET(PROTOBUF_PROTOC_EXECUTABLE "${PROTOBUF_INSTALL_DIR}/bin/protoc" CACHE FILEPATH "protobuf executable." FORCE) - ENDIF(WIN32) + SET(OPTIONAL_CACHE_ARGS "") + SET(OPTIONAL_ARGS "") + IF(BUILD_FOR_HOST) + SET(OPTIONAL_ARGS "-Dprotobuf_WITH_ZLIB=OFF") + ELSE() + SET(OPTIONAL_ARGS + "-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}" + "-DCMAKE_C_COMPILER=${CMAKE_C_COMPILER}" + "-DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS}" + "-DCMAKE_C_FLAGS=${CMAKE_C_FLAGS}" + "-Dprotobuf_WITH_ZLIB=ON" + "-DZLIB_ROOT:FILEPATH=${ZLIB_ROOT}") + SET(OPTIONAL_CACHE_ARGS "-DZLIB_ROOT:STRING=${ZLIB_ROOT}") + ENDIF() ExternalProject_Add( - protobuf + ${TARGET_NAME} ${EXTERNAL_PROJECT_LOG_ARGS} PREFIX ${PROTOBUF_SOURCES_DIR} UPDATE_COMMAND "" @@ -57,11 +56,9 @@ IF(NOT PROTOBUF_FOUND) GIT_REPOSITORY "https://github.com/google/protobuf.git" GIT_TAG "9f75c5aa851cd877fb0d93ccc31b8567a6706546" CONFIGURE_COMMAND - ${CMAKE_COMMAND} ${PROTOBUF_SOURCES_DIR}/src/protobuf/cmake + ${CMAKE_COMMAND} ${PROTOBUF_SOURCES_DIR}/src/${TARGET_NAME}/cmake + ${OPTIONAL_ARGS} -Dprotobuf_BUILD_TESTS=OFF - -DZLIB_ROOT:FILEPATH=${ZLIB_ROOT} - -DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER} - -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=${PROTOBUF_INSTALL_DIR} @@ -71,10 +68,42 @@ IF(NOT PROTOBUF_FOUND) -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_VERBOSE_MAKEFILE:BOOL=OFF -DCMAKE_POSITION_INDEPENDENT_CODE:BOOL=ON - -DZLIB_ROOT:STRING=${ZLIB_ROOT} + ${OPTIONAL_CACHE_ARGS} ) - LIST(APPEND external_project_dependencies protobuf) + LIST(APPEND external_project_dependencies ${TARGET_NAME} PARENT_SCOPE) +ENDFUNCTION() + +IF(NOT CMAKE_CROSSCOMPILING) + SET(PROTOBUF_VERSION 3.1) + FIND_PACKAGE(Protobuf ${PROTOBUF_VERSION}) + + IF(PROTOBUF_FOUND) + EXEC_PROGRAM(${PROTOBUF_PROTOC_EXECUTABLE} ARGS --version OUTPUT_VARIABLE PROTOBUF_VERSION) + STRING(REGEX MATCH "[0-9]+.[0-9]+" PROTOBUF_VERSION "${PROTOBUF_VERSION}") + IF (${PROTOBUF_VERSION} VERSION_LESS "3.1.0") + SET(PROTOBUF_FOUND OFF) + ENDIF() + ENDIF(PROTOBUF_FOUND) +ELSE() + build_protobuf(protobuf_host TRUE) + SET(PROTOBUF_PROTOC_EXECUTABLE ${protobuf_host_PROTOC_EXECUTABLE} + CACHE FILEPATH "protobuf executable." FORCE) +ENDIF() + +IF(NOT PROTOBUF_FOUND) + build_protobuf(protobuf FALSE) + SET(PROTOBUF_INCLUDE_DIR ${protobuf_INCLUDE_DIR} + CACHE PATH "protobuf include directory." FORCE) + IF(NOT CMAKE_CROSSCOMPILING) + SET(PROTOBUF_PROTOC_EXECUTABLE ${protobuf_PROTOC_EXECUTABLE} + CACHE FILEPATH "protobuf executable." FORCE) + ENDIF() + SET(PROTOBUF_LITE_LIBRARY ${protobuf_LITE_LIBRARY} CACHE FILEPATH "protobuf lite library." FORCE) + SET(PROTOBUF_LIBRARY ${protobuf_LIBRARY} CACHE FILEPATH "protobuf library." FORCE) + SET(PROTOBUF_PROTOC_LIBRARY ${protobuf_PROTOC_LIBRARY} CACHE FILEPATH "protoc library." FORCE) ENDIF(NOT PROTOBUF_FOUND) +MESSAGE(STATUS "Protobuf protoc executable: ${PROTOBUF_PROTOC_EXECUTABLE}") +MESSAGE(STATUS "Protobuf library: ${PROTOBUF_LIBRARY}") INCLUDE_DIRECTORIES(${PROTOBUF_INCLUDE_DIR}) -- GitLab From 285daf91a5a2acd0153dc3e0d9aa30da8f47dc6d Mon Sep 17 00:00:00 2001 From: Liu Yiqun Date: Fri, 28 Apr 2017 10:01:16 +0000 Subject: [PATCH 0143/3256] Support the auto-compiling for openblas when cross-compiling for Android. --- cmake/external/openblas.cmake | 91 ++++++++++++++++++++--------------- cmake/system.cmake | 4 +- 2 files changed, 54 insertions(+), 41 deletions(-) diff --git a/cmake/external/openblas.cmake b/cmake/external/openblas.cmake index 92ea23c7633..c5d457d3e0a 100644 --- a/cmake/external/openblas.cmake +++ b/cmake/external/openblas.cmake @@ -21,65 +21,76 @@ IF(NOT ${CBLAS_FOUND}) SET(CBLAS_INSTALL_DIR ${THIRD_PARTY_PATH}/install/openblas) SET(CBLAS_INC_DIR "${CBLAS_INSTALL_DIR}/include" CACHE PATH "openblas include directory." FORCE) - IF(WIN32) - SET(CBLAS_LIBRARIES "${CBLAS_INSTALL_DIR}/lib/openblas.lib" CACHE FILEPATH "openblas library." FORCE) - ELSE(WIN32) - SET(CBLAS_LIBRARIES "${CBLAS_INSTALL_DIR}/lib/libopenblas.a" CACHE FILEPATH "openblas library" FORCE) - ENDIF(WIN32) + SET(CBLAS_LIBRARIES "${CBLAS_INSTALL_DIR}/lib/${LIBRARY_PREFIX}openblas${STATIC_LIBRARY_SUFFIX}" + CACHE FILEPATH "openblas library." FORCE) - IF(CMAKE_COMPILER_IS_GNUCC) - ENABLE_LANGUAGE(Fortran) - if (NOT CMAKE_Fortran_COMPILER_VERSION) - # cmake < 3.4 cannot get CMAKE_Fortran_COMPILER_VERSION directly. - execute_process(COMMAND ${CMAKE_Fortran_COMPILER} -dumpversion - OUTPUT_VARIABLE CMAKE_Fortran_COMPILER_VERSION) - endif() - string(REGEX MATCHALL "[0-9]+" Fortran_VERSION ${CMAKE_Fortran_COMPILER_VERSION}) - list(GET Fortran_VERSION 0 Fortran_MAJOR) - list(GET Fortran_VERSION 1 Fortran_MINOR) - find_library(GFORTRAN_LIBRARY NAMES gfortran PATHS - /lib - /usr/lib - /usr/lib/gcc/x86_64-linux-gnu/${Fortran_MAJOR}.${Fortran_MINOR}/ - /usr/lib/gcc/x86_64-linux-gnu/${Fortran_MAJOR}/) - if (NOT GFORTRAN_LIBRARY) - message(FATAL_ERROR "Cannot found gfortran library which it is used by openblas") - endif() - find_package(Threads REQUIRED) - LIST(APPEND CBLAS_LIBRARIES ${GFORTRAN_LIBRARY} ${CMAKE_THREAD_LIBS_INIT}) - ENDIF(CMAKE_COMPILER_IS_GNUCC) + # check fortran compiler and library + IF(NOT ANDROID) + IF(CMAKE_COMPILER_IS_GNUCC) + ENABLE_LANGUAGE(Fortran) + if (NOT CMAKE_Fortran_COMPILER_VERSION) + # cmake < 3.4 cannot get CMAKE_Fortran_COMPILER_VERSION directly. + execute_process(COMMAND ${CMAKE_Fortran_COMPILER} -dumpversion + OUTPUT_VARIABLE CMAKE_Fortran_COMPILER_VERSION) + endif() + string(REGEX MATCHALL "[0-9]+" Fortran_VERSION ${CMAKE_Fortran_COMPILER_VERSION}) + list(GET Fortran_VERSION 0 Fortran_MAJOR) + list(GET Fortran_VERSION 1 Fortran_MINOR) + find_library(GFORTRAN_LIBRARY NAMES gfortran PATHS + /lib + /usr/lib + /usr/lib/gcc/x86_64-linux-gnu/${Fortran_MAJOR}.${Fortran_MINOR}/ + /usr/lib/gcc/x86_64-linux-gnu/${Fortran_MAJOR}/) + if (NOT GFORTRAN_LIBRARY) + message(FATAL_ERROR "Cannot found gfortran library which it is used by openblas") + endif() + find_package(Threads REQUIRED) + LIST(APPEND CBLAS_LIBRARIES ${GFORTRAN_LIBRARY} ${CMAKE_THREAD_LIBS_INIT}) + ENDIF(CMAKE_COMPILER_IS_GNUCC) - IF(NOT CMAKE_Fortran_COMPILER) - MESSAGE(FATAL_ERROR "To build lapack in libopenblas, " - "you need to set gfortran compiler: cmake .. -DCMAKE_Fortran_COMPILER=...") - ENDIF(NOT CMAKE_Fortran_COMPILER) + IF(NOT CMAKE_Fortran_COMPILER) + MESSAGE(FATAL_ERROR "To build lapack in libopenblas, " + "you need to set gfortran compiler: cmake .. -DCMAKE_Fortran_COMPILER=...") + ENDIF(NOT CMAKE_Fortran_COMPILER) - ADD_DEFINITIONS(-DPADDLE_USE_LAPACK) + ADD_DEFINITIONS(-DPADDLE_USE_LAPACK) + + SET(OPENBLAS_COMMIT "v0.2.19") + SET(OPENBLAS_ARGS FC=${CMAKE_Fortran_COMPILER} DYNAMIC_ARCH=1 libs netlib) + SET(HOST_C_COMPILER ${CMAKE_C_COMPILER}) + ELSE() + SET(OPENBLAS_COMMIT "b5c96fcfcdc82945502a2303116a64d89985daf5") + SET(OPTIONAL_ARGS TARGET=ARMV7 ARM_SOFTFP_ABI=1 NOFORTRAN=1 USE_THREAD=0 libs) + SET(HOST_C_COMPILER gcc) + ENDIF() ExternalProject_Add( openblas ${EXTERNAL_PROJECT_LOG_ARGS} GIT_REPOSITORY https://github.com/xianyi/OpenBLAS.git - GIT_TAG v0.2.19 + GIT_TAG ${OPENBLAS_COMMIT} PREFIX ${CBLAS_SOURCES_DIR} INSTALL_DIR ${CBLAS_INSTALL_DIR} BUILD_IN_SOURCE 1 - BUILD_COMMAND ${CMAKE_MAKE_PROGRAM} FC=${CMAKE_Fortran_COMPILER} CC=${CMAKE_C_COMPILER} HOSTCC=${CMAKE_C_COMPILER} DYNAMIC_ARCH=1 NO_SHARED=1 libs netlib + BUILD_COMMAND ${CMAKE_MAKE_PROGRAM} CC=${CMAKE_C_COMPILER} HOSTCC=${HOST_C_COMPILER} NO_SHARED=1 ${OPTIONAL_ARGS} INSTALL_COMMAND ${CMAKE_MAKE_PROGRAM} install NO_SHARED=1 PREFIX= UPDATE_COMMAND "" CONFIGURE_COMMAND "" ) - ExternalProject_Add_Step( - openblas lapacke_install - COMMAND ${CMAKE_COMMAND} -E copy "${CBLAS_SOURCES_DIR}/src/openblas/lapack-netlib/LAPACKE/include/lapacke_mangling_with_flags.h" "${CBLAS_INSTALL_DIR}/include/lapacke_mangling.h" - COMMAND ${CMAKE_COMMAND} -E copy "${CBLAS_SOURCES_DIR}/src/openblas/lapack-netlib/LAPACKE/include/lapacke.h" "${CBLAS_INSTALL_DIR}/include/lapacke.h" - COMMAND ${CMAKE_COMMAND} -E copy "${CBLAS_SOURCES_DIR}/src/openblas/lapack-netlib/LAPACKE/include/lapacke_config.h" "${CBLAS_INSTALL_DIR}/include/lapacke_config.h" - COMMAND ${CMAKE_COMMAND} -E copy "${CBLAS_SOURCES_DIR}/src/openblas/lapack-netlib/LAPACKE/include/lapacke_utils.h" "${CBLAS_INSTALL_DIR}/include/lapacke_utils.h" - DEPENDEES install + IF(NOT ANDROID) + ExternalProject_Add_Step( + openblas lapacke_install + COMMAND ${CMAKE_COMMAND} -E copy "${CBLAS_SOURCES_DIR}/src/openblas/lapack-netlib/LAPACKE/include/lapacke_mangling_with_flags.h" "${CBLAS_INSTALL_DIR}/include/lapacke_mangling.h" + COMMAND ${CMAKE_COMMAND} -E copy "${CBLAS_SOURCES_DIR}/src/openblas/lapack-netlib/LAPACKE/include/lapacke.h" "${CBLAS_INSTALL_DIR}/include/lapacke.h" + COMMAND ${CMAKE_COMMAND} -E copy "${CBLAS_SOURCES_DIR}/src/openblas/lapack-netlib/LAPACKE/include/lapacke_config.h" "${CBLAS_INSTALL_DIR}/include/lapacke_config.h" + COMMAND ${CMAKE_COMMAND} -E copy "${CBLAS_SOURCES_DIR}/src/openblas/lapack-netlib/LAPACKE/include/lapacke_utils.h" "${CBLAS_INSTALL_DIR}/include/lapacke_utils.h" + DEPENDEES install ) + ENDIF() LIST(APPEND external_project_dependencies openblas) ENDIF(NOT ${CBLAS_FOUND}) +MESSAGE(STATUS "BLAS library: ${CBLAS_LIBRARIES}") INCLUDE_DIRECTORIES(${CBLAS_INC_DIR}) diff --git a/cmake/system.cmake b/cmake/system.cmake index b34b7436622..22f74b2e372 100644 --- a/cmake/system.cmake +++ b/cmake/system.cmake @@ -80,12 +80,14 @@ IF(DEFINED CMAKE_SYSTEM_NAME) ENDIF() ENDIF() -# suffix on different os +# prefix and suffix on different os IF(WIN32) + SET(LIBRARY_PREFIX "") SET(SHARED_LIBRARY_SUFFIX ".dll") SET(STATIC_LIBRARY_SUFFIX ".lib") SET(EXECUTABLE_SUFFIX ".exe") ELSE(WIN32) + SET(LIBRARY_PREFIX "lib") IF(APPLE) SET(SHARED_LIBRARY_SUFFIX ".dylib") ELSE(APPLE) -- GitLab From 39f9056c775d4a46d2a5785266517e659d1f24ee Mon Sep 17 00:00:00 2001 From: Liu Yiqun Date: Fri, 28 Apr 2017 11:17:12 +0000 Subject: [PATCH 0144/3256] Config HOST_C/CXX_COMPILER for cross-compiling. --- cmake/cross_compiling/android.cmake | 10 +++--- cmake/cross_compiling/host.cmake | 49 +++++++++++++++++++++++++++++ cmake/external/openblas.cmake | 6 ++-- cmake/system.cmake | 1 + 4 files changed, 57 insertions(+), 9 deletions(-) create mode 100644 cmake/cross_compiling/host.cmake diff --git a/cmake/cross_compiling/android.cmake b/cmake/cross_compiling/android.cmake index 4dd81bf29be..3b13fca5151 100644 --- a/cmake/cross_compiling/android.cmake +++ b/cmake/cross_compiling/android.cmake @@ -112,19 +112,19 @@ IF(${CMAKE_VERSION} VERSION_LESS "3.7.0") SET(ANDROID_TOOLCHAIN_PREFIX "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_NAME}-") IF(EXISTS "${ANDROID_TOOLCHAIN_PREFIX}gcc") - SET(CMAKE_C_COMPILER "${ANDROID_TOOLCHAIN_PREFIX}gcc") + SET(CMAKE_C_COMPILER "${ANDROID_TOOLCHAIN_PREFIX}gcc" CACHE PATH "C compiler") ELSE() - MESSAGE(FATAL_ERROR "Cannot find C compiler: ${ANDROID_TOOLCHAIN_PREFIX}gcc") + MESSAGE(FATAL_ERROR "Cannot found C compiler: ${ANDROID_TOOLCHAIN_PREFIX}gcc") ENDIF() IF(EXISTS "${ANDROID_TOOLCHAIN_PREFIX}g++") - SET(CMAKE_CXX_COMPILER "${ANDROID_TOOLCHAIN_PREFIX}g++") + SET(CMAKE_CXX_COMPILER "${ANDROID_TOOLCHAIN_PREFIX}g++" CACHE PATH "CXX compiler") ELSE() - MESSAGE(FATAL_ERROR "Cannot find CXX compiler: ${ANDROID_TOOLCHAIN_PREFIX}g++") + MESSAGE(FATAL_ERROR "Cannot found CXX compiler: ${ANDROID_TOOLCHAIN_PREFIX}g++") ENDIF() IF(EXISTS "${ANDROID_TOOLCHAIN_PREFIX}gfortran") - SET(CMAKE_Fortran_COMPILER "${ANDROID_TOOLCHAIN_PREFIX}gfortran") + SET(CMAKE_Fortran_COMPILER "${ANDROID_TOOLCHAIN_PREFIX}gfortran" CACHE PATH "Fortran compiler") ENDIF() # Toolchain and ABI specific flags. diff --git a/cmake/cross_compiling/host.cmake b/cmake/cross_compiling/host.cmake new file mode 100644 index 00000000000..62f3196b1ed --- /dev/null +++ b/cmake/cross_compiling/host.cmake @@ -0,0 +1,49 @@ +# 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. + +# find host C compiler +IF(HOST_C_COMPILER) + SET(HOST_C_COMPILER_NAME ${HOST_C_COMPILER}) +ELSEIF(NOT $ENV{CC} STREQUAL "") + SET(HOST_C_COMPILER_NAME $ENV{CC}) +ELSE() + SET(HOST_C_COMPILER_NAME cc) +ENDIF() + +GET_FILENAME_COMPONENT(HOST_C_COMPILER_PATH ${HOST_C_COMPILER_NAME} PROGRAM) +IF(NOT HOST_C_COMPILER_PATH OR NOT EXISTS ${HOST_C_COMPILER_PATH}) + MESSAGE(FATAL_ERROR "Cannot found host C compiler, set host C compiler:\n" + "\tcmake .. -DHOST_C_COMPILER=...") +ENDIF() + +# find host CXX compiler +IF(HOST_CXX_COMPILER) + SET(HOST_CXX_COMPILER_NAME ${HOST_CXX_COMPILER}) +ELSEIF(NOT $ENV{CXX} STREQUAL "") + SET(HOST_CXX_COMPILER_NAME $ENV{CXX}) +ELSE() + SET(HOST_CXX_COMPILER_NAME c++) +ENDIF() + +GET_FILENAME_COMPONENT(HOST_CXX_COMPILER_PATH ${HOST_CXX_COMPILER_NAME} PROGRAM) +IF(NOT HOST_CXX_COMPILER_PATH OR NOT EXISTS ${HOST_CXX_COMPILER_PATH}) + MESSAGE(FATAL_ERROR "Cannot found host CXX compiler, set host CXX compiler:\n" + "\tcmake .. -DHOST_CXX_COMPILER=...") +ENDIF() + +SET(HOST_C_COMPILER ${HOST_C_COMPILER_PATH} CACHE PATH "Host C compiler") +SET(HOST_CXX_COMPILER ${HOST_CXX_COMPILER_PATH} CACHE PATH "Host CXX compiler") + +MESSAGE(STATUS "Found host C compiler: " ${HOST_C_COMPILER}) +MESSAGE(STATUS "Found host CXX compiler: " ${HOST_CXX_COMPILER}) diff --git a/cmake/external/openblas.cmake b/cmake/external/openblas.cmake index c5d457d3e0a..ece0f0d4445 100644 --- a/cmake/external/openblas.cmake +++ b/cmake/external/openblas.cmake @@ -57,11 +57,9 @@ IF(NOT ${CBLAS_FOUND}) SET(OPENBLAS_COMMIT "v0.2.19") SET(OPENBLAS_ARGS FC=${CMAKE_Fortran_COMPILER} DYNAMIC_ARCH=1 libs netlib) - SET(HOST_C_COMPILER ${CMAKE_C_COMPILER}) ELSE() SET(OPENBLAS_COMMIT "b5c96fcfcdc82945502a2303116a64d89985daf5") - SET(OPTIONAL_ARGS TARGET=ARMV7 ARM_SOFTFP_ABI=1 NOFORTRAN=1 USE_THREAD=0 libs) - SET(HOST_C_COMPILER gcc) + SET(OPTIONAL_ARGS HOSTCC=${HOST_C_COMPILER} TARGET=ARMV7 ARM_SOFTFP_ABI=1 NOFORTRAN=1 USE_THREAD=0 libs) ENDIF() ExternalProject_Add( @@ -72,7 +70,7 @@ IF(NOT ${CBLAS_FOUND}) PREFIX ${CBLAS_SOURCES_DIR} INSTALL_DIR ${CBLAS_INSTALL_DIR} BUILD_IN_SOURCE 1 - BUILD_COMMAND ${CMAKE_MAKE_PROGRAM} CC=${CMAKE_C_COMPILER} HOSTCC=${HOST_C_COMPILER} NO_SHARED=1 ${OPTIONAL_ARGS} + BUILD_COMMAND ${CMAKE_MAKE_PROGRAM} CC=${CMAKE_C_COMPILER} NO_SHARED=1 ${OPTIONAL_ARGS} INSTALL_COMMAND ${CMAKE_MAKE_PROGRAM} install NO_SHARED=1 PREFIX= UPDATE_COMMAND "" CONFIGURE_COMMAND "" diff --git a/cmake/system.cmake b/cmake/system.cmake index 22f74b2e372..6c381458d9b 100644 --- a/cmake/system.cmake +++ b/cmake/system.cmake @@ -74,6 +74,7 @@ MESSAGE(STATUS "Found Paddle host system's CPU: ${CPU_CORES} cores") # configuration for cross-compiling IF(DEFINED CMAKE_SYSTEM_NAME) + INCLUDE(cross_compiling/host) IF(${CMAKE_SYSTEM_NAME} STREQUAL "Android") SET(ANDROID TRUE) INCLUDE(cross_compiling/android) -- GitLab From e4af8ef4e89eed055832de7cdac64ddb79d73fc4 Mon Sep 17 00:00:00 2001 From: xuwei06 Date: Fri, 28 Apr 2017 11:49:04 -0700 Subject: [PATCH 0145/3256] Fix protobuf.cmake to handle empty PROTOBUF_VERSION correctly Sometime FIND_PACKAGE(ProtoBuf) get an empty PROTOBUF_VERSION. In this case, ${PROTOBUF_VERSION} needs to be quoted. --- cmake/external/protobuf.cmake | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cmake/external/protobuf.cmake b/cmake/external/protobuf.cmake index 2df042d226a..a9db4e8ba41 100644 --- a/cmake/external/protobuf.cmake +++ b/cmake/external/protobuf.cmake @@ -1,11 +1,11 @@ # 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. @@ -20,7 +20,7 @@ FIND_PACKAGE(Protobuf ${PROTOBUF_VERSION}) IF(PROTOBUF_FOUND) EXEC_PROGRAM(${PROTOBUF_PROTOC_EXECUTABLE} ARGS --version OUTPUT_VARIABLE PROTOBUF_VERSION) STRING(REGEX MATCH "[0-9]+.[0-9]+" PROTOBUF_VERSION "${PROTOBUF_VERSION}") - IF (${PROTOBUF_VERSION} VERSION_LESS "3.1.0") + IF ("${PROTOBUF_VERSION}" VERSION_LESS "3.1.0") SET(PROTOBUF_FOUND OFF) ENDIF() ENDIF(PROTOBUF_FOUND) -- GitLab From b23a23c9dbe1683f53a5daf73b7298f82801a3ae Mon Sep 17 00:00:00 2001 From: zhanghaichao Date: Fri, 28 Apr 2017 17:29:46 -0700 Subject: [PATCH 0146/3256] fixed error in beam_search example and documents --- .../paddle/trainer_config_helpers/layers.py | 47 +++++++++++-------- 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/python/paddle/trainer_config_helpers/layers.py b/python/paddle/trainer_config_helpers/layers.py index b9e3d264042..46fd752d527 100755 --- a/python/paddle/trainer_config_helpers/layers.py +++ b/python/paddle/trainer_config_helpers/layers.py @@ -1349,9 +1349,9 @@ def last_seq(input, """ Get Last Timestamp Activation of a sequence. - If stride > 0, this layer slides a window whose size is determined by stride, - and return the last value of the window as the output. Thus, a long sequence - will be shorten. Note that for sequence with sub-sequence, the default value + If stride > 0, this layer slides a window whose size is determined by stride, + and return the last value of the window as the output. Thus, a long sequence + will be shorten. Note that for sequence with sub-sequence, the default value of stride is -1. The simple usage is: @@ -1365,7 +1365,7 @@ def last_seq(input, :type name: basestring :param input: Input layer name. :type input: LayerOutput - :param stride: window size. + :param stride: window size. :type stride: Int :param layer_attr: extra layer attributes. :type layer_attr: ExtraLayerAttribute. @@ -1405,9 +1405,9 @@ def first_seq(input, """ Get First Timestamp Activation of a sequence. - If stride > 0, this layer slides a window whose size is determined by stride, - and return the first value of the window as the output. Thus, a long sequence - will be shorten. Note that for sequence with sub-sequence, the default value + If stride > 0, this layer slides a window whose size is determined by stride, + and return the first value of the window as the output. Thus, a long sequence + will be shorten. Note that for sequence with sub-sequence, the default value of stride is -1. The simple usage is: @@ -1421,7 +1421,7 @@ def first_seq(input, :type name: basestring :param input: Input layer name. :type input: LayerOutput - :param stride: window size. + :param stride: window size. :type stride: Int :param layer_attr: extra layer attributes. :type layer_attr: ExtraLayerAttribute. @@ -1561,7 +1561,7 @@ def seq_reshape_layer(input, bias_attr=None): """ A layer for reshaping the sequence. Assume the input sequence has T instances, - the dimension of each instance is M, and the input reshape_size is N, then the + the dimension of each instance is M, and the input reshape_size is N, then the output sequence has T*M/N instances, the dimension of each instance is N. Note that T*M/N must be an integer. @@ -2118,8 +2118,8 @@ def img_conv_layer(input, :param trans: true if it is a convTransLayer, false if it is a convLayer :type trans: bool :param layer_type: specify the layer_type, default is None. If trans=True, - layer_type has to be "exconvt" or "cudnn_convt", - otherwise layer_type has to be either "exconv" or + layer_type has to be "exconvt" or "cudnn_convt", + otherwise layer_type has to be either "exconv" or "cudnn_conv" :type layer_type: String :return: LayerOutput object. @@ -2337,9 +2337,9 @@ def spp_layer(input, .. code-block:: python - spp = spp_layer(input=data, - pyramid_height=2, - num_channels=16, + spp = spp_layer(input=data, + pyramid_height=2, + num_channels=16, pool_type=MaxPooling()) :param name: layer name. @@ -2433,7 +2433,7 @@ def img_cmrnorm_layer(input, The example usage is: .. code-block:: python - + norm = img_cmrnorm_layer(input=net, size=5) :param name: layer name. @@ -2494,7 +2494,7 @@ def batch_norm_layer(input, The example usage is: .. code-block:: python - + norm = batch_norm_layer(input=net, act=ReluActivation()) :param name: layer name. @@ -2795,11 +2795,11 @@ def seq_concat_layer(a, b, act=None, name=None, layer_attr=None, """ Concat sequence a with sequence b. - Inputs: + Inputs: - a = [a1, a2, ..., an] - b = [b1, b2, ..., bn] - Note that the length of a and b should be the same. - + Output: [a1, b1, a2, b2, ..., an, bn] The example usage is: @@ -3563,9 +3563,15 @@ def beam_search(step, simple_rnn += last_time_step_output return simple_rnn + generated_word_embedding = GeneratedInput( + size=target_dictionary_dim, + embedding_name="target_language_embedding", + embedding_size=word_vector_dim) + beam_gen = beam_search(name="decoder", step=rnn_step, - input=[StaticInput(encoder_last)], + input=[StaticInput(encoder_last), + generated_word_embedding], bos_id=0, eos_id=1, beam_size=5) @@ -3584,7 +3590,8 @@ def beam_search(step, You can refer to the first parameter of recurrent_group, or demo/seqToseq/seqToseq_net.py for more details. :type step: callable - :param input: Input data for the recurrent unit + :param input: Input data for the recurrent unit, which should include the + previously generated words as a GeneratedInput object. :type input: list :param bos_id: Index of the start symbol in the dictionary. The start symbol is a special token for NLP task, which indicates the -- GitLab From 9572e11d73ede8040d6495f8e588b61fb9ea20ed Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Mon, 1 May 2017 18:27:28 -0700 Subject: [PATCH 0147/3256] Design doc: master process --- doc/design/cluster_train/data_dispatch.md | 6 +- doc/design/cluster_train/master_process.md | 89 +++++++++++++++++++ doc/design/cluster_train/src/dataset.graffle | Bin 0 -> 2770 bytes doc/design/cluster_train/src/dataset.png | Bin 0 -> 10845 bytes 4 files changed, 92 insertions(+), 3 deletions(-) create mode 100644 doc/design/cluster_train/master_process.md create mode 100644 doc/design/cluster_train/src/dataset.graffle create mode 100644 doc/design/cluster_train/src/dataset.png diff --git a/doc/design/cluster_train/data_dispatch.md b/doc/design/cluster_train/data_dispatch.md index a3eb4e28db0..f60c3b843d2 100644 --- a/doc/design/cluster_train/data_dispatch.md +++ b/doc/design/cluster_train/data_dispatch.md @@ -21,7 +21,7 @@ ### 文件预处理 -在数据集可以被训练之前,文件需要预先被转换成PaddlePaddle集群内部的存储格式(SSTable)。我们提供两个转换方式: +在数据集可以被训练之前,文件需要预先被转换成PaddlePaddle集群内部的存储格式(RecordIO)。我们提供两个转换方式: - 提供给用户本地转换的库,用户可以编写程序完成转换。 - 用户可以上传自己的数据集,在集群运行MapReduce job完成转换。 @@ -92,11 +92,11 @@ random_images-00099-of-00099 #### 进行训练 -PaddlePaddle提供专用的[data reader creator](https://github.com/PaddlePaddle/Paddle/blob/develop/doc/design/reader/README.md#python-data-reader-design-doc),生成给定SSTable文件对应的data reader。**无论在本地还是在云端,reader的使用方式都是一致的**: +PaddlePaddle提供专用的[data reader creator](https://github.com/PaddlePaddle/Paddle/blob/develop/doc/design/reader/README.md#python-data-reader-design-doc),生成给定RecordIO文件对应的data reader。**无论在本地还是在云端,reader的使用方式都是一致的**: ```python # ... -reader = paddle.reader.creator.SSTable("/home/random_images-*-of-*") +reader = paddle.reader.creator.RecordIO("/home/random_images-*-of-*") batch_reader = paddle.batch(paddle.dataset.mnist.train(), 128) trainer.train(batch_reader, ...) ``` diff --git a/doc/design/cluster_train/master_process.md b/doc/design/cluster_train/master_process.md new file mode 100644 index 00000000000..949811b4f76 --- /dev/null +++ b/doc/design/cluster_train/master_process.md @@ -0,0 +1,89 @@ +# Design Doc: Master Process + +For an overview of master process' role, please refer to [distributed training design doc](./README.md). In this design doc we will discuss the master process in more details. The master will be implemented in [golang](https://golang.org/). + +## Dataset + + + +A dataset is represented by a list of files in *RecordIO* format on the distributed filesystem, each RecordIO file consists of multiple *blocks*, and each block has multiple data instances. + +## Task Queue + +As mentioned in [distributed training design doc](./README.md), a *task* is a data shard that the master process assigns to the trainer process to train on. A task consists of one or multiple *blocks* from one or multiple files. The master process maintains *task queues* to track the training progress. + +### Task Queue Creation + +1. Each trainer will make an RPC call (using [golang rpc](https://golang.org/pkg/net/rpc/)) to the master process, telling it the RecordIO files representing the dataset specified by the user. Since every trainer will tell the master process the same dataset, only the first RPC call will be honored. + + The RPC interface is: + ```go + func (m *RPCServer) ReportDataset(Paths []string, dummy *int) error { + } + ``` +1. The master process will scan through each RecordIO file to generate the *block index* and know how many blocks does each file have. A block can be referenced by the file path and the index of the block within the file. The block index is in memory data structure that enables fast access to each block, and the index of the block with the file is an integer start from 0, representing the n-th block within the file. + + The definition of the block is: + ```go + type Block struct { + Idx int // index of the block within the file + Path string + Index recordio.Index // block index + } + ``` +1. Blocks are grouped into tasks, and tasks are filled into the todo queue. The pending queue and the done queue are initialized with no element. + + The definition of the task is: + ```go + type Task struct { + Index int + Blocks []Block + } + ``` + + The elements in the tasks queues is of type `TaskEntry`, containing a timeout counter (described in [task retry logic](#task-retry-logic)), and a task: + ```go + type TaskEntry struct { + NumTimeout int + Task Task + } + ``` + + The definition of task queues is: + ```go + type TaskQueues struct { + Todo []TaskEntry + Pending map[int]TaskEntry // map from task index to task entry + Done []TaskEntry + } + ``` + +### Task Queue Persistence + +The task queues need to be persisted on [etcd](https://github.com/coreos/etcd) for fault recovery. Since the task queues only change once a task is completed or timed out, which is not very frequent, we can afford to synchronize with etcd every time the task queues change. + +We will serialize the task queues data structure with [gob encoding](https://golang.org/pkg/encoding/gob/), compress with gzip, and save into etcd synchronously under key `/task_queues`. + +### Task Dispatch + +The trainer will make an RPC call to master to get a new task when: + +- the trainer first started, or +- the trainer finishes a task. + +The RPC interface is: +```go +func (m *RPCServer) GetTask(finished *Task, result *Task) error { +} +``` +Argument `finished` will be `nil` when the trainer is just started. + +During the RPC call the master will do the following: + +- Make a copy of the task queues, and update the copy reflecting the finished tasks and the new pending tasks. +- Synchronize the copy of task queues with etcd using a transaction conditioned on holding the master lock. +- Replace the task queues with the copy and report to the trainer with the new tasks if succeeded, or discard the copy and report the error to the trainer if failed. + +### Task Retry Logic + +When a task is dispatched to the trainer, the master will schedule a function for execution after the timeout duration (based on the moving average of task completion time). If the task entry in still in the pending queue, its timeout counter will increase by one, and the task will be moved to todo queue. If the timeout counter is above the threshold, the master will log the error and discard the task. diff --git a/doc/design/cluster_train/src/dataset.graffle b/doc/design/cluster_train/src/dataset.graffle new file mode 100644 index 0000000000000000000000000000000000000000..c10a423ed16a23229a9ee33d11bfc82bb59646c8 GIT binary patch literal 2770 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 YAREP)V|R4QE?%Ad7qtPw^$k)00GL^19RL6T literal 0 HcmV?d00001 diff --git a/doc/design/cluster_train/src/dataset.png b/doc/design/cluster_train/src/dataset.png new file mode 100644 index 0000000000000000000000000000000000000000..2fb7f1cce3b6dd21489392557826e95a9f207c34 GIT binary patch literal 10845 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 FzX23JaCHCx literal 0 HcmV?d00001 -- GitLab From 5b8fe87faf2a73c27ac85905fa995a9db0da2b71 Mon Sep 17 00:00:00 2001 From: liaogang Date: Tue, 2 May 2017 13:51:22 +0800 Subject: [PATCH 0148/3256] dlopen lapacke api and remove gfotran --- cmake/external/openblas.cmake | 29 +-------- paddle/cuda/CMakeLists.txt | 6 +- paddle/cuda/src/hl_cuda_cublas.cc | 2 +- paddle/cuda/src/hl_cuda_cudnn.cc | 2 +- paddle/cuda/src/hl_cuda_device.cc | 28 ++++----- paddle/cuda/src/hl_warpctc_wrap.cc | 2 +- paddle/math/MathFunctions.cpp | 59 ++++++++++++++++--- .../hl_dso_loader.cc => utils/DynamicLoad.cc} | 14 ++++- .../hl_dso_loader.h => utils/DynamicLoad.h} | 15 +++-- 9 files changed, 93 insertions(+), 64 deletions(-) rename paddle/{cuda/src/hl_dso_loader.cc => utils/DynamicLoad.cc} (94%) rename paddle/{cuda/include/hl_dso_loader.h => utils/DynamicLoad.h} (84%) diff --git a/cmake/external/openblas.cmake b/cmake/external/openblas.cmake index 92ea23c7633..317a1a92043 100644 --- a/cmake/external/openblas.cmake +++ b/cmake/external/openblas.cmake @@ -27,33 +27,6 @@ IF(NOT ${CBLAS_FOUND}) SET(CBLAS_LIBRARIES "${CBLAS_INSTALL_DIR}/lib/libopenblas.a" CACHE FILEPATH "openblas library" FORCE) ENDIF(WIN32) - IF(CMAKE_COMPILER_IS_GNUCC) - ENABLE_LANGUAGE(Fortran) - if (NOT CMAKE_Fortran_COMPILER_VERSION) - # cmake < 3.4 cannot get CMAKE_Fortran_COMPILER_VERSION directly. - execute_process(COMMAND ${CMAKE_Fortran_COMPILER} -dumpversion - OUTPUT_VARIABLE CMAKE_Fortran_COMPILER_VERSION) - endif() - string(REGEX MATCHALL "[0-9]+" Fortran_VERSION ${CMAKE_Fortran_COMPILER_VERSION}) - list(GET Fortran_VERSION 0 Fortran_MAJOR) - list(GET Fortran_VERSION 1 Fortran_MINOR) - find_library(GFORTRAN_LIBRARY NAMES gfortran PATHS - /lib - /usr/lib - /usr/lib/gcc/x86_64-linux-gnu/${Fortran_MAJOR}.${Fortran_MINOR}/ - /usr/lib/gcc/x86_64-linux-gnu/${Fortran_MAJOR}/) - if (NOT GFORTRAN_LIBRARY) - message(FATAL_ERROR "Cannot found gfortran library which it is used by openblas") - endif() - find_package(Threads REQUIRED) - LIST(APPEND CBLAS_LIBRARIES ${GFORTRAN_LIBRARY} ${CMAKE_THREAD_LIBS_INIT}) - ENDIF(CMAKE_COMPILER_IS_GNUCC) - - IF(NOT CMAKE_Fortran_COMPILER) - MESSAGE(FATAL_ERROR "To build lapack in libopenblas, " - "you need to set gfortran compiler: cmake .. -DCMAKE_Fortran_COMPILER=...") - ENDIF(NOT CMAKE_Fortran_COMPILER) - ADD_DEFINITIONS(-DPADDLE_USE_LAPACK) ExternalProject_Add( @@ -64,7 +37,7 @@ IF(NOT ${CBLAS_FOUND}) PREFIX ${CBLAS_SOURCES_DIR} INSTALL_DIR ${CBLAS_INSTALL_DIR} BUILD_IN_SOURCE 1 - BUILD_COMMAND ${CMAKE_MAKE_PROGRAM} FC=${CMAKE_Fortran_COMPILER} CC=${CMAKE_C_COMPILER} HOSTCC=${CMAKE_C_COMPILER} DYNAMIC_ARCH=1 NO_SHARED=1 libs netlib + BUILD_COMMAND ${CMAKE_MAKE_PROGRAM} FC=${CMAKE_Fortran_COMPILER} CC=${CMAKE_C_COMPILER} HOSTCC=${CMAKE_C_COMPILER} NO_LAPACK=1 DYNAMIC_ARCH=1 NO_SHARED=1 libs netlib INSTALL_COMMAND ${CMAKE_MAKE_PROGRAM} install NO_SHARED=1 PREFIX= UPDATE_COMMAND "" CONFIGURE_COMMAND "" diff --git a/paddle/cuda/CMakeLists.txt b/paddle/cuda/CMakeLists.txt index a28ccd6f07c..f9061e96deb 100755 --- a/paddle/cuda/CMakeLists.txt +++ b/paddle/cuda/CMakeLists.txt @@ -21,16 +21,13 @@ set(CUDA_CXX_WITH_GPU_SOURCES if(WITH_GPU) set(CUDA_CXX_SOURCES - src/hl_dso_loader.cc src/hl_warpctc_wrap.cc ${CUDA_CXX_WITH_GPU_SOURCES}) set_source_files_properties(${CUDA_CXX_SOURCES} PROPERTIES COMPILE_FLAGS "-D__NVCC__") else() - set(CUDA_CXX_SOURCES - src/hl_dso_loader.cc - src/hl_warpctc_wrap.cc) + set(CUDA_CXX_SOURCES src/hl_warpctc_wrap.cc) endif() set(CUDA_CU_SOURCES @@ -47,7 +44,6 @@ set(CUDA_CU_SOURCES set(CUDA_HEADERS include/hl_time.h - include/hl_dso_loader.h include/hl_warpctc_wrap.h include/hl_sequence.h include/hl_cuda_cublas.h diff --git a/paddle/cuda/src/hl_cuda_cublas.cc b/paddle/cuda/src/hl_cuda_cublas.cc index 182e8ab218c..54c6d60c16e 100644 --- a/paddle/cuda/src/hl_cuda_cublas.cc +++ b/paddle/cuda/src/hl_cuda_cublas.cc @@ -16,8 +16,8 @@ limitations under the License. */ #include #include #include "hl_cuda.h" -#include "hl_dso_loader.h" #include "hl_thread.ph" +#include "paddle/utils/DynamicLoad.h" #include "paddle/utils/Logging.h" namespace dynload { diff --git a/paddle/cuda/src/hl_cuda_cudnn.cc b/paddle/cuda/src/hl_cuda_cudnn.cc index 6198f067bab..4de6a863543 100644 --- a/paddle/cuda/src/hl_cuda_cudnn.cc +++ b/paddle/cuda/src/hl_cuda_cudnn.cc @@ -17,8 +17,8 @@ limitations under the License. */ #include #include #include "hl_cuda_cudnn.ph" -#include "hl_dso_loader.h" #include "hl_thread.ph" +#include "paddle/utils/DynamicLoad.h" #include "paddle/utils/Logging.h" DEFINE_int32(cudnn_conv_workspace_limit_in_mb, diff --git a/paddle/cuda/src/hl_cuda_device.cc b/paddle/cuda/src/hl_cuda_device.cc index 6dfb12e00b8..e7a8d563908 100644 --- a/paddle/cuda/src/hl_cuda_device.cc +++ b/paddle/cuda/src/hl_cuda_device.cc @@ -24,8 +24,8 @@ limitations under the License. */ #include #include "hl_cuda.ph" #include "hl_thread.ph" -#include "hl_dso_loader.h" #include "paddle/utils/Logging.h" +#include "paddle/utils/DynamicLoad.h" // clang-format on namespace dynload { @@ -98,11 +98,11 @@ int g_cuda_lib_version = 0; * Check build-in cuda function using glog and it **does not** * support << operator for more details error info. */ -#define CHECK_CUDA(cudaFunc) \ - do { \ - cudaError_t cudaStat = cudaFunc; \ - CHECK_EQ(cudaSuccess, cudaStat) << "Cuda Error: " \ - << cudaGetErrorString(cudaStat); \ +#define CHECK_CUDA(cudaFunc) \ + do { \ + cudaError_t cudaStat = cudaFunc; \ + CHECK_EQ(cudaSuccess, cudaStat) \ + << "Cuda Error: " << cudaGetErrorString(cudaStat); \ } while (0) /** @@ -469,8 +469,8 @@ void hl_specify_devices_start(int *device, int number) { CHECK(tmp) << "[Start failed] System memory is not enough."; g_device = (hl_device_prop *)tmp; - device_prop = (hl_device_prop)( - (char *)tmp + g_system_device_num * sizeof(hl_device_prop *)); + device_prop = (hl_device_prop)((char *)tmp + g_system_device_num * + sizeof(hl_device_prop *)); memset(g_device, 0, g_system_device_num * sizeof(hl_device_prop *)); int num = 0; for (int i = 0; i < number; i++) { @@ -559,8 +559,8 @@ bool hl_get_sync_flag() { return g_sync_flag; } void hl_stream_synchronize(hl_stream_t stream) { cudaStream_t cu_stream; - CHECK_LT(stream, HPPL_STREAM_END) << __func__ - << ": the parameter stream is error."; + CHECK_LT(stream, HPPL_STREAM_END) + << __func__ << ": the parameter stream is error."; cu_stream = t_resource.stream[stream]; CHECK_CUDA(cudaStreamSynchronize(cu_stream)); @@ -590,8 +590,8 @@ void hl_stream_record_event(hl_stream_t stream, hl_event_t event) { cudaStream_t cu_stream; CHECK_NOTNULL(event); - CHECK_LT(stream, HPPL_STREAM_END) << __func__ - << ": the parameter stream is error."; + CHECK_LT(stream, HPPL_STREAM_END) + << __func__ << ": the parameter stream is error."; cu_stream = t_resource.stream[stream]; CHECK_CUDA(cudaEventRecord(event->cu_event, cu_stream)); @@ -601,8 +601,8 @@ void hl_stream_wait_event(hl_stream_t stream, hl_event_t event) { cudaStream_t cu_stream; CHECK_NOTNULL(event); - CHECK_LT(stream, HPPL_STREAM_END) << __func__ - << ": the parameter stream is error."; + CHECK_LT(stream, HPPL_STREAM_END) + << __func__ << ": the parameter stream is error."; cu_stream = t_resource.stream[stream]; CHECK_CUDA(cudaStreamWaitEvent(cu_stream, event->cu_event, 0)); diff --git a/paddle/cuda/src/hl_warpctc_wrap.cc b/paddle/cuda/src/hl_warpctc_wrap.cc index f57efb2b467..5a4de24ced0 100644 --- a/paddle/cuda/src/hl_warpctc_wrap.cc +++ b/paddle/cuda/src/hl_warpctc_wrap.cc @@ -14,7 +14,7 @@ limitations under the License. */ #include "hl_warpctc_wrap.h" #include -#include "hl_dso_loader.h" +#include "paddle/utils/DynamicLoad.h" #include "paddle/utils/Logging.h" namespace dynload { diff --git a/paddle/math/MathFunctions.cpp b/paddle/math/MathFunctions.cpp index 6203cd3b9ab..895ae104bef 100644 --- a/paddle/math/MathFunctions.cpp +++ b/paddle/math/MathFunctions.cpp @@ -15,6 +15,49 @@ limitations under the License. */ #include "MathFunctions.h" #include "hl_matrix_apply.cuh" #include "hl_matrix_ops.cuh" +#include "paddle/utils/DynamicLoad.h" + +namespace dynload { + +std::once_flag lapack_dso_flag; +void* lapack_dso_handle = nullptr; + +/** + * The following macro definition can generate structs + * (for each function) to dynamic load lapack routine + * via operator overloading. + * + * note: default dynamic linked libs + */ +#define DYNAMIC_LOAD_LAPACK_WRAP(__name) \ + struct DynLoad__##__name { \ + template \ + int operator()(Args... args)->decltype(__name(args...)) { \ + using lapack_func = decltype(__name(args...)) (*)(Args...); \ + std::call_once(lapack_dso_flag, GetLapackDsoHandle, &lapack_dso_handle); \ + void* p_##__name = dlsym(lapack_dso_handle, #__name); \ + return reinterpret_cast(p_##__name)(args...); \ + } \ + } __name; // struct DynLoad__##__name + +// clang-format off +#ifdef PADDLE_USE_LAPACK +#ifdef PADDLE_USE_ATLAS + #define LAPACK_ROUTINE_EACH(__macro) \ + __macro(clapack_sgetrf) \ + __macro(clapack_dgetrf) \ + __macro(clapack_sgetri) \ + __macro(clapack_dgetri) +#else + #define LAPACK_ROUTINE_EACH(__macro) \ + __macro(LAPACKE_sgetrf) \ + __macro(LAPACKE_dgetrf) \ + __macro(LAPACKE_sgetri) \ + __macro(LAPACKE_dgetri) +#endif +#endif +// clang-format on +} // namespace dynload namespace paddle { @@ -87,9 +130,9 @@ int getrf(const CBLAS_ORDER order, int* ipiv) { #ifdef PADDLE_USE_LAPACK #ifdef PADDLE_USE_ATLAS - return clapack_sgetrf(order, M, N, A, lda, ipiv); + return dynload::clapack_sgetrf(order, M, N, A, lda, ipiv); #else - return LAPACKE_sgetrf(order, M, N, A, lda, ipiv); + return dynload::LAPACKE_sgetrf(order, M, N, A, lda, ipiv); #endif #else LOG(FATAL) << "Not implemented"; @@ -106,9 +149,9 @@ int getrf(const CBLAS_ORDER order, int* ipiv) { #ifdef PADDLE_USE_LAPACK #ifdef PADDLE_USE_ATLAS - return clapack_dgetrf(order, M, N, A, lda, ipiv); + return dynload::clapack_dgetrf(order, M, N, A, lda, ipiv); #else - return LAPACKE_dgetrf(order, M, N, A, lda, ipiv); + return dynload::LAPACKE_dgetrf(order, M, N, A, lda, ipiv); #endif #else LOG(FATAL) << "Not implemented"; @@ -124,9 +167,9 @@ int getri(const CBLAS_ORDER order, const int* ipiv) { #ifdef PADDLE_USE_LAPACK #ifdef PADDLE_USE_ATLAS - return clapack_sgetri(order, N, A, lda, ipiv); + return dynload::clapack_sgetri(order, N, A, lda, ipiv); #else - return LAPACKE_sgetri(order, N, A, lda, ipiv); + return dynload::LAPACKE_sgetri(order, N, A, lda, ipiv); #endif #else LOG(FATAL) << "Not implemented"; @@ -142,9 +185,9 @@ int getri(const CBLAS_ORDER order, const int* ipiv) { #ifdef PADDLE_USE_LAPACK #ifdef PADDLE_USE_ATLAS - return clapack_dgetri(order, N, A, lda, ipiv); + return dynload::clapack_dgetri(order, N, A, lda, ipiv); #else - return LAPACKE_dgetri(order, N, A, lda, ipiv); + return dynload::LAPACKE_dgetri(order, N, A, lda, ipiv); #endif #else LOG(FATAL) << "Not implemented"; diff --git a/paddle/cuda/src/hl_dso_loader.cc b/paddle/utils/DynamicLoad.cc similarity index 94% rename from paddle/cuda/src/hl_dso_loader.cc rename to paddle/utils/DynamicLoad.cc index 53164dd27c7..8f0532942e7 100644 --- a/paddle/cuda/src/hl_dso_loader.cc +++ b/paddle/utils/DynamicLoad.cc @@ -12,9 +12,9 @@ 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 "hl_dso_loader.h" +#include "DynamicLoad.h" +#include "Logging.h" #include -#include "paddle/utils/Logging.h" DEFINE_string(cudnn_dir, "", @@ -30,6 +30,8 @@ DEFINE_string(cuda_dir, DEFINE_string(warpctc_dir, "", "Specify path for loading libwarpctc.so."); +DEFINE_string(lapack_dir, "", "Specify path for loading liblapack.so."); + static inline std::string join(const std::string& part1, const std::string& part2) { // directory separator @@ -160,3 +162,11 @@ void GetWarpCTCDsoHandle(void** dso_handle) { GetDsoHandleFromSearchPath(FLAGS_warpctc_dir, "libwarpctc.so", dso_handle); #endif } + +void GetLapackDsoHandle(void** dso_handle) { +#if defined(__APPLE__) || defined(__OSX__) + GetDsoHandleFromSearchPath(FLAGS_warpctc_dir, "liblapack.dylib", dso_handle); +#else + GetDsoHandleFromSearchPath(FLAGS_warpctc_dir, "liblapack.so", dso_handle); +#endif +} diff --git a/paddle/cuda/include/hl_dso_loader.h b/paddle/utils/DynamicLoad.h similarity index 84% rename from paddle/cuda/include/hl_dso_loader.h rename to paddle/utils/DynamicLoad.h index 276a07d3c73..5587993f874 100644 --- a/paddle/cuda/include/hl_dso_loader.h +++ b/paddle/utils/DynamicLoad.h @@ -12,13 +12,12 @@ 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. */ -#ifndef HL_DSO_LOADER_H_ -#define HL_DSO_LOADER_H_ +#ifndef HL_DYNAMIC_LOAD_H_ +#define HL_DYNAMIC_LOAD_H_ #include #include #include -#include "hl_base.h" /** * @brief load the DSO of CUBLAS @@ -52,4 +51,12 @@ void GetCurandDsoHandle(void** dso_handle); */ void GetWarpCTCDsoHandle(void** dso_handle); -#endif // HL_DSO_LOADER_H_ +/** + * @brief load the DSO of lapack + * + * @param **dso_handle dso handler + * + */ +void GetLapackDsoHandle(void** dso_handle); + +#endif // HL_DYNAMIC_LOAD_H_ -- GitLab From af80ff94a5b56a2fc8aa32cf8b50d1e8a013a84c Mon Sep 17 00:00:00 2001 From: Liu Yiqun Date: Tue, 2 May 2017 07:19:45 +0000 Subject: [PATCH 0149/3256] Allow the case user sets CMAKE_C_COMPILER directly instead of standalone toolchain. --- cmake/cross_compiling/android.cmake | 93 ++++++++++++++++++----------- 1 file changed, 58 insertions(+), 35 deletions(-) diff --git a/cmake/cross_compiling/android.cmake b/cmake/cross_compiling/android.cmake index 3b13fca5151..0be13616351 100644 --- a/cmake/cross_compiling/android.cmake +++ b/cmake/cross_compiling/android.cmake @@ -41,7 +41,8 @@ IF(NOT DEFINED ANDROID_STANDALONE_TOOLCHAIN) CACHE PATH "Folder holds the standalone toolchain of Android NDK") ENDIF() IF(NOT ANDROID_STANDALONE_TOOLCHAIN) - MESSAGE(FATAL_ERROR "Set ANDROID_STANDALONE_TOOLCHAIN to use a standalone toolchain.\n" + MESSAGE(WARNING "It is recommened to set ANDROID_STANDALONE_TOOLCHAIN to " + "use a standalone toolchain.\n" "To cross-compile for Android, you need to:\n" "1. Download an Android NDK from" " https://developer.android.com/ndk/downloads/index.html\n" @@ -65,11 +66,6 @@ ENDIF() IF(NOT DEFINED ANDROID_ARM_MODE) SET(ANDROID_ARM_MODE ON) ENDIF() - -IF(NOT DEFINED ANDROID_ARM_NEON) - SET(ANDROID_ARM_NEON ON) -ENDIF() - IF(NOT ANDROID_ARM_MODE OR ANDROID_ARM_MODE STREQUAL "thumb") SET(ANDROID_ARM_MODE OFF) SET(ANDROID_ARM_MODE_NAME "thumb") @@ -78,6 +74,10 @@ ELSEIF(ANDROID_ARM_MODE OR ANDROID_ARM_MODE STREQUAL "arm") SET(ANDROID_ARM_MODE_NAME "arm") ENDIF() +IF(NOT DEFINED ANDROID_ARM_NEON) + SET(ANDROID_ARM_NEON ON) +ENDIF() + IF(${CMAKE_VERSION} VERSION_LESS "3.7.0") IF(${CMAKE_VERSION} VERSION_LESS "3.1.0") SET(CMAKE_SYSTEM_NAME "Linux") @@ -85,47 +85,70 @@ IF(${CMAKE_VERSION} VERSION_LESS "3.7.0") MESSAGE(WARNING "It is recommended to use CMake >= 3.7.0 (current version: " "${CMAKE_VERSION}), when cross-compiling for Android.") - SET(CMAKE_SYSROOT "${ANDROID_STANDALONE_TOOLCHAIN}/sysroot") - - IF(NOT CMAKE_SYSTEM_VERSION) - SET(ANDROID_STANDALONE_TOOLCHAIN_API "") - SET(ANDROID_API_LEVEL_H_REGEX "^[\t ]*#[\t ]*define[\t ]+__ANDROID_API__[\t ]+([0-9]+)") - FILE(STRINGS "${ANDROID_STANDALONE_TOOLCHAIN}/sysroot/usr/include/android/api-level.h" - ANDROID_API_LEVEL_H_CONTENT REGEX "${ANDROID_API_LEVEL_H_REGEX}") - IF(ANDROID_API_LEVEL_H_CONTENT MATCHES "${ANDROID_API_LEVEL_H_REGEX}") - SET(ANDROID_STANDALONE_TOOLCHAIN_API "${CMAKE_MATCH_1}") + IF(ANDROID_STANDALONE_TOOLCHAIN) + SET(CMAKE_SYSROOT "${ANDROID_STANDALONE_TOOLCHAIN}/sysroot") + + IF(NOT CMAKE_SYSTEM_VERSION) + SET(ANDROID_STANDALONE_TOOLCHAIN_API "") + SET(ANDROID_API_LEVEL_H_REGEX "^[\t ]*#[\t ]*define[\t ]+__ANDROID_API__[\t ]+([0-9]+)") + FILE(STRINGS "${ANDROID_STANDALONE_TOOLCHAIN}/sysroot/usr/include/android/api-level.h" + ANDROID_API_LEVEL_H_CONTENT REGEX "${ANDROID_API_LEVEL_H_REGEX}") + IF(ANDROID_API_LEVEL_H_CONTENT MATCHES "${ANDROID_API_LEVEL_H_REGEX}") + SET(ANDROID_STANDALONE_TOOLCHAIN_API "${CMAKE_MATCH_1}") + ENDIF() + SET(CMAKE_SYSTEM_VERSION ${ANDROID_STANDALONE_TOOLCHAIN_API}) ENDIF() - SET(CMAKE_SYSTEM_VERSION ${ANDROID_STANDALONE_TOOLCHAIN_API}) - ENDIF() - # Toolchain - SET(ANDROID_TOOLCHAIN "gcc") - SET(ANDROID_TOOLCHAIN_ROOT ${ANDROID_STANDALONE_TOOLCHAIN}) - IF(ANDROID_ABI MATCHES "^armeabi(-v7a)?$") - SET(ANDROID_TOOLCHAIN_NAME arm-linux-androideabi) - IF(ANDROID_ABI STREQUAL "armeabi") - SET(CMAKE_SYSTEM_PROCESSOR armv5te) - ELSEIF(ANDROID_ABI STREQUAL "armeabi-v7a") - SET(CMAKE_SYSTEM_PROCESSOR armv7-a) + # Toolchain + SET(ANDROID_TOOLCHAIN "gcc") + SET(ANDROID_TOOLCHAIN_ROOT ${ANDROID_STANDALONE_TOOLCHAIN}) + IF(ANDROID_ABI MATCHES "^armeabi(-v7a)?$") + SET(ANDROID_TOOLCHAIN_NAME arm-linux-androideabi) + IF(ANDROID_ABI STREQUAL "armeabi") + SET(CMAKE_SYSTEM_PROCESSOR armv5te) + ELSEIF(ANDROID_ABI STREQUAL "armeabi-v7a") + SET(CMAKE_SYSTEM_PROCESSOR armv7-a) + ENDIF() ENDIF() + SET(ANDROID_TOOLCHAIN_PREFIX "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_NAME}-") ENDIF() - SET(ANDROID_TOOLCHAIN_PREFIX "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_NAME}-") - IF(EXISTS "${ANDROID_TOOLCHAIN_PREFIX}gcc") - SET(CMAKE_C_COMPILER "${ANDROID_TOOLCHAIN_PREFIX}gcc" CACHE PATH "C compiler") + # C compiler + IF(NOT CMAKE_C_COMPILER) + SET(ANDROID_C_COMPILER "${ANDROID_TOOLCHAIN_PREFIX}gcc") ELSE() - MESSAGE(FATAL_ERROR "Cannot found C compiler: ${ANDROID_TOOLCHAIN_PREFIX}gcc") + GET_FILENAME_COMPONENT(ANDROID_C_COMPILER_PATH ${CMAKE_C_COMPILER} PROGRAM) + SET(ANDROID_C_COMPILER ${ANDROID_C_COMPILER_PATH}) + ENDIF() + IF(NOT EXISTS ${ANDROID_C_COMPILER}) + MESSAGE(FATAL_ERROR "Cannot found C compiler: ${ANDROID_C_COMPILER}") ENDIF() - IF(EXISTS "${ANDROID_TOOLCHAIN_PREFIX}g++") - SET(CMAKE_CXX_COMPILER "${ANDROID_TOOLCHAIN_PREFIX}g++" CACHE PATH "CXX compiler") + # CXX compiler + IF(NOT CMAKE_CXX_COMPILER) + SET(ANDROID_CXX_COMPILER "${ANDROID_TOOLCHAIN_PREFIX}g++") ELSE() - MESSAGE(FATAL_ERROR "Cannot found CXX compiler: ${ANDROID_TOOLCHAIN_PREFIX}g++") + GET_FILENAME_COMPONENT(ANDROID_CXX_COMPILER_PATH ${CMAKE_CXX_COMPILER} PROGRAM) + SET(ANDROID_CXX_COMPILER ${ANDROID_CXX_COMPILER_PATH}) + ENDIF() + IF(NOT EXISTS ${ANDROID_CXX_COMPILER}) + MESSAGE(FATAL_ERROR "Cannot found CXX compiler: ${ANDROID_CXX_COMPILER}") ENDIF() - IF(EXISTS "${ANDROID_TOOLCHAIN_PREFIX}gfortran") - SET(CMAKE_Fortran_COMPILER "${ANDROID_TOOLCHAIN_PREFIX}gfortran" CACHE PATH "Fortran compiler") + # Fortran compiler + IF(NOT CMAKE_Fortran_COMPILER) + SET(ANDROID_Fortran_COMPILER "${ANDROID_TOOLCHAIN_PREFIX}gfortran") + ELSE() + GET_FILENAME_COMPONENT(ANDROID_Fortran_COMPILER_PATH ${CMAKE_Fortran_COMPILER}) + SET(ANDROID_Fortran_COMPILER ${ANDROID_Fortran_COMPILER_PATH}) ENDIF() + IF(NOT EXISTS ${ANDROID_Fortran_COMPILER}) + SET(ANDROID_Fortran_COMPILER "") + ENDIF() + + SET(CMAKE_C_COMPILER ${ANDROID_C_COMPILER} CACHE PATH "C compiler" FORCE) + SET(CMAKE_CXX_COMPILER ${ANDROID_CXX_COMPILER} CACHE PATH "CXX compiler" FORCE) + SET(CMAKE_Fortran_COMPILER ${ANDROID_Fortran_COMPILER} CACHE PATH "Fortran compiler" FORCE) # Toolchain and ABI specific flags. SET(ANDROID_COMPILER_FLAGS "-ffunction-sections -fdata-sections -finline-limit=64") -- GitLab From afc84114ae19b8ae749d9f77868e7658f22bf6d7 Mon Sep 17 00:00:00 2001 From: liaogang Date: Tue, 2 May 2017 16:23:55 +0800 Subject: [PATCH 0150/3256] fix dsohandle bug --- paddle/math/MathFunctions.cpp | 9 ++++++--- paddle/utils/{DynamicLoad.cc => DynamicLoad.cpp} | 0 paddle/utils/DynamicLoad.h | 7 ++++--- 3 files changed, 10 insertions(+), 6 deletions(-) rename paddle/utils/{DynamicLoad.cc => DynamicLoad.cpp} (100%) diff --git a/paddle/math/MathFunctions.cpp b/paddle/math/MathFunctions.cpp index 895ae104bef..1ed25728bc1 100644 --- a/paddle/math/MathFunctions.cpp +++ b/paddle/math/MathFunctions.cpp @@ -32,7 +32,7 @@ void* lapack_dso_handle = nullptr; #define DYNAMIC_LOAD_LAPACK_WRAP(__name) \ struct DynLoad__##__name { \ template \ - int operator()(Args... args)->decltype(__name(args...)) { \ + auto operator()(Args... args)->decltype(__name(args...)) { \ using lapack_func = decltype(__name(args...)) (*)(Args...); \ std::call_once(lapack_dso_flag, GetLapackDsoHandle, &lapack_dso_handle); \ void* p_##__name = dlsym(lapack_dso_handle, #__name); \ @@ -47,15 +47,18 @@ void* lapack_dso_handle = nullptr; __macro(clapack_sgetrf) \ __macro(clapack_dgetrf) \ __macro(clapack_sgetri) \ - __macro(clapack_dgetri) + __macro(clapack_dgetri) #else #define LAPACK_ROUTINE_EACH(__macro) \ __macro(LAPACKE_sgetrf) \ __macro(LAPACKE_dgetrf) \ __macro(LAPACKE_sgetri) \ - __macro(LAPACKE_dgetri) + __macro(LAPACKE_dgetri) #endif #endif + +LAPACK_ROUTINE_EACH(DYNAMIC_LOAD_LAPACK_WRAP) + // clang-format on } // namespace dynload diff --git a/paddle/utils/DynamicLoad.cc b/paddle/utils/DynamicLoad.cpp similarity index 100% rename from paddle/utils/DynamicLoad.cc rename to paddle/utils/DynamicLoad.cpp diff --git a/paddle/utils/DynamicLoad.h b/paddle/utils/DynamicLoad.h index 5587993f874..67f52a3a5da 100644 --- a/paddle/utils/DynamicLoad.h +++ b/paddle/utils/DynamicLoad.h @@ -12,12 +12,13 @@ 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. */ -#ifndef HL_DYNAMIC_LOAD_H_ -#define HL_DYNAMIC_LOAD_H_ +#ifndef DYNAMIC_LOAD_H_ +#define DYNAMIC_LOAD_H_ #include #include #include +#include /** * @brief load the DSO of CUBLAS @@ -59,4 +60,4 @@ void GetWarpCTCDsoHandle(void** dso_handle); */ void GetLapackDsoHandle(void** dso_handle); -#endif // HL_DYNAMIC_LOAD_H_ +#endif // DYNAMIC_LOAD_H_ -- GitLab From 665cc0e7b2dcda103a9de67e99f485660b240446 Mon Sep 17 00:00:00 2001 From: liaogang Date: Tue, 2 May 2017 16:31:48 +0800 Subject: [PATCH 0151/3256] remove redundant mutex --- paddle/cuda/src/hl_cuda_cublas.cc | 1 - paddle/cuda/src/hl_cuda_cudnn.cc | 1 - paddle/cuda/src/hl_cuda_device.cc | 1 - 3 files changed, 3 deletions(-) diff --git a/paddle/cuda/src/hl_cuda_cublas.cc b/paddle/cuda/src/hl_cuda_cublas.cc index 54c6d60c16e..4a56a7a9d06 100644 --- a/paddle/cuda/src/hl_cuda_cublas.cc +++ b/paddle/cuda/src/hl_cuda_cublas.cc @@ -14,7 +14,6 @@ limitations under the License. */ #include "hl_cuda_cublas.h" #include -#include #include "hl_cuda.h" #include "hl_thread.ph" #include "paddle/utils/DynamicLoad.h" diff --git a/paddle/cuda/src/hl_cuda_cudnn.cc b/paddle/cuda/src/hl_cuda_cudnn.cc index 4de6a863543..c8d321f1ad1 100644 --- a/paddle/cuda/src/hl_cuda_cudnn.cc +++ b/paddle/cuda/src/hl_cuda_cudnn.cc @@ -15,7 +15,6 @@ limitations under the License. */ #include "hl_cuda_cudnn.h" #include #include -#include #include "hl_cuda_cudnn.ph" #include "hl_thread.ph" #include "paddle/utils/DynamicLoad.h" diff --git a/paddle/cuda/src/hl_cuda_device.cc b/paddle/cuda/src/hl_cuda_device.cc index e7a8d563908..43841411b8a 100644 --- a/paddle/cuda/src/hl_cuda_device.cc +++ b/paddle/cuda/src/hl_cuda_device.cc @@ -21,7 +21,6 @@ limitations under the License. */ #include #include #include -#include #include "hl_cuda.ph" #include "hl_thread.ph" #include "paddle/utils/Logging.h" -- GitLab From 8cde2d119fcd929c908179c371e9a2e5c5cec324 Mon Sep 17 00:00:00 2001 From: liaogang Date: Tue, 2 May 2017 16:33:51 +0800 Subject: [PATCH 0152/3256] fix LAPACK_ROUTINE_EACHbug --- paddle/math/MathFunctions.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/paddle/math/MathFunctions.cpp b/paddle/math/MathFunctions.cpp index 1ed25728bc1..d78ab3422d6 100644 --- a/paddle/math/MathFunctions.cpp +++ b/paddle/math/MathFunctions.cpp @@ -55,9 +55,8 @@ void* lapack_dso_handle = nullptr; __macro(LAPACKE_sgetri) \ __macro(LAPACKE_dgetri) #endif -#endif - LAPACK_ROUTINE_EACH(DYNAMIC_LOAD_LAPACK_WRAP) +#endif // clang-format on } // namespace dynload -- GitLab From c914f6367689f393f8e6f0817faa2087114e94f9 Mon Sep 17 00:00:00 2001 From: Liu Yiqun Date: Tue, 2 May 2017 12:12:37 +0000 Subject: [PATCH 0153/3256] Add toolchain for raspberry pi. --- cmake/cross_compiling/android.cmake | 41 +++++----- cmake/cross_compiling/host.cmake | 4 +- cmake/cross_compiling/raspberry_pi.cmake | 95 ++++++++++++++++++++++++ cmake/external/openblas.cmake | 13 ++-- cmake/system.cmake | 3 + 5 files changed, 126 insertions(+), 30 deletions(-) create mode 100644 cmake/cross_compiling/raspberry_pi.cmake diff --git a/cmake/cross_compiling/android.cmake b/cmake/cross_compiling/android.cmake index 0be13616351..5b730743252 100644 --- a/cmake/cross_compiling/android.cmake +++ b/cmake/cross_compiling/android.cmake @@ -17,12 +17,11 @@ # https://github.com/taka-no-me/android-cmake # Most of the variables are compatible with that used in # https://developer.android.com/ndk/guides/cmake.html -# The supported are listed belows: +# The supported variables are listed belows: # # ANDROID_STANDALONE_TOOLCHAIN # ANDROID_ABI # ANDROID_NATIVE_API_LEVEL -# ANDROID_PLATFORM # ANDROID_ARM_MODE # ANDROID_ARM_NEON # @@ -41,22 +40,21 @@ IF(NOT DEFINED ANDROID_STANDALONE_TOOLCHAIN) CACHE PATH "Folder holds the standalone toolchain of Android NDK") ENDIF() IF(NOT ANDROID_STANDALONE_TOOLCHAIN) - MESSAGE(WARNING "It is recommened to set ANDROID_STANDALONE_TOOLCHAIN to " + MESSAGE(WARNING "It is recommended to set ANDROID_STANDALONE_TOOLCHAIN to " "use a standalone toolchain.\n" "To cross-compile for Android, you need to:\n" "1. Download an Android NDK from" " https://developer.android.com/ndk/downloads/index.html\n" "2. Setup a standalone toolchain" - " https://developer.android.google.cn/ndk/guides/standalone_toolchain.html?hl=zh-cn") + "https://developer.android.google.cn/ndk/guides/standalone_toolchain.html?hl=zh-cn\n") ENDIF() -IF(DEFINED ANDROID_NATIVE_API_LEVEL) +IF(NOT DEFINED CMAKE_SYSTEM_VERSION AND ANDROID_NATIVE_API_LEVEL) IF(ANDROID_NATIVE_API_LEVEL MATCHES "^android-[0-9]+$") STRING(REPLACE "android-" "" CMAKE_SYSTEM_VERSION "${CMAKE_MATCH_0}") ELSEIF(ANDROID_NATIVE_API_LEVEL MATCHES "^[0-9]+$") SET(CMAKE_SYSTEM_VERSION ${ANDROID_NATIVE_API_LEVEL}) ENDIF() - SET(ANDROID_PLATFORM android-${CMAKE_SYSTEM_VERSION}) ENDIF() IF(NOT DEFINED ANDROID_ABI) @@ -66,13 +64,11 @@ ENDIF() IF(NOT DEFINED ANDROID_ARM_MODE) SET(ANDROID_ARM_MODE ON) ENDIF() -IF(NOT ANDROID_ARM_MODE OR ANDROID_ARM_MODE STREQUAL "thumb") - SET(ANDROID_ARM_MODE OFF) - SET(ANDROID_ARM_MODE_NAME "thumb") -ELSEIF(ANDROID_ARM_MODE OR ANDROID_ARM_MODE STREQUAL "arm") - SET(ANDROID_ARM_MODE ON) +IF(ANDROID_ARM_MODE) SET(ANDROID_ARM_MODE_NAME "arm") -ENDIF() +ELSE(ANDROID_ARM_MODE) + SET(ANDROID_ARM_MODE_NAME "thumb") +ENDIF(ANDROID_ARM_MODE) IF(NOT DEFINED ANDROID_ARM_NEON) SET(ANDROID_ARM_NEON ON) @@ -117,30 +113,27 @@ IF(${CMAKE_VERSION} VERSION_LESS "3.7.0") IF(NOT CMAKE_C_COMPILER) SET(ANDROID_C_COMPILER "${ANDROID_TOOLCHAIN_PREFIX}gcc") ELSE() - GET_FILENAME_COMPONENT(ANDROID_C_COMPILER_PATH ${CMAKE_C_COMPILER} PROGRAM) - SET(ANDROID_C_COMPILER ${ANDROID_C_COMPILER_PATH}) + GET_FILENAME_COMPONENT(ANDROID_C_COMPILER ${CMAKE_C_COMPILER} PROGRAM) ENDIF() IF(NOT EXISTS ${ANDROID_C_COMPILER}) - MESSAGE(FATAL_ERROR "Cannot found C compiler: ${ANDROID_C_COMPILER}") + MESSAGE(FATAL_ERROR "Cannot find C compiler: ${ANDROID_C_COMPILER}") ENDIF() # CXX compiler IF(NOT CMAKE_CXX_COMPILER) SET(ANDROID_CXX_COMPILER "${ANDROID_TOOLCHAIN_PREFIX}g++") ELSE() - GET_FILENAME_COMPONENT(ANDROID_CXX_COMPILER_PATH ${CMAKE_CXX_COMPILER} PROGRAM) - SET(ANDROID_CXX_COMPILER ${ANDROID_CXX_COMPILER_PATH}) + GET_FILENAME_COMPONENT(ANDROID_CXX_COMPILER ${CMAKE_CXX_COMPILER} PROGRAM) ENDIF() IF(NOT EXISTS ${ANDROID_CXX_COMPILER}) - MESSAGE(FATAL_ERROR "Cannot found CXX compiler: ${ANDROID_CXX_COMPILER}") + MESSAGE(FATAL_ERROR "Cannot find CXX compiler: ${ANDROID_CXX_COMPILER}") ENDIF() # Fortran compiler IF(NOT CMAKE_Fortran_COMPILER) SET(ANDROID_Fortran_COMPILER "${ANDROID_TOOLCHAIN_PREFIX}gfortran") ELSE() - GET_FILENAME_COMPONENT(ANDROID_Fortran_COMPILER_PATH ${CMAKE_Fortran_COMPILER}) - SET(ANDROID_Fortran_COMPILER ${ANDROID_Fortran_COMPILER_PATH}) + GET_FILENAME_COMPONENT(ANDROID_Fortran_COMPILER ${CMAKE_Fortran_COMPILER}) ENDIF() IF(NOT EXISTS ${ANDROID_Fortran_COMPILER}) SET(ANDROID_Fortran_COMPILER "") @@ -184,9 +177,9 @@ IF(${CMAKE_VERSION} VERSION_LESS "3.7.0") STRING(REPLACE ";" " " ANDROID_LINKER_FLAGS "${ANDROID_LINKER_FLAGS}") SET(CMAKE_C_FLAGS "${ANDROID_COMPILER_FLAGS} ${CMAKE_C_FLAGS}" - CACHE STRING "c flags") + CACHE STRING "C flags") SET(CMAKE_CXX_FLAGS "${ANDROID_COMPILER_FLAGS} ${CMAKE_CXX_FLAGS}" - CACHE STRING "c++ flags") + CACHE STRING "CXX flags") SET(CMAKE_SHARED_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} ${CMAKE_SHARED_LINKER_FLAGS}" CACHE STRING "shared linker flags") @@ -200,7 +193,9 @@ IF(${CMAKE_VERSION} VERSION_LESS "3.7.0") MESSAGE(STATUS "System CMAKE_C_FLAGS: " ${CMAKE_C_FLAGS}) MESSAGE(STATUS "System CMAKE_CXX_FLAGS: " ${CMAKE_CXX_FLAGS}) ELSE() - SET(CMAKE_ANDROID_STANDALONE_TOOLCHAIN ${ANDROID_STANDALONE_TOOLCHAIN}) + IF(ANDROID_STANDALONE_TOOLCHAIN) + SET(CMAKE_ANDROID_STANDALONE_TOOLCHAIN ${ANDROID_STANDALONE_TOOLCHAIN}) + ENDIF() SET(CMAKE_ANDROID_ARCH_ABI ${ANDROID_ABI}) SET(CMAKE_ANDROID_ARM_MODE ${ANDROID_ARM_MODE}) SET(CMAKE_ANDROID_ARM_NEON ${ANDROID_ARM_NEON}) diff --git a/cmake/cross_compiling/host.cmake b/cmake/cross_compiling/host.cmake index 62f3196b1ed..14c35266ec6 100644 --- a/cmake/cross_compiling/host.cmake +++ b/cmake/cross_compiling/host.cmake @@ -23,7 +23,7 @@ ENDIF() GET_FILENAME_COMPONENT(HOST_C_COMPILER_PATH ${HOST_C_COMPILER_NAME} PROGRAM) IF(NOT HOST_C_COMPILER_PATH OR NOT EXISTS ${HOST_C_COMPILER_PATH}) - MESSAGE(FATAL_ERROR "Cannot found host C compiler, set host C compiler:\n" + MESSAGE(FATAL_ERROR "Cannot find host C compiler, set host C compiler:\n" "\tcmake .. -DHOST_C_COMPILER=...") ENDIF() @@ -38,7 +38,7 @@ ENDIF() GET_FILENAME_COMPONENT(HOST_CXX_COMPILER_PATH ${HOST_CXX_COMPILER_NAME} PROGRAM) IF(NOT HOST_CXX_COMPILER_PATH OR NOT EXISTS ${HOST_CXX_COMPILER_PATH}) - MESSAGE(FATAL_ERROR "Cannot found host CXX compiler, set host CXX compiler:\n" + MESSAGE(FATAL_ERROR "Cannot find host CXX compiler, set host CXX compiler:\n" "\tcmake .. -DHOST_CXX_COMPILER=...") ENDIF() diff --git a/cmake/cross_compiling/raspberry_pi.cmake b/cmake/cross_compiling/raspberry_pi.cmake new file mode 100644 index 00000000000..bfd26f130c1 --- /dev/null +++ b/cmake/cross_compiling/raspberry_pi.cmake @@ -0,0 +1,95 @@ +# 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. + +# This is a toolchain file for cross-compiling for Raspberry Pi. +# The supported variables are listed belows: +# +# RPI_TOOLCHAIN +# RPI_ARM_NEON +# +# Also you can set CMAKE_C/CXX_COMPILER yourself, through cmake arguments. + +IF(NOT RPI) + return() +ENDIF() + +SET(CMAKE_SYSTEM_NAME Linux) +SET(CMAKE_SYSTEM_VERSION 1) +SET(CMAKE_SYSTEM_PROCESSOR arm) + +# check the exist of raspberry pi toolchain +IF(NOT DEFINED RPI_TOOLCHAIN) + SET(RPI_TOOLCHAIN $ENV{RPI_TOOLCHAIN} + CACHE PATH "Folder holds the toolchain of Raspberr Pi") +ENDIF() +IF(NOT RPI_TOOLCHAIN) + MESSAGE(WARNING "It is recommended to set RPI_TOOLCHAIN to use toolchain.\n" + "To cross-compile for Raspberry Pi, you need to download the tools using:\n" + " git clone https://github.com/raspberrypi/tools\n") +ENDIF() + +IF(NOT DEFINED RPI_ARM_NEON) + SET(RPI_ARM_NEON ON) +ENDIF() + +IF(RPI_TOOLCHAIN) + SET(RPI_TOOLCHAIN_ROOT ${RPI_TOOLCHAIN}) + IF(RPI_TOOLCHAIN_ROOT MATCHES "gcc-linaro-arm-linux-gnueabihf-raspbian(-x64)?$") + # gcc-linaro-arm-linux-gnueabihf-raspbian + # gcc-linaro-arm-linux-gnueabihf-raspbian-x64 + SET(RPI_TOOLCHAIN_NAME arm-linux-gnueabihf) + ENDIF() + SET(RPI_TOOLCHAIN_PREFIX "${RPI_TOOLCHAIN_ROOT}/bin/${RPI_TOOLCHAIN_NAME}-") +ENDIF() + +# C compiler +IF(NOT CMAKE_C_COMPILER) + SET(RPI_C_COMPILER "${RPI_TOOLCHAIN_PREFIX}gcc") +ELSE() + GET_FILENAME_COMPONENT(RPI_C_COMPILER ${CMAKE_C_COMPILER} PROGRAM) +ENDIF() +IF(NOT EXISTS ${RPI_C_COMPILER}) + MESSAGE(FATAL_ERROR "Cannot find C compiler: ${RPI_C_COMPILER}") +ENDIF() + +# CXX compiler +IF(NOT CMAKE_CXX_COMPILER) + SET(RPI_CXX_COMPILER "${RPI_TOOLCHAIN_PREFIX}g++") +ELSE() + GET_FILENAME_COMPONENT(RPI_CXX_COMPILER ${CMAKE_CXX_COMPILER} PROGRAM) +ENDIF() +IF(NOT EXISTS ${RPI_CXX_COMPILER}) + MESSAGE(FATAL_ERROR "Cannot find CXX compiler: ${RPI_CXX_COMPILER}") +ENDIF() + +# Fortran compiler +IF(NOT CMAKE_Fortran_COMPILER) + SET(RPI_Fortran_COMPILER "${RPI_TOOLCHAIN_PREFIX}gfortran") +ELSE() + GET_FILENAME_COMPONENT(RPI_Fortran_COMPILER ${CMAKE_Fortran_COMPILER} PROGRAM) +ENDIF() +IF(NOT EXISTS RPI_Fortran_COMPILER) + SET(RPI_Fortran_COMPILER "") +ENDIF() + +SET(CMAKE_C_COMPILER ${RPI_C_COMPILER} CACHE PATH "C compiler" FORCE) +SET(CMAKE_CXX_COMPILER ${RPI_CXX_COMPILER} CACHE PATH "CXX compiler" FORCE) +SET(CMAKE_Fortran_COMPILER ${RPI_Fortran_COMPILER} CACHE PATH "Fortran compiler" FORCE) + +IF(RPI_ARM_NEON) + SET(RPI_C_FLAGS "${RPI_C_FLAGS} -mfpu=neon") +ENDIF() + +SET(CMAKE_C_FLAGS "${RPI_C_FLAGS} ${CMAKE_C_FLAGS}" CACHE STRING "C flags") +SET(CMAKE_CXX_FLAGS "${RPI_C_FLAGS} ${CMAKE_CXX_FLAGS}" CACHE STRING "CXX flags") diff --git a/cmake/external/openblas.cmake b/cmake/external/openblas.cmake index ece0f0d4445..4ddadb9fa3b 100644 --- a/cmake/external/openblas.cmake +++ b/cmake/external/openblas.cmake @@ -25,7 +25,13 @@ IF(NOT ${CBLAS_FOUND}) CACHE FILEPATH "openblas library." FORCE) # check fortran compiler and library - IF(NOT ANDROID) + IF(ANDROID) + SET(OPENBLAS_COMMIT "b5c96fcfcdc82945502a2303116a64d89985daf5") + SET(OPTIONAL_ARGS HOSTCC=${HOST_C_COMPILER} TARGET=ARMV7 ARM_SOFTFP_ABI=1 NOFORTRAN=1 USE_THREAD=0 libs) + ELSEIF(RPI) + SET(OPENBLAS_COMMIT "v0.2.19") + SET(OPTIONAL_ARGS HOSTCC=${HOST_C_COMPILER} TARGET=ARMV7 NOFORTRAN=1 USE_THREAD=0 libs) + ELSE() IF(CMAKE_COMPILER_IS_GNUCC) ENABLE_LANGUAGE(Fortran) if (NOT CMAKE_Fortran_COMPILER_VERSION) @@ -57,9 +63,6 @@ IF(NOT ${CBLAS_FOUND}) SET(OPENBLAS_COMMIT "v0.2.19") SET(OPENBLAS_ARGS FC=${CMAKE_Fortran_COMPILER} DYNAMIC_ARCH=1 libs netlib) - ELSE() - SET(OPENBLAS_COMMIT "b5c96fcfcdc82945502a2303116a64d89985daf5") - SET(OPTIONAL_ARGS HOSTCC=${HOST_C_COMPILER} TARGET=ARMV7 ARM_SOFTFP_ABI=1 NOFORTRAN=1 USE_THREAD=0 libs) ENDIF() ExternalProject_Add( @@ -76,7 +79,7 @@ IF(NOT ${CBLAS_FOUND}) CONFIGURE_COMMAND "" ) - IF(NOT ANDROID) + IF(NOT ANDROID AND NOT RPI) ExternalProject_Add_Step( openblas lapacke_install COMMAND ${CMAKE_COMMAND} -E copy "${CBLAS_SOURCES_DIR}/src/openblas/lapack-netlib/LAPACKE/include/lapacke_mangling_with_flags.h" "${CBLAS_INSTALL_DIR}/include/lapacke_mangling.h" diff --git a/cmake/system.cmake b/cmake/system.cmake index 6c381458d9b..904652413e0 100644 --- a/cmake/system.cmake +++ b/cmake/system.cmake @@ -78,6 +78,9 @@ IF(DEFINED CMAKE_SYSTEM_NAME) IF(${CMAKE_SYSTEM_NAME} STREQUAL "Android") SET(ANDROID TRUE) INCLUDE(cross_compiling/android) + ELSEIF(${CMAKE_SYSTEM_NAME} STREQUAL "RPi") + SET(RPI TRUE) + INCLUDE(cross_compiling/raspberry_pi) ENDIF() ENDIF() -- GitLab From f27fd9dc28389d98fe10098b6de1084b324e1dff Mon Sep 17 00:00:00 2001 From: liaogang Date: Tue, 2 May 2017 20:37:17 +0800 Subject: [PATCH 0154/3256] follow comments --- cmake/cblas.cmake | 3 - cmake/external/openblas.cmake | 2 - paddle/cuda/include/hl_activation_functions.h | 6 +- paddle/cuda/include/hl_cnn.h | 34 ++++---- paddle/cuda/src/hl_cuda_cublas.cc | 2 +- paddle/cuda/src/hl_cuda_cudnn.cc | 2 +- paddle/cuda/src/hl_cuda_device.cc | 2 +- paddle/cuda/src/hl_warpctc_wrap.cc | 2 +- paddle/function/BufferArgTest.cpp | 2 +- paddle/function/CosSimOp.cpp | 12 +-- paddle/function/FunctionTest.cpp | 2 +- paddle/function/MulOpTest.cpp | 18 ++--- paddle/function/TensorShapeTest.cpp | 2 +- paddle/function/TensorTypeTest.cpp | 2 +- .../gserver/dataproviders/PyDataProvider.cpp | 4 +- paddle/gserver/evaluators/Evaluator.cpp | 6 +- .../gradientmachines/GradientMachine.cpp | 1 - .../RecurrentGradientMachine.cpp | 2 +- .../RecurrentGradientMachine.h | 30 +++---- paddle/gserver/layers/Layer.cpp | 5 +- paddle/gserver/layers/Layer.h | 12 +-- paddle/gserver/layers/RotateLayer.h | 2 +- paddle/gserver/layers/SequencePoolLayer.cpp | 2 +- paddle/gserver/tests/test_RecurrentLayer.cpp | 39 +++++----- paddle/math/MathFunctions.cpp | 78 ++++++------------- paddle/math/MathFunctions.h | 4 - paddle/math/MatrixBitCode.cpp | 30 +++---- paddle/math/tests/TensorCheck.h | 4 +- paddle/math/tests/TestUtils.h | 2 +- paddle/math/tests/test_SIMDFunctions.cpp | 18 ++--- paddle/math/tests/test_matrixCompare.cpp | 14 +++- paddle/parameter/Argument.cpp | 50 ++++++------ paddle/parameter/AverageOptimizer.cpp | 5 +- paddle/parameter/FirstOrderOptimizer.h | 2 +- paddle/parameter/Parameter.cpp | 4 +- paddle/pserver/LightNetwork.cpp | 4 +- paddle/pserver/ParameterClient2.cpp | 12 +-- paddle/pserver/ParameterServer2.cpp | 4 +- paddle/pserver/ParameterServer2.h | 8 +- paddle/pserver/ProtoServer.cpp | 4 +- paddle/trainer/TrainerInternal.cpp | 2 +- paddle/trainer/tests/picojson.h | 4 +- paddle/utils/BarrierStat.h | 16 ++-- paddle/utils/ClassRegistrar.h | 4 +- .../{DynamicLoad.cpp => DynamicLoader.cpp} | 8 +- .../utils/{DynamicLoad.h => DynamicLoader.h} | 2 +- 46 files changed, 222 insertions(+), 251 deletions(-) rename paddle/utils/{DynamicLoad.cpp => DynamicLoader.cpp} (97%) rename paddle/utils/{DynamicLoad.h => DynamicLoader.h} (100%) diff --git a/cmake/cblas.cmake b/cmake/cblas.cmake index b8bf1bb07a1..1122a043dac 100644 --- a/cmake/cblas.cmake +++ b/cmake/cblas.cmake @@ -44,7 +44,6 @@ if(MKL_INC_DIR AND MKL_CORE_LIB AND MKL_SEQUENTIAL_LIB AND MKL_INTEL_LP64) message(STATUS "Found MKL (include: ${CBLAS_INC_DIR}, library: ${CBLAS_LIBRARIES})") set(CBLAS_FOUND ON) if(${MKL_LAPACK_INC_DIR}) - add_definitions(-DPADDLE_USE_LAPACK) message(STATUS "Found lapack in MKL (include: ${MKL_LAPACK_INC_DIR})") endif() return() # return file. @@ -80,7 +79,6 @@ if(ATLAS_INC_DIR AND ATLAS_CBLAS_LIB AND ATLAS_LIB AND NOT CBLAS_FOUND) message(STATUS "Found ATLAS (include: ${CBLAS_INC_DIR}, library: ${CBLAS_LIBRARIES})") set(CBLAS_FOUND ON) if(ATLAS_CLAPACK_INC_DIR) - add_definitions(-DPADDLE_USE_LAPACK) message(STATUS "Found lapack in ATLAS (include: ${ATLAS_CLAPACK_INC_DIR})") endif() return() @@ -114,7 +112,6 @@ if(OPENBLAS_INC_DIR AND OPENBLAS_LIB) message(STATUS "Found OpenBLAS (include: ${CBLAS_INC_DIR}, library: ${CBLAS_LIBRARIES})") set(CBLAS_FOUND ON) if(OPENBLAS_LAPACKE_INC_DIR) - add_definitions(-DPADDLE_USE_LAPACK) message(STATUS "Found lapack in OpenBLAS (include: ${OPENBLAS_LAPACKE_INC_DIR})") endif() return() diff --git a/cmake/external/openblas.cmake b/cmake/external/openblas.cmake index 317a1a92043..46398b22c27 100644 --- a/cmake/external/openblas.cmake +++ b/cmake/external/openblas.cmake @@ -27,8 +27,6 @@ IF(NOT ${CBLAS_FOUND}) SET(CBLAS_LIBRARIES "${CBLAS_INSTALL_DIR}/lib/libopenblas.a" CACHE FILEPATH "openblas library" FORCE) ENDIF(WIN32) - ADD_DEFINITIONS(-DPADDLE_USE_LAPACK) - ExternalProject_Add( openblas ${EXTERNAL_PROJECT_LOG_ARGS} diff --git a/paddle/cuda/include/hl_activation_functions.h b/paddle/cuda/include/hl_activation_functions.h index cdb2dba06cb..93957fd9644 100644 --- a/paddle/cuda/include/hl_activation_functions.h +++ b/paddle/cuda/include/hl_activation_functions.h @@ -40,18 +40,18 @@ public: namespace gpu { static __device__ Active::forward forward[] = HPPL_ACTIVE_FUNCTION; static __device__ Active::backward backward[] = HPPL_ACTIVE_FUNCTION; -} +} // namespace gpu #else namespace cpu { static Active::forward forward[] = HPPL_ACTIVE_FUNCTION; static Active::backward backward[] = HPPL_ACTIVE_FUNCTION; -} +} // namespace cpu #ifdef __AVX__ namespace avx { static Active<__m256>::forward forward[] = HPPL_ACTIVE_FUNCTION; static Active<__m256>::backward backward[] = HPPL_ACTIVE_FUNCTION; -} +} // namespace avx #endif #endif diff --git a/paddle/cuda/include/hl_cnn.h b/paddle/cuda/include/hl_cnn.h index c5787630abb..f55197c8c9e 100644 --- a/paddle/cuda/include/hl_cnn.h +++ b/paddle/cuda/include/hl_cnn.h @@ -273,23 +273,23 @@ extern void hl_bilinear_forward(const real* inData, const real ratioW); /** -* @brief Bilinear interpolation backward. -* -* @param[out] inGrad input gradient. -* @param[in] inImgH input image height. -* @param[in] inImgW input image width. -* @param[in] inputH input batchSize. -* @param[in] inputW input image data dim. -* @param[in] outGrad output gradient. -* @param[in] outImgH output image height. -* @param[in] outImgW output image width. -* @param[in] outputH output batchSize. -* @param[in] outputW output image data dim. -* @param[in] numChannels number of channels. -* @param[in] ratioH inImgH / outImgH. -* @param[in] ratioW inImgW / outImgW. -* -*/ + * @brief Bilinear interpolation backward. + * + * @param[out] inGrad input gradient. + * @param[in] inImgH input image height. + * @param[in] inImgW input image width. + * @param[in] inputH input batchSize. + * @param[in] inputW input image data dim. + * @param[in] outGrad output gradient. + * @param[in] outImgH output image height. + * @param[in] outImgW output image width. + * @param[in] outputH output batchSize. + * @param[in] outputW output image data dim. + * @param[in] numChannels number of channels. + * @param[in] ratioH inImgH / outImgH. + * @param[in] ratioW inImgW / outImgW. + * + */ extern void hl_bilinear_backward(real* inGrad, const size_t inImgH, const size_t inImgW, diff --git a/paddle/cuda/src/hl_cuda_cublas.cc b/paddle/cuda/src/hl_cuda_cublas.cc index 4a56a7a9d06..6163209e9bc 100644 --- a/paddle/cuda/src/hl_cuda_cublas.cc +++ b/paddle/cuda/src/hl_cuda_cublas.cc @@ -16,7 +16,7 @@ limitations under the License. */ #include #include "hl_cuda.h" #include "hl_thread.ph" -#include "paddle/utils/DynamicLoad.h" +#include "paddle/utils/DynamicLoader.h" #include "paddle/utils/Logging.h" namespace dynload { diff --git a/paddle/cuda/src/hl_cuda_cudnn.cc b/paddle/cuda/src/hl_cuda_cudnn.cc index c8d321f1ad1..c53a5636829 100644 --- a/paddle/cuda/src/hl_cuda_cudnn.cc +++ b/paddle/cuda/src/hl_cuda_cudnn.cc @@ -17,7 +17,7 @@ limitations under the License. */ #include #include "hl_cuda_cudnn.ph" #include "hl_thread.ph" -#include "paddle/utils/DynamicLoad.h" +#include "paddle/utils/DynamicLoader.h" #include "paddle/utils/Logging.h" DEFINE_int32(cudnn_conv_workspace_limit_in_mb, diff --git a/paddle/cuda/src/hl_cuda_device.cc b/paddle/cuda/src/hl_cuda_device.cc index 43841411b8a..f8ce8cb428a 100644 --- a/paddle/cuda/src/hl_cuda_device.cc +++ b/paddle/cuda/src/hl_cuda_device.cc @@ -24,7 +24,7 @@ limitations under the License. */ #include "hl_cuda.ph" #include "hl_thread.ph" #include "paddle/utils/Logging.h" -#include "paddle/utils/DynamicLoad.h" +#include "paddle/utils/DynamicLoader.h" // clang-format on namespace dynload { diff --git a/paddle/cuda/src/hl_warpctc_wrap.cc b/paddle/cuda/src/hl_warpctc_wrap.cc index 5a4de24ced0..9f812dd0dea 100644 --- a/paddle/cuda/src/hl_warpctc_wrap.cc +++ b/paddle/cuda/src/hl_warpctc_wrap.cc @@ -14,7 +14,7 @@ limitations under the License. */ #include "hl_warpctc_wrap.h" #include -#include "paddle/utils/DynamicLoad.h" +#include "paddle/utils/DynamicLoader.h" #include "paddle/utils/Logging.h" namespace dynload { diff --git a/paddle/function/BufferArgTest.cpp b/paddle/function/BufferArgTest.cpp index 1744f377808..f1a234ab1a1 100644 --- a/paddle/function/BufferArgTest.cpp +++ b/paddle/function/BufferArgTest.cpp @@ -12,8 +12,8 @@ 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 "BufferArg.h" #include +#include "BufferArg.h" #include "paddle/math/MemoryHandle.h" namespace paddle { diff --git a/paddle/function/CosSimOp.cpp b/paddle/function/CosSimOp.cpp index 7ece7b2dfed..e44581ae106 100644 --- a/paddle/function/CosSimOp.cpp +++ b/paddle/function/CosSimOp.cpp @@ -165,12 +165,12 @@ void CosSimBackward(const CpuMatrix& out_grad, real reciprocal_square_sum_x = 1.0f / square_sum_x; real reciprocal_square_sum_y = 1.0f / square_sum_y; for (size_t j = 0; j < dim; ++j) { - prev_grad_x[j] += - out[i] * grad[i] * (prev_out_y[j] * reciprocal_xy - - prev_out_x[j] * reciprocal_square_sum_x); - prev_grad_y[j] += - out[i] * grad[i] * (prev_out_x[j] * reciprocal_xy - - prev_out_y[j] * reciprocal_square_sum_y); + prev_grad_x[j] += out[i] * grad[i] * + (prev_out_y[j] * reciprocal_xy - + prev_out_x[j] * reciprocal_square_sum_x); + prev_grad_y[j] += out[i] * grad[i] * + (prev_out_x[j] * reciprocal_xy - + prev_out_y[j] * reciprocal_square_sum_y); } } } diff --git a/paddle/function/FunctionTest.cpp b/paddle/function/FunctionTest.cpp index fdf7e631e5a..f9ea7c7e4f6 100644 --- a/paddle/function/FunctionTest.cpp +++ b/paddle/function/FunctionTest.cpp @@ -12,8 +12,8 @@ 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 "Function.h" #include +#include "Function.h" #include "paddle/math/SparseMatrix.h" namespace paddle { diff --git a/paddle/function/MulOpTest.cpp b/paddle/function/MulOpTest.cpp index 8748eb0d79f..8753057ebf7 100644 --- a/paddle/function/MulOpTest.cpp +++ b/paddle/function/MulOpTest.cpp @@ -74,9 +74,9 @@ TEST(MulOp, DDDMatrixMul) { } /** - * C += A * B, B, C dense, A sparse - * dense = sparse * dense - */ + * C += A * B, B, C dense, A sparse + * dense = sparse * dense + */ void testFuncDSparseDMatrix( size_t dimM, size_t dimN, size_t dimK, size_t nnz, SparseFormat FORMAT) { real scaleT = 1.0; @@ -119,9 +119,9 @@ TEST(MuLOp, DSparseDMul) { } /** - * C += A * B, A, C dense, B sparse - * dense = dense * sparse - */ + * C += A * B, A, C dense, B sparse + * dense = dense * sparse + */ void testFuncDDSparseMatrix( size_t dimM, size_t dimN, size_t dimK, size_t nnz, SparseFormat FORMAT) { real scaleT = 1.0; @@ -165,9 +165,9 @@ TEST(MulOp, DDSparseMul) { } /** - * C += A * B, A sparse, B, C dense - * sparse = dense * dense - */ + * C += A * B, A sparse, B, C dense + * sparse = dense * dense + */ void testFuncSparseDDMatrix( size_t dimM, size_t dimN, size_t dimK, size_t nnz, SparseFormat FORMAT) { real scaleT = 1.0; diff --git a/paddle/function/TensorShapeTest.cpp b/paddle/function/TensorShapeTest.cpp index 45a2e106e7f..e19afe0c4d5 100644 --- a/paddle/function/TensorShapeTest.cpp +++ b/paddle/function/TensorShapeTest.cpp @@ -12,8 +12,8 @@ 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 "TensorShape.h" #include +#include "TensorShape.h" namespace paddle { diff --git a/paddle/function/TensorTypeTest.cpp b/paddle/function/TensorTypeTest.cpp index e50e46f3e99..5b5c504ae2a 100644 --- a/paddle/function/TensorTypeTest.cpp +++ b/paddle/function/TensorTypeTest.cpp @@ -12,8 +12,8 @@ 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 "TensorType.h" #include +#include "TensorType.h" namespace paddle { diff --git a/paddle/gserver/dataproviders/PyDataProvider.cpp b/paddle/gserver/dataproviders/PyDataProvider.cpp index b53790e764b..e23051cd8ef 100644 --- a/paddle/gserver/dataproviders/PyDataProvider.cpp +++ b/paddle/gserver/dataproviders/PyDataProvider.cpp @@ -194,8 +194,8 @@ void PyDataProvider::fillSlotsByStr(const std::string& samples) { auto& slot = slots_[j]; CHECK(SlotDef::INDEX >= slot.type || SlotDef::STRING == slot.type) << " Slot type:" << slot.type << " is out of range."; - CHECK_GE(slot.type, SlotDef::VECTOR_DENSE) << " Slot type:" << slot.type - << " is out of range."; + CHECK_GE(slot.type, SlotDef::VECTOR_DENSE) + << " Slot type:" << slot.type << " is out of range."; switch (slot.type) { case SlotDef::VECTOR_DENSE: fillDenseSlot(slot, data, dataEnd); diff --git a/paddle/gserver/evaluators/Evaluator.cpp b/paddle/gserver/evaluators/Evaluator.cpp index 9db6d252d97..56cf9ac7809 100644 --- a/paddle/gserver/evaluators/Evaluator.cpp +++ b/paddle/gserver/evaluators/Evaluator.cpp @@ -446,9 +446,9 @@ real AucEvaluator::evalImp(std::vector& arguments) { for (size_t i = 0; i < insNum; ++i) { real value = outputD[pos]; uint32_t binIdx = static_cast(value * kBinNum_); - CHECK(binIdx <= kBinNum_) << "bin index [" << binIdx - << "] out of range, predict value[" << value - << "]"; + CHECK(binIdx <= kBinNum_) + << "bin index [" << binIdx << "] out of range, predict value[" << value + << "]"; real w = supportWeight ? weightD[i] : 1.0; if (labelD[i] == kNegativeLabel_) { statNeg_[binIdx] += w; diff --git a/paddle/gserver/gradientmachines/GradientMachine.cpp b/paddle/gserver/gradientmachines/GradientMachine.cpp index 3eb87d9b85c..b44e4dc202f 100644 --- a/paddle/gserver/gradientmachines/GradientMachine.cpp +++ b/paddle/gserver/gradientmachines/GradientMachine.cpp @@ -21,7 +21,6 @@ limitations under the License. */ #include "MultiGradientMachine.h" #include "MultiNetwork.h" #include "NeuralNetwork.h" -#include "NeuralNetwork.h" #include "ParallelNeuralNetwork.h" #include "hl_gpu.h" diff --git a/paddle/gserver/gradientmachines/RecurrentGradientMachine.cpp b/paddle/gserver/gradientmachines/RecurrentGradientMachine.cpp index 2ab964b8fc2..01158d1dce8 100644 --- a/paddle/gserver/gradientmachines/RecurrentGradientMachine.cpp +++ b/paddle/gserver/gradientmachines/RecurrentGradientMachine.cpp @@ -637,7 +637,7 @@ void RecurrentGradientMachine::removeBeamSearchStatisticsCallbacks() { /* create scattered id infomation for all realLayer of inFrameLines one time. * If hasSubseq, will also create scattered sequenceStartPositions infomation * for all realLayer of inFrameLines one time. -*/ + */ void RecurrentGradientMachine::createInFrameInfo(int inlinkId, const Argument& input, diff --git a/paddle/gserver/gradientmachines/RecurrentGradientMachine.h b/paddle/gserver/gradientmachines/RecurrentGradientMachine.h index 910ca4376be..c2bc52709ab 100644 --- a/paddle/gserver/gradientmachines/RecurrentGradientMachine.h +++ b/paddle/gserver/gradientmachines/RecurrentGradientMachine.h @@ -107,18 +107,18 @@ public: DropCallback; /** - * @brief NormOrDropNodeCallback - * - * Normalize a path's probabilities or just drop it by modifying path.logProb - * - * The first parameter is sequence index in a batch - * - * The second parameter is path.ids - * - * The third parameter is probabilites for each node in this path. - * - * The fourth parameter is the probability of the whole path. - */ + * @brief NormOrDropNodeCallback + * + * Normalize a path's probabilities or just drop it by modifying path.logProb + * + * The first parameter is sequence index in a batch + * + * The second parameter is path.ids + * + * The third parameter is probabilites for each node in this path. + * + * The fourth parameter is the probability of the whole path. + */ typedef std::function&, std::vector&, real*)> NormOrDropNodeCallback; @@ -348,9 +348,9 @@ protected: int targetInfoInlinkId_; /* create scattered id infomation for all realLayer of inFrameLines one time. - * If hasSubseq, will also create scattered sequenceStartPositions infomation - * for all realLayer of inFrameLines one time. - */ + * If hasSubseq, will also create scattered sequenceStartPositions infomation + * for all realLayer of inFrameLines one time. + */ void createInFrameInfo(int inlinks_id, const Argument& input, PassType passType); diff --git a/paddle/gserver/layers/Layer.cpp b/paddle/gserver/layers/Layer.cpp index 125aaf947f3..00dd8a8508b 100644 --- a/paddle/gserver/layers/Layer.cpp +++ b/paddle/gserver/layers/Layer.cpp @@ -263,8 +263,9 @@ void Layer::zeroGrad() { } void Layer::initNeedFlags() { - auto initFlag = [this]( - bool& flag, bool (Layer::*flagQueryFunc)() const, ParameterType type) { + auto initFlag = [this](bool& flag, + bool (Layer::*flagQueryFunc)() const, + ParameterType type) { flag = false; if (biasParameter_ && biasParameter_->hasType(type)) { flag = true; diff --git a/paddle/gserver/layers/Layer.h b/paddle/gserver/layers/Layer.h index 47182c9ecc6..0ed482889d0 100644 --- a/paddle/gserver/layers/Layer.h +++ b/paddle/gserver/layers/Layer.h @@ -106,9 +106,9 @@ protected: public: /** - * Wait until all input value ready. - * Called before Layer::forward() function. - */ + * Wait until all input value ready. + * Called before Layer::forward() function. + */ virtual void waitInputValue(); /** @@ -118,9 +118,9 @@ public: virtual void copyOutputToOtherDevice(); /** - * Wait until all output grad ready and merge them to output_.grad. - * Called before Layer::backward() function. - */ + * Wait until all output grad ready and merge them to output_.grad. + * Called before Layer::backward() function. + */ virtual void waitAndMergeOutputGrad(); /** diff --git a/paddle/gserver/layers/RotateLayer.h b/paddle/gserver/layers/RotateLayer.h index 1a64d4d5a51..d05c2065cb1 100644 --- a/paddle/gserver/layers/RotateLayer.h +++ b/paddle/gserver/layers/RotateLayer.h @@ -29,7 +29,7 @@ namespace paddle { * * The config file api is rotate_layer * -*/ + */ class RotateLayer : public Layer { public: diff --git a/paddle/gserver/layers/SequencePoolLayer.cpp b/paddle/gserver/layers/SequencePoolLayer.cpp index 8c495020115..235d9a9b0f0 100644 --- a/paddle/gserver/layers/SequencePoolLayer.cpp +++ b/paddle/gserver/layers/SequencePoolLayer.cpp @@ -60,7 +60,7 @@ void SequencePoolLayer::forward(PassType passType) { * thus, in this case, output_ has no sequenceStartPositions. * If type_ = kSeq, seq has sub-seq degrades to a seq, thus, only in this * case, we should compute the new sequenceStartPositions. - */ + */ if (type_) { CHECK(input.subSequenceStartPositions) << "when trans_type = seq, input must hasSubseq"; diff --git a/paddle/gserver/tests/test_RecurrentLayer.cpp b/paddle/gserver/tests/test_RecurrentLayer.cpp index 16ab0e6aecb..e8e1db51ae6 100644 --- a/paddle/gserver/tests/test_RecurrentLayer.cpp +++ b/paddle/gserver/tests/test_RecurrentLayer.cpp @@ -292,26 +292,27 @@ void checkRecurrentLayer(LayerConfig layerConfig, TestRecurrentLayer testGpu(layerConfig, true, gpuBatch); testCpu.init(batchSize); testGpu.init(batchSize); - auto checkError = []( - MatrixPtr cpu, MatrixPtr gpu, int numSequences, const char* str) { - CpuMatrix check(gpu->getHeight(), gpu->getWidth()); - check.copyFrom(*gpu); - int height = cpu->getHeight(); - int width = cpu->getWidth(); - const real* data1 = cpu->getData(); - const real* data2 = check.getData(); - int count = 0; - for (int i = 0; i < height; i++) { - for (int j = 0; j < width; j++) { - if (fabs(data1[i * width + j] - data2[i * width + j]) / numSequences > - 1e-4) { - count++; + auto checkError = + [](MatrixPtr cpu, MatrixPtr gpu, int numSequences, const char* str) { + CpuMatrix check(gpu->getHeight(), gpu->getWidth()); + check.copyFrom(*gpu); + int height = cpu->getHeight(); + int width = cpu->getWidth(); + const real* data1 = cpu->getData(); + const real* data2 = check.getData(); + int count = 0; + for (int i = 0; i < height; i++) { + for (int j = 0; j < width; j++) { + if (fabs(data1[i * width + j] - data2[i * width + j]) / + numSequences > + 1e-4) { + count++; + } + } } - } - } - EXPECT_EQ(count, 0) << "[" << str << "]" - << "There are " << count << " different element."; - }; + EXPECT_EQ(count, 0) << "[" << str << "]" + << "There are " << count << " different element."; + }; T* cpuLayer = dynamic_cast(testCpu.testLayer_.get()); T* gpuLayer = dynamic_cast(testGpu.testLayer_.get()); diff --git a/paddle/math/MathFunctions.cpp b/paddle/math/MathFunctions.cpp index d78ab3422d6..178fce5b0a9 100644 --- a/paddle/math/MathFunctions.cpp +++ b/paddle/math/MathFunctions.cpp @@ -15,7 +15,7 @@ limitations under the License. */ #include "MathFunctions.h" #include "hl_matrix_apply.cuh" #include "hl_matrix_ops.cuh" -#include "paddle/utils/DynamicLoad.h" +#include "paddle/utils/DynamicLoader.h" namespace dynload { @@ -32,7 +32,7 @@ void* lapack_dso_handle = nullptr; #define DYNAMIC_LOAD_LAPACK_WRAP(__name) \ struct DynLoad__##__name { \ template \ - auto operator()(Args... args)->decltype(__name(args...)) { \ + auto operator()(Args... args) -> decltype(__name(args...)) { \ using lapack_func = decltype(__name(args...)) (*)(Args...); \ std::call_once(lapack_dso_flag, GetLapackDsoHandle, &lapack_dso_handle); \ void* p_##__name = dlsym(lapack_dso_handle, #__name); \ @@ -41,24 +41,27 @@ void* lapack_dso_handle = nullptr; } __name; // struct DynLoad__##__name // clang-format off -#ifdef PADDLE_USE_LAPACK #ifdef PADDLE_USE_ATLAS - #define LAPACK_ROUTINE_EACH(__macro) \ - __macro(clapack_sgetrf) \ - __macro(clapack_dgetrf) \ - __macro(clapack_sgetri) \ - __macro(clapack_dgetri) + #define PADDLE_SGETRF clapack_sgetrf + #define PADDLE_DGETRF clapack_dgetrf + #define PADDLE_SGETRI clapack_sgetri + #define PADDLE_DGETRI clapack_dgetri #else - #define LAPACK_ROUTINE_EACH(__macro) \ - __macro(LAPACKE_sgetrf) \ - __macro(LAPACKE_dgetrf) \ - __macro(LAPACKE_sgetri) \ - __macro(LAPACKE_dgetri) -#endif + #define PADDLE_SGETRF LAPACKE_sgetrf + #define PADDLE_DGETRF LAPACKE_dgetrf + #define PADDLE_SGETRI LAPACKE_sgetri + #define PADDLE_DGETRI LAPACKE_dgetri +#endif + +#define LAPACK_ROUTINE_EACH(__macro) \ + __macro(PADDLE_SGETRF) \ + __macro(PADDLE_DGETRF) \ + __macro(PADDLE_SGETRI) \ + __macro(PADDLE_DGETRI) +// clang-format on + LAPACK_ROUTINE_EACH(DYNAMIC_LOAD_LAPACK_WRAP) -#endif -// clang-format on } // namespace dynload namespace paddle { @@ -130,16 +133,7 @@ int getrf(const CBLAS_ORDER order, float* A, const int lda, int* ipiv) { -#ifdef PADDLE_USE_LAPACK -#ifdef PADDLE_USE_ATLAS - return dynload::clapack_sgetrf(order, M, N, A, lda, ipiv); -#else - return dynload::LAPACKE_sgetrf(order, M, N, A, lda, ipiv); -#endif -#else - LOG(FATAL) << "Not implemented"; -#endif - return 0; + return dynload::PADDLE_SGETRF(order, M, N, A, lda, ipiv); } template <> @@ -149,16 +143,7 @@ int getrf(const CBLAS_ORDER order, double* A, const int lda, int* ipiv) { -#ifdef PADDLE_USE_LAPACK -#ifdef PADDLE_USE_ATLAS - return dynload::clapack_dgetrf(order, M, N, A, lda, ipiv); -#else - return dynload::LAPACKE_dgetrf(order, M, N, A, lda, ipiv); -#endif -#else - LOG(FATAL) << "Not implemented"; -#endif - return 0; + return dynload::PADDLE_DGETRF(order, M, N, A, lda, ipiv); } template <> @@ -167,16 +152,7 @@ int getri(const CBLAS_ORDER order, float* A, const int lda, const int* ipiv) { -#ifdef PADDLE_USE_LAPACK -#ifdef PADDLE_USE_ATLAS - return dynload::clapack_sgetri(order, N, A, lda, ipiv); -#else - return dynload::LAPACKE_sgetri(order, N, A, lda, ipiv); -#endif -#else - LOG(FATAL) << "Not implemented"; -#endif - return 0; + return dynload::PADDLE_SGETRI(order, N, A, lda, ipiv); } template <> @@ -185,15 +161,7 @@ int getri(const CBLAS_ORDER order, double* A, const int lda, const int* ipiv) { -#ifdef PADDLE_USE_LAPACK -#ifdef PADDLE_USE_ATLAS - return dynload::clapack_dgetri(order, N, A, lda, ipiv); -#else - return dynload::LAPACKE_dgetri(order, N, A, lda, ipiv); -#endif -#else - LOG(FATAL) << "Not implemented"; -#endif + return dynload::PADDLE_DGETRI(order, N, A, lda, ipiv); return 0; } diff --git a/paddle/math/MathFunctions.h b/paddle/math/MathFunctions.h index 9f8f84a87c5..c8559eefd83 100644 --- a/paddle/math/MathFunctions.h +++ b/paddle/math/MathFunctions.h @@ -17,14 +17,11 @@ limitations under the License. */ #ifdef PADDLE_USE_MKL #include -#ifdef PADDLE_USE_LAPACK #include -#endif #else extern "C" { #include } -#ifdef PADDLE_USE_LAPACK #ifdef PADDLE_USE_ATLAS extern "C" { #include @@ -33,7 +30,6 @@ extern "C" { #include #endif #endif -#endif #include diff --git a/paddle/math/MatrixBitCode.cpp b/paddle/math/MatrixBitCode.cpp index cea912d3ca0..0ea387d0208 100644 --- a/paddle/math/MatrixBitCode.cpp +++ b/paddle/math/MatrixBitCode.cpp @@ -174,8 +174,10 @@ void CpuMatrix::mulByBitCode(size_t numClasses, const IVector& codes, const Matrix& weight, const Matrix& input) { - auto op = []( - real& t, const real* weightRow, const real* inputRow, size_t inputDim) { + auto op = [](real& t, + const real* weightRow, + const real* inputRow, + size_t inputDim) { real sum = 0; for (size_t k = 0; k < inputDim; ++k) { sum += weightRow[k] * inputRow[k]; @@ -193,12 +195,12 @@ void CpuMatrix::mulByBitCodeBackwardWeight(size_t numClasses, const IVector& codes, Matrix& weight, const Matrix& input) { - auto op = []( - const real t, real* weightRow, const real* inputRow, size_t inputDim) { - for (size_t k = 0; k < inputDim; ++k) { - weightRow[k] += t * inputRow[k]; - } - }; + auto op = + [](const real t, real* weightRow, const real* inputRow, size_t inputDim) { + for (size_t k = 0; k < inputDim; ++k) { + weightRow[k] += t * inputRow[k]; + } + }; mulByBitCodeT(op, SimpleCodeTable(numClasses), codes, *this, weight, input); } @@ -210,12 +212,12 @@ void CpuMatrix::mulByBitCodeBackwardError(size_t numClasses, const IVector& codes, const Matrix& weight, Matrix& input) { - auto op = []( - const real t, const real* weightRow, real* inputRow, size_t inputDim) { - for (size_t k = 0; k < inputDim; ++k) { - inputRow[k] += t * weightRow[k]; - } - }; + auto op = + [](const real t, const real* weightRow, real* inputRow, size_t inputDim) { + for (size_t k = 0; k < inputDim; ++k) { + inputRow[k] += t * weightRow[k]; + } + }; mulByBitCodeT(op, SimpleCodeTable(numClasses), codes, *this, weight, input); } diff --git a/paddle/math/tests/TensorCheck.h b/paddle/math/tests/TensorCheck.h index 5bc4a03067a..86f5982e758 100644 --- a/paddle/math/tests/TensorCheck.h +++ b/paddle/math/tests/TensorCheck.h @@ -183,8 +183,8 @@ void TensorCheck(AssertEq compare, template void TensorCheck(AssertEq compare, real args1, real args2) { - EXPECT_EQ(compare(args1, args2), true) << "[Test error] args1 = " << args1 - << ", args2 = " << args2; + EXPECT_EQ(compare(args1, args2), true) + << "[Test error] args1 = " << args1 << ", args2 = " << args2; } template diff --git a/paddle/math/tests/TestUtils.h b/paddle/math/tests/TestUtils.h index c3020961880..713f407f496 100644 --- a/paddle/math/tests/TestUtils.h +++ b/paddle/math/tests/TestUtils.h @@ -37,7 +37,7 @@ limitations under the License. */ * * AutoCompare test; * test.cmpWithoutArg(function, height, width) -*/ + */ #include #include "TensorCheck.h" diff --git a/paddle/math/tests/test_SIMDFunctions.cpp b/paddle/math/tests/test_SIMDFunctions.cpp index e8f9b26ff24..e4f8cf4c24b 100644 --- a/paddle/math/tests/test_SIMDFunctions.cpp +++ b/paddle/math/tests/test_SIMDFunctions.cpp @@ -126,15 +126,15 @@ TEST(SIMDFunction, decayL1_WithLR) { typedef std::function DecayL1MethodType; - DecayL1MethodType naive = []( - float* d, float* s, float* lr, float l, size_t len) { - paddle::simd::naive::decayL1(d, s, lr, l, len); - }; - - DecayL1MethodType simd = []( - float* d, float* s, float* lr, float l, size_t len) { - paddle::simd::decayL1(d, s, lr, l, len); - }; + DecayL1MethodType naive = + [](float* d, float* s, float* lr, float l, size_t len) { + paddle::simd::naive::decayL1(d, s, lr, l, len); + }; + + DecayL1MethodType simd = + [](float* d, float* s, float* lr, float l, size_t len) { + paddle::simd::decayL1(d, s, lr, l, len); + }; naive(dest.get(), src.get(), lr.get(), lambda, VECTOR_LEN); simd(simd_dest.get(), src.get(), lr.get(), lambda, VECTOR_LEN); diff --git a/paddle/math/tests/test_matrixCompare.cpp b/paddle/math/tests/test_matrixCompare.cpp index 5210fe3fa1f..3b1b0065af3 100644 --- a/paddle/math/tests/test_matrixCompare.cpp +++ b/paddle/math/tests/test_matrixCompare.cpp @@ -21,6 +21,7 @@ limitations under the License. */ #include "paddle/math/Matrix.h" #include "paddle/math/SparseMatrix.h" #include "paddle/testing/TestUtil.h" +#include "paddle/utils/DynamicLoader.h" #include "paddle/utils/Stat.h" #include "paddle/utils/Util.h" @@ -235,10 +236,15 @@ TEST(Matrix, unary) { testMatrixTranspose(height, width); testMatrixRotate(height, width); } -// inverse -#ifdef PADDLE_USE_LAPACK - testMatrixInverse(height); -#endif + // inverse matrix + void** dso_handler = nullptr; + GetLapackDsoHandle(dso_handler); + if (nullptr == *dso_handler) { + LOG(WARNING) << "Failed to find liblapack.so, please specify its path " + "using LD_LIBRARY_PATH."; + } else { + testMatrixInverse(height); + } } } diff --git a/paddle/parameter/Argument.cpp b/paddle/parameter/Argument.cpp index 6d9365af2d1..10e4a0ae68f 100644 --- a/paddle/parameter/Argument.cpp +++ b/paddle/parameter/Argument.cpp @@ -379,7 +379,7 @@ void Argument::concat(const std::vector& args, } auto copyArg = [batchSize, stream]( - MatrixPtr& dst, MatrixPtr src, int startRow, bool useGpu) { + MatrixPtr& dst, MatrixPtr src, int startRow, bool useGpu) { if (!src) { dst.reset(); return; @@ -395,29 +395,31 @@ void Argument::concat(const std::vector& args, tmpMatrix->copyFrom(*src, stream); }; - auto copyIds = [batchSize, stream]( - IVectorPtr& dst, const IVectorPtr& src, int startRow, bool useGpu) { - if (!src) { - dst.reset(); - return; - } - IVector::resizeOrCreate(dst, batchSize, useGpu); - dst->subVec(startRow, src->getSize())->copyFrom(*src, stream); - }; - - auto copyStrs = [batchSize, stream]( - SVectorPtr& dst, const SVectorPtr& src, int startRow, bool useGpu) { - if (!src) { - dst.reset(); - return; - } - if (!dst) { - dst = std::make_shared>(batchSize); - } else { - dst->resize(batchSize); - } - std::copy(src->begin(), src->end(), dst->begin() + startRow); - }; + auto copyIds = + [batchSize, stream]( + IVectorPtr& dst, const IVectorPtr& src, int startRow, bool useGpu) { + if (!src) { + dst.reset(); + return; + } + IVector::resizeOrCreate(dst, batchSize, useGpu); + dst->subVec(startRow, src->getSize())->copyFrom(*src, stream); + }; + + auto copyStrs = + [batchSize, stream]( + SVectorPtr& dst, const SVectorPtr& src, int startRow, bool useGpu) { + if (!src) { + dst.reset(); + return; + } + if (!dst) { + dst = std::make_shared>(batchSize); + } else { + dst->resize(batchSize); + } + std::copy(src->begin(), src->end(), dst->begin() + startRow); + }; auto copySequencePos = [](ICpuGpuVectorPtr& dstSeq, const ICpuGpuVectorPtr& srcSeq, diff --git a/paddle/parameter/AverageOptimizer.cpp b/paddle/parameter/AverageOptimizer.cpp index e51ca565209..5db5ddd10c4 100644 --- a/paddle/parameter/AverageOptimizer.cpp +++ b/paddle/parameter/AverageOptimizer.cpp @@ -155,8 +155,9 @@ ParameterOptimizer::TraverseCallback AverageOptimizer::restore() { return nullptr; } - return []( - const VectorPtr vecs[], const ParameterConfig& config, size_t sparseId) { + return [](const VectorPtr vecs[], + const ParameterConfig& config, + size_t sparseId) { vecs[PARAMETER_VALUE]->copyFrom(*vecs[PARAMETER_GRADIENT]); vecs[PARAMETER_GRADIENT]->zeroMem(); }; diff --git a/paddle/parameter/FirstOrderOptimizer.h b/paddle/parameter/FirstOrderOptimizer.h index 095019b74f4..caa78acd98e 100644 --- a/paddle/parameter/FirstOrderOptimizer.h +++ b/paddle/parameter/FirstOrderOptimizer.h @@ -126,7 +126,7 @@ protected: /* * AdaDelta Optimization. * http://www.matthewzeiler.com/pubs/googleTR2012/googleTR2012.pdf -*/ + */ class AdaDeltaParameterOptimizer : public ParameterOptimizer { public: explicit AdaDeltaParameterOptimizer(const OptimizationConfig& optConfig) diff --git a/paddle/parameter/Parameter.cpp b/paddle/parameter/Parameter.cpp index b8efabbe2a0..7eb90920ae3 100644 --- a/paddle/parameter/Parameter.cpp +++ b/paddle/parameter/Parameter.cpp @@ -352,8 +352,8 @@ bool Parameter::load(std::istream& s) { Header header; CHECK(s.read(reinterpret_cast(&header), sizeof(header))) << "Fail to read parameter " << getName(); - CHECK_EQ(header.version, kFormatVersion) << "Incorrect format version: " - << header.version; + CHECK_EQ(header.version, kFormatVersion) + << "Incorrect format version: " << header.version; CHECK_EQ(header.size, getSize()) << "The size (" << header.size << ") in the file does not match the size " << "(" << getSize() << ") of the parameter: " << getName(); diff --git a/paddle/pserver/LightNetwork.cpp b/paddle/pserver/LightNetwork.cpp index 8c8ba0a2e51..09175f249fc 100644 --- a/paddle/pserver/LightNetwork.cpp +++ b/paddle/pserver/LightNetwork.cpp @@ -359,8 +359,8 @@ void SocketClient::TcpClient(const std::string &serverAddr, int serverPort) { #if defined(__OSX__) || defined(__APPLE__) server = getipnodebyname(serverAddr.c_str(), AF_INET, AI_DEFAULT, &errRet); - CHECK_NE(HOST_NOT_FOUND, errRet) << "ERROR, no such host: " << serverAddr - << " ret = " << errRet; + CHECK_NE(HOST_NOT_FOUND, errRet) + << "ERROR, no such host: " << serverAddr << " ret = " << errRet; CHECK(server) << "getipnodebyname error!"; #else struct hostent hostinfo; diff --git a/paddle/pserver/ParameterClient2.cpp b/paddle/pserver/ParameterClient2.cpp index a97859f83fe..df80a322e1b 100644 --- a/paddle/pserver/ParameterClient2.cpp +++ b/paddle/pserver/ParameterClient2.cpp @@ -549,9 +549,9 @@ PServerVector ParameterClient2::createVector() { if (handle == -1) { handle = response.handle(); } else { - CHECK_EQ(handle, response.handle()) << "Inconsistent handle from client" - << &response - &responses[0] << " " - << handle << " " << response.handle(); + CHECK_EQ(handle, response.handle()) + << "Inconsistent handle from client" << &response - &responses[0] + << " " << handle << " " << response.handle(); } } return PServerVector{handle}; @@ -579,9 +579,9 @@ PServerMatrix ParameterClient2::createMatrix(int32_t numCols) { if (handle == -1) { handle = response.handle(); } else { - CHECK_EQ(handle, response.handle()) << "Inconsistent handle from client" - << &response - &responses[0] << " " - << handle << " " << response.handle(); + CHECK_EQ(handle, response.handle()) + << "Inconsistent handle from client" << &response - &responses[0] + << " " << handle << " " << response.handle(); } } return PServerMatrix{handle}; diff --git a/paddle/pserver/ParameterServer2.cpp b/paddle/pserver/ParameterServer2.cpp index 19ff40ba7e9..a72dd3dc08c 100644 --- a/paddle/pserver/ParameterServer2.cpp +++ b/paddle/pserver/ParameterServer2.cpp @@ -1213,8 +1213,8 @@ void ParameterServer2::loadValueVector(const LoadValueRequest& request, CHECK_EQ(header.size, (size_t)size_) << "The size (" << header.size << ") in the file does not match the size " << "(" << size_ << ") of the pserver: " << serverId_; - CHECK_EQ(header.valueSize, sizeof(real)) << "Unsupported valueSize " - << header.valueSize; + CHECK_EQ(header.valueSize, sizeof(real)) + << "Unsupported valueSize " << header.valueSize; CHECK(fs.read(reinterpret_cast(vec.getData()), header.size * sizeof(real))); diff --git a/paddle/pserver/ParameterServer2.h b/paddle/pserver/ParameterServer2.h index 0f5a5895907..0344196d7b8 100644 --- a/paddle/pserver/ParameterServer2.h +++ b/paddle/pserver/ParameterServer2.h @@ -545,11 +545,11 @@ protected: std::vector* buffers); const ParameterConfig& getParameterConfig(const ParameterBlock& block) { - CHECK_LT(block.para_id(), -1UL) << "invalid parameter id:" - << block.para_id(); + CHECK_LT(block.para_id(), -1UL) + << "invalid parameter id:" << block.para_id(); const auto it = configMap_.find(block.para_id()); - CHECK(it != configMap_.end()) << "can not find parameter id: " - << block.para_id(); + CHECK(it != configMap_.end()) + << "can not find parameter id: " << block.para_id(); return it->second; } diff --git a/paddle/pserver/ProtoServer.cpp b/paddle/pserver/ProtoServer.cpp index 410317ece28..52344123a6b 100644 --- a/paddle/pserver/ProtoServer.cpp +++ b/paddle/pserver/ProtoServer.cpp @@ -41,8 +41,8 @@ void ProtoServer::handleRequest(std::unique_ptr msgReader, void ProtoServer::registerServiceFunctionImp(const std::string& funcName, ServiceFunction func) { - CHECK(!nameToFuncMap_.count(funcName)) << "Duplicated registration: " - << funcName; + CHECK(!nameToFuncMap_.count(funcName)) + << "Duplicated registration: " << funcName; nameToFuncMap_[funcName] = func; } diff --git a/paddle/trainer/TrainerInternal.cpp b/paddle/trainer/TrainerInternal.cpp index 4c5d4a0913a..97b401688e9 100644 --- a/paddle/trainer/TrainerInternal.cpp +++ b/paddle/trainer/TrainerInternal.cpp @@ -97,7 +97,7 @@ void TrainerInternal::trainOneBatch(int64_t batchId, } UpdateCallback updateCallback = [this, showStats, ¶Stats]( - Parameter* para) { + Parameter* para) { if (showStats) { //! @TODO(yuyang18) Show stats is actually a ParameterHook, refactor // it diff --git a/paddle/trainer/tests/picojson.h b/paddle/trainer/tests/picojson.h index 23bfa164080..4aa64961d09 100644 --- a/paddle/trainer/tests/picojson.h +++ b/paddle/trainer/tests/picojson.h @@ -1059,14 +1059,14 @@ inline bool operator==(const value& x, const value& y) { } inline bool operator!=(const value& x, const value& y) { return !(x == y); } -} +} // namespace picojson namespace std { template <> inline void swap(picojson::value& x, picojson::value& y) { x.swap(y); } -} +} // namespace std inline std::istream& operator>>(std::istream& is, picojson::value& x) { picojson::set_last_error(std::string()); diff --git a/paddle/utils/BarrierStat.h b/paddle/utils/BarrierStat.h index a9c925eff66..817ab8fc9d6 100644 --- a/paddle/utils/BarrierStat.h +++ b/paddle/utils/BarrierStat.h @@ -344,14 +344,14 @@ private: } while (0); // check end barrier -#define __CHECK_BARRIER_TIMER(set, statName, numConnThreads, ...) \ - do { \ - std::string internalName = \ - std::string(statName) + std::string(__VA_ARGS__); \ - BarrierStatPtr __stat = \ - (set).getStat(numConnThreads, internalName, BARRIER_END); \ - PCHECK(__stat->checkPassBarrier()) << internalName \ - << ": invalid barrier data"; \ +#define __CHECK_BARRIER_TIMER(set, statName, numConnThreads, ...) \ + do { \ + std::string internalName = \ + std::string(statName) + std::string(__VA_ARGS__); \ + BarrierStatPtr __stat = \ + (set).getStat(numConnThreads, internalName, BARRIER_END); \ + PCHECK(__stat->checkPassBarrier()) \ + << internalName << ": invalid barrier data"; \ } while (0); /* diff --git a/paddle/utils/ClassRegistrar.h b/paddle/utils/ClassRegistrar.h index 1ac27bafabd..e4351dbcb7d 100644 --- a/paddle/utils/ClassRegistrar.h +++ b/paddle/utils/ClassRegistrar.h @@ -62,8 +62,8 @@ public: // Create a class instance of type @type using args BaseClass* createByType(const std::string& type, CreateArgs... args) { ClassCreator creator; - CHECK(mapGet(type, creatorMap_, &creator)) << "Unknown class type: " - << type; + CHECK(mapGet(type, creatorMap_, &creator)) + << "Unknown class type: " << type; return creator(args...); } diff --git a/paddle/utils/DynamicLoad.cpp b/paddle/utils/DynamicLoader.cpp similarity index 97% rename from paddle/utils/DynamicLoad.cpp rename to paddle/utils/DynamicLoader.cpp index 8f0532942e7..368c35e1518 100644 --- a/paddle/utils/DynamicLoad.cpp +++ b/paddle/utils/DynamicLoader.cpp @@ -12,9 +12,9 @@ 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 "DynamicLoad.h" -#include "Logging.h" +#include "DynamicLoader.h" #include +#include "Logging.h" DEFINE_string(cudnn_dir, "", @@ -165,8 +165,8 @@ void GetWarpCTCDsoHandle(void** dso_handle) { void GetLapackDsoHandle(void** dso_handle) { #if defined(__APPLE__) || defined(__OSX__) - GetDsoHandleFromSearchPath(FLAGS_warpctc_dir, "liblapack.dylib", dso_handle); + GetDsoHandleFromSearchPath(FLAGS_lapack_dir, "liblapack.dylib", dso_handle); #else - GetDsoHandleFromSearchPath(FLAGS_warpctc_dir, "liblapack.so", dso_handle); + GetDsoHandleFromSearchPath(FLAGS_lapack_dir, "liblapack.so", dso_handle); #endif } diff --git a/paddle/utils/DynamicLoad.h b/paddle/utils/DynamicLoader.h similarity index 100% rename from paddle/utils/DynamicLoad.h rename to paddle/utils/DynamicLoader.h index 67f52a3a5da..9b5ad21724a 100644 --- a/paddle/utils/DynamicLoad.h +++ b/paddle/utils/DynamicLoader.h @@ -17,8 +17,8 @@ limitations under the License. */ #include #include -#include #include +#include /** * @brief load the DSO of CUBLAS -- GitLab From a621a9b2d58e0a634b1da965e5ab54a17f6475b4 Mon Sep 17 00:00:00 2001 From: Haonan Date: Tue, 2 May 2017 12:20:43 -0700 Subject: [PATCH 0155/3256] set ATLAS_CLAPACK_INC_DIR to CBLAS_INC_DIR in cblas.cmake --- cmake/cblas.cmake | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/cmake/cblas.cmake b/cmake/cblas.cmake index b8bf1bb07a1..fc083242c95 100644 --- a/cmake/cblas.cmake +++ b/cmake/cblas.cmake @@ -5,7 +5,7 @@ # If any cblas implementation found, the following variable will be set. # CBLAS_PROVIDER # one of MKL, ATLAS, OPENBLAS, REFERENCE # CBLAS_INC_DIR # the include directory for cblas. -# CBLAS_LIBS # a list of libraries should be linked by paddle. +# CBLAS_LIBS # a list of libraries should be linked by paddle. # # Each library should be full path to object file. # # User should set one of MKL_ROOT, ATLAS_ROOT, OPENBLAS_ROOT, REFERENCE_CBLAS_ROOT @@ -63,11 +63,11 @@ set(ATLAS_LIB_SEARCH_PATHS /usr/lib/atlas /usr/lib/atlas-base # special for ubuntu 14.04. ) -find_path(ATLAS_INC_DIR NAMES cblas.h +find_path(ATLAS_INC_DIR NAMES cblas.h PATHS ${ATLAS_INCLUDE_SEARCH_PATHS}) find_path(ATLAS_CLAPACK_INC_DIR NAMES clapack.h PATHS ${ATLAS_INCLUDE_SEARCH_PATHS}) -find_library(ATLAS_CBLAS_LIB NAMES cblas libcblas.so.3 +find_library(ATLAS_CBLAS_LIB NAMES cblas libcblas.so.3 PATHS ${ATLAS_LIB_SEARCH_PATHS}) find_library(ATLAS_LIB NAMES lapack_atlas liblapack_atlas.so.3 PATHS ${ATLAS_LIB_SEARCH_PATHS}) @@ -76,11 +76,12 @@ if(ATLAS_INC_DIR AND ATLAS_CBLAS_LIB AND ATLAS_LIB AND NOT CBLAS_FOUND) set(CBLAS_PROVIDER ATLAS) set(CBLAS_INC_DIR ${ATLAS_INC_DIR}) set(CBLAS_LIBRARIES ${ATLAS_LIB} ${ATLAS_CBLAS_LIB}) - add_definitions(-DPADDLE_USE_ATLAS) + add_definitions(-DPADDLE_USE_ATLAS) message(STATUS "Found ATLAS (include: ${CBLAS_INC_DIR}, library: ${CBLAS_LIBRARIES})") set(CBLAS_FOUND ON) if(ATLAS_CLAPACK_INC_DIR) add_definitions(-DPADDLE_USE_LAPACK) + set(CBLAS_INC_DIR ${ATLAS_CLAPACK_INC_DIR}) message(STATUS "Found lapack in ATLAS (include: ${ATLAS_CLAPACK_INC_DIR})") endif() return() @@ -124,7 +125,7 @@ endif() ## Then find the reference-cblas. www.netlib.org/blas/ -set(REFERENCE_CBLAS_ROOT $ENV{REFERENCE_CBLAS_ROOT} CACHE PATH +set(REFERENCE_CBLAS_ROOT $ENV{REFERENCE_CBLAS_ROOT} CACHE PATH "Folder contains reference-cblas") set(REFERENCE_CBLAS_INCLUDE_SEARCH_PATHS ${REFERENCE_CBLAS_ROOT}/include -- GitLab From 30b73ee397e171ee0e13446c9c3673742e0ab793 Mon Sep 17 00:00:00 2001 From: Haonan Date: Tue, 2 May 2017 13:55:33 -0700 Subject: [PATCH 0156/3256] Also include the path already set for CBLAS_INC_DIR --- cmake/cblas.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/cblas.cmake b/cmake/cblas.cmake index fc083242c95..aebb5d9fcb1 100644 --- a/cmake/cblas.cmake +++ b/cmake/cblas.cmake @@ -81,7 +81,7 @@ if(ATLAS_INC_DIR AND ATLAS_CBLAS_LIB AND ATLAS_LIB AND NOT CBLAS_FOUND) set(CBLAS_FOUND ON) if(ATLAS_CLAPACK_INC_DIR) add_definitions(-DPADDLE_USE_LAPACK) - set(CBLAS_INC_DIR ${ATLAS_CLAPACK_INC_DIR}) + set(CBLAS_INC_DIR ${CBLAS_INC_DIR} ${ATLAS_CLAPACK_INC_DIR}) message(STATUS "Found lapack in ATLAS (include: ${ATLAS_CLAPACK_INC_DIR})") endif() return() -- GitLab From 478745f6bcdad7b078732a620dbf254c355dae1d Mon Sep 17 00:00:00 2001 From: xuwei06 Date: Tue, 2 May 2017 14:53:44 -0700 Subject: [PATCH 0157/3256] Fix dataset typo: cretor=>creator --- python/paddle/v2/dataset/cifar.py | 4 ++-- python/paddle/v2/dataset/mnist.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/python/paddle/v2/dataset/cifar.py b/python/paddle/v2/dataset/cifar.py index 41fda1e8f24..81af0a8e66a 100644 --- a/python/paddle/v2/dataset/cifar.py +++ b/python/paddle/v2/dataset/cifar.py @@ -80,7 +80,7 @@ def train100(): def test100(): """ - CIFAR-100 test set cretor. + CIFAR-100 test set creator. It returns a reader creator, each sample in the reader is image pixels in [0, 1] and label in [0, 9]. @@ -107,7 +107,7 @@ def train10(): def test10(): """ - CIFAR-10 test set cretor. + CIFAR-10 test set creator. It returns a reader creator, each sample in the reader is image pixels in [0, 1] and label in [0, 9]. diff --git a/python/paddle/v2/dataset/mnist.py b/python/paddle/v2/dataset/mnist.py index c1347d3c66d..435556b2921 100644 --- a/python/paddle/v2/dataset/mnist.py +++ b/python/paddle/v2/dataset/mnist.py @@ -93,7 +93,7 @@ def train(): def test(): """ - MNIST test set cretor. + MNIST test set creator. It returns a reader creator, each sample in the reader is image pixels in [0, 1] and label in [0, 9]. -- GitLab From 295f871c92f7e2a60590b1f81a7cfd5d9e3e94d4 Mon Sep 17 00:00:00 2001 From: Yi Wang Date: Tue, 2 May 2017 17:00:34 -0700 Subject: [PATCH 0158/3256] Correct TLS error in Docker building --- .pre-commit-config.yaml | 8 ++++---- Dockerfile | 5 +++++ 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3402223b044..9b138576fcc 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,15 +1,15 @@ - repo: https://github.com/Lucas-C/pre-commit-hooks.git - sha: c25201a00e6b0514370501050cf2a8538ac12270 + sha: v1.0.1 hooks: - id: remove-crlf files: (?!.*third_party)^.*$ | (?!.*book)^.*$ - repo: https://github.com/reyoung/mirrors-yapf.git sha: v0.13.2 hooks: - - id: yapf - files: (.*\.(py|bzl)|BUILD|.*\.BUILD|WORKSPACE)$ + - id: yapf + files: (.*\.(py|bzl)|BUILD|.*\.BUILD|WORKSPACE)$ - repo: https://github.com/pre-commit/pre-commit-hooks - sha: 7539d8bd1a00a3c1bfd34cdb606d3a6372e83469 + sha: 5bf6c09bfa1297d3692cadd621ef95f1284e33c0 hooks: - id: check-added-large-files - id: check-merge-conflict diff --git a/Dockerfile b/Dockerfile index f12be36ceb7..9b5b931f27e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -46,6 +46,11 @@ RUN pip install --upgrade pip && \ pip install pre-commit 'requests==2.9.2' 'ipython==5.3.0' && \ pip install 'ipykernel==4.6.0' 'jupyter==1.0.0' +# # To fix https://github.com/PaddlePaddle/Paddle/issues/1954, we use +# # the solution in https://urllib3.readthedocs.io/en/latest/user-guide.html#ssl-py2 +RUN apt-get install -y libssl-dev libffi-dev +RUN pip install certifi urllib3[secure] + RUN curl -sSL https://cmake.org/files/v3.4/cmake-3.4.1.tar.gz | tar -xz && \ cd cmake-3.4.1 && ./bootstrap && make -j `nproc` && make install && \ cd .. && rm -rf cmake-3.4.1 -- GitLab From 9d2592e95e9a8f3f2f8899abec9e6179fdf5ea6a Mon Sep 17 00:00:00 2001 From: Yi Wang Date: Tue, 2 May 2017 17:02:52 -0700 Subject: [PATCH 0159/3256] Minor correction of comments --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 9b5b931f27e..c3ad0c9c2f6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -46,8 +46,8 @@ RUN pip install --upgrade pip && \ pip install pre-commit 'requests==2.9.2' 'ipython==5.3.0' && \ pip install 'ipykernel==4.6.0' 'jupyter==1.0.0' -# # To fix https://github.com/PaddlePaddle/Paddle/issues/1954, we use -# # the solution in https://urllib3.readthedocs.io/en/latest/user-guide.html#ssl-py2 +# To fix https://github.com/PaddlePaddle/Paddle/issues/1954, we use +# the solution in https://urllib3.readthedocs.io/en/latest/user-guide.html#ssl-py2 RUN apt-get install -y libssl-dev libffi-dev RUN pip install certifi urllib3[secure] -- GitLab From f2b35277b5eb77a64f354ae2bced5c09d3bd8f64 Mon Sep 17 00:00:00 2001 From: Liu Yiqun Date: Wed, 3 May 2017 03:28:09 +0000 Subject: [PATCH 0160/3256] Fix typo. --- cmake/cross_compiling/android.cmake | 4 ++-- cmake/external/protobuf.cmake | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cmake/cross_compiling/android.cmake b/cmake/cross_compiling/android.cmake index 5b730743252..25081671477 100644 --- a/cmake/cross_compiling/android.cmake +++ b/cmake/cross_compiling/android.cmake @@ -74,8 +74,8 @@ IF(NOT DEFINED ANDROID_ARM_NEON) SET(ANDROID_ARM_NEON ON) ENDIF() -IF(${CMAKE_VERSION} VERSION_LESS "3.7.0") - IF(${CMAKE_VERSION} VERSION_LESS "3.1.0") +IF("${CMAKE_VERSION}" VERSION_LESS "3.7.0") + IF("${CMAKE_VERSION}" VERSION_LESS "3.1.0") SET(CMAKE_SYSTEM_NAME "Linux") ENDIF() MESSAGE(WARNING "It is recommended to use CMake >= 3.7.0 (current version: " diff --git a/cmake/external/protobuf.cmake b/cmake/external/protobuf.cmake index 9892691a314..c5dc5bee619 100644 --- a/cmake/external/protobuf.cmake +++ b/cmake/external/protobuf.cmake @@ -18,7 +18,7 @@ FUNCTION(build_protobuf TARGET_NAME BUILD_FOR_HOST) SET(PROTOBUF_SOURCES_DIR ${THIRD_PARTY_PATH}/${TARGET_NAME}) SET(PROTOBUF_INSTALL_DIR ${THIRD_PARTY_PATH}/install/${TARGET_NAME}) - SET(${TARGET_NAME}_INCLUDE_DIR "${PROTOBUF_INSTALL_DIR}/include" + SET(${TARGET_NAME}_INCLUDE_DIR "${PROTOBUF_INSTALL_DIR}/include") SET(PROTOBUF_INCLUDE_DIR "${PROTOBUF_INSTALL_DIR}/include" PARENT_SCOPE) SET(${TARGET_NAME}_LITE_LIBRARY "${PROTOBUF_INSTALL_DIR}/lib/libprotobuf-lite${STATIC_LIBRARY_SUFFIX}" -- GitLab From e67854638832fe1c58089d2559fcb0c5321f5222 Mon Sep 17 00:00:00 2001 From: Liu Yiqun Date: Wed, 3 May 2017 03:54:26 +0000 Subject: [PATCH 0161/3256] Change ${TARGET_NAME}_INCLUDE_DIR to PARENT_SCOPE. --- cmake/external/protobuf.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/external/protobuf.cmake b/cmake/external/protobuf.cmake index c5dc5bee619..3e417f123b5 100644 --- a/cmake/external/protobuf.cmake +++ b/cmake/external/protobuf.cmake @@ -18,7 +18,7 @@ FUNCTION(build_protobuf TARGET_NAME BUILD_FOR_HOST) SET(PROTOBUF_SOURCES_DIR ${THIRD_PARTY_PATH}/${TARGET_NAME}) SET(PROTOBUF_INSTALL_DIR ${THIRD_PARTY_PATH}/install/${TARGET_NAME}) - SET(${TARGET_NAME}_INCLUDE_DIR "${PROTOBUF_INSTALL_DIR}/include") + SET(${TARGET_NAME}_INCLUDE_DIR "${PROTOBUF_INSTALL_DIR}/include" PARENT_SCOPE) SET(PROTOBUF_INCLUDE_DIR "${PROTOBUF_INSTALL_DIR}/include" PARENT_SCOPE) SET(${TARGET_NAME}_LITE_LIBRARY "${PROTOBUF_INSTALL_DIR}/lib/libprotobuf-lite${STATIC_LIBRARY_SUFFIX}" -- GitLab From 6237f6f57a7ed163f70fcd14668c6eed15840acf Mon Sep 17 00:00:00 2001 From: liaogang Date: Wed, 3 May 2017 12:55:38 +0800 Subject: [PATCH 0162/3256] revert clang-format --- paddle/cuda/src/hl_cuda_device.cc | 26 +++++----- paddle/function/BufferArgTest.cpp | 2 +- paddle/function/CosSimOp.cpp | 12 ++--- paddle/function/FunctionTest.cpp | 2 +- paddle/function/TensorShapeTest.cpp | 2 +- paddle/function/TensorTypeTest.cpp | 2 +- .../gserver/dataproviders/PyDataProvider.cpp | 4 +- paddle/gserver/evaluators/Evaluator.cpp | 6 +-- paddle/gserver/layers/Layer.cpp | 5 +- paddle/gserver/tests/test_RecurrentLayer.cpp | 39 +++++++-------- paddle/math/MatrixBitCode.cpp | 30 ++++++----- paddle/math/tests/TensorCheck.h | 4 +- paddle/math/tests/test_SIMDFunctions.cpp | 18 +++---- paddle/parameter/Argument.cpp | 50 +++++++++---------- paddle/parameter/AverageOptimizer.cpp | 5 +- paddle/parameter/Parameter.cpp | 4 +- paddle/pserver/LightNetwork.cpp | 4 +- paddle/pserver/ParameterClient2.cpp | 12 ++--- paddle/pserver/ParameterServer2.cpp | 4 +- paddle/pserver/ParameterServer2.h | 8 +-- paddle/pserver/ProtoServer.cpp | 4 +- paddle/trainer/TrainerInternal.cpp | 2 +- paddle/utils/BarrierStat.h | 16 +++--- paddle/utils/ClassRegistrar.h | 4 +- 24 files changed, 129 insertions(+), 136 deletions(-) diff --git a/paddle/cuda/src/hl_cuda_device.cc b/paddle/cuda/src/hl_cuda_device.cc index f8ce8cb428a..4042d9742a9 100644 --- a/paddle/cuda/src/hl_cuda_device.cc +++ b/paddle/cuda/src/hl_cuda_device.cc @@ -97,11 +97,11 @@ int g_cuda_lib_version = 0; * Check build-in cuda function using glog and it **does not** * support << operator for more details error info. */ -#define CHECK_CUDA(cudaFunc) \ - do { \ - cudaError_t cudaStat = cudaFunc; \ - CHECK_EQ(cudaSuccess, cudaStat) \ - << "Cuda Error: " << cudaGetErrorString(cudaStat); \ +#define CHECK_CUDA(cudaFunc) \ + do { \ + cudaError_t cudaStat = cudaFunc; \ + CHECK_EQ(cudaSuccess, cudaStat) << "Cuda Error: " \ + << cudaGetErrorString(cudaStat); \ } while (0) /** @@ -468,8 +468,8 @@ void hl_specify_devices_start(int *device, int number) { CHECK(tmp) << "[Start failed] System memory is not enough."; g_device = (hl_device_prop *)tmp; - device_prop = (hl_device_prop)((char *)tmp + g_system_device_num * - sizeof(hl_device_prop *)); + device_prop = (hl_device_prop)( + (char *)tmp + g_system_device_num * sizeof(hl_device_prop *)); memset(g_device, 0, g_system_device_num * sizeof(hl_device_prop *)); int num = 0; for (int i = 0; i < number; i++) { @@ -558,8 +558,8 @@ bool hl_get_sync_flag() { return g_sync_flag; } void hl_stream_synchronize(hl_stream_t stream) { cudaStream_t cu_stream; - CHECK_LT(stream, HPPL_STREAM_END) - << __func__ << ": the parameter stream is error."; + CHECK_LT(stream, HPPL_STREAM_END) << __func__ + << ": the parameter stream is error."; cu_stream = t_resource.stream[stream]; CHECK_CUDA(cudaStreamSynchronize(cu_stream)); @@ -589,8 +589,8 @@ void hl_stream_record_event(hl_stream_t stream, hl_event_t event) { cudaStream_t cu_stream; CHECK_NOTNULL(event); - CHECK_LT(stream, HPPL_STREAM_END) - << __func__ << ": the parameter stream is error."; + CHECK_LT(stream, HPPL_STREAM_END) << __func__ + << ": the parameter stream is error."; cu_stream = t_resource.stream[stream]; CHECK_CUDA(cudaEventRecord(event->cu_event, cu_stream)); @@ -600,8 +600,8 @@ void hl_stream_wait_event(hl_stream_t stream, hl_event_t event) { cudaStream_t cu_stream; CHECK_NOTNULL(event); - CHECK_LT(stream, HPPL_STREAM_END) - << __func__ << ": the parameter stream is error."; + CHECK_LT(stream, HPPL_STREAM_END) << __func__ + << ": the parameter stream is error."; cu_stream = t_resource.stream[stream]; CHECK_CUDA(cudaStreamWaitEvent(cu_stream, event->cu_event, 0)); diff --git a/paddle/function/BufferArgTest.cpp b/paddle/function/BufferArgTest.cpp index f1a234ab1a1..1744f377808 100644 --- a/paddle/function/BufferArgTest.cpp +++ b/paddle/function/BufferArgTest.cpp @@ -12,8 +12,8 @@ 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 "BufferArg.h" +#include #include "paddle/math/MemoryHandle.h" namespace paddle { diff --git a/paddle/function/CosSimOp.cpp b/paddle/function/CosSimOp.cpp index e44581ae106..7ece7b2dfed 100644 --- a/paddle/function/CosSimOp.cpp +++ b/paddle/function/CosSimOp.cpp @@ -165,12 +165,12 @@ void CosSimBackward(const CpuMatrix& out_grad, real reciprocal_square_sum_x = 1.0f / square_sum_x; real reciprocal_square_sum_y = 1.0f / square_sum_y; for (size_t j = 0; j < dim; ++j) { - prev_grad_x[j] += out[i] * grad[i] * - (prev_out_y[j] * reciprocal_xy - - prev_out_x[j] * reciprocal_square_sum_x); - prev_grad_y[j] += out[i] * grad[i] * - (prev_out_x[j] * reciprocal_xy - - prev_out_y[j] * reciprocal_square_sum_y); + prev_grad_x[j] += + out[i] * grad[i] * (prev_out_y[j] * reciprocal_xy - + prev_out_x[j] * reciprocal_square_sum_x); + prev_grad_y[j] += + out[i] * grad[i] * (prev_out_x[j] * reciprocal_xy - + prev_out_y[j] * reciprocal_square_sum_y); } } } diff --git a/paddle/function/FunctionTest.cpp b/paddle/function/FunctionTest.cpp index f9ea7c7e4f6..fdf7e631e5a 100644 --- a/paddle/function/FunctionTest.cpp +++ b/paddle/function/FunctionTest.cpp @@ -12,8 +12,8 @@ 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 "Function.h" +#include #include "paddle/math/SparseMatrix.h" namespace paddle { diff --git a/paddle/function/TensorShapeTest.cpp b/paddle/function/TensorShapeTest.cpp index e19afe0c4d5..45a2e106e7f 100644 --- a/paddle/function/TensorShapeTest.cpp +++ b/paddle/function/TensorShapeTest.cpp @@ -12,8 +12,8 @@ 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 "TensorShape.h" +#include namespace paddle { diff --git a/paddle/function/TensorTypeTest.cpp b/paddle/function/TensorTypeTest.cpp index 5b5c504ae2a..e50e46f3e99 100644 --- a/paddle/function/TensorTypeTest.cpp +++ b/paddle/function/TensorTypeTest.cpp @@ -12,8 +12,8 @@ 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 "TensorType.h" +#include namespace paddle { diff --git a/paddle/gserver/dataproviders/PyDataProvider.cpp b/paddle/gserver/dataproviders/PyDataProvider.cpp index e23051cd8ef..b53790e764b 100644 --- a/paddle/gserver/dataproviders/PyDataProvider.cpp +++ b/paddle/gserver/dataproviders/PyDataProvider.cpp @@ -194,8 +194,8 @@ void PyDataProvider::fillSlotsByStr(const std::string& samples) { auto& slot = slots_[j]; CHECK(SlotDef::INDEX >= slot.type || SlotDef::STRING == slot.type) << " Slot type:" << slot.type << " is out of range."; - CHECK_GE(slot.type, SlotDef::VECTOR_DENSE) - << " Slot type:" << slot.type << " is out of range."; + CHECK_GE(slot.type, SlotDef::VECTOR_DENSE) << " Slot type:" << slot.type + << " is out of range."; switch (slot.type) { case SlotDef::VECTOR_DENSE: fillDenseSlot(slot, data, dataEnd); diff --git a/paddle/gserver/evaluators/Evaluator.cpp b/paddle/gserver/evaluators/Evaluator.cpp index 56cf9ac7809..9db6d252d97 100644 --- a/paddle/gserver/evaluators/Evaluator.cpp +++ b/paddle/gserver/evaluators/Evaluator.cpp @@ -446,9 +446,9 @@ real AucEvaluator::evalImp(std::vector& arguments) { for (size_t i = 0; i < insNum; ++i) { real value = outputD[pos]; uint32_t binIdx = static_cast(value * kBinNum_); - CHECK(binIdx <= kBinNum_) - << "bin index [" << binIdx << "] out of range, predict value[" << value - << "]"; + CHECK(binIdx <= kBinNum_) << "bin index [" << binIdx + << "] out of range, predict value[" << value + << "]"; real w = supportWeight ? weightD[i] : 1.0; if (labelD[i] == kNegativeLabel_) { statNeg_[binIdx] += w; diff --git a/paddle/gserver/layers/Layer.cpp b/paddle/gserver/layers/Layer.cpp index 00dd8a8508b..125aaf947f3 100644 --- a/paddle/gserver/layers/Layer.cpp +++ b/paddle/gserver/layers/Layer.cpp @@ -263,9 +263,8 @@ void Layer::zeroGrad() { } void Layer::initNeedFlags() { - auto initFlag = [this](bool& flag, - bool (Layer::*flagQueryFunc)() const, - ParameterType type) { + auto initFlag = [this]( + bool& flag, bool (Layer::*flagQueryFunc)() const, ParameterType type) { flag = false; if (biasParameter_ && biasParameter_->hasType(type)) { flag = true; diff --git a/paddle/gserver/tests/test_RecurrentLayer.cpp b/paddle/gserver/tests/test_RecurrentLayer.cpp index e8e1db51ae6..16ab0e6aecb 100644 --- a/paddle/gserver/tests/test_RecurrentLayer.cpp +++ b/paddle/gserver/tests/test_RecurrentLayer.cpp @@ -292,27 +292,26 @@ void checkRecurrentLayer(LayerConfig layerConfig, TestRecurrentLayer testGpu(layerConfig, true, gpuBatch); testCpu.init(batchSize); testGpu.init(batchSize); - auto checkError = - [](MatrixPtr cpu, MatrixPtr gpu, int numSequences, const char* str) { - CpuMatrix check(gpu->getHeight(), gpu->getWidth()); - check.copyFrom(*gpu); - int height = cpu->getHeight(); - int width = cpu->getWidth(); - const real* data1 = cpu->getData(); - const real* data2 = check.getData(); - int count = 0; - for (int i = 0; i < height; i++) { - for (int j = 0; j < width; j++) { - if (fabs(data1[i * width + j] - data2[i * width + j]) / - numSequences > - 1e-4) { - count++; - } - } + auto checkError = []( + MatrixPtr cpu, MatrixPtr gpu, int numSequences, const char* str) { + CpuMatrix check(gpu->getHeight(), gpu->getWidth()); + check.copyFrom(*gpu); + int height = cpu->getHeight(); + int width = cpu->getWidth(); + const real* data1 = cpu->getData(); + const real* data2 = check.getData(); + int count = 0; + for (int i = 0; i < height; i++) { + for (int j = 0; j < width; j++) { + if (fabs(data1[i * width + j] - data2[i * width + j]) / numSequences > + 1e-4) { + count++; } - EXPECT_EQ(count, 0) << "[" << str << "]" - << "There are " << count << " different element."; - }; + } + } + EXPECT_EQ(count, 0) << "[" << str << "]" + << "There are " << count << " different element."; + }; T* cpuLayer = dynamic_cast(testCpu.testLayer_.get()); T* gpuLayer = dynamic_cast(testGpu.testLayer_.get()); diff --git a/paddle/math/MatrixBitCode.cpp b/paddle/math/MatrixBitCode.cpp index 0ea387d0208..cea912d3ca0 100644 --- a/paddle/math/MatrixBitCode.cpp +++ b/paddle/math/MatrixBitCode.cpp @@ -174,10 +174,8 @@ void CpuMatrix::mulByBitCode(size_t numClasses, const IVector& codes, const Matrix& weight, const Matrix& input) { - auto op = [](real& t, - const real* weightRow, - const real* inputRow, - size_t inputDim) { + auto op = []( + real& t, const real* weightRow, const real* inputRow, size_t inputDim) { real sum = 0; for (size_t k = 0; k < inputDim; ++k) { sum += weightRow[k] * inputRow[k]; @@ -195,12 +193,12 @@ void CpuMatrix::mulByBitCodeBackwardWeight(size_t numClasses, const IVector& codes, Matrix& weight, const Matrix& input) { - auto op = - [](const real t, real* weightRow, const real* inputRow, size_t inputDim) { - for (size_t k = 0; k < inputDim; ++k) { - weightRow[k] += t * inputRow[k]; - } - }; + auto op = []( + const real t, real* weightRow, const real* inputRow, size_t inputDim) { + for (size_t k = 0; k < inputDim; ++k) { + weightRow[k] += t * inputRow[k]; + } + }; mulByBitCodeT(op, SimpleCodeTable(numClasses), codes, *this, weight, input); } @@ -212,12 +210,12 @@ void CpuMatrix::mulByBitCodeBackwardError(size_t numClasses, const IVector& codes, const Matrix& weight, Matrix& input) { - auto op = - [](const real t, const real* weightRow, real* inputRow, size_t inputDim) { - for (size_t k = 0; k < inputDim; ++k) { - inputRow[k] += t * weightRow[k]; - } - }; + auto op = []( + const real t, const real* weightRow, real* inputRow, size_t inputDim) { + for (size_t k = 0; k < inputDim; ++k) { + inputRow[k] += t * weightRow[k]; + } + }; mulByBitCodeT(op, SimpleCodeTable(numClasses), codes, *this, weight, input); } diff --git a/paddle/math/tests/TensorCheck.h b/paddle/math/tests/TensorCheck.h index 86f5982e758..5bc4a03067a 100644 --- a/paddle/math/tests/TensorCheck.h +++ b/paddle/math/tests/TensorCheck.h @@ -183,8 +183,8 @@ void TensorCheck(AssertEq compare, template void TensorCheck(AssertEq compare, real args1, real args2) { - EXPECT_EQ(compare(args1, args2), true) - << "[Test error] args1 = " << args1 << ", args2 = " << args2; + EXPECT_EQ(compare(args1, args2), true) << "[Test error] args1 = " << args1 + << ", args2 = " << args2; } template diff --git a/paddle/math/tests/test_SIMDFunctions.cpp b/paddle/math/tests/test_SIMDFunctions.cpp index e4f8cf4c24b..e8f9b26ff24 100644 --- a/paddle/math/tests/test_SIMDFunctions.cpp +++ b/paddle/math/tests/test_SIMDFunctions.cpp @@ -126,15 +126,15 @@ TEST(SIMDFunction, decayL1_WithLR) { typedef std::function DecayL1MethodType; - DecayL1MethodType naive = - [](float* d, float* s, float* lr, float l, size_t len) { - paddle::simd::naive::decayL1(d, s, lr, l, len); - }; - - DecayL1MethodType simd = - [](float* d, float* s, float* lr, float l, size_t len) { - paddle::simd::decayL1(d, s, lr, l, len); - }; + DecayL1MethodType naive = []( + float* d, float* s, float* lr, float l, size_t len) { + paddle::simd::naive::decayL1(d, s, lr, l, len); + }; + + DecayL1MethodType simd = []( + float* d, float* s, float* lr, float l, size_t len) { + paddle::simd::decayL1(d, s, lr, l, len); + }; naive(dest.get(), src.get(), lr.get(), lambda, VECTOR_LEN); simd(simd_dest.get(), src.get(), lr.get(), lambda, VECTOR_LEN); diff --git a/paddle/parameter/Argument.cpp b/paddle/parameter/Argument.cpp index 10e4a0ae68f..6d9365af2d1 100644 --- a/paddle/parameter/Argument.cpp +++ b/paddle/parameter/Argument.cpp @@ -379,7 +379,7 @@ void Argument::concat(const std::vector& args, } auto copyArg = [batchSize, stream]( - MatrixPtr& dst, MatrixPtr src, int startRow, bool useGpu) { + MatrixPtr& dst, MatrixPtr src, int startRow, bool useGpu) { if (!src) { dst.reset(); return; @@ -395,31 +395,29 @@ void Argument::concat(const std::vector& args, tmpMatrix->copyFrom(*src, stream); }; - auto copyIds = - [batchSize, stream]( - IVectorPtr& dst, const IVectorPtr& src, int startRow, bool useGpu) { - if (!src) { - dst.reset(); - return; - } - IVector::resizeOrCreate(dst, batchSize, useGpu); - dst->subVec(startRow, src->getSize())->copyFrom(*src, stream); - }; - - auto copyStrs = - [batchSize, stream]( - SVectorPtr& dst, const SVectorPtr& src, int startRow, bool useGpu) { - if (!src) { - dst.reset(); - return; - } - if (!dst) { - dst = std::make_shared>(batchSize); - } else { - dst->resize(batchSize); - } - std::copy(src->begin(), src->end(), dst->begin() + startRow); - }; + auto copyIds = [batchSize, stream]( + IVectorPtr& dst, const IVectorPtr& src, int startRow, bool useGpu) { + if (!src) { + dst.reset(); + return; + } + IVector::resizeOrCreate(dst, batchSize, useGpu); + dst->subVec(startRow, src->getSize())->copyFrom(*src, stream); + }; + + auto copyStrs = [batchSize, stream]( + SVectorPtr& dst, const SVectorPtr& src, int startRow, bool useGpu) { + if (!src) { + dst.reset(); + return; + } + if (!dst) { + dst = std::make_shared>(batchSize); + } else { + dst->resize(batchSize); + } + std::copy(src->begin(), src->end(), dst->begin() + startRow); + }; auto copySequencePos = [](ICpuGpuVectorPtr& dstSeq, const ICpuGpuVectorPtr& srcSeq, diff --git a/paddle/parameter/AverageOptimizer.cpp b/paddle/parameter/AverageOptimizer.cpp index 5db5ddd10c4..e51ca565209 100644 --- a/paddle/parameter/AverageOptimizer.cpp +++ b/paddle/parameter/AverageOptimizer.cpp @@ -155,9 +155,8 @@ ParameterOptimizer::TraverseCallback AverageOptimizer::restore() { return nullptr; } - return [](const VectorPtr vecs[], - const ParameterConfig& config, - size_t sparseId) { + return []( + const VectorPtr vecs[], const ParameterConfig& config, size_t sparseId) { vecs[PARAMETER_VALUE]->copyFrom(*vecs[PARAMETER_GRADIENT]); vecs[PARAMETER_GRADIENT]->zeroMem(); }; diff --git a/paddle/parameter/Parameter.cpp b/paddle/parameter/Parameter.cpp index 7eb90920ae3..b8efabbe2a0 100644 --- a/paddle/parameter/Parameter.cpp +++ b/paddle/parameter/Parameter.cpp @@ -352,8 +352,8 @@ bool Parameter::load(std::istream& s) { Header header; CHECK(s.read(reinterpret_cast(&header), sizeof(header))) << "Fail to read parameter " << getName(); - CHECK_EQ(header.version, kFormatVersion) - << "Incorrect format version: " << header.version; + CHECK_EQ(header.version, kFormatVersion) << "Incorrect format version: " + << header.version; CHECK_EQ(header.size, getSize()) << "The size (" << header.size << ") in the file does not match the size " << "(" << getSize() << ") of the parameter: " << getName(); diff --git a/paddle/pserver/LightNetwork.cpp b/paddle/pserver/LightNetwork.cpp index 09175f249fc..8c8ba0a2e51 100644 --- a/paddle/pserver/LightNetwork.cpp +++ b/paddle/pserver/LightNetwork.cpp @@ -359,8 +359,8 @@ void SocketClient::TcpClient(const std::string &serverAddr, int serverPort) { #if defined(__OSX__) || defined(__APPLE__) server = getipnodebyname(serverAddr.c_str(), AF_INET, AI_DEFAULT, &errRet); - CHECK_NE(HOST_NOT_FOUND, errRet) - << "ERROR, no such host: " << serverAddr << " ret = " << errRet; + CHECK_NE(HOST_NOT_FOUND, errRet) << "ERROR, no such host: " << serverAddr + << " ret = " << errRet; CHECK(server) << "getipnodebyname error!"; #else struct hostent hostinfo; diff --git a/paddle/pserver/ParameterClient2.cpp b/paddle/pserver/ParameterClient2.cpp index df80a322e1b..a97859f83fe 100644 --- a/paddle/pserver/ParameterClient2.cpp +++ b/paddle/pserver/ParameterClient2.cpp @@ -549,9 +549,9 @@ PServerVector ParameterClient2::createVector() { if (handle == -1) { handle = response.handle(); } else { - CHECK_EQ(handle, response.handle()) - << "Inconsistent handle from client" << &response - &responses[0] - << " " << handle << " " << response.handle(); + CHECK_EQ(handle, response.handle()) << "Inconsistent handle from client" + << &response - &responses[0] << " " + << handle << " " << response.handle(); } } return PServerVector{handle}; @@ -579,9 +579,9 @@ PServerMatrix ParameterClient2::createMatrix(int32_t numCols) { if (handle == -1) { handle = response.handle(); } else { - CHECK_EQ(handle, response.handle()) - << "Inconsistent handle from client" << &response - &responses[0] - << " " << handle << " " << response.handle(); + CHECK_EQ(handle, response.handle()) << "Inconsistent handle from client" + << &response - &responses[0] << " " + << handle << " " << response.handle(); } } return PServerMatrix{handle}; diff --git a/paddle/pserver/ParameterServer2.cpp b/paddle/pserver/ParameterServer2.cpp index a72dd3dc08c..19ff40ba7e9 100644 --- a/paddle/pserver/ParameterServer2.cpp +++ b/paddle/pserver/ParameterServer2.cpp @@ -1213,8 +1213,8 @@ void ParameterServer2::loadValueVector(const LoadValueRequest& request, CHECK_EQ(header.size, (size_t)size_) << "The size (" << header.size << ") in the file does not match the size " << "(" << size_ << ") of the pserver: " << serverId_; - CHECK_EQ(header.valueSize, sizeof(real)) - << "Unsupported valueSize " << header.valueSize; + CHECK_EQ(header.valueSize, sizeof(real)) << "Unsupported valueSize " + << header.valueSize; CHECK(fs.read(reinterpret_cast(vec.getData()), header.size * sizeof(real))); diff --git a/paddle/pserver/ParameterServer2.h b/paddle/pserver/ParameterServer2.h index 0344196d7b8..0f5a5895907 100644 --- a/paddle/pserver/ParameterServer2.h +++ b/paddle/pserver/ParameterServer2.h @@ -545,11 +545,11 @@ protected: std::vector* buffers); const ParameterConfig& getParameterConfig(const ParameterBlock& block) { - CHECK_LT(block.para_id(), -1UL) - << "invalid parameter id:" << block.para_id(); + CHECK_LT(block.para_id(), -1UL) << "invalid parameter id:" + << block.para_id(); const auto it = configMap_.find(block.para_id()); - CHECK(it != configMap_.end()) - << "can not find parameter id: " << block.para_id(); + CHECK(it != configMap_.end()) << "can not find parameter id: " + << block.para_id(); return it->second; } diff --git a/paddle/pserver/ProtoServer.cpp b/paddle/pserver/ProtoServer.cpp index 52344123a6b..410317ece28 100644 --- a/paddle/pserver/ProtoServer.cpp +++ b/paddle/pserver/ProtoServer.cpp @@ -41,8 +41,8 @@ void ProtoServer::handleRequest(std::unique_ptr msgReader, void ProtoServer::registerServiceFunctionImp(const std::string& funcName, ServiceFunction func) { - CHECK(!nameToFuncMap_.count(funcName)) - << "Duplicated registration: " << funcName; + CHECK(!nameToFuncMap_.count(funcName)) << "Duplicated registration: " + << funcName; nameToFuncMap_[funcName] = func; } diff --git a/paddle/trainer/TrainerInternal.cpp b/paddle/trainer/TrainerInternal.cpp index 97b401688e9..4c5d4a0913a 100644 --- a/paddle/trainer/TrainerInternal.cpp +++ b/paddle/trainer/TrainerInternal.cpp @@ -97,7 +97,7 @@ void TrainerInternal::trainOneBatch(int64_t batchId, } UpdateCallback updateCallback = [this, showStats, ¶Stats]( - Parameter* para) { + Parameter* para) { if (showStats) { //! @TODO(yuyang18) Show stats is actually a ParameterHook, refactor // it diff --git a/paddle/utils/BarrierStat.h b/paddle/utils/BarrierStat.h index 817ab8fc9d6..a9c925eff66 100644 --- a/paddle/utils/BarrierStat.h +++ b/paddle/utils/BarrierStat.h @@ -344,14 +344,14 @@ private: } while (0); // check end barrier -#define __CHECK_BARRIER_TIMER(set, statName, numConnThreads, ...) \ - do { \ - std::string internalName = \ - std::string(statName) + std::string(__VA_ARGS__); \ - BarrierStatPtr __stat = \ - (set).getStat(numConnThreads, internalName, BARRIER_END); \ - PCHECK(__stat->checkPassBarrier()) \ - << internalName << ": invalid barrier data"; \ +#define __CHECK_BARRIER_TIMER(set, statName, numConnThreads, ...) \ + do { \ + std::string internalName = \ + std::string(statName) + std::string(__VA_ARGS__); \ + BarrierStatPtr __stat = \ + (set).getStat(numConnThreads, internalName, BARRIER_END); \ + PCHECK(__stat->checkPassBarrier()) << internalName \ + << ": invalid barrier data"; \ } while (0); /* diff --git a/paddle/utils/ClassRegistrar.h b/paddle/utils/ClassRegistrar.h index e4351dbcb7d..1ac27bafabd 100644 --- a/paddle/utils/ClassRegistrar.h +++ b/paddle/utils/ClassRegistrar.h @@ -62,8 +62,8 @@ public: // Create a class instance of type @type using args BaseClass* createByType(const std::string& type, CreateArgs... args) { ClassCreator creator; - CHECK(mapGet(type, creatorMap_, &creator)) - << "Unknown class type: " << type; + CHECK(mapGet(type, creatorMap_, &creator)) << "Unknown class type: " + << type; return creator(args...); } -- GitLab From 73af8d785f1c29e677c09d57207c2db51689dcdc Mon Sep 17 00:00:00 2001 From: Luo Tao Date: Wed, 3 May 2017 13:53:11 +0800 Subject: [PATCH 0163/3256] fix conflict --- python/setup.py.in | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/python/setup.py.in b/python/setup.py.in index 2ec0841b14d..5dfb46192ae 100644 --- a/python/setup.py.in +++ b/python/setup.py.in @@ -15,13 +15,11 @@ setup(name='paddle', description='Parallel Distributed Deep Learning', install_requires=[ "requests", - ], - packages=packages, - install_requires=[ "numpy", "protobuf==${PROTOBUF_VERSION}", "matplotlib", ], + packages=packages, package_dir={ '': '${CMAKE_CURRENT_SOURCE_DIR}' } -- GitLab From a0c3465b6e8a91da1865971ff97ecf44360fd290 Mon Sep 17 00:00:00 2001 From: yangyaming Date: Wed, 3 May 2017 18:36:51 +0800 Subject: [PATCH 0164/3256] Set filter theshold as a paramter for function build_dict of dataset imikolov --- python/paddle/v2/dataset/imikolov.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/python/paddle/v2/dataset/imikolov.py b/python/paddle/v2/dataset/imikolov.py index 41ca27e2363..bf88fe15570 100644 --- a/python/paddle/v2/dataset/imikolov.py +++ b/python/paddle/v2/dataset/imikolov.py @@ -41,7 +41,7 @@ def word_count(f, word_freq=None): return word_freq -def build_dict(): +def build_dict(typo_freq=50): """ Build a word dictionary from the corpus, Keys of the dictionary are words, and values are zero-based IDs of these words. @@ -59,8 +59,7 @@ def build_dict(): # remove for now, since we will set it as last index del word_freq[''] - TYPO_FREQ = 50 - word_freq = filter(lambda x: x[1] > TYPO_FREQ, word_freq.items()) + word_freq = filter(lambda x: x[1] > typo_freq, word_freq.items()) word_freq_sorted = sorted(word_freq, key=lambda x: (-x[1], x[0])) words, _ = list(zip(*word_freq_sorted)) -- GitLab From 1ddb9bf7828026f60332969c033cb247ab40b4a9 Mon Sep 17 00:00:00 2001 From: Liu Yiqun Date: Wed, 3 May 2017 12:17:40 +0000 Subject: [PATCH 0165/3256] Delete the configuration of fortran compiler. --- cmake/cross_compiling/android.cmake | 11 ----------- cmake/cross_compiling/raspberry_pi.cmake | 11 ----------- cmake/external/protobuf.cmake | 2 +- 3 files changed, 1 insertion(+), 23 deletions(-) diff --git a/cmake/cross_compiling/android.cmake b/cmake/cross_compiling/android.cmake index 25081671477..9724c16122a 100644 --- a/cmake/cross_compiling/android.cmake +++ b/cmake/cross_compiling/android.cmake @@ -129,19 +129,8 @@ IF("${CMAKE_VERSION}" VERSION_LESS "3.7.0") MESSAGE(FATAL_ERROR "Cannot find CXX compiler: ${ANDROID_CXX_COMPILER}") ENDIF() - # Fortran compiler - IF(NOT CMAKE_Fortran_COMPILER) - SET(ANDROID_Fortran_COMPILER "${ANDROID_TOOLCHAIN_PREFIX}gfortran") - ELSE() - GET_FILENAME_COMPONENT(ANDROID_Fortran_COMPILER ${CMAKE_Fortran_COMPILER}) - ENDIF() - IF(NOT EXISTS ${ANDROID_Fortran_COMPILER}) - SET(ANDROID_Fortran_COMPILER "") - ENDIF() - SET(CMAKE_C_COMPILER ${ANDROID_C_COMPILER} CACHE PATH "C compiler" FORCE) SET(CMAKE_CXX_COMPILER ${ANDROID_CXX_COMPILER} CACHE PATH "CXX compiler" FORCE) - SET(CMAKE_Fortran_COMPILER ${ANDROID_Fortran_COMPILER} CACHE PATH "Fortran compiler" FORCE) # Toolchain and ABI specific flags. SET(ANDROID_COMPILER_FLAGS "-ffunction-sections -fdata-sections -finline-limit=64") diff --git a/cmake/cross_compiling/raspberry_pi.cmake b/cmake/cross_compiling/raspberry_pi.cmake index bfd26f130c1..817b39f6833 100644 --- a/cmake/cross_compiling/raspberry_pi.cmake +++ b/cmake/cross_compiling/raspberry_pi.cmake @@ -73,19 +73,8 @@ IF(NOT EXISTS ${RPI_CXX_COMPILER}) MESSAGE(FATAL_ERROR "Cannot find CXX compiler: ${RPI_CXX_COMPILER}") ENDIF() -# Fortran compiler -IF(NOT CMAKE_Fortran_COMPILER) - SET(RPI_Fortran_COMPILER "${RPI_TOOLCHAIN_PREFIX}gfortran") -ELSE() - GET_FILENAME_COMPONENT(RPI_Fortran_COMPILER ${CMAKE_Fortran_COMPILER} PROGRAM) -ENDIF() -IF(NOT EXISTS RPI_Fortran_COMPILER) - SET(RPI_Fortran_COMPILER "") -ENDIF() - SET(CMAKE_C_COMPILER ${RPI_C_COMPILER} CACHE PATH "C compiler" FORCE) SET(CMAKE_CXX_COMPILER ${RPI_CXX_COMPILER} CACHE PATH "CXX compiler" FORCE) -SET(CMAKE_Fortran_COMPILER ${RPI_Fortran_COMPILER} CACHE PATH "Fortran compiler" FORCE) IF(RPI_ARM_NEON) SET(RPI_C_FLAGS "${RPI_C_FLAGS} -mfpu=neon") diff --git a/cmake/external/protobuf.cmake b/cmake/external/protobuf.cmake index 3e417f123b5..994ba14cfa0 100644 --- a/cmake/external/protobuf.cmake +++ b/cmake/external/protobuf.cmake @@ -76,7 +76,6 @@ FUNCTION(build_protobuf TARGET_NAME BUILD_FOR_HOST) ENDFUNCTION() IF(NOT CMAKE_CROSSCOMPILING) - SET(PROTOBUF_VERSION 3.1) FIND_PACKAGE(Protobuf ${PROTOBUF_VERSION}) IF(PROTOBUF_FOUND) @@ -92,6 +91,7 @@ ELSE() CACHE FILEPATH "protobuf executable." FORCE) ENDIF() +SET(PROTOBUF_VERSION 3.1) IF(NOT PROTOBUF_FOUND) build_protobuf(protobuf FALSE) SET(PROTOBUF_INCLUDE_DIR ${protobuf_INCLUDE_DIR} -- GitLab From 33eadfb8bd79fae054055681451a8acac17da697 Mon Sep 17 00:00:00 2001 From: gongweibao Date: Wed, 3 May 2017 20:29:31 +0800 Subject: [PATCH 0166/3256] add filemanager doc --- doc/design/file_mananger/README.md | 72 ++++++++++++++++++ .../file_mananger/src/filemanager.graffle | Bin 0 -> 3656 bytes doc/design/file_mananger/src/filemanager.png | Bin 0 -> 50663 bytes 3 files changed, 72 insertions(+) create mode 100644 doc/design/file_mananger/README.md create mode 100644 doc/design/file_mananger/src/filemanager.graffle create mode 100644 doc/design/file_mananger/src/filemanager.png diff --git a/doc/design/file_mananger/README.md b/doc/design/file_mananger/README.md new file mode 100644 index 00000000000..0dd7e05d248 --- /dev/null +++ b/doc/design/file_mananger/README.md @@ -0,0 +1,72 @@ +# Desgin doc: FileManager +## Objetive +在本文档中,我们设计说明了用户上传、下载、管理自己在PaddlePaddle Cloud上的文件所涉及到的模块和流程 + + +## Module +### Client +Client是用户操作的界面,支持的命令如下 + +- ls + +```bash +ls +``` + +- cp + +```bash +cp +``` + +- sync + +```bash +sync +``` + +- mv + +```bash +mv +``` + + +### Ingress +- 在kubernets中运行 +- 做Http转发 +- 注意配置session保持 + + +### FileServer +FileServer是gorpc写的HttpServer, 用来接收Client的REST API的请求,自身由kubernets来管理。 +REST API说明 + +- file + +``` +GET /file: Get attribue of files +POST /file: Touch a file +DELETE /file: Delete a File +``` + +- chunk + +``` +GET /file/chunk: Get a chunk info +POST /file/chunk: Update a chunk +``` + +- dir + +``` +GET /dir: List all files in a directory +POST /dir: Touch a directory +DELETE /dir: Delete a directory +``` + + +## 流程 +### cp +### 断点续传 +### sync diff --git a/doc/design/file_mananger/src/filemanager.graffle b/doc/design/file_mananger/src/filemanager.graffle new file mode 100644 index 0000000000000000000000000000000000000000..68fc8224d33cbc17dfa3634284901fdc5e2cb775 GIT binary patch literal 3656 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 aZY51p#(d0=(dTjAzWP4}9#6rwX8-^_ek|Jn literal 0 HcmV?d00001 diff --git a/doc/design/file_mananger/src/filemanager.png b/doc/design/file_mananger/src/filemanager.png new file mode 100644 index 0000000000000000000000000000000000000000..661d547e414f0119728e1bb5262d0cbb446b395a GIT binary patch literal 50663 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 literal 0 HcmV?d00001 -- GitLab From 5cdc4955271ff04866a0b9cc5247e6dee79a7ddc Mon Sep 17 00:00:00 2001 From: hedaoyuan Date: Wed, 3 May 2017 21:29:45 +0800 Subject: [PATCH 0167/3256] Fix some bug --- doc/howto/raspberry/build_for_raspberry.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/doc/howto/raspberry/build_for_raspberry.md b/doc/howto/raspberry/build_for_raspberry.md index 7e843a659f8..4a98aba8f2a 100644 --- a/doc/howto/raspberry/build_for_raspberry.md +++ b/doc/howto/raspberry/build_for_raspberry.md @@ -33,13 +33,15 @@ cmake ../protobuf/cmake \ 注意:这样编译出来的`libprotobuf.a`和`protoc`都是ARM版本的,而我们需要的是一个x86-64版本的`protoc`,所以需要用host gcc再编译一遍protobuf然后使用其中的`protoc`。 -## 编译Paddle +## 编译PaddlePaddle +cmake参数如下;其中`WITH_C_API`设置为ON,编译输出的output目录会中包含`include`和`lib`目录,其中`include`中包含CAPI的头文件,`lib`中包含一个ARM版本的库。另外,`CMAKE_BUILD_TYPE`设置为`MinSizeRel`可以减小编译的库的大小。 ``` -cmake .. -DWITH_GPU=OFF -DWITH_PYTHON=OFF -DWITH_SWIG_PY=OFF \ +cmake .. -DWITH_GPU=OFF -DWITH_C_API=ON -DWITH_PYTHON=OFF -DWITH_SWIG_PY=OFF \ -DCMAKE_CXX_COMPILER:FILEPATH=arm-linux-gnueabihf-g++ \ -DCMAKE_C_COMPILER:FILEPATH=arm-linux-gnueabihf-gcc \ -DCMAKE_C_FLAGS="-mfpu=neon" \ -DCMAKE_CXX_FLAGS="-mfpu=neon" \ -DOPENBLAS_ROOT=openblas \ --DCMAKE_PREFIX_PATH=protobuf +-DCMAKE_PREFIX_PATH=protobuf \ +-DCMAKE_BUILD_TYPE=MinSizeRel ``` -- GitLab From 7e29901ab2441a118a7796cc3a9a8ece9eccc121 Mon Sep 17 00:00:00 2001 From: xuwei06 Date: Wed, 3 May 2017 12:47:03 -0700 Subject: [PATCH 0168/3256] Avoid relink executables when cmake files are changed In the original util.cmake. enable_virtualenv.c is always regenerated when cmake files are changed, which leads to the relinking of all the targets depends on paddle_utils. --- cmake/make_resource.py | 11 +++++++++++ cmake/util.cmake | 18 +++++------------- paddle/scripts/submit_local.sh.in | 4 ++-- paddle/utils/CMakeLists.txt | 5 +++-- 4 files changed, 21 insertions(+), 17 deletions(-) create mode 100644 cmake/make_resource.py diff --git a/cmake/make_resource.py b/cmake/make_resource.py new file mode 100644 index 00000000000..a9241b0e3e3 --- /dev/null +++ b/cmake/make_resource.py @@ -0,0 +1,11 @@ +import os +import re +import sys + +res = sys.argv[1] +out = sys.argv[2] +var = re.sub(r'[ .-]', '_', os.path.basename(res)) + +open(out, "w").write("const unsigned char " + var + "[] = {" + ",".join([ + "0x%02x" % ord(c) for c in open(res).read() +]) + ",0};\n" + "const unsigned " + var + "_size = sizeof(" + var + ");\n") diff --git a/cmake/util.cmake b/cmake/util.cmake index 099a85809d9..be4c591da84 100644 --- a/cmake/util.cmake +++ b/cmake/util.cmake @@ -138,17 +138,9 @@ macro(add_simple_unittest TARGET_NAME) endmacro() # Creates C resources file from files in given resource file -function(create_resources res_file output) - # Create empty output file - file(WRITE ${output} "") - # Get short filename - string(REGEX MATCH "([^/]+)$" filename ${res_file}) - # Replace filename spaces & extension separator for C compatibility - string(REGEX REPLACE "\\.| |-" "_" filename ${filename}) - # Read hex data from file - file(READ ${res_file} filedata HEX) - # Convert hex data for C compatibility - string(REGEX REPLACE "([0-9a-f][0-9a-f])" "0x\\1," filedata ${filedata}) - # Append data to output file - file(APPEND ${output} "const unsigned char ${filename}[] = {${filedata}0};\nconst unsigned ${filename}_size = sizeof(${filename});\n") +function(create_resources res_file output_file) + add_custom_command( + OUTPUT ${output_file} + COMMAND python ARGS ${PROJ_ROOT}/cmake/make_resource.py ${res_file} ${output_file} + DEPENDS ${res_file} ${PROJ_ROOT}/cmake/make_resource.py) endfunction() diff --git a/paddle/scripts/submit_local.sh.in b/paddle/scripts/submit_local.sh.in index 8fba4a19ba2..12bf629ea92 100644 --- a/paddle/scripts/submit_local.sh.in +++ b/paddle/scripts/submit_local.sh.in @@ -50,7 +50,7 @@ if [ -z "${PADDLE_NO_STAT+x}" ]; then -c ${PADDLE_CONF_HOME}/paddle.cookie \ http://api.paddlepaddle.org/version 2>/dev/null` if [ $? -eq 0 ] && [ "$(ver2num @PADDLE_VERSION@)" -lt $(ver2num $SERVER_VER) ]; then - echo "Paddle release a new version ${SERVER_VER}, you can get the install package in http://www.paddlepaddle.org" + echo "Paddle release a new version ${SERVER_VER}, you can get the install package in http://www.paddlepaddle.org" fi fi @@ -95,7 +95,7 @@ if [ $? -eq 1 ]; then # Older version installed, or not installed at all echo "First time run paddle, need to install some python dependencies." # setuptools normalizes package version, so we need to use normalized # package version for paddle python package - PYTHON_PADDLE_VERSION=$(python -c 'import packaging + PYTHON_PADDLE_VERSION=$(python -c 'import packaging.version import setuptools print str(packaging.version.Version("@PADDLE_VERSION@")) ' 2>/dev/null) diff --git a/paddle/utils/CMakeLists.txt b/paddle/utils/CMakeLists.txt index 171eae381af..af59951752d 100644 --- a/paddle/utils/CMakeLists.txt +++ b/paddle/utils/CMakeLists.txt @@ -1,8 +1,9 @@ # The utilities for paddle file(GLOB UTIL_HEADERS . *.h) file(GLOB UTIL_SOURCES . *.cpp) -create_resources(enable_virtualenv.py enable_virtualenv.c) -set(UTIL_RES enable_virtualenv.c) +create_resources(${CMAKE_CURRENT_SOURCE_DIR}/enable_virtualenv.py + ${CMAKE_CURRENT_SOURCE_DIR}/enable_virtualenv.c) +set(UTIL_RES ${CMAKE_CURRENT_SOURCE_DIR}/enable_virtualenv.c) if(APPLE) file(GLOB UTIL_ARCH_SOURCES . arch/osx/*.cpp) -- GitLab From b2f14e496faaea09e20ccfeae6bf2b6d51c81bf8 Mon Sep 17 00:00:00 2001 From: xuwei06 Date: Wed, 3 May 2017 16:05:52 -0700 Subject: [PATCH 0169/3256] Fix dynamic loading of Lapack caused by #1958 This only fixes the issue for ATLAS MKL is still another fix. --- paddle/math/MathFunctions.cpp | 11 +++++++++-- paddle/math/tests/test_matrixCompare.cpp | 9 +-------- paddle/utils/DynamicLoader.cpp | 2 +- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/paddle/math/MathFunctions.cpp b/paddle/math/MathFunctions.cpp index 178fce5b0a9..802a56a0d15 100644 --- a/paddle/math/MathFunctions.cpp +++ b/paddle/math/MathFunctions.cpp @@ -29,13 +29,20 @@ void* lapack_dso_handle = nullptr; * * note: default dynamic linked libs */ + +// The argument for stringizing operator is not macro-expanded first. +// We have to use two levels of macro to do the expansion. +// See https://gcc.gnu.org/onlinedocs/cpp/Stringizing.html +#define STR(x) #x #define DYNAMIC_LOAD_LAPACK_WRAP(__name) \ struct DynLoad__##__name { \ template \ auto operator()(Args... args) -> decltype(__name(args...)) { \ using lapack_func = decltype(__name(args...)) (*)(Args...); \ std::call_once(lapack_dso_flag, GetLapackDsoHandle, &lapack_dso_handle); \ - void* p_##__name = dlsym(lapack_dso_handle, #__name); \ + void* p_##__name = dlsym(lapack_dso_handle, STR(__name)); \ + CHECK(p_##__name) << "Cannot find symbol " << STR(__name) \ + << " in liblapack.so"; \ return reinterpret_cast(p_##__name)(args...); \ } \ } __name; // struct DynLoad__##__name @@ -51,7 +58,7 @@ void* lapack_dso_handle = nullptr; #define PADDLE_DGETRF LAPACKE_dgetrf #define PADDLE_SGETRI LAPACKE_sgetri #define PADDLE_DGETRI LAPACKE_dgetri -#endif +#endif #define LAPACK_ROUTINE_EACH(__macro) \ __macro(PADDLE_SGETRF) \ diff --git a/paddle/math/tests/test_matrixCompare.cpp b/paddle/math/tests/test_matrixCompare.cpp index 3b1b0065af3..782a9613d87 100644 --- a/paddle/math/tests/test_matrixCompare.cpp +++ b/paddle/math/tests/test_matrixCompare.cpp @@ -237,14 +237,7 @@ TEST(Matrix, unary) { testMatrixRotate(height, width); } // inverse matrix - void** dso_handler = nullptr; - GetLapackDsoHandle(dso_handler); - if (nullptr == *dso_handler) { - LOG(WARNING) << "Failed to find liblapack.so, please specify its path " - "using LD_LIBRARY_PATH."; - } else { - testMatrixInverse(height); - } + testMatrixInverse(height); } } diff --git a/paddle/utils/DynamicLoader.cpp b/paddle/utils/DynamicLoader.cpp index 368c35e1518..87c36eae6fb 100644 --- a/paddle/utils/DynamicLoader.cpp +++ b/paddle/utils/DynamicLoader.cpp @@ -52,7 +52,7 @@ static inline std::string join(const std::string& part1, static inline void GetDsoHandleFromDefaultPath(std::string& dso_path, void** dso_handle, int dynload_flags) { - VLOG(3) << "Try to find cuda library: " << dso_path + VLOG(3) << "Try to find library: " << dso_path << " from default system path."; // default search from LD_LIBRARY_PATH/DYLD_LIBRARY_PATH *dso_handle = dlopen(dso_path.c_str(), dynload_flags); -- GitLab From 668a1b8bfe6067d805e6a5fbc7164eff8cf217d9 Mon Sep 17 00:00:00 2001 From: Liu Yiqun Date: Thu, 4 May 2017 06:00:35 +0000 Subject: [PATCH 0170/3256] Correct the definition of PROTOBUF_VERSION. --- cmake/external/protobuf.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/external/protobuf.cmake b/cmake/external/protobuf.cmake index 994ba14cfa0..b61833e2687 100644 --- a/cmake/external/protobuf.cmake +++ b/cmake/external/protobuf.cmake @@ -75,6 +75,7 @@ FUNCTION(build_protobuf TARGET_NAME BUILD_FOR_HOST) LIST(APPEND external_project_dependencies ${TARGET_NAME} PARENT_SCOPE) ENDFUNCTION() +SET(PROTOBUF_VERSION 3.1) IF(NOT CMAKE_CROSSCOMPILING) FIND_PACKAGE(Protobuf ${PROTOBUF_VERSION}) @@ -91,7 +92,6 @@ ELSE() CACHE FILEPATH "protobuf executable." FORCE) ENDIF() -SET(PROTOBUF_VERSION 3.1) IF(NOT PROTOBUF_FOUND) build_protobuf(protobuf FALSE) SET(PROTOBUF_INCLUDE_DIR ${protobuf_INCLUDE_DIR} -- GitLab From 88401fe7a4b61a94a244f6f83eeeedf2f537e2d7 Mon Sep 17 00:00:00 2001 From: livc Date: Thu, 4 May 2017 14:37:53 +0800 Subject: [PATCH 0171/3256] update contribute_to_paddle_en.md --- doc/howto/dev/contribute_to_paddle_cn.md | 2 +- doc/howto/dev/contribute_to_paddle_en.md | 225 +++++++++++++++-------- 2 files changed, 150 insertions(+), 77 deletions(-) diff --git a/doc/howto/dev/contribute_to_paddle_cn.md b/doc/howto/dev/contribute_to_paddle_cn.md index 775938612e8..9fc1a173a60 100644 --- a/doc/howto/dev/contribute_to_paddle_cn.md +++ b/doc/howto/dev/contribute_to_paddle_cn.md @@ -83,7 +83,7 @@ no changes added to commit (use "git add" and/or "git commit -a") ➜ docker build -t paddle:dev . ``` -随后可以用这个开发镜像开build PaddlePaddle的源码。比如如果要build一个不依赖GPU,但是支持AVX指令集,并且包括unit tests的PaddlePaddle,可以: +随后可以用这个开发镜像开始build PaddlePaddle的源码。比如如果要build一个不依赖GPU,但是支持AVX指令集,并且包括unit tests的PaddlePaddle,可以: ```bash ➜ docker run -v $(pwd):/paddle -e "WITH_GPU=OFF" -e "WITH_AVX=ON" -e "WITH_TEST=ON" paddle:dev diff --git a/doc/howto/dev/contribute_to_paddle_en.md b/doc/howto/dev/contribute_to_paddle_en.md index 9b0d3e83c0d..a13eb549bab 100644 --- a/doc/howto/dev/contribute_to_paddle_en.md +++ b/doc/howto/dev/contribute_to_paddle_en.md @@ -4,9 +4,9 @@ We sincerely appreciate your contributions. You can use fork and pull request workflow to merge your code. ## Code Requirements -- Your code must be fully documented by - [doxygen](http://www.stack.nl/~dimitri/doxygen/) style. -- Make sure the compiler option WITH\_STYLE\_CHECK is on and the compiler +- Your code comments must be fully documented by + [Doxygen](http://www.stack.nl/~dimitri/doxygen/) style. +- Make sure the compiler option `WITH_STYLE_CHECK` is on and the compiler passes the code style check. - All code must have unit test. - Pass all unit tests. @@ -20,32 +20,25 @@ It's just that simple. ## Clone -Paddle is currently using [git-flow branching model](http://nvie.com/posts/a-successful-git-branching-model/). -The **develop** is the main branch, and other user's branches are feature branches. +Clone remote repository. -Once you've created a fork, you can use your favorite git client to clone your -repo or just head straight to the command line: - -```shell -# Clone your fork to your local machine -git clone --branch develop https://github.com/USERNAME/Paddle.git -``` -If your repository doesn't contain **develop** branch, just create it by your own. - -```shell -git clone https://github.com/USERNAME/Paddle.git Paddle -cd Paddle -git checkout -b develop # create develop branch. -git remote add upstream https://github.com/PaddlePaddle/Paddle.git # add upstream to baidu/Paddle -git pull upstream develop # update to upstream +```bash +➜ git clone https://github.com/USERNAME/Paddle +➜ cd Paddle ``` -Then you can start to develop by making a local developement branch +## Create a local branch + +Paddle is currently using [Git-flow branching model](http://nvie.com/posts/a-successful-git-branching-model/). -```shell -git checkout -b MY_COOL_STUFF_BRANCH +All feature and bug fix development work should be done on a new branch, generally create new branch from `develop` branch . + +```bash +➜ git checkout -b my-cool-stuff ``` +Before the checkout, you need to keep the current branch directory clean, otherwise the untracked file will be brought to the new branch, which can be inspected by `git status`. + ## Using `pre-commit` hook Paddle developers use [pre-commit](http://pre-commit.com/) tool to manage git @@ -58,89 +51,169 @@ To use [pre-commit](http://pre-commit.com/), you should install it by `pip install pre-commit`, and currently, Paddle uses `clang-format` to format c/cpp sources. Please make sure clang-format 3.8+ installed. -Then just run `pre-commit install` in your Paddle clone directory. When you -commit your code, the pre-commit hook will check the local code if there is +Install and run it as follow: + +```bash +➜ pip install pre-commit +➜ pre-commit install +``` + +When you commit your code, the pre-commit hook will check the local code if there is anything not suitable to commit, and so on. +## Start to develop + +In this tutorial, I delete a line in README.md and created a new file. + +We can use `git status` to inspect the changes of current directory, `git diff` to see difference. + +```bash +➜ git status +On branch test +Changes not staged for commit: + (use "git add ..." to update what will be committed) + (use "git checkout -- ..." to discard changes in working directory) + + modified: README.md + +Untracked files: + (use "git add ..." to include in what will be committed) + + test + +no changes added to commit (use "git add" and/or "git commit -a") +``` +## Build and Test + +We package PaddlePaddle's compile environment into a Docker image, called the develop image named `paddle:dev`, it contains all compiling tools that PaddlePaddle needs. + +If you want to build the develop image, just run: + +```bash +➜ docker build -t paddle:dev . +``` + +Then we can use the develop image to build PaddlePaddle source. For example: + +```bash +➜ docker run -v $(pwd):/paddle -e "WITH_GPU=OFF" -e "WITH_AVX=ON" -e "WITH_TEST=ON" paddle:dev +``` + +The above command will compile PaddlePaddle and create a Dockerfile for building production image. All the generated files are in the build directory. "WITH_GPU" controls if the generated production image supports GPU. "WITH_AVX" controls if the generated production image supports AVX. "WITH_TEST" controls if the unit test will be generated. + +Then we can generate the production image by copying the compiled PaddlePaddle program into the image by + +```bash +➜ docker build -t paddle:prod -f build/Dockerfile . +``` + +Run unit test finally: + +```bash +➜ docker run -it -v $(pwd):/paddle paddle:dev bash -c "cd /paddle/build && ctest" +``` + +For more details, you can read [this doc](https://github.com/PaddlePaddle/Paddle/blob/develop/doc/getstarted/build_and_install/docker_install_en.rst). + ## Commit -Commit your changes by following command lines: +Next we cancel the changes to the README.md file and then commit our changes by following command lines: + +```bash +➜ git checkout -- README.md +➜ git status +On branch test +Untracked files: + (use "git add ..." to include in what will be committed) + + test + +nothing added to commit but untracked files present (use "git add" to track) +➜ git add test +``` -```shell -# show the working tree status -git status -# add modified files -git add xx -env EDITOR=vim git commit # You can write your comments by vim/nano/emacs. +We should write a description of each commit by `git commit` to allow others to know +the changes in these files. + +```bash +➜ git commit +CRLF end-lines remover...............................(no files to check)Skipped +yapf.................................................(no files to check)Skipped +Check for added large files..............................................Passed +Check for merge conflicts................................................Passed +Check for broken symlinks................................................Passed +Detect Private Key...................................(no files to check)Skipped +Fix End of Files.....................................(no files to check)Skipped +clang-formater.......................................(no files to check)Skipped +[my-cool-stuff c703c041] add test file + 1 file changed, 0 insertions(+), 0 deletions(-) + create mode 100644 233 ``` -The first line of commit infomation is the title. The second and later lines -are the details if any. ## Keeping Fork Up to Date Before pull your request, you should sync your code from the latest PaddlePaddle. To do this, you'll need to add a remote at first: -```shell -# see the current configured remote repository -git remote -v -# add upstream repository -git remote add upstream https://github.com/PaddlePaddle/Paddle.git -# verify the new upstream -git remote -v +```bash +➜ git remote add upstream https://github.com/PaddlePaddle/Paddle +➜ git remote +origin +upstream ``` Update your fork with the latest upstream changes: -```shell -git pull --rebase upstream develop +```bash +➜ git fetch upstream +➜ git pull upstream develop ``` -If there are no unique commits locally, git will simply perform a fast-forward. -However, if you have been making changes (in the vast majority of cases you -probably shouldn't be), you may have to deal with conflicts. - Now, your local master branch is up-to-date with everything modified upstream. ## Push to GitHub -```shell +```bash # push to your repository in Github -git push -u origin MY_COOL_STUFF_BRANCH # create remote branch MY_COOL_STUFF_BRANCH to origin. +➜ git push origin my-cool-stuff ``` -## Pull Request +## Create an issue and a Ppull Request + +Create an Issue to describe the problem and record its number. Go to the page for your fork on GitHub, select your development branch, -and click the **pull request button**. - -## Update your pull request with the lastest version - -During the code review, your pull request may become stale because new commits in -baidu/Paddle. GitHub allows autmotic update if there is no conflict. You can do this -by clicking the "Update Branch" button in your pull request page. However, in the case -of conflict, you need to do the update manually. You need to do the following on -your local repository: -```shell -git checkout MY_COOL_STUFF_BRANCH -git pull upstream develop -# You may need to resolve the conflict according to the git prompt. -# Make and test your code. -git push origin MY_COOL_STUFF_BRANCH +and click the `New pull request`. + +screen shot 2017-04-26 at 9 09 28 pm + +Then select the target branch: + +screen shot 2017-04-26 at 9 11 52 pm + +We can add `resolve #Issue number` in PR description to close the issue automatically after the PR is merge. More details in . + +Then wait for review, if there is a need to modify, refer to the above steps to update the corresponding branch in origin. + +## Delete origin branch + +After the PR is merge into the main repository, we can delete the remote branch on the PR page. + +screen shot 2017-04-26 at 9 18 24 pm + +Or just run: + +```bash +➜ git push origin :my-cool-stuff ``` -Now your Pull Request is updated with the latest version. -## Revise your pull request +## Delete local branch -When you revise your pull request according to reviewer's comments, please use 'git commit' instead of 'git commit --amend' to commit your changes so that the reviewers can see the difference between the new pull requrest and the old pull request. +Finally, we delete local branch: -The possible commands are +```bash +➜ git checkout develop -```shell -git checkout MY_COOL_STUFF_BRANCH -git pull upstream develop # update local to newest code base. -# May be some conflicts will occured. -# And develop your cool stuff -env EDITOR=vim git commit # add your revise log -git push origin MY_COOL_STUFF_BRANCH +# delete my-cool-stuff branch +➜ git branch -D my-cool-stuff ``` -- GitLab From aa87b1c8a13077d29311156257af17710a44c762 Mon Sep 17 00:00:00 2001 From: livc Date: Thu, 4 May 2017 14:51:52 +0800 Subject: [PATCH 0172/3256] fix details --- doc/howto/dev/contribute_to_paddle_en.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/howto/dev/contribute_to_paddle_en.md b/doc/howto/dev/contribute_to_paddle_en.md index a13eb549bab..40d1eb62d72 100644 --- a/doc/howto/dev/contribute_to_paddle_en.md +++ b/doc/howto/dev/contribute_to_paddle_en.md @@ -178,7 +178,7 @@ Now, your local master branch is up-to-date with everything modified upstream. ➜ git push origin my-cool-stuff ``` -## Create an issue and a Ppull Request +## Create an issue and a Pull Request Create an Issue to describe the problem and record its number. @@ -193,7 +193,7 @@ Then select the target branch: We can add `resolve #Issue number` in PR description to close the issue automatically after the PR is merge. More details in . -Then wait for review, if there is a need to modify, refer to the above steps to update the corresponding branch in origin. +Then wait for review, if there need to modify, refer to the above steps to update the corresponding origin branch. ## Delete origin branch -- GitLab From b55bde1b51926c6cf7aafeb3e07ad2d87ad8fed7 Mon Sep 17 00:00:00 2001 From: Liu Yiqun Date: Thu, 4 May 2017 07:36:00 +0000 Subject: [PATCH 0173/3256] Fix bug of setting external_project_dependencies in cmake. --- cmake/external/protobuf.cmake | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cmake/external/protobuf.cmake b/cmake/external/protobuf.cmake index b61833e2687..b35e6839cdc 100644 --- a/cmake/external/protobuf.cmake +++ b/cmake/external/protobuf.cmake @@ -71,8 +71,6 @@ FUNCTION(build_protobuf TARGET_NAME BUILD_FOR_HOST) -DCMAKE_POSITION_INDEPENDENT_CODE:BOOL=ON ${OPTIONAL_CACHE_ARGS} ) - - LIST(APPEND external_project_dependencies ${TARGET_NAME} PARENT_SCOPE) ENDFUNCTION() SET(PROTOBUF_VERSION 3.1) @@ -88,12 +86,16 @@ IF(NOT CMAKE_CROSSCOMPILING) ENDIF(PROTOBUF_FOUND) ELSE() build_protobuf(protobuf_host TRUE) + LIST(APPEND external_project_dependencies protobuf_host) + SET(PROTOBUF_PROTOC_EXECUTABLE ${protobuf_host_PROTOC_EXECUTABLE} CACHE FILEPATH "protobuf executable." FORCE) ENDIF() IF(NOT PROTOBUF_FOUND) build_protobuf(protobuf FALSE) + LIST(APPEND external_project_dependencies protobuf) + SET(PROTOBUF_INCLUDE_DIR ${protobuf_INCLUDE_DIR} CACHE PATH "protobuf include directory." FORCE) IF(NOT CMAKE_CROSSCOMPILING) -- GitLab From 0d7d8990db3f40f2bdc5853fd2d2aa97e8161774 Mon Sep 17 00:00:00 2001 From: gongweibao Date: Thu, 4 May 2017 16:36:53 +0800 Subject: [PATCH 0174/3256] add cp... --- doc/design/file_mananger/README.md | 56 ++++++++++++++++++++++++++---- 1 file changed, 50 insertions(+), 6 deletions(-) diff --git a/doc/design/file_mananger/README.md b/doc/design/file_mananger/README.md index 0dd7e05d248..d7aa808d6c3 100644 --- a/doc/design/file_mananger/README.md +++ b/doc/design/file_mananger/README.md @@ -5,7 +5,7 @@ ## Module ### Client -Client是用户操作的界面,支持的命令如下 +Client是用户操作的命令行程序,支持的命令如下 - ls @@ -39,8 +39,12 @@ mv ### FileServer -FileServer是gorpc写的HttpServer, 用来接收Client的REST API的请求,自身由kubernets来管理。 -REST API说明 +功能说明: +- gorpc写的HttpServer +- 响应外部的REST API的请求 +- 在kubernets中运行 + +REST API说明: - file @@ -56,6 +60,17 @@ DELETE /file: Delete a File GET /file/chunk: Get a chunk info POST /file/chunk: Update a chunk ``` +为什么有chunk的抽象: +用户文件可能是比较大的,上传到Cloud或者下载到本地的时间可能比较长,而且在传输的过程中也可能出现网络不稳定的情况。为了应对以上的问题,我们提出了chunk的概念。chunk由所在的文件偏移、数据、数据长度及校验值组成。数据内容的上传和下载都是都过chunk的操作来实现的。由于chunk比较小(默认256K),完成一个传输动作的transaction的时间也比较短,不容易出错。 + +``` +type Chunk struct { + filePos int64 + checkSum uint32 + len uint32 + data []byte +} +``` - dir @@ -67,6 +82,35 @@ DELETE /dir: Delete a directory ## 流程 -### cp -### 断点续传 -### sync +### 关于文件权限 +- 每一个用户在Cloud注册后可以申请分配用户空间,系统默认会在CephFS上分配一个固定大小(比如初始10G)的、有所有权限的volume,对用户而言就是自己的`home`目录。用户彼此之间的数据是隔离的、无法访问的。用户的空间大小第一期也不允许扩大。 +- 公共数据集合放到一个单独的volume下,对所有外部用户只读。由于其被读取的可能比较频繁,需要提高其备份数,防止成为热点文件。 + +### 关于认证 +> 通信各方都需要有各自的身份证。一个公司可以自签名一个CA身份证,并 且用它来给每个雇员以及每个程序签署身份证。这样,只要每台电脑上都预先安 装好公司自己的CA身份证,就可以用这个身份证验证每个雇员和程序的身份了。 这是目前很多公司的常用做法 + +身份的认证来自于用户或者程序是否有crt标识身份,以及是否有可信的CA确认这个身份证是否有效。我们这里描述的crt涉及到两个部分,一个是Client端程序访问FileServer的crt,不妨称之为Client crt;另外一个是FileServer访问CephFS的crt,不妨称之为CephFS crt。 + +- Client和FileServer相互认证的办法 +`cloud.paddlepaddle.org`需要有自己的CA,FileServer和注册用户也要为其生成各自的私钥(key)、crt。这样用户把CA、自己的key和crt下载到本地后,Client程序可以用之和FileServer可以做相互的认证 + +- CephFS验证FileServer的身份的两种方法 + - 第一种:每一个用户都有自己单独的访问CephFS crt。 + 用户访问其空间时,由FileServer读取它然后才可以在CephFS上完成操作。 + - 第二种:CephFS crt只有一个,也就是admin crt,拥有所有volume的读写权限。 + FileServer从Client crt提取Client的身份(username),限制其可以操作的volume。 我们选择这种。 + +### 关于cp +cp的关键在于需要Client端对比src和dst的文件chunks的checkSum是否保持一致,不一致的由Client Get或者Post完成。藉由上述的方法完成断点的数据传输。 + +优化的方法: + +- dst文件不存在时,可以没有Get的过程,只有Post。 +- 文件的chunks信息可以做cache,不用每次启动传输都去读和计算。这个由于比较复杂,第一期暂时不做。 + +tricky: + +- 可以用[Fallocate](https://golang.org/pkg/syscall/#Fallocate)让dst和src文件保持相同的大小。这样,chunk就可以写固定的偏移上。 + +## 参考文档 +- [Do you see tls?](https://github.com/k8sp/tls/blob/master/README.md) -- GitLab From 20cda7bd23195da38dec324f8a7b4ac698fec1f4 Mon Sep 17 00:00:00 2001 From: liaogang Date: Thu, 4 May 2017 17:27:05 +0800 Subject: [PATCH 0175/3256] auto check lapack libs --- cmake/cblas.cmake | 4 +++- cmake/external/openblas.cmake | 1 + paddle/math/MathFunctions.cpp | 13 ++++++++++++- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/cmake/cblas.cmake b/cmake/cblas.cmake index 0918e6cc633..91663a16ba6 100644 --- a/cmake/cblas.cmake +++ b/cmake/cblas.cmake @@ -33,7 +33,6 @@ find_library(MKL_INTEL_LP64 NAMES mkl_intel_lp64 PATHS ${MKL_ROOT}/lib ${MKL_ROOT}/lib/intel64) - if(MKL_INC_DIR AND MKL_CORE_LIB AND MKL_SEQUENTIAL_LIB AND MKL_INTEL_LP64) set(CBLAS_PROVIDER MKL) set(CBLAS_INC_DIR ${MKL_INC_DIR}) @@ -41,6 +40,7 @@ if(MKL_INC_DIR AND MKL_CORE_LIB AND MKL_SEQUENTIAL_LIB AND MKL_INTEL_LP64) ${MKL_SEQUENTIAL_LIB} ${MKL_CORE_LIB}) add_definitions(-DPADDLE_USE_MKL) + add_definitions(-DLAPACK_FOUND) message(STATUS "Found MKL (include: ${CBLAS_INC_DIR}, library: ${CBLAS_LIBRARIES})") set(CBLAS_FOUND ON) if(${MKL_LAPACK_INC_DIR}) @@ -76,6 +76,7 @@ if(ATLAS_INC_DIR AND ATLAS_CBLAS_LIB AND ATLAS_LIB AND NOT CBLAS_FOUND) set(CBLAS_INC_DIR ${ATLAS_INC_DIR}) set(CBLAS_LIBRARIES ${ATLAS_LIB} ${ATLAS_CBLAS_LIB}) add_definitions(-DPADDLE_USE_ATLAS) + add_definitions(-DLAPACK_FOUND) message(STATUS "Found ATLAS (include: ${CBLAS_INC_DIR}, library: ${CBLAS_LIBRARIES})") set(CBLAS_FOUND ON) if(ATLAS_CLAPACK_INC_DIR) @@ -110,6 +111,7 @@ if(OPENBLAS_INC_DIR AND OPENBLAS_LIB) set(CBLAS_PROVIDER OPENBLAS) set(CBLAS_INC_DIR ${OPENBLAS_INC_DIR}) set(CBLAS_LIBRARIES ${OPENBLAS_LIB}) + add_definitions(-DLAPACK_FOUND) message(STATUS "Found OpenBLAS (include: ${CBLAS_INC_DIR}, library: ${CBLAS_LIBRARIES})") set(CBLAS_FOUND ON) if(OPENBLAS_LAPACKE_INC_DIR) diff --git a/cmake/external/openblas.cmake b/cmake/external/openblas.cmake index 46398b22c27..86cb473c385 100644 --- a/cmake/external/openblas.cmake +++ b/cmake/external/openblas.cmake @@ -15,6 +15,7 @@ INCLUDE(cblas) IF(NOT ${CBLAS_FOUND}) + SET(LAPACK_FOUND OFF) INCLUDE(ExternalProject) SET(CBLAS_SOURCES_DIR ${THIRD_PARTY_PATH}/openblas) diff --git a/paddle/math/MathFunctions.cpp b/paddle/math/MathFunctions.cpp index 802a56a0d15..1a3bb432bfb 100644 --- a/paddle/math/MathFunctions.cpp +++ b/paddle/math/MathFunctions.cpp @@ -34,6 +34,9 @@ void* lapack_dso_handle = nullptr; // We have to use two levels of macro to do the expansion. // See https://gcc.gnu.org/onlinedocs/cpp/Stringizing.html #define STR(x) #x + +// clang-format off +#ifndef LAPACK_FOUND #define DYNAMIC_LOAD_LAPACK_WRAP(__name) \ struct DynLoad__##__name { \ template \ @@ -46,8 +49,16 @@ void* lapack_dso_handle = nullptr; return reinterpret_cast(p_##__name)(args...); \ } \ } __name; // struct DynLoad__##__name +#else +#define DYNAMIC_LOAD_LAPACK_WRAP(__name) \ + struct DynLoad__##__name { \ + template \ + auto operator()(Args... args) -> decltype(__name(args...)) { \ + return __name(args...); \ + } \ + } __name; // struct DynLoad__##__name +#endif -// clang-format off #ifdef PADDLE_USE_ATLAS #define PADDLE_SGETRF clapack_sgetrf #define PADDLE_DGETRF clapack_dgetrf -- GitLab From 6c2e45537ed7de127c8e43b3958f001eae777a95 Mon Sep 17 00:00:00 2001 From: Luo Tao Date: Thu, 4 May 2017 18:54:51 +0800 Subject: [PATCH 0176/3256] add missing api doc of network and evaluator --- .../v1/trainer_config_helpers/activations.rst | 108 ---- doc/api/v1/trainer_config_helpers/attrs.rst | 5 - .../trainer_config_helpers/data_sources.rst | 7 - .../v1/trainer_config_helpers/evaluators.rst | 108 ---- doc/api/v1/trainer_config_helpers/layers.rst | 514 ------------------ .../v1/trainer_config_helpers/networks.rst | 123 ----- .../v1/trainer_config_helpers/optimizers.rst | 61 --- .../v1/trainer_config_helpers/poolings.rst | 33 -- doc/api/v2/config/evaluators.rst | 101 ++++ doc/api/v2/config/networks.rst | 18 + doc/api/v2/model_configs.rst | 1 + python/paddle/v2/inference.py | 5 +- 12 files changed, 123 insertions(+), 961 deletions(-) delete mode 100644 doc/api/v1/trainer_config_helpers/activations.rst delete mode 100644 doc/api/v1/trainer_config_helpers/attrs.rst delete mode 100644 doc/api/v1/trainer_config_helpers/data_sources.rst delete mode 100644 doc/api/v1/trainer_config_helpers/evaluators.rst delete mode 100644 doc/api/v1/trainer_config_helpers/layers.rst delete mode 100644 doc/api/v1/trainer_config_helpers/networks.rst delete mode 100644 doc/api/v1/trainer_config_helpers/optimizers.rst delete mode 100644 doc/api/v1/trainer_config_helpers/poolings.rst create mode 100644 doc/api/v2/config/evaluators.rst diff --git a/doc/api/v1/trainer_config_helpers/activations.rst b/doc/api/v1/trainer_config_helpers/activations.rst deleted file mode 100644 index 269e6491e7e..00000000000 --- a/doc/api/v1/trainer_config_helpers/activations.rst +++ /dev/null @@ -1,108 +0,0 @@ -=========== -Activations -=========== - -BaseActivation -============== - -.. automodule:: paddle.trainer_config_helpers.activations - :members: BaseActivation - :noindex: - -AbsActivation -=============== - -.. automodule:: paddle.trainer_config_helpers.activations - :members: AbsActivation - :noindex: - -ExpActivation -=============== - -.. automodule:: paddle.trainer_config_helpers.activations - :members: ExpActivation - :noindex: - -IdentityActivation -================== - -.. automodule:: paddle.trainer_config_helpers.activations - :members: IdentityActivation - :noindex: - -LinearActivation -================== - -.. automodule:: paddle.trainer_config_helpers.activations - :members: LinearActivation - :noindex: - -LogActivation -================== - -.. automodule:: paddle.trainer_config_helpers.activations - :members: LogActivation - :noindex: - -SquareActivation -================ - -.. automodule:: paddle.trainer_config_helpers.activations - :members: SquareActivation - :noindex: - -SigmoidActivation -================= - -.. automodule:: paddle.trainer_config_helpers.activations - :members: SigmoidActivation - :noindex: - -SoftmaxActivation -================= - -.. automodule:: paddle.trainer_config_helpers.activations - :members: SoftmaxActivation - :noindex: - -SequenceSoftmaxActivation -========================= - -.. automodule:: paddle.trainer_config_helpers.activations - :members: SequenceSoftmaxActivation - :noindex: - -ReluActivation -============== - -.. automodule:: paddle.trainer_config_helpers.activations - :members: ReluActivation - :noindex: - -BReluActivation -=============== - -.. automodule:: paddle.trainer_config_helpers.activations - :members: BReluActivation - :noindex: - -SoftReluActivation -================== - -.. automodule:: paddle.trainer_config_helpers.activations - :members: SoftReluActivation - :noindex: - -TanhActivation -============== - -.. automodule:: paddle.trainer_config_helpers.activations - :members: TanhActivation - :noindex: - -STanhActivation -=============== - -.. automodule:: paddle.trainer_config_helpers.activations - :members: STanhActivation - :noindex: diff --git a/doc/api/v1/trainer_config_helpers/attrs.rst b/doc/api/v1/trainer_config_helpers/attrs.rst deleted file mode 100644 index ac63127bf7d..00000000000 --- a/doc/api/v1/trainer_config_helpers/attrs.rst +++ /dev/null @@ -1,5 +0,0 @@ -Parameter Attributes -======================= - -.. automodule:: paddle.trainer_config_helpers.attrs - :members: diff --git a/doc/api/v1/trainer_config_helpers/data_sources.rst b/doc/api/v1/trainer_config_helpers/data_sources.rst deleted file mode 100644 index b9dd4dda01a..00000000000 --- a/doc/api/v1/trainer_config_helpers/data_sources.rst +++ /dev/null @@ -1,7 +0,0 @@ -.. _api_trainer_config_helpers_data_sources: - -DataSources -=========== - -.. automodule:: paddle.trainer_config_helpers.data_sources - :members: diff --git a/doc/api/v1/trainer_config_helpers/evaluators.rst b/doc/api/v1/trainer_config_helpers/evaluators.rst deleted file mode 100644 index 11dc7351642..00000000000 --- a/doc/api/v1/trainer_config_helpers/evaluators.rst +++ /dev/null @@ -1,108 +0,0 @@ -.. _api_trainer_config_helpers_evaluators: - -========== -Evaluators -========== - -Base -==== -.. automodule:: paddle.trainer_config_helpers.evaluators - :members: evaluator_base - :noindex: - -Classification -============== - -classification_error_evaluator ------------------------------- -.. automodule:: paddle.trainer_config_helpers.evaluators - :members: classification_error_evaluator - :noindex: - -auc_evaluator -------------- -.. automodule:: paddle.trainer_config_helpers.evaluators - :members: auc_evaluator - :noindex: - -ctc_error_evaluator -------------------- -.. automodule:: paddle.trainer_config_helpers.evaluators - :members: ctc_error_evaluator - :noindex: - -chunk_evaluator ---------------- -.. automodule:: paddle.trainer_config_helpers.evaluators - :members: chunk_evaluator - :noindex: - -precision_recall_evaluator --------------------------- -.. automodule:: paddle.trainer_config_helpers.evaluators - :members: precision_recall_evaluator - :noindex: - -Rank -==== - -pnpair_evaluator ----------------- -.. automodule:: paddle.trainer_config_helpers.evaluators - :members: pnpair_evaluator - :noindex: - -Utils -===== - -sum_evaluator -------------- -.. automodule:: paddle.trainer_config_helpers.evaluators - :members: sum_evaluator - :noindex: - -column_sum_evaluator --------------------- -.. automodule:: paddle.trainer_config_helpers.evaluators - :members: column_sum_evaluator - :noindex: - -Print -===== - -classification_error_printer_evaluator --------------------------------------- -.. automodule:: paddle.trainer_config_helpers.evaluators - :members: classification_error_printer_evaluator - :noindex: - -gradient_printer_evaluator --------------------------- -.. automodule:: paddle.trainer_config_helpers.evaluators - :members: gradient_printer_evaluator - :noindex: - -maxid_printer_evaluator ------------------------ -.. automodule:: paddle.trainer_config_helpers.evaluators - :members: maxid_printer_evaluator - :noindex: - -maxframe_printer_evaluator ---------------------------- -.. automodule:: paddle.trainer_config_helpers.evaluators - :members: maxframe_printer_evaluator - :noindex: - -seqtext_printer_evaluator -------------------------- -.. automodule:: paddle.trainer_config_helpers.evaluators - :members: seqtext_printer_evaluator - :noindex: - -value_printer_evaluator ------------------------ -.. automodule:: paddle.trainer_config_helpers.evaluators - :members: value_printer_evaluator - :noindex: - diff --git a/doc/api/v1/trainer_config_helpers/layers.rst b/doc/api/v1/trainer_config_helpers/layers.rst deleted file mode 100644 index 75c1b352464..00000000000 --- a/doc/api/v1/trainer_config_helpers/layers.rst +++ /dev/null @@ -1,514 +0,0 @@ -.. _api_trainer_config_helpers_layers: - -====== -Layers -====== - -Base -====== - -LayerType ---------- -.. automodule:: paddle.trainer_config_helpers.layers - :members: LayerType - :noindex: - -LayerOutput ------------ -.. automodule:: paddle.trainer_config_helpers.layers - :members: LayerOutput - :noindex: - -Data layer -=========== - -.. _api_trainer_config_helpers_layers_data_layer: - -data_layer ----------- -.. automodule:: paddle.trainer_config_helpers.layers - :members: data_layer - :noindex: - -Fully Connected Layers -====================== - -.. _api_trainer_config_helpers_layers_fc_layer: - -fc_layer --------- -.. automodule:: paddle.trainer_config_helpers.layers - :members: fc_layer - :noindex: - -selective_fc_layer ------------------- -.. automodule:: paddle.trainer_config_helpers.layers - :members: selective_fc_layer - :noindex: - -Conv Layers -=========== - -conv_operator -------------- -.. automodule:: paddle.trainer_config_helpers.layers - :members: conv_operator - :noindex: - -conv_projection ---------------- -.. automodule:: paddle.trainer_config_helpers.layers - :members: conv_projection - :noindex: - -conv_shift_layer ------------------- -.. automodule:: paddle.trainer_config_helpers.layers - :members: conv_shift_layer - :noindex: - -img_conv_layer --------------- -.. automodule:: paddle.trainer_config_helpers.layers - :members: img_conv_layer - :noindex: - -.. _api_trainer_config_helpers_layers_context_projection: - -context_projection ------------------- -.. automodule:: paddle.trainer_config_helpers.layers - :members: context_projection - :noindex: - -Image Pooling Layer -=================== - -img_pool_layer --------------- -.. automodule:: paddle.trainer_config_helpers.layers - :members: img_pool_layer - :noindex: - -spp_layer --------------- -.. automodule:: paddle.trainer_config_helpers.layers - :members: spp_layer - :noindex: - -maxout_layer ------------- -.. automodule:: paddle.trainer_config_helpers.layers - :members: maxout_layer - :noindex: - -Norm Layer -========== - -img_cmrnorm_layer ------------------ -.. automodule:: paddle.trainer_config_helpers.layers - :members: img_cmrnorm_layer - :noindex: - -batch_norm_layer ---------------------- -.. automodule:: paddle.trainer_config_helpers.layers - :members: batch_norm_layer - :noindex: - -sum_to_one_norm_layer ---------------------- -.. automodule:: paddle.trainer_config_helpers.layers - :members: sum_to_one_norm_layer - :noindex: - -Recurrent Layers -================ - -recurrent_layer ------------------ -.. automodule:: paddle.trainer_config_helpers.layers - :members: recurrent_layer - :noindex: - -lstmemory ---------- -.. automodule:: paddle.trainer_config_helpers.layers - :members: lstmemory - :noindex: - -grumemory ---------- -.. automodule:: paddle.trainer_config_helpers.layers - :members: grumemory - :noindex: - -Recurrent Layer Group -===================== - -memory ------- -.. automodule:: paddle.trainer_config_helpers.layers - :members: memory - :noindex: - -recurrent_group ---------------- -.. automodule:: paddle.trainer_config_helpers.layers - :members: recurrent_group - :noindex: - -lstm_step_layer ---------------- -.. automodule:: paddle.trainer_config_helpers.layers - :members: lstm_step_layer - :noindex: - -gru_step_layer ---------------- -.. automodule:: paddle.trainer_config_helpers.layers - :members: gru_step_layer - :noindex: - -beam_search ------------- -.. automodule:: paddle.trainer_config_helpers.layers - :members: beam_search - :noindex: - -get_output_layer ------------------ -.. automodule:: paddle.trainer_config_helpers.layers - :members: get_output_layer - :noindex: - -Mixed Layer -=========== - -.. _api_trainer_config_helpers_layers_mixed_layer: - -mixed_layer ------------ -.. automodule:: paddle.trainer_config_helpers.layers - :members: mixed_layer - :noindex: - -.. _api_trainer_config_helpers_layers_embedding_layer: - -embedding_layer ---------------- -.. automodule:: paddle.trainer_config_helpers.layers - :members: embedding_layer - :noindex: - -scaling_projection ------------------- -.. automodule:: paddle.trainer_config_helpers.layers - :members: scaling_projection - :noindex: - -dotmul_projection ------------------ -.. automodule:: paddle.trainer_config_helpers.layers - :members: dotmul_projection - :noindex: - -dotmul_operator ---------------- -.. automodule:: paddle.trainer_config_helpers.layers - :members: dotmul_operator - :noindex: - -full_matrix_projection ----------------------- -.. automodule:: paddle.trainer_config_helpers.layers - :members: full_matrix_projection - :noindex: - -identity_projection -------------------- -.. automodule:: paddle.trainer_config_helpers.layers - :members: identity_projection - :noindex: - - -table_projection ----------------------- -.. automodule:: paddle.trainer_config_helpers.layers - :members: table_projection - :noindex: - -trans_full_matrix_projection ----------------------------- -.. automodule:: paddle.trainer_config_helpers.layers - :members: trans_full_matrix_projection - :noindex: - -Aggregate Layers -================ - -.. _api_trainer_config_helpers_layers_pooling_layer: - -pooling_layer -------------- -.. automodule:: paddle.trainer_config_helpers.layers - :members: pooling_layer - :noindex: - -.. _api_trainer_config_helpers_layers_last_seq: - -last_seq --------- -.. automodule:: paddle.trainer_config_helpers.layers - :members: last_seq - :noindex: - -.. _api_trainer_config_helpers_layers_first_seq: - -first_seq ---------- -.. automodule:: paddle.trainer_config_helpers.layers - :members: first_seq - :noindex: - -concat_layer ------------- -.. automodule:: paddle.trainer_config_helpers.layers - :members: concat_layer - :noindex: - -seq_concat_layer ----------------- -.. automodule:: paddle.trainer_config_helpers.layers - :members: seq_concat_layer - :noindex: - -Reshaping Layers -================ - -block_expand_layer ------------------- -.. automodule:: paddle.trainer_config_helpers.layers - :members: block_expand_layer - :noindex: - -.. _api_trainer_config_helpers_layers_expand_layer: - -expand_layer ------------- -.. automodule:: paddle.trainer_config_helpers.layers - :members: expand_layer - :noindex: - -repeat_layer ------------- -.. automodule:: paddle.trainer_config_helpers.layers - :members: repeat_layer - :noindex: - -rotate_layer ------------- -.. automodule:: paddle.trainer_config_helpers.layers - :members: rotate_layer - :noindex: - -seq_reshape_layer ------------------ -.. automodule:: paddle.trainer_config_helpers.layers - :members: seq_reshape_layer - :noindex: - -Math Layers -=========== - -addto_layer ------------ -.. automodule:: paddle.trainer_config_helpers.layers - :members: addto_layer - :noindex: - -linear_comb_layer ------------------ -.. automodule:: paddle.trainer_config_helpers.layers - :members: linear_comb_layer - :noindex: - -interpolation_layer -------------------- -.. automodule:: paddle.trainer_config_helpers.layers - :members: interpolation_layer - :noindex: - -bilinear_interp_layer ----------------------- -.. automodule:: paddle.trainer_config_helpers.layers - :members: bilinear_interp_layer - :noindex: - -power_layer ------------ -.. automodule:: paddle.trainer_config_helpers.layers - :members: power_layer - :noindex: - -scaling_layer -------------- -.. automodule:: paddle.trainer_config_helpers.layers - :members: scaling_layer - :noindex: - -slope_intercept_layer ----------------------- -.. automodule:: paddle.trainer_config_helpers.layers - :members: slope_intercept_layer - :noindex: - -tensor_layer ------------- -.. automodule:: paddle.trainer_config_helpers.layers - :members: tensor_layer - :noindex: - -.. _api_trainer_config_helpers_layers_cos_sim: - -cos_sim -------- -.. automodule:: paddle.trainer_config_helpers.layers - :members: cos_sim - :noindex: - -trans_layer ------------- -.. automodule:: paddle.trainer_config_helpers.layers - :members: trans_layer - :noindex: - -Sampling Layers -=============== - -maxid_layer ------------ -.. automodule:: paddle.trainer_config_helpers.layers - :members: maxid_layer - :noindex: - -sampling_id_layer ------------------ -.. automodule:: paddle.trainer_config_helpers.layers - :members: sampling_id_layer - :noindex: - -Slicing and Joining Layers -========================== - -pad_layer ------------ -.. automodule:: paddle.trainer_config_helpers.layers - :members: pad_layer - :noindex: - -.. _api_trainer_config_helpers_layers_cost_layers: - -Cost Layers -=========== - -cross_entropy -------------- -.. automodule:: paddle.trainer_config_helpers.layers - :members: cross_entropy - :noindex: - -cross_entropy_with_selfnorm ---------------------------- -.. automodule:: paddle.trainer_config_helpers.layers - :members: cross_entropy_with_selfnorm - :noindex: - -multi_binary_label_cross_entropy --------------------------------- -.. automodule:: paddle.trainer_config_helpers.layers - :members: multi_binary_label_cross_entropy - :noindex: - -mse_cost ---------- -.. automodule:: paddle.trainer_config_helpers.layers - :members: mse_cost - :noindex: - -huber_cost ----------- -.. automodule:: paddle.trainer_config_helpers.layers - :members: huber_cost - :noindex: - -lambda_cost ------------ -.. automodule:: paddle.trainer_config_helpers.layers - :members: lambda_cost - :noindex: - -rank_cost ---------- -.. automodule:: paddle.trainer_config_helpers.layers - :members: rank_cost - :noindex: - -sum_cost ---------- -.. automodule:: paddle.trainer_config_helpers.layers - :members: sum_cost - :noindex: - -crf_layer ------------------ -.. automodule:: paddle.trainer_config_helpers.layers - :members: crf_layer - :noindex: - -crf_decoding_layer -------------------- -.. automodule:: paddle.trainer_config_helpers.layers - :members: crf_decoding_layer - :noindex: - -ctc_layer ------------ -.. automodule:: paddle.trainer_config_helpers.layers - :members: ctc_layer - :noindex: - -warp_ctc_layer --------------- -.. automodule:: paddle.trainer_config_helpers.layers - :members: warp_ctc_layer - :noindex: - -nce_layer ------------ -.. automodule:: paddle.trainer_config_helpers.layers - :members: nce_layer - :noindex: - -hsigmoid ---------- -.. automodule:: paddle.trainer_config_helpers.layers - :members: hsigmoid - :noindex: - -smooth_l1_cost --------------- -.. automodule:: paddle.trainer_config_helpers.layers - :members: smooth_l1_cost - :noindex: - -Check Layer -============ - -eos_layer ------------- -.. automodule:: paddle.trainer_config_helpers.layers - :members: eos_layer - :noindex: diff --git a/doc/api/v1/trainer_config_helpers/networks.rst b/doc/api/v1/trainer_config_helpers/networks.rst deleted file mode 100644 index edb53acbf0c..00000000000 --- a/doc/api/v1/trainer_config_helpers/networks.rst +++ /dev/null @@ -1,123 +0,0 @@ -======== -Networks -======== - -The networks module contains pieces of neural network that combine multiple layers. - -NLP -=== - -sequence_conv_pool ------------------- -.. automodule:: paddle.trainer_config_helpers.networks - :members: sequence_conv_pool - :noindex: - -.. _api_trainer_config_helpers_network_text_conv_pool: - -text_conv_pool --------------- -.. automodule:: paddle.trainer_config_helpers.networks - :members: text_conv_pool - :noindex: - -Images -====== - -img_conv_bn_pool ----------------- -.. automodule:: paddle.trainer_config_helpers.networks - :members: img_conv_bn_pool - :noindex: - -img_conv_group --------------- -.. automodule:: paddle.trainer_config_helpers.networks - :members: img_conv_group - :noindex: - -.. _api_trainer_config_helpers_network_simple_img_conv_pool: - -simple_img_conv_pool --------------------- -.. automodule:: paddle.trainer_config_helpers.networks - :members: simple_img_conv_pool - :noindex: - -vgg_16_network ---------------- -.. automodule:: paddle.trainer_config_helpers.networks - :members: vgg_16_network - :noindex: - -Recurrent -========= - -LSTM ----- - -lstmemory_unit -`````````````` -.. automodule:: paddle.trainer_config_helpers.networks - :members: lstmemory_unit - :noindex: - -lstmemory_group -``````````````` -.. automodule:: paddle.trainer_config_helpers.networks - :members: lstmemory_group - :noindex: - -simple_lstm -``````````` -.. automodule:: paddle.trainer_config_helpers.networks - :members: simple_lstm - :noindex: - -bidirectional_lstm -`````````````````` -.. automodule:: paddle.trainer_config_helpers.networks - :members: bidirectional_lstm - :noindex: - -GRU ---- - -gru_unit -```````` -.. automodule:: paddle.trainer_config_helpers.networks - :members: gru_unit - :noindex: - -gru_group -````````` -.. automodule:: paddle.trainer_config_helpers.networks - :members: gru_group - :noindex: - -simple_gru -`````````` -.. automodule:: paddle.trainer_config_helpers.networks - :members: simple_gru - :noindex: - -simple_attention ----------------- -.. automodule:: paddle.trainer_config_helpers.networks - :members: simple_attention - :noindex: - -Miscs -===== - -dropout_layer --------------- -.. automodule:: paddle.trainer_config_helpers.networks - :members: dropout_layer - :noindex: - -outputs -------- -.. automodule:: paddle.trainer_config_helpers.networks - :members: outputs - :noindex: diff --git a/doc/api/v1/trainer_config_helpers/optimizers.rst b/doc/api/v1/trainer_config_helpers/optimizers.rst deleted file mode 100644 index d2f4958c92b..00000000000 --- a/doc/api/v1/trainer_config_helpers/optimizers.rst +++ /dev/null @@ -1,61 +0,0 @@ -.. _api_trainer_config_helpers_optimizers: - -========== -Optimizers -========== - -BaseSGDOptimizer -================ -.. automodule:: paddle.trainer_config_helpers.optimizers - :members: BaseSGDOptimizer - :noindex: - -MomentumOptimizer -================= -.. automodule:: paddle.trainer_config_helpers.optimizers - :members: MomentumOptimizer - :noindex: - -AdamOptimizer -============= -.. automodule:: paddle.trainer_config_helpers.optimizers - :members: AdamOptimizer - :noindex: - -AdamaxOptimizer -================ -.. automodule:: paddle.trainer_config_helpers.optimizers - :members: AdamaxOptimizer - :noindex: - -AdaGradOptimizer -================ -.. automodule:: paddle.trainer_config_helpers.optimizers - :members: AdaGradOptimizer - :noindex: - -DecayedAdaGradOptimizer -======================= -.. automodule:: paddle.trainer_config_helpers.optimizers - :members: DecayedAdaGradOptimizer - :noindex: - -AdaDeltaOptimizer -================= -.. automodule:: paddle.trainer_config_helpers.optimizers - :members: AdaDeltaOptimizer - :noindex: - -RMSPropOptimizer -================ -.. automodule:: paddle.trainer_config_helpers.optimizers - :members: RMSPropOptimizer - :noindex: - -.. _api_trainer_config_helpers_optimizers_settings: - -settings -======== -.. automodule:: paddle.trainer_config_helpers.optimizers - :members: settings - :noindex: diff --git a/doc/api/v1/trainer_config_helpers/poolings.rst b/doc/api/v1/trainer_config_helpers/poolings.rst deleted file mode 100644 index 66566809d26..00000000000 --- a/doc/api/v1/trainer_config_helpers/poolings.rst +++ /dev/null @@ -1,33 +0,0 @@ -======== -Poolings -======== - -BasePoolingType -=============== -.. automodule:: paddle.trainer_config_helpers.poolings - :members: BasePoolingType - :noindex: - -AvgPooling -========== -.. automodule:: paddle.trainer_config_helpers.poolings - :members: AvgPooling - :noindex: - -MaxPooling -========== -.. automodule:: paddle.trainer_config_helpers.poolings - :members: MaxPooling - :noindex: - -SumPooling -========== -.. automodule:: paddle.trainer_config_helpers.poolings - :members: SumPooling - :noindex: - -SquareRootNPooling -================== -.. automodule:: paddle.trainer_config_helpers.poolings - :members: SquareRootNPooling - :noindex: diff --git a/doc/api/v2/config/evaluators.rst b/doc/api/v2/config/evaluators.rst new file mode 100644 index 00000000000..39db51fa4ab --- /dev/null +++ b/doc/api/v2/config/evaluators.rst @@ -0,0 +1,101 @@ +.. _api_v2: + +========== +Evaluators +========== + +Classification +============== + +classification_error +-------------------- +.. automodule:: paddle.v2.evaluator + :members: classification_error + :noindex: + +auc +--- +.. automodule:: paddle.v2.evaluator + :members: auc + :noindex: + +ctc_error +--------- +.. automodule:: paddle.v2.evaluator + :members: ctc_error + :noindex: + +chunk +----- +.. automodule:: paddle.v2.evaluator + :members: chunk + :noindex: + +precision_recall +---------------- +.. automodule:: paddle.v2.evaluator + :members: precision_recall + :noindex: + +Rank +==== + +pnpair +------ +.. automodule:: paddle.v2.evaluator + :members: pnpair + :noindex: + +Utils +===== + +sum +--- +.. automodule:: paddle.v2.evaluator + :members: sum + :noindex: + +column_sum +---------- +.. automodule:: paddle.v2.evaluator + :members: column_sum + :noindex: + +Print +===== + +classification_error_printer +---------------------------- +.. automodule:: paddle.v2.evaluator + :members: classification_error_printer + :noindex: + +gradient_printer +---------------- +.. automodule:: paddle.v2.evaluator + :members: gradient_printer + :noindex: + +maxid_printer +------------- +.. automodule:: paddle.v2.evaluator + :members: maxid_printer + :noindex: + +maxframe_printer +---------------- +.. automodule:: paddle.v2.evaluator + :members: maxframe_printer + :noindex: + +seqtext_printer +--------------- +.. automodule:: paddle.v2.evaluator + :members: seqtext_printer + :noindex: + +value_printer +------------- +.. automodule:: paddle.v2.evaluator + :members: value_printer + :noindex: diff --git a/doc/api/v2/config/networks.rst b/doc/api/v2/config/networks.rst index 6f209bc95be..b2a617fff13 100644 --- a/doc/api/v2/config/networks.rst +++ b/doc/api/v2/config/networks.rst @@ -44,6 +44,12 @@ simple_img_conv_pool :members: simple_img_conv_pool :noindex: +small_vgg +--------- +.. automodule:: paddle.v2.networks + :members: small_vgg + :noindex: + vgg_16_network --------------- .. automodule:: paddle.v2.networks @@ -101,6 +107,18 @@ simple_gru :members: simple_gru :noindex: +simple_gru2 +``````````` +.. automodule:: paddle.v2.networks + :members: simple_gru2 + :noindex: + +bidirectional_gru +`````````````````` +.. automodule:: paddle.v2.networks + :members: bidirectional_gru + :noindex: + simple_attention ---------------- .. automodule:: paddle.v2.networks diff --git a/doc/api/v2/model_configs.rst b/doc/api/v2/model_configs.rst index a5fae7e29e5..992b559cbd8 100644 --- a/doc/api/v2/model_configs.rst +++ b/doc/api/v2/model_configs.rst @@ -6,6 +6,7 @@ Model Configuration config/activation.rst config/layer.rst + config/evaluators.rst config/optimizer.rst config/pooling.rst config/networks.rst diff --git a/python/paddle/v2/inference.py b/python/paddle/v2/inference.py index c178336303f..1fea7917e15 100644 --- a/python/paddle/v2/inference.py +++ b/python/paddle/v2/inference.py @@ -79,8 +79,9 @@ def infer(output_layer, parameters, input, feeding=None, field='value'): .. code-block:: python - result = paddle.infer(prediction, parameters, input=SomeData, - batch_size=32) + result = paddle.infer(outptut_layer=prediction, + parameters=parameters, + input=SomeData) print result :param output_layer: output of the neural network that would be inferred -- GitLab From e61d97204c395d61cbce12fdbbfc23a5a39d7a82 Mon Sep 17 00:00:00 2001 From: gongweibao Date: Thu, 4 May 2017 20:53:29 +0800 Subject: [PATCH 0177/3256] add cmds --- doc/design/file_mananger/README.md | 58 +++++++++--------- .../file_mananger/src/filemanager.graffle | Bin 3656 -> 3605 bytes doc/design/file_mananger/src/filemanager.png | Bin 50663 -> 49686 bytes 3 files changed, 28 insertions(+), 30 deletions(-) diff --git a/doc/design/file_mananger/README.md b/doc/design/file_mananger/README.md index d7aa808d6c3..322cd10473e 100644 --- a/doc/design/file_mananger/README.md +++ b/doc/design/file_mananger/README.md @@ -1,41 +1,35 @@ # Desgin doc: FileManager ## Objetive 在本文档中,我们设计说明了用户上传、下载、管理自己在PaddlePaddle Cloud上的文件所涉及到的模块和流程 + ## Module ### Client -Client是用户操作的命令行程序,支持的命令如下 - -- ls - -```bash -ls -``` - -- cp +Client提供用户管理本地或者远程文件的命令行程序。 -```bash -cp -``` +- 路径参数: +当用户输入一个命令的时候,最起码需要指定一个路径参数。这里有两种路径参数:LocalPath 或者 PFSPath。 -- sync +LocalPath:代表本地的一个路径 +PFSPath:代表PaddlePaddle Cloud上的一个路径。它需要满足类似这样的格式:`pfs://dir1/dir2`。路径必须要以`pds://`开始。 -```bash -sync -``` +- 路径参数的顺序 +如果命令都有一个或者多个路径参数,那么一般第一个路径参数代表source,第二个路径参数代表destination。 -- mv - -```bash -mv -``` +- 支持的操作命令 + - [rm](cmd_rm.md) + - [mv](cmd_mv.md) + - [cp](cmd_cp.md) + - [ls](cmd_ls.md) + - [mkdir](cmd_mkdir.md) + - [sync](cmd_sync.md) ### Ingress - 在kubernets中运行 -- 做Http转发 -- 注意配置session保持 +- 做Http转发、负载均衡 + - 注意配置session保持,以便来自一个用户的访问可以定向到一个固定的机器上,减少冲突写的机会。 ### FileServer @@ -61,7 +55,7 @@ GET /file/chunk: Get a chunk info POST /file/chunk: Update a chunk ``` 为什么有chunk的抽象: -用户文件可能是比较大的,上传到Cloud或者下载到本地的时间可能比较长,而且在传输的过程中也可能出现网络不稳定的情况。为了应对以上的问题,我们提出了chunk的概念。chunk由所在的文件偏移、数据、数据长度及校验值组成。数据内容的上传和下载都是都过chunk的操作来实现的。由于chunk比较小(默认256K),完成一个传输动作的transaction的时间也比较短,不容易出错。 +用户文件可能是比较大的,上传到Cloud或者下载到本地的时间可能比较长,而且在传输的过程中也可能出现网络不稳定的情况。为了应对以上的问题,我们提出了chunk的概念,一个chunk由所在的文件偏移、数据、数据长度及校验值组成。文件数据内容的上传和下载都是都过chunk的操作来实现的。由于chunk比较小(默认256K),完成一个传输动作的transaction的时间也比较短,不容易出错。 ``` type Chunk struct { @@ -101,16 +95,20 @@ DELETE /dir: Delete a directory FileServer从Client crt提取Client的身份(username),限制其可以操作的volume。 我们选择这种。 ### 关于cp -cp的关键在于需要Client端对比src和dst的文件chunks的checkSum是否保持一致,不一致的由Client Get或者Post完成。藉由上述的方法完成断点的数据传输。 +cp的关键在于需要Client端对比src和dst的文件chunks的checkSum是否保持一致,不一致的由Client Get或者Post完成。藉由上述的方法完成断点的数据传输。 upload文件时,由于一个文件可以是多个FileServer可写的,存在冲突的机会,需要Client端在Post最后一个chunk的时候检查dest文件的MD5值是否和src的一致。 + +- 优化的方法: -优化的方法: + - dst文件不存在时,可以没有Get的过程,只有Post。 + - 文件的chunks信息可以做cache,不用每次启动传输都去读和计算。这个由于比较复杂,第一期暂时不做。 -- dst文件不存在时,可以没有Get的过程,只有Post。 -- 文件的chunks信息可以做cache,不用每次启动传输都去读和计算。这个由于比较复杂,第一期暂时不做。 +- 小的技巧: -tricky: + - 可以用[Fallocate](https://golang.org/pkg/syscall/#Fallocate)生成sparse文件,让dst和src文件保持相同的大小。不同位置的chunk可以同时写入。 -- 可以用[Fallocate](https://golang.org/pkg/syscall/#Fallocate)让dst和src文件保持相同的大小。这样,chunk就可以写固定的偏移上。 +### 关于框架 +准备拿出一点时间测试一下用[swagger-api](https://github.com/swagger-api/swagger-codegen)生成Client和FileServer的框架部分。如果框架生成好用,我们的精力就可以更多的放到逻辑本身上。 ## 参考文档 - [Do you see tls?](https://github.com/k8sp/tls/blob/master/README.md) +- [s3](http://docs.aws.amazon.com/cli/latest/reference/s3/) diff --git a/doc/design/file_mananger/src/filemanager.graffle b/doc/design/file_mananger/src/filemanager.graffle index 68fc8224d33cbc17dfa3634284901fdc5e2cb775..d8ec4299f42eff52e7153b542e101cc28e2f9801 100644 GIT binary patch literal 3605 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 bz35aLl5+4d-^QPpdh_D{MYq+?^=beB1dRsQ literal 3656 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 aZY51p#(d0=(dTjAzWP4}9#6rwX8-^_ek|Jn diff --git a/doc/design/file_mananger/src/filemanager.png b/doc/design/file_mananger/src/filemanager.png index 661d547e414f0119728e1bb5262d0cbb446b395a..d8b14404bab846c42937b28296a63978c5cfdbb1 100644 GIT binary patch literal 49686 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 aA6)5_N0knSm8Ce~@2rM_da0`2)&Bz_Fijx< literal 50663 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 -- GitLab From 5cdef642fa2806b403b00d659fcdd2aa8d9e5ef0 Mon Sep 17 00:00:00 2001 From: gongweibao Date: Thu, 4 May 2017 21:04:12 +0800 Subject: [PATCH 0178/3256] modify stly --- doc/design/file_mananger/README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/doc/design/file_mananger/README.md b/doc/design/file_mananger/README.md index 322cd10473e..4a9d0214d94 100644 --- a/doc/design/file_mananger/README.md +++ b/doc/design/file_mananger/README.md @@ -8,13 +8,13 @@ ### Client Client提供用户管理本地或者远程文件的命令行程序。 -- 路径参数: -当用户输入一个命令的时候,最起码需要指定一个路径参数。这里有两种路径参数:LocalPath 或者 PFSPath。 +- 路径参数: +当用户输入一个命令的时候,一般需要指定路径参数。这里有两种路径参数:LocalPath 或者 PFSPath。 -LocalPath:代表本地的一个路径 -PFSPath:代表PaddlePaddle Cloud上的一个路径。它需要满足类似这样的格式:`pfs://dir1/dir2`。路径必须要以`pds://`开始。 + LocalPath:代表本地的一个路径 + PFSPath:代表PaddlePaddle Cloud上的一个路径。它需要满足类似这样的格式:`pfs://dir1/dir2`。路径必须要以`pds://`开始。 -- 路径参数的顺序 +- 路径参数的顺序 如果命令都有一个或者多个路径参数,那么一般第一个路径参数代表source,第二个路径参数代表destination。 - 支持的操作命令 @@ -94,8 +94,8 @@ DELETE /dir: Delete a directory - 第二种:CephFS crt只有一个,也就是admin crt,拥有所有volume的读写权限。 FileServer从Client crt提取Client的身份(username),限制其可以操作的volume。 我们选择这种。 -### 关于cp -cp的关键在于需要Client端对比src和dst的文件chunks的checkSum是否保持一致,不一致的由Client Get或者Post完成。藉由上述的方法完成断点的数据传输。 upload文件时,由于一个文件可以是多个FileServer可写的,存在冲突的机会,需要Client端在Post最后一个chunk的时候检查dest文件的MD5值是否和src的一致。 +### 关于文件传输 +文件传输的的关键在于需要Client端对比src和dst的文件chunks的checkSum是否保持一致,不一致的由Client Get或者Post chunk完成。藉由上述的方法完成断点的数据传输。 upload文件时,由于一个文件可以是多个FileServer可写的,存在冲突的机会,需要Client端在Post最后一个chunk的时候检查dest文件的MD5值是否和本地文件一致。 - 优化的方法: -- GitLab From c1cb483a6c5cdd10956a2659dc76214004e75bb1 Mon Sep 17 00:00:00 2001 From: "yi.wu" Date: Thu, 4 May 2017 22:34:35 +0800 Subject: [PATCH 0179/3256] paddle.init default use env --- python/paddle/v2/__init__.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/python/paddle/v2/__init__.py b/python/paddle/v2/__init__.py index 7c8f6ea62fc..7a9d3a73f4f 100644 --- a/python/paddle/v2/__init__.py +++ b/python/paddle/v2/__init__.py @@ -11,6 +11,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. +import os import optimizer import layer import activation @@ -42,6 +43,12 @@ __all__ = [ def init(**kwargs): args = [] + # NOTE: append arguments if they are in ENV + for ek, ev in os.environ.iteritems(): + if ek.startswith("PADDLE_"): + args.append('--%s=%s' % (ek.replace("PADDLE_", "").lower(), str(ev))) + + # NOTE: overwrite arguments from ENV if it is in kwargs for key in kwargs.keys(): args.append('--%s=%s' % (key, str(kwargs[key]))) -- GitLab From 6f6882802fd63ef1e368c01ef2a9417049e64272 Mon Sep 17 00:00:00 2001 From: "yi.wu" Date: Fri, 5 May 2017 07:59:59 +0800 Subject: [PATCH 0180/3256] delete paddle/dist before build --- paddle/scripts/docker/build.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/paddle/scripts/docker/build.sh b/paddle/scripts/docker/build.sh index 4172063d923..be465ee2615 100644 --- a/paddle/scripts/docker/build.sh +++ b/paddle/scripts/docker/build.sh @@ -21,6 +21,8 @@ cd /paddle/build # build script will not fail if *.deb does not exist rm *.deb 2>/dev/null || true +# delete previous built whl packages +rm -rf /paddle/paddle/dist 2>/dev/null || true cmake .. \ -DCMAKE_BUILD_TYPE=Release \ -- GitLab From f9e36971f3346cd3274bb9d67c4d9b39ea5ee732 Mon Sep 17 00:00:00 2001 From: liaogang Date: Fri, 5 May 2017 10:29:14 +0800 Subject: [PATCH 0181/3256] Add Lapack warning --- cmake/external/openblas.cmake | 1 - paddle/math/tests/test_matrixCompare.cpp | 8 ++++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/cmake/external/openblas.cmake b/cmake/external/openblas.cmake index 86cb473c385..46398b22c27 100644 --- a/cmake/external/openblas.cmake +++ b/cmake/external/openblas.cmake @@ -15,7 +15,6 @@ INCLUDE(cblas) IF(NOT ${CBLAS_FOUND}) - SET(LAPACK_FOUND OFF) INCLUDE(ExternalProject) SET(CBLAS_SOURCES_DIR ${THIRD_PARTY_PATH}/openblas) diff --git a/paddle/math/tests/test_matrixCompare.cpp b/paddle/math/tests/test_matrixCompare.cpp index 782a9613d87..05adec63701 100644 --- a/paddle/math/tests/test_matrixCompare.cpp +++ b/paddle/math/tests/test_matrixCompare.cpp @@ -236,8 +236,16 @@ TEST(Matrix, unary) { testMatrixTranspose(height, width); testMatrixRotate(height, width); } + #ifdef LAPACK_FOUND // inverse matrix testMatrixInverse(height); + #else + LOG(WARNING) << "Cannot run Matrix Inverse Unit Test.\n" + << "Failed to find lapack library in current system.\n" + << "To address this issue, Please adopt one of the following approaches: \n" + << "1. Simply issue `sudo apt-get install liblapacke-dev` to avoid re-build source code. \n" + << "2. Install MKL/Openblas/ATLAS and re-build PaddlePaddle source code." + #endif } } -- GitLab From 81195c877e809fda705f7f9d854bfcd196b184b3 Mon Sep 17 00:00:00 2001 From: liaogang Date: Fri, 5 May 2017 10:30:38 +0800 Subject: [PATCH 0182/3256] remove lapack header in openblas --- cmake/external/openblas.cmake | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/cmake/external/openblas.cmake b/cmake/external/openblas.cmake index 46398b22c27..5b042fec012 100644 --- a/cmake/external/openblas.cmake +++ b/cmake/external/openblas.cmake @@ -40,16 +40,6 @@ IF(NOT ${CBLAS_FOUND}) UPDATE_COMMAND "" CONFIGURE_COMMAND "" ) - - ExternalProject_Add_Step( - openblas lapacke_install - COMMAND ${CMAKE_COMMAND} -E copy "${CBLAS_SOURCES_DIR}/src/openblas/lapack-netlib/LAPACKE/include/lapacke_mangling_with_flags.h" "${CBLAS_INSTALL_DIR}/include/lapacke_mangling.h" - COMMAND ${CMAKE_COMMAND} -E copy "${CBLAS_SOURCES_DIR}/src/openblas/lapack-netlib/LAPACKE/include/lapacke.h" "${CBLAS_INSTALL_DIR}/include/lapacke.h" - COMMAND ${CMAKE_COMMAND} -E copy "${CBLAS_SOURCES_DIR}/src/openblas/lapack-netlib/LAPACKE/include/lapacke_config.h" "${CBLAS_INSTALL_DIR}/include/lapacke_config.h" - COMMAND ${CMAKE_COMMAND} -E copy "${CBLAS_SOURCES_DIR}/src/openblas/lapack-netlib/LAPACKE/include/lapacke_utils.h" "${CBLAS_INSTALL_DIR}/include/lapacke_utils.h" - DEPENDEES install - ) - LIST(APPEND external_project_dependencies openblas) ENDIF(NOT ${CBLAS_FOUND}) -- GitLab From ee454f938073eb675ed9d20891df33bdb863c3d4 Mon Sep 17 00:00:00 2001 From: gongweibao Date: Fri, 5 May 2017 12:00:19 +0800 Subject: [PATCH 0183/3256] add commands --- doc/design/file_mananger/README.md | 55 +++++---------------------- doc/design/file_mananger/RESTAPI.md | 23 +++++++++++ doc/design/file_mananger/pfs/cp.md | 47 +++++++++++++++++++++++ doc/design/file_mananger/pfs/ls.md | 49 ++++++++++++++++++++++++ doc/design/file_mananger/pfs/mkdir.md | 33 ++++++++++++++++ doc/design/file_mananger/pfs/mv.md | 38 ++++++++++++++++++ doc/design/file_mananger/pfs/pfs.md | 36 ++++++++++++++++++ doc/design/file_mananger/pfs/rm.md | 50 ++++++++++++++++++++++++ doc/design/file_mananger/pfs/sync.md | 53 ++++++++++++++++++++++++++ 9 files changed, 339 insertions(+), 45 deletions(-) create mode 100644 doc/design/file_mananger/RESTAPI.md create mode 100644 doc/design/file_mananger/pfs/cp.md create mode 100644 doc/design/file_mananger/pfs/ls.md create mode 100644 doc/design/file_mananger/pfs/mkdir.md create mode 100644 doc/design/file_mananger/pfs/mv.md create mode 100644 doc/design/file_mananger/pfs/pfs.md create mode 100644 doc/design/file_mananger/pfs/rm.md create mode 100644 doc/design/file_mananger/pfs/sync.md diff --git a/doc/design/file_mananger/README.md b/doc/design/file_mananger/README.md index 4a9d0214d94..6078696e6e2 100644 --- a/doc/design/file_mananger/README.md +++ b/doc/design/file_mananger/README.md @@ -5,25 +5,11 @@ ## Module -### Client -Client提供用户管理本地或者远程文件的命令行程序。 +### PFS Client +- 提供用户管理Cloud文件的命令 +- 用Golang写,可以跨平台执行 -- 路径参数: -当用户输入一个命令的时候,一般需要指定路径参数。这里有两种路径参数:LocalPath 或者 PFSPath。 - - LocalPath:代表本地的一个路径 - PFSPath:代表PaddlePaddle Cloud上的一个路径。它需要满足类似这样的格式:`pfs://dir1/dir2`。路径必须要以`pds://`开始。 - -- 路径参数的顺序 -如果命令都有一个或者多个路径参数,那么一般第一个路径参数代表source,第二个路径参数代表destination。 - -- 支持的操作命令 - - [rm](cmd_rm.md) - - [mv](cmd_mv.md) - - [cp](cmd_cp.md) - - [ls](cmd_ls.md) - - [mkdir](cmd_mkdir.md) - - [sync](cmd_sync.md) +命令的详细内容看[这里](./pfs/pfs.md) ### Ingress @@ -37,26 +23,14 @@ Client提供用户管理本地或者远程文件的命令行程序。 - gorpc写的HttpServer - 响应外部的REST API的请求 - 在kubernets中运行 +- [RESTAPI](./RESTAPI.md)接口 -REST API说明: - -- file - -``` -GET /file: Get attribue of files -POST /file: Touch a file -DELETE /file: Delete a File -``` - -- chunk - -``` -GET /file/chunk: Get a chunk info -POST /file/chunk: Update a chunk -``` -为什么有chunk的抽象: +## 流程 +### 为什么有chunk的抽象: 用户文件可能是比较大的,上传到Cloud或者下载到本地的时间可能比较长,而且在传输的过程中也可能出现网络不稳定的情况。为了应对以上的问题,我们提出了chunk的概念,一个chunk由所在的文件偏移、数据、数据长度及校验值组成。文件数据内容的上传和下载都是都过chunk的操作来实现的。由于chunk比较小(默认256K),完成一个传输动作的transaction的时间也比较短,不容易出错。 +一个典型的chunk如下所示: + ``` type Chunk struct { filePos int64 @@ -66,16 +40,6 @@ type Chunk struct { } ``` -- dir - -``` -GET /dir: List all files in a directory -POST /dir: Touch a directory -DELETE /dir: Delete a directory -``` - - -## 流程 ### 关于文件权限 - 每一个用户在Cloud注册后可以申请分配用户空间,系统默认会在CephFS上分配一个固定大小(比如初始10G)的、有所有权限的volume,对用户而言就是自己的`home`目录。用户彼此之间的数据是隔离的、无法访问的。用户的空间大小第一期也不允许扩大。 - 公共数据集合放到一个单独的volume下,对所有外部用户只读。由于其被读取的可能比较频繁,需要提高其备份数,防止成为热点文件。 @@ -112,3 +76,4 @@ DELETE /dir: Delete a directory ## 参考文档 - [Do you see tls?](https://github.com/k8sp/tls/blob/master/README.md) - [s3](http://docs.aws.amazon.com/cli/latest/reference/s3/) +- linux man document diff --git a/doc/design/file_mananger/RESTAPI.md b/doc/design/file_mananger/RESTAPI.md new file mode 100644 index 00000000000..4286195970b --- /dev/null +++ b/doc/design/file_mananger/RESTAPI.md @@ -0,0 +1,23 @@ +# REST API +- file + +``` +GET /file: Get attribue of files +POST /file: Touch a file +DELETE /file: Delete a File +``` + +- chunk + +``` +GET /file/chunk: Get a chunk info +POST /file/chunk: Update a chunk +``` + +- dir + +``` +GET /dir: List all files in a directory +POST /dir: Touch a directory +DELETE /dir: Delete a directory +``` diff --git a/doc/design/file_mananger/pfs/cp.md b/doc/design/file_mananger/pfs/cp.md new file mode 100644 index 00000000000..fcc57258a17 --- /dev/null +++ b/doc/design/file_mananger/pfs/cp.md @@ -0,0 +1,47 @@ +# Name +pfs_cp - copy files and directories + +# Synopsis +` pfs_cp + or or ` + +# Description + +``` + --only-show-errors (boolean) + Only errors and warnings are displayed. All other output is suppressed. + + --page-size (integer) + The number of results to return in each response to a list operation. The default value is 1000 (the maximum allowed). Using a lower value may help if an operation times out. + + --preserve--links + Reserve links when copy files + + -R, -r, --recursive + Copy directories recursively +``` + +# Examples +- The following command cp a single file to pfs + +``` +pfs_cp ./text1.txt pfs://mydir/text1.txt +``` + +Output + +``` +upload ./text1.txt to pfs://mydir/text1.txt +``` + +- The following command cp pfs file to a local file + +``` +pfs_cp pfs://mydir/text1.txt ./text1.txt +``` + +Output + +``` +download pfs://mydir/text1.txt to ./text1.txt +``` diff --git a/doc/design/file_mananger/pfs/ls.md b/doc/design/file_mananger/pfs/ls.md new file mode 100644 index 00000000000..41cf6fdf77f --- /dev/null +++ b/doc/design/file_mananger/pfs/ls.md @@ -0,0 +1,49 @@ +# Name +pfs_ls - list directory contents o + +# Synopsis +` pfs_ls [OPTION]... ` + +# Description + +``` + -h, --help + Display this help and exit + + --version + Output version information and exit + + --only-show-errors (boolean) + Only errors and warnings are displayed. All other output is suppressed. + + --page-size (integer) + The number of results to return in each response to a list operation. The default value is 1000 (the maximum allowed). Using a lower value may help if an operation times out. + + -R, -r, --recursive + Copy directories recursively +``` + +# Examples +- The following command cp a single file to pfs + +``` +pfs_cp ./text1.txt pfs://mydir/text1.txt +``` + +Output + +``` +upload ./text1.txt to pfs://mydir/text1.txt +``` + +- The following command cp pfs file to a local file + +``` +pfs_cp pfs://mydir/text1.txt ./text1.txt +``` + +Output + +``` +download pfs://mydir/text1.txt to ./text1.txt +``` diff --git a/doc/design/file_mananger/pfs/mkdir.md b/doc/design/file_mananger/pfs/mkdir.md new file mode 100644 index 00000000000..e3b0d0cae78 --- /dev/null +++ b/doc/design/file_mananger/pfs/mkdir.md @@ -0,0 +1,33 @@ +# Name +pdf_mkdir - mkdir directory(ies) + +# Synopsis +`pfs_mkdir [OPTION]... PFS_DIRECTORY...` + +# Description +Create the pfs directory(ies), if they do not already exist. + +``` + -h, --help + display this help and exit + + --version + output version information and exit + + --only-show-errors (boolean) + Only errors and warnings are displayed. All other output is suppressed. + + --page-size (integer) + The number of results to return in each response to a list operation. The default value is 1000 (the maximum allowed). Using a lower value may help if an operation times out. +``` + +# Examples +``` +pfs_mkdir pfs://mydir1 +``` + +Output + +``` +make directory pfs://mydir1 +``` diff --git a/doc/design/file_mananger/pfs/mv.md b/doc/design/file_mananger/pfs/mv.md new file mode 100644 index 00000000000..3f4c1ac0761 --- /dev/null +++ b/doc/design/file_mananger/pfs/mv.md @@ -0,0 +1,38 @@ +# Name +mv - move (rename) files or directories + + +# Synopsis +If destination already exist, please rm it first. +``` +mv [OPTION]... + or or +``` + +# Description +``` + -h, --help + display this help and exit + + -v, --version + output version information and exit + + --only-show-errors (boolean) + Only errors and warnings are displayed. All other output is suppressed. + + --page-size (integer) + The number of results to return in each response to a list operation. The default value is 1000 (the maximum allowed). Using a lower value may help if an operation times out. +``` + +# Examples +- The following command move a single file to pfs + +``` +mv ./text1.txt pfs://mydirectory/test1.txt +``` + +Output + +``` +move text1.txt pfs://mydirectory/test1.txt +``` diff --git a/doc/design/file_mananger/pfs/pfs.md b/doc/design/file_mananger/pfs/pfs.md new file mode 100644 index 00000000000..a17de7d8140 --- /dev/null +++ b/doc/design/file_mananger/pfs/pfs.md @@ -0,0 +1,36 @@ +# PFS Client + +## Description +The `pfs` command is a Command Line Interface to manage your files on Paddle Cloud + +## Synopsis +``` +paddle [options] pfs [parameters] +``` + +## Options +``` +--profile (string) + Use a specific profile from your credential file. +--help pfs + Display more information about pfs +--version + Output version information and exit +--debug + Show detailed debugging log +``` + +## Path Arguments +When using a commnd, we need to specify path arguments. There are two path argument type: `localpath` and `pfspath`. +A pfspath begin with `pfs://`, eg: `pfs://mydir/text1.txt`. + +## Path Arguments‘s order +Commonly, there maybe two path arguments. The first is source, and the second is destination. + +## Subcommonds +- [rm](rm.md) +- [mv](mv.md) +- [cp](cp.md) +- [ls](ls.md) +- [mkdir](mkdir.md) +- [sync](sync.md) diff --git a/doc/design/file_mananger/pfs/rm.md b/doc/design/file_mananger/pfs/rm.md new file mode 100644 index 00000000000..6056c401f95 --- /dev/null +++ b/doc/design/file_mananger/pfs/rm.md @@ -0,0 +1,50 @@ +# Name +rm - remove files or directories + +# Synopsis +`rm [OPTION]... PFS_DIRECTORY...` + +# Description +Create the directory, if it does not already exits + +``` + -r, -R, --recursive + remove directories and their contents recursively + + -v, --version + output version information and exit + + --only-show-errors (boolean) + Only errors and warnings are displayed. All other output is suppressed. + + --page-size (integer) + The number of results to return in each response to a list operation. The default value is 1000 (the maximum allowed). Using a lower value may help if an operation times out. +``` + +# Examples +- The following command delete a single file + +``` +pfs_rm pfs://mydirectory/test1.txt +``` + +Output + +``` +delete pfs://mydirectory/test1.txt +``` + + +- The following command delete a directory recursively. + +``` +pfs_rm -r pfs://mydirectory +``` + +Output + +``` +delete pfs://mydirectory/1.txt +delete pfs://mydirectory/2.txt +... +``` diff --git a/doc/design/file_mananger/pfs/sync.md b/doc/design/file_mananger/pfs/sync.md new file mode 100644 index 00000000000..99248082743 --- /dev/null +++ b/doc/design/file_mananger/pfs/sync.md @@ -0,0 +1,53 @@ +# Name +pfs_cp - copy files and directories + +# Synopsis +` pfs_cp + or or ` + +# Description + +``` + -h, --help + Display this help and exit + + --version + Output version information and exit + + --only-show-errors (boolean) + Only errors and warnings are displayed. All other output is suppressed. + + --page-size (integer) + The number of results to return in each response to a list operation. The default value is 1000 (the maximum allowed). Using a lower value may help if an operation times out. + + --preserve--links + Reserve links when copy files + + -R, -r, --recursive + Copy directories recursively +``` + +# Examples +- The following command cp a single file to pfs + +``` +pfs_cp ./text1.txt pfs://mydir/text1.txt +``` + +Output + +``` +upload ./text1.txt to pfs://mydir/text1.txt +``` + +- The following command cp pfs file to a local file + +``` +pfs_cp pfs://mydir/text1.txt ./text1.txt +``` + +Output + +``` +download pfs://mydir/text1.txt to ./text1.txt +``` -- GitLab From 70d15e84bebfc9bbb7d1a1aa37e32ed9806033ac Mon Sep 17 00:00:00 2001 From: yangyaming Date: Fri, 5 May 2017 12:29:10 +0800 Subject: [PATCH 0184/3256] Add dataset ptb --- python/paddle/v2/dataset/imikolov.py | 4 +- python/paddle/v2/dataset/ptb.py | 169 +++++++++++++++++++++ python/paddle/v2/dataset/tests/ptb_test.py | 53 +++++++ 3 files changed, 224 insertions(+), 2 deletions(-) create mode 100644 python/paddle/v2/dataset/ptb.py create mode 100644 python/paddle/v2/dataset/tests/ptb_test.py diff --git a/python/paddle/v2/dataset/imikolov.py b/python/paddle/v2/dataset/imikolov.py index bf88fe15570..97b9f7d9158 100644 --- a/python/paddle/v2/dataset/imikolov.py +++ b/python/paddle/v2/dataset/imikolov.py @@ -41,7 +41,7 @@ def word_count(f, word_freq=None): return word_freq -def build_dict(typo_freq=50): +def build_dict(min_word_freq=50): """ Build a word dictionary from the corpus, Keys of the dictionary are words, and values are zero-based IDs of these words. @@ -59,7 +59,7 @@ def build_dict(typo_freq=50): # remove for now, since we will set it as last index del word_freq[''] - word_freq = filter(lambda x: x[1] > typo_freq, word_freq.items()) + word_freq = filter(lambda x: x[1] > min_word_freq, word_freq.items()) word_freq_sorted = sorted(word_freq, key=lambda x: (-x[1], x[0])) words, _ = list(zip(*word_freq_sorted)) diff --git a/python/paddle/v2/dataset/ptb.py b/python/paddle/v2/dataset/ptb.py new file mode 100644 index 00000000000..ea68daf9c6b --- /dev/null +++ b/python/paddle/v2/dataset/ptb.py @@ -0,0 +1,169 @@ +# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +langauge model's simple dataset. + +This module will download dataset from +http://www.fit.vutbr.cz/~imikolov/rnnlm/ and parse training set and test set +into paddle reader creators. +""" +import paddle.v2.dataset.common +import collections +import tarfile + +__all__ = ['train', 'test', 'build_dict'] + +URL = 'http://www.fit.vutbr.cz/~imikolov/rnnlm/simple-examples.tgz' +MD5 = '30177ea32e27c525793142b6bf2c8e2d' + + +def word_count(f, word_freq=None): + if word_freq is None: + word_freq = collections.defaultdict(int) + + for l in f: + for w in l.strip().split(): + word_freq[w] += 1 + word_freq[''] += 1 + word_freq[''] += 1 + + return word_freq + + +def build_dict(min_word_freq=50): + """ + Build a word dictionary from the corpus, Keys of the dictionary are words, + and values are zero-based IDs of these words. + """ + train_filename = './simple-examples/data/ptb.train.txt' + test_filename = './simple-examples/data/ptb.valid.txt' + with tarfile.open( + paddle.v2.dataset.common.download( + paddle.v2.dataset.imikolov.URL, 'imikolov', + paddle.v2.dataset.imikolov.MD5)) as tf: + trainf = tf.extractfile(train_filename) + testf = tf.extractfile(test_filename) + word_freq = word_count(testf, word_count(trainf)) + if '' in word_freq: + # remove for now, since we will set it as last index + del word_freq[''] + + word_freq = filter(lambda x: x[1] > min_word_freq, word_freq.items()) + + word_freq_sorted = sorted(word_freq, key=lambda x: (-x[1], x[0])) + words, _ = list(zip(*word_freq_sorted)) + word_idx = dict(zip(words, xrange(len(words)))) + word_idx[''] = len(words) + + return word_idx + + +def reader_creator(filename, reader_type, word_idx, n=-1): + def reader(): + with tarfile.open( + paddle.v2.dataset.common.download( + paddle.v2.dataset.imikolov.URL, 'imikolov', + paddle.v2.dataset.imikolov.MD5)) as tf: + f = tf.extractfile(filename) + + UNK = word_idx[''] + + for l in f: + if 'ngram' == reader_type: + assert n > -1, 'Invalid gram length' + l = [''] + l.strip().split() + [''] + if len(l) < n: continue + l = [word_idx.get(w, UNK) for w in l] + for i in range(n, len(l) + 1): + yield tuple(l[i - n:i]) + elif 'seq' == reader_type: + l = l.strip().split() + l = [word_idx.get(w, UNK) for w in l] + src_seq = [word_idx['']] + l + trg_seq = l + [word_idx['']] + yield src_seq, trg_seq + + return reader + + +def ngram_train(word_idx, n): + """ + ptb ngram type training set creator. + + It returns a reader creator, each sample in the reader is a word ID + tuple. + + :param word_idx: word dictionary + :type word_idx: dict + :param n: sliding window size + :type n: int + :return: Training reader creator + :rtype: callable + """ + return reader_creator('./simple-examples/data/ptb.train.txt', 'ngram', + word_idx, n) + + +def ngram_test(word_idx, n): + """ + ptb ngram test set creator. + + It returns a reader creator, each sample in the reader is a word ID + tuple. + + :param word_idx: word dictionary + :type word_idx: dict + :param n: sliding window size + :type n: int + :return: Test reader creator + :rtype: callable + """ + return reader_creator('./simple-examples/data/ptb.valid.txt', 'ngram', + word_idx, n) + + +def seq_train(word_idx): + """ + ptb sequence type training set creator. + + It returns a reader creator, each sample in the reader is a word ID + pair. + + :param word_idx: word dictionary + :type word_idx: dict + :return: Test reader creator + :rtype: callable + """ + return reader_creator('./simple-examples/data/ptb.train.txt', 'seq', + word_idx) + + +def seq_test(word_idx): + """ + ptb sequence type test set creator. + + It returns a reader creator, each sample in the reader is a word ID + pair. + + :param word_idx: word dictionary + :type word_idx: dict + :return: Test reader creator + :rtype: callable + """ + return reader_creator('./simple-examples/data/ptb.valid.txt', 'seq', + word_idx) + + +def fetch(): + paddle.v2.dataset.common.download(URL, "imikolov", MD5) diff --git a/python/paddle/v2/dataset/tests/ptb_test.py b/python/paddle/v2/dataset/tests/ptb_test.py new file mode 100644 index 00000000000..5e584a734d3 --- /dev/null +++ b/python/paddle/v2/dataset/tests/ptb_test.py @@ -0,0 +1,53 @@ +import paddle.v2.dataset.ptb +import unittest + +WORD_DICT = paddle.v2.dataset.ptb.build_dict() + + +class TestMikolov(unittest.TestCase): + def check_reader(self, reader, n): + for l in reader(): + self.assertEqual(len(l), n) + + def test_ngram_train(self): + n = 5 + self.check_reader(paddle.v2.dataset.ptb.ngram_train(WORD_DICT, n), n) + + def test_ngram_test(self): + n = 5 + self.check_reader(paddle.v2.dataset.ptb.ngram_test(WORD_DICT, n), n) + + def test_seq_train(self): + first_line = 'aer banknote berlitz calloway centrust cluett fromstein '\ + 'gitano guterman hydro-quebec ipo kia memotec mlx nahb punts '\ + 'rake regatta rubens sim snack-food ssangyong swapo wachter' + first_line = [ + WORD_DICT.get(ch, WORD_DICT['']) + for ch in first_line.split(' ') + ] + for l in paddle.v2.dataset.ptb.seq_train(WORD_DICT)(): + read_line = l[0][1:] + break + + self.assertEqual(first_line, read_line) + + def test_seq_test(self): + first_line = 'consumers may want to move their telephones a little '\ + 'closer to the tv set' + first_line = [ + WORD_DICT.get(ch, WORD_DICT['']) + for ch in first_line.split(' ') + ] + for l in paddle.v2.dataset.ptb.seq_test(WORD_DICT)(): + read_line = l[0][1:] + break + + self.assertEqual(first_line, read_line) + + def test_total(self): + _, idx = zip(*WORD_DICT.items()) + self.assertEqual(sorted(idx)[-1], len(WORD_DICT) - 1) + + +if __name__ == '__main__': + unittest.main() -- GitLab From 47b13a409ab6f030b3c193d09ee852662c89ed54 Mon Sep 17 00:00:00 2001 From: liaogang Date: Fri, 5 May 2017 13:30:19 +0800 Subject: [PATCH 0185/3256] clang format code --- paddle/math/tests/test_matrixCompare.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/paddle/math/tests/test_matrixCompare.cpp b/paddle/math/tests/test_matrixCompare.cpp index 05adec63701..2bdcab4d866 100644 --- a/paddle/math/tests/test_matrixCompare.cpp +++ b/paddle/math/tests/test_matrixCompare.cpp @@ -236,16 +236,19 @@ TEST(Matrix, unary) { testMatrixTranspose(height, width); testMatrixRotate(height, width); } - #ifdef LAPACK_FOUND +#ifdef LAPACK_FOUND // inverse matrix testMatrixInverse(height); - #else +#else LOG(WARNING) << "Cannot run Matrix Inverse Unit Test.\n" << "Failed to find lapack library in current system.\n" - << "To address this issue, Please adopt one of the following approaches: \n" - << "1. Simply issue `sudo apt-get install liblapacke-dev` to avoid re-build source code. \n" - << "2. Install MKL/Openblas/ATLAS and re-build PaddlePaddle source code." - #endif + << "To address this issue, Please adopt one of the following " + "approaches: \n" + << "1. Simply issue `sudo apt-get install liblapacke-dev` to " + "avoid re-build source code. \n" + << "2. Install MKL/Openblas/ATLAS and re-build PaddlePaddle " + "source code." +#endif } } -- GitLab From 177d35afa1edfb6906d49c4066adda7a7c94b1d2 Mon Sep 17 00:00:00 2001 From: gongweibao Date: Fri, 5 May 2017 15:07:40 +0800 Subject: [PATCH 0186/3256] modify commands --- doc/design/file_mananger/README.md | 4 +-- doc/design/file_mananger/RESTAPI.md | 2 +- doc/design/file_mananger/pfs/cp.md | 20 +++++--------- doc/design/file_mananger/pfs/ls.md | 35 ++++++++++-------------- doc/design/file_mananger/pfs/mkdir.md | 22 +++------------ doc/design/file_mananger/pfs/mv.md | 18 +++---------- doc/design/file_mananger/pfs/pfs.md | 18 ++++++++----- doc/design/file_mananger/pfs/rm.md | 21 ++++++--------- doc/design/file_mananger/pfs/sync.md | 39 ++++++++++----------------- 9 files changed, 66 insertions(+), 113 deletions(-) diff --git a/doc/design/file_mananger/README.md b/doc/design/file_mananger/README.md index 6078696e6e2..bca8f48b8e2 100644 --- a/doc/design/file_mananger/README.md +++ b/doc/design/file_mananger/README.md @@ -2,7 +2,7 @@ ## Objetive 在本文档中,我们设计说明了用户上传、下载、管理自己在PaddlePaddle Cloud上的文件所涉及到的模块和流程 - + ## Module ### PFS Client @@ -25,7 +25,7 @@ - 在kubernets中运行 - [RESTAPI](./RESTAPI.md)接口 -## 流程 +## 解释 ### 为什么有chunk的抽象: 用户文件可能是比较大的,上传到Cloud或者下载到本地的时间可能比较长,而且在传输的过程中也可能出现网络不稳定的情况。为了应对以上的问题,我们提出了chunk的概念,一个chunk由所在的文件偏移、数据、数据长度及校验值组成。文件数据内容的上传和下载都是都过chunk的操作来实现的。由于chunk比较小(默认256K),完成一个传输动作的transaction的时间也比较短,不容易出错。 diff --git a/doc/design/file_mananger/RESTAPI.md b/doc/design/file_mananger/RESTAPI.md index 4286195970b..8d0f37be995 100644 --- a/doc/design/file_mananger/RESTAPI.md +++ b/doc/design/file_mananger/RESTAPI.md @@ -1,4 +1,4 @@ -# REST API +# REST API Interface - file ``` diff --git a/doc/design/file_mananger/pfs/cp.md b/doc/design/file_mananger/pfs/cp.md index fcc57258a17..af4204d717f 100644 --- a/doc/design/file_mananger/pfs/cp.md +++ b/doc/design/file_mananger/pfs/cp.md @@ -1,19 +1,13 @@ # Name -pfs_cp - copy files and directories +cp - copy files and directories # Synopsis -` pfs_cp +` cp [OPTION]... or or ` # Description -``` - --only-show-errors (boolean) - Only errors and warnings are displayed. All other output is suppressed. - - --page-size (integer) - The number of results to return in each response to a list operation. The default value is 1000 (the maximum allowed). Using a lower value may help if an operation times out. - +``` --preserve--links Reserve links when copy files @@ -22,10 +16,10 @@ pfs_cp - copy files and directories ``` # Examples -- The following command cp a single file to pfs +- The following command copies a single file to pfs ``` -pfs_cp ./text1.txt pfs://mydir/text1.txt +paddle pfs cp ./text1.txt pfs://mydir/text1.txt ``` Output @@ -34,10 +28,10 @@ Output upload ./text1.txt to pfs://mydir/text1.txt ``` -- The following command cp pfs file to a local file +- The following command copies pfs file to a local file ``` -pfs_cp pfs://mydir/text1.txt ./text1.txt +paddle pfs cp pfs://mydir/text1.txt ./text1.txt ``` Output diff --git a/doc/design/file_mananger/pfs/ls.md b/doc/design/file_mananger/pfs/ls.md index 41cf6fdf77f..bec4c39b37e 100644 --- a/doc/design/file_mananger/pfs/ls.md +++ b/doc/design/file_mananger/pfs/ls.md @@ -1,49 +1,42 @@ # Name -pfs_ls - list directory contents o +ls - list directory contents or a file attributes # Synopsis -` pfs_ls [OPTION]... ` +` ls [OPTION]... ` # Description -``` - -h, --help - Display this help and exit - - --version - Output version information and exit - - --only-show-errors (boolean) - Only errors and warnings are displayed. All other output is suppressed. - - --page-size (integer) - The number of results to return in each response to a list operation. The default value is 1000 (the maximum allowed). Using a lower value may help if an operation times out. - +``` -R, -r, --recursive Copy directories recursively + + --page-size (integer) + The number of results to return in each response to a list operation. The default value is 1000 (the maximum allowed). Using a lower value may help if operation time out. ``` # Examples -- The following command cp a single file to pfs +- The following command lists a single file ``` -pfs_cp ./text1.txt pfs://mydir/text1.txt +paddle pfs ls pfs://mydir/text1.txt ``` Output ``` -upload ./text1.txt to pfs://mydir/text1.txt +2017-05-5 17:04:30 text1.txt ``` -- The following command cp pfs file to a local file +- The following command lists directory contents ``` -pfs_cp pfs://mydir/text1.txt ./text1.txt +paddle pfs ls pfs://mydir ``` Output ``` -download pfs://mydir/text1.txt to ./text1.txt +2017-05-5 17:04:30 text1.txt +2017-05-5 17:04:30 text2.txt +... ``` diff --git a/doc/design/file_mananger/pfs/mkdir.md b/doc/design/file_mananger/pfs/mkdir.md index e3b0d0cae78..45906dc2083 100644 --- a/doc/design/file_mananger/pfs/mkdir.md +++ b/doc/design/file_mananger/pfs/mkdir.md @@ -1,29 +1,15 @@ # Name -pdf_mkdir - mkdir directory(ies) +mkdir - mkdir directory(ies) # Synopsis -`pfs_mkdir [OPTION]... PFS_DIRECTORY...` +`mkdir [OPTION]... ...` # Description -Create the pfs directory(ies), if they do not already exist. - -``` - -h, --help - display this help and exit - - --version - output version information and exit - - --only-show-errors (boolean) - Only errors and warnings are displayed. All other output is suppressed. - - --page-size (integer) - The number of results to return in each response to a list operation. The default value is 1000 (the maximum allowed). Using a lower value may help if an operation times out. -``` +Create the pfs directory(ies), if they do not already exist. And create intermediate directories as required # Examples ``` -pfs_mkdir pfs://mydir1 +paddle pfs mkdir pfs://mydir1 ``` Output diff --git a/doc/design/file_mananger/pfs/mv.md b/doc/design/file_mananger/pfs/mv.md index 3f4c1ac0761..7f7ef209c82 100644 --- a/doc/design/file_mananger/pfs/mv.md +++ b/doc/design/file_mananger/pfs/mv.md @@ -3,7 +3,8 @@ mv - move (rename) files or directories # Synopsis -If destination already exist, please rm it first. +If destination already exist, please [rm](rm.md) it first. + ``` mv [OPTION]... or or @@ -11,28 +12,17 @@ mv [OPTION]... # Description ``` - -h, --help - display this help and exit - - -v, --version - output version information and exit - - --only-show-errors (boolean) - Only errors and warnings are displayed. All other output is suppressed. - - --page-size (integer) - The number of results to return in each response to a list operation. The default value is 1000 (the maximum allowed). Using a lower value may help if an operation times out. ``` # Examples - The following command move a single file to pfs ``` -mv ./text1.txt pfs://mydirectory/test1.txt +paddle pfs mv ./text1.txt pfs://mydirectory/test1.txt ``` Output ``` -move text1.txt pfs://mydirectory/test1.txt +move ./text1.txt pfs://mydirectory/test1.txt ``` diff --git a/doc/design/file_mananger/pfs/pfs.md b/doc/design/file_mananger/pfs/pfs.md index a17de7d8140..76ee0a6917d 100644 --- a/doc/design/file_mananger/pfs/pfs.md +++ b/doc/design/file_mananger/pfs/pfs.md @@ -12,20 +12,26 @@ paddle [options] pfs [parameters] ``` --profile (string) Use a specific profile from your credential file. ---help pfs - Display more information about pfs + +--help (string) + Display more information about command + --version Output version information and exit + --debug Show detailed debugging log + +--only-show-errors (boolean) + Only errors and warnings are displayed. All other output is suppressed. ``` ## Path Arguments -When using a commnd, we need to specify path arguments. There are two path argument type: `localpath` and `pfspath`. -A pfspath begin with `pfs://`, eg: `pfs://mydir/text1.txt`. +When using a command, we need to specify path arguments. There are two path argument type: `localpath` and `pfspath`. +A `pfspath` begin with `pfs://`, eg: `pfs://mydir/text1.txt`. -## Path Arguments‘s order -Commonly, there maybe two path arguments. The first is source, and the second is destination. +## order of Path Arguments +Commonly, if there are two path arguments. The first is the source, and the second is the destination. ## Subcommonds - [rm](rm.md) diff --git a/doc/design/file_mananger/pfs/rm.md b/doc/design/file_mananger/pfs/rm.md index 6056c401f95..fa881fe1e94 100644 --- a/doc/design/file_mananger/pfs/rm.md +++ b/doc/design/file_mananger/pfs/rm.md @@ -2,30 +2,25 @@ rm - remove files or directories # Synopsis -`rm [OPTION]... PFS_DIRECTORY...` +``` +rm [OPTION]... ... +``` # Description -Create the directory, if it does not already exits ``` -r, -R, --recursive remove directories and their contents recursively - - -v, --version - output version information and exit - - --only-show-errors (boolean) - Only errors and warnings are displayed. All other output is suppressed. --page-size (integer) - The number of results to return in each response to a list operation. The default value is 1000 (the maximum allowed). Using a lower value may help if an operation times out. + The number of results to return in each response to a list operation. The default value is 1000 (the maximum allowed). Using a lower value may help if an operation times out ``` # Examples -- The following command delete a single file +- The following command deletes a single files ``` -pfs_rm pfs://mydirectory/test1.txt +paddle pfs rm pfs://mydirectory/test1.txt ``` Output @@ -35,10 +30,10 @@ delete pfs://mydirectory/test1.txt ``` -- The following command delete a directory recursively. +- The following command deletes a directory recursively. ``` -pfs_rm -r pfs://mydirectory +paddle pfs rm -r pfs://mydirectory ``` Output diff --git a/doc/design/file_mananger/pfs/sync.md b/doc/design/file_mananger/pfs/sync.md index 99248082743..7a8e0b74cf1 100644 --- a/doc/design/file_mananger/pfs/sync.md +++ b/doc/design/file_mananger/pfs/sync.md @@ -1,53 +1,42 @@ # Name -pfs_cp - copy files and directories +sync - sync directories. Recursively copies new and updated files from the source directory to the destination # Synopsis -` pfs_cp +` sync [OPTIONS]... or or ` # Description ``` - -h, --help - Display this help and exit - - --version - Output version information and exit - - --only-show-errors (boolean) - Only errors and warnings are displayed. All other output is suppressed. - - --page-size (integer) - The number of results to return in each response to a list operation. The default value is 1000 (the maximum allowed). Using a lower value may help if an operation times out. - - --preserve--links - Reserve links when copy files - - -R, -r, --recursive - Copy directories recursively + -l, --links + copy symlinks as symlinks ``` # Examples -- The following command cp a single file to pfs +- The following command sync locally directory to pfs ``` -pfs_cp ./text1.txt pfs://mydir/text1.txt +sync ./dir1 pfs://mydir1 ``` Output ``` -upload ./text1.txt to pfs://mydir/text1.txt +upload ./dir1/text1.txt to pfs://mydir1/text2.txt +upload ./dir1/text2.txt to pfs://mydir1/text2.txt +... ``` -- The following command cp pfs file to a local file +- The following command sync pfs directory to local ``` -pfs_cp pfs://mydir/text1.txt ./text1.txt +sync pfs://mydir1 . ``` Output ``` -download pfs://mydir/text1.txt to ./text1.txt +download pfs://mydir1/text1.txt to ./text1.txt +download pfs://mydir1/text2.txt to ./text2.txt +... ``` -- GitLab From 1297bbd1a2a24676d6091947c14c510a44c41a4f Mon Sep 17 00:00:00 2001 From: gongweibao Date: Fri, 5 May 2017 15:40:26 +0800 Subject: [PATCH 0187/3256] modify some bugs --- doc/design/file_mananger/pfs/cp.md | 10 +++++----- doc/design/file_mananger/pfs/ls.md | 10 +++++----- doc/design/file_mananger/pfs/rm.md | 8 ++++---- doc/design/file_mananger/pfs/sync.md | 8 ++++---- .../file_mananger/src/filemanager.graffle | Bin 3605 -> 3819 bytes doc/design/file_mananger/src/filemanager.png | Bin 49686 -> 57379 bytes 6 files changed, 18 insertions(+), 18 deletions(-) diff --git a/doc/design/file_mananger/pfs/cp.md b/doc/design/file_mananger/pfs/cp.md index af4204d717f..80eed5d0794 100644 --- a/doc/design/file_mananger/pfs/cp.md +++ b/doc/design/file_mananger/pfs/cp.md @@ -8,11 +8,11 @@ cp - copy files and directories # Description ``` - --preserve--links - Reserve links when copy files - - -R, -r, --recursive - Copy directories recursively +--preserve--links + Reserve links when copy links + +-R, -r, --recursive + Copy directories recursively ``` # Examples diff --git a/doc/design/file_mananger/pfs/ls.md b/doc/design/file_mananger/pfs/ls.md index bec4c39b37e..8dc81aeaba2 100644 --- a/doc/design/file_mananger/pfs/ls.md +++ b/doc/design/file_mananger/pfs/ls.md @@ -7,11 +7,11 @@ ls - list directory contents or a file attributes # Description ``` - -R, -r, --recursive - Copy directories recursively - - --page-size (integer) - The number of results to return in each response to a list operation. The default value is 1000 (the maximum allowed). Using a lower value may help if operation time out. +-R, -r, --recursive + Copy directories recursively + +--page-size (integer) + The number of results to return in each response to a list operation. The default value is 1000 (the maximum allowed). Using a lower value may help if operation time out. ``` # Examples diff --git a/doc/design/file_mananger/pfs/rm.md b/doc/design/file_mananger/pfs/rm.md index fa881fe1e94..0b3760f332c 100644 --- a/doc/design/file_mananger/pfs/rm.md +++ b/doc/design/file_mananger/pfs/rm.md @@ -9,11 +9,11 @@ rm [OPTION]... ... # Description ``` - -r, -R, --recursive - remove directories and their contents recursively +-r, -R, --recursive + remove directories and their contents recursively - --page-size (integer) - The number of results to return in each response to a list operation. The default value is 1000 (the maximum allowed). Using a lower value may help if an operation times out +--page-size (integer) + The number of results to return in each response to a list operation. The default value is 1000 (the maximum allowed). Using a lower value may help if an operation times out ``` # Examples diff --git a/doc/design/file_mananger/pfs/sync.md b/doc/design/file_mananger/pfs/sync.md index 7a8e0b74cf1..c317742e501 100644 --- a/doc/design/file_mananger/pfs/sync.md +++ b/doc/design/file_mananger/pfs/sync.md @@ -8,15 +8,15 @@ sync - sync directories. Recursively copies new and updated files from the sourc # Description ``` - -l, --links - copy symlinks as symlinks +--preserve--links + Reserve links when copy links ``` # Examples - The following command sync locally directory to pfs ``` -sync ./dir1 pfs://mydir1 +paddle pfs sync ./dir1 pfs://mydir1 ``` Output @@ -30,7 +30,7 @@ upload ./dir1/text2.txt to pfs://mydir1/text2.txt - The following command sync pfs directory to local ``` -sync pfs://mydir1 . +paddle pfs sync pfs://mydir1 . ``` Output diff --git a/doc/design/file_mananger/src/filemanager.graffle b/doc/design/file_mananger/src/filemanager.graffle index d8ec4299f42eff52e7153b542e101cc28e2f9801..4a4338488dc1157745023790ed576d42ea214f1d 100644 GIT binary patch literal 3819 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 literal 3605 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 bz35aLl5+4d-^QPpdh_D{MYq+?^=beB1dRsQ diff --git a/doc/design/file_mananger/src/filemanager.png b/doc/design/file_mananger/src/filemanager.png index d8b14404bab846c42937b28296a63978c5cfdbb1..e6b9bfa48ea6371569c2d8458de55758270e64c9 100644 GIT binary patch literal 57379 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 aA6)5_N0knSm8Ce~@2rM_da0`2)&Bz_Fijx< -- GitLab From 5f02c4248a3937c6a2b54f74226e9b65e4b4db03 Mon Sep 17 00:00:00 2001 From: gongweibao Date: Fri, 5 May 2017 15:53:48 +0800 Subject: [PATCH 0188/3256] rm no ascii character --- doc/design/file_mananger/pfs/mv.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/design/file_mananger/pfs/mv.md b/doc/design/file_mananger/pfs/mv.md index 7f7ef209c82..8ea006bdaf5 100644 --- a/doc/design/file_mananger/pfs/mv.md +++ b/doc/design/file_mananger/pfs/mv.md @@ -1,5 +1,5 @@ # Name -mv - move (rename) files or directories +mv - move (rename) files or directories # Synopsis -- GitLab From 0e0de915c8235824ca9043f04c0ef18ccd82e6ef Mon Sep 17 00:00:00 2001 From: gongweibao Date: Fri, 5 May 2017 17:17:45 +0800 Subject: [PATCH 0189/3256] rm chinese links --- doc/design/file_mananger/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/design/file_mananger/README.md b/doc/design/file_mananger/README.md index bca8f48b8e2..ea267e64d4c 100644 --- a/doc/design/file_mananger/README.md +++ b/doc/design/file_mananger/README.md @@ -9,7 +9,7 @@ - 提供用户管理Cloud文件的命令 - 用Golang写,可以跨平台执行 -命令的详细内容看[这里](./pfs/pfs.md) +命令的详细内容看[Here](./pfs/pfs.md) ### Ingress -- GitLab From b24f6d0feaad426be2f3dfb3eb3e1671bf7a5765 Mon Sep 17 00:00:00 2001 From: gangliao Date: Fri, 5 May 2017 18:58:41 +0800 Subject: [PATCH 0190/3256] accelerate test and add debug info --- paddle/scripts/docker/build.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/paddle/scripts/docker/build.sh b/paddle/scripts/docker/build.sh index 4172063d923..33d2d3a8b1e 100644 --- a/paddle/scripts/docker/build.sh +++ b/paddle/scripts/docker/build.sh @@ -1,6 +1,6 @@ #!/bin/bash -set -e +set -xe # Set BASE_IMAGE according to env variables if [ ${WITH_GPU} == "ON" ]; then @@ -34,7 +34,7 @@ cmake .. \ -DCMAKE_EXPORT_COMPILE_COMMANDS=ON make -j `nproc` if [ ${WITH_TESTING:-OFF} == "ON" ] && [ ${RUN_TEST:-OFF} == "ON" ] ; then - make test + ctest -j `nproc` fi make install pip install /usr/local/opt/paddle/share/wheels/*.whl @@ -71,7 +71,7 @@ if [[ ${WOBOQ:-OFF} == 'ON' ]]; then cmake -DLLVM_CONFIG_EXECUTABLE=/usr/bin/llvm-config-3.8 \ -DCMAKE_BUILD_TYPE=Release \ . - make + make -j `nproc` export WOBOQ_OUT=/woboq_out/paddle export BUILD_DIR=/paddle/build -- GitLab From 444c4638c21167e138e945f9b91d6afa9a72a63c Mon Sep 17 00:00:00 2001 From: "yi.wu" Date: Fri, 5 May 2017 22:54:50 +0800 Subject: [PATCH 0191/3256] update for pre-commit --- python/paddle/v2/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/python/paddle/v2/__init__.py b/python/paddle/v2/__init__.py index 7a9d3a73f4f..0942334ca2a 100644 --- a/python/paddle/v2/__init__.py +++ b/python/paddle/v2/__init__.py @@ -46,7 +46,8 @@ def init(**kwargs): # NOTE: append arguments if they are in ENV for ek, ev in os.environ.iteritems(): if ek.startswith("PADDLE_"): - args.append('--%s=%s' % (ek.replace("PADDLE_", "").lower(), str(ev))) + args.append('--%s=%s' % (ek.replace("PADDLE_", "").lower(), + str(ev))) # NOTE: overwrite arguments from ENV if it is in kwargs for key in kwargs.keys(): -- GitLab From 6a7feb35e5980f91a784e8fe9dc08d2ad8f6bd3c Mon Sep 17 00:00:00 2001 From: xuwei06 Date: Thu, 4 May 2017 16:53:19 -0700 Subject: [PATCH 0192/3256] run python tests in virtualenv When using system python, "pip install" needs root previlege. Using virtualenv to avoid this. --- cmake/external/python.cmake | 8 ++--- cmake/util.cmake | 10 ++++++ paddle/api/test/CMakeLists.txt | 4 +-- .../run_python_tests.sh} | 24 +++++++++---- python/paddle/v2/plot/tests/CMakeLists.txt | 4 +-- python/paddle/v2/plot/tests/run_tests.sh | 36 ------------------- python/paddle/v2/reader/tests/CMakeLists.txt | 4 +-- python/paddle/v2/reader/tests/run_tests.sh | 35 ------------------ python/paddle/v2/tests/CMakeLists.txt | 17 +-------- python/paddle/v2/tests/run_tests.sh | 36 ------------------- 10 files changed, 36 insertions(+), 142 deletions(-) rename paddle/{api/test/run_tests.sh => scripts/run_python_tests.sh} (64%) delete mode 100755 python/paddle/v2/plot/tests/run_tests.sh delete mode 100755 python/paddle/v2/reader/tests/run_tests.sh delete mode 100755 python/paddle/v2/tests/run_tests.sh diff --git a/cmake/external/python.cmake b/cmake/external/python.cmake index 9fd3afd0998..fc66d6b2154 100644 --- a/cmake/external/python.cmake +++ b/cmake/external/python.cmake @@ -1,11 +1,11 @@ # 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. @@ -19,7 +19,7 @@ FIND_PACKAGE(PythonInterp 2.7) FIND_PACKAGE(PythonLibs 2.7) SET(py_env "") - +SET(USE_VIRTUALENV_FOR_TEST 1) IF(PYTHONLIBS_FOUND AND PYTHONINTERP_FOUND) find_python_module(pip REQUIRED) find_python_module(numpy REQUIRED) diff --git a/cmake/util.cmake b/cmake/util.cmake index be4c591da84..b828eef322b 100644 --- a/cmake/util.cmake +++ b/cmake/util.cmake @@ -144,3 +144,13 @@ function(create_resources res_file output_file) COMMAND python ARGS ${PROJ_ROOT}/cmake/make_resource.py ${res_file} ${output_file} DEPENDS ${res_file} ${PROJ_ROOT}/cmake/make_resource.py) endfunction() + + +# Create a python unittest using run_python_tests.sh, +# which takes care of making correct running environment +function(add_python_test TEST_NAME) + add_test(NAME ${TEST_NAME} + COMMAND bash ${PROJ_ROOT}/paddle/scripts/run_python_tests.sh + ${USE_VIRTUALENV_FOR_TEST} ${PYTHON_EXECUTABLE} ${ARGN} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) +endfunction() diff --git a/paddle/api/test/CMakeLists.txt b/paddle/api/test/CMakeLists.txt index a2fa623c800..f3b1c2c4d43 100644 --- a/paddle/api/test/CMakeLists.txt +++ b/paddle/api/test/CMakeLists.txt @@ -1,2 +1,2 @@ -add_test(NAME test_swig_api - COMMAND bash ${PROJ_ROOT}/paddle/api/test/run_tests.sh ${PYTHON_EXECUTABLE}) +add_python_test(test_swig_api + testArguments.py testGradientMachine.py testMatrix.py testVector.py testTrain.py testTrainer.py) diff --git a/paddle/api/test/run_tests.sh b/paddle/scripts/run_python_tests.sh similarity index 64% rename from paddle/api/test/run_tests.sh rename to paddle/scripts/run_python_tests.sh index bcf06afa86a..9a23c10d14e 100755 --- a/paddle/api/test/run_tests.sh +++ b/paddle/scripts/run_python_tests.sh @@ -18,19 +18,29 @@ pushd `dirname $0` > /dev/null SCRIPTPATH=$PWD popd > /dev/null -cd $SCRIPTPATH +USE_VIRTUALENV_FOR_TEST=$1; shift +PYTHON=$1; shift -$1 -m pip install ../../dist/*.whl +if [ $USE_VIRTUALENV_FOR_TEST -ne 0 ]; then + rm -rf .test_env + virtualenv .test_env + source .test_env/bin/activate + PYTHON=python +fi +$PYTHON -m pip install $SCRIPTPATH/../dist/*.whl requests matplotlib IPython -test_list="testArguments.py testGradientMachine.py testMatrix.py testVector.py testTrain.py testTrainer.py" +export PYTHONPATH=$SCRIPTPATH/../../python/ -export PYTHONPATH=$PWD/../../../python/ - -for fn in $test_list +for fn in "$@" do echo "test $fn" - $1 $fn + $PYTHON $fn if [ $? -ne 0 ]; then exit 1 fi done + +if [ $USE_VIRTUALENV_FOR_TEST -ne 0 ]; then + deactivate + rm -rf .test_env +fi diff --git a/python/paddle/v2/plot/tests/CMakeLists.txt b/python/paddle/v2/plot/tests/CMakeLists.txt index da550a178ce..b1132f13173 100644 --- a/python/paddle/v2/plot/tests/CMakeLists.txt +++ b/python/paddle/v2/plot/tests/CMakeLists.txt @@ -1,3 +1 @@ -add_test(NAME test_ploter - COMMAND bash ${PROJ_ROOT}/python/paddle/v2/plot/tests/run_tests.sh - ${PYTHON_EXECUTABLE}) +add_python_test(test_ploter test_ploter.py) diff --git a/python/paddle/v2/plot/tests/run_tests.sh b/python/paddle/v2/plot/tests/run_tests.sh deleted file mode 100755 index 9c1a4a71ce4..00000000000 --- a/python/paddle/v2/plot/tests/run_tests.sh +++ /dev/null @@ -1,36 +0,0 @@ -#!/bin/bash -# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - -pushd `dirname $0` > /dev/null -SCRIPTPATH=$PWD -popd > /dev/null - -cd $SCRIPTPATH -$1 -m pip install ../../../../../paddle/dist/*.whl - -export DISABLE_PLOT="True" -test_list="test_ploter.py" - -export PYTHONPATH=$PWD/../../../../../python/ - -for fn in $test_list -do - echo "test $fn" - $1 $fn - if [ $? -ne 0 ]; then - exit 1 - fi -done diff --git a/python/paddle/v2/reader/tests/CMakeLists.txt b/python/paddle/v2/reader/tests/CMakeLists.txt index a51f700406b..6a1d337b232 100644 --- a/python/paddle/v2/reader/tests/CMakeLists.txt +++ b/python/paddle/v2/reader/tests/CMakeLists.txt @@ -1,3 +1 @@ -add_test(NAME reader_tests - COMMAND bash ${PROJ_ROOT}/python/paddle/v2/reader/tests/run_tests.sh - ${PYTHON_EXECUTABLE}) +add_python_test(reader_tests creator_test.py decorator_test.py) diff --git a/python/paddle/v2/reader/tests/run_tests.sh b/python/paddle/v2/reader/tests/run_tests.sh deleted file mode 100755 index a544a563602..00000000000 --- a/python/paddle/v2/reader/tests/run_tests.sh +++ /dev/null @@ -1,35 +0,0 @@ -#!/bin/bash -# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - -pushd `dirname $0` > /dev/null -SCRIPTPATH=$PWD -popd > /dev/null - -cd $SCRIPTPATH -$1 -m pip install ../../../../../paddle/dist/*.whl - -test_list="creator_test.py decorator_test.py" - -export PYTHONPATH=$PWD/../../../../../python/ - -for fn in $test_list -do - echo "test $fn" - $1 $fn - if [ $? -ne 0 ]; then - exit 1 - fi -done diff --git a/python/paddle/v2/tests/CMakeLists.txt b/python/paddle/v2/tests/CMakeLists.txt index 572deaff356..5554a37df03 100644 --- a/python/paddle/v2/tests/CMakeLists.txt +++ b/python/paddle/v2/tests/CMakeLists.txt @@ -1,16 +1 @@ -add_test(NAME test_v2_api - COMMAND bash ${PROJ_ROOT}/python/paddle/v2/tests/run_tests.sh ${PYTHON_EXECUTABLE}) - -add_test(NAME test_v2_layer - COMMAND ${PROJ_ROOT}/paddle/.set_python_path.sh -d ${PROJ_ROOT}/python/ - ${PYTHON_EXECUTABLE} ${PROJ_ROOT}/python/paddle/v2/tests/test_layer.py - WORKING_DIRECTORY ${PROJ_ROOT}/python/paddle) - -add_test(NAME test_v2_rnn_layer - COMMAND ${PROJ_ROOT}/paddle/.set_python_path.sh -d ${PROJ_ROOT}/python/ - ${PYTHON_EXECUTABLE} ${PROJ_ROOT}/python/paddle/v2/tests/test_rnn_layer.py) - -add_test(NAME test_topology - COMMAND ${PROJ_ROOT}/paddle/.set_python_path.sh -d ${PROJ_ROOT}/python/ - ${PYTHON_EXECUTABLE} ${PROJ_ROOT}/python/paddle/v2/tests/test_topology.py - WORKING_DIRECTORY ${PROJ_ROOT}/python/paddle) +add_python_test(test_v2_api test_data_feeder.py test_parameters.py test_layer.py test_rnn_layer.py test_topology.py) diff --git a/python/paddle/v2/tests/run_tests.sh b/python/paddle/v2/tests/run_tests.sh deleted file mode 100755 index dda1b1bd222..00000000000 --- a/python/paddle/v2/tests/run_tests.sh +++ /dev/null @@ -1,36 +0,0 @@ -#!/bin/bash -# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - -pushd `dirname $0` > /dev/null -SCRIPTPATH=$PWD -popd > /dev/null - -cd $SCRIPTPATH - -$1 -m pip install ../../../../paddle/dist/*.whl - -test_list="test_data_feeder.py test_parameters.py" - -export PYTHONPATH=$PWD/../../../../python/ - -for fn in $test_list -do - echo "test $fn" - $1 $fn - if [ $? -ne 0 ]; then - exit 1 - fi -done -- GitLab From 71c77cbed58cd1d95d494dcdc7e6a036546aafdc Mon Sep 17 00:00:00 2001 From: xuwei06 Date: Fri, 5 May 2017 09:10:35 -0700 Subject: [PATCH 0193/3256] Use IPython 5.3 for unittest --- cmake/util.cmake | 1 + paddle/scripts/run_python_tests.sh | 19 +++++++++++++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/cmake/util.cmake b/cmake/util.cmake index b828eef322b..966e0a7bf60 100644 --- a/cmake/util.cmake +++ b/cmake/util.cmake @@ -149,6 +149,7 @@ endfunction() # Create a python unittest using run_python_tests.sh, # which takes care of making correct running environment function(add_python_test TEST_NAME) + message("PYTHON: ${PYTHON_EXECUTABLE}") add_test(NAME ${TEST_NAME} COMMAND bash ${PROJ_ROOT}/paddle/scripts/run_python_tests.sh ${USE_VIRTUALENV_FOR_TEST} ${PYTHON_EXECUTABLE} ${ARGN} diff --git a/paddle/scripts/run_python_tests.sh b/paddle/scripts/run_python_tests.sh index 9a23c10d14e..f49b233dfdd 100755 --- a/paddle/scripts/run_python_tests.sh +++ b/paddle/scripts/run_python_tests.sh @@ -20,6 +20,8 @@ popd > /dev/null USE_VIRTUALENV_FOR_TEST=$1; shift PYTHON=$1; shift +echo python: $PYTHON +pip list if [ $USE_VIRTUALENV_FOR_TEST -ne 0 ]; then rm -rf .test_env @@ -27,9 +29,22 @@ if [ $USE_VIRTUALENV_FOR_TEST -ne 0 ]; then source .test_env/bin/activate PYTHON=python fi -$PYTHON -m pip install $SCRIPTPATH/../dist/*.whl requests matplotlib IPython -export PYTHONPATH=$SCRIPTPATH/../../python/ +$PYTHON -m pip install $SCRIPTPATH/../dist/*.whl +# The next line is for debug, will be deleted +$PYTHON -m pip list +$PYTHON -m pip install requests matplotlib numpy ipython==5.3 +$PYTHON -m pip list +echo $PYTHON +echo PYTHONPATH: $PYTHONPATH +python -c 'import pkgutil; print(str(list(pkgutil.iter_modules("/opt/python/2.7.12/lib/python2.7/site-packages"))))' +echo "=========================" +python -c 'import numpy; import google; print(dir(google)); import google.protobuf; import pkgutil; print(str(list(pkgutil.iter_modules(google.protobuf.__path__)))); import google.protobuf.descriptor; ' +echo $PYTHON +echo PYTHONPATH: $PYTHONPATH +$PYTHON -c 'import numpy; import google.protobuf.descriptor; print("debug---------------")' +export PYTHONPATH=$PYTHONPATH:$SCRIPTPATH/../../python/ +$PYTHON -c 'import numpy; import google.protobuf.descriptor; print("debug---------------")' for fn in "$@" do -- GitLab From 4c31f1892c5e4dc71c1f76a9f3c35ff85746222d Mon Sep 17 00:00:00 2001 From: gangliao Date: Sat, 6 May 2017 09:40:43 +0800 Subject: [PATCH 0194/3256] add verbose output for unit test --- 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 33d2d3a8b1e..bc8eef4ea87 100644 --- a/paddle/scripts/docker/build.sh +++ b/paddle/scripts/docker/build.sh @@ -34,7 +34,7 @@ cmake .. \ -DCMAKE_EXPORT_COMPILE_COMMANDS=ON make -j `nproc` if [ ${WITH_TESTING:-OFF} == "ON" ] && [ ${RUN_TEST:-OFF} == "ON" ] ; then - ctest -j `nproc` + ctest -V -j `nproc` fi make install pip install /usr/local/opt/paddle/share/wheels/*.whl -- GitLab From 01b5d223b634597e8a4e5a361588095cdc9466c0 Mon Sep 17 00:00:00 2001 From: emailweixu Date: Fri, 5 May 2017 22:46:34 -0700 Subject: [PATCH 0195/3256] Update run_python_tests.sh debug --- paddle/scripts/run_python_tests.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/paddle/scripts/run_python_tests.sh b/paddle/scripts/run_python_tests.sh index f49b233dfdd..cde09ce67c2 100755 --- a/paddle/scripts/run_python_tests.sh +++ b/paddle/scripts/run_python_tests.sh @@ -37,6 +37,7 @@ $PYTHON -m pip install requests matplotlib numpy ipython==5.3 $PYTHON -m pip list echo $PYTHON echo PYTHONPATH: $PYTHONPATH +python -c 'import numpy; import pkgutil; print numpy.__path__; print(str(list(pkgutil.iter_modules(numpy.__path__))))' python -c 'import pkgutil; print(str(list(pkgutil.iter_modules("/opt/python/2.7.12/lib/python2.7/site-packages"))))' echo "=========================" python -c 'import numpy; import google; print(dir(google)); import google.protobuf; import pkgutil; print(str(list(pkgutil.iter_modules(google.protobuf.__path__)))); import google.protobuf.descriptor; ' -- GitLab From 5e38c2b6752571234a8bc4589bfd61039d7b0e17 Mon Sep 17 00:00:00 2001 From: emailweixu Date: Sat, 6 May 2017 00:00:00 -0700 Subject: [PATCH 0196/3256] Update run_python_tests.sh keep debugging --- paddle/scripts/run_python_tests.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/paddle/scripts/run_python_tests.sh b/paddle/scripts/run_python_tests.sh index cde09ce67c2..2d7dd6a2d2c 100755 --- a/paddle/scripts/run_python_tests.sh +++ b/paddle/scripts/run_python_tests.sh @@ -37,6 +37,7 @@ $PYTHON -m pip install requests matplotlib numpy ipython==5.3 $PYTHON -m pip list echo $PYTHON echo PYTHONPATH: $PYTHONPATH +ls $PYTHONPATH python -c 'import numpy; import pkgutil; print numpy.__path__; print(str(list(pkgutil.iter_modules(numpy.__path__))))' python -c 'import pkgutil; print(str(list(pkgutil.iter_modules("/opt/python/2.7.12/lib/python2.7/site-packages"))))' echo "=========================" -- GitLab From 883cb227435e98d5d606b2aad8f99c89fc001257 Mon Sep 17 00:00:00 2001 From: emailweixu Date: Sat, 6 May 2017 00:47:08 -0700 Subject: [PATCH 0197/3256] Update run_python_tests.sh --- paddle/scripts/run_python_tests.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paddle/scripts/run_python_tests.sh b/paddle/scripts/run_python_tests.sh index 2d7dd6a2d2c..3c9baae9612 100755 --- a/paddle/scripts/run_python_tests.sh +++ b/paddle/scripts/run_python_tests.sh @@ -29,7 +29,7 @@ if [ $USE_VIRTUALENV_FOR_TEST -ne 0 ]; then source .test_env/bin/activate PYTHON=python fi - +export PYTHONPATH= $PYTHON -m pip install $SCRIPTPATH/../dist/*.whl # The next line is for debug, will be deleted $PYTHON -m pip list -- GitLab From 710198026f4f35f2607393899c079c30d7ba42c6 Mon Sep 17 00:00:00 2001 From: emailweixu Date: Sat, 6 May 2017 01:36:24 -0700 Subject: [PATCH 0198/3256] Update run_python_tests.sh --- paddle/scripts/run_python_tests.sh | 23 +++-------------------- 1 file changed, 3 insertions(+), 20 deletions(-) diff --git a/paddle/scripts/run_python_tests.sh b/paddle/scripts/run_python_tests.sh index 3c9baae9612..712539bce86 100755 --- a/paddle/scripts/run_python_tests.sh +++ b/paddle/scripts/run_python_tests.sh @@ -20,8 +20,6 @@ popd > /dev/null USE_VIRTUALENV_FOR_TEST=$1; shift PYTHON=$1; shift -echo python: $PYTHON -pip list if [ $USE_VIRTUALENV_FOR_TEST -ne 0 ]; then rm -rf .test_env @@ -29,24 +27,9 @@ if [ $USE_VIRTUALENV_FOR_TEST -ne 0 ]; then source .test_env/bin/activate PYTHON=python fi -export PYTHONPATH= -$PYTHON -m pip install $SCRIPTPATH/../dist/*.whl -# The next line is for debug, will be deleted -$PYTHON -m pip list -$PYTHON -m pip install requests matplotlib numpy ipython==5.3 -$PYTHON -m pip list -echo $PYTHON -echo PYTHONPATH: $PYTHONPATH -ls $PYTHONPATH -python -c 'import numpy; import pkgutil; print numpy.__path__; print(str(list(pkgutil.iter_modules(numpy.__path__))))' -python -c 'import pkgutil; print(str(list(pkgutil.iter_modules("/opt/python/2.7.12/lib/python2.7/site-packages"))))' -echo "=========================" -python -c 'import numpy; import google; print(dir(google)); import google.protobuf; import pkgutil; print(str(list(pkgutil.iter_modules(google.protobuf.__path__)))); import google.protobuf.descriptor; ' -echo $PYTHON -echo PYTHONPATH: $PYTHONPATH -$PYTHON -c 'import numpy; import google.protobuf.descriptor; print("debug---------------")' -export PYTHONPATH=$PYTHONPATH:$SCRIPTPATH/../../python/ -$PYTHON -c 'import numpy; import google.protobuf.descriptor; print("debug---------------")' + +export PYTHONPATH=$SCRIPTPATH/../../python/ +$PYTHON -m pip install $SCRIPTPATH/../dist/*.wh requests matplotlib ioython==5. for fn in "$@" do -- GitLab From 0762f7ee9b495e6e61eb5c7bf2318c6348801a8c Mon Sep 17 00:00:00 2001 From: emailweixu Date: Sat, 6 May 2017 02:07:04 -0700 Subject: [PATCH 0199/3256] Update run_python_tests.sh --- paddle/scripts/run_python_tests.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paddle/scripts/run_python_tests.sh b/paddle/scripts/run_python_tests.sh index 712539bce86..411f23ff9d4 100755 --- a/paddle/scripts/run_python_tests.sh +++ b/paddle/scripts/run_python_tests.sh @@ -29,7 +29,7 @@ if [ $USE_VIRTUALENV_FOR_TEST -ne 0 ]; then fi export PYTHONPATH=$SCRIPTPATH/../../python/ -$PYTHON -m pip install $SCRIPTPATH/../dist/*.wh requests matplotlib ioython==5. +$PYTHON -m pip install $SCRIPTPATH/../dist/*.whl requests matplotlib ioython==5. for fn in "$@" do -- GitLab From bc01717ce23ccd9ae88a0d7a46feccb8b5824de4 Mon Sep 17 00:00:00 2001 From: emailweixu Date: Sat, 6 May 2017 02:07:33 -0700 Subject: [PATCH 0200/3256] Update run_python_tests.sh --- paddle/scripts/run_python_tests.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paddle/scripts/run_python_tests.sh b/paddle/scripts/run_python_tests.sh index 411f23ff9d4..8297980734f 100755 --- a/paddle/scripts/run_python_tests.sh +++ b/paddle/scripts/run_python_tests.sh @@ -29,7 +29,7 @@ if [ $USE_VIRTUALENV_FOR_TEST -ne 0 ]; then fi export PYTHONPATH=$SCRIPTPATH/../../python/ -$PYTHON -m pip install $SCRIPTPATH/../dist/*.whl requests matplotlib ioython==5. +$PYTHON -m pip install $SCRIPTPATH/../dist/*.whl requests matplotlib ipython==5. for fn in "$@" do -- GitLab From 5458be66444631c4b26b39af732b22d42273a79f Mon Sep 17 00:00:00 2001 From: emailweixu Date: Sat, 6 May 2017 02:07:55 -0700 Subject: [PATCH 0201/3256] Update run_python_tests.sh --- paddle/scripts/run_python_tests.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paddle/scripts/run_python_tests.sh b/paddle/scripts/run_python_tests.sh index 8297980734f..05c187522ca 100755 --- a/paddle/scripts/run_python_tests.sh +++ b/paddle/scripts/run_python_tests.sh @@ -29,7 +29,7 @@ if [ $USE_VIRTUALENV_FOR_TEST -ne 0 ]; then fi export PYTHONPATH=$SCRIPTPATH/../../python/ -$PYTHON -m pip install $SCRIPTPATH/../dist/*.whl requests matplotlib ipython==5. +$PYTHON -m pip install $SCRIPTPATH/../dist/*.whl requests matplotlib ipython==5.3 for fn in "$@" do -- GitLab From e4656c27172b8eb243ef79bd72c97f2728c87f86 Mon Sep 17 00:00:00 2001 From: liaogang Date: Sat, 6 May 2017 17:20:14 +0800 Subject: [PATCH 0202/3256] using one thread to dump sequential output --- 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 bc8eef4ea87..2850e24b3af 100644 --- a/paddle/scripts/docker/build.sh +++ b/paddle/scripts/docker/build.sh @@ -34,7 +34,7 @@ cmake .. \ -DCMAKE_EXPORT_COMPILE_COMMANDS=ON make -j `nproc` if [ ${WITH_TESTING:-OFF} == "ON" ] && [ ${RUN_TEST:-OFF} == "ON" ] ; then - ctest -V -j `nproc` + ctest -V fi make install pip install /usr/local/opt/paddle/share/wheels/*.whl -- GitLab From 286696aa2b300b63d460f117c834500e87ca407a Mon Sep 17 00:00:00 2001 From: yangyaming Date: Sat, 6 May 2017 17:33:12 +0800 Subject: [PATCH 0203/3256] extend imikolov instead of adding ptb --- python/paddle/v2/dataset/imikolov.py | 44 +++-- python/paddle/v2/dataset/ptb.py | 169 ------------------ .../paddle/v2/dataset/tests/imikolov_test.py | 27 +++ python/paddle/v2/dataset/tests/ptb_test.py | 53 ------ 4 files changed, 60 insertions(+), 233 deletions(-) delete mode 100644 python/paddle/v2/dataset/ptb.py delete mode 100644 python/paddle/v2/dataset/tests/ptb_test.py diff --git a/python/paddle/v2/dataset/imikolov.py b/python/paddle/v2/dataset/imikolov.py index 97b9f7d9158..dd3a4552d2e 100644 --- a/python/paddle/v2/dataset/imikolov.py +++ b/python/paddle/v2/dataset/imikolov.py @@ -28,6 +28,11 @@ URL = 'http://www.fit.vutbr.cz/~imikolov/rnnlm/simple-examples.tgz' MD5 = '30177ea32e27c525793142b6bf2c8e2d' +class DataType(object): + NGRAM = 1 + SEQ = 2 + + def word_count(f, word_freq=None): if word_freq is None: word_freq = collections.defaultdict(int) @@ -69,7 +74,7 @@ def build_dict(min_word_freq=50): return word_idx -def reader_creator(filename, word_idx, n): +def reader_creator(filename, word_idx, n, data_type): def reader(): with tarfile.open( paddle.v2.dataset.common.download( @@ -79,16 +84,27 @@ def reader_creator(filename, word_idx, n): UNK = word_idx[''] for l in f: - l = [''] + l.strip().split() + [''] - if len(l) >= n: + if DataType.NGRAM == data_type: + assert n > -1, 'Invalid gram length' + l = [''] + l.strip().split() + [''] + if len(l) >= n: + l = [word_idx.get(w, UNK) for w in l] + for i in range(n, len(l) + 1): + yield tuple(l[i - n:i]) + elif DataType.SEQ == data_type: + l = l.strip().split() l = [word_idx.get(w, UNK) for w in l] - for i in range(n, len(l) + 1): - yield tuple(l[i - n:i]) + src_seq = [word_idx['']] + l + trg_seq = l + [word_idx['']] + if n > 0 and len(src_seq) > n: continue + yield src_seq, trg_seq + else: + assert False, 'Unknow data type' return reader -def train(word_idx, n): +def train(word_idx, n, data_type=DataType.NGRAM): """ imikolov training set creator. @@ -97,15 +113,18 @@ def train(word_idx, n): :param word_idx: word dictionary :type word_idx: dict - :param n: sliding window size + :param n: sliding window size if type is ngram, otherwise max length of sequence :type n: int + :param data_type: data type (ngram or sequence) + :type data_type: member variable of DataType (NGRAM or SEQ) :return: Training reader creator :rtype: callable """ - return reader_creator('./simple-examples/data/ptb.train.txt', word_idx, n) + return reader_creator('./simple-examples/data/ptb.train.txt', word_idx, n, + data_type) -def test(word_idx, n): +def test(word_idx, n, data_type=DataType.NGRAM): """ imikolov test set creator. @@ -114,12 +133,15 @@ def test(word_idx, n): :param word_idx: word dictionary :type word_idx: dict - :param n: sliding window size + :param n: sliding window size if type is ngram, otherwise max length of sequence :type n: int + :param data_type: data type (ngram or sequence) + :type data_type: member variable of DataType (NGRAM or SEQ) :return: Test reader creator :rtype: callable """ - return reader_creator('./simple-examples/data/ptb.valid.txt', word_idx, n) + return reader_creator('./simple-examples/data/ptb.valid.txt', word_idx, n, + data_type) def fetch(): diff --git a/python/paddle/v2/dataset/ptb.py b/python/paddle/v2/dataset/ptb.py deleted file mode 100644 index ea68daf9c6b..00000000000 --- a/python/paddle/v2/dataset/ptb.py +++ /dev/null @@ -1,169 +0,0 @@ -# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -""" -langauge model's simple dataset. - -This module will download dataset from -http://www.fit.vutbr.cz/~imikolov/rnnlm/ and parse training set and test set -into paddle reader creators. -""" -import paddle.v2.dataset.common -import collections -import tarfile - -__all__ = ['train', 'test', 'build_dict'] - -URL = 'http://www.fit.vutbr.cz/~imikolov/rnnlm/simple-examples.tgz' -MD5 = '30177ea32e27c525793142b6bf2c8e2d' - - -def word_count(f, word_freq=None): - if word_freq is None: - word_freq = collections.defaultdict(int) - - for l in f: - for w in l.strip().split(): - word_freq[w] += 1 - word_freq[''] += 1 - word_freq[''] += 1 - - return word_freq - - -def build_dict(min_word_freq=50): - """ - Build a word dictionary from the corpus, Keys of the dictionary are words, - and values are zero-based IDs of these words. - """ - train_filename = './simple-examples/data/ptb.train.txt' - test_filename = './simple-examples/data/ptb.valid.txt' - with tarfile.open( - paddle.v2.dataset.common.download( - paddle.v2.dataset.imikolov.URL, 'imikolov', - paddle.v2.dataset.imikolov.MD5)) as tf: - trainf = tf.extractfile(train_filename) - testf = tf.extractfile(test_filename) - word_freq = word_count(testf, word_count(trainf)) - if '' in word_freq: - # remove for now, since we will set it as last index - del word_freq[''] - - word_freq = filter(lambda x: x[1] > min_word_freq, word_freq.items()) - - word_freq_sorted = sorted(word_freq, key=lambda x: (-x[1], x[0])) - words, _ = list(zip(*word_freq_sorted)) - word_idx = dict(zip(words, xrange(len(words)))) - word_idx[''] = len(words) - - return word_idx - - -def reader_creator(filename, reader_type, word_idx, n=-1): - def reader(): - with tarfile.open( - paddle.v2.dataset.common.download( - paddle.v2.dataset.imikolov.URL, 'imikolov', - paddle.v2.dataset.imikolov.MD5)) as tf: - f = tf.extractfile(filename) - - UNK = word_idx[''] - - for l in f: - if 'ngram' == reader_type: - assert n > -1, 'Invalid gram length' - l = [''] + l.strip().split() + [''] - if len(l) < n: continue - l = [word_idx.get(w, UNK) for w in l] - for i in range(n, len(l) + 1): - yield tuple(l[i - n:i]) - elif 'seq' == reader_type: - l = l.strip().split() - l = [word_idx.get(w, UNK) for w in l] - src_seq = [word_idx['']] + l - trg_seq = l + [word_idx['']] - yield src_seq, trg_seq - - return reader - - -def ngram_train(word_idx, n): - """ - ptb ngram type training set creator. - - It returns a reader creator, each sample in the reader is a word ID - tuple. - - :param word_idx: word dictionary - :type word_idx: dict - :param n: sliding window size - :type n: int - :return: Training reader creator - :rtype: callable - """ - return reader_creator('./simple-examples/data/ptb.train.txt', 'ngram', - word_idx, n) - - -def ngram_test(word_idx, n): - """ - ptb ngram test set creator. - - It returns a reader creator, each sample in the reader is a word ID - tuple. - - :param word_idx: word dictionary - :type word_idx: dict - :param n: sliding window size - :type n: int - :return: Test reader creator - :rtype: callable - """ - return reader_creator('./simple-examples/data/ptb.valid.txt', 'ngram', - word_idx, n) - - -def seq_train(word_idx): - """ - ptb sequence type training set creator. - - It returns a reader creator, each sample in the reader is a word ID - pair. - - :param word_idx: word dictionary - :type word_idx: dict - :return: Test reader creator - :rtype: callable - """ - return reader_creator('./simple-examples/data/ptb.train.txt', 'seq', - word_idx) - - -def seq_test(word_idx): - """ - ptb sequence type test set creator. - - It returns a reader creator, each sample in the reader is a word ID - pair. - - :param word_idx: word dictionary - :type word_idx: dict - :return: Test reader creator - :rtype: callable - """ - return reader_creator('./simple-examples/data/ptb.valid.txt', 'seq', - word_idx) - - -def fetch(): - paddle.v2.dataset.common.download(URL, "imikolov", MD5) diff --git a/python/paddle/v2/dataset/tests/imikolov_test.py b/python/paddle/v2/dataset/tests/imikolov_test.py index 009e55243a5..4e52810e6b9 100644 --- a/python/paddle/v2/dataset/tests/imikolov_test.py +++ b/python/paddle/v2/dataset/tests/imikolov_test.py @@ -13,10 +13,37 @@ class TestMikolov(unittest.TestCase): n = 5 self.check_reader(paddle.v2.dataset.imikolov.train(WORD_DICT, n), n) + first_line = 'aer banknote berlitz calloway centrust cluett fromstein '\ + 'gitano guterman hydro-quebec ipo kia memotec mlx nahb punts '\ + 'rake regatta rubens sim snack-food ssangyong swapo wachter' + first_line = [ + WORD_DICT.get(ch, WORD_DICT['']) + for ch in first_line.split(' ') + ] + for l in paddle.v2.dataset.imikolov.train( + WORD_DICT, n=-1, + data_type=paddle.v2.dataset.imikolov.DataType.SEQ)(): + read_line = l[0][1:] + break + self.assertEqual(first_line, read_line) + def test_test(self): n = 5 self.check_reader(paddle.v2.dataset.imikolov.test(WORD_DICT, n), n) + first_line = 'consumers may want to move their telephones a little '\ + 'closer to the tv set' + first_line = [ + WORD_DICT.get(ch, WORD_DICT['']) + for ch in first_line.split(' ') + ] + for l in paddle.v2.dataset.imikolov.test( + WORD_DICT, n=-1, + data_type=paddle.v2.dataset.imikolov.DataType.SEQ)(): + read_line = l[0][1:] + break + self.assertEqual(first_line, read_line) + def test_total(self): _, idx = zip(*WORD_DICT.items()) self.assertEqual(sorted(idx)[-1], len(WORD_DICT) - 1) diff --git a/python/paddle/v2/dataset/tests/ptb_test.py b/python/paddle/v2/dataset/tests/ptb_test.py deleted file mode 100644 index 5e584a734d3..00000000000 --- a/python/paddle/v2/dataset/tests/ptb_test.py +++ /dev/null @@ -1,53 +0,0 @@ -import paddle.v2.dataset.ptb -import unittest - -WORD_DICT = paddle.v2.dataset.ptb.build_dict() - - -class TestMikolov(unittest.TestCase): - def check_reader(self, reader, n): - for l in reader(): - self.assertEqual(len(l), n) - - def test_ngram_train(self): - n = 5 - self.check_reader(paddle.v2.dataset.ptb.ngram_train(WORD_DICT, n), n) - - def test_ngram_test(self): - n = 5 - self.check_reader(paddle.v2.dataset.ptb.ngram_test(WORD_DICT, n), n) - - def test_seq_train(self): - first_line = 'aer banknote berlitz calloway centrust cluett fromstein '\ - 'gitano guterman hydro-quebec ipo kia memotec mlx nahb punts '\ - 'rake regatta rubens sim snack-food ssangyong swapo wachter' - first_line = [ - WORD_DICT.get(ch, WORD_DICT['']) - for ch in first_line.split(' ') - ] - for l in paddle.v2.dataset.ptb.seq_train(WORD_DICT)(): - read_line = l[0][1:] - break - - self.assertEqual(first_line, read_line) - - def test_seq_test(self): - first_line = 'consumers may want to move their telephones a little '\ - 'closer to the tv set' - first_line = [ - WORD_DICT.get(ch, WORD_DICT['']) - for ch in first_line.split(' ') - ] - for l in paddle.v2.dataset.ptb.seq_test(WORD_DICT)(): - read_line = l[0][1:] - break - - self.assertEqual(first_line, read_line) - - def test_total(self): - _, idx = zip(*WORD_DICT.items()) - self.assertEqual(sorted(idx)[-1], len(WORD_DICT) - 1) - - -if __name__ == '__main__': - unittest.main() -- GitLab From feaf12d4f2ec451fb141577a27d50588ea6f70a6 Mon Sep 17 00:00:00 2001 From: liaogang Date: Sat, 6 May 2017 19:05:19 +0800 Subject: [PATCH 0204/3256] fix bug when enable gpu unit test on docker --- Dockerfile | 2 +- paddle/scripts/docker/build.sh | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index c3ad0c9c2f6..6d5797da9e0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -26,7 +26,7 @@ RUN apt-get update && \ apt-get install -y git python-pip python-dev openssh-server bison && \ apt-get install -y wget unzip tar xz-utils bzip2 gzip coreutils && \ apt-get install -y curl sed grep graphviz libjpeg-dev zlib1g-dev && \ - apt-get install -y python-numpy python-matplotlib gcc g++ gfortran && \ + apt-get install -y python-numpy python-matplotlib gcc g++ liblapack-dev liblapacke-dev && \ apt-get install -y automake locales clang-format-3.8 swig doxygen && \ apt-get clean -y diff --git a/paddle/scripts/docker/build.sh b/paddle/scripts/docker/build.sh index 2850e24b3af..f1743813abc 100644 --- a/paddle/scripts/docker/build.sh +++ b/paddle/scripts/docker/build.sh @@ -34,7 +34,8 @@ cmake .. \ -DCMAKE_EXPORT_COMPILE_COMMANDS=ON make -j `nproc` if [ ${WITH_TESTING:-OFF} == "ON" ] && [ ${RUN_TEST:-OFF} == "ON" ] ; then - ctest -V + pip uninstall -y py-paddle paddle + ctest -V fi make install pip install /usr/local/opt/paddle/share/wheels/*.whl -- GitLab From f7d0d99f936a9288855c7d15263378473ab8c4b9 Mon Sep 17 00:00:00 2001 From: liaogang Date: Sat, 6 May 2017 19:16:43 +0800 Subject: [PATCH 0205/3256] 'numpy.ndarray' object has no attribute 'tobytes' --- python/paddle/v2/parameters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/paddle/v2/parameters.py b/python/paddle/v2/parameters.py index d686d09f220..64805d0c504 100644 --- a/python/paddle/v2/parameters.py +++ b/python/paddle/v2/parameters.py @@ -249,7 +249,7 @@ class Parameters(object): size = reduce(lambda a, b: a * b, param.shape) f.write(struct.pack("IIQ", 0, 4, size)) param = param.astype(np.float32) - f.write(param.tobytes()) + f.write(param.tostring()) def deserialize(self, name, f): """ -- GitLab From fa288b70afe934cafe368429b85227c67dfc5171 Mon Sep 17 00:00:00 2001 From: Yi Wang Date: Thu, 4 May 2017 10:45:33 -0700 Subject: [PATCH 0206/3256] Ongong work --- Dockerfile | 25 +++-- doc/howto/dev/write_docs_cn.rst | 9 +- doc/howto/dev/write_docs_en.rst | 77 +++++++++++++ paddle/scripts/docker/build.sh | 106 +++++++++++++----- paddle/scripts/tools/build_docs/build_docs.sh | 42 ++----- 5 files changed, 180 insertions(+), 79 deletions(-) create mode 100644 doc/howto/dev/write_docs_en.rst diff --git a/Dockerfile b/Dockerfile index c3ad0c9c2f6..d24042d63eb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ # A image for building paddle binaries # Use cuda devel base image for both cpu and gpu environment -FROM nvidia/cuda:8.0-cudnn5-devel-ubuntu14.04 +FROM nvidia/cuda:8.0-cudnn5-devel-ubuntu16.04 MAINTAINER PaddlePaddle Authors ARG UBUNTU_MIRROR @@ -23,11 +23,13 @@ ENV HOME /root COPY ./paddle/scripts/docker/root/ /root/ RUN apt-get update && \ - apt-get install -y git python-pip python-dev openssh-server bison && \ - apt-get install -y wget unzip tar xz-utils bzip2 gzip coreutils && \ - apt-get install -y curl sed grep graphviz libjpeg-dev zlib1g-dev && \ - apt-get install -y python-numpy python-matplotlib gcc g++ gfortran && \ - apt-get install -y automake locales clang-format-3.8 swig doxygen && \ + apt-get install -y \ + git python-pip python-dev openssh-server bison \ + wget unzip tar xz-utils bzip2 gzip coreutils \ + curl sed grep graphviz libjpeg-dev zlib1g-dev \ + python-numpy python-matplotlib gcc g++ gfortran \ + automake locales clang-format-3.8 swig doxygen cmake \ + clang-3.8 llvm-3.8 libclang-3.8-dev && \ apt-get clean -y # git credential to skip password typing @@ -51,11 +53,12 @@ RUN pip install --upgrade pip && \ RUN apt-get install -y libssl-dev libffi-dev RUN pip install certifi urllib3[secure] -RUN curl -sSL https://cmake.org/files/v3.4/cmake-3.4.1.tar.gz | tar -xz && \ - cd cmake-3.4.1 && ./bootstrap && make -j `nproc` && make install && \ - cd .. && rm -rf cmake-3.4.1 - -VOLUME ["/woboq_out"] +# Install woboq_codebrowser to /woboq +RUN git clone https://github.com/woboq/woboq_codebrowser /woboq && \ + (cd /woboq \ + cmake -DLLVM_CONFIG_EXECUTABLE=/usr/bin/llvm-config-3.8 \ + -DCMAKE_BUILD_TYPE=Release . \ + make) # Configure OpenSSH server. c.f. https://docs.docker.com/engine/examples/running_ssh_service RUN mkdir /var/run/sshd diff --git a/doc/howto/dev/write_docs_cn.rst b/doc/howto/dev/write_docs_cn.rst index d536f53abc0..5d1297d0796 100644 --- a/doc/howto/dev/write_docs_cn.rst +++ b/doc/howto/dev/write_docs_cn.rst @@ -2,14 +2,13 @@ 如何贡献/修改文档 ################## -PaddlePaddle的文档包括英文文档 ``doc`` 和中文文档 ``doc_cn`` 两个部分。文档都是通过 `cmake`_ 驱动 `sphinx`_ 编译生成,生成后的文档分别存储在编译目录的 ``doc`` 和 ``doc_cn`` 两个子目录下。 +PaddlePaddle的文档文件都在 :code:`doc` 这个子目录里。源文件是 `RST `_ 格式的。 在编译PaddlePaddle源码的时候,可以选择让 `cmake`_ 调用 `sphinx `_ 从 RST 文件生成 HTML 格式的文档。 如何构建PaddlePaddle的文档 ========================== -PaddlePaddle的文档构建有直接构建和基于Docker构建两种方式,我们提供了一个构建脚本build_docs.sh来进行构建。 -PaddlePaddle文档需要准备的环境相对较复杂,所以我们推荐使用基于Docker来构建PaddlePaddle的文档。 +为了简化大家安装文档构建工具的过程,我们提供一个Docker image。 使用Docker构建PaddlePaddle的文档 @@ -74,5 +73,5 @@ PaddlePaddle文档使用 `sphinx`_ 自动生成,用户可以参考sphinx教程 -.. _cmake: https://cmake.org/ -.. _sphinx: http://www.sphinx-doc.org/en/1.4.8/ +.. _cmake: +.. _sphinx: diff --git a/doc/howto/dev/write_docs_en.rst b/doc/howto/dev/write_docs_en.rst new file mode 100644 index 00000000000..65e7edca94d --- /dev/null +++ b/doc/howto/dev/write_docs_en.rst @@ -0,0 +1,77 @@ +############### +Build Documents +############### + +Document files of PaddlePaddle are in sub-directory :code:`doc`. Source files are in `RST `_ format. We can build the document by letting `cmake`_ invoke `sphinx `_ to convert RST files into HTML files. + + +How to Build Documents +====================== + +To save the time of installing building tools, we provide a Docker image. + + +使用Docker构建PaddlePaddle的文档 +-------------------------------- + +使用Docker构建PaddlePaddle的文档,需要在系统里先安装好Docker工具包。Docker安装请参考 `Docker的官网 `_ 。安装好Docker之后可以使用源码目录下的脚本构建文档,即 + +.. code-block:: bash + + cd TO_YOUR_PADDLE_CLONE_PATH + cd paddle/scripts/tools/build_docs + bash build_docs.sh with_docker + +编译完成后,会在当前目录生成两个子目录\: + +* doc 英文文档目录 +* doc_cn 中文文档目录 + +打开浏览器访问对应目录下的index.html即可访问本地文档。 + + + +直接构建PaddlePaddle的文档 +-------------------------- + +因为PaddlePaddle的v2 api文档生成过程依赖于py_paddle Python包,用户需要首先确认py_paddle包已经安装。 + +.. code-block:: bash + + python -c "import py_paddle" + +如果提示错误,那么用户需要在本地编译安装PaddlePaddle,请参考 `源码编译文档 `_ 。 +注意,用户在首次编译安装PaddlePaddle时,请将WITH_DOC选项关闭。在编译安装正确之后,请再次确认py_paddle包已经安装,即可进行下一步操作。 + +如果提示正确,可以执行以下命令编译生成文档,即 + +.. code-block:: bash + + cd TO_YOUR_PADDLE_CLONE_PATH + cd paddle/scripts/tools/build_docs + bash build_docs.sh local + +编译完成之后,会在当前目录生成两个子目录\: + +* doc 英文文档目录 +* doc_cn 中文文档目录 + +打开浏览器访问对应目录下的index.html即可访问本地文档。 + + +如何书写PaddlePaddle的文档 +========================== + +PaddlePaddle文档使用 `sphinx`_ 自动生成,用户可以参考sphinx教程进行书写。 + +如何更新www.paddlepaddle.org文档 +================================ + +开发者给PaddlePaddle代码增加的注释以PR的形式提交到github中,提交方式可参见 `贡献文档 `_ 。 +目前PaddlePaddle的develop分支的文档是自动触发更新的,用户可以分别查看最新的 `中文文档 `_ 和 +`英文文档 `_ 。 + + + +.. _cmake: https://cmake.org/ +.. _sphinx: http://www.sphinx-doc.org/en/1.4.8/ diff --git a/paddle/scripts/docker/build.sh b/paddle/scripts/docker/build.sh index bc8eef4ea87..3b458b88646 100644 --- a/paddle/scripts/docker/build.sh +++ b/paddle/scripts/docker/build.sh @@ -4,9 +4,9 @@ set -xe # Set BASE_IMAGE according to env variables if [ ${WITH_GPU} == "ON" ]; then - BASE_IMAGE="nvidia/cuda:8.0-cudnn5-runtime-ubuntu14.04" + BASE_IMAGE="nvidia/cuda:8.0-cudnn5-runtime-ubuntu16.04" else - BASE_IMAGE="ubuntu:14.04" + BASE_IMAGE="ubuntu:16.04" fi DOCKERFILE_GPU_ENV="" @@ -22,6 +22,20 @@ cd /paddle/build # build script will not fail if *.deb does not exist rm *.deb 2>/dev/null || true +cat < /paddle/build/Dockerfile <]" - echo "This script generates doc and doc_cn in the script's directory." - echo "These are common commands used in various situations:" - echo " with_docker build doc and doc_cn with docker" - echo " local build doc and doc_cn locally" -} - - -case "$1" in - "with_docker") - docker run --rm -v $PWD/../../../../:/paddle \ - -e "WITH_GPU=OFF" -e "WITH_AVX=ON" -e "WITH_DOC=ON" paddledev/paddle:dev - ;; - "local") - mkdir -p doc - mkdir -p doc_cn - PADDLE_SOURCE_DIR=$PWD/../../../../ - mkdir -p $PADDLE_SOURCE_DIR/build_doc - pushd $PADDLE_SOURCE_DIR/build_doc - cmake .. -DWITH_DOC=ON - make paddle_docs paddle_docs_cn - popd - cp -r $PADDLE_SOURCE_DIR/build_doc/doc/en/html/* doc - cp -r $PADDLE_SOURCE_DIR/build_doc/doc/cn/html/* doc_cn - rm -rf $PADDLE_SOURCE_DIR/build_doc - ;; - "--help") - usage - ;; - *) - usage - ;; -esac +docker run --rm \ + -v $(git rev-parse --show-toplevel):/paddle \ + -e "WITH_GPU=OFF" \ + -e "WITH_AVX=ON" \ + -e "WITH_DOC=ON" \ + -e "WOBOQ=ON" \ + ${1:-"paddledev/paddle:dev"} -- GitLab From 6066b36e1d3a1ff7afb3bfe0295ec6c3b6dd1af7 Mon Sep 17 00:00:00 2001 From: Yi Wang Date: Sun, 7 May 2017 05:42:47 -0700 Subject: [PATCH 0207/3256] Update base image --- paddle/api/CMakeLists.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/paddle/api/CMakeLists.txt b/paddle/api/CMakeLists.txt index 4d0dacae905..f2c56a4a362 100644 --- a/paddle/api/CMakeLists.txt +++ b/paddle/api/CMakeLists.txt @@ -92,7 +92,9 @@ add_custom_command(OUTPUT ${PROJ_ROOT}/paddle/py_paddle/_swig_paddle.so # TODO(yuyang18) : make wheel name calculated by cmake add_custom_target(python_api_wheel ALL DEPENDS ${PROJ_ROOT}/paddle/py_paddle/_swig_paddle.so) -install(DIRECTORY ${PROJ_ROOT}/paddle/dist/ DESTINATION opt/paddle/share/wheels) +install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/dist/ + DESTINATION opt/paddle/share/wheels +) if(WITH_TESTING) IF(NOT PY_PIP_FOUND) -- GitLab From e84e69e1416c33db368fcace76d7db1791d6264b Mon Sep 17 00:00:00 2001 From: Yi Wang Date: Sun, 7 May 2017 07:27:32 -0700 Subject: [PATCH 0208/3256] Update Dockerfile and build.sh --- paddle/api/CMakeLists.txt | 2 +- paddle/scripts/docker/build.sh | 11 ----------- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/paddle/api/CMakeLists.txt b/paddle/api/CMakeLists.txt index f2c56a4a362..1cec77c0cae 100644 --- a/paddle/api/CMakeLists.txt +++ b/paddle/api/CMakeLists.txt @@ -92,7 +92,7 @@ add_custom_command(OUTPUT ${PROJ_ROOT}/paddle/py_paddle/_swig_paddle.so # TODO(yuyang18) : make wheel name calculated by cmake add_custom_target(python_api_wheel ALL DEPENDS ${PROJ_ROOT}/paddle/py_paddle/_swig_paddle.so) -install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/dist/ +install(DIRECTORY ${CMAKE_SOURCE_DIR}/paddle/dist/ DESTINATION opt/paddle/share/wheels ) diff --git a/paddle/scripts/docker/build.sh b/paddle/scripts/docker/build.sh index 3b458b88646..9739ec9555b 100644 --- a/paddle/scripts/docker/build.sh +++ b/paddle/scripts/docker/build.sh @@ -128,17 +128,6 @@ Generate /paddle/build/Dockerfile ... ======================================== EOF -if [ ${WITH_GPU} == "ON" ]; then - BASE_IMAGE="nvidia/cuda:8.0-cudnn5-runtime-ubuntu14.04" - # additional packages to install when building gpu images - GPU_DOCKER_PKG="python-pip python-dev" -else - BASE_IMAGE="python:2.7.13-slim" - # FIXME: Python base image uses different python version than WITH_GPU - # need to change PYTHONHOME to /usr/local when using python base image - CPU_DOCKER_PYTHON_HOME_ENV="ENV PYTHONHOME /usr/local" -fi - cat > /paddle/build/Dockerfile < -- GitLab From 1cc60626a34241e3f6433120b1fade6eed794896 Mon Sep 17 00:00:00 2001 From: gongweibao Date: Mon, 8 May 2017 10:09:06 +0800 Subject: [PATCH 0209/3256] modify by comments --- doc/design/file_mananger/README.md | 84 +++++++++--------- .../file_mananger/src/filemanager.graffle | Bin 3819 -> 3442 bytes doc/design/file_mananger/src/filemanager.png | Bin 57379 -> 54827 bytes 3 files changed, 44 insertions(+), 40 deletions(-) diff --git a/doc/design/file_mananger/README.md b/doc/design/file_mananger/README.md index ea267e64d4c..00ca6db1725 100644 --- a/doc/design/file_mananger/README.md +++ b/doc/design/file_mananger/README.md @@ -1,11 +1,20 @@ -# Desgin doc: FileManager -## Objetive -在本文档中,我们设计说明了用户上传、下载、管理自己在PaddlePaddle Cloud上的文件所涉及到的模块和流程 +# FileManager设计文档 +## 名词解释 +- PFS:是Paddle cloud File System的简称。与之相对的是Local File System。 +- FileServer:接收用户管理文件命令的服务端 +- FileManger:用户管理自己自己在PFS文件上的系统称为FileManager +- CephFS:是个POSIX 兼容的文件系统,它使用 Ceph 存储集群来存储数据. +- Chunk:逻辑划分文件的一个单位,用于文件分块上传或者下载。 +- Ingress:Layer 7 Load Balancer + +## 目标 +在本文档中,我们设计说明了用户上传、下载、管理自己在PaddlePaddle Cloud上的文件所涉及到的模块和流程。架构图如下所示: -## Module -### PFS Client + + +## PFS Client - 提供用户管理Cloud文件的命令 - 用Golang写,可以跨平台执行 @@ -14,66 +23,61 @@ ### Ingress - 在kubernets中运行 -- 做Http转发、负载均衡 +- 做HTTP转发、负载均衡 - 注意配置session保持,以便来自一个用户的访问可以定向到一个固定的机器上,减少冲突写的机会。 -### FileServer +## FileServer 功能说明: -- gorpc写的HttpServer +- goRPC写的HTTPServer - 响应外部的REST API的请求 -- 在kubernets中运行 +- 在kubernetes中运行 - [RESTAPI](./RESTAPI.md)接口 -## 解释 -### 为什么有chunk的抽象: -用户文件可能是比较大的,上传到Cloud或者下载到本地的时间可能比较长,而且在传输的过程中也可能出现网络不稳定的情况。为了应对以上的问题,我们提出了chunk的概念,一个chunk由所在的文件偏移、数据、数据长度及校验值组成。文件数据内容的上传和下载都是都过chunk的操作来实现的。由于chunk比较小(默认256K),完成一个传输动作的transaction的时间也比较短,不容易出错。 +## 文件传输 +### 文件权限 +- 每一个用户在Cloud注册后可以申请分配用户空间,系统默认会在CephFS上分配一个固定大小(比如初始10G)的、有所有权限的volume,对用户而言就是自己的`home`目录。用户彼此之间的数据是隔离的、无法访问的。用户的空间大小第一期也不允许扩大。 +- 公共数据集合放到一个单独的volume下,对所有外部用户只读。由于其被读取的可能比较频繁,需要提高其备份数,防止成为热点文件。 + +### 用户认证 +身份的认证来自于用户或者程序是否有crt标识身份,以及是否有可信的CA确认这个身份证是否有效。我们这里描述的crt涉及到两个部分,一个是Client端程序访问FileServer的crt,不妨称之为Client crt;另外一个是FileServer访问CephFS的crt,不妨称之为CephFS crt。 + +- Client和FileServer相互认证的办法 +`cloud.paddlepaddle.org`需要有自己的CA,FileServer和注册用户也要为其生成各自的私钥(key)、crt。这样用户把CA、自己的key和crt下载到本地后,Client程序可以用之和FileServer可以做相互的认证。 + +- CephFS验证FileServer的身份的方法 + CephFS crt只有一个,也就是admin crt,拥有所有volume的读写权限。 FileServer从Client crt提取Client的身份(username),限制其可以操作的volume。 + +### 分块文件上传 +用户文件可能是比较大的,上传到Cloud或者下载到本地的时间可能比较长,而且在传输的过程中也可能出现网络不稳定的情况。为了应对以上的问题,我们提出了chunk的概念,一个chunk由所在的文件偏移、数据、数据长度及校验值组成。文件数据内容的上传和下载都是都过chunk的操作来实现的。由于chunk比较小(默认256K),完成一个传输动作完成的时间也比较短,不容易出错。 一个典型的chunk如下所示: ``` type Chunk struct { - filePos int64 - checkSum uint32 + fileOffset int64 + checksum uint32 len uint32 data []byte } ``` -### 关于文件权限 -- 每一个用户在Cloud注册后可以申请分配用户空间,系统默认会在CephFS上分配一个固定大小(比如初始10G)的、有所有权限的volume,对用户而言就是自己的`home`目录。用户彼此之间的数据是隔离的、无法访问的。用户的空间大小第一期也不允许扩大。 -- 公共数据集合放到一个单独的volume下,对所有外部用户只读。由于其被读取的可能比较频繁,需要提高其备份数,防止成为热点文件。 - -### 关于认证 -> 通信各方都需要有各自的身份证。一个公司可以自签名一个CA身份证,并 且用它来给每个雇员以及每个程序签署身份证。这样,只要每台电脑上都预先安 装好公司自己的CA身份证,就可以用这个身份证验证每个雇员和程序的身份了。 这是目前很多公司的常用做法 - -身份的认证来自于用户或者程序是否有crt标识身份,以及是否有可信的CA确认这个身份证是否有效。我们这里描述的crt涉及到两个部分,一个是Client端程序访问FileServer的crt,不妨称之为Client crt;另外一个是FileServer访问CephFS的crt,不妨称之为CephFS crt。 - -- Client和FileServer相互认证的办法 -`cloud.paddlepaddle.org`需要有自己的CA,FileServer和注册用户也要为其生成各自的私钥(key)、crt。这样用户把CA、自己的key和crt下载到本地后,Client程序可以用之和FileServer可以做相互的认证 - -- CephFS验证FileServer的身份的两种方法 - - 第一种:每一个用户都有自己单独的访问CephFS crt。 - 用户访问其空间时,由FileServer读取它然后才可以在CephFS上完成操作。 - - 第二种:CephFS crt只有一个,也就是admin crt,拥有所有volume的读写权限。 - FileServer从Client crt提取Client的身份(username),限制其可以操作的volume。 我们选择这种。 - -### 关于文件传输 -文件传输的的关键在于需要Client端对比src和dst的文件chunks的checkSum是否保持一致,不一致的由Client Get或者Post chunk完成。藉由上述的方法完成断点的数据传输。 upload文件时,由于一个文件可以是多个FileServer可写的,存在冲突的机会,需要Client端在Post最后一个chunk的时候检查dest文件的MD5值是否和本地文件一致。 +### 文件传输的优化 +文件传输的的关键在于需要Client端对比source和destination的文件chunks的checkSum是否保持一致,不一致的由Client Get或者Post chunk完成。藉由上述的方法完成断点的数据传输。 upload文件时,由于一个文件可以是多个FileServer可写的,存在冲突的机会,需要Client端在Post最后一个chunk的时候检查dest文件的MD5值是否和本地文件一致。 - 优化的方法: - dst文件不存在时,可以没有Get的过程,只有Post。 - - 文件的chunks信息可以做cache,不用每次启动传输都去读和计算。这个由于比较复杂,第一期暂时不做。 - 小的技巧: - 可以用[Fallocate](https://golang.org/pkg/syscall/#Fallocate)生成sparse文件,让dst和src文件保持相同的大小。不同位置的chunk可以同时写入。 -### 关于框架 -准备拿出一点时间测试一下用[swagger-api](https://github.com/swagger-api/swagger-codegen)生成Client和FileServer的框架部分。如果框架生成好用,我们的精力就可以更多的放到逻辑本身上。 + +## 框架生成 +用[swagger-api](https://github.com/swagger-api/swagger-codegen)生成Client和FileServer的框架部分,以便我们可以把更多的精力放到逻辑本身上。 ## 参考文档 -- [Do you see tls?](https://github.com/k8sp/tls/blob/master/README.md) -- [s3](http://docs.aws.amazon.com/cli/latest/reference/s3/) -- linux man document +- [TLS complete guide](https://github.com/k8sp/tls/blob/master/README.md) +- [aws.s3](http://docs.aws.amazon.com/cli/latest/reference/s3/) +- [linux man document](https://linux.die.net/man/) diff --git a/doc/design/file_mananger/src/filemanager.graffle b/doc/design/file_mananger/src/filemanager.graffle index 4a4338488dc1157745023790ed576d42ea214f1d..069882a1e93a3471febd250f1b5b8acf894465d4 100644 GIT binary patch literal 3442 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 diff --git a/doc/design/file_mananger/src/filemanager.png b/doc/design/file_mananger/src/filemanager.png index e6b9bfa48ea6371569c2d8458de55758270e64c9..e1926fc6aa43afe0a66a86c9815e3ee20744c161 100644 GIT binary patch literal 54827 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 MN-9Z|i5Ug{KLk%^x&QzG literal 57379 zcmeFY^;^{6_C5><0@5HYp_G7hcL_*KcXu}Nv7e3`e#KxV9dicos%VOrft&c(@;{)q|+R~!K^7BOL1j-cr=%GD!{ zlQm(J-rZwdqj)nHi8;8AB%`iu0y zFhR1!*SRX}f}CSK<;q6;3|0Yq@m=1VuGU+lZh3tS4%q^>2jeG0mcEz6wJ!yRJhNxN zhx&T&RE&8ORbms}oTw*XM%=CGm_;j`V5V)o%On9NuMhT3w;&%}z zwxrKjeJ97Uct_V!#1aIoFBp+y0~JA!TqKy!QG%$Sz79%y{|H}# z2{*XjB z66Q%fiDfE4Ar;?@Wm!k|mb`z<{uHHK937VmUxNG#@GYVSW|D+#F4;Emwy+0E3;LNj z&X?ylm_DffL3pW1lcttBZZlSkf{cb5d0 zgO^6PX!is`if`#s3EI%ckwu?>d@ET(XYk?_-w3xTTP*<<1K13wjGKEI4(u9Au5;H{)vi3)j`x$=9DBTYtus&RJE~ zRmM~GdK;~-$B6UtuvDXrcRG|zNvZhY3+Qc?T8^5S+!$>noesXir(ygJ{3!f(d}rEE z@p|!<@yv8wbPBYiZx-YHSfmptlHF3w6ddE3QUuv}sCk|%CGaL-Cn_hgCRQeOFp4mR zq>LvYC9Vvt4`>V+B#{<9=f+AGijsoJ3(2&~kmswjWVd@>RjlRM0d864=-8weY$ zOkhcAe<{=_PBMo{L=&p|lYe;I+AmH$**z&D0m7=y24=oc4Tx)t@XPe;@C)(Fxg|y^ z3NDtc`OX?jAIk8VMB2Z*@UuN34x^E}Uwe2*pDCWT>V4j11SQiq`D^)a^1AX#@>%i( z155*p14tWp1KLT(;8u1$EB?fAw>V>6(rNoa(8CLtdLr>GA0? z>2LN+^it*$TcUkZeWHCrt_iT>!+7MC0_2GIAdY|-e>ICaV z?VvWYHqMhy6O_|&VACCo-Kia6P~om1v~nCc)?WN^vUXYlBvb+h;z0Fx7JapkIEs#)W)96x53_HfUniWMwlrGe|H{Y3gw0@+4@< za=+PjI_wy4pMG@o>%8w``#A9k^W@cu-)`YS(z50j&w=Ac*2cna$l1}!>;_ktnw6^# zPlc;7`BGHJe!$aC+)mC;>-+0_Nmw!he;1z#plgjzPvKG_9Z7#2Z2#NFvAi)^(-zab>IRqoUmGhtD^xVXFFw;ercrvq zC$ku>7P%_TBfFOWBfmV4A=#T1(&}K*Jbm1@amVbTRiW?dUbs4tm_)2CS7}xyNDvbB zDZDi5Y5?0pyPCmF_baMdufRd3f2Otp@d4$5PNunm>wLt!-pkd?apc|)3+U#bMn2Jf zs`5T)<6MN$<`n#pqe^&`5W}ES{JMa<0AI~qZS=(Qvj6scnyDNNJisGCJ4Oz%-vjR% zaGEbyW?%32$GENZQQIie047?n@tmF0DPxG9BUP1-I(5j`5d+8$W%~9Rd zSISzD$MsnHyqJz23-3G#>KMJ$Ay!et%^9*a?X~5Nu?@7&S1i5U#r#`8K;b}^=7Hwf z<{x73U~f|x;uwa=XEv+%f5oIyd5d{DHqLYe-dy-O-2;&(gX4p%LNmYXi>2LZTu+`x zUs4!KgQP!tt$G~;&JW!dH4R@GX6g#J37G)aC^xfjMX1gz=X`5_)~x>$oe&-G_@)M% z*t|=>0UW^el664Z2U?3R;yYdEkEn3maLy^+1T+G5u3WyYu*trYJ)k-hozuxRR8{*_ zK2+it@ak5lceFtatih;7sNpX(A||RsVX$CiuCA_LuA#4i3jVa)qx?#2+<#)t!o6Mf zZv3%~)Hg;Kd5_%d5%W2A^aUl)N_sKjU#me!B0-O}4YW ztBt@#`B7CbTZ!Sz&X>MlxAAw>4hnY$(^44pP=Hlcv<9S3`qSszcI(r=`wEcO$}7WG z*9GYHG&VSTknfqZL6dFZ$t?>+U#Su3%;%-&nSC?7+vsixGK>N^9B>05o{guk4w|Q+ z^S&FEy$jF_oeQT6mL2F04zyFO+Rx;d=*9hZNFu5;m6dR&PvRZP4L|JDR6=~Qyb5yw z_@>=-%;(o?Z13Bx+F0wDFo7Rc_it|2pl=S`cTeJp%h>E>hGp3zy`wxk4bI)xla{Ba zplzLUu>CIb1wLz-$5pMnu$$jH@Ynun>-UQ%hcSWGw`06`+(#aqo^HG5_Y9#b(>ab} z0KZR{$7Xi>+PB~dXP+g#jvN0#;VeNB788uwS|A*CJ)^?u5~@@AB7-Onef*x5157je zBpnL1M!#jqhUQyaVu@QaBjnB^T;{I@cudJiO`yD|=9V->a4>xy9r%)E@w?wh#QcW_ zdb-KU!|_GjD%7sVpq-u4qMaRQO`ESch&EZ7&|z!Z&~un<%~0F^t&n!VWk}%o5~3#s zisZ(XJ2nD>ID*`335|D%dkZ)QNn8FfSR*5?&1ZByr81@IbPK!P+;PEL`mdpNSq+BR z3uX{$19R|uXk!f80zSr)@qAV4&u<+|GPj#4dHNCd-3(^(LOIv{c#0n`66Dzehkqd-z9{ z+C}UkS#)$! zAzcM>3G4hL{8O(9|IoqjLmKxIes1t(LwT_n#i`BTkg+~IxvtOQV%D%U<^Mfaxa9By zYG(dyt%!K2FFYf+DE~pjU0>kh$CfJ=1y6AM!l&YL4Ihdx;m4SS%G!>U1`&zW99fRz zfml58cddz_2(T4FUpP3fc$2lKL@Tp_*G=E5&R67=f~O?P z@TOO$3LPFOh58r;&EoYv$@upG_z15}f8qZ}U%xdME(NdT%D?SF`k+`GA;^Xh-W!mH zxOm@6MkW(mctPgBnuhejq9M9i>Anw}0!0l`DlaeQqg%5o# zTuJkRSRDCx?JkL2IVt#eBy(ZR55>apg$VzsS`_o37(V)+;qWn&koLXXdCbMRYFV(u z@Zct9Zxa8$Tr3bFZt=VQA=g~ZuZ%9;gxYh=P)Y%fA%l6C~{Lht!R&cm5&@Xja68D z^uukLLMu+a7C+O+tqO0EUOwCB;TGt_g|8LqnmDrze0Q6FcuOnwV6DtF-iymbVmnlL zMTpB(M8CR|d?1#ghgY&tE_hf9A6XFE?{*PG#0g|ryCNgw^yB_#^GL&2;46O_$2A2iAj4L=ptoC?0hjtsDGYz8=vT!+)?dO%VY2Dz$>*gFnI^Xd;$DZT3=Cq zOp60WCpX#QU#j{X9Z=={>E8qQIu@=7G&0O|H&^&-5c9o2K_Xy{mw6jz?T6<^x3krNc_F}LGOs*t2>%c73vQg?7UU*hutj2d{3UVKsHnOQdnd# zOS*HwLXn_0S+{r9Cz^Z2 zfR(yzfVXkW;-subV%ADIII)&Durb?Nl>C)-HSt2!hF(T??l7d0IS|-6oS>G}?%k+- zR#nxm3ONBYiA;lKAXW2@NrDSiV{xFs-CxR<#Toi?4uMJn(yf6NDQuiSa@zws-5wR) zpVwb9q(wI2>}m}kys$*x<=wEbAl`B5o2j#hHQbtrsdF7`diMdy$Hu-AsXH|@^4TQh zIwo}p-@SX>_H2v@s;f~co@3F&w-l*hX&!-nI0O(Q-+}A_S@s2+MC$y70O>7UZc1YG zUZCOk*vJ1G8hZJzmA~&^6I{d-m_kUVXC_E0G`ii2Dowy!S=lNb=3k_~U|QobEokJ< z0&D`Mbe50w9~W!u6(yu3Os91IOew1TX+KhUb&P69p`)x{uU`oSC#0>dZ&*Ga{JQno z9)ctwl`XU@e_g2H?sj~fsTfmrC(rDWzA!bc90bek&9EF8ahfnx%__Udd@vlwce3mIYq3|H}8HMEH1M=Sl{^T{p6a;(xgvc|7qZg z=-4iQhjLU4|7h*?tAPttR3yjadI6L4 zooAdx9Y2tfp7=?8l04ZEi8cCh`i`wek^IzO`Lo0dhP!h01(^&{Jh=#5%l|9vr7hzXlvMmDo)o$ zDt(D)QMp*|zw>Eo8qPWe)8Ni+evjT?#J96(6Goe$o#_@prgnxp*umlBr-tsb*{-O|FRv{X_y~jBy7i?)5o*OvMV%<=(mH)^+74H{n@4KNlV#3T=2 zkfJImWUoBt^wMlEIY&KoQSm+0spyK$zPeL5XucRue>P-hJaqpBR*UbJ%=Zt*!L34q zW7dCQWfvV9t+80|-HSQba|4@$MWPchY+)|2fbO)-8?bnH9H8|G=CtJ5!jsDTOS7F} zUC7mD`P-eSAwUxN+_peF@XoI~bH!+=mG_tDY|cO;*Fu}(P2Y=p&CMNN@}Iwmw#D~d z>l-}#cS@47pm;O!9<*l+OD^R@b7aRK0&w`OeWp3y;Qlapdn|06T^HA`9UxYe3^C=El@b@l*l1d*nOlPUkJ~(buIdrbQi0wJLrW~)A1n1#<0@Vg5U1b7X*y+*Q!Wvkxi+1~VOZx~>U&Sh9=aK8Qi%XbxfCH*!xOJg-feum z7+kyhY~=N^O`oObRwJTnk`HBfRmdeT|+Smyrjkp#c)mO!=iTmqhU2+u@dJ@vMo~9)7)1d@qsryBk@Lm!#YwAU- zYpI&<;+*!M4>r1N_3Z*O!#D$Hi`YWDyOrhSB&rvr_Vp&jRQj|1m8ETS1Hn&Y%!#Ea z%?g0V0nMzpr{|+{{54&)LxCQe+o}tz$S?ut5ns`ct0EmD~q5XB+Ol zWldfEdwe}~QhSQ#wb*Cgo)H_U|6taiiCxQ`g*_U?JD9&qx(d-S@4Bmh2)uoUhCIV| zy=fXuM(BADCW;M-j2s-+7a%i$?2x0gAg31tny69o_<6{0M%gU-MU8@XruBGn&S3s`)M*U*F zoUZfo28G_NRYXzV5>@c#Qq51X%^>k8O_!EPTV>@c@@pzN8BjGmA%e1=#a@)!fEY3*CA>uF|j z<^^&FYmE9NaeFZON=ULhf3`j8%CEW?zfq)80oc09f4)xV`hNXL6g?q{Q^y)r%V`Pq z7;_t9bB+5d-?%+%e=|7-v51&F;#U&4GHC$7}BvmC|EW%@)>u3_Vc>x%PIe zL;`$`PJf^<6wr@c>~VN2AJTF_h*R5ighpC14AK$0*}d5+mQG!Jx`<+`?-hQHOCLuT z;DjV`=;W~R5`}mhZFA|Ajk)I!Oit4tws;o3Z)XxZ#ku|&Tzb7f+5vHZ3Z0TPE^BB5PsR`@ovxtEvBWT$9BIM7 zu1qTNGAKTkYV{YO`s7Vq5o>)b&eO7C?>97eLbi7!nBJhQx1PPKES)Z4fJ<`G*I03L zmCw@$ZIQpz<8rB=EkJm7^5$}JsWsm5@@(H>^l|ORPuya)$ zSXS1e)!0=u@WOKIP|N>LV#B(?PHmU=q;IU@E~?{6E4gN322i$B-+EE|rMqE++X(P{qlZ_1U z^+^#^Yl%VeBku(yAJ2leH#_$=3tpO_n?cGB!;aUtuOGwq6HkyiWiHiZa zEI$JS>IFLI=9LW%^zwwO>de>dhW%VRXBWCC+Xb7h67*Vp@<%K-OY15$yt4`Bmd{?A z>qljnm3TE1U;PIf{vB9hUqPyf<5-cxeT20wnituxFs*7(9)j}<-6scuZe9f3rfF1lTBs!cyZ zNg$1G`r4?W3ajBc4}{YL5{6A-CsZOdj6_A;FbGBoc!BGTR3U)tOhdVFw{b$YuqF%r z+nAC>A#icZdnIKs>BCrBe=HJ1b-j z3@fOl%c={hlou9u3oDlcuFbgICFicEPIh_0j+J$*O8yd<#$FfJn_Hr@huOQd{_N8h z+_Ev=&u+RqTdEFqyxQkfAT`5l9<7y~o=F16g~nOG8m+}!=M``Nj7t6vEu#HC`r)SG z-7-Uy^>QH>_9ZxmV0*T~_F0QbCMB-Mj|MJ^V&+KrR4yu22BL?-gA|*J*Q>M7x2)OP zf3(4Jb#2!_A_(HmgpDO+G^>%EP2yB+^vyJ)x)V-`lqgnye4t$5sC&gJ(Np!B*aG5wVZxj ztOI0v^>!_W96th2Gbhuy4ATQ9Z@Wmd@xpb3oc-3weKymqJ#O|J?Z@+^^_pCAojR_p z481p!I3U)7VgC+-8dv{!(8^vozl@Jp#x&+N?7Uo#k5#qEyR-brGpXp+A!MNb?#l%6 zqNUa^ZhIGtW?j~u-ZMTHo-3v)iw0RaU3MCsp3(sWmQyE9Im3@N#0;&Cxib+#UMK{| zNI@jH?+%)`PHS`S6j5;~r>3=yS$bVhKZ@N-9D0xX&cE*oM!`4*1YQ8H^Tn?7Nw!%G z|GOmN(uknG@NQL#Ls66$`1uupYv29-=Y-cI2}1RqMg2oKdSB+vYGZq}szlRT4Q0tP zGydU8gkWpUzqan@a!&+_zm+&VAgJj ziCPDfoHuzbMk?)Nhm)LUiXt0G@c6kOpKFpH|59oK*uC z#|2<0GmdjJqS>P%N6T6}hA|-BssQsVlWjFmTexM=;-XV6r9yCgf*DOEWMKhl47&#V z++S@ie8e)!6!es*x*q#@x`Gp^6G6-dTDl!xDrp2a$}6g=#nY=~S2zdWK(6l3i+|P`L%@lh^aO-YO9PR=ZxzeM6mu&ovU6Hs5kOs5LLx3t`>I823uWx zVPlDNtX^_vf}v z|4mS^2f@!UJ#J1JUN~)##pqp`$g=8cFhut0&uT{%k1f-o$)~UpuDGe3K8HaILrbz` zUiSeyzQba%yHcUSRrj2m%w$$Wrq+gTn;29ozqhcHzPdo(hOOlO5oHa02ii_DWqVV0H#)7&1}q(PxWR=0 z>^HT|HAYdWjf}qTh0eC1MP3ousfwm1cz1LXNV5S+jN4^)pD57SAWDKJ>bhtyL@^Bv zgYuVvT+U31&8+&(S3;Iv4W2P85H3ICTj>wNo{dvB`uBR1TXF~HYS()E!hQoeJ;p}f zQFcs?j0?`O9lXXL7kbVKr)Q^@7&RgGilqIuH~5%WWndi=K;UeGTCL|VjRm$!*C*i(U?om92e1{4?hfYP{anO-tZ57o-nFG7#@ z=i{~V0^iLT>o=6GUDx}V6i#V$NGNC@#`b39$IokIiL_{w^HqXC*CM;u#W=ge z1Xbl%kysXebsL$=ZKpLOsfT+B#w|L&VFwZVGhI|!vZtZ5jcG2Q64iC`EV)MQm)6MZ zoVr=-wHkMGH{^VP4Wsl`O|MC<152?o8cOWIq|=^`V6)n6d%&8Ll3`O%q8XOFhY}PI z*y&hV#d}E6vGqKFlC?#anKzwC2ZMlJ@MeCk#lDgaAzrIZYZfr#79!M~Ks?~okYG7? zHJ)aDRt4qVt^CZwWWPNY-#7w21IMHRKlt~OGeq0EL7Jc2t#kje{m?KOmtCmei$6v4 z;4d^1*rcjSs4cEE_RYvba#ywu9qG^Rn54k6?5HDT81C09<{#cX6a1(rHTllL$obQx zsR{rd;64$B2e{kNr&88t@R4LaP16|}CQG5mS+*~SJqCdoAd&cU_ zY=J_od-;XI-mGWtBkC?)Z5W9un|lI_DMjAKK^av|4M5}cA>5FLIeb2)G3q+IrPfXV zXlSD5ru%VmDeF=FAk;@d+4F1t5kI}nxVHSLYjKx(l){~NYo;0(kuBX_Z#A1h)jt89 zzwqkvD$=|jr$zWj47Ar@-WR{Ux_E`nThUmkys??ZFq~hSRXja0VMbXzE#&|8c&PCZ zs3KBzFrw{IJuoEoa%3`#D_A$FZP|!$Gwp)Hf9u+a&fZq0$h%b&V7+d4aZi>dxKO%* zL&4pu<2RbMVlsK2d9lDdZtgWu-y1%+reH@#F_+S{6Euo(aF<gAhito5F*Ub zGU&ehhzs?0i_^A+C~bY(DH$1nr_w+FnQIboqhcdBDYbNr{byE@Rrft9X^Nt(3^KhD zwd;WIyo^P%MmZ3uISd5aIr8G1_|@b}^a2*jEnrlz;)|H~P0c=MtM2st>^}i}iwCdo z`rC1KzO0o1&kD*el9=qTS!;Q_4fdVLXqn6go~ioE^lNnv694RD7An|mz&<-YO(i7* zayAZP3MG8kBFJ>W&qToUTXB{yCd=mGVhm$ZE_o&gP}2U80i;^pH~JkF@jaNO z0`io;MT8nSWTYz|8ml1X;xGGbF6rR^V1C ztzNjWi_g`U^IaXZe?UE{#KWP;NAF|lbF+tPB9rB*)w_jTv zcv09osNJo&0q_W7=9Sgvm}102h&s%A*^bf}qDvX#b< zc{ZUp*i~+ghuZ!LjEz>E4F|s#K5}|~W^?1XYd=xked52>pEYM}-hfY`>N|vwm&=25 z;#9Nfo6u}<=IF?!KiW{2Ibm9hWw&Zj)#@ZojplxIyt!RKtF7xUyxp;ZYQW# zx+j#Jo*^-08Z2y03UARbh0riwG zP>S)wse>-DENGc#7t?XuwDX+bK0v=PHKNV(+ViOZb-?KH9f3-!dt5pZ zA9**%HqLS!IITC#%OK{-S32#gjr+$biRyVG_UE=|0R$mC<)^{g5r+$v4c$2)G10HG zZ0)D<@k$+Gw=s63L!sYvN##xsHSK{Oun8VLfBhvvQt!f#Lk{sM_nZl5)=MwP6Evaq z0ZO5q3gmTe{S_FyYsi1zbC@jb{Z0;?rOr zs&XpQxYVxwISxVh!x!Zh_Vw~qtwQU!Ba!`jfbYcb-=MRwURJi=)y=i+c|^Td$`Fu} z`{BQav&qTzEX+Zr^EFMtZ29>laJNPR!yQBE7!AU1b5vx=0_soXnVb)EAJ1zcufM`D z25aI7mwueML7|w#Ve*_aOxIQ`Z=Y4T)|__qVzE_^afEVDf-pezJ1L0|crfgCK@s=Z zV!QbL@5M<_Rsk|S-KqrfYTWi9la+AV1@5K!)9RjbawpM(`A!`xC*GZ@Q3V*)MFXmH zKLyz-;1=t$_O!Z-`ri0HDXyb_FDR#=b`JVJ8e)dKHc4<_JNzT^K9G1V#g%x4X3@uDaO0g&Y$*w9^WPhxSM9c`y3^*SX@ad zit^nu;;ePFD%vS;>2@}G{nr=VF$~2TDq;aSRv)rA7Z8F*dfdE;E3p@4@S6;(izRTD z2$~#eU6QT;Y!s~+9?bY&sxWK*n{A>%tCU*x@mx?3P$pNEB~b2eg)wPg*{O@MY4BYm zLcu|j+gicMB?kN4V|(f-LD2m?v)R$&vy)EU+gesAWr__^?w`CN#Z35J`>D2gQvptC zaYeVes~Jk=GPYvSj8?#V^9GcO5`nT*xEzQ2UsDtGmB8d?`fF`Lfh)WU2X>#v!w}Nz z>fgKz%4g|bx$qwtO8kH2Gyda7{7itCz9ZWw*=B~*ib!6!{73Q^yfp}eiwC=CnE%|h ze@P;%;F6=WqK3hzCNe#iNXjPXzeCO!$|jFW$o@M#L}W8bIIBky)BM&WSiI+5lxLM* zx^k)eP#JWpQH)13;cymsz`>jl{3jx!ru?1fS}T!t&4IgEqs!BWZL+n_ZTf6chO|=J zN<5B-+Zy0Yk08AVm1KTb+V1MHYlPG5M*HZQW;M7~)ahgV{n5>hY3crC%85Tihclh> zzeR^u;`fm(y!6TutQfYRv|QMup@EY~if<=hwPa}2@oFc@M%w@a!qkS(rr2O?jw65j zCxn{+MlmftrlZ+CtL(g-w9e=1=XBlYa*DNO2O<&=oDU18FdfS)09BrRnoL48`Wt8a zxf=jbhG^wdu*oH_t_8~m_oMZ3f6&Ia(kh7}B z%s}<0?hO>-2C4f)6XkD)m5k`K1*Hu=Q9vl~zF>ZEyjl{`W{DY8W`P%6{)3lj%w22r zV25w;N93p_eRM`VEGxlPnZd|M*ShBy5y2Ap&S`(?R&JreDWPm> zEeUXisjgpzo$jun?L3t>B%G9-#5C$IN!~_j=}@jZF?6wW_L)NO?q^V}C$F-mfA6Vl zN((`8lVe946${h#x}rDV!B|_HSyB?wpu5#a&_exN9Ib04Iu=wuXb+)%-PunE!j$CZ zN=+eNyPY9et`8AHvv7dhAM87Je8i8Bf?ndkJL)FRl8zvfR*hgNE2U7jEqc}ueP(lj z&YQP&N$IW}-B>r`+G3ZbcrsTs3ut3b?dh?0q{;B$LN;<)-9W~?A-OwB-jm zq=A;j$h+~-@Phf^WMJbYSo_EIcdT%YRO+zKWDp;!##TgXg|Q&`0}HEgzzIZ)Zr(j* z2on>?HlOc!AzGo$++mLIbJ~@2f%2MlNMM4?@cK?Q0VE_l`Pf2E*QFTgYt%u}-Q2IO zatkw#XMbQ(YIelm0dd4_4euSKulRlM;Gs@73R7Bv1zRNg70pKk7iCAk0;ajIh=%sq zQ`o{iF^4#14*e5^Cp7d7i$Aw7~}j*~Kk4I_riKQrV!S#T{l5%3S_X0jCVG@H484)zn#y{y8Vzh8S1RJOEhiK@CFXD zrMPZm+}RwaB}XOiARNXmx>PV`0-0D)s`nl4_bal68Ez8()=+lzCzEV8rj8T~CR zKPt%7>LqT-ZmbVL9i8n;SEIiM&kS3FaL=fTMhMsEpX0cF{NBd}1A~M0u4^)d#l_G$`>N?O&GOTHs=GI} z)}x^O7|}AYk+0*^_Kw5?_e8^$jAnLU$2e?0-qNo{YR!v;HGtO+9lfvd!UT#s!HZ66 zxrf5d>Nob+R!ZFI6q2ASqq5Vy5aJ;{k|O?#3GYCp*1fQy&qO$4{^Fagc)$}sX&$TK zK6r@OD}+4bAiZ?B6{ozkR23DlXmoyGc(Xti2lKoaU6|le*5)Xd$U%?l{#K7^2`jU z7Dh9KKse)3C@CpvyQlAy z`33I1)JsaE;?vl`K%83pPw1%pAqREiTF~{T)}U0sY_nQ@*`@JnhF+FId#`7tSK}Lr zvxXWI@pQ;SgEUnlG+&#GYL(@7al#m>i2bsLcuB|!yTgc@79bdxfZWT-A z*$Fq#2xvKMk2BYT996FksSYfAkNlOB8J0JEN0=6gx%s`j8l~jE_|yMf*%Poh#M zYy!>bV%I~Vyp^VZ-Ii;Zh4v9h`rv@OC@YM6cK&ff1){d>TT!_`@yD_n9Df*;J-Z5f zie^o{c6qeM$wg(6^TYf0A`sSSXKybROjN9r15&GH4Z$+-yZk}rm9sTm+i3E0ZQR!B z<}z;6Z^Z~}rkUrKq&;dt*`S0X&2sm8vHizmD?RK(5C%9v62L~8KP~=aNyzSj>4|`I z#hcJ@Q)Jn2N5o~EovT_qe*TX8kMlW%KieA30apc_mB)VRE>$m1Oiw4nue*fjU1$Jt zGtWP-obsb*mJNA=hhOqfm3=&<73Wai)A$1-ivRpgL{H5P&kFm%73>*qeqBd3t(YccF^n{?)XZwUL1*;f>PkHR~weKXAc&@!Q|X zt&;=CH->>Jc6@5;X{N1d2(edG1hlGCl-A~}ML)iw|Jk%DAN-}mpUZ;~^b0-{v>1DX z6hX?M$jwbFfKo&k_6yim4y<&2ecfd2MK#G*J=lmGY%a0&khiV_=l|o`(~u$FkwV3L z$er8p@aw9-MZg0g5W~qA)ggUUup=t_&7m~BG2iru0Gb;2@8=FtlQnD%vE$?8D~ps9 zfrb%$OMWN8X+($cv%RK~;`HYf5s;2p;Nd;p(a+E;?MpZ-=+2S>yu5e`w`_Rh}uc)o0i1cuPs>PCt& zXtg^6z7sKsS*L!dwZac+u>Ln~PVzf#zD7A&KPN8Z@9*E&LUevN%(wImQ*Wl5f4`yl z)2B}yjuKlBw2O$`^E*^B142%T|8z5O z#k=6oH9}$}HPAn}H^^#8jOje+lv){nJR{#Nw`!&d$t6rQmb?G-e!~xfqv#&J#N!9u z1#QD$uyCv%?T#PDMDuf?|A4=0pi!)T`tb3{>rZe8NEH*#TKE4n%kJ^i#ym4oyoV%2 z?b)Arof_BjH&7Qb0ba)(I}W_zX-rFXlwl~eDk-TkY7cU2Ay3N5&K>9>qF79;(nz59 zhv2!_>+`fmB*ta$j;2)3=egB4kqT*McWUj}M@&6^Rk8qJHLoRW4GArGJJGXTKhSCw znD%Wkf+&gVe{@u^yVi4rE)gXZ{U)GuP565%%WIx*F5C+`CWP79f9dG+rH{q)HkXWe zIOC(Ia3>Pa9$;tPqX8`cW&O*F(K+sPDw(2;tC&;4?yP-}PHhX%QqizXoHL{4R~Msq z_B|JH&P?4WG2y~C>z!Q$(;lQwl84IgP1VyvXhW1HljHW7KSsO~@@n-&G4O4YoSGUq zcdvWX0MHInKDW`fN^6l%VP-p$ajd7avf7zE6ewoV2c!YizU5kp_-(zar<$#_RRFhc z)wsExUVj622)5JNK{9}e5|Fuyj}z9--mGV`{2^GdyCoP9e)f&d)Bfdi|1&i>fiE@A z72W)RMYtfl9db5f4)Lrzi{(@zZ3PjR^e88=Uu31mhWM^0YL@5sj)OO0#cbg#{pwO+ zRgwD{^Eux-B}-k(QoH>7ug8gMhxg2Uqd~@*9yt(|+W{=I8NNQ)JJE^q^;KAiK5KE@an$>27OFMhpur59$xv1NQqbD#-yn9~$n%a<(wLow zMiMp5>Q5MkfV2ib>*jT1nw?YS-@5ty(;xxRjD@kzlIRovpkVi`stkqST;0lhxq@u> z4pO{Q_XUWm=;_XA29(gJW%oUHD!!>6*SaIZtYb9BDL#>UKvv3lHGI>Sl7Qdzo*d^$onxb-AVd=Bom--Cd1lR-T#U zdq=G7$T4F0`yme#8=?od3%l~~?Sa*Y_;Yr5{kT{<0Bt@0^YnOZM`z<~lnKu1IU^#}7o~>t%D& z2n+;h3Z~Y(11rtQP1vEjio<={-j;<88`d^EKCK?sMQ$*MAU6N+QP&RHhCDwgFZ$M; zC^_{TA=_ahLyjzsBfr`ejXXZK_HAo5vOqL#2j;T_J3$$`)ZW0nE*sP>KNcOcgnjoG3r%j7M}q8zVPg?d4&&cDL?XK*d|4Ex>?~+-Ls^MM3A#V zF~z;8x4Zn=y4WIt0l|-~6jx0c_UPCnF@e(ZV_us(5R0B;a!d8c-i%@j6(Sosmlv|Z z$-XeXxteeD_-?If>ZUQYEH@u?8O(dNEd8(Ytvr_AO5OjnYtU$d^w7!bV94@6>j9sm+|?nyZ0MW#M-c)HG%v!@~Y& zE5Bgx=jd&k;{KKtc}2W@pj;0)V={HCiBFKB#M$M!5>ygRP-W~7+7M^MDQ>M*H*Eh= zSJDT&EO^ZN%aMP zW>BONLA9K`e7yhWp8yqZro+X4(|S5NihCcZ7heB05MIB8qUF-z9ly2j&*GQu4}IXN&SD#eh@$WQ7PUQ)q5MLXi%gLImg^X8y)$(@vmnEb?g{3 z;*bBObo_r@ePuu#OVf2Agg}7c!9BRU6WkqwySs*9!Ggo$?gV#tcXxMpcL@3oSH9=w z{l&7qGuzW$-F@m*)!BcZfcNpWL~^w}>>#zbzocD`TbK#=f42r+LHPq$+vLd`sl2Oe z{#?|A68gv9ELa6J2jJpjvi?)!=l@4@*Xz}H=*BLAZ!Xaj%Ky~@_+$Bc@=CtK>p)#l zsWh-1we%#d{FkPD1O5bnmaEw{2gTQq1?gr){4spIDnN~%k8ccr=W8`!a>M^QJOpci z9`xbjsj9wofM|P7qBvl{7DK3mU;_R} z*Xj6xw@IK<&1f}&&t)%0ApbFT0p9@-InML53h$4%0iuizNi-m?WubLr@x931yN=@_ z5a=w7fca0eil9JX^#TEDSfuj3p5H^{fv+HI7yWbmyTaep#&(VA^hhWJ#P6W!q|6_H zZ;c=50a~IBNe3{7f_HDmx&COz2Lb)>2#^?GM+8g=S6TeeohpM5Fgqp?k|peGb4NSY zpBI+^1K%}m(<^KYZP3-jHUaZT^P2o??PL(<|3$Gt(FQ-zA_>JAqZJAS5okohn*N;7 zZ?6@&C+ER~GeAx8plAqm18<`OC63Gj`+By^# zLgIgXM8Ir7nZVn&T{#qgjKk|nt>XfE6SpgJ0*wH~2_VuHi2pDn1cF!VGQc3hZJ zOF_|p_PXe`*VTw2-p{~aZ@!%1KbnDnBLst==lZv;*QLBv{>$)>X5KpBo<8dF7rlVj z*%KcnkN@X)|0`x;!V63`@6SJ?JiGvW1m<`(Uc@a7+CcCLVd76iUW)|ot-*kU@rSGU z`Wc<3*C(lEyVk6Eo#UkNSLL|>UF?TGNC7r1b&cKnT>|3vY!$Q#ZV>_ux0Se-_IJ=mIkVls}g* zFazb#92HkE|LYX+s;=(!BCH6)4&b>3=RC)-|Cmc1?ANCvV)^=~T>#_Vwhi>Aj_Zq^ zj3@BCByUnE{&*lSsMi%l@RpBJmz4eRo13*t?%Nr4>-wZHhx&(S$R5fJWMn%83+9Q!suv}+xeE7eKry;U|74tz^Ku)fgzCl8G-$u zuRupMv9g*8rftcK({@)aZMqQe+q`XkX^xDHN4BN{}Yu3-*)<|a(-i@ zdP+(P0pCZgy}g42d3AMlI&?j6q8r=mY2`z=Q&;UX+fF#vy{A);)=$)GS?X5x-Oac} zQ;5rOz~gaqFk<&402t=4pIj^=^X zOPTd~7V&O7r3q{5%AUcfo&6NCy^T z#r?sO)v4TY@Kf{U2&Y=5QONU^%!}f8C4o9)6%gO0KXu1SgS@6@ac-)Mxa-Z4t+kl* zS^N9#YZw3F-tKk4bYV|Br=d{F5QjAzzr$g^3u)V#`21f3N9S*&X{a}wrg#GKSjULM zxHd*w&0&nMx#1Z}R>F9e^BC zr&HI}LV%`dJd^^-MIfq!o^b7NmmLoQyR&x`{0$_-7UI3!^&&2=h+3h0fkbg}7FhBC zFi1<4c9Y1v^3j30UJpz%oYH2@;Zw|C9A5<0x0=)YO#I z!A<-PbCX0vsHp-uEEs^tV0obb-UkK!J>m4u(7$-^-6@WVL)=3c4JU55kdu?|b7H(N zU2)ok`-vXMJ_4veAcr4q0{n^o<8VIWf2_a!Q#^}hqPl^cTtv%NHf{Z4T&M$1grb(R za$L)Gv9)Or2*c%ST!bj!RBrYYIEEan0aHAn0rI~)0P$au^pXb$54{yt2;VXa^EciDjDOsLoz9i@)C| z{C`yG)rYzk2EWP5q7s(I%p6k8`!ZUhNxAuIoQ+bZU%$qTq>`#xoU*eV%wq%~SWUdlMe+U;Um&R%?G1*+5 zx~^f55&_H~Jp-w{(7);he`BOJI`GFx9@r6d?G6_ZAGy^Mgx|waDwp#F93(c+50&q_ zV{uw8Vn76=7;8)g$ig=oO+6m5t6TTO`Z@ZYVAuxNcqDn8vb1UAq(U$4ZCIvASOXvI zVr+LXn)&`jwiSXAKywRNm2+sUcFy~&kR|a7Q-So7uiKw{*ZN7*qW>6(xT|w4tfJo0 z#KayY9i}wRMyE4?=v~5q?+wG7J$1ir)ZtwRQt+n%j-;fpq(K~*eXF+2mrH>CqhU?A z*^W_e7zOr7D>csGjpEuBJ4fkqMt<A#WW%bLC^(>~09&RHVlvemacBPoYGeggpii3rOD z0giB^fzONRRKo`Y)Xs{Ej!oq&c(;e}rguV89a&O+hZM!f*Fz(%215)Dd{vD(*XM;1 ze$~N=*7+>*zoQgi8p1-hgXf3}I&=;d>J(0}{7`t5L>Ac0AvP5LRLr1je?_anGIhTK zUI<$YfBf&-=fzigJ?jTVd*r?Vf5&wuzy8yVu95x_Fe9Cq1v(aiY#mVr-p2O>c9X8N zwH~us;O{}-1ns|{4dKldTe5X$Vi$e&Lb_g4C+5&aT>cjdM34c84m_VyzYhZ1eO~G4 z^))FbC;<6m3J0?Mjr$#|2ch^1=t7=U25!{^1J=n3fCXEhn;a?b{j z@pJsmhx3A9KJQVyC_-DW6I>rHm-ON;mSh(DN1M8|n7etIEl8nnPHAZ)bVlX+m1Md+ zmiJcOnj{TWaLG>#Kk09iJFq_Wp>>Hedchf&;=0boVPUyV<`7MbA+6zmU;hJVDh zoG8z|{XOwRKP4drkxl-a_VZ2~)@vQGSKTY~?$AN2suVmBgUu4r7`>oI@dnJP(yZr8f z*(znTtrc(L$RtTF)AVo~{HsZ}yqfQ74(VY|SdpE!_1rU&M_q?oAyH!*9c!9s!l2d6BX45wt#1~)#(e-*-`V!stFjP+ zHh8=G%|_wk)xY8*zzBGq!B_wk4x?}OebMeRhhl}=2_&(q>s10fwD?3?A)940%aKZE zeranSw|0XR_4MkvAGH;_K)pu|Z-4)!o9XxBaU8RWPIGyOoiWEM_uzRX-&g)HKmmY0 zwj5{UOO{3kf|q;a-~V|G(YC0}Nal(Aki)H$RI`SYVe;vy7pE|2y*Yh9+`E1;&6rxg z!&<9xGpts+e`%M%Mk+rsg>ihhZOVl*^C+d$i|qb^LAmSLV)0b7Pm61>kbFZ@CsIF@ z?T~Rf#Lg?s<8kx;ezuk7Vd&o#8$pTC>hWW5Y6O(8HY-<9;(be=->7h`*(@HPT(p(d z{t`N8KJhJ)i6lZk(px>=2iD5bxbcUWyd*n897^lVuaD(7C5@HpVsGDb_HbYnr!AjI zS4IwR^^1$6KdxSh`jM+W@IAMa>!o;{=}U`y{o}+SeDb=qi`$bFR}v3`7l3bdV;O%lgmizx#P&t)H*sE7-tdAu77EPt#-r_67xzE ze~d-b*GDY86-o_dTlhGtZNFD^KW9RY{tqPR5yV&3el$!vkz%Ti1c^DLAd!Oyo&<~l zMW~x&8R%NiT_sjyK!wreE-ex>`uiQPuaeCNgl9LgNY2;rVcc2o-ZEagF9F0pz15T= zlM=9*_lQT@{mb}kfZwo$uUipAD>sj;St%GsR9re9h)UJZ*c~=yN0;HF>sXQ4ON3hzsKmf)v!c48f8|C=AF(##1Dwm&x<(s*OT+M~! zgB8))bgTOH_4}B#hyt-1xO|L-n$~e;X~)bUyUOc5$HSqA4~8X!N+qV&5@oS1XvIt_n7Yv@K9-tlg%VJ`GRR{Om~9I804!&UkfAdR)+idha6a_xo{f zR3!J~>5|34&3ty^&Ac705n53=S{#WE+QSwvej!fLfZ-Fq?fE=Sd`|lU$7P+BqnN#lFGg$yD{7v?-u{u+(9upsMU0wR_EB zimQ#)oRAPYo3uOS8@*54nz<0iTG+9_ue|LA9%ky_%RaWS3Cdc=aUd??vn{s#wF#=h z%e1v9yqPvM!esN!HgtPV1>ZGzk-j<%b5oU<-#A*TpZxuN+j{!A*!q<8zhQ%g&SkyP%(cvWlJJMVCCfJJr6SetSEHW2Xh% zzN;|PF#Gg2kCDGri=NVu*;NhUtTe$Sv|E@+Nohy>ufnn?KBO2W6%@_@`a-c54OCGi zgYLpd^+OD~gIN?e3-niMjHB#1k7l=*h9mP0mv-3J=}TancoU{CSn8j8v!U@FO|7;P zd4a7IIY4Im@2v#Hzoyrk<>>H@O_RIxhXkgO=6ULs-pp&oqy664uK>dj5J(pkq3;)+ zDhNlPCibX{KBgWPP&$mF)^v!s?aCqRp=4@VMw45-aGpq;X!>+uO;!ztR;s{y)LYX#AE)1Xc2iiH+umQH?OAyf zPuoRg?zlGSh!4bm7%tpFBjxktWtNQEdk^oaIWPw#@rL5AgQDC>be)(Vjc2Qd;b7zG zt@g0mI2^Gh$IOAEn%ynj+LY8H23E3dmh#Mze)~&9b~vLV@YstjcDROVdtLA~_~&k>-dy)p8CQvv9-UeATkrD}RD!pW{H#rGCr^#rZE$-sbej5Mwy z^%RQHD~Bb_lxAcQepOOxI7IyB;81^voXn0q2d#gl>Asud`0Zqg{nfj-NjpAl(=W3Z z$bJG+b8ZPJ(h9oVSEXIH)S)h#o-`oBaBl~96d!o%Nc7Ky=%zC`Cx#E3-Nb$>`?=D~ z%tE$q`Q;qW7LCCOG)ZVR$vf{X#8#vg>TEZ2JbH)hXofy-$QX$>lR$0a>Q7sF*tX{a z`$O|h#nAxXLY4R=%R88TSNbQ=QELx@$^C@y`q$lZ?IfFUXrv)}tEaFm47H{u#}kl8 z^H@-b7OUlp!Oiu&{mvz0w_}av8fiz7p?+9__h=!bdBTH>*@W+=9xbdp3!ftzQ{LUu zY_)DLjb+}#HXv1^_s;eE{<`a@ht*4_8Su;}R`!`|1zhkndp^1@5Q}h4ljpd*=q}>8|(74B%h> z1Pd64AY5@dq?Srx5K`85tA4Q1evj*&0wn%ygwASvxY4C>vwjlsr|!Z#OmrWfMniGF zaOS);C`kHz_m1hVBaW--!Ek6?JJzY%@Tv8n{>8B+b-`>$>{0t>fc!@~<63=$>T z=RUg2pP$rViOtotK3J#^+s#eTaKx&BYbvF)m{O^tJxR#6qSV4#AhWg)r)!qMnxn!t zu3=hzB+p7$;q!%Nj!u_deQ{2$r_>n!KCb^{`I5 z(gJEda?Ei|;!F6bVvGGV^Uya=tUocDzo^j>h;Lx~gEPWKHggt_`B7K6D+nn5FQNl$1xFcKXB@MEE;Q3?@(ZP&V&P%@Nc z>LCwJb{1;&v1B2gc80AO*2ZAg6f)rgAvYbUdMAY*@WIt{^I#!izu5T?L(AjeVhy9m zb<|B!4e?Y+9ulhdo~yOS<9R$2kj4LQ zU}(4=!n*S2ooAN>ZL`nULM6V!X+jE$4?w+n&*Se)LnD~NkX1KyD-!?I$2>)p+V}?> z@Za?tj*k&_poy%|c>E?tJ6SbTTan~B|KbcWR+wg@!Hosqt3ohGp$Q46k2xW!pL1il zzK7X<!RC6Tl;R};(DfAHXpZBH<+A2 z&U_^Tmz(7JwzV4UcNOE+fD$fV4rEGk(f|T;AL?6v1-Zi==vL3bc;s@-7xBqH#_KPt zOZF-K8}}!OHnDUBg59cq3%i?D%!iF94HEtR{ri`;75~^m8$>*BFT{d7sR`D#wuBeN zp#&@#wxO)Rhq~OHQ_J@Lp7h{oYC<&#LX=*n@lO~qihiCyLJW6YVn{T`<$J)#m8j0& zF`#+LD;Ny)@g_iJP*;4&Q-Ey;yY}Oe+6}xesEW&ijrktejbEq^@q8#*Lhfl$sUcP9 z8TBb+)K=k&uLnB2s!H)Uz?No!(CPVY?d=sHKVqBgtREZ{G~I0k@6JV8O0&L~wFuqK z+Y6Z;Mmdtdv1n4tdC1J6o2P48Ga*)1&WYatF83X=^EwU;dq1lu2-`j>nLj0ivd>~f zTDR%_!eJV}U=0)$#eHdCU~EN3)MWHh$4*<8d@YLyXgBy_9NlSZDvQ!330yI;XLZu{ zo3FlH1=MGXiq0%DA!0*F;U4@X_MNsklH=mWPfy!p_XJB7|0r7b}}QhE5aq1 zNTx|leBqMo?;p-CqU_myqp58xk)RUPqKH`>DLJo{$he8_T~XtHgC4`MjFnTwN_~qJ zGi1~GM6zWx#wnh|z8NCE57IQ&KMe@3oOSpydqu?YBkF3UyNcQTzMHpyWaOk7%vR>_ z<@-8iVNl7(oL>7v>3#9sZC8=wsRW%@KMfOne=S5)FmS}J$by8S22^wg7{^qM`t%Tz z2|)xoMei}~2c%u9zuIlW(OL!iMlVbx7xQK*P$^!?hwnF;BwX{pF)3ndLU>OaOof?q zUpZ*WVvrYuox+~&gP6{uv{9dK4X`ygRZ4+d!b13= z*+;A}Rrb}~7NaY;-j=YfXj~1HY{l+7`N|QA7>i0!E>6dh_pj$v6+u4v$_XgzJ5j`| zK>CZY=4zteirdshsvA|MjyDUrqwGW%=RCvRk{EuW=P8AKFo>GYhmJ`^ms6z0Le4AmPLAkH8YKqlaH*dA4Fwq(SG4Z*-&x4!y+u{ zP0${BgC;BKdodi(;z2z%CBw4ajpDXr!XVq~oXVc3yPxw89V^yq%)E(IyjD=>=n<}u zs<0P3?QP0{Cc6z>2qR8(3zE4JeC@t32bQ@)u3kewK>CKx&8cYE^%LiGJ{d17`Xf&} zpdfdmp75;LSd${@C}>g_9Gawh6>>_YnT&Xj=OQ$uJDNXPu!rVA>M0VT!~G1aC0%Hr zQ%kX5IXzlob{w1XRI_~&rF4|J{qA6HmM4$`b+OI>m3lI~E+#b^A06o;c-NR-QWKh! zTHhA6rBcYm%p|*dZS$*Tnfm@j)IF|uB?5u8_%OtEQf&O9Ea9KA22Qayh>;La7|4Mu z6!!Qp#m%0O&TPMm+RYmz>qLfy?)})dCJ7~M*zB}j5u-d7C_}L45CX*rUknTFiueS% z=>%mIWuWP>=mi;HR4>VU_&BsypWnrTT5<87Qy}t?Y+(SdDw8QpJ~MRdBMKP3;$7#d z-v?VawLVG#NU&oCVvG6uV9suby}RrwUk1fH%u2;M`53GIlbu^kfW>@jSVDJ_Q~H=t&@qjA?II4nf8dLFa&>q($3B;wyH>qG zfql>KpR!LLuv`YlczPpneUgMg<%gaYTpU61;V1d`zHfY{7+0j%=u8H%$2gI_3t2y| zQ$gq-<`6%{1y8)kDrd%n8jXj;_++yMmohN@A`yTCop!VI)*$TBADYffi(t&8Pj1te zp!NCY!>QI@SIs`Li|(5x-%CUt0l!O4dtCZ|RslR%#Bs)L5NQuY6b65sr%a2z_o>Z* zFx?NfC%vVB+1>)Wn|yfLMh|Yt`j5OrHFkB*_j-?07%GYPB=SDj{I}4d9cYBgg6>NX zY+~1)Vty)+hnMt9*&Pe;l$_|HoK|5|@^0WKN!%$FSP+{ns}EQUjcLJ49~-tPfI%yg zZqq~!#Y|-A%$F?=tr>-=AjljYv+w05Zu0}BMs8mIyI7Ms!6Jke9<{0TzCImWdqnNC zjwb)AL*^#yXw9f(I9@kNOa>E#lxnYVNGdZNB%y-A;mdh<5jrN>jk=lBHx*$%orxF` zO`+hIL!&Nf%bdq|CJDN{5)g!Z*WRWEf>ZOHD#DqoA%}JZ!DZ@GEI9<@6_0BDuudV` zJ_LR{Ujm)14A_Zq7;Xp<5PqkSBiWAQxWs2a&zb0L6=2BAC@5b3R0*O1Q` zy=noQIDeZgnsmxFdBGWNWn*boN`>wjqd0CiduB;NGlZONrRq?edDoaIT)9Hi{k}E zf#Ibi5gSkOv0OimMgWoo#iQ3c?;51noK`q|iWmI%W@q$kw0jev8)#zGzYFq~&Tay3 zD|IZ9Naz{>E+=3zs|hwUf@vbPFtEt_a_IEtGQP!{wn=4a9Ovn)*T*f2TZ!nebG$yJ zCo(dyB#^a#h~RKwF<@U0l&_>9A_&8>7F(K&7hWEfGzcE*? zKf$YOnuh=xeA@bgn<*T^fJONVxnH$?PPA>iQIEe9SHwO(UXI$>+h?NWl&Dmjq2jt8 z(bv_T%?^C`I_LZQ{$(6sBWijMSyW>)5OYMGGd`+3ujZPL>eXOpp^D+fdtm?#n?>xy z@Wp~Mw)}%6wYh{NJ_(a-k08S6{yWM_S;K_vN=A&7>9hjg_JI=k~A zbntLG70EOnob~l}1UDMh3ZD@4k8ZnV7k}ZcUZS>k9-Py}`R1M)7ACHY*4Y0REWxF#oOxXPzzmarSPx2fD&9)1~gz&rJk_;eVkcML(c=urs zlwqU&RfgnyC7p!7*47n;MS$IV*|lMTa{MW(>~oGaDtIDZ%sF5t|RX+K_Pcd?ex0r}a^_(qA<$I@2qY$*y+a!ABmG zZFb7j4&6q1Ja@&zM^7EVAH7Z;ww4Hh*&LBfKOw8=XG;&FZQI#aAP#hfnLfj$1BX66 zMELU7Nfn*`C$ijmyF7zh0Ldkt^$GPEIwzj_sw;x|&(=eg9)r=)!+sTTbgeF_d5*#h z`7A_%C}H7XUxO-y=8;A;R%&!mSs$IxvZfD$f~2ei14U&VuV{ORKOK(|;ZBK^S7SRR`bx8S@+e5))OP4BuR`x=m-@xQbM}sq*A`P619|Mm2@XyRZi2yaMYi zZe`eu51z3|%AYM({e9+GaSGxPPfe1eRUZ-yNCY z@PosNFyF3N(dqSRvTC4_F_=y9nH=O9UhWKs7H}L6zb-^xoNRHxYweX&QRxGc9>Sn( zKpa4KLSFJ#f$f8<_BI z!a0VU4N0)TKwYYE6ljiMYX6cUGv zu%Er!xjc#0va)h^lii(-uB+dxXFG%VjYL3s$`&uXy7>LNUm#;={)j!En^hSwy5upeI?ub`w1>yg8EY30MipcgO1 z0@2T(W_0$uzPEsV{~C@{qaOy#*+f=^gj~i7PJ{i_<`QSXw(*VqjxV$4abIu3uRM>b zZ~mUF$#mAWKZy)Iue+*G6rz}!c?`dDk9g0K-gPb%K-SHFo?E`X^(iG|F18wSihON|L6**NH2`^@<{u(E}3D*j%p2fLu85 z@~!OI2B^#)fROZkO?GiHGpSYs0Rm|bmF|_Lvgt=aO7Z(qo(C#Gmk^Ykob1k6vw{od zYH-DJ&hk{RyRsuZHTwn9JP`}B9&=(!(@ys#$+5=|#KMY*rvv^`I33zmK_5Z9b;Wv6 zoZkfBIeVNb9L(#N=z?YQ;^SK<3onuW6BBG(gG@f_5b zwU2l+RD%Y`bmaZ!f17yO2d@_((@8d8@eDXMfLyHA?;+?pp+3s|mX^g9oNo8uB$HUA zdPeU6gd!z{?Xo;J=DFPHxVfL3*3%b3lzrO|lxLrwzH_#HJ<^> z6%|>mmP{zGRcVm-7{P0vkFMt17QSg@X2qvzUcoB(Q>t0kWgv}msxgrZ^)O<_XcVuj z3iorzm=xFiu18PKQyfroA!b~%lJv%7#wCoc z@`QV4X>uNVOrjA92d7vAkWLlX(ps6vO3eo9I~fOVju%nS-O}`o$prjhO9$c>o9sp#;I};RW5i zrFJrd=Dsf0ls*#c7&9%GFCtM39&{(`TBt4;)m8`bKN(ehkG-#Jsk!V^-0-;B)cyMHM;X185xAqfLjo?zy?`Zl>U&~v9^@T9{IDSHW zHpCcbiFL|78V}5*JgGUzPJ3DGjsH+7v7S&H?A9w>`)FKp-{P%X zenV<|JTXg}(?25(%n;`cLOog4twU02w`fQc5aHV472Zwok6-Y|c*v{L0`pY*g6A$Vuv=?XDoF z_&lWT9q+1;Br}us%jl9{Ac^#Zzi(`xP1Y6(OkyUTrw`AYOjy`1B2~jos-$%|m*Zjv zS%z!9@1XJo0Gsj95S#hkkVJl5TimGh%nb zB|04E_XDM&!xJ+CFR2%hLru`d|+ht068*t2yt+DNB};s`E6XNd4<)n43vLS3^639^!qzOTN) z+xH)%g;8|R5PDE1?*V@xPJx}KZVkNtlQ8mlO?i2FH@_Bvowg)2qAicldPh|NIUNw= zE@w)Ohb7f>J1nlbaG?9J9ZnZr7rBM5xw{3v>UB8x&2+7mJb2ZKK3%?50wi-E0)yMJD&#jlAbjQ z0{6Agt^riT5p(L2pIK)vg8m9`yodOC+UZd24oJ-Df|JL79E+Blh3P6qqmVo^pYB^u z3Y9N>q)l%frTh5hR0(o%P-GxFJ;u-M88{?7QK189f2XKRuy-ypB~V6&UVO-I}L=FaM(c5@TXe5bmqILsY4ZT@)?8IJaG^= z?tFO#$6P>>=|s2sBL#UAt%-D9f|t#H4*PN6$OsGGCLC}c+>?Txv%|x~`=7;rQf2e$ z_2xza{tNKcvzYVdDDn<@r6~qIHu+86i_cJ95;vZ)amPCgt;t3Khm>0 zucOo}_3eaQv*lZN#prXL5AX6d1Y(Twfws%dO&918l(2djJnEi36BiVK`bYtuS{>YYh& zM)?#EIH5B0zmq8RJX6z32$ULiOrOEf)u=`VmkkeF-eeW)2?(ERNuCS`LUCPBDTwB( zXaa#G`y{#GmqBuTJQ@IavN-$zB4u9Bd}}}!KcAu^O83@aZ{ul8Fi^~2?g0xDS(gPU zJKf{KZAS1Y(H+0Jxbm0rz#S&rubkRFSM|_Pqg0(+O7sj=U(xMh-W`3eJ?MB#y2j1f zrcupG4iTm2)se{cjaE@|Rq*%FzyL#|9dsN`lzJSL-(@$NcAwiMy+X5%Q1e!##L(rO z8Wx8nB*NrEg%);Yags7>B)>7C!0Ee0D&rO!Pc&ia4?P(H{`9U*u#5o>6V$R{QlE1% z0LjpijPj+aeUe|IX=Zh-m<45)oiHl6)k-bjFcu0BQkswkag*Sz<2XK8u)Vv4@%3&d z(>fzW6-2bZffagRs><$UeQhzgni=9it^CzlES{bFQ^6L$g%(4(p<&#OSwyuR^h9fd z=azD;4%-*L2Eh=lloZ~dE=Tw zWuKove|}m)?|^#%G)=nl<0zwq0uj!=TMLt=5A}j(F4R zr}9wHcn3ssFtIYo3j3mAB9L5RAfL$Gc4vmi~xO#*R(M*Sps#W!vmb83^L{cSNCcrlMkzKA$wtX21ZPxNf_nL7J;)s- zCOBKVs-28AiwpuPgBgwPA!<|nkdq%JG-YN4}{K8hWC!qxa z1@}k$NjS@n>hX)pjg#?rXl$M2k8!~Ap+P{PrHPi8gkH?P2u2mZ%^IWS|1i9$ zz+$Tc<^I6PD^|TL z?g9@%J7rt0vqmddFxVU?xhR0rqM?PH7)PL#b})RFjiWLvW=PmjxoQ1AcaMs;!ek~d zD_=<4RJ?aWJrKoM$zb65=d@Y<%t?X8VjheLg~Pjnl8y?i+tqjD(yLjeJeU2Zo8`#0d4_THdPOK_)Uoe>%V*E`_RXPJHH9k`@`dGe0sF`jAUqx() z%FOoVfB_lRQk*YX-}p4fyg{H1@eAiNhPM4;>T2&W!-(aevq~Bksd?(MifX!G6v5Y| znHBph3vOnL7(-O*)UU?o{k@g~ypOG~Y&sNWq zYerAaAeoX(OG|6uRYZb3T4_E(?riQD)aHCOwy8%6PGiNe2B=fH zhW%u1sH?{s$z2Q6u#n*GnoXxQt=mcb2p%8)0r%~qy}}Fbww{(p(!}`?^{uUfflFVV zDUV8Q16=mk>S3fG#%pi3`$N%X>;p&SIbv51vU21tK*PCnZ-ePpCednp!(XJO(J#V+ zso4DN4i@!-jx%}!#wX?Sf<)BRK2#+}1*Bm4EJod+t52^-U6+fhynGRuftu`ZG6G-z zY;zyK?qjvwyL{xGw20<)f6CLnkc|D==h&xvvP6J5n02^)Y`oG|^z-4yL?MJA$233( zBW$&kZkgZJL->Me#^&eaT;8Ca_Nzk4f;FP+OT-m$z>^npJ(5C8sx z6+ayJK6dbppalgp****Ip@bcxG`$=W;WRquH#$fNKfU<|DiplagV`V6c_YuMWPQ=} zP?2a-F;Y*)zWr;z%x9?e=n6zI7sadM*jD%%iGiD66FC#Z^ z$Nmm8L)sx7A(i-kiG)|$eMc~?g>&-E7@nBRtF8J|Gl4t+*BKUC&i)rBJvsrsdc$L3 zcKzsg)`K!PP3-BeO|QV7+4rXiMXdl%ER z&g#f*L}?+*$40P+${B*_g%6rO#bB})X`2PE1n}ONI1DiH15IC7?AWRCF z_4M`W0T;9B`Tj87iOt4dfAp*->n=bOp{z2WhDE_&XHk*`#!9ShrU>u{OnsM@)V`Y? zaw~v9f{0ida`~mDtW@`A+4xcr+P)cLj1b36-F@B=zbb%i+ty*B{Sq$e87NG=!4dPDU>i9$P0*Vm6mPUCQ zcq7A@=9)j5BS<m8YpkpIet69l*LmkfN!4*5!U;Xq03tc*~0 zZR}6Zio8cX11P|WySumWBd#QIo<>nOMDWPZv39l4x#0O+%epDhKPr##$ww1u0HHq49GVpC0-fCf)MORU= zhDx*uVpfbbg$gQ`7oj9KH5J#ZB0!*LO1Sz#EmwsiLo|n8DpEX7W>&3)cpgUS*o}3s zY#O~)1xrj#zfcxtqpxGWX+LlW0t!>e`p-#Hq%8Nwm14>D5hK8q17*Z2!;yIVggP75y_(wHP=V;8b^IoC8#sY$g*G1j;okV3 zIH71t0gcD&t_-3Jr-T>tao8n=hxno-bvejwuX+&y1qNjbyG;@1i#e;zmm&2hmX>)Q z^zt-!uh~!cj`-b6%c`t5#@T(*g=TQ|I*(^>r2}KHj3w(JNm;XqzIS?*860qJ{&q>| zsrD`FQOL`B8>J|qhJ9kPyAez)?BKxs5swEpf~3}=U?hLS#4&81U4cDt-U@&1`ra5Cd8q!)Q zNk;t~sDio+vPN=nMP!JR1V4W_*R(|EYq`SD+yn{Y?Renm8a?xssANoJ19P1anpifm z7}WA!uX;z-MXl_kBX99Bw29??VP&OUgDF^#-?Xmbf)PZYHI;*?CNbAgRUTW}g3}7u zlKrBn6NC`K%h)9py7j#oGH9scmQG{`H>@ogT3LT@h|NBURe31eI1{sxq7s4dJ($T! z0_^O#{kaEwcRXD_`Xu>vJxMk~5)p|w9KAKc6f!?Oxp*))%!L|PBODy|ZmE+n;L%;C zA8DynnIMwckdnE^O~2PUtHIE5sHZBU25Wzf-a&f-v@dGMTZt@X%_b*X8NoAl$@E$1 z_~+Ww>(F`uk}(BvH`Bhc+QKT%A5)5QPS_pabSd6ya;8pkU&Q{NV*0dy?5oke6jJX7cci*=|8a+uC&OicWzxPaxrOFG4w9i2_i?0B}) zd3=TY5#g;UCHT;~Ep=S47bQZUKL`??A%+_d}s&=iL<2 zx9c^=S0<a(0Vi(Y^djk_a%T#`4k zfd5*b|KN6@0tqbNaUjK1a4#PBtp5^S6bs>btGfLCG*K0ymPk~*UlNB6zU}( zIcQ9st$2KwWuKQ#^?uieC2as;dZ?kHyp2>;(8=uac%H5lFwlBqpUN#rbfjLxOe9jV z)07bR6XD~}ZZ`?0vbinb)Gl$tKO-}XDs~gH zNcs|`kr3XusM~A{8QWZ4E~{W*kT#Ra^Osg~)h{kqr?p76mP}a3MKCU))BBV`U*Zxp z?-FgNJgJyQx9C2rIO(F{rgCBTIyQm!Bd!WfZ9YsrcQj_1`@v@BRhD!yqd>kd2ht=r zijGH=_!L~#oSds}?hx#ogM)(?nSEprc=vI1O_$1+PKFRRHYS>Ta{_iZ*4Mp?)oXvs zqii^~KHw-7sd$BlhgZUtZrd^lQ`_T&2#^c)g`%%kgx(g3rG!B-uJ!MTyu^&<`}PQ9u^f_V3eFkyI!FSWbt-C zehg)==5h;ksjIfZ%+%z99ZF#QjO}f}{88*x7-^;WqTMM?_Z{8|cL1_JckX2YT5Hpa z^Qz}LKCOX1q}SQ@9>H?8Mf=sY=T?;D*3HjjuU1=mb3Su-^0Dh=e}1#x24-I;_{&%_ zQ7>)&!O(yr=r2*Z2y@Cf5%=0Cx|Wlea=t=~ugTBu5{>#!yC)(B)}qV5wcPfSubwxP zalgSw7-W9eW-#(2Ct>CqeUK;Qi2r#YgCB*suKF<^nyYoyZ>O5V{6Ny>PT4beALZAY+0U3EWo2J$#Y1#QKctc7I$DSZ1ucAa9kTlj z+l&!_D!UoD?PMX^2d0X)RA|P_nA;BLDWd1oF6n#Xc$yQ2x=;x8h zwuZ59!<1j1);)TW|?z4yjv3y zODc^#GlIFf&~nDOdYu~^PETt<;b!1z{|E!?Ko5D4_1Zp+?6}*6h_WBOby6*sYt>X5 zcX+4x-?;>=fO50Fx{PAt3x+0IIc?}(bit%hyh58TET--$p>xVCmEHUmsM*X8 zxIQHd$XliDeV|#c#1#B1pPd==9izs(_z+vLkXzO+dImr#&c#E zQZ=P=ogFyjxWs7A&8k^Z7qMQD&6l<{Cw?sH%>RtLA=FIpiDPTjLm zxQ~b`-XMad0`3ahc%5Y=eNz`Q>zf`0Kfd)ZPk(0hmGeMk-#LqVBn`q~Y^I1d{2HzP9)7Ws{dg&15oh7@oPG*4vw7atGV zRcZPOsxi`)C9DWyziruwmkcW++C_)q<7VW4U=dj+7M9(&CHKVF$Jb#CP8ah;#B5i@%>J8SuO1`CHiU__Tec59deYK zR(*vO75w>)8edh)M%9igj_pe<3w(^JP{V#|sQ*%l!JcR~u9NIw>w2VQbv42}R{m6H zsjT`jKU<8jlExU1m96@@IG@>sH`Co#E$XAH>MyLVY&^EKw{+7YE=9$Z{y$#TkJHu% zmln&7WAD(NNqA1k?04hKh=-F=NITdi{roJ&^AU#`dO>j4ZQ2Euz^Z)w=KAPnahY=F)$(8cu?yqN7(UJq)H>$|Zyi{@E?-FGFj2 zHrz@GU1!z^D@LbzmY6|HYnMrjh*XDsCa8A>D&0 zIv^F5#yW1vJ}zbvYg65y0VSTQCFWKWLvi=X%-(2V&=hHYJjujQoT+(^BMF#Kg~eES z>}AtRvmP0LxjKU0&+-G$heF#e?{gNJC{dYFB#T_=Y8AX+oQoA#?EWug`+-l&gjSkR ziLJ=pkzUnSg*yb=;q5dgWP~x~9C=;0iewaG+|&MF2tuihI9Ld!pRfy2X`dQIO7WnU;!$jNh<8%gX|@{ zLB;`)2T%7d*xbmqxk1_*{BaEZQbHcTtBhTNF=x!}t;=Vp^_U961B;mbq@*2O_`Gbr zN8#PAEgFbqiAQ^e85NP!*zuP@FL9iAy6qA$VoA3-iPzPM0lK~ZZ548zWitV|?2~1E zbinxOa(`Zm81Gfq_x?!zpQ81Yu@wZJ7M4mC3#6ajyzx{@nB-WR*d=!9D9r+J^^yei zhy(Xh*xf1*idQ^-5?NLaF1)_1NJW$oedW3m)F`5>U7(_>N;mB$iy2^iawWqtt){9< z?>1>=q~SSpB$1Qbut3__V4*sGM||d#mGztL&+?ce7Zo%*d>j>!7JJr7Rk&yzUERGNCVZ!6JxHzIOMV;qpC3YbYQIm z_6`#&JW6=gq@EkcSKvp~x_XdXc1cJwb<=xf(TF)mhY4^tli?i1+3?}*f5mx!nIv?< zNzfvSZfvh;Hmg_jUi}x6iI@P~CoL~~-ufJg^*i|6ezT|Qx%dwSZnT8mpjEGTl12wQ zpml%9-z8RjtiASpIA68c*07!YD*C|y`MALf1G-~HY!icPz*!hwRo+qSox+Zwr@W9@ z`swAh6<4%UrJeQZu`shX;*Ll~^XFG^);J5tamm!(Q_Q`arnSXJ@%PJ6aZ1e~Jj2<0a&>hW$|eT_&dkm>K$=ZT5byM#bp?cft?@t$hfxw}KMz zNIXuVAl>xdH}Dq2uso!IY5jW5Ce{-JuwXrPaAyi}5nv+%jdunm3`-F*g_ol|vC<-k z&^tjkFEhN}VJFMI&j>$|YK_J+pUGUZ=4Hp}0#prbTawkr=X82DsL;B-mlQ88&ZwEhkDYSe@|hp{{^!2zut)|z{|KE zRM>F5W(e*5v1v>ra7s22CrR#k9W3*lMQ5~?YlYx!*Km|!VHc=-2P zEd^)_uU*E~w^u0E_Ci>ul9`U5%WG@u7n@?=ws}rqxE+B|iRj z*O{r2<|3pUr(;-<7PyD!1aZbuZ)<*mfA@81| zEdcqxk*YX~2;JsPQcjbb^Sf1Bc6(-!!fWBjcFVVx%sst$E>7B@)MVT=g%Iq|w(zu^ z@lU%y>q})QcW@}LJD=$<$h66&E523M3_{0!2z|I@hVM5=RYS&SE$DPB-1>hs5%nK0 zURsOU*;Rzbitc?v$H6h7>}Lo-m=lD`V=)Sf*Zpntn;&Y&(!FQN&mKKepMe1djWtgE z^D7u_ln94s{{kBP!@%CD-$5Z5XH4HRKTH|8GbFxc5Pe(q^B>l{W1{-6#E|d#JMTmR z)d)?nH{zRUj}mgIn;g>Tko>>Hw(K3z_Gb?rFOn*lb35;92rkh7rB6W!(A2Orix^-; zC`dU^UVpX46Yss9kwO=M7~1p^kkM$>->AB)F3}S>o0; zHby_psAX2(%^bOVuksYT>wZ0sg0LYVgmn1x`z}m{H3jcG%qmUB5y5AVfDYJ{aG~U~ zba_c1xI-J*+m~JIO!)LmMX>v4`a>RKAB{#m%^J}>dGXmm&xropjtnDlB1ft0LY*?7 zeaJ!! zL$vS=0XA|Ak})@i5CsU@{)~BgdfEos@xGvA^AoJYXS)k{-v9q~=n2+gYVZ_Hm`h6` z#%lW1CYBb9WvF1&e1N_#Zg91&CbYl5zj}*D{Sb4xK{p~ut({Pu7aD!xM*d~b^OHda zM-(^h?_wZ4T`WQ8V`f;bwLRVxoIs@nY-8xhG$EjN#;)f9tcQX^LZW@|jF6MP1nr1P zAv}1`G3}cP%Aa~B3|AR+p$OP3z*snXq^>rl_lUiQA@^joYD#9BIZxnSuYj9XeaQWKWgO_+HZ9pkDs5`D!rV}v zsLQH$IpFZn@~;7p?S@zod*U+?WXzxFYuz0o_%z4iyHu!)etW(6^$Zv?BY-B;>E-43gj_5Vk_dncXX}PV!*%|* zTyzk?a?vS6K5$e8Pl=nJpcLHJwpXxX0YBE(^xjfZN-Kl`*@GpIi(x4Rh2C=gPE8lP zxeRw4GfCLOqN1~{Xa$3%I$Kto-6#r4fED?Gym2uqt{4##|9yp&MTrOqxEocy>*Dxc zEuw)#-m0!bg%EPuqpVk${(A#~v}OMuEX1!Pv;-X&7w+tv`$_Z7MlgLTv%4J#S&noe zgxs$nbX;;yPMoKwr`xMpT_sJ;V89(y>iK9Jie4TER-3w?(DIMX{K5m2O%WoU^gq5o z4jg6_lZ}~uy&wet$McHo6XD-vS>C$3#6*}jZ=uOkcR-K`ueO-o0OLEif3hNM9B_&e$? zAuj&%xamSZf$x2d)nb6I*WVn`_geF{u$ON9Pe3rrU@Bkf_UZn__{H4S!R(+F74L{&0@R_GEd=$CVasL~Kw<5G>NCJvm(1hoAM$CeQCUxwd%(uwmdUO@=#w zBH%aBwOD}?4!TLpxs+YiH?&82aQ`B)k;xPMO zflH);N;P%@&mKcTOa4m3{hFME9CpKf#RrN^YFV_PEIAys`xGyJ$9}%f+U^S1+{$4R z&EN|hLDKS>YC1O&6S@8dZ%O}kPkdal1qB_wARnW{h;ZNg*~_RzOYe{#4P9D;n~b-p zvnCr)j4$o`8*^fOY=687=g(%ci4~K%be6#yKE?$=VMKaUDrb|v7K_mFK)y}EEl|8a$;(3%fOZeAFu-FWn;~cf7<{ZOb$}xmLGFbX4U)7i zk*?C3{pcxYnqhkgQ?6$m=ldZo#rCq3RY{(-JKhKXr#D9;0xnyC7Ohy zjiaNIp=2(FOf!+?_o<6u3D_{fUWx}NJw0QN8pA@GU_XZM&+7uhb7rnVRfEyKLJkm&C^vo{qv@ZDS1n=vgPB^y zKGCZEV?9`th#{h2ia?yGSd~?#tG`6H`DZ3Nj~P~Kcb1lJ4EZR^C_@%zLq_c{wU4Gv z?fn6^Sd<|xekgZIjqFvvbQ_7pmLpr7=BaItWZ^w&TW?wt`=7O zkSWY1RPr|0m_0o&SdETgIQ8rmW;rkPL&^Lx69WAdzOUoKO#i!w)ap$)sx?#CySwd$ zahKGPmg^qPyh+-ajZcX{?bB{t9G8~u%ZLilZ*8UB-rnA2l@qANgzhjE(_ul(nfe68 z9E;xYIOIpO)g^(`s0cHki-48g?A%{#plpQ_1#rkazhh$o%Vbz&Y@BWn>!iA=3M(FoAp_+QoqA&1Ik*$ATHQGxxD znZxKk7@`Gz<1*S)Km2n=Wty3WGt#S!ROrq+%F$EiI!rU)^+#GIg-Ps8y80o^p+Kc+ ziYO2;g^T;-#c|JE8CXF$5Gg`JLOw&mXW(f1Ral4^g2UM1WoHj0J}ZV|$Ub^K?Fv4( zC$fFv!R=w>p(N!FrWG_m{Gr!kzboGa5*$2UC(FEDI~WjnEJaDR__5Qd##g*ZVPI9x z*jSbvko{OJv(54l8k{#wsR`)WH$T9c9U%+{%V{2*&W}6gdZ|}gHW$?2INc_pHmo-Y zooi=InDVHu!Z-6Ne&v}sUSKoDF?-pSb0pzsI<6R61v?;s(F`wqJTu#-hV_b6(*-Y*G_+M$UTKa?2?5 ztBd)jcG$x}5D^b|HOr~A-+~N->Dz`02;AInp#Vn|l9qrHvI{sLg?K+*q@SLj|7@r3 zb_1gqQqt1x0Cw1p+&qxYNlr=vr_igQjAC-q13;Mmq=liCANmvFyQNtoz**chx zVQCmzTdbIl7axU(kQ^&eZBIUj!x;7FFUE&Bn8!Bh(P@ooH!lXC+y=pEF$OFIO;-Hf zlNekh2+MwHv^H^f@;bUda7e(Gjm4!r0@;+$As_YCeLkt>J8rv*`A7=NGZHF2K}g*w zIQxxbj~pEF4(M21h9E!HMT5wAqulnl^LP(IjWMdn~ggt7fm3y4<#~sHu#SJCB zLRTzi+zHtPR0`J_tthQhtMHZOClRp|HTl13O73d&_pVJM?^t-KN!~YxoY`74SXpEe zzkB%5Q0m;6TEm))H-vRmb9kA-yi%v*l4zMHO)K`{0|#AEk&?DFQVG3`jAEPs>T#;f z#w)puuOR!(`dVjSwkrgV+}#%u3V?XomWklv=C%Qjv0jjaleL;!FH|9bvzrByXDMdL zEZL9pqxE*hD>AhKXgwe_6giR61dPK?G;TlMto8zNmOdBocx_CftrNJ}QnU~ZqNpm8 z)nhhLYX+MSdXPCsk>-UbX_90aoh-E$n-+&ld`naFbRF>ph&pH~^m7r}F#BxZ8i(5k z%K`nBoXH>rGKaTtIk7lt*wokh7V<>_%S(=g{5o!B+3{Ye&I(^|C!Q%*TB_=>C_-}= z7)N*}KKxipLAWG_WM#w_$AA>_Z>cm54-eKl0_JIa>%&X;o8xYf1ECQRAP#tt>n?Q< z-hqjXaxnFUKDTK8$n%YqMF;%?k}N?6)wbDFo0|9_X;pZJzyXACj|+dh{0Q3@<2z?o ztG`^+4Cc7RRVSeHJ2+pgYqS%lGB-74_da2;$2rT_)AU>&Qdtnu*lCBxPoC;?jcG|fgg+S z9*7j^j9K}hP;Ock|Pqrc+Z1E1Py-Dei_o_u#S-KewwiJJN)c#iT!Jr{lJRM zmn+c+kX{nRjdAbFG2}9op%0_RY00f2l_Mqw8ZkZ+4nt%}(95%NNfPO-o(_K`+l?-S zCEH%21P%)v>()mnh+f2FTx5{zZ;!urir2V`JGc)J1-x3rZC@7OClQ~O?nWKCux02| z8AtRc95^x=egq{61am7YozMJmMm-rDVU*7U;%9#ngOUYN$oXi#ZAK-tk+4mt2k~pj zk$<=!wg`T|ZoXvSN-%y#kiTgV)yj$If5#_xsJGI6D!7EoF+(ni>8Ga~qE^bZw_NNy zXNNC9*nrHaa)FtVW^_t5{x+Aao*P*yR^?qXlCkD1ZpY>>*%=ot({VwRKXyEB^=0eN zzGt#4V(mhVx#AM!eTVs=M>j5;U{z2X)8(Du^N5B=dY(5I!BdO?3HIi68lkjFt^Zc- zF9`SCPRw}-4>o%v2HWDu>x_kkeM8I0LJ17eb&wu2gA#CoRreb(B*<5+SuI0geb-s% zf((MBgM7xd7&c7-{J-s^_V458#jjHG3p3kdOY!8CsTVB9D?CpQ9ZQ{M`IGj|m9!-_ zl(m-42&CXtW8(50&B_XVjb`EJ{nj>*%-F*n_EfC9VDG*|%2ZfhXE!ETv97;hc1=zq zO%V6J2NPuAZsgi-IsPFv(NRq-8MY@fdJUN zZey#csPyeoQ%Hoifhk>h5|VTr$L0bx=X=)frl21`ge@%@UccdPlL$Q)*}DF(rhF6Q z)BT+mjtHvg$N@yub%*#AxFN6tQ4oR(C=Jlfw65Iv1#Z`1_6pERNKirPx>o|O8*p-= zae-*g@k}<$7OW@16*Mq(bRC9;QPMt!IMY z0sIuC$5#L1L`wpO|DIWrOyu8HAw|78gLVTJB!Nf9qRY()+CCo?ud7?3hm40HZ@7X& zJ2A}o>ESOo;F`DtJ%;~+2$(b^gnmQOY0dPKe&Q1}I00kEZ@d4yhnKIRqW+3yJ7I^M zF5vOIRk54Ge?^IUHKW=?hK?z1ME2g{4`|+RoQ{d2&ij9nI#Jx!`L8l&_PvIT4PWP< zvVc_K-=Ai@go;}Egoj>Zy-YV*ADloyZu94tc!-fDiW0=$U>%H_S=#C-I17Lg2MErP znVHFYEe<5Kh`nz0ISnqcXwrNCdn*#i__Sa{!U!wYSrN$>6sNeN;Z1p&%8S@juoE_?WImMEI}y};C9R#VfPsHiA_ zMI6;I2!O`JKS358>*=uE5)#i%rH^hZfFn8^9}fpE!UqNettUj1#8ZW-LzY5NP`78M z4hT1#fl2GeB#_!&ARia=E{C7)1-3ftg z;@Djl$>7T+14Ol`RDVLQofILIh!+Qv$>cyaR8(L@@SVe7pR7gz-&KTMp7r+exjC9e zdn$|;Tkvekv3*l{oecne#|3a=ZSOA4K09{WL6XIn(0tX|xTN;DGeDID`QY5h46+G) zSI9{x8iQ|f4bXpwe-}0cU({tM@rxp0^`c{T#;iE@MUpk=;M{;ZUr3OeG`GpDS`z|t zgHshA0by^KVrL!1S_M!j-aT_1J5v~mO0+g+^N~WTSsx#%RdbE5Nsab}+ zPg@Za1X2@a2vnC^FW)r}`WfJ|0WAtleD&>016_P%`5_4aer?%<00@zJdDO<7$;SjW z=^t1$wSVsWVUQ0PZUSm^oD!1T6@yHj5OfEqz+(c^9gL3K9%agVdkJ}Q^cDPH^4VV1 zs&2F44*#fRyh-b zG}!6d4|CWt-KFunH!KAPMYC8fu=9biLo%sn?MW{|a*%c0%MQUn_7)TZz(e#wpq=m` ztB8I8P=%?Le@>R~E(lFjJ}>GWf2wNj&uvuZccbH**|N z`eL4IzHsHhjN-3WP*_;E>+V@mxVA1kU^%3~Ks`C6!|(}I|8E-i&sw{Nhlq>VA@+e; z8k?;?^q{s>z$N1eKqXnxu`eRI0An}G3jF@S{6ZM^0tpqh7wqfs{%Gn>mH7DhFY+hm zjxW_A04L})W!t=lhmTCtB`2H!7|!Z0&+mT*)8by_=~gr9##zHaFbpUaoc@1S7nLul zs*817iGd#gYwSAM?6BylS?@v`8+jqZ4)sBor5*HQ+0NL?N-{DsMq`3eD~>b88f%xO zo~}7{U#awnpvg(gZ#|wb(x1C>KO`F=emRgbp@rNCUa0H7@*DcDyJ`q#%PDpH51yDY z3!ft3NdS~U{7&0$foTL;6H%9}m@XlUCJfrMS}2Ji@nHNrNqzlHxf22cf?%*`2QEv= zuZjx7CmU;Wd++7io)945E-MZ|cKneMAMRJ5SDs=`P{39$Cjj}QItnGyJ|kpTFitE% zn%Qz0i9Wa67XXq0P#{YffM|&N1_nBSnaAQ%UwEyU+w4mu9I31jsS`Q_bD((GUqC}% z6OfNtpeye4kqPou-a)NfRhtYGz{0_8LL3}MGa(pUSw}HGczaH=Hf34MZ?XOhkZocq z9dG>4R@B5mdw1Vq{2BHfOCrQ-u}0X1joS`=Pa9~S(8YiM4(kDcFc$!8kX8@{;Squv z9J{+83E#+fmO<-5u1x?wNT>r<;Fc8JgHr&A-~!>BD%5UuBoIE_QUS<&$zyjM`Cpd& zhzY^#&Vcv~jA2h``%KB_ZE~G!qUE4*;=lTx0c7OlA_QjgDcrtb07d@QYw$3|Ql%d0 zkhxPX9IzRPp`AJE^3C=H&>gE@KJ!M9I%qTmpMul| z0CO;5-#-~chf72Be9n4F;)(Idi6eqMJO6z``f#fC_QK*0yCaRUpv>n*v|AHXZ4Ze{~4lP!ZnOcqfwWOT?=Q;!z{ ziC@af_WC;6BwZzXXM=|Z0j7-+Bo&A%ON-tg2|%W9KIO9n-@{UZl$V##*47rN>qHA* zKAi>%&K19t#DOfdO^BwCRu{M2o_4oN3;>jc>za}$8g+muPdJ}S@*l4i*Ii#CU#o95 zVTxzk{ZW-?@SPbhRL#KO*9AOd7WScy7d{sFNKxtV`Omr;B=81M8={j&NMI59yksOF?2-Qo^MHS?d%L>WXi@f zbqAqS3`vCjz@!%OXi04jx(`LcZ??w``jV1`qmPv$=q&(($LsaSx2&&i|B)7{q5`{c zM$BdP7=lM1zeA^=6NP+u;F_CF5`}mFNxRKr%6U5+i4Y#M*y#XZ8B_)eCkJ?B48k@w zHD!#OZjnRhc-`&gP2`h7`v=P*fd)sUEywp-q`crmR#odiVz6D$knRsF1XTsoC+Gn2 zFUJf^3k2*=A!qQRS&dzFp6L)RlXP8ZRj{oDeSumCGYz~Wgz~u7W|NB)m!AF(1+T+h zsEJ`Hl^1u!dWz-u?;TALe}0R1k@IOo;yXwhgjK|yuwrG5U*pl$fbr@t)TbcPG+6KO zzXgg1Mv9|=X7!#IgIEyk&Qvr#!Cv_nw$=Z8Vg@11QTXzFl<{65<~Z<76?nQe1pAtT zkMB){z#YwNV!j}NjmL-KH9OEkqJF;sH8mWBvz~JQ4mxlJ(ALl#JL<^xy}$z3nFwms zhrSjSGRj0kMvlSv?Wzn8!@IV(Qpcpy8-)N7qiTVU0Dpkw6KMcXKALxZJS=W@zu5r2 zgZPVK61P8OK&WKEDtva#8TalR5>w>t+xPGBfuKp>;Gkb?YpX*639FG*NJGoyPj?gO z8frx~wZQUnW}u6KUnU8AgS8?D!DT?}0up3P6*oAY#6ik&xDS+Qx^cC+r`!i|=^bumMb+HQx+q zyR-ZmcIW2-OE0B?G&ej*yonj9*2+t|yw!aT9N`jja>yX}dKRji6}+A^1nQvgm_Y`R zurIc+296}}*>H~i_fYGuWVxRZI@9`|*z`NTry*^!fk15|0}Vq{9=;Jnu`A5W;0rpG zhL3kw6K*HvetO-BLcFjky;5o+Q<3dKLtG+?fug*x=hDidczCii>u{2dwL+*4)p5Qj zB5dR%jxT6%*YHEVH{kkL!ytsmNZdv5_!XHfiq!-6n*r7ubjhEz5 z9}4RMV!(HQSm^?xo`!tO=E6%8iqZ*65KUGaRtILzTpUUdKu%NL{im# zdJa_(gA#&areBVY{n(2-oBNE!PBh91)C@FZrbyZd5R(!-GhX4ZW4&P!#Cd1Q8y;}( zj4XkyKc`sn+pRwHLz3yHn0PilRvNAW!H*z+B^@0m?|Ix-Cp%j$Xdn;M%r4~#lG*(H z{5Fu-mAl_qYk|Hc>roN2>W>B>sy1v3*Pq0T0@;-qT+0ra-+hmPx|?N}O%nYmj4^Z# zSTo3rHpT^<04hW#>@Xge%TW7A^FUc&K7AXadC(CP<<}f&q4YDIqWXvT`&UoaRNZ_m zU#AJqM_-)3j@j%R(GpE=4Ty3MC~z@YuZYb}^=cOIxMkiMPNojS1s%Yy--f+t*w`<* zxY)|Q_`SXmq)5&4ct3d<85#BYWRic#p)sHfuC1*tUaT-iGB-C*K?t!A|72uL!2BY! z;GW3jp(EPqe5+r*$%O^%J#8R@^YDCq497jNzw&$=NMMN;MD+uD^vH9sQ#BJ zc}|ANM{HwKnxI#gz3CZGUCbRh;}}tNzGh}p2mbivYEN>qbvfueoTOuj#u>9d{&ev2 zNPeW*RB;*7^X?<_Z{LC*n_%PpVM}`A+s4uKi(SXKz}>&c_9JI<^RzU;`6mxJ1D#sp z$~YnbmHzlPeN1Q_3@Hzl+OBCPo6$nA36tWH8Z{kbPO3jO$GC@EjWaJ!>rMK6Y z%dV#7|J)@nO%i=twD@}Y)CjtX>Q1Nf?uKKvUv+O_AuY!;r=tbeR=cSTVJKepBCGp! zV90R1XtkL02RL4h?@t#wZiaC+SUD}I-4Aog8X>wEt5Wguj)K6Hw>=nt0BFPZY?opt zA&T_C0~O*SsuvQiTM1l`*pnl{Pf&e_7q4zAt3&MWHan;3M`FCpb#V7OH;tzcmO{?i zOpK5#FB_GPV9GA%6OpcI`S*Gj|ES+fjkex{M&wc*u9IHV@p5D1ivXgtR&W@ml@Vf_ zHVDm@YA_r4as$PsW_8|jydIN0(%_?C|(N$B=GzpYS^n<*{%hB zG1$Hm2no+G2&&U>oaxTq$;PF!3_fNL>+ZaxQQsWR*Nm^}(cPhQ?P#U>ZP&Vm{G9aQ z@%$@vYhd%^W25Y${O*mk)bM2)ZQsqV$_YhlVs*AWQ<-}59`_??MPK!HcmDaOr~agFiC-Kb^X}6nk9+aim%+=YBovr4CE5#6W%6ON2on5a}@o2 zntVzryolrPrOf-w#yatU5WCCEMz3sqzWwwq^!vbt#y3HJiq7;DMb{SDloP@IYr3;C z=_0K>BX0?lFi$zRtJKxQ%*+bTWyy!tza!2MV1Hl6$Hl46#0mnJ1))JCvB6LFgM(29 zeXxfwXCui(MdAeUDaJIRGaF7VOYWx9t&K@3$d=RD1e?!A1Z;k%rL+urS2guS8B0FA z-}}@a(k6gFmH#_yN1pQjG2|nrMNV{F)UoxOkMY>kQil(NHnp6~&<{I{a=2~f-hz*p zlNbKG*wBn`^?JrmlWjR4oR@3^S9dS|Mn^|Kf*k1zLJRns`No_Y^Z5DLJieyjWCYH9 zxLEFKe{%jui5FYU@AnFn_s%KT_k$6|2q~2qtPl4vQJ$X6i?7-zygKI*1bNyYuvEW%f3~qKhNn?( z^?GCn9PEGm5hfc2&ig1z>1DtmKjC7A`C{wSRoN?xdyr4E*C{w0q48I;&Rfe+;pGZG z?kQONXkem+l;-Bvfc1<4A{v=K_2dq4H!}?fb5E0ouZf#HqIfp#G-Yt+3sEPF#%hlJ{ zGX=4DJ-Ez|3db{P|N32Bzy=~P#PAhigHMm`9|>kN=z>oym0GB=N(SAP#C&UO zI$*cijti{DJnZc(b3w#4fwtp@Cc{15J3Gqzbl&*m+`ow-lh!Bo6&pSM;4&65kAoDi z^YK)f*A+cz=`ymvBv*rFBGb#Wft==7$*T`N{3ZH+Urp9D!if17_&JPb7rT-1_G?x_ zkaq*opDD_K-7^^jBt=-f)yv{&fE{^_15Oq6fJ_ z-G6YDmk-zRy!r$g#-n3kn7V1XY0h_sYP*N7ahKNi^IgabhvW>7M+=_-H}?0CCC*CO z;r0|Mnf80boPIWHn%Z-2Hu~r|2A4Y4Aor5o$K#-uG#2gX%1ZWuo_9kACdMC+f684s zM-(M`p5F{!E{$}haqR0K>s;aH+@54F*&J2A&r|XFdaM<4UaJ6JYTyX{O=SLmDYM@nrKGv-5 zLCMi6+@>Ys$TUhEK6#*w~si zmWhN6XK|mc$M3EVIP}_l)^NTpf#2{sBEFh!efk7;vYjA6Y*YbQYO!sZhY{XF$xN?Y zoN8O(+vb-XTj1*{GEOuqAkF4?>Iv&z+u+ zAf}gq|KOm|CZEbP&A*P$Ab1XOS|*?nBF$ovImCq6j&&5ir$L5>$Mdz{9oYlLqZ1VJ zeaSZHCN8Ptt%0Mg3IW!{m|$|of$~gm`pA-9f&VHd{LTAcV05ogAzgsD_@%hS3tv+- zy}M~s({1=73Sg^kp;+kW6ZrB_{e{I$anzB3!550(FmBfK75=0Ffr~M}D8{s)7E!uz z51LO9LAYCrDFl~VnT(M{Dq$~(@CF2t656%qN=pm#$Qo+kp5yW>k>)Kl`a1}@?q<%> z(z5uFJx1_h9hIVGLbwL-*gDA{DPWu4LfZSoY5a@BLwb@I-ToLud$kPi-$kj^>}RaMU$q!-*$Rv zY7(IXqAbIWKi>@=2qaX8VL&F{l0WUG1O4NY$I&$YBGAs>2Q1O*nG28wqrp3a#lOBH z5_3Wh{(&1+PX6=dfWYKy<<`!0dvuawlNAu=-Iv@zhuaNk*E#~a>TKYHh3;nSkRI?N zagkgDLed26rjbBw@lP(9^~Zjzcnr#eY1enA6P=IPtM0$tHP_}Bhohmbl4Ho>rqMv< zs3;>7Y&rJJ{P!*!FR{d8jnx%MnpXJ}m8!#9!87o|g{`Bz5TRG<(FEJZ-1d}e;-yFJ zf%DGUYU=0;@g4_?>mxwqO2Dkw>J>H#hk1;^-5-iD59^!juIss*;+iqzJfV{R1tT-k2O1Z1G%!Dcx6rujFm?$UyV%#qW9|_4|sSA5F{@Y)MaKbKLcg z7?fwac^oXua1<(B^ZKsS%#J82C`6qi71Bp{p>g@$E+HC=e zlX^lzz4%MK4+Y}yr5W(RJ^Gd@N32hwc=p3CJ*jk5vbl7gqS0(|<2i3OVzp%Vm3J;} zXI@bs7CRZsC$`ERi=m^l>Fy_^MVaCIh`$5NmN!yUzc}6%Y^E9Wu-sxdGatG&Uz{s1 z+?LSFDBLFNBr(glrrJ3)*!1uq`IA$RFSCDaZ$`3MuZmO=N5i|y z2UiM9nYU7A7XQ1s!AJkJ;~Mv5A}0ixDH8yE>64(V{<~AdAw569xVARI;1$x*7Tg8g zFniDGmi30^G|{8l-`8J<(|bxcZ`bbp+XZ7~aoW8`j=tng@qZ#S{_@2FD7S)KApWq9 zm9uTU#q+*lGUzCqDJL-2SV7sOe{@tWof;>xolu_M+rIQ?cxz#{1@vN}cQR>bXn1q1 zmFnBvTzc&jdfs=k$rqgYYk#a!8jmp_pARsWuD?&Q8#@SO6(90_SWU?rttFm5Y~1+N z^rfH_pLw1#F)poC0jV;U_e-&zbNv1#k3BK_vc^-zdtHrYD<8`8Ce~Y%=Bh)D)VneS zf=R9dyCZ2UO;*WduDk=n9jtl9hV8s3lYzt>V%JXvxvI^k)2*CAETFw~qY`k$1S`igdA5YHDWEzkyz!NA)(TVTE^cJUjQxpTbVUm}HVVWhu_R z;zm-UMne&a7N?PV4g8~#Rnkj)E_h!b|7Kylk2RWB?xFnY$6u(H`I4;*g9lXeZ^mQe zmsYrDhzM|6i>rz1E^f~@7izzzx7=;b!kpStsy;-P#kc4S$|4$^GQv6yMe(2P`UT#EAx<2K1~aynMSFG0)Fn<7c=I6nB;U@oszSd*rn z*;raFt&*^I+({rnt$X2|GK2K^+9IWYvWIYDbs=*|(mMX#j{3Ik-hyt6VzXQacJEA) z)vOUE(}MoU=&CvM^7=FH@YG~(Qt`ttU2`Md_F)EX0fIEdXdE}k!QMZK{*p?aN^Nt{)2wcFWE6{(cV^<|D!;@dgu z@l@aOPN|Sd;aGfNQiua#3_Qc9JsJ*4I9vsln>svaKfTuNVpl(A3z@Z7`nTVn$Dh1T z8vNM2y+tac4`1H$eP|e7QQ0!PiJ~-b+?-gvzeePnMyC<}YFR1 z&ImXtKG{$=ia4&3oigY%odA$y_zvBJoWWD=cJt=ZoRd_&ug z`xky2{e*6oAZQW3-A!-VeVuH7f|i;lC`u84tg?#mwa7!W_HC2}uUdIKO5@y`Kau*mFC6h0$SsBwFnn2PG)oUz?%X7kHCuXKmwK?5sBZZ4 z?CIv~;*>;vZP;Utw`lN8AgO(uTd)={IGr)-oRl^ElUD=A9wyoRNx)-9LKzx;Ym4`O zCb;Ob!MhkCVQX&~<(|Fdxpi+@HxU+N=fE11nQ3`4$f4D`lLFyxy+qr>q~8K!oa##j zDWRbW0wGp?WY|)NWu+V=E8BM!4cs{-M|QY-{mV=)M)S@k_dVyPS7|%q8#Y%3UQ)l- z3BR*7*H|1>Y$mai$%K|1**oGG(5l31l6V1if;h*d;lvsG z`D^nu3h7_KP?;8_lBg`w$b2U&M~;9`N0*Q(zs>na&ueIi5a0?bD>c#9mx*Nj?t;bG z{$7t2w+T-bWm3=4@mY!W^#o6P&Z%?fC%F`98mvfF=62uq?hIDv-VS`SD-=6wxMV+h z+L+O1-#)1JW2ewaV$KtSBc`9FyNLX}@R?$Jq=w70wcSD1%PP8AdiF0jK98Ok8TjhM zXnHe0P1bBpO<`TgDcFy<_EDhH>)~s0_W~6jvYdQnBO2Oym*W$wL7*tI^|QJB9!~t; z+$;RgpYbf7x>l)sRGdFGPDhs~o4b3S-u3ct=F3)hcWI{iMpM)IRSRUV6JBqkSeCKr zs*;1u^d*qbT_3@0bUK+b0;de-i|?3k#HHB<-{_nztvuH~1kavhOKWnFAcWIr6`piG|T zZAZqqvo5hI8?Nfvm>lcJ-|>tiCUPy~Os+i263qAD8JSISurO+u{bv!RvSrG&yQl+$}G7;;Ui=O-_@I*&QkE6qheHzy$U%k;W&*cnQu*gShGos zUX5wCIEL_pX*(ZLlv{ku{H{FPLwO=UWqlx3bB)TuY#EpGZ-JbCry$aMEKM!v4{r3s z$C)cIJ!X3gz|H%wMMK#QY$cO0#qn!oQuCFm`mWWBa@p3tF#Ct><9xSU#$4CEnM8d0 ze|$anvDfBn+|1_CN+C5>wK0vFI(*mVM5wXZKx$->?`$@Wz0%JC0rk9MEqCm%ar;UF zFkda6oVegk-k`Yqo%a92Z^)^v`g-+!pYZ!JkpO$+7qeSOTFpzO(1%sZpgg71LX#h?&-0svpb`2t z;~K@R-)vUe-A}u=gnk(N9xAqNY^A#M-Q7GQb6lt)kZ0NGoaaqn#45N1u(B~g@n>>o zYeNGIrtFGX414;XjD<;m!tNtaq%LpfLNRbmzA8EW(V*`so-Ajl1wF!@RH~ zeZo32?(!!N{%F3*m-vTM#rPL+=AAmO}ZyK{_9tU^6(1-MMl{2ST#JS}qE@%_!r z4hH*@w869W%>eNW9+54vD!UD&cI+TMnr~%*7yBtRsO6&iM`+#r=x2%4I@`_0$hy=F9A-tT@3 z14}OBqkj^%XPx?e2+`pQ1-M=J!jiyI5Mn>iag24e_F@ISx3Rso7;Elzwm$b(*I zM8ousk89B_Rf`~+&dZZ=kg&QcNo#gsWC=aB=9Yn=zmWe2PdWw;bo?*_gFG9IwIBa( z_ZDcD;E)p&ua4U9G06Y>{x5uU;XO)d;cStiGqJ#dDdTw#;`x7{4*n%VFGNaF^|ZAD z3*PaA&?egd&ugHj7Xc->n3Sl5G6(ou*+NG~`XH(mI=@I^*t#}F6$Kkuz6 z4eDO{_Mesqkl@7s`Q!in=nif0a$POjcB7P!P>=ZA;{Uy&|JkDe2%Io-_yYkPi@e%s}wiW28EUvf;*9M2S@TjgLC5SjQ@O1To zqccx^JvUAThWr*IgOkcA(uq32@aU0lUvw2XQ?xiw{2Hq6iwZz#sVK`suRIi#4@pgV z1&I%s5z`eN6doIGxM6Aox|G=6I)Do$yc9%$E_Yh!H}9+vFvaND@u3EY4QO)e@>%zX z;35x^z65}GOfyMdyn0VCFdFFNjK3dmtCVZeaQ z_rkb0fmUy|t$5A|q~$VHx;jwI-ZBXo z*kMhoH?IN5Xf9T5ThfCf-Le~08|5pWDxU~sbbCZJqDZ^+0!3G3W@&*&UXeqx;X7;6 zo_N+Qfm|k=iGsPhezo8rQF1)+?|*K6L{vP(^K0g6?7$}XTA&FReod|x1_c3Qud|Kf z0oS?@vpT!~Jcyhu%oNH5>S+~4#ZXkEp+lN#XRBe>%)BmqM>s%KJ56VM*z zh)YkJ;Zop;m=AR9hiqZ5E5Q8H!`1-2vC)agz1G8aakPxbF8{lx| zVPjuc$JIbTg?N4tM=l-&I_yA6@A|WO>kS)u&YLTWB0>tpngz@*i%*uwNN9lqbdowW zoS|t~;R`6e-)Echzz9^#sY&cc&1`}yz+h^Un!E}*?m?axcnQ>~w6|4xLme>w8+*N` zBIO2Po#Dm>^ofed?$&-#h-PwofrfSwhOmHqyy57{^*2E&-q! Date: Mon, 8 May 2017 10:09:17 +0800 Subject: [PATCH 0210/3256] Remove the feenableexcept in initPaddle. --- paddle/capi/Main.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/paddle/capi/Main.cpp b/paddle/capi/Main.cpp index 7f24561e9aa..78c43949dfe 100644 --- a/paddle/capi/Main.cpp +++ b/paddle/capi/Main.cpp @@ -25,7 +25,6 @@ limitations under the License. */ static void initPaddle(int argc, char** argv) { paddle::initMain(argc, argv); paddle::initPython(argc, argv); - feenableexcept(FE_INVALID | FE_DIVBYZERO | FE_OVERFLOW); } extern "C" { -- GitLab From 886e4becc2fffb7ec5e8313e154a4da9ad6988cd Mon Sep 17 00:00:00 2001 From: liaogang Date: Mon, 8 May 2017 10:49:37 +0800 Subject: [PATCH 0211/3256] fix gpu_img_conv2 float precision issue --- paddle/gserver/tests/test_NetworkCompare.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/paddle/gserver/tests/test_NetworkCompare.cpp b/paddle/gserver/tests/test_NetworkCompare.cpp index 4db30f37a5b..ff1c195cee6 100644 --- a/paddle/gserver/tests/test_NetworkCompare.cpp +++ b/paddle/gserver/tests/test_NetworkCompare.cpp @@ -261,9 +261,12 @@ TEST(Compare, img_conv2) { std::string config_file_a = "./gserver/tests/img_conv_a.conf"; std::string config_file_b = "./gserver/tests/img_conv_c.conf"; bool useGpu = FLAGS_use_gpu; + double eps = FLAGS_checkgrad_eps; FLAGS_use_gpu = true; + FLAGS_checkgrad_eps = 1e-3; compareNetwork(config_file_a, config_file_b); FLAGS_use_gpu = useGpu; + FLAGS_checkgrad_eps = eps; } #endif -- GitLab From faf26f0a6d3828ba6edea238a2b8450d51fdb4ae Mon Sep 17 00:00:00 2001 From: liaogang Date: Mon, 8 May 2017 11:09:04 +0800 Subject: [PATCH 0212/3256] force delete packages before unit test --- 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 f1743813abc..9e72c68d4bb 100644 --- a/paddle/scripts/docker/build.sh +++ b/paddle/scripts/docker/build.sh @@ -34,7 +34,7 @@ cmake .. \ -DCMAKE_EXPORT_COMPILE_COMMANDS=ON make -j `nproc` if [ ${WITH_TESTING:-OFF} == "ON" ] && [ ${RUN_TEST:-OFF} == "ON" ] ; then - pip uninstall -y py-paddle paddle + pip uninstall -y py-paddle paddle || true ctest -V fi make install -- GitLab From 33fb2d6b4fb9e8ab317560733d65c848ff1b3fdf Mon Sep 17 00:00:00 2001 From: Liu Yiqun Date: Mon, 8 May 2017 04:05:58 +0000 Subject: [PATCH 0213/3256] Add some annotations to different OpenBLAS versions. --- cmake/external/openblas.cmake | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cmake/external/openblas.cmake b/cmake/external/openblas.cmake index 97b6768decb..9d3428cc5bf 100644 --- a/cmake/external/openblas.cmake +++ b/cmake/external/openblas.cmake @@ -27,9 +27,12 @@ IF(NOT ${CBLAS_FOUND}) SET(COMMON_ARGS CC=${CMAKE_C_COMPILER} NO_LAPACK=1 NO_SHARED=1) 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") SET(OPTIONAL_ARGS HOSTCC=${HOST_C_COMPILER} TARGET=ARMV7 ARM_SOFTFP_ABI=1 USE_THREAD=0 libs) ELSEIF(RPI) + # use hardfp SET(OPENBLAS_COMMIT "v0.2.19") SET(OPTIONAL_ARGS HOSTCC=${HOST_C_COMPILER} TARGET=ARMV7 USE_THREAD=0 libs) ELSE() -- GitLab From e5b10732b2e30f8015d1c49ce76ac722dba961d9 Mon Sep 17 00:00:00 2001 From: liaogang Date: Mon, 8 May 2017 16:28:53 +0800 Subject: [PATCH 0214/3256] change lapack to lapacke --- paddle/utils/DynamicLoader.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/paddle/utils/DynamicLoader.cpp b/paddle/utils/DynamicLoader.cpp index 87c36eae6fb..76cf3c30061 100644 --- a/paddle/utils/DynamicLoader.cpp +++ b/paddle/utils/DynamicLoader.cpp @@ -165,8 +165,8 @@ void GetWarpCTCDsoHandle(void** dso_handle) { void GetLapackDsoHandle(void** dso_handle) { #if defined(__APPLE__) || defined(__OSX__) - GetDsoHandleFromSearchPath(FLAGS_lapack_dir, "liblapack.dylib", dso_handle); + GetDsoHandleFromSearchPath(FLAGS_lapack_dir, "liblapacke.dylib", dso_handle); #else - GetDsoHandleFromSearchPath(FLAGS_lapack_dir, "liblapack.so", dso_handle); + GetDsoHandleFromSearchPath(FLAGS_lapack_dir, "liblapacke.so", dso_handle); #endif } -- GitLab From 724130258b8c70c35cb271f6dd483082ae2a705b Mon Sep 17 00:00:00 2001 From: liaogang Date: Mon, 8 May 2017 16:41:38 +0800 Subject: [PATCH 0215/3256] remove cmake python output --- cmake/util.cmake | 1 - 1 file changed, 1 deletion(-) diff --git a/cmake/util.cmake b/cmake/util.cmake index 966e0a7bf60..b828eef322b 100644 --- a/cmake/util.cmake +++ b/cmake/util.cmake @@ -149,7 +149,6 @@ endfunction() # Create a python unittest using run_python_tests.sh, # which takes care of making correct running environment function(add_python_test TEST_NAME) - message("PYTHON: ${PYTHON_EXECUTABLE}") add_test(NAME ${TEST_NAME} COMMAND bash ${PROJ_ROOT}/paddle/scripts/run_python_tests.sh ${USE_VIRTUALENV_FOR_TEST} ${PYTHON_EXECUTABLE} ${ARGN} -- GitLab From e9ddff0047283beeeebf15ec9c64c6e0bc35cf95 Mon Sep 17 00:00:00 2001 From: liaogang Date: Mon, 8 May 2017 19:17:27 +0800 Subject: [PATCH 0216/3256] modify img_conv2 unit test --- paddle/gserver/tests/config_file_a.conf | 28 ++++++++++++++++++++ paddle/gserver/tests/config_file_b.conf | 28 ++++++++++++++++++++ paddle/gserver/tests/test_NetworkCompare.cpp | 7 ++--- 3 files changed, 58 insertions(+), 5 deletions(-) create mode 100644 paddle/gserver/tests/config_file_a.conf create mode 100644 paddle/gserver/tests/config_file_b.conf diff --git a/paddle/gserver/tests/config_file_a.conf b/paddle/gserver/tests/config_file_a.conf new file mode 100644 index 00000000000..5e4668f4fff --- /dev/null +++ b/paddle/gserver/tests/config_file_a.conf @@ -0,0 +1,28 @@ +#edit-mode: -*- python -*- +# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from paddle.trainer_config_helpers import * + +settings(batch_size=10) +data = data_layer(name ="input", size=8*16*16) +conv = img_conv_layer(input=data, filter_size=1, filter_size_y=1, + num_channels=8, + num_filters=16, stride=1, + bias_attr=True, + act=LinearActivation(), + groups=2, + layer_type="cudnn_conv") + +outputs(conv) diff --git a/paddle/gserver/tests/config_file_b.conf b/paddle/gserver/tests/config_file_b.conf new file mode 100644 index 00000000000..2de45466ced --- /dev/null +++ b/paddle/gserver/tests/config_file_b.conf @@ -0,0 +1,28 @@ +#edit-mode: -*- python -*- +# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from paddle.trainer_config_helpers import * + +settings(batch_size=10) +data = data_layer(name ="input", size=8*16*16) +conv = img_conv_layer(input=data, filter_size=1, filter_size_y=1, + num_channels=8, + num_filters=16, stride=1, + bias_attr=True, + act=LinearActivation(), + groups=2, + layer_type="exconv") + +outputs(conv) diff --git a/paddle/gserver/tests/test_NetworkCompare.cpp b/paddle/gserver/tests/test_NetworkCompare.cpp index ff1c195cee6..51b0e48bcc4 100644 --- a/paddle/gserver/tests/test_NetworkCompare.cpp +++ b/paddle/gserver/tests/test_NetworkCompare.cpp @@ -258,15 +258,12 @@ TEST(Compare, img_conv) { // Test cudnn_conv and exconv give the same result TEST(Compare, img_conv2) { - std::string config_file_a = "./gserver/tests/img_conv_a.conf"; - std::string config_file_b = "./gserver/tests/img_conv_c.conf"; + std::string config_file_a = "./gserver/tests/config_file_a.conf"; + std::string config_file_b = "./gserver/tests/config_file_b.conf"; bool useGpu = FLAGS_use_gpu; - double eps = FLAGS_checkgrad_eps; FLAGS_use_gpu = true; - FLAGS_checkgrad_eps = 1e-3; compareNetwork(config_file_a, config_file_b); FLAGS_use_gpu = useGpu; - FLAGS_checkgrad_eps = eps; } #endif -- GitLab From e094e095b5ea88cadf3c149ba06e0565146acac1 Mon Sep 17 00:00:00 2001 From: liaogang Date: Mon, 8 May 2017 19:19:44 +0800 Subject: [PATCH 0217/3256] change to lapacke --- paddle/utils/DynamicLoader.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/paddle/utils/DynamicLoader.cpp b/paddle/utils/DynamicLoader.cpp index 87c36eae6fb..76cf3c30061 100644 --- a/paddle/utils/DynamicLoader.cpp +++ b/paddle/utils/DynamicLoader.cpp @@ -165,8 +165,8 @@ void GetWarpCTCDsoHandle(void** dso_handle) { void GetLapackDsoHandle(void** dso_handle) { #if defined(__APPLE__) || defined(__OSX__) - GetDsoHandleFromSearchPath(FLAGS_lapack_dir, "liblapack.dylib", dso_handle); + GetDsoHandleFromSearchPath(FLAGS_lapack_dir, "liblapacke.dylib", dso_handle); #else - GetDsoHandleFromSearchPath(FLAGS_lapack_dir, "liblapack.so", dso_handle); + GetDsoHandleFromSearchPath(FLAGS_lapack_dir, "liblapacke.so", dso_handle); #endif } -- GitLab From b99891325afdd1cfe3a37ba27f7c0f9a3bd91afa Mon Sep 17 00:00:00 2001 From: liaogang Date: Mon, 8 May 2017 19:52:06 +0800 Subject: [PATCH 0218/3256] update file name --- paddle/gserver/tests/{config_file_a.conf => img_conv_cudnn.py} | 0 paddle/gserver/tests/{config_file_b.conf => img_conv_exconv.py} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename paddle/gserver/tests/{config_file_a.conf => img_conv_cudnn.py} (100%) rename paddle/gserver/tests/{config_file_b.conf => img_conv_exconv.py} (100%) diff --git a/paddle/gserver/tests/config_file_a.conf b/paddle/gserver/tests/img_conv_cudnn.py similarity index 100% rename from paddle/gserver/tests/config_file_a.conf rename to paddle/gserver/tests/img_conv_cudnn.py diff --git a/paddle/gserver/tests/config_file_b.conf b/paddle/gserver/tests/img_conv_exconv.py similarity index 100% rename from paddle/gserver/tests/config_file_b.conf rename to paddle/gserver/tests/img_conv_exconv.py -- GitLab From 96b33e1e6a33de6fee8276f60b10068080a9b3b3 Mon Sep 17 00:00:00 2001 From: liaogang Date: Mon, 8 May 2017 19:53:14 +0800 Subject: [PATCH 0219/3256] update file name --- paddle/gserver/tests/test_NetworkCompare.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/paddle/gserver/tests/test_NetworkCompare.cpp b/paddle/gserver/tests/test_NetworkCompare.cpp index 51b0e48bcc4..698a972b62b 100644 --- a/paddle/gserver/tests/test_NetworkCompare.cpp +++ b/paddle/gserver/tests/test_NetworkCompare.cpp @@ -258,8 +258,8 @@ TEST(Compare, img_conv) { // Test cudnn_conv and exconv give the same result TEST(Compare, img_conv2) { - std::string config_file_a = "./gserver/tests/config_file_a.conf"; - std::string config_file_b = "./gserver/tests/config_file_b.conf"; + std::string config_file_a = "./gserver/tests/img_conv_cudnn.py"; + std::string config_file_b = "./gserver/tests/img_conv_exconv.py"; bool useGpu = FLAGS_use_gpu; FLAGS_use_gpu = true; compareNetwork(config_file_a, config_file_b); -- GitLab From eef91cd811d78d083949c54d4c6865893263e55b Mon Sep 17 00:00:00 2001 From: liaogang Date: Mon, 8 May 2017 20:31:05 +0800 Subject: [PATCH 0220/3256] clang-format --- paddle/gserver/tests/img_conv_cudnn.py | 20 ++++++++++++-------- paddle/gserver/tests/img_conv_exconv.py | 20 ++++++++++++-------- 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/paddle/gserver/tests/img_conv_cudnn.py b/paddle/gserver/tests/img_conv_cudnn.py index 5e4668f4fff..3934607fa41 100644 --- a/paddle/gserver/tests/img_conv_cudnn.py +++ b/paddle/gserver/tests/img_conv_cudnn.py @@ -16,13 +16,17 @@ from paddle.trainer_config_helpers import * settings(batch_size=10) -data = data_layer(name ="input", size=8*16*16) -conv = img_conv_layer(input=data, filter_size=1, filter_size_y=1, - num_channels=8, - num_filters=16, stride=1, - bias_attr=True, - act=LinearActivation(), - groups=2, - layer_type="cudnn_conv") +data = data_layer(name="input", size=8 * 16 * 16) +conv = img_conv_layer( + input=data, + filter_size=1, + filter_size_y=1, + num_channels=8, + num_filters=16, + stride=1, + bias_attr=True, + act=LinearActivation(), + groups=2, + layer_type="cudnn_conv") outputs(conv) diff --git a/paddle/gserver/tests/img_conv_exconv.py b/paddle/gserver/tests/img_conv_exconv.py index 2de45466ced..ad5a8ba2bde 100644 --- a/paddle/gserver/tests/img_conv_exconv.py +++ b/paddle/gserver/tests/img_conv_exconv.py @@ -16,13 +16,17 @@ from paddle.trainer_config_helpers import * settings(batch_size=10) -data = data_layer(name ="input", size=8*16*16) -conv = img_conv_layer(input=data, filter_size=1, filter_size_y=1, - num_channels=8, - num_filters=16, stride=1, - bias_attr=True, - act=LinearActivation(), - groups=2, - layer_type="exconv") +data = data_layer(name="input", size=8 * 16 * 16) +conv = img_conv_layer( + input=data, + filter_size=1, + filter_size_y=1, + num_channels=8, + num_filters=16, + stride=1, + bias_attr=True, + act=LinearActivation(), + groups=2, + layer_type="exconv") outputs(conv) -- GitLab From 191a3268da8c22bab5bbdeced5e684fefcf7c8d3 Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Mon, 8 May 2017 17:42:10 -0700 Subject: [PATCH 0221/3256] fix according to comments --- doc/design/cluster_train/data_dispatch.md | 4 ++-- doc/design/cluster_train/master_process.md | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/design/cluster_train/data_dispatch.md b/doc/design/cluster_train/data_dispatch.md index f60c3b843d2..241902cca40 100644 --- a/doc/design/cluster_train/data_dispatch.md +++ b/doc/design/cluster_train/data_dispatch.md @@ -23,8 +23,8 @@ 在数据集可以被训练之前,文件需要预先被转换成PaddlePaddle集群内部的存储格式(RecordIO)。我们提供两个转换方式: -- 提供给用户本地转换的库,用户可以编写程序完成转换。 -- 用户可以上传自己的数据集,在集群运行MapReduce job完成转换。 +1. 用户在本地转换好再上传 +1. 用户上传数据后,在机群上运行转换程序 转换生成的文件名会是以下格式: diff --git a/doc/design/cluster_train/master_process.md b/doc/design/cluster_train/master_process.md index 949811b4f76..e0be8df6343 100644 --- a/doc/design/cluster_train/master_process.md +++ b/doc/design/cluster_train/master_process.md @@ -1,12 +1,12 @@ # Design Doc: Master Process -For an overview of master process' role, please refer to [distributed training design doc](./README.md). In this design doc we will discuss the master process in more details. The master will be implemented in [golang](https://golang.org/). +For an overview of master process' role, please refer to [distributed training design doc](./README.md). In this design doc we will discuss the master process in more details. The master will be implemented in [Go](https://golang.org/). ## Dataset -A dataset is represented by a list of files in *RecordIO* format on the distributed filesystem, each RecordIO file consists of multiple *blocks*, and each block has multiple data instances. +A dataset is a list of files in *RecordIO* format. A RecordIO file consists of chunks, whereas each chunk consists some records. ## Task Queue @@ -14,7 +14,7 @@ As mentioned in [distributed training design doc](./README.md), a *task* is a da ### Task Queue Creation -1. Each trainer will make an RPC call (using [golang rpc](https://golang.org/pkg/net/rpc/)) to the master process, telling it the RecordIO files representing the dataset specified by the user. Since every trainer will tell the master process the same dataset, only the first RPC call will be honored. +1. Each trainer will make an RPC call (using Go's [rpc](https://golang.org/pkg/net/rpc/) package) to the master process, telling it the RecordIO files representing the dataset specified by the user. Since every trainer will tell the master process the same dataset, only the first RPC call will be honored. The RPC interface is: ```go -- GitLab From adb6d43e0bbcc75c1a051bd4a64fc66779d79454 Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Mon, 8 May 2017 18:34:50 -0700 Subject: [PATCH 0222/3256] fix according to comments --- doc/design/cluster_train/master_process.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/design/cluster_train/master_process.md b/doc/design/cluster_train/master_process.md index e0be8df6343..d9c66559548 100644 --- a/doc/design/cluster_train/master_process.md +++ b/doc/design/cluster_train/master_process.md @@ -87,3 +87,5 @@ During the RPC call the master will do the following: ### Task Retry Logic When a task is dispatched to the trainer, the master will schedule a function for execution after the timeout duration (based on the moving average of task completion time). If the task entry in still in the pending queue, its timeout counter will increase by one, and the task will be moved to todo queue. If the timeout counter is above the threshold, the master will log the error and discard the task. + +Please note that since a timed out task could be completed after it has been dispatched for retry, so it is possible for a task to be processed multiple times. We do not try to prevent it from happening since it's fine to train on the same task multiple times due to the stochastic nature of the stochastic gradient decent algorithm. -- GitLab From 2b924ac85dac1312828a16a6e270908b0dd15c21 Mon Sep 17 00:00:00 2001 From: Liu Yiqun Date: Tue, 9 May 2017 11:29:12 +0800 Subject: [PATCH 0223/3256] Disable the finding of PythonLibs when WITH_PYTHON is set to OFF. --- cmake/external/python.cmake | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/cmake/external/python.cmake b/cmake/external/python.cmake index fc66d6b2154..f4d0daab06c 100644 --- a/cmake/external/python.cmake +++ b/cmake/external/python.cmake @@ -16,11 +16,13 @@ INCLUDE(ExternalProject) INCLUDE(python_module) FIND_PACKAGE(PythonInterp 2.7) -FIND_PACKAGE(PythonLibs 2.7) +IF(WITH_PYTHON) + FIND_PACKAGE(PythonLibs 2.7) +ENDIF(WITH_PYTHON) SET(py_env "") SET(USE_VIRTUALENV_FOR_TEST 1) -IF(PYTHONLIBS_FOUND AND PYTHONINTERP_FOUND) +IF(PYTHONINTERP_FOUND) find_python_module(pip REQUIRED) find_python_module(numpy REQUIRED) find_python_module(wheel REQUIRED) @@ -30,7 +32,7 @@ IF(PYTHONLIBS_FOUND AND PYTHONINTERP_FOUND) MESSAGE(FATAL_ERROR "Found Python Protobuf ${PY_GOOGLE.PROTOBUF_VERSION} < 3.0.0, " "please use pip to upgrade protobuf. pip install -U protobuf") ENDIF() -ELSE(PYTHONLIBS_FOUND AND PYTHONINTERP_FOUND) +ELSE(PYTHONINTERP_FOUND) MESSAGE(FATAL_ERROR "Please install python 2.7 before building PaddlePaddle.") ##################################### PYTHON ######################################## SET(PYTHON_SOURCES_DIR ${THIRD_PARTY_PATH}/python) @@ -217,7 +219,7 @@ ELSE(PYTHONLIBS_FOUND AND PYTHONINTERP_FOUND) LIST(APPEND external_project_dependencies python setuptools six cython wheel python-protobuf numpy) -ENDIF(PYTHONLIBS_FOUND AND PYTHONINTERP_FOUND) +ENDIF(PYTHONINTERP_FOUND) IF(WITH_PYTHON) INCLUDE_DIRECTORIES(${PYTHON_INCLUDE_DIR}) -- GitLab From 43493b2a59f17cd768875f15c09c3432c3955300 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Tue, 9 May 2017 14:05:35 +0800 Subject: [PATCH 0224/3256] Expose Inference in Python V2 API. --- python/paddle/v2/inference.py | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/python/paddle/v2/inference.py b/python/paddle/v2/inference.py index 1fea7917e15..b4bb3849693 100644 --- a/python/paddle/v2/inference.py +++ b/python/paddle/v2/inference.py @@ -5,15 +5,22 @@ import topology import minibatch from data_feeder import DataFeeder -__all__ = ['infer'] +__all__ = ['infer', 'Inference'] class Inference(object): """ Inference combines neural network output and parameters together to do inference. + + .. code-block:: python + + inferer = Inference(output_layer=prediction, parameters=parameters) + for data_batch in batches: + print inferer.infer(data_batch) + - :param outptut_layer: The neural network that should be inferenced. + :param output_layer: The neural network that should be inferenced. :type output_layer: paddle.v2.config_base.Layer or the sequence of paddle.v2.config_base.Layer :param parameters: The parameters dictionary. @@ -56,8 +63,14 @@ class Inference(object): item = [each_result[each_field] for each_field in field] yield item - def infer(self, field='value', **kwargs): + def infer(self, input, field='value', **kwargs): + """ + Infer a data by model. + :param input: input data batch. Should be python iterable object. + :param field: output field. + """ retv = None + kwargs['input'] = input for result in self.iter_infer_field(field=field, **kwargs): if retv is None: retv = [[] for i in xrange(len(result))] @@ -79,7 +92,7 @@ def infer(output_layer, parameters, input, feeding=None, field='value'): .. code-block:: python - result = paddle.infer(outptut_layer=prediction, + result = paddle.infer(output_layer=prediction, parameters=parameters, input=SomeData) print result -- GitLab From f3eb6e91469e98c3c0af9c2e86ae071bdbd6b657 Mon Sep 17 00:00:00 2001 From: liaogang Date: Tue, 9 May 2017 14:18:50 +0800 Subject: [PATCH 0225/3256] refine cblas.cmake --- cmake/cblas.cmake | 58 +++++++++++++++++++++++------------------------ 1 file changed, 28 insertions(+), 30 deletions(-) diff --git a/cmake/cblas.cmake b/cmake/cblas.cmake index 91663a16ba6..bb084192f86 100644 --- a/cmake/cblas.cmake +++ b/cmake/cblas.cmake @@ -33,20 +33,18 @@ find_library(MKL_INTEL_LP64 NAMES mkl_intel_lp64 PATHS ${MKL_ROOT}/lib ${MKL_ROOT}/lib/intel64) -if(MKL_INC_DIR AND MKL_CORE_LIB AND MKL_SEQUENTIAL_LIB AND MKL_INTEL_LP64) +if(MKL_LAPACK_INC_DIR AND MKL_INC_DIR AND MKL_CORE_LIB AND MKL_SEQUENTIAL_LIB AND MKL_INTEL_LP64) + set(CBLAS_FOUND ON) set(CBLAS_PROVIDER MKL) - set(CBLAS_INC_DIR ${MKL_INC_DIR}) - set(CBLAS_LIBRARIES ${MKL_INTEL_LP64} - ${MKL_SEQUENTIAL_LIB} - ${MKL_CORE_LIB}) + set(CBLAS_INC_DIR ${MKL_INC_DIR} ${MKL_LAPACK_INC_DIR}) + set(CBLAS_LIBRARIES ${MKL_INTEL_LP64} ${MKL_SEQUENTIAL_LIB} ${MKL_CORE_LIB}) + add_definitions(-DPADDLE_USE_MKL) add_definitions(-DLAPACK_FOUND) - message(STATUS "Found MKL (include: ${CBLAS_INC_DIR}, library: ${CBLAS_LIBRARIES})") - set(CBLAS_FOUND ON) - if(${MKL_LAPACK_INC_DIR}) - message(STATUS "Found lapack in MKL (include: ${MKL_LAPACK_INC_DIR})") - endif() - return() # return file. + + message(STATUS "Found MKL (include: ${MKL_INC_DIR}, library: ${CBLAS_LIBRARIES})") + message(STATUS "Found lapack in MKL (include: ${MKL_LAPACK_INC_DIR})") + return() endif() ## Then find atlas. @@ -68,21 +66,20 @@ find_path(ATLAS_CLAPACK_INC_DIR NAMES clapack.h PATHS ${ATLAS_INCLUDE_SEARCH_PATHS}) find_library(ATLAS_CBLAS_LIB NAMES cblas libcblas.so.3 PATHS ${ATLAS_LIB_SEARCH_PATHS}) -find_library(ATLAS_LIB NAMES lapack_atlas liblapack_atlas.so.3 +find_library(ATLAS_CLAPACK_LIB NAMES lapack_atlas liblapack_atlas.so.3 PATHS ${ATLAS_LIB_SEARCH_PATHS}) -if(ATLAS_INC_DIR AND ATLAS_CBLAS_LIB AND ATLAS_LIB AND NOT CBLAS_FOUND) +if(ATLAS_CLAPACK_INC_DIR AND ATLAS_INC_DIR AND ATLAS_CBLAS_LIB AND ATLAS_CLAPACK_LIB) + set(CBLAS_FOUND ON) set(CBLAS_PROVIDER ATLAS) - set(CBLAS_INC_DIR ${ATLAS_INC_DIR}) - set(CBLAS_LIBRARIES ${ATLAS_LIB} ${ATLAS_CBLAS_LIB}) + set(CBLAS_INC_DIR ${ATLAS_INC_DIR} ${ATLAS_CLAPACK_INC_DIR}) + set(CBLAS_LIBRARIES ${ATLAS_CLAPACK_LIB} ${ATLAS_CBLAS_LIB}) + add_definitions(-DPADDLE_USE_ATLAS) add_definitions(-DLAPACK_FOUND) - message(STATUS "Found ATLAS (include: ${CBLAS_INC_DIR}, library: ${CBLAS_LIBRARIES})") - set(CBLAS_FOUND ON) - if(ATLAS_CLAPACK_INC_DIR) - set(CBLAS_INC_DIR ${CBLAS_INC_DIR} ${ATLAS_CLAPACK_INC_DIR}) - message(STATUS "Found lapack in ATLAS (include: ${ATLAS_CLAPACK_INC_DIR})") - endif() + + message(STATUS "Found ATLAS (include: ${ATLAS_INC_DIR}, library: ${CBLAS_LIBRARIES})") + message(STATUS "Found lapack in ATLAS (include: ${ATLAS_CLAPACK_INC_DIR})") return() endif() @@ -107,16 +104,16 @@ find_path(OPENBLAS_LAPACKE_INC_DIR NAMES lapacke.h find_library(OPENBLAS_LIB NAMES openblas PATHS ${OPENBLAS_LIB_SEARCH_PATHS}) -if(OPENBLAS_INC_DIR AND OPENBLAS_LIB) +if(OPENBLAS_LAPACKE_INC_DIR AND OPENBLAS_INC_DIR AND OPENBLAS_LIB) + set(CBLAS_FOUND ON) set(CBLAS_PROVIDER OPENBLAS) - set(CBLAS_INC_DIR ${OPENBLAS_INC_DIR}) + set(CBLAS_INC_DIR ${OPENBLAS_INC_DIR} ${OPENBLAS_LAPACKE_INC_DIR}) set(CBLAS_LIBRARIES ${OPENBLAS_LIB}) + add_definitions(-DLAPACK_FOUND) - message(STATUS "Found OpenBLAS (include: ${CBLAS_INC_DIR}, library: ${CBLAS_LIBRARIES})") - set(CBLAS_FOUND ON) - if(OPENBLAS_LAPACKE_INC_DIR) - message(STATUS "Found lapack in OpenBLAS (include: ${OPENBLAS_LAPACKE_INC_DIR})") - endif() + + message(STATUS "Found OpenBLAS (include: ${OPENBLAS_INC_DIR}, library: ${CBLAS_LIBRARIES})") + message(STATUS "Found lapack in OpenBLAS (include: ${OPENBLAS_LAPACKE_INC_DIR})") return() endif() @@ -145,9 +142,10 @@ find_library(REFERENCE_CBLAS_LIBRARY NAMES cblas PATHS ${REFERENCE_CBLAS_LIB_SEARCH_PATHS}) if (REFERENCE_CBLAS_INCLUDE_DIR AND REFERENCE_CBLAS_LIBRARY) + set(CBLAS_FOUND ON) set(CBLAS_PROVIDER REFERENCE) set(CBLAS_INC_DIR ${REFERENCE_CBLAS_INCLUDE_DIR}) set(CBLAS_LIBRARIES ${REFERENCE_CBLAS_LIBRARY}) - message(STATUS "Found reference-cblas (include: ${CBLAS_INC_DIR}, library: ${CBLAS_LIBS})") - set(CBLAS_FOUND ON) + + message(STATUS "Found reference-cblas (include: ${CBLAS_INC_DIR}, library: ${CBLAS_LIBRARIES})") endif() -- GitLab From 6dc58cdd2585873e95ccde3ace0696e54c6a6c53 Mon Sep 17 00:00:00 2001 From: liaogang Date: Tue, 9 May 2017 14:21:58 +0800 Subject: [PATCH 0226/3256] modify float precision in img_conv2 --- paddle/gserver/tests/test_NetworkCompare.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/paddle/gserver/tests/test_NetworkCompare.cpp b/paddle/gserver/tests/test_NetworkCompare.cpp index 698a972b62b..40e662b22ba 100644 --- a/paddle/gserver/tests/test_NetworkCompare.cpp +++ b/paddle/gserver/tests/test_NetworkCompare.cpp @@ -261,9 +261,12 @@ TEST(Compare, img_conv2) { std::string config_file_a = "./gserver/tests/img_conv_cudnn.py"; std::string config_file_b = "./gserver/tests/img_conv_exconv.py"; bool useGpu = FLAGS_use_gpu; + double eps = FLAGS_checkgrad_eps; FLAGS_use_gpu = true; + FLAGS_checkgrad_eps = 1e-2; compareNetwork(config_file_a, config_file_b); FLAGS_use_gpu = useGpu; + FLAGS_checkgrad_eps = eps; } #endif -- GitLab From 1d5d9da9db2dfed356de33dd07ae96769ca52f10 Mon Sep 17 00:00:00 2001 From: liaogang Date: Tue, 9 May 2017 15:15:02 +0800 Subject: [PATCH 0227/3256] refine cblas.cmake --- cmake/cblas.cmake | 3 ++- cmake/external/openblas.cmake | 2 +- paddle/math/MathFunctions.h | 29 ++++++++++++++++++++---- paddle/math/tests/test_matrixCompare.cpp | 2 +- 4 files changed, 28 insertions(+), 8 deletions(-) diff --git a/cmake/cblas.cmake b/cmake/cblas.cmake index bb084192f86..913f711afff 100644 --- a/cmake/cblas.cmake +++ b/cmake/cblas.cmake @@ -110,6 +110,7 @@ if(OPENBLAS_LAPACKE_INC_DIR AND OPENBLAS_INC_DIR AND OPENBLAS_LIB) set(CBLAS_INC_DIR ${OPENBLAS_INC_DIR} ${OPENBLAS_LAPACKE_INC_DIR}) set(CBLAS_LIBRARIES ${OPENBLAS_LIB}) + add_definitions(-DPADDLE_USE_OPENBLAS) add_definitions(-DLAPACK_FOUND) message(STATUS "Found OpenBLAS (include: ${OPENBLAS_INC_DIR}, library: ${CBLAS_LIBRARIES})") @@ -146,6 +147,6 @@ if (REFERENCE_CBLAS_INCLUDE_DIR AND REFERENCE_CBLAS_LIBRARY) set(CBLAS_PROVIDER REFERENCE) set(CBLAS_INC_DIR ${REFERENCE_CBLAS_INCLUDE_DIR}) set(CBLAS_LIBRARIES ${REFERENCE_CBLAS_LIBRARY}) - + add_definitions(-DPADDLE_USE_REFERENCE_CBLAS) message(STATUS "Found reference-cblas (include: ${CBLAS_INC_DIR}, library: ${CBLAS_LIBRARIES})") endif() diff --git a/cmake/external/openblas.cmake b/cmake/external/openblas.cmake index 5b042fec012..18ac74aa6f7 100644 --- a/cmake/external/openblas.cmake +++ b/cmake/external/openblas.cmake @@ -36,7 +36,7 @@ IF(NOT ${CBLAS_FOUND}) INSTALL_DIR ${CBLAS_INSTALL_DIR} BUILD_IN_SOURCE 1 BUILD_COMMAND ${CMAKE_MAKE_PROGRAM} FC=${CMAKE_Fortran_COMPILER} CC=${CMAKE_C_COMPILER} HOSTCC=${CMAKE_C_COMPILER} NO_LAPACK=1 DYNAMIC_ARCH=1 NO_SHARED=1 libs netlib - INSTALL_COMMAND ${CMAKE_MAKE_PROGRAM} install NO_SHARED=1 PREFIX= + INSTALL_COMMAND ${CMAKE_MAKE_PROGRAM} install NO_SHARED=1 NO_LAPACK=1 PREFIX= UPDATE_COMMAND "" CONFIGURE_COMMAND "" ) diff --git a/paddle/math/MathFunctions.h b/paddle/math/MathFunctions.h index c8559eefd83..c1d233b6cbd 100644 --- a/paddle/math/MathFunctions.h +++ b/paddle/math/MathFunctions.h @@ -18,17 +18,36 @@ limitations under the License. */ #ifdef PADDLE_USE_MKL #include #include -#else -extern "C" { -#include -} +#endif + #ifdef PADDLE_USE_ATLAS extern "C" { +#include #include } -#else +#endif + +#ifdef PADDLE_USE_OPENBLAS +#include #include #endif + +#ifdef PADDLE_USE_REFERENCE_CBLAS +extern "C" { +#include +} +#endif + +#ifndef LAPACK_FOUND +extern "C" { +int LAPACKE_sgetrf(int matrix_layout, int m, int n, +int LAPACKE_dgetrf(int matrix_layout, int m, int n, + double* a, int lda, int* ipiv); +int LAPACKE_sgetri(int matrix_layout, int n, float* a, + int lda, const int* ipiv); +int LAPACKE_dgetri(int matrix_layout, int n, double* a, + int lda, const int* ipiv); +} #endif #include diff --git a/paddle/math/tests/test_matrixCompare.cpp b/paddle/math/tests/test_matrixCompare.cpp index 2bdcab4d866..5a0dffe086c 100644 --- a/paddle/math/tests/test_matrixCompare.cpp +++ b/paddle/math/tests/test_matrixCompare.cpp @@ -247,7 +247,7 @@ TEST(Matrix, unary) { << "1. Simply issue `sudo apt-get install liblapacke-dev` to " "avoid re-build source code. \n" << "2. Install MKL/Openblas/ATLAS and re-build PaddlePaddle " - "source code." + "source code."; #endif } } -- GitLab From e2644fffc5b62293b1f12ff56bcd5d470b2e2e3d Mon Sep 17 00:00:00 2001 From: liaogang Date: Tue, 9 May 2017 15:29:48 +0800 Subject: [PATCH 0228/3256] add semicolon --- paddle/math/MathFunctions.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paddle/math/MathFunctions.h b/paddle/math/MathFunctions.h index c1d233b6cbd..315e16bff77 100644 --- a/paddle/math/MathFunctions.h +++ b/paddle/math/MathFunctions.h @@ -47,7 +47,7 @@ int LAPACKE_sgetri(int matrix_layout, int n, float* a, int lda, const int* ipiv); int LAPACKE_dgetri(int matrix_layout, int n, double* a, int lda, const int* ipiv); -} +}; #endif #include -- GitLab From e22aa12b5246a1ec5d58e8daab5eb04308a475af Mon Sep 17 00:00:00 2001 From: liaogang Date: Tue, 9 May 2017 15:50:07 +0800 Subject: [PATCH 0229/3256] fix a bug --- paddle/math/MathFunctions.h | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/paddle/math/MathFunctions.h b/paddle/math/MathFunctions.h index 315e16bff77..8c5f9dd479c 100644 --- a/paddle/math/MathFunctions.h +++ b/paddle/math/MathFunctions.h @@ -32,14 +32,9 @@ extern "C" { #include #endif -#ifdef PADDLE_USE_REFERENCE_CBLAS -extern "C" { -#include -} -#endif - #ifndef LAPACK_FOUND extern "C" { +#include int LAPACKE_sgetrf(int matrix_layout, int m, int n, int LAPACKE_dgetrf(int matrix_layout, int m, int n, double* a, int lda, int* ipiv); @@ -47,7 +42,7 @@ int LAPACKE_sgetri(int matrix_layout, int n, float* a, int lda, const int* ipiv); int LAPACKE_dgetri(int matrix_layout, int n, double* a, int lda, const int* ipiv); -}; +} #endif #include -- GitLab From f23e800f6727bacadb603e56a013ec1f815f24f3 Mon Sep 17 00:00:00 2001 From: liaogang Date: Tue, 9 May 2017 17:26:15 +0800 Subject: [PATCH 0230/3256] fix a bug --- paddle/math/MathFunctions.h | 1 + 1 file changed, 1 insertion(+) diff --git a/paddle/math/MathFunctions.h b/paddle/math/MathFunctions.h index 8c5f9dd479c..a941c548d43 100644 --- a/paddle/math/MathFunctions.h +++ b/paddle/math/MathFunctions.h @@ -36,6 +36,7 @@ extern "C" { extern "C" { #include int LAPACKE_sgetrf(int matrix_layout, int m, int n, + float* a, int lda, int* ipiv); int LAPACKE_dgetrf(int matrix_layout, int m, int n, double* a, int lda, int* ipiv); int LAPACKE_sgetri(int matrix_layout, int n, float* a, -- GitLab From 950aa8a178aacad6751627d8402063e329966748 Mon Sep 17 00:00:00 2001 From: wuyi05 Date: Tue, 9 May 2017 17:26:57 +0800 Subject: [PATCH 0231/3256] overwrite env with kwargs --- python/paddle/v2/__init__.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/python/paddle/v2/__init__.py b/python/paddle/v2/__init__.py index 0942334ca2a..70c8ec0baa6 100644 --- a/python/paddle/v2/__init__.py +++ b/python/paddle/v2/__init__.py @@ -43,14 +43,15 @@ __all__ = [ def init(**kwargs): args = [] + args_dict = {} # NOTE: append arguments if they are in ENV for ek, ev in os.environ.iteritems(): - if ek.startswith("PADDLE_"): - args.append('--%s=%s' % (ek.replace("PADDLE_", "").lower(), - str(ev))) + if ek.startswith("PADDLE_INIT_"): + args_dict[ek.replace("PADDLE_INIT_", "").lower()] = str(ev) + args_dict.update(kwargs) # NOTE: overwrite arguments from ENV if it is in kwargs - for key in kwargs.keys(): + for key in args_dict.keys(): args.append('--%s=%s' % (key, str(kwargs[key]))) api.initPaddle(*args) -- GitLab From dee0d9215bb27e5d4eab338fee06d6508c7a7b1c Mon Sep 17 00:00:00 2001 From: liaogang Date: Tue, 9 May 2017 17:44:00 +0800 Subject: [PATCH 0232/3256] clang-format code --- paddle/math/MathFunctions.h | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/paddle/math/MathFunctions.h b/paddle/math/MathFunctions.h index a941c548d43..8ada0d34c67 100644 --- a/paddle/math/MathFunctions.h +++ b/paddle/math/MathFunctions.h @@ -34,15 +34,15 @@ extern "C" { #ifndef LAPACK_FOUND extern "C" { -#include -int LAPACKE_sgetrf(int matrix_layout, int m, int n, - float* a, int lda, int* ipiv); -int LAPACKE_dgetrf(int matrix_layout, int m, int n, - double* a, int lda, int* ipiv); -int LAPACKE_sgetri(int matrix_layout, int n, float* a, - int lda, const int* ipiv); -int LAPACKE_dgetri(int matrix_layout, int n, double* a, - int lda, const int* ipiv); +#include +int LAPACKE_sgetrf( + int matrix_layout, int m, int n, float* a, int lda, int* ipiv); +int LAPACKE_dgetrf( + int matrix_layout, int m, int n, double* a, int lda, int* ipiv); +int LAPACKE_sgetri( + int matrix_layout, int n, float* a, int lda, const int* ipiv); +int LAPACKE_dgetri( + int matrix_layout, int n, double* a, int lda, const int* ipiv); } #endif -- GitLab From 1976f137b2dc28fa6080f6df27755a5503956240 Mon Sep 17 00:00:00 2001 From: qiaolongfei Date: Tue, 9 May 2017 22:27:09 +0800 Subject: [PATCH 0233/3256] add attr() interface to layer --- python/paddle/v2/config_base.py | 5 +++++ python/paddle/v2/topology.py | 6 ++++++ 2 files changed, 11 insertions(+) diff --git a/python/paddle/v2/config_base.py b/python/paddle/v2/config_base.py index b0e8da563e0..acda778e0ae 100644 --- a/python/paddle/v2/config_base.py +++ b/python/paddle/v2/config_base.py @@ -16,6 +16,7 @@ import collections import re from paddle.trainer_config_helpers.default_decorators import wrap_name_default import paddle.trainer_config_helpers as conf_helps +from topology import Topology class LayerType(type): @@ -161,6 +162,10 @@ class Layer(object): """ return self.__context__[self.context_name()].size + def attr(self): + topo = Topology(self) + return topo.get_layer_proto(self.name) + def __convert_to_v2__(method_name, parent_names, diff --git a/python/paddle/v2/topology.py b/python/paddle/v2/topology.py index ff28c85c53d..1e46e4973f4 100644 --- a/python/paddle/v2/topology.py +++ b/python/paddle/v2/topology.py @@ -130,6 +130,12 @@ class Topology(object): return [(nm, data_layers[nm].type) for nm in self.proto().input_layer_names] + def get_layer_proto(self, name): + for layer in self.__model_config__.layers: + if layer.name == name: + return layer + return None + def __check_layer_type__(layer): if not isinstance(layer, v2_layer.LayerV2): -- GitLab From a6f248f5476f0a5ce38bbdd2e85276d929d3f55c Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Tue, 9 May 2017 13:54:57 -0700 Subject: [PATCH 0234/3256] change master process to master server process --- doc/design/cluster_train/README.md | 26 +++++++++++----------- doc/design/cluster_train/master_process.md | 10 ++++----- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/doc/design/cluster_train/README.md b/doc/design/cluster_train/README.md index b88a8f382bf..74961f80050 100644 --- a/doc/design/cluster_train/README.md +++ b/doc/design/cluster_train/README.md @@ -15,7 +15,7 @@ This poses technical challenges to PaddlePaddle: A training job will be created once user asks Paddle cloud to train a model. The training job is made up of different processes that collaboratively consume data and produce a trained model. There are three kinds of processes: -1. the *master process*, which dispatches tasks to +1. the *master server process*, which dispatches tasks to 1. one or more *trainer processes*, which run distributed training and synchronize gradients/models via 1. one or more *parameter server processes*, where each holds a shard of the global model, and receive the uploaded gradients from every *trainer process*, so they can run the optimize functions to update their parameters. @@ -27,9 +27,9 @@ By coordinating these processes, PaddlePaddle supports use both Synchronize Stoc When training with sync SGD, parameter servers wait for all trainers to finish gradients update and then send the updated parameters to trainers, training can not proceed until the trainer received the updated parameters. This creates a synchronization point between trainers. When training with async SGD, each trainer upload gradient and download new parameters individually, without the synchronization with other trainers. Using asyc SGD will be faster in terms of time per pass, but have more noise in gradient since trainers are likely to have a stale model. -### Master Process +### Master Server Process -The master process will: +The master server process will: - Partition a dataset into [tasks](#task) and dispatch tasks to trainers. - Keep track of training progress on the dataset with [task queue](#task-queue). A training job will iterate on the dataset for a full pass until it goes into next pass. @@ -41,11 +41,11 @@ A task is a data shard to be trained. The total number of tasks will be much big #### Task Queue -The master process has three task queues to track training progress. As illustrated in the graph below, Job A and Job B both have one master process. Each master process has three task queues. +The master server has three task queues to track training progress. As illustrated in the graph below, Job A and Job B both have one master server. Each master server process has three task queues. -- The todo queue holds tasks to be dispatched. When a job starts, the master process fills in the todo queue with all tasks. +- The todo queue holds tasks to be dispatched. When a job starts, the master server fills in the todo queue with all tasks. - The pending queue holds tasks that are currently training by trainers. - the done queue holds tasks that are already trained. @@ -54,10 +54,10 @@ The life cycle of a single task is illustrated below: 1. When a new pass of training starts, all tasks will be placed in the todo queue. -1. The master process will dispatch few tasks to each trainer at a time, puts them in the pending queue and waits for completion. -1. The trainer will work on its tasks and tell the master process once a task is completed. The master process will dispatch a new task to that trainer. -1. If a task timeout. the master process will move it back to the todo queue. The timeout count will increase by one. If the timeout count is above a threshold, the task is likely to cause a trainer to crash, so it will be discarded. -1. The master process will move completed task to the done queue. When the todo queue is empty, the master process will start a new pass by moving all tasks in the done queue to todo queue and reset the timeout counter of all tasks to zero. +1. The master server will dispatch few tasks to each trainer at a time, puts them in the pending queue and waits for completion. +1. The trainer will work on its tasks and tell the master server once a task is completed. The master server will dispatch a new task to that trainer. +1. If a task timeout. the master server will move it back to the todo queue. The timeout count will increase by one. If the timeout count is above a threshold, the task is likely to cause a trainer to crash, so it will be discarded. +1. The master server will move completed task to the done queue. When the todo queue is empty, the master server will start a new pass by moving all tasks in the done queue to todo queue and reset the timeout counter of all tasks to zero. ### Trainer Process @@ -93,7 +93,7 @@ The communication pattern between the trainers and the parameter servers depends ## Fault Tolerant -The training job will pause if the master processes is dead, or any of the parameter server process is dead. They will be started by [Kubernetes](https://kubernetes.io/) and recover in few minutes. Please refer to [fault recovery](#fault-recovery). +The training job will pause if the master server processes is dead, or any of the parameter server process is dead. They will be started by [Kubernetes](https://kubernetes.io/) and recover in few minutes. Please refer to [fault recovery](#fault-recovery). The training job will continue to make progress if there is at least one training process running. The strategy depends on the type of optimization algorithm: @@ -113,7 +113,7 @@ Now we will introduce how each process recovers from a failure, the graph below -### Master Process +### Master Server Process When the master is started by the Kubernetes, it executes the following steps at startup: @@ -122,7 +122,7 @@ When the master is started by the Kubernetes, it executes the following steps at 1. Watches the trainer prefix keys `/trainer/` on etcd to find the live trainers. 1. Starts dispatching the tasks to the trainers, and updates task queue using an etcd transaction to ensure lock is held during the update. -When the master process is dead for any reason, Kubernetes will restart it. It will be online again with all states recovered from etcd in few minutes. +When the master server process is dead for any reason, Kubernetes will restart it. It will be online again with all states recovered from etcd in few minutes. ### Trainer Process @@ -132,7 +132,7 @@ When the trainer is started by the Kubernetes, it executes the following steps a 1. Generates a unique ID, and sets key `/trainer/` with its contact address as value. The key will be deleted when the lease expires, so the master will be aware of the trainer being online and offline. 1. Waits for tasks from the master to start training. -If trainer's etcd lease expires, it will try set key `/trainer/` again so that the master process can discover the trainer again. +If trainer's etcd lease expires, it will try set key `/trainer/` again so that the master server can discover the trainer again. When a trainer fails, Kuberentes would try to restart it. The recovered trainer would fetch tasks from the TODO queue and go on training. diff --git a/doc/design/cluster_train/master_process.md b/doc/design/cluster_train/master_process.md index d9c66559548..2334f51390a 100644 --- a/doc/design/cluster_train/master_process.md +++ b/doc/design/cluster_train/master_process.md @@ -1,6 +1,6 @@ -# Design Doc: Master Process +# Design Doc: Master Server -For an overview of master process' role, please refer to [distributed training design doc](./README.md). In this design doc we will discuss the master process in more details. The master will be implemented in [Go](https://golang.org/). +For an overview of master server' role, please refer to [distributed training design doc](./README.md). In this design doc we will discuss the master server in more details. The master will be implemented in [Go](https://golang.org/). ## Dataset @@ -10,18 +10,18 @@ A dataset is a list of files in *RecordIO* format. A RecordIO file consists of c ## Task Queue -As mentioned in [distributed training design doc](./README.md), a *task* is a data shard that the master process assigns to the trainer process to train on. A task consists of one or multiple *blocks* from one or multiple files. The master process maintains *task queues* to track the training progress. +As mentioned in [distributed training design doc](./README.md), a *task* is a data shard that the master server assigns to the trainer process to train on. A task consists of one or multiple *blocks* from one or multiple files. The master server maintains *task queues* to track the training progress. ### Task Queue Creation -1. Each trainer will make an RPC call (using Go's [rpc](https://golang.org/pkg/net/rpc/) package) to the master process, telling it the RecordIO files representing the dataset specified by the user. Since every trainer will tell the master process the same dataset, only the first RPC call will be honored. +1. Each trainer will make an RPC call (using Go's [rpc](https://golang.org/pkg/net/rpc/) package) to the master server, telling it the RecordIO files representing the dataset specified by the user. Since every trainer will tell the master server the same dataset, only the first RPC call will be honored. The RPC interface is: ```go func (m *RPCServer) ReportDataset(Paths []string, dummy *int) error { } ``` -1. The master process will scan through each RecordIO file to generate the *block index* and know how many blocks does each file have. A block can be referenced by the file path and the index of the block within the file. The block index is in memory data structure that enables fast access to each block, and the index of the block with the file is an integer start from 0, representing the n-th block within the file. +1. The master server will scan through each RecordIO file to generate the *block index* and know how many blocks does each file have. A block can be referenced by the file path and the index of the block within the file. The block index is in memory data structure that enables fast access to each block, and the index of the block with the file is an integer start from 0, representing the n-th block within the file. The definition of the block is: ```go -- GitLab From 7a78e02d5299a6d7ecdac43348d03aa786e80e55 Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Tue, 9 May 2017 14:04:17 -0700 Subject: [PATCH 0235/3256] file rename --- .../cluster_train/{master_process.md => master_server.md} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename doc/design/cluster_train/{master_process.md => master_server.md} (94%) diff --git a/doc/design/cluster_train/master_process.md b/doc/design/cluster_train/master_server.md similarity index 94% rename from doc/design/cluster_train/master_process.md rename to doc/design/cluster_train/master_server.md index 2334f51390a..bb830765258 100644 --- a/doc/design/cluster_train/master_process.md +++ b/doc/design/cluster_train/master_server.md @@ -1,6 +1,6 @@ # Design Doc: Master Server -For an overview of master server' role, please refer to [distributed training design doc](./README.md). In this design doc we will discuss the master server in more details. The master will be implemented in [Go](https://golang.org/). +For an overview of master server's role, please refer to [distributed training design doc](./README.md). In this design doc we will discuss the master server in more details. The master will be implemented in [Go](https://golang.org/). ## Dataset -- GitLab From 50602e6afc70c00bba16bbbca7a2142da6b1e4a7 Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Tue, 9 May 2017 17:37:46 -0700 Subject: [PATCH 0236/3256] Trainer Communication Library design doc, the Go interface part --- doc/design/cluster_train/trainer.md | 76 +++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 doc/design/cluster_train/trainer.md diff --git a/doc/design/cluster_train/trainer.md b/doc/design/cluster_train/trainer.md new file mode 100644 index 00000000000..638cfb179b8 --- /dev/null +++ b/doc/design/cluster_train/trainer.md @@ -0,0 +1,76 @@ +# Design Doc: Trainer Communication Library + +For an overview of trainer's role, please refer to [distributed training design doc](README.md). In this design doc, we will discuss the trainer's communication library, which will manage communication with parameter servers and the [master server](master_server.md). The library will be implemented in [Go](https://golang.org/) and made available as a static or dynamic library with a C header file. + +## Go Interface + +The Go interface is the basic abstraction of communications with the master server and parameter servers. We will add another layer on top (add retry logic, polish interface with C idiom) before exposing the library with a [C interface](#c-interface). + +```go +// MasterClient is the client to the master server. +type MasterClient struct {} + +// GetTask gets a new task by telling the master server the finished task. +// Use nil as the finished task when getting the task for the first time. +func (*MasterClient) GetTask(finished master.Task) (master.Task, error) + +// ElementType is the type of elements of a Parameter. +type ElementType int + +// Different element types. +const ( + Int32 ElementType = iota + UInt32 + Int64 + UInt64 + Float32 + Float64 +) + +// Parameter is a piece data to sync with the parameter server. +type Parameter struct { + Name string + ElementType ElementType + Buffer []byte +} + +// Gradient is the gradient of the parameter. +type Gradient Parameter + +// PServerClient is the client to parameter servers. +type PServerClient struct {} + +// UpdateRule specifies the rule for updating parameters with gradients. +type UpdateRule struct { + UpdateMethod pserver.UpdateMethod + LearningRate float32 +} + +// ParamInitChans returns a send channel for parameter initialization. +// +// ParamInitChans will be called from multiple trainers, only one trainer should +// initialize the parameters on parameter servers, other trainers will instead +// get the initialized parameters from parameter servers using GetParam. +// +// If send channel is not nil, the trainer is selected to do the initialization, +// the trainer needs to signal for finishing initializing the parameters by +// closing the send channel. +func (*PServerClient) ParamInitChan() (send chan<- Parameter, err error) + +// SendGrad sends gradients to parameter servers. +func (*PServerClient) SendGrad(method UpdateMethod, grads []Gradient) error + +// GetParam gets parameters from parameter servers. +func (*PServerClient) GetParam(names []string) ([]Parameter, error) + +// Save indicates parameters to save the parameter to the given path. +// +// Path needs to be the path to a distributed file system which is visible +// to all parameter servers. +func (*PServerClient) Save(path string) error +``` +Please see [master server design doc](master_server.md) for the definition of `master.Task`. + +## C Interface + +TODO -- GitLab From 0ed68da9b36bbe388d678b9a56fbcca3090381b4 Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Tue, 9 May 2017 17:56:09 -0700 Subject: [PATCH 0237/3256] update grammar --- doc/design/cluster_train/trainer.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/design/cluster_train/trainer.md b/doc/design/cluster_train/trainer.md index 638cfb179b8..c9c090dc984 100644 --- a/doc/design/cluster_train/trainer.md +++ b/doc/design/cluster_train/trainer.md @@ -27,7 +27,7 @@ const ( Float64 ) -// Parameter is a piece data to sync with the parameter server. +// Parameter is a piece of data to sync with the parameter server. type Parameter struct { Name string ElementType ElementType -- GitLab From cd978b70f4162e454a1c1bbfa67a52250469a4d4 Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Tue, 9 May 2017 18:02:51 -0700 Subject: [PATCH 0238/3256] add SetParam --- doc/design/cluster_train/trainer.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/doc/design/cluster_train/trainer.md b/doc/design/cluster_train/trainer.md index c9c090dc984..bcb4a9c09dc 100644 --- a/doc/design/cluster_train/trainer.md +++ b/doc/design/cluster_train/trainer.md @@ -57,9 +57,15 @@ type UpdateRule struct { // closing the send channel. func (*PServerClient) ParamInitChan() (send chan<- Parameter, err error) -// SendGrad sends gradients to parameter servers. +// SendGrad sends gradients to parameter servers for updating parameters. func (*PServerClient) SendGrad(method UpdateMethod, grads []Gradient) error +// SetParam sets parameters. +// +// SetParam can be used for the parameters that are not suitable for updating +// using gradients. +func (*PServerClient) SetParam(params []Paramter) error + // GetParam gets parameters from parameter servers. func (*PServerClient) GetParam(names []string) ([]Parameter, error) -- GitLab From d5b7e92cab3f08f898da5bdb3f576dc9e3a166b3 Mon Sep 17 00:00:00 2001 From: Liu Yiqun Date: Wed, 10 May 2017 10:53:46 +0800 Subject: [PATCH 0239/3256] Add dockerfile and build script for Android. --- Dockerfile.android | 43 ++++++++++++++++++++++++++ paddle/scripts/docker/build_android.sh | 25 +++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 Dockerfile.android create mode 100644 paddle/scripts/docker/build_android.sh diff --git a/Dockerfile.android b/Dockerfile.android new file mode 100644 index 00000000000..9df291250dc --- /dev/null +++ b/Dockerfile.android @@ -0,0 +1,43 @@ +FROM ubuntu:16.04 +MAINTAINER PaddlePaddle Authors + +ARG UBUNTU_MIRROR +RUN /bin/bash -c 'if [[ -n ${UBUNTU_MIRROR} ]]; then sed -i 's#http://archive.ubuntu.com/ubuntu#${UBUNTU_MIRROR}#g' /etc/apt/sources.list; fi' + +ENV HOME=/root \ + ANDROID_HOME=/opt/android-sdk-linux \ + ANDROID_NDK_HOME=/opt/android-ndk-linux \ + ANDROID_STANDALONE_TOOLCHAIN=/opt/android-toolchain-gcc \ + PATH=${PATH}:${ANDROID_HOME}:${ANDROID_NDK_HOME} + +RUN apt-get update && \ + apt-get install -y git python-dev python-pip python-numpy && \ + apt-get install -y wget curl tar unzip && \ + apt-get install -y gcc g++ locales swig && \ + apt-get clean -y + +RUN pip install --upgrade pip && \ + pip install -U 'protobuf==3.1.0' && \ + pip install -U wheel sphinx && \ + pip install pre-commit + +# git credential to skip password typing +RUN git config --global credential.helper store + +# Fix locales to en_US.UTF-8 +RUN localedef -i en_US -f UTF-8 en_US.UTF-8 + +RUN curl -sSL https://cmake.org/files/v3.2/cmake-3.2.2.tar.gz | tar -xz && \ + cd cmake-3.2.2 && ./bootstrap && make -j `nproc` && make install && \ + cd .. && rm -rf cmake-3.2.2 + +# Android NDK +RUN mkdir /opt/android-ndk-tmp && \ + cd /opt/android-ndk-tmp && \ + wget -q https://dl.google.com/android/repository/android-ndk-r14b-linux-x86_64.zip && \ + unzip -q android-ndk-r14b-linux-x86_64.zip && \ + mv android-ndk-r14b ${ANDROID_NDK_HOME} && \ + ${ANDROID_NDK_HOME}/build/tools/make-standalone-toolchain.sh --arch=arm --platform=android-21 --install-dir=${ANDROID_STANDALONE_TOOLCHAIN} && \ + rm -rf /opt/android-ndk-tmp + +CMD ["bash", "paddle/paddle/scripts/docker/build_android.sh"] diff --git a/paddle/scripts/docker/build_android.sh b/paddle/scripts/docker/build_android.sh new file mode 100644 index 00000000000..ab432c8524e --- /dev/null +++ b/paddle/scripts/docker/build_android.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +set -xe + +mkdir -p /paddle/build +cd /paddle/build +cmake -DCMAKE_SYSTEM_NAME=Android \ + -DANDROID_STANDALONE_TOOLCHAIN=$ANDROID_STANDALONE_TOOLCHAIN \ + -DANDROID_ABI=armeabi-v7a \ + -DANDROID_ARM_NEON=ON \ + -DANDROID_ARM_MODE=ON \ + -DHOST_C_COMPILER=/usr/bin/gcc \ + -DHOST_CXX_COMPILER=/usr/bin/g++ \ + -DCMAKE_INSTALL_PREFIX=/paddle/install \ + -DCMAKE_BUILD_TYPE=RelWithDebInfo \ + -DCMAKE_C_FLAGS_RELWITHDEBINFO="-O3" \ + -DCMAKE_CXX_FLAGS_RELWITHDEBINFO="-O3" \ + -DWITH_C_API=ON \ + -DWITH_SWIG_PY=OFF \ + .. +make -j `nproc` +make install + +export PATH=/paddle/install/bin:/paddle/install/opt/paddle/bin:$PATH +paddle version -- GitLab From b5dd70fd862170fdcfe0c2e276002421ced76031 Mon Sep 17 00:00:00 2001 From: Liu Yiqun Date: Wed, 10 May 2017 13:03:03 +0800 Subject: [PATCH 0240/3256] Remove the extra return statement. --- paddle/math/MathFunctions.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/paddle/math/MathFunctions.cpp b/paddle/math/MathFunctions.cpp index 1a3bb432bfb..7045562dd44 100644 --- a/paddle/math/MathFunctions.cpp +++ b/paddle/math/MathFunctions.cpp @@ -180,7 +180,6 @@ int getri(const CBLAS_ORDER order, const int lda, const int* ipiv) { return dynload::PADDLE_DGETRI(order, N, A, lda, ipiv); - return 0; } template <> -- GitLab From d1014269abdaac1c09596f0808537ea66b008e5f Mon Sep 17 00:00:00 2001 From: gongweibao Date: Wed, 10 May 2017 15:28:03 +0800 Subject: [PATCH 0241/3256] modify by comments --- doc/design/file_mananger/README.md | 95 ++++++++---------- doc/design/file_mananger/pfs/cp.md | 8 +- doc/design/file_mananger/pfs/ls.md | 4 +- doc/design/file_mananger/pfs/mkdir.md | 4 +- doc/design/file_mananger/pfs/mv.md | 4 +- doc/design/file_mananger/pfs/pfs.md | 2 +- doc/design/file_mananger/pfs/rm.md | 10 +- doc/design/file_mananger/pfs/sync.md | 12 +-- .../file_mananger/src/filemanager.graffle | Bin 3442 -> 3442 bytes doc/design/file_mananger/src/filemanager.png | Bin 54827 -> 62630 bytes 10 files changed, 64 insertions(+), 75 deletions(-) diff --git a/doc/design/file_mananger/README.md b/doc/design/file_mananger/README.md index 00ca6db1725..16074f8a01c 100644 --- a/doc/design/file_mananger/README.md +++ b/doc/design/file_mananger/README.md @@ -1,57 +1,51 @@ # FileManager设计文档 -## 名词解释 -- PFS:是Paddle cloud File System的简称。与之相对的是Local File System。 -- FileServer:接收用户管理文件命令的服务端 -- FileManger:用户管理自己自己在PFS文件上的系统称为FileManager -- CephFS:是个POSIX 兼容的文件系统,它使用 Ceph 存储集群来存储数据. -- Chunk:逻辑划分文件的一个单位,用于文件分块上传或者下载。 -- Ingress:Layer 7 Load Balancer - ## 目标 -在本文档中,我们设计说明了用户上传、下载、管理自己在PaddlePaddle Cloud上的文件所涉及到的模块和流程。架构图如下所示: - - - - - -## PFS Client -- 提供用户管理Cloud文件的命令 -- 用Golang写,可以跨平台执行 - -命令的详细内容看[Here](./pfs/pfs.md) +在本文档中,我们设计说明了名为FileManager系统,方便用户管理存放到PaddlePaddle Cloud上的文件。 +主要功能包括: +- 提供常用的命令行文件管理命令管理文件 + - 支持的命令在[Here] (./pfs/pfs.md) +- 支持大文件的断点上传、下载 +## 名词解释 +- PFS:是Paddlepaddle cloud File System的简称,是对用户文件存储空间的抽象,与之相对的是Local File System。目前我们用CephFS来搭建。 +- [CephFS](http://docs.ceph.com/docs/master/cephfs/):一个POSIX兼容的文件系统。 +- Chunk:逻辑划上文件分块的单位。 +- [Ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/):提供七层协议的反向代理、基于粘性会话的负载均衡。 +- CA:certificate authority[tls](#tls) +- CRT:CA signed certificate[tls](#tls) +- Key:用户私钥[tls](#tls) + +## 模块 + +### 架构图 + + +### PFSClient +- 功能: 详细的内容看[Here](./pfs/pfs.md) + - 提供用户管理文件的命令 + - 用Golang写,可以跨平台执行 + +- 双向验证 + PFSClient需要和Ingress之间做双向验证[tls](#tls),所有用户需要首先在`cloud.paddlepaddle.org`上注册一下,申请用户空间,并且把系统生成的Key、CRT、CA下载到本地,然后才能使用PFSClient。 + ### Ingress -- 在kubernets中运行 -- 做HTTP转发、负载均衡 - - 注意配置session保持,以便来自一个用户的访问可以定向到一个固定的机器上,减少冲突写的机会。 - - -## FileServer -功能说明: -- goRPC写的HTTPServer -- 响应外部的REST API的请求 -- 在kubernetes中运行 -- [RESTAPI](./RESTAPI.md)接口 - -## 文件传输 -### 文件权限 -- 每一个用户在Cloud注册后可以申请分配用户空间,系统默认会在CephFS上分配一个固定大小(比如初始10G)的、有所有权限的volume,对用户而言就是自己的`home`目录。用户彼此之间的数据是隔离的、无法访问的。用户的空间大小第一期也不允许扩大。 -- 公共数据集合放到一个单独的volume下,对所有外部用户只读。由于其被读取的可能比较频繁,需要提高其备份数,防止成为热点文件。 +- 功能: + 提供七层协议的反向代理、基于粘性会话的负载均衡功能。 + +- 透传用户身份的办法 + Ingress需要把PFSClient的身份头传给FileServer,配置的方法参考[Here](http://www.integralist.co.uk/posts/clientcertauth.html#3) -### 用户认证 -身份的认证来自于用户或者程序是否有crt标识身份,以及是否有可信的CA确认这个身份证是否有效。我们这里描述的crt涉及到两个部分,一个是Client端程序访问FileServer的crt,不妨称之为Client crt;另外一个是FileServer访问CephFS的crt,不妨称之为CephFS crt。 -- Client和FileServer相互认证的办法 -`cloud.paddlepaddle.org`需要有自己的CA,FileServer和注册用户也要为其生成各自的私钥(key)、crt。这样用户把CA、自己的key和crt下载到本地后,Client程序可以用之和FileServer可以做相互的认证。 +### FileServer +FileServer是一个用GoRPC写的HTTPServer,提供[RESTful API](./RESTAPI.md)接口,接收处理PFSClient端的文件管理请求,并且把结果返回PFSClient端。 -- CephFS验证FileServer的身份的方法 - CephFS crt只有一个,也就是admin crt,拥有所有volume的读写权限。 FileServer从Client crt提取Client的身份(username),限制其可以操作的volume。 +## 大文件传输优化 ### 分块文件上传 -用户文件可能是比较大的,上传到Cloud或者下载到本地的时间可能比较长,而且在传输的过程中也可能出现网络不稳定的情况。为了应对以上的问题,我们提出了chunk的概念,一个chunk由所在的文件偏移、数据、数据长度及校验值组成。文件数据内容的上传和下载都是都过chunk的操作来实现的。由于chunk比较小(默认256K),完成一个传输动作完成的时间也比较短,不容易出错。 +用户文件可能是比较大的,上传到Cloud或者下载到本地的时间可能比较长,而且在传输的过程中也可能出现网络不稳定的情况。为了应对以上的问题,我们提出了Chunk的概念,一个Chunk由所在的文件偏移、数据、数据长度及校验值组成。文件数据内容的上传和下载都是都过Chunk的操作来实现的。由于Chunk比较小(默认256K),完成一个传输动作完成的时间也比较短,不容易出错。PFSClient在传输完毕最后一个Chunk的时候检查desttination文件的MD5值是否和source文件一致。 -一个典型的chunk如下所示: +一个典型的Chunk如下所示: ``` type Chunk struct { @@ -62,22 +56,17 @@ type Chunk struct { } ``` -### 文件传输的优化 -文件传输的的关键在于需要Client端对比source和destination的文件chunks的checkSum是否保持一致,不一致的由Client Get或者Post chunk完成。藉由上述的方法完成断点的数据传输。 upload文件时,由于一个文件可以是多个FileServer可写的,存在冲突的机会,需要Client端在Post最后一个chunk的时候检查dest文件的MD5值是否和本地文件一致。 - -- 优化的方法: - - - dst文件不存在时,可以没有Get的过程,只有Post。 - -- 小的技巧: +### 生成sparse文件 +当destination文件不存在或者大小和source文件不一致时,可以用[Fallocate](https://golang.org/pkg/syscall/#Fallocate)生成sparse文件,然后就可以并发写入多个Chunk。 - - 可以用[Fallocate](https://golang.org/pkg/syscall/#Fallocate)生成sparse文件,让dst和src文件保持相同的大小。不同位置的chunk可以同时写入。 +### 覆盖不一致的部分 +文件传输的的关键在于需要PFSClient端对比source和destination的文件Chunks的checksum是否保持一致,不一致的由PFSClient下载或者传输Chunk完成。这样已经传输成功的部分就不用重新传输了。 ## 框架生成 用[swagger-api](https://github.com/swagger-api/swagger-codegen)生成Client和FileServer的框架部分,以便我们可以把更多的精力放到逻辑本身上。 ## 参考文档 -- [TLS complete guide](https://github.com/k8sp/tls/blob/master/README.md) +- [TLS complete guide](https://github.com/k8sp/tls/blob/master/tls.md) - [aws.s3](http://docs.aws.amazon.com/cli/latest/reference/s3/) - [linux man document](https://linux.die.net/man/) diff --git a/doc/design/file_mananger/pfs/cp.md b/doc/design/file_mananger/pfs/cp.md index 80eed5d0794..605c0b7c8bf 100644 --- a/doc/design/file_mananger/pfs/cp.md +++ b/doc/design/file_mananger/pfs/cp.md @@ -19,23 +19,23 @@ cp - copy files and directories - The following command copies a single file to pfs ``` -paddle pfs cp ./text1.txt pfs://mydir/text1.txt +paddle pfs cp ./text1.txt /pfs/mydir/text1.txt ``` Output ``` -upload ./text1.txt to pfs://mydir/text1.txt +upload ./text1.txt to /pfs/mydir/text1.txt ``` - The following command copies pfs file to a local file ``` -paddle pfs cp pfs://mydir/text1.txt ./text1.txt +paddle pfs cp /pfs/mydir/text1.txt ./text1.txt ``` Output ``` -download pfs://mydir/text1.txt to ./text1.txt +download /pfs/mydir/text1.txt to ./text1.txt ``` diff --git a/doc/design/file_mananger/pfs/ls.md b/doc/design/file_mananger/pfs/ls.md index 8dc81aeaba2..ab254cfc5d9 100644 --- a/doc/design/file_mananger/pfs/ls.md +++ b/doc/design/file_mananger/pfs/ls.md @@ -18,7 +18,7 @@ ls - list directory contents or a file attributes - The following command lists a single file ``` -paddle pfs ls pfs://mydir/text1.txt +paddle pfs ls /pfs/mydir/text1.txt ``` Output @@ -30,7 +30,7 @@ Output - The following command lists directory contents ``` -paddle pfs ls pfs://mydir +paddle pfs ls /pfs/mydir ``` Output diff --git a/doc/design/file_mananger/pfs/mkdir.md b/doc/design/file_mananger/pfs/mkdir.md index 45906dc2083..c11dadd61f5 100644 --- a/doc/design/file_mananger/pfs/mkdir.md +++ b/doc/design/file_mananger/pfs/mkdir.md @@ -9,11 +9,11 @@ Create the pfs directory(ies), if they do not already exist. And create intermed # Examples ``` -paddle pfs mkdir pfs://mydir1 +paddle pfs mkdir /pfs/mydir1 ``` Output ``` -make directory pfs://mydir1 +make directory /pfs/mydir1 ``` diff --git a/doc/design/file_mananger/pfs/mv.md b/doc/design/file_mananger/pfs/mv.md index 8ea006bdaf5..3929d43394a 100644 --- a/doc/design/file_mananger/pfs/mv.md +++ b/doc/design/file_mananger/pfs/mv.md @@ -18,11 +18,11 @@ mv [OPTION]... - The following command move a single file to pfs ``` -paddle pfs mv ./text1.txt pfs://mydirectory/test1.txt +paddle pfs mv ./text1.txt /pfs/mydirectory/test1.txt ``` Output ``` -move ./text1.txt pfs://mydirectory/test1.txt +move ./text1.txt /pfs/mydirectory/test1.txt ``` diff --git a/doc/design/file_mananger/pfs/pfs.md b/doc/design/file_mananger/pfs/pfs.md index 76ee0a6917d..c23ffc74791 100644 --- a/doc/design/file_mananger/pfs/pfs.md +++ b/doc/design/file_mananger/pfs/pfs.md @@ -28,7 +28,7 @@ paddle [options] pfs [parameters] ## Path Arguments When using a command, we need to specify path arguments. There are two path argument type: `localpath` and `pfspath`. -A `pfspath` begin with `pfs://`, eg: `pfs://mydir/text1.txt`. +A `pfspath` begin with `/pfs`, eg: `/pfs/mydir/text1.txt`. ## order of Path Arguments Commonly, if there are two path arguments. The first is the source, and the second is the destination. diff --git a/doc/design/file_mananger/pfs/rm.md b/doc/design/file_mananger/pfs/rm.md index 0b3760f332c..64774c7ad96 100644 --- a/doc/design/file_mananger/pfs/rm.md +++ b/doc/design/file_mananger/pfs/rm.md @@ -20,26 +20,26 @@ rm [OPTION]... ... - The following command deletes a single files ``` -paddle pfs rm pfs://mydirectory/test1.txt +paddle pfs rm /pfs/mydirectory/test1.txt ``` Output ``` -delete pfs://mydirectory/test1.txt +delete /pfs/mydirectory/test1.txt ``` - The following command deletes a directory recursively. ``` -paddle pfs rm -r pfs://mydirectory +paddle pfs rm -r /pfs/mydirectory ``` Output ``` -delete pfs://mydirectory/1.txt -delete pfs://mydirectory/2.txt +delete /pfs/mydirectory/1.txt +delete /pfs/mydirectory/2.txt ... ``` diff --git a/doc/design/file_mananger/pfs/sync.md b/doc/design/file_mananger/pfs/sync.md index c317742e501..c921e8ac81a 100644 --- a/doc/design/file_mananger/pfs/sync.md +++ b/doc/design/file_mananger/pfs/sync.md @@ -16,27 +16,27 @@ sync - sync directories. Recursively copies new and updated files from the sourc - The following command sync locally directory to pfs ``` -paddle pfs sync ./dir1 pfs://mydir1 +paddle pfs sync ./dir1 /pfs/mydir1 ``` Output ``` -upload ./dir1/text1.txt to pfs://mydir1/text2.txt -upload ./dir1/text2.txt to pfs://mydir1/text2.txt +upload ./dir1/text1.txt to /pfs/mydir1/text2.txt +upload ./dir1/text2.txt to /pfs/mydir1/text2.txt ... ``` - The following command sync pfs directory to local ``` -paddle pfs sync pfs://mydir1 . +paddle pfs sync /pfs/mydir1 . ``` Output ``` -download pfs://mydir1/text1.txt to ./text1.txt -download pfs://mydir1/text2.txt to ./text2.txt +download /pfs/mydir1/text1.txt to ./text1.txt +download /pfs/mydir1/text2.txt to ./text2.txt ... ``` diff --git a/doc/design/file_mananger/src/filemanager.graffle b/doc/design/file_mananger/src/filemanager.graffle index 069882a1e93a3471febd250f1b5b8acf894465d4..beb57cdd786e24a57e63c06ef4d6cfa0df3d8c5a 100644 GIT binary patch literal 3442 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 UX4~j9A8%j%AKeFi?|ozd08lvlWB>pF literal 3442 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

    %e@$P&L)4Z01!{VAsyYoYJIl|1 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�aY3b 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 MN-9Z|i5Ug{KLk%^x&QzG -- GitLab From 9910b5517059edf856ff5ba97aabdfdd70530db8 Mon Sep 17 00:00:00 2001 From: gongweibao Date: Wed, 10 May 2017 15:32:36 +0800 Subject: [PATCH 0242/3256] modify by comments --- doc/design/file_mananger/README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/doc/design/file_mananger/README.md b/doc/design/file_mananger/README.md index 16074f8a01c..27af3c51e68 100644 --- a/doc/design/file_mananger/README.md +++ b/doc/design/file_mananger/README.md @@ -40,9 +40,9 @@ ### FileServer FileServer是一个用GoRPC写的HTTPServer,提供[RESTful API](./RESTAPI.md)接口,接收处理PFSClient端的文件管理请求,并且把结果返回PFSClient端。 -## 大文件传输优化 +## 文件传输优化 -### 分块文件上传 +### 分块文件传输 用户文件可能是比较大的,上传到Cloud或者下载到本地的时间可能比较长,而且在传输的过程中也可能出现网络不稳定的情况。为了应对以上的问题,我们提出了Chunk的概念,一个Chunk由所在的文件偏移、数据、数据长度及校验值组成。文件数据内容的上传和下载都是都过Chunk的操作来实现的。由于Chunk比较小(默认256K),完成一个传输动作完成的时间也比较短,不容易出错。PFSClient在传输完毕最后一个Chunk的时候检查desttination文件的MD5值是否和source文件一致。 一个典型的Chunk如下所示: @@ -62,7 +62,6 @@ type Chunk struct { ### 覆盖不一致的部分 文件传输的的关键在于需要PFSClient端对比source和destination的文件Chunks的checksum是否保持一致,不一致的由PFSClient下载或者传输Chunk完成。这样已经传输成功的部分就不用重新传输了。 - ## 框架生成 用[swagger-api](https://github.com/swagger-api/swagger-codegen)生成Client和FileServer的框架部分,以便我们可以把更多的精力放到逻辑本身上。 -- GitLab From 70c30efcaabb030c9c07045ad3f9cb0a133183d4 Mon Sep 17 00:00:00 2001 From: dangqingqing Date: Wed, 10 May 2017 10:03:49 +0800 Subject: [PATCH 0243/3256] image preprcoess module --- python/paddle/v2/__init__.py | 3 +- python/paddle/v2/image.py | 223 ++++++++++++++++++++++++++ python/paddle/v2/tests/CMakeLists.txt | 3 +- python/paddle/v2/tests/cat.jpg | Bin 0 -> 57218 bytes python/paddle/v2/tests/test_image.py | 42 +++++ python/setup.py.in | 1 + 6 files changed, 270 insertions(+), 2 deletions(-) create mode 100644 python/paddle/v2/image.py create mode 100644 python/paddle/v2/tests/cat.jpg create mode 100644 python/paddle/v2/tests/test_image.py diff --git a/python/paddle/v2/__init__.py b/python/paddle/v2/__init__.py index 7c8f6ea62fc..35131594f0a 100644 --- a/python/paddle/v2/__init__.py +++ b/python/paddle/v2/__init__.py @@ -32,11 +32,12 @@ import networks import py_paddle.swig_paddle as api import minibatch import plot +import image __all__ = [ 'optimizer', 'layer', 'activation', 'parameters', 'init', 'trainer', 'event', 'data_type', 'attr', 'pooling', 'data_feeder', 'dataset', 'reader', - 'topology', 'networks', 'infer', 'plot', 'evaluator' + 'topology', 'networks', 'infer', 'plot', 'evaluator', 'image' ] diff --git a/python/paddle/v2/image.py b/python/paddle/v2/image.py new file mode 100644 index 00000000000..20dcc96782c --- /dev/null +++ b/python/paddle/v2/image.py @@ -0,0 +1,223 @@ +import numpy as np +try: + import cv2 +except ImportError: + cv2 = None + +from cv2 import resize + +__all__ = [ + "load_image", "resize_short", "to_chw", "center_crop", "random_crop", + "left_right_flip", "simple_transform", "load_and_transform" +] +""" +This file contains some common interface for image preprocess. +Many users are confused about the image layout. We introduce +the image layout firstly. + +- CHW Layout + - The abbreviations: C=channel, H=Height, W=Width + - The default image layout is HWC opened by cv2 or PIL. + PaddlePaddle only support the image layout with CHW. + CHW is simply a transpose of HWC. It must transpose + the input image. + +- Color format: RGB or BGR + OpenCV use BGR color format. PIL use RGB color format. Both + formats can be used for training. But it must be noted that, + the format should be keep consistent between the training and + inference peroid. +""" + + +def load_image(file, is_color=True): + """ + Load an color or gray image from the file path. + + Example usage: + + .. code-block:: python + im = load_image('cat.jpg') + + :param file: the input image path. + :type file: string + :param is_color: If set is_color True, it will load and + return a color image. Otherwise, it will + load and return a gray image. + """ + flag = cv2.CV_LOAD_IMAGE_COLOR if is_color else \ + cv2.CV_LOAD_IMAGE_GRAYSCALE + im = cv2.imread(file, flag) + return im + + +def resize_short(im, size): + """ + Resize an image so that the length of shorter edge is size. + + Example usage: + + .. code-block:: python + im = load_image('cat.jpg') + im = resize_short(im, 256) + + :param im: the input image with HWC layout. + :type im: ndarray + :param size: the shorter edge size of image after resizing. + :type size: int + """ + assert im.shape[-1] == 1 or im.shape[-1] == 3 + h, w = im.shape[:2] + h_new, w_new = size, size + if h > w: + h_new = size * h / w + else: + w_new = size * w / h + im = resize(im, (h_new, w_new), interpolation=cv2.INTER_CUBIC) + return im + + +def to_chw(im, order=(2, 0, 1)): + """ + Transpose the input image order. The image layout is HWC format + opened by cv2 or PIL. Transposed the input image to CHW layouts + by order (2,0,1). + + Example usage: + + .. code-block:: python + im = load_image('cat.jpg') + im = resize_short(im, 256) + im = to_chw(im) + + :param im: the input image with HWC layout. + :type im: ndarray + :param order: the transposed order. + :type order: tuple|list + """ + assert len(im.shape) == len(order) + im = im.transpose(order) + return im + + +def center_crop(im, size, is_color=True): + """ + Crop the center of image with size. + + Example usage: + + .. code-block:: python + im = center_crop(im, 224) + + :param im: the input image with HWC layout. + :type im: ndarray + :param size: the cropping size + :type size: int + :param is_color: whether the image is color or not. + :type is_color: bool + """ + h, w = im.shape[:2] + h_start = (h - size) / 2 + w_start = (w - size) / 2 + h_end, w_end = h_start + size, w_start + size + if is_color: + im = im[h_start:h_end, w_start:w_end, :] + else: + im = im[h_start:h_end, w_start:w_end] + return im + + +def random_crop(im, size, is_color=True): + """ + Randomly crop input image with size. + + Example usage: + + .. code-block:: python + im = random_crop(im, 224) + + :param im: the input image with HWC layout. + :type im: ndarray + :param size: the cropping size + :type size: int + :param is_color: whether the image is color or not. + :type is_color: bool + """ + h, w = im.shape[:2] + h_start = np.random.randint(0, h - size + 1) + w_start = np.random.randint(0, w - size + 1) + h_end, w_end = h_start + size, w_start + size + if is_color: + im = im[h_start:h_end, w_start:w_end, :] + else: + im = im[h_start:h_end, w_start:w_end] + return im + + +def left_right_flip(im): + """ + Flip an image along the horizontal direction. + Return the flipped image. + + Example usage: + + .. code-block:: python + im = left_right_flip(im) + + :paam im: input image with HWC layout + :type im: ndarray + """ + if len(im.shape) == 3: + return im[:, ::-1, :] + else: + return im[:, ::-1, :] + + +def simple_transform(im, resize_size, crop_size, is_train, is_color=True): + """ + Simply data argumentation for traing. These operations includes + resizing, croping and flipping. + + :param im: The input image with HWC layout. + :type im: ndarray + :param resize_size: The shorter edge length of the resized image. + :type resize_size: int + :param crop_size: The cropping size. + :type crop_size: int + :param is_train: Whether it is training or not. + :type is_train: bool + """ + im = resize_short(im, resize_size) + if is_train: + im = random_crop(im, crop_size) + if np.random.randint(2) == 0: + im = left_right_flip(im) + else: + im = center_crop(im, crop_size) + im = to_chw(im) + + return im + + +def load_and_transform(filename, + resize_size, + crop_size, + is_train, + is_color=True): + """ + Load image from the input file `filename` and transform image for + data argumentation. Please refer the `simple_transform` interface + for the transform operation. + + :param filename: The file name of input image. + :type filename: string + :param resize_size: The shorter edge length of the resized image. + :type resize_size: int + :param crop_size: The cropping size. + :type crop_size: int + :param is_train: Whether it is training or not. + :type is_train: bool + """ + im = load_image(filename) + im = simple_transform(im, resize_size, crop_size, is_train, is_color) + return im diff --git a/python/paddle/v2/tests/CMakeLists.txt b/python/paddle/v2/tests/CMakeLists.txt index 5554a37df03..0b8c78b465c 100644 --- a/python/paddle/v2/tests/CMakeLists.txt +++ b/python/paddle/v2/tests/CMakeLists.txt @@ -1 +1,2 @@ -add_python_test(test_v2_api test_data_feeder.py test_parameters.py test_layer.py test_rnn_layer.py test_topology.py) +add_python_test(test_v2_api test_data_feeder.py test_parameters.py +test_layer.py test_rnn_layer.py test_topology.py test_image) diff --git a/python/paddle/v2/tests/cat.jpg b/python/paddle/v2/tests/cat.jpg new file mode 100644 index 0000000000000000000000000000000000000000..bc1fbbd371216b9904b522ed302700c79d2e4876 GIT binary patch literal 57218 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 Bp^E?j literal 0 HcmV?d00001 diff --git a/python/paddle/v2/tests/test_image.py b/python/paddle/v2/tests/test_image.py new file mode 100644 index 00000000000..5aa612f91d3 --- /dev/null +++ b/python/paddle/v2/tests/test_image.py @@ -0,0 +1,42 @@ +# Copyright PaddlePaddle contributors. All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import unittest +import numpy as np + +import paddle.v2.image as image + + +class Image(unittest.TestCase): + def test_resize_flip_chw(self): + # resize + im = image.load_image('cat.jpg') + im = image.resize_short(im, 256) + self.assertEqual(256, min(im.shape[:2])) + self.assertEqual(3, im.shape[2]) + + # flip + im = left_right_flip(im) + im2 = np.flip(im, 1) + self.assertEqual(im.all(), im2.all()) + + # to_chw + h, w, c = im.shape + im = image.to_chw(im) + self.assertEqual(c, im.shape[0]) + self.assertEqual(h, im.shape[1]) + self.assertEqual(w, im.shape[2]) + + +if __name__ == '__main__': + unittest.main() diff --git a/python/setup.py.in b/python/setup.py.in index 5dfb46192ae..7d9438e3f81 100644 --- a/python/setup.py.in +++ b/python/setup.py.in @@ -18,6 +18,7 @@ setup(name='paddle', "numpy", "protobuf==${PROTOBUF_VERSION}", "matplotlib", + "opencv-python", ], packages=packages, package_dir={ -- GitLab From 60ef01773f2df0266cfed3e60dac72ac4c823f60 Mon Sep 17 00:00:00 2001 From: gongweibao Date: Wed, 10 May 2017 16:14:51 +0800 Subject: [PATCH 0244/3256] modify by talks --- doc/design/cluster_train/data_dispatch.md | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/doc/design/cluster_train/data_dispatch.md b/doc/design/cluster_train/data_dispatch.md index a3eb4e28db0..7690cb279dd 100644 --- a/doc/design/cluster_train/data_dispatch.md +++ b/doc/design/cluster_train/data_dispatch.md @@ -12,16 +12,18 @@ 在上图中显示了在一个实际生产环境中的应用(人脸识别)的数据流图。生产环境的日志数据会通过实时流的方式(Kafka)和离线数据的方式(HDFS)存储,并在集群中运行多个分布式数据处理任务,比如流式数据处理(online data process),离线批处理(offline data process)完成数据的预处理,提供给paddle作为训练数据。用于也可以上传labeled data到分布式存储补充训练数据。在paddle之上运行的深度学习训练输出的模型会提供给在线人脸识别的应用使用。 ### 训练数据的存储 +We select CephFS to store our data. -选择CephFS作为训练数据的存储服务。 +From the perspective of user program running in a Pod, it is only I/O with the local filesystem, as -在Kubernetes上运行的不同的计算框架,可以通过Volume或PersistentVolume挂载存储空间到每个容器中。 +1. the home directory should have been mapped to the Pod-local directory `/home`, and +1. some shared directories, e.g., the pre-downloaded `paddle.v2.dataset` data, should have been mapped to the Pod-local directory `/common`. -在CephFS存储系统中的公开目录,需要保存一些预置的公开数据集(比如MNIST, BOW, ImageNet数据集等),并且可以被提交的job直接使用。 +and from the perspective of our client tool `paddle`, it has to refer to files in the distributed filesystem in a special format, just like `/pfs/$DATACENTER/home/$USER/cifa/...`. ### 文件预处理 -在数据集可以被训练之前,文件需要预先被转换成PaddlePaddle集群内部的存储格式(SSTable)。我们提供两个转换方式: +在数据集可以被训练之前,文件需要预先被转换成PaddlePaddle集群内部的存储格式[RecordIO](https://github.com/PaddlePaddle/Paddle/issues/1947)。我们提供两个转换方式: - 提供给用户本地转换的库,用户可以编写程序完成转换。 - 用户可以上传自己的数据集,在集群运行MapReduce job完成转换。 @@ -92,11 +94,11 @@ random_images-00099-of-00099 #### 进行训练 -PaddlePaddle提供专用的[data reader creator](https://github.com/PaddlePaddle/Paddle/blob/develop/doc/design/reader/README.md#python-data-reader-design-doc),生成给定SSTable文件对应的data reader。**无论在本地还是在云端,reader的使用方式都是一致的**: +PaddlePaddle提供专用的[data reader creator](https://github.com/PaddlePaddle/Paddle/blob/develop/doc/design/reader/README.md#python-data-reader-design-doc),生成给定`RecordIO`文件对应的data reader。**无论在本地还是在云端,reader的使用方式都是一致的**: ```python # ... -reader = paddle.reader.creator.SSTable("/home/random_images-*-of-*") +reader = paddle.reader.creator.RecordIO("/home/random_images-*-of-*") batch_reader = paddle.batch(paddle.dataset.mnist.train(), 128) trainer.train(batch_reader, ...) ``` @@ -107,13 +109,14 @@ trainer.train(batch_reader, ...) 使用下面命令,可以把本地的数据上传到存储集群中。 -```bash -paddle cp filenames pfs://home/folder/ +```bash +paddle pfs cp filenames /pfs/folder/ ``` 比如,把之前示例中转换完毕的random_images数据集上传到云端的`/home/`可以用以下指令: -```bash -paddle cp random_images-*-of-* pfs://home/ + +```bash +paddle pfs cp random_images-*-of-* /pfs/folder/ ``` ## TODO -- GitLab From e3fd0d56f46b26894c682213f17272c122d7c2a8 Mon Sep 17 00:00:00 2001 From: dangqingqing Date: Wed, 10 May 2017 16:34:24 +0800 Subject: [PATCH 0245/3256] Pass unit test --- paddle/scripts/run_python_tests.sh | 2 +- python/paddle/v2/image.py | 19 +++++++++++++++++-- python/paddle/v2/tests/CMakeLists.txt | 2 +- python/paddle/v2/tests/test_image.py | 2 +- 4 files changed, 20 insertions(+), 5 deletions(-) diff --git a/paddle/scripts/run_python_tests.sh b/paddle/scripts/run_python_tests.sh index 05c187522ca..02d2cdb9774 100755 --- a/paddle/scripts/run_python_tests.sh +++ b/paddle/scripts/run_python_tests.sh @@ -29,7 +29,7 @@ if [ $USE_VIRTUALENV_FOR_TEST -ne 0 ]; then fi export PYTHONPATH=$SCRIPTPATH/../../python/ -$PYTHON -m pip install $SCRIPTPATH/../dist/*.whl requests matplotlib ipython==5.3 +$PYTHON -m pip install $SCRIPTPATH/../dist/*.whl requests matplotlib opencv-python ipython==5.3 for fn in "$@" do diff --git a/python/paddle/v2/image.py b/python/paddle/v2/image.py index 20dcc96782c..ca75efd90dd 100644 --- a/python/paddle/v2/image.py +++ b/python/paddle/v2/image.py @@ -45,8 +45,13 @@ def load_image(file, is_color=True): return a color image. Otherwise, it will load and return a gray image. """ - flag = cv2.CV_LOAD_IMAGE_COLOR if is_color else \ - cv2.CV_LOAD_IMAGE_GRAYSCALE + # cv2.IMAGE_COLOR for OpenCV3 + # cv2.CV_LOAD_IMAGE_COLOR for older OpenCV Version + # cv2.IMAGE_GRAYSCALE for OpenCV3 + # cv2.CV_LOAD_IMAGE_GRAYSCALE for older OpenCV Version + # Here, use constant 1 and 0 + # 1: COLOR, 0: GRAYSCALE + flag = 1 if is_color else 0 im = cv2.imread(file, flag) return im @@ -178,6 +183,11 @@ def simple_transform(im, resize_size, crop_size, is_train, is_color=True): Simply data argumentation for traing. These operations includes resizing, croping and flipping. + Example usage: + + .. code-block:: python + im = simple_transform(im, 256, 224, True) + :param im: The input image with HWC layout. :type im: ndarray :param resize_size: The shorter edge length of the resized image. @@ -209,6 +219,11 @@ def load_and_transform(filename, data argumentation. Please refer the `simple_transform` interface for the transform operation. + Example usage: + + .. code-block:: python + im = load_and_transform('cat.jpg', 256, 224, True) + :param filename: The file name of input image. :type filename: string :param resize_size: The shorter edge length of the resized image. diff --git a/python/paddle/v2/tests/CMakeLists.txt b/python/paddle/v2/tests/CMakeLists.txt index 0b8c78b465c..eb02e53706b 100644 --- a/python/paddle/v2/tests/CMakeLists.txt +++ b/python/paddle/v2/tests/CMakeLists.txt @@ -1,2 +1,2 @@ add_python_test(test_v2_api test_data_feeder.py test_parameters.py -test_layer.py test_rnn_layer.py test_topology.py test_image) +test_layer.py test_rnn_layer.py test_topology.py test_image.py) diff --git a/python/paddle/v2/tests/test_image.py b/python/paddle/v2/tests/test_image.py index 5aa612f91d3..b2d773510de 100644 --- a/python/paddle/v2/tests/test_image.py +++ b/python/paddle/v2/tests/test_image.py @@ -26,7 +26,7 @@ class Image(unittest.TestCase): self.assertEqual(3, im.shape[2]) # flip - im = left_right_flip(im) + im = image.left_right_flip(im) im2 = np.flip(im, 1) self.assertEqual(im.all(), im2.all()) -- GitLab From ad0608847e8eef659f57d580c3816af653275339 Mon Sep 17 00:00:00 2001 From: Yancey1989 Date: Wed, 10 May 2017 19:30:04 +0800 Subject: [PATCH 0246/3256] Fix paddle.init bug --- python/paddle/v2/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/paddle/v2/__init__.py b/python/paddle/v2/__init__.py index 70c8ec0baa6..d13850597e0 100644 --- a/python/paddle/v2/__init__.py +++ b/python/paddle/v2/__init__.py @@ -52,7 +52,7 @@ def init(**kwargs): args_dict.update(kwargs) # NOTE: overwrite arguments from ENV if it is in kwargs for key in args_dict.keys(): - args.append('--%s=%s' % (key, str(kwargs[key]))) + args.append('--%s=%s' % (key, str(args_dict[key]))) api.initPaddle(*args) -- GitLab From 3513935f7ee4a6a9d6eb06b37dd681c015a37694 Mon Sep 17 00:00:00 2001 From: dangqingqing Date: Wed, 10 May 2017 19:30:46 +0800 Subject: [PATCH 0247/3256] refine the annotation. --- python/paddle/v2/image.py | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/python/paddle/v2/image.py b/python/paddle/v2/image.py index ca75efd90dd..13f53919aa4 100644 --- a/python/paddle/v2/image.py +++ b/python/paddle/v2/image.py @@ -11,22 +11,20 @@ __all__ = [ "left_right_flip", "simple_transform", "load_and_transform" ] """ -This file contains some common interface for image preprocess. +This file contains some common interfaces for image preprocess. Many users are confused about the image layout. We introduce -the image layout firstly. +the image layout as follows. - CHW Layout - The abbreviations: C=channel, H=Height, W=Width - - The default image layout is HWC opened by cv2 or PIL. - PaddlePaddle only support the image layout with CHW. - CHW is simply a transpose of HWC. It must transpose - the input image. + - The default layout of image opened by cv2 or PIL is HWC. + PaddlePaddle only supports the CHW layout. And CHW is simply + a transpose of HWC. It must transpose the input image. - Color format: RGB or BGR OpenCV use BGR color format. PIL use RGB color format. Both - formats can be used for training. But it must be noted that, - the format should be keep consistent between the training and - inference peroid. + formats can be used for training. Noted that, the format should + be keep consistent between the training and inference peroid. """ @@ -85,8 +83,8 @@ def resize_short(im, size): def to_chw(im, order=(2, 0, 1)): """ Transpose the input image order. The image layout is HWC format - opened by cv2 or PIL. Transposed the input image to CHW layouts - by order (2,0,1). + opened by cv2 or PIL. Transpose the input image to CHW layout + according the order (2,0,1). Example usage: @@ -116,7 +114,7 @@ def center_crop(im, size, is_color=True): :param im: the input image with HWC layout. :type im: ndarray - :param size: the cropping size + :param size: the cropping size. :type size: int :param is_color: whether the image is color or not. :type is_color: bool @@ -143,7 +141,7 @@ def random_crop(im, size, is_color=True): :param im: the input image with HWC layout. :type im: ndarray - :param size: the cropping size + :param size: the cropping size. :type size: int :param is_color: whether the image is color or not. :type is_color: bool @@ -180,7 +178,7 @@ def left_right_flip(im): def simple_transform(im, resize_size, crop_size, is_train, is_color=True): """ - Simply data argumentation for traing. These operations includes + Simply data argumentation for training. These operations include resizing, croping and flipping. Example usage: @@ -216,8 +214,8 @@ def load_and_transform(filename, is_color=True): """ Load image from the input file `filename` and transform image for - data argumentation. Please refer the `simple_transform` interface - for the transform operation. + data argumentation. Please refer to the `simple_transform` interface + for the transform operations. Example usage: -- GitLab From e59c183afb5c86055f0b29b77e37b1d5f0673c9d Mon Sep 17 00:00:00 2001 From: Yi Wang Date: Wed, 10 May 2017 11:12:32 -0700 Subject: [PATCH 0248/3256] Remove -D CPACK_DEBIAN_PACKAGE_DEPENDS="" from the invocation of cpack --- paddle/scripts/docker/build.sh | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/paddle/scripts/docker/build.sh b/paddle/scripts/docker/build.sh index b1a8274b1dd..101b44e6c62 100644 --- a/paddle/scripts/docker/build.sh +++ b/paddle/scripts/docker/build.sh @@ -113,14 +113,12 @@ fi # generate deb package for current build # FIXME(typhoonzero): should we remove paddle/scripts/deb ? -# FIXME: CPACK_DEBIAN_PACKAGE_DEPENDS removes all dev dependencies, must -# install them in docker cat < Date: Wed, 10 May 2017 11:15:49 -0700 Subject: [PATCH 0249/3256] Remove the incomplete file write_docs_en.rst. Will create it in another PR. --- doc/howto/dev/write_docs_en.rst | 77 --------------------------------- 1 file changed, 77 deletions(-) delete mode 100644 doc/howto/dev/write_docs_en.rst diff --git a/doc/howto/dev/write_docs_en.rst b/doc/howto/dev/write_docs_en.rst deleted file mode 100644 index 65e7edca94d..00000000000 --- a/doc/howto/dev/write_docs_en.rst +++ /dev/null @@ -1,77 +0,0 @@ -############### -Build Documents -############### - -Document files of PaddlePaddle are in sub-directory :code:`doc`. Source files are in `RST `_ format. We can build the document by letting `cmake`_ invoke `sphinx `_ to convert RST files into HTML files. - - -How to Build Documents -====================== - -To save the time of installing building tools, we provide a Docker image. - - -使用Docker构建PaddlePaddle的文档 --------------------------------- - -使用Docker构建PaddlePaddle的文档,需要在系统里先安装好Docker工具包。Docker安装请参考 `Docker的官网 `_ 。安装好Docker之后可以使用源码目录下的脚本构建文档,即 - -.. code-block:: bash - - cd TO_YOUR_PADDLE_CLONE_PATH - cd paddle/scripts/tools/build_docs - bash build_docs.sh with_docker - -编译完成后,会在当前目录生成两个子目录\: - -* doc 英文文档目录 -* doc_cn 中文文档目录 - -打开浏览器访问对应目录下的index.html即可访问本地文档。 - - - -直接构建PaddlePaddle的文档 --------------------------- - -因为PaddlePaddle的v2 api文档生成过程依赖于py_paddle Python包,用户需要首先确认py_paddle包已经安装。 - -.. code-block:: bash - - python -c "import py_paddle" - -如果提示错误,那么用户需要在本地编译安装PaddlePaddle,请参考 `源码编译文档 `_ 。 -注意,用户在首次编译安装PaddlePaddle时,请将WITH_DOC选项关闭。在编译安装正确之后,请再次确认py_paddle包已经安装,即可进行下一步操作。 - -如果提示正确,可以执行以下命令编译生成文档,即 - -.. code-block:: bash - - cd TO_YOUR_PADDLE_CLONE_PATH - cd paddle/scripts/tools/build_docs - bash build_docs.sh local - -编译完成之后,会在当前目录生成两个子目录\: - -* doc 英文文档目录 -* doc_cn 中文文档目录 - -打开浏览器访问对应目录下的index.html即可访问本地文档。 - - -如何书写PaddlePaddle的文档 -========================== - -PaddlePaddle文档使用 `sphinx`_ 自动生成,用户可以参考sphinx教程进行书写。 - -如何更新www.paddlepaddle.org文档 -================================ - -开发者给PaddlePaddle代码增加的注释以PR的形式提交到github中,提交方式可参见 `贡献文档 `_ 。 -目前PaddlePaddle的develop分支的文档是自动触发更新的,用户可以分别查看最新的 `中文文档 `_ 和 -`英文文档 `_ 。 - - - -.. _cmake: https://cmake.org/ -.. _sphinx: http://www.sphinx-doc.org/en/1.4.8/ -- GitLab From 66a3cfe36f10c95dbccb1b3e2b08aafb02247437 Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Wed, 10 May 2017 16:41:37 -0700 Subject: [PATCH 0250/3256] change to C API --- doc/design/cluster_train/pserver_client.md | 95 ++++++++++++++++++++++ doc/design/cluster_train/trainer.md | 82 ------------------- 2 files changed, 95 insertions(+), 82 deletions(-) create mode 100644 doc/design/cluster_train/pserver_client.md delete mode 100644 doc/design/cluster_train/trainer.md diff --git a/doc/design/cluster_train/pserver_client.md b/doc/design/cluster_train/pserver_client.md new file mode 100644 index 00000000000..62edd349f3f --- /dev/null +++ b/doc/design/cluster_train/pserver_client.md @@ -0,0 +1,95 @@ +# Design Doc: The Client Library of Parameter Server + +For an overview of trainer's role, please refer to [distributed training design doc](README.md). In this design doc, we will discuss the parameter server's client library, which will manage communication with parameter servers. The library will be implemented in [Go](https://golang.org/) and made available as a static or dynamic library with a C header file. + +## C Interface + +```c +#define PADDLE_ELEMENT_TYPE_INT32 0 +#define PADDLE_ELEMENT_TYPE_UINT32 1 +#define PADDLE_ELEMENT_TYPE_INT64 2 +#define PADDLE_ELEMENT_TYPE_UINT64 3 +#define PADDLE_ELEMENT_TYPE_FLOAT32 4 +#define PADDLE_ELEMENT_TYPE_FLOAT64 5 + +typedef struct paddle_pserver_client paddle_pserver_client; + +/** + * @brief paddle_new_pserver_client creates a new parameter server + * client. + */ +paddle_pserver_client* paddle_new_pserver_client(); + +/** + * @brief paddle_pserver_client_release releases the parameter server + * client. + */ +void paddle_pserver_client_release(paddle_pserver_client* client); + +/** + * @brief paddle_begin_init_param begins to initialize parameters + * on parameter servers. + * + * paddle_begin_init_param will be called from multiple trainers, only + * one trainer will be selected to initialize the parameters on + * parameter servers. Other trainers will be blocked until the + * initialization is done, and they need to get the initialized + * parameters from parameter servers using @paddle_get_param. + * + * @return 1 if trainer is selected to initialize parameter + * servers, otherwise 0. + */ +int paddle_begin_init_param(paddle_pserver_client* client); + +/** + * @brief paddle_init_param initializes the parameter on parameter + * servers. + * + * @return 0 if successful, otherwise -1. On failure the trainer need + * to restart the entire initialization process starting from + * paddle_begin_init_param. Or simply exit the program and wait for + * cluster management system to restart trainer. + */ +int paddle_init_param(paddle_pserver_client* client, const char* name, int element_type, const void* content); + +/** + * @brief paddle_finish_init_param tells parameter servers client has + * sent all parameters to parameter servers as initialization. + * + * @return 0 if successful, otherwise -1. On failure the trainer need + * to restart the entire initialization process starting from + * paddle_begin_init_param. Or simply exit the program and wait for + * cluster management system to restart trainer. + */ +int paddle_finish_init_param(paddle_pserver_client* client); + +/** + * @brief paddle_send_grad sends gradients to parameter servers for + * updating parameters. + * + * @return 0 if successful, otherwise -1. + */ +int paddle_send_grad(paddle_pserver_client* client, const char* name, int element_type, const void* content); + +/** + * @brief paddle_set_param sets a parameter on parameter servers. + * + * @return 0 if successful, otherwise -1. + */ +int paddle_set_param(paddle_pserver_client* client, const char* name, int element_type, const void* content); + +/** + * @brief paddle_get_param gets the parameter from parameter servers. + * + * @return 0 if successful, otherwise -1. + */ +int paddle_get_param(paddle_pserver_client* client, const char* name, void** dst, int* dstLen); + +/** + * @brief paddle_save_model indicates parameters to save the parameter + * to the given path + * + * @return 0 if successful, otherwise -1. + */ +int paddle_save_model(paddle_pserver_client* client, const char* path); +``` diff --git a/doc/design/cluster_train/trainer.md b/doc/design/cluster_train/trainer.md deleted file mode 100644 index bcb4a9c09dc..00000000000 --- a/doc/design/cluster_train/trainer.md +++ /dev/null @@ -1,82 +0,0 @@ -# Design Doc: Trainer Communication Library - -For an overview of trainer's role, please refer to [distributed training design doc](README.md). In this design doc, we will discuss the trainer's communication library, which will manage communication with parameter servers and the [master server](master_server.md). The library will be implemented in [Go](https://golang.org/) and made available as a static or dynamic library with a C header file. - -## Go Interface - -The Go interface is the basic abstraction of communications with the master server and parameter servers. We will add another layer on top (add retry logic, polish interface with C idiom) before exposing the library with a [C interface](#c-interface). - -```go -// MasterClient is the client to the master server. -type MasterClient struct {} - -// GetTask gets a new task by telling the master server the finished task. -// Use nil as the finished task when getting the task for the first time. -func (*MasterClient) GetTask(finished master.Task) (master.Task, error) - -// ElementType is the type of elements of a Parameter. -type ElementType int - -// Different element types. -const ( - Int32 ElementType = iota - UInt32 - Int64 - UInt64 - Float32 - Float64 -) - -// Parameter is a piece of data to sync with the parameter server. -type Parameter struct { - Name string - ElementType ElementType - Buffer []byte -} - -// Gradient is the gradient of the parameter. -type Gradient Parameter - -// PServerClient is the client to parameter servers. -type PServerClient struct {} - -// UpdateRule specifies the rule for updating parameters with gradients. -type UpdateRule struct { - UpdateMethod pserver.UpdateMethod - LearningRate float32 -} - -// ParamInitChans returns a send channel for parameter initialization. -// -// ParamInitChans will be called from multiple trainers, only one trainer should -// initialize the parameters on parameter servers, other trainers will instead -// get the initialized parameters from parameter servers using GetParam. -// -// If send channel is not nil, the trainer is selected to do the initialization, -// the trainer needs to signal for finishing initializing the parameters by -// closing the send channel. -func (*PServerClient) ParamInitChan() (send chan<- Parameter, err error) - -// SendGrad sends gradients to parameter servers for updating parameters. -func (*PServerClient) SendGrad(method UpdateMethod, grads []Gradient) error - -// SetParam sets parameters. -// -// SetParam can be used for the parameters that are not suitable for updating -// using gradients. -func (*PServerClient) SetParam(params []Paramter) error - -// GetParam gets parameters from parameter servers. -func (*PServerClient) GetParam(names []string) ([]Parameter, error) - -// Save indicates parameters to save the parameter to the given path. -// -// Path needs to be the path to a distributed file system which is visible -// to all parameter servers. -func (*PServerClient) Save(path string) error -``` -Please see [master server design doc](master_server.md) for the definition of `master.Task`. - -## C Interface - -TODO -- GitLab From 0064fcb082930bbaf3d2f144697af677db0b0d6a Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Wed, 10 May 2017 17:19:54 -0700 Subject: [PATCH 0251/3256] update C API --- doc/design/cluster_train/pserver_client.md | 49 +++++++++++++++------- 1 file changed, 34 insertions(+), 15 deletions(-) diff --git a/doc/design/cluster_train/pserver_client.md b/doc/design/cluster_train/pserver_client.md index 62edd349f3f..7ecd4cff065 100644 --- a/doc/design/cluster_train/pserver_client.md +++ b/doc/design/cluster_train/pserver_client.md @@ -12,6 +12,13 @@ For an overview of trainer's role, please refer to [distributed training design #define PADDLE_ELEMENT_TYPE_FLOAT32 4 #define PADDLE_ELEMENT_TYPE_FLOAT64 5 +typedef struct { + char* name; + int element_type; + void* content; + int content_len; +} paddle_parameter, paddle_gradient; + typedef struct paddle_pserver_client paddle_pserver_client; /** @@ -27,33 +34,36 @@ paddle_pserver_client* paddle_new_pserver_client(); void paddle_pserver_client_release(paddle_pserver_client* client); /** - * @brief paddle_begin_init_param begins to initialize parameters + * @brief paddle_begin_init_params begins to initialize parameters * on parameter servers. * - * paddle_begin_init_param will be called from multiple trainers, only + * paddle_begin_init_params will be called from multiple trainers, only * one trainer will be selected to initialize the parameters on * parameter servers. Other trainers will be blocked until the * initialization is done, and they need to get the initialized - * parameters from parameter servers using @paddle_get_param. + * parameters from parameter servers using @paddle_get_params. * - * @return 1 if trainer is selected to initialize parameter - * servers, otherwise 0. + * @param config_proto serialized parameter server configuration + * protobuffer. + * @return 1 if trainer is selected to initialize parameter servers, + * otherwise 0. */ -int paddle_begin_init_param(paddle_pserver_client* client); +int paddle_begin_init_params(paddle_pserver_client* client, const char* config_proto); /** * @brief paddle_init_param initializes the parameter on parameter * servers. * + * @param param the parameter to initialize. * @return 0 if successful, otherwise -1. On failure the trainer need * to restart the entire initialization process starting from * paddle_begin_init_param. Or simply exit the program and wait for * cluster management system to restart trainer. */ -int paddle_init_param(paddle_pserver_client* client, const char* name, int element_type, const void* content); +int paddle_init_param(paddle_pserver_client* client, paddle_parameter params); /** - * @brief paddle_finish_init_param tells parameter servers client has + * @brief paddle_finish_init_params tells parameter servers client has * sent all parameters to parameter servers as initialization. * * @return 0 if successful, otherwise -1. On failure the trainer need @@ -61,34 +71,43 @@ int paddle_init_param(paddle_pserver_client* client, const char* name, int eleme * paddle_begin_init_param. Or simply exit the program and wait for * cluster management system to restart trainer. */ -int paddle_finish_init_param(paddle_pserver_client* client); +int paddle_finish_init_params(paddle_pserver_client* client); /** - * @brief paddle_send_grad sends gradients to parameter servers for + * @brief paddle_send_grads sends gradients to parameter servers for * updating parameters. * + * @param grads the array of gradients to send. + * @param total the total number of gradient inside the gradient array. + * @param learning_rate the learning rate for the gradients. * @return 0 if successful, otherwise -1. */ -int paddle_send_grad(paddle_pserver_client* client, const char* name, int element_type, const void* content); +int paddle_send_grads(paddle_pserver_client* client, const paddle_gradient* grads, int total, double learning_rate); /** - * @brief paddle_set_param sets a parameter on parameter servers. + * @brief paddle_set_params sets parameters to parameter servers. * + * @param params the array of parameters to set to parameter servers. + * @param total number of parameters inside the parameter array. * @return 0 if successful, otherwise -1. */ -int paddle_set_param(paddle_pserver_client* client, const char* name, int element_type, const void* content); +int paddle_set_params(paddle_pserver_client* client, const paddle_parameter* params, int total); /** - * @brief paddle_get_param gets the parameter from parameter servers. + * @brief paddle_get_params gets parameters from parameter servers. * + * @param names the array of names of the parameters to get. + * @param dst the destination array of parameters to save to. + * @param total the total number of parameters to get. * @return 0 if successful, otherwise -1. */ -int paddle_get_param(paddle_pserver_client* client, const char* name, void** dst, int* dstLen); +int paddle_get_params(paddle_pserver_client* client, const char** names, paddle_parameter* dst, int total); /** * @brief paddle_save_model indicates parameters to save the parameter * to the given path * + * @param path the path to save parameters. * @return 0 if successful, otherwise -1. */ int paddle_save_model(paddle_pserver_client* client, const char* path); -- GitLab From 1525216078c6fbaa2f3cf201f52943ed11f81c63 Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Wed, 10 May 2017 17:30:27 -0700 Subject: [PATCH 0252/3256] update documentation --- doc/design/cluster_train/pserver_client.md | 41 +++++++++------------- 1 file changed, 16 insertions(+), 25 deletions(-) diff --git a/doc/design/cluster_train/pserver_client.md b/doc/design/cluster_train/pserver_client.md index 7ecd4cff065..8c4ed46199f 100644 --- a/doc/design/cluster_train/pserver_client.md +++ b/doc/design/cluster_train/pserver_client.md @@ -21,32 +21,23 @@ typedef struct { typedef struct paddle_pserver_client paddle_pserver_client; -/** - * @brief paddle_new_pserver_client creates a new parameter server - * client. - */ paddle_pserver_client* paddle_new_pserver_client(); - -/** - * @brief paddle_pserver_client_release releases the parameter server - * client. - */ void paddle_pserver_client_release(paddle_pserver_client* client); /** - * @brief paddle_begin_init_params begins to initialize parameters - * on parameter servers. + * @brief paddle_begin_init_params begins to initialize parameters on + * parameter servers. * - * paddle_begin_init_params will be called from multiple trainers, only - * one trainer will be selected to initialize the parameters on + * paddle_begin_init_params will be called from multiple trainers, + * only one trainer will be selected to initialize the parameters on * parameter servers. Other trainers will be blocked until the * initialization is done, and they need to get the initialized * parameters from parameter servers using @paddle_get_params. * - * @param config_proto serialized parameter server configuration - * protobuffer. - * @return 1 if trainer is selected to initialize parameter servers, - * otherwise 0. + * @param config_proto serialized parameter server configuration in + * Protocol Buffers format. + * @return 1 if the trainer is selected to initialize parameter + * servers, otherwise 0. */ int paddle_begin_init_params(paddle_pserver_client* client, const char* config_proto); @@ -55,10 +46,10 @@ int paddle_begin_init_params(paddle_pserver_client* client, const char* config_p * servers. * * @param param the parameter to initialize. - * @return 0 if successful, otherwise -1. On failure the trainer need - * to restart the entire initialization process starting from - * paddle_begin_init_param. Or simply exit the program and wait for - * cluster management system to restart trainer. + * @return 0 if successful, otherwise -1. On failure, the trainer need + * to restart the entire initialization process (starting + * from @paddle_begin_init_param). Or simply exit the program and wait + * for cluster management system to restart trainer. */ int paddle_init_param(paddle_pserver_client* client, paddle_parameter params); @@ -66,10 +57,10 @@ int paddle_init_param(paddle_pserver_client* client, paddle_parameter params); * @brief paddle_finish_init_params tells parameter servers client has * sent all parameters to parameter servers as initialization. * - * @return 0 if successful, otherwise -1. On failure the trainer need - * to restart the entire initialization process starting from - * paddle_begin_init_param. Or simply exit the program and wait for - * cluster management system to restart trainer. + * @return 0 if successful, otherwise -1. On failure, the trainer need + * to restart the entire initialization process (starting + * from @paddle_begin_init_param). Or simply exit the program and wait + * for cluster management system to restart trainer. */ int paddle_finish_init_params(paddle_pserver_client* client); -- GitLab From 31654e267f1b2645de249b96ca5e8099aa030a32 Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Wed, 10 May 2017 17:42:22 -0700 Subject: [PATCH 0253/3256] fix grammar --- doc/design/cluster_train/pserver_client.md | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/doc/design/cluster_train/pserver_client.md b/doc/design/cluster_train/pserver_client.md index 8c4ed46199f..96cc649b2c2 100644 --- a/doc/design/cluster_train/pserver_client.md +++ b/doc/design/cluster_train/pserver_client.md @@ -46,10 +46,10 @@ int paddle_begin_init_params(paddle_pserver_client* client, const char* config_p * servers. * * @param param the parameter to initialize. - * @return 0 if successful, otherwise -1. On failure, the trainer need - * to restart the entire initialization process (starting - * from @paddle_begin_init_param). Or simply exit the program and wait - * for cluster management system to restart trainer. + * @return 0 if successful, otherwise -1. On failure, the trainer + * needs to restart the entire initialization process (starting from + * @paddle_begin_init_param). Or simply exit the program and wait for + * the cluster management system to restart the trainer. */ int paddle_init_param(paddle_pserver_client* client, paddle_parameter params); @@ -57,10 +57,10 @@ int paddle_init_param(paddle_pserver_client* client, paddle_parameter params); * @brief paddle_finish_init_params tells parameter servers client has * sent all parameters to parameter servers as initialization. * - * @return 0 if successful, otherwise -1. On failure, the trainer need - * to restart the entire initialization process (starting - * from @paddle_begin_init_param). Or simply exit the program and wait - * for cluster management system to restart trainer. + * @return 0 if successful, otherwise -1. On failure, the trainer + * needs to restart the entire initialization process (starting from + * @paddle_begin_init_param). Or simply exit the program and wait for + * the cluster management system to restart the trainer. */ int paddle_finish_init_params(paddle_pserver_client* client); @@ -79,7 +79,8 @@ int paddle_send_grads(paddle_pserver_client* client, const paddle_gradient* grad * @brief paddle_set_params sets parameters to parameter servers. * * @param params the array of parameters to set to parameter servers. - * @param total number of parameters inside the parameter array. + * @param total the total number of parameters inside the parameter + * array. * @return 0 if successful, otherwise -1. */ int paddle_set_params(paddle_pserver_client* client, const paddle_parameter* params, int total); -- GitLab From 8673366329b11bb102a5639b516c84d938f25863 Mon Sep 17 00:00:00 2001 From: Yi Wang Date: Wed, 10 May 2017 18:17:11 -0700 Subject: [PATCH 0254/3256] Abandon my change to the Chinese document --- doc/howto/dev/write_docs_cn.rst | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/doc/howto/dev/write_docs_cn.rst b/doc/howto/dev/write_docs_cn.rst index 5d1297d0796..d536f53abc0 100644 --- a/doc/howto/dev/write_docs_cn.rst +++ b/doc/howto/dev/write_docs_cn.rst @@ -2,13 +2,14 @@ 如何贡献/修改文档 ################## -PaddlePaddle的文档文件都在 :code:`doc` 这个子目录里。源文件是 `RST `_ 格式的。 在编译PaddlePaddle源码的时候,可以选择让 `cmake`_ 调用 `sphinx `_ 从 RST 文件生成 HTML 格式的文档。 +PaddlePaddle的文档包括英文文档 ``doc`` 和中文文档 ``doc_cn`` 两个部分。文档都是通过 `cmake`_ 驱动 `sphinx`_ 编译生成,生成后的文档分别存储在编译目录的 ``doc`` 和 ``doc_cn`` 两个子目录下。 如何构建PaddlePaddle的文档 ========================== -为了简化大家安装文档构建工具的过程,我们提供一个Docker image。 +PaddlePaddle的文档构建有直接构建和基于Docker构建两种方式,我们提供了一个构建脚本build_docs.sh来进行构建。 +PaddlePaddle文档需要准备的环境相对较复杂,所以我们推荐使用基于Docker来构建PaddlePaddle的文档。 使用Docker构建PaddlePaddle的文档 @@ -73,5 +74,5 @@ PaddlePaddle文档使用 `sphinx`_ 自动生成,用户可以参考sphinx教程 -.. _cmake: -.. _sphinx: +.. _cmake: https://cmake.org/ +.. _sphinx: http://www.sphinx-doc.org/en/1.4.8/ -- GitLab From f1b89f93204abc0db45ed155af049374fbe4b75b Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Wed, 10 May 2017 18:33:12 -0700 Subject: [PATCH 0255/3256] add illustration --- doc/design/cluster_train/pserver_client.md | 14 ++++++++++++++ doc/design/cluster_train/src/init_lock.graffle | Bin 0 -> 3090 bytes doc/design/cluster_train/src/init_lock.png | Bin 0 -> 26774 bytes .../cluster_train/src/pserver_init.graffle | Bin 0 -> 2622 bytes doc/design/cluster_train/src/pserver_init.png | Bin 0 -> 28853 bytes 5 files changed, 14 insertions(+) create mode 100644 doc/design/cluster_train/src/init_lock.graffle create mode 100644 doc/design/cluster_train/src/init_lock.png create mode 100644 doc/design/cluster_train/src/pserver_init.graffle create mode 100644 doc/design/cluster_train/src/pserver_init.png diff --git a/doc/design/cluster_train/pserver_client.md b/doc/design/cluster_train/pserver_client.md index 96cc649b2c2..c1cb93434e5 100644 --- a/doc/design/cluster_train/pserver_client.md +++ b/doc/design/cluster_train/pserver_client.md @@ -2,6 +2,20 @@ For an overview of trainer's role, please refer to [distributed training design doc](README.md). In this design doc, we will discuss the parameter server's client library, which will manage communication with parameter servers. The library will be implemented in [Go](https://golang.org/) and made available as a static or dynamic library with a C header file. +## Parameter Initialization + +The parameters on parameter servers need to be initialized. To provide maximum flexibility, we need to allow trainer initialized the parameters. Only one trainer will do the initialization, the other trainers will wait for the completion of initialization and get the parameters from the parameter servers. + +To select the trainer for initialization, every trainer will try to get a distributed lock, whoever owns the lock will do the initialization. As illustrated below: + + +The select process is encapsulated in the C API function: +```c +int paddle_begin_init_params(paddle_pserver_client* client, const char* config_proto); +``` +The selected trainer's call to `paddle_begin_init_params` will return with 1, and the other trainers' call to `paddle_begin_init_params` will block until initialization is done, and return 0. As illustrated below: + + ## C Interface ```c diff --git a/doc/design/cluster_train/src/init_lock.graffle b/doc/design/cluster_train/src/init_lock.graffle new file mode 100644 index 0000000000000000000000000000000000000000..fa9149f21b1311eed48ef72ec55e556559d0fc94 GIT binary patch literal 3090 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 literal 0 HcmV?d00001 diff --git a/doc/design/cluster_train/src/init_lock.png b/doc/design/cluster_train/src/init_lock.png new file mode 100644 index 0000000000000000000000000000000000000000..92404ee6d6c0f9a7727952bae3c869ba338ecd7f GIT binary patch literal 26774 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

    ?^QzZmlwkCBs^iLIKjO8EzT)3arMfVTO!Q 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 gH&f(Yg7l4S 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 Date: Wed, 10 May 2017 18:34:04 -0700 Subject: [PATCH 0256/3256] change img positioning --- doc/design/cluster_train/pserver_client.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/design/cluster_train/pserver_client.md b/doc/design/cluster_train/pserver_client.md index c1cb93434e5..56469fc2153 100644 --- a/doc/design/cluster_train/pserver_client.md +++ b/doc/design/cluster_train/pserver_client.md @@ -7,6 +7,7 @@ For an overview of trainer's role, please refer to [distributed training design The parameters on parameter servers need to be initialized. To provide maximum flexibility, we need to allow trainer initialized the parameters. Only one trainer will do the initialization, the other trainers will wait for the completion of initialization and get the parameters from the parameter servers. To select the trainer for initialization, every trainer will try to get a distributed lock, whoever owns the lock will do the initialization. As illustrated below: + The select process is encapsulated in the C API function: @@ -14,6 +15,7 @@ The select process is encapsulated in the C API function: int paddle_begin_init_params(paddle_pserver_client* client, const char* config_proto); ``` The selected trainer's call to `paddle_begin_init_params` will return with 1, and the other trainers' call to `paddle_begin_init_params` will block until initialization is done, and return 0. As illustrated below: + ## C Interface -- GitLab From c583447e900af61ac69fa6b0a41e9ab10cf3e90a Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Wed, 10 May 2017 18:36:46 -0700 Subject: [PATCH 0257/3256] add subtitle --- doc/design/cluster_train/pserver_client.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/design/cluster_train/pserver_client.md b/doc/design/cluster_train/pserver_client.md index 56469fc2153..ee40eb32c55 100644 --- a/doc/design/cluster_train/pserver_client.md +++ b/doc/design/cluster_train/pserver_client.md @@ -6,10 +6,14 @@ For an overview of trainer's role, please refer to [distributed training design The parameters on parameter servers need to be initialized. To provide maximum flexibility, we need to allow trainer initialized the parameters. Only one trainer will do the initialization, the other trainers will wait for the completion of initialization and get the parameters from the parameter servers. +### Trainer Selection + To select the trainer for initialization, every trainer will try to get a distributed lock, whoever owns the lock will do the initialization. As illustrated below: +### Selection Process + The select process is encapsulated in the C API function: ```c int paddle_begin_init_params(paddle_pserver_client* client, const char* config_proto); -- GitLab From 63c74257de0081ed158c3cb48c4eb55b95c3a78a Mon Sep 17 00:00:00 2001 From: gongweibao Date: Thu, 11 May 2017 09:50:43 +0800 Subject: [PATCH 0258/3256] modify by comments --- doc/design/cluster_train/data_dispatch.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/design/cluster_train/data_dispatch.md b/doc/design/cluster_train/data_dispatch.md index 7690cb279dd..64f24bc05e3 100644 --- a/doc/design/cluster_train/data_dispatch.md +++ b/doc/design/cluster_train/data_dispatch.md @@ -14,7 +14,7 @@ ### 训练数据的存储 We select CephFS to store our data. -From the perspective of user program running in a Pod, it is only I/O with the local filesystem, as +From the perspective of user program running in a Pod, it is mounted locally, as 1. the home directory should have been mapped to the Pod-local directory `/home`, and 1. some shared directories, e.g., the pre-downloaded `paddle.v2.dataset` data, should have been mapped to the Pod-local directory `/common`. @@ -98,7 +98,7 @@ PaddlePaddle提供专用的[data reader creator](https://github.com/PaddlePaddle ```python # ... -reader = paddle.reader.creator.RecordIO("/home/random_images-*-of-*") +reader = paddle.reader.creator.RecordIO("/home/user_name/random_images-*-of-*") batch_reader = paddle.batch(paddle.dataset.mnist.train(), 128) trainer.train(batch_reader, ...) ``` @@ -110,13 +110,13 @@ trainer.train(batch_reader, ...) 使用下面命令,可以把本地的数据上传到存储集群中。 ```bash -paddle pfs cp filenames /pfs/folder/ +paddle pfs cp filenames /pfs/$DATACENTER/home/$USER/folder/ ``` 比如,把之前示例中转换完毕的random_images数据集上传到云端的`/home/`可以用以下指令: ```bash -paddle pfs cp random_images-*-of-* /pfs/folder/ +paddle pfs cp random_images-*-of-* /pfs/$DATACENTER/home/$USER/folder/ ``` ## TODO -- GitLab From ffd45b79672a2168925f06a13f03fc4cf9e30123 Mon Sep 17 00:00:00 2001 From: gongweibao Date: Thu, 11 May 2017 11:52:28 +0800 Subject: [PATCH 0259/3256] modify by comments --- doc/design/file_mananger/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/design/file_mananger/README.md b/doc/design/file_mananger/README.md index 27af3c51e68..9d75089257f 100644 --- a/doc/design/file_mananger/README.md +++ b/doc/design/file_mananger/README.md @@ -4,7 +4,7 @@ 主要功能包括: - 提供常用的命令行文件管理命令管理文件 - - 支持的命令在[Here] (./pfs/pfs.md) + - 支持的命令在[Here](./pfs/pfs.md) - 支持大文件的断点上传、下载 ## 名词解释 -- GitLab From 634648c2ecbc7d9332c00f7fdd8b81eae93dd78e Mon Sep 17 00:00:00 2001 From: Liu Yiqun Date: Wed, 10 May 2017 13:03:03 +0800 Subject: [PATCH 0260/3256] Remove the extra return statement. --- Dockerfile.android | 2 +- paddle/math/MathFunctions.cpp | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/Dockerfile.android b/Dockerfile.android index 9df291250dc..4d9ced33f43 100644 --- a/Dockerfile.android +++ b/Dockerfile.android @@ -40,4 +40,4 @@ RUN mkdir /opt/android-ndk-tmp && \ ${ANDROID_NDK_HOME}/build/tools/make-standalone-toolchain.sh --arch=arm --platform=android-21 --install-dir=${ANDROID_STANDALONE_TOOLCHAIN} && \ rm -rf /opt/android-ndk-tmp -CMD ["bash", "paddle/paddle/scripts/docker/build_android.sh"] +CMD ["bash", "/paddle/paddle/scripts/docker/build_android.sh"] diff --git a/paddle/math/MathFunctions.cpp b/paddle/math/MathFunctions.cpp index 1a3bb432bfb..7045562dd44 100644 --- a/paddle/math/MathFunctions.cpp +++ b/paddle/math/MathFunctions.cpp @@ -180,7 +180,6 @@ int getri(const CBLAS_ORDER order, const int lda, const int* ipiv) { return dynload::PADDLE_DGETRI(order, N, A, lda, ipiv); - return 0; } template <> -- GitLab From 45d800b34a880b34d0a69b9e0e698d0e7dea5dd6 Mon Sep 17 00:00:00 2001 From: liaogang Date: Thu, 11 May 2017 14:16:41 +0800 Subject: [PATCH 0261/3256] add tensor doc --- paddle/tensor/README.md | 125 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 paddle/tensor/README.md diff --git a/paddle/tensor/README.md b/paddle/tensor/README.md new file mode 100644 index 00000000000..ea1fddb665f --- /dev/null +++ b/paddle/tensor/README.md @@ -0,0 +1,125 @@ +# Tensor: An Unified Data Type in PaddlePaddle + +## Pain Point + +In this week, we discussed several potential weaknesses of PaddlePaddle caused by rapid iteration and development to promote new business products on the line in recent four years. For instance, current Matrix/Vector implementation in PaddlePaddle are long and tedious to read, which interfered seriously with the contribution of both fresh and professional engineers. More seriously for this issue, it will also become too challenging to maintain over time. + + +## Learn from Majel + +Consequently, we decide to refactor PaddlePaddle step-by-step. First, refactor and replace Matrix/Vector to Tensor, a modern terminology in the deep learning system. Fortunately, we can learn from Majel how to define a Tensor. + +To simplify heterogeneous resource allocation in any dimensions (1-9) and types (double, float, float16), Majel consists of several primitives such as `Dim`, `Place` and `Array`, all of them are standard C++ classes. + +1. `Place`: memory location [i.e. CPU/GPU]. +2. `Allocation`: heterogeneous resource allocator [i.e. 20MB in GPU]. +3. `Dim`: size of each dimension. [i.e. Dim<4>({10, 2, 5, 1})] +4. `Array`: dynamic array consists of `Place`, `Dim`, and a pointer to memory. + +If you dig deeper into Majel source code, you will find Majel heavily use `boost.variant`. The variant class template is a safe, generic, stack-based discriminated union container, **offering a simple solution for manipulating an object from a heterogeneous set of types in a uniform manner**. Whereas standard containers such as std::vector may be thought of as "multi-value, single type," variant is "multi-type, single value." + +As a simple example, consider the following: + +```c++ +#include "boost/variant.hpp" +#include + +class my_visitor : public boost::static_visitor +{ +public: + int operator()(int i) const + { + return i; + } + + int operator()(const std::string & str) const + { + return str.length(); + } +}; + +int main() +{ + boost::variant< int, std::string > u("hello world"); + std::cout << u; // output: hello world + + int result = boost::apply_visitor( my_visitor(), u ); + std::cout << result; // output: 11 (i.e., length of "hello world") +} +``` + +In Majel, `DDimVar` is derived from `Dim`, `DArrayVar` is from `Array`. + +```c++ +template +struct Dim { +... +int head; +Dim tail; +} +``` + +```c++ +template +class Array : public Buffer { + ... +private: + Dim size_; + Dim stride_; + T* ptr_; +}; +``` + +```c++ +typedef boost::variant Place; +typedef boost::variant, Dim<2>, Dim<3>, Dim<4>, Dim<5>, + Dim<6>, Dim<7>, Dim<8>, Dim<9>> DDimVar; +typedef boost::variant< + Array, + Array, + Array, + Array, + + Array, + Array, + Array, + Array, + + Array, + Array, + Array, + Array > DArrayVar; +``` + +Because `variant` may be thought of as "multi-type, single value", we can utilize it to implement unified interfaces for PaddlePaddle. + +## implement Tensor in Paddle + +Before writing code, please make sure you already look through Majel Source Code and grabbed the design philosophy of `DArray` in Majel. + +To assign subtasks to our colleagues, we have to discuss how to divide it to independent subtasks. + +- [ ] 1. First, we need to consider the third-party dependencies in Majel. + +Majel heavily use `boost.variant`, but we don't want to integrate `boost` into PaddlePaddle. It's better to replace boost using the lightweight implementation. https://github.com/mapbox/variant Mapbox variant has the same speedy performance of `boost::variant `but is faster to compile, results in smaller binaries, and has no dependencies. + +> @gangliao + +- [ ] 2. Re-implement `Place` and `Allocation/Memory` + +I found @wangkuiyi submitted a pull request includes `Place`. @gangliao and @qijun could re-implement `Allocation', because we have the GPU development experience before joining Paddle team. + +> @wangkuiyi @gangliao @qijun + +- [ ] 3. Re-implement `Dim`. +`Dim` is an excellent implementation in Majel. + +> ??? + +- [ ] 4. Re-implement `Array/Tensor`. + +> Prerequisites: 1 - 3 + +- [ ] 5. Re-implement fundamental operators for `Array/Tensor`. + +> Prerequisites: 1 - 4 -- GitLab From 9fbeb3e8842a9f3520f72f47f5fec2a8a4811108 Mon Sep 17 00:00:00 2001 From: gongweibao Date: Thu, 11 May 2017 14:20:33 +0800 Subject: [PATCH 0262/3256] modify by comments --- doc/design/cluster_train/data_dispatch.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/doc/design/cluster_train/data_dispatch.md b/doc/design/cluster_train/data_dispatch.md index 64f24bc05e3..beee05787f7 100644 --- a/doc/design/cluster_train/data_dispatch.md +++ b/doc/design/cluster_train/data_dispatch.md @@ -118,6 +118,23 @@ paddle pfs cp filenames /pfs/$DATACENTER/home/$USER/folder/ ```bash paddle pfs cp random_images-*-of-* /pfs/$DATACENTER/home/$USER/folder/ ``` + +需要`$DATACENTER`的配置写到配置文件中,例如 + +``` +# config file +[datacenter_1] +username=wuyi +usercert=wuyi.pem +userkey=wuyi-key.pem +endpoint=datacenter1.paddlepaddle.org + +[datacenter_2] +username=wuyi +usercert=wuyi.pem +userkey=wuyi-key.pem +endpoint=datacenter2.paddlepaddle.org +``` ## TODO ### 支持用户自定义的数据预处理job -- GitLab From 85502783aaa85a43c82a851e9944f535f09c394f Mon Sep 17 00:00:00 2001 From: Yancey1989 Date: Thu, 11 May 2017 14:24:33 +0800 Subject: [PATCH 0263/3256] Bugfix: checkout input_type failed --- python/paddle/trainer/PyDataProvider2.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/paddle/trainer/PyDataProvider2.py b/python/paddle/trainer/PyDataProvider2.py index 0e752c117c1..a36f0ebfdcb 100644 --- a/python/paddle/trainer/PyDataProvider2.py +++ b/python/paddle/trainer/PyDataProvider2.py @@ -270,7 +270,7 @@ class CheckWrapper(object): assert isinstance(each, collections.Sequence) for d in each: assert isinstance(d, float) - assert len(each, input_type.dim) + assert len(each) == input_type.dim elif input_type.type == DataType.Index: assert isinstance(each, int) assert each < input_type.dim @@ -304,7 +304,7 @@ class CheckInputTypeWrapper(object): def __call__(self, obj, filename): for items in self.generator(obj, filename): try: - # dict type is required for input_types when item is dict type + # dict type is required for input_types when item is dict type assert (isinstance(items, dict) and \ not isinstance(self.input_types, dict))==False yield items -- GitLab From ea25aab57592bcc4f3645b417c2ea000147f800d Mon Sep 17 00:00:00 2001 From: liaogang Date: Thu, 11 May 2017 14:26:48 +0800 Subject: [PATCH 0264/3256] update doc --- paddle/tensor/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paddle/tensor/README.md b/paddle/tensor/README.md index ea1fddb665f..71074e9b6ba 100644 --- a/paddle/tensor/README.md +++ b/paddle/tensor/README.md @@ -5,7 +5,7 @@ In this week, we discussed several potential weaknesses of PaddlePaddle caused by rapid iteration and development to promote new business products on the line in recent four years. For instance, current Matrix/Vector implementation in PaddlePaddle are long and tedious to read, which interfered seriously with the contribution of both fresh and professional engineers. More seriously for this issue, it will also become too challenging to maintain over time. -## Learn from Majel +## Learn from Majel Consequently, we decide to refactor PaddlePaddle step-by-step. First, refactor and replace Matrix/Vector to Tensor, a modern terminology in the deep learning system. Fortunately, we can learn from Majel how to define a Tensor. -- GitLab From 1ba60de83610c3e37062bb479c94f9b94f6aa883 Mon Sep 17 00:00:00 2001 From: liaogang Date: Thu, 11 May 2017 14:31:03 +0800 Subject: [PATCH 0265/3256] update docs --- paddle/tensor/README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/paddle/tensor/README.md b/paddle/tensor/README.md index 71074e9b6ba..b37ed72216e 100644 --- a/paddle/tensor/README.md +++ b/paddle/tensor/README.md @@ -101,18 +101,19 @@ To assign subtasks to our colleagues, we have to discuss how to divide it to ind - [ ] 1. First, we need to consider the third-party dependencies in Majel. -Majel heavily use `boost.variant`, but we don't want to integrate `boost` into PaddlePaddle. It's better to replace boost using the lightweight implementation. https://github.com/mapbox/variant Mapbox variant has the same speedy performance of `boost::variant `but is faster to compile, results in smaller binaries, and has no dependencies. + Majel heavily use `boost.variant`, but we don't want to integrate `boost` into PaddlePaddle. It's better to replace boost using the lightweight implementation. https://github.com/mapbox/variant Mapbox variant has the same speedy performance of `boost::variant `but is faster to compile, results in smaller binaries, and has no dependencies. > @gangliao - [ ] 2. Re-implement `Place` and `Allocation/Memory` -I found @wangkuiyi submitted a pull request includes `Place`. @gangliao and @qijun could re-implement `Allocation', because we have the GPU development experience before joining Paddle team. + I found @wangkuiyi submitted a pull request includes `Place`. @gangliao and @qijun could re-implement `Allocation`, because we have the GPU development experience before joining Paddle team. > @wangkuiyi @gangliao @qijun - [ ] 3. Re-implement `Dim`. -`Dim` is an excellent implementation in Majel. + + `Dim` is an excellent implementation in Majel. > ??? -- GitLab From 3c5e0f8a5db328205d29916a8fc9a6ced9a2e2ba Mon Sep 17 00:00:00 2001 From: dangqingqing Date: Thu, 11 May 2017 15:18:06 +0800 Subject: [PATCH 0266/3256] remove g_pass_height_width in config_parse.py --- python/paddle/trainer/config_parser.py | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/python/paddle/trainer/config_parser.py b/python/paddle/trainer/config_parser.py index 32e31fe2c44..57d30b088b8 100644 --- a/python/paddle/trainer/config_parser.py +++ b/python/paddle/trainer/config_parser.py @@ -138,14 +138,7 @@ def init_config_environment( g_root_submodel=None, g_submodel_map={}, g_submodel_stack=[], - g_add_submodel_suffix=False, - - # Whether current layer needs to pass the image height and width. - # Default value is true, but if it encounters recurrent_layer_group, - # it will be false. The reason is that image is converted to be sequence, - # image height will be sequence length, and image width will be feature - # length of each timestep. - g_pass_height_width=True, ): + g_add_submodel_suffix=False, ): for k, v in locals().iteritems(): globals()[k] = copy.deepcopy(v) @@ -1437,12 +1430,6 @@ class LayerBase(object): g_current_submodel.layer_names.append(self.config.name) - if self.config.type != 'data' and g_pass_height_width: - height = self.get_input_layer(0).height - width = self.get_input_layer(0).width - if height and width: - self.set_layer_height_width(height, width) - def get_input_layer(self, input_index): return g_layer_map[self.config.inputs[input_index].input_layer_name] @@ -3164,8 +3151,6 @@ class WarpCTCLayer(LayerBase): @config_layer('recurrent_layer_group') class RecurrentLayerGroup(LayerBase): def __init__(self, name, device=None): - global g_pass_height_width - g_pass_height_width = False super(RecurrentLayerGroup, self).__init__( name, 'recurrent_layer_group', 0, inputs=[], device=device) -- GitLab From 805088c615c32437cbdcd910582eb8e8bf174025 Mon Sep 17 00:00:00 2001 From: dangqingqing Date: Thu, 11 May 2017 16:54:07 +0800 Subject: [PATCH 0267/3256] pass unit test --- .../tests/configs/protostr/test_bilinear_interp.protostr | 2 -- .../tests/configs/protostr/test_maxout.protostr | 4 ---- 2 files changed, 6 deletions(-) diff --git a/python/paddle/trainer_config_helpers/tests/configs/protostr/test_bilinear_interp.protostr b/python/paddle/trainer_config_helpers/tests/configs/protostr/test_bilinear_interp.protostr index 9fae596f281..fd5224ca55c 100644 --- a/python/paddle/trainer_config_helpers/tests/configs/protostr/test_bilinear_interp.protostr +++ b/python/paddle/trainer_config_helpers/tests/configs/protostr/test_bilinear_interp.protostr @@ -90,8 +90,6 @@ layers { input_layer_name: "__pool_0__" input_parameter_name: "___fc_layer_0__.w0" } - height: 32 - width: 32 } parameters { name: "___conv_0__.w0" diff --git a/python/paddle/trainer_config_helpers/tests/configs/protostr/test_maxout.protostr b/python/paddle/trainer_config_helpers/tests/configs/protostr/test_maxout.protostr index c763a95f9d1..03f4f3a31d6 100644 --- a/python/paddle/trainer_config_helpers/tests/configs/protostr/test_maxout.protostr +++ b/python/paddle/trainer_config_helpers/tests/configs/protostr/test_maxout.protostr @@ -153,8 +153,6 @@ layers { img_size_y: 0 } } - height: 24 - width: 24 } layers { name: "__fc_layer_0__" @@ -165,8 +163,6 @@ layers { input_layer_name: "__block_expand_layer_0__" input_parameter_name: "___fc_layer_0__.w0" } - height: 24 - width: 24 } parameters { name: "___conv_0__.w0" -- GitLab From d7bde1ff2d46452b012698fb362e6cc187e23d57 Mon Sep 17 00:00:00 2001 From: gongweibao Date: Thu, 11 May 2017 17:50:57 +0800 Subject: [PATCH 0268/3256] first time to add --- doc/howto/dev/introduction_to_pr.md | 122 ++++++++++++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 doc/howto/dev/introduction_to_pr.md diff --git a/doc/howto/dev/introduction_to_pr.md b/doc/howto/dev/introduction_to_pr.md new file mode 100644 index 00000000000..39d8101c667 --- /dev/null +++ b/doc/howto/dev/introduction_to_pr.md @@ -0,0 +1,122 @@ +# Desgin Doc的一点总结 +## 前言 +故事还要从前两天我提交的[FileManager](https://github.com/PaddlePaddle/Paddle/pull/2013)的Design Doc PR开始。 + +我肯定是用心写了,我也清楚地记得,提交之前也仔细的检查了(这点自觉性还是有的),结果,我突然发现,我的PR被Comments刷屏了;这还是其次,更要命的是,当网络速度稍慢的时候会提示我:Comments 太多啦!页面打不开!!哦。简直有点人间惨剧的味道!我要把这些和之前的Comments以及一些典型的Comments总结一下,避免同样的问题,同时也希望对您有用。 + +我觉得里边有几个基本的原则: + +- 做事情要精益求精 + 这个是做事情的基础,也是我们讨论的基础。美,多是相似的,丑才是千奇百怪。 + + 精益求精是一种态度,态度比什么都重要。[yi](#yi) +- 节约别人的时间 + 同时也是给自己节约时间 + +- 有礼貌,有感恩的心 + > 我发现诸多之前提了comment,但是没有改,也没有回复。这个不太好吧。[ying](#ying) + > 每个comment都必须回复。这是开源社区的基本礼貌。别人帮了忙,应该说谢谢。[yi](#yi) + +我的理解,写Doc精益求精要从基础开始,首先要规范;其次要有提纲挈领的东西,让人一眼基本明白要做的事情;然后,讲述事情结构要清晰,要分层,用最小化涉及的原则,该讲的要解释,不该讲的不讲;接下来逻辑要清晰,要流畅,不要太突兀。。。当然还有很多,比如文笔要好!:) + +锻炼一下,中国人办事也不一定那么糙的,对吧! :) + +## 基础规范 + +- 语言选择:中文 or 英文? + 最好用英文,因为外国同学无法阅读中文。但是,语言是用来交流的,如果您认为英文无法完善的表达自己的意思的时候,还是用中文吧! + + 重要的是,不管哪种文,***英文的拼写要对,中文要没有错别字***! + + 如果文章用的是中文,那么请用中文的标点符号。反之,用英文的标点符号。***而且要保持统一、而且要保持完整***。[wuyi](#wuyi) + + 例如这个[Here](https://github.com/PaddlePaddle/Paddle/pull/2013#discussion_r114951817) + + 还有这个[Here](https://github.com/PaddlePaddle/Paddle/pull/2013#discussion_r115093563) + + (请原谅我多加了一个Here做锚文本,Sphinx有一个bug,用中文的会报错。下边的处理类似。) + +- 缩写的全称 + 一个缩写第一次出现的时候,要把他们的未缩写形式写出来。如果该单词代表的概念比较复杂,请在术语解释中把他写清楚! + + 例如这个[Here](https://github.com/PaddlePaddle/Paddle/pull/2013#discussion_r115093329) + +- 英语写法错误 + [yi](#yi)总结了一下在code review的时候经常见的一些英语写法错误: Python写成python,TensorFlow写成Tensorflow,Travis CI 写成 Travis-CI,ReLU写成Relu,unit test写成unittest,MD5写成md5。 + + 大小写简单的规则如下: + - 英文的缩写是要用大写的。 + 例如这个[Here](https://github.com/PaddlePaddle/Paddle/pull/2013#discussion_r115091985) + - 英文的句子的首字母是要大写的。 + - 一个专有的名词第一个字母一般是要大写的。 + + yiwang推荐了一个工具:[grammer](https://www.grammarly.com/),可以作为插件安装到Chrome中,自动检查拼写和语法问题。 + +- 不要提交没有用的文件 + 例如这个 [Here](https://github.com/PaddlePaddle/Paddle/pull/1964#discussion_r114414822) + +- 提交的代码要经过验证 + 提交的代码都不能通过编译是几个意思? + +- 参考资料要设置超链,链接到对方 + 不设置的话俗称Copy。可以用`[name](#name)`设置MarkDown文件中的引用。一如此文中很多地方出现的那样。 + +- 给别人的东西步骤是要可以执行的 + 看这个[Here](https://github.com/wangkuiyi/ipynb)是如何写步骤的,或者这个[Here](https://github.com/PaddlePaddle/Paddle/pull/1602#issuecomment-285964510)(虽然只是一个Comment)。 + +- 链接可以直接跳到精确的位置 + 如果能一步到位就不要让别人走两步,节约大家彼此的时间。 + +## 提纲挈领 +### 架构图 +一个系统或者模块的设计文档,首先要有架构图。***要有架构图,要有架构图,要有架构图***。。。。这句话可以多重复几遍。俗称,无图无真相或者一图胜千言。最起码有一个架构图说明各个模块的关系。图的表现能力远远超过了文字,特别是模块关系相关的和流程相关的场景下。 + +可以看一下这个文档中的图 [link](https://github.com/PaddlePaddle/Paddle/tree/develop/doc/design/cluster_train) + +顺便提一句,里边的图都是用[OmniGraffle](https://www.omnigroup.com/omnigraffle)画的,就是贵了点:$99。“99你买了不吃亏,99你买了不上当。”[wuyi](#wuyi) + +### 层次结构清晰 +代码层面上,我们为了开发大的项目,很自然的把代码分模块、分文件。文档也是如此。每个文档或者章节说明他应该要想说的事情,尽量的减少涉及的范围。涉及的范围越少,需要阐述、解释的东西就越少,理解起来需要的背景知识就越少,就越好理解,写的人出错的概率也越少。。。 + +- 整体分层 + - 系统设计 + - 模块设计 + - 接口设计 + - 部署 + +相互之间需要尽量少的“越权”。例如这个[Here](https://github.com/PaddlePaddle/Paddle/pull/2013#discussion_r115147388) + +另外一个容易忽视的问题是,文档内部的层次结构。MarkDown文件不想Doc一样可以设置目录页,一个部分如果太多,就会让看得人失去层次感。以这个文档为例 [link](https://github.com/PaddlePaddle/Paddle/tree/develop/doc/design/cluster_train)。这个文档我也写过,只是把`Fault Recovery`的部分和前边正常的`Training Job`合到一起去了,结果发现越写越乱,后来看到Helin写的分层之后的文档,感觉流畅多了。 + +## 逻辑要清晰、流畅 +- 概念的出现不要突兀。 + 文档的最前边有一个术语的解释,把文档中提到的概念先解释一下,这样,别人在看到那些概念的时候不会觉得很突兀。同时,前后要呼应。 + + 例如这个[Here](https://github.com/PaddlePaddle/Paddle/pull/2013#discussion_r114952115) + + 如果概念或者名词后边没有出现,该删除还是删除了吧! + +- 多种方案的选择要简述原因。 + 例如这个[Here](https://github.com/PaddlePaddle/Paddle/pull/2013#discussion_r115147115) + +- Design doc中不应该有“?” [wuyi](#wuyi) + 应该都是陈述句的描述。有不确定的问题可以提Issue来讨论获得结论。 + 对于自己不确定的地方,与其含混而过不如找人讨论先搞一个靠谱的可以讨论的。绝大多数情况下,这种含混而过的都会被Reivew给纠出来。即便就不出来,岂不是自己给自己埋一个坑? + +- 文档当中不要黏贴大量的代码 + 代码一般都是细节,改变的非常的快,文档可能很快就失效了,需要重新的修正。另外,最主要的是大段的代码会让人的思路中断,陷于实现的细节当中。 + +- 不准备实现的就不要写了 + 最多放到放到`Future`中展望一下。 + +## 文笔要好 +啊呀,不想当作家的程序员不是好程序员。这个当然比较难,要看大家的“学好数理化,走遍天下都不怕”的基本功的“深厚”程度了:) + +推荐一下公众号:老万故事会[link](https://freewechat.com/profile/MzI1MDQ3NTAxOQ==),一个文章和代码写的一样好的人[yi](#yi)。 + +## 参考资料 +- WangYi +- WuYi +- Helin +- YanXu +- CaoYing -- GitLab From f23ee80daa682833604bbc22874aa25c1d50bfdd Mon Sep 17 00:00:00 2001 From: gongweibao Date: Thu, 11 May 2017 17:59:44 +0800 Subject: [PATCH 0269/3256] Fix bugs --- doc/howto/dev/introduction_to_pr.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/howto/dev/introduction_to_pr.md b/doc/howto/dev/introduction_to_pr.md index 39d8101c667..663e81eeb98 100644 --- a/doc/howto/dev/introduction_to_pr.md +++ b/doc/howto/dev/introduction_to_pr.md @@ -2,7 +2,7 @@ ## 前言 故事还要从前两天我提交的[FileManager](https://github.com/PaddlePaddle/Paddle/pull/2013)的Design Doc PR开始。 -我肯定是用心写了,我也清楚地记得,提交之前也仔细的检查了(这点自觉性还是有的),结果,我突然发现,我的PR被Comments刷屏了;这还是其次,更要命的是,当网络速度稍慢的时候会提示我:Comments 太多啦!页面打不开!!哦。简直有点人间惨剧的味道!我要把这些和之前的Comments以及一些典型的Comments总结一下,避免同样的问题,同时也希望对您有用。 +这个文档我肯定是用心写了,我也清楚地记得,提交之前也仔细的检查了(这点自觉性还是有的),结果,我突然发现,我的PR被Comments刷屏了;这还是其次,更要命的是,当网络速度稍慢的时候会提示我:Comments 太多啦!页面打不开!!哦。简直有点人间惨剧的味道!我要把这些和之前的Comments以及一些典型的Comments总结一下,避免同样的问题,同时也希望对您有用。 我觉得里边有几个基本的原则: @@ -103,18 +103,18 @@ 应该都是陈述句的描述。有不确定的问题可以提Issue来讨论获得结论。 对于自己不确定的地方,与其含混而过不如找人讨论先搞一个靠谱的可以讨论的。绝大多数情况下,这种含混而过的都会被Reivew给纠出来。即便就不出来,岂不是自己给自己埋一个坑? -- 文档当中不要黏贴大量的代码 +- 文档当中不要黏贴大量的代码 代码一般都是细节,改变的非常的快,文档可能很快就失效了,需要重新的修正。另外,最主要的是大段的代码会让人的思路中断,陷于实现的细节当中。 -- 不准备实现的就不要写了 +- 不准备实现的就不要写了 最多放到放到`Future`中展望一下。 ## 文笔要好 啊呀,不想当作家的程序员不是好程序员。这个当然比较难,要看大家的“学好数理化,走遍天下都不怕”的基本功的“深厚”程度了:) -推荐一下公众号:老万故事会[link](https://freewechat.com/profile/MzI1MDQ3NTAxOQ==),一个文章和代码写的一样好的人[yi](#yi)。 +顺便推荐一下公众号:老万故事会[link](https://freewechat.com/profile/MzI1MDQ3NTAxOQ==),一个文章和代码写的一样好的人[yi](#yi)。 -## 参考资料 +## 参考 - WangYi - WuYi - Helin -- GitLab From e3a37a7913b7bd2a2f2612105e885334e74743b7 Mon Sep 17 00:00:00 2001 From: gongweibao Date: Thu, 11 May 2017 18:09:16 +0800 Subject: [PATCH 0270/3256] Fix bugs --- doc/howto/dev/introduction_to_pr.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/howto/dev/introduction_to_pr.md b/doc/howto/dev/introduction_to_pr.md index 663e81eeb98..bb9ff53322a 100644 --- a/doc/howto/dev/introduction_to_pr.md +++ b/doc/howto/dev/introduction_to_pr.md @@ -56,7 +56,7 @@ 例如这个 [Here](https://github.com/PaddlePaddle/Paddle/pull/1964#discussion_r114414822) - 提交的代码要经过验证 - 提交的代码都不能通过编译是几个意思? + 提交的代码都没有验证过是几个意思? - 参考资料要设置超链,链接到对方 不设置的话俗称Copy。可以用`[name](#name)`设置MarkDown文件中的引用。一如此文中很多地方出现的那样。 -- GitLab From 02990b824d64ede5d038019f78f1dc34503e1f45 Mon Sep 17 00:00:00 2001 From: Yi Wang Date: Wed, 10 May 2017 11:15:49 -0700 Subject: [PATCH 0271/3256] Initial trial to port Majel to Paddle --- Dockerfile | 11 +++++++- majel/Makefile | 10 ++++++++ majel/place.cu | 61 +++++++++++++++++++++++++++++++++++++++++++++ majel/place.h | 50 +++++++++++++++++++++++++++++++++++++ majel/place_test.cu | 40 +++++++++++++++++++++++++++++ 5 files changed, 171 insertions(+), 1 deletion(-) create mode 100644 majel/Makefile create mode 100644 majel/place.cu create mode 100644 majel/place.h create mode 100644 majel/place_test.cu diff --git a/Dockerfile b/Dockerfile index 0d2f5691144..dc443887323 100644 --- a/Dockerfile +++ b/Dockerfile @@ -29,7 +29,7 @@ RUN apt-get update && \ curl sed grep graphviz libjpeg-dev zlib1g-dev \ python-numpy python-matplotlib gcc g++ \ automake locales clang-format-3.8 swig doxygen cmake \ - liblapack-dev liblapacke-dev \ + liblapack-dev liblapacke-dev libboost-dev \ clang-3.8 llvm-3.8 libclang-3.8-dev && \ apt-get clean -y @@ -61,6 +61,15 @@ RUN git clone https://github.com/woboq/woboq_codebrowser /woboq && \ -DCMAKE_BUILD_TYPE=Release . \ make) +# Install gtest. +# +# NOTE: This is added for quick hack of the development work of +# majel-in-paddle. +RUN git clone https://github.com/google/googletest /gtest && \ + cd /gtest && \ + git checkout -b release-1.8.0 && \ + cmake . && make install + # Configure OpenSSH server. c.f. https://docs.docker.com/engine/examples/running_ssh_service RUN mkdir /var/run/sshd RUN echo 'root:root' | chpasswd diff --git a/majel/Makefile b/majel/Makefile new file mode 100644 index 00000000000..403c1d3a6ae --- /dev/null +++ b/majel/Makefile @@ -0,0 +1,10 @@ +CCFLAGS = -std=c++11 -I/work/paddle +CC = nvcc + +all : place_test + +place.o : place.h place.cu + $(CC) $(CCFLAGS) -c place.cu -o $@ + +place_test : place.o place_test.cu + $(CC) $(CCFLAGS) -lgtest -lgtest_main $^ -o $@ diff --git a/majel/place.cu b/majel/place.cu new file mode 100644 index 00000000000..ee84e96048d --- /dev/null +++ b/majel/place.cu @@ -0,0 +1,61 @@ +#include + +namespace majel { + +namespace detail { + +class PlacePrinter + : public boost::static_visitor<> { +private: + std::ostream& os_; +public: + PlacePrinter(std::ostream& os) : os_(os) {} + + void operator()(const CpuPlace&) { + os_ << "CpuPlace"; + } + + void operator()(const GpuPlace& p) { + os_ << "GpuPlace(" << p.device << ")"; + } +}; + +} // namespace majel + +static Place the_default_place; + +void set_place(const Place& place) { + the_default_place = place; +} + +const Place& get_place() { + return the_default_place; +} + +const GpuPlace default_gpu() { + return GpuPlace(0); +} + +const CpuPlace default_cpu() { + return CpuPlace(); +} + +bool is_gpu_place(const Place& p) { + return boost::apply_visitor(IsGpuPlace(), p); +} + +bool is_cpu_place(const Place& p) { + return !boost::apply_visitor(IsGpuPlace(), p); +} + +bool places_are_same_class(const Place& p1, const Place& p2) { + return is_gpu_place(p1) == is_gpu_place(p2); +} + +std::ostream& operator<<(std::ostream& os, const majel::Place& p) { + majel::detail::PlacePrinter printer(os); + boost::apply_visitor(printer, p); + return os; +} + +} // namespace majel diff --git a/majel/place.h b/majel/place.h new file mode 100644 index 00000000000..ad3dc3fe0b8 --- /dev/null +++ b/majel/place.h @@ -0,0 +1,50 @@ +#pragma once +#include +#include + +namespace majel { + +struct CpuPlace { + CpuPlace() {} // WORKAROUND: for some reason, omitting this constructor + // causes errors with boost 1.59 and OSX + // needed for variant equality comparison + inline bool operator==(const CpuPlace&) const { return true; } + + inline bool operator!=(const CpuPlace&) const { return false; } +}; + +struct GpuPlace { + GpuPlace(int d) : device(d) {} + + // needed for variant equality comparison + inline bool operator==(const GpuPlace& o) const { return device == o.device; } + + inline bool operator!=(const GpuPlace& o) const { return !(*this == o); } + + GpuPlace() : GpuPlace(0) {} + int device; +}; + +class IsGpuPlace : public boost::static_visitor { +public: + bool operator()(const CpuPlace&) const { return false; } + + bool operator()(const GpuPlace& gpu) const { return true; } +}; + +typedef boost::variant Place; + +void set_place(const Place&); + +const Place& get_place(); + +const GpuPlace default_gpu(); +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&); + +std::ostream& operator<<(std::ostream&, const majel::Place&); + +} // namespace majel diff --git a/majel/place_test.cu b/majel/place_test.cu new file mode 100644 index 00000000000..cb829461303 --- /dev/null +++ b/majel/place_test.cu @@ -0,0 +1,40 @@ +#include "gtest/gtest.h" +#include "majel/place.h" +#include + +TEST(Place, Equality) { + majel::CpuPlace cpu; + majel::GpuPlace g0(0), g1(1), gg0(0); + + EXPECT_EQ(cpu, cpu); + EXPECT_EQ(g0, g0); + EXPECT_EQ(g1, g1); + EXPECT_EQ(g0, gg0); + + EXPECT_NE(g0, g1); + + EXPECT_TRUE(majel::places_are_same_class(g0, gg0)); + EXPECT_FALSE(majel::places_are_same_class(g0, cpu)); +} + +TEST(Place, Default) { + EXPECT_TRUE(majel::is_gpu_place( majel::get_place())); + EXPECT_TRUE(majel::is_gpu_place( majel::default_gpu())); + EXPECT_TRUE(majel::is_cpu_place( majel::default_cpu())); + + majel::set_place(majel::CpuPlace()); + EXPECT_TRUE(majel::is_cpu_place( majel::get_place())); +} + +TEST(Place, Print) { + { + std::stringstream ss; + ss << majel::GpuPlace(1); + EXPECT_EQ("GpuPlace(1)", ss.str()); + } + { + std::stringstream ss; + ss << majel::CpuPlace(); + EXPECT_EQ("CpuPlace", ss.str()); + } +} -- GitLab From 97955f063e39c3b580a70ed42c2baa77a0efbeeb Mon Sep 17 00:00:00 2001 From: liaogang Date: Fri, 12 May 2017 00:23:54 +0800 Subject: [PATCH 0272/3256] rename majel --- paddle/{tensor => majel}/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename paddle/{tensor => majel}/README.md (99%) diff --git a/paddle/tensor/README.md b/paddle/majel/README.md similarity index 99% rename from paddle/tensor/README.md rename to paddle/majel/README.md index b37ed72216e..55398530567 100644 --- a/paddle/tensor/README.md +++ b/paddle/majel/README.md @@ -9,7 +9,7 @@ In this week, we discussed several potential weaknesses of PaddlePaddle caused b Consequently, we decide to refactor PaddlePaddle step-by-step. First, refactor and replace Matrix/Vector to Tensor, a modern terminology in the deep learning system. Fortunately, we can learn from Majel how to define a Tensor. -To simplify heterogeneous resource allocation in any dimensions (1-9) and types (double, float, float16), Majel consists of several primitives such as `Dim`, `Place` and `Array`, all of them are standard C++ classes. +To simplify heterogeneous resource allocation in any dimensions (1-9) and types (double, float, float16), Majel consists of several primitives such as `Dim`, `Place` and `Array`, all of them are standard C++ class templates. 1. `Place`: memory location [i.e. CPU/GPU]. 2. `Allocation`: heterogeneous resource allocator [i.e. 20MB in GPU]. -- GitLab From 00d4d89a28bece66daea64989bed2990d5838df5 Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Thu, 11 May 2017 17:37:02 -0700 Subject: [PATCH 0273/3256] add more sections --- doc/design/cluster_train/pserver_client.md | 59 +++++++++++++++------- 1 file changed, 42 insertions(+), 17 deletions(-) diff --git a/doc/design/cluster_train/pserver_client.md b/doc/design/cluster_train/pserver_client.md index ee40eb32c55..e1fa4526396 100644 --- a/doc/design/cluster_train/pserver_client.md +++ b/doc/design/cluster_train/pserver_client.md @@ -2,6 +2,39 @@ For an overview of trainer's role, please refer to [distributed training design doc](README.md). In this design doc, we will discuss the parameter server's client library, which will manage communication with parameter servers. The library will be implemented in [Go](https://golang.org/) and made available as a static or dynamic library with a C header file. +## Parameter Partition + +Each parameter will be partitioned into parameter chunks to make the parameters evenly distributed on parameter servers. The partition is done automatically by the client library. The *sparse parameter* require a little different treatment: + +### Sparse Parameter + +The sparse parameter is a parameter that is updated sparsely. The name is somewhat misleading, it does not have a sparse representation, it is conceptually a dense vector. + +Because a sparse parameter is updated sparsely, the trainer will have to partition the sparse parameter. Because the parameter server will merge all sparse parameter shard into the same file when saving the parameter. It needs special naming convention: + +If a sparse parameter is partitioned into n shards, they should be named as: + +```text +name:sparse-0 +name:sparse-1 +... +name:sparse-n-1 +``` + +## Gradient Optimization + +There are two ways to perform model optimization according to gradients: + +- On Client + The client does forward and backward update multiple steps. In each step, the gradients are calculated each step and a new model is generated. After some steps, the client will calculate the difference between the newest model and the old model at step 0. The difference will be updated to parameter servers. Parameter servers will just update parameters according to the difference without any optimization using gradients (such as Adam and L1 regularization). + +- On Parameter Server + The client will send gradients to parameter servers, the parameter server will do the optimization using gradients. + +## L1 and L2 Regularization + +PaddlePaddle allows L1 or L2 regularizations to be specified per parameter, so when the trainer initializes the parameter. When the parameter server is doing the optimization, the trainer needs to pass a parameter configuration to parameter servers to indicate the Regularization. + ## Parameter Initialization The parameters on parameter servers need to be initialized. To provide maximum flexibility, we need to allow trainer initialized the parameters. Only one trainer will do the initialization, the other trainers will wait for the completion of initialization and get the parameters from the parameter servers. @@ -54,24 +87,25 @@ void paddle_pserver_client_release(paddle_pserver_client* client); * initialization is done, and they need to get the initialized * parameters from parameter servers using @paddle_get_params. * - * @param config_proto serialized parameter server configuration in + * @param pserver_config_proto serialized parameter server configuration in * Protocol Buffers format. * @return 1 if the trainer is selected to initialize parameter * servers, otherwise 0. */ -int paddle_begin_init_params(paddle_pserver_client* client, const char* config_proto); +int paddle_begin_init_params(paddle_pserver_client* client, const char* pserver_config_proto); /** * @brief paddle_init_param initializes the parameter on parameter * servers. * * @param param the parameter to initialize. + * @param param_config_proto the configuration for the parameter. * @return 0 if successful, otherwise -1. On failure, the trainer * needs to restart the entire initialization process (starting from * @paddle_begin_init_param). Or simply exit the program and wait for * the cluster management system to restart the trainer. */ -int paddle_init_param(paddle_pserver_client* client, paddle_parameter params); +int paddle_init_param(paddle_pserver_client* client, paddle_parameter params, const char* param_config_proto); /** * @brief paddle_finish_init_params tells parameter servers client has @@ -89,31 +123,22 @@ int paddle_finish_init_params(paddle_pserver_client* client); * updating parameters. * * @param grads the array of gradients to send. - * @param total the total number of gradient inside the gradient array. + * @param len the length of the gradient array. * @param learning_rate the learning rate for the gradients. * @return 0 if successful, otherwise -1. */ -int paddle_send_grads(paddle_pserver_client* client, const paddle_gradient* grads, int total, double learning_rate); - -/** - * @brief paddle_set_params sets parameters to parameter servers. - * - * @param params the array of parameters to set to parameter servers. - * @param total the total number of parameters inside the parameter - * array. - * @return 0 if successful, otherwise -1. - */ -int paddle_set_params(paddle_pserver_client* client, const paddle_parameter* params, int total); +int paddle_send_grads(paddle_pserver_client* client, const paddle_gradient* grads, int len); /** * @brief paddle_get_params gets parameters from parameter servers. * * @param names the array of names of the parameters to get. * @param dst the destination array of parameters to save to. - * @param total the total number of parameters to get. + * @param len the length of the names array and the paddle_parameter + * array. * @return 0 if successful, otherwise -1. */ -int paddle_get_params(paddle_pserver_client* client, const char** names, paddle_parameter* dst, int total); +int paddle_get_params(paddle_pserver_client* client, const char** names, paddle_parameter* dst, int len); /** * @brief paddle_save_model indicates parameters to save the parameter -- GitLab From 3da6c853400ab19557108b865f493f8ec73fb538 Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Thu, 11 May 2017 17:40:51 -0700 Subject: [PATCH 0274/3256] use enum instead of define --- doc/design/cluster_train/pserver_client.md | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/doc/design/cluster_train/pserver_client.md b/doc/design/cluster_train/pserver_client.md index e1fa4526396..0a45a861177 100644 --- a/doc/design/cluster_train/pserver_client.md +++ b/doc/design/cluster_train/pserver_client.md @@ -58,18 +58,20 @@ The selected trainer's call to `paddle_begin_init_params` will return with 1, an ## C Interface ```c -#define PADDLE_ELEMENT_TYPE_INT32 0 -#define PADDLE_ELEMENT_TYPE_UINT32 1 -#define PADDLE_ELEMENT_TYPE_INT64 2 -#define PADDLE_ELEMENT_TYPE_UINT64 3 -#define PADDLE_ELEMENT_TYPE_FLOAT32 4 -#define PADDLE_ELEMENT_TYPE_FLOAT64 5 +typedef enum { + PADDLE_ELEMENT_TYPE_INT32 = 0, + PADDLE_ELEMENT_TYPE_UINT32 = 1, + PADDLE_ELEMENT_TYPE_INT64 = 2, + PADDLE_ELEMENT_TYPE_UINT64 = 3, + PADDLE_ELEMENT_TYPE_FLOAT32 = 4, + PADDLE_ELEMENT_TYPE_FLOAT64 = 5, +} paddle_element_type; typedef struct { - char* name; - int element_type; - void* content; - int content_len; + char* name; + paddle_element_type element_type; + void* content; + int content_len; } paddle_parameter, paddle_gradient; typedef struct paddle_pserver_client paddle_pserver_client; -- GitLab From 747994d00fbc9ab202bd362438b9d3f7c8976217 Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Thu, 11 May 2017 17:54:41 -0700 Subject: [PATCH 0275/3256] polish wording --- doc/design/cluster_train/pserver_client.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/doc/design/cluster_train/pserver_client.md b/doc/design/cluster_train/pserver_client.md index 0a45a861177..0531630fb8f 100644 --- a/doc/design/cluster_train/pserver_client.md +++ b/doc/design/cluster_train/pserver_client.md @@ -8,7 +8,7 @@ Each parameter will be partitioned into parameter chunks to make the parameters ### Sparse Parameter -The sparse parameter is a parameter that is updated sparsely. The name is somewhat misleading, it does not have a sparse representation, it is conceptually a dense vector. +The sparse parameter is a parameter that is updated sparsely. The name is somewhat misleading, it does not have a sparse representation, it has the same representation as a dense vector. Because a sparse parameter is updated sparsely, the trainer will have to partition the sparse parameter. Because the parameter server will merge all sparse parameter shard into the same file when saving the parameter. It needs special naming convention: @@ -21,14 +21,18 @@ name:sparse-1 name:sparse-n-1 ``` -## Gradient Optimization +The library is unaware of the partition, and treat each parameter independently. Only when saving parameters, the parameter servers will merge the sparse parameters according to the naming convention. -There are two ways to perform model optimization according to gradients: +## Model Optimization Using Gradient + +There are two ways to perform model optimization using gradients: - On Client - The client does forward and backward update multiple steps. In each step, the gradients are calculated each step and a new model is generated. After some steps, the client will calculate the difference between the newest model and the old model at step 0. The difference will be updated to parameter servers. Parameter servers will just update parameters according to the difference without any optimization using gradients (such as Adam and L1 regularization). + + The client does forward and backward update multiple steps. In each step, the gradients are calculated each step and a new model is generated. After some steps, the client will calculate the difference between the newest model and the old model at step 0. The difference will be updated to parameter servers. Parameter servers will just update parameters using the difference without any optimization using gradients (such as Adam and L1 regularization). - On Parameter Server + The client will send gradients to parameter servers, the parameter server will do the optimization using gradients. ## L1 and L2 Regularization -- GitLab From 7d7473842f2ad84bf00988b161ad25992b17b8c4 Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Thu, 11 May 2017 18:00:55 -0700 Subject: [PATCH 0276/3256] polish wording --- doc/design/cluster_train/pserver_client.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/doc/design/cluster_train/pserver_client.md b/doc/design/cluster_train/pserver_client.md index 0531630fb8f..500894fac76 100644 --- a/doc/design/cluster_train/pserver_client.md +++ b/doc/design/cluster_train/pserver_client.md @@ -23,25 +23,25 @@ name:sparse-n-1 The library is unaware of the partition, and treat each parameter independently. Only when saving parameters, the parameter servers will merge the sparse parameters according to the naming convention. -## Model Optimization Using Gradient +## Model Optimization Using Gradients There are two ways to perform model optimization using gradients: - On Client - The client does forward and backward update multiple steps. In each step, the gradients are calculated each step and a new model is generated. After some steps, the client will calculate the difference between the newest model and the old model at step 0. The difference will be updated to parameter servers. Parameter servers will just update parameters using the difference without any optimization using gradients (such as Adam and L1 regularization). + The client does multiple steps of forward and backward update. In each step, the gradients are calculated and a new model is generated. After some steps, the client will calculate the difference between the newest model and the old model at step 0. The difference will be updated to parameter servers. Parameter servers will just update parameters using the difference without any optimization using gradients (such as Adam and L1 regularization). - On Parameter Server - The client will send gradients to parameter servers, the parameter server will do the optimization using gradients. + The client will send accumulated gradients to parameter servers, the parameter server will do the optimization using gradients. ## L1 and L2 Regularization -PaddlePaddle allows L1 or L2 regularizations to be specified per parameter, so when the trainer initializes the parameter. When the parameter server is doing the optimization, the trainer needs to pass a parameter configuration to parameter servers to indicate the Regularization. +PaddlePaddle allows L1 or L2 regularizations to be specified per parameter, so when the trainer initializes the parameter it needs include a parameter configuration when L1 or L2 regularization is necessary. ## Parameter Initialization -The parameters on parameter servers need to be initialized. To provide maximum flexibility, we need to allow trainer initialized the parameters. Only one trainer will do the initialization, the other trainers will wait for the completion of initialization and get the parameters from the parameter servers. +The parameters on parameter servers need to be initialized. To provide maximum flexibility, the trainer will initialize the parameters. Only one trainer will do the initialization, the other trainers will wait for the completion of initialization and get the parameters from the parameter servers. ### Trainer Selection @@ -49,9 +49,9 @@ To select the trainer for initialization, every trainer will try to get a distri -### Selection Process +### Trainer Selection Process -The select process is encapsulated in the C API function: +The trainer select process is encapsulated in the C API function: ```c int paddle_begin_init_params(paddle_pserver_client* client, const char* config_proto); ``` -- GitLab From c0794374d27267bf1af60980f311aeea1acfbf45 Mon Sep 17 00:00:00 2001 From: gongweibao Date: Fri, 12 May 2017 09:32:11 +0800 Subject: [PATCH 0277/3256] fix bugs and add sections --- doc/howto/dev/introduction_to_pr.md | 43 ++++++++++++++++++++--------- 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/doc/howto/dev/introduction_to_pr.md b/doc/howto/dev/introduction_to_pr.md index bb9ff53322a..71cbbdf8611 100644 --- a/doc/howto/dev/introduction_to_pr.md +++ b/doc/howto/dev/introduction_to_pr.md @@ -2,8 +2,8 @@ ## 前言 故事还要从前两天我提交的[FileManager](https://github.com/PaddlePaddle/Paddle/pull/2013)的Design Doc PR开始。 -这个文档我肯定是用心写了,我也清楚地记得,提交之前也仔细的检查了(这点自觉性还是有的),结果,我突然发现,我的PR被Comments刷屏了;这还是其次,更要命的是,当网络速度稍慢的时候会提示我:Comments 太多啦!页面打不开!!哦。简直有点人间惨剧的味道!我要把这些和之前的Comments以及一些典型的Comments总结一下,避免同样的问题,同时也希望对您有用。 - +[FileManager](https://github.com/PaddlePaddle/Paddle/pull/2013)这个文档我肯定是用心写了,我也清楚地记得,提交之前也仔细的检查了(这点自觉性还是有的),结果,我突然发现,我的PR被Comments刷屏了;这还是其次,更要命的是,当网络速度稍慢的时候会提示我:Comments 太多啦!页面打不开!!哦。简直有点人间惨剧的味道!我要把这些和之前的Comments以及一些典型的Comments总结一下,避免同样的问题,同时也希望对您有用。 +link 我觉得里边有几个基本的原则: - 做事情要精益求精 @@ -30,30 +30,30 @@ 如果文章用的是中文,那么请用中文的标点符号。反之,用英文的标点符号。***而且要保持统一、而且要保持完整***。[wuyi](#wuyi) - 例如这个[Here](https://github.com/PaddlePaddle/Paddle/pull/2013#discussion_r114951817) + 例如这个[link](https://github.com/PaddlePaddle/Paddle/pull/2013#discussion_r114951817) - 还有这个[Here](https://github.com/PaddlePaddle/Paddle/pull/2013#discussion_r115093563) + 还有这个[link](https://github.com/PaddlePaddle/Paddle/pull/2013#discussion_r115093563) - (请原谅我多加了一个Here做锚文本,Sphinx有一个bug,用中文的会报错。下边的处理类似。) + (请原谅我多加了一个link做锚文本,Sphinx有一个bug:用中文的会报错。下边的处理类似。) - 缩写的全称 一个缩写第一次出现的时候,要把他们的未缩写形式写出来。如果该单词代表的概念比较复杂,请在术语解释中把他写清楚! - 例如这个[Here](https://github.com/PaddlePaddle/Paddle/pull/2013#discussion_r115093329) + 例如这个[link](https://github.com/PaddlePaddle/Paddle/pull/2013#discussion_r115093329) - 英语写法错误 [yi](#yi)总结了一下在code review的时候经常见的一些英语写法错误: Python写成python,TensorFlow写成Tensorflow,Travis CI 写成 Travis-CI,ReLU写成Relu,unit test写成unittest,MD5写成md5。 大小写简单的规则如下: - 英文的缩写是要用大写的。 - 例如这个[Here](https://github.com/PaddlePaddle/Paddle/pull/2013#discussion_r115091985) + 例如这个[link](https://github.com/PaddlePaddle/Paddle/pull/2013#discussion_r115091985) - 英文的句子的首字母是要大写的。 - 一个专有的名词第一个字母一般是要大写的。 yiwang推荐了一个工具:[grammer](https://www.grammarly.com/),可以作为插件安装到Chrome中,自动检查拼写和语法问题。 - 不要提交没有用的文件 - 例如这个 [Here](https://github.com/PaddlePaddle/Paddle/pull/1964#discussion_r114414822) + 例如这个 [link](https://github.com/PaddlePaddle/Paddle/pull/1964#discussion_r114414822) - 提交的代码要经过验证 提交的代码都没有验证过是几个意思? @@ -62,12 +62,16 @@ 不设置的话俗称Copy。可以用`[name](#name)`设置MarkDown文件中的引用。一如此文中很多地方出现的那样。 - 给别人的东西步骤是要可以执行的 - 看这个[Here](https://github.com/wangkuiyi/ipynb)是如何写步骤的,或者这个[Here](https://github.com/PaddlePaddle/Paddle/pull/1602#issuecomment-285964510)(虽然只是一个Comment)。 + 看这个[link](https://github.com/wangkuiyi/ipynb)是如何写步骤的,或者这个[link](https://github.com/PaddlePaddle/Paddle/pull/1602#issuecomment-285964510)(虽然只是一个Comment)。 - 链接可以直接跳到精确的位置 如果能一步到位就不要让别人走两步,节约大家彼此的时间。 ## 提纲挈领 +### 目标要明确 +一般开头的一段话就要用简短的几句话把文档要描述的目标写清楚,别人看了之后,心中有了一个大概:这个文档准备讲什么。 + +例如这个[link](https://github.com/PaddlePaddle/Paddle/tree/develop/doc/design/cluster_train#objective) ### 架构图 一个系统或者模块的设计文档,首先要有架构图。***要有架构图,要有架构图,要有架构图***。。。。这句话可以多重复几遍。俗称,无图无真相或者一图胜千言。最起码有一个架构图说明各个模块的关系。图的表现能力远远超过了文字,特别是模块关系相关的和流程相关的场景下。 @@ -84,20 +88,20 @@ - 接口设计 - 部署 -相互之间需要尽量少的“越权”。例如这个[Here](https://github.com/PaddlePaddle/Paddle/pull/2013#discussion_r115147388) +相互之间需要尽量少的“越权”。例如这个[link](https://github.com/PaddlePaddle/Paddle/pull/2013#discussion_r115147388) -另外一个容易忽视的问题是,文档内部的层次结构。MarkDown文件不想Doc一样可以设置目录页,一个部分如果太多,就会让看得人失去层次感。以这个文档为例 [link](https://github.com/PaddlePaddle/Paddle/tree/develop/doc/design/cluster_train)。这个文档我也写过,只是把`Fault Recovery`的部分和前边正常的`Training Job`合到一起去了,结果发现越写越乱,后来看到Helin写的分层之后的文档,感觉流畅多了。 +另外一个容易忽视的问题是,文档内部的层次结构。MarkDown文件不像Doc一样可以自动生成目录页,一个部分如果太多,就会让看得人失去层次感。以这个文档为例 [link](https://github.com/PaddlePaddle/Paddle/tree/develop/doc/design/cluster_train)。这个文档我也写过,只是把`Fault Recovery`的部分和前边正常的`Training Job`合到一起去了,结果发现越写越乱,后来看到Helin写的分层之后的文档,感觉流畅多了。 ## 逻辑要清晰、流畅 - 概念的出现不要突兀。 文档的最前边有一个术语的解释,把文档中提到的概念先解释一下,这样,别人在看到那些概念的时候不会觉得很突兀。同时,前后要呼应。 - 例如这个[Here](https://github.com/PaddlePaddle/Paddle/pull/2013#discussion_r114952115) + 例如这个[link](https://github.com/PaddlePaddle/Paddle/pull/2013#discussion_r114952115) 如果概念或者名词后边没有出现,该删除还是删除了吧! - 多种方案的选择要简述原因。 - 例如这个[Here](https://github.com/PaddlePaddle/Paddle/pull/2013#discussion_r115147115) + 例如这个[link](https://github.com/PaddlePaddle/Paddle/pull/2013#discussion_r115147115) - Design doc中不应该有“?” [wuyi](#wuyi) 应该都是陈述句的描述。有不确定的问题可以提Issue来讨论获得结论。 @@ -114,6 +118,19 @@ 顺便推荐一下公众号:老万故事会[link](https://freewechat.com/profile/MzI1MDQ3NTAxOQ==),一个文章和代码写的一样好的人[yi](#yi)。 +## 如何提高写文档的效率 +这段其实本来没想到加的,开完组会之后听老大讲提高工作效率的事情有点感想,就写了。 + +我不是很怀疑自己写代码的效率,但是严重怀疑自己写文档的效率。感觉写文档比写代码烧脑多了。现在想想,最主要的点在于文档的结构和分层问题。 + +提高效率最好的办法是什么?是确定范围,很多东西不用讲了,当然效率就提高上去了。 + +写系统设计文档,主要需要表现模块间的关系和通信,特别是特定场景下的他们是如何配合的。这个过程中需要把关键的概念说清楚,例如这个[link](https://github.com/PaddlePaddle/Paddle/tree/develop/doc/design/cluster_train)中,`Trainjob`和`Fault Recovery`主要是讲模块间关系和配合的,[`task`](https://github.com/PaddlePaddle/Paddle/tree/develop/doc/design/cluster_train#task)作为一个关键的概念讲了。其他的部分,稍显细节的都可以放到模块设计中去讲。 + +另外一个,认真≠ 犹豫,也≠ 纠结。 + +如果感到了纠结,那说明没找到问题的根本。我在写文件上传的时候对做不做缓存优化纠结了很长时间,请教了Helin一会就讨论完毕了。如果感到了纠结,那是需要跟别人请教。不纠结的地方,下决断要果断。 + ## 参考 - WangYi - WuYi -- GitLab From b7e4b2ddc5db6c3fcea711748a62dd32cc7a17c7 Mon Sep 17 00:00:00 2001 From: Yancey1989 Date: Fri, 12 May 2017 13:42:39 +0800 Subject: [PATCH 0278/3256] submit job design doc --- .../cluster_train/src/submit-job.graffle | Bin 0 -> 3931 bytes doc/design/cluster_train/src/submit-job.png | Bin 0 -> 52772 bytes doc/design/cluster_train/submit-job.md | 127 ++++++++++++++++++ 3 files changed, 127 insertions(+) create mode 100644 doc/design/cluster_train/src/submit-job.graffle create mode 100644 doc/design/cluster_train/src/submit-job.png create mode 100644 doc/design/cluster_train/submit-job.md diff --git a/doc/design/cluster_train/src/submit-job.graffle b/doc/design/cluster_train/src/submit-job.graffle new file mode 100644 index 0000000000000000000000000000000000000000..677cdfb6d9a32168bf71729eb841fa1ca0dd31d6 GIT binary patch literal 3931 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 + +- `paddle.job.dist_train()` will call the Job Server API `/v1/packages` to upload the trainer package and save them on CephFS, and then call `/v1/trainer/job` to submit the PaddlePaddle distributed job. +- `/v1/trainer/job` will start a building job for preparing the runtime Docker image. When the building job is finished, Job Server will submit the PaddlePaddle distributed job to Kubernetes. +- *NOTE*: For the first version, we will not prepare the runtime Docker image, instead, the package is uploaded to Paddle Cloud, and Paddle Cloud will mount the package in a temporary folder into the base Docker image. We will not support custom Python dependencies in the first version as well. + +You can call `paddle.job.dist_train` and provide distributed training configuration as the parameters: +```python +paddle.job.dist_train( + trainer=dist_trainer(), + paddle_job=PaddleJob( + job_name = "paddle-cloud", + entry_point = "python %s"%__file__, + trainer_package = "/example/word2vec", + image = "yancey1989/paddle-job", + trainers = 10, + pservers = 3, + trainer_cpu = 1, + trainer_gpu = 1, + trainer_mem = "10G", + pserver_cpu = 1, + pserver_mem = "2G" + )) +``` + +The parameter `trainer` of `paddle.job.dist_train` is a function and you can implement it as follows: +```python +def dist_trainer(): + def trainer_creator(): + trainer = paddle.v2.trainer.SGD(...) + trainer.train(...) + return trainer_creator +``` + +The pseudo code of `paddle.job.dist_train` is as follows: +```python +def dist_train(trainer, paddle_job): + # if the code is running on cloud, set PADDLE_ON_CLOUD=YES + if os.getenv("RUNNING_ON_CLOUD", "NO") == "NO": + #submit the paddle job + paddle_job.submit() + else: + #start the training + trainer() +``` +### PaddleJob Parameters +parameter | type | explanation + --- | --- | --- +job_name | str | the unique name for the training job +entry_point | str | entry point for startup trainer process +trainer_package | str | trainer package file path which user have the access right +image|str|the [base image](#base-docker-image) for building the [runtime image](#runtime-docker-image) +pservers|int| Parameter Server process count +trainers|int| Trainer process count +pserver_cpu|int| CPU count for each Parameter Server process +pserver_mem|str| memory allocated for each Parameter Server process, a plain integer using one of these suffixes: E, P, T, G, M, K +trainer_cpu|int| CPU count for each Trainer process +trainer_mem|str| memory allocated for each Trainer process, a plain integer using one of these suffixes: E, P, T, G, M, K +trainer_gpu|int| GPU count for each Trainer process, if you only want CPU, do not set this parameter + +### Deploy Parameter Server, Trainer and Master Process + - Deploy PaddlePaddle Parameter Server processes, it's a Kubernetes ReplicaSet. + - Deploy PaddlePaddle Trainer processes, it's a Kubernetes Job. + - Deploy PaddlePaddle Master processes, it's a Kubernetes ReplicaSet. + +## Job Server + +- RESTful API + + Job server provides RESTful HTTP API for receiving the trainer package and displaying + PaddlePaddle job related informations. + - `POST /v1/package` receive the trainer package and save them on CephFS + - `POST /v1/trainer/job` submit a trainer job + - `GET /v1/jobs/` list all jobs + - `GET /v1/jobs/` the status of a job + - `DELETE /v1/jobs/` delete a job + - `GET /v1/version` job server version + +- Build Runtime Docker Image on Kubernetes + + `paddle.job.dist_train` will upload the trainer package to Job Server, save them on the distributed filesystem, and then start up a job for building the runtime Docker image that gets scheduled by Kubernetes to run during training. + + There are some benefits for building runtime Docker image on JobServer: + - On Paddle Cloud, users will run the trainer code in a Jupyter Notebook which is a Kubernetes Pod, if we want to execute `docker build` in the Pod, we should mount the host's `docker.sock` to the Pod, user's code will connect the host's Docker Engine directly, it's not safe. + - Users only need to upload the training package files, does not need to install docker engine, docker registry as dependencies. + - If we want to change another image type, such as RKT, users do not need to care about it. + +- Deploy Parameter Server, Trainer and Master Processes + + `POST /v1/trainer/job` receives the distributed training parameters, and deploy the job as follows: + - Deploy PaddlePaddle Parameter Server processes, it's a Kubernetes ReplicaSet. + - Deploy PaddlePaddle Trainer processes, it's a Kubernetes Job. + - Deploy PaddlePaddle Master processes, it's a Kubernetes ReplicaSet. -- GitLab From 3026a6b74a9d71bf5c617195a6a28765ff641189 Mon Sep 17 00:00:00 2001 From: gongweibao Date: Fri, 12 May 2017 14:52:54 +0800 Subject: [PATCH 0279/3256] fix by comments --- doc/design/cluster_train/data_dispatch.md | 39 ++++++++++++++++------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/doc/design/cluster_train/data_dispatch.md b/doc/design/cluster_train/data_dispatch.md index beee05787f7..39fbe55c26a 100644 --- a/doc/design/cluster_train/data_dispatch.md +++ b/doc/design/cluster_train/data_dispatch.md @@ -1,29 +1,32 @@ ## 训练数据的存储和分发 +### 概念解释 + ### 流程介绍 生产环境中的训练数据集通常体积很大,并被存储在诸如Hadoop HDFS,Ceph,AWS S3之类的分布式存储之上。这些分布式存储服务通常会把数据切割成多个分片分布式的存储在多个节点之上。这样就可以在云端执行多种数据类计算任务,包括: * 数据预处理任务 * Paddle训练任务 * 在线模型预测服务 +

    + +
    - - -在上图中显示了在一个实际生产环境中的应用(人脸识别)的数据流图。生产环境的日志数据会通过实时流的方式(Kafka)和离线数据的方式(HDFS)存储,并在集群中运行多个分布式数据处理任务,比如流式数据处理(online data process),离线批处理(offline data process)完成数据的预处理,提供给paddle作为训练数据。用于也可以上传labeled data到分布式存储补充训练数据。在paddle之上运行的深度学习训练输出的模型会提供给在线人脸识别的应用使用。 - -### 训练数据的存储 -We select CephFS to store our data. +在上图中显示了在一个实际生产环境中的应用(人脸识别)的数据流图。生产环境的日志数据会通过实时流的方式(Kafka)和离线数据的方式(HDFS)存储,并在集群中运行多个分布式数据处理任务,比如流式数据处理(online data process),离线批处理(offline data process)完成数据的预处理,提供给paddle作为训练数据。用户也可以上传labeled data到分布式存储补充训练数据。在paddle之上运行的深度学习训练输出的模型会提供给在线人脸识别的应用使用。 -From the perspective of user program running in a Pod, it is mounted locally, as +### 训练数据存储 +我们选择[CephFS](http://docs.ceph.com/docs/master/cephfs/)作为存储系统。 -1. the home directory should have been mapped to the Pod-local directory `/home`, and -1. some shared directories, e.g., the pre-downloaded `paddle.v2.dataset` data, should have been mapped to the Pod-local directory `/common`. +- 无论是从[PFSClient](../file_manager/README.md)的角度,还是从[Pod](https://kubernetes.io/docs/concepts/workloads/pods/pod/)中运行任务的角度,统一用`/pfs/$DATACENTER/home/$USER`来访问用户自己的数据。 +- `/pfs/$DATACENTER/Common`下存放公共数据集合 -and from the perspective of our client tool `paddle`, it has to refer to files in the distributed filesystem in a special format, just like `/pfs/$DATACENTER/home/$USER/cifa/...`. +
    + +
    ### 文件预处理 -在数据集可以被训练之前,文件需要预先被转换成PaddlePaddle集群内部的存储格式[RecordIO](https://github.com/PaddlePaddle/Paddle/issues/1947)。我们提供两个转换方式: +在开始训练之前, 数据集需要预先被转换成PaddlePaddle分布式训练使用的存储格[RecordIO](https://github.com/PaddlePaddle/Paddle/issues/1947)。我们提供两个转换方式: - 提供给用户本地转换的库,用户可以编写程序完成转换。 - 用户可以上传自己的数据集,在集群运行MapReduce job完成转换。 @@ -136,5 +139,19 @@ userkey=wuyi-key.pem endpoint=datacenter2.paddlepaddle.org ``` ## TODO +### 文件访问的权限 +控制用户权限 + +- `Common`数据集合只读不能写 + - 现在mount到本地以后读写权限 +- 用户可以把自己的数据分享给别人 + +### 文件访问方式 +不用mount的方式来访问数据,而是直接用API的接口远程访问 + +``` +f = open('/pfs/datacenter/home/user/test1.dat') +``` + ### 支持用户自定义的数据预处理job -- GitLab From a35981756e18ce7f4306b2dc5ffb52908eb78eb9 Mon Sep 17 00:00:00 2001 From: gongweibao Date: Fri, 12 May 2017 14:57:34 +0800 Subject: [PATCH 0280/3256] add pics --- .../cluster_train/src/file_storage.graffle | Bin 0 -> 3031 bytes doc/design/cluster_train/src/file_storage.png | Bin 0 -> 42615 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 doc/design/cluster_train/src/file_storage.graffle create mode 100644 doc/design/cluster_train/src/file_storage.png diff --git a/doc/design/cluster_train/src/file_storage.graffle b/doc/design/cluster_train/src/file_storage.graffle new file mode 100644 index 0000000000000000000000000000000000000000..95ad2758ccae252dd322c497e7b167135cf478a8 GIT binary patch literal 3031 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 ZRy-tS!pHg;esbz?=fBA`h-V2)000rd;<*3- literal 0 HcmV?d00001 diff --git a/doc/design/cluster_train/src/file_storage.png b/doc/design/cluster_train/src/file_storage.png new file mode 100644 index 0000000000000000000000000000000000000000..b744bed48551b4361171bf4502e24cdff69a68fb GIT binary patch literal 42615 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�@bTUii=8=lw7{~g2`KwJ7`7~EYu?gPrQ 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 }c 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 literal 0 HcmV?d00001 -- GitLab From 17511370841a0b2cdb0c04a22394086c9e842263 Mon Sep 17 00:00:00 2001 From: gongweibao Date: Fri, 12 May 2017 15:05:42 +0800 Subject: [PATCH 0281/3256] fix bugs --- doc/design/cluster_train/data_dispatch.md | 4 ++-- .../cluster_train/src/file_storage.graffle | Bin 3031 -> 3031 bytes doc/design/cluster_train/src/file_storage.png | Bin 42615 -> 42121 bytes 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/design/cluster_train/data_dispatch.md b/doc/design/cluster_train/data_dispatch.md index 39fbe55c26a..c82e7b558ed 100644 --- a/doc/design/cluster_train/data_dispatch.md +++ b/doc/design/cluster_train/data_dispatch.md @@ -101,7 +101,7 @@ PaddlePaddle提供专用的[data reader creator](https://github.com/PaddlePaddle ```python # ... -reader = paddle.reader.creator.RecordIO("/home/user_name/random_images-*-of-*") +reader = paddle.reader.creator.RecordIO("/pfs/datacenter_name/home/user_name/random_images-*-of-*") batch_reader = paddle.batch(paddle.dataset.mnist.train(), 128) trainer.train(batch_reader, ...) ``` @@ -150,7 +150,7 @@ endpoint=datacenter2.paddlepaddle.org 不用mount的方式来访问数据,而是直接用API的接口远程访问 ``` -f = open('/pfs/datacenter/home/user/test1.dat') +f = open('/pfs/datacenter_name/home/user_name/test1.dat') ``` diff --git a/doc/design/cluster_train/src/file_storage.graffle b/doc/design/cluster_train/src/file_storage.graffle index 95ad2758ccae252dd322c497e7b167135cf478a8..5331f407f7ee77fca263be24eb83ed1d44ca2bd4 100644 GIT binary patch delta 2724 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 delta 2726 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 literal 42615 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�@bTUii=8=lw7{~g2`KwJ7`7~EYu?gPrQ zY~kp(_2q%f>>CaaPDtZ%gD5%jM2^^j+}&+_Rz#LcoZHIP0jSVrrK!q$Kzdf#*? z4{EBi6ll{&*mF&{S&#&Q`{{K~pU?=krPJRt+fLheHzPK$q z?j+Z(2a>afEU2Wo&|ISLrN_QonEq9GaZEw8AYnhQt4#3A6}ZnKXADO&GFo5?(W3X> zEB#L2@NoCb^U2G;o|;0Dq9=b4`kq{T!$MjE8-RS`vzOGxIzrN1Fb;!By%X^`>IL={ zC~=Cup7X6ICF@MyGo{@t9@T0IJ{}$ryOZT&GyIHQy2_G5Bf;+O8)eAWwwST3>Mwt&?A8tO3%t1gTaiDS3Inq9O_oEUT+21)Z&d zXvG#6mdPt_D;FBLs01tr>k6|g)8r`yg)~k>&>nxd5nqrwH>aAXFSbBiE1=t+BMzP% zVz1p|hR(F1@@$u;=yO!e>mlIb;cur(-F&&8&%zWN?T$R8iKudh-~g7ZUtv)6xZbvh zx{sr5S5HDDfxLfkuBlLPBcozZ0c9U6T<-UB9iH!YUXuXDUkL~Zb|0R#RgVP2-}?fL zdTGc|P+gYcu7_o-p30yDVt@X<6A04AIz6a~?g04Jx**p*5|KnoaI^Ex{(=3nBcw6p ze0th9Rj0jd$<9RfzP81;vW@SPO8yJJ%v~xK;_wk5JuQDqL|VJ&pa9B(di&}9av$dW z(Z-HzkUeepASI8^6pVVcHjZ^ti0?DX;quAXEz`b*Fbc&DTL~?>qYA#u!k>>;xC{(o zmQJTF>;(y@K32&u8&qow4lD^$@dqPjkxo}SfA1Q+E+xGGLFx3j^n;bnn%66ECYq_}l>ZS2+`-aGeX)S;lxsTI?Pe44> zrygNpMwx~YpfvlADE#n?ion7dr+V9qDY-NKP>tEKL@9-F&3j#KwoDgcTX9BPT>0&I zgegg%cu!NmiB$&8dxbT2&c`z8Tun?j+}st85Q(9^vxMJP-UHkRXyLxnqGPr{{va(a z{SyxdngPaYf07jN|8m|DYEJ{Up;@Bs58CnRawA4d`pL3u@F%ypFiwoGZ(nD0+*7^b z&|Deh$>gBz1yYfE3(gM_o~^a3_od+KHE{MT4G0slrfu%WFJ!bBVclrPDaa*;cM{KxI*{vPxr29r6&_^3UP_!A-SCezg^j(QE5+yXH(jdkXQ`-|xV`w;pPye9N zG=)%Bq4+<_y6foRznU?Q&r))75dNK>o1401D6WziLlNNj5gyCdv}A8JE?oqn+9_$| zkx0?$a>?{M_Fln>Et2=#PkUS$+#{s@VXV>628ceC&$tm3Z6$~JDdBLj3Ol(#vz#C~ z%82GGE|Flv7G5k*#$F75*`VnkE;1A#WRKIlywtO?p#zk=_s8c`|2PH{bb#B!tE{Xn zWh7<~ASk>91*sm(y62WuQ#)b-IKJ?o4ts$^#xL|~$zF<WXth0WZ!WS#?s)BjML%_yiaRj$^?N9)5bnS>1DUbBN$K^*?x~OB|4i)}j^^ZIaER z7Vbs|~g?G49%_ME_ZaB}%h zo5J>Y06)2R0Po}b$PV2@fp{Ol*P+&QzhBrlG}HxfxonLl5xrWE-2@LT8p&jLTaxTH z*CWoxR8kcphJsc353)_aen|#nF^G^u9al=DPue;-)NzvQwY#Xg-EFDCPQl}O@gr>g zg9T(n9!3)^liB9w&t7`VV3$4>Sh;fZ+h4*qXYNbF~WL9 zWodE%kjV9gm1jm=07VfM6}4JWShC-~825b?C9iG!hi}~ZC#w!X8 zz|dGg$R`h={zed5_!Woa`%6;TQC*E&a9A2)Pm{oUM1w;mPDbXJr>Xz?H}wVDn*e+sCT83d`Xm8^43Dj+iLZdU1EY0sY8t zd*l9yO&FmG1`2`nl^YR6tn3p6bsDsBx-u`4D4}Ku3{RD66L&hHnRj42S*$Wv8H`BV zXCZib9zq@-%DnVB`qr>c5{%Yghw-<%+8m*G*cX}LSMzt zMHsA>zv$Zuo0~Cd(cIj6Igo@AExhrBY*6!wnw$@^atO*RK6J7{?m~LBz9<;1LKsw~ zO3r3|kq&7X{o%Ch{CPokpU3p;8&747vKKQh+kM&MSU!*`jMbX*DJF^`t+5P#ELzMhi|9om^GC#}8(ClC=chumhrxnN?DjmFI znjM z>H1x(MN6u(nuG48h3neWFw=l~rlX$&_WEY>(AH=~>v6*sG{j3e-)y1ASViEAmI~_R zz#)?n6w$!3hI6|%7|il}O9Y2$7Va-n*Y36K=3#g4xaw1=%#P<-Cij0#_Ys{nw%_rAy?*b7NdHd9%?a zn^D6hb>(;?H{GPYs=dZA>?ZMM9p`c!_{mvlN?lw{Y>A-wH3}sP-h0-Pn3vMWhd!R-^I=RD|A4>U1U8-CALMfSWAI!L!M4QcsiSO?<(DXWOVgdZ>6!SsKD#kNL$Jiks00A zP~-BuANq07`zja+awk^FRX{gfKT;rab>Dc$my52A6zG9ux-3ny0T4R5f@FO$l|mi+ zoI;~M&|;1{G4DgZVe1+)in3b29NitRklCm2>znw2m+sl)M!k$Gg>6(wfx_&0m|Axd zqJyFu_%ESr2?pmdPt4MI8%_slCVolHN{O2@mjE`Fx`lR!UvE5W7q{h;#~VHSTojB3 zSz5DWVVkdc=0`HOyLt@9Qe`4D{ewks5R8gDw4X+q@*VO14CxK10W~+bRgB$rP4sok z1)JUBuZvMG_n3OixE8_S5P)Un2MH+=iFqs}$C~wv^1|=GKPEH2ML{cJ6j!$&n@&wkL zLWBI-R>V9S5YWKNw5oS44SP9>^p5AcbE8dIW!&5D-vygc*_srz@Ud-g>ye3kNjPc0 zMq-r6%zMJ=Wm(L0-sC3er28vfL{E!G)M#G2D3C8OIvl<$i zHn7%%t)1@A7zREaY9ePiazfa@j^yWfeg-u6nha0V;|MKe?cu*o*g**%U&TER4@4Qz z$T*@3ZUy%1_olVY!PZ$gI9{{d&V~ayh+k&4OF2NkeK+19<*a=ALET0M<=Idlk#ai3 zEU30Q+;7NH0UF~;d~M^c$uaXyi)1(9vApcdF?!pnLPG|K9woh3^ex_)k~gVCjxy z2pGnTq?!d{IG-xCQTCDCHI08(v*k@tMWLbLN5vb!mav%+6*%OL5|()1KjbJCPj9Pi1j7fMM?~&6fAIa&^PUH}jg1YUTf<^6QiAjBx#yRc2sAy6 zu{jZS^aTu*H2DS9jo$T4FN3Q`{Y9L;Kk`;V1={IpPtwD0%X@M&5=aENP(a>BE4#+ zKk7*un2L1+2NRP*GMoLhNojMAdUGJIsr(4uA(>!WhDP`(%g%>-)e>uK%~)!f*^6zU z(`k#bIm4!K(R(i7x17BJok9jo&DLqLTBCe2tL^7FlHNQbd%5ppijjKZ7_a){j}jS% z0+h&y-dGJcB_LumU?H9L{d)u85)TVl;M?Hm+Mx1fr_{A7b(AYiT_Rvu?!R(RWAuLk;y{7)$M6u zc2NJXto@$*-)|Yy>HvDz!TGJi@ zW_mWD0t4eq^vHmVt6X$h?}FCT)7t~K%OD=iA5XHOZGWn;zT0Mx-t_MRii1Ou3}~89 z6+|C)n5r}2e{nd~^!4%C1(5Ai_A9Ombny{Eqp)4Qy$3*rX0mdW^WXyg&7ZxL)OcN8 z;AKH$e5pVR{P9Eb`}e9t$`o&J z@9@7e?dgOJj2EoM`Q~`O-p1h1J+8&ssatL{00z#im8WC-3Z6uG+QaW8>QJ%*ZEx=~ z1;eJ%6jayPiS>I!ag0}5T=ftL;r|NQWW*Z~e$29KFJGiUTFZj^IZjWPg&MPafPJ_i znp~kkERpg4HE&&QGE>o$kx&DFc3M?MKb*uOqoD4XZQgt(Z?M0(-0XbSg(5-n=~EgW zV?5&@TeB($%I=>Le9UUS`GeJVKPxyG?)a08pOl!{E7wv|+j{EDV=f)p_fB}GUEmKf zYW7+W9>?KjlUfz!40&WYvpoGLkqO zwi8S)fuMn$f}$u%_OL)01iP)Cn&3#|VSlDH)l_4))%E@oudPt{)M;dJ%X$y6b~FAT z-SUPLnb=MMTBzNIgAyAr5p2czDw>{G5_M96*5YGbyN>Z=u(+H3vH}wC(*`K)4WJ_WVUE*$EVS^&c!OV*Sn0;EPuzr z%=S8t#TY8Jf>+fBP>$+Dkj@?DgWAbM-eBJJM zJbG&=ks8WS1WOSZN_@rG6QO{YRIJ0?Y69%<1-6XH%|=5v&g6!qBqxYnfnYS)Pw! zch@l{?40%W^#g(|Z+z=;Fw5=nDr%{#L4?n4!rU(L4GL5@dP0(YH@jcvFYVIHlLPfu z?7nwB%{@H<#2KkJ*lKL7lJjiD#Jx%zI$%T(HKP}0?lv|y$*gluXzmM|9codX%B}U#QFs~NZre1ne~eQ4xNoLX1M z9D+&L6OXOY-zntxuPx*DM%VPo2wY+VYN2Y@{e5u{Q#_1uvfKfQjB%iLf`*Ih2KdPD|8vnYC9o?Y&kum^ zxdOC->POeha6rw;h9ohg{%7q@P&MKC8SaZEQaJ(k`st(Em6Awe33+^Cgg^G=kCVdo z>TT$^&RwB0rWsD;grHL>N)9hFfB5f>kb%WXc-q?8$-TXGFq^C32YUF5^vyqn{=0%6 z_R#d_CHqeLW{LDh8B8lKGJr!M2Pl08AO$sAL{iQBj|YRuo#qDtMPdwf*#S#E2|)Ph zHQU|*I$m5$ON$|jZtk!1@p|jkdeEZuytS6ODx*k%VLu0;%wv|AELl73NBGb1!jOS^ z!rKeJJU=nmAFJ-zRRC(QaBcDbbezAl$u%s2%W?w$I|)ymB|83|KqFVaRGv>T=kL3JPC-=U-kgb2U%_H z465eKab=2-qW@m${CXv$`>H2oQh4A&vOLV&0&jya?-NVC-;%Xv0*B@v$jX1eJ=IzQSZ4=@hKg39?EZ7xRTkj3xW6#O66nVO@5vS@pcGoH zw9Gd<*FsJPlKppy$^qx2Tfi0|7qb}|lab)qD$1?{u!l)fL9C>4yuW?zKOcF1z?BTA z#v8wY2}@A7K315wKG{7zow%2VeD3=v!~m}K#6khWg%GJ}QvtjA2>|*ndbKwp2oP6G za&vPJqjQ`8D`rrW9)M0_@KD57`o1^LuTo<=27nLsa=BfB5m!sWIez|kizF!l2{JyL zI&QZszXmFI5+PU&1DN?j-@X+sf%fD4XOGBWfQ1Uwyo!DVM0)~AQC{}phcTbr7eH?P z_kOB?6xI3pyVtyH$7VE*JFFyE2QqN*8~9NpLD|Md$Z?tGJuAz*MXBP8^L z4rd@Jk#FkXqtztofdoT+NM8{AudI0FLVe*~Bjw=X_|FEO^1eRz|F{rQ(m_N*^4k=R|{C9y>ySyVm6lMxn8nR;X?tP-R|LuM2TmkNhD zm?m?7*}Jm+cucj^BalNyo!{9UIZ`&?VqC-cxN9d?CxteBqj1DIQCVtcv!i}HgPI9K zbkfqx6!(;`--s=4yjWCe4)fycWJ}U5#_t*tStHW=JaMDkaA_VLQ&CpN`e=FY=%{&s z#nE^QF&ro%`D4#NKJw$_Pj$0oEIo6z>>cU(gY?FSg02^txr^2OtA@R?N4Md@Z&T!> zl#}EK5mh^uqF30QyizxzveeG%a+6`+^F|X9cTTmZT|>QwOLweK+KO@WDOSsqcgO0E(4t@fB{0sV6gX)+4cCQ|i-qe(}lCNV@tUrQfh zpR?kbjJdn~joss+iFi9F&x3?~tK7KrN2o{ruf``0UBj0Y84(R#c+zUPKBoV)6Jv0^ zi_gSdK-o52FH(X@ZI);0E?AlcM;SRIicw^;Ey!_m_&RLrc1I&n9)%g`{zbY{KV;(K z1ec^{$z@1*>N16r{)=^VNiwU9jdm~j?JX<(;j*%nuWf&+MMYyvP4N2GChqjS(iqmF zUd*`YW=b|ZUGDx}R3=bk-)}VNROJ`6_#(y2^9m6;e07d*!3}z_~}&Rv1w?LRpZFX|G;wR3(Ea~4+wd9N2jdM}4$)*EPw%Kt@-5P8t+NwDcFb4^(7NsT$bBtvA33C|h z$|@Dg5dJ$2>%5x@vC_Y4*5eaK=h!e3b@BZ9CFM87Ehn^>0tS>#Etc9b4AdiTAOK zsRttOO$Jh86yq8n_|7<4VrWahoZvs)G}xUixhr!5FJGQ{Zf+FgI@aayzqed>LSQ2HAz5|t<-_;xI5(@2;lTT`%bBL}kxZ}Wkm?<(|2e#A zp%DA7KA&|qL<48o;i=+wA}dyR&JS3WwLFWbvPCg$O=oyHa!ak;?v@%>(IIMZ)8MxU zn4P#y?(TezMCD_qhd&gwp&y-3)S4Sl3sXo1Ouc7OVQ?;?x2eMnDJvE5Y?yt#JKfO5 zVb*m|uI2h@KRxdKaHTo=hy+*3(>pAivR-P=hdV`Uqlf#@6WTGXq=GWkCs^mkWtM>M zpJRBvPbc_g=mW60>_$-&_Cvj*KlDdmMWLoyJRWf3krV{NPHspSZ^PYbhq9n{^MvIcVPOW#cHxVdbD!$Yl5a4hY zedsXTy2=!bigZ>E^oVIFJ2m9|pLb+SE}1VZP_2;tq4{iz)-N;Ysu({Q)RUNyo&BdLDdwQ1q|qrNtVodPadU?+4O$Msw37X*#Dol|o=&3AZVu z4P89EApOY*pBlZ29p}p|Gloh}^c&=VCWyyp^|e!za2SWOMlqKpQEzw5zlFbuc*v9b z*@~X%p|>5V3!geZqZ>^yjnmsai=fk(vA5%XoNSBRA>KAUbS^D-6 zku9L*ELJfzw20QJui)l~KbMkihAJtWj+8bI-$lpPtA*7-jrnl0Re#rIiVSm2c2xIU zWD(6Ix|WIMo>0<<=w3J5+h3~n7EzJj(3KkC;%PpftS~Ai&9FD&k8?|AJmG|Iz0$ve zIWB!>bRB6qe;7-Wt;u$7STqL=UISbWit6rE@qBinjXL&Woi@$0yu=Qn5B{=5EXpf4|IAyG?$1KJK$;HKIO* zRk!QPURW9>Wh79?LpfS18NzyZGa~ZBk#m?SXj_#Qj6&Qp5q=ViCOcN6RiZ>UCXO>q z!YfF}`eijVUtcA-#}^IUM4To2CLsPgyT7$xWAekVHRY!Pa0TJ~l{nZV4rMLBZ26vf z%?^FUv=)0VeQU9?`IiP~)2L%Cti#P^tv1bxcjLE6VBbWX5tcH}wh34g5_8ooT6Jr> z@-!6OafBq8@KMYzgJvbWdt211p7Az@E-Iu7Ta@=%bD}~9hZH3OKR?(;wXPTvW;b?{ zyI!YE@Vn9Ssix<7i;;g*HaM{HWu<^=F+ zI*q{N54yYv7i4vBOeV!d!wx4`4R#E-J(e3f51}#HBnn?>d9Jr59QOhe;*d+COoB9i zZIhTis8=H#=cM|p<5iY$lMCn@``n>94>3Gl&X*>GiaZhHyL$7MJO$aLCp0iF*RO_s z!LTi7v=s`x;8^qh$}ws05TLOsIy|I0Qj%!0tsSq>m}0R>ENsOKRlIl`tgq@^Uf;Ex z=6#(|9Vg}1L1D9#tFZO)-sFl2AF=mPNMH!=imX^rRP~}}JgR!TMEU5g$@LFZHB-ZP zI|hmY?jbIHQ2e7mtmobDK^Cf44pVdS7l=?pnf2ns{l@gO)&dypf!`hhQWehfCpOvS zB#-uOQvvS|Ua>E0rA-#C^e}4)GZ_v6AjTGUqj#I4V%Vmk@Oznd$2Ezc2Aou#(w+It z*XA~YJxG<8RK8;9p(AIUYJ*4^?da*>+qoXN$9p}zo486}3v@UZ&gNQF1M^oc)mg|G z`sIn4OnRi3LtO7lCsVsCV=MP!o{Yx0*aEzf0Ta)0`x@~IdrYQx8IH8|qgbDfc33i~ z{a2a$20{Gus0RI*27j)M;&5SbwCpR8g}<>fnVm$9L)q8J>36A|m5< z_I0a=Y;Jw3c`;`y4bAX731Ynr);+vtHuXcLt^ND8h8cd8%ljV^o3wM{BQ7ob_c&PC z=qa)q(Q^}&Se_Z@?u@4#e-MSxP6#95+J;uX+_F8uv?i?ThP)k#_*_zcvGmSRuj?~P zaFML_`De;`)8(LuYO5`Jx^)_vmwpP?k|GI5qX1g)K$S+N{vdRtN>^fw>2+cKkUkv? z67n>lK|y00a#WZVKPc&Fa5y>jbM!*iDNNIs9#;IbJ?wde@#E-7dRxsOpVza+^AFjsNOb^mSmmVxquwE8i*SRYEB|7X7;u`IyZhp`Aqk-$8 zd~T%=T0Fu2I43WjDEJ%8T2dhnU&qi+G3)N8)TPAsOS75RP)c_Zm1@sJ5l8^Vr zJ#^I6e2sCACGq=}FPBq2cDQc5KKjK6*+$+C@zr&M!;4q&2~~Dh&taFxh2QMA=QK#z z7ayIPtx9_|CPo4f6Y6fZviFtf+}+QPIgcst+`*g-3!rg7yD@1fgwk@|>D$XCkQ(^a z*CD7Z^kkJ|wEACdh0<5kn;;)%AB~7n8(C%r5cN<^JUyw9nmq7cu-X4W{`jH9i*0P) zM|y=ZN3QbnJ8!}L$`Yl!3g*kRK|Qn`@5W^!kE`h8`by&*Ex%pBFF&w)tY&6$6z6xc zaWQ-K{jHLXZ|KZ+Ho(O`FpnrTBI$qRUkm0&ovQ7+GR(}`UoA=}Q3@F8)EROLsizIw zCgHr5p&i3q`L>evf!>5*e2SvffwbOua&k8I%y_E5h;2^BGG9$THOi`}l4X^4$>71H zj{>69UABF=vidmwY()EZvkiD%&ti zSb5sf(bfkV6_P7Yt`h_MSZBW0ws6_G>)Q@i^$_$|P$!>+d`Xlk!h%>k&TDd8IO8mf z+UD#&5PgEKqVn>IaBDw`$Q%K~$H&n7iR7oL`7SR=d$TdqME&u3FP=Q>dm8r^h5om5 z7DT7?-VCfxO%AC^@xhe;WE!}Op(fANvS9-0YLRjD%oVTN7Sg@ z5j0TN8xkniCz1N|i1W=GY-8cCJhCR{okk@ZqT5F12Uv}?^<}sL6NhGB70}(NQ*K7O zGlwW8chJaZ7E`8{kLl`X#rR?8X(-D##lBu#u~+Grgk}{{G#wQ#=t`?erWJAax0)R`A=eIx#bfNl1W?vZSzMdXu-lBFMt?jOnT=mbqYJe)&&1j% zGqz}PH*IIBbT%_BcL}!n#?zXJ-4xOfbZ+K2sop~M=9luxAxvcmpZ<5}Z@Eqiywaf`h;#R^~ZGxpE>(7{yHiYDaU114zCvTQ z6#2@M7HbE>hpZIl0XFKOf~eeJuz2I;8o7Y#&-@Q}|I6s!#&9=-qi!L6oUIZ`O>+14 zyJV%CB{^Zf{vS>Vkag}QSO-;ZDWEz3$G_P<06|198CDCte}&BE!+$brQ2!UXH*W6# zoAltVCWjkP2j&+PECdpj_dv0w2zbTBN7=Ogf6k+aJuHw&w1085JsRbDap~^r+HHM# zDg|B_lmGha33B-&Nmur!r9!-X_J2k!8TMM8#yul#$ZC772Mb3Mv4C`IJfCAMS$4t` zqMXA2vm4xP8$elry+kzcCr}p7MEvk2gFtST18Idsd@;P^zsmJ5{4}AMAkGW^=`?{~|oO7Mm^PDR| z&MB$%$p~}v%a?_xqxdtZ+chRnJk9yE;}w^2)uw_PchCss0{MRED4++Y0k=bG%+y<< z3TmQw<^rTcqo(*imi9o&#P~V?zDkF9f^Gj@BWO^f4$a0JSk)(~X;yYFNOhL8Whw~M zC&@bGiKgT9#l^+@s+`jaF00P|tI4yovsxl!J1GwrpjywwfhMvj1{)a;k3<~!5G{Om z9%{QL0TVJ_oF1%%Jb$6Jx;HiL8py{3S##E)Xp2FnV#`KM)bZJ{BpJJXKBw^jw>-Ya zoJWz5A;Z2ElCi=^mqxVd7QDEB{P?kgWZ|KiLSG_ZSEYlovppS_s%_O_0OZ4b3~uPB zfmC!Pzmlo@rv>OoKYB<1j(`e=k)40uVw)*7@k~Snf!#sp&0APxt_@s>QeMGxT|hA~ zC`?N7`>$R9IrE%d)^htXs^4Xb#fvmhO#q}KAI?4Y zbS2<7q*PF|+7WSLP|0B05vAb%4G%MFBGqF^PhUT}#%&^NJk95>SxXwL$Sdlb<%Xd& zpm}laJ2w*>n~|E@O^h$u5D|xJ)@I+Ps*VCHGSn|-Pkjc@1R4*oS>$b1iq1e`qaDzf zh$?N}no1;O3gMp+y7?a1twDmD`J(e1frb9%Gz9;EHFIdN{&4T zeHVIdutto`)JMH#Rno?DNR5I4DTln z!HRN~$7AZf?O;vy5o~l@xYcy1CW99-8P~`4$TeKHAkJ}J=;4frkghocxJWxFH${lX zG%ayLlX?LoGhASmj}~s@M&=q6prkbSP4!oG>-w*pKc%6e;2?dx>H~2L<+1TfAQ|-$ zgvS~7Tji_u+8FmV_XFpp4}=F6YLZ-y@eBjwG88kT#S0U8Ft~)tKq~_XF3^hI^OUjE z1HILfvA(5%b>Q?0Mm4GS+p`vgj;h5yk$$AO-To;fEFBA`UkTaH|QB_KVT08k-8;I}Y>HZCQI;kWS&%!59 z`6172M5J>Xtm%q3v8Aj)nLRKp48%m4W>ef7K&Gel5yd0=Zjn)mKFHJv5(P*H&}PeOb9RCo_wJbp?=>E~ zy^Fj%5TQ;j*H_}BqoXT)SKYDSdfL7C7C{HcF$TtMn81Kn$TY_B>YcFU{o0-#Is$Yi zz)E6*g6as|pw6z817yw1ZaZ5Q$}a@KFEF~%8LDO+rgJBqH#bjFUj24td3kx_wq)sh zrNx2gomG-Sr};L91}}?f=h?!SQTC^Yvm&HB0GKEN1k3_B8NP#70dpC3fO2NIGYc70 z+)2hR+UZ#XHnv)h8HT`=lwDHLXj4~-96NWf4vu(NpbgpGJp5NKcnl`ri6usb4l86O z5M2eX>6xNhuURXI>!t{{vb!tb(nh$~#0zEt_psZ}HHa5lsRUWKrIo-TD(k@e#kj5i zTr7g->8uTdG8`Lgkc0`4-G+7631=eKsM^}%Ka^S&A*YoCeOCaC;_z{y@4(qrV&v#I ztbKsl;(SL%Ge;Pa9^~rCF=N@ z3{X^)j@aC7SereI1vz=aZlH0g`M^AwHy^ZA0W)RzvuPZ}>K=CEkI;2(EB6TT{?W|K z@_oxyUKe%%QpQuBJ8$XA`RwxN2XH!IX~%0)7zEKmR^PXffiXPB@`Qc2LJpiX@Kz_> zz9UCdZ{J@R@floIY|wLQ6i#y()L%FLghiO35)u+xNb}vR0$Hyp)Dwr8Zoo3@=ZSK5 zT7j)k7Wy2>q@pqsFum=dGM7QEz*qUkAO5P zj(`+8=%dC%lf9Abt|%7LrET(?81?AUSN)j&pmG^`vg?YGo5 z%SxMfd|5-KQwwE(68SXJs~MaT8A`434&9}EN36H7zbWFhIQ$2lY*IvYV8TbdEFD?G z1g^qeK)X%7ig8in$Y^J ztxY#ZM7smp$q}DiA7$t^;-eU4VfcP6ZzOOm6fgJt>D`j5XpBGw@Ubi1CKWI@ld5kV zrObmuBwVbt!Sm8#B&?hdt-?7=W(z005)%_iL-kPEJ1LDA&a-AGD>r;x#&MWzvu-_L zcWtCxdauS$^xC9j(`-0*?J5SNKmz=Q2)G3qBH!=DbDvc==IG3R7$z?xfAmfiUvU8I zJ~(;7|5#@x^s>%p6u_au6Qw7~I}cg)pL_mimCex8YKY-7p?ncu4O!bv1Xa6Ff>FU#f zph^kLCR1!c3~GDkOwLe-G}96ui9hBBn0<U)beZNSyW70ZU?4gN3VG^yFmiUs2Z*T?*gkXF&_w=*f@kEA5i^U@vqa&} zHR*(Ue|}(`GJ)Q)W5=Ux9(RRp9-*@jxdOlZw!ognfROO_%Nl^&FAhj$`BZwdw%6X)GL}2y%E!c!ewEd^d1pH&wq6)svA1SkMdtov)MX2EDKg{MUbh }c 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 -- GitLab From 785ac9e0afceb4b60e49e82c024bb8ebc7376b39 Mon Sep 17 00:00:00 2001 From: gongweibao Date: Fri, 12 May 2017 15:09:41 +0800 Subject: [PATCH 0282/3256] fix bugs --- doc/design/cluster_train/data_dispatch.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/doc/design/cluster_train/data_dispatch.md b/doc/design/cluster_train/data_dispatch.md index c82e7b558ed..5b70e802ba7 100644 --- a/doc/design/cluster_train/data_dispatch.md +++ b/doc/design/cluster_train/data_dispatch.md @@ -18,7 +18,7 @@ 我们选择[CephFS](http://docs.ceph.com/docs/master/cephfs/)作为存储系统。 - 无论是从[PFSClient](../file_manager/README.md)的角度,还是从[Pod](https://kubernetes.io/docs/concepts/workloads/pods/pod/)中运行任务的角度,统一用`/pfs/$DATACENTER/home/$USER`来访问用户自己的数据。 -- `/pfs/$DATACENTER/Common`下存放公共数据集合 +- `/pfs/$DATACENTER/common`下存放公共数据集合
    @@ -142,13 +142,15 @@ endpoint=datacenter2.paddlepaddle.org ### 文件访问的权限 控制用户权限 -- `Common`数据集合只读不能写 - - 现在mount到本地以后读写权限 +- `/pfs/$DATACENTER/common`数据集合只读不能写 + - 现在mount到本地以后有读写权限 - 用户可以把自己的数据分享给别人 ### 文件访问方式 不用mount的方式来访问数据,而是直接用API的接口远程访问 +例如: + ``` f = open('/pfs/datacenter_name/home/user_name/test1.dat') ``` -- GitLab From 327727d462ed087f74ffa6cff25c9f7d00074b2a Mon Sep 17 00:00:00 2001 From: gongweibao Date: Fri, 12 May 2017 15:13:27 +0800 Subject: [PATCH 0283/3256] fix name bugs --- .../{file_mananger => file_manager}/README.md | 0 .../{file_mananger => file_manager}/RESTAPI.md | 0 .../{file_mananger => file_manager}/pfs/cp.md | 0 .../{file_mananger => file_manager}/pfs/ls.md | 0 .../{file_mananger => file_manager}/pfs/mkdir.md | 0 .../{file_mananger => file_manager}/pfs/mv.md | 0 .../{file_mananger => file_manager}/pfs/pfs.md | 0 .../{file_mananger => file_manager}/pfs/rm.md | 0 .../{file_mananger => file_manager}/pfs/sync.md | 0 .../src/filemanager.graffle | Bin .../src/filemanager.png | Bin 11 files changed, 0 insertions(+), 0 deletions(-) rename doc/design/{file_mananger => file_manager}/README.md (100%) rename doc/design/{file_mananger => file_manager}/RESTAPI.md (100%) rename doc/design/{file_mananger => file_manager}/pfs/cp.md (100%) rename doc/design/{file_mananger => file_manager}/pfs/ls.md (100%) rename doc/design/{file_mananger => file_manager}/pfs/mkdir.md (100%) rename doc/design/{file_mananger => file_manager}/pfs/mv.md (100%) rename doc/design/{file_mananger => file_manager}/pfs/pfs.md (100%) rename doc/design/{file_mananger => file_manager}/pfs/rm.md (100%) rename doc/design/{file_mananger => file_manager}/pfs/sync.md (100%) rename doc/design/{file_mananger => file_manager}/src/filemanager.graffle (100%) rename doc/design/{file_mananger => file_manager}/src/filemanager.png (100%) diff --git a/doc/design/file_mananger/README.md b/doc/design/file_manager/README.md similarity index 100% rename from doc/design/file_mananger/README.md rename to doc/design/file_manager/README.md diff --git a/doc/design/file_mananger/RESTAPI.md b/doc/design/file_manager/RESTAPI.md similarity index 100% rename from doc/design/file_mananger/RESTAPI.md rename to doc/design/file_manager/RESTAPI.md diff --git a/doc/design/file_mananger/pfs/cp.md b/doc/design/file_manager/pfs/cp.md similarity index 100% rename from doc/design/file_mananger/pfs/cp.md rename to doc/design/file_manager/pfs/cp.md diff --git a/doc/design/file_mananger/pfs/ls.md b/doc/design/file_manager/pfs/ls.md similarity index 100% rename from doc/design/file_mananger/pfs/ls.md rename to doc/design/file_manager/pfs/ls.md diff --git a/doc/design/file_mananger/pfs/mkdir.md b/doc/design/file_manager/pfs/mkdir.md similarity index 100% rename from doc/design/file_mananger/pfs/mkdir.md rename to doc/design/file_manager/pfs/mkdir.md diff --git a/doc/design/file_mananger/pfs/mv.md b/doc/design/file_manager/pfs/mv.md similarity index 100% rename from doc/design/file_mananger/pfs/mv.md rename to doc/design/file_manager/pfs/mv.md diff --git a/doc/design/file_mananger/pfs/pfs.md b/doc/design/file_manager/pfs/pfs.md similarity index 100% rename from doc/design/file_mananger/pfs/pfs.md rename to doc/design/file_manager/pfs/pfs.md diff --git a/doc/design/file_mananger/pfs/rm.md b/doc/design/file_manager/pfs/rm.md similarity index 100% rename from doc/design/file_mananger/pfs/rm.md rename to doc/design/file_manager/pfs/rm.md diff --git a/doc/design/file_mananger/pfs/sync.md b/doc/design/file_manager/pfs/sync.md similarity index 100% rename from doc/design/file_mananger/pfs/sync.md rename to doc/design/file_manager/pfs/sync.md diff --git a/doc/design/file_mananger/src/filemanager.graffle b/doc/design/file_manager/src/filemanager.graffle similarity index 100% rename from doc/design/file_mananger/src/filemanager.graffle rename to doc/design/file_manager/src/filemanager.graffle diff --git a/doc/design/file_mananger/src/filemanager.png b/doc/design/file_manager/src/filemanager.png similarity index 100% rename from doc/design/file_mananger/src/filemanager.png rename to doc/design/file_manager/src/filemanager.png -- GitLab From 6801c1b5945d35d1a2ddbb1e04408441d542f563 Mon Sep 17 00:00:00 2001 From: gongweibao Date: Fri, 12 May 2017 15:30:40 +0800 Subject: [PATCH 0284/3256] fix username --- doc/design/cluster_train/data_dispatch.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/doc/design/cluster_train/data_dispatch.md b/doc/design/cluster_train/data_dispatch.md index 5b70e802ba7..e7798b24f63 100644 --- a/doc/design/cluster_train/data_dispatch.md +++ b/doc/design/cluster_train/data_dispatch.md @@ -127,15 +127,15 @@ paddle pfs cp random_images-*-of-* /pfs/$DATACENTER/home/$USER/folder/ ``` # config file [datacenter_1] -username=wuyi -usercert=wuyi.pem -userkey=wuyi-key.pem +username=user +usercert=user.pem +userkey=user-key.pem endpoint=datacenter1.paddlepaddle.org [datacenter_2] -username=wuyi -usercert=wuyi.pem -userkey=wuyi-key.pem +username=user +usercert=user.pem +userkey=user-key.pem endpoint=datacenter2.paddlepaddle.org ``` ## TODO -- GitLab From 3f347ef539e5e6a81e5ba2747d2fc306b15f759d Mon Sep 17 00:00:00 2001 From: luotao02 Date: Fri, 12 May 2017 16:50:53 +0800 Subject: [PATCH 0285/3256] fix error cmake infomation --- paddle/utils/DynamicLoader.cpp | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/paddle/utils/DynamicLoader.cpp b/paddle/utils/DynamicLoader.cpp index 76cf3c30061..5aa1fe12d86 100644 --- a/paddle/utils/DynamicLoader.cpp +++ b/paddle/utils/DynamicLoader.cpp @@ -104,20 +104,9 @@ static inline void GetDsoHandleFromSearchPath(const std::string& search_root, CHECK(nullptr != *dso_handle) << "Failed to find dynamic library: " << dlPath << " (" << dlerror() << ") \n" << "Please specify its path correctly using " - "one of the following ways: \n" - - << "Method 1. set cuda and cudnn lib path at " - "runtime. " - << "http://www.paddlepaddle.org/doc/ui/" - "cmd_argument/" - "argument_outline.html \n" - << "For instance, issue command: paddle train " - "--use_gpu=1 " - << "--cuda_dir=/usr/local/cuda/lib64 " - "--cudnn_dir=/usr/local/cudnn/lib " - "...\n" - - << "Method 2. set environment variable " + "following ways: \n" + + << "Method. set environment variable " "LD_LIBRARY_PATH on Linux or " << "DYLD_LIBRARY_PATH on Mac OS. \n" << "For instance, issue command: export " -- GitLab From 20aef7febf0d33128da6d2c485d98eb1f6e83e2e Mon Sep 17 00:00:00 2001 From: luotao02 Date: Fri, 12 May 2017 17:49:41 +0800 Subject: [PATCH 0286/3256] faq --- doc/faq/index_cn.rst | 614 ++++++++++++++++++++++--------------------- 1 file changed, 313 insertions(+), 301 deletions(-) diff --git a/doc/faq/index_cn.rst b/doc/faq/index_cn.rst index df5e1722522..081e3a60a24 100644 --- a/doc/faq/index_cn.rst +++ b/doc/faq/index_cn.rst @@ -1,301 +1,313 @@ -#################### -FAQ -#################### - -.. contents:: - -1. 如何减少内存占用 ---------------------------------- - -神经网络的训练本身是一个非常消耗内存和显存的工作,经常会消耗数10GB的内存和数GB的显存。 -PaddlePaddle的内存占用主要分为如下几个方面\: - -* DataProvider缓冲池内存(只针对内存) -* 神经元激活内存(针对内存和显存) -* 参数内存 (针对内存和显存) -* 其他内存杂项 - -其中,其他内存杂项是指PaddlePaddle本身所用的一些内存,包括字符串分配,临时变量等等,暂不考虑在内。 - -减少DataProvider缓冲池内存 -++++++++++++++++++++++++++ - -PyDataProvider使用的是异步加载,同时在内存里直接随即选取数据来做Shuffle。即 - -.. graphviz:: - - digraph { - rankdir=LR; - 数据文件 -> 内存池 -> PaddlePaddle训练 - } - -所以,减小这个内存池即可减小内存占用,同时也可以加速开始训练前数据载入的过程。但是,这 -个内存池实际上决定了shuffle的粒度。所以,如果将这个内存池减小,又要保证数据是随机的, -那么最好将数据文件在每次读取之前做一次shuffle。可能的代码为 - -.. literalinclude:: src/reduce_min_pool_size.py - -这样做可以极大的减少内存占用,并且可能会加速训练过程,详细文档参考 :ref:`api_pydataprovider2` 。 - -神经元激活内存 -++++++++++++++ - -神经网络在训练的时候,会对每一个激活暂存一些数据,如神经元激活值等。 -在反向传递的时候,这些数据会被用来更新参数。这些数据使用的内存主要和两个参数有关系, -一是batch size,另一个是每条序列(Sequence)长度。所以,其实也是和每个mini-batch中包含 -的时间步信息成正比。 - -所以做法可以有两种: - -* 减小batch size。 即在网络配置中 :code:`settings(batch_size=1000)` 设置成一个小一些的值。但是batch size本身是神经网络的超参数,减小batch size可能会对训练结果产生影响。 -* 减小序列的长度,或者直接扔掉非常长的序列。比如,一个数据集大部分序列长度是100-200, - 但是突然有一个10000长的序列,就很容易导致内存超限,特别是在LSTM等RNN中。 - -参数内存 -++++++++ - -PaddlePaddle支持非常多的优化算法(Optimizer),不同的优化算法需要使用不同大小的内存。 -例如使用 :code:`adadelta` 算法,则需要使用等于权重参数规模大约5倍的内存。举例,如果参数保存下来的模型目录 -文件为 :code:`100M`, 那么该优化算法至少需要 :code:`500M` 的内存。 - -可以考虑使用一些优化算法,例如 :code:`momentum`。 - -2. 如何加速PaddlePaddle的训练速度 ---------------------------------- - -加速PaddlePaddle训练可以考虑从以下几个方面\: - -* 减少数据载入的耗时 -* 加速训练速度 -* 利用分布式训练驾驭更多的计算资源 - -减少数据载入的耗时 -++++++++++++++++++ - -使用\ :code:`pydataprovider`\ 时,可以减少缓存池的大小,同时设置内存缓存功能,即可以极大的加速数据载入流程。 -:code:`DataProvider` 缓存池的减小,和之前减小通过减小缓存池来减小内存占用的原理一致。 - -.. literalinclude:: src/reduce_min_pool_size.py - -同时 :code:`@provider` 接口有一个 :code:`cache` 参数来控制缓存方法,将其设置成 :code:`CacheType.CACHE_PASS_IN_MEM` 的话,会将第一个 :code:`pass` (过完所有训练数据即为一个pass)生成的数据缓存在内存里,在之后的 :code:`pass` 中,不会再从 :code:`python` 端读取数据,而是直接从内存的缓存里读取数据。这也会极大减少数据读入的耗时。 - - -加速训练速度 -++++++++++++ - -PaddlePaddle支持Sparse的训练,sparse训练需要训练特征是 :code:`sparse_binary_vector` 、 :code:`sparse_vector` 、或者 :code:`integer_value` 的任一一种。同时,与这个训练数据交互的Layer,需要将其Parameter设置成 sparse 更新模式,即设置 :code:`sparse_update=True` - -这里使用简单的 :code:`word2vec` 训练语言模型距离,具体使用方法为\: - -使用一个词前两个词和后两个词,来预测这个中间的词。这个任务的DataProvider为\: - -.. literalinclude:: src/word2vec_dataprovider.py - -这个任务的配置为\: - -.. literalinclude:: src/word2vec_config.py - - -利用更多的计算资源 -++++++++++++++++++ - -利用更多的计算资源可以分为一下几个方式来进行\: - -* 单机CPU训练 - - * 使用多线程训练。设置命令行参数 :code:`trainer_count`。 - -* 单机GPU训练 - - * 使用显卡训练。设置命令行参数 :code:`use_gpu`。 - * 使用多块显卡训练。设置命令行参数 :code:`use_gpu` 和 :code:`trainer_count` 。 - -* 多机训练 - - * 请参考 :ref:`cluster_train` 。 - - -3. 遇到“非法指令”或者是“illegal instruction” --------------------------------------------- - -PaddlePaddle使用avx SIMD指令提高cpu执行效率,因此错误的使用二进制发行版可能会导致这种错误,请选择正确的版本。 - -4. 如何选择SGD算法的学习率 --------------------------- - -在采用sgd/async_sgd进行训练时,一个重要的问题是选择正确的learning_rate。如果learning_rate太大,那么训练有可能不收敛,如果learning_rate太小,那么收敛可能很慢,导致训练时间过长。 - -通常做法是从一个比较大的learning_rate开始试,如果不收敛,那减少学习率10倍继续试验,直到训练收敛为止。那么如何判断训练不收敛呢?可以估计出如果模型采用不变的输出最小的cost0是多少。 - -如果训练过程的的cost明显高于这个常数输出的cost,那么我们可以判断为训练不收敛。举一个例子,假如我们是三分类问题,采用multi-class-cross-entropy作为cost,数据中0,1,2三类的比例为 :code:`0.2, 0.5, 0.3` , 那么常数输出所能达到的最小cost是 :code:`-(0.2*log(0.2)+0.5*log(0.5)+0.3*log(0.3))=1.03` 。如果训练一个pass(或者更早)后,cost还大于这个数,那么可以认为训练不收敛,应该降低学习率。 - - -5. 如何初始化参数 ------------------ - -默认情况下,PaddlePaddle使用均值0,标准差为 :math:`\frac{1}{\sqrt{d}}` 来初始化参数。其中 :math:`d` 为参数矩阵的宽度。这种初始化方式在一般情况下不会产生很差的结果。如果用户想要自定义初始化方式,PaddlePaddle目前提供两种参数初始化的方式\: - -* 高斯分布。将 :code:`param_attr` 设置成 :code:`param_attr=ParamAttr(initial_mean=0.0, initial_std=1.0)` -* 均匀分布。将 :code:`param_attr` 设置成 :code:`param_attr=ParamAttr(initial_max=1.0, initial_min=-1.0)` - -比如设置一个全连接层的参数初始化方式和bias初始化方式,可以使用如下代码。 - -.. code-block:: python - - hidden = fc_layer(input=ipt, param_attr=ParamAttr(initial_max=1.0, initial_min=-1.0), - bias_attr=ParamAttr(initial_mean=1.0, initial_std=0.0)) - -上述代码将bias全部初始化为1.0, 同时将参数初始化为 :code:`[1.0, -1.0]` 的均匀分布。 - -6. 如何共享参数 ---------------- - -PaddlePaddle的参数使用名字 :code:`name` 作为参数的ID,相同名字的参数,会共享参数。设置参数的名字,可以使用 :code:`ParamAttr(name="YOUR_PARAM_NAME")` 来设置。更方便的设置方式,是使得要共享的参数使用同样的 :code:`ParamAttr` 对象。 - -简单的全连接网络,参数共享的配置示例为\: - -.. literalinclude:: ../../python/paddle/trainer_config_helpers/tests/configs/shared_fc.py - -这里 :code:`hidden_a` 和 :code:`hidden_b` 使用了同样的parameter和bias。并且softmax层的两个输入也使用了同样的参数 :code:`softmax_param`。 - -7. \*-cp27mu-linux_x86_64.whl is not a supported wheel on this platform. ------------------------------------------------------------------------- - -出现这个问题的主要原因是,系统编译wheel包的时候,使用的 :code:`wheel` 包是最新的, -而系统中的 :code:`pip` 包比较老。具体的解决方法是,更新 :code:`pip` 包并重新编译PaddlePaddle。 -更新 :code:`pip` 包的方法是\: - -.. code-block:: bash - - pip install --upgrade pip - -8. python相关的单元测试都过不了 --------------------------------- - -如果出现以下python相关的单元测试都过不了的情况: - -.. code-block:: bash - - 24 - test_PyDataProvider (Failed) - 26 - test_RecurrentGradientMachine (Failed) - 27 - test_NetworkCompare (Failed) - 28 - test_PyDataProvider2 (Failed) - 32 - test_Prediction (Failed) - 33 - test_Compare (Failed) - 34 - test_Trainer (Failed) - 35 - test_TrainerOnePass (Failed) - 36 - test_CompareTwoNets (Failed) - 37 - test_CompareTwoOpts (Failed) - 38 - test_CompareSparse (Failed) - 39 - test_recurrent_machine_generation (Failed) - 40 - test_PyDataProviderWrapper (Failed) - 41 - test_config_parser (Failed) - 42 - test_swig_api (Failed) - 43 - layers_test (Failed) - -并且查询PaddlePaddle单元测试的日志,提示: - -.. code-block:: bash - - paddle package is already in your PYTHONPATH. But unittest need a clean environment. - Please uninstall paddle package before start unittest. Try to 'pip uninstall paddle'. - -解决办法是: - -* 卸载PaddlePaddle包 :code:`pip uninstall paddle`, 清理掉老旧的PaddlePaddle安装包,使得单元测试有一个干净的环境。如果PaddlePaddle包已经在python的site-packages里面,单元测试会引用site-packages里面的python包,而不是源码目录里 :code:`/python` 目录下的python包。同时,即便设置 :code:`PYTHONPATH` 到 :code:`/python` 也没用,因为python的搜索路径是优先已经安装的python包。 - - -9. 运行Docker GPU镜像出现 "CUDA driver version is insufficient" ----------------------------------------------------------------- - -用户在使用PaddlePaddle GPU的Docker镜像的时候,常常出现 `Cuda Error: CUDA driver version is insufficient for CUDA runtime version`, 原因在于没有把机器上CUDA相关的驱动和库映射到容器内部。 -具体的解决方法是: - -.. code-block:: bash - - $ export CUDA_SO="$(\ls usr/lib64/libcuda* | xargs -I{} echo '-v {}:{}') $(\ls /usr/lib64/libnvidia* | xargs -I{} echo '-v {}:{}')" - $ export DEVICES=$(\ls /dev/nvidia* | xargs -I{} echo '--device {}:{}') - $ docker run ${CUDA_SO} ${DEVICES} -it paddledev/paddlepaddle:latest-gpu - -更多关于Docker的安装与使用, 请参考 `PaddlePaddle Docker 文档 `_ 。 - - -10. CMake源码编译, 找到的PythonLibs和PythonInterp版本不一致 ----------------------------------------------------------------- - -这是目前CMake寻找Python的逻辑存在缺陷,如果系统安装了多个Python版本,CMake找到的Python库和Python解释器版本可能有不一致现象,导致编译PaddlePaddle失败。正确的解决方法是, -用户强制指定特定的Python版本,具体操作如下: - - .. code-block:: bash - - cmake .. -DPYTHON_EXECUTABLE= -DPYTHON_LIBRARY= -DPYTHON_INCLUDE_DIR= - -用户需要指定本机上Python的路径:````, ````, ```` - -10. A protocol message was rejected because it was too big ----------------------------------------------------------- - -如果在训练NLP相关模型时,出现以下错误: - -.. code-block:: bash - - [libprotobuf ERROR google/protobuf/io/coded_stream.cc:171] A protocol message was rejected because it was too big (more than 67108864 bytes). To increase the limit (or to disable these warnings), see CodedInputStream::SetTotalBytesLimit() in google/protobuf/io/coded_stream.h. - F1205 14:59:50.295174 14703 TrainerConfigHelper.cpp:59] Check failed: m->conf.ParseFromString(configProtoStr) - -可能的原因是:传给dataprovider的某一个args过大,一般是由于直接传递大字典导致的。错误的define_py_data_sources2类似: - -.. code-block:: python - - src_dict = dict() - for line_count, line in enumerate(open(src_dict_path, "r")): - src_dict[line.strip()] = line_count - - define_py_data_sources2( - train_list, - test_list, - module="dataprovider", - obj="process", - args={"src_dict": src_dict}) - -解决方案是:将字典的地址作为args传给dataprovider,然后在dataprovider里面根据该地址加载字典。即define_py_data_sources2应改为: - -.. code-block:: python - - define_py_data_sources2( - train_list, - test_list, - module="dataprovider", - obj="process", - args={"src_dict_path": src_dict_path}) - -完整源码可参考 `seqToseq `_ 示例。 - -11. 如何指定GPU设备 -------------------- - -例如机器上有4块GPU,编号从0开始,指定使用2、3号GPU: - -* 方式1:通过 `CUDA_VISIBLE_DEVICES `_ 环境变量来指定特定的GPU。 - -.. code-block:: bash - - env CUDA_VISIBLE_DEVICES=2,3 paddle train --use_gpu=true --trainer_count=2 - -* 方式2:通过命令行参数 ``--gpu_id`` 指定。 - -.. code-block:: bash - - paddle train --use_gpu=true --trainer_count=2 --gpu_id=2 - - -12. 训练过程中出现 :code:`Floating point exception`, 训练因此退出怎么办? ------------------------------------------------------------------------- - -Paddle二进制在运行时捕获了浮点数异常,只要出现浮点数异常(即训练过程中出现NaN或者Inf),立刻退出。浮点异常通常的原因是浮点数溢出、除零等问题。 -主要原因包括两个方面: - -* 训练过程中参数或者训练过程中的梯度尺度过大,导致参数累加,乘除等时候,导致了浮点数溢出。 -* 模型一直不收敛,发散到了一个数值特别大的地方。 -* 训练数据有问题,导致参数收敛到了一些奇异的情况。或者输入数据尺度过大,有些特征的取值达到数百万,这时进行矩阵乘法运算就可能导致浮点数溢出。 - -主要的解决办法是减小学习律或者对数据进行归一化处理。 +#################### +FAQ +#################### + +.. contents:: + +1. 如何减少内存占用 +--------------------------------- + +神经网络的训练本身是一个非常消耗内存和显存的工作,经常会消耗数10GB的内存和数GB的显存。 +PaddlePaddle的内存占用主要分为如下几个方面\: + +* DataProvider缓冲池内存(只针对内存) +* 神经元激活内存(针对内存和显存) +* 参数内存 (针对内存和显存) +* 其他内存杂项 + +其中,其他内存杂项是指PaddlePaddle本身所用的一些内存,包括字符串分配,临时变量等等,暂不考虑在内。 + +减少DataProvider缓冲池内存 +++++++++++++++++++++++++++ + +PyDataProvider使用的是异步加载,同时在内存里直接随即选取数据来做Shuffle。即 + +.. graphviz:: + + digraph { + rankdir=LR; + 数据文件 -> 内存池 -> PaddlePaddle训练 + } + +所以,减小这个内存池即可减小内存占用,同时也可以加速开始训练前数据载入的过程。但是,这 +个内存池实际上决定了shuffle的粒度。所以,如果将这个内存池减小,又要保证数据是随机的, +那么最好将数据文件在每次读取之前做一次shuffle。可能的代码为 + +.. literalinclude:: src/reduce_min_pool_size.py + +这样做可以极大的减少内存占用,并且可能会加速训练过程,详细文档参考 :ref:`api_pydataprovider2` 。 + +神经元激活内存 +++++++++++++++ + +神经网络在训练的时候,会对每一个激活暂存一些数据,如神经元激活值等。 +在反向传递的时候,这些数据会被用来更新参数。这些数据使用的内存主要和两个参数有关系, +一是batch size,另一个是每条序列(Sequence)长度。所以,其实也是和每个mini-batch中包含 +的时间步信息成正比。 + +所以做法可以有两种: + +* 减小batch size。 即在网络配置中 :code:`settings(batch_size=1000)` 设置成一个小一些的值。但是batch size本身是神经网络的超参数,减小batch size可能会对训练结果产生影响。 +* 减小序列的长度,或者直接扔掉非常长的序列。比如,一个数据集大部分序列长度是100-200, + 但是突然有一个10000长的序列,就很容易导致内存超限,特别是在LSTM等RNN中。 + +参数内存 +++++++++ + +PaddlePaddle支持非常多的优化算法(Optimizer),不同的优化算法需要使用不同大小的内存。 +例如使用 :code:`adadelta` 算法,则需要使用等于权重参数规模大约5倍的内存。举例,如果参数保存下来的模型目录 +文件为 :code:`100M`, 那么该优化算法至少需要 :code:`500M` 的内存。 + +可以考虑使用一些优化算法,例如 :code:`momentum`。 + +2. 如何加速PaddlePaddle的训练速度 +--------------------------------- + +加速PaddlePaddle训练可以考虑从以下几个方面\: + +* 减少数据载入的耗时 +* 加速训练速度 +* 利用分布式训练驾驭更多的计算资源 + +减少数据载入的耗时 +++++++++++++++++++ + +使用\ :code:`pydataprovider`\ 时,可以减少缓存池的大小,同时设置内存缓存功能,即可以极大的加速数据载入流程。 +:code:`DataProvider` 缓存池的减小,和之前减小通过减小缓存池来减小内存占用的原理一致。 + +.. literalinclude:: src/reduce_min_pool_size.py + +同时 :code:`@provider` 接口有一个 :code:`cache` 参数来控制缓存方法,将其设置成 :code:`CacheType.CACHE_PASS_IN_MEM` 的话,会将第一个 :code:`pass` (过完所有训练数据即为一个pass)生成的数据缓存在内存里,在之后的 :code:`pass` 中,不会再从 :code:`python` 端读取数据,而是直接从内存的缓存里读取数据。这也会极大减少数据读入的耗时。 + + +加速训练速度 +++++++++++++ + +PaddlePaddle支持Sparse的训练,sparse训练需要训练特征是 :code:`sparse_binary_vector` 、 :code:`sparse_vector` 、或者 :code:`integer_value` 的任一一种。同时,与这个训练数据交互的Layer,需要将其Parameter设置成 sparse 更新模式,即设置 :code:`sparse_update=True` + +这里使用简单的 :code:`word2vec` 训练语言模型距离,具体使用方法为\: + +使用一个词前两个词和后两个词,来预测这个中间的词。这个任务的DataProvider为\: + +.. literalinclude:: src/word2vec_dataprovider.py + +这个任务的配置为\: + +.. literalinclude:: src/word2vec_config.py + + +利用更多的计算资源 +++++++++++++++++++ + +利用更多的计算资源可以分为一下几个方式来进行\: + +* 单机CPU训练 + + * 使用多线程训练。设置命令行参数 :code:`trainer_count`。 + +* 单机GPU训练 + + * 使用显卡训练。设置命令行参数 :code:`use_gpu`。 + * 使用多块显卡训练。设置命令行参数 :code:`use_gpu` 和 :code:`trainer_count` 。 + +* 多机训练 + + * 请参考 :ref:`cluster_train` 。 + + +3. 遇到“非法指令”或者是“illegal instruction” +-------------------------------------------- + +PaddlePaddle使用avx SIMD指令提高cpu执行效率,因此错误的使用二进制发行版可能会导致这种错误,请选择正确的版本。 + +4. 如何选择SGD算法的学习率 +-------------------------- + +在采用sgd/async_sgd进行训练时,一个重要的问题是选择正确的learning_rate。如果learning_rate太大,那么训练有可能不收敛,如果learning_rate太小,那么收敛可能很慢,导致训练时间过长。 + +通常做法是从一个比较大的learning_rate开始试,如果不收敛,那减少学习率10倍继续试验,直到训练收敛为止。那么如何判断训练不收敛呢?可以估计出如果模型采用不变的输出最小的cost0是多少。 + +如果训练过程的的cost明显高于这个常数输出的cost,那么我们可以判断为训练不收敛。举一个例子,假如我们是三分类问题,采用multi-class-cross-entropy作为cost,数据中0,1,2三类的比例为 :code:`0.2, 0.5, 0.3` , 那么常数输出所能达到的最小cost是 :code:`-(0.2*log(0.2)+0.5*log(0.5)+0.3*log(0.3))=1.03` 。如果训练一个pass(或者更早)后,cost还大于这个数,那么可以认为训练不收敛,应该降低学习率。 + + +5. 如何初始化参数 +----------------- + +默认情况下,PaddlePaddle使用均值0,标准差为 :math:`\frac{1}{\sqrt{d}}` 来初始化参数。其中 :math:`d` 为参数矩阵的宽度。这种初始化方式在一般情况下不会产生很差的结果。如果用户想要自定义初始化方式,PaddlePaddle目前提供两种参数初始化的方式\: + +* 高斯分布。将 :code:`param_attr` 设置成 :code:`param_attr=ParamAttr(initial_mean=0.0, initial_std=1.0)` +* 均匀分布。将 :code:`param_attr` 设置成 :code:`param_attr=ParamAttr(initial_max=1.0, initial_min=-1.0)` + +比如设置一个全连接层的参数初始化方式和bias初始化方式,可以使用如下代码。 + +.. code-block:: python + + hidden = fc_layer(input=ipt, param_attr=ParamAttr(initial_max=1.0, initial_min=-1.0), + bias_attr=ParamAttr(initial_mean=1.0, initial_std=0.0)) + +上述代码将bias全部初始化为1.0, 同时将参数初始化为 :code:`[1.0, -1.0]` 的均匀分布。 + +6. 如何共享参数 +--------------- + +PaddlePaddle的参数使用名字 :code:`name` 作为参数的ID,相同名字的参数,会共享参数。设置参数的名字,可以使用 :code:`ParamAttr(name="YOUR_PARAM_NAME")` 来设置。更方便的设置方式,是使得要共享的参数使用同样的 :code:`ParamAttr` 对象。 + +简单的全连接网络,参数共享的配置示例为\: + +.. literalinclude:: ../../python/paddle/trainer_config_helpers/tests/configs/shared_fc.py + +这里 :code:`hidden_a` 和 :code:`hidden_b` 使用了同样的parameter和bias。并且softmax层的两个输入也使用了同样的参数 :code:`softmax_param`。 + +7. \*-cp27mu-linux_x86_64.whl is not a supported wheel on this platform. +------------------------------------------------------------------------ + +出现这个问题的主要原因是,系统编译wheel包的时候,使用的 :code:`wheel` 包是最新的, +而系统中的 :code:`pip` 包比较老。具体的解决方法是,更新 :code:`pip` 包并重新编译PaddlePaddle。 +更新 :code:`pip` 包的方法是\: + +.. code-block:: bash + + pip install --upgrade pip + +8. python相关的单元测试都过不了 +-------------------------------- + +如果出现以下python相关的单元测试都过不了的情况: + +.. code-block:: bash + + 24 - test_PyDataProvider (Failed) + 26 - test_RecurrentGradientMachine (Failed) + 27 - test_NetworkCompare (Failed) + 28 - test_PyDataProvider2 (Failed) + 32 - test_Prediction (Failed) + 33 - test_Compare (Failed) + 34 - test_Trainer (Failed) + 35 - test_TrainerOnePass (Failed) + 36 - test_CompareTwoNets (Failed) + 37 - test_CompareTwoOpts (Failed) + 38 - test_CompareSparse (Failed) + 39 - test_recurrent_machine_generation (Failed) + 40 - test_PyDataProviderWrapper (Failed) + 41 - test_config_parser (Failed) + 42 - test_swig_api (Failed) + 43 - layers_test (Failed) + +并且查询PaddlePaddle单元测试的日志,提示: + +.. code-block:: bash + + paddle package is already in your PYTHONPATH. But unittest need a clean environment. + Please uninstall paddle package before start unittest. Try to 'pip uninstall paddle'. + +解决办法是: + +* 卸载PaddlePaddle包 :code:`pip uninstall paddle`, 清理掉老旧的PaddlePaddle安装包,使得单元测试有一个干净的环境。如果PaddlePaddle包已经在python的site-packages里面,单元测试会引用site-packages里面的python包,而不是源码目录里 :code:`/python` 目录下的python包。同时,即便设置 :code:`PYTHONPATH` 到 :code:`/python` 也没用,因为python的搜索路径是优先已经安装的python包。 + + +9. 运行Docker GPU镜像出现 "CUDA driver version is insufficient" +---------------------------------------------------------------- + +用户在使用PaddlePaddle GPU的Docker镜像的时候,常常出现 `Cuda Error: CUDA driver version is insufficient for CUDA runtime version`, 原因在于没有把机器上CUDA相关的驱动和库映射到容器内部。 +具体的解决方法是: + +.. code-block:: bash + + $ export CUDA_SO="$(\ls usr/lib64/libcuda* | xargs -I{} echo '-v {}:{}') $(\ls /usr/lib64/libnvidia* | xargs -I{} echo '-v {}:{}')" + $ export DEVICES=$(\ls /dev/nvidia* | xargs -I{} echo '--device {}:{}') + $ docker run ${CUDA_SO} ${DEVICES} -it paddledev/paddlepaddle:latest-gpu + +更多关于Docker的安装与使用, 请参考 `PaddlePaddle Docker 文档 `_ 。 + + +10. CMake源码编译, 找到的PythonLibs和PythonInterp版本不一致 +---------------------------------------------------------------- + +这是目前CMake寻找Python的逻辑存在缺陷,如果系统安装了多个Python版本,CMake找到的Python库和Python解释器版本可能有不一致现象,导致编译PaddlePaddle失败。正确的解决方法是, +用户强制指定特定的Python版本,具体操作如下: + + .. code-block:: bash + + cmake .. -DPYTHON_EXECUTABLE= -DPYTHON_LIBRARY= -DPYTHON_INCLUDE_DIR= + +用户需要指定本机上Python的路径:````, ````, ```` + +11. CMake源码编译,Paddle版本号为0.0.0 +-------------------------------------- + +如果运行 :code:`paddle version`, 出现 :code:`PaddlePaddle 0.0.0`;或者运行 :code:`cmake ..`,出现 + + .. code-block:: bash + + CMake Warning at cmake/version.cmake:20 (message): + Cannot add paddle version from git tag + +那么用户需要拉取所有的远程分支到本机,命令为 :code:`git fetch upstream`,然后重新cmake即可。 + +12. A protocol message was rejected because it was too big +---------------------------------------------------------- + +如果在训练NLP相关模型时,出现以下错误: + +.. code-block:: bash + + [libprotobuf ERROR google/protobuf/io/coded_stream.cc:171] A protocol message was rejected because it was too big (more than 67108864 bytes). To increase the limit (or to disable these warnings), see CodedInputStream::SetTotalBytesLimit() in google/protobuf/io/coded_stream.h. + F1205 14:59:50.295174 14703 TrainerConfigHelper.cpp:59] Check failed: m->conf.ParseFromString(configProtoStr) + +可能的原因是:传给dataprovider的某一个args过大,一般是由于直接传递大字典导致的。错误的define_py_data_sources2类似: + +.. code-block:: python + + src_dict = dict() + for line_count, line in enumerate(open(src_dict_path, "r")): + src_dict[line.strip()] = line_count + + define_py_data_sources2( + train_list, + test_list, + module="dataprovider", + obj="process", + args={"src_dict": src_dict}) + +解决方案是:将字典的地址作为args传给dataprovider,然后在dataprovider里面根据该地址加载字典。即define_py_data_sources2应改为: + +.. code-block:: python + + define_py_data_sources2( + train_list, + test_list, + module="dataprovider", + obj="process", + args={"src_dict_path": src_dict_path}) + +完整源码可参考 `seqToseq `_ 示例。 + +13. 如何指定GPU设备 +------------------- + +例如机器上有4块GPU,编号从0开始,指定使用2、3号GPU: + +* 方式1:通过 `CUDA_VISIBLE_DEVICES `_ 环境变量来指定特定的GPU。 + +.. code-block:: bash + + env CUDA_VISIBLE_DEVICES=2,3 paddle train --use_gpu=true --trainer_count=2 + +* 方式2:通过命令行参数 ``--gpu_id`` 指定。 + +.. code-block:: bash + + paddle train --use_gpu=true --trainer_count=2 --gpu_id=2 + + +14. 训练过程中出现 :code:`Floating point exception`, 训练因此退出怎么办? +------------------------------------------------------------------------ + +Paddle二进制在运行时捕获了浮点数异常,只要出现浮点数异常(即训练过程中出现NaN或者Inf),立刻退出。浮点异常通常的原因是浮点数溢出、除零等问题。 +主要原因包括两个方面: + +* 训练过程中参数或者训练过程中的梯度尺度过大,导致参数累加,乘除等时候,导致了浮点数溢出。 +* 模型一直不收敛,发散到了一个数值特别大的地方。 +* 训练数据有问题,导致参数收敛到了一些奇异的情况。或者输入数据尺度过大,有些特征的取值达到数百万,这时进行矩阵乘法运算就可能导致浮点数溢出。 + +主要的解决办法是减小学习律或者对数据进行归一化处理。 -- GitLab From df00e2928d1f69f55ef6aba1a91ea820a2c2d612 Mon Sep 17 00:00:00 2001 From: luotao02 Date: Fri, 12 May 2017 17:51:05 +0800 Subject: [PATCH 0287/3256] follow comments --- paddle/utils/DynamicLoader.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/paddle/utils/DynamicLoader.cpp b/paddle/utils/DynamicLoader.cpp index 5aa1fe12d86..5604a90038b 100644 --- a/paddle/utils/DynamicLoader.cpp +++ b/paddle/utils/DynamicLoader.cpp @@ -115,9 +115,7 @@ static inline void GetDsoHandleFromSearchPath(const std::string& search_root, << "Note: After Mac OS 10.11, using the " "DYLD_LIBRARY_PATH is impossible " << "unless System Integrity Protection (SIP) " - "is disabled. However, " - "method 1 " - << "always work well."; + "is disabled."; } void GetCublasDsoHandle(void** dso_handle) { -- GitLab From 60f69c2b0e960d9f60649e1e83342933273768e1 Mon Sep 17 00:00:00 2001 From: gongweibao Date: Fri, 12 May 2017 17:53:31 +0800 Subject: [PATCH 0288/3256] fix by comments --- doc/design/file_manager/README.md | 6 ++-- doc/design/file_manager/pfs/cp.md | 46 +++++++++++++++------------- doc/design/file_manager/pfs/ls.md | 33 ++++++-------------- doc/design/file_manager/pfs/mkdir.md | 12 ++------ doc/design/file_manager/pfs/mv.md | 30 ++++++++++-------- doc/design/file_manager/pfs/pfs.md | 6 ++-- doc/design/file_manager/pfs/rm.md | 35 +++++++-------------- doc/design/file_manager/pfs/sync.md | 40 ++++++++++-------------- 8 files changed, 88 insertions(+), 120 deletions(-) diff --git a/doc/design/file_manager/README.md b/doc/design/file_manager/README.md index 9d75089257f..2fe874581e7 100644 --- a/doc/design/file_manager/README.md +++ b/doc/design/file_manager/README.md @@ -3,7 +3,7 @@ 在本文档中,我们设计说明了名为FileManager系统,方便用户管理存放到PaddlePaddle Cloud上的文件。 主要功能包括: -- 提供常用的命令行文件管理命令管理文件 +- 提供常用的命令行管理命令管理文件和目录 - 支持的命令在[Here](./pfs/pfs.md) - 支持大文件的断点上传、下载 @@ -27,7 +27,7 @@ - 用Golang写,可以跨平台执行 - 双向验证 - PFSClient需要和Ingress之间做双向验证[tls](#tls),所有用户需要首先在`cloud.paddlepaddle.org`上注册一下,申请用户空间,并且把系统生成的Key、CRT、CA下载到本地,然后才能使用PFSClient。 + PFSClient需要和Ingress之间做双向验证[tls](#tls),所以用户需要首先在`cloud.paddlepaddle.org`上注册一下,申请用户空间,并且把系统生成的Key、CRT、CA下载到本地,然后才能使用PFSClient。 ### Ingress - 功能: @@ -43,7 +43,7 @@ FileServer是一个用GoRPC写的HTTPServer,提供[RESTful API](./RESTAPI.md) ## 文件传输优化 ### 分块文件传输 -用户文件可能是比较大的,上传到Cloud或者下载到本地的时间可能比较长,而且在传输的过程中也可能出现网络不稳定的情况。为了应对以上的问题,我们提出了Chunk的概念,一个Chunk由所在的文件偏移、数据、数据长度及校验值组成。文件数据内容的上传和下载都是都过Chunk的操作来实现的。由于Chunk比较小(默认256K),完成一个传输动作完成的时间也比较短,不容易出错。PFSClient在传输完毕最后一个Chunk的时候检查desttination文件的MD5值是否和source文件一致。 +用户文件可能是比较大的,上传到Cloud或者下载到本地的时间可能比较长,而且在传输的过程中也可能出现网络不稳定的情况。为了应对以上的问题,我们提出了Chunk的概念,一个Chunk由所在的文件偏移、数据、数据长度及校验值组成。文件数据内容的上传和下载都是都过Chunk的操作来实现的。由于Chunk比较小(默认256K),完成一个传输动作完成的时间也比较短,不容易出错。PFSClient在传输完毕最后一个Chunk的时候检查destination文件的MD5值是否和source文件一致。 一个典型的Chunk如下所示: diff --git a/doc/design/file_manager/pfs/cp.md b/doc/design/file_manager/pfs/cp.md index 605c0b7c8bf..1f1670e8827 100644 --- a/doc/design/file_manager/pfs/cp.md +++ b/doc/design/file_manager/pfs/cp.md @@ -1,41 +1,45 @@ # Name -cp - copy files and directories +cp - copy files # Synopsis -` cp [OPTION]... - or or ` +``` +cp [-r] [-f | -n] [-v] [--preserve--links] +cp [-r] [-f | -n] [-v] [--preserve--links] ... +cp [-r] [-f | -n] [-v] [--preserve--links] +cp [-r] [-f | -n] [-v] [--preserve--links] ... +cp [-r] [-f | -n] [-v] [--preserve--links] +cp [-r] [-f | -n] [-v] [--preserve--links] ... +``` # Description +``` +The following options are available: + +-r + Copy directories recursively + +-f + Do not prompt for confirmation before overwriting the destination path. (The -f option overrides previous -n options.) + +-n + Do not overwrite an existing file. (The -n option overrides previous -f options.) + +-v + Cause cp to be verbose, showing files after they are copied. -``` --preserve--links Reserve links when copy links - --R, -r, --recursive - Copy directories recursively ``` # Examples - The following command copies a single file to pfs ``` -paddle pfs cp ./text1.txt /pfs/mydir/text1.txt -``` - -Output - -``` -upload ./text1.txt to /pfs/mydir/text1.txt +paddle pfs cp ./text1.txt /pfs/$DATACENTER/home/$USER/text1.txt ``` - The following command copies pfs file to a local file ``` -paddle pfs cp /pfs/mydir/text1.txt ./text1.txt -``` - -Output - -``` -download /pfs/mydir/text1.txt to ./text1.txt +paddle pfs cp /pfs/$DATACENTER/home/$USER/text1.txt ./text1.txt ``` diff --git a/doc/design/file_manager/pfs/ls.md b/doc/design/file_manager/pfs/ls.md index ab254cfc5d9..0db163e08bd 100644 --- a/doc/design/file_manager/pfs/ls.md +++ b/doc/design/file_manager/pfs/ls.md @@ -1,42 +1,27 @@ # Name -ls - list directory contents or a file attributes +ls - list directory(ies)'s contents or file(s)'s attributes # Synopsis -` ls [OPTION]... ` +`ls [-r] ...` # Description -``` --R, -r, --recursive - Copy directories recursively - ---page-size (integer) - The number of results to return in each response to a list operation. The default value is 1000 (the maximum allowed). Using a lower value may help if operation time out. +``` +The following options are available: + +-r + List directory(ies) recursively ``` # Examples - The following command lists a single file ``` -paddle pfs ls /pfs/mydir/text1.txt -``` - -Output - -``` -2017-05-5 17:04:30 text1.txt +paddle pfs ls /pfs/$DATACENTER/home/$USER/text1.txt ``` - The following command lists directory contents ``` -paddle pfs ls /pfs/mydir -``` - -Output - -``` -2017-05-5 17:04:30 text1.txt -2017-05-5 17:04:30 text2.txt -... +paddle pfs ls / /pfs/$DATACENTER/home/$USER/folder ``` diff --git a/doc/design/file_manager/pfs/mkdir.md b/doc/design/file_manager/pfs/mkdir.md index c11dadd61f5..3ca51704027 100644 --- a/doc/design/file_manager/pfs/mkdir.md +++ b/doc/design/file_manager/pfs/mkdir.md @@ -2,18 +2,12 @@ mkdir - mkdir directory(ies) # Synopsis -`mkdir [OPTION]... ...` +`mkdir ...` # Description -Create the pfs directory(ies), if they do not already exist. And create intermediate directories as required +Create the pfs directory(ies), if it(they) does(do) not already exist. And create intermediate directories as required. # Examples ``` -paddle pfs mkdir /pfs/mydir1 -``` - -Output - -``` -make directory /pfs/mydir1 +paddle pfs mkdir /pfs/$DATACENTER/home/$USER/folder ``` diff --git a/doc/design/file_manager/pfs/mv.md b/doc/design/file_manager/pfs/mv.md index 3929d43394a..01b795be02c 100644 --- a/doc/design/file_manager/pfs/mv.md +++ b/doc/design/file_manager/pfs/mv.md @@ -1,28 +1,34 @@ # Name -mv - move (rename) files or directories +mv - move (rename) files # Synopsis -If destination already exist, please [rm](rm.md) it first. - ``` -mv [OPTION]... - or or +mv [-f | -n] [-v] +mv [-f | -n] [-v] ... +mv [-f | -n] [-v] +mv [-f | -n] [-v] ... +mv [-f | -n] [-v] +mv [-f | -n] [-v] ... ``` # Description -``` ``` +The following options are available: -# Examples -- The following command move a single file to pfs +-f + Do not prompt for confirmation before overwriting the destination path. (The -f option overrides previous -n options.) -``` -paddle pfs mv ./text1.txt /pfs/mydirectory/test1.txt +-n + Do not overwrite an existing file. (The -n option overrides previous -f options.) + +-v + Cause mv to be verbose, showing files after they are moved. ``` -Output +# Examples +- The following command moves a single file to pfs ``` -move ./text1.txt /pfs/mydirectory/test1.txt +paddle pfs mv ./text1.txt /pfs/$DATACENTER/home/$USER/text1.txt ``` diff --git a/doc/design/file_manager/pfs/pfs.md b/doc/design/file_manager/pfs/pfs.md index c23ffc74791..e26fc1095d2 100644 --- a/doc/design/file_manager/pfs/pfs.md +++ b/doc/design/file_manager/pfs/pfs.md @@ -1,7 +1,7 @@ # PFS Client ## Description -The `pfs` command is a Command Line Interface to manage your files on Paddle Cloud +The `pfs` command is a Command Line Interface to manage your files on PaddlePaddle Cloud ## Synopsis ``` @@ -28,10 +28,10 @@ paddle [options] pfs [parameters] ## Path Arguments When using a command, we need to specify path arguments. There are two path argument type: `localpath` and `pfspath`. -A `pfspath` begin with `/pfs`, eg: `/pfs/mydir/text1.txt`. +A `pfspath` begin with `/pfs`, eg: `/pfs/$DATACENTER/home/$USER/folder`. ## order of Path Arguments -Commonly, if there are two path arguments. The first is the source, and the second is the destination. +Commonly, if there are two path arguments, the first is the source, and the second is the destination. ## Subcommonds - [rm](rm.md) diff --git a/doc/design/file_manager/pfs/rm.md b/doc/design/file_manager/pfs/rm.md index 64774c7ad96..8bcbabdfb10 100644 --- a/doc/design/file_manager/pfs/rm.md +++ b/doc/design/file_manager/pfs/rm.md @@ -3,43 +3,30 @@ rm - remove files or directories # Synopsis ``` -rm [OPTION]... ... +rm [-r] [-v] ... ``` # Description ``` --r, -R, --recursive - remove directories and their contents recursively +The following options are available: ---page-size (integer) - The number of results to return in each response to a list operation. The default value is 1000 (the maximum allowed). Using a lower value may help if an operation times out +-r + remove directories and their contents recursively + +-v + Cause rm to be verbose, showing files after they are removed. ``` # Examples -- The following command deletes a single files - -``` -paddle pfs rm /pfs/mydirectory/test1.txt -``` - -Output - -``` -delete /pfs/mydirectory/test1.txt -``` - - -- The following command deletes a directory recursively. +- The following command deletes a single file: ``` -paddle pfs rm -r /pfs/mydirectory +paddle pfs rm /pfs/$DATACENTER/home/$USER/test1.txt ``` -Output +- The following command deletes a directory recursively: ``` -delete /pfs/mydirectory/1.txt -delete /pfs/mydirectory/2.txt -... +paddle pfs rm -r /pfs/$DATACENTER/home/$USER/folder ``` diff --git a/doc/design/file_manager/pfs/sync.md b/doc/design/file_manager/pfs/sync.md index c921e8ac81a..4801ceedf92 100644 --- a/doc/design/file_manager/pfs/sync.md +++ b/doc/design/file_manager/pfs/sync.md @@ -1,42 +1,34 @@ # Name -sync - sync directories. Recursively copies new and updated files from the source directory to the destination +sync - sync directories. Recursively copies new and updated files from the source directory to the destination. # Synopsis -` sync [OPTIONS]... - or or ` +``` +sync [--preserve--links] [-v] +sync [--preserve--links] [-v] +sync [--preserve--links] [-v] ` +``` # Description ``` +The following options are available: + --preserve--links - Reserve links when copy links + Reserve links when copy links. + +-v + Cause sync to be verbose, showing files after their's synchronization is complete. ``` # Examples -- The following command sync locally directory to pfs - -``` -paddle pfs sync ./dir1 /pfs/mydir1 -``` - -Output - -``` -upload ./dir1/text1.txt to /pfs/mydir1/text2.txt -upload ./dir1/text2.txt to /pfs/mydir1/text2.txt -... -``` - -- The following command sync pfs directory to local +- The following command sync locally directory to pfs. ``` -paddle pfs sync /pfs/mydir1 . +paddle pfs sync ./dir1 /pfs/$DATACENTER/home/$USER/mydir1 ``` -Output +- The following command sync pfs directory to local. ``` -download /pfs/mydir1/text1.txt to ./text1.txt -download /pfs/mydir1/text2.txt to ./text2.txt -... +paddle pfs sync /pfs/$DATACENTER/home/$USER/mydir1 . ``` -- GitLab From bd69fa311ef70536c06b85416d3b8a6444eaa736 Mon Sep 17 00:00:00 2001 From: gongweibao Date: Fri, 12 May 2017 18:02:01 +0800 Subject: [PATCH 0289/3256] fix by comments --- doc/design/file_manager/RESTAPI.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/design/file_manager/RESTAPI.md b/doc/design/file_manager/RESTAPI.md index 8d0f37be995..9c94804d6b1 100644 --- a/doc/design/file_manager/RESTAPI.md +++ b/doc/design/file_manager/RESTAPI.md @@ -3,7 +3,7 @@ ``` GET /file: Get attribue of files -POST /file: Touch a file +POST /file: Create a file DELETE /file: Delete a File ``` @@ -18,6 +18,6 @@ POST /file/chunk: Update a chunk ``` GET /dir: List all files in a directory -POST /dir: Touch a directory +POST /dir: Create a directory DELETE /dir: Delete a directory ``` -- GitLab From 494aa8f63fce3def92d46fbb89871709a13ec43a Mon Sep 17 00:00:00 2001 From: Luo Tao Date: Fri, 12 May 2017 18:25:25 +0800 Subject: [PATCH 0290/3256] add explanation of paddle version 0.0.0 in faq --- doc/faq/index_cn.rst | 626 +++++++++++++++++++++---------------------- 1 file changed, 313 insertions(+), 313 deletions(-) diff --git a/doc/faq/index_cn.rst b/doc/faq/index_cn.rst index 081e3a60a24..c14160d55ec 100644 --- a/doc/faq/index_cn.rst +++ b/doc/faq/index_cn.rst @@ -1,313 +1,313 @@ -#################### -FAQ -#################### - -.. contents:: - -1. 如何减少内存占用 ---------------------------------- - -神经网络的训练本身是一个非常消耗内存和显存的工作,经常会消耗数10GB的内存和数GB的显存。 -PaddlePaddle的内存占用主要分为如下几个方面\: - -* DataProvider缓冲池内存(只针对内存) -* 神经元激活内存(针对内存和显存) -* 参数内存 (针对内存和显存) -* 其他内存杂项 - -其中,其他内存杂项是指PaddlePaddle本身所用的一些内存,包括字符串分配,临时变量等等,暂不考虑在内。 - -减少DataProvider缓冲池内存 -++++++++++++++++++++++++++ - -PyDataProvider使用的是异步加载,同时在内存里直接随即选取数据来做Shuffle。即 - -.. graphviz:: - - digraph { - rankdir=LR; - 数据文件 -> 内存池 -> PaddlePaddle训练 - } - -所以,减小这个内存池即可减小内存占用,同时也可以加速开始训练前数据载入的过程。但是,这 -个内存池实际上决定了shuffle的粒度。所以,如果将这个内存池减小,又要保证数据是随机的, -那么最好将数据文件在每次读取之前做一次shuffle。可能的代码为 - -.. literalinclude:: src/reduce_min_pool_size.py - -这样做可以极大的减少内存占用,并且可能会加速训练过程,详细文档参考 :ref:`api_pydataprovider2` 。 - -神经元激活内存 -++++++++++++++ - -神经网络在训练的时候,会对每一个激活暂存一些数据,如神经元激活值等。 -在反向传递的时候,这些数据会被用来更新参数。这些数据使用的内存主要和两个参数有关系, -一是batch size,另一个是每条序列(Sequence)长度。所以,其实也是和每个mini-batch中包含 -的时间步信息成正比。 - -所以做法可以有两种: - -* 减小batch size。 即在网络配置中 :code:`settings(batch_size=1000)` 设置成一个小一些的值。但是batch size本身是神经网络的超参数,减小batch size可能会对训练结果产生影响。 -* 减小序列的长度,或者直接扔掉非常长的序列。比如,一个数据集大部分序列长度是100-200, - 但是突然有一个10000长的序列,就很容易导致内存超限,特别是在LSTM等RNN中。 - -参数内存 -++++++++ - -PaddlePaddle支持非常多的优化算法(Optimizer),不同的优化算法需要使用不同大小的内存。 -例如使用 :code:`adadelta` 算法,则需要使用等于权重参数规模大约5倍的内存。举例,如果参数保存下来的模型目录 -文件为 :code:`100M`, 那么该优化算法至少需要 :code:`500M` 的内存。 - -可以考虑使用一些优化算法,例如 :code:`momentum`。 - -2. 如何加速PaddlePaddle的训练速度 ---------------------------------- - -加速PaddlePaddle训练可以考虑从以下几个方面\: - -* 减少数据载入的耗时 -* 加速训练速度 -* 利用分布式训练驾驭更多的计算资源 - -减少数据载入的耗时 -++++++++++++++++++ - -使用\ :code:`pydataprovider`\ 时,可以减少缓存池的大小,同时设置内存缓存功能,即可以极大的加速数据载入流程。 -:code:`DataProvider` 缓存池的减小,和之前减小通过减小缓存池来减小内存占用的原理一致。 - -.. literalinclude:: src/reduce_min_pool_size.py - -同时 :code:`@provider` 接口有一个 :code:`cache` 参数来控制缓存方法,将其设置成 :code:`CacheType.CACHE_PASS_IN_MEM` 的话,会将第一个 :code:`pass` (过完所有训练数据即为一个pass)生成的数据缓存在内存里,在之后的 :code:`pass` 中,不会再从 :code:`python` 端读取数据,而是直接从内存的缓存里读取数据。这也会极大减少数据读入的耗时。 - - -加速训练速度 -++++++++++++ - -PaddlePaddle支持Sparse的训练,sparse训练需要训练特征是 :code:`sparse_binary_vector` 、 :code:`sparse_vector` 、或者 :code:`integer_value` 的任一一种。同时,与这个训练数据交互的Layer,需要将其Parameter设置成 sparse 更新模式,即设置 :code:`sparse_update=True` - -这里使用简单的 :code:`word2vec` 训练语言模型距离,具体使用方法为\: - -使用一个词前两个词和后两个词,来预测这个中间的词。这个任务的DataProvider为\: - -.. literalinclude:: src/word2vec_dataprovider.py - -这个任务的配置为\: - -.. literalinclude:: src/word2vec_config.py - - -利用更多的计算资源 -++++++++++++++++++ - -利用更多的计算资源可以分为一下几个方式来进行\: - -* 单机CPU训练 - - * 使用多线程训练。设置命令行参数 :code:`trainer_count`。 - -* 单机GPU训练 - - * 使用显卡训练。设置命令行参数 :code:`use_gpu`。 - * 使用多块显卡训练。设置命令行参数 :code:`use_gpu` 和 :code:`trainer_count` 。 - -* 多机训练 - - * 请参考 :ref:`cluster_train` 。 - - -3. 遇到“非法指令”或者是“illegal instruction” --------------------------------------------- - -PaddlePaddle使用avx SIMD指令提高cpu执行效率,因此错误的使用二进制发行版可能会导致这种错误,请选择正确的版本。 - -4. 如何选择SGD算法的学习率 --------------------------- - -在采用sgd/async_sgd进行训练时,一个重要的问题是选择正确的learning_rate。如果learning_rate太大,那么训练有可能不收敛,如果learning_rate太小,那么收敛可能很慢,导致训练时间过长。 - -通常做法是从一个比较大的learning_rate开始试,如果不收敛,那减少学习率10倍继续试验,直到训练收敛为止。那么如何判断训练不收敛呢?可以估计出如果模型采用不变的输出最小的cost0是多少。 - -如果训练过程的的cost明显高于这个常数输出的cost,那么我们可以判断为训练不收敛。举一个例子,假如我们是三分类问题,采用multi-class-cross-entropy作为cost,数据中0,1,2三类的比例为 :code:`0.2, 0.5, 0.3` , 那么常数输出所能达到的最小cost是 :code:`-(0.2*log(0.2)+0.5*log(0.5)+0.3*log(0.3))=1.03` 。如果训练一个pass(或者更早)后,cost还大于这个数,那么可以认为训练不收敛,应该降低学习率。 - - -5. 如何初始化参数 ------------------ - -默认情况下,PaddlePaddle使用均值0,标准差为 :math:`\frac{1}{\sqrt{d}}` 来初始化参数。其中 :math:`d` 为参数矩阵的宽度。这种初始化方式在一般情况下不会产生很差的结果。如果用户想要自定义初始化方式,PaddlePaddle目前提供两种参数初始化的方式\: - -* 高斯分布。将 :code:`param_attr` 设置成 :code:`param_attr=ParamAttr(initial_mean=0.0, initial_std=1.0)` -* 均匀分布。将 :code:`param_attr` 设置成 :code:`param_attr=ParamAttr(initial_max=1.0, initial_min=-1.0)` - -比如设置一个全连接层的参数初始化方式和bias初始化方式,可以使用如下代码。 - -.. code-block:: python - - hidden = fc_layer(input=ipt, param_attr=ParamAttr(initial_max=1.0, initial_min=-1.0), - bias_attr=ParamAttr(initial_mean=1.0, initial_std=0.0)) - -上述代码将bias全部初始化为1.0, 同时将参数初始化为 :code:`[1.0, -1.0]` 的均匀分布。 - -6. 如何共享参数 ---------------- - -PaddlePaddle的参数使用名字 :code:`name` 作为参数的ID,相同名字的参数,会共享参数。设置参数的名字,可以使用 :code:`ParamAttr(name="YOUR_PARAM_NAME")` 来设置。更方便的设置方式,是使得要共享的参数使用同样的 :code:`ParamAttr` 对象。 - -简单的全连接网络,参数共享的配置示例为\: - -.. literalinclude:: ../../python/paddle/trainer_config_helpers/tests/configs/shared_fc.py - -这里 :code:`hidden_a` 和 :code:`hidden_b` 使用了同样的parameter和bias。并且softmax层的两个输入也使用了同样的参数 :code:`softmax_param`。 - -7. \*-cp27mu-linux_x86_64.whl is not a supported wheel on this platform. ------------------------------------------------------------------------- - -出现这个问题的主要原因是,系统编译wheel包的时候,使用的 :code:`wheel` 包是最新的, -而系统中的 :code:`pip` 包比较老。具体的解决方法是,更新 :code:`pip` 包并重新编译PaddlePaddle。 -更新 :code:`pip` 包的方法是\: - -.. code-block:: bash - - pip install --upgrade pip - -8. python相关的单元测试都过不了 --------------------------------- - -如果出现以下python相关的单元测试都过不了的情况: - -.. code-block:: bash - - 24 - test_PyDataProvider (Failed) - 26 - test_RecurrentGradientMachine (Failed) - 27 - test_NetworkCompare (Failed) - 28 - test_PyDataProvider2 (Failed) - 32 - test_Prediction (Failed) - 33 - test_Compare (Failed) - 34 - test_Trainer (Failed) - 35 - test_TrainerOnePass (Failed) - 36 - test_CompareTwoNets (Failed) - 37 - test_CompareTwoOpts (Failed) - 38 - test_CompareSparse (Failed) - 39 - test_recurrent_machine_generation (Failed) - 40 - test_PyDataProviderWrapper (Failed) - 41 - test_config_parser (Failed) - 42 - test_swig_api (Failed) - 43 - layers_test (Failed) - -并且查询PaddlePaddle单元测试的日志,提示: - -.. code-block:: bash - - paddle package is already in your PYTHONPATH. But unittest need a clean environment. - Please uninstall paddle package before start unittest. Try to 'pip uninstall paddle'. - -解决办法是: - -* 卸载PaddlePaddle包 :code:`pip uninstall paddle`, 清理掉老旧的PaddlePaddle安装包,使得单元测试有一个干净的环境。如果PaddlePaddle包已经在python的site-packages里面,单元测试会引用site-packages里面的python包,而不是源码目录里 :code:`/python` 目录下的python包。同时,即便设置 :code:`PYTHONPATH` 到 :code:`/python` 也没用,因为python的搜索路径是优先已经安装的python包。 - - -9. 运行Docker GPU镜像出现 "CUDA driver version is insufficient" ----------------------------------------------------------------- - -用户在使用PaddlePaddle GPU的Docker镜像的时候,常常出现 `Cuda Error: CUDA driver version is insufficient for CUDA runtime version`, 原因在于没有把机器上CUDA相关的驱动和库映射到容器内部。 -具体的解决方法是: - -.. code-block:: bash - - $ export CUDA_SO="$(\ls usr/lib64/libcuda* | xargs -I{} echo '-v {}:{}') $(\ls /usr/lib64/libnvidia* | xargs -I{} echo '-v {}:{}')" - $ export DEVICES=$(\ls /dev/nvidia* | xargs -I{} echo '--device {}:{}') - $ docker run ${CUDA_SO} ${DEVICES} -it paddledev/paddlepaddle:latest-gpu - -更多关于Docker的安装与使用, 请参考 `PaddlePaddle Docker 文档 `_ 。 - - -10. CMake源码编译, 找到的PythonLibs和PythonInterp版本不一致 ----------------------------------------------------------------- - -这是目前CMake寻找Python的逻辑存在缺陷,如果系统安装了多个Python版本,CMake找到的Python库和Python解释器版本可能有不一致现象,导致编译PaddlePaddle失败。正确的解决方法是, -用户强制指定特定的Python版本,具体操作如下: - - .. code-block:: bash - - cmake .. -DPYTHON_EXECUTABLE= -DPYTHON_LIBRARY= -DPYTHON_INCLUDE_DIR= - -用户需要指定本机上Python的路径:````, ````, ```` - -11. CMake源码编译,Paddle版本号为0.0.0 --------------------------------------- - -如果运行 :code:`paddle version`, 出现 :code:`PaddlePaddle 0.0.0`;或者运行 :code:`cmake ..`,出现 - - .. code-block:: bash - - CMake Warning at cmake/version.cmake:20 (message): - Cannot add paddle version from git tag - -那么用户需要拉取所有的远程分支到本机,命令为 :code:`git fetch upstream`,然后重新cmake即可。 - -12. A protocol message was rejected because it was too big ----------------------------------------------------------- - -如果在训练NLP相关模型时,出现以下错误: - -.. code-block:: bash - - [libprotobuf ERROR google/protobuf/io/coded_stream.cc:171] A protocol message was rejected because it was too big (more than 67108864 bytes). To increase the limit (or to disable these warnings), see CodedInputStream::SetTotalBytesLimit() in google/protobuf/io/coded_stream.h. - F1205 14:59:50.295174 14703 TrainerConfigHelper.cpp:59] Check failed: m->conf.ParseFromString(configProtoStr) - -可能的原因是:传给dataprovider的某一个args过大,一般是由于直接传递大字典导致的。错误的define_py_data_sources2类似: - -.. code-block:: python - - src_dict = dict() - for line_count, line in enumerate(open(src_dict_path, "r")): - src_dict[line.strip()] = line_count - - define_py_data_sources2( - train_list, - test_list, - module="dataprovider", - obj="process", - args={"src_dict": src_dict}) - -解决方案是:将字典的地址作为args传给dataprovider,然后在dataprovider里面根据该地址加载字典。即define_py_data_sources2应改为: - -.. code-block:: python - - define_py_data_sources2( - train_list, - test_list, - module="dataprovider", - obj="process", - args={"src_dict_path": src_dict_path}) - -完整源码可参考 `seqToseq `_ 示例。 - -13. 如何指定GPU设备 -------------------- - -例如机器上有4块GPU,编号从0开始,指定使用2、3号GPU: - -* 方式1:通过 `CUDA_VISIBLE_DEVICES `_ 环境变量来指定特定的GPU。 - -.. code-block:: bash - - env CUDA_VISIBLE_DEVICES=2,3 paddle train --use_gpu=true --trainer_count=2 - -* 方式2:通过命令行参数 ``--gpu_id`` 指定。 - -.. code-block:: bash - - paddle train --use_gpu=true --trainer_count=2 --gpu_id=2 - - -14. 训练过程中出现 :code:`Floating point exception`, 训练因此退出怎么办? ------------------------------------------------------------------------- - -Paddle二进制在运行时捕获了浮点数异常,只要出现浮点数异常(即训练过程中出现NaN或者Inf),立刻退出。浮点异常通常的原因是浮点数溢出、除零等问题。 -主要原因包括两个方面: - -* 训练过程中参数或者训练过程中的梯度尺度过大,导致参数累加,乘除等时候,导致了浮点数溢出。 -* 模型一直不收敛,发散到了一个数值特别大的地方。 -* 训练数据有问题,导致参数收敛到了一些奇异的情况。或者输入数据尺度过大,有些特征的取值达到数百万,这时进行矩阵乘法运算就可能导致浮点数溢出。 - -主要的解决办法是减小学习律或者对数据进行归一化处理。 +#################### +FAQ +#################### + +.. contents:: + +1. 如何减少内存占用 +--------------------------------- + +神经网络的训练本身是一个非常消耗内存和显存的工作,经常会消耗数10GB的内存和数GB的显存。 +PaddlePaddle的内存占用主要分为如下几个方面\: + +* DataProvider缓冲池内存(只针对内存) +* 神经元激活内存(针对内存和显存) +* 参数内存 (针对内存和显存) +* 其他内存杂项 + +其中,其他内存杂项是指PaddlePaddle本身所用的一些内存,包括字符串分配,临时变量等等,暂不考虑在内。 + +减少DataProvider缓冲池内存 +++++++++++++++++++++++++++ + +PyDataProvider使用的是异步加载,同时在内存里直接随即选取数据来做Shuffle。即 + +.. graphviz:: + + digraph { + rankdir=LR; + 数据文件 -> 内存池 -> PaddlePaddle训练 + } + +所以,减小这个内存池即可减小内存占用,同时也可以加速开始训练前数据载入的过程。但是,这 +个内存池实际上决定了shuffle的粒度。所以,如果将这个内存池减小,又要保证数据是随机的, +那么最好将数据文件在每次读取之前做一次shuffle。可能的代码为 + +.. literalinclude:: src/reduce_min_pool_size.py + +这样做可以极大的减少内存占用,并且可能会加速训练过程,详细文档参考 :ref:`api_pydataprovider2` 。 + +神经元激活内存 +++++++++++++++ + +神经网络在训练的时候,会对每一个激活暂存一些数据,如神经元激活值等。 +在反向传递的时候,这些数据会被用来更新参数。这些数据使用的内存主要和两个参数有关系, +一是batch size,另一个是每条序列(Sequence)长度。所以,其实也是和每个mini-batch中包含 +的时间步信息成正比。 + +所以做法可以有两种: + +* 减小batch size。 即在网络配置中 :code:`settings(batch_size=1000)` 设置成一个小一些的值。但是batch size本身是神经网络的超参数,减小batch size可能会对训练结果产生影响。 +* 减小序列的长度,或者直接扔掉非常长的序列。比如,一个数据集大部分序列长度是100-200, + 但是突然有一个10000长的序列,就很容易导致内存超限,特别是在LSTM等RNN中。 + +参数内存 +++++++++ + +PaddlePaddle支持非常多的优化算法(Optimizer),不同的优化算法需要使用不同大小的内存。 +例如使用 :code:`adadelta` 算法,则需要使用等于权重参数规模大约5倍的内存。举例,如果参数保存下来的模型目录 +文件为 :code:`100M`, 那么该优化算法至少需要 :code:`500M` 的内存。 + +可以考虑使用一些优化算法,例如 :code:`momentum`。 + +2. 如何加速PaddlePaddle的训练速度 +--------------------------------- + +加速PaddlePaddle训练可以考虑从以下几个方面\: + +* 减少数据载入的耗时 +* 加速训练速度 +* 利用分布式训练驾驭更多的计算资源 + +减少数据载入的耗时 +++++++++++++++++++ + +使用\ :code:`pydataprovider`\ 时,可以减少缓存池的大小,同时设置内存缓存功能,即可以极大的加速数据载入流程。 +:code:`DataProvider` 缓存池的减小,和之前减小通过减小缓存池来减小内存占用的原理一致。 + +.. literalinclude:: src/reduce_min_pool_size.py + +同时 :code:`@provider` 接口有一个 :code:`cache` 参数来控制缓存方法,将其设置成 :code:`CacheType.CACHE_PASS_IN_MEM` 的话,会将第一个 :code:`pass` (过完所有训练数据即为一个pass)生成的数据缓存在内存里,在之后的 :code:`pass` 中,不会再从 :code:`python` 端读取数据,而是直接从内存的缓存里读取数据。这也会极大减少数据读入的耗时。 + + +加速训练速度 +++++++++++++ + +PaddlePaddle支持Sparse的训练,sparse训练需要训练特征是 :code:`sparse_binary_vector` 、 :code:`sparse_vector` 、或者 :code:`integer_value` 的任一一种。同时,与这个训练数据交互的Layer,需要将其Parameter设置成 sparse 更新模式,即设置 :code:`sparse_update=True` + +这里使用简单的 :code:`word2vec` 训练语言模型距离,具体使用方法为\: + +使用一个词前两个词和后两个词,来预测这个中间的词。这个任务的DataProvider为\: + +.. literalinclude:: src/word2vec_dataprovider.py + +这个任务的配置为\: + +.. literalinclude:: src/word2vec_config.py + + +利用更多的计算资源 +++++++++++++++++++ + +利用更多的计算资源可以分为一下几个方式来进行\: + +* 单机CPU训练 + + * 使用多线程训练。设置命令行参数 :code:`trainer_count`。 + +* 单机GPU训练 + + * 使用显卡训练。设置命令行参数 :code:`use_gpu`。 + * 使用多块显卡训练。设置命令行参数 :code:`use_gpu` 和 :code:`trainer_count` 。 + +* 多机训练 + + * 请参考 :ref:`cluster_train` 。 + + +3. 遇到“非法指令”或者是“illegal instruction” +-------------------------------------------- + +PaddlePaddle使用avx SIMD指令提高cpu执行效率,因此错误的使用二进制发行版可能会导致这种错误,请选择正确的版本。 + +4. 如何选择SGD算法的学习率 +-------------------------- + +在采用sgd/async_sgd进行训练时,一个重要的问题是选择正确的learning_rate。如果learning_rate太大,那么训练有可能不收敛,如果learning_rate太小,那么收敛可能很慢,导致训练时间过长。 + +通常做法是从一个比较大的learning_rate开始试,如果不收敛,那减少学习率10倍继续试验,直到训练收敛为止。那么如何判断训练不收敛呢?可以估计出如果模型采用不变的输出最小的cost0是多少。 + +如果训练过程的的cost明显高于这个常数输出的cost,那么我们可以判断为训练不收敛。举一个例子,假如我们是三分类问题,采用multi-class-cross-entropy作为cost,数据中0,1,2三类的比例为 :code:`0.2, 0.5, 0.3` , 那么常数输出所能达到的最小cost是 :code:`-(0.2*log(0.2)+0.5*log(0.5)+0.3*log(0.3))=1.03` 。如果训练一个pass(或者更早)后,cost还大于这个数,那么可以认为训练不收敛,应该降低学习率。 + + +5. 如何初始化参数 +----------------- + +默认情况下,PaddlePaddle使用均值0,标准差为 :math:`\frac{1}{\sqrt{d}}` 来初始化参数。其中 :math:`d` 为参数矩阵的宽度。这种初始化方式在一般情况下不会产生很差的结果。如果用户想要自定义初始化方式,PaddlePaddle目前提供两种参数初始化的方式\: + +* 高斯分布。将 :code:`param_attr` 设置成 :code:`param_attr=ParamAttr(initial_mean=0.0, initial_std=1.0)` +* 均匀分布。将 :code:`param_attr` 设置成 :code:`param_attr=ParamAttr(initial_max=1.0, initial_min=-1.0)` + +比如设置一个全连接层的参数初始化方式和bias初始化方式,可以使用如下代码。 + +.. code-block:: python + + hidden = fc_layer(input=ipt, param_attr=ParamAttr(initial_max=1.0, initial_min=-1.0), + bias_attr=ParamAttr(initial_mean=1.0, initial_std=0.0)) + +上述代码将bias全部初始化为1.0, 同时将参数初始化为 :code:`[1.0, -1.0]` 的均匀分布。 + +6. 如何共享参数 +--------------- + +PaddlePaddle的参数使用名字 :code:`name` 作为参数的ID,相同名字的参数,会共享参数。设置参数的名字,可以使用 :code:`ParamAttr(name="YOUR_PARAM_NAME")` 来设置。更方便的设置方式,是使得要共享的参数使用同样的 :code:`ParamAttr` 对象。 + +简单的全连接网络,参数共享的配置示例为\: + +.. literalinclude:: ../../python/paddle/trainer_config_helpers/tests/configs/shared_fc.py + +这里 :code:`hidden_a` 和 :code:`hidden_b` 使用了同样的parameter和bias。并且softmax层的两个输入也使用了同样的参数 :code:`softmax_param`。 + +7. \*-cp27mu-linux_x86_64.whl is not a supported wheel on this platform. +------------------------------------------------------------------------ + +出现这个问题的主要原因是,系统编译wheel包的时候,使用的 :code:`wheel` 包是最新的, +而系统中的 :code:`pip` 包比较老。具体的解决方法是,更新 :code:`pip` 包并重新编译PaddlePaddle。 +更新 :code:`pip` 包的方法是\: + +.. code-block:: bash + + pip install --upgrade pip + +8. python相关的单元测试都过不了 +-------------------------------- + +如果出现以下python相关的单元测试都过不了的情况: + +.. code-block:: bash + + 24 - test_PyDataProvider (Failed) + 26 - test_RecurrentGradientMachine (Failed) + 27 - test_NetworkCompare (Failed) + 28 - test_PyDataProvider2 (Failed) + 32 - test_Prediction (Failed) + 33 - test_Compare (Failed) + 34 - test_Trainer (Failed) + 35 - test_TrainerOnePass (Failed) + 36 - test_CompareTwoNets (Failed) + 37 - test_CompareTwoOpts (Failed) + 38 - test_CompareSparse (Failed) + 39 - test_recurrent_machine_generation (Failed) + 40 - test_PyDataProviderWrapper (Failed) + 41 - test_config_parser (Failed) + 42 - test_swig_api (Failed) + 43 - layers_test (Failed) + +并且查询PaddlePaddle单元测试的日志,提示: + +.. code-block:: bash + + paddle package is already in your PYTHONPATH. But unittest need a clean environment. + Please uninstall paddle package before start unittest. Try to 'pip uninstall paddle'. + +解决办法是: + +* 卸载PaddlePaddle包 :code:`pip uninstall paddle`, 清理掉老旧的PaddlePaddle安装包,使得单元测试有一个干净的环境。如果PaddlePaddle包已经在python的site-packages里面,单元测试会引用site-packages里面的python包,而不是源码目录里 :code:`/python` 目录下的python包。同时,即便设置 :code:`PYTHONPATH` 到 :code:`/python` 也没用,因为python的搜索路径是优先已经安装的python包。 + + +9. 运行Docker GPU镜像出现 "CUDA driver version is insufficient" +---------------------------------------------------------------- + +用户在使用PaddlePaddle GPU的Docker镜像的时候,常常出现 `Cuda Error: CUDA driver version is insufficient for CUDA runtime version`, 原因在于没有把机器上CUDA相关的驱动和库映射到容器内部。 +具体的解决方法是: + +.. code-block:: bash + + $ export CUDA_SO="$(\ls usr/lib64/libcuda* | xargs -I{} echo '-v {}:{}') $(\ls /usr/lib64/libnvidia* | xargs -I{} echo '-v {}:{}')" + $ export DEVICES=$(\ls /dev/nvidia* | xargs -I{} echo '--device {}:{}') + $ docker run ${CUDA_SO} ${DEVICES} -it paddledev/paddlepaddle:latest-gpu + +更多关于Docker的安装与使用, 请参考 `PaddlePaddle Docker 文档 `_ 。 + + +10. CMake源码编译, 找到的PythonLibs和PythonInterp版本不一致 +---------------------------------------------------------------- + +这是目前CMake寻找Python的逻辑存在缺陷,如果系统安装了多个Python版本,CMake找到的Python库和Python解释器版本可能有不一致现象,导致编译PaddlePaddle失败。正确的解决方法是, +用户强制指定特定的Python版本,具体操作如下: + + .. code-block:: bash + + cmake .. -DPYTHON_EXECUTABLE= -DPYTHON_LIBRARY= -DPYTHON_INCLUDE_DIR= + +用户需要指定本机上Python的路径:````, ````, ```` + +11. CMake源码编译,Paddle版本号为0.0.0 +-------------------------------------- + +如果运行 :code:`paddle version`, 出现 :code:`PaddlePaddle 0.0.0`;或者运行 :code:`cmake ..`,出现 + +.. code-block:: bash + + CMake Warning at cmake/version.cmake:20 (message): + Cannot add paddle version from git tag + +那么用户需要拉取所有的远程分支到本机,命令为 :code:`git fetch upstream`,然后重新cmake即可。 + +12. A protocol message was rejected because it was too big +---------------------------------------------------------- + +如果在训练NLP相关模型时,出现以下错误: + +.. code-block:: bash + + [libprotobuf ERROR google/protobuf/io/coded_stream.cc:171] A protocol message was rejected because it was too big (more than 67108864 bytes). To increase the limit (or to disable these warnings), see CodedInputStream::SetTotalBytesLimit() in google/protobuf/io/coded_stream.h. + F1205 14:59:50.295174 14703 TrainerConfigHelper.cpp:59] Check failed: m->conf.ParseFromString(configProtoStr) + +可能的原因是:传给dataprovider的某一个args过大,一般是由于直接传递大字典导致的。错误的define_py_data_sources2类似: + +.. code-block:: python + + src_dict = dict() + for line_count, line in enumerate(open(src_dict_path, "r")): + src_dict[line.strip()] = line_count + + define_py_data_sources2( + train_list, + test_list, + module="dataprovider", + obj="process", + args={"src_dict": src_dict}) + +解决方案是:将字典的地址作为args传给dataprovider,然后在dataprovider里面根据该地址加载字典。即define_py_data_sources2应改为: + +.. code-block:: python + + define_py_data_sources2( + train_list, + test_list, + module="dataprovider", + obj="process", + args={"src_dict_path": src_dict_path}) + +完整源码可参考 `seqToseq `_ 示例。 + +13. 如何指定GPU设备 +------------------- + +例如机器上有4块GPU,编号从0开始,指定使用2、3号GPU: + +* 方式1:通过 `CUDA_VISIBLE_DEVICES `_ 环境变量来指定特定的GPU。 + +.. code-block:: bash + + env CUDA_VISIBLE_DEVICES=2,3 paddle train --use_gpu=true --trainer_count=2 + +* 方式2:通过命令行参数 ``--gpu_id`` 指定。 + +.. code-block:: bash + + paddle train --use_gpu=true --trainer_count=2 --gpu_id=2 + + +14. 训练过程中出现 :code:`Floating point exception`, 训练因此退出怎么办? +------------------------------------------------------------------------ + +Paddle二进制在运行时捕获了浮点数异常,只要出现浮点数异常(即训练过程中出现NaN或者Inf),立刻退出。浮点异常通常的原因是浮点数溢出、除零等问题。 +主要原因包括两个方面: + +* 训练过程中参数或者训练过程中的梯度尺度过大,导致参数累加,乘除等时候,导致了浮点数溢出。 +* 模型一直不收敛,发散到了一个数值特别大的地方。 +* 训练数据有问题,导致参数收敛到了一些奇异的情况。或者输入数据尺度过大,有些特征的取值达到数百万,这时进行矩阵乘法运算就可能导致浮点数溢出。 + +主要的解决办法是减小学习律或者对数据进行归一化处理。 -- GitLab From 33625aeaebf895d866b79af7ba1c99f92d8a86b9 Mon Sep 17 00:00:00 2001 From: gongweibao Date: Fri, 12 May 2017 19:28:24 +0800 Subject: [PATCH 0291/3256] fix common to read-only --- doc/design/cluster_train/data_dispatch.md | 4 +--- .../cluster_train/src/file_storage.graffle | Bin 3031 -> 3059 bytes doc/design/cluster_train/src/file_storage.png | Bin 42121 -> 43413 bytes 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/doc/design/cluster_train/data_dispatch.md b/doc/design/cluster_train/data_dispatch.md index e7798b24f63..9f7167498ce 100644 --- a/doc/design/cluster_train/data_dispatch.md +++ b/doc/design/cluster_train/data_dispatch.md @@ -19,6 +19,7 @@ - 无论是从[PFSClient](../file_manager/README.md)的角度,还是从[Pod](https://kubernetes.io/docs/concepts/workloads/pods/pod/)中运行任务的角度,统一用`/pfs/$DATACENTER/home/$USER`来访问用户自己的数据。 - `/pfs/$DATACENTER/common`下存放公共数据集合 + - 做只读挂载
    @@ -141,9 +142,6 @@ endpoint=datacenter2.paddlepaddle.org ## TODO ### 文件访问的权限 控制用户权限 - -- `/pfs/$DATACENTER/common`数据集合只读不能写 - - 现在mount到本地以后有读写权限 - 用户可以把自己的数据分享给别人 ### 文件访问方式 diff --git a/doc/design/cluster_train/src/file_storage.graffle b/doc/design/cluster_train/src/file_storage.graffle index 5331f407f7ee77fca263be24eb83ed1d44ca2bd4..50a17e70fa255495337c529a3bf12a5c0024a5be 100644 GIT binary patch literal 3059 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 literal 3031 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 Z6^}`o__02QU!6MK`7h6L<~i8?O- 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 UAIlX0Ce6fjLS08KPsPIbf0w76rT_o{ literal 42121 zcmeFYRa7R+x~>Tn?oI)PyF=k_g=+zY{J6WjySux)+mAa`;qLD4?$XRP*PdhTKBs&1 zO^s@nx#!Gb*akq!xIg1jC) zI0kJ9d{lQx9T0R4cqLILMy0Q_{bi)>Pk!_PV)}p!b$j% zE62$7eUi0YR_lzy$m=w&+jZa8_+Y{=Z;-+wi_`Rc>Sol?{c)_Gl5^BCYpFNT-Swzq z(iO7`5%c9nG3hbsUI;2dxVvJP@EcQXqHXt? zDFn6&35$&!h-|{EXAq=KnAa%+v;a_h9hlKca}bat+Y_UE78`9kRfb(M(jsQfDMhpa zgDcMdAZdF=W?T@X@y%5VFK_RA3qkl^Az7d2mE0H1QjNhs)L@eV=Wr0)Lb5j;AaSuE zEJpS}dLvrC8SVQ)kP^Zi3V@b?AmqSBOb%E0ez6gxxrgv2ME~KN=<@|tkQ&*qLFdaN z*tG!YBFw$tcbl(Der7hP8IUrYV9j7v{wq1Sx4vRJ$Pqp&I^U%IsG$8WkumZFVPmKZ zAg~4YW9W9lU4`D|8POsB2*4l{pbFyVRLX-Y!6yoe<>DM-9r8Fpw8Gp8AmtF5z`H?t z`l6(O&FC7c;Z&XvAqJ{+!(8HUpp6GQcNf|~cz{_2RChOR@wp&-LC6t;5Rt(D zKwuKGKyfBT4i()-A*zEJ5|fO9ExFrkFvpDhf=LXcYAj=Ma+LRBxT+=6?buWiLQ;q;6<{xWz}R+emcoVDFUdF zC@)Ht%Gl-tab#qQ&vWMGs}-^p_$4MuBgj-yHA2Tw_fR8IJ5a4jL*oE(RdFf8GR|#9gyTeMu z8j09NL@Wqt+>yd7lH8(gqPV#`hg=SryrEhG{1O*=(IdlyfZ?E#s(8BO4odDpfv?N( zm?VKee{+l-8hgYlCfO&3$FI<7s_Uf>BoCwr z`zqr3r!dSM1BpsY(W4`zb5Iw>_~(1xOgKJukL0stkEEJpqGYBd+A#I-`Y_nu+ptQa zb~&SxhEkl;eTjwwwqju^SLwCVLy4BM=TL%S>vzSJ!cnLF=Cru9=rr-O5k z1$p$r_JNXw#g_jJ`k6Tu!_>F9!7DjJX`NF7RfO500m zNamwP`8d+qjX9M+VQf`3wq4!U4-dwh!&{0H z!-L99k!6w<%Ye+B%UBx67@yE!(P*asMbDsdrxC9q*W78#?1$&A#=HA+||J~Ki(jIfSf|0E%YlW>g?nY$inHPE& za@Y4R+p3#a(`Ykvxwpl-ML@zs97Y03B16nBx*nwvu`R+Xwv*qN zU!F&iAtmxILVZh@~P?rB}^`<{udvQX2U*jA)=zL!qZu z3#B)}d7j~!p`w9xj(@J2p|4@P8tyXixcxWP3#XdQ^tWirg@UFu(`hqD zCiOE^(-?1rIKwu>loO9YHjlGZ$LW*D6Rw4m>W$8Kks_7-3AGejS>+YT1_X6BcuR*h z7Y8&Tm6PI%QRG-lx>#LDeR)$%BdPTh-2h85$3fqGNF`m%aLZClAHU0od@@BW#VGFL ze$ClsbP9nhzmsLtVyE}ZgNN07CD@E#oL_ZdMz1=5>YLK@%x%;oo~FpWNU+nk)3x*c zh28ouOi=O2J1)p?b3 z<#ii@qwf1J1W0yB_xN_4O5UnZHa%MmVlHCm1b2MPs=1m!6++8LOFX=2UsVSt8u=HL zsFX33Jh{jD`Bd>V*0l5$6&1^s)RmwXLXZ1pY5Au-r*{nOJAS@RL5d3ZP}xX29PcqO zb=_v)bFVq|-%G!#Rn|j|PmS-f3q_vb4y2=|L#F4axsShOS)1FMa6Xh@RSz(fXi|1j zy4N0}9x0p`9*v|XQve_;tE)*hu&vbR?hnm&=iJYvR@$nbG}~;~PM+rw7otYkzgcTE zn|j~8(yge=G*w!&I{_TCUdE1_>^0{#Bb_bIS)5lKn{H{(TV_vI-S?^n)=nN&AFLke zj!uq{PP+JOJajJk9^McA6QHaKjCeBK65hVPaD0T$#>W*)%F%c&ymaVJx?OI^49FkU z#~4QkarRZei@)rgh@aaZ-^5{+F_?*ti7`aDMmlzB+}rIYZqCh~w0B8-oOR=_u^WFl zJk{It*m>+$UY^~y^*+E}M0?k~PO_n}TseGqv^&;+rwEjr%eLfq_6U8v)-yX(d0m*c zcH01SzIb}`WODJ+>A)@3SAIra-w2Spxq)&yqM!mNkUpeL3nUY;X(uPfFs~F@C;7&P z2m*#QAh}B*kNG=Z4zr@d(-1y{EJB(f7P>@H-jsCfy&mJ?s{xJVWHU1tQ|rjpP~A{{6dqN1OXL)C(uq7)e-{$1QZN&GM&jAs-aL*)PceLqr?IR9@| zcqJw51!ixOrvGQPKFiSs?Eh2RUp4hXzKr*Qp7HXFxA^b1`m3T}{(I8@RP=wo=>Lq- z|C;0fGnxKR$`heD@_%U~G&)$amKzm|3|zvwIDddZH*)iCoYduo_XoGmv;Jua^o%Hu z9o^rG`6UemZkkvc>5wR)ABqwbv*-(JNUB&)RK=~-e@b=Ez^Blz?SvG9{jHIIOS`WC z8DX!(|LM%&jK2Lls)Akx`t?yzHWVutY0xug^|4?7v!WO9Zz2S!kUFR+MPF>> z3#I-;%MV$>1baG^e|cAk2OPa~P*0Qp$HNYAzDD0hB!5`wVItx$1}zRsb*sB;qhg0! zLdoSVsRr+$-;)lI#O)k~L}>X|^c6=?9kwC{GsmHv(NcV8!-fi%En^W@FZf1Fz%WG# zpENGXANa2ohp+~w288fq9?U(W&{>mAKDgo#MOE`~)Hqal8h%s$*lO`8iM1a5W!o4m zk^+Umtk+7`sF9e;4qCyvL@tZPW>er*p7v)Gow#JGuQO6XZ$HG(nu6rb!UiQ}T%wq+ z^t@Cn{qDV_|5)$9`~{G!Y|0|&OhruGsNmF_jJAF_qEyWGWXw@G%mVodg79 z!CMNhT&bVjmA(1$t(3^M?UlWi&{9@7@gB9&YBelbc9xiX9Q|v_>uKQsvQmEK03UTZ z-ExJtV|AJ3AvLPwuH3O``K zkk1~7`W6TiYw%#P5s(Q*gJ4QsdqaVH%hD&rbhZE0vBS!}GkMaJ#XTIg2mu>v&JfP< zJ?FU1LHR#smVb~ync?^GLc^W(qH_6!7Vy&3KoWYS2We=*Nu(K_WfXs-J|S(o?tA2(Qx28Lq;&0kp9ASbBJA~Q12IOqHc0*nVz$B z$6HbMFHdb$y$;r+gr&lNcZyq}0O3d9IfjoyDDolEXG_-5nWOR3SQIM$c?;oZIJ=34 ztjl#dpz22XpMmG3F!+y@bE2C6Uo!vpc1H3|)Po`im6@5Gob>U2zsc4b2!zA3x!M-- zcz-&m9G8)iIezc@c(VpNYq%X1np{95B0>_VXxKu~cfHy&ZGAmdTt(Hh~W>cua}$;fyGmg zn&0PF*UkW;aeDk%)IqE>9nrym16_RZeml{A-1JA1|M}DRv%ME3Wo5lf^~N1h(R|O) zw41HqLrKP%e35>|SIp+VP+|SatcR}xn_Lk{kau@?9M0z%FM@sq)EY>bh{3`zv)a$o z$&5~?s?Fmbyk4&^(it2mXynrJz0V*S#M8Tivfas%L>>||G+)!@bk&bdd0K5ze$>y= z{C=?{+k^Ukxq!Q%GgaWrj^^>k_moz(fv5n5fTr8}g#&et9bA=JU0FG(>+Q_?n8Wj# zWq4%drcG(Lau+dRB>(Ka4O%uyhR}z`4AOU`(4*LX@9X%_4HdK_RBU2 zS4CUx_1EuCJpSx_sR0yOTG6INc;vx5v-qoZK%6Iv`TQ+$O#F>HIL<&P)%dzAmuX39 z*e^IV(S-UJ1cKGwB{ku<{l~Bo(d{tK>zqjXE0iCzJ+!C1pgb|i(WX6ngKl>GWmF6Y zY(e2z{C^JBkchmtNLi8ydg8mDNs2Z;pfSY)aN|qOq3&q7Pq5n^O1>U2qE3Yqbo-Zg zMTY!b^)dR!UjrAsZf)@ss=;wlhc+S{}psFWZMnkni3a^kz+;C7{;KVzwaDcG9EYx8+#te3dV(l5zn52 zBoKshzJPVU_MJ1z>`43$N<-GK7V`j?XgUiLJ&i{AbIq(=P^sqk>nfbFYz_ErDx&YI zC#0nj8YA{8#Y7^IwI?hCkJF7_k;_c3LS*s^9zGHPRnLm37zBbv-yWQiAtb3UqU#!pmhkTK`=4~9(!s}6tgLfAPKG&m-GkYkanNzVOBbsUb8oZG{D~Mz$ zRyD>9igu~Q+mtM#qk=OjaLQg;F%+}Fa3k29A&JEDjvVz?y5`rnzC0M2!7%)=xj-eW zRrHRt*JSAl`iLf-h55D`?LS{^TM7pOY-5@GE~2D)^SlA|h5EJcVr=j&M$q&g8WG=c zLu0Gny*xmZB5fD%8lEUw)KLbIGX(F5f}SrU;X?3n_V6x_8RF#Uff!a0)$TkK+j*w6 z<5&xNJE!9iKU6Y#?bv`+7AvZ>6HGl8c~{Nj5kl%j6-$MSO?Eg@#y`*)a+=?vjc_4{ za`y+T58q)%5$VM2E)5E%A4v--wu5r#Ty*Ck7fZHW4G~m%h8C{AM;$<*UySE6L47}K zxaB=eQ3>9l_#&V^gIx!oA%$u}3hk>!B+xhv@bcC_vg zM+OHc-*1cgO>dKWJ|sztkuUv0-X6|aRK!fnuF(clxJ1g&feDawEAV3(YRjgQi8-CB z%s*GIsgHruXe^`{QhN{#kqseBh+RIJv$e!Og%FAYy4JqZzC*zv&6~k0`0hYDH{>?m< z66Fiue|4!RoKzKRjWpV*KQWp^K*rolGbd(Bkhuscn&Q4$QcjH~@XxGoN|>~pQFKaP z`6Ov=6K!EnxY1=c7juuCj(jkmvrT1edKImlG}K9#bFIwC{HBvA&wF2HrgN~Il8op+ z>@<(Me0z>__`}sS+da(LUj)>XT|07V;LKv#S2jymOVkb6cy7(Vh-Yf3K8kRK9Bydt z&!0-1WCmZ<*lAeM61KDZo|l)GFx1^>xiYKQ-eA5!UCCn3>wS~Hp)-<1r&?zKQzV^A zOpe3L{A~?D&0v+m?My#kq2^yvLEHYcpKg}BJHpTvv0~kY_Zqa-w((X~H>TBWL*#W| zpWS2CG?G_sg$F@Z@dCqfnU27%Go~4ISsz?W{2epBiLBfaz2aAR)I>KG)SP76pT|M@ z3Tkjz6*)13Ye5gU`>$*RqOCJkiyk1XL6AuwRurd2(yOdn-(&W`k;}xmOgx1b3tr@v z6~Jzwqg?@t{zUeV(mX#CMA@A-V>-sMx4Ls9WJsOgzRK}r!7VNjb+u2$m*z@nE@;nA zRD}%pdKNSQw?m5?V-S-idAgs)cv-TjNf9VaN$Syf(ln!Bvm+_46?6)naMKG9%-$aK zc&UD)DIf<6(Ym-GeN}VsS(0|STS!fbK;VMMIzmWgis-PVKQfN&Va}?U=<-|sP~Nh_ zX_K189hV|TvjrBZO-!nWz75f=?qK0{AyAy$Huo#Opx|YOR7Cu6_Snwq>c0 zK0Gh>A2mVEXRs}b6rGQ?6V8vYoZhHoV`KGqpI$_d2w|F?-q!CF3kMm4`NP?gvV8&R;gU*`X2r9 zh;%l<|6c2Kd-yXF6A3XwR-C3S5`->bR?Fi`C5X zZa^yVu^&OO(r(2dqbP!J`ZNltoqnwCS6Zmr$6RWd-saKdHkTx+bo5sSBozONEt)}3 zuG+)usC@GSHq;cDk{KETBs#0;SlSwT=K!#Yi7^w;qB1m+BnWh#Y-*~C=><#Wya18> z`Kj_NS<4Fb6F_41u=q}YoG7R7h?K5n6SKVAymb1El!o)6Mmexvd{E^f>=!rBvXwiV zdAS7Iv1t@h^@+lQgWt2JynN0&+coaMt4F%BfiWvk5iL!#Y>um;NTLxov4w8TA^pv< zk(Zt~`gpVMt}jWu;8#8I8g{mVe9_#(f?31d9KbP9b0k8)vPvY9K*i*noK>=vepZ}H zLEYS&k^=5aH&%;0qe7uhcMxHL{)2_#gJssXUn{h1rZEKf>O z-+hCfnTW%sh6N@RNJp+0H6kKl0Gt3}Nl8ijjs>KJCQsw=4TTT@fKY~!QzK2N*D1|1 zWqEF1$g#u%yftp-9jggMDiuJ|3FXbF35!z3Ev;?*UIF3v3ou4WNePF-@V$H=ex+#LM(_xPFA*hDRnCE_F6X zo~q1hHqNj!vPA1i-KGwp9SI}Wy8BZ`-lb>spi^y##|$Kv>{+7#yOC8HwkYEfC)>Pf z3oE%+r+HJOc)c14)3Tb>JHkwVIt=GV%!GM2)~s%SnnyC z)qMQ@a+Z@-cJ%GuYB2yISdRLF=u|Kudgdal-}siR_S^+iR9f2yLCk0IH-f6$4RS%1 zx?gC0lWWKLUaz0uNlBLDAT?v>dVWZ2Q;c}HtE@Jka^?Iyjnz{V%tghDPZQtOUZB}X z``Zh%yUtRxZT|N!x$Iw$N5f^7&_h&qq>#bhQFXjeZ!v#woC5hS(VeVnNN!?VAlV%; zS9$vCuW1azBvMgccX+(n(+3wIA@1CG;5UXCM8l52_43H|DO(mZnym<^ac-UOtPLuO znNJ6hb2G$Q`nF8^fro~RX>aIsH&OzEg5;-dDqC9>@n=mNKMOGvS<5!6BzfA>l2i5w z&3-VZq6O&nkh3ie_S9PgKWLuztDQ@)*~bWqrIAH{TnP#5^f5!|e2LX1sm|@9n4FiB z+uO1XA!AOKJVs5oO__15r$aSUjZ+xtd`#8IP*ME>hgUg!U6SC>L9nlTlvomp^vfaI zB6Q90#PPIG+8D)xD^6M77xH_2X<>e6EBXV=)1;+$lO6fun)GGZk|dS%&vy?=hUh;+wrpT8we_R!CcSKifewy3!XT(ANW;vWx>*JlR-ByB&oB$?P9Uv z9N@y5(+UKEA;(P;d;41K@s+_aDPGy<8B$M8iuT0(r*sitO?7{@9JX|DL}ZLP!>2*z zz7>O{N!0B}Zu)gD8n3NxrNy2?d}T3_7*J@*bN5Y;4Wlb}8P_^xjOlfaM zYP#3)aqee6hFm8wGeItAEzLDeGIg6*pH@CC@W)WlYhp8K!ui z(3Mu=zAl=Gt`y5c?9B(O<}Jw6wKlB3w#|a!*ooGc-mg0ZU)oisUpt9wbo(GZPSiIP z<~AH=WOADHhp{@U&q}G5rSeQ*&+o1`b^C^3qZy}OuU>}xdJc7>E$g*9zg$e&-+Ynd z4ot1nD~Me2O(}elKj4U> zl8?HKQcfh53#;G;Y0ju!kiQbmXia7auJ&euF7O#=u`xC{1WsiqElJZ^WpZ3Z9NcUC ze!UcPR4uEIze^KZ3%UKe_7dm22e^>908%lD#VpJ3O1a`TitY(6%V-KL-Pfsui7NS! zg$?G{s2N4L6>WFXjYL^~_WMKWw|!&yZ>AqfEiPUvhhBx~Z|w zk1lPQYi-W-%LHDa5H()rOj;XrmnUhXDk_POQo;CDI?|c3i-y#5H@4b<1HC{K(&AXg zhzE4I&K0$<*x$eD68-+3p&AsZ-^iMU@8>gv(U57F=t68$D*ver$d4o1?|B|b)*GF8 zZBA#`ig0Un`$p}x&fIGeeG~JeH(}q}E{P1{aj86{%ODWPj7@9BE2gV1dkM8(e->Lg z*O-VK?((zoyxbW4Q0Uy;ltNDT5ZH4RajqaBqguUG=P`T#UN9q+j!lv|-BONh$7bji z<27p0cA2F9?woUp{akJKMsF0zzO(CV8B)Y%^TH6S;rlkQJWa`2!_SBLg0SqX_=1Ll z-Ie{WYqtt(u^u0YF>XgJVefEz2s&}FXz9cD z_1b`-;YS(XAh-+u)p*diu<-DnUe>mS3=Ui3`D*QI%%SVN!^SUk`zvv}#d?j#$c(nKuVI+kMytWDbNTlxLF15id22aDlt`^!Fhg5(qM_4E%E5&f^i%%G7zb^N(L7&6x^lKD8pO1lEkFhQSgK6DC zAbZ|ssw1$M0gq;1rf(4jjnQ-(oJ&k+DUIWKOf67InV<=pRm8Owt|QoLAfPtExR=y1 zs?(beCeq1(fGacyMmw0UCm`b&IjIqz=#$#{gM`7KjVQ-JQsVx#Ew6lKmm=_iD1red zvwW{_g%f-I*<#LcqGRp}!;ap!M}Dkb#*$inS36bLM;^D7QOP$99W=m0W5xsO-~49$ zmd-qyoRJ4Hrp_dhqz8^a&23_yZ5m%G6f?*+?x7w`>d%R=9>x1pKJeNYIfYn$I3Oe|!1zdu=7Ralu1 zgzLkbGcK3(8P;PJFNK`}7bz55GW61cZp8?MZ($~tSO~7CgiDPvH<&Ba37Khi*bd8I z-5A_#>B^>nWyHL6o`t~X1cxzY`HDnrOvDDWl9{ug8d)XQ|w zu7J>v#TJ_&4gFFP>Q=a77DJn}cBf?)JG^J&EQdO2QY~35rK2Y@fQ0Y3?}$tjMjMuN z>a+v5=o)<$YrXQ>c^|H;tLt)Q)B18ST}&Ai7^v3b^ZUz%xpl`~5+EZfK0cpLt6eoe zQ*OkzRcZYOS3d{vP6cxovO)1;9=$R4OLHhf;x%=56IE)>j1;C=%@5Y(BXTKJ~ z=^@ZdU;KS)ZL7s=BH_)_KWXIrW)LB}{O+8`LF`YeX4RdUsjp^6?!+ozebP}`_H>iA z{)nN=igepaZlE3Mu_A7}=FZW57u{;t&lL&B={$sI}f0? zoB_p1tR=FOu5!1iI*sGvrKPN6RJu^C*w*8{EBmO1k^Qv}#u>FPD{eVM*2L1zmSx4C zF;!kCnucm|*T8x++wzBRJIYEJ#^p@*g@)<}JPk35*7}ueMVPH_+opV~Gc0YoRmvEK z=C>aErretLX$q{1UQx;BT9Yo=Wz)EG+YC)($(!a}+QY}zrXD1d8vweqBSo#3OcK7_HN>aJW^oCG9U zG=?P811Jscg50LU+_E1dGICS2K?Ir;(aIzDY$mSb-a75lg}wb?EDS<+yey0GNC>}L zy%!2RB$t91EmemO)ljOF^&J_Sr?7q;b&A|ICQSNKV-|7i?W>{B)xC9mEU`AZbs3e` zRzqKcDQTrhF3X%Xb-h)J7V+bBTq$%O?Ck6$#(1n)HkkqiWBS*1Og_(r%F3&z#dj)V zM`X^b+Rha^$A%#CTdmQ#4lARtz2E1my?)g%Spe+q>yM4fy^m!Oc6++jM;%~Xhc}o# zjn#ZE9*MLm#M#;t=35vet>)s3VJ0Z7hY`$T)#H@6q>RZW{`cb`H!t=Z^+S*e3!Z4>p|^i^%D+G0plL?tFG>Qx0NrqL=TD(B@Riqm$dl z=Od3}xhp&6&StmGTb=42PF6G{(0G`u9J>nVHYOq+Fcn3NwQlR?N37zk*tl)V zfMCKD*GmkuZUHz&g;tjuH>lmYOF_JpaO5|F9f-H*bdlHfAo>zLA^#n zQ3^MPD@{a_W~Ft&kQ~?;)CH{{C#v zu>r~~x{1&>z zjYLkMa-0;rsdeD!(@(ip=6znf@G6$(99ObT zv}|7a4XJHx<9lj76q0Z%fnbZLQHM~?WV%Q>k=K~CBt*U{(KyboYvKJWCw*U^fA}>f zxAN+?mV|C#V!*Dv31uqDRU|l9LPgMZ=E>yX^%joz%q?uP;afz41V@7E{ z3`Ah(x8v1puHgByqi(zY-s0YPin>O&Uvpe|(sVdXSsEPu7S6Ob^QrqNLXG31Q*D_B zcHEmV0TF4F#~)(d&RjlRhfhd&0-14Dw}_xl-UI7ASzgbk9-m?JMUtd3{R;ev85%7b z7D<~9^LeK&{IT2zX!OKZAU|eBij>^BuJEj0?A`;p?rZo^Gtn&xk7Zlr+p{!xSUgw!m_gA6UKFOIRSijs|*0kFxYpv0S_a=q_{wXn?#;b93!?yX%y@P$6o@Jyrne z3s{&`5pm7=tnxO(QLR_x(%gku?182=^;^;37eahgQ+i1gw&`W0dr3NP7G7khgcHT| z$X$$Cvy?y}d%OuLt}CxYq5y zBH@@@D;w`@Cc2YY)AFob$D1akXpoJpBpb<=fuhSSMWH+nL7WIUcog-Fvk6GzXy^sar{!+xIzMU;jw9{5>8s2G7Kx>5a-dDhnhW#>8G%Mn{b8aGPi^zd9uiw_yF z7d0o-ge9xghTrs{OO9-?;~p~MPmp~0!bk@1Uv&lxkq^bd!Tx2l^QW17DWy8|q*}C! zTTQ3(Ed>J6clY6)S8ao*?L1LlM6{PUqKtJ+W~=2jyus>MlDa(UB&Qv}_ZAVIODK-iktKPkz6u zFV)~gJ9QE*X{|Ze-yvbWepP-y3J+qtzsw{0Siu_Pbme&(`#s@x&&)c`vu@Gavoq0q zQ>s`z+rIfUl|{Jr;$iagY#F-fu}K+qD#vC4=Ae4!;q=;V)X;+TffgdO>Th=)p$DG` z!kvh+{>VDCY!fcG#A5Fkd32`+4V>jww zBkTYs#HjTE8_EFTokw??9L~(Rj(x*JF>}N7LGv%aJ2p0!F}4_fA(ho!qch-L!e`8V zJc}2uZLJFNzOzS6L@nclfKyJc=qaus{fgE(oF!UTnCCBN8^ z{!elBg^(FyC8T3e!L3CE=MC!oSCS@@LMY?A^Mk=nJ%~d7VEz3TO z5}sOgt-(u`H1l|2{{3%2v*nDb`_W<>K}aq-E=j+P__L;3!1nrmg){On0!?SL?f!Js=XzE^Zin9=hPd(YCd1fdVjJQk$7Tr9A)q|()na5F}`iEo(UnN|1``0Ek2OzDG(#KpQy z?XjagLfou3htwwm(i?3z6v!}=*#QC*$k4kN`&epRbIicPSwUeR2nNjFN5-HDz87g2 zOZu%yODnZMi@qaEe{cZdH7<~(Zp69T)t-tVf-eAA^k3>Y9)v|I~OA(zs7D3>|ICaU}!$~-QOnCYs8 zg@vI4LSecvOiWDV)0Jwnb>{bRCpkSs!_nJC}4uH*~ zji?jNfEbI3K_-i+7dYoq?KYl(J{3-1(c!Uo^YZ+&PvKFcyy|%NJi4sImT7WfVqpRP zqO~6kvD4Dhvb0tOhecmfOZS|aIGTyYmGqlD~#qm=*PseZmcd?1uC zm7a8NkYT95=FRheJWc3C@~!MOQAj)I2wOGX2%_o5POSVc?$@z3 zp=V=A4=;K^)c-YVhMGV}sl}wY*TI_97^&R=DitrbQwp`eIkVlNS~J{YUR-}$HEXS% z>{n5K(HCIaq`M`Z-{90HlL-AcO?9aIc|iQYs6(X%4efhQ(QW;?z+YeO3?yE9T<=O4 zLjAR=!|>p_9wgbctaa@%d+~LiC|Fcx!!VeupvhJ@a8%=l(%qV0h~f93(_^Czwv1;^ zO>JzLko{v(Om)X%j`Mv3sPA}8{iDzh2W|SM&emXv+BoR`bBp{L^jp6=lbd#^=-=7l ze$oxZ(%dk^5`T5$o` zZz_c0Lell=TtE|kHm`dlSG~8b)5C*^^8)-gv+4LM)XTl5(1Xm;+N!w3TcI3Fu-D8T zh|Nq0?auVf#3}6^k(Bdll9`DWj6v-%uPCo>mT2}_PPbdib(b9k$IE>#i-pnjzM4xO zekcT;4dH)aIOSRqYSO@by3j|+0WB|BnXTaM+FqhD@7v}f>2z&uo|Df5(ch%Kr z^+*MW*+#7{|6uR}L8^LZBNny&l4kqbP5;fUt5yVWc3#*1s_*g1C3zdEogb55qf7Nk z-8r{}(+ecEI$B_RTjzB735T)WkfwAXTkALNiGs)N^-YPD+85WWREGm$Rm`OTUTpV{ z?WMkn8j1Rv$MwKUjwi0HDyLyJXoo}}lcF|B>1mqJ;S)e5yvhmcRX9*}_&<@YPLjG+572ami=iYE{d?204{?(R`R2&$(cgqs9Fna&?>5PzwVrz} z%8R~m?P~;vsdDWRY4R zg()|z#*M#|SdF7oALvIk@%UDT7jKE%kzy81&kO!5jzaC%n*TP^d&pT{`U7sd?xL>& zYb3VO4ed#ey+_#=KAfC|=&$e`OQFR!DAQyp`z7!S^K{t_>%QEDkiH3BZG@-m%K$Sm zO2au(1|44b9eUrxW)^!R(_*S$e$RSiKf2OgF&qI!FgS5xhmD%`fVzgV zI*l?kfRmcPvh_rPdA*VKcXW02ygxsALPr6qTV9TWVUoDUg&c~l%I1T4tV^tLd$k73 zkjJ?sL-5cnx!|8DV3rf zW*0~-radW1K92TL=pCsiXLSa@@uA6~7fwauaTrwFi*ndd`a8i%J~L&x@L<(c3wycd zQz(9U;wzT-{>k4)Obz$o+xXUlCTB^6N1y28!!kQQ=~9MV>X4b5xjA)er}1dP6PpZ1 zV`*Suk;JTCJ|nUWK9msS$APZb)92njxC}RL*^hl`W&fTsbgvIXeVn<(`COowbvuCVO&#B|AE|1V?8X}nsBxO7$DI1|rC$NTluhN_36S-lG70OU6fElRbVLxDqv%`%*w zYJ-~`-&aFQn0GSz%JDt@aKB$NZh#{2kk8lrGFNN2Cuw=H-m&}_`NN>mSq(9`dYzb1 zU>@$na8umDB7@;7nGTRHn|Vf^PdQ$mTP1E&24obQ8_Sp$G+0eahF=3YrWw<>ryE}L zni{lY8ZnbtDhn#4X@=xfT*s^`Z5on<49Rv?O3ASbV}p|T(rGJ7Wu{N(@DVDcm^Jm3 zHtgt&+2$)9D~DAK3vBfz@-w>k3Tqlyuqk=Xi^h(~s<<7h=BCwXvSO;wG0L4>fNS>_ z^UCT#R5^QiSIy*BBb?a_KR+-!e*O*DMIbSEd2VLf$*DqRB4>GJC8IkWQehGW>aBy3}aGZk^lR9l{wi&RN{tEpL+ zq(v5*D__oZ!0B{R`oG~HC+Q!-6Z*W)B~Cm|=wPBwLwo zm4XUBEn@pa98O`q>4p|6*;(Slj~4J1XfuTEXQs zb9S#af?4AsoQoOH(6=pNT62cL+^CW_dPnoe-W66Hb>|fuwV>{jV;gbhz^trN+j{!1 zENt$1*OpXdm9*2U3yrZ%=46@E(fws46$54N!d*`)W>$?@FkEfXlLev~-AruK%4(1= z+yR+r*lp!cMafEq=}V-0_9qVZIDLKcrffIny5l<*!rECb79KF2MK^^N?@6MgJSGlg z(7ruFIb~(Aa{O;B=N%7P5#Kmbqj~RB+$vMEWW%$~%ysLRE(b}XeVUGw2*DvSKmnso z_WpRO(W9)*rS+Tp*jan7M2gbG5t{@DON+Uo9+vX*M(DbavR+II(iF&Otk|D>E+*SG zJ|&jJ>fMb&fQZpzG?8DX?qs=Va6a}-Ub3$Fy&`aw0TcovwdO)AFt}dU&acrwKWvnF zu=j@=o6`2u+$$B-PZGZ)?r&ib>5Cd9P!pY7-_cAoN7K933$3iu_+>pW#Qqmjz=85#Z#MBwe&5XUL5%{{O|)RR&buJYA(j zLJ^P#L6Gk5?(PQZmhLWTk?!v9ZUO1;7P)kHzrU-Gf8P(>56WFOW_M@ioH-k>6S8afT=QYDkrd&aveAy>A+ej=I z1xmHCA}aI8$VbClOk6RXVIod)i4Cx-HL*bHm~$Stloz)g;h9>vTC=2GdofXFDHihT zV$`9jdo-BuBxzZH$Zd_ARnm|3+IMzq<`*j5%A)y`p^)Jl5IVy#8Rd+6Zy}9>&Iyh#&5i5(I(g#q0TmrjD$9Q`(Sh z^2WI#|6n9CN-n1#BLp3?I@CTBUDUl;u&b10*AB7-L>m<^XN-Eqa2g=#C9ktaZE-c7 zUNj9E?@C+L_AoHLU5Zp}(h7IQ&d%B7ueh)UJwThB2ftTK7*5T=vHg4xBNLvIACz;H zD80Z^fEkxvoDE@Qgzix9KaBD<`BY+wWspP*Wce|G{R08Ia%@4iR2t&sz&pchfWZN? z3TALPzWuaap^0$GTxqrRVkxl1R(hzMQ*-`^oCJc`#Qj(a5kxiivfP5z2b(1ueO{`(-o7Vm*TRRkPLPLJ#80MD4_*yM%xJK zn@)+k*p{I0i-jdCIKZa1@ogl%!R3_6&X%M`oIHYw5}qEO2-3J5l0V1Kvt#WPKtXR0 z%8EUa8n9rKLFH2RkWpUtN!rxPt3_q5_^=tP9&k#wRE=U(L2ZIutOUsvdMH`ejj7IV5CE;_MYK4 zSe3waf!Ur5uf{h?-vM8$-J+5c%v^1BSzm*Mj67=^=H~ePBbvDQA>Lj}w-Bc6$EN&< z6{A(e6lTWmmPXwJ!wPb9p0q{TN)z9Na>qdNg2dXK7+Fa#JLY;5$DESIoWk?#{+Ibo10%`}p5d)k93e^&71)8l`PA-$?##V(;>v6jUPyD70`?}S% z;G;senjwq^m72UN>B{IF^&y8D*!r8b$YRElYTu+{wW>R zTG*I1th_VFJWoAV*Z(7CF4P8!s0z%%R|Z9s+P7eFhS{*@2thHu)CC37nn7THa~P$C z;B-C`Qc!rqbAP~C@c@t{u)w8h4?snO@TX7Np%%>j$vErw~i?1B+>>#TA`iqpOnT) ziP1PH=M$?~%W?1uZ7ZV%QIq5X8Wx^bO@nkDYw6Sb>|DaJO~bp>45k@I*}VE1@mok| zxjOs=dr&9WK)xtx)|Z~2S z1~`$d+71tsZU!kUz9X?N`vB+(G8&nTWrs395s6sKt9Pjb$&ICqjh$hq^P~;J4(@?f zU<~*j{N!QaS9s@dW#`g#YTC64wb#eN0s3$8AQ@_z(}N;waftE@6>zJLlT3m`@;zFT}My`zX zT?wD-!{`p@rlY0OJjpr22x6fIm}lyzr(7I1vtSNvEHA!u_FyPPN`eIDw_5W1X*&W z*S?AQfm?Mn&Ck_zEyz_%j!!eDPT~ExPHfHQ`>FqPn5XM4n>_R@JZHR5@w{JxF+@Ig zwN{o1QD4dI_cWCET=|}^Fid?e4Z%TdHy0I zw$ZnrwD)MP9}Te9L}HDYN3}c|+wWJ@gf`$*`V%YJ_8SLh9FFiT=WF4EW#dam)LaDP z&(5Y?8gj8)k%Tl)3lLfL2K;s#?U&>QN9@R<+@km#9qZ1jyTTyNG~LJRuUwAXuD@nv zL>UdmJBoijYEzMv{%xx^5ZOsrAfgPI_b)}^Sq@;%OqD%OHll0A=j8(Ic#~5mq!6R; z48OH3z4h%013`)l&o{)egt67UFyn2(-6W+AF5^O$S3Mzxq!E4IQ+UpC+Q%-kc`>dP zxjj1(i6jo~VrEf`-!UAzE9h5ccu6!pj?qp;(T26VpkCV)kcGRQmoswXKqa1vG!*2a zRGtwGUsf}KM>I|h1y11|EY?Xbu%26|QgL~2QR?nSH|NUzo^)9>)DZ_+wD>dkRKD9| zr*7-P@7ja%l<)NOLD$~&J7%FDFy z<_S6=Qmzg40~vW3cpyci@J~DTW#MSAD>NcqGlX2Av%GEuOx>(R%!&|+d7|<;UvcSHuna&N!_m{hM2%Ew}HR8 zS!=byA+JO9y@H?Cd;W$?@~eRV`pclV6j4GiBt-bIGqRx0-6g`B>-Tn`zdRecsyVFTI#d z;8j*y-;vGAcBS`jRt{poYF6RR&9?a}(_<_hrR~ zqLX|T9pAS^CYxE4c?w9Z)w>QpKhLEnk4hn@ojDnaq&Wrd7$*mTBcFskA zGHXSXt^}sEIHJ=>f1efWIqQx`rwA6AiQg567}!@XJ%&#gY@dqw;BJJ$p!g>ce-5So z`WqKw3-qsN@7^yb&$x9mEp84F-$;DbNgO6H2cniw&cBrWkuKHLCdHVz9W;_mwYXq~ zq;L)c_*;r5W`3ZKMIv<(Syu3uw~X%R6BW_Nn8kc~{f@ssb%y;dBUCLsPV3V2tA8j8VYLosL^4yH>!3x3U-@d4?w1}1JV$be z*}*WeOs3{3&UblJW3;`T%vY)3!~H_tn5O5l(}$&t4_re>>JJP?AMD;htO#b-*JA)c zCY{(<6|XGWRJND(5ymwi;lYQIFCQ^jy(D5sZ!c<-t^VMJUBbv->m0uzeb6uCC9{MG zCX=Eb0=q-L3nBl`h4zW6p$(Dw8yqFiCL<*4uDYyZDA#&edT8p!Y13dM(~tFdWI2W` z#SF574h~#m`w@)WwCjr%B2&^h#oA|;6j+62#Y(DLCicBHddWP^61}BB`{cq*42=Un2fxrOrq1TeBi&hX$nx;RoIiX(9b<)AeM)ak4bd7aq(`in z<|V$iFd|;gG~Qzrbkhe}gV(O~wyD23oiv!(`%x=&jVB?7jP}0G*TM;kh$z%MvS~Dl z#7(`|;6Zqq0r&89-JzONTB_5AP1}C+-fti#^0!h$ryddEQV2?X^byF*sh--@ z-U?xN_KG(k|86>pWhV8%f{pt5=5FUad~>^NW&o{S$y!LZE|=2 znA_e?^7-mOz4h^G3BU(?ehtKQUH>V*FN%c4ci~H0&*fQ-aP3JQhp^MsBEqlWQe z2qv56&%1et$GcMt3E4F_JUjJD3n*|m477j*21+x7X>w-^mIf6@UFTPBCoR_x>cCZKa{(}x1Nu3N_>5~YUqdn3g2ISIqWogayZ8Q zItg}y|5)4#v*evTJ0`7;Quq0>*EMJB-CoH@N=nEj*6Net{)2^@*Ka242@G^wUjNWV z(8UsKPA4L?FEMF!s#Chcdz?3jA|fUhZ0g)Me`=Qia{LuPB-;cs4SwU!`puhF1awt- zd3kypNhq@dHNi+un!T_~Ts3RkN3VPz@^)lo6jbKme=P5D)EtezCLoC7eb_)TXBA|* z#TMfSjqzbqo9*kp2T#Z;h@Q`++TUq)Xl%?GaU6)lBtCoHYy_V!+mGqbSL#F7noL>( zCU%z$Ca}=P#>QT_-|w`8NG^Y~UJ?OB6Ly@dFzYbiiUsHC`1whe8aF&LvJZ%|vI2I< z74t95Wg%|iYgG9qjLP7CESm=kSv&ap3}?`Ee#;fe#QbmFbyCHilr8}hoi_X!aHeE0 z8hzgA4!i-ec7(JXx8ta4JI3(}J4vPkX?F)iLh|o65DX0uB4k)0KEnh_Fanw0Z~n~s zM}~-&7UlWA(4zBwk;Z?8yv1qCaMK|2e*z00i(Pft40r5J(v@_SoiJ>*Bs%_FrFL z=do1ZRtERwd{r_u9gsUJA=`!VT43;o@D9%lhK%7O^kxMBeSda!9i7O71IxU?cEEhV zC=iuyJFW+}oB)`+F!&D2oYTnq^56%DImHGXR&bTR@9wZTNOo+LfLt5yMiT5g9UfyI z(ozr1(Y(A`8U&~fYaWc)*w`lkV-o{RB&xDrX@2=ZtYL{i1kCpIMlTTA_p>_fSpe~y_s7MY(3vC7Y|Oy0%E21qia8vr+goKzOSPxtu8~C&TMhT_AkHF6!w=u@uQYZ$V9Igx&I(LZyg0 z>JK%Y;|dY_)W<*FxY=~zLUPQ0)Sg3wJyc~S%!M3Z6Q49`8QEVC+5h=K4<m{eywb-dgW43Qetd+Qn7UOZj;GfFh=DoMu9q&vE!Iwl{rR@@DR0QUANjvXFGo4 zxrrWQ!YtxT-QNZb22EOsQ+d7d&CJYx3=bTH`-+#x9O5?N>Y6t~P44 zw|<{byWsQ8b3$!MjUPcd&jP91xevRvdqNhFv)mQlTD|t^l%yRes<$Tplh!cl+xB~? z+43;~hja1oFe#{Qw`QdgC!@PG0yMupBY{jwD2U56iv&WC=4ep8$O@oH zd6g`yrmlD_xLV(=ufq~TFXa5Ir3 z+Mq5Q%sS?Xy7b*P@MD?z_X9JLnlNK6C5m5RhbGoHWTUkN`;qd!jl2WDd%y?hzzRS) zEqt-hFQ3<8c{@ug+Cu?8)bHW~0c&vzU{`cNsVNTo(GpOhwv=m|%yH|qbS@euj1S*- z_w>*M>R2CeTSloqo;wN&1#r}4T5^F?r`{=xc^+BQ$jD3A?J)bZfz~NIpuwDZ;VS~r zg=Qy*#Rr89&I&I7##bVPh&lnplzzR;I&($h$l$|+Zwb&WvrdsLX#winZUC<{ z3QW}p?!Csn;u4L#XdK9>&VNj!SFL?0;~1Ejn443wyvh*t8JhUZ)Lu(Hes-RoZIkbU z0|Gu~&3|}zzTmR_xd+KQD=84@KbPe^-Bx}%tDGa4ba$5tU-d(lfrykDCQQ)2%5_1D zsm=La$tRVVOiTXL&?JiEvMLq585k;Q{D=blM1R?^>rY)28jYrKJTq&#N*gXuNv{Hu zYy^h0T_x05-x4Q)V>y7`CJa#SoFT%QwW)*%IGv;p)!6LwpT^2!EZ=aW!ZD=_?ZM!D zI=F`suz<{uXtjOa6sgorn{lhRaSe!Ysm{^1o+AJFrkm#z4SL zhb59qeQS7J=TR=DmJRKtzGC#(Pk}~LFmI3Oa=;=(ElMj#LUzDO0ghd?i9W;M!yHHi zb;ZUro0f(|hJ?uDa|XS_Vn}RW)7N!Hi;T>dF*(J3 z=p#g59^%f9!)IW@?@OQ3OVxmi3PI=VqZPGU)7M_NL%N^l%C%AXoAKlzuj(N?7AhfU zmyMbUrCSCL0jkEa)^?qww!w!hvWn0B!u>Wq0~{;;-9?WfYE@Re3_pp}P64I{SBqg2 zuPE`pSYGzDi#cz04q9|v^bQTB?brqeCkW4>MZi9mKqT8;wN>(KqT_e0?arqg^!Zj3 z6csk-<`xo$*P<32XZLh^u;$}TTpump_C?}_RUN`jzIt!@wpS7Rb?#CI#=}^0pc#9B z@38L^Gc1JiSCtMCrUjCBa9}+=FkgVhpzl=#iw^zwA9>L)r13ZVRp!!q8wSURSd%Hv zb|S*nDOGSfXE>ZIa_}}n4>Zg|q=^xgk&IXKM(b))#fOGzXC*?FOUH0TK2j_N9jPl z!?62kT}Rq5ATcsQL!sY9#%W=#G8$pqUf>Ffs#!A_MsP5WXuh}|r8lc4`@p>t zld9G#pq=}ezOhj0&}{D^lT1F{{x&L!U_O?pE9P4~NWG?VN6qjj46)Iml1JM2EgUYa zU2=TJgINPFESF&;2)B}9Px&OKm)hb8!7m`-2-himLy6z3%DZrZ2E4~QeIdC4(xyKd zE9;z5U4@3DEXGOdfy+!e=y^Kj&4|L-=B$ep*TKM&KBR>!$dW?4^!9Tf_E>5ldAcWO zJV*Z6^d>LAy-Gh5KS;aK_4X;&;39~y+kFuz@~F>D(ZSK%T4 zv8w3$6@*D{;}DcF+!`(i`LEUqzBC+$&(gZ4{>SfR7dNuE@^V1Aak#^3rhaz@2`Mz} z*lYw8_PR=>((BV-z7hHWqiEFj(Z>gQHyx_zrQ1b?hE6tZr|%ErjacFd(_EgdbxU!x zQ){0eH+N$tXnZ!UC^8Puc8jG+eZA`Vm2~w@*@@rrNYzK}xfTZ{M}JV>ZYtU6`;_=_ z8}61NVbDTlq`A&3RW4UQiMc`3EEOID?X>>!){+Tyj0&0URErG5{aky`Nu69o%P(>=eKMzZwP9Z9R}oies`15hff+FRSHL)lQ|(x)d@gp+(1bt=7Y%| zkV1OTLA??RenG--WewI#1rkcFTFxXT5?idIgZj8X;219nGp{~Po5*iJK$wJpCZ|6X zu=`polRk4>p9TLcMM!lWZ6EKNEUnw_DdSl?5bYBqfb3R<0$vt+31zO`dD&H~J-q?c z(M`mXD;o0F%sBgMwVka?^-{G}8Nx)JeBn>FF6K7>?qD^{FeGYXrQ~5)`mF;&HKlxx zeBzZ(`m}|gip{{Jj{U*YYe=b9E)9Rlc%O2Y0TW6MrWTv%vSX71lV(%rB%zgx6uqm_ z*-G-T*rYbqC54R1N}Q3_Zexum9VsIa`X=5>aG!iRFySr+G%eol0Fi0))7}0|i?a>) z)6FKc8#M#UE8p&exwkDkizf=I84z`<59M$1-ZAh!a$?d3yk^!0YRonCjbHRnJDS}Y z3(@voNKkN)+0}k?9##y%@~avd{fV<6Vl*4W*yX%XR)(Eoi=QQf;(~UrbG7r#M9tG8 zujmSE9pH#vu8jkttoJT$b`4{)&T``B_#XRF6-gSSoqIE)XC!HB)I{q_!$T05K=3R+ zBQ(0Do3*?f!qoNH2#>y^wRjvmG_-%2ao~~$j}Ef%mhsi7r|NKL+ki)0V;F%w2$--k8r;*fW4VT>qypK8 z54!+~bqdT`tByjUK{Z7+hS=SNG_o1#8D?-I z_(t=@{%B&+=_Y#KBJ?s!Q4|#-yd*)R^J?< zvDoAe2n`+cyNJJ*gS2~ge^0{ik4PU2xBNr9jWiC&$ztV_=^7K+H5LfNDr~L$!rLzM^WeBn$!atp=qgqccIt66haw73K--yjkA~{w26vc;j z8U5Z0VB5n+|2#q$g{kAh&${BE81d;F`~Sw{(*mVMKitFPGT-cIak$#1DV@UNsERlz zXLYOSfzb?0aP3O54rwXIkBB%22-w9Da3)4N`ZfBGLK^^}%?%UUY><+vLiBMXv zrH+>xWXm+_5b~X}O8?CkKjC`l0v9%mWuZ;SqYB``@{f~r$qN~4!9_YD^eNccKpT|E z8v~7Wwn3EdB+*b&#cD@KA^orI7cadL8sB$YE;m*kXiZr@y%v*}R_^e6e2BqnB3vgv zdAwfR4`&=bS?Ce{(^m*hA3cuBB{D)uXFyennssakG`J(Ow9j}Qeu zl4U#5yBq+aJtG1E!}F-l^)7#OEUY3xQcnxOJ|nytK48uvk^rWjpJF`;DA4Y@A-?!C zv!dWH0#3>$5H!NbmO$}~V**4#8tvvn^%`S0VCMWXzrUrIg9b8{CGt5U za~)60LkYC1`?F=2Kn_3!h;H?z97n#wI+m-et0&VN1c41h+j+%t^mCSWi__&)jQv+`s4ps*oy<+wWz7oLi1a_`3}*(;u;MN6iF<)PN1j5kP}G90Vx! z4gs+?3ONTyGf%w%-|WDX%PEZf)rvJ>VvdX5?Z70b5U{>nuq#mc7}cK>#rvcNG+NGL zwfL*m>1er#vb?eTmfy%*Y2kF-=?f~(AN#%(?qu$|cm{G*9Gk8LuD_!1CY|_Iqk!78 zVfe}?poWg~*eTpH7#VLmM-*jy>~ZxPXb|-?w9Ect$JP8*jiAQ?B{#SBQoWTT@N-#{ z@i=@tVdzb_z+$vD@xn$b^tWFkNBwdRYAPuyDHOQQk5*b{GWq?@vuvxqp$;JMuDaxG z=#1B_e2fd0T28tV(-P9?zqcxC5?#XSueg_UA(#cHh>h-vye@;#v_(syi`Z8nHL$GWolf%xq+sv2CGKMv;dn@r1 zWB*2Ak$zm6o0ynzZmu4Nqp&pA8&n=bM%9xowU4rI<8H6OEEi;Wxp^9#YI+`g;&ifX zN&9vbM%(^*^>DkQ{ce!H{$;Ianky)2R2TU`7li{TH#>vue;PNNDUMqk6cqWhrJ-tk zO8j;|NXG8~W!U3D^oTud1=_1#TD8PcT1q&iuj9|W-a>kH2*J&Q`T*#n5o{Y_9-eyD zAA=7n9g{T2S=$U8X7jgi1*Hydezmr8;qiE=0ZU=$cJ8@$_yo?@{xWh;YFj(30@$^& zw=aJ_^4kIQjd-Z>2pm?Y%Qlg%KX*xCIJhIkqLFOAex7W559ebQ;V5Q)N}Z9rmoQQ7 zzPT$IF)@myFD_xS>P44FS@a7`Yq}bb_knmngpsRIzb9x^FsA*OrOmT&6q&b*c8T)n z@!;{P2ZfhS5qeQ)<4ouF_I5PU>)xQN{lH`8sx33KlN@;P?05)fa`kM10CtrCru}#% zg_ZX9^~Sw_H_Rj#)Vo|(mR=F?dcgtv8+uBGm!4i8DEIO3 zJ_FOZN;r4D0@LdBs)|*Nhvjp<>mAHeJHT$YBp9!uwej)l!!n8WsP}bq?P@z@CMD#l zzr2YA`@=qbhlq_diS@qGhjB8}(vB)RC7yQWUw;1=_(M(?N~;PY+s0slH$oQ3_d6AH zvHb58e+gAgyxyE{7H0HpXj5=`cfa;r{t^;KsXxm8zbhK(y%wNdoo==?>(mznq{=OT zf*%>Z!=G0Ld;d=L8Pv~4W3_V@zkWn>9Jii-FaDzQdi7rm#qZbm^cF`&Jx+TuEr3&Q z)NWSUml(YD!3PWYqZ}8sVdc~kx&&Zpg@C~9!tT)g-wP0!zrs+T68C)SSfuRWS6Dx( z;vRrBxpHr0?Y{IY;URgFaLFef|8F;o9Yq}$l1s2 zxCvZ!KjwA>MpNyT``2lLLj7FjQ_?SWU+)f(?$HSdDsZeD`PaOk+m4BNexHJWdMsD| zf_sXG%ITpNDPOa}x(q1Xxy9We{(l4NM;vE7GPt&yXOTS%-`jeZ}%}Zb-?d+9Qs7OeJj`BV~ zNTiE1{B7)o?;Qm&z`*!609;AK-;+t>FxwtY8=ZDlW=qTc*RxA0AHC*KBo2VjwNP{p zzOAfQnw429jsM^36};%WAg{f*@jAx=rc%tQK59cWt z(n?KcwE>&F=}fV5^TXMYEIpabzxLo%USe1k84tCc_Td1KSV}EWCWC~})tKC5%kjU( z=A(nJG?ir5++Vb)m;sK!VomENVM>+KLSyUHKU)-RIv-zdy__AgVJ7 z6nH9c2K%X-s84XAU?*TgA z6TXnJ;HNjXe*L+$Eu zwda}jxF6WSKK;+Fr-5>LQH3#mC>N#sxTm_e($WCnYz38-l^Tr{34eYE+`B@vmu~qM z8uP!_0B0P$M?@oWqw%gz0jF}VTe%79U-zUDf$r6Qwtc$l`VLkJ#0ILMrv%f!eN&KB z1u^_H5#|q_bWzFQ$_Up0o0TLM2_J8}Ufpk{5E~@=+qoCMFTjJy>8nC)eTBm!6Ci(m9`$tag6Z!Zwu0Rok?uG?t^_}p%62E`>w9}51R2GiBR zVNmx|w6bPtNs;|R;{l71|K*Qy? zy=nb7PC_|hU_Vp%MdSYd&YzD8z+hGVoc}jQ;6?$TR>E}O{@)kZ(F=X`I7Dj%1eh!& zBd@WU&(fPr3k80vBn-6Bos;*Dtf&|h=$gxBwz3qpm1G=9Y^@~RYA}(?Xd=iu(e(d4 z34PC?v12>#r>`-L%O;p2R74DGB#geL)X5`ecq{7S4=X;mHD#3j~O zvROs4@I2RIJa{I5n-!G^&fh6N%|V}-RHEh83EP7{~>@1kht)A{8_8&&`Sziqur>ArW=Stf|7wDJL+NacEO$wg_&_MrA)UdXHuhvq zgkp9oHRXka8|R9rBthJ*H>x`USF_CAvg&ksAbXPA>2ATr&qbv}u5kg+;3&CeE8>}{ zRaG*JetxSu+X<~UbvpwL{n|Z;KrePG4Xel&8}rv;lK)*Pe0k6htBjAw+oIy|o6~ht zZ|$ubwFAXIZ4PZ~ye4Lj~v#q;}a#KB|q4RZ6s@3p# zQ}RQ_nPO_!mMgP&>gT1nj^F}e<>OmZ z#ycV3;X~P~RUn(|6tu+ELkCkp)D>+($BqcdrX#~HNJm_X3r2QhGj~{17gBdbCdUJV zE%uL=z5NxeukCD~dXFQpnUzaOBa|FGlFvLMP0FzIbnudQitL{D)|W`3gLxF~CiS5e zf-8yVE_Rqmnvzs}b4BNQ&_*vV8KV=OhAW9Rs}SR(Z(j`iZwj zcdH&(la~MOSJXrZd~a)LEPS+7m-wR}_IA`CKe}E2Iy?v5o1Pkrv98Gy^tXvCI9&3i zp4~y7dG{j)t^V$zlgFG|#vRu~$pdFXqvDiz8kIeaqrajPRn|`r5|2Z~rC9HsT|Qa1 zrwm`7O%5|>bM1mEkK?0L*XgMh6lSvxTDYc0+9%+rMykZdlB;_uc-lG{c@@&xcY7Vr zN2WbShty0*BhE;qqRu{=xA!-|kWU#!N?+yPA2DqosPT**M4cMWQ_0G$ej7^MthcT| zkcY>SaB?4x1BoVi>Lh$4-b!HgY$w(#-J6T^cu76D(A3~8Ho zf~nYah~vkf?%uA5Ag$s9&mg1-&EkmNC#_9eaL~#G-Kw`)9V1 z>51Acd@{^q-g_?ieC<^CbHhW^MGG0%YkAvy(na@#zZnV$oz4pGse4u`m2V)^&!_W2 z7p@(0Vo~&=*3%McoX#r2^&>NzPct%6Yw@m-Glk0DWvuf09?vQW@FBCHWv#L_dZmcu z25O15;t(v%HZda+-EEtR(uK+JjeN{SYb5Mlt@Ep+d$Yy+y2H4-m7ug*!!`VsnViDy z#e?zJj2>Oi`y9V`sF~%>eo`(;I&E8vvv9A{-}PMVYP^dpSEr0RZerQwNcBazh3+?0 zOC4$nGsv^`p1#=ic5hQjyfS6Vss|vKKlh4)e6hZ)Q8#P#zIRnuQ6;ubSE}0JSaJ!oELVDPu)(Q` zdQ0r* zU{EwgH0)S0*-88TguPa-rrv419b8`1*4D&`@TO)|$&nK7eoN0LvAeML_LzG5u7Gsl zqB)s}V#A!lvHBA%Z^=O;*>wA`E4l|ge;!iecJ8H$|A`zmQ`cR%kr_5mT~vrfR3iPP zeCRd&iKLzRxJb{YHTj)6L<=d1tWS~AWH&pXbpGtAY3-GE`voW@)aiX0xA}raE(1n; zp|vL&1%*w>`(m!X+4>P|ZjS51k>BX%()UPm*~ueH)*?M|0(9eoJytvz)2kBVi|?q6Qon+9lJVGeTUh#wJk{D+ zXp?~)BW%fiS6MU3bb5={drn7PV1C_ena{H$u}^xEi`G$XT{g07=Akj)EE)M;eaY`d#f zbrsx6qZ+&=Yu%RC;P8wSxirs{&(|&(1KxV7Xi?!NzAMU*&gf|Q>BPhuSGGGk#xt{u zL7hq<8yE;%dzu7AJUxJFu6=H|8qp4!hN3I@~=CJ<}c-&FSmKJwkLW3eE`)| zc!vXwRWktQtb#){E>+a=he6ec^+Ee57!O%eZdP<>F{DBgQQiRd17#)#lZ-bVVBm zF_7v#WLTEhaOwQ5t_h?vCCaTiB(r#~nQU>jFDb>xtKH+icoswsN{7mmb)Up)(RMkZ z%v;LscMytC*S?yHZq4Cv_`&l_?cxr!&U&{BQ`ehvXm;#2G$j!(rSOY{vm^v->X2J) z_qu10(?Kjaw8Hjchn3@IWaH=iMUG(>gWRQJ|6T4QJ-5L7HMWrh$?=<=p9B=6j)OL6 zjMKu3UYZv#bk**ME-lV`-ZP&|q8T1dGn{XK*$QF#T8|Y_G)9e*tTj~!;$6kSDN6|! zVQXqQ=AEzgXVMq)j+tB)gkp@B8 z%>mAoj>?O~?2&uP=WaQ9P8KHC=K~z~Bz(7h3%3)_YlQ=;bG~1xZ0rr=;u4+u|2eaT z?k{bwt?MMKt+uG9D7ln9Enlg$E{xMwHy|ZQHuP;+jU-HBy_agVs{gs3!>>xso|x4o z%aeum=)NmrPhz)>TlO#{v>?udP@~91&MmT=a1UdDJGfXcS@ynp4^tjin?ZVrY6%&`F^m#i* z#;oPV1>=X1g~cnEypQ6|Xgn6TLH(DVTieMec5PP+E+hSgAN9+VHa0&5iad%uN{yZC zFZrjP__)Q-aTxt#jT%mmAGl| z@Ol6Hw+^OX$@n1p{NUS-8P*hhP&R=yph#o2mbJG7HN;|auIjV%5lt+(_@rbkTp%38 zblvtTaWSb!*5f28ABAOG;v~^v(B%SjxH9dy9_LCXIoarVnK7PWcD>Y(}8<) zburn&ORA~(%-ow%Yp6x-#BMt$CDG^&Iq=s}N3}g($mZq*Hna{|460*F4T$Hmm!#_j>ARNPoL+69Wk=zpy@ihXf&%OFpuy29V%-$NLnzNer{by^oG8;@ zkg{ea{RVE2lhe*!BuJW#z3i-33$GYoZKhWXVxKiws2Q?S4>J+J7O<*joT;8dmBzPv z3TQw6YM=f{HqEYbHUkAJVb;rM+GF5scdUqy($IrA-I9>h%!E!Ew%|`@p~|p!Yc|PV#Vq{6WhH98aA25l4@RCgVXXES)cWd(Kcq9`UOrdTS9h&K`_Qr8}t<| zfjpuSK6b1tN0%#}l*DvJ&M52Sg%jm-`|?e{n4|ldr+uq0S|aQIw6ZPZux#N)&S={r z5yPU#YPP2ge%|x1hik`k$dZtQvRZpb-OJxnU~d)5(FjWq&K2&xVH%!u3YP@q z+|~32zxIf|$yiEN@jkQbNO$(Ax=p}Vo82l|Xb62YW@T$kaggf_{i!K%A zr=@RCp2dgsVl&CRn+!WF2smJ;>-Cw8#V5HfHB5C_W^cA9)r+s0^a+`im}MO+BTQ$8 zRPlDrR_9=uwAZi=bIGTqu4|b%&qFt_)X#lQ3RQE?c%QswcDRX_3d=i1L;s?Mf}`o6 z>Il*%fi&sf!jX1`H8+PSm#mqV7SG6w1gTw;GKiLGIk?V!2&2uh z0;`+Btq3h3*fENshnKX-UQ5=9>&C(n&L*>>zCC~bDIaz@yr@Bt?kt!pE)@u5E$VX?|ITyg=fQCGUp|b_2TsYm z{WYWZqABBtB@fOnwFPQVnQ$_@GhA7}r0Nl+IoDp2{8*WocvU8&g;b_vCOznMX+*Mw zT*uK6iO>1pmKun<4rDJ}6z_D3^mLQSi1IJ6uUaQU287Kq{OOc_U(a5MIk$#)K7jm>z_3ee{a+$0u4CPU8_w#Aut4MIJU^J!_5 z7Dp`@T)iaBp0?r}8XxL$dlkzUb{7UMVOiO4BXttxjT;z^R;#v{$tSl{-0-#)9MXP# zmj<~E+bZ5KgkR^*JH6W;pj+u8GmptGIt9fhVbFQX#F8;oxp4Hf;-#~L(#hMr!pYck z3I+Pp_NCARv1c`|jF313tHdqhF8!1KGAik4p|-+dvaN?x zfon5d(+taMb%>isoU26vlwyWClpm7JwYE9@%C*{wDt&NVGip*~=$xv7Woj<==qK7= zFQ=663w09wI~mWVjN-2lKgL zkjL??{XP7llG z=4Z+q;-wcauspGm%nS0B$V+SxZUaQg@Lth(-I|VK@^D?PCUbk5vt(@Ux6^37_Fu5iy$psFM4A}1J z6J#UNvPuu2|Ig9Cl*(M}a?n50Ih@BVE>DXL$wiuuS0~zYbco?XxhNvl%(^l*Dv`gJ zF<0>#R*dZ+mvr*7ud`7v8!t^Zava)?vy9>RS-8_qMXiu^bvz^!Sgdn8GG{lhOZ_2oGs{osEiLE9o)1j9p0-KvHx)M7ru(hHg}VD0M!To_p$O@}9yhA^=q=Vs>$;=!ZiX|a z5)lAz!xo+UWFO{vt^2~prle@P2d+!}uWcl7%KHU{dOYoTt(hV>AbThwD?0}uw`sv| zpMe68A|PM=fHoiYcg>(0UvxS6u*!K-tpaSV54W0#tglt--jyO)pRgXU}1fgZ9ca8)N`+)(UJ^Ybwt3{;dRE{8zpC;eR{g1DD?f4 zn+vYjXug<~Oxqv?7$5q4u@M6Qg9rXg4A1M9KoAiTVKW+s9|(qmpLCewIfKHeKi|sr6 zKDPh0>1=5dzWV_^z_v957+-tSMKQqBC^-Nj`hCFv<{})Ji%uqJXmZ(1;3ZIvW``nh zt#V#|elbAzce~pyNRp}GqPqHpD)|e5A}AX@@t92)4Lya=eh%eMba(EXt8H7qDJdyA6o=YEKt5^);}3s_LP_n7OKQxWcI>)Fv!~XX z629@f)^F*-_1@LtUV-6TL8+^(yqvX&oXpNt(MS_;1)~|UsI0;|TUXN1k>q)LUY1%p z;rm24&bc9;+DbZXs$}?&D@ub&tX*dJah}ao@t#6cALcFI@@qARLTLb89YHUDqv5xU zBY;uw25QM2Q9jlL+$DQ5U@!)HdOjjXo?R3YavkMfW#!Ht&cYv*?tN#<+{_;IuX0aI z0198XdG)>@u7^Y5aNtC~M%dyg9Ej*Nmk{nH$F=4^vHkGT947W=iE7)=&?*W(4Gxk9 zL;$}!cY5{y4F2M`M#+HV^0EljgP2YP6c>f_WwfiujL7;)60Pl@p@;-(HySNX@fj`v z`mDHI-gkY`$7uvLjyg|41Az^wfq{X==b}IRU(fjhYEuApR}A3xoQ14pUn$huhqH*o zhl}wXl|xzAwc^yn7X2mjr`qlj`7M!qtI*Vl9+%`z40%V`LGg#8u(Uf*zs+sc^1GM`uBIHT|iJxZ(=<(srYllwm(|*%-8HEKM_aQBXGylLL2uH<66?F{2SD282j<+sB28MZp=Nx$kgZf`n>7b^Jk5 z3V?z7+eV-%mN}Lo&M!Yk&Y8WBf=+{pSW)lCU$5YcZSFw-yKD8g7cc$*WNUGJ>*sZ& z2tdk9#QEbibrke#AjjvLg4=oD@O3M>9SitG*4SPK+~1L0OY8 z{FpK@FlYx(kqjq2jza;n1Ds+B5KQtu+{TSehthu;1TUFAumPnc`SyXcE>eI+>=>$f zE@{$FXFKg}tsV@&YTU6ltr##;3LjoxhkOn-a?39sah<15ouUJQ5|p~HDy9x;8BA#o zw_MlsJk;x9^StHbJ0r?az?4A!?}kX&cXL;k-)cjvc_?1fO6y)3jkt^c%8h%puz@V; zDbm(Whi8Z+yf+-ks0I=4?HcX{1j&$1GcYAEh|2wS>2y2xk#n4uR$}}186bU5y{R<=uqxOeQw`DwK+RJo>F+F2{LN#-$Cw!|Iga~*m0GGFj0+aGXxLl(f!tCvje4U?uHkDYn zp~q0>|0*$YRBV*(BOEI4Dwhr}8t3cli(HZ-S&QAYF6w#f@TaUxT}dB!p#>jd{-8B2 zQDeTJHEe0J3eu8V!ctG4JFs&s<{DfPYpNB7%=`fveRW1J1cFVhRzJk6Tk}oyz)Jw=X#7cL( zWFlT?(VV&C^`s(kvk?^hC)nw6gx@SL`~)Tcf>as%&fsb(rV;(U8w%ei++~`g!$sL9 znxa9c%alFeli^o7P~nQ?ayBs^9C`)w?aw7{%6)MYX3n+j3hMidL$fK5wVn-p9y=^tX4 zCL53{T@~}UQTo9Ms(|6mc}9L_W{T*IzP1Gq8f8v~RG}4h5U@md;P%REJ^`p*L@lyb zK=0-iij`;NXO^}AlUPX%qG9}2EQisbUkW-V3!};|t`hR9Xn>qJz#n(buQ z8j5}R_^y6&c&iIgLeM3b#Bb@{TSL9c=7Qi0=* z202VG@Np$iA##V0!kZ)I&&v2V;a~Z+Uz#T+Uq!=svDd&uZ)6h%=J`6C1rB%WlKawG zR(BzrAbNYw2XX#Qsm&I2(!5nh^ksN=`1uVtGLbcymYABF9mB&cf6-R*qIKLXr%lebuG_Zy-6tN z!`3+fUR2AlrrBeFp0Z=)PG0^PGA@&oojnR&GKdem!Xt$D9@EodOb^O2{5HMLdzS&& zc*?f?h;1x1;_p~$_9(&^-yl($nl2-MToY)MCBCG80ZkwF0FpwP%YFFk zIRZrPxm^;r|Dx9nAr3iA8hfoTBw4@{z;n7r|3aR8(y**SR%tW@;Uz8a z${vmckrXutIh*HqcWukn^0JjPEe%bbPSR2f`7TsB7MA&mJs|O1dF9CgoPkMi_IHv+ z-{?^cp9;RsAv5K3!SWNkr4I^rHJ(xJTl9pN{>*Eus&_k|HD{7B(UDg%KVzu4lBz*- z*3g_CV2jTB&5Lre(EiRhKW?hn5u@dXpO!p_wm`a&Zv!%q4pW8P^&H`7(8Xu4w7E}dXg^~YtwksGBWgBZ7t$lv_ptlItBqHn2a5v%GO zbEWntV=VIwt9>%>YD>}&1Q(3aOFDgXGzq#XEAek4)e!^ofHhp8F%E~g+M9K4s6Imf zK?i@|>_ev#;?rA447~mtA?mt+x<$|Mar598a2>Pd*V8pI{kk#RE8YpCtYkZQQq6tU z`lb5&mx4{9glP8q($w-QpVHe;FXnq*_i%JRU4oBKlI=ndIx9N2iO22L*xv3Otvq$W za_XVZ7$2r!c6c>JiK}g;s65(lm{Ieu%R*7^B)uaW)lXy z1Tv49G-kF3oNcX*7T$TUDB%Wqk{2QQrK;@afUm-V3bc8d{a1-1J-uf4?!Nrg(~jCg zF|z5|EZm{|v3QyjzY+0AM`C0crpi4BFL3}j5vLHt|MH9*?H#65wfXxN!(E}KB^|Ca zSeH%@iR-Dkr8f)nr&^53s~$4xs}C3B9m!rlT9SfIEX@7d6$8$jN;YKXy*M+>?q}NwVe}0V7W7z7c-S%T56^fqXr(z^(Zm2n zw;o%sXOkBt`~tq;=do)^r(ff1plSwrrv?rwnLi!yZ5_Y1XMvWrKoJp|3)jl*lryHF z?9CKLuFi=5cy>2h^`2>bwP)kTPzKnTpSr<0x9DX}vhJCJ&B1maX4%_T`?+b3iMgZN!_wS}hl3Mn zl`@*N75&;6(q+S-&y9m$RZAbMyV>0iEs~Wwlyl~Mlbmf@C=ZX!!Yi0G#JNH?va}<4 z0%Fotw&(^U>q_=s;(n#P*LdO`h2$C;!wWyAt%mDIlmfqaz9Xq?30|o(pGdlP`t9uQ zN{oVGX8D}^Q~mO<3rkcP+_)B!X|2ng}k1lw1glnH#a*>63OSVJ#g@*?uBjH>W@kg3PQfSuJ5CDoD6MUFff_ z%Re7ruJ3eww(XFb`Hq=SpZE8)dlv@gC|XT~e;vzIEZN;M`ocw>@PpUY=2*VM!P-$X z`bcWcgn|Vy z9sO>~-P?VASq0DOA~f&GL;>cy3>;!DHQw2ElggT57N! zUH>1#G<@Oz6%;u_bVeK*xR@APOH0ee(KAmXytkm*cRTcF;ly4zD+l38cFgTAM4V>o zkVsoH5}-qai-K?nM9!^EQ!=LTbcV;AUU50 zFvJ+4tioTzMpO(K-?NALwb^T>t<8 -- GitLab From 9b5e94034233a4285430f3f7e6fc61d37a5fed98 Mon Sep 17 00:00:00 2001 From: gongweibao Date: Fri, 12 May 2017 19:30:49 +0800 Subject: [PATCH 0292/3256] fix filenames --- doc/design/cluster_train/data_dispatch.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/doc/design/cluster_train/data_dispatch.md b/doc/design/cluster_train/data_dispatch.md index 9f7167498ce..a84fcd851d9 100644 --- a/doc/design/cluster_train/data_dispatch.md +++ b/doc/design/cluster_train/data_dispatch.md @@ -114,7 +114,7 @@ trainer.train(batch_reader, ...) 使用下面命令,可以把本地的数据上传到存储集群中。 ```bash -paddle pfs cp filenames /pfs/$DATACENTER/home/$USER/folder/ +paddle pfs cp filename /pfs/$DATACENTER/home/$USER/folder/ ``` 比如,把之前示例中转换完毕的random_images数据集上传到云端的`/home/`可以用以下指令: @@ -141,7 +141,8 @@ endpoint=datacenter2.paddlepaddle.org ``` ## TODO ### 文件访问的权限 -控制用户权限 +控制用户权限 + - 用户可以把自己的数据分享给别人 ### 文件访问方式 -- GitLab From 373cdb543c886bd4f9001ec43a6828ecc72d9b7f Mon Sep 17 00:00:00 2001 From: liaogang Date: Fri, 12 May 2017 20:45:41 +0800 Subject: [PATCH 0293/3256] Fix locales to en_US.UTF-8 --- paddle/scripts/docker/build.sh | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/paddle/scripts/docker/build.sh b/paddle/scripts/docker/build.sh index 101b44e6c62..0387ab02d47 100644 --- a/paddle/scripts/docker/build.sh +++ b/paddle/scripts/docker/build.sh @@ -131,8 +131,6 @@ cat > /paddle/build/Dockerfile < ENV HOME /root -ENV LANG en_US.UTF-8 -# Use Fix locales to en_US.UTF-8 EOF if [[ -n ${APT_MIRROR} ]]; then @@ -146,13 +144,18 @@ cat >> /paddle/build/Dockerfile < Date: Sat, 13 May 2017 00:11:59 +0800 Subject: [PATCH 0294/3256] add cmake for majel --- paddle/CMakeLists.txt | 8 ++++++++ paddle/majel/CMakeLists.txt | 15 +++++++++++++++ paddle/majel/test/CMakeLists.txt | 10 ++++++++++ 3 files changed, 33 insertions(+) create mode 100644 paddle/majel/CMakeLists.txt create mode 100644 paddle/majel/test/CMakeLists.txt diff --git a/paddle/CMakeLists.txt b/paddle/CMakeLists.txt index eff296bcb01..c6fd9cc54ae 100644 --- a/paddle/CMakeLists.txt +++ b/paddle/CMakeLists.txt @@ -9,6 +9,14 @@ add_subdirectory(pserver) add_subdirectory(trainer) add_subdirectory(scripts) +find_package(boost QUIET) + +if(Boost_FOUND) + include_directories(${Boost_INCLUDE_DIRS}) + include_directories(${CMAKE_CURRENT_SOURCE_DIR}) + add_subdirectory(majel) +endif() + if(WITH_C_API) add_subdirectory(capi) endif() diff --git a/paddle/majel/CMakeLists.txt b/paddle/majel/CMakeLists.txt new file mode 100644 index 00000000000..f2f57900c8b --- /dev/null +++ b/paddle/majel/CMakeLists.txt @@ -0,0 +1,15 @@ +set(MAJEL_CXX_FILES place.cpp) +set(MAJEL_CUDA_FILES "") + +if(CUDA_FOUND) + cuda_add_library(majel ${MAJEL_CUDA_FILES} ${MAJEL_CXX_FILES}) +else() + add_library(majel ${MAJEL_CXX_FILES}) +endif() + +file(GLOB_RECURSE CHECK_FILES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "*.cpp" "*.h") +add_style_check_target(majel ${CHECK_FILES}) + +if(WITH_TESTING) + add_subdirectory(test) +endif() \ No newline at end of file diff --git a/paddle/majel/test/CMakeLists.txt b/paddle/majel/test/CMakeLists.txt new file mode 100644 index 00000000000..a07b1bf5d85 --- /dev/null +++ b/paddle/majel/test/CMakeLists.txt @@ -0,0 +1,10 @@ +file(GLOB_RECURSE ALL_TEST_FILES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "*.cpp" "*.cc") + +add_executable(majel_tests ${ALL_TEST_FILES}) +add_dependencies(majel_tests majel ${external_project_dependencies}) +target_link_libraries(majel_tests + ${Boost_LIBRARIES} + ${GTEST_LIBRARIES} + majel + ) +add_test(majel_tests majel_tests) -- GitLab From 3a734557d80129cd0f557ee590b2f4db91b63276 Mon Sep 17 00:00:00 2001 From: liaogang Date: Sat, 13 May 2017 00:12:28 +0800 Subject: [PATCH 0295/3256] move majel into paddle/majel --- majel/Makefile | 10 ---------- majel/place.cu => paddle/majel/place.cpp | 2 +- {majel => paddle/majel}/place.h | 0 .../place_test.cu => paddle/majel/test/place_test.cpp | 0 paddle/majel/test/test_framework.cpp | 6 ++++++ 5 files changed, 7 insertions(+), 11 deletions(-) delete mode 100644 majel/Makefile rename majel/place.cu => paddle/majel/place.cpp (97%) rename {majel => paddle/majel}/place.h (100%) rename majel/place_test.cu => paddle/majel/test/place_test.cpp (100%) create mode 100644 paddle/majel/test/test_framework.cpp diff --git a/majel/Makefile b/majel/Makefile deleted file mode 100644 index 403c1d3a6ae..00000000000 --- a/majel/Makefile +++ /dev/null @@ -1,10 +0,0 @@ -CCFLAGS = -std=c++11 -I/work/paddle -CC = nvcc - -all : place_test - -place.o : place.h place.cu - $(CC) $(CCFLAGS) -c place.cu -o $@ - -place_test : place.o place_test.cu - $(CC) $(CCFLAGS) -lgtest -lgtest_main $^ -o $@ diff --git a/majel/place.cu b/paddle/majel/place.cpp similarity index 97% rename from majel/place.cu rename to paddle/majel/place.cpp index ee84e96048d..396534b84b5 100644 --- a/majel/place.cu +++ b/paddle/majel/place.cpp @@ -1,4 +1,4 @@ -#include +#include "majel/place.h" namespace majel { diff --git a/majel/place.h b/paddle/majel/place.h similarity index 100% rename from majel/place.h rename to paddle/majel/place.h diff --git a/majel/place_test.cu b/paddle/majel/test/place_test.cpp similarity index 100% rename from majel/place_test.cu rename to paddle/majel/test/place_test.cpp diff --git a/paddle/majel/test/test_framework.cpp b/paddle/majel/test/test_framework.cpp new file mode 100644 index 00000000000..a62216ba85c --- /dev/null +++ b/paddle/majel/test/test_framework.cpp @@ -0,0 +1,6 @@ +#include "gtest/gtest.h" + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} -- GitLab From 9c501e90cf35ce159c5ddd26faea1fe955e4abdd Mon Sep 17 00:00:00 2001 From: liaogang Date: Sat, 13 May 2017 00:12:40 +0800 Subject: [PATCH 0296/3256] remove gtest in dockerfile --- Dockerfile | 9 --------- 1 file changed, 9 deletions(-) diff --git a/Dockerfile b/Dockerfile index dc443887323..ad0d086d3c6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -61,15 +61,6 @@ RUN git clone https://github.com/woboq/woboq_codebrowser /woboq && \ -DCMAKE_BUILD_TYPE=Release . \ make) -# Install gtest. -# -# NOTE: This is added for quick hack of the development work of -# majel-in-paddle. -RUN git clone https://github.com/google/googletest /gtest && \ - cd /gtest && \ - git checkout -b release-1.8.0 && \ - cmake . && make install - # Configure OpenSSH server. c.f. https://docs.docker.com/engine/examples/running_ssh_service RUN mkdir /var/run/sshd RUN echo 'root:root' | chpasswd -- GitLab From 92e0c893e4101eeb8303a07777cb7d097f705a9d Mon Sep 17 00:00:00 2001 From: liaogang Date: Sat, 13 May 2017 00:15:07 +0800 Subject: [PATCH 0297/3256] precommit --- paddle/majel/CMakeLists.txt | 2 +- paddle/majel/place.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/paddle/majel/CMakeLists.txt b/paddle/majel/CMakeLists.txt index f2f57900c8b..f1cd4ee9a1d 100644 --- a/paddle/majel/CMakeLists.txt +++ b/paddle/majel/CMakeLists.txt @@ -12,4 +12,4 @@ add_style_check_target(majel ${CHECK_FILES}) if(WITH_TESTING) add_subdirectory(test) -endif() \ No newline at end of file +endif() diff --git a/paddle/majel/place.cpp b/paddle/majel/place.cpp index 396534b84b5..ee84e96048d 100644 --- a/paddle/majel/place.cpp +++ b/paddle/majel/place.cpp @@ -1,4 +1,4 @@ -#include "majel/place.h" +#include namespace majel { -- GitLab From d8c8f419f974a9d02bddd937330e8201a3a2a699 Mon Sep 17 00:00:00 2001 From: liaogang Date: Sat, 13 May 2017 01:09:24 +0800 Subject: [PATCH 0298/3256] support build majel in subdir --- paddle/majel/.gitignore | 2 ++ paddle/majel/CMakeLists.txt | 31 +++++++++++++++++++++++++------ paddle/majel/test/CMakeLists.txt | 2 +- 3 files changed, 28 insertions(+), 7 deletions(-) create mode 100644 paddle/majel/.gitignore diff --git a/paddle/majel/.gitignore b/paddle/majel/.gitignore new file mode 100644 index 00000000000..1f5acdebb56 --- /dev/null +++ b/paddle/majel/.gitignore @@ -0,0 +1,2 @@ +build +third-party \ No newline at end of file diff --git a/paddle/majel/CMakeLists.txt b/paddle/majel/CMakeLists.txt index f1cd4ee9a1d..f3bc984a26a 100644 --- a/paddle/majel/CMakeLists.txt +++ b/paddle/majel/CMakeLists.txt @@ -1,3 +1,26 @@ +cmake_minimum_required(VERSION 3.0) + +if(GTEST_INCLUDE_DIR AND GTEST_LIBRARIES) + message("-- Found gtest (include: ${GTEST_INCLUDE_DIR}, library: ${GTEST_LIBRARIES})") +else() + # find #include + get_filename_component(PARENT_DIR ${CMAKE_CURRENT_SOURCE_DIR} DIRECTORY) + include_directories(${PARENT_DIR}) + + # find cmake directory modules + get_filename_component(PARENT_DIR ${PARENT_DIR} DIRECTORY) + set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${PARENT_DIR}/cmake") + + # enable c++11 + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") + + # enable gtest + set(THIRD_PARTY_PATH ${CMAKE_CURRENT_SOURCE_DIR}/third_party) + set(WITH_TESTING ON) + include(external/gtest) +endif() + +########################### Build Majel ############################# set(MAJEL_CXX_FILES place.cpp) set(MAJEL_CUDA_FILES "") @@ -6,10 +29,6 @@ if(CUDA_FOUND) else() add_library(majel ${MAJEL_CXX_FILES}) endif() +########################### Build Majel ############################# -file(GLOB_RECURSE CHECK_FILES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "*.cpp" "*.h") -add_style_check_target(majel ${CHECK_FILES}) - -if(WITH_TESTING) - add_subdirectory(test) -endif() +add_subdirectory(test) diff --git a/paddle/majel/test/CMakeLists.txt b/paddle/majel/test/CMakeLists.txt index a07b1bf5d85..0cc7103b039 100644 --- a/paddle/majel/test/CMakeLists.txt +++ b/paddle/majel/test/CMakeLists.txt @@ -1,7 +1,7 @@ file(GLOB_RECURSE ALL_TEST_FILES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "*.cpp" "*.cc") add_executable(majel_tests ${ALL_TEST_FILES}) -add_dependencies(majel_tests majel ${external_project_dependencies}) +add_dependencies(majel_tests majel) target_link_libraries(majel_tests ${Boost_LIBRARIES} ${GTEST_LIBRARIES} -- GitLab From 05b3196e0a9e92e7123fb51fe1a7f46992843607 Mon Sep 17 00:00:00 2001 From: liaogang Date: Sat, 13 May 2017 01:10:48 +0800 Subject: [PATCH 0299/3256] clang-format --- paddle/majel/place.cpp | 44 ++++++++++------------------ paddle/majel/test/place_test.cpp | 16 +++++----- paddle/majel/test/test_framework.cpp | 4 +-- 3 files changed, 26 insertions(+), 38 deletions(-) diff --git a/paddle/majel/place.cpp b/paddle/majel/place.cpp index ee84e96048d..eecd8e5b730 100644 --- a/paddle/majel/place.cpp +++ b/paddle/majel/place.cpp @@ -4,58 +4,46 @@ namespace majel { namespace detail { -class PlacePrinter - : public boost::static_visitor<> { +class PlacePrinter : public boost::static_visitor<> { private: - std::ostream& os_; + std::ostream& os_; + public: - PlacePrinter(std::ostream& os) : os_(os) {} + PlacePrinter(std::ostream& os) : os_(os) {} - void operator()(const CpuPlace&) { - os_ << "CpuPlace"; - } + void operator()(const CpuPlace&) { os_ << "CpuPlace"; } - void operator()(const GpuPlace& p) { - os_ << "GpuPlace(" << p.device << ")"; - } + void operator()(const GpuPlace& p) { os_ << "GpuPlace(" << p.device << ")"; } }; } // namespace majel static Place the_default_place; -void set_place(const Place& place) { - the_default_place = place; -} +void set_place(const Place& place) { the_default_place = place; } -const Place& get_place() { - return the_default_place; -} +const Place& get_place() { return the_default_place; } -const GpuPlace default_gpu() { - return GpuPlace(0); -} +const GpuPlace default_gpu() { return GpuPlace(0); } -const CpuPlace default_cpu() { - return CpuPlace(); -} +const CpuPlace default_cpu() { return CpuPlace(); } bool is_gpu_place(const Place& p) { - return boost::apply_visitor(IsGpuPlace(), p); + return boost::apply_visitor(IsGpuPlace(), p); } bool is_cpu_place(const Place& p) { - return !boost::apply_visitor(IsGpuPlace(), p); + return !boost::apply_visitor(IsGpuPlace(), p); } bool places_are_same_class(const Place& p1, const Place& p2) { - return is_gpu_place(p1) == is_gpu_place(p2); + return is_gpu_place(p1) == is_gpu_place(p2); } std::ostream& operator<<(std::ostream& os, const majel::Place& p) { - majel::detail::PlacePrinter printer(os); - boost::apply_visitor(printer, p); - return os; + majel::detail::PlacePrinter printer(os); + boost::apply_visitor(printer, p); + return os; } } // namespace majel diff --git a/paddle/majel/test/place_test.cpp b/paddle/majel/test/place_test.cpp index cb829461303..c9a53802b23 100644 --- a/paddle/majel/test/place_test.cpp +++ b/paddle/majel/test/place_test.cpp @@ -1,6 +1,6 @@ -#include "gtest/gtest.h" #include "majel/place.h" #include +#include "gtest/gtest.h" TEST(Place, Equality) { majel::CpuPlace cpu; @@ -18,12 +18,12 @@ TEST(Place, Equality) { } TEST(Place, Default) { - EXPECT_TRUE(majel::is_gpu_place( majel::get_place())); - EXPECT_TRUE(majel::is_gpu_place( majel::default_gpu())); - EXPECT_TRUE(majel::is_cpu_place( majel::default_cpu())); + EXPECT_TRUE(majel::is_gpu_place(majel::get_place())); + EXPECT_TRUE(majel::is_gpu_place(majel::default_gpu())); + EXPECT_TRUE(majel::is_cpu_place(majel::default_cpu())); majel::set_place(majel::CpuPlace()); - EXPECT_TRUE(majel::is_cpu_place( majel::get_place())); + EXPECT_TRUE(majel::is_cpu_place(majel::get_place())); } TEST(Place, Print) { @@ -33,8 +33,8 @@ TEST(Place, Print) { EXPECT_EQ("GpuPlace(1)", ss.str()); } { - std::stringstream ss; - ss << majel::CpuPlace(); - EXPECT_EQ("CpuPlace", ss.str()); + std::stringstream ss; + ss << majel::CpuPlace(); + EXPECT_EQ("CpuPlace", ss.str()); } } diff --git a/paddle/majel/test/test_framework.cpp b/paddle/majel/test/test_framework.cpp index a62216ba85c..443e2dbb3f2 100644 --- a/paddle/majel/test/test_framework.cpp +++ b/paddle/majel/test/test_framework.cpp @@ -1,6 +1,6 @@ #include "gtest/gtest.h" int main(int argc, char** argv) { - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); } -- GitLab From d444251c4a43facb2d15f0952b6312a616210c22 Mon Sep 17 00:00:00 2001 From: liaogang Date: Sat, 13 May 2017 01:15:04 +0800 Subject: [PATCH 0300/3256] refine format --- paddle/majel/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paddle/majel/CMakeLists.txt b/paddle/majel/CMakeLists.txt index f3bc984a26a..0c91fa72da4 100644 --- a/paddle/majel/CMakeLists.txt +++ b/paddle/majel/CMakeLists.txt @@ -29,6 +29,6 @@ if(CUDA_FOUND) else() add_library(majel ${MAJEL_CXX_FILES}) endif() -########################### Build Majel ############################# +##################################################################### add_subdirectory(test) -- GitLab From 5081c691b1f706080dc7e87c6ca459162f4a8ca8 Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Fri, 12 May 2017 11:14:11 -0700 Subject: [PATCH 0301/3256] rename parameter chunks to parameter blocks --- doc/design/cluster_train/pserver_client.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/design/cluster_train/pserver_client.md b/doc/design/cluster_train/pserver_client.md index 500894fac76..392bab25e9d 100644 --- a/doc/design/cluster_train/pserver_client.md +++ b/doc/design/cluster_train/pserver_client.md @@ -4,7 +4,7 @@ For an overview of trainer's role, please refer to [distributed training design ## Parameter Partition -Each parameter will be partitioned into parameter chunks to make the parameters evenly distributed on parameter servers. The partition is done automatically by the client library. The *sparse parameter* require a little different treatment: +Each parameter will be partitioned into parameter blocks to make the parameters evenly distributed on parameter servers. The partition is done automatically by the client library. The *sparse parameter* require a little different treatment: ### Sparse Parameter -- GitLab From 4bf8e372b950699285ec2f59c437483240cce01c Mon Sep 17 00:00:00 2001 From: liaogang Date: Sat, 13 May 2017 10:59:56 +0800 Subject: [PATCH 0302/3256] Add Google style conventions to Majel --- .travis.yml | 1 + paddle/majel/CMakeLists.txt | 2 +- paddle/majel/{place.cpp => place.cc} | 0 paddle/majel/test/CMakeLists.txt | 3 ++- paddle/majel/test/{place_test.cpp => place_test.cc} | 0 paddle/majel/test/test_framework.cpp | 6 ------ 6 files changed, 4 insertions(+), 8 deletions(-) rename paddle/majel/{place.cpp => place.cc} (100%) rename paddle/majel/test/{place_test.cpp => place_test.cc} (100%) delete mode 100644 paddle/majel/test/test_framework.cpp diff --git a/.travis.yml b/.travis.yml index 865e21f046b..d73fd39aa7a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,6 +25,7 @@ addons: - python2.7-dev - python-numpy - python-wheel + - libboost-dev - curl - swig - graphviz diff --git a/paddle/majel/CMakeLists.txt b/paddle/majel/CMakeLists.txt index 0c91fa72da4..baa3bb9e914 100644 --- a/paddle/majel/CMakeLists.txt +++ b/paddle/majel/CMakeLists.txt @@ -21,7 +21,7 @@ else() endif() ########################### Build Majel ############################# -set(MAJEL_CXX_FILES place.cpp) +set(MAJEL_CXX_FILES place.cc) set(MAJEL_CUDA_FILES "") if(CUDA_FOUND) diff --git a/paddle/majel/place.cpp b/paddle/majel/place.cc similarity index 100% rename from paddle/majel/place.cpp rename to paddle/majel/place.cc diff --git a/paddle/majel/test/CMakeLists.txt b/paddle/majel/test/CMakeLists.txt index 0cc7103b039..46da6ff89b4 100644 --- a/paddle/majel/test/CMakeLists.txt +++ b/paddle/majel/test/CMakeLists.txt @@ -1,10 +1,11 @@ -file(GLOB_RECURSE ALL_TEST_FILES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "*.cpp" "*.cc") +file(GLOB_RECURSE ALL_TEST_FILES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "*.cc") add_executable(majel_tests ${ALL_TEST_FILES}) add_dependencies(majel_tests majel) target_link_libraries(majel_tests ${Boost_LIBRARIES} ${GTEST_LIBRARIES} + ${GTEST_MAIN_LIBRARIES} majel ) add_test(majel_tests majel_tests) diff --git a/paddle/majel/test/place_test.cpp b/paddle/majel/test/place_test.cc similarity index 100% rename from paddle/majel/test/place_test.cpp rename to paddle/majel/test/place_test.cc diff --git a/paddle/majel/test/test_framework.cpp b/paddle/majel/test/test_framework.cpp deleted file mode 100644 index 443e2dbb3f2..00000000000 --- a/paddle/majel/test/test_framework.cpp +++ /dev/null @@ -1,6 +0,0 @@ -#include "gtest/gtest.h" - -int main(int argc, char** argv) { - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} -- GitLab From b2ccc4778837f788d4014156e5586e4c547d588e Mon Sep 17 00:00:00 2001 From: gongweibao Date: Sun, 14 May 2017 11:32:50 +0800 Subject: [PATCH 0303/3256] fix by comments --- doc/design/file_manager/README.md | 44 ++++++--- doc/design/file_manager/RESTAPI.md | 23 ----- doc/design/file_manager/pfs/pfs.md | 92 ++++++++++++++++-- .../file_manager/src/filemanager.graffle | Bin 3442 -> 3438 bytes doc/design/file_manager/src/filemanager.png | Bin 62630 -> 145125 bytes 5 files changed, 119 insertions(+), 40 deletions(-) delete mode 100644 doc/design/file_manager/RESTAPI.md diff --git a/doc/design/file_manager/README.md b/doc/design/file_manager/README.md index 2fe874581e7..8d9c7487c52 100644 --- a/doc/design/file_manager/README.md +++ b/doc/design/file_manager/README.md @@ -8,13 +8,10 @@ - 支持大文件的断点上传、下载 ## 名词解释 -- PFS:是Paddlepaddle cloud File System的简称,是对用户文件存储空间的抽象,与之相对的是Local File System。目前我们用CephFS来搭建。 +- PFS:是Paddlepaddle cloud File System的简称,是对用户文件存储空间的抽象,与之相对的是local filesystem。目前我们用CephFS来搭建。 - [CephFS](http://docs.ceph.com/docs/master/cephfs/):一个POSIX兼容的文件系统。 - Chunk:逻辑划上文件分块的单位。 - [Ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/):提供七层协议的反向代理、基于粘性会话的负载均衡。 -- CA:certificate authority[tls](#tls) -- CRT:CA signed certificate[tls](#tls) -- Key:用户私钥[tls](#tls) ## 模块 @@ -22,24 +19,49 @@ ### PFSClient -- 功能: 详细的内容看[Here](./pfs/pfs.md) +- 功能: 详细的内容看 - 提供用户管理文件的命令 - - 用Golang写,可以跨平台执行 + - 用Go写,可以跨平台执行 - 双向验证 - PFSClient需要和Ingress之间做双向验证[tls](#tls),所以用户需要首先在`cloud.paddlepaddle.org`上注册一下,申请用户空间,并且把系统生成的Key、CRT、CA下载到本地,然后才能使用PFSClient。 + PFSClient需要和Ingress之间做双向验证[tls](#tls),所以用户需要首先在`cloud.paddlepaddle.org`上注册一下,申请用户空间,并且把系统生成的CA(certificate authority)、Key、CRT(CA signed certificate)下载到本地,然后才能使用PFSClient。 + +- 命令格式 + +``` +paddle [options] pfs [parameters] + +options: + +``` ### Ingress - 功能: 提供七层协议的反向代理、基于粘性会话的负载均衡功能。 - 透传用户身份的办法 - Ingress需要把PFSClient的身份头传给FileServer,配置的方法参考[Here](http://www.integralist.co.uk/posts/clientcertauth.html#3) + Ingress需要把PFSClient的身份信息传给FileServer,配置的方法参考[Here](http://www.integralist.co.uk/posts/clientcertauth.html#3) -### FileServer -FileServer是一个用GoRPC写的HTTPServer,提供[RESTful API](./RESTAPI.md)接口,接收处理PFSClient端的文件管理请求,并且把结果返回PFSClient端。 +### PFSServer +PFSServer提供RESTful API接口,接收处理PFSClient端的文件管理请求,并且把结果返回PFSClient端。 +``` +RESTful API + +- /api/v1/files + - `GET /api/v1/files`: Get attributes of files or directories. + - `POST /api/v1/files`: Create files or directories. + - `DELETE /api/v1/files`: Delete files or directories. + +- /api/v1/storage/files + - `GET /api/v1/storage/files`: Download files or directories to local. + - `POST /api/v1/storage/files`: Upload files or directories to server. + +- /api/v1/storage/file/chunks + - `GET /api/v1/storage/file/chunks`: Get chunks's attributes in a file. + - `POST /api/v1/storage/file/chunks`: Upload chunks to a file. +``` ## 文件传输优化 ### 分块文件传输 @@ -57,7 +79,7 @@ type Chunk struct { ``` ### 生成sparse文件 -当destination文件不存在或者大小和source文件不一致时,可以用[Fallocate](https://golang.org/pkg/syscall/#Fallocate)生成sparse文件,然后就可以并发写入多个Chunk。 +当destination文件不存在或者大小和source文件不一致时,可以用[Fallocate](https://Go.org/pkg/syscall/#Fallocate)生成sparse文件,然后就可以并发写入多个Chunk。 ### 覆盖不一致的部分 文件传输的的关键在于需要PFSClient端对比source和destination的文件Chunks的checksum是否保持一致,不一致的由PFSClient下载或者传输Chunk完成。这样已经传输成功的部分就不用重新传输了。 diff --git a/doc/design/file_manager/RESTAPI.md b/doc/design/file_manager/RESTAPI.md deleted file mode 100644 index 9c94804d6b1..00000000000 --- a/doc/design/file_manager/RESTAPI.md +++ /dev/null @@ -1,23 +0,0 @@ -# REST API Interface -- file - -``` -GET /file: Get attribue of files -POST /file: Create a file -DELETE /file: Delete a File -``` - -- chunk - -``` -GET /file/chunk: Get a chunk info -POST /file/chunk: Update a chunk -``` - -- dir - -``` -GET /dir: List all files in a directory -POST /dir: Create a directory -DELETE /dir: Delete a directory -``` diff --git a/doc/design/file_manager/pfs/pfs.md b/doc/design/file_manager/pfs/pfs.md index e26fc1095d2..dd4578e3696 100644 --- a/doc/design/file_manager/pfs/pfs.md +++ b/doc/design/file_manager/pfs/pfs.md @@ -34,9 +34,89 @@ A `pfspath` begin with `/pfs`, eg: `/pfs/$DATACENTER/home/$USER/folder`. Commonly, if there are two path arguments, the first is the source, and the second is the destination. ## Subcommonds -- [rm](rm.md) -- [mv](mv.md) -- [cp](cp.md) -- [ls](ls.md) -- [mkdir](mkdir.md) -- [sync](sync.md) +- rm - remove files or directories + +``` +Synopsis: + rm [-r] [-v] ... + +Options: + -r + remove directories and their contents recursively + -v + Cause rm to be verbose, showing files after they are removed. + +Examples: + paddle pfs rm /pfs/$DATACENTER/home/$USER/file + paddle pfs rm -r /pfs/$DATACENTER/home/$USER/folder +``` +- mv - move (rename) files + +``` +Synopsis: + mv [-f | -n] [-v] + mv [-f | -n] [-v] ... + mv [-f | -n] [-v] + mv [-f | -n] [-v] ... + mv [-f | -n] [-v] + mv [-f | -n] [-v] ... + +Options: + -f + Do not prompt for confirmation before overwriting the destination path. (The -f option overrides previous -n options.) + -n + Do not overwrite an existing file. (The -n option overrides previous -f options.) + -v + Cause mv to be verbose, showing files after they are moved. + +Examples: + paddle pfs mv ./text1.txt /pfs/$DATACENTER/home/$USER/text1.txt +``` +- cp - copy files or directories + +``` +Synopsis: + cp [-r] [-f | -n] [-v] [--preserve--links] + cp [-r] [-f | -n] [-v] [--preserve--links] ... + cp [-r] [-f | -n] [-v] [--preserve--links] + cp [-r] [-f | -n] [-v] [--preserve--links] ... + cp [-r] [-f | -n] [-v] [--preserve--links] + cp [-r] [-f | -n] [-v] [--preserve--links] ... + +Options: + -r + Copy directories recursively + -f + Do not prompt for confirmation before overwriting the destination path. (The -f option overrides previous -n options.) + -n + Do not overwrite an existing file. (The -n option overrides previous -f options.) + -v + Cause cp to be verbose, showing files after they are copied. + --preserve--links + Reserve links when copy links +``` +- ls- list files + +``` +Synopsis: + ls [-r] ... + +Options: + -r + List directory(ies) recursively + +Examples: + paddle pfs ls /pfs/$DATACENTER/home/$USER/file + paddle pfs ls /pfs/$DATACENTER/home/$USER/folder +``` + +- mkdir - mkdir directory(ies) +Create intermediate directory(ies) as required. + +``` +Synopsis: + mkdir ... + +Examples: + paddle pfs mkdir /pfs/$DATACENTER/home/$USER/folder +``` diff --git a/doc/design/file_manager/src/filemanager.graffle b/doc/design/file_manager/src/filemanager.graffle index beb57cdd786e24a57e63c06ef4d6cfa0df3d8c5a..7861a33072bc1908f69d12b37c20491dd8663103 100644 GIT binary patch delta 3348 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 esX%(sr6eBZuw!s1c)02S delta 3325 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

    %e@$P&L)4Z01!{VAsyYoYJIl|1 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�aY3b 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 Date: Sun, 14 May 2017 11:33:40 +0800 Subject: [PATCH 0304/3256] rm not need files --- doc/design/file_manager/pfs/cp.md | 45 ---------------------------- doc/design/file_manager/pfs/ls.md | 27 ----------------- doc/design/file_manager/pfs/mkdir.md | 13 -------- doc/design/file_manager/pfs/mv.md | 34 --------------------- doc/design/file_manager/pfs/rm.md | 32 -------------------- doc/design/file_manager/pfs/sync.md | 34 --------------------- 6 files changed, 185 deletions(-) delete mode 100644 doc/design/file_manager/pfs/cp.md delete mode 100644 doc/design/file_manager/pfs/ls.md delete mode 100644 doc/design/file_manager/pfs/mkdir.md delete mode 100644 doc/design/file_manager/pfs/mv.md delete mode 100644 doc/design/file_manager/pfs/rm.md delete mode 100644 doc/design/file_manager/pfs/sync.md diff --git a/doc/design/file_manager/pfs/cp.md b/doc/design/file_manager/pfs/cp.md deleted file mode 100644 index 1f1670e8827..00000000000 --- a/doc/design/file_manager/pfs/cp.md +++ /dev/null @@ -1,45 +0,0 @@ -# Name -cp - copy files - -# Synopsis -``` -cp [-r] [-f | -n] [-v] [--preserve--links] -cp [-r] [-f | -n] [-v] [--preserve--links] ... -cp [-r] [-f | -n] [-v] [--preserve--links] -cp [-r] [-f | -n] [-v] [--preserve--links] ... -cp [-r] [-f | -n] [-v] [--preserve--links] -cp [-r] [-f | -n] [-v] [--preserve--links] ... -``` - -# Description -``` -The following options are available: - --r - Copy directories recursively - --f - Do not prompt for confirmation before overwriting the destination path. (The -f option overrides previous -n options.) - --n - Do not overwrite an existing file. (The -n option overrides previous -f options.) - --v - Cause cp to be verbose, showing files after they are copied. - ---preserve--links - Reserve links when copy links -``` - -# Examples -- The following command copies a single file to pfs - -``` -paddle pfs cp ./text1.txt /pfs/$DATACENTER/home/$USER/text1.txt -``` - -- The following command copies pfs file to a local file - -``` -paddle pfs cp /pfs/$DATACENTER/home/$USER/text1.txt ./text1.txt -``` diff --git a/doc/design/file_manager/pfs/ls.md b/doc/design/file_manager/pfs/ls.md deleted file mode 100644 index 0db163e08bd..00000000000 --- a/doc/design/file_manager/pfs/ls.md +++ /dev/null @@ -1,27 +0,0 @@ -# Name -ls - list directory(ies)'s contents or file(s)'s attributes - -# Synopsis -`ls [-r] ...` - -# Description - -``` -The following options are available: - --r - List directory(ies) recursively -``` - -# Examples -- The following command lists a single file - -``` -paddle pfs ls /pfs/$DATACENTER/home/$USER/text1.txt -``` - -- The following command lists directory contents - -``` -paddle pfs ls / /pfs/$DATACENTER/home/$USER/folder -``` diff --git a/doc/design/file_manager/pfs/mkdir.md b/doc/design/file_manager/pfs/mkdir.md deleted file mode 100644 index 3ca51704027..00000000000 --- a/doc/design/file_manager/pfs/mkdir.md +++ /dev/null @@ -1,13 +0,0 @@ -# Name -mkdir - mkdir directory(ies) - -# Synopsis -`mkdir ...` - -# Description -Create the pfs directory(ies), if it(they) does(do) not already exist. And create intermediate directories as required. - -# Examples -``` -paddle pfs mkdir /pfs/$DATACENTER/home/$USER/folder -``` diff --git a/doc/design/file_manager/pfs/mv.md b/doc/design/file_manager/pfs/mv.md deleted file mode 100644 index 01b795be02c..00000000000 --- a/doc/design/file_manager/pfs/mv.md +++ /dev/null @@ -1,34 +0,0 @@ -# Name -mv - move (rename) files - - -# Synopsis -``` -mv [-f | -n] [-v] -mv [-f | -n] [-v] ... -mv [-f | -n] [-v] -mv [-f | -n] [-v] ... -mv [-f | -n] [-v] -mv [-f | -n] [-v] ... -``` - -# Description -``` -The following options are available: - --f - Do not prompt for confirmation before overwriting the destination path. (The -f option overrides previous -n options.) - --n - Do not overwrite an existing file. (The -n option overrides previous -f options.) - --v - Cause mv to be verbose, showing files after they are moved. -``` - -# Examples -- The following command moves a single file to pfs - -``` -paddle pfs mv ./text1.txt /pfs/$DATACENTER/home/$USER/text1.txt -``` diff --git a/doc/design/file_manager/pfs/rm.md b/doc/design/file_manager/pfs/rm.md deleted file mode 100644 index 8bcbabdfb10..00000000000 --- a/doc/design/file_manager/pfs/rm.md +++ /dev/null @@ -1,32 +0,0 @@ -# Name -rm - remove files or directories - -# Synopsis -``` -rm [-r] [-v] ... -``` - -# Description - -``` -The following options are available: - --r - remove directories and their contents recursively - --v - Cause rm to be verbose, showing files after they are removed. -``` - -# Examples -- The following command deletes a single file: - -``` -paddle pfs rm /pfs/$DATACENTER/home/$USER/test1.txt -``` - -- The following command deletes a directory recursively: - -``` -paddle pfs rm -r /pfs/$DATACENTER/home/$USER/folder -``` diff --git a/doc/design/file_manager/pfs/sync.md b/doc/design/file_manager/pfs/sync.md deleted file mode 100644 index 4801ceedf92..00000000000 --- a/doc/design/file_manager/pfs/sync.md +++ /dev/null @@ -1,34 +0,0 @@ -# Name -sync - sync directories. Recursively copies new and updated files from the source directory to the destination. - -# Synopsis -``` -sync [--preserve--links] [-v] -sync [--preserve--links] [-v] -sync [--preserve--links] [-v] ` -``` - -# Description - -``` -The following options are available: - ---preserve--links - Reserve links when copy links. - --v - Cause sync to be verbose, showing files after their's synchronization is complete. -``` - -# Examples -- The following command sync locally directory to pfs. - -``` -paddle pfs sync ./dir1 /pfs/$DATACENTER/home/$USER/mydir1 -``` - -- The following command sync pfs directory to local. - -``` -paddle pfs sync /pfs/$DATACENTER/home/$USER/mydir1 . -``` -- GitLab From 9221f895a8e855cbb64d5aebcdf2f870057fc246 Mon Sep 17 00:00:00 2001 From: gongweibao Date: Sun, 14 May 2017 11:34:06 +0800 Subject: [PATCH 0305/3256] rename pfsclient.md --- doc/design/file_manager/pfs/{pfs.md => pfsclient.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename doc/design/file_manager/pfs/{pfs.md => pfsclient.md} (100%) diff --git a/doc/design/file_manager/pfs/pfs.md b/doc/design/file_manager/pfs/pfsclient.md similarity index 100% rename from doc/design/file_manager/pfs/pfs.md rename to doc/design/file_manager/pfs/pfsclient.md -- GitLab From 5f0ac852888c86ed5d1d3aed1b0001e8f6f065a1 Mon Sep 17 00:00:00 2001 From: gongweibao Date: Sun, 14 May 2017 11:59:17 +0800 Subject: [PATCH 0306/3256] fix bugs --- doc/design/file_manager/README.md | 34 +++++++++--------------- doc/design/file_manager/pfs/pfsclient.md | 13 ++++++--- 2 files changed, 22 insertions(+), 25 deletions(-) diff --git a/doc/design/file_manager/README.md b/doc/design/file_manager/README.md index 8d9c7487c52..2fe8fc28187 100644 --- a/doc/design/file_manager/README.md +++ b/doc/design/file_manager/README.md @@ -1,47 +1,34 @@ # FileManager设计文档 ## 目标 -在本文档中,我们设计说明了名为FileManager系统,方便用户管理存放到PaddlePaddle Cloud上的文件。 +在本文档中,我们设计说明了名为FileManager系统,方便让用户上传自己的训练数据以进行分布式训练 主要功能包括: - 提供常用的命令行管理命令管理文件和目录 - - 支持的命令在[Here](./pfs/pfs.md) - 支持大文件的断点上传、下载 ## 名词解释 -- PFS:是Paddlepaddle cloud File System的简称,是对用户文件存储空间的抽象,与之相对的是local filesystem。目前我们用CephFS来搭建。 +- PFS:是`Paddlepaddle cloud File System`的缩写,是对用户文件存储空间的抽象,与之相对的是local filesystem。目前我们用CephFS来搭建。 - [CephFS](http://docs.ceph.com/docs/master/cephfs/):一个POSIX兼容的文件系统。 - Chunk:逻辑划上文件分块的单位。 -- [Ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/):提供七层协议的反向代理、基于粘性会话的负载均衡。 ## 模块 - ### 架构图 ### PFSClient -- 功能: 详细的内容看 +- 功能: 详细设计[link](./pfs/pfsclient.md) - 提供用户管理文件的命令 - - 用Go写,可以跨平台执行 + - 需要可以跨平台执行 - 双向验证 PFSClient需要和Ingress之间做双向验证[tls](#tls),所以用户需要首先在`cloud.paddlepaddle.org`上注册一下,申请用户空间,并且把系统生成的CA(certificate authority)、Key、CRT(CA signed certificate)下载到本地,然后才能使用PFSClient。 - -- 命令格式 - -``` -paddle [options] pfs [parameters] - -options: - -``` - -### Ingress + +### [Ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/) - 功能: 提供七层协议的反向代理、基于粘性会话的负载均衡功能。 - 透传用户身份的办法 - Ingress需要把PFSClient的身份信息传给FileServer,配置的方法参考[Here](http://www.integralist.co.uk/posts/clientcertauth.html#3) - + Ingress需要把PFSClient的身份信息传给PFSServer,配置的方法参考[link](http://www.integralist.co.uk/posts/clientcertauth.html#3) ### PFSServer PFSServer提供RESTful API接口,接收处理PFSClient端的文件管理请求,并且把结果返回PFSClient端。 @@ -65,7 +52,7 @@ RESTful API ## 文件传输优化 ### 分块文件传输 -用户文件可能是比较大的,上传到Cloud或者下载到本地的时间可能比较长,而且在传输的过程中也可能出现网络不稳定的情况。为了应对以上的问题,我们提出了Chunk的概念,一个Chunk由所在的文件偏移、数据、数据长度及校验值组成。文件数据内容的上传和下载都是都过Chunk的操作来实现的。由于Chunk比较小(默认256K),完成一个传输动作完成的时间也比较短,不容易出错。PFSClient在传输完毕最后一个Chunk的时候检查destination文件的MD5值是否和source文件一致。 +用户文件可能是比较大的,上传到Cloud或者下载到本地的时间可能比较长,而且在传输的过程中也可能出现网络不稳定的情况。为了应对以上的问题,我们提出了Chunk的概念,一个Chunk由所在的文件偏移、数据、数据长度及校验值组成。文件数据内容的上传和下载都是都过Chunk的操作来实现的。由于Chunk比较小(默认256K),完成一个传输动作完成的时间也比较短,不容易出错。PFSClient需要在传输完毕最后一个Chunk的时候检查destination文件的MD5值是否和source文件一致。 一个典型的Chunk如下所示: @@ -84,8 +71,11 @@ type Chunk struct { ### 覆盖不一致的部分 文件传输的的关键在于需要PFSClient端对比source和destination的文件Chunks的checksum是否保持一致,不一致的由PFSClient下载或者传输Chunk完成。这样已经传输成功的部分就不用重新传输了。 +## 用户使用流程 +参考[link](https://github.com/PaddlePaddle/Paddle/blob/develop/doc/design/cluster_train/data_dispatch.md) + ## 框架生成 -用[swagger-api](https://github.com/swagger-api/swagger-codegen)生成Client和FileServer的框架部分,以便我们可以把更多的精力放到逻辑本身上。 +用[swagger](https://github.com/swagger-api/swagger-codegen)生成PFSClient和PFSServer的框架部分,以便我们可以把更多的精力放到逻辑本身上。 ## 参考文档 - [TLS complete guide](https://github.com/k8sp/tls/blob/master/tls.md) diff --git a/doc/design/file_manager/pfs/pfsclient.md b/doc/design/file_manager/pfs/pfsclient.md index dd4578e3696..307d6b448f0 100644 --- a/doc/design/file_manager/pfs/pfsclient.md +++ b/doc/design/file_manager/pfs/pfsclient.md @@ -1,4 +1,4 @@ -# PFS Client +# PFSClient ## Description The `pfs` command is a Command Line Interface to manage your files on PaddlePaddle Cloud @@ -28,8 +28,11 @@ paddle [options] pfs [parameters] ## Path Arguments When using a command, we need to specify path arguments. There are two path argument type: `localpath` and `pfspath`. + A `pfspath` begin with `/pfs`, eg: `/pfs/$DATACENTER/home/$USER/folder`. +[Here](https://github.com/PaddlePaddle/Paddle/blob/develop/doc/design/cluster_train/data_dispatch.md#上传训练文件) is how to config DataCenter + ## order of Path Arguments Commonly, if there are two path arguments, the first is the source, and the second is the destination. @@ -42,7 +45,7 @@ Synopsis: Options: -r - remove directories and their contents recursively + Remove directories and their contents recursively -v Cause rm to be verbose, showing files after they are removed. @@ -86,7 +89,7 @@ Synopsis: Options: -r Copy directories recursively - -f + -f Do not prompt for confirmation before overwriting the destination path. (The -f option overrides previous -n options.) -n Do not overwrite an existing file. (The -n option overrides previous -f options.) @@ -94,6 +97,10 @@ Options: Cause cp to be verbose, showing files after they are copied. --preserve--links Reserve links when copy links + +Examples: + paddle pfs cp ./file /pfs/$DATACENTER/home/$USER/file + paddle pfs cp /pfs/$DATACENTER/home/$USER/file ./file ``` - ls- list files -- GitLab From ebdd6bae1b44fd1277fb08394287d927d39d2e1b Mon Sep 17 00:00:00 2001 From: gongweibao Date: Sun, 14 May 2017 12:07:56 +0800 Subject: [PATCH 0307/3256] fix bugs --- doc/design/file_manager/README.md | 8 ++++++-- doc/design/file_manager/pfs/pfsclient.md | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/doc/design/file_manager/README.md b/doc/design/file_manager/README.md index 2fe8fc28187..2bb0af98ae2 100644 --- a/doc/design/file_manager/README.md +++ b/doc/design/file_manager/README.md @@ -1,6 +1,7 @@ # FileManager设计文档 ## 目标 在本文档中,我们设计说明了名为FileManager系统,方便让用户上传自己的训练数据以进行分布式训练 + 主要功能包括: - 提供常用的命令行管理命令管理文件和目录 @@ -41,13 +42,16 @@ RESTful API - `POST /api/v1/files`: Create files or directories. - `DELETE /api/v1/files`: Delete files or directories. +- /api/v1/file/chunks + - `GET /api/v1/storage/file/chunks`: Get chunks's attributes in a file. + - /api/v1/storage/files - `GET /api/v1/storage/files`: Download files or directories to local. - `POST /api/v1/storage/files`: Upload files or directories to server. - /api/v1/storage/file/chunks - - `GET /api/v1/storage/file/chunks`: Get chunks's attributes in a file. - - `POST /api/v1/storage/file/chunks`: Upload chunks to a file. + - `GET /api/v1/storage/file/chunks`: Upload chunks data. + - `POST /api/v1/storage/file/chunks`: Download chunks data. ``` ## 文件传输优化 diff --git a/doc/design/file_manager/pfs/pfsclient.md b/doc/design/file_manager/pfs/pfsclient.md index 307d6b448f0..eb119689bf5 100644 --- a/doc/design/file_manager/pfs/pfsclient.md +++ b/doc/design/file_manager/pfs/pfsclient.md @@ -31,7 +31,7 @@ When using a command, we need to specify path arguments. There are two path argu A `pfspath` begin with `/pfs`, eg: `/pfs/$DATACENTER/home/$USER/folder`. -[Here](https://github.com/PaddlePaddle/Paddle/blob/develop/doc/design/cluster_train/data_dispatch.md#上传训练文件) is how to config DataCenter +[Here](https://github.com/PaddlePaddle/Paddle/blob/develop/doc/design/cluster_train/data_dispatch.md#上传训练文件) is how to config datacenters. ## order of Path Arguments Commonly, if there are two path arguments, the first is the source, and the second is the destination. -- GitLab From d187cdfa8eb9d29a347892e066cbc6bb83bd55dc Mon Sep 17 00:00:00 2001 From: gongweibao Date: Sun, 14 May 2017 12:15:17 +0800 Subject: [PATCH 0308/3256] fix bugs --- doc/design/file_manager/README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/doc/design/file_manager/README.md b/doc/design/file_manager/README.md index 2bb0af98ae2..f06dee1cf30 100644 --- a/doc/design/file_manager/README.md +++ b/doc/design/file_manager/README.md @@ -1,6 +1,6 @@ # FileManager设计文档 ## 目标 -在本文档中,我们设计说明了名为FileManager系统,方便让用户上传自己的训练数据以进行分布式训练 +在本文档中,我们设计说明了名为FileManager系统,方便用户上传自己的训练数据以进行分布式训练 主要功能包括: @@ -43,17 +43,17 @@ RESTful API - `DELETE /api/v1/files`: Delete files or directories. - /api/v1/file/chunks - - `GET /api/v1/storage/file/chunks`: Get chunks's attributes in a file. + - `GET /api/v1/storage/file/chunks`: Get chunks's attributes of a file. - /api/v1/storage/files - - `GET /api/v1/storage/files`: Download files or directories to local. - - `POST /api/v1/storage/files`: Upload files or directories to server. + - `GET /api/v1/storage/files`: Download files or directories. + - `POST /api/v1/storage/files`: Upload files or directories. - /api/v1/storage/file/chunks - - `GET /api/v1/storage/file/chunks`: Upload chunks data. - - `POST /api/v1/storage/file/chunks`: Download chunks data. + - `GET /api/v1/storage/file/chunks`: Download chunks's data. + - `POST /api/v1/storage/file/chunks`: Upload chunks's data. ``` -## 文件传输优化 +## 大文件传输优化 ### 分块文件传输 用户文件可能是比较大的,上传到Cloud或者下载到本地的时间可能比较长,而且在传输的过程中也可能出现网络不稳定的情况。为了应对以上的问题,我们提出了Chunk的概念,一个Chunk由所在的文件偏移、数据、数据长度及校验值组成。文件数据内容的上传和下载都是都过Chunk的操作来实现的。由于Chunk比较小(默认256K),完成一个传输动作完成的时间也比较短,不容易出错。PFSClient需要在传输完毕最后一个Chunk的时候检查destination文件的MD5值是否和source文件一致。 -- GitLab From 3c702059650bcc83114e050013f56ed942c017ed Mon Sep 17 00:00:00 2001 From: gongweibao Date: Sun, 14 May 2017 12:23:39 +0800 Subject: [PATCH 0309/3256] fix bugs --- doc/design/file_manager/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/design/file_manager/README.md b/doc/design/file_manager/README.md index f06dee1cf30..2cfc459d2bc 100644 --- a/doc/design/file_manager/README.md +++ b/doc/design/file_manager/README.md @@ -39,7 +39,7 @@ RESTful API - /api/v1/files - `GET /api/v1/files`: Get attributes of files or directories. - - `POST /api/v1/files`: Create files or directories. + - `POST /api/v1/files`: Update files or directories. - `DELETE /api/v1/files`: Delete files or directories. - /api/v1/file/chunks @@ -53,7 +53,7 @@ RESTful API - `GET /api/v1/storage/file/chunks`: Download chunks's data. - `POST /api/v1/storage/file/chunks`: Upload chunks's data. ``` -## 大文件传输优化 +## 文件传输优化 ### 分块文件传输 用户文件可能是比较大的,上传到Cloud或者下载到本地的时间可能比较长,而且在传输的过程中也可能出现网络不稳定的情况。为了应对以上的问题,我们提出了Chunk的概念,一个Chunk由所在的文件偏移、数据、数据长度及校验值组成。文件数据内容的上传和下载都是都过Chunk的操作来实现的。由于Chunk比较小(默认256K),完成一个传输动作完成的时间也比较短,不容易出错。PFSClient需要在传输完毕最后一个Chunk的时候检查destination文件的MD5值是否和source文件一致。 -- GitLab From e371ae9c4990ba08fb2aa4ac1338afa0ba29a8cb Mon Sep 17 00:00:00 2001 From: dangqingqing Date: Mon, 15 May 2017 11:03:34 +0800 Subject: [PATCH 0310/3256] Remove opencv-python in python/setup.py. --- python/setup.py.in | 1 - 1 file changed, 1 deletion(-) diff --git a/python/setup.py.in b/python/setup.py.in index 7d9438e3f81..5dfb46192ae 100644 --- a/python/setup.py.in +++ b/python/setup.py.in @@ -18,7 +18,6 @@ setup(name='paddle', "numpy", "protobuf==${PROTOBUF_VERSION}", "matplotlib", - "opencv-python", ], packages=packages, package_dir={ -- GitLab From 1171014d3ffc31aae5c4572c495c110975220444 Mon Sep 17 00:00:00 2001 From: caoying03 Date: Mon, 15 May 2017 11:32:06 +0800 Subject: [PATCH 0311/3256] add param_attr to nce_layer and enable multiple inputs. --- python/paddle/trainer_config_helpers/layers.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/python/paddle/trainer_config_helpers/layers.py b/python/paddle/trainer_config_helpers/layers.py index 31652613fb3..52c7a57a1f8 100755 --- a/python/paddle/trainer_config_helpers/layers.py +++ b/python/paddle/trainer_config_helpers/layers.py @@ -4921,12 +4921,14 @@ def crf_decoding_layer(input, @wrap_act_default(act=SigmoidActivation()) @wrap_bias_attr_default(has_bias=True) +@wrap_param_attr_default() @wrap_name_default() @layer_support() def nce_layer(input, label, num_classes, act=None, + param_attr=None, weight=None, num_neg_samples=10, neg_distribution=None, @@ -4957,6 +4959,8 @@ def nce_layer(input, :type num_classes: int :param act: Activation, default is Sigmoid. :type act: BaseActivation + :param param_attr: The Parameter Attribute|list. + :type param_attr: ParameterAttribute :param num_neg_samples: number of negative samples. Default is 10. :type num_neg_samples: int :param neg_distribution: The distribution for generating the random negative labels. @@ -4972,7 +4976,16 @@ def nce_layer(input, """ if isinstance(input, LayerOutput): input = [input] + assert not isinstance(param_attr, collections.Sequence) + param_attr = [param_attr] + else: + if isinstance(param_attr, collections.Sequence): + assert len(input) == len(param_attr) + else: + param_attr = [copy.deepcopy(param_attr) for _ in range(len(input))] + assert isinstance(input, collections.Sequence) + assert isinstance(label, LayerOutput) assert label.layer_type == LayerType.DATA if neg_distribution is not None: @@ -4984,9 +4997,9 @@ def nce_layer(input, ipts_for_layer = [] parents = [] - for each_input in input: + for each_input, attr in zip(input, param_attr): assert isinstance(each_input, LayerOutput) - ipts_for_layer.append(each_input.name) + ipts_for_layer.append(Input(each_input.name, **attr.attr)) parents.append(each_input) ipts_for_layer.append(label.name) parents.append(label) -- GitLab From 3e36e564fba2d8e22754a9c2aea458450ba492ca Mon Sep 17 00:00:00 2001 From: Luo Tao Date: Mon, 15 May 2017 13:24:45 +0800 Subject: [PATCH 0312/3256] add multiple outputs in annotation of paddle.infer --- python/paddle/v2/inference.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/python/paddle/v2/inference.py b/python/paddle/v2/inference.py index b4bb3849693..2c1ae08f46a 100644 --- a/python/paddle/v2/inference.py +++ b/python/paddle/v2/inference.py @@ -97,8 +97,22 @@ def infer(output_layer, parameters, input, feeding=None, field='value'): input=SomeData) print result + If there are multiple outout_layers and fields, the examples usages: + + .. code-block:: python + + result = paddle.infer(output_layer=[prediction1, prediction2], + parameters=parameters, + input=SomeData, + field=[id, value]]) + print result + + The result order is prediction1.id, prediction2.id, prediction1.value, + prediction2.value. + :param output_layer: output of the neural network that would be inferred - :type output_layer: paddle.v2.config_base.Layer + :type output_layer: paddle.v2.config_base.Layer or a list of + paddle.v2.config_base.Layer :param parameters: parameters of the neural network. :type parameters: paddle.v2.parameters.Parameters :param input: input data batch. Should be a python iterable object, and each -- GitLab From 7556ceffa6d0174ce7d2135b62b5c4c888400dcb Mon Sep 17 00:00:00 2001 From: caoying03 Date: Mon, 15 May 2017 13:17:36 +0800 Subject: [PATCH 0313/3256] add unitest for nce layer. --- .../paddle/trainer_config_helpers/layers.py | 7 ++- .../protostr/test_cost_layers.protostr | 39 +++++++++++++++ .../test_cost_layers_with_weight.protostr | 49 +++++++++++++++++++ .../tests/configs/test_cost_layers.py | 4 +- .../configs/test_cost_layers_with_weight.py | 7 ++- 5 files changed, 102 insertions(+), 4 deletions(-) diff --git a/python/paddle/trainer_config_helpers/layers.py b/python/paddle/trainer_config_helpers/layers.py index 52c7a57a1f8..2af7c9c9c44 100755 --- a/python/paddle/trainer_config_helpers/layers.py +++ b/python/paddle/trainer_config_helpers/layers.py @@ -4926,7 +4926,7 @@ def crf_decoding_layer(input, @layer_support() def nce_layer(input, label, - num_classes, + num_classes=None, act=None, param_attr=None, weight=None, @@ -4944,7 +4944,8 @@ def nce_layer(input, .. code-block:: python - cost = nce_layer(input=layer1, label=layer2, weight=layer3, + cost = nce_layer(input=[layer1, layer2], label=layer2, + param_attr=[attr1, attr2], weight=layer3, num_classes=3, neg_distribution=[0.1,0.3,0.6]) :param name: layer name @@ -4988,6 +4989,8 @@ def nce_layer(input, assert isinstance(label, LayerOutput) assert label.layer_type == LayerType.DATA + if num_classes is None: + num_classes = label.size if neg_distribution is not None: assert isinstance(neg_distribution, collections.Sequence) assert len(neg_distribution) == num_classes diff --git a/python/paddle/trainer_config_helpers/tests/configs/protostr/test_cost_layers.protostr b/python/paddle/trainer_config_helpers/tests/configs/protostr/test_cost_layers.protostr index 05fd1c99d2d..05847344be6 100644 --- a/python/paddle/trainer_config_helpers/tests/configs/protostr/test_cost_layers.protostr +++ b/python/paddle/trainer_config_helpers/tests/configs/protostr/test_cost_layers.protostr @@ -215,6 +215,22 @@ layers { } coeff: 1.0 } +layers { + name: "__nce_layer_0__" + type: "nce" + size: 1 + active_type: "sigmoid" + inputs { + input_layer_name: "__fc_layer_0__" + input_parameter_name: "___nce_layer_0__.w0" + } + inputs { + input_layer_name: "labels" + } + bias_parameter_name: "___nce_layer_0__.wbias" + num_classes: 5000 + num_neg_samples: 10 +} parameters { name: "___fc_layer_0__.w0" size: 800 @@ -245,6 +261,26 @@ parameters { initial_strategy: 0 initial_smart: true } +parameters { + name: "___nce_layer_0__.w0" + size: 20000 + initial_mean: 0.0 + initial_std: 0.0141421356237 + dims: 5000 + dims: 4 + initial_strategy: 0 + initial_smart: true +} +parameters { + name: "___nce_layer_0__.wbias" + size: 5000 + initial_mean: 0.0 + initial_std: 0.0 + dims: 1 + dims: 5000 + initial_strategy: 0 + initial_smart: false +} input_layer_names: "input" input_layer_names: "labels" input_layer_names: "crf_label" @@ -267,6 +303,7 @@ output_layer_names: "__cross_entropy_with_selfnorm_0__" output_layer_names: "__huber_cost_0__" output_layer_names: "__multi_binary_label_cross_entropy_0__" output_layer_names: "__sum_cost_0__" +output_layer_names: "__nce_layer_0__" sub_models { name: "root" layer_names: "input" @@ -292,6 +329,7 @@ sub_models { layer_names: "__huber_cost_0__" layer_names: "__multi_binary_label_cross_entropy_0__" layer_names: "__sum_cost_0__" + layer_names: "__nce_layer_0__" input_layer_names: "input" input_layer_names: "labels" input_layer_names: "crf_label" @@ -314,6 +352,7 @@ sub_models { output_layer_names: "__huber_cost_0__" output_layer_names: "__multi_binary_label_cross_entropy_0__" output_layer_names: "__sum_cost_0__" + output_layer_names: "__nce_layer_0__" is_recurrent_layer_group: false } diff --git a/python/paddle/trainer_config_helpers/tests/configs/protostr/test_cost_layers_with_weight.protostr b/python/paddle/trainer_config_helpers/tests/configs/protostr/test_cost_layers_with_weight.protostr index 3244181a631..b7d74f85ab4 100644 --- a/python/paddle/trainer_config_helpers/tests/configs/protostr/test_cost_layers_with_weight.protostr +++ b/python/paddle/trainer_config_helpers/tests/configs/protostr/test_cost_layers_with_weight.protostr @@ -60,6 +60,31 @@ layers { } coeff: 1.0 } +layers { + name: "multi_class_label" + type: "data" + size: 500 + active_type: "" +} +layers { + name: "__nce_layer_0__" + type: "nce" + size: 1 + active_type: "sigmoid" + inputs { + input_layer_name: "__fc_layer_0__" + input_parameter_name: "___nce_layer_0__.w0" + } + inputs { + input_layer_name: "multi_class_label" + } + inputs { + input_layer_name: "weight" + } + bias_parameter_name: "___nce_layer_0__.wbias" + num_classes: 500 + num_neg_samples: 10 +} parameters { name: "___fc_layer_0__.w0" size: 3000 @@ -80,9 +105,30 @@ parameters { initial_strategy: 0 initial_smart: false } +parameters { + name: "___nce_layer_0__.w0" + size: 5000 + initial_mean: 0.0 + initial_std: 0.04472135955 + dims: 500 + dims: 10 + initial_strategy: 0 + initial_smart: true +} +parameters { + name: "___nce_layer_0__.wbias" + size: 500 + initial_mean: 0.0 + initial_std: 0.0 + dims: 1 + dims: 500 + initial_strategy: 0 + initial_smart: false +} input_layer_names: "input" input_layer_names: "label" input_layer_names: "weight" +input_layer_names: "multi_class_label" output_layer_names: "__cost_0__" output_layer_names: "__mse_cost_0__" evaluators { @@ -100,9 +146,12 @@ sub_models { layer_names: "__fc_layer_0__" layer_names: "__cost_0__" layer_names: "__mse_cost_0__" + layer_names: "multi_class_label" + layer_names: "__nce_layer_0__" input_layer_names: "input" input_layer_names: "label" input_layer_names: "weight" + input_layer_names: "multi_class_label" output_layer_names: "__cost_0__" output_layer_names: "__mse_cost_0__" evaluator_names: "classification_error_evaluator" diff --git a/python/paddle/trainer_config_helpers/tests/configs/test_cost_layers.py b/python/paddle/trainer_config_helpers/tests/configs/test_cost_layers.py index 18ff6b48c49..d2a3b702a1d 100644 --- a/python/paddle/trainer_config_helpers/tests/configs/test_cost_layers.py +++ b/python/paddle/trainer_config_helpers/tests/configs/test_cost_layers.py @@ -40,4 +40,6 @@ outputs( name='huber_label', size=1)), multi_binary_label_cross_entropy( input=probs, label=xe_label), - sum_cost(input=hidden)) + sum_cost(input=hidden), + nce_layer( + input=hidden, label=labels)) diff --git a/python/paddle/trainer_config_helpers/tests/configs/test_cost_layers_with_weight.py b/python/paddle/trainer_config_helpers/tests/configs/test_cost_layers_with_weight.py index 1c0aa7f9b9e..c369062930e 100644 --- a/python/paddle/trainer_config_helpers/tests/configs/test_cost_layers_with_weight.py +++ b/python/paddle/trainer_config_helpers/tests/configs/test_cost_layers_with_weight.py @@ -11,4 +11,9 @@ outputs( classification_cost( input=fc, label=lbl, weight=wt), mse_cost( - input=fc, label=lbl, weight=wt)) + input=fc, label=lbl, weight=wt), + nce_layer( + input=fc, + label=data_layer( + name='multi_class_label', size=500), + weight=wt)) -- GitLab From bc02d1aa96acce39522dc45b25bab84d420ed27a Mon Sep 17 00:00:00 2001 From: Luo Tao Date: Mon, 15 May 2017 14:11:48 +0800 Subject: [PATCH 0314/3256] follow comments --- python/paddle/v2/inference.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/python/paddle/v2/inference.py b/python/paddle/v2/inference.py index 2c1ae08f46a..139339902e9 100644 --- a/python/paddle/v2/inference.py +++ b/python/paddle/v2/inference.py @@ -88,7 +88,7 @@ def infer(output_layer, parameters, input, feeding=None, field='value'): Infer a neural network by given neural network output and parameters. The user should pass either a batch of input data or reader method. - Example usages: + Example usage for sinlge output_layer: .. code-block:: python @@ -97,7 +97,7 @@ def infer(output_layer, parameters, input, feeding=None, field='value'): input=SomeData) print result - If there are multiple outout_layers and fields, the examples usages: + Example usage for multiple outout_layers and fields: .. code-block:: python @@ -107,9 +107,6 @@ def infer(output_layer, parameters, input, feeding=None, field='value'): field=[id, value]]) print result - The result order is prediction1.id, prediction2.id, prediction1.value, - prediction2.value. - :param output_layer: output of the neural network that would be inferred :type output_layer: paddle.v2.config_base.Layer or a list of paddle.v2.config_base.Layer @@ -126,7 +123,9 @@ def infer(output_layer, parameters, input, feeding=None, field='value'): Note that `prob` only used when output_layer is beam_search or max_id. :type field: str - :return: a numpy array + :return: The prediction result. If there are multiple outout_layers and fields, + the return order is outout_layer1.field1, outout_layer2.field1, ..., + outout_layer1.field2, outout_layer2.field2 ... :rtype: numpy.ndarray """ -- GitLab From 7f9a424858c823137c4977d35e411f5594b6e5e1 Mon Sep 17 00:00:00 2001 From: cxysteven Date: Mon, 15 May 2017 14:41:37 +0800 Subject: [PATCH 0315/3256] vae demo --- demo/vae/README.md | 13 +++ demo/vae/data/get_mnist_data.sh | 17 ++++ demo/vae/dataloader.py | 60 +++++++++++ demo/vae/dataloader.pyc | Bin 0 -> 2148 bytes demo/vae/vae_conf.py | 116 +++++++++++++++++++++ demo/vae/vae_train.py | 175 ++++++++++++++++++++++++++++++++ 6 files changed, 381 insertions(+) create mode 100644 demo/vae/README.md create mode 100755 demo/vae/data/get_mnist_data.sh create mode 100644 demo/vae/dataloader.py create mode 100644 demo/vae/dataloader.pyc create mode 100644 demo/vae/vae_conf.py create mode 100644 demo/vae/vae_train.py diff --git a/demo/vae/README.md b/demo/vae/README.md new file mode 100644 index 00000000000..e55d483b023 --- /dev/null +++ b/demo/vae/README.md @@ -0,0 +1,13 @@ +#Variational Autoencoder (VAE) + +This demo implements VAE training described in the original paper (https://arxiv.org/abs/1312.6114). + + +In order to run the model, first download the MNIST dataset by running the shell script in ./data. + +Then you can run the command below. The flag --useGpu specifies whether to use gpu for training (0 is cpu, 1 is gpu). + +$python vae_train.py [--use_gpu 1] + +The generated images will be stored in ./samples/ +The corresponding models will be stored in ./params/ diff --git a/demo/vae/data/get_mnist_data.sh b/demo/vae/data/get_mnist_data.sh new file mode 100755 index 00000000000..a77c81bf5af --- /dev/null +++ b/demo/vae/data/get_mnist_data.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env sh +# This script downloads the mnist data and unzips it. +set -e +DIR="$( cd "$(dirname "$0")" ; pwd -P )" +rm -rf "$DIR/mnist_data" +mkdir "$DIR/mnist_data" +cd "$DIR/mnist_data" + +echo "Downloading..." + +for fname in train-images-idx3-ubyte train-labels-idx1-ubyte t10k-images-idx3-ubyte t10k-labels-idx1-ubyte +do + if [ ! -e $fname ]; then + wget --no-check-certificate http://yann.lecun.com/exdb/mnist/${fname}.gz + gunzip ${fname}.gz + fi +done diff --git a/demo/vae/dataloader.py b/demo/vae/dataloader.py new file mode 100644 index 00000000000..e9ff95d44f8 --- /dev/null +++ b/demo/vae/dataloader.py @@ -0,0 +1,60 @@ +# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import numpy as np + + +class MNISTloader(): + def __init__(self, + data_path="./data/mnist_data/", + batch_size=60, + process='train'): + self.batch_size = batch_size + self.data_path = data_path + self._pointer = 0 + self.image_batches = np.array([]) + self.process = process + + def _extract_images(self, filename, n): + f = open(filename, 'rb') + f.read(16) + data = np.fromfile(f, 'ubyte', count=n * 28 * 28).reshape((n, 28 * 28)) + #Mapping data into [-1, 1] + data = data / 255. * 2. - 1 + data_batches = np.split(data, 60000 / self.batch_size, 0) + + f.close() + + return data_batches + + @property + def pointer(self): + return self._pointer + + def load_data(self): + TRAIN_IMAGES = '%s/train-images-idx3-ubyte' % self.data_path + TEST_IMAGES = '%s/t10k-images-idx3-ubyte' % self.data_path + + if self.process == 'train': + self.image_batches = self._extract_images(TRAIN_IMAGES, 60000) + else: + self.image_batches = self._extract_images(TEST_IMAGES, 10000) + + def next_batch(self): + batch = self.image_batches[self._pointer] + self._pointer = (self._pointer + 1) % (60000 / self.batch_size) + return np.array(batch) + + def reset_pointer(self): + self._pointer = 0 diff --git a/demo/vae/dataloader.pyc b/demo/vae/dataloader.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1be8890dafd76e6cb2028bcbfdf2022c18a229ae GIT binary patch literal 2148 zcmb_dO>f&q5S=CUVL4W97_l3-=?4%L)uE0W^il*t5w{2mAFKoAOLSprab?qyNP)W! zqQW}4K>udXz4iyR@6FN<(4Oj6_Kap{$=R7VZ%6ST{mql-2d}5nd}4gRgt#YAIsT2z zMJ7h_Nb;8CvHT)(Bl3EJwUP0ljpvF#@frCCFi%FuJ zOazRD!CH(wiN)fSww_{S(w4lV{*L5bHM){-sIei*c+g|mU8twSJoDLbQ5MdpY8Efx z_Ds*$Sy4`z1to&~0CCTut|Pe?Nnsc@P-ieA@v&UCA0VRVj zl;4Cp%lz~rb;ZxtGc)FqE;E1OH?T^VRZ)6dd%jsNHMOj+vz70|Q7p1^n`!{-JR>ga zYHFPuFlkTa?0lBQFcL;c7;^v#%F!HNR0})I=2@N><*>--!`v>a;oHnAJfSa7m#dIZ zT9k!PQ=a9Ry_lZTDm#Sxa1j*r9in63> z{C)emjX0@&2c}Pbs#J0xz@t$t6MvfVL;f6X74g*SuMgH!aL*7>(F{>zzK(2G#$cf1~o$yW3iUdfcS literal 0 HcmV?d00001 diff --git a/demo/vae/vae_conf.py b/demo/vae/vae_conf.py new file mode 100644 index 00000000000..301dd23793d --- /dev/null +++ b/demo/vae/vae_conf.py @@ -0,0 +1,116 @@ +# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from paddle.trainer_config_helpers import * +import numpy as np + +is_generating = get_config_arg("is_generating", bool, False) + +settings(batch_size=32, learning_rate=1e-3, learning_method=AdamOptimizer()) + +X_dim = 28 * 28 +h_dim = 128 +z_dim = 100 + + +def reparameterization(mu, logvar): + eps = ParamAttr(initial_mean=0., initial_std=1) + with mixed_layer() as sigma: + sigma += dotmul_projection(layer_math.exp(logvar) * 0.5, param_attr=eps) + return mu + sigma + + +def q_func(X): + """ + xavier initialization + """ + param_attr = ParamAttr( + name='share.w', initial_mean=0., initial_std=1. / np.sqrt(X_dim / 2.)) + mu_param = ParamAttr( + name='mu.w', initial_mean=0., initial_std=1. / np.sqrt(h_dim / 2.)) + logvar_param = ParamAttr( + name='logvar.w', initial_mean=0., initial_std=1. / np.sqrt(h_dim / 2.)) + + bias_attr = ParamAttr(name='share.bias', initial_mean=0., initial_std=0.) + mu_bias = ParamAttr(name='mu.bias', initial_mean=0., initial_std=0.) + logvar_bias = ParamAttr(name='logvar.bias', initial_mean=0., initial_std=0.) + + share_layer = fc_layer( + X, + size=h_dim, + param_attr=param_attr, + bias_attr=bias_attr, + act=ReluActivation()) + + return (fc_layer( + share_layer, + size=z_dim, + param_attr=mu_param, + bias_attr=mu_bias, + act=LinearActivation()), fc_layer( + share_layer, + size=z_dim, + param_attr=logvar_param, + bias_attr=logvar_bias, + act=LinearActivation())) + + +def generator(z): + + hidden_param = ParamAttr( + name='hidden.w', initial_mean=0., initial_std=1. / np.sqrt(z_dim / 2.)) + hidden_bias = ParamAttr(name='hidden.bias', initial_mean=0., initial_std=0.) + prob_param = ParamAttr( + name='prob.w', initial_mean=0., initial_std=1. / np.sqrt(h_dim / 2.)) + prob_bias = ParamAttr(name='prob.bias', initial_mean=0., initial_std=0.) + + hidden_layer = fc_layer( + z, + size=h_dim, + act=ReluActivation(), + param_attr=hidden_param, + bias_attr=hidden_bias) + prob = fc_layer( + hidden_layer, + size=X_dim, + act=SigmoidActivation(), + param_attr=prob_param, + bias_attr=prob_bias) + + return prob + + +def reconstruct_error(prob, X): + cost = multi_binary_label_cross_entropy(input=prob, label=X) + return cost + + +def KL_loss(mu, logvar): + with mixed_layer() as mu_square: + mu_square += dotmul_operator(mu, mu, scale=1.) + + cost = 0.5 * sum_cost(layer_math.exp(logvar) + mu_square - 1. - logvar) + + return cost + + +if not is_generating: + x_batch = data_layer(name='x_batch', size=X_dim) + mu, logvar = q_func(x_batch) + z_samples = reparameterization(mu, logvar) + prob = generator(z_samples) + outputs(reconstruct_error(prob, x_batch) + KL_loss(mu, logvar)) +else: + z_samples = data_layer(name='noise', size=z_dim) + outputs(generator(z_samples)) diff --git a/demo/vae/vae_train.py b/demo/vae/vae_train.py new file mode 100644 index 00000000000..1babb011c77 --- /dev/null +++ b/demo/vae/vae_train.py @@ -0,0 +1,175 @@ +# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import argparse +import random +import numpy as np +import cPickle +import sys, os +from PIL import Image + +from paddle.trainer.config_parser import parse_config +from paddle.trainer.config_parser import logger +import py_paddle.swig_paddle as api +import dataloader +import matplotlib.pyplot as plt + + +def plot_samples(samples): + fig = plt.figure(figsize=(4, 4)) + gs = gridspec.GridSpec(4, 4) + gs.update(wspace=0.05, hspace=0.05) + for i, sample in enumerate(samples): + plt.subplot(gs[i]) + plt.axis('off') + plt.imshow(sample.reshape(28, 28), cmap='Greys_r') + + return fig + + +def CHECK_EQ(a, b): + assert a == b, "a=%s, b=%s" % (a, b) + + +def get_fake_samples(generator_machine, batch_size, noise): + gen_inputs = api.Arguments.createArguments(1) + gen_inputs.setSlotValue(0, api.Matrix.createDenseFromNumpy(noise)) + gen_outputs = api.Arguments.createArguments(0) + generator_machine.forward(gen_inputs, gen_outputs, api.PASS_TEST) + fake_samples = gen_outputs.getSlotValue(0).copyToNumpyMat() + return fake_samples + + +def copy_shared_parameters(src, dst): + ''' + copy the parameters from src to dst + :param src: the source of the parameters + :type src: GradientMachine + :param dst: the destination of the parameters + :type dst: GradientMachine + ''' + src_params = [src.getParameter(i) for i in xrange(src.getParameterSize())] + src_params = dict([(p.getName(), p) for p in src_params]) + + for i in xrange(dst.getParameterSize()): + dst_param = dst.getParameter(i) + src_param = src_params.get(dst_param.getName(), None) + if src_param is None: + continue + src_value = src_param.getBuf(api.PARAMETER_VALUE) + dst_value = dst_param.getBuf(api.PARAMETER_VALUE) + CHECK_EQ(len(src_value), len(dst_value)) + dst_value.copyFrom(src_value) + dst_param.setValueUpdated() + + +def find(iterable, cond): + for item in iterable: + if cond(item): + return item + return None + + +def get_layer_size(model_conf, layer_name): + layer_conf = find(model_conf.layers, lambda x: x.name == layer_name) + assert layer_conf is not None, "Cannot find '%s' layer" % layer_name + return layer_conf.size + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument( + "--use_gpu", default="1", help="1 means use gpu for training") + parser.add_argument("--gpu_id", default="0", help="the gpu_id parameter") + args = parser.parse_args() + use_gpu = args.use_gpu + assert use_gpu in ["0", "1"] + + if not os.path.exists("./samples/"): + os.makedirs("./samples/") + + if not os.path.exists("./params/"): + os.makedirs("./params/") + + api.initPaddle('--use_gpu=' + use_gpu, '--dot_period=10', + '--log_period=1000', '--gpu_id=' + args.gpu_id, + '--save_dir=' + "./params/") + + conf = "vae_conf.py" + + trainer_conf = parse_config(conf, "is_generating=False") + gener_conf = parse_config(conf, "is_generating=True") + + batch_size = trainer_conf.opt_config.batch_size + + noise_dim = get_layer_size(gener_conf.model_config, "noise") + + mnist = dataloader.MNISTloader(batch_size=batch_size) + mnist.load_data() + + training_machine = api.GradientMachine.createFromConfigProto( + trainer_conf.model_config) + + generator_machine = api.GradientMachine.createFromConfigProto( + gener_conf.model_config) + + trainer = api.Trainer.create(trainer_conf, training_machine) + + trainer.startTrain() + + for train_pass in xrange(100): + trainer.startTrainPass() + mnist.reset_pointer() + i = 0 + it = 0 + while mnist.pointer != 0 or i == 0: + X = mnist.next_batch().astype('float32') + + inputs = api.Arguments.createArguments(1) + inputs.setSlotValue(0, api.Matrix.createDenseFromNumpy(X)) + + trainer.trainOneDataBatch(batch_size, inputs) + + if it % 1000 == 0: + + outputs = api.Arguments.createArguments(0) + training_machine.forward(inputs, outputs, api.PASS_TEST) + loss = np.mean(outputs.getSlotValue(0).copyToNumpyMat()) + print "\niter: {}".format(str(it).zfill(3)) + print "VAE loss: {}".format(str(loss).zfill(3)) + + #Sync parameters between networks (GradientMachine) at the beginning + copy_shared_parameters(training_machine, generator_machine) + + z_samples = np.random.randn(batch_size, + noise_dim).astype('float32') + samples = get_fake_samples(generator_machine, batch_size, + z_samples) + + #Generating the first 16 images for a picture. + figure = plot_samples(samples[:16]) + plt.savefig( + "./samples/{}_{}.png".format( + str(train_pass).zfill(3), str(i).zfill(3)), + bbox_inches='tight') + plt.close(figure) + i += 1 + it += 1 + + trainer.finishTrainPass() + trainer.finishTrain() + + +if __name__ == '__main__': + main() -- GitLab From 87cb840ce981145ad5fbdbb34569546bbbf725d9 Mon Sep 17 00:00:00 2001 From: Peng Li Date: Mon, 15 May 2017 16:50:53 +0800 Subject: [PATCH 0316/3256] add coeff parameter to the helpers of mse_cost, crf_layer and smooth_l1_cost --- python/paddle/trainer_config_helpers/layers.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/python/paddle/trainer_config_helpers/layers.py b/python/paddle/trainer_config_helpers/layers.py index 2af7c9c9c44..3b6f0270de1 100755 --- a/python/paddle/trainer_config_helpers/layers.py +++ b/python/paddle/trainer_config_helpers/layers.py @@ -3765,7 +3765,7 @@ def __cost_input__(input, label, weight=None): @wrap_name_default() @layer_support() -def mse_cost(input, label, weight=None, name=None, layer_attr=None): +def mse_cost(input, label, weight=None, name=None, coeff=1.0, layer_attr=None): """ mean squared error cost: @@ -3782,6 +3782,8 @@ def mse_cost(input, label, weight=None, name=None, layer_attr=None): :param weight: The weight affects the cost, namely the scale of cost. It is an optional argument. :type weight: LayerOutput + :param coeff: The coefficient affects the gradient in the backward. + :type coeff: float :param layer_attr: layer's extra attribute. :type layer_attr: ExtraLayerAttribute :return: LayerOutput object. @@ -3793,6 +3795,7 @@ def mse_cost(input, label, weight=None, name=None, layer_attr=None): inputs=ipts, type="square_error", name=name, + coeff=coeff, **ExtraLayerAttribute.to_kwargs(layer_attr)) return LayerOutput(name, LayerType.COST, parents=parents, size=1) @@ -4798,6 +4801,7 @@ def crf_layer(input, weight=None, param_attr=None, name=None, + coeff=1.0, layer_attr=None): """ A layer for calculating the cost of sequential conditional random @@ -4824,6 +4828,8 @@ def crf_layer(input, :type param_attr: ParameterAttribute :param name: The name of this layers. It is not necessary. :type name: None|basestring + :param coeff: The coefficient affects the gradient in the backward. + :type coeff: float :param layer_attr: Extra Layer config. :type layer_attr: ExtraLayerAttribute|None :return: LayerOutput object. @@ -4848,6 +4854,7 @@ def crf_layer(input, type=LayerType.CRF_LAYER, size=size, inputs=ipts, + coeff=coeff, **ExtraLayerAttribute.to_kwargs(layer_attr)) parents = [input, label] if weight is not None: @@ -5379,7 +5386,7 @@ def multi_binary_label_cross_entropy(input, @wrap_name_default() @layer_support() -def smooth_l1_cost(input, label, name=None, layer_attr=None): +def smooth_l1_cost(input, label, name=None, coeff=1.0, layer_attr=None): """ This is a L1 loss but more smooth. It requires that the size of input and label are equal. The formula is as follows, @@ -5408,6 +5415,8 @@ def smooth_l1_cost(input, label, name=None, layer_attr=None): :type input: LayerOutput :param name: The name of this layers. It is not necessary. :type name: None|basestring + :param coeff: The coefficient affects the gradient in the backward. + :type coeff: float :param layer_attr: Extra Layer Attribute. :type layer_attr: ExtraLayerAttribute :return: LayerOutput object. @@ -5421,6 +5430,7 @@ def smooth_l1_cost(input, label, name=None, layer_attr=None): name=name, type=LayerType.SMOOTH_L1, inputs=[input.name, label.name], + coeff=coeff, **ExtraLayerAttribute.to_kwargs(layer_attr)) return LayerOutput( name, LayerType.SMOOTH_L1, parents=[input, label], size=1) -- GitLab From bade3e976b5f60982fc3aa08ee7812a53000d857 Mon Sep 17 00:00:00 2001 From: Luo Tao Date: Mon, 15 May 2017 17:08:51 +0800 Subject: [PATCH 0317/3256] add appointments for contributing codes in document --- doc/howto/dev/contribute_to_paddle_cn.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/doc/howto/dev/contribute_to_paddle_cn.md b/doc/howto/dev/contribute_to_paddle_cn.md index 775938612e8..a48b143c760 100644 --- a/doc/howto/dev/contribute_to_paddle_cn.md +++ b/doc/howto/dev/contribute_to_paddle_cn.md @@ -7,6 +7,7 @@ - 确保编译器选项 `WITH_STYLE_CHECK` 已打开,并且编译能通过代码样式检查。 - 所有代码必须具有单元测试。 - 通过所有单元测试。 +- 请遵守[提交代码的一些约定](#提交代码的一些约定)。 以下教程将指导您提交代码。 ## [Fork](https://help.github.com/articles/fork-a-repo/) @@ -217,3 +218,22 @@ upstream ``` 至此,我们就完成了一次代码贡献的过程。 + +## 提交代码的一些约定 + +为了使评审人在评审代码时更好地专注于代码本身,请您每次提交代码时,遵守以下约定: +1. 请保证Travis-CI 中单元测试能顺利通过。如果没过,说明提交的代码存在问题,评审人一般不做评审。 +2. 提交PUll Request前: + - 请注意commit的数量: + - 原因:如果仅仅修改一个文件但提交了十几个commit,每个commit只做了少量的修改,这会给评审人带来很大困扰。评审人需要逐一查看每个commit才能知道做了哪些修改,且不排除commit之间的修改存在相互覆盖的情况。 + - 建议:每次提交时,保持尽量少的commit,可以通过`git commit --amend`补充上次的commit。对已经Push到远程仓库的多个commit,可以参考[squash commits after push](http://stackoverflow.com/questions/5667884/how-to-squash-commits-in-git-after-they-have-been-pushed)。 + - 请注意每个commit的名称:应能反映当前commit的内容,不能太随意。 +3. 如果解决了某个Issue的问题,请在该PUll Request的**第一个**评论框中加上:`fix #issue_number`,这样当该PUll Request被合并后,会自动关闭对应的Issue。关键词包括:close, closes, closed, fix, fixes, fixed, resolve, resolves, resolved,请选择合适的词汇。详细可参考[Closing issues via commit messages](https://help.github.com/articles/closing-issues-via-commit-messages)。 + +此外,在回复评审人意见时,请您遵守以下约定: +1. 评审人的每个意见都必须回复(这是开源社区的基本礼貌,别人帮了忙,应该说谢谢): + - 对评审意见同意且按其修改完的,给个简单的`Done`即可; + - 对评审意见不同意的,请给出您自己的反驳理由。 +2. 如果评审意见比较多: + - 请给出总体的修改情况。 + - 请采用[start a review](https://help.github.com/articles/reviewing-proposed-changes-in-a-pull-request/)进行回复,而非直接回复的方式。原因是每个回复都会发送一封邮件,会造成邮件灾难。 -- GitLab From 89bb7fd2bf53ec0fc731f163a70224eff3aa54a4 Mon Sep 17 00:00:00 2001 From: Liu Yiqun Date: Mon, 15 May 2017 17:46:05 +0800 Subject: [PATCH 0318/3256] Delete the ndk directory in Dockerfile. --- Dockerfile.android | 7 +++---- paddle/scripts/docker/build_android.sh | 1 + 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Dockerfile.android b/Dockerfile.android index 4d9ced33f43..1334799ed2d 100644 --- a/Dockerfile.android +++ b/Dockerfile.android @@ -5,10 +5,8 @@ ARG UBUNTU_MIRROR RUN /bin/bash -c 'if [[ -n ${UBUNTU_MIRROR} ]]; then sed -i 's#http://archive.ubuntu.com/ubuntu#${UBUNTU_MIRROR}#g' /etc/apt/sources.list; fi' ENV HOME=/root \ - ANDROID_HOME=/opt/android-sdk-linux \ ANDROID_NDK_HOME=/opt/android-ndk-linux \ - ANDROID_STANDALONE_TOOLCHAIN=/opt/android-toolchain-gcc \ - PATH=${PATH}:${ANDROID_HOME}:${ANDROID_NDK_HOME} + ANDROID_STANDALONE_TOOLCHAIN=/opt/android-toolchain-gcc RUN apt-get update && \ apt-get install -y git python-dev python-pip python-numpy && \ @@ -38,6 +36,7 @@ RUN mkdir /opt/android-ndk-tmp && \ unzip -q android-ndk-r14b-linux-x86_64.zip && \ mv android-ndk-r14b ${ANDROID_NDK_HOME} && \ ${ANDROID_NDK_HOME}/build/tools/make-standalone-toolchain.sh --arch=arm --platform=android-21 --install-dir=${ANDROID_STANDALONE_TOOLCHAIN} && \ - rm -rf /opt/android-ndk-tmp + rm -rf /opt/android-ndk-tmp && \ + rm -rf ${ANDROID_NDK_HOME} CMD ["bash", "/paddle/paddle/scripts/docker/build_android.sh"] diff --git a/paddle/scripts/docker/build_android.sh b/paddle/scripts/docker/build_android.sh index ab432c8524e..bfa10c91553 100644 --- a/paddle/scripts/docker/build_android.sh +++ b/paddle/scripts/docker/build_android.sh @@ -4,6 +4,7 @@ set -xe mkdir -p /paddle/build cd /paddle/build +rm -f /paddle/install 2>/dev/null || true cmake -DCMAKE_SYSTEM_NAME=Android \ -DANDROID_STANDALONE_TOOLCHAIN=$ANDROID_STANDALONE_TOOLCHAIN \ -DANDROID_ABI=armeabi-v7a \ -- GitLab From 1b31a8df8b518bfa2e6f26b2d4d31d8dc3e41700 Mon Sep 17 00:00:00 2001 From: Liu Yiqun Date: Mon, 15 May 2017 17:47:35 +0800 Subject: [PATCH 0319/3256] Install the paddle c-api libraries to subdirectory named by ANDROID_ABI. --- paddle/capi/CMakeLists.txt | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/paddle/capi/CMakeLists.txt b/paddle/capi/CMakeLists.txt index 1b52a79cebb..206f5125634 100644 --- a/paddle/capi/CMakeLists.txt +++ b/paddle/capi/CMakeLists.txt @@ -58,10 +58,16 @@ target_include_directories(paddle_capi_shared PUBLIC ${CMAKE_CURRENT_BINARY_DIR} link_paddle_exe(paddle_capi_shared) # install library & headers. -install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${capi_whole_library} DESTINATION lib) install(FILES ${CAPI_HEADERS} DESTINATION include/paddle) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/config.h DESTINATION include/paddle) -install(TARGETS paddle_capi_shared DESTINATION lib) +if(ANDROID) + install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${capi_whole_library} + DESTINATION lib/${ANDROID_ABI}) + install(TARGETS paddle_capi_shared DESTINATION lib/${ANDROID_ABI}) +else(ANDROID) + install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${capi_whole_library} DESTINATION lib) + install(TARGETS paddle_capi_shared DESTINATION lib) +endif(ANDROID) # this variable used for unittest set(PADDLE_CAPI_INC_PATH -- GitLab From 96ca1e966aaf75ab7644e594603e77f71ed14ca7 Mon Sep 17 00:00:00 2001 From: dzhwinter Date: Mon, 8 May 2017 16:30:27 +0800 Subject: [PATCH 0320/3256] "add mq2007 dataset for learning to rank task" --- python/paddle/v2/dataset/mq2007.py | 293 ++++++++++++++++++ python/paddle/v2/dataset/tests/mq2007_test.py | 31 ++ 2 files changed, 324 insertions(+) create mode 100644 python/paddle/v2/dataset/mq2007.py create mode 100644 python/paddle/v2/dataset/tests/mq2007_test.py diff --git a/python/paddle/v2/dataset/mq2007.py b/python/paddle/v2/dataset/mq2007.py new file mode 100644 index 00000000000..8884dfd5b11 --- /dev/null +++ b/python/paddle/v2/dataset/mq2007.py @@ -0,0 +1,293 @@ +# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +MQ2007 dataset + +MQ2007 is a query set from Million Query track of TREC 2007. There are about 1700 queries in it with labeled documents. In MQ2007, the 5-fold cross +validation strategy is adopted and the 5-fold partitions are included in the package. In each fold, there are three subsets for learning: training set, +validation set and testing set. + +MQ2007 dataset from +http://research.microsoft.com/en-us/um/beijing/projects/letor/LETOR4.0/Data/MQ2007.rar and parse training set and test set into paddle reader creators + +""" + + +import os +import random +import functools +import rarfile +from common import download +import numpy as np + + +# URL = "http://research.microsoft.com/en-us/um/beijing/projects/letor/LETOR4.0/Data/MQ2007.rar" +URL = "http://www.bigdatalab.ac.cn/benchmark/upload/download_source/7b6dbbe2-842c-11e4-a536-bcaec51b9163_MQ2007.rar" +MD5 = "7be1640ae95c6408dab0ae7207bdc706" + + +def __initialize_meta_info__(): + """ + download and extract the MQ2007 dataset + """ + fn = fetch() + rar = rarfile.RarFile(fn) + dirpath = os.path.dirname(fn) + rar.extractall(path=dirpath) + return dirpath + + +class Query(object): + """ + queries used for learning to rank algorithms. It is created from relevance scores, query-document feature vectors + + Parameters: + ---------- + query_id : int + query_id in dataset, mapping from query to relevance documents + relevance_score : int + relevance score of query and document pair + feature_vector : array, dense feature + feature in vector format + description : string + comment section in query doc pair data + """ + def __init__(self, query_id=-1, relevance_score=-1, + feature_vector=None, description=""): + self.query_id = query_id + self.relevance_score = relevance_score + if feature_vector is None: + self.feature_vector = [] + else: + self.feature_vector = feature_vector + self.description = description + + def __str__(self): + string = "%s %s %s" %(str(self.relevance_score), str(self.query_id), " ".join(str(f) for f in self.feature_vector)) + return string + + # @classmethod + def _parse_(self, text): + """ + parse line into Query + """ + comment_position = text.find('#') + line = text[:comment_position].strip() + self.description = text[comment_position+1:].strip() + parts = line.split() + assert(len(parts) == 48), "expect 48 space split parts, get %d" %(len(parts)) + # format : 0 qid:10 1:0.000272 2:0.000000 .... + self.relevance_score = int(parts[0]) + self.query_id = int(parts[1].split(':')[1]) + for p in parts[2:]: + pair = p.split(':') + self.feature_vector.append(float(pair[1])) + return self + +class QueryList(object): + """ + group query into list, every item in list is a Query + """ + def __init__(self, querylist=None): + self.query_id = -1 + if querylist is None: + self.querylist = [] + else: + self.querylist = querylist + for query in self.querylist: + if self.query_id == -1: + self.query_id = query.query_id + else: + if self.query_id != query.query_id: + raise ValueError("query in list must be same query_id") + + def __iter__(self): + for query in self.querylist: + yield query + + def _correct_ranking_(self): + if self.querylist is None: + return + self.querylist.sort(key=lambda x:x.relevance_score, reverse=True) + + def _add_query(self, query): + if self.query_id == -1: + self.query_id = query.query_id + else: + if self.query_id != query.query_id: + raise ValueError("query in list must be same query_id") + self.querylist.append(query) + + + +def gen_pair(querylist, partial_order="full"): + """ + gen pair for pair-wise learning to rank algorithm + Paramters: + -------- + querylist : querylist, one query match many docment pairs in list, see QueryList + pairtial_order : "full" or "neighbour" + gen pairs for neighbour items or the full partial order pairs + + return : + ------ + label : np.array, shape=(1) + query_left : np.array, shape=(1, feature_dimension) + query_right : same as left + """ + if not isinstance(querylist, QueryList): + querylist = QueryList(querylist) + querylist._correct_ranking_() + # C(n,2) + if partial_order == "full": + for i, query_left in enumerate(querylist): + for j, query_right in enumerate(querylist): + if query_left.relevance_score > query_right.relevance_score: + yield np.ones(1), np.array(query_left.feature_vector), np.array(query_right.feature_vector) + else: + yield np.ones(1), np.array(query_left.feature_vector), np.array(query_right.feature_vector) + + elif partial_order == "neighbour": + # C(n) + k = 0 + while k < len(querylist)-1: + query_left = querylist[k] + query_right = querylist[k+1] + if query_left.relevance_score > query_right.relevance_score: + yield np.ones(1), np.array(query_left.feature_vector), np.array(query_right.feature_vector) + else: + yield np.ones(1), np.array(query_left.feature_vector), np.array(query_right.feature_vector) + k += 1 + else: + raise ValueError("unsupport parameter of partial_order, Only can be neighbour or full") + + +def gen_list(querylist): + """ + gen pair for pair-wise learning to rank algorithm + Paramters: + -------- + querylist : querylist, one query match many docment pairs in list, see QueryList + + return : + ------ + label : np.array, shape=(samples_num, ) + querylist : np.array, shape=(samples_num, feature_dimension) + """ + if not isinstance(querylist, QueryList): + querylist = QueryList(querylist) + querylist._correct_ranking_() + relevance_score_list = [query.relevance_score for query in querylist] + feature_vector_list = [query.feature_vector for query in querylist] + yield np.array(relevance_score_list).T, np.array(feature_vector_list) + + +def load_from_text(filepath, shuffle=True, fill_missing=-1): + """ + parse data file into querys + """ + prev_query_id = -1; + querylists = [] + querylist = None + fn = __initialize_meta_info__() + with open(os.path.join(fn, filepath)) as f: + for line in f: + query = Query() + query = query._parse_(line) + if query.query_id != prev_query_id: + if querylist is not None: + querylists.append(querylist) + querylist = QueryList() + prev_query_id = query.query_id + querylist._add_query(query) + if shuffle == True: + random.shuffle(querylists) + return querylists + + +def __reader__(filepath, format="pairwise", shuffle=True, fill_missing=-1): + """ + Parameters + -------- + filename : string + shuffle : shuffle query-doc pair under the same query + fill_missing : fill the missing value. default in MQ2007 is -1 + + Returns + ------ + yield + label query_left, query_right # format = "pairwise" + label querylist # format = "listwise" + """ + querylists = load_from_text(filepath, shuffle=shuffle, fill_missing=fill_missing) + for querylist in querylists: + if format == "pairwise": + for pair in gen_pair(querylist): + yield pair + elif format == "listwise": + yield next(gen_list(querylist)) + +train = functools.partial(__reader__,filepath="MQ2007/MQ2007/Fold1/train.txt") +test = functools.partial(__reader__, filepath="MQ2007/MQ2007/Fold1/test.txt") +# def __parse_line__(line_stream): +# """ +# return : score, qid, 46-dim feature vector +# parse line of file +# """ +# score = -1, qid = -1, features = [] +# line = line_stream[:line_stream.find('#')].strip() +# parts = line.split() +# assert(len(parts) == 48), "expect 48 space split parts, get ", len(parts) +# # format : 0 qid:10 1:0.000272 2:0.000000 .... +# score = int(parts[0]) +# qid = int(parts[1].split(':')[1]) +# for p in parts[2:]: +# pair = p.split(':') +# features.append(float(part[1])) +# return score, qid, features + + +# def __reader__(filename, rand_seed=0, is_test=False, test_rate=0.0): +# """ +# create a line reader Generator + +# Parameters +# -------- +# filename : string +# rand_seed : sample instance from dataset, set the sample random seed +# is_test : sample test set or generate train set +# test_rate : sample test set rate + +# Returns +# ------ +# yield +# int int lists +# score query_id, features +# """ +# rand = random.Random(x=rand_seed) +# with open(file_name, 'r') as f: +# for line in f: +# if (rand.random() < test_rate) == is_test: +# yield __parse_line__(line) + + +# def __pair_reader__(filename, shuffle=True): + + +def fetch(): + return download(URL, "MQ2007", MD5) + +if __name__ == "__main__": + fetch() + diff --git a/python/paddle/v2/dataset/tests/mq2007_test.py b/python/paddle/v2/dataset/tests/mq2007_test.py new file mode 100644 index 00000000000..c9bddddeb07 --- /dev/null +++ b/python/paddle/v2/dataset/tests/mq2007_test.py @@ -0,0 +1,31 @@ +# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import paddle.v2.dataset.mq2007 +import unittest + + +class TestMQ2007(unittest.TestCase): + def test_pairwise(self): + for label, query_left, query_right in paddle.v2.dataset.mq2007.test(format="pairwise"): + self.assertEqual(query_left.shape(), (46, )) + self.assertEqual(query_right.shape(), (46, )) + + def test_listwise(self): + for label_array, query_array in paddle.v2.dataset.mq2007.test(format="listwise"): + self.assertEqual(len(label_array), len(query_array)) + + +if __name__ == "__main__": + unittest.main() -- GitLab From d7ef56245020a7fbb71677337db4da8b01532802 Mon Sep 17 00:00:00 2001 From: dzhwinter Date: Mon, 8 May 2017 16:36:59 +0800 Subject: [PATCH 0321/3256] "better format" --- python/paddle/v2/dataset/mq2007.py | 45 +----------------------------- 1 file changed, 1 insertion(+), 44 deletions(-) diff --git a/python/paddle/v2/dataset/mq2007.py b/python/paddle/v2/dataset/mq2007.py index 8884dfd5b11..5705ba60dea 100644 --- a/python/paddle/v2/dataset/mq2007.py +++ b/python/paddle/v2/dataset/mq2007.py @@ -240,50 +240,7 @@ def __reader__(filepath, format="pairwise", shuffle=True, fill_missing=-1): train = functools.partial(__reader__,filepath="MQ2007/MQ2007/Fold1/train.txt") test = functools.partial(__reader__, filepath="MQ2007/MQ2007/Fold1/test.txt") -# def __parse_line__(line_stream): -# """ -# return : score, qid, 46-dim feature vector -# parse line of file -# """ -# score = -1, qid = -1, features = [] -# line = line_stream[:line_stream.find('#')].strip() -# parts = line.split() -# assert(len(parts) == 48), "expect 48 space split parts, get ", len(parts) -# # format : 0 qid:10 1:0.000272 2:0.000000 .... -# score = int(parts[0]) -# qid = int(parts[1].split(':')[1]) -# for p in parts[2:]: -# pair = p.split(':') -# features.append(float(part[1])) -# return score, qid, features - - -# def __reader__(filename, rand_seed=0, is_test=False, test_rate=0.0): -# """ -# create a line reader Generator - -# Parameters -# -------- -# filename : string -# rand_seed : sample instance from dataset, set the sample random seed -# is_test : sample test set or generate train set -# test_rate : sample test set rate - -# Returns -# ------ -# yield -# int int lists -# score query_id, features -# """ -# rand = random.Random(x=rand_seed) -# with open(file_name, 'r') as f: -# for line in f: -# if (rand.random() < test_rate) == is_test: -# yield __parse_line__(line) - - -# def __pair_reader__(filename, shuffle=True): - + def fetch(): return download(URL, "MQ2007", MD5) -- GitLab From 16d6bd7c38d91c2fe8e7b4d5f46902233a5913a8 Mon Sep 17 00:00:00 2001 From: dzhwinter Date: Mon, 8 May 2017 21:43:01 +0800 Subject: [PATCH 0322/3256] "fix label genenerate type. avoid IVector create error when init label" --- python/paddle/v2/dataset/mq2007.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/python/paddle/v2/dataset/mq2007.py b/python/paddle/v2/dataset/mq2007.py index 5705ba60dea..9dd33a22bac 100644 --- a/python/paddle/v2/dataset/mq2007.py +++ b/python/paddle/v2/dataset/mq2007.py @@ -154,9 +154,9 @@ def gen_pair(querylist, partial_order="full"): for i, query_left in enumerate(querylist): for j, query_right in enumerate(querylist): if query_left.relevance_score > query_right.relevance_score: - yield np.ones(1), np.array(query_left.feature_vector), np.array(query_right.feature_vector) + yield 1, np.array(query_left.feature_vector), np.array(query_right.feature_vector) else: - yield np.ones(1), np.array(query_left.feature_vector), np.array(query_right.feature_vector) + yield 1, np.array(query_left.feature_vector), np.array(query_right.feature_vector) elif partial_order == "neighbour": # C(n) @@ -165,9 +165,9 @@ def gen_pair(querylist, partial_order="full"): query_left = querylist[k] query_right = querylist[k+1] if query_left.relevance_score > query_right.relevance_score: - yield np.ones(1), np.array(query_left.feature_vector), np.array(query_right.feature_vector) + yield 1, np.array(query_left.feature_vector), np.array(query_right.feature_vector) else: - yield np.ones(1), np.array(query_left.feature_vector), np.array(query_right.feature_vector) + yield 1, np.array(query_left.feature_vector), np.array(query_right.feature_vector) k += 1 else: raise ValueError("unsupport parameter of partial_order, Only can be neighbour or full") -- GitLab From 82eb0fe45b293a884cd6e8be805a60366be88d6b Mon Sep 17 00:00:00 2001 From: dzhwinter Date: Mon, 8 May 2017 22:26:46 +0800 Subject: [PATCH 0323/3256] "fix len type error of QueryList" --- python/paddle/v2/dataset/mq2007.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/python/paddle/v2/dataset/mq2007.py b/python/paddle/v2/dataset/mq2007.py index 9dd33a22bac..2e8a2f9b089 100644 --- a/python/paddle/v2/dataset/mq2007.py +++ b/python/paddle/v2/dataset/mq2007.py @@ -116,6 +116,9 @@ class QueryList(object): for query in self.querylist: yield query + def __len__(self): + return len(self.querylist) + def _correct_ranking_(self): if self.querylist is None: return @@ -175,7 +178,7 @@ def gen_pair(querylist, partial_order="full"): def gen_list(querylist): """ - gen pair for pair-wise learning to rank algorithm + gen item in list for list-wise learning to rank algorithm Paramters: -------- querylist : querylist, one query match many docment pairs in list, see QueryList @@ -190,7 +193,9 @@ def gen_list(querylist): querylist._correct_ranking_() relevance_score_list = [query.relevance_score for query in querylist] feature_vector_list = [query.feature_vector for query in querylist] - yield np.array(relevance_score_list).T, np.array(feature_vector_list) + # yield np.array(relevance_score_list).T, np.array(feature_vector_list) + for i in range(len(querylist)): + yield relevance_score_list[i], np.array(feature_vector_list[i]) def load_from_text(filepath, shuffle=True, fill_missing=-1): @@ -236,7 +241,9 @@ def __reader__(filepath, format="pairwise", shuffle=True, fill_missing=-1): for pair in gen_pair(querylist): yield pair elif format == "listwise": - yield next(gen_list(querylist)) + # yield next(gen_list(querylist)) + for instance in gen_list(querylist): + yield instance train = functools.partial(__reader__,filepath="MQ2007/MQ2007/Fold1/train.txt") test = functools.partial(__reader__, filepath="MQ2007/MQ2007/Fold1/test.txt") -- GitLab From d86fb1d133b13128822e8ee276ab02bc3a2f8594 Mon Sep 17 00:00:00 2001 From: dzhwinter Date: Fri, 12 May 2017 22:45:12 +0800 Subject: [PATCH 0324/3256] "precommit format with github style" --- python/paddle/v2/dataset/mq2007.py | 287 +++++++++++++++-------------- 1 file changed, 150 insertions(+), 137 deletions(-) diff --git a/python/paddle/v2/dataset/mq2007.py b/python/paddle/v2/dataset/mq2007.py index 2e8a2f9b089..1122ca88bf5 100644 --- a/python/paddle/v2/dataset/mq2007.py +++ b/python/paddle/v2/dataset/mq2007.py @@ -23,7 +23,6 @@ http://research.microsoft.com/en-us/um/beijing/projects/letor/LETOR4.0/Data/MQ20 """ - import os import random import functools @@ -31,25 +30,24 @@ import rarfile from common import download import numpy as np - # URL = "http://research.microsoft.com/en-us/um/beijing/projects/letor/LETOR4.0/Data/MQ2007.rar" URL = "http://www.bigdatalab.ac.cn/benchmark/upload/download_source/7b6dbbe2-842c-11e4-a536-bcaec51b9163_MQ2007.rar" MD5 = "7be1640ae95c6408dab0ae7207bdc706" def __initialize_meta_info__(): - """ + """ download and extract the MQ2007 dataset """ - fn = fetch() - rar = rarfile.RarFile(fn) - dirpath = os.path.dirname(fn) - rar.extractall(path=dirpath) - return dirpath + fn = fetch() + rar = rarfile.RarFile(fn) + dirpath = os.path.dirname(fn) + rar.extractall(path=dirpath) + return dirpath class Query(object): - """ + """ queries used for learning to rank algorithms. It is created from relevance scores, query-document feature vectors Parameters: @@ -63,79 +61,86 @@ class Query(object): description : string comment section in query doc pair data """ - def __init__(self, query_id=-1, relevance_score=-1, - feature_vector=None, description=""): - self.query_id = query_id - self.relevance_score = relevance_score - if feature_vector is None: - self.feature_vector = [] - else: - self.feature_vector = feature_vector - self.description = description - def __str__(self): - string = "%s %s %s" %(str(self.relevance_score), str(self.query_id), " ".join(str(f) for f in self.feature_vector)) - return string + def __init__(self, + query_id=-1, + relevance_score=-1, + feature_vector=None, + description=""): + self.query_id = query_id + self.relevance_score = relevance_score + if feature_vector is None: + self.feature_vector = [] + else: + self.feature_vector = feature_vector + self.description = description - # @classmethod - def _parse_(self, text): - """ + def __str__(self): + string = "%s %s %s" % (str(self.relevance_score), str(self.query_id), + " ".join(str(f) for f in self.feature_vector)) + return string + + # @classmethod + def _parse_(self, text): + """ parse line into Query """ - comment_position = text.find('#') - line = text[:comment_position].strip() - self.description = text[comment_position+1:].strip() - parts = line.split() - assert(len(parts) == 48), "expect 48 space split parts, get %d" %(len(parts)) - # format : 0 qid:10 1:0.000272 2:0.000000 .... - self.relevance_score = int(parts[0]) - self.query_id = int(parts[1].split(':')[1]) - for p in parts[2:]: - pair = p.split(':') - self.feature_vector.append(float(pair[1])) - return self + comment_position = text.find('#') + line = text[:comment_position].strip() + self.description = text[comment_position + 1:].strip() + parts = line.split() + assert (len(parts) == 48), "expect 48 space split parts, get %d" % ( + len(parts)) + # format : 0 qid:10 1:0.000272 2:0.000000 .... + self.relevance_score = int(parts[0]) + self.query_id = int(parts[1].split(':')[1]) + for p in parts[2:]: + pair = p.split(':') + self.feature_vector.append(float(pair[1])) + return self + class QueryList(object): - """ + """ group query into list, every item in list is a Query """ - def __init__(self, querylist=None): - self.query_id = -1 - if querylist is None: - self.querylist = [] - else: - self.querylist = querylist - for query in self.querylist: + + def __init__(self, querylist=None): + self.query_id = -1 + if querylist is None: + self.querylist = [] + else: + self.querylist = querylist + for query in self.querylist: + if self.query_id == -1: + self.query_id = query.query_id + else: + if self.query_id != query.query_id: + raise ValueError("query in list must be same query_id") + + def __iter__(self): + for query in self.querylist: + yield query + + def __len__(self): + return len(self.querylist) + + def _correct_ranking_(self): + if self.querylist is None: + return + self.querylist.sort(key=lambda x: x.relevance_score, reverse=True) + + def _add_query(self, query): if self.query_id == -1: - self.query_id = query.query_id + self.query_id = query.query_id else: - if self.query_id != query.query_id: - raise ValueError("query in list must be same query_id") - - def __iter__(self): - for query in self.querylist: - yield query - - def __len__(self): - return len(self.querylist) - - def _correct_ranking_(self): - if self.querylist is None: - return - self.querylist.sort(key=lambda x:x.relevance_score, reverse=True) - - def _add_query(self, query): - if self.query_id == -1: - self.query_id = query.query_id - else: - if self.query_id != query.query_id: - raise ValueError("query in list must be same query_id") - self.querylist.append(query) - + if self.query_id != query.query_id: + raise ValueError("query in list must be same query_id") + self.querylist.append(query) def gen_pair(querylist, partial_order="full"): - """ + """ gen pair for pair-wise learning to rank algorithm Paramters: -------- @@ -149,35 +154,41 @@ def gen_pair(querylist, partial_order="full"): query_left : np.array, shape=(1, feature_dimension) query_right : same as left """ - if not isinstance(querylist, QueryList): - querylist = QueryList(querylist) - querylist._correct_ranking_() - # C(n,2) - if partial_order == "full": - for i, query_left in enumerate(querylist): - for j, query_right in enumerate(querylist): - if query_left.relevance_score > query_right.relevance_score: - yield 1, np.array(query_left.feature_vector), np.array(query_right.feature_vector) - else: - yield 1, np.array(query_left.feature_vector), np.array(query_right.feature_vector) - - elif partial_order == "neighbour": - # C(n) - k = 0 - while k < len(querylist)-1: - query_left = querylist[k] - query_right = querylist[k+1] - if query_left.relevance_score > query_right.relevance_score: - yield 1, np.array(query_left.feature_vector), np.array(query_right.feature_vector) - else: - yield 1, np.array(query_left.feature_vector), np.array(query_right.feature_vector) - k += 1 - else: - raise ValueError("unsupport parameter of partial_order, Only can be neighbour or full") + if not isinstance(querylist, QueryList): + querylist = QueryList(querylist) + querylist._correct_ranking_() + # C(n,2) + if partial_order == "full": + for i, query_left in enumerate(querylist): + for j, query_right in enumerate(querylist): + if query_left.relevance_score > query_right.relevance_score: + yield 1, np.array(query_left.feature_vector), np.array( + query_right.feature_vector) + else: + yield 1, np.array(query_left.feature_vector), np.array( + query_right.feature_vector) + + elif partial_order == "neighbour": + # C(n) + k = 0 + while k < len(querylist) - 1: + query_left = querylist[k] + query_right = querylist[k + 1] + if query_left.relevance_score > query_right.relevance_score: + yield 1, np.array(query_left.feature_vector), np.array( + query_right.feature_vector) + else: + yield 1, np.array(query_left.feature_vector), np.array( + query_right.feature_vector) + k += 1 + else: + raise ValueError( + "unsupport parameter of partial_order, Only can be neighbour or full" + ) + - def gen_list(querylist): - """ + """ gen item in list for list-wise learning to rank algorithm Paramters: -------- @@ -188,41 +199,39 @@ def gen_list(querylist): label : np.array, shape=(samples_num, ) querylist : np.array, shape=(samples_num, feature_dimension) """ - if not isinstance(querylist, QueryList): - querylist = QueryList(querylist) - querylist._correct_ranking_() - relevance_score_list = [query.relevance_score for query in querylist] - feature_vector_list = [query.feature_vector for query in querylist] - # yield np.array(relevance_score_list).T, np.array(feature_vector_list) - for i in range(len(querylist)): - yield relevance_score_list[i], np.array(feature_vector_list[i]) + if not isinstance(querylist, QueryList): + querylist = QueryList(querylist) + # querylist._correct_ranking_() + relevance_score_list = [query.relevance_score for query in querylist] + feature_vector_list = [query.feature_vector for query in querylist] + yield np.array(relevance_score_list).T, np.array(feature_vector_list) def load_from_text(filepath, shuffle=True, fill_missing=-1): - """ + """ parse data file into querys """ - prev_query_id = -1; - querylists = [] - querylist = None - fn = __initialize_meta_info__() - with open(os.path.join(fn, filepath)) as f: - for line in f: - query = Query() - query = query._parse_(line) - if query.query_id != prev_query_id: - if querylist is not None: - querylists.append(querylist) - querylist = QueryList() - prev_query_id = query.query_id - querylist._add_query(query) - if shuffle == True: - random.shuffle(querylists) - return querylists + prev_query_id = -1 + querylists = [] + querylist = None + fn = __initialize_meta_info__() + with open(os.path.join(fn, filepath)) as f: + for line in f: + query = Query() + query = query._parse_(line) + if query.query_id != prev_query_id: + if querylist is not None: + querylists.append(querylist) + querylist = QueryList() + prev_query_id = query.query_id + querylist._add_query(query) + if shuffle == True: + random.shuffle(querylists) + return querylists def __reader__(filepath, format="pairwise", shuffle=True, fill_missing=-1): - """ + """ Parameters -------- filename : string @@ -235,23 +244,27 @@ def __reader__(filepath, format="pairwise", shuffle=True, fill_missing=-1): label query_left, query_right # format = "pairwise" label querylist # format = "listwise" """ - querylists = load_from_text(filepath, shuffle=shuffle, fill_missing=fill_missing) - for querylist in querylists: - if format == "pairwise": - for pair in gen_pair(querylist): - yield pair - elif format == "listwise": - # yield next(gen_list(querylist)) - for instance in gen_list(querylist): - yield instance - -train = functools.partial(__reader__,filepath="MQ2007/MQ2007/Fold1/train.txt") + querylists = load_from_text( + filepath, shuffle=shuffle, fill_missing=fill_missing) + for querylist in querylists: + if format == "pairwise": + for pair in gen_pair(querylist): + yield pair + elif format == "listwise": + yield next(gen_list(querylist)) + + +train = functools.partial(__reader__, filepath="MQ2007/MQ2007/Fold1/train.txt") test = functools.partial(__reader__, filepath="MQ2007/MQ2007/Fold1/test.txt") def fetch(): - return download(URL, "MQ2007", MD5) + return download(URL, "MQ2007", MD5) -if __name__ == "__main__": - fetch() +if __name__ == "__main__": + fetch() + for i, (score, + samples) in enumerate(train( + format="listwise", shuffle=False)): + np.savetxt("query_%d" % (i), score, fmt="%.2f") -- GitLab From 4ac5caaaa72635b0a948bd8b9826efb98d73a0c9 Mon Sep 17 00:00:00 2001 From: dzhwinter Date: Fri, 12 May 2017 22:58:04 +0800 Subject: [PATCH 0325/3256] "formatter" --- python/paddle/v2/dataset/mq2007.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/paddle/v2/dataset/mq2007.py b/python/paddle/v2/dataset/mq2007.py index 1122ca88bf5..9c6f8927b76 100644 --- a/python/paddle/v2/dataset/mq2007.py +++ b/python/paddle/v2/dataset/mq2007.py @@ -16,9 +16,9 @@ MQ2007 dataset MQ2007 is a query set from Million Query track of TREC 2007. There are about 1700 queries in it with labeled documents. In MQ2007, the 5-fold cross validation strategy is adopted and the 5-fold partitions are included in the package. In each fold, there are three subsets for learning: training set, -validation set and testing set. +validation set and testing set. -MQ2007 dataset from +MQ2007 dataset from website http://research.microsoft.com/en-us/um/beijing/projects/letor/LETOR4.0/Data/MQ2007.rar and parse training set and test set into paddle reader creators """ -- GitLab From a4313de808e7c7d269f5fa0a12511884fe3dedf8 Mon Sep 17 00:00:00 2001 From: dzhwinter Date: Mon, 15 May 2017 17:51:32 +0800 Subject: [PATCH 0326/3256] "remove the pairwise other genereate method" --- python/paddle/v2/dataset/mq2007.py | 139 +++++++++++++++++++++-------- 1 file changed, 103 insertions(+), 36 deletions(-) diff --git a/python/paddle/v2/dataset/mq2007.py b/python/paddle/v2/dataset/mq2007.py index 9c6f8927b76..fd71b341662 100644 --- a/python/paddle/v2/dataset/mq2007.py +++ b/python/paddle/v2/dataset/mq2007.py @@ -89,8 +89,10 @@ class Query(object): line = text[:comment_position].strip() self.description = text[comment_position + 1:].strip() parts = line.split() - assert (len(parts) == 48), "expect 48 space split parts, get %d" % ( - len(parts)) + if len(parts) != 48: + sys.stdout.write("expect 48 space split parts, get %d" % + (len(parts))) + return None # format : 0 qid:10 1:0.000272 2:0.000000 .... self.relevance_score = int(parts[0]) self.query_id = int(parts[1].split(':')[1]) @@ -125,6 +127,9 @@ class QueryList(object): def __len__(self): return len(self.querylist) + def __getitem__(self, i): + return self.querylist[i] + def _correct_ranking_(self): if self.querylist is None: return @@ -139,6 +144,46 @@ class QueryList(object): self.querylist.append(query) +def gen_plain_txt(querylist): + """ + gen plain text in list for other usage + Paramters: + -------- + querylist : querylist, one query match many docment pairs in list, see QueryList + + return : + ------ + query_id : np.array, shape=(samples_num, ) + label : np.array, shape=(samples_num, ) + querylist : np.array, shape=(samples_num, feature_dimension) + """ + if not isinstance(querylist, QueryList): + querylist = QueryList(querylist) + querylist._correct_ranking_() + for query in querylist: + yield querylist.query_id, query.relevance_score, np.array( + query.feature_vector) + + +def gen_point(querylist): + """ + gen item in list for point-wise learning to rank algorithm + Paramters: + -------- + querylist : querylist, one query match many docment pairs in list, see QueryList + + return : + ------ + label : np.array, shape=(samples_num, ) + querylist : np.array, shape=(samples_num, feature_dimension) + """ + if not isinstance(querylist, QueryList): + querylist = QueryList(querylist) + querylist._correct_ranking_() + for query in querylist: + yield query.relevance_score, np.array(query.feature_vector) + + def gen_pair(querylist, partial_order="full"): """ gen pair for pair-wise learning to rank algorithm @@ -146,6 +191,7 @@ def gen_pair(querylist, partial_order="full"): -------- querylist : querylist, one query match many docment pairs in list, see QueryList pairtial_order : "full" or "neighbour" + there is redudant in all possiable pair combinations, which can be simplifed gen pairs for neighbour items or the full partial order pairs return : @@ -157,34 +203,28 @@ def gen_pair(querylist, partial_order="full"): if not isinstance(querylist, QueryList): querylist = QueryList(querylist) querylist._correct_ranking_() + labels = [] + docpairs = [] + # C(n,2) - if partial_order == "full": - for i, query_left in enumerate(querylist): - for j, query_right in enumerate(querylist): - if query_left.relevance_score > query_right.relevance_score: - yield 1, np.array(query_left.feature_vector), np.array( - query_right.feature_vector) - else: - yield 1, np.array(query_left.feature_vector), np.array( - query_right.feature_vector) - - elif partial_order == "neighbour": - # C(n) - k = 0 - while k < len(querylist) - 1: - query_left = querylist[k] - query_right = querylist[k + 1] + for i in range(len(querylist)): + query_left = querylist[i] + for j in range(i + 1, len(querylist)): + query_right = querylist[j] if query_left.relevance_score > query_right.relevance_score: - yield 1, np.array(query_left.feature_vector), np.array( - query_right.feature_vector) - else: - yield 1, np.array(query_left.feature_vector), np.array( - query_right.feature_vector) - k += 1 - else: - raise ValueError( - "unsupport parameter of partial_order, Only can be neighbour or full" - ) + labels.append(1) + docpairs.append([ + np.array(query_left.feature_vector), + np.array(query_right.feature_vector) + ]) + elif query_left.relevance_score < query_right.relevance_score: + labels.append(1) + docpairs.append([ + np.array(query_right.feature_vector), + np.array(query_left.feature_vector) + ]) + for label, pair in zip(labels, docpairs): + yield label, pair[0], pair[1] def gen_list(querylist): @@ -201,12 +241,30 @@ def gen_list(querylist): """ if not isinstance(querylist, QueryList): querylist = QueryList(querylist) - # querylist._correct_ranking_() + querylist._correct_ranking_() relevance_score_list = [query.relevance_score for query in querylist] feature_vector_list = [query.feature_vector for query in querylist] yield np.array(relevance_score_list).T, np.array(feature_vector_list) +def query_filter(querylists): + """ + filter query get only document with label 0. + label 0, 1, 2 means the relevance score document with query + parameters : + querylist : QueyList list + + return : + querylist : QueyList list + """ + filter_query = [] + for querylist in querylists: + relevance_score_list = [query.relevance_score for query in querylist] + if sum(relevance_score_list) != .0: + filter_query.append(querylist) + return filter_query + + def load_from_text(filepath, shuffle=True, fill_missing=-1): """ parse data file into querys @@ -219,12 +277,16 @@ def load_from_text(filepath, shuffle=True, fill_missing=-1): for line in f: query = Query() query = query._parse_(line) + if query == None: + continue if query.query_id != prev_query_id: if querylist is not None: querylists.append(querylist) querylist = QueryList() prev_query_id = query.query_id querylist._add_query(query) + if querylist is not None: + querylists.append(querylist) if shuffle == True: random.shuffle(querylists) return querylists @@ -244,10 +306,15 @@ def __reader__(filepath, format="pairwise", shuffle=True, fill_missing=-1): label query_left, query_right # format = "pairwise" label querylist # format = "listwise" """ - querylists = load_from_text( - filepath, shuffle=shuffle, fill_missing=fill_missing) + querylists = query_filter( + load_from_text( + filepath, shuffle=shuffle, fill_missing=fill_missing)) for querylist in querylists: - if format == "pairwise": + if format == "plain_txt": + yield next(gen_plain_txt(querylist)) + elif format == "pointwise": + yield next(gen_point(querylist)) + elif format == "pairwise": for pair in gen_pair(querylist): yield pair elif format == "listwise": @@ -264,7 +331,7 @@ def fetch(): if __name__ == "__main__": fetch() - for i, (score, - samples) in enumerate(train( - format="listwise", shuffle=False)): - np.savetxt("query_%d" % (i), score, fmt="%.2f") + mytest = functools.partial( + __reader__, filepath="MQ2007/MQ2007/Fold1/sample", format="listwise") + for label, query in mytest(): + print label, query -- GitLab From 8a4bd171c7b41dc9218dcde76af4aae24dbccf4e Mon Sep 17 00:00:00 2001 From: fengjiayi Date: Mon, 15 May 2017 20:06:36 +0800 Subject: [PATCH 0327/3256] add brief explanation of DDim usage --- paddle/majel/README.md | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/paddle/majel/README.md b/paddle/majel/README.md index 55398530567..0f49b83c2ac 100644 --- a/paddle/majel/README.md +++ b/paddle/majel/README.md @@ -48,7 +48,9 @@ int main() } ``` -In Majel, `DDimVar` is derived from `Dim`, `DArrayVar` is from `Array`. +In Majel, `DDimVar` is derived from `Dim`, `DArrayVar` is from +`Array`. + ```c++ template @@ -68,8 +70,8 @@ private: Dim stride_; T* ptr_; }; -``` - +``` + ```c++ typedef boost::variant Place; typedef boost::variant, Dim<2>, Dim<3>, Dim<4>, Dim<5>, @@ -93,6 +95,20 @@ typedef boost::variant< Because `variant` may be thought of as "multi-type, single value", we can utilize it to implement unified interfaces for PaddlePaddle. +`DDim` plays two kinds of roles in Majel. First, it is used to indicate the size of a tensor. For example, we can construct a new `DArray` by following way: + + ```c++ + DArray arr = make_darray(make_ddim({2,3}), 0.0f); + ``` + It means that `arr` will be a two-dimension tensor, or a matrix. The size of its first dimension is 2 and the second is 3. All the element value of `arr` will be initialized as 0.0 . + + The second meaning of `DDim` is tensor index. For example, if we want to access the value in the 1st row and 2nd column of `arr` and set it to 1.0, we can do like this: + + ```c++ + arr[make_ddim({0, 1})] = 1.0; + ``` + + ## implement Tensor in Paddle Before writing code, please make sure you already look through Majel Source Code and grabbed the design philosophy of `DArray` in Majel. @@ -113,8 +129,8 @@ To assign subtasks to our colleagues, we have to discuss how to divide it to ind - [ ] 3. Re-implement `Dim`. - `Dim` is an excellent implementation in Majel. - + `Dim` is an excellent implementation in Majel. + > ??? - [ ] 4. Re-implement `Array/Tensor`. -- GitLab From ad19d0e1477b2188b5d2e617ccbaf107a72d1eff Mon Sep 17 00:00:00 2001 From: fengjiayi Date: Mon, 15 May 2017 20:08:41 +0800 Subject: [PATCH 0328/3256] add brief explanation of DDim usage --- paddle/majel/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/paddle/majel/README.md b/paddle/majel/README.md index 0f49b83c2ac..6482d1402bf 100644 --- a/paddle/majel/README.md +++ b/paddle/majel/README.md @@ -108,7 +108,6 @@ Because `variant` may be thought of as "multi-type, single value", we can utiliz arr[make_ddim({0, 1})] = 1.0; ``` - ## implement Tensor in Paddle Before writing code, please make sure you already look through Majel Source Code and grabbed the design philosophy of `DArray` in Majel. -- GitLab From 397dcf0dbaefabbcfc728188011aee9663bd3da0 Mon Sep 17 00:00:00 2001 From: fengjiayi Date: Mon, 15 May 2017 20:12:02 +0800 Subject: [PATCH 0329/3256] add brief explanation of DDim usage --- paddle/majel/README.md | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/paddle/majel/README.md b/paddle/majel/README.md index 6482d1402bf..2573738b66b 100644 --- a/paddle/majel/README.md +++ b/paddle/majel/README.md @@ -48,9 +48,7 @@ int main() } ``` -In Majel, `DDimVar` is derived from `Dim`, `DArrayVar` is from -`Array`. - +In Majel, `DDimVar` is derived from `Dim`, `DArrayVar` is from `Array`. ```c++ template @@ -70,8 +68,8 @@ private: Dim stride_; T* ptr_; }; -``` - +``` + ```c++ typedef boost::variant Place; typedef boost::variant, Dim<2>, Dim<3>, Dim<4>, Dim<5>, @@ -129,7 +127,7 @@ To assign subtasks to our colleagues, we have to discuss how to divide it to ind - [ ] 3. Re-implement `Dim`. `Dim` is an excellent implementation in Majel. - + > ??? - [ ] 4. Re-implement `Array/Tensor`. -- GitLab From bb75543577fe4be8f3eb14a4516096d7cc668789 Mon Sep 17 00:00:00 2001 From: fengjiayi Date: Mon, 15 May 2017 20:08:41 +0800 Subject: [PATCH 0330/3256] add brief explanation of DDim usage --- paddle/majel/README.md | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/paddle/majel/README.md b/paddle/majel/README.md index 0f49b83c2ac..2573738b66b 100644 --- a/paddle/majel/README.md +++ b/paddle/majel/README.md @@ -48,9 +48,7 @@ int main() } ``` -In Majel, `DDimVar` is derived from `Dim`, `DArrayVar` is from -`Array`. - +In Majel, `DDimVar` is derived from `Dim`, `DArrayVar` is from `Array`. ```c++ template @@ -70,8 +68,8 @@ private: Dim stride_; T* ptr_; }; -``` - +``` + ```c++ typedef boost::variant Place; typedef boost::variant, Dim<2>, Dim<3>, Dim<4>, Dim<5>, @@ -108,7 +106,6 @@ Because `variant` may be thought of as "multi-type, single value", we can utiliz arr[make_ddim({0, 1})] = 1.0; ``` - ## implement Tensor in Paddle Before writing code, please make sure you already look through Majel Source Code and grabbed the design philosophy of `DArray` in Majel. @@ -130,7 +127,7 @@ To assign subtasks to our colleagues, we have to discuss how to divide it to ind - [ ] 3. Re-implement `Dim`. `Dim` is an excellent implementation in Majel. - + > ??? - [ ] 4. Re-implement `Array/Tensor`. -- GitLab From 590c6038fcf5e378c562b57db12891dc2db79ea6 Mon Sep 17 00:00:00 2001 From: dzhwinter Date: Mon, 15 May 2017 21:30:27 +0800 Subject: [PATCH 0331/3256] "format Test" --- python/paddle/v2/dataset/tests/mq2007_test.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/python/paddle/v2/dataset/tests/mq2007_test.py b/python/paddle/v2/dataset/tests/mq2007_test.py index c9bddddeb07..59847b6c18e 100644 --- a/python/paddle/v2/dataset/tests/mq2007_test.py +++ b/python/paddle/v2/dataset/tests/mq2007_test.py @@ -17,15 +17,17 @@ import unittest class TestMQ2007(unittest.TestCase): - def test_pairwise(self): - for label, query_left, query_right in paddle.v2.dataset.mq2007.test(format="pairwise"): - self.assertEqual(query_left.shape(), (46, )) - self.assertEqual(query_right.shape(), (46, )) + def test_pairwise(self): + for label, query_left, query_right in paddle.v2.dataset.mq2007.test( + format="pairwise"): + self.assertEqual(query_left.shape(), (46, )) + self.assertEqual(query_right.shape(), (46, )) - def test_listwise(self): - for label_array, query_array in paddle.v2.dataset.mq2007.test(format="listwise"): - self.assertEqual(len(label_array), len(query_array)) + def test_listwise(self): + for label_array, query_array in paddle.v2.dataset.mq2007.test( + format="listwise"): + self.assertEqual(len(label_array), len(query_array)) if __name__ == "__main__": - unittest.main() + unittest.main() -- GitLab From f5dd9950bb67d46c0fa368c01add64cd25da6be5 Mon Sep 17 00:00:00 2001 From: Yi Wang Date: Mon, 15 May 2017 15:07:13 -0700 Subject: [PATCH 0332/3256] Have to capitalize Boost in find_package --- paddle/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paddle/CMakeLists.txt b/paddle/CMakeLists.txt index c6fd9cc54ae..76995549097 100644 --- a/paddle/CMakeLists.txt +++ b/paddle/CMakeLists.txt @@ -9,7 +9,7 @@ add_subdirectory(pserver) add_subdirectory(trainer) add_subdirectory(scripts) -find_package(boost QUIET) +find_package(Boost QUIET) if(Boost_FOUND) include_directories(${Boost_INCLUDE_DIRS}) -- GitLab From 0fb76c3fd68784088ac75cf5f9e9875396de15b3 Mon Sep 17 00:00:00 2001 From: gongweibao Date: Tue, 16 May 2017 08:24:12 +0800 Subject: [PATCH 0333/3256] fix by yancey --- doc/design/file_manager/README.md | 12 ++++++------ doc/design/file_manager/pfs/pfsclient.md | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/doc/design/file_manager/README.md b/doc/design/file_manager/README.md index 2cfc459d2bc..3df10d801e5 100644 --- a/doc/design/file_manager/README.md +++ b/doc/design/file_manager/README.md @@ -34,16 +34,16 @@ ### PFSServer PFSServer提供RESTful API接口,接收处理PFSClient端的文件管理请求,并且把结果返回PFSClient端。 -``` RESTful API - /api/v1/files - - `GET /api/v1/files`: Get attributes of files or directories. - - `POST /api/v1/files`: Update files or directories. + - `GET /api/v1/files`: Get metadata of files or directories. + - `POST /api/v1/files`: Create files or directories. + - `PATCH /api/v1/files`: Update files or directories. - `DELETE /api/v1/files`: Delete files or directories. - /api/v1/file/chunks - - `GET /api/v1/storage/file/chunks`: Get chunks's attributes of a file. + - `GET /api/v1/storage/file/chunks`: Get chunks's metadata of a file. - /api/v1/storage/files - `GET /api/v1/storage/files`: Download files or directories. @@ -52,11 +52,11 @@ RESTful API - /api/v1/storage/file/chunks - `GET /api/v1/storage/file/chunks`: Download chunks's data. - `POST /api/v1/storage/file/chunks`: Upload chunks's data. -``` + ## 文件传输优化 ### 分块文件传输 -用户文件可能是比较大的,上传到Cloud或者下载到本地的时间可能比较长,而且在传输的过程中也可能出现网络不稳定的情况。为了应对以上的问题,我们提出了Chunk的概念,一个Chunk由所在的文件偏移、数据、数据长度及校验值组成。文件数据内容的上传和下载都是都过Chunk的操作来实现的。由于Chunk比较小(默认256K),完成一个传输动作完成的时间也比较短,不容易出错。PFSClient需要在传输完毕最后一个Chunk的时候检查destination文件的MD5值是否和source文件一致。 +用户文件可能是比较大的,上传到Cloud或者下载到本地的时间可能比较长,而且在传输的过程中也可能出现网络不稳定的情况。为了应对以上的问题,我们提出了Chunk的概念,一个Chunk由所在的文件偏移、数据、数据长度及校验值组成。文件的上传和下载都是通过对Chunk的操作来实现的。由于Chunk比较小(默认256K),完成一个传输动作完成的时间也比较短,不容易出错。PFSClient需要在传输完毕最后一个Chunk的时候检查destination文件的MD5值是否和source文件一致。 一个典型的Chunk如下所示: diff --git a/doc/design/file_manager/pfs/pfsclient.md b/doc/design/file_manager/pfs/pfsclient.md index eb119689bf5..56bc70c54bb 100644 --- a/doc/design/file_manager/pfs/pfsclient.md +++ b/doc/design/file_manager/pfs/pfsclient.md @@ -109,7 +109,7 @@ Synopsis: ls [-r] ... Options: - -r + -R List directory(ies) recursively Examples: -- GitLab From 8b6fad67aef34db7b798bff29efe3115444c356d Mon Sep 17 00:00:00 2001 From: liaogang Date: Tue, 16 May 2017 13:31:22 +0800 Subject: [PATCH 0334/3256] add WITH_TESTING --- paddle/majel/CMakeLists.txt | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/paddle/majel/CMakeLists.txt b/paddle/majel/CMakeLists.txt index baa3bb9e914..34a4ad58f47 100644 --- a/paddle/majel/CMakeLists.txt +++ b/paddle/majel/CMakeLists.txt @@ -1,8 +1,6 @@ cmake_minimum_required(VERSION 3.0) -if(GTEST_INCLUDE_DIR AND GTEST_LIBRARIES) - message("-- Found gtest (include: ${GTEST_INCLUDE_DIR}, library: ${GTEST_LIBRARIES})") -else() +if(${CMAKE_CURRENT_SOURCE_DIR} STREQUAL ${CMAKE_SOURCE_DIR}) # find #include get_filename_component(PARENT_DIR ${CMAKE_CURRENT_SOURCE_DIR} DIRECTORY) include_directories(${PARENT_DIR}) @@ -18,6 +16,8 @@ else() set(THIRD_PARTY_PATH ${CMAKE_CURRENT_SOURCE_DIR}/third_party) set(WITH_TESTING ON) include(external/gtest) +else() + message("-- Found gtest (include: ${GTEST_INCLUDE_DIR}, library: ${GTEST_LIBRARIES})") endif() ########################### Build Majel ############################# @@ -29,6 +29,9 @@ if(CUDA_FOUND) else() add_library(majel ${MAJEL_CXX_FILES}) endif() +add_dependencies(majel ${external_project_dependencies}) ##################################################################### -add_subdirectory(test) +if(WITH_TESTING) + add_subdirectory(test) +endif() \ No newline at end of file -- GitLab From 0e7d42f1941c01fc0c220bab73874666dc757ee8 Mon Sep 17 00:00:00 2001 From: liaogang Date: Tue, 16 May 2017 13:31:57 +0800 Subject: [PATCH 0335/3256] add blank --- paddle/majel/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paddle/majel/CMakeLists.txt b/paddle/majel/CMakeLists.txt index 34a4ad58f47..4b1438d570a 100644 --- a/paddle/majel/CMakeLists.txt +++ b/paddle/majel/CMakeLists.txt @@ -34,4 +34,4 @@ add_dependencies(majel ${external_project_dependencies}) if(WITH_TESTING) add_subdirectory(test) -endif() \ No newline at end of file +endif() -- GitLab From 4201debc720764d1e0b3f22517cd09d66f485f3e Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Tue, 16 May 2017 14:28:44 +0800 Subject: [PATCH 0336/3256] Remove deploy documentation for 0.10.0 --- paddle/scripts/travis/docs.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/paddle/scripts/travis/docs.sh b/paddle/scripts/travis/docs.sh index 67b89adb4dd..c784293695b 100755 --- a/paddle/scripts/travis/docs.sh +++ b/paddle/scripts/travis/docs.sh @@ -60,7 +60,6 @@ function deploy_docs() { deploy_docs "master" "." deploy_docs "develop" "./develop/" -deploy_docs "release/0.10.0" "./release/0.10.0/" # Check is there anything changed. set +e -- GitLab From fdb2de78f3d6c6faac76065e821aff25930ed607 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Tue, 16 May 2017 14:31:11 +0800 Subject: [PATCH 0337/3256] Also remove executable flag for RELEASE.cn.md --- RELEASE.cn.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 RELEASE.cn.md diff --git a/RELEASE.cn.md b/RELEASE.cn.md old mode 100755 new mode 100644 -- GitLab From f5900d38e2a5925eb89573042fefecf1048d90fc Mon Sep 17 00:00:00 2001 From: liaogang Date: Tue, 16 May 2017 14:52:44 +0800 Subject: [PATCH 0338/3256] delete locale --- paddle/scripts/docker/build.sh | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/paddle/scripts/docker/build.sh b/paddle/scripts/docker/build.sh index 0387ab02d47..2dfa712427d 100644 --- a/paddle/scripts/docker/build.sh +++ b/paddle/scripts/docker/build.sh @@ -144,15 +144,11 @@ cat >> /paddle/build/Dockerfile < Date: Tue, 16 May 2017 15:52:33 +0800 Subject: [PATCH 0339/3256] overload several virtual functions to make ChunkEvaluator output multiple metrics --- paddle/gserver/evaluators/ChunkEvaluator.cpp | 56 ++++++++++++++++++-- 1 file changed, 51 insertions(+), 5 deletions(-) diff --git a/paddle/gserver/evaluators/ChunkEvaluator.cpp b/paddle/gserver/evaluators/ChunkEvaluator.cpp index 13f02e51fe9..b94a641b4a2 100644 --- a/paddle/gserver/evaluators/ChunkEvaluator.cpp +++ b/paddle/gserver/evaluators/ChunkEvaluator.cpp @@ -16,6 +16,7 @@ limitations under the License. */ #include #include "paddle/math/Vector.h" +#include "paddle/utils/StringUtil.h" #include "Evaluator.h" @@ -121,11 +122,9 @@ public: } virtual void printStats(std::ostream& os) const { - double precision = (double)numCorrect_ / numOutputSegments_; - double recall = (double)numCorrect_ / numLabelSegments_; - double f1 = - !numCorrect_ ? 0 : 2 * precision * recall / (precision + recall); - os << config_.name() << "=" << f1 << " true_chunks=" << numLabelSegments_ + storeLocalValues(); + os << config_.name() << "=" << values_["F1-score"] + << " true_chunks=" << numLabelSegments_ << " result_chunks=" << numOutputSegments_ << " correct_chunks=" << numCorrect_; } @@ -243,6 +242,53 @@ public: if (tag == tagSingle_) return true; return false; } + +public: + // three metrics: precision, recall and F1-score + void getNames(std::vector* names) { + this->storeLocalValues(); + names->reserve(this->values_.size()); + for (auto it = this->values_.begin(); it != this->values_.end(); ++it) { + names->push_back(this->config_.name() + "." + it->first); + } + } + + // get value by field name + real getValue(const std::string& name, Error* err) const { + this->storeLocalValues(); + std::vector buffers; + paddle::str::split(name, '.', &buffers); + auto it = this->values_.find(buffers[buffers.size() - 1]); + if (it == this->values_.end()) { // not found + *err = Error("No such key %s", name.c_str()); + return 0.0f; + } + + return it->second; + } + + // get type of evaluator + std::string getType(const std::string& name, Error* err) const { + this->getValue(name, err); + if (!err->isOK()) { + return std::string(); + } + return "chunk"; + } + +private: + void storeLocalValues() const { + CHECK_GT(numOutputSegments_, 0); + CHECK_GT(numLabelSegments_, 0); + double precision = (double)numCorrect_ / numOutputSegments_; + double recall = (double)numCorrect_ / numLabelSegments_; + values_["precision"] = precision; + values_["recall"] = recall; + values_["F1-score"] = + !numCorrect_ ? 0 : 2 * precision * recall / (precision + recall); + } + + mutable std::unordered_map values_; }; REGISTER_EVALUATOR(chunk, ChunkEvaluator); -- GitLab From 96b0fb34f44c876925e34863f9976542c89411f5 Mon Sep 17 00:00:00 2001 From: liaogang Date: Tue, 16 May 2017 17:50:45 +0800 Subject: [PATCH 0340/3256] dynamic link boost --- paddle/majel/CMakeLists.txt | 7 +++++++ paddle/majel/test/CMakeLists.txt | 1 - 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/paddle/majel/CMakeLists.txt b/paddle/majel/CMakeLists.txt index 4b1438d570a..d4977df1185 100644 --- a/paddle/majel/CMakeLists.txt +++ b/paddle/majel/CMakeLists.txt @@ -9,6 +9,13 @@ if(${CMAKE_CURRENT_SOURCE_DIR} STREQUAL ${CMAKE_SOURCE_DIR}) get_filename_component(PARENT_DIR ${PARENT_DIR} DIRECTORY) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${PARENT_DIR}/cmake") + # enable boost + find_package(Boost REQUIRED) + if(NOT Boost_FOUND) + message(FATAL "Cannot find Boost library.") + endif() + include_directories(${Boost_INCLUDE_DIRS}) + # enable c++11 set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") diff --git a/paddle/majel/test/CMakeLists.txt b/paddle/majel/test/CMakeLists.txt index 46da6ff89b4..76327fdd70c 100644 --- a/paddle/majel/test/CMakeLists.txt +++ b/paddle/majel/test/CMakeLists.txt @@ -3,7 +3,6 @@ file(GLOB_RECURSE ALL_TEST_FILES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "*.cc") add_executable(majel_tests ${ALL_TEST_FILES}) add_dependencies(majel_tests majel) target_link_libraries(majel_tests - ${Boost_LIBRARIES} ${GTEST_LIBRARIES} ${GTEST_MAIN_LIBRARIES} majel -- GitLab From a74060d48e735a77d8746685a8fac1f640cf5f9b Mon Sep 17 00:00:00 2001 From: yangyaming Date: Tue, 16 May 2017 19:00:13 +0800 Subject: [PATCH 0341/3256] modify usage document of chunk evaluator --- .../trainer_config_helpers/evaluators.py | 47 ++++++++++++------- 1 file changed, 30 insertions(+), 17 deletions(-) diff --git a/python/paddle/trainer_config_helpers/evaluators.py b/python/paddle/trainer_config_helpers/evaluators.py index 567521ee9db..8704a8cde2d 100644 --- a/python/paddle/trainer_config_helpers/evaluators.py +++ b/python/paddle/trainer_config_helpers/evaluators.py @@ -347,32 +347,45 @@ def chunk_evaluator( excluded_chunk_types=None, ): """ Chunk evaluator is used to evaluate segment labelling accuracy for a - sequence. It calculates the chunk detection F1 score. + sequence. It calculates precision, recall and F1 score of the chunk detection. - A chunk is correctly detected if its beginning, end and type are correct. - Other chunk type is ignored. - - For each label in the label sequence, we have: + To use chunk evaluator, the construction of label dict should obey the following rules: + (1) Use one of the listed labelling schemes. These schemes differ in ways indicating chunk boundry. + .. code-block:: python + Scheme Begin Inside End Single + plain 0 - - - + IOB 0 1 - - + IOE - 0 1 - + IOBES 0 1 2 3 .. code-block:: python - tagType = label % numTagType - chunkType = label / numTagType - otherChunkType = numChunkTypes + To make it clear, let's illustrate by a NER example. + Assuming that there are two named entity types including ORG and PER which are called 'chunk type' here, + if 'IOB' scheme were used, the label set will be extended to a set including B-ORG, I-ORG, B-PER, I-PER and O, + in which B-ORG for begining of ORG and I-ORG for end of ORG. + Prefixes which are called 'tag type' here are added to chunk types and there are two tag types including B and I. + Of course, the training data should be labeled accordingly. - The total number of different labels is numTagType*numChunkTypes+1. - We support 4 labelling scheme. - The tag type for each of the scheme is shown as follows: + (2) Map can be done correctly by the listed equations. + .. code-block:: python + tagType = label % numTagType + chunkType = label / numTagType + otherChunkType = numChunkTypes .. code-block:: python - Scheme Begin Inside End Single - plain 0 - - - - IOB 0 1 - - - IOE - 0 1 - - IOBES 0 1 2 3 + Continue the NER example, and the label dict should like this to satify above equations: + + .. code-block:: python + B-ORG 0 + I-ORG 1 + B-PER 2 + I-PER 3 + O 4 + .. code-block:: python - 'plain' means the whole chunk must contain exactly the same chunk label. + Realizing that the number of is chunk type is 2 and number of tag type is 2, it is easy to validate this. The simple usage is: -- GitLab From 41780aef177175c201a5d81a95e33959227fa51c Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Tue, 16 May 2017 14:00:49 -0400 Subject: [PATCH 0342/3256] implment pserver client lib cgo part, and temporary cmake solution --- paddle/go/pserver/client.go | 61 +++++ paddle/go/pserver/lib/CMakeLists.txt | 33 +++ paddle/go/pserver/lib/client/.gitignore | 2 + paddle/go/pserver/lib/client/CMakeLists.txt | 5 + paddle/go/pserver/lib/client/main.go | 212 ++++++++++++++++++ .../go/pserver/lib/client/test/CMakeLists.txt | 8 + paddle/go/pserver/lib/client/test/main.c | 8 + .../lib/cmake/CMakeDetermineGoCompiler.cmake | 44 ++++ .../lib/cmake/CMakeGoCompiler.cmake.in | 8 + .../lib/cmake/CMakeGoInformation.cmake | 7 + .../lib/cmake/CMakeTestGoCompiler.cmake | 1 + paddle/go/pserver/lib/cmake/flags.cmake | 45 ++++ paddle/go/pserver/lib/cmake/golang.cmake | 46 ++++ 13 files changed, 480 insertions(+) create mode 100644 paddle/go/pserver/client.go create mode 100644 paddle/go/pserver/lib/CMakeLists.txt create mode 100644 paddle/go/pserver/lib/client/.gitignore create mode 100644 paddle/go/pserver/lib/client/CMakeLists.txt create mode 100644 paddle/go/pserver/lib/client/main.go create mode 100644 paddle/go/pserver/lib/client/test/CMakeLists.txt create mode 100644 paddle/go/pserver/lib/client/test/main.c create mode 100644 paddle/go/pserver/lib/cmake/CMakeDetermineGoCompiler.cmake create mode 100644 paddle/go/pserver/lib/cmake/CMakeGoCompiler.cmake.in create mode 100644 paddle/go/pserver/lib/cmake/CMakeGoInformation.cmake create mode 100644 paddle/go/pserver/lib/cmake/CMakeTestGoCompiler.cmake create mode 100644 paddle/go/pserver/lib/cmake/flags.cmake create mode 100644 paddle/go/pserver/lib/cmake/golang.cmake diff --git a/paddle/go/pserver/client.go b/paddle/go/pserver/client.go new file mode 100644 index 00000000000..04bf58cd3e2 --- /dev/null +++ b/paddle/go/pserver/client.go @@ -0,0 +1,61 @@ +package pserver + +// ElementType is the type of elements of a Parameter. +type ElementType int + +// Supported element types +const ( + Int32 ElementType = iota + UInt32 + Int64 + UInt64 + Float32 + Float64 +) + +type Parameter struct { + Name string + ElementType ElementType + Content []byte +} + +type ParameterWithConfig struct { + Param Parameter + Config []byte +} + +type Gradient Parameter + +type Client struct { +} + +func NewClient(addr string) *Client { + return &Client{} +} + +func (c *Client) BeginInitParams(pserverConfigProto []byte) (bool, error) { + return true, nil +} + +func (c *Client) InitParam(paramWithConfigs ParameterWithConfig) error { + return nil +} + +func (c *Client) FinishInitParams() error { + return nil +} + +func (c *Client) SendGrads(grads []Gradient) error { + return nil +} + +func (c *Client) GetParams(names []string) ([]Parameter, error) { + return nil, nil +} + +func (c *Client) SaveModel(path string) error { + return nil +} + +func (c *Client) Cleanup() { +} diff --git a/paddle/go/pserver/lib/CMakeLists.txt b/paddle/go/pserver/lib/CMakeLists.txt new file mode 100644 index 00000000000..eb98f4b0b67 --- /dev/null +++ b/paddle/go/pserver/lib/CMakeLists.txt @@ -0,0 +1,33 @@ +cmake_minimum_required(VERSION 3.0) + +if(GTEST_INCLUDE_DIR AND GTEST_LIBRARIES) + message("-- Found gtest (include: ${GTEST_INCLUDE_DIR}, library: ${GTEST_LIBRARIES})") +else() + # find #include + get_filename_component(PARENT_DIR ${CMAKE_CURRENT_SOURCE_DIR} DIRECTORY) + include_directories(${PARENT_DIR}) + + # find cmake directory modules + get_filename_component(PARENT_DIR ${PARENT_DIR} DIRECTORY) + get_filename_component(PARENT_DIR ${PARENT_DIR} DIRECTORY) + get_filename_component(PARENT_DIR ${PARENT_DIR} DIRECTORY) + + set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${PARENT_DIR}/cmake") + + # enable c++11 + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") + + # enable gtest + set(THIRD_PARTY_PATH ./third_party) + set(WITH_TESTING ON) + include(external/gtest) +endif() + +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake") + +project(cxx_go CXX C Go) + +include(cmake/golang.cmake) +include(cmake/flags.cmake) + +add_subdirectory(client) diff --git a/paddle/go/pserver/lib/client/.gitignore b/paddle/go/pserver/lib/client/.gitignore new file mode 100644 index 00000000000..946d2d10ca4 --- /dev/null +++ b/paddle/go/pserver/lib/client/.gitignore @@ -0,0 +1,2 @@ +client.h +client.a diff --git a/paddle/go/pserver/lib/client/CMakeLists.txt b/paddle/go/pserver/lib/client/CMakeLists.txt new file mode 100644 index 00000000000..0f6d47f27c1 --- /dev/null +++ b/paddle/go/pserver/lib/client/CMakeLists.txt @@ -0,0 +1,5 @@ +cmake_minimum_required(VERSION 3.0) + +ExternalGoProject_Add(pserver github.com/PaddlePaddle/Paddle/paddle/go/pserver) +add_go_library(client STATIC pserver) +add_subdirectory(test) diff --git a/paddle/go/pserver/lib/client/main.go b/paddle/go/pserver/lib/client/main.go new file mode 100644 index 00000000000..575f536ba2e --- /dev/null +++ b/paddle/go/pserver/lib/client/main.go @@ -0,0 +1,212 @@ +package main + +/* +#include +#include +typedef enum { + PADDLE_ELEMENT_TYPE_INT32 = 0, + PADDLE_ELEMENT_TYPE_UINT32 = 1, + PADDLE_ELEMENT_TYPE_INT64 = 2, + PADDLE_ELEMENT_TYPE_UINT64 = 3, + PADDLE_ELEMENT_TYPE_FLOAT32 = 4, + PADDLE_ELEMENT_TYPE_FLOAT64 = 5, +} paddle_element_type; + +typedef struct { + char* name; + paddle_element_type element_type; + void* content; + int content_len; +} paddle_parameter, paddle_gradient; + +typedef int client; +*/ +import "C" + +import ( + "log" + "sync" + "unsafe" + + "github.com/PaddlePaddle/Paddle/paddle/go/pserver" +) + +const ( + ptrSize = unsafe.Sizeof(uintptr(0)) +) + +var mu sync.Mutex +var handleMap = make(map[C.client]*pserver.Client) +var curHandle C.client + +func add(c *pserver.Client) C.client { + mu.Lock() + defer mu.Unlock() + client := curHandle + curHandle++ + handleMap[client] = c + return client +} + +func get(client C.client) *pserver.Client { + mu.Lock() + defer mu.Unlock() + return handleMap[client] +} + +func remove(client C.client) *pserver.Client { + mu.Lock() + defer mu.Unlock() + h := handleMap[client] + delete(handleMap, client) + return h +} + +func cArrayToSlice(p unsafe.Pointer, len int) []byte { + // create a Go clice backed by a C array, + // reference: https://github.com/golang/go/wiki/cgo#turning-c-arrays-into-go-slices + return (*[1 << 30]byte)(p)[:len:len] +} + +//export paddle_new_pserver_client +func paddle_new_pserver_client(addr *C.char) C.client { + c := pserver.NewClient(C.GoString(addr)) + return add(c) +} + +//export paddle_pserver_client_release +func paddle_pserver_client_release(client C.client) { + c := remove(client) + c.Cleanup() +} + +//export paddle_begin_init_params +func paddle_begin_init_params(client C.client, pserver_config unsafe.Pointer, config_len C.int) C.int { + c := get(client) + b := cArrayToSlice(pserver_config, int(config_len)) + selected, err := c.BeginInitParams(b) + if err != nil { + log.Println(err) + return -1 + } + + if selected { + return 1 + } + return 0 +} + +//export paddle_init_param +func paddle_init_param(client C.client, param C.paddle_parameter, param_config unsafe.Pointer, config_len C.int) C.int { + et := pserver.ElementType(param.element_type) + name := C.GoString(param.name) + content := cArrayToSlice(unsafe.Pointer(param.content), int(param.content_len)) + pc := pserver.ParameterWithConfig{ + Param: pserver.Parameter{Name: name, ElementType: et, Content: content}, + Config: cArrayToSlice(param_config, int(config_len)), + } + c := get(client) + err := c.InitParam(pc) + if err != nil { + log.Println(err) + return -1 + } + + return 0 +} + +//export paddle_finish_init_params +func paddle_finish_init_params(client C.client) C.int { + c := get(client) + err := c.FinishInitParams() + if err != nil { + log.Println(err) + return -1 + } + + return 0 +} + +//export paddle_send_grads +func paddle_send_grads(client C.client, grads *C.paddle_gradient, total C.int) C.int { + var gs []pserver.Gradient + for i := 0; i < int(total); i++ { + grad := (*C.paddle_gradient)(unsafe.Pointer((uintptr(unsafe.Pointer(grads)) + uintptr(i)*ptrSize))) + et := pserver.ElementType(grad.element_type) + name := C.GoString(grad.name) + content := cArrayToSlice(unsafe.Pointer(grad.content), int(grad.content_len)) + gs = append(gs, pserver.Gradient{Name: name, ElementType: et, Content: content}) + } + + c := get(client) + err := c.SendGrads(gs) + if err != nil { + log.Println(err) + return -1 + } + + return 0 +} + +//export paddle_get_params +func paddle_get_params(client C.client, names **C.char, dst *C.paddle_parameter, total C.int) C.int { + var ns []string + for i := 0; i < int(total); i++ { + name := *(**C.char)(unsafe.Pointer((uintptr(unsafe.Pointer(names)) + uintptr(i)*ptrSize))) + ns = append(ns, C.GoString(name)) + } + c := get(client) + ps, err := c.GetParams(ns) + if err != nil { + log.Println(err) + return -1 + } + + for i := 0; i < int(total); i++ { + if i >= len(ps) { + break + } + + p := ps[i] + name := C.CString(p.Name) + param := (*C.paddle_parameter)(unsafe.Pointer((uintptr(unsafe.Pointer(dst)) + uintptr(i)*ptrSize))) + + if unsafe.Pointer(param.name) != unsafe.Pointer(uintptr(0)) { + C.free(unsafe.Pointer(param.name)) + } + param.name = name + + memReady := false + if param.content != unsafe.Pointer(uintptr(0)) { + if int(param.content_len) == len(p.Content) { + memReady = true + } else { + C.free(param.content) + } + } + + if !memReady { + param.content = C.malloc(C.size_t(len(p.Content))) + } + C.memcpy(param.content, unsafe.Pointer(&p.Content[0]), C.size_t(len(p.Content))) + param.content_len = C.int(len(p.Content)) + param.element_type = C.paddle_element_type(p.ElementType) + } + + return 0 +} + +//export paddle_save_model +func paddle_save_model(client C.client, path *C.char) C.int { + p := C.GoString(path) + c := get(client) + err := c.SaveModel(p) + if err != nil { + log.Println(err) + return -1 + } + + return 0 +} + +func main() {} // Required but ignored diff --git a/paddle/go/pserver/lib/client/test/CMakeLists.txt b/paddle/go/pserver/lib/client/test/CMakeLists.txt new file mode 100644 index 00000000000..d3bb8ee50e5 --- /dev/null +++ b/paddle/go/pserver/lib/client/test/CMakeLists.txt @@ -0,0 +1,8 @@ +cmake_minimum_required(VERSION 3.0) + +include_directories(/env/gopath/src/github.com/PaddlePaddle/Paddle/paddle/go/pserver/lib/build/client) + +add_executable(main main.c) +add_dependencies(main client) +set (CMAKE_EXE_LINKER_FLAGS "-pthread") +target_link_libraries(main /env/gopath/src/github.com/PaddlePaddle/Paddle/paddle/go/pserver/lib/build/client/libclient.a ${GTEST_LIBRARIES}) diff --git a/paddle/go/pserver/lib/client/test/main.c b/paddle/go/pserver/lib/client/test/main.c new file mode 100644 index 00000000000..a36509b0d9f --- /dev/null +++ b/paddle/go/pserver/lib/client/test/main.c @@ -0,0 +1,8 @@ +#include "libclient.h" + +#include "gtest/gtest.h" + +int main() { + client c = paddle_new_pserver_client(NULL); + return 0; +} diff --git a/paddle/go/pserver/lib/cmake/CMakeDetermineGoCompiler.cmake b/paddle/go/pserver/lib/cmake/CMakeDetermineGoCompiler.cmake new file mode 100644 index 00000000000..b3f8fbe271d --- /dev/null +++ b/paddle/go/pserver/lib/cmake/CMakeDetermineGoCompiler.cmake @@ -0,0 +1,44 @@ +if(NOT CMAKE_Go_COMPILER) + if(NOT $ENV{GO_COMPILER} STREQUAL "") + get_filename_component(CMAKE_Go_COMPILER_INIT $ENV{GO_COMPILER} PROGRAM PROGRAM_ARGS CMAKE_Go_FLAGS_ENV_INIT) + + if(CMAKE_Go_FLAGS_ENV_INIT) + set(CMAKE_Go_COMPILER_ARG1 "${CMAKE_Go_FLAGS_ENV_INIT}" CACHE STRING "First argument to Go compiler") + endif() + + if(NOT EXISTS ${CMAKE_Go_COMPILER_INIT}) + message(SEND_ERROR "Could not find compiler set in environment variable GO_COMPILER:\n$ENV{GO_COMPILER}.") + endif() + + endif() + + set(Go_BIN_PATH + $ENV{GOPATH} + $ENV{GOROOT} + $ENV{GOROOT}/../bin + $ENV{GO_COMPILER} + /usr/bin + /usr/local/bin + ) + + if(CMAKE_Go_COMPILER_INIT) + set(CMAKE_Go_COMPILER ${CMAKE_Go_COMPILER_INIT} CACHE PATH "Go Compiler") + else() + find_program(CMAKE_Go_COMPILER + NAMES go + PATHS ${Go_BIN_PATH} + ) + EXEC_PROGRAM(${CMAKE_Go_COMPILER} ARGS version OUTPUT_VARIABLE GOLANG_VERSION) + STRING(REGEX MATCH "go[0-9]+.[0-9]+.[0-9]+[ /A-Za-z0-9]*" VERSION "${GOLANG_VERSION}") + message("-- The Golang compiler identification is ${VERSION}") + message("-- Check for working Golang compiler: ${CMAKE_Go_COMPILER}") + endif() + +endif() + +mark_as_advanced(CMAKE_Go_COMPILER) + +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/cmake/CMakeGoCompiler.cmake.in + ${CMAKE_PLATFORM_INFO_DIR}/CMakeGoCompiler.cmake @ONLY) + +set(CMAKE_Go_COMPILER_ENV_VAR "GO_COMPILER") diff --git a/paddle/go/pserver/lib/cmake/CMakeGoCompiler.cmake.in b/paddle/go/pserver/lib/cmake/CMakeGoCompiler.cmake.in new file mode 100644 index 00000000000..a71f08e0646 --- /dev/null +++ b/paddle/go/pserver/lib/cmake/CMakeGoCompiler.cmake.in @@ -0,0 +1,8 @@ +set(CMAKE_Go_COMPILER "@CMAKE_Go_COMPILER@") +set(CMAKE_Go_COMPILER_LOADED 1) + +set(CMAKE_Go_SOURCE_FILE_EXTENSIONS go) +set(CMAKE_Go_LINKER_PREFERENCE 40) +set(CMAKE_Go_OUTPUT_EXTENSION .o) +set(CMAKE_Go_OUTPUT_EXTENSION_REPLACE 1) +set(CMAKE_Go_COMPILER_ENV_VAR "GO_COMPILER") diff --git a/paddle/go/pserver/lib/cmake/CMakeGoInformation.cmake b/paddle/go/pserver/lib/cmake/CMakeGoInformation.cmake new file mode 100644 index 00000000000..ba51ac93fcd --- /dev/null +++ b/paddle/go/pserver/lib/cmake/CMakeGoInformation.cmake @@ -0,0 +1,7 @@ +if(NOT CMAKE_Go_COMPILE_OBJECT) + set(CMAKE_Go_COMPILE_OBJECT "go tool compile -l -N -o ") +endif() + +if(NOT CMAKE_Go_LINK_EXECUTABLE) + set(CMAKE_Go_LINK_EXECUTABLE "go tool link -o ") +endif() diff --git a/paddle/go/pserver/lib/cmake/CMakeTestGoCompiler.cmake b/paddle/go/pserver/lib/cmake/CMakeTestGoCompiler.cmake new file mode 100644 index 00000000000..b9891b015ba --- /dev/null +++ b/paddle/go/pserver/lib/cmake/CMakeTestGoCompiler.cmake @@ -0,0 +1 @@ +set(CMAKE_Go_COMPILER_WORKS 1 CACHE INTERNAL "") diff --git a/paddle/go/pserver/lib/cmake/flags.cmake b/paddle/go/pserver/lib/cmake/flags.cmake new file mode 100644 index 00000000000..062d5ab660d --- /dev/null +++ b/paddle/go/pserver/lib/cmake/flags.cmake @@ -0,0 +1,45 @@ +# Setting Paddle Compile Flags +include(CheckCXXCompilerFlag) +include(CheckCCompilerFlag) +include(CheckCXXSymbolExists) +include(CheckTypeSize) + +function(CheckCompilerCXX11Flag) + if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + if(${CMAKE_CXX_COMPILER_VERSION} VERSION_LESS 4.8) + message(FATAL_ERROR "Unsupported GCC version. GCC >= 4.8 required.") + endif() + elseif(CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang" OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang") + # cmake >= 3.0 compiler id "AppleClang" on Mac OS X, otherwise "Clang" + # Apple Clang is a different compiler than upstream Clang which havs different version numbers. + # https://gist.github.com/yamaya/2924292 + if(APPLE) # cmake < 3.0 compiler id "Clang" on Mac OS X + if(${CMAKE_CXX_COMPILER_VERSION} VERSION_LESS 5.1) + message(FATAL_ERROR "Unsupported AppleClang version. AppleClang >= 5.1 required.") + endif() + else() + if (${CMAKE_CXX_COMPILER_VERSION} VERSION_LESS 3.3) + message(FATAL_ERROR "Unsupported Clang version. Clang >= 3.3 required.") + endif() + endif() + endif() +endfunction() + +CheckCompilerCXX11Flag() +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") + +# Common gpu architectures: Kepler, Maxwell +foreach(capability 30 35 50) + list(APPEND __arch_flags " -gencode arch=compute_${capability},code=sm_${capability}") +endforeach() + +if (CUDA_VERSION VERSION_GREATER "7.0" OR CUDA_VERSION VERSION_EQUAL "7.0") + list(APPEND __arch_flags " -gencode arch=compute_52,code=sm_52") +endif() + +# Modern gpu architectures: Pascal +if (CUDA_VERSION VERSION_GREATER "8.0" OR CUDA_VERSION VERSION_EQUAL "8.0") + list(APPEND __arch_flags " -gencode arch=compute_60,code=sm_60") +endif() + +set(CUDA_NVCC_FLAGS ${__arch_flags} ${CUDA_NVCC_FLAGS}) \ No newline at end of file diff --git a/paddle/go/pserver/lib/cmake/golang.cmake b/paddle/go/pserver/lib/cmake/golang.cmake new file mode 100644 index 00000000000..5d39868bfdf --- /dev/null +++ b/paddle/go/pserver/lib/cmake/golang.cmake @@ -0,0 +1,46 @@ +set(GOPATH "${CMAKE_CURRENT_BINARY_DIR}/go") +file(MAKE_DIRECTORY ${GOPATH}) + +function(ExternalGoProject_Add TARG) + add_custom_target(${TARG} env GOPATH=${GOPATH} ${CMAKE_Go_COMPILER} get ${ARGN}) +endfunction(ExternalGoProject_Add) + +function(add_go_executable NAME) + file(GLOB GO_SOURCE RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "*.go") + add_custom_command(OUTPUT ${OUTPUT_DIR}/.timestamp + COMMAND env GOPATH=${GOPATH} ${CMAKE_Go_COMPILER} build + -o "${CMAKE_CURRENT_BINARY_DIR}/${NAME}" + ${CMAKE_GO_FLAGS} ${GO_SOURCE} + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}) + + add_custom_target(${NAME} ALL DEPENDS ${OUTPUT_DIR}/.timestamp ${ARGN}) + install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/${NAME} DESTINATION bin) +endfunction(add_go_executable) + + +function(ADD_GO_LIBRARY NAME BUILD_TYPE) + if(BUILD_TYPE STREQUAL "STATIC") + set(BUILD_MODE -buildmode=c-archive) + set(LIB_NAME "lib${NAME}.a") + else() + set(BUILD_MODE -buildmode=c-shared) + if(APPLE) + set(LIB_NAME "lib${NAME}.dylib") + else() + set(LIB_NAME "lib${NAME}.so") + endif() + endif() + + file(GLOB GO_SOURCE RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "*.go") + add_custom_command(OUTPUT ${OUTPUT_DIR}/.timestamp + COMMAND env GOPATH=${GOPATH} ${CMAKE_Go_COMPILER} build ${BUILD_MODE} + -o "${CMAKE_CURRENT_BINARY_DIR}/${LIB_NAME}" + ${CMAKE_GO_FLAGS} ${GO_SOURCE} + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}) + + add_custom_target(${NAME} ALL DEPENDS ${OUTPUT_DIR}/.timestamp ${ARGN}) + + if(NOT BUILD_TYPE STREQUAL "STATIC") + install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/${LIB_NAME} DESTINATION bin) + endif() +endfunction(ADD_GO_LIBRARY) -- GitLab From 6011b2e74575da9b5306da114ccb30b5fb2f89b4 Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Tue, 16 May 2017 14:02:55 -0400 Subject: [PATCH 0343/3256] fix cmake --- paddle/go/pserver/lib/client/test/CMakeLists.txt | 2 +- paddle/go/pserver/lib/client/test/main.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/paddle/go/pserver/lib/client/test/CMakeLists.txt b/paddle/go/pserver/lib/client/test/CMakeLists.txt index d3bb8ee50e5..895f0f66c90 100644 --- a/paddle/go/pserver/lib/client/test/CMakeLists.txt +++ b/paddle/go/pserver/lib/client/test/CMakeLists.txt @@ -5,4 +5,4 @@ include_directories(/env/gopath/src/github.com/PaddlePaddle/Paddle/paddle/go/pse add_executable(main main.c) add_dependencies(main client) set (CMAKE_EXE_LINKER_FLAGS "-pthread") -target_link_libraries(main /env/gopath/src/github.com/PaddlePaddle/Paddle/paddle/go/pserver/lib/build/client/libclient.a ${GTEST_LIBRARIES}) +target_link_libraries(main /env/gopath/src/github.com/PaddlePaddle/Paddle/paddle/go/pserver/lib/build/client/libclient.a) # ${GTEST_LIBRARIES}) diff --git a/paddle/go/pserver/lib/client/test/main.c b/paddle/go/pserver/lib/client/test/main.c index a36509b0d9f..7bf89eddf93 100644 --- a/paddle/go/pserver/lib/client/test/main.c +++ b/paddle/go/pserver/lib/client/test/main.c @@ -1,6 +1,6 @@ #include "libclient.h" -#include "gtest/gtest.h" +//#include "gtest/gtest.h" int main() { client c = paddle_new_pserver_client(NULL); -- GitLab From a5091ef2cec535bcd46bfcffd0c0c0afdfcada2f Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Tue, 16 May 2017 15:42:09 -0400 Subject: [PATCH 0344/3256] fix bug, add functional test --- paddle/go/pserver/lib/client/main.go | 31 ++++++++------ paddle/go/pserver/lib/client/test/main.c | 51 +++++++++++++++++++++++- 2 files changed, 69 insertions(+), 13 deletions(-) diff --git a/paddle/go/pserver/lib/client/main.go b/paddle/go/pserver/lib/client/main.go index 575f536ba2e..9040d24611a 100644 --- a/paddle/go/pserver/lib/client/main.go +++ b/paddle/go/pserver/lib/client/main.go @@ -31,10 +31,7 @@ import ( "github.com/PaddlePaddle/Paddle/paddle/go/pserver" ) -const ( - ptrSize = unsafe.Sizeof(uintptr(0)) -) - +var nullPtr = unsafe.Pointer(uintptr(0)) var mu sync.Mutex var handleMap = make(map[C.client]*pserver.Client) var curHandle C.client @@ -63,6 +60,10 @@ func remove(client C.client) *pserver.Client { } func cArrayToSlice(p unsafe.Pointer, len int) []byte { + if p == nullPtr { + return nil + } + // create a Go clice backed by a C array, // reference: https://github.com/golang/go/wiki/cgo#turning-c-arrays-into-go-slices return (*[1 << 30]byte)(p)[:len:len] @@ -131,7 +132,7 @@ func paddle_finish_init_params(client C.client) C.int { func paddle_send_grads(client C.client, grads *C.paddle_gradient, total C.int) C.int { var gs []pserver.Gradient for i := 0; i < int(total); i++ { - grad := (*C.paddle_gradient)(unsafe.Pointer((uintptr(unsafe.Pointer(grads)) + uintptr(i)*ptrSize))) + grad := (*C.paddle_gradient)(unsafe.Pointer((uintptr(unsafe.Pointer(grads)) + uintptr(i)*unsafe.Sizeof(*grads)))) et := pserver.ElementType(grad.element_type) name := C.GoString(grad.name) content := cArrayToSlice(unsafe.Pointer(grad.content), int(grad.content_len)) @@ -152,7 +153,7 @@ func paddle_send_grads(client C.client, grads *C.paddle_gradient, total C.int) C func paddle_get_params(client C.client, names **C.char, dst *C.paddle_parameter, total C.int) C.int { var ns []string for i := 0; i < int(total); i++ { - name := *(**C.char)(unsafe.Pointer((uintptr(unsafe.Pointer(names)) + uintptr(i)*ptrSize))) + name := *(**C.char)(unsafe.Pointer((uintptr(unsafe.Pointer(names)) + uintptr(i)*unsafe.Sizeof(*names)))) ns = append(ns, C.GoString(name)) } c := get(client) @@ -169,18 +170,24 @@ func paddle_get_params(client C.client, names **C.char, dst *C.paddle_parameter, p := ps[i] name := C.CString(p.Name) - param := (*C.paddle_parameter)(unsafe.Pointer((uintptr(unsafe.Pointer(dst)) + uintptr(i)*ptrSize))) + param := (*C.paddle_parameter)(unsafe.Pointer((uintptr(unsafe.Pointer(dst)) + uintptr(i)*unsafe.Sizeof(*dst)))) - if unsafe.Pointer(param.name) != unsafe.Pointer(uintptr(0)) { - C.free(unsafe.Pointer(param.name)) + if unsafe.Pointer(param.name) != nullPtr { + if n := C.GoString(param.name); n != p.Name { + log.Println("warning: pre-allocated parameter name not match parameter name, pre-allocated parameter name will be freed.", n, p.Name) + C.free(unsafe.Pointer(param.name)) + param.name = name + } + } else { + param.name = name } - param.name = name memReady := false - if param.content != unsafe.Pointer(uintptr(0)) { - if int(param.content_len) == len(p.Content) { + if param.content != nullPtr { + if int(param.content_len) < len(p.Content) { memReady = true } else { + log.Println("warning: pre-allocated content len is smaller than parameter content len, pre-allocated content will be freed.", param.content_len, len(p.Content)) C.free(param.content) } } diff --git a/paddle/go/pserver/lib/client/test/main.c b/paddle/go/pserver/lib/client/test/main.c index 7bf89eddf93..f27e57508c2 100644 --- a/paddle/go/pserver/lib/client/test/main.c +++ b/paddle/go/pserver/lib/client/test/main.c @@ -2,7 +2,56 @@ //#include "gtest/gtest.h" +void panic() { + // TODO(helin): fix: gtest using cmake is not working, using this + // hacky way for now. + *(void*)0; +} + int main() { - client c = paddle_new_pserver_client(NULL); + char addr[] = "localhost:3000"; + client c = paddle_new_pserver_client(addr); + retry: + if (paddle_begin_init_params(c, NULL, 0)) { + paddle_parameter param; + char name_a[] = "param_a"; + char name_b[] = "param_b"; + char content[] = {0x00, 0x11, 0x22}; + param.element_type = PADDLE_ELEMENT_TYPE_FLOAT32; + param.name = name_a; + param.content = content; + param.content_len = 3; + if (paddle_init_param(c, param, NULL, 0) != 0) { + goto retry; + } + param.element_type = PADDLE_ELEMENT_TYPE_INT32; + param.name = name_b; + param.content = content; + param.content_len = 3; + if (paddle_init_param(c, param, NULL, 0) != 0) { + goto retry; + } + if (paddle_finish_init_params(c) != 0) { + goto retry; + } + } else { + panic(); + } + + char content[] = {0x00, 0x11, 0x22}; + paddle_gradient params[] = {{"param_a", PADDLE_ELEMENT_TYPE_INT32, content, 3}, {"param_b", PADDLE_ELEMENT_TYPE_FLOAT32, content, 3}}; + if (!paddle_send_grads(c, params, 2)) { + panic(); + } + + char* names[]={"param_a", "param_b"}; + if (!paddle_get_params(c, names, params, 2)) { + panic(); + } + + if (!paddle_save_model(c, "/tmp/")) { + panic(); + } + return 0; } -- GitLab From e21d56ee3fea86fc9728b9419ee031a5a89faa8c Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Tue, 16 May 2017 16:18:49 -0400 Subject: [PATCH 0345/3256] add reuse variable --- paddle/go/pserver/lib/client/main.go | 6 +++--- paddle/go/pserver/lib/client/test/main.c | 8 +++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/paddle/go/pserver/lib/client/main.go b/paddle/go/pserver/lib/client/main.go index 9040d24611a..ec61398ef45 100644 --- a/paddle/go/pserver/lib/client/main.go +++ b/paddle/go/pserver/lib/client/main.go @@ -150,7 +150,7 @@ func paddle_send_grads(client C.client, grads *C.paddle_gradient, total C.int) C } //export paddle_get_params -func paddle_get_params(client C.client, names **C.char, dst *C.paddle_parameter, total C.int) C.int { +func paddle_get_params(client C.client, names **C.char, dst *C.paddle_parameter, reuse C.int, total C.int) C.int { var ns []string for i := 0; i < int(total); i++ { name := *(**C.char)(unsafe.Pointer((uintptr(unsafe.Pointer(names)) + uintptr(i)*unsafe.Sizeof(*names)))) @@ -172,7 +172,7 @@ func paddle_get_params(client C.client, names **C.char, dst *C.paddle_parameter, name := C.CString(p.Name) param := (*C.paddle_parameter)(unsafe.Pointer((uintptr(unsafe.Pointer(dst)) + uintptr(i)*unsafe.Sizeof(*dst)))) - if unsafe.Pointer(param.name) != nullPtr { + if reuse != 0 && unsafe.Pointer(param.name) != nullPtr { if n := C.GoString(param.name); n != p.Name { log.Println("warning: pre-allocated parameter name not match parameter name, pre-allocated parameter name will be freed.", n, p.Name) C.free(unsafe.Pointer(param.name)) @@ -183,7 +183,7 @@ func paddle_get_params(client C.client, names **C.char, dst *C.paddle_parameter, } memReady := false - if param.content != nullPtr { + if reuse != 0 && param.content != nullPtr { if int(param.content_len) < len(p.Content) { memReady = true } else { diff --git a/paddle/go/pserver/lib/client/test/main.c b/paddle/go/pserver/lib/client/test/main.c index f27e57508c2..d9d9e997017 100644 --- a/paddle/go/pserver/lib/client/test/main.c +++ b/paddle/go/pserver/lib/client/test/main.c @@ -39,13 +39,15 @@ int main() { } char content[] = {0x00, 0x11, 0x22}; - paddle_gradient params[] = {{"param_a", PADDLE_ELEMENT_TYPE_INT32, content, 3}, {"param_b", PADDLE_ELEMENT_TYPE_FLOAT32, content, 3}}; - if (!paddle_send_grads(c, params, 2)) { + paddle_gradient grads[2] = {{"param_a", PADDLE_ELEMENT_TYPE_INT32, content, 3}, {"param_b", PADDLE_ELEMENT_TYPE_FLOAT32, content, 3}}; + + if (!paddle_send_grads(c, grads, 2)) { panic(); } + paddle_parameter params[2]; char* names[]={"param_a", "param_b"}; - if (!paddle_get_params(c, names, params, 2)) { + if (!paddle_get_params(c, names, params, 0, 2)) { panic(); } -- GitLab From d08c8ea7a15400f6a0a6318f2f8e9ff771c87ce4 Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Tue, 16 May 2017 16:30:50 -0400 Subject: [PATCH 0346/3256] change interface for clearity --- paddle/go/pserver/lib/client/main.go | 46 +++++++++++++----------- paddle/go/pserver/lib/client/test/main.c | 4 +-- 2 files changed, 28 insertions(+), 22 deletions(-) diff --git a/paddle/go/pserver/lib/client/main.go b/paddle/go/pserver/lib/client/main.go index ec61398ef45..92aa54bd109 100644 --- a/paddle/go/pserver/lib/client/main.go +++ b/paddle/go/pserver/lib/client/main.go @@ -150,7 +150,7 @@ func paddle_send_grads(client C.client, grads *C.paddle_gradient, total C.int) C } //export paddle_get_params -func paddle_get_params(client C.client, names **C.char, dst *C.paddle_parameter, reuse C.int, total C.int) C.int { +func paddle_get_params(client C.client, names **C.char, dst **C.paddle_parameter, total C.int) C.int { var ns []string for i := 0; i < int(total); i++ { name := *(**C.char)(unsafe.Pointer((uintptr(unsafe.Pointer(names)) + uintptr(i)*unsafe.Sizeof(*names)))) @@ -169,30 +169,36 @@ func paddle_get_params(client C.client, names **C.char, dst *C.paddle_parameter, } p := ps[i] - name := C.CString(p.Name) - param := (*C.paddle_parameter)(unsafe.Pointer((uintptr(unsafe.Pointer(dst)) + uintptr(i)*unsafe.Sizeof(*dst)))) - - if reuse != 0 && unsafe.Pointer(param.name) != nullPtr { - if n := C.GoString(param.name); n != p.Name { - log.Println("warning: pre-allocated parameter name not match parameter name, pre-allocated parameter name will be freed.", n, p.Name) - C.free(unsafe.Pointer(param.name)) - param.name = name - } + param := *(**C.paddle_parameter)(unsafe.Pointer((uintptr(unsafe.Pointer(dst)) + uintptr(i)*unsafe.Sizeof(*dst)))) + nameReady := false + contentAllocated := false + + if unsafe.Pointer(param) == nullPtr { + param = (*C.paddle_parameter)(C.calloc(1, C.size_t(unsafe.Sizeof(*param)))) } else { - param.name = name - } + if unsafe.Pointer(param.name) != nullPtr { + if n := C.GoString(param.name); n != p.Name { + log.Println("warning: pre-allocated parameter name not match parameter name, pre-allocated parameter name will be freed.", n, p.Name) + C.free(unsafe.Pointer(param.name)) + } else { + nameReady = true + } + } - memReady := false - if reuse != 0 && param.content != nullPtr { - if int(param.content_len) < len(p.Content) { - memReady = true - } else { - log.Println("warning: pre-allocated content len is smaller than parameter content len, pre-allocated content will be freed.", param.content_len, len(p.Content)) - C.free(param.content) + if param.content != nullPtr { + if int(param.content_len) == len(p.Content) { + contentAllocated = true + } else { + log.Println("warning: pre-allocated content len does not match parameter content len, pre-allocated content will be freed.", param.content_len, len(p.Content)) + C.free(param.content) + } } } - if !memReady { + if !nameReady { + param.name = C.CString(p.Name) + } + if !contentAllocated { param.content = C.malloc(C.size_t(len(p.Content))) } C.memcpy(param.content, unsafe.Pointer(&p.Content[0]), C.size_t(len(p.Content))) diff --git a/paddle/go/pserver/lib/client/test/main.c b/paddle/go/pserver/lib/client/test/main.c index d9d9e997017..6fec3a7a83a 100644 --- a/paddle/go/pserver/lib/client/test/main.c +++ b/paddle/go/pserver/lib/client/test/main.c @@ -45,9 +45,9 @@ int main() { panic(); } - paddle_parameter params[2]; + paddle_parameter* params[2] = {NULL, NULL}; char* names[]={"param_a", "param_b"}; - if (!paddle_get_params(c, names, params, 0, 2)) { + if (!paddle_get_params(c, names, params, 2)) { panic(); } -- GitLab From 1c908df681943623fdded59d52a54b376fb016ec Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Tue, 16 May 2017 16:36:33 -0400 Subject: [PATCH 0347/3256] fix test memory leak --- paddle/go/pserver/lib/client/test/main.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/paddle/go/pserver/lib/client/test/main.c b/paddle/go/pserver/lib/client/test/main.c index 6fec3a7a83a..14d4522eac0 100644 --- a/paddle/go/pserver/lib/client/test/main.c +++ b/paddle/go/pserver/lib/client/test/main.c @@ -8,6 +8,20 @@ void panic() { *(void*)0; } +void releaseParam(paddle_parameter* param) { + if (param != NULL) { + if (param->name != NULL) { + free(param->name); + } + + if (param->content != NULL) { + free(param->content); + } + + free(param); + } +} + int main() { char addr[] = "localhost:3000"; client c = paddle_new_pserver_client(addr); @@ -51,6 +65,9 @@ int main() { panic(); } + releaseParam(params[0]); + releaseParam(params[1]); + if (!paddle_save_model(c, "/tmp/")) { panic(); } -- GitLab From 61a49e49f0e8a12ad6ccfe6c4b102a3b86732991 Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Tue, 16 May 2017 16:54:45 -0400 Subject: [PATCH 0348/3256] add paddle_release_param --- paddle/go/pserver/lib/client/main.go | 24 +++++++++++++++++++----- paddle/go/pserver/lib/client/test/main.c | 18 ++---------------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/paddle/go/pserver/lib/client/main.go b/paddle/go/pserver/lib/client/main.go index 92aa54bd109..8aa757edcdc 100644 --- a/paddle/go/pserver/lib/client/main.go +++ b/paddle/go/pserver/lib/client/main.go @@ -15,10 +15,24 @@ typedef enum { typedef struct { char* name; paddle_element_type element_type; - void* content; + char* content; int content_len; } paddle_parameter, paddle_gradient; +static inline void paddle_release_param(paddle_parameter* param) { + if (param != NULL) { + if (param->name != NULL) { + free(param->name); + } + + if (param->content != NULL) { + free(param->content); + } + + free(param); + } +} + typedef int client; */ import "C" @@ -185,12 +199,12 @@ func paddle_get_params(client C.client, names **C.char, dst **C.paddle_parameter } } - if param.content != nullPtr { + if unsafe.Pointer(param.content) != nullPtr { if int(param.content_len) == len(p.Content) { contentAllocated = true } else { log.Println("warning: pre-allocated content len does not match parameter content len, pre-allocated content will be freed.", param.content_len, len(p.Content)) - C.free(param.content) + C.free(unsafe.Pointer(param.content)) } } } @@ -199,9 +213,9 @@ func paddle_get_params(client C.client, names **C.char, dst **C.paddle_parameter param.name = C.CString(p.Name) } if !contentAllocated { - param.content = C.malloc(C.size_t(len(p.Content))) + param.content = (*C.char)(C.malloc(C.size_t(len(p.Content)))) } - C.memcpy(param.content, unsafe.Pointer(&p.Content[0]), C.size_t(len(p.Content))) + C.memcpy(unsafe.Pointer(param.content), unsafe.Pointer(&p.Content[0]), C.size_t(len(p.Content))) param.content_len = C.int(len(p.Content)) param.element_type = C.paddle_element_type(p.ElementType) } diff --git a/paddle/go/pserver/lib/client/test/main.c b/paddle/go/pserver/lib/client/test/main.c index 14d4522eac0..619661d4ac3 100644 --- a/paddle/go/pserver/lib/client/test/main.c +++ b/paddle/go/pserver/lib/client/test/main.c @@ -8,20 +8,6 @@ void panic() { *(void*)0; } -void releaseParam(paddle_parameter* param) { - if (param != NULL) { - if (param->name != NULL) { - free(param->name); - } - - if (param->content != NULL) { - free(param->content); - } - - free(param); - } -} - int main() { char addr[] = "localhost:3000"; client c = paddle_new_pserver_client(addr); @@ -65,8 +51,8 @@ int main() { panic(); } - releaseParam(params[0]); - releaseParam(params[1]); + paddle_release_param(params[0]); + paddle_release_param(params[1]); if (!paddle_save_model(c, "/tmp/")) { panic(); -- GitLab From 4db7c546f4dcdec614ef83de6686272041ef603d Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Tue, 16 May 2017 16:55:02 -0400 Subject: [PATCH 0349/3256] add documentation for client --- paddle/go/pserver/client.go | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/paddle/go/pserver/client.go b/paddle/go/pserver/client.go index 04bf58cd3e2..5b110af648d 100644 --- a/paddle/go/pserver/client.go +++ b/paddle/go/pserver/client.go @@ -13,49 +13,71 @@ const ( Float64 ) +// Parameter is a piece of data to sync with the parameter server. type Parameter struct { Name string ElementType ElementType Content []byte } +// ParameterWithConfig contains the parameter and the configuration. type ParameterWithConfig struct { Param Parameter - Config []byte + Config []byte // parameter configuration in Proto Buffer format } +// Gradient is the gradient of the parameter. type Gradient Parameter +// Client is the client to parameter servers. type Client struct { } +// NewClient creates a new client. func NewClient(addr string) *Client { return &Client{} } -func (c *Client) BeginInitParams(pserverConfigProto []byte) (bool, error) { +// BeginInitParams begins to initialize parameters on parameter +// servers. +// +// BeginInitParams will be called from multiple trainers, only one +// trainer will be selected to initialize the parameters on parameter +// servers. Other trainers will be blocked until the initialization is +// done, and they need to get the initialized parameters from +// parameter servers using GetParams. +func (c *Client) BeginInitParams(pserverConfigProto []byte) (selected bool, err error) { return true, nil } +// InitParam initializes the parameter on parameter servers. func (c *Client) InitParam(paramWithConfigs ParameterWithConfig) error { return nil } +// FinishInitParams tells parameter servers client has sent all +// parameters to parameter servers as initialization. func (c *Client) FinishInitParams() error { return nil } +// SendGrads sends gradients to parameter servers for updating +// parameters. func (c *Client) SendGrads(grads []Gradient) error { return nil } +// GetParams gets parameters from parameter servers. func (c *Client) GetParams(names []string) ([]Parameter, error) { return nil, nil } +// SaveModel indicates parameters to save the parameter to the given +// path. func (c *Client) SaveModel(path string) error { return nil } +// Cleanup cleans up the client states. func (c *Client) Cleanup() { } -- GitLab From c17cef98099a11a6a07da77682428129aaf5ef5b Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Tue, 16 May 2017 17:07:42 -0400 Subject: [PATCH 0350/3256] better warning message --- paddle/go/pserver/lib/client/main.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/paddle/go/pserver/lib/client/main.go b/paddle/go/pserver/lib/client/main.go index 8aa757edcdc..2bb76dcb7d6 100644 --- a/paddle/go/pserver/lib/client/main.go +++ b/paddle/go/pserver/lib/client/main.go @@ -192,7 +192,7 @@ func paddle_get_params(client C.client, names **C.char, dst **C.paddle_parameter } else { if unsafe.Pointer(param.name) != nullPtr { if n := C.GoString(param.name); n != p.Name { - log.Println("warning: pre-allocated parameter name not match parameter name, pre-allocated parameter name will be freed.", n, p.Name) + log.Println("Warning: the pre-allocated parameter name does not match the parameter name, it will be freed.", n, p.Name) C.free(unsafe.Pointer(param.name)) } else { nameReady = true @@ -203,7 +203,7 @@ func paddle_get_params(client C.client, names **C.char, dst **C.paddle_parameter if int(param.content_len) == len(p.Content) { contentAllocated = true } else { - log.Println("warning: pre-allocated content len does not match parameter content len, pre-allocated content will be freed.", param.content_len, len(p.Content)) + log.Println("Warning: the pre-allocated content len does not match parameter content len, the pre-allocated content will be freed.", param.content_len, len(p.Content)) C.free(unsafe.Pointer(param.content)) } } -- GitLab From 4759b373aa15881f52b78298ebcc62a3d9f67915 Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Tue, 16 May 2017 17:23:45 -0400 Subject: [PATCH 0351/3256] add reuse buffer in functional test --- paddle/go/pserver/lib/client/test/main.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/paddle/go/pserver/lib/client/test/main.c b/paddle/go/pserver/lib/client/test/main.c index 619661d4ac3..d71a023a6ad 100644 --- a/paddle/go/pserver/lib/client/test/main.c +++ b/paddle/go/pserver/lib/client/test/main.c @@ -51,6 +51,11 @@ int main() { panic(); } + // get parameters again by reusing the allocated parameter buffers. + if (!paddle_get_params(c, names, params, 2)) { + panic(); + } + paddle_release_param(params[0]); paddle_release_param(params[1]); -- GitLab From 52a1e0197bc88916a19c25d7c27ee007883d6b57 Mon Sep 17 00:00:00 2001 From: liaogang Date: Wed, 17 May 2017 13:10:38 +0800 Subject: [PATCH 0352/3256] Define cc_xxx to simplify cmake --- CMakeLists.txt | 1 + cmake/generic.cmake | 77 ++++++++++++++++++++++++++++++++ paddle/majel/CMakeLists.txt | 41 +---------------- paddle/majel/test/CMakeLists.txt | 13 ++---- 4 files changed, 82 insertions(+), 50 deletions(-) create mode 100644 cmake/generic.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index fc85f83b94f..884afa962bb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -92,6 +92,7 @@ include(external/swig) # download, build, install swig include(external/warpctc) # download, build, install warpctc include(external/any) # download libn::any +include(generic) # simplify cmake module include(package) # set paddle packages include(cpplint) # set paddle c++ style include(ccache) # set ccache for compilation diff --git a/cmake/generic.cmake b/cmake/generic.cmake new file mode 100644 index 00000000000..076a9514adc --- /dev/null +++ b/cmake/generic.cmake @@ -0,0 +1,77 @@ +# 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. + + +# To simplify the build process of PaddlePaddle, we defined couple of +# fundamental abstractions, e.g., how to build library, binary and +# test in C++, CUDA and Go. +# +# ------------------------------------------- +# C++ CUDA C++ Go +# ------------------------------------------- +# cc_library nv_library go_library +# cc_binary nv_binary go_binary +# cc_test nv_test go_test +# ------------------------------------------- + +# cc_binary parses tensor.cc and figures out that target also depend on tensor.h. +# cc_binary(tensor +# SRCS +# tensor.cc) +function(cc_binary TARGET_NAME) + set(options OPTIONAL) + set(oneValueArgs "") + set(multiValueArgs SRCS DEPS) + cmake_parse_arguments(cc_binary "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + add_executable(${TARGET_NAME} ${cc_binary_SRCS}) + add_dependencies(${TARGET_NAME} ${cc_binary_DEPS} ${external_project_dependencies}) + target_link_libraries(${TARGET_NAME} ${cc_binary_DEPS}) +endfunction(cc_binary) + +# cc_library parses tensor.cc and figures out that target also depend on tensor.h. +# cc_library(tensor +# SRCS +# tensor.cc +# DEPS +# variant) +function(cc_library TARGET_NAME) + set(options OPTIONAL) + set(oneValueArgs "") + set(multiValueArgs SRCS DEPS) + cmake_parse_arguments(cc_library "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + if (${cc_library_OPTIONAL} STREQUAL "STATIC") + add_library(${TARGET_NAME} STATIC ${cc_library_SRCS}) + else() + add_library(${TARGET_NAME} SHARED ${cc_library_SRCS}) + endif() + add_dependencies(${TARGET_NAME} ${cc_library_DEPS} ${external_project_dependencies}) +endfunction(cc_library) + +# The dependency to target tensor implies that if any of +# tensor{.h,.cc,_test.cc} is changed, tensor_test need to be re-built. +# cc_test(tensor_test +# SRCS +# tensor_test.cc +# DEPS +# tensor) +function(cc_test TARGET_NAME) + set(options "") + set(oneValueArgs "") + set(multiValueArgs SRCS DEPS) + cmake_parse_arguments(cc_test "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + add_executable(${TARGET_NAME} ${cc_test_SRCS}) + add_dependencies(${TARGET_NAME} ${cc_test_DEPS} ${external_project_dependencies}) + target_link_libraries(${TARGET_NAME} ${cc_test_DEPS} ${GTEST_MAIN_LIBRARIES} ${GTEST_LIBRARIES}) + add_test(${TARGET_NAME} ${TARGET_NAME}) +endfunction(cc_test) diff --git a/paddle/majel/CMakeLists.txt b/paddle/majel/CMakeLists.txt index d4977df1185..d4bce38906e 100644 --- a/paddle/majel/CMakeLists.txt +++ b/paddle/majel/CMakeLists.txt @@ -1,43 +1,4 @@ -cmake_minimum_required(VERSION 3.0) - -if(${CMAKE_CURRENT_SOURCE_DIR} STREQUAL ${CMAKE_SOURCE_DIR}) - # find #include - get_filename_component(PARENT_DIR ${CMAKE_CURRENT_SOURCE_DIR} DIRECTORY) - include_directories(${PARENT_DIR}) - - # find cmake directory modules - get_filename_component(PARENT_DIR ${PARENT_DIR} DIRECTORY) - set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${PARENT_DIR}/cmake") - - # enable boost - find_package(Boost REQUIRED) - if(NOT Boost_FOUND) - message(FATAL "Cannot find Boost library.") - endif() - include_directories(${Boost_INCLUDE_DIRS}) - - # enable c++11 - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") - - # enable gtest - set(THIRD_PARTY_PATH ${CMAKE_CURRENT_SOURCE_DIR}/third_party) - set(WITH_TESTING ON) - include(external/gtest) -else() - message("-- Found gtest (include: ${GTEST_INCLUDE_DIR}, library: ${GTEST_LIBRARIES})") -endif() - -########################### Build Majel ############################# -set(MAJEL_CXX_FILES place.cc) -set(MAJEL_CUDA_FILES "") - -if(CUDA_FOUND) - cuda_add_library(majel ${MAJEL_CUDA_FILES} ${MAJEL_CXX_FILES}) -else() - add_library(majel ${MAJEL_CXX_FILES}) -endif() -add_dependencies(majel ${external_project_dependencies}) -##################################################################### +cc_library(majel SRCS place.cc) if(WITH_TESTING) add_subdirectory(test) diff --git a/paddle/majel/test/CMakeLists.txt b/paddle/majel/test/CMakeLists.txt index 76327fdd70c..6379a4d6e71 100644 --- a/paddle/majel/test/CMakeLists.txt +++ b/paddle/majel/test/CMakeLists.txt @@ -1,10 +1,3 @@ -file(GLOB_RECURSE ALL_TEST_FILES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "*.cc") - -add_executable(majel_tests ${ALL_TEST_FILES}) -add_dependencies(majel_tests majel) -target_link_libraries(majel_tests - ${GTEST_LIBRARIES} - ${GTEST_MAIN_LIBRARIES} - majel - ) -add_test(majel_tests majel_tests) +cc_test(place_test + SRCS place_test.cc + DEPS majel) -- GitLab From 418917e813fd5d617e5e8cffca683551e6b5b9e0 Mon Sep 17 00:00:00 2001 From: qijun Date: Wed, 17 May 2017 13:32:01 +0800 Subject: [PATCH 0353/3256] add details about implementing tensor in paddle --- paddle/majel/README.md | 82 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 67 insertions(+), 15 deletions(-) diff --git a/paddle/majel/README.md b/paddle/majel/README.md index 55398530567..ed9fa7f02a5 100644 --- a/paddle/majel/README.md +++ b/paddle/majel/README.md @@ -93,34 +93,86 @@ typedef boost::variant< Because `variant` may be thought of as "multi-type, single value", we can utilize it to implement unified interfaces for PaddlePaddle. -## implement Tensor in Paddle +## Implement Tensor in Paddle + +We want to create a Tensor class to replace Vector and Matrix, and to support high-dimensional data. The operations on Tensor are implemented in both CPU and GPU. We also want to make sure that the Tensor interface is friendly to its callers. + +Tensor is only responsible for describing computing. It will not take charge of memory allocation policy, handles of some CUDA library context(e.g. cublasHandle, cudnnHandle), and dispatching CUDA kernels. Paddle has realize the initialization and resources management of hardware. Before writing code, please make sure you already look through Majel Source Code and grabbed the design philosophy of `DArray` in Majel. -To assign subtasks to our colleagues, we have to discuss how to divide it to independent subtasks. -- [ ] 1. First, we need to consider the third-party dependencies in Majel. +### Memory Management +`Allocation` manages a block of memory in device(CPU/GPU). We use `Place` to decribe memory location. The details of memory allocation and deallocation are implememted in `Allocator` and `DeAllocator`. Related low-level API such as `hl_malloc_device()` and `hl_malloc_host()` are provided by Paddle. + +### Dim and Array +#### Dim + +`Dim` decribes the dimension information of an array. + +`DDimVar` is an alias of a specializd class of boost.variant class template. + +`DDim` is introduced to represent a dynamically sized dimension. + +For example: + +``` +Dim<2> d1 = make_dim(3, 3); +DDim d2 = make_ddim({1, 2, 3}); +``` + +You must appoint a concrete sized dimension to Dim, whereas DDim can represent a dynamically sized dimension. +#### Array + +`Array` represents for a tensor with specific type and size. - Majel heavily use `boost.variant`, but we don't want to integrate `boost` into PaddlePaddle. It's better to replace boost using the lightweight implementation. https://github.com/mapbox/variant Mapbox variant has the same speedy performance of `boost::variant `but is faster to compile, results in smaller binaries, and has no dependencies. +`DArrarVar` is an alias of a specialized class of boost.variant class template. -> @gangliao +`DArray` is introduced to represent a dynamically typed array. + +For example: + +``` +Array a1(Dim<2>(2, 2)); +DArray a2 = make_darray(make_ddim({3, 4}), 0.0, CpuPlace()); +``` -- [ ] 2. Re-implement `Place` and `Allocation/Memory` +You must appoint the type and dimension of a Array, whereas DArray can represent a dynanmically typed array. - I found @wangkuiyi submitted a pull request includes `Place`. @gangliao and @qijun could re-implement `Allocation`, because we have the GPU development experience before joining Paddle team. -> @wangkuiyi @gangliao @qijun +Please reference the section of `Learn from Majel` for more details. -- [ ] 3. Re-implement `Dim`. - `Dim` is an excellent implementation in Majel. -> ??? +### ArrayView -- [ ] 4. Re-implement `Array/Tensor`. +`ViewIterator` is a class template which implements basic iterator operation, including increment(++), decrement(--), dereference(*), equalit comparisons(==) and so on. + +`ArrayView` is an encapsulation of `Array`, which introduces extra iterator menthods, such as `begin()` and `end()`. The `begin()` method returns an iterator pointing to the first element in the ArrayView. And the `end()` method returns an iterator pointing to the pass-the-end element in the ArrayView. + +`ArrayView` make the visting and manipulating an array more efficently, flexibly and safely. + + +A global function `make_view` is provided to transform an array to corresponding arrayview. + +``` +template +ArrayView make_view(const Array& in) { + return in; +} +``` + +A global function `make_iterator` is provided to make iterator of an array. + +``` +template +ViewIterator> make_iterator(const Array& in, Dim idx) { + return make_iterator(make_view(in), idx); +} +``` -> Prerequisites: 1 - 3 +### Basic Operations -- [ ] 5. Re-implement fundamental operators for `Array/Tensor`. +The operations that manipulate DArray are defined as global functions, such as `ones`, `zeros`, `reshape`, `gemm` and so on. -> Prerequisites: 1 - 4 +An array will be trasformed into an arrayview and then passed to the operation launching on a specific deviec(CPU/GPU). -- GitLab From 802c273363d9c42b068485584c94b641ab1d65b5 Mon Sep 17 00:00:00 2001 From: Luo Tao Date: Wed, 17 May 2017 13:32:58 +0800 Subject: [PATCH 0354/3256] fix travis-ci of docs --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index d73fd39aa7a..387367a2305 100644 --- a/.travis.yml +++ b/.travis.yml @@ -48,7 +48,7 @@ before_install: - if [[ "$JOB" == "PRE_COMMIT" ]]; then sudo ln -s /usr/bin/clang-format-3.8 /usr/bin/clang-format; fi # Paddle is using protobuf 3.1 currently. Protobuf 3.2 breaks the compatibility. So we specify the python # protobuf version. - - pip install numpy wheel 'protobuf==3.1' sphinx recommonmark sphinx-rtd-theme==0.1.9 virtualenv pre-commit requests==2.9.2 LinkChecker + - pip install numpy wheel 'protobuf==3.1' sphinx==1.5.6 recommonmark sphinx-rtd-theme==0.1.9 virtualenv pre-commit requests==2.9.2 LinkChecker - | function timeout() { perl -e 'alarm shift; exec @ARGV' "$@"; } script: -- GitLab From 5409e5e3cc1617f93350f13636fe527bc4096c37 Mon Sep 17 00:00:00 2001 From: liaogang Date: Wed, 17 May 2017 13:36:06 +0800 Subject: [PATCH 0355/3256] add reference --- cmake/generic.cmake | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cmake/generic.cmake b/cmake/generic.cmake index 076a9514adc..4001c915072 100644 --- a/cmake/generic.cmake +++ b/cmake/generic.cmake @@ -22,8 +22,12 @@ # ------------------------------------------- # cc_library nv_library go_library # cc_binary nv_binary go_binary -# cc_test nv_test go_test +# cc_test nv_test go_test # ------------------------------------------- +# +# cmake_parse_arguments can help us to achieve this goal. +# https://cmake.org/cmake/help/v3.0/module/CMakeParseArguments.html + # cc_binary parses tensor.cc and figures out that target also depend on tensor.h. # cc_binary(tensor -- GitLab From ed5bcfe8e37973d3750ed9ed04452ff3d98ec211 Mon Sep 17 00:00:00 2001 From: liaogang Date: Wed, 17 May 2017 15:05:02 +0800 Subject: [PATCH 0356/3256] add cu_xx to simplify cmake --- cmake/generic.cmake | 84 +++++++++++++++++++++++++------- paddle/majel/test/CMakeLists.txt | 4 ++ paddle/majel/test/cuda_test.cu | 56 +++++++++++++++++++++ 3 files changed, 126 insertions(+), 18 deletions(-) create mode 100644 paddle/majel/test/cuda_test.cu diff --git a/cmake/generic.cmake b/cmake/generic.cmake index 4001c915072..22a26d7c5b0 100644 --- a/cmake/generic.cmake +++ b/cmake/generic.cmake @@ -28,21 +28,6 @@ # cmake_parse_arguments can help us to achieve this goal. # https://cmake.org/cmake/help/v3.0/module/CMakeParseArguments.html - -# cc_binary parses tensor.cc and figures out that target also depend on tensor.h. -# cc_binary(tensor -# SRCS -# tensor.cc) -function(cc_binary TARGET_NAME) - set(options OPTIONAL) - set(oneValueArgs "") - set(multiValueArgs SRCS DEPS) - cmake_parse_arguments(cc_binary "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) - add_executable(${TARGET_NAME} ${cc_binary_SRCS}) - add_dependencies(${TARGET_NAME} ${cc_binary_DEPS} ${external_project_dependencies}) - target_link_libraries(${TARGET_NAME} ${cc_binary_DEPS}) -endfunction(cc_binary) - # cc_library parses tensor.cc and figures out that target also depend on tensor.h. # cc_library(tensor # SRCS @@ -54,14 +39,28 @@ function(cc_library TARGET_NAME) set(oneValueArgs "") set(multiValueArgs SRCS DEPS) cmake_parse_arguments(cc_library "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) - if (${cc_library_OPTIONAL} STREQUAL "STATIC") - add_library(${TARGET_NAME} STATIC ${cc_library_SRCS}) - else() + if (${cc_library_OPTIONAL} STREQUAL "SHARED") add_library(${TARGET_NAME} SHARED ${cc_library_SRCS}) + else() + add_library(${TARGET_NAME} STATIC ${cc_library_SRCS}) endif() add_dependencies(${TARGET_NAME} ${cc_library_DEPS} ${external_project_dependencies}) endfunction(cc_library) +# cc_binary parses tensor.cc and figures out that target also depend on tensor.h. +# cc_binary(tensor +# SRCS +# tensor.cc) +function(cc_binary TARGET_NAME) + set(options OPTIONAL) + set(oneValueArgs "") + set(multiValueArgs SRCS DEPS) + cmake_parse_arguments(cc_binary "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + add_executable(${TARGET_NAME} ${cc_binary_SRCS}) + add_dependencies(${TARGET_NAME} ${cc_binary_DEPS} ${external_project_dependencies}) + target_link_libraries(${TARGET_NAME} ${cc_binary_DEPS}) +endfunction(cc_binary) + # The dependency to target tensor implies that if any of # tensor{.h,.cc,_test.cc} is changed, tensor_test need to be re-built. # cc_test(tensor_test @@ -79,3 +78,52 @@ function(cc_test TARGET_NAME) target_link_libraries(${TARGET_NAME} ${cc_test_DEPS} ${GTEST_MAIN_LIBRARIES} ${GTEST_LIBRARIES}) add_test(${TARGET_NAME} ${TARGET_NAME}) endfunction(cc_test) + +# Suppose that ops.cu includes global functions that take Tensor as +# their parameters, so ops depend on tensor. This implies that if +# any of tensor.{h.cc}, ops.{h,cu} is changed, ops need to be re-built. +# nv_library(ops +# SRCS +# ops.cu +# DEPS +# tensor) +function(nv_library TARGET_NAME) + set(options OPTIONAL) + set(oneValueArgs "") + set(multiValueArgs SRCS DEPS) + cmake_parse_arguments(nv_library "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + if (${nv_library_OPTIONAL} STREQUAL "SHARED") + cuda_add_library(${TARGET_NAME} SHARED ${nv_library_SRCS}) + else() + cuda_add_library(${TARGET_NAME} STATIC ${nv_library_SRCS}) + endif() + add_dependencies(${TARGET_NAME} ${nv_library_DEPS} ${external_project_dependencies}) +endfunction(nv_library) + +function(nv_binary TARGET_NAME) + set(options "") + set(oneValueArgs "") + set(multiValueArgs SRCS DEPS) + cmake_parse_arguments(nv_binary "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + cuda_add_executable(${TARGET_NAME} ${nv_binary_SRCS}) + add_dependencies(${TARGET_NAME} ${nv_binary_DEPS} ${external_project_dependencies}) + target_link_libraries(${TARGET_NAME} ${nv_binary_DEPS}) +endfunction(nv_binary) + +# The dependency to target tensor implies that if any of +# ops{.h,.cu,_test.cu} is changed, ops_test need to be re-built. +# nv_test(ops_test +# SRCS +# ops_test.cu +# DEPS +# ops) +function(nv_test TARGET_NAME) + set(options "") + set(oneValueArgs "") + set(multiValueArgs SRCS DEPS) + cmake_parse_arguments(nv_test "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + cuda_add_executable(${TARGET_NAME} ${nv_test_SRCS}) + add_dependencies(${TARGET_NAME} ${nv_test_DEPS} ${external_project_dependencies}) + target_link_libraries(${TARGET_NAME} ${nv_test_DEPS} ${GTEST_MAIN_LIBRARIES} ${GTEST_LIBRARIES}) + add_test(${TARGET_NAME} ${TARGET_NAME}) +endfunction(nv_test) diff --git a/paddle/majel/test/CMakeLists.txt b/paddle/majel/test/CMakeLists.txt index 6379a4d6e71..68f9059874a 100644 --- a/paddle/majel/test/CMakeLists.txt +++ b/paddle/majel/test/CMakeLists.txt @@ -1,3 +1,7 @@ cc_test(place_test SRCS place_test.cc DEPS majel) + +if(WITH_GPU) + nv_test(cuda_test SRCS cuda_test.cu) +endif() diff --git a/paddle/majel/test/cuda_test.cu b/paddle/majel/test/cuda_test.cu new file mode 100644 index 00000000000..ebc9a2786e1 --- /dev/null +++ b/paddle/majel/test/cuda_test.cu @@ -0,0 +1,56 @@ +#include +#include +#include "gtest/gtest.h" + +#define CHECK_ERR(x) \ + if (x != cudaSuccess) { \ + fprintf(stderr,"%s in %s at line %d\n", \ + cudaGetErrorString(err),__FILE__,__LINE__); \ + exit(-1); \ + } \ + +__global__ void vecAdd (float* d_A, float* d_B, float* d_C, int n) { + int i = blockDim.x * blockIdx.x + threadIdx.x; + if (i < n) { + d_C[i] = d_A[i] + d_B[i]; + } +} + +TEST(Cuda, Equality) { + int n = 10; + // Memory allocation for h_A, h_B and h_C (in the host) + float h_A[10] = { 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 0.0 }; + float h_B[10] = { 0.0, 9.0, 8.0, 7.0, 6.0, 5.0, 4.0, 3.0, 2.0, 1.0 }; + float h_C[10]; + float *d_A, *d_B, *d_C; + + // Memory allocation for d_A, d_B and d_C (in the device) + err = cudaMalloc((void **) &d_A, sizeof(float)*n); + CHECK_ERR(err); + + err =cudaMalloc((void **) &d_B, sizeof(float)*n); + CHECK_ERR(err); + + err =cudaMalloc((void **) &d_C, sizeof(float)*n); + CHECK_ERR(err); + + // Copying memory to device + err = cudaMemcpy(d_A, h_A, sizeof(float)*n, cudaMemcpyHostToDevice); + CHECK_ERR(err); + + err = cudaMemcpy(d_B, h_B, sizeof(float)*n, cudaMemcpyHostToDevice); + CHECK_ERR(err); + + // Calling the kernel + vecAdd<<>>(d_A,d_B,d_C,n); + + // Copying results back to host + err = cudaMemcpy(h_C, d_C, sizeof(float)*n, cudaMemcpyDeviceToHost); + CHECK_ERR(err); + + EXPECT_EQ(h_C[1], 1.0); + for (size_t i = 1; i < n - 1; ++i) { + EXPECT_EQ(h_C[i], 11.0); + } + EXPECT_EQ(h_C[0], 1.0); +} -- GitLab From 26a73e48f976216374fc8a9294228673648a1d1d Mon Sep 17 00:00:00 2001 From: liaogang Date: Wed, 17 May 2017 15:07:05 +0800 Subject: [PATCH 0357/3256] update code --- paddle/majel/test/cuda_test.cu | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/paddle/majel/test/cuda_test.cu b/paddle/majel/test/cuda_test.cu index ebc9a2786e1..28cda19ef98 100644 --- a/paddle/majel/test/cuda_test.cu +++ b/paddle/majel/test/cuda_test.cu @@ -5,9 +5,9 @@ #define CHECK_ERR(x) \ if (x != cudaSuccess) { \ fprintf(stderr,"%s in %s at line %d\n", \ - cudaGetErrorString(err),__FILE__,__LINE__); \ - exit(-1); \ - } \ + cudaGetErrorString(err),__FILE__,__LINE__); \ + exit(-1); \ + } __global__ void vecAdd (float* d_A, float* d_B, float* d_C, int n) { int i = blockDim.x * blockIdx.x + threadIdx.x; -- GitLab From 966a95e329ee4c5907c643cb72d341e51c10cf3a Mon Sep 17 00:00:00 2001 From: Luo Tao Date: Tue, 16 May 2017 16:40:41 +0800 Subject: [PATCH 0358/3256] add assert when is_seq=True, there must be bootLayer --- python/paddle/trainer_config_helpers/layers.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/python/paddle/trainer_config_helpers/layers.py b/python/paddle/trainer_config_helpers/layers.py index 3b6f0270de1..dcf1c05bd6a 100755 --- a/python/paddle/trainer_config_helpers/layers.py +++ b/python/paddle/trainer_config_helpers/layers.py @@ -2916,6 +2916,8 @@ def memory(name, boot_bias = ParamAttr.to_bias(boot_bias) assert boot_layer is None or isinstance(boot_layer, LayerOutput) + if is_seq == True: + assert isinstance(boot_layer, LayerOutput) if name is not None: memory_name = None -- GitLab From 23e00ba37990f34981c80f25f5a66c18ba0154b4 Mon Sep 17 00:00:00 2001 From: Luo Tao Date: Wed, 17 May 2017 12:55:38 +0800 Subject: [PATCH 0359/3256] follow comments --- python/paddle/trainer/config_parser.py | 3 +++ python/paddle/trainer_config_helpers/layers.py | 2 -- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/python/paddle/trainer/config_parser.py b/python/paddle/trainer/config_parser.py index 57d30b088b8..9135f38719a 100644 --- a/python/paddle/trainer/config_parser.py +++ b/python/paddle/trainer/config_parser.py @@ -2320,6 +2320,9 @@ def Memory(name, memory_name = name + "+delay1" agent_name = memory_name if is_sequence: + config_assert( + boot_layer is not None, + "there must be boot_layer in network when is_sequence = True") agent_layer = SequenceAgentLayer(agent_name, size) else: agent_layer = AgentLayer(agent_name, size) diff --git a/python/paddle/trainer_config_helpers/layers.py b/python/paddle/trainer_config_helpers/layers.py index dcf1c05bd6a..3b6f0270de1 100755 --- a/python/paddle/trainer_config_helpers/layers.py +++ b/python/paddle/trainer_config_helpers/layers.py @@ -2916,8 +2916,6 @@ def memory(name, boot_bias = ParamAttr.to_bias(boot_bias) assert boot_layer is None or isinstance(boot_layer, LayerOutput) - if is_seq == True: - assert isinstance(boot_layer, LayerOutput) if name is not None: memory_name = None -- GitLab From 0a21649f796c4c40c11b9e620d3ea3a028a9d1d4 Mon Sep 17 00:00:00 2001 From: caoying03 Date: Wed, 17 May 2017 15:27:37 +0800 Subject: [PATCH 0360/3256] update the SRL demo to add infering. --- demo/semantic_role_labeling/api_train_v2.py | 169 ++++++++++++++------ 1 file changed, 116 insertions(+), 53 deletions(-) diff --git a/demo/semantic_role_labeling/api_train_v2.py b/demo/semantic_role_labeling/api_train_v2.py index 036cad4b0a3..c323046407f 100644 --- a/demo/semantic_role_labeling/api_train_v2.py +++ b/demo/semantic_role_labeling/api_train_v2.py @@ -1,26 +1,34 @@ -import sys import math import numpy as np -import paddle.v2 as paddle +import gzip +import logging import paddle.v2.dataset.conll05 as conll05 +import paddle.v2.evaluator as evaluator +import paddle.v2 as paddle +logger = logging.getLogger('paddle') +logger.setLevel(logging.WARN) -def db_lstm(): - word_dict, verb_dict, label_dict = conll05.get_dict() - word_dict_len = len(word_dict) - label_dict_len = len(label_dict) - pred_len = len(verb_dict) +word_dict, verb_dict, label_dict = conll05.get_dict() +word_dict_len = len(word_dict) +label_dict_len = len(label_dict) +pred_len = len(verb_dict) - mark_dict_len = 2 - word_dim = 32 - mark_dim = 5 - hidden_dim = 512 - depth = 8 +mark_dict_len = 2 +word_dim = 32 +mark_dim = 5 +hidden_dim = 512 +depth = 8 +default_std = 1 / math.sqrt(hidden_dim) / 3.0 +mix_hidden_lr = 1e-3 - #8 features - def d_type(size): - return paddle.data_type.integer_value_sequence(size) +def d_type(size): + return paddle.data_type.integer_value_sequence(size) + + +def db_lstm(): + #8 features word = paddle.layer.data(name='word_data', type=d_type(word_dict_len)) predicate = paddle.layer.data(name='verb_data', type=d_type(pred_len)) @@ -31,11 +39,7 @@ def db_lstm(): ctx_p2 = paddle.layer.data(name='ctx_p2_data', type=d_type(word_dict_len)) mark = paddle.layer.data(name='mark_data', type=d_type(mark_dict_len)) - target = paddle.layer.data(name='target', type=d_type(label_dict_len)) - - default_std = 1 / math.sqrt(hidden_dim) / 3.0 - - emb_para = paddle.attr.Param(name='emb', initial_std=0., learning_rate=0.) + emb_para = paddle.attr.Param(name='emb', initial_std=0., is_static=True) std_0 = paddle.attr.Param(initial_std=0.) std_default = paddle.attr.Param(initial_std=default_std) @@ -63,7 +67,6 @@ def db_lstm(): input=emb, param_attr=std_default) for emb in emb_layers ]) - mix_hidden_lr = 1e-3 lstm_para_attr = paddle.attr.Param(initial_std=0.0, learning_rate=1.0) hidden_para_attr = paddle.attr.Param( initial_std=default_std, learning_rate=mix_hidden_lr) @@ -111,6 +114,33 @@ def db_lstm(): input=input_tmp[1], param_attr=lstm_para_attr) ], ) + return feature_out + + +def load_parameter(file_name, h, w): + with open(file_name, 'rb') as f: + f.read(16) # skip header. + return np.fromfile(f, dtype=np.float32).reshape(h, w) + + +def test_a_batch(inferer, test_data, tag_dict): + probs = inferer.infer(input=test_data, field='id') + assert len(probs) == sum(len(x[0]) for x in test_data) + for test_sample in test_data: + start_id = 0 + pre_lab = [ + tag_dict[probs[start_id + i]] for i in xrange(len(test_sample[0])) + ] + print pre_lab + start_id += len(test_sample[0]) + + +def main(is_predict=False): + paddle.init(use_gpu=False, trainer_count=1) + + # define network topology + feature_out = db_lstm() + target = paddle.layer.data(name='target', type=d_type(label_dict_len)) crf_cost = paddle.layer.crf(size=label_dict_len, input=feature_out, label=target, @@ -120,29 +150,20 @@ def db_lstm(): learning_rate=mix_hidden_lr)) crf_dec = paddle.layer.crf_decoding( - name='crf_dec_l', size=label_dict_len, input=feature_out, label=target, param_attr=paddle.attr.Param(name='crfw')) - - return crf_cost, crf_dec - - -def load_parameter(file_name, h, w): - with open(file_name, 'rb') as f: - f.read(16) # skip header. - return np.fromfile(f, dtype=np.float32).reshape(h, w) - - -def main(): - paddle.init(use_gpu=False, trainer_count=1) - - # define network topology - crf_cost, crf_dec = db_lstm() + evaluator.sum(input=crf_dec) + evaluator.chunk( + input=crf_dec, + label=target, + chunk_scheme="IOB", + num_chunk_types=label_dict_len / 2) # create parameters - parameters = paddle.parameters.create([crf_cost, crf_dec]) + parameters = paddle.parameters.create(crf_cost) + parameters.set('emb', load_parameter(conll05.get_embedding(), 44068, 32)) # create optimizer optimizer = paddle.optimizer.Momentum( @@ -152,18 +173,12 @@ def main(): model_average=paddle.optimizer.ModelAverage( average_window=0.5, max_average_window=10000), ) - def event_handler(event): - if isinstance(event, paddle.event.EndIteration): - if event.batch_id % 100 == 0: - print "Pass %d, Batch %d, Cost %f, %s" % ( - event.pass_id, event.batch_id, event.cost, event.metrics) - trainer = paddle.trainer.SGD(cost=crf_cost, parameters=parameters, - update_equation=optimizer) - parameters.set('emb', load_parameter(conll05.get_embedding(), 44068, 32)) + update_equation=optimizer, + extra_layers=crf_dec) - trn_reader = paddle.batch( + reader = paddle.batch( paddle.reader.shuffle( conll05.test(), buf_size=8192), batch_size=10) @@ -179,12 +194,60 @@ def main(): 'target': 8 } - trainer.train( - reader=trn_reader, - event_handler=event_handler, - num_passes=10000, - feeding=feeding) + def event_handler(event): + if isinstance(event, paddle.event.EndIteration): + if event.batch_id % 100 == 0: + print "Pass %d, Batch %d, Cost %f, %s" % ( + event.pass_id, event.batch_id, event.cost, event.metrics) + if event.batch_id % 1000 == 0: + result = trainer.test(reader=reader, feeding=feeding) + print "\nTest with Pass %d, Batch %d, %s" % ( + event.pass_id, event.batch_id, result.metrics) + + if isinstance(event, paddle.event.EndPass): + # save parameters + with gzip.open('params_pass_%d.tar.gz' % event.pass_id, 'w') as f: + parameters.to_tar(f) + + result = trainer.test(reader=reader, feeding=feeding) + print "\nTest with Pass %d, %s" % (event.pass_id, result.metrics) + + if not is_predict: + trainer.train( + reader=reader, + event_handler=event_handler, + num_passes=10, + feeding=feeding) + else: + labels_reverse = {} + for (k, v) in label_dict.items(): + labels_reverse[v] = k + test_creator = paddle.dataset.conll05.test() + + predict = paddle.layer.crf_decoding( + size=label_dict_len, + input=feature_out, + param_attr=paddle.attr.Param(name='crfw')) + + test_pass = 0 + with gzip.open('params_pass_%d.tar.gz' % (test_pass)) as f: + parameters = paddle.parameters.Parameters.from_tar(f) + inferer = paddle.inference.Inference( + output_layer=predict, parameters=parameters) + + # prepare test data + test_data = [] + test_batch_size = 50 + + for idx, item in enumerate(test_creator()): + test_data.append(item[0:8]) + + if idx and (not idx % test_batch_size): + test_a_batch(inferer, test_data, labels_reverse) + test_data = [] + test_a_batch(inferer, test_data, labels_reverse) + test_data = [] if __name__ == '__main__': - main() + main(is_predict=True) -- GitLab From d7ee421be952cdf13dc9c7bfc0ffe2181b39b5b7 Mon Sep 17 00:00:00 2001 From: liaogang Date: Wed, 17 May 2017 15:29:26 +0800 Subject: [PATCH 0361/3256] add cuda unit test --- paddle/majel/test/cuda_test.cu | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/paddle/majel/test/cuda_test.cu b/paddle/majel/test/cuda_test.cu index 28cda19ef98..360c2548755 100644 --- a/paddle/majel/test/cuda_test.cu +++ b/paddle/majel/test/cuda_test.cu @@ -23,7 +23,7 @@ TEST(Cuda, Equality) { float h_B[10] = { 0.0, 9.0, 8.0, 7.0, 6.0, 5.0, 4.0, 3.0, 2.0, 1.0 }; float h_C[10]; float *d_A, *d_B, *d_C; - + cudaError_t err; // Memory allocation for d_A, d_B and d_C (in the device) err = cudaMalloc((void **) &d_A, sizeof(float)*n); CHECK_ERR(err); @@ -48,9 +48,9 @@ TEST(Cuda, Equality) { err = cudaMemcpy(h_C, d_C, sizeof(float)*n, cudaMemcpyDeviceToHost); CHECK_ERR(err); - EXPECT_EQ(h_C[1], 1.0); - for (size_t i = 1; i < n - 1; ++i) { + EXPECT_EQ(h_C[0], 1.0); + for (int i = 1; i < n - 1; ++i) { EXPECT_EQ(h_C[i], 11.0); } - EXPECT_EQ(h_C[0], 1.0); + EXPECT_EQ(h_C[9], 1.0); } -- GitLab From 32f7176de3f20e53ae2116a2364e8413d8f31469 Mon Sep 17 00:00:00 2001 From: caoying03 Date: Wed, 17 May 2017 15:54:27 +0800 Subject: [PATCH 0362/3256] remove chunk evaluator. --- demo/semantic_role_labeling/api_train_v2.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/demo/semantic_role_labeling/api_train_v2.py b/demo/semantic_role_labeling/api_train_v2.py index c323046407f..f9c1fa80dac 100644 --- a/demo/semantic_role_labeling/api_train_v2.py +++ b/demo/semantic_role_labeling/api_train_v2.py @@ -6,9 +6,6 @@ import paddle.v2.dataset.conll05 as conll05 import paddle.v2.evaluator as evaluator import paddle.v2 as paddle -logger = logging.getLogger('paddle') -logger.setLevel(logging.WARN) - word_dict, verb_dict, label_dict = conll05.get_dict() word_dict_len = len(word_dict) label_dict_len = len(label_dict) @@ -155,11 +152,6 @@ def main(is_predict=False): label=target, param_attr=paddle.attr.Param(name='crfw')) evaluator.sum(input=crf_dec) - evaluator.chunk( - input=crf_dec, - label=target, - chunk_scheme="IOB", - num_chunk_types=label_dict_len / 2) # create parameters parameters = paddle.parameters.create(crf_cost) @@ -250,4 +242,4 @@ def main(is_predict=False): if __name__ == '__main__': - main(is_predict=True) + main(is_predict=False) -- GitLab From f3eb9cb36a37efd58cbbf91f9f9e4c888bec1d65 Mon Sep 17 00:00:00 2001 From: yangyaming Date: Wed, 17 May 2017 16:38:21 +0800 Subject: [PATCH 0363/3256] Override getValueImpl and revise document --- paddle/gserver/evaluators/ChunkEvaluator.cpp | 36 +++++------- .../trainer_config_helpers/evaluators.py | 57 +++++++++++++------ 2 files changed, 56 insertions(+), 37 deletions(-) diff --git a/paddle/gserver/evaluators/ChunkEvaluator.cpp b/paddle/gserver/evaluators/ChunkEvaluator.cpp index b94a641b4a2..1658282f3a5 100644 --- a/paddle/gserver/evaluators/ChunkEvaluator.cpp +++ b/paddle/gserver/evaluators/ChunkEvaluator.cpp @@ -75,6 +75,7 @@ class ChunkEvaluator : public Evaluator { std::vector labelSegments_; std::vector outputSegments_; std::set excludedChunkTypes_; + mutable std::unordered_map values_; public: virtual void init(const EvaluatorConfig& config) { @@ -243,23 +244,22 @@ public: return false; } -public: // three metrics: precision, recall and F1-score void getNames(std::vector* names) { - this->storeLocalValues(); - names->reserve(this->values_.size()); - for (auto it = this->values_.begin(); it != this->values_.end(); ++it) { - names->push_back(this->config_.name() + "." + it->first); + storeLocalValues(); + names->reserve(names->size() + values_.size()); + for (auto it = values_.begin(); it != values_.end(); ++it) { + names->push_back(config_.name() + "." + it->first); } } // get value by field name real getValue(const std::string& name, Error* err) const { - this->storeLocalValues(); + storeLocalValues(); std::vector buffers; paddle::str::split(name, '.', &buffers); - auto it = this->values_.find(buffers[buffers.size() - 1]); - if (it == this->values_.end()) { // not found + auto it = values_.find(buffers.back()); + if (it == values_.end()) { // not found *err = Error("No such key %s", name.c_str()); return 0.0f; } @@ -268,27 +268,21 @@ public: } // get type of evaluator - std::string getType(const std::string& name, Error* err) const { - this->getValue(name, err); - if (!err->isOK()) { - return std::string(); - } - return "chunk"; - } + std::string getTypeImpl() const { return "chunk"; } private: void storeLocalValues() const { - CHECK_GT(numOutputSegments_, 0); - CHECK_GT(numLabelSegments_, 0); - double precision = (double)numCorrect_ / numOutputSegments_; - double recall = (double)numCorrect_ / numLabelSegments_; + CHECK_GE(numOutputSegments_, 0); + CHECK_GE(numLabelSegments_, 0); + double precision = + !numOutputSegments_ ? 0 : (double)numCorrect_ / numOutputSegments_; + double recall = + !numLabelSegments_ ? 0 : (double)numCorrect_ / numLabelSegments_; values_["precision"] = precision; values_["recall"] = recall; values_["F1-score"] = !numCorrect_ ? 0 : 2 * precision * recall / (precision + recall); } - - mutable std::unordered_map values_; }; REGISTER_EVALUATOR(chunk, ChunkEvaluator); diff --git a/python/paddle/trainer_config_helpers/evaluators.py b/python/paddle/trainer_config_helpers/evaluators.py index 8704a8cde2d..6900133fde2 100644 --- a/python/paddle/trainer_config_helpers/evaluators.py +++ b/python/paddle/trainer_config_helpers/evaluators.py @@ -347,45 +347,68 @@ def chunk_evaluator( excluded_chunk_types=None, ): """ Chunk evaluator is used to evaluate segment labelling accuracy for a - sequence. It calculates precision, recall and F1 score of the chunk detection. + sequence. It calculates precision, recall and F1 scores for the chunk detection. - To use chunk evaluator, the construction of label dict should obey the following rules: + To use chunk evaluator, several concepts need to be clarified firstly. + Chunk type is the type of the whole chunk and a chunk consists of one or several words. (For example in NER, ORG for organization name, PER for person name etc.) + Tag indicates the position of a word in a chunk. (B for begin, I for inside, E for end, S for single) + We can name a label by combining tag type and chunk type. (ie. B-ORG for begining of an organization name) + + The construction of label dict should obey the following rules: (1) Use one of the listed labelling schemes. These schemes differ in ways indicating chunk boundry. .. code-block:: python - Scheme Begin Inside End Single - plain 0 - - - - IOB 0 1 - - - IOE - 0 1 - - IOBES 0 1 2 3 + Scheme Description + plain Use the same label for the whole chunk. + IOB Two labels for chunk type X, B-X for chunk begining and I-X for chunk inside. + IOE Two labels for chunk type X, E-X for chunk ending and I-X for chunk inside. + IOBES Four labels for chunk type X, B-X for chunk begining, I-X for chunk inside, E-X for chunk end and S-X for single word chunk. .. code-block:: python - - To make it clear, let's illustrate by a NER example. - Assuming that there are two named entity types including ORG and PER which are called 'chunk type' here, - if 'IOB' scheme were used, the label set will be extended to a set including B-ORG, I-ORG, B-PER, I-PER and O, - in which B-ORG for begining of ORG and I-ORG for end of ORG. + + To make it clear, let's illustrate by an NER example. + Assuming that there are three named entity types including ORG, PER and LOC which are called 'chunk type' here, + if 'IOB' scheme were used, the label set will be extended to a set including B-ORG, I-ORG, B-PER, I-PER, B-LOC, I-LOC and O, + in which B-ORG for begining of ORG and I-ORG for inside of ORG. Prefixes which are called 'tag type' here are added to chunk types and there are two tag types including B and I. Of course, the training data should be labeled accordingly. - (2) Map can be done correctly by the listed equations. + (2) Mapping is done correctly by the listed equations and assigning protocol. + + The following table are equations to extract tag type and chunk type from a label. .. code-block:: python tagType = label % numTagType chunkType = label / numTagType otherChunkType = numChunkTypes .. code-block:: python + + The following table shows the mapping rule between tagType and tag type in each scheme. - Continue the NER example, and the label dict should like this to satify above equations: + .. code-block:: python + Scheme Begin Inside End Single + plain 0 - - - + IOB 0 1 - - + IOE - 0 1 - + IOBES 0 1 2 3 + .. code-block:: python + + Continue the NER example, and the label dict should look like this to satify above equations: .. code-block:: python B-ORG 0 I-ORG 1 B-PER 2 I-PER 3 - O 4 + B-LOC 4 + I-LOC 5 + O 6 .. code-block:: python - Realizing that the number of is chunk type is 2 and number of tag type is 2, it is easy to validate this. + In this example, chunkType has three values: 0 for ORG, 1 for PER, 2 for LOC, because the scheme is + "IOB" so tagType has two values: 0 for B and 1 for I. + Here we will use I-LOC to explain the above mapping rules in detail. + For I-LOC, the label id is 5, so we can get tagType=1 and ChunkType=2, which means I-LOC is a part of NER chunk LOC + and the tag is I. The simple usage is: @@ -393,6 +416,8 @@ def chunk_evaluator( eval = chunk_evaluator(input, label, chunk_scheme, num_chunk_types) + .. code-block:: python + :param input: The input layers. :type input: LayerOutput :param label: An input layer containing the ground truth label. -- GitLab From 09c6ddcd635a939414a31dcb4609054c95a8401e Mon Sep 17 00:00:00 2001 From: liaogang Date: Wed, 17 May 2017 17:15:46 +0800 Subject: [PATCH 0364/3256] clang-format cuda --- paddle/majel/test/cuda_test.cu | 39 ++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/paddle/majel/test/cuda_test.cu b/paddle/majel/test/cuda_test.cu index 360c2548755..4067dda2f19 100644 --- a/paddle/majel/test/cuda_test.cu +++ b/paddle/majel/test/cuda_test.cu @@ -1,15 +1,18 @@ -#include #include +#include #include "gtest/gtest.h" -#define CHECK_ERR(x) \ - if (x != cudaSuccess) { \ - fprintf(stderr,"%s in %s at line %d\n", \ - cudaGetErrorString(err),__FILE__,__LINE__); \ - exit(-1); \ +#define CHECK_ERR(x) \ + if (x != cudaSuccess) { \ + fprintf(stderr, \ + "%s in %s at line %d\n", \ + cudaGetErrorString(err), \ + __FILE__, \ + __LINE__); \ + exit(-1); \ } -__global__ void vecAdd (float* d_A, float* d_B, float* d_C, int n) { +__global__ void vecAdd(float *d_A, float *d_B, float *d_C, int n) { int i = blockDim.x * blockIdx.x + threadIdx.x; if (i < n) { d_C[i] = d_A[i] + d_B[i]; @@ -19,35 +22,35 @@ __global__ void vecAdd (float* d_A, float* d_B, float* d_C, int n) { TEST(Cuda, Equality) { int n = 10; // Memory allocation for h_A, h_B and h_C (in the host) - float h_A[10] = { 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 0.0 }; - float h_B[10] = { 0.0, 9.0, 8.0, 7.0, 6.0, 5.0, 4.0, 3.0, 2.0, 1.0 }; + float h_A[10] = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 0.0}; + float h_B[10] = {0.0, 9.0, 8.0, 7.0, 6.0, 5.0, 4.0, 3.0, 2.0, 1.0}; float h_C[10]; float *d_A, *d_B, *d_C; cudaError_t err; // Memory allocation for d_A, d_B and d_C (in the device) - err = cudaMalloc((void **) &d_A, sizeof(float)*n); + err = cudaMalloc((void **)&d_A, sizeof(float) * n); CHECK_ERR(err); - err =cudaMalloc((void **) &d_B, sizeof(float)*n); + err = cudaMalloc((void **)&d_B, sizeof(float) * n); CHECK_ERR(err); - err =cudaMalloc((void **) &d_C, sizeof(float)*n); + err = cudaMalloc((void **)&d_C, sizeof(float) * n); CHECK_ERR(err); - + // Copying memory to device - err = cudaMemcpy(d_A, h_A, sizeof(float)*n, cudaMemcpyHostToDevice); + err = cudaMemcpy(d_A, h_A, sizeof(float) * n, cudaMemcpyHostToDevice); CHECK_ERR(err); - err = cudaMemcpy(d_B, h_B, sizeof(float)*n, cudaMemcpyHostToDevice); + err = cudaMemcpy(d_B, h_B, sizeof(float) * n, cudaMemcpyHostToDevice); CHECK_ERR(err); // Calling the kernel - vecAdd<<>>(d_A,d_B,d_C,n); + vecAdd<<>>(d_A, d_B, d_C, n); // Copying results back to host - err = cudaMemcpy(h_C, d_C, sizeof(float)*n, cudaMemcpyDeviceToHost); + err = cudaMemcpy(h_C, d_C, sizeof(float) * n, cudaMemcpyDeviceToHost); CHECK_ERR(err); - + EXPECT_EQ(h_C[0], 1.0); for (int i = 1; i < n - 1; ++i) { EXPECT_EQ(h_C[i], 11.0); -- GitLab From 8f1a5ff2fda48ba6021330b1366d46daadd6538f Mon Sep 17 00:00:00 2001 From: Liu Yiqun Date: Wed, 17 May 2017 16:55:17 +0800 Subject: [PATCH 0365/3256] Add document, cross-compiling for Android. --- .../cross_compiling_for_android_cn.md | 76 +++++++++++++++++++ .../cross_compiling_for_raspberry_cn.md} | 4 +- 2 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 doc/howto/cross_compiling/cross_compiling_for_android_cn.md rename doc/howto/{raspberry/build_for_raspberry.md => cross_compiling/cross_compiling_for_raspberry_cn.md} (97%) diff --git a/doc/howto/cross_compiling/cross_compiling_for_android_cn.md b/doc/howto/cross_compiling/cross_compiling_for_android_cn.md new file mode 100644 index 00000000000..81cefb46234 --- /dev/null +++ b/doc/howto/cross_compiling/cross_compiling_for_android_cn.md @@ -0,0 +1,76 @@ +# 构建Android平台上的PaddlePaddle库 + +用户可通过交叉编译的方式,在用户熟悉的开发平台(Linux,Mac OS X和Windows)上编译Android平台上适用的PaddlePaddle库。 +本文档将以Linux x86-64平台为例,介绍交叉编译Android平台上适用的PaddlePaddle库的方法和步骤。 + +## 准备交叉编译环境 + +从源码交叉编译PaddlePaddle,用户需要提前准备好交叉编译环境。Android平台上使用的C/C++交叉编译工具链为[Android NDK](https://developer.android.com/ndk/downloads/index.html?hl=zh-cn),用户可自行前往下载预编译好的版本,也可通过以下命令获取: + +```bash +wget -q https://dl.google.com/android/repository/android-ndk-r14b-linux-x86_64.zip +unzip -q android-ndk-r14b-linux-x86_64.zip +``` + +Android NDK中包含了所有Android API级别、所有架构(arm/arm64/x86/mips)需要用到的编译工具和系统库。用户可根据自己的编译目标架构、所需支持的最低Android API级别,构建[独立工具链](https://developer.android.google.cn/ndk/guides/standalone_toolchain.html?hl=zh-cn)。 +比如: + +```bash +your/path/to/android-ndk-r14b-linux-x86_64/build/tools/make-standalone-toolchain.sh \ + --arch=arm --platform=android-21 --install-dir=your/path/to/my_standalone_toolchain +``` + +此命令将在your/path/to/my_standalone_toolchain目录生成一套编译工具链,面向架构为32位ARM架构,支持的最小的Android API级别为21,使用的编译器为arm-linux-androideabi-gcc (GCC) 4.9。 + +注意:**PaddlePaddle要求使用的编译工具链所支持的Andoid 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`)。 +- `WITH_C_API`,必须设置为`ON`。在Android平台上只支持使用C-API来预测。 +- `WITH_SWIG_PY`,必须设置为`OFF`。在Android平台上不支持通过swig调用来训练或者预测。 + +Android平台可选配置参数: + +- `ANDROID_STANDALONE_TOOLCHAIN`,独立工具链所在的绝对路径,或者相对构建目录的相对路径。PaddlePaddle的CMake系统将根据该值自动推导和设置需要使用的交叉编译器、sysroot、以及Android API级别;否则,用户需要在cmake时自动设置这些值。无默认值。 +- `ANDROID_ABI`,目标架构ABI。目前只支持`armeabi-v7a`,默认值为`armeabi-v7a`。 +- `ANDROID_NATIVE_API_LEVEL`,工具链的Android API级别。若没有显式设置,PaddlePaddle将根据`ANDROID_STANDALONE_TOOLCHAIN`的值自动推导得到。 +- `ANROID_ARM_MODE`,是否使用ARM模式。可设置`ON/OFF`,默认值为`ON`。 +- `ANDROID_ARM_NEON`,是否使用NEON指令。目前必须设置成`ON`,默认值为`ON`。 + +其他配置参数: + +- `HOST_C/CXX_COMPILER`,CMake编译的宿主平台的C/C++编译器。在编译宿主机版protoc可执行文件和目标机版OpenBLAS时需要用到。 + +一种常用的cmake配置如下: + +```bash +cmake -DCMAKE_SYSTEM_NAME=Android \ + -DANDROID_STANDALONE_TOOLCHAIN=your/path/to/my_standalone_toolchain \ + -DANDROID_ABI=armeabi-v7a \ + -DANDROID_ARM_NEON=ON \ + -DANDROID_ARM_MODE=ON \ + -DCMAKE_INSTALL_PREFIX=your/path/to/install \ + -DWITH_C_API=ON \ + -DWITH_SWIG_PY=OFF \ + .. +``` + +用户还可根据自己的需求设置其他编译参数。比如希望最小化生成的库的大小,可以设置`CMAKE_BUILD_TYPE`为`MinSizeRel`;若希望最快的执行速度,则可设置`CMAKE_BUILD_TYPE`为`Release`。 + +## 编译和安装 + +CMake配置完成后,执行以下命令,PaddlePaddle将自动下载和编译所有所需要的第三方依赖库、编译和安装PaddlePaddle预测库。 + +```bash +make +make install +``` + +注意:如果你曾经在源码目录下编译过其他平台的PaddlePaddle库,请先使用`rm -rf`命令删除`third_party`目录和`build`目录,以确保所有的第三方依赖库和PaddlePaddle代码都是针对新的CMake配置重新编译的。 + +执行完安装命令后,`your/path/to/install`目录会中包含`include`和`lib`目录,其中`include`中包含C-API的头文件,`lib`中包含一个Android版本的库。自此,PaddlePaddle的已经安装完成,用户可将`your/path/to/install`目录下的生成文件用于深度学习相关Android App中,调用方法见C-API文档。 + diff --git a/doc/howto/raspberry/build_for_raspberry.md b/doc/howto/cross_compiling/cross_compiling_for_raspberry_cn.md similarity index 97% rename from doc/howto/raspberry/build_for_raspberry.md rename to doc/howto/cross_compiling/cross_compiling_for_raspberry_cn.md index 4a98aba8f2a..d0239ec4d01 100644 --- a/doc/howto/raspberry/build_for_raspberry.md +++ b/doc/howto/cross_compiling/cross_compiling_for_raspberry_cn.md @@ -1,4 +1,4 @@ -# 如何构建Raspberry pi下运行的PaddlePaddle +# 如何构建Raspberry Pi下运行的PaddlePaddle 这里考虑的是交叉编译方式,即在Linux-x86环境下构建Raspberry pi下可运行的PaddlePaddle。 @@ -34,7 +34,9 @@ cmake ../protobuf/cmake \ ## 编译PaddlePaddle + cmake参数如下;其中`WITH_C_API`设置为ON,编译输出的output目录会中包含`include`和`lib`目录,其中`include`中包含CAPI的头文件,`lib`中包含一个ARM版本的库。另外,`CMAKE_BUILD_TYPE`设置为`MinSizeRel`可以减小编译的库的大小。 + ``` cmake .. -DWITH_GPU=OFF -DWITH_C_API=ON -DWITH_PYTHON=OFF -DWITH_SWIG_PY=OFF \ -DCMAKE_CXX_COMPILER:FILEPATH=arm-linux-gnueabihf-g++ \ -- GitLab From c9cdc986279606b7016d22c04e784f4077805241 Mon Sep 17 00:00:00 2001 From: liaogang Date: Wed, 17 May 2017 17:45:27 +0800 Subject: [PATCH 0366/3256] detect golang in cmake --- cmake/CMakeDetermineGoCompiler.cmake | 44 ++++++++++++++++++++++++++++ cmake/CMakeGoCompiler.cmake.in | 8 +++++ cmake/CMakeGoInformation.cmake | 7 +++++ cmake/CMakeTestGoCompiler.cmake | 1 + 4 files changed, 60 insertions(+) create mode 100644 cmake/CMakeDetermineGoCompiler.cmake create mode 100644 cmake/CMakeGoCompiler.cmake.in create mode 100644 cmake/CMakeGoInformation.cmake create mode 100644 cmake/CMakeTestGoCompiler.cmake diff --git a/cmake/CMakeDetermineGoCompiler.cmake b/cmake/CMakeDetermineGoCompiler.cmake new file mode 100644 index 00000000000..b3f8fbe271d --- /dev/null +++ b/cmake/CMakeDetermineGoCompiler.cmake @@ -0,0 +1,44 @@ +if(NOT CMAKE_Go_COMPILER) + if(NOT $ENV{GO_COMPILER} STREQUAL "") + get_filename_component(CMAKE_Go_COMPILER_INIT $ENV{GO_COMPILER} PROGRAM PROGRAM_ARGS CMAKE_Go_FLAGS_ENV_INIT) + + if(CMAKE_Go_FLAGS_ENV_INIT) + set(CMAKE_Go_COMPILER_ARG1 "${CMAKE_Go_FLAGS_ENV_INIT}" CACHE STRING "First argument to Go compiler") + endif() + + if(NOT EXISTS ${CMAKE_Go_COMPILER_INIT}) + message(SEND_ERROR "Could not find compiler set in environment variable GO_COMPILER:\n$ENV{GO_COMPILER}.") + endif() + + endif() + + set(Go_BIN_PATH + $ENV{GOPATH} + $ENV{GOROOT} + $ENV{GOROOT}/../bin + $ENV{GO_COMPILER} + /usr/bin + /usr/local/bin + ) + + if(CMAKE_Go_COMPILER_INIT) + set(CMAKE_Go_COMPILER ${CMAKE_Go_COMPILER_INIT} CACHE PATH "Go Compiler") + else() + find_program(CMAKE_Go_COMPILER + NAMES go + PATHS ${Go_BIN_PATH} + ) + EXEC_PROGRAM(${CMAKE_Go_COMPILER} ARGS version OUTPUT_VARIABLE GOLANG_VERSION) + STRING(REGEX MATCH "go[0-9]+.[0-9]+.[0-9]+[ /A-Za-z0-9]*" VERSION "${GOLANG_VERSION}") + message("-- The Golang compiler identification is ${VERSION}") + message("-- Check for working Golang compiler: ${CMAKE_Go_COMPILER}") + endif() + +endif() + +mark_as_advanced(CMAKE_Go_COMPILER) + +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/cmake/CMakeGoCompiler.cmake.in + ${CMAKE_PLATFORM_INFO_DIR}/CMakeGoCompiler.cmake @ONLY) + +set(CMAKE_Go_COMPILER_ENV_VAR "GO_COMPILER") diff --git a/cmake/CMakeGoCompiler.cmake.in b/cmake/CMakeGoCompiler.cmake.in new file mode 100644 index 00000000000..a71f08e0646 --- /dev/null +++ b/cmake/CMakeGoCompiler.cmake.in @@ -0,0 +1,8 @@ +set(CMAKE_Go_COMPILER "@CMAKE_Go_COMPILER@") +set(CMAKE_Go_COMPILER_LOADED 1) + +set(CMAKE_Go_SOURCE_FILE_EXTENSIONS go) +set(CMAKE_Go_LINKER_PREFERENCE 40) +set(CMAKE_Go_OUTPUT_EXTENSION .o) +set(CMAKE_Go_OUTPUT_EXTENSION_REPLACE 1) +set(CMAKE_Go_COMPILER_ENV_VAR "GO_COMPILER") diff --git a/cmake/CMakeGoInformation.cmake b/cmake/CMakeGoInformation.cmake new file mode 100644 index 00000000000..ba51ac93fcd --- /dev/null +++ b/cmake/CMakeGoInformation.cmake @@ -0,0 +1,7 @@ +if(NOT CMAKE_Go_COMPILE_OBJECT) + set(CMAKE_Go_COMPILE_OBJECT "go tool compile -l -N -o ") +endif() + +if(NOT CMAKE_Go_LINK_EXECUTABLE) + set(CMAKE_Go_LINK_EXECUTABLE "go tool link -o ") +endif() diff --git a/cmake/CMakeTestGoCompiler.cmake b/cmake/CMakeTestGoCompiler.cmake new file mode 100644 index 00000000000..b9891b015ba --- /dev/null +++ b/cmake/CMakeTestGoCompiler.cmake @@ -0,0 +1 @@ +set(CMAKE_Go_COMPILER_WORKS 1 CACHE INTERNAL "") -- GitLab From 4c73240d51f27a8f5a6390bcd66fc2f4e48c0854 Mon Sep 17 00:00:00 2001 From: caoying03 Date: Wed, 17 May 2017 17:30:44 +0800 Subject: [PATCH 0367/3256] follow comments. --- demo/semantic_role_labeling/api_train_v2.py | 134 ++++++++++++-------- 1 file changed, 83 insertions(+), 51 deletions(-) diff --git a/demo/semantic_role_labeling/api_train_v2.py b/demo/semantic_role_labeling/api_train_v2.py index f9c1fa80dac..3af636aef58 100644 --- a/demo/semantic_role_labeling/api_train_v2.py +++ b/demo/semantic_role_labeling/api_train_v2.py @@ -6,6 +6,8 @@ import paddle.v2.dataset.conll05 as conll05 import paddle.v2.evaluator as evaluator import paddle.v2 as paddle +logger = logging.getLogger('paddle') + word_dict, verb_dict, label_dict = conll05.get_dict() word_dict_len = len(word_dict) label_dict_len = len(label_dict) @@ -120,19 +122,7 @@ def load_parameter(file_name, h, w): return np.fromfile(f, dtype=np.float32).reshape(h, w) -def test_a_batch(inferer, test_data, tag_dict): - probs = inferer.infer(input=test_data, field='id') - assert len(probs) == sum(len(x[0]) for x in test_data) - for test_sample in test_data: - start_id = 0 - pre_lab = [ - tag_dict[probs[start_id + i]] for i in xrange(len(test_sample[0])) - ] - print pre_lab - start_id += len(test_sample[0]) - - -def main(is_predict=False): +def train(): paddle.init(use_gpu=False, trainer_count=1) # define network topology @@ -189,12 +179,12 @@ def main(is_predict=False): def event_handler(event): if isinstance(event, paddle.event.EndIteration): if event.batch_id % 100 == 0: - print "Pass %d, Batch %d, Cost %f, %s" % ( - event.pass_id, event.batch_id, event.cost, event.metrics) - if event.batch_id % 1000 == 0: + logger.info("Pass %d, Batch %d, Cost %f, %s" % ( + event.pass_id, event.batch_id, event.cost, event.metrics)) + if event.batch_id and event.batch_id % 1000 == 0: result = trainer.test(reader=reader, feeding=feeding) - print "\nTest with Pass %d, Batch %d, %s" % ( - event.pass_id, event.batch_id, result.metrics) + logger.info("\nTest with Pass %d, Batch %d, %s" % + (event.pass_id, event.batch_id, result.metrics)) if isinstance(event, paddle.event.EndPass): # save parameters @@ -202,44 +192,86 @@ def main(is_predict=False): parameters.to_tar(f) result = trainer.test(reader=reader, feeding=feeding) - print "\nTest with Pass %d, %s" % (event.pass_id, result.metrics) - - if not is_predict: - trainer.train( - reader=reader, - event_handler=event_handler, - num_passes=10, - feeding=feeding) - else: - labels_reverse = {} - for (k, v) in label_dict.items(): - labels_reverse[v] = k - test_creator = paddle.dataset.conll05.test() + logger.info("\nTest with Pass %d, %s" % + (event.pass_id, result.metrics)) + + trainer.train( + reader=reader, + event_handler=event_handler, + num_passes=10, + feeding=feeding) - predict = paddle.layer.crf_decoding( - size=label_dict_len, - input=feature_out, - param_attr=paddle.attr.Param(name='crfw')) - test_pass = 0 - with gzip.open('params_pass_%d.tar.gz' % (test_pass)) as f: - parameters = paddle.parameters.Parameters.from_tar(f) - inferer = paddle.inference.Inference( - output_layer=predict, parameters=parameters) +def infer_a_batch(inferer, test_data, word_dict, pred_dict, label_dict): + probs = inferer.infer(input=test_data, field='id') + assert len(probs) == sum(len(x[0]) for x in test_data) - # prepare test data - test_data = [] - test_batch_size = 50 + for idx, test_sample in enumerate(test_data): + start_id = 0 + pred_str = "%s\t" % (pred_dict[test_sample[6][0]]) - for idx, item in enumerate(test_creator()): - test_data.append(item[0:8]) + for w, tag in zip(test_sample[0], + probs[start_id:start_id + len(test_sample[0])]): + pred_str += "%s[%s] " % (word_dict[w], label_dict[tag]) + print(pred_str.strip()) + start_id += len(test_sample[0]) - if idx and (not idx % test_batch_size): - test_a_batch(inferer, test_data, labels_reverse) - test_data = [] - test_a_batch(inferer, test_data, labels_reverse) - test_data = [] + +def infer(): + label_dict_reverse = dict((value, key) + for key, value in label_dict.iteritems()) + word_dict_reverse = dict((value, key) + for key, value in word_dict.iteritems()) + pred_dict_reverse = dict((value, key) + for key, value in verb_dict.iteritems()) + + test_creator = paddle.dataset.conll05.test() + + paddle.init(use_gpu=False, trainer_count=1) + + # define network topology + feature_out = db_lstm() + predict = paddle.layer.crf_decoding( + size=label_dict_len, + input=feature_out, + param_attr=paddle.attr.Param(name='crfw')) + + test_pass = 0 + with gzip.open('params_pass_%d.tar.gz' % (test_pass)) as f: + parameters = paddle.parameters.Parameters.from_tar(f) + inferer = paddle.inference.Inference( + output_layer=predict, parameters=parameters) + + # prepare test data + test_data = [] + test_batch_size = 50 + + for idx, item in enumerate(test_creator()): + test_data.append(item[0:8]) + + if idx and (not idx % test_batch_size): + infer_a_batch( + inferer, + test_data, + word_dict_reverse, + pred_dict_reverse, + label_dict_reverse, ) + test_data = [] + infer_a_batch( + inferer, + test_data, + word_dict_reverse, + pred_dict_reverse, + label_dict_reverse, ) + test_data = [] + + +def main(is_inferring=False): + if is_inferring: + infer() + else: + train() if __name__ == '__main__': - main(is_predict=False) + main(is_inferring=False) -- GitLab From 5005ca73ee646c40789178374fc7203e64664b70 Mon Sep 17 00:00:00 2001 From: Liu Yiqun Date: Wed, 17 May 2017 19:43:15 +0800 Subject: [PATCH 0368/3256] Add document, cross-compiling for Raspberry Pi; and refine document, cross-compiling for Android. --- .../cross_compiling_for_android_cn.md | 10 +-- .../cross_compiling_for_raspberry_cn.md | 86 +++++++++++-------- 2 files changed, 56 insertions(+), 40 deletions(-) diff --git a/doc/howto/cross_compiling/cross_compiling_for_android_cn.md b/doc/howto/cross_compiling/cross_compiling_for_android_cn.md index 81cefb46234..a51ff1ed1fa 100644 --- a/doc/howto/cross_compiling/cross_compiling_for_android_cn.md +++ b/doc/howto/cross_compiling/cross_compiling_for_android_cn.md @@ -35,7 +35,7 @@ CMake系统对交叉编译提供了支持[cmake-toolchains](https://cmake.org/cm Android平台可选配置参数: -- `ANDROID_STANDALONE_TOOLCHAIN`,独立工具链所在的绝对路径,或者相对构建目录的相对路径。PaddlePaddle的CMake系统将根据该值自动推导和设置需要使用的交叉编译器、sysroot、以及Android API级别;否则,用户需要在cmake时自动设置这些值。无默认值。 +- `ANDROID_STANDALONE_TOOLCHAIN`,独立工具链所在的绝对路径,或者相对于构建目录的相对路径。PaddlePaddle的CMake系统将根据该值自动推导和设置需要使用的交叉编译器、sysroot、以及Android API级别;否则,用户需要在cmake时手动设置这些值。无默认值。 - `ANDROID_ABI`,目标架构ABI。目前只支持`armeabi-v7a`,默认值为`armeabi-v7a`。 - `ANDROID_NATIVE_API_LEVEL`,工具链的Android API级别。若没有显式设置,PaddlePaddle将根据`ANDROID_STANDALONE_TOOLCHAIN`的值自动推导得到。 - `ANROID_ARM_MODE`,是否使用ARM模式。可设置`ON/OFF`,默认值为`ON`。 @@ -43,7 +43,7 @@ Android平台可选配置参数: 其他配置参数: -- `HOST_C/CXX_COMPILER`,CMake编译的宿主平台的C/C++编译器。在编译宿主机版protoc可执行文件和目标机版OpenBLAS时需要用到。 +- `HOST_C/CXX_COMPILER`,宿主机的C/C++编译器。在编译宿主机版protoc可执行文件和目标机版OpenBLAS库时需要用到。默认设置成环境变量`CC`的值;若环境变量`CC`没有设置,则设置成`cc`编译器。 一种常用的cmake配置如下: @@ -59,11 +59,11 @@ cmake -DCMAKE_SYSTEM_NAME=Android \ .. ``` -用户还可根据自己的需求设置其他编译参数。比如希望最小化生成的库的大小,可以设置`CMAKE_BUILD_TYPE`为`MinSizeRel`;若希望最快的执行速度,则可设置`CMAKE_BUILD_TYPE`为`Release`。 +用户还可根据自己的需求设置其他编译参数。比如希望最小化生成的库的大小,可以设置`CMAKE_BUILD_TYPE`为`MinSizeRel`;若希望最快的执行速度,则可设置`CMAKE_BUILD_TYPE`为`Release`。亦可以通过手动设置`CMAKE_C/CXX_FLAGS_MINSIZEREL/RELEASE`来影响PaddlePaddle的编译过程。 ## 编译和安装 -CMake配置完成后,执行以下命令,PaddlePaddle将自动下载和编译所有所需要的第三方依赖库、编译和安装PaddlePaddle预测库。 +CMake配置完成后,执行以下命令,PaddlePaddle将自动下载和编译所有第三方依赖库、编译和安装PaddlePaddle预测库。 ```bash make @@ -72,5 +72,5 @@ make install 注意:如果你曾经在源码目录下编译过其他平台的PaddlePaddle库,请先使用`rm -rf`命令删除`third_party`目录和`build`目录,以确保所有的第三方依赖库和PaddlePaddle代码都是针对新的CMake配置重新编译的。 -执行完安装命令后,`your/path/to/install`目录会中包含`include`和`lib`目录,其中`include`中包含C-API的头文件,`lib`中包含一个Android版本的库。自此,PaddlePaddle的已经安装完成,用户可将`your/path/to/install`目录下的生成文件用于深度学习相关Android App中,调用方法见C-API文档。 +执行完安装命令后,`your/path/to/install`目录中会包含`include`和`lib`目录,其中`include`中包含C-API的头文件,`lib`中包含一个Android版本的库。自此,PaddlePaddle的已经安装完成,用户可将`your/path/to/install`目录下的生成文件用于深度学习相关Android App中,调用方法见C-API文档。 diff --git a/doc/howto/cross_compiling/cross_compiling_for_raspberry_cn.md b/doc/howto/cross_compiling/cross_compiling_for_raspberry_cn.md index d0239ec4d01..0f314c311aa 100644 --- a/doc/howto/cross_compiling/cross_compiling_for_raspberry_cn.md +++ b/doc/howto/cross_compiling/cross_compiling_for_raspberry_cn.md @@ -1,49 +1,65 @@ -# 如何构建Raspberry Pi下运行的PaddlePaddle +# 构建Raspberry Pi平台上的PaddlePaddle库 -这里考虑的是交叉编译方式,即在Linux-x86环境下构建Raspberry pi下可运行的PaddlePaddle。 +对于Rasspberry Pi系统,用户可通过ssh等方式登录到Raspberry Pi系统上,按照[源码编译PaddlePaddle](http://www.paddlepaddle.org/doc_cn/getstarted/build_and_install/cmake/build_from_source_cn.html)相关文档所述,直接编译Raspberry Pi平台上适用的PaddlePaddle库。 -## 下载交叉编译环境 -``` -git clone https://github.com/raspberrypi/tools -``` -如果host是x86-64环境,选用`arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian-x64`下的作为编译工具。注意,需要系统glibc支持2.14以上。 +用户也可以在自己熟悉的开发平台上,通过交叉编译的方式来编译。这篇文档将以Linux x86-64平台为例,介绍交叉编译Raspberry Pi平台上适用的PaddlePaddle的方法和步骤。 +## 准备交叉编译环境 -## 编译第三方库 -cmake编译PaddlePaddle时候会自动下载编译依赖的第三方库,不过openblas和protobuf最好还是在编译PaddlePaddle之前先编译好,这样可以保证编译PaddlePaddle的时候更加顺畅。 +从源码交叉编译PaddlePaddle,用户需要提前准备好交叉编译环境。用户可自行前往[github](https://github.com/raspberrypi/tools)下载Raspberry Pi平台使用的C/C++交叉编译工具链,也可通过以下命令获取: -### 编译OpenBLAS -``` -git clone https://github.com/xianyi/OpenBLAS.git -make TARGET=ARMV7 HOSTCC=gcc CC=arm-linux-gnueabihf-gcc NOFORTRAN=1 USE_THREAD=0 +```bash +git clone https://github.com/raspberrypi/tools.git ``` -### 编译protobuf +该github仓库中包含若干个预编译好的、针对不同平台的编译工具。宿主机是Linux x86-64环境,则需选用`arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian-x64`下的作为编译工具,所使用的编译器为arm-linux-gnueabihf-gcc 4.8.3。 + +注意,该编译工具链需要系统glibc支持2.14以上。 + +## 配置交叉编译参数 + +CMake系统对交叉编译提供了支持[cmake-toolchains](https://cmake.org/cmake/help/v3.0/manual/cmake-toolchains.7.html#cross-compiling)。为了简化cmake配置,PaddlePaddle为交叉编译提供了工具链配置文档[cmake/cross_compiling/raspberry_pi.cmake](https://github.com/PaddlePaddle/Paddle/blob/develop/cmake/cross_compiling/raspberry_pi.cmake),以提供一些默认的编译器和编译参数相关配置。 + +交叉编译Raspberry Pi版本PaddlePaddle库时,有一些必须配置的参数: + +- `CMAKE_SYSTEM_NAME`,CMake编译的目标平台,必须配置为`RPi`。在设置`CMAKE_SYSTEM_NAME=RPi`后,PaddlePaddle的CMake系统才认为在是在交叉编译Raspberry Pi系统的版本,并自动编译宿主机版protoc可执行文件、目标机版protobuf库、以及目标机版OpenBLAS库。 + +Raspberry Pi平台可选配置参数: + +- `RPI_TOOLCHAIN`,编译工具链所在的绝对路径,或者相对于构建目录的相对路径。PaddlePaddle的CMake系统将根据该值自动设置需要使用的交叉编译器;否则,用户需要在cmake时手动设置这些值。无默认值。 +- `RPI_ARM_NEON`,是否使用NEON指令。目前必须设置成`ON`,默认值为`ON`。 + +其他配置参数: + +- `HOST_C/CXX_COMPILER`,宿主机的C/C++编译器。在编译宿主机版protoc可执行文件和目标机版OpenBLAS库时需要用到。默认设置成环境变量`CC`的值;若环境变量`CC`没有设置,则设置成`cc`编译器。 + +cmake参数如下; + ``` -git clone https://github.com/google/protobuf.git -git checkout 9f75c5aa851cd877fb0d93ccc31b8567a6706546 -cmake ../protobuf/cmake \ --Dprotobuf_BUILD_TESTS=OFF \ --DCMAKE_CXX_COMPILER=arm-linux-gnueabihf-g++ \ --DCMAKE_C_COMPILER=arm-linux-gnueabihf-gcc \ --DCMAKE_POSITION_INDEPENDENT_CODE=ON \ --DCMAKE_BUILD_TYPE=Release \ --DCMAKE_INSTALL_LIBDIR=lib +cmake -DCMAKE_SYSTEM_NAME=RPi \ + -DRPI_TOOLCHAIN=your/path/to/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian-x64 \ + -DRPI_ARM_NEON=ON \ + -DCMAKE_INSTALL_PREFIX=your/path/to/install \ + -DWITH_GPU=OFF \ + -DWITH_C_API=ON \ + -DWITH_PYTHON=OFF \ + -DWITH_SWIG_PY=OFF \ + .. ``` -注意:这样编译出来的`libprotobuf.a`和`protoc`都是ARM版本的,而我们需要的是一个x86-64版本的`protoc`,所以需要用host gcc再编译一遍protobuf然后使用其中的`protoc`。 +用户还可根据自己的需求设置其他编译参数。比如希望最小化生成的库的大小,可以设置`CMAKE_BUILD_TYPE`为`MinSizeRel`;若希望最快的执行速度,则可设置`CMAKE_BUILD_TYPE`为`Release`。亦可以通过手动设置`CMAKE_C/CXX_FLAGS_MINSIZEREL/RELEASE`来影响PaddlePaddle的编译过程。 -## 编译PaddlePaddle +## 编译和安装 -cmake参数如下;其中`WITH_C_API`设置为ON,编译输出的output目录会中包含`include`和`lib`目录,其中`include`中包含CAPI的头文件,`lib`中包含一个ARM版本的库。另外,`CMAKE_BUILD_TYPE`设置为`MinSizeRel`可以减小编译的库的大小。 +CMake配置完成后,执行以下命令,PaddlePaddle将自动下载和编译所有第三方依赖库、编译和安装PaddlePaddle。 +```bash +make +make install ``` -cmake .. -DWITH_GPU=OFF -DWITH_C_API=ON -DWITH_PYTHON=OFF -DWITH_SWIG_PY=OFF \ --DCMAKE_CXX_COMPILER:FILEPATH=arm-linux-gnueabihf-g++ \ --DCMAKE_C_COMPILER:FILEPATH=arm-linux-gnueabihf-gcc \ --DCMAKE_C_FLAGS="-mfpu=neon" \ --DCMAKE_CXX_FLAGS="-mfpu=neon" \ --DOPENBLAS_ROOT=openblas \ --DCMAKE_PREFIX_PATH=protobuf \ --DCMAKE_BUILD_TYPE=MinSizeRel -``` + +注意:如果你曾经在源码目录下编译过其他平台的PaddlePaddle库,请先使用`rm -rf`命令删除`third_party`目录和`build`目录,以确保所有的第三方依赖库和PaddlePaddle代码都是针对新的CMake配置重新编译的。 + +执行完安装命令后,由于上一步cmake配置中`WITH_C_API`设置为`ON`,`your/path/to/install`目录中会包含`include`和`lib`目录,其中`include`中包含C-API的头文件,`lib`中包含一个Raspberry Pi版本的库。 + +更多的编译配置见[源码编译PaddlePaddle](http://www.paddlepaddle.org/doc_cn/getstarted/build_and_install/cmake/build_from_source_cn.html)相关文档。 -- GitLab From b1d4ec3772174d0fef3fe4c26033a314c3e8884d Mon Sep 17 00:00:00 2001 From: Liu Yiqun Date: Wed, 17 May 2017 19:54:36 +0800 Subject: [PATCH 0369/3256] Format the documents. --- doc/howto/cross_compiling/cross_compiling_for_android_cn.md | 1 - doc/howto/cross_compiling/cross_compiling_for_raspberry_cn.md | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/doc/howto/cross_compiling/cross_compiling_for_android_cn.md b/doc/howto/cross_compiling/cross_compiling_for_android_cn.md index a51ff1ed1fa..90dc84718c9 100644 --- a/doc/howto/cross_compiling/cross_compiling_for_android_cn.md +++ b/doc/howto/cross_compiling/cross_compiling_for_android_cn.md @@ -73,4 +73,3 @@ make install 注意:如果你曾经在源码目录下编译过其他平台的PaddlePaddle库,请先使用`rm -rf`命令删除`third_party`目录和`build`目录,以确保所有的第三方依赖库和PaddlePaddle代码都是针对新的CMake配置重新编译的。 执行完安装命令后,`your/path/to/install`目录中会包含`include`和`lib`目录,其中`include`中包含C-API的头文件,`lib`中包含一个Android版本的库。自此,PaddlePaddle的已经安装完成,用户可将`your/path/to/install`目录下的生成文件用于深度学习相关Android App中,调用方法见C-API文档。 - diff --git a/doc/howto/cross_compiling/cross_compiling_for_raspberry_cn.md b/doc/howto/cross_compiling/cross_compiling_for_raspberry_cn.md index 0f314c311aa..085b5dda161 100644 --- a/doc/howto/cross_compiling/cross_compiling_for_raspberry_cn.md +++ b/doc/howto/cross_compiling/cross_compiling_for_raspberry_cn.md @@ -1,6 +1,6 @@ # 构建Raspberry Pi平台上的PaddlePaddle库 -对于Rasspberry Pi系统,用户可通过ssh等方式登录到Raspberry Pi系统上,按照[源码编译PaddlePaddle](http://www.paddlepaddle.org/doc_cn/getstarted/build_and_install/cmake/build_from_source_cn.html)相关文档所述,直接编译Raspberry Pi平台上适用的PaddlePaddle库。 +对于Rasspberry Pi系统,用户可通过ssh等方式登录到Raspberry Pi系统上,按照[源码编译PaddlePaddle](http://www.paddlepaddle.org/doc_cn/getstarted/build_and_install/cmake/build_from_source_cn.html)相关文档所述,直接编译Raspberry Pi平台上适用的PaddlePaddle库。 用户也可以在自己熟悉的开发平台上,通过交叉编译的方式来编译。这篇文档将以Linux x86-64平台为例,介绍交叉编译Raspberry Pi平台上适用的PaddlePaddle的方法和步骤。 -- GitLab From 4dcb9f1ca1995011b8a0c1f65663e756ff95e854 Mon Sep 17 00:00:00 2001 From: liaogang Date: Wed, 17 May 2017 20:44:29 +0800 Subject: [PATCH 0370/3256] add go_xxx to simplify cmake --- CMakeLists.txt | 3 +- cmake/generic.cmake | 97 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 cmake/generic.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index fc85f83b94f..79210d04364 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,7 +19,7 @@ set(PROJ_ROOT ${CMAKE_CURRENT_SOURCE_DIR}) include(system) -project(paddle CXX C) +project(paddle CXX C Go) find_package(Sphinx) if(NOT CMAKE_CROSSCOMPILING) @@ -92,6 +92,7 @@ include(external/swig) # download, build, install swig include(external/warpctc) # download, build, install warpctc include(external/any) # download libn::any +include(generic) # simplify cmake module include(package) # set paddle packages include(cpplint) # set paddle c++ style include(ccache) # set ccache for compilation diff --git a/cmake/generic.cmake b/cmake/generic.cmake new file mode 100644 index 00000000000..063a09b63e0 --- /dev/null +++ b/cmake/generic.cmake @@ -0,0 +1,97 @@ +# 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. + + +# To simplify the build process of PaddlePaddle, we defined couple of +# fundamental abstractions, e.g., how to build library, binary and +# test in C++, CUDA and Go. +# +# ------------------------------------------- +# C++ CUDA C++ Go +# ------------------------------------------- +# cc_library nv_library go_library +# cc_binary nv_binary go_binary +# cc_test nv_test go_test +# ------------------------------------------- +# +# cmake_parse_arguments can help us to achieve this goal. +# https://cmake.org/cmake/help/v3.0/module/CMakeParseArguments.html + + +set(GOPATH "${CMAKE_CURRENT_BINARY_DIR}/go") +file(MAKE_DIRECTORY ${GOPATH}) + +# Because api.go defines a GO wrapper to ops and tensor, it depends on +# both. This implies that if any of tensor.{h,cc}, ops.{h,cu}, or +# api.go is changed, api need to be re-built. +# go_library(api +# SRCS +# api.go +# DEPS +# tensor # Because ops depend on tensor, this line is optional. +# ops) +function(go_library TARGET_NAME) + set(options OPTIONAL) + set(oneValueArgs "") + set(multiValueArgs SRCS DEPS) + cmake_parse_arguments(go_library "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + if (${go_library_OPTIONAL} STREQUAL "SHARED") + set(BUILD_MODE "-buildmode=c-shared") + if(APPLE) + set(LIB_NAME "lib${TARGET_NAME}.dylib") + else() + set(LIB_NAME "lib${TARGET_NAME}.so") + endif() + else() + set(BUILD_MODE "-buildmode=c-archive") + set(LIB_NAME "lib${TARGET_NAME}.a") + endif() + add_custom_command(OUTPUT ${TARGET_NAME}_timestamp + COMMAND env GOPATH=${GOPATH} ${CMAKE_Go_COMPILER} build ${BUILD_MODE} + -o "${CMAKE_CURRENT_BINARY_DIR}/${LIB_NAME}" + ${go_library_SRCS} + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}) + add_custom_target(${TARGET_NAME}_lib ALL DEPENDS ${TARGET_NAME}_timestamp ${go_library_DEPS}) + add_library(${TARGET_NAME} STATIC IMPORTED) + set_target_properties(${TARGET_NAME} PROPERTIES + IMPORTED_LOCATION ${CMAKE_CURRENT_BINARY_DIR}/${LIB_NAME}) +endfunction(go_library) + +function(go_binary TARGET_NAME) + set(options OPTIONAL) + set(oneValueArgs "") + set(multiValueArgs SRCS DEPS) + cmake_parse_arguments(go_binary "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + add_custom_command(OUTPUT ${TARGET_NAME}_timestamp + COMMAND env GOPATH=${GOPATH} ${CMAKE_Go_COMPILER} build + -o "${CMAKE_CURRENT_BINARY_DIR}/${TARGET_NAME}" + ${go_library_SRCS} + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}) + add_custom_target(${TARGET_NAME} ALL DEPENDS ${TARGET_NAME}_timestamp ${go_binary_DEPS}) + install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/${TARGET_NAME} DESTINATION bin) +endfunction(go_binary) + +function(go_test TARGET_NAME) + set(options OPTIONAL) + set(oneValueArgs "") + set(multiValueArgs SRCS DEPS) + cmake_parse_arguments(go_test "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + add_custom_command(OUTPUT ${TARGET_NAME}_timestamp + COMMAND env GOPATH=${GOPATH} ${CMAKE_Go_COMPILER} test + -c -o "${CMAKE_CURRENT_BINARY_DIR}/${TARGET_NAME}" + ${go_test_SRCS} + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}) + add_custom_target(${TARGET_NAME} ALL DEPENDS ${TARGET_NAME}_timestamp ${go_test_DEPS}) + add_test(${TARGET_NAME} ${CMAKE_CURRENT_BINARY_DIR}/${TARGET_NAME}) +endfunction(go_test) -- GitLab From e254c7a799db1b309d87da9fce5bc1c0d0e5b718 Mon Sep 17 00:00:00 2001 From: liaogang Date: Wed, 17 May 2017 20:44:43 +0800 Subject: [PATCH 0371/3256] add go unit test --- paddle/CMakeLists.txt | 4 ++++ paddle/go/CMakeLists.txt | 13 +++++++++++++ paddle/go/adder.go | 10 ++++++++++ paddle/go/cgo_test.cc | 7 +++++++ 4 files changed, 34 insertions(+) create mode 100644 paddle/go/CMakeLists.txt create mode 100644 paddle/go/adder.go create mode 100644 paddle/go/cgo_test.cc diff --git a/paddle/CMakeLists.txt b/paddle/CMakeLists.txt index 76995549097..694ebb9ba04 100644 --- a/paddle/CMakeLists.txt +++ b/paddle/CMakeLists.txt @@ -9,6 +9,10 @@ add_subdirectory(pserver) add_subdirectory(trainer) add_subdirectory(scripts) +if(${CMAKE_Go_COMPILER}) + add_subdirectory(go) +endif() + find_package(Boost QUIET) if(Boost_FOUND) diff --git a/paddle/go/CMakeLists.txt b/paddle/go/CMakeLists.txt new file mode 100644 index 00000000000..482c948a516 --- /dev/null +++ b/paddle/go/CMakeLists.txt @@ -0,0 +1,13 @@ +include_directories(${CMAKE_CURRENT_BINARY_DIR}) + +go_library(adder SRCS adder.go) + +# cc_test(cgo_test +# SRCS +# cgo_test.cc +# DEPS +# adder) +add_executable(cgo_test cgo_test.cc) +add_dependencies(cgo_test adder) +target_link_libraries(cgo_test ${GTEST_LIBRARIES} ${GTEST_MAIN_LIBRARIES} adder) +add_test(cgo_test cgo_test) diff --git a/paddle/go/adder.go b/paddle/go/adder.go new file mode 100644 index 00000000000..e14f40fd9fe --- /dev/null +++ b/paddle/go/adder.go @@ -0,0 +1,10 @@ +package main + +import "C" + +//export GoAdder +func GoAdder(x, y int) int { + return x + y +} + +func main() {} // Required but ignored diff --git a/paddle/go/cgo_test.cc b/paddle/go/cgo_test.cc new file mode 100644 index 00000000000..8d89a92c08a --- /dev/null +++ b/paddle/go/cgo_test.cc @@ -0,0 +1,7 @@ +#include "libadder.h" +#include +#include "gtest/gtest.h" + +TEST(Cgo, Invoke) { + EXPECT_EQ(GoAdder(30, 12), 42); +} -- GitLab From 4d5417b55d69df5268ff32d6d4dc8de0a4208f53 Mon Sep 17 00:00:00 2001 From: liaogang Date: Wed, 17 May 2017 20:57:30 +0800 Subject: [PATCH 0372/3256] modify travis --- .travis.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 387367a2305..d1e5080f68e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,7 @@ -language: cpp +language: go +go: + - 1.8 + cache: directories: - $HOME/third_party -- GitLab From 447145207608f2825e8d4dd0351a9167b0893a9f Mon Sep 17 00:00:00 2001 From: liaogang Date: Wed, 17 May 2017 21:15:02 +0800 Subject: [PATCH 0373/3256] update travis --- .travis.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index d1e5080f68e..387367a2305 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,4 @@ -language: go -go: - - 1.8 - +language: cpp cache: directories: - $HOME/third_party -- GitLab From af065196400ca0254e61df3916888f4341306e97 Mon Sep 17 00:00:00 2001 From: liaogang Date: Wed, 17 May 2017 21:21:45 +0800 Subject: [PATCH 0374/3256] clang-format test.cc --- paddle/go/cgo_test.cc | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/paddle/go/cgo_test.cc b/paddle/go/cgo_test.cc index 8d89a92c08a..64efa606fff 100644 --- a/paddle/go/cgo_test.cc +++ b/paddle/go/cgo_test.cc @@ -1,7 +1,5 @@ -#include "libadder.h" #include #include "gtest/gtest.h" +#include "libadder.h" -TEST(Cgo, Invoke) { - EXPECT_EQ(GoAdder(30, 12), 42); -} +TEST(Cgo, Invoke) { EXPECT_EQ(GoAdder(30, 12), 42); } -- GitLab From a7edafc4bf653c05444939c5fd6dc5482f6a51cb Mon Sep 17 00:00:00 2001 From: liaogang Date: Thu, 18 May 2017 00:34:43 +0800 Subject: [PATCH 0375/3256] add cc_test --- cmake/CMakeDetermineGoCompiler.cmake | 10 ++++++---- cmake/generic.cmake | 5 +++-- paddle/CMakeLists.txt | 2 +- paddle/go/CMakeLists.txt | 14 +++++--------- 4 files changed, 15 insertions(+), 16 deletions(-) diff --git a/cmake/CMakeDetermineGoCompiler.cmake b/cmake/CMakeDetermineGoCompiler.cmake index b3f8fbe271d..9196880c0ec 100644 --- a/cmake/CMakeDetermineGoCompiler.cmake +++ b/cmake/CMakeDetermineGoCompiler.cmake @@ -28,10 +28,12 @@ if(NOT CMAKE_Go_COMPILER) NAMES go PATHS ${Go_BIN_PATH} ) - EXEC_PROGRAM(${CMAKE_Go_COMPILER} ARGS version OUTPUT_VARIABLE GOLANG_VERSION) - STRING(REGEX MATCH "go[0-9]+.[0-9]+.[0-9]+[ /A-Za-z0-9]*" VERSION "${GOLANG_VERSION}") - message("-- The Golang compiler identification is ${VERSION}") - message("-- Check for working Golang compiler: ${CMAKE_Go_COMPILER}") + if(CMAKE_Go_COMPILER) + EXEC_PROGRAM(${CMAKE_Go_COMPILER} ARGS version OUTPUT_VARIABLE GOLANG_VERSION) + STRING(REGEX MATCH "go[0-9]+[.0-9]*[ /A-Za-z0-9]*" VERSION "${GOLANG_VERSION}") + message("-- The Golang compiler identification is ${VERSION}") + message("-- Check for working Golang compiler: ${CMAKE_Go_COMPILER}") + endif() endif() endif() diff --git a/cmake/generic.cmake b/cmake/generic.cmake index 555faff499e..e4c1e2b41a1 100644 --- a/cmake/generic.cmake +++ b/cmake/generic.cmake @@ -163,8 +163,9 @@ function(go_library TARGET_NAME) WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}) add_custom_target(${TARGET_NAME}_lib ALL DEPENDS ${TARGET_NAME}_timestamp ${go_library_DEPS}) add_library(${TARGET_NAME} STATIC IMPORTED) - set_target_properties(${TARGET_NAME} PROPERTIES - IMPORTED_LOCATION ${CMAKE_CURRENT_BINARY_DIR}/${LIB_NAME}) + set_property(TARGET ${TARGET_NAME} PROPERTY + IMPORTED_LOCATION "${CMAKE_CURRENT_BINARY_DIR}/${LIB_NAME}") + add_dependencies(${TARGET_NAME} ${TARGET_NAME}_lib) endfunction(go_library) function(go_binary TARGET_NAME) diff --git a/paddle/CMakeLists.txt b/paddle/CMakeLists.txt index 694ebb9ba04..cf31b4a3429 100644 --- a/paddle/CMakeLists.txt +++ b/paddle/CMakeLists.txt @@ -9,7 +9,7 @@ add_subdirectory(pserver) add_subdirectory(trainer) add_subdirectory(scripts) -if(${CMAKE_Go_COMPILER}) +if(CMAKE_Go_COMPILER) add_subdirectory(go) endif() diff --git a/paddle/go/CMakeLists.txt b/paddle/go/CMakeLists.txt index 482c948a516..20f14769433 100644 --- a/paddle/go/CMakeLists.txt +++ b/paddle/go/CMakeLists.txt @@ -2,12 +2,8 @@ include_directories(${CMAKE_CURRENT_BINARY_DIR}) go_library(adder SRCS adder.go) -# cc_test(cgo_test -# SRCS -# cgo_test.cc -# DEPS -# adder) -add_executable(cgo_test cgo_test.cc) -add_dependencies(cgo_test adder) -target_link_libraries(cgo_test ${GTEST_LIBRARIES} ${GTEST_MAIN_LIBRARIES} adder) -add_test(cgo_test cgo_test) +cc_test(cgo_test + SRCS + cgo_test.cc + DEPS + adder) -- GitLab From 589cea1f920ae2aae89b315ef24f3da9875e5e63 Mon Sep 17 00:00:00 2001 From: liaogang Date: Thu, 18 May 2017 00:38:11 +0800 Subject: [PATCH 0376/3256] add $ENV{GOROOT}/bin --- cmake/CMakeDetermineGoCompiler.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/CMakeDetermineGoCompiler.cmake b/cmake/CMakeDetermineGoCompiler.cmake index 9196880c0ec..abf0a00c5e9 100644 --- a/cmake/CMakeDetermineGoCompiler.cmake +++ b/cmake/CMakeDetermineGoCompiler.cmake @@ -15,7 +15,7 @@ if(NOT CMAKE_Go_COMPILER) set(Go_BIN_PATH $ENV{GOPATH} $ENV{GOROOT} - $ENV{GOROOT}/../bin + $ENV{GOROOT}/bin $ENV{GO_COMPILER} /usr/bin /usr/local/bin -- GitLab From 071c65f9cc4d3db168397af9ce4f0bbf5d752552 Mon Sep 17 00:00:00 2001 From: liaogang Date: Thu, 18 May 2017 00:46:11 +0800 Subject: [PATCH 0377/3256] add go extern --- cmake/generic.cmake | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/cmake/generic.cmake b/cmake/generic.cmake index e4c1e2b41a1..90ec9532e5f 100644 --- a/cmake/generic.cmake +++ b/cmake/generic.cmake @@ -195,3 +195,10 @@ function(go_test TARGET_NAME) add_custom_target(${TARGET_NAME} ALL DEPENDS ${TARGET_NAME}_timestamp ${go_test_DEPS}) add_test(${TARGET_NAME} ${CMAKE_CURRENT_BINARY_DIR}/${TARGET_NAME}) endfunction(go_test) + +# go_extern will download extern go project. +# go_extern(target_name extern_source) +# go_extern(go_redis github.com/hoisie/redis) +function(go_extern TARGET_NAME) + add_custom_target(${TARGET_NAME} env GOPATH=${GOPATH} ${CMAKE_Go_COMPILER} get ${ARGN}) +endfunction(go_extern) -- GitLab From 7ee280eb7f6b125ba9587f6259b14eac7fc714cc Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Wed, 17 May 2017 15:00:53 -0400 Subject: [PATCH 0378/3256] change folder structure --- paddle/go/{pserver/lib/client => cclient}/.gitignore | 0 paddle/go/{pserver/lib => cclient}/CMakeLists.txt | 5 +++-- paddle/go/{pserver/lib/client/main.go => cclient/cclient.go} | 0 .../lib => cclient}/cmake/CMakeDetermineGoCompiler.cmake | 0 .../{pserver/lib => cclient}/cmake/CMakeGoCompiler.cmake.in | 0 .../{pserver/lib => cclient}/cmake/CMakeGoInformation.cmake | 0 .../{pserver/lib => cclient}/cmake/CMakeTestGoCompiler.cmake | 0 paddle/go/{pserver/lib => cclient}/cmake/flags.cmake | 0 paddle/go/{pserver/lib => cclient}/cmake/golang.cmake | 0 .../go/{pserver/lib/client => cclient}/test/CMakeLists.txt | 4 ++-- paddle/go/{pserver/lib/client => cclient}/test/main.c | 0 paddle/go/pserver/lib/client/CMakeLists.txt | 5 ----- 12 files changed, 5 insertions(+), 9 deletions(-) rename paddle/go/{pserver/lib/client => cclient}/.gitignore (100%) rename paddle/go/{pserver/lib => cclient}/CMakeLists.txt (86%) rename paddle/go/{pserver/lib/client/main.go => cclient/cclient.go} (100%) rename paddle/go/{pserver/lib => cclient}/cmake/CMakeDetermineGoCompiler.cmake (100%) rename paddle/go/{pserver/lib => cclient}/cmake/CMakeGoCompiler.cmake.in (100%) rename paddle/go/{pserver/lib => cclient}/cmake/CMakeGoInformation.cmake (100%) rename paddle/go/{pserver/lib => cclient}/cmake/CMakeTestGoCompiler.cmake (100%) rename paddle/go/{pserver/lib => cclient}/cmake/flags.cmake (100%) rename paddle/go/{pserver/lib => cclient}/cmake/golang.cmake (100%) rename paddle/go/{pserver/lib/client => cclient}/test/CMakeLists.txt (68%) rename paddle/go/{pserver/lib/client => cclient}/test/main.c (100%) delete mode 100644 paddle/go/pserver/lib/client/CMakeLists.txt diff --git a/paddle/go/pserver/lib/client/.gitignore b/paddle/go/cclient/.gitignore similarity index 100% rename from paddle/go/pserver/lib/client/.gitignore rename to paddle/go/cclient/.gitignore diff --git a/paddle/go/pserver/lib/CMakeLists.txt b/paddle/go/cclient/CMakeLists.txt similarity index 86% rename from paddle/go/pserver/lib/CMakeLists.txt rename to paddle/go/cclient/CMakeLists.txt index eb98f4b0b67..29a2089fb10 100644 --- a/paddle/go/pserver/lib/CMakeLists.txt +++ b/paddle/go/cclient/CMakeLists.txt @@ -10,7 +10,6 @@ else() # find cmake directory modules get_filename_component(PARENT_DIR ${PARENT_DIR} DIRECTORY) get_filename_component(PARENT_DIR ${PARENT_DIR} DIRECTORY) - get_filename_component(PARENT_DIR ${PARENT_DIR} DIRECTORY) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${PARENT_DIR}/cmake") @@ -30,4 +29,6 @@ project(cxx_go CXX C Go) include(cmake/golang.cmake) include(cmake/flags.cmake) -add_subdirectory(client) +ExternalGoProject_Add(pserver github.com/PaddlePaddle/Paddle/paddle/go/pserver) +add_go_library(client STATIC pserver) +add_subdirectory(test) diff --git a/paddle/go/pserver/lib/client/main.go b/paddle/go/cclient/cclient.go similarity index 100% rename from paddle/go/pserver/lib/client/main.go rename to paddle/go/cclient/cclient.go diff --git a/paddle/go/pserver/lib/cmake/CMakeDetermineGoCompiler.cmake b/paddle/go/cclient/cmake/CMakeDetermineGoCompiler.cmake similarity index 100% rename from paddle/go/pserver/lib/cmake/CMakeDetermineGoCompiler.cmake rename to paddle/go/cclient/cmake/CMakeDetermineGoCompiler.cmake diff --git a/paddle/go/pserver/lib/cmake/CMakeGoCompiler.cmake.in b/paddle/go/cclient/cmake/CMakeGoCompiler.cmake.in similarity index 100% rename from paddle/go/pserver/lib/cmake/CMakeGoCompiler.cmake.in rename to paddle/go/cclient/cmake/CMakeGoCompiler.cmake.in diff --git a/paddle/go/pserver/lib/cmake/CMakeGoInformation.cmake b/paddle/go/cclient/cmake/CMakeGoInformation.cmake similarity index 100% rename from paddle/go/pserver/lib/cmake/CMakeGoInformation.cmake rename to paddle/go/cclient/cmake/CMakeGoInformation.cmake diff --git a/paddle/go/pserver/lib/cmake/CMakeTestGoCompiler.cmake b/paddle/go/cclient/cmake/CMakeTestGoCompiler.cmake similarity index 100% rename from paddle/go/pserver/lib/cmake/CMakeTestGoCompiler.cmake rename to paddle/go/cclient/cmake/CMakeTestGoCompiler.cmake diff --git a/paddle/go/pserver/lib/cmake/flags.cmake b/paddle/go/cclient/cmake/flags.cmake similarity index 100% rename from paddle/go/pserver/lib/cmake/flags.cmake rename to paddle/go/cclient/cmake/flags.cmake diff --git a/paddle/go/pserver/lib/cmake/golang.cmake b/paddle/go/cclient/cmake/golang.cmake similarity index 100% rename from paddle/go/pserver/lib/cmake/golang.cmake rename to paddle/go/cclient/cmake/golang.cmake diff --git a/paddle/go/pserver/lib/client/test/CMakeLists.txt b/paddle/go/cclient/test/CMakeLists.txt similarity index 68% rename from paddle/go/pserver/lib/client/test/CMakeLists.txt rename to paddle/go/cclient/test/CMakeLists.txt index 895f0f66c90..c899bd275d3 100644 --- a/paddle/go/pserver/lib/client/test/CMakeLists.txt +++ b/paddle/go/cclient/test/CMakeLists.txt @@ -1,8 +1,8 @@ cmake_minimum_required(VERSION 3.0) -include_directories(/env/gopath/src/github.com/PaddlePaddle/Paddle/paddle/go/pserver/lib/build/client) +include_directories(/env/gopath/src/github.com/PaddlePaddle/Paddle/paddle/go/cclient/build/) add_executable(main main.c) add_dependencies(main client) set (CMAKE_EXE_LINKER_FLAGS "-pthread") -target_link_libraries(main /env/gopath/src/github.com/PaddlePaddle/Paddle/paddle/go/pserver/lib/build/client/libclient.a) # ${GTEST_LIBRARIES}) +target_link_libraries(main /env/gopath/src/github.com/PaddlePaddle/Paddle/paddle/go/cclient/build/libclient.a) # ${GTEST_LIBRARIES}) diff --git a/paddle/go/pserver/lib/client/test/main.c b/paddle/go/cclient/test/main.c similarity index 100% rename from paddle/go/pserver/lib/client/test/main.c rename to paddle/go/cclient/test/main.c diff --git a/paddle/go/pserver/lib/client/CMakeLists.txt b/paddle/go/pserver/lib/client/CMakeLists.txt deleted file mode 100644 index 0f6d47f27c1..00000000000 --- a/paddle/go/pserver/lib/client/CMakeLists.txt +++ /dev/null @@ -1,5 +0,0 @@ -cmake_minimum_required(VERSION 3.0) - -ExternalGoProject_Add(pserver github.com/PaddlePaddle/Paddle/paddle/go/pserver) -add_go_library(client STATIC pserver) -add_subdirectory(test) -- GitLab From f71453be9e74471724c912c2786502f4515de61e Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Wed, 17 May 2017 15:04:34 -0400 Subject: [PATCH 0379/3256] remove unnecessary .gitignore --- paddle/go/cclient/.gitignore | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 paddle/go/cclient/.gitignore diff --git a/paddle/go/cclient/.gitignore b/paddle/go/cclient/.gitignore deleted file mode 100644 index 946d2d10ca4..00000000000 --- a/paddle/go/cclient/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -client.h -client.a -- GitLab From 877447361e3edccb90306ff276342899f115f9d1 Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Wed, 17 May 2017 15:45:09 -0400 Subject: [PATCH 0380/3256] use unsigned char* for parameter.content --- paddle/go/cclient/cclient.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/paddle/go/cclient/cclient.go b/paddle/go/cclient/cclient.go index 2bb76dcb7d6..dc86d47e8d0 100644 --- a/paddle/go/cclient/cclient.go +++ b/paddle/go/cclient/cclient.go @@ -15,7 +15,7 @@ typedef enum { typedef struct { char* name; paddle_element_type element_type; - char* content; + unsigned char* content; int content_len; } paddle_parameter, paddle_gradient; @@ -213,7 +213,7 @@ func paddle_get_params(client C.client, names **C.char, dst **C.paddle_parameter param.name = C.CString(p.Name) } if !contentAllocated { - param.content = (*C.char)(C.malloc(C.size_t(len(p.Content)))) + param.content = (*C.uchar)(C.malloc(C.size_t(len(p.Content)))) } C.memcpy(unsafe.Pointer(param.content), unsafe.Pointer(&p.Content[0]), C.size_t(len(p.Content))) param.content_len = C.int(len(p.Content)) -- GitLab From e6063f6c333d0473f5e460e045c2b8f137dfd0f6 Mon Sep 17 00:00:00 2001 From: Yi Wang Date: Wed, 17 May 2017 16:02:45 -0700 Subject: [PATCH 0381/3256] Design Doc: New Building System --- doc/design/build_system/README.md | 110 ++++++++++++++++++++++++++++++ doc/design/build_system/go.md | 98 ++++++++++++++++++++++++++ 2 files changed, 208 insertions(+) create mode 100644 doc/design/build_system/README.md create mode 100644 doc/design/build_system/go.md diff --git a/doc/design/build_system/README.md b/doc/design/build_system/README.md new file mode 100644 index 00000000000..4c4315d6b9b --- /dev/null +++ b/doc/design/build_system/README.md @@ -0,0 +1,110 @@ +A few months ago when we were trying to replace CMake with Bazel, @emailweixu suggested that we rewrite those handy Bazel functions using CMake. Now it seems that it's the right time to get this done, as we are facing problems from the porting of Majel and the development of new the parameter server using Go and C++. + +Here are some initial thoughts. Your comments are welcome! + +### Required CMake Function + +I think we need only the following few CMake functions to make a project description mean and clean: + +| C++ | CUDA C++ | Go | +|---|---|---| +| cc_library | nv_library | go_library | +| cc_binary | nv_binary | go_binary | +| cc_test | nv_test | go_test | + +- The `_library` functions generate .a files from source code. +- The `_binary` functions generate executable binary files. +- The `_test` functions generate executable unit test files. They work like `_binary` but links `-lgtest` and `-lgtest_main`. + +The difference between `nv_` functions and `cc_` functions is that the former use `nvcc` instead of the system-default C++ compiler. + +Both `nv_` and `cc_` functions enables C++11 (-std=c++11). + +In addition, + +- to describe external dependencies, we need `external_library`. +- to build shared libraries, we need `shared_library`. + +### An Example Project + +Suppose that we have aforementioned functions defined in our `/cmake` directory. The following example `CMakeLists.txt` describes a project including the following source files: + +- tensor.h +- tensor.cc +- tensor_test.cc +- ops.h +- ops.cu +- ops_test.cu +- api.go +- api_test.go + +Suppose that ops.cu depends on CUDNN. + +```cmake +# cc_binary parses tensor.cc and figures out that target also depend +# on tensor.h. +cc_binary(tensor + SRCS + tensor.cc) + +# The dependency to target tensor implies that if any of +# tensor{.h,.cc,_test.cc} is changed, tensor_test need to be re-built. +cc_test(tensor_test + SRCS + tensor_test.cc + DEPS + tensor) + +# I don't have a clear idea what parameters external_library need to +# have. @gangliao as a CMake expert would have better ideas. +external_library(cudnn + ....) + +# Suppose that ops.cu depends on external target CUDNN. Also, ops.cu +# include global functions that take Tensor as their parameters, so +# ops depend on tensor. This implies that if any of tensor.{h.cc}, +# ops.{h,cu} is changed, ops need to be re-built. +nv_library(ops + SRCS + ops.cu + DEPS + tensor + cudnn) # cudnn is defined later. + +nv_test(ops_test + SRCS + ops_test.cu + DEPS + ops) + +# Because api.go defines a GO wrapper to ops and tensor, it depends on +# both. This implies that if any of tensor.{h,cc}, ops.{h,cu}, or +# api.go is changed, api need to be re-built. +go_library(api + SRCS + api.go + DEPS + tensor # Because ops depend on tensor, this line is optional. + ops) + +go_test(api_test + SRCS + api_test.go + DEPS + api) + + +# This builds libapi.so. shared_library might use CMake target +# api_shared so to distinguish it from above target api. +shared_library(api + DEPS + api) + +``` + +### Implementation + +As above example CMakeLists.txt executes, each function invocation +adds "nodes" to a dependency graph. It also use this graph to +generate CMake commands including `add_executable`, +`add_dependencies`, `target_link_libraries`, and `add_test`. diff --git a/doc/design/build_system/go.md b/doc/design/build_system/go.md new file mode 100644 index 00000000000..90399ff7011 --- /dev/null +++ b/doc/design/build_system/go.md @@ -0,0 +1,98 @@ +# Design Doc: `go_{library,binary,test}` + +## Concerns + +1. Need to build Go libraries callable from Go and from C. + + For usual Go libraries, the bulding command line is as + + ```bash + go build foo.go bar.go -o libfoobar.a + ``` + + For Go libraries callable from C/C++, the command line is + + ```bash + go build -buildmode=c-archive foo.go bar.go -o libstatic.a + ``` + + or for shared libraries: + + ```bash + go build -buildmode=c-shared foo.go bar.go -o libdynamic.so + ``` + + and `foo.go`, `bar.go`, etc must start with a line `package main`, + which defines all symbols in special pacakge `main`. There also + must be a `func main`, which could have empty body. + +1. Need to support building-in-Docker. + + We are going to support two ways to building -- (1) in Ubuntu, and + (2) in Docker container whose base image is Ubuntu. + + The challenge is (2), because to build in Docker, we run the + development image as: + + ```bash + git clone https://github.com/PaddlePaddle/Paddle -o paddle + cd paddle + docker run -v $PWD:/paddle paddle:dev + ``` + + which maps the local repo to `/paddle` in the container. + + This assumes that all Go code, including third-party packages, must + be in the local repo. Actually, it assumes that `$GOPATH` must be + in the local repo. This would affect how we write `import` + statemetns, and how we maintain third-party packages. + + +## A Solution + +This might not be the optimal solution. Comments are welcome. + +### Directory structure + +We propose the following directory structure: + +``` +https://github.com/PaddlePaddle/Paddle + ↓ git clone + ~/work/paddle/go/pkg1/foo.go + /pkg2/bar.go + /cmd/cmd1/wee.go + /cmd/cmd2/qux.go + /github.com/someone/a_3rd_party_pkg (Git submodule to a 3rd-party pkg) + /golang.org/another_3rd_party_pkg (Git submodule to another one) + /build/go ($GOPATH, required by Go toolchain) + /src (symlink to ~/work/paddle/go/) + /pkg (libraries built by Go toolchain) + /bin (binaries bulit by Go toolchain) +``` + +Above figure explains how we organize Paddle's Go code: + +1. Go source code in Paddle is in `/go` of the repo. +1. Each library package is a sub-directory under `/go`. +1. Each (executable) binary package is a sub-directory under + `/go/cmd`. This is the source tree convention of Go itself. +1. Each 3rd-party Go package is a Git submodule under `/go`. + +These rules make sure that all Go source code are in `/go`. + +At build-time, Go toolchain requires a directory structure rooted at +`$GOPATH` and having three sub-directories: `$GOPATH/src`, +`$GOPATH/pkg`, and `$GOPATH/bin`, where `$GOPATH/src` is the source +tree and the root of Go's `import` paths. + +For example, if `/go/pkg1/foo.go` contains `import +"github.com/someone/a_3rd_party_pkg"`, the Go toolchain will find the +package at `$GOPATH/src/github.com/someone/a_3rd_party_pkg`. + +In order to create such a `$GOPATH`, our build system creates +`/build/go`. Remeber that we want to make sure that all output files +generated at build-time are place in `/build`. + +Under `/build/go`, our build system creates a symoblic link `src` +pointing to `/go`, where all Go source code resides. -- GitLab From 4f837b9f382814c883fa9c0fa9b9003f5db6e094 Mon Sep 17 00:00:00 2001 From: liaogang Date: Thu, 18 May 2017 09:38:12 +0800 Subject: [PATCH 0382/3256] add ${CMAKE_THREAD_LIBS_INIT} --- cmake/generic.cmake | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/cmake/generic.cmake b/cmake/generic.cmake index 90ec9532e5f..89bf1ef1ec7 100644 --- a/cmake/generic.cmake +++ b/cmake/generic.cmake @@ -58,7 +58,7 @@ function(cc_binary TARGET_NAME) cmake_parse_arguments(cc_binary "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) add_executable(${TARGET_NAME} ${cc_binary_SRCS}) add_dependencies(${TARGET_NAME} ${cc_binary_DEPS} ${external_project_dependencies}) - target_link_libraries(${TARGET_NAME} ${cc_binary_DEPS}) + target_link_libraries(${TARGET_NAME} ${cc_binary_DEPS} ${CMAKE_THREAD_LIBS_INIT}) endfunction(cc_binary) # The dependency to target tensor implies that if any of @@ -75,7 +75,11 @@ function(cc_test TARGET_NAME) cmake_parse_arguments(cc_test "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) add_executable(${TARGET_NAME} ${cc_test_SRCS}) add_dependencies(${TARGET_NAME} ${cc_test_DEPS} ${external_project_dependencies}) - target_link_libraries(${TARGET_NAME} ${cc_test_DEPS} ${GTEST_MAIN_LIBRARIES} ${GTEST_LIBRARIES}) + target_link_libraries(${TARGET_NAME} + ${cc_test_DEPS} + ${GTEST_MAIN_LIBRARIES} + ${GTEST_LIBRARIES} + ${CMAKE_THREAD_LIBS_INIT}) add_test(${TARGET_NAME} ${TARGET_NAME}) endfunction(cc_test) @@ -107,7 +111,7 @@ function(nv_binary TARGET_NAME) cmake_parse_arguments(nv_binary "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) cuda_add_executable(${TARGET_NAME} ${nv_binary_SRCS}) add_dependencies(${TARGET_NAME} ${nv_binary_DEPS} ${external_project_dependencies}) - target_link_libraries(${TARGET_NAME} ${nv_binary_DEPS}) + target_link_libraries(${TARGET_NAME} ${nv_binary_DEPS} ${CMAKE_THREAD_LIBS_INIT}) endfunction(nv_binary) # The dependency to target tensor implies that if any of @@ -124,7 +128,11 @@ function(nv_test TARGET_NAME) cmake_parse_arguments(nv_test "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) cuda_add_executable(${TARGET_NAME} ${nv_test_SRCS}) add_dependencies(${TARGET_NAME} ${nv_test_DEPS} ${external_project_dependencies}) - target_link_libraries(${TARGET_NAME} ${nv_test_DEPS} ${GTEST_MAIN_LIBRARIES} ${GTEST_LIBRARIES}) + target_link_libraries(${TARGET_NAME} + ${nv_test_DEPS} + ${GTEST_MAIN_LIBRARIES} + ${GTEST_LIBRARIES} + ${CMAKE_THREAD_LIBS_INIT}) add_test(${TARGET_NAME} ${TARGET_NAME}) endfunction(nv_test) -- GitLab From 75674710b5e795c009ab570708602e26091c9d55 Mon Sep 17 00:00:00 2001 From: dangqingqing Date: Thu, 18 May 2017 11:52:53 +0800 Subject: [PATCH 0383/3256] Remove opencv-python in setup --- python/paddle/v2/image.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/python/paddle/v2/image.py b/python/paddle/v2/image.py index 13f53919aa4..85ad6984ba0 100644 --- a/python/paddle/v2/image.py +++ b/python/paddle/v2/image.py @@ -1,10 +1,10 @@ import numpy as np try: import cv2 -except ImportError: - cv2 = None - -from cv2 import resize +except: + print( + "import cv2 error, please install opencv-python: pip install opencv-python" + ) __all__ = [ "load_image", "resize_short", "to_chw", "center_crop", "random_crop", @@ -76,7 +76,7 @@ def resize_short(im, size): h_new = size * h / w else: w_new = size * w / h - im = resize(im, (h_new, w_new), interpolation=cv2.INTER_CUBIC) + im = cv2.resize(im, (h_new, w_new), interpolation=cv2.INTER_CUBIC) return im -- GitLab From 44a022b0c7a5bedddb91cac5da2485b8e773c84c Mon Sep 17 00:00:00 2001 From: Liu Yiqun Date: Thu, 18 May 2017 13:57:40 +0800 Subject: [PATCH 0384/3256] Use apt to install cmake. --- Dockerfile.android | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/Dockerfile.android b/Dockerfile.android index 1334799ed2d..fa24f6f06c4 100644 --- a/Dockerfile.android +++ b/Dockerfile.android @@ -9,25 +9,21 @@ ENV HOME=/root \ ANDROID_STANDALONE_TOOLCHAIN=/opt/android-toolchain-gcc RUN apt-get update && \ - apt-get install -y git python-dev python-pip python-numpy && \ - apt-get install -y wget curl tar unzip && \ - apt-get install -y gcc g++ locales swig && \ + apt-get install -y \ + git python-dev python-pip python-numpy \ + wget curl tar unzip gcc g++ locales clang-format-3.8 swig cmake && \ apt-get clean -y -RUN pip install --upgrade pip && \ - pip install -U 'protobuf==3.1.0' && \ - pip install -U wheel sphinx && \ - pip install pre-commit - # git credential to skip password typing RUN git config --global credential.helper store # Fix locales to en_US.UTF-8 RUN localedef -i en_US -f UTF-8 en_US.UTF-8 -RUN curl -sSL https://cmake.org/files/v3.2/cmake-3.2.2.tar.gz | tar -xz && \ - cd cmake-3.2.2 && ./bootstrap && make -j `nproc` && make install && \ - cd .. && rm -rf cmake-3.2.2 +RUN pip install --upgrade pip && \ + pip install -U 'protobuf==3.1.0' && \ + pip install -U wheel sphinx && \ + pip install pre-commit # Android NDK RUN mkdir /opt/android-ndk-tmp && \ -- GitLab From b67291aedb30f0d00ed86de3bc9d15b89d14a7b6 Mon Sep 17 00:00:00 2001 From: qijun Date: Thu, 18 May 2017 14:42:36 +0800 Subject: [PATCH 0385/3256] fix typo error --- paddle/majel/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/paddle/majel/README.md b/paddle/majel/README.md index 670c1792c9a..7a80816d8e4 100644 --- a/paddle/majel/README.md +++ b/paddle/majel/README.md @@ -157,11 +157,11 @@ Please reference the section of `Learn from Majel` for more details. ### ArrayView -`ViewIterator` is a class template which implements basic iterator operation, including increment(++), decrement(--), dereference(*), equalit comparisons(==) and so on. +`ViewIterator` is a class template which implements basic iterator operation, including increment(++), decrement(--), dereference(*), equality comparisons(==) and so on. -`ArrayView` is an encapsulation of `Array`, which introduces extra iterator menthods, such as `begin()` and `end()`. The `begin()` method returns an iterator pointing to the first element in the ArrayView. And the `end()` method returns an iterator pointing to the pass-the-end element in the ArrayView. +`ArrayView` is an encapsulation of `Array`, which introduces extra iterator methods, such as `begin()` and `end()`. The `begin()` method returns an iterator pointing to the first element in the ArrayView. And the `end()` method returns an iterator pointing to the pass-the-end element in the ArrayView. -`ArrayView` make the visting and manipulating an array more efficently, flexibly and safely. +`ArrayView` make the visting and manipulating an array more efficiently, flexibly and safely. A global function `make_view` is provided to transform an array to corresponding arrayview. @@ -186,4 +186,4 @@ ViewIterator> make_iterator(const Array& in, Dim idx) { The operations that manipulate DArray are defined as global functions, such as `ones`, `zeros`, `reshape`, `gemm` and so on. -An array will be trasformed into an arrayview and then passed to the operation launching on a specific deviec(CPU/GPU). +An array will be trasformed into an arrayview and then passed to the operation launching on a specific device(CPU/GPU). -- GitLab From dc530a714f1414f70a6e30c11f9d4a22eeaef544 Mon Sep 17 00:00:00 2001 From: dangqingqing Date: Thu, 18 May 2017 14:54:22 +0800 Subject: [PATCH 0386/3256] Expose more interfaces for Arguments in swig. --- paddle/api/Arguments.cpp | 20 ++++++++++++++++++++ paddle/api/PaddleAPI.h | 19 +++++++++++++++++++ paddle/api/test/testArguments.py | 12 ++++++++++++ 3 files changed, 51 insertions(+) diff --git a/paddle/api/Arguments.cpp b/paddle/api/Arguments.cpp index d49b189e253..c6f9106912c 100644 --- a/paddle/api/Arguments.cpp +++ b/paddle/api/Arguments.cpp @@ -151,4 +151,24 @@ int64_t Arguments::getBatchSize(size_t idx) const throw(RangeError) { return a.getBatchSize(); } +void Arguments::setSlotFrameHeight(size_t idx, size_t h) throw(RangeError) { + auto& a = m->getArg(idx); + a.setFrameHeight(h); +} + +void Arguments::setSlotFrameWidth(size_t idx, size_t w) throw(RangeError) { + auto& a = m->getArg(idx); + a.setFrameWidth(w); +} + +size_t Arguments::getSlotFrameHeight(size_t idx) const throw(RangeError) { + auto& a = m->getArg(idx); + return a.getFrameHeight(); +} + +size_t Arguments::getSlotFrameWidth(size_t idx) const throw(RangeError) { + auto& a = m->getArg(idx); + return a.getFrameWidth(); +} + void* Arguments::getInternalArgumentsPtr() const { return &m->outputs; } diff --git a/paddle/api/PaddleAPI.h b/paddle/api/PaddleAPI.h index d5120401217..da0f157abd6 100644 --- a/paddle/api/PaddleAPI.h +++ b/paddle/api/PaddleAPI.h @@ -454,6 +454,25 @@ public: IVector* vec) throw(RangeError); void setSlotSequenceDim(size_t idx, IVector* vec) throw(RangeError); + /** + * Set the frame height of the idx-th Argument. + * + * @param ids The index of which Argument. + * @param h The height value. + */ + void setSlotFrameHeight(size_t idx, size_t h) throw(RangeError); + + /** + * Set the frame height of the idx-th Argument. + * + * @param ids The index of which Argument. + * @param h The height value. + */ + void setSlotFrameWidth(size_t idx, size_t w) throw(RangeError); + + size_t getSlotFrameHeight(size_t idx = 0) const throw(RangeError); + size_t getSlotFrameWidth(size_t idx = 0) const throw(RangeError); + float sum() const; private: diff --git a/paddle/api/test/testArguments.py b/paddle/api/test/testArguments.py index 9fe44de94ea..4d40ffec9a0 100644 --- a/paddle/api/test/testArguments.py +++ b/paddle/api/test/testArguments.py @@ -13,6 +13,7 @@ # limitations under the License. from py_paddle import swig_paddle +import numpy as np import unittest @@ -36,6 +37,17 @@ class TestArguments(unittest.TestCase): np_arr = iv.toNumpyArrayInplace() self.assertEqual(np_arr.shape, (6, )) + def test_arguments_shape(self): + h, w = 4, 6 + v = np.random.rand(2, h * w) + m = swig_paddle.Matrix.createDense(v.flatten(), 2, h * w) + args = swig_paddle.Arguments.createArguments(1) + args.setSlotValue(0, m) + args.setSlotFrameHeight(0, h) + args.setSlotFrameWidth(0, w) + self.assertEqual(args.getSlotFrameHeight(), h) + self.assertEqual(args.getSlotFrameWidth(), w) + if __name__ == '__main__': swig_paddle.initPaddle("--use_gpu=0") -- GitLab From 646334b558d617b393bb6583162fb27baa7371cc Mon Sep 17 00:00:00 2001 From: fengjiayi Date: Thu, 18 May 2017 15:34:18 +0800 Subject: [PATCH 0387/3256] import DDim of Majel into Paddle --- paddle/majel/CMakeLists.txt | 2 +- paddle/majel/ddim.cc | 222 +++++++++++++++ paddle/majel/ddim.h | 109 ++++++++ paddle/majel/dim.h | 456 +++++++++++++++++++++++++++++++ paddle/majel/hostdevice.h | 9 + paddle/majel/test/CMakeLists.txt | 5 + paddle/majel/test/ddim_test.cc | 65 +++++ paddle/majel/test/dim_test.cu | 128 +++++++++ 8 files changed, 995 insertions(+), 1 deletion(-) create mode 100644 paddle/majel/ddim.cc create mode 100644 paddle/majel/ddim.h create mode 100644 paddle/majel/dim.h create mode 100644 paddle/majel/hostdevice.h create mode 100644 paddle/majel/test/ddim_test.cc create mode 100644 paddle/majel/test/dim_test.cu diff --git a/paddle/majel/CMakeLists.txt b/paddle/majel/CMakeLists.txt index d4bce38906e..47f33dd8321 100644 --- a/paddle/majel/CMakeLists.txt +++ b/paddle/majel/CMakeLists.txt @@ -1,4 +1,4 @@ -cc_library(majel SRCS place.cc) +cc_library(majel SRCS place.cc ddim.cc) if(WITH_TESTING) add_subdirectory(test) diff --git a/paddle/majel/ddim.cc b/paddle/majel/ddim.cc new file mode 100644 index 00000000000..e92d9afa0b6 --- /dev/null +++ b/paddle/majel/ddim.cc @@ -0,0 +1,222 @@ +#include + +namespace majel { + +///@cond HIDDEN + +template +Dim make_dim(const int* d) { + return Dim(*d, make_dim(d + 1)); +} + +template <> +Dim<1> make_dim<1>(const int* d) { + return Dim<1>(*d); +} + +void make_ddim(DDim& ddim, const int* dims, int n) { + switch (n) { + case 1: + ddim = make_dim<1>(dims); + break; + case 2: + ddim = make_dim<2>(dims); + break; + case 3: + ddim = make_dim<3>(dims); + break; + case 4: + ddim = make_dim<4>(dims); + break; + case 5: + ddim = make_dim<5>(dims); + break; + case 6: + ddim = make_dim<6>(dims); + break; + case 7: + ddim = make_dim<7>(dims); + break; + case 8: + ddim = make_dim<8>(dims); + break; + case 9: + ddim = make_dim<9>(dims); + break; + default: + throw std::invalid_argument( + "Dynamic dimensions must have between [1, 9] dimensions."); + } +} + +///@endcond + +DDim make_ddim(std::initializer_list dims) { + DDim result(make_dim(0)); + make_ddim(result, dims.begin(), dims.size()); + return result; +} + +DDim make_ddim(const std::vector& dims) { + DDim result(make_dim(0)); + make_ddim(result, &dims[0], dims.size()); + return result; +} + +///@cond HIDDEN +// XXX For some reason, putting this in an anonymous namespace causes errors +class DynamicMutableIndexer : public boost::static_visitor { +public: + DynamicMutableIndexer(int idx) : idx_(idx) {} + + template + int& operator()(Dim& dim) const { + return dim[idx_]; + } + +private: + int idx_; +}; + +class DynamicConstIndexer : public boost::static_visitor { +public: + DynamicConstIndexer(int idx) : idx_(idx) {} + + template + int operator()(const Dim& dim) const { + return dim[idx_]; + } + +private: + int idx_; +}; + +///@endcond + +int& DDim::operator[](int idx) { + return boost::apply_visitor(DynamicMutableIndexer(idx), var); +} + +int DDim::operator[](int idx) const { + return boost::apply_visitor(DynamicConstIndexer(idx), var); +} + +bool DDim::operator==(DDim d) const { + if (var.which() != d.getVar().which()) { + return false; + } else { + std::vector v1 = vectorize(*this); + std::vector v2 = vectorize(d); + + for (unsigned int i = 0; i < v1.size(); i++) { + if (v1[i] != v2[i]) { + return false; + } + } + + return true; + } +} + +bool DDim::operator!=(DDim d) const { return !(*this == d); } + +DDim DDim::operator+(DDim d) const { + std::vector v1 = vectorize(*this); + std::vector v2 = vectorize(d); + + std::vector v3; + + assert(v1.size() == v2.size()); + + for (unsigned int i = 0; i < v1.size(); i++) { + v3.push_back(v1[i] + v2[i]); + } + + return make_ddim(v3); +} + +DDim DDim::operator*(DDim d) const { + std::vector v1 = vectorize(*this); + std::vector v2 = vectorize(d); + + std::vector v3; + + assert(v1.size() == v2.size()); + + for (unsigned int i = 0; i < v1.size(); i++) { + v3.push_back(v1[i] * v2[i]); + } + + return make_ddim(v3); +} + +int get(const DDim& ddim, int idx) { return ddim[idx]; } + +void set(DDim& ddim, int idx, int value) { ddim[idx] = value; } + +///@cond HIDDEN +struct VectorizeVisitor : public boost::static_visitor<> { + std::vector& vector; + + VectorizeVisitor(std::vector& v) : vector(v) {} + + template + void operator()(const T& t) { + vector.push_back(t.head); + this->operator()(t.tail); + } + + void operator()(const Dim<1>& t) { vector.push_back(t.head); } +}; +///@endcond + +std::vector vectorize(const DDim& ddim) { + std::vector result; + VectorizeVisitor visitor(result); + boost::apply_visitor(visitor, ddim); + return result; +} + +ssize_t product(const DDim& ddim) { + ssize_t result = 1; + std::vector v = vectorize(ddim); + for (auto i : v) { + result *= i; + } + return result; +} + +///\cond HIDDEN + +struct ArityVisitor : boost::static_visitor { + template + int operator()(Dim) const { + return D; + } +}; + +///\endcond + +int arity(const DDim& d) { return boost::apply_visitor(ArityVisitor(), d); } + +///\cond HIDDEN + +struct DDimPrinter : boost::static_visitor { + std::ostream& os; + DDimPrinter(std::ostream& os_) : os(os_) {} + + template + void operator()(const T& t) { + os << t; + } +}; + +///\endcond + +std::ostream& operator<<(std::ostream& os, const majel::DDim& ddim) { + DDimPrinter printer(os); + boost::apply_visitor(printer, ddim); + return os; +} + +} // namespace majel diff --git a/paddle/majel/ddim.h b/paddle/majel/ddim.h new file mode 100644 index 00000000000..64cebf89581 --- /dev/null +++ b/paddle/majel/ddim.h @@ -0,0 +1,109 @@ +#pragma once + +#include +#include +#include +#include + +#include "majel/dim.h" + +namespace majel { + +namespace { +typedef boost::variant, + Dim<2>, + Dim<3>, + Dim<4>, + Dim<5>, + Dim<6>, + Dim<7>, + Dim<8>, + Dim<9>> + DDimVar; +} + +/** + * \brief A dynamically sized dimension. + * + * The number of dimensions must be between [1, 9]. + */ +struct DDim { + DDimVar var; + + DDim() : var(Dim<1>()) {} + + template + DDim(const Dim& in) : var(in) {} + + template + DDim& operator=(const Dim& in) { + var = in; + return *this; + } + + int& operator[](int idx); + int operator[](int idx) const; + + template + typename Visitor::result_type apply_visitor(Visitor& visitor) { + return var.apply_visitor(visitor); + } + + template + typename Visitor::result_type apply_visitor(Visitor& visitor) const { + return var.apply_visitor(visitor); + } + + DDimVar getVar() { return var; } + + bool operator==(DDim d) const; + + bool operator!=(DDim d) const; + + DDim operator+(DDim d) const; + + DDim operator*(DDim d) const; +}; + +/** + * \brief Make a DDim from std::vector + * + * \param dims An vector of ints. Must be sized between [1, 9] + */ +DDim make_ddim(const std::vector& dims); + +/** + * \brief Make a DDim from an initializer list + * + * \param dims An initializer list of ints. Must be sized between [1, 9] + * + */ +DDim make_ddim(std::initializer_list dims); + +int get(const DDim& dim, int idx); +void set(DDim& dim, int idx, int val); + +std::vector vectorize(const DDim& ddim); + +ssize_t product(const DDim& ddim); + +/** + * \brief What is the length of this dimension? + * + * \param Dynamic dimension to inspect + */ + +int arity(const DDim& ddim); + +std::ostream& operator<<(std::ostream&, const majel::DDim&); + +} // namespace majel + +namespace boost { + +template +T get(const majel::DDim& in) { + return boost::get(in.var); +} + +} // namespace boost diff --git a/paddle/majel/dim.h b/paddle/majel/dim.h new file mode 100644 index 00000000000..cf7682b6865 --- /dev/null +++ b/paddle/majel/dim.h @@ -0,0 +1,456 @@ +#pragma once + +#include +#include +#include +#include +/* +#ifdef __CUDACC__ + #include +#endif +*/ + +#include "hostdevice.h" +#include "paddle/utils/Logging.h" + +namespace majel { + +// Statically sized, statically indexed dimension +template +struct Dim { + static constexpr int dimensions = i; + + template + HOSTDEVICE Dim(int _head, Args... _tail) : head(_head), tail(_tail...) { + static_assert(sizeof...(_tail) == i - 1, + "Dim initialized with the wrong number of parameters"); + } + + HOSTDEVICE + Dim(int _head, const Dim& _tail) : head(_head), tail(_tail) {} + + HOSTDEVICE + Dim() : head(0), tail() {} + + /** Construct a Dim from a linear index and size. Uses Fortran order + * indexing. */ + HOSTDEVICE + Dim(int idx, const Dim& size) + : head(idx % size.head), tail(idx / size.head, size.tail) {} + + /** Construct a Dim with each dimension set to the given index */ + HOSTDEVICE + Dim(int idx) : head(idx), tail(idx) {} + + HOSTDEVICE + bool operator==(const Dim& o) const { + return (head == o.head) && (tail == o.tail); + } + + HOSTDEVICE + bool operator!=(const Dim& o) const { return !(*this == o); } + + HOSTDEVICE + int& operator[](int idx); + HOSTDEVICE + int operator[](int idx) const; + + HOST std::string to_string() const; + + int head; + Dim tail; +}; + +// Base case specialization +template <> +struct Dim<1> { + static constexpr int dimensions = 1; + + HOSTDEVICE + Dim(int _head) : head(_head) {} + + HOSTDEVICE + Dim() : head(0) {} + + HOSTDEVICE + Dim(int idx, const Dim<1>& size) : head(idx) { +#ifndef __CUDA_ARCH__ + if (idx >= size.head) { + throw std::invalid_argument("Index out of range."); + } +#else + CHECK(idx < size.head); +#endif + } + + HOSTDEVICE + bool operator==(const Dim<1>& o) const { return (head == o.head); } + + HOSTDEVICE + bool operator!=(const Dim<1>& o) const { return !(*this == o); } + + HOSTDEVICE + int& operator[](int idx); + HOSTDEVICE + int operator[](int idx) const; + + int head; +}; + +namespace { + +// Helper for accessing Dim classes +template +struct DimGetter { + // Return a copy if Dim is const + template + HOSTDEVICE static int impl(const D& d) { + return DimGetter::impl(d.tail); + } + // Return a reference if Dim is mutable + template + HOSTDEVICE static int& impl(D& d) { + return DimGetter::impl(d.tail); + } +}; + +// Eureka! We found the element! +template <> +struct DimGetter<0> { + // Return a copy if Dim is const + template + HOSTDEVICE static int impl(const D& d) { + return d.head; + } + // Return a reference if Dim is mutable + template + HOSTDEVICE static int& impl(D& d) { + return d.head; + } +}; + +template +HOSTDEVICE int& indexer(Dim& dim, int idx) { +#ifndef __CUDA_ARCH__ + if (idx < 0) { + throw std::invalid_argument("Tried to access a negative dimension"); + } +#else + CHECK(idx >= 0); +#endif + if (idx == 0) { + return dim.head; + } + return indexer(dim.tail, idx - 1); +} + +template <> +HOSTDEVICE int& indexer<1>(Dim<1>& dim, int idx) { +#ifndef __CUDA_ARCH__ + if (idx != 0) { + throw std::invalid_argument("Invalid index"); + } +#else + CHECK(idx == 0); +#endif + return dim.head; +} + +template +HOSTDEVICE int indexer(const Dim& dim, int idx) { +#ifndef __CUDA_ARCH__ + if (idx < 0) { + throw std::invalid_argument("Tried to access a negative dimension"); + } +#else + CHECK(idx >= 0); +#endif + if (idx == 0) { + return dim.head; + } + return indexer(dim.tail, idx - 1); +} + +template <> +HOSTDEVICE int indexer<1>(const Dim<1>& dim, int idx) { +#ifndef __CUDA_ARCH__ + if (idx != 0) { + throw std::invalid_argument("Invalid index"); + } +#else + CHECK(idx == 0); +#endif + return dim.head; +} + +} // namespace +// Static access to constant Dim +template +HOSTDEVICE int get(const Dim& d) { + return DimGetter::impl(d); +} + +// Static access to mutable Dim +template +HOSTDEVICE int& get(Dim& d) { + return DimGetter::impl(d); +} + +// Dynamic access to constant Dim +template +HOSTDEVICE int Dim::operator[](int i) const { + return indexer(*this, i); +} + +// Dynamic access to mutable Dim +template +HOSTDEVICE int& Dim::operator[](int i) { + return indexer(*this, i); +} + +// Dynamic access to constant Dim +inline HOSTDEVICE int Dim<1>::operator[](int i) const { + return indexer(*this, i); +} + +// Dynamic access to mutable Dim +inline HOSTDEVICE int& Dim<1>::operator[](int i) { return indexer(*this, i); } + +// Dynamic access to constant Dim +// without std::enable_if will try to instantiate this on get<0>(d) +template +HOSTDEVICE typename std::enable_if<(l > 0), int>::type get(const Dim& d, + int i) { + return d[i]; +} + +// Dynamic access to mutable Dim +template +HOSTDEVICE typename std::enable_if<(l > 0), int&>::type get(Dim& d, int i) { + return d[i]; +} + +// Dot product of two dims +template +HOSTDEVICE int linearize(const Dim& a, const Dim& b) { + return a.head * b.head + linearize(a.tail, b.tail); +} + +// Base case dot product of two Dims +// Notice it is inline because it is no longer a template +template <> +HOSTDEVICE inline int linearize(const Dim<1>& a, const Dim<1>& b) { + return a.head * b.head; +} + +// Product of a Dim +template +HOSTDEVICE int product(const Dim& a, int prod = 1) { + return prod * a.head * product(a.tail); +} + +// Base case product of a Dim +// Notice it is inline because it is no longer a template +template <> +HOSTDEVICE inline int product(const Dim<1>& a, int prod) { + return prod * a.head; +} + +// Is 0 <= idx_i < size_i for all i? +template +HOSTDEVICE bool contained(const Dim& idx, const Dim& size) { + return ((0 <= idx.head) && (idx.head < size.head) && + contained(idx.tail, size.tail)); +} + +// Base case of is 0 <= idx_i < size_i ? +// Notice it is inline because it is no longer a template +template <> +HOSTDEVICE inline bool contained(const Dim<1>& idx, const Dim<1>& size) { + return ((0 <= idx.head) && (idx.head < size.head)); +} + +/** + * \brief Check if a size and a stride create a Fortran order contiguous + * block of memory. + */ +template +HOST bool contiguous(const Dim& size, const Dim& stride, int mul = 1) { + if (product(size) == 0) return true; + int contiguous_stride = get<0>(size) == 1 ? 0 : mul; + return (get<0>(stride) == contiguous_stride && + contiguous(size.tail, stride.tail, mul * get<0>(size))); +} + +///\cond HIDDEN +// Base case of contiguous, check the nth stride is the size of +// the prefix multiply of n-1 dims. +template <> +inline bool contiguous(const Dim<1>& size, const Dim<1>& stride, int mul) { + if (get<0>(size) == 0) return true; + int contiguous_stride = get<0>(size) == 1 ? 0 : mul; + return get<0>(stride) == contiguous_stride; +} +///\endcond + +/** + * \brief Compute exclusive prefix-multiply of a Dim. + */ +template +HOSTDEVICE Dim ex_prefix_mul(const Dim& src, int mul = 1) { + return Dim(mul, ex_prefix_mul(src.tail, mul * src.head)); +} + +///\cond HIDDEN +// Base case of ex_prefix_mul +// Notice it is inline because it is no longer a template +template <> +HOSTDEVICE inline Dim<1> ex_prefix_mul(const Dim<1>& src, int mul) { + return Dim<1>(mul); +} +///\endcond + +/** + * \brief Calculate strides of a contiguous array of the given size + * + * Sets the stride for any dimension with an extent of 1 to 0. + * \param size Dim object containing the size of the array. + * \param base The base stride to use. + * \return Dim object the same size as \p size with the strides. + */ +template +HOSTDEVICE Dim contiguous_strides(const Dim& size, int base = 1) { + int stride = size.head == 1 ? 0 : base; + return Dim(stride, contiguous_strides(size.tail, base * size.head)); +} + +///\cond HIDDEN + +// Base case of contiguous_strides +template <> +HOSTDEVICE inline Dim<1> contiguous_strides(const Dim<1>& size, int base) { + int stride = size.head == 1 ? 0 : base; + return Dim<1>(stride); +} + +///\endcond + +/** + * Add two dimensions together + */ +template +HOSTDEVICE Dim dim_plus(const Dim& a, const Dim& b) { + return Dim(a.head + b.head, dim_plus(a.tail, b.tail)); +} + +// Base case +template <> +HOSTDEVICE inline Dim<1> dim_plus(const Dim<1>& a, const Dim<1>& b) { + return Dim<1>(a.head + b.head); +} + +template +HOSTDEVICE Dim operator+(const Dim& lhs, const Dim& rhs) { + return dim_plus(lhs, rhs); +} + +/** + * Multiply two dimensions together + */ +template +HOSTDEVICE Dim dim_mult(const Dim& a, const Dim& b) { + return Dim(a.head * b.head, dim_mult(a.tail, b.tail)); +} + +// Base case +template <> +HOSTDEVICE inline Dim<1> dim_mult(const Dim<1>& a, const Dim<1>& b) { + return Dim<1>(a.head * b.head); +} + +template +HOSTDEVICE Dim operator*(const Dim& lhs, const Dim& rhs) { + return dim_mult(lhs, rhs); +} + +/** + * \brief Normalize strides to ensure any dimension with extent 1 + * has stride 0. + * + * \param size Dim object containing the size of an array + * \param stride Dim object containing stride of an array + * \return Dim object the same size as \p size with normalized strides + * + */ + +template +HOSTDEVICE Dim normalize_strides(const Dim& size, const Dim& stride) { + int norm_stride = size.head == 1 ? 0 : stride.head; + return Dim(norm_stride, normalize_strides(size.tail, stride.tail)); +} + +///\cond HIDDEN + +template <> +HOSTDEVICE inline Dim<1> normalize_strides(const Dim<1>& size, + const Dim<1>& stride) { + int norm_stride = size.head == 1 ? 0 : stride.head; + return Dim<1>(norm_stride); +} + +///\endcond + +/** + * Helper function to create a Dim + * + * \param idxes The type of Dim constructed depends on the number of params + * + */ + +template +HOSTDEVICE Dim make_dim(Args... idxes) { + return Dim(idxes...); +} + +// Allows us to output a Dim +// XXX For some reason, overloading fails to resolve this correctly +template +typename std::enable_if<(i > 1), std::ostream&>::type operator<<( + std::ostream& os, const majel::Dim& d) { + os << d.head << ", " << d.tail; + return os; +} + +// Base case that allows us to output a Dim +// XXX I wish this could be an overload instead of a template +template +typename std::enable_if<(i == 1), std::ostream&>::type operator<<( + std::ostream& os, const majel::Dim& d) { + os << d.head; + return os; +} + +template +HOST std::string Dim::to_string() const { + std::stringstream stream; + + stream << *this; + + return stream.str(); +} + +template +HOSTDEVICE Dim linear_to_dimension(int linear_index, Dim extents) { + Dim result; + + for (int i = 0; i < D - 1; ++i) { + result[i] = linear_index % extents[i]; + linear_index /= extents[i]; + } + + result[D - 1] = linear_index; + + return result; +} + +} // namespace majel diff --git a/paddle/majel/hostdevice.h b/paddle/majel/hostdevice.h new file mode 100644 index 00000000000..e7de86b7b2f --- /dev/null +++ b/paddle/majel/hostdevice.h @@ -0,0 +1,9 @@ +#pragma once + +#ifdef __CUDACC__ +#define HOSTDEVICE __host__ __device__ +#define HOST __host__ +#else +#define HOSTDEVICE +#define HOST +#endif diff --git a/paddle/majel/test/CMakeLists.txt b/paddle/majel/test/CMakeLists.txt index 68f9059874a..42453799526 100644 --- a/paddle/majel/test/CMakeLists.txt +++ b/paddle/majel/test/CMakeLists.txt @@ -2,6 +2,11 @@ cc_test(place_test SRCS place_test.cc DEPS majel) +cc_test(ddim_test + SRCS ddim_test.cc + DEPS majel) + if(WITH_GPU) nv_test(cuda_test SRCS cuda_test.cu) + nv_test(dim_test SRCS dim_test.cu DEPS majel) endif() diff --git a/paddle/majel/test/ddim_test.cc b/paddle/majel/test/ddim_test.cc new file mode 100644 index 00000000000..5c604517961 --- /dev/null +++ b/paddle/majel/test/ddim_test.cc @@ -0,0 +1,65 @@ +//#include +//#include +#include +#include + +#include "gtest/gtest.h" +#include "majel/ddim.h" + +TEST(DDim, Equality) { + // construct a DDim from an initialization list + majel::DDim ddim = majel::make_ddim({9, 1, 5}); + EXPECT_EQ(ddim[0], 9); + EXPECT_EQ(ddim[1], 1); + EXPECT_EQ(ddim[2], 5); + + // construct a DDim from a vector + std::vector vec({9, 1, 5}); + majel::DDim vddim = majel::make_ddim(vec); + EXPECT_EQ(ddim[0], 9); + EXPECT_EQ(ddim[1], 1); + EXPECT_EQ(ddim[2], 5); + + // mutate a DDim + ddim[1] = 2; + EXPECT_EQ(ddim[1], 2); + majel::set(ddim, 0, 6); + EXPECT_EQ(majel::get(ddim, 0), 6); + + // vectorize a DDim + std::vector res_vec = majel::vectorize(vddim); + EXPECT_EQ(res_vec[0], 9); + EXPECT_EQ(res_vec[1], 1); + EXPECT_EQ(res_vec[2], 5); + majel::Dim<3> d(3, 2, 1); + res_vec = majel::vectorize(majel::DDim(d)); + EXPECT_EQ(res_vec[0], 3); + EXPECT_EQ(res_vec[1], 2); + EXPECT_EQ(res_vec[2], 1); + + // add two DDims + majel::DDim ddim_sum = ddim + vddim; + EXPECT_EQ(ddim_sum[0], 15); + EXPECT_EQ(ddim_sum[1], 3); + EXPECT_EQ(ddim_sum[2], 10); + + // multiply two DDims + majel::DDim ddim_mul = ddim * vddim; + EXPECT_EQ(ddim_mul[0], 54); + EXPECT_EQ(ddim_mul[1], 2); + EXPECT_EQ(ddim_mul[2], 25); + + // arity of a DDim + EXPECT_EQ(majel::arity(ddim), 3); + + // product of a DDim + EXPECT_EQ(majel::product(vddim), 45); +} + +TEST(DDim, Print) { + // print a DDim + std::stringstream ss; + majel::DDim ddim = majel::make_ddim({2, 3, 4}); + ss << ddim; + EXPECT_EQ("2, 3, 4", ss.str()); +} diff --git a/paddle/majel/test/dim_test.cu b/paddle/majel/test/dim_test.cu new file mode 100644 index 00000000000..380204531d8 --- /dev/null +++ b/paddle/majel/test/dim_test.cu @@ -0,0 +1,128 @@ +#include +#include +#include "majel/dim.h" + +#include "gtest/gtest.h" + +__global__ void test(majel::Dim<2>* o) { + o[0] = majel::make_dim(5, 6); +} + +__global__ void dyn_idx_gpu(int* o) { + auto d = majel::make_dim(5, 6); + o[0] = d[1]; +} + +TEST(Dim, Equality) { + // construct a Dim on the CPU + auto a = majel::make_dim(3, 4); + EXPECT_EQ(get<0>(a), 3); + EXPECT_EQ(get<1>(a), 4); + + // construct a Dim on the GPU + thrust::device_vector> t(2); + test<<<1,1>>>(thrust::raw_pointer_cast(t.data())); + a = t[0]; + EXPECT_EQ(get<0>(a), 5); + EXPECT_EQ(get<1>(a), 6); + + // linearization + auto b = make_dim(7, 8); + EXPECT_EQ(linearize(a, b), 83); + + // product + EXPECT_EQ(product(a), 30); + + // mutate a Dim + majel::get<1>(b) = 10; + EXPECT_EQ(majel::get<0>(b), 7); + EXPECT_EQ(majel::get<1>(b), 10); + + // dynamic access + majel::get(b, 0) = 8; + b[1] = 11; + EXPECT_EQ(majel::get<0>(b), 8); + EXPECT_EQ(majel::get<1>(b), 11); + EXPECT_EQ(majel::get(b, 0), 8); + EXPECT_EQ(b[1], 11); + + // dynamic access on GPU + thrust::device_vector r(1); + dyn_idx_gpu<<<1,1>>>(thrust::raw_pointer_cast(r.data())); + int res = r[0]; + EXPECT_EQ(res, 6); + + // ex_prefix_mul + majel::Dim<3> c = majel::ex_prefix_mul(Dim<3>(3, 4, 5)); + EXPECT_EQ(majel::get<0>(c), 1); + EXPECT_EQ(majel::get<1>(c), 3); + EXPECT_EQ(majel::get<2>(c), 12); + + // contiguous_strides + c = majel::contiguous_strides(majel::Dim<3>(10, 1, 10)); + EXPECT_EQ(majel::get<0>(c), 1); + EXPECT_EQ(majel::get<1>(c), 0); + EXPECT_EQ(majel::get<2>(c), 10); + c = majel::contiguous_strides(majel::Dim<3>(10, 10, 1)); + EXPECT_EQ(majel::get<0>(c), 1); + EXPECT_EQ(majel::get<1>(c), 10); + EXPECT_EQ(majel::get<2>(c), 0); + c = majel::contiguous_strides(majel::Dim<3>(1, 10, 10)); + EXPECT_EQ(majel::get<0>(c), 0); + EXPECT_EQ(majel::get<1>(c), 1); + EXPECT_EQ(majel::get<2>(c), 10); + c = majel::contiguous_strides(majel::Dim<3>(2, 3, 4)); + EXPECT_EQ(majel::get<0>(c), 1); + EXPECT_EQ(majel::get<1>(c), 2); + EXPECT_EQ(majel::get<2>(c), 6); + + // generate from an index + auto size = majel::make_dim(4, 5, 2); + c = majel::Dim<3>(14, size); + EXPECT_EQ(majel::get<0>(c), 2); + EXPECT_EQ(majel::get<1>(c), 3); + EXPECT_EQ(majel::get<2>(c), 0); + c = majel::Dim<3>(25, size); + EXPECT_EQ(majel::get<0>(c), 1); + EXPECT_EQ(majel::get<1>(c), 1); + EXPECT_EQ(majel::get<2>(c), 1); +} + +TEST(Dim, Bool) { + auto a = majel::make_dim(3, 4); + auto b = majel::make_dim(5, 6); + auto c = majel::make_dim(3, 4); + + // in_bounds check + EXPECT_TRUE(majel::contained(a, b)); + EXPECT_FALSE(majel::contained(b, a)); + + // comparison + EXPECT_TRUE(a == a); + EXPECT_FALSE(a == b); + EXPECT_TRUE(a == c); + + // contiguous check + int x = 4, y = 5, z = 2; + majel::Dim<3> sizef(x, y, z); + majel::Dim<3> stridea(1, x, x*y); + majel::Dim<3> strideb(2, 2*x, 2*x*y); + majel::Dim<3> stridec(1, x, 2*x*y); + EXPECT_TRUE(majel::contiguous(sizef, stridea)); + EXPECT_FALSE(majel::contiguous(sizef, strideb)); + EXPECT_FALSE(majel::contiguous(sizef, stridec)); +} + +TEST(Dim, Print) { + { + std::stringstream ss; + auto a = majel::make_dim(2, 3); + ss << a; + EXPECT_EQ(ss.str(), "2, 3"); + } + { + std::stringstream ss; + ss << majel::make_dim(8); + EXPECT_EQ(ss.str(), "8"); + } +} -- GitLab From 835ccb326252846fa54c17298d235751c82fd974 Mon Sep 17 00:00:00 2001 From: liaogang Date: Thu, 18 May 2017 16:31:14 +0800 Subject: [PATCH 0388/3256] refine cc_xxx in generic --- cmake/generic.cmake | 16 ++++++++++------ paddle/majel/test/place_test.cc | 2 ++ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/cmake/generic.cmake b/cmake/generic.cmake index 22a26d7c5b0..06b09a6ef84 100644 --- a/cmake/generic.cmake +++ b/cmake/generic.cmake @@ -57,8 +57,9 @@ function(cc_binary TARGET_NAME) set(multiValueArgs SRCS DEPS) cmake_parse_arguments(cc_binary "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) add_executable(${TARGET_NAME} ${cc_binary_SRCS}) - add_dependencies(${TARGET_NAME} ${cc_binary_DEPS} ${external_project_dependencies}) + link_paddle_exe(${TARGET_NAME}) target_link_libraries(${TARGET_NAME} ${cc_binary_DEPS}) + add_dependencies(${TARGET_NAME} ${cc_binary_DEPS}) endfunction(cc_binary) # The dependency to target tensor implies that if any of @@ -74,8 +75,9 @@ function(cc_test TARGET_NAME) set(multiValueArgs SRCS DEPS) cmake_parse_arguments(cc_test "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) add_executable(${TARGET_NAME} ${cc_test_SRCS}) - add_dependencies(${TARGET_NAME} ${cc_test_DEPS} ${external_project_dependencies}) - target_link_libraries(${TARGET_NAME} ${cc_test_DEPS} ${GTEST_MAIN_LIBRARIES} ${GTEST_LIBRARIES}) + link_paddle_test(${TARGET_NAME}) + target_link_libraries(${TARGET_NAME} ${cc_test_DEPS}) + add_dependencies(${TARGET_NAME} ${cc_test_DEPS}) add_test(${TARGET_NAME} ${TARGET_NAME}) endfunction(cc_test) @@ -106,8 +108,9 @@ function(nv_binary TARGET_NAME) set(multiValueArgs SRCS DEPS) cmake_parse_arguments(nv_binary "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) cuda_add_executable(${TARGET_NAME} ${nv_binary_SRCS}) - add_dependencies(${TARGET_NAME} ${nv_binary_DEPS} ${external_project_dependencies}) + link_paddle_exe(${TARGET_NAME}) target_link_libraries(${TARGET_NAME} ${nv_binary_DEPS}) + add_dependencies(${TARGET_NAME} ${nv_binary_DEPS}) endfunction(nv_binary) # The dependency to target tensor implies that if any of @@ -123,7 +126,8 @@ function(nv_test TARGET_NAME) set(multiValueArgs SRCS DEPS) cmake_parse_arguments(nv_test "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) cuda_add_executable(${TARGET_NAME} ${nv_test_SRCS}) - add_dependencies(${TARGET_NAME} ${nv_test_DEPS} ${external_project_dependencies}) - target_link_libraries(${TARGET_NAME} ${nv_test_DEPS} ${GTEST_MAIN_LIBRARIES} ${GTEST_LIBRARIES}) + link_paddle_test(${TARGET_NAME}) + target_link_libraries(${TARGET_NAME} ${nv_test_DEPS}) + add_dependencies(${TARGET_NAME} ${nv_test_DEPS}) add_test(${TARGET_NAME} ${TARGET_NAME}) endfunction(nv_test) diff --git a/paddle/majel/test/place_test.cc b/paddle/majel/test/place_test.cc index c9a53802b23..686ed9c0e89 100644 --- a/paddle/majel/test/place_test.cc +++ b/paddle/majel/test/place_test.cc @@ -1,6 +1,7 @@ #include "majel/place.h" #include #include "gtest/gtest.h" +#include "paddle/utils/Logging.h" TEST(Place, Equality) { majel::CpuPlace cpu; @@ -37,4 +38,5 @@ TEST(Place, Print) { ss << majel::CpuPlace(); EXPECT_EQ("CpuPlace", ss.str()); } + LOG(INFO) << "\n[----------] Done \n"; } -- GitLab From 8ea4548ad3b61c31d1e125baf742bc4d5f8d741f Mon Sep 17 00:00:00 2001 From: liaogang Date: Thu, 18 May 2017 16:51:09 +0800 Subject: [PATCH 0389/3256] fix a bug --- cmake/generic.cmake | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/cmake/generic.cmake b/cmake/generic.cmake index 06b09a6ef84..3ca735189da 100644 --- a/cmake/generic.cmake +++ b/cmake/generic.cmake @@ -58,8 +58,10 @@ function(cc_binary TARGET_NAME) cmake_parse_arguments(cc_binary "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) add_executable(${TARGET_NAME} ${cc_binary_SRCS}) link_paddle_exe(${TARGET_NAME}) - target_link_libraries(${TARGET_NAME} ${cc_binary_DEPS}) - add_dependencies(${TARGET_NAME} ${cc_binary_DEPS}) + if(cc_binary_DEPS) + target_link_libraries(${TARGET_NAME} ${cc_binary_DEPS}) + add_dependencies(${TARGET_NAME} ${cc_binary_DEPS}) + endif() endfunction(cc_binary) # The dependency to target tensor implies that if any of @@ -76,8 +78,10 @@ function(cc_test TARGET_NAME) cmake_parse_arguments(cc_test "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) add_executable(${TARGET_NAME} ${cc_test_SRCS}) link_paddle_test(${TARGET_NAME}) - target_link_libraries(${TARGET_NAME} ${cc_test_DEPS}) - add_dependencies(${TARGET_NAME} ${cc_test_DEPS}) + if(cc_test_DEPS) + target_link_libraries(${TARGET_NAME} ${cc_test_DEPS}) + add_dependencies(${TARGET_NAME} ${cc_test_DEPS}) + endif() add_test(${TARGET_NAME} ${TARGET_NAME}) endfunction(cc_test) @@ -109,8 +113,10 @@ function(nv_binary TARGET_NAME) cmake_parse_arguments(nv_binary "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) cuda_add_executable(${TARGET_NAME} ${nv_binary_SRCS}) link_paddle_exe(${TARGET_NAME}) - target_link_libraries(${TARGET_NAME} ${nv_binary_DEPS}) - add_dependencies(${TARGET_NAME} ${nv_binary_DEPS}) + if(nv_binary_DEPS) + target_link_libraries(${TARGET_NAME} ${nv_binary_DEPS}) + add_dependencies(${TARGET_NAME} ${nv_binary_DEPS}) + endif() endfunction(nv_binary) # The dependency to target tensor implies that if any of @@ -127,7 +133,9 @@ function(nv_test TARGET_NAME) cmake_parse_arguments(nv_test "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) cuda_add_executable(${TARGET_NAME} ${nv_test_SRCS}) link_paddle_test(${TARGET_NAME}) - target_link_libraries(${TARGET_NAME} ${nv_test_DEPS}) - add_dependencies(${TARGET_NAME} ${nv_test_DEPS}) + if(nv_test_DEPS) + target_link_libraries(${TARGET_NAME} ${nv_test_DEPS}) + add_dependencies(${TARGET_NAME} ${nv_test_DEPS}) + endif() add_test(${TARGET_NAME} ${TARGET_NAME}) endfunction(nv_test) -- GitLab From f8a42d5de98f83bc9703c31dc47ca05c6590341e Mon Sep 17 00:00:00 2001 From: Luo Tao Date: Thu, 18 May 2017 16:54:32 +0800 Subject: [PATCH 0390/3256] add doc for AggregateLevel and ExpandLevel --- doc/api/v2/config/layer.rst | 10 ++++++ .../paddle/trainer_config_helpers/layers.py | 31 +++++++++++++++++++ python/paddle/v2/layer.py | 4 +-- 3 files changed, 43 insertions(+), 2 deletions(-) diff --git a/doc/api/v2/config/layer.rst b/doc/api/v2/config/layer.rst index 154cfe24432..1efa74ecda4 100644 --- a/doc/api/v2/config/layer.rst +++ b/doc/api/v2/config/layer.rst @@ -207,6 +207,11 @@ trans_full_matrix_projection Aggregate Layers ================ +AggregateLevel +-------------- +.. autoclass:: paddle.v2.layer.AggregateLevel + :noindex: + .. _api_v2.layer_pooling: pooling @@ -248,6 +253,11 @@ block_expand .. _api_v2.layer_expand: +ExpandLevel +----------- +.. autoclass:: paddle.v2.layer.ExpandLevel + :noindex: + expand ------ .. autoclass:: paddle.v2.layer.expand diff --git a/python/paddle/trainer_config_helpers/layers.py b/python/paddle/trainer_config_helpers/layers.py index 3b6f0270de1..cf7e54ef507 100755 --- a/python/paddle/trainer_config_helpers/layers.py +++ b/python/paddle/trainer_config_helpers/layers.py @@ -225,6 +225,24 @@ class LayerType(object): class AggregateLevel(object): + """ + As PaddlePaddle supports three sequence types: + + - :code:`SequenceType.NO_SEQUENCE` means the sample is not a sequence. + - :code:`SequenceType.SEQUENCE` means the sample is a sequence. + - :code:`SequenceType.SUB_SEQUENCE` means it is a nested sequence, that + each timestep of the input sequence is also a sequence. + + Thus, AggregateLevel supports two modes: + + - :code:`AggregateLevel.EACH_TIMESTEP` means the aggregation acts on each + timestep of sequence, both :code:`SUB_SEQUENCE` and :code:`SEQUENCE` will + be aggregated to :code:`NO_SEQUENCE`. + + - :code:`AggregateLevel.EACH_SEQUENCE` means the aggregation acts on each + sequence of a nested sequence, :code:`SUB_SEQUENCE` will be aggregated to + :code:`SEQUENCE`. + """ EACH_TIMESTEP = 'non-seq' EACH_SEQUENCE = 'seq' @@ -1454,6 +1472,19 @@ def first_seq(input, class ExpandLevel(object): + """ + Please refer to AggregateLevel first. + + ExpandLevel supports two modes: + + - :code:`ExpandLevel.FROM_TIMESTEP` means the expandation acts on each + timestep of a sequence, :code:`NO_SEQUENCE` will be expanded to + :code:`SEQUENCE` or :code:`SUB_SEQUENCE`. + + - :code:`ExpandLevel.FROM_SEQUENCE` means the expandation acts on each + sequence of a nested sequence, :code:`SEQUENCE` will be expanded to + :code:`SUB_SEQUENCE`. + """ FROM_TIMESTEP = AggregateLevel.EACH_TIMESTEP FROM_SEQUENCE = AggregateLevel.EACH_SEQUENCE diff --git a/python/paddle/v2/layer.py b/python/paddle/v2/layer.py index 89cca7acd34..3d9caeec589 100644 --- a/python/paddle/v2/layer.py +++ b/python/paddle/v2/layer.py @@ -404,8 +404,8 @@ class RecurrentLayerOutput(Layer): LayerV2 = Layer data = DataLayerV2 data.__name__ = 'data' -AggregateLevel = conf_helps.layers.AggregateLevel -ExpandLevel = conf_helps.layers.ExpandLevel +AggregateLevel = conf_helps.AggregateLevel +ExpandLevel = conf_helps.ExpandLevel memory = MemoryV2 memory.__name__ = 'memory' memory.__doc__ = conf_helps.memory.__doc__ -- GitLab From 1cf9b5ae055dab5e9513f67c5c6cc6c14ebfe612 Mon Sep 17 00:00:00 2001 From: liaogang Date: Thu, 18 May 2017 17:11:58 +0800 Subject: [PATCH 0391/3256] fix swig compile flags --- paddle/api/CMakeLists.txt | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/paddle/api/CMakeLists.txt b/paddle/api/CMakeLists.txt index c9a285c90b0..53db938b35f 100644 --- a/paddle/api/CMakeLists.txt +++ b/paddle/api/CMakeLists.txt @@ -26,10 +26,7 @@ FILE(GLOB PY_PADDLE_PYTHON_FILES ${PROJ_ROOT}/paddle/py_paddle/*.py) SET_SOURCE_FILES_PROPERTIES(Paddle.i PROPERTIES CPLUSPLUS ON) SET(CMAKE_SWIG_OUTDIR ${CMAKE_CURRENT_BINARY_DIR}) -SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -fPIC -Wall") -IF(WITH_COVERAGE) - SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -O0 -fprofile-arcs -ftest-coverage") -ENDIF(WITH_COVERAGE) +SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-parentheses-equality -Wno-self-assign") SET(SWIG_MODULE_swig_paddle_EXTRA_DEPS paddle_parameter -- GitLab From 3a4ab8206a8e1b0d7a59926a2dae701628bba65e Mon Sep 17 00:00:00 2001 From: liaogang Date: Thu, 18 May 2017 17:19:54 +0800 Subject: [PATCH 0392/3256] add Wno-missing-field-initializers --- paddle/api/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paddle/api/CMakeLists.txt b/paddle/api/CMakeLists.txt index 53db938b35f..e147659566d 100644 --- a/paddle/api/CMakeLists.txt +++ b/paddle/api/CMakeLists.txt @@ -26,7 +26,7 @@ FILE(GLOB PY_PADDLE_PYTHON_FILES ${PROJ_ROOT}/paddle/py_paddle/*.py) SET_SOURCE_FILES_PROPERTIES(Paddle.i PROPERTIES CPLUSPLUS ON) SET(CMAKE_SWIG_OUTDIR ${CMAKE_CURRENT_BINARY_DIR}) -SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-parentheses-equality -Wno-self-assign") +SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-parentheses-equality -Wno-missing-field-initializers -Wno-self-assign") SET(SWIG_MODULE_swig_paddle_EXTRA_DEPS paddle_parameter -- GitLab From 110a402221d7d719f8c0eb50cea8f1f3bfedee9d Mon Sep 17 00:00:00 2001 From: fengjiayi Date: Thu, 18 May 2017 09:28:49 +0000 Subject: [PATCH 0393/3256] make up missing 'majel::' and import majel/util.h --- paddle/majel/dim.h | 19 +++++++---------- paddle/majel/test/dim_test.cu | 18 ++++++++-------- paddle/majel/util.h | 39 +++++++++++++++++++++++++++++++++++ 3 files changed, 55 insertions(+), 21 deletions(-) create mode 100644 paddle/majel/util.h diff --git a/paddle/majel/dim.h b/paddle/majel/dim.h index cf7682b6865..ba718ce5b76 100644 --- a/paddle/majel/dim.h +++ b/paddle/majel/dim.h @@ -4,14 +4,9 @@ #include #include #include -/* -#ifdef __CUDACC__ - #include -#endif -*/ -#include "hostdevice.h" -#include "paddle/utils/Logging.h" +#include "majel/hostdevice.h" +#include "majel/util.h" namespace majel { @@ -79,7 +74,7 @@ struct Dim<1> { throw std::invalid_argument("Index out of range."); } #else - CHECK(idx < size.head); + MAJEL_ASSERT(idx < size.head); #endif } @@ -136,7 +131,7 @@ HOSTDEVICE int& indexer(Dim& dim, int idx) { throw std::invalid_argument("Tried to access a negative dimension"); } #else - CHECK(idx >= 0); + MAJEL_ASSERT(idx >= 0); #endif if (idx == 0) { return dim.head; @@ -151,7 +146,7 @@ HOSTDEVICE int& indexer<1>(Dim<1>& dim, int idx) { throw std::invalid_argument("Invalid index"); } #else - CHECK(idx == 0); + MAJEL_ASSERT(idx == 0); #endif return dim.head; } @@ -163,7 +158,7 @@ HOSTDEVICE int indexer(const Dim& dim, int idx) { throw std::invalid_argument("Tried to access a negative dimension"); } #else - CHECK(idx >= 0); + MAJEL_ASSERT(idx >= 0); #endif if (idx == 0) { return dim.head; @@ -178,7 +173,7 @@ HOSTDEVICE int indexer<1>(const Dim<1>& dim, int idx) { throw std::invalid_argument("Invalid index"); } #else - CHECK(idx == 0); + MAJEL_ASSERT(idx == 0); #endif return dim.head; } diff --git a/paddle/majel/test/dim_test.cu b/paddle/majel/test/dim_test.cu index 380204531d8..ed7c34ffd1d 100644 --- a/paddle/majel/test/dim_test.cu +++ b/paddle/majel/test/dim_test.cu @@ -1,7 +1,7 @@ #include #include -#include "majel/dim.h" +#include "majel/dim.h" #include "gtest/gtest.h" __global__ void test(majel::Dim<2>* o) { @@ -16,22 +16,22 @@ __global__ void dyn_idx_gpu(int* o) { TEST(Dim, Equality) { // construct a Dim on the CPU auto a = majel::make_dim(3, 4); - EXPECT_EQ(get<0>(a), 3); - EXPECT_EQ(get<1>(a), 4); + EXPECT_EQ(majel::get<0>(a), 3); + EXPECT_EQ(majel::get<1>(a), 4); // construct a Dim on the GPU thrust::device_vector> t(2); test<<<1,1>>>(thrust::raw_pointer_cast(t.data())); a = t[0]; - EXPECT_EQ(get<0>(a), 5); - EXPECT_EQ(get<1>(a), 6); + EXPECT_EQ(majel::get<0>(a), 5); + EXPECT_EQ(majel::get<1>(a), 6); // linearization - auto b = make_dim(7, 8); - EXPECT_EQ(linearize(a, b), 83); + auto b = majel::make_dim(7, 8); + EXPECT_EQ(majel::linearize(a, b), 83); // product - EXPECT_EQ(product(a), 30); + EXPECT_EQ(majel::product(a), 30); // mutate a Dim majel::get<1>(b) = 10; @@ -53,7 +53,7 @@ TEST(Dim, Equality) { EXPECT_EQ(res, 6); // ex_prefix_mul - majel::Dim<3> c = majel::ex_prefix_mul(Dim<3>(3, 4, 5)); + majel::Dim<3> c = majel::ex_prefix_mul(majel::Dim<3>(3, 4, 5)); EXPECT_EQ(majel::get<0>(c), 1); EXPECT_EQ(majel::get<1>(c), 3); EXPECT_EQ(majel::get<2>(c), 12); diff --git a/paddle/majel/util.h b/paddle/majel/util.h new file mode 100644 index 00000000000..ab21e1869f7 --- /dev/null +++ b/paddle/majel/util.h @@ -0,0 +1,39 @@ +#pragma once + +#define STRINGIFY(x) #x +#define TOSTRING(x) STRINGIFY(x) + +#if defined(__APPLE__) && defined(__CUDA_ARCH__) && !defined(NDEBUG) +#include +#define MAJEL_ASSERT(e) \ + do { \ + if (!(e)) { \ + printf( \ + "%s:%d Assertion `%s` failed.\n", __FILE__, __LINE__, TOSTRING(e)); \ + asm("trap;"); \ + } \ + } while (0) + +#define MAJEL_ASSERT_MSG(e, m) \ + do { \ + if (!(e)) { \ + printf("%s:%d Assertion `%s` failed (%s).\n", \ + __FILE__, \ + __LINE__, \ + TOSTRING(e), \ + m); \ + asm("trap;"); \ + } \ + } while (0) +#else +#include +#define MAJEL_ASSERT(e) assert(e) +#define MAJEL_ASSERT_MSG(e, m) assert((e) && (m)) +#endif + +namespace majel { +namespace detail { + +inline int div_up(int x, int y) { return (x + y - 1) / y; } +} +} -- GitLab From c7fb78b0a5245cf6852b3cf7d0e617e75c404b46 Mon Sep 17 00:00:00 2001 From: Luo Tao Date: Thu, 18 May 2017 19:48:54 +0800 Subject: [PATCH 0394/3256] follow comments --- python/paddle/trainer_config_helpers/layers.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/python/paddle/trainer_config_helpers/layers.py b/python/paddle/trainer_config_helpers/layers.py index cf7e54ef507..ec81e1dc3d2 100755 --- a/python/paddle/trainer_config_helpers/layers.py +++ b/python/paddle/trainer_config_helpers/layers.py @@ -226,17 +226,17 @@ class LayerType(object): class AggregateLevel(object): """ - As PaddlePaddle supports three sequence types: + PaddlePaddle supports three sequence types: - :code:`SequenceType.NO_SEQUENCE` means the sample is not a sequence. - :code:`SequenceType.SEQUENCE` means the sample is a sequence. - - :code:`SequenceType.SUB_SEQUENCE` means it is a nested sequence, that - each timestep of the input sequence is also a sequence. + - :code:`SequenceType.SUB_SEQUENCE` means the sample is a nested sequence, + each timestep of which is also a sequence. - Thus, AggregateLevel supports two modes: + Accordingly, AggregateLevel supports two modes: - :code:`AggregateLevel.EACH_TIMESTEP` means the aggregation acts on each - timestep of sequence, both :code:`SUB_SEQUENCE` and :code:`SEQUENCE` will + timestep of a sequence, both :code:`SUB_SEQUENCE` and :code:`SEQUENCE` will be aggregated to :code:`NO_SEQUENCE`. - :code:`AggregateLevel.EACH_SEQUENCE` means the aggregation acts on each -- GitLab From a36c6fb965152b8ecf3c0b0ebb7a6d51470d567f Mon Sep 17 00:00:00 2001 From: Yi Wang Date: Thu, 18 May 2017 14:12:25 -0700 Subject: [PATCH 0395/3256] Remove go.md and Grammerly README.md --- doc/design/build_system/README.md | 7 +-- doc/design/build_system/go.md | 98 ------------------------------- 2 files changed, 2 insertions(+), 103 deletions(-) delete mode 100644 doc/design/build_system/go.md diff --git a/doc/design/build_system/README.md b/doc/design/build_system/README.md index 4c4315d6b9b..310739f37ae 100644 --- a/doc/design/build_system/README.md +++ b/doc/design/build_system/README.md @@ -20,7 +20,7 @@ The difference between `nv_` functions and `cc_` functions is that the former us Both `nv_` and `cc_` functions enables C++11 (-std=c++11). -In addition, +Also, - to describe external dependencies, we need `external_library`. - to build shared libraries, we need `shared_library`. @@ -104,7 +104,4 @@ shared_library(api ### Implementation -As above example CMakeLists.txt executes, each function invocation -adds "nodes" to a dependency graph. It also use this graph to -generate CMake commands including `add_executable`, -`add_dependencies`, `target_link_libraries`, and `add_test`. +As above example CMakeLists.txt executes, each function invocation adds "nodes" to a dependency graph. It also use this graph to generate CMake commands including `add_executable`, `add_dependencies`, `target_link_libraries`, and `add_test`. diff --git a/doc/design/build_system/go.md b/doc/design/build_system/go.md deleted file mode 100644 index 90399ff7011..00000000000 --- a/doc/design/build_system/go.md +++ /dev/null @@ -1,98 +0,0 @@ -# Design Doc: `go_{library,binary,test}` - -## Concerns - -1. Need to build Go libraries callable from Go and from C. - - For usual Go libraries, the bulding command line is as - - ```bash - go build foo.go bar.go -o libfoobar.a - ``` - - For Go libraries callable from C/C++, the command line is - - ```bash - go build -buildmode=c-archive foo.go bar.go -o libstatic.a - ``` - - or for shared libraries: - - ```bash - go build -buildmode=c-shared foo.go bar.go -o libdynamic.so - ``` - - and `foo.go`, `bar.go`, etc must start with a line `package main`, - which defines all symbols in special pacakge `main`. There also - must be a `func main`, which could have empty body. - -1. Need to support building-in-Docker. - - We are going to support two ways to building -- (1) in Ubuntu, and - (2) in Docker container whose base image is Ubuntu. - - The challenge is (2), because to build in Docker, we run the - development image as: - - ```bash - git clone https://github.com/PaddlePaddle/Paddle -o paddle - cd paddle - docker run -v $PWD:/paddle paddle:dev - ``` - - which maps the local repo to `/paddle` in the container. - - This assumes that all Go code, including third-party packages, must - be in the local repo. Actually, it assumes that `$GOPATH` must be - in the local repo. This would affect how we write `import` - statemetns, and how we maintain third-party packages. - - -## A Solution - -This might not be the optimal solution. Comments are welcome. - -### Directory structure - -We propose the following directory structure: - -``` -https://github.com/PaddlePaddle/Paddle - ↓ git clone - ~/work/paddle/go/pkg1/foo.go - /pkg2/bar.go - /cmd/cmd1/wee.go - /cmd/cmd2/qux.go - /github.com/someone/a_3rd_party_pkg (Git submodule to a 3rd-party pkg) - /golang.org/another_3rd_party_pkg (Git submodule to another one) - /build/go ($GOPATH, required by Go toolchain) - /src (symlink to ~/work/paddle/go/) - /pkg (libraries built by Go toolchain) - /bin (binaries bulit by Go toolchain) -``` - -Above figure explains how we organize Paddle's Go code: - -1. Go source code in Paddle is in `/go` of the repo. -1. Each library package is a sub-directory under `/go`. -1. Each (executable) binary package is a sub-directory under - `/go/cmd`. This is the source tree convention of Go itself. -1. Each 3rd-party Go package is a Git submodule under `/go`. - -These rules make sure that all Go source code are in `/go`. - -At build-time, Go toolchain requires a directory structure rooted at -`$GOPATH` and having three sub-directories: `$GOPATH/src`, -`$GOPATH/pkg`, and `$GOPATH/bin`, where `$GOPATH/src` is the source -tree and the root of Go's `import` paths. - -For example, if `/go/pkg1/foo.go` contains `import -"github.com/someone/a_3rd_party_pkg"`, the Go toolchain will find the -package at `$GOPATH/src/github.com/someone/a_3rd_party_pkg`. - -In order to create such a `$GOPATH`, our build system creates -`/build/go`. Remeber that we want to make sure that all output files -generated at build-time are place in `/build`. - -Under `/build/go`, our build system creates a symoblic link `src` -pointing to `/go`, where all Go source code resides. -- GitLab From 478ebc42b73e30e16389bf27693d93ce3a9c0cb0 Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Thu, 18 May 2017 18:37:08 -0400 Subject: [PATCH 0396/3256] apply clang-format --- paddle/go/cclient/test/main.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/paddle/go/cclient/test/main.c b/paddle/go/cclient/test/main.c index d71a023a6ad..28e3d03b7a0 100644 --- a/paddle/go/cclient/test/main.c +++ b/paddle/go/cclient/test/main.c @@ -11,7 +11,7 @@ void panic() { int main() { char addr[] = "localhost:3000"; client c = paddle_new_pserver_client(addr); - retry: +retry: if (paddle_begin_init_params(c, NULL, 0)) { paddle_parameter param; char name_a[] = "param_a"; @@ -39,14 +39,16 @@ int main() { } char content[] = {0x00, 0x11, 0x22}; - paddle_gradient grads[2] = {{"param_a", PADDLE_ELEMENT_TYPE_INT32, content, 3}, {"param_b", PADDLE_ELEMENT_TYPE_FLOAT32, content, 3}}; + paddle_gradient grads[2] = { + {"param_a", PADDLE_ELEMENT_TYPE_INT32, content, 3}, + {"param_b", PADDLE_ELEMENT_TYPE_FLOAT32, content, 3}}; if (!paddle_send_grads(c, grads, 2)) { panic(); } paddle_parameter* params[2] = {NULL, NULL}; - char* names[]={"param_a", "param_b"}; + char* names[] = {"param_a", "param_b"}; if (!paddle_get_params(c, names, params, 2)) { panic(); } -- GitLab From dfc27aaadc4a9f0929a2b4639f7b9d37cb2080cf Mon Sep 17 00:00:00 2001 From: yangyaming Date: Fri, 19 May 2017 11:21:11 +0800 Subject: [PATCH 0397/3256] fix document formation bugs. --- .../trainer_config_helpers/evaluators.py | 72 ++++++++++--------- 1 file changed, 37 insertions(+), 35 deletions(-) diff --git a/python/paddle/trainer_config_helpers/evaluators.py b/python/paddle/trainer_config_helpers/evaluators.py index 6900133fde2..a5234f3e47f 100644 --- a/python/paddle/trainer_config_helpers/evaluators.py +++ b/python/paddle/trainer_config_helpers/evaluators.py @@ -350,20 +350,23 @@ def chunk_evaluator( sequence. It calculates precision, recall and F1 scores for the chunk detection. To use chunk evaluator, several concepts need to be clarified firstly. - Chunk type is the type of the whole chunk and a chunk consists of one or several words. (For example in NER, ORG for organization name, PER for person name etc.) - Tag indicates the position of a word in a chunk. (B for begin, I for inside, E for end, S for single) + + * **Chunk type** is the type of the whole chunk and a chunk consists of one or several words. (For example in NER, ORG for organization name, PER for person name etc.) + + * **Tag type** indicates the position of a word in a chunk. (B for begin, I for inside, E for end, S for single) We can name a label by combining tag type and chunk type. (ie. B-ORG for begining of an organization name) - The construction of label dict should obey the following rules: - (1) Use one of the listed labelling schemes. These schemes differ in ways indicating chunk boundry. + The construction of label dictionary should obey the following rules: - .. code-block:: python - Scheme Description - plain Use the same label for the whole chunk. - IOB Two labels for chunk type X, B-X for chunk begining and I-X for chunk inside. - IOE Two labels for chunk type X, E-X for chunk ending and I-X for chunk inside. - IOBES Four labels for chunk type X, B-X for chunk begining, I-X for chunk inside, E-X for chunk end and S-X for single word chunk. - .. code-block:: python + - Use one of the listed labelling schemes. These schemes differ in ways indicating chunk boundry. + + .. code-block:: text + + Scheme Description + plain Use the same label for the whole chunk. + IOB Two labels for chunk type X, B-X for chunk begining and I-X for chunk inside. + IOE Two labels for chunk type X, E-X for chunk ending and I-X for chunk inside. + IOBES Four labels for chunk type X, B-X for chunk begining, I-X for chunk inside, E-X for chunk end and S-X for single word chunk. To make it clear, let's illustrate by an NER example. Assuming that there are three named entity types including ORG, PER and LOC which are called 'chunk type' here, @@ -372,42 +375,42 @@ def chunk_evaluator( Prefixes which are called 'tag type' here are added to chunk types and there are two tag types including B and I. Of course, the training data should be labeled accordingly. - (2) Mapping is done correctly by the listed equations and assigning protocol. + - Mapping is done correctly by the listed equations and assigning protocol. The following table are equations to extract tag type and chunk type from a label. - .. code-block:: python - tagType = label % numTagType - chunkType = label / numTagType - otherChunkType = numChunkTypes - .. code-block:: python + .. code-block:: text + + tagType = label % numTagType + chunkType = label / numTagType + otherChunkType = numChunkTypes The following table shows the mapping rule between tagType and tag type in each scheme. - .. code-block:: python - Scheme Begin Inside End Single - plain 0 - - - - IOB 0 1 - - - IOE - 0 1 - - IOBES 0 1 2 3 - .. code-block:: python + .. code-block:: text + + Scheme Begin Inside End Single + plain 0 - - - + IOB 0 1 - - + IOE - 0 1 - + IOBES 0 1 2 3 Continue the NER example, and the label dict should look like this to satify above equations: - .. code-block:: python - B-ORG 0 - I-ORG 1 - B-PER 2 - I-PER 3 - B-LOC 4 - I-LOC 5 - O 6 - .. code-block:: python + .. code-block:: text + + B-ORG 0 + I-ORG 1 + B-PER 2 + I-PER 3 + B-LOC 4 + I-LOC 5 + O 6 In this example, chunkType has three values: 0 for ORG, 1 for PER, 2 for LOC, because the scheme is "IOB" so tagType has two values: 0 for B and 1 for I. Here we will use I-LOC to explain the above mapping rules in detail. - For I-LOC, the label id is 5, so we can get tagType=1 and ChunkType=2, which means I-LOC is a part of NER chunk LOC + For I-LOC, the label id is 5, so we can get tagType=1 and chunkType=2, which means I-LOC is a part of NER chunk LOC and the tag is I. The simple usage is: @@ -416,7 +419,6 @@ def chunk_evaluator( eval = chunk_evaluator(input, label, chunk_scheme, num_chunk_types) - .. code-block:: python :param input: The input layers. :type input: LayerOutput -- GitLab From 82c60bbbb5831ffb5a0a8f65cdc528b0c637e60d Mon Sep 17 00:00:00 2001 From: fengjiayi Date: Fri, 19 May 2017 11:49:38 +0800 Subject: [PATCH 0398/3256] adjust '#include' style and move macro definition files into folder 'detail' --- paddle/majel/ddim.cc | 2 +- paddle/majel/ddim.h | 2 +- paddle/majel/{util.h => detail/cuda_assert.h} | 7 ------- paddle/majel/{ => detail}/hostdevice.h | 0 paddle/majel/dim.h | 4 ++-- paddle/majel/place.cc | 4 ++-- paddle/majel/test/ddim_test.cc | 2 +- paddle/majel/test/dim_test.cu | 2 +- paddle/majel/test/place_test.cc | 2 +- 9 files changed, 9 insertions(+), 16 deletions(-) rename paddle/majel/{util.h => detail/cuda_assert.h} (93%) rename paddle/majel/{ => detail}/hostdevice.h (100%) diff --git a/paddle/majel/ddim.cc b/paddle/majel/ddim.cc index e92d9afa0b6..f32408ed530 100644 --- a/paddle/majel/ddim.cc +++ b/paddle/majel/ddim.cc @@ -1,4 +1,4 @@ -#include +#include "paddle/majel/ddim.h" namespace majel { diff --git a/paddle/majel/ddim.h b/paddle/majel/ddim.h index 64cebf89581..7be756f8c09 100644 --- a/paddle/majel/ddim.h +++ b/paddle/majel/ddim.h @@ -5,7 +5,7 @@ #include #include -#include "majel/dim.h" +#include "paddle/majel/dim.h" namespace majel { diff --git a/paddle/majel/util.h b/paddle/majel/detail/cuda_assert.h similarity index 93% rename from paddle/majel/util.h rename to paddle/majel/detail/cuda_assert.h index ab21e1869f7..9490d0ae3ef 100644 --- a/paddle/majel/util.h +++ b/paddle/majel/detail/cuda_assert.h @@ -30,10 +30,3 @@ #define MAJEL_ASSERT(e) assert(e) #define MAJEL_ASSERT_MSG(e, m) assert((e) && (m)) #endif - -namespace majel { -namespace detail { - -inline int div_up(int x, int y) { return (x + y - 1) / y; } -} -} diff --git a/paddle/majel/hostdevice.h b/paddle/majel/detail/hostdevice.h similarity index 100% rename from paddle/majel/hostdevice.h rename to paddle/majel/detail/hostdevice.h diff --git a/paddle/majel/dim.h b/paddle/majel/dim.h index ba718ce5b76..5eff4c05cc2 100644 --- a/paddle/majel/dim.h +++ b/paddle/majel/dim.h @@ -5,8 +5,8 @@ #include #include -#include "majel/hostdevice.h" -#include "majel/util.h" +#include "paddle/majel/detail/hostdevice.h" +#include "paddle/majel/detail/util.h" namespace majel { diff --git a/paddle/majel/place.cc b/paddle/majel/place.cc index eecd8e5b730..ca50b37843e 100644 --- a/paddle/majel/place.cc +++ b/paddle/majel/place.cc @@ -1,4 +1,4 @@ -#include +#include "paddle/majel/place.h" namespace majel { @@ -16,7 +16,7 @@ public: void operator()(const GpuPlace& p) { os_ << "GpuPlace(" << p.device << ")"; } }; -} // namespace majel +} // namespace detail static Place the_default_place; diff --git a/paddle/majel/test/ddim_test.cc b/paddle/majel/test/ddim_test.cc index 5c604517961..a5b8a7c4d26 100644 --- a/paddle/majel/test/ddim_test.cc +++ b/paddle/majel/test/ddim_test.cc @@ -4,7 +4,7 @@ #include #include "gtest/gtest.h" -#include "majel/ddim.h" +#include "paddle/majel/ddim.h" TEST(DDim, Equality) { // construct a DDim from an initialization list diff --git a/paddle/majel/test/dim_test.cu b/paddle/majel/test/dim_test.cu index ed7c34ffd1d..a7d81e595be 100644 --- a/paddle/majel/test/dim_test.cu +++ b/paddle/majel/test/dim_test.cu @@ -1,7 +1,7 @@ #include #include -#include "majel/dim.h" +#include "paddle/majel/dim.h" #include "gtest/gtest.h" __global__ void test(majel::Dim<2>* o) { diff --git a/paddle/majel/test/place_test.cc b/paddle/majel/test/place_test.cc index c9a53802b23..6a099ae6b6e 100644 --- a/paddle/majel/test/place_test.cc +++ b/paddle/majel/test/place_test.cc @@ -1,4 +1,4 @@ -#include "majel/place.h" +#include "paddle/majel/place.h" #include #include "gtest/gtest.h" -- GitLab From fc358816cbb32965c825746c634294ffba5187ba Mon Sep 17 00:00:00 2001 From: fengjiayi Date: Fri, 19 May 2017 12:01:13 +0800 Subject: [PATCH 0399/3256] try adjusting building config --- paddle/majel/CMakeLists.txt | 3 ++- paddle/majel/dim.h | 2 +- paddle/majel/test/CMakeLists.txt | 6 +++--- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/paddle/majel/CMakeLists.txt b/paddle/majel/CMakeLists.txt index 47f33dd8321..cb8bece00e1 100644 --- a/paddle/majel/CMakeLists.txt +++ b/paddle/majel/CMakeLists.txt @@ -1,4 +1,5 @@ -cc_library(majel SRCS place.cc ddim.cc) +cc_library(place SRCS place.cc) +cc_library(ddim SRCS ddim.cc) if(WITH_TESTING) add_subdirectory(test) diff --git a/paddle/majel/dim.h b/paddle/majel/dim.h index 5eff4c05cc2..c4b0c6aea68 100644 --- a/paddle/majel/dim.h +++ b/paddle/majel/dim.h @@ -5,8 +5,8 @@ #include #include +#include "paddle/majel/detail/cuda_assert.h" #include "paddle/majel/detail/hostdevice.h" -#include "paddle/majel/detail/util.h" namespace majel { diff --git a/paddle/majel/test/CMakeLists.txt b/paddle/majel/test/CMakeLists.txt index 42453799526..9d632d568e9 100644 --- a/paddle/majel/test/CMakeLists.txt +++ b/paddle/majel/test/CMakeLists.txt @@ -1,12 +1,12 @@ cc_test(place_test SRCS place_test.cc - DEPS majel) + DEPS place) cc_test(ddim_test SRCS ddim_test.cc - DEPS majel) + DEPS ddim) if(WITH_GPU) nv_test(cuda_test SRCS cuda_test.cu) - nv_test(dim_test SRCS dim_test.cu DEPS majel) + nv_test(dim_test SRCS dim_test.cu DEPS ddim) endif() -- GitLab From 576e7f477993f02c5f89919c3a9ed229fb7c50ff Mon Sep 17 00:00:00 2001 From: dangqingqing Date: Fri, 19 May 2017 16:01:40 +0800 Subject: [PATCH 0400/3256] Support variable-dimension for convolution operation. --- demo/sentiment/train_v2.py | 3 +- paddle/py_paddle/dataprovider_converter.py | 39 +++++++++++++++++----- python/paddle/trainer/PyDataProvider2.py | 17 ++++++++-- python/paddle/v2/data_type.py | 3 +- python/paddle/v2/tests/test_data_feeder.py | 24 +++++++++++++ 5 files changed, 73 insertions(+), 13 deletions(-) diff --git a/demo/sentiment/train_v2.py b/demo/sentiment/train_v2.py index 1c856556bd0..5151444a862 100644 --- a/demo/sentiment/train_v2.py +++ b/demo/sentiment/train_v2.py @@ -103,7 +103,7 @@ def stacked_lstm_net(input_dim, if __name__ == '__main__': # init - paddle.init(use_gpu=False) + paddle.init(use_gpu=False, log_clipping=True) #data print 'load dictionary...' @@ -131,6 +131,7 @@ if __name__ == '__main__': # create optimizer adam_optimizer = paddle.optimizer.Adam( learning_rate=2e-3, + gradient_clipping_threshold=0.003, regularization=paddle.optimizer.L2Regularization(rate=8e-4), model_average=paddle.optimizer.ModelAverage(average_window=0.5)) diff --git a/paddle/py_paddle/dataprovider_converter.py b/paddle/py_paddle/dataprovider_converter.py index 7c6b8354100..cfb82e92d51 100644 --- a/paddle/py_paddle/dataprovider_converter.py +++ b/paddle/py_paddle/dataprovider_converter.py @@ -17,6 +17,7 @@ import collections import swig_paddle import numpy import itertools +from functools import reduce __all__ = ['DataProviderConverter'] @@ -59,12 +60,14 @@ class IScanner(object): """ pass - def finish_pre_scan(self, argument): + def finish_pre_scan(self, argument, dat=None): """ Finish first scan pass. Allocate the memory. :param argument: Output arguments object. :type argument: swig_paddle.Arguments + :param dat: Output arguments object. + :type dat: The Python object, numpy.array or List. :return: """ pass @@ -95,17 +98,27 @@ class DenseScanner(IScanner): def __init__(self, input_type, pos): IScanner.__init__(self, input_type, pos) self.__mat__ = None + self.__shape__ = None self.__height__ = 0 def pre_scan(self, dat): self.__height__ += 1 - def finish_pre_scan(self, argument): + def finish_pre_scan(self, argument, dat=None): + self.__shape__ = numpy.array(dat).shape + if len(self.__shape__) > 3: + raise ValueError("The dimension of input is greater than 3.") + dim = reduce(lambda x, y: x * y, self.__shape__) + if len(self.__shape__) == 1: + assert dim == self.input_type.dim self.__mat__ = numpy.ndarray( - shape=(self.__height__, self.input_type.dim), dtype=numpy.float32) + shape=(self.__height__, dim), dtype=numpy.float32) self.__height__ = 0 def scan(self, dat): + if isinstance(dat, numpy.ndarray): + assert self.__shape__ == dat.shape + dat = dat.flatten() self.__mat__[self.__height__] = dat self.__height__ += 1 @@ -116,6 +129,13 @@ class DenseScanner(IScanner): m = swig_paddle.Matrix.createDenseFromNumpy(self.__mat__, True, self.data_in_gpu) argument.setSlotValue(self.pos, m) + if len(self.__shape__) > 1: + # The last-two dimenstions are the frame height and width. + # For example, the layout is CHW for 3-D feature of image. + # The H and W are the fram height and width. + h, w = self.__shape__[-2:] + argument.setSlotFrameHeight(self.pos, h) + argument.setSlotFrameWidth(self.pos, w) class SparseBinaryScanner(IScanner): @@ -166,7 +186,7 @@ class IndexScanner(IScanner): def pre_scan(self, dat): self.__idx__ += 1 - def finish_pre_scan(self, argument): + def finish_pre_scan(self, argument, dat=None): self.__ids__ = [0] * self.__idx__ self.__idx__ = 0 @@ -191,8 +211,8 @@ class SequenceScanner(IScanner): for each in dat: self.__inner_scanner__.pre_scan(each) - def finish_pre_scan(self, argument): - self.__inner_scanner__.finish_pre_scan(argument) + def finish_pre_scan(self, argument, dat=None): + self.__inner_scanner__.finish_pre_scan(argument, dat) def scan(self, dat): self.__seq__.append(self.__seq__[-1] + self.get_size(dat)) @@ -233,8 +253,11 @@ class DataProviderConverter(object): for each_step, scanner in itertools.izip(each_sample, scanners): scanner.pre_scan(each_step) - for scanner in scanners: - scanner.finish_pre_scan(argument) + # Some scanners, like dense scanner, pre-allocate memory for mini-batch + # in finish_pre_scan function. The dat[0] is used to calculate the size + # of input data. + for scanner, each_feature in itertools.izip(scanners, dat[0]): + scanner.finish_pre_scan(argument, each_feature) for each_sample in dat: for each_step, scanner in itertools.izip(each_sample, scanners): diff --git a/python/paddle/trainer/PyDataProvider2.py b/python/paddle/trainer/PyDataProvider2.py index a36f0ebfdcb..7e305e2cd9f 100644 --- a/python/paddle/trainer/PyDataProvider2.py +++ b/python/paddle/trainer/PyDataProvider2.py @@ -72,9 +72,16 @@ class InputType(object): def dense_slot(dim, seq_type=SequenceType.NO_SEQUENCE): """ - Dense Vector. It means the input feature is dense float vector. For example, - if the input is an image with 28*28 pixels, the input of Paddle neural - network should be a dense vector with dimension 784. + Dense Array. It means the input feature is dense array with float type. + For example, if the input is an image with 28*28 pixels, the input of + Paddle neural network could be a dense vector with dimension 784 or a + numpy array with shape (28, 28). + + For the 2-D convolution operation, each sample in one mini-batch must have + the similarly size in PaddlePaddle now. But, it supports variable-dimension + feature across mini-batch. For the variable-dimension, the param dim is not + used. While the data reader must yield numpy array and the data feeder will + set the data shape correctly. :param dim: dimension of this vector. :type dim: int @@ -135,6 +142,10 @@ sparse_binary_vector = sparse_non_value_slot sparse_vector = sparse_value_slot integer_value = index_slot +# dense_array can be used for variable-length input feature. +# Each feature is not a vector, but a multi-dimensional array. +dense_array = dense_slot + def dense_vector_sequence(dim): """ diff --git a/python/paddle/v2/data_type.py b/python/paddle/v2/data_type.py index d582f76ddf0..226997465f2 100644 --- a/python/paddle/v2/data_type.py +++ b/python/paddle/v2/data_type.py @@ -16,7 +16,8 @@ import paddle.trainer.PyDataProvider2 as pydp2 import_list = [ nm for nm in dir(pydp2) - if '_' in nm and nm[0] != '_' and ('value' in nm or 'vector' in nm) + if '_' in nm and nm[0] != '_' and ('value' in nm or 'vector' in nm or + 'array' in nm) ] import_list.extend(['InputType']) diff --git a/python/paddle/v2/tests/test_data_feeder.py b/python/paddle/v2/tests/test_data_feeder.py index 71eb3bf3142..83da678da38 100644 --- a/python/paddle/v2/tests/test_data_feeder.py +++ b/python/paddle/v2/tests/test_data_feeder.py @@ -233,6 +233,30 @@ class DataFeederTest(unittest.TestCase): self.assertEqual(out_sparse.getSparseRowCols(i), data[i][1]) self.assertEqual(out_index[i], data[i][0]) + def test_dense_set_shape(self): + # test 2-D data + def gen_data(batch_size, shape): + data = [] + for i in xrange(batch_size): + each_sample = [] + each_sample.append(np.random.random(shape)) + data.append(each_sample) + return data + + feeder = DataFeeder([('image', data_type.dense_array(2352))], + {'image': 0}) + arg = feeder(gen_data(32, (3, 28, 28))) + h = arg.getSlotFrameHeight(0) + w = arg.getSlotFrameWidth(0) + self.assertEqual(h, 28) + self.assertEqual(w, 28) + + arg = feeder(gen_data(32, (3, 30, 32))) + h = arg.getSlotFrameHeight(0) + w = arg.getSlotFrameWidth(0) + self.assertEqual(h, 30) + self.assertEqual(w, 32) + if __name__ == '__main__': api.initPaddle("--use_gpu=0") -- GitLab From 4d617d2458403f626e61e37b1e89cf82877f32ec Mon Sep 17 00:00:00 2001 From: hedaoyuan Date: Fri, 19 May 2017 16:26:42 +0800 Subject: [PATCH 0401/3256] Add a error message. --- paddle/math/TensorEvaluate.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/paddle/math/TensorEvaluate.h b/paddle/math/TensorEvaluate.h index 9de2099b850..687bad37113 100644 --- a/paddle/math/TensorEvaluate.h +++ b/paddle/math/TensorEvaluate.h @@ -103,7 +103,10 @@ inline void TensorGpuApply(LeftType& lhs, const RightType& rhs) { } #else template -inline void TensorGpuApply(LeftType& lhs, RightType& rhs) {} +inline void TensorGpuApply(LeftType& lhs, RightType& rhs) { + LOG(FATAL) << "Since it is gcc compiled, " + "this calculation does not support GPU implementation."; +} #endif } // namespace paddle -- GitLab From 6dfba39c96cd1d485fdf3686816b3d346675850a Mon Sep 17 00:00:00 2001 From: dangqingqing Date: Fri, 19 May 2017 16:17:15 +0800 Subject: [PATCH 0402/3256] resume file in sentiment demo. --- demo/sentiment/train_v2.py | 3 +-- python/setup.py.in | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/demo/sentiment/train_v2.py b/demo/sentiment/train_v2.py index 5151444a862..1c856556bd0 100644 --- a/demo/sentiment/train_v2.py +++ b/demo/sentiment/train_v2.py @@ -103,7 +103,7 @@ def stacked_lstm_net(input_dim, if __name__ == '__main__': # init - paddle.init(use_gpu=False, log_clipping=True) + paddle.init(use_gpu=False) #data print 'load dictionary...' @@ -131,7 +131,6 @@ if __name__ == '__main__': # create optimizer adam_optimizer = paddle.optimizer.Adam( learning_rate=2e-3, - gradient_clipping_threshold=0.003, regularization=paddle.optimizer.L2Regularization(rate=8e-4), model_average=paddle.optimizer.ModelAverage(average_window=0.5)) diff --git a/python/setup.py.in b/python/setup.py.in index 5dfb46192ae..7d9438e3f81 100644 --- a/python/setup.py.in +++ b/python/setup.py.in @@ -18,6 +18,7 @@ setup(name='paddle', "numpy", "protobuf==${PROTOBUF_VERSION}", "matplotlib", + "opencv-python", ], packages=packages, package_dir={ -- GitLab From 50d0e26746e1d9ced00449238043bbe36dc5843d Mon Sep 17 00:00:00 2001 From: liaogang Date: Sat, 20 May 2017 09:06:17 +0800 Subject: [PATCH 0403/3256] pass travis ci --- cmake/generic.cmake | 1 + 1 file changed, 1 insertion(+) diff --git a/cmake/generic.cmake b/cmake/generic.cmake index d73ab176f22..efc49b8fd3c 100644 --- a/cmake/generic.cmake +++ b/cmake/generic.cmake @@ -27,6 +27,7 @@ # # cmake_parse_arguments can help us to achieve this goal. # https://cmake.org/cmake/help/v3.0/module/CMakeParseArguments.html +# # cc_library parses tensor.cc and figures out that target also depend on tensor.h. # cc_library(tensor -- GitLab From 09622b4569b7b1dd6b9a7f24f1c820624184d419 Mon Sep 17 00:00:00 2001 From: Xinghai Sun Date: Sat, 20 May 2017 16:11:14 +0800 Subject: [PATCH 0404/3256] Add DS2 design doc (doc/design/speech/README.md). --- doc/design/speech/README.MD | 155 ++++++++++++++++++++++++ doc/design/speech/image/ds2_network.png | Bin 0 -> 116482 bytes 2 files changed, 155 insertions(+) create mode 100644 doc/design/speech/README.MD create mode 100644 doc/design/speech/image/ds2_network.png diff --git a/doc/design/speech/README.MD b/doc/design/speech/README.MD new file mode 100644 index 00000000000..7304650e628 --- /dev/null +++ b/doc/design/speech/README.MD @@ -0,0 +1,155 @@ +# DeepSpeech2 on PaddlePaddle: Design Doc + +We are planning to build Deep Speech 2 (DS2) \[[1](#references)\], a powerful Automatic Speech Recognition (ASR) engine, on PaddlePaddle. For the first-stage plan, we have the following short-term goals: + +- Release a basic distributed implementation of DS2 on PaddlePaddle. +- Contribute a chapter of Deep Speech to PaddlePaddle Book. + +Intensive system optimization and low-latency inference library (details in \[[1](#references)\]) are not yet covered in this first-stage plan. + +## Table of Contents + +- [Tasks](#tasks) +- [Task Dependency](#task-dependency) +- [Design Details](#design-details) + - [Overview](#overview) + - [Row Convolution](#row-convolution) + - [Beam Search With CTC and LM](#beam-search-with-ctc-and-lm) +- [Future Work](#future-work) +- [References](#references) + +## Tasks + +We roughly break down the project into 14 tasks: + +1. Develop an **audio data provider**: + - Json filelist generator. + - Audio file format transformer. + - Spectrogram feature extraction, power normalization etc. + - Batch data reader with SortaGrad. + - Data augmentation (optional). + - Prepare (one or more) public English data sets & baseline. +2. Create a **simplified DS2 model configuration**: + - With only fixed-length (by padding) audio sequences (otherwise need *Task 3*). + - With only bidirectional-GRU (otherwise need *Task 4*). + - With only greedy decoder (otherwise need *Task 5, 6*). +3. Develop to support **variable-shaped** dense-vector (image) batches of input data. + - Update `DenseScanner` in `dataprovider_converter.py`, etc. +4. Develop a new **lookahead-row-convolution layer** (See \[[1](#references)\] for details): + - Lookahead convolution windows. + - Within-row convolution, without kernels shared across rows. +5. Build KenLM **language model** (5-gram) for beam search decoder: + - Use KenLM toolkit. + - Prepare the corpus & train the model. + - Create infererence interfaces (for Task 6). +6. Develop a **beam search decoder** with CTC + LM + WORDCOUNT: + - Beam search with CTC. + - Beam search with external custom scorer (e.g. LM). + - Try to design a more general beam search interface. +7. Develop a **Word Error Rate evaluator**: + - update `ctc_error_evaluator`(CER) to support WER. +8. Prepare internal dataset for Mandarin (optional): + - Dataset, baseline, evaluation details. + - Particular data preprocessing for Mandarin. + - Might need cooperating with the Speech Department. +9. Create **standard DS2 model configuration**: + - With variable-length audio sequences (need *Task 3*). + - With unidirectional-GRU + row-convolution (need *Task 4*). + - With CTC-LM beam search decoder (need *Task 5, 6*). +10. Make it run perfectly on **clusters**. +11. Experiments and **benchmarking** (for accuracy, not efficiency): + - With public English dataset. + - With internal (Baidu) Mandarin dataset (optional). +12. Time **profiling** and optimization. +13. Prepare **docs**. +14. Prepare PaddlePaddle **Book** chapter with a simplified version. + +## Task Dependency + +Tasks parallelizable within phases: + +Roadmap | Description | Parallelizable Tasks +----------- | :------------------------------------ | :-------------------- +Phase I | Simplified model & components | *Task 1* ~ *Task 8* +Phase II | Standard model & benchmarking & profiling | *Task 9* ~ *Task 12* +Phase III | Documentations | *Task13* ~ *Task14* + +Issue for each task will be created later. Contributions, discussions and comments are all highly appreciated and welcomed! + +## Design Details + +### Overview + +Traditional **ASR** (Automatic Speech Recognition) pipelines require great human efforts devoted to elaborately tuning multiple hand-engineered components (e.g. audio feature design, accoustic model, pronuncation model and language model etc.). **Deep Speech 2** (**DS2**) \[[1](#references)\], however, trains such ASR models in an end-to-end manner, replacing most intermediate modules with only a single deep network architecture. With scaling up both the data and model sizes, DS2 achieves a very significant performance boost. + +Please read Deep Speech 2 \[[1](#references),[2](#references)\] paper for more background knowledge. + +The classical DS2 network contains 15 layers (from bottom to top): + +- **Two** data layers (audio spectrogram, transcription text) +- **Three** 2D convolution layers +- **Seven** uni-directional simple-RNN layers +- **One** lookahead row convolution layers +- **One** fully-connected layers +- **One** CTC-loss layer + +
    +
    +Figure 1. Archetecture of Deep Speech 2 Network. +
    + +We don't have to persist on this 2-3-7-1-1-1 depth \[[2](#references)\]. Similar networks with different depths might also work well. As in \[[1](#references)\], authors use a different depth (e.g. 2-2-3-1-1-1) for final experiments. + +Key ingredients about the layers: + +- **Data Layers**: + - Frame sequences data of audio **spectrogram** (with FFT). + - Token sequences data of **transcription** text (labels). + - These two type of sequences do not have the same lengthes, thus a CTC-loss layer is required. +- **2D Convolution Layers**: + - Not only temporal convolution, but also **frequency convolution**. Like a 2D image convolution, but with a variable dimension (i.e. temporal dimension). + - With striding for only the first convlution layer. + - No pooling for all convolution layers. +- **Uni-directional RNNs** + - Uni-directional + row convolution: for low-latency inference. + - Bi-direcitional + without row convolution: if we don't care about the inference latency. +- **Row convolution**: + - For looking only a few steps ahead into the feature, instead of looking into a whole sequence in bi-directional RNNs. + - Not nessesary if with bi-direcitional RNNs. + - "**Row**" means convolutions are done within each frequency dimension (row), and no convolution kernels shared across. +- **Batch Normalization Layers**: + - Added to all above layers (except for data and loss layer). + - Sequence-wise normalization for RNNs: BatchNorm only performed on input-state projection and not state-state projection, for efficiency consideration. + + +Required Components | PaddlePaddle Support | Need to Develop +:------------------------------------- | :-------------------------------------- | :----------------------- +Data Layer I (Spectrogram) | Not supported yet. | TBD (Task 3) +Data Layer II (Transcription) | `paddle.data_type.integer_value_sequence` | - +2D Convolution Layer | `paddle.layer.image_conv_layer` | - +DataType Converter (vec2seq) | `paddle.layer.block_expand` | - +Bi-/Uni-directional RNNs | `paddle.layer.recurrent_group` | - +Row Convolution Layer | Not supported yet. | TBD (Task 4) +CTC-loss Layer | `paddle.layer.warp_ctc` | - +Batch Normalization Layer | `paddle.layer.batch_norm` | - +CTC-Beam search | Not supported yet. | TBD (Task 6) + +### Row Convolution + +TODO by Assignees + +### Beam Search with CTC and LM + +TODO by Assignees + +## Future Work + +- Efficiency Improvement +- Accuracy Improvement +- Low-latency Inference Library +- Large-scale benchmarking + +## References + +1. Dario Amodei, etc., [Deep Speech 2 : End-to-End Speech Recognition in English and Mandarin](http://proceedings.mlr.press/v48/amodei16.pdf). ICML 2016. +2. Dario Amodei, etc., [Deep Speech 2 : End-to-End Speech Recognition in English and Mandarin](https://arxiv.org/abs/1512.02595). arXiv:1512.02595. diff --git a/doc/design/speech/image/ds2_network.png b/doc/design/speech/image/ds2_network.png new file mode 100644 index 0000000000000000000000000000000000000000..1a5b2184d47928cc2849d5a7c8ea2d8cf5337e11 GIT binary patch literal 116482 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�#I22A}lSN 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 literal 0 HcmV?d00001 -- GitLab From 55217c962d127e72ee88e042d2dd95cfe7375a65 Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Tue, 16 May 2017 18:41:01 -0400 Subject: [PATCH 0405/3256] Implement Pserver RPC, gradient update logic, cgo part --- paddle/go/cmd/pserver/.gitignore | 1 + paddle/go/cmd/pserver/pserver.go | 33 ++++++ paddle/go/pserver/optimizer.c | 22 ++++ paddle/go/pserver/optimizer.go | 51 +++++++++ paddle/go/pserver/optimizer.h | 19 ++++ paddle/go/pserver/service.go | 165 ++++++++++++++++++++++++++++++ paddle/go/pserver/service_test.go | 154 ++++++++++++++++++++++++++++ 7 files changed, 445 insertions(+) create mode 100644 paddle/go/cmd/pserver/.gitignore create mode 100644 paddle/go/cmd/pserver/pserver.go create mode 100644 paddle/go/pserver/optimizer.c create mode 100644 paddle/go/pserver/optimizer.go create mode 100644 paddle/go/pserver/optimizer.h create mode 100644 paddle/go/pserver/service.go create mode 100644 paddle/go/pserver/service_test.go diff --git a/paddle/go/cmd/pserver/.gitignore b/paddle/go/cmd/pserver/.gitignore new file mode 100644 index 00000000000..fffd9adc4fd --- /dev/null +++ b/paddle/go/cmd/pserver/.gitignore @@ -0,0 +1 @@ +pserver diff --git a/paddle/go/cmd/pserver/pserver.go b/paddle/go/cmd/pserver/pserver.go new file mode 100644 index 00000000000..41417875fb9 --- /dev/null +++ b/paddle/go/cmd/pserver/pserver.go @@ -0,0 +1,33 @@ +package main + +import ( + "flag" + "net" + "net/http" + "net/rpc" + "strconv" + + "github.com/PaddlePaddle/Paddle/paddle/go/pserver" +) + +func main() { + port := flag.Int("p", 0, "port of the pserver") + flag.Parse() + + s := pserver.NewService() + err := rpc.Register(s) + if err != nil { + panic(err) + } + + rpc.HandleHTTP() + l, err := net.Listen("tcp", ":"+strconv.Itoa(*port)) + if err != nil { + panic(err) + } + + err = http.Serve(l, nil) + if err != nil { + panic(err) + } +} diff --git a/paddle/go/pserver/optimizer.c b/paddle/go/pserver/optimizer.c new file mode 100644 index 00000000000..d83409297ba --- /dev/null +++ b/paddle/go/pserver/optimizer.c @@ -0,0 +1,22 @@ +#include + +#include "optimizer.h" + +typedef struct { + double learning_rate; +} SGD_optimizer; + +paddle_optimizer* paddle_create_SGD_optimizer(double learning_rate) { + SGD_optimizer* o = (SGD_optimizer*)malloc(sizeof(SGD_optimizer)); + o->learning_rate = learning_rate; + return (paddle_optimizer*)o; +} + +void paddle_release_optimizer(paddle_optimizer* o) { + free(o); +} + +int paddle_update_parameter(paddle_optimizer* o, void *buffer, paddle_element_type datatype, const void* gradient, int num_bytes) { + // TODO + return 0; +} diff --git a/paddle/go/pserver/optimizer.go b/paddle/go/pserver/optimizer.go new file mode 100644 index 00000000000..aa02bed3e0f --- /dev/null +++ b/paddle/go/pserver/optimizer.go @@ -0,0 +1,51 @@ +package pserver + +/* +#include "optimizer.h" +*/ +import "C" +import ( + "fmt" + "unsafe" +) + +type optimizerType int + +const ( + sgd optimizerType = iota +) + +var nullPtr = unsafe.Pointer(uintptr(0)) + +type optimizer struct { + opt *C.paddle_optimizer +} + +func newOptimizer(t optimizerType, learning_rate float64) *optimizer { + o := &optimizer{} + o.opt = C.paddle_create_SGD_optimizer(C.double(learning_rate)) + return o +} + +func (o *optimizer) UpdateParameter(p Parameter, g Gradient) error { + if len(p.Content) != len(g.Content) { + return fmt.Errorf("parameter and gradient length not match, parameter: %d, gradient: %d", len(p.Content), len(g.Content)) + } + + if p.ElementType != g.ElementType { + return fmt.Errorf("parameter and gradient element type not match, parameter: %v, gradient: %v", p.ElementType, g.ElementType) + } + + r := C.paddle_update_parameter(o.opt, unsafe.Pointer(&p.Content[0]), C.paddle_element_type(p.ElementType), unsafe.Pointer(&g.Content[0]), C.int(len(g.Content))) + if r != 0 { + return fmt.Errorf("optimier returned error code: %d", r) + } + return nil +} + +func (o *optimizer) Cleanup() { + if unsafe.Pointer(o.opt) != nullPtr { + C.paddle_release_optimizer(o.opt) + o.opt = (*C.paddle_optimizer)(nullPtr) + } +} diff --git a/paddle/go/pserver/optimizer.h b/paddle/go/pserver/optimizer.h new file mode 100644 index 00000000000..e1750ca608e --- /dev/null +++ b/paddle/go/pserver/optimizer.h @@ -0,0 +1,19 @@ +#ifndef PADDLE_PSERVER_OPTIMIZER_H +#define PADDLE_PSERVER_OPTIMIZER_H + +typedef enum { + PADDLE_ELEMENT_TYPE_INT32 = 0, + PADDLE_ELEMENT_TYPE_UINT32 = 1, + PADDLE_ELEMENT_TYPE_INT64 = 2, + PADDLE_ELEMENT_TYPE_UINT64 = 3, + PADDLE_ELEMENT_TYPE_FLOAT32 = 4, + PADDLE_ELEMENT_TYPE_FLOAT64 = 5, +} paddle_element_type; + +typedef struct paddle_optimizer paddle_optimizer; + +paddle_optimizer* paddle_create_SGD_optimizer(double learning_rate); +void paddle_release_optimizer(paddle_optimizer* o); +int paddle_update_parameter(paddle_optimizer* o, void *buffer, paddle_element_type datatype, const void* gradient, int num_bytes); + +#endif /* PADDLE_PSERVER_OPTIMIZER_H */ diff --git a/paddle/go/pserver/service.go b/paddle/go/pserver/service.go new file mode 100644 index 00000000000..0d10da9880a --- /dev/null +++ b/paddle/go/pserver/service.go @@ -0,0 +1,165 @@ +package pserver + +import ( + "errors" + "fmt" + "sync" +) + +// ElementType is the type of elements of a Parameter. +type ElementType int + +var ErrUnintialized = errors.New("pserver not initialized") +var ErrAlreadyIntialized = errors.New("pserver already initialized") + +// Supported element types +const ( + Int32 ElementType = iota + UInt32 + Int64 + UInt64 + Float32 + Float64 +) + +// Parameter is a piece of data to sync with the parameter server. +type Parameter struct { + Name string + ElementType ElementType + Content []byte +} + +// ParameterWithConfig contains the parameter and the configuration. +type ParameterWithConfig struct { + Param Parameter + Config []byte // parameter configuration in Proto Buffer format +} + +// Gradient is the gradient of the parameter. +type Gradient Parameter + +type Service struct { + initialized chan struct{} + + mu sync.Mutex + opt *optimizer + paramMap map[string]Parameter +} + +func NewService() *Service { + s := &Service{} + s.paramMap = make(map[string]Parameter) + s.initialized = make(chan struct{}) + return s +} + +func (s *Service) BeginInitParams(config []byte, dummy *int) error { + select { + case <-s.initialized: + return ErrAlreadyIntialized + default: + } + + s.mu.Lock() + defer s.mu.Unlock() + + if s.opt != nil { + s.opt.Cleanup() + } + + // TODO(helin): parse learning rate from config + s.opt = newOptimizer(sgd, 0.01) + return nil +} + +func (s *Service) InitParam(paramWithConfigs ParameterWithConfig, dummy *int) error { + select { + case <-s.initialized: + return ErrAlreadyIntialized + default: + } + + // TODO(helin): parse parameter config + + s.mu.Lock() + defer s.mu.Unlock() + + // TODO(helin): check if paramWithConfigs.Param.Content is + // properly memory aligned, if not, make copy to a memory + // aligned region. + s.paramMap[paramWithConfigs.Param.Name] = paramWithConfigs.Param + return nil +} + +func (s *Service) FinishInitParams(dummy0 int, dummy1 *int) error { + select { + case <-s.initialized: + return ErrAlreadyIntialized + default: + } + + close(s.initialized) + return nil +} + +func (s *Service) SendGrads(grads []Gradient, dummy *int) error { + select { + case <-s.initialized: + default: + return ErrUnintialized + } + + s.mu.Lock() + s.mu.Unlock() + + for _, g := range grads { + if _, ok := s.paramMap[g.Name]; !ok { + return fmt.Errorf("parameter: %s does not exist", g.Name) + } + } + + var wg sync.WaitGroup + for _, g := range grads { + wg.Add(1) + go func(p Parameter, g Gradient) { + s.opt.UpdateParameter(p, g) + wg.Done() + }(s.paramMap[g.Name], g) + } + + wg.Wait() + return nil +} + +func (s *Service) GetParams(names []string, parameters *[]Parameter) error { + <-s.initialized + s.mu.Lock() + s.mu.Unlock() + + for _, n := range names { + if _, ok := s.paramMap[n]; !ok { + return fmt.Errorf("parameter: %s does not exist", n) + } + } + + *parameters = make([]Parameter, len(names)) + for i, n := range names { + // The parameter content (a byte slice) may change + // during RPC serialization due to write from other + // goroutine, we allow it since mini-batch based deep + // learning optimization methods are stochastic in + // nature. This race condition is allowed deliberately + // to save the program from making a copy of the + // paramter content. + (*parameters)[i] = s.paramMap[n] + } + + return nil +} + +func (s *Service) SaveModel(path string, dummy *int) error { + <-s.initialized + + // TODO + return nil +} diff --git a/paddle/go/pserver/service_test.go b/paddle/go/pserver/service_test.go new file mode 100644 index 00000000000..ebeff1fb89c --- /dev/null +++ b/paddle/go/pserver/service_test.go @@ -0,0 +1,154 @@ +package pserver_test + +import ( + "reflect" + "sync" + "testing" + + "github.com/PaddlePaddle/Paddle/paddle/go/pserver" +) + +func TestFull(t *testing.T) { + s := pserver.NewService() + var dummy int + err := s.BeginInitParams(nil, &dummy) + if err != nil { + t.FailNow() + } + + var p pserver.Parameter + p.Name = "param_a" + p.Content = []byte{1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0} + p.ElementType = pserver.Int32 + err = s.InitParam(pserver.ParameterWithConfig{p, nil}, &dummy) + if err != nil { + t.FailNow() + } + + var p1 pserver.Parameter + p1.Name = "param_b" + p1.Content = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} + p1.ElementType = pserver.Float32 + err = s.InitParam(pserver.ParameterWithConfig{p1, nil}, &dummy) + if err != nil { + t.FailNow() + } + + err = s.FinishInitParams(0, &dummy) + if err != nil { + t.FailNow() + } + + var params []pserver.Parameter + err = s.GetParams([]string{"param_b", "param_a"}, ¶ms) + if err != nil { + t.FailNow() + } + + if len(params) != 2 || !reflect.DeepEqual(params[0], p1) || !reflect.DeepEqual(params[0], p1) { + t.FailNow() + } + + grads := []pserver.Gradient{pserver.Gradient(p1), pserver.Gradient(p)} + err = s.SendGrads(grads, &dummy) + if err != nil { + t.FailNow() + } + + var params1 []pserver.Parameter + err = s.GetParams([]string{"param_b", "param_a"}, ¶ms1) + if err != nil { + t.FailNow() + } + + if len(params) != 2 { + t.FailNow() + } + + // we don't care the content, since it's already optimized with gradient + params1[0].Content = nil + params1[0].Content = nil + p.Content = nil + p1.Content = nil + + if !reflect.DeepEqual(params1[0], p1) || !reflect.DeepEqual(params1[0], p1) { + t.FailNow() + } +} + +func TestMultipleInit(t *testing.T) { + s := pserver.NewService() + var dummy int + err := s.BeginInitParams(nil, &dummy) + if err != nil { + t.FailNow() + } + + // this is fine, it's possible for client to call init + // multiple times. + err = s.BeginInitParams(nil, &dummy) + if err != nil { + t.FailNow() + } + + err = s.FinishInitParams(0, &dummy) + if err != nil { + t.FailNow() + } + + err = s.FinishInitParams(0, &dummy) + if err != pserver.ErrAlreadyIntialized { + t.FailNow() + } + + err = s.BeginInitParams(nil, &dummy) + if err != pserver.ErrAlreadyIntialized { + t.FailNow() + } +} + +func TestUninitialized(t *testing.T) { + s := pserver.NewService() + var dummy int + err := s.SendGrads(nil, &dummy) + if err != pserver.ErrUnintialized { + t.FailNow() + } +} + +func TestBlockUntilInitialized(t *testing.T) { + s := pserver.NewService() + var wg sync.WaitGroup + wg.Add(1) + go func() { + var params []pserver.Parameter + err := s.GetParams(nil, ¶ms) + if err != nil { + t.FailNow() + } + wg.Done() + }() + + wg.Add(1) + go func() { + var dummy int + err := s.SaveModel("", &dummy) + if err != nil { + t.FailNow() + } + wg.Done() + }() + + var dummy int + err := s.BeginInitParams(nil, &dummy) + if err != nil { + t.FailNow() + } + + err = s.FinishInitParams(0, &dummy) + if err != nil { + t.FailNow() + } + + wg.Wait() +} -- GitLab From 6ee5bc81c021e063369d1e9ba9333d534219a2cb Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Wed, 17 May 2017 19:51:36 -0400 Subject: [PATCH 0406/3256] use function pointer for updater dispatching --- paddle/go/pserver/optimizer.c | 32 +++++++++++++++++++++++--------- paddle/go/pserver/optimizer.go | 4 ++-- paddle/go/pserver/optimizer.h | 9 ++++----- 3 files changed, 29 insertions(+), 16 deletions(-) diff --git a/paddle/go/pserver/optimizer.c b/paddle/go/pserver/optimizer.c index d83409297ba..123684970f9 100644 --- a/paddle/go/pserver/optimizer.c +++ b/paddle/go/pserver/optimizer.c @@ -2,21 +2,35 @@ #include "optimizer.h" -typedef struct { - double learning_rate; -} SGD_optimizer; +typedef int (*update_func)(void*, void *, paddle_element_type, const void*, int); -paddle_optimizer* paddle_create_SGD_optimizer(double learning_rate) { - SGD_optimizer* o = (SGD_optimizer*)malloc(sizeof(SGD_optimizer)); - o->learning_rate = learning_rate; - return (paddle_optimizer*)o; -} +typedef struct paddle_optimizer{ + update_func func; + void* optimizer; +} paddle_optimizer; void paddle_release_optimizer(paddle_optimizer* o) { free(o); } -int paddle_update_parameter(paddle_optimizer* o, void *buffer, paddle_element_type datatype, const void* gradient, int num_bytes) { +int paddle_update_parameter(paddle_optimizer* o, void *buffer, paddle_element_type element_type, const void* gradient, int num_bytes) { + return o->func(o->optimizer, buffer, element_type, gradient, num_bytes); +} + +typedef struct { + double learning_rate; +} SGD_optimizer; + +int paddle_SGD_update_parameter(void* optimizer, void *buffer, paddle_element_type element_type, const void* gradient, int num_bytes) { // TODO return 0; } + +paddle_optimizer* paddle_create_SGD_optimizer(double learning_rate) { + SGD_optimizer* o = (SGD_optimizer*)malloc(sizeof(SGD_optimizer)); + o->learning_rate = learning_rate; + paddle_optimizer* container = (paddle_optimizer*)malloc(sizeof(paddle_optimizer)); + container->func = paddle_SGD_update_parameter; + container->optimizer = o; + return container; +} diff --git a/paddle/go/pserver/optimizer.go b/paddle/go/pserver/optimizer.go index aa02bed3e0f..8c6450bca0b 100644 --- a/paddle/go/pserver/optimizer.go +++ b/paddle/go/pserver/optimizer.go @@ -18,7 +18,7 @@ const ( var nullPtr = unsafe.Pointer(uintptr(0)) type optimizer struct { - opt *C.paddle_optimizer + opt *C.struct_paddle_optimizer } func newOptimizer(t optimizerType, learning_rate float64) *optimizer { @@ -46,6 +46,6 @@ func (o *optimizer) UpdateParameter(p Parameter, g Gradient) error { func (o *optimizer) Cleanup() { if unsafe.Pointer(o.opt) != nullPtr { C.paddle_release_optimizer(o.opt) - o.opt = (*C.paddle_optimizer)(nullPtr) + o.opt = (*C.struct_paddle_optimizer)(nullPtr) } } diff --git a/paddle/go/pserver/optimizer.h b/paddle/go/pserver/optimizer.h index e1750ca608e..cde8da70cca 100644 --- a/paddle/go/pserver/optimizer.h +++ b/paddle/go/pserver/optimizer.h @@ -10,10 +10,9 @@ typedef enum { PADDLE_ELEMENT_TYPE_FLOAT64 = 5, } paddle_element_type; -typedef struct paddle_optimizer paddle_optimizer; - -paddle_optimizer* paddle_create_SGD_optimizer(double learning_rate); -void paddle_release_optimizer(paddle_optimizer* o); -int paddle_update_parameter(paddle_optimizer* o, void *buffer, paddle_element_type datatype, const void* gradient, int num_bytes); +struct paddle_optimizer; +struct paddle_optimizer* paddle_create_SGD_optimizer(double learning_rate); +void paddle_release_optimizer(struct paddle_optimizer* o); +int paddle_update_parameter(struct paddle_optimizer* o, void *buffer, paddle_element_type element_type, const void* gradient, int num_bytes); #endif /* PADDLE_PSERVER_OPTIMIZER_H */ -- GitLab From bd2469f21c663c192a7ab73b2b2fdfafffdf5edb Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Wed, 17 May 2017 20:01:29 -0400 Subject: [PATCH 0407/3256] correct optimizer release, add test --- paddle/go/pserver/optimizer.c | 18 ++++++++++++++---- paddle/go/pserver/optimizer_test.go | 8 ++++++++ 2 files changed, 22 insertions(+), 4 deletions(-) create mode 100644 paddle/go/pserver/optimizer_test.go diff --git a/paddle/go/pserver/optimizer.c b/paddle/go/pserver/optimizer.c index 123684970f9..36a612a56f4 100644 --- a/paddle/go/pserver/optimizer.c +++ b/paddle/go/pserver/optimizer.c @@ -3,34 +3,44 @@ #include "optimizer.h" typedef int (*update_func)(void*, void *, paddle_element_type, const void*, int); +typedef void (*release_func)(void*); typedef struct paddle_optimizer{ - update_func func; + update_func update; + release_func release; void* optimizer; } paddle_optimizer; void paddle_release_optimizer(paddle_optimizer* o) { + o->release(o->optimizer); free(o); } int paddle_update_parameter(paddle_optimizer* o, void *buffer, paddle_element_type element_type, const void* gradient, int num_bytes) { - return o->func(o->optimizer, buffer, element_type, gradient, num_bytes); + return o->update(o->optimizer, buffer, element_type, gradient, num_bytes); } typedef struct { double learning_rate; } SGD_optimizer; -int paddle_SGD_update_parameter(void* optimizer, void *buffer, paddle_element_type element_type, const void* gradient, int num_bytes) { +int update_SGD(void* optimizer, void *buffer, paddle_element_type element_type, const void* gradient, int num_bytes) { + SGD_optimizer* o = (SGD_optimizer*)optimizer; // TODO return 0; } +void release_SGD(void *optimizer) { + SGD_optimizer* o = (SGD_optimizer*)optimizer; + // nothing allocated on heap +} + paddle_optimizer* paddle_create_SGD_optimizer(double learning_rate) { SGD_optimizer* o = (SGD_optimizer*)malloc(sizeof(SGD_optimizer)); o->learning_rate = learning_rate; paddle_optimizer* container = (paddle_optimizer*)malloc(sizeof(paddle_optimizer)); - container->func = paddle_SGD_update_parameter; + container->update = update_SGD; + container->release = release_SGD; container->optimizer = o; return container; } diff --git a/paddle/go/pserver/optimizer_test.go b/paddle/go/pserver/optimizer_test.go new file mode 100644 index 00000000000..64d6d092aa1 --- /dev/null +++ b/paddle/go/pserver/optimizer_test.go @@ -0,0 +1,8 @@ +package pserver + +import "testing" + +func TestSGDCreateRelease(t *testing.T) { + o := newOptimizer(sgd, 1) + o.Cleanup() +} -- GitLab From 4808e22e04c2448f6c65933197716a9bbb037766 Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Wed, 17 May 2017 20:15:50 -0400 Subject: [PATCH 0408/3256] fix typo --- paddle/go/pserver/optimizer.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paddle/go/pserver/optimizer.go b/paddle/go/pserver/optimizer.go index 8c6450bca0b..64bdefe660a 100644 --- a/paddle/go/pserver/optimizer.go +++ b/paddle/go/pserver/optimizer.go @@ -38,7 +38,7 @@ func (o *optimizer) UpdateParameter(p Parameter, g Gradient) error { r := C.paddle_update_parameter(o.opt, unsafe.Pointer(&p.Content[0]), C.paddle_element_type(p.ElementType), unsafe.Pointer(&g.Content[0]), C.int(len(g.Content))) if r != 0 { - return fmt.Errorf("optimier returned error code: %d", r) + return fmt.Errorf("optimizer update returned error code: %d", r) } return nil } -- GitLab From bc33f9b165d15254a434b3175f465dd2e4e7f70f Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Wed, 17 May 2017 20:19:14 -0400 Subject: [PATCH 0409/3256] fix bug lock is released too soon --- paddle/go/pserver/service.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/paddle/go/pserver/service.go b/paddle/go/pserver/service.go index 0d10da9880a..22f6cdf40d4 100644 --- a/paddle/go/pserver/service.go +++ b/paddle/go/pserver/service.go @@ -110,7 +110,7 @@ func (s *Service) SendGrads(grads []Gradient, dummy *int) error { } s.mu.Lock() - s.mu.Unlock() + defer s.mu.Unlock() for _, g := range grads { if _, ok := s.paramMap[g.Name]; !ok { @@ -134,7 +134,7 @@ func (s *Service) SendGrads(grads []Gradient, dummy *int) error { func (s *Service) GetParams(names []string, parameters *[]Parameter) error { <-s.initialized s.mu.Lock() - s.mu.Unlock() + defer s.mu.Unlock() for _, n := range names { if _, ok := s.paramMap[n]; !ok { -- GitLab From e39e14d1572be690a513dff435b639221c35311d Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Wed, 17 May 2017 20:25:47 -0400 Subject: [PATCH 0410/3256] handle error from s.opt.UpdateParameter --- paddle/go/pserver/service.go | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/paddle/go/pserver/service.go b/paddle/go/pserver/service.go index 22f6cdf40d4..47a862c5ad2 100644 --- a/paddle/go/pserver/service.go +++ b/paddle/go/pserver/service.go @@ -109,6 +109,11 @@ func (s *Service) SendGrads(grads []Gradient, dummy *int) error { return ErrUnintialized } + count := len(grads) + if count == 0 { + return nil + } + s.mu.Lock() defer s.mu.Unlock() @@ -118,16 +123,25 @@ func (s *Service) SendGrads(grads []Gradient, dummy *int) error { } } - var wg sync.WaitGroup + errCh := make(chan error, count) for _, g := range grads { - wg.Add(1) go func(p Parameter, g Gradient) { - s.opt.UpdateParameter(p, g) - wg.Done() + err := s.opt.UpdateParameter(p, g) + errCh <- err }(s.paramMap[g.Name], g) } - wg.Wait() + recv := 0 + for err := range errCh { + if err != nil { + return err + } + + recv++ + if recv == count { + break + } + } return nil } -- GitLab From f4bc10daac82d8e43406f19faac673ae972b9152 Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Wed, 17 May 2017 20:27:39 -0400 Subject: [PATCH 0411/3256] update comment --- paddle/go/pserver/service_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/paddle/go/pserver/service_test.go b/paddle/go/pserver/service_test.go index ebeff1fb89c..78dd4d6b58c 100644 --- a/paddle/go/pserver/service_test.go +++ b/paddle/go/pserver/service_test.go @@ -65,7 +65,8 @@ func TestFull(t *testing.T) { t.FailNow() } - // we don't care the content, since it's already optimized with gradient + // don't compare content, since it's already changed by + // gradient update. params1[0].Content = nil params1[0].Content = nil p.Content = nil -- GitLab From 9920a06cc6a4a8987b85cd2ad0d0898c74a2bacf Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Wed, 17 May 2017 20:32:40 -0400 Subject: [PATCH 0412/3256] rename local variable --- paddle/go/pserver/optimizer.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/paddle/go/pserver/optimizer.c b/paddle/go/pserver/optimizer.c index 36a612a56f4..8d63089b4cd 100644 --- a/paddle/go/pserver/optimizer.c +++ b/paddle/go/pserver/optimizer.c @@ -36,11 +36,11 @@ void release_SGD(void *optimizer) { } paddle_optimizer* paddle_create_SGD_optimizer(double learning_rate) { - SGD_optimizer* o = (SGD_optimizer*)malloc(sizeof(SGD_optimizer)); - o->learning_rate = learning_rate; - paddle_optimizer* container = (paddle_optimizer*)malloc(sizeof(paddle_optimizer)); - container->update = update_SGD; - container->release = release_SGD; - container->optimizer = o; - return container; + SGD_optimizer* impl = (SGD_optimizer*)malloc(sizeof(SGD_optimizer)); + impl->learning_rate = learning_rate; + paddle_optimizer* opt = (paddle_optimizer*)malloc(sizeof(paddle_optimizer)); + opt->update = update_SGD; + opt->release = release_SGD; + opt->optimizer = impl; + return opt; } -- GitLab From 27fdccc38040349125f0ab601870649a4c5d4e3e Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Fri, 19 May 2017 15:04:45 -0400 Subject: [PATCH 0413/3256] fix according to comments --- paddle/go/pserver/service.go | 15 +++++---------- paddle/go/pserver/service_test.go | 23 ++++++++++++----------- 2 files changed, 17 insertions(+), 21 deletions(-) diff --git a/paddle/go/pserver/service.go b/paddle/go/pserver/service.go index 47a862c5ad2..3bf26b7651e 100644 --- a/paddle/go/pserver/service.go +++ b/paddle/go/pserver/service.go @@ -9,8 +9,7 @@ import ( // ElementType is the type of elements of a Parameter. type ElementType int -var ErrUnintialized = errors.New("pserver not initialized") -var ErrAlreadyIntialized = errors.New("pserver already initialized") +var ErrAlreadyInitialized = errors.New("pserver already initialized") // Supported element types const ( @@ -56,7 +55,7 @@ func NewService() *Service { func (s *Service) BeginInitParams(config []byte, dummy *int) error { select { case <-s.initialized: - return ErrAlreadyIntialized + return ErrAlreadyInitialized default: } @@ -75,7 +74,7 @@ func (s *Service) BeginInitParams(config []byte, dummy *int) error { func (s *Service) InitParam(paramWithConfigs ParameterWithConfig, dummy *int) error { select { case <-s.initialized: - return ErrAlreadyIntialized + return ErrAlreadyInitialized default: } @@ -94,7 +93,7 @@ func (s *Service) InitParam(paramWithConfigs ParameterWithConfig, dummy *int) er func (s *Service) FinishInitParams(dummy0 int, dummy1 *int) error { select { case <-s.initialized: - return ErrAlreadyIntialized + return ErrAlreadyInitialized default: } @@ -103,11 +102,7 @@ func (s *Service) FinishInitParams(dummy0 int, dummy1 *int) error { } func (s *Service) SendGrads(grads []Gradient, dummy *int) error { - select { - case <-s.initialized: - default: - return ErrUnintialized - } + <-s.initialized count := len(grads) if count == 0 { diff --git a/paddle/go/pserver/service_test.go b/paddle/go/pserver/service_test.go index 78dd4d6b58c..437d14b28c0 100644 --- a/paddle/go/pserver/service_test.go +++ b/paddle/go/pserver/service_test.go @@ -98,21 +98,12 @@ func TestMultipleInit(t *testing.T) { } err = s.FinishInitParams(0, &dummy) - if err != pserver.ErrAlreadyIntialized { + if err != pserver.ErrAlreadyInitialized { t.FailNow() } err = s.BeginInitParams(nil, &dummy) - if err != pserver.ErrAlreadyIntialized { - t.FailNow() - } -} - -func TestUninitialized(t *testing.T) { - s := pserver.NewService() - var dummy int - err := s.SendGrads(nil, &dummy) - if err != pserver.ErrUnintialized { + if err != pserver.ErrAlreadyInitialized { t.FailNow() } } @@ -140,6 +131,16 @@ func TestBlockUntilInitialized(t *testing.T) { wg.Done() }() + wg.Add(1) + go func() { + var dummy int + err := s.SendGrads(nil, &dummy) + if err != nil { + t.FailNow() + } + wg.Done() + }() + var dummy int err := s.BeginInitParams(nil, &dummy) if err != nil { -- GitLab From 44d60bd91eb696a617f50cacc6257b9af76accc1 Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Fri, 19 May 2017 15:11:23 -0400 Subject: [PATCH 0414/3256] add comments for exported functions --- paddle/go/pserver/service.go | 13 ++++++++++++- paddle/go/pserver/service_test.go | 2 +- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/paddle/go/pserver/service.go b/paddle/go/pserver/service.go index 3bf26b7651e..a009b456330 100644 --- a/paddle/go/pserver/service.go +++ b/paddle/go/pserver/service.go @@ -37,6 +37,7 @@ type ParameterWithConfig struct { // Gradient is the gradient of the parameter. type Gradient Parameter +// Service is the RPC service for pserver. type Service struct { initialized chan struct{} @@ -45,6 +46,7 @@ type Service struct { paramMap map[string]Parameter } +// NewService creates a new service. func NewService() *Service { s := &Service{} s.paramMap = make(map[string]Parameter) @@ -52,6 +54,8 @@ func NewService() *Service { return s } +// BeginInitParams tells the parameter server that the parameter +// initialization has begun. func (s *Service) BeginInitParams(config []byte, dummy *int) error { select { case <-s.initialized: @@ -71,6 +75,7 @@ func (s *Service) BeginInitParams(config []byte, dummy *int) error { return nil } +// InitParam initializes a parameter. func (s *Service) InitParam(paramWithConfigs ParameterWithConfig, dummy *int) error { select { case <-s.initialized: @@ -90,6 +95,8 @@ func (s *Service) InitParam(paramWithConfigs ParameterWithConfig, dummy *int) er return nil } +// FinishInitParams tells the parameter server that the parameter +// initialization has finished. func (s *Service) FinishInitParams(dummy0 int, dummy1 *int) error { select { case <-s.initialized: @@ -101,6 +108,8 @@ func (s *Service) FinishInitParams(dummy0 int, dummy1 *int) error { return nil } +// SendGrads sends gradients to parameter servers for parameter +// optimization. func (s *Service) SendGrads(grads []Gradient, dummy *int) error { <-s.initialized @@ -140,6 +149,7 @@ func (s *Service) SendGrads(grads []Gradient, dummy *int) error { return nil } +// GetParams gets parameters from the parameter server. func (s *Service) GetParams(names []string, parameters *[]Parameter) error { <-s.initialized s.mu.Lock() @@ -166,7 +176,8 @@ func (s *Service) GetParams(names []string, parameters *[]Parameter) error { return nil } -func (s *Service) SaveModel(path string, dummy *int) error { +// Save tells the parameter server to save parameters. +func (s *Service) Save(path string, dummy *int) error { <-s.initialized // TODO diff --git a/paddle/go/pserver/service_test.go b/paddle/go/pserver/service_test.go index 437d14b28c0..23b2d17dc7f 100644 --- a/paddle/go/pserver/service_test.go +++ b/paddle/go/pserver/service_test.go @@ -124,7 +124,7 @@ func TestBlockUntilInitialized(t *testing.T) { wg.Add(1) go func() { var dummy int - err := s.SaveModel("", &dummy) + err := s.Save("", &dummy) if err != nil { t.FailNow() } -- GitLab From ea18f2eeb6bd609be321d997a3c21ded52801fa7 Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Fri, 19 May 2017 15:15:46 -0400 Subject: [PATCH 0415/3256] make test more precise --- paddle/go/pserver/service_test.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/paddle/go/pserver/service_test.go b/paddle/go/pserver/service_test.go index 23b2d17dc7f..6aa7f47c747 100644 --- a/paddle/go/pserver/service_test.go +++ b/paddle/go/pserver/service_test.go @@ -110,6 +110,7 @@ func TestMultipleInit(t *testing.T) { func TestBlockUntilInitialized(t *testing.T) { s := pserver.NewService() + ch := make(chan struct{}, 3) var wg sync.WaitGroup wg.Add(1) go func() { @@ -119,6 +120,7 @@ func TestBlockUntilInitialized(t *testing.T) { t.FailNow() } wg.Done() + ch <- struct{}{} }() wg.Add(1) @@ -129,6 +131,7 @@ func TestBlockUntilInitialized(t *testing.T) { t.FailNow() } wg.Done() + ch <- struct{}{} }() wg.Add(1) @@ -139,6 +142,7 @@ func TestBlockUntilInitialized(t *testing.T) { t.FailNow() } wg.Done() + ch <- struct{}{} }() var dummy int @@ -147,6 +151,13 @@ func TestBlockUntilInitialized(t *testing.T) { t.FailNow() } + select { + case <-ch: + // some function returned before initialization is completed. + t.FailNow() + default: + } + err = s.FinishInitParams(0, &dummy) if err != nil { t.FailNow() -- GitLab From e2fae1685de73772fb2b46ed67b4ddc0b897c83c Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Fri, 19 May 2017 15:30:53 -0400 Subject: [PATCH 0416/3256] SendGrad will return error if pserver is not initialized. --- paddle/go/pserver/service.go | 7 ++++++- paddle/go/pserver/service_test.go | 22 ++++++++++------------ 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/paddle/go/pserver/service.go b/paddle/go/pserver/service.go index a009b456330..f43e59403a7 100644 --- a/paddle/go/pserver/service.go +++ b/paddle/go/pserver/service.go @@ -10,6 +10,7 @@ import ( type ElementType int var ErrAlreadyInitialized = errors.New("pserver already initialized") +var ErrUninitialized = errors.New("pserver not fully initialized") // Supported element types const ( @@ -111,7 +112,11 @@ func (s *Service) FinishInitParams(dummy0 int, dummy1 *int) error { // SendGrads sends gradients to parameter servers for parameter // optimization. func (s *Service) SendGrads(grads []Gradient, dummy *int) error { - <-s.initialized + select { + case <-s.initialized: + default: + return ErrUninitialized + } count := len(grads) if count == 0 { diff --git a/paddle/go/pserver/service_test.go b/paddle/go/pserver/service_test.go index 6aa7f47c747..10185bd0f20 100644 --- a/paddle/go/pserver/service_test.go +++ b/paddle/go/pserver/service_test.go @@ -108,9 +108,18 @@ func TestMultipleInit(t *testing.T) { } } +func TestUninitialized(t *testing.T) { + s := pserver.NewService() + var dummy int + err := s.SendGrads(nil, &dummy) + if err != pserver.ErrUninitialized { + t.FailNow() + } +} + func TestBlockUntilInitialized(t *testing.T) { s := pserver.NewService() - ch := make(chan struct{}, 3) + ch := make(chan struct{}, 2) var wg sync.WaitGroup wg.Add(1) go func() { @@ -134,17 +143,6 @@ func TestBlockUntilInitialized(t *testing.T) { ch <- struct{}{} }() - wg.Add(1) - go func() { - var dummy int - err := s.SendGrads(nil, &dummy) - if err != nil { - t.FailNow() - } - wg.Done() - ch <- struct{}{} - }() - var dummy int err := s.BeginInitParams(nil, &dummy) if err != nil { -- GitLab From 599eb3663e3124f976dfa3d487e47534df61a899 Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Sun, 21 May 2017 19:29:34 -0400 Subject: [PATCH 0417/3256] do clang-format --- paddle/go/pserver/optimizer.c | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/paddle/go/pserver/optimizer.c b/paddle/go/pserver/optimizer.c index 8d63089b4cd..b8da3ec9592 100644 --- a/paddle/go/pserver/optimizer.c +++ b/paddle/go/pserver/optimizer.c @@ -2,10 +2,10 @@ #include "optimizer.h" -typedef int (*update_func)(void*, void *, paddle_element_type, const void*, int); +typedef int (*update_func)(void*, void*, paddle_element_type, const void*, int); typedef void (*release_func)(void*); -typedef struct paddle_optimizer{ +typedef struct paddle_optimizer { update_func update; release_func release; void* optimizer; @@ -16,23 +16,29 @@ void paddle_release_optimizer(paddle_optimizer* o) { free(o); } -int paddle_update_parameter(paddle_optimizer* o, void *buffer, paddle_element_type element_type, const void* gradient, int num_bytes) { +int paddle_update_parameter(paddle_optimizer* o, + void* buffer, + paddle_element_type element_type, + const void* gradient, + int num_bytes) { return o->update(o->optimizer, buffer, element_type, gradient, num_bytes); } -typedef struct { - double learning_rate; -} SGD_optimizer; +typedef struct { double learning_rate; } SGD_optimizer; -int update_SGD(void* optimizer, void *buffer, paddle_element_type element_type, const void* gradient, int num_bytes) { +int update_SGD(void* optimizer, + void* buffer, + paddle_element_type element_type, + const void* gradient, + int num_bytes) { SGD_optimizer* o = (SGD_optimizer*)optimizer; // TODO return 0; } -void release_SGD(void *optimizer) { - SGD_optimizer* o = (SGD_optimizer*)optimizer; - // nothing allocated on heap +void release_SGD(void* optimizer) { + SGD_optimizer* o = (SGD_optimizer*)optimizer; + // nothing allocated on heap } paddle_optimizer* paddle_create_SGD_optimizer(double learning_rate) { -- GitLab From 97dbb7609c80eb6195f18f28ddf8c7f9134a9c4b Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Sun, 21 May 2017 19:30:52 -0400 Subject: [PATCH 0418/3256] resolve compile caused by merging branches --- paddle/go/pserver/client.go | 29 ----------------------------- 1 file changed, 29 deletions(-) diff --git a/paddle/go/pserver/client.go b/paddle/go/pserver/client.go index 5b110af648d..1c98aea6d1c 100644 --- a/paddle/go/pserver/client.go +++ b/paddle/go/pserver/client.go @@ -1,34 +1,5 @@ package pserver -// ElementType is the type of elements of a Parameter. -type ElementType int - -// Supported element types -const ( - Int32 ElementType = iota - UInt32 - Int64 - UInt64 - Float32 - Float64 -) - -// Parameter is a piece of data to sync with the parameter server. -type Parameter struct { - Name string - ElementType ElementType - Content []byte -} - -// ParameterWithConfig contains the parameter and the configuration. -type ParameterWithConfig struct { - Param Parameter - Config []byte // parameter configuration in Proto Buffer format -} - -// Gradient is the gradient of the parameter. -type Gradient Parameter - // Client is the client to parameter servers. type Client struct { } -- GitLab From 25c3f118f00b601fa64d3984a7eb44c572bf287e Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Sun, 21 May 2017 20:32:55 -0400 Subject: [PATCH 0419/3256] apply clang-format --- paddle/go/pserver/optimizer.h | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/paddle/go/pserver/optimizer.h b/paddle/go/pserver/optimizer.h index cde8da70cca..a7e3ff05300 100644 --- a/paddle/go/pserver/optimizer.h +++ b/paddle/go/pserver/optimizer.h @@ -2,10 +2,10 @@ #define PADDLE_PSERVER_OPTIMIZER_H typedef enum { - PADDLE_ELEMENT_TYPE_INT32 = 0, - PADDLE_ELEMENT_TYPE_UINT32 = 1, - PADDLE_ELEMENT_TYPE_INT64 = 2, - PADDLE_ELEMENT_TYPE_UINT64 = 3, + PADDLE_ELEMENT_TYPE_INT32 = 0, + PADDLE_ELEMENT_TYPE_UINT32 = 1, + PADDLE_ELEMENT_TYPE_INT64 = 2, + PADDLE_ELEMENT_TYPE_UINT64 = 3, PADDLE_ELEMENT_TYPE_FLOAT32 = 4, PADDLE_ELEMENT_TYPE_FLOAT64 = 5, } paddle_element_type; @@ -13,6 +13,10 @@ typedef enum { struct paddle_optimizer; struct paddle_optimizer* paddle_create_SGD_optimizer(double learning_rate); void paddle_release_optimizer(struct paddle_optimizer* o); -int paddle_update_parameter(struct paddle_optimizer* o, void *buffer, paddle_element_type element_type, const void* gradient, int num_bytes); +int paddle_update_parameter(struct paddle_optimizer* o, + void* buffer, + paddle_element_type element_type, + const void* gradient, + int num_bytes); #endif /* PADDLE_PSERVER_OPTIMIZER_H */ -- GitLab From a0a3a68551dd41a168eaaf88b9de7bcd37bfe5e8 Mon Sep 17 00:00:00 2001 From: xuwei06 Date: Fri, 19 May 2017 11:52:15 -0700 Subject: [PATCH 0420/3256] Add Sqrt and Reciprocal activation --- .../activations/ActivationFunction.cpp | 38 +++++++++++++++++++ .../trainer_config_helpers/activations.py | 26 ++++++++++++- .../trainer_config_helpers/layer_math.py | 2 + .../tests/configs/math_ops.py | 2 + .../tests/configs/protostr/math_ops.protostr | 34 ++++++++++++++++- python/paddle/v2/trainer.py | 2 +- 6 files changed, 101 insertions(+), 3 deletions(-) diff --git a/paddle/gserver/activations/ActivationFunction.cpp b/paddle/gserver/activations/ActivationFunction.cpp index c541b72e104..a40530f4131 100644 --- a/paddle/gserver/activations/ActivationFunction.cpp +++ b/paddle/gserver/activations/ActivationFunction.cpp @@ -396,6 +396,44 @@ Error __must_check backward(Argument& act) { } END_DEFINE_ACTIVATION(exponential) +/** + * @brief Reciprocal Activation. + * \f[ + * f(z) = 1/z + * \f] + */ +BEGIN_DEFINE_ACTIVATION(reciprocal) +Error __must_check forward(Argument& act) { + act.value->reciprocal2(); + return Error(); +} + +Error __must_check backward(Argument& act) { + act.grad->dotMulSquare(*act.value); + act.grad->neg(); + return Error(); +} +END_DEFINE_ACTIVATION(reciprocal) + +/** + * @brief Square Root Activation. + * \f[ + * f(z) = sqrt(z) + * \f] + */ +BEGIN_DEFINE_ACTIVATION(sqrt) +Error __must_check forward(Argument& act) { + act.value->sqrt2(); + return Error(); +} + +Error __must_check backward(Argument& act) { + act.grad->dotDiv(*act.grad, *act.value); + act.grad->mulScalar(0.5); + return Error(); +} +END_DEFINE_ACTIVATION(sqrt) + /** * @brief Logarithm Activation. * \f[ diff --git a/python/paddle/trainer_config_helpers/activations.py b/python/paddle/trainer_config_helpers/activations.py index 06be3e45993..c749fa827fe 100644 --- a/python/paddle/trainer_config_helpers/activations.py +++ b/python/paddle/trainer_config_helpers/activations.py @@ -17,7 +17,7 @@ __all__ = [ "IdentityActivation", "LinearActivation", 'SequenceSoftmaxActivation', 'ExpActivation', "ReluActivation", "BReluActivation", "SoftReluActivation", "STanhActivation", "AbsActivation", "SquareActivation", "BaseActivation", - "LogActivation" + "LogActivation", "SqrtActivation", "ReciprocalActivation" ] @@ -224,3 +224,27 @@ class LogActivation(BaseActivation): def __init__(self): BaseActivation.__init__(self, 'log', False) + + +class SqrtActivation(BaseActivation): + """ + Square Root Activation. + + .. math:: + f(z) = sqrt(z) + """ + + def __init__(self): + BaseActivation.__init__(self, 'sqrt', False) + + +class ReciprocalActivation(BaseActivation): + """ + Reciprocal Activation. + + .. math:: + f(z) = 1/z + """ + + def __init__(self): + BaseActivation.__init__(self, 'reciprocal', False) diff --git a/python/paddle/trainer_config_helpers/layer_math.py b/python/paddle/trainer_config_helpers/layer_math.py index 544b4438253..e1c8f0c3500 100644 --- a/python/paddle/trainer_config_helpers/layer_math.py +++ b/python/paddle/trainer_config_helpers/layer_math.py @@ -40,6 +40,8 @@ register_unary_math_op('sigmoid', act.SigmoidActivation()) register_unary_math_op('tanh', act.TanhActivation()) register_unary_math_op('square', act.SquareActivation()) register_unary_math_op('relu', act.ReluActivation()) +register_unary_math_op('sqrt', act.SqrtActivation()) +register_unary_math_op('reciprocal', act.ReciprocalActivation()) def add(layeroutput, other): diff --git a/python/paddle/trainer_config_helpers/tests/configs/math_ops.py b/python/paddle/trainer_config_helpers/tests/configs/math_ops.py index 24c901c8ee3..a607a62c99f 100644 --- a/python/paddle/trainer_config_helpers/tests/configs/math_ops.py +++ b/python/paddle/trainer_config_helpers/tests/configs/math_ops.py @@ -4,6 +4,8 @@ settings(batch_size=1000, learning_rate=1e-5) x = data_layer(name='data', size=100) x = layer_math.exp(x) +x = layer_math.sqrt(x) +x = layer_math.reciprocal(x) x = layer_math.log(x) x = layer_math.abs(x) x = layer_math.sigmoid(x) diff --git a/python/paddle/trainer_config_helpers/tests/configs/protostr/math_ops.protostr b/python/paddle/trainer_config_helpers/tests/configs/protostr/math_ops.protostr index 9b8a2ad9687..eaaf7fd6f5b 100644 --- a/python/paddle/trainer_config_helpers/tests/configs/protostr/math_ops.protostr +++ b/python/paddle/trainer_config_helpers/tests/configs/protostr/math_ops.protostr @@ -20,13 +20,43 @@ layers { } } } +layers { + name: "__sqrt_0__" + type: "mixed" + size: 100 + active_type: "sqrt" + inputs { + input_layer_name: "__exp_0__" + proj_conf { + type: "identity" + name: "___sqrt_0__.w0" + input_size: 100 + output_size: 100 + } + } +} +layers { + name: "__reciprocal_0__" + type: "mixed" + size: 100 + active_type: "reciprocal" + inputs { + input_layer_name: "__sqrt_0__" + proj_conf { + type: "identity" + name: "___reciprocal_0__.w0" + input_size: 100 + output_size: 100 + } + } +} layers { name: "__log_0__" type: "mixed" size: 100 active_type: "log" inputs { - input_layer_name: "__exp_0__" + input_layer_name: "__reciprocal_0__" proj_conf { type: "identity" name: "___log_0__.w0" @@ -351,6 +381,8 @@ sub_models { name: "root" layer_names: "data" layer_names: "__exp_0__" + layer_names: "__sqrt_0__" + layer_names: "__reciprocal_0__" layer_names: "__log_0__" layer_names: "__abs_0__" layer_names: "__sigmoid_0__" diff --git a/python/paddle/v2/trainer.py b/python/paddle/v2/trainer.py index ec9fcfb749f..8fdb67cc268 100644 --- a/python/paddle/v2/trainer.py +++ b/python/paddle/v2/trainer.py @@ -177,7 +177,7 @@ class SGD(object): Testing method. Will test input data. :param reader: A reader that reads and yeilds data items. - :type reader: collections.Iterable + :type reader: collections.Iterable :param feeding: Feeding is a map of neural network input name and array index that reader returns. :type feeding: dict -- GitLab From b3ea63470518247c7df929459372c8f424294b44 Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Mon, 22 May 2017 12:47:54 -0700 Subject: [PATCH 0421/3256] add go 1.8.1 into dev image --- Dockerfile | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Dockerfile b/Dockerfile index ad0d086d3c6..571c3e1476e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -33,6 +33,15 @@ RUN apt-get update && \ clang-3.8 llvm-3.8 libclang-3.8-dev && \ apt-get clean -y +# Install Go +RUN wget -O go.tgz https://storage.googleapis.com/golang/go1.8.1.linux-amd64.tar.gz && \ + tar -C /usr/local -xzf go.tgz && \ + mkdir /root/gopath && \ + rm go.tgz +ENV GOROOT=/usr/local/go GOPATH=/root/gopath +# should not be in the same line with GOROOT definition, otherwise docker build could not find GOROOT. +ENV PATH=${PATH}:${GOROOT}/bin + # git credential to skip password typing RUN git config --global credential.helper store -- GitLab From 3ae7541ad38f261eeee5f55c30a1321907d91553 Mon Sep 17 00:00:00 2001 From: Luo Tao Date: Fri, 19 May 2017 18:30:54 +0800 Subject: [PATCH 0422/3256] rename AggregateLevel and ExpandLevel --- .../deep_model/rnn/hierarchical_layer_cn.rst | 16 ++++---- .../rnn/hrnn_rnn_api_compare_cn.rst | 2 +- .../tests/sequence_nest_layer_group.conf | 4 +- .../paddle/trainer_config_helpers/layers.py | 40 +++++++++---------- .../tests/configs/last_first_seq.py | 5 ++- .../tests/configs/test_expand_layer.py | 4 +- .../tests/configs/test_sequence_pooling.py | 2 +- python/paddle/v2/tests/test_layer.py | 2 +- 8 files changed, 39 insertions(+), 36 deletions(-) diff --git a/doc/howto/deep_model/rnn/hierarchical_layer_cn.rst b/doc/howto/deep_model/rnn/hierarchical_layer_cn.rst index 79048e92482..e05173c2006 100644 --- a/doc/howto/deep_model/rnn/hierarchical_layer_cn.rst +++ b/doc/howto/deep_model/rnn/hierarchical_layer_cn.rst @@ -28,17 +28,17 @@ pooling 的使用示例如下,详细见 :ref:`api_v2.layer_pooling` 配置API seq_pool = pooling(input=layer, pooling_type=pooling.Max(), - agg_level=AggregateLevel.EACH_SEQUENCE) + agg_level=AggregateLevel.TO_SEQUENCE) - `pooling_type` 目前支持两种,分别是:pooling.Max()和pooling.Avg()。 -- `agg_level=AggregateLevel.EACH_TIMESTEP` 时(默认值): +- `agg_level=AggregateLevel.TO_NO_SEQUENCE` 时(默认值): - 作用:双层序列经过运算变成一个0层序列,或单层序列经过运算变成一个0层序列 - 输入:一个双层序列,或一个单层序列 - 输出:一个0层序列,即整个输入序列(单层或双层)的平均值(或最大值) -- `agg_level=AggregateLevel.EACH_SEQUENCE` 时: +- `agg_level=AggregateLevel.TO_SEQUENCE` 时: - 作用:一个双层序列经过运算变成一个单层序列 - 输入:必须是一个双层序列 @@ -52,15 +52,15 @@ last_seq 的使用示例如下( :ref:`api_v2.layer_first_seq` 类似),详 .. code-block:: bash last = last_seq(input=layer, - agg_level=AggregateLevel.EACH_SEQUENCE) + agg_level=AggregateLevel.TO_SEQUENCE) -- `agg_level=AggregateLevel.EACH_TIMESTEP` 时(默认值): +- `agg_level=AggregateLevel.TO_NO_SEQUENCE` 时(默认值): - 作用:一个双层序列经过运算变成一个0层序列,或一个单层序列经过运算变成一个0层序列 - 输入:一个双层序列或一个单层序列 - 输出:一个0层序列,即整个输入序列(双层或者单层)最后一个,或第一个元素。 -- `agg_level=AggregateLevel.EACH_SEQUENCE` 时: +- `agg_level=AggregateLevel.TO_SEQUENCE` 时: - 作用:一个双层序列经过运算变成一个单层序列 - 输入:必须是一个双层序列 - 输出:一个单层序列,其中每个元素是双层序列中每个subseq最后一个(或第一个)元素。 @@ -74,9 +74,9 @@ expand 的使用示例如下,详细见 :ref:`api_v2.layer_expand` 配置API。 ex = expand(input=layer1, expand_as=layer2, - expand_level=ExpandLevel.FROM_TIMESTEP) + expand_level=ExpandLevel.FROM_NO_SEQUENCE) -- `expand_level=ExpandLevel.FROM_TIMESTEP` 时(默认值): +- `expand_level=ExpandLevel.FROM_NO_SEQUENCE` 时(默认值): - 作用:一个0层序列经过运算扩展成一个单层序列,或者一个双层序列 - 输入:layer1必须是一个0层序列,是待扩展的数据;layer2 可以是一个单层序列,或者是一个双层序列,提供扩展的长度信息 diff --git a/doc/howto/deep_model/rnn/hrnn_rnn_api_compare_cn.rst b/doc/howto/deep_model/rnn/hrnn_rnn_api_compare_cn.rst index 96e52b910a2..efdc44455ea 100644 --- a/doc/howto/deep_model/rnn/hrnn_rnn_api_compare_cn.rst +++ b/doc/howto/deep_model/rnn/hrnn_rnn_api_compare_cn.rst @@ -81,7 +81,7 @@ * 在本例中,我们将原始数据的每一组,通过\ :code:`recurrent_group`\ 进行拆解,拆解成的每一句话再通过一个LSTM网络。这和单层RNN的配置是等价的。 -* 与单层RNN的配置类似,我们只需要使用LSTM encode成的最后一个向量。所以对\ :code:`recurrent_group`\ 进行了\ :code:`last_seq`\ 操作。但和单层RNN不同,我们是对每一个子序列取最后一个元素,因此\ :code:`agg_level=AggregateLevel.EACH_SEQUENCE`\ 。 +* 与单层RNN的配置类似,我们只需要使用LSTM encode成的最后一个向量。所以对\ :code:`recurrent_group`\ 进行了\ :code:`last_seq`\ 操作。但和单层RNN不同,我们是对每一个子序列取最后一个元素,因此\ :code:`agg_level=AggregateLevel.TO_SEQUENCE`\ 。 * 至此,\ :code:`lstm_last`\ 便和单层RNN配置中的\ :code:`lstm_last`\ 具有相同的结果了。 diff --git a/paddle/gserver/tests/sequence_nest_layer_group.conf b/paddle/gserver/tests/sequence_nest_layer_group.conf index c01b95f7a29..71ef53d08a2 100644 --- a/paddle/gserver/tests/sequence_nest_layer_group.conf +++ b/paddle/gserver/tests/sequence_nest_layer_group.conf @@ -59,7 +59,7 @@ lstm_nest_group = recurrent_group( input=SubsequenceInput(emb_group), step=lstm_group, name="lstm_nest_group") # hasSubseq ->(seqlastins) seq lstm_last = last_seq( - input=lstm_nest_group, agg_level=AggregateLevel.EACH_SEQUENCE) + input=lstm_nest_group, agg_level=AggregateLevel.TO_SEQUENCE) # seq ->(expand) hasSubseq lstm_expand = expand_layer( @@ -71,7 +71,7 @@ lstm_expand = expand_layer( lstm_average = pooling_layer( input=lstm_expand, pooling_type=AvgPooling(), - agg_level=AggregateLevel.EACH_SEQUENCE) + agg_level=AggregateLevel.TO_SEQUENCE) with mixed_layer( size=label_dim, act=SoftmaxActivation(), bias_attr=True) as output: diff --git a/python/paddle/trainer_config_helpers/layers.py b/python/paddle/trainer_config_helpers/layers.py index ec81e1dc3d2..5a1e31c29d5 100755 --- a/python/paddle/trainer_config_helpers/layers.py +++ b/python/paddle/trainer_config_helpers/layers.py @@ -235,16 +235,16 @@ class AggregateLevel(object): Accordingly, AggregateLevel supports two modes: - - :code:`AggregateLevel.EACH_TIMESTEP` means the aggregation acts on each + - :code:`AggregateLevel.TO_NO_SEQUENCE` means the aggregation acts on each timestep of a sequence, both :code:`SUB_SEQUENCE` and :code:`SEQUENCE` will be aggregated to :code:`NO_SEQUENCE`. - - :code:`AggregateLevel.EACH_SEQUENCE` means the aggregation acts on each + - :code:`AggregateLevel.TO_SEQUENCE` means the aggregation acts on each sequence of a nested sequence, :code:`SUB_SEQUENCE` will be aggregated to :code:`SEQUENCE`. """ - EACH_TIMESTEP = 'non-seq' - EACH_SEQUENCE = 'seq' + TO_NO_SEQUENCE = 'non-seq' + TO_SEQUENCE = 'seq' class LayerOutput(object): @@ -1078,7 +1078,7 @@ def pooling_layer(input, pooling_type=None, name=None, bias_attr=None, - agg_level=AggregateLevel.EACH_TIMESTEP, + agg_level=AggregateLevel.TO_NO_SEQUENCE, layer_attr=None): """ Pooling layer for sequence inputs, not used for Image. @@ -1089,10 +1089,10 @@ def pooling_layer(input, seq_pool = pooling_layer(input=layer, pooling_type=AvgPooling(), - agg_level=AggregateLevel.EACH_SEQUENCE) + agg_level=AggregateLevel.TO_NO_SEQUENCE) - :param agg_level: AggregateLevel.EACH_TIMESTEP or - AggregateLevel.EACH_SEQUENCE + :param agg_level: AggregateLevel.TO_NO_SEQUENCE or + AggregateLevel.TO_SEQUENCE :type agg_level: AggregateLevel :param name: layer name. :type name: basestring @@ -1362,7 +1362,7 @@ def grumemory(input, @layer_support() def last_seq(input, name=None, - agg_level=AggregateLevel.EACH_TIMESTEP, + agg_level=AggregateLevel.TO_NO_SEQUENCE, stride=-1, layer_attr=None): """ @@ -1397,7 +1397,7 @@ def last_seq(input, " series information at all. Maybe you want to use" " first_seq instead.") - if agg_level == AggregateLevel.EACH_SEQUENCE: + if agg_level == AggregateLevel.TO_SEQUENCE: assert stride == -1 Layer( @@ -1418,7 +1418,7 @@ def last_seq(input, @layer_support() def first_seq(input, name=None, - agg_level=AggregateLevel.EACH_TIMESTEP, + agg_level=AggregateLevel.TO_NO_SEQUENCE, stride=-1, layer_attr=None): """ @@ -1454,7 +1454,7 @@ def first_seq(input, ' time series information at all. Maybe you want to use' ' last_seq instead.') - if agg_level == AggregateLevel.EACH_SEQUENCE: + if agg_level == AggregateLevel.TO_SEQUENCE: assert stride == -1 Layer( @@ -1477,16 +1477,16 @@ class ExpandLevel(object): ExpandLevel supports two modes: - - :code:`ExpandLevel.FROM_TIMESTEP` means the expandation acts on each - timestep of a sequence, :code:`NO_SEQUENCE` will be expanded to + - :code:`ExpandLevel.FROM_NO_SEQUENCE` means the expansion acts on + :code:`NO_SEQUENCE`, which will be expanded to :code:`SEQUENCE` or :code:`SUB_SEQUENCE`. - - :code:`ExpandLevel.FROM_SEQUENCE` means the expandation acts on each - sequence of a nested sequence, :code:`SEQUENCE` will be expanded to + - :code:`ExpandLevel.FROM_SEQUENCE` means the expansion acts on + :code:`SEQUENCE`, which will be expanded to :code:`SUB_SEQUENCE`. """ - FROM_TIMESTEP = AggregateLevel.EACH_TIMESTEP - FROM_SEQUENCE = AggregateLevel.EACH_SEQUENCE + FROM_NO_SEQUENCE = AggregateLevel.TO_NO_SEQUENCE + FROM_SEQUENCE = AggregateLevel.TO_SEQUENCE @wrap_name_default() @@ -1495,7 +1495,7 @@ def expand_layer(input, expand_as, name=None, bias_attr=False, - expand_level=ExpandLevel.FROM_TIMESTEP, + expand_level=ExpandLevel.FROM_NO_SEQUENCE, layer_attr=None): """ A layer for "Expand Dense data or (sequence data where the length of each @@ -1507,7 +1507,7 @@ def expand_layer(input, expand = expand_layer(input=layer1, expand_as=layer2, - expand_level=ExpandLevel.FROM_TIMESTEP) + expand_level=ExpandLevel.FROM_NO_SEQUENCE) :param input: Input layer :type input: LayerOutput diff --git a/python/paddle/trainer_config_helpers/tests/configs/last_first_seq.py b/python/paddle/trainer_config_helpers/tests/configs/last_first_seq.py index 3c6dbc95e54..f87237f9b59 100644 --- a/python/paddle/trainer_config_helpers/tests/configs/last_first_seq.py +++ b/python/paddle/trainer_config_helpers/tests/configs/last_first_seq.py @@ -6,7 +6,7 @@ din = data_layer(name='data', size=30) seq_op = [first_seq, last_seq] -agg_level = [AggregateLevel.EACH_SEQUENCE, AggregateLevel.EACH_TIMESTEP] +agg_level = [AggregateLevel.TO_SEQUENCE, AggregateLevel.TO_NO_SEQUENCE] opts = [] @@ -15,6 +15,7 @@ for op in seq_op: opts.append(op(input=din, agg_level=al)) for op in seq_op: - opts.append(op(input=din, agg_level=AggregateLevel.EACH_TIMESTEP, stride=5)) + opts.append( + op(input=din, agg_level=AggregateLevel.TO_NO_SEQUENCE, stride=5)) outputs(opts) diff --git a/python/paddle/trainer_config_helpers/tests/configs/test_expand_layer.py b/python/paddle/trainer_config_helpers/tests/configs/test_expand_layer.py index 81e5161ebc1..c53f10e0a41 100644 --- a/python/paddle/trainer_config_helpers/tests/configs/test_expand_layer.py +++ b/python/paddle/trainer_config_helpers/tests/configs/test_expand_layer.py @@ -9,4 +9,6 @@ outputs( expand_layer( input=din, expand_as=data_seq, expand_level=ExpandLevel.FROM_SEQUENCE), expand_layer( - input=din, expand_as=data_seq, expand_level=ExpandLevel.FROM_TIMESTEP)) + input=din, + expand_as=data_seq, + expand_level=ExpandLevel.FROM_NO_SEQUENCE)) diff --git a/python/paddle/trainer_config_helpers/tests/configs/test_sequence_pooling.py b/python/paddle/trainer_config_helpers/tests/configs/test_sequence_pooling.py index f67b6364d88..3c49eb56c13 100644 --- a/python/paddle/trainer_config_helpers/tests/configs/test_sequence_pooling.py +++ b/python/paddle/trainer_config_helpers/tests/configs/test_sequence_pooling.py @@ -6,7 +6,7 @@ din = data_layer(name='dat_in', size=100) POOL_TYPE = [MaxPooling, AvgPooling, SumPooling] -AGG_LEVEL = [AggregateLevel.EACH_SEQUENCE, AggregateLevel.EACH_TIMESTEP] +AGG_LEVEL = [AggregateLevel.TO_SEQUENCE, AggregateLevel.TO_NO_SEQUENCE] opts = [] diff --git a/python/paddle/v2/tests/test_layer.py b/python/paddle/v2/tests/test_layer.py index c67f3b84d96..b953f4748b8 100644 --- a/python/paddle/v2/tests/test_layer.py +++ b/python/paddle/v2/tests/test_layer.py @@ -73,7 +73,7 @@ class AggregateLayerTest(unittest.TestCase): pool = layer.pooling( input=pixel, pooling_type=pooling.Avg(), - agg_level=layer.AggregateLevel.EACH_SEQUENCE) + agg_level=layer.AggregateLevel.TO_SEQUENCE) last_seq = layer.last_seq(input=pixel) first_seq = layer.first_seq(input=pixel) concat = layer.concat(input=[last_seq, first_seq]) -- GitLab From ca739d12f198024fff3bb81e7af9b1b84959a474 Mon Sep 17 00:00:00 2001 From: Luo Tao Date: Tue, 23 May 2017 10:32:49 +0800 Subject: [PATCH 0423/3256] fix conflict --- python/paddle/v2/tests/test_layer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/paddle/v2/tests/test_layer.py b/python/paddle/v2/tests/test_layer.py index b953f4748b8..7926d2c40f4 100644 --- a/python/paddle/v2/tests/test_layer.py +++ b/python/paddle/v2/tests/test_layer.py @@ -109,7 +109,7 @@ class ReshapeLayerTest(unittest.TestCase): expand = layer.expand( input=weight, expand_as=pixel, - expand_level=layer.ExpandLevel.FROM_TIMESTEP) + expand_level=layer.ExpandLevel.FROM_NO_SEQUENCE) repeat = layer.repeat(input=pixel, num_repeats=4) reshape = layer.seq_reshape(input=pixel, reshape_size=4) rotate = layer.rotate(input=pixel, height=16, width=49) -- GitLab From 2e4c0bd2eac57f1b31c11011e9ba2160646110ce Mon Sep 17 00:00:00 2001 From: Yibing Liu Date: Fri, 19 May 2017 17:06:27 +0800 Subject: [PATCH 0424/3256] enable global gradient_clipping_threshold correct a typo optimize code fix a bug --- paddle/parameter/FirstOrderOptimizer.cpp | 30 +++++++++++++++---- paddle/parameter/OptimizerWithRegularizer.cpp | 3 +- paddle/parameter/ParameterOptimizer.h | 10 +++++++ proto/TrainerConfig.proto | 3 ++ python/paddle/trainer/config_parser.py | 1 + .../trainer_config_helpers/optimizers.py | 3 +- 6 files changed, 42 insertions(+), 8 deletions(-) diff --git a/paddle/parameter/FirstOrderOptimizer.cpp b/paddle/parameter/FirstOrderOptimizer.cpp index dbb738e98b5..02e600adb9e 100644 --- a/paddle/parameter/FirstOrderOptimizer.cpp +++ b/paddle/parameter/FirstOrderOptimizer.cpp @@ -161,6 +161,7 @@ void AdaDeltaParameterOptimizer::update(const VectorPtr vecs[], const ParameterConfig& config, size_t sparseId) const { CHECK(sparseId == -1LU) << "Sparse update is not supported"; + BaseMatrix& value = *vecs[PARAMETER_VALUE]; BaseMatrix& grad = *vecs[PARAMETER_GRADIENT]; BaseMatrix& mom = *vecs[PARAMETER_MOMENTUM]; @@ -265,6 +266,7 @@ void AdamParameterOptimizer::update(const VectorPtr vecs[], const ParameterConfig& config, size_t sparseId) const { CHECK(sparseId == -1UL) << "Sparse update is not supported"; + real beta1_power = std::pow(beta1_, step_); real beta2_power = std::pow(beta2_, step_); real learningRate = config.learning_rate() * learningRate_; @@ -303,18 +305,34 @@ void AdamaxParameterOptimizer::update(const VectorPtr vecs[], void OptimizerWithGradientClipping::update(const VectorPtr vecs[], const ParameterConfig& config, size_t sparseId) const { + // globalGradientClipping(vecs, config, FLAGS_log_clipping); + real global_thres_ = optConfig_.gradient_clipping_threshold(); + real local_thres_ = config.gradient_clipping_threshold(); + + real threshold; + std::string field; + if (global_thres_ > 0.0f && local_thres_ > 0.0f) { + threshold = global_thres_ < local_thres_ ? global_thres_ : local_thres_; + field = global_thres_ < local_thres_ ? "global" : "local"; + } else if (global_thres_ > 0.0f) { + threshold = global_thres_; + field = "global"; + } else { + threshold = local_thres_; + field = "local"; + } + real maxAbsGrad = vecs[PARAMETER_GRADIENT]->getAbsMax(); - if (maxAbsGrad > config.gradient_clipping_threshold()) { + if (maxAbsGrad > threshold) { if (FLAGS_log_clipping) { real avgAbsGrad = vecs[PARAMETER_GRADIENT]->getAbsSum() / vecs[PARAMETER_GRADIENT]->getSize(); - LOG(INFO) << "parameter=" << config.name() << " need clipping," - << " max grad=" << maxAbsGrad << " avg grad=" << avgAbsGrad; + LOG(INFO) << "parameter=" << config.name() << " need clipping by " + << field << " threshold=" << threshold + << ", max grad=" << maxAbsGrad << ", avg grad=" << avgAbsGrad; } - vecs[PARAMETER_GRADIENT]->clip(-config.gradient_clipping_threshold(), - config.gradient_clipping_threshold()); + vecs[PARAMETER_GRADIENT]->clip(-threshold, threshold); } - optimizer_->update(vecs, config, sparseId); } diff --git a/paddle/parameter/OptimizerWithRegularizer.cpp b/paddle/parameter/OptimizerWithRegularizer.cpp index 85f13c8bc08..7910b124449 100644 --- a/paddle/parameter/OptimizerWithRegularizer.cpp +++ b/paddle/parameter/OptimizerWithRegularizer.cpp @@ -131,7 +131,8 @@ ParameterOptimizer* OptimizerWithRegularizer::create( bool inPserver) { ParameterOptimizer* optimizer = ParameterOptimizer::create(optConfig, inPserver); - if (paraConfig.gradient_clipping_threshold() > 0.0f && + if ((optConfig.gradient_clipping_threshold() > 0.0f || + paraConfig.gradient_clipping_threshold() > 0.0f) && !dynamic_cast(optimizer)) { optimizer = new OptimizerWithGradientClipping(optConfig, optimizer); } diff --git a/paddle/parameter/ParameterOptimizer.h b/paddle/parameter/ParameterOptimizer.h index 2bdc793d605..38d432ba9bc 100644 --- a/paddle/parameter/ParameterOptimizer.h +++ b/paddle/parameter/ParameterOptimizer.h @@ -167,8 +167,12 @@ public: } parameterTypes_.push_back(type); } + real getLearningRate() const { return learningRate_; } + // real getGradientClippingThreshold() const {return + // gradientClippingThreshold_;} + virtual void setNoDecay() { applyDecay_ = false; } static ParameterOptimizer* create(const OptimizationConfig& optConfig, @@ -201,6 +205,12 @@ protected: * so, if lr change in StartBatch, please assign to learningRate_ */ real learningRate_; + + /** + * global threshold for gradient clipping, + * init value is opt_config.gradient_clipping_thresholod + */ + std::unique_ptr learningRateScheduler_; int64_t pass_; // current training pass (starting from 0) bool firstTime_; diff --git a/proto/TrainerConfig.proto b/proto/TrainerConfig.proto index a334e07b628..a819d20d11f 100644 --- a/proto/TrainerConfig.proto +++ b/proto/TrainerConfig.proto @@ -128,6 +128,9 @@ message OptimizationConfig { // when async_lagged_grad_discard_ratio * num_gradient_servers commit passed, // current async gradient will be discard silently. optional double async_lagged_grad_discard_ratio = 37 [default = 1.5]; + + // global threshold for gradient clipping + optional double gradient_clipping_threshold = 38 [default = 0.0]; }; message TrainerConfig { diff --git a/python/paddle/trainer/config_parser.py b/python/paddle/trainer/config_parser.py index 9135f38719a..9fe8794691e 100644 --- a/python/paddle/trainer/config_parser.py +++ b/python/paddle/trainer/config_parser.py @@ -3377,6 +3377,7 @@ settings = dict( algorithm='async_sgd', async_lagged_grad_discard_ratio=1.5, learning_method='momentum', + gradient_clipping_threshold=None, num_batches_per_send_parameter=None, num_batches_per_get_parameter=None, center_parameter_update_method=None, diff --git a/python/paddle/trainer_config_helpers/optimizers.py b/python/paddle/trainer_config_helpers/optimizers.py index a53ebe160be..c3495ee110b 100644 --- a/python/paddle/trainer_config_helpers/optimizers.py +++ b/python/paddle/trainer_config_helpers/optimizers.py @@ -408,7 +408,8 @@ def settings(batch_size, args = [ 'batch_size', 'learning_rate', 'learning_rate_decay_a', - 'learning_rate_decay_b', 'learning_rate_schedule', 'learning_rate_args' + 'learning_rate_decay_b', 'learning_rate_schedule', 'learning_rate_args', + 'gradient_clipping_threshold' ] kwargs = dict() kwargs['algorithm'] = algorithm -- GitLab From 4d4593b91392a2a1414a2cceca7cea62879d01ee Mon Sep 17 00:00:00 2001 From: Yibing Liu Date: Mon, 22 May 2017 18:24:54 +0800 Subject: [PATCH 0425/3256] code cleanup --- paddle/parameter/FirstOrderOptimizer.cpp | 3 ++- paddle/parameter/ParameterOptimizer.h | 8 -------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/paddle/parameter/FirstOrderOptimizer.cpp b/paddle/parameter/FirstOrderOptimizer.cpp index 02e600adb9e..207fb33f4e8 100644 --- a/paddle/parameter/FirstOrderOptimizer.cpp +++ b/paddle/parameter/FirstOrderOptimizer.cpp @@ -305,12 +305,13 @@ void AdamaxParameterOptimizer::update(const VectorPtr vecs[], void OptimizerWithGradientClipping::update(const VectorPtr vecs[], const ParameterConfig& config, size_t sparseId) const { - // globalGradientClipping(vecs, config, FLAGS_log_clipping); real global_thres_ = optConfig_.gradient_clipping_threshold(); real local_thres_ = config.gradient_clipping_threshold(); real threshold; std::string field; + // Get the minimum of local and global threshold + // as the real threshold for clipping if (global_thres_ > 0.0f && local_thres_ > 0.0f) { threshold = global_thres_ < local_thres_ ? global_thres_ : local_thres_; field = global_thres_ < local_thres_ ? "global" : "local"; diff --git a/paddle/parameter/ParameterOptimizer.h b/paddle/parameter/ParameterOptimizer.h index 38d432ba9bc..f98ba569b56 100644 --- a/paddle/parameter/ParameterOptimizer.h +++ b/paddle/parameter/ParameterOptimizer.h @@ -170,9 +170,6 @@ public: real getLearningRate() const { return learningRate_; } - // real getGradientClippingThreshold() const {return - // gradientClippingThreshold_;} - virtual void setNoDecay() { applyDecay_ = false; } static ParameterOptimizer* create(const OptimizationConfig& optConfig, @@ -206,11 +203,6 @@ protected: */ real learningRate_; - /** - * global threshold for gradient clipping, - * init value is opt_config.gradient_clipping_thresholod - */ - std::unique_ptr learningRateScheduler_; int64_t pass_; // current training pass (starting from 0) bool firstTime_; -- GitLab From 8c9ab5f183870065f973170b56ca73a38ee6ce80 Mon Sep 17 00:00:00 2001 From: Yibing Liu Date: Tue, 23 May 2017 15:06:58 +0800 Subject: [PATCH 0426/3256] rename two variables --- paddle/parameter/FirstOrderOptimizer.cpp | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/paddle/parameter/FirstOrderOptimizer.cpp b/paddle/parameter/FirstOrderOptimizer.cpp index 207fb33f4e8..826864b99ec 100644 --- a/paddle/parameter/FirstOrderOptimizer.cpp +++ b/paddle/parameter/FirstOrderOptimizer.cpp @@ -305,21 +305,22 @@ void AdamaxParameterOptimizer::update(const VectorPtr vecs[], void OptimizerWithGradientClipping::update(const VectorPtr vecs[], const ParameterConfig& config, size_t sparseId) const { - real global_thres_ = optConfig_.gradient_clipping_threshold(); - real local_thres_ = config.gradient_clipping_threshold(); + real globalThreshold = optConfig_.gradient_clipping_threshold(); + real localThreshold = config.gradient_clipping_threshold(); real threshold; std::string field; // Get the minimum of local and global threshold // as the real threshold for clipping - if (global_thres_ > 0.0f && local_thres_ > 0.0f) { - threshold = global_thres_ < local_thres_ ? global_thres_ : local_thres_; - field = global_thres_ < local_thres_ ? "global" : "local"; - } else if (global_thres_ > 0.0f) { - threshold = global_thres_; + if (globalThreshold > 0.0f && localThreshold > 0.0f) { + threshold = + globalThreshold < localThreshold ? globalThreshold : localThreshold; + field = globalThreshold < localThreshold ? "global" : "local"; + } else if (globalThreshold > 0.0f) { + threshold = globalThreshold; field = "global"; } else { - threshold = local_thres_; + threshold = localThreshold; field = "local"; } -- GitLab From c64a142ceaef016020961a50aff8359cc76bde83 Mon Sep 17 00:00:00 2001 From: Yibing Liu Date: Tue, 23 May 2017 17:23:54 +0800 Subject: [PATCH 0427/3256] change the way of setting threshold --- paddle/parameter/FirstOrderOptimizer.cpp | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/paddle/parameter/FirstOrderOptimizer.cpp b/paddle/parameter/FirstOrderOptimizer.cpp index 826864b99ec..d8292601629 100644 --- a/paddle/parameter/FirstOrderOptimizer.cpp +++ b/paddle/parameter/FirstOrderOptimizer.cpp @@ -310,18 +310,14 @@ void OptimizerWithGradientClipping::update(const VectorPtr vecs[], real threshold; std::string field; - // Get the minimum of local and global threshold - // as the real threshold for clipping - if (globalThreshold > 0.0f && localThreshold > 0.0f) { - threshold = - globalThreshold < localThreshold ? globalThreshold : localThreshold; - field = globalThreshold < localThreshold ? "global" : "local"; - } else if (globalThreshold > 0.0f) { - threshold = globalThreshold; - field = "global"; - } else { + // Use local gradient clipping threshold if it's enabled, + // otherwise using the global one. + if (localThreshold > 0.0f) { threshold = localThreshold; field = "local"; + } else { + threshold = globalThreshold; + field = "global"; } real maxAbsGrad = vecs[PARAMETER_GRADIENT]->getAbsMax(); -- GitLab From 5cf2b2e81cab838833911c3f54c033fb3dacdf62 Mon Sep 17 00:00:00 2001 From: Yibing Liu Date: Tue, 23 May 2017 18:18:38 +0800 Subject: [PATCH 0428/3256] compress code --- paddle/parameter/FirstOrderOptimizer.cpp | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/paddle/parameter/FirstOrderOptimizer.cpp b/paddle/parameter/FirstOrderOptimizer.cpp index d8292601629..5938b2210c7 100644 --- a/paddle/parameter/FirstOrderOptimizer.cpp +++ b/paddle/parameter/FirstOrderOptimizer.cpp @@ -308,17 +308,10 @@ void OptimizerWithGradientClipping::update(const VectorPtr vecs[], real globalThreshold = optConfig_.gradient_clipping_threshold(); real localThreshold = config.gradient_clipping_threshold(); - real threshold; - std::string field; // Use local gradient clipping threshold if it's enabled, // otherwise using the global one. - if (localThreshold > 0.0f) { - threshold = localThreshold; - field = "local"; - } else { - threshold = globalThreshold; - field = "global"; - } + real threshold = localThreshold > 0.0f ? localThreshold : globalThreshold; + std::string field = localThreshold > 0.0f ? "local" : "global"; real maxAbsGrad = vecs[PARAMETER_GRADIENT]->getAbsMax(); if (maxAbsGrad > threshold) { -- GitLab From 6a329f0c8c392932d2885d0ada939353b5291f19 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Wed, 24 May 2017 16:22:33 +0800 Subject: [PATCH 0429/3256] design doc for implementation parameters in CPP. --- doc/design/parameters_in_cpp.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 doc/design/parameters_in_cpp.md diff --git a/doc/design/parameters_in_cpp.md b/doc/design/parameters_in_cpp.md new file mode 100644 index 00000000000..4989a3d16bd --- /dev/null +++ b/doc/design/parameters_in_cpp.md @@ -0,0 +1,27 @@ +# Parameters in CPP + +`Parameters` is a concept we designed in Paddle V2 API. `Parameters` is a container of parameters, and make Paddle can shared parameter between topologies. We described usages of `Parameter` in [api.md](./api.md). + +We used Python to implementation Parameters before during API design phase. There are several defects for current implementation: +* We just use `memcpy` to share Parameters between topologies, but this is very inefficient. +* We did not implement share Parameters while training is not complete. We just trigger `memcpy` when start training. + +It is necessary we implement Parameters in CPP side. However, it could be a refactorization for Paddle, because Paddle was designed for training only one topology before. In current Paddle implementation, there are three concepts associated with `Parameters`: + +1. `paddle::Parameter`. A `Parameters` is a container for `paddle::Parameter`. It is evident that we should use `paddle::Parameter` when developing `Parameters`. However, the `Parameter` class contains many functions and does not have a clear interface. It contains `create/store Parameter`, `serialize/deserialize`, `optimize(i.e SGD)`, `randomize/zero`. We just need `paddle::Parameter` just create and store `Tensors (or Matrix currently)`. We should extract functionalities of Parameter into many classes to clean Paddle CPP implementation. +2. `paddle::GradientMachine` and its sub-classes, i.e., `paddle::MultiGradientMachine`, `paddle::NeuralNetwork`. We should pass `Parameters` to `paddle::GradientMachine` when `forward/backward` to avoid `memcpy` between topologies. Also, we should handle multi-GPU/CPU training, because `forward` and `backward` would perform on multi-GPUs and multi-CPUs. `Parameters` should dispatch the parameter value to each device, and gather the parameter gradient from each device. + +3. `paddle::ParameterUpdater`. The ParameterUpdater is used to update parameters in Paddle. So `Parameters` should be used by `paddle::ParameterUpdater`, and `paddle::ParameterUpdater` should optimize `Parameters` (by SGD). + + +The step by step approach for implementation Parameters in Paddle C++ core is listed below. Each step should be a PR merged into Paddle. + +1. Clean `paddle::Parameter` interface. Extract the functionalities of `paddle::Parameter` to prepare for the implementation of Parameters. + +2. Implementation a `Parameters` class. It just stores the `paddle::Parameter` inside. Make `GradientMachine` uses `Parameters` as a class member. + +3. Make `Parameters` support Multi-CPU and Multi-GPU training to prepare for sharing `Parameter` between topologies. Because we need sharing `Parameter` between topologies, it is `Parameters`'s response to exchange Parameter between GPUs not `GradientMachine`, because `GradientMachine` only used for one topology. + +4. Make `Parameters` as an argument for `forward/backward` function, not a data member for `GradientMachine`. For example, `forward` could be `forward(const Parameters& params, ...)` and `backward` could be `backward(Parameters* params, ...)`. After this step, Paddle could share `Parameters` between topologies. + +5. `ParameterUpdater` is invoked by `GradientMachine` and `Trainer`, but it updates `Parameters`. In the end, we could change `ParameterUpdater` directly uses `Parameters` to make Paddle implementation clear. -- GitLab From c57e98d4fea0af85a6ceff8eb838fb0a071f2222 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Wed, 24 May 2017 16:38:32 +0800 Subject: [PATCH 0430/3256] Refine english --- doc/design/parameters_in_cpp.md | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/doc/design/parameters_in_cpp.md b/doc/design/parameters_in_cpp.md index 4989a3d16bd..357c057897f 100644 --- a/doc/design/parameters_in_cpp.md +++ b/doc/design/parameters_in_cpp.md @@ -2,26 +2,38 @@ `Parameters` is a concept we designed in Paddle V2 API. `Parameters` is a container of parameters, and make Paddle can shared parameter between topologies. We described usages of `Parameter` in [api.md](./api.md). -We used Python to implementation Parameters before during API design phase. There are several defects for current implementation: +We used Python to implement Parameters when disigning V2 API before. There are several defects for current implementation: * We just use `memcpy` to share Parameters between topologies, but this is very inefficient. -* We did not implement share Parameters while training is not complete. We just trigger `memcpy` when start training. +* We did not implement share Parameters while training. We just trigger `memcpy` when start training. -It is necessary we implement Parameters in CPP side. However, it could be a refactorization for Paddle, because Paddle was designed for training only one topology before. In current Paddle implementation, there are three concepts associated with `Parameters`: +It is necessary that we implement Parameters in CPP side. However, it could be a refactorization for Paddle, because Paddle was designed for training only one topology before, i.e., each GradientMachine contains its Parameter as a data member. In current Paddle implementation, there are three concepts associated with `Parameters`: -1. `paddle::Parameter`. A `Parameters` is a container for `paddle::Parameter`. It is evident that we should use `paddle::Parameter` when developing `Parameters`. However, the `Parameter` class contains many functions and does not have a clear interface. It contains `create/store Parameter`, `serialize/deserialize`, `optimize(i.e SGD)`, `randomize/zero`. We just need `paddle::Parameter` just create and store `Tensors (or Matrix currently)`. We should extract functionalities of Parameter into many classes to clean Paddle CPP implementation. -2. `paddle::GradientMachine` and its sub-classes, i.e., `paddle::MultiGradientMachine`, `paddle::NeuralNetwork`. We should pass `Parameters` to `paddle::GradientMachine` when `forward/backward` to avoid `memcpy` between topologies. Also, we should handle multi-GPU/CPU training, because `forward` and `backward` would perform on multi-GPUs and multi-CPUs. `Parameters` should dispatch the parameter value to each device, and gather the parameter gradient from each device. +1. `paddle::Parameter`. A `Parameters` is a container for `paddle::Parameter`. +It is evident that we should use `paddle::Parameter` when developing `Parameters`. +However, the `Parameter` class contains many functions and does not have a clear interface. +It contains `create/store Parameter`, `serialize/deserialize`, `optimize(i.e SGD)`, `randomize/zero`. +When we developing `Parameters`, we only use `create/store Parameter` functionality. +We should extract functionalities of Parameter into many classes to clean Paddle CPP implementation. -3. `paddle::ParameterUpdater`. The ParameterUpdater is used to update parameters in Paddle. So `Parameters` should be used by `paddle::ParameterUpdater`, and `paddle::ParameterUpdater` should optimize `Parameters` (by SGD). +2. `paddle::GradientMachine` and its sub-classes, e.g., `paddle::MultiGradientMachine`, `paddle::NeuralNetwork`. +We should pass `Parameters` to `paddle::GradientMachine` when `forward/backward` to avoid `memcpy` between topologies. +Also, we should handle multi-GPU/CPU training, because `forward` and `backward` would perform on multi-GPUs and multi-CPUs. +`Parameters` should dispatch the parameter value to each device, and gather the parameter gradient from each device. +3. `paddle::ParameterUpdater`. The ParameterUpdater is used to update parameters in Paddle. +So `Parameters` should be used by `paddle::ParameterUpdater`, and `paddle::ParameterUpdater` should optimize `Parameters` (by SGD). -The step by step approach for implementation Parameters in Paddle C++ core is listed below. Each step should be a PR merged into Paddle. + +The step by step approach for implementation Parameters in Paddle C++ core is listed below. Each step should be a PR and could be merged into Paddle one by one. 1. Clean `paddle::Parameter` interface. Extract the functionalities of `paddle::Parameter` to prepare for the implementation of Parameters. 2. Implementation a `Parameters` class. It just stores the `paddle::Parameter` inside. Make `GradientMachine` uses `Parameters` as a class member. -3. Make `Parameters` support Multi-CPU and Multi-GPU training to prepare for sharing `Parameter` between topologies. Because we need sharing `Parameter` between topologies, it is `Parameters`'s response to exchange Parameter between GPUs not `GradientMachine`, because `GradientMachine` only used for one topology. +3. Make `Parameters` support Multi-CPU and Multi-GPU training to prepare for sharing `Parameter` between topologies. +Because we need share `Parameters` between topologies, it is `Parameters`'s response to exchange Parameters between GPUs. +`GradientMachine` should not handle how to exchange Parameters because `GradientMachine` only used to train one topology and we need to support train many topologies in Paddle, i.e., there could be many GradientMachines use one `Parameters`. 4. Make `Parameters` as an argument for `forward/backward` function, not a data member for `GradientMachine`. For example, `forward` could be `forward(const Parameters& params, ...)` and `backward` could be `backward(Parameters* params, ...)`. After this step, Paddle could share `Parameters` between topologies. -5. `ParameterUpdater` is invoked by `GradientMachine` and `Trainer`, but it updates `Parameters`. In the end, we could change `ParameterUpdater` directly uses `Parameters` to make Paddle implementation clear. +5. `ParameterUpdater` is invoked by `GradientMachine` and `Trainer`, but it updates `Parameters`. In the end of this refactorization, we could change `ParameterUpdater` directly uses `Parameters` to make `ParameterUpdater`'s implementation clear. -- GitLab From bb777f8e4abc8e4e191074864a1851fe1d725fe4 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Wed, 24 May 2017 16:44:14 +0800 Subject: [PATCH 0431/3256] Refine English --- doc/design/parameters_in_cpp.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/design/parameters_in_cpp.md b/doc/design/parameters_in_cpp.md index 357c057897f..d603de3114d 100644 --- a/doc/design/parameters_in_cpp.md +++ b/doc/design/parameters_in_cpp.md @@ -6,7 +6,7 @@ We used Python to implement Parameters when disigning V2 API before. There are s * We just use `memcpy` to share Parameters between topologies, but this is very inefficient. * We did not implement share Parameters while training. We just trigger `memcpy` when start training. -It is necessary that we implement Parameters in CPP side. However, it could be a refactorization for Paddle, because Paddle was designed for training only one topology before, i.e., each GradientMachine contains its Parameter as a data member. In current Paddle implementation, there are three concepts associated with `Parameters`: +It is necessary that we implement Parameters in CPP side. However, it could be a code refactoring for Paddle, because Paddle was designed for training only one topology before, i.e., each GradientMachine contains its Parameter as a data member. In current Paddle implementation, there are three concepts associated with `Parameters`: 1. `paddle::Parameter`. A `Parameters` is a container for `paddle::Parameter`. It is evident that we should use `paddle::Parameter` when developing `Parameters`. @@ -36,4 +36,4 @@ Because we need share `Parameters` between topologies, it is `Parameters`'s resp 4. Make `Parameters` as an argument for `forward/backward` function, not a data member for `GradientMachine`. For example, `forward` could be `forward(const Parameters& params, ...)` and `backward` could be `backward(Parameters* params, ...)`. After this step, Paddle could share `Parameters` between topologies. -5. `ParameterUpdater` is invoked by `GradientMachine` and `Trainer`, but it updates `Parameters`. In the end of this refactorization, we could change `ParameterUpdater` directly uses `Parameters` to make `ParameterUpdater`'s implementation clear. +5. `ParameterUpdater` is invoked by `GradientMachine` and `Trainer`, but it updates `Parameters`. In the end of this code refactoring, we could change `ParameterUpdater` directly uses `Parameters` to make `ParameterUpdater`'s implementation clear. -- GitLab From 27c70f39ec3917cb72b63a0dd7ccec08abb494fa Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Wed, 24 May 2017 16:51:24 +0800 Subject: [PATCH 0432/3256] typo --- doc/design/parameters_in_cpp.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/design/parameters_in_cpp.md b/doc/design/parameters_in_cpp.md index d603de3114d..4c0dcc1823e 100644 --- a/doc/design/parameters_in_cpp.md +++ b/doc/design/parameters_in_cpp.md @@ -2,7 +2,7 @@ `Parameters` is a concept we designed in Paddle V2 API. `Parameters` is a container of parameters, and make Paddle can shared parameter between topologies. We described usages of `Parameter` in [api.md](./api.md). -We used Python to implement Parameters when disigning V2 API before. There are several defects for current implementation: +We used Python to implement Parameters when designing V2 API before. There are several defects for current implementation: * We just use `memcpy` to share Parameters between topologies, but this is very inefficient. * We did not implement share Parameters while training. We just trigger `memcpy` when start training. -- GitLab From b098ef69a4a45efa8011a2f4b93ee55eca4bcc26 Mon Sep 17 00:00:00 2001 From: dzhwinter Date: Wed, 24 May 2017 19:26:39 +0800 Subject: [PATCH 0433/3256] "remove the rar extractfile, prevent small files" --- python/paddle/v2/dataset/__init__.py | 3 ++- python/paddle/v2/dataset/mq2007.py | 6 ++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/python/paddle/v2/dataset/__init__.py b/python/paddle/v2/dataset/__init__.py index 80ff6295c34..26252d5bbd7 100644 --- a/python/paddle/v2/dataset/__init__.py +++ b/python/paddle/v2/dataset/__init__.py @@ -24,8 +24,9 @@ import conll05 import uci_housing import sentiment import wmt14 +import mq2007 __all__ = [ 'mnist', 'imikolov', 'imdb', 'cifar', 'movielens', 'conll05', 'sentiment' - 'uci_housing', 'wmt14' + 'uci_housing', 'wmt14', 'mq2007' ] diff --git a/python/paddle/v2/dataset/mq2007.py b/python/paddle/v2/dataset/mq2007.py index fd71b341662..d8c9918d140 100644 --- a/python/paddle/v2/dataset/mq2007.py +++ b/python/paddle/v2/dataset/mq2007.py @@ -41,9 +41,7 @@ def __initialize_meta_info__(): """ fn = fetch() rar = rarfile.RarFile(fn) - dirpath = os.path.dirname(fn) - rar.extractall(path=dirpath) - return dirpath + return rar class Query(object): @@ -273,7 +271,7 @@ def load_from_text(filepath, shuffle=True, fill_missing=-1): querylists = [] querylist = None fn = __initialize_meta_info__() - with open(os.path.join(fn, filepath)) as f: + with fn.open(os.path.join(fn, filepath)) as f: for line in f: query = Query() query = query._parse_(line) -- GitLab From 6e91ebc0dd9dbb2ce9142216a1aa0c4575f350eb Mon Sep 17 00:00:00 2001 From: dzhwinter Date: Wed, 24 May 2017 19:30:27 +0800 Subject: [PATCH 0434/3256] "remove extrafile" --- python/paddle/v2/dataset/mq2007.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/python/paddle/v2/dataset/mq2007.py b/python/paddle/v2/dataset/mq2007.py index d8c9918d140..fd71b341662 100644 --- a/python/paddle/v2/dataset/mq2007.py +++ b/python/paddle/v2/dataset/mq2007.py @@ -41,7 +41,9 @@ def __initialize_meta_info__(): """ fn = fetch() rar = rarfile.RarFile(fn) - return rar + dirpath = os.path.dirname(fn) + rar.extractall(path=dirpath) + return dirpath class Query(object): @@ -271,7 +273,7 @@ def load_from_text(filepath, shuffle=True, fill_missing=-1): querylists = [] querylist = None fn = __initialize_meta_info__() - with fn.open(os.path.join(fn, filepath)) as f: + with open(os.path.join(fn, filepath)) as f: for line in f: query = Query() query = query._parse_(line) -- GitLab From f9eb4b74b2d572f1237d7fdecb5ac7f4bc02f22f Mon Sep 17 00:00:00 2001 From: dzhwinter Date: Wed, 24 May 2017 20:22:16 +0800 Subject: [PATCH 0435/3256] "update python package" --- .travis.yml | 2 +- Dockerfile | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 387367a2305..2dad0e77e67 100644 --- a/.travis.yml +++ b/.travis.yml @@ -48,7 +48,7 @@ before_install: - if [[ "$JOB" == "PRE_COMMIT" ]]; then sudo ln -s /usr/bin/clang-format-3.8 /usr/bin/clang-format; fi # Paddle is using protobuf 3.1 currently. Protobuf 3.2 breaks the compatibility. So we specify the python # protobuf version. - - pip install numpy wheel 'protobuf==3.1' sphinx==1.5.6 recommonmark sphinx-rtd-theme==0.1.9 virtualenv pre-commit requests==2.9.2 LinkChecker + - pip install numpy wheel 'protobuf==3.1' sphinx==1.5.6 recommonmark sphinx-rtd-theme==0.1.9 virtualenv pre-commit requests==2.9.2 LinkChecker rarfile - | function timeout() { perl -e 'alarm shift; exec @ARGV' "$@"; } script: diff --git a/Dockerfile b/Dockerfile index 571c3e1476e..b6f99ca539d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -56,7 +56,8 @@ RUN pip install --upgrade pip && \ pip install -U docopt PyYAML sphinx && \ pip install -U sphinx-rtd-theme==0.1.9 recommonmark && \ pip install pre-commit 'requests==2.9.2' 'ipython==5.3.0' && \ - pip install 'ipykernel==4.6.0' 'jupyter==1.0.0' + pip install 'ipykernel==4.6.0' 'jupyter==1.0.0' && \ + pip install rarfile # To fix https://github.com/PaddlePaddle/Paddle/issues/1954, we use # the solution in https://urllib3.readthedocs.io/en/latest/user-guide.html#ssl-py2 -- GitLab From d3bf8ca161a73f3355f38c8ea8f6355cde1c5349 Mon Sep 17 00:00:00 2001 From: dzhwinter Date: Wed, 24 May 2017 22:00:45 +0800 Subject: [PATCH 0436/3256] "fix travis failed on rarfile " --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 2dad0e77e67..44b755ee32d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -48,7 +48,8 @@ before_install: - if [[ "$JOB" == "PRE_COMMIT" ]]; then sudo ln -s /usr/bin/clang-format-3.8 /usr/bin/clang-format; fi # Paddle is using protobuf 3.1 currently. Protobuf 3.2 breaks the compatibility. So we specify the python # protobuf version. - - pip install numpy wheel 'protobuf==3.1' sphinx==1.5.6 recommonmark sphinx-rtd-theme==0.1.9 virtualenv pre-commit requests==2.9.2 LinkChecker rarfile + - pip install numpy wheel 'protobuf==3.1' sphinx==1.5.6 recommonmark sphinx-rtd-theme==0.1.9 virtualenv pre-commit requests==2.9.2 LinkChecker + - pip install rarfile - | function timeout() { perl -e 'alarm shift; exec @ARGV' "$@"; } script: -- GitLab From 8ecd722958a182e923a5476cbb5a9dff0ce7deb8 Mon Sep 17 00:00:00 2001 From: dzhwinter Date: Wed, 24 May 2017 23:06:33 +0800 Subject: [PATCH 0437/3256] "update setup.in" --- python/setup.py.in | 2 ++ 1 file changed, 2 insertions(+) diff --git a/python/setup.py.in b/python/setup.py.in index 5dfb46192ae..1afaffd2617 100644 --- a/python/setup.py.in +++ b/python/setup.py.in @@ -1,5 +1,6 @@ from setuptools import setup + packages=['paddle', 'paddle.proto', 'paddle.trainer', @@ -18,6 +19,7 @@ setup(name='paddle', "numpy", "protobuf==${PROTOBUF_VERSION}", "matplotlib", + "rarfile" ], packages=packages, package_dir={ -- GitLab From 3712822e9fc794713e39d0416665cdb53f6a3acf Mon Sep 17 00:00:00 2001 From: Yibing Liu Date: Thu, 25 May 2017 00:49:39 +0800 Subject: [PATCH 0438/3256] modify seq2seq demo to show gradient/error clipping --- demo/seqToseq/api_train_v2.py | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/demo/seqToseq/api_train_v2.py b/demo/seqToseq/api_train_v2.py index 3072c375123..bb535f09260 100644 --- a/demo/seqToseq/api_train_v2.py +++ b/demo/seqToseq/api_train_v2.py @@ -21,9 +21,12 @@ def seqToseq_net(source_dict_dim, target_dict_dim, is_generating=False): size=word_vector_dim, param_attr=paddle.attr.ParamAttr(name='_source_language_embedding')) src_forward = paddle.networks.simple_gru( - input=src_embedding, size=encoder_size) + name='src_forward_gru', input=src_embedding, size=encoder_size) src_backward = paddle.networks.simple_gru( - input=src_embedding, size=encoder_size, reverse=True) + name='src_backward_gru', + input=src_embedding, + size=encoder_size, + reverse=True) encoded_vector = paddle.layer.concat(input=[src_forward, src_backward]) #### Decoder @@ -34,7 +37,9 @@ def seqToseq_net(source_dict_dim, target_dict_dim, is_generating=False): backward_first = paddle.layer.first_seq(input=src_backward) with paddle.layer.mixed( - size=decoder_size, act=paddle.activation.Tanh()) as decoder_boot: + name="decoder_boot_mixed", + size=decoder_size, + act=paddle.activation.Tanh()) as decoder_boot: decoder_boot += paddle.layer.full_matrix_projection( input=backward_first) @@ -44,11 +49,17 @@ def seqToseq_net(source_dict_dim, target_dict_dim, is_generating=False): name='gru_decoder', size=decoder_size, boot_layer=decoder_boot) context = paddle.networks.simple_attention( + name="simple_attention", encoded_sequence=enc_vec, encoded_proj=enc_proj, decoder_state=decoder_mem) - with paddle.layer.mixed(size=decoder_size * 3) as decoder_inputs: + with paddle.layer.mixed( + name="input_recurrent", + size=decoder_size * 3, + # enable error clipping + layer_attr=paddle.attr.ExtraAttr( + error_clipping_threshold=100.0)) as decoder_inputs: decoder_inputs += paddle.layer.full_matrix_projection(input=context) decoder_inputs += paddle.layer.full_matrix_projection( input=current_word) @@ -57,9 +68,12 @@ def seqToseq_net(source_dict_dim, target_dict_dim, is_generating=False): name='gru_decoder', input=decoder_inputs, output_mem=decoder_mem, + # uncomment to enable local threshold for gradient clipping + # param_attr=paddle.attr.ParamAttr(gradient_clipping_threshold=9.9), size=decoder_size) with paddle.layer.mixed( + name="gru_step_output", size=target_dict_dim, bias_attr=True, act=paddle.activation.Softmax()) as out: @@ -125,7 +139,13 @@ def seqToseq_net(source_dict_dim, target_dict_dim, is_generating=False): def main(): - paddle.init(use_gpu=False, trainer_count=1) + paddle.init( + use_gpu=False, + trainer_count=1, + # log gradient clipping info + log_clipping=True, + # log error clipping info + log_error_clipping=True) is_generating = False # source and target dict dim. @@ -140,6 +160,8 @@ def main(): # define optimize method and trainer optimizer = paddle.optimizer.Adam( learning_rate=5e-5, + # uncomment to enable global threshold for gradient clipping + # gradient_clipping_threshold=10.0, regularization=paddle.optimizer.L2Regularization(rate=8e-4)) trainer = paddle.trainer.SGD(cost=cost, parameters=parameters, -- GitLab From c6b8c13721e5a05e5c8787546c4b870fa32b54ef Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Wed, 24 May 2017 20:16:08 -0400 Subject: [PATCH 0439/3256] move recordio to Paddle from github.com/wangkuiyi/recordio --- paddle/go/recordio/README.md | 36 ++++ paddle/go/recordio/chunk.go | 181 +++++++++++++++++++ paddle/go/recordio/header.go | 59 ++++++ paddle/go/recordio/reader.go | 135 ++++++++++++++ paddle/go/recordio/recordio_internal_test.go | 90 +++++++++ paddle/go/recordio/recordio_test.go | 81 +++++++++ paddle/go/recordio/writer.go | 60 ++++++ 7 files changed, 642 insertions(+) create mode 100644 paddle/go/recordio/README.md create mode 100644 paddle/go/recordio/chunk.go create mode 100644 paddle/go/recordio/header.go create mode 100644 paddle/go/recordio/reader.go create mode 100644 paddle/go/recordio/recordio_internal_test.go create mode 100644 paddle/go/recordio/recordio_test.go create mode 100644 paddle/go/recordio/writer.go diff --git a/paddle/go/recordio/README.md b/paddle/go/recordio/README.md new file mode 100644 index 00000000000..8b0b9308b1a --- /dev/null +++ b/paddle/go/recordio/README.md @@ -0,0 +1,36 @@ +# RecordIO + +## Write + +```go +f, e := os.Create("a_file.recordio") +w := recordio.NewWriter(f) +w.Write([]byte("Hello")) +w.Write([]byte("World!")) +w.Close() +``` + +## Read + +1. Load chunk index: + + ```go + f, e := os.Open("a_file.recordio") + idx, e := recordio.LoadIndex(f) + fmt.Println("Total records: ", idx.Len()) + ``` + +2. Create one or more scanner to read a range of records. The + following example reads the range + [1, 3), i.e., the second and the third records: + + ```go + f, e := os.Open("a_file.recordio") + s := recrodio.NewScanner(f, idx, 1, 3) + for s.Scan() { + fmt.Println(string(s.Record())) + } + if s.Err() != nil && s.Err() != io.EOF { + log.Fatalf("Something wrong with scanning: %v", e) + } + ``` diff --git a/paddle/go/recordio/chunk.go b/paddle/go/recordio/chunk.go new file mode 100644 index 00000000000..4e983ab72bd --- /dev/null +++ b/paddle/go/recordio/chunk.go @@ -0,0 +1,181 @@ +package recordio + +import ( + "bytes" + "compress/gzip" + "encoding/binary" + "fmt" + "hash/crc32" + "io" + + "github.com/golang/snappy" +) + +// A Chunk contains the Header and optionally compressed records. To +// create a chunk, just use ch := &Chunk{}. +type Chunk struct { + records [][]byte + numBytes int // sum of record lengths. +} + +func (ch *Chunk) add(record []byte) { + ch.records = append(ch.records, record) + ch.numBytes += len(record) +} + +// dump the chunk into w, and clears the chunk and makes it ready for +// the next add invocation. +func (ch *Chunk) dump(w io.Writer, compressorIndex int) error { + // NOTE: don't check ch.numBytes instead, because empty + // records are allowed. + if len(ch.records) == 0 { + return nil + } + + // Write raw records and their lengths into data buffer. + var data bytes.Buffer + + for _, r := range ch.records { + var rs [4]byte + binary.LittleEndian.PutUint32(rs[:], uint32(len(r))) + + if _, e := data.Write(rs[:]); e != nil { + return fmt.Errorf("Failed to write record length: %v", e) + } + + if _, e := data.Write(r); e != nil { + return fmt.Errorf("Failed to write record: %v", e) + } + } + + compressed, e := compressData(&data, compressorIndex) + if e != nil { + return e + } + + // Write chunk header and compressed data. + hdr := &Header{ + checkSum: crc32.ChecksumIEEE(compressed.Bytes()), + compressor: uint32(compressorIndex), + compressedSize: uint32(compressed.Len()), + numRecords: uint32(len(ch.records)), + } + + if _, e := hdr.write(w); e != nil { + return fmt.Errorf("Failed to write chunk header: %v", e) + } + + if _, e := w.Write(compressed.Bytes()); e != nil { + return fmt.Errorf("Failed to write chunk data: %v", e) + } + + // Clear the current chunk. + ch.records = nil + ch.numBytes = 0 + + return nil +} + +type noopCompressor struct { + *bytes.Buffer +} + +func (c *noopCompressor) Close() error { + return nil +} + +func compressData(src io.Reader, compressorIndex int) (*bytes.Buffer, error) { + compressed := new(bytes.Buffer) + var compressor io.WriteCloser + + switch compressorIndex { + case NoCompression: + compressor = &noopCompressor{compressed} + case Snappy: + compressor = snappy.NewBufferedWriter(compressed) + case Gzip: + compressor = gzip.NewWriter(compressed) + default: + return nil, fmt.Errorf("Unknown compression algorithm: %d", compressorIndex) + } + + if _, e := io.Copy(compressor, src); e != nil { + return nil, fmt.Errorf("Failed to compress chunk data: %v", e) + } + compressor.Close() + + return compressed, nil +} + +// parse the specified chunk from r. +func parseChunk(r io.ReadSeeker, chunkOffset int64) (*Chunk, error) { + var e error + var hdr *Header + + if _, e = r.Seek(chunkOffset, io.SeekStart); e != nil { + return nil, fmt.Errorf("Failed to seek chunk: %v", e) + } + + hdr, e = parseHeader(r) + if e != nil { + return nil, fmt.Errorf("Failed to parse chunk header: %v", e) + } + + var buf bytes.Buffer + if _, e = io.CopyN(&buf, r, int64(hdr.compressedSize)); e != nil { + return nil, fmt.Errorf("Failed to read chunk data: %v", e) + } + + if hdr.checkSum != crc32.ChecksumIEEE(buf.Bytes()) { + return nil, fmt.Errorf("Checksum checking failed.") + } + + deflated, e := deflateData(&buf, int(hdr.compressor)) + if e != nil { + return nil, e + } + + ch := &Chunk{} + for i := 0; i < int(hdr.numRecords); i++ { + var rs [4]byte + if _, e = deflated.Read(rs[:]); e != nil { + return nil, fmt.Errorf("Failed to read record length: %v", e) + } + + r := make([]byte, binary.LittleEndian.Uint32(rs[:])) + if _, e = deflated.Read(r); e != nil { + return nil, fmt.Errorf("Failed to read a record: %v", e) + } + + ch.records = append(ch.records, r) + ch.numBytes += len(r) + } + + return ch, nil +} + +func deflateData(src io.Reader, compressorIndex int) (*bytes.Buffer, error) { + var e error + var deflator io.Reader + + switch compressorIndex { + case NoCompression: + deflator = src + case Snappy: + deflator = snappy.NewReader(src) + case Gzip: + deflator, e = gzip.NewReader(src) + if e != nil { + return nil, fmt.Errorf("Failed to create gzip reader: %v", e) + } + default: + return nil, fmt.Errorf("Unknown compression algorithm: %d", compressorIndex) + } + + deflated := new(bytes.Buffer) + if _, e = io.Copy(deflated, deflator); e != nil { + return nil, fmt.Errorf("Failed to deflate chunk data: %v", e) + } + + return deflated, nil +} diff --git a/paddle/go/recordio/header.go b/paddle/go/recordio/header.go new file mode 100644 index 00000000000..d3aefae3646 --- /dev/null +++ b/paddle/go/recordio/header.go @@ -0,0 +1,59 @@ +package recordio + +import ( + "encoding/binary" + "fmt" + "io" +) + +const ( + // NoCompression means writing raw chunk data into files. + // With other choices, chunks are compressed before written. + NoCompression = iota + // Snappy had been the default compressing algorithm widely + // used in Google. It compromises between speech and + // compression ratio. + Snappy + // Gzip is a well-known compression algorithm. It is + // recommmended only you are looking for compression ratio. + Gzip + + magicNumber uint32 = 0x01020304 + defaultCompressor = Snappy +) + +// Header is the metadata of Chunk. +type Header struct { + checkSum uint32 + compressor uint32 + compressedSize uint32 + numRecords uint32 +} + +func (c *Header) write(w io.Writer) (int, error) { + var buf [20]byte + binary.LittleEndian.PutUint32(buf[0:4], magicNumber) + binary.LittleEndian.PutUint32(buf[4:8], c.checkSum) + binary.LittleEndian.PutUint32(buf[8:12], c.compressor) + binary.LittleEndian.PutUint32(buf[12:16], c.compressedSize) + binary.LittleEndian.PutUint32(buf[16:20], c.numRecords) + return w.Write(buf[:]) +} + +func parseHeader(r io.Reader) (*Header, error) { + var buf [20]byte + if _, e := r.Read(buf[:]); e != nil { + return nil, e + } + + if v := binary.LittleEndian.Uint32(buf[0:4]); v != magicNumber { + return nil, fmt.Errorf("Failed to parse magic number") + } + + return &Header{ + checkSum: binary.LittleEndian.Uint32(buf[4:8]), + compressor: binary.LittleEndian.Uint32(buf[8:12]), + compressedSize: binary.LittleEndian.Uint32(buf[12:16]), + numRecords: binary.LittleEndian.Uint32(buf[16:20]), + }, nil +} diff --git a/paddle/go/recordio/reader.go b/paddle/go/recordio/reader.go new file mode 100644 index 00000000000..a12c604f7b2 --- /dev/null +++ b/paddle/go/recordio/reader.go @@ -0,0 +1,135 @@ +package recordio + +import "io" + +// Index consists offsets and sizes of the consequetive chunks in a RecordIO file. +type Index struct { + chunkOffsets []int64 + chunkLens []uint32 + numRecords int // the number of all records in a file. + chunkRecords []int // the number of records in chunks. +} + +// LoadIndex scans the file and parse chunkOffsets, chunkLens, and len. +func LoadIndex(r io.ReadSeeker) (*Index, error) { + f := &Index{} + offset := int64(0) + var e error + var hdr *Header + + for { + hdr, e = parseHeader(r) + if e != nil { + break + } + + f.chunkOffsets = append(f.chunkOffsets, offset) + f.chunkLens = append(f.chunkLens, hdr.numRecords) + f.chunkRecords = append(f.chunkRecords, int(hdr.numRecords)) + f.numRecords += int(hdr.numRecords) + + offset, e = r.Seek(int64(hdr.compressedSize), io.SeekCurrent) + if e != nil { + break + } + } + + if e == io.EOF { + return f, nil + } + return nil, e +} + +// NumRecords returns the total number of records in a RecordIO file. +func (r *Index) NumRecords() int { + return r.numRecords +} + +// NumChunks returns the total number of chunks in a RecordIO file. +func (r *Index) NumChunks() int { + return len(r.chunkLens) +} + +// ChunkIndex return the Index of i-th Chunk. +func (r *Index) ChunkIndex(i int) *Index { + idx := &Index{} + idx.chunkOffsets = []int64{r.chunkOffsets[i]} + idx.chunkLens = []uint32{r.chunkLens[i]} + idx.chunkRecords = []int{r.chunkRecords[i]} + idx.numRecords = idx.chunkRecords[0] + return idx +} + +// Locate returns the index of chunk that contains the given record, +// and the record index within the chunk. It returns (-1, -1) if the +// record is out of range. +func (r *Index) Locate(recordIndex int) (int, int) { + sum := 0 + for i, l := range r.chunkLens { + sum += int(l) + if recordIndex < sum { + return i, recordIndex - sum + int(l) + } + } + return -1, -1 +} + +// Scanner scans records in a specified range within [0, numRecords). +type Scanner struct { + reader io.ReadSeeker + index *Index + start, end, cur int + chunkIndex int + chunk *Chunk + err error +} + +// NewScanner creates a scanner that sequencially reads records in the +// range [start, start+len). If start < 0, it scans from the +// beginning. If len < 0, it scans till the end of file. +func NewScanner(r io.ReadSeeker, index *Index, start, len int) *Scanner { + if start < 0 { + start = 0 + } + if len < 0 || start+len >= index.NumRecords() { + len = index.NumRecords() - start + } + + return &Scanner{ + reader: r, + index: index, + start: start, + end: start + len, + cur: start - 1, // The intial status required by Scan. + chunkIndex: -1, + chunk: &Chunk{}, + } +} + +// Scan moves the cursor forward for one record and loads the chunk +// containing the record if not yet. +func (s *Scanner) Scan() bool { + s.cur++ + + if s.cur >= s.end { + s.err = io.EOF + } else { + if ci, _ := s.index.Locate(s.cur); s.chunkIndex != ci { + s.chunkIndex = ci + s.chunk, s.err = parseChunk(s.reader, s.index.chunkOffsets[ci]) + } + } + + return s.err == nil +} + +// Record returns the record under the current cursor. +func (s *Scanner) Record() []byte { + _, ri := s.index.Locate(s.cur) + return s.chunk.records[ri] +} + +// Error returns the error that stopped Scan. +func (s *Scanner) Error() error { + return s.err +} diff --git a/paddle/go/recordio/recordio_internal_test.go b/paddle/go/recordio/recordio_internal_test.go new file mode 100644 index 00000000000..e0f7dd0407c --- /dev/null +++ b/paddle/go/recordio/recordio_internal_test.go @@ -0,0 +1,90 @@ +package recordio + +import ( + "bytes" + "testing" + "unsafe" + + "github.com/stretchr/testify/assert" +) + +func TestChunkHead(t *testing.T) { + assert := assert.New(t) + + c := &Header{ + checkSum: 123, + compressor: 456, + compressedSize: 789, + } + + var buf bytes.Buffer + _, e := c.write(&buf) + assert.Nil(e) + + cc, e := parseHeader(&buf) + assert.Nil(e) + assert.Equal(c, cc) +} + +func TestWriteAndRead(t *testing.T) { + assert := assert.New(t) + + data := []string{ + "12345", + "1234", + "12"} + + var buf bytes.Buffer + w := NewWriter(&buf, 10, NoCompression) // use a small maxChunkSize. + + n, e := w.Write([]byte(data[0])) // not exceed chunk size. + assert.Nil(e) + assert.Equal(5, n) + + n, e = w.Write([]byte(data[1])) // not exceed chunk size. + assert.Nil(e) + assert.Equal(4, n) + + n, e = w.Write([]byte(data[2])) // exeeds chunk size, dump and create a new chunk. + assert.Nil(e) + assert.Equal(n, 2) + + assert.Nil(w.Close()) // flush the second chunk. + assert.Nil(w.Writer) + + n, e = w.Write([]byte("anything")) // not effective after close. + assert.NotNil(e) + assert.Equal(n, 0) + + idx, e := LoadIndex(bytes.NewReader(buf.Bytes())) + assert.Nil(e) + assert.Equal([]uint32{2, 1}, idx.chunkLens) + assert.Equal( + []int64{0, + int64(4 + // magic number + unsafe.Sizeof(Header{}) + + 5 + // first record + 4 + // second record + 2*4)}, // two record legnths + idx.chunkOffsets) + + s := NewScanner(bytes.NewReader(buf.Bytes()), idx, -1, -1) + i := 0 + for s.Scan() { + assert.Equal(data[i], string(s.Record())) + i++ + } +} + +func TestWriteEmptyFile(t *testing.T) { + assert := assert.New(t) + + var buf bytes.Buffer + w := NewWriter(&buf, 10, NoCompression) // use a small maxChunkSize. + assert.Nil(w.Close()) + assert.Equal(0, buf.Len()) + + idx, e := LoadIndex(bytes.NewReader(buf.Bytes())) + assert.Nil(e) + assert.Equal(0, idx.NumRecords()) +} diff --git a/paddle/go/recordio/recordio_test.go b/paddle/go/recordio/recordio_test.go new file mode 100644 index 00000000000..8bf1b020ab7 --- /dev/null +++ b/paddle/go/recordio/recordio_test.go @@ -0,0 +1,81 @@ +package recordio_test + +import ( + "bytes" + "reflect" + "testing" + + "github.com/PaddlePaddle/Paddle/paddle/go/recordio" +) + +func TestWriteRead(t *testing.T) { + const total = 1000 + var buf bytes.Buffer + w := recordio.NewWriter(&buf, 0, -1) + for i := 0; i < total; i++ { + _, err := w.Write(make([]byte, i)) + if err != nil { + t.Fatal(err) + } + } + w.Close() + + idx, err := recordio.LoadIndex(bytes.NewReader(buf.Bytes())) + if err != nil { + t.Fatal(err) + } + + if idx.NumRecords() != total { + t.Fatal("num record does not match:", idx.NumRecords(), total) + } + + s := recordio.NewScanner(bytes.NewReader(buf.Bytes()), idx, -1, -1) + i := 0 + for s.Scan() { + if !reflect.DeepEqual(s.Record(), make([]byte, i)) { + t.Fatal("not equal:", len(s.Record()), len(make([]byte, i))) + } + i++ + } + + if i != total { + t.Fatal("total count not match:", i, total) + } +} + +func TestChunkIndex(t *testing.T) { + const total = 1000 + var buf bytes.Buffer + w := recordio.NewWriter(&buf, 0, -1) + for i := 0; i < total; i++ { + _, err := w.Write(make([]byte, i)) + if err != nil { + t.Fatal(err) + } + } + w.Close() + + idx, err := recordio.LoadIndex(bytes.NewReader(buf.Bytes())) + if err != nil { + t.Fatal(err) + } + + if idx.NumChunks() != total { + t.Fatal("unexpected chunk num:", idx.NumChunks(), total) + } + + for i := 0; i < total; i++ { + newIdx := idx.ChunkIndex(i) + s := recordio.NewScanner(bytes.NewReader(buf.Bytes()), newIdx, -1, -1) + j := 0 + for s.Scan() { + if !reflect.DeepEqual(s.Record(), make([]byte, i)) { + t.Fatal("not equal:", len(s.Record()), len(make([]byte, i))) + } + j++ + } + if j != 1 { + t.Fatal("unexpected record per chunk:", j) + } + } +} diff --git a/paddle/go/recordio/writer.go b/paddle/go/recordio/writer.go new file mode 100644 index 00000000000..39112e518fb --- /dev/null +++ b/paddle/go/recordio/writer.go @@ -0,0 +1,60 @@ +package recordio + +import ( + "fmt" + "io" +) + +const ( + defaultMaxChunkSize = 32 * 1024 * 1024 +) + +// Writer creates a RecordIO file. +type Writer struct { + io.Writer // Set to nil to mark a closed writer. + chunk *Chunk + maxChunkSize int // total records size, excluding metadata, before compression. + compressor int +} + +// NewWriter creates a RecordIO file writer. Each chunk is compressed +// using the deflate algorithm given compression level. Note that +// level 0 means no compression and -1 means default compression. +func NewWriter(w io.Writer, maxChunkSize, compressor int) *Writer { + if maxChunkSize < 0 { + maxChunkSize = defaultMaxChunkSize + } + + if compressor < 0 { + compressor = defaultCompressor + } + + return &Writer{ + Writer: w, + chunk: &Chunk{}, + maxChunkSize: maxChunkSize, + compressor: compressor} +} + +// Writes a record. It returns an error if Close has been called. +func (w *Writer) Write(record []byte) (int, error) { + if w.Writer == nil { + return 0, fmt.Errorf("Cannot write since writer had been closed") + } + + if w.chunk.numBytes+len(record) > w.maxChunkSize { + if e := w.chunk.dump(w.Writer, w.compressor); e != nil { + return 0, e + } + } + + w.chunk.add(record) + return len(record), nil +} + +// Close flushes the current chunk and makes the writer invalid. +func (w *Writer) Close() error { + e := w.chunk.dump(w.Writer, w.compressor) + w.Writer = nil + return e +} -- GitLab From 398bab41b84b39f0f0341b79b40314d932bf03f9 Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Wed, 24 May 2017 19:17:18 -0700 Subject: [PATCH 0440/3256] fix hardcoded cmake for cclient/test --- paddle/go/cclient/CMakeLists.txt | 7 ++----- paddle/go/cclient/test/CMakeLists.txt | 5 +++-- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/paddle/go/cclient/CMakeLists.txt b/paddle/go/cclient/CMakeLists.txt index 29a2089fb10..c85ff3db09d 100644 --- a/paddle/go/cclient/CMakeLists.txt +++ b/paddle/go/cclient/CMakeLists.txt @@ -2,12 +2,9 @@ cmake_minimum_required(VERSION 3.0) if(GTEST_INCLUDE_DIR AND GTEST_LIBRARIES) message("-- Found gtest (include: ${GTEST_INCLUDE_DIR}, library: ${GTEST_LIBRARIES})") -else() - # find #include - get_filename_component(PARENT_DIR ${CMAKE_CURRENT_SOURCE_DIR} DIRECTORY) - include_directories(${PARENT_DIR}) - +else() # find cmake directory modules + get_filename_component(PARENT_DIR ${CMAKE_CURRENT_SOURCE_DIR} DIRECTORY) get_filename_component(PARENT_DIR ${PARENT_DIR} DIRECTORY) get_filename_component(PARENT_DIR ${PARENT_DIR} DIRECTORY) diff --git a/paddle/go/cclient/test/CMakeLists.txt b/paddle/go/cclient/test/CMakeLists.txt index c899bd275d3..185e7ec80f3 100644 --- a/paddle/go/cclient/test/CMakeLists.txt +++ b/paddle/go/cclient/test/CMakeLists.txt @@ -1,8 +1,9 @@ cmake_minimum_required(VERSION 3.0) -include_directories(/env/gopath/src/github.com/PaddlePaddle/Paddle/paddle/go/cclient/build/) +include_directories(${CMAKE_BINARY_DIR}) add_executable(main main.c) add_dependencies(main client) set (CMAKE_EXE_LINKER_FLAGS "-pthread") -target_link_libraries(main /env/gopath/src/github.com/PaddlePaddle/Paddle/paddle/go/cclient/build/libclient.a) # ${GTEST_LIBRARIES}) +message(${CMAKE_BINARY_DIR}) +target_link_libraries(main ${CMAKE_BINARY_DIR}/libclient.a) -- GitLab From 25ca5a31c718ab37c131af3e0b6f7ffaf00c23f5 Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Wed, 24 May 2017 19:19:28 -0700 Subject: [PATCH 0441/3256] remove debug message --- paddle/go/cclient/test/CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/paddle/go/cclient/test/CMakeLists.txt b/paddle/go/cclient/test/CMakeLists.txt index 185e7ec80f3..de7ef6a47ac 100644 --- a/paddle/go/cclient/test/CMakeLists.txt +++ b/paddle/go/cclient/test/CMakeLists.txt @@ -5,5 +5,4 @@ include_directories(${CMAKE_BINARY_DIR}) add_executable(main main.c) add_dependencies(main client) set (CMAKE_EXE_LINKER_FLAGS "-pthread") -message(${CMAKE_BINARY_DIR}) target_link_libraries(main ${CMAKE_BINARY_DIR}/libclient.a) -- GitLab From 8813ad9d2f4bfed97e2e24a5e20fa4b6db561a50 Mon Sep 17 00:00:00 2001 From: dzhwinter Date: Thu, 25 May 2017 11:26:38 +0800 Subject: [PATCH 0442/3256] "fix dependency build failed" --- paddle/scripts/run_python_tests.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paddle/scripts/run_python_tests.sh b/paddle/scripts/run_python_tests.sh index 02d2cdb9774..c588b9e08de 100755 --- a/paddle/scripts/run_python_tests.sh +++ b/paddle/scripts/run_python_tests.sh @@ -29,7 +29,7 @@ if [ $USE_VIRTUALENV_FOR_TEST -ne 0 ]; then fi export PYTHONPATH=$SCRIPTPATH/../../python/ -$PYTHON -m pip install $SCRIPTPATH/../dist/*.whl requests matplotlib opencv-python ipython==5.3 +$PYTHON -m pip install $SCRIPTPATH/../dist/*.whl requests matplotlib opencv-python ipython==5.3 rarfile for fn in "$@" do -- GitLab From f170111031b1f202effea678ebb5eaa4a9bf13bb Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Thu, 25 May 2017 12:18:16 +0800 Subject: [PATCH 0443/3256] Fix compile when not enable testing * WITH_TESTING is a flag that user could configure --- paddle/go/CMakeLists.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/paddle/go/CMakeLists.txt b/paddle/go/CMakeLists.txt index 20f14769433..51c5252d663 100644 --- a/paddle/go/CMakeLists.txt +++ b/paddle/go/CMakeLists.txt @@ -2,8 +2,10 @@ include_directories(${CMAKE_CURRENT_BINARY_DIR}) go_library(adder SRCS adder.go) -cc_test(cgo_test +if (WITH_TESTING) + cc_test(cgo_test SRCS cgo_test.cc DEPS adder) +endif() -- GitLab From 19127b479c40d5f00c4310eb44e2888f52c65978 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Thu, 25 May 2017 12:16:56 +0800 Subject: [PATCH 0444/3256] Fix bug in run_python_tests.sh * Also, test_plot cannot run in MacOS because of matplotlib & ipython's limit. * Add missing dependency in setup.py. * fix #2264 --- cmake/util.cmake | 5 +++-- paddle/scripts/run_python_tests.sh | 9 +++++++-- python/CMakeLists.txt | 4 +++- python/paddle/v2/plot/tests/CMakeLists.txt | 6 +++++- python/setup.py.in | 1 + 5 files changed, 19 insertions(+), 6 deletions(-) diff --git a/cmake/util.cmake b/cmake/util.cmake index b828eef322b..8c914346222 100644 --- a/cmake/util.cmake +++ b/cmake/util.cmake @@ -149,8 +149,9 @@ endfunction() # Create a python unittest using run_python_tests.sh, # which takes care of making correct running environment function(add_python_test TEST_NAME) - add_test(NAME ${TEST_NAME} - COMMAND bash ${PROJ_ROOT}/paddle/scripts/run_python_tests.sh + add_test(NAME ${TEST_NAME} + COMMAND env PADDLE_PACKAGE_DIR=${PADDLE_PYTHON_PACKAGE_DIR} + bash ${PROJ_ROOT}/paddle/scripts/run_python_tests.sh ${USE_VIRTUALENV_FOR_TEST} ${PYTHON_EXECUTABLE} ${ARGN} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) endfunction() diff --git a/paddle/scripts/run_python_tests.sh b/paddle/scripts/run_python_tests.sh index 02d2cdb9774..6dfc05a9497 100755 --- a/paddle/scripts/run_python_tests.sh +++ b/paddle/scripts/run_python_tests.sh @@ -28,8 +28,13 @@ if [ $USE_VIRTUALENV_FOR_TEST -ne 0 ]; then PYTHON=python fi -export PYTHONPATH=$SCRIPTPATH/../../python/ -$PYTHON -m pip install $SCRIPTPATH/../dist/*.whl requests matplotlib opencv-python ipython==5.3 +$PYTHON -m pip install $SCRIPTPATH/../dist/*.whl + +if [ "X${PADDLE_PACKAGE_DIR}" != "X" ]; then + $PYTHON -m pip install ${PADDLE_PACKAGE_DIR}/*.whl +fi + +$PYTHON -m pip install ipython==5.3 for fn in "$@" do diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index bfa19d5ecc8..4f52f0f6cfd 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -23,7 +23,9 @@ add_custom_command(OUTPUT ${OUTPUT_DIR}/.timestamp add_custom_target(paddle_python ALL DEPENDS ${OUTPUT_DIR}/.timestamp) +set(PADDLE_PYTHON_PACKAGE_DIR ${CMAKE_CURRENT_BINARY_DIR}/dist/) add_subdirectory(paddle/trainer_config_helpers/tests) + if (WITH_SWIG_PY) # enable v2 API unittest only when paddle swig api is compiled add_subdirectory(paddle/v2/tests) @@ -31,6 +33,6 @@ if (WITH_SWIG_PY) add_subdirectory(paddle/v2/plot/tests) endif() -install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/dist/ +install(DIRECTORY ${PADDLE_PYTHON_PACKAGE_DIR} DESTINATION opt/paddle/share/wheels ) diff --git a/python/paddle/v2/plot/tests/CMakeLists.txt b/python/paddle/v2/plot/tests/CMakeLists.txt index b1132f13173..da5cd764889 100644 --- a/python/paddle/v2/plot/tests/CMakeLists.txt +++ b/python/paddle/v2/plot/tests/CMakeLists.txt @@ -1 +1,5 @@ -add_python_test(test_ploter test_ploter.py) +if (NOT APPLE) + # The Mac OS X backend will not be able to function correctly if Python is + # not installed as a framework. + add_python_test(test_ploter test_ploter.py) +endif() diff --git a/python/setup.py.in b/python/setup.py.in index 5dfb46192ae..d7472884344 100644 --- a/python/setup.py.in +++ b/python/setup.py.in @@ -18,6 +18,7 @@ setup(name='paddle', "numpy", "protobuf==${PROTOBUF_VERSION}", "matplotlib", + "opencv-python" ], packages=packages, package_dir={ -- GitLab From a038163eefd4c9868f15eaeb5be4839c8e990e9d Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Thu, 25 May 2017 13:38:51 +0800 Subject: [PATCH 0445/3256] Follow comments --- doc/design/parameters_in_cpp.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/design/parameters_in_cpp.md b/doc/design/parameters_in_cpp.md index 4c0dcc1823e..b6f99bc7d9d 100644 --- a/doc/design/parameters_in_cpp.md +++ b/doc/design/parameters_in_cpp.md @@ -1,4 +1,4 @@ -# Parameters in CPP +# Design Doc: The C++ Class `Parameters` `Parameters` is a concept we designed in Paddle V2 API. `Parameters` is a container of parameters, and make Paddle can shared parameter between topologies. We described usages of `Parameter` in [api.md](./api.md). @@ -33,6 +33,8 @@ The step by step approach for implementation Parameters in Paddle C++ core is li 3. Make `Parameters` support Multi-CPU and Multi-GPU training to prepare for sharing `Parameter` between topologies. Because we need share `Parameters` between topologies, it is `Parameters`'s response to exchange Parameters between GPUs. `GradientMachine` should not handle how to exchange Parameters because `GradientMachine` only used to train one topology and we need to support train many topologies in Paddle, i.e., there could be many GradientMachines use one `Parameters`. + * We should use a global function to exchange Parameters between GPUs, not a member function in `Parameters`. The `MultiGradientMachine` invoke this function, which uses `Parameters` as this function inputs. + * The MultiGradientMachine contains many functionalities. Extracting the Parameters exchanging logic could make MultiGradientMachine clearer and simpler. 4. Make `Parameters` as an argument for `forward/backward` function, not a data member for `GradientMachine`. For example, `forward` could be `forward(const Parameters& params, ...)` and `backward` could be `backward(Parameters* params, ...)`. After this step, Paddle could share `Parameters` between topologies. -- GitLab From 7ee942bba88c65eca4cccfc89277fd8babadb809 Mon Sep 17 00:00:00 2001 From: yangyaming Date: Thu, 25 May 2017 12:29:42 +0800 Subject: [PATCH 0446/3256] Add scale factor for smoothL1 and smoothL1Bp --- paddle/gserver/layers/CostLayer.cpp | 8 ++++---- paddle/math/Matrix.cpp | 6 ++++-- paddle/math/Matrix.h | 8 ++++---- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/paddle/gserver/layers/CostLayer.cpp b/paddle/gserver/layers/CostLayer.cpp index 69d5830dd2a..6bfdea3c6e3 100644 --- a/paddle/gserver/layers/CostLayer.cpp +++ b/paddle/gserver/layers/CostLayer.cpp @@ -217,10 +217,10 @@ void SmoothL1CostLayer::forwardImp(Matrix& output, targetCpu->copyFrom(target); outputCpu->copyFrom(output); labelCpu->copyFrom(*label.value); - targetCpu->smoothL1(*outputCpu, *labelCpu); + targetCpu->smoothL1(*outputCpu, *labelCpu, 1.0); target.copyFrom(*targetCpu); } else { - target.smoothL1(output, *label.value); + target.smoothL1(output, *label.value, 1.0); } } @@ -238,10 +238,10 @@ void SmoothL1CostLayer::backwardImp(Matrix& output, outputGCpu->copyFrom(outputG); outputCpu->copyFrom(output); labelCpu->copyFrom(*label.value); - outputGCpu->smoothL1Bp(*outputCpu, *labelCpu); + outputGCpu->smoothL1Bp(*outputCpu, *labelCpu, 1.0); outputG.copyFrom(*outputGCpu); } else { - outputG.smoothL1Bp(output, *label.value); + outputG.smoothL1Bp(output, *label.value, 1.0); } } diff --git a/paddle/math/Matrix.cpp b/paddle/math/Matrix.cpp index 6ac61be0bf1..c910146164e 100644 --- a/paddle/math/Matrix.cpp +++ b/paddle/math/Matrix.cpp @@ -3606,7 +3606,7 @@ void CpuMatrix::sumOfSquaresBp(Matrix& output, Matrix& label) { } } -void CpuMatrix::smoothL1(Matrix& output, Matrix& label) { +void CpuMatrix::smoothL1(Matrix& output, Matrix& label, real destScale) { CHECK(output.useGpu_ == false && label.useGpu_ == false) << "Matrix type are not equal"; @@ -3624,6 +3624,7 @@ void CpuMatrix::smoothL1(Matrix& output, Matrix& label) { for (size_t i = 0; i < numSamples; ++i, out += dim, lbl += dim) { for (size_t j = 0; j < dim; ++j) { real absVal = std::fabs(out[j] - lbl[j]); + cost[i] *= destScale; if (absVal < 1.0) cost[i] += 0.5 * absVal * absVal; else @@ -3632,7 +3633,7 @@ void CpuMatrix::smoothL1(Matrix& output, Matrix& label) { } } -void CpuMatrix::smoothL1Bp(Matrix& output, Matrix& label) { +void CpuMatrix::smoothL1Bp(Matrix& output, Matrix& label, real destScale) { CHECK(output.useGpu_ == false && label.useGpu_ == false) << "Matrix type are not equal"; @@ -3650,6 +3651,7 @@ void CpuMatrix::smoothL1Bp(Matrix& output, Matrix& label) { for (size_t i = 0; i < numSamples; ++i, out += dim, grad += dim, lbl += dim) { for (size_t j = 0; j < dim; ++j) { real val = out[j] - lbl[j]; + grad[j] *= destScale; if (std::fabs(val) < 1) { grad[j] += val; } else { diff --git a/paddle/math/Matrix.h b/paddle/math/Matrix.h index 3252adb19e4..748be850b4c 100644 --- a/paddle/math/Matrix.h +++ b/paddle/math/Matrix.h @@ -789,11 +789,11 @@ public: LOG(FATAL) << "Not implemented"; } - virtual void smoothL1(Matrix& output, Matrix& label) { + virtual void smoothL1(Matrix& output, Matrix& label, real destScale) { LOG(FATAL) << "Not implemented"; } - virtual void smoothL1Bp(Matrix& outputV, Matrix& label) { + virtual void smoothL1Bp(Matrix& outputV, Matrix& label, real destScale) { LOG(FATAL) << "Not implemented"; } @@ -1736,8 +1736,8 @@ public: /// gradient of sumOfSquares. void sumOfSquaresBp(Matrix& outputV, Matrix& label); - void smoothL1(Matrix& output, Matrix& label); - void smoothL1Bp(Matrix& output, Matrix& label); + void smoothL1(Matrix& output, Matrix& label, real destScale); + void smoothL1Bp(Matrix& output, Matrix& label, real destScale); void tanh(Matrix& output); void tanhDerivative(Matrix& output); -- GitLab From ec6068b187c679727246ebbed43468d56e274cb6 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Thu, 25 May 2017 13:45:37 +0800 Subject: [PATCH 0447/3256] Tinker with TravisCI --- paddle/scripts/run_python_tests.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/paddle/scripts/run_python_tests.sh b/paddle/scripts/run_python_tests.sh index 6dfc05a9497..1ed497aaecc 100755 --- a/paddle/scripts/run_python_tests.sh +++ b/paddle/scripts/run_python_tests.sh @@ -24,6 +24,8 @@ PYTHON=$1; shift if [ $USE_VIRTUALENV_FOR_TEST -ne 0 ]; then rm -rf .test_env virtualenv .test_env + unset PYTHONHOME + unset PYTHONPATH source .test_env/bin/activate PYTHON=python fi @@ -32,6 +34,8 @@ $PYTHON -m pip install $SCRIPTPATH/../dist/*.whl if [ "X${PADDLE_PACKAGE_DIR}" != "X" ]; then $PYTHON -m pip install ${PADDLE_PACKAGE_DIR}/*.whl +else + export PYTHONPATH=$SCRIPTPATH/../../python/ fi $PYTHON -m pip install ipython==5.3 -- GitLab From 273e3f444f083d7e113d77eb89b3290c2187f9d8 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Thu, 25 May 2017 14:49:53 +0800 Subject: [PATCH 0448/3256] Remove not necessary functionalities in Parameter --- paddle/math/BaseMatrix.h | 2 +- paddle/math/SparseRowMatrix.cpp | 2 +- paddle/math/SparseRowMatrix.h | 2 +- paddle/parameter/Parameter.cpp | 43 +---------------------- paddle/parameter/Parameter.h | 19 ---------- paddle/parameter/ThreadLocalBuffer.cpp | 35 ++++++++++++++++++ paddle/parameter/ThreadLocalBuffer.h | 22 ++++++++++++ paddle/pserver/ParameterClient2.cpp | 3 +- paddle/pserver/ParameterServer2.cpp | 22 ++++++------ paddle/trainer/RemoteParameterUpdater.cpp | 38 +++++++++++--------- paddle/trainer/ThreadParameterUpdater.cpp | 7 ++-- 11 files changed, 99 insertions(+), 96 deletions(-) create mode 100644 paddle/parameter/ThreadLocalBuffer.cpp create mode 100644 paddle/parameter/ThreadLocalBuffer.h diff --git a/paddle/math/BaseMatrix.h b/paddle/math/BaseMatrix.h index 6ed48c8d88e..120d69f718b 100644 --- a/paddle/math/BaseMatrix.h +++ b/paddle/math/BaseMatrix.h @@ -758,7 +758,7 @@ public: T p3); // decayRate /// apply L1/L2 to *this* - void applyL1(T learningRate, T decayRate); + virtual void applyL1(T learningRate, T decayRate); void applyL1(BaseMatrixT& lr, T learningRate, T decayRate); void applyL2(T learningRate, T decayRate); void applyL2(BaseMatrixT& lr, T learningRate, T decayRate); diff --git a/paddle/math/SparseRowMatrix.cpp b/paddle/math/SparseRowMatrix.cpp index b8c781ca1fd..b086433fe53 100644 --- a/paddle/math/SparseRowMatrix.cpp +++ b/paddle/math/SparseRowMatrix.cpp @@ -54,7 +54,7 @@ void SparseRowCpuMatrix::zeroMem() { clearRows(); } -void SparseRowCpuMatrix::applyL1Decay(real learningRate, real decayRate) { +void SparseRowCpuMatrix::applyL1(real learningRate, real decayRate) { apply([=](real* buf, size_t len) { CpuVector value(0, nullptr); value.subVecFrom(buf, 0, len); diff --git a/paddle/math/SparseRowMatrix.h b/paddle/math/SparseRowMatrix.h index 1ccbf97b259..8704eb038d5 100644 --- a/paddle/math/SparseRowMatrix.h +++ b/paddle/math/SparseRowMatrix.h @@ -94,7 +94,7 @@ public: /** * apply L1 to all sparse rows, should be apply after indices ready. */ - void applyL1Decay(real learningRate, real decayRate); + virtual void applyL1(real learningRate, real decayRate); void clearIndices() { clearRows(); } void zeroMemThread(size_t tid, size_t numThreads); diff --git a/paddle/parameter/Parameter.cpp b/paddle/parameter/Parameter.cpp index b8efabbe2a0..ebe36d49376 100644 --- a/paddle/parameter/Parameter.cpp +++ b/paddle/parameter/Parameter.cpp @@ -20,6 +20,7 @@ limitations under the License. */ #include "OptimizerFunctions.h" #include "OptimizerWithRegularizer.h" #include "ParameterUpdateFunctions.h" +#include "ThreadLocalBuffer.h" #include "hl_gpu.h" #include "paddle/math/CpuSparseMatrix.h" #include "paddle/math/MathUtils.h" @@ -262,15 +263,6 @@ void Parameter::setMat(ParameterType pType, int matType) { } } -SparsePrefetchRowCpuMatrix* Parameter::getPrefetchMatrix() { - MatrixPtr mat = mats_[PARAMETER_VALUE]; - if (mat) { - return dynamic_cast(mat.get()); - } - - return nullptr; -} - void Parameter::incUpdate(const UpdateCallback& callback) { // Static parameter is fixed, and does not need to be updated if (isStatic()) { @@ -422,37 +414,4 @@ bool Parameter::load(std::istream& s) { return true; } -ThreadLocal> Parameter::tlsTempBufs_; - -VectorPtr* Parameter::getTlsTempBufs() { - std::vector& bufs = *tlsTempBufs_; - if (bufs.empty()) { - bufs.resize(NUM_PARAMETER_TYPES); - for (auto& vec : bufs) { - vec.reset(new CpuVector(0, nullptr)); - } - } - return bufs.data(); -} - -void Parameter::exec(ExecFunc func) { - auto execFunc = [this, func](int tid, size_t numThreads) { - if (numThreads == 1) { // single thread - func(this->getBufs()); - } else { // multi thread - VectorPtr* vecs = Parameter::getTlsTempBufs(); - auto interval = calcSplitArrayInterval( - this->getSize(), (size_t)tid, numThreads, 8LU /*for avx*/); - for (size_t i = 0; i < (size_t)NUM_PARAMETER_TYPES; ++i) { - if (bufs_[i]) { - vecs[i]->subVecFrom(*bufs_[i], interval); - } - } - func(vecs); - } - }; - - getBuf(PARAMETER_VALUE)->exec(execFunc); -} - } // namespace paddle diff --git a/paddle/parameter/Parameter.h b/paddle/parameter/Parameter.h index 36d2b65f3bd..eb88c862034 100644 --- a/paddle/parameter/Parameter.h +++ b/paddle/parameter/Parameter.h @@ -209,14 +209,6 @@ public: intBufs_[pType] = iVec; } - SparsePrefetchRowCpuMatrix* getPrefetchMatrix(); - - float getLearnRate() const { return config_.learning_rate(); } - - float getInitMean() const { return config_.initial_mean(); } - - float getInitStandardDeviation() const { return config_.initial_std(); } - void setValueUpdated() { updated_ = true; } void clearValueUpdated() { updated_ = false; } @@ -356,8 +348,6 @@ protected: bool updated_; SparseFormat format_; - static ThreadLocal> tlsTempBufs_; - std::vector> updaterHooks_; public: @@ -371,15 +361,6 @@ public: static const std::string kMissParameterFail; static const std::string kMissParameterRand; static const std::string kMissParameterZero; - - static VectorPtr* getTlsTempBufs(); - - /** - * exec a func in single/multi thread. - * vecs is bufs_ of Parameter, as input of ExecFunc. - */ - typedef std::function ExecFunc; - void exec(ExecFunc func); }; typedef std::map ParameterMap; diff --git a/paddle/parameter/ThreadLocalBuffer.cpp b/paddle/parameter/ThreadLocalBuffer.cpp new file mode 100644 index 00000000000..b21dd15245c --- /dev/null +++ b/paddle/parameter/ThreadLocalBuffer.cpp @@ -0,0 +1,35 @@ +/* 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 "ThreadLocalBuffer.h" +#include "Parameter.h" + +namespace paddle { +namespace parameter { + +static ThreadLocal> tlsTempBufs_; + +VectorPtr* getThreadLocalBuffer() { + std::vector& bufs = *tlsTempBufs_; + if (bufs.empty()) { + bufs.resize(NUM_PARAMETER_TYPES); + for (auto& vec : bufs) { + vec.reset(new CpuVector(0, nullptr)); + } + } + return bufs.data(); +} + +} // namespace parameter +} // namespace paddle diff --git a/paddle/parameter/ThreadLocalBuffer.h b/paddle/parameter/ThreadLocalBuffer.h new file mode 100644 index 00000000000..c916519c974 --- /dev/null +++ b/paddle/parameter/ThreadLocalBuffer.h @@ -0,0 +1,22 @@ +/* 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/math/Vector.h" + +namespace paddle { +namespace parameter { +extern VectorPtr* getThreadLocalBuffer(); +} // namespace parameter +} // namespace paddle diff --git a/paddle/pserver/ParameterClient2.cpp b/paddle/pserver/ParameterClient2.cpp index a97859f83fe..f7e391f7632 100644 --- a/paddle/pserver/ParameterClient2.cpp +++ b/paddle/pserver/ParameterClient2.cpp @@ -243,7 +243,8 @@ void ParameterClient2::prepareSendData( CHECK_GE(blockSize, 1LU) << "blockSize should > 0 " << blockSize; const auto paraSize = parameter->getSize(); if (sparseUpdate) { - const auto prefetchMat = parameter->getPrefetchMatrix(); + auto prefetchMat = std::dynamic_pointer_cast( + parameter->getMat(PARAMETER_VALUE)); CHECK(prefetchMat != nullptr) << "prefetchMat is nullptr"; auto sendMat = dynamic_cast( parameter->getMat(parameterType).get()); diff --git a/paddle/pserver/ParameterServer2.cpp b/paddle/pserver/ParameterServer2.cpp index 19ff40ba7e9..41ac15336d3 100644 --- a/paddle/pserver/ParameterServer2.cpp +++ b/paddle/pserver/ParameterServer2.cpp @@ -18,7 +18,6 @@ limitations under the License. */ #include #include "paddle/math/SIMDFunctions.h" - #include "paddle/parameter/AverageOptimizer.h" #include "paddle/parameter/FirstOrderOptimizer.h" #include "paddle/parameter/OptimizerFunctions.h" @@ -26,6 +25,7 @@ limitations under the License. */ #include "paddle/parameter/ParameterOptimizer.h" #include "paddle/parameter/ParameterUpdateFunctions.h" #include "paddle/parameter/Regularizer.h" +#include "paddle/parameter/ThreadLocalBuffer.h" #include "paddle/utils/Flags.h" #include "paddle/utils/GlobalConstants.h" #include "paddle/utils/Stat.h" @@ -618,7 +618,7 @@ void ParameterServer2::asyncSGD(const SendParameterRequest& request, bool commitGradient = asyncGrdientCommitCheckAndStat(request); - VectorPtr* vecs = Parameter::getTlsTempBufs(); + VectorPtr* vecs = parameter::getThreadLocalBuffer(); size_t bufferIndex = 0; for (const auto& block : request.blocks()) { int64_t offset = getBlockOffset(block); @@ -1051,15 +1051,15 @@ void ParameterServer2::clearUnusedSegments(CpuVector* vec) { } void ParameterServer2::parallelExecForEachBlock(ExecFunc func) { - SyncThreadPool::execHelper(syncThreadPool_.get(), - [&](int tid, size_t numThreads) { - int64_t numBlocks = blockIdMap_.size(); - VectorPtr* vecs = Parameter::getTlsTempBufs(); - for (int64_t blockId = tid; blockId < numBlocks; - blockId += numThreads) { - func(blockId, vecs); - } - }); + SyncThreadPool::execHelper( + syncThreadPool_.get(), [&](int tid, size_t numThreads) { + int64_t numBlocks = blockIdMap_.size(); + VectorPtr* vecs = parameter::getThreadLocalBuffer(); + for (int64_t blockId = tid; blockId < numBlocks; + blockId += numThreads) { + func(blockId, vecs); + } + }); } void ParameterServer2::blockTraverse( diff --git a/paddle/trainer/RemoteParameterUpdater.cpp b/paddle/trainer/RemoteParameterUpdater.cpp index 6939738203f..7314266cb24 100644 --- a/paddle/trainer/RemoteParameterUpdater.cpp +++ b/paddle/trainer/RemoteParameterUpdater.cpp @@ -747,28 +747,32 @@ void SparseRemoteParameterUpdater::getParametersRemote(bool fullSize, bool apply) { ParameterType sendBackParameterType = (useApplyInPserver_ && apply) ? PARAMETER_APPLY : PARAMETER_VALUE; + std::function getParams; + std::function applyL1; if (fullSize) { - parameterClient_->getParameter( - /* recvParameterType= */ PARAMETER_VALUE, sendBackParameterType); - if (config_.shrink_parameter_value() > 0) { - for (auto& para : parameters_) { - if (para->getConfig().decay_rate_l1() > 0) { - para->getBuf(PARAMETER_VALUE) - ->applyL1(1.0f, // learningRate - config_.shrink_parameter_value()); // decayRate - } - } - } + getParams = [&] { + parameterClient_->getParameter( + /* recvParameterType= */ PARAMETER_VALUE, sendBackParameterType); + }; + applyL1 = [](Parameter& para, real decayRate) { + para.getBuf(PARAMETER_VALUE)->applyL1(/*lr=*/1.0f, decayRate); + }; } else { - REGISTER_TIMER("getParamSparse"); - parameterClient_->getParameterSparse( - /* recvParameterType= */ PARAMETER_VALUE, sendBackParameterType); + getParams = [&] { + parameterClient_->getParameterSparse( + /* recvParameterType= */ PARAMETER_VALUE, sendBackParameterType); + }; + applyL1 = [](Parameter& para, real decayRate) { + para.getMat(PARAMETER_VALUE)->applyL1(/*lr=*/1.0f, decayRate); + }; + } + { + REGISTER_TIMER("getParamDenseAndSparse"); + getParams(); if (config_.shrink_parameter_value() > 0) { for (auto& para : parameters_) { if (para->getConfig().decay_rate_l1() > 0) { - para->getPrefetchMatrix()->applyL1Decay( - 1.0f, // learningRate - config_.shrink_parameter_value()); // decayRate + applyL1(*para, config_.shrink_parameter_value()); } } } diff --git a/paddle/trainer/ThreadParameterUpdater.cpp b/paddle/trainer/ThreadParameterUpdater.cpp index 870d4a4b024..3c85c3aaac6 100644 --- a/paddle/trainer/ThreadParameterUpdater.cpp +++ b/paddle/trainer/ThreadParameterUpdater.cpp @@ -17,6 +17,7 @@ limitations under the License. */ #include "paddle/utils/Logging.h" #include "paddle/math/SparseRowMatrix.h" +#include "paddle/parameter/ThreadLocalBuffer.h" #include "paddle/utils/Thread.h" DECLARE_int32(trainer_count); @@ -98,7 +99,7 @@ void SgdThreadUpdater::threadTraverse( int tid, size_t numThreads, Parameter* para) { - VectorPtr* vecs = Parameter::getTlsTempBufs(); + VectorPtr* vecs = parameter::getThreadLocalBuffer(); if (para->isGradSparseUpdate()) { size_t height = para->getConfig().dims(0); size_t width = para->getConfig().dims(1); @@ -214,7 +215,7 @@ void SgdThreadUpdater::threadUpdateSparse(int tid, Parameter* para) { int pid = para->getID(); ParameterOptimizer* optimizer = optimizers_[pid].get(); - VectorPtr* vecs = Parameter::getTlsTempBufs(); + VectorPtr* vecs = parameter::getThreadLocalBuffer(); size_t height = para->getConfig().dims(0); size_t width = para->getConfig().dims(1); @@ -286,7 +287,7 @@ void SgdThreadUpdater::threadUpdateDense(int tid, Parameter* para) { int pid = para->getID(); ParameterOptimizer* optimizer = optimizers_[pid].get(); - VectorPtr* vecs = Parameter::getTlsTempBufs(); + VectorPtr* vecs = parameter::getThreadLocalBuffer(); auto interval = calcSplitArrayInterval( para->getSize(), (size_t)tid, numThreads, 8LU /*for avx*/); -- GitLab From 9a23cd60ad2c63ac7bf975ddf4789eeb926bed49 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Thu, 25 May 2017 15:29:52 +0800 Subject: [PATCH 0449/3256] Clean Unused functions --- .gitignore | 3 +++ paddle/parameter/Parameter.h | 29 ----------------------------- 2 files changed, 3 insertions(+), 29 deletions(-) diff --git a/.gitignore b/.gitignore index 2b30f7938c8..275173b9677 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,6 @@ third_party/ *~ bazel-* third_party/ + +# clion workspace. +cmake-build-* diff --git a/paddle/parameter/Parameter.h b/paddle/parameter/Parameter.h index eb88c862034..d77486ce42e 100644 --- a/paddle/parameter/Parameter.h +++ b/paddle/parameter/Parameter.h @@ -40,17 +40,6 @@ class Parameter; typedef std::function UpdateCallback; typedef std::function ParamInitCallback; -struct Segment { - int64_t beginDim; - int64_t endDim; - - // We allow the possibility that the parameters are not stored at contiguous - // memory locations for speed reason (i.e. data alignemnt) - // This means that the dimenstion is not same as the position in the memroy - // buffer. - int64_t beginPos; // beginning position in the local value or grad buffer -}; - class Parameter; typedef std::shared_ptr ParameterPtr; @@ -167,13 +156,6 @@ public: } } - void enableSharedType(ParameterType type, VectorPtr vec, MatType matType) { - if (!bufs_[type]) { - bufs_[type] = vec; - setMat(type, matType); - } - } - /// for batchGradientMachine: blockNum is number of partitions of the matrix. bool isGradShared(size_t* blockNum = NULL); @@ -203,12 +185,6 @@ public: const MatrixPtr& getMat(ParameterType pType) const { return mats_[pType]; } - const IVectorPtr& getIntBuf(ParameterType pType) { return intBufs_[pType]; } - - void setIntBuf(ParameterType pType, const IVectorPtr& iVec) { - intBufs_[pType] = iVec; - } - void setValueUpdated() { updated_ = true; } void clearValueUpdated() { updated_ = false; } @@ -235,8 +211,6 @@ public: */ bool load(std::istream& is); - std::vector& getGradientSegments() { return gradSegments_; } - void incShared() { sharedCount_++; } /** @@ -343,7 +317,6 @@ protected: int sharedCount_; int updateCounter_; - std::vector gradSegments_; // segments of non-zero gradient bool updated_; SparseFormat format_; @@ -351,10 +324,8 @@ protected: std::vector> updaterHooks_; public: - void setSharedCount(int cnt) { sharedCount_ = cnt; } int getSharedCount() { return sharedCount_; } - void singleUpdate(void* data); bool isSparse() { return config_.is_sparse(); } SparseFormat getFormat() { return format_; } -- GitLab From 32a85081385fec62332e8b1b8f8d9a305dc075d4 Mon Sep 17 00:00:00 2001 From: Jiangtao Hu Date: Thu, 25 May 2017 01:23:11 -0700 Subject: [PATCH 0450/3256] Support scalar computing. --- paddle/cuda/include/hl_cpu_matrix_kernel.cuh | 36 +-- ...el.cuh => hl_cpu_matrix_kernel_detail.cuh} | 122 ++++--- paddle/cuda/include/hl_cpu_scalar.cuh | 39 +++ paddle/cuda/include/hl_cpu_simd_neon.cuh | 58 ++++ paddle/cuda/include/hl_cpu_simd_sse.cuh | 65 ++++ paddle/cuda/include/hl_matrix_base.cuh | 10 +- paddle/cuda/include/hl_matrix_type.cuh | 22 +- paddle/cuda/include/hl_neon_matrix_kernel.cuh | 299 ------------------ 8 files changed, 233 insertions(+), 418 deletions(-) rename paddle/cuda/include/{hl_sse_matrix_kernel.cuh => hl_cpu_matrix_kernel_detail.cuh} (89%) create mode 100644 paddle/cuda/include/hl_cpu_scalar.cuh create mode 100644 paddle/cuda/include/hl_cpu_simd_neon.cuh create mode 100644 paddle/cuda/include/hl_cpu_simd_sse.cuh delete mode 100644 paddle/cuda/include/hl_neon_matrix_kernel.cuh diff --git a/paddle/cuda/include/hl_cpu_matrix_kernel.cuh b/paddle/cuda/include/hl_cpu_matrix_kernel.cuh index 9c49a4bd208..aaa24325514 100644 --- a/paddle/cuda/include/hl_cpu_matrix_kernel.cuh +++ b/paddle/cuda/include/hl_cpu_matrix_kernel.cuh @@ -17,10 +17,9 @@ limitations under the License. */ #include #include "hl_base.h" -#if defined(__ARM_NEON__) || defined(__ARM_NEON) -#include "hl_neon_matrix_kernel.cuh" -#else -#include "hl_sse_matrix_kernel.cuh" + +#ifndef __CUDA_ARCH__ +#include "hl_cpu_matrix_kernel_detail.cuh" #endif /** @@ -114,35 +113,6 @@ void hl_cpu_apply_quaternary_op(Op op, } } -template -void hl_matrix_row_op(Agg agg, Op op, Saver sv, - int dimM, int dimN, - real *dst, int ld, - real *A, int lda) { - for (int i = 0; i < dimM; i++) { - real tmp = agg.init(); - for (int j = 0; j < dimN; j++) { - tmp = agg(tmp, op(A[i * lda + j])); - } - dst[i*ld] = sv(dst[i*ld], tmp); - } -} - -template -void hl_matrix_row_op(Agg agg, Op op, Saver sv, - int dimM, int dimN, - real *dst, int ld, - real *A, int lda, - real *B, int ldb) { - for (int i = 0; i < dimM; i++) { - real tmp = agg.init(); - for (int j = 0; j < dimN; j++) { - tmp = agg(tmp, op(A[i * lda + j], B[i * ldb + j])); - } - dst[i*ld] = sv(dst[i*ld], tmp); - } -} - template void hl_cpu_matrix_row_op(Agg agg, Op op, Saver sv, int dimM, int dimN, diff --git a/paddle/cuda/include/hl_sse_matrix_kernel.cuh b/paddle/cuda/include/hl_cpu_matrix_kernel_detail.cuh similarity index 89% rename from paddle/cuda/include/hl_sse_matrix_kernel.cuh rename to paddle/cuda/include/hl_cpu_matrix_kernel_detail.cuh index 9e50580669d..85ca836fdc4 100644 --- a/paddle/cuda/include/hl_sse_matrix_kernel.cuh +++ b/paddle/cuda/include/hl_cpu_matrix_kernel_detail.cuh @@ -13,26 +13,11 @@ See the License for the specific language governing permissions and limitations under the License. */ -#ifndef HL_SSE_MATRIX_KERNEL_CUH_ -#define HL_SSE_MATRIX_KERNEL_CUH_ +#ifndef HL_MATRIX_KERNEL_DETAIL_CUH_ +#define HL_MATRIX_KERNEL_DETAIL_CUH_ #include "hl_matrix_type.cuh" -#define VECTOR_SIZE 16 - -#ifndef PADDLE_TYPE_DOUBLE -/* number of float in vector */ -#define VECTOR_LEN 4 -#define VECTOR_SET _mm_set_ps1 -#else -#if defined(__APPLE__) || defined(__OSX__) -#define _mm_set_pd1 _mm_set1_pd -#endif -/* number of double in vector */ -#define VECTOR_LEN 2 -#define VECTOR_SET _mm_set_pd1 -#endif - inline bool hl_check_align(size_t size) { return !(size & (VECTOR_SIZE - 1)); } @@ -41,27 +26,63 @@ inline bool hl_check_align(void *ptr) { return hl_check_align(reinterpret_cast(ptr)); } -#ifndef PADDLE_TYPE_DOUBLE -template -inline real hl_agg_op(Agg agg, vecType mm) { - __m128 lo = _mm_unpacklo_ps(mm, mm); - __m128 hi = _mm_unpackhi_ps(mm, mm); - __m128 tmp1 = agg.vecOp(lo, hi); - __m128 tmp2 = _mm_movehl_ps(tmp1, tmp1); - __m128 ret = agg.vecOp(tmp1, tmp2); +template +void hl_matrix_row_op(Agg agg, Op op, Saver sv, + int dimM, int dimN, + real *dst, int ld, + real *A, int lda) { + for (int i = 0; i < dimM; i++) { + real tmp = agg.init(); + for (int j = 0; j < dimN; j++) { + tmp = agg(tmp, op(A[i * lda + j])); + } + dst[i*ld] = sv(dst[i*ld], tmp); + } +} - return _mm_cvtss_f32(ret); +template +void hl_matrix_row_op(Agg agg, Op op, Saver sv, + int dimM, int dimN, + real *dst, int ld, + real *A, int lda, + real *B, int ldb) { + for (int i = 0; i < dimM; i++) { + real tmp = agg.init(); + for (int j = 0; j < dimN; j++) { + tmp = agg(tmp, op(A[i * lda + j], B[i * ldb + j])); + } + dst[i*ld] = sv(dst[i*ld], tmp); + } } -#else -template -inline real hl_agg_op(Agg agg, vecType mm) { - __m128d lo = _mm_unpacklo_pd(mm, mm); - __m128d hi = _mm_unpackhi_pd(mm, mm); - __m128d ret = agg.vecOp(lo, hi); - - return _mm_cvtsd_f64(ret); + +template +void hl_matrix_column_op(Agg agg, Op op, Saver sv, + int dimM, int dimN, + real *dst, + real *A, int lda) { + for (int j = 0; j < dimN; j++) { + real tmp = agg.init(); + for (int i = 0; i < dimM; i++) { + tmp = agg(tmp, op(A[i * lda + j])); + } + dst[j] = sv(dst[j], tmp); + } +} + +template +void hl_matrix_column_op(Agg agg, Op op, Saver sv, + int dimM, int dimN, + real *dst, + real *A, int lda, + real *B, int ldb) { + for (int j = 0; j < dimN; j++) { + real tmp = agg.init(); + for (int i = 0; i < dimM; i++) { + tmp = agg(tmp, op(A[i * lda + j], B[i * ldb + j])); + } + dst[j] = sv(dst[j], tmp); + } } -#endif template void hl_sse_matrix_row_op(Agg agg, Op op, Saver sv, @@ -118,35 +139,6 @@ void hl_sse_matrix_row_op(Agg agg, Op op, Saver sv, } } -template -void hl_matrix_column_op(Agg agg, Op op, Saver sv, - int dimM, int dimN, - real *dst, - real *A, int lda) { - for (int j = 0; j < dimN; j++) { - real tmp = agg.init(); - for (int i = 0; i < dimM; i++) { - tmp = agg(tmp, op(A[i * lda + j])); - } - dst[j] = sv(dst[j], tmp); - } -} - -template -void hl_matrix_column_op(Agg agg, Op op, Saver sv, - int dimM, int dimN, - real *dst, - real *A, int lda, - real *B, int ldb) { - for (int j = 0; j < dimN; j++) { - real tmp = agg.init(); - for (int i = 0; i < dimM; i++) { - tmp = agg(tmp, op(A[i * lda + j], B[i * ldb + j])); - } - dst[j] = sv(dst[j], tmp); - } -} - /* * MaxRow greater than or equal dimN * dimN is multiples of VECTOR_LEN @@ -315,4 +307,4 @@ void hl_sse_matrix_column_op(Agg agg, Op op, Saver sv, } } -#endif /* HL_SSE_MATRIX_KERNEL_CUH_ */ +#endif /* HL_MATRIX_KERNEL_DETAIL_CUH_ */ diff --git a/paddle/cuda/include/hl_cpu_scalar.cuh b/paddle/cuda/include/hl_cpu_scalar.cuh new file mode 100644 index 00000000000..c5e58015f31 --- /dev/null +++ b/paddle/cuda/include/hl_cpu_scalar.cuh @@ -0,0 +1,39 @@ +/* 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. */ + +#ifndef HL_CPU_SCALAR_CUH_ +#define HL_CPU_SCALAR_CUH_ + +#ifndef PADDLE_TYPE_DOUBLE +/* size of float */ +#define VECTOR_SIZE 4 +#else +/* size of double */ +#define VECTOR_SIZE 8 +#endif + +typedef real vecType; + +inline void set_zero(vecType &mm) { mm = (vecType) 0.0f; } + +/* Consider a real as a vector */ +#define VECTOR_LEN 1 +#define VECTOR_SET set_zero + +template +inline real hl_agg_op(Agg agg, vecType mm) { + return mm; +} + +#endif // HL_CPU_SCALAR_CUH_ diff --git a/paddle/cuda/include/hl_cpu_simd_neon.cuh b/paddle/cuda/include/hl_cpu_simd_neon.cuh new file mode 100644 index 00000000000..aaba35df091 --- /dev/null +++ b/paddle/cuda/include/hl_cpu_simd_neon.cuh @@ -0,0 +1,58 @@ +/* Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserve. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. */ + +#ifndef HL_CPU_SIMD_NEON_CUH_ +#define HL_CPU_SIMD_NEON_CUH_ + +#include + +#define VECTOR_SIZE 16 + +#ifndef PADDLE_TYPE_DOUBLE + +typedef float32x4_t vecType; + +/* number of float in vector */ +#define VECTOR_LEN 4 +#define VECTOR_SET vdupq_n_f32 + +template +inline real hl_agg_op(Agg agg, vecType mm) { + float32x4_t rev = vrev64q_f32(mm); + float32x4_t tmp1 = agg.vecOp(rev, rev); + float32x2_t lo = vget_high_f32(rev); + float32x2_t hi = vget_low_f32(rev); + float32x4_t tmp2 = vcombine_f32(hi, lo); + float32x4_t ret = agg.vecOp(tmp1, tmp2); + + return vgetq_lane_f32(ret, 0); +} + +#else + +#ifdef __aarch64__ +typedef float64x2_t vecType; + +/* number of float in vector */ +#define VECTOR_LEN 2 +#define VECTOR_SET vdupq_n_f64 + +#error To be implemented +#else +#error NEON instructions does not support double precision +#endif + +#endif + +#endif // HL_CPU_SIMD_NEON_CUH_ diff --git a/paddle/cuda/include/hl_cpu_simd_sse.cuh b/paddle/cuda/include/hl_cpu_simd_sse.cuh new file mode 100644 index 00000000000..99286c1a3f0 --- /dev/null +++ b/paddle/cuda/include/hl_cpu_simd_sse.cuh @@ -0,0 +1,65 @@ +/* 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. */ + +#ifndef HL_SIMD_SSE_CUH_ +#define HL_SIMD_SSE_CUH_ + +#include +#include +#include + +#define VECTOR_SIZE 16 + +#ifndef PADDLE_TYPE_DOUBLE + +typedef __m128 vecType; + +/* number of float in vector */ +#define VECTOR_LEN 4 +#define VECTOR_SET _mm_set_ps1 + +template +inline real hl_agg_op(Agg agg, vecType mm) { + __m128 lo = _mm_unpacklo_ps(mm, mm); + __m128 hi = _mm_unpackhi_ps(mm, mm); + __m128 tmp1 = agg.vecOp(lo, hi); + __m128 tmp2 = _mm_movehl_ps(tmp1, tmp1); + __m128 ret = agg.vecOp(tmp1, tmp2); + + return _mm_cvtss_f32(ret); +} + +#else + +typedef __m128d vecType; + +/* number of double in vector */ +#define VECTOR_LEN 2 +#if defined(__APPLE__) || defined(__OSX__) +#define _mm_set_pd1 _mm_set1_pd +#endif +#define VECTOR_SET _mm_set_pd1 + +template +inline real hl_agg_op(Agg agg, vecType mm) { + __m128d lo = _mm_unpacklo_pd(mm, mm); + __m128d hi = _mm_unpackhi_pd(mm, mm); + __m128d ret = agg.vecOp(lo, hi); + + return _mm_cvtsd_f64(ret); +} + +#endif + +#endif // HL_SIMD_SSE_CUH_ diff --git a/paddle/cuda/include/hl_matrix_base.cuh b/paddle/cuda/include/hl_matrix_base.cuh index 8b755c1095c..545120128b4 100644 --- a/paddle/cuda/include/hl_matrix_base.cuh +++ b/paddle/cuda/include/hl_matrix_base.cuh @@ -52,7 +52,11 @@ public: } }; -#ifdef __CUDA_ARCH__ +#if defined(__SSE3__) +#include "hl_matrix_base_sse.cuh" +#elif (defined(__ARM__NEON__) || defined(__ARM_NEON)) +#include "hl_matrix_base_neon.cuh" +#else typedef BaseOp SSESum; typedef BaseOp SSEMax; typedef BaseOp SSEMin; @@ -66,10 +70,6 @@ typedef BaseOp SSESquaredDiff; typedef BaseOp SSEFirst; typedef BaseOp SSESecond; typedef BaseOp SSEClassificationError; -#elif defined(__ARM__NEON__) || defined(__ARM_NEON) -#include "hl_matrix_base_neon.cuh" -#else -#include "hl_matrix_base_sse.cuh" #endif namespace aggregate { diff --git a/paddle/cuda/include/hl_matrix_type.cuh b/paddle/cuda/include/hl_matrix_type.cuh index f965ba96679..7d6face5f0e 100644 --- a/paddle/cuda/include/hl_matrix_type.cuh +++ b/paddle/cuda/include/hl_matrix_type.cuh @@ -17,29 +17,19 @@ limitations under the License. */ #include "hl_base.h" -#if defined(__CUDA_ARCH__) +#ifdef __CUDA_ARCH__ #include #ifndef PADDLE_TYPE_DOUBLE typedef float4 vecType; #else typedef double2 vecType; #endif -#elif (defined __ARM_NEON) || (defined __ARM_NEON__) -#include -#ifndef PADDLE_TYPE_DOUBLE -typedef float32x4_t vecType; -#else -#error NEON instructions does not support double precision -#endif +#elif defined(__SSE3__) +#include "hl_cpu_simd_sse.cuh" +#elif defined(__ARM_NEON) || defined(__ARM_NEON__) +#include "hl_cpu_simd_neon.cuh" #else -#include -#include -#include -#ifndef PADDLE_TYPE_DOUBLE -typedef __m128 vecType; -#else -typedef __m128d vecType; -#endif +#include "hl_cpu_scalar.cuh" #endif #ifdef __CUDA_ARCH__ diff --git a/paddle/cuda/include/hl_neon_matrix_kernel.cuh b/paddle/cuda/include/hl_neon_matrix_kernel.cuh deleted file mode 100644 index 7b4e5b00079..00000000000 --- a/paddle/cuda/include/hl_neon_matrix_kernel.cuh +++ /dev/null @@ -1,299 +0,0 @@ -/* 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. */ - - -#ifndef HL_NEON_MATRIX_KERNEL_CUH_ -#define HL_NEON_MATRIX_KERNEL_CUH_ - -#include "hl_matrix_type.cuh" - -#define VECTOR_SIZE 16 - -/* number of float in vector */ -#define VECTOR_LEN 4 -#define VECTOR_SET vdupq_n_f32 - -inline bool hl_check_align(size_t size) { - return !(size & (VECTOR_SIZE - 1)); -} - -inline bool hl_check_align(void *ptr) { - return hl_check_align(reinterpret_cast(ptr)); -} - -template -inline real hl_agg_op(Agg agg, vecType mm) { - float32x4_t rev = vrev64q_f32(mm); - float32x4_t tmp1 = agg.vecOp(rev, rev); - float32x2_t lo = vget_high_f32(rev); - float32x2_t hi = vget_low_f32(rev); - float32x4_t tmp2 = vcombine_f32(hi, lo); - float32x4_t ret = agg.vecOp(tmp1, tmp2); - - return vgetq_lane_f32(ret, 0); -} - -template -void hl_sse_matrix_row_op(Agg agg, Op op, Saver sv, - int dimM, int dimN, - real *dst, int ld, - real *A, int lda) { - for (int i = 0; i < dimM; i++, A += lda) { - vecType mm = VECTOR_SET(agg.init()); - vecType *a = (vecType*)(A); - for (int j = 0; j < dimN / VECTOR_LEN; j++, a++) { - mm = agg.vecOp(mm, op.vecOp(*a)); - } - - int rem = dimN % VECTOR_LEN; - if (rem) { - real tmp = hl_agg_op(agg, mm); - real *a = A + (dimN / VECTOR_LEN) * VECTOR_LEN; - for (int j = 0; j < rem; j++) { - tmp = agg(tmp, op(a[j])); - } - dst[i*ld] = sv(dst[i*ld], tmp); - } else { - dst[i*ld] = sv(dst[i*ld], hl_agg_op(agg, mm)); - } - } -} - -template -void hl_sse_matrix_row_op(Agg agg, Op op, Saver sv, - int dimM, int dimN, - real *dst, int ld, - real *A, int lda, - real *B, int ldb) { - for (int i = 0; i < dimM; i++, A += lda, B += ldb) { - vecType mm = VECTOR_SET(agg.init()); - vecType *a = (vecType*)(A); - vecType *b = (vecType*)(B); - for (int j = 0; j < dimN / VECTOR_LEN; j++, a++, b++) { - mm = agg.vecOp(mm, op.vecOp(*a, *b)); - } - - int rem = dimN % VECTOR_LEN; - if (rem) { - real tmp = hl_agg_op(agg, mm); - real *a = A + (dimN / VECTOR_LEN) * VECTOR_LEN; - real *b = B + (dimN / VECTOR_LEN) * VECTOR_LEN; - for (int j = 0; j < rem; j++) { - tmp = agg(tmp, op(a[j], b[j])); - } - dst[i*ld] = sv(dst[i*ld], tmp); - } else { - dst[i*ld] = sv(dst[i*ld], hl_agg_op(agg, mm)); - } - } -} - -template -void hl_matrix_column_op(Agg agg, Op op, Saver sv, - int dimM, int dimN, - real *dst, - real *A, int lda) { - for (int j = 0; j < dimN; j++) { - real tmp = agg.init(); - for (int i = 0; i < dimM; i++) { - tmp = agg(tmp, op(A[i * lda + j])); - } - dst[j] = sv(dst[j], tmp); - } -} - -template -void hl_matrix_column_op(Agg agg, Op op, Saver sv, - int dimM, int dimN, - real *dst, - real *A, int lda, - real *B, int ldb) { - for (int j = 0; j < dimN; j++) { - real tmp = agg.init(); - for (int i = 0; i < dimM; i++) { - tmp = agg(tmp, op(A[i * lda + j], B[i * ldb + j])); - } - dst[j] = sv(dst[j], tmp); - } -} - -/* - * MaxRow greater than or equal dimN - * dimN is multiples of VECTOR_LEN - * so rem <= MaxRow / VECTOR_LEN - */ -template -void hl_sse_column_op_with_rem(Agg agg, Op op, Saver sv, - int dimM, int dimN, - real *dst, - real *A, int lda) { - vecType mm[MaxRow / VECTOR_LEN]; - for (int n = 0; n < MaxRow / VECTOR_LEN; n++) { - mm[n] = VECTOR_SET(agg.init()); - } - - for (int i = 0; i < dimM; i++) { - vecType *a = (vecType*)(A + i * lda); - for (int n = 0; n < dimN / VECTOR_LEN; n++) { - mm[n] = agg.vecOp(mm[n], op.vecOp(a[n])); - } - } - - vecType *result = (vecType*)(dst); - for (int n = 0; n < dimN / VECTOR_LEN; n++) { - result[n] = sv.vecOp(result[n], mm[n]); - } - - int rem = dimN % VECTOR_LEN; - if (rem) { - A += (dimN / VECTOR_LEN) * VECTOR_LEN; - dst += (dimN / VECTOR_LEN) * VECTOR_LEN; - hl_matrix_column_op(agg, op, sv, dimM, rem, dst, A, lda); - } -} - -/* - * dimN is multiples of VECTOR_LEN - * dimN greater than Step - */ -template -void hl_sse_matrix_column_op(Agg agg, Op op, Saver sv, - int dimM, int dimN, - real *dst, - real *A, int lda) { - for (int j = 0; j < dimN / Step; j++, dst += Step, A += Step) { - vecType mm[Step / VECTOR_LEN]; - for (int n = 0; n < Step / VECTOR_LEN; n++) { - mm[n] = VECTOR_SET(agg.init()); - } - - for (int i = 0; i < dimM; i++) { - vecType *a = (vecType*)(A + i * lda); - for (int n = 0; n < Step / VECTOR_LEN; n++) { - mm[n] = agg.vecOp(mm[n], op.vecOp(a[n])); - } - } - - vecType *result = (vecType*)(dst); - for (int n = 0; n < Step / VECTOR_LEN; n++) { - result[n] = sv.vecOp(result[n], mm[n]); - } - } - - int remRow = dimN % Step; - if (remRow) { - hl_sse_column_op_with_rem(agg, op, sv, dimM, remRow, dst, A, lda); - } -} - -template -void hl_sse_matrix_column_op(Agg agg, Op op, Saver sv, - int dimM, int dimN, - real *dst, - real *A, int lda) { - if (dimN <= 16) { - hl_sse_matrix_column_op<16>(agg, op, sv, dimM, dimN, dst, A, lda); - } else if (dimN <= 32) { - hl_sse_matrix_column_op<32>(agg, op, sv, dimM, dimN, dst, A, lda); - } else if (dimN <= 1024 || dimM <= 512) { - hl_sse_matrix_column_op<64>(agg, op, sv, dimM, dimN, dst, A, lda); - } else { - hl_sse_matrix_column_op<1024>(agg, op, sv, dimM, dimN, dst, A, lda); - } -} - -template -void hl_sse_column_op_with_rem(Agg agg, Op op, Saver sv, - int dimM, int dimN, - real *dst, - real *A, int lda, - real *B, int ldb) { - vecType mm[MaxRow / VECTOR_LEN]; - for (int n = 0; n < MaxRow / VECTOR_LEN; n++) { - mm[n] = VECTOR_SET(agg.init()); - } - - for (int i = 0; i < dimM; i++) { - vecType *a = (vecType*)(A + i * lda); - vecType *b = (vecType*)(B + i * ldb); - for (int n = 0; n < dimN / VECTOR_LEN; n++) { - mm[n] = agg.vecOp(mm[n], op.vecOp(a[n], b[n])); - } - } - - vecType *result = (vecType*)(dst); - for (int n = 0; n < dimN / VECTOR_LEN; n++) { - result[n] = sv.vecOp(result[n], mm[n]); - } - - int rem = dimN % VECTOR_LEN; - if (rem) { - A += (dimN / VECTOR_LEN) * VECTOR_LEN; - B += (dimN / VECTOR_LEN) * VECTOR_LEN; - dst += (dimN / VECTOR_LEN) * VECTOR_LEN; - hl_matrix_column_op(agg, op, sv, dimM, rem, dst, A, lda, B, ldb); - } -} - -template -void hl_sse_matrix_column_op(Agg agg, Op op, Saver sv, - int dimM, int dimN, - real *dst, - real *A, int lda, - real *B, int ldb) { - for (int j = 0; j < dimN / Step; j++, dst += Step, A += Step, B += Step) { - vecType mm[Step / VECTOR_LEN]; - for (int n = 0; n < Step / VECTOR_LEN; n++) { - mm[n] = VECTOR_SET(agg.init()); - } - - for (int i = 0; i < dimM; i++) { - vecType *a = (vecType*)(A + i * lda); - vecType *b = (vecType*)(B + i * ldb); - for (int n = 0; n < Step / VECTOR_LEN; n++) { - mm[n] = agg.vecOp(mm[n], op.vecOp(a[n], b[n])); - } - } - - vecType *result = (vecType*)(dst); - for (int n = 0; n < Step / VECTOR_LEN; n++) { - result[n] = sv.vecOp(result[n], mm[n]); - } - } - - int remRow = dimN % Step; - if (remRow) { - hl_sse_column_op_with_rem( - agg, op, sv, dimM, remRow, dst, A, lda, B, ldb); - } -} - -template -void hl_sse_matrix_column_op(Agg agg, Op op, Saver sv, - int dimM, int dimN, - real *dst, - real *A, int lda, - real *B, int ldb) { - if (dimN <= 16) { - hl_sse_matrix_column_op<16>(agg, op, sv, dimM, dimN, dst, A, lda, B, ldb); - } else if (dimN <= 32) { - hl_sse_matrix_column_op<32>(agg, op, sv, dimM, dimN, dst, A, lda, B, ldb); - } else if (dimN <= 1024 || dimM <= 512) { - hl_sse_matrix_column_op<64>(agg, op, sv, dimM, dimN, dst, A, lda, B, ldb); - } else { - hl_sse_matrix_column_op<1024>(agg, op, sv, dimM, dimN, dst, A, lda, B, ldb); - } -} - -#endif /* HL_NEON_MATRIX_KERNEL_CUH_ */ -- GitLab From 0ee31a03b015a7fe2d5c7eed724ad60a85a5777d Mon Sep 17 00:00:00 2001 From: Luo Tao Date: Thu, 25 May 2017 17:25:26 +0800 Subject: [PATCH 0451/3256] Expose the "reverse" argument for recurrent_group in V2 API --- python/paddle/v2/layer.py | 14 ++++++++------ python/paddle/v2/tests/test_rnn_layer.py | 5 +++-- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/python/paddle/v2/layer.py b/python/paddle/v2/layer.py index 3d9caeec589..919c531d184 100644 --- a/python/paddle/v2/layer.py +++ b/python/paddle/v2/layer.py @@ -360,7 +360,7 @@ mixed.__doc__ = conf_helps.mixed_layer.__doc__ class RecurrentLayerInput(Layer): - def __init__(self, recurrent_name, index, parent_layers): + def __init__(self, recurrent_name, index, parent_layers, reverse): parents_len = len(parent_layers) assert parents_len <= 1 if parents_len == 0: @@ -368,6 +368,7 @@ class RecurrentLayerInput(Layer): else: self.__parents__ = parent_layers.values()[0] self.__recurrent_name__ = recurrent_name + self.__reverse__ = reverse name = self.__parents__[ index].name if index >= 0 else self.context_name() super(RecurrentLayerInput, self).__init__( @@ -380,7 +381,8 @@ class RecurrentLayerInput(Layer): model_type('recurrent_nn') RecurrentLayerGroupWithoutOutLinksBegin( name=self.__recurrent_name__, - in_links=map(lambda x: x.name, self.__parents__)) + in_links=map(lambda x: x.name, self.__parents__), + seq_reversed=self.__reverse__) return self @@ -461,7 +463,7 @@ del each_layer_name @wrap_name_default() -def recurrent_group(step, input, name=None): +def recurrent_group(step, input, reverse=False, name=None): if not isinstance(input, collections.Sequence): input = [input] @@ -471,14 +473,14 @@ def recurrent_group(step, input, name=None): RecurrentLayerInput( recurrent_name=name, index=i, - parent_layers={'recurrent_inputs': non_static_inputs}) - for i in xrange(len(non_static_inputs)) + parent_layers={'recurrent_inputs': non_static_inputs}, + reverse=reverse) for i in xrange(len(non_static_inputs)) ] extra_input = None if len(non_static_inputs) == 0: extra_input = RecurrentLayerInput( - recurrent_name=name, index=-1, parent_layers={}) + recurrent_name=name, index=-1, parent_layers={}, reverse=reverse) def __real_step__(*args): rnn_input = list(args) diff --git a/python/paddle/v2/tests/test_rnn_layer.py b/python/paddle/v2/tests/test_rnn_layer.py index 5fbbd20eb76..845277c0128 100644 --- a/python/paddle/v2/tests/test_rnn_layer.py +++ b/python/paddle/v2/tests/test_rnn_layer.py @@ -42,7 +42,8 @@ class RNNTest(unittest.TestCase): def test(): data = conf_helps.data_layer(name="word", size=dict_dim) embd = conf_helps.embedding_layer(input=data, size=word_dim) - conf_helps.recurrent_group(name="rnn", step=step, input=embd) + conf_helps.recurrent_group( + name="rnn", step=step, input=embd, reverse=True) return str(parse_network(test)) @@ -60,7 +61,7 @@ class RNNTest(unittest.TestCase): name="word", type=data_type.integer_value(dict_dim)) embd = layer.embedding(input=data, size=word_dim) rnn_layer = layer.recurrent_group( - name="rnn", step=new_step, input=embd) + name="rnn", step=new_step, input=embd, reverse=True) return str(layer.parse_network(rnn_layer)) diff = difflib.unified_diff(parse_old_rnn().splitlines(1), -- GitLab From 7e99af556fca2a7179dc77193754c4b8831b5ac4 Mon Sep 17 00:00:00 2001 From: dangqingqing Date: Thu, 25 May 2017 20:07:30 +0800 Subject: [PATCH 0452/3256] follow comments --- paddle/py_paddle/dataprovider_converter.py | 42 ++++++++++++---------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/paddle/py_paddle/dataprovider_converter.py b/paddle/py_paddle/dataprovider_converter.py index cfb82e92d51..6234dc65dcd 100644 --- a/paddle/py_paddle/dataprovider_converter.py +++ b/paddle/py_paddle/dataprovider_converter.py @@ -60,7 +60,7 @@ class IScanner(object): """ pass - def finish_pre_scan(self, argument, dat=None): + def finish_pre_scan(self, argument): """ Finish first scan pass. Allocate the memory. @@ -103,23 +103,29 @@ class DenseScanner(IScanner): def pre_scan(self, dat): self.__height__ += 1 + if self.__shape__ is None: + self.__shape__ = numpy.array(dat).shape + if len(self.__shape__) > 3: + raise ValueError( + "The dimension of input cannot be greater than 3.") + else: + if self.__shape__ != numpy.array(dat).shape: + raise ValueError( + "The data shape must be same in one mini-batch.") - def finish_pre_scan(self, argument, dat=None): - self.__shape__ = numpy.array(dat).shape - if len(self.__shape__) > 3: - raise ValueError("The dimension of input is greater than 3.") + def finish_pre_scan(self, argument): dim = reduce(lambda x, y: x * y, self.__shape__) - if len(self.__shape__) == 1: - assert dim == self.input_type.dim + if len(self.__shape__) == 1 and dim != self.input_type.dim: + raise ValueError("The data size must be equal to it in data layer.") self.__mat__ = numpy.ndarray( shape=(self.__height__, dim), dtype=numpy.float32) self.__height__ = 0 def scan(self, dat): - if isinstance(dat, numpy.ndarray): - assert self.__shape__ == dat.shape - dat = dat.flatten() - self.__mat__[self.__height__] = dat + # It's better to use NumPy array for speed. + d = numpy.array(dat) + d = d.flatten() + self.__mat__[self.__height__] = d self.__height__ += 1 def finish_scan(self, argument): @@ -136,6 +142,7 @@ class DenseScanner(IScanner): h, w = self.__shape__[-2:] argument.setSlotFrameHeight(self.pos, h) argument.setSlotFrameWidth(self.pos, w) + self.__shape__ = None class SparseBinaryScanner(IScanner): @@ -186,7 +193,7 @@ class IndexScanner(IScanner): def pre_scan(self, dat): self.__idx__ += 1 - def finish_pre_scan(self, argument, dat=None): + def finish_pre_scan(self, argument): self.__ids__ = [0] * self.__idx__ self.__idx__ = 0 @@ -211,8 +218,8 @@ class SequenceScanner(IScanner): for each in dat: self.__inner_scanner__.pre_scan(each) - def finish_pre_scan(self, argument, dat=None): - self.__inner_scanner__.finish_pre_scan(argument, dat) + def finish_pre_scan(self, argument): + self.__inner_scanner__.finish_pre_scan(argument) def scan(self, dat): self.__seq__.append(self.__seq__[-1] + self.get_size(dat)) @@ -253,11 +260,8 @@ class DataProviderConverter(object): for each_step, scanner in itertools.izip(each_sample, scanners): scanner.pre_scan(each_step) - # Some scanners, like dense scanner, pre-allocate memory for mini-batch - # in finish_pre_scan function. The dat[0] is used to calculate the size - # of input data. - for scanner, each_feature in itertools.izip(scanners, dat[0]): - scanner.finish_pre_scan(argument, each_feature) + for scanner in scanners: + scanner.finish_pre_scan(argument) for each_sample in dat: for each_step, scanner in itertools.izip(each_sample, scanners): -- GitLab From 8e06f7314a5b18ae0a28687e9fb05b4f417fca20 Mon Sep 17 00:00:00 2001 From: dangqingqing Date: Thu, 25 May 2017 20:15:04 +0800 Subject: [PATCH 0453/3256] refine code --- paddle/py_paddle/dataprovider_converter.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/paddle/py_paddle/dataprovider_converter.py b/paddle/py_paddle/dataprovider_converter.py index 6234dc65dcd..edc2e029237 100644 --- a/paddle/py_paddle/dataprovider_converter.py +++ b/paddle/py_paddle/dataprovider_converter.py @@ -100,6 +100,7 @@ class DenseScanner(IScanner): self.__mat__ = None self.__shape__ = None self.__height__ = 0 + self.__dim__ = 0 def pre_scan(self, dat): self.__height__ += 1 @@ -108,24 +109,25 @@ class DenseScanner(IScanner): if len(self.__shape__) > 3: raise ValueError( "The dimension of input cannot be greater than 3.") + self.__dim__ = reduce(lambda x, y: x * y, self.__shape__) + if len(self.__shape__) == 1 and self.__dim__ != self.input_type.dim: + raise ValueError( + "The data size must be equal to it in data layer.") else: if self.__shape__ != numpy.array(dat).shape: raise ValueError( "The data shape must be same in one mini-batch.") def finish_pre_scan(self, argument): - dim = reduce(lambda x, y: x * y, self.__shape__) - if len(self.__shape__) == 1 and dim != self.input_type.dim: - raise ValueError("The data size must be equal to it in data layer.") self.__mat__ = numpy.ndarray( - shape=(self.__height__, dim), dtype=numpy.float32) + shape=(self.__height__, self.__dim__), dtype=numpy.float32) self.__height__ = 0 def scan(self, dat): # It's better to use NumPy array for speed. - d = numpy.array(dat) - d = d.flatten() - self.__mat__[self.__height__] = d + dat = numpy.array(dat) + dat = dat.flatten() + self.__mat__[self.__height__] = dat self.__height__ += 1 def finish_scan(self, argument): -- GitLab From 35bfb1d5c3d516314dbc436ea4e557eae47be81c Mon Sep 17 00:00:00 2001 From: xuwei06 Date: Thu, 25 May 2017 10:26:32 -0700 Subject: [PATCH 0454/3256] Allow setting both is_static and initial_mean, initial_std at the same time. Some time we simply want to have a fixed initialization for a model. --- python/paddle/trainer_config_helpers/attrs.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/python/paddle/trainer_config_helpers/attrs.py b/python/paddle/trainer_config_helpers/attrs.py index 7ae9e5cb305..d1167a234ca 100644 --- a/python/paddle/trainer_config_helpers/attrs.py +++ b/python/paddle/trainer_config_helpers/attrs.py @@ -110,15 +110,16 @@ class ParameterAttribute(object): momentum=None, gradient_clipping_threshold=None, sparse_update=False): - # initialize strategy. + self.attr = {} + if is_static: - self.attr = {'is_static': True} - elif initial_std is None and initial_mean is None and initial_max \ + self.attr['is_static'] = True + + if initial_std is None and initial_mean is None and initial_max \ is None and initial_min is None: - self.attr = {'initial_smart': True} + self.attr['initial_smart'] = True elif is_compatible_with(initial_std, float) or \ is_compatible_with(initial_mean, float): - self.attr = dict() if initial_std is not None: self.attr['initial_std'] = initial_std if initial_mean is not None: @@ -131,7 +132,6 @@ class ParameterAttribute(object): assert initial_min < initial_max initial_mean = (initial_max + initial_min) / 2 initial_std = initial_mean - initial_min - self.attr = dict() self.attr['initial_mean'] = initial_mean self.attr['initial_std'] = initial_std self.attr['initial_strategy'] = 1 # Uniform Random -- GitLab From bf358be479523f0f7848ece557054835d21d30ed Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Thu, 25 May 2017 12:06:48 -0700 Subject: [PATCH 0455/3256] update design doc: Client Library of Parameter Server --- doc/design/cluster_train/pserver_client.md | 12 +++++------- .../cluster_train/src/pserver_init.graffle | Bin 2622 -> 2436 bytes doc/design/cluster_train/src/pserver_init.png | Bin 28853 -> 28616 bytes 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/doc/design/cluster_train/pserver_client.md b/doc/design/cluster_train/pserver_client.md index 392bab25e9d..007285640e9 100644 --- a/doc/design/cluster_train/pserver_client.md +++ b/doc/design/cluster_train/pserver_client.md @@ -55,7 +55,7 @@ The trainer select process is encapsulated in the C API function: ```c int paddle_begin_init_params(paddle_pserver_client* client, const char* config_proto); ``` -The selected trainer's call to `paddle_begin_init_params` will return with 1, and the other trainers' call to `paddle_begin_init_params` will block until initialization is done, and return 0. As illustrated below: +The selected trainer's call to `paddle_begin_init_params` will return with 1, and the other trainers' call to `paddle_begin_init_params` will return 0. `paddle_get_params` will be blocked until initialization is completed. As illustrated below: @@ -89,16 +89,13 @@ void paddle_pserver_client_release(paddle_pserver_client* client); * * paddle_begin_init_params will be called from multiple trainers, * only one trainer will be selected to initialize the parameters on - * parameter servers. Other trainers will be blocked until the - * initialization is done, and they need to get the initialized + * parameter servers. Other trainers need to get the initialized * parameters from parameter servers using @paddle_get_params. * - * @param pserver_config_proto serialized parameter server configuration in - * Protocol Buffers format. * @return 1 if the trainer is selected to initialize parameter * servers, otherwise 0. */ -int paddle_begin_init_params(paddle_pserver_client* client, const char* pserver_config_proto); +int paddle_begin_init_params(paddle_pserver_client* client); /** * @brief paddle_init_param initializes the parameter on parameter @@ -106,12 +103,13 @@ int paddle_begin_init_params(paddle_pserver_client* client, const char* pserver_ * * @param param the parameter to initialize. * @param param_config_proto the configuration for the parameter. + * @param config_len the length of param_config_proto * @return 0 if successful, otherwise -1. On failure, the trainer * needs to restart the entire initialization process (starting from * @paddle_begin_init_param). Or simply exit the program and wait for * the cluster management system to restart the trainer. */ -int paddle_init_param(paddle_pserver_client* client, paddle_parameter params, const char* param_config_proto); +int paddle_init_param(paddle_pserver_client* client, paddle_parameter param, const unsigned char* param_config_proto, int config_len); /** * @brief paddle_finish_init_params tells parameter servers client has diff --git a/doc/design/cluster_train/src/pserver_init.graffle b/doc/design/cluster_train/src/pserver_init.graffle index 730d3a561ffdc19e723b3cf6612471440951826a..5f3f1f52be8aa7f9049a8fcd6b7c93c8560c1676 100644 GIT binary patch literal 2436 zcmV-~348V*iwFP!000030PUPvQ{%W6fS=)4`0_G6bwlDMjuVDvDhV{Co3JIoz)e+` zVk?OT$4bjGVJZImNY3I#b_hKX=(^?s+tJa{k$gUFw$JvTcSBpcg~UbJ`MjazrfidC$XnFfV8u+dN6cZr zTF9h3>ucLL@P4OiQgjOs!964m$AWj;1^z2bL=J_0NOt7y!t8XGg_Vs=K$*&miRO7G zKnUO`Yuhd*tjasg!aN>2s86sr${(|{eFAzt8yee%sYpm#F-m!rS+5#erBVz@Y!_yl zCl+`VSFOK2mkQ=8x?pb&m^Zw9$6{rV2FRRenl{8#j7zw)w7E@>kDH2IQgYH}siHpR zq(`-^897OnjndOoq!K)1-3G3xqcs2{7?d8%)7T9%%uH9qwU~&t=)DaC$(ikwM5Yqo z1gW9qN%}4UxFW(&;u0Y%jI%T;x|CZgKcl;mGqm{M`{>e;~U2qrQ z1ynFcl@()C);ASJl9eq*-_k15e`WS3SjUxM63>4CZRALofZGtwu#4Q04enbeu!T|? zz0!zEpC$j~E}r!ZvSIsvBxC~&mbk3%io4kKhR~sLi093x3GFv^VVCza3D8GP0WU|uG3b7SA zF)s;+iSY+}HZigIys;d)(mT{PtN z$4(DBly>b-Pww;_#E#|w5EoKeYC^}xn`f}^*?|1<#AHouW5R_0=n!bBy58v%2pxXX zwLQp=eu7b){4URbeOm8~0I@psPA|#qzoMxAp;YDvRW165vdj;f=8I?*U!+*{MReI0 zVY&ICr1&BwgNbmxh&X_H1latFqW*wFrw2?%rM$lGSIkvasbluozdEn~A?ouCkTC`< z%ZA_kkbY-*!O)cmWGRmO^jxR8JPMmiNq-L(N*N0o3m=n(=`?F~EGQWZ84E99p-_;J zgD3(vdhq9n5?ki5^%#ZuSEoUhS3NSQ84Vc?|7K+P^uNw;6%DX|#hk=XyR*}>{=4+* zgs_^dm=G-^BqQYIgmhsaIp6unF$E#IIOr;ap(MXO4f?N1!;1X-0NS^ZA`=`z4=(B7EA!+ZX1X(LzB9{Gn*iiMB7rn- z4Vv@B1}5a&tO;%s?PGqz!zUZ5t6nfGm8`GN`uY#IVD@29Wj$XB{~4=gLzOc&GB$pR z<+INVt4LlB|E+ktNzFE2GhROIVtRszqP_2slI_T5hMwSZ_5(Lz!njkwoirQ*L`GNm+i%t|+U z8C=(rkd1WtS`6E@6m-QprXZXkn1b>moMT8QT_(VrVq<)~TR7$|(4CEatJrD~?{)0z zr%03Kcjl~^ah7qmx;bW#!R8Lalgv;4KE(=HQ+4g#M>Rd8Eu(F9v;~-bif;_A0yx=t zXWU8|ZW(Uxh8tKdZi>H~^1i?s8E_eJA7N8pSp{&9GThz~x1U(M1z%>T z_;=vlYW%TJNRP02D&iV~RY`dEpcE5$UAzywh}DHPsl@#>nbL^=rxBXaRVC1w&Svax z#M?1Wj%bs>R`zV-nP(GS!OaqzJ>mJpqKBa2CrbfS=}*EmCHWagELOrkK~}@*;pCtP zJT>+?{PcTxW&Sz*M6wC~#)OO4`AtiULjc5wDRN#BAEk5?kK`l4681f0LH7`wH_(cIl^+2)Bz=Kg)D^$|zuIfXOEKeq+c4>#RSekSq}+;TwdbHjYLh=mFb>g_(pOEyR8H`~NCKn{^bC2gq|p zQyJbk3zCn(FyWs0Y!mLIz&n*?Tea- zn7E0Y-B93!8EEAgX})X=@*$aq;xbId<-8n|w|z7B95$GXwpf@7#yC7E#UYbEgifCh zgaXv*t6*xSEUOypWMc*T`Cnil9pe#NNgX2lB#WK##T{Ijsr4L1YZycq=|3+vEu+ZY^t_&rH(L#%QwL>L9B?# zOKF7Vp#(3aVA&qj#+PK`Swtg^~FL(C1zQ5S~ zL)fGLrg?ZTv`GI6n<|rVXr3hXuAiQu{jsF5Il2*z-;-1d_k%OY16IKV=lm6JgkJ?n z-oj*?nOM6Am%4oYSED*;oHp9_(aF_tWM65g)xq_*z3Fdlreo9{nl-y|TKhJ3?~WcC zUbB7UH6Ki`iNSJJhz!0-ciTTCdy}E&l}cVS^6KKE&iI7;E&aBQM{l7%I#BrycI-gj z<)zG-&LFUk9sBmknpdM3;(XyUXaDeZeK`*wD~>JUiSc#RS+L>VoHzFOe0VD+V9DB-Ch{<$D`+mui7&>z~1Xq_+=$$yw$R_;^#y zL&1qH5splkvW(>-7r^$Qo7f?Q-{(t5WGz8^(of;;Na#9(qaUSlXZ;@XsiwFP!000030PS3DbJ{u<{@nZuZ@%o^*#u-8^U|B-=mYI%h z3#`W2t}Q}nr~iE=+klM?C3l;UcAc4E*?M$zB%S9-Ncf!m^)xZKN94HJGCv&h8F0uU zQ?)c~jz1jUcg~NChkuEwZ1${k#@$`j z+Z~R|$Y!fIhuon~i2XjBoy}$$$WR&8nlPZ7y>l!ZIb?oCg&orZ8I5R%v>IP}L>>B6 z!zwvFcz5!E=BKhs@FTi{bL7-b4LzM?*;AT{O@hYAIRz)#MRHMu){Rw2sK~A_d07ZL z4rGaUCoXYllTWFJ8EazVv13i`%q>mT95@;osBn@EAR#ng$Y%tqy-*NKrCbPcl3hsl zHJB1h);>>N5>uC#V)~Y$`oQWpHCnf=V|7_5=!gKu5bh#xsnfS_$2`dMN8E9~Bz!;O zz6nLKaKs6qkpKQY(g;?tVn8=8qpd?5t!iH@Q$Jf3Sg0<9Tj9gnWNx4pa-qAQNkv)@ ztK3kwpMU1S*-EdKW;eHCb!a~?yJV4CCH%g0(ZI4MJD5oYnvc-Eb?_Lk3}n%%;D`g^ z-V2pzm?RYdc#co z43d~S0^keBKsx4m4)E`J>AhIu{tf6~OgkpTa@NaVAOo9R4>BF11(Z%oy5!T6Jz}sn6S(IOakenHW}r@ zQlA=g1yQRDiw+f73Xa%wB3s`@37eOWxSUwZNFHIuLWbUp0=rk3VFEhniEP&?Lat~-!FoHBWv<3%=#UXV zgr8Jk|*^ zM^L4{0UR874RZy78>%DvQDN=B!edqd0WO>6u!#wV2L9@KB#ut1boM7G6SL;f=tk1d zXOCDC_rx6`I&R2Ipi^e^ru`IO7= zZ8j29Mx~7Uxr|Z}UDbbL^HM;pBhEn-VMdqrKbb^4pW73Kaw&yU3cWCeUWhmaAtO+> z6r^DOkp%kP2ozqAJJRpOomXSR1u`BH!798#Q?w%!Ud`sesGH4R^voV`9S33)IUL+( z*Q-vwGoXstc0{5Pt^1{rjMd&?f*r1`Fe)Lm(e*|)EyTRI*F6iqlumK!6!(knS$O2x z+m?j@QtG7C`Hi+Ld^+uK$3jT=&r?GE;yV@|hxWE%A*8$LDT98=4GU3{GKGEaR|sOd zU-AFEU!lH<{R+PL2KFm9aw{%=V+GMF5*ZP92VsX$D($!y-K6wxE+Nq=ga$;6xrI%R zVZDtTokI+`(Lro;5o-n-zfE)li8{vT*{w6}M%n3X`#O6k>tnrk_qUh^z@B-QOL>;^ z?DbZ)BT6<`4=-!~@OD;Sfop;!?tNC1QnsaRdp)+TX!ghYR_1FE4ixrGw|q*slx};Y z8+={5(dO)Zek-JeO9}TDP8s;uAl#3S8(x@h#guL--S$YgzwzBIydS&$WIz-)@9IA9 zkfX%M&p1Rr_iASx;&1h1uU6;$Y)vCb;Nqq%ldsxE!|3;TfIgOuLH)v#bIo$I-mV4^ zz1+CH2j_so?7rCrgI=q{(#k}Y7m#|h-Z5_O`V-sei%nS{Tvmf;od6P)C>!-=`Eq95 ztCMqj-<{WMm$iiyxnZ}P)y9L>uIlm=t+ZwL#g?rIk{&=*^Fd}3VDq-EH(;*}q)t;P zxr(@y;+NKF>6=HTR+`I=c2(|OpHbwA6w0Zwy*}4~7 zl2W^8s#lxfM!uibmtEJa)TqimV?3ZL4;~e>UGtkTxD6ow<0dj14-dNBRj$7dK7VZX zW%{QE5SGqnrMgTjQRK3~u9{r7<=XY4qJFb{>pgCksjAKLK(8or!!1u{6=fl%4r}`O z!-tjWg)HUmADFQW5cbmGEuEnW%QH0PO`M^LeDWFES~P*0gSEOjvXa9Bu-vs5#)CJ; z`Q;1aKJprVwj2hpuyNePA@|BsACR{G-g;!B2%)>!#Y+6`_J67}^{Sh==lwBaacpB~x-;kFn+o|seuM$>*0u1Y%E!j74 zk}zgo{ak`o-O<|++fIlx4NL1{G)v0eL>*d>H6@$LBb#^$Mkx=A<01h%>4R3gAR!zECjO(IVB&LkX!Zut{^c6D4M2}S<0RK-Y z6@eg9C%aamSN<6al9^@Wo$yiqP!%6G5hBi?YdAI$c@EyCglOxC7K+;P($KFh4Ud9g zFh7ozA&BpVd~&1=Ul4jmsEljt(VIIiph>?9?A)jui=f@R;MT#kipZdB@gsh)lbwY# z_f)3i`rz(>WyIY~w3~L>BE*_pLnjn&CH&Kih3JuCK@xQgK{^MbZhqyp{2DGS2YaOO8axvR^`c)b)A;acd$*;ti{S7J}NXY0Q7j?v^#%HG~m` z?i9bsbK4zE;*Q7??adxGHEXun|5m1sLp#mu^!N%G#G`qg zJK=@fYG+8|b4MJXVy|-i~QEqK+Z-09U=w(D7%NX@bYK% z##2I*f+C+fP}!*qg6?mjam0azUtKP;Fk!SOmf^qR9TAR3w&9BzgLEPNw(KT4gzzsQ gH&f(Yg7lqi++E-o7&~zdM-ULWw=e%eL6TE3fdW$&%IZ$)GSXZ|wjb#XjBO1~=-fWq z0i}U9+_-=rKbklh5W0P|wsGWg<0bx6f(!WhtIID$jQk`&%i{_#6$~} zpmlV&aWZhDwQ(f*r;)$yd@yk|a zIQ}~=V1o27pU^YXG0?xZ4OHcMxymJM`_az9#L*FGpO2O2Ps#sw?LYneQ(oTO*2xyI zf`f&TgpHGl15nw?;AM1tO#jyYUzhlw+mdszFac)$r#9oiwg2n2f6MdGzfAnU7~&r* z|G5g7nGcqS{`Hpe!B(y_{RROM0Fn40sO$!MkOq~A)-%z&j~IpfPEa1S2bzisn)qoL zlrc1kCVQ>GFH;;D-Y+`68A`~^&u=uA7YjswNywWwGf%%Uc^%Q%#fT^ z0PNqds3-_?Dl+4`YWde|V?+WqZ33 zEU0=Z{IyY6+@huY+GKP`8j}j`qF`E&@_UAvEKxfSCh%dn#AUOdrO5CCTQ8E&5`v~w$o`OcH&d)qVz=65 zAZp5j8UhD~OTU(#?2t(+lV_c&<2cM}8ul3uV{K*8aisOAdB0lX+vORSSt_IDWMQcR z4~ahqxqO?~tv>zD-QV1p;?d9gEA zV3^mbDJM+l1Z{xq#pK74Q1ar^TUe8cj4%w?WLUeBw`89lvn zZO$7X`y7dHZcUOm@s@{XN4IOZy_sw-9PmlNPR@3k(KDV0*Qgltm zhx2vf0j6H_76k~n91MjgoqUhuD)-?55<}n|f!rSdaBU*AzL zxo@ZSFQfVwkVe@;97LX~hhunNAAMY?r{zL>lPN{tda<41`->gg-kT&@lC!j}Osl~V z-tFPEZwv~X#oSL@v%%)^b~}UrVpO<07^!_V2&dWg*e03V^Xedp)e1fRCZxve_AHu2 zjPG(%NhXEuGi{wg_Zyxc!;o#R%ib9N>c4SV%#lqG8-BZ_%!M4X!%ktUF=j1S8Hr`^ zdA~uz4;v0!I!;(+ z`ECGW$N)_97hY^(aY6#6Zt*>4Cr`(F#awX|8pPy+eCImz zF=8o}^dMA_^HHU~b-OhVa_Rbx?Q~DX($S~kfNthbKA@4&?t5c=3w2hmtDiB5=V$bM z!*TB}_sz%!^#s1XERA6)4pAsZdf=uWHm*kmj=&Ve<0PR3_^zgL?dE=CuxA&3xh*1*ba# zA(P4`$-G}ddxX|{9Azcknu4Qi1j&D=+G>yOL2_=nv+Q}?%HG)bu<=bUC{wA&3*-q(089G&`H&kyIc_HL43 zw-SW4Wn0zW*?RihMVpZ0nRw_PB%YvEkW?(l1xu&GB*Z(~4EEW{+pvDG-2j^k{(VLl zWzTGtQQ#N@rv5HyD-C8BIBE!yKd=Se1gRgKcBJ#y9L(0+kBMNkUW`jIen6pb$S*=m zOJ*v7GwKbSYp^A_g8%J({d@MP)%|=6*nWZ?!M%DqfGbd;P)JS(a$R~}38%orm|CU> zd;Qt}^D#mnqN;UI$HG{nj@=wxsunXM%myAXv=P-Z%`7T)oDGFY^ILiNyb4*BJnL>M{l_Pjq_SFvI|xsTTKh zFFGHdr3g5?u_9Klx2X6#<{>xLAO})C2^0nrZXJH0H~64~=`aoVt>?eE-6(dpQfzx& zSv&E*(%<-;bVB_mhUz}0V)5bVS#0(H%-^C2MU6nOY}D*WhZ5Ec7zhS5@(L@0Z#Dtp z+Hx?Swu%)mQ@Pc0~YhM_P!F$qkY9PTm!j;v5Gk$($0a>*%JC{kju z(UxB)n8}YfokYSx9aXV2n=*b6&ddn)?vnvwJ35JQdEDtc`puo)GsiDLSTP(n|cUTLePJ|?8pR^2Hqjizsi*;5E-)i zzn=od{{LD)K#(1xsHiB&pi#$!1Lg;$e{5`Qj##9qg349s`z~YED&h_w6za20~WPP>sQDsdAseCV!9@bMh4JI}4Nc?Ew;EkFYW7`e#L05vo zVzTK}60Ta$f~GRRvL_dGHby&XhIe3 zL`=i3zjj+K0m^=@M9*!;bWO+k5uve#Zf~Ffj?XA~fjI}+u-*t4L43 z>G_U)t^vx4i^q6WE;QjMp99km{dT}zF+W^4dqFz8cA46r)kgSZRHgx1%X~t0qq9HiT>f{b2t2iYGCWB%lLqZ#WIYJ_Cg z&M;=*QprNZpD~}pSj!5|o5m9oqq3)4A{))6R&-ooua(CC$f@y^M#f!leiJcV{V@lP zI`4_JpA%y+9nXWowt2EJ2OrO?IK7A{A^-t&U$y^ci}t~JiylfpUOF8EVzy1RPwlKw&B zq@+wev(8St8T*7kPP|WdPhF(hAxpIT!gbA#Qd67mCPUf20x?_?CekktSZjW?jCVcJ zESHQaK8w~1LRnog=(kc-bEY`$D6{kj&Iy>0UApC;kWOm}t@G%W#RE@1a5U;6<$KY( zUlFNH8WN~LqSaxg9?V+qV-cUx8ET$^1)bfGQz#lAG>w_W<39Svbg=Qa) z2B8u7{mIOxzge!~JFG>ih@e8(wW1^}kTaaLTz4}ah*Un^t>^NDAw}>2i4O5)N@pAI z-Me%;=(gV~Gf+4|RgF>ZIQX0>mC9K6kc4ES?H!^4F(%G+^QqrBRD4V^m-LHq$H|!K zN%v9iKeukZ&C#13j5{HUZUw!Ty943z%}-U58=y%iIXTjY?&HJch$o!-rI_$0mFwh` z=0;eUurjA=35go^yYb37^gHXcPEi7-N_!-HLhDlhZtx72k%S&;CH+gq%bi$G**)Ey zpB*)yKb;)mN@gsisp+NZGU#nR=Hqx9(rwDc((YH!bb(BG%bz1s{jb=VKA9>4mCm~`rf#s(@CSEct!MwaBRMNhdR>9&ow zp2$#)5MP62DW++{csyb6h}C3){L*n<;{yz;emit_f2aS*y$GTt+KdG8Io0FoH)&iV z?gsH~f6cv;qe<2Zm~P0>59-|k9qRrGVEFmto4MAI2>u;!y52(gpMa=HN16t$(_cw- znJ^jp)w-)<1RhyXcrRGu9vt*)st=ZThD?K(Ry((f9Tua@?UQZz?#hg}MJvlROkuNr zd;VQ&WhZ|#u#kJ##Kp?u8fz5D$rjnIa&E6zM^!n&=>@}lXuCYPLGR3n$Z|5h!0Ni6 zZUxPjV#HsU+wYCHQG2d!Yvr$fZFzgCwL7Kh8n#w+NFG-_Fi;>dVp{E6q0BFKb?6fM zem&@CYLViddiX?XjTMV7bjF_rk2}_5(QZB~eey_%p<8aky`#4t06t0N{iZ9 zUSV09Kpr{03q=+SaxcPRB$Ij-I{H;M-l*@R>iZTl+=Yt>+-bbU8yqxigkGPIp$7|x zmA+WqAxH%p#9XO{W-RsM#|!Ir1E`cNP!RM;V^+xrKt7 zXF2hk%@)f*W|ty&9cA%sCDBs4+cCG4d96cH#)?!@hrh?*ixt7jjPMQ?vGd zLBTb430P(JLvkrBMMXuL)!Ea)GKQu59NMHGS7`TnE6 z_IT7j0V!1*`-4?`HIoC3_-5HIB~ND4b^v5{Md(vji?xSzC|4W*R02RCXk`9zS&NYn zsXg5FuW&Vq4mAkelq#c4k&g`=HmmU)JY=B!Z|$smP4$g!J8qkzCd2=A1he^;*jwy78$Jrp*N=h6B<>A|^&)a>34{0hr{^ z-dKUprtRXHKaCZ#MLTlz8j~f(PQT5ep%VI+0(!3cYy}JEJs_m(1AmZrKsNp;*NcyM zZB`8!%qA?s+i!nCEGZi>g)choGwvXU0HRCn zB`y6|okz{SaA23mYNuDC+e8f5OYJ5x@wN9Vm{DcPgFBPme+9fBgo6lRo3v(M?FHWwp+ z&H4DP@$?3bWb1DcM7$x?m<|eGnP|P<4TD>KqSohBr&cZeR!!6Am|*s=9@Ds}9Hbvw z703jFaOpX8H*}bPJ-S5)=f}8K$?@rwx9EIDJCBG{Q~TaRJvB0Hu=U5)Sezvuq%ml}Ni)#jhp8$>>Dxp2skhsQ^-5ANd$ z2d+OQu8SLrM`csD)^MOjM z^B|pKPWco3L?#dy=!cu4sbo(4%H(D>`!d5(}$re)4Hk${H zutehr=8ZBknH4{9OC5cz@Gq=3f*o93Hva8UpB%b&Eg{RNT@wxi^CXpaeq+~h6($>6TvzNi)P1V41yB-IsLhUr6J?4e z{;}_AyO*OPY>QPc9JGpD;!Uf3@`lpLM|_Ldy{|pNr-i3mz|AVZ9rM^KT?!`jJnj&dCrLbv|<})o`6>tZ`cyIh34>-Vp|)Slih0kQb2;{(W~8(VrP+ zHXgxJuRDb{?Q1B>fJ^sqTz{+QapWb`1Eq0%mq3-lbQ zfg?PB9(|$J^OMc-({O8C3zvRKScw#~JTH?vI6d<`(vjy3)9=nXfOCfLs}aFVROz^X zcV7z~W$_gZuhGa^m z!d4Sl&&&wR z$NVZ7cwfI%_ell_gGsYX6A^AjjhZz&d8;kt-hzQzo_v{^N=7=G)W~4`>=*d9g4->G z^RwR+oD_xRmLj0LmZ2BLntvPO$cuI+k|ALzF)pGsG)K zCJnHnKgkk#c$f43lEufvGHJP!bp~1QaH?CQvr&TP2s?IDU%JsPZ`X6~L<5s!lP1Du z8>9S`vSPnYP{jj0rN1HU8(^>yN{Lf2Aqw06*9M)1g8!4@l2a} z=XhM&o#+07zhYNF(G8=1#0Frg4J%DB8{m`3g)JiS5`=v5wShBeG&T)^uJk`C6`NK^0=;jpRx1X< zN5p%mEaUJIntHwznC3$mV@eEjm)u+4xg}-T3?}Ejf%xVZZ6l{&y7#Zw_*>uYOq3K8 z#Q+wfC)(@8atG9jL1uYH-&z-e_A{0tF{>3DOYrFWc;7&wYi*qX0gYfhAKQhY4*^s# z#4O86y`i`t<5v+;c-t*BFhpiYSkPhp<&^4nl5$L1AZ#U5|b0);eEj?WDAPY-qA?gdWe z2Kd5Vg-+bFta-L{>i8zO^zzKO^amt$MuC?LZYW7=15V__8I;?T6XTtp{$k8Qb{yY^ z$ljGUuDXAsV8X|RdxAz5$mo;ADt+(9O5+1I6jL?Uhh{z2v5tddbyC{}C8ddPm<(2u zP%H)|#W1@CX2L*2M1()QrSGNdjaPN@KJ_}~DCJ92EWbj1>7Xtgobk}T+>4(Z=JJo9 zTZ>aq8DxOm;gKDz4~Jo$hLb!{hP0=foyjw!t?SL>hJNHv;!Ya$eQ?bvuma`2na~up zyVYN+#gTrxi8~p+Ey{30lau$i?+PmWNZ_F`SSs!;15^X7CP_ZB`Ub);+yJO(St5N5 z;_jLD^XhxRg5ozPaA%VX&7&|9xs(ve%OtInG3V(*D@~sUL<3X6zF|dDLTKwH3Uz2< z6=g2Z6(FOZMYxN)irck_l!DQ|?0+aK5PfiZ;TX~GaZdOB&~r^9Tm^Dg<;_Yn807FX zX-)s#f$sHywI)<&hW?ZRVr7@*;QiJ)DGZbFWBOFmBJW|YtyXwooiERmX7jO17h2Ok zucca@o94OjQ^FGIA&$h9lUA7mvrc6n1U4+cd&-eq7e2Y87$;kz(K&}UE5Wr&2^e=; zcLMMVHQU^vpOObbu-0hZvT<$_W^gggQ&e=qh;Cw8akGNkXja=F7pR!t8?QL3#=^Z- zE+(hQ$iL64d<(s=p$=;Q_MpGF*Rs(7xj!jpC#VB_6TYdev&N16vAy&L|}Wy45IR zCsaOLJeUD&q7rmz+pQ&RblKofFiwuoe!G#BpPGWK`cPAa^nZS${4gq8*9R|4v_jI? z7LchAtI$&G3lDAT*;@5Bdt4ka-zL=eLxOsO%5RLrD<#TeXT z6($vfTxI1|=M?M=0ZXZ#@bhbDz{KAImbRxR;6QO^)fq*;Re)C2mDi1gKODxq<$j}i zw2$70A3@a}3dvpnCLcLYPJw_bAKCm6SehW-wUI}R*waLb18Wpe1 zLn`NxCem+cWf3}@Sz!U8HUW09{O-|&T(i3^{6r_aA#EQm0p3A=Pff29bS^yIF+U#X zV4l-tDgrK2eLYP(SpUm4m<$u<&y~WWQ|@&r0^VnC&aM~SSV|#i&M|^~`xSgtH6Kj#-4M`i)6-qQ@U1a``#(hl zu$@mH-aUS7M5(7(i&Mn>=u*XQ^ur#$HrE*dzph@f0QdcumkgYCp;+7WhK9)GWkzol zN;-ivHm?{(x8*n?!-}z$j?auTVDu^D;NYEP!YDEL(IzF3(Rs1WX5)z1Q_maC6!Ru$ zL=6iWbnZ0RpNSv8M<2t`n9Ep^A7mWOF4w~KY+RrCDje`H$&o{Z;RLs`?d|Vl9F1vz z`BcN?_V&G_XQ4w&rLy6X+&4hyb|?pY$Ja8bcDCMV!_JNaH=R|tT6ke0Wy|Mi7^tpM z-m+6q5W!slDhg->P?4i}W#-^;(Sms&`3Zn!uK|dA`~@TR{muzn|D0F;?PPK( zEbx&SOoC}y^slho~7*w3lPK-@URZ5{F}(=boYH=0g%q3h^2 z62AbH!VKDHYW-$h`OL9Lp5NDw*$HM9u9@a&F#H!@@)f zsDk050u)6<8}$Se)j<_M0)unCSNT+^V@mmJ0+_rUYQG#%;_MsS@t;&RAHt4SNuNT< zz1N($6(wM>UYd-`p?}Fo2NE#r+br}#@K>B5_kt5FdgN*U+5wawIkRXZSThsx)ed|w z5#@q+!ga5_qnKM-3r-=XJniFsU>HI$#P~+1ZZA;5y15CcD zWXos)H;9v(%!jG^6M05-n2l7BzR=VfBgy4|tVK2}0nAY;P?r-RFrbJqd_~a3(Ks)KY^qH(QN@+P7E59a zSqB|nY0n3LfcCr>Fh+m1!WT(kHDpiI4E`rG5E3wOHb}I_s})G8UNDws4GqR$0}G%5 z80#z82UEenD<-P&1yNZJS8KmEtw0DYJy_%<$k$rgsIQzQZlT)Oc>fK8c8zp?#MDS9 z_%w%vv(HXu%bBmkg?YRX@FM0}mP8*xfWTj*hkKq}Cv!A8E~jA>mIJxEh6&KiNjfhn z)k5(gyEnS0oi2-=k}897bcD0g-QDK*E&J#v1V$RQ0l}gBX##{p_lM)Q8$ZK$yD)U0mE^p$vCvg9v!T?t zD{d%=+z}X6*!0ziW2=)Yv1_Sb&D!qY(J0aL4z#Q1+SSlUT$;8bR|xpxsBNwlA&q() zO=RWtw6VjAxMh%HNu|rkAR`T;h$jNgAL(yUP9thHnb1^gU2*KOHaLX6wfGeF zILoSegCSc=M~&WZdd(e_@y=)~2&5108-8CXkSv4p@MiH(l-j>0>)>>}z9F`RQ0h58 z%X{<0fYU+U#*z{N+u~!_yVL)3IP~M2$Ic+vY=(}QjWy9`gzJ&E+fSrDI?NR-o#MAU z5w_+mJX-DlEllk-DW*yji%_!B6U-vi$G(}^<)I%P2WN7@y*JyDJ*82Xr{b#OFrIL> zjGk8b@v=>kvdWcy)lihn<7Auv z!pU~UMu$%w;&)$Ud4qdXe@0hpkx)?$+3SO-?%-T$_uFk(N=+ zP8+MYs`E8gaC}){DVaZavp}nOdZR|&_4C1&5#uK;cfrk8^~R5;hU^AWG_B{(6d!T1 zo|?6t3pNcA<>LYWH);vH+UE0EQ&pAK_o^rUb^p8G z3j&tWSVVSgEq3EnOwke3-T)1cyYm_U+N2qwh@~S-X-pejPHV4=nA1r-4}%feoORk_ zInCtLC)^(OhLllUITFw?qlk(%&F&v3qvVZNs3PK6#EGDxN}Z0SvUNQCz;IJA4% z-FFs6*N0>6E?`#iSUSh5zh6Sxjq{inVWuIa_^qOGpRAWlePwPvgHF4^u_%b1wWcR< zd44GHM&58XvRoOJNut>edaPIN*B-c&`pPisTBeA(;{dN+Zs4`cVh8AxW3S-T0cs#{ zZlRJK?moctky+JZ0brNbua<9*Ex%gL?k{0YxpPqlBYRpwjo#hKaW zv22U2UH`{qxybz9+%f--DXFk0yf8uDFxiG3nMizuPe?~)?2jj(J1EgayXw^}CVD#M zum+fGesOp-6zusCyEaeVu(y`1{S0mmD>!CJZ~5(Z2xGK8EuhRp-S(_(b#=O5Hz*MG zG2uH4ISW8Yg@f9xFRThsf`~_Ydu#2np#@9s+pUJXJC<#(v6p>}rSVE+#?V;$b<0@0 zDQua8E|D%0Y=bfRBNz}5n45pU-D%fw7#JJ)>Wi?*+xcTDOhEt$@pS$M!uXs3^s8h^ zFH8mU08gCGGH=b|nrHUPlm89PQ|d}SrP4hAiow%j@#-_3t5^7gn>JEIA&+4DTXq$5 zB4TNBGK$SCLvlI@z}tVlgyu64*jU+*i76?k8)r8lo+WE2oM#YOG){s=J5=#M`98$l z`By3#%(xV}rm<%AaXuIq%#81E>k#`s)3NRqA4ADzia0t^PsLw&@0dC)ZZ_ZNs(%qM zJ<-vC&drq^s4m7?iRDw3jE!&oo+lt!-8&R$g(E!eVxwy7X zgInU0m?JD(M0Eb#Pu+(Eo!fIh?Q_7}#k5APw+}m(J2p?hLhj`e#fHkU!S$P}4%?g= z^C{?#byeNFsZ5z$s;FF#a@P0!p`cl%{k~OqzjDXMm7mT`=e<&IKRbbVXnFD?-RDs8 z1Xq&_!cIO?rSWm6$mbdYsS=yO2 zCf%_;o-!Ht-SUf*o6OQN=%;m}U`Y?HNrZ2e%{q8&N=U<=d79_S< z!H+}pQsj%Ug3!j(-LqJ{nWEUngOfGrND;?FRQV#( z!GjtmtnL$Gb=n=Z@}{zDz<<+d_PZO&6ieJ9aqwG2M}m^^?db$p&z+Jt>Xu@Cee>+o z$NH#v300zDrIynw0AK`1SunF`MVl4aM{)c>xK1M1ftRhpO7LiYuNl zk#5}{rT`J2F25bh-_6i47_{{LjoJwhLL8OZPQ#nT09tG}s?|+;7J9FfMkh3$N=t3h z8uFs>cZ-PH*Vrm=(rHP5yuFnVN&quOxB}PE?P)Lz4bMJ70HWL0=pT1^(pI_(nC{g) zFNTo0{fDfG#NKmnT_;xX&nitZQ_M9+w2GZ%9DaGg>tLAbrnReZLD<~C7OsW4w;Qu2 z`(o?Zfsf)g&$H3Ab+)&TKjRFw&>#$9x$aFo35{zl2yMC?_Jx> z_%r(|kU4loF!u$lMXl~HH^~y=VxfxQyT%|f$v8jpDHU>;w| zr)l6|EP;f-$G?YTeDn)GFb^W{#DdJ{?exL^g$5S!-dbF!&o?*0gEtUstIaPScjb_; z+`;rxqx@_#5YP^AxDgoSQEs^O|0Q(-!bRI~opibd;68`ri7i4W7uK^Mc%>7`<7F}= ziILgllh#?}XR@ko*xTB^@Ewc7%jF>j{30J$%{ok1KtPnFUu@7e338q%i|hE|)| zufLG^x}7bs#}x0@Yv!1qwWqLBqVr)dz8_&zB7wIej#y|$LEyWZ;ohAvT_P#pZJF^4X) z*|*1!_go*k(4}2&f31t!>2y+8&nI!j-)yDR{JIPC$3vP9C)CU&p} zn)vE^yn4Ch!Y6UM-K)=95Pq?bKtDD^$PFV@M{d`=bze|Y=?K@9!! zJ14ppf#5F)x^(`9lzJ2C0Yr0vL^Uw@aoPoe8oBE&;7j5JK@zz9EfEZ8xafjv^dZWvV?R!_s+PxjdMN!W(sn&dclKHML#|FNrF{ls*jH+T`fv4XP*Ou? zk(`EguniLu2vjoAJ+OXGRZ9u1x;764P2JH^9K17LXam?{X*->Y5jiqii&&6`Kj&j2 zqu`sz(zoeW$8p0}%6TbT12wd{$AtnH$3d{ubG*P_=j2(Jc16zPtFPHPf$LGRA~PJ= z@Vyx8`WLCyF`Q`k3^lx)q%JO{&e0@>a@EKE`02Yfid;Dyi?iHRFHHs;Vq6Y3TR=cq z{39ULN0SPGNR?Ad$gDCfwnW4sVY5G`&Pf$Ce!j1qn%alcn6qnmi)qe)&6MZw(=kcf zE}=)wpNr7E{FkvwevMRJ1bHi92A#`R&R#5VxaYbq{U+MxT*z4I2k4Fi(o5z|I|596 zz=vzJeAHArH%@SLBo`V;$ScWJ!#S^a(8R$O%Ov^hXvA*cS3sxl808!nCNaaNS4DN? z+Rw-I1sb7d^%23ZS24Q63FuFO)Wl=}qeyN5$j{~qE&>DHkB@)T&EOOn@?HcYAJv7d zdpP}2iIN~0prq=fow>Njlg9h`KUE11GBw+oaG-qZlNbW{VP-Q{MkfIJ^oK9`20`3Z zWs}*}wMC!Cd|()v<~8oxwxsI({8lXB`e22utD(Z3X1hnhK0CX(6^lfxPhjxyC5Mtz zSAFz!@)-gc6jzErN}}WwJq0T%C>)SgWBbK|(^)zMnEW$5Og!;M4`mxEx>j-vhVl7Y z+Yoc}OP#;$l6EmBwSaWR3@l=J7CyIVWBY54O%vJ5*hOoNi*yL7&FWIqDHY7kS3e_W z7x>!3L-fA?Rs()c>R=mHp8XY*poTyKzJ68aFVC32Ni08LfGhTj3d-O90vAH`7t9pE zbu97M`$wjDxgsH=AoI5+e{}%3puz36P`rBosV`S3-iffjCiEBzc3e`-od5($z3_)8 zkcIf=CyKa~P8iv$RspiilB z!RsQw8W~kuknLFG*786mzX6ABfD#Y6!=5kL!;b*kuHyeSsPn&>GTk^fy`U&$b_hi! zC4}w}9Ot6VI*J-ackmQ#gbr* z6hIDBq8jMC*JBnZ^uW!2D#Nk-4^wmmaFC%cr1IY^Eb)%^7v9!jQ;79?Yli^d_SOBJ z{9j5&2>J}9_-rgUXPLg@AVvxR2W_bcK>mlfNddggcRwikl~4Uh1^Cqe)d3MUL0kVh zAb{@ve>fn(FfvmXi|}kh;ChXy!Nh)VUiH=fFI?Js+?JhK4=*u!l;oYQQQ8`r43wAP zCT*9C(pfN`^U}C6+lj-)uh5_(Xie;{l?E&k#qa{Rq*ba3PX+b=r1PjuebIw}PskD5 z7$v$rV4eFuflV>wI15`tZbsmGsfcxM5O2<*!^bZr4lkGQLoxL$ell$Pt#n4TZ4sBS z$6$V)N2Psu7=A8PR<&ya`~kU@TE83#kc5z>=HTXK=&VsCz*ex#7_7kn*jV7$Ks~ z6imec**IP88yDwH-Lzs>2~vN6L1(9knik#G$wjr6$-_3M^q?_tlfDRM>Kmd3<`Md< zqqsF!O_ovsP^-F6YGFySJRo96`2Oc=x5Gx|;-n%ri{g}NSiF@!6ht5p5unNfhPrwwSQi)Uj zovX}=@L*brtE9g5M7|#{_1kKrjRMHX1^A93rh_z!#c3^9IG?-7@y#-Ek@x4JXJH!~ zGQe+|KTI)s*5$0v#!sm>X;tP|V=EsY>KsJ_*`1M{;#>D}U%{eu3iI#-!A<9kYTpPF zsMUVr&I^*pP8;M&Oob39e1gG5Llq|n+o_&IpP`N5Z86a?8Vc@>Ch0w44~w?7>Rn*N z@^szrjGz_Wf75$}=doCxRb~05$}C|l=L=(QLr|%ffs>8(!MsiB^N3q!8{;Ds0!vI;||ZAAji@P%L?lrS?t{Glq?ox<@?IU@Jha_b#@>v>{foJzcF zTYKYeyqer%h}%?e+sW$Ljm&p2&Bu6BIpRry6P%6Y`t|N312oz4!1G;AB7d#hh7rh8oCQ$@sRptWj+g8gx8Mv%n@-km^`MfO-a9q`flPofQ*#qH(C7q4mBG!>sVg_##DFr|UPcR__yrf=msFO=& zZ0uv5ed0YGJ>PowXhoN8lJ1HBZbZGDN?w4rY!OZx3R+sg>!epBi^V6YgL9vhjDp-V>J7zjzK-wv+mlLU7$Yc+x>TysDxp(V z5I`cAhqlW{wdWrJ{45Y)1#oPX-CqOPA?cFjfdf7^os5t;tp;f;B@kcHXOMX4+)4Y? z;)nNZMi#t8p4&ewmS@k~-5i0dQ`yA{HgL9kkm+&BH4qytTw0Hq_h);N)$N@)gDh2> zr1`jlSW6Q2xG*ANP3z6G!edrPjLG93Gzyul(l3?Wm-?ErW}(WJ?Iwo!1Dn!-ke_az zagjwl^D*?pe=_MkzLgxsM(V}DbUW)YPq!1=b+A}xzwu1$Jmq)EoAnl|D>|+)cUB+KvgrLd?)T2QHE2nRN%*pnp^3 zn^ABEKlV?{V6!G9;46IJi@kspY%qroegaaRN}QJ}&hUJ*0fGHdM%`j5AJsb3+cUN% zr9<=+{mI?^<#8VS&szKDm=Ku@WcAu)A{rXvsNxh+F)=Y8 zQ(OUx`NzQ`zY!I)=7hB|>vW}9RDWPx^KS9K#g0xCXEP=#S-K-9U zy*MJ&(uc|{*oZ8ezCcshTY+xEWCG|J3d0ZzA*u2RDMUI9g5^aQ*o9u-?Hfjh85QIIzG*@8wZ~Iv|h`kOUmE2#QD*)C+4yc$J=p@ir z?X&js_ct1y2V-7U%trD{PBn?~n^*1g1yDMQYtyt+ud*u23fM**!lSVM6J?`&iLz-m z(ELYMl>@TsC##U@Yw$Cw5#pws{H2fvK61xQwBQ+`k*NgOt(f0Oj%W3ASr|c}A0cHD zxM)3yh_zrl%6~>M@WBtxZa{f~hWd5Xp8!Q80v$H?s>Nfz0E0Fz{um$kD)WVaAreFM z?z~O|XeVEhX^L%7@~=UViGV?)fEhWx>VD&wfOC>%iV4|YF&F|I)y;$UXXJYAoarUO zUAk_#`qjz!@Dh~%)Pb4y+IbQXVU*0DH`RC@Au%b?vXFk5?BC9#q#*zW{4y`Aa+uuw zeC(j;ed|O}m>)3$l9I5#H=ObBIz0PufDxXm8cnk2L+DeCDSx#0T z;@Du?ep1QBrh@dVZ7}OaBaMOs zOPTKf>RYy(@JOP?ZoZOi!sJ})gNR?*3v>RkqoBc}^>jlibijk({xed1nI{>U|4Fyc?@X9|HsBCGhHV)P!`+ho8YhsKv<7&gUU zzmmW4hpaIM&c0q+L@c$n(jn5n9^moY^ay>M+dN%z3@ z<`f4G-`eZ6;ZQx(o_$63`1nT$oBN$t>F}z5>%QX_yOP+MJkFh;i`l!q;i+maRVPfD zx%*&J3|KnK5$pk>@g)Y18p7rOO%CHF8fMT7bqyTB1v1GrVvIic`dY>#H+-WzZq^x) zxS6Yi%&_h$UwUe-c5|uUHN;vCmdiWuIC$51po;a}#IhUfE!_*;9w55{2-uh7?V&?K zq}$N2?hhR3<^C_@k(B>c*lN9Iq_Wb`K7sPoofTb8X<6RVq$J z3HjpOksgryCIAy%H8Y?i`Xasj#0xfX3ZQ|zrrbOD_ExT_(M@YMM*)}3&vQjQ1;bbSub zYF>g7loSeui6Sr8ZN>_o90RY_p}a70q;SjClUC5i2>0)rp|F+ssm~NR#k%>iO^BYn zMQ>S>-|jA1pIDk)afxRI&`t&qCff)(eWegR+vC-ll-3=p{O($S6_R7c(3fLH~=Um{!Hbo|L%s5#RvOym9w zMvZo%82_MR{McwFx&grC@TGCQM>b@9+t7(7%2g@V^#`(TAn1v}X93kaJedGIqt3qw zTJ00U1)&zu6TK)`NB&zo`r|%UG3LP72x4pt=&($pv26=0GAGj#`x*02SUe5LYm1LS zG*9f<_Gs6zxJjM`c3e5elpORmEv-e$K)WE&eod9Sd`a$!_OL_gt#~8b@wsE}avC9N zirnr05R(S+v}Dor*ef-a+SI_+2yF(-T}bp8ou5Q=R4Zn$N*E^)&G#MRxQbrTB;Eqg z_LVTdO=dC&4T3^BxN@q)kps`A3$FDPPtP*<2v6gC7z|%Jr70*!F4tJI1JksO84eG& zMgoNzO0;z{(=XXT$q>dXZm7%3`7gTp+Zv(+!AHO!7G8BpW zRdlXoKB3dr=vc-Go}`T=*E}VaorMD04ZGPM{u=o>Ra5w)8y_KO~A?@%5iyOn>bAd1+&V#@#l}YCNE9FNZ2b?f&yIRi?k&L|F^G7oD2x+lgzA zviK%MZ#;9U@@T7`HAn{DJm1en@d zM{YIu{u1Eq)3Z|)H^ruDH_^e4z;!$Z26CPPJ))uG{{}<~f9H<#msC-}`}l0AP7&cr z=m~iaz2?opt)6xBrde%oYNQUllheL?s1Ew18mdpwOIxU7d)1lwW8?@h0sp_-F&Sz4 zkqT(PR%bJtRwm^UY133`?Di%thrce~!eZE`B$JGF`hTGcQT1C0kh z*G=mK-ygQD+#%Vzl7ArJs9FHAtg{+yJfJYFX<$p2%c-)*1M+DCtaXfb7o2VnTk7s$ zb4gKT|3fx%jTQJ__83U0f7sH=1K#!VWcq7_m_s;pF^#g=?7xzxG(ags-(vOcI#Y{z8KQGXy}v zf*Ng&hto&CJ8&$btcP=@#Rbm(zeVJJJ0t74_qbO6sn!$%t)GEXk#GK7Cd+dvQ7sT}ce=~?_e_gZxow?dFe0PEM+i-vMM1^nmpo620 zgm$I8#!XSewk~T>sA)(C?)|j|XH&NxQ5O^n?xRB9>*w4fRt?I3OF2QVq3&caPMHP|VoBKrjwny3*mp+4~u^b+ed_CsdfG zoffVWObzUBn!JczKmL-S$&Md*sU(CvQ|;XtO7`3W(_FgczFzS=8M`COGk?UoJW{zK zPtHq1Mz5x-J=S%m-tn(y76t9<*A6FOPs0gFFOcqk;nH&14wv z$gSSukmKxI5_=GYWo(xrr+=HptYAn%On!iM*K$EjgC&Y755%6P2Rb~;bjK>QzWn9v z0#>D_YdvbedA8|}ZOIH(e5W`-SCpLC70<*_`i_-mDOx5g#{sn9cydC?s3EpFlxq7; zIrEhz@l7xZUD-!{Thf{Qsy`V@CNDRcUta4U=PNfz9Ulq)-0xX0MlHSeFz6PD2G`(0;pREG!r_Iu(1lt3-z6vX zQkN(RU)k`6-ygp2nlmaX-5ZLO*QvUdwJX)$M!b2C>Qk&;d`mPP4nVmz%-D2_B?mGLnT$Ql4!@QELN`Id=`>#_%M)mbg}w+t*b9?r zq2Hf@iin!GlSOrbLg~aTzE`wbm_Y>ymF)7T+`?TUtyKq;!;+E6sQD4Mx@qs2iI(jT z?v!p8QzEXuiK(qmJ1e62%N#0+HZqD2(TRU+1a(dS2Z@xy6HAtowSC zA~&Pn=Qae;0TjMu#t^QI<; z{R=XNi3SGZ-10f`q=16bT}-D4!id*&Y;ZKs;*`<=mmN*ku=@5TxF8UGJJPq^+PW2b zNDgs%#-<^AEfeL$Mi4d;XNe)ykiKvfEVo9~dU;u4}Fz3r2RqiFnS-4z6LL3m` z0!(jK`mC42=rAT#U+%cg@bK3GAV&X!XBhn~&=6-e@Tl1o zb>DHp+lo9HZYeHERJJhLdKBG$S;24b12zV|W>?H)s#BZU_6d|=fYs;bxu!%%^Iy)X z%WrsFFq;!V2UI?o1nZM0nB|;35a#OL>WjFu(qM#~M$I2#fvTh8hWjQPqYXahc#Tw{ z*55|doxc7GaNH85yn8Q8r6}is>WgOnT)d~WZ`0zSaojS9{Ug0w6u?{R6FytJHEzucIK^oO;Q&IB0|a3D^6sa-$Poa_g? z9nVJQsPp}tm7ii$VqsEkV4<*g{P^zCBLJs}%)&E3c&(Fw>% zpFK$`P|J^suTnBqFA(Otgen_WKTVaqLDf|0&~p7F2m;LNgK-oL(fLRPY6fk zCx#}CKaopS`P09G+iohb*ChHzD6eHut`=^u5-;2evYG_W^i?)ERL;aHM}p~j$`f1O z)NI||^C%z{w3fP0ve&BbBFJNdlZ+e51+?y#2@MM9oAO4u3(7wE)}cYNPu6>tz>$P9PB3`j?+g7(Y z>JZwK;!pw9LCW)O4*nm?`0reh8|^b5r?pFe$nzN0@YAmJuG|-g5)`i(ZHGM0tkmW% zmYTH7e{nVos2A0^_F>@_CUD^bd<04vY4uneL=zNS8r*fLHN_)1iLQU`3;{hUp zEuqe;;)TkNkBbn+pEorb_#YTMGXtInwTgLI*zS9n>c1>~h4j8vQYxWy5LHE}<0V?U z(y0A3nl>WtBlk!#38#TXHzSQl>QoVxc!8j98?JOEICp)j4*FE*!m~rPao^&ODP}Hw z?)nM`%DLl?we7}1!@Ws&S7kBaA|vO3O#tf)rGqm`lBSGXZP=)c`sPUxJ**bKGZ9cT^M-Aw7ga0Sbcr&Q-J=*s$Mc27{|K(%UT&y9%oqoHgF>7}q_(A{0tO955#Q9^PVf?)OI%6Hof2WweIrN- zt`~t$0%78$jrs+>UhZG9TxYj7x?sY`?9IxAvqn6H140YNJ7a2Rd(mxNoOb;*5rpx) z0EU30jZknw9PktDqGx1ej6c*kT??>mK!*cG_%rJ!k{eGv6%#{*$Z#~HmFzu<{?cl9L}_BU<&mNy-xs%$?*r{?oM=~;&BJ+;ouE& zavY(*;0X8;4=)85&(AMWwFgn-b25G#xlE2@KK5)~;@c~=cgHRDY9pWvBK>MUlc%eI zFftA(-DLDUxkzR3{{D6h?E~n-BB+n*C=8ALG&e`#%GEKh5xtDd&`3!Wqeyi)cbEBE zrmNh+cxdAG*m0%V=s58lVP3r-YS+?P*TfhVj-SQ>X27_1#KaXpO!ilCkRT%PvWjV4~FTC|5(vmC3!%fkb^63g^}vF0NB% zlX;%Ugu7)e8*DE4COC+=WZpp2R~a)#FwJ85){=fB47bg^RO*n{8p-eIlPfJQFz2C+ zIIdKi$s5r(JlYY0FKfkky=6^|2zeJ9{5TU?tGv{KF+>dTEMcjmST!lX&D2q2`?dbp4Y&&x}K6X;Isq5}W+|{(0(q2+WQ9b0Z>@}<1$|lZ@?m@c(l=pBNIEvRxjd&3# zpE_|OsBl4P<$Zi=hwYPQwLod2SckDk7@~u`xpOlk3C;#y`;i6_!yy!@IL9VWKBCX) z(C8C}AV~lmN|*GP^;Bd#ciUB4sb5P zcRVv0JIj{)wl5q&m!uB0BFod=Cd80L`vv()P1JT)r97v9MOJGs=a}~Uy`OKl&FwP> zn{I$rNnKc}3j#$S$0A(G1Nlh^80a_Eb(mzWy+4AfyUWG;U640|%~`6F$+$>ayU#^A2viEyw97@bq7AP`H zWw;&O6>Q9TiHOy`U^_;$Y{iswN1tFflX5WNe1L_OkF`AM^b@p8G6kZ6wAw)o<_^Ml z=EA$u+?-Auj6OA{5=PoPdWVAsAQsHGJXW``fdFgW7eUX&B;1ELwEa%Nv{&zkd(eDe zMFMxmR=KId122Rk1xT9h>WpZw{?;q8gnf2Tg`##M+u?0Sp_VADKV{?h^V0v5?qMx!GTH|T74@WsY`J5{O0Uv$Q7RW}Li zAV@2wK4n!>Zlggr)Upf-oVeWuxnrb6d4a283jcI2K|tRfYbd0E+cy94PCDI408+L$ z4bXfgm$AOKr3~cD=>{)O23~60P!c_lpHJ%ZNvEu&7u+-qUSZh9Adi+Y=#Tc8vWN$1 zu{WkiUMR(n)avUut6}YnA5j`kpY5?ZZe<83Qr(>fAol?u>s@|*Ex*pLf*rfoddqZI zY5It${U@gNHv>ck-$Z4fKs#1f&&2NpR?7FldD=Ji8ciH& zR{(Kz%cVZ;>h+nAN$dQMT0n44u_cb!Smd{5EMR>H=J3)HctWIK@ti$i}^Cgh}Q72+sw1ja4;V|P<8snhHDb@1tiocp%JQe{V_e$zpQH3Mk5 z{WG*;{~2194JIQE@@;$YJ`pI&Racq;RlZPGfbZfhjXMwlGFb)WkBG;m;F&dJG#22K zZ02c~e5sFbqg1VX(O<6z7FL)Sgpob%lI1VWl(>5!AOAXyL3>{KAf%?od~gJt&K2ZUoOFQ1#b&gkTAp8fqFZZ9dvi z?amAU@@<}2O!s}arjLqsElw<=JxGK|P9r!rI$CGj;Jb5qJX7M$Pws1Mi3Z$j^Egf$ z2UU-I9nrY3_`H8BnEb*J2HmDR{70rx-UCS2ov{-$5Z`XzYl48(sCY@7^@yoRCM%rp zx#c+&QL=Kb8+xI$$mT{`lwc}ZLw0>~yO-)^gQt$yttcKESECh%kH}nZxeAYpGbWbx z8JW6VQeOI#BuN&LNjNs1PV9RUw9FeL9VOaF;on{*pMS(Q2>G1LXzBN1CEx?q3(EN1 zAY;UrtYJ&D+Ghfdl_1x-)M2*?7Opo3%}yf+!y9D70+p8$N9~TQTBRQ`_op?BH^&4d z-QJJQn+ORkoxe@*cd#!Uakp<6^Jtx^>OXJM^MYgdTW(-|-3?+d?6ko~sNw$IX!Jz$ zyLWC(W@Rcrh0wx+m0_g9&6iMZZ8Ng{$1UrhX$IzJRJN#d>eO(ESm4TE2*x|kq0NQ1 zH7H-Y=>h-4qt`5|vW7@y-DR1uLvGkk3rP_QfY(C<3bS| zPp|z|K7u@MUtDIy?K~>jio!DL`?0gl8S({Gc(zWgraAS*W~rvHYMxstKzeXdk7P@X z)Vm-!H^Xks{lctNmIZXlK*BK4H^J9K@N>s)PlZ#a!`V2hUaGp zA`QlynhMZq!}Q{32gZTmYq7BHyi$BTf63A3Q^s>5aUzZDIdM>^sw1>6ICbX!GwuLa zMHo`{xmh}JJmZ%P8*J?O!A3)S>HzEL}ZqU4%Yk5N+cj=;d#<;eK>(_f|0 zq1}B&r;g7}#a_D*^G?uY)wXC9QlNQ)OGOyyaLw9`FN&n5=I|vX25$~z`UX{q)xM4T;PXITTAEKM4~_eymC7ZP1Bxm4T^*?o=BU6t2nIu-r-Lx<7HiTZ{t6DbbX5MP)3ly>q(LSjmWj@zh;;TI zl_i2tBkXw0Wu=HQR4+|wwVaa%`a7*Mc?Ay+*~qfO{&|6 zINxHE1w|#s%bQvAg>jmgxO<(7Y4h^UgiGB$<*{D-ox#alum^`VOH&sH-co<3NVDju z#if#USmzmg5+f}m9=@!M+^IPwlu`es2SP2FxCtUTRel%vA(?bij9rH{3yr7E**i!` z?@MO#I;5)D>#_&h)i}P8*++$-j1| zJo>nN@rrhMoA=S>E38C4I#MhA<@$OZjIX&}OSt%3r9z>8acZn%u}f>DhyrzV_?)@Y z$IjBPjfHLQzX_3H=g^dZ`*lsjLE?!w78;_`nDtO^FLGQ*KpU6!sa>#3C%o#!W`8WC zBQU^yd_D_NIN45CLR=TGgi!qt)rdxhiL!u~uemdwM){^>gsX~*M$=&h{F z-2M&Tw&@SBV#P=>@L8Ub`I>=txF41dU!&J?SGXL?F3eEdny4bmWs+8}Cd(N5p>+P< zebsBp9z91m7n*G^FfFjc%gb(xit7@`eQWlL#E|IVrXFs%V0%63wPxRx3>5D(e{D-1 zzmhhNGz#nhCZ2gj)P#O^U`-i@gABgDl&UC|ml&Dj7KExRmUH z`iC9sDgf8*zdfAZHM?JR?~v-RvQauOGSjm+S72}sae&*JeUH~g9WJ-|{K*sUsbd0T T<)yoy*OQS@cvT{1=>I4S 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 Date: Tue, 23 May 2017 21:56:51 -0400 Subject: [PATCH 0456/3256] implement task handling for master server's service --- doc/design/cluster_train/master_server.md | 16 +- paddle/go/master/service.go | 179 ++++++++++++++++++++++ paddle/go/master/service_internal_test.go | 32 ++++ 3 files changed, 219 insertions(+), 8 deletions(-) create mode 100644 paddle/go/master/service.go create mode 100644 paddle/go/master/service_internal_test.go diff --git a/doc/design/cluster_train/master_server.md b/doc/design/cluster_train/master_server.md index bb830765258..4bf3c506f10 100644 --- a/doc/design/cluster_train/master_server.md +++ b/doc/design/cluster_train/master_server.md @@ -10,7 +10,7 @@ A dataset is a list of files in *RecordIO* format. A RecordIO file consists of c ## Task Queue -As mentioned in [distributed training design doc](./README.md), a *task* is a data shard that the master server assigns to the trainer process to train on. A task consists of one or multiple *blocks* from one or multiple files. The master server maintains *task queues* to track the training progress. +As mentioned in [distributed training design doc](./README.md), a *task* is a data shard that the master server assigns to the trainer process to train on. A task consists of one or multiple *chunks* from one or multiple files. The master server maintains *task queues* to track the training progress. ### Task Queue Creation @@ -21,23 +21,23 @@ As mentioned in [distributed training design doc](./README.md), a *task* is a da func (m *RPCServer) ReportDataset(Paths []string, dummy *int) error { } ``` -1. The master server will scan through each RecordIO file to generate the *block index* and know how many blocks does each file have. A block can be referenced by the file path and the index of the block within the file. The block index is in memory data structure that enables fast access to each block, and the index of the block with the file is an integer start from 0, representing the n-th block within the file. +1. The master server will scan through each RecordIO file to generate the *chunk index* and know how many chunks does each file have. A chunk can be referenced by the file path and the index of the chunk within the file. The chunk index is in memory data structure that enables fast access to each chunk, and the index of the chunk with the file is an integer start from 0, representing the n-th chunk within the file. - The definition of the block is: + The definition of the chunk is: ```go - type Block struct { - Idx int // index of the block within the file + type Chunk struct { + Idx int // index of the chunk within the file Path string - Index recordio.Index // block index + Index recordio.Index // chunk index } ``` -1. Blocks are grouped into tasks, and tasks are filled into the todo queue. The pending queue and the done queue are initialized with no element. +1. Chunks are grouped into tasks, and tasks are filled into the todo queue. The pending queue and the done queue are initialized with no element. The definition of the task is: ```go type Task struct { Index int - Blocks []Block + Chunks []Chunk } ``` diff --git a/paddle/go/master/service.go b/paddle/go/master/service.go new file mode 100644 index 00000000000..ae7f9687a5e --- /dev/null +++ b/paddle/go/master/service.go @@ -0,0 +1,179 @@ +package master + +import ( + "errors" + "log" + "sync" + "time" + + "github.com/wangkuiyi/recordio" +) + +const ( + targetTaskCount = 300 +) + +// errors +var ( + ErrNoMoreTask = errors.New("no more task for current pass") + ErrPendingTaskNotFound = errors.New("pending task not found") +) + +// Service is the master server service. +type Service struct { + timeoutDur time.Duration + timeoutMax int + + mu sync.Mutex + taskQueues taskQueues +} + +// Recover recovers service state from etcd. +func Recover() (*Service, error) { + // TODO(helin): recover from snapshot state from etcd. + return nil, nil +} + +func partition(chunks []Chunk, targetTaskCount int) []taskEntry { + id := 0 + chunkPerTask := len(chunks) / targetTaskCount + if chunkPerTask <= 0 { + chunkPerTask = 1 + } + + var result []taskEntry + var cur taskEntry + for i, c := range chunks { + if i%chunkPerTask == 0 && len(cur.Task.Chunks) > 0 { + cur.Task.ID = id + id++ + result = append(result, cur) + cur.Task.Chunks = nil + } + + cur.Task.Chunks = append(cur.Task.Chunks, c) + } + + if len(cur.Task.Chunks) > 0 { + cur.Task.ID = id + id++ + result = append(result, cur) + } + + return result +} + +// NewService creates a new service. +func NewService(chunks []Chunk, timeoutDur time.Duration, timeoutMax int) (*Service, error) { + s := &Service{} + s.timeoutDur = timeoutDur + s.timeoutMax = timeoutMax + s.taskQueues = taskQueues{} + s.taskQueues.Pending = make(map[int]taskEntry) + s.taskQueues.Todo = partition(chunks, targetTaskCount) + return s, nil +} + +// Chunk is a chunk of data consisted of several data instances. +type Chunk struct { + Idx int // index of the chunk within the file + Path string + Index recordio.Index // block index +} + +// Task is the basic unit of data instances assigned to trainers. +type Task struct { + ID int + Chunks []Chunk +} + +type taskEntry struct { + Epoch int + NumTimeout int + Task Task +} + +type taskQueues struct { + Todo []taskEntry + Pending map[int]taskEntry // map from task ID to task entry + Done []taskEntry + Failed []Task +} + +// *must* be called with s.mu being held. +func (s *Service) snapshot() error { + // TODO(helin): snapshot state on etcd. + return nil +} + +// GetTask gets a new task from the service. +func (s *Service) GetTask(dummy int, task *Task) error { + s.mu.Lock() + defer s.mu.Unlock() + + if len(s.taskQueues.Todo) == 0 { + return ErrNoMoreTask + } + + t := s.taskQueues.Todo[0] + t.Epoch++ + s.taskQueues.Todo = s.taskQueues.Todo[1:] + s.taskQueues.Pending[t.Task.ID] = t + err := s.snapshot() + if err != nil { + return err + } + + time.AfterFunc(s.timeoutDur, func(taskID int, epoch int) func() { + return func() { + s.mu.Lock() + defer s.mu.Unlock() + + t, ok := s.taskQueues.Pending[taskID] + if !ok { + return + } + + if t.Epoch != epoch { + // new epoch, task launched after the + // schedule of this timeout check. + return + } + + defer func() { + err := s.snapshot() + if err != nil { + log.Println(err) + } + }() + + delete(s.taskQueues.Pending, t.Task.ID) + + t.NumTimeout++ + if t.NumTimeout > s.timeoutMax { + s.taskQueues.Failed = append(s.taskQueues.Failed, t.Task) + return + } + + s.taskQueues.Todo = append(s.taskQueues.Todo, t) + } + }(t.Task.ID, t.Epoch)) + return nil +} + +// TaskFinished tell the service that a task is finished. +func (s *Service) TaskFinished(taskID int, dummy *int) error { + s.mu.Lock() + defer s.mu.Unlock() + + t, ok := s.taskQueues.Pending[taskID] + if !ok { + return ErrPendingTaskNotFound + } + + // task finished, reset timeout + t.NumTimeout = 0 + s.taskQueues.Done = append(s.taskQueues.Done, t) + delete(s.taskQueues.Pending, taskID) + return s.snapshot() +} diff --git a/paddle/go/master/service_internal_test.go b/paddle/go/master/service_internal_test.go new file mode 100644 index 00000000000..1e6197d2418 --- /dev/null +++ b/paddle/go/master/service_internal_test.go @@ -0,0 +1,32 @@ +package master + +import "testing" + +func TestPartitionCount(t *testing.T) { + cs := make([]Chunk, 100) + ts := partition(cs, 20) + if len(ts) != 20 { + t.Error(len(ts)) + } + + cs = make([]Chunk, 101) + ts = partition(cs, 20) + if len(ts) != 21 { + t.Error(len(ts)) + } + + ts = partition(cs, 200) + if len(ts) != 101 { + t.Error(len(ts)) + } +} + +func TestPartionIndex(t *testing.T) { + cs := make([]Chunk, 100) + ts := partition(cs, 20) + for i := range ts { + if ts[i].Task.ID != i { + t.Error(ts[i], i) + } + } +} -- GitLab From 025e7f9cb6c4865f18f66d4c6a19b60ddbea3da2 Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Wed, 24 May 2017 20:08:55 -0400 Subject: [PATCH 0457/3256] implement basic master server --- paddle/go/cmd/master/master.go | 78 ++++++++++++++++++++++++++++++++++ paddle/go/master/service.go | 4 +- 2 files changed, 80 insertions(+), 2 deletions(-) create mode 100644 paddle/go/cmd/master/master.go diff --git a/paddle/go/cmd/master/master.go b/paddle/go/cmd/master/master.go new file mode 100644 index 00000000000..8346b42a329 --- /dev/null +++ b/paddle/go/cmd/master/master.go @@ -0,0 +1,78 @@ +package main + +import ( + "flag" + "net" + "net/http" + "net/rpc" + "os" + "strconv" + "strings" + "time" + + "github.com/PaddlePaddle/Paddle/paddle/go/master" + "github.com/wangkuiyi/recordio" +) + +const ( + taskTimeoutDur = 20 * time.Minute + taskTimeoutMax = 3 +) + +func main() { + port := flag.Int("p", 0, "port of the master server") + dataset := flag.String("d", "", "dataset: comma separated path to RecordIO files") + faultTolerant := flag.Bool("fault-tolerance", false, "enable fault tolerance (requires etcd).") + flag.Parse() + + if *dataset == "" { + panic("no dataset specified.") + } + + if *faultTolerant { + panic("fault tolernat not implemented.") + } + + var chunks []master.Chunk + paths := strings.Split(*dataset, ",") + idx := 0 + for _, path := range paths { + f, err := os.Open(path) + if err != nil { + panic(err) + } + + index, err := recordio.LoadIndex(f) + if err != nil { + panic(err) + } + f.Close() + + count := index.NumChunks() + for i := 0; i < count; i++ { + chunk := master.Chunk{ + Idx: idx, + Path: path, + Index: *index.ChunkIndex(i), + } + chunks = append(chunks, chunk) + } + } + + s := master.NewService(chunks, taskTimeoutDur, taskTimeoutMax) + err := rpc.Register(s) + if err != nil { + panic(err) + } + + rpc.HandleHTTP() + l, err := net.Listen("tcp", ":"+strconv.Itoa(*port)) + if err != nil { + panic(err) + } + + err = http.Serve(l, nil) + if err != nil { + panic(err) + } +} diff --git a/paddle/go/master/service.go b/paddle/go/master/service.go index ae7f9687a5e..652d345e01d 100644 --- a/paddle/go/master/service.go +++ b/paddle/go/master/service.go @@ -64,14 +64,14 @@ func partition(chunks []Chunk, targetTaskCount int) []taskEntry { } // NewService creates a new service. -func NewService(chunks []Chunk, timeoutDur time.Duration, timeoutMax int) (*Service, error) { +func NewService(chunks []Chunk, timeoutDur time.Duration, timeoutMax int) *Service { s := &Service{} s.timeoutDur = timeoutDur s.timeoutMax = timeoutMax s.taskQueues = taskQueues{} s.taskQueues.Pending = make(map[int]taskEntry) s.taskQueues.Todo = partition(chunks, targetTaskCount) - return s, nil + return s } // Chunk is a chunk of data consisted of several data instances. -- GitLab From 6ce7c8bc873b73a7cd7a5cdb1d0b861d8d3ef23a Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Thu, 25 May 2017 16:05:29 -0400 Subject: [PATCH 0458/3256] update recordIO include path --- paddle/go/cmd/master/master.go | 2 +- paddle/go/master/service.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/paddle/go/cmd/master/master.go b/paddle/go/cmd/master/master.go index 8346b42a329..16052fd75c7 100644 --- a/paddle/go/cmd/master/master.go +++ b/paddle/go/cmd/master/master.go @@ -11,7 +11,7 @@ import ( "time" "github.com/PaddlePaddle/Paddle/paddle/go/master" - "github.com/wangkuiyi/recordio" + "github.com/PaddlePaddle/Paddle/paddle/go/recordio" ) const ( diff --git a/paddle/go/master/service.go b/paddle/go/master/service.go index 652d345e01d..cf15f28cc74 100644 --- a/paddle/go/master/service.go +++ b/paddle/go/master/service.go @@ -6,7 +6,7 @@ import ( "sync" "time" - "github.com/wangkuiyi/recordio" + "github.com/PaddlePaddle/Paddle/paddle/go/recordio" ) const ( -- GitLab From 9b11e17d8d38e83c25be358193bb66b778cbc31c Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Thu, 25 May 2017 17:11:29 -0400 Subject: [PATCH 0459/3256] fix according to comments --- paddle/go/cmd/master/master.go | 41 ++++++++++++++++------- paddle/go/cmd/pserver/pserver.go | 5 +-- paddle/go/master/service.go | 13 ++++--- paddle/go/master/service_internal_test.go | 11 ++++-- 4 files changed, 45 insertions(+), 25 deletions(-) diff --git a/paddle/go/cmd/master/master.go b/paddle/go/cmd/master/master.go index 16052fd75c7..ef1f87c2dd5 100644 --- a/paddle/go/cmd/master/master.go +++ b/paddle/go/cmd/master/master.go @@ -1,40 +1,55 @@ package main import ( - "flag" + "fmt" "net" "net/http" "net/rpc" "os" + "path/filepath" "strconv" "strings" "time" + "github.com/namsral/flag" + "github.com/PaddlePaddle/Paddle/paddle/go/master" "github.com/PaddlePaddle/Paddle/paddle/go/recordio" ) -const ( - taskTimeoutDur = 20 * time.Minute - taskTimeoutMax = 3 -) - func main() { - port := flag.Int("p", 0, "port of the master server") - dataset := flag.String("d", "", "dataset: comma separated path to RecordIO files") - faultTolerant := flag.Bool("fault-tolerance", false, "enable fault tolerance (requires etcd).") + port := flag.Int("port", 8080, "port of the master server.") + dataset := flag.String("training_dataset", "", "dataset: comma separated path to RecordIO paths, supports golb patterns.") + faultTolerance := flag.Bool("fault_tolerance", false, "enable fault tolerance (requires etcd).") + taskTimeoutDur := flag.Duration("task_timout_dur", 20*time.Minute, "task timout duration.") + taskTimeoutMax := flag.Int("task_timeout_max", 3, "max timtout count for each task before it being declared failed task.") + chunkPerTask := flag.Int("chunk_per_task", 10, "chunk per task.") flag.Parse() if *dataset == "" { panic("no dataset specified.") } - if *faultTolerant { - panic("fault tolernat not implemented.") + if *faultTolerance { + panic("fault tolernance not implemented.") } var chunks []master.Chunk - paths := strings.Split(*dataset, ",") + var paths []string + ss := strings.Split(*dataset, ",") + fmt.Println(ss) + for _, s := range ss { + match, err := filepath.Glob(s) + if err != nil { + panic(err) + } + paths = append(paths, match...) + } + + if len(paths) == 0 { + panic("no valid datset specified.") + } + idx := 0 for _, path := range paths { f, err := os.Open(path) @@ -59,7 +74,7 @@ func main() { } } - s := master.NewService(chunks, taskTimeoutDur, taskTimeoutMax) + s := master.NewService(chunks, *chunkPerTask, *taskTimeoutDur, *taskTimeoutMax) err := rpc.Register(s) if err != nil { panic(err) diff --git a/paddle/go/cmd/pserver/pserver.go b/paddle/go/cmd/pserver/pserver.go index 41417875fb9..bd4bfc70283 100644 --- a/paddle/go/cmd/pserver/pserver.go +++ b/paddle/go/cmd/pserver/pserver.go @@ -1,17 +1,18 @@ package main import ( - "flag" "net" "net/http" "net/rpc" "strconv" + "github.com/namsral/flag" + "github.com/PaddlePaddle/Paddle/paddle/go/pserver" ) func main() { - port := flag.Int("p", 0, "port of the pserver") + port := flag.Int("port", 0, "port of the pserver") flag.Parse() s := pserver.NewService() diff --git a/paddle/go/master/service.go b/paddle/go/master/service.go index cf15f28cc74..75266482870 100644 --- a/paddle/go/master/service.go +++ b/paddle/go/master/service.go @@ -34,17 +34,16 @@ func Recover() (*Service, error) { return nil, nil } -func partition(chunks []Chunk, targetTaskCount int) []taskEntry { +func partition(chunks []Chunk, chunksPerTask int) []taskEntry { id := 0 - chunkPerTask := len(chunks) / targetTaskCount - if chunkPerTask <= 0 { - chunkPerTask = 1 + if chunksPerTask <= 0 { + chunksPerTask = 1 } var result []taskEntry var cur taskEntry for i, c := range chunks { - if i%chunkPerTask == 0 && len(cur.Task.Chunks) > 0 { + if i%chunksPerTask == 0 && len(cur.Task.Chunks) > 0 { cur.Task.ID = id id++ result = append(result, cur) @@ -64,13 +63,13 @@ func partition(chunks []Chunk, targetTaskCount int) []taskEntry { } // NewService creates a new service. -func NewService(chunks []Chunk, timeoutDur time.Duration, timeoutMax int) *Service { +func NewService(chunks []Chunk, chunksPerTask int, timeoutDur time.Duration, timeoutMax int) *Service { s := &Service{} s.timeoutDur = timeoutDur s.timeoutMax = timeoutMax s.taskQueues = taskQueues{} s.taskQueues.Pending = make(map[int]taskEntry) - s.taskQueues.Todo = partition(chunks, targetTaskCount) + s.taskQueues.Todo = partition(chunks, chunksPerTask) return s } diff --git a/paddle/go/master/service_internal_test.go b/paddle/go/master/service_internal_test.go index 1e6197d2418..bc435b505c0 100644 --- a/paddle/go/master/service_internal_test.go +++ b/paddle/go/master/service_internal_test.go @@ -4,18 +4,23 @@ import "testing" func TestPartitionCount(t *testing.T) { cs := make([]Chunk, 100) - ts := partition(cs, 20) + ts := partition(cs, 5) if len(ts) != 20 { t.Error(len(ts)) } cs = make([]Chunk, 101) - ts = partition(cs, 20) + ts = partition(cs, 5) if len(ts) != 21 { t.Error(len(ts)) } - ts = partition(cs, 200) + ts = partition(cs, 1) + if len(ts) != 101 { + t.Error(len(ts)) + } + + ts = partition(cs, 0) if len(ts) != 101 { t.Error(len(ts)) } -- GitLab From 630b27ec10721dce6cf56a15dc1502e931733fb5 Mon Sep 17 00:00:00 2001 From: Luo Tao Date: Fri, 26 May 2017 10:54:22 +0800 Subject: [PATCH 0460/3256] Compatible with previous configurations --- python/paddle/trainer_config_helpers/layers.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/python/paddle/trainer_config_helpers/layers.py b/python/paddle/trainer_config_helpers/layers.py index 5a1e31c29d5..2b4b4dea890 100755 --- a/python/paddle/trainer_config_helpers/layers.py +++ b/python/paddle/trainer_config_helpers/layers.py @@ -245,6 +245,9 @@ class AggregateLevel(object): """ TO_NO_SEQUENCE = 'non-seq' TO_SEQUENCE = 'seq' + # compatible with previous configuration + EACH_TIMESTEP = TO_NO_SEQUENCE + EACH_SEQUENCE = TO_SEQUENCE class LayerOutput(object): @@ -1487,6 +1490,8 @@ class ExpandLevel(object): """ FROM_NO_SEQUENCE = AggregateLevel.TO_NO_SEQUENCE FROM_SEQUENCE = AggregateLevel.TO_SEQUENCE + # compatible with previous configuration + FROM_TIMESTEP = FROM_NO_SEQUENCE @wrap_name_default() -- GitLab From 9068da12202f402525dc129dde28520a2e522fb0 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Fri, 26 May 2017 11:45:52 +0800 Subject: [PATCH 0461/3256] Add user can define PROTOBUF_ROOT. --- .gitignore | 2 ++ cmake/external/protobuf.cmake | 26 +++++++++++++++++++++++--- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 2b30f7938c8..9ed10d92d8c 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,5 @@ third_party/ *~ bazel-* third_party/ +# for clion +cmake-build-* diff --git a/cmake/external/protobuf.cmake b/cmake/external/protobuf.cmake index b35e6839cdc..831118b03a8 100644 --- a/cmake/external/protobuf.cmake +++ b/cmake/external/protobuf.cmake @@ -14,6 +14,28 @@ INCLUDE(ExternalProject) +macro(PROMPT_PROTOBUF_LIB) + MESSAGE(STATUS "Protobuf protoc executable: ${PROTOBUF_PROTOC_EXECUTABLE}") + MESSAGE(STATUS "Protobuf library: ${PROTOBUF_LIBRARY}") + INCLUDE_DIRECTORIES(${PROTOBUF_INCLUDE_DIR}) + RETURN() +endmacro() + +set(PROTOBUF_ROOT "" CACHE PATH "Folder contains protobuf") +if (NOT "${PROTOBUF_ROOT}" STREQUAL "") + find_path(PROTOBUF_INCLUDE_DIR google/protobuf/message.h PATHS ${PROTOBUF_ROOT}/include) + find_library(PROTOBUF_LIBRARY protobuf PATHS ${PROTOBUF_ROOT}/lib) + find_library(PROTOBUF_LITE_LIBRARY protobuf-lite PATHS ${PROTOBUF_ROOT}/lib) + find_library(PROTOBUF_PROTOC_LIBRARY protoc PATHS ${PROTOBUF_ROOT}/lib) + find_program(PROTOBUF_PROTOC_EXECUTABLE protoc PATHS ${PROTOBUF_ROOT}/bin) + if (PROTOBUF_INCLUDE_DIR AND PROTOBUF_LIBRARY AND PROTOBUF_LITE_LIBRARY AND PROTOBUF_PROTOC_LIBRARY) + message(STATUS "Using custom protobuf library in ${PROTOBUF_ROOT}.") + PROMPT_PROTOBUF_LIB() + else() + message(WARNING "Cannot find protobuf library in ${PROTOBUF_ROOT}.") + endif() +endif() + FUNCTION(build_protobuf TARGET_NAME BUILD_FOR_HOST) SET(PROTOBUF_SOURCES_DIR ${THIRD_PARTY_PATH}/${TARGET_NAME}) SET(PROTOBUF_INSTALL_DIR ${THIRD_PARTY_PATH}/install/${TARGET_NAME}) @@ -107,6 +129,4 @@ IF(NOT PROTOBUF_FOUND) SET(PROTOBUF_PROTOC_LIBRARY ${protobuf_PROTOC_LIBRARY} CACHE FILEPATH "protoc library." FORCE) ENDIF(NOT PROTOBUF_FOUND) -MESSAGE(STATUS "Protobuf protoc executable: ${PROTOBUF_PROTOC_EXECUTABLE}") -MESSAGE(STATUS "Protobuf library: ${PROTOBUF_LIBRARY}") -INCLUDE_DIRECTORIES(${PROTOBUF_INCLUDE_DIR}) +PROMPT_PROTOBUF_LIB() \ No newline at end of file -- GitLab From f6cf9fa8393ccd9829629a82d6fafd830b85da93 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Fri, 26 May 2017 12:38:22 +0800 Subject: [PATCH 0462/3256] Add missing condition in if --- cmake/external/protobuf.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/external/protobuf.cmake b/cmake/external/protobuf.cmake index 831118b03a8..da46eaff501 100644 --- a/cmake/external/protobuf.cmake +++ b/cmake/external/protobuf.cmake @@ -28,7 +28,7 @@ if (NOT "${PROTOBUF_ROOT}" STREQUAL "") find_library(PROTOBUF_LITE_LIBRARY protobuf-lite PATHS ${PROTOBUF_ROOT}/lib) find_library(PROTOBUF_PROTOC_LIBRARY protoc PATHS ${PROTOBUF_ROOT}/lib) find_program(PROTOBUF_PROTOC_EXECUTABLE protoc PATHS ${PROTOBUF_ROOT}/bin) - if (PROTOBUF_INCLUDE_DIR AND PROTOBUF_LIBRARY AND PROTOBUF_LITE_LIBRARY AND PROTOBUF_PROTOC_LIBRARY) + if (PROTOBUF_INCLUDE_DIR AND PROTOBUF_LIBRARY AND PROTOBUF_LITE_LIBRARY AND PROTOBUF_PROTOC_LIBRARY AND PROTOBUF_PROTOC_EXECUTABLE) message(STATUS "Using custom protobuf library in ${PROTOBUF_ROOT}.") PROMPT_PROTOBUF_LIB() else() -- GitLab From 3b65bc7a2631fe2326149cd228d2ecde3a86081b Mon Sep 17 00:00:00 2001 From: hedaoyuan Date: Fri, 26 May 2017 14:17:25 +0800 Subject: [PATCH 0463/3256] Add a naive convolution implement --- paddle/function/ConvOp.cpp | 128 +++++++++++++++++++++++++++++++++++++ paddle/function/ConvOp.h | 67 +++++++++++++++++++ 2 files changed, 195 insertions(+) create mode 100644 paddle/function/ConvOp.cpp create mode 100644 paddle/function/ConvOp.h diff --git a/paddle/function/ConvOp.cpp b/paddle/function/ConvOp.cpp new file mode 100644 index 00000000000..50f030585a0 --- /dev/null +++ b/paddle/function/ConvOp.cpp @@ -0,0 +1,128 @@ +/* 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 "ConvFunc.h" + +namespace paddle { + +/* + * The three arguments are stored in memory in row major order. + * inputData = [batchSize, inputChannels, inputHeight, inputWidth] + * filterData = [outputChannels, inputChannels, filterHeight, filterWidth] + * outputData = [batchSize, outputChannels, outputHeight, outputWidth] + */ +template +class NaiveConvFunctor { +public: + void operator()(const T* inputData, + size_t batchSize, + size_t inputChannels, + size_t inputHeight, + size_t inputWidth, + const T* filterData, + size_t filterHeight, + size_t filterWidth, + T* outputData, + size_t outputChannels, + size_t outputHeight, + size_t outputWidth, + size_t padding, + size_t stride) { + for (size_t batch = 0; batch < batchSize; batch++) { + for (size_t outC = 0; outC < outputChannels; outC++) { + for (size_t outH = 0; outH < outputHeight; outH++) { + for (size_t outW = 0; outW < outputWidth; outW++) { + const int inStartH = (outH * stride) - padding; + const int inStartW = (outW * stride) - padding; + T outValue = (T)0; + for (size_t inC = 0; inC < inputChannels; inC++) { + for (size_t fH = 0; fH < filterHeight; fH++) { + for (size_t fW = 0; fW < filterWidth; fW++) { + T inValue; + const int inH = inStartH + fH; + const int inW = inStartW + fW; + if ((inH >= 0 && inH < inputHeight) && + (inW >= 0 && inW < inputWidth)) { + size_t offsetInput = + batch * inputChannels * inputHeight * inputWidth + + inC * inputHeight * inputWidth + inH * inputWidth + inW; + inValue = inputData[offsetInput]; + } else { + inValue = (T)0; + } + size_t offsetFilter = + outC * inputChannels * filterHeight * filterWidth + + inC * filterHeight * filterWidth + fH * filterWidth + fW; + T filterValue = filterData[offsetFilter]; + outValue += (inValue * filterValue); + } + } + } + + size_t offset = + batch * outputChannels * outputHeight * outputWidth + + outC * outputHeight * outputWidth + outH * outputWidth + outW; + outputData[offset] = outValue; + } + } + } + } + } +}; + +template +class NaiveConvFunction : public ConvFunctionBase { +public: + void init(const FuncConfig& config) override { + ConvFunctionBase::init(config); + } + + void calc(const BufferArgs& inputs, const BufferArgs& outputs) override { + check(inputs, outputs); + CHECK_EQ(outputs[0].getArgType(), ASSIGN_TO); + + size_t batchSize = inputs[0].shape()[0]; + size_t inputChannels = inputs[0].shape()[1]; + size_t inputHeight = inputs[0].shape()[2]; + size_t inputWidth = inputs[0].shape()[3]; + size_t filterHeight = inputs[1].shape()[2]; + size_t filterWidth = inputs[1].shape()[2]; + size_t outputChannels = outputs[0].shape()[1]; + size_t outputHeight = outputs[0].shape()[2]; + size_t outputWidth = outputs[0].shape()[3]; + + float* inputData = inputs[0].data(); + float* filterData = inputs[1].data(); + float* outputData = outputs[0].data(); + NaiveConvFunctor conv; + conv(inputData, + batchSize, + inputChannels, + inputHeight, + inputWidth, + filterData, + filterHeight, + filterWidth, + outputData, + outputChannels, + outputHeight, + outputWidth, + padding_, + stride_); + } +}; + +REGISTER_TYPED_FUNC(NaiveConv, CPU, NaiveConvFunction); + +} // namespace paddle diff --git a/paddle/function/ConvOp.h b/paddle/function/ConvOp.h new file mode 100644 index 00000000000..4d678cfe273 --- /dev/null +++ b/paddle/function/ConvOp.h @@ -0,0 +1,67 @@ +/* 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 "Function.h" + +namespace paddle { + +/* + * Function Arguments: + * + * \param inputs[0] Input image data, is NCHW format, where N is batch size, + * C is the number of channels, H and W is the height and + * width of input image. + * \param inputs[1] Filter data, is MCHW, where M is the number of output + * channels, C is the number of input channels, H and W + * is height and width of filter. + * \param outputs[0] Output image data, is NCHW format, where N is batch size, + * C is the number of channels, H and W is the height and + * width of output image. + * + * \note Implemented based on the ConvFunctionBase class only supports + * input data in the NCHW format. + */ +class ConvFunctionBase : public FunctionBase { +public: + void init(const FuncConfig& config) override { + // function arguments + stride_ = config.get("stride"); + padding_ = config.get("padding"); + + // number of inputs and outputs + numInputs_ = 2; + numOutputs_ = 1; + } + + virtual void calc(const BufferArgs& inputs, const BufferArgs& outputs) {} + + void check(const BufferArgs& inputs, const BufferArgs& outputs) override { + CHECK_EQ(numInputs_, inputs.size()); + CHECK_EQ(numOutputs_, outputs.size()); + + CHECK_EQ(inputs[0].shape().ndims(), (size_t)4); + CHECK_EQ(inputs[1].shape().ndims(), (size_t)4); + CHECK_EQ(outputs[0].shape().ndims(), (size_t)4); + + CHECK(inputs[0].shape()[0] == outputs[0].shape()[0]); + CHECK(inputs[0].shape()[1] == inputs[1].shape()[1]); + CHECK(outputs[0].shape()[1] == inputs[1].shape()[0]); + } + +protected: + size_t padding_; + size_t stride_; +}; + +} // namespace paddle -- GitLab From a91f7847db16da39183cd6013231b122341d7149 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Fri, 26 May 2017 14:43:22 +0800 Subject: [PATCH 0464/3256] Add with_testing guard for python/CMakeLists.txt --- python/CMakeLists.txt | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index 4f52f0f6cfd..3640dd3a75e 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -24,15 +24,16 @@ add_custom_target(paddle_python ALL DEPENDS ${OUTPUT_DIR}/.timestamp) set(PADDLE_PYTHON_PACKAGE_DIR ${CMAKE_CURRENT_BINARY_DIR}/dist/) -add_subdirectory(paddle/trainer_config_helpers/tests) -if (WITH_SWIG_PY) - # enable v2 API unittest only when paddle swig api is compiled - add_subdirectory(paddle/v2/tests) - add_subdirectory(paddle/v2/reader/tests) - add_subdirectory(paddle/v2/plot/tests) +if (WITH_TESTING) + add_subdirectory(paddle/trainer_config_helpers/tests) + if (WITH_SWIG_PY) + # enable v2 API unittest only when paddle swig api is compiled + add_subdirectory(paddle/v2/tests) + add_subdirectory(paddle/v2/reader/tests) + add_subdirectory(paddle/v2/plot/tests) + endif() endif() - install(DIRECTORY ${PADDLE_PYTHON_PACKAGE_DIR} DESTINATION opt/paddle/share/wheels ) -- GitLab From b6de52c47bbd0a5f5d943364a8ecfcec3df7da6a Mon Sep 17 00:00:00 2001 From: hedaoyuan Date: Fri, 26 May 2017 14:50:01 +0800 Subject: [PATCH 0465/3256] Bug fix --- paddle/function/{ConvOp.cpp => NaiveConvOp.cpp} | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) rename paddle/function/{ConvOp.cpp => NaiveConvOp.cpp} (95%) diff --git a/paddle/function/ConvOp.cpp b/paddle/function/NaiveConvOp.cpp similarity index 95% rename from paddle/function/ConvOp.cpp rename to paddle/function/NaiveConvOp.cpp index 50f030585a0..f13aa880a1e 100644 --- a/paddle/function/ConvOp.cpp +++ b/paddle/function/NaiveConvOp.cpp @@ -12,7 +12,7 @@ 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 "ConvFunc.h" +#include "ConvOp.h" namespace paddle { @@ -102,10 +102,10 @@ public: size_t outputHeight = outputs[0].shape()[2]; size_t outputWidth = outputs[0].shape()[3]; - float* inputData = inputs[0].data(); - float* filterData = inputs[1].data(); - float* outputData = outputs[0].data(); - NaiveConvFunctor conv; + real* inputData = inputs[0].data(); + real* filterData = inputs[1].data(); + real* outputData = outputs[0].data(); + NaiveConvFunctor conv; conv(inputData, batchSize, inputChannels, -- GitLab From 1846d9e1725465fcde1bc4e54384036edd483d80 Mon Sep 17 00:00:00 2001 From: hedaoyuan Date: Fri, 26 May 2017 15:12:54 +0800 Subject: [PATCH 0466/3256] Add a convolution Function based on gemm. --- paddle/function/GemmConvOp.cpp | 162 +++++++++++++++++++++++++++++++++ 1 file changed, 162 insertions(+) create mode 100644 paddle/function/GemmConvOp.cpp diff --git a/paddle/function/GemmConvOp.cpp b/paddle/function/GemmConvOp.cpp new file mode 100644 index 00000000000..5e6ee244862 --- /dev/null +++ b/paddle/function/GemmConvOp.cpp @@ -0,0 +1,162 @@ +/* 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 "ConvOp.h" +#include "paddle/math/MathFunctions.h" +#include "paddle/math/MemoryHandle.h" + +namespace paddle { + +/* + * imData = [input_channels, input_height, input_width] + * colData = [input_channels, filter_height, filter_width, + * output_height, output_width] + */ +template +class Im2ColFunctor { +public: + void operator()(const T* imData, + int inputChannels, + int inputHeight, + int inputWidth, + int filterHeight, + int filterWidth, + int strideHeight, + int strideWidth, + int paddingHeight, + int paddingWidth, + int outputHeight, + int outputWidth, + T* colData) { + int channelsCol = inputChannels * filterHeight * filterWidth; + + for (int c = 0; c < channelsCol; ++c) { + int wOffset = c % filterWidth; + int hOffset = (c / filterWidth) % filterHeight; + int c_im = c / filterHeight / filterWidth; + for (int h = 0; h < outputHeight; ++h) { + for (int w = 0; w < outputWidth; ++w) { + // no c_im*height to Exclude the channel number + int imgRowIdx = h * strideHeight + hOffset; + int imgColIdx = w * strideWidth + wOffset; + if ((imgRowIdx - paddingHeight) < 0 || + (imgRowIdx - paddingHeight) >= inputHeight || + (imgColIdx - paddingWidth) < 0 || + (imgColIdx - paddingWidth) >= inputWidth) { + colData[(c * outputHeight + h) * outputWidth + w] = T(0); + } else { + imgRowIdx += c_im * inputHeight - paddingHeight; + imgColIdx -= paddingWidth; + colData[(c * outputHeight + h) * outputWidth + w] = + imData[imgRowIdx * inputWidth + imgColIdx]; + } + } + } + } + } +}; + +/* + * Function Arguments: + * + * \param inputs[0] Input image data, is NCHW format, where N is batch size, + * C is the number of channels, H and W is the height and + * width of input image. + * \param inputs[1] Filter data, is MCHW, where M is the number of output + * channels, C is the number of input channels, H and W + * is height and width of filter. + * \param outputs[0] Output image data, is NCHW format, where N is batch size, + * C is the number of channels, H and W is the height and + * width of output image. + */ +template +class GemmConvFunction : public ConvFunctionBase { +public: + void init(const FuncConfig& config) override { + ConvFunctionBase::init(config); + } + + void calc(const BufferArgs& inputs, const BufferArgs& outputs) override { + check(inputs, outputs); + CHECK_EQ(outputs[0].getArgType(), ASSIGN_TO); + + size_t batchSize = inputs[0].shape()[0]; + size_t inputChannels = inputs[0].shape()[1]; + size_t inputHeight = inputs[0].shape()[2]; + size_t inputWidth = inputs[0].shape()[3]; + size_t filterHeight = inputs[1].shape()[2]; + size_t filterWidth = inputs[1].shape()[2]; + size_t outputChannels = outputs[0].shape()[1]; + size_t outputHeight = outputs[0].shape()[2]; + size_t outputWidth = outputs[0].shape()[3]; + + real* inputData = inputs[0].data(); + real* filterData = inputs[1].data(); + real* outputData = outputs[0].data(); + + size_t size = + inputChannels * filterHeight * filterWidth * outputHeight * outputWidth; + resizeBuffer(size); + real* colData = reinterpret_cast(memory_->getBuf()); + + Im2ColFunctor im2col; + for (size_t i = 0; i < batchSize; i++) { + im2col(inputData, + inputChannels, + inputHeight, + inputWidth, + filterHeight, + filterWidth, + stride_, + stride_, + padding_, + padding_, + outputHeight, + outputWidth, + colData); + + int M = outputChannels; + int N = outputHeight * outputWidth; + int K = inputChannels * filterHeight * filterWidth; + gemm(CblasNoTrans, + CblasNoTrans, + M, + N, + K, + 1.0f, + filterData, + K, + colData, + N, + 0.0f, + outputData, + N); + inputData += inputChannels * inputHeight * inputWidth; + outputData += outputChannels * outputHeight * outputWidth; + } + } + + void resizeBuffer(size_t newSize) { + if (!memory_ || newSize * sizeof(real) > memory_->getAllocSize()) { + memory_ = std::make_shared(newSize * sizeof(real)); + } + } + +private: + CpuMemHandlePtr memory_; +}; + +REGISTER_TYPED_FUNC(GemmConv, CPU, GemmConvFunction); + +} // namespace paddle -- GitLab From 688305f82da096aebcbe5881d17915145febe9ba Mon Sep 17 00:00:00 2001 From: Liu Yiqun Date: Fri, 26 May 2017 08:28:09 +0000 Subject: [PATCH 0467/3256] Centralize the use of sse and neon instrinsic. --- paddle/cuda/include/hl_cpu_scalar.cuh | 47 +++- paddle/cuda/include/hl_cpu_simd_neon.cuh | 52 ++++- paddle/cuda/include/hl_cpu_simd_sse.cuh | 99 +++++++- paddle/cuda/include/hl_matrix_base.cuh | 30 +-- paddle/cuda/include/hl_matrix_base_detail.cuh | 151 +++++++++++++ paddle/cuda/include/hl_matrix_base_neon.cuh | 161 ------------- paddle/cuda/include/hl_matrix_base_sse.cuh | 211 ------------------ paddle/cuda/include/hl_matrix_type.cuh | 18 +- 8 files changed, 342 insertions(+), 427 deletions(-) create mode 100644 paddle/cuda/include/hl_matrix_base_detail.cuh delete mode 100644 paddle/cuda/include/hl_matrix_base_neon.cuh delete mode 100644 paddle/cuda/include/hl_matrix_base_sse.cuh diff --git a/paddle/cuda/include/hl_cpu_scalar.cuh b/paddle/cuda/include/hl_cpu_scalar.cuh index c5e58015f31..cddf08ce6b6 100644 --- a/paddle/cuda/include/hl_cpu_scalar.cuh +++ b/paddle/cuda/include/hl_cpu_scalar.cuh @@ -15,25 +15,60 @@ limitations under the License. */ #ifndef HL_CPU_SCALAR_CUH_ #define HL_CPU_SCALAR_CUH_ +#define VECTOR_SIMD false +#define VECTOR_SET hl_vec_set + #ifndef PADDLE_TYPE_DOUBLE /* size of float */ -#define VECTOR_SIZE 4 +#define VECTOR_SIZE 4 #else /* size of double */ -#define VECTOR_SIZE 8 +#define VECTOR_SIZE 8 #endif typedef real vecType; -inline void set_zero(vecType &mm) { mm = (vecType) 0.0f; } - /* Consider a real as a vector */ -#define VECTOR_LEN 1 -#define VECTOR_SET set_zero +#define VECTOR_LEN 1 template inline real hl_agg_op(Agg agg, vecType mm) { return mm; } +INLINE real hl_vec_set(const real r) { + return r; +} + +INLINE real hl_vec_max(const real a, const real b) { + return a > b ? a : b; +} + +INLINE real hl_vec_min(const real a, const real b) { + return a > b ? b : a; +} + +INLINE real hl_vec_add(const real a, const real b) { + return a + b; +} + +INLINE real hl_vec_sub(const real a, const real b) { + return a - b; +} + +INLINE real hl_vec_mul(const real a, const real b) { + return a * b; +} + +INLINE real hl_vec_div(const real a, const real b) { + return a / b; +} + +INLINE real hl_vec_classification_error(const real a, + const real b, + const real p, + const real r) { + return ((a > p) == (b > p)) ? 0.0f : 1.0f; +} + #endif // HL_CPU_SCALAR_CUH_ diff --git a/paddle/cuda/include/hl_cpu_simd_neon.cuh b/paddle/cuda/include/hl_cpu_simd_neon.cuh index aaba35df091..9ff360c576f 100644 --- a/paddle/cuda/include/hl_cpu_simd_neon.cuh +++ b/paddle/cuda/include/hl_cpu_simd_neon.cuh @@ -17,15 +17,16 @@ limitations under the License. */ #include -#define VECTOR_SIZE 16 +#define VECTOR_SIMD true +#define VECTOR_SIZE 16 +#define VECTOR_SET hl_vec_set #ifndef PADDLE_TYPE_DOUBLE typedef float32x4_t vecType; /* number of float in vector */ -#define VECTOR_LEN 4 -#define VECTOR_SET vdupq_n_f32 +#define VECTOR_LEN 4 template inline real hl_agg_op(Agg agg, vecType mm) { @@ -39,19 +40,58 @@ inline real hl_agg_op(Agg agg, vecType mm) { return vgetq_lane_f32(ret, 0); } +inline float32x4_t hl_vec_set(const real f) { + return vdupq_n_f32(f); +} + +inline float32x4_t hl_vec_max(const float32x4_t a, const float32x4_t b) { + return vmaxq_f32(a, b); +} + +inline float32x4_t hl_vec_min(const float32x4_t a, const float32x4_t b) { + return vminq_f32(a, b); +} + +inline float32x4_t hl_vec_add(const float32x4_t a, const float32x4_t b) { + return vaddq_f32(a, b); +} + +inline float32x4_t hl_vec_sub(const float32x4_t a, const float32x4_t b) { + return vsubq_f32(a, b); +} + +inline float32x4_t hl_vec_mul(const float32x4_t a, const float32x4_t b) { + return vmulq_f32(a, b); +} + +inline float32x4_t hl_vec_div(const float32x4_t a, const float32x4_t b) { + float32x4_t tmp = vrecpeq_f32(b); + return vmulq_f32(a, tmp); +} + +inline float32x4_t hl_vec_classification_error(const float32x4_t a, + const float32x4_t b, + const float32x4_t p, + const float32x4_t r) { + uint32x4_t tmp1 = vcgtq_f32(a, p); + uint32x4_t tmp2 = vcgtq_f32(b, p); + uint32x4_t tmp3 = veorq_u32(tmp1, tmp2); + return vcvtq_f32_u32(vandq_u32(tmp3, vcvtq_u32_f32(r))); +} + #else #ifdef __aarch64__ typedef float64x2_t vecType; /* number of float in vector */ -#define VECTOR_LEN 2 -#define VECTOR_SET vdupq_n_f64 +#define VECTOR_LEN 2 +#define VECTOR_SET vdupq_n_f64 #error To be implemented #else #error NEON instructions does not support double precision -#endif +#endif // __aarch64__ #endif diff --git a/paddle/cuda/include/hl_cpu_simd_sse.cuh b/paddle/cuda/include/hl_cpu_simd_sse.cuh index 99286c1a3f0..9a918770b14 100644 --- a/paddle/cuda/include/hl_cpu_simd_sse.cuh +++ b/paddle/cuda/include/hl_cpu_simd_sse.cuh @@ -12,22 +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. */ -#ifndef HL_SIMD_SSE_CUH_ -#define HL_SIMD_SSE_CUH_ +#ifndef HL_CPU_SIMD_SSE_CUH_ +#define HL_CPU_SIMD_SSE_CUH_ #include #include #include -#define VECTOR_SIZE 16 +#define VECTOR_SIMD true +#define VECTOR_SIZE 16 +#define VECTOR_SET hl_vec_set #ifndef PADDLE_TYPE_DOUBLE typedef __m128 vecType; /* number of float in vector */ -#define VECTOR_LEN 4 -#define VECTOR_SET _mm_set_ps1 +#define VECTOR_LEN 4 template inline real hl_agg_op(Agg agg, vecType mm) { @@ -40,16 +41,50 @@ inline real hl_agg_op(Agg agg, vecType mm) { return _mm_cvtss_f32(ret); } +inline __m128 hl_vec_set(const real f) { + return _mm_set_ps1(f); +} + +inline __m128 hl_vec_max(const __m128 a, const __m128 b) { + return _mm_max_ps(a, b); +} + +inline __m128 hl_vec_min(const __m128 a, const __m128 b) { + return _mm_min_ps(a, b); +} + +inline __m128 hl_vec_add(const __m128 a, const __m128 b) { + return _mm_add_ps(a, b); +} + +inline __m128 hl_vec_sub(const __m128 a, const __m128 b) { + return _mm_sub_ps(a, b); +} + +inline __m128 hl_vec_mul(const __m128 a, const __m128 b) { + return _mm_mul_ps(a, b); +} + +inline __m128 hl_vec_div(const __m128 a, const __m128 b) { + return _mm_div_ps(a, b); +} + +inline __m128 hl_vec_classification_error(const __m128 a, + const __m128 b, + const __m128 p, + const __m128 r) { + __m128 tmp1 = _mm_cmpgt_ps(a, p); + __m128 tmp2 = _mm_cmpgt_ps(b, p); + __m128 tmp3 = _mm_xor_ps(tmp1, tmp2); + return _mm_and_ps(tmp3, r); +} + #else typedef __m128d vecType; /* number of double in vector */ -#define VECTOR_LEN 2 -#if defined(__APPLE__) || defined(__OSX__) -#define _mm_set_pd1 _mm_set1_pd -#endif -#define VECTOR_SET _mm_set_pd1 +#define VECTOR_LEN 2 template inline real hl_agg_op(Agg agg, vecType mm) { @@ -60,6 +95,48 @@ inline real hl_agg_op(Agg agg, vecType mm) { return _mm_cvtsd_f64(ret); } +inline __m128d hl_vec_set(const real d) { +#if defined(__APPLE__) || defined(__OSX__) + return _mm_set1_pd(d); +#else + return _mm_set_pd1(d); +#endif +} + +inline __m128d hl_vec_max(const __m128d a, const __m128d b) { + return _mm_max_pd(a, b); +} + +inline __m128d hl_vec_min(const __m128d a, const __m128d b) { + return _mm_min_pd(a, b); +} + +inline __m128d hl_vec_add(const __m128d a, const __m128d b) { + return _mm_add_pd(a, b); +} + +inline __m128d hl_vec_sub(const __m128d a, const __m128d b) { + return _mm_sub_pd(a, b); +} + +inline __m128d hl_vec_mul(const __m128d a, const __m128d b) { + return _mm_mul_pd(a, b); +} + +inline __m128d hl_vec_div(const __m128d a, const __m128d b) { + return _mm_div_pd(a, b); +} + +inline __m128d hl_vec_classification_error(const __m128d a, + const __m128d b, + const __m128d p, + const __m128d r) { + __m128d tmp1 = _mm_cmpgt_pd(a, p); + __m128d tmp2 = _mm_cmpgt_pd(b, p); + __m128d tmp3 = _mm_xor_pd(tmp1, tmp2); + return _mm_and_pd(tmp3, r); +} + #endif -#endif // HL_SIMD_SSE_CUH_ +#endif // HL_CPU_SIMD_SSE_CUH_ diff --git a/paddle/cuda/include/hl_matrix_base.cuh b/paddle/cuda/include/hl_matrix_base.cuh index 545120128b4..53fdb47ec9c 100644 --- a/paddle/cuda/include/hl_matrix_base.cuh +++ b/paddle/cuda/include/hl_matrix_base.cuh @@ -18,26 +18,6 @@ limitations under the License. */ #include "hl_matrix_type.cuh" -#ifdef __CUDA_ARCH__ -/** - * CUDA kernel inline function - */ -#define INLINE __device__ inline -#else -/** - * CPP inline function - */ -#define INLINE inline -#endif - -#ifndef PADDLE_TYPE_DOUBLE -#define DEVICE_FMAX fmaxf -#define DEVICE_FMIN fminf -#else -#define DEVICE_FMAX fmax -#define DEVICE_FMIN fmin -#endif - class BaseOp { public: static const bool sse = false; @@ -52,11 +32,7 @@ public: } }; -#if defined(__SSE3__) -#include "hl_matrix_base_sse.cuh" -#elif (defined(__ARM__NEON__) || defined(__ARM_NEON)) -#include "hl_matrix_base_neon.cuh" -#else +#ifdef __CUDA_ARCH__ typedef BaseOp SSESum; typedef BaseOp SSEMax; typedef BaseOp SSEMin; @@ -70,6 +46,8 @@ typedef BaseOp SSESquaredDiff; typedef BaseOp SSEFirst; typedef BaseOp SSESecond; typedef BaseOp SSEClassificationError; +#else +#include "hl_matrix_base_detail.cuh" #endif namespace aggregate { @@ -124,7 +102,7 @@ public: add2(const real s1, const real s2) : SSEAdd2(s1, s2), p1(s1), p2(s2) {} INLINE real operator()(const real a, const real b) const { - return p1 * a + p2 * b; + return p1 * a + p2 * b; } }; diff --git a/paddle/cuda/include/hl_matrix_base_detail.cuh b/paddle/cuda/include/hl_matrix_base_detail.cuh new file mode 100644 index 00000000000..50079ed53de --- /dev/null +++ b/paddle/cuda/include/hl_matrix_base_detail.cuh @@ -0,0 +1,151 @@ +/* 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. */ + +#ifndef HL_MATRIX_BASE_DETAIL_CUH_ +#define HL_MATRIX_BASE_DETAIL_CUH_ + +#include "hl_matrix_type.cuh" + +namespace aggregate { +class SSESum { +public: + static const bool sse = VECTOR_SIMD; + INLINE vecType vecOp(const vecType a, const vecType b) const { + return hl_vec_add(a, b); + } +}; + +class SSEMax { +public: + static const bool sse = VECTOR_SIMD; + INLINE vecType vecOp(const vecType a, const vecType b) const { + return hl_vec_max(a, b); + } +}; + +class SSEMin { +public: + static const bool sse = VECTOR_SIMD; + INLINE vecType vecOp(const vecType a, const vecType b) const { + return hl_vec_min(a, b); + } +}; +} // namespace aggregate + +namespace base { +namespace unary { +class SSEIdentity { +public: + static const bool sse = VECTOR_SIMD; + INLINE vecType vecOp(const vecType a) const { + return a; + } +}; +} // namespace unary + +namespace binary { +class SSEAdd { +public: + static const bool sse = VECTOR_SIMD; + INLINE vecType vecOp(const vecType a, const vecType b) const { + return hl_vec_add(a, b); + } +}; + +class SSEAdd2 { +public: + static const bool sse = VECTOR_SIMD; + const real p1; + const real p2; + vecType mp1; + vecType mp2; + +public: + SSEAdd2(const real s1, const real s2) : p1(s1), p2(s2) { + mp1 = hl_vec_set(p1); + mp2 = hl_vec_set(p2); + } + INLINE vecType vecOp(const vecType a, const vecType b) const { + return hl_vec_add(hl_vec_mul(mp1, a), hl_vec_mul(mp2, b)); + } +}; + +class SSESub { +public: + static const bool sse = VECTOR_SIMD; + INLINE vecType vecOp(const vecType a, const vecType b) const { + return hl_vec_sub(a, b); + } +}; + +class SSEMul { +public: + static const bool sse = VECTOR_SIMD; + INLINE vecType vecOp(const vecType a, const vecType b) const { + return hl_vec_mul(a, b); + } +}; + +class SSEDiv { +public: + static const bool sse = VECTOR_SIMD; + INLINE vecType vecOp(const vecType a, const vecType b) const { + return hl_vec_div(a, b); + } +}; + +class SSESquaredDiff { +public: + static const bool sse = VECTOR_SIMD; + INLINE vecType vecOp(const vecType a, const vecType b) const { + return hl_vec_mul(hl_vec_sub(a, b), hl_vec_sub(a, b)); + } +}; + +class SSEFirst { +public: + static const bool sse = VECTOR_SIMD; + INLINE vecType vecOp(const vecType a, const vecType b) const { + return a; + } +}; + +class SSESecond { +public: + static const bool sse = VECTOR_SIMD; + INLINE vecType vecOp(const vecType a, const vecType b) const { + return b; + } +}; + +class SSEClassificationError { +public: + static const bool sse = VECTOR_SIMD; + const real p; + vecType mp; + vecType result; + +public: + explicit SSEClassificationError(const real s) : p(s) { + mp = hl_vec_set(p); + result = hl_vec_set(1.0f); + } + INLINE vecType vecOp(const vecType a, const vecType b) const { + return hl_vec_classification_error(a, b, mp, result); + } +}; +} // namespace binary +} // namespace base + +#endif /* HL_MATRIX_BASE_DETAIL_CUH_ */ diff --git a/paddle/cuda/include/hl_matrix_base_neon.cuh b/paddle/cuda/include/hl_matrix_base_neon.cuh deleted file mode 100644 index e13019f5ee2..00000000000 --- a/paddle/cuda/include/hl_matrix_base_neon.cuh +++ /dev/null @@ -1,161 +0,0 @@ -/* 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. */ - - -#ifndef HL_MATRIX_BASE_NEON_CUH_ -#define HL_MATRIX_BASE_NEON_CUH_ - -namespace aggregate { -class SSESum { -public: - static const bool sse = true; - INLINE float32x4_t vecOp(const float32x4_t a, const float32x4_t b) const { - return vaddq_f32(a, b); - } -}; - -class SSEMax { -public: - static const bool sse = true; - INLINE float32x4_t vecOp(const float32x4_t a, const float32x4_t b) const { - return vmaxq_f32(a, b); - } -}; - -class SSEMin { -public: - static const bool sse = true; - INLINE float32x4_t vecOp(const float32x4_t a, const float32x4_t b) const { - return vminq_f32(a, b); - } -}; -} // namespace aggregate - -namespace base { -namespace unary { -class SSEIdentity { -public: - static const bool sse = true; - INLINE float32x4_t vecOp(const float32x4_t a) const { - return a; - } -}; -} // namespace unary - -namespace binary { -class SSEAdd { -public: - static const bool sse = true; - INLINE float32x4_t vecOp(const float32x4_t a, const float32x4_t b) const { - return vaddq_f32(a, b); - } -}; - -class SSEAdd2 { -public: - static const bool sse = true; - const real p1; - const real p2; - float32x4_t mp1; - float32x4_t mp2; - -public: - SSEAdd2(const real s1, const real s2) : p1(s1), p2(s2) { - mp1 = vdupq_n_f32(p1); - mp2 = vdupq_n_f32(p2); - } - INLINE float32x4_t vecOp(const float32x4_t a, const float32x4_t b) const { - float32x4_t tmp1, tmp2; - tmp1 = vmulq_f32(mp1, a); - tmp2 = vmulq_f32(mp2, b); - return vaddq_f32(tmp1, tmp2); - } -}; - -class SSESub { -public: - static const bool sse = true; - INLINE float32x4_t vecOp(const float32x4_t a, const float32x4_t b) const { - return vsubq_f32(a, b); - } -}; - -class SSEMul { -public: - static const bool sse = true; - INLINE float32x4_t vecOp(const float32x4_t a, const float32x4_t b) const { - return vmulq_f32(a, b); - } -}; - -class SSEDiv { -public: - static const bool sse = true; - INLINE float32x4_t vecOp(const float32x4_t a, const float32x4_t b) const { - float32x4_t tmp; - tmp = vrecpeq_f32(b); - return vmulq_f32(a, tmp); - } -}; - -class SSESquaredDiff { -public: - static const bool sse = true; - INLINE float32x4_t vecOp(const float32x4_t a, const float32x4_t b) const { - float32x4_t tmp; - tmp = vsubq_f32(a, b); - return vmulq_f32(tmp, tmp); - } -}; - -class SSEFirst { -public: - static const bool sse = true; - INLINE float32x4_t vecOp(const float32x4_t a, const float32x4_t b) const { - return a; - } -}; - -class SSESecond { -public: - static const bool sse = true; - INLINE float32x4_t vecOp(const float32x4_t a, const float32x4_t b) const { - return b; - } -}; - -class SSEClassificationError { -public: - static const bool sse = true; - const real p; - float32x4_t mp; - uint32x4_t result; - -public: - explicit SSEClassificationError(const real s) : p(s) { - mp = vdupq_n_f32(p); - result = vdupq_n_u32(1); - } - // TODO: to be check - INLINE float32x4_t vecOp(const float32x4_t a, const float32x4_t b) const { - uint32x4_t tmp1 = vcgtq_f32(a, mp); - uint32x4_t tmp2 = vcgtq_f32(b, mp); - uint32x4_t tmp3 = veorq_u32(tmp1, tmp2); - return vcvtq_f32_u32(vandq_u32(tmp3, result)); - } -}; -} // namespace binary -} // namespace base - -#endif /* HL_MATRIX_BASE_NEON_CUH_ */ diff --git a/paddle/cuda/include/hl_matrix_base_sse.cuh b/paddle/cuda/include/hl_matrix_base_sse.cuh deleted file mode 100644 index db6c9cca03a..00000000000 --- a/paddle/cuda/include/hl_matrix_base_sse.cuh +++ /dev/null @@ -1,211 +0,0 @@ -/* 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. */ - - -#ifndef HL_MATRIX_BASE_SSE_CUH_ -#define HL_MATRIX_BASE_SSE_CUH_ - -namespace aggregate { -class SSESum { -public: - static const bool sse = true; - INLINE __m128 vecOp(const __m128 a, const __m128 b) const { - return _mm_add_ps(a, b); - } - INLINE __m128d vecOp(const __m128d a, const __m128d b) const { - return _mm_add_pd(a, b); - } -}; - -class SSEMax { -public: - static const bool sse = true; - INLINE __m128 vecOp(const __m128 a, const __m128 b) const { - return _mm_max_ps(a, b); - } - INLINE __m128d vecOp(const __m128d a, const __m128d b) const { - return _mm_max_pd(a, b); - } -}; - -class SSEMin { -public: - static const bool sse = true; - INLINE __m128 vecOp(const __m128 a, const __m128 b) const { - return _mm_min_ps(a, b); - } - INLINE __m128d vecOp(const __m128d a, const __m128d b) const { - return _mm_min_pd(a, b); - } -}; -} // namespace aggregate - -namespace base { -namespace unary { -class SSEIdentity { -public: - static const bool sse = true; - INLINE __m128 vecOp(const __m128 a) const { - return a; - } - INLINE __m128d vecOp(const __m128d a) const { - return a; - } -}; -} // namespace unary - -namespace binary { -class SSEAdd { -public: - static const bool sse = true; - INLINE __m128 vecOp(const __m128 a, const __m128 b) const { - return _mm_add_ps(a, b); - } - INLINE __m128d vecOp(const __m128d a, const __m128d b) const { - return _mm_add_pd(a, b); - } -}; - -class SSEAdd2 { -public: - static const bool sse = true; - const real p1; - const real p2; - union {__m128 f; __m128d d;} mp1; - union {__m128 f; __m128d d;} mp2; - -public: - SSEAdd2(const real s1, const real s2) : p1(s1), p2(s2) { - if (sizeof(real) == sizeof(float)) { - mp1.f = _mm_set1_ps(p1); - mp2.f = _mm_set1_ps(p2); - } else { - mp1.d = _mm_set1_pd(p1); - mp2.d = _mm_set1_pd(p2); - } - } - INLINE __m128 vecOp(const __m128 a, const __m128 b) const { - __m128 tmp1, tmp2; - tmp1 = _mm_mul_ps(mp1.f, a); - tmp2 = _mm_mul_ps(mp2.f, b); - return _mm_add_ps(tmp1, tmp2); - } - INLINE __m128d vecOp(const __m128d a, const __m128d b) const { - __m128d tmp1, tmp2; - tmp1 = _mm_mul_pd(mp1.d, a); - tmp2 = _mm_mul_pd(mp2.d, b); - return _mm_add_pd(tmp1, tmp2); - } -}; - -class SSESub { -public: - static const bool sse = true; - INLINE __m128 vecOp(const __m128 a, const __m128 b) const { - return _mm_sub_ps(a, b); - } - INLINE __m128d vecOp(const __m128d a, const __m128d b) const { - return _mm_sub_pd(a, b); - } -}; - -class SSEMul { -public: - static const bool sse = true; - INLINE __m128 vecOp(const __m128 a, const __m128 b) const { - return _mm_mul_ps(a, b); - } - INLINE __m128d vecOp(const __m128d a, const __m128d b) const { - return _mm_mul_pd(a, b); - } -}; - -class SSEDiv { -public: - static const bool sse = true; - INLINE __m128 vecOp(const __m128 a, const __m128 b) const { - return _mm_div_ps(a, b); - } - INLINE __m128d vecOp(const __m128d a, const __m128d b) const { - return _mm_div_pd(a, b); - } -}; - -class SSESquaredDiff { -public: - static const bool sse = true; - INLINE __m128 vecOp(const __m128 a, const __m128 b) const { - return _mm_mul_ps(_mm_sub_ps(a, b), _mm_sub_ps(a, b)); - } - INLINE __m128d vecOp(const __m128d a, const __m128d b) const { - return _mm_mul_pd(_mm_sub_pd(a, b), _mm_sub_pd(a, b)); - } -}; - -class SSEFirst { -public: - static const bool sse = true; - INLINE __m128 vecOp(const __m128 a, const __m128 b) const { - return a; - } - INLINE __m128d vecOp(const __m128d a, const __m128d b) const { - return a; - } -}; - -class SSESecond { -public: - static const bool sse = true; - INLINE __m128 vecOp(const __m128 a, const __m128 b) const { - return b; - } - INLINE __m128d vecOp(const __m128d a, const __m128d b) const { - return b; - } -}; - -class SSEClassificationError { -public: - static const bool sse = true; - const real p; - union {__m128 f; __m128d d;} mp; - union {__m128 f; __m128d d;} result; - -public: - explicit SSEClassificationError(const real s) : p(s) { - if (sizeof(real) == sizeof(float)) { - mp.f = _mm_set1_ps(p); - result.f = _mm_set1_ps(1.0f); - } else { - mp.d = _mm_set1_pd(p); - result.d = _mm_set1_pd(1.0); - } - } - INLINE __m128 vecOp(const __m128 a, const __m128 b) const { - __m128 tmp1 = _mm_cmpgt_ps(a, mp.f); - __m128 tmp2 = _mm_cmpgt_ps(b, mp.f); - __m128 tmp3 = _mm_xor_ps(tmp1, tmp2); - return _mm_and_ps(tmp3, result.f); - } - INLINE __m128d vecOp(const __m128d a, const __m128d b) const { - __m128d tmp1 = _mm_cmpgt_pd(a, mp.d); - __m128d tmp2 = _mm_cmpgt_pd(b, mp.d); - __m128d tmp3 = _mm_xor_pd(tmp1, tmp2); - return _mm_and_pd(tmp3, result.d); - } -}; -} // namespace binary -} // namespace base - -#endif /* HL_MATRIX_BASE_SSE_CUH_ */ diff --git a/paddle/cuda/include/hl_matrix_type.cuh b/paddle/cuda/include/hl_matrix_type.cuh index 7d6face5f0e..2ced2fb1ab1 100644 --- a/paddle/cuda/include/hl_matrix_type.cuh +++ b/paddle/cuda/include/hl_matrix_type.cuh @@ -17,6 +17,18 @@ limitations under the License. */ #include "hl_base.h" +#ifdef __CUDA_ARCH__ +/** + * CUDA kernel inline function + */ +#define INLINE __device__ inline +#else +/** + * CPP inline function + */ +#define INLINE inline +#endif + #ifdef __CUDA_ARCH__ #include #ifndef PADDLE_TYPE_DOUBLE @@ -32,10 +44,4 @@ typedef double2 vecType; #include "hl_cpu_scalar.cuh" #endif -#ifdef __CUDA_ARCH__ -#define INLINE __device__ inline -#else -#define INLINE inline -#endif - #endif // HL_MATRIX_TYPE_CUH_ -- GitLab From 98239302204a1938c37d5f56a0b926fa050cbbb2 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Fri, 26 May 2017 17:40:10 +0800 Subject: [PATCH 0468/3256] Fix add libn_any to dependencies * It will cause build error when concurrent is large. --- cmake/external/any.cmake | 1 + 1 file changed, 1 insertion(+) diff --git a/cmake/external/any.cmake b/cmake/external/any.cmake index 8116f235d53..62eea42692b 100644 --- a/cmake/external/any.cmake +++ b/cmake/external/any.cmake @@ -18,3 +18,4 @@ ExternalProject_Add( ) add_definitions(-DANY_IMPL_ANY_CAST_MOVEABLE) +LIST(APPEND external_project_dependencies linb_any) \ No newline at end of file -- GitLab From ff8d2617779ac86ef1ce2515d30bf92f4c205a10 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Fri, 26 May 2017 18:12:31 +0800 Subject: [PATCH 0469/3256] Fix baidu gcc toolchain compile --- paddle/utils/arch/linux/Locks.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/paddle/utils/arch/linux/Locks.cpp b/paddle/utils/arch/linux/Locks.cpp index 310c9a65425..3a0903d1f26 100644 --- a/paddle/utils/arch/linux/Locks.cpp +++ b/paddle/utils/arch/linux/Locks.cpp @@ -55,8 +55,11 @@ public: }; #else - +// clang-format off +#include #include +// clang-format on + class SpinLockPrivate { public: inline void lock() { -- GitLab From 4ed5f7dbb994f19355eb861966fef6e9760c0fce Mon Sep 17 00:00:00 2001 From: Dong Li Date: Fri, 26 May 2017 04:38:16 -0700 Subject: [PATCH 0470/3256] Disable neon when enable gpu. --- paddle/cuda/include/hl_matrix_type.cuh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paddle/cuda/include/hl_matrix_type.cuh b/paddle/cuda/include/hl_matrix_type.cuh index 2ced2fb1ab1..77f73167fe6 100644 --- a/paddle/cuda/include/hl_matrix_type.cuh +++ b/paddle/cuda/include/hl_matrix_type.cuh @@ -38,7 +38,7 @@ typedef double2 vecType; #endif #elif defined(__SSE3__) #include "hl_cpu_simd_sse.cuh" -#elif defined(__ARM_NEON) || defined(__ARM_NEON__) +#elif (defined(__ARM_NEON) || defined(__ARM_NEON__)) && !defined(__NVCC__) #include "hl_cpu_simd_neon.cuh" #else #include "hl_cpu_scalar.cuh" -- GitLab From 1497e2ba58c453f9abf254f07d4727459f77502d Mon Sep 17 00:00:00 2001 From: Liu Yiqun Date: Fri, 26 May 2017 19:55:42 +0800 Subject: [PATCH 0471/3256] Fix typo when set openblas compiling flags. --- cmake/external/openblas.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/external/openblas.cmake b/cmake/external/openblas.cmake index b6bd24fe8ae..836a8b06f89 100644 --- a/cmake/external/openblas.cmake +++ b/cmake/external/openblas.cmake @@ -37,7 +37,7 @@ IF(NOT ${CBLAS_FOUND}) SET(OPTIONAL_ARGS HOSTCC=${HOST_C_COMPILER} TARGET=ARMV7 USE_THREAD=0 libs) ELSE() SET(OPENBLAS_COMMIT "v0.2.19") - SET(OPENBLAS_ARGS DYNAMIC_ARCH=1 libs) + SET(OPTIONAL_ARGS DYNAMIC_ARCH=1 libs) ENDIF() ExternalProject_Add( -- GitLab From 1879332a30e0d403a0a36c0f0b834c0b8070cef4 Mon Sep 17 00:00:00 2001 From: hedaoyuan Date: Fri, 26 May 2017 20:01:04 +0800 Subject: [PATCH 0472/3256] Modify FunctionCompare to Compare2Function to support comparison of two CPU functions. --- paddle/function/ContextProjectionOpTest.cpp | 4 +- paddle/function/CosSimOpTest.cpp | 4 +- paddle/function/CrossMapNormalOpTest.cpp | 20 +- paddle/function/FunctionTest.h | 278 ++++++++++++-------- paddle/function/MulOpTest.cpp | 14 +- paddle/function/PadOpTest.cpp | 2 +- 6 files changed, 187 insertions(+), 135 deletions(-) diff --git a/paddle/function/ContextProjectionOpTest.cpp b/paddle/function/ContextProjectionOpTest.cpp index 1b25172ca5c..9e9dd20e6f3 100644 --- a/paddle/function/ContextProjectionOpTest.cpp +++ b/paddle/function/ContextProjectionOpTest.cpp @@ -28,7 +28,7 @@ void testMatrixProjectionForward(int context_start, std::max(0, (int)(context_start + context_length - 1)); if (pad == 0) is_padding = false; - FunctionCompare test( + CpuGpuFuncCompare test( "ContextProjectionForward", FuncConfig() .set("context_length", context_length) @@ -60,7 +60,7 @@ void testMatrixProjectionBackward(int context_start, std::max(0, (int)(context_start + context_length - 1)); if (pad == 0) is_padding = false; - FunctionCompare test( + CpuGpuFuncCompare test( "ContextProjectionBackward", FuncConfig() .set("context_length", context_length) diff --git a/paddle/function/CosSimOpTest.cpp b/paddle/function/CosSimOpTest.cpp index 48c815f0271..f6c0041101f 100644 --- a/paddle/function/CosSimOpTest.cpp +++ b/paddle/function/CosSimOpTest.cpp @@ -22,7 +22,7 @@ void testCosSimForward(size_t height_x, size_t height_y, size_t width, real scale) { - FunctionCompare test("CosSimForward", FuncConfig().set("scale", scale)); + CpuGpuFuncCompare test("CosSimForward", FuncConfig().set("scale", scale)); // prepare input arguments test.addInputs(BufferArg(VALUE_TYPE_FLOAT, TensorShape{height_x, width})); test.addInputs(BufferArg(VALUE_TYPE_FLOAT, TensorShape{height_y, width})); @@ -36,7 +36,7 @@ void testCosSimBackward(size_t height_x, size_t height_y, size_t width, real scale) { - FunctionCompare test("CosSimBackward", FuncConfig().set("scale", scale)); + CpuGpuFuncCompare test("CosSimBackward", FuncConfig().set("scale", scale)); // prepare input arguments test.addInputs(BufferArg(VALUE_TYPE_FLOAT, TensorShape{height_x, 1})); test.addInputs(BufferArg(VALUE_TYPE_FLOAT, TensorShape{height_x, 1})); diff --git a/paddle/function/CrossMapNormalOpTest.cpp b/paddle/function/CrossMapNormalOpTest.cpp index 51f5da81bfc..ed17b17da61 100644 --- a/paddle/function/CrossMapNormalOpTest.cpp +++ b/paddle/function/CrossMapNormalOpTest.cpp @@ -28,11 +28,11 @@ TEST(CrossMapNormal, real) { << " size=" << size; // init Test object - FunctionCompare test("CrossMapNormal", - FuncConfig() - .set("size", size) - .set("scale", (real)1.5) - .set("pow", (real)0.5)); + CpuGpuFuncCompare test("CrossMapNormal", + FuncConfig() + .set("size", size) + .set("scale", (real)1.5) + .set("pow", (real)0.5)); // prepare input arguments TensorShape shape{numSamples, channels, imgSizeH, imgSizeW}; test.addInputs(BufferArg(VALUE_TYPE_FLOAT, shape)); @@ -57,11 +57,11 @@ TEST(CrossMapNormalGrad, real) { << " imgSizeH=" << imgSizeH << " imgSizeW=" << imgSizeW << " size=" << size; - FunctionCompare test("CrossMapNormalGrad", - FuncConfig() - .set("size", size) - .set("scale", (real)1.5) - .set("pow", (real)0.5)); + CpuGpuFuncCompare test("CrossMapNormalGrad", + FuncConfig() + .set("size", size) + .set("scale", (real)1.5) + .set("pow", (real)0.5)); TensorShape shape{numSamples, channels, imgSizeH, imgSizeW}; test.addInputs(BufferArg(VALUE_TYPE_FLOAT, shape)); test.addInputs(BufferArg(VALUE_TYPE_FLOAT, shape)); diff --git a/paddle/function/FunctionTest.h b/paddle/function/FunctionTest.h index 0cfafdb27f5..69ab33052da 100644 --- a/paddle/function/FunctionTest.h +++ b/paddle/function/FunctionTest.h @@ -22,14 +22,62 @@ namespace paddle { typedef std::shared_ptr BufferArgPtr; +namespace test { +template +struct Allocator; + +template <> +struct Allocator { + using type = CpuMemoryHandle; +}; + +template <> +struct Allocator { + using type = GpuMemoryHandle; +}; + +// Copy argument1 to argument2 +template +class CopyArgument { +public: + void operator()(const BufferArg& arg1, BufferArg& arg2) { + CHECK_EQ(arg1.valueType(), arg2.valueType()); + CHECK_LE(arg1.shape().getElements(), arg2.shape().getElements()); + + if (arg1.valueType() == VALUE_TYPE_INT32) { + IVectorPtr vector1 = + IVector::create((int*)arg1.data(), + arg1.shape().getElements(), + DType1 == DEVICE_TYPE_CPU ? false : true); + IVectorPtr vector2 = + IVector::create((int*)arg2.data(), + arg2.shape().getElements(), + DType2 == DEVICE_TYPE_CPU ? false : true); + vector2->copyFrom(*vector1); + } else { + VectorPtr vector1 = + Vector::create((real*)arg1.data(), + arg1.shape().getElements(), + DType1 == DEVICE_TYPE_CPU ? false : true); + VectorPtr vector2 = + Vector::create((real*)arg2.data(), + arg2.shape().getElements(), + DType2 == DEVICE_TYPE_CPU ? false : true); + vector2->copyFrom(*vector1); + } + } +}; +} // namespace test + /** - * \brief A class for comparing CPU and GPU implementations of Function. - * + * \brief A class for comparing two Functions of different implementations. + * For example, can be used to compare the CPU and GPU implementation + * of the function is consistent. * * Use case: * // Initializes a test object, the corresponding cpu and gpu Function * // are constructed according to FunctionName and FuncConfig. - * FunctionCompare test(FunctionName, FuncConfig); + * CpuGpuFuncCompare test(FunctionName, FuncConfig); * // Prepare inputs and outputs arguments. * // Here the input and output can not contain real data, * // only contains the argument type and shape. @@ -45,28 +93,38 @@ typedef std::shared_ptr BufferArgPtr; * // Compares CPU and GPU calculation results for consistency. * test.run(); */ -class FunctionCompare { +template +class Compare2Function { public: - FunctionCompare(const std::string& name, const FuncConfig& config) - : cpuFunc_(FunctionBase::funcRegistrar_.createByType(name + "-CPU")), - gpuFunc_(FunctionBase::funcRegistrar_.createByType(name + "-GPU")) { - cpuFunc_->init(config); - gpuFunc_->init(config); + typedef typename test::Allocator::type Allocator1; + typedef typename test::Allocator::type Allocator2; + typedef typename Tensor::Vector Vector1; + typedef typename Tensor::Vector Vector2; + typedef typename Tensor::SparseMatrix SparseMatrix1; + typedef typename Tensor::SparseMatrix SparseMatrix2; + + Compare2Function(const std::string& name1, + const std::string& name2, + const FuncConfig& config) + : function1_(FunctionBase::funcRegistrar_.createByType(name1)), + function2_(FunctionBase::funcRegistrar_.createByType(name2)) { + function1_->init(config); + function2_->init(config); } - ~FunctionCompare() {} + ~Compare2Function() {} // input need only contains shape, do not contains data. void addInputs(const BufferArg& input) { size_t size = input.shape().getElements() * sizeOfValuType(input.valueType()); - cpuMemory_.emplace_back(std::make_shared(size)); - gpuMemory_.emplace_back(std::make_shared(size)); + func1Memory_.emplace_back(std::make_shared(size)); + func2Memory_.emplace_back(std::make_shared(size)); - cpuInputs_.emplace_back(std::make_shared( - cpuMemory_.back()->getBuf(), input.valueType(), input.shape())); - gpuInputs_.emplace_back(std::make_shared( - gpuMemory_.back()->getBuf(), input.valueType(), input.shape())); + func1Inputs_.emplace_back(std::make_shared( + func1Memory_.back()->getBuf(), input.valueType(), input.shape())); + func2Inputs_.emplace_back(std::make_shared( + func2Memory_.back()->getBuf(), input.valueType(), input.shape())); } // assume one copy of sequence is shared by different SequenceArgs @@ -75,62 +133,57 @@ public: size_t batchSize = input.shape()[0]; size_t numSeqs = batchSize / 10 + 1; size_t sizeId = (numSeqs + 1) * sizeOfValuType(VALUE_TYPE_INT32); - cpuMemory_.emplace_back(std::make_shared(sizeId)); - gpuMemory_.emplace_back(std::make_shared(sizeId)); - cpuSeq_ = std::make_shared(cpuMemory_.back()->getBuf(), - TensorShape{numSeqs + 1}); - gpuSeq_ = std::make_shared(gpuMemory_.back()->getBuf(), - TensorShape{numSeqs + 1}); + func1Memory_.emplace_back(std::make_shared(sizeId)); + func2Memory_.emplace_back(std::make_shared(sizeId)); + seq1_ = std::make_shared(func1Memory_.back()->getBuf(), + TensorShape{numSeqs + 1}); + seq2_ = std::make_shared(func2Memory_.back()->getBuf(), + TensorShape{numSeqs + 1}); /// init sequence Id - initArg(*cpuSeq_, batchSize); + initArg(*seq1_, batchSize); - // todo(tianbing), delete it - CHECK_EQ(cpuSeq_->shape().getElements(), cpuSeq_->numSeqs() + 1); - - CpuIVector cpuSeq(cpuSeq_->shape().getElements(), (int*)cpuSeq_->data()); - GpuIVector gpuSeq(gpuSeq_->shape().getElements(), (int*)gpuSeq_->data()); - gpuSeq.copyFrom(cpuSeq); + copyArg_(*seq1_, *seq2_); } void addInputs(const SequenceArg& input) { CHECK_EQ(input.shape().ndims(), 2UL); size_t batchSize = input.shape()[0]; - if (!cpuSeq_ || !gpuSeq_) { // sequence not exist + if (!seq1_ || !seq2_) { // sequence not exist addSequence(SequenceIdArg(TensorShape{batchSize})); } size_t size = input.shape().getElements() * sizeOfValuType(input.valueType()); - cpuMemory_.emplace_back(std::make_shared(size)); - gpuMemory_.emplace_back(std::make_shared(size)); + func1Memory_.emplace_back(std::make_shared(size)); + func2Memory_.emplace_back(std::make_shared(size)); /// SequenceArg - cpuInputs_.emplace_back( - std::make_shared(cpuMemory_.back()->getBuf(), + func1Inputs_.emplace_back( + std::make_shared(func1Memory_.back()->getBuf(), input.valueType(), input.shape(), - *cpuSeq_)); - gpuInputs_.emplace_back( - std::make_shared(gpuMemory_.back()->getBuf(), + *seq1_)); + func2Inputs_.emplace_back( + std::make_shared(func2Memory_.back()->getBuf(), input.valueType(), input.shape(), - *gpuSeq_)); + *seq2_)); } // output need only contains shape, do not contains data. void addOutputs(const BufferArg& output, ArgType argType = ASSIGN_TO) { size_t size = output.shape().getElements() * sizeOfValuType(output.valueType()); - cpuMemory_.emplace_back(std::make_shared(size)); - gpuMemory_.emplace_back(std::make_shared(size)); + func1Memory_.emplace_back(std::make_shared(size)); + func2Memory_.emplace_back(std::make_shared(size)); - cpuOutputs_.emplace_back( - std::make_shared(cpuMemory_.back()->getBuf(), + func1Outputs_.emplace_back( + std::make_shared(func1Memory_.back()->getBuf(), output.valueType(), output.shape(), argType)); - gpuOutputs_.emplace_back( - std::make_shared(gpuMemory_.back()->getBuf(), + func2Outputs_.emplace_back( + std::make_shared(func2Memory_.back()->getBuf(), output.valueType(), output.shape(), argType)); @@ -138,14 +191,14 @@ public: /// add and init output sparse matrix void addOutputs(const SparseMatrixArg& output, ArgType argType = ASSIGN_TO) { - cpuSparse_ = std::make_shared( + sparse1_ = std::make_shared( output.shape()[0], output.shape()[1], output.nnz(), static_cast(output.dataType()), static_cast(output.dataFormat())); - gpuSparse_ = std::make_shared( + sparse2_ = std::make_shared( output.shape()[0], output.shape()[1], output.nnz(), @@ -154,52 +207,52 @@ public: /// init sparse matrix hl_stream_t stream(HPPL_STREAM_1); - cpuSparse_->randomizeUniform(); - gpuSparse_->copyFrom(*cpuSparse_, stream); + sparse1_->randomizeUniform(); + sparse2_->copyFrom(*sparse1_, stream); hl_stream_synchronize(stream); - cpuOutputs_.emplace_back( - std::make_shared(*cpuSparse_, argType)); - gpuOutputs_.emplace_back( - std::make_shared(*gpuSparse_, argType)); + func1Outputs_.emplace_back( + std::make_shared(*sparse1_, argType)); + func2Outputs_.emplace_back( + std::make_shared(*sparse2_, argType)); } void addOutputs(const SequenceArg& output, ArgType argType = ASSIGN_TO) { CHECK_EQ(output.shape().ndims(), 2UL); size_t batchSize = output.shape()[0]; - if (!cpuSeq_ || !gpuSeq_) { // sequence not exist + if (!seq1_ || !seq2_) { // sequence not exist addSequence(SequenceIdArg(TensorShape{batchSize})); } size_t size = output.shape().getElements() * sizeOfValuType(output.valueType()); - cpuMemory_.emplace_back(std::make_shared(size)); - gpuMemory_.emplace_back(std::make_shared(size)); + func1Memory_.emplace_back(std::make_shared(size)); + func2Memory_.emplace_back(std::make_shared(size)); /// SequenceArg - cpuOutputs_.emplace_back( - std::make_shared(cpuMemory_.back()->getBuf(), + func1Outputs_.emplace_back( + std::make_shared(func1Memory_.back()->getBuf(), output.valueType(), output.shape(), - *cpuSeq_, + *seq1_, argType)); - gpuOutputs_.emplace_back( - std::make_shared(gpuMemory_.back()->getBuf(), + func2Outputs_.emplace_back( + std::make_shared(func2Memory_.back()->getBuf(), output.valueType(), output.shape(), - *gpuSeq_, + *seq2_, argType)); } void addInputs(const SparseMatrixArg& input) { - cpuSparse_ = std::make_shared( + sparse1_ = std::make_shared( input.shape()[0], input.shape()[1], input.nnz(), static_cast(input.dataType()), static_cast(input.dataFormat())); - gpuSparse_ = std::make_shared( + sparse2_ = std::make_shared( input.shape()[0], input.shape()[1], input.nnz(), @@ -208,12 +261,12 @@ public: /// init sparse matrix hl_stream_t stream(HPPL_STREAM_1); - cpuSparse_->randomizeUniform(); - gpuSparse_->copyFrom(*cpuSparse_, stream); + sparse1_->randomizeUniform(); + sparse2_->copyFrom(*sparse1_, stream); hl_stream_synchronize(stream); - cpuInputs_.emplace_back(std::make_shared(*cpuSparse_)); - gpuInputs_.emplace_back(std::make_shared(*gpuSparse_)); + func1Inputs_.emplace_back(std::make_shared(*sparse1_)); + func2Inputs_.emplace_back(std::make_shared(*sparse2_)); } void run() { @@ -236,27 +289,27 @@ public: function->calc(inArgs, outArgs); }; - callFunction(cpuFunc_.get(), cpuInputs_, cpuOutputs_); - callFunction(gpuFunc_.get(), gpuInputs_, gpuOutputs_); + callFunction(function1_.get(), func1Inputs_, func1Outputs_); + callFunction(function2_.get(), func2Inputs_, func2Outputs_); // check outputs compareOutputs(); } - std::shared_ptr getCpuFunction() const { return cpuFunc_; } + std::shared_ptr getCpuFunction() const { return function1_; } - std::shared_ptr getGpuFunction() const { return gpuFunc_; } + std::shared_ptr getGpuFunction() const { return function2_; } protected: // only init cpu argument, gpu argument copy from cpu argument. void initArg(BufferArg& arg) { - CpuVector vector(arg.shape().getElements(), (real*)arg.data()); + Vector1 vector(arg.shape().getElements(), (real*)arg.data()); vector.uniform(0.001, 1); } void initArg(SequenceArg& arg) { /// init only matrix - CpuVector vector(arg.shape().getElements(), (real*)arg.data()); + Vector1 vector(arg.shape().getElements(), (real*)arg.data()); vector.uniform(0.001, 1); } @@ -276,73 +329,72 @@ protected: } void initInputs() { - for (size_t i = 0; i < cpuInputs_.size(); i++) { - if (cpuInputs_[i]->isSparseArg()) { + for (size_t i = 0; i < func1Inputs_.size(); i++) { + if (func1Inputs_[i]->isSparseArg()) { continue; /// sparse matrix already init } - if (cpuInputs_[i]->isSequenceArg()) { - initArg(dynamic_cast(*cpuInputs_[i])); + if (func1Inputs_[i]->isSequenceArg()) { + initArg(dynamic_cast(*func1Inputs_[i])); } else { - initArg(*cpuInputs_[i]); + initArg(*func1Inputs_[i]); } - // TODO: Need a BufferCopy used to copy from one BufferArg to another. - CpuVector cpuVector(cpuInputs_[i]->shape().getElements(), - (real*)cpuInputs_[i]->data()); - GpuVector gpuVector(gpuInputs_[i]->shape().getElements(), - (real*)gpuInputs_[i]->data()); - gpuVector.copyFrom(cpuVector); + copyArg_(*func1Inputs_[i], *func2Inputs_[i]); } } void initOutputs() { - for (size_t i = 0; i < cpuOutputs_.size(); i++) { - if (cpuOutputs_[i]->isSparseArg()) { + for (size_t i = 0; i < func1Outputs_.size(); i++) { + if (func1Outputs_[i]->isSparseArg()) { continue; /// sparse matrix already init } - if (cpuOutputs_[i]->isSequenceArg()) { - initArg(dynamic_cast(*cpuOutputs_[i])); + if (func1Outputs_[i]->isSequenceArg()) { + initArg(dynamic_cast(*func1Outputs_[i])); } else { - initArg(*cpuOutputs_[i]); + initArg(*func1Outputs_[i]); } - // TODO: Need a BufferCopy used to copy from one BufferArg to another. - CpuVector cpuVector(cpuOutputs_[i]->shape().getElements(), - (real*)cpuOutputs_[i]->data()); - GpuVector gpuVector(gpuOutputs_[i]->shape().getElements(), - (real*)gpuOutputs_[i]->data()); - - gpuVector.copyFrom(cpuVector); + copyArg_(*func1Outputs_[i], *func2Outputs_[i]); } } void compareOutputs() { - for (size_t i = 0; i < cpuOutputs_.size(); i++) { + for (size_t i = 0; i < func1Outputs_.size(); i++) { // TODO, Need a BufferCheck used to compare the two buffers. - const auto cpu = cpuOutputs_[i]; - const auto gpu = gpuOutputs_[i]; + const auto cpu = func1Outputs_[i]; + const auto gpu = func2Outputs_[i]; CHECK_EQ(cpu->numElements(), gpu->numElements()); - CpuVector cpuVector(cpu->numElements(), (real*)cpu->data()); - GpuVector gpuVector(gpu->numElements(), (real*)gpu->data()); + Vector1 cpuVector(cpu->numElements(), (real*)cpu->data()); + Vector2 gpuVector(gpu->numElements(), (real*)gpu->data()); autotest::TensorCheckErr(cpuVector, gpuVector); } } protected: - std::shared_ptr cpuFunc_; - std::shared_ptr gpuFunc_; - std::vector cpuMemory_; - std::vector gpuMemory_; - std::vector cpuInputs_; - std::vector cpuOutputs_; - std::vector gpuInputs_; - std::vector gpuOutputs_; - std::shared_ptr cpuSparse_; - std::shared_ptr gpuSparse_; - std::shared_ptr cpuSeq_; - std::shared_ptr gpuSeq_; + std::shared_ptr function1_; + std::shared_ptr function2_; + std::vector> func1Memory_; + std::vector> func2Memory_; + std::vector func1Inputs_; + std::vector func1Outputs_; + std::vector func2Inputs_; + std::vector func2Outputs_; + std::shared_ptr sparse1_; + std::shared_ptr sparse2_; + std::shared_ptr seq1_; + std::shared_ptr seq2_; + test::CopyArgument copyArg_; +}; + +class CpuGpuFuncCompare + : public Compare2Function { +public: + CpuGpuFuncCompare(const std::string& name, const FuncConfig& config) + : Compare2Function(name + "-CPU", name + "-GPU", config) {} + + ~CpuGpuFuncCompare() {} }; } // namespace paddle diff --git a/paddle/function/MulOpTest.cpp b/paddle/function/MulOpTest.cpp index 8753057ebf7..d31eb0c74f2 100644 --- a/paddle/function/MulOpTest.cpp +++ b/paddle/function/MulOpTest.cpp @@ -35,7 +35,7 @@ void testFuncDDDMatrix( size_t heightC = dimM; size_t widthC = dimN; // init Test object - FunctionCompare test( + CpuGpuFuncCompare test( "MulOp", FuncConfig().set("aTrans", transa).set("bTrans", transb)); // prepare input arguments /// matrix A : HA * WA @@ -81,8 +81,8 @@ void testFuncDSparseDMatrix( size_t dimM, size_t dimN, size_t dimK, size_t nnz, SparseFormat FORMAT) { real scaleT = 1.0; // init Test object - FunctionCompare test("MulOp", - FuncConfig().set("aTrans", false).set("bTrans", false)); + CpuGpuFuncCompare test( + "MulOp", FuncConfig().set("aTrans", false).set("bTrans", false)); // prepare input arguments /// sparse matrix A : M * K test.addInputs(SparseMatrixArg( @@ -126,8 +126,8 @@ void testFuncDDSparseMatrix( size_t dimM, size_t dimN, size_t dimK, size_t nnz, SparseFormat FORMAT) { real scaleT = 1.0; // init Test object - FunctionCompare test("MulOp", - FuncConfig().set("aTrans", false).set("bTrans", false)); + CpuGpuFuncCompare test( + "MulOp", FuncConfig().set("aTrans", false).set("bTrans", false)); // prepare input arguments /// matrix A : M * K test.addInputs(BufferArg(VALUE_TYPE_FLOAT, TensorShape{dimM, dimK})); @@ -172,8 +172,8 @@ void testFuncSparseDDMatrix( size_t dimM, size_t dimN, size_t dimK, size_t nnz, SparseFormat FORMAT) { real scaleT = 1.0; // init Test object - FunctionCompare test("MulOp", - FuncConfig().set("aTrans", false).set("bTrans", false)); + CpuGpuFuncCompare test( + "MulOp", FuncConfig().set("aTrans", false).set("bTrans", false)); // prepare input arguments /// matrix A : M * K test.addInputs(BufferArg(VALUE_TYPE_FLOAT, TensorShape{dimM, dimK})); diff --git a/paddle/function/PadOpTest.cpp b/paddle/function/PadOpTest.cpp index f77ac2a8c49..e286f4e5b8a 100644 --- a/paddle/function/PadOpTest.cpp +++ b/paddle/function/PadOpTest.cpp @@ -25,7 +25,7 @@ TEST(Pad, real) { VLOG(3) << " numSamples=" << numSamples << " channels=" << channels << " imgSizeH=" << imgSizeH << " imgSizeW=" << imgSizeW; for (bool test_grad : {false, true}) { - FunctionCompare compare( + CpuGpuFuncCompare compare( test_grad ? "PadGrad" : "Pad", FuncConfig() .set>("channel", {2, 3}) -- GitLab From 455888c50964f86f6f434380ae9d17d7e7f4a454 Mon Sep 17 00:00:00 2001 From: hedaoyuan Date: Fri, 26 May 2017 20:09:11 +0800 Subject: [PATCH 0473/3256] Add ConvOpTest for NaiveConv and GemmConv --- paddle/function/CMakeLists.txt | 4 +- paddle/function/ConvOpTest.cpp | 82 ++++++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 paddle/function/ConvOpTest.cpp diff --git a/paddle/function/CMakeLists.txt b/paddle/function/CMakeLists.txt index 233a53709a8..472a43fa485 100644 --- a/paddle/function/CMakeLists.txt +++ b/paddle/function/CMakeLists.txt @@ -14,8 +14,8 @@ add_library(paddle_function STATIC ${cpp_files} ${cu_objs}) add_dependencies(paddle_function ${external_project_dependencies}) add_dependencies(paddle_function gen_proto_cpp) -if(WITH_GPU) if(WITH_TESTING) +if(WITH_GPU) # TODO: # file(GLOB test_files . *OpTest.cpp) # add_executable(${test_bin} EXCLUDE_FROM_ALL ${test_files}) @@ -29,6 +29,8 @@ if(WITH_TESTING) add_simple_unittest(MulOpTest) add_simple_unittest(CosSimOpTest) endif() + +add_simple_unittest(ConvOpTest) endif() add_style_check_target(paddle_function ${h_files}) diff --git a/paddle/function/ConvOpTest.cpp b/paddle/function/ConvOpTest.cpp new file mode 100644 index 00000000000..715fa58b559 --- /dev/null +++ b/paddle/function/ConvOpTest.cpp @@ -0,0 +1,82 @@ +/* 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 "Function.h" +#include "FunctionTest.h" + +namespace paddle { + +typedef Compare2Function Compare2CpuFunction; + +class ConvolutionTest { +public: + ConvolutionTest(const std::string& conv1, + const std::string& conv2, + std::string algo = "auto") { + for (size_t batchSize : {1, 32}) { + for (size_t inputSize : {7, 14, 54}) { + for (size_t filterSize : {1, 3, 5}) { + for (size_t inputChannels : {3, 64}) { + for (size_t outputChannels : {3, 64, 128}) { + if (inputChannels < outputChannels) break; + for (size_t stride : {1, 2}) { + for (size_t padding : {0, 1}) { + if (padding >= filterSize) break; + size_t outputSize = + (inputSize - filterSize + 2 * padding + stride) / stride; + LOG(INFO) << " batchSize=" << batchSize + << " inputChannels=" << inputChannels + << " inputHeight=" << inputSize + << " inputWidth=" << inputSize + << " outputChannels=" << outputChannels + << " filterHeight=" << filterSize + << " filterWidth=" << filterSize + << " outputHeight=" << outputSize + << " outputWidth=" << outputSize + << " stride=" << stride << " padding=" << padding; + + Compare2CpuFunction test(conv1, + conv2, + FuncConfig() + .set("padding", padding) + .set("stride", stride) + .set("algo", algo)); + + TensorShape shape0{ + batchSize, inputChannels, inputSize, inputSize}; + TensorShape shape1{ + outputChannels, inputChannels, filterSize, filterSize}; + TensorShape shape2{ + batchSize, outputChannels, outputSize, outputSize}; + test.addInputs(BufferArg(VALUE_TYPE_FLOAT, shape0)); + test.addInputs(BufferArg(VALUE_TYPE_FLOAT, shape1)); + test.addOutputs(BufferArg(VALUE_TYPE_FLOAT, shape2)); + test.run(); + } + } + } + } + } + } + } + } +}; + +TEST(Convolution, GEMM) { + ConvolutionTest test("NaiveConv-CPU", "GemmConv-CPU"); +} + +} // namespace paddle -- GitLab From 7d0355cdd060149ba466920521c2d9aed321bb23 Mon Sep 17 00:00:00 2001 From: xuwei06 Date: Fri, 26 May 2017 02:29:58 -0700 Subject: [PATCH 0474/3256] Fix V2 API --- paddle/parameter/Parameter.h | 1 + python/paddle/trainer/config_parser.py | 31 +- .../config_parser_utils.py | 22 +- .../paddle/trainer_config_helpers/layers.py | 6 + python/paddle/v2/evaluator.py | 14 +- python/paddle/v2/layer.py | 746 +++++------------- python/paddle/v2/networks.py | 15 +- python/paddle/v2/tests/test_layer.py | 18 +- python/paddle/v2/tests/test_rnn_layer.py | 7 + python/paddle/v2/tests/test_topology.py | 12 +- python/paddle/v2/topology.py | 29 +- 11 files changed, 278 insertions(+), 623 deletions(-) diff --git a/paddle/parameter/Parameter.h b/paddle/parameter/Parameter.h index d77486ce42e..0bac76f068e 100644 --- a/paddle/parameter/Parameter.h +++ b/paddle/parameter/Parameter.h @@ -324,6 +324,7 @@ protected: std::vector> updaterHooks_; public: + void setSharedCount(int cnt) { sharedCount_ = cnt; } int getSharedCount() { return sharedCount_; } bool isSparse() { return config_.is_sparse(); } diff --git a/python/paddle/trainer/config_parser.py b/python/paddle/trainer/config_parser.py index 9fe8794691e..56e1ba170d8 100644 --- a/python/paddle/trainer/config_parser.py +++ b/python/paddle/trainer/config_parser.py @@ -3371,7 +3371,7 @@ def make_importer(config_dir, config_args): return Import -settings = dict( +default_settings = dict( batch_size=None, mini_batch_size=None, algorithm='async_sgd', @@ -3404,6 +3404,8 @@ settings = dict( adam_beta2=0.999, adam_epsilon=1e-8, ) +settings = copy.deepcopy(default_settings) + settings_deprecated = dict(usage_ratio=1., ) trainer_settings = dict( @@ -3544,10 +3546,8 @@ def update_g_config(): return g_config -def parse_config(trainer_config, config_arg_str): +def begin_parse(config_arg_str=''): ''' - @param trainer_config: can be a string of config file name or a function name - with config logic @param config_arg_str: a string of the form var1=val1,var2=val2. It will be passed to config script as a dictionary CONFIG_ARGS ''' @@ -3555,12 +3555,23 @@ def parse_config(trainer_config, config_arg_str): for hook in _parse_config_hooks: hook() - config_args = {} - logger.findCaller = find_caller logger.fatal = my_fatal g_config.model_config.type = "nn" + + global g_current_submodel, g_root_submodel + g_root_submodel = g_config.model_config.sub_models.add() + g_root_submodel.name = 'root' + g_root_submodel.is_recurrent_layer_group = False + g_current_submodel = g_root_submodel + + +def parse_config(trainer_config, config_arg_str): + begin_parse(config_arg_str) + + config_args = {} + if config_arg_str: config_args = dict([f.split('=') for f in config_arg_str.split(',')]) @@ -3573,14 +3584,6 @@ def parse_config(trainer_config, config_arg_str): extension_module = importlib(extension_module_name) g_extended_config_funcs = extension_module.get_config_funcs(g_config) - g_config.model_config.type = 'nn' - - global g_current_submodel, g_root_submodel - g_root_submodel = g_config.model_config.sub_models.add() - g_root_submodel.name = 'root' - g_root_submodel.is_recurrent_layer_group = False - g_current_submodel = g_root_submodel - if hasattr(trainer_config, '__call__'): trainer_config.func_globals.update( make_config_environment("", config_args)) diff --git a/python/paddle/trainer_config_helpers/config_parser_utils.py b/python/paddle/trainer_config_helpers/config_parser_utils.py index 681b177a55f..9e56556d0a4 100644 --- a/python/paddle/trainer_config_helpers/config_parser_utils.py +++ b/python/paddle/trainer_config_helpers/config_parser_utils.py @@ -12,15 +12,19 @@ # See the License for the specific language governing permissions and # limitations under the License. +import copy import paddle.trainer.config_parser as config_parser +from paddle.proto.TrainerConfig_pb2 import OptimizationConfig + ''' -This file is a wrapper of formal config_parser. The main idea of this file is to +This file is a wrapper of formal config_parser. The main idea of this file is to separete different config logic into different function, such as network configuration and optimizer configuration. ''' __all__ = [ - "parse_trainer_config", "parse_network_config", "parse_optimizer_config" + "parse_trainer_config", "parse_network_config", "parse_optimizer_config", + "reset_parser" ] @@ -34,5 +38,15 @@ def parse_network_config(network_conf, config_arg_str=''): def parse_optimizer_config(optimizer_conf, config_arg_str=''): - config = config_parser.parse_config(optimizer_conf, config_arg_str) - return config.opt_config + config_parser.settings = copy.deepcopy(config_parser.default_settings) + optimizer_conf() + opt_config = OptimizationConfig() + for k, v in config_parser.settings.iteritems(): + if v is None: + continue + opt_config.__setattr__(k, v) + return opt_config + + +def reset_parser(): + config_parser.begin_parse() diff --git a/python/paddle/trainer_config_helpers/layers.py b/python/paddle/trainer_config_helpers/layers.py index ec81e1dc3d2..08d80b3b527 100755 --- a/python/paddle/trainer_config_helpers/layers.py +++ b/python/paddle/trainer_config_helpers/layers.py @@ -285,6 +285,7 @@ class LayerOutput(object): assert size is not None assert LayerType.is_layer_type(layer_type) self.name = name + self.full_name = MakeLayerNameInSubmodel(name) self.layer_type = layer_type if parents is not None and type(parents) != list: parents = [parents] @@ -3489,6 +3490,11 @@ def recurrent_group(step, RecurrentLayerGroupEnd(name=name) + for layer_out in layer_outs: + # Thee previous full_name is the name is the rnn group + # We need a full_name outside the rnn group + layer_out.full_name = MakeLayerNameInSubmodel(layer_out.name) + if len(layer_outs) == 1: return layer_outs[0] else: diff --git a/python/paddle/v2/evaluator.py b/python/paddle/v2/evaluator.py index 588eefa3912..c474f74235b 100644 --- a/python/paddle/v2/evaluator.py +++ b/python/paddle/v2/evaluator.py @@ -25,21 +25,9 @@ def initialize(): for __ev_name__ in filter(lambda x: x.endswith('_evaluator'), evs.__all__): __ev__ = getattr(evs, __ev_name__) - if hasattr(__ev__, 'argspec'): - argspec = __ev__.argspec - else: - argspec = inspect.getargspec(__ev__) - parent_names = filter(lambda x: x in ['input', 'label', 'weight'], - argspec.args) - v2_ev = __convert_to_v2__( - __ev_name__, - parent_names=parent_names, - is_default_name='name' in argspec.args, - attach_parent=True) - __new_name__ = convert_to_new_name(__ev_name__) - globals()[__new_name__] = v2_ev + globals()[__new_name__] = __ev__ globals()[__new_name__].__name__ = __new_name__ __all__.append(__new_name__) diff --git a/python/paddle/v2/layer.py b/python/paddle/v2/layer.py index 919c531d184..ad36364ca8e 100644 --- a/python/paddle/v2/layer.py +++ b/python/paddle/v2/layer.py @@ -32,392 +32,39 @@ The primary usage shows below. """ import collections -import inspect -import re - -import paddle.trainer_config_helpers as conf_helps -from paddle.trainer.config_parser import \ - RecurrentLayerGroupWithoutOutLinksBegin, RecurrentLayerGroupSetOutLink, \ - RecurrentLayerGroupEnd, model_type -from paddle.trainer_config_helpers.config_parser_utils import \ - parse_network_config as __parse__ -from paddle.trainer_config_helpers.default_decorators import wrap_act_default -from paddle.trainer_config_helpers.default_decorators import \ - wrap_bias_attr_default -from paddle.trainer_config_helpers.default_decorators import wrap_name_default -from paddle.trainer_config_helpers.layers import RecurrentLayerGroupSetGenerator, Generator -from paddle.trainer_config_helpers.layers import layer_support - -import activation -import attr -import data_type -from config_base import Layer, __convert_to_v2__ - -__all__ = ['parse_network', 'data'] +import copy +import paddle.trainer_config_helpers.layers as v1_layers +import paddle.trainer.config_parser as cp +from paddle.proto.ModelConfig_pb2 import ModelConfig, SubModelConfig +__all__ = ['data', 'parse_network'] +__layer_map__ = {} -def parse_network(output_layers, extra_layers=None): - """ - Parse all layers in the neural network graph and - then generate a ModelConfig object. - - .. note:: - - This function is used internally in paddle.v2 module. User should never - invoke this method. - - :param output_layers: Output layers. - :type output_layers: Layer - :param extra_layers: Some layers in the neural network graph are not in the - path of output_layers. - :type extra_layers: Layer - :return: A ModelConfig object instance. - :rtype: ModelConfig - """ - if not isinstance(output_layers, collections.Sequence): - output_layers = [output_layers] - if extra_layers is not None and not isinstance(extra_layers, - collections.Sequence): - extra_layers = [extra_layers] +def __wrap__(f): + def wrapped(*args, **xargs): + out = f(*args, **xargs) + outs = out + if not isinstance(out, collections.Sequence): + outs = [out] + for l in outs: + if isinstance(l, v1_layers.LayerOutput): + __layer_map__[l.full_name] = l + return out - def __real_func__(): - """ - __real_func__ is the function that config_parser.parse invoked. It is - the plain old paddle configuration function. - """ - context = dict() - real_output = [each.to_proto(context=context) for each in output_layers] - if extra_layers is not None: - extra_output = [ - each.to_proto(context=context) for each in extra_layers - ] - conf_helps.outputs(real_output) + return wrapped - return __parse__(__real_func__) +def __need_to_keep__(name): + if name in ['StaticInput', 'LayerType', 'layer_support']: + return False + return True -""" -Some layer may need some special config, and can not use __convert_to_v2__ to convert. -So we also need to implement some special LayerV2. -""" +def __need_to_wrap__(name): + return name not in ['AggregateLevel', 'ExpandLevel'] -class DataLayerV2(Layer): - METHOD_NAME = 'data_layer' - - def __init__(self, name, type, **kwargs): - assert isinstance(type, data_type.InputType) - - self.type = type - self.__method_name__ = 'data_layer' - self.__kwargs__ = kwargs - - super(DataLayerV2, self).__init__(name=name, parent_layers=dict()) - - def to_proto_impl(self, **kwargs): - args = dict() - args['size'] = self.type.dim - for each in kwargs: - args[each] = kwargs[each] - for each in self.__kwargs__: - args[each] = self.__kwargs__[each] - return getattr(conf_helps, self.__method_name__)(name=self.name, **args) - - def __map_docstr__(doc): - doc = re.sub(r'(data = [^\)]+)\).*', - "data = paddle.layer.data(name=\"input\", " - "type=paddle.data_type.dense_vector(1000))", doc) - - doc = re.sub(r':param size:.*', - ':param type: Data type of this data layer', doc) - doc = re.sub(r':type size:.*', - ":type size: paddle.v2.data_type.InputType", doc) - return doc - - -class MemoryV2(Layer): - def __init__(self, name, extra_input=None, **kwargs): - """ - Init memory object, if memory is inited inside recurrent_group step - function, it may depend on a boot_layer that should be initialized - outside recurrent_group, so we: - 1. add RecurrentLayerInput to extra_parent of self. - 2. add boot_layer to the extra_parent of RecurrentLayerInput. - - :param extra_input: list of RecurrentLayerInput - :type extra_input: [RecurrentLayerInput] - """ - self.name = name - super(MemoryV2, self).__init__(name=name, parent_layers=dict()) - self.__kwargs__ = kwargs - self.__boot_layer_name__ = None - - if 'boot_layer' in kwargs: - begin_of_current_rnn = [] - # TODO(yuyang18): Fix inspect, it could be wrong when user invoke a - # function inside step. - st = inspect.stack() - for i in xrange(len(st)): - locs = inspect.stack()[i][0].f_locals - keys = locs.keys() - for key in keys: - val = locs[key] - if isinstance(val, RecurrentLayerInput): - begin_of_current_rnn.append(val) - elif isinstance(val, collections.Sequence): - for v in val: - if isinstance(v, RecurrentLayerInput): - begin_of_current_rnn.append(v) - - if begin_of_current_rnn: - break - assert begin_of_current_rnn is not None - for extra in begin_of_current_rnn: - self.append_extra_parent(extra) - extra.append_extra_parent(kwargs['boot_layer']) - self.__boot_layer_name__ = kwargs['boot_layer'].name - - def to_proto_impl(self, **kwargs): - args = dict() - for each in kwargs: - args[each] = kwargs[each] - for each in self.__kwargs__: - args[each] = self.__kwargs__[each] - - if self.__boot_layer_name__ is not None: - args['boot_layer'] = self.__context__[self.__boot_layer_name__] - - size = args.get('size', None) - if size is not None: - if callable(size): - real_size = size() - else: - real_size = size - args['size'] = real_size - return conf_helps.memory(name=self.name, **args) - - def context_name(self): - return self.name + "#memory" - - def use_context_name(self): - """ - memory layer will have the same name with some layer - :return: - """ - return True - - -class StaticInputV2(object): - def __init__(self, input, is_seq=False, size=None): - assert isinstance(input, LayerV2) - self.name = input.name - self.input = input - self.is_seq = is_seq - self.size = size - # TODO(add size check) - # assert input.size is not None or size is not None - - -class BaseGeneratedInputV2(object): - def __init__(self): - self.bos_id = None - self.eos_id = None - - def before_real_step(self): - raise NotImplementedError() - - def after_real_step(self, *args): - raise NotImplementedError() - - -class GeneratedInputV2(BaseGeneratedInputV2): - def __init__(self, size, embedding_name, embedding_size): - super(GeneratedInputV2, self).__init__() - self.size = size - self.embedding_name = embedding_name - self.embedding_size = embedding_size - - def after_real_step(self, input): - return max_id(input=input, name='__beam_search_predict__') - - def before_real_step(self): - predict_id = memory( - name='__beam_search_predict__', - size=self.size, - boot_with_const_id=self.bos_id) - - trg_emb = embedding( - input=predict_id, - size=self.embedding_size, - param_attr=attr.ParamAttr(name=self.embedding_name)) - return trg_emb - - -class RecurrentLayerGroupSetGeneratorV2(Layer): - def __init__(self, eos_name, max_length, beam_size, num_results_per_sample): - self.eos_name = eos_name - self.max_length = max_length - self.beam_size = beam_size - self.num_results_per_sample = num_results_per_sample - super(RecurrentLayerGroupSetGeneratorV2, self).__init__( - name=eos_name, parent_layers={}) - - def to_proto_impl(self, **kwargs): - RecurrentLayerGroupSetGenerator( - Generator( - eos_layer_name=self.eos_name, - max_num_frames=self.max_length, - beam_size=self.beam_size, - num_results_per_sample=self.num_results_per_sample)) - return self - - def context_name(self): - return self.eos_name + ".fake" - - def use_context_name(self): - return True - - -class MixedLayerV2(Layer): - """ - This class is use to support `with` grammar. If not, the following code - could convert mixed_layer simply. - - mixed = __convert_to_v2__( - 'mixed_layer', name_prefix='mixed', parent_names=['input']) - """ - - class AddToSealedMixedLayerExceptionV2(Exception): - pass - - def __init__(self, - size=0, - input=None, - name=None, - act=None, - bias_attr=None, - layer_attr=None): - self.__method_name__ = 'mixed_layer' - self.finalized = False - self.__inputs__ = [] - if input is not None: - self.__inputs__ = input - - other_kwargs = dict() - other_kwargs['name'] = name - other_kwargs['size'] = size - other_kwargs['act'] = act - other_kwargs['bias_attr'] = bias_attr - other_kwargs['layer_attr'] = layer_attr - parent_layers = {"input": self.__inputs__} - super(MixedLayerV2, self).__init__(name, parent_layers) - self.__other_kwargs__ = other_kwargs - - def __iadd__(self, other): - if not self.finalized: - self.__inputs__.append(other) - return self - else: - raise MixedLayerV2.AddToSealedMixedLayerExceptionV2() - - def __enter__(self): - assert len(self.__inputs__) == 0 - return self - - def __exit__(self, *args, **kwargs): - self.finalized = True - - def to_proto_impl(self, **kwargs): - args = dict() - for each in kwargs: - args[each] = kwargs[each] - for each in self.__other_kwargs__: - args[each] = self.__other_kwargs__[each] - size = args.get('size', None) - if size is not None: - if callable(size): - real_size = size() - else: - real_size = size - args['size'] = real_size - return getattr(conf_helps, self.__method_name__)(**args) - - -@wrap_name_default("mixed") -@wrap_act_default(act=activation.Linear()) -@wrap_bias_attr_default(has_bias=False) -@layer_support(conf_helps.layers.ERROR_CLIPPING, conf_helps.layers.DROPOUT) -def mixed(size=0, - name=None, - input=None, - act=None, - bias_attr=False, - layer_attr=None): - return MixedLayerV2(size, input, name, act, bias_attr, layer_attr) - - -mixed.__doc__ = conf_helps.mixed_layer.__doc__ - - -class RecurrentLayerInput(Layer): - def __init__(self, recurrent_name, index, parent_layers, reverse): - parents_len = len(parent_layers) - assert parents_len <= 1 - if parents_len == 0: - self.__parents__ = [] - else: - self.__parents__ = parent_layers.values()[0] - self.__recurrent_name__ = recurrent_name - self.__reverse__ = reverse - name = self.__parents__[ - index].name if index >= 0 else self.context_name() - super(RecurrentLayerInput, self).__init__( - name=name, parent_layers=parent_layers) - - def context_name(self): - return self.__recurrent_name__ + ".begin" - - def to_proto_impl(self, **kwargs): - model_type('recurrent_nn') - RecurrentLayerGroupWithoutOutLinksBegin( - name=self.__recurrent_name__, - in_links=map(lambda x: x.name, self.__parents__), - seq_reversed=self.__reverse__) - return self - - -class RecurrentLayerOutput(Layer): - def __init__(self, recurrent_name, index, parent_layers): - assert len(parent_layers) == 1 - self.__parents__ = parent_layers.values()[0] - super(RecurrentLayerOutput, self).__init__( - name=self.__parents__[index].name, parent_layers=parent_layers) - self.__recurrent_name__ = recurrent_name - - def context_name(self): - return self.__recurrent_name__ + ".end" - - def to_proto_impl(self, **kwargs): - for l in self.__parents__: - RecurrentLayerGroupSetOutLink(l.name) - RecurrentLayerGroupEnd(name=self.__recurrent_name__) - - -LayerV2 = Layer -data = DataLayerV2 -data.__name__ = 'data' -AggregateLevel = conf_helps.AggregateLevel -ExpandLevel = conf_helps.ExpandLevel -memory = MemoryV2 -memory.__name__ = 'memory' -memory.__doc__ = conf_helps.memory.__doc__ - - -def __layer_name_mapping__(inname): - if inname in ['data_layer', 'memory', 'mixed_layer', 'recurrent_group']: - # Do Not handle these layers - return - elif inname == 'maxid_layer': +def __convert_name__(inname): + if inname == 'maxid_layer': return 'max_id' elif inname.endswith('memory') or inname.endswith( '_seq') or inname.endswith('_sim') or inname == 'hsigmoid': @@ -431,187 +78,202 @@ def __layer_name_mapping__(inname): return inname elif inname.endswith("_layer"): return inname[:-len("_layer")] + else: + return inname -def __layer_name_mapping_parent_names__(inname): - all_args = getattr(conf_helps, inname).argspec.args - return filter( - lambda x: x in ['input1', 'input2', 'label', 'input', 'a', 'b', - 'expand_as', - 'weights', 'vectors', 'weight', 'score', 'left', - 'right', 'output_mem'], - all_args) - - -def __convert_layer__(_new_name_, _old_name_, _parent_names_): - global __all__ - __all__.append(_new_name_) - globals()[new_name] = __convert_to_v2__(_old_name_, _parent_names_) - globals()[new_name].__name__ = new_name - - -for each_layer_name in dir(conf_helps): - new_name = __layer_name_mapping__(each_layer_name) - if new_name is not None: - parent_names = __layer_name_mapping_parent_names__(each_layer_name) - assert len(parent_names) != 0, each_layer_name - __convert_layer__(new_name, each_layer_name, parent_names) - -del parent_names -del new_name -del each_layer_name - - -@wrap_name_default() -def recurrent_group(step, input, reverse=False, name=None): - if not isinstance(input, collections.Sequence): - input = [input] - - non_static_inputs = filter(lambda x: not isinstance(x, StaticInputV2), - input) - actual_input = [ - RecurrentLayerInput( - recurrent_name=name, - index=i, - parent_layers={'recurrent_inputs': non_static_inputs}, - reverse=reverse) for i in xrange(len(non_static_inputs)) - ] - - extra_input = None - if len(non_static_inputs) == 0: - extra_input = RecurrentLayerInput( - recurrent_name=name, index=-1, parent_layers={}, reverse=reverse) - - def __real_step__(*args): - rnn_input = list(args) - static_inputs = filter(lambda x: isinstance(x, StaticInputV2), input) - for static_input in static_inputs: - mem_name = "__%s_memory__" % static_input.input.name - mem = memory( - name=mem_name, - extra_input=extra_input, - is_seq=static_input.is_seq, - size=static_input.input.calculate_size, - boot_layer=static_input.input) - with mixed( - name=mem_name, - size=static_input.input.calculate_size, - act=activation.Identity()) as mix: - mix += identity_projection(input=mem) - rnn_input.insert(input.index(static_input), mix) - return step(*rnn_input) - - actual_output = __real_step__(*actual_input) - - if not isinstance(actual_output, collections.Sequence): - actual_output = [actual_output] - - retv = [ - RecurrentLayerOutput( - recurrent_name=name, - index=i, - parent_layers={'recurrent_outputs': actual_output}) - for i in xrange(len(actual_output)) - ] - if len(retv) == 1: - return retv[0] +for name in v1_layers.__all__: + obj = getattr(v1_layers, name) + if not __need_to_keep__(name): + continue + new_name = __convert_name__(name) + if callable(obj) and __need_to_wrap__(name): + globals()[new_name] = __wrap__(obj) else: - return retv - - -recurrent_group.__doc__ = conf_helps.recurrent_group.__doc__ - - -@wrap_name_default() -def beam_search(step, - input, - bos_id, - eos_id, - beam_size, - max_length=500, - name=None, - num_results_per_sample=None): - if num_results_per_sample is None: - num_results_per_sample = beam_size - assert num_results_per_sample <= beam_size - # logger.warning("num_results_per_sample should be less than beam_size") - - if isinstance(input, StaticInputV2) or isinstance(input, - BaseGeneratedInputV2): - input = [input] - - generated_input_index = -1 - - real_input = [] - for i, each_input in enumerate(input): - assert isinstance(each_input, StaticInputV2) or isinstance( - each_input, BaseGeneratedInputV2) - if isinstance(each_input, BaseGeneratedInputV2): - assert generated_input_index == -1 - generated_input_index = i - else: - real_input.append(each_input) + globals()[new_name] = obj + __all__.append(new_name) + + +def __data_layer__(name, type, **kwargs): + l = v1_layers.data_layer(name, type.dim, **kwargs) + l.data_type = type + return l - assert generated_input_index != -1 +data = __wrap__(__data_layer__) - gipt = input[generated_input_index] - assert isinstance(gipt, BaseGeneratedInputV2) +LayerV2 = v1_layers.LayerOutput - gipt.bos_id = bos_id - gipt.eos_id = eos_id - def __real_step__(*args): - eos_name = "__%s_eos_layer__" % name - generator = RecurrentLayerGroupSetGeneratorV2( - eos_name, max_length, beam_size, num_results_per_sample) +def __get_used_layers__(output_layers, extra_layers=None): + layer_names = set() + parents = {} + def add_parent(child, parent): + if child in parents: + parents[child].append(parent) + else: + parents[child] = [parent] + + def add_additional_parents(): + for sub_model in cp.g_config.model_config.sub_models: + if sub_model.name == 'root': + continue + for link in sub_model.in_links: + add_parent(link.link_name, link.layer_name) + add_parent(sub_model.name, link.layer_name) + for link in sub_model.out_links: + add_parent(link.link_name, link.layer_name) + add_parent(link.link_name, sub_model.name) + for mem in sub_model.memories: + if mem.boot_layer_name: + add_parent(mem.layer_name, mem.boot_layer_name) + add_parent(mem.link_name, mem.layer_name) + + def dfs_travel(layer_name): + if layer_name in layer_names: + return + layer_names.add(layer_name) + layer = cp.g_layer_map[layer_name] + + for inp in layer.inputs: + dfs_travel(inp.input_layer_name) + if layer.name in parents: + for p in parents[layer.name]: + dfs_travel(p) + + add_additional_parents() + + for layer in output_layers: + dfs_travel(layer.full_name) + + return layer_names + + +def __get_used_parameters__(layer_names): + parameter_names = set() + for name in layer_names: + l = cp.g_layer_map[name] + for inp in l.inputs: + if inp.input_parameter_name: + parameter_names.add(inp.input_parameter_name) + if l.bias_parameter_name: + parameter_names.add(l.bias_parameter_name) + return parameter_names + + +def __get_used_submodels__(layer_names): + submodel_names = set() + for submodel in cp.g_config.model_config.sub_models: + if submodel.name in layer_names: + submodel_names.add(submodel.name) + return submodel_names + + +def __get_used_evaluators__(layer_names): + evaluator_names = set() + for e in cp.g_config.model_config.evaluators: + used = True + for name in e.input_layers: + if name not in layer_names: + used = False + break + if used: + evaluator_names.add(e.name) + return evaluator_names + + +def __trim_submodel__(old_submodel, + layer_names, + input_layer_names, + output_layer_names, + evaluator_names): + + submodel = SubModelConfig() + submodel.name = old_submodel.name + submodel.layer_names.extend(filter(lambda x: x in layer_names, + old_submodel.layer_names)) + submodel.input_layer_names.extend(filter(lambda x: x in input_layer_names, + submodel.layer_names)) + submodel.output_layer_names.extend(filter(lambda x: x in output_layer_names, + submodel.layer_names)) + submodel.evaluator_names.extend(filter(lambda x: x in evaluator_names, + old_submodel.evaluator_names)) + + submodel.is_recurrent_layer_group = old_submodel.is_recurrent_layer_group + submodel.reversed = old_submodel.reversed + + submodel.memories.extend(filter(lambda x: x.link_name in layer_names, + old_submodel.memories)) + target_inlinkid = (old_submodel.target_inlinkid + if old_submodel.HasField('target_inlinkid') else -1) + in_links = [] + for i, link in enumerate(old_submodel.in_links): + if link.link_name in layer_names or i == target_inlinkid: + in_links.append(link) + if i == target_inlinkid: + target_inlinkid = len(in_links) - 1 + submodel.in_links.extend(in_links) + + submodel.out_links.extend(filter(lambda x: x.link_name in layer_names, + old_submodel.out_links)) + if old_submodel.HasField('generator'): + submodel.generator.CopyFrom(old_submodel.generator) + + if old_submodel.HasField('target_inlinkid'): + submodel.target_inlinkid = target_inlinkid + return submodel - args = list(args) - before_step_layer = gipt.before_real_step() - before_step_layer.append_child( - layer=generator, parent_names=[before_step_layer.name]) - args.insert(generated_input_index, before_step_layer) - predict = gipt.after_real_step(step(*args)) +def parse_network(output_layers, extra_layers=None): + if not isinstance(output_layers, collections.Sequence): + output_layers = [output_layers] + if extra_layers is not None and not isinstance(extra_layers, + collections.Sequence): + extra_layers = [extra_layers] + else: + extra_layers = [] - eos_layer = eos(input=predict, eos_id=eos_id, name=eos_name) - predict.append_child(layer=eos_layer, parent_names=[predict.name]) + layer_names = __get_used_layers__(output_layers + extra_layers) + submodel_names = __get_used_submodels__(layer_names) + submodel_names.add('root') + parameter_names = __get_used_parameters__(layer_names) + evaluator_names = __get_used_evaluators__(layer_names) + input_layer_names = set() + output_layer_names = set() - return predict + model_config = ModelConfig() + model_config.type = cp.g_config.model_config.type + for l in cp.g_config.model_config.layers: + if l.name not in layer_names: + continue + model_config.layers.extend([l]) + if l.type == 'data': + model_config.input_layer_names.append(l.name) + input_layer_names.add(l.name) - # tmp = paddle.layer.recurrent_group( - # step=__real_step__, - # input=real_input, - # reverse=False, - # name=name, - # is_generating=True) - tmp = recurrent_group(step=__real_step__, input=real_input, name=name) + for p in cp.g_config.model_config.parameters: + if p.name in parameter_names: + model_config.parameters.extend([p]) - return tmp + for layer in output_layers: + model_config.output_layer_names.append(layer.full_name) + output_layer_names.add(layer.full_name) + for e in cp.g_config.model_config.evaluators: + if e.name in evaluator_names: + model_config.evaluators.extend([e]) -beam_search.__doc__ = conf_helps.beam_search.__doc__ + for s in cp.g_config.model_config.sub_models: + if s.name in submodel_names: + s = __trim_submodel__( + s, layer_names, input_layer_names, output_layer_names, + evaluator_names) + model_config.sub_models.extend([s]) -__projection_names__ = filter(lambda x: x.endswith('_projection'), - dir(conf_helps)) + return model_config -__all__ += __projection_names__ -__operator_names__ = filter(lambda x: x.endswith('_operator'), dir(conf_helps)) -__all__ += __operator_names__ +def get_layer(name): + return __layer_map__.get(name) -# convert projection -for prj in __projection_names__: - globals()[prj] = __convert_to_v2__( - prj, parent_names=['input'], is_default_name=False) - globals()[prj].__name__ = prj -# convert operator -operator_list = [ - # [V1_method_name, parent_names], - ['dotmul_operator', ['a', 'b']], - ['conv_operator', ['img', 'filter']] -] -for op in operator_list: - globals()[op[0]] = __convert_to_v2__( - op[0], parent_names=op[1], is_default_name=False) - globals()[op[0]].__name__ = op[0] +cp.begin_parse() diff --git a/python/paddle/v2/networks.py b/python/paddle/v2/networks.py index 9e6644196c8..8ae9f3b202d 100644 --- a/python/paddle/v2/networks.py +++ b/python/paddle/v2/networks.py @@ -24,20 +24,7 @@ def __initialize__(): if each_subnetwork in ['inputs', 'outputs']: continue func = getattr(conf_nw, each_subnetwork) - if hasattr(func, 'argspec'): - argspec = func.argspec - else: - argspec = inspect.getargspec(func) - if each_subnetwork == 'simple_attention': - parents = ['encoded_sequence', 'encoded_proj', 'decoder_state'] - else: - parents = filter(lambda x: x.startswith('input'), argspec.args) - assert len(parents) != 0, each_subnetwork - v2_subnet = __convert_to_v2__( - each_subnetwork, - parent_names=parents, - is_default_name='name' in argspec.args) - globals()[each_subnetwork] = v2_subnet + globals()[each_subnetwork] = func globals()[each_subnetwork].__name__ = each_subnetwork global __all__ __all__.append(each_subnetwork) diff --git a/python/paddle/v2/tests/test_layer.py b/python/paddle/v2/tests/test_layer.py index c67f3b84d96..341da1c8520 100644 --- a/python/paddle/v2/tests/test_layer.py +++ b/python/paddle/v2/tests/test_layer.py @@ -173,9 +173,9 @@ class OtherLayerTest(unittest.TestCase): class ProjOpTest(unittest.TestCase): def test_projection(self): - input = layer.data(name='data', type=data_type.dense_vector(784)) + input = layer.data(name='data2', type=data_type.dense_vector(784)) word = layer.data( - name='word', type=data_type.integer_value_sequence(10000)) + name='word2', type=data_type.integer_value_sequence(10000)) fc0 = layer.fc(input=input, size=100, act=activation.Sigmoid()) fc1 = layer.fc(input=input, size=200, act=activation.Sigmoid()) mixed0 = layer.mixed( @@ -204,8 +204,8 @@ class ProjOpTest(unittest.TestCase): dotmul1 += dotmul context = layer.context_projection(input=fc0, context_len=5) - context0 = layer.mixed(size=100, input=context) - with layer.mixed(size=100) as context1: + context0 = layer.mixed(size=500, input=context) + with layer.mixed(size=500) as context1: context1 += context conv = layer.conv_projection( @@ -231,8 +231,8 @@ class ProjOpTest(unittest.TestCase): print layer.parse_network(conv1) def test_operator(self): - ipt0 = layer.data(name='data', type=data_type.dense_vector(784)) - ipt1 = layer.data(name='word', type=data_type.dense_vector(128)) + ipt0 = layer.data(name='data1', type=data_type.dense_vector(784)) + ipt1 = layer.data(name='word1', type=data_type.dense_vector(128)) fc0 = layer.fc(input=ipt0, size=100, act=activation.Sigmoid()) fc1 = layer.fc(input=ipt0, size=100, act=activation.Sigmoid()) @@ -261,7 +261,7 @@ class ProjOpTest(unittest.TestCase): class NetworkTests(unittest.TestCase): def test_vgg(self): - img = layer.data(name='pixel', type=data_type.dense_vector(784)) + img = layer.data(name='pixel1', type=data_type.dense_vector(784)) vgg_out = networks.small_vgg( input_image=img, num_channels=1, num_classes=2) print layer.parse_network(vgg_out) @@ -269,12 +269,12 @@ class NetworkTests(unittest.TestCase): class EvaluatorTest(unittest.TestCase): def test_evaluator(self): - img = layer.data(name='pixel', type=data_type.dense_vector(784)) + img = layer.data(name='pixel2', type=data_type.dense_vector(784)) output = layer.fc(input=img, size=10, act=activation.Softmax(), name='fc_here') - lbl = layer.data(name='label', type=data_type.integer_value(10)) + lbl = layer.data(name='label2', type=data_type.integer_value(10)) cost = layer.cross_entropy_cost(input=output, label=lbl) evaluator.classification_error(input=output, label=lbl) diff --git a/python/paddle/v2/tests/test_rnn_layer.py b/python/paddle/v2/tests/test_rnn_layer.py index 845277c0128..b334f3b1ffa 100644 --- a/python/paddle/v2/tests/test_rnn_layer.py +++ b/python/paddle/v2/tests/test_rnn_layer.py @@ -20,6 +20,8 @@ import paddle.v2.data_type as data_type import paddle.v2.layer as layer from paddle.trainer_config_helpers.config_parser_utils import \ parse_network_config as parse_network +from paddle.trainer_config_helpers.config_parser_utils import \ + reset_parser class RNNTest(unittest.TestCase): @@ -29,6 +31,7 @@ class RNNTest(unittest.TestCase): hidden_dim = 8 def parse_old_rnn(): + reset_parser() def step(y): mem = conf_helps.memory(name="rnn_state", size=hidden_dim) out = conf_helps.fc_layer( @@ -48,6 +51,7 @@ class RNNTest(unittest.TestCase): return str(parse_network(test)) def parse_new_rnn(): + reset_parser() def new_step(y): mem = layer.memory(name="rnn_state", size=hidden_dim) out = layer.fc(input=[y, mem], @@ -68,6 +72,7 @@ class RNNTest(unittest.TestCase): parse_new_rnn().splitlines(1)) print ''.join(diff) + def test_sequence_rnn_multi_input(self): dict_dim = 10 word_dim = 8 @@ -75,6 +80,7 @@ class RNNTest(unittest.TestCase): label_dim = 3 def parse_old_rnn(): + reset_parser() def test(): data = conf_helps.data_layer(name="word", size=dict_dim) label = conf_helps.data_layer(name="label", size=label_dim) @@ -114,6 +120,7 @@ class RNNTest(unittest.TestCase): return str(parse_network(test)) def parse_new_rnn(): + reset_parser() data = layer.data( name="word", type=data_type.dense_vector(dict_dim)) label = layer.data( diff --git a/python/paddle/v2/tests/test_topology.py b/python/paddle/v2/tests/test_topology.py index 5c6dbcdb4f4..7fd2ee82fde 100644 --- a/python/paddle/v2/tests/test_topology.py +++ b/python/paddle/v2/tests/test_topology.py @@ -46,8 +46,8 @@ class TestTopology(unittest.TestCase): self.assertEqual(label_data_type[1].dim, 10) def test_get_layer(self): - pixel = layer.data(name='pixel', type=data_type.dense_vector(784)) - label = layer.data(name='label', type=data_type.integer_value(10)) + pixel = layer.data(name='pixel2', type=data_type.dense_vector(784)) + label = layer.data(name='label2', type=data_type.integer_value(10)) hidden = layer.fc(input=pixel, size=100, act=conf_helps.SigmoidActivation()) @@ -56,14 +56,14 @@ class TestTopology(unittest.TestCase): act=conf_helps.SoftmaxActivation()) cost = layer.classification_cost(input=inference, label=label) topo = topology.Topology(cost) - pixel_layer = topo.get_layer("pixel") - label_layer = topo.get_layer("label") + pixel_layer = topo.get_layer("pixel2") + label_layer = topo.get_layer("label2") self.assertEqual(pixel_layer, pixel) self.assertEqual(label_layer, label) def test_parse(self): - pixel = layer.data(name='pixel', type=data_type.dense_vector(784)) - label = layer.data(name='label', type=data_type.integer_value(10)) + pixel = layer.data(name='pixel3', type=data_type.dense_vector(784)) + label = layer.data(name='label3', type=data_type.integer_value(10)) hidden = layer.fc(input=pixel, size=100, act=conf_helps.SigmoidActivation()) diff --git a/python/paddle/v2/topology.py b/python/paddle/v2/topology.py index 1e46e4973f4..962d5ab76d1 100644 --- a/python/paddle/v2/topology.py +++ b/python/paddle/v2/topology.py @@ -15,7 +15,7 @@ import collections from paddle.proto.ModelConfig_pb2 import ModelConfig - +import paddle.trainer_config_helpers as conf_helps import layer as v2_layer __all__ = ['Topology'] @@ -94,31 +94,18 @@ class Topology(object): :param name: :return: """ - result_layer = [None] - - def __impl__(l): - if l.name == name: - result_layer[0] = l - return True # break - return False - - __bfs_travel__(__impl__, *self.layers) - if result_layer[0] is None: - raise ValueError("No such layer %s" % name) - return result_layer[0] + return v2_layer.get_layer(name) def data_layers(self): """ get all data layer :return: """ - data_layers = dict() - - def __impl__(l): - if isinstance(l, v2_layer.DataLayerV2): - data_layers[l.name] = l - - __bfs_travel__(__impl__, *self.layers) + data_layers = {} + for layer in self.proto().layers: + l = v2_layer.get_layer(layer.name) + if l and l.layer_type == conf_helps.LayerType.DATA: + data_layers[layer.name] = l return data_layers def data_type(self): @@ -127,7 +114,7 @@ class Topology(object): [('image', dense_vector(768)), ('label', integer_value(10))] """ data_layers = self.data_layers() - return [(nm, data_layers[nm].type) + return [(nm, data_layers[nm].data_type) for nm in self.proto().input_layer_names] def get_layer_proto(self, name): -- GitLab From 048b14a9164d62c9b80138bb291d74f995478ddb Mon Sep 17 00:00:00 2001 From: hedaoyuan Date: Fri, 26 May 2017 21:28:59 +0800 Subject: [PATCH 0475/3256] Change stride to strides, and change padding to paddings. --- paddle/function/ConvOp.h | 15 +++++++++++---- paddle/function/ConvOpTest.cpp | 6 ++++-- paddle/function/GemmConvOp.cpp | 8 ++++---- paddle/function/NaiveConvOp.cpp | 16 ++++++++++------ 4 files changed, 29 insertions(+), 16 deletions(-) diff --git a/paddle/function/ConvOp.h b/paddle/function/ConvOp.h index 4d678cfe273..465db57ae7d 100644 --- a/paddle/function/ConvOp.h +++ b/paddle/function/ConvOp.h @@ -36,8 +36,8 @@ class ConvFunctionBase : public FunctionBase { public: void init(const FuncConfig& config) override { // function arguments - stride_ = config.get("stride"); - padding_ = config.get("padding"); + strides_ = config.get>("strides"); + paddings_ = config.get>("paddings"); // number of inputs and outputs numInputs_ = 2; @@ -60,8 +60,15 @@ public: } protected: - size_t padding_; - size_t stride_; + std::vector strides_; + std::vector paddings_; + inline int strideH() const { return strides_[0]; } + + inline int strideW() const { return strides_[1]; } + + inline int paddingH() const { return paddings_[0]; } + + inline int paddingW() const { return paddings_[1]; } }; } // namespace paddle diff --git a/paddle/function/ConvOpTest.cpp b/paddle/function/ConvOpTest.cpp index 715fa58b559..db8d9fa9da4 100644 --- a/paddle/function/ConvOpTest.cpp +++ b/paddle/function/ConvOpTest.cpp @@ -48,11 +48,13 @@ public: << " outputWidth=" << outputSize << " stride=" << stride << " padding=" << padding; + std::vector paddings = {padding, padding}; + std::vector strides = {stride, stride}; Compare2CpuFunction test(conv1, conv2, FuncConfig() - .set("padding", padding) - .set("stride", stride) + .set("paddings", paddings) + .set("strides", strides) .set("algo", algo)); TensorShape shape0{ diff --git a/paddle/function/GemmConvOp.cpp b/paddle/function/GemmConvOp.cpp index 5e6ee244862..42786e44e0e 100644 --- a/paddle/function/GemmConvOp.cpp +++ b/paddle/function/GemmConvOp.cpp @@ -118,10 +118,10 @@ public: inputWidth, filterHeight, filterWidth, - stride_, - stride_, - padding_, - padding_, + strideH(), + strideW(), + paddingH(), + paddingW(), outputHeight, outputWidth, colData); diff --git a/paddle/function/NaiveConvOp.cpp b/paddle/function/NaiveConvOp.cpp index f13aa880a1e..f5d2aa16ab9 100644 --- a/paddle/function/NaiveConvOp.cpp +++ b/paddle/function/NaiveConvOp.cpp @@ -37,14 +37,16 @@ public: size_t outputChannels, size_t outputHeight, size_t outputWidth, - size_t padding, - size_t stride) { + size_t paddingH, + size_t paddingW, + size_t strideH, + size_t strideW) { for (size_t batch = 0; batch < batchSize; batch++) { for (size_t outC = 0; outC < outputChannels; outC++) { for (size_t outH = 0; outH < outputHeight; outH++) { for (size_t outW = 0; outW < outputWidth; outW++) { - const int inStartH = (outH * stride) - padding; - const int inStartW = (outW * stride) - padding; + const int inStartH = (outH * strideH) - paddingH; + const int inStartW = (outW * strideW) - paddingW; T outValue = (T)0; for (size_t inC = 0; inC < inputChannels; inC++) { for (size_t fH = 0; fH < filterHeight; fH++) { @@ -118,8 +120,10 @@ public: outputChannels, outputHeight, outputWidth, - padding_, - stride_); + paddingH(), + paddingW(), + strideH(), + strideW()); } }; -- GitLab From 0cb8a6669ed04afc526c818bb3907645a16c7a34 Mon Sep 17 00:00:00 2001 From: xuwei06 Date: Fri, 26 May 2017 12:14:07 -0700 Subject: [PATCH 0476/3256] Fix style --- python/paddle/v2/layer.py | 40 +++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/python/paddle/v2/layer.py b/python/paddle/v2/layer.py index ad36364ca8e..5500b8b342a 100644 --- a/python/paddle/v2/layer.py +++ b/python/paddle/v2/layer.py @@ -40,6 +40,7 @@ from paddle.proto.ModelConfig_pb2 import ModelConfig, SubModelConfig __all__ = ['data', 'parse_network'] __layer_map__ = {} + def __wrap__(f): def wrapped(*args, **xargs): out = f(*args, **xargs) @@ -53,6 +54,7 @@ def __wrap__(f): return wrapped + def __need_to_keep__(name): if name in ['StaticInput', 'LayerType', 'layer_support']: return False @@ -99,6 +101,7 @@ def __data_layer__(name, type, **kwargs): l.data_type = type return l + data = __wrap__(__data_layer__) LayerV2 = v1_layers.LayerOutput @@ -107,6 +110,7 @@ LayerV2 = v1_layers.LayerOutput def __get_used_layers__(output_layers, extra_layers=None): layer_names = set() parents = {} + def add_parent(child, parent): if child in parents: parents[child].append(parent) @@ -181,28 +185,25 @@ def __get_used_evaluators__(layer_names): return evaluator_names -def __trim_submodel__(old_submodel, - layer_names, - input_layer_names, - output_layer_names, - evaluator_names): +def __trim_submodel__(old_submodel, layer_names, input_layer_names, + output_layer_names, evaluator_names): submodel = SubModelConfig() submodel.name = old_submodel.name - submodel.layer_names.extend(filter(lambda x: x in layer_names, - old_submodel.layer_names)) - submodel.input_layer_names.extend(filter(lambda x: x in input_layer_names, - submodel.layer_names)) - submodel.output_layer_names.extend(filter(lambda x: x in output_layer_names, - submodel.layer_names)) - submodel.evaluator_names.extend(filter(lambda x: x in evaluator_names, - old_submodel.evaluator_names)) + submodel.layer_names.extend( + filter(lambda x: x in layer_names, old_submodel.layer_names)) + submodel.input_layer_names.extend( + filter(lambda x: x in input_layer_names, submodel.layer_names)) + submodel.output_layer_names.extend( + filter(lambda x: x in output_layer_names, submodel.layer_names)) + submodel.evaluator_names.extend( + filter(lambda x: x in evaluator_names, old_submodel.evaluator_names)) submodel.is_recurrent_layer_group = old_submodel.is_recurrent_layer_group submodel.reversed = old_submodel.reversed - submodel.memories.extend(filter(lambda x: x.link_name in layer_names, - old_submodel.memories)) + submodel.memories.extend( + filter(lambda x: x.link_name in layer_names, old_submodel.memories)) target_inlinkid = (old_submodel.target_inlinkid if old_submodel.HasField('target_inlinkid') else -1) in_links = [] @@ -213,8 +214,8 @@ def __trim_submodel__(old_submodel, target_inlinkid = len(in_links) - 1 submodel.in_links.extend(in_links) - submodel.out_links.extend(filter(lambda x: x.link_name in layer_names, - old_submodel.out_links)) + submodel.out_links.extend( + filter(lambda x: x.link_name in layer_names, old_submodel.out_links)) if old_submodel.HasField('generator'): submodel.generator.CopyFrom(old_submodel.generator) @@ -264,9 +265,8 @@ def parse_network(output_layers, extra_layers=None): for s in cp.g_config.model_config.sub_models: if s.name in submodel_names: - s = __trim_submodel__( - s, layer_names, input_layer_names, output_layer_names, - evaluator_names) + s = __trim_submodel__(s, layer_names, input_layer_names, + output_layer_names, evaluator_names) model_config.sub_models.extend([s]) return model_config -- GitLab From 97c4d23fab10e4c6a502b126d1fa13b83360b147 Mon Sep 17 00:00:00 2001 From: xuwei06 Date: Fri, 26 May 2017 13:58:26 -0700 Subject: [PATCH 0477/3256] Add docs and clean up unused code --- python/paddle/trainer/config_parser.py | 4 +- .../config_parser_utils.py | 3 +- python/paddle/v2/config_base.py | 247 ++++-------------- python/paddle/v2/evaluator.py | 5 +- python/paddle/v2/inference.py | 24 +- python/paddle/v2/layer.py | 38 +-- python/paddle/v2/tests/test_rnn_layer.py | 4 +- python/paddle/v2/topology.py | 29 +- 8 files changed, 91 insertions(+), 263 deletions(-) diff --git a/python/paddle/trainer/config_parser.py b/python/paddle/trainer/config_parser.py index 56e1ba170d8..5d540664a7f 100644 --- a/python/paddle/trainer/config_parser.py +++ b/python/paddle/trainer/config_parser.py @@ -3371,7 +3371,7 @@ def make_importer(config_dir, config_args): return Import -default_settings = dict( +DEFAULT_SETTING = dict( batch_size=None, mini_batch_size=None, algorithm='async_sgd', @@ -3404,7 +3404,7 @@ default_settings = dict( adam_beta2=0.999, adam_epsilon=1e-8, ) -settings = copy.deepcopy(default_settings) +settings = copy.deepcopy(DEFAULT_SETTING) settings_deprecated = dict(usage_ratio=1., ) diff --git a/python/paddle/trainer_config_helpers/config_parser_utils.py b/python/paddle/trainer_config_helpers/config_parser_utils.py index 9e56556d0a4..ee5bbbfb2de 100644 --- a/python/paddle/trainer_config_helpers/config_parser_utils.py +++ b/python/paddle/trainer_config_helpers/config_parser_utils.py @@ -15,7 +15,6 @@ import copy import paddle.trainer.config_parser as config_parser from paddle.proto.TrainerConfig_pb2 import OptimizationConfig - ''' This file is a wrapper of formal config_parser. The main idea of this file is to separete different config logic into different function, such as network configuration @@ -38,7 +37,7 @@ def parse_network_config(network_conf, config_arg_str=''): def parse_optimizer_config(optimizer_conf, config_arg_str=''): - config_parser.settings = copy.deepcopy(config_parser.default_settings) + config_parser.settings = copy.deepcopy(config_parser.DEFAULT_SETTING) optimizer_conf() opt_config = OptimizationConfig() for k, v in config_parser.settings.iteritems(): diff --git a/python/paddle/v2/config_base.py b/python/paddle/v2/config_base.py index acda778e0ae..be8ed2e1e51 100644 --- a/python/paddle/v2/config_base.py +++ b/python/paddle/v2/config_base.py @@ -14,206 +14,55 @@ import collections import re -from paddle.trainer_config_helpers.default_decorators import wrap_name_default import paddle.trainer_config_helpers as conf_helps -from topology import Topology - - -class LayerType(type): - def __new__(cls, name, bases, attrs): - method_name = attrs.get('METHOD_NAME', None) - if method_name is not None: - method = getattr(conf_helps, method_name) - if method.__doc__ is not None: - mapper = attrs.get("__map_docstr__", None) - if mapper is not None: - attrs['__doc__'] = LayerType.__map_docstr__( - mapper(method.__doc__), - method_name=method_name, - name=name) - else: - attrs['__doc__'] = LayerType.__map_docstr__( - method.__doc__, method_name=method_name, name=name) - return super(LayerType, cls).__new__(cls, name, bases, attrs) - - @staticmethod - def __map_docstr__(doc, name, method_name): - assert isinstance(doc, basestring) - - # replace LayerOutput to paddle.v2.config_base.Layer - doc = doc.replace("LayerOutput", "paddle.v2.config_base.Layer") - - doc = doc.replace('ParameterAttribute', - 'paddle.v2.attr.ParameterAttribute') - - doc = re.sub(r'ExtraLayerAttribute[^\s]?', - 'paddle.v2.attr.ExtraAttribute', doc) - - # xxx_layer to xxx - doc = re.sub(r"(?P[a-z]+)_layer", r"\g", doc) - - # XxxxActivation to paddle.v2.Activation.Xxxx - doc = re.sub(r"(?P[A-Z][a-zA-Z]+)Activation", - r"paddle.v2.Activation.\g", doc) - - # TODO(yuyang18): Add more rules if needed. + +__layer_map__ = {} + + +def __map_docstr__(doc, name): + if doc is None: return doc + assert isinstance(doc, basestring) + + # replace LayerOutput to paddle.v2.config_base.Layer + doc = doc.replace("LayerOutput", "paddle.v2.config_base.Layer") + + doc = doc.replace('ParameterAttribute', 'paddle.v2.attr.ParameterAttribute') + + doc = re.sub(r'ExtraLayerAttribute[^\s]?', 'paddle.v2.attr.ExtraAttribute', + doc) + + # xxx_layer to xxx + doc = re.sub(r"(?P[a-z]+)_layer", r"\g", doc) + + # XxxxActivation to paddle.v2.Activation.Xxxx + doc = re.sub(r"(?P[A-Z][a-zA-Z]+)Activation", + r"paddle.v2.Activation.\g", doc) + + # xxx_evaluator to paddle.v2.evaluator.xxx + doc = re.sub(r"(?P[a-z]+)_evaluator", r"evaluator.\g", doc) + + # TODO(yuyang18): Add more rules if needed. + return doc + + +def __convert_to_v2__(f, name, module): + def wrapped(*args, **xargs): + out = f(*args, **xargs) + outs = out + if not isinstance(out, collections.Sequence): + outs = [out] + for l in outs: + if isinstance(l, conf_helps.LayerOutput): + __layer_map__[l.full_name] = l + return out + + wrapped.__doc__ = __map_docstr__(f.__doc__, name) + wrapped.__name__ = name + wrapped.__module__ = module + + return wrapped + -class Layer(object): - __metaclass__ = LayerType - - def __init__(self, name=None, parent_layers=None): - assert isinstance(parent_layers, dict) - self.name = name - self.__context__ = {} - self.__parent_layers__ = parent_layers - # some layer may have some extra parent layer - self.__extra_parent__ = [] - # used for evaluator. - self.__children_layers__ = [] - - def extra_parent(self): - return self.__extra_parent__ - - def append_extra_parent(self, parent): - self.__extra_parent__.append(parent) - - def append_child(self, layer, parent_names): - self.__children_layers__.append((layer, parent_names)) - - def to_proto(self, context): - """ - function to set proto attribute - """ - self.__context__ = context - - # STEP: short cut if this layer is parsed before. - if self.context_name() in context: - if self.use_context_name(): - return context[self.context_name()] - else: - return context[self.name] - - # STEP: parse extra_parent that is not used by this layer but must - # be parsed before this layer. - for p in self.__extra_parent__: - p.to_proto(context=context) - - # STEP: parse parent that is used by this layer, get the result and - # insert into kwargs of the next layer's to_proto_impl method. - kwargs = dict() - for layer_name in self.__parent_layers__: - if not isinstance(self.__parent_layers__[layer_name], - collections.Sequence): - v1_layer = self.__parent_layers__[layer_name].to_proto( - context=context) - else: - v1_layer = map(lambda x: x.to_proto(context=context), - self.__parent_layers__[layer_name]) - kwargs[layer_name] = v1_layer - - # STEP: parse myself and add myself into context. - ret_val = self.to_proto_impl(**kwargs) - if self.context_name() is not None \ - and self.context_name() not in context: - context[self.context_name()] = ret_val - - # STEP: parse children that should be pased after this layer. - for layer, pnames in self.__children_layers__: - drop = False - - # child will only be parsed if all parents are in context. - for pname in pnames: - if pname not in context: - drop = True - break - if drop: - continue - layer.to_proto(context=context) - - # STEP: return v1 layer result - if self.context_name() is None: - return ret_val - elif self.use_context_name(): - return context[self.context_name()] - else: - return context[self.name] - - def to_proto_impl(self, **kwargs): - raise NotImplementedError() - - def context_name(self): - """ - Context name means the context which stores `to_proto_impl` result. - If multiple layer share same context_name, the `to_proto_impl` of them - will be invoked only once. - """ - return self.name - - def use_context_name(self): - return False - - def calculate_size(self): - """ - lazy calculate size of the layer, should be called when to_proto_impl of - this layer is called. - :return: - """ - return self.__context__[self.context_name()].size - - def attr(self): - topo = Topology(self) - return topo.get_layer_proto(self.name) - - -def __convert_to_v2__(method_name, - parent_names, - is_default_name=True, - attach_parent=False): - if is_default_name: - wrapper = wrap_name_default(name_prefix=method_name) - else: - wrapper = None - - class V2LayerImpl(Layer): - METHOD_NAME = method_name - - def __init__(self, **kwargs): - parent_layers = dict() - other_kwargs = dict() - for pname in parent_names: - if pname in kwargs: - parent_layers[pname] = kwargs[pname] - - if attach_parent: - pnames = [x.context_name() for x in parent_layers.values()] - - for pname in parent_layers: - layers = kwargs[pname] - if not isinstance(layers, collections.Sequence): - layers = [layers] - - for layer in layers: - layer.append_child(self, pnames) - - for key in kwargs.keys(): - if key not in parent_names: - other_kwargs[key] = kwargs[key] - - name = kwargs.get('name', None) - super(V2LayerImpl, self).__init__(name, parent_layers) - self.__other_kwargs__ = other_kwargs - - if wrapper is not None: - __init__ = wrapper(__init__) - - def to_proto_impl(self, **kwargs): - args = dict() - for each in kwargs: - args[each] = kwargs[each] - for each in self.__other_kwargs__: - args[each] = self.__other_kwargs__[each] - return getattr(conf_helps, method_name)(**args) - - return V2LayerImpl +Layer = conf_helps.LayerOutput diff --git a/python/paddle/v2/evaluator.py b/python/paddle/v2/evaluator.py index c474f74235b..eaaadbe53bc 100644 --- a/python/paddle/v2/evaluator.py +++ b/python/paddle/v2/evaluator.py @@ -13,8 +13,8 @@ # limitations under the License. import paddle.trainer_config_helpers.evaluators as evs -import inspect from config_base import __convert_to_v2__ +import inspect __all__ = [] @@ -27,7 +27,8 @@ def initialize(): __ev__ = getattr(evs, __ev_name__) __new_name__ = convert_to_new_name(__ev_name__) - globals()[__new_name__] = __ev__ + globals()[__new_name__] = __convert_to_v2__(__ev__, __new_name__, + __name__) globals()[__new_name__].__name__ = __new_name__ __all__.append(__new_name__) diff --git a/python/paddle/v2/inference.py b/python/paddle/v2/inference.py index 139339902e9..34b73086013 100644 --- a/python/paddle/v2/inference.py +++ b/python/paddle/v2/inference.py @@ -12,9 +12,9 @@ class Inference(object): """ Inference combines neural network output and parameters together to do inference. - + .. code-block:: python - + inferer = Inference(output_layer=prediction, parameters=parameters) for data_batch in batches: print inferer.infer(data_batch) @@ -92,8 +92,8 @@ def infer(output_layer, parameters, input, feeding=None, field='value'): .. code-block:: python - result = paddle.infer(output_layer=prediction, - parameters=parameters, + result = paddle.infer(output_layer=prediction, + parameters=parameters, input=SomeData) print result @@ -101,14 +101,14 @@ def infer(output_layer, parameters, input, feeding=None, field='value'): .. code-block:: python - result = paddle.infer(output_layer=[prediction1, prediction2], - parameters=parameters, + result = paddle.infer(output_layer=[prediction1, prediction2], + parameters=parameters, input=SomeData, field=[id, value]]) print result :param output_layer: output of the neural network that would be inferred - :type output_layer: paddle.v2.config_base.Layer or a list of + :type output_layer: paddle.v2.config_base.Layer or a list of paddle.v2.config_base.Layer :param parameters: parameters of the neural network. :type parameters: paddle.v2.parameters.Parameters @@ -117,14 +117,14 @@ def infer(output_layer, parameters, input, feeding=None, field='value'): :type input: collections.Iterable :param feeding: Reader dictionary. Default could generate from input value. - :param field: The prediction field. It should in [`value`, `id`, `prob`]. - `value` and `prob` mean return the prediction probabilities, + :param field: The prediction field. It should in [`value`, `id`, `prob`]. + `value` and `prob` mean return the prediction probabilities, `id` means return the prediction labels. Default is `value`. - Note that `prob` only used when output_layer is beam_search + Note that `prob` only used when output_layer is beam_search or max_id. :type field: str - :return: The prediction result. If there are multiple outout_layers and fields, - the return order is outout_layer1.field1, outout_layer2.field1, ..., + :return: The prediction result. If there are multiple outout_layers and fields, + the return order is outout_layer1.field1, outout_layer2.field1, ..., outout_layer1.field2, outout_layer2.field2 ... :rtype: numpy.ndarray """ diff --git a/python/paddle/v2/layer.py b/python/paddle/v2/layer.py index 5500b8b342a..8717619c475 100644 --- a/python/paddle/v2/layer.py +++ b/python/paddle/v2/layer.py @@ -33,26 +33,14 @@ The primary usage shows below. import collections import copy +import re import paddle.trainer_config_helpers.layers as v1_layers import paddle.trainer.config_parser as cp from paddle.proto.ModelConfig_pb2 import ModelConfig, SubModelConfig +from config_base import __convert_to_v2__ +import config_base __all__ = ['data', 'parse_network'] -__layer_map__ = {} - - -def __wrap__(f): - def wrapped(*args, **xargs): - out = f(*args, **xargs) - outs = out - if not isinstance(out, collections.Sequence): - outs = [out] - for l in outs: - if isinstance(l, v1_layers.LayerOutput): - __layer_map__[l.full_name] = l - return out - - return wrapped def __need_to_keep__(name): @@ -90,7 +78,7 @@ for name in v1_layers.__all__: continue new_name = __convert_name__(name) if callable(obj) and __need_to_wrap__(name): - globals()[new_name] = __wrap__(obj) + globals()[new_name] = __convert_to_v2__(obj, new_name, __name__) else: globals()[new_name] = obj __all__.append(new_name) @@ -102,9 +90,21 @@ def __data_layer__(name, type, **kwargs): return l -data = __wrap__(__data_layer__) +def __map_data_docstr__(doc): + doc = re.sub(r'(data = [^\)]+)\).*', + "data = paddle.layer.data(name=\"input\", " + "type=paddle.data_type.dense_vector(1000))", doc) + + doc = re.sub(r':param size:.*', ':param type: Data type of this data layer', + doc) + doc = re.sub(r':type size:.*', ":type size: paddle.v2.data_type.InputType", + doc) + return doc + + +__data_layer__.__doc__ = __map_data_docstr__(v1_layers.data_layer.__doc__) -LayerV2 = v1_layers.LayerOutput +data = __convert_to_v2__(__data_layer__, 'name', __name__) def __get_used_layers__(output_layers, extra_layers=None): @@ -273,7 +273,7 @@ def parse_network(output_layers, extra_layers=None): def get_layer(name): - return __layer_map__.get(name) + return config_base.__layer_map__.get(name) cp.begin_parse() diff --git a/python/paddle/v2/tests/test_rnn_layer.py b/python/paddle/v2/tests/test_rnn_layer.py index b334f3b1ffa..192b0ee678b 100644 --- a/python/paddle/v2/tests/test_rnn_layer.py +++ b/python/paddle/v2/tests/test_rnn_layer.py @@ -32,6 +32,7 @@ class RNNTest(unittest.TestCase): def parse_old_rnn(): reset_parser() + def step(y): mem = conf_helps.memory(name="rnn_state", size=hidden_dim) out = conf_helps.fc_layer( @@ -52,6 +53,7 @@ class RNNTest(unittest.TestCase): def parse_new_rnn(): reset_parser() + def new_step(y): mem = layer.memory(name="rnn_state", size=hidden_dim) out = layer.fc(input=[y, mem], @@ -72,7 +74,6 @@ class RNNTest(unittest.TestCase): parse_new_rnn().splitlines(1)) print ''.join(diff) - def test_sequence_rnn_multi_input(self): dict_dim = 10 word_dim = 8 @@ -81,6 +82,7 @@ class RNNTest(unittest.TestCase): def parse_old_rnn(): reset_parser() + def test(): data = conf_helps.data_layer(name="word", size=dict_dim) label = conf_helps.data_layer(name="label", size=label_dim) diff --git a/python/paddle/v2/topology.py b/python/paddle/v2/topology.py index 962d5ab76d1..f3bb4d5f10d 100644 --- a/python/paddle/v2/topology.py +++ b/python/paddle/v2/topology.py @@ -17,34 +17,11 @@ import collections from paddle.proto.ModelConfig_pb2 import ModelConfig import paddle.trainer_config_helpers as conf_helps import layer as v2_layer +import config_base __all__ = ['Topology'] -def __flatten__(lis): - """ - Given a list, possibly nested to any level, return it flattened. - """ - new_lis = [] - for item in lis: - if isinstance(item, collections.Sequence): - new_lis.extend(__flatten__(item)) - else: - new_lis.append(item) - return new_lis - - -def __bfs_travel__(callback, *layers): - layers = __flatten__(layers) - for each_layer in layers: - __break__ = callback(each_layer) - if __break__: - return - __layers__ = each_layer.__parent_layers__.values() + \ - each_layer.extra_parent() - __bfs_travel__(callback, *__layers__) - - class Topology(object): """ Topology is used to store the information about all layers @@ -125,5 +102,5 @@ class Topology(object): def __check_layer_type__(layer): - if not isinstance(layer, v2_layer.LayerV2): - raise ValueError('layer should have type paddle.layer.Layer') + if not isinstance(layer, config_base.Layer): + raise ValueError('layer should have type paddle.v2.config_base.Layer') -- GitLab From 9e774a1cdf1d107bc0ba67aca82799a0a7a9d21f Mon Sep 17 00:00:00 2001 From: qiaolongfei Date: Sat, 27 May 2017 07:47:09 +0800 Subject: [PATCH 0478/3256] remove duplicate code of python/paddle/trainer_config_helpers/config_parser.py --- .../trainer_config_helpers/config_parser.py | 38 ------------------- 1 file changed, 38 deletions(-) delete mode 100644 python/paddle/trainer_config_helpers/config_parser.py diff --git a/python/paddle/trainer_config_helpers/config_parser.py b/python/paddle/trainer_config_helpers/config_parser.py deleted file mode 100644 index 4b91b8d2824..00000000000 --- a/python/paddle/trainer_config_helpers/config_parser.py +++ /dev/null @@ -1,38 +0,0 @@ -# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import paddle.trainer.config_parser as config_parser -''' -This file is a wrapper of formal config_parser. The main idea of this file is to -separete different config logic into different function, such as network configuration - and optimizer configuration. -''' - -__all__ = [ - "parse_trainer_config", "parse_network_config", "parse_optimizer_config" -] - - -def parse_trainer_config(trainer_conf, config_arg_str): - return config_parser.parse_config(trainer_conf, config_arg_str) - - -def parse_network_config(network_conf): - config = config_parser.parse_config(network_conf, '') - return config.model_config - - -def parse_optimizer_config(optimizer_conf): - config = config_parser.parse_config(optimizer_conf, '') - return config.opt_config -- GitLab From a868d010658642c3612ca37b488123d63623a967 Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Thu, 25 May 2017 20:53:13 -0400 Subject: [PATCH 0479/3256] add cgo wrapper for recordio, make go_cmake automatically download go dependency --- paddle/go/CMakeLists.txt | 11 - paddle/go/adder.go | 10 - paddle/go/cclient/CMakeLists.txt | 31 +-- paddle/go/cclient/cclient.go | 7 +- paddle/go/cgo_test.cc | 5 - .../cmake/CMakeDetermineGoCompiler.cmake | 2 +- .../cmake/CMakeGoCompiler.cmake.in | 0 .../cmake/CMakeGoInformation.cmake | 0 .../cmake/CMakeTestGoCompiler.cmake | 0 paddle/go/{cclient => }/cmake/flags.cmake | 4 +- paddle/go/{cclient => }/cmake/golang.cmake | 40 ++-- paddle/go/crecordio/CMakeLists.txt | 12 + paddle/go/crecordio/crecordio.go | 208 ++++++++++++++++++ paddle/go/crecordio/register.go | 61 +++++ paddle/go/crecordio/test/CMakeLists.txt | 8 + paddle/go/crecordio/test/test.c | 31 +++ paddle/go/recordio/README.md | 5 +- 17 files changed, 361 insertions(+), 74 deletions(-) delete mode 100644 paddle/go/CMakeLists.txt delete mode 100644 paddle/go/adder.go delete mode 100644 paddle/go/cgo_test.cc rename paddle/go/{cclient => }/cmake/CMakeDetermineGoCompiler.cmake (94%) rename paddle/go/{cclient => }/cmake/CMakeGoCompiler.cmake.in (100%) rename paddle/go/{cclient => }/cmake/CMakeGoInformation.cmake (100%) rename paddle/go/{cclient => }/cmake/CMakeTestGoCompiler.cmake (100%) rename paddle/go/{cclient => }/cmake/flags.cmake (95%) rename paddle/go/{cclient => }/cmake/golang.cmake (50%) create mode 100644 paddle/go/crecordio/CMakeLists.txt create mode 100644 paddle/go/crecordio/crecordio.go create mode 100644 paddle/go/crecordio/register.go create mode 100644 paddle/go/crecordio/test/CMakeLists.txt create mode 100644 paddle/go/crecordio/test/test.c diff --git a/paddle/go/CMakeLists.txt b/paddle/go/CMakeLists.txt deleted file mode 100644 index 51c5252d663..00000000000 --- a/paddle/go/CMakeLists.txt +++ /dev/null @@ -1,11 +0,0 @@ -include_directories(${CMAKE_CURRENT_BINARY_DIR}) - -go_library(adder SRCS adder.go) - -if (WITH_TESTING) - cc_test(cgo_test - SRCS - cgo_test.cc - DEPS - adder) -endif() diff --git a/paddle/go/adder.go b/paddle/go/adder.go deleted file mode 100644 index e14f40fd9fe..00000000000 --- a/paddle/go/adder.go +++ /dev/null @@ -1,10 +0,0 @@ -package main - -import "C" - -//export GoAdder -func GoAdder(x, y int) int { - return x + y -} - -func main() {} // Required but ignored diff --git a/paddle/go/cclient/CMakeLists.txt b/paddle/go/cclient/CMakeLists.txt index c85ff3db09d..e3e9fa9f1a4 100644 --- a/paddle/go/cclient/CMakeLists.txt +++ b/paddle/go/cclient/CMakeLists.txt @@ -1,31 +1,12 @@ cmake_minimum_required(VERSION 3.0) -if(GTEST_INCLUDE_DIR AND GTEST_LIBRARIES) - message("-- Found gtest (include: ${GTEST_INCLUDE_DIR}, library: ${GTEST_LIBRARIES})") -else() - # find cmake directory modules - get_filename_component(PARENT_DIR ${CMAKE_CURRENT_SOURCE_DIR} DIRECTORY) - get_filename_component(PARENT_DIR ${PARENT_DIR} DIRECTORY) - get_filename_component(PARENT_DIR ${PARENT_DIR} DIRECTORY) +get_filename_component(PARENT_DIR ${CMAKE_CURRENT_SOURCE_DIR} DIRECTORY) +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${PARENT_DIR}/cmake") - set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${PARENT_DIR}/cmake") +project(cxx_go C Go) - # enable c++11 - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") +include(golang) +include(flags) - # enable gtest - set(THIRD_PARTY_PATH ./third_party) - set(WITH_TESTING ON) - include(external/gtest) -endif() - -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake") - -project(cxx_go CXX C Go) - -include(cmake/golang.cmake) -include(cmake/flags.cmake) - -ExternalGoProject_Add(pserver github.com/PaddlePaddle/Paddle/paddle/go/pserver) -add_go_library(client STATIC pserver) +add_go_library(client STATIC) add_subdirectory(test) diff --git a/paddle/go/cclient/cclient.go b/paddle/go/cclient/cclient.go index dc86d47e8d0..654b6f68a4f 100644 --- a/paddle/go/cclient/cclient.go +++ b/paddle/go/cclient/cclient.go @@ -78,8 +78,11 @@ func cArrayToSlice(p unsafe.Pointer, len int) []byte { return nil } - // create a Go clice backed by a C array, - // reference: https://github.com/golang/go/wiki/cgo#turning-c-arrays-into-go-slices + // create a Go clice backed by a C array, reference: + // https://github.com/golang/go/wiki/cgo#turning-c-arrays-into-go-slices + // + // Go garbage collector will not interact with this data, need + // to be freed from C side. return (*[1 << 30]byte)(p)[:len:len] } diff --git a/paddle/go/cgo_test.cc b/paddle/go/cgo_test.cc deleted file mode 100644 index 64efa606fff..00000000000 --- a/paddle/go/cgo_test.cc +++ /dev/null @@ -1,5 +0,0 @@ -#include -#include "gtest/gtest.h" -#include "libadder.h" - -TEST(Cgo, Invoke) { EXPECT_EQ(GoAdder(30, 12), 42); } diff --git a/paddle/go/cclient/cmake/CMakeDetermineGoCompiler.cmake b/paddle/go/cmake/CMakeDetermineGoCompiler.cmake similarity index 94% rename from paddle/go/cclient/cmake/CMakeDetermineGoCompiler.cmake rename to paddle/go/cmake/CMakeDetermineGoCompiler.cmake index b3f8fbe271d..a9bb6906c74 100644 --- a/paddle/go/cclient/cmake/CMakeDetermineGoCompiler.cmake +++ b/paddle/go/cmake/CMakeDetermineGoCompiler.cmake @@ -38,7 +38,7 @@ endif() mark_as_advanced(CMAKE_Go_COMPILER) -configure_file(${CMAKE_CURRENT_SOURCE_DIR}/cmake/CMakeGoCompiler.cmake.in +configure_file(${CMAKE_MODULE_PATH}/CMakeGoCompiler.cmake.in ${CMAKE_PLATFORM_INFO_DIR}/CMakeGoCompiler.cmake @ONLY) set(CMAKE_Go_COMPILER_ENV_VAR "GO_COMPILER") diff --git a/paddle/go/cclient/cmake/CMakeGoCompiler.cmake.in b/paddle/go/cmake/CMakeGoCompiler.cmake.in similarity index 100% rename from paddle/go/cclient/cmake/CMakeGoCompiler.cmake.in rename to paddle/go/cmake/CMakeGoCompiler.cmake.in diff --git a/paddle/go/cclient/cmake/CMakeGoInformation.cmake b/paddle/go/cmake/CMakeGoInformation.cmake similarity index 100% rename from paddle/go/cclient/cmake/CMakeGoInformation.cmake rename to paddle/go/cmake/CMakeGoInformation.cmake diff --git a/paddle/go/cclient/cmake/CMakeTestGoCompiler.cmake b/paddle/go/cmake/CMakeTestGoCompiler.cmake similarity index 100% rename from paddle/go/cclient/cmake/CMakeTestGoCompiler.cmake rename to paddle/go/cmake/CMakeTestGoCompiler.cmake diff --git a/paddle/go/cclient/cmake/flags.cmake b/paddle/go/cmake/flags.cmake similarity index 95% rename from paddle/go/cclient/cmake/flags.cmake rename to paddle/go/cmake/flags.cmake index 062d5ab660d..a167c432a92 100644 --- a/paddle/go/cclient/cmake/flags.cmake +++ b/paddle/go/cmake/flags.cmake @@ -21,7 +21,7 @@ function(CheckCompilerCXX11Flag) if (${CMAKE_CXX_COMPILER_VERSION} VERSION_LESS 3.3) message(FATAL_ERROR "Unsupported Clang version. Clang >= 3.3 required.") endif() - endif() + endif() endif() endfunction() @@ -42,4 +42,4 @@ if (CUDA_VERSION VERSION_GREATER "8.0" OR CUDA_VERSION VERSION_EQUAL "8.0") list(APPEND __arch_flags " -gencode arch=compute_60,code=sm_60") endif() -set(CUDA_NVCC_FLAGS ${__arch_flags} ${CUDA_NVCC_FLAGS}) \ No newline at end of file +set(CUDA_NVCC_FLAGS ${__arch_flags} ${CUDA_NVCC_FLAGS}) diff --git a/paddle/go/cclient/cmake/golang.cmake b/paddle/go/cmake/golang.cmake similarity index 50% rename from paddle/go/cclient/cmake/golang.cmake rename to paddle/go/cmake/golang.cmake index 5d39868bfdf..caddaae1bf4 100644 --- a/paddle/go/cclient/cmake/golang.cmake +++ b/paddle/go/cmake/golang.cmake @@ -1,22 +1,7 @@ set(GOPATH "${CMAKE_CURRENT_BINARY_DIR}/go") file(MAKE_DIRECTORY ${GOPATH}) - -function(ExternalGoProject_Add TARG) - add_custom_target(${TARG} env GOPATH=${GOPATH} ${CMAKE_Go_COMPILER} get ${ARGN}) -endfunction(ExternalGoProject_Add) - -function(add_go_executable NAME) - file(GLOB GO_SOURCE RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "*.go") - add_custom_command(OUTPUT ${OUTPUT_DIR}/.timestamp - COMMAND env GOPATH=${GOPATH} ${CMAKE_Go_COMPILER} build - -o "${CMAKE_CURRENT_BINARY_DIR}/${NAME}" - ${CMAKE_GO_FLAGS} ${GO_SOURCE} - WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}) - - add_custom_target(${NAME} ALL DEPENDS ${OUTPUT_DIR}/.timestamp ${ARGN}) - install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/${NAME} DESTINATION bin) -endfunction(add_go_executable) - +set(PADDLE_IN_GOPATH "${GOPATH}/src/github.com/PaddlePaddle") +file(MAKE_DIRECTORY ${PADDLE_IN_GOPATH}) function(ADD_GO_LIBRARY NAME BUILD_TYPE) if(BUILD_TYPE STREQUAL "STATIC") @@ -32,6 +17,26 @@ function(ADD_GO_LIBRARY NAME BUILD_TYPE) endif() file(GLOB GO_SOURCE RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "*.go") + file(RELATIVE_PATH rel ${CMAKE_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}) + + # find Paddle directory. + get_filename_component(PARENT_DIR ${CMAKE_CURRENT_SOURCE_DIR} DIRECTORY) + get_filename_component(PARENT_DIR ${PARENT_DIR} DIRECTORY) + get_filename_component(PADDLE_DIR ${PARENT_DIR} DIRECTORY) + + # automatically get all dependencies specified in the source code + # for given target. + add_custom_target(goGet env GOPATH=${GOPATH} ${CMAKE_Go_COMPILER} get -d ${rel}/...) + + # make a symlink that references Paddle inside $GOPATH, so go get + # will use the local changes in Paddle rather than checkout Paddle + # in github. + if(NOT EXISTS ${PADDLE_IN_GOPATH}) + add_custom_target(copyPaddle + COMMAND ln -s ${PADDLE_DIR} ${PADDLE_IN_GOPATH}) + add_dependencies(goGet copyPaddle) + endif() + add_custom_command(OUTPUT ${OUTPUT_DIR}/.timestamp COMMAND env GOPATH=${GOPATH} ${CMAKE_Go_COMPILER} build ${BUILD_MODE} -o "${CMAKE_CURRENT_BINARY_DIR}/${LIB_NAME}" @@ -39,6 +44,7 @@ function(ADD_GO_LIBRARY NAME BUILD_TYPE) WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}) add_custom_target(${NAME} ALL DEPENDS ${OUTPUT_DIR}/.timestamp ${ARGN}) + add_dependencies(${NAME} goGet) if(NOT BUILD_TYPE STREQUAL "STATIC") install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/${LIB_NAME} DESTINATION bin) diff --git a/paddle/go/crecordio/CMakeLists.txt b/paddle/go/crecordio/CMakeLists.txt new file mode 100644 index 00000000000..db8f556e50b --- /dev/null +++ b/paddle/go/crecordio/CMakeLists.txt @@ -0,0 +1,12 @@ +cmake_minimum_required(VERSION 3.0) + +get_filename_component(PARENT_DIR ${CMAKE_CURRENT_SOURCE_DIR} DIRECTORY) +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${PARENT_DIR}/cmake") + +project(cxx_go C Go) + +include(golang) +include(flags) + +add_go_library(recordio STATIC) +add_subdirectory(test) diff --git a/paddle/go/crecordio/crecordio.go b/paddle/go/crecordio/crecordio.go new file mode 100644 index 00000000000..3335d0795f0 --- /dev/null +++ b/paddle/go/crecordio/crecordio.go @@ -0,0 +1,208 @@ +package main + +/* +#include + +typedef int reader; +typedef int writer; +*/ +import "C" + +import ( + "io" + "log" + "os" + "path/filepath" + "strings" + "unsafe" + + "github.com/PaddlePaddle/Paddle/paddle/go/recordio" +) + +var nullPtr = unsafe.Pointer(uintptr(0)) + +type writer struct { + w *recordio.Writer + f *os.File +} + +type reader struct { + buffer chan []byte + cancel chan struct{} +} + +func read(paths []string, buffer chan<- []byte, cancel chan struct{}) { + var curFile *os.File + var curScanner *recordio.Scanner + var pathIdx int + + var nextFile func() bool + nextFile = func() bool { + if pathIdx >= len(paths) { + return false + } + + path := paths[pathIdx] + pathIdx++ + f, err := os.Open(path) + if err != nil { + return nextFile() + } + + idx, err := recordio.LoadIndex(f) + if err != nil { + log.Println(err) + err = f.Close() + if err != nil { + log.Println(err) + } + + return nextFile() + } + + curFile = f + curScanner = recordio.NewScanner(f, idx, 0, -1) + return true + } + + more := nextFile() + if !more { + close(buffer) + return + } + + closeFile := func() { + err := curFile.Close() + if err != nil { + log.Println(err) + } + curFile = nil + } + + for { + for curScanner.Scan() { + select { + case buffer <- curScanner.Record(): + case <-cancel: + close(buffer) + closeFile() + return + } + } + + if err := curScanner.Error(); err != nil && err != io.EOF { + log.Println(err) + } + + closeFile() + more := nextFile() + if !more { + close(buffer) + return + } + } +} + +//export paddle_new_writer +func paddle_new_writer(path *C.char) C.writer { + p := C.GoString(path) + f, err := os.Create(p) + if err != nil { + log.Println(err) + return -1 + } + + w := recordio.NewWriter(f, -1, -1) + writer := &writer{f: f, w: w} + return addWriter(writer) +} + +func cArrayToSlice(p unsafe.Pointer, len int) []byte { + if p == nullPtr { + return nil + } + + // create a Go clice backed by a C array, reference: + // https://github.com/golang/go/wiki/cgo#turning-c-arrays-into-go-slices + // + // Go garbage collector will not interact with this data, need + // to be freed from C side. + return (*[1 << 30]byte)(p)[:len:len] +} + +//export paddle_writer_write +func paddle_writer_write(writer C.writer, buf *C.uchar, size C.int) int { + w := getWriter(writer) + b := cArrayToSlice(unsafe.Pointer(buf), int(size)) + _, err := w.w.Write(b) + if err != nil { + log.Println(err) + return -1 + } + + return 0 +} + +//export paddle_writer_release +func paddle_writer_release(writer C.writer) { + w := removeWriter(writer) + w.w.Close() + w.f.Close() +} + +//export paddle_new_reader +func paddle_new_reader(path *C.char, bufferSize C.int) C.reader { + p := C.GoString(path) + ss := strings.Split(p, ",") + var paths []string + for _, s := range ss { + match, err := filepath.Glob(s) + if err != nil { + log.Printf("error applying glob to %s: %v\n", s, err) + return -1 + } + + paths = append(paths, match...) + } + + if len(paths) == 0 { + log.Println("no valid path provided.", p) + return -1 + } + + buffer := make(chan []byte, int(bufferSize)) + cancel := make(chan struct{}) + r := &reader{buffer: buffer, cancel: cancel} + go read(paths, buffer, cancel) + return addReader(r) +} + +//export paddle_reader_next_item +func paddle_reader_next_item(reader C.reader, size *C.int) *C.uchar { + r := getReader(reader) + buf, ok := <-r.buffer + if !ok { + // channel closed and empty, reached EOF. + *size = -1 + return (*C.uchar)(nullPtr) + } + + if len(buf) == 0 { + // empty item + *size = 0 + return (*C.uchar)(nullPtr) + } + + ptr := C.malloc(C.size_t(len(buf))) + C.memcpy(ptr, unsafe.Pointer(&buf[0]), C.size_t(len(buf))) + *size = C.int(len(buf)) + return (*C.uchar)(ptr) +} + +//export paddle_reader_release +func paddle_reader_release(reader C.reader) { + r := removeReader(reader) + close(r.cancel) +} + +func main() {} // Required but ignored diff --git a/paddle/go/crecordio/register.go b/paddle/go/crecordio/register.go new file mode 100644 index 00000000000..61dfdbd4ab6 --- /dev/null +++ b/paddle/go/crecordio/register.go @@ -0,0 +1,61 @@ +package main + +/* +typedef int reader; +typedef int writer; +*/ +import "C" + +import "sync" + +var mu sync.Mutex +var handleMap = make(map[C.reader]*reader) +var curHandle C.reader +var writerMap = make(map[C.writer]*writer) +var curWriterHandle C.writer + +func addReader(r *reader) C.reader { + mu.Lock() + defer mu.Unlock() + reader := curHandle + curHandle++ + handleMap[reader] = r + return reader +} + +func getReader(reader C.reader) *reader { + mu.Lock() + defer mu.Unlock() + return handleMap[reader] +} + +func removeReader(reader C.reader) *reader { + mu.Lock() + defer mu.Unlock() + r := handleMap[reader] + delete(handleMap, reader) + return r +} + +func addWriter(w *writer) C.writer { + mu.Lock() + defer mu.Unlock() + writer := curWriterHandle + curWriterHandle++ + writerMap[writer] = w + return writer +} + +func getWriter(writer C.writer) *writer { + mu.Lock() + defer mu.Unlock() + return writerMap[writer] +} + +func removeWriter(writer C.writer) *writer { + mu.Lock() + defer mu.Unlock() + w := writerMap[writer] + delete(writerMap, writer) + return w +} diff --git a/paddle/go/crecordio/test/CMakeLists.txt b/paddle/go/crecordio/test/CMakeLists.txt new file mode 100644 index 00000000000..bac1006ae12 --- /dev/null +++ b/paddle/go/crecordio/test/CMakeLists.txt @@ -0,0 +1,8 @@ +cmake_minimum_required(VERSION 3.0) + +include_directories(${CMAKE_BINARY_DIR}) + +add_executable(recordio_test test.c) +add_dependencies(recordio_test recordio) +set (CMAKE_EXE_LINKER_FLAGS "-pthread") +target_link_libraries(recordio_test ${CMAKE_BINARY_DIR}/librecordio.a) diff --git a/paddle/go/crecordio/test/test.c b/paddle/go/crecordio/test/test.c new file mode 100644 index 00000000000..bbf5964fd39 --- /dev/null +++ b/paddle/go/crecordio/test/test.c @@ -0,0 +1,31 @@ +#include +#include + +#include "librecordio.h" + +void panic() { + // TODO(helin): fix: gtest using cmake is not working, using this + // hacky way for now. + *(void*)0; +} + +int main() { + writer w = paddle_new_writer("/tmp/test"); + paddle_writer_write(w, "hello", 6); + paddle_writer_write(w, "hi", 3); + paddle_writer_release(w); + + reader r = paddle_new_reader("/tmp/test", 10); + int size; + unsigned char* item = paddle_reader_next_item(r, &size); + if (!strcmp(item, "hello") || size != 6) { + panic(); + } + free(item); + + item = paddle_reader_next_item(r, &size); + if (!strcmp(item, "hi") || size != 2) { + panic(); + } + free(item); +} diff --git a/paddle/go/recordio/README.md b/paddle/go/recordio/README.md index 8b0b9308b1a..fbf568ceba4 100644 --- a/paddle/go/recordio/README.md +++ b/paddle/go/recordio/README.md @@ -8,6 +8,7 @@ w := recordio.NewWriter(f) w.Write([]byte("Hello")) w.Write([]byte("World!")) w.Close() +f.Close() ``` ## Read @@ -18,6 +19,7 @@ w.Close() f, e := os.Open("a_file.recordio") idx, e := recordio.LoadIndex(f) fmt.Println("Total records: ", idx.Len()) + f.Close() ``` 2. Create one or more scanner to read a range of records. The @@ -30,7 +32,8 @@ w.Close() for s.Scan() { fmt.Println(string(s.Record())) } - if s.Err() != nil && s.Err() != io.EOF { + if s.Error() != nil && s.Error() != io.EOF { log.Fatalf("Something wrong with scanning: %v", e) } + f.Close() ``` -- GitLab From 9e8503b64cf49e3cc0eb531d227e3681597720d2 Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Fri, 26 May 2017 23:54:48 +0000 Subject: [PATCH 0480/3256] fix comment --- paddle/go/cclient/cclient.go | 2 +- paddle/go/crecordio/crecordio.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/paddle/go/cclient/cclient.go b/paddle/go/cclient/cclient.go index 654b6f68a4f..ee2d9d24fd8 100644 --- a/paddle/go/cclient/cclient.go +++ b/paddle/go/cclient/cclient.go @@ -82,7 +82,7 @@ func cArrayToSlice(p unsafe.Pointer, len int) []byte { // https://github.com/golang/go/wiki/cgo#turning-c-arrays-into-go-slices // // Go garbage collector will not interact with this data, need - // to be freed from C side. + // to be freed properly. return (*[1 << 30]byte)(p)[:len:len] } diff --git a/paddle/go/crecordio/crecordio.go b/paddle/go/crecordio/crecordio.go index 3335d0795f0..cfc15d29a66 100644 --- a/paddle/go/crecordio/crecordio.go +++ b/paddle/go/crecordio/crecordio.go @@ -126,7 +126,7 @@ func cArrayToSlice(p unsafe.Pointer, len int) []byte { // https://github.com/golang/go/wiki/cgo#turning-c-arrays-into-go-slices // // Go garbage collector will not interact with this data, need - // to be freed from C side. + // to be freed properly. return (*[1 << 30]byte)(p)[:len:len] } -- GitLab From f074198e2795658030d5aeca1e7038373d050b74 Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Sat, 27 May 2017 00:31:41 +0000 Subject: [PATCH 0481/3256] clang format --- paddle/go/crecordio/test/test.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/paddle/go/crecordio/test/test.c b/paddle/go/crecordio/test/test.c index bbf5964fd39..598b0965d8a 100644 --- a/paddle/go/crecordio/test/test.c +++ b/paddle/go/crecordio/test/test.c @@ -1,5 +1,5 @@ -#include #include +#include #include "librecordio.h" @@ -22,7 +22,7 @@ int main() { panic(); } free(item); - + item = paddle_reader_next_item(r, &size); if (!strcmp(item, "hi") || size != 2) { panic(); -- GitLab From cab5076860aa7ecbd8595aeb47e0e2536d401c7c Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Sat, 27 May 2017 00:42:38 +0000 Subject: [PATCH 0482/3256] do not include paddle/go into cmake yet. --- paddle/CMakeLists.txt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/paddle/CMakeLists.txt b/paddle/CMakeLists.txt index cf31b4a3429..9898dc083eb 100644 --- a/paddle/CMakeLists.txt +++ b/paddle/CMakeLists.txt @@ -9,9 +9,10 @@ add_subdirectory(pserver) add_subdirectory(trainer) add_subdirectory(scripts) -if(CMAKE_Go_COMPILER) - add_subdirectory(go) -endif() +# Do not build go directory until go cmake is working smoothly. +# if(CMAKE_Go_COMPILER) +# add_subdirectory(go) +# endif() find_package(Boost QUIET) -- GitLab From 0e80dadf37e6ba532fcccf17d52ebd6f746ec1e6 Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Sat, 27 May 2017 00:54:10 +0000 Subject: [PATCH 0483/3256] release reader in c example --- paddle/go/crecordio/test/test.c | 1 + 1 file changed, 1 insertion(+) diff --git a/paddle/go/crecordio/test/test.c b/paddle/go/crecordio/test/test.c index 598b0965d8a..5461a0911f5 100644 --- a/paddle/go/crecordio/test/test.c +++ b/paddle/go/crecordio/test/test.c @@ -28,4 +28,5 @@ int main() { panic(); } free(item); + paddle_reader_release(r); } -- GitLab From e73f4a7944f6859814d2fe550cddcee403056b23 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Sat, 27 May 2017 12:38:58 +0800 Subject: [PATCH 0484/3256] Set protobuf version when PROTOBUF_ROOT set --- cmake/external/protobuf.cmake | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/cmake/external/protobuf.cmake b/cmake/external/protobuf.cmake index da46eaff501..7340394b1e1 100644 --- a/cmake/external/protobuf.cmake +++ b/cmake/external/protobuf.cmake @@ -17,9 +17,14 @@ INCLUDE(ExternalProject) macro(PROMPT_PROTOBUF_LIB) MESSAGE(STATUS "Protobuf protoc executable: ${PROTOBUF_PROTOC_EXECUTABLE}") MESSAGE(STATUS "Protobuf library: ${PROTOBUF_LIBRARY}") + MESSAGE(STATUS "Protobuf version: ${PROTOBUF_VERSION}") INCLUDE_DIRECTORIES(${PROTOBUF_INCLUDE_DIR}) RETURN() endmacro() +macro(SET_PROTOBUF_VERSION) + EXEC_PROGRAM(${PROTOBUF_PROTOC_EXECUTABLE} ARGS --version OUTPUT_VARIABLE PROTOBUF_VERSION) + STRING(REGEX MATCH "[0-9]+.[0-9]+" PROTOBUF_VERSION "${PROTOBUF_VERSION}") +endmacro() set(PROTOBUF_ROOT "" CACHE PATH "Folder contains protobuf") if (NOT "${PROTOBUF_ROOT}" STREQUAL "") @@ -30,6 +35,7 @@ if (NOT "${PROTOBUF_ROOT}" STREQUAL "") find_program(PROTOBUF_PROTOC_EXECUTABLE protoc PATHS ${PROTOBUF_ROOT}/bin) if (PROTOBUF_INCLUDE_DIR AND PROTOBUF_LIBRARY AND PROTOBUF_LITE_LIBRARY AND PROTOBUF_PROTOC_LIBRARY AND PROTOBUF_PROTOC_EXECUTABLE) message(STATUS "Using custom protobuf library in ${PROTOBUF_ROOT}.") + SET_PROTOBUF_VERSION() PROMPT_PROTOBUF_LIB() else() message(WARNING "Cannot find protobuf library in ${PROTOBUF_ROOT}.") @@ -100,8 +106,7 @@ IF(NOT CMAKE_CROSSCOMPILING) FIND_PACKAGE(Protobuf ${PROTOBUF_VERSION}) IF(PROTOBUF_FOUND) - EXEC_PROGRAM(${PROTOBUF_PROTOC_EXECUTABLE} ARGS --version OUTPUT_VARIABLE PROTOBUF_VERSION) - STRING(REGEX MATCH "[0-9]+.[0-9]+" PROTOBUF_VERSION "${PROTOBUF_VERSION}") + SET_PROTOBUF_VERSION() IF("${PROTOBUF_VERSION}" VERSION_LESS "3.1.0") SET(PROTOBUF_FOUND OFF) ENDIF() -- GitLab From 987f065a82390cc2228c2acfe7f0a726d64a8a54 Mon Sep 17 00:00:00 2001 From: Liu Yiqun Date: Fri, 26 May 2017 22:55:27 -0700 Subject: [PATCH 0485/3256] 1. Support scalar computing. 2. Centralize the use of sse and neon instrinsic. 3. Disable neon intrinsic when enable gpu. --- paddle/cuda/include/hl_cpu_matrix_kernel.cuh | 36 +-- ...el.cuh => hl_cpu_matrix_kernel_detail.cuh} | 122 ++++--- paddle/cuda/include/hl_cpu_scalar.cuh | 74 +++++ paddle/cuda/include/hl_cpu_simd_neon.cuh | 98 ++++++ paddle/cuda/include/hl_cpu_simd_sse.cuh | 142 +++++++++ paddle/cuda/include/hl_matrix_base.cuh | 26 +- paddle/cuda/include/hl_matrix_base_detail.cuh | 151 +++++++++ paddle/cuda/include/hl_matrix_base_neon.cuh | 161 ---------- paddle/cuda/include/hl_matrix_base_sse.cuh | 211 ------------ paddle/cuda/include/hl_matrix_type.cuh | 40 ++- paddle/cuda/include/hl_neon_matrix_kernel.cuh | 299 ------------------ 11 files changed, 545 insertions(+), 815 deletions(-) rename paddle/cuda/include/{hl_sse_matrix_kernel.cuh => hl_cpu_matrix_kernel_detail.cuh} (89%) create mode 100644 paddle/cuda/include/hl_cpu_scalar.cuh create mode 100644 paddle/cuda/include/hl_cpu_simd_neon.cuh create mode 100644 paddle/cuda/include/hl_cpu_simd_sse.cuh create mode 100644 paddle/cuda/include/hl_matrix_base_detail.cuh delete mode 100644 paddle/cuda/include/hl_matrix_base_neon.cuh delete mode 100644 paddle/cuda/include/hl_matrix_base_sse.cuh delete mode 100644 paddle/cuda/include/hl_neon_matrix_kernel.cuh diff --git a/paddle/cuda/include/hl_cpu_matrix_kernel.cuh b/paddle/cuda/include/hl_cpu_matrix_kernel.cuh index 9c49a4bd208..aaa24325514 100644 --- a/paddle/cuda/include/hl_cpu_matrix_kernel.cuh +++ b/paddle/cuda/include/hl_cpu_matrix_kernel.cuh @@ -17,10 +17,9 @@ limitations under the License. */ #include #include "hl_base.h" -#if defined(__ARM_NEON__) || defined(__ARM_NEON) -#include "hl_neon_matrix_kernel.cuh" -#else -#include "hl_sse_matrix_kernel.cuh" + +#ifndef __CUDA_ARCH__ +#include "hl_cpu_matrix_kernel_detail.cuh" #endif /** @@ -114,35 +113,6 @@ void hl_cpu_apply_quaternary_op(Op op, } } -template -void hl_matrix_row_op(Agg agg, Op op, Saver sv, - int dimM, int dimN, - real *dst, int ld, - real *A, int lda) { - for (int i = 0; i < dimM; i++) { - real tmp = agg.init(); - for (int j = 0; j < dimN; j++) { - tmp = agg(tmp, op(A[i * lda + j])); - } - dst[i*ld] = sv(dst[i*ld], tmp); - } -} - -template -void hl_matrix_row_op(Agg agg, Op op, Saver sv, - int dimM, int dimN, - real *dst, int ld, - real *A, int lda, - real *B, int ldb) { - for (int i = 0; i < dimM; i++) { - real tmp = agg.init(); - for (int j = 0; j < dimN; j++) { - tmp = agg(tmp, op(A[i * lda + j], B[i * ldb + j])); - } - dst[i*ld] = sv(dst[i*ld], tmp); - } -} - template void hl_cpu_matrix_row_op(Agg agg, Op op, Saver sv, int dimM, int dimN, diff --git a/paddle/cuda/include/hl_sse_matrix_kernel.cuh b/paddle/cuda/include/hl_cpu_matrix_kernel_detail.cuh similarity index 89% rename from paddle/cuda/include/hl_sse_matrix_kernel.cuh rename to paddle/cuda/include/hl_cpu_matrix_kernel_detail.cuh index 9e50580669d..85ca836fdc4 100644 --- a/paddle/cuda/include/hl_sse_matrix_kernel.cuh +++ b/paddle/cuda/include/hl_cpu_matrix_kernel_detail.cuh @@ -13,26 +13,11 @@ See the License for the specific language governing permissions and limitations under the License. */ -#ifndef HL_SSE_MATRIX_KERNEL_CUH_ -#define HL_SSE_MATRIX_KERNEL_CUH_ +#ifndef HL_MATRIX_KERNEL_DETAIL_CUH_ +#define HL_MATRIX_KERNEL_DETAIL_CUH_ #include "hl_matrix_type.cuh" -#define VECTOR_SIZE 16 - -#ifndef PADDLE_TYPE_DOUBLE -/* number of float in vector */ -#define VECTOR_LEN 4 -#define VECTOR_SET _mm_set_ps1 -#else -#if defined(__APPLE__) || defined(__OSX__) -#define _mm_set_pd1 _mm_set1_pd -#endif -/* number of double in vector */ -#define VECTOR_LEN 2 -#define VECTOR_SET _mm_set_pd1 -#endif - inline bool hl_check_align(size_t size) { return !(size & (VECTOR_SIZE - 1)); } @@ -41,27 +26,63 @@ inline bool hl_check_align(void *ptr) { return hl_check_align(reinterpret_cast(ptr)); } -#ifndef PADDLE_TYPE_DOUBLE -template -inline real hl_agg_op(Agg agg, vecType mm) { - __m128 lo = _mm_unpacklo_ps(mm, mm); - __m128 hi = _mm_unpackhi_ps(mm, mm); - __m128 tmp1 = agg.vecOp(lo, hi); - __m128 tmp2 = _mm_movehl_ps(tmp1, tmp1); - __m128 ret = agg.vecOp(tmp1, tmp2); +template +void hl_matrix_row_op(Agg agg, Op op, Saver sv, + int dimM, int dimN, + real *dst, int ld, + real *A, int lda) { + for (int i = 0; i < dimM; i++) { + real tmp = agg.init(); + for (int j = 0; j < dimN; j++) { + tmp = agg(tmp, op(A[i * lda + j])); + } + dst[i*ld] = sv(dst[i*ld], tmp); + } +} - return _mm_cvtss_f32(ret); +template +void hl_matrix_row_op(Agg agg, Op op, Saver sv, + int dimM, int dimN, + real *dst, int ld, + real *A, int lda, + real *B, int ldb) { + for (int i = 0; i < dimM; i++) { + real tmp = agg.init(); + for (int j = 0; j < dimN; j++) { + tmp = agg(tmp, op(A[i * lda + j], B[i * ldb + j])); + } + dst[i*ld] = sv(dst[i*ld], tmp); + } } -#else -template -inline real hl_agg_op(Agg agg, vecType mm) { - __m128d lo = _mm_unpacklo_pd(mm, mm); - __m128d hi = _mm_unpackhi_pd(mm, mm); - __m128d ret = agg.vecOp(lo, hi); - - return _mm_cvtsd_f64(ret); + +template +void hl_matrix_column_op(Agg agg, Op op, Saver sv, + int dimM, int dimN, + real *dst, + real *A, int lda) { + for (int j = 0; j < dimN; j++) { + real tmp = agg.init(); + for (int i = 0; i < dimM; i++) { + tmp = agg(tmp, op(A[i * lda + j])); + } + dst[j] = sv(dst[j], tmp); + } +} + +template +void hl_matrix_column_op(Agg agg, Op op, Saver sv, + int dimM, int dimN, + real *dst, + real *A, int lda, + real *B, int ldb) { + for (int j = 0; j < dimN; j++) { + real tmp = agg.init(); + for (int i = 0; i < dimM; i++) { + tmp = agg(tmp, op(A[i * lda + j], B[i * ldb + j])); + } + dst[j] = sv(dst[j], tmp); + } } -#endif template void hl_sse_matrix_row_op(Agg agg, Op op, Saver sv, @@ -118,35 +139,6 @@ void hl_sse_matrix_row_op(Agg agg, Op op, Saver sv, } } -template -void hl_matrix_column_op(Agg agg, Op op, Saver sv, - int dimM, int dimN, - real *dst, - real *A, int lda) { - for (int j = 0; j < dimN; j++) { - real tmp = agg.init(); - for (int i = 0; i < dimM; i++) { - tmp = agg(tmp, op(A[i * lda + j])); - } - dst[j] = sv(dst[j], tmp); - } -} - -template -void hl_matrix_column_op(Agg agg, Op op, Saver sv, - int dimM, int dimN, - real *dst, - real *A, int lda, - real *B, int ldb) { - for (int j = 0; j < dimN; j++) { - real tmp = agg.init(); - for (int i = 0; i < dimM; i++) { - tmp = agg(tmp, op(A[i * lda + j], B[i * ldb + j])); - } - dst[j] = sv(dst[j], tmp); - } -} - /* * MaxRow greater than or equal dimN * dimN is multiples of VECTOR_LEN @@ -315,4 +307,4 @@ void hl_sse_matrix_column_op(Agg agg, Op op, Saver sv, } } -#endif /* HL_SSE_MATRIX_KERNEL_CUH_ */ +#endif /* HL_MATRIX_KERNEL_DETAIL_CUH_ */ diff --git a/paddle/cuda/include/hl_cpu_scalar.cuh b/paddle/cuda/include/hl_cpu_scalar.cuh new file mode 100644 index 00000000000..cddf08ce6b6 --- /dev/null +++ b/paddle/cuda/include/hl_cpu_scalar.cuh @@ -0,0 +1,74 @@ +/* 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. */ + +#ifndef HL_CPU_SCALAR_CUH_ +#define HL_CPU_SCALAR_CUH_ + +#define VECTOR_SIMD false +#define VECTOR_SET hl_vec_set + +#ifndef PADDLE_TYPE_DOUBLE +/* size of float */ +#define VECTOR_SIZE 4 +#else +/* size of double */ +#define VECTOR_SIZE 8 +#endif + +typedef real vecType; + +/* Consider a real as a vector */ +#define VECTOR_LEN 1 + +template +inline real hl_agg_op(Agg agg, vecType mm) { + return mm; +} + +INLINE real hl_vec_set(const real r) { + return r; +} + +INLINE real hl_vec_max(const real a, const real b) { + return a > b ? a : b; +} + +INLINE real hl_vec_min(const real a, const real b) { + return a > b ? b : a; +} + +INLINE real hl_vec_add(const real a, const real b) { + return a + b; +} + +INLINE real hl_vec_sub(const real a, const real b) { + return a - b; +} + +INLINE real hl_vec_mul(const real a, const real b) { + return a * b; +} + +INLINE real hl_vec_div(const real a, const real b) { + return a / b; +} + +INLINE real hl_vec_classification_error(const real a, + const real b, + const real p, + const real r) { + return ((a > p) == (b > p)) ? 0.0f : 1.0f; +} + +#endif // HL_CPU_SCALAR_CUH_ diff --git a/paddle/cuda/include/hl_cpu_simd_neon.cuh b/paddle/cuda/include/hl_cpu_simd_neon.cuh new file mode 100644 index 00000000000..9ff360c576f --- /dev/null +++ b/paddle/cuda/include/hl_cpu_simd_neon.cuh @@ -0,0 +1,98 @@ +/* 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. */ + +#ifndef HL_CPU_SIMD_NEON_CUH_ +#define HL_CPU_SIMD_NEON_CUH_ + +#include + +#define VECTOR_SIMD true +#define VECTOR_SIZE 16 +#define VECTOR_SET hl_vec_set + +#ifndef PADDLE_TYPE_DOUBLE + +typedef float32x4_t vecType; + +/* number of float in vector */ +#define VECTOR_LEN 4 + +template +inline real hl_agg_op(Agg agg, vecType mm) { + float32x4_t rev = vrev64q_f32(mm); + float32x4_t tmp1 = agg.vecOp(rev, rev); + float32x2_t lo = vget_high_f32(rev); + float32x2_t hi = vget_low_f32(rev); + float32x4_t tmp2 = vcombine_f32(hi, lo); + float32x4_t ret = agg.vecOp(tmp1, tmp2); + + return vgetq_lane_f32(ret, 0); +} + +inline float32x4_t hl_vec_set(const real f) { + return vdupq_n_f32(f); +} + +inline float32x4_t hl_vec_max(const float32x4_t a, const float32x4_t b) { + return vmaxq_f32(a, b); +} + +inline float32x4_t hl_vec_min(const float32x4_t a, const float32x4_t b) { + return vminq_f32(a, b); +} + +inline float32x4_t hl_vec_add(const float32x4_t a, const float32x4_t b) { + return vaddq_f32(a, b); +} + +inline float32x4_t hl_vec_sub(const float32x4_t a, const float32x4_t b) { + return vsubq_f32(a, b); +} + +inline float32x4_t hl_vec_mul(const float32x4_t a, const float32x4_t b) { + return vmulq_f32(a, b); +} + +inline float32x4_t hl_vec_div(const float32x4_t a, const float32x4_t b) { + float32x4_t tmp = vrecpeq_f32(b); + return vmulq_f32(a, tmp); +} + +inline float32x4_t hl_vec_classification_error(const float32x4_t a, + const float32x4_t b, + const float32x4_t p, + const float32x4_t r) { + uint32x4_t tmp1 = vcgtq_f32(a, p); + uint32x4_t tmp2 = vcgtq_f32(b, p); + uint32x4_t tmp3 = veorq_u32(tmp1, tmp2); + return vcvtq_f32_u32(vandq_u32(tmp3, vcvtq_u32_f32(r))); +} + +#else + +#ifdef __aarch64__ +typedef float64x2_t vecType; + +/* number of float in vector */ +#define VECTOR_LEN 2 +#define VECTOR_SET vdupq_n_f64 + +#error To be implemented +#else +#error NEON instructions does not support double precision +#endif // __aarch64__ + +#endif + +#endif // HL_CPU_SIMD_NEON_CUH_ diff --git a/paddle/cuda/include/hl_cpu_simd_sse.cuh b/paddle/cuda/include/hl_cpu_simd_sse.cuh new file mode 100644 index 00000000000..9a918770b14 --- /dev/null +++ b/paddle/cuda/include/hl_cpu_simd_sse.cuh @@ -0,0 +1,142 @@ +/* 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. */ + +#ifndef HL_CPU_SIMD_SSE_CUH_ +#define HL_CPU_SIMD_SSE_CUH_ + +#include +#include +#include + +#define VECTOR_SIMD true +#define VECTOR_SIZE 16 +#define VECTOR_SET hl_vec_set + +#ifndef PADDLE_TYPE_DOUBLE + +typedef __m128 vecType; + +/* number of float in vector */ +#define VECTOR_LEN 4 + +template +inline real hl_agg_op(Agg agg, vecType mm) { + __m128 lo = _mm_unpacklo_ps(mm, mm); + __m128 hi = _mm_unpackhi_ps(mm, mm); + __m128 tmp1 = agg.vecOp(lo, hi); + __m128 tmp2 = _mm_movehl_ps(tmp1, tmp1); + __m128 ret = agg.vecOp(tmp1, tmp2); + + return _mm_cvtss_f32(ret); +} + +inline __m128 hl_vec_set(const real f) { + return _mm_set_ps1(f); +} + +inline __m128 hl_vec_max(const __m128 a, const __m128 b) { + return _mm_max_ps(a, b); +} + +inline __m128 hl_vec_min(const __m128 a, const __m128 b) { + return _mm_min_ps(a, b); +} + +inline __m128 hl_vec_add(const __m128 a, const __m128 b) { + return _mm_add_ps(a, b); +} + +inline __m128 hl_vec_sub(const __m128 a, const __m128 b) { + return _mm_sub_ps(a, b); +} + +inline __m128 hl_vec_mul(const __m128 a, const __m128 b) { + return _mm_mul_ps(a, b); +} + +inline __m128 hl_vec_div(const __m128 a, const __m128 b) { + return _mm_div_ps(a, b); +} + +inline __m128 hl_vec_classification_error(const __m128 a, + const __m128 b, + const __m128 p, + const __m128 r) { + __m128 tmp1 = _mm_cmpgt_ps(a, p); + __m128 tmp2 = _mm_cmpgt_ps(b, p); + __m128 tmp3 = _mm_xor_ps(tmp1, tmp2); + return _mm_and_ps(tmp3, r); +} + +#else + +typedef __m128d vecType; + +/* number of double in vector */ +#define VECTOR_LEN 2 + +template +inline real hl_agg_op(Agg agg, vecType mm) { + __m128d lo = _mm_unpacklo_pd(mm, mm); + __m128d hi = _mm_unpackhi_pd(mm, mm); + __m128d ret = agg.vecOp(lo, hi); + + return _mm_cvtsd_f64(ret); +} + +inline __m128d hl_vec_set(const real d) { +#if defined(__APPLE__) || defined(__OSX__) + return _mm_set1_pd(d); +#else + return _mm_set_pd1(d); +#endif +} + +inline __m128d hl_vec_max(const __m128d a, const __m128d b) { + return _mm_max_pd(a, b); +} + +inline __m128d hl_vec_min(const __m128d a, const __m128d b) { + return _mm_min_pd(a, b); +} + +inline __m128d hl_vec_add(const __m128d a, const __m128d b) { + return _mm_add_pd(a, b); +} + +inline __m128d hl_vec_sub(const __m128d a, const __m128d b) { + return _mm_sub_pd(a, b); +} + +inline __m128d hl_vec_mul(const __m128d a, const __m128d b) { + return _mm_mul_pd(a, b); +} + +inline __m128d hl_vec_div(const __m128d a, const __m128d b) { + return _mm_div_pd(a, b); +} + +inline __m128d hl_vec_classification_error(const __m128d a, + const __m128d b, + const __m128d p, + const __m128d r) { + __m128d tmp1 = _mm_cmpgt_pd(a, p); + __m128d tmp2 = _mm_cmpgt_pd(b, p); + __m128d tmp3 = _mm_xor_pd(tmp1, tmp2); + return _mm_and_pd(tmp3, r); +} + +#endif + +#endif // HL_CPU_SIMD_SSE_CUH_ diff --git a/paddle/cuda/include/hl_matrix_base.cuh b/paddle/cuda/include/hl_matrix_base.cuh index 8b755c1095c..53fdb47ec9c 100644 --- a/paddle/cuda/include/hl_matrix_base.cuh +++ b/paddle/cuda/include/hl_matrix_base.cuh @@ -18,26 +18,6 @@ limitations under the License. */ #include "hl_matrix_type.cuh" -#ifdef __CUDA_ARCH__ -/** - * CUDA kernel inline function - */ -#define INLINE __device__ inline -#else -/** - * CPP inline function - */ -#define INLINE inline -#endif - -#ifndef PADDLE_TYPE_DOUBLE -#define DEVICE_FMAX fmaxf -#define DEVICE_FMIN fminf -#else -#define DEVICE_FMAX fmax -#define DEVICE_FMIN fmin -#endif - class BaseOp { public: static const bool sse = false; @@ -66,10 +46,8 @@ typedef BaseOp SSESquaredDiff; typedef BaseOp SSEFirst; typedef BaseOp SSESecond; typedef BaseOp SSEClassificationError; -#elif defined(__ARM__NEON__) || defined(__ARM_NEON) -#include "hl_matrix_base_neon.cuh" #else -#include "hl_matrix_base_sse.cuh" +#include "hl_matrix_base_detail.cuh" #endif namespace aggregate { @@ -124,7 +102,7 @@ public: add2(const real s1, const real s2) : SSEAdd2(s1, s2), p1(s1), p2(s2) {} INLINE real operator()(const real a, const real b) const { - return p1 * a + p2 * b; + return p1 * a + p2 * b; } }; diff --git a/paddle/cuda/include/hl_matrix_base_detail.cuh b/paddle/cuda/include/hl_matrix_base_detail.cuh new file mode 100644 index 00000000000..50079ed53de --- /dev/null +++ b/paddle/cuda/include/hl_matrix_base_detail.cuh @@ -0,0 +1,151 @@ +/* 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. */ + +#ifndef HL_MATRIX_BASE_DETAIL_CUH_ +#define HL_MATRIX_BASE_DETAIL_CUH_ + +#include "hl_matrix_type.cuh" + +namespace aggregate { +class SSESum { +public: + static const bool sse = VECTOR_SIMD; + INLINE vecType vecOp(const vecType a, const vecType b) const { + return hl_vec_add(a, b); + } +}; + +class SSEMax { +public: + static const bool sse = VECTOR_SIMD; + INLINE vecType vecOp(const vecType a, const vecType b) const { + return hl_vec_max(a, b); + } +}; + +class SSEMin { +public: + static const bool sse = VECTOR_SIMD; + INLINE vecType vecOp(const vecType a, const vecType b) const { + return hl_vec_min(a, b); + } +}; +} // namespace aggregate + +namespace base { +namespace unary { +class SSEIdentity { +public: + static const bool sse = VECTOR_SIMD; + INLINE vecType vecOp(const vecType a) const { + return a; + } +}; +} // namespace unary + +namespace binary { +class SSEAdd { +public: + static const bool sse = VECTOR_SIMD; + INLINE vecType vecOp(const vecType a, const vecType b) const { + return hl_vec_add(a, b); + } +}; + +class SSEAdd2 { +public: + static const bool sse = VECTOR_SIMD; + const real p1; + const real p2; + vecType mp1; + vecType mp2; + +public: + SSEAdd2(const real s1, const real s2) : p1(s1), p2(s2) { + mp1 = hl_vec_set(p1); + mp2 = hl_vec_set(p2); + } + INLINE vecType vecOp(const vecType a, const vecType b) const { + return hl_vec_add(hl_vec_mul(mp1, a), hl_vec_mul(mp2, b)); + } +}; + +class SSESub { +public: + static const bool sse = VECTOR_SIMD; + INLINE vecType vecOp(const vecType a, const vecType b) const { + return hl_vec_sub(a, b); + } +}; + +class SSEMul { +public: + static const bool sse = VECTOR_SIMD; + INLINE vecType vecOp(const vecType a, const vecType b) const { + return hl_vec_mul(a, b); + } +}; + +class SSEDiv { +public: + static const bool sse = VECTOR_SIMD; + INLINE vecType vecOp(const vecType a, const vecType b) const { + return hl_vec_div(a, b); + } +}; + +class SSESquaredDiff { +public: + static const bool sse = VECTOR_SIMD; + INLINE vecType vecOp(const vecType a, const vecType b) const { + return hl_vec_mul(hl_vec_sub(a, b), hl_vec_sub(a, b)); + } +}; + +class SSEFirst { +public: + static const bool sse = VECTOR_SIMD; + INLINE vecType vecOp(const vecType a, const vecType b) const { + return a; + } +}; + +class SSESecond { +public: + static const bool sse = VECTOR_SIMD; + INLINE vecType vecOp(const vecType a, const vecType b) const { + return b; + } +}; + +class SSEClassificationError { +public: + static const bool sse = VECTOR_SIMD; + const real p; + vecType mp; + vecType result; + +public: + explicit SSEClassificationError(const real s) : p(s) { + mp = hl_vec_set(p); + result = hl_vec_set(1.0f); + } + INLINE vecType vecOp(const vecType a, const vecType b) const { + return hl_vec_classification_error(a, b, mp, result); + } +}; +} // namespace binary +} // namespace base + +#endif /* HL_MATRIX_BASE_DETAIL_CUH_ */ diff --git a/paddle/cuda/include/hl_matrix_base_neon.cuh b/paddle/cuda/include/hl_matrix_base_neon.cuh deleted file mode 100644 index e13019f5ee2..00000000000 --- a/paddle/cuda/include/hl_matrix_base_neon.cuh +++ /dev/null @@ -1,161 +0,0 @@ -/* 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. */ - - -#ifndef HL_MATRIX_BASE_NEON_CUH_ -#define HL_MATRIX_BASE_NEON_CUH_ - -namespace aggregate { -class SSESum { -public: - static const bool sse = true; - INLINE float32x4_t vecOp(const float32x4_t a, const float32x4_t b) const { - return vaddq_f32(a, b); - } -}; - -class SSEMax { -public: - static const bool sse = true; - INLINE float32x4_t vecOp(const float32x4_t a, const float32x4_t b) const { - return vmaxq_f32(a, b); - } -}; - -class SSEMin { -public: - static const bool sse = true; - INLINE float32x4_t vecOp(const float32x4_t a, const float32x4_t b) const { - return vminq_f32(a, b); - } -}; -} // namespace aggregate - -namespace base { -namespace unary { -class SSEIdentity { -public: - static const bool sse = true; - INLINE float32x4_t vecOp(const float32x4_t a) const { - return a; - } -}; -} // namespace unary - -namespace binary { -class SSEAdd { -public: - static const bool sse = true; - INLINE float32x4_t vecOp(const float32x4_t a, const float32x4_t b) const { - return vaddq_f32(a, b); - } -}; - -class SSEAdd2 { -public: - static const bool sse = true; - const real p1; - const real p2; - float32x4_t mp1; - float32x4_t mp2; - -public: - SSEAdd2(const real s1, const real s2) : p1(s1), p2(s2) { - mp1 = vdupq_n_f32(p1); - mp2 = vdupq_n_f32(p2); - } - INLINE float32x4_t vecOp(const float32x4_t a, const float32x4_t b) const { - float32x4_t tmp1, tmp2; - tmp1 = vmulq_f32(mp1, a); - tmp2 = vmulq_f32(mp2, b); - return vaddq_f32(tmp1, tmp2); - } -}; - -class SSESub { -public: - static const bool sse = true; - INLINE float32x4_t vecOp(const float32x4_t a, const float32x4_t b) const { - return vsubq_f32(a, b); - } -}; - -class SSEMul { -public: - static const bool sse = true; - INLINE float32x4_t vecOp(const float32x4_t a, const float32x4_t b) const { - return vmulq_f32(a, b); - } -}; - -class SSEDiv { -public: - static const bool sse = true; - INLINE float32x4_t vecOp(const float32x4_t a, const float32x4_t b) const { - float32x4_t tmp; - tmp = vrecpeq_f32(b); - return vmulq_f32(a, tmp); - } -}; - -class SSESquaredDiff { -public: - static const bool sse = true; - INLINE float32x4_t vecOp(const float32x4_t a, const float32x4_t b) const { - float32x4_t tmp; - tmp = vsubq_f32(a, b); - return vmulq_f32(tmp, tmp); - } -}; - -class SSEFirst { -public: - static const bool sse = true; - INLINE float32x4_t vecOp(const float32x4_t a, const float32x4_t b) const { - return a; - } -}; - -class SSESecond { -public: - static const bool sse = true; - INLINE float32x4_t vecOp(const float32x4_t a, const float32x4_t b) const { - return b; - } -}; - -class SSEClassificationError { -public: - static const bool sse = true; - const real p; - float32x4_t mp; - uint32x4_t result; - -public: - explicit SSEClassificationError(const real s) : p(s) { - mp = vdupq_n_f32(p); - result = vdupq_n_u32(1); - } - // TODO: to be check - INLINE float32x4_t vecOp(const float32x4_t a, const float32x4_t b) const { - uint32x4_t tmp1 = vcgtq_f32(a, mp); - uint32x4_t tmp2 = vcgtq_f32(b, mp); - uint32x4_t tmp3 = veorq_u32(tmp1, tmp2); - return vcvtq_f32_u32(vandq_u32(tmp3, result)); - } -}; -} // namespace binary -} // namespace base - -#endif /* HL_MATRIX_BASE_NEON_CUH_ */ diff --git a/paddle/cuda/include/hl_matrix_base_sse.cuh b/paddle/cuda/include/hl_matrix_base_sse.cuh deleted file mode 100644 index db6c9cca03a..00000000000 --- a/paddle/cuda/include/hl_matrix_base_sse.cuh +++ /dev/null @@ -1,211 +0,0 @@ -/* 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. */ - - -#ifndef HL_MATRIX_BASE_SSE_CUH_ -#define HL_MATRIX_BASE_SSE_CUH_ - -namespace aggregate { -class SSESum { -public: - static const bool sse = true; - INLINE __m128 vecOp(const __m128 a, const __m128 b) const { - return _mm_add_ps(a, b); - } - INLINE __m128d vecOp(const __m128d a, const __m128d b) const { - return _mm_add_pd(a, b); - } -}; - -class SSEMax { -public: - static const bool sse = true; - INLINE __m128 vecOp(const __m128 a, const __m128 b) const { - return _mm_max_ps(a, b); - } - INLINE __m128d vecOp(const __m128d a, const __m128d b) const { - return _mm_max_pd(a, b); - } -}; - -class SSEMin { -public: - static const bool sse = true; - INLINE __m128 vecOp(const __m128 a, const __m128 b) const { - return _mm_min_ps(a, b); - } - INLINE __m128d vecOp(const __m128d a, const __m128d b) const { - return _mm_min_pd(a, b); - } -}; -} // namespace aggregate - -namespace base { -namespace unary { -class SSEIdentity { -public: - static const bool sse = true; - INLINE __m128 vecOp(const __m128 a) const { - return a; - } - INLINE __m128d vecOp(const __m128d a) const { - return a; - } -}; -} // namespace unary - -namespace binary { -class SSEAdd { -public: - static const bool sse = true; - INLINE __m128 vecOp(const __m128 a, const __m128 b) const { - return _mm_add_ps(a, b); - } - INLINE __m128d vecOp(const __m128d a, const __m128d b) const { - return _mm_add_pd(a, b); - } -}; - -class SSEAdd2 { -public: - static const bool sse = true; - const real p1; - const real p2; - union {__m128 f; __m128d d;} mp1; - union {__m128 f; __m128d d;} mp2; - -public: - SSEAdd2(const real s1, const real s2) : p1(s1), p2(s2) { - if (sizeof(real) == sizeof(float)) { - mp1.f = _mm_set1_ps(p1); - mp2.f = _mm_set1_ps(p2); - } else { - mp1.d = _mm_set1_pd(p1); - mp2.d = _mm_set1_pd(p2); - } - } - INLINE __m128 vecOp(const __m128 a, const __m128 b) const { - __m128 tmp1, tmp2; - tmp1 = _mm_mul_ps(mp1.f, a); - tmp2 = _mm_mul_ps(mp2.f, b); - return _mm_add_ps(tmp1, tmp2); - } - INLINE __m128d vecOp(const __m128d a, const __m128d b) const { - __m128d tmp1, tmp2; - tmp1 = _mm_mul_pd(mp1.d, a); - tmp2 = _mm_mul_pd(mp2.d, b); - return _mm_add_pd(tmp1, tmp2); - } -}; - -class SSESub { -public: - static const bool sse = true; - INLINE __m128 vecOp(const __m128 a, const __m128 b) const { - return _mm_sub_ps(a, b); - } - INLINE __m128d vecOp(const __m128d a, const __m128d b) const { - return _mm_sub_pd(a, b); - } -}; - -class SSEMul { -public: - static const bool sse = true; - INLINE __m128 vecOp(const __m128 a, const __m128 b) const { - return _mm_mul_ps(a, b); - } - INLINE __m128d vecOp(const __m128d a, const __m128d b) const { - return _mm_mul_pd(a, b); - } -}; - -class SSEDiv { -public: - static const bool sse = true; - INLINE __m128 vecOp(const __m128 a, const __m128 b) const { - return _mm_div_ps(a, b); - } - INLINE __m128d vecOp(const __m128d a, const __m128d b) const { - return _mm_div_pd(a, b); - } -}; - -class SSESquaredDiff { -public: - static const bool sse = true; - INLINE __m128 vecOp(const __m128 a, const __m128 b) const { - return _mm_mul_ps(_mm_sub_ps(a, b), _mm_sub_ps(a, b)); - } - INLINE __m128d vecOp(const __m128d a, const __m128d b) const { - return _mm_mul_pd(_mm_sub_pd(a, b), _mm_sub_pd(a, b)); - } -}; - -class SSEFirst { -public: - static const bool sse = true; - INLINE __m128 vecOp(const __m128 a, const __m128 b) const { - return a; - } - INLINE __m128d vecOp(const __m128d a, const __m128d b) const { - return a; - } -}; - -class SSESecond { -public: - static const bool sse = true; - INLINE __m128 vecOp(const __m128 a, const __m128 b) const { - return b; - } - INLINE __m128d vecOp(const __m128d a, const __m128d b) const { - return b; - } -}; - -class SSEClassificationError { -public: - static const bool sse = true; - const real p; - union {__m128 f; __m128d d;} mp; - union {__m128 f; __m128d d;} result; - -public: - explicit SSEClassificationError(const real s) : p(s) { - if (sizeof(real) == sizeof(float)) { - mp.f = _mm_set1_ps(p); - result.f = _mm_set1_ps(1.0f); - } else { - mp.d = _mm_set1_pd(p); - result.d = _mm_set1_pd(1.0); - } - } - INLINE __m128 vecOp(const __m128 a, const __m128 b) const { - __m128 tmp1 = _mm_cmpgt_ps(a, mp.f); - __m128 tmp2 = _mm_cmpgt_ps(b, mp.f); - __m128 tmp3 = _mm_xor_ps(tmp1, tmp2); - return _mm_and_ps(tmp3, result.f); - } - INLINE __m128d vecOp(const __m128d a, const __m128d b) const { - __m128d tmp1 = _mm_cmpgt_pd(a, mp.d); - __m128d tmp2 = _mm_cmpgt_pd(b, mp.d); - __m128d tmp3 = _mm_xor_pd(tmp1, tmp2); - return _mm_and_pd(tmp3, result.d); - } -}; -} // namespace binary -} // namespace base - -#endif /* HL_MATRIX_BASE_SSE_CUH_ */ diff --git a/paddle/cuda/include/hl_matrix_type.cuh b/paddle/cuda/include/hl_matrix_type.cuh index f965ba96679..77f73167fe6 100644 --- a/paddle/cuda/include/hl_matrix_type.cuh +++ b/paddle/cuda/include/hl_matrix_type.cuh @@ -17,35 +17,31 @@ limitations under the License. */ #include "hl_base.h" -#if defined(__CUDA_ARCH__) +#ifdef __CUDA_ARCH__ +/** + * CUDA kernel inline function + */ +#define INLINE __device__ inline +#else +/** + * CPP inline function + */ +#define INLINE inline +#endif + +#ifdef __CUDA_ARCH__ #include #ifndef PADDLE_TYPE_DOUBLE typedef float4 vecType; #else typedef double2 vecType; #endif -#elif (defined __ARM_NEON) || (defined __ARM_NEON__) -#include -#ifndef PADDLE_TYPE_DOUBLE -typedef float32x4_t vecType; -#else -#error NEON instructions does not support double precision -#endif +#elif defined(__SSE3__) +#include "hl_cpu_simd_sse.cuh" +#elif (defined(__ARM_NEON) || defined(__ARM_NEON__)) && !defined(__NVCC__) +#include "hl_cpu_simd_neon.cuh" #else -#include -#include -#include -#ifndef PADDLE_TYPE_DOUBLE -typedef __m128 vecType; -#else -typedef __m128d vecType; -#endif -#endif - -#ifdef __CUDA_ARCH__ -#define INLINE __device__ inline -#else -#define INLINE inline +#include "hl_cpu_scalar.cuh" #endif #endif // HL_MATRIX_TYPE_CUH_ diff --git a/paddle/cuda/include/hl_neon_matrix_kernel.cuh b/paddle/cuda/include/hl_neon_matrix_kernel.cuh deleted file mode 100644 index 7b4e5b00079..00000000000 --- a/paddle/cuda/include/hl_neon_matrix_kernel.cuh +++ /dev/null @@ -1,299 +0,0 @@ -/* 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. */ - - -#ifndef HL_NEON_MATRIX_KERNEL_CUH_ -#define HL_NEON_MATRIX_KERNEL_CUH_ - -#include "hl_matrix_type.cuh" - -#define VECTOR_SIZE 16 - -/* number of float in vector */ -#define VECTOR_LEN 4 -#define VECTOR_SET vdupq_n_f32 - -inline bool hl_check_align(size_t size) { - return !(size & (VECTOR_SIZE - 1)); -} - -inline bool hl_check_align(void *ptr) { - return hl_check_align(reinterpret_cast(ptr)); -} - -template -inline real hl_agg_op(Agg agg, vecType mm) { - float32x4_t rev = vrev64q_f32(mm); - float32x4_t tmp1 = agg.vecOp(rev, rev); - float32x2_t lo = vget_high_f32(rev); - float32x2_t hi = vget_low_f32(rev); - float32x4_t tmp2 = vcombine_f32(hi, lo); - float32x4_t ret = agg.vecOp(tmp1, tmp2); - - return vgetq_lane_f32(ret, 0); -} - -template -void hl_sse_matrix_row_op(Agg agg, Op op, Saver sv, - int dimM, int dimN, - real *dst, int ld, - real *A, int lda) { - for (int i = 0; i < dimM; i++, A += lda) { - vecType mm = VECTOR_SET(agg.init()); - vecType *a = (vecType*)(A); - for (int j = 0; j < dimN / VECTOR_LEN; j++, a++) { - mm = agg.vecOp(mm, op.vecOp(*a)); - } - - int rem = dimN % VECTOR_LEN; - if (rem) { - real tmp = hl_agg_op(agg, mm); - real *a = A + (dimN / VECTOR_LEN) * VECTOR_LEN; - for (int j = 0; j < rem; j++) { - tmp = agg(tmp, op(a[j])); - } - dst[i*ld] = sv(dst[i*ld], tmp); - } else { - dst[i*ld] = sv(dst[i*ld], hl_agg_op(agg, mm)); - } - } -} - -template -void hl_sse_matrix_row_op(Agg agg, Op op, Saver sv, - int dimM, int dimN, - real *dst, int ld, - real *A, int lda, - real *B, int ldb) { - for (int i = 0; i < dimM; i++, A += lda, B += ldb) { - vecType mm = VECTOR_SET(agg.init()); - vecType *a = (vecType*)(A); - vecType *b = (vecType*)(B); - for (int j = 0; j < dimN / VECTOR_LEN; j++, a++, b++) { - mm = agg.vecOp(mm, op.vecOp(*a, *b)); - } - - int rem = dimN % VECTOR_LEN; - if (rem) { - real tmp = hl_agg_op(agg, mm); - real *a = A + (dimN / VECTOR_LEN) * VECTOR_LEN; - real *b = B + (dimN / VECTOR_LEN) * VECTOR_LEN; - for (int j = 0; j < rem; j++) { - tmp = agg(tmp, op(a[j], b[j])); - } - dst[i*ld] = sv(dst[i*ld], tmp); - } else { - dst[i*ld] = sv(dst[i*ld], hl_agg_op(agg, mm)); - } - } -} - -template -void hl_matrix_column_op(Agg agg, Op op, Saver sv, - int dimM, int dimN, - real *dst, - real *A, int lda) { - for (int j = 0; j < dimN; j++) { - real tmp = agg.init(); - for (int i = 0; i < dimM; i++) { - tmp = agg(tmp, op(A[i * lda + j])); - } - dst[j] = sv(dst[j], tmp); - } -} - -template -void hl_matrix_column_op(Agg agg, Op op, Saver sv, - int dimM, int dimN, - real *dst, - real *A, int lda, - real *B, int ldb) { - for (int j = 0; j < dimN; j++) { - real tmp = agg.init(); - for (int i = 0; i < dimM; i++) { - tmp = agg(tmp, op(A[i * lda + j], B[i * ldb + j])); - } - dst[j] = sv(dst[j], tmp); - } -} - -/* - * MaxRow greater than or equal dimN - * dimN is multiples of VECTOR_LEN - * so rem <= MaxRow / VECTOR_LEN - */ -template -void hl_sse_column_op_with_rem(Agg agg, Op op, Saver sv, - int dimM, int dimN, - real *dst, - real *A, int lda) { - vecType mm[MaxRow / VECTOR_LEN]; - for (int n = 0; n < MaxRow / VECTOR_LEN; n++) { - mm[n] = VECTOR_SET(agg.init()); - } - - for (int i = 0; i < dimM; i++) { - vecType *a = (vecType*)(A + i * lda); - for (int n = 0; n < dimN / VECTOR_LEN; n++) { - mm[n] = agg.vecOp(mm[n], op.vecOp(a[n])); - } - } - - vecType *result = (vecType*)(dst); - for (int n = 0; n < dimN / VECTOR_LEN; n++) { - result[n] = sv.vecOp(result[n], mm[n]); - } - - int rem = dimN % VECTOR_LEN; - if (rem) { - A += (dimN / VECTOR_LEN) * VECTOR_LEN; - dst += (dimN / VECTOR_LEN) * VECTOR_LEN; - hl_matrix_column_op(agg, op, sv, dimM, rem, dst, A, lda); - } -} - -/* - * dimN is multiples of VECTOR_LEN - * dimN greater than Step - */ -template -void hl_sse_matrix_column_op(Agg agg, Op op, Saver sv, - int dimM, int dimN, - real *dst, - real *A, int lda) { - for (int j = 0; j < dimN / Step; j++, dst += Step, A += Step) { - vecType mm[Step / VECTOR_LEN]; - for (int n = 0; n < Step / VECTOR_LEN; n++) { - mm[n] = VECTOR_SET(agg.init()); - } - - for (int i = 0; i < dimM; i++) { - vecType *a = (vecType*)(A + i * lda); - for (int n = 0; n < Step / VECTOR_LEN; n++) { - mm[n] = agg.vecOp(mm[n], op.vecOp(a[n])); - } - } - - vecType *result = (vecType*)(dst); - for (int n = 0; n < Step / VECTOR_LEN; n++) { - result[n] = sv.vecOp(result[n], mm[n]); - } - } - - int remRow = dimN % Step; - if (remRow) { - hl_sse_column_op_with_rem(agg, op, sv, dimM, remRow, dst, A, lda); - } -} - -template -void hl_sse_matrix_column_op(Agg agg, Op op, Saver sv, - int dimM, int dimN, - real *dst, - real *A, int lda) { - if (dimN <= 16) { - hl_sse_matrix_column_op<16>(agg, op, sv, dimM, dimN, dst, A, lda); - } else if (dimN <= 32) { - hl_sse_matrix_column_op<32>(agg, op, sv, dimM, dimN, dst, A, lda); - } else if (dimN <= 1024 || dimM <= 512) { - hl_sse_matrix_column_op<64>(agg, op, sv, dimM, dimN, dst, A, lda); - } else { - hl_sse_matrix_column_op<1024>(agg, op, sv, dimM, dimN, dst, A, lda); - } -} - -template -void hl_sse_column_op_with_rem(Agg agg, Op op, Saver sv, - int dimM, int dimN, - real *dst, - real *A, int lda, - real *B, int ldb) { - vecType mm[MaxRow / VECTOR_LEN]; - for (int n = 0; n < MaxRow / VECTOR_LEN; n++) { - mm[n] = VECTOR_SET(agg.init()); - } - - for (int i = 0; i < dimM; i++) { - vecType *a = (vecType*)(A + i * lda); - vecType *b = (vecType*)(B + i * ldb); - for (int n = 0; n < dimN / VECTOR_LEN; n++) { - mm[n] = agg.vecOp(mm[n], op.vecOp(a[n], b[n])); - } - } - - vecType *result = (vecType*)(dst); - for (int n = 0; n < dimN / VECTOR_LEN; n++) { - result[n] = sv.vecOp(result[n], mm[n]); - } - - int rem = dimN % VECTOR_LEN; - if (rem) { - A += (dimN / VECTOR_LEN) * VECTOR_LEN; - B += (dimN / VECTOR_LEN) * VECTOR_LEN; - dst += (dimN / VECTOR_LEN) * VECTOR_LEN; - hl_matrix_column_op(agg, op, sv, dimM, rem, dst, A, lda, B, ldb); - } -} - -template -void hl_sse_matrix_column_op(Agg agg, Op op, Saver sv, - int dimM, int dimN, - real *dst, - real *A, int lda, - real *B, int ldb) { - for (int j = 0; j < dimN / Step; j++, dst += Step, A += Step, B += Step) { - vecType mm[Step / VECTOR_LEN]; - for (int n = 0; n < Step / VECTOR_LEN; n++) { - mm[n] = VECTOR_SET(agg.init()); - } - - for (int i = 0; i < dimM; i++) { - vecType *a = (vecType*)(A + i * lda); - vecType *b = (vecType*)(B + i * ldb); - for (int n = 0; n < Step / VECTOR_LEN; n++) { - mm[n] = agg.vecOp(mm[n], op.vecOp(a[n], b[n])); - } - } - - vecType *result = (vecType*)(dst); - for (int n = 0; n < Step / VECTOR_LEN; n++) { - result[n] = sv.vecOp(result[n], mm[n]); - } - } - - int remRow = dimN % Step; - if (remRow) { - hl_sse_column_op_with_rem( - agg, op, sv, dimM, remRow, dst, A, lda, B, ldb); - } -} - -template -void hl_sse_matrix_column_op(Agg agg, Op op, Saver sv, - int dimM, int dimN, - real *dst, - real *A, int lda, - real *B, int ldb) { - if (dimN <= 16) { - hl_sse_matrix_column_op<16>(agg, op, sv, dimM, dimN, dst, A, lda, B, ldb); - } else if (dimN <= 32) { - hl_sse_matrix_column_op<32>(agg, op, sv, dimM, dimN, dst, A, lda, B, ldb); - } else if (dimN <= 1024 || dimM <= 512) { - hl_sse_matrix_column_op<64>(agg, op, sv, dimM, dimN, dst, A, lda, B, ldb); - } else { - hl_sse_matrix_column_op<1024>(agg, op, sv, dimM, dimN, dst, A, lda, B, ldb); - } -} - -#endif /* HL_NEON_MATRIX_KERNEL_CUH_ */ -- GitLab From 07ac67ec7c15d0c4b93ab6d7e119570f10952a3f Mon Sep 17 00:00:00 2001 From: Liu Yiqun Date: Fri, 26 May 2017 23:38:52 -0700 Subject: [PATCH 0486/3256] Support native build on NVIDIA DRIVE PX2 (arm64 + GPU). --- cmake/cudnn.cmake | 14 +++++++++++++- paddle/utils/CpuId.cpp | 4 ++-- paddle/utils/tests/test_SIMDFlags.cpp | 2 +- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/cmake/cudnn.cmake b/cmake/cudnn.cmake index af9be869618..92dce20c698 100644 --- a/cmake/cudnn.cmake +++ b/cmake/cudnn.cmake @@ -11,11 +11,23 @@ find_path(CUDNN_INCLUDE_DIR cudnn.h get_filename_component(__libpath_hist ${CUDA_CUDART_LIBRARY} PATH) +if(NOT ${CMAKE_HOST_SYSTEM_PROCESSOR}) + execute_process( + COMMAND uname -m COMMAND tr -d '\n' + OUTPUT_VARIABLE HOST_ARCH + RESULT_VARIABLE UNAME_RESULT) + if(${UNAME_RESULT}) + set(HOST_ARCH "x86_64") + endif(${UNAME_RESULT}) +else(NOT ${CMAKE_HOST_SYSTEM_PROCESSOR}) + set(HOST_ARCH ${CMAKE_HOST_SYSTEM_PROCESSOR}) +endif(NOT ${CMAKE_HOST_SYSTEM_PROCESSOR}) + list(APPEND CUDNN_CHECK_LIBRARY_DIRS ${CUDNN_ROOT} ${CUDNN_ROOT}/lib64 ${CUDNN_ROOT}/lib - ${CUDNN_ROOT}/lib/x86_64-linux-gnu + ${CUDNN_ROOT}/lib/${HOST_ARCH}-linux-gnu $ENV{CUDNN_ROOT} $ENV{CUDNN_ROOT}/lib64 $ENV{CUDNN_ROOT}/lib diff --git a/paddle/utils/CpuId.cpp b/paddle/utils/CpuId.cpp index 5abeeecae8d..7186feef041 100644 --- a/paddle/utils/CpuId.cpp +++ b/paddle/utils/CpuId.cpp @@ -21,7 +21,7 @@ limitations under the License. */ #else -#if !defined(__arm__) +#if !defined(__arm__) && !defined(__aarch64__) #include /// for GCC/Clang #define CPUID(info, x) __cpuid_count(x, 0, info[0], info[1], info[2], info[3]) @@ -32,7 +32,7 @@ limitations under the License. */ namespace paddle { SIMDFlags::SIMDFlags() { -#if defined(__arm__) +#if defined(__arm__) || defined(__aarch64__) simd_flags_ = SIMD_NEON; #else unsigned int cpuInfo[4]; diff --git a/paddle/utils/tests/test_SIMDFlags.cpp b/paddle/utils/tests/test_SIMDFlags.cpp index 185789c927b..a808d456a69 100644 --- a/paddle/utils/tests/test_SIMDFlags.cpp +++ b/paddle/utils/tests/test_SIMDFlags.cpp @@ -19,7 +19,7 @@ using namespace paddle; // NOLINT TEST(SIMDFlags, gccTest) { #if (defined(__GNUC__) || defined(__GNUG__)) && !(defined(__clang__)) && \ - !defined(__arm__) + !defined(__arm__) && !defined(__aarch64__) // clang-format off CHECK(!__builtin_cpu_supports("sse") != HAS_SSE); CHECK(!__builtin_cpu_supports("sse2") != HAS_SSE2); -- GitLab From 633171c2d3a1f6b8e245844fa4fb254895565da7 Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Sat, 27 May 2017 14:49:05 +0000 Subject: [PATCH 0487/3256] fix according to comments --- paddle/go/cclient/test/main.c | 19 ++-- paddle/go/cmake/golang.cmake | 8 +- paddle/go/crecordio/crecordio.go | 169 +++++++---------------------- paddle/go/crecordio/test/test.c | 53 ++++++--- paddle/go/recordio/README.md | 2 +- paddle/go/recordio/multi_reader.go | 140 ++++++++++++++++++++++++ paddle/go/recordio/reader.go | 9 +- 7 files changed, 239 insertions(+), 161 deletions(-) create mode 100644 paddle/go/recordio/multi_reader.go diff --git a/paddle/go/cclient/test/main.c b/paddle/go/cclient/test/main.c index 28e3d03b7a0..abfb32e5603 100644 --- a/paddle/go/cclient/test/main.c +++ b/paddle/go/cclient/test/main.c @@ -1,11 +1,12 @@ -#include "libclient.h" +#include -//#include "gtest/gtest.h" +#include "libclient.h" -void panic() { +void fail() { // TODO(helin): fix: gtest using cmake is not working, using this // hacky way for now. - *(void*)0; + printf("test failed.\n"); + exit(-1); } int main() { @@ -35,7 +36,7 @@ retry: goto retry; } } else { - panic(); + fail(); } char content[] = {0x00, 0x11, 0x22}; @@ -44,25 +45,25 @@ retry: {"param_b", PADDLE_ELEMENT_TYPE_FLOAT32, content, 3}}; if (!paddle_send_grads(c, grads, 2)) { - panic(); + fail(); } paddle_parameter* params[2] = {NULL, NULL}; char* names[] = {"param_a", "param_b"}; if (!paddle_get_params(c, names, params, 2)) { - panic(); + fail(); } // get parameters again by reusing the allocated parameter buffers. if (!paddle_get_params(c, names, params, 2)) { - panic(); + fail(); } paddle_release_param(params[0]); paddle_release_param(params[1]); if (!paddle_save_model(c, "/tmp/")) { - panic(); + fail(); } return 0; diff --git a/paddle/go/cmake/golang.cmake b/paddle/go/cmake/golang.cmake index caddaae1bf4..0ac17a967bf 100644 --- a/paddle/go/cmake/golang.cmake +++ b/paddle/go/cmake/golang.cmake @@ -31,11 +31,9 @@ function(ADD_GO_LIBRARY NAME BUILD_TYPE) # make a symlink that references Paddle inside $GOPATH, so go get # will use the local changes in Paddle rather than checkout Paddle # in github. - if(NOT EXISTS ${PADDLE_IN_GOPATH}) - add_custom_target(copyPaddle - COMMAND ln -s ${PADDLE_DIR} ${PADDLE_IN_GOPATH}) - add_dependencies(goGet copyPaddle) - endif() + add_custom_target(copyPaddle + COMMAND ln -sf ${PADDLE_DIR} ${PADDLE_IN_GOPATH}) + add_dependencies(goGet copyPaddle) add_custom_command(OUTPUT ${OUTPUT_DIR}/.timestamp COMMAND env GOPATH=${GOPATH} ${CMAKE_Go_COMPILER} build ${BUILD_MODE} diff --git a/paddle/go/crecordio/crecordio.go b/paddle/go/crecordio/crecordio.go index cfc15d29a66..e96bb49017b 100644 --- a/paddle/go/crecordio/crecordio.go +++ b/paddle/go/crecordio/crecordio.go @@ -9,10 +9,8 @@ typedef int writer; import "C" import ( - "io" "log" "os" - "path/filepath" "strings" "unsafe" @@ -27,84 +25,24 @@ type writer struct { } type reader struct { - buffer chan []byte - cancel chan struct{} + scanner *recordio.MultiScanner } -func read(paths []string, buffer chan<- []byte, cancel chan struct{}) { - var curFile *os.File - var curScanner *recordio.Scanner - var pathIdx int - - var nextFile func() bool - nextFile = func() bool { - if pathIdx >= len(paths) { - return false - } - - path := paths[pathIdx] - pathIdx++ - f, err := os.Open(path) - if err != nil { - return nextFile() - } - - idx, err := recordio.LoadIndex(f) - if err != nil { - log.Println(err) - err = f.Close() - if err != nil { - log.Println(err) - } - - return nextFile() - } - - curFile = f - curScanner = recordio.NewScanner(f, idx, 0, -1) - return true - } - - more := nextFile() - if !more { - close(buffer) - return - } - - closeFile := func() { - err := curFile.Close() - if err != nil { - log.Println(err) - } - curFile = nil +func cArrayToSlice(p unsafe.Pointer, len int) []byte { + if p == nullPtr { + return nil } - for { - for curScanner.Scan() { - select { - case buffer <- curScanner.Record(): - case <-cancel: - close(buffer) - closeFile() - return - } - } - - if err := curScanner.Error(); err != nil && err != io.EOF { - log.Println(err) - } - - closeFile() - more := nextFile() - if !more { - close(buffer) - return - } - } + // create a Go clice backed by a C array, reference: + // https://github.com/golang/go/wiki/cgo#turning-c-arrays-into-go-slices + // + // Go garbage collector will not interact with this data, need + // to be freed properly. + return (*[1 << 30]byte)(p)[:len:len] } -//export paddle_new_writer -func paddle_new_writer(path *C.char) C.writer { +//export create_recordio_writer +func create_recordio_writer(path *C.char) C.writer { p := C.GoString(path) f, err := os.Create(p) if err != nil { @@ -117,21 +55,8 @@ func paddle_new_writer(path *C.char) C.writer { return addWriter(writer) } -func cArrayToSlice(p unsafe.Pointer, len int) []byte { - if p == nullPtr { - return nil - } - - // create a Go clice backed by a C array, reference: - // https://github.com/golang/go/wiki/cgo#turning-c-arrays-into-go-slices - // - // Go garbage collector will not interact with this data, need - // to be freed properly. - return (*[1 << 30]byte)(p)[:len:len] -} - -//export paddle_writer_write -func paddle_writer_write(writer C.writer, buf *C.uchar, size C.int) int { +//export write_recordio +func write_recordio(writer C.writer, buf *C.uchar, size C.int) int { w := getWriter(writer) b := cArrayToSlice(unsafe.Pointer(buf), int(size)) _, err := w.w.Write(b) @@ -143,66 +68,50 @@ func paddle_writer_write(writer C.writer, buf *C.uchar, size C.int) int { return 0 } -//export paddle_writer_release -func paddle_writer_release(writer C.writer) { +//export release_recordio +func release_recordio(writer C.writer) { w := removeWriter(writer) w.w.Close() w.f.Close() } -//export paddle_new_reader -func paddle_new_reader(path *C.char, bufferSize C.int) C.reader { +//export create_recordio_reader +func create_recordio_reader(path *C.char) C.reader { p := C.GoString(path) - ss := strings.Split(p, ",") - var paths []string - for _, s := range ss { - match, err := filepath.Glob(s) - if err != nil { - log.Printf("error applying glob to %s: %v\n", s, err) - return -1 - } - - paths = append(paths, match...) - } - - if len(paths) == 0 { - log.Println("no valid path provided.", p) + s, err := recordio.NewMultiScanner(strings.Split(p, ",")) + if err != nil { + log.Println(err) return -1 } - buffer := make(chan []byte, int(bufferSize)) - cancel := make(chan struct{}) - r := &reader{buffer: buffer, cancel: cancel} - go read(paths, buffer, cancel) + r := &reader{scanner: s} return addReader(r) } -//export paddle_reader_next_item -func paddle_reader_next_item(reader C.reader, size *C.int) *C.uchar { +//export read_next_item +func read_next_item(reader C.reader, size *C.int) *C.uchar { r := getReader(reader) - buf, ok := <-r.buffer - if !ok { - // channel closed and empty, reached EOF. - *size = -1 - return (*C.uchar)(nullPtr) - } + if r.scanner.Scan() { + buf := r.scanner.Record() + *size = C.int(len(buf)) + + if len(buf) == 0 { + return (*C.uchar)(nullPtr) + } - if len(buf) == 0 { - // empty item - *size = 0 - return (*C.uchar)(nullPtr) + ptr := C.malloc(C.size_t(len(buf))) + C.memcpy(ptr, unsafe.Pointer(&buf[0]), C.size_t(len(buf))) + return (*C.uchar)(ptr) } - ptr := C.malloc(C.size_t(len(buf))) - C.memcpy(ptr, unsafe.Pointer(&buf[0]), C.size_t(len(buf))) - *size = C.int(len(buf)) - return (*C.uchar)(ptr) + *size = -1 + return (*C.uchar)(nullPtr) } -//export paddle_reader_release -func paddle_reader_release(reader C.reader) { +//export release_recordio_reader +func release_recordio_reader(reader C.reader) { r := removeReader(reader) - close(r.cancel) + r.scanner.Close() } func main() {} // Required but ignored diff --git a/paddle/go/crecordio/test/test.c b/paddle/go/crecordio/test/test.c index 5461a0911f5..54c3773ee94 100644 --- a/paddle/go/crecordio/test/test.c +++ b/paddle/go/crecordio/test/test.c @@ -3,30 +3,55 @@ #include "librecordio.h" -void panic() { +void fail() { // TODO(helin): fix: gtest using cmake is not working, using this // hacky way for now. - *(void*)0; + printf("test failed.\n"); + exit(-1); } int main() { - writer w = paddle_new_writer("/tmp/test"); - paddle_writer_write(w, "hello", 6); - paddle_writer_write(w, "hi", 3); - paddle_writer_release(w); + writer w = create_recordio_writer("/tmp/test_recordio_0"); + write_recordio(w, "hello", 6); + write_recordio(w, "hi", 3); + release_recordio(w); - reader r = paddle_new_reader("/tmp/test", 10); + w = create_recordio_writer("/tmp/test_recordio_1"); + write_recordio(w, "dog", 4); + write_recordio(w, "cat", 4); + release_recordio(w); + + reader r = create_recordio_reader("/tmp/test_recordio_*"); int size; - unsigned char* item = paddle_reader_next_item(r, &size); - if (!strcmp(item, "hello") || size != 6) { - panic(); + unsigned char* item = read_next_item(r, &size); + if (strcmp(item, "hello") || size != 6) { + fail(); + } + + free(item); + + item = read_next_item(r, &size); + if (strcmp(item, "hi") || size != 3) { + fail(); } free(item); - item = paddle_reader_next_item(r, &size); - if (!strcmp(item, "hi") || size != 2) { - panic(); + item = read_next_item(r, &size); + if (strcmp(item, "dog") || size != 4) { + fail(); } free(item); - paddle_reader_release(r); + + item = read_next_item(r, &size); + if (strcmp(item, "cat") || size != 4) { + fail(); + } + free(item); + + item = read_next_item(r, &size); + if (item != NULL || size != -1) { + fail(); + } + + release_recordio_reader(r); } diff --git a/paddle/go/recordio/README.md b/paddle/go/recordio/README.md index fbf568ceba4..50e7e954764 100644 --- a/paddle/go/recordio/README.md +++ b/paddle/go/recordio/README.md @@ -32,7 +32,7 @@ f.Close() for s.Scan() { fmt.Println(string(s.Record())) } - if s.Error() != nil && s.Error() != io.EOF { + if s.Err() != nil { log.Fatalf("Something wrong with scanning: %v", e) } f.Close() diff --git a/paddle/go/recordio/multi_reader.go b/paddle/go/recordio/multi_reader.go new file mode 100644 index 00000000000..07e28342118 --- /dev/null +++ b/paddle/go/recordio/multi_reader.go @@ -0,0 +1,140 @@ +package recordio + +import ( + "fmt" + "os" + "path/filepath" +) + +// MultiScanner is a scanner for multiple recordio files. +type MultiScanner struct { + paths []string + curFile *os.File + curScanner *Scanner + pathIdx int + end bool + err error +} + +// NewMultiScanner creates a new MultiScanner. +func NewMultiScanner(paths []string) (*MultiScanner, error) { + var ps []string + for _, s := range paths { + match, err := filepath.Glob(s) + if err != nil { + return nil, err + } + + ps = append(ps, match...) + } + + if len(ps) == 0 { + return nil, fmt.Errorf("no valid path provided: %v", paths) + } + + return &MultiScanner{paths: ps}, nil +} + +// Scan moves the cursor forward for one record and loads the chunk +// containing the record if not yet. +func (s *MultiScanner) Scan() bool { + if s.err != nil { + return false + } + + if s.end { + return false + } + + if s.curScanner == nil { + more, err := s.nextFile() + if err != nil { + s.err = err + return false + } + + if !more { + s.end = true + return false + } + } + + curMore := s.curScanner.Scan() + s.err = s.curScanner.Err() + + if s.err != nil { + return curMore + } + + if !curMore { + err := s.curFile.Close() + if err != nil { + s.err = err + return false + } + s.curFile = nil + + more, err := s.nextFile() + if err != nil { + s.err = err + return false + } + + if !more { + s.end = true + return false + } + + return s.Scan() + } + return true +} + +// Err returns the first non-EOF error that was encountered by the +// Scanner. +func (s *MultiScanner) Err() error { + return s.err +} + +// Record returns the record under the current cursor. +func (s *MultiScanner) Record() []byte { + if s.curScanner == nil { + return nil + } + + return s.curScanner.Record() +} + +// Close release the resources. +func (s *MultiScanner) Close() error { + s.curScanner = nil + if s.curFile != nil { + err := s.curFile.Close() + s.curFile = nil + return err + } + return nil +} + +func (s *MultiScanner) nextFile() (bool, error) { + if s.pathIdx >= len(s.paths) { + return false, nil + } + + path := s.paths[s.pathIdx] + s.pathIdx++ + f, err := os.Open(path) + if err != nil { + return false, err + } + + idx, err := LoadIndex(f) + if err != nil { + f.Close() + return false, err + } + + s.curFile = f + s.curScanner = NewScanner(f, idx, 0, -1) + return true, nil +} diff --git a/paddle/go/recordio/reader.go b/paddle/go/recordio/reader.go index a12c604f7b2..d00aef7ca99 100644 --- a/paddle/go/recordio/reader.go +++ b/paddle/go/recordio/reader.go @@ -129,7 +129,12 @@ func (s *Scanner) Record() []byte { return s.chunk.records[ri] } -// Error returns the error that stopped Scan. -func (s *Scanner) Error() error { +// Err returns the first non-EOF error that was encountered by the +// Scanner. +func (s *Scanner) Err() error { + if s.err == io.EOF { + return nil + } + return s.err } -- GitLab From ec5db3801c20c4ac2f93b404eadfc9c2ed48c079 Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Sat, 27 May 2017 14:51:40 +0000 Subject: [PATCH 0488/3256] fix according to comments --- paddle/go/cclient/CMakeLists.txt | 2 +- paddle/go/cmake/golang.cmake | 4 ++-- paddle/go/crecordio/CMakeLists.txt | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/paddle/go/cclient/CMakeLists.txt b/paddle/go/cclient/CMakeLists.txt index e3e9fa9f1a4..dfd104fb589 100644 --- a/paddle/go/cclient/CMakeLists.txt +++ b/paddle/go/cclient/CMakeLists.txt @@ -8,5 +8,5 @@ project(cxx_go C Go) include(golang) include(flags) -add_go_library(client STATIC) +go_library(client STATIC) add_subdirectory(test) diff --git a/paddle/go/cmake/golang.cmake b/paddle/go/cmake/golang.cmake index 0ac17a967bf..e73b0c865bc 100644 --- a/paddle/go/cmake/golang.cmake +++ b/paddle/go/cmake/golang.cmake @@ -3,7 +3,7 @@ file(MAKE_DIRECTORY ${GOPATH}) set(PADDLE_IN_GOPATH "${GOPATH}/src/github.com/PaddlePaddle") file(MAKE_DIRECTORY ${PADDLE_IN_GOPATH}) -function(ADD_GO_LIBRARY NAME BUILD_TYPE) +function(GO_LIBRARY NAME BUILD_TYPE) if(BUILD_TYPE STREQUAL "STATIC") set(BUILD_MODE -buildmode=c-archive) set(LIB_NAME "lib${NAME}.a") @@ -47,4 +47,4 @@ function(ADD_GO_LIBRARY NAME BUILD_TYPE) if(NOT BUILD_TYPE STREQUAL "STATIC") install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/${LIB_NAME} DESTINATION bin) endif() -endfunction(ADD_GO_LIBRARY) +endfunction(GO_LIBRARY) diff --git a/paddle/go/crecordio/CMakeLists.txt b/paddle/go/crecordio/CMakeLists.txt index db8f556e50b..c395fe0b4a4 100644 --- a/paddle/go/crecordio/CMakeLists.txt +++ b/paddle/go/crecordio/CMakeLists.txt @@ -8,5 +8,5 @@ project(cxx_go C Go) include(golang) include(flags) -add_go_library(recordio STATIC) +go_library(recordio STATIC) add_subdirectory(test) -- GitLab From f13ce7b6e837ff3cb6b76216a518d0a212138b57 Mon Sep 17 00:00:00 2001 From: bixiaopeng Date: Mon, 29 May 2017 14:51:02 +0800 Subject: [PATCH 0489/3256] =?UTF-8?q?bugfix[]:=E4=BF=AE=E6=94=B9=E6=AD=BB?= =?UTF-8?q?=E9=93=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bcc24b84128..fa16cc3cf2e 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ before looking into the We provide [English](http://www.paddlepaddle.org/develop/doc/) and [Chinese](http://www.paddlepaddle.org/doc_cn/) documentation. -- [Deep Learning 101](http://book.paddlepaddle.org/index.en.html) +- [Deep Learning 101](http://book.paddlepaddle.org/index.html) You might want to start from the this online interactive book that can run in Jupyter Notebook. -- GitLab From efd2d3351ca165ecf846f5c3dfe43a009e139666 Mon Sep 17 00:00:00 2001 From: wwhu Date: Mon, 29 May 2017 15:11:47 +0800 Subject: [PATCH 0490/3256] add multiplex layer for python API --- .../paddle/trainer_config_helpers/layers.py | 53 ++++++++++++++++ .../tests/configs/file_list.sh | 2 +- .../protostr/test_multiplex_layer.protostr | 63 +++++++++++++++++++ .../tests/configs/test_multiplex_layer.py | 12 ++++ 4 files changed, 129 insertions(+), 1 deletion(-) create mode 100644 python/paddle/trainer_config_helpers/tests/configs/protostr/test_multiplex_layer.protostr create mode 100644 python/paddle/trainer_config_helpers/tests/configs/test_multiplex_layer.py diff --git a/python/paddle/trainer_config_helpers/layers.py b/python/paddle/trainer_config_helpers/layers.py index ec81e1dc3d2..049b78d514c 100755 --- a/python/paddle/trainer_config_helpers/layers.py +++ b/python/paddle/trainer_config_helpers/layers.py @@ -119,6 +119,7 @@ __all__ = [ 'eos_layer', 'smooth_l1_cost', 'layer_support', + 'multiplex_layer', ] @@ -185,6 +186,7 @@ class LayerType(object): MAXOUT = "maxout" SPP_LAYER = "spp" PAD_LAYER = "pad" + MULTIPLEX_LAYER = "multiplex" PRINT_LAYER = "print" PRIORBOX_LAYER = "priorbox" @@ -5465,3 +5467,54 @@ def smooth_l1_cost(input, label, name=None, coeff=1.0, layer_attr=None): **ExtraLayerAttribute.to_kwargs(layer_attr)) return LayerOutput( name, LayerType.SMOOTH_L1, parents=[input, label], size=1) + + +@wrap_name_default() +def multiplex_layer(input, name=None, layer_attr=None): + """ + This layer multiplex multiple layers according to the index, + which is provided by the first input layer. + inputs[0]: the index of the layer to output of size batchSize. + inputs[1:N]; the candidate output data. + For each index i from 0 to batchSize -1, the output is the i-th row of the + (index[i] + 1)-th layer. + + For each i-th row of output: + .. math:: + y[i][j] = x_{x_{0}[i] + 1}[i][j], j = 0,1, ... , (x_{1}.width - 1) + + where, y is output. :math:`x_{k}` is the k-th input layer and + :math:`k = x_{0}[i] + 1`. + + .. code-block:: python + + maxid = multiplex_layer(input=layers) + + :param input: Input layers. + :type input: list of LayerOutput + :param name: Layer name. + :type name: basestring + :param layer_attr: extra layer attributes. + :type layer_attr: ExtraLayerAttribute. + :return: LayerOutput object. + :rtype: LayerOutput + """ + + assert isinstance(input, collections.Sequence) + assert len(input) > 2, 'multiplex_layer should have more than 2 inputs' + for i in range(1, len(input)): + assert isinstance(input[i], LayerOutput) + assert input[i].size == input[1].size, \ + "All the input layers except the first one should have the same size" + + l = Layer( + name=name, + type='multiplex', + inputs=[x.name for x in input], + size=input[1].size, + **ExtraLayerAttribute.to_kwargs(layer_attr)) + return LayerOutput( + name=name, + layer_type=LayerType.MULTIPLEX_LAYER, + parents=input, + size=l.config.size) diff --git a/python/paddle/trainer_config_helpers/tests/configs/file_list.sh b/python/paddle/trainer_config_helpers/tests/configs/file_list.sh index c5dc8e1aab0..981ccbf2483 100755 --- a/python/paddle/trainer_config_helpers/tests/configs/file_list.sh +++ b/python/paddle/trainer_config_helpers/tests/configs/file_list.sh @@ -5,6 +5,6 @@ last_first_seq test_expand_layer test_ntm_layers test_hsigmoid img_layers img_trans_layers util_layers simple_rnn_layers unused_layers test_cost_layers test_rnn_group shared_fc shared_lstm shared_gru test_cost_layers_with_weight test_spp_layer test_bilinear_interp test_maxout test_bi_grumemory math_ops -test_seq_concat_reshape test_pad test_smooth_l1) +test_seq_concat_reshape test_pad test_smooth_l1 test_multiplex_layer) export whole_configs=(test_split_datasource) diff --git a/python/paddle/trainer_config_helpers/tests/configs/protostr/test_multiplex_layer.protostr b/python/paddle/trainer_config_helpers/tests/configs/protostr/test_multiplex_layer.protostr new file mode 100644 index 00000000000..379842ba8d3 --- /dev/null +++ b/python/paddle/trainer_config_helpers/tests/configs/protostr/test_multiplex_layer.protostr @@ -0,0 +1,63 @@ +type: "nn" +layers { + name: "index" + type: "data" + size: 1 + active_type: "" +} +layers { + name: "data1" + type: "data" + size: 30 + active_type: "" +} +layers { + name: "data2" + type: "data" + size: 30 + active_type: "" +} +layers { + name: "data3" + type: "data" + size: 30 + active_type: "" +} +layers { + name: "__multiplex_layer_0__" + type: "multiplex" + size: 30 + active_type: "" + inputs { + input_layer_name: "index" + } + inputs { + input_layer_name: "data1" + } + inputs { + input_layer_name: "data2" + } + inputs { + input_layer_name: "data3" + } +} +input_layer_names: "index" +input_layer_names: "data1" +input_layer_names: "data2" +input_layer_names: "data3" +output_layer_names: "__multiplex_layer_0__" +sub_models { + name: "root" + layer_names: "index" + layer_names: "data1" + layer_names: "data2" + layer_names: "data3" + layer_names: "__multiplex_layer_0__" + input_layer_names: "index" + input_layer_names: "data1" + input_layer_names: "data2" + input_layer_names: "data3" + output_layer_names: "__multiplex_layer_0__" + is_recurrent_layer_group: false +} + diff --git a/python/paddle/trainer_config_helpers/tests/configs/test_multiplex_layer.py b/python/paddle/trainer_config_helpers/tests/configs/test_multiplex_layer.py new file mode 100644 index 00000000000..d2500019325 --- /dev/null +++ b/python/paddle/trainer_config_helpers/tests/configs/test_multiplex_layer.py @@ -0,0 +1,12 @@ +from paddle.trainer_config_helpers import * + +settings(batch_size=1000, learning_rate=1e-5) + +index = data_layer(name='index', size=1) +din1 = data_layer(name='data1', size=30) +din2 = data_layer(name='data2', size=30) +din3 = data_layer(name='data3', size=30) + +dout = multiplex_layer([index, din1, din2, din3]) + +outputs(dout) -- GitLab From 2fa274cf3672df25a0926b100cf2585596d24fe0 Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Tue, 30 May 2017 20:34:27 +0000 Subject: [PATCH 0491/3256] fix according to comments --- paddle/go/crecordio/crecordio.go | 35 +++++++++---------- paddle/go/crecordio/test/test.c | 27 +++++++------- .../recordio/{reader.go => range_scanner.go} | 16 ++++----- paddle/go/recordio/recordio_internal_test.go | 2 +- paddle/go/recordio/recordio_test.go | 4 +-- .../recordio/{multi_reader.go => scanner.go} | 24 ++++++------- 6 files changed, 53 insertions(+), 55 deletions(-) rename paddle/go/recordio/{reader.go => range_scanner.go} (88%) rename paddle/go/recordio/{multi_reader.go => scanner.go} (77%) diff --git a/paddle/go/crecordio/crecordio.go b/paddle/go/crecordio/crecordio.go index e96bb49017b..33f97de8cf7 100644 --- a/paddle/go/crecordio/crecordio.go +++ b/paddle/go/crecordio/crecordio.go @@ -25,7 +25,7 @@ type writer struct { } type reader struct { - scanner *recordio.MultiScanner + scanner *recordio.Scanner } func cArrayToSlice(p unsafe.Pointer, len int) []byte { @@ -55,21 +55,21 @@ func create_recordio_writer(path *C.char) C.writer { return addWriter(writer) } -//export write_recordio -func write_recordio(writer C.writer, buf *C.uchar, size C.int) int { +//export recordio_write +func recordio_write(writer C.writer, buf *C.uchar, size C.int) C.int { w := getWriter(writer) b := cArrayToSlice(unsafe.Pointer(buf), int(size)) - _, err := w.w.Write(b) + c, err := w.w.Write(b) if err != nil { log.Println(err) return -1 } - return 0 + return C.int(c) } -//export release_recordio -func release_recordio(writer C.writer) { +//export release_recordio_writer +func release_recordio_writer(writer C.writer) { w := removeWriter(writer) w.w.Close() w.f.Close() @@ -78,7 +78,7 @@ func release_recordio(writer C.writer) { //export create_recordio_reader func create_recordio_reader(path *C.char) C.reader { p := C.GoString(path) - s, err := recordio.NewMultiScanner(strings.Split(p, ",")) + s, err := recordio.NewScanner(strings.Split(p, ",")...) if err != nil { log.Println(err) return -1 @@ -88,24 +88,23 @@ func create_recordio_reader(path *C.char) C.reader { return addReader(r) } -//export read_next_item -func read_next_item(reader C.reader, size *C.int) *C.uchar { +//export recordio_read +func recordio_read(reader C.reader, record **C.uchar) C.int { r := getReader(reader) if r.scanner.Scan() { buf := r.scanner.Record() - *size = C.int(len(buf)) - if len(buf) == 0 { - return (*C.uchar)(nullPtr) + *record = (*C.uchar)(nullPtr) + return 0 } - ptr := C.malloc(C.size_t(len(buf))) - C.memcpy(ptr, unsafe.Pointer(&buf[0]), C.size_t(len(buf))) - return (*C.uchar)(ptr) + size := C.int(len(buf)) + *record = (*C.uchar)(C.malloc(C.size_t(len(buf)))) + C.memcpy(unsafe.Pointer(*record), unsafe.Pointer(&buf[0]), C.size_t(len(buf))) + return size } - *size = -1 - return (*C.uchar)(nullPtr) + return -1 } //export release_recordio_reader diff --git a/paddle/go/crecordio/test/test.c b/paddle/go/crecordio/test/test.c index 54c3773ee94..b25536a9d76 100644 --- a/paddle/go/crecordio/test/test.c +++ b/paddle/go/crecordio/test/test.c @@ -12,44 +12,43 @@ void fail() { int main() { writer w = create_recordio_writer("/tmp/test_recordio_0"); - write_recordio(w, "hello", 6); - write_recordio(w, "hi", 3); - release_recordio(w); + recordio_write(w, "hello", 6); + recordio_write(w, "hi", 3); + release_recordio_writer(w); w = create_recordio_writer("/tmp/test_recordio_1"); - write_recordio(w, "dog", 4); - write_recordio(w, "cat", 4); - release_recordio(w); + recordio_write(w, "dog", 4); + recordio_write(w, "cat", 4); + release_recordio_writer(w); reader r = create_recordio_reader("/tmp/test_recordio_*"); - int size; - unsigned char* item = read_next_item(r, &size); + unsigned char* item = NULL; + int size = recordio_read(r, &item); if (strcmp(item, "hello") || size != 6) { fail(); } - free(item); - item = read_next_item(r, &size); + size = recordio_read(r, &item); if (strcmp(item, "hi") || size != 3) { fail(); } free(item); - item = read_next_item(r, &size); + size = recordio_read(r, &item); if (strcmp(item, "dog") || size != 4) { fail(); } free(item); - item = read_next_item(r, &size); + size = recordio_read(r, &item); if (strcmp(item, "cat") || size != 4) { fail(); } free(item); - item = read_next_item(r, &size); - if (item != NULL || size != -1) { + size = recordio_read(r, &item); + if (size != -1) { fail(); } diff --git a/paddle/go/recordio/reader.go b/paddle/go/recordio/range_scanner.go similarity index 88% rename from paddle/go/recordio/reader.go rename to paddle/go/recordio/range_scanner.go index d00aef7ca99..46e2eee68c7 100644 --- a/paddle/go/recordio/reader.go +++ b/paddle/go/recordio/range_scanner.go @@ -74,8 +74,8 @@ func (r *Index) Locate(recordIndex int) (int, int) { return -1, -1 } -// Scanner scans records in a specified range within [0, numRecords). -type Scanner struct { +// RangeScanner scans records in a specified range within [0, numRecords). +type RangeScanner struct { reader io.ReadSeeker index *Index start, end, cur int @@ -84,10 +84,10 @@ type Scanner struct { err error } -// NewScanner creates a scanner that sequencially reads records in the +// NewRangeScanner creates a scanner that sequencially reads records in the // range [start, start+len). If start < 0, it scans from the // beginning. If len < 0, it scans till the end of file. -func NewScanner(r io.ReadSeeker, index *Index, start, len int) *Scanner { +func NewRangeScanner(r io.ReadSeeker, index *Index, start, len int) *RangeScanner { if start < 0 { start = 0 } @@ -95,7 +95,7 @@ func NewScanner(r io.ReadSeeker, index *Index, start, len int) *Scanner { len = index.NumRecords() - start } - return &Scanner{ + return &RangeScanner{ reader: r, index: index, start: start, @@ -108,7 +108,7 @@ func NewScanner(r io.ReadSeeker, index *Index, start, len int) *Scanner { // Scan moves the cursor forward for one record and loads the chunk // containing the record if not yet. -func (s *Scanner) Scan() bool { +func (s *RangeScanner) Scan() bool { s.cur++ if s.cur >= s.end { @@ -124,14 +124,14 @@ func (s *Scanner) Scan() bool { } // Record returns the record under the current cursor. -func (s *Scanner) Record() []byte { +func (s *RangeScanner) Record() []byte { _, ri := s.index.Locate(s.cur) return s.chunk.records[ri] } // Err returns the first non-EOF error that was encountered by the // Scanner. -func (s *Scanner) Err() error { +func (s *RangeScanner) Err() error { if s.err == io.EOF { return nil } diff --git a/paddle/go/recordio/recordio_internal_test.go b/paddle/go/recordio/recordio_internal_test.go index e0f7dd0407c..30e317925d8 100644 --- a/paddle/go/recordio/recordio_internal_test.go +++ b/paddle/go/recordio/recordio_internal_test.go @@ -68,7 +68,7 @@ func TestWriteAndRead(t *testing.T) { 2*4)}, // two record legnths idx.chunkOffsets) - s := NewScanner(bytes.NewReader(buf.Bytes()), idx, -1, -1) + s := NewRangeScanner(bytes.NewReader(buf.Bytes()), idx, -1, -1) i := 0 for s.Scan() { assert.Equal(data[i], string(s.Record())) diff --git a/paddle/go/recordio/recordio_test.go b/paddle/go/recordio/recordio_test.go index 8bf1b020ab7..ab117d2050e 100644 --- a/paddle/go/recordio/recordio_test.go +++ b/paddle/go/recordio/recordio_test.go @@ -29,7 +29,7 @@ func TestWriteRead(t *testing.T) { t.Fatal("num record does not match:", idx.NumRecords(), total) } - s := recordio.NewScanner(bytes.NewReader(buf.Bytes()), idx, -1, -1) + s := recordio.NewRangeScanner(bytes.NewReader(buf.Bytes()), idx, -1, -1) i := 0 for s.Scan() { if !reflect.DeepEqual(s.Record(), make([]byte, i)) { @@ -66,7 +66,7 @@ func TestChunkIndex(t *testing.T) { for i := 0; i < total; i++ { newIdx := idx.ChunkIndex(i) - s := recordio.NewScanner(bytes.NewReader(buf.Bytes()), newIdx, -1, -1) + s := recordio.NewRangeScanner(bytes.NewReader(buf.Bytes()), newIdx, -1, -1) j := 0 for s.Scan() { if !reflect.DeepEqual(s.Record(), make([]byte, i)) { diff --git a/paddle/go/recordio/multi_reader.go b/paddle/go/recordio/scanner.go similarity index 77% rename from paddle/go/recordio/multi_reader.go rename to paddle/go/recordio/scanner.go index 07e28342118..865228ff651 100644 --- a/paddle/go/recordio/multi_reader.go +++ b/paddle/go/recordio/scanner.go @@ -6,18 +6,18 @@ import ( "path/filepath" ) -// MultiScanner is a scanner for multiple recordio files. -type MultiScanner struct { +// Scanner is a scanner for multiple recordio files. +type Scanner struct { paths []string curFile *os.File - curScanner *Scanner + curScanner *RangeScanner pathIdx int end bool err error } -// NewMultiScanner creates a new MultiScanner. -func NewMultiScanner(paths []string) (*MultiScanner, error) { +// NewScanner creates a new Scanner. +func NewScanner(paths ...string) (*Scanner, error) { var ps []string for _, s := range paths { match, err := filepath.Glob(s) @@ -32,12 +32,12 @@ func NewMultiScanner(paths []string) (*MultiScanner, error) { return nil, fmt.Errorf("no valid path provided: %v", paths) } - return &MultiScanner{paths: ps}, nil + return &Scanner{paths: ps}, nil } // Scan moves the cursor forward for one record and loads the chunk // containing the record if not yet. -func (s *MultiScanner) Scan() bool { +func (s *Scanner) Scan() bool { if s.err != nil { return false } @@ -92,12 +92,12 @@ func (s *MultiScanner) Scan() bool { // Err returns the first non-EOF error that was encountered by the // Scanner. -func (s *MultiScanner) Err() error { +func (s *Scanner) Err() error { return s.err } // Record returns the record under the current cursor. -func (s *MultiScanner) Record() []byte { +func (s *Scanner) Record() []byte { if s.curScanner == nil { return nil } @@ -106,7 +106,7 @@ func (s *MultiScanner) Record() []byte { } // Close release the resources. -func (s *MultiScanner) Close() error { +func (s *Scanner) Close() error { s.curScanner = nil if s.curFile != nil { err := s.curFile.Close() @@ -116,7 +116,7 @@ func (s *MultiScanner) Close() error { return nil } -func (s *MultiScanner) nextFile() (bool, error) { +func (s *Scanner) nextFile() (bool, error) { if s.pathIdx >= len(s.paths) { return false, nil } @@ -135,6 +135,6 @@ func (s *MultiScanner) nextFile() (bool, error) { } s.curFile = f - s.curScanner = NewScanner(f, idx, 0, -1) + s.curScanner = NewRangeScanner(f, idx, 0, -1) return true, nil } -- GitLab From 28b23e6db596e7acc4e2434373188324ba11dd6b Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Tue, 30 May 2017 22:34:19 +0000 Subject: [PATCH 0492/3256] move paddle/go to go, move go/cclient to go/pserver/cclient, move go/crecordio to go/recordio/c --- {paddle/go => go}/cmake/CMakeDetermineGoCompiler.cmake | 0 {paddle/go => go}/cmake/CMakeGoCompiler.cmake.in | 0 {paddle/go => go}/cmake/CMakeGoInformation.cmake | 0 {paddle/go => go}/cmake/CMakeTestGoCompiler.cmake | 0 {paddle/go => go}/cmake/flags.cmake | 0 {paddle/go => go}/cmake/golang.cmake | 0 {paddle/go => go}/cmd/master/master.go | 4 ++-- {paddle/go => go}/cmd/pserver/.gitignore | 0 {paddle/go => go}/cmd/pserver/pserver.go | 2 +- {paddle/go => go}/master/service.go | 2 +- {paddle/go => go}/master/service_internal_test.go | 0 {paddle/go => go/pserver}/cclient/CMakeLists.txt | 1 + {paddle/go => go/pserver}/cclient/cclient.go | 2 +- {paddle/go => go/pserver}/cclient/test/CMakeLists.txt | 0 {paddle/go => go/pserver}/cclient/test/main.c | 0 {paddle/go => go}/pserver/client.go | 0 {paddle/go => go}/pserver/optimizer.c | 0 {paddle/go => go}/pserver/optimizer.go | 0 {paddle/go => go}/pserver/optimizer.h | 0 {paddle/go => go}/pserver/optimizer_test.go | 0 {paddle/go => go}/pserver/service.go | 0 {paddle/go => go}/pserver/service_test.go | 2 +- {paddle/go => go}/recordio/README.md | 0 {paddle/go/crecordio => go/recordio/c}/CMakeLists.txt | 1 + {paddle/go/crecordio => go/recordio/c}/crecordio.go | 2 +- {paddle/go/crecordio => go/recordio/c}/register.go | 0 {paddle/go/crecordio => go/recordio/c}/test/CMakeLists.txt | 0 {paddle/go/crecordio => go/recordio/c}/test/test.c | 0 {paddle/go => go}/recordio/chunk.go | 0 {paddle/go => go}/recordio/header.go | 0 {paddle/go => go}/recordio/range_scanner.go | 0 {paddle/go => go}/recordio/recordio_internal_test.go | 0 {paddle/go => go}/recordio/recordio_test.go | 2 +- {paddle/go => go}/recordio/scanner.go | 0 {paddle/go => go}/recordio/writer.go | 0 35 files changed, 10 insertions(+), 8 deletions(-) rename {paddle/go => go}/cmake/CMakeDetermineGoCompiler.cmake (100%) rename {paddle/go => go}/cmake/CMakeGoCompiler.cmake.in (100%) rename {paddle/go => go}/cmake/CMakeGoInformation.cmake (100%) rename {paddle/go => go}/cmake/CMakeTestGoCompiler.cmake (100%) rename {paddle/go => go}/cmake/flags.cmake (100%) rename {paddle/go => go}/cmake/golang.cmake (100%) rename {paddle/go => go}/cmd/master/master.go (94%) rename {paddle/go => go}/cmd/pserver/.gitignore (100%) rename {paddle/go => go}/cmd/pserver/pserver.go (89%) rename {paddle/go => go}/master/service.go (98%) rename {paddle/go => go}/master/service_internal_test.go (100%) rename {paddle/go => go/pserver}/cclient/CMakeLists.txt (82%) rename {paddle/go => go/pserver}/cclient/cclient.go (99%) rename {paddle/go => go/pserver}/cclient/test/CMakeLists.txt (100%) rename {paddle/go => go/pserver}/cclient/test/main.c (100%) rename {paddle/go => go}/pserver/client.go (100%) rename {paddle/go => go}/pserver/optimizer.c (100%) rename {paddle/go => go}/pserver/optimizer.go (100%) rename {paddle/go => go}/pserver/optimizer.h (100%) rename {paddle/go => go}/pserver/optimizer_test.go (100%) rename {paddle/go => go}/pserver/service.go (100%) rename {paddle/go => go}/pserver/service_test.go (98%) rename {paddle/go => go}/recordio/README.md (100%) rename {paddle/go/crecordio => go/recordio/c}/CMakeLists.txt (82%) rename {paddle/go/crecordio => go/recordio/c}/crecordio.go (97%) rename {paddle/go/crecordio => go/recordio/c}/register.go (100%) rename {paddle/go/crecordio => go/recordio/c}/test/CMakeLists.txt (100%) rename {paddle/go/crecordio => go/recordio/c}/test/test.c (100%) rename {paddle/go => go}/recordio/chunk.go (100%) rename {paddle/go => go}/recordio/header.go (100%) rename {paddle/go => go}/recordio/range_scanner.go (100%) rename {paddle/go => go}/recordio/recordio_internal_test.go (100%) rename {paddle/go => go}/recordio/recordio_test.go (96%) rename {paddle/go => go}/recordio/scanner.go (100%) rename {paddle/go => go}/recordio/writer.go (100%) diff --git a/paddle/go/cmake/CMakeDetermineGoCompiler.cmake b/go/cmake/CMakeDetermineGoCompiler.cmake similarity index 100% rename from paddle/go/cmake/CMakeDetermineGoCompiler.cmake rename to go/cmake/CMakeDetermineGoCompiler.cmake diff --git a/paddle/go/cmake/CMakeGoCompiler.cmake.in b/go/cmake/CMakeGoCompiler.cmake.in similarity index 100% rename from paddle/go/cmake/CMakeGoCompiler.cmake.in rename to go/cmake/CMakeGoCompiler.cmake.in diff --git a/paddle/go/cmake/CMakeGoInformation.cmake b/go/cmake/CMakeGoInformation.cmake similarity index 100% rename from paddle/go/cmake/CMakeGoInformation.cmake rename to go/cmake/CMakeGoInformation.cmake diff --git a/paddle/go/cmake/CMakeTestGoCompiler.cmake b/go/cmake/CMakeTestGoCompiler.cmake similarity index 100% rename from paddle/go/cmake/CMakeTestGoCompiler.cmake rename to go/cmake/CMakeTestGoCompiler.cmake diff --git a/paddle/go/cmake/flags.cmake b/go/cmake/flags.cmake similarity index 100% rename from paddle/go/cmake/flags.cmake rename to go/cmake/flags.cmake diff --git a/paddle/go/cmake/golang.cmake b/go/cmake/golang.cmake similarity index 100% rename from paddle/go/cmake/golang.cmake rename to go/cmake/golang.cmake diff --git a/paddle/go/cmd/master/master.go b/go/cmd/master/master.go similarity index 94% rename from paddle/go/cmd/master/master.go rename to go/cmd/master/master.go index ef1f87c2dd5..cc6e45049a3 100644 --- a/paddle/go/cmd/master/master.go +++ b/go/cmd/master/master.go @@ -13,8 +13,8 @@ import ( "github.com/namsral/flag" - "github.com/PaddlePaddle/Paddle/paddle/go/master" - "github.com/PaddlePaddle/Paddle/paddle/go/recordio" + "github.com/PaddlePaddle/Paddle/go/master" + "github.com/PaddlePaddle/Paddle/go/recordio" ) func main() { diff --git a/paddle/go/cmd/pserver/.gitignore b/go/cmd/pserver/.gitignore similarity index 100% rename from paddle/go/cmd/pserver/.gitignore rename to go/cmd/pserver/.gitignore diff --git a/paddle/go/cmd/pserver/pserver.go b/go/cmd/pserver/pserver.go similarity index 89% rename from paddle/go/cmd/pserver/pserver.go rename to go/cmd/pserver/pserver.go index bd4bfc70283..f0be251c247 100644 --- a/paddle/go/cmd/pserver/pserver.go +++ b/go/cmd/pserver/pserver.go @@ -8,7 +8,7 @@ import ( "github.com/namsral/flag" - "github.com/PaddlePaddle/Paddle/paddle/go/pserver" + "github.com/PaddlePaddle/Paddle/go/pserver" ) func main() { diff --git a/paddle/go/master/service.go b/go/master/service.go similarity index 98% rename from paddle/go/master/service.go rename to go/master/service.go index 75266482870..50e646b01f0 100644 --- a/paddle/go/master/service.go +++ b/go/master/service.go @@ -6,7 +6,7 @@ import ( "sync" "time" - "github.com/PaddlePaddle/Paddle/paddle/go/recordio" + "github.com/PaddlePaddle/Paddle/go/recordio" ) const ( diff --git a/paddle/go/master/service_internal_test.go b/go/master/service_internal_test.go similarity index 100% rename from paddle/go/master/service_internal_test.go rename to go/master/service_internal_test.go diff --git a/paddle/go/cclient/CMakeLists.txt b/go/pserver/cclient/CMakeLists.txt similarity index 82% rename from paddle/go/cclient/CMakeLists.txt rename to go/pserver/cclient/CMakeLists.txt index dfd104fb589..c017d746561 100644 --- a/paddle/go/cclient/CMakeLists.txt +++ b/go/pserver/cclient/CMakeLists.txt @@ -1,6 +1,7 @@ cmake_minimum_required(VERSION 3.0) get_filename_component(PARENT_DIR ${CMAKE_CURRENT_SOURCE_DIR} DIRECTORY) +get_filename_component(PARENT_DIR ${PARENT_DIR} DIRECTORY) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${PARENT_DIR}/cmake") project(cxx_go C Go) diff --git a/paddle/go/cclient/cclient.go b/go/pserver/cclient/cclient.go similarity index 99% rename from paddle/go/cclient/cclient.go rename to go/pserver/cclient/cclient.go index ee2d9d24fd8..1b5560451a8 100644 --- a/paddle/go/cclient/cclient.go +++ b/go/pserver/cclient/cclient.go @@ -42,7 +42,7 @@ import ( "sync" "unsafe" - "github.com/PaddlePaddle/Paddle/paddle/go/pserver" + "github.com/PaddlePaddle/Paddle/go/pserver" ) var nullPtr = unsafe.Pointer(uintptr(0)) diff --git a/paddle/go/cclient/test/CMakeLists.txt b/go/pserver/cclient/test/CMakeLists.txt similarity index 100% rename from paddle/go/cclient/test/CMakeLists.txt rename to go/pserver/cclient/test/CMakeLists.txt diff --git a/paddle/go/cclient/test/main.c b/go/pserver/cclient/test/main.c similarity index 100% rename from paddle/go/cclient/test/main.c rename to go/pserver/cclient/test/main.c diff --git a/paddle/go/pserver/client.go b/go/pserver/client.go similarity index 100% rename from paddle/go/pserver/client.go rename to go/pserver/client.go diff --git a/paddle/go/pserver/optimizer.c b/go/pserver/optimizer.c similarity index 100% rename from paddle/go/pserver/optimizer.c rename to go/pserver/optimizer.c diff --git a/paddle/go/pserver/optimizer.go b/go/pserver/optimizer.go similarity index 100% rename from paddle/go/pserver/optimizer.go rename to go/pserver/optimizer.go diff --git a/paddle/go/pserver/optimizer.h b/go/pserver/optimizer.h similarity index 100% rename from paddle/go/pserver/optimizer.h rename to go/pserver/optimizer.h diff --git a/paddle/go/pserver/optimizer_test.go b/go/pserver/optimizer_test.go similarity index 100% rename from paddle/go/pserver/optimizer_test.go rename to go/pserver/optimizer_test.go diff --git a/paddle/go/pserver/service.go b/go/pserver/service.go similarity index 100% rename from paddle/go/pserver/service.go rename to go/pserver/service.go diff --git a/paddle/go/pserver/service_test.go b/go/pserver/service_test.go similarity index 98% rename from paddle/go/pserver/service_test.go rename to go/pserver/service_test.go index 10185bd0f20..c58ccf9231d 100644 --- a/paddle/go/pserver/service_test.go +++ b/go/pserver/service_test.go @@ -5,7 +5,7 @@ import ( "sync" "testing" - "github.com/PaddlePaddle/Paddle/paddle/go/pserver" + "github.com/PaddlePaddle/Paddle/go/pserver" ) func TestFull(t *testing.T) { diff --git a/paddle/go/recordio/README.md b/go/recordio/README.md similarity index 100% rename from paddle/go/recordio/README.md rename to go/recordio/README.md diff --git a/paddle/go/crecordio/CMakeLists.txt b/go/recordio/c/CMakeLists.txt similarity index 82% rename from paddle/go/crecordio/CMakeLists.txt rename to go/recordio/c/CMakeLists.txt index c395fe0b4a4..c300c091f87 100644 --- a/paddle/go/crecordio/CMakeLists.txt +++ b/go/recordio/c/CMakeLists.txt @@ -1,6 +1,7 @@ cmake_minimum_required(VERSION 3.0) get_filename_component(PARENT_DIR ${CMAKE_CURRENT_SOURCE_DIR} DIRECTORY) +get_filename_component(PARENT_DIR ${PARENT_DIR} DIRECTORY) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${PARENT_DIR}/cmake") project(cxx_go C Go) diff --git a/paddle/go/crecordio/crecordio.go b/go/recordio/c/crecordio.go similarity index 97% rename from paddle/go/crecordio/crecordio.go rename to go/recordio/c/crecordio.go index 33f97de8cf7..e5cc3029928 100644 --- a/paddle/go/crecordio/crecordio.go +++ b/go/recordio/c/crecordio.go @@ -14,7 +14,7 @@ import ( "strings" "unsafe" - "github.com/PaddlePaddle/Paddle/paddle/go/recordio" + "github.com/PaddlePaddle/Paddle/go/recordio" ) var nullPtr = unsafe.Pointer(uintptr(0)) diff --git a/paddle/go/crecordio/register.go b/go/recordio/c/register.go similarity index 100% rename from paddle/go/crecordio/register.go rename to go/recordio/c/register.go diff --git a/paddle/go/crecordio/test/CMakeLists.txt b/go/recordio/c/test/CMakeLists.txt similarity index 100% rename from paddle/go/crecordio/test/CMakeLists.txt rename to go/recordio/c/test/CMakeLists.txt diff --git a/paddle/go/crecordio/test/test.c b/go/recordio/c/test/test.c similarity index 100% rename from paddle/go/crecordio/test/test.c rename to go/recordio/c/test/test.c diff --git a/paddle/go/recordio/chunk.go b/go/recordio/chunk.go similarity index 100% rename from paddle/go/recordio/chunk.go rename to go/recordio/chunk.go diff --git a/paddle/go/recordio/header.go b/go/recordio/header.go similarity index 100% rename from paddle/go/recordio/header.go rename to go/recordio/header.go diff --git a/paddle/go/recordio/range_scanner.go b/go/recordio/range_scanner.go similarity index 100% rename from paddle/go/recordio/range_scanner.go rename to go/recordio/range_scanner.go diff --git a/paddle/go/recordio/recordio_internal_test.go b/go/recordio/recordio_internal_test.go similarity index 100% rename from paddle/go/recordio/recordio_internal_test.go rename to go/recordio/recordio_internal_test.go diff --git a/paddle/go/recordio/recordio_test.go b/go/recordio/recordio_test.go similarity index 96% rename from paddle/go/recordio/recordio_test.go rename to go/recordio/recordio_test.go index ab117d2050e..e4ef835afa6 100644 --- a/paddle/go/recordio/recordio_test.go +++ b/go/recordio/recordio_test.go @@ -5,7 +5,7 @@ import ( "reflect" "testing" - "github.com/PaddlePaddle/Paddle/paddle/go/recordio" + "github.com/PaddlePaddle/Paddle/go/recordio" ) func TestWriteRead(t *testing.T) { diff --git a/paddle/go/recordio/scanner.go b/go/recordio/scanner.go similarity index 100% rename from paddle/go/recordio/scanner.go rename to go/recordio/scanner.go diff --git a/paddle/go/recordio/writer.go b/go/recordio/writer.go similarity index 100% rename from paddle/go/recordio/writer.go rename to go/recordio/writer.go -- GitLab From 350b268b7ccbafd53261f13659296f5e77c3f3b1 Mon Sep 17 00:00:00 2001 From: xuwei06 Date: Tue, 30 May 2017 16:10:49 -0700 Subject: [PATCH 0493/3256] Adding simple operators for v2 API --- python/paddle/v2/__init__.py | 1 + python/paddle/v2/config_base.py | 4 +- python/paddle/v2/op.py | 120 ++++++++++++++++++++++++++ python/paddle/v2/tests/CMakeLists.txt | 2 +- python/paddle/v2/tests/test_op.py | 50 +++++++++++ 5 files changed, 174 insertions(+), 3 deletions(-) create mode 100644 python/paddle/v2/op.py create mode 100644 python/paddle/v2/tests/test_op.py diff --git a/python/paddle/v2/__init__.py b/python/paddle/v2/__init__.py index 851fe7060fd..b9d0a7f2913 100644 --- a/python/paddle/v2/__init__.py +++ b/python/paddle/v2/__init__.py @@ -27,6 +27,7 @@ from . import dataset from . import reader from . import plot import attr +import op import pooling import inference import networks diff --git a/python/paddle/v2/config_base.py b/python/paddle/v2/config_base.py index be8ed2e1e51..d9613e001ac 100644 --- a/python/paddle/v2/config_base.py +++ b/python/paddle/v2/config_base.py @@ -36,9 +36,9 @@ def __map_docstr__(doc, name): # xxx_layer to xxx doc = re.sub(r"(?P[a-z]+)_layer", r"\g", doc) - # XxxxActivation to paddle.v2.Activation.Xxxx + # XxxxActivation to paddle.v2.activation.Xxxx doc = re.sub(r"(?P[A-Z][a-zA-Z]+)Activation", - r"paddle.v2.Activation.\g", doc) + r"paddle.v2.activation.\g", doc) # xxx_evaluator to paddle.v2.evaluator.xxx doc = re.sub(r"(?P[a-z]+)_evaluator", r"evaluator.\g", doc) diff --git a/python/paddle/v2/op.py b/python/paddle/v2/op.py new file mode 100644 index 00000000000..03f3b9b9ef2 --- /dev/null +++ b/python/paddle/v2/op.py @@ -0,0 +1,120 @@ +# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import layer +import activation as act +from config_base import Layer +from paddle.trainer_config_helpers.attrs import is_compatible_with +from paddle.trainer_config_helpers.default_decorators import wrap_name_default + +__all__ = [] + + +def __register_unary_math_op__(op_name, act): + def op(input, name=None): + return layer.mixed( + input=[layer.identity_projection(input=input)], name=name, act=act) + + op = wrap_name_default(op_name)(op) + op.__doc__ = type(act).__doc__ + globals()[op_name] = op + __all__.append(op_name) + + +__register_unary_math_op__('exp', act.Exp()) +__register_unary_math_op__('log', act.Log()) +__register_unary_math_op__('abs', act.Abs()) +__register_unary_math_op__('sigmoid', act.Sigmoid()) +__register_unary_math_op__('tanh', act.Tanh()) +__register_unary_math_op__('square', act.Square()) +__register_unary_math_op__('relu', act.Relu()) +__register_unary_math_op__('sqrt', act.Sqrt()) +__register_unary_math_op__('reciprocal', act.Reciprocal()) +__register_unary_math_op__('softmax', act.Softmax()) + + +def __add__(layeroutput, other): + if is_compatible_with(other, float): + return layer.slope_intercept(input=layeroutput, intercept=other) + if not isinstance(other, Layer): + raise TypeError("Layer can only be added with" + " another Layer or a number") + if layeroutput.size == other.size: + return layer.mixed(input=[ + layer.identity_projection(input=layeroutput), + layer.identity_projection(input=other) + ]) + if other.size != 1 and layeroutput.size != 1: + raise TypeError("Two Layer can be added only if they have equal size" + " or one of their sizes is 1. sizes are %s and %s" % + (layeroutput.size, other.size)) + elif layeroutput.size == 1: + tmp = layeroutput + layeroutput = other + other = tmp + other = layer.repeat(other, layeroutput.size) + return layer.mixed(input=[ + layer.identity_projection(input=layeroutput), + layer.identity_projection(input=other) + ]) + + +Layer.__radd__ = __add__ +Layer.__add__ = __add__ + + +def __neg__(layeroutput): + return layer.slope_intercept(input=layeroutput, slope=-1.0) + + +Layer.__neg__ = __neg__ + + +def __sub__(layeroutput, other): + if is_compatible_with(other, float): + return layer.slope_intercept(input=layeroutput, intercept=other) + if not isinstance(other, Layer): + raise TypeError("Layer can only be subtracted with" + " another Layeroutput or a number") + return __add__(layeroutput, -other) + + +Layer.__sub__ = __sub__ + + +def __rsub__(layeroutput, other): + neg = layer.slope_intercept(input=layeroutput, slope=-1.0) + return __add__(neg, other) + + +Layer.__rsub__ = __rsub__ + + +def __mul__(layeroutput, other): + if is_compatible_with(other, float): + return layer.slope_intercept(input=layeroutput, slope=other) + if not isinstance(other, Layer): + raise TypeError("Layer can only be multiplied with" + " another Layer or a number") + elif layeroutput.size == 1: + return layer.scaling(input=other, weight=layeroutput) + elif other.size == 1: + return layer.scaling(input=layeroutput, weight=other) + else: + raise TypeError("At least one of the operand of '*' must be a number" + " or a Layer with size=1") + + +Layer.__mul__ = __mul__ +Layer.__rmul__ = __mul__ diff --git a/python/paddle/v2/tests/CMakeLists.txt b/python/paddle/v2/tests/CMakeLists.txt index eb02e53706b..058f22befd0 100644 --- a/python/paddle/v2/tests/CMakeLists.txt +++ b/python/paddle/v2/tests/CMakeLists.txt @@ -1,2 +1,2 @@ -add_python_test(test_v2_api test_data_feeder.py test_parameters.py +add_python_test(test_v2_api test_data_feeder.py test_op.py test_parameters.py test_layer.py test_rnn_layer.py test_topology.py test_image.py) diff --git a/python/paddle/v2/tests/test_op.py b/python/paddle/v2/tests/test_op.py new file mode 100644 index 00000000000..69acccddf42 --- /dev/null +++ b/python/paddle/v2/tests/test_op.py @@ -0,0 +1,50 @@ +# Copyright PaddlePaddle contributors. All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import unittest + +import paddle.v2.data_type as data_type +import paddle.v2.layer as layer +import paddle.v2.op as op + + +class OpTest(unittest.TestCase): + def test_op(self): + x = layer.data(name='data', type=data_type.dense_vector(128)) + x = op.exp(x) + x = op.sqrt(x) + x = op.reciprocal(x) + x = op.log(x) + x = op.abs(x) + x = op.sigmoid(x) + x = op.tanh(x) + x = op.square(x) + x = op.relu(x) + y = 1 + x + y = y + 1 + y = x + y + y = y - x + y = y - 2 + y = 2 - y + y = 2 * y + y = y * 3 + z = layer.data(name='data_2', type=data_type.dense_vector(1)) + y = y * z + y = z * y + y = y + z + y = z + y + print layer.parse_network(y) + + +if __name__ == '__main__': + unittest.main() -- GitLab From 0babf84b0f3a32b9c99b570170bd5276899b5f26 Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Fri, 19 May 2017 15:00:22 -0400 Subject: [PATCH 0494/3256] implement pserver RPC part, and simple parameter partition. --- doc/design/cluster_train/pserver_client.md | 3 + go/pserver/cclient/cclient.go | 46 ++-- go/pserver/client.go | 200 +++++++++++++++++- go/pserver/optimizer.go | 4 +- go/pserver/service.go | 91 ++------ go/pserver/service_test.go | 74 +++---- paddle/go/pserver/client_test.go | 123 +++++++++++ paddle/go/pserver/internal/connection/conn.go | 84 ++++++++ paddle/go/pserver/partitioner.go | 10 + 9 files changed, 492 insertions(+), 143 deletions(-) create mode 100644 paddle/go/pserver/client_test.go create mode 100644 paddle/go/pserver/internal/connection/conn.go create mode 100644 paddle/go/pserver/partitioner.go diff --git a/doc/design/cluster_train/pserver_client.md b/doc/design/cluster_train/pserver_client.md index 007285640e9..b3e40790104 100644 --- a/doc/design/cluster_train/pserver_client.md +++ b/doc/design/cluster_train/pserver_client.md @@ -136,6 +136,9 @@ int paddle_send_grads(paddle_pserver_client* client, const paddle_gradient* grad /** * @brief paddle_get_params gets parameters from parameter servers. * + * paddle_get_params will block until parameters are initialized on + * the parameter servers. + * * @param names the array of names of the parameters to get. * @param dst the destination array of parameters to save to. * @param len the length of the names array and the paddle_parameter diff --git a/go/pserver/cclient/cclient.go b/go/pserver/cclient/cclient.go index 1b5560451a8..ac84b993d1a 100644 --- a/go/pserver/cclient/cclient.go +++ b/go/pserver/cclient/cclient.go @@ -39,6 +39,7 @@ import "C" import ( "log" + "strings" "sync" "unsafe" @@ -86,29 +87,46 @@ func cArrayToSlice(p unsafe.Pointer, len int) []byte { return (*[1 << 30]byte)(p)[:len:len] } +type selector bool + +func (s selector) Select() bool { + return bool(s) +} + +type lister []pserver.Server + +func (l lister) List() []pserver.Server { + return l +} + //export paddle_new_pserver_client -func paddle_new_pserver_client(addr *C.char) C.client { - c := pserver.NewClient(C.GoString(addr)) +func paddle_new_pserver_client(addrs *C.char, selected bool) C.client { + a := C.GoString(addrs) + as := strings.Split(a, ",") + servers := make([]pserver.Server, len(as)) + for i := range as { + servers[i].Index = i + servers[i].Addr = as[i] + } + c := pserver.NewClient(lister(servers), len(as), selector(selected)) return add(c) } +//export paddle_new_etcd_pserver_client +func paddle_new_etcd_pserver_client(etcd_addr *C.char) C.client { + // TODO(helin): fault tolerant pserver client using etcd. + panic("not implemented.") +} + //export paddle_pserver_client_release func paddle_pserver_client_release(client C.client) { - c := remove(client) - c.Cleanup() + remove(client) } //export paddle_begin_init_params -func paddle_begin_init_params(client C.client, pserver_config unsafe.Pointer, config_len C.int) C.int { +func paddle_begin_init_params(client C.client) C.int { c := get(client) - b := cArrayToSlice(pserver_config, int(config_len)) - selected, err := c.BeginInitParams(b) - if err != nil { - log.Println(err) - return -1 - } - - if selected { + if selected := c.BeginInitParams(); selected { return 1 } return 0 @@ -230,7 +248,7 @@ func paddle_get_params(client C.client, names **C.char, dst **C.paddle_parameter func paddle_save_model(client C.client, path *C.char) C.int { p := C.GoString(path) c := get(client) - err := c.SaveModel(p) + err := c.Save(p) if err != nil { log.Println(err) return -1 diff --git a/go/pserver/client.go b/go/pserver/client.go index 1c98aea6d1c..5e01d60a8e8 100644 --- a/go/pserver/client.go +++ b/go/pserver/client.go @@ -1,12 +1,82 @@ package pserver +import ( + "hash/fnv" + "log" + "sort" + "time" + + "github.com/PaddlePaddle/Paddle/paddle/go/pserver/internal/connection" +) + +// TODO(helin): add RPC call retry logic + +// Selector selects if the client should initialize parameter servers. +type Selector interface { + Select() bool +} + +// Server is the identification of a parameter Server. +type Server struct { + Index int + Addr string +} + +// Lister lists currently available parameter servers. +type Lister interface { + List() []Server +} + // Client is the client to parameter servers. type Client struct { + sel Selector + pservers []*connection.Conn } // NewClient creates a new client. -func NewClient(addr string) *Client { - return &Client{} +func NewClient(l Lister, pserverNum int, sel Selector) *Client { + c := &Client{sel: sel} + c.pservers = make([]*connection.Conn, pserverNum) + for i := 0; i < pserverNum; i++ { + c.pservers[i] = connection.New() + } + go c.monitorPservers(l, pserverNum) + return c +} + +// monitorPservers monitors pserver addresses, and updates connection +// when the address changes. +func (c *Client) monitorPservers(l Lister, pserverNum int) { + knownServers := make([]Server, pserverNum) + ticker := time.NewTicker(10 * time.Second) + monitor := func() { + curServers := make([]Server, pserverNum) + list := l.List() + for _, l := range list { + curServers[l.Index] = l + } + + for i := range knownServers { + if knownServers[i].Addr != curServers[i].Addr { + err := c.pservers[i].Connect(curServers[i].Addr) + if err != nil { + log.Println(err) + + // connect to addr failed, set + // to last known addr in order + // to retry next time. + curServers[i].Addr = knownServers[i].Addr + } + } + } + + knownServers = curServers + } + + monitor() + for _ = range ticker.C { + monitor() + } } // BeginInitParams begins to initialize parameters on parameter @@ -17,38 +87,146 @@ func NewClient(addr string) *Client { // servers. Other trainers will be blocked until the initialization is // done, and they need to get the initialized parameters from // parameter servers using GetParams. -func (c *Client) BeginInitParams(pserverConfigProto []byte) (selected bool, err error) { - return true, nil +func (c *Client) BeginInitParams() bool { + return c.sel.Select() } // InitParam initializes the parameter on parameter servers. func (c *Client) InitParam(paramWithConfigs ParameterWithConfig) error { - return nil + var dummy int + return c.pservers[c.partition(paramWithConfigs.Param.Name)].Call("Service.InitParam", paramWithConfigs, &dummy) } // FinishInitParams tells parameter servers client has sent all // parameters to parameter servers as initialization. func (c *Client) FinishInitParams() error { + for _, p := range c.pservers { + var dummy int + err := p.Call("Service.FinishInitParams", dummy, &dummy) + if err != nil { + return err + } + } return nil } // SendGrads sends gradients to parameter servers for updating // parameters. func (c *Client) SendGrads(grads []Gradient) error { + errCh := make(chan error, len(grads)) + for _, g := range grads { + go func(g Gradient) { + var dummy int + err := c.pservers[c.partition(g.Name)].Call("Service.SendGrad", g, &dummy) + errCh <- err + }(g) + } + + recv := 0 + for err := range errCh { + if err != nil { + return err + } + + recv++ + if recv == len(grads) { + break + } + } return nil } +type result struct { + idx int + p Parameter + err error +} + +type results []result + +func (r results) Len() int { + return len(r) +} + +func (r results) Less(i int, j int) bool { + return r[i].idx < r[j].idx +} + +func (r results) Swap(i int, j int) { + r[i], r[j] = r[j], r[i] +} + // GetParams gets parameters from parameter servers. func (c *Client) GetParams(names []string) ([]Parameter, error) { - return nil, nil + rCh := make(chan result, len(names)) + + for idx, name := range names { + go func(name string, idx int) { + var parameter Parameter + err := c.pservers[c.partition(name)].Call("Service.GetParam", name, ¶meter) + rCh <- result{idx: idx, p: parameter, err: err} + }(name, idx) + } + + var rs results + recv := 0 + for r := range rCh { + if r.err != nil { + return nil, r.err + } + rs = append(rs, r) + + recv++ + if recv == len(names) { + break + } + } + sort.Sort(rs) + + ps := make([]Parameter, len(rs)) + for i := range rs { + ps[i] = rs[i].p + } + + return ps, nil } -// SaveModel indicates parameters to save the parameter to the given -// path. -func (c *Client) SaveModel(path string) error { +// Save indicates parameters to save the parameter to the given path. +func (c *Client) Save(path string) error { + errCh := make(chan error, len(c.pservers)) + + for _, p := range c.pservers { + var dummy int + err := p.Call("Service.Save", path, &dummy) + errCh <- err + } + + recv := 0 + for err := range errCh { + if err != nil { + return err + } + + recv++ + if recv == len(c.pservers) { + break + } + } + + // TODO(helin): there will be many files under path, need to + // merge them into a single file. return nil } -// Cleanup cleans up the client states. -func (c *Client) Cleanup() { +func strHash(s string) uint32 { + h := fnv.New32a() + h.Write([]byte(s)) + return h.Sum32() +} + +// TODO(helin): now partition only select which parameter server to +// send the entire parameter. We need to partition a parameter into +// small blocks and send to different parameter servers. +func (c *Client) partition(key string) int { + return int(strHash(key) % uint32(len(c.pservers))) } diff --git a/go/pserver/optimizer.go b/go/pserver/optimizer.go index 64bdefe660a..417f8c50938 100644 --- a/go/pserver/optimizer.go +++ b/go/pserver/optimizer.go @@ -29,11 +29,11 @@ func newOptimizer(t optimizerType, learning_rate float64) *optimizer { func (o *optimizer) UpdateParameter(p Parameter, g Gradient) error { if len(p.Content) != len(g.Content) { - return fmt.Errorf("parameter and gradient length not match, parameter: %d, gradient: %d", len(p.Content), len(g.Content)) + return fmt.Errorf("Name: %s, parameter and gradient length not match, parameter: %d, gradient: %d", p.Name, len(p.Content), len(g.Content)) } if p.ElementType != g.ElementType { - return fmt.Errorf("parameter and gradient element type not match, parameter: %v, gradient: %v", p.ElementType, g.ElementType) + return fmt.Errorf("Name: %s, parameter and gradient element type not match, parameter: %v, gradient: %v", p.Name, p.ElementType, g.ElementType) } r := C.paddle_update_parameter(o.opt, unsafe.Pointer(&p.Content[0]), C.paddle_element_type(p.ElementType), unsafe.Pointer(&g.Content[0]), C.int(len(g.Content))) diff --git a/go/pserver/service.go b/go/pserver/service.go index f43e59403a7..d5787b9708b 100644 --- a/go/pserver/service.go +++ b/go/pserver/service.go @@ -49,33 +49,12 @@ type Service struct { // NewService creates a new service. func NewService() *Service { - s := &Service{} + s := &Service{opt: newOptimizer(sgd, 0.01)} s.paramMap = make(map[string]Parameter) s.initialized = make(chan struct{}) return s } -// BeginInitParams tells the parameter server that the parameter -// initialization has begun. -func (s *Service) BeginInitParams(config []byte, dummy *int) error { - select { - case <-s.initialized: - return ErrAlreadyInitialized - default: - } - - s.mu.Lock() - defer s.mu.Unlock() - - if s.opt != nil { - s.opt.Cleanup() - } - - // TODO(helin): parse learning rate from config - s.opt = newOptimizer(sgd, 0.01) - return nil -} - // InitParam initializes a parameter. func (s *Service) InitParam(paramWithConfigs ParameterWithConfig, dummy *int) error { select { @@ -109,75 +88,45 @@ func (s *Service) FinishInitParams(dummy0 int, dummy1 *int) error { return nil } -// SendGrads sends gradients to parameter servers for parameter +// SendGrad sends gradient to parameter servers for parameter // optimization. -func (s *Service) SendGrads(grads []Gradient, dummy *int) error { +func (s *Service) SendGrad(g Gradient, dummy *int) error { select { case <-s.initialized: default: return ErrUninitialized } - count := len(grads) - if count == 0 { - return nil - } - s.mu.Lock() defer s.mu.Unlock() - for _, g := range grads { - if _, ok := s.paramMap[g.Name]; !ok { - return fmt.Errorf("parameter: %s does not exist", g.Name) - } - } - - errCh := make(chan error, count) - for _, g := range grads { - go func(p Parameter, g Gradient) { - err := s.opt.UpdateParameter(p, g) - errCh <- err - }(s.paramMap[g.Name], g) + p, ok := s.paramMap[g.Name] + if !ok { + return fmt.Errorf("parameter: %s does not exist", g.Name) } - recv := 0 - for err := range errCh { - if err != nil { - return err - } - - recv++ - if recv == count { - break - } - } - return nil + return s.opt.UpdateParameter(p, g) } -// GetParams gets parameters from the parameter server. -func (s *Service) GetParams(names []string, parameters *[]Parameter) error { +// GetParam gets parameters from the parameter server. +func (s *Service) GetParam(name string, parameter *Parameter) error { <-s.initialized s.mu.Lock() defer s.mu.Unlock() - for _, n := range names { - if _, ok := s.paramMap[n]; !ok { - return fmt.Errorf("parameter: %s does not exist", n) - } - } - - *parameters = make([]Parameter, len(names)) - for i, n := range names { - // The parameter content (a byte slice) may change - // during RPC serialization due to write from other - // goroutine, we allow it since mini-batch based deep - // learning optimization methods are stochastic in - // nature. This race condition is allowed deliberately - // to save the program from making a copy of the - // paramter content. - (*parameters)[i] = s.paramMap[n] + p, ok := s.paramMap[name] + if !ok { + return fmt.Errorf("parameter: %s does not exist", name) } + // The parameter content (a byte slice) may change + // during RPC serialization due to write from other + // goroutine, we allow it since mini-batch based deep + // learning optimization methods are stochastic in + // nature. This race condition is allowed deliberately + // to save the program from making a copy of the + // paramter content. + *parameter = p return nil } diff --git a/go/pserver/service_test.go b/go/pserver/service_test.go index c58ccf9231d..4c9fac4536e 100644 --- a/go/pserver/service_test.go +++ b/go/pserver/service_test.go @@ -4,23 +4,19 @@ import ( "reflect" "sync" "testing" + "time" "github.com/PaddlePaddle/Paddle/go/pserver" ) func TestFull(t *testing.T) { s := pserver.NewService() - var dummy int - err := s.BeginInitParams(nil, &dummy) - if err != nil { - t.FailNow() - } - var p pserver.Parameter p.Name = "param_a" p.Content = []byte{1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0} p.ElementType = pserver.Int32 - err = s.InitParam(pserver.ParameterWithConfig{p, nil}, &dummy) + var dummy int + err := s.InitParam(pserver.ParameterWithConfig{p, nil}, &dummy) if err != nil { t.FailNow() } @@ -39,40 +35,39 @@ func TestFull(t *testing.T) { t.FailNow() } - var params []pserver.Parameter - err = s.GetParams([]string{"param_b", "param_a"}, ¶ms) + var param pserver.Parameter + err = s.GetParam("param_b", ¶m) if err != nil { t.FailNow() } - if len(params) != 2 || !reflect.DeepEqual(params[0], p1) || !reflect.DeepEqual(params[0], p1) { + if !reflect.DeepEqual(param, p1) { t.FailNow() } - grads := []pserver.Gradient{pserver.Gradient(p1), pserver.Gradient(p)} - err = s.SendGrads(grads, &dummy) + g1, g2 := pserver.Gradient(p1), pserver.Gradient(p) + err = s.SendGrad(g1, &dummy) if err != nil { t.FailNow() } + err = s.SendGrad(g2, &dummy) - var params1 []pserver.Parameter - err = s.GetParams([]string{"param_b", "param_a"}, ¶ms1) if err != nil { t.FailNow() } - if len(params) != 2 { + var param1 pserver.Parameter + err = s.GetParam("param_a", ¶m1) + if err != nil { t.FailNow() } // don't compare content, since it's already changed by // gradient update. - params1[0].Content = nil - params1[0].Content = nil + param1.Content = nil p.Content = nil - p1.Content = nil - if !reflect.DeepEqual(params1[0], p1) || !reflect.DeepEqual(params1[0], p1) { + if !reflect.DeepEqual(param1, p) { t.FailNow() } } @@ -80,19 +75,7 @@ func TestFull(t *testing.T) { func TestMultipleInit(t *testing.T) { s := pserver.NewService() var dummy int - err := s.BeginInitParams(nil, &dummy) - if err != nil { - t.FailNow() - } - - // this is fine, it's possible for client to call init - // multiple times. - err = s.BeginInitParams(nil, &dummy) - if err != nil { - t.FailNow() - } - - err = s.FinishInitParams(0, &dummy) + err := s.FinishInitParams(0, &dummy) if err != nil { t.FailNow() } @@ -101,17 +84,12 @@ func TestMultipleInit(t *testing.T) { if err != pserver.ErrAlreadyInitialized { t.FailNow() } - - err = s.BeginInitParams(nil, &dummy) - if err != pserver.ErrAlreadyInitialized { - t.FailNow() - } } func TestUninitialized(t *testing.T) { s := pserver.NewService() var dummy int - err := s.SendGrads(nil, &dummy) + err := s.SendGrad(pserver.Gradient{}, &dummy) if err != pserver.ErrUninitialized { t.FailNow() } @@ -123,8 +101,8 @@ func TestBlockUntilInitialized(t *testing.T) { var wg sync.WaitGroup wg.Add(1) go func() { - var params []pserver.Parameter - err := s.GetParams(nil, ¶ms) + var param pserver.Parameter + err := s.GetParam("param_a", ¶m) if err != nil { t.FailNow() } @@ -143,11 +121,7 @@ func TestBlockUntilInitialized(t *testing.T) { ch <- struct{}{} }() - var dummy int - err := s.BeginInitParams(nil, &dummy) - if err != nil { - t.FailNow() - } + time.Sleep(50 * time.Millisecond) select { case <-ch: @@ -156,6 +130,16 @@ func TestBlockUntilInitialized(t *testing.T) { default: } + var p pserver.Parameter + p.Name = "param_a" + p.Content = []byte{1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0} + p.ElementType = pserver.Int32 + var dummy int + err := s.InitParam(pserver.ParameterWithConfig{p, nil}, &dummy) + if err != nil { + t.FailNow() + } + err = s.FinishInitParams(0, &dummy) if err != nil { t.FailNow() diff --git a/paddle/go/pserver/client_test.go b/paddle/go/pserver/client_test.go new file mode 100644 index 00000000000..eab3f9a86bc --- /dev/null +++ b/paddle/go/pserver/client_test.go @@ -0,0 +1,123 @@ +package pserver_test + +import ( + "net" + "net/http" + "net/rpc" + "strconv" + "strings" + "testing" + + "github.com/PaddlePaddle/Paddle/paddle/go/pserver" +) + +const numPserver = 10 + +var port [numPserver]int + +func init() { + for i := 0; i < numPserver; i++ { + l, err := net.Listen("tcp", ":0") + if err != nil { + panic(err) + } + + ss := strings.Split(l.Addr().String(), ":") + p, err := strconv.Atoi(ss[len(ss)-1]) + if err != nil { + panic(err) + } + port[i] = p + + go func(l net.Listener) { + s := pserver.NewService() + server := rpc.NewServer() + err := server.Register(s) + if err != nil { + panic(err) + } + + mux := http.NewServeMux() + mux.Handle(rpc.DefaultRPCPath, server) + err = http.Serve(l, mux) + if err != nil { + panic(err) + } + }(l) + } +} + +type selector bool + +func (s selector) Select() bool { + return bool(s) +} + +type lister []pserver.Server + +func (l lister) List() []pserver.Server { + return l +} + +func TestClientFull(t *testing.T) { + servers := make([]pserver.Server, numPserver) + for i := 0; i < numPserver; i++ { + servers[i] = pserver.Server{Index: i, Addr: ":" + strconv.Itoa(port[i])} + } + c := pserver.NewClient(lister(servers), len(servers), selector(true)) + selected := c.BeginInitParams() + if !selected { + t.Fatal("should be selected.") + } + + const numParameter = 100 + for i := 0; i < numParameter; i++ { + var p pserver.Parameter + p.Name = "p_" + strconv.Itoa(i) + p.ElementType = pserver.Float32 + p.Content = make([]byte, (i+1)*100) + err := c.InitParam(pserver.ParameterWithConfig{Param: p}) + if err != nil { + t.Fatal(err) + } + } + + err := c.FinishInitParams() + if err != nil { + t.Fatal(err) + } + + var grads []pserver.Gradient + for i := 0; i < numParameter/2; i++ { + var g pserver.Gradient + g.Name = "p_" + strconv.Itoa(i) + g.ElementType = pserver.Float32 + g.Content = make([]byte, (i+1)*100) + grads = append(grads, g) + } + + err = c.SendGrads(grads) + if err != nil { + t.Fatal(err) + } + + names := make([]string, numParameter) + for i := 0; i < numParameter; i++ { + names[i] = "p_" + strconv.Itoa(i) + } + + params, err := c.GetParams(names) + if err != nil { + t.Fatal(err) + } + + if len(names) != len(params) { + t.Fatalf("parameter size not match, need: %d, have: %d", len(names), len(params)) + } + + for i := range params { + if names[i] != params[i].Name { + t.Fatalf("order of returned parameter does not required: parameter name: %s, required name: %s", names[i], params[i]) + } + } +} diff --git a/paddle/go/pserver/internal/connection/conn.go b/paddle/go/pserver/internal/connection/conn.go new file mode 100644 index 00000000000..1c04f117254 --- /dev/null +++ b/paddle/go/pserver/internal/connection/conn.go @@ -0,0 +1,84 @@ +package connection + +import ( + "errors" + "net/rpc" + "sync" +) + +// TODO(helin): add TCP re-connect logic + +// Conn is a connection to a parameter server +type Conn struct { + mu sync.Mutex + client *rpc.Client + waitConn chan struct{} +} + +// New creates a new connection. +func New() *Conn { + c := &Conn{} + return c +} + +// Connect connects the connection to a address. +func (c *Conn) Connect(addr string) error { + c.mu.Lock() + if c.client != nil { + err := c.client.Close() + if err != nil { + c.mu.Unlock() + return err + } + + c.client = nil + } + c.mu.Unlock() + + client, err := rpc.DialHTTP("tcp", addr) + if err != nil { + return err + } + + c.mu.Lock() + defer c.mu.Unlock() + + if c.client == nil { + c.client = client + if c.waitConn != nil { + close(c.waitConn) + c.waitConn = nil + } + } else { + return errors.New("client already set from a concurrent goroutine") + } + + return nil +} + +// Call make a RPC call. +// +// Call will be blocked until the connection to remote RPC service +// being established. +func (c *Conn) Call(serviceMethod string, args interface{}, reply interface{}) error { + c.mu.Lock() + client := c.client + var waitCh chan struct{} + if client == nil { + if c.waitConn != nil { + waitCh = c.waitConn + } else { + waitCh = make(chan struct{}) + c.waitConn = waitCh + } + } + c.mu.Unlock() + + if waitCh != nil { + // wait until new connection being established + <-waitCh + return c.Call(serviceMethod, args, reply) + } + + return client.Call(serviceMethod, args, reply) +} diff --git a/paddle/go/pserver/partitioner.go b/paddle/go/pserver/partitioner.go new file mode 100644 index 00000000000..a4c06e3d5af --- /dev/null +++ b/paddle/go/pserver/partitioner.go @@ -0,0 +1,10 @@ +package pserver + +type partitioner struct { + shardNum int +} + +// partitioner partitions the parameters into shards. +func newPartitioner(shardNum int) *partitioner { + return &partitioner{shardNum: shardNum} +} -- GitLab From 1e5795b8277c97ad768c51639837a5f9d731f63b Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Tue, 23 May 2017 18:59:15 -0400 Subject: [PATCH 0495/3256] remove unused file --- paddle/go/pserver/partitioner.go | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 paddle/go/pserver/partitioner.go diff --git a/paddle/go/pserver/partitioner.go b/paddle/go/pserver/partitioner.go deleted file mode 100644 index a4c06e3d5af..00000000000 --- a/paddle/go/pserver/partitioner.go +++ /dev/null @@ -1,10 +0,0 @@ -package pserver - -type partitioner struct { - shardNum int -} - -// partitioner partitions the parameters into shards. -func newPartitioner(shardNum int) *partitioner { - return &partitioner{shardNum: shardNum} -} -- GitLab From c225528739703cacef12d4d73144a97fc05fe07c Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Tue, 23 May 2017 19:12:30 -0400 Subject: [PATCH 0496/3256] fix cgo example --- go/pserver/cclient/cclient.go | 4 ++-- go/pserver/cclient/test/main.c | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/go/pserver/cclient/cclient.go b/go/pserver/cclient/cclient.go index ac84b993d1a..0b4aa79806b 100644 --- a/go/pserver/cclient/cclient.go +++ b/go/pserver/cclient/cclient.go @@ -100,7 +100,7 @@ func (l lister) List() []pserver.Server { } //export paddle_new_pserver_client -func paddle_new_pserver_client(addrs *C.char, selected bool) C.client { +func paddle_new_pserver_client(addrs *C.char, selected int) C.client { a := C.GoString(addrs) as := strings.Split(a, ",") servers := make([]pserver.Server, len(as)) @@ -108,7 +108,7 @@ func paddle_new_pserver_client(addrs *C.char, selected bool) C.client { servers[i].Index = i servers[i].Addr = as[i] } - c := pserver.NewClient(lister(servers), len(as), selector(selected)) + c := pserver.NewClient(lister(servers), len(as), selector(selected != 0)) return add(c) } diff --git a/go/pserver/cclient/test/main.c b/go/pserver/cclient/test/main.c index abfb32e5603..c14037235c1 100644 --- a/go/pserver/cclient/test/main.c +++ b/go/pserver/cclient/test/main.c @@ -11,9 +11,9 @@ void fail() { int main() { char addr[] = "localhost:3000"; - client c = paddle_new_pserver_client(addr); + client c = paddle_new_pserver_client(addr, 1); retry: - if (paddle_begin_init_params(c, NULL, 0)) { + if (paddle_begin_init_params(c)) { paddle_parameter param; char name_a[] = "param_a"; char name_b[] = "param_b"; -- GitLab From 319c91cc14d569737d0668a1431669e5749df50d Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Thu, 25 May 2017 15:53:57 -0400 Subject: [PATCH 0497/3256] make variable name more expressive --- go/pserver/client.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/go/pserver/client.go b/go/pserver/client.go index 5e01d60a8e8..ad0d2bcb078 100644 --- a/go/pserver/client.go +++ b/go/pserver/client.go @@ -137,9 +137,9 @@ func (c *Client) SendGrads(grads []Gradient) error { } type result struct { - idx int - p Parameter - err error + idx int + param Parameter + err error } type results []result @@ -164,7 +164,7 @@ func (c *Client) GetParams(names []string) ([]Parameter, error) { go func(name string, idx int) { var parameter Parameter err := c.pservers[c.partition(name)].Call("Service.GetParam", name, ¶meter) - rCh <- result{idx: idx, p: parameter, err: err} + rCh <- result{idx: idx, param: parameter, err: err} }(name, idx) } @@ -185,7 +185,7 @@ func (c *Client) GetParams(names []string) ([]Parameter, error) { ps := make([]Parameter, len(rs)) for i := range rs { - ps[i] = rs[i].p + ps[i] = rs[i].param } return ps, nil -- GitLab From 17e2b3a19a7d16cb6e6287951d19902d992dd62a Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Wed, 31 May 2017 00:55:32 +0000 Subject: [PATCH 0498/3256] fix merge conflict --- go/pserver/client.go | 2 +- {paddle/go => go}/pserver/client_test.go | 2 +- {paddle/go => go}/pserver/internal/connection/conn.go | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename {paddle/go => go}/pserver/client_test.go (97%) rename {paddle/go => go}/pserver/internal/connection/conn.go (100%) diff --git a/go/pserver/client.go b/go/pserver/client.go index ad0d2bcb078..f8bd0aa59f3 100644 --- a/go/pserver/client.go +++ b/go/pserver/client.go @@ -6,7 +6,7 @@ import ( "sort" "time" - "github.com/PaddlePaddle/Paddle/paddle/go/pserver/internal/connection" + "github.com/PaddlePaddle/Paddle/go/pserver/internal/connection" ) // TODO(helin): add RPC call retry logic diff --git a/paddle/go/pserver/client_test.go b/go/pserver/client_test.go similarity index 97% rename from paddle/go/pserver/client_test.go rename to go/pserver/client_test.go index eab3f9a86bc..a9a0948a51a 100644 --- a/paddle/go/pserver/client_test.go +++ b/go/pserver/client_test.go @@ -8,7 +8,7 @@ import ( "strings" "testing" - "github.com/PaddlePaddle/Paddle/paddle/go/pserver" + "github.com/PaddlePaddle/Paddle/go/pserver" ) const numPserver = 10 diff --git a/paddle/go/pserver/internal/connection/conn.go b/go/pserver/internal/connection/conn.go similarity index 100% rename from paddle/go/pserver/internal/connection/conn.go rename to go/pserver/internal/connection/conn.go -- GitLab From 8f5d22b045a7f2863ba1ce2d183d88f958687996 Mon Sep 17 00:00:00 2001 From: Liu Yiqun Date: Wed, 31 May 2017 02:20:40 +0000 Subject: [PATCH 0499/3256] Add annotations. --- paddle/cuda/include/hl_matrix_type.cuh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/paddle/cuda/include/hl_matrix_type.cuh b/paddle/cuda/include/hl_matrix_type.cuh index 77f73167fe6..12c717b612d 100644 --- a/paddle/cuda/include/hl_matrix_type.cuh +++ b/paddle/cuda/include/hl_matrix_type.cuh @@ -39,6 +39,8 @@ typedef double2 vecType; #elif defined(__SSE3__) #include "hl_cpu_simd_sse.cuh" #elif (defined(__ARM_NEON) || defined(__ARM_NEON__)) && !defined(__NVCC__) +// Currently nvcc does not support neon intrinsic. +// TODO: Extract simd intrinsic implementation from .cu files. #include "hl_cpu_simd_neon.cuh" #else #include "hl_cpu_scalar.cuh" -- GitLab From e6ef6c79bb6f56ad6c4f28b1b4fdd8610229e8ad Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Wed, 31 May 2017 10:37:46 +0800 Subject: [PATCH 0500/3256] Update .pre-commit-config.yaml * use PaddlePaddle org to manage mirrors-yapf * Fix #2289 --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9b138576fcc..4cd8eb12f6b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,8 +3,8 @@ hooks: - id: remove-crlf files: (?!.*third_party)^.*$ | (?!.*book)^.*$ -- repo: https://github.com/reyoung/mirrors-yapf.git - sha: v0.13.2 +- repo: https://github.com/PaddlePaddle/mirrors-yapf.git + sha: 0d79c0c469bab64f7229c9aca2b1186ef47f0e37 hooks: - id: yapf files: (.*\.(py|bzl)|BUILD|.*\.BUILD|WORKSPACE)$ -- GitLab From 008b0bb67359ad5ae058f67a10c306e9abdbfd1e Mon Sep 17 00:00:00 2001 From: wuyi05 Date: Wed, 31 May 2017 12:51:58 +0800 Subject: [PATCH 0501/3256] fix openblas build --- cmake/external/openblas.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/external/openblas.cmake b/cmake/external/openblas.cmake index b6bd24fe8ae..bf234320a0d 100644 --- a/cmake/external/openblas.cmake +++ b/cmake/external/openblas.cmake @@ -37,7 +37,7 @@ IF(NOT ${CBLAS_FOUND}) SET(OPTIONAL_ARGS HOSTCC=${HOST_C_COMPILER} TARGET=ARMV7 USE_THREAD=0 libs) ELSE() SET(OPENBLAS_COMMIT "v0.2.19") - SET(OPENBLAS_ARGS DYNAMIC_ARCH=1 libs) + SET(OPTIONAL_ARGS DYNAMIC_ARCH=1 libs NUM_THREADS=64) ENDIF() ExternalProject_Add( -- GitLab From 3ce974b94eb582f519110a63e03a89cb6c142de7 Mon Sep 17 00:00:00 2001 From: hedaoyuan Date: Wed, 31 May 2017 13:01:45 +0800 Subject: [PATCH 0502/3256] Add group argument in ConvFunctionBase --- paddle/function/ConvOp.h | 6 +++ paddle/function/ConvOpTest.cpp | 1 + paddle/function/GemmConvOp.cpp | 76 +++++++++++++++++++--------------- 3 files changed, 49 insertions(+), 34 deletions(-) diff --git a/paddle/function/ConvOp.h b/paddle/function/ConvOp.h index 465db57ae7d..173ca228096 100644 --- a/paddle/function/ConvOp.h +++ b/paddle/function/ConvOp.h @@ -38,6 +38,7 @@ public: // function arguments strides_ = config.get>("strides"); paddings_ = config.get>("paddings"); + groups_ = config.get("groups"); // number of inputs and outputs numInputs_ = 2; @@ -62,6 +63,11 @@ public: protected: std::vector strides_; std::vector paddings_; + /// Group size, refer to grouped convolution in + /// Alex Krizhevsky's paper: when group=2, the first half of the + /// filters are only connected to the first half of the input channels, + /// and the second half only connected to the second half. + size_t groups_; inline int strideH() const { return strides_[0]; } inline int strideW() const { return strides_[1]; } diff --git a/paddle/function/ConvOpTest.cpp b/paddle/function/ConvOpTest.cpp index db8d9fa9da4..eb008480481 100644 --- a/paddle/function/ConvOpTest.cpp +++ b/paddle/function/ConvOpTest.cpp @@ -55,6 +55,7 @@ public: FuncConfig() .set("paddings", paddings) .set("strides", strides) + .set("groups", (size_t)1) .set("algo", algo)); TensorShape shape0{ diff --git a/paddle/function/GemmConvOp.cpp b/paddle/function/GemmConvOp.cpp index 42786e44e0e..b8e44cc60bc 100644 --- a/paddle/function/GemmConvOp.cpp +++ b/paddle/function/GemmConvOp.cpp @@ -101,49 +101,57 @@ public: size_t outputHeight = outputs[0].shape()[2]; size_t outputWidth = outputs[0].shape()[3]; + CHECK_EQ(inputChannels / groups_, inputs[1].shape()[1]); + real* inputData = inputs[0].data(); real* filterData = inputs[1].data(); real* outputData = outputs[0].data(); - size_t size = - inputChannels * filterHeight * filterWidth * outputHeight * outputWidth; + size_t size = inputChannels / groups_ * filterHeight * filterWidth * + outputHeight * outputWidth; resizeBuffer(size); real* colData = reinterpret_cast(memory_->getBuf()); Im2ColFunctor im2col; + size_t inputOffset = (inputChannels / groups_) * inputHeight * inputWidth; + size_t outputOffset = + (outputChannels / groups_) * outputHeight * outputWidth; + size_t filterOffset = inputs[1].shape().getElements() / groups_; for (size_t i = 0; i < batchSize; i++) { - im2col(inputData, - inputChannels, - inputHeight, - inputWidth, - filterHeight, - filterWidth, - strideH(), - strideW(), - paddingH(), - paddingW(), - outputHeight, - outputWidth, - colData); - - int M = outputChannels; - int N = outputHeight * outputWidth; - int K = inputChannels * filterHeight * filterWidth; - gemm(CblasNoTrans, - CblasNoTrans, - M, - N, - K, - 1.0f, - filterData, - K, - colData, - N, - 0.0f, - outputData, - N); - inputData += inputChannels * inputHeight * inputWidth; - outputData += outputChannels * outputHeight * outputWidth; + for (int g = 0; g < groups_; g++) { + im2col(inputData + g * inputOffset, + inputChannels / groups_, + inputHeight, + inputWidth, + filterHeight, + filterWidth, + strideH(), + strideW(), + paddingH(), + paddingW(), + outputHeight, + outputWidth, + colData); + + int M = outputChannels; + int N = outputHeight * outputWidth; + int K = inputChannels * filterHeight * filterWidth; + gemm(CblasNoTrans, + CblasNoTrans, + M, + N, + K, + 1.0f, + filterData + g * filterOffset, + K, + colData, + N, + 0.0f, + outputData + g * outputOffset, + N); + inputData += inputChannels * inputHeight * inputWidth; + outputData += outputChannels * outputHeight * outputWidth; + } } } -- GitLab From 430adf43d1f98cb3d640815306ade5b2cce20ff8 Mon Sep 17 00:00:00 2001 From: Liu Yiqun Date: Wed, 31 May 2017 08:50:56 +0000 Subject: [PATCH 0503/3256] Move the definition of hl_vec_add/sub/mul/div/max/min to hl_tensor_ops.h --- paddle/cuda/include/hl_cpu_scalar.cuh | 24 --- paddle/cuda/include/hl_cpu_simd_neon.cuh | 25 --- paddle/cuda/include/hl_cpu_simd_sse.cuh | 48 ----- paddle/cuda/include/hl_matrix_base_detail.cuh | 20 +- paddle/cuda/include/hl_matrix_type.cuh | 2 + paddle/cuda/include/hl_tensor_ops.h | 202 ++++++++++++++++++ 6 files changed, 215 insertions(+), 106 deletions(-) diff --git a/paddle/cuda/include/hl_cpu_scalar.cuh b/paddle/cuda/include/hl_cpu_scalar.cuh index cddf08ce6b6..93043cd4bc0 100644 --- a/paddle/cuda/include/hl_cpu_scalar.cuh +++ b/paddle/cuda/include/hl_cpu_scalar.cuh @@ -40,30 +40,6 @@ INLINE real hl_vec_set(const real r) { return r; } -INLINE real hl_vec_max(const real a, const real b) { - return a > b ? a : b; -} - -INLINE real hl_vec_min(const real a, const real b) { - return a > b ? b : a; -} - -INLINE real hl_vec_add(const real a, const real b) { - return a + b; -} - -INLINE real hl_vec_sub(const real a, const real b) { - return a - b; -} - -INLINE real hl_vec_mul(const real a, const real b) { - return a * b; -} - -INLINE real hl_vec_div(const real a, const real b) { - return a / b; -} - INLINE real hl_vec_classification_error(const real a, const real b, const real p, diff --git a/paddle/cuda/include/hl_cpu_simd_neon.cuh b/paddle/cuda/include/hl_cpu_simd_neon.cuh index 9ff360c576f..0b1cf4abdc4 100644 --- a/paddle/cuda/include/hl_cpu_simd_neon.cuh +++ b/paddle/cuda/include/hl_cpu_simd_neon.cuh @@ -44,31 +44,6 @@ inline float32x4_t hl_vec_set(const real f) { return vdupq_n_f32(f); } -inline float32x4_t hl_vec_max(const float32x4_t a, const float32x4_t b) { - return vmaxq_f32(a, b); -} - -inline float32x4_t hl_vec_min(const float32x4_t a, const float32x4_t b) { - return vminq_f32(a, b); -} - -inline float32x4_t hl_vec_add(const float32x4_t a, const float32x4_t b) { - return vaddq_f32(a, b); -} - -inline float32x4_t hl_vec_sub(const float32x4_t a, const float32x4_t b) { - return vsubq_f32(a, b); -} - -inline float32x4_t hl_vec_mul(const float32x4_t a, const float32x4_t b) { - return vmulq_f32(a, b); -} - -inline float32x4_t hl_vec_div(const float32x4_t a, const float32x4_t b) { - float32x4_t tmp = vrecpeq_f32(b); - return vmulq_f32(a, tmp); -} - inline float32x4_t hl_vec_classification_error(const float32x4_t a, const float32x4_t b, const float32x4_t p, diff --git a/paddle/cuda/include/hl_cpu_simd_sse.cuh b/paddle/cuda/include/hl_cpu_simd_sse.cuh index 9a918770b14..a104b626220 100644 --- a/paddle/cuda/include/hl_cpu_simd_sse.cuh +++ b/paddle/cuda/include/hl_cpu_simd_sse.cuh @@ -45,30 +45,6 @@ inline __m128 hl_vec_set(const real f) { return _mm_set_ps1(f); } -inline __m128 hl_vec_max(const __m128 a, const __m128 b) { - return _mm_max_ps(a, b); -} - -inline __m128 hl_vec_min(const __m128 a, const __m128 b) { - return _mm_min_ps(a, b); -} - -inline __m128 hl_vec_add(const __m128 a, const __m128 b) { - return _mm_add_ps(a, b); -} - -inline __m128 hl_vec_sub(const __m128 a, const __m128 b) { - return _mm_sub_ps(a, b); -} - -inline __m128 hl_vec_mul(const __m128 a, const __m128 b) { - return _mm_mul_ps(a, b); -} - -inline __m128 hl_vec_div(const __m128 a, const __m128 b) { - return _mm_div_ps(a, b); -} - inline __m128 hl_vec_classification_error(const __m128 a, const __m128 b, const __m128 p, @@ -103,30 +79,6 @@ inline __m128d hl_vec_set(const real d) { #endif } -inline __m128d hl_vec_max(const __m128d a, const __m128d b) { - return _mm_max_pd(a, b); -} - -inline __m128d hl_vec_min(const __m128d a, const __m128d b) { - return _mm_min_pd(a, b); -} - -inline __m128d hl_vec_add(const __m128d a, const __m128d b) { - return _mm_add_pd(a, b); -} - -inline __m128d hl_vec_sub(const __m128d a, const __m128d b) { - return _mm_sub_pd(a, b); -} - -inline __m128d hl_vec_mul(const __m128d a, const __m128d b) { - return _mm_mul_pd(a, b); -} - -inline __m128d hl_vec_div(const __m128d a, const __m128d b) { - return _mm_div_pd(a, b); -} - inline __m128d hl_vec_classification_error(const __m128d a, const __m128d b, const __m128d p, diff --git a/paddle/cuda/include/hl_matrix_base_detail.cuh b/paddle/cuda/include/hl_matrix_base_detail.cuh index 50079ed53de..de1fd17d524 100644 --- a/paddle/cuda/include/hl_matrix_base_detail.cuh +++ b/paddle/cuda/include/hl_matrix_base_detail.cuh @@ -16,13 +16,14 @@ limitations under the License. */ #define HL_MATRIX_BASE_DETAIL_CUH_ #include "hl_matrix_type.cuh" +#include "hl_tensor_ops.h" namespace aggregate { class SSESum { public: static const bool sse = VECTOR_SIMD; INLINE vecType vecOp(const vecType a, const vecType b) const { - return hl_vec_add(a, b); + return hppl::binary::add()(a, b); } }; @@ -30,7 +31,7 @@ class SSEMax { public: static const bool sse = VECTOR_SIMD; INLINE vecType vecOp(const vecType a, const vecType b) const { - return hl_vec_max(a, b); + return hppl::binary::max()(a, b); } }; @@ -38,7 +39,7 @@ class SSEMin { public: static const bool sse = VECTOR_SIMD; INLINE vecType vecOp(const vecType a, const vecType b) const { - return hl_vec_min(a, b); + return hppl::binary::min()(a, b); } }; } // namespace aggregate @@ -59,7 +60,7 @@ class SSEAdd { public: static const bool sse = VECTOR_SIMD; INLINE vecType vecOp(const vecType a, const vecType b) const { - return hl_vec_add(a, b); + return hppl::binary::add()(a, b); } }; @@ -77,7 +78,7 @@ public: mp2 = hl_vec_set(p2); } INLINE vecType vecOp(const vecType a, const vecType b) const { - return hl_vec_add(hl_vec_mul(mp1, a), hl_vec_mul(mp2, b)); + return hppl::binary::add_scale(mp1, mp2)(a, b); } }; @@ -85,7 +86,7 @@ class SSESub { public: static const bool sse = VECTOR_SIMD; INLINE vecType vecOp(const vecType a, const vecType b) const { - return hl_vec_sub(a, b); + return hppl::binary::sub()(a, b); } }; @@ -93,7 +94,7 @@ class SSEMul { public: static const bool sse = VECTOR_SIMD; INLINE vecType vecOp(const vecType a, const vecType b) const { - return hl_vec_mul(a, b); + return hppl::binary::mul()(a, b); } }; @@ -101,7 +102,7 @@ class SSEDiv { public: static const bool sse = VECTOR_SIMD; INLINE vecType vecOp(const vecType a, const vecType b) const { - return hl_vec_div(a, b); + return hppl::binary::div()(a, b); } }; @@ -109,7 +110,8 @@ class SSESquaredDiff { public: static const bool sse = VECTOR_SIMD; INLINE vecType vecOp(const vecType a, const vecType b) const { - return hl_vec_mul(hl_vec_sub(a, b), hl_vec_sub(a, b)); + vecType tmp = hppl::binary::sub()(a, b); + return hppl::binary::mul()(tmp, tmp); } }; diff --git a/paddle/cuda/include/hl_matrix_type.cuh b/paddle/cuda/include/hl_matrix_type.cuh index 12c717b612d..e18235219bd 100644 --- a/paddle/cuda/include/hl_matrix_type.cuh +++ b/paddle/cuda/include/hl_matrix_type.cuh @@ -38,10 +38,12 @@ typedef double2 vecType; #endif #elif defined(__SSE3__) #include "hl_cpu_simd_sse.cuh" +#define PADDLE_USE_SSE3 #elif (defined(__ARM_NEON) || defined(__ARM_NEON__)) && !defined(__NVCC__) // Currently nvcc does not support neon intrinsic. // TODO: Extract simd intrinsic implementation from .cu files. #include "hl_cpu_simd_neon.cuh" +#define PADDLE_USE_NEON #else #include "hl_cpu_scalar.cuh" #endif diff --git a/paddle/cuda/include/hl_tensor_ops.h b/paddle/cuda/include/hl_tensor_ops.h index 7945b98201b..523503f5fec 100644 --- a/paddle/cuda/include/hl_tensor_ops.h +++ b/paddle/cuda/include/hl_tensor_ops.h @@ -328,6 +328,208 @@ public: INLINE T operator()(const T a, const T b) const { return a < b ? b : a; } }; +#ifdef PADDLE_USE_SSE3 +#ifndef PADDLE_TYPE_DOUBLE +template <> +class add<__m128> { +public: + INLINE __m128 operator()(const __m128 a, const __m128 b) const { + return _mm_add_ps(a, b); + } +}; + +template <> +class add_scale<__m128> { +private: + const __m128 p1; + const __m128 p2; + +public: + INLINE add_scale(const __m128 s1, const __m128 s2) : p1(s1), p2(s2) {} + INLINE __m128 operator()(const __m128 a, const __m128 b) const { + return _mm_add_ps(_mm_mul_ps(p1, a), _mm_mul_ps(p2, b)); + } +}; + +template <> +class sub<__m128> { +public: + INLINE __m128 operator()(const __m128 a, const __m128 b) const { + return _mm_sub_ps(a, b); + } +}; + +template <> +class mul<__m128> { +public: + INLINE __m128 operator()(const __m128 a, const __m128 b) const { + return _mm_mul_ps(a, b); + } +}; + +template <> +class div<__m128> { +public: + INLINE __m128 operator()(const __m128 a, const __m128 b) const { + return _mm_div_ps(a, b); + } +}; + +template <> +class min<__m128> { +public: + INLINE __m128 operator()(const __m128 a, const __m128 b) const { + return _mm_min_ps(a, b); + } +}; + +template <> +class max<__m128> { +public: + INLINE __m128 operator()(const __m128 a, const __m128 b) const { + return _mm_max_ps(a, b); + } +}; +#else +template <> +class add<__m128d> { +public: + INLINE __m128d operator()(const __m128d a, const __m128d b) const { + return _mm_add_pd(a, b); + } +}; + +template <> +class add_scale<__m128d> { +private: + const __m128d p1; + const __m128d p2; + +public: + INLINE add_scale(const __m128d s1, const __m128d s2) : p1(s1), p2(s2) {} + INLINE __m128d operator()(const __m128d a, const __m128d b) const { + return _mm_add_pd(_mm_mul_pd(p1, a), _mm_mul_pd(p2, b)); + } +}; + +template <> +class sub<__m128d> { +public: + INLINE __m128d operator()(const __m128d a, const __m128d b) const { + return _mm_sub_pd(a, b); + } +}; + +template <> +class mul<__m128d> { +public: + INLINE __m128d operator()(const __m128d a, const __m128d b) const { + return _mm_mul_pd(a, b); + } +}; + +template <> +class div<__m128d> { +public: + INLINE __m128d operator()(const __m128d a, const __m128d b) const { + return _mm_div_pd(a, b); + } +}; + +template <> +class min<__m128d> { +public: + INLINE __m128d operator()(const __m128d a, const __m128d b) const { + return _mm_min_pd(a, b); + } +}; + +template <> +class max<__m128d> { +public: + INLINE __m128d operator()(const __m128d a, const __m128d b) const { + return _mm_max_pd(a, b); + } +}; +#endif // PADDLE_TYPE_DOUBLE +#endif // PADDLE_USE_SSE3 + +#ifdef PADDLE_USE_NEON +#ifndef PADDLE_TYPE_DOUBLE +template <> +class add { +public: + INLINE float32x4_t operator()(const float32x4_t a, + const float32x4_t b) const { + return vmulq_f32(a, b); + } +}; + +template <> +class add_scale { +private: + const float32x4_t p1; + const float32x4_t p2; + +public: + INLINE add_scale(const float32x4_t s1, const float32x4_t s2) + : p1(s1), p2(s2) {} + INLINE float32x4_t operator()(const float32x4_t a, + const float32x4_t b) const { + return vaddq_f32(vmulq_f32(p1, a), vmulq_f32(p2, b)); + } +}; + +template <> +class sub { +public: + INLINE float32x4_t operator()(const float32x4_t a, + const float32x4_t b) const { + return vsubq_f32(a, b); + } +}; + +template <> +class mul { +public: + INLINE float32x4_t operator()(const float32x4_t a, + const float32x4_t b) const { + return vmulq_f32(a, b); + } +}; + +template <> +class div { +public: + INLINE float32x4_t operator()(const float32x4_t a, + const float32x4_t b) const { + float32x4_t tmp = vrecpeq_f32(b); + return vmulq_f32(a, tmp); + } +}; + +template <> +class min { +public: + INLINE float32x4_t operator()(const float32x4_t a, + const float32x4_t b) const { + return vminq_f32(a, b); + } +}; + +template <> +class max { +public: + INLINE float32x4_t operator()(const float32x4_t a, + const float32x4_t b) const { + return vmaxq_f32(a, b); + } +} +#else +#error To be implemented +#endif // PADDLE_TYPE_DOUBLE +#endif // PADDLE_USE_NEON + } // namespace binary } // namespace hppl -- GitLab From 7fb0684a910a1542021e76ace295785ac94394e2 Mon Sep 17 00:00:00 2001 From: Liu Yiqun Date: Wed, 31 May 2017 12:13:05 +0000 Subject: [PATCH 0504/3256] Add the missing semicolon. --- paddle/cuda/include/hl_tensor_ops.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paddle/cuda/include/hl_tensor_ops.h b/paddle/cuda/include/hl_tensor_ops.h index 523503f5fec..93d38b7d229 100644 --- a/paddle/cuda/include/hl_tensor_ops.h +++ b/paddle/cuda/include/hl_tensor_ops.h @@ -524,7 +524,7 @@ public: const float32x4_t b) const { return vmaxq_f32(a, b); } -} +}; #else #error To be implemented #endif // PADDLE_TYPE_DOUBLE -- GitLab From cb6436b50ce40985609cf18ae81ef308c32c8602 Mon Sep 17 00:00:00 2001 From: dangqingqing Date: Thu, 1 Jun 2017 00:56:07 +0800 Subject: [PATCH 0505/3256] CPU implementation of row convolution --- paddle/function/RowConvOp.cpp | 172 ++++++++++++++++++++++++ paddle/function/RowConvOp.h | 42 ++++++ paddle/gserver/layers/RowConvLayer.cpp | 105 +++++++++++++++ paddle/gserver/layers/RowConvLayer.h | 46 +++++++ paddle/gserver/tests/test_LayerGrad.cpp | 20 +++ proto/ModelConfig.proto | 5 + 6 files changed, 390 insertions(+) create mode 100644 paddle/function/RowConvOp.cpp create mode 100644 paddle/function/RowConvOp.h create mode 100644 paddle/gserver/layers/RowConvLayer.cpp create mode 100644 paddle/gserver/layers/RowConvLayer.h diff --git a/paddle/function/RowConvOp.cpp b/paddle/function/RowConvOp.cpp new file mode 100644 index 00000000000..f92b286c697 --- /dev/null +++ b/paddle/function/RowConvOp.cpp @@ -0,0 +1,172 @@ +/* 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 "RowConvOp.h" +#include "paddle/math/Vector.h" + +namespace paddle { + +template <> +void RowConv(CpuMatrix& out, + const CpuMatrix& in, + const CpuMatrix& filter, + const CpuIVector& seq) { + const int* starts = seq.getData(); + const size_t numSeq = seq.getSize() - 1; + const size_t contextLength = filter.getHeight(); + for (size_t i = 0; i < numSeq; ++i) { + size_t begin = starts[i]; + size_t end = starts[i + 1]; + for (size_t j = begin; j < end; ++j) { + MatrixPtr x; + MatrixPtr w; + if ((j + contextLength) < end) { + x = (const_cast(in)).subMatrix(j, contextLength); + w = (const_cast(filter)).subMatrix(0, contextLength); + } else { + x = (const_cast(in)).subMatrix(j, end - j); + w = (const_cast(filter)).subMatrix(0, end - j); + } + MatrixPtr y = out.subMatrix(j, 1); + y->addDotMulVMM(*x, *w); + } + } +} + +template <> +void RowConvGrad(const CpuMatrix& outG, + const CpuMatrix& in, + const CpuMatrix& filter, + CpuMatrix& inG, + CpuMatrix& filterG, + const CpuIVector& seq) { + // gradient w.r.t filter + const int* starts = seq.getData(); + const size_t numSeq = seq.getSize() - 1; + const size_t contextLength = filter.getHeight(); + if (filterG) { + for (size_t i = 0; i < numSeq; ++i) { + size_t begin = starts[i]; + size_t end = starts[i + 1]; + size_t steps = end - begin; + for (size_t j = 0; j < contextLength; ++j) { + MatrixPtr x = + (const_cast(in)).subMatrix(begin + j, steps - j); + MatrixPtr dy = + (const_cast(outG)).subMatrix(begin, steps - j); + MatrixPtr dw = filterG.subMatrix(j, 1); + dw->addDotMulVMM(*dy, *x); + } + } + } + + // gradient w.r.t input feature + if (inG) { + for (size_t i = 0; i < numSeq; ++i) { + size_t begin = starts[i]; + size_t end = starts[i + 1]; + size_t steps = end - begin; + for (size_t j = 0; j < steps; ++j) { + MatrixPtr dx = inG.subMatrix(begin + j, 1); + for (size_t t = 0; t < contextLength; ++t) { + if ((int(j) - int(t)) >= 0) { + MatrixPtr dy = + (const_cast(outG)).subMatrix(begin + j - t, 1); + MatrixPtr w = (const_cast(filter)).subMatrix(t, 1); + dx->addDotMul(*dy, *w, 1.0, 1.0); + } + } + } + } + } +} + +/** + * \brief TODO(qingqing) + * + */ + +template +class RowConvFunc : public FunctionBase { +public: + void init(const FuncConfig& config) override {} + + void calc(const BufferArgs& inputs, const BufferArgs& outputs) override { + // check + CHECK_EQ(2UL, inputs.size()); + CHECK_EQ(1UL, outputs.size()); + CHECK_EQ(outputs[0].getArgType(), ADD_TO); + CHECK(inputs[0].isSequenceArg() && outputs[0].isSequenceArg()) + << "SequenceArg required here."; + const auto in = dynamic_cast(inputs[0]); + auto out = dynamic_cast(outputs[0]); + auto w = inputs[1]; + CHECK(in.data() && out.data() && in.getSequenceId().data()); + CHECK_EQ(in.shape().ndims(), 2UL); + CHECK_EQ(out.shape().ndims(), 2UL); + CHECK_EQ(in.shape()[1], out.shape()[1]); + CHECK_EQ(in.shape()[0], out.shape()[0]); + CHECK_EQ(w.shape()[1], in.shape()[1]); + + auto outMat = out.matrix(); + const auto inMat = in.matrix(); + const auto wMat = w.matrix(); + const auto seqId = in.getSequenceId().vector(); + + RowConv(outMat, inMat, wMat, seqId); + } +}; + +/** + * \brief The backward propagation of padding Function. Remove the elements + * in the padding positions of forward. + * + * Argument in this Function: + */ + +template +class RowConvGradFunc : public FunctionBase { +public: + void init(const FuncConfig& config) override {} + + void calc(const BufferArgs& inputs, const BufferArgs& outputs) override { + const auto outGrad = dynamic_cast(inputs[0]); + const auto in = dynamic_cast(inputs[1]); + const auto w = inputs[2]; + auto inGrad = dynamic_cast(outputs[0]); + auto wGrad = outputs[1]; + + const auto outGMat = outGrad.matrix(); + const auto inMat = in.matrix(); + const auto wMat = w.matrix(); + auto inGMat = inGrad.data() + ? inGrad.matrix() + : typename Tensor::Matrix(nullptr, 0, 0); + auto wGMat = wGrad.data() + ? wGrad.matrix() + : typename Tensor::Matrix(nullptr, 0, 0); + const auto seqId = in.getSequenceId().vector(); + + RowConvGrad(outGMat, inMat, wMat, inGMat, wGMat, seqId); + } +}; + +REGISTER_TYPED_FUNC(RowConv, CPU, RowConvFunc); +REGISTER_TYPED_FUNC(RowConvGrad, CPU, RowConvGradFunc); +#ifndef PADDLE_ONLY_CPU +REGISTER_TYPED_FUNC(RowConv, GPU, RowConvFunc); +REGISTER_TYPED_FUNC(RowConvGrad, GPU, PadGradFunc); +#endif + +} // namespace paddle diff --git a/paddle/function/RowConvOp.h b/paddle/function/RowConvOp.h new file mode 100644 index 00000000000..cd78ec724ab --- /dev/null +++ b/paddle/function/RowConvOp.h @@ -0,0 +1,42 @@ +/* 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 "Function.h" + +namespace paddle { + +/** + * \brief TODO(qingqing) + * + */ +template +void RowConv(typename Tensor::Matrix& out, + const typename Tensor::Matrix& in, + const typename Tensor::Matrix& filter, + const typename Tensor::Vector& seq); + +/** + * \brief TODO(qingqing) + * + */ +template +void RowConvGrad(const typename Tensor::Matrix& outG, + const typename Tensor::Matrix& in, + const typename Tensor::Matrix& filter, + typename Tensor::Matrix& inG, + typename Tensor::Matrix& filterG, + const typename Tensor::Vector& seq); +} // namespace paddle diff --git a/paddle/gserver/layers/RowConvLayer.cpp b/paddle/gserver/layers/RowConvLayer.cpp new file mode 100644 index 00000000000..d4b14062977 --- /dev/null +++ b/paddle/gserver/layers/RowConvLayer.cpp @@ -0,0 +1,105 @@ +/* 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 "RowConvLayer.h" +#include "paddle/utils/Stat.h" + +namespace paddle { + +REGISTER_LAYER(row_conv, RowConvLayer); + +bool RowConvLayer::init(const LayerMap& layerMap, + const ParameterMap& parameterMap) { + /* Initialize the basic parent class */ + Layer::init(layerMap, parameterMap); + + contexLength_ = config_.inputs(0).row_conv_conf().context_length(); + + CHECK_EQ(inputLayers_.size(), 1UL); + weight_.reset(new Weight(contexLength_, getSize(), parameters_[0])); + createFunction(forward_, "RowConv", FuncConfig()); + createFunction(backward_, "RowConvGrad", FuncConfig()); + + return true; +} + +void RowConvLayer::forward(PassType passType) { + Layer::forward(passType); + MatrixPtr input = getInputValue(0); + size_t height = input->getHeight(); + size_t width = input->getWidth(); + CHECK_EQ(width, getSize()); + resetOutput(height, width); + + const auto startPos = getInput(0).sequenceStartPositions->getVector(useGpu_); + wDims_ = TensorShape({contexLength_, width}); + + MatrixPtr outV = getOutputValue(); + BufferArgs inputs; + BufferArgs outputs; + inputs.addArg(*getInputValue(0), *startPos); + inputs.addArg(*weight_->getW(), wDims_); + outputs.addArg(*getOutputValue(), *startPos, ADD_TO); + + { + REGISTER_TIMER_INFO("RowConvForward", getName().c_str()); + forward_[0]->calc(inputs, outputs); + } + + /* activation */ { + REGISTER_TIMER_INFO("FwAtvTimer", getName().c_str()); + forwardActivation(); + } +} + +void RowConvLayer::backward(const UpdateCallback& callback) { + /* Do derivation */ { + REGISTER_TIMER_INFO("BpAvtTimer", getName().c_str()); + backwardActivation(); + } + + const auto startPos = getInput(0).sequenceStartPositions->getVector(useGpu_); + + BufferArgs inputs; + BufferArgs outputs; + inputs.addArg(*getOutputGrad(), *startPos); + inputs.addArg(*getInputValue(0), *startPos); + inputs.addArg(*weight_->getW(), *startPos); + + MatrixPtr inGrad = getInputGrad(0); + MatrixPtr wGrad = weight_->getWGrad(); + size_t h = getInputValue(0)->getHeight(); + size_t w = getInputValue(0)->getWidth(); + outputs.addArg( + inGrad ? (*inGrad) : *(Matrix::create(nullptr, h, w, false, useGpu_)), + *startPos, + ADD_TO); + outputs.addArg( + wGrad ? (*wGrad) + : *(Matrix::create(nullptr, contexLength_, w, false, useGpu_)), + wDims_, + ADD_TO); + + { + REGISTER_TIMER_INFO("RowConvBackward", getName().c_str()); + backward_[0]->calc(inputs, outputs); + } + + { + REGISTER_TIMER_INFO("WeightUpdate", getName().c_str()); + weight_->getParameterPtr()->incUpdate(callback); + } +} + +} // namespace paddle diff --git a/paddle/gserver/layers/RowConvLayer.h b/paddle/gserver/layers/RowConvLayer.h new file mode 100644 index 00000000000..05be6ca6a9b --- /dev/null +++ b/paddle/gserver/layers/RowConvLayer.h @@ -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. */ + +#pragma once + +#include "Layer.h" + +namespace paddle { + +/** + * \brief Row Convolution Layer. + */ +class RowConvLayer : public Layer { +public: + explicit RowConvLayer(const LayerConfig& config) : Layer(config) {} + + ~RowConvLayer() {} + + bool init(const LayerMap& layerMap, + const ParameterMap& parameterMap) override; + void forward(PassType passType) override; + void backward(const UpdateCallback& callback = nullptr) override; + +protected: + // Row convolution weight, context_lenght_ * fan_out. + // fan_out is the size of output feature. + std::unique_ptr weight_; + + // std::unique_ptr biases_; + + // how many steps to look ahead + size_t contexLength_; + TensorShape wDims_; +}; +} // namespace paddle diff --git a/paddle/gserver/tests/test_LayerGrad.cpp b/paddle/gserver/tests/test_LayerGrad.cpp index e1e8e7fae7c..6adffcf53b7 100644 --- a/paddle/gserver/tests/test_LayerGrad.cpp +++ b/paddle/gserver/tests/test_LayerGrad.cpp @@ -1705,6 +1705,26 @@ TEST(Layer, TransLayer) { } } +TEST(Layer, RowConvLayer) { + const int context = 3; + const int size = 512; + + TestConfig config; + config.layerConfig.set_type("row_conv"); + config.layerConfig.set_size(size); + config.layerConfig.set_active_type("sigmoid"); + + config.inputDefs.push_back( + {INPUT_SEQUENCE_DATA, "layer_0", size, context * size}); + LayerInputConfig* input = config.layerConfig.add_inputs(); + RowConvConfig* conv = input->mutable_row_conv_conf(); + conv->set_context_length(context); + + for (auto useGpu : {false, true}) { + testLayerGrad(config, "row_conv", 100, false, useGpu, false); + } +} + int main(int argc, char** argv) { testing::InitGoogleTest(&argc, argv); initMain(argc, argv); diff --git a/proto/ModelConfig.proto b/proto/ModelConfig.proto index 4f9b53d6f65..29270829bbc 100644 --- a/proto/ModelConfig.proto +++ b/proto/ModelConfig.proto @@ -194,6 +194,10 @@ message MaxOutConfig { required uint32 groups = 2; } +message RowConvConfig { + required uint32 context_length = 1; +} + message ProjectionConfig { required string type = 1; required string name = 2; @@ -279,6 +283,7 @@ message LayerInputConfig { optional SppConfig spp_conf = 12; optional PriorBoxConfig priorbox_conf = 13; optional PadConfig pad_conf = 14; + optional RowConvConfig row_conv_conf = 15; } message LayerConfig { -- GitLab From 9baac14b8a9a0d557b3fe732d4cb89a9cb7951ce Mon Sep 17 00:00:00 2001 From: Yi Wang Date: Wed, 31 May 2017 14:54:27 -0700 Subject: [PATCH 0506/3256] Make generic.cmake no longer depend on util.cmake --- cmake/generic.cmake | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/cmake/generic.cmake b/cmake/generic.cmake index efc49b8fd3c..55bf89ea8e6 100644 --- a/cmake/generic.cmake +++ b/cmake/generic.cmake @@ -29,6 +29,11 @@ # https://cmake.org/cmake/help/v3.0/module/CMakeParseArguments.html # +# Because gflags depends on pthread, I copied the following snippet +# from https://stackoverflow.com/a/29871891/724872. +set(THREADS_PREFER_PTHREAD_FLAG ON) +find_package(Threads REQUIRED) + # cc_library parses tensor.cc and figures out that target also depend on tensor.h. # cc_library(tensor # SRCS @@ -45,7 +50,9 @@ function(cc_library TARGET_NAME) else() add_library(${TARGET_NAME} STATIC ${cc_library_SRCS}) endif() - add_dependencies(${TARGET_NAME} ${cc_library_DEPS} ${external_project_dependencies}) + if (cc_library_DEPS) + add_dependencies(${TARGET_NAME} ${cc_library_DEPS}) + endif() endfunction(cc_library) # cc_binary parses tensor.cc and figures out that target also depend on tensor.h. @@ -58,7 +65,6 @@ function(cc_binary TARGET_NAME) set(multiValueArgs SRCS DEPS) cmake_parse_arguments(cc_binary "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) add_executable(${TARGET_NAME} ${cc_binary_SRCS}) - link_paddle_exe(${TARGET_NAME}) if(cc_binary_DEPS) target_link_libraries(${TARGET_NAME} ${cc_binary_DEPS}) add_dependencies(${TARGET_NAME} ${cc_binary_DEPS}) @@ -78,11 +84,16 @@ function(cc_test TARGET_NAME) set(multiValueArgs SRCS DEPS) cmake_parse_arguments(cc_test "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) add_executable(${TARGET_NAME} ${cc_test_SRCS}) - link_paddle_test(${TARGET_NAME}) if(cc_test_DEPS) target_link_libraries(${TARGET_NAME} ${cc_test_DEPS}) add_dependencies(${TARGET_NAME} ${cc_test_DEPS}) endif() + target_link_libraries(${TARGET_NAME} + ${GTEST_LIBRARIES} ${GTEST_MAIN_LIBRARIES} + ${GLOG_LIBRARIES} + ${GFLAGS_LIBRARIES} + Threads::Threads) + add_dependencies(${TARGET_NAME} gtest glog gflags) add_test(${TARGET_NAME} ${TARGET_NAME}) endfunction(cc_test) @@ -104,7 +115,9 @@ function(nv_library TARGET_NAME) else() cuda_add_library(${TARGET_NAME} STATIC ${nv_library_SRCS}) endif() - add_dependencies(${TARGET_NAME} ${nv_library_DEPS} ${external_project_dependencies}) + if (nv_library_DEPS) + add_dependencies(${TARGET_NAME} ${nv_library_DEPS}) + endif() endfunction(nv_library) function(nv_binary TARGET_NAME) @@ -113,7 +126,6 @@ function(nv_binary TARGET_NAME) set(multiValueArgs SRCS DEPS) cmake_parse_arguments(nv_binary "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) cuda_add_executable(${TARGET_NAME} ${nv_binary_SRCS}) - link_paddle_exe(${TARGET_NAME}) if(nv_binary_DEPS) target_link_libraries(${TARGET_NAME} ${nv_binary_DEPS}) add_dependencies(${TARGET_NAME} ${nv_binary_DEPS}) @@ -133,11 +145,16 @@ function(nv_test TARGET_NAME) set(multiValueArgs SRCS DEPS) cmake_parse_arguments(nv_test "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) cuda_add_executable(${TARGET_NAME} ${nv_test_SRCS}) - link_paddle_test(${TARGET_NAME}) if(nv_test_DEPS) target_link_libraries(${TARGET_NAME} ${nv_test_DEPS}) add_dependencies(${TARGET_NAME} ${nv_test_DEPS}) endif() + target_link_libraries(${TARGET_NAME} + ${GTEST_LIBRARIES} ${GTEST_MAIN_LIBRARIES} + ${GLOG_LIBRARIES} + ${GFLAGS_LIBRARIES} + Threads::Threads) + add_dependencies(${TARGET_NAME} gtest glog gflags) add_test(${TARGET_NAME} ${TARGET_NAME}) endfunction(nv_test) -- GitLab From 31b7569847d5f84625fa7c56825a1b2aace36a8f Mon Sep 17 00:00:00 2001 From: Yi Wang Date: Wed, 31 May 2017 15:30:31 -0700 Subject: [PATCH 0507/3256] Create link_glog_gflags and link_gtest to replace link_paddle_exe and link_paddle_test --- cmake/generic.cmake | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/cmake/generic.cmake b/cmake/generic.cmake index 55bf89ea8e6..bd61e951b0c 100644 --- a/cmake/generic.cmake +++ b/cmake/generic.cmake @@ -29,11 +29,25 @@ # https://cmake.org/cmake/help/v3.0/module/CMakeParseArguments.html # +# generic.cmake depends on {GLOG,GFLAGS,GTEST,GTEST_MAIN}_LIBRARIES +# generated by cmake/external/*.cmake. + # Because gflags depends on pthread, I copied the following snippet # from https://stackoverflow.com/a/29871891/724872. set(THREADS_PREFER_PTHREAD_FLAG ON) find_package(Threads REQUIRED) +function(link_glog_gflags TARGET_NAME) + target_link_libraries(${TARGET_NAME} ${GLOG_LIBRARIES} ${GFLAGS_LIBRARIES} Threads::Threads) + add_dependencies(${TARGET_NAME} glog gflags) +endfunction() + +function(link_gtest TARGET_NAME) + target_link_libraries(${TARGET_NAME} ${GTEST_LIBRARIES} ${GTEST_MAIN_LIBRARIES}) + add_dependencies(${TARGET_NAME} gtest) +endfunction() + + # cc_library parses tensor.cc and figures out that target also depend on tensor.h. # cc_library(tensor # SRCS @@ -69,6 +83,7 @@ function(cc_binary TARGET_NAME) target_link_libraries(${TARGET_NAME} ${cc_binary_DEPS}) add_dependencies(${TARGET_NAME} ${cc_binary_DEPS}) endif() + link_glog_gflags(${TARGET_NAME}) endfunction(cc_binary) # The dependency to target tensor implies that if any of @@ -88,12 +103,8 @@ function(cc_test TARGET_NAME) target_link_libraries(${TARGET_NAME} ${cc_test_DEPS}) add_dependencies(${TARGET_NAME} ${cc_test_DEPS}) endif() - target_link_libraries(${TARGET_NAME} - ${GTEST_LIBRARIES} ${GTEST_MAIN_LIBRARIES} - ${GLOG_LIBRARIES} - ${GFLAGS_LIBRARIES} - Threads::Threads) - add_dependencies(${TARGET_NAME} gtest glog gflags) + link_glog_gflags(${TARGET_NAME}) + link_gtest(${TARGET_NAME}) add_test(${TARGET_NAME} ${TARGET_NAME}) endfunction(cc_test) @@ -130,6 +141,7 @@ function(nv_binary TARGET_NAME) target_link_libraries(${TARGET_NAME} ${nv_binary_DEPS}) add_dependencies(${TARGET_NAME} ${nv_binary_DEPS}) endif() + link_glog_gflags(${TARGET_NAME}) endfunction(nv_binary) # The dependency to target tensor implies that if any of @@ -149,12 +161,8 @@ function(nv_test TARGET_NAME) target_link_libraries(${TARGET_NAME} ${nv_test_DEPS}) add_dependencies(${TARGET_NAME} ${nv_test_DEPS}) endif() - target_link_libraries(${TARGET_NAME} - ${GTEST_LIBRARIES} ${GTEST_MAIN_LIBRARIES} - ${GLOG_LIBRARIES} - ${GFLAGS_LIBRARIES} - Threads::Threads) - add_dependencies(${TARGET_NAME} gtest glog gflags) + link_glog_gflags(${TARGET_NAME}) + link_gtest(${TARGET_NAME}) add_test(${TARGET_NAME} ${TARGET_NAME}) endfunction(nv_test) -- GitLab From d90e40cdd1db4407314a885d51a29ce31d458091 Mon Sep 17 00:00:00 2001 From: xuwei06 Date: Wed, 31 May 2017 16:00:37 -0700 Subject: [PATCH 0508/3256] MOdify make_model_diagram.py so that it can be used to draw dot diagram given model proto --- python/paddle/utils/make_model_diagram.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/python/paddle/utils/make_model_diagram.py b/python/paddle/utils/make_model_diagram.py index 1370ea83a49..40f99075de7 100644 --- a/python/paddle/utils/make_model_diagram.py +++ b/python/paddle/utils/make_model_diagram.py @@ -39,6 +39,10 @@ def make_layer_label(layer_config): def make_diagram(config_file, dot_file, config_arg_str): config = parse_config(config_file, config_arg_str) + make_diagram_from_proto(config.model_config, dot_file) + + +def make_diagram_from_proto(model_config, dot_file): # print >> sys.stderr, config name2id = {} f = open(dot_file, 'w') @@ -59,12 +63,12 @@ def make_diagram(config_file, dot_file, config_arg_str): print >> f, 'digraph graphname {' print >> f, 'node [width=0.375,height=0.25];' - for i in xrange(len(config.model_config.layers)): - l = config.model_config.layers[i] + for i in xrange(len(model_config.layers)): + l = model_config.layers[i] name2id[l.name] = i i = 0 - for sub_model in config.model_config.sub_models: + for sub_model in model_config.sub_models: if sub_model.name == 'root': continue print >> f, 'subgraph cluster_%s {' % i @@ -78,18 +82,18 @@ def make_diagram(config_file, dot_file, config_arg_str): for layer_name in sub_model.layer_names: submodel_layers.add(layer_name) lid = name2id[layer_name] - layer_config = config.model_config.layers[lid] + layer_config = model_config.layers[lid] label = make_layer_label(layer_config) print >> f, 'l%s [label="%s", shape=box];' % (lid, label) print >> f, '}' - for i in xrange(len(config.model_config.layers)): - l = config.model_config.layers[i] + for i in xrange(len(model_config.layers)): + l = model_config.layers[i] if l.name not in submodel_layers: label = make_layer_label(l) print >> f, 'l%s [label="%s", shape=box];' % (i, label) - for sub_model in config.model_config.sub_models: + for sub_model in model_config.sub_models: if sub_model.name == 'root': continue for link in sub_model.in_links: @@ -99,8 +103,8 @@ def make_diagram(config_file, dot_file, config_arg_str): for mem in sub_model.memories: print >> f, make_mem(mem) - for i in xrange(len(config.model_config.layers)): - for l in config.model_config.layers[i].inputs: + for i in xrange(len(model_config.layers)): + for l in model_config.layers[i].inputs: print >> f, 'l%s -> l%s [label="%s"];' % ( name2id[l.input_layer_name], i, l.input_parameter_name) -- GitLab From 05f260d849dc09a4d91795c364b175a1e693fb9a Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Wed, 31 May 2017 16:25:05 -0700 Subject: [PATCH 0509/3256] fix cclient build on mac, and test warnings --- go/pserver/cclient/test/CMakeLists.txt | 5 ++++- go/pserver/cclient/test/main.c | 4 ++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/go/pserver/cclient/test/CMakeLists.txt b/go/pserver/cclient/test/CMakeLists.txt index de7ef6a47ac..16f84648c1d 100644 --- a/go/pserver/cclient/test/CMakeLists.txt +++ b/go/pserver/cclient/test/CMakeLists.txt @@ -4,5 +4,8 @@ include_directories(${CMAKE_BINARY_DIR}) add_executable(main main.c) add_dependencies(main client) -set (CMAKE_EXE_LINKER_FLAGS "-pthread") + +if(APPLE) + set(CMAKE_EXE_LINKER_FLAGS "-framework CoreFoundation -framework Security") +endif() target_link_libraries(main ${CMAKE_BINARY_DIR}/libclient.a) diff --git a/go/pserver/cclient/test/main.c b/go/pserver/cclient/test/main.c index c14037235c1..f75a2110b94 100644 --- a/go/pserver/cclient/test/main.c +++ b/go/pserver/cclient/test/main.c @@ -17,7 +17,7 @@ retry: paddle_parameter param; char name_a[] = "param_a"; char name_b[] = "param_b"; - char content[] = {0x00, 0x11, 0x22}; + unsigned char content[] = {0x00, 0x11, 0x22}; param.element_type = PADDLE_ELEMENT_TYPE_FLOAT32; param.name = name_a; param.content = content; @@ -39,7 +39,7 @@ retry: fail(); } - char content[] = {0x00, 0x11, 0x22}; + unsigned char content[] = {0x00, 0x11, 0x22}; paddle_gradient grads[2] = { {"param_a", PADDLE_ELEMENT_TYPE_INT32, content, 3}, {"param_b", PADDLE_ELEMENT_TYPE_FLOAT32, content, 3}}; -- GitLab From 8a2cf0fbffbe1becd626d6ec3f31740540807b20 Mon Sep 17 00:00:00 2001 From: Yi Wang Date: Wed, 31 May 2017 21:18:19 -0700 Subject: [PATCH 0510/3256] Move majel/test/* to majel/; Update generic.cmake --- cmake/generic.cmake | 114 ++++++++++++++------------ paddle/majel/CMakeLists.txt | 8 +- paddle/majel/{test => }/cuda_test.cu | 0 paddle/majel/{test => }/ddim_test.cc | 0 paddle/majel/{test => }/dim_test.cu | 0 paddle/majel/{test => }/place_test.cc | 0 6 files changed, 66 insertions(+), 56 deletions(-) rename paddle/majel/{test => }/cuda_test.cu (100%) rename paddle/majel/{test => }/ddim_test.cc (100%) rename paddle/majel/{test => }/dim_test.cu (100%) rename paddle/majel/{test => }/place_test.cc (100%) diff --git a/cmake/generic.cmake b/cmake/generic.cmake index bd61e951b0c..b6126bcecf5 100644 --- a/cmake/generic.cmake +++ b/cmake/generic.cmake @@ -1,11 +1,11 @@ # 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. @@ -16,7 +16,7 @@ # To simplify the build process of PaddlePaddle, we defined couple of # fundamental abstractions, e.g., how to build library, binary and # test in C++, CUDA and Go. -# +# # ------------------------------------------- # C++ CUDA C++ Go # ------------------------------------------- @@ -47,7 +47,7 @@ function(link_gtest TARGET_NAME) add_dependencies(${TARGET_NAME} gtest) endfunction() - + # cc_library parses tensor.cc and figures out that target also depend on tensor.h. # cc_library(tensor # SRCS @@ -79,7 +79,7 @@ function(cc_binary TARGET_NAME) set(multiValueArgs SRCS DEPS) cmake_parse_arguments(cc_binary "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) add_executable(${TARGET_NAME} ${cc_binary_SRCS}) - if(cc_binary_DEPS) + if(cc_binary_DEPS) target_link_libraries(${TARGET_NAME} ${cc_binary_DEPS}) add_dependencies(${TARGET_NAME} ${cc_binary_DEPS}) endif() @@ -94,18 +94,20 @@ endfunction(cc_binary) # DEPS # tensor) function(cc_test TARGET_NAME) - set(options "") - set(oneValueArgs "") - set(multiValueArgs SRCS DEPS) - cmake_parse_arguments(cc_test "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) - add_executable(${TARGET_NAME} ${cc_test_SRCS}) - if(cc_test_DEPS) - target_link_libraries(${TARGET_NAME} ${cc_test_DEPS}) - add_dependencies(${TARGET_NAME} ${cc_test_DEPS}) + if(WITH_TESTING) + set(options "") + set(oneValueArgs "") + set(multiValueArgs SRCS DEPS) + cmake_parse_arguments(cc_test "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + add_executable(${TARGET_NAME} ${cc_test_SRCS}) + if(cc_test_DEPS) + target_link_libraries(${TARGET_NAME} ${cc_test_DEPS}) + add_dependencies(${TARGET_NAME} ${cc_test_DEPS}) + endif() + link_glog_gflags(${TARGET_NAME}) + link_gtest(${TARGET_NAME}) + add_test(${TARGET_NAME} ${TARGET_NAME}) endif() - link_glog_gflags(${TARGET_NAME}) - link_gtest(${TARGET_NAME}) - add_test(${TARGET_NAME} ${TARGET_NAME}) endfunction(cc_test) # Suppose that ops.cu includes global functions that take Tensor as @@ -117,31 +119,35 @@ endfunction(cc_test) # DEPS # tensor) function(nv_library TARGET_NAME) - set(options OPTIONAL) - set(oneValueArgs "") - set(multiValueArgs SRCS DEPS) - cmake_parse_arguments(nv_library "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) - if (${nv_library_OPTIONAL} STREQUAL "SHARED") - cuda_add_library(${TARGET_NAME} SHARED ${nv_library_SRCS}) - else() - cuda_add_library(${TARGET_NAME} STATIC ${nv_library_SRCS}) - endif() - if (nv_library_DEPS) - add_dependencies(${TARGET_NAME} ${nv_library_DEPS}) + if (WITH_GPU) + set(options OPTIONAL) + set(oneValueArgs "") + set(multiValueArgs SRCS DEPS) + cmake_parse_arguments(nv_library "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + if (${nv_library_OPTIONAL} STREQUAL "SHARED") + cuda_add_library(${TARGET_NAME} SHARED ${nv_library_SRCS}) + else() + cuda_add_library(${TARGET_NAME} STATIC ${nv_library_SRCS}) + endif() + if (nv_library_DEPS) + add_dependencies(${TARGET_NAME} ${nv_library_DEPS}) + endif() endif() endfunction(nv_library) function(nv_binary TARGET_NAME) - set(options "") - set(oneValueArgs "") - set(multiValueArgs SRCS DEPS) - cmake_parse_arguments(nv_binary "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) - cuda_add_executable(${TARGET_NAME} ${nv_binary_SRCS}) - if(nv_binary_DEPS) - target_link_libraries(${TARGET_NAME} ${nv_binary_DEPS}) - add_dependencies(${TARGET_NAME} ${nv_binary_DEPS}) + if (WITH_GPU) + set(options "") + set(oneValueArgs "") + set(multiValueArgs SRCS DEPS) + cmake_parse_arguments(nv_binary "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + cuda_add_executable(${TARGET_NAME} ${nv_binary_SRCS}) + if(nv_binary_DEPS) + target_link_libraries(${TARGET_NAME} ${nv_binary_DEPS}) + add_dependencies(${TARGET_NAME} ${nv_binary_DEPS}) + endif() + link_glog_gflags(${TARGET_NAME}) endif() - link_glog_gflags(${TARGET_NAME}) endfunction(nv_binary) # The dependency to target tensor implies that if any of @@ -152,18 +158,20 @@ endfunction(nv_binary) # DEPS # ops) function(nv_test TARGET_NAME) - set(options "") - set(oneValueArgs "") - set(multiValueArgs SRCS DEPS) - cmake_parse_arguments(nv_test "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) - cuda_add_executable(${TARGET_NAME} ${nv_test_SRCS}) - if(nv_test_DEPS) - target_link_libraries(${TARGET_NAME} ${nv_test_DEPS}) - add_dependencies(${TARGET_NAME} ${nv_test_DEPS}) + if (WITH_GPU AND WITH_TESTING) + set(options "") + set(oneValueArgs "") + set(multiValueArgs SRCS DEPS) + cmake_parse_arguments(nv_test "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + cuda_add_executable(${TARGET_NAME} ${nv_test_SRCS}) + if(nv_test_DEPS) + target_link_libraries(${TARGET_NAME} ${nv_test_DEPS}) + add_dependencies(${TARGET_NAME} ${nv_test_DEPS}) + endif() + link_glog_gflags(${TARGET_NAME}) + link_gtest(${TARGET_NAME}) + add_test(${TARGET_NAME} ${TARGET_NAME}) endif() - link_glog_gflags(${TARGET_NAME}) - link_gtest(${TARGET_NAME}) - add_test(${TARGET_NAME} ${TARGET_NAME}) endfunction(nv_test) set(GOPATH "${CMAKE_CURRENT_BINARY_DIR}/go") @@ -189,7 +197,7 @@ function(go_library TARGET_NAME) set(LIB_NAME "lib${TARGET_NAME}.dylib") else() set(LIB_NAME "lib${TARGET_NAME}.so") - endif() + endif() else() set(BUILD_MODE "-buildmode=c-archive") set(LIB_NAME "lib${TARGET_NAME}.a") @@ -215,8 +223,8 @@ function(go_binary TARGET_NAME) COMMAND env GOPATH=${GOPATH} ${CMAKE_Go_COMPILER} build -o "${CMAKE_CURRENT_BINARY_DIR}/${TARGET_NAME}" ${go_library_SRCS} - WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}) - add_custom_target(${TARGET_NAME} ALL DEPENDS ${TARGET_NAME}_timestamp ${go_binary_DEPS}) + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}) + add_custom_target(${TARGET_NAME} ALL DEPENDS ${TARGET_NAME}_timestamp ${go_binary_DEPS}) install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/${TARGET_NAME} DESTINATION bin) endfunction(go_binary) @@ -229,8 +237,8 @@ function(go_test TARGET_NAME) COMMAND env GOPATH=${GOPATH} ${CMAKE_Go_COMPILER} test -c -o "${CMAKE_CURRENT_BINARY_DIR}/${TARGET_NAME}" ${go_test_SRCS} - WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}) - add_custom_target(${TARGET_NAME} ALL DEPENDS ${TARGET_NAME}_timestamp ${go_test_DEPS}) + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}) + add_custom_target(${TARGET_NAME} ALL DEPENDS ${TARGET_NAME}_timestamp ${go_test_DEPS}) add_test(${TARGET_NAME} ${CMAKE_CURRENT_BINARY_DIR}/${TARGET_NAME}) endfunction(go_test) diff --git a/paddle/majel/CMakeLists.txt b/paddle/majel/CMakeLists.txt index cb8bece00e1..f492ce49110 100644 --- a/paddle/majel/CMakeLists.txt +++ b/paddle/majel/CMakeLists.txt @@ -1,6 +1,8 @@ cc_library(place SRCS place.cc) +cc_test(place_test SRCS place_test.cc DEPS place) + cc_library(ddim SRCS ddim.cc) +cc_test(ddim_test SRCS ddim_test.cc DEPS ddim) -if(WITH_TESTING) - add_subdirectory(test) -endif() +nv_test(cuda_test SRCS cuda_test.cu) +nv_test(dim_test SRCS dim_test.cu DEPS ddim) diff --git a/paddle/majel/test/cuda_test.cu b/paddle/majel/cuda_test.cu similarity index 100% rename from paddle/majel/test/cuda_test.cu rename to paddle/majel/cuda_test.cu diff --git a/paddle/majel/test/ddim_test.cc b/paddle/majel/ddim_test.cc similarity index 100% rename from paddle/majel/test/ddim_test.cc rename to paddle/majel/ddim_test.cc diff --git a/paddle/majel/test/dim_test.cu b/paddle/majel/dim_test.cu similarity index 100% rename from paddle/majel/test/dim_test.cu rename to paddle/majel/dim_test.cu diff --git a/paddle/majel/test/place_test.cc b/paddle/majel/place_test.cc similarity index 100% rename from paddle/majel/test/place_test.cc rename to paddle/majel/place_test.cc -- GitLab From 02a509f1cbf9a931463c142b989859e137a3d643 Mon Sep 17 00:00:00 2001 From: xuwei06 Date: Wed, 31 May 2017 16:02:33 -0700 Subject: [PATCH 0511/3256] Fix handling of boot_bias_layer for recurrent_group in v2 API --- python/paddle/v2/layer.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/python/paddle/v2/layer.py b/python/paddle/v2/layer.py index 8717619c475..da2abdd2d1e 100644 --- a/python/paddle/v2/layer.py +++ b/python/paddle/v2/layer.py @@ -152,7 +152,7 @@ def __get_used_layers__(output_layers, extra_layers=None): return layer_names -def __get_used_parameters__(layer_names): +def __get_used_parameters__(layer_names, sub_models): parameter_names = set() for name in layer_names: l = cp.g_layer_map[name] @@ -161,6 +161,12 @@ def __get_used_parameters__(layer_names): parameter_names.add(inp.input_parameter_name) if l.bias_parameter_name: parameter_names.add(l.bias_parameter_name) + + for sub_model in sub_models: + for mem in sub_model.memories: + if mem.HasField("boot_bias_parameter_name"): + parameter_names.add(mem.boot_bias_parameter_name) + return parameter_names @@ -236,7 +242,6 @@ def parse_network(output_layers, extra_layers=None): layer_names = __get_used_layers__(output_layers + extra_layers) submodel_names = __get_used_submodels__(layer_names) submodel_names.add('root') - parameter_names = __get_used_parameters__(layer_names) evaluator_names = __get_used_evaluators__(layer_names) input_layer_names = set() output_layer_names = set() @@ -251,10 +256,6 @@ def parse_network(output_layers, extra_layers=None): model_config.input_layer_names.append(l.name) input_layer_names.add(l.name) - for p in cp.g_config.model_config.parameters: - if p.name in parameter_names: - model_config.parameters.extend([p]) - for layer in output_layers: model_config.output_layer_names.append(layer.full_name) output_layer_names.add(layer.full_name) @@ -269,6 +270,13 @@ def parse_network(output_layers, extra_layers=None): output_layer_names, evaluator_names) model_config.sub_models.extend([s]) + parameter_names = __get_used_parameters__(layer_names, + model_config.sub_models) + + for p in cp.g_config.model_config.parameters: + if p.name in parameter_names: + model_config.parameters.extend([p]) + return model_config -- GitLab From a8d44cc56a92b8fbc9fa90e04f5a8d98e37f1830 Mon Sep 17 00:00:00 2001 From: xuwei06 Date: Wed, 31 May 2017 15:59:20 -0700 Subject: [PATCH 0512/3256] Fix size for lstm, identity_projection and concat --- .../paddle/trainer_config_helpers/layers.py | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/python/paddle/trainer_config_helpers/layers.py b/python/paddle/trainer_config_helpers/layers.py index 1564be10ac7..27fba42c70a 100755 --- a/python/paddle/trainer_config_helpers/layers.py +++ b/python/paddle/trainer_config_helpers/layers.py @@ -482,7 +482,7 @@ def table_projection(input, size=0, param_attr=None): return proj -def identity_projection(input, offset=None): +def identity_projection(input, offset=None, size=None): """ 1. IdentityProjection if offset=None. It performs: @@ -523,8 +523,10 @@ def identity_projection(input, offset=None): proj = IdentityProjection(input_layer_name=input.name) proj.origin = input else: + if size is None: + size = input.size - offset proj = IdentityOffsetProjection( - input_layer_name=input.name, offset=offset) + input_layer_name=input.name, offset=offset, size=size) proj.origin = input return proj @@ -2797,7 +2799,7 @@ def concat_layer(input, act=None, name=None, layer_attr=None, bias_attr=None): if layer_type == LayerType.CONCAT_LAYER: assert not bias_attr - Layer( + layer = Layer( name=name, type=layer_type, inputs=[x.name for x in input] if is_concat_layer else input, @@ -2805,13 +2807,7 @@ def concat_layer(input, act=None, name=None, layer_attr=None, bias_attr=None): bias=ParamAttr.to_bias(bias_attr), **ExtraLayerAttribute.to_kwargs(layer_attr)) - sz = 0 - for each_input in input: - if each_input.size is not None: - sz += each_input.size - else: - sz = None - break + sz = layer.config.size return LayerOutput( name, @@ -2979,7 +2975,7 @@ def memory(name, @layer_support() def lstm_step_layer(input, state, - size, + size=None, act=None, name=None, gate_act=None, @@ -3045,6 +3041,9 @@ def lstm_step_layer(input, :return: LayerOutput object. :rtype: LayerOutput """ + + assert size is None or state.size == size + size = state.size Layer( name=name, type=LayerType.LSTM_STEP_LAYER, @@ -3052,7 +3051,7 @@ def lstm_step_layer(input, active_gate_type=gate_act.name, active_state_type=state_act.name, bias=ParamAttr.to_bias(bias_attr), - size=size, + size=state.size, inputs=[input.name, state.name], **ExtraLayerAttribute.to_kwargs(layer_attr)) -- GitLab From 9b835471c2a3c6ba486419f593117e42e0c36fd9 Mon Sep 17 00:00:00 2001 From: liaogang Date: Thu, 1 Jun 2017 14:21:19 +0800 Subject: [PATCH 0513/3256] import 3rd party target in cmake --- cmake/external/gflags.cmake | 6 ++++- cmake/external/glog.cmake | 6 ++++- cmake/external/gtest.cmake | 13 +++++++++-- cmake/external/openblas.cmake | 10 +++++++-- cmake/external/warpctc.cmake | 6 ++++- cmake/generic.cmake | 38 +++++++------------------------- paddle/majel/CMakeLists.txt | 2 +- paddle/majel/test/CMakeLists.txt | 12 ---------- 8 files changed, 43 insertions(+), 50 deletions(-) delete mode 100644 paddle/majel/test/CMakeLists.txt diff --git a/cmake/external/gflags.cmake b/cmake/external/gflags.cmake index 0afb3ab9af4..30027a336c0 100644 --- a/cmake/external/gflags.cmake +++ b/cmake/external/gflags.cmake @@ -26,7 +26,7 @@ ENDIF(WIN32) INCLUDE_DIRECTORIES(${GFLAGS_INCLUDE_DIR}) ExternalProject_Add( - gflags + extern_gflags ${EXTERNAL_PROJECT_LOG_ARGS} GIT_REPOSITORY "https://github.com/gflags/gflags.git" PREFIX ${GFLAGS_SOURCES_DIR} @@ -44,4 +44,8 @@ ExternalProject_Add( -DCMAKE_BUILD_TYPE:STRING=Release ) +ADD_LIBRARY(gflags STATIC IMPORTED) +SET_PROPERTY(TARGET gflags PROPERTY IMPORTED_LOCATION ${GFLAGS_LIBRARIES}) +ADD_DEPENDENCIES(gflags extern_gflags) + LIST(APPEND external_project_dependencies gflags) diff --git a/cmake/external/glog.cmake b/cmake/external/glog.cmake index 4a9e2ecc6bb..fa9a5092877 100644 --- a/cmake/external/glog.cmake +++ b/cmake/external/glog.cmake @@ -27,7 +27,7 @@ ENDIF(WIN32) INCLUDE_DIRECTORIES(${GLOG_INCLUDE_DIR}) ExternalProject_Add( - glog + extern_glog ${EXTERNAL_PROJECT_LOG_ARGS} DEPENDS gflags GIT_REPOSITORY "https://github.com/google/glog.git" @@ -48,4 +48,8 @@ ExternalProject_Add( -DCMAKE_BUILD_TYPE:STRING=Release ) +ADD_LIBRARY(glog STATIC IMPORTED) +SET_PROPERTY(TARGET glog PROPERTY IMPORTED_LOCATION ${GLOG_LIBRARIES}) +ADD_DEPENDENCIES(glog extern_glog) + LIST(APPEND external_project_dependencies glog) diff --git a/cmake/external/gtest.cmake b/cmake/external/gtest.cmake index 49c7d71443c..386204dc37d 100644 --- a/cmake/external/gtest.cmake +++ b/cmake/external/gtest.cmake @@ -35,7 +35,7 @@ IF(WITH_TESTING) ENDIF(WIN32) ExternalProject_Add( - gtest + extern_gtest ${EXTERNAL_PROJECT_LOG_ARGS} GIT_REPOSITORY "https://github.com/google/googletest.git" GIT_TAG "release-1.8.0" @@ -55,5 +55,14 @@ IF(WITH_TESTING) -DCMAKE_POSITION_INDEPENDENT_CODE:BOOL=ON -DCMAKE_BUILD_TYPE:STRING=Release ) - LIST(APPEND external_project_dependencies gtest) + + ADD_LIBRARY(gtest STATIC IMPORTED) + SET_PROPERTY(TARGET gtest PROPERTY IMPORTED_LOCATION ${GTEST_LIBRARIES}) + ADD_DEPENDENCIES(gtest extern_gtest) + + ADD_LIBRARY(gtest_main STATIC IMPORTED) + SET_PROPERTY(TARGET gtest_main PROPERTY IMPORTED_LOCATION ${GTEST_MAIN_LIBRARIES}) + ADD_DEPENDENCIES(gtest_main extern_gtest) + + LIST(APPEND external_project_dependencies gtest gtest_main) ENDIF(WITH_TESTING) diff --git a/cmake/external/openblas.cmake b/cmake/external/openblas.cmake index bf234320a0d..cb67793cf97 100644 --- a/cmake/external/openblas.cmake +++ b/cmake/external/openblas.cmake @@ -41,7 +41,7 @@ IF(NOT ${CBLAS_FOUND}) ENDIF() ExternalProject_Add( - openblas + extern_openblas ${EXTERNAL_PROJECT_LOG_ARGS} GIT_REPOSITORY https://github.com/xianyi/OpenBLAS.git GIT_TAG ${OPENBLAS_COMMIT} @@ -53,8 +53,14 @@ IF(NOT ${CBLAS_FOUND}) UPDATE_COMMAND "" CONFIGURE_COMMAND "" ) - LIST(APPEND external_project_dependencies openblas) ENDIF(NOT ${CBLAS_FOUND}) MESSAGE(STATUS "BLAS library: ${CBLAS_LIBRARIES}") INCLUDE_DIRECTORIES(${CBLAS_INC_DIR}) + +ADD_LIBRARY(cblas STATIC IMPORTED) +SET_PROPERTY(TARGET cblas PROPERTY IMPORTED_LOCATION ${CBLAS_LIBRARIES}) +IF(NOT ${CBLAS_FOUND}) + ADD_DEPENDENCIES(cblas extern_openblas) + LIST(APPEND external_project_dependencies cblas) +ENDIF(NOT ${CBLAS_FOUND}) diff --git a/cmake/external/warpctc.cmake b/cmake/external/warpctc.cmake index 293070c3cfc..367d5b98c7f 100644 --- a/cmake/external/warpctc.cmake +++ b/cmake/external/warpctc.cmake @@ -43,7 +43,7 @@ ELSE() ENDIF() ExternalProject_Add( - warpctc + extern_warpctc ${EXTERNAL_PROJECT_LOG_ARGS} GIT_REPOSITORY "https://github.com/gangliao/warp-ctc.git" PREFIX ${WARPCTC_SOURCES_DIR} @@ -65,4 +65,8 @@ ExternalProject_Add( -DCMAKE_INSTALL_PREFIX:PATH=${WARPCTC_INSTALL_DIR} ) +ADD_LIBRARY(warpctc STATIC IMPORTED) +SET_PROPERTY(TARGET warpctc PROPERTY IMPORTED_LOCATION ${WARPCTC_LIBRARIES}) +ADD_DEPENDENCIES(warpctc extern_warpctc) + LIST(APPEND external_project_dependencies warpctc) diff --git a/cmake/generic.cmake b/cmake/generic.cmake index b6126bcecf5..ee13dcee2d1 100644 --- a/cmake/generic.cmake +++ b/cmake/generic.cmake @@ -29,24 +29,10 @@ # https://cmake.org/cmake/help/v3.0/module/CMakeParseArguments.html # -# generic.cmake depends on {GLOG,GFLAGS,GTEST,GTEST_MAIN}_LIBRARIES -# generated by cmake/external/*.cmake. - -# Because gflags depends on pthread, I copied the following snippet -# from https://stackoverflow.com/a/29871891/724872. -set(THREADS_PREFER_PTHREAD_FLAG ON) -find_package(Threads REQUIRED) - -function(link_glog_gflags TARGET_NAME) - target_link_libraries(${TARGET_NAME} ${GLOG_LIBRARIES} ${GFLAGS_LIBRARIES} Threads::Threads) - add_dependencies(${TARGET_NAME} glog gflags) -endfunction() - -function(link_gtest TARGET_NAME) - target_link_libraries(${TARGET_NAME} ${GTEST_LIBRARIES} ${GTEST_MAIN_LIBRARIES}) - add_dependencies(${TARGET_NAME} gtest) -endfunction() - +if(NOT APPLE) + find_package(Threads REQUIRED) + link_libraries(${CMAKE_THREAD_LIBS_INIT}) +endif(NOT APPLE) # cc_library parses tensor.cc and figures out that target also depend on tensor.h. # cc_library(tensor @@ -83,7 +69,6 @@ function(cc_binary TARGET_NAME) target_link_libraries(${TARGET_NAME} ${cc_binary_DEPS}) add_dependencies(${TARGET_NAME} ${cc_binary_DEPS}) endif() - link_glog_gflags(${TARGET_NAME}) endfunction(cc_binary) # The dependency to target tensor implies that if any of @@ -100,12 +85,8 @@ function(cc_test TARGET_NAME) set(multiValueArgs SRCS DEPS) cmake_parse_arguments(cc_test "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) add_executable(${TARGET_NAME} ${cc_test_SRCS}) - if(cc_test_DEPS) - target_link_libraries(${TARGET_NAME} ${cc_test_DEPS}) - add_dependencies(${TARGET_NAME} ${cc_test_DEPS}) - endif() - link_glog_gflags(${TARGET_NAME}) - link_gtest(${TARGET_NAME}) + target_link_libraries(${TARGET_NAME} ${cc_test_DEPS} gtest gtest_main) + add_dependencies(${TARGET_NAME} ${cc_test_DEPS} gtest gtest_main) add_test(${TARGET_NAME} ${TARGET_NAME}) endif() endfunction(cc_test) @@ -146,7 +127,6 @@ function(nv_binary TARGET_NAME) target_link_libraries(${TARGET_NAME} ${nv_binary_DEPS}) add_dependencies(${TARGET_NAME} ${nv_binary_DEPS}) endif() - link_glog_gflags(${TARGET_NAME}) endif() endfunction(nv_binary) @@ -165,11 +145,9 @@ function(nv_test TARGET_NAME) cmake_parse_arguments(nv_test "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) cuda_add_executable(${TARGET_NAME} ${nv_test_SRCS}) if(nv_test_DEPS) - target_link_libraries(${TARGET_NAME} ${nv_test_DEPS}) - add_dependencies(${TARGET_NAME} ${nv_test_DEPS}) + target_link_libraries(${TARGET_NAME} ${nv_test_DEPS} gtest gtest_main) + add_dependencies(${TARGET_NAME} ${nv_test_DEPS} gtest gtest_main) endif() - link_glog_gflags(${TARGET_NAME}) - link_gtest(${TARGET_NAME}) add_test(${TARGET_NAME} ${TARGET_NAME}) endif() endfunction(nv_test) diff --git a/paddle/majel/CMakeLists.txt b/paddle/majel/CMakeLists.txt index f492ce49110..93e5e2c22f0 100644 --- a/paddle/majel/CMakeLists.txt +++ b/paddle/majel/CMakeLists.txt @@ -1,5 +1,5 @@ cc_library(place SRCS place.cc) -cc_test(place_test SRCS place_test.cc DEPS place) +cc_test(place_test SRCS place_test.cc DEPS place glog gflags) cc_library(ddim SRCS ddim.cc) cc_test(ddim_test SRCS ddim_test.cc DEPS ddim) diff --git a/paddle/majel/test/CMakeLists.txt b/paddle/majel/test/CMakeLists.txt deleted file mode 100644 index 9d632d568e9..00000000000 --- a/paddle/majel/test/CMakeLists.txt +++ /dev/null @@ -1,12 +0,0 @@ -cc_test(place_test - SRCS place_test.cc - DEPS place) - -cc_test(ddim_test - SRCS ddim_test.cc - DEPS ddim) - -if(WITH_GPU) - nv_test(cuda_test SRCS cuda_test.cu) - nv_test(dim_test SRCS dim_test.cu DEPS ddim) -endif() -- GitLab From 661a8e9354fbc70302d24dcc01a2a733ac9fae3a Mon Sep 17 00:00:00 2001 From: liaogang Date: Thu, 1 Jun 2017 14:25:54 +0800 Subject: [PATCH 0514/3256] fix link gtest in nv_test --- cmake/generic.cmake | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/cmake/generic.cmake b/cmake/generic.cmake index ee13dcee2d1..052530608e6 100644 --- a/cmake/generic.cmake +++ b/cmake/generic.cmake @@ -144,10 +144,8 @@ function(nv_test TARGET_NAME) set(multiValueArgs SRCS DEPS) cmake_parse_arguments(nv_test "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) cuda_add_executable(${TARGET_NAME} ${nv_test_SRCS}) - if(nv_test_DEPS) - target_link_libraries(${TARGET_NAME} ${nv_test_DEPS} gtest gtest_main) - add_dependencies(${TARGET_NAME} ${nv_test_DEPS} gtest gtest_main) - endif() + target_link_libraries(${TARGET_NAME} ${nv_test_DEPS} gtest gtest_main) + add_dependencies(${TARGET_NAME} ${nv_test_DEPS} gtest gtest_main) add_test(${TARGET_NAME} ${TARGET_NAME}) endif() endfunction(nv_test) -- GitLab From ccfd37c330a8b62debd855238fa65bddb43eb08e Mon Sep 17 00:00:00 2001 From: Yi Wang Date: Thu, 1 Jun 2017 09:01:39 -0700 Subject: [PATCH 0515/3256] remove unnecessary unit test outputs --- paddle/majel/place_test.cc | 1 - 1 file changed, 1 deletion(-) diff --git a/paddle/majel/place_test.cc b/paddle/majel/place_test.cc index c5fa65ef6d6..2d16b708a34 100644 --- a/paddle/majel/place_test.cc +++ b/paddle/majel/place_test.cc @@ -38,5 +38,4 @@ TEST(Place, Print) { ss << majel::CpuPlace(); EXPECT_EQ("CpuPlace", ss.str()); } - LOG(INFO) << "\n[----------] Done \n"; } -- GitLab From c44495edc36487fa3f167439490e2fcd0c73385a Mon Sep 17 00:00:00 2001 From: Yi Wang Date: Thu, 1 Jun 2017 11:24:07 -0700 Subject: [PATCH 0516/3256] Remove unncessary header file --- paddle/majel/place_test.cc | 1 - 1 file changed, 1 deletion(-) diff --git a/paddle/majel/place_test.cc b/paddle/majel/place_test.cc index 2d16b708a34..6a099ae6b6e 100644 --- a/paddle/majel/place_test.cc +++ b/paddle/majel/place_test.cc @@ -1,7 +1,6 @@ #include "paddle/majel/place.h" #include #include "gtest/gtest.h" -#include "paddle/utils/Logging.h" TEST(Place, Equality) { majel::CpuPlace cpu; -- GitLab From 3bf8a60c9bc521ce2183dbcdb921cb6fea32758c Mon Sep 17 00:00:00 2001 From: wuyi05 Date: Fri, 2 Jun 2017 13:30:37 +0800 Subject: [PATCH 0517/3256] hack rdma cmake build lib --- cmake/rdma.cmake | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/cmake/rdma.cmake b/cmake/rdma.cmake index 9ff1a77cac7..b698f3bdc3f 100644 --- a/cmake/rdma.cmake +++ b/cmake/rdma.cmake @@ -10,7 +10,7 @@ if(WITH_RDMA) function(generate_rdma_links) #redirect to current DIR to isolate the pollution from system runtime environment - #it can benifits unified control for different gcc environment. + #it can benifits unified control for different gcc environment. #e.g, by default gcc48 did not refer /usr/lib64 which could contain low version #runtime libraries that will crash process while loading it. That redirect trick #can fix it. @@ -19,7 +19,9 @@ if(WITH_RDMA) COMMAND ln -s -f /usr/lib64/libibverbs.so.1.0.0 librdma/libibverbs.so.1 COMMAND ln -s -f /usr/lib64/libibverbs.so.1.0.0 librdma/libibverbs.so COMMAND ln -s -f /usr/lib64/librdmacm.so.1.0.0 librdma/librdmacm.so.1 - COMMAND ln -s -f /usr/lib64/librdmacm.so.1.0.0 librdma/librdmacm.so + COMMAND ln -s -f /usr/lib64/librdmacm.so.1.0.0 librdma/librdmacm.so + COMMAND ln -s -f /lib64/libnl.so.1.1.4 librdma/libnl.so.1 + COMMAND ln -s -f /lib64/libnl.so.1.1.4 librdma/libnl.so WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} ) endfunction(generate_rdma_links) @@ -44,7 +46,7 @@ if(WITH_RDMA) RDMA_INC_XIO AND RDMA_INC_EVENT AND RDMA_INC_NUMA AND - RDMA_LIB_SXISOCK AND + RDMA_LIB_SXISOCK AND RDMA_LIB_XIO AND RDMA_LIB_EVENT AND RDMA_LIB_EVENT_CORE AND @@ -53,19 +55,19 @@ if(WITH_RDMA) RDMA_LIB_NUMA ) - set(RDMA_INC_DIR - ${RDMA_INC_SXISOCK} + set(RDMA_INC_DIR + ${RDMA_INC_SXISOCK} ${RDMA_INC_XIO} ${RDMA_INC_EVENT} ${RDMA_INC_NUMA}) - set(RDMA_LIBS - ${RDMA_LIB_SXISOCK} - ${RDMA_LIB_XIO} - ${RDMA_LIB_EVENT} - ${RDMA_LIB_EVENT_CORE} - ${RDMA_LIB_EVENT_EXTRA} - ${RDMA_LIB_EVENT_PTHREADS} - ${RDMA_LIB_NUMA} + set(RDMA_LIBS + ${RDMA_LIB_SXISOCK} + ${RDMA_LIB_XIO} + ${RDMA_LIB_EVENT} + ${RDMA_LIB_EVENT_CORE} + ${RDMA_LIB_EVENT_EXTRA} + ${RDMA_LIB_EVENT_PTHREADS} + ${RDMA_LIB_NUMA} ) set(RDMA_LD_FLAGS "-L./librdma -libverbs -lrdmacm -Xlinker -rpath ./librdma") include_directories("${RDMA_INC_DIR}") -- GitLab From 5413af8d11eb8662598e5f89d5f4fa284e1c6fdf Mon Sep 17 00:00:00 2001 From: xzl Date: Fri, 2 Jun 2017 14:28:20 +0800 Subject: [PATCH 0518/3256] imporve pruning module --- paddle/parameter/ParameterUpdaterHook.cpp | 90 +++++++++++++++++-- proto/ParameterConfig.proto | 2 + python/paddle/trainer/config_parser.py | 15 +++- python/paddle/trainer_config_helpers/attrs.py | 46 +++++++++- python/paddle/v2/attr.py | 2 + 5 files changed, 144 insertions(+), 11 deletions(-) diff --git a/paddle/parameter/ParameterUpdaterHook.cpp b/paddle/parameter/ParameterUpdaterHook.cpp index f826e8448c6..76cc3ecad14 100644 --- a/paddle/parameter/ParameterUpdaterHook.cpp +++ b/paddle/parameter/ParameterUpdaterHook.cpp @@ -25,6 +25,9 @@ limitations under the License. */ #include "paddle/utils/Flags.h" #include "paddle/utils/Util.h" +using std::vector; +using std::pair; + namespace paddle { /** @@ -131,6 +134,73 @@ private: std::vector mask_; }; +class DynamicPruningHook : public IParameterUpdaterHook { +public: + explicit DynamicPruningHook(const ParameterUpdaterHookConfig& hookConfig) + : initCount_(0) { + sparsityRatio_ = hookConfig.sparsity_ratio(); + } + + static bool sortPairAscend(const pair& pair1, + const pair& pair2) { + return pair1.first > pair2.first; + } + + void update(Parameter* para) { + updateThreadChecker_.check(); + auto& vec = para->getBuf(PARAMETER_GRADIENT); + if (vec) { + vec->dotMul(*maskVec_); + } + } + + void generateMask(Parameter* para) { + VectorPtr vec = para->getBuf(PARAMETER_VALUE); + maskTemp_ = Vector::create(para->getSize(), false); + maskTemp_->zeroMem(); + real* dataPtr = maskTemp_->getData(); + + VectorPtr vecCpu = Vector::create(para->getSize(), false); + vecCpu->copyFrom(*vec); + vector> param; + + for (size_t i = 0; i < para->getSize(); i++) + param.push_back(std::make_pair(fabs(vecCpu->getData()[i]), i)); + std::sort(param.begin(), param.end(), sortPairAscend); + + for (size_t i = 0; i < para->getSize() * sparsityRatio_; i++) + dataPtr[param[i].second] = 1.0; + } + + void init(Parameter* para) { + generateMask(para); + size_t initCount = this->initCount_.fetch_add(1); + CHECK_EQ(initCount, 0UL) << "Currently the DynamicPruningHook must invoke " + "in same ParamterUpdater"; + VLOG(3) << "Initialize Parameter " << para; + SetDevice device(para->getDeviceId()); + + // Currently just use a mask vector for hack. + // @TODO(yuyang18): Implemented the mask operation in vector. + if (para->useGpu()) { + maskVec_ = Vector::create(para->getSize(), para->useGpu()); + maskVec_->copyFrom(*maskTemp_); + } else { + maskVec_ = maskTemp_; + } + + auto& vec = para->getBuf(PARAMETER_VALUE); + vec->dotMul(*maskVec_); + } + +private: + SameThreadChecker updateThreadChecker_; + std::atomic initCount_; + VectorPtr maskVec_; + VectorPtr maskTemp_; + real sparsityRatio_; +}; + IParameterUpdaterHook::IParameterUpdaterHook() {} IParameterUpdaterHook::~IParameterUpdaterHook() {} @@ -156,8 +226,7 @@ private: static WeakKVCache, IParameterUpdaterHook, - StringIntPairHasher> - g_hookCache_; + StringIntPairHasher> g_hookCache_; /** * ParameterUpdaterHook actually factory method. @@ -165,11 +234,22 @@ static WeakKVCache, static IParameterUpdaterHook* createImpl( const ParameterUpdaterHookConfig& config) { auto& type = config.type(); - if (type == "pruning") { - if (config.has_purning_mask_filename()) { + if (type == "pruning_static") { + if (config.has_purning_mask_filename()) return new StaticPruningHook(config.purning_mask_filename()); - } + else + LOG(FATAL) << "There must be mask_filename parameter for " << type + << " Hook"; + + } else if (type == "pruning") { + if (config.has_sparsity_ratio()) + return new DynamicPruningHook(config); + else + LOG(FATAL) << "There must be sparsity_ratio parameter for " << type + << " Hook"; } + + LOG(FATAL) << "Unknown Hook type: " << type; return nullptr; } diff --git a/proto/ParameterConfig.proto b/proto/ParameterConfig.proto index cbcd0af598d..61f4b037cf0 100644 --- a/proto/ParameterConfig.proto +++ b/proto/ParameterConfig.proto @@ -26,7 +26,9 @@ enum ParameterInitStrategy { message ParameterUpdaterHookConfig { required string type = 1; + //hook type such as 'pruning', 'pruning_static' optional string purning_mask_filename = 2; + optional double sparsity_ratio = 3; } message ParameterConfig { diff --git a/python/paddle/trainer/config_parser.py b/python/paddle/trainer/config_parser.py index 9fe8794691e..d80590210f2 100644 --- a/python/paddle/trainer/config_parser.py +++ b/python/paddle/trainer/config_parser.py @@ -3171,12 +3171,19 @@ def Layer(name, type, **xargs): @config_func def ParameterHook(type, **kwargs): - if type == 'pruning': + if type == 'pruning_static': + hook = ParameterUpdaterHookConfig() + hook.type = type mask_filename = kwargs.get('mask_filename', None) assert mask_filename is not None + hook.pruning_mask_filename = mask_filename + return hook + elif type == 'pruning': hook = ParameterUpdaterHookConfig() hook.type = type - hook.purning_mask_filename = mask_filename + sparsity_ratio = kwargs.get('sparsity_ratio', None) + assert sparsity_ratio is not None + hook.sparsity_ratio = sparsity_ratio return hook else: return None @@ -3283,13 +3290,13 @@ def Parameter(name, if update_hooks is not None: if hasattr(update_hooks, '__call__'): - update_hooks = update_hooks(para.name) + update_hooks = update_hooks() if isinstance(update_hooks, list): for hook in update_hooks: para.update_hooks.extend([hook]) else: - para.update_hooks.extend(update_hooks) + para.update_hooks.extend([update_hooks]) g_parameter_map[name] = para diff --git a/python/paddle/trainer_config_helpers/attrs.py b/python/paddle/trainer_config_helpers/attrs.py index d1167a234ca..011147a3685 100644 --- a/python/paddle/trainer_config_helpers/attrs.py +++ b/python/paddle/trainer_config_helpers/attrs.py @@ -14,7 +14,8 @@ from paddle.trainer.config_parser import * __all__ = [ - 'ParamAttr', 'ExtraAttr', 'ParameterAttribute', 'ExtraLayerAttribute' + 'HookAttr', 'ParamAttr', 'ExtraAttr', 'ParameterAttribute', + 'ExtraLayerAttribute' ] @@ -55,6 +56,42 @@ def is_compatible_with(x, Type): return False +class HookAttribute(object): + """ + Hook Attribute object. The hook is an auxiliary operation that occurs + during network propagation. Such as pruning operation, It will cut off + redundant parameters in the network before training. More detail can see + here paddle/parameter/ParameterUpdaterHook.cpp + NOTE: IT IS A HIGH LEVEL USER INTERFACE. + + :param type: Hook type, eg: 'pruning', 'pruning_static' + :type type: string + + :param mask_file: Must be specified if hook type is 'pruning_static', + the network reads the mask from the file to determine which parameters should be cut off + :type mask_file: string + + :param sparsity_ratio: Must be specified if hook type is 'pruning', + the network will hold the sparsity_ratio maximum parameters, and cut off the rest. + :type sparsity_ratio: float number between 0 and 1 + + """ + + def __init__(self, type, mask_filename=None, sparsity_ratio=None): + self.type = type + self.mask_filename = mask_filename + self.sparsity_ratio = sparsity_ratio + assert is_compatible_with(self.sparsity_ratio, + float), 'sparisity_ratio must be float type' + assert self.sparsity_ratio <= 1 and self.sparsity_ratio >= 0, 'sparisity must be a flaot between [0, 1] ' + + def __call__(self): + return ParameterHook( + self.type, + mask_filename=self.mask_filename, + sparsity_ratio=self.sparsity_ratio) + + class ParameterAttribute(object): """ Parameter Attributes object. To fine-tuning network training process, user @@ -109,7 +146,8 @@ class ParameterAttribute(object): learning_rate=None, momentum=None, gradient_clipping_threshold=None, - sparse_update=False): + sparse_update=False, + update_hooks=None): self.attr = {} if is_static: @@ -162,6 +200,9 @@ class ParameterAttribute(object): self.attr['gradient_clipping_threshold'] = \ gradient_clipping_threshold + if update_hooks: + self.attr['update_hooks'] = update_hooks + def set_default_parameter_name(self, name): """ Set default parameter name. If parameter not set, then will use default @@ -237,5 +278,6 @@ class ExtraLayerAttribute(object): return attr.attr +HookAttr = HookAttribute ParamAttr = ParameterAttribute ExtraAttr = ExtraLayerAttribute diff --git a/python/paddle/v2/attr.py b/python/paddle/v2/attr.py index 32f78614e7f..5d23894d735 100644 --- a/python/paddle/v2/attr.py +++ b/python/paddle/v2/attr.py @@ -17,10 +17,12 @@ import paddle.trainer_config_helpers.attrs __all__ = [ "Param", "Extra", + "Hook", ] Param = paddle.trainer_config_helpers.attrs.ParameterAttribute Extra = paddle.trainer_config_helpers.attrs.ExtraLayerAttribute +Hook = paddle.trainer_config_helpers.attrs.HookAttribute for each in paddle.trainer_config_helpers.attrs.__all__: globals()[each] = getattr(paddle.trainer_config_helpers.attrs, each) -- GitLab From 97a594e7f8a43e3e7fadd436933f84fb0e835c36 Mon Sep 17 00:00:00 2001 From: Yancey Date: Fri, 2 Jun 2017 15:01:09 +0800 Subject: [PATCH 0519/3256] Split dataset into multiple files (#2320) cluster dataset split and reader --- python/paddle/v2/dataset/common.py | 77 ++++++++++++++++++- python/paddle/v2/dataset/tests/common_test.py | 25 ++++++ 2 files changed, 101 insertions(+), 1 deletion(-) diff --git a/python/paddle/v2/dataset/common.py b/python/paddle/v2/dataset/common.py index 2eb018b8d60..418b592a5ac 100644 --- a/python/paddle/v2/dataset/common.py +++ b/python/paddle/v2/dataset/common.py @@ -19,8 +19,10 @@ import shutil import sys import importlib import paddle.v2.dataset +import cPickle +import glob -__all__ = ['DATA_HOME', 'download', 'md5file'] +__all__ = ['DATA_HOME', 'download', 'md5file', 'split', 'cluster_files_reader'] DATA_HOME = os.path.expanduser('~/.cache/paddle/dataset') @@ -74,3 +76,76 @@ def fetch_all(): getattr( importlib.import_module("paddle.v2.dataset.%s" % module_name), "fetch")() + + +def split(reader, line_count, suffix="%05d.pickle", dumper=cPickle.dump): + """ + you can call the function as: + + split(paddle.v2.dataset.cifar.train10(), line_count=1000, + suffix="imikolov-train-%05d.pickle") + + the output files as: + + |-imikolov-train-00000.pickle + |-imikolov-train-00001.pickle + |- ... + |-imikolov-train-00480.pickle + + :param reader: is a reader creator + :param line_count: line count for each file + :param suffix: the suffix for the output files, should contain "%d" + means the id for each file. Default is "%05d.pickle" + :param dumper: is a callable function that dump object to file, this + function will be called as dumper(obj, f) and obj is the object + will be dumped, f is a file object. Default is cPickle.dump. + """ + if not callable(dumper): + raise TypeError("dumper should be callable.") + lines = [] + indx_f = 0 + for i, d in enumerate(reader()): + lines.append(d) + if i >= line_count and i % line_count == 0: + with open(suffix % indx_f, "w") as f: + dumper(lines, f) + lines = [] + indx_f += 1 + if lines: + with open(suffix % indx_f, "w") as f: + dumper(lines, f) + + +def cluster_files_reader(files_pattern, + trainer_count, + trainer_id, + loader=cPickle.load): + """ + Create a reader that yield element from the given files, select + a file set according trainer count and trainer_id + + :param files_pattern: the files which generating by split(...) + :param trainer_count: total trainer count + :param trainer_id: the trainer rank id + :param loader: is a callable function that load object from file, this + function will be called as loader(f) and f is a file object. + Default is cPickle.load + """ + + def reader(): + if not callable(loader): + raise TypeError("loader should be callable.") + file_list = glob.glob(files_pattern) + file_list.sort() + my_file_list = [] + for idx, fn in enumerate(file_list): + if idx % trainer_count == trainer_id: + print "append file: %s" % fn + my_file_list.append(fn) + for fn in my_file_list: + with open(fn, "r") as f: + lines = loader(f) + for line in lines: + yield line + + return reader diff --git a/python/paddle/v2/dataset/tests/common_test.py b/python/paddle/v2/dataset/tests/common_test.py index 5babcef0eb4..f9815d4f9e1 100644 --- a/python/paddle/v2/dataset/tests/common_test.py +++ b/python/paddle/v2/dataset/tests/common_test.py @@ -15,6 +15,7 @@ import paddle.v2.dataset.common import unittest import tempfile +import glob class TestCommon(unittest.TestCase): @@ -32,6 +33,30 @@ class TestCommon(unittest.TestCase): paddle.v2.dataset.common.download( yi_avatar, 'test', 'f75287202d6622414c706c36c16f8e0d')) + def test_split(self): + def test_reader(): + def reader(): + for x in xrange(10): + yield x + + return reader + + _, temp_path = tempfile.mkstemp() + paddle.v2.dataset.common.split( + test_reader(), 4, suffix=temp_path + '/test-%05d.pickle') + files = glob.glob(temp_path + '/test-%05d.pickle') + self.assertEqual(len(files), 3) + + def test_cluster_file_reader(self): + _, temp_path = tempfile.mkstemp() + for x in xrange(5): + with open(temp_path + '/%05d.test' % x) as f: + f.write('%d\n' % x) + reader = paddle.v2.dataset.common.cluster_files_reader( + temp_path + '/*.test', 5, 0) + for idx, e in enumerate(reader()): + self.assertEqual(e, str("0")) + if __name__ == '__main__': unittest.main() -- GitLab From e7ac28895cdfd55be95b7bdab41db4c809d30a8d Mon Sep 17 00:00:00 2001 From: yangyaming Date: Fri, 2 Jun 2017 16:27:08 +0800 Subject: [PATCH 0520/3256] Abstract BBox and provide general functions --- paddle/gserver/layers/DetectionUtil.cpp | 567 ++++++++++++++++++++++++ paddle/gserver/layers/DetectionUtil.h | 305 +++++++++++++ 2 files changed, 872 insertions(+) create mode 100644 paddle/gserver/layers/DetectionUtil.cpp create mode 100644 paddle/gserver/layers/DetectionUtil.h diff --git a/paddle/gserver/layers/DetectionUtil.cpp b/paddle/gserver/layers/DetectionUtil.cpp new file mode 100644 index 00000000000..f301d2d332b --- /dev/null +++ b/paddle/gserver/layers/DetectionUtil.cpp @@ -0,0 +1,567 @@ +/* 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 "DetectionUtil.h" + +namespace paddle { + +size_t appendWithPermute(const MatrixPtr inMatrix, + size_t height, + size_t width, + size_t outTotalSize, + size_t outOffset, + size_t batchSize, + MatrixPtr outMatrix, + PermMode permMode, + bool useGpu) { + if (permMode == NCHWTONHWC) { + size_t inElementCnt = inMatrix->getElementCnt(); + size_t channels = inElementCnt / (height * width * batchSize); + size_t imgSize = height * width; + for (size_t i = 0; i < batchSize; ++i) { + size_t offset = i * (outTotalSize / batchSize) + outOffset; + const MatrixPtr inTmp = + Matrix::create(inMatrix->getData() + i * channels * imgSize, + channels, + imgSize, + false, + useGpu); + MatrixPtr outTmp = Matrix::create( + outMatrix->getData() + offset, imgSize, channels, false, useGpu); + inTmp->transpose(outTmp, false); + } + return channels * imgSize; + } else { + LOG(FATAL) << "Unkown permute mode"; + } +} + +size_t decomposeWithPermute(const MatrixPtr inMatrix, + size_t height, + size_t width, + size_t inTotalSize, + size_t inOffset, + size_t batchSize, + MatrixPtr outMatrix, + PermMode permMode, + bool useGpu) { + if (permMode == NHWCTONCHW) { + size_t outElementCnt = outMatrix->getElementCnt(); + size_t channels = outElementCnt / (height * width * batchSize); + size_t imgSize = height * width; + for (size_t i = 0; i < batchSize; ++i) { + size_t offset = i * (inTotalSize / batchSize) + inOffset; + const MatrixPtr inTmp = Matrix::create( + inMatrix->getData() + offset, imgSize, channels, false, useGpu); + MatrixPtr outTmp = + Matrix::create(outMatrix->getData() + i * channels * imgSize, + channels, + imgSize, + false, + useGpu); + inTmp->transpose(outTmp, false); + } + return channels * imgSize; + } else { + LOG(FATAL) << "Unkown permute mode"; + } +} + +real jaccardOverlap(const NormalizedBBox& bbox1, const NormalizedBBox& bbox2) { + if (bbox2.xMin > bbox1.xMax || bbox2.xMax < bbox1.xMin || + bbox2.yMin > bbox1.yMax || bbox2.yMax < bbox1.yMin) { + return 0.0; + } else { + real interXMin = std::max(bbox1.xMin, bbox2.xMin); + real interYMin = std::max(bbox1.yMin, bbox2.yMin); + real interXMax = std::min(bbox1.xMax, bbox2.xMax); + real interYMax = std::min(bbox1.yMax, bbox2.yMax); + + real interWidth = interXMax - interXMin; + real interHeight = interYMax - interYMin; + real interSize = interWidth * interHeight; + + real bboxSize1 = bbox1.getSize(); + real bboxSize2 = bbox2.getSize(); + + return interSize / (bboxSize1 + bboxSize2 - interSize); + } +} + +vector encodeBBoxWithVar(const NormalizedBBox& priorBBox, + const vector priorBBoxVar, + const NormalizedBBox& gtBBox) { + real priorBBoxWidth = priorBBox.getWidth(); + real priorBBoxHeight = priorBBox.getHeight(); + real priorBBoxCenterX = priorBBox.getCenterX(); + real priorBBoxCenterY = priorBBox.getCenterY(); + + real gtBBoxWidth = gtBBox.getWidth(); + real gtBBoxHeight = gtBBox.getHeight(); + real gtBBoxCenterX = gtBBox.getCenterX(); + real gtBBoxCenterY = gtBBox.getCenterY(); + + vector offsetParam; + offsetParam.push_back((gtBBoxCenterX - priorBBoxCenterX) / priorBBoxWidth / + priorBBoxVar[0]); + offsetParam.push_back((gtBBoxCenterY - priorBBoxCenterY) / priorBBoxHeight / + priorBBoxVar[1]); + offsetParam.push_back(std::log(std::fabs(gtBBoxWidth / priorBBoxWidth)) / + priorBBoxVar[2]); + offsetParam.push_back(std::log(std::fabs(gtBBoxHeight / priorBBoxHeight)) / + priorBBoxVar[3]); + + return offsetParam; +} + +NormalizedBBox decodeBBoxWithVar(const NormalizedBBox& priorBBox, + const vector& priorBBoxVar, + const vector& locPredData) { + real priorBBoxWidth = priorBBox.getWidth(); + real priorBBoxHeight = priorBBox.getHeight(); + real priorBBoxCenterX = priorBBox.getCenterX(); + real priorBBoxCenterY = priorBBox.getCenterY(); + + real decodedBBoxCenterX = + priorBBoxVar[0] * locPredData[0] * priorBBoxWidth + priorBBoxCenterX; + real decodedBBoxCenterY = + priorBBoxVar[1] * locPredData[1] * priorBBoxHeight + priorBBoxCenterY; + real decodedBBoxWidth = + std::exp(priorBBoxVar[2] * locPredData[2]) * priorBBoxWidth; + real decodedBBoxHeight = + std::exp(priorBBoxVar[3] * locPredData[3]) * priorBBoxHeight; + + NormalizedBBox decodedBBox; + decodedBBox.xMin = decodedBBoxCenterX - decodedBBoxWidth / 2; + decodedBBox.yMin = decodedBBoxCenterY - decodedBBoxHeight / 2; + decodedBBox.xMax = decodedBBoxCenterX + decodedBBoxWidth / 2; + decodedBBox.yMax = decodedBBoxCenterY + decodedBBoxHeight / 2; + + return decodedBBox; +} + +void getBBoxFromPriorData(const real* priorData, + const size_t numBBoxes, + vector& bboxVec) { + size_t outOffset = bboxVec.size(); + bboxVec.resize(bboxVec.size() + numBBoxes); + for (size_t i = 0; i < numBBoxes; ++i) { + NormalizedBBox bbox; + bbox.xMin = *(priorData + i * 8); + bbox.yMin = *(priorData + i * 8 + 1); + bbox.xMax = *(priorData + i * 8 + 2); + bbox.yMax = *(priorData + i * 8 + 3); + bboxVec[outOffset + i] = bbox; + } +} + +void getBBoxVarFromPriorData(const real* priorData, + const size_t num, + vector>& varVec) { + size_t outOffset = varVec.size(); + varVec.resize(varVec.size() + num); + for (size_t i = 0; i < num; ++i) { + vector var; + var.push_back(*(priorData + i * 8 + 4)); + var.push_back(*(priorData + i * 8 + 5)); + var.push_back(*(priorData + i * 8 + 6)); + var.push_back(*(priorData + i * 8 + 7)); + varVec[outOffset + i] = var; + } +} + +void getBBoxFromLabelData(const real* labelData, + const size_t numBBoxes, + vector& bboxVec) { + size_t outOffset = bboxVec.size(); + bboxVec.resize(bboxVec.size() + numBBoxes); + for (size_t i = 0; i < numBBoxes; ++i) { + NormalizedBBox bbox; + bbox.xMin = *(labelData + i * 6 + 1); + bbox.yMin = *(labelData + i * 6 + 2); + bbox.xMax = *(labelData + i * 6 + 3); + bbox.yMax = *(labelData + i * 6 + 4); + real isDifficult = *(labelData + i * 6 + 5); + if (std::abs(isDifficult - 0.0) < 1e-6) + bbox.isDifficult = false; + else + bbox.isDifficult = true; + bboxVec[outOffset + i] = bbox; + } +} + +void getBBoxFromDetectData(const real* detectData, + const size_t numBBoxes, + vector& labelVec, + vector& scoreVec, + vector& bboxVec) { + size_t outOffset = bboxVec.size(); + labelVec.resize(outOffset + numBBoxes); + scoreVec.resize(outOffset + numBBoxes); + bboxVec.resize(outOffset + numBBoxes); + for (size_t i = 0; i < numBBoxes; ++i) { + labelVec[outOffset + i] = *(detectData + i * 7 + 1); + scoreVec[outOffset + i] = *(detectData + i * 7 + 2); + NormalizedBBox bbox; + bbox.xMin = *(detectData + i * 7 + 3); + bbox.yMin = *(detectData + i * 7 + 4); + bbox.xMax = *(detectData + i * 7 + 5); + bbox.yMax = *(detectData + i * 7 + 6); + bboxVec[outOffset + i] = bbox; + } +} + +void matchBBox(const vector& priorBBoxes, + const vector& gtBBoxes, + real overlapThreshold, + vector* matchIndices, + vector* matchOverlaps) { + map> overlaps; + size_t numPriors = priorBBoxes.size(); + size_t numGTs = gtBBoxes.size(); + + matchIndices->clear(); + matchIndices->resize(numPriors, -1); + matchOverlaps->clear(); + matchOverlaps->resize(numPriors, 0.0); + + // Store the positive overlap between predictions and ground truth + for (size_t i = 0; i < numPriors; ++i) { + for (size_t j = 0; j < numGTs; ++j) { + real overlap = jaccardOverlap(priorBBoxes[i], gtBBoxes[j]); + if (overlap > 1e-6) { + (*matchOverlaps)[i] = std::max((*matchOverlaps)[i], overlap); + overlaps[i][j] = overlap; + } + } + } + // Bipartite matching + vector gtPool; + for (size_t i = 0; i < numGTs; ++i) { + gtPool.push_back(i); + } + while (gtPool.size() > 0) { + // Find the most overlapped gt and corresponding predictions + int maxPriorIdx = -1; + int maxGTIdx = -1; + real maxOverlap = -1.0; + for (map>::iterator it = overlaps.begin(); + it != overlaps.end(); + ++it) { + size_t i = it->first; + if ((*matchIndices)[i] != -1) { + // The prediction already has matched ground truth or is ignored + continue; + } + for (size_t p = 0; p < gtPool.size(); ++p) { + int j = gtPool[p]; + if (it->second.find(j) == it->second.end()) { + // No overlap between the i-th prediction and j-th ground truth + continue; + } + // Find the maximum overlapped pair + if (it->second[j] > maxOverlap) { + maxPriorIdx = (int)i; + maxGTIdx = (int)j; + maxOverlap = it->second[j]; + } + } + } + if (maxPriorIdx == -1) { + break; + } else { + (*matchIndices)[maxPriorIdx] = maxGTIdx; + (*matchOverlaps)[maxPriorIdx] = maxOverlap; + gtPool.erase(std::find(gtPool.begin(), gtPool.end(), maxGTIdx)); + } + } + + // Get most overlaped for the rest prediction bboxes + for (map>::iterator it = overlaps.begin(); + it != overlaps.end(); + ++it) { + size_t i = it->first; + if ((*matchIndices)[i] != -1) { + // The prediction already has matched ground truth or is ignored + continue; + } + int maxGTIdx = -1; + real maxOverlap = -1; + for (size_t j = 0; j < numGTs; ++j) { + if (it->second.find(j) == it->second.end()) { + // No overlap between the i-th prediction and j-th ground truth + continue; + } + // Find the maximum overlapped pair + real overlap = it->second[j]; + if (overlap > maxOverlap && overlap >= overlapThreshold) { + maxGTIdx = j; + maxOverlap = overlap; + } + } + if (maxGTIdx != -1) { + (*matchIndices)[i] = maxGTIdx; + (*matchOverlaps)[i] = maxOverlap; + } + } +} + +pair generateMatchIndices( + const MatrixPtr priorValue, + const size_t numPriorBBoxes, + const MatrixPtr gtValue, + const int* gtStartPosPtr, + const size_t seqNum, + const vector>& maxConfScore, + const size_t batchSize, + const real overlapThreshold, + const real negOverlapThreshold, + const size_t negPosRatio, + vector>* matchIndicesVecPtr, + vector>* negIndicesVecPtr) { + vector priorBBoxes; // share same prior bboxes + getBBoxFromPriorData(priorValue->getData(), numPriorBBoxes, priorBBoxes); + size_t totalPos = 0; + size_t totalNeg = 0; + for (size_t n = 0; n < batchSize; ++n) { + vector matchIndices; + vector negIndices; + vector matchOverlaps; + matchIndices.resize(numPriorBBoxes, -1); + matchOverlaps.resize(numPriorBBoxes, 0.0); + size_t numGTBBoxes = 0; + if (n < seqNum) numGTBBoxes = gtStartPosPtr[n + 1] - gtStartPosPtr[n]; + if (!numGTBBoxes) { + matchIndicesVecPtr->push_back(matchIndices); + negIndicesVecPtr->push_back(negIndices); + continue; + } + vector gtBBoxes; + getBBoxFromLabelData( + gtValue->getData() + gtStartPosPtr[n] * 6, numGTBBoxes, gtBBoxes); + + matchBBox( + priorBBoxes, gtBBoxes, overlapThreshold, &matchIndices, &matchOverlaps); + + size_t numPos = 0; + size_t numNeg = 0; + for (size_t i = 0; i < matchIndices.size(); ++i) + if (matchIndices[i] != -1) ++numPos; + totalPos += numPos; + vector> scoresIndices; + for (size_t i = 0; i < matchIndices.size(); ++i) + if (matchIndices[i] == -1 && matchOverlaps[i] < negOverlapThreshold) { + scoresIndices.push_back(std::make_pair(maxConfScore[n][i], i)); + ++numNeg; + } + numNeg = std::min(static_cast(numPos * negPosRatio), numNeg); + std::sort(scoresIndices.begin(), + scoresIndices.end(), + sortScorePairDescend); + for (size_t i = 0; i < numNeg; ++i) + negIndices.push_back(scoresIndices[i].second); + totalNeg += numNeg; + matchIndicesVecPtr->push_back(matchIndices); + negIndicesVecPtr->push_back(negIndices); + } + return std::make_pair(totalPos, totalNeg); +} + +void getMaxConfidenceScores(const real* confData, + const size_t batchSize, + const size_t numPriorBBoxes, + const size_t numClasses, + const size_t backgroundId, + vector>* maxConfScoreVecPtr) { + maxConfScoreVecPtr->clear(); + for (size_t i = 0; i < batchSize; ++i) { + vector maxConfScore; + for (size_t j = 0; j < numPriorBBoxes; ++j) { + int offset = j * numClasses; + real maxVal = -FLT_MAX; + real maxPosVal = -FLT_MAX; + real maxScore = 0.0; + for (size_t c = 0; c < numClasses; ++c) { + maxVal = std::max(confData[offset + c], maxVal); + if (c != backgroundId) + maxPosVal = std::max(confData[offset + c], maxPosVal); + } + real sum = 0.0; + for (size_t c = 0; c < numClasses; ++c) + sum += std::exp(confData[offset + c] - maxVal); + maxScore = std::exp(maxPosVal - maxVal) / sum; + maxConfScore.push_back(maxScore); + } + confData += numPriorBBoxes * numClasses; + maxConfScoreVecPtr->push_back(maxConfScore); + } +} + +template +bool sortScorePairDescend(const pair& pair1, + const pair& pair2) { + return pair1.first > pair2.first; +} + +template <> +bool sortScorePairDescend(const pair& pair1, + const pair& pair2) { + return pair1.first > pair2.first; +} + +void applyNMSFast(const vector& bboxes, + const real* confScoreData, + size_t classIdx, + size_t topK, + real confThreshold, + real nmsThreshold, + size_t numPriorBBoxes, + size_t numClasses, + vector* indices) { + vector> scores; + for (size_t i = 0; i < numPriorBBoxes; ++i) { + size_t confOffset = i * numClasses + classIdx; + if (confScoreData[confOffset] > confThreshold) + scores.push_back(std::make_pair(confScoreData[confOffset], i)); + } + std::stable_sort(scores.begin(), scores.end(), sortScorePairDescend); + if (topK > 0 && topK < scores.size()) scores.resize(topK); + while (scores.size() > 0) { + const size_t idx = scores.front().second; + bool keep = true; + for (size_t i = 0; i < indices->size(); ++i) { + if (keep) { + const size_t savedIdx = (*indices)[i]; + real overlap = jaccardOverlap(bboxes[idx], bboxes[savedIdx]); + keep = overlap <= nmsThreshold; + } else { + break; + } + } + if (keep) indices->push_back(idx); + scores.erase(scores.begin()); + } +} + +size_t getDetectionIndices( + const real* confData, + const size_t numPriorBBoxes, + const size_t numClasses, + const size_t backgroundId, + const size_t batchSize, + const size_t confThreshold, + const size_t nmsTopK, + const real nmsThreshold, + const size_t keepTopK, + const vector>& allDecodedBBoxes, + vector>>* allDetectionIndices) { + size_t totalKeepNum = 0; + for (size_t n = 0; n < batchSize; ++n) { + const vector& decodedBBoxes = allDecodedBBoxes[n]; + size_t numDetected = 0; + map> indices; + size_t confOffset = n * numPriorBBoxes * numClasses; + for (size_t c = 0; c < numClasses; ++c) { + if (c == backgroundId) continue; + applyNMSFast(decodedBBoxes, + confData + confOffset, + c, + nmsTopK, + confThreshold, + nmsThreshold, + numPriorBBoxes, + numClasses, + &(indices[c])); + numDetected += indices[c].size(); + } + if (keepTopK > 0 && numDetected > keepTopK) { + vector>> scoreIndexPairs; + for (size_t c = 0; c < numClasses; ++c) { + const vector& labelIndices = indices[c]; + for (size_t i = 0; i < labelIndices.size(); ++i) { + size_t idx = labelIndices[i]; + scoreIndexPairs.push_back( + std::make_pair((confData + confOffset)[idx * numClasses + c], + std::make_pair(c, idx))); + } + } + std::sort(scoreIndexPairs.begin(), + scoreIndexPairs.end(), + sortScorePairDescend>); + scoreIndexPairs.resize(keepTopK); + map> newIndices; + for (size_t i = 0; i < scoreIndexPairs.size(); ++i) { + size_t label = scoreIndexPairs[i].second.first; + size_t idx = scoreIndexPairs[i].second.second; + newIndices[label].push_back(idx); + } + allDetectionIndices->push_back(newIndices); + totalKeepNum += keepTopK; + } else { + allDetectionIndices->push_back(indices); + totalKeepNum += numDetected; + } + } + return totalKeepNum; +} + +void getDetectionOutput(const real* confData, + const size_t numKept, + const size_t numPriorBBoxes, + const size_t numClasses, + const size_t batchSize, + const vector>>& allIndices, + const vector>& allDecodedBBoxes, + MatrixPtr out) { + MatrixPtr outBuffer; + Matrix::resizeOrCreate(outBuffer, numKept, 7, false, false); + real* bufferData = outBuffer->getData(); + size_t count = 0; + for (size_t n = 0; n < batchSize; ++n) { + for (map>::const_iterator it = allIndices[n].begin(); + it != allIndices[n].end(); + ++it) { + size_t label = it->first; + const vector& indices = it->second; + const vector& decodedBBoxes = allDecodedBBoxes[n]; + for (size_t i = 0; i < indices.size(); ++i) { + size_t idx = indices[i]; + size_t confOffset = n * numPriorBBoxes * numClasses + idx * numClasses; + bufferData[count * 7] = n; + bufferData[count * 7 + 1] = label; + bufferData[count * 7 + 2] = (confData + confOffset)[label]; + NormalizedBBox clippedBBox = clipBBox(decodedBBoxes[idx]); + bufferData[count * 7 + 3] = clippedBBox.xMin; + bufferData[count * 7 + 4] = clippedBBox.yMin; + bufferData[count * 7 + 5] = clippedBBox.xMax; + bufferData[count * 7 + 6] = clippedBBox.yMax; + ++count; + } + } + } + out->copyFrom(bufferData, numKept * 7); +} + +NormalizedBBox clipBBox(const NormalizedBBox& bbox) { + real realOne = static_cast(1.0); + real realZero = static_cast(0.0); + NormalizedBBox clippedBBox; + clippedBBox.xMin = std::max(std::min(bbox.xMin, realOne), realZero); + clippedBBox.yMin = std::max(std::min(bbox.yMin, realOne), realZero); + clippedBBox.xMax = std::max(std::min(bbox.xMax, realOne), realZero); + clippedBBox.yMax = std::max(std::min(bbox.yMax, realOne), realZero); + return clippedBBox; +} + +} // namespace paddle diff --git a/paddle/gserver/layers/DetectionUtil.h b/paddle/gserver/layers/DetectionUtil.h new file mode 100644 index 00000000000..e30cfa8f082 --- /dev/null +++ b/paddle/gserver/layers/DetectionUtil.h @@ -0,0 +1,305 @@ +/* Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserve. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. */ + +#pragma once + +#include +#include +#include +#include "paddle/math/Matrix.h" + +using std::vector; +using std::pair; +using std::map; + +namespace paddle { + +template +struct BBoxBase { + BBoxBase(T xMin, T yMin, T xMax, T yMax) + : xMin(xMin), yMin(yMin), xMax(xMax), yMax(yMax), isDifficult(false) {} + + BBoxBase() {} + + T getWidth() const { return xMax - xMin; } + + T getHeight() const { return yMax - yMin; } + + T getCenterX() const { return (xMin + xMax) / 2; } + + T getCenterY() const { return (yMin + yMax) / 2; } + + T getSize() const { return getWidth() * getHeight(); } + + T xMin; + T yMin; + T xMax; + T yMax; + bool isDifficult; +}; + +struct NormalizedBBox : BBoxBase { + NormalizedBBox() : BBoxBase() {} +}; + +enum PermMode { NCHWTONHWC, NHWCTONCHW }; + +/** + * @brief First permute input maxtrix then append to output matrix + */ +size_t appendWithPermute(const MatrixPtr inMatrix, + size_t height, + size_t width, + size_t outTotalSize, + size_t outOffset, + size_t batchSize, + MatrixPtr outMatrix, + PermMode permMode, + bool useGpu); + +/** + * @brief First permute input maxtrix then decompose to output + */ +size_t decomposeWithPermute(const MatrixPtr inMatrix, + size_t height, + size_t width, + size_t totalSize, + size_t offset, + size_t batchSize, + MatrixPtr outMatrix, + PermMode permMode, + bool useGpu); + +/** + * @brief Compute jaccard overlap between two bboxes. + * @param bbox1 The first bbox + * @param bbox2 The second bbox + */ +real jaccardOverlap(const NormalizedBBox& bbox1, const NormalizedBBox& bbox2); + +/** + * @brief Compute offset parameters between prior bbox and groundtruth bbox + * and variances of prior bbox are considered + * @param priorBBox Input prior bbox + * @param priorBBoxVar Variance parameters of prior bbox + * @param gtBBox Groundtruth bbox + */ +vector encodeBBoxWithVar(const NormalizedBBox& priorBBox, + const vector priorBBoxVar, + const NormalizedBBox& gtBBox); + +/** + * @brief Decode prior bbox with offset parameters + * and variances of prior bbox are considered + * @param priorBBox Prior bbox to be decoded + * @param priorBBoxVar Variance parameters of prior bbox + * @param locPredData Offset parameters + */ +NormalizedBBox decodeBBoxWithVar(const NormalizedBBox& priorBBox, + const vector& priorBBoxVar, + const vector& locPredData); + +/** + * @brief Extract bboxes from prior matrix, the layout is + * xmin1 | ymin1 | xmax1 | ymax1 | xmin1Var | ymin1Var | xmax1Var | ymax1Var ... + * @param priorData Matrix of prior value + * @param numBBoxes Number of bbox to be extracted + * @param bboxVec Append to the vector + */ +void getBBoxFromPriorData(const real* priorData, + const size_t numBBoxes, + vector& bboxVec); + +/** + * @brief Extract labels, scores and bboxes from detection matrix, the layout is + * imageId | label | score | xmin | ymin | xmax | ymax + * @param detectData Matrix of detection value + * @param numBBoxes Number of bbox to be extracted + * @param labelVec Label of bbox + * @param scoreVec Score of bbox + * @param bboxVec Append to the vector + */ +void getBBoxFromDetectData(const real* detectData, + const size_t numBBoxes, + vector& labelVec, + vector& scoreVec, + vector& bboxVec); + +/** + * @brief Extract variances from prior matrix, the layout is + * xmin1 | ymin1 | xmax1 | ymax1 | xmin1Var | ymin1Var | xmax1Var | ymax1Var ... + * @param priorData Matrix of prior value + * @param num Number to be extracted + * @param varVec Append to the vector + */ +void getBBoxVarFromPriorData(const real* priorData, + const size_t num, + vector>& varVec); + +/** + * @brief Extract bboxes from label matrix, the layout is + * class1_1 | xmin1_1 | ymin1_1 | xmax1_1 | ymax1_1 | difficult1_1 | ... + * @param labelData Matrix of label value + * @param numBBoxes Number to be extracted + * @param bboxVec Append to the vector + */ +void getBBoxFromLabelData(const real* labelData, + const size_t numBBoxes, + vector& bboxVec); + +/** +* @brief Match prior bbox to groundtruth bbox, the strategy is: +1. Find the most overlaped bbox pair (prior and groundtruth) +2. For rest of prior bboxes find the most overlaped groundtruth bbox +* @param priorBBoxes prior bbox +* @param gtBBoxes groundtruth bbox +* @param overlapThreshold Low boundary of overlap (judge whether matched) +* @param matchIndices For each prior bbox, groundtruth bbox index if matched +otherwise -1 +* @param matchOverlaps For each prior bbox, overap with all groundtruth bboxes +*/ +void matchBBox(const vector& priorBBoxes, + const vector& gtBBoxes, + real overlapThreshold, + vector* matchIndices, + vector* matchOverlaps); + +/** +* @brief Generate positive bboxes and negative bboxes, +|positive bboxes|/|negative bboxes| is negPosRatio +* @param priorValue Prior value +* @param numPriorBBoxes Number of prior bbox +* @param gtValue Groundtruth value +* @param gtStartPosPtr Since groundtruth value stored as sequence type, +this parameter indicates start position of each record +* @param seqNum Number of sequence +* @param maxConfScore Classification score for prior bbox, used to mine +negative examples +* @param batchSize Image number +* @param overlapThreshold Low boundary of overap +* @param negOverlapThreshold Upper boundary of overap (judge negative example) +* @param negPosRatio Control number of negative bboxes +* @param matchIndicesVecPtr Save indices of matched prior bbox +* @param negIndicesVecPtr Save indices of negative prior bbox +*/ +pair generateMatchIndices( + const MatrixPtr priorValue, + const size_t numPriorBBoxes, + const MatrixPtr gtValue, + const int* gtStartPosPtr, + const size_t seqNum, + const vector>& maxConfScore, + const size_t batchSize, + const real overlapThreshold, + const real negOverlapThreshold, + const size_t negPosRatio, + vector>* matchIndicesVecPtr, + vector>* negIndicesVecPtr); + +/** + * @brief Get max confidence score for each prior bbox + * @param confData Confidence scores, layout is + * class1 score | class2 score | ... | classN score ... + * @param batchSize Image number + * @param numPriorBBoxes Prior bbox number + * @param numClasses Classes number + * @param backgroundId Background id + * @param maxConfScoreVecPtr Ouput + */ +void getMaxConfidenceScores(const real* confData, + const size_t batchSize, + const size_t numPriorBBoxes, + const size_t numClasses, + const size_t backgroundId, + vector>* maxConfScoreVecPtr); + +template +bool sortScorePairDescend(const pair& pair1, + const pair& pair2); + +template <> +bool sortScorePairDescend(const pair& pair1, + const pair& pair2); + +/** + * @brief Do NMS for bboxes to remove duplicated bboxes + * @param bboxes BBoxes to apply NMS + * @param confScoreData Confidence scores + * @param classIdx Class to do NMS + * @param topK Number to keep + * @param confThreshold Low boundary of confidence score + * @param nmsThreshold Threshold of overlap + * @param numPriorBBoxes Total number of prior bboxes + * @param numClasses Total class number + * @param indices Indices of high quality bboxes + */ +void applyNMSFast(const vector& bboxes, + const real* confScoreData, + size_t classIdx, + size_t topK, + real confThreshold, + real nmsThreshold, + size_t numPriorBBoxes, + size_t numClasses, + vector* indices); + +/** + * @brief Get detection results which satify requirements + * @param numPriorBBoxes Prior bbox number + * @param numClasses Class number + * @param backgroundId Background class + * @param batchSize Image number + * @param confThreshold Threshold of class confidence + * @param nmsTopK Used in NMS operation to keep top k bbox + * @param nmsThreshold Used in NMS, threshold of overlap + * @param keepTopK How many bboxes keeped in an image + * @param allDecodedBBoxes Decoded bboxes for all images + * @param allDetectionIndices Save detection bbox indices + */ +size_t getDetectionIndices( + const real* confData, + const size_t numPriorBBoxes, + const size_t numClasses, + const size_t backgroundId, + const size_t batchSize, + const size_t confThreshold, + const size_t nmsTopK, + const real nmsThreshold, + const size_t keepTopK, + const vector>& allDecodedBBoxes, + vector>>* allDetectionIndices); + +/** + * @brief Get detection results + * @param confData Confidence scores + * @param numPriorBBoxes Prior bbox number + * @param numClasses Class number + * @param batchSize Image number + * @param allIndices Indices of predicted bboxes + * @param allDecodedBBoxes BBoxes decoded + * @param out Output matrix + * image number | label | confidence score | xMin | yMin | xMax | yMax + */ +void getDetectionOutput(const real* confData, + const size_t numKept, + const size_t numPriorBBoxes, + const size_t numClasses, + const size_t batchSize, + const vector>>& allIndices, + const vector>& allDecodedBBoxes, + MatrixPtr out); + +NormalizedBBox clipBBox(const NormalizedBBox& bbox); + +} // namespace paddle -- GitLab From f5ca74ef1974bb11c5c948af783410203a0dd33d Mon Sep 17 00:00:00 2001 From: Luo Tao Date: Fri, 2 Jun 2017 16:28:14 +0800 Subject: [PATCH 0521/3256] fix book link --- doc/getstarted/index_cn.rst | 2 +- doc/getstarted/index_en.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/getstarted/index_cn.rst b/doc/getstarted/index_cn.rst index 0cb27f802c4..aa418c657a4 100644 --- a/doc/getstarted/index_cn.rst +++ b/doc/getstarted/index_cn.rst @@ -7,4 +7,4 @@ build_and_install/index_cn.rst concepts/use_concepts_cn.rst -- `深度学习入门课程 `_ +- `深度学习入门课程 `_ diff --git a/doc/getstarted/index_en.rst b/doc/getstarted/index_en.rst index 9f771e93e8b..be3253e3d41 100644 --- a/doc/getstarted/index_en.rst +++ b/doc/getstarted/index_en.rst @@ -6,4 +6,4 @@ GET STARTED build_and_install/index_en.rst -- `Deep Learning 101 `_ +- `Deep Learning 101 `_ -- GitLab From 18435f2a738b2baec680eea6fc2648dd094e5c87 Mon Sep 17 00:00:00 2001 From: xzl Date: Fri, 2 Jun 2017 16:31:49 +0800 Subject: [PATCH 0522/3256] modify the pruning from reading mask to specify sparsity_ratio --- paddle/parameter/ParameterUpdaterHook.cpp | 130 ++---------------- proto/ParameterConfig.proto | 3 +- python/paddle/trainer/config_parser.py | 9 +- python/paddle/trainer_config_helpers/attrs.py | 14 +- 4 files changed, 17 insertions(+), 139 deletions(-) diff --git a/paddle/parameter/ParameterUpdaterHook.cpp b/paddle/parameter/ParameterUpdaterHook.cpp index 76cc3ecad14..e29494868bc 100644 --- a/paddle/parameter/ParameterUpdaterHook.cpp +++ b/paddle/parameter/ParameterUpdaterHook.cpp @@ -19,130 +19,31 @@ limitations under the License. */ #include #include #include +#include #include "paddle/math/Vector.h" #include "paddle/parameter/Parameter.h" #include "paddle/utils/Flags.h" #include "paddle/utils/Util.h" -using std::vector; -using std::pair; - namespace paddle { /** * The static pruning hook - * - * Static means user load a mask map before training started. This map will - * define which link/weight between neural is disabled. + * Static means user specific a sparsity_ratio map before training started. The + * network will + * hold the sparsity_ratio maximum numbers of parameters, and cut off the rest. */ -class StaticPruningHook : public IParameterUpdaterHook { -public: - /** - * The Mask Map Header. - * The map file started with this header. - * - * In Version 0, reset file will be: - * contains header.size bit, each bit means such weight is enabled or not. - * if bit is 1, then such weight is enabled. - * at end, the file will round to byte, and the low bits of end byte will be - * filled by zero. - * - */ - struct StaticMaskHeader { - uint32_t version; - size_t size; - } __attribute__((__packed__)); - - explicit StaticPruningHook(const std::string& mask_filename) : initCount_(0) { - bool ok = this->loadMaskFile(mask_filename); - if (!ok) { - LOG(WARNING) << "Fail to load mask file " << mask_filename - << " in current directory, searching in init_model_path"; - std::string combineMaskFilename = - path::join(FLAGS_init_model_path, mask_filename); - CHECK(this->loadMaskFile(combineMaskFilename)) - << "Cannot load " << mask_filename << " in ./" << mask_filename - << " and " << combineMaskFilename; - } - VLOG(3) << mask_filename << " mask size = " << this->mask_.size(); - } - void update(Parameter* para) { - updateThreadChecker_.check(); - auto& vec = para->getBuf(PARAMETER_GRADIENT); - if (vec) { - vec->dotMul(*maskVec_); - } - } - - void init(Parameter* para) { - size_t initCount = this->initCount_.fetch_add(1); - CHECK_EQ(initCount, 0UL) << "Currently the StaticPruningHook must invoke " - "in same ParamterUpdater"; - VLOG(3) << "Initialize Parameter " << para; - SetDevice device(para->getDeviceId()); - - auto maskVec = Vector::create(this->mask_.size(), false); - { // Initialize maskVec with float mask vector - real* dataPtr = maskVec->getData(); - size_t i = 0; - for (bool m : mask_) { - dataPtr[i++] = m ? 1.0 : 0.0; - } - } - - // Currently just use a mask vector for hack. - // @TODO(yuyang18): Implemented the mask operation in vector. - if (para->useGpu()) { - maskVec_ = Vector::create(this->mask_.size(), para->useGpu()); - maskVec_->copyFrom(*maskVec); - } else { - maskVec_ = maskVec; - } - - auto& vec = para->getBuf(PARAMETER_VALUE); - vec->dotMul(*maskVec_); - } - -private: - bool loadMaskFile(const std::string& mask_filename) { - std::ifstream fin; - fin.open(mask_filename); - if (fin.is_open()) { - StaticMaskHeader header; - fin.read(reinterpret_cast(&header), sizeof(StaticMaskHeader)); - CHECK_EQ(header.version, 0UL); - mask_.resize(header.size); - uint8_t buf; - for (size_t i = 0; i < header.size; ++i, buf <<= 1) { - if (i % 8 == 0) { - fin.read(reinterpret_cast(&buf), sizeof(uint8_t)); - } - mask_[i] = buf & 0x80; - } - fin.close(); - return true; - } else { - return false; - } - } - - SameThreadChecker updateThreadChecker_; - std::atomic initCount_; - VectorPtr maskVec_; - std::vector mask_; -}; - -class DynamicPruningHook : public IParameterUpdaterHook { +class StaticPruningHook : public IParameterUpdaterHook { public: - explicit DynamicPruningHook(const ParameterUpdaterHookConfig& hookConfig) + explicit StaticPruningHook(const ParameterUpdaterHookConfig& hookConfig) : initCount_(0) { sparsityRatio_ = hookConfig.sparsity_ratio(); } - static bool sortPairAscend(const pair& pair1, - const pair& pair2) { + static bool sortPairAscend(const std::pair& pair1, + const std::pair& pair2) { return pair1.first > pair2.first; } @@ -162,7 +63,7 @@ public: VectorPtr vecCpu = Vector::create(para->getSize(), false); vecCpu->copyFrom(*vec); - vector> param; + std::vector> param; for (size_t i = 0; i < para->getSize(); i++) param.push_back(std::make_pair(fabs(vecCpu->getData()[i]), i)); @@ -175,7 +76,7 @@ public: void init(Parameter* para) { generateMask(para); size_t initCount = this->initCount_.fetch_add(1); - CHECK_EQ(initCount, 0UL) << "Currently the DynamicPruningHook must invoke " + CHECK_EQ(initCount, 0UL) << "Currently the StaticPruningHook must invoke " "in same ParamterUpdater"; VLOG(3) << "Initialize Parameter " << para; SetDevice device(para->getDeviceId()); @@ -234,16 +135,9 @@ static WeakKVCache, static IParameterUpdaterHook* createImpl( const ParameterUpdaterHookConfig& config) { auto& type = config.type(); - if (type == "pruning_static") { - if (config.has_purning_mask_filename()) - return new StaticPruningHook(config.purning_mask_filename()); - else - LOG(FATAL) << "There must be mask_filename parameter for " << type - << " Hook"; - - } else if (type == "pruning") { + if (type == "pruning") { if (config.has_sparsity_ratio()) - return new DynamicPruningHook(config); + return new StaticPruningHook(config); else LOG(FATAL) << "There must be sparsity_ratio parameter for " << type << " Hook"; diff --git a/proto/ParameterConfig.proto b/proto/ParameterConfig.proto index 61f4b037cf0..53e3b94f031 100644 --- a/proto/ParameterConfig.proto +++ b/proto/ParameterConfig.proto @@ -26,8 +26,7 @@ enum ParameterInitStrategy { message ParameterUpdaterHookConfig { required string type = 1; - //hook type such as 'pruning', 'pruning_static' - optional string purning_mask_filename = 2; + //hook type such as 'pruning' optional double sparsity_ratio = 3; } diff --git a/python/paddle/trainer/config_parser.py b/python/paddle/trainer/config_parser.py index 3775375c9b7..bebb76d9847 100644 --- a/python/paddle/trainer/config_parser.py +++ b/python/paddle/trainer/config_parser.py @@ -3171,14 +3171,7 @@ def Layer(name, type, **xargs): @config_func def ParameterHook(type, **kwargs): - if type == 'pruning_static': - hook = ParameterUpdaterHookConfig() - hook.type = type - mask_filename = kwargs.get('mask_filename', None) - assert mask_filename is not None - hook.pruning_mask_filename = mask_filename - return hook - elif type == 'pruning': + if type == 'pruning': hook = ParameterUpdaterHookConfig() hook.type = type sparsity_ratio = kwargs.get('sparsity_ratio', None) diff --git a/python/paddle/trainer_config_helpers/attrs.py b/python/paddle/trainer_config_helpers/attrs.py index 011147a3685..a0ad8c44525 100644 --- a/python/paddle/trainer_config_helpers/attrs.py +++ b/python/paddle/trainer_config_helpers/attrs.py @@ -64,32 +64,24 @@ class HookAttribute(object): here paddle/parameter/ParameterUpdaterHook.cpp NOTE: IT IS A HIGH LEVEL USER INTERFACE. - :param type: Hook type, eg: 'pruning', 'pruning_static' + :param type: Hook type, eg: 'pruning' :type type: string - :param mask_file: Must be specified if hook type is 'pruning_static', - the network reads the mask from the file to determine which parameters should be cut off - :type mask_file: string - :param sparsity_ratio: Must be specified if hook type is 'pruning', the network will hold the sparsity_ratio maximum parameters, and cut off the rest. :type sparsity_ratio: float number between 0 and 1 """ - def __init__(self, type, mask_filename=None, sparsity_ratio=None): + def __init__(self, type, sparsity_ratio=None): self.type = type - self.mask_filename = mask_filename self.sparsity_ratio = sparsity_ratio assert is_compatible_with(self.sparsity_ratio, float), 'sparisity_ratio must be float type' assert self.sparsity_ratio <= 1 and self.sparsity_ratio >= 0, 'sparisity must be a flaot between [0, 1] ' def __call__(self): - return ParameterHook( - self.type, - mask_filename=self.mask_filename, - sparsity_ratio=self.sparsity_ratio) + return ParameterHook(self.type, sparsity_ratio=self.sparsity_ratio) class ParameterAttribute(object): -- GitLab From e73e5cd0231f7b0d7c1587b244fb6a25574a4ef0 Mon Sep 17 00:00:00 2001 From: Luo Tao Date: Fri, 2 Jun 2017 16:48:10 +0800 Subject: [PATCH 0523/3256] specify the sphinx version in travis-ci --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 865e21f046b..7bffc00ef1b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -47,7 +47,7 @@ before_install: - if [[ "$JOB" == "PRE_COMMIT" ]]; then sudo ln -s /usr/bin/clang-format-3.8 /usr/bin/clang-format; fi # Paddle is using protobuf 3.1 currently. Protobuf 3.2 breaks the compatibility. So we specify the python # protobuf version. - - pip install numpy wheel 'protobuf==3.1' sphinx recommonmark sphinx-rtd-theme==0.1.9 virtualenv pre-commit requests==2.9.2 LinkChecker + - pip install numpy wheel 'protobuf==3.1' sphinx==1.5.6 recommonmark sphinx-rtd-theme==0.1.9 virtualenv pre-commit requests==2.9.2 LinkChecker - | function timeout() { perl -e 'alarm shift; exec @ARGV' "$@"; } script: -- GitLab From 3c0aa0cc34368e5561a856e10df906f2c2efe68d Mon Sep 17 00:00:00 2001 From: hedaoyuan Date: Fri, 2 Jun 2017 16:59:52 +0800 Subject: [PATCH 0524/3256] Add GPU GemmConvFunction implementation --- paddle/function/ConvOp.h | 2 + paddle/function/ConvOpTest.cpp | 26 +++++--- paddle/function/GemmConvOp.cpp | 34 +++++------ paddle/function/GemmConvOp.h | 44 +++++++++++++ paddle/function/GemmConvOpGpu.cu | 93 ++++++++++++++++++++++++++++ paddle/function/GemmFunctor.h | 102 +++++++++++++++++++++++++++++++ 6 files changed, 274 insertions(+), 27 deletions(-) create mode 100644 paddle/function/GemmConvOp.h create mode 100644 paddle/function/GemmConvOpGpu.cu create mode 100644 paddle/function/GemmFunctor.h diff --git a/paddle/function/ConvOp.h b/paddle/function/ConvOp.h index 173ca228096..017d4e26f2b 100644 --- a/paddle/function/ConvOp.h +++ b/paddle/function/ConvOp.h @@ -12,6 +12,8 @@ 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 "Function.h" namespace paddle { diff --git a/paddle/function/ConvOpTest.cpp b/paddle/function/ConvOpTest.cpp index eb008480481..89626714133 100644 --- a/paddle/function/ConvOpTest.cpp +++ b/paddle/function/ConvOpTest.cpp @@ -19,8 +19,7 @@ limitations under the License. */ namespace paddle { -typedef Compare2Function Compare2CpuFunction; - +template class ConvolutionTest { public: ConvolutionTest(const std::string& conv1, @@ -50,13 +49,14 @@ public: std::vector paddings = {padding, padding}; std::vector strides = {stride, stride}; - Compare2CpuFunction test(conv1, - conv2, - FuncConfig() - .set("paddings", paddings) - .set("strides", strides) - .set("groups", (size_t)1) - .set("algo", algo)); + Compare2Function test( + conv1, + conv2, + FuncConfig() + .set("paddings", paddings) + .set("strides", strides) + .set("groups", (size_t)1) + .set("algo", algo)); TensorShape shape0{ batchSize, inputChannels, inputSize, inputSize}; @@ -79,7 +79,13 @@ public: }; TEST(Convolution, GEMM) { - ConvolutionTest test("NaiveConv-CPU", "GemmConv-CPU"); + ConvolutionTest test("NaiveConv-CPU", + "GemmConv-CPU"); +} + +TEST(Convolution, GEMM2) { + ConvolutionTest test("GemmConv-CPU", + "GemmConv-GPU"); } } // namespace paddle diff --git a/paddle/function/GemmConvOp.cpp b/paddle/function/GemmConvOp.cpp index b8e44cc60bc..6857fe74824 100644 --- a/paddle/function/GemmConvOp.cpp +++ b/paddle/function/GemmConvOp.cpp @@ -12,8 +12,8 @@ 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 "ConvOp.h" -#include "paddle/math/MathFunctions.h" +#include "GemmConvOp.h" +#include "GemmFunctor.h" #include "paddle/math/MemoryHandle.h" namespace paddle { @@ -24,7 +24,7 @@ namespace paddle { * output_height, output_width] */ template -class Im2ColFunctor { +class Im2ColFunctor { public: void operator()(const T* imData, int inputChannels, @@ -112,7 +112,8 @@ public: resizeBuffer(size); real* colData = reinterpret_cast(memory_->getBuf()); - Im2ColFunctor im2col; + Im2ColFunctor im2col; + GemmFunctor gemm; size_t inputOffset = (inputChannels / groups_) * inputHeight * inputWidth; size_t outputOffset = (outputChannels / groups_) * outputHeight * outputWidth; @@ -136,19 +137,17 @@ public: int M = outputChannels; int N = outputHeight * outputWidth; int K = inputChannels * filterHeight * filterWidth; - gemm(CblasNoTrans, - CblasNoTrans, - M, - N, - K, - 1.0f, - filterData + g * filterOffset, - K, - colData, - N, - 0.0f, - outputData + g * outputOffset, - N); + gemm(M, + N, + K, + 1.0f, + filterData + g * filterOffset, + K, + colData, + N, + 0.0f, + outputData + g * outputOffset, + N); inputData += inputChannels * inputHeight * inputWidth; outputData += outputChannels * outputHeight * outputWidth; } @@ -166,5 +165,6 @@ private: }; REGISTER_TYPED_FUNC(GemmConv, CPU, GemmConvFunction); +REGISTER_TYPED_FUNC(GemmConv, GPU, GemmConvFunction); } // namespace paddle diff --git a/paddle/function/GemmConvOp.h b/paddle/function/GemmConvOp.h new file mode 100644 index 00000000000..652a64afba4 --- /dev/null +++ b/paddle/function/GemmConvOp.h @@ -0,0 +1,44 @@ +/* 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 "ConvOp.h" + +namespace paddle { + +/* + * imData = [input_channels, input_height, input_width] + * colData = [input_channels, filter_height, filter_width, + * output_height, output_width] + */ +template +class Im2ColFunctor { +public: + void operator()(const T* imData, + int inputChannels, + int inputHeight, + int inputWidth, + int filterHeight, + int filterWidth, + int strideHeight, + int strideWidth, + int paddingHeight, + int paddingWidth, + int outputHeight, + int outputWidth, + T* colData); +}; + +} // namespace paddle diff --git a/paddle/function/GemmConvOpGpu.cu b/paddle/function/GemmConvOpGpu.cu new file mode 100644 index 00000000000..06b9904261c --- /dev/null +++ b/paddle/function/GemmConvOpGpu.cu @@ -0,0 +1,93 @@ +/* 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 "ConvOp.h" +#include "GemmConvOp.h" + +namespace paddle { + +template +__global__ +void im2col(const T* data_im, int numOuts, int height, int width, + int blockH, int blockW, + int strideH, int strideW, + int paddingH, int paddingW, + int height_col, int width_col, + T* data_col) { + int index = + (blockIdx.x * gridDim.y + blockIdx.y) * blockDim.x + threadIdx.x; + if (index < numOuts) { + int w_out = index % width_col; + index /= width_col; + int h_out = index % height_col; + int channel_in = index / height_col; + int channel_out = channel_in * blockH * blockW; + int h_in = h_out * strideH; + int w_in = w_out * strideW; + + data_col += (channel_out * height_col + h_out) * width_col + w_out; + for (int i = 0; i < blockH; ++i) { + for (int j = 0; j < blockW; ++j) { + int rIdx = int(h_in+i); + int cIdx = int(w_in+j); + if ((rIdx-(int)paddingH) >= (int)height || + (rIdx-(int)paddingH) < 0 || + (cIdx-(int)paddingW) >= (int)width || + (cIdx-(int)paddingW) < 0) { + *data_col = 0; + } else { + rIdx = rIdx + channel_in*height - paddingH; + cIdx = cIdx - paddingW; + *data_col = data_im[rIdx* width + cIdx]; + } + data_col += height_col * width_col; + } + } + } +} + +template +class Im2ColFunctor { +public: + void operator()(const T* imData, + int inputChannels, + int inputHeight, + int inputWidth, + int filterHeight, + int filterWidth, + int strideHeight, + int strideWidth, + int paddingHeight, + int paddingWidth, + int outputHeight, + int outputWidth, + T* colData) { + int numKernels = inputChannels * outputHeight * outputWidth; + int blocks = (numKernels + 1024 -1) / 1024; + int blockX = 512; + int blockY = (blocks + 512 - 1) / 512; + dim3 threads(1024, 1); + dim3 grid(blockX, blockY); + im2col<<< grid, threads, 0, STREAM_DEFAULT >>> + (imData, numKernels, inputHeight, inputWidth, filterHeight, filterWidth, + strideHeight, strideWidth, paddingHeight, paddingWidth, + outputHeight, outputWidth, colData); + CHECK_SYNC("Im2ColFunctor GPU failed"); + } +}; + +template class Im2ColFunctor; +template class Im2ColFunctor; + +} // namespace paddle diff --git a/paddle/function/GemmFunctor.h b/paddle/function/GemmFunctor.h new file mode 100644 index 00000000000..5fb2f8a6d9e --- /dev/null +++ b/paddle/function/GemmFunctor.h @@ -0,0 +1,102 @@ +/* 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/math/MathFunctions.h" + +namespace paddle { + +// TODO(hedaoyuan): Since the hl_matrix_mul interface does not conform to the +// cblas_dgemm interface's parameter format, it is necessary to introduce +// GemmFunctor as a new interface. Later, when considering the implementation +// of MatMulFunction, we need to consider the reconstruction of hl_matrix_mul +// interface. +template +class GemmFunctor { +public: + void operator()(const int M, + const int N, + const int K, + const T alpha, + const T* A, + const int lda, + const T* B, + const int ldb, + const T beta, + T* C, + const int ldc); +}; + +template +class GemmFunctor { +public: + void operator()(const int M, + const int N, + const int K, + const T alpha, + const T* A, + const int lda, + const T* B, + const int ldb, + const T beta, + T* C, + const int ldc) { + gemm(CblasNoTrans, + CblasNoTrans, + M, + N, + K, + alpha, + A, + lda, + B, + ldb, + beta, + C, + ldc); + } +}; + +template +class GemmFunctor { +public: + void operator()(const int M, + const int N, + const int K, + const T alpha, + const T* A, + const int lda, + const T* B, + const int ldb, + const T beta, + T* C, + const int ldc) { + hl_matrix_mul((T*)A, + HPPL_OP_N, + (T*)B, + HPPL_OP_N, + C, + M, + N, + K, + alpha, + beta, + lda, + ldb, + ldc); + } +}; + +} // namespace paddle -- GitLab From c70d3e1af85f960555fcdcd5341453e277c326ec Mon Sep 17 00:00:00 2001 From: hedaoyuan Date: Fri, 2 Jun 2017 18:17:20 +0800 Subject: [PATCH 0525/3256] Some bug fix --- paddle/function/GemmConvOp.cpp | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/paddle/function/GemmConvOp.cpp b/paddle/function/GemmConvOp.cpp index 6857fe74824..e7a93ae676f 100644 --- a/paddle/function/GemmConvOp.cpp +++ b/paddle/function/GemmConvOp.cpp @@ -96,7 +96,7 @@ public: size_t inputHeight = inputs[0].shape()[2]; size_t inputWidth = inputs[0].shape()[3]; size_t filterHeight = inputs[1].shape()[2]; - size_t filterWidth = inputs[1].shape()[2]; + size_t filterWidth = inputs[1].shape()[3]; size_t outputChannels = outputs[0].shape()[1]; size_t outputHeight = outputs[0].shape()[2]; size_t outputWidth = outputs[0].shape()[3]; @@ -148,23 +148,29 @@ public: 0.0f, outputData + g * outputOffset, N); - inputData += inputChannels * inputHeight * inputWidth; - outputData += outputChannels * outputHeight * outputWidth; } + inputData += inputChannels * inputHeight * inputWidth; + outputData += outputChannels * outputHeight * outputWidth; } } void resizeBuffer(size_t newSize) { if (!memory_ || newSize * sizeof(real) > memory_->getAllocSize()) { - memory_ = std::make_shared(newSize * sizeof(real)); + if (Device == DEVICE_TYPE_CPU) { + memory_ = std::make_shared(newSize * sizeof(real)); + } else { + memory_ = std::make_shared(newSize * sizeof(real)); + } } } private: - CpuMemHandlePtr memory_; + MemoryHandlePtr memory_; }; REGISTER_TYPED_FUNC(GemmConv, CPU, GemmConvFunction); +#ifndef PADDLE_ONLY_CPU REGISTER_TYPED_FUNC(GemmConv, GPU, GemmConvFunction); +#endif } // namespace paddle -- GitLab From bfe8ecfc31901d21ce9d4bc321a69e8e2acb34dd Mon Sep 17 00:00:00 2001 From: Liu Yiqun Date: Thu, 1 Jun 2017 12:19:32 +0000 Subject: [PATCH 0526/3256] Fix blanks. --- python/paddle/trainer_config_helpers/layers.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/python/paddle/trainer_config_helpers/layers.py b/python/paddle/trainer_config_helpers/layers.py index 1564be10ac7..8a4fe178dc3 100755 --- a/python/paddle/trainer_config_helpers/layers.py +++ b/python/paddle/trainer_config_helpers/layers.py @@ -2915,11 +2915,11 @@ def memory(name, to specify the layer needs to be remembered as the following: .. code-block:: python + mem = memory(size=256) state = fc_layer(input=mem, size=256) mem.set_input(mem) - :param name: the name of the layer which this memory remembers. If name is None, user should call set_input() to specify the name of the layer which this memory remembers. @@ -3403,7 +3403,7 @@ def recurrent_group(step, else, for training or testing, one of the input type must be LayerOutput. - : type is_generating: bool + :type is_generating: bool :return: LayerOutput object. :rtype: LayerOutput @@ -3810,7 +3810,7 @@ def mse_cost(input, label, weight=None, name=None, coeff=1.0, layer_attr=None): .. math:: - \frac{1}{N}\sum_{i=1}^N(t_i-y_i)^2 + \\frac{1}{N}\sum_{i=1}^N(t_i-y_i)^2 :param name: layer name. :type name: basestring -- GitLab From 3408b4b2f409a5a8191248c7c17e1c882779de27 Mon Sep 17 00:00:00 2001 From: hedaoyuan Date: Fri, 2 Jun 2017 19:52:08 +0800 Subject: [PATCH 0527/3256] Bug fix --- paddle/function/ConvOp.h | 2 +- paddle/function/ConvOpTest.cpp | 2 ++ paddle/function/GemmConvOp.cpp | 6 ++---- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/paddle/function/ConvOp.h b/paddle/function/ConvOp.h index 017d4e26f2b..14c20b74f20 100644 --- a/paddle/function/ConvOp.h +++ b/paddle/function/ConvOp.h @@ -58,7 +58,7 @@ public: CHECK_EQ(outputs[0].shape().ndims(), (size_t)4); CHECK(inputs[0].shape()[0] == outputs[0].shape()[0]); - CHECK(inputs[0].shape()[1] == inputs[1].shape()[1]); + CHECK(inputs[0].shape()[1] / groups_ == inputs[1].shape()[1]); CHECK(outputs[0].shape()[1] == inputs[1].shape()[0]); } diff --git a/paddle/function/ConvOpTest.cpp b/paddle/function/ConvOpTest.cpp index 89626714133..d9de2114488 100644 --- a/paddle/function/ConvOpTest.cpp +++ b/paddle/function/ConvOpTest.cpp @@ -83,9 +83,11 @@ TEST(Convolution, GEMM) { "GemmConv-CPU"); } +#ifndef PADDLE_ONLY_CPU TEST(Convolution, GEMM2) { ConvolutionTest test("GemmConv-CPU", "GemmConv-GPU"); } +#endif } // namespace paddle diff --git a/paddle/function/GemmConvOp.cpp b/paddle/function/GemmConvOp.cpp index e7a93ae676f..78aa8f14f34 100644 --- a/paddle/function/GemmConvOp.cpp +++ b/paddle/function/GemmConvOp.cpp @@ -101,8 +101,6 @@ public: size_t outputHeight = outputs[0].shape()[2]; size_t outputWidth = outputs[0].shape()[3]; - CHECK_EQ(inputChannels / groups_, inputs[1].shape()[1]); - real* inputData = inputs[0].data(); real* filterData = inputs[1].data(); real* outputData = outputs[0].data(); @@ -134,9 +132,9 @@ public: outputWidth, colData); - int M = outputChannels; + int M = outputChannels / groups_; int N = outputHeight * outputWidth; - int K = inputChannels * filterHeight * filterWidth; + int K = inputChannels / groups_ * filterHeight * filterWidth; gemm(M, N, K, -- GitLab From 02fecae2553cc7d2908e0cdadddbf04bd814d5f5 Mon Sep 17 00:00:00 2001 From: liaogang Date: Fri, 2 Jun 2017 20:04:55 +0800 Subject: [PATCH 0528/3256] update docker version --- .../build_and_install/docker_install_cn.rst | 16 ++++++++-------- .../build_and_install/docker_install_en.rst | 18 +++++++++--------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/doc/getstarted/build_and_install/docker_install_cn.rst b/doc/getstarted/build_and_install/docker_install_cn.rst index da2d4234658..87c286a1af7 100644 --- a/doc/getstarted/build_and_install/docker_install_cn.rst +++ b/doc/getstarted/build_and_install/docker_install_cn.rst @@ -12,13 +12,13 @@ PaddlePaddle需要的所有编译工具。把编译出来的PaddlePaddle也打 像,称为生产镜像,里面涵盖了PaddlePaddle运行所需的所有环境。每次 PaddlePaddle发布新版本的时候都会发布对应版本的生产镜像以及开发镜像。运 行镜像包括纯CPU版本和GPU版本以及其对应的非AVX版本。我们会在 -`dockerhub.com `_ 提供最新 +`dockerhub.com `_ 提供最新 的Docker镜像,可以在"tags"标签下找到最新的Paddle镜像版本。为了方便在国 内的开发者下载Docker镜像,我们提供了国内的镜像服务器供大家使用。如果您 在国内,请把文档里命令中的paddlepaddle/paddle替换成 docker.paddlepaddle.org/paddle。 -1. 开发镜像::code:`paddlepaddle/paddle:-dev` +1. 开发镜像::code:`paddlepaddle/paddle:0.10.0-dev` 这个镜像包含了Paddle相关的开发工具以及编译和运行环境。用户可以使用开发镜像代替配置本地环境,完成开发,编译,发布, 文档编写等工作。由于不同的Paddle的版本可能需要不同的依赖和工具,所以如果需要自行配置开发环境需要考虑版本的因素。 @@ -37,13 +37,13 @@ docker.paddlepaddle.org/paddle。 .. code-block:: bash - docker run -it --rm paddlepaddle/paddle:-dev /bin/bash + docker run -it --rm paddlepaddle/paddle:0.10.0-dev /bin/bash 或者,可以以后台进程方式运行容器: .. code-block:: bash - docker run -d -p 2202:22 -p 8888:8888 paddledev/paddle:-dev + docker run -d -p 2202:22 -p 8888:8888 paddledev/paddle:0.10.0-dev 然后用密码 :code:`root` SSH进入容器: @@ -73,7 +73,7 @@ docker.paddlepaddle.org/paddle。 .. code-block:: bash - nvidia-docker run -it --rm paddledev/paddle:0.10.0rc1-gpu /bin/bash + nvidia-docker run -it --rm paddledev/paddle:0.10.0-gpu /bin/bash 注意: 如果使用nvidia-docker存在问题,你也许可以尝试更老的方法,具体如下,但是我们并不推荐这种方法。: @@ -81,7 +81,7 @@ docker.paddlepaddle.org/paddle。 export CUDA_SO="$(\ls /usr/lib64/libcuda* | xargs -I{} echo '-v {}:{}') $(\ls /usr/lib64/libnvidia* | xargs -I{} echo '-v {}:{}')" export DEVICES=$(\ls /dev/nvidia* | xargs -I{} echo '--device {}:{}') - docker run ${CUDA_SO} ${DEVICES} -it paddledev/paddle:-gpu + docker run ${CUDA_SO} ${DEVICES} -it paddledev/paddle:0.10.0-gpu 3. 运行以及发布您的AI程序 @@ -98,7 +98,7 @@ docker.paddlepaddle.org/paddle。 nvidia-docker run -it -v $PWD:/work paddle /work/a.py - 这里`a.py`包含的所有依赖假设都可以在Paddle的运行容器中。如果需要包含更多的依赖、或者需要发布您的应用的镜像,可以编写`Dockerfile`使用`FROM paddledev/paddle:` + 这里`a.py`包含的所有依赖假设都可以在Paddle的运行容器中。如果需要包含更多的依赖、或者需要发布您的应用的镜像,可以编写`Dockerfile`使用`FROM paddledev/paddle:0.10.0` 创建和发布自己的AI程序镜像。 运行PaddlePaddle Book @@ -177,7 +177,7 @@ Paddle的Docker开发镜像带有一个通过 `woboq code browser .. code-block:: bash - docker run -d --name paddle-cpu-doc paddle:-dev + docker run -d --name paddle-cpu-doc paddle:0.10.0-dev docker run -d --volumes-from paddle-cpu-doc -p 8088:80 nginx 接着我们就能够打开浏览器在 http://localhost:8088/paddle/ 浏览代码。 diff --git a/doc/getstarted/build_and_install/docker_install_en.rst b/doc/getstarted/build_and_install/docker_install_en.rst index 03df4975060..b6fd3329b27 100644 --- a/doc/getstarted/build_and_install/docker_install_en.rst +++ b/doc/getstarted/build_and_install/docker_install_en.rst @@ -23,7 +23,7 @@ Docker is simple as long as we understand a few basic concepts: .. code-block:: bash - docker pull paddlepaddle/paddle:0.10.0rc2 + docker pull paddlepaddle/paddle:0.10.0 to download a Docker image, paddlepaddle/paddle in this example, from Dockerhub.com. @@ -35,7 +35,7 @@ Docker is simple as long as we understand a few basic concepts: .. code-block:: bash - docker run paddlepaddle/paddle:0.10.0rc2 + docker run paddlepaddle/paddle:0.10.0 to start a container to run a Docker image, paddlepaddle/paddle in this example. @@ -62,7 +62,7 @@ of PaddlePaddle, we release both of them. Production image includes CPU-only version and a CUDA GPU version and their no-AVX versions. We put the docker images on `dockerhub.com -`_. You can find the +`_. You can find the latest versions under "tags" tab at dockerhub.com. If you are in China, you can use our Docker image registry mirror to speed up the download process. To use it, please replace all paddlepaddle/paddle in @@ -89,7 +89,7 @@ the commands to docker.paddlepaddle.org/paddle. .. code-block:: bash - docker run -it --rm paddlepaddle/paddle:0.10.0rc2 /bin/bash + docker run -it --rm paddlepaddle/paddle:0.10.0 /bin/bash Above method work with the GPU image too -- the recommended way is using `nvidia-docker `_. @@ -101,7 +101,7 @@ the commands to docker.paddlepaddle.org/paddle. .. code-block:: bash - nvidia-docker run -it --rm paddlepaddle/paddle:0.10.0rc2-gpu /bin/bash + nvidia-docker run -it --rm paddlepaddle/paddle:0.10.0-gpu /bin/bash 2. development image :code:`paddlepaddle/paddle:-dev` @@ -149,13 +149,13 @@ Run the program using docker: .. code-block:: bash - docker run --rm -v ~/workspace:/workspace paddlepaddle/paddle:0.10.0rc2 python /workspace/example.py + docker run --rm -v ~/workspace:/workspace paddlepaddle/paddle:0.10.0 python /workspace/example.py Or if you are using GPU for training: .. code-block:: bash - nvidia-docker run --rm -v ~/workspace:/workspace paddlepaddle/paddle:0.10.0rc2-gpu python /workspace/example.py + nvidia-docker run --rm -v ~/workspace:/workspace paddlepaddle/paddle:0.10.0-gpu python /workspace/example.py Above commands will start a docker container by running :code:`python /workspace/example.py`. It will stop once :code:`python @@ -166,7 +166,7 @@ run PaddlePaddle program interactively: .. code-block:: bash - docker run -it -v ~/workspace:/workspace paddlepaddle/paddle:0.10.0rc2 /bin/bash + docker run -it -v ~/workspace:/workspace paddlepaddle/paddle:0.10.0 /bin/bash # now we are inside docker container cd /workspace python example.py @@ -175,7 +175,7 @@ Running with GPU is identical: .. code-block:: bash - nvidia-docker run -it -v ~/workspace:/workspace paddlepaddle/paddle:0.10.0rc2-gpu /bin/bash + nvidia-docker run -it -v ~/workspace:/workspace paddlepaddle/paddle:0.10.0-gpu /bin/bash # now we are inside docker container cd /workspace python example.py -- GitLab From cb9c588541bcbadda691ff70194812dd6bfabdf8 Mon Sep 17 00:00:00 2001 From: xuwei06 Date: Fri, 2 Jun 2017 10:09:07 -0700 Subject: [PATCH 0529/3256] Add GLOBAL option for external library targets So that they can be found when paddle is built as a add_subdirectory project in another project. --- cmake/external/gflags.cmake | 8 ++++---- cmake/external/glog.cmake | 2 +- cmake/external/gtest.cmake | 10 +++++----- cmake/external/warpctc.cmake | 8 ++++---- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/cmake/external/gflags.cmake b/cmake/external/gflags.cmake index 30027a336c0..a0d0a892c4b 100644 --- a/cmake/external/gflags.cmake +++ b/cmake/external/gflags.cmake @@ -1,11 +1,11 @@ # 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. @@ -44,7 +44,7 @@ ExternalProject_Add( -DCMAKE_BUILD_TYPE:STRING=Release ) -ADD_LIBRARY(gflags STATIC IMPORTED) +ADD_LIBRARY(gflags STATIC IMPORTED GLOBAL) SET_PROPERTY(TARGET gflags PROPERTY IMPORTED_LOCATION ${GFLAGS_LIBRARIES}) ADD_DEPENDENCIES(gflags extern_gflags) diff --git a/cmake/external/glog.cmake b/cmake/external/glog.cmake index fa9a5092877..b70e94a170f 100644 --- a/cmake/external/glog.cmake +++ b/cmake/external/glog.cmake @@ -48,7 +48,7 @@ ExternalProject_Add( -DCMAKE_BUILD_TYPE:STRING=Release ) -ADD_LIBRARY(glog STATIC IMPORTED) +ADD_LIBRARY(glog STATIC IMPORTED GLOBAL) SET_PROPERTY(TARGET glog PROPERTY IMPORTED_LOCATION ${GLOG_LIBRARIES}) ADD_DEPENDENCIES(glog extern_glog) diff --git a/cmake/external/gtest.cmake b/cmake/external/gtest.cmake index 386204dc37d..77e06e983e9 100644 --- a/cmake/external/gtest.cmake +++ b/cmake/external/gtest.cmake @@ -1,11 +1,11 @@ # 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. @@ -56,11 +56,11 @@ IF(WITH_TESTING) -DCMAKE_BUILD_TYPE:STRING=Release ) - ADD_LIBRARY(gtest STATIC IMPORTED) + ADD_LIBRARY(gtest STATIC IMPORTED GLOBAL) SET_PROPERTY(TARGET gtest PROPERTY IMPORTED_LOCATION ${GTEST_LIBRARIES}) ADD_DEPENDENCIES(gtest extern_gtest) - ADD_LIBRARY(gtest_main STATIC IMPORTED) + ADD_LIBRARY(gtest_main STATIC IMPORTED GLOBAL) SET_PROPERTY(TARGET gtest_main PROPERTY IMPORTED_LOCATION ${GTEST_MAIN_LIBRARIES}) ADD_DEPENDENCIES(gtest_main extern_gtest) diff --git a/cmake/external/warpctc.cmake b/cmake/external/warpctc.cmake index 367d5b98c7f..2d7daed9bcd 100644 --- a/cmake/external/warpctc.cmake +++ b/cmake/external/warpctc.cmake @@ -1,11 +1,11 @@ # 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. @@ -65,7 +65,7 @@ ExternalProject_Add( -DCMAKE_INSTALL_PREFIX:PATH=${WARPCTC_INSTALL_DIR} ) -ADD_LIBRARY(warpctc STATIC IMPORTED) +ADD_LIBRARY(warpctc STATIC IMPORTED GLOBAL) SET_PROPERTY(TARGET warpctc PROPERTY IMPORTED_LOCATION ${WARPCTC_LIBRARIES}) ADD_DEPENDENCIES(warpctc extern_warpctc) -- GitLab From d0f1890db33152a816d2f52c1855af84d704c51f Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Sat, 3 Jun 2017 00:00:06 +0000 Subject: [PATCH 0530/3256] move recordio to github.com/PaddlePaddle/recordio --- go/cmd/master/master.go | 2 +- go/master/service.go | 2 +- go/recordio/README.md | 39 ------ go/recordio/c/CMakeLists.txt | 13 -- go/recordio/c/crecordio.go | 116 ----------------- go/recordio/c/register.go | 61 --------- go/recordio/c/test/CMakeLists.txt | 8 -- go/recordio/c/test/test.c | 56 -------- go/recordio/chunk.go | 181 -------------------------- go/recordio/header.go | 59 --------- go/recordio/range_scanner.go | 140 -------------------- go/recordio/recordio_internal_test.go | 90 ------------- go/recordio/recordio_test.go | 81 ------------ go/recordio/scanner.go | 140 -------------------- go/recordio/writer.go | 60 --------- 15 files changed, 2 insertions(+), 1046 deletions(-) delete mode 100644 go/recordio/README.md delete mode 100644 go/recordio/c/CMakeLists.txt delete mode 100644 go/recordio/c/crecordio.go delete mode 100644 go/recordio/c/register.go delete mode 100644 go/recordio/c/test/CMakeLists.txt delete mode 100644 go/recordio/c/test/test.c delete mode 100644 go/recordio/chunk.go delete mode 100644 go/recordio/header.go delete mode 100644 go/recordio/range_scanner.go delete mode 100644 go/recordio/recordio_internal_test.go delete mode 100644 go/recordio/recordio_test.go delete mode 100644 go/recordio/scanner.go delete mode 100644 go/recordio/writer.go diff --git a/go/cmd/master/master.go b/go/cmd/master/master.go index cc6e45049a3..d1f3d7d76c4 100644 --- a/go/cmd/master/master.go +++ b/go/cmd/master/master.go @@ -14,7 +14,7 @@ import ( "github.com/namsral/flag" "github.com/PaddlePaddle/Paddle/go/master" - "github.com/PaddlePaddle/Paddle/go/recordio" + "github.com/PaddlePaddle/recordio" ) func main() { diff --git a/go/master/service.go b/go/master/service.go index 50e646b01f0..ab17a62f385 100644 --- a/go/master/service.go +++ b/go/master/service.go @@ -6,7 +6,7 @@ import ( "sync" "time" - "github.com/PaddlePaddle/Paddle/go/recordio" + "github.com/PaddlePaddle/recordio" ) const ( diff --git a/go/recordio/README.md b/go/recordio/README.md deleted file mode 100644 index 50e7e954764..00000000000 --- a/go/recordio/README.md +++ /dev/null @@ -1,39 +0,0 @@ -# RecordIO - -## Write - -```go -f, e := os.Create("a_file.recordio") -w := recordio.NewWriter(f) -w.Write([]byte("Hello")) -w.Write([]byte("World!")) -w.Close() -f.Close() -``` - -## Read - -1. Load chunk index: - - ```go - f, e := os.Open("a_file.recordio") - idx, e := recordio.LoadIndex(f) - fmt.Println("Total records: ", idx.Len()) - f.Close() - ``` - -2. Create one or more scanner to read a range of records. The - following example reads the range - [1, 3), i.e., the second and the third records: - - ```go - f, e := os.Open("a_file.recordio") - s := recrodio.NewScanner(f, idx, 1, 3) - for s.Scan() { - fmt.Println(string(s.Record())) - } - if s.Err() != nil { - log.Fatalf("Something wrong with scanning: %v", e) - } - f.Close() - ``` diff --git a/go/recordio/c/CMakeLists.txt b/go/recordio/c/CMakeLists.txt deleted file mode 100644 index c300c091f87..00000000000 --- a/go/recordio/c/CMakeLists.txt +++ /dev/null @@ -1,13 +0,0 @@ -cmake_minimum_required(VERSION 3.0) - -get_filename_component(PARENT_DIR ${CMAKE_CURRENT_SOURCE_DIR} DIRECTORY) -get_filename_component(PARENT_DIR ${PARENT_DIR} DIRECTORY) -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${PARENT_DIR}/cmake") - -project(cxx_go C Go) - -include(golang) -include(flags) - -go_library(recordio STATIC) -add_subdirectory(test) diff --git a/go/recordio/c/crecordio.go b/go/recordio/c/crecordio.go deleted file mode 100644 index e5cc3029928..00000000000 --- a/go/recordio/c/crecordio.go +++ /dev/null @@ -1,116 +0,0 @@ -package main - -/* -#include - -typedef int reader; -typedef int writer; -*/ -import "C" - -import ( - "log" - "os" - "strings" - "unsafe" - - "github.com/PaddlePaddle/Paddle/go/recordio" -) - -var nullPtr = unsafe.Pointer(uintptr(0)) - -type writer struct { - w *recordio.Writer - f *os.File -} - -type reader struct { - scanner *recordio.Scanner -} - -func cArrayToSlice(p unsafe.Pointer, len int) []byte { - if p == nullPtr { - return nil - } - - // create a Go clice backed by a C array, reference: - // https://github.com/golang/go/wiki/cgo#turning-c-arrays-into-go-slices - // - // Go garbage collector will not interact with this data, need - // to be freed properly. - return (*[1 << 30]byte)(p)[:len:len] -} - -//export create_recordio_writer -func create_recordio_writer(path *C.char) C.writer { - p := C.GoString(path) - f, err := os.Create(p) - if err != nil { - log.Println(err) - return -1 - } - - w := recordio.NewWriter(f, -1, -1) - writer := &writer{f: f, w: w} - return addWriter(writer) -} - -//export recordio_write -func recordio_write(writer C.writer, buf *C.uchar, size C.int) C.int { - w := getWriter(writer) - b := cArrayToSlice(unsafe.Pointer(buf), int(size)) - c, err := w.w.Write(b) - if err != nil { - log.Println(err) - return -1 - } - - return C.int(c) -} - -//export release_recordio_writer -func release_recordio_writer(writer C.writer) { - w := removeWriter(writer) - w.w.Close() - w.f.Close() -} - -//export create_recordio_reader -func create_recordio_reader(path *C.char) C.reader { - p := C.GoString(path) - s, err := recordio.NewScanner(strings.Split(p, ",")...) - if err != nil { - log.Println(err) - return -1 - } - - r := &reader{scanner: s} - return addReader(r) -} - -//export recordio_read -func recordio_read(reader C.reader, record **C.uchar) C.int { - r := getReader(reader) - if r.scanner.Scan() { - buf := r.scanner.Record() - if len(buf) == 0 { - *record = (*C.uchar)(nullPtr) - return 0 - } - - size := C.int(len(buf)) - *record = (*C.uchar)(C.malloc(C.size_t(len(buf)))) - C.memcpy(unsafe.Pointer(*record), unsafe.Pointer(&buf[0]), C.size_t(len(buf))) - return size - } - - return -1 -} - -//export release_recordio_reader -func release_recordio_reader(reader C.reader) { - r := removeReader(reader) - r.scanner.Close() -} - -func main() {} // Required but ignored diff --git a/go/recordio/c/register.go b/go/recordio/c/register.go deleted file mode 100644 index 61dfdbd4ab6..00000000000 --- a/go/recordio/c/register.go +++ /dev/null @@ -1,61 +0,0 @@ -package main - -/* -typedef int reader; -typedef int writer; -*/ -import "C" - -import "sync" - -var mu sync.Mutex -var handleMap = make(map[C.reader]*reader) -var curHandle C.reader -var writerMap = make(map[C.writer]*writer) -var curWriterHandle C.writer - -func addReader(r *reader) C.reader { - mu.Lock() - defer mu.Unlock() - reader := curHandle - curHandle++ - handleMap[reader] = r - return reader -} - -func getReader(reader C.reader) *reader { - mu.Lock() - defer mu.Unlock() - return handleMap[reader] -} - -func removeReader(reader C.reader) *reader { - mu.Lock() - defer mu.Unlock() - r := handleMap[reader] - delete(handleMap, reader) - return r -} - -func addWriter(w *writer) C.writer { - mu.Lock() - defer mu.Unlock() - writer := curWriterHandle - curWriterHandle++ - writerMap[writer] = w - return writer -} - -func getWriter(writer C.writer) *writer { - mu.Lock() - defer mu.Unlock() - return writerMap[writer] -} - -func removeWriter(writer C.writer) *writer { - mu.Lock() - defer mu.Unlock() - w := writerMap[writer] - delete(writerMap, writer) - return w -} diff --git a/go/recordio/c/test/CMakeLists.txt b/go/recordio/c/test/CMakeLists.txt deleted file mode 100644 index bac1006ae12..00000000000 --- a/go/recordio/c/test/CMakeLists.txt +++ /dev/null @@ -1,8 +0,0 @@ -cmake_minimum_required(VERSION 3.0) - -include_directories(${CMAKE_BINARY_DIR}) - -add_executable(recordio_test test.c) -add_dependencies(recordio_test recordio) -set (CMAKE_EXE_LINKER_FLAGS "-pthread") -target_link_libraries(recordio_test ${CMAKE_BINARY_DIR}/librecordio.a) diff --git a/go/recordio/c/test/test.c b/go/recordio/c/test/test.c deleted file mode 100644 index b25536a9d76..00000000000 --- a/go/recordio/c/test/test.c +++ /dev/null @@ -1,56 +0,0 @@ -#include -#include - -#include "librecordio.h" - -void fail() { - // TODO(helin): fix: gtest using cmake is not working, using this - // hacky way for now. - printf("test failed.\n"); - exit(-1); -} - -int main() { - writer w = create_recordio_writer("/tmp/test_recordio_0"); - recordio_write(w, "hello", 6); - recordio_write(w, "hi", 3); - release_recordio_writer(w); - - w = create_recordio_writer("/tmp/test_recordio_1"); - recordio_write(w, "dog", 4); - recordio_write(w, "cat", 4); - release_recordio_writer(w); - - reader r = create_recordio_reader("/tmp/test_recordio_*"); - unsigned char* item = NULL; - int size = recordio_read(r, &item); - if (strcmp(item, "hello") || size != 6) { - fail(); - } - free(item); - - size = recordio_read(r, &item); - if (strcmp(item, "hi") || size != 3) { - fail(); - } - free(item); - - size = recordio_read(r, &item); - if (strcmp(item, "dog") || size != 4) { - fail(); - } - free(item); - - size = recordio_read(r, &item); - if (strcmp(item, "cat") || size != 4) { - fail(); - } - free(item); - - size = recordio_read(r, &item); - if (size != -1) { - fail(); - } - - release_recordio_reader(r); -} diff --git a/go/recordio/chunk.go b/go/recordio/chunk.go deleted file mode 100644 index 4e983ab72bd..00000000000 --- a/go/recordio/chunk.go +++ /dev/null @@ -1,181 +0,0 @@ -package recordio - -import ( - "bytes" - "compress/gzip" - "encoding/binary" - "fmt" - "hash/crc32" - "io" - - "github.com/golang/snappy" -) - -// A Chunk contains the Header and optionally compressed records. To -// create a chunk, just use ch := &Chunk{}. -type Chunk struct { - records [][]byte - numBytes int // sum of record lengths. -} - -func (ch *Chunk) add(record []byte) { - ch.records = append(ch.records, record) - ch.numBytes += len(record) -} - -// dump the chunk into w, and clears the chunk and makes it ready for -// the next add invocation. -func (ch *Chunk) dump(w io.Writer, compressorIndex int) error { - // NOTE: don't check ch.numBytes instead, because empty - // records are allowed. - if len(ch.records) == 0 { - return nil - } - - // Write raw records and their lengths into data buffer. - var data bytes.Buffer - - for _, r := range ch.records { - var rs [4]byte - binary.LittleEndian.PutUint32(rs[:], uint32(len(r))) - - if _, e := data.Write(rs[:]); e != nil { - return fmt.Errorf("Failed to write record length: %v", e) - } - - if _, e := data.Write(r); e != nil { - return fmt.Errorf("Failed to write record: %v", e) - } - } - - compressed, e := compressData(&data, compressorIndex) - if e != nil { - return e - } - - // Write chunk header and compressed data. - hdr := &Header{ - checkSum: crc32.ChecksumIEEE(compressed.Bytes()), - compressor: uint32(compressorIndex), - compressedSize: uint32(compressed.Len()), - numRecords: uint32(len(ch.records)), - } - - if _, e := hdr.write(w); e != nil { - return fmt.Errorf("Failed to write chunk header: %v", e) - } - - if _, e := w.Write(compressed.Bytes()); e != nil { - return fmt.Errorf("Failed to write chunk data: %v", e) - } - - // Clear the current chunk. - ch.records = nil - ch.numBytes = 0 - - return nil -} - -type noopCompressor struct { - *bytes.Buffer -} - -func (c *noopCompressor) Close() error { - return nil -} - -func compressData(src io.Reader, compressorIndex int) (*bytes.Buffer, error) { - compressed := new(bytes.Buffer) - var compressor io.WriteCloser - - switch compressorIndex { - case NoCompression: - compressor = &noopCompressor{compressed} - case Snappy: - compressor = snappy.NewBufferedWriter(compressed) - case Gzip: - compressor = gzip.NewWriter(compressed) - default: - return nil, fmt.Errorf("Unknown compression algorithm: %d", compressorIndex) - } - - if _, e := io.Copy(compressor, src); e != nil { - return nil, fmt.Errorf("Failed to compress chunk data: %v", e) - } - compressor.Close() - - return compressed, nil -} - -// parse the specified chunk from r. -func parseChunk(r io.ReadSeeker, chunkOffset int64) (*Chunk, error) { - var e error - var hdr *Header - - if _, e = r.Seek(chunkOffset, io.SeekStart); e != nil { - return nil, fmt.Errorf("Failed to seek chunk: %v", e) - } - - hdr, e = parseHeader(r) - if e != nil { - return nil, fmt.Errorf("Failed to parse chunk header: %v", e) - } - - var buf bytes.Buffer - if _, e = io.CopyN(&buf, r, int64(hdr.compressedSize)); e != nil { - return nil, fmt.Errorf("Failed to read chunk data: %v", e) - } - - if hdr.checkSum != crc32.ChecksumIEEE(buf.Bytes()) { - return nil, fmt.Errorf("Checksum checking failed.") - } - - deflated, e := deflateData(&buf, int(hdr.compressor)) - if e != nil { - return nil, e - } - - ch := &Chunk{} - for i := 0; i < int(hdr.numRecords); i++ { - var rs [4]byte - if _, e = deflated.Read(rs[:]); e != nil { - return nil, fmt.Errorf("Failed to read record length: %v", e) - } - - r := make([]byte, binary.LittleEndian.Uint32(rs[:])) - if _, e = deflated.Read(r); e != nil { - return nil, fmt.Errorf("Failed to read a record: %v", e) - } - - ch.records = append(ch.records, r) - ch.numBytes += len(r) - } - - return ch, nil -} - -func deflateData(src io.Reader, compressorIndex int) (*bytes.Buffer, error) { - var e error - var deflator io.Reader - - switch compressorIndex { - case NoCompression: - deflator = src - case Snappy: - deflator = snappy.NewReader(src) - case Gzip: - deflator, e = gzip.NewReader(src) - if e != nil { - return nil, fmt.Errorf("Failed to create gzip reader: %v", e) - } - default: - return nil, fmt.Errorf("Unknown compression algorithm: %d", compressorIndex) - } - - deflated := new(bytes.Buffer) - if _, e = io.Copy(deflated, deflator); e != nil { - return nil, fmt.Errorf("Failed to deflate chunk data: %v", e) - } - - return deflated, nil -} diff --git a/go/recordio/header.go b/go/recordio/header.go deleted file mode 100644 index d3aefae3646..00000000000 --- a/go/recordio/header.go +++ /dev/null @@ -1,59 +0,0 @@ -package recordio - -import ( - "encoding/binary" - "fmt" - "io" -) - -const ( - // NoCompression means writing raw chunk data into files. - // With other choices, chunks are compressed before written. - NoCompression = iota - // Snappy had been the default compressing algorithm widely - // used in Google. It compromises between speech and - // compression ratio. - Snappy - // Gzip is a well-known compression algorithm. It is - // recommmended only you are looking for compression ratio. - Gzip - - magicNumber uint32 = 0x01020304 - defaultCompressor = Snappy -) - -// Header is the metadata of Chunk. -type Header struct { - checkSum uint32 - compressor uint32 - compressedSize uint32 - numRecords uint32 -} - -func (c *Header) write(w io.Writer) (int, error) { - var buf [20]byte - binary.LittleEndian.PutUint32(buf[0:4], magicNumber) - binary.LittleEndian.PutUint32(buf[4:8], c.checkSum) - binary.LittleEndian.PutUint32(buf[8:12], c.compressor) - binary.LittleEndian.PutUint32(buf[12:16], c.compressedSize) - binary.LittleEndian.PutUint32(buf[16:20], c.numRecords) - return w.Write(buf[:]) -} - -func parseHeader(r io.Reader) (*Header, error) { - var buf [20]byte - if _, e := r.Read(buf[:]); e != nil { - return nil, e - } - - if v := binary.LittleEndian.Uint32(buf[0:4]); v != magicNumber { - return nil, fmt.Errorf("Failed to parse magic number") - } - - return &Header{ - checkSum: binary.LittleEndian.Uint32(buf[4:8]), - compressor: binary.LittleEndian.Uint32(buf[8:12]), - compressedSize: binary.LittleEndian.Uint32(buf[12:16]), - numRecords: binary.LittleEndian.Uint32(buf[16:20]), - }, nil -} diff --git a/go/recordio/range_scanner.go b/go/recordio/range_scanner.go deleted file mode 100644 index 46e2eee68c7..00000000000 --- a/go/recordio/range_scanner.go +++ /dev/null @@ -1,140 +0,0 @@ -package recordio - -import "io" - -// Index consists offsets and sizes of the consequetive chunks in a RecordIO file. -type Index struct { - chunkOffsets []int64 - chunkLens []uint32 - numRecords int // the number of all records in a file. - chunkRecords []int // the number of records in chunks. -} - -// LoadIndex scans the file and parse chunkOffsets, chunkLens, and len. -func LoadIndex(r io.ReadSeeker) (*Index, error) { - f := &Index{} - offset := int64(0) - var e error - var hdr *Header - - for { - hdr, e = parseHeader(r) - if e != nil { - break - } - - f.chunkOffsets = append(f.chunkOffsets, offset) - f.chunkLens = append(f.chunkLens, hdr.numRecords) - f.chunkRecords = append(f.chunkRecords, int(hdr.numRecords)) - f.numRecords += int(hdr.numRecords) - - offset, e = r.Seek(int64(hdr.compressedSize), io.SeekCurrent) - if e != nil { - break - } - } - - if e == io.EOF { - return f, nil - } - return nil, e -} - -// NumRecords returns the total number of records in a RecordIO file. -func (r *Index) NumRecords() int { - return r.numRecords -} - -// NumChunks returns the total number of chunks in a RecordIO file. -func (r *Index) NumChunks() int { - return len(r.chunkLens) -} - -// ChunkIndex return the Index of i-th Chunk. -func (r *Index) ChunkIndex(i int) *Index { - idx := &Index{} - idx.chunkOffsets = []int64{r.chunkOffsets[i]} - idx.chunkLens = []uint32{r.chunkLens[i]} - idx.chunkRecords = []int{r.chunkRecords[i]} - idx.numRecords = idx.chunkRecords[0] - return idx -} - -// Locate returns the index of chunk that contains the given record, -// and the record index within the chunk. It returns (-1, -1) if the -// record is out of range. -func (r *Index) Locate(recordIndex int) (int, int) { - sum := 0 - for i, l := range r.chunkLens { - sum += int(l) - if recordIndex < sum { - return i, recordIndex - sum + int(l) - } - } - return -1, -1 -} - -// RangeScanner scans records in a specified range within [0, numRecords). -type RangeScanner struct { - reader io.ReadSeeker - index *Index - start, end, cur int - chunkIndex int - chunk *Chunk - err error -} - -// NewRangeScanner creates a scanner that sequencially reads records in the -// range [start, start+len). If start < 0, it scans from the -// beginning. If len < 0, it scans till the end of file. -func NewRangeScanner(r io.ReadSeeker, index *Index, start, len int) *RangeScanner { - if start < 0 { - start = 0 - } - if len < 0 || start+len >= index.NumRecords() { - len = index.NumRecords() - start - } - - return &RangeScanner{ - reader: r, - index: index, - start: start, - end: start + len, - cur: start - 1, // The intial status required by Scan. - chunkIndex: -1, - chunk: &Chunk{}, - } -} - -// Scan moves the cursor forward for one record and loads the chunk -// containing the record if not yet. -func (s *RangeScanner) Scan() bool { - s.cur++ - - if s.cur >= s.end { - s.err = io.EOF - } else { - if ci, _ := s.index.Locate(s.cur); s.chunkIndex != ci { - s.chunkIndex = ci - s.chunk, s.err = parseChunk(s.reader, s.index.chunkOffsets[ci]) - } - } - - return s.err == nil -} - -// Record returns the record under the current cursor. -func (s *RangeScanner) Record() []byte { - _, ri := s.index.Locate(s.cur) - return s.chunk.records[ri] -} - -// Err returns the first non-EOF error that was encountered by the -// Scanner. -func (s *RangeScanner) Err() error { - if s.err == io.EOF { - return nil - } - - return s.err -} diff --git a/go/recordio/recordio_internal_test.go b/go/recordio/recordio_internal_test.go deleted file mode 100644 index 30e317925d8..00000000000 --- a/go/recordio/recordio_internal_test.go +++ /dev/null @@ -1,90 +0,0 @@ -package recordio - -import ( - "bytes" - "testing" - "unsafe" - - "github.com/stretchr/testify/assert" -) - -func TestChunkHead(t *testing.T) { - assert := assert.New(t) - - c := &Header{ - checkSum: 123, - compressor: 456, - compressedSize: 789, - } - - var buf bytes.Buffer - _, e := c.write(&buf) - assert.Nil(e) - - cc, e := parseHeader(&buf) - assert.Nil(e) - assert.Equal(c, cc) -} - -func TestWriteAndRead(t *testing.T) { - assert := assert.New(t) - - data := []string{ - "12345", - "1234", - "12"} - - var buf bytes.Buffer - w := NewWriter(&buf, 10, NoCompression) // use a small maxChunkSize. - - n, e := w.Write([]byte(data[0])) // not exceed chunk size. - assert.Nil(e) - assert.Equal(5, n) - - n, e = w.Write([]byte(data[1])) // not exceed chunk size. - assert.Nil(e) - assert.Equal(4, n) - - n, e = w.Write([]byte(data[2])) // exeeds chunk size, dump and create a new chunk. - assert.Nil(e) - assert.Equal(n, 2) - - assert.Nil(w.Close()) // flush the second chunk. - assert.Nil(w.Writer) - - n, e = w.Write([]byte("anything")) // not effective after close. - assert.NotNil(e) - assert.Equal(n, 0) - - idx, e := LoadIndex(bytes.NewReader(buf.Bytes())) - assert.Nil(e) - assert.Equal([]uint32{2, 1}, idx.chunkLens) - assert.Equal( - []int64{0, - int64(4 + // magic number - unsafe.Sizeof(Header{}) + - 5 + // first record - 4 + // second record - 2*4)}, // two record legnths - idx.chunkOffsets) - - s := NewRangeScanner(bytes.NewReader(buf.Bytes()), idx, -1, -1) - i := 0 - for s.Scan() { - assert.Equal(data[i], string(s.Record())) - i++ - } -} - -func TestWriteEmptyFile(t *testing.T) { - assert := assert.New(t) - - var buf bytes.Buffer - w := NewWriter(&buf, 10, NoCompression) // use a small maxChunkSize. - assert.Nil(w.Close()) - assert.Equal(0, buf.Len()) - - idx, e := LoadIndex(bytes.NewReader(buf.Bytes())) - assert.Nil(e) - assert.Equal(0, idx.NumRecords()) -} diff --git a/go/recordio/recordio_test.go b/go/recordio/recordio_test.go deleted file mode 100644 index e4ef835afa6..00000000000 --- a/go/recordio/recordio_test.go +++ /dev/null @@ -1,81 +0,0 @@ -package recordio_test - -import ( - "bytes" - "reflect" - "testing" - - "github.com/PaddlePaddle/Paddle/go/recordio" -) - -func TestWriteRead(t *testing.T) { - const total = 1000 - var buf bytes.Buffer - w := recordio.NewWriter(&buf, 0, -1) - for i := 0; i < total; i++ { - _, err := w.Write(make([]byte, i)) - if err != nil { - t.Fatal(err) - } - } - w.Close() - - idx, err := recordio.LoadIndex(bytes.NewReader(buf.Bytes())) - if err != nil { - t.Fatal(err) - } - - if idx.NumRecords() != total { - t.Fatal("num record does not match:", idx.NumRecords(), total) - } - - s := recordio.NewRangeScanner(bytes.NewReader(buf.Bytes()), idx, -1, -1) - i := 0 - for s.Scan() { - if !reflect.DeepEqual(s.Record(), make([]byte, i)) { - t.Fatal("not equal:", len(s.Record()), len(make([]byte, i))) - } - i++ - } - - if i != total { - t.Fatal("total count not match:", i, total) - } -} - -func TestChunkIndex(t *testing.T) { - const total = 1000 - var buf bytes.Buffer - w := recordio.NewWriter(&buf, 0, -1) - for i := 0; i < total; i++ { - _, err := w.Write(make([]byte, i)) - if err != nil { - t.Fatal(err) - } - } - w.Close() - - idx, err := recordio.LoadIndex(bytes.NewReader(buf.Bytes())) - if err != nil { - t.Fatal(err) - } - - if idx.NumChunks() != total { - t.Fatal("unexpected chunk num:", idx.NumChunks(), total) - } - - for i := 0; i < total; i++ { - newIdx := idx.ChunkIndex(i) - s := recordio.NewRangeScanner(bytes.NewReader(buf.Bytes()), newIdx, -1, -1) - j := 0 - for s.Scan() { - if !reflect.DeepEqual(s.Record(), make([]byte, i)) { - t.Fatal("not equal:", len(s.Record()), len(make([]byte, i))) - } - j++ - } - if j != 1 { - t.Fatal("unexpected record per chunk:", j) - } - } -} diff --git a/go/recordio/scanner.go b/go/recordio/scanner.go deleted file mode 100644 index 865228ff651..00000000000 --- a/go/recordio/scanner.go +++ /dev/null @@ -1,140 +0,0 @@ -package recordio - -import ( - "fmt" - "os" - "path/filepath" -) - -// Scanner is a scanner for multiple recordio files. -type Scanner struct { - paths []string - curFile *os.File - curScanner *RangeScanner - pathIdx int - end bool - err error -} - -// NewScanner creates a new Scanner. -func NewScanner(paths ...string) (*Scanner, error) { - var ps []string - for _, s := range paths { - match, err := filepath.Glob(s) - if err != nil { - return nil, err - } - - ps = append(ps, match...) - } - - if len(ps) == 0 { - return nil, fmt.Errorf("no valid path provided: %v", paths) - } - - return &Scanner{paths: ps}, nil -} - -// Scan moves the cursor forward for one record and loads the chunk -// containing the record if not yet. -func (s *Scanner) Scan() bool { - if s.err != nil { - return false - } - - if s.end { - return false - } - - if s.curScanner == nil { - more, err := s.nextFile() - if err != nil { - s.err = err - return false - } - - if !more { - s.end = true - return false - } - } - - curMore := s.curScanner.Scan() - s.err = s.curScanner.Err() - - if s.err != nil { - return curMore - } - - if !curMore { - err := s.curFile.Close() - if err != nil { - s.err = err - return false - } - s.curFile = nil - - more, err := s.nextFile() - if err != nil { - s.err = err - return false - } - - if !more { - s.end = true - return false - } - - return s.Scan() - } - return true -} - -// Err returns the first non-EOF error that was encountered by the -// Scanner. -func (s *Scanner) Err() error { - return s.err -} - -// Record returns the record under the current cursor. -func (s *Scanner) Record() []byte { - if s.curScanner == nil { - return nil - } - - return s.curScanner.Record() -} - -// Close release the resources. -func (s *Scanner) Close() error { - s.curScanner = nil - if s.curFile != nil { - err := s.curFile.Close() - s.curFile = nil - return err - } - return nil -} - -func (s *Scanner) nextFile() (bool, error) { - if s.pathIdx >= len(s.paths) { - return false, nil - } - - path := s.paths[s.pathIdx] - s.pathIdx++ - f, err := os.Open(path) - if err != nil { - return false, err - } - - idx, err := LoadIndex(f) - if err != nil { - f.Close() - return false, err - } - - s.curFile = f - s.curScanner = NewRangeScanner(f, idx, 0, -1) - return true, nil -} diff --git a/go/recordio/writer.go b/go/recordio/writer.go deleted file mode 100644 index 39112e518fb..00000000000 --- a/go/recordio/writer.go +++ /dev/null @@ -1,60 +0,0 @@ -package recordio - -import ( - "fmt" - "io" -) - -const ( - defaultMaxChunkSize = 32 * 1024 * 1024 -) - -// Writer creates a RecordIO file. -type Writer struct { - io.Writer // Set to nil to mark a closed writer. - chunk *Chunk - maxChunkSize int // total records size, excluding metadata, before compression. - compressor int -} - -// NewWriter creates a RecordIO file writer. Each chunk is compressed -// using the deflate algorithm given compression level. Note that -// level 0 means no compression and -1 means default compression. -func NewWriter(w io.Writer, maxChunkSize, compressor int) *Writer { - if maxChunkSize < 0 { - maxChunkSize = defaultMaxChunkSize - } - - if compressor < 0 { - compressor = defaultCompressor - } - - return &Writer{ - Writer: w, - chunk: &Chunk{}, - maxChunkSize: maxChunkSize, - compressor: compressor} -} - -// Writes a record. It returns an error if Close has been called. -func (w *Writer) Write(record []byte) (int, error) { - if w.Writer == nil { - return 0, fmt.Errorf("Cannot write since writer had been closed") - } - - if w.chunk.numBytes+len(record) > w.maxChunkSize { - if e := w.chunk.dump(w.Writer, w.compressor); e != nil { - return 0, e - } - } - - w.chunk.add(record) - return len(record), nil -} - -// Close flushes the current chunk and makes the writer invalid. -func (w *Writer) Close() error { - e := w.chunk.dump(w.Writer, w.compressor) - w.Writer = nil - return e -} -- GitLab From 252c84dbe1e4659e5a3b6f10fe80a893e88ecb5c Mon Sep 17 00:00:00 2001 From: xuwei06 Date: Thu, 1 Jun 2017 16:20:38 -0700 Subject: [PATCH 0531/3256] Correctly handle print_layer in V2 API print_layer is used to print the values of a layer when evaluating the model for debugging purpose. No layer depends on print_layer. It should be added to the topology if it is used to print some layer already in the topology. --- python/paddle/trainer_config_helpers/layers.py | 10 +++++++++- python/paddle/v2/layer.py | 14 ++++++++++++++ python/paddle/v2/tests/test_layer.py | 1 + 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/python/paddle/trainer_config_helpers/layers.py b/python/paddle/trainer_config_helpers/layers.py index 81cce31fecc..5762e1d159e 100755 --- a/python/paddle/trainer_config_helpers/layers.py +++ b/python/paddle/trainer_config_helpers/layers.py @@ -111,6 +111,7 @@ __all__ = [ 'block_expand_layer', 'maxout_layer', 'out_prod_layer', + 'printer_layer', 'print_layer', 'priorbox_layer', 'cross_channel_norm_layer', @@ -969,7 +970,7 @@ def fc_layer(input, @wrap_name_default("print") -def print_layer(input, name=None): +def printer_layer(input, name=None): """ Print the output value of input layers. This layer is useful for debugging. @@ -991,6 +992,13 @@ def print_layer(input, name=None): inputs=[l.name for l in input], ) # this layer don't return anything, can not be input of other layer. +# Keep print_layer for compatibility with V1 API. +# 'print_layer' does not work for V2 API because it will be changed to +# 'print' for V2 API. But 'print' is a reserved key word in python. + + +print_layer = printer_layer + @wrap_name_default("priorbox") def priorbox_layer(input, diff --git a/python/paddle/v2/layer.py b/python/paddle/v2/layer.py index da2abdd2d1e..815635f5dd4 100644 --- a/python/paddle/v2/layer.py +++ b/python/paddle/v2/layer.py @@ -149,6 +149,20 @@ def __get_used_layers__(output_layers, extra_layers=None): for layer in output_layers: dfs_travel(layer.full_name) + # print layer needs to be specially handled because no other + # layer depends on it. It is used to print the result of some + # layers when running the model for debug purpose. So we explicitly + # add a print layer to the topolty if its input is in the toplogy. + for layer in cp.g_config.model_config.layers: + if layer.type == 'print': + used = True + for inp in layer.inputs: + if inp.input_layer_name not in layer_names: + used = False + break + if used: + layer_names.add(layer.name) + return layer_names diff --git a/python/paddle/v2/tests/test_layer.py b/python/paddle/v2/tests/test_layer.py index 2d25b1a9dca..f2097e195f4 100644 --- a/python/paddle/v2/tests/test_layer.py +++ b/python/paddle/v2/tests/test_layer.py @@ -164,6 +164,7 @@ class OtherLayerTest(unittest.TestCase): maxid = layer.max_id(input=inference) sampling_id = layer.sampling_id(input=inference) eos = layer.eos(input=maxid, eos_id=5) + layer.printer(maxid) print layer.parse_network([maxid, sampling_id, eos]) def test_slicing_joining_layer(self): -- GitLab From b3ac51ff90093aaa1168a3f75b4e931c5b34eb9e Mon Sep 17 00:00:00 2001 From: dangqingqing Date: Sat, 3 Jun 2017 22:48:07 +0800 Subject: [PATCH 0532/3256] GPU implementation of row conv. --- paddle/function/CMakeLists.txt | 1 + paddle/function/RowConvOp.cpp | 37 +++- paddle/function/RowConvOpGpu.cu | 329 ++++++++++++++++++++++++++++++ paddle/function/RowConvOpTest.cpp | 69 +++++++ 4 files changed, 432 insertions(+), 4 deletions(-) create mode 100644 paddle/function/RowConvOpGpu.cu create mode 100644 paddle/function/RowConvOpTest.cpp diff --git a/paddle/function/CMakeLists.txt b/paddle/function/CMakeLists.txt index 233a53709a8..1f54ac1231c 100644 --- a/paddle/function/CMakeLists.txt +++ b/paddle/function/CMakeLists.txt @@ -28,6 +28,7 @@ if(WITH_TESTING) add_simple_unittest(PadOpTest) add_simple_unittest(MulOpTest) add_simple_unittest(CosSimOpTest) + add_simple_unittest(RowConvOpTest) endif() endif() diff --git a/paddle/function/RowConvOp.cpp b/paddle/function/RowConvOp.cpp index f92b286c697..24b7e3cdffe 100644 --- a/paddle/function/RowConvOp.cpp +++ b/paddle/function/RowConvOp.cpp @@ -13,6 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. */ #include "RowConvOp.h" +#include #include "paddle/math/Vector.h" namespace paddle { @@ -127,10 +128,8 @@ public: RowConv(outMat, inMat, wMat, seqId); } }; - /** - * \brief The backward propagation of padding Function. Remove the elements - * in the padding positions of forward. + * \brief TODO(qingqing) * * Argument in this Function: */ @@ -158,7 +157,37 @@ public: : typename Tensor::Matrix(nullptr, 0, 0); const auto seqId = in.getSequenceId().vector(); + std::cout << "in:" << std::endl; + for (int i = 0; i < inMat.getHeight(); ++i) { + for (int j = 0; j < inMat.getWidth(); ++j) { + std::cout << outGMat.getElement(i, j) << " "; + } + std::cout << std::endl; + } + + std::cout << "w:" << std::endl; + for (int i = 0; i < wMat.getHeight(); ++i) { + for (int j = 0; j < wMat.getWidth(); ++j) { + std::cout << wMat.getElement(i, j) << " "; + } + std::cout << std::endl; + } + + std::cout << "w:" << std::endl; + for (int i = 0; i < seqId.getSize(); ++i) { + std::cout << seqId.getElement(i) << " "; + } + std::cout << std::endl; + RowConvGrad(outGMat, inMat, wMat, inGMat, wGMat, seqId); + + std::cout << std::endl << "out:" << std::endl; + for (int i = 0; i < inGMat.getHeight(); ++i) { + for (int j = 0; j < inGMat.getWidth(); ++j) { + std::cout << inGMat.getElement(i, j) << " "; + } + std::cout << std::endl; + } } }; @@ -166,7 +195,7 @@ REGISTER_TYPED_FUNC(RowConv, CPU, RowConvFunc); REGISTER_TYPED_FUNC(RowConvGrad, CPU, RowConvGradFunc); #ifndef PADDLE_ONLY_CPU REGISTER_TYPED_FUNC(RowConv, GPU, RowConvFunc); -REGISTER_TYPED_FUNC(RowConvGrad, GPU, PadGradFunc); +REGISTER_TYPED_FUNC(RowConvGrad, GPU, RowConvGradFunc); #endif } // namespace paddle diff --git a/paddle/function/RowConvOpGpu.cu b/paddle/function/RowConvOpGpu.cu new file mode 100644 index 00000000000..5b0e065a21e --- /dev/null +++ b/paddle/function/RowConvOpGpu.cu @@ -0,0 +1,329 @@ +/* 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 "hl_base.h" +#include "RowConvOp.h" + +namespace paddle { + +template +__global__ void KeRowConv(real* y, const real* x, const real* w, + const int* starts, const int height, const int width, + const int numSeq, const int context) { + + const int tidx = threadIdx.x; + const int tidy = threadIdx.y; + const int blky = blockDim.y; + const int gidx = blockIdx.x * blockDim.x; + + __shared__ real sw[BLOCK_H][BLOCK_W]; + + for (int i = tidy; i < context; i += blky) { + sw[i][tidx] = gidx + tidx < width ? w[i*width + gidx + tidx] : 0.0; + } + + __syncthreads(); + + for (int i = 0; i < numSeq; ++i) { + const int start = starts[i]; + const int end = starts[i + 1]; + const int steps = end - start; + for (int j = tidy; j < steps; j += blky) { + real sum = 0; + int off = (start + j) * width; + for (int t = 0; t < context; ++t) { + if ((start + j + t) < end) { + int xoff = off + t * width; + real xVal = gidx + tidx < width ? x[xoff + gidx + tidx] : 0.0; + sum += sw[t][tidx] * xVal; + } + } + if (gidx + tidx < width) { + y[off + gidx + tidx] += sum; + } + } + } +} + +__global__ void KeRowConv2(real* y, const real* x, const real* w, + const int* starts, const int height, const int width, + const int numSeq, const int context) { + const int tidx = threadIdx.x; + const int tidy = threadIdx.y; + const int blky = blockDim.y; + const int gidx = blockIdx.x * blockDim.x; + + for (int i = 0; i < numSeq; ++i) { + const int start = starts[i]; + const int end = starts[i + 1]; + const int steps = end - start; + for (int j = tidy; j < steps; j += blky) { + int off = (start + j) * width; + real sum = 0; + for (int t = 0; t < context && (start + j + t) < end; ++t) { + int xoff = off + t * width; + real xd = gidx + tidx < width ? x[xoff + gidx + tidx] : 0.0; + real wd = gidx + tidx < width ? w[t * width + gidx + tidx] : 0.0; + sum += wd * xd; + } + if (gidx + tidx < width) { + y[off + gidx + tidx] += sum; + } + } + } +} + + + +template <> +void RowConv(GpuMatrix& out, + const GpuMatrix& in, + const GpuMatrix& filter, + const GpuIVector& seq) { + const size_t numSeq = seq.getSize() - 1; + const size_t contextLength = filter.getHeight(); + const size_t height = in.getHeight(); + const size_t width = in.getWidth(); + + LOG(INFO) << numSeq; + LOG(INFO) << contextLength; + LOG(INFO) << height; + LOG(INFO) << width; + + real* y = out.getData(); + const real* x = in.getData(); + const real* w = filter.getData(); + const int* starts = seq.getData(); + + dim3 dimBlock(32, 32); + dim3 dimGrid(DIVUP(width, dimBlock.x), 1); + LOG(INFO) << dimGrid.x; + + if (contextLength <= 32) { + KeRowConv<32, 32><<>> + (y, x, w, starts, height, width, numSeq, contextLength); + } else { + KeRowConv2<<>> + (y, x, w, starts, height, width, numSeq, contextLength); + } + CHECK_SYNC("RowConv"); +} + + +template +__global__ void KeRowConvBwWeight(real* dw, const real* x, const real* dy, + const int* starts, const int height, const int width, const int numSeq, + const int context) { + + const int tidx = threadIdx.x; + const int tidy = threadIdx.y; + const int blky = blockDim.y; + const int gidx = blockIdx.x * blockDim.x; + + __shared__ real sh_x[BLOCK_H][BLOCK_W]; + __shared__ real sh_dy[BLOCK_H][BLOCK_W]; + __shared__ real sh_dw[CONTEXT][BLOCK_W]; + + for (int t = tidy; t < context; t += blky) { + sh_dw[t][tidx] = 0.0; + } + __syncthreads(); + + for (int i = 0; i < numSeq; ++i) { + const int start = starts[i]; + const int end = starts[i + 1]; + const int steps = end - start; + for (int j = tidy; j < steps; j += BLOCK_H) { + int xoff = gidx + tidx; + int yoff = start + j; + + // transpose + sh_x[tidx][tidy] = xoff < width && yoff < end ? x[yoff * width + xoff] : 0.0; + sh_dy[tidx][tidy] = xoff < width && yoff < end ? dy[yoff * width + xoff] : 0.0; + __syncthreads(); + + for (int t = 0; t < context; t++) { + real val = tidx + t < blockDim.x ? sh_x[tidy][tidx + t] * sh_dy[tidy][tidx]: 0.0; + // warp size and blockDim.x is 32. + for (int offset = 16; offset > 0; offset /= 2) { + val += __shfl_down(val, offset); + } + if (tidx == 0) { + sh_dw[t][tidy] += val; + } + __syncthreads(); + } + } + } + + for (int t = tidy; t < context && (gidx + tidx) < width; t += blky) { + dw[t * width + gidx + tidx] += sh_dw[t][tidx]; + } +} + +template +__global__ void KeRowConvBwWeight2(real* dw, const real* x, const real* dy, + const int* starts, const int height, const int width, const int numSeq, + const int context) { + + const int tidx = threadIdx.x; + const int tidy = threadIdx.y; + const int gidx = blockIdx.x * blockDim.x; + + __shared__ real sh_x[BLOCK_H][BLOCK_W]; + __shared__ real sh_dy[BLOCK_H][BLOCK_W]; + + for (int i = 0; i < numSeq; ++i) { + const int start = starts[i]; + const int end = starts[i + 1]; + const int steps = end - start; + for (int j = 0; j < steps; j += BLOCK_H) { + int xoff = gidx + tidx; + int yoff = start + j; + + // transpose + sh_x[tidx][tidy] = xoff < width && yoff < end ? x[yoff * width + xoff] : 0.0; + sh_dy[tidx][tidy] = xoff < width && yoff < end ? dy[yoff * width + xoff] : 0.0; + __syncthreads(); + + for (int t = 0; t < context; t++) { + real val = tidx + t < blockDim.x ? sh_x[tidy][tidx + t] * sh_dy[tidy][tidx]: 0.0; + // warp size and blockDim.x is 32. + for (int offset = 16; offset > 0; offset /= 2) { + val += __shfl_down(val, offset); + } + if (tidx == 0 && (gidx + tidy) < width) { + dw[t*width + gidx + tidy] += val; + } + } + } + } +} + +template +__global__ void KeRowConvBwData(real* dx, const real* w, const real* dy, + const int* starts, const int height, const int width, const int numSeq, + const int context) { + + const int tidx = threadIdx.x; + const int tidy = threadIdx.y; + const int blky = blockDim.y; + const int gidx = blockIdx.x * blockDim.x; + + __shared__ real sw[BLOCK_H][BLOCK_W]; + + for (int i = tidy; i < context; i += blky) { + sw[i][tidx] = gidx + tidx < width ? w[i*width + gidx + tidx] : 0.0; + } + + __syncthreads(); + + for (int i = 0; i < numSeq; ++i) { + const int start = starts[i]; + const int end = starts[i + 1]; + const int steps = end - start; + for (int j = tidy; j < steps; j += blky) { + real sum = 0; + int off = (start + j) * width; + for (int t = 0; t < context && (j - t) >= 0; ++t) { + int dyOff = off - t * width; + real dyVal = gidx + tidx < width ? dy[dyOff + gidx + tidx] : 0.0; + sum += sw[t][tidx] * dyVal; + } + if (gidx + tidx < width) { + dx[off + gidx + tidx] += sum; + } + } + } +} + +__global__ void KeRowConvBwData2(real* dx, const real* w, const real* dy, + const int* starts, const int height, const int width, const int numSeq, + const int context) { + + const int tidx = threadIdx.x; + const int tidy = threadIdx.y; + const int blky = blockDim.y; + const int gidx = blockIdx.x * blockDim.x; + + for (int i = 0; i < numSeq; ++i) { + const int start = starts[i]; + const int end = starts[i + 1]; + const int steps = end - start; + for (int j = tidy; j < steps; j += blky) { + real sum = 0; + int off = (start + j) * width; + for (int t = 0; t < context && (j - t) >= 0; ++t) { + int dyOff = off - t * width; + real dyVal = gidx + tidx < width ? dy[dyOff + gidx + tidx] : 0.0; + real wVal = gidx + tidx < width ? w[t * width + gidx + tidx] : 0.0; + sum += wVal * dyVal; + } + if (gidx + tidx < width) { + dx[off + gidx + tidx] += sum; + } + } + } +} + + +template <> +void RowConvGrad(const GpuMatrix& outG, + const GpuMatrix& in, + const GpuMatrix& filter, + GpuMatrix& inG, + GpuMatrix& filterG, + const GpuIVector& seq) { + const size_t numSeq = seq.getSize() - 1; + const size_t contextLength = filter.getHeight(); + const size_t height = in.getHeight(); + const size_t width = in.getWidth(); + + const real* dy = outG.getData(); + const real* x = in.getData(); + const real* w = filter.getData(); + real* dx = inG.getData(); + real* dw = filterG.getData(); + const int* starts = seq.getData(); + + dim3 dimBlock(32, 32); + dim3 dimGrid(DIVUP(width, dimBlock.x), 1); + + if (contextLength <= 16) { + KeRowConvBwWeight<32, 32, 16> + <<>> + (dw, x, dy, starts, height, width, numSeq, contextLength); + } else { + KeRowConvBwWeight2<32, 32> + <<>> + (dw, x, dy, starts, height, width, numSeq, contextLength); + } + + + dim3 dimBlock2(32, 32); + dim3 dimGrid2(DIVUP(width, dimBlock2.x), 1); + if (contextLength <= 64) { + KeRowConvBwData<32, 64> + <<>> + (dx, w, dy, starts, height, width, numSeq, contextLength); + } else { + KeRowConvBwData2 + <<>> + (dx, w, dy, starts, height, width, numSeq, contextLength); + } + + CHECK_SYNC("RowConvGrad"); +} + +} // namespace paddle diff --git a/paddle/function/RowConvOpTest.cpp b/paddle/function/RowConvOpTest.cpp new file mode 100644 index 00000000000..9898df1a974 --- /dev/null +++ b/paddle/function/RowConvOpTest.cpp @@ -0,0 +1,69 @@ +/* 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 "FunctionTest.h" + +namespace paddle { + +void testRowConvFw(size_t batchSize, size_t dim, size_t contextLength) { + FunctionCompare test("RowConv", FuncConfig()); + + test.addSequence(SequenceIdArg(TensorShape{batchSize})); + test.addInputs(SequenceArg(VALUE_TYPE_FLOAT, TensorShape{batchSize, dim})); + test.addInputs(BufferArg(VALUE_TYPE_FLOAT, TensorShape{contextLength, dim})); + + test.addOutputs(SequenceArg(VALUE_TYPE_FLOAT, TensorShape{batchSize, dim}), + ADD_TO); + + test.run(); +} + +void testRowConvBw(size_t batchSize, size_t dim, size_t contextLength) { + FunctionCompare test("RowConvGrad", FuncConfig()); + + test.addSequence(SequenceIdArg(TensorShape{batchSize})); + test.addInputs(SequenceArg(VALUE_TYPE_FLOAT, TensorShape{batchSize, dim})); + test.addInputs(SequenceArg(VALUE_TYPE_FLOAT, TensorShape{batchSize, dim})); + test.addInputs(BufferArg(VALUE_TYPE_FLOAT, TensorShape{contextLength, dim})); + + test.addOutputs(SequenceArg(VALUE_TYPE_FLOAT, TensorShape{batchSize, dim}), + ADD_TO); + test.addOutputs(BufferArg(VALUE_TYPE_FLOAT, TensorShape{contextLength, dim}), + ADD_TO); + + test.run(); +} + +TEST(RowConv, real) { + // for (size_t numSamples : {17, 129}) { + // for (size_t dim : {16, 248}) { + // for (size_t context: {3, 7, 65}) { + LOG(INFO) << "==========="; + // for (size_t numSamples : {17}) { + // for (size_t dim : {16}) { + // for (size_t context: {3}) { + size_t numSamples = 17; + size_t dim = 16; + size_t context = 3; + LOG(INFO) << " numSamples=" << numSamples << " dim=" << dim + << " context length=" << context; + testRowConvFw(numSamples, dim, context); + // testRowConvBw(numSamples, dim, context); + // } + // } + // } +} + +} // namespace paddle -- GitLab From 18cd1f2558338e3e999670cdfac7e1c61c1ea428 Mon Sep 17 00:00:00 2001 From: dangqingqing Date: Sun, 4 Jun 2017 13:29:16 +0800 Subject: [PATCH 0533/3256] Fix bug and Python API. --- paddle/function/RowConvOp.cpp | 93 ++++++++------ paddle/function/RowConvOp.h | 18 ++- paddle/function/RowConvOpGpu.cu | 113 ++++++++++-------- paddle/function/RowConvOpTest.cpp | 27 ++--- paddle/gserver/layers/RowConvLayer.cpp | 2 +- paddle/gserver/layers/RowConvLayer.h | 4 +- python/paddle/trainer/config_parser.py | 17 +++ .../paddle/trainer_config_helpers/layers.py | 76 ++++++++++++ .../tests/configs/file_list.sh | 2 +- .../configs/protostr/test_row_conv.protostr | 41 +++++++ .../tests/configs/test_row_conv.py | 9 ++ 11 files changed, 295 insertions(+), 107 deletions(-) create mode 100644 python/paddle/trainer_config_helpers/tests/configs/protostr/test_row_conv.protostr create mode 100644 python/paddle/trainer_config_helpers/tests/configs/test_row_conv.py diff --git a/paddle/function/RowConvOp.cpp b/paddle/function/RowConvOp.cpp index 24b7e3cdffe..c3abb64971f 100644 --- a/paddle/function/RowConvOp.cpp +++ b/paddle/function/RowConvOp.cpp @@ -61,7 +61,7 @@ void RowConvGrad(const CpuMatrix& outG, size_t begin = starts[i]; size_t end = starts[i + 1]; size_t steps = end - begin; - for (size_t j = 0; j < contextLength; ++j) { + for (size_t j = 0; j < contextLength && (begin + j) < end; ++j) { MatrixPtr x = (const_cast(in)).subMatrix(begin + j, steps - j); MatrixPtr dy = @@ -81,7 +81,7 @@ void RowConvGrad(const CpuMatrix& outG, for (size_t j = 0; j < steps; ++j) { MatrixPtr dx = inG.subMatrix(begin + j, 1); for (size_t t = 0; t < contextLength; ++t) { - if ((int(j) - int(t)) >= 0) { + if (int(j - t) >= 0) { MatrixPtr dy = (const_cast(outG)).subMatrix(begin + j - t, 1); MatrixPtr w = (const_cast(filter)).subMatrix(t, 1); @@ -94,8 +94,37 @@ void RowConvGrad(const CpuMatrix& outG, } /** - * \brief TODO(qingqing) + * \brief The row convolution is called lookahead convolution. It is firstly + * introduced in deep-speech2 system. The bidirectional RNN that learns + * representation for a sequence by performing a forward and a backward pass + * through the entire sequence. However, unlike unidirectional RNNs, + * bidirectional RNNs are challenging to deploy in an online and low-latency + * setting. The lookahead convolution incorporates information from future + * subsequences in a computationally efficient manner to improve unidirectional + * recurrent neural networks. * + * The connection of row convolution is different form the 1D sequence + * convolution. Assumed that, the future context-length is k, that is to say, + * it can get the output at timestep t by using the the input feature from t-th + * timestep to (t+k)-th timestep. Assumed that the hidden dim of input + * activations are d, the activations r_t for the new layer at time-step t are: + * + * + * -- k + 1 + * r(t,i) = > W(i,j) * h(t+j-1, i), for (1 <= i <= d) + * -- j = 1 + * + * + * The weight shape is: (k + 1) x d + * Function Arguments: + * + * \param inputs[0] The input activations. + * \param inputs[0] The filter (or weight) and shape is (k+1) x d. + * \param outputs[1] The output activations. + * + * [1] Dario Amodei, etc. Deep Speech 2 : End-to-End Speech Recognition in + * English + * and Mandarin. https://arxiv.org/abs/1512.02595 */ template @@ -128,10 +157,21 @@ public: RowConv(outMat, inMat, wMat, seqId); } }; + /** - * \brief TODO(qingqing) + * \brief The backward of row convolution function. This function calculated + * the gradient w.r.t filter and the gradient w.r.t input activations(or data). * * Argument in this Function: + * + * \param inputs[0] The gradient w.r.t output activations. + * \param inputs[1] The input activations. + * \param inputs[2] The filter (or weight) and shape is (k+1) x d. + * \param outputs[0] The gradient w.r.t input activations. + * \param outputs[1] The gradient w.r.r filter. + * + * Abbreviation: + * w.r.t: with respect to. */ template @@ -140,12 +180,27 @@ public: void init(const FuncConfig& config) override {} void calc(const BufferArgs& inputs, const BufferArgs& outputs) override { + // check + CHECK_EQ(3UL, inputs.size()); + CHECK_EQ(2UL, outputs.size()); + CHECK_EQ(outputs[0].getArgType(), ADD_TO); + CHECK_EQ(outputs[1].getArgType(), ADD_TO); + CHECK(inputs[0].isSequenceArg() && inputs[1].isSequenceArg() && + outputs[0].isSequenceArg()) + << "SequenceArg required here."; + const auto outGrad = dynamic_cast(inputs[0]); const auto in = dynamic_cast(inputs[1]); const auto w = inputs[2]; auto inGrad = dynamic_cast(outputs[0]); auto wGrad = outputs[1]; + CHECK_EQ(in.shape().ndims(), 2UL); + CHECK_EQ(outGrad.shape().ndims(), 2UL); + CHECK_EQ(in.shape()[1], outGrad.shape()[1]); + CHECK_EQ(in.shape()[0], outGrad.shape()[0]); + CHECK_EQ(wGrad.shape()[1], in.shape()[1]); + const auto outGMat = outGrad.matrix(); const auto inMat = in.matrix(); const auto wMat = w.matrix(); @@ -157,37 +212,7 @@ public: : typename Tensor::Matrix(nullptr, 0, 0); const auto seqId = in.getSequenceId().vector(); - std::cout << "in:" << std::endl; - for (int i = 0; i < inMat.getHeight(); ++i) { - for (int j = 0; j < inMat.getWidth(); ++j) { - std::cout << outGMat.getElement(i, j) << " "; - } - std::cout << std::endl; - } - - std::cout << "w:" << std::endl; - for (int i = 0; i < wMat.getHeight(); ++i) { - for (int j = 0; j < wMat.getWidth(); ++j) { - std::cout << wMat.getElement(i, j) << " "; - } - std::cout << std::endl; - } - - std::cout << "w:" << std::endl; - for (int i = 0; i < seqId.getSize(); ++i) { - std::cout << seqId.getElement(i) << " "; - } - std::cout << std::endl; - RowConvGrad(outGMat, inMat, wMat, inGMat, wGMat, seqId); - - std::cout << std::endl << "out:" << std::endl; - for (int i = 0; i < inGMat.getHeight(); ++i) { - for (int j = 0; j < inGMat.getWidth(); ++j) { - std::cout << inGMat.getElement(i, j) << " "; - } - std::cout << std::endl; - } } }; diff --git a/paddle/function/RowConvOp.h b/paddle/function/RowConvOp.h index cd78ec724ab..2c5de6151aa 100644 --- a/paddle/function/RowConvOp.h +++ b/paddle/function/RowConvOp.h @@ -19,7 +19,14 @@ limitations under the License. */ namespace paddle { /** - * \brief TODO(qingqing) + * \brief The forward of row convolution. + * + * \param[out] out The output data and shape is h x d. h is the sum of + * time steps of all samples in one mini-batch. + * \param[in] in The input data and shape is h x d. + * \param[in] filter The filter and shape is k x d. The lookahead step + * number plus one equals k. + * \param[in] seq The sequence start positions. * */ template @@ -29,7 +36,14 @@ void RowConv(typename Tensor::Matrix& out, const typename Tensor::Vector& seq); /** - * \brief TODO(qingqing) + * \brief The backward of row convolution. + * + * \param[in] outG The gradient w.r.t output data. + * \param[in] in The input data. + * \param[in] filter The filter. + * \param[out] inG The gradient w.r.t input data. + * \param[out] filterG The gradient w.r.t filter. + * \param[in] seq The sequence start positions. * */ template diff --git a/paddle/function/RowConvOpGpu.cu b/paddle/function/RowConvOpGpu.cu index 5b0e065a21e..06e2c2baac2 100644 --- a/paddle/function/RowConvOpGpu.cu +++ b/paddle/function/RowConvOpGpu.cu @@ -96,11 +96,6 @@ void RowConv(GpuMatrix& out, const size_t height = in.getHeight(); const size_t width = in.getWidth(); - LOG(INFO) << numSeq; - LOG(INFO) << contextLength; - LOG(INFO) << height; - LOG(INFO) << width; - real* y = out.getData(); const real* x = in.getData(); const real* w = filter.getData(); @@ -108,7 +103,6 @@ void RowConv(GpuMatrix& out, dim3 dimBlock(32, 32); dim3 dimGrid(DIVUP(width, dimBlock.x), 1); - LOG(INFO) << dimGrid.x; if (contextLength <= 32) { KeRowConv<32, 32><<>> @@ -131,12 +125,12 @@ __global__ void KeRowConvBwWeight(real* dw, const real* x, const real* dy, const int blky = blockDim.y; const int gidx = blockIdx.x * blockDim.x; - __shared__ real sh_x[BLOCK_H][BLOCK_W]; - __shared__ real sh_dy[BLOCK_H][BLOCK_W]; + __shared__ real sh_x[BLOCK_W][BLOCK_H]; + __shared__ real sh_dy[BLOCK_W][BLOCK_H + CONTEXT - 1]; __shared__ real sh_dw[CONTEXT][BLOCK_W]; - for (int t = tidy; t < context; t += blky) { - sh_dw[t][tidx] = 0.0; + if (tidy < context) { + sh_dw[tidy][tidx] = 0.0; } __syncthreads(); @@ -144,21 +138,31 @@ __global__ void KeRowConvBwWeight(real* dw, const real* x, const real* dy, const int start = starts[i]; const int end = starts[i + 1]; const int steps = end - start; - for (int j = tidy; j < steps; j += BLOCK_H) { + const int size = ((steps + BLOCK_H - 1)/BLOCK_H) * BLOCK_H; + for (int j = tidy; j < size; j += BLOCK_H) { int xoff = gidx + tidx; int yoff = start + j; // transpose - sh_x[tidx][tidy] = xoff < width && yoff < end ? x[yoff * width + xoff] : 0.0; - sh_dy[tidx][tidy] = xoff < width && yoff < end ? dy[yoff * width + xoff] : 0.0; + sh_x[tidx][tidy] = (xoff < width && yoff < end) ? x[yoff * width + xoff] : 0.0; + sh_dy[tidx][tidy + context - 1] = (xoff < width && yoff < end) ? dy[yoff * width + xoff] : 0.0; + __syncthreads(); + if (tidy < (context - 1)) { + yoff = yoff - context + 1; + sh_dy[tidx][tidy] = (xoff < width && yoff >= start) ? dy[yoff * width + xoff] : 0.0; + } __syncthreads(); for (int t = 0; t < context; t++) { - real val = tidx + t < blockDim.x ? sh_x[tidy][tidx + t] * sh_dy[tidy][tidx]: 0.0; + real val = sh_x[tidy][tidx] * sh_dy[tidy][tidx + context - 1 - t]; + __syncthreads(); // warp size and blockDim.x is 32. - for (int offset = 16; offset > 0; offset /= 2) { - val += __shfl_down(val, offset); - } + val += __shfl_down(val, 16); + val += __shfl_down(val, 8); + val += __shfl_down(val, 4); + val += __shfl_down(val, 2); + val += __shfl_down(val, 1); + __syncthreads(); if (tidx == 0) { sh_dw[t][tidy] += val; } @@ -167,7 +171,7 @@ __global__ void KeRowConvBwWeight(real* dw, const real* x, const real* dy, } } - for (int t = tidy; t < context && (gidx + tidx) < width; t += blky) { + for (int t = tidy; (t < context) && ((gidx + tidx) < width); t += blky) { dw[t * width + gidx + tidx] += sh_dw[t][tidx]; } } @@ -188,21 +192,30 @@ __global__ void KeRowConvBwWeight2(real* dw, const real* x, const real* dy, const int start = starts[i]; const int end = starts[i + 1]; const int steps = end - start; - for (int j = 0; j < steps; j += BLOCK_H) { + + const int size = ((steps + BLOCK_H - 1)/BLOCK_H) * BLOCK_H; + for (int j = tidy; j < size; j += BLOCK_H) { int xoff = gidx + tidx; int yoff = start + j; // transpose - sh_x[tidx][tidy] = xoff < width && yoff < end ? x[yoff * width + xoff] : 0.0; - sh_dy[tidx][tidy] = xoff < width && yoff < end ? dy[yoff * width + xoff] : 0.0; + sh_x[tidx][tidy] = (xoff < width && yoff < end) ? x[yoff * width + xoff] : 0.0; __syncthreads(); for (int t = 0; t < context; t++) { - real val = tidx + t < blockDim.x ? sh_x[tidy][tidx + t] * sh_dy[tidy][tidx]: 0.0; + sh_dy[tidx][tidy] = (xoff < width && (yoff - t) >= start && yoff - t < end) ? dy[(yoff - t) * width + xoff] : 0.0; + __syncthreads(); + + real val = sh_x[tidy][tidx] * sh_dy[tidy][tidx]; + __syncthreads(); // warp size and blockDim.x is 32. - for (int offset = 16; offset > 0; offset /= 2) { - val += __shfl_down(val, offset); - } + val += __shfl_down(val, 16); + val += __shfl_down(val, 8); + val += __shfl_down(val, 4); + val += __shfl_down(val, 2); + val += __shfl_down(val, 1); + __syncthreads(); + if (tidx == 0 && (gidx + tidy) < width) { dw[t*width + gidx + tidy] += val; } @@ -293,34 +306,36 @@ void RowConvGrad(const GpuMatrix& outG, const real* dy = outG.getData(); const real* x = in.getData(); const real* w = filter.getData(); - real* dx = inG.getData(); - real* dw = filterG.getData(); const int* starts = seq.getData(); - dim3 dimBlock(32, 32); - dim3 dimGrid(DIVUP(width, dimBlock.x), 1); - - if (contextLength <= 16) { - KeRowConvBwWeight<32, 32, 16> - <<>> - (dw, x, dy, starts, height, width, numSeq, contextLength); - } else { - KeRowConvBwWeight2<32, 32> - <<>> - (dw, x, dy, starts, height, width, numSeq, contextLength); + if (filterG) { + dim3 dimBlock(32, 32); + dim3 dimGrid(DIVUP(width, dimBlock.x), 1); + real* dw = filterG.getData(); + if (contextLength <= 16) { + KeRowConvBwWeight<32, 32, 16> + <<>> + (dw, x, dy, starts, height, width, numSeq, contextLength); + } else { + KeRowConvBwWeight2<32, 32> + <<>> + (dw, x, dy, starts, height, width, numSeq, contextLength); + } } - - dim3 dimBlock2(32, 32); - dim3 dimGrid2(DIVUP(width, dimBlock2.x), 1); - if (contextLength <= 64) { - KeRowConvBwData<32, 64> - <<>> - (dx, w, dy, starts, height, width, numSeq, contextLength); - } else { - KeRowConvBwData2 - <<>> - (dx, w, dy, starts, height, width, numSeq, contextLength); + if (inG) { + real* dx = inG.getData(); + dim3 dimBlock2(32, 32); + dim3 dimGrid2(DIVUP(width, dimBlock2.x), 1); + if (contextLength <= 64) { + KeRowConvBwData<32, 64> + <<>> + (dx, w, dy, starts, height, width, numSeq, contextLength); + } else { + KeRowConvBwData2 + <<>> + (dx, w, dy, starts, height, width, numSeq, contextLength); + } } CHECK_SYNC("RowConvGrad"); diff --git a/paddle/function/RowConvOpTest.cpp b/paddle/function/RowConvOpTest.cpp index 9898df1a974..1c95d3ff2cc 100644 --- a/paddle/function/RowConvOpTest.cpp +++ b/paddle/function/RowConvOpTest.cpp @@ -47,23 +47,16 @@ void testRowConvBw(size_t batchSize, size_t dim, size_t contextLength) { } TEST(RowConv, real) { - // for (size_t numSamples : {17, 129}) { - // for (size_t dim : {16, 248}) { - // for (size_t context: {3, 7, 65}) { - LOG(INFO) << "==========="; - // for (size_t numSamples : {17}) { - // for (size_t dim : {16}) { - // for (size_t context: {3}) { - size_t numSamples = 17; - size_t dim = 16; - size_t context = 3; - LOG(INFO) << " numSamples=" << numSamples << " dim=" << dim - << " context length=" << context; - testRowConvFw(numSamples, dim, context); - // testRowConvBw(numSamples, dim, context); - // } - // } - // } + for (size_t numSamples : {17, 129, 2020}) { + for (size_t dim : {16, 512, 2560}) { + for (size_t context : {3, 19, 65}) { + VLOG(3) << " numSamples=" << numSamples << " dim=" << dim + << " context length=" << context; + testRowConvFw(numSamples, dim, context); + testRowConvBw(numSamples, dim, context); + } + } + } } } // namespace paddle diff --git a/paddle/gserver/layers/RowConvLayer.cpp b/paddle/gserver/layers/RowConvLayer.cpp index d4b14062977..5302e0e1a8f 100644 --- a/paddle/gserver/layers/RowConvLayer.cpp +++ b/paddle/gserver/layers/RowConvLayer.cpp @@ -75,7 +75,7 @@ void RowConvLayer::backward(const UpdateCallback& callback) { BufferArgs outputs; inputs.addArg(*getOutputGrad(), *startPos); inputs.addArg(*getInputValue(0), *startPos); - inputs.addArg(*weight_->getW(), *startPos); + inputs.addArg(*weight_->getW(), wDims_); MatrixPtr inGrad = getInputGrad(0); MatrixPtr wGrad = weight_->getWGrad(); diff --git a/paddle/gserver/layers/RowConvLayer.h b/paddle/gserver/layers/RowConvLayer.h index 05be6ca6a9b..b3bdda2f354 100644 --- a/paddle/gserver/layers/RowConvLayer.h +++ b/paddle/gserver/layers/RowConvLayer.h @@ -37,9 +37,7 @@ protected: // fan_out is the size of output feature. std::unique_ptr weight_; - // std::unique_ptr biases_; - - // how many steps to look ahead + // The step number to look ahead plus one equals contexLength_. size_t contexLength_; TensorShape wDims_; }; diff --git a/python/paddle/trainer/config_parser.py b/python/paddle/trainer/config_parser.py index 5d540664a7f..9066ce05f33 100644 --- a/python/paddle/trainer/config_parser.py +++ b/python/paddle/trainer/config_parser.py @@ -2081,6 +2081,23 @@ class MaxOutLayer(LayerBase): g_layer_map[input_layer.name].width, out_channels) +@config_layer('row_conv') +class RowConvLayer(LayerBase): + def __init__(self, name, inputs, context_length, **xargs): + super(RowConvLayer, self).__init__( + name, 'maxout', 0, inputs=inputs, **xargs) + config_assert( + len(self.inputs) == 1, + 'TransLayer must have one and only one input') + input_layer = self.get_input_layer(0) + row_conv_conf = self.config.inputs[0].row_conv_conf + row_conv_conf.context_length = context_length + self.set_layer_size(input_layer.size) + psize = context_length * input_layer.size + dims = [context_length, input_layer.size] + self.create_input_parameter(0, psize, dims) + + # key: cost type # value: cost class g_cost_map = {} diff --git a/python/paddle/trainer_config_helpers/layers.py b/python/paddle/trainer_config_helpers/layers.py index 81cce31fecc..47b62328772 100755 --- a/python/paddle/trainer_config_helpers/layers.py +++ b/python/paddle/trainer_config_helpers/layers.py @@ -120,6 +120,7 @@ __all__ = [ 'smooth_l1_cost', 'layer_support', 'multiplex_layer', + 'row_conv_layer', ] @@ -187,6 +188,7 @@ class LayerType(object): SPP_LAYER = "spp" PAD_LAYER = "pad" MULTIPLEX_LAYER = "multiplex" + ROW_CONV_LAYER = "row_conv" PRINT_LAYER = "print" PRIORBOX_LAYER = "priorbox" @@ -5528,3 +5530,77 @@ def multiplex_layer(input, name=None, layer_attr=None): layer_type=LayerType.MULTIPLEX_LAYER, parents=input, size=l.config.size) + + +@wrap_name_default() +@wrap_act_default(act=LinearActivation()) +@wrap_param_attr_default() +@layer_support(DROPOUT) +def row_conv_layer(input, + context_len, + act=None, + name=None, + param_attr=None, + layer_attr=None): + """ + + The row convolution is called lookahead convolution. It is firstly + introduced in paper of `Deep Speech 2: End-toEnd Speech Recognition + in English and Mandarin `_ . + + The bidirectional RNN that learns representation for a sequence by + performing a forward and a backward pass through the entire sequence. + However, unlike unidirectional RNNs, bidirectional RNNs are challenging + to deploy in an online and low-latency setting. The lookahead convolution + incorporates information from future subsequences in a computationally + efficient manner to improve unidirectional recurrent neural networks. + + The connection of row convolution is different form the 1D sequence + convolution. Assumed that, the future context-length is k, that is to say, + it can get the output at timestep t by using the the input feature from t-th + timestep to (t+k+1)-th timestep. Assumed that the hidden dim of input + activations are d, the activations r_t for the new layer at time-step t are: + + .. math:: + + r_{t,r} = \sum_{j=1}^{k + 1} {w_{i,j}h_{t+j-1, i}} + \quad \text{for} \quad (1 \leq i \leq d) + + Note: + The `context_len` is `k + 1`. That is to say, the lookahead step + number plus one equals context_len. + + + .. code-block:: python + + row_conv = row_conv_layer(input=input_layer, context_len=3) + + + :param input: The input layer. + :type input: LayerOutput + :param context_len: The context length equals the lookahead step number + plus one. + :type context_len: int + :param act: Activation Type. Default is linear activation. + :type act: BaseActivation + :param param_attr: The Parameter Attribute. If None, the parameter will be + initialized smartly. It's better set it by yourself. + :type param_attr: ParameterAttribute + :param layer_attr: Extra Layer config. + :type layer_attr: ExtraLayerAttribute|None + :return: LayerOutput object. + :rtype: LayerOutput + + """ + assert isinstance(input, LayerOutput) + assert context_len > 0, "the context_len must be greatet than 0." + + Layer( + inputs=[Input(input.name, **param_attr.attr)], + name=name, + context_length=context_len, + type=LayerType.ROW_CONV_LAYER, + active_type=act.name, + **ExtraLayerAttribute.to_kwargs(layer_attr)) + return LayerOutput( + name, LayerType.ROW_CONV_LAYER, input, activation=act, size=input.size) diff --git a/python/paddle/trainer_config_helpers/tests/configs/file_list.sh b/python/paddle/trainer_config_helpers/tests/configs/file_list.sh index 981ccbf2483..db3d3c65505 100755 --- a/python/paddle/trainer_config_helpers/tests/configs/file_list.sh +++ b/python/paddle/trainer_config_helpers/tests/configs/file_list.sh @@ -5,6 +5,6 @@ last_first_seq test_expand_layer test_ntm_layers test_hsigmoid img_layers img_trans_layers util_layers simple_rnn_layers unused_layers test_cost_layers test_rnn_group shared_fc shared_lstm shared_gru test_cost_layers_with_weight test_spp_layer test_bilinear_interp test_maxout test_bi_grumemory math_ops -test_seq_concat_reshape test_pad test_smooth_l1 test_multiplex_layer) +test_seq_concat_reshape test_pad test_smooth_l1 test_multiplex_layer test_row_conv) export whole_configs=(test_split_datasource) diff --git a/python/paddle/trainer_config_helpers/tests/configs/protostr/test_row_conv.protostr b/python/paddle/trainer_config_helpers/tests/configs/protostr/test_row_conv.protostr new file mode 100644 index 00000000000..9ec15d2a19e --- /dev/null +++ b/python/paddle/trainer_config_helpers/tests/configs/protostr/test_row_conv.protostr @@ -0,0 +1,41 @@ +type: "nn" +layers { + name: "data" + type: "data" + size: 2560 + active_type: "" +} +layers { + name: "__row_conv_layer_0__" + type: "maxout" + size: 2560 + active_type: "relu" + inputs { + input_layer_name: "data" + input_parameter_name: "___row_conv_layer_0__.w0" + row_conv_conf { + context_length: 19 + } + } +} +parameters { + name: "___row_conv_layer_0__.w0" + size: 48640 + initial_mean: 0.0 + initial_std: 0.229415733871 + dims: 19 + dims: 2560 + initial_strategy: 0 + initial_smart: true +} +input_layer_names: "data" +output_layer_names: "__row_conv_layer_0__" +sub_models { + name: "root" + layer_names: "data" + layer_names: "__row_conv_layer_0__" + input_layer_names: "data" + output_layer_names: "__row_conv_layer_0__" + is_recurrent_layer_group: false +} + diff --git a/python/paddle/trainer_config_helpers/tests/configs/test_row_conv.py b/python/paddle/trainer_config_helpers/tests/configs/test_row_conv.py new file mode 100644 index 00000000000..ab33c496b06 --- /dev/null +++ b/python/paddle/trainer_config_helpers/tests/configs/test_row_conv.py @@ -0,0 +1,9 @@ +from paddle.trainer_config_helpers import * + +settings(batch_size=1000, learning_rate=1e-5) + +data = data_layer(name='data', size=2560) + +row_conv = row_conv_layer(input=data, context_len=19, act=ReluActivation()) + +outputs(row_conv) -- GitLab From 62cd5c7ae65d085d3ca0dd85ee9a1b30b542d604 Mon Sep 17 00:00:00 2001 From: dzhwinter Date: Sun, 4 Jun 2017 22:38:54 +0800 Subject: [PATCH 0534/3256] "failed to resolve conflict. apply to HEAD" --- paddle/optimizer/CMakeLists.txt | 29 +++++++ paddle/optimizer/Tensor.h | 28 +++++++ paddle/optimizer/adadelta_optimizer.cc | 47 +++++++++++ paddle/optimizer/adadelta_optimizer.h | 35 ++++++++ paddle/optimizer/adagrad_optimizer.cc | 38 +++++++++ paddle/optimizer/adagrad_optimizer.h | 29 +++++++ paddle/optimizer/adam_optimizer.cc | 38 +++++++++ paddle/optimizer/adam_optimizer.h | 29 +++++++ paddle/optimizer/lr_policy.h | 31 +++++++ paddle/optimizer/optimizer.cc | 75 +++++++++++++++++ paddle/optimizer/optimizer.h | 92 +++++++++++++++++++++ paddle/optimizer/optimizer_factory_test.cpp | 32 +++++++ paddle/optimizer/optimizer_test.cpp | 11 +++ paddle/optimizer/parameter_optimizer.cc | 71 ++++++++++++++++ paddle/optimizer/parameter_optimizer.h | 51 ++++++++++++ paddle/optimizer/regularizer.cc | 24 ++++++ paddle/optimizer/regularizer.h | 45 ++++++++++ paddle/optimizer/sgd_optimizer.h | 33 ++++++++ paddle/optimizer/sgd_optmizer.cc | 60 ++++++++++++++ 19 files changed, 798 insertions(+) create mode 100644 paddle/optimizer/CMakeLists.txt create mode 100644 paddle/optimizer/Tensor.h create mode 100644 paddle/optimizer/adadelta_optimizer.cc create mode 100644 paddle/optimizer/adadelta_optimizer.h create mode 100644 paddle/optimizer/adagrad_optimizer.cc create mode 100644 paddle/optimizer/adagrad_optimizer.h create mode 100644 paddle/optimizer/adam_optimizer.cc create mode 100644 paddle/optimizer/adam_optimizer.h create mode 100644 paddle/optimizer/lr_policy.h create mode 100644 paddle/optimizer/optimizer.cc create mode 100644 paddle/optimizer/optimizer.h create mode 100644 paddle/optimizer/optimizer_factory_test.cpp create mode 100644 paddle/optimizer/optimizer_test.cpp create mode 100644 paddle/optimizer/parameter_optimizer.cc create mode 100644 paddle/optimizer/parameter_optimizer.h create mode 100644 paddle/optimizer/regularizer.cc create mode 100644 paddle/optimizer/regularizer.h create mode 100644 paddle/optimizer/sgd_optimizer.h create mode 100644 paddle/optimizer/sgd_optmizer.cc diff --git a/paddle/optimizer/CMakeLists.txt b/paddle/optimizer/CMakeLists.txt new file mode 100644 index 00000000000..134ca9e9d64 --- /dev/null +++ b/paddle/optimizer/CMakeLists.txt @@ -0,0 +1,29 @@ +include_directories(${CMAKE_CURRENT_BINARY_DIR}) + +set(OPITMIZER_SRCS + adadelta_optimizer.cc + adagrad_optimizer.cc + adam_optimizer.cc + optimizer.cc + parameter_optimizer.cc + sgd_optmizer.cc + regularizer.cc + ) + +set(OPITMIZER_Headers + adadelta_optimizer.h + adagrad_optimizer.h + adam_optimizer.h + lr_policy.h + optimizer.h + parameter_optimizer.h + regularizer.h + sgd_optimizer.h + Tensor.h + ) + +add_library(optimizer STATIC ${OPITMIZER_SRCS}) +add_dependencies(optimizer gen_proto_cpp) + +add_simple_unittest(optimizer_test) +add_simple_unittest(optimizer_factory_test) diff --git a/paddle/optimizer/Tensor.h b/paddle/optimizer/Tensor.h new file mode 100644 index 00000000000..a8387c4df41 --- /dev/null +++ b/paddle/optimizer/Tensor.h @@ -0,0 +1,28 @@ +#ifndef PADDLE_OPTIMIZER_TENSOR_H_ +#define PADDLE_OPTIMIZER_TENSOR_H_ +/** + * @brief tensor used by optimizer + */ + +#include +#include "paddle/math/BaseMatrix.h" + +namespace paddle { +namespace optimizer { + +template +using TensorBase = BaseMatrixT; + +template +class Tensor : public TensorBase { +public: + Tensor(T* data, int size) : TensorBase(size, 1, 0, data, false, false) {} + T* get_buffer() { return this->data_; } + // TODO: replace with tensorshape + size_t width() { return this->width_; } +}; + +} // namespace optimizer +} // namespace paddle + +#endif diff --git a/paddle/optimizer/adadelta_optimizer.cc b/paddle/optimizer/adadelta_optimizer.cc new file mode 100644 index 00000000000..39d465cebe6 --- /dev/null +++ b/paddle/optimizer/adadelta_optimizer.cc @@ -0,0 +1,47 @@ +#include "adadelta_optimizer.h" +#include + +namespace paddle { +namespace optimizer { +template +AdadeltaOptimizer::AdadeltaOptimizer(const ::paddle::OptimizerConfig& config) + : ParameterOptimizer(config) { + rho = config.adadelta().rho(); + epsilon = config.adadelta().epsilon(); + decay = config.adadelta().decay(); +} + +template +void AdadeltaOptimizer::set_weight(const Tensor* p) { + size_t size = p->width(); + T* gptr = new T[size]; + accum_gradient = Tensor(gptr, size); + T* dptr = new T[size]; + accum_delta = Tensor(dtpr, size); + T* dptr_current = new T[size]; + update_delta = Tensor(dptr_current, size); +} + +template +void AdadeltaOptimizer::update(const Tensor& gradient) { + num_sample_passed += 1; + double learning_rate = lr_policy->get_learning_rate(); + for (size_t i = 0; i < parameter_.size(); ++i) { + accum_gradient[i] = + rho * accum_gradient[i] + (1.0 - rho) * gradient[i] * gradient[i]; + + update_delta[i] = std::sqrt(accum_delta[i] + epsilon) / + std::sqrt(accum_gradient[i] + epsilon) * gradient[i]; + + accum_delta[i] = + rho * accum_delta[i] + (1.0 - rho) * update_delta[i] * update_delta[i]; + + parameter_[i] -= update_delta[i] + decay * parameter_[i]; + } +} + +template class AdadeltaOptimizer; +template class AdadeltaOptimizer; + +} // namespace optimizer +} // namespace paddle diff --git a/paddle/optimizer/adadelta_optimizer.h b/paddle/optimizer/adadelta_optimizer.h new file mode 100644 index 00000000000..1a8c03f2682 --- /dev/null +++ b/paddle/optimizer/adadelta_optimizer.h @@ -0,0 +1,35 @@ +#ifndef PADDLE_ADADELTA_OPTIMIZER_H_ +#define PADDLE_ADADELTA_OPTIMIZER_H_ + +#include "parameter_optimizer.h" + +namespace paddle { +namespace optimizer { + +template +class AdadeltaOptimizer : public ParameterOptimizer { +public: + AdadeltaOptimizer(const OptimizerConfig &config); + ~AdadeltaOptimizer() { + if (accum_gradient) delete accum_gradient; + if (accum_delta) delete accum_delta; + if (update_delta) delete update_delta; + } + void update(const Tensor &gradient); + void set_weight(const Tensor *p); + T *get_weight() const; + +private: + Tensor *accum_gradient; + Tensor *accum_delta; + Tensor *update_delta; + + double rho; + double epsilon; + double decay; +}; + +} // namespace optimizer +} // namespace paddle + +#endif diff --git a/paddle/optimizer/adagrad_optimizer.cc b/paddle/optimizer/adagrad_optimizer.cc new file mode 100644 index 00000000000..40402a67108 --- /dev/null +++ b/paddle/optimizer/adagrad_optimizer.cc @@ -0,0 +1,38 @@ +#include "adagrad_optimizer.h" + +namespace paddle { +namespace optimizer { +template +AdagradOptimizer::AdagradOptimizer(const ::paddle::OptimizerConfig& config) + : ParameterOptimizer(config) { + epsilon = config.adagrad().epsilon(); + decay = config.adagrad().decay(); +} + +template +void AdagradOptimizer::set_weight(const Tensor* p) { + size_t size = p->width(); + T* gptr = new T[size]; + accum_gradient = Tensor(gptr, size); + T* dptr = new T[size]; + accum_delta = Tensor(dtpr, size); + T* dptr_current = new T[size]; + update_delta = Tensor(dptr_current, size); +} + +template +void AdagradOptimizer::update(const Tensor& gradient) { + num_sample_passed += 1; + double learning_rate = lr_policy->get_learning_rate(); + for (size_t i = 0; i < parameter_.size(); ++i) { + accum_gradient[i] += gradient[i] * gradient[i]; + parameter_[i] += + learning_rate * (gradient[i] / std::sqrt(accum_gradient[i] + epsilon) + + decay * parameter_[i]); + } +} + +template class AdagradOptimizer; +template class AdagradOptimizer; +} // namespace optimizer +} // namespace paddle diff --git a/paddle/optimizer/adagrad_optimizer.h b/paddle/optimizer/adagrad_optimizer.h new file mode 100644 index 00000000000..1ec438fd05a --- /dev/null +++ b/paddle/optimizer/adagrad_optimizer.h @@ -0,0 +1,29 @@ +#ifndef PADDLE_ADAGRAD_OPTIMIZER_H_ +#define PADDLE_ADAGRAD_OPTIMIZER_H_ + +#include "parameter_optimizer.h" + +namespace paddle { +namespace optimizer { + +template +class AdagradOptimizer : public ParameterOptimizer { +public: + AdagradOptimizer(const OptimizerConfig &config); + ~AdagradOptimizer() { + if (accum_gradient) delete accum_gradient; + } + void update(const Tensor &gradient); + void set_weight(const Tensor *p); + T *get_weight() const; + +private: + Tensor *accum_gradient; + double epsilon; + double decay; +}; + +} // namespace optimizer +} // namespace paddle + +#endif diff --git a/paddle/optimizer/adam_optimizer.cc b/paddle/optimizer/adam_optimizer.cc new file mode 100644 index 00000000000..c2303c6545e --- /dev/null +++ b/paddle/optimizer/adam_optimizer.cc @@ -0,0 +1,38 @@ +#include "adam_optimizer.h" + +namespace paddle { +namespace optimizer { +template +AdamOptimizer::AdamOptimizer(const ::paddle::OptimizerConfig &config) + : ParameterOptimizer(config) { + beta_1 = config.adam().beta_1(); + beta_2 = config.adam().beta_2(); + epsilon = config.adam().epsilon(); + decay = config.adam().decay(); +} + +template +void AdamOptimizer::set_weight(const Tensor *p) { + size_t size = p->width(); + T *mptr = new T[size]; + momentums_ = Tensor(mptr, size); + T *vptr = new T[size]; + velocitys_ = Tensor(vtpr, size); +} + +template +void AdamOptimizer::update(const Tensor &gradient) { + num_sample_passed += 1; + double learning_rate = lr_policy->get_learning_rate(); + for (size_t i = 0; i < parameter_.size(); ++i) { + accum_gradient[i] += gradient[i] * gradient[i]; + parameter_[i] += + learning_rate * (gradient[i] / std::sqrt(accum_gradient[i] + epsilon) + + decay * parameter_[i]); + } +} + +template class AdamOptimizer; +template class AdamOptimizer; +} // namespace optimizer +} // namespace paddle diff --git a/paddle/optimizer/adam_optimizer.h b/paddle/optimizer/adam_optimizer.h new file mode 100644 index 00000000000..ceec18eb336 --- /dev/null +++ b/paddle/optimizer/adam_optimizer.h @@ -0,0 +1,29 @@ +#ifndef PADDLE_ADAM_OPTIMIZER_H_ +#define PADDLE_ADAM_OPTIMIZER_H_ + +#include "parameter_optimizer.h" + +namespace paddle { +namespace optimizer { + +template +class AdamOptimizer : public ParameterOptimizer { +public: + AdamOptimizer(const OptimizerConfig &config); + ~AdamOptimizer() {} + void update(const Tensor &gradient); + void set_weight(const Tensor *p); + T *get_weight() const; + +private: + Tensor *momentums_; + Tensor *velocitys_; + double beta_1; + double beta_2; + double epsilon; + double decay; +}; + +} // namespace optimizer +} // namespace paddle +#endif diff --git a/paddle/optimizer/lr_policy.h b/paddle/optimizer/lr_policy.h new file mode 100644 index 00000000000..6977b68de7b --- /dev/null +++ b/paddle/optimizer/lr_policy.h @@ -0,0 +1,31 @@ +#ifndef PADDLE_OPTIMIZER_LR_POLICY_H_ +#define PADDLE_OPTIMIZER_LR_POLICY_H_ + +#include "OptimizerConfig.ph.h" + +namespace paddle { +namespace optimizer { + +class BaseLr { +public: + LrPolicyBase(const OpitmizerConfig &config) { + learning_rate = config.lr_config().learning_rate(); + } + virtual double get_learning_rate(const uint64_t num_sample_passed) = 0; + +private: + double learning_rate; +}; + +// constant learning rate policy +class ConstLr final : public BaseLr { +public: + double get_learning_rate(const uint64_t num_sample_passed) { + return learning_rate; + } +}; + +} // namespace optimizer +} // namespace paddle + +#endif diff --git a/paddle/optimizer/optimizer.cc b/paddle/optimizer/optimizer.cc new file mode 100644 index 00000000000..e72881e5d0d --- /dev/null +++ b/paddle/optimizer/optimizer.cc @@ -0,0 +1,75 @@ +#include "optimizer.h" +#include + +#include "parameter_optimizer.h" + +template +struct EnumToType {}; + +template +struct TypeToEnum {}; + +#define MATCH_ENUM_TYPE(TYPE, ENUM) \ + template <> \ + struct TypeToEnum { \ + static paddle_element_type v() { return ENUM; }; \ + static constexpr TYPE value = ENUM; +} +; +template <> +struct EnumToType { + typedef TYPE Type; +} + +MATCH_ENUM_TYPE(int32_t, PADDLE_ELEMENT_TYPE_INT32); +MATCH_ENUM_TYPE(uint32_t, PADDLE_ELEMENT_TYPE_UINT32); +MATCH_ENUM_TYPE(int64_t, PADDLE_ELEMENT_TYPE_INT64); +MATCH_ENUM_TYPE(uint64_t, PADDLE_ELEMENT_TYPE_UINT64); +MATCH_ENUM_TYPE(float, PADDLE_ELEMENT_TYPE_FLOAT32); +MATCH_ENUM_TYPE(double, PADDLE_ELEMENT_TYPE_FLOAT64); + +struct paddle_optimizer { + /*! \brief optmizer in C++ side */ + + paddle::optimizer::ParameterOptimzier* impl; +}; + +paddle_optimizer* paddle_create_optimizer(const unsigned char* config_proto, + int config_proto_len) { + paddle_optimizer* optimizer; + std::string config(config_proto, config_proto + config_proto_len); + optimizer->impl->create(config_proto); + return optimizer; +} + +int paddle_release_optimizer(paddle_optimizer* o) { + if (o != nullptr) delete o->impl; + return PADDLE_SUCCESS; +} + +int paddle_update_parameter(paddle_optimizer* o, + paddle_element_type data_type, + const void* grad_buffer, + int num_bytes) { + auto type = EnumToType::Type; + paddle::Tensor gradient(reinterpret_cast(grad_buffer), + num_bytes); + o->impl->update(gradient); + return PADDLE_SUCCESS; +} + +int paddle_optimizer_set_weights(paddle_optimizer* o, + paddle_element_type data_type, + void* param_buffer, + int num_bytes) { + auto type = EnumToType::Type; + paddle::Tensor* param = new paddle::Tensor( + reinterpret_cast(param_buffer), num_bytes); + o->impl->set_weight(param); + return PADDLE_SUCCESS; +} + +void* paddle_optimizer_get_weights(paddle_optimizer* o) { + void* buffer = (void*)o->impl->get_weight(); + return buffer; +} diff --git a/paddle/optimizer/optimizer.h b/paddle/optimizer/optimizer.h new file mode 100644 index 00000000000..0eba2e78118 --- /dev/null +++ b/paddle/optimizer/optimizer.h @@ -0,0 +1,92 @@ +#ifndef PADDLE_LIB_OPTIMIZER_H_ +#define PADDLE_LIB_OPTIMIZER_H_ +#include +#include + +/*! \brief optimizer export C API. which will be used in + Case A, on Trainer (On ParameterServer Client) optimize gradient + + Case B, on ParameterServer side optimize gradient + + To simplify the configuration parsing. optimizer *do not* parse any config + e.g. learning rate should be calculated by the caller + */ + +#ifdef __cplusplus +extern "C" { +#endif +/*! \brief datatypes */ +typedef enum { + PADDLE_ELEMENT_TYPE_INT32 = 0, + PADDLE_ELEMENT_TYPE_UINT32 = 1, + PADDLE_ELEMENT_TYPE_INT64 = 2, + PADDLE_ELEMENT_TYPE_UINT64 = 3, + PADDLE_ELEMENT_TYPE_FLOAT32 = 4, + PADDLE_ELEMENT_TYPE_FLOAT64 = 5, +} paddle_element_type; + +/*! \brief execute status code */ +const int32_t PADDLE_SUCCESS = 0; +const int32_t PADDLE_ERROR = -1; + +typedef struct paddle_optimizer paddle_optimizer; +/** + * this group interface called in order : + * 1. create optimizer with config + * 2. set weights + * 3. update_parameter + * 4. get_weights + * 5. release optimizer + */ + +/** + * @brief create optimizer with proto_config + * @param config_proto, optimizer protobuf, see OptimizerConfig.proto in detail + * @return return optimizer instance + */ +paddle_optimizer* paddle_create_optimizer(const unsigned char* config_proto, + int config_proto_len); + +/** + * @brief release optimizer + * @param optimizer + * @return return exec status + */ +int paddle_release_optimizer(paddle_optimizer* o); + +/** + * @brief optimizer instance + * @param datatype of gradient and parameter + * @param gradient, calculate by optimzizer caller. + * TODO(zhihong): just pass loss to reduce communicate overhead. + * Project Adam Ms'14 paper for detail + * @param num_bytes, gradient size + * @return return exec status + */ +int paddle_update_parameter(paddle_optimizer* o, + paddle_element_type data_type, + const void* gradient, + int num_bytes); + +/** + * @brief optimizer instance + * @param data_type datatype of gradient + * @param param_buffer, initilized parameter buffer + * @param num_bytes, parameter size + * @return return exec status + */ +int paddle_optimizer_set_weights(paddle_optimizer* o, + paddle_element_type data_type, + void* param_buffer, + int num_bytes); + +/** + * @brief optimizer instance + * @return return content of parameter buffer in optimizer + */ +void* paddle_optimizer_get_weights(paddle_optimizer* o); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/paddle/optimizer/optimizer_factory_test.cpp b/paddle/optimizer/optimizer_factory_test.cpp new file mode 100644 index 00000000000..67a9506996f --- /dev/null +++ b/paddle/optimizer/optimizer_factory_test.cpp @@ -0,0 +1,32 @@ +#include "optimizer_factory.h" +#include "gtest/gtest.h" +#include "parameter_optimizer.h" + +#define float TestType; + +class OptimizerTest : public testing::Test { +public: + virtual void SetUp() { + paddle::OptimizerConfig config; + config.set_learning_rate(0.01); + config.set_decay(0.0); + config.set_momentum(0.0); + config.set_nesterov(false); + config.set_lr_decay_a(0.9); + config.set_lr_decay_b(0.1); + + std::string config_proto = config.SerializeAsString(); + ParameterOptimizer::create(config_proto, ) + } + virtual void TearDown() {} + +private: + ParameterOptimizer* o; +}; + +TEST_F(OptimizerTest, createOptimizer) {} + +int main(int argc, char** argv) { + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/paddle/optimizer/optimizer_test.cpp b/paddle/optimizer/optimizer_test.cpp new file mode 100644 index 00000000000..1bdc6f40fca --- /dev/null +++ b/paddle/optimizer/optimizer_test.cpp @@ -0,0 +1,11 @@ +#include "optimizer.h" +#include "gtest/gtest.h" + +template +class Opitmizer_C_Test : public testing::Test { +private: + Tensor parameter; + Tensor gradient; +}; + +void applyGradientDescent_TEST() {} diff --git a/paddle/optimizer/parameter_optimizer.cc b/paddle/optimizer/parameter_optimizer.cc new file mode 100644 index 00000000000..c5e9e0acc30 --- /dev/null +++ b/paddle/optimizer/parameter_optimizer.cc @@ -0,0 +1,71 @@ +#include "parameter_optimizer.h" +#include +#include "optimizer_factory.h" + +namespace paddle { +namespace optimizer { + +template +ParameterOptimizer *ParameterOptimizer::create( + const ::std::string &config_proto) { + paddle::OptimizerConfig config; + CHECK(config.ParseFromString(config_proto) == 0) + << "error : optimizer config"; + CHECK(config_valid(config) == 0) << "error : invalid optimizer config "; + ParameterOptimizer *opt = nullptr; + switch (config.optimizer_name()) { + case "SGD": + opt = new SGDOptimizer(config); + break; + case "Adagrad": + opt = new AdagradOptimizer(config); + break; + case "Adadelta": + opt = new AdadeltaOptimizer(config); + break; + case "Adam": + opt = new AdamOptimizer(config); + break; + default: + opt = new SGDOptimizer(config); + } + + switch (config.lr_policy()) { + case "ConstLr": + opt.lr_policy = new ConstLr(config); + break; + } + return opt; +} + +template +T *ParameterOptimizer::get_weight() const { + return parameter.get().get_buffer(); +} + +template +char *ParameterOptimizer::get_config_proto() const { + // set config dynamic value for save checkpoint + config_.lr_policy().set_learning_rate( + lr_policy->get_learning_rate(num_sample_passed)); + config_.set_num_sample_passed(num_sample_passed); + config_.set_iterations(iterations); + return config_.SerializeAsString().c_str(); +} + +template +void ParameterOptimizer::set_weight(const Tensor *p) { + parameter_ = p; +} + +template +bool ParameterOptimizer::config_valid(const ::std::string &config) const { + // TODO(zhihong) : add more value checker, failed ASAP + return true; +} + +template class ParameterOptimzier; +template class ParameterOptimzier; + +} // namespace optimizer +} // namespace paddle diff --git a/paddle/optimizer/parameter_optimizer.h b/paddle/optimizer/parameter_optimizer.h new file mode 100644 index 00000000000..d5914857af0 --- /dev/null +++ b/paddle/optimizer/parameter_optimizer.h @@ -0,0 +1,51 @@ +#ifndef PADDLE_PARAMETER_OPTIMIZER_H_ +#define PADDLE_PARAMETER_OPTIMIZER_H_ + +#include +#include +#include +#include "OptimizerConfig.pb.h" +#include "Tensor.h" +#include "lr_policy.h" + +namespace paddle { +namespace optimizer { + +template +class ParameterOptimizer { +public: + /** + * @brief update hook for algorithm need to traverse parameter more than + * once. + */ + ParameterOptimizer(const OptimizerConfig &config) : config_(config){}; + + static ParameterOptimizer *create(const ::std::string &config_proto); + virtual void update(const Tensor &gradient) = 0; + virtual void destroy() = 0; + virtual T *get_weight() const; + virtual void set_weight(const Tensor *parameter); + // package optimizer config proto in runtime for saving checkpoint + virtual char *get_config_proto(); + ~ParameterOptimzier() { delete parameter_; } + +private: + bool config_valid(::std::string &config) const; + OptimizerConfig config_; + Tensor *parameter_; + + // learning rate policy + BaseLr *lr_policy; + uint64_t num_sample_passed; + + ParameterOptimizer(const ParameterOptimizer &) = delete; + ParameterOptimizer &operator=(const ParameterOptimizer &) = delete; + /** + * @brief indicate if use L1, L2 regularizer + */ +}; + +} // namespace optimizer +} // namespace paddle + +#endif diff --git a/paddle/optimizer/regularizer.cc b/paddle/optimizer/regularizer.cc new file mode 100644 index 00000000000..dd21c20e711 --- /dev/null +++ b/paddle/optimizer/regularizer.cc @@ -0,0 +1,24 @@ +#include "regularizer.h" + +namespace paddle { +namespace optimizer { + +template +Regularizer* Regularizer::create(const std::string& config) { + paddle::OptimizerConfig config; + Regularizer* r; + if (config.regularizer_type() == paddle::OptimizerConfig_RegularizerType_L1) { + r = new L1Regularizer(config); + } else if (config.regularizer_type() == + paddle::OptimizerConfig_RegularizerType_L2) { + r = new L2Regularizer(config); + break; + } + return r; +} + +template class L1Regularizer; +template class L1Regularizer; + +} // namespace optimizer +} // namespace paddle diff --git a/paddle/optimizer/regularizer.h b/paddle/optimizer/regularizer.h new file mode 100644 index 00000000000..e37211ce230 --- /dev/null +++ b/paddle/optimizer/regularizer.h @@ -0,0 +1,45 @@ +#ifndef PADDLE_OPITMIZER_REGULARIZER_H_ +#define PADDLE_OPTIMIZER_REGULARIZER_H_ + +#include "OptimizerConfig.pb.h" +#include "Tensor.h" + +namespace paddle { +namespace optimizer { + +/** + * @brief regularizer in L1, L2 + */ + +template +class Regularizer { +public: + /** + * @brief regularizer update interface + * @param param need to update + * @return void + */ + static Regularizer *create(const std::string &config); + virtual void update(Tensor ¶meter) = 0; + +private: + std::string regularizer_name; + OptimizerConfig config_; +}; + +template +class L1Regularizer { +public: + void update(Tensor ¶meter); +}; + +template +class L2Regularizer { +public: + void update(Tensor ¶meter); +}; + +} // namespace optimizer +} // namespace paddle + +#endif diff --git a/paddle/optimizer/sgd_optimizer.h b/paddle/optimizer/sgd_optimizer.h new file mode 100644 index 00000000000..4e1d9669c96 --- /dev/null +++ b/paddle/optimizer/sgd_optimizer.h @@ -0,0 +1,33 @@ +#ifndef PADDLE_SGD_OPTIMIZER_H_ +#define PADDLE_SGD_OPTIMIZER_H_ + +#include "parameter_optimizer.h" + +namespace paddle { +namespace optimizer { + +template +class SGDOptimizer : public ParameterOptimizer { +public: + SGDOptimizer(const ::paddle::OptimizerConfig& config); + ~SGDOptimizer() { + // clear memory by Tensor library + delete momentums_; + } + void update(const Tensor& gradient); + + void set_weight(const Tensor* p); + T* get_weight() const; + char* get_config_proto(); + +private: + Tensor* momentums_; + double momentum; + double decay; + bool nesterov; +}; + +} // namespace optimizer +} // namespace paddle + +#endif diff --git a/paddle/optimizer/sgd_optmizer.cc b/paddle/optimizer/sgd_optmizer.cc new file mode 100644 index 00000000000..ff23d46dc6f --- /dev/null +++ b/paddle/optimizer/sgd_optmizer.cc @@ -0,0 +1,60 @@ +#include "sgd_optimizer.h" + +namespace paddle { +namespace optimizer { + +template +SGDOptimizer::SGDOptimizer(const ::paddle::OptimizerConfig &config) + : ParameterOptimizer(config) { + momentum = config.sgd().momentum(); + decay = config.sgd().decay(); + nesterov = config.sgd().nesterov(); +} + +template +void SGDOptimizer::set_weight(const Tensor *p) { + // ParameterOptimizer::set_weight(p); + size_t size = p->width(); + // TODO: fix it with align aware allocator bind to Tensor + if (momentum != 0.0) { + T *ptr = new T[size]; + momentums_ = Tensor(ptr, size); + } +} + +template +void SGDOptimizer::update(const Tensor &gradient) { + num_sample_passed += 1; + double learning_rate = lr_policy->get_learning_rate(num_sample_passed); + double velocity = 0.0; + for (size_t i = 0; i < parameter_.size(); ++i) { + if (momentum == 0.0) { + velocity = + -learning_rate * gradient[i] - learning_rate * decay * parameter_[i]; + } else { + momentums_[i] = momentum * momentums_[i] - learning_rate * gradient[i] - + learning_rate * decay * parameter_[i]; + velocity = momentums_[i]; + } + if (nesterov) { + parameter_[i] += momentum * velocity - learning_rate * gradient[i]; + } else { + parameter_[i] += velocity; + } + } +} + +template +char *SGDOptimizer::get_config_proto() { + ParameterOptimizer::get_config_proto(); + config.set_learning_rate(learning_rate); + config.set_decay(decay); + config.set_nesterov(nesterov); + return config.SerializeAsString().c_str(); +} + +template class SGDOptimizer; +template class SGDOptimizer; + +} // namespace optimizer +} // namespace paddle -- GitLab From 3158efe9503402e97d21eae38f63c5f7471b8e20 Mon Sep 17 00:00:00 2001 From: dzhwinter Date: Sun, 4 Jun 2017 22:43:00 +0800 Subject: [PATCH 0535/3256] "move cmake scripts too" --- paddle/CMakeLists.txt | 1 + proto/CMakeLists.txt | 1 + proto/OptimizerConfig.proto | 99 +++++++++++++++++++++++++++++++++++++ 3 files changed, 101 insertions(+) create mode 100644 proto/OptimizerConfig.proto diff --git a/paddle/CMakeLists.txt b/paddle/CMakeLists.txt index 9898dc083eb..1dc1383dd2e 100644 --- a/paddle/CMakeLists.txt +++ b/paddle/CMakeLists.txt @@ -8,6 +8,7 @@ add_subdirectory(gserver) add_subdirectory(pserver) add_subdirectory(trainer) add_subdirectory(scripts) +add_subdirectory(optimizer) # Do not build go directory until go cmake is working smoothly. # if(CMAKE_Go_COMPILER) diff --git a/proto/CMakeLists.txt b/proto/CMakeLists.txt index 62d5b9e38b2..9b98dd3fde4 100644 --- a/proto/CMakeLists.txt +++ b/proto/CMakeLists.txt @@ -5,6 +5,7 @@ set(proto_filenames ParameterConfig.proto ParameterService.proto TrainerConfig.proto + OptimizerConfig.proto ParameterServerConfig.proto) set(PROTO_GEN) diff --git a/proto/OptimizerConfig.proto b/proto/OptimizerConfig.proto new file mode 100644 index 00000000000..c1080f4e168 --- /dev/null +++ b/proto/OptimizerConfig.proto @@ -0,0 +1,99 @@ +syntax = "proto2"; + +option optimize_for = LITE_RUNTIME; + +package paddle; + +message SGDConfig { + // SGD + // momentum: float >= 0. Parameter updates momentum. + // decay: float >= 0. Learning rate decay over each update. + // nesterov: boolean. Whether to apply Nesterov momentum. + optional double momentum = 21 [default = 0.0]; + optional double decay = 23 [default = 0.0]; + optional bool nesterov =24 [default = false]; + + + +message AdadeltaConfig { + // Adadelta + // It is recommended to leave it at the default value. + // rho: float >= 0. + // epsilon: float >= 0. Fuzz factor. + // decay: float >= 0. Learning rate decay over each update. + + // reference : [Adadelta - an adaptive learning rate method](http://arxiv.org/abs/1212.5701) + optional double rho = 33 [default = 0.90]; + optional double epsilon = 31 [default = 1e-5]; + optional double decay = 32 [default = 0.0]; + +} + +message AdagradConfig { +// Adagrad +// epsilon: float >= 0. +// decay: float >= 0. Learning rate decay over each update. + +// reference : [Adaptive Subgradient Methods for Online Learning and Stochastic Optimization](http://www.jmlr.org/papers/volume12/duchi11a/duchi11a.pdf) + optional double epsilon = 41 [default = 1e-5]; + optional double decay = 42 [default = 0.0]; +} + +message AdamConfig { + // Adaj + // beta_1: float, 0 < beta < 1. Generally close to 1. + // beta_2: float, 0 < beta < 1. Generally close to 1. + // epsilon: float >= 0. Fuzz factor. + // decay: float >= 0. Learning rate decay over each update. + // reference : [Adam - A Method for Stochastic Optimization](http://arxiv.org/abs/1412.6980v8) + optional double beta_1 = 41; + optional double beta_2 = 42; + optional double epsilon = 43; + optional double decay = 44; +} + +message LearningRateConfig { + // learninRate Policy + required double learning_rate = 40 [default = 1.0]; + optional double lr_decay_a = 25; + optional double lr_decay_b = 26; +} + + +message OptimizerConfig { + // common config of optimizer + required string optimizer_name = 1; + // algorithm config + enum OptimizerType { + SGD = 1; + Adadelta = 2; + Adagrad = 3; + Adam = 4; + } + required OptimizerType optimizer_type = 2; + optional SGDConfig sgd = 3; + optional AdadeltaConfig adadelta = 4; + optional AdagradConfig adagrad = 5; + optional AdamConfig adam = 6; + + // learning rate runtime policy config + // lr_policy : string + // ConstLr = 0; + // LinearLr = 1; + required string lr_policy = 11; + required LearningRateConfig lr_config = 12; + optional uint64 num_sample_passed = 13 [default = 0]; + + // reqularizer config + enum RegularizerType { + L1 = 1; + L2 = 2; + L1L2 = 3; + } + optional RegularizerType regularizer_type = 21; + + // common config of optimizer + optional double clipnorm = 101; + optional double clipvalue = 102; + +} -- GitLab From 46e60761d18099c34a936952db095578b411d191 Mon Sep 17 00:00:00 2001 From: Luo Tao Date: Mon, 5 Jun 2017 12:13:44 +0800 Subject: [PATCH 0536/3256] remove duplicated examples, and rename demo to v1_api_demo --- demo/image_classification/.gitignore | 9 - demo/image_classification/api_v2_resnet.py | 74 --- demo/image_classification/api_v2_train.py | 92 ---- demo/image_classification/api_v2_vgg.py | 47 -- .../data/download_cifar.sh | 21 - .../data/process_cifar.py | 89 ---- demo/image_classification/image_provider.py | 89 ---- demo/image_classification/image_util.py | 221 --------- demo/image_classification/predict.sh | 20 - demo/image_classification/prediction.py | 159 ------- demo/image_classification/preprocess.py | 54 --- demo/image_classification/preprocess.sh | 22 - demo/image_classification/train.sh | 32 -- demo/image_classification/vgg_16_cifar.py | 58 --- demo/introduction/.gitignore | 5 - demo/introduction/README.md | 3 - demo/introduction/api_train_v2.py | 58 --- demo/introduction/dataprovider.py | 26 -- demo/introduction/evaluate_model.py | 39 -- demo/introduction/train.sh | 22 - demo/introduction/trainer_config.py | 38 -- demo/mnist/.gitignore | 10 - demo/mnist/api_train.py | 196 -------- demo/mnist/api_train_v2.py | 137 ------ demo/mnist/data/generate_list.py | 21 - demo/mnist/data/get_mnist_data.sh | 21 - demo/mnist/light_mnist.py | 79 ---- demo/mnist/mnist_provider.py | 12 - demo/mnist/mnist_util.py | 30 -- demo/mnist/train.sh | 32 -- demo/mnist/vgg_16_mnist.py | 50 -- demo/recommendation/.gitignore | 10 - demo/recommendation/api_train_v2.py | 125 ----- demo/recommendation/common_utils.py | 30 -- demo/recommendation/data/config.json | 16 - demo/recommendation/data/config_generator.py | 127 ------ demo/recommendation/data/meta_config.json | 81 ---- demo/recommendation/data/meta_generator.py | 430 ------------------ demo/recommendation/data/ml_data.sh | 23 - demo/recommendation/data/split.py | 66 --- demo/recommendation/dataprovider.py | 88 ---- demo/recommendation/evaluate.py | 37 -- demo/recommendation/evaluate.sh | 27 -- demo/recommendation/prediction.py | 51 --- demo/recommendation/preprocess.sh | 40 -- demo/recommendation/requirements.txt | 2 - demo/recommendation/run.sh | 25 - demo/recommendation/trainer_config.py | 98 ---- demo/semantic_role_labeling/.gitignore | 14 - demo/semantic_role_labeling/api_train_v2.py | 277 ----------- .../data/extract_dict_feature.py | 81 ---- .../data/extract_pairs.py | 122 ----- demo/semantic_role_labeling/data/get_data.sh | 29 -- demo/semantic_role_labeling/data/test.list | 1 - demo/semantic_role_labeling/data/train.list | 1 - demo/semantic_role_labeling/dataprovider.py | 71 --- demo/semantic_role_labeling/db_lstm.py | 218 --------- demo/semantic_role_labeling/predict.py | 193 -------- demo/semantic_role_labeling/predict.sh | 43 -- demo/semantic_role_labeling/test.sh | 41 -- demo/semantic_role_labeling/train.sh | 30 -- demo/sentiment/.gitignore | 11 - demo/sentiment/data/get_imdb.sh | 51 --- demo/sentiment/dataprovider.py | 37 -- demo/sentiment/predict.py | 154 ------- demo/sentiment/predict.sh | 27 -- demo/sentiment/preprocess.py | 359 --------------- demo/sentiment/preprocess.sh | 22 - demo/sentiment/sentiment_net.py | 145 ------ demo/sentiment/test.sh | 40 -- demo/sentiment/train.sh | 30 -- demo/sentiment/train_v2.py | 159 ------- demo/sentiment/trainer_config.py | 39 -- demo/seqToseq/.gitignore | 17 - demo/seqToseq/api_train_v2.py | 236 ---------- demo/seqToseq/data/paraphrase_data.sh | 23 - demo/seqToseq/data/paraphrase_model.sh | 37 -- demo/seqToseq/data/wmt14_data.sh | 53 --- demo/seqToseq/data/wmt14_model.sh | 23 - demo/seqToseq/dataprovider.py | 94 ---- demo/seqToseq/paraphrase/train.conf | 33 -- demo/seqToseq/paraphrase/train.sh | 30 -- demo/seqToseq/preprocess.py | 219 --------- demo/seqToseq/seqToseq_net.py | 204 --------- demo/seqToseq/translation/eval_bleu.sh | 42 -- demo/seqToseq/translation/gen.conf | 36 -- demo/seqToseq/translation/gen.sh | 27 -- demo/seqToseq/translation/moses_bleu.sh | 18 - demo/seqToseq/translation/train.conf | 36 -- demo/seqToseq/translation/train.sh | 28 -- demo/vae/dataloader.pyc | Bin 2148 -> 0 bytes demo/word2vec/api_train_v2.py | 100 ---- v1_api_demo/README.md | 5 + {demo => v1_api_demo}/gan/.gitignore | 0 {demo => v1_api_demo}/gan/README.md | 0 .../gan/data/download_cifar.sh | 0 .../gan/data/get_mnist_data.sh | 0 {demo => v1_api_demo}/gan/gan_conf.py | 0 {demo => v1_api_demo}/gan/gan_conf_image.py | 0 {demo => v1_api_demo}/gan/gan_trainer.py | 0 .../model_zoo/embedding/.gitignore | 0 .../model_zoo/embedding/extract_para.py | 0 .../model_zoo/embedding/paraconvert.py | 0 .../model_zoo/embedding/pre_DictAndModel.sh | 0 .../model_zoo/resnet/.gitignore | 0 .../model_zoo/resnet/classify.py | 0 .../model_zoo/resnet/example/.gitignore | 0 .../model_zoo/resnet/example/__init__.py | 0 .../model_zoo/resnet/example/cat.jpg | Bin .../model_zoo/resnet/example/dog.jpg | Bin .../resnet/example/image_list_provider.py | 0 .../model_zoo/resnet/example/test.list | 0 .../model_zoo/resnet/extract_fea_c++.sh | 0 .../model_zoo/resnet/extract_fea_py.sh | 0 .../model_zoo/resnet/get_model.sh | 0 .../model_zoo/resnet/load_feature.py | 0 .../model_zoo/resnet/net_diagram.sh | 0 .../model_zoo/resnet/predict.sh | 0 .../model_zoo/resnet/resnet.py | 0 {demo => v1_api_demo}/quick_start/.gitignore | 0 .../quick_start/api_predict.py | 0 .../quick_start/api_predict.sh | 0 .../quick_start/api_train.py | 0 .../quick_start/api_train.sh | 0 .../quick_start/cluster/cluster_train.sh | 0 .../quick_start/cluster/env.sh | 0 .../quick_start/cluster/pserver.sh | 0 .../quick_start/data/README.md | 0 .../quick_start/data/get_data.sh | 0 .../data/proc_from_raw_data/get_data.sh | 0 .../data/proc_from_raw_data/preprocess.py | 0 .../quick_start/dataprovider_bow.py | 0 .../quick_start/dataprovider_emb.py | 0 {demo => v1_api_demo}/quick_start/predict.sh | 0 {demo => v1_api_demo}/quick_start/train.sh | 0 .../quick_start/trainer_config.bidi-lstm.py | 0 .../quick_start/trainer_config.cnn.py | 0 .../quick_start/trainer_config.db-lstm.py | 0 .../quick_start/trainer_config.emb.py | 0 .../quick_start/trainer_config.lr.py | 0 .../quick_start/trainer_config.lstm.py | 0 .../quick_start/trainer_config.resnet-lstm.py | 0 .../sequence_tagging/data/get_data.sh | 0 .../sequence_tagging/data/test.list | 0 .../sequence_tagging/data/train.list | 0 .../sequence_tagging/dataprovider.py | 0 .../sequence_tagging/linear_crf.py | 0 .../sequence_tagging/readme.md | 0 .../sequence_tagging/rnn_crf.py | 0 .../sequence_tagging/train.sh | 0 .../sequence_tagging/train_linear.sh | 0 .../traffic_prediction/README | 0 .../traffic_prediction/data/get_data.sh | 0 .../traffic_prediction/dataprovider.py | 0 .../traffic_prediction/gen_result.py | 0 .../traffic_prediction/predict.sh | 0 .../traffic_prediction/train.sh | 0 .../traffic_prediction/trainer_config.py | 0 {demo => v1_api_demo}/vae/README.md | 0 .../vae/data/get_mnist_data.sh | 0 {demo => v1_api_demo}/vae/dataloader.py | 0 {demo => v1_api_demo}/vae/vae_conf.py | 0 {demo => v1_api_demo}/vae/vae_train.py | 0 163 files changed, 5 insertions(+), 6493 deletions(-) delete mode 100644 demo/image_classification/.gitignore delete mode 100644 demo/image_classification/api_v2_resnet.py delete mode 100644 demo/image_classification/api_v2_train.py delete mode 100644 demo/image_classification/api_v2_vgg.py delete mode 100755 demo/image_classification/data/download_cifar.sh delete mode 100644 demo/image_classification/data/process_cifar.py delete mode 100644 demo/image_classification/image_provider.py delete mode 100644 demo/image_classification/image_util.py delete mode 100755 demo/image_classification/predict.sh delete mode 100755 demo/image_classification/prediction.py delete mode 100755 demo/image_classification/preprocess.py delete mode 100755 demo/image_classification/preprocess.sh delete mode 100755 demo/image_classification/train.sh delete mode 100755 demo/image_classification/vgg_16_cifar.py delete mode 100644 demo/introduction/.gitignore delete mode 100644 demo/introduction/README.md delete mode 100644 demo/introduction/api_train_v2.py delete mode 100644 demo/introduction/dataprovider.py delete mode 100755 demo/introduction/evaluate_model.py delete mode 100755 demo/introduction/train.sh delete mode 100644 demo/introduction/trainer_config.py delete mode 100644 demo/mnist/.gitignore delete mode 100644 demo/mnist/api_train.py delete mode 100644 demo/mnist/api_train_v2.py delete mode 100644 demo/mnist/data/generate_list.py delete mode 100755 demo/mnist/data/get_mnist_data.sh delete mode 100644 demo/mnist/light_mnist.py delete mode 100644 demo/mnist/mnist_provider.py delete mode 100644 demo/mnist/mnist_util.py delete mode 100755 demo/mnist/train.sh delete mode 100644 demo/mnist/vgg_16_mnist.py delete mode 100644 demo/recommendation/.gitignore delete mode 100644 demo/recommendation/api_train_v2.py delete mode 100755 demo/recommendation/common_utils.py delete mode 100644 demo/recommendation/data/config.json delete mode 100644 demo/recommendation/data/config_generator.py delete mode 100644 demo/recommendation/data/meta_config.json delete mode 100644 demo/recommendation/data/meta_generator.py delete mode 100755 demo/recommendation/data/ml_data.sh delete mode 100644 demo/recommendation/data/split.py delete mode 100755 demo/recommendation/dataprovider.py delete mode 100755 demo/recommendation/evaluate.py delete mode 100755 demo/recommendation/evaluate.sh delete mode 100755 demo/recommendation/prediction.py delete mode 100755 demo/recommendation/preprocess.sh delete mode 100644 demo/recommendation/requirements.txt delete mode 100755 demo/recommendation/run.sh delete mode 100755 demo/recommendation/trainer_config.py delete mode 100644 demo/semantic_role_labeling/.gitignore delete mode 100644 demo/semantic_role_labeling/api_train_v2.py delete mode 100644 demo/semantic_role_labeling/data/extract_dict_feature.py delete mode 100644 demo/semantic_role_labeling/data/extract_pairs.py delete mode 100755 demo/semantic_role_labeling/data/get_data.sh delete mode 100644 demo/semantic_role_labeling/data/test.list delete mode 100644 demo/semantic_role_labeling/data/train.list delete mode 100644 demo/semantic_role_labeling/dataprovider.py delete mode 100644 demo/semantic_role_labeling/db_lstm.py delete mode 100644 demo/semantic_role_labeling/predict.py delete mode 100755 demo/semantic_role_labeling/predict.sh delete mode 100755 demo/semantic_role_labeling/test.sh delete mode 100755 demo/semantic_role_labeling/train.sh delete mode 100644 demo/sentiment/.gitignore delete mode 100755 demo/sentiment/data/get_imdb.sh delete mode 100755 demo/sentiment/dataprovider.py delete mode 100755 demo/sentiment/predict.py delete mode 100755 demo/sentiment/predict.sh delete mode 100755 demo/sentiment/preprocess.py delete mode 100755 demo/sentiment/preprocess.sh delete mode 100644 demo/sentiment/sentiment_net.py delete mode 100755 demo/sentiment/test.sh delete mode 100755 demo/sentiment/train.sh delete mode 100644 demo/sentiment/train_v2.py delete mode 100644 demo/sentiment/trainer_config.py delete mode 100644 demo/seqToseq/.gitignore delete mode 100644 demo/seqToseq/api_train_v2.py delete mode 100755 demo/seqToseq/data/paraphrase_data.sh delete mode 100755 demo/seqToseq/data/paraphrase_model.sh delete mode 100755 demo/seqToseq/data/wmt14_data.sh delete mode 100755 demo/seqToseq/data/wmt14_model.sh delete mode 100755 demo/seqToseq/dataprovider.py delete mode 100644 demo/seqToseq/paraphrase/train.conf delete mode 100755 demo/seqToseq/paraphrase/train.sh delete mode 100755 demo/seqToseq/preprocess.py delete mode 100644 demo/seqToseq/seqToseq_net.py delete mode 100755 demo/seqToseq/translation/eval_bleu.sh delete mode 100644 demo/seqToseq/translation/gen.conf delete mode 100755 demo/seqToseq/translation/gen.sh delete mode 100755 demo/seqToseq/translation/moses_bleu.sh delete mode 100644 demo/seqToseq/translation/train.conf delete mode 100755 demo/seqToseq/translation/train.sh delete mode 100644 demo/vae/dataloader.pyc delete mode 100644 demo/word2vec/api_train_v2.py create mode 100644 v1_api_demo/README.md rename {demo => v1_api_demo}/gan/.gitignore (100%) rename {demo => v1_api_demo}/gan/README.md (100%) rename {demo => v1_api_demo}/gan/data/download_cifar.sh (100%) rename {demo => v1_api_demo}/gan/data/get_mnist_data.sh (100%) rename {demo => v1_api_demo}/gan/gan_conf.py (100%) rename {demo => v1_api_demo}/gan/gan_conf_image.py (100%) rename {demo => v1_api_demo}/gan/gan_trainer.py (100%) rename {demo => v1_api_demo}/model_zoo/embedding/.gitignore (100%) rename {demo => v1_api_demo}/model_zoo/embedding/extract_para.py (100%) rename {demo => v1_api_demo}/model_zoo/embedding/paraconvert.py (100%) rename {demo => v1_api_demo}/model_zoo/embedding/pre_DictAndModel.sh (100%) rename {demo => v1_api_demo}/model_zoo/resnet/.gitignore (100%) rename {demo => v1_api_demo}/model_zoo/resnet/classify.py (100%) rename {demo => v1_api_demo}/model_zoo/resnet/example/.gitignore (100%) rename {demo => v1_api_demo}/model_zoo/resnet/example/__init__.py (100%) rename {demo => v1_api_demo}/model_zoo/resnet/example/cat.jpg (100%) rename {demo => v1_api_demo}/model_zoo/resnet/example/dog.jpg (100%) rename {demo => v1_api_demo}/model_zoo/resnet/example/image_list_provider.py (100%) rename {demo => v1_api_demo}/model_zoo/resnet/example/test.list (100%) rename {demo => v1_api_demo}/model_zoo/resnet/extract_fea_c++.sh (100%) rename {demo => v1_api_demo}/model_zoo/resnet/extract_fea_py.sh (100%) rename {demo => v1_api_demo}/model_zoo/resnet/get_model.sh (100%) rename {demo => v1_api_demo}/model_zoo/resnet/load_feature.py (100%) rename {demo => v1_api_demo}/model_zoo/resnet/net_diagram.sh (100%) rename {demo => v1_api_demo}/model_zoo/resnet/predict.sh (100%) rename {demo => v1_api_demo}/model_zoo/resnet/resnet.py (100%) rename {demo => v1_api_demo}/quick_start/.gitignore (100%) rename {demo => v1_api_demo}/quick_start/api_predict.py (100%) rename {demo => v1_api_demo}/quick_start/api_predict.sh (100%) rename {demo => v1_api_demo}/quick_start/api_train.py (100%) rename {demo => v1_api_demo}/quick_start/api_train.sh (100%) rename {demo => v1_api_demo}/quick_start/cluster/cluster_train.sh (100%) rename {demo => v1_api_demo}/quick_start/cluster/env.sh (100%) rename {demo => v1_api_demo}/quick_start/cluster/pserver.sh (100%) rename {demo => v1_api_demo}/quick_start/data/README.md (100%) rename {demo => v1_api_demo}/quick_start/data/get_data.sh (100%) rename {demo => v1_api_demo}/quick_start/data/proc_from_raw_data/get_data.sh (100%) rename {demo => v1_api_demo}/quick_start/data/proc_from_raw_data/preprocess.py (100%) rename {demo => v1_api_demo}/quick_start/dataprovider_bow.py (100%) rename {demo => v1_api_demo}/quick_start/dataprovider_emb.py (100%) rename {demo => v1_api_demo}/quick_start/predict.sh (100%) rename {demo => v1_api_demo}/quick_start/train.sh (100%) rename {demo => v1_api_demo}/quick_start/trainer_config.bidi-lstm.py (100%) rename {demo => v1_api_demo}/quick_start/trainer_config.cnn.py (100%) rename {demo => v1_api_demo}/quick_start/trainer_config.db-lstm.py (100%) rename {demo => v1_api_demo}/quick_start/trainer_config.emb.py (100%) rename {demo => v1_api_demo}/quick_start/trainer_config.lr.py (100%) rename {demo => v1_api_demo}/quick_start/trainer_config.lstm.py (100%) rename {demo => v1_api_demo}/quick_start/trainer_config.resnet-lstm.py (100%) rename {demo => v1_api_demo}/sequence_tagging/data/get_data.sh (100%) rename {demo => v1_api_demo}/sequence_tagging/data/test.list (100%) rename {demo => v1_api_demo}/sequence_tagging/data/train.list (100%) rename {demo => v1_api_demo}/sequence_tagging/dataprovider.py (100%) rename {demo => v1_api_demo}/sequence_tagging/linear_crf.py (100%) rename {demo => v1_api_demo}/sequence_tagging/readme.md (100%) rename {demo => v1_api_demo}/sequence_tagging/rnn_crf.py (100%) rename {demo => v1_api_demo}/sequence_tagging/train.sh (100%) rename {demo => v1_api_demo}/sequence_tagging/train_linear.sh (100%) rename {demo => v1_api_demo}/traffic_prediction/README (100%) rename {demo => v1_api_demo}/traffic_prediction/data/get_data.sh (100%) rename {demo => v1_api_demo}/traffic_prediction/dataprovider.py (100%) rename {demo => v1_api_demo}/traffic_prediction/gen_result.py (100%) rename {demo => v1_api_demo}/traffic_prediction/predict.sh (100%) rename {demo => v1_api_demo}/traffic_prediction/train.sh (100%) rename {demo => v1_api_demo}/traffic_prediction/trainer_config.py (100%) rename {demo => v1_api_demo}/vae/README.md (100%) rename {demo => v1_api_demo}/vae/data/get_mnist_data.sh (100%) rename {demo => v1_api_demo}/vae/dataloader.py (100%) rename {demo => v1_api_demo}/vae/vae_conf.py (100%) rename {demo => v1_api_demo}/vae/vae_train.py (100%) diff --git a/demo/image_classification/.gitignore b/demo/image_classification/.gitignore deleted file mode 100644 index 6a05b8f6632..00000000000 --- a/demo/image_classification/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -data/cifar-10-batches-py -data/cifar-out -cifar_vgg_model/* -plot.png -train.log -image_provider_copy_1.py -*pyc -train.list -test.list diff --git a/demo/image_classification/api_v2_resnet.py b/demo/image_classification/api_v2_resnet.py deleted file mode 100644 index 19d20540780..00000000000 --- a/demo/image_classification/api_v2_resnet.py +++ /dev/null @@ -1,74 +0,0 @@ -# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import paddle.v2 as paddle - -__all__ = ['resnet_cifar10'] - - -def conv_bn_layer(input, - ch_out, - filter_size, - stride, - padding, - active_type=paddle.activation.Relu(), - ch_in=None): - tmp = paddle.layer.img_conv( - input=input, - filter_size=filter_size, - num_channels=ch_in, - num_filters=ch_out, - stride=stride, - padding=padding, - act=paddle.activation.Linear(), - bias_attr=False) - return paddle.layer.batch_norm(input=tmp, act=active_type) - - -def shortcut(ipt, n_in, n_out, stride): - if n_in != n_out: - return conv_bn_layer(ipt, n_out, 1, stride, 0, - paddle.activation.Linear()) - else: - return ipt - - -def basicblock(ipt, ch_out, stride): - ch_in = ch_out * 2 - tmp = conv_bn_layer(ipt, ch_out, 3, stride, 1) - tmp = conv_bn_layer(tmp, ch_out, 3, 1, 1, paddle.activation.Linear()) - short = shortcut(ipt, ch_in, ch_out, stride) - return paddle.layer.addto(input=[tmp, short], act=paddle.activation.Relu()) - - -def layer_warp(block_func, ipt, features, count, stride): - tmp = block_func(ipt, features, stride) - for i in range(1, count): - tmp = block_func(tmp, features, 1) - return tmp - - -def resnet_cifar10(ipt, depth=32): - # depth should be one of 20, 32, 44, 56, 110, 1202 - assert (depth - 2) % 6 == 0 - n = (depth - 2) / 6 - nStages = {16, 64, 128} - conv1 = conv_bn_layer( - ipt, ch_in=3, ch_out=16, filter_size=3, stride=1, padding=1) - res1 = layer_warp(basicblock, conv1, 16, n, 1) - res2 = layer_warp(basicblock, res1, 32, n, 2) - res3 = layer_warp(basicblock, res2, 64, n, 2) - pool = paddle.layer.img_pool( - input=res3, pool_size=8, stride=1, pool_type=paddle.pooling.Avg()) - return pool diff --git a/demo/image_classification/api_v2_train.py b/demo/image_classification/api_v2_train.py deleted file mode 100644 index 53cffa6fb4e..00000000000 --- a/demo/image_classification/api_v2_train.py +++ /dev/null @@ -1,92 +0,0 @@ -# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License - -import sys - -import paddle.v2 as paddle - -from api_v2_vgg import vgg_bn_drop - - -def main(): - datadim = 3 * 32 * 32 - classdim = 10 - - # PaddlePaddle init - paddle.init(use_gpu=False, trainer_count=1) - - image = paddle.layer.data( - name="image", type=paddle.data_type.dense_vector(datadim)) - - # Add neural network config - # option 1. resnet - # net = resnet_cifar10(image, depth=32) - # option 2. vgg - net = vgg_bn_drop(image) - - out = paddle.layer.fc(input=net, - size=classdim, - act=paddle.activation.Softmax()) - - lbl = paddle.layer.data( - name="label", type=paddle.data_type.integer_value(classdim)) - cost = paddle.layer.classification_cost(input=out, label=lbl) - - # Create parameters - parameters = paddle.parameters.create(cost) - - # Create optimizer - momentum_optimizer = paddle.optimizer.Momentum( - momentum=0.9, - regularization=paddle.optimizer.L2Regularization(rate=0.0002 * 128), - learning_rate=0.1 / 128.0, - learning_rate_decay_a=0.1, - learning_rate_decay_b=50000 * 100, - learning_rate_schedule='discexp', - batch_size=128) - - # End batch and end pass event handler - def event_handler(event): - if isinstance(event, paddle.event.EndIteration): - if event.batch_id % 100 == 0: - print "\nPass %d, Batch %d, Cost %f, %s" % ( - event.pass_id, event.batch_id, event.cost, event.metrics) - else: - sys.stdout.write('.') - sys.stdout.flush() - if isinstance(event, paddle.event.EndPass): - result = trainer.test( - reader=paddle.batch( - paddle.dataset.cifar.test10(), batch_size=128), - feeding={'image': 0, - 'label': 1}) - print "\nTest with Pass %d, %s" % (event.pass_id, result.metrics) - - # Create trainer - trainer = paddle.trainer.SGD(cost=cost, - parameters=parameters, - update_equation=momentum_optimizer) - trainer.train( - reader=paddle.batch( - paddle.reader.shuffle( - paddle.dataset.cifar.train10(), buf_size=50000), - batch_size=128), - num_passes=5, - event_handler=event_handler, - feeding={'image': 0, - 'label': 1}) - - -if __name__ == '__main__': - main() diff --git a/demo/image_classification/api_v2_vgg.py b/demo/image_classification/api_v2_vgg.py deleted file mode 100644 index 1e0e6b93add..00000000000 --- a/demo/image_classification/api_v2_vgg.py +++ /dev/null @@ -1,47 +0,0 @@ -# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import paddle.v2 as paddle - -__all__ = ['vgg_bn_drop'] - - -def vgg_bn_drop(input): - def conv_block(ipt, num_filter, groups, dropouts, num_channels=None): - return paddle.networks.img_conv_group( - input=ipt, - num_channels=num_channels, - pool_size=2, - pool_stride=2, - conv_num_filter=[num_filter] * groups, - conv_filter_size=3, - conv_act=paddle.activation.Relu(), - conv_with_batchnorm=True, - conv_batchnorm_drop_rate=dropouts, - pool_type=paddle.pooling.Max()) - - conv1 = conv_block(input, 64, 2, [0.3, 0], 3) - conv2 = conv_block(conv1, 128, 2, [0.4, 0]) - conv3 = conv_block(conv2, 256, 3, [0.4, 0.4, 0]) - conv4 = conv_block(conv3, 512, 3, [0.4, 0.4, 0]) - conv5 = conv_block(conv4, 512, 3, [0.4, 0.4, 0]) - - drop = paddle.layer.dropout(input=conv5, dropout_rate=0.5) - fc1 = paddle.layer.fc(input=drop, size=512, act=paddle.activation.Linear()) - bn = paddle.layer.batch_norm( - input=fc1, - act=paddle.activation.Relu(), - layer_attr=paddle.attr.Extra(drop_rate=0.5)) - fc2 = paddle.layer.fc(input=bn, size=512, act=paddle.activation.Linear()) - return fc2 diff --git a/demo/image_classification/data/download_cifar.sh b/demo/image_classification/data/download_cifar.sh deleted file mode 100755 index 532178d627f..00000000000 --- a/demo/image_classification/data/download_cifar.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/bash -# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -set -e -wget https://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz -tar zxf cifar-10-python.tar.gz -rm cifar-10-python.tar.gz -rm -rf cifar-out/* -echo Converting CIFAR data to images..... -python process_cifar.py ./cifar-10-batches-py ./cifar-out diff --git a/demo/image_classification/data/process_cifar.py b/demo/image_classification/data/process_cifar.py deleted file mode 100644 index db6666189e5..00000000000 --- a/demo/image_classification/data/process_cifar.py +++ /dev/null @@ -1,89 +0,0 @@ -# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import numpy as np -import sys -import os -import PIL.Image as Image -""" - Usage: python process_cifar input_dir output_dir -""" - - -def mkdir_not_exist(path): - """ - Make dir if the path does not exist. - path: the path to be created. - """ - if not os.path.exists(path): - os.mkdir(path) - - -def create_dir_structure(output_dir): - """ - Create the directory structure for the directory. - output_dir: the direcotry structure path. - """ - mkdir_not_exist(os.path.join(output_dir)) - mkdir_not_exist(os.path.join(output_dir, "train")) - mkdir_not_exist(os.path.join(output_dir, "test")) - - -def convert_batch(batch_path, label_set, label_map, output_dir, data_split): - """ - Convert CIFAR batch to the structure of Paddle format. - batch_path: the batch to be converted. - label_set: the set of labels. - output_dir: the output path. - data_split: whether it is training or testing data. - """ - data = np.load(batch_path) - for data, label, filename in zip(data['data'], data['labels'], - data['filenames']): - data = data.reshape((3, 32, 32)) - data = np.transpose(data, (1, 2, 0)) - label = label_map[label] - output_dir_this = os.path.join(output_dir, data_split, str(label)) - output_filename = os.path.join(output_dir_this, filename) - if not label in label_set: - label_set[label] = True - mkdir_not_exist(output_dir_this) - Image.fromarray(data).save(output_filename) - - -if __name__ == '__main__': - input_dir = sys.argv[1] - output_dir = sys.argv[2] - num_batch = 5 - create_dir_structure(output_dir) - label_map = { - 0: "airplane", - 1: "automobile", - 2: "bird", - 3: "cat", - 4: "deer", - 5: "dog", - 6: "frog", - 7: "horse", - 8: "ship", - 9: "truck" - } - labels = {} - for i in range(1, num_batch + 1): - convert_batch( - os.path.join(input_dir, "data_batch_%d" % i), labels, label_map, - output_dir, "train") - convert_batch( - os.path.join(input_dir, "test_batch"), {}, label_map, output_dir, - "test") diff --git a/demo/image_classification/image_provider.py b/demo/image_classification/image_provider.py deleted file mode 100644 index 6a315ff094c..00000000000 --- a/demo/image_classification/image_provider.py +++ /dev/null @@ -1,89 +0,0 @@ -# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import io -import random - -import paddle.utils.image_util as image_util -from paddle.trainer.PyDataProvider2 import * - - -# -# {'img_size': 32, -# 'settings': a global object, -# 'color': True, -# 'mean_img_size': 32, -# 'meta': './data/cifar-out/batches/batches.meta', -# 'num_classes': 10, -# 'file_list': ('./data/cifar-out/batches/train_batch_000',), -# 'use_jpeg': True} -def hook(settings, img_size, mean_img_size, num_classes, color, meta, use_jpeg, - is_train, **kwargs): - settings.mean_img_size = mean_img_size - settings.img_size = img_size - settings.num_classes = num_classes - settings.color = color - settings.is_train = is_train - - if settings.color: - settings.img_raw_size = settings.img_size * settings.img_size * 3 - else: - settings.img_raw_size = settings.img_size * settings.img_size - - settings.meta_path = meta - settings.use_jpeg = use_jpeg - - settings.img_mean = image_util.load_meta(settings.meta_path, - settings.mean_img_size, - settings.img_size, settings.color) - - settings.logger.info('Image size: %s', settings.img_size) - settings.logger.info('Meta path: %s', settings.meta_path) - settings.input_types = { - 'image': dense_vector(settings.img_raw_size), - 'label': integer_value(settings.num_classes) - } - - settings.logger.info('DataProvider Initialization finished') - - -@provider(init_hook=hook, min_pool_size=0) -def processData(settings, file_list): - """ - The main function for loading data. - Load the batch, iterate all the images and labels in this batch. - file_list: the batch file list. - """ - with open(file_list, 'r') as fdata: - lines = [line.strip() for line in fdata] - random.shuffle(lines) - for file_name in lines: - with io.open(file_name.strip(), 'rb') as file: - data = cPickle.load(file) - indexes = list(range(len(data['images']))) - if settings.is_train: - random.shuffle(indexes) - for i in indexes: - if settings.use_jpeg == 1: - img = image_util.decode_jpeg(data['images'][i]) - else: - img = data['images'][i] - img_feat = image_util.preprocess_img( - img, settings.img_mean, settings.img_size, - settings.is_train, settings.color) - label = data['labels'][i] - yield { - 'image': img_feat.astype('float32'), - 'label': int(label) - } diff --git a/demo/image_classification/image_util.py b/demo/image_classification/image_util.py deleted file mode 100644 index f09605394a1..00000000000 --- a/demo/image_classification/image_util.py +++ /dev/null @@ -1,221 +0,0 @@ -# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import numpy as np -from PIL import Image -from cStringIO import StringIO - - -def resize_image(img, target_size): - """ - Resize an image so that the shorter edge has length target_size. - img: the input image to be resized. - target_size: the target resized image size. - """ - percent = (target_size / float(min(img.size[0], img.size[1]))) - resized_size = int(round(img.size[0] * percent)), int( - round(img.size[1] * percent)) - img = img.resize(resized_size, Image.ANTIALIAS) - return img - - -def flip(im): - """ - Return the flipped image. - Flip an image along the horizontal direction. - im: input image, (H x W x K) ndarrays - """ - if len(im.shape) == 3: - return im[:, :, ::-1] - else: - return im[:, ::-1] - - -def crop_img(im, inner_size, color=True, test=True): - """ - Return cropped image. - The size of the cropped image is inner_size * inner_size. - im: (K x H x W) ndarrays - inner_size: the cropped image size. - color: whether it is color image. - test: whether in test mode. - If False, does random cropping and flipping. - If True, crop the center of images. - """ - if color: - height, width = max(inner_size, im.shape[1]), max(inner_size, - im.shape[2]) - padded_im = np.zeros((3, height, width)) - startY = (height - im.shape[1]) / 2 - startX = (width - im.shape[2]) / 2 - endY, endX = startY + im.shape[1], startX + im.shape[2] - padded_im[:, startY:endY, startX:endX] = im - else: - im = im.astype('float32') - height, width = max(inner_size, im.shape[0]), max(inner_size, - im.shape[1]) - padded_im = np.zeros((height, width)) - startY = (height - im.shape[0]) / 2 - startX = (width - im.shape[1]) / 2 - endY, endX = startY + im.shape[0], startX + im.shape[1] - padded_im[startY:endY, startX:endX] = im - if test: - startY = (height - inner_size) / 2 - startX = (width - inner_size) / 2 - else: - startY = np.random.randint(0, height - inner_size + 1) - startX = np.random.randint(0, width - inner_size + 1) - endY, endX = startY + inner_size, startX + inner_size - if color: - pic = padded_im[:, startY:endY, startX:endX] - else: - pic = padded_im[startY:endY, startX:endX] - if (not test) and (np.random.randint(2) == 0): - pic = flip(pic) - return pic - - -def decode_jpeg(jpeg_string): - np_array = np.array(Image.open(StringIO(jpeg_string))) - if len(np_array.shape) == 3: - np_array = np.transpose(np_array, (2, 0, 1)) - return np_array - - -def preprocess_img(im, img_mean, crop_size, is_train, color=True): - """ - Does data augmentation for images. - If is_train is false, cropping the center region from the image. - If is_train is true, randomly crop a region from the image, - and randomy does flipping. - im: (K x H x W) ndarrays - """ - im = im.astype('float32') - test = not is_train - pic = crop_img(im, crop_size, color, test) - pic -= img_mean - return pic.flatten() - - -def load_meta(meta_path, mean_img_size, crop_size, color=True): - """ - Return the loaded meta file. - Load the meta image, which is the mean of the images in the dataset. - The mean image is subtracted from every input image so that the expected mean - of each input image is zero. - """ - mean = np.load(meta_path)['data_mean'] - border = (mean_img_size - crop_size) / 2 - if color: - assert (mean_img_size * mean_img_size * 3 == mean.shape[0]) - mean = mean.reshape(3, mean_img_size, mean_img_size) - mean = mean[:, border:border + crop_size, border:border + - crop_size].astype('float32') - else: - assert (mean_img_size * mean_img_size == mean.shape[0]) - mean = mean.reshape(mean_img_size, mean_img_size) - mean = mean[border:border + crop_size, border:border + - crop_size].astype('float32') - return mean - - -def load_image(img_path, is_color=True): - """ - Load image and return. - img_path: image path. - is_color: is color image or not. - """ - img = Image.open(img_path) - img.load() - return img - - -def oversample(img, crop_dims): - """ - image : iterable of (H x W x K) ndarrays - crop_dims: (height, width) tuple for the crops. - Returned data contains ten crops of input image, namely, - four corner patches and the center patch as well as their - horizontal reflections. - """ - # Dimensions and center. - im_shape = np.array(img[0].shape) - crop_dims = np.array(crop_dims) - im_center = im_shape[:2] / 2.0 - - # Make crop coordinates - h_indices = (0, im_shape[0] - crop_dims[0]) - w_indices = (0, im_shape[1] - crop_dims[1]) - crops_ix = np.empty((5, 4), dtype=int) - curr = 0 - for i in h_indices: - for j in w_indices: - crops_ix[curr] = (i, j, i + crop_dims[0], j + crop_dims[1]) - curr += 1 - crops_ix[4] = np.tile(im_center, (1, 2)) + np.concatenate( - [-crop_dims / 2.0, crop_dims / 2.0]) - crops_ix = np.tile(crops_ix, (2, 1)) - - # Extract crops - crops = np.empty( - (10 * len(img), crop_dims[0], crop_dims[1], im_shape[-1]), - dtype=np.float32) - ix = 0 - for im in img: - for crop in crops_ix: - crops[ix] = im[crop[0]:crop[2], crop[1]:crop[3], :] - ix += 1 - crops[ix - 5:ix] = crops[ix - 5:ix, :, ::-1, :] # flip for mirrors - return crops - - -class ImageTransformer: - def __init__(self, - transpose=None, - channel_swap=None, - mean=None, - is_color=True): - self.transpose = transpose - self.channel_swap = None - self.mean = None - self.is_color = is_color - - def set_transpose(self, order): - if self.is_color: - assert 3 == len(order) - self.transpose = order - - def set_channel_swap(self, order): - if self.is_color: - assert 3 == len(order) - self.channel_swap = order - - def set_mean(self, mean): - # mean value, may be one value per channel - if mean.ndim == 1: - mean = mean[:, np.newaxis, np.newaxis] - else: - # elementwise mean - if self.is_color: - assert len(mean.shape) == 3 - self.mean = mean - - def transformer(self, data): - if self.transpose is not None: - data = data.transpose(self.transpose) - if self.channel_swap is not None: - data = data[self.channel_swap, :, :] - if self.mean is not None: - data -= self.mean - return data diff --git a/demo/image_classification/predict.sh b/demo/image_classification/predict.sh deleted file mode 100755 index 9d5785c9a1a..00000000000 --- a/demo/image_classification/predict.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/bash -# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -set -e - -model=cifar_vgg_model/pass-00299/ -image=data/cifar-out/test/airplane/seaplane_s_000978.png -use_gpu=1 -python prediction.py $model $image $use_gpu diff --git a/demo/image_classification/prediction.py b/demo/image_classification/prediction.py deleted file mode 100755 index 49c0ff600c4..00000000000 --- a/demo/image_classification/prediction.py +++ /dev/null @@ -1,159 +0,0 @@ -# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import os, sys -import numpy as np -import logging -from PIL import Image -from optparse import OptionParser - -import paddle.utils.image_util as image_util - -from py_paddle import swig_paddle, DataProviderConverter -from paddle.trainer.PyDataProvider2 import dense_vector -from paddle.trainer.config_parser import parse_config - -logging.basicConfig( - format='[%(levelname)s %(asctime)s %(filename)s:%(lineno)s] %(message)s') -logging.getLogger().setLevel(logging.INFO) - - -class ImageClassifier(): - def __init__(self, - train_conf, - use_gpu=True, - model_dir=None, - resize_dim=None, - crop_dim=None, - mean_file=None, - oversample=False, - is_color=True): - """ - train_conf: network configure. - model_dir: string, directory of model. - resize_dim: int, resized image size. - crop_dim: int, crop size. - mean_file: string, image mean file. - oversample: bool, oversample means multiple crops, namely five - patches (the four corner patches and the center - patch) as well as their horizontal reflections, - ten crops in all. - """ - self.train_conf = train_conf - self.model_dir = model_dir - if model_dir is None: - self.model_dir = os.path.dirname(train_conf) - - self.resize_dim = resize_dim - self.crop_dims = [crop_dim, crop_dim] - self.oversample = oversample - self.is_color = is_color - - self.transformer = image_util.ImageTransformer(is_color=is_color) - self.transformer.set_transpose((2, 0, 1)) - - self.mean_file = mean_file - mean = np.load(self.mean_file)['data_mean'] - mean = mean.reshape(3, self.crop_dims[0], self.crop_dims[1]) - self.transformer.set_mean(mean) # mean pixel - gpu = 1 if use_gpu else 0 - conf_args = "is_test=1,use_gpu=%d,is_predict=1" % (gpu) - conf = parse_config(train_conf, conf_args) - swig_paddle.initPaddle("--use_gpu=%d" % (gpu)) - self.network = swig_paddle.GradientMachine.createFromConfigProto( - conf.model_config) - assert isinstance(self.network, swig_paddle.GradientMachine) - self.network.loadParameters(self.model_dir) - - data_size = 3 * self.crop_dims[0] * self.crop_dims[1] - slots = [dense_vector(data_size)] - self.converter = DataProviderConverter(slots) - - def get_data(self, img_path): - """ - 1. load image from img_path. - 2. resize or oversampling. - 3. transformer data: transpose, sub mean. - return K x H x W ndarray. - img_path: image path. - """ - image = image_util.load_image(img_path, self.is_color) - if self.oversample: - # image_util.resize_image: short side is self.resize_dim - image = image_util.resize_image(image, self.resize_dim) - image = np.array(image) - input = np.zeros( - (1, image.shape[0], image.shape[1], 3), dtype=np.float32) - input[0] = image.astype(np.float32) - input = image_util.oversample(input, self.crop_dims) - else: - image = image.resize(self.crop_dims, Image.ANTIALIAS) - input = np.zeros( - (1, self.crop_dims[0], self.crop_dims[1], 3), dtype=np.float32) - input[0] = np.array(image).astype(np.float32) - - data_in = [] - for img in input: - img = self.transformer.transformer(img).flatten() - data_in.append([img.tolist()]) - return data_in - - def forward(self, input_data): - in_arg = self.converter(input_data) - return self.network.forwardTest(in_arg) - - def forward(self, data, output_layer): - """ - input_data: py_paddle input data. - output_layer: specify the name of probability, namely the layer with - softmax activation. - return: the predicting probability of each label. - """ - input = self.converter(data) - self.network.forwardTest(input) - output = self.network.getLayerOutputs(output_layer) - # For oversampling, average predictions across crops. - # If not, the shape of output[name]: (1, class_number), - # the mean is also applicable. - return output[output_layer]['value'].mean(0) - - def predict(self, image=None, output_layer=None): - assert isinstance(image, basestring) - assert isinstance(output_layer, basestring) - data = self.get_data(image) - prob = self.forward(data, output_layer) - lab = np.argsort(-prob) - logging.info("Label of %s is: %d", image, lab[0]) - - -if __name__ == '__main__': - image_size = 32 - crop_size = 32 - multi_crop = True - config = "vgg_16_cifar.py" - output_layer = "__fc_layer_1__" - mean_path = "data/cifar-out/batches/batches.meta" - model_path = sys.argv[1] - image = sys.argv[2] - use_gpu = bool(int(sys.argv[3])) - - obj = ImageClassifier( - train_conf=config, - model_dir=model_path, - resize_dim=image_size, - crop_dim=crop_size, - mean_file=mean_path, - use_gpu=use_gpu, - oversample=multi_crop) - obj.predict(image, output_layer) diff --git a/demo/image_classification/preprocess.py b/demo/image_classification/preprocess.py deleted file mode 100755 index 2947ad239c3..00000000000 --- a/demo/image_classification/preprocess.py +++ /dev/null @@ -1,54 +0,0 @@ -# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from paddle.utils.preprocess_img import ImageClassificationDatasetCreater -from optparse import OptionParser - - -def option_parser(): - parser = OptionParser(usage="usage: python preprcoess.py "\ - "-i data_dir [options]") - parser.add_option( - "-i", - "--input", - action="store", - dest="input", - help="Input data directory.") - parser.add_option( - "-s", - "--size", - action="store", - dest="size", - help="Processed image size.") - parser.add_option( - "-c", - "--color", - action="store", - dest="color", - help="whether to use color images.") - return parser.parse_args() - - -if __name__ == '__main__': - options, args = option_parser() - data_dir = options.input - processed_image_size = int(options.size) - color = options.color == "1" - data_creator = ImageClassificationDatasetCreater( - data_dir, processed_image_size, color) - data_creator.train_list_name = "train.txt" - data_creator.test_list_name = "test.txt" - data_creator.num_per_batch = 1000 - data_creator.overwrite = True - data_creator.create_batches() diff --git a/demo/image_classification/preprocess.sh b/demo/image_classification/preprocess.sh deleted file mode 100755 index c7396c63935..00000000000 --- a/demo/image_classification/preprocess.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/bash -# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -set -e - -data_dir=./data/cifar-out - -python preprocess.py -i $data_dir -s 32 -c 1 - -echo "data/cifar-out/batches/train.txt" > train.list -echo "data/cifar-out/batches/test.txt" > test.list diff --git a/demo/image_classification/train.sh b/demo/image_classification/train.sh deleted file mode 100755 index e45bd47ad59..00000000000 --- a/demo/image_classification/train.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/bin/bash -# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -set -e -config=vgg_16_cifar.py -output=./cifar_vgg_model -log=train.log - -paddle train \ ---config=$config \ ---dot_period=10 \ ---log_period=100 \ ---test_all_data_in_one_period=1 \ ---use_gpu=1 \ ---trainer_count=1 \ ---num_passes=300 \ ---save_dir=$output \ -2>&1 | tee $log -paddle usage -l $log -e $? -n "image_classification_train" >/dev/null 2>&1 - -python -m paddle.utils.plotcurve -i $log > plot.png diff --git a/demo/image_classification/vgg_16_cifar.py b/demo/image_classification/vgg_16_cifar.py deleted file mode 100755 index 8ee4a64c15f..00000000000 --- a/demo/image_classification/vgg_16_cifar.py +++ /dev/null @@ -1,58 +0,0 @@ -# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from paddle.trainer_config_helpers import * - -is_predict = get_config_arg("is_predict", bool, False) - -####################Data Configuration ################## -if not is_predict: - data_dir = 'data/cifar-out/batches/' - meta_path = data_dir + 'batches.meta' - - args = { - 'meta': meta_path, - 'mean_img_size': 32, - 'img_size': 32, - 'num_classes': 10, - 'use_jpeg': 1, - 'color': "color" - } - - define_py_data_sources2( - train_list="train.list", - test_list="train.list", - module='image_provider', - obj='processData', - args=args) - -######################Algorithm Configuration ############# -settings( - batch_size=128, - learning_rate=0.1 / 128.0, - learning_method=MomentumOptimizer(0.9), - regularization=L2Regularization(0.0005 * 128)) - -#######################Network Configuration ############# -data_size = 3 * 32 * 32 -label_size = 10 -img = data_layer(name='image', size=data_size) -# small_vgg is predefined in trainer_config_helpers.networks -predict = small_vgg(input_image=img, num_channels=3, num_classes=label_size) - -if not is_predict: - lbl = data_layer(name="label", size=label_size) - outputs(classification_cost(input=predict, label=lbl)) -else: - outputs(predict) diff --git a/demo/introduction/.gitignore b/demo/introduction/.gitignore deleted file mode 100644 index c54f3f9480c..00000000000 --- a/demo/introduction/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -dataprovider.pyc -empty.list -train.log -output -train.list diff --git a/demo/introduction/README.md b/demo/introduction/README.md deleted file mode 100644 index 0614a7afe64..00000000000 --- a/demo/introduction/README.md +++ /dev/null @@ -1,3 +0,0 @@ -This folder contains scripts used in PaddlePaddle introduction. -- use `bash train.sh` to train a simple linear regression model -- use `python evaluate_model.py` to read model parameters. You can see that `w` and `b` are very close to [2, 0.3]. diff --git a/demo/introduction/api_train_v2.py b/demo/introduction/api_train_v2.py deleted file mode 100644 index 1ba971b3688..00000000000 --- a/demo/introduction/api_train_v2.py +++ /dev/null @@ -1,58 +0,0 @@ -import paddle.v2 as paddle -import paddle.v2.dataset.uci_housing as uci_housing - - -def main(): - # init - paddle.init(use_gpu=False, trainer_count=1) - - # network config - x = paddle.layer.data(name='x', type=paddle.data_type.dense_vector(13)) - y_predict = paddle.layer.fc(input=x, - param_attr=paddle.attr.Param(name='w'), - size=1, - act=paddle.activation.Linear(), - bias_attr=paddle.attr.Param(name='b')) - y = paddle.layer.data(name='y', type=paddle.data_type.dense_vector(1)) - cost = paddle.layer.mse_cost(input=y_predict, label=y) - - # create parameters - parameters = paddle.parameters.create(cost) - - # create optimizer - optimizer = paddle.optimizer.Momentum(momentum=0) - - trainer = paddle.trainer.SGD(cost=cost, - parameters=parameters, - update_equation=optimizer) - - # event_handler to print training and testing info - def event_handler(event): - if isinstance(event, paddle.event.EndIteration): - if event.batch_id % 100 == 0: - print "Pass %d, Batch %d, Cost %f" % ( - event.pass_id, event.batch_id, event.cost) - - if isinstance(event, paddle.event.EndPass): - if (event.pass_id + 1) % 10 == 0: - result = trainer.test( - reader=paddle.batch( - uci_housing.test(), batch_size=2), - feeding={'x': 0, - 'y': 1}) - print "Test %d, %.2f" % (event.pass_id, result.cost) - - # training - trainer.train( - reader=paddle.batch( - paddle.reader.shuffle( - uci_housing.train(), buf_size=500), - batch_size=2), - feeding={'x': 0, - 'y': 1}, - event_handler=event_handler, - num_passes=30) - - -if __name__ == '__main__': - main() diff --git a/demo/introduction/dataprovider.py b/demo/introduction/dataprovider.py deleted file mode 100644 index 5b48aad0408..00000000000 --- a/demo/introduction/dataprovider.py +++ /dev/null @@ -1,26 +0,0 @@ -# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from paddle.trainer.PyDataProvider2 import * -import random - - -# define data types of input: 2 real numbers -@provider( - input_types={'x': dense_vector(1), - 'y': dense_vector(1)}, use_seq=False) -def process(settings, input_file): - for i in xrange(2000): - x = random.random() - yield {'x': [x], 'y': [2 * x + 0.3]} diff --git a/demo/introduction/evaluate_model.py b/demo/introduction/evaluate_model.py deleted file mode 100755 index eeda43c5c86..00000000000 --- a/demo/introduction/evaluate_model.py +++ /dev/null @@ -1,39 +0,0 @@ -#!/usr/bin/env python -# -*- coding: UTF-8 -*- - -# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -""" -Print model parameters in last model - -Usage: - python evaluate_model.py -""" -import numpy as np -import os - - -def load(file_name): - with open(file_name, 'rb') as f: - f.read(16) # skip header for float type. - return np.fromfile(f, dtype=np.float32) - - -def main(): - print 'w=%.6f, b=%.6f from pass 29' % (load('output/pass-00029/w'), - load('output/pass-00029/b')) - - -if __name__ == '__main__': - main() diff --git a/demo/introduction/train.sh b/demo/introduction/train.sh deleted file mode 100755 index 2ce6446d7c9..00000000000 --- a/demo/introduction/train.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/bash -# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -set -e - -paddle train \ - --config=trainer_config.py \ - --save_dir=./output \ - --num_passes=30 \ - 2>&1 |tee 'train.log' -paddle usage -l "train.log" -e $? -n "introduction" >/dev/null 2>&1 diff --git a/demo/introduction/trainer_config.py b/demo/introduction/trainer_config.py deleted file mode 100644 index 651dfaa4b7b..00000000000 --- a/demo/introduction/trainer_config.py +++ /dev/null @@ -1,38 +0,0 @@ -# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from paddle.trainer_config_helpers import * - -# 1. read data. Suppose you saved above python code as dataprovider.py -define_py_data_sources2( - train_list=['no_matter.txt'], - test_list=None, - module='dataprovider', - obj='process', - args={}) - -# 2. learning algorithm -settings(batch_size=12, learning_rate=1e-3, learning_method=MomentumOptimizer()) - -# 3. Network configuration -x = data_layer(name='x', size=1) -y = data_layer(name='y', size=1) -y_predict = fc_layer( - input=x, - param_attr=ParamAttr(name='w'), - size=1, - act=LinearActivation(), - bias_attr=ParamAttr(name='b')) -cost = mse_cost(input=y_predict, label=y) -outputs(cost) diff --git a/demo/mnist/.gitignore b/demo/mnist/.gitignore deleted file mode 100644 index 7e61d5e3a0c..00000000000 --- a/demo/mnist/.gitignore +++ /dev/null @@ -1,10 +0,0 @@ -data/raw_data -data/*.list -mnist_vgg_model -plot.png -train.log -*pyc -.ipynb_checkpoints -params.pkl -params.tar -params.tar.gz diff --git a/demo/mnist/api_train.py b/demo/mnist/api_train.py deleted file mode 100644 index ea1caa7dd96..00000000000 --- a/demo/mnist/api_train.py +++ /dev/null @@ -1,196 +0,0 @@ -""" -A very basic example for how to use current Raw SWIG API to train mnist network. - -Current implementation uses Raw SWIG, which means the API call is directly \ -passed to C++ side of Paddle. - -The user api could be simpler and carefully designed. -""" -import random - -import numpy as np -import paddle.v2 as paddle_v2 -import py_paddle.swig_paddle as api -from paddle.trainer_config_helpers import * -from py_paddle import DataProviderConverter - -from mnist_util import read_from_mnist - - -def init_parameter(network): - assert isinstance(network, api.GradientMachine) - for each_param in network.getParameters(): - assert isinstance(each_param, api.Parameter) - array_size = len(each_param) - array = np.random.uniform(-1.0, 1.0, array_size).astype('float32') - each_param.getBuf(api.PARAMETER_VALUE).copyFromNumpyArray(array) - - -def generator_to_batch(generator, batch_size): - ret_val = list() - for each_item in generator: - ret_val.append(each_item) - if len(ret_val) == batch_size: - yield ret_val - ret_val = list() - if len(ret_val) != 0: - yield ret_val - - -class BatchPool(object): - def __init__(self, generator, batch_size): - self.data = list(generator) - self.batch_size = batch_size - - def __call__(self): - random.shuffle(self.data) - for offset in xrange(0, len(self.data), self.batch_size): - limit = min(offset + self.batch_size, len(self.data)) - yield self.data[offset:limit] - - -def input_order_converter(generator): - for each_item in generator: - yield each_item['pixel'], each_item['label'] - - -def main(): - api.initPaddle("-use_gpu=false", "-trainer_count=4") # use 4 cpu cores - - optimizer = paddle_v2.optimizer.Adam( - learning_rate=1e-4, - batch_size=1000, - model_average=ModelAverage(average_window=0.5), - regularization=L2Regularization(rate=0.5)) - - # Create Local Updater. Local means not run in cluster. - # For a cluster training, here we can change to createRemoteUpdater - # in future. - updater = optimizer.create_local_updater() - assert isinstance(updater, api.ParameterUpdater) - - # define network - images = paddle_v2.layer.data( - name='pixel', type=paddle_v2.data_type.dense_vector(784)) - label = paddle_v2.layer.data( - name='label', type=paddle_v2.data_type.integer_value(10)) - hidden1 = paddle_v2.layer.fc(input=images, size=200) - hidden2 = paddle_v2.layer.fc(input=hidden1, size=200) - inference = paddle_v2.layer.fc(input=hidden2, - size=10, - act=paddle_v2.activation.Softmax()) - cost = paddle_v2.layer.classification_cost(input=inference, label=label) - - # Create Simple Gradient Machine. - model_config = paddle_v2.layer.parse_network(cost) - m = api.GradientMachine.createFromConfigProto(model_config, - api.CREATE_MODE_NORMAL, - optimizer.enable_types()) - - # This type check is not useful. Only enable type hint in IDE. - # Such as PyCharm - assert isinstance(m, api.GradientMachine) - - # Initialize Parameter by numpy. - init_parameter(network=m) - - # Initialize ParameterUpdater. - updater.init(m) - - # DataProvider Converter is a utility convert Python Object to Paddle C++ - # Input. The input format is as same as Paddle's DataProvider. - converter = DataProviderConverter(input_types=[images.type, label.type]) - - train_file = './data/raw_data/train' - test_file = './data/raw_data/t10k' - - # start gradient machine. - # the gradient machine must be started before invoke forward/backward. - # not just for training, but also for inference. - m.start() - - # evaluator can print error rate, etc. It is a C++ class. - batch_evaluator = m.makeEvaluator() - test_evaluator = m.makeEvaluator() - - # Get Train Data. - # TrainData will stored in a data pool. Currently implementation is not care - # about memory, speed. Just a very naive implementation. - train_data_generator = input_order_converter(read_from_mnist(train_file)) - train_data = BatchPool(train_data_generator, 512) - - # outArgs is Neural Network forward result. Here is not useful, just passed - # to gradient_machine.forward - outArgs = api.Arguments.createArguments(0) - - for pass_id in xrange(2): # we train 2 passes. - updater.startPass() - - for batch_id, data_batch in enumerate(train_data()): - # data_batch is input images. - # here, for online learning, we could get data_batch from network. - - # Start update one batch. - pass_type = updater.startBatch(len(data_batch)) - - # Start BatchEvaluator. - # batch_evaluator can be used between start/finish. - batch_evaluator.start() - - # forwardBackward is a shortcut for forward and backward. - # It is sometimes faster than invoke forward/backward separately, - # because in GradientMachine, it may be async. - m.forwardBackward(converter(data_batch), outArgs, pass_type) - - for each_param in m.getParameters(): - updater.update(each_param) - - # Get cost. We use numpy to calculate total cost for this batch. - cost_vec = outArgs.getSlotValue(0) - cost_vec = cost_vec.copyToNumpyMat() - cost = cost_vec.sum() / len(data_batch) - - # Make evaluator works. - m.eval(batch_evaluator) - - # Print logs. - print 'Pass id', pass_id, 'Batch id', batch_id, 'with cost=', \ - cost, batch_evaluator - - batch_evaluator.finish() - # Finish batch. - # * will clear gradient. - # * ensure all values should be updated. - updater.finishBatch(cost) - - # testing stage. use test data set to test current network. - updater.apply() - test_evaluator.start() - test_data_generator = input_order_converter(read_from_mnist(test_file)) - for data_batch in generator_to_batch(test_data_generator, 512): - # in testing stage, only forward is needed. - m.forward(converter(data_batch), outArgs, api.PASS_TEST) - m.eval(test_evaluator) - - # print error rate for test data set - print 'Pass', pass_id, ' test evaluator: ', test_evaluator - test_evaluator.finish() - updater.restore() - - updater.catchUpWith() - params = m.getParameters() - for each_param in params: - assert isinstance(each_param, api.Parameter) - value = each_param.getBuf(api.PARAMETER_VALUE) - value = value.copyToNumpyArray() - - # Here, we could save parameter to every where you want - print each_param.getName(), value - - updater.finishPass() - - m.finish() - - -if __name__ == '__main__': - main() diff --git a/demo/mnist/api_train_v2.py b/demo/mnist/api_train_v2.py deleted file mode 100644 index 6b95a88042a..00000000000 --- a/demo/mnist/api_train_v2.py +++ /dev/null @@ -1,137 +0,0 @@ -import paddle.v2 as paddle -import gzip - - -def softmax_regression(img): - predict = paddle.layer.fc(input=img, - size=10, - act=paddle.activation.Softmax()) - return predict - - -def multilayer_perceptron(img): - # The first fully-connected layer - hidden1 = paddle.layer.fc(input=img, size=128, act=paddle.activation.Relu()) - # The second fully-connected layer and the according activation function - hidden2 = paddle.layer.fc(input=hidden1, - size=64, - act=paddle.activation.Relu()) - # The thrid fully-connected layer, note that the hidden size should be 10, - # which is the number of unique digits - predict = paddle.layer.fc(input=hidden2, - size=10, - act=paddle.activation.Softmax()) - return predict - - -def convolutional_neural_network(img): - # first conv layer - conv_pool_1 = paddle.networks.simple_img_conv_pool( - input=img, - filter_size=5, - num_filters=20, - num_channel=1, - pool_size=2, - pool_stride=2, - act=paddle.activation.Tanh()) - # second conv layer - conv_pool_2 = paddle.networks.simple_img_conv_pool( - input=conv_pool_1, - filter_size=5, - num_filters=50, - num_channel=20, - pool_size=2, - pool_stride=2, - act=paddle.activation.Tanh()) - # The first fully-connected layer - fc1 = paddle.layer.fc(input=conv_pool_2, - size=128, - act=paddle.activation.Tanh()) - # The softmax layer, note that the hidden size should be 10, - # which is the number of unique digits - predict = paddle.layer.fc(input=fc1, - size=10, - act=paddle.activation.Softmax()) - return predict - - -def main(): - paddle.init(use_gpu=False, trainer_count=1) - - # define network topology - images = paddle.layer.data( - name='pixel', type=paddle.data_type.dense_vector(784)) - label = paddle.layer.data( - name='label', type=paddle.data_type.integer_value(10)) - - # Here we can build the prediction network in different ways. Please - # choose one by uncomment corresponding line. - predict = softmax_regression(images) - #predict = multilayer_perceptron(images) - #predict = convolutional_neural_network(images) - - cost = paddle.layer.classification_cost(input=predict, label=label) - - try: - with gzip.open('params.tar.gz', 'r') as f: - parameters = paddle.parameters.Parameters.from_tar(f) - except IOError: - parameters = paddle.parameters.create(cost) - - optimizer = paddle.optimizer.Momentum( - learning_rate=0.1 / 128.0, - momentum=0.9, - regularization=paddle.optimizer.L2Regularization(rate=0.0005 * 128)) - - trainer = paddle.trainer.SGD(cost=cost, - parameters=parameters, - update_equation=optimizer) - - lists = [] - - def event_handler(event): - if isinstance(event, paddle.event.EndIteration): - if event.batch_id % 1000 == 0: - print "Pass %d, Batch %d, Cost %f, %s" % ( - event.pass_id, event.batch_id, event.cost, event.metrics) - - with gzip.open('params.tar.gz', 'w') as f: - parameters.to_tar(f) - - elif isinstance(event, paddle.event.EndPass): - result = trainer.test(reader=paddle.batch( - paddle.dataset.mnist.test(), batch_size=128)) - print "Test with Pass %d, Cost %f, %s\n" % ( - event.pass_id, result.cost, result.metrics) - lists.append((event.pass_id, result.cost, - result.metrics['classification_error_evaluator'])) - - trainer.train( - reader=paddle.batch( - paddle.reader.shuffle( - paddle.dataset.mnist.train(), buf_size=8192), - batch_size=128), - event_handler=event_handler, - num_passes=100) - - # find the best pass - best = sorted(lists, key=lambda list: float(list[1]))[0] - print 'Best pass is %s, testing Avgcost is %s' % (best[0], best[1]) - print 'The classification accuracy is %.2f%%' % (100 - float(best[2]) * 100) - - test_creator = paddle.dataset.mnist.test() - test_data = [] - for item in test_creator(): - test_data.append((item[0], )) - if len(test_data) == 100: - break - - # output is a softmax layer. It returns probabilities. - # Shape should be (100, 10) - probs = paddle.infer( - output_layer=predict, parameters=parameters, input=test_data) - print probs.shape - - -if __name__ == '__main__': - main() diff --git a/demo/mnist/data/generate_list.py b/demo/mnist/data/generate_list.py deleted file mode 100644 index 49981cc7a93..00000000000 --- a/demo/mnist/data/generate_list.py +++ /dev/null @@ -1,21 +0,0 @@ -# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -o = open("./" + "train.list", "w") -o.write("./data/raw_data/train" + "\n") -o.close() - -o = open("./" + "test.list", "w") -o.write("./data/raw_data/t10k" + "\n") -o.close() diff --git a/demo/mnist/data/get_mnist_data.sh b/demo/mnist/data/get_mnist_data.sh deleted file mode 100755 index 5a2e34026d4..00000000000 --- a/demo/mnist/data/get_mnist_data.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/env sh -# This scripts downloads the mnist data and unzips it. -set -e -DIR="$( cd "$(dirname "$0")" ; pwd -P )" -rm -rf "$DIR/raw_data" -mkdir "$DIR/raw_data" -cd "$DIR/raw_data" - -echo "Downloading..." - -for fname in train-images-idx3-ubyte train-labels-idx1-ubyte t10k-images-idx3-ubyte t10k-labels-idx1-ubyte -do - if [ ! -e $fname ]; then - wget --no-check-certificate http://yann.lecun.com/exdb/mnist/${fname}.gz - gunzip ${fname}.gz - fi -done - -cd $DIR -rm -f *.list -python generate_list.py diff --git a/demo/mnist/light_mnist.py b/demo/mnist/light_mnist.py deleted file mode 100644 index 33409054357..00000000000 --- a/demo/mnist/light_mnist.py +++ /dev/null @@ -1,79 +0,0 @@ -# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from paddle.trainer_config_helpers import * - -is_predict = get_config_arg("is_predict", bool, False) - -####################Data Configuration ################## - -if not is_predict: - data_dir = './data/' - define_py_data_sources2( - train_list=data_dir + 'train.list', - test_list=data_dir + 'test.list', - module='mnist_provider', - obj='process') - -######################Algorithm Configuration ############# -settings(batch_size=50, learning_rate=0.001, learning_method=AdamOptimizer()) - -#######################Network Configuration ############# - -data_size = 1 * 28 * 28 -label_size = 10 -img = data_layer(name='pixel', size=data_size) - - -# light cnn -# A shallower cnn model: [CNN, BN, ReLU, Max-Pooling] x4 + FC x1 -# Easier to train for mnist dataset and quite efficient -# Final performance is close to deeper ones on tasks such as digital and character classification -def light_cnn(input_image, num_channels, num_classes): - def __light__(ipt, - num_filter=128, - times=1, - conv_filter_size=3, - dropouts=0, - num_channels_=None): - return img_conv_group( - input=ipt, - num_channels=num_channels_, - pool_size=2, - pool_stride=2, - conv_padding=0, - conv_num_filter=[num_filter] * times, - conv_filter_size=conv_filter_size, - conv_act=ReluActivation(), - conv_with_batchnorm=True, - conv_batchnorm_drop_rate=dropouts, - pool_type=MaxPooling()) - - tmp = __light__(input_image, num_filter=128, num_channels_=num_channels) - tmp = __light__(tmp, num_filter=128) - tmp = __light__(tmp, num_filter=128) - tmp = __light__(tmp, num_filter=128, conv_filter_size=1) - - tmp = fc_layer(input=tmp, size=num_classes, act=SoftmaxActivation()) - return tmp - - -predict = light_cnn(input_image=img, num_channels=1, num_classes=label_size) - -if not is_predict: - lbl = data_layer(name="label", size=label_size) - inputs(img, lbl) - outputs(classification_cost(input=predict, label=lbl)) -else: - outputs(predict) diff --git a/demo/mnist/mnist_provider.py b/demo/mnist/mnist_provider.py deleted file mode 100644 index 888cfef1e7e..00000000000 --- a/demo/mnist/mnist_provider.py +++ /dev/null @@ -1,12 +0,0 @@ -from paddle.trainer.PyDataProvider2 import * -from mnist_util import read_from_mnist - - -# Define a py data provider -@provider( - input_types={'pixel': dense_vector(28 * 28), - 'label': integer_value(10)}, - cache=CacheType.CACHE_PASS_IN_MEM) -def process(settings, filename): # settings is not used currently. - for each in read_from_mnist(filename): - yield each diff --git a/demo/mnist/mnist_util.py b/demo/mnist/mnist_util.py deleted file mode 100644 index 3fd88ae7edc..00000000000 --- a/demo/mnist/mnist_util.py +++ /dev/null @@ -1,30 +0,0 @@ -import numpy - -__all__ = ['read_from_mnist'] - - -def read_from_mnist(filename): - imgf = filename + "-images-idx3-ubyte" - labelf = filename + "-labels-idx1-ubyte" - f = open(imgf, "rb") - l = open(labelf, "rb") - - f.read(16) - l.read(8) - - # Define number of samples for train/test - if "train" in filename: - n = 60000 - else: - n = 10000 - - images = numpy.fromfile( - f, 'ubyte', count=n * 28 * 28).reshape((n, 28 * 28)).astype('float32') - images = images / 255.0 * 2.0 - 1.0 - labels = numpy.fromfile(l, 'ubyte', count=n).astype("int") - - for i in xrange(n): - yield {"pixel": images[i, :], 'label': labels[i]} - - f.close() - l.close() diff --git a/demo/mnist/train.sh b/demo/mnist/train.sh deleted file mode 100755 index ca2b1ad9eb9..00000000000 --- a/demo/mnist/train.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/bin/bash -# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -set -e -config=vgg_16_mnist.py -output=./mnist_vgg_model -log=train.log - -paddle train \ ---config=$config \ ---dot_period=10 \ ---log_period=100 \ ---test_all_data_in_one_period=1 \ ---use_gpu=0 \ ---trainer_count=1 \ ---num_passes=100 \ ---save_dir=$output \ -2>&1 | tee $log -paddle usage -l $log -e $? -n "mnist_train" >/dev/null 2>&1 - -python -m paddle.utils.plotcurve -i $log > plot.png diff --git a/demo/mnist/vgg_16_mnist.py b/demo/mnist/vgg_16_mnist.py deleted file mode 100644 index a819b391c69..00000000000 --- a/demo/mnist/vgg_16_mnist.py +++ /dev/null @@ -1,50 +0,0 @@ -# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from paddle.trainer_config_helpers import * - -is_predict = get_config_arg("is_predict", bool, False) - -####################Data Configuration ################## - -if not is_predict: - data_dir = './data/' - define_py_data_sources2( - train_list=data_dir + 'train.list', - test_list=data_dir + 'test.list', - module='mnist_provider', - obj='process') - -######################Algorithm Configuration ############# -settings( - batch_size=128, - learning_rate=0.1 / 128.0, - learning_method=MomentumOptimizer(0.9), - regularization=L2Regularization(0.0005 * 128)) - -#######################Network Configuration ############# - -data_size = 1 * 28 * 28 -label_size = 10 -img = data_layer(name='pixel', size=data_size) - -# small_vgg is predined in trainer_config_helpers.network -predict = small_vgg(input_image=img, num_channels=1, num_classes=label_size) - -if not is_predict: - lbl = data_layer(name="label", size=label_size) - inputs(img, lbl) - outputs(classification_cost(input=predict, label=lbl)) -else: - outputs(predict) diff --git a/demo/recommendation/.gitignore b/demo/recommendation/.gitignore deleted file mode 100644 index fd27ef62a87..00000000000 --- a/demo/recommendation/.gitignore +++ /dev/null @@ -1,10 +0,0 @@ -log.txt -data/meta.bin -data/ml-1m -data/ratings.dat.train -data/ratings.dat.test -data/train.list -data/test.list -dataprovider_copy_1.py -*.pyc -output diff --git a/demo/recommendation/api_train_v2.py b/demo/recommendation/api_train_v2.py deleted file mode 100644 index f6a061799e3..00000000000 --- a/demo/recommendation/api_train_v2.py +++ /dev/null @@ -1,125 +0,0 @@ -import paddle.v2 as paddle -import cPickle -import copy - - -def main(): - paddle.init(use_gpu=False) - movie_title_dict = paddle.dataset.movielens.get_movie_title_dict() - uid = paddle.layer.data( - name='user_id', - type=paddle.data_type.integer_value( - paddle.dataset.movielens.max_user_id() + 1)) - usr_emb = paddle.layer.embedding(input=uid, size=32) - - usr_gender_id = paddle.layer.data( - name='gender_id', type=paddle.data_type.integer_value(2)) - usr_gender_emb = paddle.layer.embedding(input=usr_gender_id, size=16) - - usr_age_id = paddle.layer.data( - name='age_id', - type=paddle.data_type.integer_value( - len(paddle.dataset.movielens.age_table))) - usr_age_emb = paddle.layer.embedding(input=usr_age_id, size=16) - - usr_job_id = paddle.layer.data( - name='job_id', - type=paddle.data_type.integer_value(paddle.dataset.movielens.max_job_id( - ) + 1)) - - usr_job_emb = paddle.layer.embedding(input=usr_job_id, size=16) - - usr_combined_features = paddle.layer.fc( - input=[usr_emb, usr_gender_emb, usr_age_emb, usr_job_emb], - size=200, - act=paddle.activation.Tanh()) - - mov_id = paddle.layer.data( - name='movie_id', - type=paddle.data_type.integer_value( - paddle.dataset.movielens.max_movie_id() + 1)) - mov_emb = paddle.layer.embedding(input=mov_id, size=32) - - mov_categories = paddle.layer.data( - name='category_id', - type=paddle.data_type.sparse_binary_vector( - len(paddle.dataset.movielens.movie_categories()))) - - mov_categories_hidden = paddle.layer.fc(input=mov_categories, size=32) - - mov_title_id = paddle.layer.data( - name='movie_title', - type=paddle.data_type.integer_value_sequence(len(movie_title_dict))) - mov_title_emb = paddle.layer.embedding(input=mov_title_id, size=32) - mov_title_conv = paddle.networks.sequence_conv_pool( - input=mov_title_emb, hidden_size=32, context_len=3) - - mov_combined_features = paddle.layer.fc( - input=[mov_emb, mov_categories_hidden, mov_title_conv], - size=200, - act=paddle.activation.Tanh()) - - inference = paddle.layer.cos_sim( - a=usr_combined_features, b=mov_combined_features, size=1, scale=5) - cost = paddle.layer.mse_cost( - input=inference, - label=paddle.layer.data( - name='score', type=paddle.data_type.dense_vector(1))) - - parameters = paddle.parameters.create(cost) - - trainer = paddle.trainer.SGD(cost=cost, - parameters=parameters, - update_equation=paddle.optimizer.Adam( - learning_rate=1e-4)) - feeding = { - 'user_id': 0, - 'gender_id': 1, - 'age_id': 2, - 'job_id': 3, - 'movie_id': 4, - 'category_id': 5, - 'movie_title': 6, - 'score': 7 - } - - def event_handler(event): - if isinstance(event, paddle.event.EndIteration): - if event.batch_id % 100 == 0: - print "Pass %d Batch %d Cost %.2f" % ( - event.pass_id, event.batch_id, event.cost) - - trainer.train( - reader=paddle.batch( - paddle.reader.shuffle( - paddle.dataset.movielens.train(), buf_size=8192), - batch_size=256), - event_handler=event_handler, - feeding=feeding, - num_passes=1) - - user_id = 234 - movie_id = 345 - - user = paddle.dataset.movielens.user_info()[user_id] - movie = paddle.dataset.movielens.movie_info()[movie_id] - - feature = user.value() + movie.value() - - def reader(): - yield feature - - infer_dict = copy.copy(feeding) - del infer_dict['score'] - - prediction = paddle.infer( - output=inference, - parameters=parameters, - reader=paddle.batch( - reader, batch_size=32), - feeding=infer_dict) - print(prediction + 5) / 2 - - -if __name__ == '__main__': - main() diff --git a/demo/recommendation/common_utils.py b/demo/recommendation/common_utils.py deleted file mode 100755 index c20c6528662..00000000000 --- a/demo/recommendation/common_utils.py +++ /dev/null @@ -1,30 +0,0 @@ -# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -from paddle.trainer.PyDataProvider2 import * - - -def meta_to_header(meta, name): - metas = meta[name]['__meta__']['raw_meta'] - for each_meta in metas: - slot_name = each_meta.get('name', '%s_id' % name) - if each_meta['type'] == 'id': - yield slot_name, integer_value(each_meta['max']) - elif each_meta['type'] == 'embedding': - is_seq = each_meta['seq'] == 'sequence' - yield slot_name, integer_value( - len(each_meta['dict']), - seq_type=SequenceType.SEQUENCE - if is_seq else SequenceType.NO_SEQUENCE) - elif each_meta['type'] == 'one_hot_dense': - yield slot_name, dense_vector(len(each_meta['dict'])) diff --git a/demo/recommendation/data/config.json b/demo/recommendation/data/config.json deleted file mode 100644 index f26e74ce47b..00000000000 --- a/demo/recommendation/data/config.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "user": { - "file": { - "name": "users.dat", - "delimiter": "::" - }, - "fields": ["id", "gender", "age", "occupation"] - }, - "movie": { - "file": { - "name": "movies.dat", - "delimiter": "::" - }, - "fields": ["id", "title", "genres"] - } -} diff --git a/demo/recommendation/data/config_generator.py b/demo/recommendation/data/config_generator.py deleted file mode 100644 index 4ca496a252d..00000000000 --- a/demo/recommendation/data/config_generator.py +++ /dev/null @@ -1,127 +0,0 @@ -#!/bin/env python2 -# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -""" -config_generator.py - -Usage: - ./config_generator.py [--output_format=] - ./config_generator.py -h | --help - -Options: - -h --help Show this screen. - --output_format= Output Config format(json or yaml) [default: json]. -""" - -import json -import docopt -import copy - -DEFAULT_FILE = {"type": "split", "delimiter": ","} - -DEFAULT_FIELD = { - "id": { - "type": "id" - }, - "gender": { - "name": "gender", - "type": "embedding", - "dict": { - "type": "char_based" - } - }, - "age": { - "name": "age", - "type": "embedding", - "dict": { - "type": "whole_content", - "sort": True - } - }, - "occupation": { - "name": "occupation", - "type": "embedding", - "dict": { - "type": "whole_content", - "sort": "true" - } - }, - "title": { - "regex": { - "pattern": r"^(.*)\((\d+)\)$", - "group_id": 1, - "strip": True - }, - "name": "title", - "type": { - "name": "embedding", - "seq_type": "sequence", - }, - "dict": { - "type": "char_based" - } - }, - "genres": { - "type": "one_hot_dense", - "dict": { - "type": "split", - "delimiter": "|" - }, - "name": "genres" - } -} - - -def merge_dict(master_dict, slave_dict): - return dict(((k, master_dict.get(k) or slave_dict.get(k)) - for k in set(slave_dict) | set(master_dict))) - - -def main(filename, fmt): - with open(filename, 'r') as f: - conf = json.load(f) - obj = dict() - for k in conf: - val = conf[k] - file_dict = val['file'] - file_dict = merge_dict(file_dict, DEFAULT_FILE) - - fields = [] - for pos, field_key in enumerate(val['fields']): - assert isinstance(field_key, basestring) - field = copy.deepcopy(DEFAULT_FIELD[field_key]) - field['pos'] = pos - fields.append(field) - obj[k] = {"file": file_dict, "fields": fields} - meta = {"meta": obj} - # print meta - if fmt == 'json': - - def formatter(x): - import json - return json.dumps(x, indent=2) - elif fmt == 'yaml': - - def formatter(x): - import yaml - return yaml.safe_dump(x, default_flow_style=False) - else: - raise NotImplementedError("Dump format %s is not implemented" % fmt) - - print formatter(meta) - - -if __name__ == '__main__': - args = docopt.docopt(__doc__, version="0.1.0") - main(args[""], args["--output_format"]) diff --git a/demo/recommendation/data/meta_config.json b/demo/recommendation/data/meta_config.json deleted file mode 100644 index cc6a046e271..00000000000 --- a/demo/recommendation/data/meta_config.json +++ /dev/null @@ -1,81 +0,0 @@ -{ - "meta": { - "movie": { - "fields": [ - { - "type": "id", - "pos": 0 - }, - { - "regex": { - "pattern": "^(.*)\\((\\d+)\\)$", - "group_id": 1, - "strip": true - }, - "type": { - "seq_type": "sequence", - "name": "embedding" - }, - "dict": { - "type": "char_based" - }, - "name": "title", - "pos": 1 - }, - { - "type": "one_hot_dense", - "dict": { - "delimiter": "|", - "type": "split" - }, - "name": "genres", - "pos": 2 - } - ], - "file": { - "delimiter": "::", - "type": "split", - "name": "movies.dat" - } - }, - "user": { - "fields": [ - { - "type": "id", - "pos": 0 - }, - { - "type": "embedding", - "dict": { - "type": "char_based" - }, - "name": "gender", - "pos": 1 - }, - { - "type": "embedding", - "dict": { - "sort": true, - "type": "whole_content" - }, - "name": "age", - "pos": 2 - }, - { - "type": "embedding", - "dict": { - "sort": "true", - "type": "whole_content" - }, - "name": "occupation", - "pos": 3 - } - ], - "file": { - "delimiter": "::", - "type": "split", - "name": "users.dat" - } - } - } -} diff --git a/demo/recommendation/data/meta_generator.py b/demo/recommendation/data/meta_generator.py deleted file mode 100644 index 38e4679d266..00000000000 --- a/demo/recommendation/data/meta_generator.py +++ /dev/null @@ -1,430 +0,0 @@ -#!/bin/env python2 -# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -""" -Preprocess Movielens dataset, to get movie/user object. - -Usage: - ./preprocess.py [--config=] - ./preprocess.py -h | --help - -Options: - -h --help Show this screen. - --version Show version. - --config= Get MetaData config file [default: config.json]. -""" -import docopt -import os -import sys -import re -import collections - -try: - import cPickle as pickle -except ImportError: - import pickle - - -class UniqueIDGenerator(object): - def __init__(self): - self.pool = collections.defaultdict(self.__next_id__) - self.next_id = 0 - - def __next_id__(self): - tmp = self.next_id - self.next_id += 1 - return tmp - - def __call__(self, k): - return self.pool[k] - - def to_list(self): - ret_val = [None] * len(self.pool) - for k in self.pool.keys(): - ret_val[self.pool[k]] = k - return ret_val - - -class SortedIDGenerator(object): - def __init__(self): - self.__key_set__ = set() - self.dict = None - - def scan(self, key): - self.__key_set__.add(key) - - def finish_scan(self, compare=None, key=None, reverse=False): - self.__key_set__ = sorted( - list(self.__key_set__), cmp=compare, key=key, reverse=reverse) - self.dict = dict() - for idx, each_key in enumerate(self.__key_set__): - self.dict[each_key] = idx - - def __call__(self, key): - return self.dict[key] - - def to_list(self): - return self.__key_set__ - - -class SplitFileReader(object): - def __init__(self, work_dir, config): - assert isinstance(config, dict) - self.filename = config['name'] - self.delimiter = config.get('delimiter', ',') - self.work_dir = work_dir - - def read(self): - with open(os.path.join(self.work_dir, self.filename), 'r') as f: - for line in f: - line = line.strip() - if isinstance(self.delimiter, unicode): - self.delimiter = str(self.delimiter) - yield line.split(self.delimiter) - - @staticmethod - def create(work_dir, config): - assert isinstance(config, dict) - if config['type'] == 'split': - return SplitFileReader(work_dir, config) - - -class IFileReader(object): - READERS = [SplitFileReader] - - def read(self): - raise NotImplementedError() - - @staticmethod - def create(work_dir, config): - for reader_cls in IFileReader.READERS: - val = reader_cls.create(work_dir, config) - if val is not None: - return val - - -class IDFieldParser(object): - TYPE = 'id' - - def __init__(self, config): - self.__max_id__ = -sys.maxint - 1 - self.__min_id__ = sys.maxint - self.__id_count__ = 0 - - def scan(self, line): - idx = int(line) - self.__max_id__ = max(self.__max_id__, idx) - self.__min_id__ = min(self.__min_id__, idx) - self.__id_count__ += 1 - - def parse(self, line): - return int(line) - - def meta_field(self): - return { - "is_key": True, - 'max': self.__max_id__, - 'min': self.__min_id__, - 'count': self.__id_count__, - 'type': 'id' - } - - -class SplitEmbeddingDict(object): - def __init__(self, delimiter): - self.__id__ = UniqueIDGenerator() - self.delimiter = delimiter - - def scan(self, multi): - for val in multi.split(self.delimiter): - self.__id__(val) - - def parse(self, multi): - return map(self.__id__, multi.split(self.delimiter)) - - def meta_field(self): - return self.__id__.to_list() - - -class EmbeddingFieldParser(object): - TYPE = 'embedding' - - NO_SEQUENCE = "no_sequence" - SEQUENCE = "sequence" - - class CharBasedEmbeddingDict(object): - def __init__(self, is_seq=True): - self.__id__ = UniqueIDGenerator() - self.is_seq = is_seq - - def scan(self, s): - for ch in s: - self.__id__(ch) - - def parse(self, s): - return map(self.__id__, s) if self.is_seq else self.__id__(s[0]) - - def meta_field(self): - return self.__id__.to_list() - - class WholeContentDict(object): - def __init__(self, need_sort=True): - assert need_sort - self.__id__ = SortedIDGenerator() - self.__has_finished__ = False - - def scan(self, txt): - self.__id__.scan(txt) - - def meta_field(self): - if not self.__has_finished__: - self.__id__.finish_scan() - self.__has_finished__ = True - return self.__id__.to_list() - - def parse(self, txt): - return self.__id__(txt) - - def __init__(self, config): - try: - self.seq_type = config['type']['seq_type'] - except TypeError: - self.seq_type = EmbeddingFieldParser.NO_SEQUENCE - - if config['dict']['type'] == 'char_based': - self.dict = EmbeddingFieldParser.CharBasedEmbeddingDict( - self.seq_type == EmbeddingFieldParser.SEQUENCE) - elif config['dict']['type'] == 'split': - self.dict = SplitEmbeddingDict(config['dict'].get('delimiter', ',')) - elif config['dict']['type'] == 'whole_content': - self.dict = EmbeddingFieldParser.WholeContentDict(config['dict'][ - 'sort']) - else: - print config - assert False - - self.name = config['name'] - - def scan(self, s): - self.dict.scan(s) - - def meta_field(self): - return { - 'name': self.name, - 'dict': self.dict.meta_field(), - 'type': 'embedding', - 'seq': self.seq_type - } - - def parse(self, s): - return self.dict.parse(s) - - -class OneHotDenseFieldParser(object): - TYPE = 'one_hot_dense' - - def __init__(self, config): - if config['dict']['type'] == 'split': - self.dict = SplitEmbeddingDict(config['dict']['delimiter']) - self.name = config['name'] - - def scan(self, s): - self.dict.scan(s) - - def meta_field(self): - # print self.dict.meta_field() - return { - 'dict': self.dict.meta_field(), - 'name': self.name, - 'type': 'one_hot_dense' - } - - def parse(self, s): - ids = self.dict.parse(s) - retv = [0.0] * len(self.dict.meta_field()) - for idx in ids: - retv[idx] = 1.0 - # print retv - return retv - - -class FieldParserFactory(object): - PARSERS = [IDFieldParser, EmbeddingFieldParser, OneHotDenseFieldParser] - - @staticmethod - def create(config): - if isinstance(config['type'], basestring): - config_type = config['type'] - elif isinstance(config['type'], dict): - config_type = config['type']['name'] - - assert config_type is not None - - for each_parser_cls in FieldParserFactory.PARSERS: - if config_type == each_parser_cls.TYPE: - return each_parser_cls(config) - print config - - -class CompositeFieldParser(object): - def __init__(self, parser, extractor): - self.extractor = extractor - self.parser = parser - - def scan(self, *args, **kwargs): - self.parser.scan(self.extractor.extract(*args, **kwargs)) - - def parse(self, *args, **kwargs): - return self.parser.parse(self.extractor.extract(*args, **kwargs)) - - def meta_field(self): - return self.parser.meta_field() - - -class PositionContentExtractor(object): - def __init__(self, pos): - self.pos = pos - - def extract(self, line): - assert isinstance(line, list) - return line[self.pos] - - -class RegexPositionContentExtractor(PositionContentExtractor): - def __init__(self, pos, pattern, group_id, strip=True): - PositionContentExtractor.__init__(self, pos) - pattern = pattern.strip() - self.pattern = re.compile(pattern) - self.group_id = group_id - self.strip = strip - - def extract(self, line): - line = PositionContentExtractor.extract(self, line) - match = self.pattern.match(line) - # print line, self.pattern.pattern, match - assert match is not None - txt = match.group(self.group_id) - if self.strip: - txt.strip() - return txt - - -class ContentExtractorFactory(object): - def extract(self, line): - pass - - @staticmethod - def create(config): - if 'pos' in config: - if 'regex' not in config: - return PositionContentExtractor(config['pos']) - else: - extra_args = config['regex'] - return RegexPositionContentExtractor( - pos=config['pos'], **extra_args) - - -class MetaFile(object): - def __init__(self, work_dir): - self.work_dir = work_dir - self.obj = dict() - - def parse(self, config): - config = config['meta'] - - ret_obj = dict() - for key in config.keys(): - val = config[key] - assert 'file' in val - reader = IFileReader.create(self.work_dir, val['file']) - assert reader is not None - assert 'fields' in val and isinstance(val['fields'], list) - fields_config = val['fields'] - field_parsers = map(MetaFile.__field_config_mapper__, fields_config) - - for each_parser in field_parsers: - assert each_parser is not None - - for each_block in reader.read(): - for each_parser in field_parsers: - each_parser.scan(each_block) - - metas = map(lambda x: x.meta_field(), field_parsers) - # print metas - key_index = filter( - lambda x: x is not None, - map(lambda (idx, meta): idx if 'is_key' in meta and meta['is_key'] else None, - enumerate(metas)))[0] - - key_map = [] - for i in range(min(key_index, len(metas))): - key_map.append(i) - for i in range(key_index + 1, len(metas)): - key_map.append(i) - - obj = {'__meta__': {'raw_meta': metas, 'feature_map': key_map}} - - for each_block in reader.read(): - idx = field_parsers[key_index].parse(each_block) - val = [] - for i, each_parser in enumerate(field_parsers): - if i != key_index: - val.append(each_parser.parse(each_block)) - obj[idx] = val - ret_obj[key] = obj - self.obj = ret_obj - return ret_obj - - @staticmethod - def __field_config_mapper__(conf): - assert isinstance(conf, dict) - extrator = ContentExtractorFactory.create(conf) - field_parser = FieldParserFactory.create(conf) - assert extrator is not None - assert field_parser is not None - return CompositeFieldParser(field_parser, extrator) - - def dump(self, fp): - pickle.dump(self.obj, fp, pickle.HIGHEST_PROTOCOL) - - -def preprocess(binary_filename, dataset_dir, config, **kwargs): - assert isinstance(config, str) - with open(config, 'r') as config_file: - file_loader = None - if config.lower().endswith('.yaml'): - import yaml - file_loader = yaml - elif config.lower().endswith('.json'): - import json - file_loader = json - config = file_loader.load(config_file) - meta = MetaFile(dataset_dir) - meta.parse(config) - with open(binary_filename, 'wb') as outf: - meta.dump(outf) - - -if __name__ == '__main__': - args = docopt.docopt(__doc__, version='0.1.0') - kwargs = dict() - for key in args.keys(): - if key != '--help': - param_name = key - assert isinstance(param_name, str) - param_name = param_name.replace('<', '') - param_name = param_name.replace('>', '') - param_name = param_name.replace('--', '') - kwargs[param_name] = args[key] - preprocess(**kwargs) diff --git a/demo/recommendation/data/ml_data.sh b/demo/recommendation/data/ml_data.sh deleted file mode 100755 index 2268d876389..00000000000 --- a/demo/recommendation/data/ml_data.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/bash -# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -set -ex -cd "$(dirname "$0")" -# download the dataset -wget http://files.grouplens.org/datasets/movielens/ml-1m.zip -# unzip the dataset -unzip ml-1m.zip -# remove the unused zip file -rm ml-1m.zip diff --git a/demo/recommendation/data/split.py b/demo/recommendation/data/split.py deleted file mode 100644 index be6869c22f0..00000000000 --- a/demo/recommendation/data/split.py +++ /dev/null @@ -1,66 +0,0 @@ -#!/bin/env python2 -# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -""" -Separate movielens 1m dataset to train/test file. - -Usage: - ./separate.py [--test_ratio=] [--delimiter=] - ./separate.py -h | --help - -Options: - -h --help Show this screen. - --version Show version. - --test_ratio= Test ratio for separate [default: 0.1]. - --delimiter= File delimiter [default: ,]. -""" -import docopt -import collections -import random - - -def process(test_ratio, input_file, delimiter, **kwargs): - test_ratio = float(test_ratio) - rating_dict = collections.defaultdict(list) - with open(input_file, 'r') as f: - for line in f: - user_id = int(line.split(delimiter)[0]) - rating_dict[user_id].append(line.strip()) - - with open(input_file + ".train", 'w') as train_file: - with open(input_file + ".test", 'w') as test_file: - for k in rating_dict.keys(): - lines = rating_dict[k] - assert isinstance(lines, list) - random.shuffle(lines) - test_len = int(len(lines) * test_ratio) - for line in lines[:test_len]: - print >> test_file, line - - for line in lines[test_len:]: - print >> train_file, line - - -if __name__ == '__main__': - args = docopt.docopt(__doc__, version='0.1.0') - kwargs = dict() - for key in args.keys(): - if key != '--help': - param_name = key - assert isinstance(param_name, str) - param_name = param_name.replace('<', '') - param_name = param_name.replace('>', '') - param_name = param_name.replace('--', '') - kwargs[param_name] = args[key] - process(**kwargs) diff --git a/demo/recommendation/dataprovider.py b/demo/recommendation/dataprovider.py deleted file mode 100755 index c4ff96d80e8..00000000000 --- a/demo/recommendation/dataprovider.py +++ /dev/null @@ -1,88 +0,0 @@ -# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from paddle.trainer.PyDataProvider2 import * -import common_utils # parse - - -def __list_to_map__(lst): - ret_val = dict() - for each in lst: - k, v = each - ret_val[k] = v - return ret_val - - -def hook(settings, meta, **kwargs): - """ - Init hook is invoked before process data. It will set obj.slots and store - data meta. - - :param obj: global object. It will passed to process routine. - :type obj: object - :param meta: the meta file object, which passed from trainer_config. Meta - file record movie/user features. - :param kwargs: unused other arguments. - """ - del kwargs # unused kwargs - - # Header define slots that used for paddle. - # first part is movie features. - # second part is user features. - # final part is rating score. - # header is a list of [USE_SEQ_OR_NOT?, SlotType] - movie_headers = list(common_utils.meta_to_header(meta, 'movie')) - settings.movie_names = [h[0] for h in movie_headers] - headers = movie_headers - user_headers = list(common_utils.meta_to_header(meta, 'user')) - settings.user_names = [h[0] for h in user_headers] - headers.extend(user_headers) - headers.append(("rating", dense_vector(1))) # Score - - # slot types. - settings.input_types = __list_to_map__(headers) - settings.meta = meta - - -@provider(init_hook=hook, cache=CacheType.CACHE_PASS_IN_MEM) -def process(settings, filename): - with open(filename, 'r') as f: - for line in f: - # Get a rating from file. - user_id, movie_id, score = map(int, line.split('::')[:-1]) - - # Scale score to [-5, +5] - score = float(score) * 2 - 5.0 - - # Get movie/user features by movie_id, user_id - movie_meta = settings.meta['movie'][movie_id] - user_meta = settings.meta['user'][user_id] - - outputs = [('movie_id', movie_id - 1)] - - # Then add movie features - for i, each_meta in enumerate(movie_meta): - outputs.append((settings.movie_names[i + 1], each_meta)) - - # Then add user id. - outputs.append(('user_id', user_id - 1)) - - # Then add user features. - for i, each_meta in enumerate(user_meta): - outputs.append((settings.user_names[i + 1], each_meta)) - - # Finally, add score - outputs.append(('rating', [score])) - # Return data to paddle - yield __list_to_map__(outputs) diff --git a/demo/recommendation/evaluate.py b/demo/recommendation/evaluate.py deleted file mode 100755 index 3afa7a1e9db..00000000000 --- a/demo/recommendation/evaluate.py +++ /dev/null @@ -1,37 +0,0 @@ -#!/usr/bin/python -# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -import sys -import re -import math - - -def get_best_pass(log_filename): - with open(log_filename, 'r') as f: - text = f.read() - pattern = re.compile('Test.*? cost=([0-9]+\.[0-9]+).*?pass-([0-9]+)', - re.S) - results = re.findall(pattern, text) - sorted_results = sorted(results, key=lambda result: float(result[0])) - return sorted_results[0] - - -log_filename = sys.argv[1] -log = get_best_pass(log_filename) -predict_error = math.sqrt(float(log[0])) / 2 -print 'Best pass is %s, error is %s, which means predict get error as %f' % ( - log[1], log[0], predict_error) - -evaluate_pass = "output/pass-%s" % log[1] -print "evaluating from pass %s" % evaluate_pass diff --git a/demo/recommendation/evaluate.sh b/demo/recommendation/evaluate.sh deleted file mode 100755 index 02b2857de02..00000000000 --- a/demo/recommendation/evaluate.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/bin/bash -# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -set -e - -function get_best_pass() { - cat $1 | grep -Pzo 'Test .*\n.*pass-.*' | sed -r 'N;s/Test.* cost=([0-9]+\.[0-9]+).*\n.*pass-([0-9]+)/\1 \2/g' | sort | head -n 1 -} - -LOG=`get_best_pass log.txt` -LOG=(${LOG}) -echo 'Best pass is '${LOG[1]}, ' error is '${LOG[0]}, 'which means predict get error as '`echo ${LOG[0]} | python -c 'import math; print math.sqrt(float(raw_input()))/2'` - -evaluate_pass="output/pass-${LOG[1]}" - -echo 'evaluating from pass '$evaluate_pass diff --git a/demo/recommendation/prediction.py b/demo/recommendation/prediction.py deleted file mode 100755 index 8ad993eab3a..00000000000 --- a/demo/recommendation/prediction.py +++ /dev/null @@ -1,51 +0,0 @@ -#!/bin/env python2 -# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from py_paddle import swig_paddle, DataProviderConverter - -from common_utils import * -from paddle.trainer.config_parser import parse_config - -try: - import cPickle as pickle -except ImportError: - import pickle -import sys - -if __name__ == '__main__': - model_path = sys.argv[1] - swig_paddle.initPaddle('--use_gpu=0') - conf = parse_config("trainer_config.py", "is_predict=1") - network = swig_paddle.GradientMachine.createFromConfigProto( - conf.model_config) - assert isinstance(network, swig_paddle.GradientMachine) - network.loadParameters(model_path) - with open('./data/meta.bin', 'rb') as f: - meta = pickle.load(f) - headers = [h[1] for h in meta_to_header(meta, 'movie')] - headers.extend([h[1] for h in meta_to_header(meta, 'user')]) - cvt = DataProviderConverter(headers) - while True: - movie_id = int(raw_input("Input movie_id: ")) - user_id = int(raw_input("Input user_id: ")) - movie_meta = meta['movie'][movie_id] # Query Data From Meta. - user_meta = meta['user'][user_id] - data = [movie_id - 1] - data.extend(movie_meta) - data.append(user_id - 1) - data.extend(user_meta) - print "Prediction Score is %.2f" % ( - (network.forwardTest(cvt.convert([data]))[0]['value'][0][0] + 5) - / 2) diff --git a/demo/recommendation/preprocess.sh b/demo/recommendation/preprocess.sh deleted file mode 100755 index eeb81ce3cb4..00000000000 --- a/demo/recommendation/preprocess.sh +++ /dev/null @@ -1,40 +0,0 @@ -#!/bin/bash -# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -set -e - -UNAME_STR=`uname` - -if [[ ${UNAME_STR} == 'Linux' ]]; then - SHUF_PROG='shuf' -else - SHUF_PROG='gshuf' -fi - - -cd "$(dirname "$0")" -delimiter='::' -dir=ml-1m -cd data -echo 'generate meta config file' -python config_generator.py config.json > meta_config.json -echo 'generate meta file' -python meta_generator.py $dir meta.bin --config=meta_config.json -echo 'split train/test file' -python split.py $dir/ratings.dat --delimiter=${delimiter} --test_ratio=0.1 -echo 'shuffle train file' -${SHUF_PROG} $dir/ratings.dat.train > ratings.dat.train -cp $dir/ratings.dat.test . -echo "./data/ratings.dat.train" > train.list -echo "./data/ratings.dat.test" > test.list diff --git a/demo/recommendation/requirements.txt b/demo/recommendation/requirements.txt deleted file mode 100644 index 1ea154584a4..00000000000 --- a/demo/recommendation/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -PyYAML -docopt diff --git a/demo/recommendation/run.sh b/demo/recommendation/run.sh deleted file mode 100755 index 22aef556082..00000000000 --- a/demo/recommendation/run.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/bin/bash -# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -set -e -paddle train \ - --config=trainer_config.py \ - --save_dir=./output \ - --use_gpu=false \ - --trainer_count=4\ - --test_all_data_in_one_period=true \ - --log_period=100 \ - --dot_period=1 \ - --num_passes=50 2>&1 | tee 'log.txt' -paddle usage -l log.txt -e $? -n "recommendation" >/dev/null 2>&1 diff --git a/demo/recommendation/trainer_config.py b/demo/recommendation/trainer_config.py deleted file mode 100755 index 25f529d7d7c..00000000000 --- a/demo/recommendation/trainer_config.py +++ /dev/null @@ -1,98 +0,0 @@ -# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from paddle.trainer_config_helpers import * - -try: - import cPickle as pickle -except ImportError: - import pickle - -is_predict = get_config_arg('is_predict', bool, False) - -META_FILE = 'data/meta.bin' - -with open(META_FILE, 'rb') as f: - # load meta file - meta = pickle.load(f) - -settings( - batch_size=1600, learning_rate=1e-3, learning_method=RMSPropOptimizer()) - - -def construct_feature(name): - """ - Construct movie/user features. - - This method read from meta data. Then convert feature to neural network due - to feature type. The map relation as follow. - - * id: embedding => fc - * embedding: - is_sequence: embedding => context_projection => fc => pool - not sequence: embedding => fc - * one_hot_dense: fc => fc - - Then gather all features vector, and use a fc layer to combined them as - return. - - :param name: 'movie' or 'user' - :type name: basestring - :return: combined feature output - :rtype: LayerOutput - """ - __meta__ = meta[name]['__meta__']['raw_meta'] - fusion = [] - for each_meta in __meta__: - type_name = each_meta['type'] - slot_name = each_meta.get('name', '%s_id' % name) - if type_name == 'id': - slot_dim = each_meta['max'] - embedding = embedding_layer( - input=data_layer( - slot_name, size=slot_dim), size=256) - fusion.append(fc_layer(input=embedding, size=256)) - elif type_name == 'embedding': - is_seq = each_meta['seq'] == 'sequence' - slot_dim = len(each_meta['dict']) - din = data_layer(slot_name, slot_dim) - embedding = embedding_layer(input=din, size=256) - if is_seq: - fusion.append( - text_conv_pool( - input=embedding, context_len=5, hidden_size=256)) - else: - fusion.append(fc_layer(input=embedding, size=256)) - elif type_name == 'one_hot_dense': - slot_dim = len(each_meta['dict']) - hidden = fc_layer(input=data_layer(slot_name, slot_dim), size=256) - fusion.append(fc_layer(input=hidden, size=256)) - - return fc_layer(name="%s_fusion" % name, input=fusion, size=256) - - -movie_feature = construct_feature("movie") -user_feature = construct_feature("user") -similarity = cos_sim(a=movie_feature, b=user_feature) -if not is_predict: - outputs(mse_cost(input=similarity, label=data_layer('rating', size=1))) - - define_py_data_sources2( - 'data/train.list', - 'data/test.list', - module='dataprovider', - obj='process', - args={'meta': meta}) -else: - outputs(similarity) diff --git a/demo/semantic_role_labeling/.gitignore b/demo/semantic_role_labeling/.gitignore deleted file mode 100644 index 65c9b674c7d..00000000000 --- a/demo/semantic_role_labeling/.gitignore +++ /dev/null @@ -1,14 +0,0 @@ -*.pyc -train.log -data/feature -data/conll05st-release/ -data/src.dict -data/test.wsj.props -data/test.wsj.seq_pair -data/test.wsj.words -data/tgt.dict -output -data/emb -data/targetDict.txt -data/verbDict.txt -data/wordDict.txt diff --git a/demo/semantic_role_labeling/api_train_v2.py b/demo/semantic_role_labeling/api_train_v2.py deleted file mode 100644 index 3af636aef58..00000000000 --- a/demo/semantic_role_labeling/api_train_v2.py +++ /dev/null @@ -1,277 +0,0 @@ -import math -import numpy as np -import gzip -import logging -import paddle.v2.dataset.conll05 as conll05 -import paddle.v2.evaluator as evaluator -import paddle.v2 as paddle - -logger = logging.getLogger('paddle') - -word_dict, verb_dict, label_dict = conll05.get_dict() -word_dict_len = len(word_dict) -label_dict_len = len(label_dict) -pred_len = len(verb_dict) - -mark_dict_len = 2 -word_dim = 32 -mark_dim = 5 -hidden_dim = 512 -depth = 8 -default_std = 1 / math.sqrt(hidden_dim) / 3.0 -mix_hidden_lr = 1e-3 - - -def d_type(size): - return paddle.data_type.integer_value_sequence(size) - - -def db_lstm(): - #8 features - word = paddle.layer.data(name='word_data', type=d_type(word_dict_len)) - predicate = paddle.layer.data(name='verb_data', type=d_type(pred_len)) - - ctx_n2 = paddle.layer.data(name='ctx_n2_data', type=d_type(word_dict_len)) - ctx_n1 = paddle.layer.data(name='ctx_n1_data', type=d_type(word_dict_len)) - ctx_0 = paddle.layer.data(name='ctx_0_data', type=d_type(word_dict_len)) - ctx_p1 = paddle.layer.data(name='ctx_p1_data', type=d_type(word_dict_len)) - ctx_p2 = paddle.layer.data(name='ctx_p2_data', type=d_type(word_dict_len)) - mark = paddle.layer.data(name='mark_data', type=d_type(mark_dict_len)) - - emb_para = paddle.attr.Param(name='emb', initial_std=0., is_static=True) - std_0 = paddle.attr.Param(initial_std=0.) - std_default = paddle.attr.Param(initial_std=default_std) - - predicate_embedding = paddle.layer.embedding( - size=word_dim, - input=predicate, - param_attr=paddle.attr.Param( - name='vemb', initial_std=default_std)) - mark_embedding = paddle.layer.embedding( - size=mark_dim, input=mark, param_attr=std_0) - - word_input = [word, ctx_n2, ctx_n1, ctx_0, ctx_p1, ctx_p2] - emb_layers = [ - paddle.layer.embedding( - size=word_dim, input=x, param_attr=emb_para) for x in word_input - ] - emb_layers.append(predicate_embedding) - emb_layers.append(mark_embedding) - - hidden_0 = paddle.layer.mixed( - size=hidden_dim, - bias_attr=std_default, - input=[ - paddle.layer.full_matrix_projection( - input=emb, param_attr=std_default) for emb in emb_layers - ]) - - lstm_para_attr = paddle.attr.Param(initial_std=0.0, learning_rate=1.0) - hidden_para_attr = paddle.attr.Param( - initial_std=default_std, learning_rate=mix_hidden_lr) - - lstm_0 = paddle.layer.lstmemory( - input=hidden_0, - act=paddle.activation.Relu(), - gate_act=paddle.activation.Sigmoid(), - state_act=paddle.activation.Sigmoid(), - bias_attr=std_0, - param_attr=lstm_para_attr) - - #stack L-LSTM and R-LSTM with direct edges - input_tmp = [hidden_0, lstm_0] - - for i in range(1, depth): - mix_hidden = paddle.layer.mixed( - size=hidden_dim, - bias_attr=std_default, - input=[ - paddle.layer.full_matrix_projection( - input=input_tmp[0], param_attr=hidden_para_attr), - paddle.layer.full_matrix_projection( - input=input_tmp[1], param_attr=lstm_para_attr) - ]) - - lstm = paddle.layer.lstmemory( - input=mix_hidden, - act=paddle.activation.Relu(), - gate_act=paddle.activation.Sigmoid(), - state_act=paddle.activation.Sigmoid(), - reverse=((i % 2) == 1), - bias_attr=std_0, - param_attr=lstm_para_attr) - - input_tmp = [mix_hidden, lstm] - - feature_out = paddle.layer.mixed( - size=label_dict_len, - bias_attr=std_default, - input=[ - paddle.layer.full_matrix_projection( - input=input_tmp[0], param_attr=hidden_para_attr), - paddle.layer.full_matrix_projection( - input=input_tmp[1], param_attr=lstm_para_attr) - ], ) - - return feature_out - - -def load_parameter(file_name, h, w): - with open(file_name, 'rb') as f: - f.read(16) # skip header. - return np.fromfile(f, dtype=np.float32).reshape(h, w) - - -def train(): - paddle.init(use_gpu=False, trainer_count=1) - - # define network topology - feature_out = db_lstm() - target = paddle.layer.data(name='target', type=d_type(label_dict_len)) - crf_cost = paddle.layer.crf(size=label_dict_len, - input=feature_out, - label=target, - param_attr=paddle.attr.Param( - name='crfw', - initial_std=default_std, - learning_rate=mix_hidden_lr)) - - crf_dec = paddle.layer.crf_decoding( - size=label_dict_len, - input=feature_out, - label=target, - param_attr=paddle.attr.Param(name='crfw')) - evaluator.sum(input=crf_dec) - - # create parameters - parameters = paddle.parameters.create(crf_cost) - parameters.set('emb', load_parameter(conll05.get_embedding(), 44068, 32)) - - # create optimizer - optimizer = paddle.optimizer.Momentum( - momentum=0, - learning_rate=2e-2, - regularization=paddle.optimizer.L2Regularization(rate=8e-4), - model_average=paddle.optimizer.ModelAverage( - average_window=0.5, max_average_window=10000), ) - - trainer = paddle.trainer.SGD(cost=crf_cost, - parameters=parameters, - update_equation=optimizer, - extra_layers=crf_dec) - - reader = paddle.batch( - paddle.reader.shuffle( - conll05.test(), buf_size=8192), batch_size=10) - - feeding = { - 'word_data': 0, - 'ctx_n2_data': 1, - 'ctx_n1_data': 2, - 'ctx_0_data': 3, - 'ctx_p1_data': 4, - 'ctx_p2_data': 5, - 'verb_data': 6, - 'mark_data': 7, - 'target': 8 - } - - def event_handler(event): - if isinstance(event, paddle.event.EndIteration): - if event.batch_id % 100 == 0: - logger.info("Pass %d, Batch %d, Cost %f, %s" % ( - event.pass_id, event.batch_id, event.cost, event.metrics)) - if event.batch_id and event.batch_id % 1000 == 0: - result = trainer.test(reader=reader, feeding=feeding) - logger.info("\nTest with Pass %d, Batch %d, %s" % - (event.pass_id, event.batch_id, result.metrics)) - - if isinstance(event, paddle.event.EndPass): - # save parameters - with gzip.open('params_pass_%d.tar.gz' % event.pass_id, 'w') as f: - parameters.to_tar(f) - - result = trainer.test(reader=reader, feeding=feeding) - logger.info("\nTest with Pass %d, %s" % - (event.pass_id, result.metrics)) - - trainer.train( - reader=reader, - event_handler=event_handler, - num_passes=10, - feeding=feeding) - - -def infer_a_batch(inferer, test_data, word_dict, pred_dict, label_dict): - probs = inferer.infer(input=test_data, field='id') - assert len(probs) == sum(len(x[0]) for x in test_data) - - for idx, test_sample in enumerate(test_data): - start_id = 0 - pred_str = "%s\t" % (pred_dict[test_sample[6][0]]) - - for w, tag in zip(test_sample[0], - probs[start_id:start_id + len(test_sample[0])]): - pred_str += "%s[%s] " % (word_dict[w], label_dict[tag]) - print(pred_str.strip()) - start_id += len(test_sample[0]) - - -def infer(): - label_dict_reverse = dict((value, key) - for key, value in label_dict.iteritems()) - word_dict_reverse = dict((value, key) - for key, value in word_dict.iteritems()) - pred_dict_reverse = dict((value, key) - for key, value in verb_dict.iteritems()) - - test_creator = paddle.dataset.conll05.test() - - paddle.init(use_gpu=False, trainer_count=1) - - # define network topology - feature_out = db_lstm() - predict = paddle.layer.crf_decoding( - size=label_dict_len, - input=feature_out, - param_attr=paddle.attr.Param(name='crfw')) - - test_pass = 0 - with gzip.open('params_pass_%d.tar.gz' % (test_pass)) as f: - parameters = paddle.parameters.Parameters.from_tar(f) - inferer = paddle.inference.Inference( - output_layer=predict, parameters=parameters) - - # prepare test data - test_data = [] - test_batch_size = 50 - - for idx, item in enumerate(test_creator()): - test_data.append(item[0:8]) - - if idx and (not idx % test_batch_size): - infer_a_batch( - inferer, - test_data, - word_dict_reverse, - pred_dict_reverse, - label_dict_reverse, ) - test_data = [] - infer_a_batch( - inferer, - test_data, - word_dict_reverse, - pred_dict_reverse, - label_dict_reverse, ) - test_data = [] - - -def main(is_inferring=False): - if is_inferring: - infer() - else: - train() - - -if __name__ == '__main__': - main(is_inferring=False) diff --git a/demo/semantic_role_labeling/data/extract_dict_feature.py b/demo/semantic_role_labeling/data/extract_dict_feature.py deleted file mode 100644 index da44111976a..00000000000 --- a/demo/semantic_role_labeling/data/extract_dict_feature.py +++ /dev/null @@ -1,81 +0,0 @@ -# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import sys -import os -from optparse import OptionParser - - -def extract_dict_features(pair_file, feature_file): - - with open(pair_file) as fin, open(feature_file, 'w') as feature_out: - for line in fin: - sentence, predicate, labels = line.strip().split('\t') - sentence_list = sentence.split() - labels_list = labels.split() - - verb_index = labels_list.index('B-V') - - mark = [0] * len(labels_list) - if verb_index > 0: - mark[verb_index - 1] = 1 - ctx_n1 = sentence_list[verb_index - 1] - else: - ctx_n1 = 'bos' - - if verb_index > 1: - mark[verb_index - 2] = 1 - ctx_n2 = sentence_list[verb_index - 2] - else: - ctx_n2 = 'bos' - - mark[verb_index] = 1 - ctx_0 = sentence_list[verb_index] - - if verb_index < len(labels_list) - 1: - mark[verb_index + 1] = 1 - ctx_p1 = sentence_list[verb_index + 1] - else: - ctx_p1 = 'eos' - - if verb_index < len(labels_list) - 2: - mark[verb_index + 2] = 1 - ctx_p2 = sentence_list[verb_index + 2] - else: - ctx_p2 = 'eos' - - - feature_str = sentence + '\t' \ - + predicate + '\t' \ - + ctx_n2 + '\t' \ - + ctx_n1 + '\t' \ - + ctx_0 + '\t' \ - + ctx_p1 + '\t' \ - + ctx_p2 + '\t' \ - + ' '.join([str(i) for i in mark]) + '\t' \ - + labels - - feature_out.write(feature_str + '\n') - - -if __name__ == '__main__': - - usage = '-p pair_file -f feature_file' - parser = OptionParser(usage) - parser.add_option('-p', dest='pair_file', help='the pair file') - parser.add_option('-f', dest='feature_file', help='the feature file') - - (options, args) = parser.parse_args() - - extract_dict_features(options.pair_file, options.feature_file) diff --git a/demo/semantic_role_labeling/data/extract_pairs.py b/demo/semantic_role_labeling/data/extract_pairs.py deleted file mode 100644 index 94a8488c167..00000000000 --- a/demo/semantic_role_labeling/data/extract_pairs.py +++ /dev/null @@ -1,122 +0,0 @@ -# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import sys -import os -from optparse import OptionParser - - -def read_labels(props_file): - ''' - a sentence maybe has more than one verb, each verb has its label sequence - label[], is a 3-dimension list. - the first dim is to store all sentence's label seqs, len is the sentence number - the second dim is to store all label sequences for one sentences - the third dim is to store each label for one word - ''' - labels = [] - with open(props_file) as fin: - label_seqs_for_one_sentences = [] - one_seg_in_file = [] - for line in fin: - line = line.strip() - if line == '': - for i in xrange(len(one_seg_in_file[0])): - a_kind_lable = [x[i] for x in one_seg_in_file] - label_seqs_for_one_sentences.append(a_kind_lable) - labels.append(label_seqs_for_one_sentences) - one_seg_in_file = [] - label_seqs_for_one_sentences = [] - else: - part = line.split() - one_seg_in_file.append(part) - return labels - - -def read_sentences(words_file): - sentences = [] - with open(words_file) as fin: - s = '' - for line in fin: - line = line.strip() - if line == '': - sentences.append(s) - s = '' - else: - s += line + ' ' - return sentences - - -def transform_labels(sentences, labels): - sen_lab_pair = [] - for i in xrange(len(sentences)): - if len(labels[i]) == 1: - continue - else: - verb_list = [] - for x in labels[i][0]: - if x != '-': - verb_list.append(x) - - for j in xrange(1, len(labels[i])): - label_list = labels[i][j] - current_tag = 'O' - is_in_bracket = False - label_seq = [] - verb_word = '' - for ll in label_list: - if ll == '*' and is_in_bracket == False: - label_seq.append('O') - elif ll == '*' and is_in_bracket == True: - label_seq.append('I-' + current_tag) - elif ll == '*)': - label_seq.append('I-' + current_tag) - is_in_bracket = False - elif ll.find('(') != -1 and ll.find(')') != -1: - current_tag = ll[1:ll.find('*')] - label_seq.append('B-' + current_tag) - is_in_bracket = False - elif ll.find('(') != -1 and ll.find(')') == -1: - current_tag = ll[1:ll.find('*')] - label_seq.append('B-' + current_tag) - is_in_bracket = True - else: - print 'error:', ll - sen_lab_pair.append((sentences[i], verb_list[j - 1], label_seq)) - return sen_lab_pair - - -def write_file(sen_lab_pair, output_file): - with open(output_file, 'w') as fout: - for x in sen_lab_pair: - sentence = x[0] - label_seq = ' '.join(x[2]) - assert len(sentence.split()) == len(x[2]) - fout.write(sentence + '\t' + x[1] + '\t' + label_seq + '\n') - - -if __name__ == '__main__': - - usage = '-w words_file -p props_file -o output_file' - parser = OptionParser(usage) - parser.add_option('-w', dest='words_file', help='the words file') - parser.add_option('-p', dest='props_file', help='the props file') - parser.add_option('-o', dest='output_file', help='the output_file') - (options, args) = parser.parse_args() - - sentences = read_sentences(options.words_file) - labels = read_labels(options.props_file) - sen_lab_pair = transform_labels(sentences, labels) - - write_file(sen_lab_pair, options.output_file) diff --git a/demo/semantic_role_labeling/data/get_data.sh b/demo/semantic_role_labeling/data/get_data.sh deleted file mode 100755 index a0ef26a13b9..00000000000 --- a/demo/semantic_role_labeling/data/get_data.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/bin/bash -# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -set -e -wget http://www.cs.upc.edu/~srlconll/conll05st-tests.tar.gz -wget http://paddlepaddle.bj.bcebos.com/demo/srl_dict_and_embedding/verbDict.txt -wget http://paddlepaddle.bj.bcebos.com/demo/srl_dict_and_embedding/targetDict.txt -wget http://paddlepaddle.bj.bcebos.com/demo/srl_dict_and_embedding/wordDict.txt -wget http://paddlepaddle.bj.bcebos.com/demo/srl_dict_and_embedding/emb -tar -xzvf conll05st-tests.tar.gz -rm conll05st-tests.tar.gz -cp ./conll05st-release/test.wsj/words/test.wsj.words.gz . -cp ./conll05st-release/test.wsj/props/test.wsj.props.gz . -gunzip test.wsj.words.gz -gunzip test.wsj.props.gz - -python extract_pairs.py -w test.wsj.words -p test.wsj.props -o test.wsj.seq_pair -python extract_dict_feature.py -p test.wsj.seq_pair -f feature diff --git a/demo/semantic_role_labeling/data/test.list b/demo/semantic_role_labeling/data/test.list deleted file mode 100644 index ec370e897a7..00000000000 --- a/demo/semantic_role_labeling/data/test.list +++ /dev/null @@ -1 +0,0 @@ -./data/feature diff --git a/demo/semantic_role_labeling/data/train.list b/demo/semantic_role_labeling/data/train.list deleted file mode 100644 index ec370e897a7..00000000000 --- a/demo/semantic_role_labeling/data/train.list +++ /dev/null @@ -1 +0,0 @@ -./data/feature diff --git a/demo/semantic_role_labeling/dataprovider.py b/demo/semantic_role_labeling/dataprovider.py deleted file mode 100644 index 360c57ea628..00000000000 --- a/demo/semantic_role_labeling/dataprovider.py +++ /dev/null @@ -1,71 +0,0 @@ -# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from paddle.trainer.PyDataProvider2 import * - -UNK_IDX = 0 - - -def hook(settings, word_dict, label_dict, predicate_dict, **kwargs): - settings.word_dict = word_dict - settings.label_dict = label_dict - settings.predicate_dict = predicate_dict - - #all inputs are integral and sequential type - settings.slots = [ - integer_value_sequence(len(word_dict)), - integer_value_sequence(len(word_dict)), - integer_value_sequence(len(word_dict)), - integer_value_sequence(len(word_dict)), - integer_value_sequence(len(word_dict)), - integer_value_sequence(len(word_dict)), - integer_value_sequence(len(predicate_dict)), integer_value_sequence(2), - integer_value_sequence(len(label_dict)) - ] - - -def get_batch_size(yeild_data): - return len(yeild_data[0]) - - -@provider( - init_hook=hook, - should_shuffle=True, - calc_batch_size=get_batch_size, - can_over_batch_size=True, - cache=CacheType.CACHE_PASS_IN_MEM) -def process(settings, file_name): - with open(file_name, 'r') as fdata: - for line in fdata: - sentence, predicate, ctx_n2, ctx_n1, ctx_0, ctx_p1, ctx_p2, mark, label = \ - line.strip().split('\t') - - words = sentence.split() - sen_len = len(words) - word_slot = [settings.word_dict.get(w, UNK_IDX) for w in words] - - predicate_slot = [settings.predicate_dict.get(predicate)] * sen_len - ctx_n2_slot = [settings.word_dict.get(ctx_n2, UNK_IDX)] * sen_len - ctx_n1_slot = [settings.word_dict.get(ctx_n1, UNK_IDX)] * sen_len - ctx_0_slot = [settings.word_dict.get(ctx_0, UNK_IDX)] * sen_len - ctx_p1_slot = [settings.word_dict.get(ctx_p1, UNK_IDX)] * sen_len - ctx_p2_slot = [settings.word_dict.get(ctx_p2, UNK_IDX)] * sen_len - - marks = mark.split() - mark_slot = [int(w) for w in marks] - - label_list = label.split() - label_slot = [settings.label_dict.get(w) for w in label_list] - yield word_slot, ctx_n2_slot, ctx_n1_slot, \ - ctx_0_slot, ctx_p1_slot, ctx_p2_slot, predicate_slot, mark_slot, label_slot diff --git a/demo/semantic_role_labeling/db_lstm.py b/demo/semantic_role_labeling/db_lstm.py deleted file mode 100644 index 04e2a559b19..00000000000 --- a/demo/semantic_role_labeling/db_lstm.py +++ /dev/null @@ -1,218 +0,0 @@ -# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import math -import os -import sys -from paddle.trainer_config_helpers import * - -#file paths -word_dict_file = './data/wordDict.txt' -label_dict_file = './data/targetDict.txt' -predicate_file = './data/verbDict.txt' -train_list_file = './data/train.list' -test_list_file = './data/test.list' - -is_test = get_config_arg('is_test', bool, False) -is_predict = get_config_arg('is_predict', bool, False) - -if not is_predict: - #load dictionaries - word_dict = dict() - label_dict = dict() - predicate_dict = dict() - with open(word_dict_file, 'r') as f_word, \ - open(label_dict_file, 'r') as f_label, \ - open(predicate_file, 'r') as f_pre: - for i, line in enumerate(f_word): - w = line.strip() - word_dict[w] = i - - for i, line in enumerate(f_label): - w = line.strip() - label_dict[w] = i - - for i, line in enumerate(f_pre): - w = line.strip() - predicate_dict[w] = i - - if is_test: - train_list_file = None - - #define data provider - define_py_data_sources2( - train_list=train_list_file, - test_list=test_list_file, - module='dataprovider', - obj='process', - args={ - 'word_dict': word_dict, - 'label_dict': label_dict, - 'predicate_dict': predicate_dict - }) - - word_dict_len = len(word_dict) - label_dict_len = len(label_dict) - pred_len = len(predicate_dict) - -else: - word_dict_len = get_config_arg('dict_len', int) - label_dict_len = get_config_arg('label_len', int) - pred_len = get_config_arg('pred_len', int) - -############################## Hyper-parameters ################################## -mark_dict_len = 2 -word_dim = 32 -mark_dim = 5 -hidden_dim = 512 -depth = 8 - -########################### Optimizer ####################################### - -settings( - batch_size=150, - learning_method=MomentumOptimizer(momentum=0), - learning_rate=2e-2, - regularization=L2Regularization(8e-4), - is_async=False, - model_average=ModelAverage( - average_window=0.5, max_average_window=10000), ) - -####################################### network ############################## -#8 features and 1 target -word = data_layer(name='word_data', size=word_dict_len) -predicate = data_layer(name='verb_data', size=pred_len) - -ctx_n2 = data_layer(name='ctx_n2_data', size=word_dict_len) -ctx_n1 = data_layer(name='ctx_n1_data', size=word_dict_len) -ctx_0 = data_layer(name='ctx_0_data', size=word_dict_len) -ctx_p1 = data_layer(name='ctx_p1_data', size=word_dict_len) -ctx_p2 = data_layer(name='ctx_p2_data', size=word_dict_len) -mark = data_layer(name='mark_data', size=mark_dict_len) - -if not is_predict: - target = data_layer(name='target', size=label_dict_len) - -default_std = 1 / math.sqrt(hidden_dim) / 3.0 - -emb_para = ParameterAttribute(name='emb', initial_std=0., learning_rate=0.) -std_0 = ParameterAttribute(initial_std=0.) -std_default = ParameterAttribute(initial_std=default_std) - -predicate_embedding = embedding_layer( - size=word_dim, - input=predicate, - param_attr=ParameterAttribute( - name='vemb', initial_std=default_std)) -mark_embedding = embedding_layer( - name='word_ctx-in_embedding', size=mark_dim, input=mark, param_attr=std_0) - -word_input = [word, ctx_n2, ctx_n1, ctx_0, ctx_p1, ctx_p2] -emb_layers = [ - embedding_layer( - size=word_dim, input=x, param_attr=emb_para) for x in word_input -] -emb_layers.append(predicate_embedding) -emb_layers.append(mark_embedding) - -hidden_0 = mixed_layer( - name='hidden0', - size=hidden_dim, - bias_attr=std_default, - input=[ - full_matrix_projection( - input=emb, param_attr=std_default) for emb in emb_layers - ]) - -mix_hidden_lr = 1e-3 -lstm_para_attr = ParameterAttribute(initial_std=0.0, learning_rate=1.0) -hidden_para_attr = ParameterAttribute( - initial_std=default_std, learning_rate=mix_hidden_lr) - -lstm_0 = lstmemory( - name='lstm0', - input=hidden_0, - act=ReluActivation(), - gate_act=SigmoidActivation(), - state_act=SigmoidActivation(), - bias_attr=std_0, - param_attr=lstm_para_attr) - -#stack L-LSTM and R-LSTM with direct edges -input_tmp = [hidden_0, lstm_0] - -for i in range(1, depth): - - mix_hidden = mixed_layer( - name='hidden' + str(i), - size=hidden_dim, - bias_attr=std_default, - input=[ - full_matrix_projection( - input=input_tmp[0], param_attr=hidden_para_attr), - full_matrix_projection( - input=input_tmp[1], param_attr=lstm_para_attr) - ]) - - lstm = lstmemory( - name='lstm' + str(i), - input=mix_hidden, - act=ReluActivation(), - gate_act=SigmoidActivation(), - state_act=SigmoidActivation(), - reverse=((i % 2) == 1), - bias_attr=std_0, - param_attr=lstm_para_attr) - - input_tmp = [mix_hidden, lstm] - -feature_out = mixed_layer( - name='output', - size=label_dict_len, - bias_attr=std_default, - input=[ - full_matrix_projection( - input=input_tmp[0], param_attr=hidden_para_attr), - full_matrix_projection( - input=input_tmp[1], param_attr=lstm_para_attr) - ], ) - -if not is_predict: - crf_l = crf_layer( - name='crf', - size=label_dict_len, - input=feature_out, - label=target, - param_attr=ParameterAttribute( - name='crfw', initial_std=default_std, learning_rate=mix_hidden_lr)) - - crf_dec_l = crf_decoding_layer( - name='crf_dec_l', - size=label_dict_len, - input=feature_out, - label=target, - param_attr=ParameterAttribute(name='crfw')) - - eval = sum_evaluator(input=crf_dec_l) - - outputs(crf_l) - -else: - crf_dec_l = crf_decoding_layer( - name='crf_dec_l', - size=label_dict_len, - input=feature_out, - param_attr=ParameterAttribute(name='crfw')) - - outputs(crf_dec_l) diff --git a/demo/semantic_role_labeling/predict.py b/demo/semantic_role_labeling/predict.py deleted file mode 100644 index 372fd090b6e..00000000000 --- a/demo/semantic_role_labeling/predict.py +++ /dev/null @@ -1,193 +0,0 @@ -# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import os -import numpy as np -from optparse import OptionParser -from py_paddle import swig_paddle, DataProviderConverter -from paddle.trainer.PyDataProvider2 import integer_value_sequence -from paddle.trainer.config_parser import parse_config -""" -Usage: run following command to show help message. - python predict.py -h -""" -UNK_IDX = 0 - - -class Prediction(): - def __init__(self, train_conf, dict_file, model_dir, label_file, - predicate_dict_file): - """ - train_conf: trainer configure. - dict_file: word dictionary file name. - model_dir: directory of model. - """ - - self.dict = {} - self.labels = {} - self.predicate_dict = {} - self.labels_reverse = {} - self.load_dict_label(dict_file, label_file, predicate_dict_file) - - len_dict = len(self.dict) - len_label = len(self.labels) - len_pred = len(self.predicate_dict) - - conf = parse_config( - train_conf, 'dict_len=' + str(len_dict) + ',label_len=' + - str(len_label) + ',pred_len=' + str(len_pred) + ',is_predict=True') - self.network = swig_paddle.GradientMachine.createFromConfigProto( - conf.model_config) - self.network.loadParameters(model_dir) - - slots = [ - integer_value_sequence(len_dict), integer_value_sequence(len_dict), - integer_value_sequence(len_dict), integer_value_sequence(len_dict), - integer_value_sequence(len_dict), integer_value_sequence(len_dict), - integer_value_sequence(len_pred), integer_value_sequence(2) - ] - self.converter = DataProviderConverter(slots) - - def load_dict_label(self, dict_file, label_file, predicate_dict_file): - """ - Load dictionary from self.dict_file. - """ - for line_count, line in enumerate(open(dict_file, 'r')): - self.dict[line.strip()] = line_count - - for line_count, line in enumerate(open(label_file, 'r')): - self.labels[line.strip()] = line_count - self.labels_reverse[line_count] = line.strip() - - for line_count, line in enumerate(open(predicate_dict_file, 'r')): - self.predicate_dict[line.strip()] = line_count - - def get_data(self, data_file): - """ - Get input data of paddle format. - """ - with open(data_file, 'r') as fdata: - for line in fdata: - sentence, predicate, ctx_n2, ctx_n1, ctx_0, ctx_p1, ctx_p2, mark, label = line.strip( - ).split('\t') - words = sentence.split() - sen_len = len(words) - - word_slot = [self.dict.get(w, UNK_IDX) for w in words] - predicate_slot = [self.predicate_dict.get(predicate, UNK_IDX) - ] * sen_len - ctx_n2_slot = [self.dict.get(ctx_n2, UNK_IDX)] * sen_len - ctx_n1_slot = [self.dict.get(ctx_n1, UNK_IDX)] * sen_len - ctx_0_slot = [self.dict.get(ctx_0, UNK_IDX)] * sen_len - ctx_p1_slot = [self.dict.get(ctx_p1, UNK_IDX)] * sen_len - ctx_p2_slot = [self.dict.get(ctx_p2, UNK_IDX)] * sen_len - - marks = mark.split() - mark_slot = [int(w) for w in marks] - - yield word_slot, ctx_n2_slot, ctx_n1_slot, \ - ctx_0_slot, ctx_p1_slot, ctx_p2_slot, predicate_slot, mark_slot - - def predict(self, data_file, output_file): - """ - data_file: file name of input data. - """ - input = self.converter(self.get_data(data_file)) - output = self.network.forwardTest(input) - lab = output[0]["id"].tolist() - - with open(data_file, 'r') as fin, open(output_file, 'w') as fout: - index = 0 - for line in fin: - sen = line.split('\t')[0] - len_sen = len(sen.split()) - line_labels = lab[index:index + len_sen] - index += len_sen - fout.write(sen + '\t' + ' '.join( - [self.labels_reverse[i] for i in line_labels]) + '\n') - - -def option_parser(): - usage = ( - "python predict.py -c config -w model_dir " - "-d word dictionary -l label_file -i input_file -p pred_dict_file") - parser = OptionParser(usage="usage: %s [options]" % usage) - parser.add_option( - "-c", - "--tconf", - action="store", - dest="train_conf", - help="network config") - parser.add_option( - "-d", - "--dict", - action="store", - dest="dict_file", - help="dictionary file") - parser.add_option( - "-l", - "--label", - action="store", - dest="label_file", - default=None, - help="label file") - parser.add_option( - "-p", - "--predict_dict_file", - action="store", - dest="predict_dict_file", - default=None, - help="predict_dict_file") - parser.add_option( - "-i", - "--data", - action="store", - dest="data_file", - help="data file to predict") - parser.add_option( - "-w", - "--model", - action="store", - dest="model_path", - default=None, - help="model path") - - parser.add_option( - "-o", - "--output_file", - action="store", - dest="output_file", - default=None, - help="output file") - return parser.parse_args() - - -def main(): - options, args = option_parser() - train_conf = options.train_conf - data_file = options.data_file - dict_file = options.dict_file - model_path = options.model_path - label_file = options.label_file - predict_dict_file = options.predict_dict_file - output_file = options.output_file - - swig_paddle.initPaddle("--use_gpu=0") - predict = Prediction(train_conf, dict_file, model_path, label_file, - predict_dict_file) - predict.predict(data_file, output_file) - - -if __name__ == '__main__': - main() diff --git a/demo/semantic_role_labeling/predict.sh b/demo/semantic_role_labeling/predict.sh deleted file mode 100755 index 873aad670d1..00000000000 --- a/demo/semantic_role_labeling/predict.sh +++ /dev/null @@ -1,43 +0,0 @@ -#!/bin/bash - -# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -set -e - -function get_best_pass() { - cat $1 | grep -Pzo 'Test .*\n.*pass-.*' | \ - sed -r 'N;s/Test.* cost=([0-9]+\.[0-9]+).*\n.*pass-([0-9]+)/\1 \2/g' | \ - sort -n | head -n 1 -} - -log=train.log -LOG=`get_best_pass $log` -LOG=(${LOG}) -best_model_path="output/pass-${LOG[1]}" - -config_file=db_lstm.py -dict_file=./data/wordDict.txt -label_file=./data/targetDict.txt -predicate_dict_file=./data/verbDict.txt -input_file=./data/feature -output_file=predict.res - -python predict.py \ - -c $config_file \ - -w $best_model_path \ - -l $label_file \ - -p $predicate_dict_file \ - -d $dict_file \ - -i $input_file \ - -o $output_file diff --git a/demo/semantic_role_labeling/test.sh b/demo/semantic_role_labeling/test.sh deleted file mode 100755 index 095bbff2ea4..00000000000 --- a/demo/semantic_role_labeling/test.sh +++ /dev/null @@ -1,41 +0,0 @@ -#!/bin/bash - -# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -set -e - -function get_best_pass() { - cat $1 | grep -Pzo 'Test .*\n.*pass-.*' | \ - sed -r 'N;s/Test.* cost=([0-9]+\.[0-9]+).*\n.*pass-([0-9]+)/\1 \2/g' |\ - sort -n | head -n 1 -} - -log=train.log -LOG=`get_best_pass $log` -LOG=(${LOG}) -evaluate_pass="output/pass-${LOG[1]}" - -echo 'evaluating from pass '$evaluate_pass -model_list=./model.list -touch $model_list | echo $evaluate_pass > $model_list - -paddle train \ - --config=./db_lstm.py \ - --model_list=$model_list \ - --job=test \ - --use_gpu=false \ - --config_args=is_test=1 \ - --test_all_data_in_one_period=1 \ -2>&1 | tee 'test.log' -paddle usage -l test.log -e $? -n "semantic_role_labeling_test" >/dev/null 2>&1 diff --git a/demo/semantic_role_labeling/train.sh b/demo/semantic_role_labeling/train.sh deleted file mode 100755 index eee14010d7b..00000000000 --- a/demo/semantic_role_labeling/train.sh +++ /dev/null @@ -1,30 +0,0 @@ -#!/bin/bash - -# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -set -e -paddle train \ - --config=./db_lstm.py \ - --use_gpu=0 \ - --log_period=5000 \ - --trainer_count=1 \ - --show_parameter_stats_period=5000 \ - --save_dir=./output \ - --num_passes=10000 \ - --average_test_period=10000000 \ - --init_model_path=./data \ - --load_missing_parameter_strategy=rand \ - --test_all_data_in_one_period=1 \ - 2>&1 | tee 'train.log' -paddle usage -l train.log -e $? -n "semantic_role_labeling_train" >/dev/null 2>&1 diff --git a/demo/sentiment/.gitignore b/demo/sentiment/.gitignore deleted file mode 100644 index bf2a9ab1ce3..00000000000 --- a/demo/sentiment/.gitignore +++ /dev/null @@ -1,11 +0,0 @@ -data/aclImdb -data/imdb -data/pre-imdb -data/mosesdecoder-master -logs/ -model_output -dataprovider_copy_1.py -model.list -test.log -train.log -*.pyc diff --git a/demo/sentiment/data/get_imdb.sh b/demo/sentiment/data/get_imdb.sh deleted file mode 100755 index 7600af6fbb9..00000000000 --- a/demo/sentiment/data/get_imdb.sh +++ /dev/null @@ -1,51 +0,0 @@ -#!/bin/bash -# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -set -e -set -x - -DIR="$( cd "$(dirname "$0")" ; pwd -P )" -cd $DIR - -#download the dataset -echo "Downloading aclImdb..." -#http://ai.stanford.edu/%7Eamaas/data/sentiment/ -wget http://ai.stanford.edu/%7Eamaas/data/sentiment/aclImdb_v1.tar.gz - -echo "Downloading mosesdecoder..." -#https://github.com/moses-smt/mosesdecoder -wget https://github.com/moses-smt/mosesdecoder/archive/master.zip - -#extract package -echo "Unzipping..." -tar -zxvf aclImdb_v1.tar.gz -unzip master.zip - -#move train and test set to imdb_data directory -#in order to process when traing -mkdir -p imdb/train -mkdir -p imdb/test - -cp -r aclImdb/train/pos/ imdb/train/pos -cp -r aclImdb/train/neg/ imdb/train/neg - -cp -r aclImdb/test/pos/ imdb/test/pos -cp -r aclImdb/test/neg/ imdb/test/neg - -#remove compressed package -rm aclImdb_v1.tar.gz -rm master.zip - -echo "Done." diff --git a/demo/sentiment/dataprovider.py b/demo/sentiment/dataprovider.py deleted file mode 100755 index 4b7f5d0e504..00000000000 --- a/demo/sentiment/dataprovider.py +++ /dev/null @@ -1,37 +0,0 @@ -# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -from paddle.trainer.PyDataProvider2 import * - - -def hook(settings, dictionary, **kwargs): - settings.word_dict = dictionary - settings.input_types = [ - integer_value_sequence(len(settings.word_dict)), integer_value(2) - ] - settings.logger.info('dict len : %d' % (len(settings.word_dict))) - - -@provider(init_hook=hook) -def process(settings, file_name): - with open(file_name, 'r') as fdata: - for line_count, line in enumerate(fdata): - label, comment = line.strip().split('\t\t') - label = int(label) - words = comment.split() - word_slot = [ - settings.word_dict[w] for w in words if w in settings.word_dict - ] - if not word_slot: - continue - yield word_slot, label diff --git a/demo/sentiment/predict.py b/demo/sentiment/predict.py deleted file mode 100755 index 64c78e0d6b9..00000000000 --- a/demo/sentiment/predict.py +++ /dev/null @@ -1,154 +0,0 @@ -# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import os, sys -import numpy as np -from optparse import OptionParser -from py_paddle import swig_paddle, DataProviderConverter -from paddle.trainer.PyDataProvider2 import integer_value_sequence -from paddle.trainer.config_parser import parse_config -""" -Usage: run following command to show help message. - python predict.py -h -""" - - -class SentimentPrediction(): - def __init__(self, train_conf, dict_file, model_dir=None, label_file=None): - """ - train_conf: trainer configure. - dict_file: word dictionary file name. - model_dir: directory of model. - """ - self.train_conf = train_conf - self.dict_file = dict_file - self.word_dict = {} - self.dict_dim = self.load_dict() - self.model_dir = model_dir - if model_dir is None: - self.model_dir = os.path.dirname(train_conf) - - self.label = None - if label_file is not None: - self.load_label(label_file) - - conf = parse_config(train_conf, "is_predict=1") - self.network = swig_paddle.GradientMachine.createFromConfigProto( - conf.model_config) - self.network.loadParameters(self.model_dir) - input_types = [integer_value_sequence(self.dict_dim)] - self.converter = DataProviderConverter(input_types) - - def load_dict(self): - """ - Load dictionary from self.dict_file. - """ - for line_count, line in enumerate(open(self.dict_file, 'r')): - self.word_dict[line.strip().split('\t')[0]] = line_count - return len(self.word_dict) - - def load_label(self, label_file): - """ - Load label. - """ - self.label = {} - for v in open(label_file, 'r'): - self.label[int(v.split('\t')[1])] = v.split('\t')[0] - - def get_index(self, data): - """ - transform word into integer index according to the dictionary. - """ - words = data.strip().split() - word_slot = [self.word_dict[w] for w in words if w in self.word_dict] - return word_slot - - def batch_predict(self, data_batch): - input = self.converter(data_batch) - output = self.network.forwardTest(input) - prob = output[0]["value"] - labs = np.argsort(-prob) - for idx, lab in enumerate(labs): - if self.label is None: - print("predicting label is %d" % (lab[0])) - else: - print("predicting label is %s" % (self.label[lab[0]])) - - -def option_parser(): - usage = "python predict.py -n config -w model_dir -d dictionary -i input_file " - parser = OptionParser(usage="usage: %s [options]" % usage) - parser.add_option( - "-n", - "--tconf", - action="store", - dest="train_conf", - help="network config") - parser.add_option( - "-d", - "--dict", - action="store", - dest="dict_file", - help="dictionary file") - parser.add_option( - "-b", - "--label", - action="store", - dest="label", - default=None, - help="dictionary file") - parser.add_option( - "-c", - "--batch_size", - type="int", - action="store", - dest="batch_size", - default=1, - help="the batch size for prediction") - parser.add_option( - "-w", - "--model", - action="store", - dest="model_path", - default=None, - help="model path") - return parser.parse_args() - - -def main(): - options, args = option_parser() - train_conf = options.train_conf - batch_size = options.batch_size - dict_file = options.dict_file - model_path = options.model_path - label = options.label - swig_paddle.initPaddle("--use_gpu=0") - predict = SentimentPrediction(train_conf, dict_file, model_path, label) - - batch = [] - for line in sys.stdin: - words = predict.get_index(line) - if words: - batch.append([words]) - else: - print('All the words in [%s] are not in the dictionary.' % line) - if len(batch) == batch_size: - predict.batch_predict(batch) - batch = [] - if len(batch) > 0: - predict.batch_predict(batch) - - -if __name__ == '__main__': - main() diff --git a/demo/sentiment/predict.sh b/demo/sentiment/predict.sh deleted file mode 100755 index c72a8e86415..00000000000 --- a/demo/sentiment/predict.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/bin/bash -# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -set -e - -#Note the default model is pass-00002, you shold make sure the model path -#exists or change the mode path. -model=model_output/pass-00002/ -config=trainer_config.py -label=data/pre-imdb/labels.list -cat ./data/aclImdb/test/pos/10007_10.txt | python predict.py \ - --tconf=$config\ - --model=$model \ - --label=$label \ - --dict=./data/pre-imdb/dict.txt \ - --batch_size=1 diff --git a/demo/sentiment/preprocess.py b/demo/sentiment/preprocess.py deleted file mode 100755 index 29b3682b747..00000000000 --- a/demo/sentiment/preprocess.py +++ /dev/null @@ -1,359 +0,0 @@ -# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import os -import sys -import random -import operator -import numpy as np -from subprocess import Popen, PIPE -from os.path import join as join_path -from optparse import OptionParser - -from paddle.utils.preprocess_util import * -""" -Usage: run following command to show help message. - python preprocess.py -h -""" - - -def save_dict(dict, filename, is_reverse=True): - """ - Save dictionary into file. - dict: input dictionary. - filename: output file name, string. - is_reverse: True, descending order by value. - False, ascending order by value. - """ - f = open(filename, 'w') - for k, v in sorted(dict.items(), key=operator.itemgetter(1),\ - reverse=is_reverse): - f.write('%s\t%s\n' % (k, v)) - f.close() - - -def tokenize(sentences): - """ - Use tokenizer.perl to tokenize input sentences. - tokenizer.perl is tool of Moses. - sentences : a list of input sentences. - return: a list of processed text. - """ - dir = './data/mosesdecoder-master/scripts/tokenizer/tokenizer.perl' - tokenizer_cmd = [dir, '-l', 'en', '-q', '-'] - assert isinstance(sentences, list) - text = "\n".join(sentences) - tokenizer = Popen(tokenizer_cmd, stdin=PIPE, stdout=PIPE) - tok_text, _ = tokenizer.communicate(text) - toks = tok_text.split('\n')[:-1] - return toks - - -def read_lines(path): - """ - path: String, file path. - return a list of sequence. - """ - seqs = [] - with open(path, 'r') as f: - for line in f.readlines(): - line = line.strip() - if len(line): - seqs.append(line) - return seqs - - -class SentimentDataSetCreate(): - """ - A class to process data for sentiment analysis task. - """ - - def __init__(self, - data_path, - output_path, - use_okenizer=True, - multi_lines=False): - """ - data_path: string, traing and testing dataset path - output_path: string, output path, store processed dataset - multi_lines: whether a file has multi lines. - In order to shuffle fully, it needs to read all files into - memory, then shuffle them if one file has multi lines. - """ - self.output_path = output_path - self.data_path = data_path - - self.train_dir = 'train' - self.test_dir = 'test' - - self.train_list = "train.list" - self.test_list = "test.list" - - self.label_list = "labels.list" - self.classes_num = 0 - - self.batch_size = 50000 - self.batch_dir = 'batches' - - self.dict_file = "dict.txt" - self.dict_with_test = False - self.dict_size = 0 - self.word_count = {} - - self.tokenizer = use_okenizer - self.overwrite = False - - self.multi_lines = multi_lines - - self.train_dir = join_path(data_path, self.train_dir) - self.test_dir = join_path(data_path, self.test_dir) - self.train_list = join_path(output_path, self.train_list) - self.test_list = join_path(output_path, self.test_list) - self.label_list = join_path(output_path, self.label_list) - self.dict_file = join_path(output_path, self.dict_file) - - def data_list(self, path): - """ - create dataset from path - path: data path - return: data list - """ - label_set = get_label_set_from_dir(path) - data = [] - for lab_name in label_set.keys(): - file_paths = list_files(join_path(path, lab_name)) - for p in file_paths: - data.append({"label" : label_set[lab_name],\ - "seq_path": p}) - return data, label_set - - def create_dict(self, data): - """ - create dict for input data. - data: list, [sequence, sequnce, ...] - """ - for seq in data: - for w in seq.strip().lower().split(): - if w not in self.word_count: - self.word_count[w] = 1 - else: - self.word_count[w] += 1 - - def create_dataset(self): - """ - create file batches and dictionary of train data set. - If the self.overwrite is false and train.list already exists in - self.output_path, this function will not create and save file - batches from the data set path. - return: dictionary size, class number. - """ - out_path = self.output_path - if out_path and not os.path.exists(out_path): - os.makedirs(out_path) - - # If self.overwrite is false or self.train_list has existed, - # it will not process dataset. - if not (self.overwrite or not os.path.exists(self.train_list)): - print "%s already exists." % self.train_list - return - - # Preprocess train data. - train_data, train_lab_set = self.data_list(self.train_dir) - print "processing train set..." - file_lists = self.save_data(train_data, "train", self.batch_size, True, - True) - save_list(file_lists, self.train_list) - - # If have test data path, preprocess test data. - if os.path.exists(self.test_dir): - test_data, test_lab_set = self.data_list(self.test_dir) - assert (train_lab_set == test_lab_set) - print "processing test set..." - file_lists = self.save_data(test_data, "test", self.batch_size, - False, self.dict_with_test) - save_list(file_lists, self.test_list) - - # save labels set. - save_dict(train_lab_set, self.label_list, False) - self.classes_num = len(train_lab_set.keys()) - - # save dictionary. - save_dict(self.word_count, self.dict_file, True) - self.dict_size = len(self.word_count) - - def save_data(self, - data, - prefix="", - batch_size=50000, - is_shuffle=False, - build_dict=False): - """ - Create batches for a Dataset object. - data: the Dataset object to process. - prefix: the prefix of each batch. - batch_size: number of data in each batch. - build_dict: whether to build dictionary for data - - return: list of batch names - """ - if is_shuffle and self.multi_lines: - return self.save_data_multi_lines(data, prefix, batch_size, - build_dict) - - if is_shuffle: - random.shuffle(data) - num_batches = int(math.ceil(len(data) / float(batch_size))) - batch_names = [] - for i in range(num_batches): - batch_name = join_path(self.output_path, - "%s_part_%03d" % (prefix, i)) - begin = i * batch_size - end = min((i + 1) * batch_size, len(data)) - # read a batch of data - label_list, data_list = self.get_data_list(begin, end, data) - if build_dict: - self.create_dict(data_list) - self.save_file(label_list, data_list, batch_name) - batch_names.append(batch_name) - - return batch_names - - def get_data_list(self, begin, end, data): - """ - begin: int, begining index of data. - end: int, ending index of data. - data: a list of {"seq_path": seqquence path, "label": label index} - - return a list of label and a list of sequence. - """ - label_list = [] - data_list = [] - for j in range(begin, end): - seqs = read_lines(data[j]["seq_path"]) - lab = int(data[j]["label"]) - #File may have multiple lines. - for seq in seqs: - data_list.append(seq) - label_list.append(lab) - if self.tokenizer: - data_list = tokenize(data_list) - return label_list, data_list - - def save_data_multi_lines(self, - data, - prefix="", - batch_size=50000, - build_dict=False): - """ - In order to shuffle fully, there is no need to load all data if - each file only contains one sample, it only needs to shuffle list - of file name. But one file contains multi lines, each line is one - sample. It needs to read all data into memory to shuffle fully. - This interface is mainly for data containning multi lines in each - file, which consumes more memory if there is a great mount of data. - - data: the Dataset object to process. - prefix: the prefix of each batch. - batch_size: number of data in each batch. - build_dict: whether to build dictionary for data - - return: list of batch names - """ - assert self.multi_lines - label_list = [] - data_list = [] - - # read all data - label_list, data_list = self.get_data_list(0, len(data), data) - if build_dict: - self.create_dict(data_list) - - length = len(label_list) - perm_list = np.array([i for i in xrange(length)]) - random.shuffle(perm_list) - - num_batches = int(math.ceil(length / float(batch_size))) - batch_names = [] - for i in range(num_batches): - batch_name = join_path(self.output_path, - "%s_part_%03d" % (prefix, i)) - begin = i * batch_size - end = min((i + 1) * batch_size, length) - sub_label = [label_list[perm_list[i]] for i in range(begin, end)] - sub_data = [data_list[perm_list[i]] for i in range(begin, end)] - self.save_file(sub_label, sub_data, batch_name) - batch_names.append(batch_name) - - return batch_names - - def save_file(self, label_list, data_list, filename): - """ - Save data into file. - label_list: a list of int value. - data_list: a list of sequnece. - filename: output file name. - """ - f = open(filename, 'w') - print "saving file: %s" % filename - for lab, seq in zip(label_list, data_list): - f.write('%s\t\t%s\n' % (lab, seq)) - f.close() - - -def option_parser(): - parser = OptionParser(usage="usage: python preprcoess.py "\ - "-i data_dir [options]") - parser.add_option( - "-i", - "--data", - action="store", - dest="input", - help="Input data directory.") - parser.add_option( - "-o", - "--output", - action="store", - dest="output", - default=None, - help="Output directory.") - parser.add_option( - "-t", - "--tokenizer", - action="store", - dest="use_tokenizer", - default=True, - help="Whether to use tokenizer.") - parser.add_option("-m", "--multi_lines", action="store", - dest="multi_lines", default=False, - help="If input text files have multi lines and they "\ - "need to be shuffled, you should set -m True,") - return parser.parse_args() - - -def main(): - options, args = option_parser() - data_dir = options.input - output_dir = options.output - use_tokenizer = options.use_tokenizer - multi_lines = options.multi_lines - if output_dir is None: - outname = os.path.basename(options.input) - output_dir = join_path(os.path.dirname(data_dir), 'pre-' + outname) - data_creator = SentimentDataSetCreate(data_dir, output_dir, use_tokenizer, - multi_lines) - data_creator.create_dataset() - - -if __name__ == '__main__': - main() diff --git a/demo/sentiment/preprocess.sh b/demo/sentiment/preprocess.sh deleted file mode 100755 index 19ec34d4f01..00000000000 --- a/demo/sentiment/preprocess.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/bash -# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -set -e - -echo "Start to preprcess..." - -data_dir="./data/imdb" -python preprocess.py -i $data_dir - -echo "Done." diff --git a/demo/sentiment/sentiment_net.py b/demo/sentiment/sentiment_net.py deleted file mode 100644 index a01577ca5ae..00000000000 --- a/demo/sentiment/sentiment_net.py +++ /dev/null @@ -1,145 +0,0 @@ -# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from os.path import join as join_path - -from paddle.trainer_config_helpers import * - - -def sentiment_data(data_dir=None, - is_test=False, - is_predict=False, - train_list="train.list", - test_list="test.list", - dict_file="dict.txt"): - """ - Predefined data provider for sentiment analysis. - is_test: whether this config is used for test. - is_predict: whether this config is used for prediction. - train_list: text file name, containing a list of training set. - test_list: text file name, containing a list of testing set. - dict_file: text file name, containing dictionary. - """ - dict_dim = len(open(join_path(data_dir, "dict.txt")).readlines()) - class_dim = len(open(join_path(data_dir, 'labels.list')).readlines()) - if is_predict: - return dict_dim, class_dim - - if data_dir is not None: - train_list = join_path(data_dir, train_list) - test_list = join_path(data_dir, test_list) - dict_file = join_path(data_dir, dict_file) - - train_list = train_list if not is_test else None - word_dict = dict() - with open(dict_file, 'r') as f: - for i, line in enumerate(open(dict_file, 'r')): - word_dict[line.split('\t')[0]] = i - - define_py_data_sources2( - train_list, - test_list, - module="dataprovider", - obj="process", - args={'dictionary': word_dict}) - - return dict_dim, class_dim - - -def bidirectional_lstm_net(input_dim, - class_dim=2, - emb_dim=128, - lstm_dim=128, - is_predict=False): - data = data_layer("word", input_dim) - emb = embedding_layer(input=data, size=emb_dim) - bi_lstm = bidirectional_lstm(input=emb, size=lstm_dim) - dropout = dropout_layer(input=bi_lstm, dropout_rate=0.5) - output = fc_layer(input=dropout, size=class_dim, act=SoftmaxActivation()) - - if not is_predict: - lbl = data_layer("label", 1) - outputs(classification_cost(input=output, label=lbl)) - else: - outputs(output) - - -def stacked_lstm_net(input_dim, - class_dim=2, - emb_dim=128, - hid_dim=512, - stacked_num=3, - is_predict=False): - """ - A Wrapper for sentiment classification task. - This network uses bi-directional recurrent network, - consisting three LSTM layers. This configure is referred to - the paper as following url, but use fewer layrs. - http://www.aclweb.org/anthology/P15-1109 - - input_dim: here is word dictionary dimension. - class_dim: number of categories. - emb_dim: dimension of word embedding. - hid_dim: dimension of hidden layer. - stacked_num: number of stacked lstm-hidden layer. - is_predict: is predicting or not. - Some layers is not needed in network when predicting. - """ - hid_lr = 1e-3 - assert stacked_num % 2 == 1 - - layer_attr = ExtraLayerAttribute(drop_rate=0.5) - fc_para_attr = ParameterAttribute(learning_rate=hid_lr) - lstm_para_attr = ParameterAttribute(initial_std=0., learning_rate=1.) - para_attr = [fc_para_attr, lstm_para_attr] - bias_attr = ParameterAttribute(initial_std=0., l2_rate=0.) - relu = ReluActivation() - linear = LinearActivation() - - data = data_layer("word", input_dim) - emb = embedding_layer(input=data, size=emb_dim) - - fc1 = fc_layer(input=emb, size=hid_dim, act=linear, bias_attr=bias_attr) - lstm1 = lstmemory( - input=fc1, act=relu, bias_attr=bias_attr, layer_attr=layer_attr) - - inputs = [fc1, lstm1] - for i in range(2, stacked_num + 1): - fc = fc_layer( - input=inputs, - size=hid_dim, - act=linear, - param_attr=para_attr, - bias_attr=bias_attr) - lstm = lstmemory( - input=fc, - reverse=(i % 2) == 0, - act=relu, - bias_attr=bias_attr, - layer_attr=layer_attr) - inputs = [fc, lstm] - - fc_last = pooling_layer(input=inputs[0], pooling_type=MaxPooling()) - lstm_last = pooling_layer(input=inputs[1], pooling_type=MaxPooling()) - output = fc_layer( - input=[fc_last, lstm_last], - size=class_dim, - act=SoftmaxActivation(), - bias_attr=bias_attr, - param_attr=para_attr) - - if is_predict: - outputs(output) - else: - outputs(classification_cost(input=output, label=data_layer('label', 1))) diff --git a/demo/sentiment/test.sh b/demo/sentiment/test.sh deleted file mode 100755 index 85c4f3ccfc3..00000000000 --- a/demo/sentiment/test.sh +++ /dev/null @@ -1,40 +0,0 @@ -#!/bin/bash -# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -set -e - -function get_best_pass() { - cat $1 | grep -Pzo 'Test .*\n.*pass-.*' | \ - sed -r 'N;s/Test.* classification_error_evaluator=([0-9]+\.[0-9]+).*\n.*pass-([0-9]+)/\1 \2/g' |\ - sort -n | head -n 1 -} - -log=train.log -LOG=`get_best_pass $log` -LOG=(${LOG}) -evaluate_pass="model_output/pass-${LOG[1]}" - -echo 'evaluating from pass '$evaluate_pass - -model_list=./model.list -touch $model_list | echo $evaluate_pass > $model_list -net_conf=trainer_config.py -paddle train --config=$net_conf \ - --model_list=$model_list \ - --job=test \ - --use_gpu=false \ - --trainer_count=4 \ - --config_args=is_test=1 \ - 2>&1 | tee 'test.log' -paddle usage -l test.log -e $? -n "sentiment_test" >/dev/null 2>&1 diff --git a/demo/sentiment/train.sh b/demo/sentiment/train.sh deleted file mode 100755 index 14620f733bf..00000000000 --- a/demo/sentiment/train.sh +++ /dev/null @@ -1,30 +0,0 @@ -#!/bin/bash -# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -set -e - -config=trainer_config.py -output=./model_output -paddle train --config=$config \ - --save_dir=$output \ - --job=train \ - --use_gpu=false \ - --trainer_count=4 \ - --num_passes=10 \ - --log_period=10 \ - --dot_period=20 \ - --show_parameter_stats_period=100 \ - --test_all_data_in_one_period=1 \ - 2>&1 | tee 'train.log' -paddle usage -l train.log -e $? -n "sentiment_train" >/dev/null 2>&1 diff --git a/demo/sentiment/train_v2.py b/demo/sentiment/train_v2.py deleted file mode 100644 index 1c856556bd0..00000000000 --- a/demo/sentiment/train_v2.py +++ /dev/null @@ -1,159 +0,0 @@ -# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import sys -import paddle.v2 as paddle - - -def convolution_net(input_dim, class_dim=2, emb_dim=128, hid_dim=128): - data = paddle.layer.data("word", - paddle.data_type.integer_value_sequence(input_dim)) - emb = paddle.layer.embedding(input=data, size=emb_dim) - conv_3 = paddle.networks.sequence_conv_pool( - input=emb, context_len=3, hidden_size=hid_dim) - conv_4 = paddle.networks.sequence_conv_pool( - input=emb, context_len=4, hidden_size=hid_dim) - output = paddle.layer.fc(input=[conv_3, conv_4], - size=class_dim, - act=paddle.activation.Softmax()) - lbl = paddle.layer.data("label", paddle.data_type.integer_value(2)) - cost = paddle.layer.classification_cost(input=output, label=lbl) - return cost - - -def stacked_lstm_net(input_dim, - class_dim=2, - emb_dim=128, - hid_dim=512, - stacked_num=3): - """ - A Wrapper for sentiment classification task. - This network uses bi-directional recurrent network, - consisting three LSTM layers. This configure is referred to - the paper as following url, but use fewer layrs. - http://www.aclweb.org/anthology/P15-1109 - - input_dim: here is word dictionary dimension. - class_dim: number of categories. - emb_dim: dimension of word embedding. - hid_dim: dimension of hidden layer. - stacked_num: number of stacked lstm-hidden layer. - """ - assert stacked_num % 2 == 1 - - layer_attr = paddle.attr.Extra(drop_rate=0.5) - fc_para_attr = paddle.attr.Param(learning_rate=1e-3) - lstm_para_attr = paddle.attr.Param(initial_std=0., learning_rate=1.) - para_attr = [fc_para_attr, lstm_para_attr] - bias_attr = paddle.attr.Param(initial_std=0., l2_rate=0.) - relu = paddle.activation.Relu() - linear = paddle.activation.Linear() - - data = paddle.layer.data("word", - paddle.data_type.integer_value_sequence(input_dim)) - emb = paddle.layer.embedding(input=data, size=emb_dim) - - fc1 = paddle.layer.fc(input=emb, - size=hid_dim, - act=linear, - bias_attr=bias_attr) - lstm1 = paddle.layer.lstmemory( - input=fc1, act=relu, bias_attr=bias_attr, layer_attr=layer_attr) - - inputs = [fc1, lstm1] - for i in range(2, stacked_num + 1): - fc = paddle.layer.fc(input=inputs, - size=hid_dim, - act=linear, - param_attr=para_attr, - bias_attr=bias_attr) - lstm = paddle.layer.lstmemory( - input=fc, - reverse=(i % 2) == 0, - act=relu, - bias_attr=bias_attr, - layer_attr=layer_attr) - inputs = [fc, lstm] - - fc_last = paddle.layer.pooling( - input=inputs[0], pooling_type=paddle.pooling.Max()) - lstm_last = paddle.layer.pooling( - input=inputs[1], pooling_type=paddle.pooling.Max()) - output = paddle.layer.fc(input=[fc_last, lstm_last], - size=class_dim, - act=paddle.activation.Softmax(), - bias_attr=bias_attr, - param_attr=para_attr) - - lbl = paddle.layer.data("label", paddle.data_type.integer_value(2)) - cost = paddle.layer.classification_cost(input=output, label=lbl) - return cost - - -if __name__ == '__main__': - # init - paddle.init(use_gpu=False) - - #data - print 'load dictionary...' - word_dict = paddle.dataset.imdb.word_dict() - dict_dim = len(word_dict) - class_dim = 2 - train_reader = paddle.batch( - paddle.reader.shuffle( - lambda: paddle.dataset.imdb.train(word_dict), buf_size=1000), - batch_size=100) - test_reader = paddle.batch( - lambda: paddle.dataset.imdb.test(word_dict), batch_size=100) - - feeding = {'word': 0, 'label': 1} - - # network config - # Please choose the way to build the network - # by uncommenting the corresponding line. - cost = convolution_net(dict_dim, class_dim=class_dim) - # cost = stacked_lstm_net(dict_dim, class_dim=class_dim, stacked_num=3) - - # create parameters - parameters = paddle.parameters.create(cost) - - # create optimizer - adam_optimizer = paddle.optimizer.Adam( - learning_rate=2e-3, - regularization=paddle.optimizer.L2Regularization(rate=8e-4), - model_average=paddle.optimizer.ModelAverage(average_window=0.5)) - - # End batch and end pass event handler - def event_handler(event): - if isinstance(event, paddle.event.EndIteration): - if event.batch_id % 100 == 0: - print "\nPass %d, Batch %d, Cost %f, %s" % ( - event.pass_id, event.batch_id, event.cost, event.metrics) - else: - sys.stdout.write('.') - sys.stdout.flush() - if isinstance(event, paddle.event.EndPass): - result = trainer.test(reader=test_reader, feeding=feeding) - print "\nTest with Pass %d, %s" % (event.pass_id, result.metrics) - - # create trainer - trainer = paddle.trainer.SGD(cost=cost, - parameters=parameters, - update_equation=adam_optimizer) - - trainer.train( - reader=train_reader, - event_handler=event_handler, - feeding=feeding, - num_passes=2) diff --git a/demo/sentiment/trainer_config.py b/demo/sentiment/trainer_config.py deleted file mode 100644 index f1cadaa728a..00000000000 --- a/demo/sentiment/trainer_config.py +++ /dev/null @@ -1,39 +0,0 @@ -# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from sentiment_net import * -from paddle.trainer_config_helpers import * - -# whether this config is used for test -is_test = get_config_arg('is_test', bool, False) -# whether this config is used for prediction -is_predict = get_config_arg('is_predict', bool, False) - -data_dir = "./data/pre-imdb" -dict_dim, class_dim = sentiment_data(data_dir, is_test, is_predict) - -################## Algorithm Config ##################### - -settings( - batch_size=128, - learning_rate=2e-3, - learning_method=AdamOptimizer(), - model_average=ModelAverage(0.5), - regularization=L2Regularization(8e-4), - gradient_clipping_threshold=25) - -#################### Network Config ###################### -stacked_lstm_net( - dict_dim, class_dim=class_dim, stacked_num=3, is_predict=is_predict) -# bidirectional_lstm_net(dict_dim, class_dim=class_dim, is_predict=is_predict) diff --git a/demo/seqToseq/.gitignore b/demo/seqToseq/.gitignore deleted file mode 100644 index 21cec2c2c1f..00000000000 --- a/demo/seqToseq/.gitignore +++ /dev/null @@ -1,17 +0,0 @@ -data/wmt14 -data/pre-wmt14 -data/wmt14_model -data/paraphrase -data/pre-paraphrase -data/paraphrase_model -translation/gen.log -translation/gen_result -translation/train.log -paraphrase/train.log -dataprovider_copy_1.py -translation/thirdparty.tgz -translation/thirdparty/train.conf -translation/thirdparty/dataprovider.py -translation/thirdparty/seqToseq_net.py -translation/thirdparty/*.dict -*.pyc diff --git a/demo/seqToseq/api_train_v2.py b/demo/seqToseq/api_train_v2.py deleted file mode 100644 index bb535f09260..00000000000 --- a/demo/seqToseq/api_train_v2.py +++ /dev/null @@ -1,236 +0,0 @@ -import sys - -import paddle.v2 as paddle - - -def seqToseq_net(source_dict_dim, target_dict_dim, is_generating=False): - ### Network Architecture - word_vector_dim = 512 # dimension of word vector - decoder_size = 512 # dimension of hidden unit in GRU Decoder network - encoder_size = 512 # dimension of hidden unit in GRU Encoder network - - beam_size = 3 - max_length = 250 - - #### Encoder - src_word_id = paddle.layer.data( - name='source_language_word', - type=paddle.data_type.integer_value_sequence(source_dict_dim)) - src_embedding = paddle.layer.embedding( - input=src_word_id, - size=word_vector_dim, - param_attr=paddle.attr.ParamAttr(name='_source_language_embedding')) - src_forward = paddle.networks.simple_gru( - name='src_forward_gru', input=src_embedding, size=encoder_size) - src_backward = paddle.networks.simple_gru( - name='src_backward_gru', - input=src_embedding, - size=encoder_size, - reverse=True) - encoded_vector = paddle.layer.concat(input=[src_forward, src_backward]) - - #### Decoder - with paddle.layer.mixed(size=decoder_size) as encoded_proj: - encoded_proj += paddle.layer.full_matrix_projection( - input=encoded_vector) - - backward_first = paddle.layer.first_seq(input=src_backward) - - with paddle.layer.mixed( - name="decoder_boot_mixed", - size=decoder_size, - act=paddle.activation.Tanh()) as decoder_boot: - decoder_boot += paddle.layer.full_matrix_projection( - input=backward_first) - - def gru_decoder_with_attention(enc_vec, enc_proj, current_word): - - decoder_mem = paddle.layer.memory( - name='gru_decoder', size=decoder_size, boot_layer=decoder_boot) - - context = paddle.networks.simple_attention( - name="simple_attention", - encoded_sequence=enc_vec, - encoded_proj=enc_proj, - decoder_state=decoder_mem) - - with paddle.layer.mixed( - name="input_recurrent", - size=decoder_size * 3, - # enable error clipping - layer_attr=paddle.attr.ExtraAttr( - error_clipping_threshold=100.0)) as decoder_inputs: - decoder_inputs += paddle.layer.full_matrix_projection(input=context) - decoder_inputs += paddle.layer.full_matrix_projection( - input=current_word) - - gru_step = paddle.layer.gru_step( - name='gru_decoder', - input=decoder_inputs, - output_mem=decoder_mem, - # uncomment to enable local threshold for gradient clipping - # param_attr=paddle.attr.ParamAttr(gradient_clipping_threshold=9.9), - size=decoder_size) - - with paddle.layer.mixed( - name="gru_step_output", - size=target_dict_dim, - bias_attr=True, - act=paddle.activation.Softmax()) as out: - out += paddle.layer.full_matrix_projection(input=gru_step) - return out - - decoder_group_name = "decoder_group" - group_input1 = paddle.layer.StaticInputV2(input=encoded_vector, is_seq=True) - group_input2 = paddle.layer.StaticInputV2(input=encoded_proj, is_seq=True) - group_inputs = [group_input1, group_input2] - - if not is_generating: - trg_embedding = paddle.layer.embedding( - input=paddle.layer.data( - name='target_language_word', - type=paddle.data_type.integer_value_sequence(target_dict_dim)), - size=word_vector_dim, - param_attr=paddle.attr.ParamAttr(name='_target_language_embedding')) - group_inputs.append(trg_embedding) - - # For decoder equipped with attention mechanism, in training, - # target embeding (the groudtruth) is the data input, - # while encoded source sequence is accessed to as an unbounded memory. - # Here, the StaticInput defines a read-only memory - # for the recurrent_group. - decoder = paddle.layer.recurrent_group( - name=decoder_group_name, - step=gru_decoder_with_attention, - input=group_inputs) - - lbl = paddle.layer.data( - name='target_language_next_word', - type=paddle.data_type.integer_value_sequence(target_dict_dim)) - cost = paddle.layer.classification_cost(input=decoder, label=lbl) - - return cost - else: - # In generation, the decoder predicts a next target word based on - # the encoded source sequence and the last generated target word. - - # The encoded source sequence (encoder's output) must be specified by - # StaticInput, which is a read-only memory. - # Embedding of the last generated word is automatically gotten by - # GeneratedInputs, which is initialized by a start mark, such as , - # and must be included in generation. - - trg_embedding = paddle.layer.GeneratedInputV2( - size=target_dict_dim, - embedding_name='_target_language_embedding', - embedding_size=word_vector_dim) - group_inputs.append(trg_embedding) - - beam_gen = paddle.layer.beam_search( - name=decoder_group_name, - step=gru_decoder_with_attention, - input=group_inputs, - bos_id=0, - eos_id=1, - beam_size=beam_size, - max_length=max_length) - - return beam_gen - - -def main(): - paddle.init( - use_gpu=False, - trainer_count=1, - # log gradient clipping info - log_clipping=True, - # log error clipping info - log_error_clipping=True) - is_generating = False - - # source and target dict dim. - dict_size = 30000 - source_dict_dim = target_dict_dim = dict_size - - # train the network - if not is_generating: - cost = seqToseq_net(source_dict_dim, target_dict_dim) - parameters = paddle.parameters.create(cost) - - # define optimize method and trainer - optimizer = paddle.optimizer.Adam( - learning_rate=5e-5, - # uncomment to enable global threshold for gradient clipping - # gradient_clipping_threshold=10.0, - regularization=paddle.optimizer.L2Regularization(rate=8e-4)) - trainer = paddle.trainer.SGD(cost=cost, - parameters=parameters, - update_equation=optimizer) - # define data reader - wmt14_reader = paddle.batch( - paddle.reader.shuffle( - paddle.dataset.wmt14.train(dict_size), buf_size=8192), - batch_size=5) - - # define event_handler callback - def event_handler(event): - if isinstance(event, paddle.event.EndIteration): - if event.batch_id % 10 == 0: - print "\nPass %d, Batch %d, Cost %f, %s" % ( - event.pass_id, event.batch_id, event.cost, - event.metrics) - else: - sys.stdout.write('.') - sys.stdout.flush() - - # start to train - trainer.train( - reader=wmt14_reader, event_handler=event_handler, num_passes=2) - - # generate a english sequence to french - else: - # use the first 3 samples for generation - gen_creator = paddle.dataset.wmt14.gen(dict_size) - gen_data = [] - gen_num = 3 - for item in gen_creator(): - gen_data.append((item[0], )) - if len(gen_data) == gen_num: - break - - beam_gen = seqToseq_net(source_dict_dim, target_dict_dim, is_generating) - # get the pretrained model, whose bleu = 26.92 - parameters = paddle.dataset.wmt14.model() - # prob is the prediction probabilities, and id is the prediction word. - beam_result = paddle.infer( - output_layer=beam_gen, - parameters=parameters, - input=gen_data, - field=['prob', 'id']) - - # get the dictionary - src_dict, trg_dict = paddle.dataset.wmt14.get_dict(dict_size) - - # the delimited element of generated sequences is -1, - # the first element of each generated sequence is the sequence length - seq_list = [] - seq = [] - for w in beam_result[1]: - if w != -1: - seq.append(w) - else: - seq_list.append(' '.join([trg_dict.get(w) for w in seq[1:]])) - seq = [] - - prob = beam_result[0] - beam_size = 3 - for i in xrange(gen_num): - print "\n*******************************************************\n" - print "src:", ' '.join( - [src_dict.get(w) for w in gen_data[i][0]]), "\n" - for j in xrange(beam_size): - print "prob = %f:" % (prob[i][j]), seq_list[i * beam_size + j] - - -if __name__ == '__main__': - main() diff --git a/demo/seqToseq/data/paraphrase_data.sh b/demo/seqToseq/data/paraphrase_data.sh deleted file mode 100755 index e6497c91286..00000000000 --- a/demo/seqToseq/data/paraphrase_data.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/bash -# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -set -e -set -x - -# download the in-house paraphrase dataset -wget http://paddlepaddle.bj.bcebos.com/model_zoo/embedding/paraphrase.tar.gz - -# untar the dataset -tar -zxvf paraphrase.tar.gz -rm paraphrase.tar.gz diff --git a/demo/seqToseq/data/paraphrase_model.sh b/demo/seqToseq/data/paraphrase_model.sh deleted file mode 100755 index d0e7f214a38..00000000000 --- a/demo/seqToseq/data/paraphrase_model.sh +++ /dev/null @@ -1,37 +0,0 @@ -#!/bin/bash -# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -set -e -set -x - -dim=32 -pretrained_dir='../../model_zoo/embedding/' -preModel=$pretrained_dir'model_'$dim'.emb' -preDict=$pretrained_dir'baidu.dict' - -usrDict_dir='pre-paraphrase/' -srcDict=$usrDict_dir'src.dict' -trgDict=$usrDict_dir'trg.dict' - -usrModel_dir='paraphrase_model/' -mkdir $usrModel_dir -srcModel=$usrModel_dir'_source_language_embedding' -trgModel=$usrModel_dir'_target_language_embedding' - -echo 'extract desired parameters based on user dictionary' -script=$pretrained_dir'extract_para.py' -python $script --preModel $preModel --preDict $preDict \ - --usrModel $srcModel --usrDict $srcDict -d $dim -python $script --preModel $preModel --preDict $preDict \ - --usrModel $trgModel --usrDict $trgDict -d $dim diff --git a/demo/seqToseq/data/wmt14_data.sh b/demo/seqToseq/data/wmt14_data.sh deleted file mode 100755 index 43f67168d2a..00000000000 --- a/demo/seqToseq/data/wmt14_data.sh +++ /dev/null @@ -1,53 +0,0 @@ -#!/bin/bash -# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -set -e -set -x -mkdir wmt14 -cd wmt14 - -# download the dataset -wget http://www-lium.univ-lemans.fr/~schwenk/cslm_joint_paper/data/bitexts.tgz -wget http://www-lium.univ-lemans.fr/~schwenk/cslm_joint_paper/data/dev+test.tgz - -# untar the dataset -tar -zxvf bitexts.tgz -tar -zxvf dev+test.tgz -gunzip bitexts.selected/* -mv bitexts.selected train -rm bitexts.tgz -rm dev+test.tgz - -# separate the dev and test dataset -mkdir test gen -mv dev/ntst1213.* test -mv dev/ntst14.* gen -rm -rf dev - -set +x -# rename the suffix, .fr->.src, .en->.trg -for dir in train test gen -do - filelist=`ls $dir` - cd $dir - for file in $filelist - do - if [ ${file##*.} = "fr" ]; then - mv $file ${file/%fr/src} - elif [ ${file##*.} = 'en' ]; then - mv $file ${file/%en/trg} - fi - done - cd .. -done diff --git a/demo/seqToseq/data/wmt14_model.sh b/demo/seqToseq/data/wmt14_model.sh deleted file mode 100755 index c4b55b90a3e..00000000000 --- a/demo/seqToseq/data/wmt14_model.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/bash -# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -set -e -set -x - -# download the pretrained model -wget http://paddlepaddle.bj.bcebos.com/model_zoo/wmt14_model.tar.gz - -# untar the model -tar -zxvf wmt14_model.tar.gz -rm wmt14_model.tar.gz diff --git a/demo/seqToseq/dataprovider.py b/demo/seqToseq/dataprovider.py deleted file mode 100755 index c2b49804be5..00000000000 --- a/demo/seqToseq/dataprovider.py +++ /dev/null @@ -1,94 +0,0 @@ -# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from paddle.trainer.PyDataProvider2 import * - -UNK_IDX = 2 -START = "" -END = "" - - -def hook(settings, src_dict_path, trg_dict_path, is_generating, file_list, - **kwargs): - # job_mode = 1: training mode - # job_mode = 0: generating mode - settings.job_mode = not is_generating - - def fun(dict_path): - out_dict = dict() - with open(dict_path, "r") as fin: - out_dict = { - line.strip(): line_count - for line_count, line in enumerate(fin) - } - return out_dict - - settings.src_dict = fun(src_dict_path) - settings.trg_dict = fun(trg_dict_path) - - settings.logger.info("src dict len : %d" % (len(settings.src_dict))) - - if settings.job_mode: - settings.slots = { - 'source_language_word': - integer_value_sequence(len(settings.src_dict)), - 'target_language_word': - integer_value_sequence(len(settings.trg_dict)), - 'target_language_next_word': - integer_value_sequence(len(settings.trg_dict)) - } - settings.logger.info("trg dict len : %d" % (len(settings.trg_dict))) - else: - settings.slots = { - 'source_language_word': - integer_value_sequence(len(settings.src_dict)), - 'sent_id': - integer_value_sequence(len(open(file_list[0], "r").readlines())) - } - - -def _get_ids(s, dictionary): - words = s.strip().split() - return [dictionary[START]] + \ - [dictionary.get(w, UNK_IDX) for w in words] + \ - [dictionary[END]] - - -@provider(init_hook=hook, pool_size=50000) -def process(settings, file_name): - with open(file_name, 'r') as f: - for line_count, line in enumerate(f): - line_split = line.strip().split('\t') - if settings.job_mode and len(line_split) != 2: - continue - src_seq = line_split[0] # one source sequence - src_ids = _get_ids(src_seq, settings.src_dict) - - if settings.job_mode: - trg_seq = line_split[1] # one target sequence - trg_words = trg_seq.split() - trg_ids = [settings.trg_dict.get(w, UNK_IDX) for w in trg_words] - - # remove sequence whose length > 80 in training mode - if len(src_ids) > 80 or len(trg_ids) > 80: - continue - trg_ids_next = trg_ids + [settings.trg_dict[END]] - trg_ids = [settings.trg_dict[START]] + trg_ids - yield { - 'source_language_word': src_ids, - 'target_language_word': trg_ids, - 'target_language_next_word': trg_ids_next - } - else: - yield {'source_language_word': src_ids, 'sent_id': [line_count]} diff --git a/demo/seqToseq/paraphrase/train.conf b/demo/seqToseq/paraphrase/train.conf deleted file mode 100644 index be79c5e771c..00000000000 --- a/demo/seqToseq/paraphrase/train.conf +++ /dev/null @@ -1,33 +0,0 @@ -#edit-mode: -*- python -*- -# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import sys -sys.path.append("..") - -from seqToseq_net import * - -is_generating = False -### Data Definiation -train_conf = seq_to_seq_data(data_dir = "./data/pre-paraphrase", - is_generating = is_generating) - -### Algorithm Configuration -settings( - learning_method = AdamOptimizer(), - batch_size = 50, - learning_rate = 5e-4) - -### Network Architecture -gru_encoder_decoder(train_conf, is_generating, word_vector_dim = 32) diff --git a/demo/seqToseq/paraphrase/train.sh b/demo/seqToseq/paraphrase/train.sh deleted file mode 100755 index 9bb6dbdb1d4..00000000000 --- a/demo/seqToseq/paraphrase/train.sh +++ /dev/null @@ -1,30 +0,0 @@ -#!/bin/bash -# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -set -e -cd .. - -paddle train \ - --config='paraphrase/train.conf' \ - --save_dir='paraphrase/model' \ - --init_model_path='data/paraphrase_model' \ - --load_missing_parameter_strategy=rand \ - --use_gpu=false \ - --num_passes=16 \ - --show_parameter_stats_period=100 \ - --trainer_count=4 \ - --log_period=10 \ - --dot_period=5 \ - 2>&1 | tee 'paraphrase/train.log' -paddle usage -l 'paraphrase/train.log' -e $? -n "seqToseq_paraphrase_train" >/dev/null 2>&1 diff --git a/demo/seqToseq/preprocess.py b/demo/seqToseq/preprocess.py deleted file mode 100755 index 03f371331a0..00000000000 --- a/demo/seqToseq/preprocess.py +++ /dev/null @@ -1,219 +0,0 @@ -#!/bin/env python -# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -""" -Example: - python preprocess.py -i INPUT [-d DICTSIZE] [-m] - -Options: - -h, --help show this help message and exit - -i INPUT input original dataset path - -d DICTSIZE specified word count of dictionary - -m --mergeDict merge source and target dictionary -""" -import os -import sys - -import string -from optparse import OptionParser -from paddle.utils.preprocess_util import save_list, DatasetCreater - - -class SeqToSeqDatasetCreater(DatasetCreater): - """ - A class to process data for sequence to sequence application. - """ - - def __init__(self, data_path, output_path): - """ - data_path: the path to store the train data, test data and gen data - output_path: the path to store the processed dataset - """ - DatasetCreater.__init__(self, data_path) - self.gen_dir_name = 'gen' - self.gen_list_name = 'gen.list' - self.output_path = output_path - - def concat_file(self, file_path, file1, file2, output_path, output): - """ - Concat file1 and file2 to be one output file - The i-th line of output = i-th line of file1 + '\t' + i-th line of file2 - file_path: the path to store file1 and file2 - output_path: the path to store output file - """ - file1 = os.path.join(file_path, file1) - file2 = os.path.join(file_path, file2) - output = os.path.join(output_path, output) - if not os.path.exists(output): - os.system('paste ' + file1 + ' ' + file2 + ' > ' + output) - - def cat_file(self, dir_path, suffix, output_path, output): - """ - Cat all the files in dir_path with suffix to be one output file - dir_path: the base directory to store input file - suffix: suffix of file name - output_path: the path to store output file - """ - cmd = 'cat ' - file_list = os.listdir(dir_path) - file_list.sort() - for file in file_list: - if file.endswith(suffix): - cmd += os.path.join(dir_path, file) + ' ' - output = os.path.join(output_path, output) - if not os.path.exists(output): - os.system(cmd + '> ' + output) - - def build_dict(self, file_path, dict_path, dict_size=-1): - """ - Create the dictionary for the file, Note that - 1. Valid characters include all printable characters - 2. There is distinction between uppercase and lowercase letters - 3. There is 3 special token: - : the start of a sequence - : the end of a sequence - : a word not included in dictionary - file_path: the path to store file - dict_path: the path to store dictionary - dict_size: word count of dictionary - if is -1, dictionary will contains all the words in file - """ - if not os.path.exists(dict_path): - dictory = dict() - with open(file_path, "r") as fdata: - for line in fdata: - line = line.split('\t') - for line_split in line: - words = line_split.strip().split() - for word in words: - if word not in dictory: - dictory[word] = 1 - else: - dictory[word] += 1 - output = open(dict_path, "w+") - output.write('\n\n\n') - count = 3 - for key, value in sorted( - dictory.items(), key=lambda d: d[1], reverse=True): - output.write(key + "\n") - count += 1 - if count == dict_size: - break - self.dict_size = count - - def create_dataset(self, - dict_size=-1, - mergeDict=False, - suffixes=['.src', '.trg']): - """ - Create seqToseq dataset - """ - # dataset_list and dir_list has one-to-one relationship - train_dataset = os.path.join(self.data_path, self.train_dir_name) - test_dataset = os.path.join(self.data_path, self.test_dir_name) - gen_dataset = os.path.join(self.data_path, self.gen_dir_name) - dataset_list = [train_dataset, test_dataset, gen_dataset] - - train_dir = os.path.join(self.output_path, self.train_dir_name) - test_dir = os.path.join(self.output_path, self.test_dir_name) - gen_dir = os.path.join(self.output_path, self.gen_dir_name) - dir_list = [train_dir, test_dir, gen_dir] - - # create directory - for dir in dir_list: - if not os.path.exists(dir): - os.mkdir(dir) - - # checkout dataset should be parallel corpora - suffix_len = len(suffixes[0]) - for dataset in dataset_list: - file_list = os.listdir(dataset) - if len(file_list) % 2 == 1: - raise RuntimeError("dataset should be parallel corpora") - file_list.sort() - for i in range(0, len(file_list), 2): - if file_list[i][:-suffix_len] != file_list[i + 1][:-suffix_len]: - raise RuntimeError( - "source and target file name should be equal") - - # cat all the files with the same suffix in dataset - for suffix in suffixes: - for dataset in dataset_list: - outname = os.path.basename(dataset) + suffix - self.cat_file(dataset, suffix, dataset, outname) - - # concat parallel corpora and create file.list - print 'concat parallel corpora for dataset' - id = 0 - list = ['train.list', 'test.list', 'gen.list'] - for dataset in dataset_list: - outname = os.path.basename(dataset) - self.concat_file(dataset, outname + suffixes[0], - outname + suffixes[1], dir_list[id], outname) - save_list([os.path.join(dir_list[id], outname)], - os.path.join(self.output_path, list[id])) - id += 1 - - # build dictionary for train data - dict = ['src.dict', 'trg.dict'] - dict_path = [ - os.path.join(self.output_path, dict[0]), - os.path.join(self.output_path, dict[1]) - ] - if mergeDict: - outname = os.path.join(train_dir, train_dataset.split('/')[-1]) - print 'build src dictionary for train data' - self.build_dict(outname, dict_path[0], dict_size) - print 'build trg dictionary for train data' - os.system('cp ' + dict_path[0] + ' ' + dict_path[1]) - else: - outname = os.path.join(train_dataset, self.train_dir_name) - for id in range(0, 2): - suffix = suffixes[id] - print 'build ' + suffix[1:] + ' dictionary for train data' - self.build_dict(outname + suffix, dict_path[id], dict_size) - print 'dictionary size is', self.dict_size - - -def main(): - usage = "usage: \n" \ - "python %prog -i INPUT [-d DICTSIZE] [-m]" - parser = OptionParser(usage) - parser.add_option( - "-i", action="store", dest="input", help="input original dataset path") - parser.add_option( - "-d", - action="store", - dest="dictsize", - help="specified word count of dictionary") - parser.add_option( - "-m", - "--mergeDict", - action="store_true", - dest="mergeDict", - help="merge source and target dictionary") - (options, args) = parser.parse_args() - if options.input[-1] == os.path.sep: - options.input = options.input[:-1] - outname = os.path.basename(options.input) - output_path = os.path.join(os.path.dirname(options.input), 'pre-' + outname) - dictsize = int(options.dictsize) if options.dictsize else -1 - if not os.path.exists(output_path): - os.mkdir(output_path) - data_creator = SeqToSeqDatasetCreater(options.input, output_path) - data_creator.create_dataset(dictsize, options.mergeDict) - - -if __name__ == "__main__": - main() diff --git a/demo/seqToseq/seqToseq_net.py b/demo/seqToseq/seqToseq_net.py deleted file mode 100644 index 3d1f86ec3b7..00000000000 --- a/demo/seqToseq/seqToseq_net.py +++ /dev/null @@ -1,204 +0,0 @@ -# edit-mode: -*- python -*- - -# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import sys -import os -from paddle.trainer_config_helpers import * - - -def seq_to_seq_data(data_dir, - is_generating, - dict_size=30000, - train_list='train.list', - test_list='test.list', - gen_list='gen.list', - gen_result='gen_result'): - """ - Predefined seqToseq train data provider for application - is_generating: whether this config is used for generating - dict_size: word count of dictionary - train_list: a text file containing a list of training data - test_list: a text file containing a list of testing data - gen_list: a text file containing a list of generating data - gen_result: a text file containing generating result - """ - src_lang_dict = os.path.join(data_dir, 'src.dict') - trg_lang_dict = os.path.join(data_dir, 'trg.dict') - - if is_generating: - train_list = None - test_list = os.path.join(data_dir, gen_list) - else: - train_list = os.path.join(data_dir, train_list) - test_list = os.path.join(data_dir, test_list) - - define_py_data_sources2( - train_list, - test_list, - module="dataprovider", - obj="process", - args={ - "src_dict_path": src_lang_dict, - "trg_dict_path": trg_lang_dict, - "is_generating": is_generating - }) - - return { - "src_dict_path": src_lang_dict, - "trg_dict_path": trg_lang_dict, - "gen_result": gen_result - } - - -def gru_encoder_decoder(data_conf, - is_generating, - word_vector_dim=512, - encoder_size=512, - decoder_size=512, - beam_size=3, - max_length=250, - error_clipping=50): - """ - A wrapper for an attention version of GRU Encoder-Decoder network - is_generating: whether this config is used for generating - encoder_size: dimension of hidden unit in GRU Encoder network - decoder_size: dimension of hidden unit in GRU Decoder network - word_vector_dim: dimension of word vector - beam_size: expand width in beam search - max_length: a stop condition of sequence generation - """ - for k, v in data_conf.iteritems(): - globals()[k] = v - source_dict_dim = len(open(src_dict_path, "r").readlines()) - target_dict_dim = len(open(trg_dict_path, "r").readlines()) - gen_trans_file = gen_result - - src_word_id = data_layer(name='source_language_word', size=source_dict_dim) - src_embedding = embedding_layer( - input=src_word_id, - size=word_vector_dim, - param_attr=ParamAttr(name='_source_language_embedding')) - src_forward = simple_gru( - input=src_embedding, - size=encoder_size, - naive=True, - gru_layer_attr=ExtraLayerAttribute( - error_clipping_threshold=error_clipping)) - src_backward = simple_gru( - input=src_embedding, - size=encoder_size, - reverse=True, - naive=True, - gru_layer_attr=ExtraLayerAttribute( - error_clipping_threshold=error_clipping)) - encoded_vector = concat_layer(input=[src_forward, src_backward]) - - with mixed_layer(size=decoder_size) as encoded_proj: - encoded_proj += full_matrix_projection(input=encoded_vector) - - backward_first = first_seq(input=src_backward) - with mixed_layer( - size=decoder_size, - act=TanhActivation(), ) as decoder_boot: - decoder_boot += full_matrix_projection(input=backward_first) - - def gru_decoder_with_attention(enc_vec, enc_proj, current_word): - decoder_mem = memory( - name='gru_decoder', size=decoder_size, boot_layer=decoder_boot) - - context = simple_attention( - encoded_sequence=enc_vec, - encoded_proj=enc_proj, - decoder_state=decoder_mem, ) - - with mixed_layer(size=decoder_size * 3) as decoder_inputs: - decoder_inputs += full_matrix_projection(input=context) - decoder_inputs += full_matrix_projection(input=current_word) - - gru_step = gru_step_naive_layer( - name='gru_decoder', - input=decoder_inputs, - output_mem=decoder_mem, - size=decoder_size, - layer_attr=ExtraLayerAttribute( - error_clipping_threshold=error_clipping)) - - with mixed_layer( - size=target_dict_dim, bias_attr=True, - act=SoftmaxActivation()) as out: - out += full_matrix_projection(input=gru_step) - return out - - decoder_group_name = "decoder_group" - group_inputs = [ - StaticInput( - input=encoded_vector, is_seq=True), StaticInput( - input=encoded_proj, is_seq=True) - ] - - if not is_generating: - trg_embedding = embedding_layer( - input=data_layer( - name='target_language_word', size=target_dict_dim), - size=word_vector_dim, - param_attr=ParamAttr(name='_target_language_embedding')) - group_inputs.append(trg_embedding) - - # For decoder equipped with attention mechanism, in training, - # target embeding (the groudtruth) is the data input, - # while encoded source sequence is accessed to as an unbounded memory. - # Here, the StaticInput defines a read-only memory - # for the recurrent_group. - decoder = recurrent_group( - name=decoder_group_name, - step=gru_decoder_with_attention, - input=group_inputs) - - lbl = data_layer(name='target_language_next_word', size=target_dict_dim) - cost = classification_cost(input=decoder, label=lbl) - outputs(cost) - else: - # In generation, the decoder predicts a next target word based on - # the encoded source sequence and the last generated target word. - - # The encoded source sequence (encoder's output) must be specified by - # StaticInput, which is a read-only memory. - # Embedding of the last generated word is automatically gotten by - # GeneratedInputs, which is initialized by a start mark, such as , - # and must be included in generation. - - trg_embedding = GeneratedInput( - size=target_dict_dim, - embedding_name='_target_language_embedding', - embedding_size=word_vector_dim) - group_inputs.append(trg_embedding) - - beam_gen = beam_search( - name=decoder_group_name, - step=gru_decoder_with_attention, - input=group_inputs, - bos_id=0, - eos_id=1, - beam_size=beam_size, - max_length=max_length) - - seqtext_printer_evaluator( - input=beam_gen, - id_input=data_layer( - name="sent_id", size=1), - dict_file=trg_dict_path, - result_file=gen_trans_file) - outputs(beam_gen) diff --git a/demo/seqToseq/translation/eval_bleu.sh b/demo/seqToseq/translation/eval_bleu.sh deleted file mode 100755 index 54c2ed237e9..00000000000 --- a/demo/seqToseq/translation/eval_bleu.sh +++ /dev/null @@ -1,42 +0,0 @@ -#!/bin/bash -# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -set -e -gen_file=$1 -beam_size=$2 - -# find top1 generating result -top1=$(printf '%s_top1.txt' `basename $gen_file .txt`) -if [ $beam_size -eq 1 ]; then - awk -F "\t" '{sub(" ","",$2);sub(" ","",$2);print $2}' $gen_file >$top1 -else - awk 'BEGIN{ - FS="\t"; - OFS="\t"; - read_pos = 2} { - if (NR == read_pos){ - sub(" ","",$3); - sub(" ","",$3); - print $3; - read_pos += (2 + res_num); - }}' res_num=$beam_size $gen_file >$top1 -fi - -# evalute bleu value -bleu_script=multi-bleu.perl -standard_res=../data/wmt14/gen/ntst14.trg -bleu_res=`perl $bleu_script $standard_res <$top1` - -echo $bleu_res -rm $top1 diff --git a/demo/seqToseq/translation/gen.conf b/demo/seqToseq/translation/gen.conf deleted file mode 100644 index e9bea4e4559..00000000000 --- a/demo/seqToseq/translation/gen.conf +++ /dev/null @@ -1,36 +0,0 @@ -#edit-mode: -*- python -*- -# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import sys -sys.path.append("..") - -from seqToseq_net import * - -# whether this config is used for generating -is_generating = True - -### Data Definiation -gen_conf = seq_to_seq_data(data_dir = "./data/pre-wmt14", - is_generating = is_generating, - gen_result = "./translation/gen_result") - -### Algorithm Configuration -settings( - learning_method = AdamOptimizer(), - batch_size = 1, - learning_rate = 0) - -### Network Architecture -gru_encoder_decoder(gen_conf, is_generating) diff --git a/demo/seqToseq/translation/gen.sh b/demo/seqToseq/translation/gen.sh deleted file mode 100755 index 64b78f5e965..00000000000 --- a/demo/seqToseq/translation/gen.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/bin/bash -# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -set -e -cd .. - -paddle train \ - --job=test \ - --config='translation/gen.conf' \ - --save_dir='data/wmt14_model' \ - --use_gpu=false \ - --num_passes=13 \ - --test_pass=12 \ - --trainer_count=1 \ - 2>&1 | tee 'translation/gen.log' -paddle usage -l 'translation/gen.log' -e $? -n "seqToseq_translation_gen" >/dev/null 2>&1 diff --git a/demo/seqToseq/translation/moses_bleu.sh b/demo/seqToseq/translation/moses_bleu.sh deleted file mode 100755 index 2f230d7f4c7..00000000000 --- a/demo/seqToseq/translation/moses_bleu.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/bash -# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -set -e -set -x -echo "Downloading multi-bleu.perl" -wget https://raw.githubusercontent.com/moses-smt/mosesdecoder/master/scripts/generic/multi-bleu.perl --no-check-certificate diff --git a/demo/seqToseq/translation/train.conf b/demo/seqToseq/translation/train.conf deleted file mode 100644 index 72b7ccdbb95..00000000000 --- a/demo/seqToseq/translation/train.conf +++ /dev/null @@ -1,36 +0,0 @@ -#edit-mode: -*- python -*- -# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import sys -sys.path.append("..") - -from seqToseq_net import * - -# whether this config is used for generating -is_generating = False - -### Data Definiation -data_dir = "./data/pre-wmt14" -train_conf = seq_to_seq_data(data_dir = data_dir, - is_generating = is_generating) - -### Algorithm Configuration -settings( - learning_method = AdamOptimizer(), - batch_size = 50, - learning_rate = 5e-4) - -### Network Architecture -gru_encoder_decoder(train_conf, is_generating) diff --git a/demo/seqToseq/translation/train.sh b/demo/seqToseq/translation/train.sh deleted file mode 100755 index b0ec9854b11..00000000000 --- a/demo/seqToseq/translation/train.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/bin/bash -# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -set -e -cd .. - -paddle train \ ---config='translation/train.conf' \ ---save_dir='translation/model' \ ---use_gpu=false \ ---num_passes=16 \ ---show_parameter_stats_period=100 \ ---trainer_count=4 \ ---log_period=10 \ ---dot_period=5 \ -2>&1 | tee 'translation/train.log' -paddle usage -l 'translation/train.log' -e $? -n "seqToseq_translation_train" >/dev/null 2>&1 diff --git a/demo/vae/dataloader.pyc b/demo/vae/dataloader.pyc deleted file mode 100644 index 1be8890dafd76e6cb2028bcbfdf2022c18a229ae..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2148 zcmb_dO>f&q5S=CUVL4W97_l3-=?4%L)uE0W^il*t5w{2mAFKoAOLSprab?qyNP)W! zqQW}4K>udXz4iyR@6FN<(4Oj6_Kap{$=R7VZ%6ST{mql-2d}5nd}4gRgt#YAIsT2z zMJ7h_Nb;8CvHT)(Bl3EJwUP0ljpvF#@frCCFi%FuJ zOazRD!CH(wiN)fSww_{S(w4lV{*L5bHM){-sIei*c+g|mU8twSJoDLbQ5MdpY8Efx z_Ds*$Sy4`z1to&~0CCTut|Pe?Nnsc@P-ieA@v&UCA0VRVj zl;4Cp%lz~rb;ZxtGc)FqE;E1OH?T^VRZ)6dd%jsNHMOj+vz70|Q7p1^n`!{-JR>ga zYHFPuFlkTa?0lBQFcL;c7;^v#%F!HNR0})I=2@N><*>--!`v>a;oHnAJfSa7m#dIZ zT9k!PQ=a9Ry_lZTDm#Sxa1j*r9in63> z{C)emjX0@&2c}Pbs#J0xz@t$t6MvfVL;f6X74g*SuMgH!aL*7>(F{>zzK(2G#$cf1~o$yW3iUdfcS diff --git a/demo/word2vec/api_train_v2.py b/demo/word2vec/api_train_v2.py deleted file mode 100644 index c0940f0e56e..00000000000 --- a/demo/word2vec/api_train_v2.py +++ /dev/null @@ -1,100 +0,0 @@ -import gzip -import math - -import paddle.v2 as paddle - -embsize = 32 -hiddensize = 256 -N = 5 - - -def wordemb(inlayer): - wordemb = paddle.layer.embedding( - input=inlayer, - size=embsize, - param_attr=paddle.attr.Param( - name="_proj", - initial_std=0.001, - learning_rate=1, - l2_rate=0, - sparse_update=True)) - return wordemb - - -def main(): - # for local training - cluster_train = False - - if not cluster_train: - paddle.init(use_gpu=False, trainer_count=1) - else: - paddle.init( - use_gpu=False, - trainer_count=2, - port=7164, - ports_num=1, - ports_num_for_sparse=1, - num_gradient_servers=1) - word_dict = paddle.dataset.imikolov.build_dict() - dict_size = len(word_dict) - firstword = paddle.layer.data( - name="firstw", type=paddle.data_type.integer_value(dict_size)) - secondword = paddle.layer.data( - name="secondw", type=paddle.data_type.integer_value(dict_size)) - thirdword = paddle.layer.data( - name="thirdw", type=paddle.data_type.integer_value(dict_size)) - fourthword = paddle.layer.data( - name="fourthw", type=paddle.data_type.integer_value(dict_size)) - nextword = paddle.layer.data( - name="fifthw", type=paddle.data_type.integer_value(dict_size)) - - Efirst = wordemb(firstword) - Esecond = wordemb(secondword) - Ethird = wordemb(thirdword) - Efourth = wordemb(fourthword) - - contextemb = paddle.layer.concat(input=[Efirst, Esecond, Ethird, Efourth]) - hidden1 = paddle.layer.fc(input=contextemb, - size=hiddensize, - act=paddle.activation.Sigmoid(), - layer_attr=paddle.attr.Extra(drop_rate=0.5), - bias_attr=paddle.attr.Param(learning_rate=2), - param_attr=paddle.attr.Param( - initial_std=1. / math.sqrt(embsize * 8), - learning_rate=1)) - predictword = paddle.layer.fc(input=hidden1, - size=dict_size, - bias_attr=paddle.attr.Param(learning_rate=2), - act=paddle.activation.Softmax()) - - def event_handler(event): - if isinstance(event, paddle.event.EndIteration): - if event.batch_id % 100 == 0: - with gzip.open("batch-" + str(event.batch_id) + ".tar.gz", - 'w') as f: - trainer.save_parameter_to_tar(f) - result = trainer.test( - paddle.batch( - paddle.dataset.imikolov.test(word_dict, N), 32)) - print "Pass %d, Batch %d, Cost %f, %s, Testing metrics %s" % ( - event.pass_id, event.batch_id, event.cost, event.metrics, - result.metrics) - - cost = paddle.layer.classification_cost(input=predictword, label=nextword) - - parameters = paddle.parameters.create(cost) - adagrad = paddle.optimizer.AdaGrad( - learning_rate=3e-3, - regularization=paddle.optimizer.L2Regularization(8e-4)) - trainer = paddle.trainer.SGD(cost, - parameters, - adagrad, - is_local=not cluster_train) - trainer.train( - paddle.batch(paddle.dataset.imikolov.train(word_dict, N), 32), - num_passes=30, - event_handler=event_handler) - - -if __name__ == '__main__': - main() diff --git a/v1_api_demo/README.md b/v1_api_demo/README.md new file mode 100644 index 00000000000..9442f769412 --- /dev/null +++ b/v1_api_demo/README.md @@ -0,0 +1,5 @@ +The examples in v1_api_demo are using v1_api now, and will be upgraded into v2_api later. +Thus, v1_api_demo is a temporary directory. We decide not to maintain it and will delete it in future. + +Please go to [PaddlePaddle/book](https://github.com/PaddlePaddle/book) and +[PaddlePaddle/models](https://github.com/PaddlePaddle/models) to learn PaddlePaddle. diff --git a/demo/gan/.gitignore b/v1_api_demo/gan/.gitignore similarity index 100% rename from demo/gan/.gitignore rename to v1_api_demo/gan/.gitignore diff --git a/demo/gan/README.md b/v1_api_demo/gan/README.md similarity index 100% rename from demo/gan/README.md rename to v1_api_demo/gan/README.md diff --git a/demo/gan/data/download_cifar.sh b/v1_api_demo/gan/data/download_cifar.sh similarity index 100% rename from demo/gan/data/download_cifar.sh rename to v1_api_demo/gan/data/download_cifar.sh diff --git a/demo/gan/data/get_mnist_data.sh b/v1_api_demo/gan/data/get_mnist_data.sh similarity index 100% rename from demo/gan/data/get_mnist_data.sh rename to v1_api_demo/gan/data/get_mnist_data.sh diff --git a/demo/gan/gan_conf.py b/v1_api_demo/gan/gan_conf.py similarity index 100% rename from demo/gan/gan_conf.py rename to v1_api_demo/gan/gan_conf.py diff --git a/demo/gan/gan_conf_image.py b/v1_api_demo/gan/gan_conf_image.py similarity index 100% rename from demo/gan/gan_conf_image.py rename to v1_api_demo/gan/gan_conf_image.py diff --git a/demo/gan/gan_trainer.py b/v1_api_demo/gan/gan_trainer.py similarity index 100% rename from demo/gan/gan_trainer.py rename to v1_api_demo/gan/gan_trainer.py diff --git a/demo/model_zoo/embedding/.gitignore b/v1_api_demo/model_zoo/embedding/.gitignore similarity index 100% rename from demo/model_zoo/embedding/.gitignore rename to v1_api_demo/model_zoo/embedding/.gitignore diff --git a/demo/model_zoo/embedding/extract_para.py b/v1_api_demo/model_zoo/embedding/extract_para.py similarity index 100% rename from demo/model_zoo/embedding/extract_para.py rename to v1_api_demo/model_zoo/embedding/extract_para.py diff --git a/demo/model_zoo/embedding/paraconvert.py b/v1_api_demo/model_zoo/embedding/paraconvert.py similarity index 100% rename from demo/model_zoo/embedding/paraconvert.py rename to v1_api_demo/model_zoo/embedding/paraconvert.py diff --git a/demo/model_zoo/embedding/pre_DictAndModel.sh b/v1_api_demo/model_zoo/embedding/pre_DictAndModel.sh similarity index 100% rename from demo/model_zoo/embedding/pre_DictAndModel.sh rename to v1_api_demo/model_zoo/embedding/pre_DictAndModel.sh diff --git a/demo/model_zoo/resnet/.gitignore b/v1_api_demo/model_zoo/resnet/.gitignore similarity index 100% rename from demo/model_zoo/resnet/.gitignore rename to v1_api_demo/model_zoo/resnet/.gitignore diff --git a/demo/model_zoo/resnet/classify.py b/v1_api_demo/model_zoo/resnet/classify.py similarity index 100% rename from demo/model_zoo/resnet/classify.py rename to v1_api_demo/model_zoo/resnet/classify.py diff --git a/demo/model_zoo/resnet/example/.gitignore b/v1_api_demo/model_zoo/resnet/example/.gitignore similarity index 100% rename from demo/model_zoo/resnet/example/.gitignore rename to v1_api_demo/model_zoo/resnet/example/.gitignore diff --git a/demo/model_zoo/resnet/example/__init__.py b/v1_api_demo/model_zoo/resnet/example/__init__.py similarity index 100% rename from demo/model_zoo/resnet/example/__init__.py rename to v1_api_demo/model_zoo/resnet/example/__init__.py diff --git a/demo/model_zoo/resnet/example/cat.jpg b/v1_api_demo/model_zoo/resnet/example/cat.jpg similarity index 100% rename from demo/model_zoo/resnet/example/cat.jpg rename to v1_api_demo/model_zoo/resnet/example/cat.jpg diff --git a/demo/model_zoo/resnet/example/dog.jpg b/v1_api_demo/model_zoo/resnet/example/dog.jpg similarity index 100% rename from demo/model_zoo/resnet/example/dog.jpg rename to v1_api_demo/model_zoo/resnet/example/dog.jpg diff --git a/demo/model_zoo/resnet/example/image_list_provider.py b/v1_api_demo/model_zoo/resnet/example/image_list_provider.py similarity index 100% rename from demo/model_zoo/resnet/example/image_list_provider.py rename to v1_api_demo/model_zoo/resnet/example/image_list_provider.py diff --git a/demo/model_zoo/resnet/example/test.list b/v1_api_demo/model_zoo/resnet/example/test.list similarity index 100% rename from demo/model_zoo/resnet/example/test.list rename to v1_api_demo/model_zoo/resnet/example/test.list diff --git a/demo/model_zoo/resnet/extract_fea_c++.sh b/v1_api_demo/model_zoo/resnet/extract_fea_c++.sh similarity index 100% rename from demo/model_zoo/resnet/extract_fea_c++.sh rename to v1_api_demo/model_zoo/resnet/extract_fea_c++.sh diff --git a/demo/model_zoo/resnet/extract_fea_py.sh b/v1_api_demo/model_zoo/resnet/extract_fea_py.sh similarity index 100% rename from demo/model_zoo/resnet/extract_fea_py.sh rename to v1_api_demo/model_zoo/resnet/extract_fea_py.sh diff --git a/demo/model_zoo/resnet/get_model.sh b/v1_api_demo/model_zoo/resnet/get_model.sh similarity index 100% rename from demo/model_zoo/resnet/get_model.sh rename to v1_api_demo/model_zoo/resnet/get_model.sh diff --git a/demo/model_zoo/resnet/load_feature.py b/v1_api_demo/model_zoo/resnet/load_feature.py similarity index 100% rename from demo/model_zoo/resnet/load_feature.py rename to v1_api_demo/model_zoo/resnet/load_feature.py diff --git a/demo/model_zoo/resnet/net_diagram.sh b/v1_api_demo/model_zoo/resnet/net_diagram.sh similarity index 100% rename from demo/model_zoo/resnet/net_diagram.sh rename to v1_api_demo/model_zoo/resnet/net_diagram.sh diff --git a/demo/model_zoo/resnet/predict.sh b/v1_api_demo/model_zoo/resnet/predict.sh similarity index 100% rename from demo/model_zoo/resnet/predict.sh rename to v1_api_demo/model_zoo/resnet/predict.sh diff --git a/demo/model_zoo/resnet/resnet.py b/v1_api_demo/model_zoo/resnet/resnet.py similarity index 100% rename from demo/model_zoo/resnet/resnet.py rename to v1_api_demo/model_zoo/resnet/resnet.py diff --git a/demo/quick_start/.gitignore b/v1_api_demo/quick_start/.gitignore similarity index 100% rename from demo/quick_start/.gitignore rename to v1_api_demo/quick_start/.gitignore diff --git a/demo/quick_start/api_predict.py b/v1_api_demo/quick_start/api_predict.py similarity index 100% rename from demo/quick_start/api_predict.py rename to v1_api_demo/quick_start/api_predict.py diff --git a/demo/quick_start/api_predict.sh b/v1_api_demo/quick_start/api_predict.sh similarity index 100% rename from demo/quick_start/api_predict.sh rename to v1_api_demo/quick_start/api_predict.sh diff --git a/demo/quick_start/api_train.py b/v1_api_demo/quick_start/api_train.py similarity index 100% rename from demo/quick_start/api_train.py rename to v1_api_demo/quick_start/api_train.py diff --git a/demo/quick_start/api_train.sh b/v1_api_demo/quick_start/api_train.sh similarity index 100% rename from demo/quick_start/api_train.sh rename to v1_api_demo/quick_start/api_train.sh diff --git a/demo/quick_start/cluster/cluster_train.sh b/v1_api_demo/quick_start/cluster/cluster_train.sh similarity index 100% rename from demo/quick_start/cluster/cluster_train.sh rename to v1_api_demo/quick_start/cluster/cluster_train.sh diff --git a/demo/quick_start/cluster/env.sh b/v1_api_demo/quick_start/cluster/env.sh similarity index 100% rename from demo/quick_start/cluster/env.sh rename to v1_api_demo/quick_start/cluster/env.sh diff --git a/demo/quick_start/cluster/pserver.sh b/v1_api_demo/quick_start/cluster/pserver.sh similarity index 100% rename from demo/quick_start/cluster/pserver.sh rename to v1_api_demo/quick_start/cluster/pserver.sh diff --git a/demo/quick_start/data/README.md b/v1_api_demo/quick_start/data/README.md similarity index 100% rename from demo/quick_start/data/README.md rename to v1_api_demo/quick_start/data/README.md diff --git a/demo/quick_start/data/get_data.sh b/v1_api_demo/quick_start/data/get_data.sh similarity index 100% rename from demo/quick_start/data/get_data.sh rename to v1_api_demo/quick_start/data/get_data.sh diff --git a/demo/quick_start/data/proc_from_raw_data/get_data.sh b/v1_api_demo/quick_start/data/proc_from_raw_data/get_data.sh similarity index 100% rename from demo/quick_start/data/proc_from_raw_data/get_data.sh rename to v1_api_demo/quick_start/data/proc_from_raw_data/get_data.sh diff --git a/demo/quick_start/data/proc_from_raw_data/preprocess.py b/v1_api_demo/quick_start/data/proc_from_raw_data/preprocess.py similarity index 100% rename from demo/quick_start/data/proc_from_raw_data/preprocess.py rename to v1_api_demo/quick_start/data/proc_from_raw_data/preprocess.py diff --git a/demo/quick_start/dataprovider_bow.py b/v1_api_demo/quick_start/dataprovider_bow.py similarity index 100% rename from demo/quick_start/dataprovider_bow.py rename to v1_api_demo/quick_start/dataprovider_bow.py diff --git a/demo/quick_start/dataprovider_emb.py b/v1_api_demo/quick_start/dataprovider_emb.py similarity index 100% rename from demo/quick_start/dataprovider_emb.py rename to v1_api_demo/quick_start/dataprovider_emb.py diff --git a/demo/quick_start/predict.sh b/v1_api_demo/quick_start/predict.sh similarity index 100% rename from demo/quick_start/predict.sh rename to v1_api_demo/quick_start/predict.sh diff --git a/demo/quick_start/train.sh b/v1_api_demo/quick_start/train.sh similarity index 100% rename from demo/quick_start/train.sh rename to v1_api_demo/quick_start/train.sh diff --git a/demo/quick_start/trainer_config.bidi-lstm.py b/v1_api_demo/quick_start/trainer_config.bidi-lstm.py similarity index 100% rename from demo/quick_start/trainer_config.bidi-lstm.py rename to v1_api_demo/quick_start/trainer_config.bidi-lstm.py diff --git a/demo/quick_start/trainer_config.cnn.py b/v1_api_demo/quick_start/trainer_config.cnn.py similarity index 100% rename from demo/quick_start/trainer_config.cnn.py rename to v1_api_demo/quick_start/trainer_config.cnn.py diff --git a/demo/quick_start/trainer_config.db-lstm.py b/v1_api_demo/quick_start/trainer_config.db-lstm.py similarity index 100% rename from demo/quick_start/trainer_config.db-lstm.py rename to v1_api_demo/quick_start/trainer_config.db-lstm.py diff --git a/demo/quick_start/trainer_config.emb.py b/v1_api_demo/quick_start/trainer_config.emb.py similarity index 100% rename from demo/quick_start/trainer_config.emb.py rename to v1_api_demo/quick_start/trainer_config.emb.py diff --git a/demo/quick_start/trainer_config.lr.py b/v1_api_demo/quick_start/trainer_config.lr.py similarity index 100% rename from demo/quick_start/trainer_config.lr.py rename to v1_api_demo/quick_start/trainer_config.lr.py diff --git a/demo/quick_start/trainer_config.lstm.py b/v1_api_demo/quick_start/trainer_config.lstm.py similarity index 100% rename from demo/quick_start/trainer_config.lstm.py rename to v1_api_demo/quick_start/trainer_config.lstm.py diff --git a/demo/quick_start/trainer_config.resnet-lstm.py b/v1_api_demo/quick_start/trainer_config.resnet-lstm.py similarity index 100% rename from demo/quick_start/trainer_config.resnet-lstm.py rename to v1_api_demo/quick_start/trainer_config.resnet-lstm.py diff --git a/demo/sequence_tagging/data/get_data.sh b/v1_api_demo/sequence_tagging/data/get_data.sh similarity index 100% rename from demo/sequence_tagging/data/get_data.sh rename to v1_api_demo/sequence_tagging/data/get_data.sh diff --git a/demo/sequence_tagging/data/test.list b/v1_api_demo/sequence_tagging/data/test.list similarity index 100% rename from demo/sequence_tagging/data/test.list rename to v1_api_demo/sequence_tagging/data/test.list diff --git a/demo/sequence_tagging/data/train.list b/v1_api_demo/sequence_tagging/data/train.list similarity index 100% rename from demo/sequence_tagging/data/train.list rename to v1_api_demo/sequence_tagging/data/train.list diff --git a/demo/sequence_tagging/dataprovider.py b/v1_api_demo/sequence_tagging/dataprovider.py similarity index 100% rename from demo/sequence_tagging/dataprovider.py rename to v1_api_demo/sequence_tagging/dataprovider.py diff --git a/demo/sequence_tagging/linear_crf.py b/v1_api_demo/sequence_tagging/linear_crf.py similarity index 100% rename from demo/sequence_tagging/linear_crf.py rename to v1_api_demo/sequence_tagging/linear_crf.py diff --git a/demo/sequence_tagging/readme.md b/v1_api_demo/sequence_tagging/readme.md similarity index 100% rename from demo/sequence_tagging/readme.md rename to v1_api_demo/sequence_tagging/readme.md diff --git a/demo/sequence_tagging/rnn_crf.py b/v1_api_demo/sequence_tagging/rnn_crf.py similarity index 100% rename from demo/sequence_tagging/rnn_crf.py rename to v1_api_demo/sequence_tagging/rnn_crf.py diff --git a/demo/sequence_tagging/train.sh b/v1_api_demo/sequence_tagging/train.sh similarity index 100% rename from demo/sequence_tagging/train.sh rename to v1_api_demo/sequence_tagging/train.sh diff --git a/demo/sequence_tagging/train_linear.sh b/v1_api_demo/sequence_tagging/train_linear.sh similarity index 100% rename from demo/sequence_tagging/train_linear.sh rename to v1_api_demo/sequence_tagging/train_linear.sh diff --git a/demo/traffic_prediction/README b/v1_api_demo/traffic_prediction/README similarity index 100% rename from demo/traffic_prediction/README rename to v1_api_demo/traffic_prediction/README diff --git a/demo/traffic_prediction/data/get_data.sh b/v1_api_demo/traffic_prediction/data/get_data.sh similarity index 100% rename from demo/traffic_prediction/data/get_data.sh rename to v1_api_demo/traffic_prediction/data/get_data.sh diff --git a/demo/traffic_prediction/dataprovider.py b/v1_api_demo/traffic_prediction/dataprovider.py similarity index 100% rename from demo/traffic_prediction/dataprovider.py rename to v1_api_demo/traffic_prediction/dataprovider.py diff --git a/demo/traffic_prediction/gen_result.py b/v1_api_demo/traffic_prediction/gen_result.py similarity index 100% rename from demo/traffic_prediction/gen_result.py rename to v1_api_demo/traffic_prediction/gen_result.py diff --git a/demo/traffic_prediction/predict.sh b/v1_api_demo/traffic_prediction/predict.sh similarity index 100% rename from demo/traffic_prediction/predict.sh rename to v1_api_demo/traffic_prediction/predict.sh diff --git a/demo/traffic_prediction/train.sh b/v1_api_demo/traffic_prediction/train.sh similarity index 100% rename from demo/traffic_prediction/train.sh rename to v1_api_demo/traffic_prediction/train.sh diff --git a/demo/traffic_prediction/trainer_config.py b/v1_api_demo/traffic_prediction/trainer_config.py similarity index 100% rename from demo/traffic_prediction/trainer_config.py rename to v1_api_demo/traffic_prediction/trainer_config.py diff --git a/demo/vae/README.md b/v1_api_demo/vae/README.md similarity index 100% rename from demo/vae/README.md rename to v1_api_demo/vae/README.md diff --git a/demo/vae/data/get_mnist_data.sh b/v1_api_demo/vae/data/get_mnist_data.sh similarity index 100% rename from demo/vae/data/get_mnist_data.sh rename to v1_api_demo/vae/data/get_mnist_data.sh diff --git a/demo/vae/dataloader.py b/v1_api_demo/vae/dataloader.py similarity index 100% rename from demo/vae/dataloader.py rename to v1_api_demo/vae/dataloader.py diff --git a/demo/vae/vae_conf.py b/v1_api_demo/vae/vae_conf.py similarity index 100% rename from demo/vae/vae_conf.py rename to v1_api_demo/vae/vae_conf.py diff --git a/demo/vae/vae_train.py b/v1_api_demo/vae/vae_train.py similarity index 100% rename from demo/vae/vae_train.py rename to v1_api_demo/vae/vae_train.py -- GitLab From 31e333a212b524effee88f07b6a8a63bd3fd1dd1 Mon Sep 17 00:00:00 2001 From: Liu Yiqun Date: Mon, 5 Jun 2017 04:26:41 +0000 Subject: [PATCH 0537/3256] Add doc to the usage of warp-ctc. --- .../paddle/trainer_config_helpers/layers.py | 29 ++++++++++++++----- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/python/paddle/trainer_config_helpers/layers.py b/python/paddle/trainer_config_helpers/layers.py index 8a4fe178dc3..60734085559 100755 --- a/python/paddle/trainer_config_helpers/layers.py +++ b/python/paddle/trainer_config_helpers/layers.py @@ -4765,21 +4765,36 @@ def warp_ctc_layer(input, layer_attr=None): """ A layer intergrating the open-source `warp-ctc - ` library, which is used in + `_ library, which is used in `Deep Speech 2: End-toEnd Speech Recognition in English and Mandarin - `, to compute Connectionist Temporal - Classification (CTC) loss. + `_, to compute Connectionist Temporal + Classification (CTC) loss. Besides, another `warp-ctc + `_ repository, which is forked from + the official one, is maintained to enable more compiling options. During the + building process, PaddlePaddle will clone the source codes, build and + install it to :code:`third_party/install/warpctc` directory. + + To use warp_ctc layer, you need to specify the path of :code:`libwarpctc.so`, + using following methods: + + 1. Set it in :code:`paddle.init` (python api) or :code:`paddle_init` (c api), + such as :code:`paddle.init(use_gpu=True, + warpctc_dir=your_paddle_source_dir/third_party/install/warpctc/lib)`. + + 2. Set environment variable LD_LIBRARY_PATH on Linux or DYLD_LIBRARY_PATH + on Mac OS. For instance, :code:`export + LD_LIBRARY_PATH=your_paddle_source_dir/third_party/install/warpctc/lib:$LD_LIBRARY_PATH`. More details of CTC can be found by referring to `Connectionist Temporal Classification: Labelling Unsegmented Sequence Data with Recurrent Neural Networks `_ + icml2006_GravesFGS06.pdf>`_. Note: - Let num_classes represent the category number. Considering the 'blank' - label needed by CTC, you need to use (num_classes + 1) as the input - size. Thus, the size of both warp_ctc_layer and 'input' layer should - be set to num_classes + 1. + label needed by CTC, you need to use (num_classes + 1) as the input size. + Thus, the size of both warp_ctc layer and 'input' layer should be set to + num_classes + 1. - You can set 'blank' to any value ranged in [0, num_classes], which should be consistent as that used in your labels. - As a native 'softmax' activation is interated to the warp-ctc library, -- GitLab From 76f1fd18f98a8067480a4c0815fef1a7f361af43 Mon Sep 17 00:00:00 2001 From: Luo Tao Date: Mon, 5 Jun 2017 14:13:06 +0800 Subject: [PATCH 0538/3256] remain mnist demo --- v1_api_demo/mnist/.gitignore | 10 ++ v1_api_demo/mnist/api_train.py | 196 +++++++++++++++++++++++ v1_api_demo/mnist/data/generate_list.py | 21 +++ v1_api_demo/mnist/data/get_mnist_data.sh | 21 +++ v1_api_demo/mnist/light_mnist.py | 79 +++++++++ v1_api_demo/mnist/mnist_provider.py | 12 ++ v1_api_demo/mnist/mnist_util.py | 30 ++++ v1_api_demo/mnist/train.sh | 32 ++++ v1_api_demo/mnist/vgg_16_mnist.py | 50 ++++++ 9 files changed, 451 insertions(+) create mode 100644 v1_api_demo/mnist/.gitignore create mode 100644 v1_api_demo/mnist/api_train.py create mode 100644 v1_api_demo/mnist/data/generate_list.py create mode 100755 v1_api_demo/mnist/data/get_mnist_data.sh create mode 100644 v1_api_demo/mnist/light_mnist.py create mode 100644 v1_api_demo/mnist/mnist_provider.py create mode 100644 v1_api_demo/mnist/mnist_util.py create mode 100755 v1_api_demo/mnist/train.sh create mode 100644 v1_api_demo/mnist/vgg_16_mnist.py diff --git a/v1_api_demo/mnist/.gitignore b/v1_api_demo/mnist/.gitignore new file mode 100644 index 00000000000..7e61d5e3a0c --- /dev/null +++ b/v1_api_demo/mnist/.gitignore @@ -0,0 +1,10 @@ +data/raw_data +data/*.list +mnist_vgg_model +plot.png +train.log +*pyc +.ipynb_checkpoints +params.pkl +params.tar +params.tar.gz diff --git a/v1_api_demo/mnist/api_train.py b/v1_api_demo/mnist/api_train.py new file mode 100644 index 00000000000..ea1caa7dd96 --- /dev/null +++ b/v1_api_demo/mnist/api_train.py @@ -0,0 +1,196 @@ +""" +A very basic example for how to use current Raw SWIG API to train mnist network. + +Current implementation uses Raw SWIG, which means the API call is directly \ +passed to C++ side of Paddle. + +The user api could be simpler and carefully designed. +""" +import random + +import numpy as np +import paddle.v2 as paddle_v2 +import py_paddle.swig_paddle as api +from paddle.trainer_config_helpers import * +from py_paddle import DataProviderConverter + +from mnist_util import read_from_mnist + + +def init_parameter(network): + assert isinstance(network, api.GradientMachine) + for each_param in network.getParameters(): + assert isinstance(each_param, api.Parameter) + array_size = len(each_param) + array = np.random.uniform(-1.0, 1.0, array_size).astype('float32') + each_param.getBuf(api.PARAMETER_VALUE).copyFromNumpyArray(array) + + +def generator_to_batch(generator, batch_size): + ret_val = list() + for each_item in generator: + ret_val.append(each_item) + if len(ret_val) == batch_size: + yield ret_val + ret_val = list() + if len(ret_val) != 0: + yield ret_val + + +class BatchPool(object): + def __init__(self, generator, batch_size): + self.data = list(generator) + self.batch_size = batch_size + + def __call__(self): + random.shuffle(self.data) + for offset in xrange(0, len(self.data), self.batch_size): + limit = min(offset + self.batch_size, len(self.data)) + yield self.data[offset:limit] + + +def input_order_converter(generator): + for each_item in generator: + yield each_item['pixel'], each_item['label'] + + +def main(): + api.initPaddle("-use_gpu=false", "-trainer_count=4") # use 4 cpu cores + + optimizer = paddle_v2.optimizer.Adam( + learning_rate=1e-4, + batch_size=1000, + model_average=ModelAverage(average_window=0.5), + regularization=L2Regularization(rate=0.5)) + + # Create Local Updater. Local means not run in cluster. + # For a cluster training, here we can change to createRemoteUpdater + # in future. + updater = optimizer.create_local_updater() + assert isinstance(updater, api.ParameterUpdater) + + # define network + images = paddle_v2.layer.data( + name='pixel', type=paddle_v2.data_type.dense_vector(784)) + label = paddle_v2.layer.data( + name='label', type=paddle_v2.data_type.integer_value(10)) + hidden1 = paddle_v2.layer.fc(input=images, size=200) + hidden2 = paddle_v2.layer.fc(input=hidden1, size=200) + inference = paddle_v2.layer.fc(input=hidden2, + size=10, + act=paddle_v2.activation.Softmax()) + cost = paddle_v2.layer.classification_cost(input=inference, label=label) + + # Create Simple Gradient Machine. + model_config = paddle_v2.layer.parse_network(cost) + m = api.GradientMachine.createFromConfigProto(model_config, + api.CREATE_MODE_NORMAL, + optimizer.enable_types()) + + # This type check is not useful. Only enable type hint in IDE. + # Such as PyCharm + assert isinstance(m, api.GradientMachine) + + # Initialize Parameter by numpy. + init_parameter(network=m) + + # Initialize ParameterUpdater. + updater.init(m) + + # DataProvider Converter is a utility convert Python Object to Paddle C++ + # Input. The input format is as same as Paddle's DataProvider. + converter = DataProviderConverter(input_types=[images.type, label.type]) + + train_file = './data/raw_data/train' + test_file = './data/raw_data/t10k' + + # start gradient machine. + # the gradient machine must be started before invoke forward/backward. + # not just for training, but also for inference. + m.start() + + # evaluator can print error rate, etc. It is a C++ class. + batch_evaluator = m.makeEvaluator() + test_evaluator = m.makeEvaluator() + + # Get Train Data. + # TrainData will stored in a data pool. Currently implementation is not care + # about memory, speed. Just a very naive implementation. + train_data_generator = input_order_converter(read_from_mnist(train_file)) + train_data = BatchPool(train_data_generator, 512) + + # outArgs is Neural Network forward result. Here is not useful, just passed + # to gradient_machine.forward + outArgs = api.Arguments.createArguments(0) + + for pass_id in xrange(2): # we train 2 passes. + updater.startPass() + + for batch_id, data_batch in enumerate(train_data()): + # data_batch is input images. + # here, for online learning, we could get data_batch from network. + + # Start update one batch. + pass_type = updater.startBatch(len(data_batch)) + + # Start BatchEvaluator. + # batch_evaluator can be used between start/finish. + batch_evaluator.start() + + # forwardBackward is a shortcut for forward and backward. + # It is sometimes faster than invoke forward/backward separately, + # because in GradientMachine, it may be async. + m.forwardBackward(converter(data_batch), outArgs, pass_type) + + for each_param in m.getParameters(): + updater.update(each_param) + + # Get cost. We use numpy to calculate total cost for this batch. + cost_vec = outArgs.getSlotValue(0) + cost_vec = cost_vec.copyToNumpyMat() + cost = cost_vec.sum() / len(data_batch) + + # Make evaluator works. + m.eval(batch_evaluator) + + # Print logs. + print 'Pass id', pass_id, 'Batch id', batch_id, 'with cost=', \ + cost, batch_evaluator + + batch_evaluator.finish() + # Finish batch. + # * will clear gradient. + # * ensure all values should be updated. + updater.finishBatch(cost) + + # testing stage. use test data set to test current network. + updater.apply() + test_evaluator.start() + test_data_generator = input_order_converter(read_from_mnist(test_file)) + for data_batch in generator_to_batch(test_data_generator, 512): + # in testing stage, only forward is needed. + m.forward(converter(data_batch), outArgs, api.PASS_TEST) + m.eval(test_evaluator) + + # print error rate for test data set + print 'Pass', pass_id, ' test evaluator: ', test_evaluator + test_evaluator.finish() + updater.restore() + + updater.catchUpWith() + params = m.getParameters() + for each_param in params: + assert isinstance(each_param, api.Parameter) + value = each_param.getBuf(api.PARAMETER_VALUE) + value = value.copyToNumpyArray() + + # Here, we could save parameter to every where you want + print each_param.getName(), value + + updater.finishPass() + + m.finish() + + +if __name__ == '__main__': + main() diff --git a/v1_api_demo/mnist/data/generate_list.py b/v1_api_demo/mnist/data/generate_list.py new file mode 100644 index 00000000000..49981cc7a93 --- /dev/null +++ b/v1_api_demo/mnist/data/generate_list.py @@ -0,0 +1,21 @@ +# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +o = open("./" + "train.list", "w") +o.write("./data/raw_data/train" + "\n") +o.close() + +o = open("./" + "test.list", "w") +o.write("./data/raw_data/t10k" + "\n") +o.close() diff --git a/v1_api_demo/mnist/data/get_mnist_data.sh b/v1_api_demo/mnist/data/get_mnist_data.sh new file mode 100755 index 00000000000..5a2e34026d4 --- /dev/null +++ b/v1_api_demo/mnist/data/get_mnist_data.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env sh +# This scripts downloads the mnist data and unzips it. +set -e +DIR="$( cd "$(dirname "$0")" ; pwd -P )" +rm -rf "$DIR/raw_data" +mkdir "$DIR/raw_data" +cd "$DIR/raw_data" + +echo "Downloading..." + +for fname in train-images-idx3-ubyte train-labels-idx1-ubyte t10k-images-idx3-ubyte t10k-labels-idx1-ubyte +do + if [ ! -e $fname ]; then + wget --no-check-certificate http://yann.lecun.com/exdb/mnist/${fname}.gz + gunzip ${fname}.gz + fi +done + +cd $DIR +rm -f *.list +python generate_list.py diff --git a/v1_api_demo/mnist/light_mnist.py b/v1_api_demo/mnist/light_mnist.py new file mode 100644 index 00000000000..33409054357 --- /dev/null +++ b/v1_api_demo/mnist/light_mnist.py @@ -0,0 +1,79 @@ +# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from paddle.trainer_config_helpers import * + +is_predict = get_config_arg("is_predict", bool, False) + +####################Data Configuration ################## + +if not is_predict: + data_dir = './data/' + define_py_data_sources2( + train_list=data_dir + 'train.list', + test_list=data_dir + 'test.list', + module='mnist_provider', + obj='process') + +######################Algorithm Configuration ############# +settings(batch_size=50, learning_rate=0.001, learning_method=AdamOptimizer()) + +#######################Network Configuration ############# + +data_size = 1 * 28 * 28 +label_size = 10 +img = data_layer(name='pixel', size=data_size) + + +# light cnn +# A shallower cnn model: [CNN, BN, ReLU, Max-Pooling] x4 + FC x1 +# Easier to train for mnist dataset and quite efficient +# Final performance is close to deeper ones on tasks such as digital and character classification +def light_cnn(input_image, num_channels, num_classes): + def __light__(ipt, + num_filter=128, + times=1, + conv_filter_size=3, + dropouts=0, + num_channels_=None): + return img_conv_group( + input=ipt, + num_channels=num_channels_, + pool_size=2, + pool_stride=2, + conv_padding=0, + conv_num_filter=[num_filter] * times, + conv_filter_size=conv_filter_size, + conv_act=ReluActivation(), + conv_with_batchnorm=True, + conv_batchnorm_drop_rate=dropouts, + pool_type=MaxPooling()) + + tmp = __light__(input_image, num_filter=128, num_channels_=num_channels) + tmp = __light__(tmp, num_filter=128) + tmp = __light__(tmp, num_filter=128) + tmp = __light__(tmp, num_filter=128, conv_filter_size=1) + + tmp = fc_layer(input=tmp, size=num_classes, act=SoftmaxActivation()) + return tmp + + +predict = light_cnn(input_image=img, num_channels=1, num_classes=label_size) + +if not is_predict: + lbl = data_layer(name="label", size=label_size) + inputs(img, lbl) + outputs(classification_cost(input=predict, label=lbl)) +else: + outputs(predict) diff --git a/v1_api_demo/mnist/mnist_provider.py b/v1_api_demo/mnist/mnist_provider.py new file mode 100644 index 00000000000..888cfef1e7e --- /dev/null +++ b/v1_api_demo/mnist/mnist_provider.py @@ -0,0 +1,12 @@ +from paddle.trainer.PyDataProvider2 import * +from mnist_util import read_from_mnist + + +# Define a py data provider +@provider( + input_types={'pixel': dense_vector(28 * 28), + 'label': integer_value(10)}, + cache=CacheType.CACHE_PASS_IN_MEM) +def process(settings, filename): # settings is not used currently. + for each in read_from_mnist(filename): + yield each diff --git a/v1_api_demo/mnist/mnist_util.py b/v1_api_demo/mnist/mnist_util.py new file mode 100644 index 00000000000..3fd88ae7edc --- /dev/null +++ b/v1_api_demo/mnist/mnist_util.py @@ -0,0 +1,30 @@ +import numpy + +__all__ = ['read_from_mnist'] + + +def read_from_mnist(filename): + imgf = filename + "-images-idx3-ubyte" + labelf = filename + "-labels-idx1-ubyte" + f = open(imgf, "rb") + l = open(labelf, "rb") + + f.read(16) + l.read(8) + + # Define number of samples for train/test + if "train" in filename: + n = 60000 + else: + n = 10000 + + images = numpy.fromfile( + f, 'ubyte', count=n * 28 * 28).reshape((n, 28 * 28)).astype('float32') + images = images / 255.0 * 2.0 - 1.0 + labels = numpy.fromfile(l, 'ubyte', count=n).astype("int") + + for i in xrange(n): + yield {"pixel": images[i, :], 'label': labels[i]} + + f.close() + l.close() diff --git a/v1_api_demo/mnist/train.sh b/v1_api_demo/mnist/train.sh new file mode 100755 index 00000000000..ca2b1ad9eb9 --- /dev/null +++ b/v1_api_demo/mnist/train.sh @@ -0,0 +1,32 @@ +#!/bin/bash +# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +set -e +config=vgg_16_mnist.py +output=./mnist_vgg_model +log=train.log + +paddle train \ +--config=$config \ +--dot_period=10 \ +--log_period=100 \ +--test_all_data_in_one_period=1 \ +--use_gpu=0 \ +--trainer_count=1 \ +--num_passes=100 \ +--save_dir=$output \ +2>&1 | tee $log +paddle usage -l $log -e $? -n "mnist_train" >/dev/null 2>&1 + +python -m paddle.utils.plotcurve -i $log > plot.png diff --git a/v1_api_demo/mnist/vgg_16_mnist.py b/v1_api_demo/mnist/vgg_16_mnist.py new file mode 100644 index 00000000000..a819b391c69 --- /dev/null +++ b/v1_api_demo/mnist/vgg_16_mnist.py @@ -0,0 +1,50 @@ +# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from paddle.trainer_config_helpers import * + +is_predict = get_config_arg("is_predict", bool, False) + +####################Data Configuration ################## + +if not is_predict: + data_dir = './data/' + define_py_data_sources2( + train_list=data_dir + 'train.list', + test_list=data_dir + 'test.list', + module='mnist_provider', + obj='process') + +######################Algorithm Configuration ############# +settings( + batch_size=128, + learning_rate=0.1 / 128.0, + learning_method=MomentumOptimizer(0.9), + regularization=L2Regularization(0.0005 * 128)) + +#######################Network Configuration ############# + +data_size = 1 * 28 * 28 +label_size = 10 +img = data_layer(name='pixel', size=data_size) + +# small_vgg is predined in trainer_config_helpers.network +predict = small_vgg(input_image=img, num_channels=1, num_classes=label_size) + +if not is_predict: + lbl = data_layer(name="label", size=label_size) + inputs(img, lbl) + outputs(classification_cost(input=predict, label=lbl)) +else: + outputs(predict) -- GitLab From 5b8a0c5dd8fcf44cb005e20af42074b166e3271d Mon Sep 17 00:00:00 2001 From: dzhwinter Date: Mon, 5 Jun 2017 14:43:04 +0800 Subject: [PATCH 0539/3256] "optimizer remove init create with proto" --- paddle/optimizer/CMakeLists.txt | 12 +++---- paddle/optimizer/Tensor.h | 9 ++++-- paddle/optimizer/adadelta_optimizer.cc | 18 ++++------- paddle/optimizer/adadelta_optimizer.h | 7 ++++- paddle/optimizer/adagrad_optimizer.cc | 5 --- paddle/optimizer/adagrad_optimizer.h | 6 +++- paddle/optimizer/adam_optimizer.cc | 23 ++++++-------- paddle/optimizer/adam_optimizer.h | 16 ++++++++-- paddle/optimizer/lr_policy.h | 9 +++--- paddle/optimizer/optimizer.cc | 26 +++++++-------- paddle/optimizer/optimizer.h | 4 +-- paddle/optimizer/parameter_optimizer.cc | 42 +++++++++++++++++-------- paddle/optimizer/parameter_optimizer.h | 24 +++++++------- paddle/optimizer/regularizer.cc | 2 ++ paddle/optimizer/sgd_optimizer.h | 15 +++++++-- paddle/optimizer/sgd_optmizer.cc | 21 ++----------- proto/OptimizerConfig.proto | 3 +- 17 files changed, 130 insertions(+), 112 deletions(-) diff --git a/paddle/optimizer/CMakeLists.txt b/paddle/optimizer/CMakeLists.txt index 134ca9e9d64..06f6d83efe1 100644 --- a/paddle/optimizer/CMakeLists.txt +++ b/paddle/optimizer/CMakeLists.txt @@ -1,9 +1,9 @@ include_directories(${CMAKE_CURRENT_BINARY_DIR}) set(OPITMIZER_SRCS - adadelta_optimizer.cc - adagrad_optimizer.cc - adam_optimizer.cc + # adadelta_optimizer.cc + # adagrad_optimizer.cc + # adam_optimizer.cc optimizer.cc parameter_optimizer.cc sgd_optmizer.cc @@ -11,9 +11,9 @@ set(OPITMIZER_SRCS ) set(OPITMIZER_Headers - adadelta_optimizer.h - adagrad_optimizer.h - adam_optimizer.h + # adadelta_optimizer.h + # adagrad_optimizer.h + # adam_optimizer.h lr_policy.h optimizer.h parameter_optimizer.h diff --git a/paddle/optimizer/Tensor.h b/paddle/optimizer/Tensor.h index a8387c4df41..d5ba4b3159f 100644 --- a/paddle/optimizer/Tensor.h +++ b/paddle/optimizer/Tensor.h @@ -5,6 +5,7 @@ */ #include +#include "optimizer.h" #include "paddle/math/BaseMatrix.h" namespace paddle { @@ -16,10 +17,14 @@ using TensorBase = BaseMatrixT; template class Tensor : public TensorBase { public: - Tensor(T* data, int size) : TensorBase(size, 1, 0, data, false, false) {} + Tensor(T* data, int size) : TensorBase(1, size, 0, data, false, false) {} T* get_buffer() { return this->data_; } + T& operator[](const int idx) { + CHECK(idx >= 0 && idx < this->width_) << " out of index range"; + return this->data_[idx]; + } // TODO: replace with tensorshape - size_t width() { return this->width_; } + size_t size() const { return this->width_; } }; } // namespace optimizer diff --git a/paddle/optimizer/adadelta_optimizer.cc b/paddle/optimizer/adadelta_optimizer.cc index 39d465cebe6..b76c123ec80 100644 --- a/paddle/optimizer/adadelta_optimizer.cc +++ b/paddle/optimizer/adadelta_optimizer.cc @@ -3,21 +3,14 @@ namespace paddle { namespace optimizer { -template -AdadeltaOptimizer::AdadeltaOptimizer(const ::paddle::OptimizerConfig& config) - : ParameterOptimizer(config) { - rho = config.adadelta().rho(); - epsilon = config.adadelta().epsilon(); - decay = config.adadelta().decay(); -} template void AdadeltaOptimizer::set_weight(const Tensor* p) { - size_t size = p->width(); + size_t size = p->size(); T* gptr = new T[size]; accum_gradient = Tensor(gptr, size); T* dptr = new T[size]; - accum_delta = Tensor(dtpr, size); + accum_delta = Tensor(dptr, size); T* dptr_current = new T[size]; update_delta = Tensor(dptr_current, size); } @@ -25,8 +18,8 @@ void AdadeltaOptimizer::set_weight(const Tensor* p) { template void AdadeltaOptimizer::update(const Tensor& gradient) { num_sample_passed += 1; - double learning_rate = lr_policy->get_learning_rate(); - for (size_t i = 0; i < parameter_.size(); ++i) { + double learning_rate = lr_policy->get_learning_rate(num_sample_passed); + for (size_t i = 0; i < parameter_->size(); ++i) { accum_gradient[i] = rho * accum_gradient[i] + (1.0 - rho) * gradient[i] * gradient[i]; @@ -36,7 +29,8 @@ void AdadeltaOptimizer::update(const Tensor& gradient) { accum_delta[i] = rho * accum_delta[i] + (1.0 - rho) * update_delta[i] * update_delta[i]; - parameter_[i] -= update_delta[i] + decay * parameter_[i]; + parameter_[i] -= + learning_rate * update_delta[i] + learning_rate * decay * parameter_[i]; } } diff --git a/paddle/optimizer/adadelta_optimizer.h b/paddle/optimizer/adadelta_optimizer.h index 1a8c03f2682..35f3ff86fcf 100644 --- a/paddle/optimizer/adadelta_optimizer.h +++ b/paddle/optimizer/adadelta_optimizer.h @@ -9,7 +9,12 @@ namespace optimizer { template class AdadeltaOptimizer : public ParameterOptimizer { public: - AdadeltaOptimizer(const OptimizerConfig &config); + using ParameterOptimizer::parameter_; + using ParameterOptimizer::num_sample_passed; + using ParameterOptimizer::lr_policy; + + AdadeltaOptimizer(double rho, double epsilon, double decay, BaseLr *lr) + : ParameterOptimizer(lr), rho(rho), epsilon(epsilon), decay(decay) {} ~AdadeltaOptimizer() { if (accum_gradient) delete accum_gradient; if (accum_delta) delete accum_delta; diff --git a/paddle/optimizer/adagrad_optimizer.cc b/paddle/optimizer/adagrad_optimizer.cc index 40402a67108..7b451cb4074 100644 --- a/paddle/optimizer/adagrad_optimizer.cc +++ b/paddle/optimizer/adagrad_optimizer.cc @@ -3,11 +3,6 @@ namespace paddle { namespace optimizer { template -AdagradOptimizer::AdagradOptimizer(const ::paddle::OptimizerConfig& config) - : ParameterOptimizer(config) { - epsilon = config.adagrad().epsilon(); - decay = config.adagrad().decay(); -} template void AdagradOptimizer::set_weight(const Tensor* p) { diff --git a/paddle/optimizer/adagrad_optimizer.h b/paddle/optimizer/adagrad_optimizer.h index 1ec438fd05a..a01040f30f9 100644 --- a/paddle/optimizer/adagrad_optimizer.h +++ b/paddle/optimizer/adagrad_optimizer.h @@ -9,7 +9,11 @@ namespace optimizer { template class AdagradOptimizer : public ParameterOptimizer { public: - AdagradOptimizer(const OptimizerConfig &config); + using ParameterOptimizer::parameter_; + using ParameterOptimizer::num_sample_passed; + using ParameterOptimizer::lr_policy; + AdagradOptimizer(double epsilon, double decay, BaseLr *lr) + : ParameterOptimizer(lr), epsilon(epsilon), decay(decay) {} ~AdagradOptimizer() { if (accum_gradient) delete accum_gradient; } diff --git a/paddle/optimizer/adam_optimizer.cc b/paddle/optimizer/adam_optimizer.cc index c2303c6545e..b2d2ddc596a 100644 --- a/paddle/optimizer/adam_optimizer.cc +++ b/paddle/optimizer/adam_optimizer.cc @@ -2,14 +2,6 @@ namespace paddle { namespace optimizer { -template -AdamOptimizer::AdamOptimizer(const ::paddle::OptimizerConfig &config) - : ParameterOptimizer(config) { - beta_1 = config.adam().beta_1(); - beta_2 = config.adam().beta_2(); - epsilon = config.adam().epsilon(); - decay = config.adam().decay(); -} template void AdamOptimizer::set_weight(const Tensor *p) { @@ -23,11 +15,16 @@ void AdamOptimizer::set_weight(const Tensor *p) { template void AdamOptimizer::update(const Tensor &gradient) { num_sample_passed += 1; - double learning_rate = lr_policy->get_learning_rate(); - for (size_t i = 0; i < parameter_.size(); ++i) { - accum_gradient[i] += gradient[i] * gradient[i]; - parameter_[i] += - learning_rate * (gradient[i] / std::sqrt(accum_gradient[i] + epsilon) + + double learning_rate = lr_policy->get_learning_rate(num_sample_passed); + double coef1 = 1.0 - std::pow(beta_1, num_sample_passed); + double coef2 = 1.0 - std::pow(beta_2, num_sample_passed); + learning_rate *= std::sqrt(coef2) / coef1; + for (size_t i = 0; i < parameter_->size(); ++i) { + momentums_[i] = beta_1 * momentums_[i] + (1.0 - beta_1) * gradient[i]; + velocitys_[i] = + beta_2 * velocitys_[i] + (1.0 - beta_2) * gradient[i] * gradient[i]; + parameter_[i] -= + learning_rate * (momentums_[i] / std::sqrt(velocitys_[i] + epsilon) + decay * parameter_[i]); } } diff --git a/paddle/optimizer/adam_optimizer.h b/paddle/optimizer/adam_optimizer.h index ceec18eb336..cf81bd70a64 100644 --- a/paddle/optimizer/adam_optimizer.h +++ b/paddle/optimizer/adam_optimizer.h @@ -9,8 +9,20 @@ namespace optimizer { template class AdamOptimizer : public ParameterOptimizer { public: - AdamOptimizer(const OptimizerConfig &config); - ~AdamOptimizer() {} + using ParameterOptimizer::parameter_; + using ParameterOptimizer::num_sample_passed; + using ParameterOptimizer::lr_policy; + AdamOptimizer( + double beta_1, double beta_2, double epsilon, double decay, BaseLr *lr) + : ParameterOptimizer(lr), + beta_1(beta_1), + beta_2(beta_2), + epsilon(epsilon), + decay(decay) {} + ~AdamOptimizer() { + if (momentums_) delete momentums_; + if (velocitys_) delete velocitys_; + } void update(const Tensor &gradient); void set_weight(const Tensor *p); T *get_weight() const; diff --git a/paddle/optimizer/lr_policy.h b/paddle/optimizer/lr_policy.h index 6977b68de7b..5aad87f45fb 100644 --- a/paddle/optimizer/lr_policy.h +++ b/paddle/optimizer/lr_policy.h @@ -1,19 +1,18 @@ #ifndef PADDLE_OPTIMIZER_LR_POLICY_H_ #define PADDLE_OPTIMIZER_LR_POLICY_H_ -#include "OptimizerConfig.ph.h" +#include "OptimizerConfig.pb.h" namespace paddle { namespace optimizer { class BaseLr { public: - LrPolicyBase(const OpitmizerConfig &config) { - learning_rate = config.lr_config().learning_rate(); - } + BaseLr(double lr) : learning_rate(lr) {} + virtual ~BaseLr() {} virtual double get_learning_rate(const uint64_t num_sample_passed) = 0; -private: +protected: double learning_rate; }; diff --git a/paddle/optimizer/optimizer.cc b/paddle/optimizer/optimizer.cc index e72881e5d0d..ff6558147ee 100644 --- a/paddle/optimizer/optimizer.cc +++ b/paddle/optimizer/optimizer.cc @@ -3,7 +3,7 @@ #include "parameter_optimizer.h" -template +template struct EnumToType {}; template @@ -11,15 +11,14 @@ struct TypeToEnum {}; #define MATCH_ENUM_TYPE(TYPE, ENUM) \ template <> \ - struct TypeToEnum { \ + struct TypeToEnum { \ static paddle_element_type v() { return ENUM; }; \ - static constexpr TYPE value = ENUM; -} -; -template <> -struct EnumToType { - typedef TYPE Type; -} + static constexpr TYPE value = ENUM; \ + }; \ + template <> \ + struct EnumToType { \ + typedef TYPE Type; \ + } MATCH_ENUM_TYPE(int32_t, PADDLE_ELEMENT_TYPE_INT32); MATCH_ENUM_TYPE(uint32_t, PADDLE_ELEMENT_TYPE_UINT32); @@ -27,11 +26,10 @@ MATCH_ENUM_TYPE(int64_t, PADDLE_ELEMENT_TYPE_INT64); MATCH_ENUM_TYPE(uint64_t, PADDLE_ELEMENT_TYPE_UINT64); MATCH_ENUM_TYPE(float, PADDLE_ELEMENT_TYPE_FLOAT32); MATCH_ENUM_TYPE(double, PADDLE_ELEMENT_TYPE_FLOAT64); - -struct paddle_optimizer { + struct paddle_optimizer { /*! \brief optmizer in C++ side */ - paddle::optimizer::ParameterOptimzier* impl; + paddle::optimizer::ParameterOptimizerBase* impl; }; paddle_optimizer* paddle_create_optimizer(const unsigned char* config_proto, @@ -48,7 +46,7 @@ int paddle_release_optimizer(paddle_optimizer* o) { } int paddle_update_parameter(paddle_optimizer* o, - paddle_element_type data_type, + const paddle_element_type data_type, const void* grad_buffer, int num_bytes) { auto type = EnumToType::Type; @@ -59,7 +57,7 @@ int paddle_update_parameter(paddle_optimizer* o, } int paddle_optimizer_set_weights(paddle_optimizer* o, - paddle_element_type data_type, + const paddle_element_type data_type, void* param_buffer, int num_bytes) { auto type = EnumToType::Type; diff --git a/paddle/optimizer/optimizer.h b/paddle/optimizer/optimizer.h index 0eba2e78118..a2c2b13405b 100644 --- a/paddle/optimizer/optimizer.h +++ b/paddle/optimizer/optimizer.h @@ -64,7 +64,7 @@ int paddle_release_optimizer(paddle_optimizer* o); * @return return exec status */ int paddle_update_parameter(paddle_optimizer* o, - paddle_element_type data_type, + const paddle_element_type data_type, const void* gradient, int num_bytes); @@ -76,7 +76,7 @@ int paddle_update_parameter(paddle_optimizer* o, * @return return exec status */ int paddle_optimizer_set_weights(paddle_optimizer* o, - paddle_element_type data_type, + const paddle_element_type data_type, void* param_buffer, int num_bytes); diff --git a/paddle/optimizer/parameter_optimizer.cc b/paddle/optimizer/parameter_optimizer.cc index c5e9e0acc30..4bd384d77da 100644 --- a/paddle/optimizer/parameter_optimizer.cc +++ b/paddle/optimizer/parameter_optimizer.cc @@ -1,6 +1,11 @@ -#include "parameter_optimizer.h" #include -#include "optimizer_factory.h" +#include "adadelta_optimizer.h" +#include "adagrad_optimizer.h" +#include "adam_optimizer.h" +#include "lr_policy.h" +#include "sgd_optimizer.h" + +#include "parameter_optimizer.h" namespace paddle { namespace optimizer { @@ -12,29 +17,40 @@ ParameterOptimizer *ParameterOptimizer::create( CHECK(config.ParseFromString(config_proto) == 0) << "error : optimizer config"; CHECK(config_valid(config) == 0) << "error : invalid optimizer config "; + + BaseLr *lr = nullptr; + switch (config.lr_policy()) { + case "ConstLr": + lr = new ConstLr(config.lr_config().learning_rate()); + break; + } ParameterOptimizer *opt = nullptr; switch (config.optimizer_name()) { case "SGD": - opt = new SGDOptimizer(config); + opt = new SGDOptimizer(config.sgd().momentum(), + config.sgd().decay(), + config.sgd().nesterov(), + lr); break; case "Adagrad": - opt = new AdagradOptimizer(config); + opt = new AdagradOptimizer( + config.adagrad().epsilon(), config.adagrad().decay(), lr); break; case "Adadelta": - opt = new AdadeltaOptimizer(config); + opt = new AdadeltaOptimizer(config.adadelta().rho(), + config.adadelta().epsilon(), + config.adadelta().decay(), + lr); break; case "Adam": - opt = new AdamOptimizer(config); + opt = new AdamOptimizer(config.adam().beta_1(), + config.adam().beta_2(), + config.adam().epsilon(), + config.adam().decay(), + lr); break; - default: - opt = new SGDOptimizer(config); } - switch (config.lr_policy()) { - case "ConstLr": - opt.lr_policy = new ConstLr(config); - break; - } return opt; } diff --git a/paddle/optimizer/parameter_optimizer.h b/paddle/optimizer/parameter_optimizer.h index d5914857af0..40994aa8670 100644 --- a/paddle/optimizer/parameter_optimizer.h +++ b/paddle/optimizer/parameter_optimizer.h @@ -11,6 +11,12 @@ namespace paddle { namespace optimizer { +class ParameterOptimizerBase { +private: + ParameterOptimizerBase(const ParameterOptimizerBase &) = delete; + ParameterOptimizerBase &operator=(const ParameterOptimizerBase &) = delete; +}; + template class ParameterOptimizer { public: @@ -18,18 +24,18 @@ public: * @brief update hook for algorithm need to traverse parameter more than * once. */ + // use config for pack trainig state ParameterOptimizer(const OptimizerConfig &config) : config_(config){}; + ParameterOptimizer(BaseLr *lr) : lr_policy(lr), num_sample_passed(0) {} + virtual ~ParameterOptimizer() { delete parameter_; }; + static ParameterOptimizer *create(const ::std::string &config_proto); - virtual void update(const Tensor &gradient) = 0; - virtual void destroy() = 0; + virtual void update(const Tensor &gradient) = 0; virtual T *get_weight() const; virtual void set_weight(const Tensor *parameter); - // package optimizer config proto in runtime for saving checkpoint - virtual char *get_config_proto(); - ~ParameterOptimzier() { delete parameter_; } -private: +public: bool config_valid(::std::string &config) const; OptimizerConfig config_; Tensor *parameter_; @@ -37,12 +43,6 @@ private: // learning rate policy BaseLr *lr_policy; uint64_t num_sample_passed; - - ParameterOptimizer(const ParameterOptimizer &) = delete; - ParameterOptimizer &operator=(const ParameterOptimizer &) = delete; - /** - * @brief indicate if use L1, L2 regularizer - */ }; } // namespace optimizer diff --git a/paddle/optimizer/regularizer.cc b/paddle/optimizer/regularizer.cc index dd21c20e711..5724511a827 100644 --- a/paddle/optimizer/regularizer.cc +++ b/paddle/optimizer/regularizer.cc @@ -19,6 +19,8 @@ Regularizer* Regularizer::create(const std::string& config) { template class L1Regularizer; template class L1Regularizer; +template class L2Regularizer; +template class L2Regularizer; } // namespace optimizer } // namespace paddle diff --git a/paddle/optimizer/sgd_optimizer.h b/paddle/optimizer/sgd_optimizer.h index 4e1d9669c96..5115825590d 100644 --- a/paddle/optimizer/sgd_optimizer.h +++ b/paddle/optimizer/sgd_optimizer.h @@ -9,8 +9,18 @@ namespace optimizer { template class SGDOptimizer : public ParameterOptimizer { public: - SGDOptimizer(const ::paddle::OptimizerConfig& config); - ~SGDOptimizer() { + using ParameterOptimizer::parameter_; + using ParameterOptimizer::num_sample_passed; + using ParameterOptimizer::lr_policy; + + SGDOptimizer(double m, + double d, + bool n, + double learning_rate, + uint64_t num_sample_passed, + BaseLr* lr) + : ParameterOptimizer(lr), momentum(m), decay(d), nesterov(n) {} + virtual ~SGDOptimizer() { // clear memory by Tensor library delete momentums_; } @@ -18,7 +28,6 @@ public: void set_weight(const Tensor* p); T* get_weight() const; - char* get_config_proto(); private: Tensor* momentums_; diff --git a/paddle/optimizer/sgd_optmizer.cc b/paddle/optimizer/sgd_optmizer.cc index ff23d46dc6f..cd1635fecdc 100644 --- a/paddle/optimizer/sgd_optmizer.cc +++ b/paddle/optimizer/sgd_optmizer.cc @@ -3,18 +3,10 @@ namespace paddle { namespace optimizer { -template -SGDOptimizer::SGDOptimizer(const ::paddle::OptimizerConfig &config) - : ParameterOptimizer(config) { - momentum = config.sgd().momentum(); - decay = config.sgd().decay(); - nesterov = config.sgd().nesterov(); -} - template void SGDOptimizer::set_weight(const Tensor *p) { // ParameterOptimizer::set_weight(p); - size_t size = p->width(); + size_t size = p->size(); // TODO: fix it with align aware allocator bind to Tensor if (momentum != 0.0) { T *ptr = new T[size]; @@ -27,7 +19,7 @@ void SGDOptimizer::update(const Tensor &gradient) { num_sample_passed += 1; double learning_rate = lr_policy->get_learning_rate(num_sample_passed); double velocity = 0.0; - for (size_t i = 0; i < parameter_.size(); ++i) { + Tensor &for (size_t i = 0; i < parameter_->size(); ++i) { if (momentum == 0.0) { velocity = -learning_rate * gradient[i] - learning_rate * decay * parameter_[i]; @@ -44,15 +36,6 @@ void SGDOptimizer::update(const Tensor &gradient) { } } -template -char *SGDOptimizer::get_config_proto() { - ParameterOptimizer::get_config_proto(); - config.set_learning_rate(learning_rate); - config.set_decay(decay); - config.set_nesterov(nesterov); - return config.SerializeAsString().c_str(); -} - template class SGDOptimizer; template class SGDOptimizer; diff --git a/proto/OptimizerConfig.proto b/proto/OptimizerConfig.proto index c1080f4e168..d4242676507 100644 --- a/proto/OptimizerConfig.proto +++ b/proto/OptimizerConfig.proto @@ -12,7 +12,7 @@ message SGDConfig { optional double momentum = 21 [default = 0.0]; optional double decay = 23 [default = 0.0]; optional bool nesterov =24 [default = false]; - +} message AdadeltaConfig { @@ -95,5 +95,4 @@ message OptimizerConfig { // common config of optimizer optional double clipnorm = 101; optional double clipvalue = 102; - } -- GitLab From 2799b0ec50de669709d7e95ae82b7512426d5387 Mon Sep 17 00:00:00 2001 From: "wanghaoshuang@baidu.com" Date: Wed, 24 May 2017 00:14:07 +0800 Subject: [PATCH 0540/3256] Add flowers dataset for image classification model --- python/paddle/v2/dataset/flowers.py | 255 ++++++++++++++++++ .../paddle/v2/dataset/tests/flowers_test.py | 51 ++++ python/paddle/v2/image.py | 36 ++- python/paddle/v2/reader/decorator.py | 75 +++++- 4 files changed, 409 insertions(+), 8 deletions(-) create mode 100644 python/paddle/v2/dataset/flowers.py create mode 100644 python/paddle/v2/dataset/tests/flowers_test.py diff --git a/python/paddle/v2/dataset/flowers.py b/python/paddle/v2/dataset/flowers.py new file mode 100644 index 00000000000..3d38b5dab97 --- /dev/null +++ b/python/paddle/v2/dataset/flowers.py @@ -0,0 +1,255 @@ +# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +CIFAR dataset. + +This module will download dataset from +http://www.robots.ox.ac.uk/~vgg/data/flowers/102/index.html +and parse train/test set intopaddle reader creators. + +This set contains images of flowers belonging to 102 different categories. +The images were acquired by searching the web and taking pictures. There are a +minimum of 40 images for each category. + +The database was used in: + +Nilsback, M-E. and Zisserman, A. Automated flower classification over a large + number of classes.Proceedings of the Indian Conference on Computer Vision, +Graphics and Image Processing (2008) +http://www.robots.ox.ac.uk/~vgg/publications/papers/nilsback08.{pdf,ps.gz}. + +""" +import cPickle +import itertools +from common import download +import tarfile +import scipy.io as scio +from image import * +import os +from multiprocessing import Process +from multiprocessing import Pool +from multiprocessing import cpu_count +import numpy as np +import paddle.v2 as paddle +__all__ = ['train', 'test', 'valid'] + +DATA_URL = 'http://www.robots.ox.ac.uk/~vgg/data/flowers/102/102flowers.tgz' +LABEL_URL = 'http://www.robots.ox.ac.uk/~vgg/data/flowers/102/imagelabels.mat' +SETID_URL = 'http://www.robots.ox.ac.uk/~vgg/data/flowers/102/setid.mat' +DATA_MD5 = '52808999861908f626f3c1f4e79d11fa' +LABEL_MD5 = 'e0620be6f572b9609742df49c70aed4d' +SETID_MD5 = 'a5357ecc9cb78c4bef273ce3793fc85c' + + +def extract_file(tarFile): + ''' + Extract tar file to tmp dir. + + Example usage: + + .. code-block:: python + tmp = extract_file("/home/work/test.tar.gz") + + :param tarFile: target tar file + :type tarFile: string + :return: extracted dir. For example: + '/home/work/test/' while input is '/home/work/test.tar.gz' + :rtype: string + ''' + base_dir = os.path.dirname(tarFile) + base_name = os.path.basename(tarFile) + if '.' in base_name: + base_name = base_name.split('.', 1)[0] + out_path = '/'.join([base_dir, base_name]) + if not os.path.exists(out_path): + df = tarfile.open(tarFile, mode='r') + df.extractall(path=out_path) + df.close() + return out_path + + +def default_mapper(sample): + ''' + map image bytes data to type needed by model input layer + ''' + img, label = sample + img = paddle.image.load_image_bytes(img) + img = paddle.image.simple_transform(img, 256, 224, True) + return img.flatten().astype('float32'), label + + +def reader_creator(data_file, + label_file, + setid_file, + flag, + mapper=default_mapper): + ''' + 1. extract 102flowers.tgz to 102flowers/ + 2. merge images into batch files in 102flowers_batch/ + 3. get a reader to read sample from batch file + + :param data_file: downloaded data file + :type data_file: string + :param label_file: downloaded label file + :type label_file: string + :param setid_file: downloaded setid file containing information + about how to split dataset + :type setid_file: string + :param flag: data set name (tstid|trnid|valid) + :type flag: string + :param mapper: a function to map image bytes data to type + needed by model input layer + :type mapper: callable + :return: data reader + :rtype: callable + ''' + base_dir = os.path.dirname(data_file) + tmp_dir = extract_file(data_file) + file_list = create_batch(tmp_dir, label_file, setid_file, flag) + + def reader(): + for file in open(file_list): + file = file.strip() + batch = None + with open(file, 'r') as f: + batch = cPickle.load(f) + data = batch['data'] + labels = batch['label'] + for sample, label in itertools.izip(data, batch['label']): + yield sample, int(label) + + return paddle.reader.xmap(mapper, reader, cpu_count(), 1024 * 8) + + +def create_batch(data_dir, + label_file, + setid_file, + flag, + numPerBatch=1024, + nThread=16): + batch_dir = data_dir + "_batch" + labels = scio.loadmat(label_file)['labels'][0] + indexes = scio.loadmat(setid_file)[flag][0] + count = len(indexes) + out_path = "%s/%s" % (batch_dir, flag) + meta_file = "%s/%s.txt" % (batch_dir, flag) + + if os.path.exists(out_path): + return meta_file + else: + os.makedirs(out_path) + + def batch(file_out, start, end): + data = [] + labellist = [] + for index in indexes[start:end]: + img_name = "%s/jpg/image_%05d.jpg" % (data_dir, index) + with open(img_name, 'r') as f: + data.append(f.read()) + labellist.append(labels[index - 1]) + output = {} + output['label'] = labellist + output['data'] = data + cPickle.dump( + output, open(file_out, 'w'), protocol=cPickle.HIGHEST_PROTOCOL) + + cur_id = 0 + file_id = 0 + while cur_id < count: + thread = [] + for i in xrange(nThread): + end_id = min(cur_id + numPerBatch, count) + batch_file_name = "%s/batch_%05d" % (out_path, file_id) + w = Process(target=batch, args=(batch_file_name, cur_id, end_id)) + w.daemon = True + thread.append(w) + cur_id = end_id + file_id += 1 + if cur_id == count: + break + for t in thread: + t.start() + for t in thread: + t.join() + with open(meta_file, 'a') as meta: + for file in os.listdir(out_path): + meta.write(os.path.abspath("%s/%s" % (out_path, file)) + "\n") + return meta_file + + +def train(mapper=default_mapper): + ''' + Create flowers training set reader. + It returns a reader, each sample in the reader is + image pixels in [0, 1] and label in [1, 102] + translated from original color image by steps: + 1. resize to 256*256 + 2. random crop to 224*224 + 3. flatten + :param mapper: a function to map sample. + :type mapper: callable + :return: train data reader + :rtype: callable + ''' + return reader_creator( + download(DATA_URL, 'flowers', DATA_MD5), + download(LABEL_URL, 'flowers', LABEL_MD5), + download(SETID_URL, 'flowers', SETID_MD5), 'trnid') + + +def test(mapper=default_mapper): + ''' + Create flowers test set reader. + It returns a reader, each sample in the reader is + image pixels in [0, 1] and label in [1, 102] + translated from original color image by steps: + 1. resize to 256*256 + 2. random crop to 224*224 + 3. flatten + :param mapper: a function to map sample. + :type mapper: callable + :return: test data reader + :rtype: callable + ''' + return reader_creator( + download(DATA_URL, 'flowers', DATA_MD5), + download(LABEL_URL, 'flowers', LABEL_MD5), + download(SETID_URL, 'flowers', SETID_MD5), 'tstid') + + +def valid(): + ''' + Create flowers validation set reader. + It returns a reader, each sample in the reader is + image pixels in [0, 1] and label in [1, 102] + translated from original color image by steps: + 1. resize to 256*256 + 2. random crop to 224*224 + 3. flatten + ''' + return reader_creator( + download(DATA_URL, 'flowers', DATA_MD5), + download(LABEL_URL, 'flowers', LABEL_MD5), + download(SETID_URL, 'flowers', SETID_MD5), 'valid') + + +def fetch(): + download(DATA_URL, 'flowers', DATA_MD5) + download(LABEL_URL, 'flowers', LABEL_MD5) + download(SETID_URL, 'flowers', SETID_MD5) + + +if __name__ == '__main__': + for i in test()(): + pass diff --git a/python/paddle/v2/dataset/tests/flowers_test.py b/python/paddle/v2/dataset/tests/flowers_test.py new file mode 100644 index 00000000000..cc0626f4fea --- /dev/null +++ b/python/paddle/v2/dataset/tests/flowers_test.py @@ -0,0 +1,51 @@ +# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import paddle.v2.dataset.flowers +import unittest + + +class TestFlowers(unittest.TestCase): + def check_reader(self, reader): + sum = 0 + label = 0 + size = 224 * 224 * 3 + for l in reader(): + self.assertEqual(l[0].size, size) + if l[1] > label: + label = l[1] + sum += 1 + return sum, label + + def test_train(self): + instances, max_label_value = self.check_reader( + paddle.v2.dataset.flowers.train()) + self.assertEqual(instances, 1020) + self.assertEqual(max_label_value, 102) + + def test_test(self): + instances, max_label_value = self.check_reader( + paddle.v2.dataset.flowers.test()) + self.assertEqual(instances, 6149) + self.assertEqual(max_label_value, 102) + + def test_valid(self): + instances, max_label_value = self.check_reader( + paddle.v2.dataset.flowers.valid()) + self.assertEqual(instances, 1020) + self.assertEqual(max_label_value, 102) + + +if __name__ == '__main__': + unittest.main() diff --git a/python/paddle/v2/image.py b/python/paddle/v2/image.py index 85ad6984ba0..cb5725de686 100644 --- a/python/paddle/v2/image.py +++ b/python/paddle/v2/image.py @@ -1,14 +1,14 @@ import numpy as np try: import cv2 -except: - print( - "import cv2 error, please install opencv-python: pip install opencv-python" - ) +except ImportError: + cv2 = None + +from cv2 import resize __all__ = [ - "load_image", "resize_short", "to_chw", "center_crop", "random_crop", - "left_right_flip", "simple_transform", "load_and_transform" + "load_image_bytes", "load_image", "resize_short", "to_chw", "center_crop", + "random_crop", "left_right_flip", "simple_transform", "load_and_transform" ] """ This file contains some common interfaces for image preprocess. @@ -28,6 +28,28 @@ the image layout as follows. """ +def load_image_bytes(bytes, is_color=True): + """ + Load an color or gray image from bytes array. + + Example usage: + + .. code-block:: python + with open('cat.jpg') as f: + im = load_image(f.read()) + + :param bytes: the input image bytes array. + :type file: str + :param is_color: If set is_color True, it will load and + return a color image. Otherwise, it will + load and return a gray image. + """ + flag = 1 if is_color else 0 + file_bytes = np.asarray(bytearray(bytes), dtype=np.uint8) + img = cv2.imdecode(file_bytes, flag) + return img + + def load_image(file, is_color=True): """ Load an color or gray image from the file path. @@ -76,7 +98,7 @@ def resize_short(im, size): h_new = size * h / w else: w_new = size * w / h - im = cv2.resize(im, (h_new, w_new), interpolation=cv2.INTER_CUBIC) + im = resize(im, (h_new, w_new), interpolation=cv2.INTER_CUBIC) return im diff --git a/python/paddle/v2/reader/decorator.py b/python/paddle/v2/reader/decorator.py index 104ce9a0411..f06792314fd 100644 --- a/python/paddle/v2/reader/decorator.py +++ b/python/paddle/v2/reader/decorator.py @@ -14,13 +14,15 @@ __all__ = [ 'map_readers', 'buffered', 'compose', 'chain', 'shuffle', - 'ComposeNotAligned', 'firstn' + 'ComposeNotAligned', 'firstn', 'xmap' ] import itertools import random from Queue import Queue from threading import Thread +from multiprocessing import Queue as MQueue +from multiprocessing import Process def map_readers(func, *readers): @@ -224,3 +226,74 @@ def firstn(reader, n): yield item return firstn_reader + + +class XmapEndSignal(): + pass + + +def xmap(mapper, reader, process_num, buffer_size): + """ + Use multiprocess to map samples from reader by a mapper defined by user. + And this function contains a buffered decorator. + :param mapper: a function to map sample. + :type mapper: callable + :param reader: the data reader to read from + :type reader: callable + :param process_num: process number to handle original sample + :type process_num: int + :param buffer_size: max buffer size + :type buffer_size: int + :return: the decarated reader + :rtype: callable + """ + end = XmapEndSignal() + in_queue = MQueue(buffer_size) + out_queue = MQueue(buffer_size) + + # define a worker to read samples from reader to in_queue + def read_worker(reader, in_queue): + for i in reader(): + in_queue.put(i) + in_queue.put(end) + + # start a read worker in a thread + t = Thread(target=read_worker, args=(reader, in_queue)) + t.daemon = True + t.start() + + # define a worker to handle samples from in_queue by mapper + # and put mapped samples into out_queue + def handle_worker(in_queue, out_queue, mapper): + sample = in_queue.get() + while not isinstance(sample, XmapEndSignal): + r = mapper(sample) + out_queue.put(r) + sample = in_queue.get() + in_queue.put(end) + out_queue.put(end) + + # start several handle_workers + workers = [] + for i in xrange(process_num): + worker = Process( + target=handle_worker, args=(in_queue, out_queue, mapper)) + worker.daemon = True + workers.append(worker) + for w in workers: + w.start() + + def xreader(): + sample = out_queue.get() + while not isinstance(sample, XmapEndSignal): + yield sample + sample = out_queue.get() + finish = 1 + while finish < process_num: + sample = out_queue.get() + if isinstance(sample, XmapEndSignal): + finish += 1 + else: + yield sample + + return xreader -- GitLab From e62a4d7abe5287fd5fdc3464ef81a5c682a49589 Mon Sep 17 00:00:00 2001 From: wanghaoshuang Date: Fri, 2 Jun 2017 10:56:15 +0800 Subject: [PATCH 0541/3256] xmap: change multiprocess to multithread. images reader: read the data without untarring the tarball file. image.py: move batch function from reader to image.py --- python/paddle/v2/dataset/flowers.py | 150 +++++++-------------------- python/paddle/v2/image.py | 70 ++++++++++++- python/paddle/v2/reader/decorator.py | 8 +- 3 files changed, 110 insertions(+), 118 deletions(-) diff --git a/python/paddle/v2/dataset/flowers.py b/python/paddle/v2/dataset/flowers.py index 3d38b5dab97..d9a39b11df3 100644 --- a/python/paddle/v2/dataset/flowers.py +++ b/python/paddle/v2/dataset/flowers.py @@ -12,8 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. """ -CIFAR dataset. - This module will download dataset from http://www.robots.ox.ac.uk/~vgg/data/flowers/102/index.html and parse train/test set intopaddle reader creators. @@ -35,13 +33,11 @@ import itertools from common import download import tarfile import scipy.io as scio -from image import * +from paddle.v2.image import * import os -from multiprocessing import Process -from multiprocessing import Pool -from multiprocessing import cpu_count import numpy as np import paddle.v2 as paddle +from multiprocessing import cpu_count __all__ = ['train', 'test', 'valid'] DATA_URL = 'http://www.robots.ox.ac.uk/~vgg/data/flowers/102/102flowers.tgz' @@ -52,33 +48,6 @@ LABEL_MD5 = 'e0620be6f572b9609742df49c70aed4d' SETID_MD5 = 'a5357ecc9cb78c4bef273ce3793fc85c' -def extract_file(tarFile): - ''' - Extract tar file to tmp dir. - - Example usage: - - .. code-block:: python - tmp = extract_file("/home/work/test.tar.gz") - - :param tarFile: target tar file - :type tarFile: string - :return: extracted dir. For example: - '/home/work/test/' while input is '/home/work/test.tar.gz' - :rtype: string - ''' - base_dir = os.path.dirname(tarFile) - base_name = os.path.basename(tarFile) - if '.' in base_name: - base_name = base_name.split('.', 1)[0] - out_path = '/'.join([base_dir, base_name]) - if not os.path.exists(out_path): - df = tarfile.open(tarFile, mode='r') - df.extractall(path=out_path) - df.close() - return out_path - - def default_mapper(sample): ''' map image bytes data to type needed by model input layer @@ -92,12 +61,13 @@ def default_mapper(sample): def reader_creator(data_file, label_file, setid_file, - flag, - mapper=default_mapper): + dataset_name, + mapper=default_mapper, + buffered_size=1024): ''' - 1. extract 102flowers.tgz to 102flowers/ - 2. merge images into batch files in 102flowers_batch/ - 3. get a reader to read sample from batch file + 1. read images from tar file and + merge images into batch files in 102flowers.tgz_batch/ + 2. get a reader to read sample from batch file :param data_file: downloaded data file :type data_file: string @@ -106,17 +76,23 @@ def reader_creator(data_file, :param setid_file: downloaded setid file containing information about how to split dataset :type setid_file: string - :param flag: data set name (tstid|trnid|valid) - :type flag: string + :param dataset_name: data set name (tstid|trnid|valid) + :type dataset_name: string :param mapper: a function to map image bytes data to type needed by model input layer :type mapper: callable + :param buffered_size: the size of buffer used to process images + :type buffered_size: int :return: data reader :rtype: callable ''' - base_dir = os.path.dirname(data_file) - tmp_dir = extract_file(data_file) - file_list = create_batch(tmp_dir, label_file, setid_file, flag) + labels = scio.loadmat(label_file)['labels'][0] + indexes = scio.loadmat(setid_file)[dataset_name][0] + img2label = {} + for i in indexes: + img = "jpg/image_%05d.jpg" % i + img2label[img] = labels[i - 1] + file_list = batch_images_from_tar(data_file, dataset_name, img2label) def reader(): for file in open(file_list): @@ -129,66 +105,10 @@ def reader_creator(data_file, for sample, label in itertools.izip(data, batch['label']): yield sample, int(label) - return paddle.reader.xmap(mapper, reader, cpu_count(), 1024 * 8) + return paddle.reader.xmap(mapper, reader, cpu_count(), buffered_size) -def create_batch(data_dir, - label_file, - setid_file, - flag, - numPerBatch=1024, - nThread=16): - batch_dir = data_dir + "_batch" - labels = scio.loadmat(label_file)['labels'][0] - indexes = scio.loadmat(setid_file)[flag][0] - count = len(indexes) - out_path = "%s/%s" % (batch_dir, flag) - meta_file = "%s/%s.txt" % (batch_dir, flag) - - if os.path.exists(out_path): - return meta_file - else: - os.makedirs(out_path) - - def batch(file_out, start, end): - data = [] - labellist = [] - for index in indexes[start:end]: - img_name = "%s/jpg/image_%05d.jpg" % (data_dir, index) - with open(img_name, 'r') as f: - data.append(f.read()) - labellist.append(labels[index - 1]) - output = {} - output['label'] = labellist - output['data'] = data - cPickle.dump( - output, open(file_out, 'w'), protocol=cPickle.HIGHEST_PROTOCOL) - - cur_id = 0 - file_id = 0 - while cur_id < count: - thread = [] - for i in xrange(nThread): - end_id = min(cur_id + numPerBatch, count) - batch_file_name = "%s/batch_%05d" % (out_path, file_id) - w = Process(target=batch, args=(batch_file_name, cur_id, end_id)) - w.daemon = True - thread.append(w) - cur_id = end_id - file_id += 1 - if cur_id == count: - break - for t in thread: - t.start() - for t in thread: - t.join() - with open(meta_file, 'a') as meta: - for file in os.listdir(out_path): - meta.write(os.path.abspath("%s/%s" % (out_path, file)) + "\n") - return meta_file - - -def train(mapper=default_mapper): +def train(mapper=default_mapper, buffered_size=1024): ''' Create flowers training set reader. It returns a reader, each sample in the reader is @@ -199,16 +119,19 @@ def train(mapper=default_mapper): 3. flatten :param mapper: a function to map sample. :type mapper: callable + :param buffered_size: the size of buffer used to process images + :type buffered_size: int :return: train data reader :rtype: callable ''' return reader_creator( download(DATA_URL, 'flowers', DATA_MD5), download(LABEL_URL, 'flowers', LABEL_MD5), - download(SETID_URL, 'flowers', SETID_MD5), 'trnid') + download(SETID_URL, 'flowers', SETID_MD5), 'trnid', mapper, + buffered_size) -def test(mapper=default_mapper): +def test(mapper=default_mapper, buffered_size=1024): ''' Create flowers test set reader. It returns a reader, each sample in the reader is @@ -219,16 +142,19 @@ def test(mapper=default_mapper): 3. flatten :param mapper: a function to map sample. :type mapper: callable + :param buffered_size: the size of buffer used to process images + :type buffered_size: int :return: test data reader :rtype: callable ''' return reader_creator( download(DATA_URL, 'flowers', DATA_MD5), download(LABEL_URL, 'flowers', LABEL_MD5), - download(SETID_URL, 'flowers', SETID_MD5), 'tstid') + download(SETID_URL, 'flowers', SETID_MD5), 'tstid', mapper, + buffered_size) -def valid(): +def valid(mapper=default_mapper, buffered_size=1024): ''' Create flowers validation set reader. It returns a reader, each sample in the reader is @@ -237,19 +163,21 @@ def valid(): 1. resize to 256*256 2. random crop to 224*224 3. flatten + :param mapper: a function to map sample. + :type mapper: callable + :param buffered_size: the size of buffer used to process images + :type buffered_size: int + :return: test data reader + :rtype: callable ''' return reader_creator( download(DATA_URL, 'flowers', DATA_MD5), download(LABEL_URL, 'flowers', LABEL_MD5), - download(SETID_URL, 'flowers', SETID_MD5), 'valid') + download(SETID_URL, 'flowers', SETID_MD5), 'valid', mapper, + buffered_size) def fetch(): download(DATA_URL, 'flowers', DATA_MD5) download(LABEL_URL, 'flowers', LABEL_MD5) download(SETID_URL, 'flowers', SETID_MD5) - - -if __name__ == '__main__': - for i in test()(): - pass diff --git a/python/paddle/v2/image.py b/python/paddle/v2/image.py index cb5725de686..56031e8734b 100644 --- a/python/paddle/v2/image.py +++ b/python/paddle/v2/image.py @@ -5,10 +5,14 @@ except ImportError: cv2 = None from cv2 import resize +import os +import tarfile +import cPickle __all__ = [ "load_image_bytes", "load_image", "resize_short", "to_chw", "center_crop", - "random_crop", "left_right_flip", "simple_transform", "load_and_transform" + "random_crop", "left_right_flip", "simple_transform", "load_and_transform", + "batch_images_from_tar" ] """ This file contains some common interfaces for image preprocess. @@ -28,6 +32,68 @@ the image layout as follows. """ +def batch_images_from_tar(data_file, + dataset_name, + img2label, + num_per_batch=1024): + """ + Read images from tar file and batch them into batch file. + param data_file: path of image tar file + type data_file: string + param dataset_name: 'train','test' or 'valid' + type dataset_name: string + param img2label: a dic with image file name as key + and image's label as value + type img2label: dic + param num_per_batch: image number per batch file + type num_per_batch: int + return: path of list file containing paths of batch file + rtype: string + """ + batch_dir = data_file + "_batch" + out_path = "%s/%s" % (batch_dir, dataset_name) + meta_file = "%s/%s.txt" % (batch_dir, dataset_name) + + if os.path.exists(out_path): + return meta_file + else: + os.makedirs(out_path) + + tf = tarfile.open(data_file) + mems = tf.getmembers() + data = [] + labels = [] + file_id = 0 + for mem in mems: + if mem.name in img2label: + data.append(tf.extractfile(mem).read()) + labels.append(img2label[mem.name]) + if len(data) == num_per_batch: + output = {} + output['label'] = labels + output['data'] = data + cPickle.dump( + output, + open('%s/batch_%d' % (out_path, file_id), 'w'), + protocol=cPickle.HIGHEST_PROTOCOL) + file_id += 1 + data = [] + labels = [] + if len(data) > 0: + output = {} + output['label'] = labels + output['data'] = data + cPickle.dump( + output, + open('%s/batch_%d' % (out_path, file_id), 'w'), + protocol=cPickle.HIGHEST_PROTOCOL) + + with open(meta_file, 'a') as meta: + for file in os.listdir(out_path): + meta.write(os.path.abspath("%s/%s" % (out_path, file)) + "\n") + return meta_file + + def load_image_bytes(bytes, is_color=True): """ Load an color or gray image from bytes array. @@ -36,7 +102,7 @@ def load_image_bytes(bytes, is_color=True): .. code-block:: python with open('cat.jpg') as f: - im = load_image(f.read()) + im = load_image_bytes(f.read()) :param bytes: the input image bytes array. :type file: str diff --git a/python/paddle/v2/reader/decorator.py b/python/paddle/v2/reader/decorator.py index f06792314fd..1b5df21b3de 100644 --- a/python/paddle/v2/reader/decorator.py +++ b/python/paddle/v2/reader/decorator.py @@ -21,8 +21,6 @@ import itertools import random from Queue import Queue from threading import Thread -from multiprocessing import Queue as MQueue -from multiprocessing import Process def map_readers(func, *readers): @@ -248,8 +246,8 @@ def xmap(mapper, reader, process_num, buffer_size): :rtype: callable """ end = XmapEndSignal() - in_queue = MQueue(buffer_size) - out_queue = MQueue(buffer_size) + in_queue = Queue(buffer_size) + out_queue = Queue(buffer_size) # define a worker to read samples from reader to in_queue def read_worker(reader, in_queue): @@ -276,7 +274,7 @@ def xmap(mapper, reader, process_num, buffer_size): # start several handle_workers workers = [] for i in xrange(process_num): - worker = Process( + worker = Thread( target=handle_worker, args=(in_queue, out_queue, mapper)) worker.daemon = True workers.append(worker) -- GitLab From 990b7d7bc9e7f4963d4555ab2dc1dce40ae28bb3 Mon Sep 17 00:00:00 2001 From: wanghaoshuang Date: Mon, 5 Jun 2017 16:40:12 +0800 Subject: [PATCH 0542/3256] rename xmap to xmap_readers and remove 'from cv2 import resize' in image.py --- python/paddle/v2/dataset/flowers.py | 3 ++- python/paddle/v2/image.py | 4 +--- python/paddle/v2/reader/decorator.py | 4 ++-- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/python/paddle/v2/dataset/flowers.py b/python/paddle/v2/dataset/flowers.py index d9a39b11df3..07c13cf719a 100644 --- a/python/paddle/v2/dataset/flowers.py +++ b/python/paddle/v2/dataset/flowers.py @@ -105,7 +105,8 @@ def reader_creator(data_file, for sample, label in itertools.izip(data, batch['label']): yield sample, int(label) - return paddle.reader.xmap(mapper, reader, cpu_count(), buffered_size) + return paddle.reader.xmap_readers(mapper, reader, + cpu_count(), buffered_size) def train(mapper=default_mapper, buffered_size=1024): diff --git a/python/paddle/v2/image.py b/python/paddle/v2/image.py index 56031e8734b..0d648e9ae69 100644 --- a/python/paddle/v2/image.py +++ b/python/paddle/v2/image.py @@ -3,8 +3,6 @@ try: import cv2 except ImportError: cv2 = None - -from cv2 import resize import os import tarfile import cPickle @@ -164,7 +162,7 @@ def resize_short(im, size): h_new = size * h / w else: w_new = size * w / h - im = resize(im, (h_new, w_new), interpolation=cv2.INTER_CUBIC) + im = cv2.resize(im, (h_new, w_new), interpolation=cv2.INTER_CUBIC) return im diff --git a/python/paddle/v2/reader/decorator.py b/python/paddle/v2/reader/decorator.py index 1b5df21b3de..c76faa596c9 100644 --- a/python/paddle/v2/reader/decorator.py +++ b/python/paddle/v2/reader/decorator.py @@ -14,7 +14,7 @@ __all__ = [ 'map_readers', 'buffered', 'compose', 'chain', 'shuffle', - 'ComposeNotAligned', 'firstn', 'xmap' + 'ComposeNotAligned', 'firstn', 'xmap_readers' ] import itertools @@ -230,7 +230,7 @@ class XmapEndSignal(): pass -def xmap(mapper, reader, process_num, buffer_size): +def xmap_readers(mapper, reader, process_num, buffer_size): """ Use multiprocess to map samples from reader by a mapper defined by user. And this function contains a buffered decorator. -- GitLab From afbe556e565a880c40a60ea90e21debd4cb139bd Mon Sep 17 00:00:00 2001 From: hedaoyuan Date: Mon, 5 Jun 2017 17:08:34 +0800 Subject: [PATCH 0543/3256] Modify the arguments description of ConvFunctionBase. And add the definition of backward input and backward filter function. --- paddle/function/ConvOp.h | 70 ++++++++++++++++++++---------- paddle/function/GemmConvOp.cpp | 75 +++++++++++++++++++++++++++------ paddle/function/NaiveConvOp.cpp | 7 ++- 3 files changed, 114 insertions(+), 38 deletions(-) diff --git a/paddle/function/ConvOp.h b/paddle/function/ConvOp.h index 14c20b74f20..8f2c0c4cb8a 100644 --- a/paddle/function/ConvOp.h +++ b/paddle/function/ConvOp.h @@ -19,20 +19,36 @@ limitations under the License. */ namespace paddle { /* - * Function Arguments: + * \brief Based on the ConvFunctionBase class, the forward calculation, + * backward input calculation and backward filter calculation + * of convolution operations can be implemented. * - * \param inputs[0] Input image data, is NCHW format, where N is batch size, - * C is the number of channels, H and W is the height and - * width of input image. - * \param inputs[1] Filter data, is MCHW, where M is the number of output - * channels, C is the number of input channels, H and W - * is height and width of filter. - * \param outputs[0] Output image data, is NCHW format, where N is batch size, - * C is the number of channels, H and W is the height and - * width of output image. + * Arguments of forward and backward calculation: + * 1. Forward calculation of convolution. + * inputs = {INPUT, FILTER}, outputs = {OUTPUT} + * The first and second input arguments are input image and filter data. + * The output argument is output image. * - * \note Implemented based on the ConvFunctionBase class only supports - * input data in the NCHW format. + * 2. Backward input calculation of convolution. + * inputs = {OUTPUT_GRAD, FILTER}, outputs = {INPUT_GRAD} + * The first and second input arguments are output grad image + * and filter data. + * The output argument is input grad image. + * + * 3. Backward filter calculation of convolution. + * inputs = {OUTPUT_GRAD, INPUT}, outputs = {FILTER_GRAD} + * The first and second input arguments are output grad image + * and input image. + * The output argument is filter grad. + * + * Arguments format of input, filter and output: + * 1. Input image, output image, input image gradient, output image gradient + * are all NCHW format. Where N is batch size, C is the number of channels, + * H and W is the height and width of image or image gradient. + * + * 2. The format of the filter data is MCHW, where M is the number of + * output image channels, C is the number of input image channels, + * H and W is height and width of filter. */ class ConvFunctionBase : public FunctionBase { public: @@ -49,17 +65,25 @@ public: virtual void calc(const BufferArgs& inputs, const BufferArgs& outputs) {} - void check(const BufferArgs& inputs, const BufferArgs& outputs) override { - CHECK_EQ(numInputs_, inputs.size()); - CHECK_EQ(numOutputs_, outputs.size()); - - CHECK_EQ(inputs[0].shape().ndims(), (size_t)4); - CHECK_EQ(inputs[1].shape().ndims(), (size_t)4); - CHECK_EQ(outputs[0].shape().ndims(), (size_t)4); - - CHECK(inputs[0].shape()[0] == outputs[0].shape()[0]); - CHECK(inputs[0].shape()[1] / groups_ == inputs[1].shape()[1]); - CHECK(outputs[0].shape()[1] == inputs[1].shape()[0]); + // input can be INPUT and INPUT_GRAD + // filter can be FILTER and FILTER_GRAD + // output can be OUTPUT and OUTPUT_GRAD + void check(const TensorShape& input, + const TensorShape& filter, + const TensorShape& output) { + // inputs and outputs arguments should be 4-dimensional. + CHECK_EQ(input.ndims(), (size_t)4); + CHECK_EQ(filter.ndims(), (size_t)4); + CHECK_EQ(output.ndims(), (size_t)4); + + // The batchSize of the input needs to be equal to + // the batchSize of the output. + CHECK_EQ(input[0], output[0]); + + // The input and output channel dimensions are the second and first + // dimensions of the filter shape. + CHECK_EQ(input[1] / groups_, filter[1]); + CHECK_EQ(output[1], filter[0]); } protected: diff --git a/paddle/function/GemmConvOp.cpp b/paddle/function/GemmConvOp.cpp index 78aa8f14f34..109ed20ab06 100644 --- a/paddle/function/GemmConvOp.cpp +++ b/paddle/function/GemmConvOp.cpp @@ -68,17 +68,7 @@ public: }; /* - * Function Arguments: - * - * \param inputs[0] Input image data, is NCHW format, where N is batch size, - * C is the number of channels, H and W is the height and - * width of input image. - * \param inputs[1] Filter data, is MCHW, where M is the number of output - * channels, C is the number of input channels, H and W - * is height and width of filter. - * \param outputs[0] Output image data, is NCHW format, where N is batch size, - * C is the number of channels, H and W is the height and - * width of output image. + * \brief Forward calculation of convolution. */ template class GemmConvFunction : public ConvFunctionBase { @@ -88,8 +78,21 @@ public: } void calc(const BufferArgs& inputs, const BufferArgs& outputs) override { - check(inputs, outputs); - CHECK_EQ(outputs[0].getArgType(), ASSIGN_TO); + CHECK_EQ(numInputs_, inputs.size()); + CHECK_EQ(numOutputs_, outputs.size()); + // TODO(hedaoyuan): Need to define some index macros, + // to avoid useing 0 and 1. + const TensorShape& input = inputs[0].shape(); + const TensorShape& filter = inputs[1].shape(); + const TensorShape& output = outputs[0].shape(); + check(input, filter, output); + + real beta; + if (outputs[0].getArgType() == ADD_TO) { + beta = 1.0; + } else { + beta = 0.0; + } size_t batchSize = inputs[0].shape()[0]; size_t inputChannels = inputs[0].shape()[1]; @@ -143,7 +146,7 @@ public: K, colData, N, - 0.0f, + beta, outputData + g * outputOffset, N); } @@ -166,9 +169,53 @@ private: MemoryHandlePtr memory_; }; +/* + * \brief Backward input calculation of convolution. + */ +template +class GemmConvGradInputFunction : public ConvFunctionBase { +public: + void init(const FuncConfig& config) override { + ConvFunctionBase::init(config); + } + + void calc(const BufferArgs& inputs, const BufferArgs& outputs) override { + CHECK_EQ(numInputs_, inputs.size()); + CHECK_EQ(numOutputs_, outputs.size()); + const TensorShape& outputGrad = inputs[0].shape(); + const TensorShape& filter = inputs[1].shape(); + const TensorShape& inputGrad = outputs[0].shape(); + check(inputGrad, filter, outputGrad); + } +}; + +/* + * \brief Backward filter calculation of convolution. + */ +template +class GemmConvGradFilterFunction : public ConvFunctionBase { +public: + void init(const FuncConfig& config) override { + ConvFunctionBase::init(config); + } + + void calc(const BufferArgs& inputs, const BufferArgs& outputs) override { + CHECK_EQ(numInputs_, inputs.size()); + CHECK_EQ(numOutputs_, outputs.size()); + const TensorShape& outputGrad = inputs[0].shape(); + const TensorShape& input = inputs[1].shape(); + const TensorShape& filterGrad = outputs[0].shape(); + check(input, filterGrad, outputGrad); + } +}; + REGISTER_TYPED_FUNC(GemmConv, CPU, GemmConvFunction); +REGISTER_TYPED_FUNC(GemmConvGradInput, CPU, GemmConvGradInputFunction); +REGISTER_TYPED_FUNC(GemmConvGradFilter, CPU, GemmConvGradFilterFunction); #ifndef PADDLE_ONLY_CPU REGISTER_TYPED_FUNC(GemmConv, GPU, GemmConvFunction); +REGISTER_TYPED_FUNC(GemmConvGradInput, GPU, GemmConvGradInputFunction); +REGISTER_TYPED_FUNC(GemmConvGradFilter, GPU, GemmConvGradFilterFunction); #endif } // namespace paddle diff --git a/paddle/function/NaiveConvOp.cpp b/paddle/function/NaiveConvOp.cpp index f5d2aa16ab9..94aba253e3e 100644 --- a/paddle/function/NaiveConvOp.cpp +++ b/paddle/function/NaiveConvOp.cpp @@ -91,7 +91,12 @@ public: } void calc(const BufferArgs& inputs, const BufferArgs& outputs) override { - check(inputs, outputs); + CHECK_EQ(numInputs_, inputs.size()); + CHECK_EQ(numOutputs_, outputs.size()); + const TensorShape& input = inputs[0].shape(); + const TensorShape& filter = inputs[1].shape(); + const TensorShape& output = outputs[0].shape(); + check(input, filter, output); CHECK_EQ(outputs[0].getArgType(), ASSIGN_TO); size_t batchSize = inputs[0].shape()[0]; -- GitLab From 092828fbe30e40b72fc25d8ab9c56ac7ecb5afe4 Mon Sep 17 00:00:00 2001 From: xzl Date: Mon, 5 Jun 2017 17:42:33 +0800 Subject: [PATCH 0544/3256] modify the doc of the interface --- paddle/parameter/ParameterUpdaterHook.cpp | 6 +++--- proto/ParameterConfig.proto | 4 ++-- python/paddle/trainer_config_helpers/attrs.py | 11 ++++------- 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/paddle/parameter/ParameterUpdaterHook.cpp b/paddle/parameter/ParameterUpdaterHook.cpp index e29494868bc..5e8c77ced03 100644 --- a/paddle/parameter/ParameterUpdaterHook.cpp +++ b/paddle/parameter/ParameterUpdaterHook.cpp @@ -30,9 +30,9 @@ namespace paddle { /** * The static pruning hook - * Static means user specific a sparsity_ratio map before training started. The - * network will - * hold the sparsity_ratio maximum numbers of parameters, and cut off the rest. + * Static means user specific a sparsity_ratio before training start, and the + * network will prune the parameters based on the sparsity_ratio. More deatils + * can see https://arxiv.org/pdf/1506.02626.pdf. */ class StaticPruningHook : public IParameterUpdaterHook { diff --git a/proto/ParameterConfig.proto b/proto/ParameterConfig.proto index 53e3b94f031..360342bac6f 100644 --- a/proto/ParameterConfig.proto +++ b/proto/ParameterConfig.proto @@ -25,9 +25,9 @@ enum ParameterInitStrategy { } message ParameterUpdaterHookConfig { + // hook type such as 'pruning' required string type = 1; - //hook type such as 'pruning' - optional double sparsity_ratio = 3; + optional double sparsity_ratio = 2 [default = 0.8]; } message ParameterConfig { diff --git a/python/paddle/trainer_config_helpers/attrs.py b/python/paddle/trainer_config_helpers/attrs.py index a0ad8c44525..556701ca7a8 100644 --- a/python/paddle/trainer_config_helpers/attrs.py +++ b/python/paddle/trainer_config_helpers/attrs.py @@ -59,17 +59,14 @@ def is_compatible_with(x, Type): class HookAttribute(object): """ Hook Attribute object. The hook is an auxiliary operation that occurs - during network propagation. Such as pruning operation, It will cut off - redundant parameters in the network before training. More detail can see - here paddle/parameter/ParameterUpdaterHook.cpp + during network propagation. NOTE: IT IS A HIGH LEVEL USER INTERFACE. - + :param type: Hook type, eg: 'pruning' :type type: string - :param sparsity_ratio: Must be specified if hook type is 'pruning', - the network will hold the sparsity_ratio maximum parameters, and cut off the rest. - :type sparsity_ratio: float number between 0 and 1 + :param sparsity_ratio: Must be specified if hook type is 'pruning' + :type sparsity_ratio: float or None """ -- GitLab From 6cb6a548a9af17dcfe09d47b1177cc24a2cbdb7e Mon Sep 17 00:00:00 2001 From: liaogang Date: Mon, 5 Jun 2017 17:55:53 +0800 Subject: [PATCH 0545/3256] rename CMAKE_CURRENT_LIST_DIR to CMAKE_CURRENT_SOURCE_DIR --- cmake/cpplint.cmake | 2 +- cmake/generic.cmake | 6 +++--- go/cmake/golang.cmake | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cmake/cpplint.cmake b/cmake/cpplint.cmake index 02a5c0b2c9b..48f705818b7 100644 --- a/cmake/cpplint.cmake +++ b/cmake/cpplint.cmake @@ -59,7 +59,7 @@ macro(add_style_check_target TARGET_NAME) "--filter=${STYLE_FILTER}" "--write-success=${CUR_GEN}" ${filename} DEPENDS ${filename} - WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}) + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) endif() endforeach() endif() diff --git a/cmake/generic.cmake b/cmake/generic.cmake index 052530608e6..43cd6b398b1 100644 --- a/cmake/generic.cmake +++ b/cmake/generic.cmake @@ -182,7 +182,7 @@ function(go_library TARGET_NAME) COMMAND env GOPATH=${GOPATH} ${CMAKE_Go_COMPILER} build ${BUILD_MODE} -o "${CMAKE_CURRENT_BINARY_DIR}/${LIB_NAME}" ${go_library_SRCS} - WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}) + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) add_custom_target(${TARGET_NAME}_lib ALL DEPENDS ${TARGET_NAME}_timestamp ${go_library_DEPS}) add_library(${TARGET_NAME} STATIC IMPORTED) set_property(TARGET ${TARGET_NAME} PROPERTY @@ -199,7 +199,7 @@ function(go_binary TARGET_NAME) COMMAND env GOPATH=${GOPATH} ${CMAKE_Go_COMPILER} build -o "${CMAKE_CURRENT_BINARY_DIR}/${TARGET_NAME}" ${go_library_SRCS} - WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}) + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) add_custom_target(${TARGET_NAME} ALL DEPENDS ${TARGET_NAME}_timestamp ${go_binary_DEPS}) install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/${TARGET_NAME} DESTINATION bin) endfunction(go_binary) @@ -213,7 +213,7 @@ function(go_test TARGET_NAME) COMMAND env GOPATH=${GOPATH} ${CMAKE_Go_COMPILER} test -c -o "${CMAKE_CURRENT_BINARY_DIR}/${TARGET_NAME}" ${go_test_SRCS} - WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}) + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) add_custom_target(${TARGET_NAME} ALL DEPENDS ${TARGET_NAME}_timestamp ${go_test_DEPS}) add_test(${TARGET_NAME} ${CMAKE_CURRENT_BINARY_DIR}/${TARGET_NAME}) endfunction(go_test) diff --git a/go/cmake/golang.cmake b/go/cmake/golang.cmake index e73b0c865bc..d38d06de234 100644 --- a/go/cmake/golang.cmake +++ b/go/cmake/golang.cmake @@ -39,7 +39,7 @@ function(GO_LIBRARY NAME BUILD_TYPE) COMMAND env GOPATH=${GOPATH} ${CMAKE_Go_COMPILER} build ${BUILD_MODE} -o "${CMAKE_CURRENT_BINARY_DIR}/${LIB_NAME}" ${CMAKE_GO_FLAGS} ${GO_SOURCE} - WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}) + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) add_custom_target(${NAME} ALL DEPENDS ${OUTPUT_DIR}/.timestamp ${ARGN}) add_dependencies(${NAME} goGet) -- GitLab From 8610ba1cf75be635030cb820b46c941f057dfe5c Mon Sep 17 00:00:00 2001 From: dzhwinter Date: Mon, 5 Jun 2017 21:09:03 +0800 Subject: [PATCH 0546/3256] "remove get config proto" --- paddle/optimizer/Tensor.h | 16 +++- paddle/optimizer/adadelta_optimizer.cc | 22 ++--- paddle/optimizer/adadelta_optimizer.h | 23 +++-- paddle/optimizer/adagrad_optimizer.cc | 21 ++--- paddle/optimizer/adagrad_optimizer.h | 16 ++-- paddle/optimizer/adam_optimizer.cc | 17 ++-- paddle/optimizer/adam_optimizer.h | 18 ++-- paddle/optimizer/lr_policy.h | 15 ++++ paddle/optimizer/optimizer.cc | 23 +++-- paddle/optimizer/parameter_optimizer.cc | 106 ++++++++++-------------- paddle/optimizer/parameter_optimizer.h | 16 +--- paddle/optimizer/sgd_optimizer.h | 35 +++----- paddle/optimizer/sgd_optmizer.cc | 15 ++-- 13 files changed, 150 insertions(+), 193 deletions(-) diff --git a/paddle/optimizer/Tensor.h b/paddle/optimizer/Tensor.h index d5ba4b3159f..d779bb50709 100644 --- a/paddle/optimizer/Tensor.h +++ b/paddle/optimizer/Tensor.h @@ -5,7 +5,6 @@ */ #include -#include "optimizer.h" #include "paddle/math/BaseMatrix.h" namespace paddle { @@ -15,18 +14,27 @@ template using TensorBase = BaseMatrixT; template -class Tensor : public TensorBase { +class TensorT : public TensorBase { public: - Tensor(T* data, int size) : TensorBase(1, size, 0, data, false, false) {} + TensorT(T* data, int size) : TensorBase(1, size, 0, data, false, false) {} + TensorT(const TensorT& t) + : TensorBase(1, t.size(), 0, t.get_buffer(), false, false) {} + TensorT& operator=(const TensorT& t) { + this->size_ = t.size(); + this->data_ = t.get_buffer(); + } T* get_buffer() { return this->data_; } T& operator[](const int idx) { - CHECK(idx >= 0 && idx < this->width_) << " out of index range"; + CHECK(idx >= 0 && idx < this->width_) << "out of index range"; return this->data_[idx]; } // TODO: replace with tensorshape size_t size() const { return this->width_; } }; +// TODO(zhihong): design problem of dynamic datatype, need to fix +typedef TensorT Tensor; + } // namespace optimizer } // namespace paddle diff --git a/paddle/optimizer/adadelta_optimizer.cc b/paddle/optimizer/adadelta_optimizer.cc index b76c123ec80..c5537bde853 100644 --- a/paddle/optimizer/adadelta_optimizer.cc +++ b/paddle/optimizer/adadelta_optimizer.cc @@ -4,19 +4,17 @@ namespace paddle { namespace optimizer { -template -void AdadeltaOptimizer::set_weight(const Tensor* p) { +void AdadeltaOptimizer::set_weight(Tensor* p) { size_t size = p->size(); - T* gptr = new T[size]; - accum_gradient = Tensor(gptr, size); - T* dptr = new T[size]; - accum_delta = Tensor(dptr, size); - T* dptr_current = new T[size]; - update_delta = Tensor(dptr_current, size); + real* gptr = new real[size]; + accum_gradient = Tensor(gptr, size); + real* dptr = new real[size]; + accum_delta = Tensor(dptr, size); + real* dptr_current = new real[size]; + update_delta = Tensor(dptr_current, size); } -template -void AdadeltaOptimizer::update(const Tensor& gradient) { +void AdadeltaOptimizer::update(const Tensor& gradient) { num_sample_passed += 1; double learning_rate = lr_policy->get_learning_rate(num_sample_passed); for (size_t i = 0; i < parameter_->size(); ++i) { @@ -33,9 +31,5 @@ void AdadeltaOptimizer::update(const Tensor& gradient) { learning_rate * update_delta[i] + learning_rate * decay * parameter_[i]; } } - -template class AdadeltaOptimizer; -template class AdadeltaOptimizer; - } // namespace optimizer } // namespace paddle diff --git a/paddle/optimizer/adadelta_optimizer.h b/paddle/optimizer/adadelta_optimizer.h index 35f3ff86fcf..d9db5d09c22 100644 --- a/paddle/optimizer/adadelta_optimizer.h +++ b/paddle/optimizer/adadelta_optimizer.h @@ -6,28 +6,27 @@ namespace paddle { namespace optimizer { -template -class AdadeltaOptimizer : public ParameterOptimizer { +class AdadeltaOptimizer : public ParameterOptimizer { public: - using ParameterOptimizer::parameter_; - using ParameterOptimizer::num_sample_passed; - using ParameterOptimizer::lr_policy; + using ParameterOptimizer::parameter_; + using ParameterOptimizer::num_sample_passed; + using ParameterOptimizer::lr_policy; AdadeltaOptimizer(double rho, double epsilon, double decay, BaseLr *lr) - : ParameterOptimizer(lr), rho(rho), epsilon(epsilon), decay(decay) {} + : ParameterOptimizer(lr), rho(rho), epsilon(epsilon), decay(decay) {} ~AdadeltaOptimizer() { if (accum_gradient) delete accum_gradient; if (accum_delta) delete accum_delta; if (update_delta) delete update_delta; } - void update(const Tensor &gradient); - void set_weight(const Tensor *p); - T *get_weight() const; + void update(const Tensor &gradient); + void set_weight(Tensor *p); + real *get_weight() const; private: - Tensor *accum_gradient; - Tensor *accum_delta; - Tensor *update_delta; + Tensor *accum_gradient; + Tensor *accum_delta; + Tensor *update_delta; double rho; double epsilon; diff --git a/paddle/optimizer/adagrad_optimizer.cc b/paddle/optimizer/adagrad_optimizer.cc index 7b451cb4074..3d47e35896c 100644 --- a/paddle/optimizer/adagrad_optimizer.cc +++ b/paddle/optimizer/adagrad_optimizer.cc @@ -2,21 +2,18 @@ namespace paddle { namespace optimizer { -template -template -void AdagradOptimizer::set_weight(const Tensor* p) { +void AdagradOptimizer::set_weight(Tensor* p) { size_t size = p->width(); - T* gptr = new T[size]; - accum_gradient = Tensor(gptr, size); - T* dptr = new T[size]; - accum_delta = Tensor(dtpr, size); - T* dptr_current = new T[size]; - update_delta = Tensor(dptr_current, size); + real* gptr = new real[size]; + accum_gradient = Tensor(gptr, size); + real* dptr = new real[size]; + accum_delta = Tensor(dtpr, size); + real* dptr_current = new real[size]; + update_delta = Tensor(dptr_current, size); } -template -void AdagradOptimizer::update(const Tensor& gradient) { +void AdagradOptimizer::update(const Tensor& gradient) { num_sample_passed += 1; double learning_rate = lr_policy->get_learning_rate(); for (size_t i = 0; i < parameter_.size(); ++i) { @@ -27,7 +24,5 @@ void AdagradOptimizer::update(const Tensor& gradient) { } } -template class AdagradOptimizer; -template class AdagradOptimizer; } // namespace optimizer } // namespace paddle diff --git a/paddle/optimizer/adagrad_optimizer.h b/paddle/optimizer/adagrad_optimizer.h index a01040f30f9..0f6ce06f35a 100644 --- a/paddle/optimizer/adagrad_optimizer.h +++ b/paddle/optimizer/adagrad_optimizer.h @@ -6,23 +6,19 @@ namespace paddle { namespace optimizer { -template -class AdagradOptimizer : public ParameterOptimizer { +class AdagradOptimizer : public ParameterOptimizer { public: - using ParameterOptimizer::parameter_; - using ParameterOptimizer::num_sample_passed; - using ParameterOptimizer::lr_policy; AdagradOptimizer(double epsilon, double decay, BaseLr *lr) - : ParameterOptimizer(lr), epsilon(epsilon), decay(decay) {} + : ParameterOptimizer(lr), epsilon(epsilon), decay(decay) {} ~AdagradOptimizer() { if (accum_gradient) delete accum_gradient; } - void update(const Tensor &gradient); - void set_weight(const Tensor *p); - T *get_weight() const; + void update(const Tensor &gradient); + void set_weight(Tensor *p); + real *get_weight() const; private: - Tensor *accum_gradient; + Tensor *accum_gradient; double epsilon; double decay; }; diff --git a/paddle/optimizer/adam_optimizer.cc b/paddle/optimizer/adam_optimizer.cc index b2d2ddc596a..d9cc3344d59 100644 --- a/paddle/optimizer/adam_optimizer.cc +++ b/paddle/optimizer/adam_optimizer.cc @@ -3,17 +3,15 @@ namespace paddle { namespace optimizer { -template -void AdamOptimizer::set_weight(const Tensor *p) { +void AdamOptimizer::set_weight(Tensor *p) { size_t size = p->width(); - T *mptr = new T[size]; - momentums_ = Tensor(mptr, size); - T *vptr = new T[size]; - velocitys_ = Tensor(vtpr, size); + real *mptr = new real[size]; + momentums_ = Tensor(mptr, size); + real *vptr = new real[size]; + velocitys_ = Tensor(vtpr, size); } -template -void AdamOptimizer::update(const Tensor &gradient) { +void AdamOptimizer::update(const Tensor &gradient) { num_sample_passed += 1; double learning_rate = lr_policy->get_learning_rate(num_sample_passed); double coef1 = 1.0 - std::pow(beta_1, num_sample_passed); @@ -28,8 +26,5 @@ void AdamOptimizer::update(const Tensor &gradient) { decay * parameter_[i]); } } - -template class AdamOptimizer; -template class AdamOptimizer; } // namespace optimizer } // namespace paddle diff --git a/paddle/optimizer/adam_optimizer.h b/paddle/optimizer/adam_optimizer.h index cf81bd70a64..68e2aa0223e 100644 --- a/paddle/optimizer/adam_optimizer.h +++ b/paddle/optimizer/adam_optimizer.h @@ -6,15 +6,11 @@ namespace paddle { namespace optimizer { -template -class AdamOptimizer : public ParameterOptimizer { +class AdamOptimizer : public ParameterOptimizer { public: - using ParameterOptimizer::parameter_; - using ParameterOptimizer::num_sample_passed; - using ParameterOptimizer::lr_policy; AdamOptimizer( double beta_1, double beta_2, double epsilon, double decay, BaseLr *lr) - : ParameterOptimizer(lr), + : ParameterOptimizer(lr), beta_1(beta_1), beta_2(beta_2), epsilon(epsilon), @@ -23,13 +19,13 @@ public: if (momentums_) delete momentums_; if (velocitys_) delete velocitys_; } - void update(const Tensor &gradient); - void set_weight(const Tensor *p); - T *get_weight() const; + void update(const Tensor &gradient); + void set_weight(Tensor *p); + real *get_weight() const; private: - Tensor *momentums_; - Tensor *velocitys_; + Tensor *momentums_; + Tensor *velocitys_; double beta_1; double beta_2; double epsilon; diff --git a/paddle/optimizer/lr_policy.h b/paddle/optimizer/lr_policy.h index 5aad87f45fb..e1017cf32dc 100644 --- a/paddle/optimizer/lr_policy.h +++ b/paddle/optimizer/lr_policy.h @@ -1,6 +1,7 @@ #ifndef PADDLE_OPTIMIZER_LR_POLICY_H_ #define PADDLE_OPTIMIZER_LR_POLICY_H_ +#include #include "OptimizerConfig.pb.h" namespace paddle { @@ -19,11 +20,25 @@ protected: // constant learning rate policy class ConstLr final : public BaseLr { public: + ConstLr(double lr) : BaseLr(lr){}; double get_learning_rate(const uint64_t num_sample_passed) { return learning_rate; } }; +class LinearLr final : public BaseLr { +public: + LinearLr(double lr, double lr_decay_a, double lr_decay_b) + : BaseLr(lr), lr_decay_a(lr_decay_a), lr_decay_b(lr_decay_b) {} + double get_learning_rate(const uint64_t num_sample_passed) { + return std::max(learning_rate - lr_decay_a * num_sample_passed, lr_decay_b); + } + +private: + double lr_decay_a; + double lr_decay_b; +}; + } // namespace optimizer } // namespace paddle diff --git a/paddle/optimizer/optimizer.cc b/paddle/optimizer/optimizer.cc index ff6558147ee..fb2e543bf32 100644 --- a/paddle/optimizer/optimizer.cc +++ b/paddle/optimizer/optimizer.cc @@ -2,8 +2,9 @@ #include #include "parameter_optimizer.h" +using namespace paddle::optimizer; -template +template struct EnumToType {}; template @@ -26,17 +27,16 @@ MATCH_ENUM_TYPE(int64_t, PADDLE_ELEMENT_TYPE_INT64); MATCH_ENUM_TYPE(uint64_t, PADDLE_ELEMENT_TYPE_UINT64); MATCH_ENUM_TYPE(float, PADDLE_ELEMENT_TYPE_FLOAT32); MATCH_ENUM_TYPE(double, PADDLE_ELEMENT_TYPE_FLOAT64); - struct paddle_optimizer { - /*! \brief optmizer in C++ side */ - paddle::optimizer::ParameterOptimizerBase* impl; +struct paddle_optimizer { + paddle::optimizer::ParameterOptimizer* impl; }; paddle_optimizer* paddle_create_optimizer(const unsigned char* config_proto, int config_proto_len) { - paddle_optimizer* optimizer; + paddle_optimizer* optimizer = new paddle_optimizer; std::string config(config_proto, config_proto + config_proto_len); - optimizer->impl->create(config_proto); + optimizer->impl = ParameterOptimizer::create(config); return optimizer; } @@ -49,9 +49,9 @@ int paddle_update_parameter(paddle_optimizer* o, const paddle_element_type data_type, const void* grad_buffer, int num_bytes) { - auto type = EnumToType::Type; - paddle::Tensor gradient(reinterpret_cast(grad_buffer), - num_bytes); + // TOOD(zhihong): datatype not work. need to add the runtime datatype + auto grad = reinterpret_cast(grad_buffer); + Tensor gradient(const_cast(grad), num_bytes); o->impl->update(gradient); return PADDLE_SUCCESS; } @@ -60,9 +60,8 @@ int paddle_optimizer_set_weights(paddle_optimizer* o, const paddle_element_type data_type, void* param_buffer, int num_bytes) { - auto type = EnumToType::Type; - paddle::Tensor* param = new paddle::Tensor( - reinterpret_cast(param_buffer), num_bytes); + // TOOD(zhihong): datatype not work. need to add the runtime datatype + Tensor* param = new Tensor(reinterpret_cast(param_buffer), num_bytes); o->impl->set_weight(param); return PADDLE_SUCCESS; } diff --git a/paddle/optimizer/parameter_optimizer.cc b/paddle/optimizer/parameter_optimizer.cc index 4bd384d77da..6d9fa5c8024 100644 --- a/paddle/optimizer/parameter_optimizer.cc +++ b/paddle/optimizer/parameter_optimizer.cc @@ -10,78 +10,60 @@ namespace paddle { namespace optimizer { -template -ParameterOptimizer *ParameterOptimizer::create( +ParameterOptimizer *ParameterOptimizer::create( const ::std::string &config_proto) { paddle::OptimizerConfig config; CHECK(config.ParseFromString(config_proto) == 0) << "error : optimizer config"; - CHECK(config_valid(config) == 0) << "error : invalid optimizer config "; - BaseLr *lr = nullptr; - switch (config.lr_policy()) { - case "ConstLr": - lr = new ConstLr(config.lr_config().learning_rate()); - break; - } - ParameterOptimizer *opt = nullptr; - switch (config.optimizer_name()) { - case "SGD": - opt = new SGDOptimizer(config.sgd().momentum(), - config.sgd().decay(), - config.sgd().nesterov(), - lr); - break; - case "Adagrad": - opt = new AdagradOptimizer( + auto select_lr_policy = [=](const OptimizerConfig &config) -> BaseLr * { + std::string s(config.lr_policy()); + if (s == "ConstLr") return new ConstLr(config.lr_config().learning_rate()); + if (s == "LinearLr") + return new LinearLr(config.lr_config().learning_rate(), + config.lr_config().lr_decay_a(), + config.lr_config().lr_decay_b()); + // default + return new ConstLr(config.lr_config().learning_rate()); + }; + BaseLr *lr = select_lr_policy(config); + auto select_optimizer = + [=](const OptimizerConfig &config) -> ParameterOptimizer * { + std::string s(config.optimizer_name()); + if (s == "SGD") { + return new SGDOptimizer(config.sgd().momentum(), + config.sgd().decay(), + config.sgd().nesterov(), + lr); + } + if (s == "Adadelta") { + return new AdagradOptimizer( config.adagrad().epsilon(), config.adagrad().decay(), lr); - break; - case "Adadelta": - opt = new AdadeltaOptimizer(config.adadelta().rho(), - config.adadelta().epsilon(), - config.adadelta().decay(), - lr); - break; - case "Adam": - opt = new AdamOptimizer(config.adam().beta_1(), - config.adam().beta_2(), - config.adam().epsilon(), - config.adam().decay(), - lr); - break; - } - - return opt; -} - -template -T *ParameterOptimizer::get_weight() const { - return parameter.get().get_buffer(); -} - -template -char *ParameterOptimizer::get_config_proto() const { - // set config dynamic value for save checkpoint - config_.lr_policy().set_learning_rate( - lr_policy->get_learning_rate(num_sample_passed)); - config_.set_num_sample_passed(num_sample_passed); - config_.set_iterations(iterations); - return config_.SerializeAsString().c_str(); -} - -template -void ParameterOptimizer::set_weight(const Tensor *p) { - parameter_ = p; + } + if (s == "Adagrad") { + return new AdagradOptimizer( + config.adagrad().epsilon(), config.adagrad().decay(), lr); + } + if (s == "Adam") { + return new AdadeltaOptimizer(config.adadelta().rho(), + config.adadelta().epsilon(), + config.adadelta().decay(), + lr); + } + // default + return new SGDOptimizer(config.sgd().momentum(), + config.sgd().decay(), + config.sgd().nesterov(), + lr); + }; + return select_optimizer(config); } -template -bool ParameterOptimizer::config_valid(const ::std::string &config) const { - // TODO(zhihong) : add more value checker, failed ASAP - return true; +real *ParameterOptimizer::get_weight() const { + return parameter_->get_buffer(); } -template class ParameterOptimzier; -template class ParameterOptimzier; +void ParameterOptimizer::set_weight(Tensor *p) { parameter_ = p; } } // namespace optimizer } // namespace paddle diff --git a/paddle/optimizer/parameter_optimizer.h b/paddle/optimizer/parameter_optimizer.h index 40994aa8670..a4f39836baf 100644 --- a/paddle/optimizer/parameter_optimizer.h +++ b/paddle/optimizer/parameter_optimizer.h @@ -11,13 +11,6 @@ namespace paddle { namespace optimizer { -class ParameterOptimizerBase { -private: - ParameterOptimizerBase(const ParameterOptimizerBase &) = delete; - ParameterOptimizerBase &operator=(const ParameterOptimizerBase &) = delete; -}; - -template class ParameterOptimizer { public: /** @@ -31,14 +24,13 @@ public: virtual ~ParameterOptimizer() { delete parameter_; }; static ParameterOptimizer *create(const ::std::string &config_proto); - virtual void update(const Tensor &gradient) = 0; - virtual T *get_weight() const; - virtual void set_weight(const Tensor *parameter); + virtual void update(const Tensor &gradient) = 0; + virtual real *get_weight() const; + virtual void set_weight(Tensor *parameter); public: - bool config_valid(::std::string &config) const; OptimizerConfig config_; - Tensor *parameter_; + Tensor *parameter_; // learning rate policy BaseLr *lr_policy; diff --git a/paddle/optimizer/sgd_optimizer.h b/paddle/optimizer/sgd_optimizer.h index 5115825590d..375c99b30b8 100644 --- a/paddle/optimizer/sgd_optimizer.h +++ b/paddle/optimizer/sgd_optimizer.h @@ -6,31 +6,22 @@ namespace paddle { namespace optimizer { -template -class SGDOptimizer : public ParameterOptimizer { +class SGDOptimizer : public ParameterOptimizer { public: - using ParameterOptimizer::parameter_; - using ParameterOptimizer::num_sample_passed; - using ParameterOptimizer::lr_policy; - - SGDOptimizer(double m, - double d, - bool n, - double learning_rate, - uint64_t num_sample_passed, - BaseLr* lr) - : ParameterOptimizer(lr), momentum(m), decay(d), nesterov(n) {} - virtual ~SGDOptimizer() { - // clear memory by Tensor library - delete momentums_; - } - void update(const Tensor& gradient); - - void set_weight(const Tensor* p); - T* get_weight() const; + using ParameterOptimizer::parameter_; + using ParameterOptimizer::num_sample_passed; + using ParameterOptimizer::lr_policy; + + SGDOptimizer(double m, double d, bool n, BaseLr* lr) + : ParameterOptimizer(lr), momentum(m), decay(d), nesterov(n) {} + virtual ~SGDOptimizer() { delete momentums_; } + void update(const Tensor& gradient); + + void set_weight(Tensor* p); + real* get_weight() const; private: - Tensor* momentums_; + Tensor* momentums_; double momentum; double decay; bool nesterov; diff --git a/paddle/optimizer/sgd_optmizer.cc b/paddle/optimizer/sgd_optmizer.cc index cd1635fecdc..03ddc814517 100644 --- a/paddle/optimizer/sgd_optmizer.cc +++ b/paddle/optimizer/sgd_optmizer.cc @@ -3,23 +3,21 @@ namespace paddle { namespace optimizer { -template -void SGDOptimizer::set_weight(const Tensor *p) { +void SGDOptimizer::set_weight(Tensor *p) { // ParameterOptimizer::set_weight(p); size_t size = p->size(); // TODO: fix it with align aware allocator bind to Tensor if (momentum != 0.0) { - T *ptr = new T[size]; - momentums_ = Tensor(ptr, size); + real *ptr = new real[size]; + momentums_ = new Tensor(ptr, size); } } -template -void SGDOptimizer::update(const Tensor &gradient) { +void SGDOptimizer::update(const Tensor &gradient) { num_sample_passed += 1; double learning_rate = lr_policy->get_learning_rate(num_sample_passed); double velocity = 0.0; - Tensor &for (size_t i = 0; i < parameter_->size(); ++i) { + for (size_t i = 0; i < parameter_->size(); ++i) { if (momentum == 0.0) { velocity = -learning_rate * gradient[i] - learning_rate * decay * parameter_[i]; @@ -36,8 +34,5 @@ void SGDOptimizer::update(const Tensor &gradient) { } } -template class SGDOptimizer; -template class SGDOptimizer; - } // namespace optimizer } // namespace paddle -- GitLab From 6a93f0f37a7e4463ed2b1ed19a738a3ceeb3d04a Mon Sep 17 00:00:00 2001 From: hedaoyuan Date: Mon, 5 Jun 2017 21:17:46 +0800 Subject: [PATCH 0547/3256] Add the calculation implementation of GemmConvGradFilterFunction --- paddle/function/ConvOp.h | 16 ++++++ paddle/function/GemmConvOp.cpp | 90 +++++++++++++++++++++++++++------- paddle/function/GemmFunctor.h | 30 +++++------- 3 files changed, 99 insertions(+), 37 deletions(-) diff --git a/paddle/function/ConvOp.h b/paddle/function/ConvOp.h index 8f2c0c4cb8a..9ad1785fbb4 100644 --- a/paddle/function/ConvOp.h +++ b/paddle/function/ConvOp.h @@ -89,11 +89,13 @@ public: protected: std::vector strides_; std::vector paddings_; + /// Group size, refer to grouped convolution in /// Alex Krizhevsky's paper: when group=2, the first half of the /// filters are only connected to the first half of the input channels, /// and the second half only connected to the second half. size_t groups_; + inline int strideH() const { return strides_[0]; } inline int strideW() const { return strides_[1]; } @@ -101,6 +103,20 @@ protected: inline int paddingH() const { return paddings_[0]; } inline int paddingW() const { return paddings_[1]; } + + // A temporary memory in convolution calculation. + MemoryHandlePtr memory_; + + template + void resizeBuffer(size_t newSize) { + if (!memory_ || newSize * sizeof(real) > memory_->getAllocSize()) { + if (Device == DEVICE_TYPE_CPU) { + memory_ = std::make_shared(newSize * sizeof(real)); + } else { + memory_ = std::make_shared(newSize * sizeof(real)); + } + } + } }; } // namespace paddle diff --git a/paddle/function/GemmConvOp.cpp b/paddle/function/GemmConvOp.cpp index 109ed20ab06..6b5db1d62ed 100644 --- a/paddle/function/GemmConvOp.cpp +++ b/paddle/function/GemmConvOp.cpp @@ -110,7 +110,7 @@ public: size_t size = inputChannels / groups_ * filterHeight * filterWidth * outputHeight * outputWidth; - resizeBuffer(size); + resizeBuffer(size); real* colData = reinterpret_cast(memory_->getBuf()); Im2ColFunctor im2col; @@ -120,7 +120,7 @@ public: (outputChannels / groups_) * outputHeight * outputWidth; size_t filterOffset = inputs[1].shape().getElements() / groups_; for (size_t i = 0; i < batchSize; i++) { - for (int g = 0; g < groups_; g++) { + for (size_t g = 0; g < groups_; g++) { im2col(inputData + g * inputOffset, inputChannels / groups_, inputHeight, @@ -138,7 +138,9 @@ public: int M = outputChannels / groups_; int N = outputHeight * outputWidth; int K = inputChannels / groups_ * filterHeight * filterWidth; - gemm(M, + gemm(CblasNoTrans, + CblasNoTrans, + M, N, K, 1.0f, @@ -154,19 +156,6 @@ public: outputData += outputChannels * outputHeight * outputWidth; } } - - void resizeBuffer(size_t newSize) { - if (!memory_ || newSize * sizeof(real) > memory_->getAllocSize()) { - if (Device == DEVICE_TYPE_CPU) { - memory_ = std::make_shared(newSize * sizeof(real)); - } else { - memory_ = std::make_shared(newSize * sizeof(real)); - } - } - } - -private: - MemoryHandlePtr memory_; }; /* @@ -202,10 +191,73 @@ public: void calc(const BufferArgs& inputs, const BufferArgs& outputs) override { CHECK_EQ(numInputs_, inputs.size()); CHECK_EQ(numOutputs_, outputs.size()); - const TensorShape& outputGrad = inputs[0].shape(); + CHECK_EQ(outputs[0].getArgType(), ASSIGN_TO); + const TensorShape& output = inputs[0].shape(); const TensorShape& input = inputs[1].shape(); - const TensorShape& filterGrad = outputs[0].shape(); - check(input, filterGrad, outputGrad); + const TensorShape& filter = outputs[0].shape(); + check(input, filter, output); + + size_t batchSize = input[0]; + size_t inputChannels = input[1]; + size_t inputHeight = input[2]; + size_t inputWidth = input[3]; + size_t filterHeight = filter[2]; + size_t filterWidth = filter[3]; + size_t outputChannels = output[1]; + size_t outputHeight = output[2]; + size_t outputWidth = output[3]; + + real* outputGrad = inputs[0].data(); + real* inputData = inputs[1].data(); + real* filterGrad = outputs[0].data(); + + size_t size = inputChannels / groups_ * filterHeight * filterWidth * + outputHeight * outputWidth; + resizeBuffer(size); + real* colData = reinterpret_cast(memory_->getBuf()); + + Im2ColFunctor im2col; + GemmFunctor gemm; + size_t inputOffset = (inputChannels / groups_) * inputHeight * inputWidth; + size_t outputOffset = + (outputChannels / groups_) * outputHeight * outputWidth; + size_t filterOffset = filter.getElements() / groups_; + for (size_t i = 0; i < batchSize; i++) { + for (size_t g = 0; g < groups_; g++) { + im2col(inputData + g * inputOffset, + inputChannels / groups_, + inputHeight, + inputWidth, + filterHeight, + filterWidth, + strideH(), + strideW(), + paddingH(), + paddingW(), + outputHeight, + outputWidth, + colData); + + int M = outputChannels / groups_; + int K = outputHeight * outputWidth; + int N = inputChannels / groups_ * filterHeight * filterWidth; + gemm(CblasNoTrans, + CblasTrans, + M, + N, + K, + 1.0f, + outputGrad + g * outputOffset, + K, + colData, + K, + 1.0f, + filterGrad + g * filterOffset, + N); + } + } + inputData += inputChannels * inputHeight * inputWidth; + outputGrad += outputChannels * outputHeight * outputWidth; } }; diff --git a/paddle/function/GemmFunctor.h b/paddle/function/GemmFunctor.h index 5fb2f8a6d9e..d5db5cf5e7a 100644 --- a/paddle/function/GemmFunctor.h +++ b/paddle/function/GemmFunctor.h @@ -26,7 +26,9 @@ namespace paddle { template class GemmFunctor { public: - void operator()(const int M, + void operator()(const CBLAS_TRANSPOSE transA, + const CBLAS_TRANSPOSE TransB, + const int M, const int N, const int K, const T alpha, @@ -42,7 +44,9 @@ public: template class GemmFunctor { public: - void operator()(const int M, + void operator()(const CBLAS_TRANSPOSE transA, + const CBLAS_TRANSPOSE TransB, + const int M, const int N, const int K, const T alpha, @@ -53,26 +57,16 @@ public: const T beta, T* C, const int ldc) { - gemm(CblasNoTrans, - CblasNoTrans, - M, - N, - K, - alpha, - A, - lda, - B, - ldb, - beta, - C, - ldc); + gemm(transA, TransB, M, N, K, alpha, A, lda, B, ldb, beta, C, ldc); } }; template class GemmFunctor { public: - void operator()(const int M, + void operator()(const CBLAS_TRANSPOSE transA, + const CBLAS_TRANSPOSE TransB, + const int M, const int N, const int K, const T alpha, @@ -84,9 +78,9 @@ public: T* C, const int ldc) { hl_matrix_mul((T*)A, - HPPL_OP_N, + transA == CblasNoTrans ? HPPL_OP_N : HPPL_OP_T, (T*)B, - HPPL_OP_N, + TransB == CblasNoTrans ? HPPL_OP_N : HPPL_OP_T, C, M, N, -- GitLab From 90326198e929772fe3e87fe5c067f057927f7f64 Mon Sep 17 00:00:00 2001 From: hedaoyuan Date: Mon, 5 Jun 2017 21:35:10 +0800 Subject: [PATCH 0548/3256] Bug fix & add test of GemmConvGradFilter. --- paddle/function/ConvOpTest.cpp | 53 +++++++++++++++++++++++++--------- paddle/function/GemmConvOp.cpp | 4 +-- 2 files changed, 42 insertions(+), 15 deletions(-) diff --git a/paddle/function/ConvOpTest.cpp b/paddle/function/ConvOpTest.cpp index d9de2114488..e2997df0128 100644 --- a/paddle/function/ConvOpTest.cpp +++ b/paddle/function/ConvOpTest.cpp @@ -19,11 +19,18 @@ limitations under the License. */ namespace paddle { +enum TestType { + FORWARD_TEST = 0, + BACKWARD_INPUT_TEST = 1, + BACKWARD_FILTER_TEST = 2, +}; + template class ConvolutionTest { public: ConvolutionTest(const std::string& conv1, const std::string& conv2, + TestType type, std::string algo = "auto") { for (size_t batchSize : {1, 32}) { for (size_t inputSize : {7, 14, 54}) { @@ -58,16 +65,31 @@ public: .set("groups", (size_t)1) .set("algo", algo)); - TensorShape shape0{ + TensorShape input{ batchSize, inputChannels, inputSize, inputSize}; - TensorShape shape1{ + TensorShape filter{ outputChannels, inputChannels, filterSize, filterSize}; - TensorShape shape2{ + TensorShape output{ batchSize, outputChannels, outputSize, outputSize}; - test.addInputs(BufferArg(VALUE_TYPE_FLOAT, shape0)); - test.addInputs(BufferArg(VALUE_TYPE_FLOAT, shape1)); - test.addOutputs(BufferArg(VALUE_TYPE_FLOAT, shape2)); - test.run(); + + if (type == FORWARD_TEST) { + test.addInputs(BufferArg(VALUE_TYPE_FLOAT, input)); + test.addInputs(BufferArg(VALUE_TYPE_FLOAT, filter)); + test.addOutputs(BufferArg(VALUE_TYPE_FLOAT, output)); + test.run(); + } else if (type == BACKWARD_INPUT_TEST) { +#if 0 + test.addInputs(BufferArg(VALUE_TYPE_FLOAT, output)); + test.addInputs(BufferArg(VALUE_TYPE_FLOAT, filter)); + test.addOutputs(BufferArg(VALUE_TYPE_FLOAT, input)); + test.run(); +#endif + } else if (type == BACKWARD_FILTER_TEST) { + test.addInputs(BufferArg(VALUE_TYPE_FLOAT, output)); + test.addInputs(BufferArg(VALUE_TYPE_FLOAT, input)); + test.addOutputs(BufferArg(VALUE_TYPE_FLOAT, filter)); + test.run(); + } } } } @@ -78,15 +100,20 @@ public: } }; -TEST(Convolution, GEMM) { - ConvolutionTest test("NaiveConv-CPU", - "GemmConv-CPU"); +TEST(Forward, GEMM) { + ConvolutionTest test( + "NaiveConv-CPU", "GemmConv-CPU", FORWARD_TEST); } #ifndef PADDLE_ONLY_CPU -TEST(Convolution, GEMM2) { - ConvolutionTest test("GemmConv-CPU", - "GemmConv-GPU"); +TEST(Forward, GEMM2) { + ConvolutionTest test( + "GemmConv-CPU", "GemmConv-GPU", FORWARD_TEST); +} + +TEST(BackwardFilter, GEMM) { + ConvolutionTest test( + "GemmConvGradFilter-CPU", "GemmConvGradFilter-GPU", BACKWARD_FILTER_TEST); } #endif diff --git a/paddle/function/GemmConvOp.cpp b/paddle/function/GemmConvOp.cpp index 6b5db1d62ed..414c7a885b6 100644 --- a/paddle/function/GemmConvOp.cpp +++ b/paddle/function/GemmConvOp.cpp @@ -255,9 +255,9 @@ public: filterGrad + g * filterOffset, N); } + inputData += inputChannels * inputHeight * inputWidth; + outputGrad += outputChannels * outputHeight * outputWidth; } - inputData += inputChannels * inputHeight * inputWidth; - outputGrad += outputChannels * outputHeight * outputWidth; } }; -- GitLab From b4aa0eca4426a9f8358a15cd22577554e6051818 Mon Sep 17 00:00:00 2001 From: dzhwinter Date: Mon, 5 Jun 2017 22:05:55 +0800 Subject: [PATCH 0549/3256] "modify update interface" --- paddle/optimizer/CMakeLists.txt | 1 + paddle/optimizer/Tensor.h | 28 ++++++++++++-------- paddle/optimizer/Tensor_test.cpp | 21 +++++++++++++++ paddle/optimizer/optimizer.cc | 5 ++-- paddle/optimizer/parameter_optimizer.cc | 34 ++++++++++++------------- paddle/optimizer/sgd_optmizer.cc | 3 ++- 6 files changed, 62 insertions(+), 30 deletions(-) create mode 100644 paddle/optimizer/Tensor_test.cpp diff --git a/paddle/optimizer/CMakeLists.txt b/paddle/optimizer/CMakeLists.txt index 06f6d83efe1..95d7ad720f3 100644 --- a/paddle/optimizer/CMakeLists.txt +++ b/paddle/optimizer/CMakeLists.txt @@ -27,3 +27,4 @@ add_dependencies(optimizer gen_proto_cpp) add_simple_unittest(optimizer_test) add_simple_unittest(optimizer_factory_test) +add_simple_unittest(Tensor_test) diff --git a/paddle/optimizer/Tensor.h b/paddle/optimizer/Tensor.h index d779bb50709..fbfba4806a1 100644 --- a/paddle/optimizer/Tensor.h +++ b/paddle/optimizer/Tensor.h @@ -5,34 +5,42 @@ */ #include -#include "paddle/math/BaseMatrix.h" +#include "paddle/utils/Common.h" +#include "paddle/utils/Logging.h" namespace paddle { namespace optimizer { template -using TensorBase = BaseMatrixT; - -template -class TensorT : public TensorBase { +class TensorT { public: - TensorT(T* data, int size) : TensorBase(1, size, 0, data, false, false) {} + TensorT(size_t h, size_t w, T* data) : height_(h), width_(w), data_(data_) {} + TensorT(T* data, int size) : height_(1), width_(size), data_(data) {} TensorT(const TensorT& t) - : TensorBase(1, t.size(), 0, t.get_buffer(), false, false) {} + : TensorT(1, t.size(), 0, t.get_buffer(), false, false) {} TensorT& operator=(const TensorT& t) { - this->size_ = t.size(); + this->width_ = t.size(); this->data_ = t.get_buffer(); } T* get_buffer() { return this->data_; } T& operator[](const int idx) { CHECK(idx >= 0 && idx < this->width_) << "out of index range"; - return this->data_[idx]; + return data_[idx]; + } + T& operator[](const int idx) const { + CHECK(idx >= 0 && idx < this->width_) << "out of index range"; + return data_[idx]; } // TODO: replace with tensorshape size_t size() const { return this->width_; } + +protected: + size_t height_; + size_t width_; + T* data_; }; -// TODO(zhihong): design problem of dynamic datatype, need to fix +// TODO(zhihong): design problem of dynamic datatype, need to fix it typedef TensorT Tensor; } // namespace optimizer diff --git a/paddle/optimizer/Tensor_test.cpp b/paddle/optimizer/Tensor_test.cpp new file mode 100644 index 00000000000..4b7059f9943 --- /dev/null +++ b/paddle/optimizer/Tensor_test.cpp @@ -0,0 +1,21 @@ +#include "Tensor.h" +#include +#include "gtest/gtest.h" + +using namespace paddle; +using namespace paddle::optimizer; + +TEST(Tensor, indexer) { + real* ptr = new real[3]; + Tensor t(ptr, 3); + for (auto i = 0; i < t.size(); ++i) { + t[i] = i; + } + ASSERT_EQ(t[2], 2); + ASSERT_EQ(t[1], 1); +} + +int main(int argc, char** argv) { + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/paddle/optimizer/optimizer.cc b/paddle/optimizer/optimizer.cc index fb2e543bf32..10b3339c2d5 100644 --- a/paddle/optimizer/optimizer.cc +++ b/paddle/optimizer/optimizer.cc @@ -2,6 +2,7 @@ #include #include "parameter_optimizer.h" +using namespace paddle; using namespace paddle::optimizer; template @@ -50,8 +51,8 @@ int paddle_update_parameter(paddle_optimizer* o, const void* grad_buffer, int num_bytes) { // TOOD(zhihong): datatype not work. need to add the runtime datatype - auto grad = reinterpret_cast(grad_buffer); - Tensor gradient(const_cast(grad), num_bytes); + auto grad_type = reinterpret_cast(grad_buffer); + Tensor* gradient = new Tensor(const_cast(grad_type), num_bytes); o->impl->update(gradient); return PADDLE_SUCCESS; } diff --git a/paddle/optimizer/parameter_optimizer.cc b/paddle/optimizer/parameter_optimizer.cc index 6d9fa5c8024..7e4aa42c4bf 100644 --- a/paddle/optimizer/parameter_optimizer.cc +++ b/paddle/optimizer/parameter_optimizer.cc @@ -1,7 +1,7 @@ #include -#include "adadelta_optimizer.h" -#include "adagrad_optimizer.h" -#include "adam_optimizer.h" +// #include "adadelta_optimizer.h" +// #include "adagrad_optimizer.h" +// #include "adam_optimizer.h" #include "lr_policy.h" #include "sgd_optimizer.h" @@ -36,20 +36,20 @@ ParameterOptimizer *ParameterOptimizer::create( config.sgd().nesterov(), lr); } - if (s == "Adadelta") { - return new AdagradOptimizer( - config.adagrad().epsilon(), config.adagrad().decay(), lr); - } - if (s == "Adagrad") { - return new AdagradOptimizer( - config.adagrad().epsilon(), config.adagrad().decay(), lr); - } - if (s == "Adam") { - return new AdadeltaOptimizer(config.adadelta().rho(), - config.adadelta().epsilon(), - config.adadelta().decay(), - lr); - } + // if (s == "Adadelta") { + // return new AdagradOptimizer( + // config.adagrad().epsilon(), config.adagrad().decay(), lr); + // } + // if (s == "Adagrad") { + // return new AdagradOptimizer( + // config.adagrad().epsilon(), config.adagrad().decay(), lr); + // } + // if (s == "Adam") { + // return new AdadeltaOptimizer(config.adadelta().rho(), + // config.adadelta().epsilon(), + // config.adadelta().decay(), + // lr); + // } // default return new SGDOptimizer(config.sgd().momentum(), config.sgd().decay(), diff --git a/paddle/optimizer/sgd_optmizer.cc b/paddle/optimizer/sgd_optmizer.cc index 03ddc814517..020867b93d5 100644 --- a/paddle/optimizer/sgd_optmizer.cc +++ b/paddle/optimizer/sgd_optmizer.cc @@ -16,7 +16,8 @@ void SGDOptimizer::set_weight(Tensor *p) { void SGDOptimizer::update(const Tensor &gradient) { num_sample_passed += 1; double learning_rate = lr_policy->get_learning_rate(num_sample_passed); - double velocity = 0.0; + real velocity = 0.0; + Tensor ¶m = *parameter_; for (size_t i = 0; i < parameter_->size(); ++i) { if (momentum == 0.0) { velocity = -- GitLab From 26e9c4e26fb4322fb4f4fd626bc52530d698d95a Mon Sep 17 00:00:00 2001 From: dzhwinter Date: Mon, 5 Jun 2017 23:21:09 +0800 Subject: [PATCH 0550/3256] "add vector alias to make name clear" --- paddle/optimizer/CMakeLists.txt | 14 ++++++------- paddle/optimizer/adadelta_optimizer.cc | 29 ++++++++++++++------------ paddle/optimizer/adadelta_optimizer.h | 2 +- paddle/optimizer/adagrad_optimizer.cc | 26 +++++++++++------------ paddle/optimizer/adagrad_optimizer.h | 2 +- paddle/optimizer/adam_optimizer.cc | 25 ++++++++++++---------- paddle/optimizer/adam_optimizer.h | 2 +- paddle/optimizer/parameter_optimizer.h | 2 +- paddle/optimizer/sgd_optimizer.h | 2 +- paddle/optimizer/sgd_optmizer.cc | 19 +++++++++-------- 10 files changed, 64 insertions(+), 59 deletions(-) diff --git a/paddle/optimizer/CMakeLists.txt b/paddle/optimizer/CMakeLists.txt index 95d7ad720f3..192d0756202 100644 --- a/paddle/optimizer/CMakeLists.txt +++ b/paddle/optimizer/CMakeLists.txt @@ -1,23 +1,21 @@ include_directories(${CMAKE_CURRENT_BINARY_DIR}) set(OPITMIZER_SRCS - # adadelta_optimizer.cc - # adagrad_optimizer.cc - # adam_optimizer.cc + adadelta_optimizer.cc + adagrad_optimizer.cc + adam_optimizer.cc optimizer.cc parameter_optimizer.cc sgd_optmizer.cc - regularizer.cc ) set(OPITMIZER_Headers - # adadelta_optimizer.h - # adagrad_optimizer.h - # adam_optimizer.h + adadelta_optimizer.h + adagrad_optimizer.h + adam_optimizer.h lr_policy.h optimizer.h parameter_optimizer.h - regularizer.h sgd_optimizer.h Tensor.h ) diff --git a/paddle/optimizer/adadelta_optimizer.cc b/paddle/optimizer/adadelta_optimizer.cc index c5537bde853..f10ee1bcd4b 100644 --- a/paddle/optimizer/adadelta_optimizer.cc +++ b/paddle/optimizer/adadelta_optimizer.cc @@ -1,5 +1,6 @@ #include "adadelta_optimizer.h" #include +#include namespace paddle { namespace optimizer { @@ -7,28 +8,30 @@ namespace optimizer { void AdadeltaOptimizer::set_weight(Tensor* p) { size_t size = p->size(); real* gptr = new real[size]; - accum_gradient = Tensor(gptr, size); + accum_gradient = new Tensor(gptr, size); real* dptr = new real[size]; - accum_delta = Tensor(dptr, size); + accum_delta = new Tensor(dptr, size); real* dptr_current = new real[size]; - update_delta = Tensor(dptr_current, size); + update_delta = new Tensor(dptr_current, size); } -void AdadeltaOptimizer::update(const Tensor& gradient) { +void AdadeltaOptimizer::update(const Tensor* gradient) { num_sample_passed += 1; double learning_rate = lr_policy->get_learning_rate(num_sample_passed); - for (size_t i = 0; i < parameter_->size(); ++i) { - accum_gradient[i] = - rho * accum_gradient[i] + (1.0 - rho) * gradient[i] * gradient[i]; + Tensor& param = *parameter_; + const Tensor& grad = *gradient; + Tensor& accum_g = *accum_gradient; + Tensor& accum_d = *accum_delta; + Tensor& update_d = *update_delta; + for (size_t i = 0; i < param.size(); ++i) { + accum_g[i] = rho * accum_g[i] + (1.0 - rho) * grad[i] * grad[i]; - update_delta[i] = std::sqrt(accum_delta[i] + epsilon) / - std::sqrt(accum_gradient[i] + epsilon) * gradient[i]; + update_d[i] = std::sqrt(accum_d[i] + epsilon) / + std::sqrt(accum_g[i] + epsilon) * grad[i]; - accum_delta[i] = - rho * accum_delta[i] + (1.0 - rho) * update_delta[i] * update_delta[i]; + accum_d[i] = rho * accum_d[i] + (1.0 - rho) * update_d[i] * update_d[i]; - parameter_[i] -= - learning_rate * update_delta[i] + learning_rate * decay * parameter_[i]; + param[i] -= learning_rate * update_d[i] + learning_rate * decay * param[i]; } } } // namespace optimizer diff --git a/paddle/optimizer/adadelta_optimizer.h b/paddle/optimizer/adadelta_optimizer.h index d9db5d09c22..1d8bd5a654c 100644 --- a/paddle/optimizer/adadelta_optimizer.h +++ b/paddle/optimizer/adadelta_optimizer.h @@ -19,7 +19,7 @@ public: if (accum_delta) delete accum_delta; if (update_delta) delete update_delta; } - void update(const Tensor &gradient); + void update(const Tensor *gradient); void set_weight(Tensor *p); real *get_weight() const; diff --git a/paddle/optimizer/adagrad_optimizer.cc b/paddle/optimizer/adagrad_optimizer.cc index 3d47e35896c..437bd4682d5 100644 --- a/paddle/optimizer/adagrad_optimizer.cc +++ b/paddle/optimizer/adagrad_optimizer.cc @@ -1,26 +1,26 @@ +#include + #include "adagrad_optimizer.h" namespace paddle { namespace optimizer { void AdagradOptimizer::set_weight(Tensor* p) { - size_t size = p->width(); + size_t size = p->size(); real* gptr = new real[size]; - accum_gradient = Tensor(gptr, size); - real* dptr = new real[size]; - accum_delta = Tensor(dtpr, size); - real* dptr_current = new real[size]; - update_delta = Tensor(dptr_current, size); + accum_gradient = new Tensor(gptr, size); } -void AdagradOptimizer::update(const Tensor& gradient) { +void AdagradOptimizer::update(const Tensor* gradient) { num_sample_passed += 1; - double learning_rate = lr_policy->get_learning_rate(); - for (size_t i = 0; i < parameter_.size(); ++i) { - accum_gradient[i] += gradient[i] * gradient[i]; - parameter_[i] += - learning_rate * (gradient[i] / std::sqrt(accum_gradient[i] + epsilon) + - decay * parameter_[i]); + double learning_rate = lr_policy->get_learning_rate(num_sample_passed); + Tensor& param = *parameter_; + const Tensor& grad = *gradient; + Tensor& accum_g = *accum_gradient; + for (size_t i = 0; i < param.size(); ++i) { + accum_g[i] += grad[i] * grad[i]; + param[i] += learning_rate * grad[i] / std::sqrt(accum_g[i] + epsilon) + + learning_rate * decay * param[i]; } } diff --git a/paddle/optimizer/adagrad_optimizer.h b/paddle/optimizer/adagrad_optimizer.h index 0f6ce06f35a..aa5f74ffcdf 100644 --- a/paddle/optimizer/adagrad_optimizer.h +++ b/paddle/optimizer/adagrad_optimizer.h @@ -13,7 +13,7 @@ public: ~AdagradOptimizer() { if (accum_gradient) delete accum_gradient; } - void update(const Tensor &gradient); + void update(const Tensor *gradient); void set_weight(Tensor *p); real *get_weight() const; diff --git a/paddle/optimizer/adam_optimizer.cc b/paddle/optimizer/adam_optimizer.cc index d9cc3344d59..6b3f275bf06 100644 --- a/paddle/optimizer/adam_optimizer.cc +++ b/paddle/optimizer/adam_optimizer.cc @@ -1,29 +1,32 @@ #include "adam_optimizer.h" +#include namespace paddle { namespace optimizer { void AdamOptimizer::set_weight(Tensor *p) { - size_t size = p->width(); + size_t size = p->size(); real *mptr = new real[size]; - momentums_ = Tensor(mptr, size); + momentums_ = new Tensor(mptr, size); real *vptr = new real[size]; - velocitys_ = Tensor(vtpr, size); + velocitys_ = new Tensor(vptr, size); } -void AdamOptimizer::update(const Tensor &gradient) { +void AdamOptimizer::update(const Tensor *gradient) { num_sample_passed += 1; double learning_rate = lr_policy->get_learning_rate(num_sample_passed); double coef1 = 1.0 - std::pow(beta_1, num_sample_passed); double coef2 = 1.0 - std::pow(beta_2, num_sample_passed); learning_rate *= std::sqrt(coef2) / coef1; - for (size_t i = 0; i < parameter_->size(); ++i) { - momentums_[i] = beta_1 * momentums_[i] + (1.0 - beta_1) * gradient[i]; - velocitys_[i] = - beta_2 * velocitys_[i] + (1.0 - beta_2) * gradient[i] * gradient[i]; - parameter_[i] -= - learning_rate * (momentums_[i] / std::sqrt(velocitys_[i] + epsilon) + - decay * parameter_[i]); + Tensor ¶m = *parameter_; + const Tensor &grad = *gradient; + Tensor &m = *momentums_; + Tensor &v = *velocitys_; + for (size_t i = 0; i < param.size(); ++i) { + m[i] = beta_1 * m[i] + (1.0 - beta_1) * grad[i]; + v[i] = beta_2 * v[i] + (1.0 - beta_2) * grad[i] * grad[i]; + param[i] -= + learning_rate * (m[i] / std::sqrt(v[i] + epsilon) + decay * param[i]); } } } // namespace optimizer diff --git a/paddle/optimizer/adam_optimizer.h b/paddle/optimizer/adam_optimizer.h index 68e2aa0223e..55a44b032df 100644 --- a/paddle/optimizer/adam_optimizer.h +++ b/paddle/optimizer/adam_optimizer.h @@ -19,7 +19,7 @@ public: if (momentums_) delete momentums_; if (velocitys_) delete velocitys_; } - void update(const Tensor &gradient); + void update(const Tensor *gradient); void set_weight(Tensor *p); real *get_weight() const; diff --git a/paddle/optimizer/parameter_optimizer.h b/paddle/optimizer/parameter_optimizer.h index a4f39836baf..0124cfdc191 100644 --- a/paddle/optimizer/parameter_optimizer.h +++ b/paddle/optimizer/parameter_optimizer.h @@ -24,7 +24,7 @@ public: virtual ~ParameterOptimizer() { delete parameter_; }; static ParameterOptimizer *create(const ::std::string &config_proto); - virtual void update(const Tensor &gradient) = 0; + virtual void update(const Tensor *gradient) = 0; virtual real *get_weight() const; virtual void set_weight(Tensor *parameter); diff --git a/paddle/optimizer/sgd_optimizer.h b/paddle/optimizer/sgd_optimizer.h index 375c99b30b8..4eb483c0fbd 100644 --- a/paddle/optimizer/sgd_optimizer.h +++ b/paddle/optimizer/sgd_optimizer.h @@ -15,7 +15,7 @@ public: SGDOptimizer(double m, double d, bool n, BaseLr* lr) : ParameterOptimizer(lr), momentum(m), decay(d), nesterov(n) {} virtual ~SGDOptimizer() { delete momentums_; } - void update(const Tensor& gradient); + void update(const Tensor* gradient); void set_weight(Tensor* p); real* get_weight() const; diff --git a/paddle/optimizer/sgd_optmizer.cc b/paddle/optimizer/sgd_optmizer.cc index 020867b93d5..5fdfc89c1f8 100644 --- a/paddle/optimizer/sgd_optmizer.cc +++ b/paddle/optimizer/sgd_optmizer.cc @@ -13,24 +13,25 @@ void SGDOptimizer::set_weight(Tensor *p) { } } -void SGDOptimizer::update(const Tensor &gradient) { +void SGDOptimizer::update(const Tensor *gradient) { num_sample_passed += 1; double learning_rate = lr_policy->get_learning_rate(num_sample_passed); real velocity = 0.0; Tensor ¶m = *parameter_; - for (size_t i = 0; i < parameter_->size(); ++i) { + const Tensor &grad = *gradient; + Tensor &m = *momentums_; + for (size_t i = 0; i < param.size(); ++i) { if (momentum == 0.0) { - velocity = - -learning_rate * gradient[i] - learning_rate * decay * parameter_[i]; + velocity = -learning_rate * grad[i] - learning_rate * decay * param[i]; } else { - momentums_[i] = momentum * momentums_[i] - learning_rate * gradient[i] - - learning_rate * decay * parameter_[i]; - velocity = momentums_[i]; + m[i] = momentum * m[i] - learning_rate * grad[i] - + learning_rate * decay * param[i]; + velocity = m[i]; } if (nesterov) { - parameter_[i] += momentum * velocity - learning_rate * gradient[i]; + param[i] += momentum * velocity - learning_rate * grad[i]; } else { - parameter_[i] += velocity; + param[i] += velocity; } } } -- GitLab From 5f9cd8c9ae32fbc7a4e4ad34baab71fff873e9a6 Mon Sep 17 00:00:00 2001 From: dzhwinter Date: Tue, 6 Jun 2017 01:00:15 +0800 Subject: [PATCH 0551/3256] "rename test file name" --- paddle/optimizer/CMakeLists.txt | 2 + paddle/optimizer/optimizer_factory_test.cpp | 31 ----- paddle/optimizer/parameter_optimizer.cc | 44 +++---- paddle/optimizer/parameter_optimizer.h | 3 - paddle/optimizer/parameter_optimizer_test.cpp | 109 ++++++++++++++++++ paddle/optimizer/regularizer.cc | 26 ----- paddle/optimizer/regularizer.h | 45 -------- proto/OptimizerConfig.proto | 33 +++--- 8 files changed, 147 insertions(+), 146 deletions(-) create mode 100644 paddle/optimizer/parameter_optimizer_test.cpp delete mode 100644 paddle/optimizer/regularizer.cc delete mode 100644 paddle/optimizer/regularizer.h diff --git a/paddle/optimizer/CMakeLists.txt b/paddle/optimizer/CMakeLists.txt index 192d0756202..9f8d737cac0 100644 --- a/paddle/optimizer/CMakeLists.txt +++ b/paddle/optimizer/CMakeLists.txt @@ -26,3 +26,5 @@ add_dependencies(optimizer gen_proto_cpp) add_simple_unittest(optimizer_test) add_simple_unittest(optimizer_factory_test) add_simple_unittest(Tensor_test) +add_simple_unittest(parameter_optimizer_test) +add_dependencies(parameter_optimizer_test optimizer) diff --git a/paddle/optimizer/optimizer_factory_test.cpp b/paddle/optimizer/optimizer_factory_test.cpp index 67a9506996f..60e66ad0238 100644 --- a/paddle/optimizer/optimizer_factory_test.cpp +++ b/paddle/optimizer/optimizer_factory_test.cpp @@ -1,32 +1 @@ #include "optimizer_factory.h" -#include "gtest/gtest.h" -#include "parameter_optimizer.h" - -#define float TestType; - -class OptimizerTest : public testing::Test { -public: - virtual void SetUp() { - paddle::OptimizerConfig config; - config.set_learning_rate(0.01); - config.set_decay(0.0); - config.set_momentum(0.0); - config.set_nesterov(false); - config.set_lr_decay_a(0.9); - config.set_lr_decay_b(0.1); - - std::string config_proto = config.SerializeAsString(); - ParameterOptimizer::create(config_proto, ) - } - virtual void TearDown() {} - -private: - ParameterOptimizer* o; -}; - -TEST_F(OptimizerTest, createOptimizer) {} - -int main(int argc, char** argv) { - testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/paddle/optimizer/parameter_optimizer.cc b/paddle/optimizer/parameter_optimizer.cc index 7e4aa42c4bf..cbdccd973ce 100644 --- a/paddle/optimizer/parameter_optimizer.cc +++ b/paddle/optimizer/parameter_optimizer.cc @@ -1,7 +1,7 @@ #include -// #include "adadelta_optimizer.h" -// #include "adagrad_optimizer.h" -// #include "adam_optimizer.h" +#include "adadelta_optimizer.h" +#include "adagrad_optimizer.h" +#include "adam_optimizer.h" #include "lr_policy.h" #include "sgd_optimizer.h" @@ -18,13 +18,13 @@ ParameterOptimizer *ParameterOptimizer::create( auto select_lr_policy = [=](const OptimizerConfig &config) -> BaseLr * { std::string s(config.lr_policy()); - if (s == "ConstLr") return new ConstLr(config.lr_config().learning_rate()); + if (s == "ConstLr") return new ConstLr(config.const_lr().learning_rate()); if (s == "LinearLr") - return new LinearLr(config.lr_config().learning_rate(), - config.lr_config().lr_decay_a(), - config.lr_config().lr_decay_b()); + return new LinearLr(config.linear_lr().learning_rate(), + config.linear_lr().lr_decay_a(), + config.linear_lr().lr_decay_b()); // default - return new ConstLr(config.lr_config().learning_rate()); + return nullptr; }; BaseLr *lr = select_lr_policy(config); auto select_optimizer = @@ -36,20 +36,20 @@ ParameterOptimizer *ParameterOptimizer::create( config.sgd().nesterov(), lr); } - // if (s == "Adadelta") { - // return new AdagradOptimizer( - // config.adagrad().epsilon(), config.adagrad().decay(), lr); - // } - // if (s == "Adagrad") { - // return new AdagradOptimizer( - // config.adagrad().epsilon(), config.adagrad().decay(), lr); - // } - // if (s == "Adam") { - // return new AdadeltaOptimizer(config.adadelta().rho(), - // config.adadelta().epsilon(), - // config.adadelta().decay(), - // lr); - // } + if (s == "Adadelta") { + return new AdagradOptimizer( + config.adagrad().epsilon(), config.adagrad().decay(), lr); + } + if (s == "Adagrad") { + return new AdagradOptimizer( + config.adagrad().epsilon(), config.adagrad().decay(), lr); + } + if (s == "Adam") { + return new AdadeltaOptimizer(config.adadelta().rho(), + config.adadelta().epsilon(), + config.adadelta().decay(), + lr); + } // default return new SGDOptimizer(config.sgd().momentum(), config.sgd().decay(), diff --git a/paddle/optimizer/parameter_optimizer.h b/paddle/optimizer/parameter_optimizer.h index 0124cfdc191..42e460b6762 100644 --- a/paddle/optimizer/parameter_optimizer.h +++ b/paddle/optimizer/parameter_optimizer.h @@ -17,9 +17,6 @@ public: * @brief update hook for algorithm need to traverse parameter more than * once. */ - // use config for pack trainig state - ParameterOptimizer(const OptimizerConfig &config) : config_(config){}; - ParameterOptimizer(BaseLr *lr) : lr_policy(lr), num_sample_passed(0) {} virtual ~ParameterOptimizer() { delete parameter_; }; diff --git a/paddle/optimizer/parameter_optimizer_test.cpp b/paddle/optimizer/parameter_optimizer_test.cpp new file mode 100644 index 00000000000..742e7ec9655 --- /dev/null +++ b/paddle/optimizer/parameter_optimizer_test.cpp @@ -0,0 +1,109 @@ +#include "parameter_optimizer.h" +#include +#include +#include +#include "adadelta_optimizer.h" +#include "adagrad_optimizer.h" +#include "adam_optimizer.h" +#include "gtest/gtest.h" +#include "sgd_optimizer.h" +using namespace paddle; +using namespace paddle::optimizer; + +Tensor* fill_n_Tensor(size_t size) { + real* ptr = new real[size]; + Tensor* param = new Tensor(ptr, size); + Tensor& p = *param; + for (auto i = 0; i < p.size(); ++i) { + p[i] = (float)rand() / (float)RAND_MAX; + } + return param; +} + +Tensor* fix_n_Tensor(size_t size) { + real* ptr = new real[size]; + Tensor* param = new Tensor(ptr, size); + Tensor& p = *param; + for (auto i = 0; i < p.size(); ++i) { + p[i] = i; + } + return param; +} + +class OptimizerTest : public testing::Test { +public: + // init tensor shape + const size_t size = 5; + + virtual void SetUp() { + create_sgd(); + create_adam(); + } + virtual void TearDown() {} + + void create_sgd() { + config.set_optimizer_name("SGD"); + config.mutable_sgd()->set_momentum(0.0); + config.mutable_sgd()->set_decay(0.0); + config.mutable_sgd()->set_nesterov(false); + config.set_lr_policy("ConstLr"); + config.mutable_const_lr()->set_learning_rate(0.1); + + ParameterOptimizer* opt = + ParameterOptimizer::create(config.SerializeAsString()); + opts.push_back(opt); + } + + void create_adam() { + config.set_optimizer_name("Adam"); + config.mutable_adam()->set_beta_1(0.9); + config.mutable_adam()->set_beta_2(0.1); + config.mutable_adam()->set_epsilon(1e-3); + config.mutable_adam()->set_decay(0.0); + config.set_lr_policy("ConstLr"); + config.mutable_const_lr()->set_learning_rate(0.1); + ParameterOptimizer* opt = + ParameterOptimizer::create(config.SerializeAsString()); + opts.push_back(opt); + } + void test_set_weight() { + Tensor* p = fill_n_Tensor(size); + for (size_t i = 0; i < opts.size(); ++i) { + opts[i]->set_weight(p); + } + } + + void test_get_weight() { + Tensor* p = fix_n_Tensor(size); + for (size_t i = 0; i < opts.size(); ++i) { + opts[i]->set_weight(p); + } + for (size_t i = 0; i < opts.size(); ++i) { + real* newp = (real*)opts[i]->get_weight(); + for (size_t j = 0; j < size; ++j) { + EXPECT_EQ(newp[j], (*p)[j]); + } + } + } + void test_update() { + Tensor* g = fix_n_Tensor(size); + for (size_t i = 0; i < opts.size(); ++i) { + opts[i]->update(g); + } + } + +private: + std::vector opts; + OptimizerConfig config; +}; + +TEST_F(OptimizerTest, test_set_get_weight) { + test_set_weight(); + test_get_weight(); +} +TEST_F(OptimizerTest, test_update) { test_update(); } + +int main(int argc, char** argv) { + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/paddle/optimizer/regularizer.cc b/paddle/optimizer/regularizer.cc deleted file mode 100644 index 5724511a827..00000000000 --- a/paddle/optimizer/regularizer.cc +++ /dev/null @@ -1,26 +0,0 @@ -#include "regularizer.h" - -namespace paddle { -namespace optimizer { - -template -Regularizer* Regularizer::create(const std::string& config) { - paddle::OptimizerConfig config; - Regularizer* r; - if (config.regularizer_type() == paddle::OptimizerConfig_RegularizerType_L1) { - r = new L1Regularizer(config); - } else if (config.regularizer_type() == - paddle::OptimizerConfig_RegularizerType_L2) { - r = new L2Regularizer(config); - break; - } - return r; -} - -template class L1Regularizer; -template class L1Regularizer; -template class L2Regularizer; -template class L2Regularizer; - -} // namespace optimizer -} // namespace paddle diff --git a/paddle/optimizer/regularizer.h b/paddle/optimizer/regularizer.h deleted file mode 100644 index e37211ce230..00000000000 --- a/paddle/optimizer/regularizer.h +++ /dev/null @@ -1,45 +0,0 @@ -#ifndef PADDLE_OPITMIZER_REGULARIZER_H_ -#define PADDLE_OPTIMIZER_REGULARIZER_H_ - -#include "OptimizerConfig.pb.h" -#include "Tensor.h" - -namespace paddle { -namespace optimizer { - -/** - * @brief regularizer in L1, L2 - */ - -template -class Regularizer { -public: - /** - * @brief regularizer update interface - * @param param need to update - * @return void - */ - static Regularizer *create(const std::string &config); - virtual void update(Tensor ¶meter) = 0; - -private: - std::string regularizer_name; - OptimizerConfig config_; -}; - -template -class L1Regularizer { -public: - void update(Tensor ¶meter); -}; - -template -class L2Regularizer { -public: - void update(Tensor ¶meter); -}; - -} // namespace optimizer -} // namespace paddle - -#endif diff --git a/proto/OptimizerConfig.proto b/proto/OptimizerConfig.proto index d4242676507..0d7b3407b8e 100644 --- a/proto/OptimizerConfig.proto +++ b/proto/OptimizerConfig.proto @@ -52,7 +52,12 @@ message AdamConfig { optional double decay = 44; } -message LearningRateConfig { +message ConstLr { + // learninRate Policy + required double learning_rate = 40 [default = 1.0]; +} + +message LinearLr { // learninRate Policy required double learning_rate = 40 [default = 1.0]; optional double lr_decay_a = 25; @@ -62,36 +67,26 @@ message LearningRateConfig { message OptimizerConfig { // common config of optimizer + // algorithm config, type : string + // SGD = 1; + // Adadelta = 2; + // Adagrad = 3; + // Adam = 4; required string optimizer_name = 1; - // algorithm config - enum OptimizerType { - SGD = 1; - Adadelta = 2; - Adagrad = 3; - Adam = 4; - } - required OptimizerType optimizer_type = 2; optional SGDConfig sgd = 3; optional AdadeltaConfig adadelta = 4; optional AdagradConfig adagrad = 5; optional AdamConfig adam = 6; // learning rate runtime policy config - // lr_policy : string + // lr_policy , type : string // ConstLr = 0; // LinearLr = 1; required string lr_policy = 11; - required LearningRateConfig lr_config = 12; + optional ConstLr const_lr = 12; + optional LinearLr linear_lr = 15; optional uint64 num_sample_passed = 13 [default = 0]; - // reqularizer config - enum RegularizerType { - L1 = 1; - L2 = 2; - L1L2 = 3; - } - optional RegularizerType regularizer_type = 21; - // common config of optimizer optional double clipnorm = 101; optional double clipvalue = 102; -- GitLab From b9d024e5e1e9345247aaed59c067da8ab9a06775 Mon Sep 17 00:00:00 2001 From: dzhwinter Date: Tue, 6 Jun 2017 01:02:14 +0800 Subject: [PATCH 0552/3256] "remove useless test file" --- paddle/optimizer/CMakeLists.txt | 2 -- paddle/optimizer/optimizer_factory_test.cpp | 1 - 2 files changed, 3 deletions(-) delete mode 100644 paddle/optimizer/optimizer_factory_test.cpp diff --git a/paddle/optimizer/CMakeLists.txt b/paddle/optimizer/CMakeLists.txt index 9f8d737cac0..732c5b88233 100644 --- a/paddle/optimizer/CMakeLists.txt +++ b/paddle/optimizer/CMakeLists.txt @@ -23,8 +23,6 @@ set(OPITMIZER_Headers add_library(optimizer STATIC ${OPITMIZER_SRCS}) add_dependencies(optimizer gen_proto_cpp) -add_simple_unittest(optimizer_test) -add_simple_unittest(optimizer_factory_test) add_simple_unittest(Tensor_test) add_simple_unittest(parameter_optimizer_test) add_dependencies(parameter_optimizer_test optimizer) diff --git a/paddle/optimizer/optimizer_factory_test.cpp b/paddle/optimizer/optimizer_factory_test.cpp deleted file mode 100644 index 60e66ad0238..00000000000 --- a/paddle/optimizer/optimizer_factory_test.cpp +++ /dev/null @@ -1 +0,0 @@ -#include "optimizer_factory.h" -- GitLab From 5ab958ba3b9a49ab8ea9891710bda7c8e6a61a70 Mon Sep 17 00:00:00 2001 From: dzhwinter Date: Tue, 6 Jun 2017 01:14:21 +0800 Subject: [PATCH 0553/3256] "change size_t type to avoid warning" --- paddle/optimizer/Tensor.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/paddle/optimizer/Tensor.h b/paddle/optimizer/Tensor.h index fbfba4806a1..41afacd7565 100644 --- a/paddle/optimizer/Tensor.h +++ b/paddle/optimizer/Tensor.h @@ -23,11 +23,11 @@ public: this->data_ = t.get_buffer(); } T* get_buffer() { return this->data_; } - T& operator[](const int idx) { + T& operator[](const size_t idx) { CHECK(idx >= 0 && idx < this->width_) << "out of index range"; return data_[idx]; } - T& operator[](const int idx) const { + T& operator[](const size_t idx) const { CHECK(idx >= 0 && idx < this->width_) << "out of index range"; return data_[idx]; } -- GitLab From 35d03c847c4492d7c516b3c2f2b57c50edccb0fc Mon Sep 17 00:00:00 2001 From: liaogang Date: Tue, 6 Jun 2017 11:19:22 +0800 Subject: [PATCH 0554/3256] remove VERBOSE in ctest --- 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 9f0f9f2d74d..40e2b723304 100644 --- a/paddle/scripts/docker/build.sh +++ b/paddle/scripts/docker/build.sh @@ -58,7 +58,7 @@ EOF make -j `nproc` if [ ${WITH_TESTING:-OFF} == "ON" ] && [ ${RUN_TEST:-OFF} == "ON" ] ; then pip uninstall -y py-paddle paddle || true - ctest -V + ctest fi -- GitLab From f202d4e036788378f4bd36b2d3dcd7bf50ed24d9 Mon Sep 17 00:00:00 2001 From: liaogang Date: Tue, 6 Jun 2017 11:21:55 +0800 Subject: [PATCH 0555/3256] ctest output on failure --- 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 40e2b723304..2b48e4dc0f8 100644 --- a/paddle/scripts/docker/build.sh +++ b/paddle/scripts/docker/build.sh @@ -58,7 +58,7 @@ EOF make -j `nproc` if [ ${WITH_TESTING:-OFF} == "ON" ] && [ ${RUN_TEST:-OFF} == "ON" ] ; then pip uninstall -y py-paddle paddle || true - ctest + ctest --output-on-failure fi -- GitLab From 235918375de5b4c9ecde9db3b53db97a1ebe1f55 Mon Sep 17 00:00:00 2001 From: Liu Yiqun Date: Mon, 5 Jun 2017 20:55:19 -0700 Subject: [PATCH 0556/3256] Remove the requires of opencv-python. --- python/paddle/utils/image_multiproc.py | 2 +- python/paddle/v2/image.py | 5 ++++- python/setup.py.in | 1 - 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/python/paddle/utils/image_multiproc.py b/python/paddle/utils/image_multiproc.py index 6ce32f7811d..e8db525ff5c 100644 --- a/python/paddle/utils/image_multiproc.py +++ b/python/paddle/utils/image_multiproc.py @@ -12,7 +12,7 @@ from paddle.trainer.config_parser import logger try: import cv2 except ImportError: - logger.warning("OpenCV2 is not installed, using PIL to prcoess") + logger.warning("OpenCV2 is not installed, using PIL to process") cv2 = None __all__ = ["CvTransformer", "PILTransformer", "MultiProcessImageTransformer"] diff --git a/python/paddle/v2/image.py b/python/paddle/v2/image.py index 85ad6984ba0..afcf9a53af4 100644 --- a/python/paddle/v2/image.py +++ b/python/paddle/v2/image.py @@ -3,7 +3,10 @@ try: import cv2 except: print( - "import cv2 error, please install opencv-python: pip install opencv-python" + "import cv2 error, please install python wrapper of opencv using:\n" + " pip install opencv-python\n" + " or\n" + " apt-get install python-opencv\n" ) __all__ = [ diff --git a/python/setup.py.in b/python/setup.py.in index d1c38823080..1afaffd2617 100644 --- a/python/setup.py.in +++ b/python/setup.py.in @@ -19,7 +19,6 @@ setup(name='paddle', "numpy", "protobuf==${PROTOBUF_VERSION}", "matplotlib", - "opencv-python", "rarfile" ], packages=packages, -- GitLab From fd8c51076bf482847bc091198f9bff5f55279cf5 Mon Sep 17 00:00:00 2001 From: dzhwinter Date: Tue, 6 Jun 2017 11:58:54 +0800 Subject: [PATCH 0557/3256] "format name with google style" --- paddle/optimizer/CMakeLists.txt | 6 ++-- paddle/optimizer/Tensor.h | 2 +- paddle/optimizer/Tensor_test.cpp | 3 +- paddle/optimizer/adadelta_optimizer.cc | 29 +++++++++-------- paddle/optimizer/adadelta_optimizer.h | 28 +++++++--------- paddle/optimizer/adagrad_optimizer.cc | 15 +++++---- paddle/optimizer/adagrad_optimizer.h | 14 ++++---- paddle/optimizer/adam_optimizer.cc | 17 +++++----- paddle/optimizer/adam_optimizer.h | 20 ++++++------ paddle/optimizer/lr_policy.h | 26 +++++++-------- paddle/optimizer/optimizer.cc | 4 +-- paddle/optimizer/optimizer_test.cpp | 11 ------- paddle/optimizer/parameter_optimizer.cc | 25 +++++++-------- paddle/optimizer/parameter_optimizer.h | 14 ++++---- paddle/optimizer/parameter_optimizer_test.cpp | 14 ++++---- paddle/optimizer/sgd_optimizer.h | 16 ++++------ paddle/optimizer/sgd_optmizer.cc | 21 ++++++------ proto/OptimizerConfig.proto | 32 ++++++++++--------- 18 files changed, 142 insertions(+), 155 deletions(-) delete mode 100644 paddle/optimizer/optimizer_test.cpp diff --git a/paddle/optimizer/CMakeLists.txt b/paddle/optimizer/CMakeLists.txt index 732c5b88233..0f6c4eb2dd1 100644 --- a/paddle/optimizer/CMakeLists.txt +++ b/paddle/optimizer/CMakeLists.txt @@ -9,7 +9,7 @@ set(OPITMIZER_SRCS sgd_optmizer.cc ) -set(OPITMIZER_Headers +set(OPITMIZER_HEADERS adadelta_optimizer.h adagrad_optimizer.h adam_optimizer.h @@ -17,12 +17,12 @@ set(OPITMIZER_Headers optimizer.h parameter_optimizer.h sgd_optimizer.h - Tensor.h + tensor.h ) add_library(optimizer STATIC ${OPITMIZER_SRCS}) add_dependencies(optimizer gen_proto_cpp) -add_simple_unittest(Tensor_test) +add_simple_unittest(tensor_test) add_simple_unittest(parameter_optimizer_test) add_dependencies(parameter_optimizer_test optimizer) diff --git a/paddle/optimizer/Tensor.h b/paddle/optimizer/Tensor.h index 41afacd7565..a887005cf41 100644 --- a/paddle/optimizer/Tensor.h +++ b/paddle/optimizer/Tensor.h @@ -32,7 +32,7 @@ public: return data_[idx]; } // TODO: replace with tensorshape - size_t size() const { return this->width_; } + size_t size() const { return this->width_ * this->height_; } protected: size_t height_; diff --git a/paddle/optimizer/Tensor_test.cpp b/paddle/optimizer/Tensor_test.cpp index 4b7059f9943..3a21b6d3032 100644 --- a/paddle/optimizer/Tensor_test.cpp +++ b/paddle/optimizer/Tensor_test.cpp @@ -1,6 +1,6 @@ -#include "Tensor.h" #include #include "gtest/gtest.h" +#include "tensor.h" using namespace paddle; using namespace paddle::optimizer; @@ -13,6 +13,7 @@ TEST(Tensor, indexer) { } ASSERT_EQ(t[2], 2); ASSERT_EQ(t[1], 1); + delete ptr; } int main(int argc, char** argv) { diff --git a/paddle/optimizer/adadelta_optimizer.cc b/paddle/optimizer/adadelta_optimizer.cc index f10ee1bcd4b..7381f9d40e7 100644 --- a/paddle/optimizer/adadelta_optimizer.cc +++ b/paddle/optimizer/adadelta_optimizer.cc @@ -6,32 +6,33 @@ namespace paddle { namespace optimizer { void AdadeltaOptimizer::set_weight(Tensor* p) { + parameter_ = p; size_t size = p->size(); real* gptr = new real[size]; - accum_gradient = new Tensor(gptr, size); + accum_gradient_ = new Tensor(gptr, size); real* dptr = new real[size]; - accum_delta = new Tensor(dptr, size); + accum_delta_ = new Tensor(dptr, size); real* dptr_current = new real[size]; - update_delta = new Tensor(dptr_current, size); + update_delta_ = new Tensor(dptr_current, size); } -void AdadeltaOptimizer::update(const Tensor* gradient) { - num_sample_passed += 1; - double learning_rate = lr_policy->get_learning_rate(num_sample_passed); +void AdadeltaOptimizer::Update(const Tensor* gradient) { + num_sample_passed_ += 1; + double learning_rate = lr_policy_->LearningRate(num_sample_passed_); Tensor& param = *parameter_; const Tensor& grad = *gradient; - Tensor& accum_g = *accum_gradient; - Tensor& accum_d = *accum_delta; - Tensor& update_d = *update_delta; + Tensor& accum_g = *accum_gradient_; + Tensor& accum_d = *accum_delta_; + Tensor& update_d = *update_delta_; for (size_t i = 0; i < param.size(); ++i) { - accum_g[i] = rho * accum_g[i] + (1.0 - rho) * grad[i] * grad[i]; + accum_g[i] = rho_ * accum_g[i] + (1.0 - rho_) * grad[i] * grad[i]; - update_d[i] = std::sqrt(accum_d[i] + epsilon) / - std::sqrt(accum_g[i] + epsilon) * grad[i]; + update_d[i] = std::sqrt(accum_d[i] + epsilon_) / + std::sqrt(accum_g[i] + epsilon_) * grad[i]; - accum_d[i] = rho * accum_d[i] + (1.0 - rho) * update_d[i] * update_d[i]; + accum_d[i] = rho_ * accum_d[i] + (1.0 - rho_) * update_d[i] * update_d[i]; - param[i] -= learning_rate * update_d[i] + learning_rate * decay * param[i]; + param[i] -= learning_rate * update_d[i] + learning_rate * decay_ * param[i]; } } } // namespace optimizer diff --git a/paddle/optimizer/adadelta_optimizer.h b/paddle/optimizer/adadelta_optimizer.h index 1d8bd5a654c..20801c3794b 100644 --- a/paddle/optimizer/adadelta_optimizer.h +++ b/paddle/optimizer/adadelta_optimizer.h @@ -8,29 +8,25 @@ namespace optimizer { class AdadeltaOptimizer : public ParameterOptimizer { public: - using ParameterOptimizer::parameter_; - using ParameterOptimizer::num_sample_passed; - using ParameterOptimizer::lr_policy; - - AdadeltaOptimizer(double rho, double epsilon, double decay, BaseLr *lr) - : ParameterOptimizer(lr), rho(rho), epsilon(epsilon), decay(decay) {} + AdadeltaOptimizer(double rho, double epsilon, double decay, LrPolicy *lr) + : ParameterOptimizer(lr), rho_(rho), epsilon_(epsilon), decay_(decay) {} ~AdadeltaOptimizer() { - if (accum_gradient) delete accum_gradient; - if (accum_delta) delete accum_delta; - if (update_delta) delete update_delta; + if (accum_gradient_) delete accum_gradient_; + if (accum_delta_) delete accum_delta_; + if (update_delta_) delete update_delta_; } - void update(const Tensor *gradient); + void Update(const Tensor *gradient); void set_weight(Tensor *p); real *get_weight() const; private: - Tensor *accum_gradient; - Tensor *accum_delta; - Tensor *update_delta; + Tensor *accum_gradient_; + Tensor *accum_delta_; + Tensor *update_delta_; - double rho; - double epsilon; - double decay; + double rho_; + double epsilon_; + double decay_; }; } // namespace optimizer diff --git a/paddle/optimizer/adagrad_optimizer.cc b/paddle/optimizer/adagrad_optimizer.cc index 437bd4682d5..e3a9960e150 100644 --- a/paddle/optimizer/adagrad_optimizer.cc +++ b/paddle/optimizer/adagrad_optimizer.cc @@ -6,21 +6,22 @@ namespace paddle { namespace optimizer { void AdagradOptimizer::set_weight(Tensor* p) { + parameter_ = p; size_t size = p->size(); real* gptr = new real[size]; - accum_gradient = new Tensor(gptr, size); + accum_gradient_ = new Tensor(gptr, size); } -void AdagradOptimizer::update(const Tensor* gradient) { - num_sample_passed += 1; - double learning_rate = lr_policy->get_learning_rate(num_sample_passed); +void AdagradOptimizer::Update(const Tensor* gradient) { + num_sample_passed_ += 1; + double learning_rate = lr_policy_->LearningRate(num_sample_passed_); Tensor& param = *parameter_; + Tensor& accum_g = *accum_gradient_; const Tensor& grad = *gradient; - Tensor& accum_g = *accum_gradient; for (size_t i = 0; i < param.size(); ++i) { accum_g[i] += grad[i] * grad[i]; - param[i] += learning_rate * grad[i] / std::sqrt(accum_g[i] + epsilon) + - learning_rate * decay * param[i]; + param[i] += learning_rate * grad[i] / std::sqrt(accum_g[i] + epsilon_) + + learning_rate * decay_ * param[i]; } } diff --git a/paddle/optimizer/adagrad_optimizer.h b/paddle/optimizer/adagrad_optimizer.h index aa5f74ffcdf..bb64d7d5a77 100644 --- a/paddle/optimizer/adagrad_optimizer.h +++ b/paddle/optimizer/adagrad_optimizer.h @@ -8,19 +8,19 @@ namespace optimizer { class AdagradOptimizer : public ParameterOptimizer { public: - AdagradOptimizer(double epsilon, double decay, BaseLr *lr) - : ParameterOptimizer(lr), epsilon(epsilon), decay(decay) {} + AdagradOptimizer(double epsilon, double decay, LrPolicy *lr) + : ParameterOptimizer(lr), epsilon_(epsilon), decay_(decay) {} ~AdagradOptimizer() { - if (accum_gradient) delete accum_gradient; + if (accum_gradient_) delete accum_gradient_; } - void update(const Tensor *gradient); + void Update(const Tensor *gradient); void set_weight(Tensor *p); real *get_weight() const; private: - Tensor *accum_gradient; - double epsilon; - double decay; + Tensor *accum_gradient_; + double epsilon_; + double decay_; }; } // namespace optimizer diff --git a/paddle/optimizer/adam_optimizer.cc b/paddle/optimizer/adam_optimizer.cc index 6b3f275bf06..ae96b30b948 100644 --- a/paddle/optimizer/adam_optimizer.cc +++ b/paddle/optimizer/adam_optimizer.cc @@ -5,6 +5,7 @@ namespace paddle { namespace optimizer { void AdamOptimizer::set_weight(Tensor *p) { + parameter_ = p; size_t size = p->size(); real *mptr = new real[size]; momentums_ = new Tensor(mptr, size); @@ -12,21 +13,21 @@ void AdamOptimizer::set_weight(Tensor *p) { velocitys_ = new Tensor(vptr, size); } -void AdamOptimizer::update(const Tensor *gradient) { - num_sample_passed += 1; - double learning_rate = lr_policy->get_learning_rate(num_sample_passed); - double coef1 = 1.0 - std::pow(beta_1, num_sample_passed); - double coef2 = 1.0 - std::pow(beta_2, num_sample_passed); +void AdamOptimizer::Update(const Tensor *gradient) { + num_sample_passed_ += 1; + double learning_rate = lr_policy_->LearningRate(num_sample_passed_); + double coef1 = 1.0 - std::pow(beta_1_, num_sample_passed_); + double coef2 = 1.0 - std::pow(beta_2_, num_sample_passed_); learning_rate *= std::sqrt(coef2) / coef1; Tensor ¶m = *parameter_; const Tensor &grad = *gradient; Tensor &m = *momentums_; Tensor &v = *velocitys_; for (size_t i = 0; i < param.size(); ++i) { - m[i] = beta_1 * m[i] + (1.0 - beta_1) * grad[i]; - v[i] = beta_2 * v[i] + (1.0 - beta_2) * grad[i] * grad[i]; + m[i] = beta_1_ * m[i] + (1.0 - beta_1_) * grad[i]; + v[i] = beta_2_ * v[i] + (1.0 - beta_2_) * grad[i] * grad[i]; param[i] -= - learning_rate * (m[i] / std::sqrt(v[i] + epsilon) + decay * param[i]); + learning_rate * (m[i] / std::sqrt(v[i] + epsilon_) + decay_ * param[i]); } } } // namespace optimizer diff --git a/paddle/optimizer/adam_optimizer.h b/paddle/optimizer/adam_optimizer.h index 55a44b032df..89e68346d58 100644 --- a/paddle/optimizer/adam_optimizer.h +++ b/paddle/optimizer/adam_optimizer.h @@ -9,27 +9,27 @@ namespace optimizer { class AdamOptimizer : public ParameterOptimizer { public: AdamOptimizer( - double beta_1, double beta_2, double epsilon, double decay, BaseLr *lr) + double beta_1, double beta_2, double epsilon, double decay, LrPolicy *lr) : ParameterOptimizer(lr), - beta_1(beta_1), - beta_2(beta_2), - epsilon(epsilon), - decay(decay) {} + beta_1_(beta_1), + beta_2_(beta_2), + epsilon_(epsilon), + decay_(decay) {} ~AdamOptimizer() { if (momentums_) delete momentums_; if (velocitys_) delete velocitys_; } - void update(const Tensor *gradient); + void Update(const Tensor *gradient); void set_weight(Tensor *p); real *get_weight() const; private: Tensor *momentums_; Tensor *velocitys_; - double beta_1; - double beta_2; - double epsilon; - double decay; + double beta_1_; + double beta_2_; + double epsilon_; + double decay_; }; } // namespace optimizer diff --git a/paddle/optimizer/lr_policy.h b/paddle/optimizer/lr_policy.h index e1017cf32dc..b24a17ced01 100644 --- a/paddle/optimizer/lr_policy.h +++ b/paddle/optimizer/lr_policy.h @@ -7,34 +7,34 @@ namespace paddle { namespace optimizer { -class BaseLr { +class LrPolicy { public: - BaseLr(double lr) : learning_rate(lr) {} - virtual ~BaseLr() {} - virtual double get_learning_rate(const uint64_t num_sample_passed) = 0; - -protected: - double learning_rate; + virtual ~LrPolicy() {} + virtual double LearningRate(const uint64_t num_sample_passed) = 0; }; // constant learning rate policy -class ConstLr final : public BaseLr { +class ConstLr final : public LrPolicy { public: - ConstLr(double lr) : BaseLr(lr){}; - double get_learning_rate(const uint64_t num_sample_passed) { + ConstLr(double lr) : learning_rate(lr){}; + double LearningRate(const uint64_t num_sample_passed) { return learning_rate; } + +protected: + double learning_rate; }; -class LinearLr final : public BaseLr { +class LinearLr final : public LrPolicy { public: LinearLr(double lr, double lr_decay_a, double lr_decay_b) - : BaseLr(lr), lr_decay_a(lr_decay_a), lr_decay_b(lr_decay_b) {} - double get_learning_rate(const uint64_t num_sample_passed) { + : learning_rate(lr), lr_decay_a(lr_decay_a), lr_decay_b(lr_decay_b) {} + double LearningRate(const uint64_t num_sample_passed) { return std::max(learning_rate - lr_decay_a * num_sample_passed, lr_decay_b); } private: + double learning_rate; double lr_decay_a; double lr_decay_b; }; diff --git a/paddle/optimizer/optimizer.cc b/paddle/optimizer/optimizer.cc index 10b3339c2d5..e9bcdcd8016 100644 --- a/paddle/optimizer/optimizer.cc +++ b/paddle/optimizer/optimizer.cc @@ -37,7 +37,7 @@ paddle_optimizer* paddle_create_optimizer(const unsigned char* config_proto, int config_proto_len) { paddle_optimizer* optimizer = new paddle_optimizer; std::string config(config_proto, config_proto + config_proto_len); - optimizer->impl = ParameterOptimizer::create(config); + optimizer->impl = ParameterOptimizer::Create(config); return optimizer; } @@ -53,7 +53,7 @@ int paddle_update_parameter(paddle_optimizer* o, // TOOD(zhihong): datatype not work. need to add the runtime datatype auto grad_type = reinterpret_cast(grad_buffer); Tensor* gradient = new Tensor(const_cast(grad_type), num_bytes); - o->impl->update(gradient); + o->impl->Update(gradient); return PADDLE_SUCCESS; } diff --git a/paddle/optimizer/optimizer_test.cpp b/paddle/optimizer/optimizer_test.cpp deleted file mode 100644 index 1bdc6f40fca..00000000000 --- a/paddle/optimizer/optimizer_test.cpp +++ /dev/null @@ -1,11 +0,0 @@ -#include "optimizer.h" -#include "gtest/gtest.h" - -template -class Opitmizer_C_Test : public testing::Test { -private: - Tensor parameter; - Tensor gradient; -}; - -void applyGradientDescent_TEST() {} diff --git a/paddle/optimizer/parameter_optimizer.cc b/paddle/optimizer/parameter_optimizer.cc index cbdccd973ce..00e9b858551 100644 --- a/paddle/optimizer/parameter_optimizer.cc +++ b/paddle/optimizer/parameter_optimizer.cc @@ -10,41 +10,40 @@ namespace paddle { namespace optimizer { -ParameterOptimizer *ParameterOptimizer::create( - const ::std::string &config_proto) { +ParameterOptimizer *ParameterOptimizer::Create( + const std::string &config_proto) { paddle::OptimizerConfig config; CHECK(config.ParseFromString(config_proto) == 0) - << "error : optimizer config"; + << "failed parse optimizer config"; - auto select_lr_policy = [=](const OptimizerConfig &config) -> BaseLr * { - std::string s(config.lr_policy()); - if (s == "ConstLr") return new ConstLr(config.const_lr().learning_rate()); - if (s == "LinearLr") + auto select_lr_policy = [=](const OptimizerConfig &config) -> LrPolicy * { + if (config.lr_policy() == OptimizerConfig::ConstLr) + return new ConstLr(config.const_lr().learning_rate()); + if (config.lr_policy() == OptimizerConfig::LinearLr) return new LinearLr(config.linear_lr().learning_rate(), config.linear_lr().lr_decay_a(), config.linear_lr().lr_decay_b()); // default return nullptr; }; - BaseLr *lr = select_lr_policy(config); + LrPolicy *lr = select_lr_policy(config); auto select_optimizer = [=](const OptimizerConfig &config) -> ParameterOptimizer * { - std::string s(config.optimizer_name()); - if (s == "SGD") { + if (config.optimizer() == OptimizerConfig::SGD) { return new SGDOptimizer(config.sgd().momentum(), config.sgd().decay(), config.sgd().nesterov(), lr); } - if (s == "Adadelta") { + if (config.optimizer() == OptimizerConfig::Adadelta) { return new AdagradOptimizer( config.adagrad().epsilon(), config.adagrad().decay(), lr); } - if (s == "Adagrad") { + if (config.optimizer() == OptimizerConfig::Adagrad) { return new AdagradOptimizer( config.adagrad().epsilon(), config.adagrad().decay(), lr); } - if (s == "Adam") { + if (config.optimizer() == OptimizerConfig::Adam) { return new AdadeltaOptimizer(config.adadelta().rho(), config.adadelta().epsilon(), config.adadelta().decay(), diff --git a/paddle/optimizer/parameter_optimizer.h b/paddle/optimizer/parameter_optimizer.h index 42e460b6762..69e964069b4 100644 --- a/paddle/optimizer/parameter_optimizer.h +++ b/paddle/optimizer/parameter_optimizer.h @@ -5,8 +5,8 @@ #include #include #include "OptimizerConfig.pb.h" -#include "Tensor.h" #include "lr_policy.h" +#include "tensor.h" namespace paddle { namespace optimizer { @@ -17,21 +17,21 @@ public: * @brief update hook for algorithm need to traverse parameter more than * once. */ - ParameterOptimizer(BaseLr *lr) : lr_policy(lr), num_sample_passed(0) {} + ParameterOptimizer(LrPolicy *lr) : lr_policy_(lr), num_sample_passed_(0) {} virtual ~ParameterOptimizer() { delete parameter_; }; - static ParameterOptimizer *create(const ::std::string &config_proto); - virtual void update(const Tensor *gradient) = 0; + static ParameterOptimizer *Create(const std::string &config_proto); + virtual void Update(const Tensor *gradient) = 0; virtual real *get_weight() const; virtual void set_weight(Tensor *parameter); -public: +protected: OptimizerConfig config_; Tensor *parameter_; // learning rate policy - BaseLr *lr_policy; - uint64_t num_sample_passed; + LrPolicy *lr_policy_; + uint64_t num_sample_passed_; }; } // namespace optimizer diff --git a/paddle/optimizer/parameter_optimizer_test.cpp b/paddle/optimizer/parameter_optimizer_test.cpp index 742e7ec9655..cc791483431 100644 --- a/paddle/optimizer/parameter_optimizer_test.cpp +++ b/paddle/optimizer/parameter_optimizer_test.cpp @@ -42,28 +42,28 @@ public: virtual void TearDown() {} void create_sgd() { - config.set_optimizer_name("SGD"); + config.set_optimizer(OptimizerConfig::SGD); config.mutable_sgd()->set_momentum(0.0); config.mutable_sgd()->set_decay(0.0); config.mutable_sgd()->set_nesterov(false); - config.set_lr_policy("ConstLr"); + config.set_lr_policy(OptimizerConfig::ConstLr); config.mutable_const_lr()->set_learning_rate(0.1); ParameterOptimizer* opt = - ParameterOptimizer::create(config.SerializeAsString()); + ParameterOptimizer::Create(config.SerializeAsString()); opts.push_back(opt); } void create_adam() { - config.set_optimizer_name("Adam"); + config.set_optimizer(OptimizerConfig::Adam); config.mutable_adam()->set_beta_1(0.9); config.mutable_adam()->set_beta_2(0.1); config.mutable_adam()->set_epsilon(1e-3); config.mutable_adam()->set_decay(0.0); - config.set_lr_policy("ConstLr"); + config.set_lr_policy(OptimizerConfig::ConstLr); config.mutable_const_lr()->set_learning_rate(0.1); ParameterOptimizer* opt = - ParameterOptimizer::create(config.SerializeAsString()); + ParameterOptimizer::Create(config.SerializeAsString()); opts.push_back(opt); } void test_set_weight() { @@ -88,7 +88,7 @@ public: void test_update() { Tensor* g = fix_n_Tensor(size); for (size_t i = 0; i < opts.size(); ++i) { - opts[i]->update(g); + opts[i]->Update(g); } } diff --git a/paddle/optimizer/sgd_optimizer.h b/paddle/optimizer/sgd_optimizer.h index 4eb483c0fbd..1f6728d61e3 100644 --- a/paddle/optimizer/sgd_optimizer.h +++ b/paddle/optimizer/sgd_optimizer.h @@ -8,23 +8,19 @@ namespace optimizer { class SGDOptimizer : public ParameterOptimizer { public: - using ParameterOptimizer::parameter_; - using ParameterOptimizer::num_sample_passed; - using ParameterOptimizer::lr_policy; - - SGDOptimizer(double m, double d, bool n, BaseLr* lr) - : ParameterOptimizer(lr), momentum(m), decay(d), nesterov(n) {} + SGDOptimizer(double m, double d, bool n, LrPolicy* lr) + : ParameterOptimizer(lr), momentum_(m), decay_(d), nesterov_(n) {} virtual ~SGDOptimizer() { delete momentums_; } - void update(const Tensor* gradient); + void Update(const Tensor* gradient); void set_weight(Tensor* p); real* get_weight() const; private: Tensor* momentums_; - double momentum; - double decay; - bool nesterov; + double momentum_; + double decay_; + bool nesterov_; }; } // namespace optimizer diff --git a/paddle/optimizer/sgd_optmizer.cc b/paddle/optimizer/sgd_optmizer.cc index 5fdfc89c1f8..c58ab5bbe2b 100644 --- a/paddle/optimizer/sgd_optmizer.cc +++ b/paddle/optimizer/sgd_optmizer.cc @@ -5,31 +5,32 @@ namespace optimizer { void SGDOptimizer::set_weight(Tensor *p) { // ParameterOptimizer::set_weight(p); + parameter_ = p; size_t size = p->size(); // TODO: fix it with align aware allocator bind to Tensor - if (momentum != 0.0) { + if (momentum_ != 0.0) { real *ptr = new real[size]; momentums_ = new Tensor(ptr, size); } } -void SGDOptimizer::update(const Tensor *gradient) { - num_sample_passed += 1; - double learning_rate = lr_policy->get_learning_rate(num_sample_passed); +void SGDOptimizer::Update(const Tensor *gradient) { + num_sample_passed_ += 1; + double learning_rate = lr_policy_->LearningRate(num_sample_passed_); real velocity = 0.0; Tensor ¶m = *parameter_; const Tensor &grad = *gradient; Tensor &m = *momentums_; for (size_t i = 0; i < param.size(); ++i) { - if (momentum == 0.0) { - velocity = -learning_rate * grad[i] - learning_rate * decay * param[i]; + if (momentum_ == 0.0) { + velocity = -learning_rate * grad[i] - learning_rate * decay_ * param[i]; } else { - m[i] = momentum * m[i] - learning_rate * grad[i] - - learning_rate * decay * param[i]; + m[i] = momentum_ * m[i] - learning_rate * grad[i] - + learning_rate * decay_ * param[i]; velocity = m[i]; } - if (nesterov) { - param[i] += momentum * velocity - learning_rate * grad[i]; + if (nesterov_) { + param[i] += momentum_ * velocity - learning_rate * grad[i]; } else { param[i] += velocity; } diff --git a/proto/OptimizerConfig.proto b/proto/OptimizerConfig.proto index 0d7b3407b8e..5dd26373379 100644 --- a/proto/OptimizerConfig.proto +++ b/proto/OptimizerConfig.proto @@ -54,38 +54,40 @@ message AdamConfig { message ConstLr { // learninRate Policy - required double learning_rate = 40 [default = 1.0]; + required double learning_rate = 1 [default = 1.0]; } message LinearLr { // learninRate Policy - required double learning_rate = 40 [default = 1.0]; - optional double lr_decay_a = 25; - optional double lr_decay_b = 26; + required double learning_rate = 1 [default = 1.0]; + optional double lr_decay_a = 2; + optional double lr_decay_b = 3; } message OptimizerConfig { // common config of optimizer // algorithm config, type : string - // SGD = 1; - // Adadelta = 2; - // Adagrad = 3; - // Adam = 4; - required string optimizer_name = 1; + enum Optimizer { + SGD = 1; + Adadelta = 2; + Adagrad = 3; + Adam = 4; + } + required Optimizer optimizer = 1; optional SGDConfig sgd = 3; optional AdadeltaConfig adadelta = 4; optional AdagradConfig adagrad = 5; optional AdamConfig adam = 6; // learning rate runtime policy config - // lr_policy , type : string - // ConstLr = 0; - // LinearLr = 1; - required string lr_policy = 11; + enum LrPolicy { + ConstLr = 0; + LinearLr = 1; + } + required LrPolicy lr_policy = 11; optional ConstLr const_lr = 12; - optional LinearLr linear_lr = 15; - optional uint64 num_sample_passed = 13 [default = 0]; + optional LinearLr linear_lr = 13; // common config of optimizer optional double clipnorm = 101; -- GitLab From d99faf310865fe500083f0db53063e53efd2731f Mon Sep 17 00:00:00 2001 From: hedaoyuan Date: Tue, 6 Jun 2017 12:51:30 +0800 Subject: [PATCH 0558/3256] Add the calculation implementation of GemmConvGradInputFunction. --- paddle/function/ConvOpTest.cpp | 7 +- paddle/function/GemmConvOp.cpp | 142 +++++++++++++++++++++++++++---- paddle/function/GemmConvOp.h | 18 ++++ paddle/function/GemmConvOpGpu.cu | 93 ++++++++++++++++++++ 4 files changed, 242 insertions(+), 18 deletions(-) diff --git a/paddle/function/ConvOpTest.cpp b/paddle/function/ConvOpTest.cpp index e2997df0128..2fa0b365465 100644 --- a/paddle/function/ConvOpTest.cpp +++ b/paddle/function/ConvOpTest.cpp @@ -78,12 +78,10 @@ public: test.addOutputs(BufferArg(VALUE_TYPE_FLOAT, output)); test.run(); } else if (type == BACKWARD_INPUT_TEST) { -#if 0 test.addInputs(BufferArg(VALUE_TYPE_FLOAT, output)); test.addInputs(BufferArg(VALUE_TYPE_FLOAT, filter)); test.addOutputs(BufferArg(VALUE_TYPE_FLOAT, input)); test.run(); -#endif } else if (type == BACKWARD_FILTER_TEST) { test.addInputs(BufferArg(VALUE_TYPE_FLOAT, output)); test.addInputs(BufferArg(VALUE_TYPE_FLOAT, input)); @@ -111,6 +109,11 @@ TEST(Forward, GEMM2) { "GemmConv-CPU", "GemmConv-GPU", FORWARD_TEST); } +TEST(BackwardInput, GEMM) { + ConvolutionTest test( + "GemmConvGradInput-CPU", "GemmConvGradInput-GPU", BACKWARD_INPUT_TEST); +} + TEST(BackwardFilter, GEMM) { ConvolutionTest test( "GemmConvGradFilter-CPU", "GemmConvGradFilter-GPU", BACKWARD_FILTER_TEST); diff --git a/paddle/function/GemmConvOp.cpp b/paddle/function/GemmConvOp.cpp index 414c7a885b6..bb7bc647792 100644 --- a/paddle/function/GemmConvOp.cpp +++ b/paddle/function/GemmConvOp.cpp @@ -44,22 +44,62 @@ public: for (int c = 0; c < channelsCol; ++c) { int wOffset = c % filterWidth; int hOffset = (c / filterWidth) % filterHeight; - int c_im = c / filterHeight / filterWidth; + int c_im = c / filterWidth / filterHeight; for (int h = 0; h < outputHeight; ++h) { for (int w = 0; w < outputWidth; ++w) { - // no c_im*height to Exclude the channel number - int imgRowIdx = h * strideHeight + hOffset; - int imgColIdx = w * strideWidth + wOffset; - if ((imgRowIdx - paddingHeight) < 0 || - (imgRowIdx - paddingHeight) >= inputHeight || - (imgColIdx - paddingWidth) < 0 || - (imgColIdx - paddingWidth) >= inputWidth) { + int imRowIdx = h * strideHeight + hOffset; + int imColIdx = w * strideWidth + wOffset; + if ((imRowIdx - paddingHeight) < 0 || + (imRowIdx - paddingHeight) >= inputHeight || + (imColIdx - paddingWidth) < 0 || + (imColIdx - paddingWidth) >= inputWidth) { colData[(c * outputHeight + h) * outputWidth + w] = T(0); } else { - imgRowIdx += c_im * inputHeight - paddingHeight; - imgColIdx -= paddingWidth; + imRowIdx += c_im * inputHeight - paddingHeight; + imColIdx -= paddingWidth; colData[(c * outputHeight + h) * outputWidth + w] = - imData[imgRowIdx * inputWidth + imgColIdx]; + imData[imRowIdx * inputWidth + imColIdx]; + } + } + } + } + } +}; + +template +class Col2ImFunctor { +public: + void operator()(const T* colData, + int inputChannels, + int inputHeight, + int inputWidth, + int filterHeight, + int filterWidth, + int strideHeight, + int strideWidth, + int paddingHeight, + int paddingWidth, + int outputHeight, + int outputWidth, + T* imData) { + int channelsCol = inputChannels * filterHeight * filterWidth; + + for (int c = 0; c < channelsCol; ++c) { + int wOffset = c % filterWidth; + int hOffset = (c / filterWidth) % filterHeight; + int c_im = c / filterWidth / filterHeight; + for (int h = 0; h < outputHeight; ++h) { + for (int w = 0; w < outputWidth; ++w) { + int imRowIdx = h * strideHeight + hOffset; + int imColIdx = w * strideWidth + wOffset; + if ((imRowIdx - paddingHeight) >= 0 && + (imRowIdx - paddingHeight) < inputHeight && + (imColIdx - paddingWidth) >= 0 && + (imColIdx - paddingWidth) < inputWidth) { + imRowIdx += c_im * inputHeight - paddingHeight; + imColIdx -= paddingWidth; + imData[imRowIdx * inputWidth + imColIdx] += + colData[(c * outputHeight + h) * outputWidth + w]; } } } @@ -171,10 +211,74 @@ public: void calc(const BufferArgs& inputs, const BufferArgs& outputs) override { CHECK_EQ(numInputs_, inputs.size()); CHECK_EQ(numOutputs_, outputs.size()); - const TensorShape& outputGrad = inputs[0].shape(); + // CHECK_EQ(outputs[0].getArgType(), ADD_TO); + const TensorShape& output = inputs[0].shape(); const TensorShape& filter = inputs[1].shape(); - const TensorShape& inputGrad = outputs[0].shape(); - check(inputGrad, filter, outputGrad); + const TensorShape& input = outputs[0].shape(); + check(input, filter, output); + + size_t batchSize = input[0]; + size_t inputChannels = input[1]; + size_t inputHeight = input[2]; + size_t inputWidth = input[3]; + size_t filterHeight = filter[2]; + size_t filterWidth = filter[3]; + size_t outputChannels = output[1]; + size_t outputHeight = output[2]; + size_t outputWidth = output[3]; + + real* outputGrad = inputs[0].data(); + real* filterData = inputs[1].data(); + real* inputGrad = outputs[0].data(); + + size_t size = inputChannels / groups_ * filterHeight * filterWidth * + outputHeight * outputWidth; + resizeBuffer(size); + real* colData = reinterpret_cast(memory_->getBuf()); + + Col2ImFunctor col2im; + GemmFunctor gemm; + size_t inputOffset = (inputChannels / groups_) * inputHeight * inputWidth; + size_t outputOffset = + (outputChannels / groups_) * outputHeight * outputWidth; + size_t filterOffset = filter.getElements() / groups_; + + for (size_t i = 0; i < batchSize; i++) { + for (size_t g = 0; g < groups_; g++) { + int K = outputChannels / groups_; + int N = outputHeight * outputWidth; + int M = inputChannels / groups_ * filterHeight * filterWidth; + gemm(CblasTrans, + CblasNoTrans, + M, + N, + K, + 1.0f, + filterData + g * filterOffset, + M, + outputGrad + g * outputOffset, + N, + 0.0f, + colData, + N); + + col2im(colData, + inputChannels / groups_, + inputHeight, + inputWidth, + filterHeight, + filterWidth, + strideH(), + strideW(), + paddingH(), + paddingW(), + outputHeight, + outputWidth, + inputGrad + g * inputOffset); + } + inputGrad += inputChannels * inputHeight * inputWidth; + outputGrad += outputChannels * outputHeight * outputWidth; + } } }; @@ -191,12 +295,18 @@ public: void calc(const BufferArgs& inputs, const BufferArgs& outputs) override { CHECK_EQ(numInputs_, inputs.size()); CHECK_EQ(numOutputs_, outputs.size()); - CHECK_EQ(outputs[0].getArgType(), ASSIGN_TO); const TensorShape& output = inputs[0].shape(); const TensorShape& input = inputs[1].shape(); const TensorShape& filter = outputs[0].shape(); check(input, filter, output); + real beta; + if (outputs[0].getArgType() == ADD_TO) { + beta = 1.0; + } else { + beta = 0.0; + } + size_t batchSize = input[0]; size_t inputChannels = input[1]; size_t inputHeight = input[2]; @@ -251,7 +361,7 @@ public: K, colData, K, - 1.0f, + i == 0 ? beta : 1.0f, filterGrad + g * filterOffset, N); } diff --git a/paddle/function/GemmConvOp.h b/paddle/function/GemmConvOp.h index 652a64afba4..9f11cce597a 100644 --- a/paddle/function/GemmConvOp.h +++ b/paddle/function/GemmConvOp.h @@ -41,4 +41,22 @@ public: T* colData); }; +template +class Col2ImFunctor { +public: + void operator()(const T* colData, + int inputChannels, + int inputHeight, + int inputWidth, + int filterHeight, + int filterWidth, + int strideHeight, + int strideWidth, + int paddingHeight, + int paddingWidth, + int outputHeight, + int outputWidth, + T* imData); +}; + } // namespace paddle diff --git a/paddle/function/GemmConvOpGpu.cu b/paddle/function/GemmConvOpGpu.cu index 06b9904261c..2a1795ff0fb 100644 --- a/paddle/function/GemmConvOpGpu.cu +++ b/paddle/function/GemmConvOpGpu.cu @@ -87,7 +87,100 @@ public: } }; +template +__global__ +void col2im(size_t n, const T* data_col, size_t height, + size_t width, size_t channels, + size_t blockH, size_t blockW, + size_t strideH, size_t strideW, + size_t paddingH, size_t paddingW, + size_t height_col, size_t width_col, + T* data_im) { + size_t index = + (blockIdx.x * gridDim.y + blockIdx.y) * blockDim.x + threadIdx.x; + if (index < n) { + T val = 0; + int w = int(index % width); + int h = int((index / width) % height); + int c = int(index / (width * height)); + if ((w - (int)paddingW) >= 0 && + (w - (int)paddingW) < (width-2 * paddingW) && + (h - (int)paddingH) >= 0 && + (h - paddingH) < (height - 2 * paddingH)) { + // compute the start and end of the output + int w_col_start = + (w < (int)blockW) ? 0 : (w - int(blockW)) / (int)strideW + 1; + int w_col_end = + min((int)(w / (int)strideW + 1), (int)(width_col)); + int h_col_start = + (h < (int)blockH) ? 0 : (h - (int)blockH) / (int)strideH + 1; + int h_col_end = min(int(h / strideH + 1), int(height_col)); + for (int h_col = h_col_start; h_col < h_col_end; ++h_col) { + for (int w_col = w_col_start; w_col < w_col_end; ++w_col) { + // the col location: [c * width * height + h_out, w_out] + int c_col = int(c * blockH* blockW) + \ + (h - h_col * (int)strideH) * (int)blockW + + (w - w_col * (int)strideW); + val += data_col[(c_col * height_col + h_col) * width_col + w_col]; + } + } + h -= paddingH; + w -= paddingW; + data_im[c*((width-2*paddingW) * (height-2*paddingH)) + + h*(width-2*paddingW) + w] += val; + } + } +} + +template +class Col2ImFunctor { +public: + void operator()(const T* colData, + int inputChannels, + int inputHeight, + int inputWidth, + int filterHeight, + int filterWidth, + int strideHeight, + int strideWidth, + int paddingHeight, + int paddingWidth, + int outputHeight, + int outputWidth, + T* imData) { + size_t numKernels = inputChannels * (inputHeight + 2*paddingHeight) + * (inputWidth + 2*paddingWidth); + + size_t blocks = (numKernels + 1024 -1) / 1024; + size_t blockX = 512; + size_t blockY = (blocks+512-1)/512; + dim3 threads(1024, 1); + dim3 grid(blockX, blockY); + + // To avoid involving atomic operations, we will launch one kernel per + // bottom dimension, and then in the kernel add up the top dimensions. + col2im<<< grid, threads, 0, STREAM_DEFAULT >>> + (numKernels, + colData, + inputHeight + 2*paddingHeight, + inputWidth + 2*paddingWidth, + inputChannels, + filterHeight, + filterWidth, + strideHeight, + strideWidth, + paddingHeight, + paddingWidth, + outputHeight, + outputWidth, + imData); + CHECK_SYNC("Col2ImFunctor GPU failed"); + } +}; + template class Im2ColFunctor; template class Im2ColFunctor; +template class Col2ImFunctor; +template class Col2ImFunctor; } // namespace paddle -- GitLab From 9885c578d758b2cdb9334e4905a4842283985f5a Mon Sep 17 00:00:00 2001 From: hedaoyuan Date: Tue, 6 Jun 2017 12:53:39 +0800 Subject: [PATCH 0559/3256] format --- paddle/function/GemmConvOp.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paddle/function/GemmConvOp.cpp b/paddle/function/GemmConvOp.cpp index bb7bc647792..9dab831d32c 100644 --- a/paddle/function/GemmConvOp.cpp +++ b/paddle/function/GemmConvOp.cpp @@ -239,7 +239,7 @@ public: Col2ImFunctor col2im; GemmFunctor gemm; size_t inputOffset = (inputChannels / groups_) * inputHeight * inputWidth; - size_t outputOffset = + size_t outputOffset = (outputChannels / groups_) * outputHeight * outputWidth; size_t filterOffset = filter.getElements() / groups_; -- GitLab From 366ea1d879d88c43aecc6c9eed14ca0e19e61fcb Mon Sep 17 00:00:00 2001 From: Liu Yiqun Date: Mon, 5 Jun 2017 23:29:22 -0700 Subject: [PATCH 0560/3256] Remove DYNAMIC_ARCH when building openblas for arm-based archiectures. --- cmake/cudnn.cmake | 17 +++++------------ cmake/external/openblas.cmake | 27 ++++++++++++++++----------- 2 files changed, 21 insertions(+), 23 deletions(-) diff --git a/cmake/cudnn.cmake b/cmake/cudnn.cmake index 92dce20c698..69f40df5168 100644 --- a/cmake/cudnn.cmake +++ b/cmake/cudnn.cmake @@ -11,23 +11,16 @@ find_path(CUDNN_INCLUDE_DIR cudnn.h get_filename_component(__libpath_hist ${CUDA_CUDART_LIBRARY} PATH) -if(NOT ${CMAKE_HOST_SYSTEM_PROCESSOR}) - execute_process( - COMMAND uname -m COMMAND tr -d '\n' - OUTPUT_VARIABLE HOST_ARCH - RESULT_VARIABLE UNAME_RESULT) - if(${UNAME_RESULT}) - set(HOST_ARCH "x86_64") - endif(${UNAME_RESULT}) -else(NOT ${CMAKE_HOST_SYSTEM_PROCESSOR}) - set(HOST_ARCH ${CMAKE_HOST_SYSTEM_PROCESSOR}) -endif(NOT ${CMAKE_HOST_SYSTEM_PROCESSOR}) +set(TARGET_ARCH "x86_64") +if(NOT ${CMAKE_SYSTEM_PROCESSOR}) + set(TARGET_ARCH ${CMAKE_SYSTEM_PROCESSOR}) +endif() list(APPEND CUDNN_CHECK_LIBRARY_DIRS ${CUDNN_ROOT} ${CUDNN_ROOT}/lib64 ${CUDNN_ROOT}/lib - ${CUDNN_ROOT}/lib/${HOST_ARCH}-linux-gnu + ${CUDNN_ROOT}/lib/${TARGET_ARCH}-linux-gnu $ENV{CUDNN_ROOT} $ENV{CUDNN_ROOT}/lib64 $ENV{CUDNN_ROOT}/lib diff --git a/cmake/external/openblas.cmake b/cmake/external/openblas.cmake index cb67793cf97..46a2dca4426 100644 --- a/cmake/external/openblas.cmake +++ b/cmake/external/openblas.cmake @@ -24,20 +24,25 @@ IF(NOT ${CBLAS_FOUND}) SET(CBLAS_LIBRARIES "${CBLAS_INSTALL_DIR}/lib/${LIBRARY_PREFIX}openblas${STATIC_LIBRARY_SUFFIX}" CACHE FILEPATH "openblas library." FORCE) - SET(COMMON_ARGS CC=${CMAKE_C_COMPILER} NO_SHARED=1 NO_LAPACK=1) + SET(COMMON_ARGS CC=${CMAKE_C_COMPILER} NO_SHARED=1 NO_LAPACK=1 libs) - 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") - SET(OPTIONAL_ARGS HOSTCC=${HOST_C_COMPILER} TARGET=ARMV7 ARM_SOFTFP_ABI=1 USE_THREAD=0 libs) - ELSEIF(RPI) - # use hardfp - SET(OPENBLAS_COMMIT "v0.2.19") - SET(OPTIONAL_ARGS HOSTCC=${HOST_C_COMPILER} TARGET=ARMV7 USE_THREAD=0 libs) + IF(CMAKE_CROSSCOMPILING) + 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") + SET(OPTIONAL_ARGS HOSTCC=${HOST_C_COMPILER} TARGET=ARMV7 ARM_SOFTFP_ABI=1 USE_THREAD=0) + ELSEIF(RPI) + # use hardfp + SET(OPENBLAS_COMMIT "v0.2.19") + SET(OPTIONAL_ARGS HOSTCC=${HOST_C_COMPILER} TARGET=ARMV7 USE_THREAD=0) + ENDIF() ELSE() SET(OPENBLAS_COMMIT "v0.2.19") - SET(OPTIONAL_ARGS DYNAMIC_ARCH=1 libs NUM_THREADS=64) + SET(OPTIONAL_ARGS "") + IF(CMAKE_SYSTEM_PROCESSOR MATCHES "^x86(-64)?$") + SET(OPTIONAL_ARGS DYNAMIC_ARCH=1 NUM_THREADS=64) + ENDIF() ENDIF() ExternalProject_Add( -- GitLab From e5d33e7760f56adb135de4c322a4420851da0639 Mon Sep 17 00:00:00 2001 From: Liu Yiqun Date: Tue, 6 Jun 2017 06:53:41 +0000 Subject: [PATCH 0561/3256] Fix typo. --- cmake/external/openblas.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/external/openblas.cmake b/cmake/external/openblas.cmake index 46a2dca4426..2341e3785bd 100644 --- a/cmake/external/openblas.cmake +++ b/cmake/external/openblas.cmake @@ -40,7 +40,7 @@ IF(NOT ${CBLAS_FOUND}) ELSE() SET(OPENBLAS_COMMIT "v0.2.19") SET(OPTIONAL_ARGS "") - IF(CMAKE_SYSTEM_PROCESSOR MATCHES "^x86(-64)?$") + IF(CMAKE_SYSTEM_PROCESSOR MATCHES "^x86(_64)?$") SET(OPTIONAL_ARGS DYNAMIC_ARCH=1 NUM_THREADS=64) ENDIF() ENDIF() -- GitLab From 7aac38c794ffb1df10db4d61ac4350a32bdc62ed Mon Sep 17 00:00:00 2001 From: hedaoyuan Date: Tue, 6 Jun 2017 15:45:20 +0800 Subject: [PATCH 0562/3256] Refactoring the code implementation of exconv adn exconvt layer with GemmConvFunction. --- paddle/gserver/layers/ConvBaseLayer.cpp | 6 +- paddle/gserver/layers/CudnnConvBaseLayer.cpp | 10 +- paddle/gserver/layers/ExpandConvLayer.cpp | 104 +++++++++++++++--- paddle/gserver/layers/ExpandConvLayer.h | 5 + .../gserver/layers/ExpandConvTransLayer.cpp | 2 +- 5 files changed, 98 insertions(+), 29 deletions(-) diff --git a/paddle/gserver/layers/ConvBaseLayer.cpp b/paddle/gserver/layers/ConvBaseLayer.cpp index 7b234dc2a66..e161d89c38a 100644 --- a/paddle/gserver/layers/ConvBaseLayer.cpp +++ b/paddle/gserver/layers/ConvBaseLayer.cpp @@ -118,11 +118,7 @@ size_t ConvBaseLayer::calOutputSize() { layerSize = outH[0] * outW[0] * size_t(numFilters_); }; - if (isDeconv_) { - setLayerSize(outputH_, outputW_, imgSizeH_, imgSizeW_); - } else { - setLayerSize(imgSizeH_, imgSizeW_, outputH_, outputW_); - } + setLayerSize(imgSizeH_, imgSizeW_, outputH_, outputW_); return layerSize; } diff --git a/paddle/gserver/layers/CudnnConvBaseLayer.cpp b/paddle/gserver/layers/CudnnConvBaseLayer.cpp index 24363bb8b09..c056bbe4d1d 100644 --- a/paddle/gserver/layers/CudnnConvBaseLayer.cpp +++ b/paddle/gserver/layers/CudnnConvBaseLayer.cpp @@ -70,14 +70,8 @@ void CudnnConvBaseLayer::forward(PassType passType) { if (biases_) { REGISTER_TIMER_INFO("CudnnConvBiasTimer", getName().c_str()); int batchSize = inputLayers_[0]->getOutputValue()->getHeight(); - int outH, outW; - if (isDeconv_) { - outH = imgSizeH_[0]; - outW = imgSizeW_[0]; - } else { - outH = outputH_[0]; - outW = outputW_[0]; - } + int outH = outputH_[0]; + int outW = outputW_[0]; hl_tensor_reshape(outputDesc_, batchSize, diff --git a/paddle/gserver/layers/ExpandConvLayer.cpp b/paddle/gserver/layers/ExpandConvLayer.cpp index f9267b81a7d..379b2d339d4 100644 --- a/paddle/gserver/layers/ExpandConvLayer.cpp +++ b/paddle/gserver/layers/ExpandConvLayer.cpp @@ -18,32 +18,90 @@ limitations under the License. */ namespace paddle { +/* + * The calculation of the exconvt(convolution transpose (deconv) operation) + * is a swap of forward and backward of the calculation of exconv. + * */ REGISTER_LAYER(exconv, ExpandConvLayer); +REGISTER_LAYER(exconvt, ExpandConvLayer); bool ExpandConvLayer::init(const LayerMap &layerMap, const ParameterMap ¶meterMap) { /* Initialize the basic convolutional parent class */ ExpandConvBaseLayer::init(layerMap, parameterMap); + + size_t numInputs = config_.inputs_size(); + inputShape_.resize(numInputs); + filterShape_.resize(numInputs); + outputShape_.resize(numInputs); + for (int i = 0; i < config_.inputs_size(); i++) { + std::vector paddings = {(size_t)paddingY_[i], (size_t)padding_[i]}; + std::vector strides = {(size_t)strideY_[i], (size_t)stride_[i]}; + createFunction(forward_, + !isDeconv_ ? "GemmConv" : "GemmConvGradInput", + FuncConfig() + .set("paddings", paddings) + .set("strides", strides) + .set("groups", (size_t)groups_[i])); + + createFunction(backward_, + !isDeconv_ ? "GemmConvGradInput" : "GemmConv", + FuncConfig() + .set("paddings", paddings) + .set("strides", strides) + .set("groups", (size_t)groups_[i])); + + createFunction(backward_, + "GemmConvGradFilter", + FuncConfig() + .set("paddings", paddings) + .set("strides", strides) + .set("groups", (size_t)groups_[i])); + } return true; } +// i is the index of input layers +#define BACKWARD_INPUT(i, inputs, outputs) \ + backward_[2 * i]->calc(inputs, outputs) +#define BACKWARD_FILTER(i, inputs, outputs) \ + backward_[2 * i + 1]->calc(inputs, outputs) + void ExpandConvLayer::forward(PassType passType) { Layer::forward(passType); - /* malloc memory for the output_ if necessary */ - int batchSize = inputLayers_[0]->getOutputValue()->getHeight(); + size_t batchSize = inputLayers_[0]->getOutputValue()->getHeight(); resetOutput(batchSize, getOutputSize()); - MatrixPtr image = nullptr; - MatrixPtr outV = getOutputValue(); + // Calculate the shape of the input, output, and filter. for (size_t i = 0; i < inputLayers_.size(); ++i) { - LayerPtr prevLayer = getPrev(i); - image = prevLayer->getOutputValue(); - for (size_t off = 0; off < image->getHeight(); off++) { - REGISTER_TIMER_INFO("expandFwdOnce", getName().c_str()); - expandFwdOnce(image, outV, i, off); - } + inputShape_[i] = TensorShape({(size_t)batchSize, + (size_t)channels_[i], + (size_t)imgSizeH_[i], + (size_t)imgSizeW_[i]}); + filterShape_[i] = + TensorShape({!isDeconv_ ? (size_t)numFilters_ : (size_t)channels_[i], + !isDeconv_ ? (size_t)channels_[i] : (size_t)numFilters_, + (size_t)filterSizeY_[i], + (size_t)filterSize_[i]}); + outputShape_[i] = TensorShape({(size_t)batchSize, + (size_t)numFilters_, + (size_t)outputH_[i], + (size_t)outputW_[i]}); } + + // Calculate the output value. + for (size_t i = 0; i < inputLayers_.size(); ++i) { + BufferArgs inputs; + BufferArgs outputs; + inputs.addArg(*getInputValue(i), inputShape_[i]); + inputs.addArg(*weights_[i]->getW(), filterShape_[i]); + outputs.addArg( + *getOutputValue(), outputShape_[i], i == 0 ? ASSIGN_TO : ADD_TO); + + forward_[i]->calc(inputs, outputs); + } + /* add the bias-vector */ if (biases_.get()) { if (sharedBiases_) { @@ -67,14 +125,30 @@ void ExpandConvLayer::backward(const UpdateCallback &callback) { biases_->getParameterPtr()->incUpdate(callback); } + // Calculate the input grad and filter grad. for (size_t i = 0; i < inputLayers_.size(); ++i) { - /* First, calculate the input layers error */ - if (getPrev(i)->getOutputGrad()) { - bpropActs(outGrad, getPrev(i)->getOutputGrad(), i); + if (getInputGrad(i)) { + BufferArgs inputs; + BufferArgs outputs; + inputs.addArg(*getOutputGrad(), outputShape_[i]); + inputs.addArg(*weights_[i]->getW(), filterShape_[i]); + outputs.addArg(*getInputGrad(i), inputShape_[i], ADD_TO); + BACKWARD_INPUT(i, inputs, outputs); } + if (weights_[i]->getWGrad()) { - /* Then, calculate the W-gradient for the current layer */ - bpropWeights(getPrev(i)->getOutputValue(), outGrad, i); + BufferArgs inputs; + BufferArgs outputs; + if (!isDeconv_) { + inputs.addArg(*getOutputGrad(), outputShape_[i]); + inputs.addArg(*getInputValue(i), inputShape_[i]); + } else { + inputs.addArg(*getInputValue(i), inputShape_[i]); + inputs.addArg(*getOutputGrad(), outputShape_[i]); + } + outputs.addArg(*weights_[i]->getWGrad(), filterShape_[i], ADD_TO); + BACKWARD_FILTER(i, inputs, outputs); + /* Increasing the number of gradient */ weights_[i]->getParameterPtr()->incUpdate(callback); } diff --git a/paddle/gserver/layers/ExpandConvLayer.h b/paddle/gserver/layers/ExpandConvLayer.h index 60681690e5d..a1f943d1521 100644 --- a/paddle/gserver/layers/ExpandConvLayer.h +++ b/paddle/gserver/layers/ExpandConvLayer.h @@ -40,6 +40,11 @@ public: void forward(PassType passType) override; void backward(const UpdateCallback& callback) override; + +protected: + std::vector inputShape_; + std::vector filterShape_; + std::vector outputShape_; }; } // namespace paddle diff --git a/paddle/gserver/layers/ExpandConvTransLayer.cpp b/paddle/gserver/layers/ExpandConvTransLayer.cpp index 520586b1388..b80a01e3287 100644 --- a/paddle/gserver/layers/ExpandConvTransLayer.cpp +++ b/paddle/gserver/layers/ExpandConvTransLayer.cpp @@ -23,7 +23,7 @@ limitations under the License. */ namespace paddle { -REGISTER_LAYER(exconvt, ExpandConvTransLayer); +// REGISTER_LAYER(exconvt, ExpandConvTransLayer); bool ExpandConvTransLayer::init(const LayerMap &layerMap, const ParameterMap ¶meterMap) { -- GitLab From 6e8c5665b4a2d47ea9de2d347408d0d86d47e50d Mon Sep 17 00:00:00 2001 From: dangqingqing Date: Tue, 6 Jun 2017 16:52:35 +0800 Subject: [PATCH 0563/3256] Add layers into doc. --- doc/api/v2/config/layer.rst | 11 +++++++++++ paddle/function/RowConvOpGpu.cu | 4 ++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/doc/api/v2/config/layer.rst b/doc/api/v2/config/layer.rst index 1efa74ecda4..7c22b677752 100644 --- a/doc/api/v2/config/layer.rst +++ b/doc/api/v2/config/layer.rst @@ -59,6 +59,11 @@ context_projection .. autoclass:: paddle.v2.layer.context_projection :noindex: +row_conv +-------- +.. autoclass:: paddle.v2.layer.row_conv + :noindex: + Image Pooling Layer =================== @@ -346,6 +351,12 @@ sampling_id .. autoclass:: paddle.v2.layer.sampling_id :noindex: +multiplex +--------- +.. autoclass:: paddle.v2.layer.multiplex + :noindex: + + Slicing and Joining Layers ========================== diff --git a/paddle/function/RowConvOpGpu.cu b/paddle/function/RowConvOpGpu.cu index 06e2c2baac2..c0b947e2243 100644 --- a/paddle/function/RowConvOpGpu.cu +++ b/paddle/function/RowConvOpGpu.cu @@ -312,8 +312,8 @@ void RowConvGrad(const GpuMatrix& outG, dim3 dimBlock(32, 32); dim3 dimGrid(DIVUP(width, dimBlock.x), 1); real* dw = filterG.getData(); - if (contextLength <= 16) { - KeRowConvBwWeight<32, 32, 16> + if (contextLength <= 32) { + KeRowConvBwWeight<32, 32, 32> <<>> (dw, x, dy, starts, height, width, numSeq, contextLength); } else { -- GitLab From 3b1294ae48efd6118948f359eced8b4209dbe19a Mon Sep 17 00:00:00 2001 From: dzhwinter Date: Tue, 6 Jun 2017 17:02:05 +0800 Subject: [PATCH 0564/3256] "add checkpoint interface: set state, get state" --- paddle/optimizer/optimizer.cc | 13 ++++++- paddle/optimizer/optimizer.h | 6 +++- paddle/optimizer/parameter_optimizer.h | 4 +++ paddle/optimizer/parameter_optimizer_test.cpp | 30 ++++++++-------- paddle/optimizer/serialization.h | 36 +++++++++++++++++++ paddle/optimizer/sgd_optimizer.h | 2 ++ paddle/optimizer/sgd_optmizer.cc | 27 ++++++++++++++ proto/OptimizerConfig.proto | 20 +++++++++++ 8 files changed, 121 insertions(+), 17 deletions(-) create mode 100644 paddle/optimizer/serialization.h diff --git a/paddle/optimizer/optimizer.cc b/paddle/optimizer/optimizer.cc index e9bcdcd8016..5076029494c 100644 --- a/paddle/optimizer/optimizer.cc +++ b/paddle/optimizer/optimizer.cc @@ -34,10 +34,16 @@ struct paddle_optimizer { }; paddle_optimizer* paddle_create_optimizer(const unsigned char* config_proto, - int config_proto_len) { + const int config_proto_len, + const char** state, + const int state_size) { paddle_optimizer* optimizer = new paddle_optimizer; std::string config(config_proto, config_proto + config_proto_len); optimizer->impl = ParameterOptimizer::Create(config); + if (state != nullptr) { + std::string s(*state, *state + state_size); + optimizer->impl->DeSerializeState(s); + } return optimizer; } @@ -71,3 +77,8 @@ void* paddle_optimizer_get_weights(paddle_optimizer* o) { void* buffer = (void*)o->impl->get_weight(); return buffer; } + +int paddle_optimizer_get_state(paddle_optimizer* o, const char* state) { + state = o->impl->SerializeState(); + return PADDLE_SUCCESS; +} diff --git a/paddle/optimizer/optimizer.h b/paddle/optimizer/optimizer.h index a2c2b13405b..c3328331fc3 100644 --- a/paddle/optimizer/optimizer.h +++ b/paddle/optimizer/optimizer.h @@ -45,7 +45,9 @@ typedef struct paddle_optimizer paddle_optimizer; * @return return optimizer instance */ paddle_optimizer* paddle_create_optimizer(const unsigned char* config_proto, - int config_proto_len); + const int config_proto_len, + const char** state, + const int state_size); /** * @brief release optimizer @@ -86,6 +88,8 @@ int paddle_optimizer_set_weights(paddle_optimizer* o, */ void* paddle_optimizer_get_weights(paddle_optimizer* o); +int paddle_optimizer_get_state(paddle_optimizer* o, const char* state); + #ifdef __cplusplus } #endif diff --git a/paddle/optimizer/parameter_optimizer.h b/paddle/optimizer/parameter_optimizer.h index 69e964069b4..e60c7778205 100644 --- a/paddle/optimizer/parameter_optimizer.h +++ b/paddle/optimizer/parameter_optimizer.h @@ -11,6 +11,8 @@ namespace paddle { namespace optimizer { +const std::string kOptimizerVersion = "1.0"; + class ParameterOptimizer { public: /** @@ -21,6 +23,8 @@ public: virtual ~ParameterOptimizer() { delete parameter_; }; static ParameterOptimizer *Create(const std::string &config_proto); + virtual const char *SerializeState(); + virtual void DeSerializeState(const std::string &state); virtual void Update(const Tensor *gradient) = 0; virtual real *get_weight() const; virtual void set_weight(Tensor *parameter); diff --git a/paddle/optimizer/parameter_optimizer_test.cpp b/paddle/optimizer/parameter_optimizer_test.cpp index cc791483431..2b3ad84ca95 100644 --- a/paddle/optimizer/parameter_optimizer_test.cpp +++ b/paddle/optimizer/parameter_optimizer_test.cpp @@ -10,7 +10,7 @@ using namespace paddle; using namespace paddle::optimizer; -Tensor* fill_n_Tensor(size_t size) { +Tensor* FillTensor(size_t size) { real* ptr = new real[size]; Tensor* param = new Tensor(ptr, size); Tensor& p = *param; @@ -20,7 +20,7 @@ Tensor* fill_n_Tensor(size_t size) { return param; } -Tensor* fix_n_Tensor(size_t size) { +Tensor* FixedTensor(size_t size) { real* ptr = new real[size]; Tensor* param = new Tensor(ptr, size); Tensor& p = *param; @@ -36,12 +36,12 @@ public: const size_t size = 5; virtual void SetUp() { - create_sgd(); - create_adam(); + CreateSGD(); + CreateAdam(); } virtual void TearDown() {} - void create_sgd() { + void CreateSGD() { config.set_optimizer(OptimizerConfig::SGD); config.mutable_sgd()->set_momentum(0.0); config.mutable_sgd()->set_decay(0.0); @@ -54,7 +54,7 @@ public: opts.push_back(opt); } - void create_adam() { + void CreateAdam() { config.set_optimizer(OptimizerConfig::Adam); config.mutable_adam()->set_beta_1(0.9); config.mutable_adam()->set_beta_2(0.1); @@ -66,15 +66,15 @@ public: ParameterOptimizer::Create(config.SerializeAsString()); opts.push_back(opt); } - void test_set_weight() { - Tensor* p = fill_n_Tensor(size); + void TestSetWeight() { + Tensor* p = FillTensor(size); for (size_t i = 0; i < opts.size(); ++i) { opts[i]->set_weight(p); } } - void test_get_weight() { - Tensor* p = fix_n_Tensor(size); + void TestGetWeight() { + Tensor* p = FixedTensor(size); for (size_t i = 0; i < opts.size(); ++i) { opts[i]->set_weight(p); } @@ -85,8 +85,8 @@ public: } } } - void test_update() { - Tensor* g = fix_n_Tensor(size); + void TestUpdate() { + Tensor* g = FixedTensor(size); for (size_t i = 0; i < opts.size(); ++i) { opts[i]->Update(g); } @@ -98,10 +98,10 @@ private: }; TEST_F(OptimizerTest, test_set_get_weight) { - test_set_weight(); - test_get_weight(); + TestSetWeight(); + TestGetWeight(); } -TEST_F(OptimizerTest, test_update) { test_update(); } +TEST_F(OptimizerTest, TestUpdate) { TestUpdate(); } int main(int argc, char** argv) { testing::InitGoogleTest(&argc, argv); diff --git a/paddle/optimizer/serialization.h b/paddle/optimizer/serialization.h new file mode 100644 index 00000000000..edb90d0a164 --- /dev/null +++ b/paddle/optimizer/serialization.h @@ -0,0 +1,36 @@ +#ifndef PADDLE_OPTIMIZER_SERIALIZARION_H +#define PADDLE_OPTIMIZER_SERIALIZARION_H + +#include +#include +#include "OptimizerConfig.pb.h" +#include "paddle/utils/Logging.h" +#include "tensor.h" + +namespace paddle { +namespace optimizer { + +static void TensorToProto(const Tensor& tensor, TensorProto* proto) { + proto->set_data_type(TensorProto::PADDLE_ELEMENT_TYPE_FLOAT32); + proto->set_size(tensor.size()); + std::stringstream os; + for (size_t i = 0; i < tensor.size(); ++i) { + os << tensor[i]; + proto->add_content(os.str()); + os.clear(); + } +} + +static void ProtoToTensor(const TensorProto& proto, Tensor* tensor) { + CHECK(proto.size() == tensor->size()) << "unmatch shape of proto and tensor"; + std::stringstream sin; + for (auto i = 0; i < proto.content_size(); ++i) { + sin << proto.content(i); + sin >> (*tensor)[i]; + sin.clear(); + } +} + +} // namespace optimizer +} // namespace paddle +#endif diff --git a/paddle/optimizer/sgd_optimizer.h b/paddle/optimizer/sgd_optimizer.h index 1f6728d61e3..284d0a4d0c7 100644 --- a/paddle/optimizer/sgd_optimizer.h +++ b/paddle/optimizer/sgd_optimizer.h @@ -12,6 +12,8 @@ public: : ParameterOptimizer(lr), momentum_(m), decay_(d), nesterov_(n) {} virtual ~SGDOptimizer() { delete momentums_; } void Update(const Tensor* gradient); + const char* SerializeState(); + void DeSerializeState(const std::string& state); void set_weight(Tensor* p); real* get_weight() const; diff --git a/paddle/optimizer/sgd_optmizer.cc b/paddle/optimizer/sgd_optmizer.cc index c58ab5bbe2b..f4fa7756eab 100644 --- a/paddle/optimizer/sgd_optmizer.cc +++ b/paddle/optimizer/sgd_optmizer.cc @@ -1,3 +1,4 @@ +#include "serialization.h" #include "sgd_optimizer.h" namespace paddle { @@ -37,5 +38,31 @@ void SGDOptimizer::Update(const Tensor *gradient) { } } +const char *SGDOptimizer::SerializeState() { + OptimizerState state; + // version is a global const value + state.set_version(kOptimizerVersion); + TensorToProto(*parameter_, state.add_data()); + TensorToProto(*momentums_, state.add_data()); + // state.add_data(param_proto); + // state.add_data(momentum_proto); + state.add_hyperparam(momentum_); + return state.SerializeAsString().c_str(); +} + +void SGDOptimizer::DeSerializeState(const std::string &str) { + OptimizerState state; + state.ParseFromString(str); + CHECK(state.version() == kOptimizerVersion) + << "error version of state" + << "expected : " << kOptimizerVersion << "get : " << state.version(); + + ProtoToTensor(state.data(0), parameter_); + if (state.data_size() == 2) { + ProtoToTensor(state.data(1), momentums_); + momentum_ = state.hyperparam(0); + } +} + } // namespace optimizer } // namespace paddle diff --git a/proto/OptimizerConfig.proto b/proto/OptimizerConfig.proto index 5dd26373379..f492364a5aa 100644 --- a/proto/OptimizerConfig.proto +++ b/proto/OptimizerConfig.proto @@ -64,6 +64,26 @@ message LinearLr { optional double lr_decay_b = 3; } +message TensorProto { +enum DataType { + PADDLE_ELEMENT_TYPE_INT32 = 0; + PADDLE_ELEMENT_TYPE_UINT32 = 1; + PADDLE_ELEMENT_TYPE_INT64 = 2; + PADDLE_ELEMENT_TYPE_UINT64 = 3; + PADDLE_ELEMENT_TYPE_FLOAT32 = 4; + PADDLE_ELEMENT_TYPE_FLOAT64 = 5; +} + required DataType data_type = 1; + repeated bytes content = 2; + optional uint64 size = 3; +} + +message OptimizerState { + // match old training state with format parser + required string version = 100; + repeated TensorProto data = 1; + repeated double hyperparam = 3; +} message OptimizerConfig { // common config of optimizer -- GitLab From 81cad374916ca12b26d7acb5ced9bfd8f55ec7d8 Mon Sep 17 00:00:00 2001 From: dzhwinter Date: Tue, 6 Jun 2017 17:27:03 +0800 Subject: [PATCH 0565/3256] "remove comments" --- paddle/optimizer/sgd_optmizer.cc | 3 --- proto/OptimizerConfig.proto | 2 -- 2 files changed, 5 deletions(-) diff --git a/paddle/optimizer/sgd_optmizer.cc b/paddle/optimizer/sgd_optmizer.cc index f4fa7756eab..a222672815b 100644 --- a/paddle/optimizer/sgd_optmizer.cc +++ b/paddle/optimizer/sgd_optmizer.cc @@ -5,7 +5,6 @@ namespace paddle { namespace optimizer { void SGDOptimizer::set_weight(Tensor *p) { - // ParameterOptimizer::set_weight(p); parameter_ = p; size_t size = p->size(); // TODO: fix it with align aware allocator bind to Tensor @@ -44,8 +43,6 @@ const char *SGDOptimizer::SerializeState() { state.set_version(kOptimizerVersion); TensorToProto(*parameter_, state.add_data()); TensorToProto(*momentums_, state.add_data()); - // state.add_data(param_proto); - // state.add_data(momentum_proto); state.add_hyperparam(momentum_); return state.SerializeAsString().c_str(); } diff --git a/proto/OptimizerConfig.proto b/proto/OptimizerConfig.proto index f492364a5aa..16b041a9b25 100644 --- a/proto/OptimizerConfig.proto +++ b/proto/OptimizerConfig.proto @@ -87,7 +87,6 @@ message OptimizerState { message OptimizerConfig { // common config of optimizer - // algorithm config, type : string enum Optimizer { SGD = 1; Adadelta = 2; @@ -100,7 +99,6 @@ message OptimizerConfig { optional AdagradConfig adagrad = 5; optional AdamConfig adam = 6; - // learning rate runtime policy config enum LrPolicy { ConstLr = 0; LinearLr = 1; -- GitLab From 96204c3c3a430787a37c3cb0054e852d4cedbd79 Mon Sep 17 00:00:00 2001 From: Liu Yiqun Date: Tue, 6 Jun 2017 03:22:11 -0700 Subject: [PATCH 0566/3256] Choose to disable the dependency of opencv-python on arm-based platforms. --- python/setup.py.in | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/python/setup.py.in b/python/setup.py.in index 1afaffd2617..93724f91880 100644 --- a/python/setup.py.in +++ b/python/setup.py.in @@ -11,16 +11,19 @@ packages=['paddle', 'paddle.v2.reader', 'paddle.v2.plot'] +setup_requires=["requests", + "numpy", + "protobuf==3.1", + "matplotlib", + "rarfile"] + +if '${CMAKE_SYSTEM_PROCESSOR}' not in ['arm', 'armv7-a', 'aarch64']: + setup_requires+=["opencv-python"] + setup(name='paddle', version='${PADDLE_VERSION}', description='Parallel Distributed Deep Learning', - install_requires=[ - "requests", - "numpy", - "protobuf==${PROTOBUF_VERSION}", - "matplotlib", - "rarfile" - ], + install_requires=setup_requires, packages=packages, package_dir={ '': '${CMAKE_CURRENT_SOURCE_DIR}' -- GitLab From beb269781d4a36084e3d5c199e8eeb5fb93a3357 Mon Sep 17 00:00:00 2001 From: dzhwinter Date: Tue, 6 Jun 2017 23:36:52 +0800 Subject: [PATCH 0567/3256] "change header guard to pragma" --- paddle/optimizer/CMakeLists.txt | 11 ----------- paddle/optimizer/Tensor.h | 6 ++---- paddle/optimizer/adadelta_optimizer.h | 5 +---- paddle/optimizer/adagrad_optimizer.h | 5 +---- paddle/optimizer/adam_optimizer.h | 4 +--- paddle/optimizer/lr_policy.h | 5 +---- paddle/optimizer/optimizer.h | 5 ++--- paddle/optimizer/parameter_optimizer.h | 5 +---- paddle/optimizer/serialization.h | 4 +--- 9 files changed, 10 insertions(+), 40 deletions(-) diff --git a/paddle/optimizer/CMakeLists.txt b/paddle/optimizer/CMakeLists.txt index 0f6c4eb2dd1..746f4a69f84 100644 --- a/paddle/optimizer/CMakeLists.txt +++ b/paddle/optimizer/CMakeLists.txt @@ -9,17 +9,6 @@ set(OPITMIZER_SRCS sgd_optmizer.cc ) -set(OPITMIZER_HEADERS - adadelta_optimizer.h - adagrad_optimizer.h - adam_optimizer.h - lr_policy.h - optimizer.h - parameter_optimizer.h - sgd_optimizer.h - tensor.h - ) - add_library(optimizer STATIC ${OPITMIZER_SRCS}) add_dependencies(optimizer gen_proto_cpp) diff --git a/paddle/optimizer/Tensor.h b/paddle/optimizer/Tensor.h index a887005cf41..3dbb3ca05de 100644 --- a/paddle/optimizer/Tensor.h +++ b/paddle/optimizer/Tensor.h @@ -1,10 +1,10 @@ -#ifndef PADDLE_OPTIMIZER_TENSOR_H_ -#define PADDLE_OPTIMIZER_TENSOR_H_ +#pragma once /** * @brief tensor used by optimizer */ #include +#include #include "paddle/utils/Common.h" #include "paddle/utils/Logging.h" @@ -45,5 +45,3 @@ typedef TensorT Tensor; } // namespace optimizer } // namespace paddle - -#endif diff --git a/paddle/optimizer/adadelta_optimizer.h b/paddle/optimizer/adadelta_optimizer.h index 20801c3794b..d17fae6b70a 100644 --- a/paddle/optimizer/adadelta_optimizer.h +++ b/paddle/optimizer/adadelta_optimizer.h @@ -1,5 +1,4 @@ -#ifndef PADDLE_ADADELTA_OPTIMIZER_H_ -#define PADDLE_ADADELTA_OPTIMIZER_H_ +#pragma once #include "parameter_optimizer.h" @@ -31,5 +30,3 @@ private: } // namespace optimizer } // namespace paddle - -#endif diff --git a/paddle/optimizer/adagrad_optimizer.h b/paddle/optimizer/adagrad_optimizer.h index bb64d7d5a77..6c00665c86c 100644 --- a/paddle/optimizer/adagrad_optimizer.h +++ b/paddle/optimizer/adagrad_optimizer.h @@ -1,5 +1,4 @@ -#ifndef PADDLE_ADAGRAD_OPTIMIZER_H_ -#define PADDLE_ADAGRAD_OPTIMIZER_H_ +#pragma once #include "parameter_optimizer.h" @@ -25,5 +24,3 @@ private: } // namespace optimizer } // namespace paddle - -#endif diff --git a/paddle/optimizer/adam_optimizer.h b/paddle/optimizer/adam_optimizer.h index 89e68346d58..718039e4514 100644 --- a/paddle/optimizer/adam_optimizer.h +++ b/paddle/optimizer/adam_optimizer.h @@ -1,5 +1,4 @@ -#ifndef PADDLE_ADAM_OPTIMIZER_H_ -#define PADDLE_ADAM_OPTIMIZER_H_ +#pragma once #include "parameter_optimizer.h" @@ -34,4 +33,3 @@ private: } // namespace optimizer } // namespace paddle -#endif diff --git a/paddle/optimizer/lr_policy.h b/paddle/optimizer/lr_policy.h index b24a17ced01..51debf675f3 100644 --- a/paddle/optimizer/lr_policy.h +++ b/paddle/optimizer/lr_policy.h @@ -1,5 +1,4 @@ -#ifndef PADDLE_OPTIMIZER_LR_POLICY_H_ -#define PADDLE_OPTIMIZER_LR_POLICY_H_ +#pragma once #include #include "OptimizerConfig.pb.h" @@ -41,5 +40,3 @@ private: } // namespace optimizer } // namespace paddle - -#endif diff --git a/paddle/optimizer/optimizer.h b/paddle/optimizer/optimizer.h index c3328331fc3..ee017eddabe 100644 --- a/paddle/optimizer/optimizer.h +++ b/paddle/optimizer/optimizer.h @@ -1,5 +1,5 @@ -#ifndef PADDLE_LIB_OPTIMIZER_H_ -#define PADDLE_LIB_OPTIMIZER_H_ +#pragma once + #include #include @@ -93,4 +93,3 @@ int paddle_optimizer_get_state(paddle_optimizer* o, const char* state); #ifdef __cplusplus } #endif -#endif diff --git a/paddle/optimizer/parameter_optimizer.h b/paddle/optimizer/parameter_optimizer.h index e60c7778205..33b3f837b43 100644 --- a/paddle/optimizer/parameter_optimizer.h +++ b/paddle/optimizer/parameter_optimizer.h @@ -1,5 +1,4 @@ -#ifndef PADDLE_PARAMETER_OPTIMIZER_H_ -#define PADDLE_PARAMETER_OPTIMIZER_H_ +#pragma once #include #include @@ -40,5 +39,3 @@ protected: } // namespace optimizer } // namespace paddle - -#endif diff --git a/paddle/optimizer/serialization.h b/paddle/optimizer/serialization.h index edb90d0a164..6caa514cef2 100644 --- a/paddle/optimizer/serialization.h +++ b/paddle/optimizer/serialization.h @@ -1,5 +1,4 @@ -#ifndef PADDLE_OPTIMIZER_SERIALIZARION_H -#define PADDLE_OPTIMIZER_SERIALIZARION_H +#pragma once #include #include @@ -33,4 +32,3 @@ static void ProtoToTensor(const TensorProto& proto, Tensor* tensor) { } // namespace optimizer } // namespace paddle -#endif -- GitLab From 5a1e678ba40f45752ce91701688fa1640ae13f36 Mon Sep 17 00:00:00 2001 From: dzhwinter Date: Tue, 6 Jun 2017 23:52:01 +0800 Subject: [PATCH 0568/3256] "update macro and fix some part" --- paddle/optimizer/Tensor.h | 9 +++++++-- paddle/optimizer/Tensor_test.cpp | 4 +--- paddle/optimizer/adadelta_optimizer.cc | 9 +++------ paddle/optimizer/adagrad_optimizer.cc | 3 +-- paddle/optimizer/adam_optimizer.cc | 6 ++---- paddle/optimizer/parameter_optimizer_test.cpp | 6 ++---- paddle/optimizer/sgd_optmizer.cc | 7 +++---- 7 files changed, 19 insertions(+), 25 deletions(-) diff --git a/paddle/optimizer/Tensor.h b/paddle/optimizer/Tensor.h index 3dbb3ca05de..9f68877e3ba 100644 --- a/paddle/optimizer/Tensor.h +++ b/paddle/optimizer/Tensor.h @@ -14,10 +14,15 @@ namespace optimizer { template class TensorT { public: - TensorT(size_t h, size_t w, T* data) : height_(h), width_(w), data_(data_) {} - TensorT(T* data, int size) : height_(1), width_(size), data_(data) {} + TensorT(size_t size) : height_(1), width_(size) { data_ = new T[size]; } + TensorT(T* data, size_t size) : height_(1), width_(size), data_(data) {} + TensorT(T* data, size_t h, size_t w) : height_(h), width_(w), data_(data_) {} TensorT(const TensorT& t) : TensorT(1, t.size(), 0, t.get_buffer(), false, false) {} + ~TensorT() { + if (data_) delete data_; + } + TensorT& operator=(const TensorT& t) { this->width_ = t.size(); this->data_ = t.get_buffer(); diff --git a/paddle/optimizer/Tensor_test.cpp b/paddle/optimizer/Tensor_test.cpp index 3a21b6d3032..b6a808d6e84 100644 --- a/paddle/optimizer/Tensor_test.cpp +++ b/paddle/optimizer/Tensor_test.cpp @@ -6,14 +6,12 @@ using namespace paddle; using namespace paddle::optimizer; TEST(Tensor, indexer) { - real* ptr = new real[3]; - Tensor t(ptr, 3); + Tensor t(3); for (auto i = 0; i < t.size(); ++i) { t[i] = i; } ASSERT_EQ(t[2], 2); ASSERT_EQ(t[1], 1); - delete ptr; } int main(int argc, char** argv) { diff --git a/paddle/optimizer/adadelta_optimizer.cc b/paddle/optimizer/adadelta_optimizer.cc index 7381f9d40e7..64672da0c04 100644 --- a/paddle/optimizer/adadelta_optimizer.cc +++ b/paddle/optimizer/adadelta_optimizer.cc @@ -8,12 +8,9 @@ namespace optimizer { void AdadeltaOptimizer::set_weight(Tensor* p) { parameter_ = p; size_t size = p->size(); - real* gptr = new real[size]; - accum_gradient_ = new Tensor(gptr, size); - real* dptr = new real[size]; - accum_delta_ = new Tensor(dptr, size); - real* dptr_current = new real[size]; - update_delta_ = new Tensor(dptr_current, size); + accum_gradient_ = new Tensor(size); + accum_delta_ = new Tensor(size); + update_delta_ = new Tensor(size); } void AdadeltaOptimizer::Update(const Tensor* gradient) { diff --git a/paddle/optimizer/adagrad_optimizer.cc b/paddle/optimizer/adagrad_optimizer.cc index e3a9960e150..1698c2abdbb 100644 --- a/paddle/optimizer/adagrad_optimizer.cc +++ b/paddle/optimizer/adagrad_optimizer.cc @@ -8,8 +8,7 @@ namespace optimizer { void AdagradOptimizer::set_weight(Tensor* p) { parameter_ = p; size_t size = p->size(); - real* gptr = new real[size]; - accum_gradient_ = new Tensor(gptr, size); + accum_gradient_ = new Tensor(size); } void AdagradOptimizer::Update(const Tensor* gradient) { diff --git a/paddle/optimizer/adam_optimizer.cc b/paddle/optimizer/adam_optimizer.cc index ae96b30b948..d052ac8f9a1 100644 --- a/paddle/optimizer/adam_optimizer.cc +++ b/paddle/optimizer/adam_optimizer.cc @@ -7,10 +7,8 @@ namespace optimizer { void AdamOptimizer::set_weight(Tensor *p) { parameter_ = p; size_t size = p->size(); - real *mptr = new real[size]; - momentums_ = new Tensor(mptr, size); - real *vptr = new real[size]; - velocitys_ = new Tensor(vptr, size); + momentums_ = new Tensor(size); + velocitys_ = new Tensor(size); } void AdamOptimizer::Update(const Tensor *gradient) { diff --git a/paddle/optimizer/parameter_optimizer_test.cpp b/paddle/optimizer/parameter_optimizer_test.cpp index 2b3ad84ca95..d9f2ed8e950 100644 --- a/paddle/optimizer/parameter_optimizer_test.cpp +++ b/paddle/optimizer/parameter_optimizer_test.cpp @@ -11,8 +11,7 @@ using namespace paddle; using namespace paddle::optimizer; Tensor* FillTensor(size_t size) { - real* ptr = new real[size]; - Tensor* param = new Tensor(ptr, size); + Tensor* param = new Tensor(size); Tensor& p = *param; for (auto i = 0; i < p.size(); ++i) { p[i] = (float)rand() / (float)RAND_MAX; @@ -21,8 +20,7 @@ Tensor* FillTensor(size_t size) { } Tensor* FixedTensor(size_t size) { - real* ptr = new real[size]; - Tensor* param = new Tensor(ptr, size); + Tensor* param = new Tensor(size); Tensor& p = *param; for (auto i = 0; i < p.size(); ++i) { p[i] = i; diff --git a/paddle/optimizer/sgd_optmizer.cc b/paddle/optimizer/sgd_optmizer.cc index a222672815b..b40bf7c1020 100644 --- a/paddle/optimizer/sgd_optmizer.cc +++ b/paddle/optimizer/sgd_optmizer.cc @@ -6,11 +6,10 @@ namespace optimizer { void SGDOptimizer::set_weight(Tensor *p) { parameter_ = p; - size_t size = p->size(); - // TODO: fix it with align aware allocator bind to Tensor if (momentum_ != 0.0) { - real *ptr = new real[size]; - momentums_ = new Tensor(ptr, size); + size_t size = p->size(); + // TODO: fix it with align aware allocator bind to Tensor + momentums_ = new Tensor(size); } } -- GitLab From 692d251869769e603dc5af0e42cc0d1e2e39b0ff Mon Sep 17 00:00:00 2001 From: caoying03 Date: Mon, 5 Jun 2017 20:51:22 +0800 Subject: [PATCH 0569/3256] add missing configuration functions in v2 API. --- doc/api/v2/config/layer.rst | 10 +++++- doc/api/v2/config/networks.rst | 8 ----- python/paddle/trainer/config_parser.py | 12 +++---- .../paddle/trainer_config_helpers/layers.py | 26 ++++++++++++++-- .../paddle/trainer_config_helpers/networks.py | 31 +++---------------- python/paddle/v2/layer.py | 29 ++++++++++------- python/paddle/v2/topology.py | 3 +- 7 files changed, 62 insertions(+), 57 deletions(-) diff --git a/doc/api/v2/config/layer.rst b/doc/api/v2/config/layer.rst index 1efa74ecda4..8f0833521ce 100644 --- a/doc/api/v2/config/layer.rst +++ b/doc/api/v2/config/layer.rst @@ -434,10 +434,18 @@ smooth_l1_cost .. autoclass:: paddle.v2.layer.smooth_l1_cost :noindex: -Check Layer +Check Layer ============ eos --- .. autoclass:: paddle.v2.layer.eos :noindex: + +Miscs +===== + +dropout +-------------- +.. autoclass:: paddle.v2.layer.dropout + :noindex: diff --git a/doc/api/v2/config/networks.rst b/doc/api/v2/config/networks.rst index b2a617fff13..6e813ab1a82 100644 --- a/doc/api/v2/config/networks.rst +++ b/doc/api/v2/config/networks.rst @@ -125,11 +125,3 @@ simple_attention :members: simple_attention :noindex: -Miscs -===== - -dropout_layer --------------- -.. automodule:: paddle.v2.networks - :members: dropout_layer - :noindex: diff --git a/python/paddle/trainer/config_parser.py b/python/paddle/trainer/config_parser.py index 5d540664a7f..3be972fd39c 100644 --- a/python/paddle/trainer/config_parser.py +++ b/python/paddle/trainer/config_parser.py @@ -3546,11 +3546,7 @@ def update_g_config(): return g_config -def begin_parse(config_arg_str=''): - ''' - @param config_arg_str: a string of the form var1=val1,var2=val2. It will be - passed to config script as a dictionary CONFIG_ARGS - ''' +def begin_parse(): init_config_environment() for hook in _parse_config_hooks: hook() @@ -3568,8 +3564,12 @@ def begin_parse(config_arg_str=''): def parse_config(trainer_config, config_arg_str): - begin_parse(config_arg_str) + ''' + @param config_arg_str: a string of the form var1=val1,var2=val2. It will be + passed to config script as a dictionary CONFIG_ARGS + ''' + begin_parse() config_args = {} if config_arg_str: diff --git a/python/paddle/trainer_config_helpers/layers.py b/python/paddle/trainer_config_helpers/layers.py index c347aaf8d3c..7b2408e4326 100755 --- a/python/paddle/trainer_config_helpers/layers.py +++ b/python/paddle/trainer_config_helpers/layers.py @@ -120,6 +120,7 @@ __all__ = [ 'smooth_l1_cost', 'layer_support', 'multiplex_layer', + 'dropout_layer', ] @@ -3760,7 +3761,6 @@ def beam_search(step, assert generated_input_index != -1 gipt = input[generated_input_index] - assert isinstance(gipt, BaseGeneratedInput) gipt.bos_id = bos_id gipt.eos_id = eos_id @@ -3780,7 +3780,6 @@ def beam_search(step, predict = gipt.after_real_step(step(*args)) eos_layer(input=predict, eos_id=eos_id, name=eos_name) - return predict tmp = recurrent_group( @@ -5543,3 +5542,26 @@ def multiplex_layer(input, name=None, layer_attr=None): layer_type=LayerType.MULTIPLEX_LAYER, parents=input, size=l.config.size) + + +############################################################################ +# Miscs # +############################################################################ + + +@wrap_name_default("dropout") +def dropout_layer(input, dropout_rate, name=None): + """ + @TODO(yuyang18): Add comments. + + :param name: + :param input: + :param dropout_rate: + :return: + """ + return addto_layer( + name=name, + input=input, + act=LinearActivation(), + bias_attr=False, + layer_attr=ExtraAttr(drop_rate=dropout_rate)) diff --git a/python/paddle/trainer_config_helpers/networks.py b/python/paddle/trainer_config_helpers/networks.py index fb533a47e0b..1bf59ed4840 100755 --- a/python/paddle/trainer_config_helpers/networks.py +++ b/python/paddle/trainer_config_helpers/networks.py @@ -26,10 +26,10 @@ from paddle.trainer.config_parser import * __all__ = [ 'sequence_conv_pool', 'simple_lstm', "simple_img_conv_pool", - "img_conv_bn_pool", 'dropout_layer', 'lstmemory_group', 'lstmemory_unit', - 'small_vgg', 'img_conv_group', 'vgg_16_network', 'gru_unit', 'gru_group', - 'simple_gru', 'simple_attention', 'simple_gru2', 'bidirectional_gru', - 'text_conv_pool', 'bidirectional_lstm', 'inputs', 'outputs' + "img_conv_bn_pool", 'lstmemory_group', 'lstmemory_unit', 'small_vgg', + 'img_conv_group', 'vgg_16_network', 'gru_unit', 'gru_group', 'simple_gru', + 'simple_attention', 'simple_gru2', 'bidirectional_gru', 'text_conv_pool', + 'bidirectional_lstm', 'inputs', 'outputs' ] ###################################################### @@ -1366,29 +1366,6 @@ def simple_attention(encoded_sequence, input=scaled, pooling_type=SumPooling(), name="%s_pooling" % name) -############################################################################ -# Miscs # -############################################################################ - - -@wrap_name_default("dropout") -def dropout_layer(input, dropout_rate, name=None): - """ - @TODO(yuyang18): Add comments. - - :param name: - :param input: - :param dropout_rate: - :return: - """ - return addto_layer( - name=name, - input=input, - act=LinearActivation(), - bias_attr=False, - layer_attr=ExtraAttr(drop_rate=dropout_rate)) - - def inputs(layers, *args): """ Declare the inputs of network. The order of input should be as same as diff --git a/python/paddle/v2/layer.py b/python/paddle/v2/layer.py index da2abdd2d1e..f6b3dbf39a5 100644 --- a/python/paddle/v2/layer.py +++ b/python/paddle/v2/layer.py @@ -13,7 +13,7 @@ # limitations under the License. """ `paddle.v2.layer` is a part of model config packages in paddle.v2. In API v2, -we want to make Paddle a plain Python package. The model config package defined +we want to make Paddle a plain Python package. The model config package defines the way how to configure a neural network topology in Paddle Python code. The primary usage shows below. @@ -30,7 +30,6 @@ The primary usage shows below. # use prediction instance where needed. parameters = paddle.parameters.create(cost) """ - import collections import copy import re @@ -44,9 +43,10 @@ __all__ = ['data', 'parse_network'] def __need_to_keep__(name): - if name in ['StaticInput', 'LayerType', 'layer_support']: - return False - return True + return name in [ + 'StaticInput', 'SubsequenceInput', 'GeneratedInput', 'LayerType', + 'layer_support' + ] def __need_to_wrap__(name): @@ -54,6 +54,8 @@ def __need_to_wrap__(name): def __convert_name__(inname): + if __need_to_keep__(inname): + return inname if inname == 'maxid_layer': return 'max_id' elif inname.endswith('memory') or inname.endswith( @@ -74,8 +76,6 @@ def __convert_name__(inname): for name in v1_layers.__all__: obj = getattr(v1_layers, name) - if not __need_to_keep__(name): - continue new_name = __convert_name__(name) if callable(obj) and __need_to_wrap__(name): globals()[new_name] = __convert_to_v2__(obj, new_name, __name__) @@ -107,7 +107,7 @@ __data_layer__.__doc__ = __map_data_docstr__(v1_layers.data_layer.__doc__) data = __convert_to_v2__(__data_layer__, 'name', __name__) -def __get_used_layers__(output_layers, extra_layers=None): +def __get_used_layers__(output_layers): layer_names = set() parents = {} @@ -175,6 +175,8 @@ def __get_used_submodels__(layer_names): for submodel in cp.g_config.model_config.sub_models: if submodel.name in layer_names: submodel_names.add(submodel.name) + if submodel.is_recurrent_layer_group: + layer_names |= set(submodel.layer_names) return submodel_names @@ -248,18 +250,21 @@ def parse_network(output_layers, extra_layers=None): model_config = ModelConfig() model_config.type = cp.g_config.model_config.type + + for layer in output_layers: + model_config.output_layer_names.append(layer.full_name) + output_layer_names.add(layer.full_name) + for l in cp.g_config.model_config.layers: if l.name not in layer_names: continue model_config.layers.extend([l]) if l.type == 'data': + if l.name in model_config.output_layer_names: + continue model_config.input_layer_names.append(l.name) input_layer_names.add(l.name) - for layer in output_layers: - model_config.output_layer_names.append(layer.full_name) - output_layer_names.add(layer.full_name) - for e in cp.g_config.model_config.evaluators: if e.name in evaluator_names: model_config.evaluators.extend([e]) diff --git a/python/paddle/v2/topology.py b/python/paddle/v2/topology.py index f3bb4d5f10d..67e5535a0cc 100644 --- a/python/paddle/v2/topology.py +++ b/python/paddle/v2/topology.py @@ -91,8 +91,9 @@ class Topology(object): [('image', dense_vector(768)), ('label', integer_value(10))] """ data_layers = self.data_layers() + return [(nm, data_layers[nm].data_type) - for nm in self.proto().input_layer_names] + for nm in self.proto().input_layer_names if nm in data_layers] def get_layer_proto(self, name): for layer in self.__model_config__.layers: -- GitLab From bc26df79c6facde230e50908d1e3d8fbbbf361e8 Mon Sep 17 00:00:00 2001 From: dzhwinter Date: Wed, 7 Jun 2017 10:51:44 +0800 Subject: [PATCH 0570/3256] "polish code style and update based review comment" --- paddle/optimizer/Tensor.h | 2 +- paddle/optimizer/adadelta_optimizer.h | 10 +++-- paddle/optimizer/adagrad_optimizer.h | 6 ++- paddle/optimizer/adam_optimizer.h | 3 +- paddle/optimizer/lr_policy.h | 2 +- paddle/optimizer/optimizer.cc | 40 ++++++++--------- paddle/optimizer/optimizer.h | 44 +++++++++---------- paddle/optimizer/parameter_optimizer.cc | 28 +++++++----- paddle/optimizer/parameter_optimizer.h | 3 +- paddle/optimizer/parameter_optimizer_test.cpp | 3 +- paddle/optimizer/sgd_optimizer.h | 8 +++- paddle/optimizer/sgd_optmizer.cc | 2 +- 12 files changed, 81 insertions(+), 70 deletions(-) diff --git a/paddle/optimizer/Tensor.h b/paddle/optimizer/Tensor.h index 9f68877e3ba..a00a59bc6af 100644 --- a/paddle/optimizer/Tensor.h +++ b/paddle/optimizer/Tensor.h @@ -46,7 +46,7 @@ protected: }; // TODO(zhihong): design problem of dynamic datatype, need to fix it -typedef TensorT Tensor; +typedef TensorT Tensor; } // namespace optimizer } // namespace paddle diff --git a/paddle/optimizer/adadelta_optimizer.h b/paddle/optimizer/adadelta_optimizer.h index d17fae6b70a..4f6137cf2e5 100644 --- a/paddle/optimizer/adadelta_optimizer.h +++ b/paddle/optimizer/adadelta_optimizer.h @@ -8,7 +8,13 @@ namespace optimizer { class AdadeltaOptimizer : public ParameterOptimizer { public: AdadeltaOptimizer(double rho, double epsilon, double decay, LrPolicy *lr) - : ParameterOptimizer(lr), rho_(rho), epsilon_(epsilon), decay_(decay) {} + : ParameterOptimizer(lr), + accum_gradient_(nullptr), + accum_delta_(nullptr), + update_delta_(nullptr), + rho_(rho), + epsilon_(epsilon), + decay_(decay) {} ~AdadeltaOptimizer() { if (accum_gradient_) delete accum_gradient_; if (accum_delta_) delete accum_delta_; @@ -16,13 +22,11 @@ public: } void Update(const Tensor *gradient); void set_weight(Tensor *p); - real *get_weight() const; private: Tensor *accum_gradient_; Tensor *accum_delta_; Tensor *update_delta_; - double rho_; double epsilon_; double decay_; diff --git a/paddle/optimizer/adagrad_optimizer.h b/paddle/optimizer/adagrad_optimizer.h index 6c00665c86c..1b05e99754b 100644 --- a/paddle/optimizer/adagrad_optimizer.h +++ b/paddle/optimizer/adagrad_optimizer.h @@ -8,13 +8,15 @@ namespace optimizer { class AdagradOptimizer : public ParameterOptimizer { public: AdagradOptimizer(double epsilon, double decay, LrPolicy *lr) - : ParameterOptimizer(lr), epsilon_(epsilon), decay_(decay) {} + : ParameterOptimizer(lr), + accum_gradient_(nullptr), + epsilon_(epsilon), + decay_(decay) {} ~AdagradOptimizer() { if (accum_gradient_) delete accum_gradient_; } void Update(const Tensor *gradient); void set_weight(Tensor *p); - real *get_weight() const; private: Tensor *accum_gradient_; diff --git a/paddle/optimizer/adam_optimizer.h b/paddle/optimizer/adam_optimizer.h index 718039e4514..840927e6c7d 100644 --- a/paddle/optimizer/adam_optimizer.h +++ b/paddle/optimizer/adam_optimizer.h @@ -10,6 +10,8 @@ public: AdamOptimizer( double beta_1, double beta_2, double epsilon, double decay, LrPolicy *lr) : ParameterOptimizer(lr), + momentums_(nullptr), + velocitys_(nullptr), beta_1_(beta_1), beta_2_(beta_2), epsilon_(epsilon), @@ -20,7 +22,6 @@ public: } void Update(const Tensor *gradient); void set_weight(Tensor *p); - real *get_weight() const; private: Tensor *momentums_; diff --git a/paddle/optimizer/lr_policy.h b/paddle/optimizer/lr_policy.h index 51debf675f3..ab7d1fb0fe6 100644 --- a/paddle/optimizer/lr_policy.h +++ b/paddle/optimizer/lr_policy.h @@ -20,7 +20,7 @@ public: return learning_rate; } -protected: +private: double learning_rate; }; diff --git a/paddle/optimizer/optimizer.cc b/paddle/optimizer/optimizer.cc index 5076029494c..9572f163dde 100644 --- a/paddle/optimizer/optimizer.cc +++ b/paddle/optimizer/optimizer.cc @@ -2,6 +2,7 @@ #include #include "parameter_optimizer.h" + using namespace paddle; using namespace paddle::optimizer; @@ -26,6 +27,7 @@ MATCH_ENUM_TYPE(int32_t, PADDLE_ELEMENT_TYPE_INT32); MATCH_ENUM_TYPE(uint32_t, PADDLE_ELEMENT_TYPE_UINT32); MATCH_ENUM_TYPE(int64_t, PADDLE_ELEMENT_TYPE_INT64); MATCH_ENUM_TYPE(uint64_t, PADDLE_ELEMENT_TYPE_UINT64); +// TODO(zhihong): only implement below type, need to fix MATCH_ENUM_TYPE(float, PADDLE_ELEMENT_TYPE_FLOAT32); MATCH_ENUM_TYPE(double, PADDLE_ELEMENT_TYPE_FLOAT64); @@ -35,15 +37,20 @@ struct paddle_optimizer { paddle_optimizer* paddle_create_optimizer(const unsigned char* config_proto, const int config_proto_len, - const char** state, - const int state_size) { + const paddle_element_type data_type, + void* param_buffer, + int num_bytes, + const char* state, + const int state_len) { paddle_optimizer* optimizer = new paddle_optimizer; std::string config(config_proto, config_proto + config_proto_len); optimizer->impl = ParameterOptimizer::Create(config); if (state != nullptr) { - std::string s(*state, *state + state_size); + std::string s(state, state + state_len); optimizer->impl->DeSerializeState(s); } + Tensor* param = new Tensor(reinterpret_cast(param_buffer), num_bytes); + optimizer->impl->set_weight(param); return optimizer; } @@ -57,28 +64,19 @@ int paddle_update_parameter(paddle_optimizer* o, const void* grad_buffer, int num_bytes) { // TOOD(zhihong): datatype not work. need to add the runtime datatype - auto grad_type = reinterpret_cast(grad_buffer); - Tensor* gradient = new Tensor(const_cast(grad_type), num_bytes); + auto grad_type = reinterpret_cast(grad_buffer); + Tensor* gradient = new Tensor(const_cast(grad_type), num_bytes); o->impl->Update(gradient); return PADDLE_SUCCESS; } -int paddle_optimizer_set_weights(paddle_optimizer* o, - const paddle_element_type data_type, - void* param_buffer, - int num_bytes) { - // TOOD(zhihong): datatype not work. need to add the runtime datatype - Tensor* param = new Tensor(reinterpret_cast(param_buffer), num_bytes); - o->impl->set_weight(param); - return PADDLE_SUCCESS; -} - -void* paddle_optimizer_get_weights(paddle_optimizer* o) { - void* buffer = (void*)o->impl->get_weight(); - return buffer; +int paddle_optimizer_get_weights(paddle_optimizer* o, void** param_buffer) { + int param_size = 0; + *param_buffer = (void*)o->impl->get_weight(¶m_size); + return param_size; } -int paddle_optimizer_get_state(paddle_optimizer* o, const char* state) { - state = o->impl->SerializeState(); - return PADDLE_SUCCESS; +int paddle_optimizer_get_state(paddle_optimizer* o, const char** state) { + *state = o->impl->SerializeState(); + return strlen(*state); } diff --git a/paddle/optimizer/optimizer.h b/paddle/optimizer/optimizer.h index ee017eddabe..a5f468b06b5 100644 --- a/paddle/optimizer/optimizer.h +++ b/paddle/optimizer/optimizer.h @@ -3,19 +3,18 @@ #include #include -/*! \brief optimizer export C API. which will be used in - Case A, on Trainer (On ParameterServer Client) optimize gradient - - Case B, on ParameterServer side optimize gradient - - To simplify the configuration parsing. optimizer *do not* parse any config - e.g. learning rate should be calculated by the caller +/** + * @brief optimizer library in independent with other module + * which will be used in : + * Case A, the gradient optimized locally on the trainer. + * + * Case B, the gradient optimized on the parameter server. */ #ifdef __cplusplus extern "C" { #endif -/*! \brief datatypes */ + typedef enum { PADDLE_ELEMENT_TYPE_INT32 = 0, PADDLE_ELEMENT_TYPE_UINT32 = 1, @@ -25,7 +24,9 @@ typedef enum { PADDLE_ELEMENT_TYPE_FLOAT64 = 5, } paddle_element_type; -/*! \brief execute status code */ +/** + * @brief execution status code + */ const int32_t PADDLE_SUCCESS = 0; const int32_t PADDLE_ERROR = -1; @@ -46,8 +47,11 @@ typedef struct paddle_optimizer paddle_optimizer; */ paddle_optimizer* paddle_create_optimizer(const unsigned char* config_proto, const int config_proto_len, - const char** state, - const int state_size); + const paddle_element_type data_type, + void* param_buffer, + int num_bytes, + const char* state, + const int state_len); /** * @brief release optimizer @@ -72,23 +76,17 @@ int paddle_update_parameter(paddle_optimizer* o, /** * @brief optimizer instance - * @param data_type datatype of gradient * @param param_buffer, initilized parameter buffer - * @param num_bytes, parameter size - * @return return exec status + * @return return content length */ -int paddle_optimizer_set_weights(paddle_optimizer* o, - const paddle_element_type data_type, - void* param_buffer, - int num_bytes); +int paddle_optimizer_get_weights(paddle_optimizer* o, void** param_buffer); /** - * @brief optimizer instance - * @return return content of parameter buffer in optimizer + * @brief optimzizer instance + * @param training state for receive SerializeState + * @return return state_buffer length */ -void* paddle_optimizer_get_weights(paddle_optimizer* o); - -int paddle_optimizer_get_state(paddle_optimizer* o, const char* state); +int paddle_optimizer_get_state(paddle_optimizer* o, const char** state); #ifdef __cplusplus } diff --git a/paddle/optimizer/parameter_optimizer.cc b/paddle/optimizer/parameter_optimizer.cc index 00e9b858551..1a96880c574 100644 --- a/paddle/optimizer/parameter_optimizer.cc +++ b/paddle/optimizer/parameter_optimizer.cc @@ -24,7 +24,8 @@ ParameterOptimizer *ParameterOptimizer::Create( config.linear_lr().lr_decay_a(), config.linear_lr().lr_decay_b()); // default - return nullptr; + LOG(WARNING) << " have not select any LrPolicy. use ConstLr in default"; + return new ConstLr(0.1); }; LrPolicy *lr = select_lr_policy(config); auto select_optimizer = @@ -36,29 +37,32 @@ ParameterOptimizer *ParameterOptimizer::Create( lr); } if (config.optimizer() == OptimizerConfig::Adadelta) { - return new AdagradOptimizer( - config.adagrad().epsilon(), config.adagrad().decay(), lr); + return new AdadeltaOptimizer(config.adadelta().rho(), + config.adadelta().epsilon(), + config.adadelta().decay(), + lr); } if (config.optimizer() == OptimizerConfig::Adagrad) { return new AdagradOptimizer( config.adagrad().epsilon(), config.adagrad().decay(), lr); } if (config.optimizer() == OptimizerConfig::Adam) { - return new AdadeltaOptimizer(config.adadelta().rho(), - config.adadelta().epsilon(), - config.adadelta().decay(), - lr); + return new AdamOptimizer(config.adam().beta_1(), + config.adam().beta_2(), + config.adam().epsilon(), + config.adam().decay(), + lr); } // default - return new SGDOptimizer(config.sgd().momentum(), - config.sgd().decay(), - config.sgd().nesterov(), - lr); + LOG(WARNING) + << "have not select any Optimizer. use SGDOptimizer in default"; + return new SGDOptimizer(0.0, 0.0, false, lr); }; return select_optimizer(config); } -real *ParameterOptimizer::get_weight() const { +float *ParameterOptimizer::get_weight(int *param_size) const { + *param_size = (int)parameter_->size(); return parameter_->get_buffer(); } diff --git a/paddle/optimizer/parameter_optimizer.h b/paddle/optimizer/parameter_optimizer.h index 33b3f837b43..f65f1d71a42 100644 --- a/paddle/optimizer/parameter_optimizer.h +++ b/paddle/optimizer/parameter_optimizer.h @@ -25,11 +25,10 @@ public: virtual const char *SerializeState(); virtual void DeSerializeState(const std::string &state); virtual void Update(const Tensor *gradient) = 0; - virtual real *get_weight() const; + virtual float *get_weight(int *param_size) const; virtual void set_weight(Tensor *parameter); protected: - OptimizerConfig config_; Tensor *parameter_; // learning rate policy diff --git a/paddle/optimizer/parameter_optimizer_test.cpp b/paddle/optimizer/parameter_optimizer_test.cpp index d9f2ed8e950..d39d50a1abf 100644 --- a/paddle/optimizer/parameter_optimizer_test.cpp +++ b/paddle/optimizer/parameter_optimizer_test.cpp @@ -77,7 +77,8 @@ public: opts[i]->set_weight(p); } for (size_t i = 0; i < opts.size(); ++i) { - real* newp = (real*)opts[i]->get_weight(); + int s = 0; + float* newp = (float*)opts[i]->get_weight(&s); for (size_t j = 0; j < size; ++j) { EXPECT_EQ(newp[j], (*p)[j]); } diff --git a/paddle/optimizer/sgd_optimizer.h b/paddle/optimizer/sgd_optimizer.h index 284d0a4d0c7..1bb97349c7c 100644 --- a/paddle/optimizer/sgd_optimizer.h +++ b/paddle/optimizer/sgd_optimizer.h @@ -9,14 +9,18 @@ namespace optimizer { class SGDOptimizer : public ParameterOptimizer { public: SGDOptimizer(double m, double d, bool n, LrPolicy* lr) - : ParameterOptimizer(lr), momentum_(m), decay_(d), nesterov_(n) {} + : ParameterOptimizer(lr), + momentums_(nullptr), + momentum_(m), + decay_(d), + nesterov_(n) {} virtual ~SGDOptimizer() { delete momentums_; } void Update(const Tensor* gradient); const char* SerializeState(); void DeSerializeState(const std::string& state); void set_weight(Tensor* p); - real* get_weight() const; + float* get_weight(int* param_size) const; private: Tensor* momentums_; diff --git a/paddle/optimizer/sgd_optmizer.cc b/paddle/optimizer/sgd_optmizer.cc index b40bf7c1020..fea550f8d1d 100644 --- a/paddle/optimizer/sgd_optmizer.cc +++ b/paddle/optimizer/sgd_optmizer.cc @@ -16,7 +16,7 @@ void SGDOptimizer::set_weight(Tensor *p) { void SGDOptimizer::Update(const Tensor *gradient) { num_sample_passed_ += 1; double learning_rate = lr_policy_->LearningRate(num_sample_passed_); - real velocity = 0.0; + float velocity = 0.0; Tensor ¶m = *parameter_; const Tensor &grad = *gradient; Tensor &m = *momentums_; -- GitLab From 1e21259d5c36fd7912e957ce93d2464b3ae2f2fe Mon Sep 17 00:00:00 2001 From: caoying03 Date: Wed, 7 Jun 2017 10:54:10 +0800 Subject: [PATCH 0571/3256] fix the bug of parsing evaluator in SRL in PaddleBook. --- python/paddle/v2/layer.py | 6 +++--- python/paddle/v2/topology.py | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/python/paddle/v2/layer.py b/python/paddle/v2/layer.py index f6b3dbf39a5..67b7192bb74 100644 --- a/python/paddle/v2/layer.py +++ b/python/paddle/v2/layer.py @@ -235,9 +235,9 @@ def __trim_submodel__(old_submodel, layer_names, input_layer_names, def parse_network(output_layers, extra_layers=None): if not isinstance(output_layers, collections.Sequence): output_layers = [output_layers] - if extra_layers is not None and not isinstance(extra_layers, - collections.Sequence): - extra_layers = [extra_layers] + if extra_layers is not None: + if not isinstance(extra_layers, collections.Sequence): + extra_layers = [extra_layers] else: extra_layers = [] diff --git a/python/paddle/v2/topology.py b/python/paddle/v2/topology.py index 67e5535a0cc..5109dc58fe5 100644 --- a/python/paddle/v2/topology.py +++ b/python/paddle/v2/topology.py @@ -31,7 +31,6 @@ class Topology(object): def __init__(self, layers, extra_layers=None): def __check__(layers): if not isinstance(layers, collections.Sequence): - __check_layer_type__(layers) layers = [layers] for layer in layers: __check_layer_type__(layer) -- GitLab From b9cb0f26fdbdc876f603ded19224aaffdcd307b7 Mon Sep 17 00:00:00 2001 From: dzhwinter Date: Wed, 7 Jun 2017 13:52:28 +0800 Subject: [PATCH 0572/3256] "update marco" --- paddle/optimizer/sgd_optimizer.h | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/paddle/optimizer/sgd_optimizer.h b/paddle/optimizer/sgd_optimizer.h index 1bb97349c7c..10b46db3868 100644 --- a/paddle/optimizer/sgd_optimizer.h +++ b/paddle/optimizer/sgd_optimizer.h @@ -1,5 +1,4 @@ -#ifndef PADDLE_SGD_OPTIMIZER_H_ -#define PADDLE_SGD_OPTIMIZER_H_ +#pragma once #include "parameter_optimizer.h" @@ -31,5 +30,3 @@ private: } // namespace optimizer } // namespace paddle - -#endif -- GitLab From 6cbbc2ec1de4fc6c4d4b2baad1b53358d35d65f3 Mon Sep 17 00:00:00 2001 From: dzhwinter Date: Wed, 7 Jun 2017 14:24:43 +0800 Subject: [PATCH 0573/3256] "add comments" --- proto/OptimizerConfig.proto | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/proto/OptimizerConfig.proto b/proto/OptimizerConfig.proto index 16b041a9b25..3986fce5da6 100644 --- a/proto/OptimizerConfig.proto +++ b/proto/OptimizerConfig.proto @@ -86,7 +86,6 @@ message OptimizerState { } message OptimizerConfig { - // common config of optimizer enum Optimizer { SGD = 1; Adadelta = 2; @@ -108,6 +107,8 @@ message OptimizerConfig { optional LinearLr linear_lr = 13; // common config of optimizer - optional double clipnorm = 101; - optional double clipvalue = 102; + // gradient clip when L2 exceeding value + optional double clip_norm = 101; + // gradient clip when L1 exceeding value + optional double clip_value = 102; } -- GitLab From 35332a22aa05feca747edd17142600ab79fd2f07 Mon Sep 17 00:00:00 2001 From: caoying03 Date: Wed, 7 Jun 2017 14:37:56 +0800 Subject: [PATCH 0574/3256] add eos layer as an extra parent. --- python/paddle/v2/layer.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/python/paddle/v2/layer.py b/python/paddle/v2/layer.py index 67b7192bb74..1a0e64ea770 100644 --- a/python/paddle/v2/layer.py +++ b/python/paddle/v2/layer.py @@ -132,6 +132,13 @@ def __get_used_layers__(output_layers): add_parent(mem.layer_name, mem.boot_layer_name) add_parent(mem.link_name, mem.layer_name) + if sub_model.HasField('generator'): + # according to the implementation of text generation + # in recurrent layer group, the generated word must be + # the first out link + add_parent(sub_model.out_links[0].layer_name, + sub_model.generator.eos_layer_name) + def dfs_travel(layer_name): if layer_name in layer_names: return @@ -175,8 +182,6 @@ def __get_used_submodels__(layer_names): for submodel in cp.g_config.model_config.sub_models: if submodel.name in layer_names: submodel_names.add(submodel.name) - if submodel.is_recurrent_layer_group: - layer_names |= set(submodel.layer_names) return submodel_names -- GitLab From f5ff283878dfffda4aea0cc43863a080e0f433ba Mon Sep 17 00:00:00 2001 From: dzhwinter Date: Wed, 7 Jun 2017 15:15:42 +0800 Subject: [PATCH 0575/3256] "fix comment" --- paddle/optimizer/Tensor_test.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/paddle/optimizer/Tensor_test.cpp b/paddle/optimizer/Tensor_test.cpp index b6a808d6e84..cdf73309323 100644 --- a/paddle/optimizer/Tensor_test.cpp +++ b/paddle/optimizer/Tensor_test.cpp @@ -1,4 +1,3 @@ -#include #include "gtest/gtest.h" #include "tensor.h" -- GitLab From 9011f9e52c614c1f357a4220ffbb16ee5155f0df Mon Sep 17 00:00:00 2001 From: gongweibao Date: Wed, 7 Jun 2017 15:52:06 +0800 Subject: [PATCH 0576/3256] add precommit --- python/paddle/v2/dataset/common.py | 44 +++++++++++++++++++ python/paddle/v2/dataset/tests/common_test.py | 16 +++++++ 2 files changed, 60 insertions(+) diff --git a/python/paddle/v2/dataset/common.py b/python/paddle/v2/dataset/common.py index 418b592a5ac..89675080e25 100644 --- a/python/paddle/v2/dataset/common.py +++ b/python/paddle/v2/dataset/common.py @@ -149,3 +149,47 @@ def cluster_files_reader(files_pattern, yield line return reader + + +def convert(output_path, eader, num_shards, name_prefix): + import recordio + import cPickle as pickle + """ + Convert data from reader to recordio format files. + + :param output_path: directory in which output files will be saved. + :param reader: a data reader, from which the convert program will read data instances. + :param num_shards: the number of shards that the dataset will be partitioned into. + :param name_prefix: the name prefix of generated files. + """ + + def open_needs(idx): + n = "%s/%s-%05d" % (output_path, name_prefix, idx) + w = recordio.writer(n) + f = open(n, "w") + idx += 1 + + return w, f, idx + + def close_needs(w, f): + if w is not None: + w.close() + + if f is not None: + f.close() + + idx = 0 + w = None + f = None + + for i, d in enumerate(reader()): + if w is None: + w, f, idx = open_needs(idx) + + w.write(pickle.dumps(d, pickle.HIGHEST_PROTOCOL)) + + if i % num_shards == 0 and i >= num_shards: + close_needs(w, f) + w, f, idx = open_needs(idx) + + close_needs(w, f) diff --git a/python/paddle/v2/dataset/tests/common_test.py b/python/paddle/v2/dataset/tests/common_test.py index f9815d4f9e1..3120026e1e6 100644 --- a/python/paddle/v2/dataset/tests/common_test.py +++ b/python/paddle/v2/dataset/tests/common_test.py @@ -57,6 +57,22 @@ class TestCommon(unittest.TestCase): for idx, e in enumerate(reader()): self.assertEqual(e, str("0")) + def test_convert(self): + def test_reader(): + def reader(): + for x in xrange(10): + yield x + + return reader + + path = tempfile.mkdtemp() + + paddle.v2.dataset.common.convert(path, + test_reader(), 4, 'random_images') + + files = glob.glob(temp_path + '/random_images-*') + self.assertEqual(len(files), 3) + if __name__ == '__main__': unittest.main() -- GitLab From 96a56b962acffd0be7790c0641ab9bc87c7e98a1 Mon Sep 17 00:00:00 2001 From: gongweibao Date: Wed, 7 Jun 2017 15:59:12 +0800 Subject: [PATCH 0577/3256] rm not need --- doc/howto/dev/introduction_to_pr.md | 139 ---------------------------- 1 file changed, 139 deletions(-) delete mode 100644 doc/howto/dev/introduction_to_pr.md diff --git a/doc/howto/dev/introduction_to_pr.md b/doc/howto/dev/introduction_to_pr.md deleted file mode 100644 index 71cbbdf8611..00000000000 --- a/doc/howto/dev/introduction_to_pr.md +++ /dev/null @@ -1,139 +0,0 @@ -# Desgin Doc的一点总结 -## 前言 -故事还要从前两天我提交的[FileManager](https://github.com/PaddlePaddle/Paddle/pull/2013)的Design Doc PR开始。 - -[FileManager](https://github.com/PaddlePaddle/Paddle/pull/2013)这个文档我肯定是用心写了,我也清楚地记得,提交之前也仔细的检查了(这点自觉性还是有的),结果,我突然发现,我的PR被Comments刷屏了;这还是其次,更要命的是,当网络速度稍慢的时候会提示我:Comments 太多啦!页面打不开!!哦。简直有点人间惨剧的味道!我要把这些和之前的Comments以及一些典型的Comments总结一下,避免同样的问题,同时也希望对您有用。 -link -我觉得里边有几个基本的原则: - -- 做事情要精益求精 - 这个是做事情的基础,也是我们讨论的基础。美,多是相似的,丑才是千奇百怪。 - - 精益求精是一种态度,态度比什么都重要。[yi](#yi) -- 节约别人的时间 - 同时也是给自己节约时间 - -- 有礼貌,有感恩的心 - > 我发现诸多之前提了comment,但是没有改,也没有回复。这个不太好吧。[ying](#ying) - > 每个comment都必须回复。这是开源社区的基本礼貌。别人帮了忙,应该说谢谢。[yi](#yi) - -我的理解,写Doc精益求精要从基础开始,首先要规范;其次要有提纲挈领的东西,让人一眼基本明白要做的事情;然后,讲述事情结构要清晰,要分层,用最小化涉及的原则,该讲的要解释,不该讲的不讲;接下来逻辑要清晰,要流畅,不要太突兀。。。当然还有很多,比如文笔要好!:) - -锻炼一下,中国人办事也不一定那么糙的,对吧! :) - -## 基础规范 - -- 语言选择:中文 or 英文? - 最好用英文,因为外国同学无法阅读中文。但是,语言是用来交流的,如果您认为英文无法完善的表达自己的意思的时候,还是用中文吧! - - 重要的是,不管哪种文,***英文的拼写要对,中文要没有错别字***! - - 如果文章用的是中文,那么请用中文的标点符号。反之,用英文的标点符号。***而且要保持统一、而且要保持完整***。[wuyi](#wuyi) - - 例如这个[link](https://github.com/PaddlePaddle/Paddle/pull/2013#discussion_r114951817) - - 还有这个[link](https://github.com/PaddlePaddle/Paddle/pull/2013#discussion_r115093563) - - (请原谅我多加了一个link做锚文本,Sphinx有一个bug:用中文的会报错。下边的处理类似。) - -- 缩写的全称 - 一个缩写第一次出现的时候,要把他们的未缩写形式写出来。如果该单词代表的概念比较复杂,请在术语解释中把他写清楚! - - 例如这个[link](https://github.com/PaddlePaddle/Paddle/pull/2013#discussion_r115093329) - -- 英语写法错误 - [yi](#yi)总结了一下在code review的时候经常见的一些英语写法错误: Python写成python,TensorFlow写成Tensorflow,Travis CI 写成 Travis-CI,ReLU写成Relu,unit test写成unittest,MD5写成md5。 - - 大小写简单的规则如下: - - 英文的缩写是要用大写的。 - 例如这个[link](https://github.com/PaddlePaddle/Paddle/pull/2013#discussion_r115091985) - - 英文的句子的首字母是要大写的。 - - 一个专有的名词第一个字母一般是要大写的。 - - yiwang推荐了一个工具:[grammer](https://www.grammarly.com/),可以作为插件安装到Chrome中,自动检查拼写和语法问题。 - -- 不要提交没有用的文件 - 例如这个 [link](https://github.com/PaddlePaddle/Paddle/pull/1964#discussion_r114414822) - -- 提交的代码要经过验证 - 提交的代码都没有验证过是几个意思? - -- 参考资料要设置超链,链接到对方 - 不设置的话俗称Copy。可以用`[name](#name)`设置MarkDown文件中的引用。一如此文中很多地方出现的那样。 - -- 给别人的东西步骤是要可以执行的 - 看这个[link](https://github.com/wangkuiyi/ipynb)是如何写步骤的,或者这个[link](https://github.com/PaddlePaddle/Paddle/pull/1602#issuecomment-285964510)(虽然只是一个Comment)。 - -- 链接可以直接跳到精确的位置 - 如果能一步到位就不要让别人走两步,节约大家彼此的时间。 - -## 提纲挈领 -### 目标要明确 -一般开头的一段话就要用简短的几句话把文档要描述的目标写清楚,别人看了之后,心中有了一个大概:这个文档准备讲什么。 - -例如这个[link](https://github.com/PaddlePaddle/Paddle/tree/develop/doc/design/cluster_train#objective) -### 架构图 -一个系统或者模块的设计文档,首先要有架构图。***要有架构图,要有架构图,要有架构图***。。。。这句话可以多重复几遍。俗称,无图无真相或者一图胜千言。最起码有一个架构图说明各个模块的关系。图的表现能力远远超过了文字,特别是模块关系相关的和流程相关的场景下。 - -可以看一下这个文档中的图 [link](https://github.com/PaddlePaddle/Paddle/tree/develop/doc/design/cluster_train) - -顺便提一句,里边的图都是用[OmniGraffle](https://www.omnigroup.com/omnigraffle)画的,就是贵了点:$99。“99你买了不吃亏,99你买了不上当。”[wuyi](#wuyi) - -### 层次结构清晰 -代码层面上,我们为了开发大的项目,很自然的把代码分模块、分文件。文档也是如此。每个文档或者章节说明他应该要想说的事情,尽量的减少涉及的范围。涉及的范围越少,需要阐述、解释的东西就越少,理解起来需要的背景知识就越少,就越好理解,写的人出错的概率也越少。。。 - -- 整体分层 - - 系统设计 - - 模块设计 - - 接口设计 - - 部署 - -相互之间需要尽量少的“越权”。例如这个[link](https://github.com/PaddlePaddle/Paddle/pull/2013#discussion_r115147388) - -另外一个容易忽视的问题是,文档内部的层次结构。MarkDown文件不像Doc一样可以自动生成目录页,一个部分如果太多,就会让看得人失去层次感。以这个文档为例 [link](https://github.com/PaddlePaddle/Paddle/tree/develop/doc/design/cluster_train)。这个文档我也写过,只是把`Fault Recovery`的部分和前边正常的`Training Job`合到一起去了,结果发现越写越乱,后来看到Helin写的分层之后的文档,感觉流畅多了。 - -## 逻辑要清晰、流畅 -- 概念的出现不要突兀。 - 文档的最前边有一个术语的解释,把文档中提到的概念先解释一下,这样,别人在看到那些概念的时候不会觉得很突兀。同时,前后要呼应。 - - 例如这个[link](https://github.com/PaddlePaddle/Paddle/pull/2013#discussion_r114952115) - - 如果概念或者名词后边没有出现,该删除还是删除了吧! - -- 多种方案的选择要简述原因。 - 例如这个[link](https://github.com/PaddlePaddle/Paddle/pull/2013#discussion_r115147115) - -- Design doc中不应该有“?” [wuyi](#wuyi) - 应该都是陈述句的描述。有不确定的问题可以提Issue来讨论获得结论。 - 对于自己不确定的地方,与其含混而过不如找人讨论先搞一个靠谱的可以讨论的。绝大多数情况下,这种含混而过的都会被Reivew给纠出来。即便就不出来,岂不是自己给自己埋一个坑? - -- 文档当中不要黏贴大量的代码 - 代码一般都是细节,改变的非常的快,文档可能很快就失效了,需要重新的修正。另外,最主要的是大段的代码会让人的思路中断,陷于实现的细节当中。 - -- 不准备实现的就不要写了 - 最多放到放到`Future`中展望一下。 - -## 文笔要好 -啊呀,不想当作家的程序员不是好程序员。这个当然比较难,要看大家的“学好数理化,走遍天下都不怕”的基本功的“深厚”程度了:) - -顺便推荐一下公众号:老万故事会[link](https://freewechat.com/profile/MzI1MDQ3NTAxOQ==),一个文章和代码写的一样好的人[yi](#yi)。 - -## 如何提高写文档的效率 -这段其实本来没想到加的,开完组会之后听老大讲提高工作效率的事情有点感想,就写了。 - -我不是很怀疑自己写代码的效率,但是严重怀疑自己写文档的效率。感觉写文档比写代码烧脑多了。现在想想,最主要的点在于文档的结构和分层问题。 - -提高效率最好的办法是什么?是确定范围,很多东西不用讲了,当然效率就提高上去了。 - -写系统设计文档,主要需要表现模块间的关系和通信,特别是特定场景下的他们是如何配合的。这个过程中需要把关键的概念说清楚,例如这个[link](https://github.com/PaddlePaddle/Paddle/tree/develop/doc/design/cluster_train)中,`Trainjob`和`Fault Recovery`主要是讲模块间关系和配合的,[`task`](https://github.com/PaddlePaddle/Paddle/tree/develop/doc/design/cluster_train#task)作为一个关键的概念讲了。其他的部分,稍显细节的都可以放到模块设计中去讲。 - -另外一个,认真≠ 犹豫,也≠ 纠结。 - -如果感到了纠结,那说明没找到问题的根本。我在写文件上传的时候对做不做缓存优化纠结了很长时间,请教了Helin一会就讨论完毕了。如果感到了纠结,那是需要跟别人请教。不纠结的地方,下决断要果断。 - -## 参考 -- WangYi -- WuYi -- Helin -- YanXu -- CaoYing -- GitLab From f904e794103174e4fefcdf39bdd9db424bdc0cbc Mon Sep 17 00:00:00 2001 From: gongweibao Date: Wed, 7 Jun 2017 16:04:34 +0800 Subject: [PATCH 0578/3256] rm not need --- doc/howto/dev/introduction_to_pr.md | 139 ---------------------------- 1 file changed, 139 deletions(-) delete mode 100644 doc/howto/dev/introduction_to_pr.md diff --git a/doc/howto/dev/introduction_to_pr.md b/doc/howto/dev/introduction_to_pr.md deleted file mode 100644 index 71cbbdf8611..00000000000 --- a/doc/howto/dev/introduction_to_pr.md +++ /dev/null @@ -1,139 +0,0 @@ -# Desgin Doc的一点总结 -## 前言 -故事还要从前两天我提交的[FileManager](https://github.com/PaddlePaddle/Paddle/pull/2013)的Design Doc PR开始。 - -[FileManager](https://github.com/PaddlePaddle/Paddle/pull/2013)这个文档我肯定是用心写了,我也清楚地记得,提交之前也仔细的检查了(这点自觉性还是有的),结果,我突然发现,我的PR被Comments刷屏了;这还是其次,更要命的是,当网络速度稍慢的时候会提示我:Comments 太多啦!页面打不开!!哦。简直有点人间惨剧的味道!我要把这些和之前的Comments以及一些典型的Comments总结一下,避免同样的问题,同时也希望对您有用。 -link -我觉得里边有几个基本的原则: - -- 做事情要精益求精 - 这个是做事情的基础,也是我们讨论的基础。美,多是相似的,丑才是千奇百怪。 - - 精益求精是一种态度,态度比什么都重要。[yi](#yi) -- 节约别人的时间 - 同时也是给自己节约时间 - -- 有礼貌,有感恩的心 - > 我发现诸多之前提了comment,但是没有改,也没有回复。这个不太好吧。[ying](#ying) - > 每个comment都必须回复。这是开源社区的基本礼貌。别人帮了忙,应该说谢谢。[yi](#yi) - -我的理解,写Doc精益求精要从基础开始,首先要规范;其次要有提纲挈领的东西,让人一眼基本明白要做的事情;然后,讲述事情结构要清晰,要分层,用最小化涉及的原则,该讲的要解释,不该讲的不讲;接下来逻辑要清晰,要流畅,不要太突兀。。。当然还有很多,比如文笔要好!:) - -锻炼一下,中国人办事也不一定那么糙的,对吧! :) - -## 基础规范 - -- 语言选择:中文 or 英文? - 最好用英文,因为外国同学无法阅读中文。但是,语言是用来交流的,如果您认为英文无法完善的表达自己的意思的时候,还是用中文吧! - - 重要的是,不管哪种文,***英文的拼写要对,中文要没有错别字***! - - 如果文章用的是中文,那么请用中文的标点符号。反之,用英文的标点符号。***而且要保持统一、而且要保持完整***。[wuyi](#wuyi) - - 例如这个[link](https://github.com/PaddlePaddle/Paddle/pull/2013#discussion_r114951817) - - 还有这个[link](https://github.com/PaddlePaddle/Paddle/pull/2013#discussion_r115093563) - - (请原谅我多加了一个link做锚文本,Sphinx有一个bug:用中文的会报错。下边的处理类似。) - -- 缩写的全称 - 一个缩写第一次出现的时候,要把他们的未缩写形式写出来。如果该单词代表的概念比较复杂,请在术语解释中把他写清楚! - - 例如这个[link](https://github.com/PaddlePaddle/Paddle/pull/2013#discussion_r115093329) - -- 英语写法错误 - [yi](#yi)总结了一下在code review的时候经常见的一些英语写法错误: Python写成python,TensorFlow写成Tensorflow,Travis CI 写成 Travis-CI,ReLU写成Relu,unit test写成unittest,MD5写成md5。 - - 大小写简单的规则如下: - - 英文的缩写是要用大写的。 - 例如这个[link](https://github.com/PaddlePaddle/Paddle/pull/2013#discussion_r115091985) - - 英文的句子的首字母是要大写的。 - - 一个专有的名词第一个字母一般是要大写的。 - - yiwang推荐了一个工具:[grammer](https://www.grammarly.com/),可以作为插件安装到Chrome中,自动检查拼写和语法问题。 - -- 不要提交没有用的文件 - 例如这个 [link](https://github.com/PaddlePaddle/Paddle/pull/1964#discussion_r114414822) - -- 提交的代码要经过验证 - 提交的代码都没有验证过是几个意思? - -- 参考资料要设置超链,链接到对方 - 不设置的话俗称Copy。可以用`[name](#name)`设置MarkDown文件中的引用。一如此文中很多地方出现的那样。 - -- 给别人的东西步骤是要可以执行的 - 看这个[link](https://github.com/wangkuiyi/ipynb)是如何写步骤的,或者这个[link](https://github.com/PaddlePaddle/Paddle/pull/1602#issuecomment-285964510)(虽然只是一个Comment)。 - -- 链接可以直接跳到精确的位置 - 如果能一步到位就不要让别人走两步,节约大家彼此的时间。 - -## 提纲挈领 -### 目标要明确 -一般开头的一段话就要用简短的几句话把文档要描述的目标写清楚,别人看了之后,心中有了一个大概:这个文档准备讲什么。 - -例如这个[link](https://github.com/PaddlePaddle/Paddle/tree/develop/doc/design/cluster_train#objective) -### 架构图 -一个系统或者模块的设计文档,首先要有架构图。***要有架构图,要有架构图,要有架构图***。。。。这句话可以多重复几遍。俗称,无图无真相或者一图胜千言。最起码有一个架构图说明各个模块的关系。图的表现能力远远超过了文字,特别是模块关系相关的和流程相关的场景下。 - -可以看一下这个文档中的图 [link](https://github.com/PaddlePaddle/Paddle/tree/develop/doc/design/cluster_train) - -顺便提一句,里边的图都是用[OmniGraffle](https://www.omnigroup.com/omnigraffle)画的,就是贵了点:$99。“99你买了不吃亏,99你买了不上当。”[wuyi](#wuyi) - -### 层次结构清晰 -代码层面上,我们为了开发大的项目,很自然的把代码分模块、分文件。文档也是如此。每个文档或者章节说明他应该要想说的事情,尽量的减少涉及的范围。涉及的范围越少,需要阐述、解释的东西就越少,理解起来需要的背景知识就越少,就越好理解,写的人出错的概率也越少。。。 - -- 整体分层 - - 系统设计 - - 模块设计 - - 接口设计 - - 部署 - -相互之间需要尽量少的“越权”。例如这个[link](https://github.com/PaddlePaddle/Paddle/pull/2013#discussion_r115147388) - -另外一个容易忽视的问题是,文档内部的层次结构。MarkDown文件不像Doc一样可以自动生成目录页,一个部分如果太多,就会让看得人失去层次感。以这个文档为例 [link](https://github.com/PaddlePaddle/Paddle/tree/develop/doc/design/cluster_train)。这个文档我也写过,只是把`Fault Recovery`的部分和前边正常的`Training Job`合到一起去了,结果发现越写越乱,后来看到Helin写的分层之后的文档,感觉流畅多了。 - -## 逻辑要清晰、流畅 -- 概念的出现不要突兀。 - 文档的最前边有一个术语的解释,把文档中提到的概念先解释一下,这样,别人在看到那些概念的时候不会觉得很突兀。同时,前后要呼应。 - - 例如这个[link](https://github.com/PaddlePaddle/Paddle/pull/2013#discussion_r114952115) - - 如果概念或者名词后边没有出现,该删除还是删除了吧! - -- 多种方案的选择要简述原因。 - 例如这个[link](https://github.com/PaddlePaddle/Paddle/pull/2013#discussion_r115147115) - -- Design doc中不应该有“?” [wuyi](#wuyi) - 应该都是陈述句的描述。有不确定的问题可以提Issue来讨论获得结论。 - 对于自己不确定的地方,与其含混而过不如找人讨论先搞一个靠谱的可以讨论的。绝大多数情况下,这种含混而过的都会被Reivew给纠出来。即便就不出来,岂不是自己给自己埋一个坑? - -- 文档当中不要黏贴大量的代码 - 代码一般都是细节,改变的非常的快,文档可能很快就失效了,需要重新的修正。另外,最主要的是大段的代码会让人的思路中断,陷于实现的细节当中。 - -- 不准备实现的就不要写了 - 最多放到放到`Future`中展望一下。 - -## 文笔要好 -啊呀,不想当作家的程序员不是好程序员。这个当然比较难,要看大家的“学好数理化,走遍天下都不怕”的基本功的“深厚”程度了:) - -顺便推荐一下公众号:老万故事会[link](https://freewechat.com/profile/MzI1MDQ3NTAxOQ==),一个文章和代码写的一样好的人[yi](#yi)。 - -## 如何提高写文档的效率 -这段其实本来没想到加的,开完组会之后听老大讲提高工作效率的事情有点感想,就写了。 - -我不是很怀疑自己写代码的效率,但是严重怀疑自己写文档的效率。感觉写文档比写代码烧脑多了。现在想想,最主要的点在于文档的结构和分层问题。 - -提高效率最好的办法是什么?是确定范围,很多东西不用讲了,当然效率就提高上去了。 - -写系统设计文档,主要需要表现模块间的关系和通信,特别是特定场景下的他们是如何配合的。这个过程中需要把关键的概念说清楚,例如这个[link](https://github.com/PaddlePaddle/Paddle/tree/develop/doc/design/cluster_train)中,`Trainjob`和`Fault Recovery`主要是讲模块间关系和配合的,[`task`](https://github.com/PaddlePaddle/Paddle/tree/develop/doc/design/cluster_train#task)作为一个关键的概念讲了。其他的部分,稍显细节的都可以放到模块设计中去讲。 - -另外一个,认真≠ 犹豫,也≠ 纠结。 - -如果感到了纠结,那说明没找到问题的根本。我在写文件上传的时候对做不做缓存优化纠结了很长时间,请教了Helin一会就讨论完毕了。如果感到了纠结,那是需要跟别人请教。不纠结的地方,下决断要果断。 - -## 参考 -- WangYi -- WuYi -- Helin -- YanXu -- CaoYing -- GitLab From 784e21848fea3b183cc270b49bc94668ed9d0285 Mon Sep 17 00:00:00 2001 From: hedaoyuan Date: Wed, 7 Jun 2017 17:56:32 +0800 Subject: [PATCH 0579/3256] Fix the error of group convolution. --- paddle/function/ConvOp.h | 48 +++++++++++++++++++---- paddle/function/ConvOpTest.cpp | 2 +- paddle/function/GemmConvOp.cpp | 33 +++++++++------- paddle/gserver/layers/ExpandConvLayer.cpp | 12 ++++-- 4 files changed, 67 insertions(+), 28 deletions(-) diff --git a/paddle/function/ConvOp.h b/paddle/function/ConvOp.h index 9ad1785fbb4..2cfc0712557 100644 --- a/paddle/function/ConvOp.h +++ b/paddle/function/ConvOp.h @@ -46,8 +46,13 @@ namespace paddle { * are all NCHW format. Where N is batch size, C is the number of channels, * H and W is the height and width of image or image gradient. * - * 2. The format of the filter data is MCHW, where M is the number of - * output image channels, C is the number of input image channels, + * 2. The format of the filter data is MCHW, where M is the number of output + * image channels, C is the number of input image channels, + * H and W is height and width of filter. + * + * If groups is greater than 1, the filter's data format should be GMCHW, + * where G is the groups, and G * M is the number of output image channels, + * G * C is the number of input image channels, * H and W is height and width of filter. */ class ConvFunctionBase : public FunctionBase { @@ -73,20 +78,47 @@ public: const TensorShape& output) { // inputs and outputs arguments should be 4-dimensional. CHECK_EQ(input.ndims(), (size_t)4); - CHECK_EQ(filter.ndims(), (size_t)4); CHECK_EQ(output.ndims(), (size_t)4); - // The batchSize of the input needs to be equal to // the batchSize of the output. CHECK_EQ(input[0], output[0]); - // The input and output channel dimensions are the second and first - // dimensions of the filter shape. - CHECK_EQ(input[1] / groups_, filter[1]); - CHECK_EQ(output[1], filter[0]); + if (filter.ndims() == (size_t)4) { + // If the filter's dimension is 4, groups convolution is not supported. + CHECK_EQ(groups_, (size_t)1); + // The input and output channel dimensions are the second and first + // dimensions of the filter shape. + CHECK_EQ(input[1], filter[1]); + CHECK_EQ(output[1], filter[0]); + } else { + // filter argument should be 5-dimensional. + CHECK_EQ(filter.ndims(), (size_t)5); + // The first dimension of the filter is the size of the group + CHECK_EQ(filter[0], groups_); + // The input and output channel dimensions are the third and second + // dimensions of the filter shape. + CHECK_EQ(input[1], filter[2] * groups_); + CHECK_EQ(output[1], filter[1] * groups_); + } } protected: + size_t getFilterHeight(const TensorShape& filter) const { + if (filter.ndims() == 5) { + return filter[3]; + } else { + return filter[2]; + } + } + + size_t getFilterWidth(const TensorShape& filter) const { + if (filter.ndims() == 5) { + return filter[4]; + } else { + return filter[3]; + } + } + std::vector strides_; std::vector paddings_; diff --git a/paddle/function/ConvOpTest.cpp b/paddle/function/ConvOpTest.cpp index 2fa0b365465..280aed8a5c8 100644 --- a/paddle/function/ConvOpTest.cpp +++ b/paddle/function/ConvOpTest.cpp @@ -80,7 +80,7 @@ public: } else if (type == BACKWARD_INPUT_TEST) { test.addInputs(BufferArg(VALUE_TYPE_FLOAT, output)); test.addInputs(BufferArg(VALUE_TYPE_FLOAT, filter)); - test.addOutputs(BufferArg(VALUE_TYPE_FLOAT, input)); + test.addOutputs(BufferArg(VALUE_TYPE_FLOAT, input), ADD_TO); test.run(); } else if (type == BACKWARD_FILTER_TEST) { test.addInputs(BufferArg(VALUE_TYPE_FLOAT, output)); diff --git a/paddle/function/GemmConvOp.cpp b/paddle/function/GemmConvOp.cpp index 9dab831d32c..c7a57801ed6 100644 --- a/paddle/function/GemmConvOp.cpp +++ b/paddle/function/GemmConvOp.cpp @@ -134,15 +134,15 @@ public: beta = 0.0; } - size_t batchSize = inputs[0].shape()[0]; - size_t inputChannels = inputs[0].shape()[1]; - size_t inputHeight = inputs[0].shape()[2]; - size_t inputWidth = inputs[0].shape()[3]; - size_t filterHeight = inputs[1].shape()[2]; - size_t filterWidth = inputs[1].shape()[3]; - size_t outputChannels = outputs[0].shape()[1]; - size_t outputHeight = outputs[0].shape()[2]; - size_t outputWidth = outputs[0].shape()[3]; + size_t batchSize = input[0]; + size_t inputChannels = input[1]; + size_t inputHeight = input[2]; + size_t inputWidth = input[3]; + size_t filterHeight = getFilterHeight(filter); + size_t filterWidth = getFilterWidth(filter); + size_t outputChannels = output[1]; + size_t outputHeight = output[2]; + size_t outputWidth = output[3]; real* inputData = inputs[0].data(); real* filterData = inputs[1].data(); @@ -158,7 +158,8 @@ public: size_t inputOffset = (inputChannels / groups_) * inputHeight * inputWidth; size_t outputOffset = (outputChannels / groups_) * outputHeight * outputWidth; - size_t filterOffset = inputs[1].shape().getElements() / groups_; + size_t filterOffset = filter.getElements() / groups_; + for (size_t i = 0; i < batchSize; i++) { for (size_t g = 0; g < groups_; g++) { im2col(inputData + g * inputOffset, @@ -211,7 +212,9 @@ public: void calc(const BufferArgs& inputs, const BufferArgs& outputs) override { CHECK_EQ(numInputs_, inputs.size()); CHECK_EQ(numOutputs_, outputs.size()); - // CHECK_EQ(outputs[0].getArgType(), ADD_TO); + // Since the implementation of Col2ImFunctor is ADD_TO, + // this function only supports ADD_TO mode. + CHECK_EQ(outputs[0].getArgType(), ADD_TO); const TensorShape& output = inputs[0].shape(); const TensorShape& filter = inputs[1].shape(); const TensorShape& input = outputs[0].shape(); @@ -221,8 +224,8 @@ public: size_t inputChannels = input[1]; size_t inputHeight = input[2]; size_t inputWidth = input[3]; - size_t filterHeight = filter[2]; - size_t filterWidth = filter[3]; + size_t filterHeight = getFilterHeight(filter); + size_t filterWidth = getFilterWidth(filter); size_t outputChannels = output[1]; size_t outputHeight = output[2]; size_t outputWidth = output[3]; @@ -311,8 +314,8 @@ public: size_t inputChannels = input[1]; size_t inputHeight = input[2]; size_t inputWidth = input[3]; - size_t filterHeight = filter[2]; - size_t filterWidth = filter[3]; + size_t filterHeight = getFilterHeight(filter); + size_t filterWidth = getFilterWidth(filter); size_t outputChannels = output[1]; size_t outputHeight = output[2]; size_t outputWidth = output[3]; diff --git a/paddle/gserver/layers/ExpandConvLayer.cpp b/paddle/gserver/layers/ExpandConvLayer.cpp index 379b2d339d4..914689e66cd 100644 --- a/paddle/gserver/layers/ExpandConvLayer.cpp +++ b/paddle/gserver/layers/ExpandConvLayer.cpp @@ -80,8 +80,11 @@ void ExpandConvLayer::forward(PassType passType) { (size_t)imgSizeH_[i], (size_t)imgSizeW_[i]}); filterShape_[i] = - TensorShape({!isDeconv_ ? (size_t)numFilters_ : (size_t)channels_[i], - !isDeconv_ ? (size_t)channels_[i] : (size_t)numFilters_, + TensorShape({(size_t)groups_[i], + !isDeconv_ ? (size_t)numFilters_ / groups_[i] + : (size_t)channels_[i] / groups_[i], + !isDeconv_ ? (size_t)channels_[i] / groups_[i] + : (size_t)numFilters_ / groups_[i], (size_t)filterSizeY_[i], (size_t)filterSize_[i]}); outputShape_[i] = TensorShape({(size_t)batchSize, @@ -96,8 +99,9 @@ void ExpandConvLayer::forward(PassType passType) { BufferArgs outputs; inputs.addArg(*getInputValue(i), inputShape_[i]); inputs.addArg(*weights_[i]->getW(), filterShape_[i]); - outputs.addArg( - *getOutputValue(), outputShape_[i], i == 0 ? ASSIGN_TO : ADD_TO); + outputs.addArg(*getOutputValue(), + outputShape_[i], + !isDeconv_ && i == 0 ? ASSIGN_TO : ADD_TO); forward_[i]->calc(inputs, outputs); } -- GitLab From 95a7bc018fa4c33fb314bd1b981a4ef918e4396a Mon Sep 17 00:00:00 2001 From: hedaoyuan Date: Wed, 7 Jun 2017 18:00:39 +0800 Subject: [PATCH 0580/3256] follow comments --- paddle/function/ConvOpTest.cpp | 40 +++++++++++++++++----------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/paddle/function/ConvOpTest.cpp b/paddle/function/ConvOpTest.cpp index 280aed8a5c8..59c7238d218 100644 --- a/paddle/function/ConvOpTest.cpp +++ b/paddle/function/ConvOpTest.cpp @@ -20,9 +20,9 @@ limitations under the License. */ namespace paddle { enum TestType { - FORWARD_TEST = 0, - BACKWARD_INPUT_TEST = 1, - BACKWARD_FILTER_TEST = 2, + kForwardTest = 0, + kBackwardInputTest = 1, + kBackwardFilterTest = 2, }; template @@ -43,16 +43,16 @@ public: if (padding >= filterSize) break; size_t outputSize = (inputSize - filterSize + 2 * padding + stride) / stride; - LOG(INFO) << " batchSize=" << batchSize - << " inputChannels=" << inputChannels - << " inputHeight=" << inputSize - << " inputWidth=" << inputSize - << " outputChannels=" << outputChannels - << " filterHeight=" << filterSize - << " filterWidth=" << filterSize - << " outputHeight=" << outputSize - << " outputWidth=" << outputSize - << " stride=" << stride << " padding=" << padding; + VLOG(3) << " batchSize=" << batchSize + << " inputChannels=" << inputChannels + << " inputHeight=" << inputSize + << " inputWidth=" << inputSize + << " outputChannels=" << outputChannels + << " filterHeight=" << filterSize + << " filterWidth=" << filterSize + << " outputHeight=" << outputSize + << " outputWidth=" << outputSize + << " stride=" << stride << " padding=" << padding; std::vector paddings = {padding, padding}; std::vector strides = {stride, stride}; @@ -72,17 +72,17 @@ public: TensorShape output{ batchSize, outputChannels, outputSize, outputSize}; - if (type == FORWARD_TEST) { + if (type == kForwardTest) { test.addInputs(BufferArg(VALUE_TYPE_FLOAT, input)); test.addInputs(BufferArg(VALUE_TYPE_FLOAT, filter)); test.addOutputs(BufferArg(VALUE_TYPE_FLOAT, output)); test.run(); - } else if (type == BACKWARD_INPUT_TEST) { + } else if (type == kBackwardInputTest) { test.addInputs(BufferArg(VALUE_TYPE_FLOAT, output)); test.addInputs(BufferArg(VALUE_TYPE_FLOAT, filter)); test.addOutputs(BufferArg(VALUE_TYPE_FLOAT, input), ADD_TO); test.run(); - } else if (type == BACKWARD_FILTER_TEST) { + } else if (type == kBackwardFilterTest) { test.addInputs(BufferArg(VALUE_TYPE_FLOAT, output)); test.addInputs(BufferArg(VALUE_TYPE_FLOAT, input)); test.addOutputs(BufferArg(VALUE_TYPE_FLOAT, filter)); @@ -100,23 +100,23 @@ public: TEST(Forward, GEMM) { ConvolutionTest test( - "NaiveConv-CPU", "GemmConv-CPU", FORWARD_TEST); + "NaiveConv-CPU", "GemmConv-CPU", kForwardTest); } #ifndef PADDLE_ONLY_CPU TEST(Forward, GEMM2) { ConvolutionTest test( - "GemmConv-CPU", "GemmConv-GPU", FORWARD_TEST); + "GemmConv-CPU", "GemmConv-GPU", kForwardTest); } TEST(BackwardInput, GEMM) { ConvolutionTest test( - "GemmConvGradInput-CPU", "GemmConvGradInput-GPU", BACKWARD_INPUT_TEST); + "GemmConvGradInput-CPU", "GemmConvGradInput-GPU", kBackwardInputTest); } TEST(BackwardFilter, GEMM) { ConvolutionTest test( - "GemmConvGradFilter-CPU", "GemmConvGradFilter-GPU", BACKWARD_FILTER_TEST); + "GemmConvGradFilter-CPU", "GemmConvGradFilter-GPU", kBackwardFilterTest); } #endif -- GitLab From 5bab98a3c5296ff69ddd834c8bbbbe4bbd6a4db3 Mon Sep 17 00:00:00 2001 From: Luo Tao Date: Wed, 7 Jun 2017 18:14:37 +0800 Subject: [PATCH 0581/3256] remove top_k argument in classification_cost --- python/paddle/trainer_config_helpers/layers.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/python/paddle/trainer_config_helpers/layers.py b/python/paddle/trainer_config_helpers/layers.py index 5667e5ff2bc..67aeb94defd 100755 --- a/python/paddle/trainer_config_helpers/layers.py +++ b/python/paddle/trainer_config_helpers/layers.py @@ -3860,7 +3860,6 @@ def classification_cost(input, label, weight=None, name=None, - top_k=None, evaluator=classification_error_evaluator, layer_attr=None): """ @@ -3875,8 +3874,6 @@ def classification_cost(input, :param weight: The weight affects the cost, namely the scale of cost. It is an optional argument. :type weight: LayerOutput - :param top_k: number k in top-k error rate - :type top_k: int :param evaluator: Evaluator method. :param layer_attr: layer's extra attribute. :type layer_attr: ExtraLayerAttribute @@ -3904,7 +3901,7 @@ def classification_cost(input, assert isinstance(e.for_classification, bool) assert e.for_classification - e(name=e.__name__, input=input, label=label, weight=weight, top_k=top_k) + e(name=e.__name__, input=input, label=label, weight=weight) if not isinstance(evaluator, collections.Sequence): evaluator = [evaluator] -- GitLab From 99dc60642d6b94a5ccc92be21917cfa866d6e7f8 Mon Sep 17 00:00:00 2001 From: qiaolongfei Date: Wed, 7 Jun 2017 23:42:11 +0800 Subject: [PATCH 0582/3256] new parameterupdater use paddle pserver cclient of go --- CMakeLists.txt | 1 + .../cluster_train/remote_parameter_updater.md | 21 ++++ go/cmake/golang.cmake | 8 +- go/pserver/cclient/CMakeLists.txt | 12 +- go/pserver/cclient/test/CMakeLists.txt | 13 ++- go/pserver/cclient/test/main.c | 19 ++-- go/pserver/cclient/test/test_train.py | 60 ++++++++++ paddle/api/CMakeLists.txt | 4 +- paddle/api/Paddle.i | 1 + paddle/api/PaddleAPI.h | 2 + paddle/api/ParameterUpdater.cpp | 9 ++ paddle/trainer/CMakeLists.txt | 11 +- paddle/trainer/NewRemoteParameterUpdater.cpp | 88 +++++++++++++++ paddle/trainer/NewRemoteParameterUpdater.h | 105 ++++++++++++++++++ python/paddle/v2/optimizer.py | 15 ++- python/paddle/v2/trainer.py | 7 +- 16 files changed, 352 insertions(+), 24 deletions(-) create mode 100644 doc/design/cluster_train/remote_parameter_updater.md create mode 100644 go/pserver/cclient/test/test_train.py create mode 100644 paddle/trainer/NewRemoteParameterUpdater.cpp create mode 100644 paddle/trainer/NewRemoteParameterUpdater.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 79210d04364..c2218be5efb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -127,6 +127,7 @@ endif(WITH_GPU) add_subdirectory(proto) add_subdirectory(paddle) add_subdirectory(python) +add_subdirectory(go/pserver/cclient) if(WITH_DOC) add_subdirectory(doc) diff --git a/doc/design/cluster_train/remote_parameter_updater.md b/doc/design/cluster_train/remote_parameter_updater.md new file mode 100644 index 00000000000..6e8e5938455 --- /dev/null +++ b/doc/design/cluster_train/remote_parameter_updater.md @@ -0,0 +1,21 @@ +# Design Doc: Remote Parameter Updater for Cluster Train + +For an overview of distribute training, please refer to [distributed training design doc](README.md). In this design doc, we will discuss the parameter updater that will use parameter server cclient [The Client Library of Parameter Server Design Doc](pserver_client.md) to manage and update parameters. + +## Parameter Updater + +Parameter Updater is used by trainer to manage and update parameter, there are mainly two kind of parameter updater: local and remote, since this design is for cluster train, we will only discuss remote parameter updater here. + +### Remote Parameter Updater + +Remote Parameter Updater manage parameters through remote parameter server with the client that communicate with pserver([The Client Library of Parameter Server Design Doc](pserver_client.md)) + +In PaddlePaddle Python V2 API, trainer is implemented in python, and the trainer will hold a instance of parameter updater and call it's functions directly. In this design, we will also expose the api of RemoteParameterUpdater to python with swig. + +#### Sparse Remote Parameter Updater + +Since we will only implement dense parameter management new, the mechanism for sparse parameter will be discussed in next stage. + +### Interface Design + +TBD diff --git a/go/cmake/golang.cmake b/go/cmake/golang.cmake index d38d06de234..7c85fb6298d 100644 --- a/go/cmake/golang.cmake +++ b/go/cmake/golang.cmake @@ -17,7 +17,7 @@ function(GO_LIBRARY NAME BUILD_TYPE) endif() file(GLOB GO_SOURCE RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "*.go") - file(RELATIVE_PATH rel ${CMAKE_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}) + file(RELATIVE_PATH rel ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}) # find Paddle directory. get_filename_component(PARENT_DIR ${CMAKE_CURRENT_SOURCE_DIR} DIRECTORY) @@ -32,12 +32,14 @@ function(GO_LIBRARY NAME BUILD_TYPE) # will use the local changes in Paddle rather than checkout Paddle # in github. add_custom_target(copyPaddle - COMMAND ln -sf ${PADDLE_DIR} ${PADDLE_IN_GOPATH}) + COMMAND rm -rf ${PADDLE_IN_GOPATH}/Paddle + COMMAND ln -sf ${PADDLE_DIR} ${PADDLE_IN_GOPATH}/Paddle) add_dependencies(goGet copyPaddle) add_custom_command(OUTPUT ${OUTPUT_DIR}/.timestamp COMMAND env GOPATH=${GOPATH} ${CMAKE_Go_COMPILER} build ${BUILD_MODE} - -o "${CMAKE_CURRENT_BINARY_DIR}/${LIB_NAME}" + -gcflags=-shared -asmflags=-shared -installsuffix=_shared -a + -o "${CMAKE_CURRENT_BINARY_DIR}/${LIB_NAME}" ${CMAKE_GO_FLAGS} ${GO_SOURCE} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/go/pserver/cclient/CMakeLists.txt b/go/pserver/cclient/CMakeLists.txt index c017d746561..e00dd6b14a9 100644 --- a/go/pserver/cclient/CMakeLists.txt +++ b/go/pserver/cclient/CMakeLists.txt @@ -9,5 +9,15 @@ project(cxx_go C Go) include(golang) include(flags) -go_library(client STATIC) +go_library(paddle_pserver_cclient STATIC) + +if(PROJ_ROOT) + add_custom_command(OUTPUT ${PROJ_ROOT}/paddle/trainer/libpaddle_pserver_cclient.a + COMMAND cp ${CMAKE_BINARY_DIR}/go/pserver/cclient/libpaddle_pserver_cclient.h ${PROJ_ROOT}/paddle/trainer/ + COMMAND cp ${CMAKE_BINARY_DIR}/go/pserver/cclient/libpaddle_pserver_cclient.a ${PROJ_ROOT}/paddle/trainer/ + WORKING_DIRECTORY ${PROJ_ROOT}/paddle + DEPENDS paddle_pserver_cclient) + add_custom_target(paddle_pserver_cclient_lib ALL DEPENDS ${PROJ_ROOT}/paddle/trainer/libpaddle_pserver_cclient.a) +endif(PROJ_ROOT) + add_subdirectory(test) diff --git a/go/pserver/cclient/test/CMakeLists.txt b/go/pserver/cclient/test/CMakeLists.txt index 16f84648c1d..762772812f5 100644 --- a/go/pserver/cclient/test/CMakeLists.txt +++ b/go/pserver/cclient/test/CMakeLists.txt @@ -1,11 +1,16 @@ cmake_minimum_required(VERSION 3.0) -include_directories(${CMAKE_BINARY_DIR}) - add_executable(main main.c) -add_dependencies(main client) +add_dependencies(main paddle_pserver_cclient) if(APPLE) set(CMAKE_EXE_LINKER_FLAGS "-framework CoreFoundation -framework Security") endif() -target_link_libraries(main ${CMAKE_BINARY_DIR}/libclient.a) + +if(PROJ_ROOT) + include_directories(${CMAKE_BINARY_DIR}/go/pserver/cclient/) + target_link_libraries(main ${CMAKE_BINARY_DIR}/go/pserver/cclient/libpaddle_pserver_cclient.a pthread) +else(PROJ_ROOT) + include_directories(${CMAKE_BINARY_DIR}) + target_link_libraries(main ${CMAKE_BINARY_DIR}/libpaddle_pserver_cclient.a pthread) +endif(PROJ_ROOT) diff --git a/go/pserver/cclient/test/main.c b/go/pserver/cclient/test/main.c index f75a2110b94..0ad890daa2f 100644 --- a/go/pserver/cclient/test/main.c +++ b/go/pserver/cclient/test/main.c @@ -1,6 +1,6 @@ #include -#include "libclient.h" +#include "libpaddle_pserver_cclient.h" void fail() { // TODO(helin): fix: gtest using cmake is not working, using this @@ -14,10 +14,11 @@ int main() { client c = paddle_new_pserver_client(addr, 1); retry: if (paddle_begin_init_params(c)) { + paddle_parameter param; char name_a[] = "param_a"; char name_b[] = "param_b"; - unsigned char content[] = {0x00, 0x11, 0x22}; + unsigned char content[] = {0x00, 0x00, 0x00}; param.element_type = PADDLE_ELEMENT_TYPE_FLOAT32; param.name = name_a; param.content = content; @@ -32,6 +33,7 @@ retry: if (paddle_init_param(c, param, NULL, 0) != 0) { goto retry; } + if (paddle_finish_init_params(c) != 0) { goto retry; } @@ -41,30 +43,31 @@ retry: unsigned char content[] = {0x00, 0x11, 0x22}; paddle_gradient grads[2] = { - {"param_a", PADDLE_ELEMENT_TYPE_INT32, content, 3}, - {"param_b", PADDLE_ELEMENT_TYPE_FLOAT32, content, 3}}; + {"param_a", PADDLE_ELEMENT_TYPE_FLOAT32, content, 3}, + {"param_b", PADDLE_ELEMENT_TYPE_INT32, content, 3}}; - if (!paddle_send_grads(c, grads, 2)) { + if (paddle_send_grads(c, grads, 2) != 0) { fail(); } paddle_parameter* params[2] = {NULL, NULL}; char* names[] = {"param_a", "param_b"}; - if (!paddle_get_params(c, names, params, 2)) { + if (paddle_get_params(c, names, params, 2) != 0) { fail(); } // get parameters again by reusing the allocated parameter buffers. - if (!paddle_get_params(c, names, params, 2)) { + if (paddle_get_params(c, names, params, 2) != 0) { fail(); } paddle_release_param(params[0]); paddle_release_param(params[1]); - if (!paddle_save_model(c, "/tmp/")) { + if (paddle_save_model(c, "/tmp/") != 0) { fail(); } + printf("test success!\n"); return 0; } diff --git a/go/pserver/cclient/test/test_train.py b/go/pserver/cclient/test/test_train.py new file mode 100644 index 00000000000..ddd6371e0cc --- /dev/null +++ b/go/pserver/cclient/test/test_train.py @@ -0,0 +1,60 @@ +import paddle.v2 as paddle +import paddle.v2.dataset.uci_housing as uci_housing + + +def main(): + # init + paddle.init(use_gpu=False, trainer_count=1, trainer_id=1) + + # network config + x = paddle.layer.data(name='x', type=paddle.data_type.dense_vector(13)) + y_predict = paddle.layer.fc(input=x, + param_attr=paddle.attr.Param(name='w'), + size=1, + act=paddle.activation.Linear(), + bias_attr=paddle.attr.Param(name='b')) + y = paddle.layer.data(name='y', type=paddle.data_type.dense_vector(1)) + cost = paddle.layer.mse_cost(input=y_predict, label=y) + + # create parameters + parameters = paddle.parameters.create(cost) + + # create optimizer + optimizer = paddle.optimizer.Momentum(momentum=0) + + trainer = paddle.trainer.SGD(cost=cost, + parameters=parameters, + update_equation=optimizer, + is_local=False, + pserver_spec="localhost:3000") + + # event_handler to print training and testing info + def event_handler(event): + if isinstance(event, paddle.event.EndIteration): + if event.batch_id % 100 == 0: + print "Pass %d, Batch %d, Cost %f" % ( + event.pass_id, event.batch_id, event.cost) + + if isinstance(event, paddle.event.EndPass): + if (event.pass_id + 1) % 10 == 0: + result = trainer.test( + reader=paddle.batch( + uci_housing.test(), batch_size=2), + feeding={'x': 0, + 'y': 1}) + print "Test %d, %.2f" % (event.pass_id, result.cost) + + # training + trainer.train( + reader=paddle.batch( + paddle.reader.shuffle( + uci_housing.train(), buf_size=500), + batch_size=2), + feeding={'x': 0, + 'y': 1}, + event_handler=event_handler, + num_passes=30) + + +if __name__ == '__main__': + main() diff --git a/paddle/api/CMakeLists.txt b/paddle/api/CMakeLists.txt index e147659566d..c258a152406 100644 --- a/paddle/api/CMakeLists.txt +++ b/paddle/api/CMakeLists.txt @@ -16,7 +16,7 @@ set(API_HEADER Internal.h) add_library(paddle_api STATIC ${API_SOURCES}) -add_dependencies(paddle_api gen_proto_cpp) +add_dependencies(paddle_api gen_proto_cpp paddle_pserver_cclient_lib) INCLUDE(${SWIG_USE_FILE}) INCLUDE_DIRECTORIES(${PROJ_ROOT}/paddle) @@ -44,7 +44,7 @@ SET(SWIG_MODULE_swig_paddle_EXTRA_DEPS ) IF(APPLE) - SET(MACOS_LD_FLAGS "-undefined dynamic_lookup -Wl,-all_load") + SET(MACOS_LD_FLAGS "-undefined dynamic_lookup -Wl,-all_load -framework CoreFoundation -framework Security") ELSE(APPLE) SET(START_GROUP "-Xlinker -start-group") SET(END_GROUP "-Xlinker -end-group") diff --git a/paddle/api/Paddle.i b/paddle/api/Paddle.i index 068ba286c07..3237e73745d 100644 --- a/paddle/api/Paddle.i +++ b/paddle/api/Paddle.i @@ -179,6 +179,7 @@ namespace std { %newobject ParameterOptimizer::needSpecialTraversal; %newobject ParameterUpdater::createLocalUpdater; %newobject ParameterUpdater::createRemoteUpdater; +%newobject ParameterUpdater::createNewRemoteUpdater; %feature("director") UpdateCallback; %feature("autodoc", 1); // To generate method stub, for code hint in ide diff --git a/paddle/api/PaddleAPI.h b/paddle/api/PaddleAPI.h index da0f157abd6..7565ea51fe3 100644 --- a/paddle/api/PaddleAPI.h +++ b/paddle/api/PaddleAPI.h @@ -841,6 +841,8 @@ public: static ParameterUpdater* createRemoteUpdater(OptimizationConfig* config, int passCount, bool useSparseUpdater); + static ParameterUpdater* createNewRemoteUpdater( + OptimizationConfig* config, const std::string pserverSpec); ~ParameterUpdater(); /** diff --git a/paddle/api/ParameterUpdater.cpp b/paddle/api/ParameterUpdater.cpp index 79921ea6e78..eaf8518ae2b 100644 --- a/paddle/api/ParameterUpdater.cpp +++ b/paddle/api/ParameterUpdater.cpp @@ -15,6 +15,7 @@ limitations under the License. */ #include "PaddleAPI.h" #include "PaddleAPIPrivate.h" +#include "paddle/trainer/NewRemoteParameterUpdater.h" #include "paddle/trainer/RemoteParameterUpdater.h" #include "paddle/trainer/ThreadParameterUpdater.h" @@ -28,6 +29,14 @@ ParameterUpdater *ParameterUpdater::createLocalUpdater( return updater; } +ParameterUpdater *ParameterUpdater::createNewRemoteUpdater( + OptimizationConfig *config, const std::string pserverSpec) { + auto updater = new ParameterUpdater(); + updater->m->updater.reset(new paddle::NewRemoteParameterUpdater( + config->m->getConfig(), pserverSpec)); + return updater; +} + ParameterUpdater *ParameterUpdater::createRemoteUpdater( OptimizationConfig *config, int passCount, bool useSparseUpdater) { auto updater = new ParameterUpdater(); diff --git a/paddle/trainer/CMakeLists.txt b/paddle/trainer/CMakeLists.txt index 06c019f0a97..9d246b66901 100644 --- a/paddle/trainer/CMakeLists.txt +++ b/paddle/trainer/CMakeLists.txt @@ -4,6 +4,7 @@ set(TRAINER_SOURCES ParameterUpdater.cpp ParamUtil.cpp RemoteParameterUpdater.cpp + NewRemoteParameterUpdater.cpp Tester.cpp Trainer.cpp TrainerInternal.cpp @@ -16,6 +17,7 @@ set(TRAINER_HEADERS ParameterUpdater.h ParamUtil.h RemoteParameterUpdater.h + NewRemoteParameterUpdater.h Tester.h TesterConfig.h Trainer.h @@ -32,7 +34,7 @@ add_style_check_target(paddle_trainer_lib add_style_check_target(paddle_trainer_lib ${TRAINER_HEADERS}) add_dependencies(paddle_trainer_lib - gen_proto_cpp) + gen_proto_cpp paddle_pserver_cclient_lib) macro(add_paddle_exe TARGET_NAME) add_executable(${TARGET_NAME} ${ARGN}) @@ -56,3 +58,10 @@ install(TARGETS paddle_trainer paddle_merge_model set_target_properties(paddle_trainer PROPERTIES INSTALL_RPATH_USE_LINK_PATH TRUE) set_target_properties(paddle_merge_model PROPERTIES INSTALL_RPATH_USE_LINK_PATH TRUE) + +if(APPLE) + set(CMAKE_EXE_LINKER_FLAGS "-framework CoreFoundation -framework Security") +endif() + +target_link_libraries(paddle_trainer ${CMAKE_CURRENT_SOURCE_DIR}/libpaddle_pserver_cclient.a) +target_link_libraries(paddle_trainer_lib ${CMAKE_CURRENT_SOURCE_DIR}/libpaddle_pserver_cclient.a) diff --git a/paddle/trainer/NewRemoteParameterUpdater.cpp b/paddle/trainer/NewRemoteParameterUpdater.cpp new file mode 100644 index 00000000000..9060052e113 --- /dev/null +++ b/paddle/trainer/NewRemoteParameterUpdater.cpp @@ -0,0 +1,88 @@ +/* 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 "NewRemoteParameterUpdater.h" +#include "Trainer.h" +#include "paddle/utils/Stat.h" + +DECLARE_int32(trainer_id); +DECLARE_string(save_dir); + +namespace paddle { +NewRemoteParameterUpdater::NewRemoteParameterUpdater( + const OptimizationConfig &config, const std::string pserverSpec) + : pserverSpec_(pserverSpec) {} + +void NewRemoteParameterUpdater::init( + const std::vector ¶meters) { + ParameterUpdater::init(parameters); + LOG(INFO) << "NewRemoteParameterUpdater init in"; + + for (auto ¶ : parameters_) { + para->getBuf(PARAMETER_VALUE)->zeroMem(); + para->getBuf(PARAMETER_GRADIENT)->zeroMem(); + } + + // create parameter server client. + parameterClient_ = + paddle_new_pserver_client((char *)pserverSpec_.c_str(), FLAGS_trainer_id); + + // init names_ for get parameter through paddle_cclient + names_ = (char **)malloc(parameterSize() * sizeof(char *)); + for (int i = 0; i < parameterSize(); ++i) { + names_[i] = (char *)parameters_[i]->getName().c_str(); + } + + // init new parameter and gradient. + initNewParameter(newParameters_, PARAMETER_VALUE); + initNewParameter(newGradients_, PARAMETER_GRADIENT); + + // init parameter, one trainer will get the opportunity to int parameter and + // send them to parameter server. Others will get the initialized parameter + // from parameter server + if (paddle_begin_init_params(parameterClient_)) { + LOG(INFO) << "paddle_begin_init_params start"; + for (int i = 0; i < parameterSize(); ++i) { + paddle_init_param(parameterClient_, *newParameters_[i], NULL, 0); + } + paddle_finish_init_params(parameterClient_); + LOG(INFO) << "paddle_begin_init_params done"; + } else { + paddle_get_params( + parameterClient_, names_, newParameters_, (int)parameters_.size()); + } + + LOG(INFO) << "NewRemoteParameterUpdater initialized"; +} + +void NewRemoteParameterUpdater::updateImpl(Parameter *para) {} + +void NewRemoteParameterUpdater::finishBatch(real cost) { + LOG(INFO) << "finishBatch in, cost: " << cost; + + // send gradient to parameter server. + paddle_send_grads(parameterClient_, *newGradients_, parameterSize()); + // get the updated parameter from parameterClient. + paddle_get_params(parameterClient_, names_, newParameters_, parameterSize()); + + // clear gradient after update parameter. + for (auto ¶ : parameters_) { + para->getBuf(PARAMETER_GRADIENT)->zeroMem(); + } +} + +void NewRemoteParameterUpdater::startPass() {} + +bool NewRemoteParameterUpdater::finishPass() { return true; } +} diff --git a/paddle/trainer/NewRemoteParameterUpdater.h b/paddle/trainer/NewRemoteParameterUpdater.h new file mode 100644 index 00000000000..33640bc8a38 --- /dev/null +++ b/paddle/trainer/NewRemoteParameterUpdater.h @@ -0,0 +1,105 @@ +/* Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserve. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. */ + +#pragma once + +#include +#include +#include "ParameterUpdater.h" +#include "libpaddle_pserver_cclient.h" +#include "paddle/pserver/ParameterClient2.h" +#include "paddle/utils/Queue.h" +#include "paddle/utils/Util.h" + +namespace paddle { + +/** + * New remote parameter updater for dense parameters that use cclient of go. + */ +class NewRemoteParameterUpdater : public ParameterUpdater { +public: + NewRemoteParameterUpdater(const OptimizationConfig& config, + const std::string pserverSpec); + ~NewRemoteParameterUpdater() { + if (newGradients_) { + paddle_pserver_client_release(parameterClient_); + } + } + + /** + * initialize the internal parameter client and itself. + */ + virtual void init(const std::vector& parameters); + /** + * @brief start batch + * + * @note one batch training exhibits stateful feature to help + * to do performance tuning, sgd optimization if necessary. + */ + virtual PassType startBatch(int64_t batchSize) { return PASS_TRAIN; } + + /** + * send parameters to pservers and get returned parameters + * from all pservers if necessary. + */ + virtual void finishBatch(real cost); + virtual void startPass(); + virtual bool finishPass(); + + int parameterSize() { return (int)parameters_.size(); } + + /** + * init parameter of paddle pserver cclient. + * @param new_paras + * @param type + */ + void initNewParameter(paddle_parameter**& new_paras, ParameterType type) { + new_paras = + (paddle_parameter**)malloc(sizeof(paddle_parameter*) * parameterSize()); + for (int i = 0; i < parameterSize(); ++i) { + new_paras[i] = (paddle_parameter*)malloc(sizeof(paddle_parameter)); + memset(new_paras[i], 0, sizeof(paddle_parameter)); + } + + for (int i = 0; i < parameterSize(); ++i) { + ParameterPtr para = parameters_[i]; + new_paras[i]->content_len = 10; + new_paras[i]->element_type = PADDLE_ELEMENT_TYPE_FLOAT32; + new_paras[i]->name = (char*)para->getName().c_str(); + new_paras[i]->content = + (unsigned char*)(para->getBuf(type).get()->getData()); + new_paras[i]->content_len = (int)para->getBuf(type).get()->getSize(); + } + } + +protected: + /** + * work need to do after finishBatch + */ + virtual void updateImpl(Parameter* para); + +protected: + /// internal parameter client object for exchanging data with pserver + client parameterClient_ = -1; + /// the parameters for new pserver client + paddle_parameter** newParameters_; + /// the gradinets for new pserver client + paddle_parameter** newGradients_; + /// the names for new parameters. + char** names_; + /// the specification of parameter server "host1:port,host1:port" + std::string pserverSpec_; +}; + +} // namespace paddle diff --git a/python/paddle/v2/optimizer.py b/python/paddle/v2/optimizer.py index 5e99d4a241b..1ef2dceca91 100644 --- a/python/paddle/v2/optimizer.py +++ b/python/paddle/v2/optimizer.py @@ -45,7 +45,12 @@ class Optimizer(object): return swig_api.ParameterUpdater.createRemoteUpdater( self.__opt_conf__, pass_num, use_sparse_updater) - def create_updater(self, is_local, num_passes, use_sparse_updater): + def __create_new_remote_updater__(self, pserver_spec): + return swig_api.ParameterUpdater.createNewRemoteUpdater( + self.__opt_conf__, pserver_spec) + + def create_updater(self, is_local, num_passes, use_sparse_updater, + pserver_spec): """ create proper parameter_updater by configuration. :param is_local: create local or remote parameter updater @@ -64,8 +69,12 @@ class Optimizer(object): if is_local: parameter_updater = self.__create_local_updater__() else: - parameter_updater = self.__create_remote_updater__( - num_passes, use_sparse_updater) + if pserver_spec is None: + parameter_updater = self.__create_remote_updater__( + num_passes, use_sparse_updater) + else: + parameter_updater = self.__create_new_remote_updater__( + pserver_spec) return parameter_updater diff --git a/python/paddle/v2/trainer.py b/python/paddle/v2/trainer.py index 8fdb67cc268..f9658a8c5df 100644 --- a/python/paddle/v2/trainer.py +++ b/python/paddle/v2/trainer.py @@ -49,7 +49,8 @@ class SGD(object): parameters, update_equation, extra_layers=None, - is_local=True): + is_local=True, + pserver_spec=None): if not isinstance(parameters, v2_parameters.Parameters): raise TypeError('parameters should be parameters') @@ -63,6 +64,7 @@ class SGD(object): self.__parameters__ = parameters self.__topology_in_proto__ = topology.proto() self.__is_local__ = is_local + self.__pserver_spec__ = pserver_spec self.__use_sparse_updater__ = self.__topology__.use_sparse_updater() # # In local mode, disable sparse_remote_update. @@ -126,7 +128,8 @@ class SGD(object): __check_train_args__(**locals()) self.__parameter_updater__ = self.__optimizer__.create_updater( - self.__is_local__, num_passes, self.__use_sparse_updater__) + self.__is_local__, num_passes, self.__use_sparse_updater__, + self.__pserver_spec__) self.__parameter_updater__.init(self.__gradient_machine__) self.__gradient_machine__.start() -- GitLab From c9ce0fc650685f2eab2583241c3943a919b8f7b1 Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Wed, 7 Jun 2017 10:52:38 -0700 Subject: [PATCH 0583/3256] try to fix unit test test_ProtoServer, test_TrainerOnePass fix error: /paddle/paddle/.common_test_util.sh: line 97: netstat: command not found --- Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index b6f99ca539d..39af60966b6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -30,7 +30,8 @@ RUN apt-get update && \ python-numpy python-matplotlib gcc g++ \ automake locales clang-format-3.8 swig doxygen cmake \ liblapack-dev liblapacke-dev libboost-dev \ - clang-3.8 llvm-3.8 libclang-3.8-dev && \ + clang-3.8 llvm-3.8 libclang-3.8-dev \ + net-tools && \ apt-get clean -y # Install Go -- GitLab From 92626ba349519ce40415a4bb1074890591f836cb Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Wed, 7 Jun 2017 11:41:42 -0700 Subject: [PATCH 0584/3256] fix error handling for LightNetwork --- paddle/pserver/LightNetwork.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/paddle/pserver/LightNetwork.cpp b/paddle/pserver/LightNetwork.cpp index 8c8ba0a2e51..61bd6d358de 100644 --- a/paddle/pserver/LightNetwork.cpp +++ b/paddle/pserver/LightNetwork.cpp @@ -384,19 +384,22 @@ void SocketClient::TcpClient(const std::string &serverAddr, int serverPort) { /// Now connect to the server int retry_second = 0; - int error = 0; do { - error = connect(sockfd, (sockaddr *)&serv_addr, sizeof(serv_addr)); - if (error == ECONNREFUSED) { + if (connect(sockfd, (sockaddr *)&serv_addr, sizeof(serv_addr)) == 0) { + break; + } + + if (errno == ECONNREFUSED) { LOG(WARNING) << "connection refused by pserver, try again!"; if (retry_second++ >= 7) { LOG(FATAL) << "connection refused by pserver, maybe pserver failed!"; } std::this_thread::sleep_for(std::chrono::seconds(1)); } else { - PCHECK(error >= 0) << "ERROR connecting to " << serverAddr; + PCHECK(errno != 0) << "ERROR connecting to " << serverAddr << ":" + << serverPort << "errorno: " << errno; } - } while (error == ECONNREFUSED); + } while (errno == ECONNREFUSED); channel_.reset(new SocketChannel(sockfd, serverAddr)); tcpRdma_ = F_TCP; -- GitLab From 4c01cfa193832a51cd6cadcc986cf45cfd9cd7a4 Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Wed, 7 Jun 2017 14:20:22 -0700 Subject: [PATCH 0585/3256] fix variable name --- paddle/pserver/LightNetwork.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/paddle/pserver/LightNetwork.cpp b/paddle/pserver/LightNetwork.cpp index 61bd6d358de..922f25734de 100644 --- a/paddle/pserver/LightNetwork.cpp +++ b/paddle/pserver/LightNetwork.cpp @@ -383,7 +383,7 @@ void SocketClient::TcpClient(const std::string &serverAddr, int serverPort) { setOption(sockfd); /// Now connect to the server - int retry_second = 0; + int retry_count = 0; do { if (connect(sockfd, (sockaddr *)&serv_addr, sizeof(serv_addr)) == 0) { break; @@ -391,7 +391,7 @@ void SocketClient::TcpClient(const std::string &serverAddr, int serverPort) { if (errno == ECONNREFUSED) { LOG(WARNING) << "connection refused by pserver, try again!"; - if (retry_second++ >= 7) { + if (retry_count++ >= 7) { LOG(FATAL) << "connection refused by pserver, maybe pserver failed!"; } std::this_thread::sleep_for(std::chrono::seconds(1)); -- GitLab From 5f5ab28b67aae926128d5f0f0726f5b7f3de1a51 Mon Sep 17 00:00:00 2001 From: zhanghaichao Date: Wed, 7 Jun 2017 17:49:37 -0700 Subject: [PATCH 0586/3256] bug fix in order to print sequence contents (ids) correctly --- paddle/parameter/Argument.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paddle/parameter/Argument.cpp b/paddle/parameter/Argument.cpp index 6d9365af2d1..5beced3bb5a 100644 --- a/paddle/parameter/Argument.cpp +++ b/paddle/parameter/Argument.cpp @@ -632,7 +632,7 @@ void Argument::printValueString(std::ostream& stream, const std::string& prefix) const { std::unordered_map out; getValueString(&out); - for (auto field : {"value", "id", "sequence pos", "sub-sequence pos"}) { + for (auto field : {"value", "ids", "sequence pos", "sub-sequence pos"}) { auto it = out.find(field); if (it != out.end()) { stream << prefix << field << ":\n" << it->second; -- GitLab From 2f4e968b81a0f266c3adb31bf4e3fdd63ec4576c Mon Sep 17 00:00:00 2001 From: caoying03 Date: Thu, 8 Jun 2017 10:04:45 +0800 Subject: [PATCH 0587/3256] follow comments. --- python/paddle/v2/topology.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/paddle/v2/topology.py b/python/paddle/v2/topology.py index 5109dc58fe5..a20e878d081 100644 --- a/python/paddle/v2/topology.py +++ b/python/paddle/v2/topology.py @@ -92,7 +92,7 @@ class Topology(object): data_layers = self.data_layers() return [(nm, data_layers[nm].data_type) - for nm in self.proto().input_layer_names if nm in data_layers] + for nm in self.proto().input_layer_names] def get_layer_proto(self, name): for layer in self.__model_config__.layers: -- GitLab From c7483bf956f8a0eeb4edeeb72398415505939114 Mon Sep 17 00:00:00 2001 From: caoying03 Date: Wed, 7 Jun 2017 16:30:37 +0800 Subject: [PATCH 0588/3256] add configuration helper for prelu layer. --- doc/api/v2/config/layer.rst | 19 +- python/paddle/trainer/config_parser.py | 6 +- .../paddle/trainer_config_helpers/layers.py | 203 ++++++++++++------ .../tests/configs/file_list.sh | 3 +- .../protostr/test_prelu_layer.protostr | 36 ++++ .../tests/configs/test_prelu_layer.py | 6 + 6 files changed, 197 insertions(+), 76 deletions(-) create mode 100644 python/paddle/trainer_config_helpers/tests/configs/protostr/test_prelu_layer.protostr create mode 100644 python/paddle/trainer_config_helpers/tests/configs/test_prelu_layer.py diff --git a/doc/api/v2/config/layer.rst b/doc/api/v2/config/layer.rst index 1efa74ecda4..46f81bf9a2d 100644 --- a/doc/api/v2/config/layer.rst +++ b/doc/api/v2/config/layer.rst @@ -130,7 +130,7 @@ recurrent_group --------------- .. autoclass:: paddle.v2.layer.recurrent_group :noindex: - + lstm_step --------- .. autoclass:: paddle.v2.layer.lstm_step @@ -145,12 +145,12 @@ beam_search ------------ .. autoclass:: paddle.v2.layer.beam_search :noindex: - + get_output ---------- .. autoclass:: paddle.v2.layer.get_output :noindex: - + Mixed Layer =========== @@ -203,7 +203,7 @@ trans_full_matrix_projection ---------------------------- .. autoclass:: paddle.v2.layer.trans_full_matrix_projection :noindex: - + Aggregate Layers ================ @@ -434,10 +434,19 @@ smooth_l1_cost .. autoclass:: paddle.v2.layer.smooth_l1_cost :noindex: -Check Layer +Check Layer ============ eos --- .. autoclass:: paddle.v2.layer.eos :noindex: + +Activation with learnable parameter +=================== + +prelu +-------- +.. autoclass:: paddle.v2.layer.prelu + :noindex: + diff --git a/python/paddle/trainer/config_parser.py b/python/paddle/trainer/config_parser.py index 5d540664a7f..3712135b697 100644 --- a/python/paddle/trainer/config_parser.py +++ b/python/paddle/trainer/config_parser.py @@ -73,7 +73,6 @@ To use this from paddle_trainer, paddle_trainer should be called with --config_args=extension_module_name=[MODULE_NAME] ''' - import copy import logging import os @@ -1731,9 +1730,10 @@ class ParameterReluLayer(LayerBase): def __init__(self, name, inputs, partial_sum=1, **args): super(ParameterReluLayer, self).__init__( name, self.layer_type, 0, inputs=inputs, **args) - config_assert(len(self.inputs) == 1) - config_assert(self.input_layer.size % partial_sum == 0) input_layer = self.get_input_layer(0) + config_assert(len(self.inputs) == 1, "prelu layer has only one input.") + config_assert(input_layer.size % partial_sum == 0, + "a wrong setting for partial_sum") self.set_layer_size(input_layer.size) self.create_input_parameter(0, input_layer.size / partial_sum) diff --git a/python/paddle/trainer_config_helpers/layers.py b/python/paddle/trainer_config_helpers/layers.py index 5667e5ff2bc..8044c7aa92a 100755 --- a/python/paddle/trainer_config_helpers/layers.py +++ b/python/paddle/trainer_config_helpers/layers.py @@ -31,31 +31,31 @@ except ImportError: import copy __all__ = [ - "full_matrix_projection", - "AggregateLevel", - "ExpandLevel", - "identity_projection", - "dotmul_projection", - "dotmul_operator", - "repeat_layer", - "seq_reshape_layer", - "table_projection", - "mixed_layer", - "data_layer", - "embedding_layer", - "fc_layer", - "grumemory", - "pooling_layer", - "lstmemory", - "last_seq", - "first_seq", - "cos_sim", - "hsigmoid", - "conv_projection", - "mse_cost", - "regression_cost", + 'full_matrix_projection', + 'AggregateLevel', + 'ExpandLevel', + 'identity_projection', + 'dotmul_projection', + 'dotmul_operator', + 'repeat_layer', + 'seq_reshape_layer', + 'table_projection', + 'mixed_layer', + 'data_layer', + 'embedding_layer', + 'fc_layer', + 'grumemory', + 'pooling_layer', + 'lstmemory', + 'last_seq', + 'first_seq', + 'cos_sim', + 'hsigmoid', + 'conv_projection', + 'mse_cost', + 'regression_cost', 'classification_cost', - "LayerOutput", + 'LayerOutput', 'img_conv_layer', 'img_pool_layer', 'batch_norm_layer', @@ -121,6 +121,7 @@ __all__ = [ 'smooth_l1_cost', 'layer_support', 'multiplex_layer', + 'prelu_layer', ] @@ -129,26 +130,26 @@ class LayerType(object): Layer type enumerations. """ - DATA = "data" - MIXED_LAYER = "mixed" - LSTMEMORY = "lstmemory" - GRUMEMORY = "gated_recurrent" - SEQUENCE_LAST_INSTANCE = "seqlastins" - SEQUENCE_FIRST_INSTANCE = "seqfirstins" - SEQUENCE_RESHAPE = "seqreshape" - POOLING_MAX = "max" + DATA = 'data' + MIXED_LAYER = 'mixed' + LSTMEMORY = 'lstmemory' + GRUMEMORY = 'gated_recurrent' + SEQUENCE_LAST_INSTANCE = 'seqlastins' + SEQUENCE_FIRST_INSTANCE = 'seqfirstins' + SEQUENCE_RESHAPE = 'seqreshape' + POOLING_MAX = 'max' POOLING_AVG = 'average' - FC_LAYER = "fc" + FC_LAYER = 'fc' COST = 'cost' COSINE_SIM_VEC = 'cos_vm' COSINE_SIM = 'cos' HSIGMOID = 'hsigmoid' - CONV_LAYER = "conv" - CONVTRANS_LAYER = "convt" - EXCONV_LAYER = "exconv" - EXCONVTRANS_LAYER = "exconvt" - CUDNNCONV_LAYER = "cudnn_conv" - POOL_LAYER = "pool" + CONV_LAYER = 'conv' + CONVTRANS_LAYER = 'convt' + EXCONV_LAYER = 'exconv' + EXCONVTRANS_LAYER = 'exconvt' + CUDNNCONV_LAYER = 'cudnn_conv' + POOL_LAYER = 'pool' BATCH_NORM_LAYER = 'batch_norm' NORM_LAYER = 'norm' SUM_TO_ONE_NORM_LAYER = 'sum_to_one_norm' @@ -177,36 +178,38 @@ class LayerType(object): EOSID_LAYER = 'eos_id' RECURRENT_LAYER = 'recurrent' - CONV_SHIFT_LAYER = "conv_shift" - TENSOR_LAYER = "tensor" - SEL_FC_LAYER = "selective_fc" - SAMPLING_ID_LAYER = "sampling_id" - SLOPE_INTERCEPT_LAYER = "slope_intercept" - LINEAR_COMBINATION_LAYER = "convex_comb" - BLOCK_EXPAND = "blockexpand" - MAXOUT = "maxout" - SPP_LAYER = "spp" - PAD_LAYER = "pad" - MULTIPLEX_LAYER = "multiplex" - - PRINT_LAYER = "print" - PRIORBOX_LAYER = "priorbox" - - CTC_LAYER = "ctc" - WARP_CTC_LAYER = "warp_ctc" - CRF_LAYER = "crf" - CRF_DECODING_LAYER = "crf_decoding" + CONV_SHIFT_LAYER = 'conv_shift' + TENSOR_LAYER = 'tensor' + SEL_FC_LAYER = 'selective_fc' + SAMPLING_ID_LAYER = 'sampling_id' + SLOPE_INTERCEPT_LAYER = 'slope_intercept' + LINEAR_COMBINATION_LAYER = 'convex_comb' + BLOCK_EXPAND = 'blockexpand' + MAXOUT = 'maxout' + SPP_LAYER = 'spp' + PAD_LAYER = 'pad' + MULTIPLEX_LAYER = 'multiplex' + + PRINT_LAYER = 'print' + PRIORBOX_LAYER = 'priorbox' + + CTC_LAYER = 'ctc' + WARP_CTC_LAYER = 'warp_ctc' + CRF_LAYER = 'crf' + CRF_DECODING_LAYER = 'crf_decoding' NCE_LAYER = 'nce' - RANK_COST = "rank-cost" - LAMBDA_COST = "lambda_cost" - HUBER = "huber" - CROSS_ENTROPY = "multi-class-cross-entropy" - CROSS_ENTROPY_WITH_SELFNORM = "multi_class_cross_entropy_with_selfnorm" - SOFT_BIN_CLASS_CROSS_ENTROPY = "soft_binary_class_cross_entropy" - MULTI_BIN_LABEL_CROSS_ENTROPY = "multi_binary_label_cross_entropy" - SUM_COST = "sum_cost" - SMOOTH_L1 = "smooth_l1" + RANK_COST = 'rank-cost' + LAMBDA_COST = 'lambda_cost' + HUBER = 'huber' + CROSS_ENTROPY = 'multi-class-cross-entropy' + CROSS_ENTROPY_WITH_SELFNORM = 'multi_class_cross_entropy_with_selfnorm' + SOFT_BIN_CLASS_CROSS_ENTROPY = 'soft_binary_class_cross_entropy' + MULTI_BIN_LABEL_CROSS_ENTROPY = 'multi_binary_label_cross_entropy' + SUM_COST = 'sum_cost' + SMOOTH_L1 = 'smooth_l1' + + PRELU = 'prelu' @staticmethod def is_layer_type(type_name): @@ -5551,3 +5554,69 @@ def multiplex_layer(input, name=None, layer_attr=None): layer_type=LayerType.MULTIPLEX_LAYER, parents=input, size=l.config.size) + + +@wrap_name_default() +@layer_support() +@wrap_name_default() +@wrap_param_attr_default() +def prelu_layer(input, + name=None, + partial_sum=1, + param_attr=None, + layer_attr=None): + """ + The Parameter Relu activation that actives outputs with a learnable weight. + + Reference: + Delving Deep into Rectifiers: Surpassing Human-Level Performance on + ImageNet Classification http://arxiv.org/pdf/1502.01852v1.pdf + + .. math:: + z_i &\\quad if \\quad z_i > 0 \\\\ + a_i * z_i &\\quad \\mathrm{otherwise} + + :param name: Name of this layer. + :type name: basestring + :param input: The input layer. + :type input: LayerOutput + :param partial_sum: this parameter makes a group of inputs share a same weight. + 1. partial_sum = 1 indicates the element-wise activation: + each element has a weight + 2. partial_sum = number of elements in one channel indicates the channel-wise + activation, elements in a channel share a same weight + 3. partial_sum = number of outputs indicates all elements share a same weight + :type int + :param param_attr: The parameter attribute. See ParameterAttribute for details. + :type param_attr: ParameterAttribute|None + :param layer_attr: Extra layer configurations. Default is None. + :type layer_attr: ExtraLayerAttribute|None + :return: LayerOutput object. + :rtype: LayerOutput + """ + + if isinstance(input, collections.Sequence): + assert len(input) == 1, 'prelu_layer only accepts one input' + else: + input = [input] + assert isinstance(input[0], LayerOutput) + + if isinstance(param_attr, collections.Sequence): + assert len(param_attr) == 1, ( + 'because prelu_layer only accepts one input ' + 'it requires only one parameter setting.') + else: + param_attr = [param_attr] + assert isinstance(param_attr[0], ParameterAttribute) + + l = Layer( + name=name, + type='prelu', + inputs=Input(input[0].name, **param_attr[0].attr), + partial_sum=partial_sum, + **ExtraLayerAttribute.to_kwargs(layer_attr)) + return LayerOutput( + name=name, + layer_type=LayerType.PRELU, + parents=input, + size=l.config.size) diff --git a/python/paddle/trainer_config_helpers/tests/configs/file_list.sh b/python/paddle/trainer_config_helpers/tests/configs/file_list.sh index 981ccbf2483..bef14bffaf6 100755 --- a/python/paddle/trainer_config_helpers/tests/configs/file_list.sh +++ b/python/paddle/trainer_config_helpers/tests/configs/file_list.sh @@ -5,6 +5,7 @@ last_first_seq test_expand_layer test_ntm_layers test_hsigmoid img_layers img_trans_layers util_layers simple_rnn_layers unused_layers test_cost_layers test_rnn_group shared_fc shared_lstm shared_gru test_cost_layers_with_weight test_spp_layer test_bilinear_interp test_maxout test_bi_grumemory math_ops -test_seq_concat_reshape test_pad test_smooth_l1 test_multiplex_layer) +test_seq_concat_reshape test_pad test_smooth_l1 test_multiplex_layer +test_prelu_layer) export whole_configs=(test_split_datasource) diff --git a/python/paddle/trainer_config_helpers/tests/configs/protostr/test_prelu_layer.protostr b/python/paddle/trainer_config_helpers/tests/configs/protostr/test_prelu_layer.protostr new file mode 100644 index 00000000000..64d227565f2 --- /dev/null +++ b/python/paddle/trainer_config_helpers/tests/configs/protostr/test_prelu_layer.protostr @@ -0,0 +1,36 @@ +type: "nn" +layers { + name: "input" + type: "data" + size: 300 + active_type: "" +} +layers { + name: "__prelu_layer_0__" + type: "prelu" + size: 300 + active_type: "" + inputs { + input_layer_name: "input" + input_parameter_name: "___prelu_layer_0__.w0" + } +} +parameters { + name: "___prelu_layer_0__.w0" + size: 300 + initial_mean: 0.0 + initial_std: 0.057735026919 + initial_strategy: 0 + initial_smart: true +} +input_layer_names: "input" +output_layer_names: "__prelu_layer_0__" +sub_models { + name: "root" + layer_names: "input" + layer_names: "__prelu_layer_0__" + input_layer_names: "input" + output_layer_names: "__prelu_layer_0__" + is_recurrent_layer_group: false +} + diff --git a/python/paddle/trainer_config_helpers/tests/configs/test_prelu_layer.py b/python/paddle/trainer_config_helpers/tests/configs/test_prelu_layer.py new file mode 100644 index 00000000000..2e3057f323d --- /dev/null +++ b/python/paddle/trainer_config_helpers/tests/configs/test_prelu_layer.py @@ -0,0 +1,6 @@ +from paddle.trainer_config_helpers import * + +data = data_layer(name='input', size=300) +prelu = prelu_layer(input=data) + +outputs(prelu) -- GitLab From 50764480c1a98cfe43d64874a0324c1b309146f7 Mon Sep 17 00:00:00 2001 From: caoying03 Date: Thu, 8 Jun 2017 10:18:14 +0800 Subject: [PATCH 0589/3256] follow comments. --- doc/api/v2/config/layer.rst | 2 +- python/paddle/trainer_config_helpers/layers.py | 17 +++-------------- 2 files changed, 4 insertions(+), 15 deletions(-) diff --git a/doc/api/v2/config/layer.rst b/doc/api/v2/config/layer.rst index 46f81bf9a2d..65d4c472c35 100644 --- a/doc/api/v2/config/layer.rst +++ b/doc/api/v2/config/layer.rst @@ -443,7 +443,7 @@ eos :noindex: Activation with learnable parameter -=================== +=================================== prelu -------- diff --git a/python/paddle/trainer_config_helpers/layers.py b/python/paddle/trainer_config_helpers/layers.py index 8044c7aa92a..bb58135084d 100755 --- a/python/paddle/trainer_config_helpers/layers.py +++ b/python/paddle/trainer_config_helpers/layers.py @@ -5595,24 +5595,13 @@ def prelu_layer(input, :rtype: LayerOutput """ - if isinstance(input, collections.Sequence): - assert len(input) == 1, 'prelu_layer only accepts one input' - else: - input = [input] - assert isinstance(input[0], LayerOutput) - - if isinstance(param_attr, collections.Sequence): - assert len(param_attr) == 1, ( - 'because prelu_layer only accepts one input ' - 'it requires only one parameter setting.') - else: - param_attr = [param_attr] - assert isinstance(param_attr[0], ParameterAttribute) + assert isinstance(input, LayerOutput), 'prelu_layer only accepts one input' + assert isinstance(param_attr, ParameterAttribute) l = Layer( name=name, type='prelu', - inputs=Input(input[0].name, **param_attr[0].attr), + inputs=Input(input.name, **param_attr.attr), partial_sum=partial_sum, **ExtraLayerAttribute.to_kwargs(layer_attr)) return LayerOutput( -- GitLab From e039410eb74d24dced7c9e9108e717f0c4a0b7e4 Mon Sep 17 00:00:00 2001 From: hedaoyuan Date: Thu, 8 Jun 2017 12:07:58 +0800 Subject: [PATCH 0590/3256] Remove the code of ExpandConvTransLayer. --- paddle/gserver/layers/ExpandConvBaseLayer.cpp | 193 ------------------ paddle/gserver/layers/ExpandConvBaseLayer.h | 28 --- .../gserver/layers/ExpandConvTransLayer.cpp | 90 -------- paddle/gserver/layers/ExpandConvTransLayer.h | 44 ---- paddle/gserver/tests/test_BatchNorm.cpp | 1 - paddle/gserver/tests/test_ConvTrans.cpp | 1 - paddle/gserver/tests/test_ConvUnify.cpp | 1 - 7 files changed, 358 deletions(-) delete mode 100644 paddle/gserver/layers/ExpandConvTransLayer.cpp delete mode 100644 paddle/gserver/layers/ExpandConvTransLayer.h diff --git a/paddle/gserver/layers/ExpandConvBaseLayer.cpp b/paddle/gserver/layers/ExpandConvBaseLayer.cpp index fdcf994cdb4..77736e78f93 100644 --- a/paddle/gserver/layers/ExpandConvBaseLayer.cpp +++ b/paddle/gserver/layers/ExpandConvBaseLayer.cpp @@ -22,26 +22,8 @@ bool ExpandConvBaseLayer::init(const LayerMap &layerMap, /* Initialize the basic convolutional parent class */ ConvBaseLayer::init(layerMap, parameterMap); - /* The class fields channels_ and numFilters_ are the same as in the config - * i.e., channels_ is the for the input and numFilters_ is for the output - * - * But in order for the variables in convTrans having the same semantic - * meaning as in conv, we need to swap channels_ and numFilters here for - * convTrans, and in other functions too. - * */ - - /* Initialize the projection */ for (auto &inputConfig : config_.inputs()) { const ConvConfig &conf = inputConfig.conv_conf(); - int numFilters = isDeconv_ ? conf.channels() : numFilters_; - subM_.push_back(numFilters / conf.groups()); - subN_.push_back(conf.output_x() * - (conf.has_output_y() ? conf.output_y() : conf.output_x())); - int channel = isDeconv_ ? numFilters_ : conf.channels(); - subK_.push_back( - channel * conf.filter_size() * - (conf.has_filter_size_y() ? conf.filter_size_y() : conf.filter_size()) / - conf.groups()); /* Consistent caffe mode for multiple input */ caffeMode_ = conf.caffe_mode(); } @@ -54,17 +36,9 @@ bool ExpandConvBaseLayer::init(const LayerMap &layerMap, size_t ExpandConvBaseLayer::getOutputSize() { CHECK_NE(inputLayers_.size(), 0UL); size_t layerSize = ConvBaseLayer::calOutputSize(); - subN_.clear(); - for (size_t i = 0; i < inputLayers_.size(); i++) { - subN_.push_back(outputH_[i] * outputW_[i]); - } return layerSize; } -void ExpandConvBaseLayer::resetExpandInput(size_t height, size_t width) { - Matrix::resizeOrCreate(expandInput_, height, width, false, useGpu_); -} - void ExpandConvBaseLayer::addSharedBias() { size_t mapW = getOutputSize() / numFilters_; size_t mapH = getOutputValue()->getElementCnt() / mapW; @@ -101,173 +75,6 @@ void ExpandConvBaseLayer::addUnsharedBias() { outValue->addBias(*bias, 1.0f); } -void ExpandConvBaseLayer::expandOneFrame(MatrixPtr image, - size_t startIdx, - int inIdx) { - int channel = isDeconv_ ? numFilters_ : channels_[inIdx]; - - resetExpandInput(subK_[inIdx] * groups_[inIdx], subN_[inIdx]); - - CHECK_EQ(image->getWidth(), - static_cast(imgSizeH_[inIdx] * imgSizeW_[inIdx] * channel)); - - real *imgData = image->getData() + startIdx * image->getWidth(); - MatrixPtr imageTmp = - Matrix::create(imgData, - 1, - imgSizeH_[inIdx] * imgSizeW_[inIdx] * channel, - false, - useGpu_); - expandInput_->convExpand(*imageTmp, - imgSizeH_[inIdx], - imgSizeW_[inIdx], - channel, - filterSizeY_[inIdx], - filterSize_[inIdx], - strideY_[inIdx], - stride_[inIdx], - paddingY_[inIdx], - padding_[inIdx], - outputH_[inIdx], - outputW_[inIdx]); - imageTmp->clear(); -} - -void ExpandConvBaseLayer::expandFwdOnce(MatrixPtr image, - MatrixPtr out, - int inIdx, - int startIdx) { - int subM = subM_[inIdx]; - int subN = subN_[inIdx]; - int subK = subK_[inIdx]; - - expandOneFrame(image, startIdx, inIdx); - - int numFilters = isDeconv_ ? channels_[inIdx] : numFilters_; - - real *outData = out->getData() + startIdx * subN * numFilters; - - real *wgtData = weights_[inIdx]->getW()->getData(); - real *expInData = expandInput_->getData(); - for (int g = 0; g < groups_[inIdx]; ++g) { - MatrixPtr A = - Matrix::create(wgtData, subM, subK, false, useGpu_); // mark transpose - MatrixPtr B = Matrix::create(expInData, subK, subN, false, useGpu_); - MatrixPtr C = Matrix::create(outData, subM, subN, false, useGpu_); - C->mul(*A, *B, 1, 1); - - A->clear(); - B->clear(); - C->clear(); - wgtData += subK * subM; - expInData += subK * subN; - outData += subM * subN; - } -} - -void ExpandConvBaseLayer::bpropActs(MatrixPtr out, - MatrixPtr image, - int inpIdx) { - int channel = isDeconv_ ? numFilters_ : channels_[inpIdx]; - - int subM = subM_[inpIdx]; - int subN = subN_[inpIdx]; - int subK = subK_[inpIdx]; - size_t batchSize = image->getHeight(); - - /* reset the expand-grad memory */ - resetExpandInput(subK * groups_[inpIdx], subN); - - real *localGradData = out->getData(); - real *tgtGradData = image->getData(); - for (size_t n = 0; n < batchSize; n++) { - real *wgtData = weights_[inpIdx]->getW()->getData(); - real *expandInData = expandInput_->getData(); - - for (int g = 0; g < groups_[inpIdx]; g++) { - // create temporary matrix - MatrixPtr C = Matrix::create(expandInData, subK, subN, false, useGpu_); - MatrixPtr B = Matrix::create(localGradData, subM, subN, false, useGpu_); - MatrixPtr A = Matrix::create(wgtData, subM, subK, true, useGpu_); - C->mul(*A, *B); // mul - - // clear the temporary matrix - A->clear(); - B->clear(); - C->clear(); - - expandInData += subK * subN; - localGradData += subM * subN; - wgtData += subK * subM; - } - - // shrink one frame outGrad - MatrixPtr oneGradTmp = Matrix::create( - expandInput_->getData(), subK * groups_[inpIdx], subN, false, useGpu_); - MatrixPtr vTmp = - Matrix::create(tgtGradData, - 1, - imgSizeH_[inpIdx] * imgSizeW_[inpIdx] * channel, - false, - useGpu_); - vTmp->convShrink(*oneGradTmp, - imgSizeH_[inpIdx], - imgSizeW_[inpIdx], - channel, - filterSizeY_[inpIdx], - filterSize_[inpIdx], - strideY_[inpIdx], - stride_[inpIdx], - paddingY_[inpIdx], - padding_[inpIdx], - outputH_[inpIdx], - outputW_[inpIdx], - 1.0f, - 1.0f); - vTmp->clear(); - oneGradTmp->clear(); - - // move the data-pointer - tgtGradData += imgSizeH_[inpIdx] * imgSizeW_[inpIdx] * channel; - } -} - -void ExpandConvBaseLayer::bpropWeights(MatrixPtr image, - MatrixPtr out, - int inpIdx) { - MatrixPtr weightGrad = weights_[inpIdx]->getWGrad(); - - int subM = subM_[inpIdx]; - int subN = subN_[inpIdx]; - int subK = subK_[inpIdx]; - size_t batchSize = image->getHeight(); - resetExpandInput(subK * groups_[inpIdx], subN); - - real *gradData = out->getData(); - - for (size_t n = 0; n < batchSize; n++) { // frame by frame - // expand - expandOneFrame(image, n, inpIdx); - real *wGradData = weightGrad->getData(); - real *expandInData = expandInput_->getData(); - - // expand-mul one-group by one - for (int g = 0; g < groups_[inpIdx]; g++) { - MatrixPtr A = Matrix::create(expandInData, subK, subN, true, useGpu_); - MatrixPtr B = Matrix::create(gradData, subM, subN, false, useGpu_); - MatrixPtr C = Matrix::create(wGradData, subM, subK, false, useGpu_); - C->mul(*B, *A, 1, 1); - - A->clear(); - B->clear(); - C->clear(); - gradData += subM * subN; - wGradData += subK * subM; - expandInData += subK * subN; - } - } -} - void ExpandConvBaseLayer::bpropSharedBias(MatrixPtr biases, MatrixPtr v) { size_t mapW = getOutputSize() / numFilters_; size_t mapH = v->getElementCnt() / mapW; diff --git a/paddle/gserver/layers/ExpandConvBaseLayer.h b/paddle/gserver/layers/ExpandConvBaseLayer.h index aabcdfc392d..01c699d2344 100644 --- a/paddle/gserver/layers/ExpandConvBaseLayer.h +++ b/paddle/gserver/layers/ExpandConvBaseLayer.h @@ -26,19 +26,6 @@ namespace paddle { */ class ExpandConvBaseLayer : public ConvBaseLayer { protected: - /// For expand convolution. - /// subM_ = numFilters_ / groups_. - IntV subM_; - /// subN_ = outputH_ * outputW_. - IntV subN_; - /// subK_ = channels_ * filterPixels_ * groups_. - IntV subK_; - - /*The expandInput_ and transOutValue_ are used for CPU expand conv calc - * Expand one sample at a time. shape: - * (numChannels * filterPixels_, outputSizeH * outputSizeW) - * */ - MatrixPtr expandInput_; /// The transpose of output, which is an auxiliary matrix. MatrixPtr transOutValue_; @@ -52,10 +39,6 @@ public: const ParameterMap& parameterMap) override; size_t getOutputSize(); - /** - * Create or resize expandInput_. - */ - void resetExpandInput(size_t height, size_t width); /** * Add shared bias. @@ -66,20 +49,9 @@ public: * Add unshared bias. */ void addUnsharedBias(); - /** - * Expand one input sample. - */ - void expandOneFrame(MatrixPtr image, size_t startIdx, int inIdx); - - /** - * Expand one input sample and perform matrix multiplication. - */ - void expandFwdOnce(MatrixPtr image, MatrixPtr out, int inIdx, int startIdx); void bpropSharedBias(MatrixPtr biases, MatrixPtr v); void bpropBiases(MatrixPtr v); - void bpropWeights(MatrixPtr image, MatrixPtr out, int inpIdx); - void bpropActs(MatrixPtr image, MatrixPtr out, int inpIdx); }; } // namespace paddle diff --git a/paddle/gserver/layers/ExpandConvTransLayer.cpp b/paddle/gserver/layers/ExpandConvTransLayer.cpp deleted file mode 100644 index b80a01e3287..00000000000 --- a/paddle/gserver/layers/ExpandConvTransLayer.cpp +++ /dev/null @@ -1,90 +0,0 @@ -/* 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 "ExpandConvTransLayer.h" -#include "paddle/utils/Logging.h" -#include "paddle/utils/Stat.h" - -/* The implementation of the convTransLayer is basically a swap of forward and - * backward of the original convLayer. - * The variable naming follows the convention of the convLayer. - * */ - -namespace paddle { - -// REGISTER_LAYER(exconvt, ExpandConvTransLayer); - -bool ExpandConvTransLayer::init(const LayerMap &layerMap, - const ParameterMap ¶meterMap) { - /* Initialize the basic convolutional parent class */ - ExpandConvBaseLayer::init(layerMap, parameterMap); - - return true; -} - -void ExpandConvTransLayer::forward(PassType passType) { - Layer::forward(passType); - - /* malloc memory for the output_ if necessary */ - int batchSize = inputLayers_[0]->getOutputValue()->getHeight(); - resetOutput(batchSize, getOutputSize()); - - MatrixPtr output = nullptr; - for (size_t i = 0; i < inputLayers_.size(); ++i) { - LayerPtr prevLayer = getPrev(i); - output = prevLayer->getOutputValue(); - REGISTER_TIMER_INFO("shrinkFwd", getName().c_str()); - bpropActs(output, getOutputValue(), i); - } - - /* add the bias-vector */ - if (biases_.get()) { - if (sharedBiases_) { - addSharedBias(); - } else { - addUnsharedBias(); - } - } - - /* activation */ - forwardActivation(); -} - -void ExpandConvTransLayer::backward(const UpdateCallback &callback) { - backwardActivation(); - - MatrixPtr imageGrad = getOutputGrad(); - if (biases_ && biases_->getWGrad()) { - bpropBiases(imageGrad); - /* Increasing the number of gradient */ - biases_->getParameterPtr()->incUpdate(callback); - } - - for (size_t i = 0; i < inputLayers_.size(); ++i) { - /* First, calculate the input layers error */ - for (size_t off = 0; off < imageGrad->getHeight(); off++) { - if (getPrev(i)->getOutputGrad()) { - expandFwdOnce(imageGrad, getPrev(i)->getOutputGrad(), i, off); - } - } - if (weights_[i]->getWGrad()) { - /* Then, calculate the W-gradient for the current layer */ - bpropWeights(imageGrad, getPrev(i)->getOutputValue(), i); - /* Increasing the number of gradient */ - weights_[i]->getParameterPtr()->incUpdate(callback); - } - } -} - -} // namespace paddle diff --git a/paddle/gserver/layers/ExpandConvTransLayer.h b/paddle/gserver/layers/ExpandConvTransLayer.h deleted file mode 100644 index 00b8f241889..00000000000 --- a/paddle/gserver/layers/ExpandConvTransLayer.h +++ /dev/null @@ -1,44 +0,0 @@ -/* Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserve. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#pragma once - -#include -#include "ExpandConvBaseLayer.h" -#include "paddle/math/Matrix.h" - -namespace paddle { - -/** - * @brief A subclass of convolution layer. - * This layer expands input and use matrix multiplication to - * calculate convolution transpose (deconv) operation. - * - * The config file api is img_conv_layer with flag trans=True. - */ -class ExpandConvTransLayer : public ExpandConvBaseLayer { -public: - explicit ExpandConvTransLayer(const LayerConfig& config) - : ExpandConvBaseLayer(config) {} - - ~ExpandConvTransLayer() {} - - bool init(const LayerMap& layerMap, - const ParameterMap& parameterMap) override; - - void forward(PassType passType) override; - void backward(const UpdateCallback& callback) override; -}; - -} // namespace paddle diff --git a/paddle/gserver/tests/test_BatchNorm.cpp b/paddle/gserver/tests/test_BatchNorm.cpp index d07299bfe3c..83fcfed46cd 100644 --- a/paddle/gserver/tests/test_BatchNorm.cpp +++ b/paddle/gserver/tests/test_BatchNorm.cpp @@ -17,7 +17,6 @@ limitations under the License. */ #include #include "ModelConfig.pb.h" #include "paddle/gserver/layers/DataLayer.h" -#include "paddle/gserver/layers/ExpandConvTransLayer.h" #include "paddle/trainer/Trainer.h" #include "paddle/utils/GlobalConstants.h" diff --git a/paddle/gserver/tests/test_ConvTrans.cpp b/paddle/gserver/tests/test_ConvTrans.cpp index 40bb1e2d73c..6035a866b4e 100644 --- a/paddle/gserver/tests/test_ConvTrans.cpp +++ b/paddle/gserver/tests/test_ConvTrans.cpp @@ -17,7 +17,6 @@ limitations under the License. */ #include #include "ModelConfig.pb.h" #include "paddle/gserver/layers/DataLayer.h" -#include "paddle/gserver/layers/ExpandConvTransLayer.h" #include "paddle/math/MathUtils.h" #include "paddle/trainer/Trainer.h" #include "paddle/utils/GlobalConstants.h" diff --git a/paddle/gserver/tests/test_ConvUnify.cpp b/paddle/gserver/tests/test_ConvUnify.cpp index 54b72375b74..e7325e0cc3b 100644 --- a/paddle/gserver/tests/test_ConvUnify.cpp +++ b/paddle/gserver/tests/test_ConvUnify.cpp @@ -17,7 +17,6 @@ limitations under the License. */ #include #include "ModelConfig.pb.h" #include "paddle/gserver/layers/DataLayer.h" -#include "paddle/gserver/layers/ExpandConvTransLayer.h" #include "paddle/math/MathUtils.h" #include "paddle/trainer/Trainer.h" #include "paddle/utils/GlobalConstants.h" -- GitLab From 996614814d07cb438cbfb6da95bff6f7d3cd3634 Mon Sep 17 00:00:00 2001 From: caoying03 Date: Thu, 8 Jun 2017 12:13:04 +0800 Subject: [PATCH 0591/3256] follow comments and refine doc. --- .../paddle/trainer_config_helpers/layers.py | 46 +++++++++++++------ 1 file changed, 33 insertions(+), 13 deletions(-) diff --git a/python/paddle/trainer_config_helpers/layers.py b/python/paddle/trainer_config_helpers/layers.py index bb58135084d..d2b3103e36b 100755 --- a/python/paddle/trainer_config_helpers/layers.py +++ b/python/paddle/trainer_config_helpers/layers.py @@ -4728,7 +4728,7 @@ def ctc_layer(input, fc_layer with softmax activation, should be num_classes + 1. The size of ctc_layer should also be num_classes + 1. - The simple usage: + The example usage is: .. code-block:: python @@ -4815,7 +4815,7 @@ def warp_ctc_layer(input, - As a native 'softmax' activation is interated to the warp-ctc library, 'linear' activation is expected instead in the 'input' layer. - The simple usage: + The example usage is: .. code-block:: python @@ -4876,7 +4876,7 @@ def crf_layer(input, A layer for calculating the cost of sequential conditional random field model. - The simple usage: + The example usage is: .. code-block:: python @@ -4950,7 +4950,7 @@ def crf_decoding_layer(input, this layer will also calculate error. output.value[i] is 1 for incorrect decoding or 0 for correct decoding. - The simple usage: + The example usage is: .. code-block:: python @@ -5143,7 +5143,7 @@ def rank_cost(left, - :math:`o_i` and :math:`o_j`: the left output and right output. Their dimension is one. - The simple usage: + The example usage is: .. code-block:: python @@ -5200,7 +5200,7 @@ def lambda_cost(input, """ lambdaCost for lambdaRank LTR approach. - The simple usage: + The example usage is: .. code-block:: python @@ -5258,6 +5258,8 @@ def cross_entropy(input, """ A loss layer for multi class entropy. + The example usage is: + .. code-block:: python cost = cross_entropy(input=input_layer, @@ -5304,6 +5306,8 @@ def cross_entropy_with_selfnorm(input, A loss layer for multi class entropy with selfnorm. Input should be a vector of positive numbers, without normalization. + The example usage is: + .. code-block:: python cost = cross_entropy_with_selfnorm(input=input_layer, @@ -5345,6 +5349,8 @@ def sum_cost(input, name=None, layer_attr=None): """ A loss layer which calculate the sum of the input as loss + The example usage is: + .. code-block:: python cost = sum_cost(input=input_layer) @@ -5374,6 +5380,8 @@ def huber_cost(input, label, name=None, coeff=1.0, layer_attr=None): """ A loss layer for huber loss. + The example usage is: + .. code-block:: python cost = huber_cost(input=input_layer, @@ -5414,6 +5422,8 @@ def multi_binary_label_cross_entropy(input, """ A loss layer for multi binary label cross entropy. + The example usage is: + .. code-block:: python cost = multi_binary_label_cross_entropy(input=input_layer, @@ -5473,6 +5483,8 @@ def smooth_l1_cost(input, label, name=None, coeff=1.0, layer_attr=None): More details can be found by referring to `Fast R-CNN `_ + The example usage is: + .. code-block:: python cost = smooth_l1_cost(input=input_layer, @@ -5522,6 +5534,8 @@ def multiplex_layer(input, name=None, layer_attr=None): where, y is output. :math:`x_{k}` is the k-th input layer and :math:`k = x_{0}[i] + 1`. + The example usage is: + .. code-block:: python maxid = multiplex_layer(input=layers) @@ -5576,17 +5590,23 @@ def prelu_layer(input, z_i &\\quad if \\quad z_i > 0 \\\\ a_i * z_i &\\quad \\mathrm{otherwise} + The example usage is: + + .. code-block:: python + + prelu = prelu_layer(input=layers, partial_sum=1) + :param name: Name of this layer. :type name: basestring :param input: The input layer. :type input: LayerOutput :param partial_sum: this parameter makes a group of inputs share a same weight. - 1. partial_sum = 1 indicates the element-wise activation: - each element has a weight - 2. partial_sum = number of elements in one channel indicates the channel-wise - activation, elements in a channel share a same weight - 3. partial_sum = number of outputs indicates all elements share a same weight - :type int + + - partial_sum = 1, indicates the element-wise activation: each element has a weight. + - partial_sum = number of elements in one channel, indicates the channel-wise activation, elements in a channel share a same weight. + - partial_sum = number of outputs, indicates all elements share a same weight. + + :type partial_sum: int :param param_attr: The parameter attribute. See ParameterAttribute for details. :type param_attr: ParameterAttribute|None :param layer_attr: Extra layer configurations. Default is None. @@ -5600,7 +5620,7 @@ def prelu_layer(input, l = Layer( name=name, - type='prelu', + type=LayerType.PRELU, inputs=Input(input.name, **param_attr.attr), partial_sum=partial_sum, **ExtraLayerAttribute.to_kwargs(layer_attr)) -- GitLab From f18d83f3c492711271c0a44dcb6ef4ac62cc61c7 Mon Sep 17 00:00:00 2001 From: dangqingqing Date: Thu, 8 Jun 2017 16:11:40 +0800 Subject: [PATCH 0592/3256] follow comments --- paddle/function/RowConvOp.cpp | 11 +++++------ paddle/gserver/layers/RowConvLayer.cpp | 5 +++-- python/paddle/trainer_config_helpers/layers.py | 8 ++++++++ 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/paddle/function/RowConvOp.cpp b/paddle/function/RowConvOp.cpp index c3abb64971f..b6501e8f4db 100644 --- a/paddle/function/RowConvOp.cpp +++ b/paddle/function/RowConvOp.cpp @@ -136,6 +136,7 @@ public: // check CHECK_EQ(2UL, inputs.size()); CHECK_EQ(1UL, outputs.size()); + // TODO(qingqing): support ASSIGN_TO. CHECK_EQ(outputs[0].getArgType(), ADD_TO); CHECK(inputs[0].isSequenceArg() && outputs[0].isSequenceArg()) << "SequenceArg required here."; @@ -144,9 +145,7 @@ public: auto w = inputs[1]; CHECK(in.data() && out.data() && in.getSequenceId().data()); CHECK_EQ(in.shape().ndims(), 2UL); - CHECK_EQ(out.shape().ndims(), 2UL); - CHECK_EQ(in.shape()[1], out.shape()[1]); - CHECK_EQ(in.shape()[0], out.shape()[0]); + CHECK(in.shape() == out.shape()); CHECK_EQ(w.shape()[1], in.shape()[1]); auto outMat = out.matrix(); @@ -176,6 +175,7 @@ public: template class RowConvGradFunc : public FunctionBase { + // TODO(qingqing): split into RowConvDataFunc and RowConvWeightFunc public: void init(const FuncConfig& config) override {} @@ -196,9 +196,8 @@ public: auto wGrad = outputs[1]; CHECK_EQ(in.shape().ndims(), 2UL); - CHECK_EQ(outGrad.shape().ndims(), 2UL); - CHECK_EQ(in.shape()[1], outGrad.shape()[1]); - CHECK_EQ(in.shape()[0], outGrad.shape()[0]); + CHECK(in.shape() == inGrad.shape()); + CHECK(in.shape() == outGrad.shape()); CHECK_EQ(wGrad.shape()[1], in.shape()[1]); const auto outGMat = outGrad.matrix(); diff --git a/paddle/gserver/layers/RowConvLayer.cpp b/paddle/gserver/layers/RowConvLayer.cpp index 5302e0e1a8f..54d77999ad5 100644 --- a/paddle/gserver/layers/RowConvLayer.cpp +++ b/paddle/gserver/layers/RowConvLayer.cpp @@ -43,13 +43,14 @@ void RowConvLayer::forward(PassType passType) { resetOutput(height, width); const auto startPos = getInput(0).sequenceStartPositions->getVector(useGpu_); - wDims_ = TensorShape({contexLength_, width}); + MatrixPtr w = weight_->getW(); + wDims_ = TensorShape({w->getHeight(), w->getWidth()}); MatrixPtr outV = getOutputValue(); BufferArgs inputs; BufferArgs outputs; inputs.addArg(*getInputValue(0), *startPos); - inputs.addArg(*weight_->getW(), wDims_); + inputs.addArg(*w, wDims_); outputs.addArg(*getOutputValue(), *startPos, ADD_TO); { diff --git a/python/paddle/trainer_config_helpers/layers.py b/python/paddle/trainer_config_helpers/layers.py index 1fd62cda508..1c3d776a4f3 100755 --- a/python/paddle/trainer_config_helpers/layers.py +++ b/python/paddle/trainer_config_helpers/layers.py @@ -191,6 +191,14 @@ class LayerType(object): PAD_LAYER = "pad" MULTIPLEX_LAYER = "multiplex" ROW_CONV_LAYER = "row_conv" + + PRINT_LAYER = 'print' + PRIORBOX_LAYER = 'priorbox' + + CTC_LAYER = 'ctc' + WARP_CTC_LAYER = 'warp_ctc' + CRF_LAYER = 'crf' + CRF_DECODING_LAYER = 'crf_decoding' NCE_LAYER = 'nce' RANK_COST = 'rank-cost' -- GitLab From 6f1c91da992b9f7b230633c0ac56db184d4df5c2 Mon Sep 17 00:00:00 2001 From: qiaolongfei Date: Thu, 8 Jun 2017 22:38:30 +0800 Subject: [PATCH 0593/3256] refine code --- go/pserver/cclient/test/main.c | 1 - paddle/trainer/NewRemoteParameterUpdater.cpp | 6 +- paddle/trainer/NewRemoteParameterUpdater.h | 68 ++++++++++++-------- 3 files changed, 43 insertions(+), 32 deletions(-) diff --git a/go/pserver/cclient/test/main.c b/go/pserver/cclient/test/main.c index 0ad890daa2f..b95abf96b1d 100644 --- a/go/pserver/cclient/test/main.c +++ b/go/pserver/cclient/test/main.c @@ -14,7 +14,6 @@ int main() { client c = paddle_new_pserver_client(addr, 1); retry: if (paddle_begin_init_params(c)) { - paddle_parameter param; char name_a[] = "param_a"; char name_b[] = "param_b"; diff --git a/paddle/trainer/NewRemoteParameterUpdater.cpp b/paddle/trainer/NewRemoteParameterUpdater.cpp index 9060052e113..13110adb450 100644 --- a/paddle/trainer/NewRemoteParameterUpdater.cpp +++ b/paddle/trainer/NewRemoteParameterUpdater.cpp @@ -45,8 +45,8 @@ void NewRemoteParameterUpdater::init( } // init new parameter and gradient. - initNewParameter(newParameters_, PARAMETER_VALUE); - initNewParameter(newGradients_, PARAMETER_GRADIENT); + newParameters_ = initNewParameter(PARAMETER_VALUE); + newGradients_ = initNewParameter(PARAMETER_GRADIENT); // init parameter, one trainer will get the opportunity to int parameter and // send them to parameter server. Others will get the initialized parameter @@ -60,7 +60,7 @@ void NewRemoteParameterUpdater::init( LOG(INFO) << "paddle_begin_init_params done"; } else { paddle_get_params( - parameterClient_, names_, newParameters_, (int)parameters_.size()); + parameterClient_, names_, newParameters_, parameterSize()); } LOG(INFO) << "NewRemoteParameterUpdater initialized"; diff --git a/paddle/trainer/NewRemoteParameterUpdater.h b/paddle/trainer/NewRemoteParameterUpdater.h index 33640bc8a38..5fd404dcf8c 100644 --- a/paddle/trainer/NewRemoteParameterUpdater.h +++ b/paddle/trainer/NewRemoteParameterUpdater.h @@ -32,9 +32,9 @@ public: NewRemoteParameterUpdater(const OptimizationConfig& config, const std::string pserverSpec); ~NewRemoteParameterUpdater() { - if (newGradients_) { - paddle_pserver_client_release(parameterClient_); - } + releaseNewParameter(newParameters_); + releaseNewParameter(newGradients_); + if (parameterClient_ >= 0) paddle_pserver_client_release(parameterClient_); } /** @@ -57,37 +57,49 @@ public: virtual void startPass(); virtual bool finishPass(); - int parameterSize() { return (int)parameters_.size(); } - +protected: /** - * init parameter of paddle pserver cclient. - * @param new_paras - * @param type + * work need to do after finishBatch */ - void initNewParameter(paddle_parameter**& new_paras, ParameterType type) { - new_paras = - (paddle_parameter**)malloc(sizeof(paddle_parameter*) * parameterSize()); - for (int i = 0; i < parameterSize(); ++i) { - new_paras[i] = (paddle_parameter*)malloc(sizeof(paddle_parameter)); - memset(new_paras[i], 0, sizeof(paddle_parameter)); + virtual void updateImpl(Parameter* para); + +private: + int parameterSize() { + return (int)parameters_.size(); } - for (int i = 0; i < parameterSize(); ++i) { - ParameterPtr para = parameters_[i]; - new_paras[i]->content_len = 10; - new_paras[i]->element_type = PADDLE_ELEMENT_TYPE_FLOAT32; - new_paras[i]->name = (char*)para->getName().c_str(); - new_paras[i]->content = - (unsigned char*)(para->getBuf(type).get()->getData()); - new_paras[i]->content_len = (int)para->getBuf(type).get()->getSize(); + /** + * init parameter of paddle pserver cclient. + * @param new_params + * @param type + */ + paddle_parameter** initNewParameter(ParameterType type) { + paddle_parameter** new_params = + (paddle_parameter**)malloc(sizeof(paddle_parameter*) * parameterSize()); + for (int i = 0; i < parameterSize(); ++i) { + new_params[i] = (paddle_parameter*)malloc(sizeof(paddle_parameter)); + memset(new_params[i], 0, sizeof(paddle_parameter)); + } + + for (int i = 0; i < parameterSize(); ++i) { + ParameterPtr param = parameters_[i]; + new_params[i]->content_len = 10; + new_params[i]->element_type = PADDLE_ELEMENT_TYPE_FLOAT32; + new_params[i]->name = (char*)param->getName().c_str(); + new_params[i]->content = + (unsigned char*)(param->getBuf(type).get()->getData()); + new_params[i]->content_len = (int)param->getBuf(type).get()->getSize(); + } + return new_params; } - } -protected: - /** - * work need to do after finishBatch - */ - virtual void updateImpl(Parameter* para); + void releaseNewParameter(paddle_parameter** newParams) { + if (newParams != NULL) { + for (int i = 0; i < parameterSize(); ++i) { + paddle_release_param(newParams[i]); + } + } + } protected: /// internal parameter client object for exchanging data with pserver -- GitLab From 28476f5f6e81a219914cf70f92a6c0fde2a6c203 Mon Sep 17 00:00:00 2001 From: qiaolongfei Date: Fri, 9 Jun 2017 14:41:22 +0800 Subject: [PATCH 0594/3256] fix the problem of paddle_send_grad --- go/pserver/cclient/cclient.go | 4 +- go/pserver/cclient/test/main.c | 50 ++++++++++++++-- paddle/trainer/NewRemoteParameterUpdater.cpp | 8 ++- paddle/trainer/NewRemoteParameterUpdater.h | 62 ++++++++++---------- 4 files changed, 85 insertions(+), 39 deletions(-) diff --git a/go/pserver/cclient/cclient.go b/go/pserver/cclient/cclient.go index 0b4aa79806b..662e9254277 100644 --- a/go/pserver/cclient/cclient.go +++ b/go/pserver/cclient/cclient.go @@ -164,10 +164,10 @@ func paddle_finish_init_params(client C.client) C.int { } //export paddle_send_grads -func paddle_send_grads(client C.client, grads *C.paddle_gradient, total C.int) C.int { +func paddle_send_grads(client C.client, grads **C.paddle_gradient, total C.int) C.int { var gs []pserver.Gradient for i := 0; i < int(total); i++ { - grad := (*C.paddle_gradient)(unsafe.Pointer((uintptr(unsafe.Pointer(grads)) + uintptr(i)*unsafe.Sizeof(*grads)))) + grad := *(**C.paddle_gradient)(unsafe.Pointer((uintptr(unsafe.Pointer(grads)) + uintptr(i)*unsafe.Sizeof(*grads)))) et := pserver.ElementType(grad.element_type) name := C.GoString(grad.name) content := cArrayToSlice(unsafe.Pointer(grad.content), int(grad.content_len)) diff --git a/go/pserver/cclient/test/main.c b/go/pserver/cclient/test/main.c index b95abf96b1d..10391393070 100644 --- a/go/pserver/cclient/test/main.c +++ b/go/pserver/cclient/test/main.c @@ -1,4 +1,5 @@ #include +#include #include "libpaddle_pserver_cclient.h" @@ -9,6 +10,21 @@ void fail() { exit(-1); } +void print_parameter(paddle_gradient* param) { + if (param == NULL) { + printf("param is NULL!!\n"); + } else { + printf("==== parameter ====\n"); + printf("name: %s\n", param->name); + printf("content_len: %d\n", param->content_len); + printf("content_type: %d\n", param->element_type); + for (int i = 0; i < param->content_len; ++i) { + printf("0x%x ", param->content[i]); + } + printf("\n"); + } +} + int main() { char addr[] = "localhost:3000"; client c = paddle_new_pserver_client(addr, 1); @@ -40,12 +56,27 @@ retry: fail(); } - unsigned char content[] = {0x00, 0x11, 0x22}; - paddle_gradient grads[2] = { - {"param_a", PADDLE_ELEMENT_TYPE_FLOAT32, content, 3}, - {"param_b", PADDLE_ELEMENT_TYPE_INT32, content, 3}}; + unsigned char content1[] = {0x12, 0x23, 0x34}; + unsigned char content2[] = {0x45, 0x56, 0x67}; + + paddle_gradient** new_params = + (paddle_gradient**)malloc(sizeof(paddle_gradient*) * 2); + new_params[0] = (paddle_gradient*)malloc(sizeof(paddle_gradient)); + new_params[0]->name = "param_a"; + new_params[0]->content = content1; + new_params[0]->content_len = 3; + new_params[0]->element_type = PADDLE_ELEMENT_TYPE_FLOAT32; - if (paddle_send_grads(c, grads, 2) != 0) { + new_params[1] = (paddle_gradient*)malloc(sizeof(paddle_gradient)); + new_params[1]->name = "param_b"; + new_params[1]->content = content2; + new_params[1]->content_len = 3; + new_params[1]->element_type = PADDLE_ELEMENT_TYPE_INT32; + + print_parameter(new_params[0]); + print_parameter(new_params[1]); + + if (paddle_send_grads(c, new_params, 2) != 0) { fail(); } @@ -55,6 +86,15 @@ retry: fail(); } + print_parameter(params[0]); + print_parameter(params[1]); + + /// change name of parameter. + char* names2[] = {"param_1", "param_2"}; + if (paddle_get_params(c, names2, params, 2) == 0) { + fail(); + } + // get parameters again by reusing the allocated parameter buffers. if (paddle_get_params(c, names, params, 2) != 0) { fail(); diff --git a/paddle/trainer/NewRemoteParameterUpdater.cpp b/paddle/trainer/NewRemoteParameterUpdater.cpp index 13110adb450..35df973897b 100644 --- a/paddle/trainer/NewRemoteParameterUpdater.cpp +++ b/paddle/trainer/NewRemoteParameterUpdater.cpp @@ -22,7 +22,11 @@ DECLARE_string(save_dir); namespace paddle { NewRemoteParameterUpdater::NewRemoteParameterUpdater( const OptimizationConfig &config, const std::string pserverSpec) - : pserverSpec_(pserverSpec) {} + : parameterClient_(-1), + newParameters_(nullptr), + newGradients_(nullptr), + names_(nullptr), + pserverSpec_(pserverSpec) {} void NewRemoteParameterUpdater::init( const std::vector ¶meters) { @@ -72,7 +76,7 @@ void NewRemoteParameterUpdater::finishBatch(real cost) { LOG(INFO) << "finishBatch in, cost: " << cost; // send gradient to parameter server. - paddle_send_grads(parameterClient_, *newGradients_, parameterSize()); + paddle_send_grads(parameterClient_, newGradients_, parameterSize()); // get the updated parameter from parameterClient. paddle_get_params(parameterClient_, names_, newParameters_, parameterSize()); diff --git a/paddle/trainer/NewRemoteParameterUpdater.h b/paddle/trainer/NewRemoteParameterUpdater.h index 5fd404dcf8c..ed82de3f99f 100644 --- a/paddle/trainer/NewRemoteParameterUpdater.h +++ b/paddle/trainer/NewRemoteParameterUpdater.h @@ -32,6 +32,7 @@ public: NewRemoteParameterUpdater(const OptimizationConfig& config, const std::string pserverSpec); ~NewRemoteParameterUpdater() { + LOG(INFO) << "~NewRemoteParameterUpdater in"; releaseNewParameter(newParameters_); releaseNewParameter(newGradients_); if (parameterClient_ >= 0) paddle_pserver_client_release(parameterClient_); @@ -64,46 +65,47 @@ protected: virtual void updateImpl(Parameter* para); private: - int parameterSize() { - return (int)parameters_.size(); - } + int parameterSize() { return (int)parameters_.size(); } - /** - * init parameter of paddle pserver cclient. - * @param new_params - * @param type - */ - paddle_parameter** initNewParameter(ParameterType type) { - paddle_parameter** new_params = - (paddle_parameter**)malloc(sizeof(paddle_parameter*) * parameterSize()); - for (int i = 0; i < parameterSize(); ++i) { - new_params[i] = (paddle_parameter*)malloc(sizeof(paddle_parameter)); - memset(new_params[i], 0, sizeof(paddle_parameter)); - } + /** + * init parameter of go paddle pserver cclient. + * @param new_params + * @param type + */ + paddle_parameter** initNewParameter(ParameterType type) { + paddle_parameter** new_params = + (paddle_parameter**)malloc(sizeof(paddle_parameter*) * parameterSize()); + for (int i = 0; i < parameterSize(); ++i) { + new_params[i] = (paddle_parameter*)malloc(sizeof(paddle_parameter)); + memset(new_params[i], 0, sizeof(paddle_parameter)); + } - for (int i = 0; i < parameterSize(); ++i) { - ParameterPtr param = parameters_[i]; - new_params[i]->content_len = 10; - new_params[i]->element_type = PADDLE_ELEMENT_TYPE_FLOAT32; - new_params[i]->name = (char*)param->getName().c_str(); - new_params[i]->content = - (unsigned char*)(param->getBuf(type).get()->getData()); - new_params[i]->content_len = (int)param->getBuf(type).get()->getSize(); - } - return new_params; + for (int i = 0; i < parameterSize(); ++i) { + ParameterPtr param = parameters_[i]; + new_params[i]->content_len = 10; + new_params[i]->element_type = PADDLE_ELEMENT_TYPE_FLOAT32; + new_params[i]->name = (char*)param->getName().c_str(); + new_params[i]->content = + (unsigned char*)(param->getBuf(type).get()->getData()); + new_params[i]->content_len = (int)param->getBuf(type).get()->getSize(); } + return new_params; + } - void releaseNewParameter(paddle_parameter** newParams) { - if (newParams != NULL) { - for (int i = 0; i < parameterSize(); ++i) { - paddle_release_param(newParams[i]); + void releaseNewParameter(paddle_parameter** newParams) { + if (newParams != nullptr) { + for (int i = 0; i < parameterSize(); ++i) { + auto param = newParams[i]; + if (param != nullptr) { + paddle_release_param(param); } } } + } protected: /// internal parameter client object for exchanging data with pserver - client parameterClient_ = -1; + client parameterClient_; /// the parameters for new pserver client paddle_parameter** newParameters_; /// the gradinets for new pserver client -- GitLab From 966bf9ae1f090d404f56033e1c9f51c15eb6c2ad Mon Sep 17 00:00:00 2001 From: qiaolongfei Date: Fri, 9 Jun 2017 16:34:24 +0800 Subject: [PATCH 0595/3256] fix the problem in cclient when malloc paddle_parameter --- go/pserver/cclient/cclient.go | 7 +++++-- go/pserver/cclient/test/main.c | 9 +++++---- go/pserver/service.go | 4 ++++ paddle/trainer/NewRemoteParameterUpdater.h | 4 ++-- 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/go/pserver/cclient/cclient.go b/go/pserver/cclient/cclient.go index 662e9254277..be16a143d8e 100644 --- a/go/pserver/cclient/cclient.go +++ b/go/pserver/cclient/cclient.go @@ -42,6 +42,7 @@ import ( "strings" "sync" "unsafe" + "fmt" "github.com/PaddlePaddle/Paddle/go/pserver" ) @@ -204,12 +205,14 @@ func paddle_get_params(client C.client, names **C.char, dst **C.paddle_parameter } p := ps[i] - param := *(**C.paddle_parameter)(unsafe.Pointer((uintptr(unsafe.Pointer(dst)) + uintptr(i)*unsafe.Sizeof(*dst)))) + paramPtr := (**C.paddle_parameter)(unsafe.Pointer((uintptr(unsafe.Pointer(dst)) + uintptr(i)*unsafe.Sizeof(*dst)))) + param := *paramPtr nameReady := false contentAllocated := false if unsafe.Pointer(param) == nullPtr { - param = (*C.paddle_parameter)(C.calloc(1, C.size_t(unsafe.Sizeof(*param)))) + *paramPtr = (*C.paddle_parameter)(C.calloc(1, C.size_t(unsafe.Sizeof(*param)))) + param = *paramPtr } else { if unsafe.Pointer(param.name) != nullPtr { if n := C.GoString(param.name); n != p.Name { diff --git a/go/pserver/cclient/test/main.c b/go/pserver/cclient/test/main.c index 10391393070..59cf5756fde 100644 --- a/go/pserver/cclient/test/main.c +++ b/go/pserver/cclient/test/main.c @@ -21,7 +21,7 @@ void print_parameter(paddle_gradient* param) { for (int i = 0; i < param->content_len; ++i) { printf("0x%x ", param->content[i]); } - printf("\n"); + printf("\n\n"); } } @@ -33,17 +33,18 @@ retry: paddle_parameter param; char name_a[] = "param_a"; char name_b[] = "param_b"; - unsigned char content[] = {0x00, 0x00, 0x00}; + unsigned char content1[] = {0x01, 0x02, 0x03}; param.element_type = PADDLE_ELEMENT_TYPE_FLOAT32; param.name = name_a; - param.content = content; + param.content = content1; param.content_len = 3; if (paddle_init_param(c, param, NULL, 0) != 0) { goto retry; } + unsigned char content2[] = {0x04, 0x05, 0x06}; param.element_type = PADDLE_ELEMENT_TYPE_INT32; param.name = name_b; - param.content = content; + param.content = content2; param.content_len = 3; if (paddle_init_param(c, param, NULL, 0) != 0) { goto retry; diff --git a/go/pserver/service.go b/go/pserver/service.go index d5787b9708b..ab814662b6b 100644 --- a/go/pserver/service.go +++ b/go/pserver/service.go @@ -29,6 +29,10 @@ type Parameter struct { Content []byte } +func (p *Parameter) toString() { + fmt.Println(p.Name, p.ElementType, p.Content) +} + // ParameterWithConfig contains the parameter and the configuration. type ParameterWithConfig struct { Param Parameter diff --git a/paddle/trainer/NewRemoteParameterUpdater.h b/paddle/trainer/NewRemoteParameterUpdater.h index ed82de3f99f..1dbb3658fbe 100644 --- a/paddle/trainer/NewRemoteParameterUpdater.h +++ b/paddle/trainer/NewRemoteParameterUpdater.h @@ -33,8 +33,8 @@ public: const std::string pserverSpec); ~NewRemoteParameterUpdater() { LOG(INFO) << "~NewRemoteParameterUpdater in"; - releaseNewParameter(newParameters_); - releaseNewParameter(newGradients_); +// releaseNewParameter(newParameters_); +// releaseNewParameter(newGradients_); if (parameterClient_ >= 0) paddle_pserver_client_release(parameterClient_); } -- GitLab From e4567962fa8d3314120b6076f46b32bf595c425d Mon Sep 17 00:00:00 2001 From: dzhwinter Date: Sat, 10 Jun 2017 01:50:48 +0800 Subject: [PATCH 0596/3256] "update with comment" --- paddle/optimizer/adadelta_optimizer.cc | 44 ++++++++++++++++++++----- paddle/optimizer/adadelta_optimizer.h | 18 +++++++--- paddle/optimizer/adagrad_optimizer.cc | 10 ++---- paddle/optimizer/adagrad_optimizer.h | 15 +++++---- paddle/optimizer/adam_optimizer.cc | 28 ++++++++++++++++ paddle/optimizer/adam_optimizer.h | 9 +++-- paddle/optimizer/lr_policy.h | 7 ++++ paddle/optimizer/optimizer.cc | 11 ++++--- paddle/optimizer/parameter_optimizer.cc | 34 ++++++++++--------- paddle/optimizer/parameter_optimizer.h | 19 ++++++----- paddle/optimizer/serialization.h | 12 +++++++ paddle/optimizer/sgd_optimizer.h | 22 ++++++++----- paddle/optimizer/sgd_optmizer.cc | 39 +++++++++------------- proto/OptimizerConfig.proto | 37 +++++++++++++++++---- 14 files changed, 210 insertions(+), 95 deletions(-) diff --git a/paddle/optimizer/adadelta_optimizer.cc b/paddle/optimizer/adadelta_optimizer.cc index 64672da0c04..8d5865cd20f 100644 --- a/paddle/optimizer/adadelta_optimizer.cc +++ b/paddle/optimizer/adadelta_optimizer.cc @@ -5,14 +5,6 @@ namespace paddle { namespace optimizer { -void AdadeltaOptimizer::set_weight(Tensor* p) { - parameter_ = p; - size_t size = p->size(); - accum_gradient_ = new Tensor(size); - accum_delta_ = new Tensor(size); - update_delta_ = new Tensor(size); -} - void AdadeltaOptimizer::Update(const Tensor* gradient) { num_sample_passed_ += 1; double learning_rate = lr_policy_->LearningRate(num_sample_passed_); @@ -32,5 +24,39 @@ void AdadeltaOptimizer::Update(const Tensor* gradient) { param[i] -= learning_rate * update_d[i] + learning_rate * decay_ * param[i]; } } + +const char* AdadeltaOptimizer::SerializeState(int* state_len) { + OptimizerState state; + state.set_learning_rate(lr_policy_->LearningRate(num_sample_passed_)); + state.set_num_sample_passed(num_sample_passed_); + + TensorToProto(*parameter_, state.mutable_parameter()); + TensorToProto(*accum_gradient_, state.mutable_accum_gradient()); + TensorToProto(*accum_delta_, state.mutable_accum_delta()); + TensorToProto(*update_delta_, state.mutable_update_delta()); + state.set_nesterov(epsilon_); + state.set_momentum(rho_); + state.set_decay(decay_); + *state_len += CalStateSize(parameter_, + accum_gradient_, + accum_delta_, + update_delta_, + rho_, + epsilon_, + decay_); + return state.SerializeAsString().c_str(); +} + +void AdadeltaOptimizer::DeSerializeState(const std::string& str) { + OptimizerState state; + state.ParseFromString(str); + lr_policy_->set(state.learning_rate()); + num_sample_passed_ = state.num_sample_passed(); + + ProtoToTensor(state.parameter(), parameter_); + ProtoToTensor(state.accum_gradient(), accum_gradient_); + ProtoToTensor(state.accum_delta(), accum_delta_); + ProtoToTensor(state.update_delta(), update_delta_); + +} // namespace optimizer } // namespace optimizer -} // namespace paddle diff --git a/paddle/optimizer/adadelta_optimizer.h b/paddle/optimizer/adadelta_optimizer.h index 4f6137cf2e5..31f29f86752 100644 --- a/paddle/optimizer/adadelta_optimizer.h +++ b/paddle/optimizer/adadelta_optimizer.h @@ -7,21 +7,31 @@ namespace optimizer { class AdadeltaOptimizer : public ParameterOptimizer { public: - AdadeltaOptimizer(double rho, double epsilon, double decay, LrPolicy *lr) - : ParameterOptimizer(lr), + AdadeltaOptimizer( + Tensor *parameter, LrPolicy *lr, double rho, double epsilon, double decay) + : ParameterOptimizer(parameter, lr), accum_gradient_(nullptr), accum_delta_(nullptr), update_delta_(nullptr), rho_(rho), epsilon_(epsilon), - decay_(decay) {} + decay_(decay) { + size_t size = p->size(); + if (accum_gradient_) delete accum_gradient_; + accum_gradient_ = new Tensor(size); + if (accum_delta_) delete accum_delta_; + accum_delta_ = new Tensor(size); + if (update_delta_) delete update_delta_; + update_delta_ = new Tensor(size); + } ~AdadeltaOptimizer() { if (accum_gradient_) delete accum_gradient_; if (accum_delta_) delete accum_delta_; if (update_delta_) delete update_delta_; } void Update(const Tensor *gradient); - void set_weight(Tensor *p); + const char *SerializeState(int *state_len); + void DeSerializeState(const std::string &state); private: Tensor *accum_gradient_; diff --git a/paddle/optimizer/adagrad_optimizer.cc b/paddle/optimizer/adagrad_optimizer.cc index 1698c2abdbb..6a17cf0ed06 100644 --- a/paddle/optimizer/adagrad_optimizer.cc +++ b/paddle/optimizer/adagrad_optimizer.cc @@ -5,12 +5,6 @@ namespace paddle { namespace optimizer { -void AdagradOptimizer::set_weight(Tensor* p) { - parameter_ = p; - size_t size = p->size(); - accum_gradient_ = new Tensor(size); -} - void AdagradOptimizer::Update(const Tensor* gradient) { num_sample_passed_ += 1; double learning_rate = lr_policy_->LearningRate(num_sample_passed_); @@ -23,6 +17,8 @@ void AdagradOptimizer::Update(const Tensor* gradient) { learning_rate * decay_ * param[i]; } } +const char* SGDOptimizer::SerializeState(int* state_len) { NIMPL; } +void SGDOptimizer::DeSerializeState(const std::string& str) { NIMPL; } +// namespace optimizer } // namespace optimizer -} // namespace paddle diff --git a/paddle/optimizer/adagrad_optimizer.h b/paddle/optimizer/adagrad_optimizer.h index 1b05e99754b..14a32cb683a 100644 --- a/paddle/optimizer/adagrad_optimizer.h +++ b/paddle/optimizer/adagrad_optimizer.h @@ -7,16 +7,19 @@ namespace optimizer { class AdagradOptimizer : public ParameterOptimizer { public: - AdagradOptimizer(double epsilon, double decay, LrPolicy *lr) - : ParameterOptimizer(lr), - accum_gradient_(nullptr), - epsilon_(epsilon), - decay_(decay) {} + AdagradOptimizer(Tensor *parameter, + LrPolicy *lr, + double epsilon, + double decay) + : ParameterOptimizer(parameter, lr), epsilon_(epsilon), decay_(decay) { + size_t size = p->size(); + if (accum_gradient_) delete accum_gradient_; + accum_gradient_ = new Tensor(size); + } ~AdagradOptimizer() { if (accum_gradient_) delete accum_gradient_; } void Update(const Tensor *gradient); - void set_weight(Tensor *p); private: Tensor *accum_gradient_; diff --git a/paddle/optimizer/adam_optimizer.cc b/paddle/optimizer/adam_optimizer.cc index d052ac8f9a1..20cf4ef5a11 100644 --- a/paddle/optimizer/adam_optimizer.cc +++ b/paddle/optimizer/adam_optimizer.cc @@ -28,5 +28,33 @@ void AdamOptimizer::Update(const Tensor *gradient) { learning_rate * (m[i] / std::sqrt(v[i] + epsilon_) + decay_ * param[i]); } } + +const char *AdadeltaOptimizer::SerializeState(int *state_len) { + OptimizerState state; + state.set_learning_rate(lr_policy_->LearningRate(num_sample_passed_)); + state.set_num_sample_passed(num_sample_passed_); + + TensorToProto(*parameter_, state.mutable_parameter()); + TensorToProto(*velocitys_, state.mutable_momentums()); + + state.set_beta_1(beta_1_); + state.set_beta_2(beta_2_); + state.set_decay(decay_); + *state_len += CalStateSize( + parameter_, momentums_, velocitys_, beta_1_, beta_2, epsilon_ decay_); + return state.SerializeAsString().c_str(); +} + +void AdadeltaOptimizer::DeSerializeState(const std::string &str) { + OptimizerState state; + state.ParseFromString(str); + lr_policy_->set(state.learning_rate()); + num_sample_passed_ = state.num_sample_passed(); + + ProtoToTensor(state.parameter(), parameter_); + ProtoToTensor(state.velocitys(), velocitys__); + beta_1_ = state.beta_1(); + beta_2_ = state.beta_2(); +} } // namespace optimizer } // namespace paddle diff --git a/paddle/optimizer/adam_optimizer.h b/paddle/optimizer/adam_optimizer.h index 840927e6c7d..cb211c6d888 100644 --- a/paddle/optimizer/adam_optimizer.h +++ b/paddle/optimizer/adam_optimizer.h @@ -7,9 +7,12 @@ namespace optimizer { class AdamOptimizer : public ParameterOptimizer { public: - AdamOptimizer( - double beta_1, double beta_2, double epsilon, double decay, LrPolicy *lr) - : ParameterOptimizer(lr), + AdamOptimizer(Tensor *parameter, + LrPolicy *lr double beta_1, + double beta_2, + double epsilon, + double decay) + : ParameterOptimizer(parameter, lr), momentums_(nullptr), velocitys_(nullptr), beta_1_(beta_1), diff --git a/paddle/optimizer/lr_policy.h b/paddle/optimizer/lr_policy.h index ab7d1fb0fe6..686ba226715 100644 --- a/paddle/optimizer/lr_policy.h +++ b/paddle/optimizer/lr_policy.h @@ -10,6 +10,7 @@ class LrPolicy { public: virtual ~LrPolicy() {} virtual double LearningRate(const uint64_t num_sample_passed) = 0; + virtual void set(double current_learning_rate) = 0; }; // constant learning rate policy @@ -19,6 +20,9 @@ public: double LearningRate(const uint64_t num_sample_passed) { return learning_rate; } + void set(double current_learning_rate) { + learning_rate = current_learning_rate; + } private: double learning_rate; @@ -31,6 +35,9 @@ public: double LearningRate(const uint64_t num_sample_passed) { return std::max(learning_rate - lr_decay_a * num_sample_passed, lr_decay_b); } + void set(double current_learning_rate) { + learning_rate = current_learning_rate; + } private: double learning_rate; diff --git a/paddle/optimizer/optimizer.cc b/paddle/optimizer/optimizer.cc index 9572f163dde..c06c0737b25 100644 --- a/paddle/optimizer/optimizer.cc +++ b/paddle/optimizer/optimizer.cc @@ -44,13 +44,13 @@ paddle_optimizer* paddle_create_optimizer(const unsigned char* config_proto, const int state_len) { paddle_optimizer* optimizer = new paddle_optimizer; std::string config(config_proto, config_proto + config_proto_len); - optimizer->impl = ParameterOptimizer::Create(config); + Tensor* parameter = + new Tensor(reinterpret_cast(param_buffer), num_bytes); + optimizer->impl = ParameterOptimizer::Create(config, parameter); if (state != nullptr) { std::string s(state, state + state_len); optimizer->impl->DeSerializeState(s); } - Tensor* param = new Tensor(reinterpret_cast(param_buffer), num_bytes); - optimizer->impl->set_weight(param); return optimizer; } @@ -77,6 +77,7 @@ int paddle_optimizer_get_weights(paddle_optimizer* o, void** param_buffer) { } int paddle_optimizer_get_state(paddle_optimizer* o, const char** state) { - *state = o->impl->SerializeState(); - return strlen(*state); + int state_len = 0; + *state = o->impl->SerializeState(&state_len); + return state_len; } diff --git a/paddle/optimizer/parameter_optimizer.cc b/paddle/optimizer/parameter_optimizer.cc index 1a96880c574..ae3e97bba8d 100644 --- a/paddle/optimizer/parameter_optimizer.cc +++ b/paddle/optimizer/parameter_optimizer.cc @@ -10,8 +10,8 @@ namespace paddle { namespace optimizer { -ParameterOptimizer *ParameterOptimizer::Create( - const std::string &config_proto) { +ParameterOptimizer *ParameterOptimizer::Create(const std::string &config_proto, + Tensor *parameter) { paddle::OptimizerConfig config; CHECK(config.ParseFromString(config_proto) == 0) << "failed parse optimizer config"; @@ -29,34 +29,38 @@ ParameterOptimizer *ParameterOptimizer::Create( }; LrPolicy *lr = select_lr_policy(config); auto select_optimizer = - [=](const OptimizerConfig &config) -> ParameterOptimizer * { + [=](Tensor *parameter, + const OptimizerConfig &config) -> ParameterOptimizer * { if (config.optimizer() == OptimizerConfig::SGD) { - return new SGDOptimizer(config.sgd().momentum(), + return new SGDOptimizer(parameter, + lr, + config.sgd().momentum(), config.sgd().decay(), - config.sgd().nesterov(), - lr); + config.sgd().nesterov()); } if (config.optimizer() == OptimizerConfig::Adadelta) { - return new AdadeltaOptimizer(config.adadelta().rho(), + return new AdadeltaOptimizer(parameter, + lr, + config.adadelta().rho(), config.adadelta().epsilon(), - config.adadelta().decay(), - lr); + config.adadelta().decay()); } if (config.optimizer() == OptimizerConfig::Adagrad) { return new AdagradOptimizer( - config.adagrad().epsilon(), config.adagrad().decay(), lr); + parameter, lr, config.adagrad().epsilon(), config.adagrad().decay()); } if (config.optimizer() == OptimizerConfig::Adam) { - return new AdamOptimizer(config.adam().beta_1(), + return new AdamOptimizer(parameter, + lr, + config.adam().beta_1(), config.adam().beta_2(), config.adam().epsilon(), - config.adam().decay(), - lr); + config.adam().decay()); } // default LOG(WARNING) << "have not select any Optimizer. use SGDOptimizer in default"; - return new SGDOptimizer(0.0, 0.0, false, lr); + return new SGDOptimizer(parameter, lr, 0.0, 0.0, false); }; return select_optimizer(config); } @@ -66,7 +70,5 @@ float *ParameterOptimizer::get_weight(int *param_size) const { return parameter_->get_buffer(); } -void ParameterOptimizer::set_weight(Tensor *p) { parameter_ = p; } - } // namespace optimizer } // namespace paddle diff --git a/paddle/optimizer/parameter_optimizer.h b/paddle/optimizer/parameter_optimizer.h index f65f1d71a42..1abd659d484 100644 --- a/paddle/optimizer/parameter_optimizer.h +++ b/paddle/optimizer/parameter_optimizer.h @@ -5,32 +5,35 @@ #include #include "OptimizerConfig.pb.h" #include "lr_policy.h" +#include "serialization.h" #include "tensor.h" +// Not Implemen Yet, macr +// o +#define NIMPL crash(__PRETTY_FUNCTION__, " not implemented yet") + namespace paddle { namespace optimizer { -const std::string kOptimizerVersion = "1.0"; - class ParameterOptimizer { public: /** * @brief update hook for algorithm need to traverse parameter more than * once. */ - ParameterOptimizer(LrPolicy *lr) : lr_policy_(lr), num_sample_passed_(0) {} + ParameterOptimizer(Tensor *parameter, LrPolicy *lr) + : parameter_(parameter), lr_policy_(lr), num_sample_passed_(0) {} virtual ~ParameterOptimizer() { delete parameter_; }; - static ParameterOptimizer *Create(const std::string &config_proto); - virtual const char *SerializeState(); - virtual void DeSerializeState(const std::string &state); + static ParameterOptimizer *Create(const std::string &config_proto, + Tensor *parameter); virtual void Update(const Tensor *gradient) = 0; virtual float *get_weight(int *param_size) const; - virtual void set_weight(Tensor *parameter); + virtual const char *SerializeState(int *state_len) = 0; + virtual void DeSerializeState(const std::string &state) = 0; protected: Tensor *parameter_; - // learning rate policy LrPolicy *lr_policy_; uint64_t num_sample_passed_; diff --git a/paddle/optimizer/serialization.h b/paddle/optimizer/serialization.h index 6caa514cef2..a330dd96e93 100644 --- a/paddle/optimizer/serialization.h +++ b/paddle/optimizer/serialization.h @@ -2,6 +2,7 @@ #include #include +#include #include "OptimizerConfig.pb.h" #include "paddle/utils/Logging.h" #include "tensor.h" @@ -9,6 +10,17 @@ namespace paddle { namespace optimizer { +inline unsigned CalStateSize(int* state_len) { return 0; } + +template +unsigned CalStateSize(const HEAD& head, const TAIL&... tail) { + if (std::is_fundamental::value) { + return sizeof head + CalStateSize(tail...); + } else { + return sizeof(head[0] * head->size()) + CalStateSize(tail...); + } +} + static void TensorToProto(const Tensor& tensor, TensorProto* proto) { proto->set_data_type(TensorProto::PADDLE_ELEMENT_TYPE_FLOAT32); proto->set_size(tensor.size()); diff --git a/paddle/optimizer/sgd_optimizer.h b/paddle/optimizer/sgd_optimizer.h index 10b46db3868..d0ac375d2b8 100644 --- a/paddle/optimizer/sgd_optimizer.h +++ b/paddle/optimizer/sgd_optimizer.h @@ -7,20 +7,26 @@ namespace optimizer { class SGDOptimizer : public ParameterOptimizer { public: - SGDOptimizer(double m, double d, bool n, LrPolicy* lr) - : ParameterOptimizer(lr), + SGDOptimizer(Tensor* parameter, LrPolicy* lr, double m, double d, bool n) + : ParameterOptimizer(parameter, lr), momentums_(nullptr), momentum_(m), decay_(d), - nesterov_(n) {} - virtual ~SGDOptimizer() { delete momentums_; } + nesterov_(n) { + if (momentum_ != 0.0) { + size_t size = p->size(); + // TODO: fix it with align aware allocator bind to Tensor + if (momentums_) delete momentums_; + momentums_ = new Tensor(size); + } + } + virtual ~SGDOptimizer() { + if (momentums_) delete momentums_; + } void Update(const Tensor* gradient); - const char* SerializeState(); + const char* SerializeState(int* state_len); void DeSerializeState(const std::string& state); - void set_weight(Tensor* p); - float* get_weight(int* param_size) const; - private: Tensor* momentums_; double momentum_; diff --git a/paddle/optimizer/sgd_optmizer.cc b/paddle/optimizer/sgd_optmizer.cc index fea550f8d1d..b2c6b7a1acf 100644 --- a/paddle/optimizer/sgd_optmizer.cc +++ b/paddle/optimizer/sgd_optmizer.cc @@ -4,15 +4,6 @@ namespace paddle { namespace optimizer { -void SGDOptimizer::set_weight(Tensor *p) { - parameter_ = p; - if (momentum_ != 0.0) { - size_t size = p->size(); - // TODO: fix it with align aware allocator bind to Tensor - momentums_ = new Tensor(size); - } -} - void SGDOptimizer::Update(const Tensor *gradient) { num_sample_passed_ += 1; double learning_rate = lr_policy_->LearningRate(num_sample_passed_); @@ -36,28 +27,30 @@ void SGDOptimizer::Update(const Tensor *gradient) { } } -const char *SGDOptimizer::SerializeState() { +const char *SGDOptimizer::SerializeState(int *state_len) { OptimizerState state; - // version is a global const value - state.set_version(kOptimizerVersion); - TensorToProto(*parameter_, state.add_data()); - TensorToProto(*momentums_, state.add_data()); - state.add_hyperparam(momentum_); + state.set_learning_rate(lr_policy_->LearningRate(num_sample_passed_)); + state.set_num_sample_passed(num_sample_passed_); + + TensorToProto(*parameter_, state.mutable_parameter()); + TensorToProto(*momentums_, state.mutable_momentums()); + state.set_momentum(momentum_); + state.set_decay(decay_); + state.set_nesterov(nesterov_); + *state_len += + CalStateSize(parameter_, momentums_, momentum_, decay_, nesterov_); return state.SerializeAsString().c_str(); } void SGDOptimizer::DeSerializeState(const std::string &str) { OptimizerState state; state.ParseFromString(str); - CHECK(state.version() == kOptimizerVersion) - << "error version of state" - << "expected : " << kOptimizerVersion << "get : " << state.version(); + lr_policy_->set(state.learning_rate()); + num_sample_passed_ = state.num_sample_passed(); - ProtoToTensor(state.data(0), parameter_); - if (state.data_size() == 2) { - ProtoToTensor(state.data(1), momentums_); - momentum_ = state.hyperparam(0); - } + ProtoToTensor(state.parameter(), parameter_); + ProtoToTensor(state.parameter(), momentums_); + momentum_ = state.momentum(); } } // namespace optimizer diff --git a/proto/OptimizerConfig.proto b/proto/OptimizerConfig.proto index 3986fce5da6..1ccba6d2076 100644 --- a/proto/OptimizerConfig.proto +++ b/proto/OptimizerConfig.proto @@ -5,13 +5,14 @@ option optimize_for = LITE_RUNTIME; package paddle; message SGDConfig { - // SGD + // SGD // momentum: float >= 0. Parameter updates momentum. // decay: float >= 0. Learning rate decay over each update. // nesterov: boolean. Whether to apply Nesterov momentum. optional double momentum = 21 [default = 0.0]; optional double decay = 23 [default = 0.0]; optional bool nesterov =24 [default = false]; + } @@ -75,14 +76,38 @@ enum DataType { } required DataType data_type = 1; repeated bytes content = 2; - optional uint64 size = 3; } message OptimizerState { - // match old training state with format parser - required string version = 100; - repeated TensorProto data = 1; - repeated double hyperparam = 3; + optional double learning_rate = 101; + optional double lr_decay_a = 102; + optional double lr_decay_b = 103; + optional double num_sample_passed = 104; + // momentum + optional TensorProto parameter = 105; + optional TensorProto momentums = 1; + + // adadelta + optional TensorProto accum_gradient = 2; + optional TensorProto accum_delta = 3; + optional TensorProto update_delta = 4; + + // adam + optional TensorProto velocitys = 5; + + // momentum + optional double momentum = 6; + optional double decay = 7; + optional bool nesterov = 8; + + // adadelta + optional double rho = 9; + optional double epsilon = 10; + + // adam + optional double beta_1 = 11; + optional double beta_2 = 12; + } message OptimizerConfig { -- GitLab From 33b4deedf3212075f12541879856884ef32fad23 Mon Sep 17 00:00:00 2001 From: dzhwinter Date: Sat, 10 Jun 2017 01:57:26 +0800 Subject: [PATCH 0597/3256] "update serialization part" --- paddle/optimizer/adadelta_optimizer.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/paddle/optimizer/adadelta_optimizer.cc b/paddle/optimizer/adadelta_optimizer.cc index 8d5865cd20f..a6b079ce256 100644 --- a/paddle/optimizer/adadelta_optimizer.cc +++ b/paddle/optimizer/adadelta_optimizer.cc @@ -37,6 +37,7 @@ const char* AdadeltaOptimizer::SerializeState(int* state_len) { state.set_nesterov(epsilon_); state.set_momentum(rho_); state.set_decay(decay_); + // can be used when memory alignment to system *state_len += CalStateSize(parameter_, accum_gradient_, accum_delta_, -- GitLab From 0fc42012a0073a0614e2c0b2634ab9ef7e337916 Mon Sep 17 00:00:00 2001 From: dzhwinter Date: Sat, 10 Jun 2017 01:58:50 +0800 Subject: [PATCH 0598/3256] "update interface" --- paddle/optimizer/adadelta_optimizer.h | 3 --- paddle/optimizer/adagrad_optimizer.h | 2 ++ paddle/optimizer/adam_optimizer.cc | 7 ------- paddle/optimizer/adam_optimizer.h | 9 +++++---- paddle/optimizer/serialization.h | 2 +- 5 files changed, 8 insertions(+), 15 deletions(-) diff --git a/paddle/optimizer/adadelta_optimizer.h b/paddle/optimizer/adadelta_optimizer.h index 31f29f86752..e0f544a90e5 100644 --- a/paddle/optimizer/adadelta_optimizer.h +++ b/paddle/optimizer/adadelta_optimizer.h @@ -10,9 +10,6 @@ public: AdadeltaOptimizer( Tensor *parameter, LrPolicy *lr, double rho, double epsilon, double decay) : ParameterOptimizer(parameter, lr), - accum_gradient_(nullptr), - accum_delta_(nullptr), - update_delta_(nullptr), rho_(rho), epsilon_(epsilon), decay_(decay) { diff --git a/paddle/optimizer/adagrad_optimizer.h b/paddle/optimizer/adagrad_optimizer.h index 14a32cb683a..ebc0fe2acc6 100644 --- a/paddle/optimizer/adagrad_optimizer.h +++ b/paddle/optimizer/adagrad_optimizer.h @@ -20,6 +20,8 @@ public: if (accum_gradient_) delete accum_gradient_; } void Update(const Tensor *gradient); + const char *SerializeState(int *state_len); + void DeSerializeState(const std::string &state); private: Tensor *accum_gradient_; diff --git a/paddle/optimizer/adam_optimizer.cc b/paddle/optimizer/adam_optimizer.cc index 20cf4ef5a11..974039cf6dc 100644 --- a/paddle/optimizer/adam_optimizer.cc +++ b/paddle/optimizer/adam_optimizer.cc @@ -4,13 +4,6 @@ namespace paddle { namespace optimizer { -void AdamOptimizer::set_weight(Tensor *p) { - parameter_ = p; - size_t size = p->size(); - momentums_ = new Tensor(size); - velocitys_ = new Tensor(size); -} - void AdamOptimizer::Update(const Tensor *gradient) { num_sample_passed_ += 1; double learning_rate = lr_policy_->LearningRate(num_sample_passed_); diff --git a/paddle/optimizer/adam_optimizer.h b/paddle/optimizer/adam_optimizer.h index cb211c6d888..b8be2ca2227 100644 --- a/paddle/optimizer/adam_optimizer.h +++ b/paddle/optimizer/adam_optimizer.h @@ -13,18 +13,19 @@ public: double epsilon, double decay) : ParameterOptimizer(parameter, lr), - momentums_(nullptr), - velocitys_(nullptr), beta_1_(beta_1), beta_2_(beta_2), epsilon_(epsilon), - decay_(decay) {} + decay_(decay) { + size_t size = p->size(); + momentums_ = new Tensor(size); + velocitys_ = new Tensor(size); + } ~AdamOptimizer() { if (momentums_) delete momentums_; if (velocitys_) delete velocitys_; } void Update(const Tensor *gradient); - void set_weight(Tensor *p); private: Tensor *momentums_; diff --git a/paddle/optimizer/serialization.h b/paddle/optimizer/serialization.h index a330dd96e93..18088991754 100644 --- a/paddle/optimizer/serialization.h +++ b/paddle/optimizer/serialization.h @@ -17,7 +17,7 @@ unsigned CalStateSize(const HEAD& head, const TAIL&... tail) { if (std::is_fundamental::value) { return sizeof head + CalStateSize(tail...); } else { - return sizeof(head[0] * head->size()) + CalStateSize(tail...); + return sizeof(head[0]) * head->size() + CalStateSize(tail...); } } -- GitLab From c06c6789db7b387ebfd37b71ebedbe8f9aa979e6 Mon Sep 17 00:00:00 2001 From: Yi Wang Date: Fri, 9 Jun 2017 13:11:22 -0700 Subject: [PATCH 0599/3256] Add stringpiece module --- paddle/CMakeLists.txt | 1 + paddle/strings/CMakeLists.txt | 2 + paddle/strings/stringpiece.cc | 127 +++++++++++++++++++++++++++++ paddle/strings/stringpiece.h | 119 +++++++++++++++++++++++++++ paddle/strings/stringpiece_test.cc | 58 +++++++++++++ 5 files changed, 307 insertions(+) create mode 100644 paddle/strings/CMakeLists.txt create mode 100644 paddle/strings/stringpiece.cc create mode 100644 paddle/strings/stringpiece.h create mode 100644 paddle/strings/stringpiece_test.cc diff --git a/paddle/CMakeLists.txt b/paddle/CMakeLists.txt index 9898dc083eb..47ca1833967 100644 --- a/paddle/CMakeLists.txt +++ b/paddle/CMakeLists.txt @@ -8,6 +8,7 @@ add_subdirectory(gserver) add_subdirectory(pserver) add_subdirectory(trainer) add_subdirectory(scripts) +add_subdirectory(strings) # Do not build go directory until go cmake is working smoothly. # if(CMAKE_Go_COMPILER) diff --git a/paddle/strings/CMakeLists.txt b/paddle/strings/CMakeLists.txt new file mode 100644 index 00000000000..4e55eecd484 --- /dev/null +++ b/paddle/strings/CMakeLists.txt @@ -0,0 +1,2 @@ +cc_library(stringpiece SRCS stringpiece.cc) +cc_test(stringpiece_test SRCS stringpiece_test.cc DEPS stringpiece glog gflags) diff --git a/paddle/strings/stringpiece.cc b/paddle/strings/stringpiece.cc new file mode 100644 index 00000000000..ef46490fb1a --- /dev/null +++ b/paddle/strings/stringpiece.cc @@ -0,0 +1,127 @@ +/* + 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/strings/stringpiece.h" + +#include +#include +#include + +namespace paddle { + +StringPiece::StringPiece() : data_(NULL), size_(0) {} + +StringPiece::StringPiece(const char* d, size_t n) : data_(d), size_(n) { + if (d == NULL && n != 0) + throw std::invalid_argument( + "StringPiece requires len to be 0 for NULL data"); +} + +StringPiece::StringPiece(const char* s) : data_(s), size_(strlen(s)) { + if (s == NULL) size_ = 0; +} + +StringPiece::StringPiece(const std::string& s) + : data_(s.data()), size_(s.size()) {} + +int Compare(StringPiece a, StringPiece b) { + const size_t min_len = (a.len() < b.len()) ? a.len() : b.len(); + int r = memcmp(a.data(), b.data(), min_len); + if (r == 0) { + if (a.len() < b.len()) + return -1; + else if (a.len() > b.len()) + return 1; + } + return r; +} + +bool operator==(StringPiece x, StringPiece y) { + return ((x.len() == y.len()) && + (x.data() == y.data() || memcmp(x.data(), y.data(), x.len()) == 0)); +} + +bool operator!=(StringPiece x, StringPiece y) { return !(x == y); } + +bool operator<(StringPiece x, StringPiece y) { return Compare(x, y) < 0; } +bool operator>(StringPiece x, StringPiece y) { return Compare(x, y) > 0; } + +bool operator<=(StringPiece x, StringPiece y) { return Compare(x, y) <= 0; } +bool operator>=(StringPiece x, StringPiece y) { return Compare(x, y) >= 0; } + +bool HasPrefix(StringPiece s, StringPiece x) { + return ((s.len() >= x.len()) && (memcmp(s.data(), x.data(), x.len()) == 0)); +} + +bool HasSuffix(StringPiece s, StringPiece x) { + return ((s.len() >= x.len()) && + (memcmp(s.data() + (s.len() - x.len()), x.data(), x.len()) == 0)); +} + +StringPiece SkipPrefix(StringPiece s, size_t n) { + assert(n <= s.len()); + return StringPiece(s.data() + n, s.len() - n); +} + +StringPiece SkipSuffix(StringPiece s, size_t n) { + assert(size_ >= n); + return StringPiece(s.data(), s.len() - n); +} + +StringPiece TrimPrefix(StringPiece s, StringPiece x) { + return HasPrefix(s, x) ? SkipPrefix(s, x.len()) : s; +} + +StringPiece TrimSuffix(StringPiece s, StringPiece x) { + return HasSuffix(s, x) ? SkipSuffix(s, x.len()) : s; +} + +bool Contains(StringPiece s, StringPiece sub) { + return std::search(s.begin(), s.end(), sub.begin(), sub.end()) != s.end(); +} + +size_t Index(StringPiece s, StringPiece sub) { + auto e = std::search(s.begin(), s.end(), sub.begin(), sub.end()); + return e != s.end() ? e - s.data() : StringPiece::npos; +} + +size_t Find(StringPiece s, char c, size_t pos) { + if (pos >= s.len()) { + return StringPiece::npos; + } + const char* result = + reinterpret_cast(memchr(s.data() + pos, c, s.len() - pos)); + return result != nullptr ? result - s.data() : StringPiece::npos; +} + +size_t RFind(StringPiece s, char c, size_t pos) { + if (s.len() == 0) return StringPiece::npos; + for (const char* p = s.data() + std::min(pos, s.len() - 1); p >= s.data(); + p--) { + if (*p == c) { + return p - s.data(); + } + } + return StringPiece::npos; +} + +StringPiece SubStr(StringPiece s, size_t pos, size_t n) { + if (pos > s.len()) pos = s.len(); + if (n > s.len() - pos) n = s.len() - pos; + return StringPiece(s.data() + pos, n); +} + +} // namespace paddle diff --git a/paddle/strings/stringpiece.h b/paddle/strings/stringpiece.h new file mode 100644 index 00000000000..febb9036dba --- /dev/null +++ b/paddle/strings/stringpiece.h @@ -0,0 +1,119 @@ +/* + Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserve. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#pragma once + +#include +#include + +#include +#include + +namespace paddle { + +// StringPiece points into a std::string object but doesn't own the +// string. It is for efficient access to strings. Like Go's string +// type. StringPiece is not thread-safe. +class StringPiece { +public: + static const size_t npos = static_cast(-1); + + // We provide non-explicit singleton constructors so users can + // pass in a "const char*" or a "string" wherever a "StringPiece" + // is expected. These contructors ensure that if data_ is NULL, + // size_ is 0. + StringPiece(); + StringPiece(const char* d, size_t n); + StringPiece(const char* s); + StringPiece(const std::string& s); + + // For a string, cap() returns the size of storage and len() + // returns the currently used storage. For a StringPiece these + // are the same value. + size_t cap() const { return size_; } + size_t len() const { return size_; } + size_t size() const { return size_; } + + const char* data() const { return data_; } + + char operator[](size_t n) const { + assert(n < len()); + return data_[n]; + } + + // StringPiece doesn't own the string, so both iterator and const + // iterator are const char* indeed. + typedef const char* const_iterator; + typedef const char* iterator; + iterator begin() const { return data_; } + iterator end() const { return data_ + size_; } + + struct Hasher { + size_t operator()(StringPiece arg) const; + }; + + // Return a string that contains the copy of the referenced data. + std::string ToString() const { return std::string(data_, size_); } + +private: + const char* data_; + size_t size_; + + // Intentionally copyable +}; + +// Because StringPiece contains a little data members, and without the +// ownership, it is so cheap to pass StringPieces around, we don't +// need to define parrameters of the following operations to be +// references. Also, it is cheap to construct new StringPieces, so we +// don't define mutative operations as member functions. + +int Compare(StringPiece a, StringPiece b); + +bool operator==(StringPiece x, StringPiece y); +bool operator!=(StringPiece x, StringPiece y); +bool operator<(StringPiece x, StringPiece y); +bool operator>(StringPiece x, StringPiece y); +bool operator<=(StringPiece x, StringPiece y); +bool operator>=(StringPiece x, StringPiece y); + +bool HasPrefix(StringPiece s, StringPiece prefix); +bool HasSuffix(StringPiece s, StringPiece suffix); + +StringPiece SkipPrefix(StringPiece s, size_t n); +StringPiece SkipSuffix(StringPiece s, size_t n); + +// Skip the prefix (or suffix) if it matches with the string. +StringPiece TrimPrefix(StringPiece s, StringPiece prefix); +StringPiece TrimSuffix(StringPiece s, StringPiece suffix); + +bool Contains(StringPiece s, StringPiece sub); + +// Return the first occurrence of sub in s, or npos. +size_t Index(StringPiece s, StringPiece sub); + +// Return the first occurrence of c in s[pos:end], or npos. +size_t Find(StringPiece s, char c, size_t pos); + +// Search range is [0..pos] inclusive. If pos == npos, search everything. +size_t RFind(StringPiece s, char c, size_t pos); + +StringPiece SubStr(StringPiece s, size_t pos, size_t n); + +// allow StringPiece to be logged +extern std::ostream& operator<<(std::ostream& o, StringPiece piece); + +} // namespace paddle diff --git a/paddle/strings/stringpiece_test.cc b/paddle/strings/stringpiece_test.cc new file mode 100644 index 00000000000..79fc219a9c9 --- /dev/null +++ b/paddle/strings/stringpiece_test.cc @@ -0,0 +1,58 @@ +/* + Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserve. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#include "paddle/strings/stringpiece.h" +#include "gtest/gtest.h" + +TEST(StringPiece, Construct) { + { + paddle::StringPiece s; + EXPECT_EQ(NULL, s.data()); + EXPECT_EQ(0U, s.len()); + EXPECT_EQ(0U, s.cap()); + } + { + EXPECT_THROW([] { paddle::StringPiece s(NULL, 10000U); }(), + std::invalid_argument); + } + { + paddle::StringPiece s(NULL); + EXPECT_EQ(0U, s.len()); + } + { + std::string a; + EXPECT_EQ(0U, a.size()); + paddle::StringPiece s(a); + EXPECT_EQ(0U, s.len()); + } +} + +TEST(StringPiece, CopyAndAssign) { + paddle::StringPiece empty; + EXPECT_EQ(0U, empty.len()); + EXPECT_EQ(0U, empty.cap()); + + paddle::StringPiece a("hello"); + paddle::StringPiece b = a; + EXPECT_EQ(b.len(), strlen("hello")); + EXPECT_EQ(b.cap(), strlen("hello")); + EXPECT_EQ(a, b); + + std::string storage("hello"); + paddle::StringPiece c(storage); + EXPECT_EQ(a, c); + EXPECT_NE(a.data(), c.data()); +} -- GitLab From cd91722a958bc3acc16768f67a48a15a9db82e24 Mon Sep 17 00:00:00 2001 From: Yi Wang Date: Fri, 9 Jun 2017 13:28:54 -0700 Subject: [PATCH 0600/3256] Add more unit tests --- paddle/strings/stringpiece.cc | 4 ++-- paddle/strings/stringpiece.h | 8 +------- paddle/strings/stringpiece_test.cc | 25 ++++++++++++++++++++++--- 3 files changed, 25 insertions(+), 12 deletions(-) diff --git a/paddle/strings/stringpiece.cc b/paddle/strings/stringpiece.cc index ef46490fb1a..cc6d6a23f65 100644 --- a/paddle/strings/stringpiece.cc +++ b/paddle/strings/stringpiece.cc @@ -30,8 +30,8 @@ StringPiece::StringPiece(const char* d, size_t n) : data_(d), size_(n) { "StringPiece requires len to be 0 for NULL data"); } -StringPiece::StringPiece(const char* s) : data_(s), size_(strlen(s)) { - if (s == NULL) size_ = 0; +StringPiece::StringPiece(const char* s) : data_(s) { + size_ = (s == NULL) ? 0 : strlen(s); } StringPiece::StringPiece(const std::string& s) diff --git a/paddle/strings/stringpiece.h b/paddle/strings/stringpiece.h index febb9036dba..f10b5eecf43 100644 --- a/paddle/strings/stringpiece.h +++ b/paddle/strings/stringpiece.h @@ -40,14 +40,8 @@ public: StringPiece(const char* s); StringPiece(const std::string& s); - // For a string, cap() returns the size of storage and len() - // returns the currently used storage. For a StringPiece these - // are the same value. - size_t cap() const { return size_; } - size_t len() const { return size_; } - size_t size() const { return size_; } - const char* data() const { return data_; } + size_t len() const { return size_; } char operator[](size_t n) const { assert(n < len()); diff --git a/paddle/strings/stringpiece_test.cc b/paddle/strings/stringpiece_test.cc index 79fc219a9c9..84ebc0ee032 100644 --- a/paddle/strings/stringpiece_test.cc +++ b/paddle/strings/stringpiece_test.cc @@ -22,7 +22,6 @@ TEST(StringPiece, Construct) { paddle::StringPiece s; EXPECT_EQ(NULL, s.data()); EXPECT_EQ(0U, s.len()); - EXPECT_EQ(0U, s.cap()); } { EXPECT_THROW([] { paddle::StringPiece s(NULL, 10000U); }(), @@ -43,12 +42,10 @@ TEST(StringPiece, Construct) { TEST(StringPiece, CopyAndAssign) { paddle::StringPiece empty; EXPECT_EQ(0U, empty.len()); - EXPECT_EQ(0U, empty.cap()); paddle::StringPiece a("hello"); paddle::StringPiece b = a; EXPECT_EQ(b.len(), strlen("hello")); - EXPECT_EQ(b.cap(), strlen("hello")); EXPECT_EQ(a, b); std::string storage("hello"); @@ -56,3 +53,25 @@ TEST(StringPiece, CopyAndAssign) { EXPECT_EQ(a, c); EXPECT_NE(a.data(), c.data()); } + +TEST(StringPiece, Comparison) { + { + paddle::StringPiece a("hello"); + paddle::StringPiece b("world"); + EXPECT_TRUE(a != b); + EXPECT_FALSE(a == b); + EXPECT_TRUE(a < b); + EXPECT_TRUE(a <= b); + EXPECT_FALSE(a > b); + EXPECT_FALSE(a >= b); + } + { + paddle::StringPiece a, b; + EXPECT_TRUE(a == b); + EXPECT_FALSE(a != b); + EXPECT_FALSE(a < b); + EXPECT_FALSE(a > b); + EXPECT_TRUE(a <= b); + EXPECT_TRUE(a >= b); + } +} -- GitLab From ce3408a7a610e564b1ef08719fbe82b600685a8f Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Fri, 9 Jun 2017 22:28:57 +0000 Subject: [PATCH 0601/3256] modify pserver client C API, create better test Please refer to the change design doc for what in API have changed. --- doc/design/cluster_train/pserver_client.md | 37 +++++--- go/pserver/cclient/cclient.go | 98 +++++++++------------- go/pserver/cclient/test/CMakeLists.txt | 2 + go/pserver/cclient/test/main.c | 96 ++++++++++++--------- go/pserver/client_test.go | 2 +- go/pserver/service.go | 12 +-- go/pserver/service_test.go | 10 +-- 7 files changed, 139 insertions(+), 118 deletions(-) diff --git a/doc/design/cluster_train/pserver_client.md b/doc/design/cluster_train/pserver_client.md index b3e40790104..474b8c572cd 100644 --- a/doc/design/cluster_train/pserver_client.md +++ b/doc/design/cluster_train/pserver_client.md @@ -74,14 +74,25 @@ typedef enum { typedef struct { char* name; paddle_element_type element_type; - void* content; + unsigned char* content; int content_len; } paddle_parameter, paddle_gradient; -typedef struct paddle_pserver_client paddle_pserver_client; +typedef int paddle_pserver_client; -paddle_pserver_client* paddle_new_pserver_client(); -void paddle_pserver_client_release(paddle_pserver_client* client); +/** + * @brief creates a pserver client that talks to etcd for coordination. + */ +paddle_pserver_client paddle_new_etcd_pserver_client(char* etcd_addr); + +/** + * @brief creates a pserver client given pserver addresses. + * + * @param pserver_addrs comma-separated pserver addresses. + * @param selected if current pserver client is selected to initialize all parameter servers. + */ +paddle_pserver_client paddle_new_pserver_client(char* pserver_addrs, int selected); +void paddle_pserver_client_release(paddle_pserver_client c); /** * @brief paddle_begin_init_params begins to initialize parameters on @@ -95,7 +106,7 @@ void paddle_pserver_client_release(paddle_pserver_client* client); * @return 1 if the trainer is selected to initialize parameter * servers, otherwise 0. */ -int paddle_begin_init_params(paddle_pserver_client* client); +int paddle_begin_init_params(paddle_pserver_client client); /** * @brief paddle_init_param initializes the parameter on parameter @@ -109,7 +120,7 @@ int paddle_begin_init_params(paddle_pserver_client* client); * @paddle_begin_init_param). Or simply exit the program and wait for * the cluster management system to restart the trainer. */ -int paddle_init_param(paddle_pserver_client* client, paddle_parameter param, const unsigned char* param_config_proto, int config_len); +int paddle_init_param(paddle_pserver_client client, paddle_parameter param, const unsigned char* param_config_proto, int config_len); /** * @brief paddle_finish_init_params tells parameter servers client has @@ -120,7 +131,7 @@ int paddle_init_param(paddle_pserver_client* client, paddle_parameter param, con * @paddle_begin_init_param). Or simply exit the program and wait for * the cluster management system to restart the trainer. */ -int paddle_finish_init_params(paddle_pserver_client* client); +int paddle_finish_init_params(paddle_pserver_client client); /** * @brief paddle_send_grads sends gradients to parameter servers for @@ -131,7 +142,7 @@ int paddle_finish_init_params(paddle_pserver_client* client); * @param learning_rate the learning rate for the gradients. * @return 0 if successful, otherwise -1. */ -int paddle_send_grads(paddle_pserver_client* client, const paddle_gradient* grads, int len); +int paddle_send_grads(paddle_pserver_client client, const paddle_gradient* grads, int len); /** * @brief paddle_get_params gets parameters from parameter servers. @@ -139,13 +150,15 @@ int paddle_send_grads(paddle_pserver_client* client, const paddle_gradient* grad * paddle_get_params will block until parameters are initialized on * the parameter servers. * - * @param names the array of names of the parameters to get. - * @param dst the destination array of parameters to save to. + * @param dst the destination array of parameter pointers to save to. + * The parameter pointer must be pre-popullated with required parameter name, + * and the content of parameter must be pre-allocated of the size of required + * parameter on pserver. * @param len the length of the names array and the paddle_parameter * array. * @return 0 if successful, otherwise -1. */ -int paddle_get_params(paddle_pserver_client* client, const char** names, paddle_parameter* dst, int len); +int paddle_get_params(paddle_pserver_client client, paddle_parameter** dst, int len); /** * @brief paddle_save_model indicates parameters to save the parameter @@ -154,5 +167,5 @@ int paddle_get_params(paddle_pserver_client* client, const char** names, paddle_ * @param path the path to save parameters. * @return 0 if successful, otherwise -1. */ -int paddle_save_model(paddle_pserver_client* client, const char* path); +int paddle_save_model(paddle_pserver_client client, const char* path); ``` diff --git a/go/pserver/cclient/cclient.go b/go/pserver/cclient/cclient.go index 0b4aa79806b..c87f3853c59 100644 --- a/go/pserver/cclient/cclient.go +++ b/go/pserver/cclient/cclient.go @@ -19,21 +19,7 @@ typedef struct { int content_len; } paddle_parameter, paddle_gradient; -static inline void paddle_release_param(paddle_parameter* param) { - if (param != NULL) { - if (param->name != NULL) { - free(param->name); - } - - if (param->content != NULL) { - free(param->content); - } - - free(param); - } -} - -typedef int client; +typedef int paddle_pserver_client; */ import "C" @@ -48,10 +34,10 @@ import ( var nullPtr = unsafe.Pointer(uintptr(0)) var mu sync.Mutex -var handleMap = make(map[C.client]*pserver.Client) -var curHandle C.client +var handleMap = make(map[C.paddle_pserver_client]*pserver.Client) +var curHandle C.paddle_pserver_client -func add(c *pserver.Client) C.client { +func add(c *pserver.Client) C.paddle_pserver_client { mu.Lock() defer mu.Unlock() client := curHandle @@ -60,13 +46,13 @@ func add(c *pserver.Client) C.client { return client } -func get(client C.client) *pserver.Client { +func get(client C.paddle_pserver_client) *pserver.Client { mu.Lock() defer mu.Unlock() return handleMap[client] } -func remove(client C.client) *pserver.Client { +func remove(client C.paddle_pserver_client) *pserver.Client { mu.Lock() defer mu.Unlock() h := handleMap[client] @@ -100,7 +86,7 @@ func (l lister) List() []pserver.Server { } //export paddle_new_pserver_client -func paddle_new_pserver_client(addrs *C.char, selected int) C.client { +func paddle_new_pserver_client(addrs *C.char, selected int) C.paddle_pserver_client { a := C.GoString(addrs) as := strings.Split(a, ",") servers := make([]pserver.Server, len(as)) @@ -113,18 +99,18 @@ func paddle_new_pserver_client(addrs *C.char, selected int) C.client { } //export paddle_new_etcd_pserver_client -func paddle_new_etcd_pserver_client(etcd_addr *C.char) C.client { +func paddle_new_etcd_pserver_client(etcd_addr *C.char) C.paddle_pserver_client { // TODO(helin): fault tolerant pserver client using etcd. panic("not implemented.") } //export paddle_pserver_client_release -func paddle_pserver_client_release(client C.client) { +func paddle_pserver_client_release(client C.paddle_pserver_client) { remove(client) } //export paddle_begin_init_params -func paddle_begin_init_params(client C.client) C.int { +func paddle_begin_init_params(client C.paddle_pserver_client) C.int { c := get(client) if selected := c.BeginInitParams(); selected { return 1 @@ -133,7 +119,7 @@ func paddle_begin_init_params(client C.client) C.int { } //export paddle_init_param -func paddle_init_param(client C.client, param C.paddle_parameter, param_config unsafe.Pointer, config_len C.int) C.int { +func paddle_init_param(client C.paddle_pserver_client, param C.paddle_parameter, param_config unsafe.Pointer, config_len C.int) C.int { et := pserver.ElementType(param.element_type) name := C.GoString(param.name) content := cArrayToSlice(unsafe.Pointer(param.content), int(param.content_len)) @@ -143,7 +129,12 @@ func paddle_init_param(client C.client, param C.paddle_parameter, param_config u } c := get(client) err := c.InitParam(pc) + if err != nil { + if err.Error() == pserver.AlreadyInitialized { + log.Println("parameter", name, "already initialized, treat paddle_init_param as sucessful.") + return 0 + } log.Println(err) return -1 } @@ -152,10 +143,15 @@ func paddle_init_param(client C.client, param C.paddle_parameter, param_config u } //export paddle_finish_init_params -func paddle_finish_init_params(client C.client) C.int { +func paddle_finish_init_params(client C.paddle_pserver_client) C.int { c := get(client) err := c.FinishInitParams() if err != nil { + if err.Error() == pserver.AlreadyInitialized { + log.Println("parameters already initialized, treat paddle_finish_init_params as sucessful.") + return 0 + } + log.Println(err) return -1 } @@ -164,7 +160,7 @@ func paddle_finish_init_params(client C.client) C.int { } //export paddle_send_grads -func paddle_send_grads(client C.client, grads *C.paddle_gradient, total C.int) C.int { +func paddle_send_grads(client C.paddle_pserver_client, grads *C.paddle_gradient, total C.int) C.int { var gs []pserver.Gradient for i := 0; i < int(total); i++ { grad := (*C.paddle_gradient)(unsafe.Pointer((uintptr(unsafe.Pointer(grads)) + uintptr(i)*unsafe.Sizeof(*grads)))) @@ -185,11 +181,11 @@ func paddle_send_grads(client C.client, grads *C.paddle_gradient, total C.int) C } //export paddle_get_params -func paddle_get_params(client C.client, names **C.char, dst **C.paddle_parameter, total C.int) C.int { +func paddle_get_params(client C.paddle_pserver_client, dst **C.paddle_parameter, total C.int) C.int { var ns []string for i := 0; i < int(total); i++ { - name := *(**C.char)(unsafe.Pointer((uintptr(unsafe.Pointer(names)) + uintptr(i)*unsafe.Sizeof(*names)))) - ns = append(ns, C.GoString(name)) + param := *(**C.paddle_parameter)(unsafe.Pointer((uintptr(unsafe.Pointer(dst)) + uintptr(i)*unsafe.Sizeof(*dst)))) + ns = append(ns, C.GoString(param.name)) } c := get(client) ps, err := c.GetParams(ns) @@ -198,44 +194,32 @@ func paddle_get_params(client C.client, names **C.char, dst **C.paddle_parameter return -1 } - for i := 0; i < int(total); i++ { - if i >= len(ps) { - break + if len(ps) != len(ns) { + return -1 + } + + for i := range ps { + if ns[i] != ps[i].Name { + return -1 } + } + for i := 0; i < int(total); i++ { p := ps[i] param := *(**C.paddle_parameter)(unsafe.Pointer((uintptr(unsafe.Pointer(dst)) + uintptr(i)*unsafe.Sizeof(*dst)))) - nameReady := false - contentAllocated := false if unsafe.Pointer(param) == nullPtr { - param = (*C.paddle_parameter)(C.calloc(1, C.size_t(unsafe.Sizeof(*param)))) + log.Println("Error: must pre-allocate parameter.") + return -1 } else { - if unsafe.Pointer(param.name) != nullPtr { - if n := C.GoString(param.name); n != p.Name { - log.Println("Warning: the pre-allocated parameter name does not match the parameter name, it will be freed.", n, p.Name) - C.free(unsafe.Pointer(param.name)) - } else { - nameReady = true - } - } - if unsafe.Pointer(param.content) != nullPtr { - if int(param.content_len) == len(p.Content) { - contentAllocated = true - } else { - log.Println("Warning: the pre-allocated content len does not match parameter content len, the pre-allocated content will be freed.", param.content_len, len(p.Content)) - C.free(unsafe.Pointer(param.content)) + if int(param.content_len) != len(p.Content) { + log.Println("Error: the pre-allocated content len does not match parameter content len.", param.content_len, len(p.Content)) + return -1 } } } - if !nameReady { - param.name = C.CString(p.Name) - } - if !contentAllocated { - param.content = (*C.uchar)(C.malloc(C.size_t(len(p.Content)))) - } C.memcpy(unsafe.Pointer(param.content), unsafe.Pointer(&p.Content[0]), C.size_t(len(p.Content))) param.content_len = C.int(len(p.Content)) param.element_type = C.paddle_element_type(p.ElementType) @@ -245,7 +229,7 @@ func paddle_get_params(client C.client, names **C.char, dst **C.paddle_parameter } //export paddle_save_model -func paddle_save_model(client C.client, path *C.char) C.int { +func paddle_save_model(client C.paddle_pserver_client, path *C.char) C.int { p := C.GoString(path) c := get(client) err := c.Save(p) diff --git a/go/pserver/cclient/test/CMakeLists.txt b/go/pserver/cclient/test/CMakeLists.txt index 16f84648c1d..77bf250b7cc 100644 --- a/go/pserver/cclient/test/CMakeLists.txt +++ b/go/pserver/cclient/test/CMakeLists.txt @@ -7,5 +7,7 @@ add_dependencies(main client) if(APPLE) set(CMAKE_EXE_LINKER_FLAGS "-framework CoreFoundation -framework Security") +else() + set(CMAKE_EXE_LINKER_FLAGS "-pthread") endif() target_link_libraries(main ${CMAKE_BINARY_DIR}/libclient.a) diff --git a/go/pserver/cclient/test/main.c b/go/pserver/cclient/test/main.c index f75a2110b94..63ae7aa92a6 100644 --- a/go/pserver/cclient/test/main.c +++ b/go/pserver/cclient/test/main.c @@ -2,67 +2,87 @@ #include "libclient.h" -void fail() { - // TODO(helin): fix: gtest using cmake is not working, using this - // hacky way for now. - printf("test failed.\n"); +// TODO(helin): fix: gtest using cmake is not working, using this +// hacky way for now. +#define fail() \ + fprintf(stderr, "info: %s:%d: ", __FILE__, __LINE__); \ exit(-1); + +void sendGrads(paddle_pserver_client c) { + unsigned char grad_a[2000] = {2}; + unsigned char grad_b[3000] = {3}; + paddle_gradient grads[2] = { + {"param_a", PADDLE_ELEMENT_TYPE_FLOAT32, grad_a, 2000}, + {"param_b", PADDLE_ELEMENT_TYPE_FLOAT32, grad_b, 3000}}; + + if (paddle_send_grads(c, grads, 2)) { + fail(); + } +} + +void getParams(paddle_pserver_client c) { + paddle_parameter param_a; + paddle_parameter param_b; + char name_a[] = "param_a"; + char name_b[] = "param_b"; + // must pre-allocate the content for parameter to receive. + unsigned char content_a[2000] = {}; + unsigned char content_b[3000] = {}; + param_a.element_type = PADDLE_ELEMENT_TYPE_FLOAT32; + param_a.name = name_a; + param_a.content = content_a; + param_a.content_len = 2000; + param_b.element_type = PADDLE_ELEMENT_TYPE_FLOAT32; + param_b.name = name_b; + param_b.content = content_b; + param_b.content_len = 3000; + + paddle_parameter* params[2] = {¶m_a, ¶m_b}; + if (paddle_get_params(c, params, 2)) { + fail(); + } } int main() { char addr[] = "localhost:3000"; - client c = paddle_new_pserver_client(addr, 1); + paddle_pserver_client c = paddle_new_pserver_client(addr, 1); retry: if (paddle_begin_init_params(c)) { paddle_parameter param; char name_a[] = "param_a"; char name_b[] = "param_b"; - unsigned char content[] = {0x00, 0x11, 0x22}; + unsigned char content_a[2000] = {1}; + unsigned char content_b[3000] = {0}; param.element_type = PADDLE_ELEMENT_TYPE_FLOAT32; param.name = name_a; - param.content = content; - param.content_len = 3; - if (paddle_init_param(c, param, NULL, 0) != 0) { + param.content = content_a; + param.content_len = 2000; + int error = paddle_init_param(c, param, NULL, 0); + if (error != 0) { goto retry; } - param.element_type = PADDLE_ELEMENT_TYPE_INT32; + + param.element_type = PADDLE_ELEMENT_TYPE_FLOAT32; param.name = name_b; - param.content = content; - param.content_len = 3; - if (paddle_init_param(c, param, NULL, 0) != 0) { + param.content = content_b; + param.content_len = 3000; + error = paddle_init_param(c, param, NULL, 0); + if (error != 0) { goto retry; } - if (paddle_finish_init_params(c) != 0) { + + error = paddle_finish_init_params(c); + if (error != 0) { goto retry; } - } else { - fail(); - } - - unsigned char content[] = {0x00, 0x11, 0x22}; - paddle_gradient grads[2] = { - {"param_a", PADDLE_ELEMENT_TYPE_INT32, content, 3}, - {"param_b", PADDLE_ELEMENT_TYPE_FLOAT32, content, 3}}; - - if (!paddle_send_grads(c, grads, 2)) { - fail(); - } - - paddle_parameter* params[2] = {NULL, NULL}; - char* names[] = {"param_a", "param_b"}; - if (!paddle_get_params(c, names, params, 2)) { - fail(); } - // get parameters again by reusing the allocated parameter buffers. - if (!paddle_get_params(c, names, params, 2)) { - fail(); + for (int i = 0; i < 100; i++) { + sendGrads(c); + getParams(c); } - paddle_release_param(params[0]); - paddle_release_param(params[1]); - - if (!paddle_save_model(c, "/tmp/")) { + if (paddle_save_model(c, "/tmp/")) { fail(); } diff --git a/go/pserver/client_test.go b/go/pserver/client_test.go index a9a0948a51a..d0371a26a13 100644 --- a/go/pserver/client_test.go +++ b/go/pserver/client_test.go @@ -117,7 +117,7 @@ func TestClientFull(t *testing.T) { for i := range params { if names[i] != params[i].Name { - t.Fatalf("order of returned parameter does not required: parameter name: %s, required name: %s", names[i], params[i]) + t.Fatalf("order of returned parameter does not required: parameter name: %s, required name: %s", names[i], params[i].Name) } } } diff --git a/go/pserver/service.go b/go/pserver/service.go index d5787b9708b..33e0eb5c5a0 100644 --- a/go/pserver/service.go +++ b/go/pserver/service.go @@ -9,8 +9,10 @@ import ( // ElementType is the type of elements of a Parameter. type ElementType int -var ErrAlreadyInitialized = errors.New("pserver already initialized") -var ErrUninitialized = errors.New("pserver not fully initialized") +const ( + AlreadyInitialized = "pserver already initialized" + Uninitialized = "pserver not fully initialized" +) // Supported element types const ( @@ -59,7 +61,7 @@ func NewService() *Service { func (s *Service) InitParam(paramWithConfigs ParameterWithConfig, dummy *int) error { select { case <-s.initialized: - return ErrAlreadyInitialized + return errors.New(AlreadyInitialized) default: } @@ -80,7 +82,7 @@ func (s *Service) InitParam(paramWithConfigs ParameterWithConfig, dummy *int) er func (s *Service) FinishInitParams(dummy0 int, dummy1 *int) error { select { case <-s.initialized: - return ErrAlreadyInitialized + return errors.New(AlreadyInitialized) default: } @@ -94,7 +96,7 @@ func (s *Service) SendGrad(g Gradient, dummy *int) error { select { case <-s.initialized: default: - return ErrUninitialized + return errors.New(Uninitialized) } s.mu.Lock() diff --git a/go/pserver/service_test.go b/go/pserver/service_test.go index 4c9fac4536e..796492ffb47 100644 --- a/go/pserver/service_test.go +++ b/go/pserver/service_test.go @@ -16,7 +16,7 @@ func TestFull(t *testing.T) { p.Content = []byte{1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0} p.ElementType = pserver.Int32 var dummy int - err := s.InitParam(pserver.ParameterWithConfig{p, nil}, &dummy) + err := s.InitParam(pserver.ParameterWithConfig{Param: p, Config: nil}, &dummy) if err != nil { t.FailNow() } @@ -25,7 +25,7 @@ func TestFull(t *testing.T) { p1.Name = "param_b" p1.Content = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} p1.ElementType = pserver.Float32 - err = s.InitParam(pserver.ParameterWithConfig{p1, nil}, &dummy) + err = s.InitParam(pserver.ParameterWithConfig{Param: p1, Config: nil}, &dummy) if err != nil { t.FailNow() } @@ -81,7 +81,7 @@ func TestMultipleInit(t *testing.T) { } err = s.FinishInitParams(0, &dummy) - if err != pserver.ErrAlreadyInitialized { + if err.Error() != pserver.AlreadyInitialized { t.FailNow() } } @@ -90,7 +90,7 @@ func TestUninitialized(t *testing.T) { s := pserver.NewService() var dummy int err := s.SendGrad(pserver.Gradient{}, &dummy) - if err != pserver.ErrUninitialized { + if err.Error() != pserver.Uninitialized { t.FailNow() } } @@ -135,7 +135,7 @@ func TestBlockUntilInitialized(t *testing.T) { p.Content = []byte{1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0} p.ElementType = pserver.Int32 var dummy int - err := s.InitParam(pserver.ParameterWithConfig{p, nil}, &dummy) + err := s.InitParam(pserver.ParameterWithConfig{Param: p, Config: nil}, &dummy) if err != nil { t.FailNow() } -- GitLab From 83c852c12b4c40eef5961d1da9e0978f1ba6777f Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Fri, 9 Jun 2017 22:39:32 +0000 Subject: [PATCH 0602/3256] better logging --- go/pserver/cclient/cclient.go | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/go/pserver/cclient/cclient.go b/go/pserver/cclient/cclient.go index c87f3853c59..6a76ec920e5 100644 --- a/go/pserver/cclient/cclient.go +++ b/go/pserver/cclient/cclient.go @@ -132,7 +132,7 @@ func paddle_init_param(client C.paddle_pserver_client, param C.paddle_parameter, if err != nil { if err.Error() == pserver.AlreadyInitialized { - log.Println("parameter", name, "already initialized, treat paddle_init_param as sucessful.") + log.Printf("parameter %s already initialized, treat paddle_init_param as sucessful.\n", name) return 0 } log.Println(err) @@ -194,12 +194,38 @@ func paddle_get_params(client C.paddle_pserver_client, dst **C.paddle_parameter, return -1 } + names := func() (string, string) { + retNames := "" + for _, p := range ps { + if retNames == "" { + retNames = p.Name + } else { + retNames = ", " + p.Name + } + } + + requestedNames := "" + for _, n := range ns { + if requestedNames == "" { + requestedNames = n + } else { + requestedNames = ", " + n + } + } + + return requestedNames, retNames + } + if len(ps) != len(ns) { + requestedNames, retNames := names() + log.Printf("pserver returned wrong number of parameters. Requested: %s, returned: %s.\n", retNames, requestedNames) return -1 } for i := range ps { if ns[i] != ps[i].Name { + requestedNames, retNames := names() + log.Printf("pserver returned wrong parameters, or not in requested order. Requested: %s, returned: %s.\n", retNames, requestedNames) return -1 } } @@ -209,12 +235,12 @@ func paddle_get_params(client C.paddle_pserver_client, dst **C.paddle_parameter, param := *(**C.paddle_parameter)(unsafe.Pointer((uintptr(unsafe.Pointer(dst)) + uintptr(i)*unsafe.Sizeof(*dst)))) if unsafe.Pointer(param) == nullPtr { - log.Println("Error: must pre-allocate parameter.") + log.Println("must pre-allocate parameter.") return -1 } else { if unsafe.Pointer(param.content) != nullPtr { if int(param.content_len) != len(p.Content) { - log.Println("Error: the pre-allocated content len does not match parameter content len.", param.content_len, len(p.Content)) + log.Printf("the pre-allocated content len does not match parameter content len. Pre-allocated len: %d, returned len: %d", param.content_len, len(p.Content)) return -1 } } -- GitLab From 0e71ab29db2db20160689097dcfc88220def928d Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Fri, 9 Jun 2017 22:49:38 +0000 Subject: [PATCH 0603/3256] fix comment --- go/pserver/cclient/test/main.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/go/pserver/cclient/test/main.c b/go/pserver/cclient/test/main.c index 63ae7aa92a6..09af316e17a 100644 --- a/go/pserver/cclient/test/main.c +++ b/go/pserver/cclient/test/main.c @@ -2,7 +2,7 @@ #include "libclient.h" -// TODO(helin): fix: gtest using cmake is not working, using this +// TODO(helin): Fix: gtest using cmake is not working, using this // hacky way for now. #define fail() \ fprintf(stderr, "info: %s:%d: ", __FILE__, __LINE__); \ @@ -25,7 +25,7 @@ void getParams(paddle_pserver_client c) { paddle_parameter param_b; char name_a[] = "param_a"; char name_b[] = "param_b"; - // must pre-allocate the content for parameter to receive. + // Must pre-allocate the prameter content before calling paddle_get_params. unsigned char content_a[2000] = {}; unsigned char content_b[3000] = {}; param_a.element_type = PADDLE_ELEMENT_TYPE_FLOAT32; -- GitLab From 44dc9705835b5084cc5fcd5cc86e59a4d0697043 Mon Sep 17 00:00:00 2001 From: Yi Wang Date: Fri, 9 Jun 2017 18:13:19 -0700 Subject: [PATCH 0604/3256] Finish StringPiece and unit tests --- paddle/strings/stringpiece.cc | 14 +- paddle/strings/stringpiece.h | 27 ++-- paddle/strings/stringpiece_test.cc | 226 ++++++++++++++++++++++++++++- 3 files changed, 244 insertions(+), 23 deletions(-) diff --git a/paddle/strings/stringpiece.cc b/paddle/strings/stringpiece.cc index cc6d6a23f65..4f788c6ecd5 100644 --- a/paddle/strings/stringpiece.cc +++ b/paddle/strings/stringpiece.cc @@ -16,7 +16,9 @@ #include "paddle/strings/stringpiece.h" -#include +// #include +#include + #include #include @@ -72,12 +74,14 @@ bool HasSuffix(StringPiece s, StringPiece x) { } StringPiece SkipPrefix(StringPiece s, size_t n) { - assert(n <= s.len()); + if (n > s.len()) + throw std::invalid_argument("Skip distance larger than StringPiece length"); return StringPiece(s.data() + n, s.len() - n); } StringPiece SkipSuffix(StringPiece s, size_t n) { - assert(size_ >= n); + if (n > s.len()) + throw std::invalid_argument("Skip distance larger than StringPiece length"); return StringPiece(s.data(), s.len() - n); } @@ -124,4 +128,8 @@ StringPiece SubStr(StringPiece s, size_t pos, size_t n) { return StringPiece(s.data() + pos, n); } +std::ostream& operator<<(std::ostream& o, StringPiece piece) { + return o << piece.ToString(); +} + } // namespace paddle diff --git a/paddle/strings/stringpiece.h b/paddle/strings/stringpiece.h index f10b5eecf43..413b65d3841 100644 --- a/paddle/strings/stringpiece.h +++ b/paddle/strings/stringpiece.h @@ -17,7 +17,6 @@ #pragma once #include -#include #include #include @@ -26,7 +25,11 @@ namespace paddle { // StringPiece points into a std::string object but doesn't own the // string. It is for efficient access to strings. Like Go's string -// type. StringPiece is not thread-safe. +// type. Not that StringPiece doesn't mutate the underlying string, +// so it is thread-safe given that the underlying string doesn't +// change. Because StringPiece contains a little data members, and +// its syntax is simple as it doesn't own/manage the string, it is +// cheap to construct StringPieces and pass them around. class StringPiece { public: static const size_t npos = static_cast(-1); @@ -37,7 +40,7 @@ public: // size_ is 0. StringPiece(); StringPiece(const char* d, size_t n); - StringPiece(const char* s); + StringPiece(const char* d); StringPiece(const std::string& s); const char* data() const { return data_; } @@ -55,10 +58,6 @@ public: iterator begin() const { return data_; } iterator end() const { return data_ + size_; } - struct Hasher { - size_t operator()(StringPiece arg) const; - }; - // Return a string that contains the copy of the referenced data. std::string ToString() const { return std::string(data_, size_); } @@ -69,12 +68,6 @@ private: // Intentionally copyable }; -// Because StringPiece contains a little data members, and without the -// ownership, it is so cheap to pass StringPieces around, we don't -// need to define parrameters of the following operations to be -// references. Also, it is cheap to construct new StringPieces, so we -// don't define mutative operations as member functions. - int Compare(StringPiece a, StringPiece b); bool operator==(StringPiece x, StringPiece y); @@ -94,9 +87,13 @@ StringPiece SkipSuffix(StringPiece s, size_t n); StringPiece TrimPrefix(StringPiece s, StringPiece prefix); StringPiece TrimSuffix(StringPiece s, StringPiece suffix); +// Returns if s contains sub. Any s except for empty s contains an +// empty sub. bool Contains(StringPiece s, StringPiece sub); -// Return the first occurrence of sub in s, or npos. +// Return the first occurrence of sub in s, or npos. If both s and +// sub is empty, it returns npos; otherwise, if only sub is empty, it +// returns 0. size_t Index(StringPiece s, StringPiece sub); // Return the first occurrence of c in s[pos:end], or npos. @@ -108,6 +105,6 @@ size_t RFind(StringPiece s, char c, size_t pos); StringPiece SubStr(StringPiece s, size_t pos, size_t n); // allow StringPiece to be logged -extern std::ostream& operator<<(std::ostream& o, StringPiece piece); +std::ostream& operator<<(std::ostream& o, StringPiece piece); } // namespace paddle diff --git a/paddle/strings/stringpiece_test.cc b/paddle/strings/stringpiece_test.cc index 84ebc0ee032..2ba66a04f64 100644 --- a/paddle/strings/stringpiece_test.cc +++ b/paddle/strings/stringpiece_test.cc @@ -15,6 +15,9 @@ */ #include "paddle/strings/stringpiece.h" + +#include + #include "gtest/gtest.h" TEST(StringPiece, Construct) { @@ -23,10 +26,7 @@ TEST(StringPiece, Construct) { EXPECT_EQ(NULL, s.data()); EXPECT_EQ(0U, s.len()); } - { - EXPECT_THROW([] { paddle::StringPiece s(NULL, 10000U); }(), - std::invalid_argument); - } + { EXPECT_THROW(paddle::StringPiece s(NULL, 10000U), std::invalid_argument); } { paddle::StringPiece s(NULL); EXPECT_EQ(0U, s.len()); @@ -54,7 +54,7 @@ TEST(StringPiece, CopyAndAssign) { EXPECT_NE(a.data(), c.data()); } -TEST(StringPiece, Comparison) { +TEST(StringPiece, Compare) { { paddle::StringPiece a("hello"); paddle::StringPiece b("world"); @@ -64,6 +64,8 @@ TEST(StringPiece, Comparison) { EXPECT_TRUE(a <= b); EXPECT_FALSE(a > b); EXPECT_FALSE(a >= b); + EXPECT_LT(Compare(a, b), 0); + EXPECT_GT(Compare(b, a), 0); } { paddle::StringPiece a, b; @@ -73,5 +75,219 @@ TEST(StringPiece, Comparison) { EXPECT_FALSE(a > b); EXPECT_TRUE(a <= b); EXPECT_TRUE(a >= b); + EXPECT_EQ(0, Compare(a, b)); + EXPECT_EQ(0, Compare(b, a)); + } +} + +TEST(StringPiece, ToString) { + { + paddle::StringPiece s; + EXPECT_EQ(std::string(""), s.ToString()); + } + { + paddle::StringPiece s(NULL); + EXPECT_EQ(std::string(""), s.ToString()); + } + { + paddle::StringPiece s("hello"); + EXPECT_EQ(std::string("hello"), s.ToString()); + } +} + +TEST(StringPiece, HasPrefixSuffix) { + using paddle::HasPrefix; + using paddle::HasSuffix; + { + paddle::StringPiece s; + EXPECT_FALSE(HasPrefix(s, "something")); + EXPECT_TRUE(HasPrefix(s, "")); + EXPECT_FALSE(HasSuffix(s, "something")); + EXPECT_TRUE(HasSuffix(s, "")); + } + { + paddle::StringPiece s("app"); + EXPECT_TRUE(HasPrefix(s, "")); + EXPECT_TRUE(HasPrefix(s, "a")); + EXPECT_TRUE(HasPrefix(s, "ap")); + EXPECT_TRUE(HasPrefix(s, "app")); + + EXPECT_TRUE(HasSuffix(s, "")); + EXPECT_TRUE(HasSuffix(s, "p")); + EXPECT_TRUE(HasSuffix(s, "pp")); + EXPECT_TRUE(HasSuffix(s, "app")); } } + +TEST(StringPiece, SkipPrefixSuffix) { + using paddle::SkipPrefix; + using paddle::SkipSuffix; + { + paddle::StringPiece s; + EXPECT_EQ("", SkipPrefix(s, 0)); + EXPECT_THROW(SkipPrefix(s, 1), std::invalid_argument); + + EXPECT_EQ("", SkipSuffix(s, 0)); + EXPECT_THROW(SkipSuffix(s, 1), std::invalid_argument); + } + { + paddle::StringPiece s("app"); + EXPECT_EQ("app", SkipPrefix(s, 0)); + EXPECT_EQ("pp", SkipPrefix(s, 1)); + EXPECT_EQ("p", SkipPrefix(s, 2)); + EXPECT_EQ("", SkipPrefix(s, 3)); + EXPECT_THROW(SkipPrefix(s, 4), std::invalid_argument); + + EXPECT_EQ("app", SkipSuffix(s, 0)); + EXPECT_EQ("ap", SkipSuffix(s, 1)); + EXPECT_EQ("a", SkipSuffix(s, 2)); + EXPECT_EQ("", SkipSuffix(s, 3)); + EXPECT_THROW(SkipSuffix(s, 4), std::invalid_argument); + } +} + +TEST(StringPiece, TrimPrefixSuffix) { + using paddle::TrimPrefix; + using paddle::TrimSuffix; + { + paddle::StringPiece s; + EXPECT_EQ("", TrimPrefix(s, "")); + EXPECT_EQ("", TrimPrefix(s, "something")); + + EXPECT_EQ("", TrimSuffix(s, "")); + EXPECT_EQ("", TrimSuffix(s, "something")); + } + { + paddle::StringPiece s("app"); + EXPECT_EQ("app", TrimPrefix(s, "")); + EXPECT_EQ("pp", TrimPrefix(s, "a")); + EXPECT_EQ("p", TrimPrefix(s, "ap")); + EXPECT_EQ("", TrimPrefix(s, "app")); + EXPECT_EQ("app", TrimPrefix(s, "something")); + + EXPECT_EQ("app", TrimSuffix(s, "")); + EXPECT_EQ("ap", TrimSuffix(s, "p")); + EXPECT_EQ("a", TrimSuffix(s, "pp")); + EXPECT_EQ("", TrimSuffix(s, "app")); + EXPECT_EQ("app", TrimSuffix(s, "something")); + } +} + +TEST(StringPiece, Contains) { + using paddle::Contains; + { + paddle::StringPiece s; + EXPECT_FALSE(Contains(s, "")); + EXPECT_FALSE(Contains(s, "something")); + } + { + paddle::StringPiece s("app"); + EXPECT_TRUE(Contains(s, "")); + EXPECT_TRUE(Contains(s, "a")); + EXPECT_TRUE(Contains(s, "p")); + EXPECT_TRUE(Contains(s, "ap")); + EXPECT_TRUE(Contains(s, "pp")); + EXPECT_TRUE(Contains(s, "app")); + EXPECT_FALSE(Contains(s, "something")); + } +} + +TEST(StringPiece, Index) { + using paddle::Index; + auto npos = paddle::StringPiece::npos; + { + paddle::StringPiece s; + EXPECT_EQ(npos, Index(s, "")); + EXPECT_EQ(npos, Index(s, "something")); + } + { + paddle::StringPiece s("app"); + EXPECT_EQ(0U, Index(s, "")); + EXPECT_EQ(0U, Index(s, "a")); + EXPECT_EQ(1U, Index(s, "p")); + EXPECT_EQ(0U, Index(s, "ap")); + EXPECT_EQ(1U, Index(s, "pp")); + EXPECT_EQ(0U, Index(s, "app")); + EXPECT_EQ(npos, Index(s, "something")); + } +} + +TEST(StringPiece, Find) { + using paddle::Find; + auto npos = paddle::StringPiece::npos; + { + paddle::StringPiece s; + EXPECT_EQ(npos, Find(s, 'a', 0U)); + } + { + paddle::StringPiece s("app"); + EXPECT_EQ(0U, Find(s, 'a', 0U)); + EXPECT_EQ(1U, Find(s, 'p', 0U)); + EXPECT_EQ(1U, Find(s, 'p', 1U)); + EXPECT_EQ(2U, Find(s, 'p', 2U)); + EXPECT_EQ(npos, Find(s, 'z', 2U)); + } +} + +TEST(StringPiece, RFind) { + using paddle::RFind; + auto npos = paddle::StringPiece::npos; + { + paddle::StringPiece s; + EXPECT_EQ(npos, RFind(s, 'a', 0U)); + } + { + paddle::StringPiece s("app"); + EXPECT_EQ(2U, RFind(s, 'p', 2U)); + EXPECT_EQ(0U, RFind(s, 'a', 2U)); + EXPECT_EQ(1U, RFind(s, 'p', 1U)); + EXPECT_EQ(0U, RFind(s, 'a', 0)); + EXPECT_EQ(npos, RFind(s, 'z', 2U)); + } +} + +TEST(StringPiece, SubStr) { + using paddle::SubStr; + { + paddle::StringPiece s; + EXPECT_EQ("", SubStr(s, 0, 0)); + EXPECT_EQ("", SubStr(s, 0, 1)); + EXPECT_EQ("", SubStr(s, 1, 0)); + } + { + paddle::StringPiece s("app"); + EXPECT_EQ("", SubStr(s, 0, 0)); + EXPECT_EQ("", SubStr(s, 1, 0)); + EXPECT_EQ("", SubStr(s, 2, 0)); + EXPECT_EQ("", SubStr(s, 3, 0)); + + EXPECT_EQ("a", SubStr(s, 0, 1)); + EXPECT_EQ("p", SubStr(s, 1, 1)); + EXPECT_EQ("p", SubStr(s, 2, 1)); + EXPECT_EQ("", SubStr(s, 3, 1)); + + EXPECT_EQ("ap", SubStr(s, 0, 2)); + EXPECT_EQ("pp", SubStr(s, 1, 2)); + EXPECT_EQ("p", SubStr(s, 2, 2)); + EXPECT_EQ("", SubStr(s, 3, 2)); + + EXPECT_EQ("app", SubStr(s, 0, 3)); + EXPECT_EQ("pp", SubStr(s, 1, 3)); + EXPECT_EQ("p", SubStr(s, 2, 3)); + EXPECT_EQ("", SubStr(s, 3, 3)); + } +} + +TEST(StringPiece, StreamOutput) { + using paddle::StringPiece; + + std::stringstream o; + o << StringPiece(); + EXPECT_EQ("", o.str()); + + o << StringPiece("hello"); + EXPECT_EQ("hello", o.str()); + + o << StringPiece(); + EXPECT_EQ("hello", o.str()); +} -- GitLab From 327a8f36be328dab951cb6584fb22e55ae637aa0 Mon Sep 17 00:00:00 2001 From: Yi Wang Date: Fri, 9 Jun 2017 18:16:38 -0700 Subject: [PATCH 0605/3256] Minimize header file inclusion --- paddle/strings/stringpiece.cc | 8 +++++++- paddle/strings/stringpiece.h | 8 +------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/paddle/strings/stringpiece.cc b/paddle/strings/stringpiece.cc index 4f788c6ecd5..415b3558d5d 100644 --- a/paddle/strings/stringpiece.cc +++ b/paddle/strings/stringpiece.cc @@ -16,11 +16,11 @@ #include "paddle/strings/stringpiece.h" -// #include #include #include #include +#include namespace paddle { @@ -39,6 +39,12 @@ StringPiece::StringPiece(const char* s) : data_(s) { StringPiece::StringPiece(const std::string& s) : data_(s.data()), size_(s.size()) {} +char StringPiece::operator[](size_t n) const { + if (n >= len()) + throw std::invalid_argument("index out of StringPiece length"); + return data_[n]; +} + int Compare(StringPiece a, StringPiece b) { const size_t min_len = (a.len() < b.len()) ? a.len() : b.len(); int r = memcmp(a.data(), b.data(), min_len); diff --git a/paddle/strings/stringpiece.h b/paddle/strings/stringpiece.h index 413b65d3841..89aa084a292 100644 --- a/paddle/strings/stringpiece.h +++ b/paddle/strings/stringpiece.h @@ -16,9 +16,6 @@ #pragma once -#include - -#include #include namespace paddle { @@ -46,10 +43,7 @@ public: const char* data() const { return data_; } size_t len() const { return size_; } - char operator[](size_t n) const { - assert(n < len()); - return data_[n]; - } + char operator[](size_t n) const; // StringPiece doesn't own the string, so both iterator and const // iterator are const char* indeed. -- GitLab From c44f5dd883b49d063d336dda874f1794270c2982 Mon Sep 17 00:00:00 2001 From: qiaolongfei Date: Sat, 10 Jun 2017 20:46:26 +0800 Subject: [PATCH 0606/3256] add simple updater, this version can train a model --- go/pserver/cclient/cclient.go | 1 - go/pserver/cclient/test/CMakeLists.txt | 4 + go/pserver/cclient/test/main.c | 71 ++++-------- go/pserver/cclient/test/test_cclient.c | 114 +++++++++++++++++++ go/pserver/optimizer.c | 8 +- paddle/trainer/NewRemoteParameterUpdater.cpp | 2 - paddle/trainer/NewRemoteParameterUpdater.h | 2 +- 7 files changed, 146 insertions(+), 56 deletions(-) create mode 100644 go/pserver/cclient/test/test_cclient.c diff --git a/go/pserver/cclient/cclient.go b/go/pserver/cclient/cclient.go index be16a143d8e..7fdf9f0ec20 100644 --- a/go/pserver/cclient/cclient.go +++ b/go/pserver/cclient/cclient.go @@ -42,7 +42,6 @@ import ( "strings" "sync" "unsafe" - "fmt" "github.com/PaddlePaddle/Paddle/go/pserver" ) diff --git a/go/pserver/cclient/test/CMakeLists.txt b/go/pserver/cclient/test/CMakeLists.txt index 762772812f5..e7d0a74237e 100644 --- a/go/pserver/cclient/test/CMakeLists.txt +++ b/go/pserver/cclient/test/CMakeLists.txt @@ -2,6 +2,8 @@ cmake_minimum_required(VERSION 3.0) add_executable(main main.c) add_dependencies(main paddle_pserver_cclient) +add_executable(test_cclient test_cclient.c) +add_dependencies(test_cclient paddle_pserver_cclient) if(APPLE) set(CMAKE_EXE_LINKER_FLAGS "-framework CoreFoundation -framework Security") @@ -10,7 +12,9 @@ endif() if(PROJ_ROOT) include_directories(${CMAKE_BINARY_DIR}/go/pserver/cclient/) target_link_libraries(main ${CMAKE_BINARY_DIR}/go/pserver/cclient/libpaddle_pserver_cclient.a pthread) + target_link_libraries(test_cclient ${CMAKE_BINARY_DIR}/go/pserver/cclient/libpaddle_pserver_cclient.a pthread) else(PROJ_ROOT) include_directories(${CMAKE_BINARY_DIR}) target_link_libraries(main ${CMAKE_BINARY_DIR}/libpaddle_pserver_cclient.a pthread) + target_link_libraries(test_cclient ${CMAKE_BINARY_DIR}/libpaddle_pserver_cclient.a pthread) endif(PROJ_ROOT) diff --git a/go/pserver/cclient/test/main.c b/go/pserver/cclient/test/main.c index 59cf5756fde..a074808b16a 100644 --- a/go/pserver/cclient/test/main.c +++ b/go/pserver/cclient/test/main.c @@ -1,5 +1,4 @@ #include -#include #include "libpaddle_pserver_cclient.h" @@ -10,21 +9,6 @@ void fail() { exit(-1); } -void print_parameter(paddle_gradient* param) { - if (param == NULL) { - printf("param is NULL!!\n"); - } else { - printf("==== parameter ====\n"); - printf("name: %s\n", param->name); - printf("content_len: %d\n", param->content_len); - printf("content_type: %d\n", param->element_type); - for (int i = 0; i < param->content_len; ++i) { - printf("0x%x ", param->content[i]); - } - printf("\n\n"); - } -} - int main() { char addr[] = "localhost:3000"; client c = paddle_new_pserver_client(addr, 1); @@ -33,23 +17,21 @@ retry: paddle_parameter param; char name_a[] = "param_a"; char name_b[] = "param_b"; - unsigned char content1[] = {0x01, 0x02, 0x03}; + unsigned char content[] = {0x00, 0x11, 0x22}; param.element_type = PADDLE_ELEMENT_TYPE_FLOAT32; param.name = name_a; - param.content = content1; + param.content = content; param.content_len = 3; if (paddle_init_param(c, param, NULL, 0) != 0) { goto retry; } - unsigned char content2[] = {0x04, 0x05, 0x06}; param.element_type = PADDLE_ELEMENT_TYPE_INT32; param.name = name_b; - param.content = content2; + param.content = content; param.content_len = 3; if (paddle_init_param(c, param, NULL, 0) != 0) { goto retry; } - if (paddle_finish_init_params(c) != 0) { goto retry; } @@ -57,27 +39,22 @@ retry: fail(); } - unsigned char content1[] = {0x12, 0x23, 0x34}; - unsigned char content2[] = {0x45, 0x56, 0x67}; - - paddle_gradient** new_params = - (paddle_gradient**)malloc(sizeof(paddle_gradient*) * 2); - new_params[0] = (paddle_gradient*)malloc(sizeof(paddle_gradient)); - new_params[0]->name = "param_a"; - new_params[0]->content = content1; - new_params[0]->content_len = 3; - new_params[0]->element_type = PADDLE_ELEMENT_TYPE_FLOAT32; - - new_params[1] = (paddle_gradient*)malloc(sizeof(paddle_gradient)); - new_params[1]->name = "param_b"; - new_params[1]->content = content2; - new_params[1]->content_len = 3; - new_params[1]->element_type = PADDLE_ELEMENT_TYPE_INT32; - - print_parameter(new_params[0]); - print_parameter(new_params[1]); - - if (paddle_send_grads(c, new_params, 2) != 0) { + unsigned char content[] = {0x00, 0x11, 0x22}; + paddle_gradient** grads = + (paddle_gradient**)malloc(sizeof(paddle_gradient*) * 2); + grads[0] = (paddle_gradient*)malloc(sizeof(paddle_gradient)); + grads[0]->name = "param_a"; + grads[0]->content = content; + grads[0]->content_len = 3; + grads[0]->element_type = PADDLE_ELEMENT_TYPE_FLOAT32; + + grads[1] = (paddle_gradient*)malloc(sizeof(paddle_gradient)); + grads[1]->name = "param_b"; + grads[1]->content = content; + grads[1]->content_len = 3; + grads[1]->element_type = PADDLE_ELEMENT_TYPE_INT32; + + if (paddle_send_grads(c, grads, 2) != 0) { fail(); } @@ -87,15 +64,6 @@ retry: fail(); } - print_parameter(params[0]); - print_parameter(params[1]); - - /// change name of parameter. - char* names2[] = {"param_1", "param_2"}; - if (paddle_get_params(c, names2, params, 2) == 0) { - fail(); - } - // get parameters again by reusing the allocated parameter buffers. if (paddle_get_params(c, names, params, 2) != 0) { fail(); @@ -109,5 +77,6 @@ retry: } printf("test success!\n"); + return 0; } diff --git a/go/pserver/cclient/test/test_cclient.c b/go/pserver/cclient/test/test_cclient.c new file mode 100644 index 00000000000..4d6fca29fbe --- /dev/null +++ b/go/pserver/cclient/test/test_cclient.c @@ -0,0 +1,114 @@ +#include +#include + +#include "libpaddle_pserver_cclient.h" + +typedef float real; + +void fail() { + // TODO(helin): fix: gtest using cmake is not working, using this + // hacky way for now. + printf("test failed.\n"); + exit(-1); +} + +void print_parameter(paddle_gradient* param) { + if (param == NULL) { + printf("param is NULL!!\n"); + } else { + printf("==== parameter ====\n"); + printf("name: %s\n", param->name); + printf("content_len: %d\n", param->content_len); + printf("content_type: %d\n", param->element_type); + for (int i = 0; i < param->content_len/sizeof(real); ++i) { + printf("%f ", ((float *)param->content)[i]); + } + printf("\n\n"); + } +} + +int main() { + char addr[] = "localhost:3000"; + client c = paddle_new_pserver_client(addr, 1); + + char* names[] = {"param_a", "param_b"}; +retry: + + if (paddle_begin_init_params(c)) { + paddle_parameter param; + real param_content1[] = {0.1, 0.2, 0.3}; + param.element_type = PADDLE_ELEMENT_TYPE_FLOAT32; + param.name = names[0]; + param.content = (unsigned char*)param_content1; + param.content_len = 3 * sizeof(real); + if (paddle_init_param(c, param, NULL, 0) != 0) { + goto retry; + } + real param_content2[] = {0.4, 0.5, 0.6}; + param.element_type = PADDLE_ELEMENT_TYPE_INT32; + param.name = names[1]; + param.content = (unsigned char*)param_content2; + param.content_len = 3 * sizeof(real); + if (paddle_init_param(c, param, NULL, 0) != 0) { + goto retry; + } + + if (paddle_finish_init_params(c) != 0) { + goto retry; + } + } else { + fail(); + } + + printf("get initialized parameters from pserver:\n"); + paddle_parameter* param_ptrs[2] = {NULL, NULL}; + if (paddle_get_params(c, names, param_ptrs, 2) != 0) { + fail(); + } + print_parameter(param_ptrs[0]); + print_parameter(param_ptrs[1]); + + printf("send gradient to pserver:\n"); + real gradient_content1[] = {0.01, 0.02, 0.03}; + real gradinet_content2[] = {0.04, 0.05, 0.06}; + + paddle_gradient** grads = + (paddle_gradient**)malloc(sizeof(paddle_gradient*) * 2); + grads[0] = (paddle_gradient*)malloc(sizeof(paddle_gradient)); + grads[0]->name = names[0]; + grads[0]->content = (unsigned char*)gradient_content1; + grads[0]->content_len = 3 * sizeof(real); + grads[0]->element_type = PADDLE_ELEMENT_TYPE_FLOAT32; + + grads[1] = (paddle_gradient*)malloc(sizeof(paddle_gradient)); + grads[1]->name = names[1]; + grads[1]->content = (unsigned char*)gradinet_content2; + grads[1]->content_len = 3 * sizeof(real); + grads[1]->element_type = PADDLE_ELEMENT_TYPE_INT32; + + print_parameter(grads[0]); + print_parameter(grads[1]); + + if (paddle_send_grads(c, grads, 2) != 0) { + fail(); + } + + printf("get updated parameters from pserver:\n"); + // get parameters again by reusing the allocated parameter buffers. + if (paddle_get_params(c, names, param_ptrs, 2) != 0) { + fail(); + } + + print_parameter(param_ptrs[0]); + print_parameter(param_ptrs[1]); + + paddle_release_param(param_ptrs[0]); + paddle_release_param(param_ptrs[1]); + + if (paddle_save_model(c, "/tmp/") != 0) { + fail(); + } + + printf("test success!\n"); + return 0; +} diff --git a/go/pserver/optimizer.c b/go/pserver/optimizer.c index b8da3ec9592..5d0b1017ce3 100644 --- a/go/pserver/optimizer.c +++ b/go/pserver/optimizer.c @@ -32,7 +32,13 @@ int update_SGD(void* optimizer, const void* gradient, int num_bytes) { SGD_optimizer* o = (SGD_optimizer*)optimizer; - // TODO + // TODO(a simple SGD implement) + float* parameter = (float *)buffer; + float* grad = (float *)gradient; + + for(int i = 0; i < num_bytes/sizeof(float); ++i) { + parameter[i] -= o->learning_rate * grad[i]; + } return 0; } diff --git a/paddle/trainer/NewRemoteParameterUpdater.cpp b/paddle/trainer/NewRemoteParameterUpdater.cpp index 35df973897b..0f879dbde0a 100644 --- a/paddle/trainer/NewRemoteParameterUpdater.cpp +++ b/paddle/trainer/NewRemoteParameterUpdater.cpp @@ -73,8 +73,6 @@ void NewRemoteParameterUpdater::init( void NewRemoteParameterUpdater::updateImpl(Parameter *para) {} void NewRemoteParameterUpdater::finishBatch(real cost) { - LOG(INFO) << "finishBatch in, cost: " << cost; - // send gradient to parameter server. paddle_send_grads(parameterClient_, newGradients_, parameterSize()); // get the updated parameter from parameterClient. diff --git a/paddle/trainer/NewRemoteParameterUpdater.h b/paddle/trainer/NewRemoteParameterUpdater.h index 1dbb3658fbe..d3a032badc9 100644 --- a/paddle/trainer/NewRemoteParameterUpdater.h +++ b/paddle/trainer/NewRemoteParameterUpdater.h @@ -87,7 +87,7 @@ private: new_params[i]->name = (char*)param->getName().c_str(); new_params[i]->content = (unsigned char*)(param->getBuf(type).get()->getData()); - new_params[i]->content_len = (int)param->getBuf(type).get()->getSize(); + new_params[i]->content_len = (int)param->getBuf(type).get()->getSize() * sizeof(real); } return new_params; } -- GitLab From 39d0b3de99112e6b90c22147b6c15917f5a3e1d5 Mon Sep 17 00:00:00 2001 From: qiaolongfei Date: Sat, 10 Jun 2017 21:18:03 +0800 Subject: [PATCH 0607/3256] add test file mnist_test.py, free resource of newRemoteParameterUpdater properly --- go/pserver/cclient/test/mnist_test.py | 134 +++++++++++++++++++++ paddle/trainer/NewRemoteParameterUpdater.h | 14 +-- 2 files changed, 141 insertions(+), 7 deletions(-) create mode 100644 go/pserver/cclient/test/mnist_test.py diff --git a/go/pserver/cclient/test/mnist_test.py b/go/pserver/cclient/test/mnist_test.py new file mode 100644 index 00000000000..c77af49130f --- /dev/null +++ b/go/pserver/cclient/test/mnist_test.py @@ -0,0 +1,134 @@ +import paddle.v2 as paddle +import gzip + + +def softmax_regression(img): + predict = paddle.layer.fc(input=img, + size=10, + act=paddle.activation.Softmax()) + return predict + + +def multilayer_perceptron(img): + # The first fully-connected layer + hidden1 = paddle.layer.fc(input=img, size=128, act=paddle.activation.Relu()) + # The second fully-connected layer and the according activation function + hidden2 = paddle.layer.fc(input=hidden1, + size=64, + act=paddle.activation.Relu()) + # The thrid fully-connected layer, note that the hidden size should be 10, + # which is the number of unique digits + predict = paddle.layer.fc(input=hidden2, + size=10, + act=paddle.activation.Softmax()) + return predict + + +def convolutional_neural_network(img): + # first conv layer + conv_pool_1 = paddle.networks.simple_img_conv_pool( + input=img, + filter_size=5, + num_filters=20, + num_channel=1, + pool_size=2, + pool_stride=2, + act=paddle.activation.Tanh()) + # second conv layer + conv_pool_2 = paddle.networks.simple_img_conv_pool( + input=conv_pool_1, + filter_size=5, + num_filters=50, + num_channel=20, + pool_size=2, + pool_stride=2, + act=paddle.activation.Tanh()) + # The first fully-connected layer + fc1 = paddle.layer.fc(input=conv_pool_2, + size=128, + act=paddle.activation.Tanh()) + # The softmax layer, note that the hidden size should be 10, + # which is the number of unique digits + predict = paddle.layer.fc(input=fc1, + size=10, + act=paddle.activation.Softmax()) + return predict + + +def main(): + paddle.init(use_gpu=False, trainer_count=1, trainer_id=1) + + # define network topology + images = paddle.layer.data( + name='pixel', type=paddle.data_type.dense_vector(784)) + label = paddle.layer.data( + name='label', type=paddle.data_type.integer_value(10)) + + # Here we can build the prediction network in different ways. Please + # choose one by uncomment corresponding line. + predict = softmax_regression(images) + #predict = multilayer_perceptron(images) + #predict = convolutional_neural_network(images) + + cost = paddle.layer.classification_cost(input=predict, label=label) + parameters = paddle.parameters.create(cost) + + optimizer = paddle.optimizer.Momentum( + learning_rate=0.1 / 128.0, + momentum=0.9, + regularization=paddle.optimizer.L2Regularization(rate=0.0005 * 128)) + + trainer = paddle.trainer.SGD(cost=cost, + parameters=parameters, + update_equation=optimizer, + is_local=False, + pserver_spec="localhost:3000") + + lists = [] + + def event_handler(event): + if isinstance(event, paddle.event.EndIteration): + if event.batch_id % 1000 == 0: + print "Pass %d, Batch %d, Cost %f, %s" % ( + event.pass_id, event.batch_id, event.cost, event.metrics) + + with gzip.open('params.tar.gz', 'w') as f: + parameters.to_tar(f) + + elif isinstance(event, paddle.event.EndPass): + result = trainer.test(reader=paddle.batch( + paddle.dataset.mnist.test(), batch_size=128)) + print "Test with Pass %d, Cost %f, %s\n" % ( + event.pass_id, result.cost, result.metrics) + lists.append((event.pass_id, result.cost, + result.metrics['classification_error_evaluator'])) + + trainer.train( + reader=paddle.batch( + paddle.reader.shuffle( + paddle.dataset.mnist.train(), buf_size=8192), + batch_size=128), + event_handler=event_handler, + num_passes=100) + + # find the best pass + best = sorted(lists, key=lambda list: float(list[1]))[0] + print 'Best pass is %s, testing Avgcost is %s' % (best[0], best[1]) + print 'The classification accuracy is %.2f%%' % (100 - float(best[2]) * 100) + + test_creator = paddle.dataset.mnist.test() + test_data = [] + for item in test_creator(): + test_data.append((item[0], )) + if len(test_data) == 100: + break + + # output is a softmax layer. It returns probabilities. + # Shape should be (100, 10) + probs = paddle.infer( + output_layer=predict, parameters=parameters, input=test_data) + print probs.shape + + +if __name__ == '__main__': + main() diff --git a/paddle/trainer/NewRemoteParameterUpdater.h b/paddle/trainer/NewRemoteParameterUpdater.h index d3a032badc9..db9b44af194 100644 --- a/paddle/trainer/NewRemoteParameterUpdater.h +++ b/paddle/trainer/NewRemoteParameterUpdater.h @@ -32,9 +32,11 @@ public: NewRemoteParameterUpdater(const OptimizationConfig& config, const std::string pserverSpec); ~NewRemoteParameterUpdater() { - LOG(INFO) << "~NewRemoteParameterUpdater in"; -// releaseNewParameter(newParameters_); -// releaseNewParameter(newGradients_); + if (names_ != nullptr) { + free(names_); + } + releaseNewParameter(newParameters_); + releaseNewParameter(newGradients_); if (parameterClient_ >= 0) paddle_pserver_client_release(parameterClient_); } @@ -95,11 +97,9 @@ private: void releaseNewParameter(paddle_parameter** newParams) { if (newParams != nullptr) { for (int i = 0; i < parameterSize(); ++i) { - auto param = newParams[i]; - if (param != nullptr) { - paddle_release_param(param); - } + free(newParams[i]); } + free(newParams); } } -- GitLab From 4f366be4e298db78c7438f60d72fddf39b2b6ccc Mon Sep 17 00:00:00 2001 From: qiaolongfei Date: Sat, 10 Jun 2017 21:23:02 +0800 Subject: [PATCH 0608/3256] clang format --- go/pserver/cclient/test/main.c | 2 +- go/pserver/cclient/test/test_cclient.c | 4 ++-- go/pserver/optimizer.c | 6 +++--- paddle/trainer/NewRemoteParameterUpdater.h | 3 ++- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/go/pserver/cclient/test/main.c b/go/pserver/cclient/test/main.c index a074808b16a..72ec3590768 100644 --- a/go/pserver/cclient/test/main.c +++ b/go/pserver/cclient/test/main.c @@ -41,7 +41,7 @@ retry: unsigned char content[] = {0x00, 0x11, 0x22}; paddle_gradient** grads = - (paddle_gradient**)malloc(sizeof(paddle_gradient*) * 2); + (paddle_gradient**)malloc(sizeof(paddle_gradient*) * 2); grads[0] = (paddle_gradient*)malloc(sizeof(paddle_gradient)); grads[0]->name = "param_a"; grads[0]->content = content; diff --git a/go/pserver/cclient/test/test_cclient.c b/go/pserver/cclient/test/test_cclient.c index 4d6fca29fbe..82cef386d75 100644 --- a/go/pserver/cclient/test/test_cclient.c +++ b/go/pserver/cclient/test/test_cclient.c @@ -20,8 +20,8 @@ void print_parameter(paddle_gradient* param) { printf("name: %s\n", param->name); printf("content_len: %d\n", param->content_len); printf("content_type: %d\n", param->element_type); - for (int i = 0; i < param->content_len/sizeof(real); ++i) { - printf("%f ", ((float *)param->content)[i]); + for (int i = 0; i < param->content_len / sizeof(real); ++i) { + printf("%f ", ((float*)param->content)[i]); } printf("\n\n"); } diff --git a/go/pserver/optimizer.c b/go/pserver/optimizer.c index 5d0b1017ce3..47fe1efbf59 100644 --- a/go/pserver/optimizer.c +++ b/go/pserver/optimizer.c @@ -33,10 +33,10 @@ int update_SGD(void* optimizer, int num_bytes) { SGD_optimizer* o = (SGD_optimizer*)optimizer; // TODO(a simple SGD implement) - float* parameter = (float *)buffer; - float* grad = (float *)gradient; + float* parameter = (float*)buffer; + float* grad = (float*)gradient; - for(int i = 0; i < num_bytes/sizeof(float); ++i) { + for (int i = 0; i < num_bytes / sizeof(float); ++i) { parameter[i] -= o->learning_rate * grad[i]; } return 0; diff --git a/paddle/trainer/NewRemoteParameterUpdater.h b/paddle/trainer/NewRemoteParameterUpdater.h index db9b44af194..b7c0425982b 100644 --- a/paddle/trainer/NewRemoteParameterUpdater.h +++ b/paddle/trainer/NewRemoteParameterUpdater.h @@ -89,7 +89,8 @@ private: new_params[i]->name = (char*)param->getName().c_str(); new_params[i]->content = (unsigned char*)(param->getBuf(type).get()->getData()); - new_params[i]->content_len = (int)param->getBuf(type).get()->getSize() * sizeof(real); + new_params[i]->content_len = + (int)param->getBuf(type).get()->getSize() * sizeof(real); } return new_params; } -- GitLab From da3e84a6d25fe75f63a624e4e523aba7a8c378c6 Mon Sep 17 00:00:00 2001 From: qiaolongfei Date: Sat, 10 Jun 2017 21:43:59 +0800 Subject: [PATCH 0609/3256] change trainer_id --- go/pserver/cclient/test/mnist_test.py | 5 +---- go/pserver/cclient/test/test_train.py | 2 +- paddle/trainer/NewRemoteParameterUpdater.cpp | 4 ++-- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/go/pserver/cclient/test/mnist_test.py b/go/pserver/cclient/test/mnist_test.py index c77af49130f..c3a3af55e28 100644 --- a/go/pserver/cclient/test/mnist_test.py +++ b/go/pserver/cclient/test/mnist_test.py @@ -56,7 +56,7 @@ def convolutional_neural_network(img): def main(): - paddle.init(use_gpu=False, trainer_count=1, trainer_id=1) + paddle.init(use_gpu=False, trainer_count=1) # define network topology images = paddle.layer.data( @@ -92,9 +92,6 @@ def main(): print "Pass %d, Batch %d, Cost %f, %s" % ( event.pass_id, event.batch_id, event.cost, event.metrics) - with gzip.open('params.tar.gz', 'w') as f: - parameters.to_tar(f) - elif isinstance(event, paddle.event.EndPass): result = trainer.test(reader=paddle.batch( paddle.dataset.mnist.test(), batch_size=128)) diff --git a/go/pserver/cclient/test/test_train.py b/go/pserver/cclient/test/test_train.py index ddd6371e0cc..3f8d5d793bd 100644 --- a/go/pserver/cclient/test/test_train.py +++ b/go/pserver/cclient/test/test_train.py @@ -4,7 +4,7 @@ import paddle.v2.dataset.uci_housing as uci_housing def main(): # init - paddle.init(use_gpu=False, trainer_count=1, trainer_id=1) + paddle.init(use_gpu=False, trainer_count=1) # network config x = paddle.layer.data(name='x', type=paddle.data_type.dense_vector(13)) diff --git a/paddle/trainer/NewRemoteParameterUpdater.cpp b/paddle/trainer/NewRemoteParameterUpdater.cpp index 0f879dbde0a..d554e09759c 100644 --- a/paddle/trainer/NewRemoteParameterUpdater.cpp +++ b/paddle/trainer/NewRemoteParameterUpdater.cpp @@ -39,8 +39,8 @@ void NewRemoteParameterUpdater::init( } // create parameter server client. - parameterClient_ = - paddle_new_pserver_client((char *)pserverSpec_.c_str(), FLAGS_trainer_id); + parameterClient_ = paddle_new_pserver_client((char *)pserverSpec_.c_str(), + FLAGS_trainer_id == 0); // init names_ for get parameter through paddle_cclient names_ = (char **)malloc(parameterSize() * sizeof(char *)); -- GitLab From b7e68e06713f8a2a67a2bd576f23b740717b6e0d Mon Sep 17 00:00:00 2001 From: dzhwinter Date: Sun, 11 Jun 2017 23:53:03 +0800 Subject: [PATCH 0610/3256] "serialization modify" --- paddle/optimizer/serialization.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/paddle/optimizer/serialization.h b/paddle/optimizer/serialization.h index 18088991754..60bb7e27651 100644 --- a/paddle/optimizer/serialization.h +++ b/paddle/optimizer/serialization.h @@ -10,7 +10,7 @@ namespace paddle { namespace optimizer { -inline unsigned CalStateSize(int* state_len) { return 0; } +static unsigned CalStateSize(int* state_len) { return 0; } template unsigned CalStateSize(const HEAD& head, const TAIL&... tail) { @@ -23,7 +23,6 @@ unsigned CalStateSize(const HEAD& head, const TAIL&... tail) { static void TensorToProto(const Tensor& tensor, TensorProto* proto) { proto->set_data_type(TensorProto::PADDLE_ELEMENT_TYPE_FLOAT32); - proto->set_size(tensor.size()); std::stringstream os; for (size_t i = 0; i < tensor.size(); ++i) { os << tensor[i]; -- GitLab From a85ca79a1d97a98dfaab59a0f9d1ce2ecc014e7c Mon Sep 17 00:00:00 2001 From: wuyi05 Date: Mon, 12 Jun 2017 09:40:04 +0800 Subject: [PATCH 0611/3256] make swig link RDMA libs if RDMA enabled --- paddle/api/CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/paddle/api/CMakeLists.txt b/paddle/api/CMakeLists.txt index e147659566d..071bc36c2de 100644 --- a/paddle/api/CMakeLists.txt +++ b/paddle/api/CMakeLists.txt @@ -41,6 +41,7 @@ SET(SWIG_MODULE_swig_paddle_EXTRA_DEPS paddle_network paddle_proto ${external_project_dependencies} + ${RDMA_LIBS} ) IF(APPLE) @@ -73,6 +74,7 @@ SWIG_LINK_LIBRARIES(swig_paddle ${CMAKE_DL_LIBS} ${EXTERNAL_LIBS} ${CMAKE_THREAD_LIBS_INIT} + ${RDMA_LD_FLAGS} ${START_END} ) -- GitLab From 283bdc5062be0ba14b0ae3ca6cc211ddaf25fd1c Mon Sep 17 00:00:00 2001 From: gongweibao Date: Mon, 12 Jun 2017 10:29:35 +0800 Subject: [PATCH 0612/3256] fix by helin's comments --- paddle/parameter/tests/test_argument.cpp | 2 +- python/paddle/v2/dataset/common.py | 58 +++++++++++-------- python/paddle/v2/dataset/tests/common_test.py | 26 +++++++-- 3 files changed, 56 insertions(+), 30 deletions(-) diff --git a/paddle/parameter/tests/test_argument.cpp b/paddle/parameter/tests/test_argument.cpp index 81fe4ee3973..98ab0135487 100644 --- a/paddle/parameter/tests/test_argument.cpp +++ b/paddle/parameter/tests/test_argument.cpp @@ -42,7 +42,7 @@ TEST(Argument, poolSequenceWithStride) { CHECK_EQ(outStart[3], 4); CHECK_EQ(outStart[4], 7); - CHECK_EQ(stridePositions->getSize(), 8); + CHECK_EQ(stridePositions->getSize(), 8UL); auto result = reversed ? strideResultReversed : strideResult; for (int i = 0; i < 8; i++) { CHECK_EQ(stridePositions->getData()[i], result[i]); diff --git a/python/paddle/v2/dataset/common.py b/python/paddle/v2/dataset/common.py index 89675080e25..8023fa3cf88 100644 --- a/python/paddle/v2/dataset/common.py +++ b/python/paddle/v2/dataset/common.py @@ -151,9 +151,14 @@ def cluster_files_reader(files_pattern, return reader -def convert(output_path, eader, num_shards, name_prefix): +def convert(output_path, + reader, + num_shards, + name_prefix, + max_lines_to_shuffle=10000): import recordio import cPickle as pickle + import random """ Convert data from reader to recordio format files. @@ -161,35 +166,40 @@ def convert(output_path, eader, num_shards, name_prefix): :param reader: a data reader, from which the convert program will read data instances. :param num_shards: the number of shards that the dataset will be partitioned into. :param name_prefix: the name prefix of generated files. + :param max_lines_to_shuffle: the max lines numbers to shuffle before writing. """ - def open_needs(idx): - n = "%s/%s-%05d" % (output_path, name_prefix, idx) - w = recordio.writer(n) - f = open(n, "w") - idx += 1 + assert num_shards >= 1 + assert max_lines_to_shuffle >= 1 - return w, f, idx + def open_writers(): + w = [] + for i in range(0, num_shards): + n = "%s/%s-%05d-of-%05d" % (output_path, name_prefix, i, + num_shards - 1) + w.append(recordio.writer(n)) - def close_needs(w, f): - if w is not None: - w.close() + return w - if f is not None: - f.close() + def close_writers(w): + for i in range(0, num_shards): + w[i].close() - idx = 0 - w = None - f = None + def write_data(w, lines): + random.shuffle(lines) + for i, d in enumerate(lines): + d = pickle.dumps(d, pickle.HIGHEST_PROTOCOL) + w[i % num_shards].write(d) - for i, d in enumerate(reader()): - if w is None: - w, f, idx = open_needs(idx) - - w.write(pickle.dumps(d, pickle.HIGHEST_PROTOCOL)) + w = open_writers() + lines = [] - if i % num_shards == 0 and i >= num_shards: - close_needs(w, f) - w, f, idx = open_needs(idx) + for i, d in enumerate(reader()): + lines.append(d) + if i % max_lines_to_shuffle == 0 and i >= max_lines_to_shuffle: + write_data(w, lines) + lines = [] + continue - close_needs(w, f) + write_data(w, lines) + close_writers(w) diff --git a/python/paddle/v2/dataset/tests/common_test.py b/python/paddle/v2/dataset/tests/common_test.py index 3120026e1e6..cfa194eba38 100644 --- a/python/paddle/v2/dataset/tests/common_test.py +++ b/python/paddle/v2/dataset/tests/common_test.py @@ -58,20 +58,36 @@ class TestCommon(unittest.TestCase): self.assertEqual(e, str("0")) def test_convert(self): + record_num = 10 + num_shards = 4 + def test_reader(): def reader(): - for x in xrange(10): + for x in xrange(record_num): yield x return reader path = tempfile.mkdtemp() - paddle.v2.dataset.common.convert(path, - test_reader(), 4, 'random_images') + test_reader(), num_shards, + 'random_images') - files = glob.glob(temp_path + '/random_images-*') - self.assertEqual(len(files), 3) + files = glob.glob(path + '/random_images-*') + self.assertEqual(len(files), num_shards) + + recs = [] + for i in range(0, num_shards): + n = "%s/random_images-%05d-of-%05d" % (path, i, num_shards - 1) + r = recordio.reader(n) + while True: + d = r.read() + if d is None: + break + recs.append(d) + + recs.sort() + self.assertEqual(total, record_num) if __name__ == '__main__': -- GitLab From dc458a0d5ac7d5e88047856b773da1052eed80d8 Mon Sep 17 00:00:00 2001 From: qiaolongfei Date: Mon, 12 Jun 2017 14:18:05 +0800 Subject: [PATCH 0613/3256] change go version --- .travis.yml | 1 + go/pserver/cclient/test/test_cclient.c | 3 ++- go/pserver/optimizer.c | 3 ++- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 44b755ee32d..f9b4a7e0831 100644 --- a/.travis.yml +++ b/.travis.yml @@ -50,6 +50,7 @@ before_install: # protobuf version. - pip install numpy wheel 'protobuf==3.1' sphinx==1.5.6 recommonmark sphinx-rtd-theme==0.1.9 virtualenv pre-commit requests==2.9.2 LinkChecker - pip install rarfile + - eval "$(GIMME_GO_VERSION=1.8.3 gimme)" - | function timeout() { perl -e 'alarm shift; exec @ARGV' "$@"; } script: diff --git a/go/pserver/cclient/test/test_cclient.c b/go/pserver/cclient/test/test_cclient.c index 82cef386d75..50ba2d5597a 100644 --- a/go/pserver/cclient/test/test_cclient.c +++ b/go/pserver/cclient/test/test_cclient.c @@ -20,7 +20,8 @@ void print_parameter(paddle_gradient* param) { printf("name: %s\n", param->name); printf("content_len: %d\n", param->content_len); printf("content_type: %d\n", param->element_type); - for (int i = 0; i < param->content_len / sizeof(real); ++i) { + int i; + for (i = 0; i < param->content_len / sizeof(real); ++i) { printf("%f ", ((float*)param->content)[i]); } printf("\n\n"); diff --git a/go/pserver/optimizer.c b/go/pserver/optimizer.c index 47fe1efbf59..48bbceb343b 100644 --- a/go/pserver/optimizer.c +++ b/go/pserver/optimizer.c @@ -36,7 +36,8 @@ int update_SGD(void* optimizer, float* parameter = (float*)buffer; float* grad = (float*)gradient; - for (int i = 0; i < num_bytes / sizeof(float); ++i) { + int i; + for (i = 0; i < num_bytes / sizeof(float); ++i) { parameter[i] -= o->learning_rate * grad[i]; } return 0; -- GitLab From aeecdadf3d517c6b316ae11646d5aeb98006891c Mon Sep 17 00:00:00 2001 From: liaogang Date: Mon, 12 Jun 2017 16:23:39 +0800 Subject: [PATCH 0614/3256] add ostream --- paddle/strings/stringpiece.h | 1 + 1 file changed, 1 insertion(+) diff --git a/paddle/strings/stringpiece.h b/paddle/strings/stringpiece.h index 89aa084a292..b452116057c 100644 --- a/paddle/strings/stringpiece.h +++ b/paddle/strings/stringpiece.h @@ -17,6 +17,7 @@ #pragma once #include +#include namespace paddle { -- GitLab From 110c0570a5fae15b9f31541cb2082e4f5618538b Mon Sep 17 00:00:00 2001 From: liaogang Date: Mon, 12 Jun 2017 17:56:33 +0800 Subject: [PATCH 0615/3256] clang-format --- paddle/strings/stringpiece.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paddle/strings/stringpiece.h b/paddle/strings/stringpiece.h index b452116057c..adff713e86f 100644 --- a/paddle/strings/stringpiece.h +++ b/paddle/strings/stringpiece.h @@ -16,8 +16,8 @@ #pragma once -#include #include +#include namespace paddle { -- GitLab From 48e0f432537a97e915306601a8b5c8b72a77b6d1 Mon Sep 17 00:00:00 2001 From: hedaoyuan Date: Mon, 12 Jun 2017 21:22:15 +0800 Subject: [PATCH 0616/3256] Add ImageExpandFunction. --- paddle/function/GemmConvOp.h | 84 +++++++++++++++ paddle/function/ImageExpandOp.cpp | 164 ++++++++++++++++++++++++++++++ 2 files changed, 248 insertions(+) create mode 100644 paddle/function/GemmConvOp.h create mode 100644 paddle/function/ImageExpandOp.cpp diff --git a/paddle/function/GemmConvOp.h b/paddle/function/GemmConvOp.h new file mode 100644 index 00000000000..25d2e220bfb --- /dev/null +++ b/paddle/function/GemmConvOp.h @@ -0,0 +1,84 @@ +/* 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 "ConvOp.h" + +namespace paddle { + +/* The storage format of the coldata in the Im2ColFunctor and Col2ImFunctor. */ +enum ColFormat { kCFO = 0, kOCF = 1 }; + +/* + * \brief Converts the image data of four dimensions(NCHW) into a colData. + * Then you can reshape colData to a convolution matrix for + * convolution calculation based on matrix multiplication. + * + * \param imData Image data of NCHW format. + * The format of imData is: + * [input_channels, input_height, input_width]. + * \param colData colData data. + * If the template argument Format is kCFO, + * the format of colData is: + * [input_channels, + * filter_height, + * filter_width, + * output_height, + * output_width] + * If the template argument Format is kOCF, + * the format of colData is: + * [output_height, + * output_width, + * input_channels, + * filter_height, + * filter_width] + */ +template +class Im2ColFunctor { +public: + void operator()(const T* imData, + int inputChannels, + int inputHeight, + int inputWidth, + int filterHeight, + int filterWidth, + int strideHeight, + int strideWidth, + int paddingHeight, + int paddingWidth, + int outputHeight, + int outputWidth, + T* colData); +}; + +template +class Col2ImFunctor { +public: + void operator()(const T* colData, + int inputChannels, + int inputHeight, + int inputWidth, + int filterHeight, + int filterWidth, + int strideHeight, + int strideWidth, + int paddingHeight, + int paddingWidth, + int outputHeight, + int outputWidth, + T* imData); +}; + +} // namespace paddle diff --git a/paddle/function/ImageExpandOp.cpp b/paddle/function/ImageExpandOp.cpp new file mode 100644 index 00000000000..426b6c8e312 --- /dev/null +++ b/paddle/function/ImageExpandOp.cpp @@ -0,0 +1,164 @@ +/* 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 "Function.h" +#include "GemmConvOp.h" + +namespace paddle { + +/* + * imData = [input_channels, input_height, input_width] + * colData = [output_height, output_width, + * input_channels, filter_height, filter_width] + */ +template +class Im2ColFunctor { +public: + void operator()(const T* imData, + int inputChannels, + int inputHeight, + int inputWidth, + int filterHeight, + int filterWidth, + int strideHeight, + int strideWidth, + int paddingHeight, + int paddingWidth, + int outputHeight, + int outputWidth, + T* colData) { + for (int outputH = 0; outputH < outputHeight; ++outputH) { + for (int outputW = 0; outputW < outputWidth; ++outputW) { + for (int channel = 0; channel < inputChannels; ++channel) { + for (int filterH = 0; filterH < filterHeight; ++filterH) { + for (int filterW = 0; filterW < filterWidth; ++filterW) { + int imRowOffset = + outputH * strideHeight + filterH - paddingHeight; + int imColOffset = outputW * strideWidth + filterW - paddingWidth; + int colDataOffset = + (((outputH * outputWidth + outputW) * inputChannels + + channel) * + filterHeight + + filterH) * + filterWidth + + filterW; + if (imRowOffset < 0 || imRowOffset >= inputHeight || + imColOffset < 0 || imColOffset >= inputWidth) { + colData[colDataOffset] = T(0); + } else { + int imDataOffset = + (channel * inputHeight + imRowOffset) * inputWidth + + imColOffset; + colData[colDataOffset] = imData[imDataOffset]; + } + } + } + } + } + } + } +}; + +/* + * \brief Converts the image data of four dimensions(NCHW) into + * a sequence data of three dimensions(NST). Where N is batch size, + * S is the length of the sequence after each image is expanded, + * T is the size of each time step in the sequence. + * + * \param inputs[0] Image data of NCHW format. + * \param outputs[0] Sequence data of NST format. + */ +template +class ImageExpandFunction : public FunctionBase { +public: + void init(const FuncConfig& config) override { + // function arguments + strides_ = config.get>("strides"); + paddings_ = config.get>("paddings"); + blocks_ = config.get>("blocks"); + + // number of inputs and outputs + numInputs_ = 1; + numOutputs_ = 1; + } + + void calc(const BufferArgs& inputs, const BufferArgs& outputs) override { + CHECK_EQ(numInputs_, inputs.size()); + CHECK_EQ(numOutputs_, outputs.size()); + const TensorShape& input = inputs[0].shape(); + const TensorShape& output = outputs[0].shape(); + // input argument should be 4-dimensional. + CHECK_EQ(input.ndims(), (size_t)4); + // output argument should be 3-dimensional. + CHECK_EQ(output.ndims(), (size_t)3); + // The batchSize of the input needs to be equal to + // the batchSize of the output. + CHECK_EQ(input[0], output[0]); + + size_t batchSize = input[0]; + size_t inputChannels = input[1]; + size_t inputHeight = input[2]; + size_t inputWidth = input[3]; + size_t seqLength = output[1]; + size_t stepSize = output[2]; + size_t outputHeight = + 1 + + (inputHeight + 2 * paddingH() - blockH() + strideH() - 1) / strideH(); + size_t outputWidth = + 1 + + (inputWidth + 2 * paddingW() - blockW() + strideW() - 1) / strideW(); + CHECK_EQ(seqLength, outputHeight * outputWidth); + CHECK_EQ(stepSize, inputChannels * blockH() * blockH()); + + real* inputData = inputs[0].data(); + real* outputData = outputs[0].data(); + Im2ColFunctor im2col; + for (size_t i = 0; i < batchSize; i++) { + im2col(inputData, + inputChannels, + inputHeight, + inputWidth, + blockH(), + blockW(), + strideH(), + strideW(), + paddingH(), + paddingW(), + outputHeight, + outputWidth, + outputData); + inputData += inputChannels * inputHeight * inputWidth; + outputData += seqLength * stepSize; + } + } + +protected: + std::vector strides_; + std::vector paddings_; + std::vector blocks_; + + inline int strideH() const { return strides_[0]; } + + inline int strideW() const { return strides_[1]; } + + inline int paddingH() const { return paddings_[0]; } + + inline int paddingW() const { return paddings_[1]; } + + inline int blockH() const { return blocks_[0]; } + + inline int blockW() const { return blocks_[1]; } +}; + +} // namespace paddle -- GitLab From 37594eae1737ad7c95016f48a385521ceb0de529 Mon Sep 17 00:00:00 2001 From: qiaolongfei Date: Tue, 13 Jun 2017 08:15:12 +0800 Subject: [PATCH 0617/3256] add paramConfig for each parameter --- go/pserver/cclient/test/main.c | 2 -- go/pserver/cclient/test/test_cclient.c | 3 +-- go/pserver/optimizer.c | 1 - go/pserver/service.go | 6 +----- paddle/trainer/NewRemoteParameterUpdater.cpp | 8 ++++++-- paddle/trainer/NewRemoteParameterUpdater.h | 1 - 6 files changed, 8 insertions(+), 13 deletions(-) diff --git a/go/pserver/cclient/test/main.c b/go/pserver/cclient/test/main.c index 72ec3590768..6adc3c9b533 100644 --- a/go/pserver/cclient/test/main.c +++ b/go/pserver/cclient/test/main.c @@ -76,7 +76,5 @@ retry: fail(); } - printf("test success!\n"); - return 0; } diff --git a/go/pserver/cclient/test/test_cclient.c b/go/pserver/cclient/test/test_cclient.c index 50ba2d5597a..9083064eeeb 100644 --- a/go/pserver/cclient/test/test_cclient.c +++ b/go/pserver/cclient/test/test_cclient.c @@ -21,7 +21,7 @@ void print_parameter(paddle_gradient* param) { printf("content_len: %d\n", param->content_len); printf("content_type: %d\n", param->element_type); int i; - for (i = 0; i < param->content_len / sizeof(real); ++i) { + for (i = 0; i < param->content_len / (int)sizeof(real); ++i) { printf("%f ", ((float*)param->content)[i]); } printf("\n\n"); @@ -110,6 +110,5 @@ retry: fail(); } - printf("test success!\n"); return 0; } diff --git a/go/pserver/optimizer.c b/go/pserver/optimizer.c index 48bbceb343b..f16ba2cbf8e 100644 --- a/go/pserver/optimizer.c +++ b/go/pserver/optimizer.c @@ -32,7 +32,6 @@ int update_SGD(void* optimizer, const void* gradient, int num_bytes) { SGD_optimizer* o = (SGD_optimizer*)optimizer; - // TODO(a simple SGD implement) float* parameter = (float*)buffer; float* grad = (float*)gradient; diff --git a/go/pserver/service.go b/go/pserver/service.go index ab814662b6b..7d2a1fea865 100644 --- a/go/pserver/service.go +++ b/go/pserver/service.go @@ -29,10 +29,6 @@ type Parameter struct { Content []byte } -func (p *Parameter) toString() { - fmt.Println(p.Name, p.ElementType, p.Content) -} - // ParameterWithConfig contains the parameter and the configuration. type ParameterWithConfig struct { Param Parameter @@ -53,7 +49,7 @@ type Service struct { // NewService creates a new service. func NewService() *Service { - s := &Service{opt: newOptimizer(sgd, 0.01)} + s := &Service{opt: newOptimizer(sgd, 0.005)} s.paramMap = make(map[string]Parameter) s.initialized = make(chan struct{}) return s diff --git a/paddle/trainer/NewRemoteParameterUpdater.cpp b/paddle/trainer/NewRemoteParameterUpdater.cpp index d554e09759c..b3655d9d025 100644 --- a/paddle/trainer/NewRemoteParameterUpdater.cpp +++ b/paddle/trainer/NewRemoteParameterUpdater.cpp @@ -31,7 +31,6 @@ NewRemoteParameterUpdater::NewRemoteParameterUpdater( void NewRemoteParameterUpdater::init( const std::vector ¶meters) { ParameterUpdater::init(parameters); - LOG(INFO) << "NewRemoteParameterUpdater init in"; for (auto ¶ : parameters_) { para->getBuf(PARAMETER_VALUE)->zeroMem(); @@ -58,7 +57,12 @@ void NewRemoteParameterUpdater::init( if (paddle_begin_init_params(parameterClient_)) { LOG(INFO) << "paddle_begin_init_params start"; for (int i = 0; i < parameterSize(); ++i) { - paddle_init_param(parameterClient_, *newParameters_[i], NULL, 0); + auto paramConfig = parameters_[i]->getConfig(); + std::string bytes = paramConfig.SerializeAsString(); + const char *array = bytes.data(); + int size = (int)bytes.size(); + paddle_init_param( + parameterClient_, *newParameters_[i], (void *)array, size); } paddle_finish_init_params(parameterClient_); LOG(INFO) << "paddle_begin_init_params done"; diff --git a/paddle/trainer/NewRemoteParameterUpdater.h b/paddle/trainer/NewRemoteParameterUpdater.h index b7c0425982b..1f22c15cef5 100644 --- a/paddle/trainer/NewRemoteParameterUpdater.h +++ b/paddle/trainer/NewRemoteParameterUpdater.h @@ -84,7 +84,6 @@ private: for (int i = 0; i < parameterSize(); ++i) { ParameterPtr param = parameters_[i]; - new_params[i]->content_len = 10; new_params[i]->element_type = PADDLE_ELEMENT_TYPE_FLOAT32; new_params[i]->name = (char*)param->getName().c_str(); new_params[i]->content = -- GitLab From 61aa1098fd13339c5be752cd1dc8f0119296c966 Mon Sep 17 00:00:00 2001 From: hedaoyuan Date: Tue, 13 Jun 2017 10:51:52 +0800 Subject: [PATCH 0618/3256] BlockExpandLayer based on the ImageExpand Function. --- paddle/function/ImageExpandOp.cpp | 9 ++- paddle/gserver/layers/BlockExpandLayer.cpp | 80 ++++++++++++++-------- 2 files changed, 60 insertions(+), 29 deletions(-) diff --git a/paddle/function/ImageExpandOp.cpp b/paddle/function/ImageExpandOp.cpp index 426b6c8e312..0c10f30bbd9 100644 --- a/paddle/function/ImageExpandOp.cpp +++ b/paddle/function/ImageExpandOp.cpp @@ -119,12 +119,17 @@ public: 1 + (inputWidth + 2 * paddingW() - blockW() + strideW() - 1) / strideW(); CHECK_EQ(seqLength, outputHeight * outputWidth); - CHECK_EQ(stepSize, inputChannels * blockH() * blockH()); + CHECK_EQ(stepSize, inputChannels * blockH() * blockW()); real* inputData = inputs[0].data(); real* outputData = outputs[0].data(); Im2ColFunctor im2col; for (size_t i = 0; i < batchSize; i++) { + // The result of im2col is [output_height, output_width, + // input_channels, filter_height, filter_width], and it is easy to + // reshape into [seqLength, stepSize], where seqLength is equal + // output_height * output_width, stepSize is equal + // input_channels * filter_height * filter_width im2col(inputData, inputChannels, inputHeight, @@ -161,4 +166,6 @@ protected: inline int blockW() const { return blocks_[1]; } }; +REGISTER_TYPED_FUNC(ImageExpand, CPU, ImageExpandFunction); + } // namespace paddle diff --git a/paddle/gserver/layers/BlockExpandLayer.cpp b/paddle/gserver/layers/BlockExpandLayer.cpp index 2bafeb92158..9760d39bb4a 100644 --- a/paddle/gserver/layers/BlockExpandLayer.cpp +++ b/paddle/gserver/layers/BlockExpandLayer.cpp @@ -37,6 +37,18 @@ bool BlockExpandLayer::init(const LayerMap& layerMap, imgSizeH_ = blockConf.img_size_y(); imgSizeW_ = blockConf.img_size_x(); + if (!useGpu_) { + std::vector strides = {(size_t)strideH_, (size_t)strideW_}; + std::vector paddings = {(size_t)paddingH_, (size_t)paddingW_}; + std::vector blocks = {(size_t)blockH_, (size_t)blockW_}; + createFunction(forward_, + "ImageExpand", + FuncConfig() + .set("strides", strides) + .set("paddings", paddings) + .set("blocks", blocks)); + } + return true; } @@ -63,10 +75,11 @@ void BlockExpandLayer::forward(PassType passType) { Layer::forward(passType); size_t batchSize = inputLayers_[0]->getOutputValue()->getHeight(); - size_t blockNum = getBlockNum(); size_t blockSize = blockH_ * blockW_ * channels_; resetOutput(blockNum * batchSize, blockSize); + // TODO(hedaoyuan): After completing the GPU version of ImageExpand, + // refactor the following code. Argument& out = getOutput(); MatrixPtr outV = getOutputValue(); @@ -78,38 +91,49 @@ void BlockExpandLayer::forward(PassType passType) { int* start = out.sequenceStartPositions->getMutableData(false); int* dims = out.cpuSequenceDims->getData(); for (size_t i = 0; i < batchSize; i++) { - outVTrans_->zeroMem(); - /* expand each block as one row */ - MatrixPtr inputTmp = - Matrix::create(input->getData() + i * input->getWidth(), - 1, - input->getWidth(), - false, - useGpu_); - outVTrans_->convExpand(*inputTmp, - imgSizeH_, - imgSizeW_, - channels_, - blockH_, - blockW_, - strideH_, - strideW_, - paddingH_, - paddingW_, - outputH_, - outputW_); - MatrixPtr outVTmp = - Matrix::create(outV->getData() + i * blockNum * blockSize, - blockNum, - blockSize, - false, - useGpu_); - outVTrans_->transpose(outVTmp, false); + if (useGpu_) { + outVTrans_->zeroMem(); + /* expand each block as one row */ + MatrixPtr inputTmp = + Matrix::create(input->getData() + i * input->getWidth(), + 1, + input->getWidth(), + false, + useGpu_); + outVTrans_->convExpand(*inputTmp, + imgSizeH_, + imgSizeW_, + channels_, + blockH_, + blockW_, + strideH_, + strideW_, + paddingH_, + paddingW_, + outputH_, + outputW_); + MatrixPtr outVTmp = + Matrix::create(outV->getData() + i * blockNum * blockSize, + blockNum, + blockSize, + false, + useGpu_); + outVTrans_->transpose(outVTmp, false); + } start[i] = i * blockNum; dims[2 * i] = outputH_; dims[2 * i + 1] = outputW_; } start[batchSize] = batchSize * blockNum; + if (!useGpu_) { + TensorShape inputShape({batchSize, channels_, imgSizeH_, imgSizeW_}); + TensorShape outputShape({batchSize, blockNum, blockSize}); + BufferArgs inputs; + BufferArgs outputs; + inputs.addArg(*getInputValue(0), inputShape); + outputs.addArg(*getOutputValue(), outputShape, ASSIGN_TO); + forward_[0]->calc(inputs, outputs); + } } void BlockExpandLayer::backward(const UpdateCallback& callback) { -- GitLab From 01d52ebf167029a165809736d78879802168152b Mon Sep 17 00:00:00 2001 From: hedaoyuan Date: Tue, 13 Jun 2017 11:05:06 +0800 Subject: [PATCH 0619/3256] Fix RowConvOpTest use CpuGpuFuncCompare. --- paddle/function/RowConvOpTest.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/paddle/function/RowConvOpTest.cpp b/paddle/function/RowConvOpTest.cpp index 1c95d3ff2cc..f52d18b0491 100644 --- a/paddle/function/RowConvOpTest.cpp +++ b/paddle/function/RowConvOpTest.cpp @@ -18,7 +18,7 @@ limitations under the License. */ namespace paddle { void testRowConvFw(size_t batchSize, size_t dim, size_t contextLength) { - FunctionCompare test("RowConv", FuncConfig()); + CpuGpuFuncCompare test("RowConv", FuncConfig()); test.addSequence(SequenceIdArg(TensorShape{batchSize})); test.addInputs(SequenceArg(VALUE_TYPE_FLOAT, TensorShape{batchSize, dim})); @@ -31,7 +31,7 @@ void testRowConvFw(size_t batchSize, size_t dim, size_t contextLength) { } void testRowConvBw(size_t batchSize, size_t dim, size_t contextLength) { - FunctionCompare test("RowConvGrad", FuncConfig()); + CpuGpuFuncCompare test("RowConvGrad", FuncConfig()); test.addSequence(SequenceIdArg(TensorShape{batchSize})); test.addInputs(SequenceArg(VALUE_TYPE_FLOAT, TensorShape{batchSize, dim})); -- GitLab From 2608c4854273e47bd0958fbb03ca67050ddfb35c Mon Sep 17 00:00:00 2001 From: hedaoyuan Date: Tue, 13 Jun 2017 11:30:36 +0800 Subject: [PATCH 0620/3256] Add test cases where the height and width (input, filter) are not equal. --- paddle/function/ConvOpTest.cpp | 87 +++++++++++++++++++++++++++++++++ paddle/function/NaiveConvOp.cpp | 2 +- 2 files changed, 88 insertions(+), 1 deletion(-) diff --git a/paddle/function/ConvOpTest.cpp b/paddle/function/ConvOpTest.cpp index 59c7238d218..dfa2f784610 100644 --- a/paddle/function/ConvOpTest.cpp +++ b/paddle/function/ConvOpTest.cpp @@ -98,25 +98,112 @@ public: } }; +// Mainly used to test cases where the height and width (input, filter) +// are not equal. +template +class ConvolutionTest2 { +public: + ConvolutionTest2(const std::string& conv1, + const std::string& conv2, + TestType type, + std::string algo = "auto") { + for (size_t batchSize : {16}) { + for (size_t inputHeight : {7, 31}) { + for (size_t inputWidth : {10, 54}) { + for (size_t filterHeight : {1, 5}) { + for (size_t filterWidth : {3, 7}) { + for (size_t inputChannels : {7}) { + for (size_t outputChannels : {32}) { + size_t stride = 1; + size_t padding = 0; + size_t outputHeight = + (inputHeight - filterHeight + 2 * padding + stride) / + stride; + size_t outputWidth = + (inputWidth - filterWidth + 2 * padding + stride) / + stride; + VLOG(3) << " batchSize=" << batchSize + << " inputChannels=" << inputChannels + << " inputHeight=" << inputHeight + << " inputWidth=" << inputWidth + << " outputChannels=" << outputChannels + << " filterHeight=" << filterHeight + << " filterWidth=" << filterWidth + << " outputHeight=" << outputHeight + << " outputWidth=" << outputWidth + << " stride=" << stride << " padding=" << padding; + + std::vector paddings = {padding, padding}; + std::vector strides = {stride, stride}; + Compare2Function test( + conv1, + conv2, + FuncConfig() + .set("paddings", paddings) + .set("strides", strides) + .set("groups", (size_t)1) + .set("algo", algo)); + + TensorShape input{ + batchSize, inputChannels, inputHeight, inputWidth}; + TensorShape filter{ + outputChannels, inputChannels, filterHeight, filterWidth}; + TensorShape output{ + batchSize, outputChannels, outputHeight, outputWidth}; + + if (type == kForwardTest) { + test.addInputs(BufferArg(VALUE_TYPE_FLOAT, input)); + test.addInputs(BufferArg(VALUE_TYPE_FLOAT, filter)); + test.addOutputs(BufferArg(VALUE_TYPE_FLOAT, output)); + test.run(); + } else if (type == kBackwardInputTest) { + test.addInputs(BufferArg(VALUE_TYPE_FLOAT, output)); + test.addInputs(BufferArg(VALUE_TYPE_FLOAT, filter)); + test.addOutputs(BufferArg(VALUE_TYPE_FLOAT, input), ADD_TO); + test.run(); + } else if (type == kBackwardFilterTest) { + test.addInputs(BufferArg(VALUE_TYPE_FLOAT, output)); + test.addInputs(BufferArg(VALUE_TYPE_FLOAT, input)); + test.addOutputs(BufferArg(VALUE_TYPE_FLOAT, filter)); + test.run(); + } + } + } + } + } + } + } + } + } +}; + TEST(Forward, GEMM) { ConvolutionTest test( "NaiveConv-CPU", "GemmConv-CPU", kForwardTest); + ConvolutionTest2 test2( + "NaiveConv-CPU", "GemmConv-CPU", kForwardTest); } #ifndef PADDLE_ONLY_CPU TEST(Forward, GEMM2) { ConvolutionTest test( "GemmConv-CPU", "GemmConv-GPU", kForwardTest); + ConvolutionTest2 test2( + "GemmConv-CPU", "GemmConv-GPU", kForwardTest); } TEST(BackwardInput, GEMM) { ConvolutionTest test( "GemmConvGradInput-CPU", "GemmConvGradInput-GPU", kBackwardInputTest); + ConvolutionTest2 test2( + "GemmConvGradInput-CPU", "GemmConvGradInput-GPU", kBackwardInputTest); } TEST(BackwardFilter, GEMM) { ConvolutionTest test( "GemmConvGradFilter-CPU", "GemmConvGradFilter-GPU", kBackwardFilterTest); + ConvolutionTest2 test2( + "GemmConvGradFilter-CPU", "GemmConvGradFilter-GPU", kBackwardFilterTest); } #endif diff --git a/paddle/function/NaiveConvOp.cpp b/paddle/function/NaiveConvOp.cpp index 94aba253e3e..1d204f99e0e 100644 --- a/paddle/function/NaiveConvOp.cpp +++ b/paddle/function/NaiveConvOp.cpp @@ -104,7 +104,7 @@ public: size_t inputHeight = inputs[0].shape()[2]; size_t inputWidth = inputs[0].shape()[3]; size_t filterHeight = inputs[1].shape()[2]; - size_t filterWidth = inputs[1].shape()[2]; + size_t filterWidth = inputs[1].shape()[3]; size_t outputChannels = outputs[0].shape()[1]; size_t outputHeight = outputs[0].shape()[2]; size_t outputWidth = outputs[0].shape()[3]; -- GitLab From 5b5346ecc74fdd804ccb26c9de35bdd8d9a9a187 Mon Sep 17 00:00:00 2001 From: yangyaming Date: Tue, 13 Jun 2017 14:18:19 +0800 Subject: [PATCH 0621/3256] Follow comments. --- paddle/gserver/layers/DetectionUtil.cpp | 83 ++++++++++++++----------- paddle/gserver/layers/DetectionUtil.h | 28 +++++---- 2 files changed, 61 insertions(+), 50 deletions(-) diff --git a/paddle/gserver/layers/DetectionUtil.cpp b/paddle/gserver/layers/DetectionUtil.cpp index f301d2d332b..e9295edb6dc 100644 --- a/paddle/gserver/layers/DetectionUtil.cpp +++ b/paddle/gserver/layers/DetectionUtil.cpp @@ -16,29 +16,33 @@ limitations under the License. */ namespace paddle { -size_t appendWithPermute(const MatrixPtr inMatrix, +size_t appendWithPermute(const Matrix& inMatrix, size_t height, size_t width, size_t outTotalSize, size_t outOffset, size_t batchSize, - MatrixPtr outMatrix, + Matrix& outMatrix, PermMode permMode, bool useGpu) { - if (permMode == NCHWTONHWC) { - size_t inElementCnt = inMatrix->getElementCnt(); + if (permMode == kNCHWToNHWC) { + size_t inElementCnt = inMatrix.getElementCnt(); size_t channels = inElementCnt / (height * width * batchSize); size_t imgSize = height * width; for (size_t i = 0; i < batchSize; ++i) { size_t offset = i * (outTotalSize / batchSize) + outOffset; - const MatrixPtr inTmp = - Matrix::create(inMatrix->getData() + i * channels * imgSize, - channels, + const MatrixPtr inTmp = Matrix::create( + const_cast(inMatrix.getData()) + i * channels * imgSize, + channels, + imgSize, + false, + useGpu); + MatrixPtr outTmp = + Matrix::create(const_cast(outMatrix.getData()) + offset, imgSize, + channels, false, useGpu); - MatrixPtr outTmp = Matrix::create( - outMatrix->getData() + offset, imgSize, channels, false, useGpu); inTmp->transpose(outTmp, false); } return channels * imgSize; @@ -47,29 +51,33 @@ size_t appendWithPermute(const MatrixPtr inMatrix, } } -size_t decomposeWithPermute(const MatrixPtr inMatrix, +size_t decomposeWithPermute(const Matrix& inMatrix, size_t height, size_t width, size_t inTotalSize, size_t inOffset, size_t batchSize, - MatrixPtr outMatrix, + Matrix& outMatrix, PermMode permMode, bool useGpu) { - if (permMode == NHWCTONCHW) { - size_t outElementCnt = outMatrix->getElementCnt(); + if (permMode == kNHWCToNCHW) { + size_t outElementCnt = outMatrix.getElementCnt(); size_t channels = outElementCnt / (height * width * batchSize); size_t imgSize = height * width; for (size_t i = 0; i < batchSize; ++i) { size_t offset = i * (inTotalSize / batchSize) + inOffset; - const MatrixPtr inTmp = Matrix::create( - inMatrix->getData() + offset, imgSize, channels, false, useGpu); - MatrixPtr outTmp = - Matrix::create(outMatrix->getData() + i * channels * imgSize, - channels, + const MatrixPtr inTmp = + Matrix::create(const_cast(inMatrix.getData()) + offset, imgSize, + channels, false, useGpu); + MatrixPtr outTmp = Matrix::create( + const_cast(outMatrix.getData()) + i * channels * imgSize, + channels, + imgSize, + false, + useGpu); inTmp->transpose(outTmp, false); } return channels * imgSize; @@ -99,9 +107,10 @@ real jaccardOverlap(const NormalizedBBox& bbox1, const NormalizedBBox& bbox2) { } } -vector encodeBBoxWithVar(const NormalizedBBox& priorBBox, - const vector priorBBoxVar, - const NormalizedBBox& gtBBox) { +void encodeBBoxWithVar(const NormalizedBBox& priorBBox, + const vector priorBBoxVar, + const NormalizedBBox& gtBBox, + vector& outVec) { real priorBBoxWidth = priorBBox.getWidth(); real priorBBoxHeight = priorBBox.getHeight(); real priorBBoxCenterX = priorBBox.getCenterX(); @@ -112,17 +121,15 @@ vector encodeBBoxWithVar(const NormalizedBBox& priorBBox, real gtBBoxCenterX = gtBBox.getCenterX(); real gtBBoxCenterY = gtBBox.getCenterY(); - vector offsetParam; - offsetParam.push_back((gtBBoxCenterX - priorBBoxCenterX) / priorBBoxWidth / - priorBBoxVar[0]); - offsetParam.push_back((gtBBoxCenterY - priorBBoxCenterY) / priorBBoxHeight / - priorBBoxVar[1]); - offsetParam.push_back(std::log(std::fabs(gtBBoxWidth / priorBBoxWidth)) / - priorBBoxVar[2]); - offsetParam.push_back(std::log(std::fabs(gtBBoxHeight / priorBBoxHeight)) / - priorBBoxVar[3]); - - return offsetParam; + outVec.clear(); + outVec.push_back((gtBBoxCenterX - priorBBoxCenterX) / priorBBoxWidth / + priorBBoxVar[0]); + outVec.push_back((gtBBoxCenterY - priorBBoxCenterY) / priorBBoxHeight / + priorBBoxVar[1]); + outVec.push_back(std::log(std::fabs(gtBBoxWidth / priorBBoxWidth)) / + priorBBoxVar[2]); + outVec.push_back(std::log(std::fabs(gtBBoxHeight / priorBBoxHeight)) / + priorBBoxVar[3]); } NormalizedBBox decodeBBoxWithVar(const NormalizedBBox& priorBBox, @@ -318,9 +325,9 @@ void matchBBox(const vector& priorBBoxes, } pair generateMatchIndices( - const MatrixPtr priorValue, + const Matrix& priorValue, const size_t numPriorBBoxes, - const MatrixPtr gtValue, + const Matrix& gtValue, const int* gtStartPosPtr, const size_t seqNum, const vector>& maxConfScore, @@ -331,7 +338,7 @@ pair generateMatchIndices( vector>* matchIndicesVecPtr, vector>* negIndicesVecPtr) { vector priorBBoxes; // share same prior bboxes - getBBoxFromPriorData(priorValue->getData(), numPriorBBoxes, priorBBoxes); + getBBoxFromPriorData(priorValue.getData(), numPriorBBoxes, priorBBoxes); size_t totalPos = 0; size_t totalNeg = 0; for (size_t n = 0; n < batchSize; ++n) { @@ -349,7 +356,7 @@ pair generateMatchIndices( } vector gtBBoxes; getBBoxFromLabelData( - gtValue->getData() + gtStartPosPtr[n] * 6, numGTBBoxes, gtBBoxes); + gtValue.getData() + gtStartPosPtr[n] * 6, numGTBBoxes, gtBBoxes); matchBBox( priorBBoxes, gtBBoxes, overlapThreshold, &matchIndices, &matchOverlaps); @@ -523,7 +530,7 @@ void getDetectionOutput(const real* confData, const size_t batchSize, const vector>>& allIndices, const vector>& allDecodedBBoxes, - MatrixPtr out) { + Matrix& out) { MatrixPtr outBuffer; Matrix::resizeOrCreate(outBuffer, numKept, 7, false, false); real* bufferData = outBuffer->getData(); @@ -550,7 +557,7 @@ void getDetectionOutput(const real* confData, } } } - out->copyFrom(bufferData, numKept * 7); + out.copyFrom(bufferData, numKept * 7); } NormalizedBBox clipBBox(const NormalizedBBox& bbox) { diff --git a/paddle/gserver/layers/DetectionUtil.h b/paddle/gserver/layers/DetectionUtil.h index e30cfa8f082..263dea0013b 100644 --- a/paddle/gserver/layers/DetectionUtil.h +++ b/paddle/gserver/layers/DetectionUtil.h @@ -42,10 +42,12 @@ struct BBoxBase { T getSize() const { return getWidth() * getHeight(); } + // coordinate of bounding box T xMin; T yMin; T xMax; T yMax; + // whether difficult object (e.g. object with heavy occlusion is difficult) bool isDifficult; }; @@ -53,31 +55,31 @@ struct NormalizedBBox : BBoxBase { NormalizedBBox() : BBoxBase() {} }; -enum PermMode { NCHWTONHWC, NHWCTONCHW }; +enum PermMode { kNCHWToNHWC, kNHWCToNCHW }; /** * @brief First permute input maxtrix then append to output matrix */ -size_t appendWithPermute(const MatrixPtr inMatrix, +size_t appendWithPermute(const Matrix& inMatrix, size_t height, size_t width, size_t outTotalSize, size_t outOffset, size_t batchSize, - MatrixPtr outMatrix, + Matrix& outMatrix, PermMode permMode, bool useGpu); /** * @brief First permute input maxtrix then decompose to output */ -size_t decomposeWithPermute(const MatrixPtr inMatrix, +size_t decomposeWithPermute(const Matrix& inMatrix, size_t height, size_t width, size_t totalSize, size_t offset, size_t batchSize, - MatrixPtr outMatrix, + Matrix& outMatrix, PermMode permMode, bool useGpu); @@ -89,15 +91,17 @@ size_t decomposeWithPermute(const MatrixPtr inMatrix, real jaccardOverlap(const NormalizedBBox& bbox1, const NormalizedBBox& bbox2); /** - * @brief Compute offset parameters between prior bbox and groundtruth bbox + * @brief Compute offset parameters between prior bbox and ground truth bbox * and variances of prior bbox are considered * @param priorBBox Input prior bbox * @param priorBBoxVar Variance parameters of prior bbox * @param gtBBox Groundtruth bbox + * @param outVec Output vector */ -vector encodeBBoxWithVar(const NormalizedBBox& priorBBox, - const vector priorBBoxVar, - const NormalizedBBox& gtBBox); +void encodeBBoxWithVar(const NormalizedBBox& priorBBox, + const vector priorBBoxVar, + const NormalizedBBox& gtBBox, + vector& outVec); /** * @brief Decode prior bbox with offset parameters @@ -194,9 +198,9 @@ negative examples * @param negIndicesVecPtr Save indices of negative prior bbox */ pair generateMatchIndices( - const MatrixPtr priorValue, + const Matrix& priorValue, const size_t numPriorBBoxes, - const MatrixPtr gtValue, + const Matrix& gtValue, const int* gtStartPosPtr, const size_t seqNum, const vector>& maxConfScore, @@ -298,7 +302,7 @@ void getDetectionOutput(const real* confData, const size_t batchSize, const vector>>& allIndices, const vector>& allDecodedBBoxes, - MatrixPtr out); + Matrix& out); NormalizedBBox clipBBox(const NormalizedBBox& bbox); -- GitLab From 2acb84fe70104980c902b252a26a526a3d943c2a Mon Sep 17 00:00:00 2001 From: hedaoyuan Date: Tue, 13 Jun 2017 14:18:20 +0800 Subject: [PATCH 0622/3256] Add ImageExpandGrad Function. --- paddle/function/GemmConvOp.h | 1 + paddle/function/ImageExpandOp.cpp | 224 +++++++++++++++++---- paddle/gserver/layers/BlockExpandLayer.cpp | 89 ++++---- paddle/gserver/layers/BlockExpandLayer.h | 3 + 4 files changed, 237 insertions(+), 80 deletions(-) diff --git a/paddle/function/GemmConvOp.h b/paddle/function/GemmConvOp.h index 25d2e220bfb..f724643f35a 100644 --- a/paddle/function/GemmConvOp.h +++ b/paddle/function/GemmConvOp.h @@ -44,6 +44,7 @@ enum ColFormat { kCFO = 0, kOCF = 1 }; * input_channels, * filter_height, * filter_width] + * TODO(hedaoyuan): Refactor the arguments of the interface with TensorShape. */ template class Im2ColFunctor { diff --git a/paddle/function/ImageExpandOp.cpp b/paddle/function/ImageExpandOp.cpp index 0c10f30bbd9..4d8c25ffcda 100644 --- a/paddle/function/ImageExpandOp.cpp +++ b/paddle/function/ImageExpandOp.cpp @@ -70,16 +70,67 @@ public: } }; +template +class Col2ImFunctor { +public: + void operator()(const T* colData, + int inputChannels, + int inputHeight, + int inputWidth, + int filterHeight, + int filterWidth, + int strideHeight, + int strideWidth, + int paddingHeight, + int paddingWidth, + int outputHeight, + int outputWidth, + T* imData) { + for (int outputH = 0; outputH < outputHeight; ++outputH) { + for (int outputW = 0; outputW < outputWidth; ++outputW) { + for (int channel = 0; channel < inputChannels; ++channel) { + for (int filterH = 0; filterH < filterHeight; ++filterH) { + for (int filterW = 0; filterW < filterWidth; ++filterW) { + int imRowOffset = + outputH * strideHeight + filterH - paddingHeight; + int imColOffset = outputW * strideWidth + filterW - paddingWidth; + int colDataOffset = + (((outputH * outputWidth + outputW) * inputChannels + + channel) * + filterHeight + + filterH) * + filterWidth + + filterW; + if (imRowOffset >= 0 && imRowOffset < inputHeight && + imColOffset >= 0 && imColOffset < inputWidth) { + int imDataOffset = + (channel * inputHeight + imRowOffset) * inputWidth + + imColOffset; + imData[imDataOffset] += colData[colDataOffset]; + } + } + } + } + } + } + } +}; + /* * \brief Converts the image data of four dimensions(NCHW) into - * a sequence data of three dimensions(NST). Where N is batch size, - * S is the length of the sequence after each image is expanded, - * T is the size of each time step in the sequence. + * a sequence data of three dimensions(NST) in the forward calculation, + * which is reversed in the backward calculation. + * Where N is batch size, S is the length of the sequence after each + * image is expanded, T is the size of each time step in the sequence. * + * Arguments in forward function: * \param inputs[0] Image data of NCHW format. * \param outputs[0] Sequence data of NST format. + * + * Arguments in backward function: + * \param inputs[0] Sequence data of NST format. + * \param outputs[0] Image data of NCHW format. */ -template class ImageExpandFunction : public FunctionBase { public: void init(const FuncConfig& config) override { @@ -93,25 +144,27 @@ public: numOutputs_ = 1; } - void calc(const BufferArgs& inputs, const BufferArgs& outputs) override { - CHECK_EQ(numInputs_, inputs.size()); - CHECK_EQ(numOutputs_, outputs.size()); - const TensorShape& input = inputs[0].shape(); - const TensorShape& output = outputs[0].shape(); - // input argument should be 4-dimensional. - CHECK_EQ(input.ndims(), (size_t)4); - // output argument should be 3-dimensional. - CHECK_EQ(output.ndims(), (size_t)3); - // The batchSize of the input needs to be equal to - // the batchSize of the output. - CHECK_EQ(input[0], output[0]); - - size_t batchSize = input[0]; - size_t inputChannels = input[1]; - size_t inputHeight = input[2]; - size_t inputWidth = input[3]; - size_t seqLength = output[1]; - size_t stepSize = output[2]; + virtual void calc(const BufferArgs& inputs, const BufferArgs& outputs) {} + + void check(const TensorShape& image, const TensorShape& sequence) { + // image shape should be 4-dimensional. + CHECK_EQ(image.ndims(), (size_t)4); + // sequence shape should be 3-dimensional. + CHECK_EQ(sequence.ndims(), (size_t)3); + // The batchSize of the image needs to be equal to + // the batchSize of the sequence. + CHECK_EQ(image[0], sequence[0]); + } + + // Calculate the shape of colData based on the shape of the image + // and the shape of the sequence. + TensorShape getColShape(const TensorShape& image, + const TensorShape& sequence) { + size_t inputChannels = image[1]; + size_t inputHeight = image[2]; + size_t inputWidth = image[3]; + size_t seqLength = sequence[1]; + size_t stepSize = sequence[2]; size_t outputHeight = 1 + (inputHeight + 2 * paddingH() - blockH() + strideH() - 1) / strideH(); @@ -121,8 +174,59 @@ public: CHECK_EQ(seqLength, outputHeight * outputWidth); CHECK_EQ(stepSize, inputChannels * blockH() * blockW()); - real* inputData = inputs[0].data(); - real* outputData = outputs[0].data(); + // [output_height, output_width, + // input_channels, filter_height, filter_width] + return TensorShape({outputHeight, + outputWidth, + inputChannels, + (size_t)blockH(), + (size_t)blockW()}); + } + +protected: + std::vector strides_; + std::vector paddings_; + std::vector blocks_; + + inline int strideH() const { return strides_[0]; } + + inline int strideW() const { return strides_[1]; } + + inline int paddingH() const { return paddings_[0]; } + + inline int paddingW() const { return paddings_[1]; } + + inline int blockH() const { return blocks_[0]; } + + inline int blockW() const { return blocks_[1]; } +}; + +template +class ImageExpandForward : public ImageExpandFunction { +public: + void init(const FuncConfig& config) override { + ImageExpandFunction::init(config); + } + + void calc(const BufferArgs& inputs, const BufferArgs& outputs) override { + CHECK_EQ(numInputs_, inputs.size()); + CHECK_EQ(numOutputs_, outputs.size()); + const TensorShape& image = inputs[0].shape(); + const TensorShape& sequence = outputs[0].shape(); + check(image, sequence); + + TensorShape colShape = getColShape(image, sequence); + size_t batchSize = image[0]; + size_t inputChannels = image[1]; + size_t inputHeight = image[2]; + size_t inputWidth = image[3]; + size_t seqLength = sequence[1]; + size_t stepSize = sequence[2]; + size_t outputHeight = colShape[0]; + size_t outputWidth = colShape[1]; + + real* imageData = inputs[0].data(); + real* seqData = outputs[0].data(); Im2ColFunctor im2col; for (size_t i = 0; i < batchSize; i++) { // The result of im2col is [output_height, output_width, @@ -130,7 +234,7 @@ public: // reshape into [seqLength, stepSize], where seqLength is equal // output_height * output_width, stepSize is equal // input_channels * filter_height * filter_width - im2col(inputData, + im2col(imageData, inputChannels, inputHeight, inputWidth, @@ -142,30 +246,64 @@ public: paddingW(), outputHeight, outputWidth, - outputData); - inputData += inputChannels * inputHeight * inputWidth; - outputData += seqLength * stepSize; + seqData); + imageData += inputChannels * inputHeight * inputWidth; + seqData += seqLength * stepSize; } } +}; -protected: - std::vector strides_; - std::vector paddings_; - std::vector blocks_; - - inline int strideH() const { return strides_[0]; } - - inline int strideW() const { return strides_[1]; } - - inline int paddingH() const { return paddings_[0]; } +template +class ImageExpandBackward : public ImageExpandFunction { +public: + void init(const FuncConfig& config) override { + ImageExpandFunction::init(config); + } - inline int paddingW() const { return paddings_[1]; } + void calc(const BufferArgs& inputs, const BufferArgs& outputs) override { + CHECK_EQ(numInputs_, inputs.size()); + CHECK_EQ(numOutputs_, outputs.size()); + // Since the implementation of Col2ImFunctor is ADD_TO, + // this function only supports ADD_TO mode. + CHECK_EQ(outputs[0].getArgType(), ADD_TO); + const TensorShape& image = outputs[0].shape(); + const TensorShape& sequence = inputs[0].shape(); + check(image, sequence); - inline int blockH() const { return blocks_[0]; } + TensorShape colShape = getColShape(image, sequence); + size_t batchSize = image[0]; + size_t inputChannels = image[1]; + size_t inputHeight = image[2]; + size_t inputWidth = image[3]; + size_t seqLength = sequence[1]; + size_t stepSize = sequence[2]; + size_t outputHeight = colShape[0]; + size_t outputWidth = colShape[1]; - inline int blockW() const { return blocks_[1]; } + real* imageData = outputs[0].data(); + real* seqData = inputs[0].data(); + Col2ImFunctor col2im; + for (size_t i = 0; i < batchSize; i++) { + col2im(seqData, + inputChannels, + inputHeight, + inputWidth, + blockH(), + blockW(), + strideH(), + strideW(), + paddingH(), + paddingW(), + outputHeight, + outputWidth, + imageData); + imageData += inputChannels * inputHeight * inputWidth; + seqData += seqLength * stepSize; + } + } }; -REGISTER_TYPED_FUNC(ImageExpand, CPU, ImageExpandFunction); +REGISTER_TYPED_FUNC(ImageExpand, CPU, ImageExpandForward); +REGISTER_TYPED_FUNC(ImageExpandGrad, CPU, ImageExpandBackward); } // namespace paddle diff --git a/paddle/gserver/layers/BlockExpandLayer.cpp b/paddle/gserver/layers/BlockExpandLayer.cpp index 9760d39bb4a..c8d0b21c875 100644 --- a/paddle/gserver/layers/BlockExpandLayer.cpp +++ b/paddle/gserver/layers/BlockExpandLayer.cpp @@ -47,6 +47,12 @@ bool BlockExpandLayer::init(const LayerMap& layerMap, .set("strides", strides) .set("paddings", paddings) .set("blocks", blocks)); + createFunction(backward_, + "ImageExpandGrad", + FuncConfig() + .set("strides", strides) + .set("paddings", paddings) + .set("blocks", blocks)); } return true; @@ -126,12 +132,12 @@ void BlockExpandLayer::forward(PassType passType) { } start[batchSize] = batchSize * blockNum; if (!useGpu_) { - TensorShape inputShape({batchSize, channels_, imgSizeH_, imgSizeW_}); - TensorShape outputShape({batchSize, blockNum, blockSize}); + inputShape_ = TensorShape({batchSize, channels_, imgSizeH_, imgSizeW_}); + outputShape_ = TensorShape({batchSize, blockNum, blockSize}); BufferArgs inputs; BufferArgs outputs; - inputs.addArg(*getInputValue(0), inputShape); - outputs.addArg(*getOutputValue(), outputShape, ASSIGN_TO); + inputs.addArg(*getInputValue(0), inputShape_); + outputs.addArg(*getOutputValue(), outputShape_, ASSIGN_TO); forward_[0]->calc(inputs, outputs); } } @@ -144,41 +150,50 @@ void BlockExpandLayer::backward(const UpdateCallback& callback) { if (!preGrad) { return; } - MatrixPtr grad = getOutputGrad(); - MatrixPtr gradTrans = Matrix::create(blockSize, blockNum, false, useGpu_); - size_t batchSize = preGrad->getHeight(); - CHECK_EQ(batchSize * blockNum, grad->getHeight()); - CHECK_EQ(blockSize, grad->getWidth()); + if (useGpu_) { + MatrixPtr grad = getOutputGrad(); + MatrixPtr gradTrans = Matrix::create(blockSize, blockNum, false, useGpu_); + size_t batchSize = preGrad->getHeight(); - for (size_t i = 0; i < batchSize; i++) { - MatrixPtr gradTmp = - Matrix::create(grad->getData() + i * blockNum * blockSize, - blockNum, - blockSize, - false, - useGpu_); - gradTmp->transpose(gradTrans, false); - MatrixPtr preGradTmp = - Matrix::create(preGrad->getData() + i * preGrad->getWidth(), - 1, - preGrad->getWidth(), - false, - useGpu_); - preGradTmp->convShrink(*gradTrans, - imgSizeH_, - imgSizeW_, - channels_, - blockH_, - blockW_, - strideH_, - strideW_, - paddingH_, - paddingW_, - outputH_, - outputW_, - 1.0, - 1.0); + CHECK_EQ(batchSize * blockNum, grad->getHeight()); + CHECK_EQ(blockSize, grad->getWidth()); + + for (size_t i = 0; i < batchSize; i++) { + MatrixPtr gradTmp = + Matrix::create(grad->getData() + i * blockNum * blockSize, + blockNum, + blockSize, + false, + useGpu_); + gradTmp->transpose(gradTrans, false); + MatrixPtr preGradTmp = + Matrix::create(preGrad->getData() + i * preGrad->getWidth(), + 1, + preGrad->getWidth(), + false, + useGpu_); + preGradTmp->convShrink(*gradTrans, + imgSizeH_, + imgSizeW_, + channels_, + blockH_, + blockW_, + strideH_, + strideW_, + paddingH_, + paddingW_, + outputH_, + outputW_, + 1.0, + 1.0); + } + } else { + BufferArgs inputs; + BufferArgs outputs; + inputs.addArg(*getOutputGrad(), outputShape_); + outputs.addArg(*getInputGrad(0), inputShape_, ADD_TO); + backward_[0]->calc(inputs, outputs); } } diff --git a/paddle/gserver/layers/BlockExpandLayer.h b/paddle/gserver/layers/BlockExpandLayer.h index 8f347400e60..edda0e0b630 100644 --- a/paddle/gserver/layers/BlockExpandLayer.h +++ b/paddle/gserver/layers/BlockExpandLayer.h @@ -53,6 +53,9 @@ protected: /// auxiliary variable, which saves the transposed output value. MatrixPtr outVTrans_; + TensorShape inputShape_; + TensorShape outputShape_; + public: explicit BlockExpandLayer(const LayerConfig& config) : Layer(config) {} -- GitLab From 77c4dce75902b06229f7cb91e6500a98d0eec82a Mon Sep 17 00:00:00 2001 From: gongweibao Date: Tue, 13 Jun 2017 14:41:56 +0800 Subject: [PATCH 0623/3256] modify 10000 to 1000 --- python/paddle/v2/dataset/common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/paddle/v2/dataset/common.py b/python/paddle/v2/dataset/common.py index 8023fa3cf88..9c614914b5e 100644 --- a/python/paddle/v2/dataset/common.py +++ b/python/paddle/v2/dataset/common.py @@ -155,7 +155,7 @@ def convert(output_path, reader, num_shards, name_prefix, - max_lines_to_shuffle=10000): + max_lines_to_shuffle=1000): import recordio import cPickle as pickle import random -- GitLab From 0672d330a3d4f55c54ce8568c974a10c02ba40cf Mon Sep 17 00:00:00 2001 From: hedaoyuan Date: Tue, 13 Jun 2017 15:42:17 +0800 Subject: [PATCH 0624/3256] Use the TensorShape to reconstruct the arguments of the Im2ColFunctor and Col2ImFunctor interfaces. --- paddle/function/Im2Col.h | 92 +++++++++++++++++++++++ paddle/function/ImageExpandOp.cpp | 120 +++++++++++++----------------- 2 files changed, 145 insertions(+), 67 deletions(-) create mode 100644 paddle/function/Im2Col.h diff --git a/paddle/function/Im2Col.h b/paddle/function/Im2Col.h new file mode 100644 index 00000000000..d461ec7510b --- /dev/null +++ b/paddle/function/Im2Col.h @@ -0,0 +1,92 @@ +/* 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 + +namespace paddle { + +/* The storage format of the coldata in the Im2ColFunctor and Col2ImFunctor. */ +enum ColFormat { kCFO = 0, kOCF = 1 }; + +/* + * \brief Converts the image data of three dimensions(CHW) into a colData of + * five dimensions in the Im2ColFunctor calculation, + * And in the Col2ImFunctor calculation, it is reversed. + * + * \param imData Image data of NCHW format. + * The shape of imData is: + * [inputChannels, inputHeight, inputWidth]. + * \param colData colData data. + * + * If the template argument Format is kCFO, the shape of colData is: + * [inputChannels, filterHeight, filterWidth, outputHeight, outputWidth] + * So, it is easy to reshape into a convolution matrix for convolution + * calculation based on matrix multiplication. + * The shape of convolution matrix is [height, width], where the height is equal + * inputChannels * filterHeight * filterWidth, and the width is equal + * outputHeight * outputWidth. + * + * Reshape: + * shape of colData shape of sequence + * [inputChannels, + * filterHeight, + * filterWidth, ======> [seqLength, stepSize] + * outputHeight, + * outputWidth] + * + * If the template argument Format is kOCF, the shape of colData is: + * [outputHeight, outputWidth, inputChannels, filterHeight, filterWidth] + * So, it is easy to reshape into a sequence matrix for rnn calculation. + * The shape of sequence matrix is [seqLength, stepSize], where the seqLength + * is equal outputHeight * outputWidth, and the stepSize is equal + * inputChannels * filterHeight * filterWidth. + * + * Reshape: + * shape of colData shape of sequence + * [outputHeight, + * outputWidth, + * inputChannels, ======> [seqLength, stepSize] + * filterHeight, + * filterWidth] + * + * \note The caller needs to ensure that imShape.inputChannels is equal to + * colShape.inputChannels. + */ +template +class Im2ColFunctor { +public: + void operator()(const T* imData, + const TensorShape& imShape, + T* colData, + const TensorShape& colShape, + int strideHeight, + int strideWidth, + int paddingHeight, + int paddingWidth); +}; + +template +class Col2ImFunctor { +public: + void operator()(T* imData, + const TensorShape& imShape, + const T* colData, + const TensorShape& colShape, + int strideHeight, + int strideWidth, + int paddingHeight, + int paddingWidth); +}; + +} // namespace paddle diff --git a/paddle/function/ImageExpandOp.cpp b/paddle/function/ImageExpandOp.cpp index 4d8c25ffcda..ad34967bd65 100644 --- a/paddle/function/ImageExpandOp.cpp +++ b/paddle/function/ImageExpandOp.cpp @@ -13,31 +13,33 @@ See the License for the specific language governing permissions and limitations under the License. */ #include "Function.h" -#include "GemmConvOp.h" +#include "Im2Col.h" namespace paddle { /* - * imData = [input_channels, input_height, input_width] - * colData = [output_height, output_width, - * input_channels, filter_height, filter_width] + * imShape = [inputChannels, inputHeight, inputWidth] + * colShape = + * [outputHeight, outputWidth, inputChannels, filterHeight, filterWidth] */ template class Im2ColFunctor { public: void operator()(const T* imData, - int inputChannels, - int inputHeight, - int inputWidth, - int filterHeight, - int filterWidth, + const TensorShape& imShape, + T* colData, + const TensorShape& colShape, int strideHeight, int strideWidth, int paddingHeight, - int paddingWidth, - int outputHeight, - int outputWidth, - T* colData) { + int paddingWidth) { + int inputChannels = imShape[0]; + int inputHeight = imShape[1]; + int inputWidth = imShape[2]; + int filterHeight = colShape[3]; + int filterWidth = colShape[4]; + int outputHeight = colShape[0]; + int outputWidth = colShape[1]; for (int outputH = 0; outputH < outputHeight; ++outputH) { for (int outputW = 0; outputW < outputWidth; ++outputW) { for (int channel = 0; channel < inputChannels; ++channel) { @@ -55,7 +57,7 @@ public: filterW; if (imRowOffset < 0 || imRowOffset >= inputHeight || imColOffset < 0 || imColOffset >= inputWidth) { - colData[colDataOffset] = T(0); + colData[colDataOffset] = float(0); } else { int imDataOffset = (channel * inputHeight + imRowOffset) * inputWidth + @@ -70,22 +72,29 @@ public: } }; +/* + * imShape = [inputChannels, inputHeight, inputWidth] + * colShape = + * [outputHeight, outputWidth, inputChannels, filterHeight, filterWidth] + */ template class Col2ImFunctor { public: - void operator()(const T* colData, - int inputChannels, - int inputHeight, - int inputWidth, - int filterHeight, - int filterWidth, + void operator()(T* imData, + const TensorShape& imShape, + const T* colData, + const TensorShape& colShape, int strideHeight, int strideWidth, int paddingHeight, - int paddingWidth, - int outputHeight, - int outputWidth, - T* imData) { + int paddingWidth) { + int inputChannels = imShape[0]; + int inputHeight = imShape[1]; + int inputWidth = imShape[2]; + int filterHeight = colShape[3]; + int filterWidth = colShape[4]; + int outputHeight = colShape[0]; + int outputWidth = colShape[1]; for (int outputH = 0; outputH < outputHeight; ++outputH) { for (int outputW = 0; outputW < outputWidth; ++outputW) { for (int channel = 0; channel < inputChannels; ++channel) { @@ -146,7 +155,7 @@ public: virtual void calc(const BufferArgs& inputs, const BufferArgs& outputs) {} - void check(const TensorShape& image, const TensorShape& sequence) { + void check(const TensorShape& image, const TensorShape& sequence) const { // image shape should be 4-dimensional. CHECK_EQ(image.ndims(), (size_t)4); // sequence shape should be 3-dimensional. @@ -159,7 +168,7 @@ public: // Calculate the shape of colData based on the shape of the image // and the shape of the sequence. TensorShape getColShape(const TensorShape& image, - const TensorShape& sequence) { + const TensorShape& sequence) const { size_t inputChannels = image[1]; size_t inputHeight = image[2]; size_t inputWidth = image[3]; @@ -174,8 +183,7 @@ public: CHECK_EQ(seqLength, outputHeight * outputWidth); CHECK_EQ(stepSize, inputChannels * blockH() * blockW()); - // [output_height, output_width, - // input_channels, filter_height, filter_width] + // [outputHeight, outputWidth, inputChannels, filterHeight, filterWidth] return TensorShape({outputHeight, outputWidth, inputChannels, @@ -215,40 +223,29 @@ public: const TensorShape& sequence = outputs[0].shape(); check(image, sequence); + TensorShape imShape = TensorShape({image[1], image[2], image[3]}); TensorShape colShape = getColShape(image, sequence); size_t batchSize = image[0]; - size_t inputChannels = image[1]; - size_t inputHeight = image[2]; - size_t inputWidth = image[3]; - size_t seqLength = sequence[1]; - size_t stepSize = sequence[2]; - size_t outputHeight = colShape[0]; - size_t outputWidth = colShape[1]; real* imageData = inputs[0].data(); real* seqData = outputs[0].data(); Im2ColFunctor im2col; for (size_t i = 0; i < batchSize; i++) { - // The result of im2col is [output_height, output_width, - // input_channels, filter_height, filter_width], and it is easy to + // The result of im2col is [outputHeight, outputWidth, + // inputChannels, filterHeight, filterWidth], and it is easy to // reshape into [seqLength, stepSize], where seqLength is equal // output_height * output_width, stepSize is equal // input_channels * filter_height * filter_width im2col(imageData, - inputChannels, - inputHeight, - inputWidth, - blockH(), - blockW(), + imShape, + seqData, + colShape, strideH(), strideW(), paddingH(), - paddingW(), - outputHeight, - outputWidth, - seqData); - imageData += inputChannels * inputHeight * inputWidth; - seqData += seqLength * stepSize; + paddingW()); + imageData += imShape.getElements(); + seqData += colShape.getElements(); } } }; @@ -270,35 +267,24 @@ public: const TensorShape& sequence = inputs[0].shape(); check(image, sequence); + TensorShape imShape = TensorShape({image[1], image[2], image[3]}); TensorShape colShape = getColShape(image, sequence); size_t batchSize = image[0]; - size_t inputChannels = image[1]; - size_t inputHeight = image[2]; - size_t inputWidth = image[3]; - size_t seqLength = sequence[1]; - size_t stepSize = sequence[2]; - size_t outputHeight = colShape[0]; - size_t outputWidth = colShape[1]; real* imageData = outputs[0].data(); real* seqData = inputs[0].data(); Col2ImFunctor col2im; for (size_t i = 0; i < batchSize; i++) { - col2im(seqData, - inputChannels, - inputHeight, - inputWidth, - blockH(), - blockW(), + col2im(imageData, + imShape, + seqData, + colShape, strideH(), strideW(), paddingH(), - paddingW(), - outputHeight, - outputWidth, - imageData); - imageData += inputChannels * inputHeight * inputWidth; - seqData += seqLength * stepSize; + paddingW()); + imageData += imShape.getElements(); + seqData += colShape.getElements(); } } }; -- GitLab From 9c009b4087afa0ac61425cd9e45f8c2e60e92568 Mon Sep 17 00:00:00 2001 From: hedaoyuan Date: Tue, 13 Jun 2017 15:43:48 +0800 Subject: [PATCH 0625/3256] Remove GemmConvOp.h file. --- paddle/function/GemmConvOp.h | 85 ------------------------------------ 1 file changed, 85 deletions(-) delete mode 100644 paddle/function/GemmConvOp.h diff --git a/paddle/function/GemmConvOp.h b/paddle/function/GemmConvOp.h deleted file mode 100644 index f724643f35a..00000000000 --- a/paddle/function/GemmConvOp.h +++ /dev/null @@ -1,85 +0,0 @@ -/* 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 "ConvOp.h" - -namespace paddle { - -/* The storage format of the coldata in the Im2ColFunctor and Col2ImFunctor. */ -enum ColFormat { kCFO = 0, kOCF = 1 }; - -/* - * \brief Converts the image data of four dimensions(NCHW) into a colData. - * Then you can reshape colData to a convolution matrix for - * convolution calculation based on matrix multiplication. - * - * \param imData Image data of NCHW format. - * The format of imData is: - * [input_channels, input_height, input_width]. - * \param colData colData data. - * If the template argument Format is kCFO, - * the format of colData is: - * [input_channels, - * filter_height, - * filter_width, - * output_height, - * output_width] - * If the template argument Format is kOCF, - * the format of colData is: - * [output_height, - * output_width, - * input_channels, - * filter_height, - * filter_width] - * TODO(hedaoyuan): Refactor the arguments of the interface with TensorShape. - */ -template -class Im2ColFunctor { -public: - void operator()(const T* imData, - int inputChannels, - int inputHeight, - int inputWidth, - int filterHeight, - int filterWidth, - int strideHeight, - int strideWidth, - int paddingHeight, - int paddingWidth, - int outputHeight, - int outputWidth, - T* colData); -}; - -template -class Col2ImFunctor { -public: - void operator()(const T* colData, - int inputChannels, - int inputHeight, - int inputWidth, - int filterHeight, - int filterWidth, - int strideHeight, - int strideWidth, - int paddingHeight, - int paddingWidth, - int outputHeight, - int outputWidth, - T* imData); -}; - -} // namespace paddle -- GitLab From 34362d938175a012841275849f3b8102d736b4c6 Mon Sep 17 00:00:00 2001 From: hedaoyuan Date: Tue, 13 Jun 2017 15:57:01 +0800 Subject: [PATCH 0626/3256] Fix some of the wrong comments in im2col.h file. --- paddle/function/Im2Col.h | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/paddle/function/Im2Col.h b/paddle/function/Im2Col.h index d461ec7510b..6d76e229bfc 100644 --- a/paddle/function/Im2Col.h +++ b/paddle/function/Im2Col.h @@ -24,10 +24,11 @@ enum ColFormat { kCFO = 0, kOCF = 1 }; * five dimensions in the Im2ColFunctor calculation, * And in the Col2ImFunctor calculation, it is reversed. * - * \param imData Image data of NCHW format. - * The shape of imData is: - * [inputChannels, inputHeight, inputWidth]. - * \param colData colData data. + * \param imData Image data. + * \param imShape The shape of imData, + * [inputChannels, inputHeight, inputWidth]. + * \param colData Column data. + * \param colShape The shape of colData. * * If the template argument Format is kCFO, the shape of colData is: * [inputChannels, filterHeight, filterWidth, outputHeight, outputWidth] @@ -38,10 +39,10 @@ enum ColFormat { kCFO = 0, kOCF = 1 }; * outputHeight * outputWidth. * * Reshape: - * shape of colData shape of sequence + * shape of colData shape of convolution matrix * [inputChannels, * filterHeight, - * filterWidth, ======> [seqLength, stepSize] + * filterWidth, ======> [height, width] * outputHeight, * outputWidth] * @@ -53,7 +54,7 @@ enum ColFormat { kCFO = 0, kOCF = 1 }; * inputChannels * filterHeight * filterWidth. * * Reshape: - * shape of colData shape of sequence + * shape of colData shape of sequence matrix * [outputHeight, * outputWidth, * inputChannels, ======> [seqLength, stepSize] -- GitLab From 152bd2f9c867e8e165c3d22810281023880b3d16 Mon Sep 17 00:00:00 2001 From: hedaoyuan Date: Tue, 13 Jun 2017 20:30:02 +0800 Subject: [PATCH 0627/3256] Add the GPU version implementation of ImageExpand function. --- paddle/function/Im2Col.h | 3 + paddle/function/Im2ColOpGpu.cu | 130 +++++++++++++++++++++ paddle/function/ImageExpandOp.cpp | 3 + paddle/gserver/layers/BlockExpandLayer.cpp | 73 ++++-------- paddle/gserver/layers/BlockExpandLayer.h | 3 - 5 files changed, 156 insertions(+), 56 deletions(-) create mode 100644 paddle/function/Im2ColOpGpu.cu diff --git a/paddle/function/Im2Col.h b/paddle/function/Im2Col.h index 6d76e229bfc..48e2e32f925 100644 --- a/paddle/function/Im2Col.h +++ b/paddle/function/Im2Col.h @@ -14,6 +14,9 @@ limitations under the License. */ #pragma once +#include "TensorShape.h" +#include "TensorType.h" + namespace paddle { /* The storage format of the coldata in the Im2ColFunctor and Col2ImFunctor. */ diff --git a/paddle/function/Im2ColOpGpu.cu b/paddle/function/Im2ColOpGpu.cu new file mode 100644 index 00000000000..1dac2585db7 --- /dev/null +++ b/paddle/function/Im2ColOpGpu.cu @@ -0,0 +1,130 @@ +/* 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 "Im2Col.h" + +namespace paddle { + +template +__global__ +void im2colOCF(const T* imData, T* colData, + int inputChannels, + int inputHeight, int inputWidth, + int filterHeight, int filterWidth, + int strideHeight, int strideWidth, + int paddingHeight, int paddingWidth, + int outputHeight, int outputWidth) { + int idx = threadIdx.x; + int idy = threadIdx.y; + int swId = blockIdx.x; + int shId = blockIdx.y; + + for (int channelId = threadIdx.z; + channelId < inputChannels; + channelId += blockDim.z) { + int widthOffset = idx + swId * strideWidth - paddingWidth; + int heightOffset = idy + shId * strideHeight - paddingHeight; + int imOffset = widthOffset + heightOffset * inputWidth + + channelId * inputHeight * inputWidth; + + int colOffset = idx + idy * filterWidth + + channelId * filterHeight * filterWidth + + (shId * outputWidth + swId) + * (inputChannels * filterHeight * filterWidth); + + if (idx < filterWidth && idy < filterHeight) { + if (heightOffset >= inputHeight || heightOffset < 0 || + widthOffset >= inputWidth || widthOffset < 0) { + colData[colOffset] = T(0); + } else { + colData[colOffset] = imData[imOffset]; + } + } + } +} + +/* + * imShape = [inputChannels, inputHeight, inputWidth] + * colShape = + * [outputHeight, outputWidth, inputChannels, filterHeight, filterWidth] + */ +template +class Im2ColFunctor { +public: + void operator()(const T* imData, + const TensorShape& imShape, + T* colData, + const TensorShape& colShape, + int strideHeight, + int strideWidth, + int paddingHeight, + int paddingWidth) { + int inputChannels = imShape[0]; + int inputHeight = imShape[1]; + int inputWidth = imShape[2]; + int filterHeight = colShape[3]; + int filterWidth = colShape[4]; + int outputHeight = colShape[0]; + int outputWidth = colShape[1]; + + int blockDimX = 0; + int blockDimY = 0; + if (filterHeight <= 4 && filterWidth <= 4) { + blockDimX = 4; + blockDimY = 4; + } else if (filterHeight <= 8 && filterWidth <= 8) { + blockDimX = 8; + blockDimY = 8; + } else if (filterHeight <= 16 && filterWidth <= 16) { + blockDimX = 16; + blockDimY = 16; + } else { + blockDimX = 32; + blockDimY = 32; + } + + int blockDimZ = 1024 / blockDimX / blockDimY; + dim3 threads(blockDimX, blockDimY, std::min(blockDimZ, inputChannels)); + dim3 grid(outputWidth, outputHeight); + im2colOCF<<< grid, threads, 0, STREAM_DEFAULT >>> + (imData, colData, inputChannels, inputHeight, inputWidth, + filterHeight, filterWidth, strideHeight, strideWidth, + paddingHeight, paddingWidth, outputHeight, outputWidth); + CHECK_SYNC("Im2ColFunctor GPU failed"); + } +}; + +/* + * imShape = [inputChannels, inputHeight, inputWidth] + * colShape = + * [outputHeight, outputWidth, inputChannels, filterHeight, filterWidth] + */ +template +class Col2ImFunctor { +public: + void operator()(T* imData, + const TensorShape& imShape, + const T* colData, + const TensorShape& colShape, + int strideHeight, + int strideWidth, + int paddingHeight, + int paddingWidth) { + } +}; + +template class Im2ColFunctor; +template class Im2ColFunctor; + +} // namespace paddle diff --git a/paddle/function/ImageExpandOp.cpp b/paddle/function/ImageExpandOp.cpp index ad34967bd65..fe4c8fefcf5 100644 --- a/paddle/function/ImageExpandOp.cpp +++ b/paddle/function/ImageExpandOp.cpp @@ -291,5 +291,8 @@ public: REGISTER_TYPED_FUNC(ImageExpand, CPU, ImageExpandForward); REGISTER_TYPED_FUNC(ImageExpandGrad, CPU, ImageExpandBackward); +#ifndef PADDLE_ONLY_CPU +REGISTER_TYPED_FUNC(ImageExpand, GPU, ImageExpandForward); +#endif } // namespace paddle diff --git a/paddle/gserver/layers/BlockExpandLayer.cpp b/paddle/gserver/layers/BlockExpandLayer.cpp index c8d0b21c875..1889b347c2d 100644 --- a/paddle/gserver/layers/BlockExpandLayer.cpp +++ b/paddle/gserver/layers/BlockExpandLayer.cpp @@ -37,16 +37,16 @@ bool BlockExpandLayer::init(const LayerMap& layerMap, imgSizeH_ = blockConf.img_size_y(); imgSizeW_ = blockConf.img_size_x(); + std::vector strides = {(size_t)strideH_, (size_t)strideW_}; + std::vector paddings = {(size_t)paddingH_, (size_t)paddingW_}; + std::vector blocks = {(size_t)blockH_, (size_t)blockW_}; + createFunction(forward_, + "ImageExpand", + FuncConfig() + .set("strides", strides) + .set("paddings", paddings) + .set("blocks", blocks)); if (!useGpu_) { - std::vector strides = {(size_t)strideH_, (size_t)strideW_}; - std::vector paddings = {(size_t)paddingH_, (size_t)paddingW_}; - std::vector blocks = {(size_t)blockH_, (size_t)blockW_}; - createFunction(forward_, - "ImageExpand", - FuncConfig() - .set("strides", strides) - .set("paddings", paddings) - .set("blocks", blocks)); createFunction(backward_, "ImageExpandGrad", FuncConfig() @@ -84,62 +84,29 @@ void BlockExpandLayer::forward(PassType passType) { size_t blockNum = getBlockNum(); size_t blockSize = blockH_ * blockW_ * channels_; resetOutput(blockNum * batchSize, blockSize); - // TODO(hedaoyuan): After completing the GPU version of ImageExpand, - // refactor the following code. - Argument& out = getOutput(); - MatrixPtr outV = getOutputValue(); - MatrixPtr input = getPrev(0)->getOutputValue(); - Matrix::resizeOrCreate(outVTrans_, blockSize, blockNum, false, useGpu_); + // calculate output_.value + inputShape_ = TensorShape({batchSize, channels_, imgSizeH_, imgSizeW_}); + outputShape_ = TensorShape({batchSize, blockNum, blockSize}); + BufferArgs inputs; + BufferArgs outputs; + inputs.addArg(*getInputValue(0), inputShape_); + outputs.addArg(*getOutputValue(), outputShape_, ASSIGN_TO); + forward_[0]->calc(inputs, outputs); + + // calculate output_.sequenceStartPositions and output_.cpuSequenceDims + Argument& out = getOutput(); ICpuGpuVector::resizeOrCreate( out.sequenceStartPositions, batchSize + 1, false); IVector::resizeOrCreate(out.cpuSequenceDims, 2 * batchSize, false); int* start = out.sequenceStartPositions->getMutableData(false); int* dims = out.cpuSequenceDims->getData(); for (size_t i = 0; i < batchSize; i++) { - if (useGpu_) { - outVTrans_->zeroMem(); - /* expand each block as one row */ - MatrixPtr inputTmp = - Matrix::create(input->getData() + i * input->getWidth(), - 1, - input->getWidth(), - false, - useGpu_); - outVTrans_->convExpand(*inputTmp, - imgSizeH_, - imgSizeW_, - channels_, - blockH_, - blockW_, - strideH_, - strideW_, - paddingH_, - paddingW_, - outputH_, - outputW_); - MatrixPtr outVTmp = - Matrix::create(outV->getData() + i * blockNum * blockSize, - blockNum, - blockSize, - false, - useGpu_); - outVTrans_->transpose(outVTmp, false); - } start[i] = i * blockNum; dims[2 * i] = outputH_; dims[2 * i + 1] = outputW_; } start[batchSize] = batchSize * blockNum; - if (!useGpu_) { - inputShape_ = TensorShape({batchSize, channels_, imgSizeH_, imgSizeW_}); - outputShape_ = TensorShape({batchSize, blockNum, blockSize}); - BufferArgs inputs; - BufferArgs outputs; - inputs.addArg(*getInputValue(0), inputShape_); - outputs.addArg(*getOutputValue(), outputShape_, ASSIGN_TO); - forward_[0]->calc(inputs, outputs); - } } void BlockExpandLayer::backward(const UpdateCallback& callback) { diff --git a/paddle/gserver/layers/BlockExpandLayer.h b/paddle/gserver/layers/BlockExpandLayer.h index edda0e0b630..15ce73ab8b2 100644 --- a/paddle/gserver/layers/BlockExpandLayer.h +++ b/paddle/gserver/layers/BlockExpandLayer.h @@ -50,9 +50,6 @@ protected: size_t blockH_, blockW_, strideH_, strideW_, paddingH_, paddingW_; size_t imgSizeH_, imgSizeW_, outputH_, outputW_, channels_; - /// auxiliary variable, which saves the transposed output value. - MatrixPtr outVTrans_; - TensorShape inputShape_; TensorShape outputShape_; -- GitLab From f8ef8c174c442f14662a94e59fcda6587498c8a5 Mon Sep 17 00:00:00 2001 From: hedaoyuan Date: Tue, 13 Jun 2017 21:07:20 +0800 Subject: [PATCH 0628/3256] Add the GPU version implementation of ImageExpandGrad function. --- paddle/function/Im2ColOpGpu.cu | 107 +++++++++++++++++---- paddle/function/ImageExpandOp.cpp | 1 + paddle/gserver/layers/BlockExpandLayer.cpp | 33 +++---- 3 files changed, 103 insertions(+), 38 deletions(-) diff --git a/paddle/function/Im2ColOpGpu.cu b/paddle/function/Im2ColOpGpu.cu index 1dac2585db7..bddd8ffc7c0 100644 --- a/paddle/function/Im2ColOpGpu.cu +++ b/paddle/function/Im2ColOpGpu.cu @@ -13,6 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. */ #include "Im2Col.h" +#include "hl_device_functions.cuh" namespace paddle { @@ -25,30 +26,29 @@ void im2colOCF(const T* imData, T* colData, int strideHeight, int strideWidth, int paddingHeight, int paddingWidth, int outputHeight, int outputWidth) { - int idx = threadIdx.x; - int idy = threadIdx.y; int swId = blockIdx.x; int shId = blockIdx.y; - for (int channelId = threadIdx.z; channelId < inputChannels; channelId += blockDim.z) { - int widthOffset = idx + swId * strideWidth - paddingWidth; - int heightOffset = idy + shId * strideHeight - paddingHeight; - int imOffset = widthOffset + heightOffset * inputWidth - + channelId * inputHeight * inputWidth; - - int colOffset = idx + idy * filterWidth - + channelId * filterHeight * filterWidth - + (shId * outputWidth + swId) - * (inputChannels * filterHeight * filterWidth); - - if (idx < filterWidth && idy < filterHeight) { - if (heightOffset >= inputHeight || heightOffset < 0 || - widthOffset >= inputWidth || widthOffset < 0) { - colData[colOffset] = T(0); - } else { - colData[colOffset] = imData[imOffset]; + for (int idy = threadIdx.y; idy < filterHeight; idy += blockDim.y) { + for (int idx = threadIdx.x; idx < filterWidth; idx += blockDim.x) { + int widthOffset = idx + swId * strideWidth - paddingWidth; + int heightOffset = idy + shId * strideHeight - paddingHeight; + int imOffset = widthOffset + heightOffset * inputWidth + + channelId * inputHeight * inputWidth; + + int colOffset = idx + idy * filterWidth + + channelId * filterHeight * filterWidth + + (shId * outputWidth + swId) + * (inputChannels * filterHeight * filterWidth); + + if (heightOffset >= inputHeight || heightOffset < 0 || + widthOffset >= inputWidth || widthOffset < 0) { + colData[colOffset] = T(0); + } else { + colData[colOffset] = imData[imOffset]; + } } } } @@ -105,6 +105,41 @@ public: } }; +template +__global__ +void col2imOCF(T* imData, const T* colData, + int inputChannels, + int inputHeight, int inputWidth, + int filterHeight, int filterWidth, + int strideHeight, int strideWidth, + int paddingHeight, int paddingWidth, + int outputHeight, int outputWidth) { + int swId = blockIdx.x; + int shId = blockIdx.y; + for (int channelId = threadIdx.z; + channelId < inputChannels; + channelId += blockDim.z) { + for (int idy = threadIdx.y; idy < filterHeight; idy += blockDim.y) { + for (int idx = threadIdx.x; idx < filterWidth; idx += blockDim.x) { + int widthOffset = idx + swId * strideWidth - paddingWidth; + int heightOffset = idy + shId * strideHeight - paddingHeight; + int imOffset = widthOffset + heightOffset * inputWidth + + channelId * inputHeight * inputWidth; + + int colOffset = idx + idy * filterWidth + + channelId * filterHeight * filterWidth + + (shId * outputWidth + swId) + * (inputChannels * filterHeight * filterWidth); + + if (heightOffset >= 0 && heightOffset < inputHeight && + widthOffset >= 0 && widthOffset < inputWidth) { + paddle::paddleAtomicAdd(imData + imOffset, colData[colOffset]); + } + } + } + } +} + /* * imShape = [inputChannels, inputHeight, inputWidth] * colShape = @@ -121,10 +156,44 @@ public: int strideWidth, int paddingHeight, int paddingWidth) { + int inputChannels = imShape[0]; + int inputHeight = imShape[1]; + int inputWidth = imShape[2]; + int filterHeight = colShape[3]; + int filterWidth = colShape[4]; + int outputHeight = colShape[0]; + int outputWidth = colShape[1]; + + int blockDimX = 0; + int blockDimY = 0; + if (filterHeight <= 4 && filterWidth <= 4) { + blockDimX = 4; + blockDimY = 4; + } else if (filterHeight <= 8 && filterWidth <= 8) { + blockDimX = 8; + blockDimY = 8; + } else if (filterHeight <= 16 && filterWidth <= 16) { + blockDimX = 16; + blockDimY = 16; + } else { + blockDimX = 32; + blockDimY = 32; + } + + int blockDimZ = 1024 / blockDimX / blockDimY; + dim3 threads(blockDimX, blockDimY, std::min(blockDimZ, inputChannels)); + dim3 grid(outputWidth, outputHeight); + col2imOCF<<< grid, threads, 0, STREAM_DEFAULT >>> + (imData, colData, inputChannels, inputHeight, inputWidth, + filterHeight, filterWidth, strideHeight, strideWidth, + paddingHeight, paddingWidth, outputHeight, outputWidth); + CHECK_SYNC("Col2ImFunctor GPU failed"); } }; template class Im2ColFunctor; template class Im2ColFunctor; +template class Col2ImFunctor; +template class Col2ImFunctor; } // namespace paddle diff --git a/paddle/function/ImageExpandOp.cpp b/paddle/function/ImageExpandOp.cpp index fe4c8fefcf5..f227f6d0e10 100644 --- a/paddle/function/ImageExpandOp.cpp +++ b/paddle/function/ImageExpandOp.cpp @@ -293,6 +293,7 @@ REGISTER_TYPED_FUNC(ImageExpand, CPU, ImageExpandForward); REGISTER_TYPED_FUNC(ImageExpandGrad, CPU, ImageExpandBackward); #ifndef PADDLE_ONLY_CPU REGISTER_TYPED_FUNC(ImageExpand, GPU, ImageExpandForward); +REGISTER_TYPED_FUNC(ImageExpandGrad, GPU, ImageExpandBackward); #endif } // namespace paddle diff --git a/paddle/gserver/layers/BlockExpandLayer.cpp b/paddle/gserver/layers/BlockExpandLayer.cpp index 1889b347c2d..a5e644a4ae3 100644 --- a/paddle/gserver/layers/BlockExpandLayer.cpp +++ b/paddle/gserver/layers/BlockExpandLayer.cpp @@ -46,14 +46,12 @@ bool BlockExpandLayer::init(const LayerMap& layerMap, .set("strides", strides) .set("paddings", paddings) .set("blocks", blocks)); - if (!useGpu_) { - createFunction(backward_, - "ImageExpandGrad", - FuncConfig() - .set("strides", strides) - .set("paddings", paddings) - .set("blocks", blocks)); - } + createFunction(backward_, + "ImageExpandGrad", + FuncConfig() + .set("strides", strides) + .set("paddings", paddings) + .set("blocks", blocks)); return true; } @@ -110,14 +108,16 @@ void BlockExpandLayer::forward(PassType passType) { } void BlockExpandLayer::backward(const UpdateCallback& callback) { - size_t blockNum = outputH_ * outputW_; - size_t blockSize = blockH_ * blockW_ * channels_; /* Calculate the input layers error */ - MatrixPtr preGrad = inputLayers_[0]->getOutputGrad(); - if (!preGrad) { - return; + if (getInputGrad(0)) { + BufferArgs inputs; + BufferArgs outputs; + inputs.addArg(*getOutputGrad(), outputShape_); + outputs.addArg(*getInputGrad(0), inputShape_, ADD_TO); + backward_[0]->calc(inputs, outputs); } +#if 0 if (useGpu_) { MatrixPtr grad = getOutputGrad(); MatrixPtr gradTrans = Matrix::create(blockSize, blockNum, false, useGpu_); @@ -155,13 +155,8 @@ void BlockExpandLayer::backward(const UpdateCallback& callback) { 1.0, 1.0); } - } else { - BufferArgs inputs; - BufferArgs outputs; - inputs.addArg(*getOutputGrad(), outputShape_); - outputs.addArg(*getInputGrad(0), inputShape_, ADD_TO); - backward_[0]->calc(inputs, outputs); } +#endif } } // namespace paddle -- GitLab From bf6dfc1ff2a01cc35bf6a91177463cd40e328003 Mon Sep 17 00:00:00 2001 From: hedaoyuan Date: Tue, 13 Jun 2017 21:30:32 +0800 Subject: [PATCH 0629/3256] Remove some of the code that has been commented out. --- paddle/gserver/layers/BlockExpandLayer.cpp | 41 ---------------------- 1 file changed, 41 deletions(-) diff --git a/paddle/gserver/layers/BlockExpandLayer.cpp b/paddle/gserver/layers/BlockExpandLayer.cpp index a5e644a4ae3..adc9a814ffb 100644 --- a/paddle/gserver/layers/BlockExpandLayer.cpp +++ b/paddle/gserver/layers/BlockExpandLayer.cpp @@ -116,47 +116,6 @@ void BlockExpandLayer::backward(const UpdateCallback& callback) { outputs.addArg(*getInputGrad(0), inputShape_, ADD_TO); backward_[0]->calc(inputs, outputs); } - -#if 0 - if (useGpu_) { - MatrixPtr grad = getOutputGrad(); - MatrixPtr gradTrans = Matrix::create(blockSize, blockNum, false, useGpu_); - size_t batchSize = preGrad->getHeight(); - - CHECK_EQ(batchSize * blockNum, grad->getHeight()); - CHECK_EQ(blockSize, grad->getWidth()); - - for (size_t i = 0; i < batchSize; i++) { - MatrixPtr gradTmp = - Matrix::create(grad->getData() + i * blockNum * blockSize, - blockNum, - blockSize, - false, - useGpu_); - gradTmp->transpose(gradTrans, false); - MatrixPtr preGradTmp = - Matrix::create(preGrad->getData() + i * preGrad->getWidth(), - 1, - preGrad->getWidth(), - false, - useGpu_); - preGradTmp->convShrink(*gradTrans, - imgSizeH_, - imgSizeW_, - channels_, - blockH_, - blockW_, - strideH_, - strideW_, - paddingH_, - paddingW_, - outputH_, - outputW_, - 1.0, - 1.0); - } - } -#endif } } // namespace paddle -- GitLab From 597a58c3efe015be43e1e20a20a04921a9ae7c60 Mon Sep 17 00:00:00 2001 From: yangyaming Date: Tue, 13 Jun 2017 23:52:16 +0800 Subject: [PATCH 0630/3256] Add DetectionMAPEvaluator. --- .../evaluators/DetectionMAPEvaluator.cpp | 312 ++++++++++++++++++ paddle/gserver/tests/test_Evaluator.cpp | 17 + proto/ModelConfig.proto | 9 + python/paddle/trainer/config_parser.py | 43 ++- .../trainer_config_helpers/evaluators.py | 105 ++++-- 5 files changed, 453 insertions(+), 33 deletions(-) create mode 100644 paddle/gserver/evaluators/DetectionMAPEvaluator.cpp diff --git a/paddle/gserver/evaluators/DetectionMAPEvaluator.cpp b/paddle/gserver/evaluators/DetectionMAPEvaluator.cpp new file mode 100644 index 00000000000..7d326c2db14 --- /dev/null +++ b/paddle/gserver/evaluators/DetectionMAPEvaluator.cpp @@ -0,0 +1,312 @@ +/* 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 "Evaluator.h" +#include "paddle/gserver/layers/DetectionUtil.h" + +using std::map; +using std::vector; +using std::pair; +using std::make_pair; + +namespace paddle { + +/** + * @brief detection map Evaluator + * + * The config file api is detection_map_evaluator. + */ +class DetectionMAPEvaluator : public Evaluator { +public: + DetectionMAPEvaluator() + : evaluateDifficult_(false), cpuOutput_(nullptr), cpuLabel_(nullptr) {} + + virtual void start() { + Evaluator::start(); + allTruePos_.clear(); + allFalsePos_.clear(); + numPos_.clear(); + } + + virtual real evalImp(std::vector& arguments) { + overlapThreshold_ = config_.overlap_threshold(); + backgroundId_ = config_.background_id(); + evaluateDifficult_ = config_.evaluate_difficult(); + apType_ = config_.ap_type(); + + MatrixPtr detectTmpValue = arguments[0].value; + Matrix::resizeOrCreate(cpuOutput_, + detectTmpValue->getHeight(), + detectTmpValue->getWidth(), + false, + false); + + MatrixPtr labelTmpValue = arguments[1].value; + Matrix::resizeOrCreate(cpuLabel_, + labelTmpValue->getHeight(), + labelTmpValue->getWidth(), + false, + false); + + cpuOutput_->copyFrom(*detectTmpValue); + cpuLabel_->copyFrom(*labelTmpValue); + + Argument label = arguments[1]; + const int* labelIndex = label.sequenceStartPositions->getData(false); + size_t batchSize = label.getNumSequences(); + + vector>> allGTBBoxes; + vector>>> allDetectBBoxes; + + for (size_t n = 0; n < batchSize; ++n) { + map> bboxes; + for (int i = labelIndex[n]; i < labelIndex[n + 1]; ++i) { + vector bbox; + getBBoxFromLabelData(cpuLabel_->getData() + i * 6, 1, bbox); + int c = cpuLabel_->getData()[i * 6]; + bboxes[c].push_back(bbox[0]); + } + allGTBBoxes.push_back(bboxes); + } + + size_t imgId = 0; + for (size_t n = 0; n < cpuOutput_->getHeight();) { + map>> bboxes; + while (cpuOutput_->getData()[n * 7] == imgId && + n < cpuOutput_->getHeight()) { + vector label; + vector score; + vector bbox; + getBBoxFromDetectData( + cpuOutput_->getData() + n * 7, 1, label, score, bbox); + bboxes[label[0]].push_back(make_pair(score[0], bbox[0])); + ++n; + } + ++imgId; + if (imgId > batchSize) break; + allDetectBBoxes.push_back(bboxes); + } + + for (size_t n = 0; n < batchSize; ++n) { + for (map>::iterator it = + allGTBBoxes[n].begin(); + it != allGTBBoxes[n].end(); + ++it) { + size_t count = 0; + if (evaluateDifficult_) { + count = it->second.size(); + } else { + for (size_t i = 0; i < it->second.size(); ++i) + if (!(it->second[i].isDifficult)) ++count; + } + if (numPos_.find(it->first) == numPos_.end() && count != 0) { + numPos_[it->first] = count; + } else { + numPos_[it->first] += count; + } + } + } + + // calcTFPos + calcTFPos( + batchSize, allGTBBoxes, allDetectBBoxes, &allTruePos_, &allFalsePos_); + + return 0; + } + + virtual void printStats(std::ostream& os) const { + real mAP = calcMAP(); + os << "Detection mAP=" << mAP * 100; + } + + virtual void distributeEval(ParameterClient2* client) { + LOG(FATAL) << "Distribute detection evaluation not implemented."; + } + +protected: + void calcTFPos(const size_t batchSize, + const vector>>& allGTBBoxes, + const vector>>>& + allDetectBBoxes, + map>>* allTruePos, + map>>* allFalsePos) { + for (size_t n = 0; n < allDetectBBoxes.size(); ++n) { + if (allGTBBoxes[n].size() == 0) { + for (map>>::const_iterator + it = allDetectBBoxes[n].begin(); + it != allDetectBBoxes[n].end(); + ++it) { + size_t label = it->first; + for (size_t i = 0; i < it->second.size(); ++i) { + (*allTruePos)[label].push_back(make_pair(it->second[i].first, 0)); + (*allFalsePos)[label].push_back(make_pair(it->second[i].first, 1)); + } + } + } else { + for (map>>::const_iterator + it = allDetectBBoxes[n].begin(); + it != allDetectBBoxes[n].end(); + ++it) { + size_t label = it->first; + vector> predBBoxes = it->second; + if (allGTBBoxes[n].find(label) == allGTBBoxes[n].end()) { + for (size_t i = 0; i < predBBoxes.size(); ++i) { + (*allTruePos)[label].push_back(make_pair(predBBoxes[i].first, 0)); + (*allFalsePos)[label].push_back( + make_pair(predBBoxes[i].first, 1)); + } + } else { + vector gtBBoxes = + allGTBBoxes[n].find(label)->second; + vector visited(gtBBoxes.size(), false); + // Sort detections in descend order based on scores + std::sort(predBBoxes.begin(), + predBBoxes.end(), + sortScorePairDescend); + for (size_t i = 0; i < predBBoxes.size(); ++i) { + real maxOverlap = -1.0; + size_t maxIdx = 0; + for (size_t j = 0; j < gtBBoxes.size(); ++j) { + real overlap = + jaccardOverlap(predBBoxes[i].second, gtBBoxes[j]); + if (overlap > maxOverlap) { + maxOverlap = overlap; + maxIdx = j; + } + } + if (maxOverlap > overlapThreshold_) { + if (evaluateDifficult_ || + (!evaluateDifficult_ && !gtBBoxes[maxIdx].isDifficult)) { + if (!visited[maxIdx]) { + (*allTruePos)[label].push_back( + make_pair(predBBoxes[i].first, 1)); + (*allFalsePos)[label].push_back( + make_pair(predBBoxes[i].first, 0)); + visited[maxIdx] = true; + } else { + (*allTruePos)[label].push_back( + make_pair(predBBoxes[i].first, 0)); + (*allFalsePos)[label].push_back( + make_pair(predBBoxes[i].first, 1)); + } + } + } else { + (*allTruePos)[label].push_back( + make_pair(predBBoxes[i].first, 0)); + (*allFalsePos)[label].push_back( + make_pair(predBBoxes[i].first, 1)); + } + } + } + } + } + } + } + + real calcMAP() const { + real mAP = 0.0; + size_t count = 0; + for (map::const_iterator it = numPos_.begin(); + it != numPos_.end(); + ++it) { + size_t label = it->first; + size_t labelNumPos = it->second; + if (labelNumPos == 0 || allTruePos_.find(label) == allTruePos_.end()) + continue; + vector> labelTruePos = allTruePos_.find(label)->second; + vector> labelFalsePos = + allFalsePos_.find(label)->second; + // Compute average precision. + vector tpCumSum; + getAccumulation(labelTruePos, &tpCumSum); + vector fpCumSum; + getAccumulation(labelFalsePos, &fpCumSum); + std::vector precision, recall; + size_t num = tpCumSum.size(); + // Compute Precision. + for (size_t i = 0; i < num; ++i) { + CHECK_LE(tpCumSum[i], labelNumPos); + precision.push_back(static_cast(tpCumSum[i]) / + static_cast(tpCumSum[i] + fpCumSum[i])); + recall.push_back(static_cast(tpCumSum[i]) / labelNumPos); + } + // VOC2007 style + if (apType_ == "11point") { + vector maxPrecisions(11, 0.0); + int startIdx = num - 1; + for (int j = 10; j >= 0; --j) + for (int i = startIdx; i >= 0; --i) { + if (recall[i] < j / 10.) { + startIdx = i; + if (j > 0) maxPrecisions[j - 1] = maxPrecisions[j]; + break; + } else { + if (maxPrecisions[j] < precision[i]) + maxPrecisions[j] = precision[i]; + } + } + for (int j = 10; j >= 0; --j) mAP += maxPrecisions[j] / 11; + ++count; + } else if (apType_ == "Integral") { + // Nature integral + real averagePrecisions = 0.; + real prevRecall = 0.; + for (size_t i = 0; i < num; ++i) { + if (fabs(recall[i] - prevRecall) > 1e-6) + averagePrecisions += precision[i] * fabs(recall[i] - prevRecall); + prevRecall = recall[i]; + } + mAP += averagePrecisions; + ++count; + } else { + LOG(FATAL) << "Unkown ap version: " << apType_; + } + } + if (count != 0) mAP /= count; + return mAP; + } + + void getAccumulation(vector> inPairs, + vector* accuVec) const { + std::stable_sort( + inPairs.begin(), inPairs.end(), sortScorePairDescend); + accuVec->clear(); + size_t sum = 0; + for (size_t i = 0; i < inPairs.size(); ++i) { + sum += inPairs[i].second; + accuVec->push_back(sum); + } + } + + std::string getTypeImpl() const { return "detection_map"; } + + real getValueImpl() const { return calcMAP() * 100; } + +private: + real overlapThreshold_; + bool evaluateDifficult_; + size_t backgroundId_; + std::string apType_; + + MatrixPtr cpuOutput_; + MatrixPtr cpuLabel_; + + map numPos_; + map>> allTruePos_; + map>> allFalsePos_; +}; + +REGISTER_EVALUATOR(detection_map, DetectionMAPEvaluator); + +} // namespace paddle diff --git a/paddle/gserver/tests/test_Evaluator.cpp b/paddle/gserver/tests/test_Evaluator.cpp index 4f5fdbb37ce..93996392d22 100644 --- a/paddle/gserver/tests/test_Evaluator.cpp +++ b/paddle/gserver/tests/test_Evaluator.cpp @@ -138,6 +138,23 @@ void testEvaluatorAll(TestConfig testConf, testEvaluator(testConf, testEvaluatorName, batchSize, false); } +TEST(Evaluator, detection_map) { + TestConfig config; + config.evaluatorConfig.set_type("detection_map"); + config.evaluatorConfig.set_overlap_threshold(0.5); + config.evaluatorConfig.set_background_id(0); + config.evaluatorConfig.set_ap_type("Integral"); + config.evaluatorConfig.set_evaluate_difficult(0); + + config.inputDefs.push_back({INPUT_DATA, "output", 7}); + config.inputDefs.push_back({INPUT_SEQUENCE_DATA, "label", 6}); + config.evaluatorConfig.set_evaluate_difficult(false); + testEvaluatorAll(config, "detection_map", 100); + + config.evaluatorConfig.set_evaluate_difficult(true); + testEvaluatorAll(config, "detection_map", 100); +} + TEST(Evaluator, classification_error) { TestConfig config; config.evaluatorConfig.set_type("classification_error"); diff --git a/proto/ModelConfig.proto b/proto/ModelConfig.proto index 29270829bbc..ebe4f5cbb56 100644 --- a/proto/ModelConfig.proto +++ b/proto/ModelConfig.proto @@ -489,6 +489,15 @@ message EvaluatorConfig { // Used by ClassificationErrorEvaluator // top # classification error optional int32 top_k = 13 [default = 1]; + + // Used by DetectionMAPEvaluator + optional double overlap_threshold = 14 [default = 0.5]; + + optional int32 background_id = 15 [default = 0]; + + optional bool evaluate_difficult = 16 [default = false]; + + optional string ap_type = 17 [default = "11point"]; } message LinkConfig { diff --git a/python/paddle/trainer/config_parser.py b/python/paddle/trainer/config_parser.py index 0792e2d40b4..e78dc4f3b4d 100644 --- a/python/paddle/trainer/config_parser.py +++ b/python/paddle/trainer/config_parser.py @@ -1300,20 +1300,23 @@ def parse_maxout(maxout, input_layer_name, maxout_conf): # Define an evaluator @config_func -def Evaluator( - name, - type, - inputs, - chunk_scheme=None, - num_chunk_types=None, - classification_threshold=None, - positive_label=None, - dict_file=None, - result_file=None, - num_results=None, - top_k=None, - delimited=None, - excluded_chunk_types=None, ): +def Evaluator(name, + type, + inputs, + chunk_scheme=None, + num_chunk_types=None, + classification_threshold=None, + positive_label=None, + dict_file=None, + result_file=None, + num_results=None, + top_k=None, + delimited=None, + excluded_chunk_types=None, + overlap_threshold=None, + background_id=None, + evaluate_difficult=None, + ap_type=None): evaluator = g_config.model_config.evaluators.add() evaluator.type = type evaluator.name = MakeLayerNameInSubmodel(name) @@ -1347,6 +1350,18 @@ def Evaluator( if excluded_chunk_types: evaluator.excluded_chunk_types.extend(excluded_chunk_types) + if overlap_threshold is not None: + evaluator.overlap_threshold = overlap_threshold + + if background_id is not None: + evaluator.background_id = background_id + + if evaluate_difficult is not None: + evaluator.evaluate_difficult = evaluate_difficult + + if ap_type is not None: + evaluator.ap_type = ap_type + class LayerBase(object): def __init__( diff --git a/python/paddle/trainer_config_helpers/evaluators.py b/python/paddle/trainer_config_helpers/evaluators.py index a5234f3e47f..1dcd8048032 100644 --- a/python/paddle/trainer_config_helpers/evaluators.py +++ b/python/paddle/trainer_config_helpers/evaluators.py @@ -21,7 +21,8 @@ __all__ = [ "chunk_evaluator", "sum_evaluator", "column_sum_evaluator", "value_printer_evaluator", "gradient_printer_evaluator", "maxid_printer_evaluator", "maxframe_printer_evaluator", - "seqtext_printer_evaluator", "classification_error_printer_evaluator" + "seqtext_printer_evaluator", "classification_error_printer_evaluator", + "detection_map_evaluator" ] @@ -31,10 +32,11 @@ class EvaluatorAttribute(object): FOR_RANK = 1 << 2 FOR_PRINT = 1 << 3 FOR_UTILS = 1 << 4 + FOR_DETECTION = 1 << 5 KEYS = [ "for_classification", "for_regression", "for_rank", "for_print", - "for_utils" + "for_utils", "for_detection" ] @staticmethod @@ -57,22 +59,25 @@ def evaluator(*attrs): return impl -def evaluator_base( - input, - type, - label=None, - weight=None, - name=None, - chunk_scheme=None, - num_chunk_types=None, - classification_threshold=None, - positive_label=None, - dict_file=None, - result_file=None, - num_results=None, - delimited=None, - top_k=None, - excluded_chunk_types=None, ): +def evaluator_base(input, + type, + label=None, + weight=None, + name=None, + chunk_scheme=None, + num_chunk_types=None, + classification_threshold=None, + positive_label=None, + dict_file=None, + result_file=None, + num_results=None, + delimited=None, + top_k=None, + excluded_chunk_types=None, + overlap_threshold=None, + background_id=None, + evaluate_difficult=None, + ap_type=None): """ Evaluator will evaluate the network status while training/testing. @@ -107,6 +112,14 @@ def evaluator_base( :type weight: LayerOutput. :param top_k: number k in top-k error rate :type top_k: int + :param overlap_threshold: In detection tasks to filter detection results + :type overlap_threshold: float + :param background_id: Identifier of background class + :type background_id: int + :param evaluate_difficult: Whether to evaluate difficult objects + :type evaluate_difficult: bool + :param ap_type: How to calculate average persicion + :type ap_type: str """ # inputs type assertions. assert classification_threshold is None or isinstance( @@ -136,7 +149,61 @@ def evaluator_base( delimited=delimited, num_results=num_results, top_k=top_k, - excluded_chunk_types=excluded_chunk_types, ) + excluded_chunk_types=excluded_chunk_types, + overlap_threshold=overlap_threshold, + background_id=background_id, + evaluate_difficult=evaluate_difficult, + ap_type=ap_type) + + +@evaluator(EvaluatorAttribute.FOR_DETECTION) +@wrap_name_default() +def detection_map_evaluator(input, + label, + overlap_threshold=0.5, + background_id=0, + evaluate_difficult=False, + ap_type="11point", + name=None): + """ + Detection mAP Evaluator. It will print mean Average Precision for detection. + + The detection mAP Evaluator according to the detection_output's output count + the true positive and the false positive bbox and integral them to get the + mAP. + + The simple usage is: + + .. code-block:: python + + eval = detection_map_evaluator(input=det_output,label=lbl) + + :param input: Input layer. + :type input: LayerOutput + :param label: Label layer. + :type label: LayerOutput + :param overlap_threshold: The bbox overlap threshold of a true positive. + :type overlap_threshold: float + :param background_id: The background class index. + :type background_id: int + :param evaluate_difficult: Wether evaluate a difficult ground truth. + :type evaluate_difficult: bool + """ + if not isinstance(input, list): + input = [input] + + if label: + input.append(label) + + evaluator_base( + name=name, + type="detection_map", + input=input, + label=label, + overlap_threshold=overlap_threshold, + background_id=background_id, + evaluate_difficult=evaluate_difficult, + ap_type=ap_type) @evaluator(EvaluatorAttribute.FOR_CLASSIFICATION) -- GitLab From b72e8aa3c6d3f8c75aa87f3035638eb508091385 Mon Sep 17 00:00:00 2001 From: dzhwinter Date: Wed, 14 Jun 2017 00:11:09 +0800 Subject: [PATCH 0631/3256] "seperate serialization proto state" --- paddle/optimizer/CMakeLists.txt | 3 +- paddle/optimizer/Tensor.h | 6 -- paddle/optimizer/Tensor_test.cpp | 19 ---- paddle/optimizer/adadelta_optimizer.cc | 23 ++--- paddle/optimizer/adadelta_optimizer.h | 4 +- paddle/optimizer/adagrad_optimizer.cc | 23 ++++- paddle/optimizer/adagrad_optimizer.h | 4 +- paddle/optimizer/adam_optimizer.cc | 18 ++-- paddle/optimizer/adam_optimizer.h | 7 +- paddle/optimizer/optimizer.cc | 2 +- paddle/optimizer/optimizer.h | 4 +- paddle/optimizer/parameter_optimizer.cc | 2 +- paddle/optimizer/parameter_optimizer.h | 6 +- paddle/optimizer/parameter_optimizer_test.cpp | 96 ++++++++++--------- paddle/optimizer/serialization.h | 14 +-- paddle/optimizer/serialization_test.cpp | 24 +++++ .../{sgd_optmizer.cc => sgd_optimizer.cc} | 15 +-- paddle/optimizer/sgd_optimizer.h | 4 +- proto/OptimizerConfig.proto | 55 +++++++---- 19 files changed, 176 insertions(+), 153 deletions(-) delete mode 100644 paddle/optimizer/Tensor_test.cpp create mode 100644 paddle/optimizer/serialization_test.cpp rename paddle/optimizer/{sgd_optmizer.cc => sgd_optimizer.cc} (81%) diff --git a/paddle/optimizer/CMakeLists.txt b/paddle/optimizer/CMakeLists.txt index 746f4a69f84..e93ba102945 100644 --- a/paddle/optimizer/CMakeLists.txt +++ b/paddle/optimizer/CMakeLists.txt @@ -6,12 +6,13 @@ set(OPITMIZER_SRCS adam_optimizer.cc optimizer.cc parameter_optimizer.cc - sgd_optmizer.cc + sgd_optimizer.cc ) add_library(optimizer STATIC ${OPITMIZER_SRCS}) add_dependencies(optimizer gen_proto_cpp) add_simple_unittest(tensor_test) +add_simple_unittest(serialization_test) add_simple_unittest(parameter_optimizer_test) add_dependencies(parameter_optimizer_test optimizer) diff --git a/paddle/optimizer/Tensor.h b/paddle/optimizer/Tensor.h index a00a59bc6af..b8f212e81ff 100644 --- a/paddle/optimizer/Tensor.h +++ b/paddle/optimizer/Tensor.h @@ -17,16 +17,10 @@ public: TensorT(size_t size) : height_(1), width_(size) { data_ = new T[size]; } TensorT(T* data, size_t size) : height_(1), width_(size), data_(data) {} TensorT(T* data, size_t h, size_t w) : height_(h), width_(w), data_(data_) {} - TensorT(const TensorT& t) - : TensorT(1, t.size(), 0, t.get_buffer(), false, false) {} ~TensorT() { if (data_) delete data_; } - TensorT& operator=(const TensorT& t) { - this->width_ = t.size(); - this->data_ = t.get_buffer(); - } T* get_buffer() { return this->data_; } T& operator[](const size_t idx) { CHECK(idx >= 0 && idx < this->width_) << "out of index range"; diff --git a/paddle/optimizer/Tensor_test.cpp b/paddle/optimizer/Tensor_test.cpp deleted file mode 100644 index cdf73309323..00000000000 --- a/paddle/optimizer/Tensor_test.cpp +++ /dev/null @@ -1,19 +0,0 @@ -#include "gtest/gtest.h" -#include "tensor.h" - -using namespace paddle; -using namespace paddle::optimizer; - -TEST(Tensor, indexer) { - Tensor t(3); - for (auto i = 0; i < t.size(); ++i) { - t[i] = i; - } - ASSERT_EQ(t[2], 2); - ASSERT_EQ(t[1], 1); -} - -int main(int argc, char** argv) { - testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/paddle/optimizer/adadelta_optimizer.cc b/paddle/optimizer/adadelta_optimizer.cc index a6b079ce256..d1c6571d9b4 100644 --- a/paddle/optimizer/adadelta_optimizer.cc +++ b/paddle/optimizer/adadelta_optimizer.cc @@ -26,7 +26,7 @@ void AdadeltaOptimizer::Update(const Tensor* gradient) { } const char* AdadeltaOptimizer::SerializeState(int* state_len) { - OptimizerState state; + AdadeltaOptimizerState state; state.set_learning_rate(lr_policy_->LearningRate(num_sample_passed_)); state.set_num_sample_passed(num_sample_passed_); @@ -34,22 +34,14 @@ const char* AdadeltaOptimizer::SerializeState(int* state_len) { TensorToProto(*accum_gradient_, state.mutable_accum_gradient()); TensorToProto(*accum_delta_, state.mutable_accum_delta()); TensorToProto(*update_delta_, state.mutable_update_delta()); - state.set_nesterov(epsilon_); - state.set_momentum(rho_); - state.set_decay(decay_); - // can be used when memory alignment to system - *state_len += CalStateSize(parameter_, - accum_gradient_, - accum_delta_, - update_delta_, - rho_, - epsilon_, - decay_); + + *state_len = + CalStateSize(parameter_, accum_gradient_, accum_delta_, update_delta_); return state.SerializeAsString().c_str(); } -void AdadeltaOptimizer::DeSerializeState(const std::string& str) { - OptimizerState state; +void AdadeltaOptimizer::DeserializeState(const std::string& str) { + AdadeltaOptimizerState state; state.ParseFromString(str); lr_policy_->set(state.learning_rate()); num_sample_passed_ = state.num_sample_passed(); @@ -58,6 +50,7 @@ void AdadeltaOptimizer::DeSerializeState(const std::string& str) { ProtoToTensor(state.accum_gradient(), accum_gradient_); ProtoToTensor(state.accum_delta(), accum_delta_); ProtoToTensor(state.update_delta(), update_delta_); +} } // namespace optimizer -} // namespace optimizer +} // namespace paddle diff --git a/paddle/optimizer/adadelta_optimizer.h b/paddle/optimizer/adadelta_optimizer.h index e0f544a90e5..58a26ebb7a7 100644 --- a/paddle/optimizer/adadelta_optimizer.h +++ b/paddle/optimizer/adadelta_optimizer.h @@ -13,7 +13,7 @@ public: rho_(rho), epsilon_(epsilon), decay_(decay) { - size_t size = p->size(); + size_t size = parameter->size(); if (accum_gradient_) delete accum_gradient_; accum_gradient_ = new Tensor(size); if (accum_delta_) delete accum_delta_; @@ -28,7 +28,7 @@ public: } void Update(const Tensor *gradient); const char *SerializeState(int *state_len); - void DeSerializeState(const std::string &state); + void DeserializeState(const std::string &state); private: Tensor *accum_gradient_; diff --git a/paddle/optimizer/adagrad_optimizer.cc b/paddle/optimizer/adagrad_optimizer.cc index 6a17cf0ed06..ebc4d9e83ae 100644 --- a/paddle/optimizer/adagrad_optimizer.cc +++ b/paddle/optimizer/adagrad_optimizer.cc @@ -17,8 +17,25 @@ void AdagradOptimizer::Update(const Tensor* gradient) { learning_rate * decay_ * param[i]; } } -const char* SGDOptimizer::SerializeState(int* state_len) { NIMPL; } +const char* AdagradOptimizer::SerializeState(int* state_len) { + AdagradOptimizerState state; + state.set_learning_rate(lr_policy_->LearningRate(num_sample_passed_)); + state.set_num_sample_passed(num_sample_passed_); + + TensorToProto(*parameter_, state.mutable_parameter()); + TensorToProto(*accum_gradient_, state.mutable_accum_gradient()); + *state_len = CalStateSize(parameter_, accum_gradient_); + return state.SerializeAsString().c_str(); +} + +void AdagradOptimizer::DeserializeState(const std::string& str) { + AdagradOptimizerState state; + state.ParseFromString(str); + lr_policy_->set(state.learning_rate()); + num_sample_passed_ = state.num_sample_passed(); + ProtoToTensor(state.parameter(), parameter_); + ProtoToTensor(state.accum_gradient(), accum_gradient_); +} -void SGDOptimizer::DeSerializeState(const std::string& str) { NIMPL; } -// namespace optimizer } // namespace optimizer +} // namespace paddle diff --git a/paddle/optimizer/adagrad_optimizer.h b/paddle/optimizer/adagrad_optimizer.h index ebc0fe2acc6..90fc1dd4ac9 100644 --- a/paddle/optimizer/adagrad_optimizer.h +++ b/paddle/optimizer/adagrad_optimizer.h @@ -12,7 +12,7 @@ public: double epsilon, double decay) : ParameterOptimizer(parameter, lr), epsilon_(epsilon), decay_(decay) { - size_t size = p->size(); + size_t size = parameter->size(); if (accum_gradient_) delete accum_gradient_; accum_gradient_ = new Tensor(size); } @@ -21,7 +21,7 @@ public: } void Update(const Tensor *gradient); const char *SerializeState(int *state_len); - void DeSerializeState(const std::string &state); + void DeserializeState(const std::string &state); private: Tensor *accum_gradient_; diff --git a/paddle/optimizer/adam_optimizer.cc b/paddle/optimizer/adam_optimizer.cc index 974039cf6dc..53b3350d68f 100644 --- a/paddle/optimizer/adam_optimizer.cc +++ b/paddle/optimizer/adam_optimizer.cc @@ -22,32 +22,26 @@ void AdamOptimizer::Update(const Tensor *gradient) { } } -const char *AdadeltaOptimizer::SerializeState(int *state_len) { - OptimizerState state; +const char *AdamOptimizer::SerializeState(int *state_len) { + AdamOptimizerState state; state.set_learning_rate(lr_policy_->LearningRate(num_sample_passed_)); state.set_num_sample_passed(num_sample_passed_); TensorToProto(*parameter_, state.mutable_parameter()); TensorToProto(*velocitys_, state.mutable_momentums()); - state.set_beta_1(beta_1_); - state.set_beta_2(beta_2_); - state.set_decay(decay_); - *state_len += CalStateSize( - parameter_, momentums_, velocitys_, beta_1_, beta_2, epsilon_ decay_); + *state_len = CalStateSize(parameter_, momentums_, velocitys_); return state.SerializeAsString().c_str(); } -void AdadeltaOptimizer::DeSerializeState(const std::string &str) { - OptimizerState state; +void AdamOptimizer::DeserializeState(const std::string &str) { + AdamOptimizerState state; state.ParseFromString(str); lr_policy_->set(state.learning_rate()); num_sample_passed_ = state.num_sample_passed(); ProtoToTensor(state.parameter(), parameter_); - ProtoToTensor(state.velocitys(), velocitys__); - beta_1_ = state.beta_1(); - beta_2_ = state.beta_2(); + ProtoToTensor(state.velocitys(), velocitys_); } } // namespace optimizer } // namespace paddle diff --git a/paddle/optimizer/adam_optimizer.h b/paddle/optimizer/adam_optimizer.h index b8be2ca2227..04bc01154fb 100644 --- a/paddle/optimizer/adam_optimizer.h +++ b/paddle/optimizer/adam_optimizer.h @@ -8,7 +8,8 @@ namespace optimizer { class AdamOptimizer : public ParameterOptimizer { public: AdamOptimizer(Tensor *parameter, - LrPolicy *lr double beta_1, + LrPolicy *lr, + double beta_1, double beta_2, double epsilon, double decay) @@ -17,7 +18,7 @@ public: beta_2_(beta_2), epsilon_(epsilon), decay_(decay) { - size_t size = p->size(); + size_t size = parameter->size(); momentums_ = new Tensor(size); velocitys_ = new Tensor(size); } @@ -26,6 +27,8 @@ public: if (velocitys_) delete velocitys_; } void Update(const Tensor *gradient); + const char *SerializeState(int *state_len); + void DeserializeState(const std::string &state); private: Tensor *momentums_; diff --git a/paddle/optimizer/optimizer.cc b/paddle/optimizer/optimizer.cc index c06c0737b25..54662dc3789 100644 --- a/paddle/optimizer/optimizer.cc +++ b/paddle/optimizer/optimizer.cc @@ -49,7 +49,7 @@ paddle_optimizer* paddle_create_optimizer(const unsigned char* config_proto, optimizer->impl = ParameterOptimizer::Create(config, parameter); if (state != nullptr) { std::string s(state, state + state_len); - optimizer->impl->DeSerializeState(s); + optimizer->impl->DeserializeState(s); } return optimizer; } diff --git a/paddle/optimizer/optimizer.h b/paddle/optimizer/optimizer.h index a5f468b06b5..aabf7a458dd 100644 --- a/paddle/optimizer/optimizer.h +++ b/paddle/optimizer/optimizer.h @@ -75,14 +75,14 @@ int paddle_update_parameter(paddle_optimizer* o, int num_bytes); /** - * @brief optimizer instance + * @brief optimizer for get parameter buffer * @param param_buffer, initilized parameter buffer * @return return content length */ int paddle_optimizer_get_weights(paddle_optimizer* o, void** param_buffer); /** - * @brief optimzizer instance + * @brief optimzizer for saving training state * @param training state for receive SerializeState * @return return state_buffer length */ diff --git a/paddle/optimizer/parameter_optimizer.cc b/paddle/optimizer/parameter_optimizer.cc index ae3e97bba8d..38df3b75d77 100644 --- a/paddle/optimizer/parameter_optimizer.cc +++ b/paddle/optimizer/parameter_optimizer.cc @@ -62,7 +62,7 @@ ParameterOptimizer *ParameterOptimizer::Create(const std::string &config_proto, << "have not select any Optimizer. use SGDOptimizer in default"; return new SGDOptimizer(parameter, lr, 0.0, 0.0, false); }; - return select_optimizer(config); + return select_optimizer(parameter, config); } float *ParameterOptimizer::get_weight(int *param_size) const { diff --git a/paddle/optimizer/parameter_optimizer.h b/paddle/optimizer/parameter_optimizer.h index 1abd659d484..658b22406d6 100644 --- a/paddle/optimizer/parameter_optimizer.h +++ b/paddle/optimizer/parameter_optimizer.h @@ -8,10 +8,6 @@ #include "serialization.h" #include "tensor.h" -// Not Implemen Yet, macr -// o -#define NIMPL crash(__PRETTY_FUNCTION__, " not implemented yet") - namespace paddle { namespace optimizer { @@ -30,7 +26,7 @@ public: virtual void Update(const Tensor *gradient) = 0; virtual float *get_weight(int *param_size) const; virtual const char *SerializeState(int *state_len) = 0; - virtual void DeSerializeState(const std::string &state) = 0; + virtual void DeserializeState(const std::string &state) = 0; protected: Tensor *parameter_; diff --git a/paddle/optimizer/parameter_optimizer_test.cpp b/paddle/optimizer/parameter_optimizer_test.cpp index d39d50a1abf..8d3bfa9cf4b 100644 --- a/paddle/optimizer/parameter_optimizer_test.cpp +++ b/paddle/optimizer/parameter_optimizer_test.cpp @@ -1,19 +1,20 @@ #include "parameter_optimizer.h" #include -#include +#include #include #include "adadelta_optimizer.h" #include "adagrad_optimizer.h" #include "adam_optimizer.h" #include "gtest/gtest.h" #include "sgd_optimizer.h" + using namespace paddle; using namespace paddle::optimizer; Tensor* FillTensor(size_t size) { Tensor* param = new Tensor(size); Tensor& p = *param; - for (auto i = 0; i < p.size(); ++i) { + for (size_t i = 0; i < p.size(); ++i) { p[i] = (float)rand() / (float)RAND_MAX; } return param; @@ -22,7 +23,7 @@ Tensor* FillTensor(size_t size) { Tensor* FixedTensor(size_t size) { Tensor* param = new Tensor(size); Tensor& p = *param; - for (auto i = 0; i < p.size(); ++i) { + for (size_t i = 0; i < p.size(); ++i) { p[i] = i; } return param; @@ -31,7 +32,7 @@ Tensor* FixedTensor(size_t size) { class OptimizerTest : public testing::Test { public: // init tensor shape - const size_t size = 5; + const size_t kSize = 5; virtual void SetUp() { CreateSGD(); @@ -40,68 +41,77 @@ public: virtual void TearDown() {} void CreateSGD() { - config.set_optimizer(OptimizerConfig::SGD); - config.mutable_sgd()->set_momentum(0.0); - config.mutable_sgd()->set_decay(0.0); - config.mutable_sgd()->set_nesterov(false); - config.set_lr_policy(OptimizerConfig::ConstLr); - config.mutable_const_lr()->set_learning_rate(0.1); + Tensor* parameter = FillTensor(kSize); + config_.set_optimizer(OptimizerConfig::SGD); + config_.mutable_sgd()->set_momentum(0.0); + config_.mutable_sgd()->set_decay(0.0); + config_.mutable_sgd()->set_nesterov(false); + config_.set_lr_policy(OptimizerConfig::ConstLr); + config_.mutable_const_lr()->set_learning_rate(0.1); ParameterOptimizer* opt = - ParameterOptimizer::Create(config.SerializeAsString()); - opts.push_back(opt); + ParameterOptimizer::Create(config_.SerializeAsString(), parameter); + opts_.push_back(opt); + opts_table_[opts_.size()] = OptimizerConfig::SGD; } void CreateAdam() { - config.set_optimizer(OptimizerConfig::Adam); - config.mutable_adam()->set_beta_1(0.9); - config.mutable_adam()->set_beta_2(0.1); - config.mutable_adam()->set_epsilon(1e-3); - config.mutable_adam()->set_decay(0.0); - config.set_lr_policy(OptimizerConfig::ConstLr); - config.mutable_const_lr()->set_learning_rate(0.1); + Tensor* parameter = FixedTensor(kSize); + config_.set_optimizer(OptimizerConfig::Adam); + config_.mutable_adam()->set_beta_1(0.9); + config_.mutable_adam()->set_beta_2(0.1); + config_.mutable_adam()->set_epsilon(1e-3); + config_.mutable_adam()->set_decay(0.0); + config_.set_lr_policy(OptimizerConfig::ConstLr); + config_.mutable_const_lr()->set_learning_rate(0.1); ParameterOptimizer* opt = - ParameterOptimizer::Create(config.SerializeAsString()); - opts.push_back(opt); - } - void TestSetWeight() { - Tensor* p = FillTensor(size); - for (size_t i = 0; i < opts.size(); ++i) { - opts[i]->set_weight(p); - } + ParameterOptimizer::Create(config_.SerializeAsString(), parameter); + opts_.push_back(opt); + opts_table_[opts_.size()] = OptimizerConfig::Adam; } void TestGetWeight() { - Tensor* p = FixedTensor(size); - for (size_t i = 0; i < opts.size(); ++i) { - opts[i]->set_weight(p); - } - for (size_t i = 0; i < opts.size(); ++i) { + Tensor* p = FixedTensor(kSize); + for (size_t i = 0; i < opts_.size(); ++i) { int s = 0; - float* newp = (float*)opts[i]->get_weight(&s); - for (size_t j = 0; j < size; ++j) { + float* newp = (float*)opts_[i]->get_weight(&s); + for (size_t j = 0; j < kSize; ++j) { EXPECT_EQ(newp[j], (*p)[j]); } } } + void TestUpdate() { - Tensor* g = FixedTensor(size); - for (size_t i = 0; i < opts.size(); ++i) { - opts[i]->Update(g); + Tensor* g = FixedTensor(kSize); + for (size_t i = 0; i < opts_.size(); ++i) { + opts_[i]->Update(g); + } + } + + void TestCheckPoint() { + std::map expected_state_len = { + {OptimizerConfig::SGD, kSize}, {OptimizerConfig::Adam, kSize * 3}, + }; + for (size_t i = 0; i < opts_.size(); ++i) { + int state_len = 0; + std::string state = opts_[i]->SerializeState(&state_len); + EXPECT_EQ(state_len, expected_state_len[opts_table_[i]]); + opts_[i]->DeserializeState(state); } } private: - std::vector opts; - OptimizerConfig config; + std::vector opts_; + std::map opts_table_; + OptimizerConfig config_; }; -TEST_F(OptimizerTest, test_set_get_weight) { - TestSetWeight(); - TestGetWeight(); -} +TEST_F(OptimizerTest, TestGetWeight) { TestGetWeight(); } + TEST_F(OptimizerTest, TestUpdate) { TestUpdate(); } +TEST_F(OptimizerTest, TestCheckPoint) { TestCheckPoint(); } + int main(int argc, char** argv) { testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); diff --git a/paddle/optimizer/serialization.h b/paddle/optimizer/serialization.h index 60bb7e27651..07874502a50 100644 --- a/paddle/optimizer/serialization.h +++ b/paddle/optimizer/serialization.h @@ -10,15 +10,16 @@ namespace paddle { namespace optimizer { -static unsigned CalStateSize(int* state_len) { return 0; } +static unsigned CalStateSize() { return 0; } template unsigned CalStateSize(const HEAD& head, const TAIL&... tail) { - if (std::is_fundamental::value) { - return sizeof head + CalStateSize(tail...); - } else { - return sizeof(head[0]) * head->size() + CalStateSize(tail...); - } + return sizeof head + CalStateSize(tail...); +} + +template +unsigned CalStateSize(const Tensor* head, const TAIL&... tail) { + return head->size() + CalStateSize(tail...); } static void TensorToProto(const Tensor& tensor, TensorProto* proto) { @@ -32,7 +33,6 @@ static void TensorToProto(const Tensor& tensor, TensorProto* proto) { } static void ProtoToTensor(const TensorProto& proto, Tensor* tensor) { - CHECK(proto.size() == tensor->size()) << "unmatch shape of proto and tensor"; std::stringstream sin; for (auto i = 0; i < proto.content_size(); ++i) { sin << proto.content(i); diff --git a/paddle/optimizer/serialization_test.cpp b/paddle/optimizer/serialization_test.cpp new file mode 100644 index 00000000000..98fbdf5a5e2 --- /dev/null +++ b/paddle/optimizer/serialization_test.cpp @@ -0,0 +1,24 @@ +#include "serialization.h" +#include "gtest/gtest.h" + +using namespace paddle; +using namespace paddle::optimizer; + +TEST(TensorToProto, Case1) { + Tensor t(3), t1(3); + for (size_t i = 0; i < t.size(); ++i) { + t[i] = i; + t1[i] = 0; + } + TensorProto proto; + TensorToProto(t, &proto); + ProtoToTensor(proto, &t1); + for (size_t i = 0; i < t1.size(); ++i) { + EXPECT_EQ(t1[i], t[i]); + } +} + +int main(int argc, char** argv) { + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/paddle/optimizer/sgd_optmizer.cc b/paddle/optimizer/sgd_optimizer.cc similarity index 81% rename from paddle/optimizer/sgd_optmizer.cc rename to paddle/optimizer/sgd_optimizer.cc index b2c6b7a1acf..8b4ea777d2d 100644 --- a/paddle/optimizer/sgd_optmizer.cc +++ b/paddle/optimizer/sgd_optimizer.cc @@ -1,5 +1,5 @@ -#include "serialization.h" #include "sgd_optimizer.h" +#include "serialization.h" namespace paddle { namespace optimizer { @@ -28,29 +28,24 @@ void SGDOptimizer::Update(const Tensor *gradient) { } const char *SGDOptimizer::SerializeState(int *state_len) { - OptimizerState state; + SGDOptimizerState state; state.set_learning_rate(lr_policy_->LearningRate(num_sample_passed_)); state.set_num_sample_passed(num_sample_passed_); TensorToProto(*parameter_, state.mutable_parameter()); TensorToProto(*momentums_, state.mutable_momentums()); - state.set_momentum(momentum_); - state.set_decay(decay_); - state.set_nesterov(nesterov_); - *state_len += - CalStateSize(parameter_, momentums_, momentum_, decay_, nesterov_); + *state_len = CalStateSize(parameter_, momentums_); return state.SerializeAsString().c_str(); } -void SGDOptimizer::DeSerializeState(const std::string &str) { - OptimizerState state; +void SGDOptimizer::DeserializeState(const std::string &str) { + SGDOptimizerState state; state.ParseFromString(str); lr_policy_->set(state.learning_rate()); num_sample_passed_ = state.num_sample_passed(); ProtoToTensor(state.parameter(), parameter_); ProtoToTensor(state.parameter(), momentums_); - momentum_ = state.momentum(); } } // namespace optimizer diff --git a/paddle/optimizer/sgd_optimizer.h b/paddle/optimizer/sgd_optimizer.h index d0ac375d2b8..1d4ea46f1a4 100644 --- a/paddle/optimizer/sgd_optimizer.h +++ b/paddle/optimizer/sgd_optimizer.h @@ -14,7 +14,7 @@ public: decay_(d), nesterov_(n) { if (momentum_ != 0.0) { - size_t size = p->size(); + size_t size = parameter->size(); // TODO: fix it with align aware allocator bind to Tensor if (momentums_) delete momentums_; momentums_ = new Tensor(size); @@ -25,7 +25,7 @@ public: } void Update(const Tensor* gradient); const char* SerializeState(int* state_len); - void DeSerializeState(const std::string& state); + void DeserializeState(const std::string& state); private: Tensor* momentums_; diff --git a/proto/OptimizerConfig.proto b/proto/OptimizerConfig.proto index 1ccba6d2076..aab2fdad693 100644 --- a/proto/OptimizerConfig.proto +++ b/proto/OptimizerConfig.proto @@ -78,36 +78,51 @@ enum DataType { repeated bytes content = 2; } -message OptimizerState { +message SGDOptimizerState { + // learning rate policy optional double learning_rate = 101; optional double lr_decay_a = 102; optional double lr_decay_b = 103; optional double num_sample_passed = 104; - // momentum - optional TensorProto parameter = 105; - optional TensorProto momentums = 1; + // state + optional TensorProto parameter = 1; + optional TensorProto momentums = 2; +} - // adadelta +message AdadeltaOptimizerState { + // learning rate policy + optional double learning_rate = 101; + optional double lr_decay_a = 102; + optional double lr_decay_b = 103; + optional double num_sample_passed = 104; + // state + optional TensorProto parameter = 1; optional TensorProto accum_gradient = 2; optional TensorProto accum_delta = 3; optional TensorProto update_delta = 4; +} - // adam - optional TensorProto velocitys = 5; - - // momentum - optional double momentum = 6; - optional double decay = 7; - optional bool nesterov = 8; - - // adadelta - optional double rho = 9; - optional double epsilon = 10; - - // adam - optional double beta_1 = 11; - optional double beta_2 = 12; +message AdagradOptimizerState { + // learning rate policy + optional double learning_rate = 101; + optional double lr_decay_a = 102; + optional double lr_decay_b = 103; + optional double num_sample_passed = 104; + // state + optional TensorProto parameter = 1; + optional TensorProto accum_gradient = 2; +} +message AdamOptimizerState { + // learning rate policy + optional double learning_rate = 101; + optional double lr_decay_a = 102; + optional double lr_decay_b = 103; + optional double num_sample_passed = 104; + // state + optional TensorProto parameter = 1; + optional TensorProto momentums = 2; + optional TensorProto velocitys = 3; } message OptimizerConfig { -- GitLab From 22b5a388cfe9366876bba3a0349bcae24fd20a70 Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Tue, 13 Jun 2017 22:41:08 +0000 Subject: [PATCH 0632/3256] fix according to comments --- go/pserver/cclient/cclient.go | 72 ++++++++++++++--------------------- 1 file changed, 29 insertions(+), 43 deletions(-) diff --git a/go/pserver/cclient/cclient.go b/go/pserver/cclient/cclient.go index 6a76ec920e5..e753b461bc7 100644 --- a/go/pserver/cclient/cclient.go +++ b/go/pserver/cclient/cclient.go @@ -20,6 +20,8 @@ typedef struct { } paddle_parameter, paddle_gradient; typedef int paddle_pserver_client; +#define PSERVER_ERROR -1 +#define PSERVER_OK 0 */ import "C" @@ -115,7 +117,7 @@ func paddle_begin_init_params(client C.paddle_pserver_client) C.int { if selected := c.BeginInitParams(); selected { return 1 } - return 0 + return C.PSERVER_OK } //export paddle_init_param @@ -133,13 +135,13 @@ func paddle_init_param(client C.paddle_pserver_client, param C.paddle_parameter, if err != nil { if err.Error() == pserver.AlreadyInitialized { log.Printf("parameter %s already initialized, treat paddle_init_param as sucessful.\n", name) - return 0 + return C.PSERVER_OK } log.Println(err) - return -1 + return C.PSERVER_ERROR } - return 0 + return C.PSERVER_OK } //export paddle_finish_init_params @@ -149,14 +151,14 @@ func paddle_finish_init_params(client C.paddle_pserver_client) C.int { if err != nil { if err.Error() == pserver.AlreadyInitialized { log.Println("parameters already initialized, treat paddle_finish_init_params as sucessful.") - return 0 + return C.PSERVER_OK } log.Println(err) - return -1 + return C.PSERVER_ERROR } - return 0 + return C.PSERVER_OK } //export paddle_send_grads @@ -174,10 +176,10 @@ func paddle_send_grads(client C.paddle_pserver_client, grads *C.paddle_gradient, err := c.SendGrads(gs) if err != nil { log.Println(err) - return -1 + return C.PSERVER_ERROR } - return 0 + return C.PSERVER_OK } //export paddle_get_params @@ -191,42 +193,26 @@ func paddle_get_params(client C.paddle_pserver_client, dst **C.paddle_parameter, ps, err := c.GetParams(ns) if err != nil { log.Println(err) - return -1 - } - - names := func() (string, string) { - retNames := "" - for _, p := range ps { - if retNames == "" { - retNames = p.Name - } else { - retNames = ", " + p.Name - } - } - - requestedNames := "" - for _, n := range ns { - if requestedNames == "" { - requestedNames = n - } else { - requestedNames = ", " + n - } - } - - return requestedNames, retNames + return C.PSERVER_ERROR } if len(ps) != len(ns) { - requestedNames, retNames := names() - log.Printf("pserver returned wrong number of parameters. Requested: %s, returned: %s.\n", retNames, requestedNames) - return -1 + pn := make([]string, len(ps)) + for i, p := range ps { + pn[i] = p.Name + } + log.Printf("pserver returned wrong number of parameters. Requested: %s, returned: %s.\n", strings.Join(pn, ", "), strings.Join(ns, ", ")) + return C.PSERVER_ERROR } for i := range ps { if ns[i] != ps[i].Name { - requestedNames, retNames := names() - log.Printf("pserver returned wrong parameters, or not in requested order. Requested: %s, returned: %s.\n", retNames, requestedNames) - return -1 + pn := make([]string, len(ps)) + for i, p := range ps { + pn[i] = p.Name + } + log.Printf("pserver returned wrong parameters, or not in requested order. Requested: %s, returned: %s.\n", strings.Join(pn, ", "), strings.Join(ns, ", ")) + return C.PSERVER_ERROR } } @@ -236,12 +222,12 @@ func paddle_get_params(client C.paddle_pserver_client, dst **C.paddle_parameter, if unsafe.Pointer(param) == nullPtr { log.Println("must pre-allocate parameter.") - return -1 + return C.PSERVER_ERROR } else { if unsafe.Pointer(param.content) != nullPtr { if int(param.content_len) != len(p.Content) { log.Printf("the pre-allocated content len does not match parameter content len. Pre-allocated len: %d, returned len: %d", param.content_len, len(p.Content)) - return -1 + return C.PSERVER_ERROR } } } @@ -251,7 +237,7 @@ func paddle_get_params(client C.paddle_pserver_client, dst **C.paddle_parameter, param.element_type = C.paddle_element_type(p.ElementType) } - return 0 + return C.PSERVER_OK } //export paddle_save_model @@ -261,10 +247,10 @@ func paddle_save_model(client C.paddle_pserver_client, path *C.char) C.int { err := c.Save(p) if err != nil { log.Println(err) - return -1 + return C.PSERVER_ERROR } - return 0 + return C.PSERVER_OK } func main() {} // Required but ignored -- GitLab From f05649afb79b746e4c3d07112f79ce7a3cce0344 Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Thu, 8 Jun 2017 20:57:04 +0000 Subject: [PATCH 0633/3256] move connection to higher hierarchy, master package need to use it too. --- go/{pserver/internal => }/connection/conn.go | 0 go/pserver/client.go | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename go/{pserver/internal => }/connection/conn.go (100%) diff --git a/go/pserver/internal/connection/conn.go b/go/connection/conn.go similarity index 100% rename from go/pserver/internal/connection/conn.go rename to go/connection/conn.go diff --git a/go/pserver/client.go b/go/pserver/client.go index f8bd0aa59f3..4f35141a9f7 100644 --- a/go/pserver/client.go +++ b/go/pserver/client.go @@ -6,7 +6,7 @@ import ( "sort" "time" - "github.com/PaddlePaddle/Paddle/go/pserver/internal/connection" + "github.com/PaddlePaddle/Paddle/go/connection" ) // TODO(helin): add RPC call retry logic -- GitLab From 72a73ab6d2139fae73dc922505acad6d8aa41ec4 Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Thu, 8 Jun 2017 22:18:36 +0000 Subject: [PATCH 0634/3256] implement master server client, RPC part. --- go/cmd/master/master.go | 2 -- go/connection/conn.go | 15 ++++++++ go/master/client.go | 74 ++++++++++++++++++++++++++++++++++++++ go/master/client_test.go | 78 ++++++++++++++++++++++++++++++++++++++++ go/master/service.go | 11 ++++-- go/pserver/client.go | 19 +++++++--- 6 files changed, 190 insertions(+), 9 deletions(-) create mode 100644 go/master/client.go create mode 100644 go/master/client_test.go diff --git a/go/cmd/master/master.go b/go/cmd/master/master.go index d1f3d7d76c4..65548b7b684 100644 --- a/go/cmd/master/master.go +++ b/go/cmd/master/master.go @@ -50,7 +50,6 @@ func main() { panic("no valid datset specified.") } - idx := 0 for _, path := range paths { f, err := os.Open(path) if err != nil { @@ -66,7 +65,6 @@ func main() { count := index.NumChunks() for i := 0; i < count; i++ { chunk := master.Chunk{ - Idx: idx, Path: path, Index: *index.ChunkIndex(i), } diff --git a/go/connection/conn.go b/go/connection/conn.go index 1c04f117254..0bab2def1d9 100644 --- a/go/connection/conn.go +++ b/go/connection/conn.go @@ -21,6 +21,18 @@ func New() *Conn { return c } +// Close closes the connection. +func (c *Conn) Close() error { + c.mu.Lock() + defer c.mu.Unlock() + + if c.client == nil { + return nil + } + + return c.client.Close() +} + // Connect connects the connection to a address. func (c *Conn) Connect(addr string) error { c.mu.Lock() @@ -56,6 +68,9 @@ func (c *Conn) Connect(addr string) error { return nil } +// TODO(helin): refactor Call to be able to perform given retry +// policy. + // Call make a RPC call. // // Call will be blocked until the connection to remote RPC service diff --git a/go/master/client.go b/go/master/client.go new file mode 100644 index 00000000000..23ef18f9e27 --- /dev/null +++ b/go/master/client.go @@ -0,0 +1,74 @@ +package master + +import ( + "log" + "time" + + "github.com/PaddlePaddle/Paddle/go/connection" +) + +// Addresser provide the address of the master server. +type Addresser interface { + Address() string +} + +// Client is the client of the master server. +type Client struct { + conn *connection.Conn +} + +// NewClient creates a new Client. +func NewClient(addr Addresser) *Client { + c := &Client{} + c.conn = connection.New() + go c.monitorMaster(addr) + return c +} + +func (c *Client) monitorMaster(addr Addresser) { + lastMaster := "" + monitor := func() { + curMaster := addr.Address() + if curMaster != lastMaster { + if curMaster == "" { + err := c.conn.Close() + if err != nil { + log.Println(err) + } + } else { + err := c.conn.Connect(curMaster) + if err != nil { + log.Println(err) + + // connect to addr failed, set + // to last known addr in order + // to retry next time. + curMaster = lastMaster + } + + } + } + + lastMaster = curMaster + } + + monitor() + ticker := time.NewTicker(10 * time.Second) + for _ = range ticker.C { + monitor() + } +} + +// GetTask gets a new task from the master server. +func (c *Client) GetTask() (Task, error) { + var dummy int + var t Task + err := c.conn.Call("Service.GetTask", dummy, &t) + return t, err +} + +// TaskFinished tells the master server a task is finished. +func (c *Client) TaskFinished(taskID int) error { + var dummy int + return c.conn.Call("Service.TaskFinished", taskID, &dummy) +} diff --git a/go/master/client_test.go b/go/master/client_test.go new file mode 100644 index 00000000000..4603bdc4d6b --- /dev/null +++ b/go/master/client_test.go @@ -0,0 +1,78 @@ +package master_test + +import ( + "fmt" + "net" + "net/http" + "net/rpc" + "strconv" + "strings" + "testing" + "time" + + "github.com/PaddlePaddle/Paddle/go/master" +) + +const ( + totalTask = 20 + chunkPerTask = 10 +) + +var port int + +func init() { + l, err := net.Listen("tcp", ":0") + if err != nil { + panic(err) + } + + ss := strings.Split(l.Addr().String(), ":") + p, err := strconv.Atoi(ss[len(ss)-1]) + if err != nil { + panic(err) + } + port = p + + go func(l net.Listener) { + chunks := make([]master.Chunk, totalTask) + s := master.NewService(chunks, chunkPerTask, time.Second, 1) + server := rpc.NewServer() + err := server.Register(s) + if err != nil { + panic(err) + } + + mux := http.NewServeMux() + mux.Handle(rpc.DefaultRPCPath, server) + err = http.Serve(l, mux) + if err != nil { + panic(err) + } + }(l) +} + +type addresser string + +func (a addresser) Address() string { + return string(a) +} + +func TestClientFull(t *testing.T) { + c := master.NewClient(addresser(fmt.Sprintf(":%d", port))) + + for i := 0; i < 5*totalTask/chunkPerTask; i++ { + task, err := c.GetTask() + if err != nil { + panic(err) + } + + if len(task.Chunks) != chunkPerTask { + t.Fatal("wrong number of chunk per task", len(task.Chunks)) + } + + err = c.TaskFinished(task.ID) + if err != nil { + panic(err) + } + } +} diff --git a/go/master/service.go b/go/master/service.go index ab17a62f385..8d6bbecc497 100644 --- a/go/master/service.go +++ b/go/master/service.go @@ -75,9 +75,8 @@ func NewService(chunks []Chunk, chunksPerTask int, timeoutDur time.Duration, tim // Chunk is a chunk of data consisted of several data instances. type Chunk struct { - Idx int // index of the chunk within the file Path string - Index recordio.Index // block index + Index recordio.Index // chunk index } // Task is the basic unit of data instances assigned to trainers. @@ -123,6 +122,8 @@ func (s *Service) GetTask(dummy int, task *Task) error { return err } + *task = t.Task + time.AfterFunc(s.timeoutDur, func(taskID int, epoch int) func() { return func() { s.mu.Lock() @@ -174,5 +175,11 @@ func (s *Service) TaskFinished(taskID int, dummy *int) error { t.NumTimeout = 0 s.taskQueues.Done = append(s.taskQueues.Done, t) delete(s.taskQueues.Pending, taskID) + + if len(s.taskQueues.Todo) == 0 { + s.taskQueues.Todo = s.taskQueues.Done + s.taskQueues.Done = nil + } + return s.snapshot() } diff --git a/go/pserver/client.go b/go/pserver/client.go index 4f35141a9f7..7930f012c36 100644 --- a/go/pserver/client.go +++ b/go/pserver/client.go @@ -47,7 +47,7 @@ func NewClient(l Lister, pserverNum int, sel Selector) *Client { // monitorPservers monitors pserver addresses, and updates connection // when the address changes. func (c *Client) monitorPservers(l Lister, pserverNum int) { - knownServers := make([]Server, pserverNum) + lastServers := make([]Server, pserverNum) ticker := time.NewTicker(10 * time.Second) monitor := func() { curServers := make([]Server, pserverNum) @@ -56,8 +56,17 @@ func (c *Client) monitorPservers(l Lister, pserverNum int) { curServers[l.Index] = l } - for i := range knownServers { - if knownServers[i].Addr != curServers[i].Addr { + for i := range lastServers { + if lastServers[i].Addr != curServers[i].Addr { + if curServers[i].Addr == "" { + err := c.pservers[i].Close() + if err != nil { + log.Println(err) + } + + continue + } + err := c.pservers[i].Connect(curServers[i].Addr) if err != nil { log.Println(err) @@ -65,12 +74,12 @@ func (c *Client) monitorPservers(l Lister, pserverNum int) { // connect to addr failed, set // to last known addr in order // to retry next time. - curServers[i].Addr = knownServers[i].Addr + curServers[i].Addr = lastServers[i].Addr } } } - knownServers = curServers + lastServers = curServers } monitor() -- GitLab From 54e8263cae3ffcc597d977330f78fb5020c1dba2 Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Fri, 9 Jun 2017 01:43:38 +0000 Subject: [PATCH 0635/3256] implement master server client, remove unnecessary dummy variable --- go/cmd/master/master.go | 50 +-------------- go/master/client.go | 14 +++-- go/master/client_test.go | 49 +++++++++++---- go/master/service.go | 121 ++++++++++++++++++++++++++++++++----- go/pserver/client.go | 12 ++-- go/pserver/service_test.go | 31 ++++------ 6 files changed, 174 insertions(+), 103 deletions(-) diff --git a/go/cmd/master/master.go b/go/cmd/master/master.go index 65548b7b684..25cd1cafcdf 100644 --- a/go/cmd/master/master.go +++ b/go/cmd/master/master.go @@ -1,78 +1,32 @@ package main import ( - "fmt" "net" "net/http" "net/rpc" - "os" - "path/filepath" "strconv" - "strings" "time" "github.com/namsral/flag" "github.com/PaddlePaddle/Paddle/go/master" - "github.com/PaddlePaddle/recordio" ) func main() { port := flag.Int("port", 8080, "port of the master server.") - dataset := flag.String("training_dataset", "", "dataset: comma separated path to RecordIO paths, supports golb patterns.") + faultTolerance := flag.Bool("fault_tolerance", false, "enable fault tolerance (requires etcd).") taskTimeoutDur := flag.Duration("task_timout_dur", 20*time.Minute, "task timout duration.") taskTimeoutMax := flag.Int("task_timeout_max", 3, "max timtout count for each task before it being declared failed task.") chunkPerTask := flag.Int("chunk_per_task", 10, "chunk per task.") flag.Parse() - if *dataset == "" { - panic("no dataset specified.") - } - if *faultTolerance { panic("fault tolernance not implemented.") - } - - var chunks []master.Chunk - var paths []string - ss := strings.Split(*dataset, ",") - fmt.Println(ss) - for _, s := range ss { - match, err := filepath.Glob(s) - if err != nil { - panic(err) - } - paths = append(paths, match...) - } - - if len(paths) == 0 { - panic("no valid datset specified.") - } - - for _, path := range paths { - f, err := os.Open(path) - if err != nil { - panic(err) - } - - index, err := recordio.LoadIndex(f) - if err != nil { - panic(err) - } - f.Close() - count := index.NumChunks() - for i := 0; i < count; i++ { - chunk := master.Chunk{ - Path: path, - Index: *index.ChunkIndex(i), - } - chunks = append(chunks, chunk) - } } - s := master.NewService(chunks, *chunkPerTask, *taskTimeoutDur, *taskTimeoutMax) + s := master.NewService(*chunkPerTask, *taskTimeoutDur, *taskTimeoutMax) err := rpc.Register(s) if err != nil { panic(err) diff --git a/go/master/client.go b/go/master/client.go index 23ef18f9e27..791db5a9753 100644 --- a/go/master/client.go +++ b/go/master/client.go @@ -59,16 +59,22 @@ func (c *Client) monitorMaster(addr Addresser) { } } +// SetDataset set dataset for the master server to dispatch. +// +// SetDataset can be call multiple times from different nodes. But +// only the first call will be honored. +func (c *Client) SetDataset(globPaths []string) error { + return c.conn.Call("Service.SetDataset", globPaths, nil) +} + // GetTask gets a new task from the master server. func (c *Client) GetTask() (Task, error) { - var dummy int var t Task - err := c.conn.Call("Service.GetTask", dummy, &t) + err := c.conn.Call("Service.GetTask", 0, &t) return t, err } // TaskFinished tells the master server a task is finished. func (c *Client) TaskFinished(taskID int) error { - var dummy int - return c.conn.Call("Service.TaskFinished", taskID, &dummy) + return c.conn.Call("Service.TaskFinished", taskID, nil) } diff --git a/go/master/client_test.go b/go/master/client_test.go index 4603bdc4d6b..5abad0d8208 100644 --- a/go/master/client_test.go +++ b/go/master/client_test.go @@ -5,12 +5,14 @@ import ( "net" "net/http" "net/rpc" + "os" "strconv" "strings" "testing" "time" "github.com/PaddlePaddle/Paddle/go/master" + "github.com/PaddlePaddle/recordio" ) const ( @@ -34,8 +36,7 @@ func init() { port = p go func(l net.Listener) { - chunks := make([]master.Chunk, totalTask) - s := master.NewService(chunks, chunkPerTask, time.Second, 1) + s := master.NewService(chunkPerTask, time.Second, 1) server := rpc.NewServer() err := server.Register(s) if err != nil { @@ -58,21 +59,47 @@ func (a addresser) Address() string { } func TestClientFull(t *testing.T) { + const p = "/tmp/master_client_test_0" + f, err := os.Create(p) + if err != nil { + panic(err) + } + + for i := 0; i < totalTask*chunkPerTask; i++ { + w := recordio.NewWriter(f, -1, -1) + w.Write(nil) + // call Close to force RecordIO writing a chunk. + w.Close() + } + f.Close() + c := master.NewClient(addresser(fmt.Sprintf(":%d", port))) + c.SetDataset([]string{p}) - for i := 0; i < 5*totalTask/chunkPerTask; i++ { - task, err := c.GetTask() - if err != nil { - panic(err) + checkOnePass := func(i int) { + var tasks []master.Task + for i := 0; i < totalTask; i++ { + task, err := c.GetTask() + if err != nil { + t.Fatal(i, err) + } + tasks = append(tasks, task) } - if len(task.Chunks) != chunkPerTask { - t.Fatal("wrong number of chunk per task", len(task.Chunks)) + _, err = c.GetTask() + if err == nil { + t.Fatal(i, "should get error.") } - err = c.TaskFinished(task.ID) - if err != nil { - panic(err) + for _, task := range tasks { + err = c.TaskFinished(task.ID) + if err != nil { + t.Fatal(i, err) + } } } + + for i := 0; i < 10; i++ { + checkOnePass(i) + } } diff --git a/go/master/service.go b/go/master/service.go index 8d6bbecc497..c80037a3b35 100644 --- a/go/master/service.go +++ b/go/master/service.go @@ -3,6 +3,8 @@ package master import ( "errors" "log" + "os" + "path/filepath" "sync" "time" @@ -13,18 +15,15 @@ const ( targetTaskCount = 300 ) -// errors -var ( - ErrNoMoreTask = errors.New("no more task for current pass") - ErrPendingTaskNotFound = errors.New("pending task not found") -) - // Service is the master server service. type Service struct { - timeoutDur time.Duration - timeoutMax int + chunksPerTask int + timeoutDur time.Duration + timeoutMax int + ready chan struct{} mu sync.Mutex + initBegan bool taskQueues taskQueues } @@ -63,13 +62,14 @@ func partition(chunks []Chunk, chunksPerTask int) []taskEntry { } // NewService creates a new service. -func NewService(chunks []Chunk, chunksPerTask int, timeoutDur time.Duration, timeoutMax int) *Service { +func NewService(chunksPerTask int, timeoutDur time.Duration, timeoutMax int) *Service { s := &Service{} + s.chunksPerTask = chunksPerTask s.timeoutDur = timeoutDur s.timeoutMax = timeoutMax s.taskQueues = taskQueues{} s.taskQueues.Pending = make(map[int]taskEntry) - s.taskQueues.Todo = partition(chunks, chunksPerTask) + s.ready = make(chan struct{}) return s } @@ -104,13 +104,102 @@ func (s *Service) snapshot() error { return nil } +// SetDataset sets dataset to dispatch for the master server. +// +// SetDataset can be call multiple times. But only the first call will +// be honored. +func (s *Service) SetDataset(globPaths []string, dummy *int) error { + if len(globPaths) == 0 { + return errors.New("no dataset specified") + } + + s.mu.Lock() + defer s.mu.Unlock() + if s.initBegan { + // SetDataset already called. All trainer will call + // SetDataset, but we only handle the first one. Treat + // other calls as successful but do nothing. + return nil + } + + s.initBegan = true + + var chunks []Chunk + var paths []string + + for _, s := range globPaths { + match, err := filepath.Glob(s) + if err != nil { + panic(err) + } + paths = append(paths, match...) + } + + if len(paths) == 0 { + return errors.New("no valid datset specified") + } + + for _, path := range paths { + f, err := os.Open(path) + if err != nil { + panic(err) + } + + index, err := recordio.LoadIndex(f) + if err != nil { + return err + } + err = f.Close() + if err != nil { + return err + } + + count := index.NumChunks() + for i := 0; i < count; i++ { + chunk := Chunk{ + Path: path, + Index: *index.ChunkIndex(i), + } + chunks = append(chunks, chunk) + } + } + + s.taskQueues.Todo = partition(chunks, s.chunksPerTask) + + err := s.snapshot() + if err != nil { + return err + } + + close(s.ready) + return nil +} + // GetTask gets a new task from the service. func (s *Service) GetTask(dummy int, task *Task) error { + select { + case <-s.ready: + } + s.mu.Lock() defer s.mu.Unlock() if len(s.taskQueues.Todo) == 0 { - return ErrNoMoreTask + if len(s.taskQueues.Done) == 0 { + if len(s.taskQueues.Pending) == 0 { + return errors.New("all task failed") + } + + // TODO(helin): client need to retry in this + // error case. Gotcha: RPC client can't + // compare returned error with predefined + // erros like io.EOF. Because interface don't + // have same dynamic value when in different + // process. + return errors.New("no more available task") + } + s.taskQueues.Todo = s.taskQueues.Done + s.taskQueues.Todo = nil } t := s.taskQueues.Todo[0] @@ -163,12 +252,16 @@ func (s *Service) GetTask(dummy int, task *Task) error { // TaskFinished tell the service that a task is finished. func (s *Service) TaskFinished(taskID int, dummy *int) error { + select { + case <-s.ready: + } + s.mu.Lock() defer s.mu.Unlock() t, ok := s.taskQueues.Pending[taskID] if !ok { - return ErrPendingTaskNotFound + return errors.New("pending task not found") } // task finished, reset timeout @@ -176,8 +269,8 @@ func (s *Service) TaskFinished(taskID int, dummy *int) error { s.taskQueues.Done = append(s.taskQueues.Done, t) delete(s.taskQueues.Pending, taskID) - if len(s.taskQueues.Todo) == 0 { - s.taskQueues.Todo = s.taskQueues.Done + if len(s.taskQueues.Pending) == 0 { + s.taskQueues.Todo = append(s.taskQueues.Todo, s.taskQueues.Done...) s.taskQueues.Done = nil } diff --git a/go/pserver/client.go b/go/pserver/client.go index 7930f012c36..bbe93cbb6b3 100644 --- a/go/pserver/client.go +++ b/go/pserver/client.go @@ -102,16 +102,14 @@ func (c *Client) BeginInitParams() bool { // InitParam initializes the parameter on parameter servers. func (c *Client) InitParam(paramWithConfigs ParameterWithConfig) error { - var dummy int - return c.pservers[c.partition(paramWithConfigs.Param.Name)].Call("Service.InitParam", paramWithConfigs, &dummy) + return c.pservers[c.partition(paramWithConfigs.Param.Name)].Call("Service.InitParam", paramWithConfigs, nil) } // FinishInitParams tells parameter servers client has sent all // parameters to parameter servers as initialization. func (c *Client) FinishInitParams() error { for _, p := range c.pservers { - var dummy int - err := p.Call("Service.FinishInitParams", dummy, &dummy) + err := p.Call("Service.FinishInitParams", 0, nil) if err != nil { return err } @@ -125,8 +123,7 @@ func (c *Client) SendGrads(grads []Gradient) error { errCh := make(chan error, len(grads)) for _, g := range grads { go func(g Gradient) { - var dummy int - err := c.pservers[c.partition(g.Name)].Call("Service.SendGrad", g, &dummy) + err := c.pservers[c.partition(g.Name)].Call("Service.SendGrad", g, nil) errCh <- err }(g) } @@ -205,8 +202,7 @@ func (c *Client) Save(path string) error { errCh := make(chan error, len(c.pservers)) for _, p := range c.pservers { - var dummy int - err := p.Call("Service.Save", path, &dummy) + err := p.Call("Service.Save", path, nil) errCh <- err } diff --git a/go/pserver/service_test.go b/go/pserver/service_test.go index 796492ffb47..c40cecd0b6b 100644 --- a/go/pserver/service_test.go +++ b/go/pserver/service_test.go @@ -15,8 +15,7 @@ func TestFull(t *testing.T) { p.Name = "param_a" p.Content = []byte{1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0} p.ElementType = pserver.Int32 - var dummy int - err := s.InitParam(pserver.ParameterWithConfig{Param: p, Config: nil}, &dummy) + err := s.InitParam(pserver.ParameterWithConfig{p, nil}, nil) if err != nil { t.FailNow() } @@ -25,12 +24,12 @@ func TestFull(t *testing.T) { p1.Name = "param_b" p1.Content = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} p1.ElementType = pserver.Float32 - err = s.InitParam(pserver.ParameterWithConfig{Param: p1, Config: nil}, &dummy) + err = s.InitParam(pserver.ParameterWithConfig{p1, nil}, nil) if err != nil { t.FailNow() } - err = s.FinishInitParams(0, &dummy) + err = s.FinishInitParams(0, nil) if err != nil { t.FailNow() } @@ -46,11 +45,11 @@ func TestFull(t *testing.T) { } g1, g2 := pserver.Gradient(p1), pserver.Gradient(p) - err = s.SendGrad(g1, &dummy) + err = s.SendGrad(g1, nil) if err != nil { t.FailNow() } - err = s.SendGrad(g2, &dummy) + err = s.SendGrad(g2, nil) if err != nil { t.FailNow() @@ -74,23 +73,21 @@ func TestFull(t *testing.T) { func TestMultipleInit(t *testing.T) { s := pserver.NewService() - var dummy int - err := s.FinishInitParams(0, &dummy) + err := s.FinishInitParams(0, nil) if err != nil { t.FailNow() } - err = s.FinishInitParams(0, &dummy) - if err.Error() != pserver.AlreadyInitialized { + err = s.FinishInitParams(0, nil) + if err != pserver.ErrAlreadyInitialized { t.FailNow() } } func TestUninitialized(t *testing.T) { s := pserver.NewService() - var dummy int - err := s.SendGrad(pserver.Gradient{}, &dummy) - if err.Error() != pserver.Uninitialized { + err := s.SendGrad(pserver.Gradient{}, nil) + if err != pserver.ErrUninitialized { t.FailNow() } } @@ -112,8 +109,7 @@ func TestBlockUntilInitialized(t *testing.T) { wg.Add(1) go func() { - var dummy int - err := s.Save("", &dummy) + err := s.Save("", nil) if err != nil { t.FailNow() } @@ -134,13 +130,12 @@ func TestBlockUntilInitialized(t *testing.T) { p.Name = "param_a" p.Content = []byte{1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0} p.ElementType = pserver.Int32 - var dummy int - err := s.InitParam(pserver.ParameterWithConfig{Param: p, Config: nil}, &dummy) + err := s.InitParam(pserver.ParameterWithConfig{p, nil}, nil) if err != nil { t.FailNow() } - err = s.FinishInitParams(0, &dummy) + err = s.FinishInitParams(0, nil) if err != nil { t.FailNow() } -- GitLab From 41af738af5f7928bc04a73a7fe05f410333dc259 Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Mon, 12 Jun 2017 20:30:49 +0000 Subject: [PATCH 0636/3256] fix according to comments --- go/connection/conn.go | 1 + go/master/service.go | 68 +++++++++++++++++++++++-------------------- 2 files changed, 37 insertions(+), 32 deletions(-) diff --git a/go/connection/conn.go b/go/connection/conn.go index 0bab2def1d9..ea6bf972f64 100644 --- a/go/connection/conn.go +++ b/go/connection/conn.go @@ -62,6 +62,7 @@ func (c *Conn) Connect(addr string) error { c.waitConn = nil } } else { + client.Close() return errors.New("client already set from a concurrent goroutine") } diff --git a/go/master/service.go b/go/master/service.go index c80037a3b35..30859d92963 100644 --- a/go/master/service.go +++ b/go/master/service.go @@ -11,10 +11,6 @@ import ( "github.com/PaddlePaddle/recordio" ) -const ( - targetTaskCount = 300 -) - // Service is the master server service. type Service struct { chunksPerTask int @@ -23,7 +19,7 @@ type Service struct { ready chan struct{} mu sync.Mutex - initBegan bool + initDone bool taskQueues taskQueues } @@ -104,54 +100,35 @@ func (s *Service) snapshot() error { return nil } -// SetDataset sets dataset to dispatch for the master server. -// -// SetDataset can be call multiple times. But only the first call will -// be honored. -func (s *Service) SetDataset(globPaths []string, dummy *int) error { - if len(globPaths) == 0 { - return errors.New("no dataset specified") - } - - s.mu.Lock() - defer s.mu.Unlock() - if s.initBegan { - // SetDataset already called. All trainer will call - // SetDataset, but we only handle the first one. Treat - // other calls as successful but do nothing. - return nil - } - - s.initBegan = true - +func getChunks(globPaths []string) ([]Chunk, error) { var chunks []Chunk var paths []string for _, s := range globPaths { match, err := filepath.Glob(s) if err != nil { - panic(err) + return nil, err } paths = append(paths, match...) } if len(paths) == 0 { - return errors.New("no valid datset specified") + return nil, errors.New("no valid datset specified") } for _, path := range paths { f, err := os.Open(path) if err != nil { - panic(err) + return nil, err } index, err := recordio.LoadIndex(f) if err != nil { - return err + return nil, err } err = f.Close() if err != nil { - return err + return nil, err } count := index.NumChunks() @@ -164,14 +141,41 @@ func (s *Service) SetDataset(globPaths []string, dummy *int) error { } } + return chunks, nil +} + +// SetDataset sets dataset to dispatch for the master server. +// +// SetDataset can be call multiple times. But only the first call will +// be honored. +func (s *Service) SetDataset(globPaths []string, dummy *int) error { + if len(globPaths) == 0 { + return errors.New("no dataset specified") + } + + s.mu.Lock() + defer s.mu.Unlock() + if s.initDone { + // Already initialized. All trainer will call + // SetDataset, but we only handle the first one. Treat + // other calls as successful but do nothing. + return nil + } + + chunks, err := getChunks(globPaths) + if err != nil { + return err + } + s.taskQueues.Todo = partition(chunks, s.chunksPerTask) - err := s.snapshot() + err = s.snapshot() if err != nil { return err } close(s.ready) + s.initDone = true return nil } @@ -193,7 +197,7 @@ func (s *Service) GetTask(dummy int, task *Task) error { // TODO(helin): client need to retry in this // error case. Gotcha: RPC client can't // compare returned error with predefined - // erros like io.EOF. Because interface don't + // errors like io.EOF. Because interface don't // have same dynamic value when in different // process. return errors.New("no more available task") -- GitLab From 9bd74ddac342601ead87a07763d14b353d0d5726 Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Mon, 12 Jun 2017 20:40:13 +0000 Subject: [PATCH 0637/3256] fix some linter warnings --- go/connection/conn.go | 7 ++++++- go/master/service.go | 1 - go/pserver/client.go | 2 +- go/pserver/service_test.go | 13 ++++++++----- 4 files changed, 15 insertions(+), 8 deletions(-) diff --git a/go/connection/conn.go b/go/connection/conn.go index ea6bf972f64..bc9b5f0617e 100644 --- a/go/connection/conn.go +++ b/go/connection/conn.go @@ -2,6 +2,7 @@ package connection import ( "errors" + "log" "net/rpc" "sync" ) @@ -62,7 +63,11 @@ func (c *Conn) Connect(addr string) error { c.waitConn = nil } } else { - client.Close() + err := client.Close() + if err != nil { + log.Println(err) + } + return errors.New("client already set from a concurrent goroutine") } diff --git a/go/master/service.go b/go/master/service.go index 30859d92963..3edbb7e9c0c 100644 --- a/go/master/service.go +++ b/go/master/service.go @@ -50,7 +50,6 @@ func partition(chunks []Chunk, chunksPerTask int) []taskEntry { if len(cur.Task.Chunks) > 0 { cur.Task.ID = id - id++ result = append(result, cur) } diff --git a/go/pserver/client.go b/go/pserver/client.go index bbe93cbb6b3..d8c65b2e137 100644 --- a/go/pserver/client.go +++ b/go/pserver/client.go @@ -83,7 +83,7 @@ func (c *Client) monitorPservers(l Lister, pserverNum int) { } monitor() - for _ = range ticker.C { + for range ticker.C { monitor() } } diff --git a/go/pserver/service_test.go b/go/pserver/service_test.go index c40cecd0b6b..175c3c3ad87 100644 --- a/go/pserver/service_test.go +++ b/go/pserver/service_test.go @@ -15,7 +15,7 @@ func TestFull(t *testing.T) { p.Name = "param_a" p.Content = []byte{1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0} p.ElementType = pserver.Int32 - err := s.InitParam(pserver.ParameterWithConfig{p, nil}, nil) + err := s.InitParam(pserver.ParameterWithConfig{Param: p, Config: nil}, nil) if err != nil { t.FailNow() } @@ -24,7 +24,7 @@ func TestFull(t *testing.T) { p1.Name = "param_b" p1.Content = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} p1.ElementType = pserver.Float32 - err = s.InitParam(pserver.ParameterWithConfig{p1, nil}, nil) + err = s.InitParam(pserver.ParameterWithConfig{Param: p1, Config: nil}, nil) if err != nil { t.FailNow() } @@ -95,13 +95,14 @@ func TestUninitialized(t *testing.T) { func TestBlockUntilInitialized(t *testing.T) { s := pserver.NewService() ch := make(chan struct{}, 2) + errCh := make(chan error, 2) var wg sync.WaitGroup wg.Add(1) go func() { var param pserver.Parameter err := s.GetParam("param_a", ¶m) if err != nil { - t.FailNow() + errCh <- err } wg.Done() ch <- struct{}{} @@ -111,7 +112,7 @@ func TestBlockUntilInitialized(t *testing.T) { go func() { err := s.Save("", nil) if err != nil { - t.FailNow() + errCh <- err } wg.Done() ch <- struct{}{} @@ -123,6 +124,8 @@ func TestBlockUntilInitialized(t *testing.T) { case <-ch: // some function returned before initialization is completed. t.FailNow() + case <-errCh: + t.FailNow() default: } @@ -130,7 +133,7 @@ func TestBlockUntilInitialized(t *testing.T) { p.Name = "param_a" p.Content = []byte{1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0} p.ElementType = pserver.Int32 - err := s.InitParam(pserver.ParameterWithConfig{p, nil}, nil) + err := s.InitParam(pserver.ParameterWithConfig{Param: p, Config: nil}, nil) if err != nil { t.FailNow() } -- GitLab From f6148eb24830ed9fab0f7ea18ea4437a006c692e Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Mon, 12 Jun 2017 20:42:06 +0000 Subject: [PATCH 0638/3256] rename getChunks to readChunks --- go/master/service.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/go/master/service.go b/go/master/service.go index 3edbb7e9c0c..6d6a4e30ab1 100644 --- a/go/master/service.go +++ b/go/master/service.go @@ -99,7 +99,7 @@ func (s *Service) snapshot() error { return nil } -func getChunks(globPaths []string) ([]Chunk, error) { +func readChunks(globPaths []string) ([]Chunk, error) { var chunks []Chunk var paths []string @@ -161,7 +161,7 @@ func (s *Service) SetDataset(globPaths []string, dummy *int) error { return nil } - chunks, err := getChunks(globPaths) + chunks, err := readChunks(globPaths) if err != nil { return err } -- GitLab From 0bebaa05beda6aca1d9cbedc3fb87c9978cd7df6 Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Tue, 13 Jun 2017 21:41:57 +0000 Subject: [PATCH 0639/3256] fix according to comments --- go/master/client.go | 2 + go/master/client_test.go | 15 ++++++ go/master/service.go | 110 +++++++++++++++++++++++---------------- go/pserver/client.go | 33 ++++++------ 4 files changed, 101 insertions(+), 59 deletions(-) diff --git a/go/master/client.go b/go/master/client.go index 791db5a9753..20c66340dc2 100644 --- a/go/master/client.go +++ b/go/master/client.go @@ -28,6 +28,8 @@ func NewClient(addr Addresser) *Client { func (c *Client) monitorMaster(addr Addresser) { lastMaster := "" monitor := func() { + // get the lastest address of the master server, + // connect to the new address once address changed. curMaster := addr.Address() if curMaster != lastMaster { if curMaster == "" { diff --git a/go/master/client_test.go b/go/master/client_test.go index 5abad0d8208..df708ad7912 100644 --- a/go/master/client_test.go +++ b/go/master/client_test.go @@ -11,6 +11,8 @@ import ( "testing" "time" + log "github.com/sirupsen/logrus" + "github.com/PaddlePaddle/Paddle/go/master" "github.com/PaddlePaddle/recordio" ) @@ -23,6 +25,8 @@ const ( var port int func init() { + log.SetLevel(log.ErrorLevel) + l, err := net.Listen("tcp", ":0") if err != nil { panic(err) @@ -91,6 +95,17 @@ func TestClientFull(t *testing.T) { t.Fatal(i, "should get error.") } + err = c.TaskFinished(tasks[0].ID) + if err != nil { + t.Fatal(err) + } + tasks = tasks[1:] + task, err := c.GetTask() + if err != nil { + t.Fatal(err) + } + tasks = append(tasks, task) + for _, task := range tasks { err = c.TaskFinished(task.ID) if err != nil { diff --git a/go/master/service.go b/go/master/service.go index 6d6a4e30ab1..c2ab3cc6d82 100644 --- a/go/master/service.go +++ b/go/master/service.go @@ -2,12 +2,13 @@ package master import ( "errors" - "log" "os" "path/filepath" "sync" "time" + log "github.com/sirupsen/logrus" + "github.com/PaddlePaddle/recordio" ) @@ -112,7 +113,7 @@ func readChunks(globPaths []string) ([]Chunk, error) { } if len(paths) == 0 { - return nil, errors.New("no valid datset specified") + return nil, errors.New("no valid dataset specified") } for _, path := range paths { @@ -170,6 +171,7 @@ func (s *Service) SetDataset(globPaths []string, dummy *int) error { err = s.snapshot() if err != nil { + log.Errorln(err) return err } @@ -178,6 +180,43 @@ func (s *Service) SetDataset(globPaths []string, dummy *int) error { return nil } +func (s *Service) checkTimeoutFunc(taskID int, epoch int) func() { + return func() { + s.mu.Lock() + defer s.mu.Unlock() + + t, ok := s.taskQueues.Pending[taskID] + if !ok { + return + } + + if t.Epoch != epoch { + // new epoch, task launched after the + // schedule of this timeout check. + return + } + + defer func() { + err := s.snapshot() + if err != nil { + log.Errorln(err) + } + }() + + delete(s.taskQueues.Pending, t.Task.ID) + + t.NumTimeout++ + if t.NumTimeout > s.timeoutMax { + log.Warningf("Task %v failed %d times, discard.\n", t.Task, t.NumTimeout) + s.taskQueues.Failed = append(s.taskQueues.Failed, t.Task) + return + } + + log.Warningf("Task %v failed %d times, retry.\n", t.Task, t.NumTimeout) + s.taskQueues.Todo = append(s.taskQueues.Todo, t) + } +} + // GetTask gets a new task from the service. func (s *Service) GetTask(dummy int, task *Task) error { select { @@ -190,19 +229,25 @@ func (s *Service) GetTask(dummy int, task *Task) error { if len(s.taskQueues.Todo) == 0 { if len(s.taskQueues.Done) == 0 { if len(s.taskQueues.Pending) == 0 { - return errors.New("all task failed") + err := errors.New("all task failed") + log.Warningln(err) + return err } // TODO(helin): client need to retry in this // error case. Gotcha: RPC client can't // compare returned error with predefined - // errors like io.EOF. Because interface don't + // errors like io.EOF, because interface don't // have same dynamic value when in different - // process. - return errors.New("no more available task") + // process. So we need to figure out a way for + // client to check this error correctly. + err := errors.New("no more available task") + log.Warningln(err) + return err } s.taskQueues.Todo = s.taskQueues.Done - s.taskQueues.Todo = nil + s.taskQueues.Done = nil + log.Infoln("No more todo task, but trainer is requesting task to do. Move all done task to todo.") } t := s.taskQueues.Todo[0] @@ -215,41 +260,9 @@ func (s *Service) GetTask(dummy int, task *Task) error { } *task = t.Task + log.Infof("Task #%d dispatched\n", task.ID) - time.AfterFunc(s.timeoutDur, func(taskID int, epoch int) func() { - return func() { - s.mu.Lock() - defer s.mu.Unlock() - - t, ok := s.taskQueues.Pending[taskID] - if !ok { - return - } - - if t.Epoch != epoch { - // new epoch, task launched after the - // schedule of this timeout check. - return - } - - defer func() { - err := s.snapshot() - if err != nil { - log.Println(err) - } - }() - - delete(s.taskQueues.Pending, t.Task.ID) - - t.NumTimeout++ - if t.NumTimeout > s.timeoutMax { - s.taskQueues.Failed = append(s.taskQueues.Failed, t.Task) - return - } - - s.taskQueues.Todo = append(s.taskQueues.Todo, t) - } - }(t.Task.ID, t.Epoch)) + time.AfterFunc(s.timeoutDur, s.checkTimeoutFunc(t.Task.ID, t.Epoch)) return nil } @@ -262,9 +275,13 @@ func (s *Service) TaskFinished(taskID int, dummy *int) error { s.mu.Lock() defer s.mu.Unlock() + log.Infof("Task %d finished\n", taskID) + t, ok := s.taskQueues.Pending[taskID] if !ok { - return errors.New("pending task not found") + err := errors.New("pending task not found") + log.Warningln(err) + return err } // task finished, reset timeout @@ -272,10 +289,15 @@ func (s *Service) TaskFinished(taskID int, dummy *int) error { s.taskQueues.Done = append(s.taskQueues.Done, t) delete(s.taskQueues.Pending, taskID) - if len(s.taskQueues.Pending) == 0 { + if len(s.taskQueues.Pending) == 0 && len(s.taskQueues.Todo) == 0 { + log.Infoln("No more todo and pending task, start a new pass.") s.taskQueues.Todo = append(s.taskQueues.Todo, s.taskQueues.Done...) s.taskQueues.Done = nil } - return s.snapshot() + err := s.snapshot() + if err != nil { + log.Errorln(err) + } + return err } diff --git a/go/pserver/client.go b/go/pserver/client.go index d8c65b2e137..afe1eecd015 100644 --- a/go/pserver/client.go +++ b/go/pserver/client.go @@ -57,26 +57,29 @@ func (c *Client) monitorPservers(l Lister, pserverNum int) { } for i := range lastServers { - if lastServers[i].Addr != curServers[i].Addr { - if curServers[i].Addr == "" { - err := c.pservers[i].Close() - if err != nil { - log.Println(err) - } - - continue - } + if lastServers[i].Addr == curServers[i].Addr { + continue + } - err := c.pservers[i].Connect(curServers[i].Addr) + if curServers[i].Addr == "" { + err := c.pservers[i].Close() if err != nil { log.Println(err) - - // connect to addr failed, set - // to last known addr in order - // to retry next time. - curServers[i].Addr = lastServers[i].Addr } + + continue } + + err := c.pservers[i].Connect(curServers[i].Addr) + if err != nil { + log.Println(err) + + // connect to addr failed, set + // to last known addr in order + // to retry next time. + curServers[i].Addr = lastServers[i].Addr + } + } lastServers = curServers -- GitLab From 102e10afa148b0c509f76fd3f78d9df7b1aa71fe Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Tue, 13 Jun 2017 22:21:36 +0000 Subject: [PATCH 0640/3256] improve comment --- go/master/service.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/go/master/service.go b/go/master/service.go index c2ab3cc6d82..1e2a34972bb 100644 --- a/go/master/service.go +++ b/go/master/service.go @@ -237,10 +237,11 @@ func (s *Service) GetTask(dummy int, task *Task) error { // TODO(helin): client need to retry in this // error case. Gotcha: RPC client can't // compare returned error with predefined - // errors like io.EOF, because interface don't - // have same dynamic value when in different - // process. So we need to figure out a way for - // client to check this error correctly. + // errors like io.EOF, because the error + // instance deserialized from RPC is a + // different instance than the error defined + // in package. So we need to figure out a way + // for client to check this error correctly. err := errors.New("no more available task") log.Warningln(err) return err -- GitLab From 13867a0cb53f0169887c4dc6373a6c4a61986955 Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Wed, 14 Jun 2017 01:08:03 +0000 Subject: [PATCH 0641/3256] fix build issue caused by rebase --- go/pserver/service_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/go/pserver/service_test.go b/go/pserver/service_test.go index 175c3c3ad87..b746d13e1ca 100644 --- a/go/pserver/service_test.go +++ b/go/pserver/service_test.go @@ -79,7 +79,7 @@ func TestMultipleInit(t *testing.T) { } err = s.FinishInitParams(0, nil) - if err != pserver.ErrAlreadyInitialized { + if err.Error() != pserver.AlreadyInitialized { t.FailNow() } } @@ -87,7 +87,7 @@ func TestMultipleInit(t *testing.T) { func TestUninitialized(t *testing.T) { s := pserver.NewService() err := s.SendGrad(pserver.Gradient{}, nil) - if err != pserver.ErrUninitialized { + if err.Error() != pserver.Uninitialized { t.FailNow() } } -- GitLab From ebba2b139bec7fe44fb4d14032011271b68a3fe2 Mon Sep 17 00:00:00 2001 From: qiaolongfei Date: Wed, 14 Jun 2017 10:08:01 +0800 Subject: [PATCH 0642/3256] update code with new cclient --- go/pserver/cclient/test/main.c | 7 ++- go/pserver/cclient/test/test_cclient.c | 57 ++++++++++---------- paddle/trainer/NewRemoteParameterUpdater.cpp | 11 +--- paddle/trainer/NewRemoteParameterUpdater.h | 7 +-- 4 files changed, 36 insertions(+), 46 deletions(-) diff --git a/go/pserver/cclient/test/main.c b/go/pserver/cclient/test/main.c index c8aed0f2e8e..d052f4f5a81 100644 --- a/go/pserver/cclient/test/main.c +++ b/go/pserver/cclient/test/main.c @@ -11,10 +11,9 @@ void sendGrads(paddle_pserver_client c) { unsigned char grad_a[2000] = {2}; unsigned char grad_b[3000] = {3}; - paddle_gradient grads[2] = { - {"param_a", PADDLE_ELEMENT_TYPE_FLOAT32, grad_a, 2000}, - {"param_b", PADDLE_ELEMENT_TYPE_FLOAT32, grad_b, 3000}}; - + paddle_gradient grad1 = {"param_a", PADDLE_ELEMENT_TYPE_FLOAT32, grad_a, 2000}; + paddle_gradient grad2 = {"param_b", PADDLE_ELEMENT_TYPE_FLOAT32, grad_b, 3000}; + paddle_gradient* grads[2] = {&grad1, &grad2}; if (paddle_send_grads(c, grads, 2)) { fail(); } diff --git a/go/pserver/cclient/test/test_cclient.c b/go/pserver/cclient/test/test_cclient.c index 9083064eeeb..6830479fe97 100644 --- a/go/pserver/cclient/test/test_cclient.c +++ b/go/pserver/cclient/test/test_cclient.c @@ -30,30 +30,36 @@ void print_parameter(paddle_gradient* param) { int main() { char addr[] = "localhost:3000"; - client c = paddle_new_pserver_client(addr, 1); + paddle_pserver_client c = paddle_new_pserver_client(addr, 1); char* names[] = {"param_a", "param_b"}; + retry: + printf("init parameter to pserver:\n"); + + real param_content1[] = {0.1, 0.2, 0.3}; + real param_content2[] = {0.4, 0.5, 0.6}; + paddle_parameter** params = + (paddle_parameter**)malloc(sizeof(paddle_parameter*) * 2); + params[0] = (paddle_parameter*)malloc(sizeof(paddle_parameter)); + params[0]->name = names[0]; + params[0]->content = (unsigned char*)param_content1; + params[0]->content_len = 3 * sizeof(real); + params[0]->element_type = PADDLE_ELEMENT_TYPE_FLOAT32; + + params[1] = (paddle_parameter*)malloc(sizeof(paddle_parameter)); + params[1]->name = names[1]; + params[1]->content = (unsigned char*)param_content2; + params[1]->content_len = 3 * sizeof(real); + params[1]->element_type = PADDLE_ELEMENT_TYPE_INT32; if (paddle_begin_init_params(c)) { - paddle_parameter param; - real param_content1[] = {0.1, 0.2, 0.3}; - param.element_type = PADDLE_ELEMENT_TYPE_FLOAT32; - param.name = names[0]; - param.content = (unsigned char*)param_content1; - param.content_len = 3 * sizeof(real); - if (paddle_init_param(c, param, NULL, 0) != 0) { + if (paddle_init_param(c, *params[0], NULL, 0) != 0) { goto retry; } - real param_content2[] = {0.4, 0.5, 0.6}; - param.element_type = PADDLE_ELEMENT_TYPE_INT32; - param.name = names[1]; - param.content = (unsigned char*)param_content2; - param.content_len = 3 * sizeof(real); - if (paddle_init_param(c, param, NULL, 0) != 0) { + if (paddle_init_param(c, *params[1], NULL, 0) != 0) { goto retry; } - if (paddle_finish_init_params(c) != 0) { goto retry; } @@ -61,13 +67,13 @@ retry: fail(); } - printf("get initialized parameters from pserver:\n"); - paddle_parameter* param_ptrs[2] = {NULL, NULL}; - if (paddle_get_params(c, names, param_ptrs, 2) != 0) { + printf("get inited parameters from pserver:\n"); + // get parameters again by reusing the allocated parameter buffers. + if (paddle_get_params(c, params, 2) != 0) { fail(); } - print_parameter(param_ptrs[0]); - print_parameter(param_ptrs[1]); + print_parameter(params[0]); + print_parameter(params[1]); printf("send gradient to pserver:\n"); real gradient_content1[] = {0.01, 0.02, 0.03}; @@ -87,6 +93,7 @@ retry: grads[1]->content_len = 3 * sizeof(real); grads[1]->element_type = PADDLE_ELEMENT_TYPE_INT32; + printf("print gradient sent to pserver:\n"); print_parameter(grads[0]); print_parameter(grads[1]); @@ -96,15 +103,11 @@ retry: printf("get updated parameters from pserver:\n"); // get parameters again by reusing the allocated parameter buffers. - if (paddle_get_params(c, names, param_ptrs, 2) != 0) { + if (paddle_get_params(c, params, 2) != 0) { fail(); } - - print_parameter(param_ptrs[0]); - print_parameter(param_ptrs[1]); - - paddle_release_param(param_ptrs[0]); - paddle_release_param(param_ptrs[1]); + print_parameter(params[0]); + print_parameter(params[1]); if (paddle_save_model(c, "/tmp/") != 0) { fail(); diff --git a/paddle/trainer/NewRemoteParameterUpdater.cpp b/paddle/trainer/NewRemoteParameterUpdater.cpp index b3655d9d025..3d4d23afc79 100644 --- a/paddle/trainer/NewRemoteParameterUpdater.cpp +++ b/paddle/trainer/NewRemoteParameterUpdater.cpp @@ -25,7 +25,6 @@ NewRemoteParameterUpdater::NewRemoteParameterUpdater( : parameterClient_(-1), newParameters_(nullptr), newGradients_(nullptr), - names_(nullptr), pserverSpec_(pserverSpec) {} void NewRemoteParameterUpdater::init( @@ -41,12 +40,6 @@ void NewRemoteParameterUpdater::init( parameterClient_ = paddle_new_pserver_client((char *)pserverSpec_.c_str(), FLAGS_trainer_id == 0); - // init names_ for get parameter through paddle_cclient - names_ = (char **)malloc(parameterSize() * sizeof(char *)); - for (int i = 0; i < parameterSize(); ++i) { - names_[i] = (char *)parameters_[i]->getName().c_str(); - } - // init new parameter and gradient. newParameters_ = initNewParameter(PARAMETER_VALUE); newGradients_ = initNewParameter(PARAMETER_GRADIENT); @@ -68,7 +61,7 @@ void NewRemoteParameterUpdater::init( LOG(INFO) << "paddle_begin_init_params done"; } else { paddle_get_params( - parameterClient_, names_, newParameters_, parameterSize()); + parameterClient_, newParameters_, parameterSize()); } LOG(INFO) << "NewRemoteParameterUpdater initialized"; @@ -80,7 +73,7 @@ void NewRemoteParameterUpdater::finishBatch(real cost) { // send gradient to parameter server. paddle_send_grads(parameterClient_, newGradients_, parameterSize()); // get the updated parameter from parameterClient. - paddle_get_params(parameterClient_, names_, newParameters_, parameterSize()); + paddle_get_params(parameterClient_, newParameters_, parameterSize()); // clear gradient after update parameter. for (auto ¶ : parameters_) { diff --git a/paddle/trainer/NewRemoteParameterUpdater.h b/paddle/trainer/NewRemoteParameterUpdater.h index 1f22c15cef5..f735185f62b 100644 --- a/paddle/trainer/NewRemoteParameterUpdater.h +++ b/paddle/trainer/NewRemoteParameterUpdater.h @@ -32,9 +32,6 @@ public: NewRemoteParameterUpdater(const OptimizationConfig& config, const std::string pserverSpec); ~NewRemoteParameterUpdater() { - if (names_ != nullptr) { - free(names_); - } releaseNewParameter(newParameters_); releaseNewParameter(newGradients_); if (parameterClient_ >= 0) paddle_pserver_client_release(parameterClient_); @@ -105,13 +102,11 @@ private: protected: /// internal parameter client object for exchanging data with pserver - client parameterClient_; + paddle_pserver_client parameterClient_; /// the parameters for new pserver client paddle_parameter** newParameters_; /// the gradinets for new pserver client paddle_parameter** newGradients_; - /// the names for new parameters. - char** names_; /// the specification of parameter server "host1:port,host1:port" std::string pserverSpec_; }; -- GitLab From 1814fc294e58356db774c1308463c0bde095ebfb Mon Sep 17 00:00:00 2001 From: dzhwinter Date: Wed, 14 Jun 2017 10:20:08 +0800 Subject: [PATCH 0643/3256] "fix lr_policy serialization" --- paddle/optimizer/CMakeLists.txt | 1 - paddle/optimizer/adadelta_optimizer.cc | 11 +++++------ paddle/optimizer/adadelta_optimizer.h | 14 +++++--------- paddle/optimizer/adagrad_optimizer.cc | 9 +++++---- paddle/optimizer/adagrad_optimizer.h | 9 ++++----- paddle/optimizer/adam_optimizer.cc | 10 +++++----- paddle/optimizer/adam_optimizer.h | 8 +++----- paddle/optimizer/lr_policy.h | 13 ++++++------- paddle/optimizer/parameter_optimizer.h | 5 ++++- paddle/optimizer/serialization.h | 12 ------------ paddle/optimizer/sgd_optimizer.cc | 9 +++++---- paddle/optimizer/sgd_optimizer.h | 1 - 12 files changed, 42 insertions(+), 60 deletions(-) diff --git a/paddle/optimizer/CMakeLists.txt b/paddle/optimizer/CMakeLists.txt index e93ba102945..bafd8a9b97a 100644 --- a/paddle/optimizer/CMakeLists.txt +++ b/paddle/optimizer/CMakeLists.txt @@ -12,7 +12,6 @@ set(OPITMIZER_SRCS add_library(optimizer STATIC ${OPITMIZER_SRCS}) add_dependencies(optimizer gen_proto_cpp) -add_simple_unittest(tensor_test) add_simple_unittest(serialization_test) add_simple_unittest(parameter_optimizer_test) add_dependencies(parameter_optimizer_test optimizer) diff --git a/paddle/optimizer/adadelta_optimizer.cc b/paddle/optimizer/adadelta_optimizer.cc index d1c6571d9b4..465ad5e0d20 100644 --- a/paddle/optimizer/adadelta_optimizer.cc +++ b/paddle/optimizer/adadelta_optimizer.cc @@ -27,23 +27,22 @@ void AdadeltaOptimizer::Update(const Tensor* gradient) { const char* AdadeltaOptimizer::SerializeState(int* state_len) { AdadeltaOptimizerState state; - state.set_learning_rate(lr_policy_->LearningRate(num_sample_passed_)); + // TODO(zhihong) : add lr_policy serialization state.set_num_sample_passed(num_sample_passed_); TensorToProto(*parameter_, state.mutable_parameter()); TensorToProto(*accum_gradient_, state.mutable_accum_gradient()); TensorToProto(*accum_delta_, state.mutable_accum_delta()); TensorToProto(*update_delta_, state.mutable_update_delta()); - - *state_len = - CalStateSize(parameter_, accum_gradient_, accum_delta_, update_delta_); - return state.SerializeAsString().c_str(); + auto str = state.SerializeAsString(); + *state_len = str.size(); + return str.c_str(); } void AdadeltaOptimizer::DeserializeState(const std::string& str) { AdadeltaOptimizerState state; state.ParseFromString(str); - lr_policy_->set(state.learning_rate()); + // TODO(zhihong) : add lr_policy DeserializeState num_sample_passed_ = state.num_sample_passed(); ProtoToTensor(state.parameter(), parameter_); diff --git a/paddle/optimizer/adadelta_optimizer.h b/paddle/optimizer/adadelta_optimizer.h index 58a26ebb7a7..1d5eab097f5 100644 --- a/paddle/optimizer/adadelta_optimizer.h +++ b/paddle/optimizer/adadelta_optimizer.h @@ -10,17 +10,13 @@ public: AdadeltaOptimizer( Tensor *parameter, LrPolicy *lr, double rho, double epsilon, double decay) : ParameterOptimizer(parameter, lr), + accum_gradient_(new Tensor(parameter->size())), + accum_delta_(new Tensor(parameter->size())), + update_delta_(new Tensor(parameter->size())), rho_(rho), epsilon_(epsilon), - decay_(decay) { - size_t size = parameter->size(); - if (accum_gradient_) delete accum_gradient_; - accum_gradient_ = new Tensor(size); - if (accum_delta_) delete accum_delta_; - accum_delta_ = new Tensor(size); - if (update_delta_) delete update_delta_; - update_delta_ = new Tensor(size); - } + decay_(decay) {} + ~AdadeltaOptimizer() { if (accum_gradient_) delete accum_gradient_; if (accum_delta_) delete accum_delta_; diff --git a/paddle/optimizer/adagrad_optimizer.cc b/paddle/optimizer/adagrad_optimizer.cc index ebc4d9e83ae..bdaa7877d2b 100644 --- a/paddle/optimizer/adagrad_optimizer.cc +++ b/paddle/optimizer/adagrad_optimizer.cc @@ -19,19 +19,20 @@ void AdagradOptimizer::Update(const Tensor* gradient) { } const char* AdagradOptimizer::SerializeState(int* state_len) { AdagradOptimizerState state; - state.set_learning_rate(lr_policy_->LearningRate(num_sample_passed_)); + // TODO(zhihong) : add lr_policy serialization state.set_num_sample_passed(num_sample_passed_); TensorToProto(*parameter_, state.mutable_parameter()); TensorToProto(*accum_gradient_, state.mutable_accum_gradient()); - *state_len = CalStateSize(parameter_, accum_gradient_); - return state.SerializeAsString().c_str(); + auto str = state.SerializeAsString(); + *state_len = str.size(); + return str.c_str(); } void AdagradOptimizer::DeserializeState(const std::string& str) { AdagradOptimizerState state; state.ParseFromString(str); - lr_policy_->set(state.learning_rate()); + // TODO(zhihong) : add lr_policy DeserializeState num_sample_passed_ = state.num_sample_passed(); ProtoToTensor(state.parameter(), parameter_); ProtoToTensor(state.accum_gradient(), accum_gradient_); diff --git a/paddle/optimizer/adagrad_optimizer.h b/paddle/optimizer/adagrad_optimizer.h index 90fc1dd4ac9..15d0a965ad0 100644 --- a/paddle/optimizer/adagrad_optimizer.h +++ b/paddle/optimizer/adagrad_optimizer.h @@ -11,11 +11,10 @@ public: LrPolicy *lr, double epsilon, double decay) - : ParameterOptimizer(parameter, lr), epsilon_(epsilon), decay_(decay) { - size_t size = parameter->size(); - if (accum_gradient_) delete accum_gradient_; - accum_gradient_ = new Tensor(size); - } + : ParameterOptimizer(parameter, lr), + accum_gradient_(new Tensor(parameter->size())), + epsilon_(epsilon), + decay_(decay) {} ~AdagradOptimizer() { if (accum_gradient_) delete accum_gradient_; } diff --git a/paddle/optimizer/adam_optimizer.cc b/paddle/optimizer/adam_optimizer.cc index 53b3350d68f..96cd6e4a129 100644 --- a/paddle/optimizer/adam_optimizer.cc +++ b/paddle/optimizer/adam_optimizer.cc @@ -24,20 +24,20 @@ void AdamOptimizer::Update(const Tensor *gradient) { const char *AdamOptimizer::SerializeState(int *state_len) { AdamOptimizerState state; - state.set_learning_rate(lr_policy_->LearningRate(num_sample_passed_)); + // TODO(zhihong) : add lr_policy serialization state.set_num_sample_passed(num_sample_passed_); TensorToProto(*parameter_, state.mutable_parameter()); TensorToProto(*velocitys_, state.mutable_momentums()); - - *state_len = CalStateSize(parameter_, momentums_, velocitys_); - return state.SerializeAsString().c_str(); + auto str = state.SerializeAsString(); + *state_len = str.size(); + return str.c_str(); } void AdamOptimizer::DeserializeState(const std::string &str) { AdamOptimizerState state; state.ParseFromString(str); - lr_policy_->set(state.learning_rate()); + // TODO(zhihong) : add lr_policy DeserializeState num_sample_passed_ = state.num_sample_passed(); ProtoToTensor(state.parameter(), parameter_); diff --git a/paddle/optimizer/adam_optimizer.h b/paddle/optimizer/adam_optimizer.h index 04bc01154fb..0ea4c8bb847 100644 --- a/paddle/optimizer/adam_optimizer.h +++ b/paddle/optimizer/adam_optimizer.h @@ -14,14 +14,12 @@ public: double epsilon, double decay) : ParameterOptimizer(parameter, lr), + momentums_(new Tensor(parameter->size())), + velocitys_(new Tensor(parameter->size())), beta_1_(beta_1), beta_2_(beta_2), epsilon_(epsilon), - decay_(decay) { - size_t size = parameter->size(); - momentums_ = new Tensor(size); - velocitys_ = new Tensor(size); - } + decay_(decay) {} ~AdamOptimizer() { if (momentums_) delete momentums_; if (velocitys_) delete velocitys_; diff --git a/paddle/optimizer/lr_policy.h b/paddle/optimizer/lr_policy.h index 686ba226715..be2bf89504e 100644 --- a/paddle/optimizer/lr_policy.h +++ b/paddle/optimizer/lr_policy.h @@ -10,7 +10,8 @@ class LrPolicy { public: virtual ~LrPolicy() {} virtual double LearningRate(const uint64_t num_sample_passed) = 0; - virtual void set(double current_learning_rate) = 0; + virtual const char *SerializeState(int *state_len) = 0; + virtual void DeserializeState(const std::string &state) = 0; }; // constant learning rate policy @@ -20,9 +21,8 @@ public: double LearningRate(const uint64_t num_sample_passed) { return learning_rate; } - void set(double current_learning_rate) { - learning_rate = current_learning_rate; - } + const char *SerializeState(int *state_len); + void DeserializeState(const std::string &state); private: double learning_rate; @@ -35,9 +35,8 @@ public: double LearningRate(const uint64_t num_sample_passed) { return std::max(learning_rate - lr_decay_a * num_sample_passed, lr_decay_b); } - void set(double current_learning_rate) { - learning_rate = current_learning_rate; - } + const char *SerializeState(int *state_len); + void DeserializeState(const std::string &state); private: double learning_rate; diff --git a/paddle/optimizer/parameter_optimizer.h b/paddle/optimizer/parameter_optimizer.h index 658b22406d6..d89c9abb791 100644 --- a/paddle/optimizer/parameter_optimizer.h +++ b/paddle/optimizer/parameter_optimizer.h @@ -19,7 +19,10 @@ public: */ ParameterOptimizer(Tensor *parameter, LrPolicy *lr) : parameter_(parameter), lr_policy_(lr), num_sample_passed_(0) {} - virtual ~ParameterOptimizer() { delete parameter_; }; + virtual ~ParameterOptimizer() { + delete parameter_; + delete lr_policy_; + } static ParameterOptimizer *Create(const std::string &config_proto, Tensor *parameter); diff --git a/paddle/optimizer/serialization.h b/paddle/optimizer/serialization.h index 07874502a50..21de3259a88 100644 --- a/paddle/optimizer/serialization.h +++ b/paddle/optimizer/serialization.h @@ -10,18 +10,6 @@ namespace paddle { namespace optimizer { -static unsigned CalStateSize() { return 0; } - -template -unsigned CalStateSize(const HEAD& head, const TAIL&... tail) { - return sizeof head + CalStateSize(tail...); -} - -template -unsigned CalStateSize(const Tensor* head, const TAIL&... tail) { - return head->size() + CalStateSize(tail...); -} - static void TensorToProto(const Tensor& tensor, TensorProto* proto) { proto->set_data_type(TensorProto::PADDLE_ELEMENT_TYPE_FLOAT32); std::stringstream os; diff --git a/paddle/optimizer/sgd_optimizer.cc b/paddle/optimizer/sgd_optimizer.cc index 8b4ea777d2d..66843ecb4b2 100644 --- a/paddle/optimizer/sgd_optimizer.cc +++ b/paddle/optimizer/sgd_optimizer.cc @@ -29,19 +29,20 @@ void SGDOptimizer::Update(const Tensor *gradient) { const char *SGDOptimizer::SerializeState(int *state_len) { SGDOptimizerState state; - state.set_learning_rate(lr_policy_->LearningRate(num_sample_passed_)); + // TODO(zhihong) : add lr_policy serialization state.set_num_sample_passed(num_sample_passed_); TensorToProto(*parameter_, state.mutable_parameter()); TensorToProto(*momentums_, state.mutable_momentums()); - *state_len = CalStateSize(parameter_, momentums_); - return state.SerializeAsString().c_str(); + auto str = state.SerializeAsString(); + *state_len = str.size(); + return str.c_str(); } void SGDOptimizer::DeserializeState(const std::string &str) { SGDOptimizerState state; state.ParseFromString(str); - lr_policy_->set(state.learning_rate()); + // TODO(zhihong) : add lr_policy DeserializeState num_sample_passed_ = state.num_sample_passed(); ProtoToTensor(state.parameter(), parameter_); diff --git a/paddle/optimizer/sgd_optimizer.h b/paddle/optimizer/sgd_optimizer.h index 1d4ea46f1a4..b74a902e1aa 100644 --- a/paddle/optimizer/sgd_optimizer.h +++ b/paddle/optimizer/sgd_optimizer.h @@ -16,7 +16,6 @@ public: if (momentum_ != 0.0) { size_t size = parameter->size(); // TODO: fix it with align aware allocator bind to Tensor - if (momentums_) delete momentums_; momentums_ = new Tensor(size); } } -- GitLab From c093a2433600b7666fd8f46aca01f4d5e40b02f6 Mon Sep 17 00:00:00 2001 From: qiaolongfei Date: Wed, 14 Jun 2017 11:27:19 +0800 Subject: [PATCH 0644/3256] clang format check --- go/pserver/cclient/test/main.c | 9 ++++++--- go/pserver/cclient/test/test_cclient.c | 2 +- paddle/trainer/NewRemoteParameterUpdater.cpp | 3 +-- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/go/pserver/cclient/test/main.c b/go/pserver/cclient/test/main.c index d052f4f5a81..07e1b86b432 100644 --- a/go/pserver/cclient/test/main.c +++ b/go/pserver/cclient/test/main.c @@ -11,8 +11,10 @@ void sendGrads(paddle_pserver_client c) { unsigned char grad_a[2000] = {2}; unsigned char grad_b[3000] = {3}; - paddle_gradient grad1 = {"param_a", PADDLE_ELEMENT_TYPE_FLOAT32, grad_a, 2000}; - paddle_gradient grad2 = {"param_b", PADDLE_ELEMENT_TYPE_FLOAT32, grad_b, 3000}; + paddle_gradient grad1 = { + "param_a", PADDLE_ELEMENT_TYPE_FLOAT32, grad_a, 2000}; + paddle_gradient grad2 = { + "param_b", PADDLE_ELEMENT_TYPE_FLOAT32, grad_b, 3000}; paddle_gradient* grads[2] = {&grad1, &grad2}; if (paddle_send_grads(c, grads, 2)) { fail(); @@ -76,7 +78,8 @@ retry: } } - for (int i = 0; i < 100; i++) { + int i; + for (i = 0; i < 100; i++) { sendGrads(c); getParams(c); } diff --git a/go/pserver/cclient/test/test_cclient.c b/go/pserver/cclient/test/test_cclient.c index 6830479fe97..0f9c2ef8011 100644 --- a/go/pserver/cclient/test/test_cclient.c +++ b/go/pserver/cclient/test/test_cclient.c @@ -40,7 +40,7 @@ retry: real param_content1[] = {0.1, 0.2, 0.3}; real param_content2[] = {0.4, 0.5, 0.6}; paddle_parameter** params = - (paddle_parameter**)malloc(sizeof(paddle_parameter*) * 2); + (paddle_parameter**)malloc(sizeof(paddle_parameter*) * 2); params[0] = (paddle_parameter*)malloc(sizeof(paddle_parameter)); params[0]->name = names[0]; params[0]->content = (unsigned char*)param_content1; diff --git a/paddle/trainer/NewRemoteParameterUpdater.cpp b/paddle/trainer/NewRemoteParameterUpdater.cpp index 3d4d23afc79..f25ce2f7f06 100644 --- a/paddle/trainer/NewRemoteParameterUpdater.cpp +++ b/paddle/trainer/NewRemoteParameterUpdater.cpp @@ -60,8 +60,7 @@ void NewRemoteParameterUpdater::init( paddle_finish_init_params(parameterClient_); LOG(INFO) << "paddle_begin_init_params done"; } else { - paddle_get_params( - parameterClient_, newParameters_, parameterSize()); + paddle_get_params(parameterClient_, newParameters_, parameterSize()); } LOG(INFO) << "NewRemoteParameterUpdater initialized"; -- GitLab From 0e45f952a29d9e9e02545e06cf81218c2992cc11 Mon Sep 17 00:00:00 2001 From: hedaoyuan Date: Wed, 14 Jun 2017 13:44:05 +0800 Subject: [PATCH 0645/3256] Add a NNPACKConvFunction. --- paddle/function/nnpack/NNPACKConvOp.cpp | 224 ++++++++++++++++++++++++ 1 file changed, 224 insertions(+) create mode 100644 paddle/function/nnpack/NNPACKConvOp.cpp diff --git a/paddle/function/nnpack/NNPACKConvOp.cpp b/paddle/function/nnpack/NNPACKConvOp.cpp new file mode 100644 index 00000000000..57a6681f294 --- /dev/null +++ b/paddle/function/nnpack/NNPACKConvOp.cpp @@ -0,0 +1,224 @@ +/* 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 "ConvOp.h" +#include "nnpack.h" + +DEFINE_bool(nnpack_allocate_outside, + false, + "Allocate and free workspace memory outside the NNPACK interface."); +DEFINE_int32(nnpack_num_threads, + 0, + "The number of nnpack threads" + "default: 0; 0 to disable threadpool."); + +namespace paddle { + +nnp_convolution_algorithm get_nnp_convolution_algorithm( + const std::string& algorithm) { + if (algorithm == "auto") { + return nnp_convolution_algorithm_auto; + } else if (algorithm == "ft8x8") { + return nnp_convolution_algorithm_ft8x8; + } else if (algorithm == "ft16x16") { + return nnp_convolution_algorithm_ft16x16; + } else if (algorithm == "wt8x8") { + return nnp_convolution_algorithm_wt8x8; + } else if (algorithm == "implicit-gemm") { + return nnp_convolution_algorithm_implicit_gemm; + } else if (algorithm == "direct") { + return nnp_convolution_algorithm_direct; + } else { + return nnp_convolution_algorithm_auto; + } +} + +template +class NNPACKConvFunction : public ConvFunctionBase { +public: + void init(const FuncConfig& config) override { + ConvFunctionBase::init(config); + CHECK_EQ(groups_, (size_t)1); + algorithm_ = get_nnp_convolution_algorithm(config.get("algo")); + // algorithm_ = nnp_convolution_algorithm_auto; + transform_strategy_ = nnp_convolution_transform_strategy_compute; + nnp_status status = nnp_initialize(); + CHECK_EQ(status, nnp_status_success); + workspaceBuffer_ = nullptr; + workspaceSize_ = 0; + + threadpool_ = nullptr; + if (FLAGS_nnpack_num_threads) { + threadpool_ = pthreadpool_create(FLAGS_nnpack_num_threads); + VLOG(3) << "Number of threads " + << pthreadpool_get_threads_count(threadpool_); + } + } + + ~NNPACKConvFunction() { + if (threadpool_) { + pthreadpool_destroy(threadpool_); + } + } + + void calc(const BufferArgs& inputs, const BufferArgs& outputs) override { + CHECK_EQ(numInputs_, inputs.size()); + CHECK_EQ(numOutputs_, outputs.size()); + CHECK_EQ(outputs[0].getArgType(), ASSIGN_TO); + const TensorShape& input = inputs[0].shape(); + const TensorShape& filter = inputs[1].shape(); + const TensorShape& output = outputs[0].shape(); + check(input, filter, output); + + size_t batchSize = input[0]; + size_t inputChannels = input[1]; + size_t inputHeight = input[2]; + size_t inputWidth = input[3]; + size_t filterHeight = getFilterHeight(filter); + size_t filterWidth = getFilterWidth(filter); + size_t outputChannels = output[1]; + // size_t outputHeight = output[2]; + // size_t outputWidth = output[3]; + + nnp_size inputSize = {.width = inputWidth, .height = inputHeight}; + nnp_padding padding = {.top = paddingH(), + .right = paddingW(), + .bottom = paddingH(), + .left = paddingW()}; + nnp_size kernelSize = {.width = filterWidth, .height = filterHeight}; + nnp_size outputSubsampling = {.width = strideW(), .height = strideH()}; + + float* inputData = inputs[0].data(); + float* filterData = inputs[1].data(); + float* outputData = outputs[0].data(); + + void* bufferPtr = nullptr; + size_t* sizePtr = nullptr; + size_t needSize; + if (FLAGS_nnpack_allocate_outside) { + if (batchSize == 1) { + nnp_status status = nnp_convolution_inference(algorithm_, + transform_strategy_, + inputChannels, + outputChannels, + inputSize, + padding, + kernelSize, + outputSubsampling, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + &needSize, + nnp_activation_identity, + nullptr, + nullptr, + nullptr); + CHECK_EQ(status, nnp_status_success); + } else { + // only supports stride = 1 + CHECK_EQ(stride_, 1); + nnp_status status = nnp_convolution_output(algorithm_, + batchSize, + inputChannels, + outputChannels, + inputSize, + padding, + kernelSize, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + &needSize, + nnp_activation_identity, + nullptr, + nullptr, + nullptr); + CHECK_EQ(status, nnp_status_success); + } + + LOG(INFO) << "workspace size is " << needSize; + if (needSize > workspaceSize_) { + workspaceSize_ = needSize; + if (workspaceBuffer_) { + free(workspaceBuffer_); + } else { + posix_memalign(&workspaceBuffer_, 64, needSize); + } + } + + if (needSize) { + bufferPtr = workspaceBuffer_; + sizePtr = &needSize; + } + } + + if (batchSize == 1) { + nnp_status status = + nnp_convolution_inference(algorithm_, + transform_strategy_, + inputChannels, + outputChannels, + inputSize, + padding, + kernelSize, + outputSubsampling, + inputData, + filterData, + nullptr, /* bias */ + outputData, + bufferPtr, + sizePtr, + nnp_activation_identity, + nullptr, + threadpool_, /* threadpool */ + nullptr); + CHECK_EQ(status, nnp_status_success); + } else { + // only supports stride = 1 + CHECK_EQ(stride_, 1); + nnp_status status = nnp_convolution_output(algorithm_, + batchSize, + inputChannels, + outputChannels, + inputSize, + padding, + kernelSize, + inputData, + filterData, + nullptr, /* bias */ + outputData, + bufferPtr, + sizePtr, + nnp_activation_identity, + nullptr, + threadpool_, /* threadpool */ + nullptr); + CHECK_EQ(status, nnp_status_success); + } + } + +private: + nnp_convolution_algorithm algorithm_; + nnp_convolution_transform_strategy transform_strategy_; + void* workspaceBuffer_; + size_t workspaceSize_; + pthreadpool_t threadpool_; +}; + +REGISTER_TYPED_FUNC(NNPACKConv, CPU, NNPACKConvFunction); + +} // namespace paddle -- GitLab From 997cef2e63ef4d7c99c58710289f7581d2af08c6 Mon Sep 17 00:00:00 2001 From: xzl Date: Wed, 14 Jun 2017 17:26:08 +0800 Subject: [PATCH 0646/3256] tiny modify --- paddle/parameter/ParameterUpdaterHook.cpp | 33 +++++++++---------- python/paddle/trainer/config_parser.py | 4 +-- python/paddle/trainer_config_helpers/attrs.py | 8 +++-- 3 files changed, 23 insertions(+), 22 deletions(-) diff --git a/paddle/parameter/ParameterUpdaterHook.cpp b/paddle/parameter/ParameterUpdaterHook.cpp index 5e8c77ced03..a581cc047df 100644 --- a/paddle/parameter/ParameterUpdaterHook.cpp +++ b/paddle/parameter/ParameterUpdaterHook.cpp @@ -20,6 +20,7 @@ limitations under the License. */ #include #include #include +#include #include "paddle/math/Vector.h" #include "paddle/parameter/Parameter.h" @@ -60,6 +61,7 @@ public: maskTemp_ = Vector::create(para->getSize(), false); maskTemp_->zeroMem(); real* dataPtr = maskTemp_->getData(); + size_t sparsityNum = para->getSize() * (1 - sparsityRatio_); VectorPtr vecCpu = Vector::create(para->getSize(), false); vecCpu->copyFrom(*vec); @@ -67,10 +69,20 @@ public: for (size_t i = 0; i < para->getSize(); i++) param.push_back(std::make_pair(fabs(vecCpu->getData()[i]), i)); - std::sort(param.begin(), param.end(), sortPairAscend); - for (size_t i = 0; i < para->getSize() * sparsityRatio_; i++) - dataPtr[param[i].second] = 1.0; + std::partial_sort(param.begin(), + param.begin() + sparsityNum, + param.end(), + sortPairAscend); + for (size_t i = 0; i < sparsityNum; i++) dataPtr[param[i].second] = 1.0; + + // Currently just use a mask vector for hack. + if (para->useGpu()) { + maskVec_ = Vector::create(para->getSize(), para->useGpu()); + maskVec_->copyFrom(*maskTemp_); + } else { + maskVec_ = maskTemp_; + } } void init(Parameter* para) { @@ -81,15 +93,6 @@ public: VLOG(3) << "Initialize Parameter " << para; SetDevice device(para->getDeviceId()); - // Currently just use a mask vector for hack. - // @TODO(yuyang18): Implemented the mask operation in vector. - if (para->useGpu()) { - maskVec_ = Vector::create(para->getSize(), para->useGpu()); - maskVec_->copyFrom(*maskTemp_); - } else { - maskVec_ = maskTemp_; - } - auto& vec = para->getBuf(PARAMETER_VALUE); vec->dotMul(*maskVec_); } @@ -136,11 +139,7 @@ static IParameterUpdaterHook* createImpl( const ParameterUpdaterHookConfig& config) { auto& type = config.type(); if (type == "pruning") { - if (config.has_sparsity_ratio()) - return new StaticPruningHook(config); - else - LOG(FATAL) << "There must be sparsity_ratio parameter for " << type - << " Hook"; + return new StaticPruningHook(config); } LOG(FATAL) << "Unknown Hook type: " << type; diff --git a/python/paddle/trainer/config_parser.py b/python/paddle/trainer/config_parser.py index e0147b1b37c..3a29c91807f 100644 --- a/python/paddle/trainer/config_parser.py +++ b/python/paddle/trainer/config_parser.py @@ -3175,8 +3175,8 @@ def ParameterHook(type, **kwargs): hook = ParameterUpdaterHookConfig() hook.type = type sparsity_ratio = kwargs.get('sparsity_ratio', None) - assert sparsity_ratio is not None - hook.sparsity_ratio = sparsity_ratio + if sparsity_ratio is not None: + hook.sparsity_ratio = sparsity_ratio return hook else: return None diff --git a/python/paddle/trainer_config_helpers/attrs.py b/python/paddle/trainer_config_helpers/attrs.py index 556701ca7a8..27b54ffdea7 100644 --- a/python/paddle/trainer_config_helpers/attrs.py +++ b/python/paddle/trainer_config_helpers/attrs.py @@ -73,9 +73,11 @@ class HookAttribute(object): def __init__(self, type, sparsity_ratio=None): self.type = type self.sparsity_ratio = sparsity_ratio - assert is_compatible_with(self.sparsity_ratio, - float), 'sparisity_ratio must be float type' - assert self.sparsity_ratio <= 1 and self.sparsity_ratio >= 0, 'sparisity must be a flaot between [0, 1] ' + if self.sparsity_ratio is not None: + assert is_compatible_with( + self.sparsity_ratio, + float), 'sparisity_ratio must be float type' + assert self.sparsity_ratio <= 1 and self.sparsity_ratio >= 0, 'sparisity must be a flaot between [0, 1] ' def __call__(self): return ParameterHook(self.type, sparsity_ratio=self.sparsity_ratio) -- GitLab From b819b44a4e192a425f2ef18108a67d53ffcc2ec1 Mon Sep 17 00:00:00 2001 From: Luo Tao Date: Wed, 14 Jun 2017 18:23:07 +0800 Subject: [PATCH 0647/3256] update rnn doc from v1-api to v2-api --- doc/howto/deep_model/rnn/index_cn.rst | 1 + doc/howto/deep_model/rnn/index_en.rst | 5 + doc/howto/deep_model/rnn/rnn_config_cn.rst | 195 ++++++++++----------- doc/howto/deep_model/rnn/rnn_config_en.rst | 188 +++++++++----------- 4 files changed, 181 insertions(+), 208 deletions(-) diff --git a/doc/howto/deep_model/rnn/index_cn.rst b/doc/howto/deep_model/rnn/index_cn.rst index 9e805ca8519..9ecab5594cf 100644 --- a/doc/howto/deep_model/rnn/index_cn.rst +++ b/doc/howto/deep_model/rnn/index_cn.rst @@ -4,6 +4,7 @@ RNN相关模型 .. toctree:: :maxdepth: 1 + rnn_config_cn.rst recurrent_group_cn.md hierarchical_layer_cn.rst hrnn_rnn_api_compare_cn.rst diff --git a/doc/howto/deep_model/rnn/index_en.rst b/doc/howto/deep_model/rnn/index_en.rst index 13a153b05c5..7adc79873d6 100644 --- a/doc/howto/deep_model/rnn/index_en.rst +++ b/doc/howto/deep_model/rnn/index_en.rst @@ -1,2 +1,7 @@ RNN Models ========== + +.. toctree:: + :maxdepth: 1 + + rnn_config_en.rst diff --git a/doc/howto/deep_model/rnn/rnn_config_cn.rst b/doc/howto/deep_model/rnn/rnn_config_cn.rst index ac2bd0775f4..4d684cf8ad5 100644 --- a/doc/howto/deep_model/rnn/rnn_config_cn.rst +++ b/doc/howto/deep_model/rnn/rnn_config_cn.rst @@ -5,36 +5,13 @@ RNN配置 中配置循环神经网络(RNN)。PaddlePaddle 高度支持灵活和高效的循环神经网络配置。 在本教程中,您将了解如何: -- 准备用来学习循环神经网络的序列数据。 - 配置循环神经网络架构。 - 使用学习完成的循环神经网络模型生成序列。 我们将使用 vanilla 循环神经网络和 sequence to sequence 模型来指导你完成这些步骤。sequence to sequence -模型的代码可以在\ ``demo / seqToseq``\ 找到。 - -准备序列数据 ------------- - -PaddlePaddle -不需要对序列数据进行任何预处理,例如填充。唯一需要做的是将相应类型设置为输入。例如,以下代码段定义了三个输入。 -它们都是序列,它们的大小是\ ``src_dict``\ ,\ ``trg_dict``\ 和\ ``trg_dict``\ : - -.. code:: python - - settings.input_types = [ - integer_value_sequence(len(settings.src_dict)), - integer_value_sequence(len(settings.trg_dict)), - integer_value_sequence(len(settings.trg_dict))] - -在\ ``process``\ 函数中,每个\ ``yield``\ 函数将返回三个整数列表。每个整数列表被视为一个整数序列: - -.. code:: python - - yield src_ids, trg_ids, trg_ids_next - -有关如何编写数据提供程序的更多细节描述,请参考 :ref:`api_pydataprovider2` 。完整的数据提供文件在 -``demo/seqToseq/dataprovider.py``\ 。 +模型的代码可以在 `book/08.machine_translation `_ 找到。 +wmt14数据的提供文件在 `python/paddle/v2/dataset/wmt14.py `_ 。 配置循环神经网络架构 -------------------- @@ -85,19 +62,19 @@ vanilla act=None, rnn_layer_attr=None): def __rnn_step__(ipt): - out_mem = memory(name=name, size=size) - rnn_out = mixed_layer(input = [full_matrix_projection(ipt), - full_matrix_projection(out_mem)], - name = name, - bias_attr = rnn_bias_attr, - act = act, - layer_attr = rnn_layer_attr, - size = size) + out_mem = paddle.layer.memory(name=name, size=size) + rnn_out = paddle.layer.mixed(input = [paddle.layer.full_matrix_projection(input=ipt), + paddle.layer.full_matrix_projection(input=out_mem)], + name = name, + bias_attr = rnn_bias_attr, + act = act, + layer_attr = rnn_layer_attr, + size = size) return rnn_out - return recurrent_group(name='%s_recurrent_group' % name, - step=__rnn_step__, - reverse=reverse, - input=input) + return paddle.layer.recurrent_group(name='%s_recurrent_group' % name, + step=__rnn_step__, + reverse=reverse, + input=input) PaddlePaddle 使用“Memory”(记忆模块)实现单步函数。\ **Memory**\ 是在PaddlePaddle中构造循环神经网络时最重要的概念。 @@ -140,43 +117,52 @@ Sequence to Sequence Model with Attention .. code:: python # 定义源语句的数据层 - src_word_id = data_layer(name='source_language_word', size=source_dict_dim) + src_word_id = paddle.layer.data( + name='source_language_word', + type=paddle.data_type.integer_value_sequence(source_dict_dim)) # 计算每个词的词向量 - src_embedding = embedding_layer( + src_embedding = paddle.layer.embedding( input=src_word_id, size=word_vector_dim, - param_attr=ParamAttr(name='_source_language_embedding')) + param_attr=paddle.attr.ParamAttr(name='_source_language_embedding')) # 应用前向循环神经网络 - src_forward = grumemory(input=src_embedding, size=encoder_size) + src_forward = paddle.networks.simple_gru( + input=src_embedding, size=encoder_size) # 应用反向递归神经网络(reverse=True表示反向循环神经网络) - src_backward = grumemory(input=src_embedding, - size=encoder_size, - reverse=True) + src_backward = paddle.networks.simple_gru( + input=src_embedding, size=encoder_size, reverse=True) # 将循环神经网络的前向和反向部分混合在一起 - encoded_vector = concat_layer(input=[src_forward, src_backward]) + encoded_vector = paddle.layer.concat(input=[src_forward, src_backward]) # 投射编码向量到 decoder_size - encoder_proj = mixed_layer(input = [full_matrix_projection(encoded_vector)], - size = decoder_size) + encoded_proj = paddle.layer.mixed( + size=decoder_size, + input=paddle.layer.full_matrix_projection(encoded_vector)) # 计算反向RNN的第一个实例 - backward_first = first_seq(input=src_backward) + backward_first = paddle.layer.first_seq(input=src_backward) # 投射反向RNN的第一个实例到 decoder size - decoder_boot = mixed_layer(input=[full_matrix_projection(backward_first)], size=decoder_size, act=TanhActivation()) + decoder_boot = paddle.layer.mixed( + size=decoder_size, + act=paddle.activation.Tanh(), + input=paddle.layer.full_matrix_projection(backward_first)) 解码器使用 ``recurrent_group`` 来定义循环神经网络。单步函数和输出函数在 ``gru_decoder_with_attention`` 中定义: .. code:: python - group_inputs=[StaticInput(input=encoded_vector,is_seq=True), - StaticInput(input=encoded_proj,is_seq=True)] - trg_embedding = embedding_layer( - input=data_layer(name='target_language_word', - size=target_dict_dim), - size=word_vector_dim, - param_attr=ParamAttr(name='_target_language_embedding')) + group_input1 = paddle.layer.StaticInput(input=encoded_vector, is_seq=True) + group_input2 = paddle.layer.StaticInput(input=encoded_proj, is_seq=True) + group_inputs = [group_input1, group_input2] + trg_embedding = paddle.layer.embedding( + input=paddle.layer.data( + name='target_language_word', + type=paddle.data_type.integer_value_sequence(target_dict_dim)), + size=word_vector_dim, + param_attr=paddle.attr.ParamAttr(name='_target_language_embedding')) + group_inputs.append(trg_embedding) group_inputs.append(trg_embedding) # 对于配备有注意力机制的解码器,在训练中, @@ -185,9 +171,10 @@ Sequence to Sequence Model with Attention # StaticInput 意味着不同时间步的输入都是相同的值, # 否则它以一个序列输入,不同时间步的输入是不同的。 # 所有输入序列应该有相同的长度。 - decoder = recurrent_group(name=decoder_group_name, - step=gru_decoder_with_attention, - input=group_inputs) + decoder = paddle.layer.recurrent_group( + name=decoder_group_name, + step=gru_decoder_with_attention, + input=group_inputs) 单步函数的实现如下所示。首先,它定义解码网络的\ **Memory**\ 。然后定义 attention,门控循环单元单步函数和输出函数: @@ -198,27 +185,32 @@ attention,门控循环单元单步函数和输出函数: # 定义解码器的Memory # Memory的输出定义在 gru_step 内 # 注意 gru_step 应该与它的Memory名字相同 - decoder_mem = memory(name='gru_decoder', - size=decoder_size, - boot_layer=decoder_boot) + decoder_mem = paddle.layer.memory( + name='gru_decoder', size=decoder_size, boot_layer=decoder_boot) # 计算 attention 加权编码向量 - context = simple_attention(encoded_sequence=enc_vec, - encoded_proj=enc_proj, - decoder_state=decoder_mem) + context = paddle.networks.simple_attention( + encoded_sequence=enc_vec, + encoded_proj=enc_proj, + decoder_state=decoder_mem) # 混合当前词向量和attention加权编码向量 - decoder_inputs = mixed_layer(inputs = [full_matrix_projection(context), - full_matrix_projection(current_word)], - size = decoder_size * 3) + decoder_inputs = paddle.layer.mixed( + size=decoder_size * 3, + input=[ + paddle.layer.full_matrix_projection(input=context), + paddle.layer.full_matrix_projection(input=current_word) + ]) # 定义门控循环单元循环神经网络单步函数 - gru_step = gru_step_layer(name='gru_decoder', - input=decoder_inputs, - output_mem=decoder_mem, - size=decoder_size) + gru_step = paddle.layer.gru_step( + name='gru_decoder', + input=decoder_inputs, + output_mem=decoder_mem, + size=decoder_size) # 定义输出函数 - out = mixed_layer(input=[full_matrix_projection(input=gru_step)], - size=target_dict_dim, - bias_attr=True, - act=SoftmaxActivation()) + out = paddle.layer.mixed( + size=target_dict_dim, + bias_attr=True, + act=paddle.activation.Softmax(), + input=paddle.layer.full_matrix_projection(input=gru_step)) return out 生成序列 @@ -238,41 +230,32 @@ attention,门控循环单元单步函数和输出函数: - ``beam_size``: beam search 算法中的beam大小。 - ``max_length``: 生成序列的最大长度。 -- 使用 ``seqtext_printer_evaluator`` - 根据索引矩阵和字典打印文本。这个函数需要设置: - - - ``id_input``: 数据的整数ID,用于标识生成的文件中的相应输出。 - - ``dict_file``: 用于将词ID转换为词的字典文件。 - - ``result_file``: 生成结果文件的路径。 - 代码如下: .. code:: python - group_inputs=[StaticInput(input=encoded_vector,is_seq=True), - StaticInput(input=encoded_proj,is_seq=True)] + group_input1 = paddle.layer.StaticInput(input=encoded_vector, is_seq=True) + group_input2 = paddle.layer.StaticInput(input=encoded_proj, is_seq=True) + group_inputs = [group_input1, group_input2] # 在生成时,解码器基于编码源序列和最后生成的目标词预测下一目标词。 # 编码源序列(编码器输出)必须由只读Memory的 StaticInput 指定。 # 这里, GeneratedInputs 自动获取上一个生成的词,并在最开始初始化为起始词,如 。 - trg_embedding = GeneratedInput( - size=target_dict_dim, - embedding_name='_target_language_embedding', - embedding_size=word_vector_dim) + trg_embedding = paddle.layer.GeneratedInput( + size=target_dict_dim, + embedding_name='_target_language_embedding', + embedding_size=word_vector_dim) group_inputs.append(trg_embedding) - beam_gen = beam_search(name=decoder_group_name, - step=gru_decoder_with_attention, - input=group_inputs, - bos_id=0, # Beginnning token. - eos_id=1, # End of sentence token. - beam_size=beam_size, - max_length=max_length) - - seqtext_printer_evaluator(input=beam_gen, - id_input=data_layer(name="sent_id", size=1), - dict_file=trg_dict_path, - result_file=gen_trans_file) - outputs(beam_gen) - -注意,这种生成技术只用于类似解码器的生成过程。如果你正在处理序列标记任务,请参阅 :ref:`semantic_role_labeling` 了解更多详细信息。 - -完整的配置文件在\ ``demo/seqToseq/seqToseq_net.py``\ 。 + beam_gen = paddle.layer.beam_search( + name=decoder_group_name, + step=gru_decoder_with_attention, + input=group_inputs, + bos_id=0, # Beginnning token. + eos_id=1, # End of sentence token. + beam_size=beam_size, + max_length=max_length) + + return beam_gen + +注意,这种生成技术只用于类似解码器的生成过程。如果你正在处理序列标记任务,请参阅 `book/06.understand_sentiment `_ 了解更多详细信息。 + +完整的配置文件在 `book/08.machine_translation/train.py `_ 。 diff --git a/doc/howto/deep_model/rnn/rnn_config_en.rst b/doc/howto/deep_model/rnn/rnn_config_en.rst index 73f5d5371fc..2b581290a41 100644 --- a/doc/howto/deep_model/rnn/rnn_config_en.rst +++ b/doc/howto/deep_model/rnn/rnn_config_en.rst @@ -3,34 +3,11 @@ RNN Configuration This tutorial will guide you how to configure recurrent neural network in PaddlePaddle. PaddlePaddle supports highly flexible and efficient recurrent neural network configuration. In this tutorial, you will learn how to: -- prepare sequence data for learning recurrent neural networks. - configure recurrent neural network architecture. - generate sequence with learned recurrent neural network models. -We will use vanilla recurrent neural network, and sequence to sequence model to guide you through these steps. The code of sequence to sequence model can be found at :code:`demo/seqToseq`. - -===================== -Prepare Sequence Data -===================== - -PaddlePaddle does not need any preprocessing to sequence data, such as padding. The only thing that needs to be done is to set the type of the corresponding type to input. For example, the following code snippets defines three input. All of them are sequences, and the size of them are :code:`src_dict`, :code:`trg_dict`, and :code:`trg_dict`: - -.. code-block:: python - - settings.input_types = [ - integer_value_sequence(len(settings.src_dict)), - integer_value_sequence(len(settings.trg_dict)), - integer_value_sequence(len(settings.trg_dict))] - - -Then at the :code:`process` function, each :code:`yield` function will return three integer lists. Each integer list is treated as a sequence of integers: - -.. code-block:: python - - yield src_ids, trg_ids, trg_ids_next - - -For more details description of how to write a data provider, please refer to :ref:`api_pydataprovider2` . The full data provider file is located at :code:`demo/seqToseq/dataprovider.py`. +We will use vanilla recurrent neural network, and sequence to sequence model to guide you through these steps. The code of sequence to sequence model can be found at `book/08.machine_translation `_ . +And the data preparation of this model can be found at `python/paddle/v2/dataset/wmt14.py `_ =============================================== Configure Recurrent Neural Network Architecture @@ -75,19 +52,19 @@ Its **output function** simply takes :math:`x_t` as the output. act=None, rnn_layer_attr=None): def __rnn_step__(ipt): - out_mem = memory(name=name, size=size) - rnn_out = mixed_layer(input = [full_matrix_projection(ipt), - full_matrix_projection(out_mem)], - name = name, - bias_attr = rnn_bias_attr, - act = act, - layer_attr = rnn_layer_attr, - size = size) + out_mem = paddle.layer.memory(name=name, size=size) + rnn_out = paddle.layer.mixed(input = [paddle.layer.full_matrix_projection(input=ipt), + paddle.layer.full_matrix_projection(input=out_mem)], + name = name, + bias_attr = rnn_bias_attr, + act = act, + layer_attr = rnn_layer_attr, + size = size) return rnn_out - return recurrent_group(name='%s_recurrent_group' % name, - step=__rnn_step__, - reverse=reverse, - input=input) + return paddle.layer.recurrent_group(name='%s_recurrent_group' % name, + step=__rnn_step__, + reverse=reverse, + input=input) PaddlePaddle uses memory to construct step function. **Memory** is the most important concept when constructing recurrent neural networks in PaddlePaddle. A memory is a state that is used recurrently in step functions, such as :math:`x_{t+1} = f_x(x_t)`. One memory contains an **output** and a **input**. The output of memory at the current time step is utilized as the input of the memory at the next time step. A memory can also has a **boot layer**, whose output is utilized as the initial value of the memory. In our case, the output of the gated recurrent unit is employed as the output memory. Notice that the name of the layer :code:`rnn_out` is the same as the name of :code:`out_mem`. This means the output of the layer :code:`rnn_out` (:math:`x_{t+1}`) is utilized as the **output** of :code:`out_mem` memory. @@ -113,43 +90,52 @@ We also project the encoder vector to :code:`decoder_size` dimensional space, ge .. code-block:: python # Define the data layer of the source sentence. - src_word_id = data_layer(name='source_language_word', size=source_dict_dim) + src_word_id = paddle.layer.data( + name='source_language_word', + type=paddle.data_type.integer_value_sequence(source_dict_dim)) # Calculate the word embedding of each word. - src_embedding = embedding_layer( + src_embedding = paddle.layer.embedding( input=src_word_id, size=word_vector_dim, - param_attr=ParamAttr(name='_source_language_embedding')) + param_attr=paddle.attr.ParamAttr(name='_source_language_embedding')) # Apply forward recurrent neural network. - src_forward = grumemory(input=src_embedding, size=encoder_size) + src_forward = paddle.networks.simple_gru( + input=src_embedding, size=encoder_size) # Apply backward recurrent neural network. reverse=True means backward recurrent neural network. - src_backward = grumemory(input=src_embedding, - size=encoder_size, - reverse=True) + src_backward = paddle.networks.simple_gru( + input=src_embedding, size=encoder_size, reverse=True) # Mix the forward and backward parts of the recurrent neural network together. - encoded_vector = concat_layer(input=[src_forward, src_backward]) + encoded_vector = paddle.layer.concat(input=[src_forward, src_backward]) # Project encoding vector to decoder_size. - encoder_proj = mixed_layer(input = [full_matrix_projection(encoded_vector)], - size = decoder_size) + encoded_proj = paddle.layer.mixed( + size=decoder_size, + input=paddle.layer.full_matrix_projection(encoded_vector)) # Compute the first instance of the backward RNN. - backward_first = first_seq(input=src_backward) + backward_first = paddle.layer.first_seq(input=src_backward) # Project the first instance of backward RNN to decoder size. - decoder_boot = mixed_layer(input=[full_matrix_projection(backward_first)], size=decoder_size, act=TanhActivation()) + decoder_boot = paddle.layer.mixed( + size=decoder_size, + act=paddle.activation.Tanh(), + input=paddle.layer.full_matrix_projection(backward_first)) The decoder uses :code:`recurrent_group` to define the recurrent neural network. The step and output functions are defined in :code:`gru_decoder_with_attention`: .. code-block:: python - group_inputs=[StaticInput(input=encoded_vector,is_seq=True), - StaticInput(input=encoded_proj,is_seq=True)] - trg_embedding = embedding_layer( - input=data_layer(name='target_language_word', - size=target_dict_dim), - size=word_vector_dim, - param_attr=ParamAttr(name='_target_language_embedding')) + group_input1 = paddle.layer.StaticInput(input=encoded_vector, is_seq=True) + group_input2 = paddle.layer.StaticInput(input=encoded_proj, is_seq=True) + group_inputs = [group_input1, group_input2] + trg_embedding = paddle.layer.embedding( + input=paddle.layer.data( + name='target_language_word', + type=paddle.data_type.integer_value_sequence(target_dict_dim)), + size=word_vector_dim, + param_attr=paddle.attr.ParamAttr(name='_target_language_embedding')) + group_inputs.append(trg_embedding) group_inputs.append(trg_embedding) # For decoder equipped with attention mechanism, in training, @@ -158,9 +144,10 @@ The decoder uses :code:`recurrent_group` to define the recurrent neural network. # StaticInput means the same value is utilized at different time steps. # Otherwise, it is a sequence input. Inputs at different time steps are different. # All sequence inputs should have the same length. - decoder = recurrent_group(name=decoder_group_name, - step=gru_decoder_with_attention, - input=group_inputs) + decoder = paddle.layer.recurrent_group( + name=decoder_group_name, + step=gru_decoder_with_attention, + input=group_inputs) The implementation of the step function is listed as below. First, it defines the **memory** of the decoder network. Then it defines attention, gated recurrent unit step function, and the output function: @@ -171,27 +158,32 @@ The implementation of the step function is listed as below. First, it defines th # Defines the memory of the decoder. # The output of this memory is defined in gru_step. # Notice that the name of gru_step should be the same as the name of this memory. - decoder_mem = memory(name='gru_decoder', - size=decoder_size, - boot_layer=decoder_boot) + decoder_mem = paddle.layer.memory( + name='gru_decoder', size=decoder_size, boot_layer=decoder_boot) # Compute attention weighted encoder vector. - context = simple_attention(encoded_sequence=enc_vec, - encoded_proj=enc_proj, - decoder_state=decoder_mem) + context = paddle.networks.simple_attention( + encoded_sequence=enc_vec, + encoded_proj=enc_proj, + decoder_state=decoder_mem) # Mix the current word embedding and the attention weighted encoder vector. - decoder_inputs = mixed_layer(inputs = [full_matrix_projection(context), - full_matrix_projection(current_word)], - size = decoder_size * 3) + decoder_inputs = paddle.layer.mixed( + size=decoder_size * 3, + input=[ + paddle.layer.full_matrix_projection(input=context), + paddle.layer.full_matrix_projection(input=current_word) + ]) # Define Gated recurrent unit recurrent neural network step function. - gru_step = gru_step_layer(name='gru_decoder', - input=decoder_inputs, - output_mem=decoder_mem, - size=decoder_size) + gru_step = paddle.layer.gru_step( + name='gru_decoder', + input=decoder_inputs, + output_mem=decoder_mem, + size=decoder_size) # Defines the output function. - out = mixed_layer(input=[full_matrix_projection(input=gru_step)], - size=target_dict_dim, - bias_attr=True, - act=SoftmaxActivation()) + out = paddle.layer.mixed( + size=target_dict_dim, + bias_attr=True, + act=paddle.activation.Softmax(), + input=paddle.layer.full_matrix_projection(input=gru_step)) return out @@ -207,45 +199,37 @@ After training the model, we can use it to generate sequences. A common practice - :code:`eos_id`: the end token. Every sentence ends with the end token. - :code:`beam_size`: the beam size used in beam search. - :code:`max_length`: the maximum length of the generated sentences. - -* use :code:`seqtext_printer_evaluator` to print text according to index matrix and dictionary. This function needs to set: - - - :code:`id_input`: the integer ID of the data, used to identify the corresponding output in the generated files. - - :code:`dict_file`: the dictionary file for converting word id to word. - - :code:`result_file`: the path of the generation result file. The code is listed below: .. code-block:: python - group_inputs=[StaticInput(input=encoded_vector,is_seq=True), - StaticInput(input=encoded_proj,is_seq=True)] + group_input1 = paddle.layer.StaticInput(input=encoded_vector, is_seq=True) + group_input2 = paddle.layer.StaticInput(input=encoded_proj, is_seq=True) + group_inputs = [group_input1, group_input2] # In generation, decoder predicts a next target word based on # the encoded source sequence and the last generated target word. # The encoded source sequence (encoder's output) must be specified by # StaticInput which is a read-only memory. # Here, GeneratedInputs automatically fetchs the last generated word, # which is initialized by a start mark, such as . - trg_embedding = GeneratedInput( - size=target_dict_dim, - embedding_name='_target_language_embedding', - embedding_size=word_vector_dim) + trg_embedding = paddle.layer.GeneratedInput( + size=target_dict_dim, + embedding_name='_target_language_embedding', + embedding_size=word_vector_dim) group_inputs.append(trg_embedding) - beam_gen = beam_search(name=decoder_group_name, - step=gru_decoder_with_attention, - input=group_inputs, - bos_id=0, # Beginnning token. - eos_id=1, # End of sentence token. - beam_size=beam_size, - max_length=max_length) + beam_gen = paddle.layer.beam_search( + name=decoder_group_name, + step=gru_decoder_with_attention, + input=group_inputs, + bos_id=0, # Beginnning token. + eos_id=1, # End of sentence token. + beam_size=beam_size, + max_length=max_length) - seqtext_printer_evaluator(input=beam_gen, - id_input=data_layer(name="sent_id", size=1), - dict_file=trg_dict_path, - result_file=gen_trans_file) - outputs(beam_gen) + return beam_gen -Notice that this generation technique is only useful for decoder like generation process. If you are working on sequence tagging tasks, please refer to :ref:`semantic_role_labeling` for more details. +Notice that this generation technique is only useful for decoder like generation process. If you are working on sequence tagging tasks, please refer to `book/06.understand_sentiment `_ for more details. -The full configuration file is located at :code:`demo/seqToseq/seqToseq_net.py`. +The full configuration file is located at `book/08.machine_translation/train.py `_ . -- GitLab From 98e4bb79ea3f569b35c69272e0ffebf6613c985a Mon Sep 17 00:00:00 2001 From: Zhaolong Xing Date: Wed, 14 Jun 2017 18:48:32 +0800 Subject: [PATCH 0648/3256] Create ParameterUpdaterHook.cpp --- paddle/parameter/ParameterUpdaterHook.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/paddle/parameter/ParameterUpdaterHook.cpp b/paddle/parameter/ParameterUpdaterHook.cpp index a581cc047df..a4c0cb30994 100644 --- a/paddle/parameter/ParameterUpdaterHook.cpp +++ b/paddle/parameter/ParameterUpdaterHook.cpp @@ -130,7 +130,8 @@ private: static WeakKVCache, IParameterUpdaterHook, - StringIntPairHasher> g_hookCache_; + StringIntPairHasher> + g_hookCache_; /** * ParameterUpdaterHook actually factory method. -- GitLab From 4fbec8233b08dec0608b342563c62ecee946e460 Mon Sep 17 00:00:00 2001 From: Zhaolong Xing Date: Wed, 14 Jun 2017 18:49:00 +0800 Subject: [PATCH 0649/3256] Update ParameterUpdaterHook.cpp --- paddle/parameter/ParameterUpdaterHook.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paddle/parameter/ParameterUpdaterHook.cpp b/paddle/parameter/ParameterUpdaterHook.cpp index a4c0cb30994..3e3dcd6575d 100644 --- a/paddle/parameter/ParameterUpdaterHook.cpp +++ b/paddle/parameter/ParameterUpdaterHook.cpp @@ -130,7 +130,7 @@ private: static WeakKVCache, IParameterUpdaterHook, - StringIntPairHasher> + StringIntPairHasher> g_hookCache_; /** -- GitLab From 5405dc0a65e3bb4a9b807a46bb1296cddce44a7e Mon Sep 17 00:00:00 2001 From: Zhaolong Xing Date: Wed, 14 Jun 2017 19:15:51 +0800 Subject: [PATCH 0650/3256] Create ParameterUpdaterHook.cpp --- paddle/parameter/ParameterUpdaterHook.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paddle/parameter/ParameterUpdaterHook.cpp b/paddle/parameter/ParameterUpdaterHook.cpp index 3e3dcd6575d..44fac592002 100644 --- a/paddle/parameter/ParameterUpdaterHook.cpp +++ b/paddle/parameter/ParameterUpdaterHook.cpp @@ -14,13 +14,13 @@ limitations under the License. */ #include "ParameterUpdaterHook.h" +#include #include #include #include #include #include #include -#include #include "paddle/math/Vector.h" #include "paddle/parameter/Parameter.h" -- GitLab From e148bc15b0565404be4d880a0c8b00179da9a1a3 Mon Sep 17 00:00:00 2001 From: dzhwinter Date: Thu, 15 Jun 2017 02:24:49 +0800 Subject: [PATCH 0651/3256] "remove unused tensor line" --- cmake/util.cmake | 1 + paddle/optimizer/CMakeLists.txt | 5 ++--- paddle/optimizer/Tensor.h | 6 +++++- paddle/optimizer/parameter_optimizer_test.cpp | 8 ++++---- paddle/optimizer/serialization.h | 4 +++- paddle/optimizer/serialization_test.cpp | 1 + 6 files changed, 16 insertions(+), 9 deletions(-) diff --git a/cmake/util.cmake b/cmake/util.cmake index 8c914346222..87ad9d91d87 100644 --- a/cmake/util.cmake +++ b/cmake/util.cmake @@ -84,6 +84,7 @@ function(link_paddle_exe TARGET_NAME) paddle_parameter paddle_proto paddle_cuda + paddle_optimizer ${EXTERNAL_LIBS} ${CMAKE_THREAD_LIBS_INIT} ${CMAKE_DL_LIBS} diff --git a/paddle/optimizer/CMakeLists.txt b/paddle/optimizer/CMakeLists.txt index bafd8a9b97a..791be95efa9 100644 --- a/paddle/optimizer/CMakeLists.txt +++ b/paddle/optimizer/CMakeLists.txt @@ -9,9 +9,8 @@ set(OPITMIZER_SRCS sgd_optimizer.cc ) -add_library(optimizer STATIC ${OPITMIZER_SRCS}) -add_dependencies(optimizer gen_proto_cpp) +add_library(paddle_optimizer STATIC ${OPITMIZER_SRCS}) +add_dependencies(paddle_optimizer gen_proto_cpp) add_simple_unittest(serialization_test) add_simple_unittest(parameter_optimizer_test) -add_dependencies(parameter_optimizer_test optimizer) diff --git a/paddle/optimizer/Tensor.h b/paddle/optimizer/Tensor.h index b8f212e81ff..2eefdec3e5e 100644 --- a/paddle/optimizer/Tensor.h +++ b/paddle/optimizer/Tensor.h @@ -15,13 +15,17 @@ template class TensorT { public: TensorT(size_t size) : height_(1), width_(size) { data_ = new T[size]; } + TensorT(T* data, size_t size) : height_(1), width_(size), data_(data) {} - TensorT(T* data, size_t h, size_t w) : height_(h), width_(w), data_(data_) {} + + TensorT(T* data, size_t h, size_t w) : height_(h), width_(w), data_(data) {} + ~TensorT() { if (data_) delete data_; } T* get_buffer() { return this->data_; } + T& operator[](const size_t idx) { CHECK(idx >= 0 && idx < this->width_) << "out of index range"; return data_[idx]; diff --git a/paddle/optimizer/parameter_optimizer_test.cpp b/paddle/optimizer/parameter_optimizer_test.cpp index 8d3bfa9cf4b..afacd6d54a7 100644 --- a/paddle/optimizer/parameter_optimizer_test.cpp +++ b/paddle/optimizer/parameter_optimizer_test.cpp @@ -49,8 +49,8 @@ public: config_.set_lr_policy(OptimizerConfig::ConstLr); config_.mutable_const_lr()->set_learning_rate(0.1); - ParameterOptimizer* opt = - ParameterOptimizer::Create(config_.SerializeAsString(), parameter); + std::string str = config_.SerializeAsString(); + ParameterOptimizer* opt = ParameterOptimizer::Create(str, parameter); opts_.push_back(opt); opts_table_[opts_.size()] = OptimizerConfig::SGD; } @@ -64,8 +64,8 @@ public: config_.mutable_adam()->set_decay(0.0); config_.set_lr_policy(OptimizerConfig::ConstLr); config_.mutable_const_lr()->set_learning_rate(0.1); - ParameterOptimizer* opt = - ParameterOptimizer::Create(config_.SerializeAsString(), parameter); + std::string str = config_.SerializeAsString(); + ParameterOptimizer* opt = ParameterOptimizer::Create(str, parameter); opts_.push_back(opt); opts_table_[opts_.size()] = OptimizerConfig::Adam; } diff --git a/paddle/optimizer/serialization.h b/paddle/optimizer/serialization.h index 21de3259a88..92fbf65cc6b 100644 --- a/paddle/optimizer/serialization.h +++ b/paddle/optimizer/serialization.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -16,7 +17,7 @@ static void TensorToProto(const Tensor& tensor, TensorProto* proto) { for (size_t i = 0; i < tensor.size(); ++i) { os << tensor[i]; proto->add_content(os.str()); - os.clear(); + os.str(std::string()); } } @@ -25,6 +26,7 @@ static void ProtoToTensor(const TensorProto& proto, Tensor* tensor) { for (auto i = 0; i < proto.content_size(); ++i) { sin << proto.content(i); sin >> (*tensor)[i]; + sin.str(std::string()); sin.clear(); } } diff --git a/paddle/optimizer/serialization_test.cpp b/paddle/optimizer/serialization_test.cpp index 98fbdf5a5e2..d2454140dc2 100644 --- a/paddle/optimizer/serialization_test.cpp +++ b/paddle/optimizer/serialization_test.cpp @@ -10,6 +10,7 @@ TEST(TensorToProto, Case1) { t[i] = i; t1[i] = 0; } + TensorProto proto; TensorToProto(t, &proto); ProtoToTensor(proto, &t1); -- GitLab From a46f3fcefc1178dcdcbd4d948c0350e29c94e839 Mon Sep 17 00:00:00 2001 From: dzhwinter Date: Thu, 15 Jun 2017 02:52:16 +0800 Subject: [PATCH 0652/3256] "fix double release tensor buffer error." --- paddle/math/tests/CMakeLists.txt | 1 + paddle/optimizer/Tensor.h | 13 +++++++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/paddle/math/tests/CMakeLists.txt b/paddle/math/tests/CMakeLists.txt index ceb96b2e250..bdecba0869d 100644 --- a/paddle/math/tests/CMakeLists.txt +++ b/paddle/math/tests/CMakeLists.txt @@ -31,3 +31,4 @@ add_simple_unittest(test_FPException) add_simple_unittest(test_GpuProfiler) add_simple_unittest(test_BaseMatrix) add_simple_unittest(test_Matrix) +add_simple_unittest(test_Matrix2) diff --git a/paddle/optimizer/Tensor.h b/paddle/optimizer/Tensor.h index 2eefdec3e5e..49b8dadaaef 100644 --- a/paddle/optimizer/Tensor.h +++ b/paddle/optimizer/Tensor.h @@ -5,6 +5,7 @@ #include #include +#include "paddle/math/MemoryHandle.h" #include "paddle/utils/Common.h" #include "paddle/utils/Logging.h" @@ -14,15 +15,19 @@ namespace optimizer { template class TensorT { public: - TensorT(size_t size) : height_(1), width_(size) { data_ = new T[size]; } + TensorT(size_t size) + : TensorT(std::make_shared(size * sizeof(float)), size) { + } + TensorT(CpuMemHandlePtr handle, size_t size) + : height_(1), + width_(size), + data_(reinterpret_cast(handle->getBuf())) {} TensorT(T* data, size_t size) : height_(1), width_(size), data_(data) {} TensorT(T* data, size_t h, size_t w) : height_(h), width_(w), data_(data) {} - ~TensorT() { - if (data_) delete data_; - } + virtual ~TensorT() {} T* get_buffer() { return this->data_; } -- GitLab From fa5c3f1f736a658fa2ada9edf716b922d45ca563 Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Wed, 14 Jun 2017 21:51:45 +0000 Subject: [PATCH 0653/3256] implement master client, Go part --- go/master/c/client.go | 81 ++++++++++++++++++++ go/master/client.go | 53 ++++++++++++- go/master/client_internal_test.go | 120 ++++++++++++++++++++++++++++++ go/master/client_test.go | 85 ++++++--------------- go/master/service.go | 26 +++++-- 5 files changed, 290 insertions(+), 75 deletions(-) create mode 100644 go/master/c/client.go create mode 100644 go/master/client_internal_test.go diff --git a/go/master/c/client.go b/go/master/c/client.go new file mode 100644 index 00000000000..220184c3af7 --- /dev/null +++ b/go/master/c/client.go @@ -0,0 +1,81 @@ +package main + +/* + +typedef int paddle_master_client; +*/ +import "C" + +import ( + "log" + "sync" + "unsafe" + + "github.com/PaddlePaddle/Paddle/go/master" +) + +var mu sync.Mutex +var handleMap = make(map[C.paddle_master_client]*master.Client) +var curHandle C.paddle_master_client + +func add(c *master.Client) C.paddle_master_client { + mu.Lock() + defer mu.Unlock() + client := curHandle + curHandle++ + handleMap[client] = c + return client +} + +func get(client C.paddle_master_client) *master.Client { + mu.Lock() + defer mu.Unlock() + return handleMap[client] +} + +func remove(client C.paddle_master_client) *master.Client { + mu.Lock() + defer mu.Unlock() + h := handleMap[client] + delete(handleMap, client) + return h +} + +type addresser string + +func (a addresser) Address() string { + return string(a) +} + +//paddle_new_master_client +func paddle_new_master_client(addr *C.char, buf_size C.int) C.paddle_master_client { + a := C.GoString(addr) + c := master.NewClient(addresser(a), int(buf_size)) + return add(c) +} + +//export paddle_new_etcd_master_client +func paddle_new_etcd_master_client(etcd_addr *C.char) C.paddle_master_client { + // TODO(helin): fault tolerant master client using etcd. + panic("not implemented.") +} + +//export paddle_set_dataset +func paddle_set_dataset(client C.paddle_master_client, path **C.char, size C.int) C.int { + c := get(client) + var paths []string + for i := 0; i < int(size); i++ { + ptr := (**C.char)(unsafe.Pointer(uintptr(unsafe.Pointer(path)) + uintptr(size))) + str := C.GoString(*ptr) + paths = append(paths, str) + } + err := c.SetDataset(paths) + if err != nil { + log.Println(err) + return -1 + } + + return 0 +} + +func main() {} diff --git a/go/master/client.go b/go/master/client.go index 20c66340dc2..1c8a8d73d0c 100644 --- a/go/master/client.go +++ b/go/master/client.go @@ -2,9 +2,11 @@ package master import ( "log" + "os" "time" "github.com/PaddlePaddle/Paddle/go/connection" + "github.com/PaddlePaddle/recordio" ) // Addresser provide the address of the master server. @@ -15,16 +17,51 @@ type Addresser interface { // Client is the client of the master server. type Client struct { conn *connection.Conn + ch chan []byte } // NewClient creates a new Client. -func NewClient(addr Addresser) *Client { +// +// bufSize is the record buffer size. NextRecord will read from the +// buffer. +func NewClient(addr Addresser, bufSize int) *Client { c := &Client{} c.conn = connection.New() + c.ch = make(chan []byte, bufSize) go c.monitorMaster(addr) + go c.getRecords() return c } +func (c *Client) getRecords() { + for { + t, err := c.getTask() + if err != nil { + log.Println(err) + continue + } + + for _, chunk := range t.Chunks { + f, err := os.Open(chunk.Path) + if err != nil { + log.Println(err) + continue + } + + s := recordio.NewRangeScanner(f, &chunk.Index, -1, -1) + for s.Scan() { + c.ch <- s.Record() + } + + err = f.Close() + if err != nil { + log.Println(err) + } + } + c.taskFinished(t.ID) + } +} + func (c *Client) monitorMaster(addr Addresser) { lastMaster := "" monitor := func() { @@ -69,14 +106,22 @@ func (c *Client) SetDataset(globPaths []string) error { return c.conn.Call("Service.SetDataset", globPaths, nil) } -// GetTask gets a new task from the master server. -func (c *Client) GetTask() (Task, error) { +// getTask gets a new task from the master server. +func (c *Client) getTask() (Task, error) { var t Task err := c.conn.Call("Service.GetTask", 0, &t) return t, err } // TaskFinished tells the master server a task is finished. -func (c *Client) TaskFinished(taskID int) error { +func (c *Client) taskFinished(taskID int) error { return c.conn.Call("Service.TaskFinished", taskID, nil) } + +// NextRecord returns next record in the dataset. +// +// NextRecord will block until next record is available. It is +// thread-safe. +func (c *Client) NextRecord() []byte { + return <-c.ch +} diff --git a/go/master/client_internal_test.go b/go/master/client_internal_test.go new file mode 100644 index 00000000000..362668202a7 --- /dev/null +++ b/go/master/client_internal_test.go @@ -0,0 +1,120 @@ +package master + +import ( + "fmt" + "net" + "net/http" + "net/rpc" + "os" + "strconv" + "strings" + "testing" + "time" + + log "github.com/sirupsen/logrus" + + "github.com/PaddlePaddle/Paddle/go/connection" + "github.com/PaddlePaddle/recordio" +) + +const ( + totalTask = 20 + chunkPerTask = 10 +) + +func init() { + log.SetLevel(log.ErrorLevel) +} + +type TestAddresser string + +func (a TestAddresser) Address() string { + return string(a) +} + +func TestGetFinishTask(t *testing.T) { + const path = "/tmp/master_client_test_0" + + l, err := net.Listen("tcp", ":0") + if err != nil { + panic(err) + } + + ss := strings.Split(l.Addr().String(), ":") + p, err := strconv.Atoi(ss[len(ss)-1]) + if err != nil { + panic(err) + } + + go func(l net.Listener) { + s := NewService(chunkPerTask, time.Second, 1) + server := rpc.NewServer() + err := server.Register(s) + if err != nil { + panic(err) + } + + mux := http.NewServeMux() + mux.Handle(rpc.DefaultRPCPath, server) + err = http.Serve(l, mux) + if err != nil { + panic(err) + } + }(l) + + f, err := os.Create(path) + if err != nil { + panic(err) + } + + for i := 0; i < totalTask*chunkPerTask; i++ { + w := recordio.NewWriter(f, -1, -1) + w.Write(nil) + // call Close to force RecordIO writing a chunk. + w.Close() + } + f.Close() + + c := &Client{} + c.conn = connection.New() + go c.monitorMaster(TestAddresser(fmt.Sprintf(":%d", p))) + c.SetDataset([]string{path}) + + checkOnePass := func(i int) { + var tasks []Task + for idx := 0; idx < totalTask; idx++ { + task, err := c.getTask() + if err != nil { + t.Fatal(err, " pass:", i) + } + tasks = append(tasks, task) + } + + _, err = c.getTask() + if err == nil { + t.Fatal("Should get error. Pass:", i) + } + + err = c.taskFinished(tasks[0].ID) + if err != nil { + t.Fatal(err, "pass:", i) + } + tasks = tasks[1:] + task, err := c.getTask() + if err != nil { + t.Fatal(err) + } + tasks = append(tasks, task) + + for _, task := range tasks { + err = c.taskFinished(task.ID) + if err != nil { + t.Fatal(err, " pass:", i) + } + } + } + + for i := 0; i < 10; i++ { + checkOnePass(i) + } +} diff --git a/go/master/client_test.go b/go/master/client_test.go index df708ad7912..2b3f873ecf3 100644 --- a/go/master/client_test.go +++ b/go/master/client_test.go @@ -11,21 +11,15 @@ import ( "testing" "time" - log "github.com/sirupsen/logrus" - "github.com/PaddlePaddle/Paddle/go/master" "github.com/PaddlePaddle/recordio" ) -const ( - totalTask = 20 - chunkPerTask = 10 -) - -var port int - -func init() { - log.SetLevel(log.ErrorLevel) +func TestNextRecord(t *testing.T) { + const ( + path = "/tmp/master_client_TestFull" + total = 50 + ) l, err := net.Listen("tcp", ":0") if err != nil { @@ -37,10 +31,9 @@ func init() { if err != nil { panic(err) } - port = p go func(l net.Listener) { - s := master.NewService(chunkPerTask, time.Second, 1) + s := master.NewService(10, time.Second, 1) server := rpc.NewServer() err := server.Register(s) if err != nil { @@ -54,67 +47,33 @@ func init() { panic(err) } }(l) -} -type addresser string - -func (a addresser) Address() string { - return string(a) -} - -func TestClientFull(t *testing.T) { - const p = "/tmp/master_client_test_0" - f, err := os.Create(p) + f, err := os.Create(path) if err != nil { panic(err) } - for i := 0; i < totalTask*chunkPerTask; i++ { - w := recordio.NewWriter(f, -1, -1) - w.Write(nil) - // call Close to force RecordIO writing a chunk. - w.Close() + w := recordio.NewWriter(f, -1, -1) + for i := 0; i < total; i++ { + w.Write([]byte{byte(i)}) } + w.Close() f.Close() - c := master.NewClient(addresser(fmt.Sprintf(":%d", port))) - c.SetDataset([]string{p}) + c := master.NewClient(master.TestAddresser(fmt.Sprintf(":%d", p)), 10) + c.SetDataset([]string{path}) - checkOnePass := func(i int) { - var tasks []master.Task - for i := 0; i < totalTask; i++ { - task, err := c.GetTask() - if err != nil { - t.Fatal(i, err) + for pass := 0; pass < 50; pass++ { + received := make(map[byte]bool) + for i := 0; i < total; i++ { + r := c.NextRecord() + if len(r) != 1 { + t.Fatal("Length should be 1.", r) } - tasks = append(tasks, task) - } - - _, err = c.GetTask() - if err == nil { - t.Fatal(i, "should get error.") - } - - err = c.TaskFinished(tasks[0].ID) - if err != nil { - t.Fatal(err) - } - tasks = tasks[1:] - task, err := c.GetTask() - if err != nil { - t.Fatal(err) - } - tasks = append(tasks, task) - - for _, task := range tasks { - err = c.TaskFinished(task.ID) - if err != nil { - t.Fatal(i, err) + if received[r[0]] { + t.Fatal("Received duplicate.", received, r) } + received[r[0]] = true } } - - for i := 0; i < 10; i++ { - checkOnePass(i) - } } diff --git a/go/master/service.go b/go/master/service.go index 1e2a34972bb..2e165138fb8 100644 --- a/go/master/service.go +++ b/go/master/service.go @@ -217,6 +217,16 @@ func (s *Service) checkTimeoutFunc(taskID int, epoch int) func() { } } +// must be called with lock held. +func (s *Service) logFields() log.Fields { + return log.Fields{ + "todoLen": len(s.taskQueues.Todo), + "pendingLen": len(s.taskQueues.Pending), + "doneLen": len(s.taskQueues.Done), + "failedLen": len(s.taskQueues.Failed), + } +} + // GetTask gets a new task from the service. func (s *Service) GetTask(dummy int, task *Task) error { select { @@ -230,7 +240,7 @@ func (s *Service) GetTask(dummy int, task *Task) error { if len(s.taskQueues.Done) == 0 { if len(s.taskQueues.Pending) == 0 { err := errors.New("all task failed") - log.Warningln(err) + log.WithFields(s.logFields()).Warningln("All tasks failed.") return err } @@ -243,12 +253,12 @@ func (s *Service) GetTask(dummy int, task *Task) error { // in package. So we need to figure out a way // for client to check this error correctly. err := errors.New("no more available task") - log.Warningln(err) + log.WithFields(s.logFields()).Warningln("No more available task.") return err } s.taskQueues.Todo = s.taskQueues.Done s.taskQueues.Done = nil - log.Infoln("No more todo task, but trainer is requesting task to do. Move all done task to todo.") + log.WithFields(s.logFields()).Infoln("No more todo task, but trainer is requesting task to do. Move all done task to todo.") } t := s.taskQueues.Todo[0] @@ -261,7 +271,7 @@ func (s *Service) GetTask(dummy int, task *Task) error { } *task = t.Task - log.Infof("Task #%d dispatched\n", task.ID) + log.WithFields(s.logFields()).Infof("Task #%d dispatched.", task.ID) time.AfterFunc(s.timeoutDur, s.checkTimeoutFunc(t.Task.ID, t.Epoch)) return nil @@ -276,12 +286,10 @@ func (s *Service) TaskFinished(taskID int, dummy *int) error { s.mu.Lock() defer s.mu.Unlock() - log.Infof("Task %d finished\n", taskID) - t, ok := s.taskQueues.Pending[taskID] if !ok { err := errors.New("pending task not found") - log.Warningln(err) + log.WithFields(s.logFields()).Warningln("Pending task #%d not found.", taskID) return err } @@ -290,8 +298,10 @@ func (s *Service) TaskFinished(taskID int, dummy *int) error { s.taskQueues.Done = append(s.taskQueues.Done, t) delete(s.taskQueues.Pending, taskID) + log.WithFields(s.logFields()).Infof("Task #%d finished.", taskID) + if len(s.taskQueues.Pending) == 0 && len(s.taskQueues.Todo) == 0 { - log.Infoln("No more todo and pending task, start a new pass.") + log.WithFields(s.logFields()).Infoln("No more todo and pending task, start a new pass.") s.taskQueues.Todo = append(s.taskQueues.Todo, s.taskQueues.Done...) s.taskQueues.Done = nil } -- GitLab From dd2cbb63bfaa8429b8c023a8830396135f731352 Mon Sep 17 00:00:00 2001 From: xuwei06 Date: Thu, 1 Jun 2017 16:23:52 -0700 Subject: [PATCH 0654/3256] Parameter initializer in V2 API User can provide an initializer in ParamAttr. The initializer will be called when at parameters.create(). --- python/paddle/trainer/config_parser.py | 49 ++++++++++++------- python/paddle/trainer_config_helpers/attrs.py | 9 +++- python/paddle/v2/parameters.py | 4 ++ python/paddle/v2/tests/test_parameters.py | 22 +++++++++ 4 files changed, 64 insertions(+), 20 deletions(-) diff --git a/python/paddle/trainer/config_parser.py b/python/paddle/trainer/config_parser.py index 0792e2d40b4..fc2e3bbcde0 100644 --- a/python/paddle/trainer/config_parser.py +++ b/python/paddle/trainer/config_parser.py @@ -126,6 +126,7 @@ def init_config_environment( g_config=TrainerConfig(), g_layer_map={}, g_parameter_map={}, + g_parameter_initializer_map={}, g_extended_config_funcs={}, # store command args of paddle_trainer @@ -439,22 +440,22 @@ def model_type(name): @config_class class Bias(Cfg): - def __init__( - self, - parameter_name=None, - learning_rate=None, - momentum=None, - decay_rate=None, - decay_rate_l1=None, - initial_mean=None, - initial_std=None, - initial_strategy=None, - initial_smart=None, - num_batches_regularization=None, - sparse_remote_update=None, - gradient_clipping_threshold=None, - is_static=None, - is_shared=None, ): + def __init__(self, + parameter_name=None, + learning_rate=None, + momentum=None, + decay_rate=None, + decay_rate_l1=None, + initial_mean=None, + initial_std=None, + initial_strategy=None, + initial_smart=None, + num_batches_regularization=None, + sparse_remote_update=None, + gradient_clipping_threshold=None, + is_static=None, + is_shared=None, + initializer=None): self.add_keys(locals()) @@ -465,6 +466,7 @@ class Input(Cfg): self, input_layer_name, parameter_name=None, + initializer=None, learning_rate=None, momentum=None, decay_rate=None, @@ -521,6 +523,7 @@ class Projection(Input): initial_std=None, initial_strategy=None, initial_smart=None, + initializer=None, num_batches_regularization=None, sparse_remote_update=None, sparse_update=None, @@ -1479,7 +1482,8 @@ class LayerBase(object): gradient_clipping_threshold=bias. gradient_clipping_threshold, is_static=bias.is_static, - is_shared=bias.is_shared, ) + is_shared=bias.is_shared, + initializer=bias.initializer) if for_self: self.config.bias_parameter_name = bias.parameter_name else: @@ -1536,7 +1540,8 @@ class LayerBase(object): format=format, is_static=input_config.is_static, is_shared=input_config.is_shared, - update_hooks=input_config.update_hooks) + update_hooks=input_config.update_hooks, + initializer=input_config.initializer) def set_layer_size(self, size): if self.config.size == 0: @@ -3221,7 +3226,8 @@ def Parameter(name, need_compact=None, is_static=None, is_shared=None, - update_hooks=None): + update_hooks=None, + initializer=None): config_assert(name not in g_parameter_map, 'Duplicated parameter name: ' + name) @@ -3309,6 +3315,11 @@ def Parameter(name, para.update_hooks.extend(update_hooks) g_parameter_map[name] = para + if initializer is not None: + config_assert( + callable(initializer), + "parameter initializer should be a callable object") + g_parameter_initializer_map[name] = initializer @config_func diff --git a/python/paddle/trainer_config_helpers/attrs.py b/python/paddle/trainer_config_helpers/attrs.py index d1167a234ca..4100697c9c3 100644 --- a/python/paddle/trainer_config_helpers/attrs.py +++ b/python/paddle/trainer_config_helpers/attrs.py @@ -95,6 +95,10 @@ 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 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 """ def __init__(self, @@ -109,7 +113,8 @@ class ParameterAttribute(object): learning_rate=None, momentum=None, gradient_clipping_threshold=None, - sparse_update=False): + sparse_update=False, + initializer=None): self.attr = {} if is_static: @@ -161,6 +166,8 @@ class ParameterAttribute(object): is_compatible_with(gradient_clipping_threshold, float): self.attr['gradient_clipping_threshold'] = \ gradient_clipping_threshold + if initializer is not None: + self.attr['initializer'] = initializer def set_default_parameter_name(self, name): """ diff --git a/python/paddle/v2/parameters.py b/python/paddle/v2/parameters.py index 64805d0c504..ad20241b983 100644 --- a/python/paddle/v2/parameters.py +++ b/python/paddle/v2/parameters.py @@ -1,6 +1,7 @@ import numpy as np import py_paddle.swig_paddle as api from paddle.proto.ParameterConfig_pb2 import ParameterConfig +import paddle.trainer.config_parser as cp import struct import tarfile import cStringIO @@ -18,8 +19,11 @@ def create(layers): """ topology = Topology(layers) pool = Parameters() + initializers = cp.g_parameter_initializer_map for param in topology.proto().parameters: pool.__append_config__(param) + if param.name in initializers: + pool[param.name] = initializers[param.name](param.name) return pool diff --git a/python/paddle/v2/tests/test_parameters.py b/python/paddle/v2/tests/test_parameters.py index ebb182caab6..45372e7dd0e 100644 --- a/python/paddle/v2/tests/test_parameters.py +++ b/python/paddle/v2/tests/test_parameters.py @@ -11,6 +11,9 @@ except ImportError: sys.exit(0) import paddle.v2.parameters as parameters +import paddle.v2.data_type as data_type +import paddle.v2.layer as layer +from paddle.v2.attr import ParamAttr from paddle.proto.ParameterConfig_pb2 import ParameterConfig import random import cStringIO @@ -55,6 +58,25 @@ class TestParameters(unittest.TestCase): p1 = params_dup.get(name) self.assertTrue(numpy.isclose(p0, p1).all()) + def test_initializer(self): + def initializer(name): + assert name == "fc.w" + mat = numpy.ones((3, 2), dtype=numpy.float32) + mat[1, 1] = 2 + return mat + + x = layer.data(name="x", type=data_type.dense_vector(3)) + y = layer.fc(x, + size=2, + bias_attr=False, + param_attr=ParamAttr( + name="fc.w", initializer=initializer)) + params = parameters.create(y) + val = params["fc.w"] + assert val.shape == (3, 2) + expected = numpy.array([[1, 1], [1, 2], [1, 1]], numpy.float32) + assert numpy.logical_and.reduce(numpy.reshape(val == expected, 6)) + if __name__ == '__main__': unittest.main() -- GitLab From 7b9080ef56ff28739031118b9aee50537a6acdab Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Thu, 15 Jun 2017 00:10:51 +0000 Subject: [PATCH 0655/3256] Implement master client, cgo and Python part --- go/master/c/client.go | 49 +++++++++++++++++----- go/master/client.go | 15 ++++--- go/master/client_test.go | 2 +- go/master/python/.gitignore | 1 + go/master/python/build.sh | 4 ++ go/master/python/paddle_master/.gitignore | 2 + go/master/python/paddle_master/__init__.py | 3 ++ go/master/python/paddle_master/client.py | 39 +++++++++++++++++ go/master/python/setup.py | 19 +++++++++ go/pserver/cclient/cclient.go | 13 +++--- 10 files changed, 124 insertions(+), 23 deletions(-) create mode 100644 go/master/python/.gitignore create mode 100755 go/master/python/build.sh create mode 100644 go/master/python/paddle_master/.gitignore create mode 100644 go/master/python/paddle_master/__init__.py create mode 100644 go/master/python/paddle_master/client.py create mode 100644 go/master/python/setup.py diff --git a/go/master/c/client.go b/go/master/c/client.go index 220184c3af7..8a437eb2238 100644 --- a/go/master/c/client.go +++ b/go/master/c/client.go @@ -1,6 +1,12 @@ package main /* +#include +#include +#include + +#define PADDLE_MASTER_OK 0 +#define PADDLE_MASTER_ERROR -1 typedef int paddle_master_client; */ @@ -14,6 +20,7 @@ import ( "github.com/PaddlePaddle/Paddle/go/master" ) +var nullPtr = unsafe.Pointer(uintptr(0)) var mu sync.Mutex var handleMap = make(map[C.paddle_master_client]*master.Client) var curHandle C.paddle_master_client @@ -47,17 +54,16 @@ func (a addresser) Address() string { return string(a) } -//paddle_new_master_client -func paddle_new_master_client(addr *C.char, buf_size C.int) C.paddle_master_client { +//export paddle_new_master_client +func paddle_new_master_client(addr *C.char) C.paddle_master_client { a := C.GoString(addr) - c := master.NewClient(addresser(a), int(buf_size)) + c := master.NewClient(addresser(a)) return add(c) } -//export paddle_new_etcd_master_client -func paddle_new_etcd_master_client(etcd_addr *C.char) C.paddle_master_client { - // TODO(helin): fault tolerant master client using etcd. - panic("not implemented.") +//export paddle_release_master_client +func paddle_release_master_client(client C.paddle_master_client) { + remove(client) } //export paddle_set_dataset @@ -65,17 +71,40 @@ func paddle_set_dataset(client C.paddle_master_client, path **C.char, size C.int c := get(client) var paths []string for i := 0; i < int(size); i++ { - ptr := (**C.char)(unsafe.Pointer(uintptr(unsafe.Pointer(path)) + uintptr(size))) + ptr := (**C.char)(unsafe.Pointer(uintptr(unsafe.Pointer(path)) + uintptr(i)*unsafe.Sizeof(*path))) str := C.GoString(*ptr) paths = append(paths, str) } err := c.SetDataset(paths) if err != nil { log.Println(err) - return -1 + return C.PADDLE_MASTER_ERROR + } + + return C.PADDLE_MASTER_OK +} + +//export paddle_next_record +func paddle_next_record(client C.paddle_master_client, record **C.uchar) C.int { + c := get(client) + r := c.NextRecord() + if len(r) == 0 { + *record = (*C.uchar)(nullPtr) + return 0 } - return 0 + size := C.size_t(len(r)) + *record = (*C.uchar)(C.malloc(size)) + C.memcpy(unsafe.Pointer(*record), unsafe.Pointer(&r[0]), size) + return C.int(size) +} + +//export mem_free +func mem_free(p unsafe.Pointer) { + // "free" may be a better name for this function, but doing so + // will cause calling any function of this library from Python + // ctypes hanging. + C.free(p) } func main() {} diff --git a/go/master/client.go b/go/master/client.go index 1c8a8d73d0c..73c945ddc5a 100644 --- a/go/master/client.go +++ b/go/master/client.go @@ -21,13 +21,10 @@ type Client struct { } // NewClient creates a new Client. -// -// bufSize is the record buffer size. NextRecord will read from the -// buffer. -func NewClient(addr Addresser, bufSize int) *Client { +func NewClient(addr Addresser) *Client { c := &Client{} c.conn = connection.New() - c.ch = make(chan []byte, bufSize) + c.ch = make(chan []byte) go c.monitorMaster(addr) go c.getRecords() return c @@ -53,11 +50,19 @@ func (c *Client) getRecords() { c.ch <- s.Record() } + if s.Err() != nil { + log.Println(err, chunk.Path) + } + err = f.Close() if err != nil { log.Println(err) } } + + // We treat a task as finished whenever the last data + // instance of the task is read. This is not exactly + // correct, but a reasonable approximation. c.taskFinished(t.ID) } } diff --git a/go/master/client_test.go b/go/master/client_test.go index 2b3f873ecf3..02751aeb301 100644 --- a/go/master/client_test.go +++ b/go/master/client_test.go @@ -60,7 +60,7 @@ func TestNextRecord(t *testing.T) { w.Close() f.Close() - c := master.NewClient(master.TestAddresser(fmt.Sprintf(":%d", p)), 10) + c := master.NewClient(master.TestAddresser(fmt.Sprintf(":%d", p))) c.SetDataset([]string{path}) for pass := 0; pass < 50; pass++ { diff --git a/go/master/python/.gitignore b/go/master/python/.gitignore new file mode 100644 index 00000000000..704d307510b --- /dev/null +++ b/go/master/python/.gitignore @@ -0,0 +1 @@ +*.whl diff --git a/go/master/python/build.sh b/go/master/python/build.sh new file mode 100755 index 00000000000..e3dbd7b0bc3 --- /dev/null +++ b/go/master/python/build.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +go build -buildmode=c-shared ../c && rm c.h && mv c paddle_master/libmaster.so +pip wheel . diff --git a/go/master/python/paddle_master/.gitignore b/go/master/python/paddle_master/.gitignore new file mode 100644 index 00000000000..4cd99212590 --- /dev/null +++ b/go/master/python/paddle_master/.gitignore @@ -0,0 +1,2 @@ +*.so +*.pyc diff --git a/go/master/python/paddle_master/__init__.py b/go/master/python/paddle_master/__init__.py new file mode 100644 index 00000000000..c8975b5d4a3 --- /dev/null +++ b/go/master/python/paddle_master/__init__.py @@ -0,0 +1,3 @@ +from client import * + +__all__ = ['client'] diff --git a/go/master/python/paddle_master/client.py b/go/master/python/paddle_master/client.py new file mode 100644 index 00000000000..ea2942f6969 --- /dev/null +++ b/go/master/python/paddle_master/client.py @@ -0,0 +1,39 @@ +import ctypes +import os + +path = os.path.join(os.path.dirname(__file__), "libmaster.so") +lib = ctypes.cdll.LoadLibrary(path) + + +class client(object): + """ + client is a client to the master server. + """ + + def __init__(self, addr, buf_size): + self.c = lib.paddle_new_master_client(addr, buf_size) + + def close(self): + lib.paddle_release_master_client(self.c) + self.c = None + + def set_dataset(self, paths): + holder_type = ctypes.c_char_p * len(paths) + holder = holder_type() + print paths + for idx, path in enumerate(paths): + c_ptr = ctypes.c_char_p(path) + holder[idx] = c_ptr + lib.paddle_set_dataset(self.c, holder, len(paths)) + + def next_record(self): + p = ctypes.c_char_p() + ret = ctypes.pointer(p) + size = lib.paddle_next_record(self.c, ret) + if size == 0: + # empty record + return "" + record = ret.contents.value[:size] + # memory created from C should be freed. + lib.mem_free(ret.contents) + return record diff --git a/go/master/python/setup.py b/go/master/python/setup.py new file mode 100644 index 00000000000..d7b6e9ecab6 --- /dev/null +++ b/go/master/python/setup.py @@ -0,0 +1,19 @@ +from setuptools import setup, Distribution + + +class BinaryDistribution(Distribution): + def has_ext_modules(foo): + return True + + +setup( + name='paddle_master', + version='0.1', + description='The client of the master server of PaddlePaddle.', + url='https://github.com/PaddlePaddle/Paddle/go/master/python', + author='PaddlePaddle Authors', + author_email='paddle-dev@baidu.com', + license='Apache 2.0', + packages=['paddle_master'], + package_data={'master': ['libmaster.so'], }, + distclass=BinaryDistribution) diff --git a/go/pserver/cclient/cclient.go b/go/pserver/cclient/cclient.go index 4476e762dad..3e074a9f2aa 100644 --- a/go/pserver/cclient/cclient.go +++ b/go/pserver/cclient/cclient.go @@ -1,7 +1,6 @@ package main /* -#include #include typedef enum { PADDLE_ELEMENT_TYPE_INT32 = 0, @@ -223,12 +222,12 @@ func paddle_get_params(client C.paddle_pserver_client, dst **C.paddle_parameter, if unsafe.Pointer(param) == nullPtr { log.Println("must pre-allocate parameter.") return C.PSERVER_ERROR - } else { - if unsafe.Pointer(param.content) != nullPtr { - if int(param.content_len) != len(p.Content) { - log.Printf("the pre-allocated content len does not match parameter content len. Pre-allocated len: %d, returned len: %d", param.content_len, len(p.Content)) - return C.PSERVER_ERROR - } + } + + if unsafe.Pointer(param.content) != nullPtr { + if int(param.content_len) != len(p.Content) { + log.Printf("the pre-allocated content len does not match parameter content len. Pre-allocated len: %d, returned len: %d", param.content_len, len(p.Content)) + return C.PSERVER_ERROR } } -- GitLab From 7613733d75f0e232fcff4b6fc2320ae3c2d01346 Mon Sep 17 00:00:00 2001 From: xuwei06 Date: Wed, 14 Jun 2017 17:20:14 -0700 Subject: [PATCH 0656/3256] Fix pserver cmake Use CMAKE_CURRENT_BINARY_DIR instead of CMAKE_BINARY_DIR to make it more portable (when paddle is used as subdirectory) --- go/pserver/cclient/CMakeLists.txt | 4 ++-- go/pserver/cclient/test/CMakeLists.txt | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go/pserver/cclient/CMakeLists.txt b/go/pserver/cclient/CMakeLists.txt index e00dd6b14a9..7967af51ee9 100644 --- a/go/pserver/cclient/CMakeLists.txt +++ b/go/pserver/cclient/CMakeLists.txt @@ -13,8 +13,8 @@ go_library(paddle_pserver_cclient STATIC) if(PROJ_ROOT) add_custom_command(OUTPUT ${PROJ_ROOT}/paddle/trainer/libpaddle_pserver_cclient.a - COMMAND cp ${CMAKE_BINARY_DIR}/go/pserver/cclient/libpaddle_pserver_cclient.h ${PROJ_ROOT}/paddle/trainer/ - COMMAND cp ${CMAKE_BINARY_DIR}/go/pserver/cclient/libpaddle_pserver_cclient.a ${PROJ_ROOT}/paddle/trainer/ + COMMAND cp ${CMAKE_CURRENT_BINARY_DIR}/libpaddle_pserver_cclient.h ${PROJ_ROOT}/paddle/trainer/ + COMMAND cp ${CMAKE_CURRENT_BINARY_DIR}/libpaddle_pserver_cclient.a ${PROJ_ROOT}/paddle/trainer/ WORKING_DIRECTORY ${PROJ_ROOT}/paddle DEPENDS paddle_pserver_cclient) add_custom_target(paddle_pserver_cclient_lib ALL DEPENDS ${PROJ_ROOT}/paddle/trainer/libpaddle_pserver_cclient.a) diff --git a/go/pserver/cclient/test/CMakeLists.txt b/go/pserver/cclient/test/CMakeLists.txt index 882a894ef2b..1a3dd7e5e9e 100644 --- a/go/pserver/cclient/test/CMakeLists.txt +++ b/go/pserver/cclient/test/CMakeLists.txt @@ -8,13 +8,13 @@ add_dependencies(test_cclient paddle_pserver_cclient) if(APPLE) set(CMAKE_EXE_LINKER_FLAGS "-framework CoreFoundation -framework Security") else() - set(CMAKE_EXE_LINKER_FLAGS "-pthread") + set(CMAKE_EXE_LINKER_FLAGS "-pthread") endif() if(PROJ_ROOT) - include_directories(${CMAKE_BINARY_DIR}/go/pserver/cclient/) - target_link_libraries(main ${CMAKE_BINARY_DIR}/go/pserver/cclient/libpaddle_pserver_cclient.a pthread) - target_link_libraries(test_cclient ${CMAKE_BINARY_DIR}/go/pserver/cclient/libpaddle_pserver_cclient.a pthread) + include_directories(${CMAKE_CURRENT_BINARY_DIR}/..) + target_link_libraries(main ${CMAKE_CURRENT_BINARY_DIR}/../libpaddle_pserver_cclient.a pthread) + target_link_libraries(test_cclient ${CMAKE_CURRENT_BINARY_DIR}/../libpaddle_pserver_cclient.a pthread) else(PROJ_ROOT) include_directories(${CMAKE_BINARY_DIR}) target_link_libraries(main ${CMAKE_BINARY_DIR}/libpaddle_pserver_cclient.a pthread) -- GitLab From 4970484d37a50d25fab14e7178d8569f6473a24a Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Thu, 15 Jun 2017 00:33:09 +0000 Subject: [PATCH 0657/3256] improve comment, fix build error --- go/master/client.go | 4 +++- go/master/client_internal_test.go | 9 +++++---- go/master/service.go | 4 ++-- go/pserver/cclient/test/main.c | 1 + 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/go/master/client.go b/go/master/client.go index 73c945ddc5a..db6b17d2f0d 100644 --- a/go/master/client.go +++ b/go/master/client.go @@ -34,6 +34,8 @@ func (c *Client) getRecords() { for { t, err := c.getTask() if err != nil { + // TODO(helin): wait before move on with next + // getTask call. log.Println(err) continue } @@ -125,7 +127,7 @@ func (c *Client) taskFinished(taskID int) error { // NextRecord returns next record in the dataset. // -// NextRecord will block until next record is available. It is +// NextRecord will block until the next record is available. It is // thread-safe. func (c *Client) NextRecord() []byte { return <-c.ch diff --git a/go/master/client_internal_test.go b/go/master/client_internal_test.go index 362668202a7..00fcca0e2cf 100644 --- a/go/master/client_internal_test.go +++ b/go/master/client_internal_test.go @@ -75,6 +75,7 @@ func TestGetFinishTask(t *testing.T) { } f.Close() + // Manually intialize client to avoid calling c.getRecords() c := &Client{} c.conn = connection.New() go c.monitorMaster(TestAddresser(fmt.Sprintf(":%d", p))) @@ -85,19 +86,19 @@ func TestGetFinishTask(t *testing.T) { for idx := 0; idx < totalTask; idx++ { task, err := c.getTask() if err != nil { - t.Fatal(err, " pass:", i) + t.Fatalf("Error: %v, pass: %d\n", err, i) } tasks = append(tasks, task) } _, err = c.getTask() if err == nil { - t.Fatal("Should get error. Pass:", i) + t.Fatalf("Should get error, pass: %d\n", i) } err = c.taskFinished(tasks[0].ID) if err != nil { - t.Fatal(err, "pass:", i) + t.Fatalf("Error: %v, pass: %d\n", err, i) } tasks = tasks[1:] task, err := c.getTask() @@ -109,7 +110,7 @@ func TestGetFinishTask(t *testing.T) { for _, task := range tasks { err = c.taskFinished(task.ID) if err != nil { - t.Fatal(err, " pass:", i) + t.Fatalf("Error: %v, pass: %d\n", err, i) } } } diff --git a/go/master/service.go b/go/master/service.go index 2e165138fb8..55e1e2d1a4a 100644 --- a/go/master/service.go +++ b/go/master/service.go @@ -207,12 +207,12 @@ func (s *Service) checkTimeoutFunc(taskID int, epoch int) func() { t.NumTimeout++ if t.NumTimeout > s.timeoutMax { - log.Warningf("Task %v failed %d times, discard.\n", t.Task, t.NumTimeout) + log.Warningf("Task %v timed out %d times, discard.\n", t.Task, t.NumTimeout) s.taskQueues.Failed = append(s.taskQueues.Failed, t.Task) return } - log.Warningf("Task %v failed %d times, retry.\n", t.Task, t.NumTimeout) + log.Warningf("Task %v timed out %d times, retry.\n", t.Task, t.NumTimeout) s.taskQueues.Todo = append(s.taskQueues.Todo, t) } } diff --git a/go/pserver/cclient/test/main.c b/go/pserver/cclient/test/main.c index 07e1b86b432..03f749d4e46 100644 --- a/go/pserver/cclient/test/main.c +++ b/go/pserver/cclient/test/main.c @@ -1,4 +1,5 @@ #include +#include #include "libpaddle_pserver_cclient.h" -- GitLab From e730bc5969a6ee7c2593b9efd7faea1bef799b0c Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Thu, 15 Jun 2017 00:36:33 +0000 Subject: [PATCH 0658/3256] improve comment --- go/master/python/paddle_master/client.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/go/master/python/paddle_master/client.py b/go/master/python/paddle_master/client.py index ea2942f6969..2dfcb399046 100644 --- a/go/master/python/paddle_master/client.py +++ b/go/master/python/paddle_master/client.py @@ -31,9 +31,9 @@ class client(object): ret = ctypes.pointer(p) size = lib.paddle_next_record(self.c, ret) if size == 0: - # empty record + # Empty record return "" record = ret.contents.value[:size] - # memory created from C should be freed. + # Memory created from C should be freed. lib.mem_free(ret.contents) return record -- GitLab From df5bc787022907d7a0acfe3b1089c7d93febdb61 Mon Sep 17 00:00:00 2001 From: dzhwinter Date: Thu, 15 Jun 2017 12:27:20 +0800 Subject: [PATCH 0659/3256] "fix tensor shared_ptr" --- paddle/optimizer/Tensor.h | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/paddle/optimizer/Tensor.h b/paddle/optimizer/Tensor.h index 49b8dadaaef..80a8c93081e 100644 --- a/paddle/optimizer/Tensor.h +++ b/paddle/optimizer/Tensor.h @@ -5,7 +5,6 @@ #include #include -#include "paddle/math/MemoryHandle.h" #include "paddle/utils/Common.h" #include "paddle/utils/Logging.h" @@ -15,17 +14,16 @@ namespace optimizer { template class TensorT { public: - TensorT(size_t size) - : TensorT(std::make_shared(size * sizeof(float)), size) { + TensorT(size_t size) : height_(1), width_(size) { + data_ptr_ = std::shared_ptr(new T[size], std::default_delete()); + data_ = data_ptr_.get(); } - TensorT(CpuMemHandlePtr handle, size_t size) - : height_(1), - width_(size), - data_(reinterpret_cast(handle->getBuf())) {} - TensorT(T* data, size_t size) : height_(1), width_(size), data_(data) {} + TensorT(T* data, size_t size) + : height_(1), width_(size), data_ptr_(nullptr), data_(data) {} - TensorT(T* data, size_t h, size_t w) : height_(h), width_(w), data_(data) {} + TensorT(T* data, size_t h, size_t w) + : height_(h), width_(w), data_ptr_(nullptr), data_(data) {} virtual ~TensorT() {} @@ -45,6 +43,7 @@ public: protected: size_t height_; size_t width_; + std::shared_ptr data_ptr_; T* data_; }; -- GitLab From a421f479a23670e743005af2e9ba38eb8dcd6268 Mon Sep 17 00:00:00 2001 From: yangyaming Date: Thu, 15 Jun 2017 17:07:12 +0800 Subject: [PATCH 0660/3256] Follow comments. --- paddle/gserver/layers/DetectionUtil.cpp | 20 +++++++++++--------- paddle/gserver/layers/DetectionUtil.h | 10 ++++------ 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/paddle/gserver/layers/DetectionUtil.cpp b/paddle/gserver/layers/DetectionUtil.cpp index e9295edb6dc..3e61adc66e6 100644 --- a/paddle/gserver/layers/DetectionUtil.cpp +++ b/paddle/gserver/layers/DetectionUtil.cpp @@ -23,8 +23,9 @@ size_t appendWithPermute(const Matrix& inMatrix, size_t outOffset, size_t batchSize, Matrix& outMatrix, - PermMode permMode, - bool useGpu) { + PermMode permMode) { + CHECK_EQ(inMatrix.useGpu(), outMatrix.useGpu()); + bool useGpu = inMatrix.useGpu(); if (permMode == kNCHWToNHWC) { size_t inElementCnt = inMatrix.getElementCnt(); size_t channels = inElementCnt / (height * width * batchSize); @@ -58,8 +59,9 @@ size_t decomposeWithPermute(const Matrix& inMatrix, size_t inOffset, size_t batchSize, Matrix& outMatrix, - PermMode permMode, - bool useGpu) { + PermMode permMode) { + CHECK_EQ(inMatrix.useGpu(), outMatrix.useGpu()); + bool useGpu = inMatrix.useGpu(); if (permMode == kNHWCToNCHW) { size_t outElementCnt = outMatrix.getElementCnt(); size_t channels = outElementCnt / (height * width * batchSize); @@ -98,17 +100,17 @@ real jaccardOverlap(const NormalizedBBox& bbox1, const NormalizedBBox& bbox2) { real interWidth = interXMax - interXMin; real interHeight = interYMax - interYMin; - real interSize = interWidth * interHeight; + real interArea = interWidth * interHeight; - real bboxSize1 = bbox1.getSize(); - real bboxSize2 = bbox2.getSize(); + real bboxArea1 = bbox1.getArea(); + real bboxArea2 = bbox2.getArea(); - return interSize / (bboxSize1 + bboxSize2 - interSize); + return interArea / (bboxArea1 + bboxArea2 - interArea); } } void encodeBBoxWithVar(const NormalizedBBox& priorBBox, - const vector priorBBoxVar, + const vector& priorBBoxVar, const NormalizedBBox& gtBBox, vector& outVec) { real priorBBoxWidth = priorBBox.getWidth(); diff --git a/paddle/gserver/layers/DetectionUtil.h b/paddle/gserver/layers/DetectionUtil.h index 263dea0013b..fe4f9f075e4 100644 --- a/paddle/gserver/layers/DetectionUtil.h +++ b/paddle/gserver/layers/DetectionUtil.h @@ -40,7 +40,7 @@ struct BBoxBase { T getCenterY() const { return (yMin + yMax) / 2; } - T getSize() const { return getWidth() * getHeight(); } + T getArea() const { return getWidth() * getHeight(); } // coordinate of bounding box T xMin; @@ -67,8 +67,7 @@ size_t appendWithPermute(const Matrix& inMatrix, size_t outOffset, size_t batchSize, Matrix& outMatrix, - PermMode permMode, - bool useGpu); + PermMode permMode); /** * @brief First permute input maxtrix then decompose to output @@ -80,8 +79,7 @@ size_t decomposeWithPermute(const Matrix& inMatrix, size_t offset, size_t batchSize, Matrix& outMatrix, - PermMode permMode, - bool useGpu); + PermMode permMode); /** * @brief Compute jaccard overlap between two bboxes. @@ -99,7 +97,7 @@ real jaccardOverlap(const NormalizedBBox& bbox1, const NormalizedBBox& bbox2); * @param outVec Output vector */ void encodeBBoxWithVar(const NormalizedBBox& priorBBox, - const vector priorBBoxVar, + const vector& priorBBoxVar, const NormalizedBBox& gtBBox, vector& outVec); -- GitLab From 17994e38aa9b11b3df0e3dcf440606e849e5be4f Mon Sep 17 00:00:00 2001 From: xuwei06 Date: Wed, 14 Jun 2017 15:46:05 -0700 Subject: [PATCH 0661/3256] RecurrentGroup with mixed input sequence types No longer need to use SubsequenceInput. The framework will detect. --- .../RecurrentGradientMachine.cpp | 497 +++++++++++------- .../RecurrentGradientMachine.h | 55 +- paddle/gserver/layers/AgentLayer.cpp | 95 ++-- paddle/gserver/layers/AgentLayer.h | 84 +-- paddle/gserver/layers/SequencePoolLayer.cpp | 3 + paddle/gserver/tests/rnn_data_provider.py | 19 + .../tests/sequence_nest_rnn_multi_input.conf | 4 +- .../tests/sequence_rnn_matched_inputs.py | 85 +++ .../tests/sequence_rnn_mixed_inputs.py | 78 +++ .../tests/sequence_rnn_multi_input.conf | 2 +- .../tests/test_RecurrentGradientMachine.cpp | 9 + paddle/math/Vector.cpp | 7 +- paddle/parameter/Argument.h | 1 + .../test_recurrent_machine_generation.cpp | 2 + python/paddle/trainer/config_parser.py | 57 +- .../paddle/trainer_config_helpers/layers.py | 59 +-- .../configs/protostr/shared_gru.protostr | 4 - .../configs/protostr/shared_lstm.protostr | 4 - .../configs/protostr/test_rnn_group.protostr | 16 +- 19 files changed, 652 insertions(+), 429 deletions(-) create mode 100644 paddle/gserver/tests/sequence_rnn_matched_inputs.py create mode 100644 paddle/gserver/tests/sequence_rnn_mixed_inputs.py diff --git a/paddle/gserver/gradientmachines/RecurrentGradientMachine.cpp b/paddle/gserver/gradientmachines/RecurrentGradientMachine.cpp index 01158d1dce8..3e930380226 100644 --- a/paddle/gserver/gradientmachines/RecurrentGradientMachine.cpp +++ b/paddle/gserver/gradientmachines/RecurrentGradientMachine.cpp @@ -214,7 +214,6 @@ void RecurrentGradientMachine::init( inFrameLines_[i].linkName = subModelConfig->in_links(i).link_name(); inFrameLines_[i].inLayer = rootNetwork_->getLayer(subModelConfig->in_links(i).layer_name()); - inFrameLines_[i].hasSubseq = subModelConfig->in_links(i).has_subseq(); } outFrameLines_.resize(subModelConfig->out_links_size()); @@ -241,11 +240,8 @@ void RecurrentGradientMachine::init( rootNetwork_->getLayer(memoryConfig.boot_layer_name()); LayerConfig scatterConfig = *agentConfig; - memoryFrameLines_[i].is_sequence = memoryConfig.is_sequence(); memoryFrameLines_[i].rootAgent.reset( - memoryConfig.is_sequence() - ? new SequenceScatterAgentLayer(scatterConfig) - : new ScatterAgentLayer(scatterConfig)); + new ScatterAgentLayer(scatterConfig)); memoryFrameLines_[i].rootAgent->init(LayerMap(), parameterMap_); memoryFrameLines_[i].bootLayer = memoryFrameLines_[i].rootAgent; @@ -267,9 +263,7 @@ void RecurrentGradientMachine::init( if (subModelConfig->has_generator()) { memoryFrameLines_[i].scatterAgents.resize(2); for (auto& agent : memoryFrameLines_[i].scatterAgents) { - agent.reset(memoryConfig.is_sequence() - ? new SequenceScatterAgentLayer(*agentConfig) - : new ScatterAgentLayer(*agentConfig)); + agent.reset(new ScatterAgentLayer(*agentConfig)); agent->init(LayerMap(), parameterMap_); } } @@ -297,8 +291,6 @@ void RecurrentGradientMachine::init( if (subModelConfig->evaluator_names_size() > 0) { evaluator_.reset(frames_[0]->makeEvaluator()); } - - targetInfoInlinkId_ = subModelConfig->target_inlinkid(); } void RecurrentGradientMachine::resizeOrCreateFrames(int numFrames) { @@ -376,108 +368,102 @@ void RecurrentGradientMachine::prefetch(const std::vector& inArgs) { LOG(FATAL) << "should not use this function"; } -void RecurrentGradientMachine::forward(const std::vector& inArgs, - std::vector* outArgs, - PassType passType) { - if (inFrameLines_.empty() && passType == PASS_TEST) { - generateSequence(); - return; - } // else forward.. - - const Argument& input = inFrameLines_[0].inLayer->getOutput(); - CHECK(input.sequenceStartPositions); - int batchSize = input.getBatchSize(); - size_t numSequences = input.getNumSequences(); - const int* starts = input.sequenceStartPositions->getData(false); - bool hasSubseq = input.hasSubseq(); - - // In case of !hasSubseq or targetInfoInlinkId_ == -1, all inlinks share the - // same inframe info - bool shareInlinkInfo = !hasSubseq || targetInfoInlinkId_ == -1; - - // Defaultly, share info with the first inlink - if (shareInlinkInfo) { - targetInfoInlinkId_ = 0; - } - - // check hasSubseq in both config and input are the same - CHECK_EQ(hasSubseq, inFrameLines_[0].hasSubseq); - - CHECK_EQ(starts[numSequences], batchSize); - CHECK(input.sequenceStartPositions); - - // check other inputs has same sequence length and start - for (size_t i = 1; i < inFrameLines_.size(); ++i) { - const Argument& input1 = inFrameLines_[i].inLayer->getOutput(); - CHECK_EQ((size_t)input1.getNumSequences(), numSequences); - // check all inputs should have same hasSubseq flag - CHECK_EQ(input.hasSubseq(), inFrameLines_[0].hasSubseq); - - // if shareInlinkInfo, checks: - // 1. all inlinks have same number of total tokens - // 2. all inlinks have same number of tokens for each sentence of each - // sample. If hasSubseq, one sample has multiple sentence, else, one - // sample is one sentence - if (shareInlinkInfo) { - CHECK_EQ(input1.getBatchSize(), batchSize); - CHECK(std::equal(starts, - starts + numSequences + 1, - input1.sequenceStartPositions->getData(false))); +void RecurrentGradientMachine::checkInputConsistency( + int inlinkId, const std::vector& seqInfo) { + if (commonSeqInfo_.empty()) { + commonSeqInfo_.resize(seqInfo.size()); + for (size_t i = 0; i < seqInfo.size(); ++i) { + commonSeqInfo_[i].topLevelLength = seqInfo[i].topLevelLength; + commonSeqInfo_[i].seqId = seqInfo[i].seqId; + } + } else { + CHECK_EQ(commonSeqInfo_.size(), seqInfo.size()) + << " RecurrentGroup " << subModelName_ << " input " << inlinkId + << " has mismatched number of sequences"; + for (size_t i = 0; i < seqInfo.size(); ++i) { + CHECK_EQ(commonSeqInfo_[i].topLevelLength, seqInfo[i].topLevelLength) + << " RecurrentGroup " << subModelName_ << " input " << inlinkId + << " has mismatched sequence length"; + CHECK_EQ(commonSeqInfo_[i].seqId, seqInfo[i].seqId) + << " RecurrentGroup " << subModelName_ << " input " << inlinkId + << " has mismatched sequence length"; } } +} - if (hasSubseq) { - CHECK(input.subSequenceStartPositions); - size_t numSubSequences = input.getNumSubSequences(); - const int* subStarts = input.subSequenceStartPositions->getData(false); - CHECK_EQ(subStarts[numSubSequences], batchSize); - // if hasSubseq, check other inputs has same sub-sequence and sub-start - for (size_t i = 1; i < inFrameLines_.size(); ++i) { - const Argument& input1 = inFrameLines_[i].inLayer->getOutput(); - CHECK_EQ((size_t)input1.getNumSubSequences(), numSubSequences); - if (shareInlinkInfo) { - CHECK(std::equal(subStarts, - subStarts + numSubSequences + 1, - input1.subSequenceStartPositions->getData(false))); - } +void RecurrentGradientMachine::calcNumSequencesAtEachStep() { + int numSequences = commonSeqInfo_.size(); + numSeqs_.resize(maxSequenceLength_); + for (int i = 0; i < numSequences; ++i) { + for (int j = 0; j < commonSeqInfo_[i].topLevelLength; ++j) { + numSeqs_[j] = i + 1; } } +} +void RecurrentGradientMachine::reorganizeInput(PassType passType) { info_.clear(); info_.resize(inFrameLines_.size()); + commonSeqInfo_.clear(); seqInfos_.clear(); seqInfos_.resize(inFrameLines_.size()); + for (size_t i = 0; i < inFrameLines_.size(); i++) { + const Argument& input = inFrameLines_[i].inLayer->getOutput(); + if (!input.hasSeq()) { + continue; + } + input.getSeqInfo(&seqInfos_[i]); + checkInputConsistency(i, seqInfos_[i]); + } + CHECK(!commonSeqInfo_.empty()) + << "At least one input needs to be sequence or subsequence"; + maxSequenceLength_ = commonSeqInfo_[0].topLevelLength; + + calcNumSequencesAtEachStep(); + + for (size_t i = 0; i < inFrameLines_.size(); ++i) { + const Argument& input = inFrameLines_[i].inLayer->getOutput(); + if (!input.hasSeq()) { + seqInfos_[i] = commonSeqInfo_; + } + createInFrameInfo(i, input, passType); + } + { AsyncGpuBlock asyncGpuBlock; - // if shareInlinkInfo, only calculate info of the first inlink - // else, calculate info for each inlink - if (shareInlinkInfo) { - input.getSeqInfo(&seqInfos_[0]); - maxSequenceLength_ = seqInfos_[0][0].topLevelLength; - createInFrameInfo(0, input, passType); - } else { - for (size_t i = 0; i < inFrameLines_.size(); i++) { - const Argument& input1 = inFrameLines_[i].inLayer->getOutput(); - input1.getSeqInfo(&seqInfos_[i]); - maxSequenceLength_ = seqInfos_[i][0].topLevelLength; - createInFrameInfo(i, input1, passType); - } - } // inFrameLine select rows in real layer one time for (size_t i = 0; i < inFrameLines_.size(); i++) { - int curInlinkId = shareInlinkInfo ? 0 : i; selectRowsOneTime(inFrameLines_[i].inLayer, - info_[curInlinkId].allIds, + info_[i].allIds, &(inFrameLines_[i].outArg), passType); } } - resizeOrCreateFrames(maxSequenceLength_); - resizeBootFrame(numSequences); +} + +void RecurrentGradientMachine::reorganizeOutput(PassType passType) { + calcSequenceStartPositions(); + for (size_t i = 0; i < outFrameLines_.size(); ++i) { + Info info; + auto& outFrameLine = outFrameLines_[i]; + ICpuGpuVectorPtr sequenceStartPositions; + ICpuGpuVectorPtr subSequenceStartPositions; + createOutFrameInfo( + outFrameLine, info, sequenceStartPositions, subSequenceStartPositions); + auto gatherAgent = + dynamic_cast(outFrameLine.agentLayer.get()); + CHECK_NOTNULL(gatherAgent); + gatherAgent->copyIdAndSequenceInfo(sequenceStartPositions, + subSequenceStartPositions, + info.allIds, + info.idIndex); + } +} +void RecurrentGradientMachine::connectFrames(PassType passType) { for (auto& memoryFrameLine : memoryFrameLines_) { if (memoryFrameLine.rootAgent) { auto scatterAgent = @@ -487,8 +473,9 @@ void RecurrentGradientMachine::forward(const std::vector& inArgs, memoryFrameLine.outArg, memoryFrameLine.allIds, /* idIndex */ 0, - memoryFrameLine.allIds->getSize()); - if (memoryFrameLine.is_sequence) { // memoryConfig is sequence + memoryFrameLine.allIds->getSize(), + /* handleBackward */ true); + if (memoryFrameLine.sequenceStartPositions) { int size = memoryFrameLine.sequenceStartPositions->getSize(); scatterAgent->setSequenceStartPositions( memoryFrameLine.sequenceStartPositions, @@ -501,28 +488,26 @@ void RecurrentGradientMachine::forward(const std::vector& inArgs, for (auto& outFrameLine : outFrameLines_) { auto gatherAgent = dynamic_cast(outFrameLine.agentLayer.get()); - CHECK_NOTNULL(gatherAgent); - gatherAgent->copyIdAndSequenceInfo(input, - info_[targetInfoInlinkId_].allIds, - info_[targetInfoInlinkId_].idIndex); + gatherAgent->clearRealLayers(); } - for (int i = 0; i < maxSequenceLength_; ++i) { - int idSize = 0; // connect in_links for (size_t j = 0; j < inFrameLines_.size(); ++j) { - Info& info = info_[shareInlinkInfo ? 0 : j]; + Info& info = info_[j]; // idSize denotes the sum number of tokens in each length i - idSize = info.idIndex[i + 1] - info.idIndex[i]; + int idIndex = info.idIndex.empty() ? 0 : info.idIndex[i]; + int idSize = info.idIndex.empty() ? numSeqs_[i] + : info.idIndex[i + 1] - info.idIndex[i]; InFrameLine inFrameLine = inFrameLines_[j]; auto scatterAgent = dynamic_cast(inFrameLine.agents[i].get()); scatterAgent->setRealLayerAndOutput(inFrameLine.inLayer, inFrameLine.outArg, info.allIds, - info.idIndex[i], - idSize); - if (hasSubseq) { + idIndex, + idSize, + i == 0); + if (info.sequenceStartPositions) { // size: the length of subsequence int size = info.seqStartPosIndex[i + 1] - info.seqStartPosIndex[i]; scatterAgent->setSequenceStartPositions( @@ -536,11 +521,6 @@ void RecurrentGradientMachine::forward(const std::vector& inArgs, dynamic_cast(outFrameLine.agentLayer.get()); gatherAgent->addRealLayer(outFrameLine.frames[i]); } - // connect memory links - // Adopt info_[0].idIndex because seq which has_subseq=True - // doesn't support Memory with !hasSubseq bootlayer; - // And inlinks that !hasSubSeq must have same inlink length. - idSize = info_[0].idIndex[i + 1] - info_[0].idIndex[i]; for (auto& memoryFrameLine : memoryFrameLines_) { NeuralNetwork::connect( memoryFrameLine.agents[i], @@ -548,6 +528,28 @@ void RecurrentGradientMachine::forward(const std::vector& inArgs, numSeqs_[i] /*height of agent*/); } } +} + +void RecurrentGradientMachine::forward(const std::vector& inArgs, + std::vector* outArgs, + PassType passType) { + /* inArgs and outArgs are not used. + The inputs are inFrameLines_[i].inLayer. + The outputs are outFramesLines_[i].agentLayer + */ + + if (inFrameLines_.empty() && passType == PASS_TEST) { + generateSequence(); + return; + } // else forward.. + + reorganizeInput(passType); + int numSequences = commonSeqInfo_.size(); + + resizeOrCreateFrames(maxSequenceLength_); + resizeBootFrame(numSequences); + + connectFrames(passType); REGISTER_TIMER_INFO("RecurrentFwTime", "RecurrentFwTime"); // forward @@ -558,16 +560,12 @@ void RecurrentGradientMachine::forward(const std::vector& inArgs, const std::vector inArgs; std::vector outArgs; frames_[i]->forward(inArgs, &outArgs, passType); - if (hasSubseq) { - for (auto& outFrameLine : outFrameLines_) { - CHECK(outFrameLine.frames[i]->getOutput().sequenceStartPositions) - << "In hierachical RNN, all out links should be from sequences."; - } - } } if (evaluator_ && passType == PASS_TEST) { this->eval(evaluator_.get()); } + + reorganizeOutput(passType); } void RecurrentGradientMachine::backward(const UpdateCallback& callback) { @@ -634,76 +632,228 @@ void RecurrentGradientMachine::removeBeamSearchStatisticsCallbacks() { this->beamSearchStatistics_ = nullptr; } } + +namespace { +void lenToStarts(std::vector& starts) { + int pos = 0; + starts.back() = 0; + for (auto& start : starts) { + int tmp = start; + start = pos; + pos += tmp; + } + starts.back() = pos; +} +} + +void RecurrentGradientMachine::calcSequenceStartPositions() { + std::vector starts(commonSeqInfo_.size() + 1); + for (auto& seqInfo : commonSeqInfo_) { + starts[seqInfo.seqId] = seqInfo.topLevelLength; + } + lenToStarts(starts); + ICpuGpuVector::resizeOrCreate(sequenceStartPositions_, starts.size(), false); + std::copy(starts.begin(), + starts.end(), + sequenceStartPositions_->getMutableData(false)); +} + +void RecurrentGradientMachine::checkOutputConsistency( + OutFrameLine& outFrameLine) { + bool hasSeq = outFrameLine.frames[0]->getOutput().hasSeq(); + for (int i = 0; i < maxSequenceLength_; ++i) { + LayerPtr frame = outFrameLine.frames[i]; + CHECK_EQ(hasSeq, frame->getOutput().hasSeq()); + int numSequences = frame->getOutput().getNumSequences(); + CHECK_EQ(numSeqs_[i], numSequences); + } +} + +void RecurrentGradientMachine::createOutFrameInfo( + OutFrameLine& outFrameLine, + Info& info, + ICpuGpuVectorPtr& sequenceStartPositions, + ICpuGpuVectorPtr& subSequenceStartPositions) { + checkOutputConsistency(outFrameLine); + + if (!outFrameLine.frames[0]->getOutput().hasSeq()) { + createOutFrameInfo_seq( + outFrameLine, info, sequenceStartPositions, subSequenceStartPositions); + } else { + createOutFrameInfo_subseq( + outFrameLine, info, sequenceStartPositions, subSequenceStartPositions); + } +} + +void RecurrentGradientMachine::createOutFrameInfo_seq( + OutFrameLine& outFrameLine, + Info& info, + ICpuGpuVectorPtr& sequenceStartPositions, + ICpuGpuVectorPtr& subSequenceStartPositions) { + std::vector allIds; + info.idIndex.resize(1, 0); // first idIndex = 0 + + const int* starts = sequenceStartPositions_->getData(false); + + for (int i = 0; i < maxSequenceLength_; ++i) { + LayerPtr frame = outFrameLine.frames[i]; + size_t numSequences = frame->getOutput().getNumSequences(); + for (size_t j = 0; j < numSequences; ++j) { + int seqStart = starts[commonSeqInfo_[j].seqId]; + int seqLength = commonSeqInfo_[j].topLevelLength; + allIds.push_back(reversed_ ? (seqStart + seqLength - 1 - i) + : (seqStart + i)); + } + info.idIndex.push_back(allIds.size()); + } + sequenceStartPositions = sequenceStartPositions_; + copyScattedId(allIds, &info.allIds, allIds.size()); + CHECK_EQ(info.idIndex.size(), static_cast(maxSequenceLength_ + 1)); +} + +void RecurrentGradientMachine::createOutFrameInfo_subseq( + OutFrameLine& outFrameLine, + Info& info, + ICpuGpuVectorPtr& sequenceStartPositions, + ICpuGpuVectorPtr& subSequenceStartPositions) { + size_t numSequences = commonSeqInfo_.size(); + std::vector allIds; + info.idIndex.resize(1, 0); // first idIndex = 0 + + const int* starts = sequenceStartPositions_->getData(false); + std::vector subStarts(starts[numSequences] + 1); + for (int i = 0; i < maxSequenceLength_; ++i) { + LayerPtr frame = outFrameLine.frames[i]; + size_t numSequences = frame->getOutput().getNumSequences(); + const int* seqStarts = + frame->getOutput().sequenceStartPositions->getData(false); + for (size_t j = 0; j < numSequences; ++j) { + subStarts[starts[commonSeqInfo_[j].seqId] + i] = + seqStarts[j + 1] - seqStarts[j]; + } + } + lenToStarts(subStarts); + + for (int i = 0; i < maxSequenceLength_; ++i) { + LayerPtr frame = outFrameLine.frames[i]; + size_t numSequences = frame->getOutput().getNumSequences(); + for (size_t j = 0; j < numSequences; ++j) { + int pos = starts[commonSeqInfo_[j].seqId] + i; + int subSeqStart = subStarts[pos]; + int subSeqEnd = subStarts[pos + 1]; + for (int k = subSeqStart; k < subSeqEnd; ++k) { + allIds.push_back(k); + } + } + info.idIndex.push_back(allIds.size()); + } + + ICpuGpuVector::resizeOrCreate( + subSequenceStartPositions, subStarts.size(), false); + int* cpuSubSequenceStartPositions = + subSequenceStartPositions->getMutableData(false); + std::copy(subStarts.begin(), subStarts.end(), cpuSubSequenceStartPositions); + ICpuGpuVector::resizeOrCreate( + sequenceStartPositions, numSequences + 1, false); + int* cpuSequenceStartPositions = + sequenceStartPositions->getMutableData(false); + for (size_t i = 0; i <= numSequences; ++i) { + cpuSequenceStartPositions[i] = subStarts[starts[i]]; + } + copyScattedId(allIds, &info.allIds, allIds.size()); + CHECK_EQ(info.idIndex.size(), static_cast(maxSequenceLength_ + 1)); +} + /* create scattered id infomation for all realLayer of inFrameLines one time. * If hasSubseq, will also create scattered sequenceStartPositions infomation * for all realLayer of inFrameLines one time. */ - void RecurrentGradientMachine::createInFrameInfo(int inlinkId, const Argument& input, PassType passType) { - bool hasSubseq = input.hasSubseq(); - // numSequences: # samples(sequences) in a batch - size_t numSequences = input.getNumSequences(); + if (!input.hasSeq()) { + createInFrameInfo_nonseq(inlinkId, input, passType); + } else if (!input.hasSubseq()) { + createInFrameInfo_seq(inlinkId, input, passType); + } else { + createInFrameInfo_subseq(inlinkId, input, passType); + } +} + +void RecurrentGradientMachine::createInFrameInfo_nonseq(int inlinkId, + const Argument& input, + PassType passType) { std::vector allIds; auto& seqInfo = seqInfos_[inlinkId]; - - numSeqs_.clear(); Info* inlinkInfo = &info_[inlinkId]; inlinkInfo->idIndex.clear(); - inlinkInfo->idIndex.push_back(0); // first idIndex = 0 + for (size_t i = 0; i < seqInfo.size(); ++i) { + allIds.push_back(seqInfo[i].seqId); + } + // copy and check scatterId + copyScattedId(allIds, &inlinkInfo->allIds, input.getBatchSize()); +} +void RecurrentGradientMachine::createInFrameInfo_seq(int inlinkId, + const Argument& input, + PassType passType) { + std::vector allIds; + auto& seqInfo = seqInfos_[inlinkId]; + Info* inlinkInfo = &info_[inlinkId]; + inlinkInfo->idIndex.resize(1, 0); // first idIndex = 0 + + for (int i = 0; i < maxSequenceLength_; ++i) { + for (int j = 0; j < numSeqs_[i]; ++j) { + int seqLength = seqInfo[j].topLevelLength; + int seqStart = seqInfo[j].seqStart; + allIds.push_back(reversed_ ? (seqStart + seqLength - 1 - i) + : (seqStart + i)); + } + inlinkInfo->idIndex.push_back(allIds.size()); + } + + // copy and check scatterId + copyScattedId(allIds, &inlinkInfo->allIds, input.getBatchSize()); + CHECK_EQ(inlinkInfo->idIndex.size(), + static_cast(maxSequenceLength_ + 1)); +} +void RecurrentGradientMachine::createInFrameInfo_subseq(int inlinkId, + const Argument& input, + PassType passType) { + std::vector allIds; + + auto& seqInfo = seqInfos_[inlinkId]; + + Info* inlinkInfo = &info_[inlinkId]; + inlinkInfo->idIndex.resize(1, 0); // first idIndex = 0 std::vector sequenceStartPositions; const int* subSequenceStartPositions = nullptr; - if (hasSubseq) { // for sequenceScatterAgentLayer - subSequenceStartPositions = input.subSequenceStartPositions->getData(false); - inlinkInfo->seqStartPosIndex.clear(); - inlinkInfo->seqStartPosIndex.push_back(0); // first seqStartPosIndex = 0 - } - // maxSequenceLength_: max topLevelLength in allsamples + subSequenceStartPositions = input.subSequenceStartPositions->getData(false); + inlinkInfo->seqStartPosIndex.clear(); + inlinkInfo->seqStartPosIndex.push_back(0); // first seqStartPosIndex = 0 for (int i = 0; i < maxSequenceLength_; ++i) { - if (hasSubseq) { - sequenceStartPositions.push_back(0); // first element = 0 - } - int numSeqs = 0; - for (size_t j = 0; j < numSequences; ++j) { - int seqLength = seqInfo[j].topLevelLength; - if (i >= seqLength) { - break; - } - ++numSeqs; - if (hasSubseq) { - int subSeqStart = subSequenceStartPositions[seqInfo[j].subSeqStart + i]; - int subSeqEnd = - subSequenceStartPositions[seqInfo[j].subSeqStart + i + 1]; - for (int k = subSeqStart; k < subSeqEnd; ++k) { - allIds.push_back(k); - } - sequenceStartPositions.push_back(sequenceStartPositions.back() + - subSeqEnd - subSeqStart); - } else { - int seqStart = seqInfo[j].seqStart; - allIds.push_back(reversed_ ? (seqStart + seqLength - 1 - i) - : (seqStart + i)); + sequenceStartPositions.push_back(0); // first element = 0 + for (int j = 0; j < numSeqs_[i]; ++j) { + int subSeqStart = subSequenceStartPositions[seqInfo[j].subSeqStart + i]; + int subSeqEnd = subSequenceStartPositions[seqInfo[j].subSeqStart + i + 1]; + for (int k = subSeqStart; k < subSeqEnd; ++k) { + allIds.push_back(k); } + sequenceStartPositions.push_back(sequenceStartPositions.back() + + subSeqEnd - subSeqStart); } inlinkInfo->idIndex.push_back(allIds.size()); - numSeqs_.push_back(numSeqs); - if (hasSubseq) { - inlinkInfo->seqStartPosIndex.push_back(sequenceStartPositions.size()); - } - } - if (hasSubseq) { - // inFrameLine create sequenceStartPositions one time - CHECK_EQ( - sequenceStartPositions.size(), - static_cast(maxSequenceLength_ + input.getNumSubSequences())); - CHECK_EQ(inlinkInfo->seqStartPosIndex.size(), - static_cast(maxSequenceLength_ + 1)); - createSeqPos(sequenceStartPositions, &inlinkInfo->sequenceStartPositions); + inlinkInfo->seqStartPosIndex.push_back(sequenceStartPositions.size()); } + // inFrameLine create sequenceStartPositions one time + CHECK_EQ( + sequenceStartPositions.size(), + static_cast(maxSequenceLength_ + input.getNumSubSequences())); + CHECK_EQ(inlinkInfo->seqStartPosIndex.size(), + static_cast(maxSequenceLength_ + 1)); + createSeqPos(sequenceStartPositions, &inlinkInfo->sequenceStartPositions); // copy and check scatterId copyScattedId(allIds, &inlinkInfo->allIds, input.getBatchSize()); @@ -717,11 +867,11 @@ void RecurrentGradientMachine::createMemoryFrameInfo( const Argument& input = (*memoryFrameLine).rootLayer->getOutput(); size_t numSequences = input.getNumSequences(); std::vector allIds; - bool seqFlag = (*memoryFrameLine).is_sequence; + bool seqFlag = input.hasSeq(); + CHECK(!input.hasSubseq()) + << "Subsequence boot layer for memory is not supported"; if (seqFlag) { // for sequenceScatterAgentLayer - CHECK(input.sequenceStartPositions) - << "boot layer must be a sequence when is_sequence = true"; std::vector sequenceStartPositions; sequenceStartPositions.push_back(0); // first element = 0 const int* starts = input.sequenceStartPositions->getData(false); @@ -804,8 +954,7 @@ size_t RecurrentGradientMachine::getGenBatchSize() { for (auto& memoryFrameLine : memoryFrameLines_) { if (!memoryFrameLine.rootLayer) continue; Argument& bootArg = memoryFrameLine.rootLayer->getOutput(); - size_t batchSize = memoryFrameLine.is_sequence ? bootArg.getNumSequences() - : bootArg.getBatchSize(); + size_t batchSize = bootArg.getNumSequences(); if (numSequences) { CHECK_EQ(numSequences, batchSize); } else { @@ -845,12 +994,7 @@ void RecurrentGradientMachine::generateSequence() { if (memoryFrameLine.rootAgent) { auto scatterAgent = dynamic_cast(memoryFrameLine.rootAgent.get()); - bool seqFlag = memoryFrameLine.is_sequence; - scatterAgent->setRealLayer(memoryFrameLine.rootLayer, ids, seqFlag); - if (seqFlag) { - CHECK(memoryFrameLine.rootLayer->getOutput().sequenceStartPositions) - << "boot layer must be a sequence when is_sequence = true"; - } + scatterAgent->setRealLayer(memoryFrameLine.rootLayer, ids); } NeuralNetwork::connect( memoryFrameLine.agents[0], memoryFrameLine.bootLayer, ids.size()); @@ -858,6 +1002,7 @@ void RecurrentGradientMachine::generateSequence() { // boot layer forward AsyncGpuBlock asyncGpuBlock; + for (auto& memoryFrameLine : memoryFrameLines_) { memoryFrameLine.bootLayer->forward(PASS_TEST); } @@ -930,8 +1075,7 @@ void RecurrentGradientMachine::oneWaySearch(size_t batchSize) { auto scatterAgent = dynamic_cast( memoryFrameLine.scatterAgents[machineCur].get()); scatterAgent->setRealLayer(memoryFrameLine.frames[machinePrev], - scatterIds, - memoryFrameLine.is_sequence); + scatterIds); scatterAgent->forward(PASS_TEST); NeuralNetwork::connect(memoryFrameLine.agents[machineCur], memoryFrameLine.scatterAgents[machineCur]); @@ -1003,8 +1147,7 @@ void RecurrentGradientMachine::connectPrevFrame(int stepId, auto scatterAgent = dynamic_cast( memoryFrameLine.scatterAgents[machineCur].get()); scatterAgent->setRealLayer(memoryFrameLine.frames[machinePrev], - isOutIds ? topIds_ : machineIds_, - memoryFrameLine.is_sequence); + isOutIds ? topIds_ : machineIds_); scatterAgent->forward(PASS_TEST); NeuralNetwork::connect(memoryFrameLine.agents[machineCur], memoryFrameLine.scatterAgents[machineCur]); diff --git a/paddle/gserver/gradientmachines/RecurrentGradientMachine.h b/paddle/gserver/gradientmachines/RecurrentGradientMachine.h index c2bc52709ab..8d94d7e2df2 100644 --- a/paddle/gserver/gradientmachines/RecurrentGradientMachine.h +++ b/paddle/gserver/gradientmachines/RecurrentGradientMachine.h @@ -284,6 +284,16 @@ public: } protected: + std::vector commonSeqInfo_; + ICpuGpuVectorPtr sequenceStartPositions_; + void calcSequenceStartPositions(); + void checkInputConsistency(int inlinkId, + const std::vector& seqInfo); + void reorganizeInput(PassType passType); + void reorganizeOutput(PassType passType); + void connectFrames(PassType passType); + void calcNumSequencesAtEachStep(); + void resizeOrCreateFrames(int numFrames); void resizeBootFrame(int numSequences); @@ -295,8 +305,7 @@ protected: std::string linkName; LayerPtr inLayer; std::vector agents; // Scatter Agents to reform batch input - bool hasSubseq; - Argument outArg; // scatter output argument + Argument outArg; // scatter output argument }; std::vector inFrameLines_; @@ -318,7 +327,6 @@ protected: std::vector agents; std::vector scatterAgents; // scatter agent used by beam search Argument outArg; // scatter output argument - bool is_sequence; // Different memoryFrameLine have different element as follows IVectorPtr allIds; // scattered id of realLayer ICpuGpuVectorPtr @@ -330,22 +338,27 @@ protected: // and all outFrameLines(outlinks) share the info with one inFrameLine, // which is assigned by targetInfoInlinkId_. struct Info { - IVectorPtr allIds; // scattered id of realLayer - std::vector idIndex; // index of allIds + // The original positions in the original batch + IVectorPtr allIds; // scattered id of realLayer [batchSize] + + // index of allIds for each step [maxSequenceLength_] + // idIndex[i] is the total length of the first i sequences + std::vector idIndex; + ICpuGpuVectorPtr sequenceStartPositions; // scattered sequenceStartPositions std::vector seqStartPosIndex; // index of sequenceStartPositions }; - std::vector info_; + std::vector info_; // for input // numSeqs_[i] is the number sequences which is longer than i (for sequence // data) or has more than i subsequences (for subsequence data) + // Equivalently, numSeqs_[i] is the number of sequences at step i; std::vector numSeqs_; std::vector> seqInfos_; - // the id of inlink which share info with outlinks - int targetInfoInlinkId_; + void checkOutputConsistency(OutFrameLine& outFrameLine); /* create scattered id infomation for all realLayer of inFrameLines one time. * If hasSubseq, will also create scattered sequenceStartPositions infomation @@ -354,6 +367,28 @@ protected: void createInFrameInfo(int inlinks_id, const Argument& input, PassType passType); + void createInFrameInfo_nonseq(int inlinks_id, + const Argument& input, + PassType passType); + void createInFrameInfo_seq(int inlinks_id, + const Argument& input, + PassType passType); + void createInFrameInfo_subseq(int inlinks_id, + const Argument& input, + PassType passType); + + void createOutFrameInfo(OutFrameLine& outFrameLine, + Info& info, + ICpuGpuVectorPtr& sequenceStartPositions, + ICpuGpuVectorPtr& subSequenceStartPositions); + void createOutFrameInfo_seq(OutFrameLine& outFrameLine, + Info& info, + ICpuGpuVectorPtr& sequenceStartPositions, + ICpuGpuVectorPtr& subSequenceStartPositions); + void createOutFrameInfo_subseq(OutFrameLine& outFrameLine, + Info& info, + ICpuGpuVectorPtr& sequenceStartPositions, + ICpuGpuVectorPtr& subSequenceStartPositions); void createMemoryFrameInfo(MemoryFrameLine* memoryFrameLine, PassType passType); @@ -386,9 +421,7 @@ protected: NeuralNetwork* rootNetwork_; bool reversed_; - // if hasSubseq: max number of sentences(subseq)in batchsize samples - // else: max number of tokens in batchsize samples(sentences) - int maxSequenceLength_; + int maxSequenceLength_; // Max top-level length bool useGpu_; bool stopBeamSearch_; diff --git a/paddle/gserver/layers/AgentLayer.cpp b/paddle/gserver/layers/AgentLayer.cpp index 7b1b99b135e..31463823b3f 100644 --- a/paddle/gserver/layers/AgentLayer.cpp +++ b/paddle/gserver/layers/AgentLayer.cpp @@ -36,14 +36,23 @@ void AgentLayer::forward(PassType passType) { Layer::forward(passType); Argument& realOutput = realLayer_->getOutput(); - int realHeight = realOutput.getBatchSize(); - CHECK_LE(numSamples_, realHeight); + int realNumSequences = realOutput.getNumSequences(); + CHECK_LE(numSamples_, realNumSequences); // get Arguments from real layers - if (numSamples_ > 0 && numSamples_ < realHeight) { - if (realOutput.ids) { - output_.ids = - IVector::create(realOutput.ids->getData(), numSamples_, useGpu_); + if (numSamples_ > 0 && numSamples_ < realNumSequences) { + if (realOutput.hasSeq()) { + int numRows = + realOutput.sequenceStartPositions->getData(false)[numSamples_]; + output_.subArgFrom(realOutput, + /* offset */ 0, + numRows, + getSize(), + useGpu_, + /* trans */ false, + /* seqFlag */ true, + /* seqStart */ 0, + /* seqSize */ numSamples_ + 1); } else { output_.subArgFrom( realOutput, /* offset */ 0, numSamples_, getSize(), useGpu_); @@ -53,34 +62,6 @@ void AgentLayer::forward(PassType passType) { } } -void SequenceAgentLayer::forward(PassType passType) { - Layer::forward(passType); - - Argument& realOutput = realLayer_->getOutput(); - int realNumSequences = realOutput.getNumSequences(); - CHECK_LE(numSamples_, realNumSequences); - - // get Arguments from real layers - if (numSamples_ > 0 && numSamples_ < realNumSequences) { - int numRows = - realOutput.sequenceStartPositions->getData(false)[numSamples_]; - CHECK(!realOutput.ids) << "Not supported"; - output_.subArgFrom(realOutput, - /* offset */ 0, - numRows, - getSize(), - useGpu_, - /* trans */ false, - /* seqFlag */ true, - /* seqStart */ 0, - /* seqSize */ numSamples_ + 1); - } else { - output_ = realOutput; - } -} - -REGISTER_LAYER(sequence_agent, SequenceAgentLayer); - bool GatherAgentLayer::init(const LayerMap& layerMap, const ParameterMap& parameterMap) { CHECK_EQ(config_.inputs_size(), 0); @@ -91,18 +72,26 @@ bool GatherAgentLayer::init(const LayerMap& layerMap, return true; } -void GatherAgentLayer::copyIdAndSequenceInfo(const Argument& input, - const IVectorPtr& ids, - const std::vector& idIndex) { - output_.sequenceStartPositions = input.sequenceStartPositions; - output_.subSequenceStartPositions = input.subSequenceStartPositions; - realLayers_.clear(); +void GatherAgentLayer::copyIdAndSequenceInfo( + ICpuGpuVectorPtr sequenceStartPositions, + ICpuGpuVectorPtr subSequenceStartPositions, + const IVectorPtr& ids, + const std::vector& idIndex) { + output_.sequenceStartPositions = sequenceStartPositions; + output_.subSequenceStartPositions = subSequenceStartPositions; allIds_ = ids; idIndex_ = idIndex; } void GatherAgentLayer::forward(PassType passType) { Layer::forward(passType); + forwardIds(passType); + forwardValue(passType); +} + +void GatherAgentLayer::forwardValue(PassType passType) { + MatrixPtr valueReal = realLayers_[0]->getOutputValue(); + if (!valueReal) return; int height = allIds_->getSize(); int width = this->getSize(); @@ -147,7 +136,9 @@ void ScatterAgentLayer::forward(PassType passType) { CHECK_EQ(realLayer_->getDeviceId(), this->getDeviceId()); int width = this->getSize(); - if (realOutArg_.value || realOutArg_.ids) { + if (realOutArg_.hasSeq()) { + forwardSequence(passType); + } else if (realOutArg_.value || realOutArg_.ids) { output_.subArgFrom( realOutArg_, /* offset */ idIndex_, idSize_, width, useGpu_); } else { // used in generation @@ -174,7 +165,7 @@ void ScatterAgentLayer::backward(const UpdateCallback& callback) { if (realGrad) { // for agent in inFrameLines and memoryFrameLines, // only first scatterAgentLayer should do addToRows in backward - if (idIndex_ == 0) { + if (handleBackward_) { outputGrad->addToRows(*realGrad, *ids_); } } @@ -183,12 +174,14 @@ void ScatterAgentLayer::backward(const UpdateCallback& callback) { REGISTER_LAYER(gather_agent, GatherAgentLayer); REGISTER_LAYER(scatter_agent, ScatterAgentLayer); -void SequenceGatherAgentLayer::forward(PassType passType) { - Layer::forward(passType); +void GatherAgentLayer::forwardIds(PassType passType) { int height = 0; - int* starts = output_.subSequenceStartPositions->getMutableData(false); IVectorPtr idReal = realLayers_[0]->getOutputLabel(); - if (idReal) { + + if (!idReal) return; + + if (output_.subSequenceStartPositions) { + int* starts = output_.subSequenceStartPositions->getMutableData(false); // Gather generator.idsVec // if is beam search generation result. Get first result. if (idReal->getData()[idReal->getSize() - 1] == -1) { @@ -212,13 +205,11 @@ void SequenceGatherAgentLayer::forward(PassType passType) { ->copyFrom(*realLayers_[i]->getOutputLabel()); } } else { - // Gather output.value, same as GatherAgentLayer - CHECK(output_.subSequenceStartPositions); - GatherAgentLayer::forward(passType); + LOG(FATAL) << "Not implemented"; } } -void SequenceScatterAgentLayer::forward(PassType passType) { +void ScatterAgentLayer::forwardSequence(PassType passType) { Layer::forward(passType); CHECK_EQ(realLayer_->getDeviceId(), this->getDeviceId()); @@ -241,6 +232,7 @@ void SequenceScatterAgentLayer::forward(PassType passType) { /* seqStart */ seqStartPosIndex_, /* seqSize */ numSequences_); } else { + // Putting the generation logic here is really an ugly hack! // used in generation int height = 0; size_t numSequences = ids_->getSize(); @@ -284,7 +276,4 @@ void SequenceScatterAgentLayer::forward(PassType passType) { } } -REGISTER_LAYER(sequence_gather_agent, SequenceGatherAgentLayer); -REGISTER_LAYER(sequence_scatter_agent, SequenceScatterAgentLayer); - } // namespace paddle diff --git a/paddle/gserver/layers/AgentLayer.h b/paddle/gserver/layers/AgentLayer.h index b6dac7ae6fe..461b84b17e5 100644 --- a/paddle/gserver/layers/AgentLayer.h +++ b/paddle/gserver/layers/AgentLayer.h @@ -49,18 +49,6 @@ public: void backward(const UpdateCallback& callback = nullptr) override {} }; -/** - * like AgentLayer, but use first *numSamples* sequences - */ -class SequenceAgentLayer : public AgentLayer { -public: - explicit SequenceAgentLayer(const LayerConfig& config) : AgentLayer(config) {} - ~SequenceAgentLayer() {} - - void forward(PassType passType) override; - void backward(const UpdateCallback& callback = nullptr) override {} -}; - /** * Like AgentLayer, but it can gather many real layers. Each real * layer give a few rows of a sequence, after gather all real layers, @@ -83,7 +71,10 @@ public: const ParameterMap& parameterMap) override; // call before addRealLayer - void copyIdAndSequenceInfo(const Argument& input, + void clearRealLayers() { realLayers_.clear(); } + + void copyIdAndSequenceInfo(ICpuGpuVectorPtr sequenceStartPositions, + ICpuGpuVectorPtr subSequenceStartPositions, const IVectorPtr& allIds, const std::vector& idIndex); @@ -92,24 +83,8 @@ public: void forward(PassType passType) override; void backward(const UpdateCallback& callback) override; -}; - -/** - * Like GatherAgentLayer, but select a few sequence in real layer. - * *ids* in addRealLayer() are the ids of selected sequence. - * It's used to reorder sequence output. - */ -class SequenceGatherAgentLayer : public GatherAgentLayer { -public: - explicit SequenceGatherAgentLayer(const LayerConfig& config) - : GatherAgentLayer(config) {} - virtual ~SequenceGatherAgentLayer() {} - - void forward(PassType passType); - void backward(const UpdateCallback& callback) { - // same as GatherAgentLayer - GatherAgentLayer::backward(callback); - } + void forwardValue(PassType passType); + void forwardIds(PassType passType); }; /** @@ -129,6 +104,11 @@ protected: int idSize_; int seqStartPosIndex_; int numSequences_; // number of sequences in this scatterAgentLayer + bool handleBackward_; + + // use to store expanded cpuStartPositions or subSequenceStartPositions + // of real layer. + ICpuGpuVectorPtr inputStartPos_; public: explicit ScatterAgentLayer(const LayerConfig& config) : Layer(config) {} @@ -147,19 +127,15 @@ public: * false(default) in ScatterAgentLayer, and * true in SequenceScatterAgentLayer. */ - void setRealLayer(LayerPtr layer, - const std::vector& ids, - bool copyId = false) { + void setRealLayer(LayerPtr layer, const std::vector& ids) { realLayer_ = layer; IVector::resizeOrCreate(ids_, ids.size(), useGpu_); ids_->copyFrom(ids.data(), ids.size()); - if (copyId) { - if (useGpu_) { - IVector::resizeOrCreate(cpuIds_, ids.size(), false); - cpuIds_->copyFrom(ids.data(), ids.size()); - } else { - cpuIds_ = ids_; - } + if (useGpu_) { + IVector::resizeOrCreate(cpuIds_, ids.size(), false); + cpuIds_->copyFrom(ids.data(), ids.size()); + } else { + cpuIds_ = ids_; } } @@ -169,12 +145,14 @@ public: const Argument& outArg, const IVectorPtr& ids, int idIndex, - int idSize) { + int idSize, + bool handleBackward) { realLayer_ = layer; realOutArg_ = outArg; ids_ = ids; idIndex_ = idIndex; idSize_ = idSize; + handleBackward_ = handleBackward; } void setSequenceStartPositions(const ICpuGpuVectorPtr& sequenceStartPositions, @@ -187,28 +165,8 @@ public: void forward(PassType passType) override; void backward(const UpdateCallback& callback) override; -}; -/** - * Like ScatterAgentLayer, but select a few sequence in real layer. - * *ids* in setRealLayer() or setRealLayerAndOutput() are the ids of - * selected sequence. It's used to reorder sequence input. - */ -class SequenceScatterAgentLayer : public ScatterAgentLayer { -protected: - // use to store expanded cpuStartPositions or subSequenceStartPositions - // of real layer. - ICpuGpuVectorPtr inputStartPos_; - -public: - explicit SequenceScatterAgentLayer(const LayerConfig& config) - : ScatterAgentLayer(config) {} - virtual ~SequenceScatterAgentLayer() {} - - void forward(PassType passType); - void backward(const UpdateCallback& callback) { - ScatterAgentLayer::backward(callback); - } + void forwardSequence(PassType passType); }; } // namespace paddle diff --git a/paddle/gserver/layers/SequencePoolLayer.cpp b/paddle/gserver/layers/SequencePoolLayer.cpp index 235d9a9b0f0..4179a9e7e0c 100644 --- a/paddle/gserver/layers/SequencePoolLayer.cpp +++ b/paddle/gserver/layers/SequencePoolLayer.cpp @@ -46,6 +46,9 @@ void SequencePoolLayer::forward(PassType passType) { Layer::forward(passType); const Argument& input = getInput(0); + CHECK(input.hasSeq() || input.hasSubseq()) + << "Input should be a sequence or subsequence for layer " << getName(); + newBatchSize_ = type_ ? input.getNumSubSequences() : input.getNumSequences(); size_t dim = getSize(); // check diff --git a/paddle/gserver/tests/rnn_data_provider.py b/paddle/gserver/tests/rnn_data_provider.py index 3afd45c72f4..913365a5a40 100644 --- a/paddle/gserver/tests/rnn_data_provider.py +++ b/paddle/gserver/tests/rnn_data_provider.py @@ -95,3 +95,22 @@ def process_unequalength_seq(settings, file_name): words1 = reduce(lambda x, y: x + y, d[0]) words2 = reduce(lambda x, y: x + y, d[1]) yield words1, words2, d[2] + + +########################################################### +data3 = [ + [[[1, 2], [4, 5, 2]], [1, 2], 0], + [[[0, 2], [2, 5], [0, 1, 2]], [2, 3, 0], 1], +] + + +# Used for sequence_nest_mixed_inputs.conf +@provider( + input_types=[ + integer_value_sub_sequence(10), integer_value_sequence(10), + integer_value(2) + ], + should_shuffle=False) +def process_mixed(settings, file_name): + for d in data3: + yield d diff --git a/paddle/gserver/tests/sequence_nest_rnn_multi_input.conf b/paddle/gserver/tests/sequence_nest_rnn_multi_input.conf index ad14a2c927c..afdacfffd7a 100644 --- a/paddle/gserver/tests/sequence_nest_rnn_multi_input.conf +++ b/paddle/gserver/tests/sequence_nest_rnn_multi_input.conf @@ -19,7 +19,7 @@ from paddle.trainer_config_helpers import * define_py_data_sources2(train_list='gserver/tests/Sequence/dummy.list', test_list=None, module='rnn_data_provider', - obj='process_subseq2') + obj='process_subseq') settings(batch_size=2, learning_rate=0.01) @@ -57,7 +57,7 @@ def outer_step(wid, x): last = last_seq(input=inner_rnn_output, name="outer_rnn_state") # "return last" should also work. But currently RecurrentGradientMachine - # does not handle it, and will report error: In hierachical RNN, all out + # does not handle it, and will report error: In hierachical RNN, all out # links should be from sequences now. return inner_rnn_output diff --git a/paddle/gserver/tests/sequence_rnn_matched_inputs.py b/paddle/gserver/tests/sequence_rnn_matched_inputs.py new file mode 100644 index 00000000000..e2635b4400b --- /dev/null +++ b/paddle/gserver/tests/sequence_rnn_matched_inputs.py @@ -0,0 +1,85 @@ +# edit-mode: -*- python -*- +# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from paddle.trainer_config_helpers import * + +######################## data source ################################ +define_py_data_sources2( + train_list='gserver/tests/Sequence/dummy.list', + test_list=None, + module='rnn_data_provider', + obj='process_mixed') + +settings(batch_size=2, learning_rate=0.01) +######################## network configure ################################ +dict_dim = 10 +word_dim = 2 +hidden_dim = 2 +label_dim = 2 + +data1 = data_layer(name="word1", size=dict_dim) +data2 = data_layer(name="word2", size=dict_dim) +label = data_layer(name="label", size=label_dim) + +encoding = embedding_layer(input=data2, size=word_dim) + +subseq = embedding_layer(input=data1, size=word_dim) +seq = embedding_layer(input=data2, size=word_dim) +nonseq = embedding_layer(input=label, size=word_dim) + + +# This hierarchical RNN is designed to be equivalent to the simple RNN in +# sequence_rnn_multi_unequalength_inputs.conf +def outer_step(subseq, seq, nonseq, encoding): + outer_mem = memory(name="outer_rnn_state", size=hidden_dim) + + def inner_step(subseq, seq, nonseq): + inner_mem = memory( + name="inner_rnn_state", size=hidden_dim, boot_layer=outer_mem) + + out = fc_layer( + input=[subseq, seq, nonseq, inner_mem], + size=hidden_dim, + act=TanhActivation(), + bias_attr=True, + name='inner_rnn_state') + return out + + decoder = recurrent_group( + step=inner_step, name='inner', input=[subseq, seq, nonseq]) + last = last_seq(name="outer_rnn_state", input=decoder) + context = simple_attention( + encoded_sequence=encoding, encoded_proj=encoding, decoder_state=last) + return context + + +out = recurrent_group( + name="outer", + step=outer_step, + input=[ + subseq, expand_layer( + seq, expand_as=subseq, + expand_level=ExpandLevel.FROM_SEQUENCE), expand_layer( + nonseq, + expand_as=subseq, + expand_level=ExpandLevel.FROM_NO_SEQUENCE), + StaticInput(encoding) + ]) + +rep = last_seq(input=out) +prob = fc_layer( + size=label_dim, input=rep, act=SoftmaxActivation(), bias_attr=True) + +outputs(classification_cost(input=prob, label=label)) diff --git a/paddle/gserver/tests/sequence_rnn_mixed_inputs.py b/paddle/gserver/tests/sequence_rnn_mixed_inputs.py new file mode 100644 index 00000000000..a6f2d419f25 --- /dev/null +++ b/paddle/gserver/tests/sequence_rnn_mixed_inputs.py @@ -0,0 +1,78 @@ +# edit-mode: -*- python -*- +# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from paddle.trainer_config_helpers import * + +######################## data source ################################ +define_py_data_sources2( + train_list='gserver/tests/Sequence/dummy.list', + test_list=None, + module='rnn_data_provider', + obj='process_mixed') + +settings(batch_size=2, learning_rate=0.01) +######################## network configure ################################ +dict_dim = 10 +word_dim = 2 +hidden_dim = 2 +label_dim = 2 + +data1 = data_layer(name="word1", size=dict_dim) +data2 = data_layer(name="word2", size=dict_dim) +label = data_layer(name="label", size=label_dim) + +encoding = embedding_layer(input=data2, size=word_dim) + + +# This hierarchical RNN is designed to be equivalent to the simple RNN in +# sequence_rnn_multi_unequalength_inputs.conf +def outer_step(subseq, seq, nonseq, encoding): + outer_mem = memory(name="outer_rnn_state", size=hidden_dim) + + def inner_step(data1, data2, label): + inner_mem = memory( + name="inner_rnn_state", size=hidden_dim, boot_layer=outer_mem) + + subseq = embedding_layer(input=data1, size=word_dim) + seq = embedding_layer(input=data2, size=word_dim) + nonseq = embedding_layer(input=label, size=word_dim) + + print_layer(input=[data1, seq, label, inner_mem]) + out = fc_layer( + input=[subseq, seq, nonseq, inner_mem], + size=hidden_dim, + act=TanhActivation(), + bias_attr=True, + name='inner_rnn_state') + return out + + decoder = recurrent_group( + step=inner_step, name='inner', input=[subseq, seq, nonseq]) + last = last_seq(name="outer_rnn_state", input=decoder) + context = simple_attention( + encoded_sequence=encoding, encoded_proj=encoding, decoder_state=last) + return context + + +out = recurrent_group( + name="outer", + step=outer_step, + input=[data1, data2, label, StaticInput(encoding)]) + +rep = last_seq(input=out) +prob = fc_layer( + size=label_dim, input=rep, act=SoftmaxActivation(), bias_attr=True) + +outputs(classification_cost(input=prob, label=label)) diff --git a/paddle/gserver/tests/sequence_rnn_multi_input.conf b/paddle/gserver/tests/sequence_rnn_multi_input.conf index 40d03174157..9fae974f307 100644 --- a/paddle/gserver/tests/sequence_rnn_multi_input.conf +++ b/paddle/gserver/tests/sequence_rnn_multi_input.conf @@ -19,7 +19,7 @@ from paddle.trainer_config_helpers import * define_py_data_sources2(train_list='gserver/tests/Sequence/dummy.list', test_list=None, module='rnn_data_provider', - obj='process_seq2') + obj='process_seq') settings(batch_size=2, learning_rate=0.01) diff --git a/paddle/gserver/tests/test_RecurrentGradientMachine.cpp b/paddle/gserver/tests/test_RecurrentGradientMachine.cpp index 4a846397e6c..6b19eb0ce52 100644 --- a/paddle/gserver/tests/test_RecurrentGradientMachine.cpp +++ b/paddle/gserver/tests/test_RecurrentGradientMachine.cpp @@ -155,6 +155,15 @@ TEST(RecurrentGradientMachine, rnn_multi_unequalength_input) { } } +TEST(RecurrentGradientMachine, rnn_mixed_input) { + for (bool useGpu : {false, true}) { + test("gserver/tests/sequence_rnn_mixed_inputs.py", + "gserver/tests/sequence_rnn_matched_inputs.py", + 1e-6, + useGpu); + } +} + int main(int argc, char** argv) { testing::InitGoogleTest(&argc, argv); diff --git a/paddle/math/Vector.cpp b/paddle/math/Vector.cpp index eaa1cdce305..c519ca500af 100644 --- a/paddle/math/Vector.cpp +++ b/paddle/math/Vector.cpp @@ -908,12 +908,13 @@ const T* CpuGpuVectorT::getData(bool useGpu) const { // Operation will change data and need to reset sync_ & syncFlag_. #define MUTABLE_VECTOR_OP(OP, useGpu, args...) \ do { \ - setSync(useGpu); \ if (useGpu) { \ copyToGpu(); \ + setSync(useGpu); \ return gpuVectorT_->OP(args); \ } else { \ copyToCpu(); \ + setSync(useGpu); \ return cpuVectorT_->OP(args); \ } \ } while (0) @@ -1030,7 +1031,7 @@ void CpuGpuVectorT::copyToCpu() { case DATA_AT_GPU: CHECK(gpuVectorT_); this->resizeOrCreate(gpuVectorT_->getSize(), false); - cpuVectorT_->copyFrom(*gpuVectorT_, HPPL_STREAM_DEFAULT); + cpuVectorT_->copyFrom(*gpuVectorT_); setSync(SYNCED); break; case DATA_AT_CPU: @@ -1049,7 +1050,7 @@ void CpuGpuVectorT::copyToGpu() { case DATA_AT_CPU: CHECK(cpuVectorT_); this->resizeOrCreate(cpuVectorT_->getSize(), true); - gpuVectorT_->copyFrom(*cpuVectorT_, HPPL_STREAM_DEFAULT); + gpuVectorT_->copyFrom(*cpuVectorT_); setSync(SYNCED); break; case DATA_AT_GPU: diff --git a/paddle/parameter/Argument.h b/paddle/parameter/Argument.h index 91aca98e186..09bd6336167 100644 --- a/paddle/parameter/Argument.h +++ b/paddle/parameter/Argument.h @@ -149,6 +149,7 @@ struct Argument { : getBatchSize(); } + bool hasSeq() const { return sequenceStartPositions != nullptr; } bool hasSubseq() const { return subSequenceStartPositions != nullptr; } const int* getCpuStartPositions() const { diff --git a/paddle/trainer/tests/test_recurrent_machine_generation.cpp b/paddle/trainer/tests/test_recurrent_machine_generation.cpp index 03446b3b2f6..1322e77178a 100644 --- a/paddle/trainer/tests/test_recurrent_machine_generation.cpp +++ b/paddle/trainer/tests/test_recurrent_machine_generation.cpp @@ -124,6 +124,8 @@ TEST(RecurrentGradientMachine, test_generation) { bool beam_search) { FLAGS_config_args = beam_search ? "beam_search=1" : "beam_search=0"; for (auto useGpu : useGpuConfs) { + LOG(INFO) << configFile << " useGpu=" << useGpu + << " beam_search=" << beam_search; testGeneration(configFile, useGpu, hasSubseq, expRetFile); } }; diff --git a/python/paddle/trainer/config_parser.py b/python/paddle/trainer/config_parser.py index fc2e3bbcde0..8edb61d1840 100644 --- a/python/paddle/trainer/config_parser.py +++ b/python/paddle/trainer/config_parser.py @@ -333,48 +333,32 @@ def RecurrentLayerGroupWithoutOutLinksBegin(name, for linkid, link in enumerate(in_links): if isinstance(link, basestring): name = link - has_subseq = False else: name = link.link_name - has_subseq = link.has_subseq # assign target_inlinkid according to target_inlinkname if target_inlinkname == name: g_current_submodel.target_inlinkid = linkid - if in_links_count == 0: - in_links_has_subseq = has_subseq - else: - config_assert( - in_links_has_subseq == has_subseq, - "The sequence type of in_links should be the same in RecurrentLayerGroup" - ) in_links_count += 1 layer_name = MakeLayerNameInParentSubmodel(name) layer = g_layer_map[layer_name] - if has_subseq: - SequenceScatterAgentLayer(name=name, size=layer.size) - else: - ScatterAgentLayer(name=name, size=layer.size) + ScatterAgentLayer(name=name, size=layer.size) pair = g_current_submodel.in_links.add() pair.layer_name = layer_name pair.link_name = MakeLayerNameInSubmodel(name) - pair.has_subseq = has_subseq @config_func def RecurrentLayerGroupSetOutLink(link): if isinstance(link, basestring): name = link - has_subseq = False else: name = link.link_name - has_subseq = link.has_subseq layer_name = MakeLayerNameInParentSubmodel(name) pair = g_current_submodel.out_links.add() pair.layer_name = MakeLayerNameInSubmodel(name) pair.link_name = layer_name - pair.has_subseq = has_subseq def RecurrentLayerGroupSetGenerator(generator=None): @@ -425,8 +409,6 @@ def RecurrentLayerGroupEnd(name): agent_name = GetLayerBaseName(pair.link_name) if prev_submodel.HasField("generator"): DataLayer(name=agent_name, size=layer.size) - elif pair.has_subseq: - SequenceGatherAgentLayer(name=agent_name, size=layer.size) else: GatherAgentLayer(name=agent_name, size=layer.size) @@ -2253,13 +2235,6 @@ class AgentLayer(LayerBase): name, 'agent', size, inputs=[], device=device) -@config_layer('sequence_agent') -class SequenceAgentLayer(LayerBase): - def __init__(self, name, size, device=None): - super(SequenceAgentLayer, self).__init__( - name, 'sequence_agent', size, inputs=[], device=device) - - @config_layer('gather_agent') class GatherAgentLayer(LayerBase): def __init__(self, name, size, device=None): @@ -2274,20 +2249,6 @@ class ScatterAgentLayer(LayerBase): name, 'scatter_agent', size, inputs=[], device=device) -@config_layer('sequence_gather_agent') -class SequenceGatherAgentLayer(LayerBase): - def __init__(self, name, size, device=None): - super(SequenceGatherAgentLayer, self).__init__( - name, 'sequence_gather_agent', size, inputs=[], device=device) - - -@config_layer('sequence_scatter_agent') -class SequenceScatterAgentLayer(LayerBase): - def __init__(self, name, size, device=None): - super(SequenceScatterAgentLayer, self).__init__( - name, 'sequence_scatter_agent', size, inputs=[], device=device) - - @config_layer('multiplex') class MultiplexLayer(LayerBase): def __init__(self, name, inputs, size, device=None): @@ -2303,12 +2264,12 @@ class MultiplexLayer(LayerBase): @config_func -def Link( - name, - has_subseq=False, ): +def Link(name, has_subseq=False): + """ + Still keeping has_subseq for backward compatibility + """ link_config = LinkConfig() link_config.link_name = name - link_config.has_subseq = has_subseq return link_config @@ -2341,13 +2302,7 @@ def Memory(name, config_assert(name is not None, "name needs cannot be None") memory_name = name + "+delay1" agent_name = memory_name - if is_sequence: - config_assert( - boot_layer is not None, - "there must be boot_layer in network when is_sequence = True") - agent_layer = SequenceAgentLayer(agent_name, size) - else: - agent_layer = AgentLayer(agent_name, size) + agent_layer = AgentLayer(agent_name, size) config_assert(g_current_submodel.is_recurrent_layer_group, 'Memory should be used in recurrent layer group only') memory = g_current_submodel.memories.add() diff --git a/python/paddle/trainer_config_helpers/layers.py b/python/paddle/trainer_config_helpers/layers.py index 2d8ddbb9007..d4fd191b179 100755 --- a/python/paddle/trainer_config_helpers/layers.py +++ b/python/paddle/trainer_config_helpers/layers.py @@ -3329,8 +3329,9 @@ class StaticInput(object): input.size = size -class SubsequenceInput(object): +def SubsequenceInput(input): """ + DEPRECATED. Input sequence has sub-sequence, used in recurrent_group. The example usage is: @@ -3339,11 +3340,7 @@ class SubsequenceInput(object): input = SubsequenceInput(layer) """ - - def __init__(self, input): - assert isinstance(input, LayerOutput) - assert input.size is not None - self.input = input + return input @wrap_name_default("recurrent_group") @@ -3407,7 +3404,8 @@ def recurrent_group(step, input sequence in a reverse order. :type reverse: bool - :param targetInlink: the input layer which share info with layer group's output + :param targetInlink: DEPRECATED. + The input layer which share info with layer group's output Param input specifies multiple input layers. For SubsequenceInput inputs, config should assign one input @@ -3429,46 +3427,21 @@ def recurrent_group(step, model_type('recurrent_nn') def is_single_input(x): - return isinstance(x, LayerOutput) or isinstance(x, StaticInput) \ - or isinstance(x, SubsequenceInput) + return isinstance(x, LayerOutput) or isinstance(x, StaticInput) if is_single_input(input): input = [input] assert isinstance(input, collections.Sequence) def is_in_links(x): - return isinstance(x, LayerOutput) or isinstance(x, SubsequenceInput) + return isinstance(x, LayerOutput) in_links = filter(is_in_links, input) - def targetInlink_in_inlinks(): - for inlink in in_links: - if isinstance(inlink, SubsequenceInput): - if targetInlink == inlink.input: - return True - elif targetInlink == inlink: - return True - return False - - assert (targetInlink == None or targetInlink_in_inlinks()) - targetInlinkName = None if targetInlink == None \ - else targetInlink.name if isinstance(targetInlink, LayerOutput) \ - else targetInlink.input.name - - contains_sub_seq = [False] - - def map_in_links(x): - if isinstance(x, SubsequenceInput): - contains_sub_seq[0] = True - return Link(name=x.input.name, has_subseq=True) - else: - return x.name - RecurrentLayerGroupWithoutOutLinksBegin( name=name, - in_links=map(map_in_links, in_links), - seq_reversed=reverse, - target_inlinkname=targetInlinkName) + in_links=map(lambda x: x.name, in_links), + seq_reversed=reverse) in_args = [] has_LayerOutput = False for each_input in input: @@ -3476,10 +3449,7 @@ def recurrent_group(step, if isinstance(each_input, LayerOutput): in_args.append(each_input) has_LayerOutput = True - elif isinstance(each_input, SubsequenceInput): - in_args.append(each_input.input) - has_LayerOutput = True - else: + else: # StaticInput mem_name = "__%s_memory__" % each_input.input.name mem = memory( name=mem_name, @@ -3503,10 +3473,7 @@ def recurrent_group(step, for ot in layer_outs: assert isinstance(ot, LayerOutput) ot.reverse = reverse - if contains_sub_seq[0]: - RecurrentLayerGroupSetOutLink(Link(ot.name, has_subseq=True)) - else: - RecurrentLayerGroupSetOutLink(ot.name) + RecurrentLayerGroupSetOutLink(ot.name) RecurrentLayerGroupEnd(name=name) @@ -5608,13 +5575,13 @@ def row_conv_layer(input, to deploy in an online and low-latency setting. The lookahead convolution incorporates information from future subsequences in a computationally efficient manner to improve unidirectional recurrent neural networks. - + The connection of row convolution is different form the 1D sequence convolution. Assumed that, the future context-length is k, that is to say, it can get the output at timestep t by using the the input feature from t-th timestep to (t+k+1)-th timestep. Assumed that the hidden dim of input activations are d, the activations r_t for the new layer at time-step t are: - + .. math:: r_{t,r} = \sum_{j=1}^{k + 1} {w_{i,j}h_{t+j-1, i}} diff --git a/python/paddle/trainer_config_helpers/tests/configs/protostr/shared_gru.protostr b/python/paddle/trainer_config_helpers/tests/configs/protostr/shared_gru.protostr index 64530146a14..49bc5a77791 100644 --- a/python/paddle/trainer_config_helpers/tests/configs/protostr/shared_gru.protostr +++ b/python/paddle/trainer_config_helpers/tests/configs/protostr/shared_gru.protostr @@ -261,12 +261,10 @@ sub_models { in_links { layer_name: "__simple_gru_0___transform" link_name: "__simple_gru_0___transform@__simple_gru_0___recurrent_group" - has_subseq: false } out_links { layer_name: "__simple_gru_0__@__simple_gru_0___recurrent_group" link_name: "__simple_gru_0__" - has_subseq: false } target_inlinkid: -1 } @@ -285,12 +283,10 @@ sub_models { in_links { layer_name: "__simple_gru_1___transform" link_name: "__simple_gru_1___transform@__simple_gru_1___recurrent_group" - has_subseq: false } out_links { layer_name: "__simple_gru_1__@__simple_gru_1___recurrent_group" link_name: "__simple_gru_1__" - has_subseq: false } target_inlinkid: -1 } diff --git a/python/paddle/trainer_config_helpers/tests/configs/protostr/shared_lstm.protostr b/python/paddle/trainer_config_helpers/tests/configs/protostr/shared_lstm.protostr index 79fa4c74f08..f156c17fc99 100644 --- a/python/paddle/trainer_config_helpers/tests/configs/protostr/shared_lstm.protostr +++ b/python/paddle/trainer_config_helpers/tests/configs/protostr/shared_lstm.protostr @@ -351,12 +351,10 @@ sub_models { in_links { layer_name: "__mixed_0__" link_name: "__mixed_0__@__lstm_group_0___recurrent_group" - has_subseq: false } out_links { layer_name: "__lstm_group_0__@__lstm_group_0___recurrent_group" link_name: "__lstm_group_0__" - has_subseq: false } target_inlinkid: -1 } @@ -383,12 +381,10 @@ sub_models { in_links { layer_name: "__mixed_1__" link_name: "__mixed_1__@__lstm_group_1___recurrent_group" - has_subseq: false } out_links { layer_name: "__lstm_group_1__@__lstm_group_1___recurrent_group" link_name: "__lstm_group_1__" - has_subseq: false } target_inlinkid: -1 } diff --git a/python/paddle/trainer_config_helpers/tests/configs/protostr/test_rnn_group.protostr b/python/paddle/trainer_config_helpers/tests/configs/protostr/test_rnn_group.protostr index 77b447aa9db..6ec897f7d09 100644 --- a/python/paddle/trainer_config_helpers/tests/configs/protostr/test_rnn_group.protostr +++ b/python/paddle/trainer_config_helpers/tests/configs/protostr/test_rnn_group.protostr @@ -155,7 +155,7 @@ layers { } layers { name: "sub_seq_input@__recurrent_group_2__" - type: "sequence_scatter_agent" + type: "scatter_agent" size: 100 active_type: "" } @@ -182,7 +182,7 @@ layers { } layers { name: "rnn_subseq_forward" - type: "sequence_gather_agent" + type: "gather_agent" size: 200 active_type: "" } @@ -623,12 +623,10 @@ sub_models { in_links { layer_name: "seq_input" link_name: "seq_input@__recurrent_group_0__" - has_subseq: false } out_links { layer_name: "rnn_forward@__recurrent_group_0__" link_name: "rnn_forward" - has_subseq: false } target_inlinkid: -1 } @@ -647,12 +645,10 @@ sub_models { in_links { layer_name: "seq_input" link_name: "seq_input@__recurrent_group_1__" - has_subseq: false } out_links { layer_name: "rnn_back@__recurrent_group_1__" link_name: "rnn_back" - has_subseq: false } target_inlinkid: -1 } @@ -671,12 +667,10 @@ sub_models { in_links { layer_name: "sub_seq_input" link_name: "sub_seq_input@__recurrent_group_2__" - has_subseq: true } out_links { layer_name: "rnn_subseq_forward@__recurrent_group_2__" link_name: "rnn_subseq_forward" - has_subseq: true } target_inlinkid: -1 } @@ -703,12 +697,10 @@ sub_models { in_links { layer_name: "__mixed_0__" link_name: "__mixed_0__@__lstm_group_0___recurrent_group" - has_subseq: false } out_links { layer_name: "__lstm_group_0__@__lstm_group_0___recurrent_group" link_name: "__lstm_group_0__" - has_subseq: false } target_inlinkid: -1 } @@ -727,12 +719,10 @@ sub_models { in_links { layer_name: "__mixed_1__" link_name: "__mixed_1__@__gru_group_0___recurrent_group" - has_subseq: false } out_links { layer_name: "__gru_group_0__@__gru_group_0___recurrent_group" link_name: "__gru_group_0__" - has_subseq: false } target_inlinkid: -1 } @@ -751,12 +741,10 @@ sub_models { in_links { layer_name: "seq_input" link_name: "seq_input@__recurrent_group_3__" - has_subseq: false } out_links { layer_name: "__fc_layer_0__@__recurrent_group_3__" link_name: "__fc_layer_0__" - has_subseq: false } target_inlinkid: -1 } -- GitLab From fc0ad9048a51cfb6bb8cb247e1eb6c065b123e6e Mon Sep 17 00:00:00 2001 From: xuwei06 Date: Tue, 13 Jun 2017 23:14:15 -0700 Subject: [PATCH 0662/3256] Repeat layer for column vector --- .../gserver/layers/FeatureMapExpandLayer.cpp | 78 +++++++++++++------ paddle/gserver/tests/test_LayerGrad.cpp | 15 ++-- python/paddle/trainer/config_parser.py | 10 ++- .../paddle/trainer_config_helpers/layers.py | 33 ++++++-- 4 files changed, 97 insertions(+), 39 deletions(-) diff --git a/paddle/gserver/layers/FeatureMapExpandLayer.cpp b/paddle/gserver/layers/FeatureMapExpandLayer.cpp index b3850f543af..8a2ae6b49fc 100644 --- a/paddle/gserver/layers/FeatureMapExpandLayer.cpp +++ b/paddle/gserver/layers/FeatureMapExpandLayer.cpp @@ -40,6 +40,7 @@ namespace paddle { class FeatureMapExpandLayer : public Layer { private: int numFilters_; + bool asRowVector_; public: explicit FeatureMapExpandLayer(const LayerConfig& config) : Layer(config) {} @@ -62,6 +63,7 @@ bool FeatureMapExpandLayer::init(const LayerMap& layerMap, CHECK_EQ(inputLayers_.size(), 1UL); numFilters_ = config_.num_filters(); + asRowVector_ = config_.user_arg() != "as_col_vec"; return true; } @@ -76,16 +78,30 @@ void FeatureMapExpandLayer::forward(PassType passType) { { AsyncGpuBlock asyncGpuBlock; - for (size_t i = 0; i < batchSize; i++) { - MatrixPtr outVTmp = - Matrix::create(outputV->getData() + i * imgSize * numFilters_, - numFilters_, - imgSize, - false, - useGpu_); - MatrixPtr inVTmp = Matrix::create( - inputV->getData() + i * imgSize, 1, imgSize, false, useGpu_); - outVTmp->addRowVector(*inVTmp); + if (asRowVector_) { + for (size_t i = 0; i < batchSize; i++) { + MatrixPtr outVTmp = + Matrix::create(outputV->getData() + i * imgSize * numFilters_, + numFilters_, + imgSize, + false, + useGpu_); + MatrixPtr inVTmp = Matrix::create( + inputV->getData() + i * imgSize, 1, imgSize, false, useGpu_); + outVTmp->addRowVector(*inVTmp); + } + } else { + for (size_t i = 0; i < batchSize; i++) { + MatrixPtr outVTmp = + Matrix::create(outputV->getData() + i * imgSize * numFilters_, + imgSize, + numFilters_, + false, + useGpu_); + MatrixPtr inVTmp = Matrix::create( + inputV->getData() + i * imgSize, imgSize, 1, false, useGpu_); + outVTmp->addColVector(*inVTmp); + } } } /* activation */ { @@ -102,24 +118,38 @@ void FeatureMapExpandLayer::backward(const UpdateCallback& callback) { MatrixPtr outGrad = getOutputGrad(); size_t batchSize = getInput(0).getBatchSize(); int imgSize = inGrad->getWidth(); + /* Do activation */ { + REGISTER_TIMER_INFO("BpAvtTimer", getName().c_str()); + backwardActivation(); + } { AsyncGpuBlock asyncGpuBlock; - for (size_t i = 0; i < batchSize; i++) { - MatrixPtr outGradTmp = - Matrix::create(outGrad->getData() + i * imgSize * numFilters_, - numFilters_, - imgSize, - false, - useGpu_); - MatrixPtr inGradTmp = Matrix::create( - inGrad->getData() + i * imgSize, 1, imgSize, false, useGpu_); - inGradTmp->collectBias(*outGradTmp, 1); + if (asRowVector_) { + for (size_t i = 0; i < batchSize; i++) { + MatrixPtr outGradTmp = + Matrix::create(outGrad->getData() + i * imgSize * numFilters_, + numFilters_, + imgSize, + false, + useGpu_); + MatrixPtr inGradTmp = Matrix::create( + inGrad->getData() + i * imgSize, 1, imgSize, false, useGpu_); + inGradTmp->collectBias(*outGradTmp, 1); + } + } else { + for (size_t i = 0; i < batchSize; i++) { + MatrixPtr outGradTmp = + Matrix::create(outGrad->getData() + i * imgSize * numFilters_, + imgSize, + numFilters_, + false, + useGpu_); + MatrixPtr inGradTmp = Matrix::create( + inGrad->getData() + i * imgSize, imgSize, 1, false, useGpu_); + inGradTmp->sumRows(*outGradTmp, 1, 1); + } } } - /* Do derivation */ { - REGISTER_TIMER_INFO("BpAvtTimer", getName().c_str()); - backwardActivation(); - } } } // namespace paddle. diff --git a/paddle/gserver/tests/test_LayerGrad.cpp b/paddle/gserver/tests/test_LayerGrad.cpp index 6adffcf53b7..297756025bc 100644 --- a/paddle/gserver/tests/test_LayerGrad.cpp +++ b/paddle/gserver/tests/test_LayerGrad.cpp @@ -1598,12 +1598,15 @@ TEST(Layer, FeatureMapExpandLayer) { /* paraSize= */ 0}); config.layerConfig.add_inputs(); for (auto useGpu : {false, true}) { - testLayerGrad(config, - "featmap_expand", - /*batch_size*/ 100, - /* trans= */ false, - useGpu, - /* useWeight */ true); + for (auto asRowVec : {false, true}) { + config.layerConfig.set_user_arg(asRowVec ? "as_row_vec" : "as_col_vec"); + testLayerGrad(config, + "featmap_expand", + /*batch_size*/ 100, + /* trans= */ false, + useGpu, + /* useWeight */ true); + } } } diff --git a/python/paddle/trainer/config_parser.py b/python/paddle/trainer/config_parser.py index fc2e3bbcde0..8e3c3241623 100644 --- a/python/paddle/trainer/config_parser.py +++ b/python/paddle/trainer/config_parser.py @@ -2428,7 +2428,13 @@ class ExpandLayer(LayerBase): @config_layer('featmap_expand') class FeatMapExpandLayer(LayerBase): - def __init__(self, name, inputs, device=None, num_filters=None, bias=False): + def __init__(self, + name, + inputs, + device=None, + num_filters=None, + as_row_vector=True, + bias=False): super(FeatMapExpandLayer, self).__init__( name, 'featmap_expand', 0, inputs=inputs, device=device) config_assert( @@ -2437,6 +2443,8 @@ class FeatMapExpandLayer(LayerBase): self.config.num_filters = num_filters else: logger.fatal("FeatMapExpandLayer must specify num_filters.") + if not as_row_vector: + self.config.user_arg = "as_col_vec" self.set_layer_size(self.get_input_layer(0).size * num_filters) diff --git a/python/paddle/trainer_config_helpers/layers.py b/python/paddle/trainer_config_helpers/layers.py index 2d8ddbb9007..f84b883bc2e 100755 --- a/python/paddle/trainer_config_helpers/layers.py +++ b/python/paddle/trainer_config_helpers/layers.py @@ -1566,13 +1566,21 @@ def expand_layer(input, @wrap_name_default() @layer_support() -def repeat_layer(input, num_repeats, name=None, layer_attr=None): +def repeat_layer(input, + num_repeats, + as_row_vector=True, + name=None, + layer_attr=None): """ - A layer for repeating the input for num_repeats times. This is equivalent - to apply concat_layer() with num_repeats same input. + A layer for repeating the input for num_repeats times. + If as_row_vector: .. math:: - y = [x, x, \cdots, x] + y = [x_1,\cdots, x_n, \cdots, x_1, \cdots, x_n] + If not as_row_vector: + .. math:: + y = [x_1,\cdots, x_1, \cdots, x_n, \cdots, x_n] + The example usage is: @@ -1585,6 +1593,12 @@ def repeat_layer(input, num_repeats, name=None, layer_attr=None): :param num_repeats: Repeat the input so many times :type num_repeats: int :param name: Layer name. + :param as_row_vector: True for treating input as row vector and repeating + in the column direction. This is equivalent to apply + concat_layer() with num_repeats same input. + False for treating input as column vector and repeating + in the row direction. + :type as_row_vector: bool :type name: basestring :param layer_attr: extra layer attributes. :type layer_attr: ExtraLayerAttribute. @@ -1596,6 +1610,7 @@ def repeat_layer(input, num_repeats, name=None, layer_attr=None): inputs=[input.name], name=name, num_filters=num_repeats, + as_row_vector=as_row_vector, type=LayerType.FEATURE_MAP_EXPAND_LAYER, **ExtraAttr.to_kwargs(layer_attr)) return LayerOutput( @@ -2846,17 +2861,19 @@ def seq_concat_layer(a, b, act=None, name=None, layer_attr=None, Concat sequence a with sequence b. Inputs: - - a = [a1, a2, ..., an] + - a = [a1, a2, ..., am] - b = [b1, b2, ..., bn] - - Note that the length of a and b should be the same. - Output: [a1, b1, a2, b2, ..., an, bn] + Output: [a1, ..., am, b1, ..., bn] + + Note that the above computation is for one sample. Multiple samples are + processed in one batch. The example usage is: .. code-block:: python - concat = seq_concat_layer(a=layer1, b=layer2) + concat = seq_concat_layer(al=layer1, b=layer2) :param name: Layer name. :type name: basestring -- GitLab From 094106adfaf000a18c820fee8fba6179446f0d53 Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Thu, 15 Jun 2017 22:25:54 +0000 Subject: [PATCH 0663/3256] use logrus for logging --- go/connection/conn.go | 5 +++-- go/master/c/client.go | 4 ++-- go/master/client.go | 14 +++++++------- go/pserver/cclient/cclient.go | 24 ++++++++++++------------ go/pserver/client.go | 6 +++--- 5 files changed, 27 insertions(+), 26 deletions(-) diff --git a/go/connection/conn.go b/go/connection/conn.go index bc9b5f0617e..977e8cc1237 100644 --- a/go/connection/conn.go +++ b/go/connection/conn.go @@ -2,9 +2,10 @@ package connection import ( "errors" - "log" "net/rpc" "sync" + + log "github.com/sirupsen/logrus" ) // TODO(helin): add TCP re-connect logic @@ -65,7 +66,7 @@ func (c *Conn) Connect(addr string) error { } else { err := client.Close() if err != nil { - log.Println(err) + log.Errorln(err) } return errors.New("client already set from a concurrent goroutine") diff --git a/go/master/c/client.go b/go/master/c/client.go index 8a437eb2238..ed3e220d5ff 100644 --- a/go/master/c/client.go +++ b/go/master/c/client.go @@ -13,11 +13,11 @@ typedef int paddle_master_client; import "C" import ( - "log" "sync" "unsafe" "github.com/PaddlePaddle/Paddle/go/master" + log "github.com/sirupsen/logrus" ) var nullPtr = unsafe.Pointer(uintptr(0)) @@ -77,7 +77,7 @@ func paddle_set_dataset(client C.paddle_master_client, path **C.char, size C.int } err := c.SetDataset(paths) if err != nil { - log.Println(err) + log.Errorln(err) return C.PADDLE_MASTER_ERROR } diff --git a/go/master/client.go b/go/master/client.go index db6b17d2f0d..cd1408db3df 100644 --- a/go/master/client.go +++ b/go/master/client.go @@ -1,12 +1,12 @@ package master import ( - "log" "os" "time" "github.com/PaddlePaddle/Paddle/go/connection" "github.com/PaddlePaddle/recordio" + log "github.com/sirupsen/logrus" ) // Addresser provide the address of the master server. @@ -36,14 +36,14 @@ func (c *Client) getRecords() { if err != nil { // TODO(helin): wait before move on with next // getTask call. - log.Println(err) + log.Errorln(err) continue } for _, chunk := range t.Chunks { f, err := os.Open(chunk.Path) if err != nil { - log.Println(err) + log.Errorln(err) continue } @@ -53,12 +53,12 @@ func (c *Client) getRecords() { } if s.Err() != nil { - log.Println(err, chunk.Path) + log.Errorln(err, chunk.Path) } err = f.Close() if err != nil { - log.Println(err) + log.Errorln(err) } } @@ -79,12 +79,12 @@ func (c *Client) monitorMaster(addr Addresser) { if curMaster == "" { err := c.conn.Close() if err != nil { - log.Println(err) + log.Errorln(err) } } else { err := c.conn.Connect(curMaster) if err != nil { - log.Println(err) + log.Errorln(err) // connect to addr failed, set // to last known addr in order diff --git a/go/pserver/cclient/cclient.go b/go/pserver/cclient/cclient.go index 3e074a9f2aa..92a41b7f543 100644 --- a/go/pserver/cclient/cclient.go +++ b/go/pserver/cclient/cclient.go @@ -25,12 +25,12 @@ typedef int paddle_pserver_client; import "C" import ( - "log" "strings" "sync" "unsafe" "github.com/PaddlePaddle/Paddle/go/pserver" + log "github.com/sirupsen/logrus" ) var nullPtr = unsafe.Pointer(uintptr(0)) @@ -133,10 +133,10 @@ func paddle_init_param(client C.paddle_pserver_client, param C.paddle_parameter, if err != nil { if err.Error() == pserver.AlreadyInitialized { - log.Printf("parameter %s already initialized, treat paddle_init_param as sucessful.\n", name) + log.Warningf("parameter %s already initialized, treat paddle_init_param as sucessful.\n", name) return C.PSERVER_OK } - log.Println(err) + log.Errorln(err) return C.PSERVER_ERROR } @@ -149,11 +149,11 @@ func paddle_finish_init_params(client C.paddle_pserver_client) C.int { err := c.FinishInitParams() if err != nil { if err.Error() == pserver.AlreadyInitialized { - log.Println("parameters already initialized, treat paddle_finish_init_params as sucessful.") + log.Warningln("parameters already initialized, treat paddle_finish_init_params as sucessful.") return C.PSERVER_OK } - log.Println(err) + log.Errorln(err) return C.PSERVER_ERROR } @@ -174,7 +174,7 @@ func paddle_send_grads(client C.paddle_pserver_client, grads **C.paddle_gradient c := get(client) err := c.SendGrads(gs) if err != nil { - log.Println(err) + log.Errorln(err) return C.PSERVER_ERROR } @@ -191,7 +191,7 @@ func paddle_get_params(client C.paddle_pserver_client, dst **C.paddle_parameter, c := get(client) ps, err := c.GetParams(ns) if err != nil { - log.Println(err) + log.Errorln(err) return C.PSERVER_ERROR } @@ -200,7 +200,7 @@ func paddle_get_params(client C.paddle_pserver_client, dst **C.paddle_parameter, for i, p := range ps { pn[i] = p.Name } - log.Printf("pserver returned wrong number of parameters. Requested: %s, returned: %s.\n", strings.Join(pn, ", "), strings.Join(ns, ", ")) + log.Errorf("pserver returned wrong number of parameters. Requested: %s, returned: %s.\n", strings.Join(pn, ", "), strings.Join(ns, ", ")) return C.PSERVER_ERROR } @@ -210,7 +210,7 @@ func paddle_get_params(client C.paddle_pserver_client, dst **C.paddle_parameter, for i, p := range ps { pn[i] = p.Name } - log.Printf("pserver returned wrong parameters, or not in requested order. Requested: %s, returned: %s.\n", strings.Join(pn, ", "), strings.Join(ns, ", ")) + log.Errorf("pserver returned wrong parameters, or not in requested order. Requested: %s, returned: %s.\n", strings.Join(pn, ", "), strings.Join(ns, ", ")) return C.PSERVER_ERROR } } @@ -220,13 +220,13 @@ func paddle_get_params(client C.paddle_pserver_client, dst **C.paddle_parameter, param := *(**C.paddle_parameter)(unsafe.Pointer((uintptr(unsafe.Pointer(dst)) + uintptr(i)*unsafe.Sizeof(*dst)))) if unsafe.Pointer(param) == nullPtr { - log.Println("must pre-allocate parameter.") + log.Errorln("must pre-allocate parameter.") return C.PSERVER_ERROR } if unsafe.Pointer(param.content) != nullPtr { if int(param.content_len) != len(p.Content) { - log.Printf("the pre-allocated content len does not match parameter content len. Pre-allocated len: %d, returned len: %d", param.content_len, len(p.Content)) + log.Errorf("the pre-allocated content len does not match parameter content len. Pre-allocated len: %d, returned len: %d", param.content_len, len(p.Content)) return C.PSERVER_ERROR } } @@ -245,7 +245,7 @@ func paddle_save_model(client C.paddle_pserver_client, path *C.char) C.int { c := get(client) err := c.Save(p) if err != nil { - log.Println(err) + log.Errorln(err) return C.PSERVER_ERROR } diff --git a/go/pserver/client.go b/go/pserver/client.go index afe1eecd015..dda91597728 100644 --- a/go/pserver/client.go +++ b/go/pserver/client.go @@ -2,11 +2,11 @@ package pserver import ( "hash/fnv" - "log" "sort" "time" "github.com/PaddlePaddle/Paddle/go/connection" + log "github.com/sirupsen/logrus" ) // TODO(helin): add RPC call retry logic @@ -64,7 +64,7 @@ func (c *Client) monitorPservers(l Lister, pserverNum int) { if curServers[i].Addr == "" { err := c.pservers[i].Close() if err != nil { - log.Println(err) + log.Errorln(err) } continue @@ -72,7 +72,7 @@ func (c *Client) monitorPservers(l Lister, pserverNum int) { err := c.pservers[i].Connect(curServers[i].Addr) if err != nil { - log.Println(err) + log.Errorln(err) // connect to addr failed, set // to last known addr in order -- GitLab From 6cd1441df631b7580590de0e72f9ab854f4537c4 Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Thu, 15 Jun 2017 22:31:07 +0000 Subject: [PATCH 0664/3256] add bufSize parameter for creating master client --- go/master/c/client.go | 4 ++-- go/master/client.go | 7 +++++-- go/master/client_test.go | 2 +- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/go/master/c/client.go b/go/master/c/client.go index ed3e220d5ff..b186474dc33 100644 --- a/go/master/c/client.go +++ b/go/master/c/client.go @@ -55,9 +55,9 @@ func (a addresser) Address() string { } //export paddle_new_master_client -func paddle_new_master_client(addr *C.char) C.paddle_master_client { +func paddle_new_master_client(addr *C.char, bufSize int) C.paddle_master_client { a := C.GoString(addr) - c := master.NewClient(addresser(a)) + c := master.NewClient(addresser(a), bufSize) return add(c) } diff --git a/go/master/client.go b/go/master/client.go index cd1408db3df..8451820c196 100644 --- a/go/master/client.go +++ b/go/master/client.go @@ -21,10 +21,13 @@ type Client struct { } // NewClient creates a new Client. -func NewClient(addr Addresser) *Client { +// +// bufSize is the record buffer size. NextRecord will read from this +// buffer. +func NewClient(addr Addresser, bufSize int) *Client { c := &Client{} c.conn = connection.New() - c.ch = make(chan []byte) + c.ch = make(chan []byte, bufSize) go c.monitorMaster(addr) go c.getRecords() return c diff --git a/go/master/client_test.go b/go/master/client_test.go index 02751aeb301..2b3f873ecf3 100644 --- a/go/master/client_test.go +++ b/go/master/client_test.go @@ -60,7 +60,7 @@ func TestNextRecord(t *testing.T) { w.Close() f.Close() - c := master.NewClient(master.TestAddresser(fmt.Sprintf(":%d", p))) + c := master.NewClient(master.TestAddresser(fmt.Sprintf(":%d", p)), 10) c.SetDataset([]string{path}) for pass := 0; pass < 50; pass++ { -- GitLab From fc9e3e4bda6a4ceaa1ae9e45eb3ef522382bf8e3 Mon Sep 17 00:00:00 2001 From: zlx Date: Fri, 16 Jun 2017 14:29:16 +0800 Subject: [PATCH 0665/3256] explain the sparsity ratio --- paddle/parameter/ParameterUpdaterHook.cpp | 6 +++--- proto/ParameterConfig.proto | 3 ++- python/paddle/trainer_config_helpers/attrs.py | 3 ++- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/paddle/parameter/ParameterUpdaterHook.cpp b/paddle/parameter/ParameterUpdaterHook.cpp index 44fac592002..1cc91b727ad 100644 --- a/paddle/parameter/ParameterUpdaterHook.cpp +++ b/paddle/parameter/ParameterUpdaterHook.cpp @@ -61,7 +61,7 @@ public: maskTemp_ = Vector::create(para->getSize(), false); maskTemp_->zeroMem(); real* dataPtr = maskTemp_->getData(); - size_t sparsityNum = para->getSize() * (1 - sparsityRatio_); + size_t nonZeroNum = para->getSize() * (1 - sparsityRatio_); VectorPtr vecCpu = Vector::create(para->getSize(), false); vecCpu->copyFrom(*vec); @@ -71,10 +71,10 @@ public: param.push_back(std::make_pair(fabs(vecCpu->getData()[i]), i)); std::partial_sort(param.begin(), - param.begin() + sparsityNum, + param.begin() + nonZeroNum, param.end(), sortPairAscend); - for (size_t i = 0; i < sparsityNum; i++) dataPtr[param[i].second] = 1.0; + for (size_t i = 0; i < nonZeroNum; i++) dataPtr[param[i].second] = 1.0; // Currently just use a mask vector for hack. if (para->useGpu()) { diff --git a/proto/ParameterConfig.proto b/proto/ParameterConfig.proto index 360342bac6f..580d6632460 100644 --- a/proto/ParameterConfig.proto +++ b/proto/ParameterConfig.proto @@ -27,7 +27,8 @@ enum ParameterInitStrategy { message ParameterUpdaterHookConfig { // hook type such as 'pruning' required string type = 1; - optional double sparsity_ratio = 2 [default = 0.8]; + // this represents the ratio of zero element to be set by the Parameter + optional double sparsity_ratio = 2 [default = 0.6]; } message ParameterConfig { diff --git a/python/paddle/trainer_config_helpers/attrs.py b/python/paddle/trainer_config_helpers/attrs.py index 2e4e082efb0..bf12ad644dc 100644 --- a/python/paddle/trainer_config_helpers/attrs.py +++ b/python/paddle/trainer_config_helpers/attrs.py @@ -65,7 +65,8 @@ class HookAttribute(object): :param type: Hook type, eg: 'pruning' :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 """ -- GitLab From 885275ee77ddafa28cda0135fa752ca9d8afe1c8 Mon Sep 17 00:00:00 2001 From: Zhaolong Xing Date: Fri, 16 Jun 2017 14:59:18 +0800 Subject: [PATCH 0666/3256] Update ParameterUpdaterHook.cpp --- paddle/parameter/ParameterUpdaterHook.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/paddle/parameter/ParameterUpdaterHook.cpp b/paddle/parameter/ParameterUpdaterHook.cpp index 1cc91b727ad..738e86a6221 100644 --- a/paddle/parameter/ParameterUpdaterHook.cpp +++ b/paddle/parameter/ParameterUpdaterHook.cpp @@ -70,10 +70,8 @@ public: for (size_t i = 0; i < para->getSize(); i++) param.push_back(std::make_pair(fabs(vecCpu->getData()[i]), i)); - std::partial_sort(param.begin(), - param.begin() + nonZeroNum, - param.end(), - sortPairAscend); + std::partial_sort( + param.begin(), param.begin() + nonZeroNum, param.end(), sortPairAscend); for (size_t i = 0; i < nonZeroNum; i++) dataPtr[param[i].second] = 1.0; // Currently just use a mask vector for hack. -- GitLab From c6e010d0ddaab9d00705c1ce890ede9956559c9f Mon Sep 17 00:00:00 2001 From: hedaoyuan Date: Fri, 16 Jun 2017 23:06:43 +0800 Subject: [PATCH 0667/3256] Follow comments. --- paddle/function/ConvOp.h | 12 ++---------- paddle/function/FunctionTest.h | 4 ++-- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/paddle/function/ConvOp.h b/paddle/function/ConvOp.h index 2cfc0712557..92733bc1b76 100644 --- a/paddle/function/ConvOp.h +++ b/paddle/function/ConvOp.h @@ -104,19 +104,11 @@ public: protected: size_t getFilterHeight(const TensorShape& filter) const { - if (filter.ndims() == 5) { - return filter[3]; - } else { - return filter[2]; - } + filter[filter.ndims() - 2]; } size_t getFilterWidth(const TensorShape& filter) const { - if (filter.ndims() == 5) { - return filter[4]; - } else { - return filter[3]; - } + filter[filter.ndims() - 1]; } std::vector strides_; diff --git a/paddle/function/FunctionTest.h b/paddle/function/FunctionTest.h index 69ab33052da..ba446bf92da 100644 --- a/paddle/function/FunctionTest.h +++ b/paddle/function/FunctionTest.h @@ -296,9 +296,9 @@ public: compareOutputs(); } - std::shared_ptr getCpuFunction() const { return function1_; } + std::shared_ptr getFunction1() const { return function1_; } - std::shared_ptr getGpuFunction() const { return function2_; } + std::shared_ptr getFunction2() const { return function2_; } protected: // only init cpu argument, gpu argument copy from cpu argument. -- GitLab From 1ed31b4e4e45db3c9c0925cdb5169c5e24bde0af Mon Sep 17 00:00:00 2001 From: hedaoyuan Date: Fri, 16 Jun 2017 23:17:32 +0800 Subject: [PATCH 0668/3256] Bug fix. --- paddle/function/ConvOp.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/paddle/function/ConvOp.h b/paddle/function/ConvOp.h index 92733bc1b76..b87808915ef 100644 --- a/paddle/function/ConvOp.h +++ b/paddle/function/ConvOp.h @@ -104,11 +104,11 @@ public: protected: size_t getFilterHeight(const TensorShape& filter) const { - filter[filter.ndims() - 2]; + return filter[filter.ndims() - 2]; } size_t getFilterWidth(const TensorShape& filter) const { - filter[filter.ndims() - 1]; + return filter[filter.ndims() - 1]; } std::vector strides_; -- GitLab From ef61288f9e9d8bfecb8f8e8f67f297e20fcb2b43 Mon Sep 17 00:00:00 2001 From: xuwei06 Date: Fri, 16 Jun 2017 09:30:09 -0700 Subject: [PATCH 0669/3256] Clean-up recurrent group related python code No longer need to specify target_inlinks or is_seq. --- .../tests/sequence_rnn_mixed_inputs.py | 5 +-- python/paddle/trainer/config_parser.py | 8 +---- .../paddle/trainer_config_helpers/layers.py | 31 +++++-------------- .../configs/protostr/shared_gru.protostr | 4 --- .../configs/protostr/shared_lstm.protostr | 6 ---- .../configs/protostr/test_rnn_group.protostr | 13 -------- python/paddle/v2/layer.py | 2 +- 7 files changed, 12 insertions(+), 57 deletions(-) diff --git a/paddle/gserver/tests/sequence_rnn_mixed_inputs.py b/paddle/gserver/tests/sequence_rnn_mixed_inputs.py index a6f2d419f25..84a66e29449 100644 --- a/paddle/gserver/tests/sequence_rnn_mixed_inputs.py +++ b/paddle/gserver/tests/sequence_rnn_mixed_inputs.py @@ -59,7 +59,8 @@ def outer_step(subseq, seq, nonseq, encoding): return out decoder = recurrent_group( - step=inner_step, name='inner', input=[subseq, seq, nonseq]) + step=inner_step, name='inner', + input=[subseq, StaticInput(seq), nonseq]) last = last_seq(name="outer_rnn_state", input=decoder) context = simple_attention( encoded_sequence=encoding, encoded_proj=encoding, decoder_state=last) @@ -69,7 +70,7 @@ def outer_step(subseq, seq, nonseq, encoding): out = recurrent_group( name="outer", step=outer_step, - input=[data1, data2, label, StaticInput(encoding)]) + input=[data1, data2, StaticInput(label), StaticInput(encoding)]) rep = last_seq(input=out) prob = fc_layer( diff --git a/python/paddle/trainer/config_parser.py b/python/paddle/trainer/config_parser.py index 8edb61d1840..e2ff9020c72 100644 --- a/python/paddle/trainer/config_parser.py +++ b/python/paddle/trainer/config_parser.py @@ -328,16 +328,12 @@ def RecurrentLayerGroupWithoutOutLinksBegin(name, SubModelBegin(name) g_current_submodel.is_recurrent_layer_group = True g_current_submodel.reversed = seq_reversed - g_current_submodel.target_inlinkid = -1 in_links_count = 0 for linkid, link in enumerate(in_links): if isinstance(link, basestring): name = link else: name = link.link_name - # assign target_inlinkid according to target_inlinkname - if target_inlinkname == name: - g_current_submodel.target_inlinkid = linkid in_links_count += 1 layer_name = MakeLayerNameInParentSubmodel(name) @@ -373,8 +369,7 @@ def RecurrentLayerGroupBegin(name, generator=None, target_inlinkname="", seq_reversed=False): - RecurrentLayerGroupWithoutOutLinksBegin(name, in_links, seq_reversed, - target_inlinkname) + RecurrentLayerGroupWithoutOutLinksBegin(name, in_links, seq_reversed) for link in out_links: RecurrentLayerGroupSetOutLink(link) @@ -2309,7 +2304,6 @@ def Memory(name, if name is not None: memory.layer_name = MakeLayerNameInSubmodel(name) memory.link_name = MakeLayerNameInSubmodel(agent_name) - memory.is_sequence = is_sequence options = sum((boot_layer is not None, bool(boot_bias), boot_with_const_id is not None)) config_assert( diff --git a/python/paddle/trainer_config_helpers/layers.py b/python/paddle/trainer_config_helpers/layers.py index d4fd191b179..aa134299e3a 100755 --- a/python/paddle/trainer_config_helpers/layers.py +++ b/python/paddle/trainer_config_helpers/layers.py @@ -311,18 +311,6 @@ class LayerOutput(object): self.outputs = outputs self.reverse = reverse - def __repr__(self): - """ - Disable __repr__ for debug reason. Will be implemented when release - """ - assert False, "this method should not be invoked" - - def __str__(self): - """ - Disable __str__ for debug reason. Will be implemented when release - """ - assert False, "this method should not be invoked" - def set_input(self, input): """ Set the input for a memory layer. Can only be used for memory layer @@ -2944,7 +2932,7 @@ def memory(name, :param memory_name: the name of the memory. It is ignored when name is provided. :type memory_name: basestring - :param is_seq: is sequence for boot_layer + :param is_seq: DEPRECATED. is sequence for boot_layer :type is_seq: bool :param boot_layer: boot layer of memory. :type boot_layer: LayerOutput|None @@ -2971,7 +2959,6 @@ def memory(name, memory_name = Memory( name, size, - is_sequence=is_seq, boot_layer=boot_layer.name if boot_layer is not None else None, boot_bias=boot_bias, boot_bias_active_type=boot_bias_active_type.name, @@ -3318,15 +3305,16 @@ class StaticInput(object): """ StaticInput is only used in recurrent_group which defines a read-only memory that can be a sequence or non-sequence. + :param size: DEPRECATED + :param is_seq: DEPRECATED """ def __init__(self, input, is_seq=False, size=None): assert isinstance(input, LayerOutput) self.input = input - self.is_seq = is_seq - assert input.size is not None or size is not None + assert input.size is not None if size is not None: - input.size = size + assert input.size == size def SubsequenceInput(input): @@ -3452,15 +3440,10 @@ def recurrent_group(step, else: # StaticInput mem_name = "__%s_memory__" % each_input.input.name mem = memory( - name=mem_name, - is_seq=each_input.is_seq, + name=None, size=each_input.input.size, boot_layer=each_input.input) - with mixed_layer( - name=mem_name, - size=each_input.input.size, - act=IdentityActivation()) as mix: - mix += identity_projection(mem) + mem.set_input(mem) in_args.append(mem) assert (is_generating != has_LayerOutput) diff --git a/python/paddle/trainer_config_helpers/tests/configs/protostr/shared_gru.protostr b/python/paddle/trainer_config_helpers/tests/configs/protostr/shared_gru.protostr index 49bc5a77791..6f3d08f718c 100644 --- a/python/paddle/trainer_config_helpers/tests/configs/protostr/shared_gru.protostr +++ b/python/paddle/trainer_config_helpers/tests/configs/protostr/shared_gru.protostr @@ -256,7 +256,6 @@ sub_models { memories { layer_name: "__simple_gru_0__@__simple_gru_0___recurrent_group" link_name: "__simple_gru_0__+delay1@__simple_gru_0___recurrent_group" - is_sequence: false } in_links { layer_name: "__simple_gru_0___transform" @@ -266,7 +265,6 @@ sub_models { layer_name: "__simple_gru_0__@__simple_gru_0___recurrent_group" link_name: "__simple_gru_0__" } - target_inlinkid: -1 } sub_models { name: "__simple_gru_1___recurrent_group" @@ -278,7 +276,6 @@ sub_models { memories { layer_name: "__simple_gru_1__@__simple_gru_1___recurrent_group" link_name: "__simple_gru_1__+delay1@__simple_gru_1___recurrent_group" - is_sequence: false } in_links { layer_name: "__simple_gru_1___transform" @@ -288,6 +285,5 @@ sub_models { layer_name: "__simple_gru_1__@__simple_gru_1___recurrent_group" link_name: "__simple_gru_1__" } - target_inlinkid: -1 } diff --git a/python/paddle/trainer_config_helpers/tests/configs/protostr/shared_lstm.protostr b/python/paddle/trainer_config_helpers/tests/configs/protostr/shared_lstm.protostr index f156c17fc99..caa31403fec 100644 --- a/python/paddle/trainer_config_helpers/tests/configs/protostr/shared_lstm.protostr +++ b/python/paddle/trainer_config_helpers/tests/configs/protostr/shared_lstm.protostr @@ -341,12 +341,10 @@ sub_models { memories { layer_name: "__lstm_group_0__@__lstm_group_0___recurrent_group" link_name: "__lstm_group_0__+delay1@__lstm_group_0___recurrent_group" - is_sequence: false } memories { layer_name: "__lstm_group_0___state@__lstm_group_0___recurrent_group" link_name: "__lstm_group_0___state+delay1@__lstm_group_0___recurrent_group" - is_sequence: false } in_links { layer_name: "__mixed_0__" @@ -356,7 +354,6 @@ sub_models { layer_name: "__lstm_group_0__@__lstm_group_0___recurrent_group" link_name: "__lstm_group_0__" } - target_inlinkid: -1 } sub_models { name: "__lstm_group_1___recurrent_group" @@ -371,12 +368,10 @@ sub_models { memories { layer_name: "__lstm_group_1__@__lstm_group_1___recurrent_group" link_name: "__lstm_group_1__+delay1@__lstm_group_1___recurrent_group" - is_sequence: false } memories { layer_name: "__lstm_group_1___state@__lstm_group_1___recurrent_group" link_name: "__lstm_group_1___state+delay1@__lstm_group_1___recurrent_group" - is_sequence: false } in_links { layer_name: "__mixed_1__" @@ -386,6 +381,5 @@ sub_models { layer_name: "__lstm_group_1__@__lstm_group_1___recurrent_group" link_name: "__lstm_group_1__" } - target_inlinkid: -1 } diff --git a/python/paddle/trainer_config_helpers/tests/configs/protostr/test_rnn_group.protostr b/python/paddle/trainer_config_helpers/tests/configs/protostr/test_rnn_group.protostr index 6ec897f7d09..ea4443f652b 100644 --- a/python/paddle/trainer_config_helpers/tests/configs/protostr/test_rnn_group.protostr +++ b/python/paddle/trainer_config_helpers/tests/configs/protostr/test_rnn_group.protostr @@ -618,7 +618,6 @@ sub_models { memories { layer_name: "rnn_forward@__recurrent_group_0__" link_name: "rnn_forward+delay1@__recurrent_group_0__" - is_sequence: false } in_links { layer_name: "seq_input" @@ -628,7 +627,6 @@ sub_models { layer_name: "rnn_forward@__recurrent_group_0__" link_name: "rnn_forward" } - target_inlinkid: -1 } sub_models { name: "__recurrent_group_1__" @@ -640,7 +638,6 @@ sub_models { memories { layer_name: "rnn_back@__recurrent_group_1__" link_name: "rnn_back+delay1@__recurrent_group_1__" - is_sequence: false } in_links { layer_name: "seq_input" @@ -650,7 +647,6 @@ sub_models { layer_name: "rnn_back@__recurrent_group_1__" link_name: "rnn_back" } - target_inlinkid: -1 } sub_models { name: "__recurrent_group_2__" @@ -662,7 +658,6 @@ sub_models { memories { layer_name: "rnn_subseq_forward@__recurrent_group_2__" link_name: "rnn_subseq_forward+delay1@__recurrent_group_2__" - is_sequence: false } in_links { layer_name: "sub_seq_input" @@ -672,7 +667,6 @@ sub_models { layer_name: "rnn_subseq_forward@__recurrent_group_2__" link_name: "rnn_subseq_forward" } - target_inlinkid: -1 } sub_models { name: "__lstm_group_0___recurrent_group" @@ -687,12 +681,10 @@ sub_models { memories { layer_name: "__lstm_group_0__@__lstm_group_0___recurrent_group" link_name: "__lstm_group_0__+delay1@__lstm_group_0___recurrent_group" - is_sequence: false } memories { layer_name: "__lstm_group_0___state@__lstm_group_0___recurrent_group" link_name: "__lstm_group_0___state+delay1@__lstm_group_0___recurrent_group" - is_sequence: false } in_links { layer_name: "__mixed_0__" @@ -702,7 +694,6 @@ sub_models { layer_name: "__lstm_group_0__@__lstm_group_0___recurrent_group" link_name: "__lstm_group_0__" } - target_inlinkid: -1 } sub_models { name: "__gru_group_0___recurrent_group" @@ -714,7 +705,6 @@ sub_models { memories { layer_name: "__gru_group_0__@__gru_group_0___recurrent_group" link_name: "__gru_group_0__+delay1@__gru_group_0___recurrent_group" - is_sequence: false } in_links { layer_name: "__mixed_1__" @@ -724,7 +714,6 @@ sub_models { layer_name: "__gru_group_0__@__gru_group_0___recurrent_group" link_name: "__gru_group_0__" } - target_inlinkid: -1 } sub_models { name: "__recurrent_group_3__" @@ -736,7 +725,6 @@ sub_models { memories { layer_name: "__fc_layer_0__@__recurrent_group_3__" link_name: "__memory_6__@__recurrent_group_3__" - is_sequence: false } in_links { layer_name: "seq_input" @@ -746,6 +734,5 @@ sub_models { layer_name: "__fc_layer_0__@__recurrent_group_3__" link_name: "__fc_layer_0__" } - target_inlinkid: -1 } diff --git a/python/paddle/v2/layer.py b/python/paddle/v2/layer.py index aeed9ebd7d4..bbb9c3ea8c1 100644 --- a/python/paddle/v2/layer.py +++ b/python/paddle/v2/layer.py @@ -260,7 +260,7 @@ def parse_network(output_layers, extra_layers=None): else: extra_layers = [] - layer_names = __get_used_layers__(output_layers + extra_layers) + layer_names = __get_used_layers__(list(output_layers) + list(extra_layers)) submodel_names = __get_used_submodels__(layer_names) submodel_names.add('root') evaluator_names = __get_used_evaluators__(layer_names) -- GitLab From 226f810352db3211bdecaece8be1e189cbbba713 Mon Sep 17 00:00:00 2001 From: xuwei06 Date: Fri, 16 Jun 2017 09:59:41 -0700 Subject: [PATCH 0670/3256] Add activiation for repeat_layer Also remove active_type argument for many layers in config_parser.py because it is automatically handled by LayerBase. --- python/paddle/trainer/config_parser.py | 57 ++++--------------- .../paddle/trainer_config_helpers/layers.py | 12 +++- .../tests/configs/file_list.sh | 2 +- .../configs/protostr/last_first_seq.protostr | 12 ++-- .../configs/protostr/shared_gru.protostr | 4 +- .../configs/protostr/shared_lstm.protostr | 4 +- .../protostr/simple_rnn_layers.protostr | 12 ++-- .../protostr/test_repeat_layer.protostr | 42 ++++++++++++++ .../configs/protostr/test_rnn_group.protostr | 12 ++-- .../protostr/test_seq_concat_reshape.protostr | 2 +- .../protostr/test_sequence_pooling.protostr | 14 ++--- 11 files changed, 94 insertions(+), 79 deletions(-) create mode 100644 python/paddle/trainer_config_helpers/tests/configs/protostr/test_repeat_layer.protostr diff --git a/python/paddle/trainer/config_parser.py b/python/paddle/trainer/config_parser.py index 8e3c3241623..86f091ab59d 100644 --- a/python/paddle/trainer/config_parser.py +++ b/python/paddle/trainer/config_parser.py @@ -1949,7 +1949,6 @@ class BatchNormLayer(LayerBase): def __init__(self, name, inputs, - active_type="linear", bias=True, use_global_stats=True, moving_average_fraction=0.9, @@ -1987,12 +1986,7 @@ class BatchNormLayer(LayerBase): cudnn_version >= 4007 self.layer_type = "cudnn_batch_norm" if use_cudnn else "batch_norm" super(BatchNormLayer, self).__init__( - name, - self.layer_type, - 0, - active_type=active_type, - inputs=inputs, - **xargs) + name, self.layer_type, 0, inputs=inputs, **xargs) if use_global_stats is not None: self.config.use_global_stats = use_global_stats @@ -2431,12 +2425,12 @@ class FeatMapExpandLayer(LayerBase): def __init__(self, name, inputs, - device=None, num_filters=None, as_row_vector=True, - bias=False): + bias=False, + **xargs): super(FeatMapExpandLayer, self).__init__( - name, 'featmap_expand', 0, inputs=inputs, device=device) + name, 'featmap_expand', 0, inputs=inputs, **xargs) config_assert( len(self.inputs) == 1, 'ExpandLayer takes 1 and only 1 inputs') if num_filters is not None: @@ -2454,14 +2448,12 @@ class MaxLayer(LayerBase): name, inputs, trans_type='non-seq', - active_type='linear', bias=False, output_max_index=None, **xargs): super(MaxLayer, self).__init__(name, 'max', 0, inputs=inputs, **xargs) config_assert(len(self.inputs) == 1, 'MaxLayer must have 1 input') self.config.trans_type = trans_type - self.config.active_type = active_type for input_index in xrange(len(self.inputs)): input_layer = self.get_input_layer(input_index) self.set_layer_size(input_layer.size) @@ -2503,18 +2495,12 @@ class SequenceLastInstanceLayer(LayerBase): def __init__(self, name, inputs, - active_type='linear', trans_type='non-seq', bias=False, stride=-1, **xargs): super(SequenceLastInstanceLayer, self).__init__( - name, - 'seqlastins', - 0, - inputs=inputs, - active_type=active_type, - **xargs) + name, 'seqlastins', 0, inputs=inputs, **xargs) config_assert( len(inputs) == 1, 'SequenceLastInstanceLayer must have 1 input') if trans_type == 'seq': @@ -2530,7 +2516,6 @@ class SequenceFirstInstanceLayer(SequenceLastInstanceLayer): def __init__(self, name, inputs, - active_type='linear', trans_type='non-seq', bias=False, stride=-1, @@ -2538,7 +2523,6 @@ class SequenceFirstInstanceLayer(SequenceLastInstanceLayer): super(SequenceFirstInstanceLayer, self).__init__( name, inputs=inputs, - active_type=active_type, trans_type=trans_type, bias=bias, stride=stride, @@ -2548,14 +2532,9 @@ class SequenceFirstInstanceLayer(SequenceLastInstanceLayer): @config_layer('seqconcat') class SequenceConcatLayer(LayerBase): - def __init__(self, name, inputs, active_type='linear', bias=False, **xargs): + def __init__(self, name, inputs, bias=False, **xargs): super(SequenceConcatLayer, self).__init__( - name, - 'seqconcat', - 0, - inputs=inputs, - active_type=active_type, - **xargs) + name, 'seqconcat', 0, inputs=inputs, **xargs) config_assert( len(inputs) == 2, 'SequenceConcatLayer must have 2 inputs') for input_index in xrange(len(self.inputs)): @@ -2566,20 +2545,9 @@ class SequenceConcatLayer(LayerBase): @config_layer('seqreshape') class SequenceReshapeLayer(LayerBase): - def __init__(self, - name, - size, - inputs, - active_type='linear', - bias=False, - **xargs): + def __init__(self, name, size, inputs, bias=False, **xargs): super(SequenceReshapeLayer, self).__init__( - name, - 'seqreshape', - size, - inputs=inputs, - active_type=active_type, - **xargs) + name, 'seqreshape', size, inputs=inputs, **xargs) config_assert( len(inputs) == 1, 'SequenceReshapeLayer must have 1 inputs') self.set_layer_size(size) @@ -2588,9 +2556,9 @@ class SequenceReshapeLayer(LayerBase): @config_layer('subseq') class SubSequenceLayer(LayerBase): - def __init__(self, name, inputs, active_type='linear', bias=False, **xargs): + def __init__(self, name, inputs, bias=False, **xargs): super(SubSequenceLayer, self).__init__( - name, 'subseq', 0, inputs=inputs, active_type=active_type, **xargs) + name, 'subseq', 0, inputs=inputs, **xargs) config_assert(len(inputs) == 3, 'SubSequenceLayer must have 3 inputs') input_layer0 = self.get_input_layer(0) size = input_layer0.size @@ -2746,11 +2714,10 @@ class AverageLayer(LayerBase): inputs, average_strategy='average', trans_type='non-seq', - active_type='linear', bias=False, **xargs): super(AverageLayer, self).__init__( - name, 'average', 0, inputs=inputs, active_type=active_type, **xargs) + name, 'average', 0, inputs=inputs, **xargs) self.config.average_strategy = average_strategy self.config.trans_type = trans_type config_assert(len(inputs) == 1, 'AverageLayer must have 1 input') diff --git a/python/paddle/trainer_config_helpers/layers.py b/python/paddle/trainer_config_helpers/layers.py index f84b883bc2e..caa474e679a 100755 --- a/python/paddle/trainer_config_helpers/layers.py +++ b/python/paddle/trainer_config_helpers/layers.py @@ -1565,10 +1565,12 @@ def expand_layer(input, @wrap_name_default() +@wrap_act_default(act=IdentityActivation()) @layer_support() def repeat_layer(input, num_repeats, as_row_vector=True, + act=None, name=None, layer_attr=None): """ @@ -1599,6 +1601,8 @@ def repeat_layer(input, False for treating input as column vector and repeating in the row direction. :type as_row_vector: bool + :param act: Activation type. + :type act: BaseActivation :type name: basestring :param layer_attr: extra layer attributes. :type layer_attr: ExtraLayerAttribute. @@ -1609,6 +1613,7 @@ def repeat_layer(input, l = Layer( inputs=[input.name], name=name, + active_type=act.name, num_filters=num_repeats, as_row_vector=as_row_vector, type=LayerType.FEATURE_MAP_EXPAND_LAYER, @@ -1617,6 +1622,7 @@ def repeat_layer(input, name=name, size=l.config.size, layer_type=LayerType.FEATURE_MAP_EXPAND_LAYER, + activation=act, parents=[input]) @@ -2873,7 +2879,7 @@ def seq_concat_layer(a, b, act=None, name=None, layer_attr=None, .. code-block:: python - concat = seq_concat_layer(al=layer1, b=layer2) + concat = seq_concat_layer(a=layer1, b=layer2) :param name: Layer name. :type name: basestring @@ -5625,13 +5631,13 @@ def row_conv_layer(input, to deploy in an online and low-latency setting. The lookahead convolution incorporates information from future subsequences in a computationally efficient manner to improve unidirectional recurrent neural networks. - + The connection of row convolution is different form the 1D sequence convolution. Assumed that, the future context-length is k, that is to say, it can get the output at timestep t by using the the input feature from t-th timestep to (t+k+1)-th timestep. Assumed that the hidden dim of input activations are d, the activations r_t for the new layer at time-step t are: - + .. math:: r_{t,r} = \sum_{j=1}^{k + 1} {w_{i,j}h_{t+j-1, i}} diff --git a/python/paddle/trainer_config_helpers/tests/configs/file_list.sh b/python/paddle/trainer_config_helpers/tests/configs/file_list.sh index c24102255f5..c0e87d6de37 100755 --- a/python/paddle/trainer_config_helpers/tests/configs/file_list.sh +++ b/python/paddle/trainer_config_helpers/tests/configs/file_list.sh @@ -1,5 +1,5 @@ #!/bin/bash -export configs=(test_fc layer_activations projections test_print_layer +export configs=(test_repeat_layer test_fc layer_activations projections test_print_layer test_sequence_pooling test_lstmemory_layer test_grumemory_layer last_first_seq test_expand_layer test_ntm_layers test_hsigmoid img_layers img_trans_layers util_layers simple_rnn_layers unused_layers test_cost_layers diff --git a/python/paddle/trainer_config_helpers/tests/configs/protostr/last_first_seq.protostr b/python/paddle/trainer_config_helpers/tests/configs/protostr/last_first_seq.protostr index 12b2255f3a4..fee0f8e462b 100644 --- a/python/paddle/trainer_config_helpers/tests/configs/protostr/last_first_seq.protostr +++ b/python/paddle/trainer_config_helpers/tests/configs/protostr/last_first_seq.protostr @@ -9,7 +9,7 @@ layers { name: "__first_seq_0__" type: "seqlastins" size: 30 - active_type: "linear" + active_type: "" inputs { input_layer_name: "data" } @@ -21,7 +21,7 @@ layers { name: "__first_seq_1__" type: "seqlastins" size: 30 - active_type: "linear" + active_type: "" inputs { input_layer_name: "data" } @@ -33,7 +33,7 @@ layers { name: "__last_seq_0__" type: "seqlastins" size: 30 - active_type: "linear" + active_type: "" inputs { input_layer_name: "data" } @@ -44,7 +44,7 @@ layers { name: "__last_seq_1__" type: "seqlastins" size: 30 - active_type: "linear" + active_type: "" inputs { input_layer_name: "data" } @@ -55,7 +55,7 @@ layers { name: "__first_seq_2__" type: "seqlastins" size: 30 - active_type: "linear" + active_type: "" inputs { input_layer_name: "data" } @@ -67,7 +67,7 @@ layers { name: "__last_seq_2__" type: "seqlastins" size: 30 - active_type: "linear" + active_type: "" inputs { input_layer_name: "data" } diff --git a/python/paddle/trainer_config_helpers/tests/configs/protostr/shared_gru.protostr b/python/paddle/trainer_config_helpers/tests/configs/protostr/shared_gru.protostr index 64530146a14..712887447d9 100644 --- a/python/paddle/trainer_config_helpers/tests/configs/protostr/shared_gru.protostr +++ b/python/paddle/trainer_config_helpers/tests/configs/protostr/shared_gru.protostr @@ -123,7 +123,7 @@ layers { name: "__last_seq_0__" type: "seqlastins" size: 200 - active_type: "linear" + active_type: "" inputs { input_layer_name: "__simple_gru_0__" } @@ -134,7 +134,7 @@ layers { name: "__last_seq_1__" type: "seqlastins" size: 200 - active_type: "linear" + active_type: "" inputs { input_layer_name: "__simple_gru_1__" } diff --git a/python/paddle/trainer_config_helpers/tests/configs/protostr/shared_lstm.protostr b/python/paddle/trainer_config_helpers/tests/configs/protostr/shared_lstm.protostr index 79fa4c74f08..b2a00ef225c 100644 --- a/python/paddle/trainer_config_helpers/tests/configs/protostr/shared_lstm.protostr +++ b/python/paddle/trainer_config_helpers/tests/configs/protostr/shared_lstm.protostr @@ -205,7 +205,7 @@ layers { name: "__last_seq_0__" type: "seqlastins" size: 100 - active_type: "linear" + active_type: "" inputs { input_layer_name: "__lstm_group_0__" } @@ -216,7 +216,7 @@ layers { name: "__last_seq_1__" type: "seqlastins" size: 100 - active_type: "linear" + active_type: "" inputs { input_layer_name: "__lstm_group_1__" } diff --git a/python/paddle/trainer_config_helpers/tests/configs/protostr/simple_rnn_layers.protostr b/python/paddle/trainer_config_helpers/tests/configs/protostr/simple_rnn_layers.protostr index 68fa881b4f1..0d51f70ee01 100644 --- a/python/paddle/trainer_config_helpers/tests/configs/protostr/simple_rnn_layers.protostr +++ b/python/paddle/trainer_config_helpers/tests/configs/protostr/simple_rnn_layers.protostr @@ -138,7 +138,7 @@ layers { name: "__last_seq_0__" type: "seqlastins" size: 200 - active_type: "linear" + active_type: "" inputs { input_layer_name: "__recurrent_layer_0__" } @@ -149,7 +149,7 @@ layers { name: "__first_seq_0__" type: "seqlastins" size: 200 - active_type: "linear" + active_type: "" inputs { input_layer_name: "__recurrent_layer_1__" } @@ -161,7 +161,7 @@ layers { name: "__last_seq_1__" type: "seqlastins" size: 200 - active_type: "linear" + active_type: "" inputs { input_layer_name: "__lstmemory_0__" } @@ -172,7 +172,7 @@ layers { name: "__first_seq_1__" type: "seqlastins" size: 200 - active_type: "linear" + active_type: "" inputs { input_layer_name: "__lstmemory_1__" } @@ -184,7 +184,7 @@ layers { name: "__last_seq_2__" type: "seqlastins" size: 200 - active_type: "linear" + active_type: "" inputs { input_layer_name: "__gru_0__" } @@ -195,7 +195,7 @@ layers { name: "__first_seq_2__" type: "seqlastins" size: 200 - active_type: "linear" + active_type: "" inputs { input_layer_name: "__gru_1__" } diff --git a/python/paddle/trainer_config_helpers/tests/configs/protostr/test_repeat_layer.protostr b/python/paddle/trainer_config_helpers/tests/configs/protostr/test_repeat_layer.protostr new file mode 100644 index 00000000000..e012386ff95 --- /dev/null +++ b/python/paddle/trainer_config_helpers/tests/configs/protostr/test_repeat_layer.protostr @@ -0,0 +1,42 @@ +type: "nn" +layers { + name: "data" + type: "data" + size: 30 + active_type: "" +} +layers { + name: "__repeat_layer_0__" + type: "featmap_expand" + size: 300 + active_type: "" + inputs { + input_layer_name: "data" + } + num_filters: 10 +} +layers { + name: "__repeat_layer_1__" + type: "featmap_expand" + size: 300 + active_type: "tanh" + inputs { + input_layer_name: "data" + } + num_filters: 10 + user_arg: "as_col_vec" +} +input_layer_names: "data" +output_layer_names: "__repeat_layer_0__" +output_layer_names: "__repeat_layer_1__" +sub_models { + name: "root" + layer_names: "data" + layer_names: "__repeat_layer_0__" + layer_names: "__repeat_layer_1__" + input_layer_names: "data" + output_layer_names: "__repeat_layer_0__" + output_layer_names: "__repeat_layer_1__" + is_recurrent_layer_group: false +} + diff --git a/python/paddle/trainer_config_helpers/tests/configs/protostr/test_rnn_group.protostr b/python/paddle/trainer_config_helpers/tests/configs/protostr/test_rnn_group.protostr index 77b447aa9db..3a3e2c49398 100644 --- a/python/paddle/trainer_config_helpers/tests/configs/protostr/test_rnn_group.protostr +++ b/python/paddle/trainer_config_helpers/tests/configs/protostr/test_rnn_group.protostr @@ -91,7 +91,7 @@ layers { name: "__last_seq_0__" type: "seqlastins" size: 200 - active_type: "linear" + active_type: "" inputs { input_layer_name: "rnn_forward" } @@ -140,7 +140,7 @@ layers { name: "__first_seq_0__" type: "seqlastins" size: 200 - active_type: "linear" + active_type: "" inputs { input_layer_name: "rnn_back" } @@ -190,7 +190,7 @@ layers { name: "__last_seq_1__" type: "seqlastins" size: 200 - active_type: "linear" + active_type: "" inputs { input_layer_name: "rnn_subseq_forward" } @@ -280,7 +280,7 @@ layers { name: "__last_seq_2__" type: "seqlastins" size: 100 - active_type: "linear" + active_type: "" inputs { input_layer_name: "__lstm_group_0__" } @@ -329,7 +329,7 @@ layers { name: "__last_seq_3__" type: "seqlastins" size: 100 - active_type: "linear" + active_type: "" inputs { input_layer_name: "__gru_group_0__" } @@ -378,7 +378,7 @@ layers { name: "__last_seq_4__" type: "seqlastins" size: 200 - active_type: "linear" + active_type: "" inputs { input_layer_name: "__fc_layer_0__" } diff --git a/python/paddle/trainer_config_helpers/tests/configs/protostr/test_seq_concat_reshape.protostr b/python/paddle/trainer_config_helpers/tests/configs/protostr/test_seq_concat_reshape.protostr index 91284b4fb32..9d1b41c9d55 100644 --- a/python/paddle/trainer_config_helpers/tests/configs/protostr/test_seq_concat_reshape.protostr +++ b/python/paddle/trainer_config_helpers/tests/configs/protostr/test_seq_concat_reshape.protostr @@ -27,7 +27,7 @@ layers { name: "__seqreshape_0__" type: "seqreshape" size: 5 - active_type: "linear" + active_type: "" inputs { input_layer_name: "data1" } diff --git a/python/paddle/trainer_config_helpers/tests/configs/protostr/test_sequence_pooling.protostr b/python/paddle/trainer_config_helpers/tests/configs/protostr/test_sequence_pooling.protostr index 1999c006d23..5a217f5544a 100644 --- a/python/paddle/trainer_config_helpers/tests/configs/protostr/test_sequence_pooling.protostr +++ b/python/paddle/trainer_config_helpers/tests/configs/protostr/test_sequence_pooling.protostr @@ -9,7 +9,7 @@ layers { name: "__seq_pooling_0__" type: "max" size: 100 - active_type: "linear" + active_type: "" inputs { input_layer_name: "dat_in" } @@ -19,7 +19,7 @@ layers { name: "__seq_pooling_1__" type: "max" size: 100 - active_type: "linear" + active_type: "" inputs { input_layer_name: "dat_in" } @@ -29,7 +29,7 @@ layers { name: "__seq_pooling_2__" type: "average" size: 100 - active_type: "linear" + active_type: "" inputs { input_layer_name: "dat_in" } @@ -40,7 +40,7 @@ layers { name: "__seq_pooling_3__" type: "average" size: 100 - active_type: "linear" + active_type: "" inputs { input_layer_name: "dat_in" } @@ -51,7 +51,7 @@ layers { name: "__seq_pooling_4__" type: "average" size: 100 - active_type: "linear" + active_type: "" inputs { input_layer_name: "dat_in" } @@ -62,7 +62,7 @@ layers { name: "__seq_pooling_5__" type: "average" size: 100 - active_type: "linear" + active_type: "" inputs { input_layer_name: "dat_in" } @@ -73,7 +73,7 @@ layers { name: "__seq_pooling_6__" type: "max" size: 100 - active_type: "linear" + active_type: "" inputs { input_layer_name: "dat_in" } -- GitLab From 8742441fc0f3388a5fde3fb15c810718424e33ff Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Fri, 16 Jun 2017 19:32:24 +0000 Subject: [PATCH 0671/3256] integrate master Python lib with cmake --- CMakeLists.txt | 1 + go/cmake/golang.cmake | 12 ++++------ go/master/c/CMakeLists.txt | 21 ++++++++++++++++ go/master/python/.gitignore | 1 - go/master/python/build.sh | 4 ---- go/master/python/setup.py | 19 --------------- python/CMakeLists.txt | 4 ++-- python/paddle/v2/__init__.py | 24 ++++++++++++++++--- .../paddle/v2/master}/.gitignore | 1 + .../paddle/v2/master}/__init__.py | 0 .../paddle/v2/master}/client.py | 2 +- python/setup.py.in | 7 +++--- 12 files changed, 55 insertions(+), 41 deletions(-) create mode 100644 go/master/c/CMakeLists.txt delete mode 100644 go/master/python/.gitignore delete mode 100755 go/master/python/build.sh delete mode 100644 go/master/python/setup.py rename {go/master/python/paddle_master => python/paddle/v2/master}/.gitignore (64%) rename {go/master/python/paddle_master => python/paddle/v2/master}/__init__.py (100%) rename {go/master/python/paddle_master => python/paddle/v2/master}/client.py (93%) diff --git a/CMakeLists.txt b/CMakeLists.txt index c2218be5efb..2b6a80ca43c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -126,6 +126,7 @@ endif(WITH_GPU) add_subdirectory(proto) add_subdirectory(paddle) +add_subdirectory(go/master/c) add_subdirectory(python) add_subdirectory(go/pserver/cclient) diff --git a/go/cmake/golang.cmake b/go/cmake/golang.cmake index 7c85fb6298d..a5a43886f88 100644 --- a/go/cmake/golang.cmake +++ b/go/cmake/golang.cmake @@ -26,27 +26,23 @@ function(GO_LIBRARY NAME BUILD_TYPE) # automatically get all dependencies specified in the source code # for given target. - add_custom_target(goGet env GOPATH=${GOPATH} ${CMAKE_Go_COMPILER} get -d ${rel}/...) + add_custom_target(${NAME}_goGet env GOPATH=${GOPATH} ${CMAKE_Go_COMPILER} get -d ${rel}/...) # make a symlink that references Paddle inside $GOPATH, so go get # will use the local changes in Paddle rather than checkout Paddle # in github. - add_custom_target(copyPaddle + add_custom_target(${NAME}_copyPaddle COMMAND rm -rf ${PADDLE_IN_GOPATH}/Paddle COMMAND ln -sf ${PADDLE_DIR} ${PADDLE_IN_GOPATH}/Paddle) - add_dependencies(goGet copyPaddle) + add_dependencies(${NAME}_goGet ${NAME}_copyPaddle) add_custom_command(OUTPUT ${OUTPUT_DIR}/.timestamp COMMAND env GOPATH=${GOPATH} ${CMAKE_Go_COMPILER} build ${BUILD_MODE} - -gcflags=-shared -asmflags=-shared -installsuffix=_shared -a -o "${CMAKE_CURRENT_BINARY_DIR}/${LIB_NAME}" ${CMAKE_GO_FLAGS} ${GO_SOURCE} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) add_custom_target(${NAME} ALL DEPENDS ${OUTPUT_DIR}/.timestamp ${ARGN}) - add_dependencies(${NAME} goGet) + add_dependencies(${NAME} ${NAME}_goGet) - if(NOT BUILD_TYPE STREQUAL "STATIC") - install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/${LIB_NAME} DESTINATION bin) - endif() endfunction(GO_LIBRARY) diff --git a/go/master/c/CMakeLists.txt b/go/master/c/CMakeLists.txt new file mode 100644 index 00000000000..acce698051e --- /dev/null +++ b/go/master/c/CMakeLists.txt @@ -0,0 +1,21 @@ +cmake_minimum_required(VERSION 3.0) + +get_filename_component(PARENT_DIR ${CMAKE_CURRENT_SOURCE_DIR} DIRECTORY) +get_filename_component(PARENT_DIR ${PARENT_DIR} DIRECTORY) +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${PARENT_DIR}/cmake") + +project(cxx_go C Go) + +include(golang) +include(flags) + +set(MASTER_LIB_NAME "paddle_master") +go_library(${MASTER_LIB_NAME} SHARED) + +if(PROJ_ROOT) + add_custom_command(OUTPUT ${PROJ_ROOT}/python/paddle/v2/master/lib${MASTER_LIB_NAME}.so + COMMAND rm ${CMAKE_CURRENT_BINARY_DIR}/lib${MASTER_LIB_NAME}.h + COMMAND cp ${CMAKE_CURRENT_BINARY_DIR}/lib${MASTER_LIB_NAME}.so ${PROJ_ROOT}/python/paddle/v2/master/ + DEPENDS ${MASTER_LIB_NAME}) + add_custom_target(paddle_master_shared ALL DEPENDS ${PROJ_ROOT}/python/paddle/v2/master/lib${MASTER_LIB_NAME}.so) +endif(PROJ_ROOT) diff --git a/go/master/python/.gitignore b/go/master/python/.gitignore deleted file mode 100644 index 704d307510b..00000000000 --- a/go/master/python/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*.whl diff --git a/go/master/python/build.sh b/go/master/python/build.sh deleted file mode 100755 index e3dbd7b0bc3..00000000000 --- a/go/master/python/build.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash - -go build -buildmode=c-shared ../c && rm c.h && mv c paddle_master/libmaster.so -pip wheel . diff --git a/go/master/python/setup.py b/go/master/python/setup.py deleted file mode 100644 index d7b6e9ecab6..00000000000 --- a/go/master/python/setup.py +++ /dev/null @@ -1,19 +0,0 @@ -from setuptools import setup, Distribution - - -class BinaryDistribution(Distribution): - def has_ext_modules(foo): - return True - - -setup( - name='paddle_master', - version='0.1', - description='The client of the master server of PaddlePaddle.', - url='https://github.com/PaddlePaddle/Paddle/go/master/python', - author='PaddlePaddle Authors', - author_email='paddle-dev@baidu.com', - license='Apache 2.0', - packages=['paddle_master'], - package_data={'master': ['libmaster.so'], }, - distclass=BinaryDistribution) diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index 3640dd3a75e..345d2fa65af 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -15,10 +15,10 @@ set(PY_FILES paddle/__init__.py configure_file(${CMAKE_CURRENT_SOURCE_DIR}/setup.py.in ${CMAKE_CURRENT_BINARY_DIR}/setup.py) -add_custom_command(OUTPUT ${OUTPUT_DIR}/.timestamp + add_custom_command(OUTPUT ${OUTPUT_DIR}/.timestamp COMMAND env ${py_env} ${PYTHON_EXECUTABLE} setup.py bdist_wheel COMMAND ${CMAKE_COMMAND} -E touch ${OUTPUT_DIR}/.timestamp - DEPENDS gen_proto_py ${PY_FILES} ${external_project_dependencies}) + DEPENDS gen_proto_py ${PY_FILES} ${external_project_dependencies} paddle_master_shared) add_custom_target(paddle_python ALL DEPENDS ${OUTPUT_DIR}/.timestamp) diff --git a/python/paddle/v2/__init__.py b/python/paddle/v2/__init__.py index b9d0a7f2913..102331c0bb6 100644 --- a/python/paddle/v2/__init__.py +++ b/python/paddle/v2/__init__.py @@ -26,6 +26,7 @@ import evaluator from . import dataset from . import reader from . import plot +from . import master import attr import op import pooling @@ -37,9 +38,26 @@ import plot import image __all__ = [ - 'optimizer', 'layer', 'activation', 'parameters', 'init', 'trainer', - 'event', 'data_type', 'attr', 'pooling', 'data_feeder', 'dataset', 'reader', - 'topology', 'networks', 'infer', 'plot', 'evaluator', 'image' + 'optimizer', + 'layer', + 'activation', + 'parameters', + 'init', + 'trainer', + 'event', + 'data_type', + 'attr', + 'pooling', + 'data_feeder', + 'dataset', + 'reader', + 'topology', + 'networks', + 'infer', + 'plot', + 'evaluator', + 'image', + 'master', ] diff --git a/go/master/python/paddle_master/.gitignore b/python/paddle/v2/master/.gitignore similarity index 64% rename from go/master/python/paddle_master/.gitignore rename to python/paddle/v2/master/.gitignore index 4cd99212590..a3ac6e1a33e 100644 --- a/go/master/python/paddle_master/.gitignore +++ b/python/paddle/v2/master/.gitignore @@ -1,2 +1,3 @@ +*.whl *.so *.pyc diff --git a/go/master/python/paddle_master/__init__.py b/python/paddle/v2/master/__init__.py similarity index 100% rename from go/master/python/paddle_master/__init__.py rename to python/paddle/v2/master/__init__.py diff --git a/go/master/python/paddle_master/client.py b/python/paddle/v2/master/client.py similarity index 93% rename from go/master/python/paddle_master/client.py rename to python/paddle/v2/master/client.py index 2dfcb399046..de8e9bb88e1 100644 --- a/go/master/python/paddle_master/client.py +++ b/python/paddle/v2/master/client.py @@ -1,7 +1,7 @@ import ctypes import os -path = os.path.join(os.path.dirname(__file__), "libmaster.so") +path = os.path.join(os.path.dirname(__file__), "libpaddle_master.so") lib = ctypes.cdll.LoadLibrary(path) diff --git a/python/setup.py.in b/python/setup.py.in index 93724f91880..8fe1cfd8b33 100644 --- a/python/setup.py.in +++ b/python/setup.py.in @@ -1,6 +1,5 @@ from setuptools import setup - packages=['paddle', 'paddle.proto', 'paddle.trainer', @@ -9,7 +8,8 @@ packages=['paddle', 'paddle.v2', 'paddle.v2.dataset', 'paddle.v2.reader', - 'paddle.v2.plot'] + 'paddle.v2.plot', + 'paddle.v2.master'] setup_requires=["requests", "numpy", @@ -25,7 +25,8 @@ setup(name='paddle', description='Parallel Distributed Deep Learning', install_requires=setup_requires, packages=packages, + package_data={'paddle.v2.master': ['libpaddle_master.so'], }, package_dir={ '': '${CMAKE_CURRENT_SOURCE_DIR}' - } + }, ) -- GitLab From 4b6243c136515e5c63b05c202229301e2ad5197a Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Fri, 16 Jun 2017 19:48:40 +0000 Subject: [PATCH 0672/3256] fix cmake format --- python/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index 345d2fa65af..0e17c42d34f 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -15,7 +15,7 @@ set(PY_FILES paddle/__init__.py configure_file(${CMAKE_CURRENT_SOURCE_DIR}/setup.py.in ${CMAKE_CURRENT_BINARY_DIR}/setup.py) - add_custom_command(OUTPUT ${OUTPUT_DIR}/.timestamp +add_custom_command(OUTPUT ${OUTPUT_DIR}/.timestamp COMMAND env ${py_env} ${PYTHON_EXECUTABLE} setup.py bdist_wheel COMMAND ${CMAKE_COMMAND} -E touch ${OUTPUT_DIR}/.timestamp DEPENDS gen_proto_py ${PY_FILES} ${external_project_dependencies} paddle_master_shared) -- GitLab From 5f126432a398ac3306b23627d2430afc435d8497 Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Fri, 16 Jun 2017 17:54:02 -0700 Subject: [PATCH 0673/3256] Add documentation for installing Go when building Paddle manually --- .../build_and_install/build_from_source_en.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/doc/getstarted/build_and_install/build_from_source_en.md b/doc/getstarted/build_and_install/build_from_source_en.md index 69f4501f370..c0608ede8e5 100644 --- a/doc/getstarted/build_and_install/build_from_source_en.md +++ b/doc/getstarted/build_and_install/build_from_source_en.md @@ -22,6 +22,7 @@ To compile the source code, your computer must be equipped with the following de - **CMake**: CMake >= 3.0 (at least CMake 3.4 on Mac OS X) - **BLAS**: MKL, OpenBlas or ATLAS - **Python**: only support Python 2.7 +- **Go** **Note:** For CUDA 7.0 and CUDA 7.5, GCC 5.0 and up are not supported! For CUDA 8.0, GCC versions later than 5.3 are not supported! @@ -107,6 +108,18 @@ As a simple example, consider the following: sudo apt-get install -y python python-pip python-numpy libpython-dev bison sudo pip install 'protobuf==3.1.0.post1' + # Install Go + # You can follow https://golang.org/doc/install for a detailed explanation. + wget -O go.tgz https://storage.googleapis.com/golang/go1.8.1.linux-amd64.tar.gz && \ + tar -C $HOME -xzf go.tgz && \ + mkdir $HOME/gopath && \ + rm go.tgz + + # Setup environment variables + export GOROOT=$HOME/go + export GOPATH=$HOME/gopath + export PATH=$PATH:$GOROOT/bin + # install cmake 3.4 curl -sSL https://cmake.org/files/v3.4/cmake-3.4.1.tar.gz | tar -xz && \ cd cmake-3.4.1 && ./bootstrap && make -j4 && sudo make install && \ -- GitLab From 65969dad641a95a1ac0f744b11c1166a173d169b Mon Sep 17 00:00:00 2001 From: yangyaming Date: Fri, 16 Jun 2017 16:29:08 +0800 Subject: [PATCH 0674/3256] Add DetectionOutputLayer and MultiBoxLossLayer. --- .../gserver/layers/DetectionOutputLayer.cpp | 154 ++++++++ paddle/gserver/layers/DetectionOutputLayer.h | 81 ++++ paddle/gserver/layers/MultiBoxLossLayer.cpp | 365 ++++++++++++++++++ paddle/gserver/layers/MultiBoxLossLayer.h | 103 +++++ paddle/gserver/tests/CMakeLists.txt | 7 + paddle/gserver/tests/LayerGradUtil.cpp | 25 ++ paddle/gserver/tests/LayerGradUtil.h | 18 +- paddle/gserver/tests/test_DetectionOutput.cpp | 191 +++++++++ paddle/gserver/tests/test_LayerGrad.cpp | 64 +++ proto/ModelConfig.proto | 25 ++ python/paddle/trainer/config_parser.py | 46 +++ .../paddle/trainer_config_helpers/layers.py | 161 ++++++++ 12 files changed, 1239 insertions(+), 1 deletion(-) create mode 100644 paddle/gserver/layers/DetectionOutputLayer.cpp create mode 100644 paddle/gserver/layers/DetectionOutputLayer.h create mode 100644 paddle/gserver/layers/MultiBoxLossLayer.cpp create mode 100644 paddle/gserver/layers/MultiBoxLossLayer.h create mode 100644 paddle/gserver/tests/test_DetectionOutput.cpp diff --git a/paddle/gserver/layers/DetectionOutputLayer.cpp b/paddle/gserver/layers/DetectionOutputLayer.cpp new file mode 100644 index 00000000000..2a4d7f8b5b2 --- /dev/null +++ b/paddle/gserver/layers/DetectionOutputLayer.cpp @@ -0,0 +1,154 @@ +/* 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 "DetectionOutputLayer.h" + +namespace paddle { + +REGISTER_LAYER(detection_output, DetectionOutputLayer); + +bool DetectionOutputLayer::init(const LayerMap& layerMap, + const ParameterMap& parameterMap) { + Layer::init(layerMap, parameterMap); + auto& layerConf = config_.inputs(0).detection_output_conf(); + numClasses_ = layerConf.num_classes(); + inputNum_ = layerConf.input_num(); + nmsThreshold_ = layerConf.nms_threshold(); + confidenceThreshold_ = layerConf.confidence_threshold(); + nmsTopK_ = layerConf.nms_top_k(); + keepTopK_ = layerConf.keep_top_k(); + backgroundId_ = layerConf.background_id(); + return true; +} + +void DetectionOutputLayer::forward(PassType passType) { + Layer::forward(passType); + size_t batchSize = getInputValue(*getLocInputLayer(0))->getHeight(); + + locSizeSum_ = 0; + confSizeSum_ = 0; + for (size_t n = 0; n < inputNum_; ++n) { + const MatrixPtr inLoc = getInputValue(*getLocInputLayer(n)); + const MatrixPtr inConf = getInputValue(*getConfInputLayer(n)); + locSizeSum_ += inLoc->getElementCnt(); + confSizeSum_ += inConf->getElementCnt(); + } + + Matrix::resizeOrCreate(locTmpBuffer_, 1, locSizeSum_, false, useGpu_); + Matrix::resizeOrCreate( + confTmpBuffer_, confSizeSum_ / numClasses_, numClasses_, false, useGpu_); + locBuffer_ = locTmpBuffer_; + confBuffer_ = confTmpBuffer_; + + size_t locOffset = 0; + size_t confOffset = 0; + auto& layerConf = config_.inputs(0).detection_output_conf(); + for (size_t n = 0; n < inputNum_; ++n) { + const MatrixPtr inLoc = getInputValue(*getLocInputLayer(n)); + const MatrixPtr inConf = getInputValue(*getConfInputLayer(n)); + + size_t height = getInput(*getLocInputLayer(n)).getFrameHeight(); + if (!height) height = layerConf.height(); + size_t width = getInput(*getLocInputLayer(n)).getFrameWidth(); + if (!width) width = layerConf.width(); + locOffset += appendWithPermute(*inLoc, + height, + width, + locSizeSum_, + locOffset, + batchSize, + *locBuffer_, + kNCHWToNHWC); + confOffset += appendWithPermute(*inConf, + height, + width, + confSizeSum_, + confOffset, + batchSize, + *confBuffer_, + kNCHWToNHWC); + } + CHECK_EQ(locOffset, locSizeSum_ / batchSize); + CHECK_EQ(confOffset, confSizeSum_ / batchSize); + + MatrixPtr priorValue; + if (useGpu_) { + Matrix::resizeOrCreate(locCpuBuffer_, 1, locSizeSum_, false, false); + Matrix::resizeOrCreate( + confCpuBuffer_, confSizeSum_ / numClasses_, numClasses_, false, false); + MatrixPtr priorTmpValue = getInputValue(*getPriorBoxLayer()); + Matrix::resizeOrCreate( + priorCpuValue_, 1, priorTmpValue->getElementCnt(), false, false); + + locCpuBuffer_->copyFrom(*locTmpBuffer_); + confCpuBuffer_->copyFrom(*confTmpBuffer_); + priorCpuValue_->copyFrom(*priorTmpValue); + + locBuffer_ = locCpuBuffer_; + confBuffer_ = confCpuBuffer_; + priorValue = priorCpuValue_; + } else { + priorValue = getInputValue(*getPriorBoxLayer()); + } + confBuffer_->softmax(*confBuffer_); + + size_t numPriors = priorValue->getElementCnt() / 8; + vector> allDecodedBBoxes; + for (size_t n = 0; n < batchSize; ++n) { + vector decodedBBoxes; + for (size_t i = 0; i < numPriors; ++i) { + size_t priorOffset = i * 8; + size_t locPredOffset = n * numPriors * 4 + i * 4; + vector priorBBoxVec; + getBBoxFromPriorData( + priorValue->getData() + priorOffset, 1, priorBBoxVec); + vector> priorBBoxVar; + getBBoxVarFromPriorData( + priorValue->getData() + priorOffset, 1, priorBBoxVar); + vector locPredData; + for (size_t j = 0; j < 4; ++j) + locPredData.push_back(*(locBuffer_->getData() + locPredOffset + j)); + NormalizedBBox bbox = + decodeBBoxWithVar(priorBBoxVec[0], priorBBoxVar[0], locPredData); + decodedBBoxes.push_back(bbox); + } + allDecodedBBoxes.push_back(decodedBBoxes); + } + + vector>> allIndices; + size_t numKept = getDetectionIndices(confBuffer_->getData(), + numPriors, + numClasses_, + backgroundId_, + batchSize, + confidenceThreshold_, + nmsTopK_, + nmsThreshold_, + keepTopK_, + allDecodedBBoxes, + &allIndices); + + resetOutput(numKept, 7); + MatrixPtr outV = getOutputValue(); + getDetectionOutput(confBuffer_->getData(), + numKept, + numPriors, + numClasses_, + batchSize, + allIndices, + allDecodedBBoxes, + *outV); +} + +} // namespace paddle diff --git a/paddle/gserver/layers/DetectionOutputLayer.h b/paddle/gserver/layers/DetectionOutputLayer.h new file mode 100644 index 00000000000..38271cb0540 --- /dev/null +++ b/paddle/gserver/layers/DetectionOutputLayer.h @@ -0,0 +1,81 @@ +/* Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserve. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. */ + +#pragma once + +#include +#include +#include "DetectionUtil.h" +#include "Layer.h" + +using std::vector; +using std::map; +using std::pair; + +namespace paddle { + +/** + * The detection output layer for a SSD detection task. This layer apply the + * Non-maximum suppression to the all predicted bounding box and keep the + * Top-K bounding boxes. + * - Input: This layer need three input layers: This first input layer + * is the priorbox layer. The rest two input layers are convolution + * layers for generating bbox location offset and the classification + * confidence. + * - Output: The predict bounding box location. + */ + +class DetectionOutputLayer : public Layer { +public: + explicit DetectionOutputLayer(const LayerConfig& config) : Layer(config) {} + + bool init(const LayerMap& layerMap, const ParameterMap& parameterMap); + + void forward(PassType passType); + + void backward(const UpdateCallback& callback = nullptr) {} + +protected: + inline LayerPtr getPriorBoxLayer() { return inputLayers_[0]; } + + inline LayerPtr getLocInputLayer(size_t index) { + return inputLayers_[1 + index]; + } + + inline LayerPtr getConfInputLayer(size_t index) { + return inputLayers_[1 + inputNum_ + index]; + } + +private: + size_t numClasses_; // number of classes + size_t inputNum_; // number of input layers + real nmsThreshold_; + real confidenceThreshold_; + size_t nmsTopK_; + size_t keepTopK_; + size_t backgroundId_; + + size_t locSizeSum_; + size_t confSizeSum_; + + MatrixPtr locBuffer_; + MatrixPtr confBuffer_; + MatrixPtr locTmpBuffer_; + MatrixPtr confTmpBuffer_; + MatrixPtr priorCpuValue_; + MatrixPtr locCpuBuffer_; + MatrixPtr confCpuBuffer_; +}; + +} // namespace paddle diff --git a/paddle/gserver/layers/MultiBoxLossLayer.cpp b/paddle/gserver/layers/MultiBoxLossLayer.cpp new file mode 100644 index 00000000000..27a2cc3fa4a --- /dev/null +++ b/paddle/gserver/layers/MultiBoxLossLayer.cpp @@ -0,0 +1,365 @@ +/* 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 "MultiBoxLossLayer.h" +#include +#include +#include "DataLayer.h" + +using std::vector; +using std::map; +using std::pair; + +namespace paddle { + +REGISTER_LAYER(multibox_loss, MultiBoxLossLayer); + +bool MultiBoxLossLayer::init(const LayerMap& layerMap, + const ParameterMap& parameterMap) { + Layer::init(layerMap, parameterMap); + + auto layerConf = config_.inputs(0).multibox_loss_conf(); + numClasses_ = layerConf.num_classes(); + inputNum_ = layerConf.input_num(); + overlapThreshold_ = layerConf.overlap_threshold(); + negPosRatio_ = layerConf.neg_pos_ratio(); + negOverlap_ = layerConf.neg_overlap(); + backgroundId_ = layerConf.background_id(); + return true; +} + +void MultiBoxLossLayer::forward(PassType passType) { + Layer::forward(passType); + size_t batchSize = getInputValue(*getLocInputLayer(0))->getHeight(); + resetOutput(batchSize, 1); + + // all location data and confidence score data + locSizeSum_ = 0; + confSizeSum_ = 0; + for (size_t n = 0; n < inputNum_; ++n) { + const MatrixPtr inLoc = getInputValue(*getLocInputLayer(n)); + const MatrixPtr inConf = getInputValue(*getConfInputLayer(n)); + locSizeSum_ += inLoc->getElementCnt(); + confSizeSum_ += inConf->getElementCnt(); + } + + // locBuffer layout: + // | xmin1 | ymin1 | xmax1 | ymax1 | xmin2 ...... + Matrix::resizeOrCreate(locTmpBuffer_, 1, locSizeSum_, false, useGpu_); + locBuffer_ = locTmpBuffer_; + + // confBuffer layout: + // | class1 score | class2 score | ... |classN score | class1 score | ...... + Matrix::resizeOrCreate(confTmpBuffer_, 1, confSizeSum_, false, useGpu_); + confBuffer_ = confTmpBuffer_; + + // concate location data and confidence score data + size_t locOffset = 0; + size_t confOffset = 0; + auto& layerConf = config_.inputs(0).multibox_loss_conf(); + for (size_t n = 0; n < inputNum_; ++n) { + const MatrixPtr inLoc = getInputValue(*getLocInputLayer(n)); + const MatrixPtr inConf = getInputValue(*getConfInputLayer(n)); + size_t height = getInput(*getLocInputLayer(n)).getFrameHeight(); + if (!height) height = layerConf.height(); + size_t width = getInput(*getLocInputLayer(n)).getFrameWidth(); + if (!width) width = layerConf.width(); + locOffset += appendWithPermute(*inLoc, + height, + width, + locSizeSum_, + locOffset, + batchSize, + *locBuffer_, + kNCHWToNHWC); + confOffset += appendWithPermute(*inConf, + height, + width, + confSizeSum_, + confOffset, + batchSize, + *confBuffer_, + kNCHWToNHWC); + } + CHECK_EQ(locOffset, locSizeSum_ / batchSize); + CHECK_EQ(confOffset, confSizeSum_ / batchSize); + + // priorValue layout: + // | xmin1 | ymin1 | xmax1 | ymax1 | xmin1Var | ymin1Var | xmax1Var | ymax1Var + // | xmin2 | ...... + MatrixPtr priorValue; + + // labelValue layout: + // | class1_1 | xmin1_1 | ymin1_1 | xmax1_1 | ymax1_1 | difficult1_1 | ...... + MatrixPtr labelValue; + + // Copy data from GPU to CPU if use GPU + if (useGpu_) { + Matrix::resizeOrCreate(locCpuBuffer_, 1, locSizeSum_, false, false); + Matrix::resizeOrCreate(confCpuBuffer_, 1, confSizeSum_, false, false); + MatrixPtr priorTmpValue = getInputValue(*getPriorBoxLayer()); + Matrix::resizeOrCreate( + priorCpuValue_, 1, priorTmpValue->getElementCnt(), false, false); + MatrixPtr labelTmpValue = getInputValue(*getLabelLayer()); + Matrix::resizeOrCreate(labelCpuValue_, + labelTmpValue->getHeight(), + labelTmpValue->getWidth(), + false, + false); + + locCpuBuffer_->copyFrom(*locTmpBuffer_); + confCpuBuffer_->copyFrom(*confTmpBuffer_); + priorCpuValue_->copyFrom(*priorTmpValue); + labelCpuValue_->copyFrom(*labelTmpValue); + + locBuffer_ = locCpuBuffer_; + confBuffer_ = confCpuBuffer_; + priorValue = priorCpuValue_; + labelValue = labelCpuValue_; + } else { + priorValue = getInputValue(*getPriorBoxLayer()); + labelValue = getInputValue(*getLabelLayer()); + } + + // Get max scores for each prior bbox. Used in negative mining + vector> allMaxConfScore; + numPriors_ = priorValue->getElementCnt() / 8; + getMaxConfidenceScores(confBuffer_->getData(), + batchSize, + numPriors_, + numClasses_, + backgroundId_, + &allMaxConfScore); + + // Match prior bbox to groundtruth bbox + Argument label = getInput(*getLabelLayer()); + const int* labelIndex = label.sequenceStartPositions->getData(false); + size_t seqNum = label.getNumSequences(); + numMatches_ = 0; + numNegs_ = 0; + allMatchIndices_.clear(); + allNegIndices_.clear(); + + pair retPair = generateMatchIndices(*priorValue, + numPriors_, + *labelValue, + labelIndex, + seqNum, + allMaxConfScore, + batchSize, + overlapThreshold_, + negOverlap_, + negPosRatio_, + &allMatchIndices_, + &allNegIndices_); + numMatches_ = retPair.first; + numNegs_ = retPair.second; + + // BBox location L1 smooth loss + locLoss_ = 0.0; + if (numMatches_ >= 1) { + size_t count = 0; + MatrixPtr locLossOutput; + Matrix::resizeOrCreate(locLossOutput, numMatches_ * 4, 1, false, false); + Matrix::resizeOrCreate(locGTData_, numMatches_ * 4, 1, false, false); + Matrix::resizeOrCreate(locDiff_, numMatches_ * 4, 1, false, false); + locDiff_->zeroMem(); + vector locGTData; + + for (size_t n = 0; n < batchSize; ++n) { + for (size_t i = 0; i < numPriors_; ++i) { + if (allMatchIndices_[n][i] == -1) continue; // match none + size_t locOffset = + n * (locBuffer_->getElementCnt() / batchSize) + i * 4; + locDiff_->getData()[count++] = (locBuffer_->getData() + locOffset)[0]; + locDiff_->getData()[count++] = (locBuffer_->getData() + locOffset)[1]; + locDiff_->getData()[count++] = (locBuffer_->getData() + locOffset)[2]; + locDiff_->getData()[count++] = (locBuffer_->getData() + locOffset)[3]; + + const int gtIdx = allMatchIndices_[n][i]; + size_t priorOffset = i * 8; + vector priorBBoxVec; + getBBoxFromPriorData( + priorValue->getData() + priorOffset, 1, priorBBoxVec); + vector> priorBBoxVar; + getBBoxVarFromPriorData( + priorValue->getData() + priorOffset, 1, priorBBoxVar); + size_t labelOffset = (labelIndex[n] + gtIdx) * 6; + vector gtBBoxVec; + getBBoxFromLabelData(labelValue->getData() + labelOffset, 1, gtBBoxVec); + vector gtEncode; + encodeBBoxWithVar( + priorBBoxVec[0], priorBBoxVar[0], gtBBoxVec[0], gtEncode); + locGTData.insert(locGTData.end(), gtEncode.begin(), gtEncode.end()); + } + } + locGTData_->copyFrom(&locGTData[0], numMatches_ * 4); + locLossOutput->smoothL1(*locDiff_, *locGTData_, 0.0); + locLoss_ = locLossOutput->getSum() / numMatches_; + } + + // BBox confidence softmax loss + confLoss_ = 0; + numConf_ = numMatches_ + numNegs_; + if (numConf_ >= 1) { + Matrix::resizeOrCreate(confProb_, numConf_, numClasses_, false, false); + IVector::resizeOrCreate(confGTData_, numConf_, false); + confProb_->zeroMem(); + size_t count = 0; + + vector confPredData; + for (size_t n = 0; n < batchSize; ++n) { + for (size_t i = 0; i < numPriors_; ++i) { + if (allMatchIndices_[n][i] == -1) continue; + size_t labelOffset = (labelIndex[n] + allMatchIndices_[n][i]) * 6; + const int gtLabel = (labelValue->getData() + labelOffset)[0]; + confGTData_->getData()[count] = gtLabel; + size_t confOffset = n * numPriors_ * numClasses_ + i * numClasses_; + for (size_t j = 0; j < numClasses_; ++j) { + confProb_->getData()[count * numClasses_ + j] = + (confBuffer_->getData() + confOffset)[j]; + confPredData.push_back((confBuffer_->getData() + confOffset)[j]); + } + ++count; + } + // Negative mining samples + for (size_t i = 0; i < allNegIndices_[n].size(); ++i) { + confGTData_->getData()[count] = backgroundId_; + size_t confOffset = + n * numPriors_ * numClasses_ + allNegIndices_[n][i] * numClasses_; + for (size_t j = 0; j < numClasses_; ++j) { + confProb_->getData()[count * numClasses_ + j] = + (confBuffer_->getData() + confOffset)[j]; + confPredData.push_back((confBuffer_->getData() + confOffset)[j]); + } + count++; + } + } + confProb_->softmax(*confProb_); + MatrixPtr confLossOutput; + Matrix::resizeOrCreate(confLossOutput, numConf_, 1, false, false); + confLossOutput->oneHotCrossEntropy(*confProb_, *confGTData_); + confLoss_ = confLossOutput->getSum() / numMatches_; + } + real loss = locLoss_ + confLoss_; + MatrixPtr outV = getOutputValue(); + vector tmp(batchSize, loss); + outV->copyFrom(&tmp[0], batchSize); +} + +void MultiBoxLossLayer::backward(const UpdateCallback& callback) { + size_t batchSize = getInputValue(*getLocInputLayer(0))->getHeight(); + locBuffer_->zeroMem(); + confBuffer_->zeroMem(); + + // Back propagate on location prediction + if (numMatches_ >= 1) { + MatrixPtr locDiffBuffer; + Matrix::resizeOrCreate(locDiffBuffer, numMatches_ * 4, 1, false, false); + locDiffBuffer->smoothL1Bp(*locDiff_, *locGTData_, 0.0); + locDiff_->copyFrom(*locDiffBuffer); + // scale gradient + for (size_t i = 0; i < numMatches_ * 4; ++i) + locDiff_->getData()[i] *= (1. / numMatches_); + // Copy gradient back + size_t count = 0; + for (size_t n = 0; n < batchSize; ++n) + for (size_t i = 0; i < numPriors_; ++i) { + if (allMatchIndices_[n][i] == -1) continue; + real* locDiffData = locBuffer_->getData() + n * numPriors_ * 4 + i * 4; + locDiffData[0] = (locDiff_->getData() + count * 4)[0]; + locDiffData[1] = (locDiff_->getData() + count * 4)[1]; + locDiffData[2] = (locDiff_->getData() + count * 4)[2]; + locDiffData[3] = (locDiff_->getData() + count * 4)[3]; + ++count; + } + CHECK_EQ(count, numMatches_); + } + + if (numConf_ >= 1) { + for (size_t i = 0; i < numConf_; ++i) + confProb_->getData()[i * numClasses_ + confGTData_->getData()[i]] -= 1; + for (size_t i = 0; i < numConf_ * numClasses_; ++i) + confProb_->getData()[i] *= (1. / numMatches_); + size_t count = 0; + for (size_t n = 0; n < batchSize; ++n) { + for (size_t i = 0; i < numPriors_; ++i) { + if (allMatchIndices_[n][i] == -1) continue; + real* confDiffData = confBuffer_->getData() + + n * numPriors_ * numClasses_ + i * numClasses_; + for (size_t j = 0; j < numClasses_; ++j) + confDiffData[j] = (confProb_->getData() + count * numClasses_)[j]; + ++count; + } + for (size_t i = 0; i < allNegIndices_[n].size(); ++i) { + int idx = allNegIndices_[n][i]; + real* confDiffData = confBuffer_->getData() + + n * numPriors_ * numClasses_ + idx * numClasses_; + for (size_t j = 0; j < numClasses_; ++j) + confDiffData[j] = (confProb_->getData() + count * numClasses_)[j]; + ++count; + } + } + CHECK_EQ(count, numConf_); + } + if (useGpu_) { + locTmpBuffer_->copyFrom(*locCpuBuffer_); + confTmpBuffer_->copyFrom(*confCpuBuffer_); + locBuffer_ = locTmpBuffer_; + confBuffer_ = confTmpBuffer_; + } + // copy back + size_t locOffset = 0; + size_t confOffset = 0; + auto layerConf = config_.inputs(0).multibox_loss_conf(); + for (size_t n = 0; n < inputNum_; ++n) { + const MatrixPtr inLocG = getInputGrad(*getLocInputLayer(n)); + const MatrixPtr inConfG = getInputGrad(*getConfInputLayer(n)); + size_t height = getInput(*getLocInputLayer(n)).getFrameHeight(); + if (!height) height = layerConf.height(); + size_t width = getInput(*getLocInputLayer(n)).getFrameWidth(); + if (!width) width = layerConf.width(); + + // NHWC to NCHW + MatrixPtr locGBuffer; + Matrix::resizeOrCreate( + locGBuffer, inLocG->getHeight(), inLocG->getWidth(), false, useGpu_); + MatrixPtr confGBuffer; + Matrix::resizeOrCreate( + confGBuffer, inConfG->getHeight(), inConfG->getWidth(), false, useGpu_); + + locOffset += decomposeWithPermute(*locBuffer_, + height, + width, + locSizeSum_, + locOffset, + batchSize, + *locGBuffer, + kNHWCToNCHW); + inLocG->add(*locGBuffer); + confOffset += decomposeWithPermute(*confBuffer_, + height, + width, + confSizeSum_, + confOffset, + batchSize, + *confGBuffer, + kNHWCToNCHW); + inConfG->add(*confGBuffer); + } + CHECK_EQ(locOffset, locSizeSum_ / batchSize); + CHECK_EQ(confOffset, confSizeSum_ / batchSize); +} + +} // namespace paddle diff --git a/paddle/gserver/layers/MultiBoxLossLayer.h b/paddle/gserver/layers/MultiBoxLossLayer.h new file mode 100644 index 00000000000..9767fed7f1c --- /dev/null +++ b/paddle/gserver/layers/MultiBoxLossLayer.h @@ -0,0 +1,103 @@ +/* copyright (c) 2016 paddlepaddle authors. all rights reserve. + +licensed under the apache license, version 2.0 (the "license"); +you may not use this file except in compliance with the license. +you may obtain a copy of the license at + + http://www.apache.org/licenses/license-2.0 + +unless required by applicable law or agreed to in writing, software +distributed under the license is distributed on an "as is" basis, +without warranties or conditions of any kind, either express or implied. +see the license for the specific language governing permissions and +limitations under the license. */ + +#pragma once + +#include +#include "CostLayer.h" +#include "DataLayer.h" +#include "DetectionUtil.h" +#include "Layer.h" + +using std::vector; +using std::pair; + +namespace paddle { + +/** + * The multibox loss layer for a SSD detection task. + * The loss is composed by the location loss and the confidence loss. + * The location loss is a smooth L1 loss and the confidence loss is + * a softmax loss. + * - Input: This layer need four input layers: This first input layer + * is the priorbox layer and the second layer is a label layer. + * The rest two input layers are convolution layers for generating + * bbox location offset and the classification confidence. + * - Output: The Single Shot Multibox Detection loss value. + * Reference: + * Wei Liu, Dragomir Anguelov, Dumitru Erhan, Christian Szegedy, Scott Reed, + * Cheng-Yang Fu, Alexander C. Berg. SSD: Single Shot MultiBox Detector + */ + +class MultiBoxLossLayer : public CostLayer { +public: + explicit MultiBoxLossLayer(const LayerConfig& config) : CostLayer(config) {} + + bool init(const LayerMap& layerMap, const ParameterMap& parameterMap); + + void forward(PassType passType); + + void backward(const UpdateCallback& callback = nullptr); + + void forwardImp(Matrix& output, Argument& label, Matrix& cost) {} + + void backwardImp(Matrix& outputValue, Argument& label, Matrix& outputGrad) {} + +protected: + inline LayerPtr getPriorBoxLayer() { return inputLayers_[0]; } + inline LayerPtr getLabelLayer() { return inputLayers_[1]; } + inline LayerPtr getLocInputLayer(size_t index) { + return inputLayers_[2 + index]; + } + inline LayerPtr getConfInputLayer(size_t index) { + return inputLayers_[2 + inputNum_ + index]; + } + +protected: + size_t numClasses_; + real overlapThreshold_; + real negPosRatio_; + real negOverlap_; + size_t inputNum_; + size_t backgroundId_; + + real locLoss_; + real confLoss_; + + size_t numPriors_; + size_t numMatches_; + size_t numNegs_; + size_t numConf_; + size_t locSizeSum_; + size_t confSizeSum_; + + vector> allMatchIndices_; + vector> allNegIndices_; + MatrixPtr locGTData_; + IVectorPtr confGTData_; + + MatrixPtr locBuffer_; + MatrixPtr confBuffer_; + MatrixPtr locDiff_; + MatrixPtr confProb_; + + MatrixPtr labelCpuValue_; + MatrixPtr priorCpuValue_; + MatrixPtr locCpuBuffer_; + MatrixPtr confCpuBuffer_; + MatrixPtr locTmpBuffer_; + MatrixPtr confTmpBuffer_; +}; + +} // namespace paddle diff --git a/paddle/gserver/tests/CMakeLists.txt b/paddle/gserver/tests/CMakeLists.txt index 3c4128b5b8a..92f6cbcfe5a 100644 --- a/paddle/gserver/tests/CMakeLists.txt +++ b/paddle/gserver/tests/CMakeLists.txt @@ -45,6 +45,13 @@ add_unittest_without_exec(test_PriorBox add_test(NAME test_PriorBox COMMAND test_PriorBox) +################# test_DetectionOutput ####################### +add_unittest_without_exec(test_DetectionOutput + test_DetectionOutput.cpp + LayerGradUtil.cpp) + +add_test(NAME test_DetectionOutput + COMMAND test_DetectionOutput) ################# test_ConvUnify ####################### add_unittest_without_exec(test_ConvUnify test_ConvUnify.cpp diff --git a/paddle/gserver/tests/LayerGradUtil.cpp b/paddle/gserver/tests/LayerGradUtil.cpp index a0b1cd471dd..e3591ba4df8 100644 --- a/paddle/gserver/tests/LayerGradUtil.cpp +++ b/paddle/gserver/tests/LayerGradUtil.cpp @@ -387,6 +387,31 @@ void initDataLayer(TestConfig testConf, data.value->sigmoid(*data.value); data.grad->zeroMem(); break; + case INPUT_SELF_DEFINE_DATA: { + size_t height = testConf.inputDefs[i].selfDefinedData->getHeight(); + size_t width = testConf.inputDefs[i].selfDefinedData->getWidth(); + CHECK_GT(static_cast(height), 0); + CHECK_GT(static_cast(width), 0); + data.value = Matrix::create(height, width, false, useGpu); + data.grad = Matrix::create(height, width, false, useGpu); + data.value->copyFrom(*testConf.inputDefs[i].selfDefinedData); + data.grad->zeroMem(); + + const std::vector& labelSeqStartPositions = + testConf.inputDefs[i].labelSeqStartPositions; + if (labelSeqStartPositions.size() != 0) { + CHECK(!sequenceStartPositions); + CHECK_GE(static_cast(labelSeqStartPositions.size()), 2); + + sequenceStartPositions = + ICpuGpuVector::create(labelSeqStartPositions.size(), useGpu); + sequenceStartPositions->copyFrom(labelSeqStartPositions.data(), + labelSeqStartPositions.size(), + useGpu); + data.sequenceStartPositions = sequenceStartPositions; + } + break; + } default: LOG(FATAL) << " unknown inputType "; return; diff --git a/paddle/gserver/tests/LayerGradUtil.h b/paddle/gserver/tests/LayerGradUtil.h index 9f68eb64d0b..18a6525a145 100644 --- a/paddle/gserver/tests/LayerGradUtil.h +++ b/paddle/gserver/tests/LayerGradUtil.h @@ -31,7 +31,8 @@ enum InputType { INPUT_SEQUENCE_LABEL, INPUT_SPARSE_NON_VALUE_DATA, INPUT_SPARSE_FLOAT_VALUE_DATA, - INPUT_DENSE_DIM_DATA, // using sequence length to init dense data + INPUT_DENSE_DIM_DATA, // using sequence length to init dense data + INPUT_SELF_DEFINE_DATA, // support customizing for input value }; struct ParaSparse { @@ -66,6 +67,7 @@ struct InputDef { bool isStatic; std::vector labelInitValue; std::vector labelSeqStartPositions; + MatrixPtr selfDefinedData; InputDef(InputType type, string nameIn, size_t dimIn, size_t sizeIn) { inputType = type; @@ -76,6 +78,20 @@ struct InputDef { isStatic = false; } + InputDef(InputType type, + string nameIn, + MatrixPtr selfDefinedData, + std::vector selfDefinedSeqStartPos = {}) + : labelSeqStartPositions(selfDefinedSeqStartPos), + selfDefinedData(selfDefinedData) { + inputType = type; + name = nameIn; + dim = 0; + sparse = {""}; + paraSize = 0; + isStatic = false; + } + InputDef(InputType type, string nameIn, size_t dimIn, diff --git a/paddle/gserver/tests/test_DetectionOutput.cpp b/paddle/gserver/tests/test_DetectionOutput.cpp new file mode 100644 index 00000000000..8ec7a284502 --- /dev/null +++ b/paddle/gserver/tests/test_DetectionOutput.cpp @@ -0,0 +1,191 @@ +/* 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 + +#include "LayerGradUtil.h" +#include "paddle/testing/TestUtil.h" + +using namespace paddle; // NOLINT +using namespace std; // NOLINT + +// Do one forward pass of priorBox layer and check to see if its output +// matches the given result +void doOneDetectionOutputTest(MatrixPtr& inputLoc, + MatrixPtr& inputConf, + MatrixPtr& inputPriorBox, + size_t feature_map_width, + size_t feature_map_height, + real nms_threshold, + bool use_gpu, + MatrixPtr& result) { + // Setting up the detection output layer + TestConfig configt; + configt.layerConfig.set_type("detection_output"); + LayerInputConfig* input = configt.layerConfig.add_inputs(); + configt.layerConfig.add_inputs(); + configt.layerConfig.add_inputs(); + + DetectionOutputConfig* detOutput = input->mutable_detection_output_conf(); + detOutput->set_width(feature_map_width); + detOutput->set_height(feature_map_height); + detOutput->set_nms_threshold(nms_threshold); + detOutput->set_num_classes(2); + detOutput->set_nms_top_k(20); + detOutput->set_keep_top_k(10); + detOutput->set_background_id(0); + detOutput->set_confidence_threshold(0.01); + detOutput->set_input_num(1); + configt.inputDefs.push_back({INPUT_DATA_TARGET, "priorbox", 32, 0}); + configt.inputDefs.push_back({INPUT_DATA, "input_loc", 16, 0}); + configt.inputDefs.push_back({INPUT_DATA, "input_conf", 8, 0}); + + // data layer initialize + std::vector dataLayers; + LayerMap layerMap; + vector datas; + initDataLayer( + configt, &dataLayers, &datas, &layerMap, "priorbox", 1, false, use_gpu); + + dataLayers[0]->getOutputValue()->copyFrom(*inputPriorBox); + dataLayers[1]->getOutputValue()->copyFrom(*inputLoc); + dataLayers[2]->getOutputValue()->copyFrom(*inputConf); + + // test layer initialize + std::vector parameters; + LayerPtr detectionOutputLayer; + initTestLayer(configt, &layerMap, ¶meters, &detectionOutputLayer); + detectionOutputLayer->forward(PASS_GC); + checkMatrixEqual(detectionOutputLayer->getOutputValue(), result); +} + +TEST(Layer, detectionOutputLayerFwd) { + bool useGpu = false; + // CPU case 1. + MatrixPtr inputLoc; + MatrixPtr inputConf; + MatrixPtr inputPriorBox; + MatrixPtr result, result2, result3, result4; + real nmsTreshold = 0.01; + real inputLocData[] = {0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1}; + real inputConfData[] = {0.1, 0.9, 0.2, 0.8, 0.3, 0.7, 0.4, 0.6}; + real inputPriorBoxData[] = {0.1, 0.1, 0.5, 0.5, 0.1, 0.1, 0.2, 0.2, + 0.2, 0.2, 0.6, 0.6, 0.1, 0.1, 0.2, 0.2, + 0.3, 0.3, 0.7, 0.7, 0.1, 0.1, 0.2, 0.2, + 0.4, 0.4, 0.8, 0.8, 0.1, 0.1, 0.2, 0.2}; + real resultData[] = { + 0, 1, 0.68997443, 0.099959746, 0.099959746, 0.50804031, 0.50804031}; + inputLoc = Matrix::create(1, 16, false, useGpu); + inputConf = Matrix::create(1, 8, false, useGpu); + inputPriorBox = Matrix::create(1, 32, false, useGpu); + result = Matrix::create(1, 7, false, useGpu); + inputLoc->setData(inputLocData); + inputConf->setData(inputConfData); + inputPriorBox->setData(inputPriorBoxData); + result->setData(resultData); + doOneDetectionOutputTest(inputLoc, + inputConf, + inputPriorBox, + /* feature_map_width */ 1, + /* feature_map_height */ 1, + nmsTreshold, + useGpu, + result); + + // CPU case 2. + nmsTreshold = 0.2; + result2 = Matrix::create(2, 7, false, useGpu); + real resultData2[] = {0, + 1, + 0.68997443, + 0.099959746, + 0.099959746, + 0.50804031, + 0.50804031, + 0, + 1, + 0.59868765, + 0.29995975, + 0.29995975, + 0.70804024, + 0.70804024}; + result2->setData(resultData2); + doOneDetectionOutputTest(inputLoc, + inputConf, + inputPriorBox, + /* feature_map_width */ 1, + /* feature_map_height */ 1, + nmsTreshold, + useGpu, + result2); + +#ifndef PADDLE_ONLY_CPU + // GPU case 1. + useGpu = true; + inputLoc = Matrix::create(1, 16, false, useGpu); + inputConf = Matrix::create(1, 8, false, useGpu); + inputPriorBox = Matrix::create(1, 32, false, useGpu); + inputLoc->copyFrom(inputLocData, 16); + inputConf->copyFrom(inputConfData, 8); + inputPriorBox->copyFrom(inputPriorBoxData, 32); + + nmsTreshold = 0.01; + result3 = Matrix::create(1, 7, false, useGpu); + result3->copyFrom(resultData, 7); + doOneDetectionOutputTest(inputLoc, + inputConf, + inputPriorBox, + /* feature_map_width */ 1, + /* feature_map_height */ 1, + nmsTreshold, + useGpu, + result3); + + // GPU case 2. + nmsTreshold = 0.2; + result4 = Matrix::create(2, 7, false, useGpu); + result4->copyFrom(resultData2, 14); + doOneDetectionOutputTest(inputLoc, + inputConf, + inputPriorBox, + /* feature_map_width */ 1, + /* feature_map_height */ 1, + nmsTreshold, + useGpu, + result4); +#endif +} + +int main(int argc, char** argv) { + testing::InitGoogleTest(&argc, argv); + initMain(argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/paddle/gserver/tests/test_LayerGrad.cpp b/paddle/gserver/tests/test_LayerGrad.cpp index 6adffcf53b7..9c79bd19ee0 100644 --- a/paddle/gserver/tests/test_LayerGrad.cpp +++ b/paddle/gserver/tests/test_LayerGrad.cpp @@ -1689,6 +1689,70 @@ TEST(Layer, smooth_l1) { } } +TEST(Layer, multibox_loss) { + TestConfig config; + config.layerConfig.set_type("multibox_loss"); + config.biasSize = 0; + LayerInputConfig* input = config.layerConfig.add_inputs(); + MultiBoxLossConfig* multiboxLoss = input->mutable_multibox_loss_conf(); + multiboxLoss->set_num_classes(21); + multiboxLoss->set_input_num(1); + multiboxLoss->set_overlap_threshold(0.5); + multiboxLoss->set_neg_pos_ratio(3); + multiboxLoss->set_neg_overlap(0.5); + multiboxLoss->set_background_id(0); + multiboxLoss->set_height(3); + multiboxLoss->set_width(3); + + size_t gtNum = 1; + MatrixPtr labelValue = Matrix::create(gtNum, 6, false, false); + labelValue->randomizeUniform(); + labelValue->add(-0.5); + labelValue->sigmoid(*labelValue); + real* labelData = labelValue->getData(); + size_t labelWidth = labelValue->getWidth(); + for (size_t i = 0; i < gtNum; ++i) { + *(labelData + i * labelWidth) = std::rand() % 20 + 1; + *(labelData + i * labelWidth + 1) = 0.400259; + *(labelData + i * labelWidth + 2) = 0.377857; + *(labelData + i * labelWidth + 3) = 0.525712; + *(labelData + i * labelWidth + 4) = 0.519368; + } + vector seqStartPositions(gtNum + 1, 0); + for (size_t i = 1; i <= gtNum; ++i) { + seqStartPositions[i] = i; + } + + // Ensure at lease one matched bbox + MatrixPtr priorValue = Matrix::create(1, 72, false, false); + priorValue->randomizeUniform(); + priorValue->add(-0.5); + priorValue->sigmoid(*priorValue); + real* priorData = priorValue->getData(); + *(priorData) = 0.424811; + *(priorData + 1) = 0.397059; + *(priorData + 2) = 0.538905; + *(priorData + 3) = 0.447091; + *(priorData + 4) = 0.425720; + *(priorData + 5) = 0.515228; + *(priorData + 6) = 0.519452; + *(priorData + 7) = 0.591065; + + config.inputDefs.push_back( + {INPUT_SELF_DEFINE_DATA, "priorbox", priorValue, {}}); + config.inputDefs.push_back( + {INPUT_SELF_DEFINE_DATA, "label", labelValue, seqStartPositions}); + config.inputDefs.push_back({INPUT_DATA, "locPred", 36, 0}); + config.inputDefs.push_back({INPUT_DATA, "confPred", 189, 0}); + config.layerConfig.add_inputs(); + config.layerConfig.add_inputs(); + config.layerConfig.add_inputs(); + + for (auto useGpu : {false, true}) { + testLayerGrad(config, "multibox_loss", 1, false, useGpu, false); + } +} + TEST(Layer, TransLayer) { TestConfig config; const int height = 128; diff --git a/proto/ModelConfig.proto b/proto/ModelConfig.proto index 29270829bbc..3d01c23bf96 100644 --- a/proto/ModelConfig.proto +++ b/proto/ModelConfig.proto @@ -266,6 +266,29 @@ message PadConfig { repeated uint32 pad_w = 4; } +message MultiBoxLossConfig { + required uint32 num_classes = 1; + required float overlap_threshold = 2; + required float neg_pos_ratio = 3; + required float neg_overlap = 4; + required uint32 background_id = 5; + required uint32 input_num = 6; + optional uint32 height = 7 [default = 1]; + optional uint32 width = 8 [default = 1]; +} + +message DetectionOutputConfig { + required uint32 num_classes = 1; + required float nms_threshold = 2; + required uint32 nms_top_k = 3; + required uint32 background_id = 4; + required uint32 input_num = 5; + required uint32 keep_top_k = 6; + required float confidence_threshold = 7; + optional uint32 height = 8 [default = 1]; + optional uint32 width = 9 [default = 1]; +} + message LayerInputConfig { required string input_layer_name = 1; optional string input_parameter_name = 2; @@ -284,6 +307,8 @@ message LayerInputConfig { optional PriorBoxConfig priorbox_conf = 13; optional PadConfig pad_conf = 14; optional RowConvConfig row_conv_conf = 15; + optional MultiBoxLossConfig multibox_loss_conf = 16; + optional DetectionOutputConfig detection_output_conf = 17; } message LayerConfig { diff --git a/python/paddle/trainer/config_parser.py b/python/paddle/trainer/config_parser.py index fc2e3bbcde0..c46b335d992 100644 --- a/python/paddle/trainer/config_parser.py +++ b/python/paddle/trainer/config_parser.py @@ -1676,6 +1676,52 @@ class PriorBoxLayer(LayerBase): self.config.size = size +@config_layer('multibox_loss') +class MultiBoxLossLayer(LayerBase): + def __init__(self, name, inputs, input_num, num_classes, overlap_threshold, + neg_pos_ratio, neg_overlap, background_id): + super(MultiBoxLossLayer, self).__init__(name, 'multibox_loss', 0, + inputs) + config_assert( + len(inputs) == (input_num * 2 + 2), + 'MultiBoxLossLayer does not have enough inputs') + config_assert(num_classes > background_id, + 'Classes number must greater than background ID') + self.config.inputs[0].multibox_loss_conf.num_classes = num_classes + self.config.inputs[ + 0].multibox_loss_conf.overlap_threshold = overlap_threshold + self.config.inputs[0].multibox_loss_conf.neg_pos_ratio = neg_pos_ratio + self.config.inputs[0].multibox_loss_conf.neg_overlap = neg_overlap + self.config.inputs[0].multibox_loss_conf.background_id = background_id + self.config.inputs[0].multibox_loss_conf.input_num = input_num + self.config.size = 1 + + +@config_layer('detection_output') +class DetectionOutputLayer(LayerBase): + def __init__(self, name, inputs, size, input_num, num_classes, + nms_threshold, nms_top_k, keep_top_k, confidence_threshold, + background_id): + super(DetectionOutputLayer, self).__init__(name, 'detection_output', 0, + inputs) + config_assert( + len(inputs) == (input_num * 2 + 1), + 'DetectionOutputLayer does not have enough inputs') + config_assert(num_classes > background_id, + 'Classes number must greater than background ID') + self.config.inputs[0].detection_output_conf.num_classes = num_classes + self.config.inputs[ + 0].detection_output_conf.nms_threshold = nms_threshold + self.config.inputs[0].detection_output_conf.nms_top_k = nms_top_k + self.config.inputs[0].detection_output_conf.keep_top_k = keep_top_k + self.config.inputs[ + 0].detection_output_conf.confidence_threshold = confidence_threshold + self.config.inputs[ + 0].detection_output_conf.background_id = background_id + self.config.inputs[0].detection_output_conf.input_num = input_num + self.config.size = size + + @config_layer('data') class DataLayer(LayerBase): def __init__(self, name, size, height=None, width=None, device=None): diff --git a/python/paddle/trainer_config_helpers/layers.py b/python/paddle/trainer_config_helpers/layers.py index 2d8ddbb9007..770559dc770 100755 --- a/python/paddle/trainer_config_helpers/layers.py +++ b/python/paddle/trainer_config_helpers/layers.py @@ -115,6 +115,8 @@ __all__ = [ 'print_layer', 'priorbox_layer', 'cross_channel_norm_layer', + 'multibox_loss_layer', + 'detection_output_layer', 'spp_layer', 'pad_layer', 'eos_layer', @@ -195,6 +197,8 @@ class LayerType(object): PRINT_LAYER = 'print' PRIORBOX_LAYER = 'priorbox' + MULTIBOX_LOSS_LAYER = 'multibox_loss' + DETECTION_OUTPUT_LAYER = 'detection_output' CTC_LAYER = 'ctc' WARP_CTC_LAYER = 'warp_ctc' @@ -1052,6 +1056,163 @@ def priorbox_layer(input, size=size) +@wrap_name_default("multibox_loss") +def multibox_loss_layer(input_loc, + input_conf, + priorbox, + label, + num_classes, + overlap_threshold=0.5, + neg_pos_ratio=3.0, + neg_overlap=0.5, + background_id=0, + name=None): + """ + Compute the location loss and the confidence loss for ssd. + + :param name: The Layer Name. + :type name: basestring + :param input_loc: The input predict location. + :type input_loc: LayerOutput + :param input_conf: The input priorbox confidence. + :type input_conf: LayerOutput + :param priorbox: The input priorbox location and the variance. + :type priorbox: LayerOutput + :param label: The input label. + :type label: LayerOutput + :param num_classes: The number of the classification. + :type num_classes: int + :param overlap_threshold: The threshold of the overlap. + :type overlap_threshold: float + :param neg_pos_ratio: The ratio of the negative bbox to the positive bbox. + :type neg_pos_ratio: float + :param neg_overlap: The negative bbox overlap threshold. + :type neg_overlap: float + :param background_id: The background class index. + :type background_id: int + :return: LayerOutput + """ + input_loc_num = 0 + input_conf_num = 0 + + if isinstance(input_loc, LayerOutput): + input_loc = [input_loc] + assert isinstance(input_loc, collections.Sequence) # list or tuple + for each in input_loc: + assert isinstance(each, LayerOutput) + input_loc_num += 1 + + if isinstance(input_conf, LayerOutput): + input_conf = [input_conf] + assert isinstance(input_conf, collections.Sequence) # list or tuple + for each in input_conf: + assert isinstance(each, LayerOutput) + input_conf_num += 1 + # Check the input layer number. + assert input_loc_num == input_conf_num + + inputs = [priorbox.name, label.name] + inputs.extend([l.name for l in input_loc]) + inputs.extend([l.name for l in input_conf]) + parents = [priorbox, label] + parents.extend(input_loc) + parents.extend(input_conf) + + Layer( + name=name, + type=LayerType.MULTIBOX_LOSS_LAYER, + inputs=inputs, + input_num=input_loc_num, + num_classes=num_classes, + overlap_threshold=overlap_threshold, + neg_pos_ratio=neg_pos_ratio, + neg_overlap=neg_overlap, + background_id=background_id) + return LayerOutput( + name, LayerType.MULTIBOX_LOSS_LAYER, parents=parents, size=1) + + +@wrap_name_default("detection_output") +def detection_output_layer(input_loc, + input_conf, + priorbox, + num_classes, + nms_threshold=0.45, + nms_top_k=400, + keep_top_k=200, + confidence_threshold=0.01, + background_id=0, + name=None): + """ + Apply the NMS to the output of network and compute the predict bounding + box location. + + :param name: The Layer Name. + :type name: basestring + :param input_loc: The input predict location. + :type input_loc: LayerOutput + :param input_conf: The input priorbox confidence. + :type input_conf: LayerOutput + :param priorbox: The input priorbox location and the variance. + :type priorbox: LayerOutput + :param num_classes: The number of the classification. + :type num_classes: int + :param nms_threshold: The Non-maximum suppression threshold. + :type nms_threshold: float + :param nms_top_k: The bbox number kept of the NMS's output + :type nms_top_k: int + :param keep_top_k: The bbox number kept of the layer's output + :type keep_top_k: int + :param confidence_threshold: The classification confidence threshold + :type confidence_threshold: float + :param background_id: The background class index. + :type background_id: int + :return: LayerOutput + """ + input_loc_num = 0 + input_conf_num = 0 + + if isinstance(input_loc, LayerOutput): + input_loc = [input_loc] + assert isinstance(input_loc, collections.Sequence) # list or tuple + for each in input_loc: + assert isinstance(each, LayerOutput) + input_loc_num += 1 + + if isinstance(input_conf, LayerOutput): + input_conf = [input_conf] + assert isinstance(input_conf, collections.Sequence) # list or tuple + for each in input_conf: + assert isinstance(each, LayerOutput) + input_conf_num += 1 + # Check the input layer number. + assert input_loc_num == input_conf_num + + inputs = [priorbox.name] + inputs.extend([l.name for l in input_loc]) + inputs.extend([l.name for l in input_conf]) + parents = [priorbox] + parents.extend(input_loc) + parents.extend(input_conf) + + size = keep_top_k * 7 + + Layer( + name=name, + type=LayerType.DETECTION_OUTPUT_LAYER, + inputs=inputs, + size=size, + input_num=input_loc_num, + num_classes=num_classes, + nms_threshold=nms_threshold, + nms_top_k=nms_top_k, + keep_top_k=keep_top_k, + confidence_threshold=confidence_threshold, + background_id=background_id) + return LayerOutput( + name, LayerType.DETECTION_OUTPUT_LAYER, parents=parents, size=size) + + @wrap_name_default("cross_channel_norm") def cross_channel_norm_layer(input, name=None, param_attr=None): """ -- GitLab From 65d9e33b42366a5905a3d7b9014d1422af996967 Mon Sep 17 00:00:00 2001 From: dzhwinter Date: Mon, 19 Jun 2017 11:44:20 +0800 Subject: [PATCH 0675/3256] "modify config name" --- paddle/math/tests/CMakeLists.txt | 1 - paddle/optimizer/adam_optimizer.cc | 3 ++- paddle/optimizer/lr_policy.h | 13 +++++++++---- paddle/optimizer/parameter_optimizer.cc | 6 +++--- paddle/optimizer/parameter_optimizer_test.cpp | 16 +++++++--------- paddle/optimizer/sgd_optimizer.cc | 6 +----- proto/OptimizerConfig.proto | 12 ++++++------ 7 files changed, 28 insertions(+), 29 deletions(-) diff --git a/paddle/math/tests/CMakeLists.txt b/paddle/math/tests/CMakeLists.txt index bdecba0869d..ceb96b2e250 100644 --- a/paddle/math/tests/CMakeLists.txt +++ b/paddle/math/tests/CMakeLists.txt @@ -31,4 +31,3 @@ add_simple_unittest(test_FPException) add_simple_unittest(test_GpuProfiler) add_simple_unittest(test_BaseMatrix) add_simple_unittest(test_Matrix) -add_simple_unittest(test_Matrix2) diff --git a/paddle/optimizer/adam_optimizer.cc b/paddle/optimizer/adam_optimizer.cc index 96cd6e4a129..bfe438ec936 100644 --- a/paddle/optimizer/adam_optimizer.cc +++ b/paddle/optimizer/adam_optimizer.cc @@ -28,7 +28,8 @@ const char *AdamOptimizer::SerializeState(int *state_len) { state.set_num_sample_passed(num_sample_passed_); TensorToProto(*parameter_, state.mutable_parameter()); - TensorToProto(*velocitys_, state.mutable_momentums()); + TensorToProto(*momentums_, state.mutable_momentums()); + TensorToProto(*velocitys_, state.mutable_velocitys()); auto str = state.SerializeAsString(); *state_len = str.size(); return str.c_str(); diff --git a/paddle/optimizer/lr_policy.h b/paddle/optimizer/lr_policy.h index be2bf89504e..d8e33ad37ab 100644 --- a/paddle/optimizer/lr_policy.h +++ b/paddle/optimizer/lr_policy.h @@ -21,8 +21,8 @@ public: double LearningRate(const uint64_t num_sample_passed) { return learning_rate; } - const char *SerializeState(int *state_len); - void DeserializeState(const std::string &state); + const char *SerializeState(int *state_len) { return nullptr; } + void DeserializeState(const std::string &state) {} private: double learning_rate; @@ -35,8 +35,13 @@ public: double LearningRate(const uint64_t num_sample_passed) { return std::max(learning_rate - lr_decay_a * num_sample_passed, lr_decay_b); } - const char *SerializeState(int *state_len); - void DeserializeState(const std::string &state); + const char *SerializeState(int *state_len) { + // TODO(zhihong) : add lr_policy serialization + return nullptr; + } + void DeserializeState(const std::string &state) { + // TODO(zhihong) : add lr_policy serialization + } private: double learning_rate; diff --git a/paddle/optimizer/parameter_optimizer.cc b/paddle/optimizer/parameter_optimizer.cc index 38df3b75d77..dd018037bda 100644 --- a/paddle/optimizer/parameter_optimizer.cc +++ b/paddle/optimizer/parameter_optimizer.cc @@ -13,13 +13,13 @@ namespace optimizer { ParameterOptimizer *ParameterOptimizer::Create(const std::string &config_proto, Tensor *parameter) { paddle::OptimizerConfig config; - CHECK(config.ParseFromString(config_proto) == 0) + CHECK(config.ParseFromString(config_proto) == true) << "failed parse optimizer config"; auto select_lr_policy = [=](const OptimizerConfig &config) -> LrPolicy * { - if (config.lr_policy() == OptimizerConfig::ConstLr) + if (config.lr_policy() == OptimizerConfig::Const) return new ConstLr(config.const_lr().learning_rate()); - if (config.lr_policy() == OptimizerConfig::LinearLr) + if (config.lr_policy() == OptimizerConfig::Linear) return new LinearLr(config.linear_lr().learning_rate(), config.linear_lr().lr_decay_a(), config.linear_lr().lr_decay_b()); diff --git a/paddle/optimizer/parameter_optimizer_test.cpp b/paddle/optimizer/parameter_optimizer_test.cpp index afacd6d54a7..f599b74d71c 100644 --- a/paddle/optimizer/parameter_optimizer_test.cpp +++ b/paddle/optimizer/parameter_optimizer_test.cpp @@ -2,11 +2,8 @@ #include #include #include -#include "adadelta_optimizer.h" -#include "adagrad_optimizer.h" -#include "adam_optimizer.h" #include "gtest/gtest.h" -#include "sgd_optimizer.h" +#include "lr_policy.h" using namespace paddle; using namespace paddle::optimizer; @@ -41,12 +38,12 @@ public: virtual void TearDown() {} void CreateSGD() { - Tensor* parameter = FillTensor(kSize); + Tensor* parameter = FixedTensor(kSize); config_.set_optimizer(OptimizerConfig::SGD); config_.mutable_sgd()->set_momentum(0.0); config_.mutable_sgd()->set_decay(0.0); config_.mutable_sgd()->set_nesterov(false); - config_.set_lr_policy(OptimizerConfig::ConstLr); + config_.set_lr_policy(OptimizerConfig::Const); config_.mutable_const_lr()->set_learning_rate(0.1); std::string str = config_.SerializeAsString(); @@ -62,7 +59,7 @@ public: config_.mutable_adam()->set_beta_2(0.1); config_.mutable_adam()->set_epsilon(1e-3); config_.mutable_adam()->set_decay(0.0); - config_.set_lr_policy(OptimizerConfig::ConstLr); + config_.set_lr_policy(OptimizerConfig::Const); config_.mutable_const_lr()->set_learning_rate(0.1); std::string str = config_.SerializeAsString(); ParameterOptimizer* opt = ParameterOptimizer::Create(str, parameter); @@ -90,12 +87,13 @@ public: void TestCheckPoint() { std::map expected_state_len = { - {OptimizerConfig::SGD, kSize}, {OptimizerConfig::Adam, kSize * 3}, + {OptimizerConfig::SGD, kSize * sizeof(float) + sizeof(double)}, + {OptimizerConfig::Adam, kSize * 3 * sizeof(float) + sizeof(double)}, }; for (size_t i = 0; i < opts_.size(); ++i) { int state_len = 0; std::string state = opts_[i]->SerializeState(&state_len); - EXPECT_EQ(state_len, expected_state_len[opts_table_[i]]); + EXPECT_EQ(state_len, expected_state_len[opts_table_[i + 1]]); opts_[i]->DeserializeState(state); } } diff --git a/paddle/optimizer/sgd_optimizer.cc b/paddle/optimizer/sgd_optimizer.cc index 66843ecb4b2..252f205bb07 100644 --- a/paddle/optimizer/sgd_optimizer.cc +++ b/paddle/optimizer/sgd_optimizer.cc @@ -29,11 +29,9 @@ void SGDOptimizer::Update(const Tensor *gradient) { const char *SGDOptimizer::SerializeState(int *state_len) { SGDOptimizerState state; - // TODO(zhihong) : add lr_policy serialization state.set_num_sample_passed(num_sample_passed_); - TensorToProto(*parameter_, state.mutable_parameter()); - TensorToProto(*momentums_, state.mutable_momentums()); + if (momentum_ != 0.0) TensorToProto(*momentums_, state.mutable_momentums()); auto str = state.SerializeAsString(); *state_len = str.size(); return str.c_str(); @@ -42,9 +40,7 @@ const char *SGDOptimizer::SerializeState(int *state_len) { void SGDOptimizer::DeserializeState(const std::string &str) { SGDOptimizerState state; state.ParseFromString(str); - // TODO(zhihong) : add lr_policy DeserializeState num_sample_passed_ = state.num_sample_passed(); - ProtoToTensor(state.parameter(), parameter_); ProtoToTensor(state.parameter(), momentums_); } diff --git a/proto/OptimizerConfig.proto b/proto/OptimizerConfig.proto index aab2fdad693..56bda35be47 100644 --- a/proto/OptimizerConfig.proto +++ b/proto/OptimizerConfig.proto @@ -53,12 +53,12 @@ message AdamConfig { optional double decay = 44; } -message ConstLr { +message ConstLrConfig { // learninRate Policy required double learning_rate = 1 [default = 1.0]; } -message LinearLr { +message LinearLrConfig { // learninRate Policy required double learning_rate = 1 [default = 1.0]; optional double lr_decay_a = 2; @@ -139,12 +139,12 @@ message OptimizerConfig { optional AdamConfig adam = 6; enum LrPolicy { - ConstLr = 0; - LinearLr = 1; + Const = 0; + Linear = 1; } required LrPolicy lr_policy = 11; - optional ConstLr const_lr = 12; - optional LinearLr linear_lr = 13; + optional ConstLrConfig const_lr = 12; + optional LinearLrConfig linear_lr = 13; // common config of optimizer // gradient clip when L2 exceeding value -- GitLab From b233ed135352de1260b644112f939938798048ec Mon Sep 17 00:00:00 2001 From: yangyaming Date: Mon, 19 Jun 2017 14:53:59 +0800 Subject: [PATCH 0676/3256] Set FLAGS_use_gpu in test_DetectionOutput. --- paddle/gserver/tests/test_DetectionOutput.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/paddle/gserver/tests/test_DetectionOutput.cpp b/paddle/gserver/tests/test_DetectionOutput.cpp index 8ec7a284502..af43dc51fad 100644 --- a/paddle/gserver/tests/test_DetectionOutput.cpp +++ b/paddle/gserver/tests/test_DetectionOutput.cpp @@ -65,9 +65,12 @@ void doOneDetectionOutputTest(MatrixPtr& inputLoc, dataLayers[2]->getOutputValue()->copyFrom(*inputConf); // test layer initialize + bool store_FLAGS_use_gpu = FLAGS_use_gpu; + FLAGS_use_gpu = use_gpu; std::vector parameters; LayerPtr detectionOutputLayer; initTestLayer(configt, &layerMap, ¶meters, &detectionOutputLayer); + FLAGS_use_gpu = store_FLAGS_use_gpu; detectionOutputLayer->forward(PASS_GC); checkMatrixEqual(detectionOutputLayer->getOutputValue(), result); } -- GitLab From 9c47c42ad1953e2fbe89ff234c3d35494b5b751a Mon Sep 17 00:00:00 2001 From: hedaoyuan Date: Mon, 19 Jun 2017 15:03:01 +0800 Subject: [PATCH 0677/3256] Change the groups in the comment to 1049089. --- paddle/function/ConvOp.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/paddle/function/ConvOp.h b/paddle/function/ConvOp.h index b87808915ef..65b9d1d53f9 100644 --- a/paddle/function/ConvOp.h +++ b/paddle/function/ConvOp.h @@ -50,9 +50,9 @@ namespace paddle { * image channels, C is the number of input image channels, * H and W is height and width of filter. * - * If groups is greater than 1, the filter's data format should be GMCHW, - * where G is the groups, and G * M is the number of output image channels, - * G * C is the number of input image channels, + * If `groups` is greater than 1, the filter's data format should be GMCHW, + * where G is the `groups`, and G * M is the number of output image + * channels, G * C is the number of input image channels, * H and W is height and width of filter. */ class ConvFunctionBase : public FunctionBase { -- GitLab From ec65fa835cfed6c8435d0d3a15fb936a8cd705cc Mon Sep 17 00:00:00 2001 From: dzhwinter Date: Mon, 19 Jun 2017 15:46:20 +0800 Subject: [PATCH 0678/3256] "protobuf required to optional" --- paddle/optimizer/adam_optimizer.cc | 2 +- paddle/optimizer/parameter_optimizer_test.cpp | 9 --------- paddle/optimizer/sgd_optimizer.cc | 2 +- proto/OptimizerConfig.proto | 10 +++++----- 4 files changed, 7 insertions(+), 16 deletions(-) diff --git a/paddle/optimizer/adam_optimizer.cc b/paddle/optimizer/adam_optimizer.cc index bfe438ec936..ceab7397d87 100644 --- a/paddle/optimizer/adam_optimizer.cc +++ b/paddle/optimizer/adam_optimizer.cc @@ -26,7 +26,6 @@ const char *AdamOptimizer::SerializeState(int *state_len) { AdamOptimizerState state; // TODO(zhihong) : add lr_policy serialization state.set_num_sample_passed(num_sample_passed_); - TensorToProto(*parameter_, state.mutable_parameter()); TensorToProto(*momentums_, state.mutable_momentums()); TensorToProto(*velocitys_, state.mutable_velocitys()); @@ -42,6 +41,7 @@ void AdamOptimizer::DeserializeState(const std::string &str) { num_sample_passed_ = state.num_sample_passed(); ProtoToTensor(state.parameter(), parameter_); + ProtoToTensor(state.momentums(), momentums_); ProtoToTensor(state.velocitys(), velocitys_); } } // namespace optimizer diff --git a/paddle/optimizer/parameter_optimizer_test.cpp b/paddle/optimizer/parameter_optimizer_test.cpp index f599b74d71c..4e6254d9e4d 100644 --- a/paddle/optimizer/parameter_optimizer_test.cpp +++ b/paddle/optimizer/parameter_optimizer_test.cpp @@ -45,11 +45,9 @@ public: config_.mutable_sgd()->set_nesterov(false); config_.set_lr_policy(OptimizerConfig::Const); config_.mutable_const_lr()->set_learning_rate(0.1); - std::string str = config_.SerializeAsString(); ParameterOptimizer* opt = ParameterOptimizer::Create(str, parameter); opts_.push_back(opt); - opts_table_[opts_.size()] = OptimizerConfig::SGD; } void CreateAdam() { @@ -64,7 +62,6 @@ public: std::string str = config_.SerializeAsString(); ParameterOptimizer* opt = ParameterOptimizer::Create(str, parameter); opts_.push_back(opt); - opts_table_[opts_.size()] = OptimizerConfig::Adam; } void TestGetWeight() { @@ -86,21 +83,15 @@ public: } void TestCheckPoint() { - std::map expected_state_len = { - {OptimizerConfig::SGD, kSize * sizeof(float) + sizeof(double)}, - {OptimizerConfig::Adam, kSize * 3 * sizeof(float) + sizeof(double)}, - }; for (size_t i = 0; i < opts_.size(); ++i) { int state_len = 0; std::string state = opts_[i]->SerializeState(&state_len); - EXPECT_EQ(state_len, expected_state_len[opts_table_[i + 1]]); opts_[i]->DeserializeState(state); } } private: std::vector opts_; - std::map opts_table_; OptimizerConfig config_; }; diff --git a/paddle/optimizer/sgd_optimizer.cc b/paddle/optimizer/sgd_optimizer.cc index 252f205bb07..34e051003fa 100644 --- a/paddle/optimizer/sgd_optimizer.cc +++ b/paddle/optimizer/sgd_optimizer.cc @@ -42,7 +42,7 @@ void SGDOptimizer::DeserializeState(const std::string &str) { state.ParseFromString(str); num_sample_passed_ = state.num_sample_passed(); ProtoToTensor(state.parameter(), parameter_); - ProtoToTensor(state.parameter(), momentums_); + if (momentum_ != 0.0) ProtoToTensor(state.parameter(), momentums_); } } // namespace optimizer diff --git a/proto/OptimizerConfig.proto b/proto/OptimizerConfig.proto index 56bda35be47..c698d3c2ddb 100644 --- a/proto/OptimizerConfig.proto +++ b/proto/OptimizerConfig.proto @@ -55,12 +55,12 @@ message AdamConfig { message ConstLrConfig { // learninRate Policy - required double learning_rate = 1 [default = 1.0]; + optional double learning_rate = 1 [default = 1.0]; } message LinearLrConfig { // learninRate Policy - required double learning_rate = 1 [default = 1.0]; + optional double learning_rate = 1 [default = 1.0]; optional double lr_decay_a = 2; optional double lr_decay_b = 3; } @@ -74,7 +74,7 @@ enum DataType { PADDLE_ELEMENT_TYPE_FLOAT32 = 4; PADDLE_ELEMENT_TYPE_FLOAT64 = 5; } - required DataType data_type = 1; + optional DataType data_type = 1; repeated bytes content = 2; } @@ -132,7 +132,7 @@ message OptimizerConfig { Adagrad = 3; Adam = 4; } - required Optimizer optimizer = 1; + optional Optimizer optimizer = 1; optional SGDConfig sgd = 3; optional AdadeltaConfig adadelta = 4; optional AdagradConfig adagrad = 5; @@ -142,7 +142,7 @@ message OptimizerConfig { Const = 0; Linear = 1; } - required LrPolicy lr_policy = 11; + optional LrPolicy lr_policy = 11; optional ConstLrConfig const_lr = 12; optional LinearLrConfig linear_lr = 13; -- GitLab From 5f924d5d533831c29f1f5243eb1790467c9aac1a Mon Sep 17 00:00:00 2001 From: yangyaming Date: Mon, 19 Jun 2017 18:15:15 +0800 Subject: [PATCH 0679/3256] Follow comments. --- doc/api/v2/config/evaluators.rst | 9 +++ .../evaluators/DetectionMAPEvaluator.cpp | 66 +++++++++---------- .../trainer_config_helpers/evaluators.py | 6 +- 3 files changed, 43 insertions(+), 38 deletions(-) diff --git a/doc/api/v2/config/evaluators.rst b/doc/api/v2/config/evaluators.rst index 39db51fa4ab..9ac972fb193 100644 --- a/doc/api/v2/config/evaluators.rst +++ b/doc/api/v2/config/evaluators.rst @@ -99,3 +99,12 @@ value_printer .. automodule:: paddle.v2.evaluator :members: value_printer :noindex: + +Detection +===== + +detection_map +------------- +.. automodule:: paddle.v2.evaluator + :members: detection_map + :noindex: diff --git a/paddle/gserver/evaluators/DetectionMAPEvaluator.cpp b/paddle/gserver/evaluators/DetectionMAPEvaluator.cpp index 7d326c2db14..9b825db574c 100644 --- a/paddle/gserver/evaluators/DetectionMAPEvaluator.cpp +++ b/paddle/gserver/evaluators/DetectionMAPEvaluator.cpp @@ -80,21 +80,20 @@ public: allGTBBoxes.push_back(bboxes); } - size_t imgId = 0; - for (size_t n = 0; n < cpuOutput_->getHeight();) { + size_t n = 0; + const real* cpuOutputData = cpuOutput_->getData(); + for (size_t imgId = 0; imgId < batchSize; ++imgId) { map>> bboxes; - while (cpuOutput_->getData()[n * 7] == imgId && - n < cpuOutput_->getHeight()) { + size_t curImgId = static_cast((cpuOutputData + n * 7)[0]); + while (curImgId == imgId && n < cpuOutput_->getHeight()) { vector label; vector score; vector bbox; - getBBoxFromDetectData( - cpuOutput_->getData() + n * 7, 1, label, score, bbox); + getBBoxFromDetectData(cpuOutputData + n * 7, 1, label, score, bbox); bboxes[label[0]].push_back(make_pair(score[0], bbox[0])); ++n; + curImgId = static_cast((cpuOutputData + n * 7)[0]); } - ++imgId; - if (imgId > batchSize) break; allDetectBBoxes.push_back(bboxes); } @@ -119,15 +118,14 @@ public: } // calcTFPos - calcTFPos( - batchSize, allGTBBoxes, allDetectBBoxes, &allTruePos_, &allFalsePos_); + calcTFPos(batchSize, allGTBBoxes, allDetectBBoxes); return 0; } virtual void printStats(std::ostream& os) const { real mAP = calcMAP(); - os << "Detection mAP=" << mAP * 100; + os << "Detection mAP=" << mAP; } virtual void distributeEval(ParameterClient2* client) { @@ -138,9 +136,7 @@ protected: void calcTFPos(const size_t batchSize, const vector>>& allGTBBoxes, const vector>>>& - allDetectBBoxes, - map>>* allTruePos, - map>>* allFalsePos) { + allDetectBBoxes) { for (size_t n = 0; n < allDetectBBoxes.size(); ++n) { if (allGTBBoxes[n].size() == 0) { for (map>>::const_iterator @@ -149,8 +145,8 @@ protected: ++it) { size_t label = it->first; for (size_t i = 0; i < it->second.size(); ++i) { - (*allTruePos)[label].push_back(make_pair(it->second[i].first, 0)); - (*allFalsePos)[label].push_back(make_pair(it->second[i].first, 1)); + allTruePos_[label].push_back(make_pair(it->second[i].first, 0)); + allFalsePos_[label].push_back(make_pair(it->second[i].first, 1)); } } } else { @@ -162,9 +158,8 @@ protected: vector> predBBoxes = it->second; if (allGTBBoxes[n].find(label) == allGTBBoxes[n].end()) { for (size_t i = 0; i < predBBoxes.size(); ++i) { - (*allTruePos)[label].push_back(make_pair(predBBoxes[i].first, 0)); - (*allFalsePos)[label].push_back( - make_pair(predBBoxes[i].first, 1)); + allTruePos_[label].push_back(make_pair(predBBoxes[i].first, 0)); + allFalsePos_[label].push_back(make_pair(predBBoxes[i].first, 1)); } } else { vector gtBBoxes = @@ -189,22 +184,21 @@ protected: if (evaluateDifficult_ || (!evaluateDifficult_ && !gtBBoxes[maxIdx].isDifficult)) { if (!visited[maxIdx]) { - (*allTruePos)[label].push_back( + allTruePos_[label].push_back( make_pair(predBBoxes[i].first, 1)); - (*allFalsePos)[label].push_back( + allFalsePos_[label].push_back( make_pair(predBBoxes[i].first, 0)); visited[maxIdx] = true; } else { - (*allTruePos)[label].push_back( + allTruePos_[label].push_back( make_pair(predBBoxes[i].first, 0)); - (*allFalsePos)[label].push_back( + allFalsePos_[label].push_back( make_pair(predBBoxes[i].first, 1)); } } } else { - (*allTruePos)[label].push_back( - make_pair(predBBoxes[i].first, 0)); - (*allFalsePos)[label].push_back( + allTruePos_[label].push_back(make_pair(predBBoxes[i].first, 0)); + allFalsePos_[label].push_back( make_pair(predBBoxes[i].first, 1)); } } @@ -274,7 +268,7 @@ protected: } } if (count != 0) mAP /= count; - return mAP; + return mAP * 100; } void getAccumulation(vector> inPairs, @@ -291,20 +285,22 @@ protected: std::string getTypeImpl() const { return "detection_map"; } - real getValueImpl() const { return calcMAP() * 100; } + real getValueImpl() const { return calcMAP(); } private: - real overlapThreshold_; - bool evaluateDifficult_; - size_t backgroundId_; - std::string apType_; + real overlapThreshold_; // overlap threshold when determining whether matched + bool evaluateDifficult_; // whether evaluate difficult ground truth + size_t backgroundId_; // class index of background + std::string apType_; // how to calculate mAP (Integral or 11point) MatrixPtr cpuOutput_; MatrixPtr cpuLabel_; - map numPos_; - map>> allTruePos_; - map>> allFalsePos_; + map numPos_; // counts of true objects each classification + map>> + allTruePos_; // true positive prediction + map>> + allFalsePos_; // false positive prediction }; REGISTER_EVALUATOR(detection_map, DetectionMAPEvaluator); diff --git a/python/paddle/trainer_config_helpers/evaluators.py b/python/paddle/trainer_config_helpers/evaluators.py index 1dcd8048032..44d52edfa7b 100644 --- a/python/paddle/trainer_config_helpers/evaluators.py +++ b/python/paddle/trainer_config_helpers/evaluators.py @@ -166,9 +166,9 @@ def detection_map_evaluator(input, ap_type="11point", name=None): """ - Detection mAP Evaluator. It will print mean Average Precision for detection. + Detection mAP Evaluator. It will print mean Average Precision (mAP) for detection. - The detection mAP Evaluator according to the detection_output's output count + The detection mAP Evaluator based on the output of detection_output layer counts the true positive and the false positive bbox and integral them to get the mAP. @@ -186,7 +186,7 @@ def detection_map_evaluator(input, :type overlap_threshold: float :param background_id: The background class index. :type background_id: int - :param evaluate_difficult: Wether evaluate a difficult ground truth. + :param evaluate_difficult: Whether evaluate a difficult ground truth. :type evaluate_difficult: bool """ if not isinstance(input, list): -- GitLab From 99849cf56ae19e714e3dd78815b934edf3da85f9 Mon Sep 17 00:00:00 2001 From: dzhwinter Date: Mon, 19 Jun 2017 19:33:27 +0800 Subject: [PATCH 0680/3256] rename Tensor.h --- paddle/optimizer/{Tensor.h => tensor.h} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename paddle/optimizer/{Tensor.h => tensor.h} (100%) diff --git a/paddle/optimizer/Tensor.h b/paddle/optimizer/tensor.h similarity index 100% rename from paddle/optimizer/Tensor.h rename to paddle/optimizer/tensor.h -- GitLab From 72b6b26f5a9ec52498f934db138c1e8c8e588559 Mon Sep 17 00:00:00 2001 From: dzhwinter Date: Mon, 19 Jun 2017 20:11:58 +0800 Subject: [PATCH 0681/3256] "ci formatter" --- paddle/optimizer/parameter_optimizer.cc | 1 - 1 file changed, 1 deletion(-) diff --git a/paddle/optimizer/parameter_optimizer.cc b/paddle/optimizer/parameter_optimizer.cc index dd018037bda..7f61196c1fc 100644 --- a/paddle/optimizer/parameter_optimizer.cc +++ b/paddle/optimizer/parameter_optimizer.cc @@ -15,7 +15,6 @@ ParameterOptimizer *ParameterOptimizer::Create(const std::string &config_proto, paddle::OptimizerConfig config; CHECK(config.ParseFromString(config_proto) == true) << "failed parse optimizer config"; - auto select_lr_policy = [=](const OptimizerConfig &config) -> LrPolicy * { if (config.lr_policy() == OptimizerConfig::Const) return new ConstLr(config.const_lr().learning_rate()); -- GitLab From 03884f0fd9578897fbd0177abd3b64e175401387 Mon Sep 17 00:00:00 2001 From: dzhwinter Date: Mon, 19 Jun 2017 20:35:21 +0800 Subject: [PATCH 0682/3256] formatter --- paddle/optimizer/parameter_optimizer.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/paddle/optimizer/parameter_optimizer.cc b/paddle/optimizer/parameter_optimizer.cc index 7f61196c1fc..a7362f07807 100644 --- a/paddle/optimizer/parameter_optimizer.cc +++ b/paddle/optimizer/parameter_optimizer.cc @@ -27,9 +27,9 @@ ParameterOptimizer *ParameterOptimizer::Create(const std::string &config_proto, return new ConstLr(0.1); }; LrPolicy *lr = select_lr_policy(config); - auto select_optimizer = - [=](Tensor *parameter, - const OptimizerConfig &config) -> ParameterOptimizer * { + auto select_optimizer = [=]( + Tensor *parameter, + const OptimizerConfig &config) -> ParameterOptimizer * { if (config.optimizer() == OptimizerConfig::SGD) { return new SGDOptimizer(parameter, lr, -- GitLab From f48535102b6f71ba802e9b656c73cdd3ec746a3b Mon Sep 17 00:00:00 2001 From: xuwei06 Date: Mon, 19 Jun 2017 10:46:40 -0700 Subject: [PATCH 0683/3256] Add the test_repeat_layer.py --- .../tests/configs/test_repeat_layer.py | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 python/paddle/trainer_config_helpers/tests/configs/test_repeat_layer.py diff --git a/python/paddle/trainer_config_helpers/tests/configs/test_repeat_layer.py b/python/paddle/trainer_config_helpers/tests/configs/test_repeat_layer.py new file mode 100644 index 00000000000..004e2a5dd4e --- /dev/null +++ b/python/paddle/trainer_config_helpers/tests/configs/test_repeat_layer.py @@ -0,0 +1,11 @@ +from paddle.trainer_config_helpers import * + +settings(batch_size=1000, learning_rate=1e-5) + +din = data_layer(name='data', size=30) + +outputs( + repeat_layer( + input=din, num_repeats=10, as_row_vector=True), + repeat_layer( + input=din, num_repeats=10, act=TanhActivation(), as_row_vector=False)) -- GitLab From a166e5291dfd2a6f6894fe8d9bd961eb2421c541 Mon Sep 17 00:00:00 2001 From: dongzhihong Date: Tue, 20 Jun 2017 03:20:04 +0800 Subject: [PATCH 0684/3256] "formatter in docker" --- paddle/optimizer/parameter_optimizer.cc | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/paddle/optimizer/parameter_optimizer.cc b/paddle/optimizer/parameter_optimizer.cc index a7362f07807..38fa2bfde93 100644 --- a/paddle/optimizer/parameter_optimizer.cc +++ b/paddle/optimizer/parameter_optimizer.cc @@ -26,10 +26,11 @@ ParameterOptimizer *ParameterOptimizer::Create(const std::string &config_proto, LOG(WARNING) << " have not select any LrPolicy. use ConstLr in default"; return new ConstLr(0.1); }; + LrPolicy *lr = select_lr_policy(config); - auto select_optimizer = [=]( - Tensor *parameter, - const OptimizerConfig &config) -> ParameterOptimizer * { + auto select_optimizer = + [=](Tensor *parameter, + const OptimizerConfig &config) -> ParameterOptimizer * { if (config.optimizer() == OptimizerConfig::SGD) { return new SGDOptimizer(parameter, lr, -- GitLab From 33ddc8937105df5edf0e2006b3546018265bf40d Mon Sep 17 00:00:00 2001 From: dongzhihong Date: Mon, 19 Jun 2017 19:20:28 +0000 Subject: [PATCH 0685/3256] formatter in docker --- paddle/optimizer/parameter_optimizer.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/paddle/optimizer/parameter_optimizer.cc b/paddle/optimizer/parameter_optimizer.cc index 38fa2bfde93..f6218037925 100644 --- a/paddle/optimizer/parameter_optimizer.cc +++ b/paddle/optimizer/parameter_optimizer.cc @@ -28,9 +28,9 @@ ParameterOptimizer *ParameterOptimizer::Create(const std::string &config_proto, }; LrPolicy *lr = select_lr_policy(config); - auto select_optimizer = - [=](Tensor *parameter, - const OptimizerConfig &config) -> ParameterOptimizer * { + auto select_optimizer = [=]( + Tensor *parameter, + const OptimizerConfig &config) -> ParameterOptimizer * { if (config.optimizer() == OptimizerConfig::SGD) { return new SGDOptimizer(parameter, lr, -- GitLab From bb88202879bd10660cf44f51fa0952560a8c9b19 Mon Sep 17 00:00:00 2001 From: Yi Wang Date: Mon, 19 Jun 2017 14:26:29 -0700 Subject: [PATCH 0686/3256] Move paddle/majel/* to paddle/platform/ and paddle/framework/ --- paddle/framework/CMakeLists.txt | 4 ++++ paddle/{majel => framework}/ddim.cc | 0 paddle/{majel => framework}/ddim.h | 0 paddle/{majel => framework}/ddim_test.cc | 0 paddle/{majel => framework}/dim.h | 0 paddle/{majel => framework}/dim_test.cu | 0 paddle/{majel/README.md => framework/tensor.md} | 0 paddle/{majel => platform}/CMakeLists.txt | 8 ++------ paddle/{majel/detail/cuda_assert.h => platform/assert.h} | 0 paddle/{majel => platform}/cuda_test.cu | 0 paddle/{majel/detail => platform}/hostdevice.h | 0 paddle/{majel => platform}/place.cc | 0 paddle/{majel => platform}/place.h | 0 paddle/{majel => platform}/place_test.cc | 0 14 files changed, 6 insertions(+), 6 deletions(-) create mode 100644 paddle/framework/CMakeLists.txt rename paddle/{majel => framework}/ddim.cc (100%) rename paddle/{majel => framework}/ddim.h (100%) rename paddle/{majel => framework}/ddim_test.cc (100%) rename paddle/{majel => framework}/dim.h (100%) rename paddle/{majel => framework}/dim_test.cu (100%) rename paddle/{majel/README.md => framework/tensor.md} (100%) rename paddle/{majel => platform}/CMakeLists.txt (51%) rename paddle/{majel/detail/cuda_assert.h => platform/assert.h} (100%) rename paddle/{majel => platform}/cuda_test.cu (100%) rename paddle/{majel/detail => platform}/hostdevice.h (100%) rename paddle/{majel => platform}/place.cc (100%) rename paddle/{majel => platform}/place.h (100%) rename paddle/{majel => platform}/place_test.cc (100%) diff --git a/paddle/framework/CMakeLists.txt b/paddle/framework/CMakeLists.txt new file mode 100644 index 00000000000..673cfa19ac3 --- /dev/null +++ b/paddle/framework/CMakeLists.txt @@ -0,0 +1,4 @@ +cc_library(ddim SRCS ddim.cc) +cc_test(ddim_test SRCS ddim_test.cc DEPS ddim) + +nv_test(dim_test SRCS dim_test.cu DEPS ddim) diff --git a/paddle/majel/ddim.cc b/paddle/framework/ddim.cc similarity index 100% rename from paddle/majel/ddim.cc rename to paddle/framework/ddim.cc diff --git a/paddle/majel/ddim.h b/paddle/framework/ddim.h similarity index 100% rename from paddle/majel/ddim.h rename to paddle/framework/ddim.h diff --git a/paddle/majel/ddim_test.cc b/paddle/framework/ddim_test.cc similarity index 100% rename from paddle/majel/ddim_test.cc rename to paddle/framework/ddim_test.cc diff --git a/paddle/majel/dim.h b/paddle/framework/dim.h similarity index 100% rename from paddle/majel/dim.h rename to paddle/framework/dim.h diff --git a/paddle/majel/dim_test.cu b/paddle/framework/dim_test.cu similarity index 100% rename from paddle/majel/dim_test.cu rename to paddle/framework/dim_test.cu diff --git a/paddle/majel/README.md b/paddle/framework/tensor.md similarity index 100% rename from paddle/majel/README.md rename to paddle/framework/tensor.md diff --git a/paddle/majel/CMakeLists.txt b/paddle/platform/CMakeLists.txt similarity index 51% rename from paddle/majel/CMakeLists.txt rename to paddle/platform/CMakeLists.txt index 93e5e2c22f0..c7d7b14518e 100644 --- a/paddle/majel/CMakeLists.txt +++ b/paddle/platform/CMakeLists.txt @@ -1,8 +1,4 @@ +nv_test(cuda_test SRCS cuda_test.cu) + cc_library(place SRCS place.cc) cc_test(place_test SRCS place_test.cc DEPS place glog gflags) - -cc_library(ddim SRCS ddim.cc) -cc_test(ddim_test SRCS ddim_test.cc DEPS ddim) - -nv_test(cuda_test SRCS cuda_test.cu) -nv_test(dim_test SRCS dim_test.cu DEPS ddim) diff --git a/paddle/majel/detail/cuda_assert.h b/paddle/platform/assert.h similarity index 100% rename from paddle/majel/detail/cuda_assert.h rename to paddle/platform/assert.h diff --git a/paddle/majel/cuda_test.cu b/paddle/platform/cuda_test.cu similarity index 100% rename from paddle/majel/cuda_test.cu rename to paddle/platform/cuda_test.cu diff --git a/paddle/majel/detail/hostdevice.h b/paddle/platform/hostdevice.h similarity index 100% rename from paddle/majel/detail/hostdevice.h rename to paddle/platform/hostdevice.h diff --git a/paddle/majel/place.cc b/paddle/platform/place.cc similarity index 100% rename from paddle/majel/place.cc rename to paddle/platform/place.cc diff --git a/paddle/majel/place.h b/paddle/platform/place.h similarity index 100% rename from paddle/majel/place.h rename to paddle/platform/place.h diff --git a/paddle/majel/place_test.cc b/paddle/platform/place_test.cc similarity index 100% rename from paddle/majel/place_test.cc rename to paddle/platform/place_test.cc -- GitLab From 9b5fb276476d57d331c819a7dab8d509646cbc8f Mon Sep 17 00:00:00 2001 From: Yi Wang Date: Mon, 19 Jun 2017 15:02:34 -0700 Subject: [PATCH 0687/3256] Make place_test buildable and passed. --- .clang-format | 5 +--- paddle/CMakeLists.txt | 12 ++++++---- paddle/platform/place.cc | 39 ++++++++++++++---------------- paddle/platform/place.h | 45 +++++++++++++++++------------------ paddle/platform/place_test.cc | 26 ++++++++++---------- 5 files changed, 61 insertions(+), 66 deletions(-) diff --git a/.clang-format b/.clang-format index 9ba433b1736..5a30b52cf4b 100644 --- a/.clang-format +++ b/.clang-format @@ -16,10 +16,7 @@ --- Language: Cpp BasedOnStyle: Google -IndentWidth: 2 -TabWidth: 2 -ContinuationIndentWidth: 4 -AccessModifierOffset: -2 # The private/protected/public has no indent in class +UseTab Never Standard: Cpp11 AllowAllParametersOfDeclarationOnNextLine: true BinPackParameters: false diff --git a/paddle/CMakeLists.txt b/paddle/CMakeLists.txt index 47ca1833967..fc33def5569 100644 --- a/paddle/CMakeLists.txt +++ b/paddle/CMakeLists.txt @@ -17,11 +17,13 @@ add_subdirectory(strings) find_package(Boost QUIET) -if(Boost_FOUND) - include_directories(${Boost_INCLUDE_DIRS}) - include_directories(${CMAKE_CURRENT_SOURCE_DIR}) - add_subdirectory(majel) -endif() +add_subdirectory(platform) + +# if(Boost_FOUND) +# include_directories(${Boost_INCLUDE_DIRS}) +# include_directories(${CMAKE_CURRENT_SOURCE_DIR}) +# add_subdirectory(majel) +# endif() if(WITH_C_API) add_subdirectory(capi) diff --git a/paddle/platform/place.cc b/paddle/platform/place.cc index ca50b37843e..d500c4cc7a0 100644 --- a/paddle/platform/place.cc +++ b/paddle/platform/place.cc @@ -1,49 +1,46 @@ -#include "paddle/majel/place.h" +#include "paddle/platform/place.h" -namespace majel { +namespace paddle { +namespace platform { namespace detail { class PlacePrinter : public boost::static_visitor<> { -private: - std::ostream& os_; - public: - PlacePrinter(std::ostream& os) : os_(os) {} + PlacePrinter(std::ostream &os) : os_(os) {} + void operator()(const CpuPlace &) { os_ << "CpuPlace"; } + void operator()(const GpuPlace &p) { os_ << "GpuPlace(" << p.device << ")"; } - void operator()(const CpuPlace&) { os_ << "CpuPlace"; } - - void operator()(const GpuPlace& p) { os_ << "GpuPlace(" << p.device << ")"; } +private: + std::ostream &os_; }; -} // namespace detail +} // namespace detail static Place the_default_place; -void set_place(const Place& place) { the_default_place = place; } - -const Place& get_place() { return the_default_place; } +void set_place(const Place &place) { the_default_place = place; } +const Place &get_place() { return the_default_place; } const GpuPlace default_gpu() { return GpuPlace(0); } - const CpuPlace default_cpu() { return CpuPlace(); } -bool is_gpu_place(const Place& p) { +bool is_gpu_place(const Place &p) { return boost::apply_visitor(IsGpuPlace(), p); } - -bool is_cpu_place(const Place& p) { +bool is_cpu_place(const Place &p) { return !boost::apply_visitor(IsGpuPlace(), p); } -bool places_are_same_class(const Place& p1, const Place& p2) { +bool places_are_same_class(const Place &p1, const Place &p2) { return is_gpu_place(p1) == is_gpu_place(p2); } -std::ostream& operator<<(std::ostream& os, const majel::Place& p) { - majel::detail::PlacePrinter printer(os); +std::ostream &operator<<(std::ostream &os, const Place &p) { + detail::PlacePrinter printer(os); boost::apply_visitor(printer, p); return os; } -} // namespace majel +} // namespace platform +} // namespace paddle diff --git a/paddle/platform/place.h b/paddle/platform/place.h index ad3dc3fe0b8..ca1b76ef4b8 100644 --- a/paddle/platform/place.h +++ b/paddle/platform/place.h @@ -2,49 +2,48 @@ #include #include -namespace majel { +namespace paddle { +namespace platform { struct CpuPlace { - CpuPlace() {} // WORKAROUND: for some reason, omitting this constructor - // causes errors with boost 1.59 and OSX - // needed for variant equality comparison - inline bool operator==(const CpuPlace&) const { return true; } + // WORKAROUND: for some reason, omitting this constructor + // causes errors with boost 1.59 and OSX + CpuPlace() {} - inline bool operator!=(const CpuPlace&) const { return false; } + // needed for variant equality comparison + inline bool operator==(const CpuPlace &) const { return true; } + inline bool operator!=(const CpuPlace &) const { return false; } }; struct GpuPlace { + GpuPlace() : GpuPlace(0) {} GpuPlace(int d) : device(d) {} // needed for variant equality comparison - inline bool operator==(const GpuPlace& o) const { return device == o.device; } - - inline bool operator!=(const GpuPlace& o) const { return !(*this == o); } + inline bool operator==(const GpuPlace &o) const { return device == o.device; } + inline bool operator!=(const GpuPlace &o) const { return !(*this == o); } - GpuPlace() : GpuPlace(0) {} int device; }; -class IsGpuPlace : public boost::static_visitor { -public: - bool operator()(const CpuPlace&) const { return false; } - - bool operator()(const GpuPlace& gpu) const { return true; } +struct IsGpuPlace : public boost::static_visitor { + bool operator()(const CpuPlace &) const { return false; } + bool operator()(const GpuPlace &gpu) const { return true; } }; typedef boost::variant Place; -void set_place(const Place&); - -const Place& get_place(); +void set_place(const Place &); +const Place &get_place(); const GpuPlace default_gpu(); 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_gpu_place(const Place &); +bool is_cpu_place(const Place &); +bool places_are_same_class(const Place &, const Place &); -std::ostream& operator<<(std::ostream&, const majel::Place&); +std::ostream &operator<<(std::ostream &, const Place &); -} // namespace majel +} // namespace platform +} // namespace paddle diff --git a/paddle/platform/place_test.cc b/paddle/platform/place_test.cc index 6a099ae6b6e..9cd9c25b167 100644 --- a/paddle/platform/place_test.cc +++ b/paddle/platform/place_test.cc @@ -1,10 +1,10 @@ -#include "paddle/majel/place.h" -#include +#include "paddle/platform/place.h" #include "gtest/gtest.h" +#include TEST(Place, Equality) { - majel::CpuPlace cpu; - majel::GpuPlace g0(0), g1(1), gg0(0); + paddle::platform::CpuPlace cpu; + paddle::platform::GpuPlace g0(0), g1(1), gg0(0); EXPECT_EQ(cpu, cpu); EXPECT_EQ(g0, g0); @@ -13,28 +13,28 @@ TEST(Place, Equality) { EXPECT_NE(g0, g1); - EXPECT_TRUE(majel::places_are_same_class(g0, gg0)); - EXPECT_FALSE(majel::places_are_same_class(g0, cpu)); + EXPECT_TRUE(paddle::platform::places_are_same_class(g0, gg0)); + EXPECT_FALSE(paddle::platform::places_are_same_class(g0, cpu)); } TEST(Place, Default) { - EXPECT_TRUE(majel::is_gpu_place(majel::get_place())); - EXPECT_TRUE(majel::is_gpu_place(majel::default_gpu())); - EXPECT_TRUE(majel::is_cpu_place(majel::default_cpu())); + EXPECT_TRUE(paddle::platform::is_gpu_place(paddle::platform::get_place())); + EXPECT_TRUE(paddle::platform::is_gpu_place(paddle::platform::default_gpu())); + EXPECT_TRUE(paddle::platform::is_cpu_place(paddle::platform::default_cpu())); - majel::set_place(majel::CpuPlace()); - EXPECT_TRUE(majel::is_cpu_place(majel::get_place())); + paddle::platform::set_place(paddle::platform::CpuPlace()); + EXPECT_TRUE(paddle::platform::is_cpu_place(paddle::platform::get_place())); } TEST(Place, Print) { { std::stringstream ss; - ss << majel::GpuPlace(1); + ss << paddle::platform::GpuPlace(1); EXPECT_EQ("GpuPlace(1)", ss.str()); } { std::stringstream ss; - ss << majel::CpuPlace(); + ss << paddle::platform::CpuPlace(); EXPECT_EQ("CpuPlace", ss.str()); } } -- GitLab From d03159be093ef9cc3af01b71ec87f31c443328e5 Mon Sep 17 00:00:00 2001 From: Yi Wang Date: Mon, 19 Jun 2017 15:24:06 -0700 Subject: [PATCH 0688/3256] Use Google Style in new directories --- .clang-format | 5 ++++- paddle/framework/.clang-format | 5 +++++ paddle/platform/.clang-format | 5 +++++ 3 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 paddle/framework/.clang-format create mode 100644 paddle/platform/.clang-format diff --git a/.clang-format b/.clang-format index 5a30b52cf4b..9ba433b1736 100644 --- a/.clang-format +++ b/.clang-format @@ -16,7 +16,10 @@ --- Language: Cpp BasedOnStyle: Google -UseTab Never +IndentWidth: 2 +TabWidth: 2 +ContinuationIndentWidth: 4 +AccessModifierOffset: -2 # The private/protected/public has no indent in class Standard: Cpp11 AllowAllParametersOfDeclarationOnNextLine: true BinPackParameters: false diff --git a/paddle/framework/.clang-format b/paddle/framework/.clang-format new file mode 100644 index 00000000000..29282dc87e2 --- /dev/null +++ b/paddle/framework/.clang-format @@ -0,0 +1,5 @@ +--- +Language: Cpp +BasedOnStyle: Google +Standard: Cpp11 +... diff --git a/paddle/platform/.clang-format b/paddle/platform/.clang-format new file mode 100644 index 00000000000..29282dc87e2 --- /dev/null +++ b/paddle/platform/.clang-format @@ -0,0 +1,5 @@ +--- +Language: Cpp +BasedOnStyle: Google +Standard: Cpp11 +... -- GitLab From 8913aff1945e1bd0ff618292f91db025c84a7562 Mon Sep 17 00:00:00 2001 From: Yi Wang Date: Mon, 19 Jun 2017 16:45:47 -0700 Subject: [PATCH 0689/3256] Make paddle/framework buildable and passed --- paddle/CMakeLists.txt | 1 + paddle/framework/ddim.cc | 18 ++--- paddle/framework/ddim.h | 23 +++---- paddle/framework/ddim_test.cc | 26 +++---- paddle/framework/dim.h | 24 ++++--- paddle/framework/dim_test.cu | 126 +++++++++++++++++----------------- paddle/platform/assert.h | 35 +++++----- 7 files changed, 125 insertions(+), 128 deletions(-) diff --git a/paddle/CMakeLists.txt b/paddle/CMakeLists.txt index fc33def5569..ccfb497d41f 100644 --- a/paddle/CMakeLists.txt +++ b/paddle/CMakeLists.txt @@ -18,6 +18,7 @@ add_subdirectory(strings) find_package(Boost QUIET) add_subdirectory(platform) +add_subdirectory(framework) # if(Boost_FOUND) # include_directories(${Boost_INCLUDE_DIRS}) diff --git a/paddle/framework/ddim.cc b/paddle/framework/ddim.cc index f32408ed530..3f949a6595e 100644 --- a/paddle/framework/ddim.cc +++ b/paddle/framework/ddim.cc @@ -1,6 +1,7 @@ -#include "paddle/majel/ddim.h" +#include "paddle/framework/ddim.h" -namespace majel { +namespace paddle { +namespace framework { ///@cond HIDDEN @@ -66,7 +67,7 @@ DDim make_ddim(const std::vector& dims) { ///@cond HIDDEN // XXX For some reason, putting this in an anonymous namespace causes errors class DynamicMutableIndexer : public boost::static_visitor { -public: + public: DynamicMutableIndexer(int idx) : idx_(idx) {} template @@ -74,12 +75,12 @@ public: return dim[idx_]; } -private: + private: int idx_; }; class DynamicConstIndexer : public boost::static_visitor { -public: + public: DynamicConstIndexer(int idx) : idx_(idx) {} template @@ -87,7 +88,7 @@ public: return dim[idx_]; } -private: + private: int idx_; }; @@ -213,10 +214,11 @@ struct DDimPrinter : boost::static_visitor { ///\endcond -std::ostream& operator<<(std::ostream& os, const majel::DDim& ddim) { +std::ostream& operator<<(std::ostream& os, const DDim& ddim) { DDimPrinter printer(os); boost::apply_visitor(printer, ddim); return os; } -} // namespace majel +} // namespace framework +} // namespace paddle diff --git a/paddle/framework/ddim.h b/paddle/framework/ddim.h index 7be756f8c09..223c4180bee 100644 --- a/paddle/framework/ddim.h +++ b/paddle/framework/ddim.h @@ -5,20 +5,14 @@ #include #include -#include "paddle/majel/dim.h" +#include "paddle/framework/dim.h" -namespace majel { +namespace paddle { +namespace framework { namespace { -typedef boost::variant, - Dim<2>, - Dim<3>, - Dim<4>, - Dim<5>, - Dim<6>, - Dim<7>, - Dim<8>, - Dim<9>> +typedef boost::variant, Dim<2>, Dim<3>, Dim<4>, Dim<5>, Dim<6>, Dim<7>, + Dim<8>, Dim<9>> DDimVar; } @@ -95,14 +89,15 @@ ssize_t product(const DDim& ddim); int arity(const DDim& ddim); -std::ostream& operator<<(std::ostream&, const majel::DDim&); +std::ostream& operator<<(std::ostream&, const DDim&); -} // namespace majel +} // namespace framework +} // namespace paddle namespace boost { template -T get(const majel::DDim& in) { +T get(const paddle::framework::DDim& in) { return boost::get(in.var); } diff --git a/paddle/framework/ddim_test.cc b/paddle/framework/ddim_test.cc index a5b8a7c4d26..e5c84d7abe9 100644 --- a/paddle/framework/ddim_test.cc +++ b/paddle/framework/ddim_test.cc @@ -4,18 +4,18 @@ #include #include "gtest/gtest.h" -#include "paddle/majel/ddim.h" +#include "paddle/framework/ddim.h" TEST(DDim, Equality) { // construct a DDim from an initialization list - majel::DDim ddim = majel::make_ddim({9, 1, 5}); + paddle::framework::DDim ddim = paddle::framework::make_ddim({9, 1, 5}); EXPECT_EQ(ddim[0], 9); EXPECT_EQ(ddim[1], 1); EXPECT_EQ(ddim[2], 5); // construct a DDim from a vector std::vector vec({9, 1, 5}); - majel::DDim vddim = majel::make_ddim(vec); + paddle::framework::DDim vddim = paddle::framework::make_ddim(vec); EXPECT_EQ(ddim[0], 9); EXPECT_EQ(ddim[1], 1); EXPECT_EQ(ddim[2], 5); @@ -23,43 +23,43 @@ TEST(DDim, Equality) { // mutate a DDim ddim[1] = 2; EXPECT_EQ(ddim[1], 2); - majel::set(ddim, 0, 6); - EXPECT_EQ(majel::get(ddim, 0), 6); + paddle::framework::set(ddim, 0, 6); + EXPECT_EQ(paddle::framework::get(ddim, 0), 6); // vectorize a DDim - std::vector res_vec = majel::vectorize(vddim); + std::vector res_vec = paddle::framework::vectorize(vddim); EXPECT_EQ(res_vec[0], 9); EXPECT_EQ(res_vec[1], 1); EXPECT_EQ(res_vec[2], 5); - majel::Dim<3> d(3, 2, 1); - res_vec = majel::vectorize(majel::DDim(d)); + paddle::framework::Dim<3> d(3, 2, 1); + res_vec = paddle::framework::vectorize(paddle::framework::DDim(d)); EXPECT_EQ(res_vec[0], 3); EXPECT_EQ(res_vec[1], 2); EXPECT_EQ(res_vec[2], 1); // add two DDims - majel::DDim ddim_sum = ddim + vddim; + paddle::framework::DDim ddim_sum = ddim + vddim; EXPECT_EQ(ddim_sum[0], 15); EXPECT_EQ(ddim_sum[1], 3); EXPECT_EQ(ddim_sum[2], 10); // multiply two DDims - majel::DDim ddim_mul = ddim * vddim; + paddle::framework::DDim ddim_mul = ddim * vddim; EXPECT_EQ(ddim_mul[0], 54); EXPECT_EQ(ddim_mul[1], 2); EXPECT_EQ(ddim_mul[2], 25); // arity of a DDim - EXPECT_EQ(majel::arity(ddim), 3); + EXPECT_EQ(paddle::framework::arity(ddim), 3); // product of a DDim - EXPECT_EQ(majel::product(vddim), 45); + EXPECT_EQ(paddle::framework::product(vddim), 45); } TEST(DDim, Print) { // print a DDim std::stringstream ss; - majel::DDim ddim = majel::make_ddim({2, 3, 4}); + paddle::framework::DDim ddim = paddle::framework::make_ddim({2, 3, 4}); ss << ddim; EXPECT_EQ("2, 3, 4", ss.str()); } diff --git a/paddle/framework/dim.h b/paddle/framework/dim.h index c4b0c6aea68..bcde291d12d 100644 --- a/paddle/framework/dim.h +++ b/paddle/framework/dim.h @@ -5,10 +5,11 @@ #include #include -#include "paddle/majel/detail/cuda_assert.h" -#include "paddle/majel/detail/hostdevice.h" +#include "paddle/platform/assert.h" +#include "paddle/platform/hostdevice.h" -namespace majel { +namespace paddle { +namespace framework { // Statically sized, statically indexed dimension template @@ -74,7 +75,7 @@ struct Dim<1> { throw std::invalid_argument("Index out of range."); } #else - MAJEL_ASSERT(idx < size.head); + PADDLE_ASSERT(idx < size.head); #endif } @@ -131,7 +132,7 @@ HOSTDEVICE int& indexer(Dim& dim, int idx) { throw std::invalid_argument("Tried to access a negative dimension"); } #else - MAJEL_ASSERT(idx >= 0); + PADDLE_ASSERT(idx >= 0); #endif if (idx == 0) { return dim.head; @@ -146,7 +147,7 @@ HOSTDEVICE int& indexer<1>(Dim<1>& dim, int idx) { throw std::invalid_argument("Invalid index"); } #else - MAJEL_ASSERT(idx == 0); + PADDLE_ASSERT(idx == 0); #endif return dim.head; } @@ -158,7 +159,7 @@ HOSTDEVICE int indexer(const Dim& dim, int idx) { throw std::invalid_argument("Tried to access a negative dimension"); } #else - MAJEL_ASSERT(idx >= 0); + PADDLE_ASSERT(idx >= 0); #endif if (idx == 0) { return dim.head; @@ -173,7 +174,7 @@ HOSTDEVICE int indexer<1>(const Dim<1>& dim, int idx) { throw std::invalid_argument("Invalid index"); } #else - MAJEL_ASSERT(idx == 0); + PADDLE_ASSERT(idx == 0); #endif return dim.head; } @@ -411,7 +412,7 @@ HOSTDEVICE Dim make_dim(Args... idxes) { // XXX For some reason, overloading fails to resolve this correctly template typename std::enable_if<(i > 1), std::ostream&>::type operator<<( - std::ostream& os, const majel::Dim& d) { + std::ostream& os, const Dim& d) { os << d.head << ", " << d.tail; return os; } @@ -420,7 +421,7 @@ typename std::enable_if<(i > 1), std::ostream&>::type operator<<( // XXX I wish this could be an overload instead of a template template typename std::enable_if<(i == 1), std::ostream&>::type operator<<( - std::ostream& os, const majel::Dim& d) { + std::ostream& os, const Dim& d) { os << d.head; return os; } @@ -448,4 +449,5 @@ HOSTDEVICE Dim linear_to_dimension(int linear_index, Dim extents) { return result; } -} // namespace majel +} // namespace framework +} // namespace paddle diff --git a/paddle/framework/dim_test.cu b/paddle/framework/dim_test.cu index a7d81e595be..809bf048266 100644 --- a/paddle/framework/dim_test.cu +++ b/paddle/framework/dim_test.cu @@ -1,49 +1,49 @@ #include #include -#include "paddle/majel/dim.h" +#include "paddle/framework/dim.h" #include "gtest/gtest.h" -__global__ void test(majel::Dim<2>* o) { - o[0] = majel::make_dim(5, 6); +__global__ void test(paddle::framework::Dim<2>* o) { + o[0] = paddle::framework::make_dim(5, 6); } __global__ void dyn_idx_gpu(int* o) { - auto d = majel::make_dim(5, 6); + auto d = paddle::framework::make_dim(5, 6); o[0] = d[1]; } TEST(Dim, Equality) { // construct a Dim on the CPU - auto a = majel::make_dim(3, 4); - EXPECT_EQ(majel::get<0>(a), 3); - EXPECT_EQ(majel::get<1>(a), 4); + auto a = paddle::framework::make_dim(3, 4); + EXPECT_EQ(paddle::framework::get<0>(a), 3); + EXPECT_EQ(paddle::framework::get<1>(a), 4); // construct a Dim on the GPU - thrust::device_vector> t(2); + thrust::device_vector> t(2); test<<<1,1>>>(thrust::raw_pointer_cast(t.data())); a = t[0]; - EXPECT_EQ(majel::get<0>(a), 5); - EXPECT_EQ(majel::get<1>(a), 6); + EXPECT_EQ(paddle::framework::get<0>(a), 5); + EXPECT_EQ(paddle::framework::get<1>(a), 6); // linearization - auto b = majel::make_dim(7, 8); - EXPECT_EQ(majel::linearize(a, b), 83); + auto b = paddle::framework::make_dim(7, 8); + EXPECT_EQ(paddle::framework::linearize(a, b), 83); // product - EXPECT_EQ(majel::product(a), 30); + EXPECT_EQ(paddle::framework::product(a), 30); // mutate a Dim - majel::get<1>(b) = 10; - EXPECT_EQ(majel::get<0>(b), 7); - EXPECT_EQ(majel::get<1>(b), 10); + paddle::framework::get<1>(b) = 10; + EXPECT_EQ(paddle::framework::get<0>(b), 7); + EXPECT_EQ(paddle::framework::get<1>(b), 10); // dynamic access - majel::get(b, 0) = 8; + paddle::framework::get(b, 0) = 8; b[1] = 11; - EXPECT_EQ(majel::get<0>(b), 8); - EXPECT_EQ(majel::get<1>(b), 11); - EXPECT_EQ(majel::get(b, 0), 8); + EXPECT_EQ(paddle::framework::get<0>(b), 8); + EXPECT_EQ(paddle::framework::get<1>(b), 11); + EXPECT_EQ(paddle::framework::get(b, 0), 8); EXPECT_EQ(b[1], 11); // dynamic access on GPU @@ -53,49 +53,49 @@ TEST(Dim, Equality) { EXPECT_EQ(res, 6); // ex_prefix_mul - majel::Dim<3> c = majel::ex_prefix_mul(majel::Dim<3>(3, 4, 5)); - EXPECT_EQ(majel::get<0>(c), 1); - EXPECT_EQ(majel::get<1>(c), 3); - EXPECT_EQ(majel::get<2>(c), 12); + paddle::framework::Dim<3> c = paddle::framework::ex_prefix_mul(paddle::framework::Dim<3>(3, 4, 5)); + EXPECT_EQ(paddle::framework::get<0>(c), 1); + EXPECT_EQ(paddle::framework::get<1>(c), 3); + EXPECT_EQ(paddle::framework::get<2>(c), 12); // contiguous_strides - c = majel::contiguous_strides(majel::Dim<3>(10, 1, 10)); - EXPECT_EQ(majel::get<0>(c), 1); - EXPECT_EQ(majel::get<1>(c), 0); - EXPECT_EQ(majel::get<2>(c), 10); - c = majel::contiguous_strides(majel::Dim<3>(10, 10, 1)); - EXPECT_EQ(majel::get<0>(c), 1); - EXPECT_EQ(majel::get<1>(c), 10); - EXPECT_EQ(majel::get<2>(c), 0); - c = majel::contiguous_strides(majel::Dim<3>(1, 10, 10)); - EXPECT_EQ(majel::get<0>(c), 0); - EXPECT_EQ(majel::get<1>(c), 1); - EXPECT_EQ(majel::get<2>(c), 10); - c = majel::contiguous_strides(majel::Dim<3>(2, 3, 4)); - EXPECT_EQ(majel::get<0>(c), 1); - EXPECT_EQ(majel::get<1>(c), 2); - EXPECT_EQ(majel::get<2>(c), 6); + c = paddle::framework::contiguous_strides(paddle::framework::Dim<3>(10, 1, 10)); + EXPECT_EQ(paddle::framework::get<0>(c), 1); + EXPECT_EQ(paddle::framework::get<1>(c), 0); + EXPECT_EQ(paddle::framework::get<2>(c), 10); + c = paddle::framework::contiguous_strides(paddle::framework::Dim<3>(10, 10, 1)); + EXPECT_EQ(paddle::framework::get<0>(c), 1); + EXPECT_EQ(paddle::framework::get<1>(c), 10); + EXPECT_EQ(paddle::framework::get<2>(c), 0); + c = paddle::framework::contiguous_strides(paddle::framework::Dim<3>(1, 10, 10)); + EXPECT_EQ(paddle::framework::get<0>(c), 0); + EXPECT_EQ(paddle::framework::get<1>(c), 1); + EXPECT_EQ(paddle::framework::get<2>(c), 10); + c = paddle::framework::contiguous_strides(paddle::framework::Dim<3>(2, 3, 4)); + EXPECT_EQ(paddle::framework::get<0>(c), 1); + EXPECT_EQ(paddle::framework::get<1>(c), 2); + EXPECT_EQ(paddle::framework::get<2>(c), 6); // generate from an index - auto size = majel::make_dim(4, 5, 2); - c = majel::Dim<3>(14, size); - EXPECT_EQ(majel::get<0>(c), 2); - EXPECT_EQ(majel::get<1>(c), 3); - EXPECT_EQ(majel::get<2>(c), 0); - c = majel::Dim<3>(25, size); - EXPECT_EQ(majel::get<0>(c), 1); - EXPECT_EQ(majel::get<1>(c), 1); - EXPECT_EQ(majel::get<2>(c), 1); + auto size = paddle::framework::make_dim(4, 5, 2); + c = paddle::framework::Dim<3>(14, size); + EXPECT_EQ(paddle::framework::get<0>(c), 2); + EXPECT_EQ(paddle::framework::get<1>(c), 3); + EXPECT_EQ(paddle::framework::get<2>(c), 0); + c = paddle::framework::Dim<3>(25, size); + EXPECT_EQ(paddle::framework::get<0>(c), 1); + EXPECT_EQ(paddle::framework::get<1>(c), 1); + EXPECT_EQ(paddle::framework::get<2>(c), 1); } TEST(Dim, Bool) { - auto a = majel::make_dim(3, 4); - auto b = majel::make_dim(5, 6); - auto c = majel::make_dim(3, 4); + auto a = paddle::framework::make_dim(3, 4); + auto b = paddle::framework::make_dim(5, 6); + auto c = paddle::framework::make_dim(3, 4); // in_bounds check - EXPECT_TRUE(majel::contained(a, b)); - EXPECT_FALSE(majel::contained(b, a)); + EXPECT_TRUE(paddle::framework::contained(a, b)); + EXPECT_FALSE(paddle::framework::contained(b, a)); // comparison EXPECT_TRUE(a == a); @@ -104,25 +104,25 @@ TEST(Dim, Bool) { // contiguous check int x = 4, y = 5, z = 2; - majel::Dim<3> sizef(x, y, z); - majel::Dim<3> stridea(1, x, x*y); - majel::Dim<3> strideb(2, 2*x, 2*x*y); - majel::Dim<3> stridec(1, x, 2*x*y); - EXPECT_TRUE(majel::contiguous(sizef, stridea)); - EXPECT_FALSE(majel::contiguous(sizef, strideb)); - EXPECT_FALSE(majel::contiguous(sizef, stridec)); + paddle::framework::Dim<3> sizef(x, y, z); + paddle::framework::Dim<3> stridea(1, x, x*y); + paddle::framework::Dim<3> strideb(2, 2*x, 2*x*y); + paddle::framework::Dim<3> stridec(1, x, 2*x*y); + EXPECT_TRUE(paddle::framework::contiguous(sizef, stridea)); + EXPECT_FALSE(paddle::framework::contiguous(sizef, strideb)); + EXPECT_FALSE(paddle::framework::contiguous(sizef, stridec)); } TEST(Dim, Print) { { std::stringstream ss; - auto a = majel::make_dim(2, 3); + auto a = paddle::framework::make_dim(2, 3); ss << a; EXPECT_EQ(ss.str(), "2, 3"); } { std::stringstream ss; - ss << majel::make_dim(8); + ss << paddle::framework::make_dim(8); EXPECT_EQ(ss.str(), "8"); } } diff --git a/paddle/platform/assert.h b/paddle/platform/assert.h index 9490d0ae3ef..70d3bf75062 100644 --- a/paddle/platform/assert.h +++ b/paddle/platform/assert.h @@ -5,28 +5,25 @@ #if defined(__APPLE__) && defined(__CUDA_ARCH__) && !defined(NDEBUG) #include -#define MAJEL_ASSERT(e) \ - do { \ - if (!(e)) { \ - printf( \ - "%s:%d Assertion `%s` failed.\n", __FILE__, __LINE__, TOSTRING(e)); \ - asm("trap;"); \ - } \ +#define PADDLE_ASSERT(e) \ + do { \ + if (!(e)) { \ + printf("%s:%d Assertion `%s` failed.\n", __FILE__, __LINE__, \ + TOSTRING(e)); \ + asm("trap;"); \ + } \ } while (0) -#define MAJEL_ASSERT_MSG(e, m) \ - do { \ - if (!(e)) { \ - printf("%s:%d Assertion `%s` failed (%s).\n", \ - __FILE__, \ - __LINE__, \ - TOSTRING(e), \ - m); \ - asm("trap;"); \ - } \ +#define PADDLE_ASSERT_MSG(e, m) \ + do { \ + if (!(e)) { \ + printf("%s:%d Assertion `%s` failed (%s).\n", __FILE__, __LINE__, \ + TOSTRING(e), m); \ + asm("trap;"); \ + } \ } while (0) #else #include -#define MAJEL_ASSERT(e) assert(e) -#define MAJEL_ASSERT_MSG(e, m) assert((e) && (m)) +#define PADDLE_ASSERT(e) assert(e) +#define PADDLE_ASSERT_MSG(e, m) assert((e) && (m)) #endif -- GitLab From b984ed9d540bcccb47feea205fd2c9affc5eeda5 Mon Sep 17 00:00:00 2001 From: Yi Wang Date: Mon, 19 Jun 2017 16:46:53 -0700 Subject: [PATCH 0690/3256] Remove unused majle/.gitignore --- paddle/majel/.gitignore | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 paddle/majel/.gitignore diff --git a/paddle/majel/.gitignore b/paddle/majel/.gitignore deleted file mode 100644 index 1f5acdebb56..00000000000 --- a/paddle/majel/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -build -third-party \ No newline at end of file -- GitLab From 8e4903354ad69523d7661a9d675d5199f522d886 Mon Sep 17 00:00:00 2001 From: Yi Wang Date: Mon, 19 Jun 2017 17:01:00 -0700 Subject: [PATCH 0691/3256] Fix Google Style --- paddle/platform/place.cc | 10 +++++----- paddle/platform/place.h | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/paddle/platform/place.cc b/paddle/platform/place.cc index d500c4cc7a0..1afd03c0116 100644 --- a/paddle/platform/place.cc +++ b/paddle/platform/place.cc @@ -6,16 +6,16 @@ namespace platform { namespace detail { class PlacePrinter : public boost::static_visitor<> { -public: + public: PlacePrinter(std::ostream &os) : os_(os) {} void operator()(const CpuPlace &) { os_ << "CpuPlace"; } void operator()(const GpuPlace &p) { os_ << "GpuPlace(" << p.device << ")"; } -private: + private: std::ostream &os_; }; -} // namespace detail +} // namespace detail static Place the_default_place; @@ -42,5 +42,5 @@ std::ostream &operator<<(std::ostream &os, const Place &p) { return os; } -} // namespace platform -} // namespace paddle +} // namespace platform +} // namespace paddle diff --git a/paddle/platform/place.h b/paddle/platform/place.h index ca1b76ef4b8..489572c526e 100644 --- a/paddle/platform/place.h +++ b/paddle/platform/place.h @@ -45,5 +45,5 @@ bool places_are_same_class(const Place &, const Place &); std::ostream &operator<<(std::ostream &, const Place &); -} // namespace platform -} // namespace paddle +} // namespace platform +} // namespace paddle -- GitLab From 3124b9d9cd32f608794cc4bf6ead75e9115b4821 Mon Sep 17 00:00:00 2001 From: Yi Wang Date: Mon, 19 Jun 2017 17:47:03 -0700 Subject: [PATCH 0692/3256] Make Google style include order --- paddle/platform/place_test.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paddle/platform/place_test.cc b/paddle/platform/place_test.cc index 9cd9c25b167..73fccceedf6 100644 --- a/paddle/platform/place_test.cc +++ b/paddle/platform/place_test.cc @@ -1,6 +1,6 @@ #include "paddle/platform/place.h" -#include "gtest/gtest.h" #include +#include "gtest/gtest.h" TEST(Place, Equality) { paddle::platform::CpuPlace cpu; -- GitLab From 7872f37650c1524d6f57d975731a7557352b7b9f Mon Sep 17 00:00:00 2001 From: hedaoyuan Date: Tue, 20 Jun 2017 11:16:19 +0800 Subject: [PATCH 0693/3256] Fix some compile error. --- paddle/function/ConvOp.h | 8 +++----- paddle/function/GemmConvOp.cpp | 30 +++++++++++++++++++++++++++--- paddle/function/NaiveConvOp.cpp | 13 +++++++++---- 3 files changed, 39 insertions(+), 12 deletions(-) diff --git a/paddle/function/ConvOp.h b/paddle/function/ConvOp.h index 65b9d1d53f9..bb4f48364b9 100644 --- a/paddle/function/ConvOp.h +++ b/paddle/function/ConvOp.h @@ -68,14 +68,12 @@ public: numOutputs_ = 1; } - virtual void calc(const BufferArgs& inputs, const BufferArgs& outputs) {} - // input can be INPUT and INPUT_GRAD // filter can be FILTER and FILTER_GRAD // output can be OUTPUT and OUTPUT_GRAD - void check(const TensorShape& input, - const TensorShape& filter, - const TensorShape& output) { + void checkShape(const TensorShape& input, + const TensorShape& filter, + const TensorShape& output) { // inputs and outputs arguments should be 4-dimensional. CHECK_EQ(input.ndims(), (size_t)4); CHECK_EQ(output.ndims(), (size_t)4); diff --git a/paddle/function/GemmConvOp.cpp b/paddle/function/GemmConvOp.cpp index c7a57801ed6..a40e5d9d2e7 100644 --- a/paddle/function/GemmConvOp.cpp +++ b/paddle/function/GemmConvOp.cpp @@ -117,15 +117,23 @@ public: ConvFunctionBase::init(config); } + virtual void check(const BufferArgs& inputs, + const BufferArgs& outputs) override { + const TensorShape& input = inputs[0].shape(); + const TensorShape& filter = inputs[1].shape(); + const TensorShape& output = outputs[0].shape(); + checkShape(input, filter, output); + } + void calc(const BufferArgs& inputs, const BufferArgs& outputs) override { CHECK_EQ(numInputs_, inputs.size()); CHECK_EQ(numOutputs_, outputs.size()); + check(inputs, outputs); // TODO(hedaoyuan): Need to define some index macros, // to avoid useing 0 and 1. const TensorShape& input = inputs[0].shape(); const TensorShape& filter = inputs[1].shape(); const TensorShape& output = outputs[0].shape(); - check(input, filter, output); real beta; if (outputs[0].getArgType() == ADD_TO) { @@ -209,16 +217,24 @@ public: ConvFunctionBase::init(config); } + virtual void check(const BufferArgs& inputs, + const BufferArgs& outputs) override { + const TensorShape& output = inputs[0].shape(); + const TensorShape& filter = inputs[1].shape(); + const TensorShape& input = outputs[0].shape(); + checkShape(input, filter, output); + } + void calc(const BufferArgs& inputs, const BufferArgs& outputs) override { CHECK_EQ(numInputs_, inputs.size()); CHECK_EQ(numOutputs_, outputs.size()); + check(inputs, outputs); // Since the implementation of Col2ImFunctor is ADD_TO, // this function only supports ADD_TO mode. CHECK_EQ(outputs[0].getArgType(), ADD_TO); const TensorShape& output = inputs[0].shape(); const TensorShape& filter = inputs[1].shape(); const TensorShape& input = outputs[0].shape(); - check(input, filter, output); size_t batchSize = input[0]; size_t inputChannels = input[1]; @@ -295,13 +311,21 @@ public: ConvFunctionBase::init(config); } + virtual void check(const BufferArgs& inputs, + const BufferArgs& outputs) override { + const TensorShape& output = inputs[0].shape(); + const TensorShape& input = inputs[1].shape(); + const TensorShape& filter = outputs[0].shape(); + checkShape(input, filter, output); + } + void calc(const BufferArgs& inputs, const BufferArgs& outputs) override { CHECK_EQ(numInputs_, inputs.size()); CHECK_EQ(numOutputs_, outputs.size()); + check(inputs, outputs); const TensorShape& output = inputs[0].shape(); const TensorShape& input = inputs[1].shape(); const TensorShape& filter = outputs[0].shape(); - check(input, filter, output); real beta; if (outputs[0].getArgType() == ADD_TO) { diff --git a/paddle/function/NaiveConvOp.cpp b/paddle/function/NaiveConvOp.cpp index 1d204f99e0e..70bd196a674 100644 --- a/paddle/function/NaiveConvOp.cpp +++ b/paddle/function/NaiveConvOp.cpp @@ -90,14 +90,19 @@ public: ConvFunctionBase::init(config); } - void calc(const BufferArgs& inputs, const BufferArgs& outputs) override { - CHECK_EQ(numInputs_, inputs.size()); - CHECK_EQ(numOutputs_, outputs.size()); + virtual void check(const BufferArgs& inputs, + const BufferArgs& outputs) override { const TensorShape& input = inputs[0].shape(); const TensorShape& filter = inputs[1].shape(); const TensorShape& output = outputs[0].shape(); - check(input, filter, output); + checkShape(input, filter, output); + } + + void calc(const BufferArgs& inputs, const BufferArgs& outputs) override { + CHECK_EQ(numInputs_, inputs.size()); + CHECK_EQ(numOutputs_, outputs.size()); CHECK_EQ(outputs[0].getArgType(), ASSIGN_TO); + check(inputs, outputs); size_t batchSize = inputs[0].shape()[0]; size_t inputChannels = inputs[0].shape()[1]; -- GitLab From 61222578b457b8efc32dd2899aa7653a880ed801 Mon Sep 17 00:00:00 2001 From: xuwei06 Date: Mon, 19 Jun 2017 23:45:25 -0700 Subject: [PATCH 0694/3256] Allow printer layer to print user provided message For example: layer.printer(input=[x, y], format="x=%s y=%s") --- paddle/gserver/layers/PrintLayer.cpp | 27 +++++++++++++++++-- python/paddle/trainer/config_parser.py | 8 +++++- .../paddle/trainer_config_helpers/layers.py | 3 ++- .../protostr/test_print_layer.protostr | 1 + 4 files changed, 35 insertions(+), 4 deletions(-) diff --git a/paddle/gserver/layers/PrintLayer.cpp b/paddle/gserver/layers/PrintLayer.cpp index de198af111b..a97fa6bf78f 100644 --- a/paddle/gserver/layers/PrintLayer.cpp +++ b/paddle/gserver/layers/PrintLayer.cpp @@ -22,10 +22,33 @@ public: void forward(PassType passType) override { Layer::forward(passType); + std::vector vals; for (size_t i = 0; i != inputLayers_.size(); ++i) { - getInput(i).printValueString(LOG(INFO), - "layer=" + inputLayers_[i]->getName() + " "); + std::ostringstream s; + getInput(i).printValueString(s, ""); + vals.push_back(s.str()); } + size_t pos = 0; + int i = 0; + std::ostringstream s; + const std::string& format = config_.user_arg(); + while (true) { + size_t pos1 = format.find("%s", pos); + if (pos1 == std::string::npos) break; + if (i >= vals.size()) { + break; + } + s << format.substr(pos, pos1 - pos) << vals[i]; + pos = pos1 + 2; + ++i; + } + if (i != inputLayers_.size()) { + LOG(ERROR) << "Number of value in the format (" << format + << ") is not same as the number of inputs (" + << inputLayers_.size() << ") at " << getName(); + } + s << format.substr(pos); + LOG(INFO) << s.str(); } void backward(const UpdateCallback& callback) override {} diff --git a/python/paddle/trainer/config_parser.py b/python/paddle/trainer/config_parser.py index 95419442571..c11dc09a8b9 100644 --- a/python/paddle/trainer/config_parser.py +++ b/python/paddle/trainer/config_parser.py @@ -1628,8 +1628,14 @@ class SelectiveFCLayer(LayerBase): @config_layer('print') class PrintLayer(LayerBase): - def __init__(self, name, inputs): + def __init__(self, name, inputs, format=None): super(PrintLayer, self).__init__(name, 'print', 0, inputs) + if format is None: + format = "\n".join([ + "layer=" + input.input_layer_name + " %s" + for input in self.inputs + ]) + self.config.user_arg = format @config_layer('priorbox') diff --git a/python/paddle/trainer_config_helpers/layers.py b/python/paddle/trainer_config_helpers/layers.py index a201c68a63b..b8ce0373c0e 100755 --- a/python/paddle/trainer_config_helpers/layers.py +++ b/python/paddle/trainer_config_helpers/layers.py @@ -964,7 +964,7 @@ def fc_layer(input, @wrap_name_default("print") -def printer_layer(input, name=None): +def printer_layer(input, format=None, name=None): """ Print the output value of input layers. This layer is useful for debugging. @@ -982,6 +982,7 @@ def printer_layer(input, name=None): Layer( name=name, + format=format, type=LayerType.PRINT_LAYER, inputs=[l.name for l in input], ) # this layer don't return anything, can not be input of other layer. diff --git a/python/paddle/trainer_config_helpers/tests/configs/protostr/test_print_layer.protostr b/python/paddle/trainer_config_helpers/tests/configs/protostr/test_print_layer.protostr index c402aff174a..f4cc492dfb9 100644 --- a/python/paddle/trainer_config_helpers/tests/configs/protostr/test_print_layer.protostr +++ b/python/paddle/trainer_config_helpers/tests/configs/protostr/test_print_layer.protostr @@ -12,6 +12,7 @@ layers { inputs { input_layer_name: "input" } + user_arg: "layer=input %s" } input_layer_names: "input" output_layer_names: "input" -- GitLab From 3438d650edee11f3488994370a95ab11696d28d1 Mon Sep 17 00:00:00 2001 From: xuwei06 Date: Mon, 19 Jun 2017 23:41:49 -0700 Subject: [PATCH 0695/3256] Fix bugs for rnn generation 1. v2.layer.parse_network does not correctly handle the generation output. 2. GatherAgentLayer does not correctly handle generation output when batch_size > 1 3. Fix CustomStackTrace for rnn group --- .../gradientmachines/NeuralNetwork.cpp | 9 +-- .../RecurrentGradientMachine.cpp | 16 +++-- .../RecurrentGradientMachine.h | 1 + paddle/gserver/layers/AgentLayer.cpp | 69 +++++++++---------- .../tests/sample_trainer_nest_rnn_gen.conf | 8 +-- .../trainer/tests/sample_trainer_rnn_gen.conf | 6 +- paddle/utils/CustomStackTrace.h | 6 +- paddle/utils/tests/test_CustomStackTrace.cpp | 1 - python/paddle/v2/layer.py | 16 ++++- 9 files changed, 76 insertions(+), 56 deletions(-) diff --git a/paddle/gserver/gradientmachines/NeuralNetwork.cpp b/paddle/gserver/gradientmachines/NeuralNetwork.cpp index 4512aacc81f..a361d7deace 100644 --- a/paddle/gserver/gradientmachines/NeuralNetwork.cpp +++ b/paddle/gserver/gradientmachines/NeuralNetwork.cpp @@ -241,11 +241,14 @@ void NeuralNetwork::forward(const std::vector& inArgs, dataLayers_[i]->setData(inArgs[i]); } + gLayerStackTrace.set_stage(true); + { for (auto& layer : layers_) { REGISTER_TIMER_INFO("ForwardTimer", layer->getName().c_str()); gLayerStackTrace.push(layer->getName()); layer->forward(passType); + gLayerStackTrace.pop(layer->getName()); } } @@ -254,9 +257,6 @@ void NeuralNetwork::forward(const std::vector& inArgs, for (auto& layer : outputLayers_) { outArgs->push_back(layer->getOutput()); } - if (passType == PASS_TEST) { - gLayerStackTrace.clear(); - } } void NeuralNetwork::resetState() { @@ -283,9 +283,10 @@ void NeuralNetwork::getState(MachineState& machineState) { } void NeuralNetwork::backward(const UpdateCallback& callback) { - gLayerStackTrace.pop(""); // tell layer trace is during backward. + gLayerStackTrace.set_stage(false); FOR_EACH_R(layer, layers_) { REGISTER_TIMER_INFO("BackwardTimer", (*layer)->getName().c_str()); + gLayerStackTrace.push((*layer)->getName()); if ((*layer)->needGradient()) { (*layer)->backward(callback); } diff --git a/paddle/gserver/gradientmachines/RecurrentGradientMachine.cpp b/paddle/gserver/gradientmachines/RecurrentGradientMachine.cpp index 3e930380226..867c99ede3f 100644 --- a/paddle/gserver/gradientmachines/RecurrentGradientMachine.cpp +++ b/paddle/gserver/gradientmachines/RecurrentGradientMachine.cpp @@ -208,6 +208,7 @@ void RecurrentGradientMachine::init( }); CHECK(subModelConfig != config.sub_models().end()); reversed_ = subModelConfig->reversed(); + generating_ = subModelConfig->has_generator(); inFrameLines_.resize(subModelConfig->in_links_size()); for (size_t i = 0; i < inFrameLines_.size(); ++i) { @@ -538,7 +539,7 @@ void RecurrentGradientMachine::forward(const std::vector& inArgs, The outputs are outFramesLines_[i].agentLayer */ - if (inFrameLines_.empty() && passType == PASS_TEST) { + if (generating_) { generateSequence(); return; } // else forward.. @@ -569,6 +570,9 @@ void RecurrentGradientMachine::forward(const std::vector& inArgs, } void RecurrentGradientMachine::backward(const UpdateCallback& callback) { + if (generating_) { + return; + } REGISTER_TIMER_INFO("RecurrentBwTime", "RecurrentBwTime"); AsyncGpuBlock asyncGpuBlock; for (int i = maxSequenceLength_ - 1; i >= 0; --i) { @@ -1321,11 +1325,10 @@ void RecurrentGradientMachine::fillGenOutputs() { batchMachineIdVec_.clear(); generator_.ids.clear(); + int* starts = generator_.outArg.sequenceStartPositions->getMutableData(false); + starts[0] = 0; if (numResults > 1) { real* probs = generator_.outArg.in->getData(); - int* starts = - generator_.outArg.sequenceStartPositions->getMutableData(false); - starts[0] = 0; for (size_t i = 0; i < finalPaths_.size(); ++i) { for (size_t j = 0; j < finalPaths_[i].size(); ++j) { Path& path = finalPaths_[i][j]; @@ -1348,7 +1351,10 @@ void RecurrentGradientMachine::fillGenOutputs() { } else { for (size_t i = 0; i < finalPaths_.size(); ++i) { CHECK(!finalPaths_[i].empty()); - generator_.ids = finalPaths_[i][0].ids; + generator_.ids.insert(generator_.ids.begin(), + finalPaths_[i][0].ids.begin(), + finalPaths_[i][0].ids.end()); + starts[i + 1] = starts[i] + finalPaths_[i][0].ids.size(); } } } diff --git a/paddle/gserver/gradientmachines/RecurrentGradientMachine.h b/paddle/gserver/gradientmachines/RecurrentGradientMachine.h index 8d94d7e2df2..8e30883ac72 100644 --- a/paddle/gserver/gradientmachines/RecurrentGradientMachine.h +++ b/paddle/gserver/gradientmachines/RecurrentGradientMachine.h @@ -414,6 +414,7 @@ protected: std::vector ids; // store generated sequences Argument outArg; // final output argument }; + bool generating_; Generator generator_; std::vector> frames_; diff --git a/paddle/gserver/layers/AgentLayer.cpp b/paddle/gserver/layers/AgentLayer.cpp index 31463823b3f..512932d9a55 100644 --- a/paddle/gserver/layers/AgentLayer.cpp +++ b/paddle/gserver/layers/AgentLayer.cpp @@ -109,6 +109,40 @@ void GatherAgentLayer::forwardValue(PassType passType) { } } +namespace { + +// dest[index[i]] <- src[i] for each i +void copyElements(const IVector& srcVec, + const IVector& indexVec, + IVector& destVec) { + const int* src = srcVec.getData(); + const int* index = indexVec.getData(); + int* dest = destVec.getData(); + int len = indexVec.getSize(); + CHECK_EQ(srcVec.getSize(), indexVec.getSize()); + for (int i = 0; i < len; ++i) { + dest[index[i]] = src[i]; + } +} +} + +void GatherAgentLayer::forwardIds(PassType passType) { + IVectorPtr realId = realLayers_[0]->getOutputLabel(); + if (!realId) return; + + IVector::resizeOrCreate(output_.ids, allIds_->getSize(), useGpu_); + IVectorPtr outId = output_.ids; + idsVec_.resize(idIndex_.size()); + + for (size_t i = 0; i < realLayers_.size(); ++i) { + const IVectorPtr& realId = realLayers_[i]->getOutputLabel(); + idsVec_[i] = IVector::create(allIds_->getData() + idIndex_[i], + /* size */ realId->getSize(), + useGpu_); + execViaCpu(©Elements, *realId, *idsVec_[i], *outId); + } +} + void GatherAgentLayer::backward(const UpdateCallback& callback) { (void)callback; const MatrixPtr& outputGrad = getOutputGrad(); @@ -174,41 +208,6 @@ void ScatterAgentLayer::backward(const UpdateCallback& callback) { REGISTER_LAYER(gather_agent, GatherAgentLayer); REGISTER_LAYER(scatter_agent, ScatterAgentLayer); -void GatherAgentLayer::forwardIds(PassType passType) { - int height = 0; - IVectorPtr idReal = realLayers_[0]->getOutputLabel(); - - if (!idReal) return; - - if (output_.subSequenceStartPositions) { - int* starts = output_.subSequenceStartPositions->getMutableData(false); - // Gather generator.idsVec - // if is beam search generation result. Get first result. - if (idReal->getData()[idReal->getSize() - 1] == -1) { - for (size_t i = 0; i < realLayers_.size(); ++i) { - // The first element stores first result size - idReal = realLayers_[i]->getOutputLabel(); - idReal->subVecFrom(*idReal, 1, idReal->getData()[0]); - } - } - for (size_t i = 0; i < realLayers_.size(); ++i) { - CHECK(realLayers_[i]->getOutputLabel()); - starts[i] = height; - height += realLayers_[i]->getOutputLabel()->getSize(); - } - starts[realLayers_.size()] = height; - output_.sequenceStartPositions->getMutableData(false)[1] = height; - - IVector::resizeOrCreate(output_.ids, height, false); - for (size_t i = 0; i < realLayers_.size(); ++i) { - output_.ids->subVec(starts[i], starts[i + 1] - starts[i]) - ->copyFrom(*realLayers_[i]->getOutputLabel()); - } - } else { - LOG(FATAL) << "Not implemented"; - } -} - void ScatterAgentLayer::forwardSequence(PassType passType) { Layer::forward(passType); CHECK_EQ(realLayer_->getDeviceId(), this->getDeviceId()); diff --git a/paddle/trainer/tests/sample_trainer_nest_rnn_gen.conf b/paddle/trainer/tests/sample_trainer_nest_rnn_gen.conf index d669fbc40cb..741a0aa71df 100644 --- a/paddle/trainer/tests/sample_trainer_nest_rnn_gen.conf +++ b/paddle/trainer/tests/sample_trainer_nest_rnn_gen.conf @@ -35,7 +35,7 @@ def outer_step(dummy_data): embedding_size=num_words)] def inner_step(dummy_memory, predict_word): - + # simplified RNN for testing with mixed_layer(size=num_words) as layer: layer += full_matrix_projection(input=predict_word, @@ -46,15 +46,15 @@ def outer_step(dummy_data): param_attr=ParamAttr(name="wordvec")) return out - + beam_gen = beam_search(name="rnn_gen", step=inner_step, input=gen_inputs, bos_id=0, eos_id=num_words-1, beam_size=2 if beam_flag else 1, - num_results_per_sample=2 if beam_flag else 1, - max_length=10) + num_results_per_sample=1, + max_length=10) return beam_gen beam_gen_concat = recurrent_group(name="rnn_gen_concat", diff --git a/paddle/trainer/tests/sample_trainer_rnn_gen.conf b/paddle/trainer/tests/sample_trainer_rnn_gen.conf index 2b337282f62..58d27f15ae1 100644 --- a/paddle/trainer/tests/sample_trainer_rnn_gen.conf +++ b/paddle/trainer/tests/sample_trainer_rnn_gen.conf @@ -33,7 +33,7 @@ gen_inputs = [StaticInput(input=dummy_data, size=2), embedding_size=num_words)] def step(dummy_memory, predict_word): - + # simplified RNN for testing with mixed_layer(size=num_words) as layer: layer += full_matrix_projection(input=predict_word, @@ -44,7 +44,7 @@ def step(dummy_memory, predict_word): param_attr=ParamAttr(name="wordvec")) return out - + beam_gen = beam_search(name="rnn_gen", step=step, input=gen_inputs, @@ -52,7 +52,7 @@ beam_gen = beam_search(name="rnn_gen", eos_id=num_words-1, beam_size=2 if beam_flag else 1, num_results_per_sample=2 if beam_flag else 1, - max_length=10) + max_length=10) seqtext_printer_evaluator(input=beam_gen, id_input=sent_id, diff --git a/paddle/utils/CustomStackTrace.h b/paddle/utils/CustomStackTrace.h index 6992e856223..52a6df94979 100644 --- a/paddle/utils/CustomStackTrace.h +++ b/paddle/utils/CustomStackTrace.h @@ -55,13 +55,17 @@ public: * Else, just set status to popping. */ void pop(const T& item) { - pushing() = false; auto& s = this->stack(); if (item == s.top()) { s.pop(); } } + /** + * @brief Indicate whether we are at forward or backward stage of computation + */ + void set_stage(bool isForward) { pushing() = isForward; } + /** * @brief clear current thread stack. */ diff --git a/paddle/utils/tests/test_CustomStackTrace.cpp b/paddle/utils/tests/test_CustomStackTrace.cpp index b5d9f93f137..c320074fbad 100644 --- a/paddle/utils/tests/test_CustomStackTrace.cpp +++ b/paddle/utils/tests/test_CustomStackTrace.cpp @@ -72,7 +72,6 @@ TEST(CustomStackTrace, normalTrain) { for (size_t i = 0; i < layerSize; ++i) { tracer.push("layer_" + paddle::str::to_string(i)); } - tracer.pop(""); for (size_t i = 0; i < layerSize; ++i) { tracer.pop("layer_" + paddle::str::to_string(layerSize - 1 - i)); } diff --git a/python/paddle/v2/layer.py b/python/paddle/v2/layer.py index bbb9c3ea8c1..4ade1c6f329 100644 --- a/python/paddle/v2/layer.py +++ b/python/paddle/v2/layer.py @@ -45,12 +45,12 @@ __all__ = ['data', 'parse_network'] def __need_to_keep__(name): return name in [ 'StaticInput', 'SubsequenceInput', 'GeneratedInput', 'LayerType', - 'layer_support' + 'layer_support', 'BaseGeneratedInput' ] def __need_to_wrap__(name): - return name not in ['AggregateLevel', 'ExpandLevel'] + return name not in ['AggregateLevel', 'ExpandLevel', 'BaseGeneratedInput'] def __convert_name__(inname): @@ -199,6 +199,15 @@ def __get_used_submodels__(layer_names): return submodel_names +def __get_submodel_data_out_links__(): + data_links = set() + for submodel in cp.g_config.model_config.sub_models: + for link in submodel.out_links: + if cp.g_layer_map[link.link_name].type == 'data': + data_links.add(link.link_name) + return data_links + + def __get_used_evaluators__(layer_names): evaluator_names = set() for e in cp.g_config.model_config.evaluators: @@ -264,6 +273,7 @@ def parse_network(output_layers, extra_layers=None): submodel_names = __get_used_submodels__(layer_names) submodel_names.add('root') evaluator_names = __get_used_evaluators__(layer_names) + data_out_links = __get_submodel_data_out_links__() input_layer_names = set() output_layer_names = set() @@ -279,7 +289,7 @@ def parse_network(output_layers, extra_layers=None): continue model_config.layers.extend([l]) if l.type == 'data': - if l.name in model_config.output_layer_names: + if l.name in data_out_links: """ In text generation, the outlink to save the generated word indices is a data_layer defined in recurrent_group. This -- GitLab From 02cc7d90a606875a44d605c18e17855ce8339652 Mon Sep 17 00:00:00 2001 From: xuwei06 Date: Mon, 19 Jun 2017 13:23:24 -0700 Subject: [PATCH 0696/3256] Evaluator for recurrent group Make the evaluators inside a recurrent goup true evaluator, meaning that their evaluation results are incorporated into the whole evaluator result. --- .../gradientmachines/NeuralNetwork.cpp | 35 ++++++++++++++++++- .../gserver/gradientmachines/NeuralNetwork.h | 2 ++ .../RecurrentGradientMachine.cpp | 20 ++--------- .../RecurrentGradientMachine.h | 2 -- 4 files changed, 38 insertions(+), 21 deletions(-) diff --git a/paddle/gserver/gradientmachines/NeuralNetwork.cpp b/paddle/gserver/gradientmachines/NeuralNetwork.cpp index 4512aacc81f..f245c16bfe9 100644 --- a/paddle/gserver/gradientmachines/NeuralNetwork.cpp +++ b/paddle/gserver/gradientmachines/NeuralNetwork.cpp @@ -320,7 +320,7 @@ public: } } - virtual void eval(const NeuralNetwork& nn) { + virtual void eval(const NeuralNetwork& nn) override { for (auto& evaluator : evaluators_) { evaluator->eval(nn); } @@ -395,6 +395,30 @@ private: } }; +class SubnetEvaluator : public CombinedEvaluator { +public: + SubnetEvaluator(const std::string& layerName, + std::unique_ptr&& evaluator) + : layerName_(layerName) { + addEvaluator(std::move(evaluator)); + } + virtual void eval(const NeuralNetwork& nn) override { + const LayerPtr& layer = nn.getLayer(layerName_); + CHECK(layer) << "Nonexisted layer: " << layerName_ << " in submodel " + << nn.getName(); + bool accessed = false; + layer->accessSubNetwork([this, &accessed](NeuralNetwork& subnet) { + subnet.eval(evaluators_[0].get()); + accessed = true; + }); + CHECK(accessed) << "There is no subnetwork for layer " << layerName_ + << " in submodel " << nn.getName(); + } + +protected: + std::string layerName_; +}; + Evaluator* NeuralNetwork::makeEvaluator() const { CombinedEvaluator* combinedEvaluator = new CombinedEvaluator(); auto subModelConfig = std::find_if(config_.sub_models().begin(), @@ -421,6 +445,15 @@ Evaluator* NeuralNetwork::makeEvaluator() const { combinedEvaluator->addEvaluator(std::move(evaluator)); } } + for (auto& layer : layers_) { + layer->accessSubNetwork( + [layer, combinedEvaluator](NeuralNetwork& subnet) { + std::unique_ptr subEvaluator(new SubnetEvaluator( + layer->getName(), + std::unique_ptr(subnet.makeEvaluator()))); + combinedEvaluator->addEvaluator(std::move(subEvaluator)); + }); + } } else { for (const EvaluatorConfig& evalConfig : config_.evaluators()) { std::unique_ptr evaluator(Evaluator::create(evalConfig)); diff --git a/paddle/gserver/gradientmachines/NeuralNetwork.h b/paddle/gserver/gradientmachines/NeuralNetwork.h index e7b6c438407..12810f64251 100644 --- a/paddle/gserver/gradientmachines/NeuralNetwork.h +++ b/paddle/gserver/gradientmachines/NeuralNetwork.h @@ -129,6 +129,8 @@ public: static NeuralNetwork* newNeuralNetwork(const std::string& name = "", NeuralNetwork* rootNetwork = nullptr); + const std::string& getName() const { return subModelName_; } + protected: /** * The constructor of NeuralNetwork. diff --git a/paddle/gserver/gradientmachines/RecurrentGradientMachine.cpp b/paddle/gserver/gradientmachines/RecurrentGradientMachine.cpp index 3e930380226..5d4b67da843 100644 --- a/paddle/gserver/gradientmachines/RecurrentGradientMachine.cpp +++ b/paddle/gserver/gradientmachines/RecurrentGradientMachine.cpp @@ -287,10 +287,6 @@ void RecurrentGradientMachine::init( parameterIds_.push_back(para->getID()); } } - - if (subModelConfig->evaluator_names_size() > 0) { - evaluator_.reset(frames_[0]->makeEvaluator()); - } } void RecurrentGradientMachine::resizeOrCreateFrames(int numFrames) { @@ -561,9 +557,6 @@ void RecurrentGradientMachine::forward(const std::vector& inArgs, std::vector outArgs; frames_[i]->forward(inArgs, &outArgs, passType); } - if (evaluator_ && passType == PASS_TEST) { - this->eval(evaluator_.get()); - } reorganizeOutput(passType); } @@ -577,11 +570,6 @@ void RecurrentGradientMachine::backward(const UpdateCallback& callback) { for (auto& memoryFrameLine : memoryFrameLines_) { memoryFrameLine.bootLayer->backward(nullptr); } - - // call printers here so the gradient can be printed - if (evaluator_) { - this->eval(evaluator_.get()); - } } void RecurrentGradientMachine::forwardBackward( @@ -595,9 +583,9 @@ void RecurrentGradientMachine::forwardBackward( void RecurrentGradientMachine::eval(Evaluator* evaluator) const { // call printers frame by frame for (int i = 0; i < maxSequenceLength_; ++i) { - LOG(INFO) << "Recurrent Layer Group eval frame " << i << " begin"; + VLOG(2) << "Recurrent Layer Group eval frame " << i << " begin"; evaluator->eval(*(frames_[i].get())); - LOG(INFO) << "Recurrent Layer Group eval frame " << i << " end"; + VLOG(2) << "Recurrent Layer Group eval frame " << i << " end"; } } @@ -1093,10 +1081,6 @@ void RecurrentGradientMachine::oneWaySearch(size_t batchSize) { copyDataOutlinkFrame(machineCur); - // call value printer - if (evaluator_) { - evaluator_->eval(*(frames_[machineCur].get())); - } // check eos const IVectorPtr& eosVec = eosFrameLine_->layers[machineCur]->getOutput().ids; diff --git a/paddle/gserver/gradientmachines/RecurrentGradientMachine.h b/paddle/gserver/gradientmachines/RecurrentGradientMachine.h index 8d94d7e2df2..9f957a9401f 100644 --- a/paddle/gserver/gradientmachines/RecurrentGradientMachine.h +++ b/paddle/gserver/gradientmachines/RecurrentGradientMachine.h @@ -428,8 +428,6 @@ protected: std::vector parameterIds_; // parameters actually used by this Layer Group - std::unique_ptr evaluator_; // frame printers in this layer group - // store final argument of outFrameLines_ std::vector dataArgs_; // store each frame's output argument of outFrameLines_ -- GitLab From 599b57af837f9038af880eab708ced82331c9349 Mon Sep 17 00:00:00 2001 From: Yi Wang Date: Tue, 20 Jun 2017 00:32:24 -0700 Subject: [PATCH 0697/3256] Check boost for new code, as it uses boost::variant --- paddle/CMakeLists.txt | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/paddle/CMakeLists.txt b/paddle/CMakeLists.txt index ccfb497d41f..d871f3e28a2 100644 --- a/paddle/CMakeLists.txt +++ b/paddle/CMakeLists.txt @@ -17,14 +17,11 @@ add_subdirectory(strings) find_package(Boost QUIET) -add_subdirectory(platform) -add_subdirectory(framework) -# if(Boost_FOUND) -# include_directories(${Boost_INCLUDE_DIRS}) -# include_directories(${CMAKE_CURRENT_SOURCE_DIR}) -# add_subdirectory(majel) -# endif() +if(Boost_FOUND) + add_subdirectory(platform) + add_subdirectory(framework) +endif() if(WITH_C_API) add_subdirectory(capi) -- GitLab From d9aac1e13db88587a908efdd4820f7a9c433b378 Mon Sep 17 00:00:00 2001 From: qiaolongfei Date: Tue, 20 Jun 2017 15:57:44 +0800 Subject: [PATCH 0698/3256] add WITH_Go to disable compile go to paddle --- CMakeLists.txt | 9 +++++++-- cmake/configure.cmake | 4 ++++ go/cmake/golang.cmake | 3 ++- go/pserver/cclient/CMakeLists.txt | 9 --------- go/pserver/cclient/test/test_mnist.py | 2 +- paddle/api/CMakeLists.txt | 2 +- paddle/api/PaddleAPI.h | 2 +- paddle/api/ParameterUpdater.cpp | 11 +++++++++-- paddle/optimizer/CMakeLists.txt | 6 ++++-- paddle/trainer/CMakeLists.txt | 16 +++++++++++++--- proto/CMakeLists.txt | 6 ++---- python/CMakeLists.txt | 2 +- 12 files changed, 45 insertions(+), 27 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2b6a80ca43c..5762baa2579 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -47,6 +47,7 @@ option(WITH_COVERAGE "Compile PaddlePaddle with code coverage" OFF) option(COVERALLS_UPLOAD "Package code coverage data to coveralls" OFF) option(ON_TRAVIS "Exclude special unit test on Travis CI" OFF) option(WITH_C_API "Compile PaddlePaddle with C-API(Prediction)" OFF) +option(WITH_Go "Compile PaddlePaddle with Go)" OFF) # CMAKE_BUILD_TYPE if(NOT CMAKE_BUILD_TYPE) @@ -107,6 +108,7 @@ include(configure) # add paddle env configuration include_directories("${PROJ_ROOT}") include_directories("${PROJ_ROOT}/paddle/cuda/include") include_directories("${CMAKE_CURRENT_BINARY_DIR}/proto") +include_directories("${CMAKE_BINARY_DIR}/go/pserver/cclient") set(EXTERNAL_LIBS ${GFLAGS_LIBRARIES} @@ -126,9 +128,12 @@ endif(WITH_GPU) add_subdirectory(proto) add_subdirectory(paddle) -add_subdirectory(go/master/c) add_subdirectory(python) -add_subdirectory(go/pserver/cclient) + +if(WITH_Go) + #add_subdirectory(go/pserver/cclient) + add_subdirectory(go/master/c) +endif(WITH_Go) if(WITH_DOC) add_subdirectory(doc) diff --git a/cmake/configure.cmake b/cmake/configure.cmake index 5e507e78f74..44c86bc4797 100644 --- a/cmake/configure.cmake +++ b/cmake/configure.cmake @@ -40,6 +40,10 @@ if(NOT CMAKE_CROSSCOMPILING) endif() endif() +if(NOT WITH_Go) + add_definitions(-DPADDLE_WITHOUT_GO) +endif() + if(NOT WITH_GPU) add_definitions(-DPADDLE_ONLY_CPU) add_definitions(-DHPPL_STUB_FUNC) diff --git a/go/cmake/golang.cmake b/go/cmake/golang.cmake index a5a43886f88..446f930f38b 100644 --- a/go/cmake/golang.cmake +++ b/go/cmake/golang.cmake @@ -10,7 +10,8 @@ function(GO_LIBRARY NAME BUILD_TYPE) else() set(BUILD_MODE -buildmode=c-shared) if(APPLE) - set(LIB_NAME "lib${NAME}.dylib") +# set(LIB_NAME "lib${NAME}.dylib") + set(LIB_NAME "lib${NAME}.so") else() set(LIB_NAME "lib${NAME}.so") endif() diff --git a/go/pserver/cclient/CMakeLists.txt b/go/pserver/cclient/CMakeLists.txt index 7967af51ee9..fff7ae78582 100644 --- a/go/pserver/cclient/CMakeLists.txt +++ b/go/pserver/cclient/CMakeLists.txt @@ -11,13 +11,4 @@ include(flags) go_library(paddle_pserver_cclient STATIC) -if(PROJ_ROOT) - add_custom_command(OUTPUT ${PROJ_ROOT}/paddle/trainer/libpaddle_pserver_cclient.a - COMMAND cp ${CMAKE_CURRENT_BINARY_DIR}/libpaddle_pserver_cclient.h ${PROJ_ROOT}/paddle/trainer/ - COMMAND cp ${CMAKE_CURRENT_BINARY_DIR}/libpaddle_pserver_cclient.a ${PROJ_ROOT}/paddle/trainer/ - WORKING_DIRECTORY ${PROJ_ROOT}/paddle - DEPENDS paddle_pserver_cclient) - add_custom_target(paddle_pserver_cclient_lib ALL DEPENDS ${PROJ_ROOT}/paddle/trainer/libpaddle_pserver_cclient.a) -endif(PROJ_ROOT) - add_subdirectory(test) diff --git a/go/pserver/cclient/test/test_mnist.py b/go/pserver/cclient/test/test_mnist.py index c3a3af55e28..d6fecc81b97 100644 --- a/go/pserver/cclient/test/test_mnist.py +++ b/go/pserver/cclient/test/test_mnist.py @@ -81,7 +81,7 @@ def main(): trainer = paddle.trainer.SGD(cost=cost, parameters=parameters, update_equation=optimizer, - is_local=False, + is_local=True, pserver_spec="localhost:3000") lists = [] diff --git a/paddle/api/CMakeLists.txt b/paddle/api/CMakeLists.txt index c9433a38de4..f2315e31cc0 100644 --- a/paddle/api/CMakeLists.txt +++ b/paddle/api/CMakeLists.txt @@ -16,7 +16,7 @@ set(API_HEADER Internal.h) add_library(paddle_api STATIC ${API_SOURCES}) -add_dependencies(paddle_api gen_proto_cpp paddle_pserver_cclient_lib) +add_dependencies(paddle_api gen_proto_cpp paddle_trainer_lib) INCLUDE(${SWIG_USE_FILE}) INCLUDE_DIRECTORIES(${PROJ_ROOT}/paddle) diff --git a/paddle/api/PaddleAPI.h b/paddle/api/PaddleAPI.h index 7565ea51fe3..d4b80fccbd7 100644 --- a/paddle/api/PaddleAPI.h +++ b/paddle/api/PaddleAPI.h @@ -842,7 +842,7 @@ public: int passCount, bool useSparseUpdater); static ParameterUpdater* createNewRemoteUpdater( - OptimizationConfig* config, const std::string pserverSpec); + OptimizationConfig* config, const std::string pserverSpec) throw(UnsupportError); ~ParameterUpdater(); /** diff --git a/paddle/api/ParameterUpdater.cpp b/paddle/api/ParameterUpdater.cpp index eaf8518ae2b..7eb860cffc3 100644 --- a/paddle/api/ParameterUpdater.cpp +++ b/paddle/api/ParameterUpdater.cpp @@ -15,7 +15,9 @@ limitations under the License. */ #include "PaddleAPI.h" #include "PaddleAPIPrivate.h" +#ifndef PADDLE_WITHOUT_GO #include "paddle/trainer/NewRemoteParameterUpdater.h" +#endif #include "paddle/trainer/RemoteParameterUpdater.h" #include "paddle/trainer/ThreadParameterUpdater.h" @@ -29,12 +31,17 @@ ParameterUpdater *ParameterUpdater::createLocalUpdater( return updater; } -ParameterUpdater *ParameterUpdater::createNewRemoteUpdater( - OptimizationConfig *config, const std::string pserverSpec) { +ParameterUpdater *ParameterUpdater::createNewRemoteUpdater ( + OptimizationConfig *config, const std::string pserverSpec) +throw(UnsupportError) { +#ifndef PADDLE_WITHOUT_GO auto updater = new ParameterUpdater(); updater->m->updater.reset(new paddle::NewRemoteParameterUpdater( config->m->getConfig(), pserverSpec)); return updater; +#else + throw UnsupportError(); +#endif } ParameterUpdater *ParameterUpdater::createRemoteUpdater( diff --git a/paddle/optimizer/CMakeLists.txt b/paddle/optimizer/CMakeLists.txt index 791be95efa9..4536f62ec7c 100644 --- a/paddle/optimizer/CMakeLists.txt +++ b/paddle/optimizer/CMakeLists.txt @@ -12,5 +12,7 @@ set(OPITMIZER_SRCS add_library(paddle_optimizer STATIC ${OPITMIZER_SRCS}) add_dependencies(paddle_optimizer gen_proto_cpp) -add_simple_unittest(serialization_test) -add_simple_unittest(parameter_optimizer_test) +if(WITH_TESTING) + add_simple_unittest(serialization_test) + add_simple_unittest(parameter_optimizer_test) +endif() diff --git a/paddle/trainer/CMakeLists.txt b/paddle/trainer/CMakeLists.txt index 9d246b66901..1072e3d5080 100644 --- a/paddle/trainer/CMakeLists.txt +++ b/paddle/trainer/CMakeLists.txt @@ -26,6 +26,13 @@ set(TRAINER_HEADERS ThreadParameterUpdater.h TrainerConfigHelper.h) +if(NOT WITH_Go) + list(REMOVE_ITEM TRAINER_SOURCES + NewRemoteParameterUpdater.cpp) + list(REMOVE_ITEM TRAINER_HEADERS + NewRemoteParameterUpdater.h) +endif() + add_library(paddle_trainer_lib STATIC ${TRAINER_SOURCES}) @@ -34,7 +41,7 @@ add_style_check_target(paddle_trainer_lib add_style_check_target(paddle_trainer_lib ${TRAINER_HEADERS}) add_dependencies(paddle_trainer_lib - gen_proto_cpp paddle_pserver_cclient_lib) + gen_proto_cpp) macro(add_paddle_exe TARGET_NAME) add_executable(${TARGET_NAME} ${ARGN}) @@ -63,5 +70,8 @@ if(APPLE) set(CMAKE_EXE_LINKER_FLAGS "-framework CoreFoundation -framework Security") endif() -target_link_libraries(paddle_trainer ${CMAKE_CURRENT_SOURCE_DIR}/libpaddle_pserver_cclient.a) -target_link_libraries(paddle_trainer_lib ${CMAKE_CURRENT_SOURCE_DIR}/libpaddle_pserver_cclient.a) +if(WITH_Go) + add_dependencies(paddle_trainer_lib paddle_pserver_cclient) + target_link_libraries(paddle_trainer ${CMAKE_BINARY_DIR}/go/pserver/cclient/libpaddle_pserver_cclient.a) + target_link_libraries(paddle_trainer_lib ${CMAKE_BINARY_DIR}/go/pserver/cclient/libpaddle_pserver_cclient.a) +endif(WITH_Go) diff --git a/proto/CMakeLists.txt b/proto/CMakeLists.txt index 9b98dd3fde4..c9426209907 100644 --- a/proto/CMakeLists.txt +++ b/proto/CMakeLists.txt @@ -36,10 +36,8 @@ foreach(filename ${proto_filenames}) DEPENDS ${filename} ${external_project_dependencies}) endforeach() -include_directories(${CMAKE_CURRENT_BINARY_DIR}/proto) - add_custom_target(gen_proto_cpp ALL DEPENDS ${PROTO_GEN}) add_custom_target(gen_proto_py ALL DEPENDS ${PROTO_GEN_PY}) -add_library(paddle_proto STATIC - ${PROTO_GEN}) + +add_library(paddle_proto STATIC ${PROTO_GEN}) target_include_directories(paddle_proto PUBLIC ${CMAKE_CURRENT_BINARY_DIR}) diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index 0e17c42d34f..3640dd3a75e 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -18,7 +18,7 @@ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/setup.py.in add_custom_command(OUTPUT ${OUTPUT_DIR}/.timestamp COMMAND env ${py_env} ${PYTHON_EXECUTABLE} setup.py bdist_wheel COMMAND ${CMAKE_COMMAND} -E touch ${OUTPUT_DIR}/.timestamp - DEPENDS gen_proto_py ${PY_FILES} ${external_project_dependencies} paddle_master_shared) + DEPENDS gen_proto_py ${PY_FILES} ${external_project_dependencies}) add_custom_target(paddle_python ALL DEPENDS ${OUTPUT_DIR}/.timestamp) -- GitLab From c9a76ebba2c0b050c157232c13670b17d2ba806d Mon Sep 17 00:00:00 2001 From: wanghaoshuang Date: Tue, 20 Jun 2017 16:03:49 +0800 Subject: [PATCH 0699/3256] modified xmap reader to process sample by order --- python/paddle/v2/reader/decorator.py | 36 ++++++++++++++++--- .../paddle/v2/reader/tests/decorator_test.py | 18 ++++++++++ 2 files changed, 50 insertions(+), 4 deletions(-) diff --git a/python/paddle/v2/reader/decorator.py b/python/paddle/v2/reader/decorator.py index c76faa596c9..68ffbd6f3d4 100644 --- a/python/paddle/v2/reader/decorator.py +++ b/python/paddle/v2/reader/decorator.py @@ -230,7 +230,7 @@ class XmapEndSignal(): pass -def xmap_readers(mapper, reader, process_num, buffer_size): +def xmap_readers(mapper, reader, process_num, buffer_size, order=False): """ Use multiprocess to map samples from reader by a mapper defined by user. And this function contains a buffered decorator. @@ -242,21 +242,32 @@ def xmap_readers(mapper, reader, process_num, buffer_size): :type process_num: int :param buffer_size: max buffer size :type buffer_size: int + :param order: keep the order of reader + :type order: bool :return: the decarated reader :rtype: callable """ end = XmapEndSignal() in_queue = Queue(buffer_size) out_queue = Queue(buffer_size) - + out_order = [0] # define a worker to read samples from reader to in_queue def read_worker(reader, in_queue): for i in reader(): in_queue.put(i) in_queue.put(end) + + # define a worker to read samples from reader to in_queue with order flag + def order_read_worker(reader, in_queue): + in_order = 0 + for i in reader(): + in_queue.put((in_order,i)) + in_order+=1 + in_queue.put(end) # start a read worker in a thread - t = Thread(target=read_worker, args=(reader, in_queue)) + target = order_read_worker if order else read_worker + t = Thread(target=target, args=(reader, in_queue)) t.daemon = True t.start() @@ -270,12 +281,29 @@ def xmap_readers(mapper, reader, process_num, buffer_size): sample = in_queue.get() in_queue.put(end) out_queue.put(end) + + # define a worker to handle samples from in_queue by mapper + # and put mapped samples into out_queue by order + def order_handle_worker(in_queue, out_queue, mapper, out_order): + ins = in_queue.get() + while not isinstance(ins, XmapEndSignal): + order, sample = ins + r = mapper(sample) + while order != out_order[0]: + pass + out_queue.put(r) + out_order[0] += 1 + ins = in_queue.get() + in_queue.put(end) + out_queue.put(end) # start several handle_workers + target = order_handle_worker if order else handle_worker + args = (in_queue, out_queue, mapper, out_order) if order else (in_queue, out_queue, mapper) workers = [] for i in xrange(process_num): worker = Thread( - target=handle_worker, args=(in_queue, out_queue, mapper)) + target=target, args=args) worker.daemon = True workers.append(worker) for w in workers: diff --git a/python/paddle/v2/reader/tests/decorator_test.py b/python/paddle/v2/reader/tests/decorator_test.py index 734154b9790..76db91a44b8 100644 --- a/python/paddle/v2/reader/tests/decorator_test.py +++ b/python/paddle/v2/reader/tests/decorator_test.py @@ -120,6 +120,24 @@ class TestShuffle(unittest.TestCase): total += 1 self.assertEqual(total, 10) +class TestXmap(unittest.TestCase): + def test_xmap(self): + def mapper(x): + return (x + 1) + orders = (True, False) + thread_nums = (1, 2, 4, 8, 16) + buffered_size = (1, 2, 4, 8, 16) + for order in orders: + for tNum in thread_nums: + for size in buffered_size: + result = [] + for i in paddle.v2.reader.xmap_readers(mapper, reader_creator_10(), tNum, size, order)(): + result.append(i) + if not order: + result.sort() + for idx, e in enumerate(result): + self.assertEqual(e, mapper(idx)) + if __name__ == '__main__': unittest.main() -- GitLab From 8bc07dee4e3c1d01e0c5f5f229fd13cadc74ace8 Mon Sep 17 00:00:00 2001 From: wanghaoshuang Date: Tue, 20 Jun 2017 16:11:14 +0800 Subject: [PATCH 0700/3256] format code --- python/paddle/v2/reader/decorator.py | 17 +++++++++-------- python/paddle/v2/reader/tests/decorator_test.py | 8 ++++++-- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/python/paddle/v2/reader/decorator.py b/python/paddle/v2/reader/decorator.py index 68ffbd6f3d4..e432003129d 100644 --- a/python/paddle/v2/reader/decorator.py +++ b/python/paddle/v2/reader/decorator.py @@ -251,18 +251,19 @@ def xmap_readers(mapper, reader, process_num, buffer_size, order=False): in_queue = Queue(buffer_size) out_queue = Queue(buffer_size) out_order = [0] + # define a worker to read samples from reader to in_queue def read_worker(reader, in_queue): for i in reader(): in_queue.put(i) in_queue.put(end) - + # define a worker to read samples from reader to in_queue with order flag def order_read_worker(reader, in_queue): in_order = 0 for i in reader(): - in_queue.put((in_order,i)) - in_order+=1 + in_queue.put((in_order, i)) + in_order += 1 in_queue.put(end) # start a read worker in a thread @@ -281,7 +282,7 @@ def xmap_readers(mapper, reader, process_num, buffer_size, order=False): sample = in_queue.get() in_queue.put(end) out_queue.put(end) - + # define a worker to handle samples from in_queue by mapper # and put mapped samples into out_queue by order def order_handle_worker(in_queue, out_queue, mapper, out_order): @@ -292,18 +293,18 @@ def xmap_readers(mapper, reader, process_num, buffer_size, order=False): while order != out_order[0]: pass out_queue.put(r) - out_order[0] += 1 + out_order[0] += 1 ins = in_queue.get() in_queue.put(end) out_queue.put(end) # start several handle_workers target = order_handle_worker if order else handle_worker - args = (in_queue, out_queue, mapper, out_order) if order else (in_queue, out_queue, mapper) + args = (in_queue, out_queue, mapper, out_order) if order else ( + in_queue, out_queue, mapper) workers = [] for i in xrange(process_num): - worker = Thread( - target=target, args=args) + worker = Thread(target=target, args=args) worker.daemon = True workers.append(worker) for w in workers: diff --git a/python/paddle/v2/reader/tests/decorator_test.py b/python/paddle/v2/reader/tests/decorator_test.py index 76db91a44b8..0bd77339550 100644 --- a/python/paddle/v2/reader/tests/decorator_test.py +++ b/python/paddle/v2/reader/tests/decorator_test.py @@ -120,10 +120,12 @@ class TestShuffle(unittest.TestCase): total += 1 self.assertEqual(total, 10) + class TestXmap(unittest.TestCase): def test_xmap(self): def mapper(x): return (x + 1) + orders = (True, False) thread_nums = (1, 2, 4, 8, 16) buffered_size = (1, 2, 4, 8, 16) @@ -131,13 +133,15 @@ class TestXmap(unittest.TestCase): for tNum in thread_nums: for size in buffered_size: result = [] - for i in paddle.v2.reader.xmap_readers(mapper, reader_creator_10(), tNum, size, order)(): + for i in paddle.v2.reader.xmap_readers(mapper, + reader_creator_10(), + tNum, size, order)(): result.append(i) if not order: result.sort() for idx, e in enumerate(result): self.assertEqual(e, mapper(idx)) - + if __name__ == '__main__': unittest.main() -- GitLab From 899035dcb958f6ba48a6015784defeb297c69ae5 Mon Sep 17 00:00:00 2001 From: liaogang Date: Tue, 20 Jun 2017 16:41:04 +0800 Subject: [PATCH 0701/3256] FIX: Add boost inc dir --- paddle/CMakeLists.txt | 2 +- paddle/function/NaiveConvOp.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/paddle/CMakeLists.txt b/paddle/CMakeLists.txt index 22e55d3b3b7..573bd937a35 100644 --- a/paddle/CMakeLists.txt +++ b/paddle/CMakeLists.txt @@ -18,8 +18,8 @@ add_subdirectory(strings) find_package(Boost QUIET) - if(Boost_FOUND) + include_directories(${Boost_INCLUDE_DIRS}) add_subdirectory(platform) add_subdirectory(framework) endif() diff --git a/paddle/function/NaiveConvOp.cpp b/paddle/function/NaiveConvOp.cpp index 70bd196a674..4348f0f775e 100644 --- a/paddle/function/NaiveConvOp.cpp +++ b/paddle/function/NaiveConvOp.cpp @@ -54,8 +54,8 @@ public: T inValue; const int inH = inStartH + fH; const int inW = inStartW + fW; - if ((inH >= 0 && inH < inputHeight) && - (inW >= 0 && inW < inputWidth)) { + if ((inH >= 0 && inH < (int)inputHeight) && + (inW >= 0 && inW < (int)inputWidth)) { size_t offsetInput = batch * inputChannels * inputHeight * inputWidth + inC * inputHeight * inputWidth + inH * inputWidth + inW; -- GitLab From 4c4b689f6a57605887e7fac557bbceb33629fcf5 Mon Sep 17 00:00:00 2001 From: qiaolongfei Date: Tue, 20 Jun 2017 16:43:13 +0800 Subject: [PATCH 0702/3256] disable go master --- go/pserver/cclient/test/test_mnist.py | 2 +- python/paddle/v2/__init__.py | 1 - python/setup.py.in | 4 +--- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/go/pserver/cclient/test/test_mnist.py b/go/pserver/cclient/test/test_mnist.py index d6fecc81b97..c3a3af55e28 100644 --- a/go/pserver/cclient/test/test_mnist.py +++ b/go/pserver/cclient/test/test_mnist.py @@ -81,7 +81,7 @@ def main(): trainer = paddle.trainer.SGD(cost=cost, parameters=parameters, update_equation=optimizer, - is_local=True, + is_local=False, pserver_spec="localhost:3000") lists = [] diff --git a/python/paddle/v2/__init__.py b/python/paddle/v2/__init__.py index 102331c0bb6..6a5b1160b91 100644 --- a/python/paddle/v2/__init__.py +++ b/python/paddle/v2/__init__.py @@ -57,7 +57,6 @@ __all__ = [ 'plot', 'evaluator', 'image', - 'master', ] diff --git a/python/setup.py.in b/python/setup.py.in index 8fe1cfd8b33..2e22f640cb5 100644 --- a/python/setup.py.in +++ b/python/setup.py.in @@ -8,8 +8,7 @@ packages=['paddle', 'paddle.v2', 'paddle.v2.dataset', 'paddle.v2.reader', - 'paddle.v2.plot', - 'paddle.v2.master'] + 'paddle.v2.plot'] setup_requires=["requests", "numpy", @@ -25,7 +24,6 @@ setup(name='paddle', description='Parallel Distributed Deep Learning', install_requires=setup_requires, packages=packages, - package_data={'paddle.v2.master': ['libpaddle_master.so'], }, package_dir={ '': '${CMAKE_CURRENT_SOURCE_DIR}' }, -- GitLab From b101aaca0a8dcf87cc4ab1e6be22e49671d2105d Mon Sep 17 00:00:00 2001 From: qiaolongfei Date: Tue, 20 Jun 2017 17:04:14 +0800 Subject: [PATCH 0703/3256] disable go master --- python/paddle/v2/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/python/paddle/v2/__init__.py b/python/paddle/v2/__init__.py index 6a5b1160b91..6a1e23a343d 100644 --- a/python/paddle/v2/__init__.py +++ b/python/paddle/v2/__init__.py @@ -26,7 +26,6 @@ import evaluator from . import dataset from . import reader from . import plot -from . import master import attr import op import pooling -- GitLab From 9e13b68f0152d09ccb78e1aa4e9149919d5e80be Mon Sep 17 00:00:00 2001 From: qiaolongfei Date: Tue, 20 Jun 2017 18:04:12 +0800 Subject: [PATCH 0704/3256] refine code --- CMakeLists.txt | 12 ++++++------ cmake/configure.cmake | 6 +++--- go/cmake/golang.cmake | 3 +-- paddle/api/PaddleAPI.h | 3 ++- paddle/api/ParameterUpdater.cpp | 10 +++++----- paddle/trainer/CMakeLists.txt | 6 +++--- 6 files changed, 20 insertions(+), 20 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5762baa2579..c5d7f2c7ec7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -47,7 +47,7 @@ option(WITH_COVERAGE "Compile PaddlePaddle with code coverage" OFF) option(COVERALLS_UPLOAD "Package code coverage data to coveralls" OFF) option(ON_TRAVIS "Exclude special unit test on Travis CI" OFF) option(WITH_C_API "Compile PaddlePaddle with C-API(Prediction)" OFF) -option(WITH_Go "Compile PaddlePaddle with Go)" OFF) +option(WITH_GOLANG "Compile PaddlePaddle with GOLANG" OFF) # CMAKE_BUILD_TYPE if(NOT CMAKE_BUILD_TYPE) @@ -108,7 +108,7 @@ include(configure) # add paddle env configuration include_directories("${PROJ_ROOT}") include_directories("${PROJ_ROOT}/paddle/cuda/include") include_directories("${CMAKE_CURRENT_BINARY_DIR}/proto") -include_directories("${CMAKE_BINARY_DIR}/go/pserver/cclient") +include_directories("${CMAKE_CURRENT_BINARY_DIR}/go/pserver/cclient") set(EXTERNAL_LIBS ${GFLAGS_LIBRARIES} @@ -130,10 +130,10 @@ add_subdirectory(proto) add_subdirectory(paddle) add_subdirectory(python) -if(WITH_Go) - #add_subdirectory(go/pserver/cclient) - add_subdirectory(go/master/c) -endif(WITH_Go) +if(WITH_GOLANG) + #TODO (add go/master/c back when fixed) + add_subdirectory(go/pserver/cclient) +endif(WITH_GOLANG) if(WITH_DOC) add_subdirectory(doc) diff --git a/cmake/configure.cmake b/cmake/configure.cmake index 44c86bc4797..e8425aedbdd 100644 --- a/cmake/configure.cmake +++ b/cmake/configure.cmake @@ -40,9 +40,9 @@ if(NOT CMAKE_CROSSCOMPILING) endif() endif() -if(NOT WITH_Go) - add_definitions(-DPADDLE_WITHOUT_GO) -endif() +if(NOT WITH_GOLANG) + add_definitions(-DPADDLE_WITHOUT_GOLANG) +endif(NOT WITH_GOLANG) if(NOT WITH_GPU) add_definitions(-DPADDLE_ONLY_CPU) diff --git a/go/cmake/golang.cmake b/go/cmake/golang.cmake index 446f930f38b..a5a43886f88 100644 --- a/go/cmake/golang.cmake +++ b/go/cmake/golang.cmake @@ -10,8 +10,7 @@ function(GO_LIBRARY NAME BUILD_TYPE) else() set(BUILD_MODE -buildmode=c-shared) if(APPLE) -# set(LIB_NAME "lib${NAME}.dylib") - set(LIB_NAME "lib${NAME}.so") + set(LIB_NAME "lib${NAME}.dylib") else() set(LIB_NAME "lib${NAME}.so") endif() diff --git a/paddle/api/PaddleAPI.h b/paddle/api/PaddleAPI.h index d4b80fccbd7..5fb3d1c73bc 100644 --- a/paddle/api/PaddleAPI.h +++ b/paddle/api/PaddleAPI.h @@ -842,7 +842,8 @@ public: int passCount, bool useSparseUpdater); static ParameterUpdater* createNewRemoteUpdater( - OptimizationConfig* config, const std::string pserverSpec) throw(UnsupportError); + OptimizationConfig* config, + const std::string pserverSpec) throw(UnsupportError); ~ParameterUpdater(); /** diff --git a/paddle/api/ParameterUpdater.cpp b/paddle/api/ParameterUpdater.cpp index 7eb860cffc3..1aaefdfb810 100644 --- a/paddle/api/ParameterUpdater.cpp +++ b/paddle/api/ParameterUpdater.cpp @@ -15,7 +15,7 @@ limitations under the License. */ #include "PaddleAPI.h" #include "PaddleAPIPrivate.h" -#ifndef PADDLE_WITHOUT_GO +#ifndef PADDLE_WITHOUT_GOLANG #include "paddle/trainer/NewRemoteParameterUpdater.h" #endif #include "paddle/trainer/RemoteParameterUpdater.h" @@ -31,10 +31,10 @@ ParameterUpdater *ParameterUpdater::createLocalUpdater( return updater; } -ParameterUpdater *ParameterUpdater::createNewRemoteUpdater ( - OptimizationConfig *config, const std::string pserverSpec) -throw(UnsupportError) { -#ifndef PADDLE_WITHOUT_GO +ParameterUpdater *ParameterUpdater::createNewRemoteUpdater( + OptimizationConfig *config, + const std::string pserverSpec) throw(UnsupportError) { +#ifndef PADDLE_WITHOUT_GOLANG auto updater = new ParameterUpdater(); updater->m->updater.reset(new paddle::NewRemoteParameterUpdater( config->m->getConfig(), pserverSpec)); diff --git a/paddle/trainer/CMakeLists.txt b/paddle/trainer/CMakeLists.txt index 1072e3d5080..f34d53ae99f 100644 --- a/paddle/trainer/CMakeLists.txt +++ b/paddle/trainer/CMakeLists.txt @@ -26,7 +26,7 @@ set(TRAINER_HEADERS ThreadParameterUpdater.h TrainerConfigHelper.h) -if(NOT WITH_Go) +if(NOT WITH_GOLANG) list(REMOVE_ITEM TRAINER_SOURCES NewRemoteParameterUpdater.cpp) list(REMOVE_ITEM TRAINER_HEADERS @@ -70,8 +70,8 @@ if(APPLE) set(CMAKE_EXE_LINKER_FLAGS "-framework CoreFoundation -framework Security") endif() -if(WITH_Go) +if(WITH_GOLANG) add_dependencies(paddle_trainer_lib paddle_pserver_cclient) target_link_libraries(paddle_trainer ${CMAKE_BINARY_DIR}/go/pserver/cclient/libpaddle_pserver_cclient.a) target_link_libraries(paddle_trainer_lib ${CMAKE_BINARY_DIR}/go/pserver/cclient/libpaddle_pserver_cclient.a) -endif(WITH_Go) +endif(WITH_GOLANG) -- GitLab From 785a8d59806137636cf3c267e6ec3eda8d1a95bc Mon Sep 17 00:00:00 2001 From: liaogang Date: Tue, 20 Jun 2017 21:39:45 +0800 Subject: [PATCH 0705/3256] ENH: Merge multiple static libs into singe one --- cmake/generic.cmake | 216 ++++++++++++++++++++++++++++++++------------ 1 file changed, 159 insertions(+), 57 deletions(-) diff --git a/cmake/generic.cmake b/cmake/generic.cmake index 43cd6b398b1..609067c2fd6 100644 --- a/cmake/generic.cmake +++ b/cmake/generic.cmake @@ -11,56 +11,172 @@ # 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. - - +# # To simplify the build process of PaddlePaddle, we defined couple of # fundamental abstractions, e.g., how to build library, binary and # test in C++, CUDA and Go. # # ------------------------------------------- -# C++ CUDA C++ Go +# C++ CUDA C++ Go # ------------------------------------------- -# cc_library nv_library go_library -# cc_binary nv_binary go_binary -# cc_test nv_test go_test +# cc_library nv_library go_library +# cc_binary nv_binary go_binary +# cc_test nv_test go_test # ------------------------------------------- # # cmake_parse_arguments can help us to achieve this goal. # https://cmake.org/cmake/help/v3.0/module/CMakeParseArguments.html # +# cc_library|nv_library( [STATIC SHARED] SRCS ... DEPS ...) +# +# cc_library and nv_library can generate *.a, or *.so +# if the corresponding keyword STATIC or SHARED is specified. +# +# cc_binary|nv_binary( SRCS ... DEPS ...) +# +# cc_binary and nv_binary can build souce code and link the dependent +# libraries to generate a binary. +# +# cc_test|nv_test( SRCS ... DEPS ...) +# +# cc_test and nv_test can build test code, link gtest and other dependent +# libraries to generate test suite. +# +# For example, in one folder, it contains +# ddim{.h, .cc, _test.cc, _test.cu} +# place{.h, cc, _test.cc} +# +# We can add build script as follows: +# +# cc_library(place STATIC SRCS place.cc) +# +# place.cc -> place.a +# cc_library's STATIC OPTION will generate libplace.a. +# +# cc_test(place_test +# SRCS place_test.cc +# DEPS place glog gflags) +# +# place_test.cc, place, glog, gflags -> place_test +# cc_test will combine place_test.cc, libplace.a with libglog.a. +# and libgflags.a to generate place_test. +# +# cc_library(ddim STATIC SRCS ddim.cc) +# +# ddim.cc -> ddim.a +# cc_library's STATIC OPTION will generate libddim.a. +# +# cc_test(ddim_test +# SRCS ddim_test.cc +# DEPS ddim) +# +# ddim_test.cc, ddim.a -> ddim_test +# cc_test will build ddim_test.cc with libddim.a to generate ddim_test. +# +# nv_test(dim_test +# SRCS dim_test.cu +# DEPS ddim) +# +# dim_test.cu, ddim.a -> dim_test +# nv_test will build dim_test.cu with libddim.a to generate dim_test. +# +# cc_library(framework DEPS place ddim) +# +# place.a, ddim.a -> framework.a +# If no SRCS exists, merging libplace.a and libddim.a to generate libframework.a. +# if(NOT APPLE) find_package(Threads REQUIRED) link_libraries(${CMAKE_THREAD_LIBS_INIT}) endif(NOT APPLE) -# cc_library parses tensor.cc and figures out that target also depend on tensor.h. -# cc_library(tensor -# SRCS -# tensor.cc -# DEPS -# variant) +function(merge_static_libs TARGET_NAME) + set(libs ${ARGN}) + list(REMOVE_DUPLICATES libs) + + # First get the file names of the libraries to be merged + foreach(lib ${libs}) + get_target_property(libtype ${lib} TYPE) + if(NOT libtype STREQUAL "STATIC_LIBRARY") + message(FATAL_ERROR "merge_static_libs can only process static libraries") + endif() + set(libfiles ${libfiles} $) + endforeach() + + if(APPLE) # Use OSX's libtool to merge archives + add_custom_target(${TARGET_NAME}_archive + COMMAND libtool -static -o "${CMAKE_CURRENT_BINARY_DIR}/lib${TARGET_NAME}.a" ${libfiles} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + DEPENDS ${libs} + ) + add_library(${TARGET_NAME} STATIC IMPORTED GLOBAL) + set_property(TARGET ${TARGET_NAME} PROPERTY + IMPORTED_LOCATION "${CMAKE_CURRENT_BINARY_DIR}/lib${TARGET_NAME}.a") + add_dependencies(${TARGET_NAME} ${TARGET_NAME}_archive) + else() # general UNIX: use "ar" to extract objects and re-add to a common lib + foreach(lib ${libs}) + set(objlistfile ${lib}.objlist) # list of objects in the input library + set(objdir ${lib}.objdir) + + add_custom_command(OUTPUT ${objdir} + COMMAND ${CMAKE_COMMAND} -E make_directory ${objdir}) + + add_custom_command(OUTPUT ${objlistfile} + COMMAND ${CMAKE_AR} -x "$" + COMMAND ${CMAKE_AR} -t "$" > ../${objlistfile} + DEPENDS ${lib} ${objdir} + WORKING_DIRECTORY ${objdir}) + + # Empty dummy source file that goes into merged library + set(mergebase ${lib}.mergebase.c) + add_custom_command(OUTPUT ${mergebase} + COMMAND ${CMAKE_COMMAND} -E touch ${mergebase} + DEPENDS ${objlistfile}) + + list(APPEND mergebases "${mergebase}") + endforeach() + + # We need a target for the output merged library + add_library(${TARGET_NAME} STATIC ${mergebases}) + set(outlibfile "$") + + foreach(lib ${libs}) + add_custom_command(TARGET ${TARGET_NAME} POST_BUILD + COMMAND ${CMAKE_AR} ru ${outlibfile} @"../${objlistfile}" + WORKING_DIRECTORY ${objdir}) + endforeach() + + add_custom_command(TARGET ${TARGET_NAME} POST_BUILD + COMMAND ${CMAKE_RANLIB} ${outlibfile}) + endif() +endfunction(merge_static_libs) + function(cc_library TARGET_NAME) - set(options OPTIONAL) + set(options STATIC static SHARED shared) set(oneValueArgs "") set(multiValueArgs SRCS DEPS) cmake_parse_arguments(cc_library "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) - if (${cc_library_OPTIONAL} STREQUAL "SHARED") - add_library(${TARGET_NAME} SHARED ${cc_library_SRCS}) - else() - add_library(${TARGET_NAME} STATIC ${cc_library_SRCS}) - endif() - if (cc_library_DEPS) - add_dependencies(${TARGET_NAME} ${cc_library_DEPS}) - endif() + if (cc_library_SRCS) + if (cc_library_SHARED OR cc_library_shared) # build *.so + add_library(${TARGET_NAME} SHARED ${cc_library_SRCS}) + else() + add_library(${TARGET_NAME} STATIC ${cc_library_SRCS}) + endif() + if (cc_library_DEPS) + add_dependencies(${TARGET_NAME} ${cc_library_DEPS}) + endif() + else(cc_library_SRCS) + if (cc_library_DEPS) + merge_static_libs(${TARGET_NAME} ${cc_library_DEPS}) + else() + message(FATAL "Please specify source file or library in cc_library.") + endif() + endif(cc_library_SRCS) endfunction(cc_library) -# cc_binary parses tensor.cc and figures out that target also depend on tensor.h. -# cc_binary(tensor -# SRCS -# tensor.cc) function(cc_binary TARGET_NAME) - set(options OPTIONAL) + set(options "") set(oneValueArgs "") set(multiValueArgs SRCS DEPS) cmake_parse_arguments(cc_binary "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) @@ -71,13 +187,6 @@ function(cc_binary TARGET_NAME) endif() endfunction(cc_binary) -# The dependency to target tensor implies that if any of -# tensor{.h,.cc,_test.cc} is changed, tensor_test need to be re-built. -# cc_test(tensor_test -# SRCS -# tensor_test.cc -# DEPS -# tensor) function(cc_test TARGET_NAME) if(WITH_TESTING) set(options "") @@ -91,28 +200,28 @@ function(cc_test TARGET_NAME) endif() endfunction(cc_test) -# Suppose that ops.cu includes global functions that take Tensor as -# their parameters, so ops depend on tensor. This implies that if -# any of tensor.{h.cc}, ops.{h,cu} is changed, ops need to be re-built. -# nv_library(ops -# SRCS -# ops.cu -# DEPS -# tensor) function(nv_library TARGET_NAME) if (WITH_GPU) - set(options OPTIONAL) + set(options STATIC static SHARED shared) set(oneValueArgs "") set(multiValueArgs SRCS DEPS) cmake_parse_arguments(nv_library "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) - if (${nv_library_OPTIONAL} STREQUAL "SHARED") - cuda_add_library(${TARGET_NAME} SHARED ${nv_library_SRCS}) - else() - cuda_add_library(${TARGET_NAME} STATIC ${nv_library_SRCS}) - endif() - if (nv_library_DEPS) - add_dependencies(${TARGET_NAME} ${nv_library_DEPS}) - endif() + if(nv_library_SRCS) + if (nv_library_SHARED OR nv_library_shared) # build *.so + cuda_add_library(${TARGET_NAME} SHARED ${nv_library_SRCS}) + else() + cuda_add_library(${TARGET_NAME} STATIC ${nv_library_SRCS}) + endif() + if (nv_library_DEPS) + add_dependencies(${TARGET_NAME} ${nv_library_DEPS}) + endif() + else(nv_library_SRCS) + if (nv_library_DEPS) + merge_static_libs(${TARGET_NAME} ${nv_library_DEPS}) + else() + message(FATAL "Please specify source file or library in nv_library.") + endif() + endif(nv_library_SRCS) endif() endfunction(nv_library) @@ -130,13 +239,6 @@ function(nv_binary TARGET_NAME) endif() endfunction(nv_binary) -# The dependency to target tensor implies that if any of -# ops{.h,.cu,_test.cu} is changed, ops_test need to be re-built. -# nv_test(ops_test -# SRCS -# ops_test.cu -# DEPS -# ops) function(nv_test TARGET_NAME) if (WITH_GPU AND WITH_TESTING) set(options "") -- GitLab From 0b4939588544ed4c4e095535d81fd8bce675ace8 Mon Sep 17 00:00:00 2001 From: liaogang Date: Tue, 20 Jun 2017 21:54:32 +0800 Subject: [PATCH 0706/3256] FIX: clang-format --- cmake/generic.cmake | 56 ++++++++++++++++++++++----------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/cmake/generic.cmake b/cmake/generic.cmake index 609067c2fd6..eb0326961e2 100644 --- a/cmake/generic.cmake +++ b/cmake/generic.cmake @@ -115,41 +115,41 @@ function(merge_static_libs TARGET_NAME) IMPORTED_LOCATION "${CMAKE_CURRENT_BINARY_DIR}/lib${TARGET_NAME}.a") add_dependencies(${TARGET_NAME} ${TARGET_NAME}_archive) else() # general UNIX: use "ar" to extract objects and re-add to a common lib - foreach(lib ${libs}) - set(objlistfile ${lib}.objlist) # list of objects in the input library - set(objdir ${lib}.objdir) + foreach(lib ${libs}) + set(objlistfile ${lib}.objlist) # list of objects in the input library + set(objdir ${lib}.objdir) - add_custom_command(OUTPUT ${objdir} - COMMAND ${CMAKE_COMMAND} -E make_directory ${objdir}) + add_custom_command(OUTPUT ${objdir} + COMMAND ${CMAKE_COMMAND} -E make_directory ${objdir}) - add_custom_command(OUTPUT ${objlistfile} - COMMAND ${CMAKE_AR} -x "$" - COMMAND ${CMAKE_AR} -t "$" > ../${objlistfile} - DEPENDS ${lib} ${objdir} - WORKING_DIRECTORY ${objdir}) + add_custom_command(OUTPUT ${objlistfile} + COMMAND ${CMAKE_AR} -x "$" + COMMAND ${CMAKE_AR} -t "$" > ../${objlistfile} + DEPENDS ${lib} ${objdir} + WORKING_DIRECTORY ${objdir}) - # Empty dummy source file that goes into merged library - set(mergebase ${lib}.mergebase.c) - add_custom_command(OUTPUT ${mergebase} - COMMAND ${CMAKE_COMMAND} -E touch ${mergebase} - DEPENDS ${objlistfile}) + # Empty dummy source file that goes into merged library + set(mergebase ${lib}.mergebase.c) + add_custom_command(OUTPUT ${mergebase} + COMMAND ${CMAKE_COMMAND} -E touch ${mergebase} + DEPENDS ${objlistfile}) - list(APPEND mergebases "${mergebase}") - endforeach() + list(APPEND mergebases "${mergebase}") + endforeach() - # We need a target for the output merged library - add_library(${TARGET_NAME} STATIC ${mergebases}) - set(outlibfile "$") + # We need a target for the output merged library + add_library(${TARGET_NAME} STATIC ${mergebases}) + set(outlibfile "$") - foreach(lib ${libs}) - add_custom_command(TARGET ${TARGET_NAME} POST_BUILD - COMMAND ${CMAKE_AR} ru ${outlibfile} @"../${objlistfile}" - WORKING_DIRECTORY ${objdir}) - endforeach() + foreach(lib ${libs}) + add_custom_command(TARGET ${TARGET_NAME} POST_BUILD + COMMAND ${CMAKE_AR} ru ${outlibfile} @"../${objlistfile}" + WORKING_DIRECTORY ${objdir}) + endforeach() - add_custom_command(TARGET ${TARGET_NAME} POST_BUILD - COMMAND ${CMAKE_RANLIB} ${outlibfile}) - endif() + add_custom_command(TARGET ${TARGET_NAME} POST_BUILD + COMMAND ${CMAKE_RANLIB} ${outlibfile}) + endif() endfunction(merge_static_libs) function(cc_library TARGET_NAME) -- GitLab From bb61adf0d64de2b5c604e0657a583726497c05a2 Mon Sep 17 00:00:00 2001 From: liaogang Date: Tue, 20 Jun 2017 21:57:16 +0800 Subject: [PATCH 0707/3256] ENH: Typesetting CMake --- cmake/generic.cmake | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/cmake/generic.cmake b/cmake/generic.cmake index eb0326961e2..f92cbdc65ef 100644 --- a/cmake/generic.cmake +++ b/cmake/generic.cmake @@ -92,16 +92,16 @@ if(NOT APPLE) endif(NOT APPLE) function(merge_static_libs TARGET_NAME) - set(libs ${ARGN}) - list(REMOVE_DUPLICATES libs) + set(libs ${ARGN}) + list(REMOVE_DUPLICATES libs) - # First get the file names of the libraries to be merged - foreach(lib ${libs}) - get_target_property(libtype ${lib} TYPE) - if(NOT libtype STREQUAL "STATIC_LIBRARY") - message(FATAL_ERROR "merge_static_libs can only process static libraries") - endif() - set(libfiles ${libfiles} $) + # First get the file names of the libraries to be merged + foreach(lib ${libs}) + get_target_property(libtype ${lib} TYPE) + if(NOT libtype STREQUAL "STATIC_LIBRARY") + message(FATAL_ERROR "merge_static_libs can only process static libraries") + endif() + set(libfiles ${libfiles} $) endforeach() if(APPLE) # Use OSX's libtool to merge archives -- GitLab From b6910529181cdd1d0f560bf71d77a3fed43886f6 Mon Sep 17 00:00:00 2001 From: xuwei06 Date: Tue, 20 Jun 2017 15:10:46 -0700 Subject: [PATCH 0708/3256] Fix bug of ScatterAgentLayer for generation --- paddle/gserver/layers/AgentLayer.cpp | 61 +++++++++++++++------------- paddle/gserver/layers/AgentLayer.h | 7 +++- 2 files changed, 38 insertions(+), 30 deletions(-) diff --git a/paddle/gserver/layers/AgentLayer.cpp b/paddle/gserver/layers/AgentLayer.cpp index 512932d9a55..15e7411b5fd 100644 --- a/paddle/gserver/layers/AgentLayer.cpp +++ b/paddle/gserver/layers/AgentLayer.cpp @@ -170,23 +170,22 @@ void ScatterAgentLayer::forward(PassType passType) { CHECK_EQ(realLayer_->getDeviceId(), this->getDeviceId()); int width = this->getSize(); - if (realOutArg_.hasSeq()) { - forwardSequence(passType); - } else if (realOutArg_.value || realOutArg_.ids) { - output_.subArgFrom( - realOutArg_, /* offset */ idIndex_, idSize_, width, useGpu_); - } else { // used in generation - if (realLayer_->getOutput().ids) { - IVector::resizeOrCreate(output_.ids, ids_->getSize(), useGpu_); - output_.ids->selectFrom(*realLayer_->getOutput().ids, *ids_); - } - if (realLayer_->getOutput().value) { - int height = ids_->getSize(); - resetOutput(height, width); - - const MatrixPtr& outV = getOutputValue(); - const MatrixPtr& realV = realLayer_->getOutputValue(); - outV->selectRows(*realV, *ids_); + if (selectionMode_) { + forwardWithSelection(passType); + } else { + if (realOutArg_.hasSeq()) { + output_.subArgFrom(realOutArg_, + /* offset */ idIndex_, + idSize_, + width, + useGpu_, + /* trans */ false, + /* seqFlag */ true, + /* seqStart */ seqStartPosIndex_, + /* seqSize */ numSequences_); + } else { + output_.subArgFrom( + realOutArg_, /* offset */ idIndex_, idSize_, width, useGpu_); } } } @@ -194,6 +193,8 @@ void ScatterAgentLayer::forward(PassType passType) { void ScatterAgentLayer::backward(const UpdateCallback& callback) { (void)callback; + CHECK(!selectionMode_); + const MatrixPtr& outputGrad = realOutArg_.grad; const MatrixPtr& realGrad = realLayer_->getOutputGrad(); if (realGrad) { @@ -208,7 +209,7 @@ void ScatterAgentLayer::backward(const UpdateCallback& callback) { REGISTER_LAYER(gather_agent, GatherAgentLayer); REGISTER_LAYER(scatter_agent, ScatterAgentLayer); -void ScatterAgentLayer::forwardSequence(PassType passType) { +void ScatterAgentLayer::forwardWithSelection(PassType passType) { Layer::forward(passType); CHECK_EQ(realLayer_->getDeviceId(), this->getDeviceId()); @@ -219,17 +220,19 @@ void ScatterAgentLayer::forwardSequence(PassType passType) { AsyncGpuBlock asyncGpuBlock; REGISTER_TIMER_INFO("SequenceAgentLayerForward", getName().c_str()); - if (realOutArg_.value || realOutArg_.ids) { - CHECK(realOutArg_.sequenceStartPositions); - output_.subArgFrom(realOutArg_, - /* offset */ idIndex_, - idSize_, - width, - useGpu_, - /* trans */ false, - /* seqFlag */ true, - /* seqStart */ seqStartPosIndex_, - /* seqSize */ numSequences_); + if (!input.hasSeq()) { + if (realLayer_->getOutput().ids) { + IVector::resizeOrCreate(output_.ids, ids_->getSize(), useGpu_); + output_.ids->selectFrom(*realLayer_->getOutput().ids, *ids_); + } + if (realLayer_->getOutput().value) { + int height = ids_->getSize(); + resetOutput(height, width); + + const MatrixPtr& outV = getOutputValue(); + const MatrixPtr& realV = realLayer_->getOutputValue(); + outV->selectRows(*realV, *ids_); + } } else { // Putting the generation logic here is really an ugly hack! // used in generation diff --git a/paddle/gserver/layers/AgentLayer.h b/paddle/gserver/layers/AgentLayer.h index 461b84b17e5..29681b29c6a 100644 --- a/paddle/gserver/layers/AgentLayer.h +++ b/paddle/gserver/layers/AgentLayer.h @@ -110,6 +110,9 @@ protected: // of real layer. ICpuGpuVectorPtr inputStartPos_; + // true for setRealLayer, false for setRealLayerAndOutput + bool selectionMode_; + public: explicit ScatterAgentLayer(const LayerConfig& config) : Layer(config) {} @@ -137,6 +140,7 @@ public: } else { cpuIds_ = ids_; } + selectionMode_ = true; } // set real layer and output, [idIndex, idIndex + idSize) of *ids* @@ -153,6 +157,7 @@ public: idIndex_ = idIndex; idSize_ = idSize; handleBackward_ = handleBackward; + selectionMode_ = false; } void setSequenceStartPositions(const ICpuGpuVectorPtr& sequenceStartPositions, @@ -166,7 +171,7 @@ public: void forward(PassType passType) override; void backward(const UpdateCallback& callback) override; - void forwardSequence(PassType passType); + void forwardWithSelection(PassType passType); }; } // namespace paddle -- GitLab From 936ac658c07f585e896a83ae0492de64de99664d Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Tue, 20 Jun 2017 15:44:28 -0700 Subject: [PATCH 0709/3256] Fix unit test error: File exists: /root/.cache/paddle/dataset --- python/paddle/v2/dataset/common.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/python/paddle/v2/dataset/common.py b/python/paddle/v2/dataset/common.py index 9c614914b5e..e09ac1a7a0f 100644 --- a/python/paddle/v2/dataset/common.py +++ b/python/paddle/v2/dataset/common.py @@ -15,6 +15,7 @@ import requests import hashlib import os +import errno import shutil import sys import importlib @@ -27,7 +28,12 @@ __all__ = ['DATA_HOME', 'download', 'md5file', 'split', 'cluster_files_reader'] DATA_HOME = os.path.expanduser('~/.cache/paddle/dataset') if not os.path.exists(DATA_HOME): - os.makedirs(DATA_HOME) + try: + os.makedirs(DATA_HOME) + except OSError as exc: + if exc.errno != errno.EEXIST: + raise + pass def md5file(fname): -- GitLab From a28ba1a6acc8ae7844f85be67156e9bc2c2e4b3d Mon Sep 17 00:00:00 2001 From: Yi Wang Date: Tue, 20 Jun 2017 17:40:33 -0700 Subject: [PATCH 0710/3256] Rewrite tutorial comments in generic.cmake --- cmake/generic.cmake | 108 ++++++++++++++++++-------------------------- 1 file changed, 45 insertions(+), 63 deletions(-) diff --git a/cmake/generic.cmake b/cmake/generic.cmake index f92cbdc65ef..b1e07be5b1b 100644 --- a/cmake/generic.cmake +++ b/cmake/generic.cmake @@ -12,10 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. # -# To simplify the build process of PaddlePaddle, we defined couple of -# fundamental abstractions, e.g., how to build library, binary and -# test in C++, CUDA and Go. -# + + +# generic.cmake defines CMakes functions that look like Bazel's building rules (https://bazel.build/). +# # ------------------------------------------- # C++ CUDA C++ Go # ------------------------------------------- @@ -23,68 +23,50 @@ # cc_binary nv_binary go_binary # cc_test nv_test go_test # ------------------------------------------- -# -# cmake_parse_arguments can help us to achieve this goal. -# https://cmake.org/cmake/help/v3.0/module/CMakeParseArguments.html -# -# cc_library|nv_library( [STATIC SHARED] SRCS ... DEPS ...) -# -# cc_library and nv_library can generate *.a, or *.so -# if the corresponding keyword STATIC or SHARED is specified. -# -# cc_binary|nv_binary( SRCS ... DEPS ...) -# -# cc_binary and nv_binary can build souce code and link the dependent -# libraries to generate a binary. -# -# cc_test|nv_test( SRCS ... DEPS ...) -# -# cc_test and nv_test can build test code, link gtest and other dependent -# libraries to generate test suite. -# -# For example, in one folder, it contains -# ddim{.h, .cc, _test.cc, _test.cu} -# place{.h, cc, _test.cc} -# -# We can add build script as follows: # -# cc_library(place STATIC SRCS place.cc) -# -# place.cc -> place.a -# cc_library's STATIC OPTION will generate libplace.a. -# -# cc_test(place_test -# SRCS place_test.cc -# DEPS place glog gflags) -# -# place_test.cc, place, glog, gflags -> place_test -# cc_test will combine place_test.cc, libplace.a with libglog.a. -# and libgflags.a to generate place_test. -# -# cc_library(ddim STATIC SRCS ddim.cc) -# -# ddim.cc -> ddim.a -# cc_library's STATIC OPTION will generate libddim.a. -# -# cc_test(ddim_test -# SRCS ddim_test.cc -# DEPS ddim) -# -# ddim_test.cc, ddim.a -> ddim_test -# cc_test will build ddim_test.cc with libddim.a to generate ddim_test. -# -# nv_test(dim_test -# SRCS dim_test.cu -# DEPS ddim) -# -# dim_test.cu, ddim.a -> dim_test -# nv_test will build dim_test.cu with libddim.a to generate dim_test. -# -# cc_library(framework DEPS place ddim) +# To build a static library example.a from example.cc using the system compiler (like GCC): +# +# cc_library(example SRCS example.cc) +# +# To build a static library example.a from multiple source files example{1,2,3}.cc: +# +# cc_library(example SRCS example1.cc example2.cc example3.cc) +# +# To build a shared library example.so from example.cc: +# +# cc_library(example SHARED SRCS example.cc) +# +# To build a library using Nvidia's NVCC from .cu file(s), use the nv_ prefixed version: +# +# nv_library(example SRCS example.cu) +# +# To specify that a library new_example.a depends on other libraies: +# +# cc_library(new_example SRCS new_example.cc DEPS example) +# +# Static libraries can be composed of other static libraries: +# +# cc_library(composed DEPS dependent1 dependent2 dependent3) +# +# To build an executable binary file from some source files and dependent libraries: +# +# cc_binary(example SRCS main.cc something.cc DEPS example1 example2) +# +# To build an executable binary file using NVCC, use the nv_ prefixed version: +# +# nv_binary(example SRCS main.cc something.cu DEPS example1 example2) +# +# To build a unit test binary, which is an executable binary with GoogleTest linked: +# +# cc_test(example_test SRCS example_test.cc DEPS example) +# +# To build a unit test binary using NVCC, use the nv_ prefixed version: +# +# nv_test(example_test SRCS example_test.cu DEPS example) # -# place.a, ddim.a -> framework.a -# If no SRCS exists, merging libplace.a and libddim.a to generate libframework.a. +# It is pretty often that executable and test binaries depend on pre-defined external libaries like glog and gflags defined in /cmake/external/*.cmake: # +# cc_test(example_test SRCS example_test.cc DEPS example glog gflags) if(NOT APPLE) find_package(Threads REQUIRED) -- GitLab From 71f8c3bb98daeec1552fa569dd86320d99ddc537 Mon Sep 17 00:00:00 2001 From: Yi Wang Date: Tue, 20 Jun 2017 17:43:10 -0700 Subject: [PATCH 0711/3256] Rearrange paragraphs --- cmake/generic.cmake | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/cmake/generic.cmake b/cmake/generic.cmake index b1e07be5b1b..3582d7574a9 100644 --- a/cmake/generic.cmake +++ b/cmake/generic.cmake @@ -14,7 +14,9 @@ # -# generic.cmake defines CMakes functions that look like Bazel's building rules (https://bazel.build/). +# generic.cmake defines CMakes functions that look like Bazel's +# building rules (https://bazel.build/). +# # # ------------------------------------------- # C++ CUDA C++ Go @@ -24,11 +26,14 @@ # cc_test nv_test go_test # ------------------------------------------- # -# To build a static library example.a from example.cc using the system compiler (like GCC): +# To build a static library example.a from example.cc using the system +# compiler (like GCC): # # cc_library(example SRCS example.cc) # -# To build a static library example.a from multiple source files example{1,2,3}.cc: + +# To build a static library example.a from multiple source files +# example{1,2,3}.cc: # # cc_library(example SRCS example1.cc example2.cc example3.cc) # @@ -36,7 +41,8 @@ # # cc_library(example SHARED SRCS example.cc) # -# To build a library using Nvidia's NVCC from .cu file(s), use the nv_ prefixed version: +# To build a library using Nvidia's NVCC from .cu file(s), use the nv_ +# prefixed version: # # nv_library(example SRCS example.cu) # @@ -48,15 +54,18 @@ # # cc_library(composed DEPS dependent1 dependent2 dependent3) # -# To build an executable binary file from some source files and dependent libraries: +# To build an executable binary file from some source files and +# dependent libraries: # # cc_binary(example SRCS main.cc something.cc DEPS example1 example2) # -# To build an executable binary file using NVCC, use the nv_ prefixed version: +# To build an executable binary file using NVCC, use the nv_ prefixed +# version: # # nv_binary(example SRCS main.cc something.cu DEPS example1 example2) # -# To build a unit test binary, which is an executable binary with GoogleTest linked: +# To build a unit test binary, which is an executable binary with +# GoogleTest linked: # # cc_test(example_test SRCS example_test.cc DEPS example) # @@ -64,7 +73,9 @@ # # nv_test(example_test SRCS example_test.cu DEPS example) # -# It is pretty often that executable and test binaries depend on pre-defined external libaries like glog and gflags defined in /cmake/external/*.cmake: +# It is pretty often that executable and test binaries depend on +# pre-defined external libaries like glog and gflags defined in +# /cmake/external/*.cmake: # # cc_test(example_test SRCS example_test.cc DEPS example glog gflags) -- GitLab From 252ef0cb31586f4aa8596d8170588f5e34a8358b Mon Sep 17 00:00:00 2001 From: Yi Wang Date: Tue, 20 Jun 2017 17:43:53 -0700 Subject: [PATCH 0712/3256] Update --- cmake/generic.cmake | 1 - 1 file changed, 1 deletion(-) diff --git a/cmake/generic.cmake b/cmake/generic.cmake index 3582d7574a9..69e8164a00d 100644 --- a/cmake/generic.cmake +++ b/cmake/generic.cmake @@ -31,7 +31,6 @@ # # cc_library(example SRCS example.cc) # - # To build a static library example.a from multiple source files # example{1,2,3}.cc: # -- GitLab From ff4be82252d797746b3a4169137c7fcfd9ee7039 Mon Sep 17 00:00:00 2001 From: Yi Wang Date: Tue, 20 Jun 2017 17:54:10 -0700 Subject: [PATCH 0713/3256] Handle multiple processes trying to create the data home directory --- python/paddle/v2/dataset/common.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/python/paddle/v2/dataset/common.py b/python/paddle/v2/dataset/common.py index e09ac1a7a0f..72894c24b16 100644 --- a/python/paddle/v2/dataset/common.py +++ b/python/paddle/v2/dataset/common.py @@ -27,13 +27,17 @@ __all__ = ['DATA_HOME', 'download', 'md5file', 'split', 'cluster_files_reader'] DATA_HOME = os.path.expanduser('~/.cache/paddle/dataset') -if not os.path.exists(DATA_HOME): - try: - os.makedirs(DATA_HOME) - except OSError as exc: - if exc.errno != errno.EEXIST: - raise - pass +# When running unit tests, there could be multiple processes that +# trying to create DATA_HOME directory simultaneously, so we cannot +# use a if condition to check for the existence of the directory; +# instead, we use the filesystem as the synchronization mechanism by +# catching returned errors. +try: + os.makedirs(DATA_HOME) +except OSError as exc: + if exc.errno != errno.EEXIST: + raise + pass def md5file(fname): -- GitLab From d558b8bb82d6428b58f7ceb60ea87afcadce03ba Mon Sep 17 00:00:00 2001 From: hedaoyuan Date: Wed, 21 Jun 2017 10:36:36 +0800 Subject: [PATCH 0714/3256] Move the code in the GemmConvOpGpu.cu file into Im2ColOpGpu.cu. --- paddle/function/Im2ColOpGpu.cu | 172 +++++++++++++++++++++++++++++++++ 1 file changed, 172 insertions(+) diff --git a/paddle/function/Im2ColOpGpu.cu b/paddle/function/Im2ColOpGpu.cu index bddd8ffc7c0..361ecc4401a 100644 --- a/paddle/function/Im2ColOpGpu.cu +++ b/paddle/function/Im2ColOpGpu.cu @@ -17,6 +17,178 @@ limitations under the License. */ namespace paddle { +template +__global__ +void im2col(const T* data_im, int numOuts, int height, int width, + int blockH, int blockW, + int strideH, int strideW, + int paddingH, int paddingW, + int height_col, int width_col, + T* data_col) { + int index = + (blockIdx.x * gridDim.y + blockIdx.y) * blockDim.x + threadIdx.x; + if (index < numOuts) { + int w_out = index % width_col; + index /= width_col; + int h_out = index % height_col; + int channel_in = index / height_col; + int channel_out = channel_in * blockH * blockW; + int h_in = h_out * strideH; + int w_in = w_out * strideW; + + data_col += (channel_out * height_col + h_out) * width_col + w_out; + for (int i = 0; i < blockH; ++i) { + for (int j = 0; j < blockW; ++j) { + int rIdx = int(h_in+i); + int cIdx = int(w_in+j); + if ((rIdx-(int)paddingH) >= (int)height || + (rIdx-(int)paddingH) < 0 || + (cIdx-(int)paddingW) >= (int)width || + (cIdx-(int)paddingW) < 0) { + *data_col = 0; + } else { + rIdx = rIdx + channel_in*height - paddingH; + cIdx = cIdx - paddingW; + *data_col = data_im[rIdx* width + cIdx]; + } + data_col += height_col * width_col; + } + } + } +} + +template +class Im2ColFunctor { +public: + void operator()(const T* imData, + const TensorShape& imShape, + T* colData, + const TensorShape& colShape, + int strideHeight, + int strideWidth, + int paddingHeight, + int paddingWidth) { + int inputChannels = imShape[0]; + int inputHeight = imShape[1]; + int inputWidth = imShape[2]; + int filterHeight = colShape[3]; + int filterWidth = colShape[4]; + int outputHeight = colShape[0]; + int outputWidth = colShape[1]; + + int numKernels = inputChannels * outputHeight * outputWidth; + int blocks = (numKernels + 1024 -1) / 1024; + int blockX = 512; + int blockY = (blocks + 512 - 1) / 512; + dim3 threads(1024, 1); + dim3 grid(blockX, blockY); + im2col<<< grid, threads, 0, STREAM_DEFAULT >>> + (imData, numKernels, inputHeight, inputWidth, filterHeight, filterWidth, + strideHeight, strideWidth, paddingHeight, paddingWidth, + outputHeight, outputWidth, colData); + CHECK_SYNC("Im2ColFunctor GPU failed"); + } +}; + +template +__global__ +void col2im(size_t n, const T* data_col, size_t height, + size_t width, size_t channels, + size_t blockH, size_t blockW, + size_t strideH, size_t strideW, + size_t paddingH, size_t paddingW, + size_t height_col, size_t width_col, + T* data_im) { + size_t index = + (blockIdx.x * gridDim.y + blockIdx.y) * blockDim.x + threadIdx.x; + if (index < n) { + T val = 0; + int w = int(index % width); + int h = int((index / width) % height); + int c = int(index / (width * height)); + if ((w - (int)paddingW) >= 0 && + (w - (int)paddingW) < (width-2 * paddingW) && + (h - (int)paddingH) >= 0 && + (h - paddingH) < (height - 2 * paddingH)) { + // compute the start and end of the output + int w_col_start = + (w < (int)blockW) ? 0 : (w - int(blockW)) / (int)strideW + 1; + int w_col_end = + min((int)(w / (int)strideW + 1), (int)(width_col)); + int h_col_start = + (h < (int)blockH) ? 0 : (h - (int)blockH) / (int)strideH + 1; + int h_col_end = min(int(h / strideH + 1), int(height_col)); + for (int h_col = h_col_start; h_col < h_col_end; ++h_col) { + for (int w_col = w_col_start; w_col < w_col_end; ++w_col) { + // the col location: [c * width * height + h_out, w_out] + int c_col = int(c * blockH* blockW) + \ + (h - h_col * (int)strideH) * (int)blockW + + (w - w_col * (int)strideW); + val += data_col[(c_col * height_col + h_col) * width_col + w_col]; + } + } + h -= paddingH; + w -= paddingW; + data_im[c*((width-2*paddingW) * (height-2*paddingH)) + + h*(width-2*paddingW) + w] += val; + } + } +} + +template +class Col2ImFunctor { +public: + void operator()(T* imData, + const TensorShape& imShape, + const T* colData, + const TensorShape& colShape, + int strideHeight, + int strideWidth, + int paddingHeight, + int paddingWidth) { + int inputChannels = imShape[0]; + int inputHeight = imShape[1]; + int inputWidth = imShape[2]; + int filterHeight = colShape[3]; + int filterWidth = colShape[4]; + int outputHeight = colShape[0]; + int outputWidth = colShape[1]; + + size_t numKernels = inputChannels * (inputHeight + 2*paddingHeight) + * (inputWidth + 2*paddingWidth); + + size_t blocks = (numKernels + 1024 -1) / 1024; + size_t blockX = 512; + size_t blockY = (blocks+512-1)/512; + dim3 threads(1024, 1); + dim3 grid(blockX, blockY); + + // To avoid involving atomic operations, we will launch one kernel per + // bottom dimension, and then in the kernel add up the top dimensions. + col2im<<< grid, threads, 0, STREAM_DEFAULT >>> + (numKernels, + colData, + inputHeight + 2*paddingHeight, + inputWidth + 2*paddingWidth, + inputChannels, + filterHeight, + filterWidth, + strideHeight, + strideWidth, + paddingHeight, + paddingWidth, + outputHeight, + outputWidth, + imData); + CHECK_SYNC("Col2ImFunctor GPU failed"); + } +}; + +template class Im2ColFunctor; +template class Im2ColFunctor; +template class Col2ImFunctor; +template class Col2ImFunctor; + template __global__ void im2colOCF(const T* imData, T* colData, -- GitLab From eb0c7e5ebc9a8c267cf4dc399beeb6b93dcbe6c6 Mon Sep 17 00:00:00 2001 From: hedaoyuan Date: Wed, 21 Jun 2017 11:03:30 +0800 Subject: [PATCH 0715/3256] Move the Im2Col code of the CPU version into the Im2ColOp.cpp file. --- paddle/function/Im2ColOp.cpp | 235 ++++++++++++++++++++++++++++++ paddle/function/Im2ColOpGpu.cu | 26 +++- paddle/function/ImageExpandOp.cpp | 108 -------------- 3 files changed, 253 insertions(+), 116 deletions(-) create mode 100644 paddle/function/Im2ColOp.cpp diff --git a/paddle/function/Im2ColOp.cpp b/paddle/function/Im2ColOp.cpp new file mode 100644 index 00000000000..b7d1eb1eded --- /dev/null +++ b/paddle/function/Im2ColOp.cpp @@ -0,0 +1,235 @@ +/* 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 "Im2Col.h" + +namespace paddle { + +/* + * imShape = [inputChannels, inputHeight, inputWidth] + * colShape = + * [inputChannels, filterHeight, filterWidth, outputHeight, outputWidth] + */ +template +class Im2ColFunctor { +public: + void operator()(const T* imData, + const TensorShape& imShape, + T* colData, + const TensorShape& colShape, + int strideHeight, + int strideWidth, + int paddingHeight, + int paddingWidth) { + int inputChannels = imShape[0]; + int inputHeight = imShape[1]; + int inputWidth = imShape[2]; + int filterHeight = colShape[1]; + int filterWidth = colShape[2]; + int outputHeight = colShape[3]; + int outputWidth = colShape[4]; + int channelsCol = inputChannels * filterHeight * filterWidth; + + for (int c = 0; c < channelsCol; ++c) { + int wOffset = c % filterWidth; + int hOffset = (c / filterWidth) % filterHeight; + int c_im = c / filterWidth / filterHeight; + for (int h = 0; h < outputHeight; ++h) { + for (int w = 0; w < outputWidth; ++w) { + int imRowIdx = h * strideHeight + hOffset; + int imColIdx = w * strideWidth + wOffset; + if ((imRowIdx - paddingHeight) < 0 || + (imRowIdx - paddingHeight) >= inputHeight || + (imColIdx - paddingWidth) < 0 || + (imColIdx - paddingWidth) >= inputWidth) { + colData[(c * outputHeight + h) * outputWidth + w] = T(0); + } else { + imRowIdx += c_im * inputHeight - paddingHeight; + imColIdx -= paddingWidth; + colData[(c * outputHeight + h) * outputWidth + w] = + imData[imRowIdx * inputWidth + imColIdx]; + } + } + } + } + } +}; + +/* + * imShape = [inputChannels, inputHeight, inputWidth] + * colShape = + * [inputChannels, filterHeight, filterWidth, outputHeight, outputWidth] + */ +template +class Col2ImFunctor { +public: + void operator()(T* imData, + const TensorShape& imShape, + const T* colData, + const TensorShape& colShape, + int strideHeight, + int strideWidth, + int paddingHeight, + int paddingWidth) { + int inputChannels = imShape[0]; + int inputHeight = imShape[1]; + int inputWidth = imShape[2]; + int filterHeight = colShape[1]; + int filterWidth = colShape[2]; + int outputHeight = colShape[3]; + int outputWidth = colShape[4]; + int channelsCol = inputChannels * filterHeight * filterWidth; + + for (int c = 0; c < channelsCol; ++c) { + int wOffset = c % filterWidth; + int hOffset = (c / filterWidth) % filterHeight; + int c_im = c / filterWidth / filterHeight; + for (int h = 0; h < outputHeight; ++h) { + for (int w = 0; w < outputWidth; ++w) { + int imRowIdx = h * strideHeight + hOffset; + int imColIdx = w * strideWidth + wOffset; + if ((imRowIdx - paddingHeight) >= 0 && + (imRowIdx - paddingHeight) < inputHeight && + (imColIdx - paddingWidth) >= 0 && + (imColIdx - paddingWidth) < inputWidth) { + imRowIdx += c_im * inputHeight - paddingHeight; + imColIdx -= paddingWidth; + imData[imRowIdx * inputWidth + imColIdx] += + colData[(c * outputHeight + h) * outputWidth + w]; + } + } + } + } + } +}; + +template class Im2ColFunctor; +template class Im2ColFunctor; +template class Col2ImFunctor; +template class Col2ImFunctor; + +/* + * imShape = [inputChannels, inputHeight, inputWidth] + * colShape = + * [outputHeight, outputWidth, inputChannels, filterHeight, filterWidth] + */ +template +class Im2ColFunctor { +public: + void operator()(const T* imData, + const TensorShape& imShape, + T* colData, + const TensorShape& colShape, + int strideHeight, + int strideWidth, + int paddingHeight, + int paddingWidth) { + int inputChannels = imShape[0]; + int inputHeight = imShape[1]; + int inputWidth = imShape[2]; + int filterHeight = colShape[3]; + int filterWidth = colShape[4]; + int outputHeight = colShape[0]; + int outputWidth = colShape[1]; + for (int outputH = 0; outputH < outputHeight; ++outputH) { + for (int outputW = 0; outputW < outputWidth; ++outputW) { + for (int channel = 0; channel < inputChannels; ++channel) { + for (int filterH = 0; filterH < filterHeight; ++filterH) { + for (int filterW = 0; filterW < filterWidth; ++filterW) { + int imRowOffset = + outputH * strideHeight + filterH - paddingHeight; + int imColOffset = outputW * strideWidth + filterW - paddingWidth; + int colDataOffset = + (((outputH * outputWidth + outputW) * inputChannels + + channel) * + filterHeight + + filterH) * + filterWidth + + filterW; + if (imRowOffset < 0 || imRowOffset >= inputHeight || + imColOffset < 0 || imColOffset >= inputWidth) { + colData[colDataOffset] = float(0); + } else { + int imDataOffset = + (channel * inputHeight + imRowOffset) * inputWidth + + imColOffset; + colData[colDataOffset] = imData[imDataOffset]; + } + } + } + } + } + } + } +}; + +/* + * imShape = [inputChannels, inputHeight, inputWidth] + * colShape = + * [outputHeight, outputWidth, inputChannels, filterHeight, filterWidth] + */ +template +class Col2ImFunctor { +public: + void operator()(T* imData, + const TensorShape& imShape, + const T* colData, + const TensorShape& colShape, + int strideHeight, + int strideWidth, + int paddingHeight, + int paddingWidth) { + int inputChannels = imShape[0]; + int inputHeight = imShape[1]; + int inputWidth = imShape[2]; + int filterHeight = colShape[3]; + int filterWidth = colShape[4]; + int outputHeight = colShape[0]; + int outputWidth = colShape[1]; + for (int outputH = 0; outputH < outputHeight; ++outputH) { + for (int outputW = 0; outputW < outputWidth; ++outputW) { + for (int channel = 0; channel < inputChannels; ++channel) { + for (int filterH = 0; filterH < filterHeight; ++filterH) { + for (int filterW = 0; filterW < filterWidth; ++filterW) { + int imRowOffset = + outputH * strideHeight + filterH - paddingHeight; + int imColOffset = outputW * strideWidth + filterW - paddingWidth; + int colDataOffset = + (((outputH * outputWidth + outputW) * inputChannels + + channel) * + filterHeight + + filterH) * + filterWidth + + filterW; + if (imRowOffset >= 0 && imRowOffset < inputHeight && + imColOffset >= 0 && imColOffset < inputWidth) { + int imDataOffset = + (channel * inputHeight + imRowOffset) * inputWidth + + imColOffset; + imData[imDataOffset] += colData[colDataOffset]; + } + } + } + } + } + } + } +}; + +template class Im2ColFunctor; +template class Im2ColFunctor; +template class Col2ImFunctor; +template class Col2ImFunctor; + +} // namespace paddle diff --git a/paddle/function/Im2ColOpGpu.cu b/paddle/function/Im2ColOpGpu.cu index 361ecc4401a..15ba8540096 100644 --- a/paddle/function/Im2ColOpGpu.cu +++ b/paddle/function/Im2ColOpGpu.cu @@ -57,6 +57,11 @@ void im2col(const T* data_im, int numOuts, int height, int width, } } +/* + * imShape = [inputChannels, inputHeight, inputWidth] + * colShape = + * [inputChannels, filterHeight, filterWidth, outputHeight, outputWidth] + */ template class Im2ColFunctor { public: @@ -71,10 +76,10 @@ public: int inputChannels = imShape[0]; int inputHeight = imShape[1]; int inputWidth = imShape[2]; - int filterHeight = colShape[3]; - int filterWidth = colShape[4]; - int outputHeight = colShape[0]; - int outputWidth = colShape[1]; + int filterHeight = colShape[1]; + int filterWidth = colShape[2]; + int outputHeight = colShape[3]; + int outputWidth = colShape[4]; int numKernels = inputChannels * outputHeight * outputWidth; int blocks = (numKernels + 1024 -1) / 1024; @@ -135,6 +140,11 @@ void col2im(size_t n, const T* data_col, size_t height, } } +/* + * imShape = [inputChannels, inputHeight, inputWidth] + * colShape = + * [inputChannels, filterHeight, filterWidth, outputHeight, outputWidth] + */ template class Col2ImFunctor { public: @@ -149,10 +159,10 @@ public: int inputChannels = imShape[0]; int inputHeight = imShape[1]; int inputWidth = imShape[2]; - int filterHeight = colShape[3]; - int filterWidth = colShape[4]; - int outputHeight = colShape[0]; - int outputWidth = colShape[1]; + int filterHeight = colShape[1]; + int filterWidth = colShape[2]; + int outputHeight = colShape[3]; + int outputWidth = colShape[4]; size_t numKernels = inputChannels * (inputHeight + 2*paddingHeight) * (inputWidth + 2*paddingWidth); diff --git a/paddle/function/ImageExpandOp.cpp b/paddle/function/ImageExpandOp.cpp index f227f6d0e10..625bf5b6edf 100644 --- a/paddle/function/ImageExpandOp.cpp +++ b/paddle/function/ImageExpandOp.cpp @@ -17,114 +17,6 @@ limitations under the License. */ namespace paddle { -/* - * imShape = [inputChannels, inputHeight, inputWidth] - * colShape = - * [outputHeight, outputWidth, inputChannels, filterHeight, filterWidth] - */ -template -class Im2ColFunctor { -public: - void operator()(const T* imData, - const TensorShape& imShape, - T* colData, - const TensorShape& colShape, - int strideHeight, - int strideWidth, - int paddingHeight, - int paddingWidth) { - int inputChannels = imShape[0]; - int inputHeight = imShape[1]; - int inputWidth = imShape[2]; - int filterHeight = colShape[3]; - int filterWidth = colShape[4]; - int outputHeight = colShape[0]; - int outputWidth = colShape[1]; - for (int outputH = 0; outputH < outputHeight; ++outputH) { - for (int outputW = 0; outputW < outputWidth; ++outputW) { - for (int channel = 0; channel < inputChannels; ++channel) { - for (int filterH = 0; filterH < filterHeight; ++filterH) { - for (int filterW = 0; filterW < filterWidth; ++filterW) { - int imRowOffset = - outputH * strideHeight + filterH - paddingHeight; - int imColOffset = outputW * strideWidth + filterW - paddingWidth; - int colDataOffset = - (((outputH * outputWidth + outputW) * inputChannels + - channel) * - filterHeight + - filterH) * - filterWidth + - filterW; - if (imRowOffset < 0 || imRowOffset >= inputHeight || - imColOffset < 0 || imColOffset >= inputWidth) { - colData[colDataOffset] = float(0); - } else { - int imDataOffset = - (channel * inputHeight + imRowOffset) * inputWidth + - imColOffset; - colData[colDataOffset] = imData[imDataOffset]; - } - } - } - } - } - } - } -}; - -/* - * imShape = [inputChannels, inputHeight, inputWidth] - * colShape = - * [outputHeight, outputWidth, inputChannels, filterHeight, filterWidth] - */ -template -class Col2ImFunctor { -public: - void operator()(T* imData, - const TensorShape& imShape, - const T* colData, - const TensorShape& colShape, - int strideHeight, - int strideWidth, - int paddingHeight, - int paddingWidth) { - int inputChannels = imShape[0]; - int inputHeight = imShape[1]; - int inputWidth = imShape[2]; - int filterHeight = colShape[3]; - int filterWidth = colShape[4]; - int outputHeight = colShape[0]; - int outputWidth = colShape[1]; - for (int outputH = 0; outputH < outputHeight; ++outputH) { - for (int outputW = 0; outputW < outputWidth; ++outputW) { - for (int channel = 0; channel < inputChannels; ++channel) { - for (int filterH = 0; filterH < filterHeight; ++filterH) { - for (int filterW = 0; filterW < filterWidth; ++filterW) { - int imRowOffset = - outputH * strideHeight + filterH - paddingHeight; - int imColOffset = outputW * strideWidth + filterW - paddingWidth; - int colDataOffset = - (((outputH * outputWidth + outputW) * inputChannels + - channel) * - filterHeight + - filterH) * - filterWidth + - filterW; - if (imRowOffset >= 0 && imRowOffset < inputHeight && - imColOffset >= 0 && imColOffset < inputWidth) { - int imDataOffset = - (channel * inputHeight + imRowOffset) * inputWidth + - imColOffset; - imData[imDataOffset] += colData[colDataOffset]; - } - } - } - } - } - } - } -}; - /* * \brief Converts the image data of four dimensions(NCHW) into * a sequence data of three dimensions(NST) in the forward calculation, -- GitLab From 07cde439aae38137c42f662382e36d08c03d37fd Mon Sep 17 00:00:00 2001 From: hedaoyuan Date: Wed, 21 Jun 2017 11:18:58 +0800 Subject: [PATCH 0716/3256] Reconstruction of GemmConv Based on new im2col. --- paddle/function/GemmConvOp.cpp | 185 +++++++++------------------------ 1 file changed, 48 insertions(+), 137 deletions(-) diff --git a/paddle/function/GemmConvOp.cpp b/paddle/function/GemmConvOp.cpp index a40e5d9d2e7..3f10bb9c837 100644 --- a/paddle/function/GemmConvOp.cpp +++ b/paddle/function/GemmConvOp.cpp @@ -12,101 +12,13 @@ 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 "GemmConvOp.h" +#include "ConvOp.h" #include "GemmFunctor.h" +#include "Im2Col.h" #include "paddle/math/MemoryHandle.h" namespace paddle { -/* - * imData = [input_channels, input_height, input_width] - * colData = [input_channels, filter_height, filter_width, - * output_height, output_width] - */ -template -class Im2ColFunctor { -public: - void operator()(const T* imData, - int inputChannels, - int inputHeight, - int inputWidth, - int filterHeight, - int filterWidth, - int strideHeight, - int strideWidth, - int paddingHeight, - int paddingWidth, - int outputHeight, - int outputWidth, - T* colData) { - int channelsCol = inputChannels * filterHeight * filterWidth; - - for (int c = 0; c < channelsCol; ++c) { - int wOffset = c % filterWidth; - int hOffset = (c / filterWidth) % filterHeight; - int c_im = c / filterWidth / filterHeight; - for (int h = 0; h < outputHeight; ++h) { - for (int w = 0; w < outputWidth; ++w) { - int imRowIdx = h * strideHeight + hOffset; - int imColIdx = w * strideWidth + wOffset; - if ((imRowIdx - paddingHeight) < 0 || - (imRowIdx - paddingHeight) >= inputHeight || - (imColIdx - paddingWidth) < 0 || - (imColIdx - paddingWidth) >= inputWidth) { - colData[(c * outputHeight + h) * outputWidth + w] = T(0); - } else { - imRowIdx += c_im * inputHeight - paddingHeight; - imColIdx -= paddingWidth; - colData[(c * outputHeight + h) * outputWidth + w] = - imData[imRowIdx * inputWidth + imColIdx]; - } - } - } - } - } -}; - -template -class Col2ImFunctor { -public: - void operator()(const T* colData, - int inputChannels, - int inputHeight, - int inputWidth, - int filterHeight, - int filterWidth, - int strideHeight, - int strideWidth, - int paddingHeight, - int paddingWidth, - int outputHeight, - int outputWidth, - T* imData) { - int channelsCol = inputChannels * filterHeight * filterWidth; - - for (int c = 0; c < channelsCol; ++c) { - int wOffset = c % filterWidth; - int hOffset = (c / filterWidth) % filterHeight; - int c_im = c / filterWidth / filterHeight; - for (int h = 0; h < outputHeight; ++h) { - for (int w = 0; w < outputWidth; ++w) { - int imRowIdx = h * strideHeight + hOffset; - int imColIdx = w * strideWidth + wOffset; - if ((imRowIdx - paddingHeight) >= 0 && - (imRowIdx - paddingHeight) < inputHeight && - (imColIdx - paddingWidth) >= 0 && - (imColIdx - paddingWidth) < inputWidth) { - imRowIdx += c_im * inputHeight - paddingHeight; - imColIdx -= paddingWidth; - imData[imRowIdx * inputWidth + imColIdx] += - colData[(c * outputHeight + h) * outputWidth + w]; - } - } - } - } - } -}; - /* * \brief Forward calculation of convolution. */ @@ -155,15 +67,20 @@ public: real* inputData = inputs[0].data(); real* filterData = inputs[1].data(); real* outputData = outputs[0].data(); - - size_t size = inputChannels / groups_ * filterHeight * filterWidth * - outputHeight * outputWidth; - resizeBuffer(size); + TensorShape imShape = + TensorShape({inputChannels / groups_, inputHeight, inputWidth}); + TensorShape colShape = TensorShape({inputChannels / groups_, + filterHeight, + filterWidth, + outputHeight, + outputWidth}); + + resizeBuffer(colShape.getElements()); real* colData = reinterpret_cast(memory_->getBuf()); - Im2ColFunctor im2col; + Im2ColFunctor im2col; GemmFunctor gemm; - size_t inputOffset = (inputChannels / groups_) * inputHeight * inputWidth; + size_t inputOffset = imShape.getElements(); size_t outputOffset = (outputChannels / groups_) * outputHeight * outputWidth; size_t filterOffset = filter.getElements() / groups_; @@ -171,18 +88,13 @@ public: for (size_t i = 0; i < batchSize; i++) { for (size_t g = 0; g < groups_; g++) { im2col(inputData + g * inputOffset, - inputChannels / groups_, - inputHeight, - inputWidth, - filterHeight, - filterWidth, + imShape, + colData, + colShape, strideH(), strideW(), paddingH(), - paddingW(), - outputHeight, - outputWidth, - colData); + paddingW()); int M = outputChannels / groups_; int N = outputHeight * outputWidth; @@ -249,15 +161,20 @@ public: real* outputGrad = inputs[0].data(); real* filterData = inputs[1].data(); real* inputGrad = outputs[0].data(); - - size_t size = inputChannels / groups_ * filterHeight * filterWidth * - outputHeight * outputWidth; - resizeBuffer(size); + TensorShape imShape = + TensorShape({inputChannels / groups_, inputHeight, inputWidth}); + TensorShape colShape = TensorShape({inputChannels / groups_, + filterHeight, + filterWidth, + outputHeight, + outputWidth}); + + resizeBuffer(colShape.getElements()); real* colData = reinterpret_cast(memory_->getBuf()); - Col2ImFunctor col2im; + Col2ImFunctor col2im; GemmFunctor gemm; - size_t inputOffset = (inputChannels / groups_) * inputHeight * inputWidth; + size_t inputOffset = imShape.getElements(); size_t outputOffset = (outputChannels / groups_) * outputHeight * outputWidth; size_t filterOffset = filter.getElements() / groups_; @@ -280,20 +197,14 @@ public: 0.0f, colData, N); - - col2im(colData, - inputChannels / groups_, - inputHeight, - inputWidth, - filterHeight, - filterWidth, + col2im(inputGrad + g * inputOffset, + imShape, + colData, + colShape, strideH(), strideW(), paddingH(), - paddingW(), - outputHeight, - outputWidth, - inputGrad + g * inputOffset); + paddingW()); } inputGrad += inputChannels * inputHeight * inputWidth; outputGrad += outputChannels * outputHeight * outputWidth; @@ -347,33 +258,33 @@ public: real* outputGrad = inputs[0].data(); real* inputData = inputs[1].data(); real* filterGrad = outputs[0].data(); - - size_t size = inputChannels / groups_ * filterHeight * filterWidth * - outputHeight * outputWidth; - resizeBuffer(size); + TensorShape imShape = + TensorShape({inputChannels / groups_, inputHeight, inputWidth}); + TensorShape colShape = TensorShape({inputChannels / groups_, + filterHeight, + filterWidth, + outputHeight, + outputWidth}); + + resizeBuffer(colShape.getElements()); real* colData = reinterpret_cast(memory_->getBuf()); - Im2ColFunctor im2col; + Im2ColFunctor im2col; GemmFunctor gemm; - size_t inputOffset = (inputChannels / groups_) * inputHeight * inputWidth; + size_t inputOffset = imShape.getElements(); size_t outputOffset = (outputChannels / groups_) * outputHeight * outputWidth; size_t filterOffset = filter.getElements() / groups_; for (size_t i = 0; i < batchSize; i++) { for (size_t g = 0; g < groups_; g++) { im2col(inputData + g * inputOffset, - inputChannels / groups_, - inputHeight, - inputWidth, - filterHeight, - filterWidth, + imShape, + colData, + colShape, strideH(), strideW(), paddingH(), - paddingW(), - outputHeight, - outputWidth, - colData); + paddingW()); int M = outputChannels / groups_; int K = outputHeight * outputWidth; -- GitLab From 9e6ed83cc4295414436ab784db10bf715637cddf Mon Sep 17 00:00:00 2001 From: hedaoyuan Date: Wed, 21 Jun 2017 11:26:40 +0800 Subject: [PATCH 0717/3256] Fix ImageExpandFunction. --- paddle/function/ImageExpandOp.cpp | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/paddle/function/ImageExpandOp.cpp b/paddle/function/ImageExpandOp.cpp index 625bf5b6edf..ca1d117db88 100644 --- a/paddle/function/ImageExpandOp.cpp +++ b/paddle/function/ImageExpandOp.cpp @@ -45,9 +45,7 @@ public: numOutputs_ = 1; } - virtual void calc(const BufferArgs& inputs, const BufferArgs& outputs) {} - - void check(const TensorShape& image, const TensorShape& sequence) const { + void checkShape(const TensorShape& image, const TensorShape& sequence) const { // image shape should be 4-dimensional. CHECK_EQ(image.ndims(), (size_t)4); // sequence shape should be 3-dimensional. @@ -108,12 +106,18 @@ public: ImageExpandFunction::init(config); } + void check(const BufferArgs& inputs, const BufferArgs& outputs) override { + const TensorShape& image = inputs[0].shape(); + const TensorShape& sequence = outputs[0].shape(); + checkShape(image, sequence); + } + void calc(const BufferArgs& inputs, const BufferArgs& outputs) override { CHECK_EQ(numInputs_, inputs.size()); CHECK_EQ(numOutputs_, outputs.size()); + check(inputs, outputs); const TensorShape& image = inputs[0].shape(); const TensorShape& sequence = outputs[0].shape(); - check(image, sequence); TensorShape imShape = TensorShape({image[1], image[2], image[3]}); TensorShape colShape = getColShape(image, sequence); @@ -149,15 +153,21 @@ public: ImageExpandFunction::init(config); } + void check(const BufferArgs& inputs, const BufferArgs& outputs) override { + const TensorShape& image = outputs[0].shape(); + const TensorShape& sequence = inputs[0].shape(); + checkShape(image, sequence); + } + void calc(const BufferArgs& inputs, const BufferArgs& outputs) override { CHECK_EQ(numInputs_, inputs.size()); CHECK_EQ(numOutputs_, outputs.size()); + check(inputs, outputs); // Since the implementation of Col2ImFunctor is ADD_TO, // this function only supports ADD_TO mode. CHECK_EQ(outputs[0].getArgType(), ADD_TO); const TensorShape& image = outputs[0].shape(); const TensorShape& sequence = inputs[0].shape(); - check(image, sequence); TensorShape imShape = TensorShape({image[1], image[2], image[3]}); TensorShape colShape = getColShape(image, sequence); -- GitLab From 5bfcb7f853834009facd51ce5e2a989240bc3fcc Mon Sep 17 00:00:00 2001 From: hedaoyuan Date: Wed, 21 Jun 2017 11:31:08 +0800 Subject: [PATCH 0718/3256] Remove useless code. --- paddle/function/GemmConvOp.h | 62 ----------- paddle/function/GemmConvOpGpu.cu | 186 ------------------------------- 2 files changed, 248 deletions(-) delete mode 100644 paddle/function/GemmConvOp.h delete mode 100644 paddle/function/GemmConvOpGpu.cu diff --git a/paddle/function/GemmConvOp.h b/paddle/function/GemmConvOp.h deleted file mode 100644 index 9f11cce597a..00000000000 --- a/paddle/function/GemmConvOp.h +++ /dev/null @@ -1,62 +0,0 @@ -/* 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 "ConvOp.h" - -namespace paddle { - -/* - * imData = [input_channels, input_height, input_width] - * colData = [input_channels, filter_height, filter_width, - * output_height, output_width] - */ -template -class Im2ColFunctor { -public: - void operator()(const T* imData, - int inputChannels, - int inputHeight, - int inputWidth, - int filterHeight, - int filterWidth, - int strideHeight, - int strideWidth, - int paddingHeight, - int paddingWidth, - int outputHeight, - int outputWidth, - T* colData); -}; - -template -class Col2ImFunctor { -public: - void operator()(const T* colData, - int inputChannels, - int inputHeight, - int inputWidth, - int filterHeight, - int filterWidth, - int strideHeight, - int strideWidth, - int paddingHeight, - int paddingWidth, - int outputHeight, - int outputWidth, - T* imData); -}; - -} // namespace paddle diff --git a/paddle/function/GemmConvOpGpu.cu b/paddle/function/GemmConvOpGpu.cu deleted file mode 100644 index 2a1795ff0fb..00000000000 --- a/paddle/function/GemmConvOpGpu.cu +++ /dev/null @@ -1,186 +0,0 @@ -/* 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 "ConvOp.h" -#include "GemmConvOp.h" - -namespace paddle { - -template -__global__ -void im2col(const T* data_im, int numOuts, int height, int width, - int blockH, int blockW, - int strideH, int strideW, - int paddingH, int paddingW, - int height_col, int width_col, - T* data_col) { - int index = - (blockIdx.x * gridDim.y + blockIdx.y) * blockDim.x + threadIdx.x; - if (index < numOuts) { - int w_out = index % width_col; - index /= width_col; - int h_out = index % height_col; - int channel_in = index / height_col; - int channel_out = channel_in * blockH * blockW; - int h_in = h_out * strideH; - int w_in = w_out * strideW; - - data_col += (channel_out * height_col + h_out) * width_col + w_out; - for (int i = 0; i < blockH; ++i) { - for (int j = 0; j < blockW; ++j) { - int rIdx = int(h_in+i); - int cIdx = int(w_in+j); - if ((rIdx-(int)paddingH) >= (int)height || - (rIdx-(int)paddingH) < 0 || - (cIdx-(int)paddingW) >= (int)width || - (cIdx-(int)paddingW) < 0) { - *data_col = 0; - } else { - rIdx = rIdx + channel_in*height - paddingH; - cIdx = cIdx - paddingW; - *data_col = data_im[rIdx* width + cIdx]; - } - data_col += height_col * width_col; - } - } - } -} - -template -class Im2ColFunctor { -public: - void operator()(const T* imData, - int inputChannels, - int inputHeight, - int inputWidth, - int filterHeight, - int filterWidth, - int strideHeight, - int strideWidth, - int paddingHeight, - int paddingWidth, - int outputHeight, - int outputWidth, - T* colData) { - int numKernels = inputChannels * outputHeight * outputWidth; - int blocks = (numKernels + 1024 -1) / 1024; - int blockX = 512; - int blockY = (blocks + 512 - 1) / 512; - dim3 threads(1024, 1); - dim3 grid(blockX, blockY); - im2col<<< grid, threads, 0, STREAM_DEFAULT >>> - (imData, numKernels, inputHeight, inputWidth, filterHeight, filterWidth, - strideHeight, strideWidth, paddingHeight, paddingWidth, - outputHeight, outputWidth, colData); - CHECK_SYNC("Im2ColFunctor GPU failed"); - } -}; - -template -__global__ -void col2im(size_t n, const T* data_col, size_t height, - size_t width, size_t channels, - size_t blockH, size_t blockW, - size_t strideH, size_t strideW, - size_t paddingH, size_t paddingW, - size_t height_col, size_t width_col, - T* data_im) { - size_t index = - (blockIdx.x * gridDim.y + blockIdx.y) * blockDim.x + threadIdx.x; - if (index < n) { - T val = 0; - int w = int(index % width); - int h = int((index / width) % height); - int c = int(index / (width * height)); - if ((w - (int)paddingW) >= 0 && - (w - (int)paddingW) < (width-2 * paddingW) && - (h - (int)paddingH) >= 0 && - (h - paddingH) < (height - 2 * paddingH)) { - // compute the start and end of the output - int w_col_start = - (w < (int)blockW) ? 0 : (w - int(blockW)) / (int)strideW + 1; - int w_col_end = - min((int)(w / (int)strideW + 1), (int)(width_col)); - int h_col_start = - (h < (int)blockH) ? 0 : (h - (int)blockH) / (int)strideH + 1; - int h_col_end = min(int(h / strideH + 1), int(height_col)); - for (int h_col = h_col_start; h_col < h_col_end; ++h_col) { - for (int w_col = w_col_start; w_col < w_col_end; ++w_col) { - // the col location: [c * width * height + h_out, w_out] - int c_col = int(c * blockH* blockW) + \ - (h - h_col * (int)strideH) * (int)blockW + - (w - w_col * (int)strideW); - val += data_col[(c_col * height_col + h_col) * width_col + w_col]; - } - } - h -= paddingH; - w -= paddingW; - data_im[c*((width-2*paddingW) * (height-2*paddingH)) + - h*(width-2*paddingW) + w] += val; - } - } -} - -template -class Col2ImFunctor { -public: - void operator()(const T* colData, - int inputChannels, - int inputHeight, - int inputWidth, - int filterHeight, - int filterWidth, - int strideHeight, - int strideWidth, - int paddingHeight, - int paddingWidth, - int outputHeight, - int outputWidth, - T* imData) { - size_t numKernels = inputChannels * (inputHeight + 2*paddingHeight) - * (inputWidth + 2*paddingWidth); - - size_t blocks = (numKernels + 1024 -1) / 1024; - size_t blockX = 512; - size_t blockY = (blocks+512-1)/512; - dim3 threads(1024, 1); - dim3 grid(blockX, blockY); - - // To avoid involving atomic operations, we will launch one kernel per - // bottom dimension, and then in the kernel add up the top dimensions. - col2im<<< grid, threads, 0, STREAM_DEFAULT >>> - (numKernels, - colData, - inputHeight + 2*paddingHeight, - inputWidth + 2*paddingWidth, - inputChannels, - filterHeight, - filterWidth, - strideHeight, - strideWidth, - paddingHeight, - paddingWidth, - outputHeight, - outputWidth, - imData); - CHECK_SYNC("Col2ImFunctor GPU failed"); - } -}; - -template class Im2ColFunctor; -template class Im2ColFunctor; -template class Col2ImFunctor; -template class Col2ImFunctor; - -} // namespace paddle -- GitLab From 09d712d6aec0376b5ccea09e0d2c546ea1149aba Mon Sep 17 00:00:00 2001 From: hedaoyuan Date: Wed, 21 Jun 2017 11:38:25 +0800 Subject: [PATCH 0719/3256] Remove useless code(Matrix::convExpand and Matrix::convShrink). --- paddle/cuda/include/hl_cnn.h | 67 ---------- paddle/cuda/include/stub/hl_cnn_stub.h | 30 ----- paddle/cuda/src/hl_cuda_cnn.cu | 128 ------------------ paddle/math/Matrix.cpp | 172 ------------------------- paddle/math/Matrix.h | 99 -------------- 5 files changed, 496 deletions(-) diff --git a/paddle/cuda/include/hl_cnn.h b/paddle/cuda/include/hl_cnn.h index f55197c8c9e..9f84db72da2 100644 --- a/paddle/cuda/include/hl_cnn.h +++ b/paddle/cuda/include/hl_cnn.h @@ -17,73 +17,6 @@ limitations under the License. */ #include "hl_base.h" -/** - * @brief Shrink column to feature. - * - * @param[in] dataCol expand data. - * @param[in] channels number of channel. - * @param[in] height image height. - * @param[in] width image width. - * @param[in] blockH filter height. - * @param[in] blockW filter width. - * @param[in] strideH stride height. - * @param[in] strideW stride width. - * @param[in] paddingH padding height. - * @param[in] paddingW padding width. - * @param[in] outputH output height. - * @param[in] outputW output width. - * @param[out] dataIm output image data. - * @param[in] alpha - * @param[in] beta - */ -extern void hl_shrink_col2feature(const real* dataCol, - size_t channels, - size_t height, - size_t width, - size_t blockH, - size_t blockW, - size_t strideH, - size_t strideW, - size_t paddingH, - size_t paddingW, - size_t outputH, - size_t outputW, - real* dataIm, - real alpha = 1.0f, - real beta = 0.0f); - -/** - * @brief Expand feature to column. - * - * @param[in] dataIm input image data. - * @param[in] channels number of channel. - * @param[in] height image height. - * @param[in] width image width. - * @param[in] blockH filter height. - * @param[in] blockW filter width. - * @param[in] strideH stride height. - * @param[in] strideW stride width. - * @param[in] paddingH padding height. - * @param[in] paddingW padding width. - * @param[in] outputH output height. - * @param[in] outputW output width. - * @param[out] dataCol expand data. - * - */ -extern void hl_expand_feature2col(const real* dataIm, - size_t channels, - size_t height, - size_t width, - size_t blockH, - size_t blockW, - size_t strideH, - size_t strideW, - size_t paddingH, - size_t paddingW, - size_t outputH, - size_t outputW, - real* dataCol); - /** * @brief Maximum pool forward. * diff --git a/paddle/cuda/include/stub/hl_cnn_stub.h b/paddle/cuda/include/stub/hl_cnn_stub.h index 039551c6cc6..2bbb9fa8dfd 100644 --- a/paddle/cuda/include/stub/hl_cnn_stub.h +++ b/paddle/cuda/include/stub/hl_cnn_stub.h @@ -17,36 +17,6 @@ limitations under the License. */ #include "hl_cnn.h" -inline void hl_shrink_col2feature(const real* dataCol, - size_t channels, - size_t height, - size_t width, - size_t blockH, - size_t blockW, - size_t strideH, - size_t strideW, - size_t paddingH, - size_t paddingW, - size_t outputH, - size_t outputW, - real* dataIm, - real alpha, - real beta) {} - -inline void hl_expand_feature2col(const real* dataIm, - size_t channels, - size_t height, - size_t width, - size_t blockH, - size_t blockW, - size_t strideH, - size_t strideW, - size_t paddingH, - size_t paddingW, - size_t outputH, - size_t outputW, - real* dataCol) {} - inline void hl_maxpool_forward(const int frameCnt, const real* inputData, const int channels, diff --git a/paddle/cuda/src/hl_cuda_cnn.cu b/paddle/cuda/src/hl_cuda_cnn.cu index b94f4d8fe4a..b6e3e63a4f5 100644 --- a/paddle/cuda/src/hl_cuda_cnn.cu +++ b/paddle/cuda/src/hl_cuda_cnn.cu @@ -18,134 +18,6 @@ limitations under the License. */ #include "hl_cnn.h" #include "hl_device_functions.cuh" -__global__ void KeFeature2col(size_t n, size_t height, const real* data_im, - size_t blockH, size_t blockW, size_t width, - size_t strideH, size_t strideW, - size_t paddingH, size_t paddingW, - size_t height_col, size_t width_col, - real* data_col) { - size_t index = - (blockIdx.x * gridDim.y + blockIdx.y) * blockDim.x + threadIdx.x; - if (index < n) { - size_t w_out = index % width_col; - index /= width_col; - size_t h_out = index % height_col; - size_t channel_in = index / height_col; - size_t channel_out = channel_in * blockH * blockW; - size_t h_in = h_out * strideH; - size_t w_in = w_out * strideW; - - data_col += (channel_out * height_col + h_out) * width_col + w_out; - for (size_t i = 0; i < blockH; ++i) { - for (size_t j = 0; j < blockW; ++j) { - int rIdx = int(h_in+i); - int cIdx = int(w_in+j); - if ((rIdx-(int)paddingH) >= (int)height || - (rIdx-(int)paddingH) < 0 || - (cIdx-(int)paddingW) >= (int)width || - (cIdx-(int)paddingW) < 0) { - *data_col = 0; - } else { - rIdx = rIdx + channel_in*height - paddingH; - cIdx = cIdx - paddingW; - *data_col = data_im[rIdx* width + cIdx]; - } - data_col += height_col * width_col; - } - } - } -} - -void hl_expand_feature2col(const real* dataIm, size_t channels, - size_t height, size_t width, - size_t blockH, size_t blockW, - size_t strideH, size_t strideW, - size_t paddingH, size_t paddingW, - size_t outputH, size_t outputW, - real* dataCol) { - size_t numKernels = channels * outputH * outputW; - - size_t blocks = (numKernels + 1024 -1) / 1024; - size_t blockX = 512; - size_t blockY = (blocks+512-1)/512; - dim3 threads(1024, 1); - dim3 grid(blockX, blockY); - KeFeature2col<<< grid, threads, 0, STREAM_DEFAULT >>> - (numKernels, height, dataIm, blockH, blockW, width, - strideH, strideW, paddingH, paddingW, - outputH, outputW, dataCol); - CHECK_SYNC("hl_expand_feature2col failed"); -} - -__global__ void KeCol2Feature(size_t n, const real* data_col, size_t height, - size_t width, size_t channels, - size_t blockH, size_t blockW, - size_t strideH, size_t strideW, - size_t paddingH, size_t paddingW, - size_t height_col, size_t width_col, - real* data_im, real alpha, real beta) { - size_t index = - (blockIdx.x * gridDim.y + blockIdx.y) * blockDim.x + threadIdx.x; - if (index < n) { - real val = 0; - int w = int(index % width); - int h = int((index / width) % height); - int c = int(index / (width * height)); - if ((w - (int)paddingW) >= 0 && - (w - (int)paddingW) < (width-2 * paddingW) && - (h - (int)paddingH) >= 0 && - (h - paddingH) < (height - 2 * paddingH)) { - // compute the start and end of the output - int w_col_start = - (w < (int)blockW) ? 0 : (w - int(blockW)) / (int)strideW + 1; - int w_col_end = - min((int)(w / (int)strideW + 1), (int)(width_col)); - int h_col_start = - (h < (int)blockH) ? 0 : (h - (int)blockH) / (int)strideH + 1; - int h_col_end = min(int(h / strideH + 1), int(height_col)); - for (int h_col = h_col_start; h_col < h_col_end; ++h_col) { - for (int w_col = w_col_start; w_col < w_col_end; ++w_col) { - // the col location: [c * width * height + h_out, w_out] - int c_col = int(c * blockH* blockW) + \ - (h - h_col * (int)strideH) * (int)blockW + - (w - w_col * (int)strideW); - val += data_col[(c_col * height_col + h_col) * width_col + w_col]; - } - } - h -= paddingH; - w -= paddingW; - real tD = data_im[c*((width-2*paddingW) * (height-2*paddingH)) + - h*(width-2*paddingW) + w]; - data_im[c*((width-2*paddingW) * (height-2*paddingH)) + - h*(width-2*paddingW) + w] = alpha * val + beta*tD; - } - } -} - -void hl_shrink_col2feature(const real * dataCol, size_t channels, - size_t height, size_t width, - size_t blockH, size_t blockW, - size_t strideH, size_t strideW, - size_t paddingH, size_t paddingW, - size_t outputH, size_t outputW, - real* dataIm, real alpha, real beta) { - size_t numKernels = channels * (height + 2*paddingH) * (width + 2*paddingW); - - size_t blocks = (numKernels + 1024 -1) / 1024; - size_t blockX = 512; - size_t blockY = (blocks+512-1)/512; - dim3 threads(1024, 1); - dim3 grid(blockX, blockY); - - // To avoid involving atomic operations, we will launch one kernel per - // bottom dimension, and then in the kernel add up the top dimensions. - KeCol2Feature<<< grid, threads, 0, STREAM_DEFAULT >>> - (numKernels, dataCol, height + 2*paddingH, width + 2*paddingW, - channels, blockH, blockW, strideH, strideW, paddingH, paddingW, - outputH, outputW, dataIm, alpha, beta); - CHECK_SYNC("hl_shrink_col2feature failed"); -} - __global__ void KeMaxPoolForward(const int nthreads, const real* inputData, const int channels, const int height, const int width, diff --git a/paddle/math/Matrix.cpp b/paddle/math/Matrix.cpp index c910146164e..a3ad9d46e49 100644 --- a/paddle/math/Matrix.cpp +++ b/paddle/math/Matrix.cpp @@ -1016,81 +1016,6 @@ void GpuMatrix::check(std::ostream& os, Matrix& refMat, bool printDiff) { LOG(INFO) << "the diffCnt is " << diffCnt; } -void GpuMatrix::convExpand(Matrix& feature, - int feaImgHeight, - int feaImgWidth, - int channels, - int blockH, - int blockW, - int strideH, - int strideW, - int paddingH, - int paddingW, - int outputH, - int outputW) { - CHECK(feature.useGpu_ == true) << "Matrix type are not equal"; - - CHECK_EQ(size_t(feaImgHeight * feaImgWidth * channels), - feature.getHeight() * feature.getWidth()) - << "Matrix dimensions are not equal"; - - size_t elemCnt = outputH * outputW * blockH * blockW * channels; - CHECK_EQ(elemCnt, height_ * width_) << "Matrix dimensions are not equal"; - - hl_expand_feature2col(feature.getData(), - channels, - feaImgHeight, - feaImgWidth, - blockH, - blockW, - strideH, - strideW, - paddingH, - paddingW, - outputH, - outputW, - getData()); -} - -void GpuMatrix::convShrink(Matrix& expandFeat, - int thisImgHeight, - int thisImgWidth, - int channels, - int blockH, - int blockW, - int strideH, - int strideW, - int paddingH, - int paddingW, - int outputH, - int outputW, - real alpha, - real beta) { - CHECK(expandFeat.useGpu_ == true) << "Matrix type are not equal"; - CHECK_EQ(size_t(thisImgHeight * thisImgWidth * channels), - getHeight() * getWidth()) - << "Matrix dimensions are not equal"; - - size_t elemCnt = outputH * outputW * blockW * blockH * channels; - CHECK(elemCnt == expandFeat.getHeight() * expandFeat.getWidth()) - << "Matrix dimensions are not equal"; - hl_shrink_col2feature(expandFeat.getData(), - channels, - thisImgHeight, - thisImgWidth, - blockH, - blockW, - strideH, - strideW, - paddingH, - paddingW, - outputH, - outputW, - getData(), - alpha, - beta); -} - void GpuMatrix::maxPoolForward(Matrix& inputMat, size_t imgSizeH, size_t imgSizeW, @@ -1775,103 +1700,6 @@ void CpuMatrix::inverse(MatrixPtr& matInv, bool memAlloc) { CHECK_EQ(info, 0); } -void CpuMatrix::convExpand(Matrix& feature, - int feaImgHeight, - int feaImgWidth, - int channels, - int blockH, - int blockW, - int strideH, - int strideW, - int paddingH, - int paddingW, - int outputH, - int outputW) { - CHECK(feature.useGpu_ == false) << "Matrix type are not equal"; - - CHECK_EQ(size_t(feaImgHeight * feaImgWidth * channels), - feature.getHeight() * feature.getWidth()) - << "Matrix dimensions are not equal"; - - size_t elemCnt = outputH * outputW * blockH * blockW * channels; - CHECK_EQ(elemCnt, height_ * width_) << "Matrix dimensions are not equal"; - - int channelsCol = channels * blockH * blockW; - real* srcData = feature.getData(); - for (int c = 0; c < channelsCol; ++c) { - int wOffset = c % blockW; - int hOffset = (c / blockW) % blockH; - int c_im = c / blockH / blockW; - for (int h = 0; h < outputH; ++h) { - for (int w = 0; w < outputW; ++w) { - // no c_im*height to Exclude the channel number - int imgRowIdx = h * strideH + hOffset; - int imgColIdx = w * strideW + wOffset; - if ((imgRowIdx - paddingH) < 0 || - (imgRowIdx - paddingH) >= feaImgHeight || - (imgColIdx - paddingW) < 0 || - (imgColIdx - paddingW) >= feaImgWidth) { - data_[(c * outputH + h) * outputW + w] = 0; - } else { - imgRowIdx += c_im * feaImgHeight - paddingH; - imgColIdx -= paddingW; - data_[(c * outputH + h) * outputW + w] = - srcData[imgRowIdx * feaImgWidth + imgColIdx]; - } - } - } - } -} - -void CpuMatrix::convShrink(Matrix& expandFeat, - int thisImgHeight, - int thisImgWidth, - int channels, - int blockH, - int blockW, - int strideH, - int strideW, - int paddingH, - int paddingW, - int outputH, - int outputW, - real alpha, - real beta) { - CHECK(expandFeat.useGpu_ == false) << "Matrix type are not equal"; - CHECK_EQ(size_t(thisImgHeight * thisImgWidth * channels), - getHeight() * getWidth()) - << "Matrix dimensions are not equal"; - - size_t elemCnt = outputH * outputW * blockH * blockW * channels; - - CHECK(elemCnt == expandFeat.getHeight() * expandFeat.getWidth()) - << "Matrix dimensions are not equal"; - - real* expandData = expandFeat.getData(); - int channelsCol = channels * blockH * blockW; - for (int c = 0; c < channelsCol; ++c) { - int wOffset = c % blockW; - int hOffset = (c / blockW) % blockH; - int c_im = c / blockW / blockH; - for (int h = 0; h < outputH; ++h) { - for (int w = 0; w < outputW; ++w) { - int imRowIdx = h * strideH + hOffset; - int imColIdx = w * strideW + wOffset; - if ((imRowIdx - paddingH) >= 0 && - (imRowIdx - paddingH) < thisImgHeight && - (imColIdx - paddingW) >= 0 && - (imColIdx - paddingW) < thisImgWidth) { - imRowIdx += c_im * thisImgHeight - paddingH; - imColIdx -= paddingW; - data_[imRowIdx * thisImgWidth + imColIdx] = - alpha * expandData[(c * outputH + h) * outputW + w] + - beta * data_[imRowIdx * thisImgWidth + imColIdx]; - } - } - } - } -} - void CpuMatrix::maxPoolForward(Matrix& inputMat, size_t imgSizeH, size_t imgSizeW, diff --git a/paddle/math/Matrix.h b/paddle/math/Matrix.h index 748be850b4c..bbf98a609c6 100644 --- a/paddle/math/Matrix.h +++ b/paddle/math/Matrix.h @@ -858,49 +858,6 @@ public: LOG(FATAL) << "Not implemented"; } - /** - * This function is used to calculate the convolution: - * - * It will expand a feature matrix according to the - * convolution filters - */ - virtual void convExpand(Matrix& feature, - int feaImgHeight, - int feaImgWidth, - int channels, - int blockH, - int blockW, - int strideH, - int strideW, - int paddingH, - int paddingW, - int outputH, - int outputW) { - LOG(FATAL) << "Not implemeted"; - } - - /** - * This function is the reverse implementation of convExpand: - * - * Its function is to restore a expanded-matrix into a feature matrix - */ - virtual void convShrink(Matrix& expandColMat, - int thisImgHeight, - int thisImgWidth, - int channels, - int blockH, - int blockW, - int strideH, - int strideW, - int paddingH, - int paddingW, - int outputH, - int outputW, - real alpha = 1.0f, - real beta = 0.0f) { - LOG(FATAL) << "Not implemeted"; - } - /** * Pooling forward operation, pick out the largest element * in the sizeX of value @@ -1334,34 +1291,6 @@ public: void classificationError(Matrix& output, IVector& label, size_t topkSize = 1); - void convExpand(Matrix& feature, - int feaImgHeight, - int feaImgWidth, - int channels, - int blockH, - int blockW, - int strideH, - int strideW, - int paddingH, - int paddingW, - int outputH, - int outputW); - - void convShrink(Matrix& expandColMat, - int thisImgHeight, - int thisImgWidth, - int channels, - int blockH, - int blochW, - int strideH, - int strideW, - int paddingH, - int paddingWreal, - int outputH, - int outputW, - real alpha = 1.0f, - real beta = 0.0f); - void maxPoolForward(Matrix& inputMat, size_t imgSizeH, size_t imgSizeW, @@ -1521,34 +1450,6 @@ public: MatrixPtr clone(size_t height, size_t width, bool useGpu = false); - void convExpand(Matrix& feature, - int feaImgHeight, - int feaImgWidth, - int channels, - int blcokH, - int blockW, - int strideH, - int strideW, - int paddingH, - int paddingW, - int outputH, - int outputW); - - void convShrink(Matrix& expandFeat, - int thisImgHeight, - int thisImgWidth, - int channels, - int blockH, - int blockW, - int strideH, - int strideW, - int paddingH, - int paddingW, - int outputH, - int outputW, - real alpha = 1.0f, - real beta = 0.0f); - void maxPoolForward(Matrix& inputMat, size_t imgSizeH, size_t imgSizeW, -- GitLab From 1eab8cce32b61f201098be482359defbfffc941b Mon Sep 17 00:00:00 2001 From: zlx Date: Wed, 21 Jun 2017 14:31:29 +0800 Subject: [PATCH 0720/3256] modify the annotations of HookAttribute, Variable declaration --- paddle/parameter/ParameterUpdaterHook.cpp | 31 ++++++++++--------- python/paddle/trainer_config_helpers/attrs.py | 20 +++++++----- 2 files changed, 29 insertions(+), 22 deletions(-) diff --git a/paddle/parameter/ParameterUpdaterHook.cpp b/paddle/parameter/ParameterUpdaterHook.cpp index 738e86a6221..66e554a70d9 100644 --- a/paddle/parameter/ParameterUpdaterHook.cpp +++ b/paddle/parameter/ParameterUpdaterHook.cpp @@ -31,9 +31,9 @@ namespace paddle { /** * The static pruning hook - * Static means user specific a sparsity_ratio before training start, and the + * Static means user specify a sparsity_ratio before training started, and the * network will prune the parameters based on the sparsity_ratio. More deatils - * can see https://arxiv.org/pdf/1506.02626.pdf. + * can be found https://arxiv.org/pdf/1506.02626.pdf. */ class StaticPruningHook : public IParameterUpdaterHook { @@ -57,29 +57,31 @@ public: } void generateMask(Parameter* para) { - VectorPtr vec = para->getBuf(PARAMETER_VALUE); - maskTemp_ = Vector::create(para->getSize(), false); - maskTemp_->zeroMem(); - real* dataPtr = maskTemp_->getData(); + + VectorPtr maskTemp = Vector::create(para->getSize(), false); + maskTemp->zeroMem(); + real* maskTempData = maskTemp->getData(); size_t nonZeroNum = para->getSize() * (1 - sparsityRatio_); - VectorPtr vecCpu = Vector::create(para->getSize(), false); - vecCpu->copyFrom(*vec); + VectorPtr paraVec = para->getBuf(PARAMETER_VALUE); + VectorPtr paraCpuCopy = Vector::create(para->getSize(), false); + + paraCpuCopy->copyFrom(*paraVec); std::vector> param; for (size_t i = 0; i < para->getSize(); i++) - param.push_back(std::make_pair(fabs(vecCpu->getData()[i]), i)); + param.push_back(std::make_pair(fabs(paraCpuCopy->getData()[i]), i)); std::partial_sort( param.begin(), param.begin() + nonZeroNum, param.end(), sortPairAscend); - for (size_t i = 0; i < nonZeroNum; i++) dataPtr[param[i].second] = 1.0; + for (size_t i = 0; i < nonZeroNum; i++) maskTempData[param[i].second] = 1.0; // Currently just use a mask vector for hack. if (para->useGpu()) { maskVec_ = Vector::create(para->getSize(), para->useGpu()); - maskVec_->copyFrom(*maskTemp_); + maskVec_->copyFrom(*maskTemp); } else { - maskVec_ = maskTemp_; + maskVec_ = maskTemp; } } @@ -91,15 +93,14 @@ public: VLOG(3) << "Initialize Parameter " << para; SetDevice device(para->getDeviceId()); - auto& vec = para->getBuf(PARAMETER_VALUE); - vec->dotMul(*maskVec_); + auto& paraVec = para->getBuf(PARAMETER_VALUE); + paraVec->dotMul(*maskVec_); } private: SameThreadChecker updateThreadChecker_; std::atomic initCount_; VectorPtr maskVec_; - VectorPtr maskTemp_; real sparsityRatio_; }; diff --git a/python/paddle/trainer_config_helpers/attrs.py b/python/paddle/trainer_config_helpers/attrs.py index bf12ad644dc..66163bdc8dc 100644 --- a/python/paddle/trainer_config_helpers/attrs.py +++ b/python/paddle/trainer_config_helpers/attrs.py @@ -58,15 +58,21 @@ def is_compatible_with(x, Type): class HookAttribute(object): """ - Hook Attribute object. The hook is an auxiliary operation that occurs - during network propagation. - NOTE: IT IS A HIGH LEVEL USER INTERFACE. - - :param type: Hook type, eg: 'pruning' + 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: + 'pruning' : user specify a sparsity_ratio before training started, and the + 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, + param_attr=ParameterAttribute(update_hooks=hk) ) + The pruning deatils can be found https://arxiv.org/pdf/1506.02626.pdf :type type: string :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. + it represents the ratio of the zero elements to be set by the Parameter. :type sparsity_ratio: float or None """ @@ -78,7 +84,7 @@ class HookAttribute(object): assert is_compatible_with( self.sparsity_ratio, float), 'sparisity_ratio must be float type' - assert self.sparsity_ratio <= 1 and self.sparsity_ratio >= 0, 'sparisity must be a flaot between [0, 1] ' + assert self.sparsity_ratio <= 1 and self.sparsity_ratio >= 0, 'sparisity_ratio must be a float between [0, 1] ' def __call__(self): return ParameterHook(self.type, sparsity_ratio=self.sparsity_ratio) -- GitLab From badcdfe1e539ffcad75f601e687a83fd1512cff1 Mon Sep 17 00:00:00 2001 From: wuyi05 Date: Wed, 21 Jun 2017 15:05:41 +0800 Subject: [PATCH 0721/3256] pserver etcd registration --- go/cmd/pserver/pserver.go | 20 ++++++- go/pserver/client_test.go | 8 ++- go/pserver/service.go | 112 ++++++++++++++++++++++++++++++++++++- go/pserver/service_test.go | 25 ++++++--- go/utils/helper.go | 45 +++++++++++++++ go/utils/helper_test.go | 10 ++++ 6 files changed, 206 insertions(+), 14 deletions(-) create mode 100644 go/utils/helper.go create mode 100644 go/utils/helper_test.go diff --git a/go/cmd/pserver/pserver.go b/go/cmd/pserver/pserver.go index f0be251c247..ddf5ad40fd2 100644 --- a/go/cmd/pserver/pserver.go +++ b/go/cmd/pserver/pserver.go @@ -5,18 +5,34 @@ import ( "net/http" "net/rpc" "strconv" + "time" "github.com/namsral/flag" "github.com/PaddlePaddle/Paddle/go/pserver" + log "github.com/sirupsen/logrus" ) func main() { port := flag.Int("port", 0, "port of the pserver") + etcdEndpoint := flag.String("etcd-endpoint", "http://127.0.0.1:2379", + "comma separated endpoint string for pserver to connect to etcd") + etcdTimeout := flag.Int("etcd-timeout", 5, "timeout for etcd calls") + logLevel := flag.String("log-level", "info", "log level, one of debug") flag.Parse() - s := pserver.NewService() - err := rpc.Register(s) + level, err := log.ParseLevel(*logLevel) + if err != nil { + panic(err) + } + log.SetLevel(level) + + timeout := time.Second * time.Duration((*etcdTimeout)) + s, err := pserver.NewService(*etcdEndpoint, timeout) + if err != nil { + panic(err) + } + err = rpc.Register(s) if err != nil { panic(err) } diff --git a/go/pserver/client_test.go b/go/pserver/client_test.go index d0371a26a13..6ecf1fa08a0 100644 --- a/go/pserver/client_test.go +++ b/go/pserver/client_test.go @@ -7,6 +7,7 @@ import ( "strconv" "strings" "testing" + "time" "github.com/PaddlePaddle/Paddle/go/pserver" ) @@ -30,9 +31,12 @@ func init() { port[i] = p go func(l net.Listener) { - s := pserver.NewService() + s, err := pserver.NewService("", time.Second*5) + if err != nil { + panic(err) + } server := rpc.NewServer() - err := server.Register(s) + err = server.Register(s) if err != nil { panic(err) } diff --git a/go/pserver/service.go b/go/pserver/service.go index 78a2bfaf634..a5c76857abe 100644 --- a/go/pserver/service.go +++ b/go/pserver/service.go @@ -1,9 +1,18 @@ package pserver import ( + "context" "errors" "fmt" + "strconv" + "strings" "sync" + "time" + + "github.com/PaddlePaddle/Paddle/go/utils" + "github.com/coreos/etcd/clientv3" + "github.com/coreos/etcd/clientv3/concurrency" + log "github.com/sirupsen/logrus" ) // ElementType is the type of elements of a Parameter. @@ -47,14 +56,113 @@ type Service struct { mu sync.Mutex opt *optimizer paramMap map[string]Parameter + + etcdEndpoints string + etcdClient *clientv3.Client + // etcdTimeout is also used as retry intervals. + etcdTimeout time.Duration + // desired number of pservers in the job. + // assume desired will not change during one training job. + desired int + // FIXME: ensure GetExternalIP gets the correct ip for trainers to connect. + externalIP string } // NewService creates a new service. -func NewService() *Service { +func NewService(endpoints string, timeout time.Duration) (*Service, error) { s := &Service{opt: newOptimizer(sgd, 0.005)} s.paramMap = make(map[string]Parameter) s.initialized = make(chan struct{}) - return s + s.etcdEndpoints = endpoints + s.etcdTimeout = timeout + + var err error + s.externalIP, err = utils.GetExternalIP() + if err != nil { + return nil, err + } + + if endpoints != "" { + // initialize connection to etcd, try + ep := strings.Split(s.etcdEndpoints, ",") + for { + cli, err := clientv3.New(clientv3.Config{ + Endpoints: ep, + DialTimeout: s.etcdTimeout, + }) + if err != nil { + log.Errorf("connect to etcd error: %v", err) + time.Sleep(s.etcdTimeout) + continue + } + s.etcdClient = cli + log.Debugf("inited client to %s", s.etcdEndpoints) + break + } + // wait and set s.desired init value + for { + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + resp, err := s.etcdClient.Get(ctx, "/ps_desired") + cancel() + if err != nil { + log.Errorf("getting /ps_desired error: %v", err) + time.Sleep(s.etcdTimeout) + continue + } + for _, ev := range resp.Kvs { + log.Debugf("key: %s, value: %s", ev.Key, ev.Value) + if string(ev.Key) == "/ps_desired" { + s.desired, err = strconv.Atoi(string(ev.Value)) + if err != nil { + log.Errorf("value of /ps_desired invalid %v\n", err) + time.Sleep(s.etcdTimeout) + // NOTE: wait util ps_desired value change + continue + } + } + } + break + } + s.registerPserverEtcd() + } // if endpoints != "" + // Bypass etcd registration if no endpoints specified + return s, nil +} + +// registerPserverEtcd registers pserver node on etcd using transaction. +func (s *Service) registerPserverEtcd() (*clientv3.TxnResponse, error) { + return concurrency.NewSTMRepeatable(context.TODO(), s.etcdClient, func(c concurrency.STM) error { + for i := 0; i < s.desired; i++ { + psKey := "/ps/" + strconv.Itoa(i) + log.Debugf("checking %s", psKey) + ps := c.Get(psKey) + log.Debugf("got value (%s) for key: %s", ps, psKey) + + resp, err := s.etcdClient.Grant(context.TODO(), 5) + if err != nil { + log.Fatal(err) + } + + if ps == "" { + // find the first id and write info + c.Put(psKey, s.externalIP, clientv3.WithLease(resp.ID)) + log.Debugf("set pserver node %s with value %s", psKey, s.externalIP) + ch, kaerr := s.etcdClient.KeepAlive(context.TODO(), resp.ID) + if kaerr != nil { + log.Errorf("keepalive etcd node error: %v", kaerr) + return kaerr + } + // FIXME: does this really needed? + go func(ch <-chan *clientv3.LeaseKeepAliveResponse) { + ka := <-ch + log.Debugf("keepalive: %d\n", ka.TTL) + }(ch) + break + } + } + log.Debug("register finished") + return nil + }) } // InitParam initializes a parameter. diff --git a/go/pserver/service_test.go b/go/pserver/service_test.go index b746d13e1ca..f3175355921 100644 --- a/go/pserver/service_test.go +++ b/go/pserver/service_test.go @@ -10,12 +10,15 @@ import ( ) func TestFull(t *testing.T) { - s := pserver.NewService() + s, err := pserver.NewService("", time.Second*5) + if err != nil { + t.Error(err) + } var p pserver.Parameter p.Name = "param_a" p.Content = []byte{1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0} p.ElementType = pserver.Int32 - err := s.InitParam(pserver.ParameterWithConfig{Param: p, Config: nil}, nil) + err = s.InitParam(pserver.ParameterWithConfig{Param: p, Config: nil}, nil) if err != nil { t.FailNow() } @@ -72,8 +75,11 @@ func TestFull(t *testing.T) { } func TestMultipleInit(t *testing.T) { - s := pserver.NewService() - err := s.FinishInitParams(0, nil) + s, err := pserver.NewService("", time.Second*5) + if err != nil { + t.Error(err) + } + err = s.FinishInitParams(0, nil) if err != nil { t.FailNow() } @@ -85,15 +91,18 @@ func TestMultipleInit(t *testing.T) { } func TestUninitialized(t *testing.T) { - s := pserver.NewService() - err := s.SendGrad(pserver.Gradient{}, nil) + s, err := pserver.NewService("", time.Second*5) + err = s.SendGrad(pserver.Gradient{}, nil) if err.Error() != pserver.Uninitialized { t.FailNow() } } func TestBlockUntilInitialized(t *testing.T) { - s := pserver.NewService() + s, err := pserver.NewService("", time.Second*5) + if err != nil { + t.Error(err) + } ch := make(chan struct{}, 2) errCh := make(chan error, 2) var wg sync.WaitGroup @@ -133,7 +142,7 @@ func TestBlockUntilInitialized(t *testing.T) { p.Name = "param_a" p.Content = []byte{1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0} p.ElementType = pserver.Int32 - err := s.InitParam(pserver.ParameterWithConfig{Param: p, Config: nil}, nil) + err = s.InitParam(pserver.ParameterWithConfig{Param: p, Config: nil}, nil) if err != nil { t.FailNow() } diff --git a/go/utils/helper.go b/go/utils/helper.go new file mode 100644 index 00000000000..3220fd6c78b --- /dev/null +++ b/go/utils/helper.go @@ -0,0 +1,45 @@ +package utils + +import ( + "errors" + "net" +) + +// GetExternalIP returns the ip address of local network interface, not the +// loopback device. +func GetExternalIP() (string, error) { + ifaces, err := net.Interfaces() + if err != nil { + return "", err + } + for _, iface := range ifaces { + if iface.Flags&net.FlagUp == 0 { + continue // interface down + } + if iface.Flags&net.FlagLoopback != 0 { + continue // loopback interface + } + addrs, err := iface.Addrs() + if err != nil { + return "", err + } + for _, addr := range addrs { + var ip net.IP + switch v := addr.(type) { + case *net.IPNet: + ip = v.IP + case *net.IPAddr: + ip = v.IP + } + if ip == nil || ip.IsLoopback() { + continue + } + ip = ip.To4() + if ip == nil { + continue // not an ipv4 address + } + return ip.String(), nil + } + } + return "", errors.New("are you connected to the network?") +} diff --git a/go/utils/helper_test.go b/go/utils/helper_test.go new file mode 100644 index 00000000000..aa7c509768e --- /dev/null +++ b/go/utils/helper_test.go @@ -0,0 +1,10 @@ +package utils + +import "testing" + +func TestGetIP(t *testing.T) { + _, err := GetExternalIP() + if err != nil { + t.Errorf("GetExternalIP returns error : %v\n", err) + } +} -- GitLab From b7a52bd9767de41d65382929b1629e95e35a3fe5 Mon Sep 17 00:00:00 2001 From: wuyi05 Date: Wed, 21 Jun 2017 15:25:02 +0800 Subject: [PATCH 0722/3256] add started info log --- go/cmd/pserver/pserver.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/go/cmd/pserver/pserver.go b/go/cmd/pserver/pserver.go index ddf5ad40fd2..f42c90c6c6d 100644 --- a/go/cmd/pserver/pserver.go +++ b/go/cmd/pserver/pserver.go @@ -43,7 +43,9 @@ func main() { panic(err) } + log.Infof("start pserver at port %d", *port) err = http.Serve(l, nil) + if err != nil { panic(err) } -- GitLab From aaf11fa6259dc0c4cc248a102141b68d94685ad7 Mon Sep 17 00:00:00 2001 From: zlx Date: Wed, 21 Jun 2017 15:44:07 +0800 Subject: [PATCH 0723/3256] modify the format --- paddle/parameter/ParameterUpdaterHook.cpp | 46 +++++++++++------------ 1 file changed, 22 insertions(+), 24 deletions(-) diff --git a/paddle/parameter/ParameterUpdaterHook.cpp b/paddle/parameter/ParameterUpdaterHook.cpp index 66e554a70d9..ba2cb37fa2c 100644 --- a/paddle/parameter/ParameterUpdaterHook.cpp +++ b/paddle/parameter/ParameterUpdaterHook.cpp @@ -38,29 +38,28 @@ namespace paddle { class StaticPruningHook : public IParameterUpdaterHook { public: - explicit StaticPruningHook(const ParameterUpdaterHookConfig& hookConfig) + explicit StaticPruningHook(const ParameterUpdaterHookConfig &hookConfig) : initCount_(0) { sparsityRatio_ = hookConfig.sparsity_ratio(); } - static bool sortPairAscend(const std::pair& pair1, - const std::pair& pair2) { + static bool sortPairAscend(const std::pair &pair1, + const std::pair &pair2) { return pair1.first > pair2.first; } - void update(Parameter* para) { + void update(Parameter *para) { updateThreadChecker_.check(); - auto& vec = para->getBuf(PARAMETER_GRADIENT); + auto &vec = para->getBuf(PARAMETER_GRADIENT); if (vec) { vec->dotMul(*maskVec_); } } - void generateMask(Parameter* para) { - + void generateMask(Parameter *para) { VectorPtr maskTemp = Vector::create(para->getSize(), false); maskTemp->zeroMem(); - real* maskTempData = maskTemp->getData(); + real *maskTempData = maskTemp->getData(); size_t nonZeroNum = para->getSize() * (1 - sparsityRatio_); VectorPtr paraVec = para->getBuf(PARAMETER_VALUE); @@ -72,9 +71,10 @@ public: for (size_t i = 0; i < para->getSize(); i++) param.push_back(std::make_pair(fabs(paraCpuCopy->getData()[i]), i)); - std::partial_sort( - param.begin(), param.begin() + nonZeroNum, param.end(), sortPairAscend); - for (size_t i = 0; i < nonZeroNum; i++) maskTempData[param[i].second] = 1.0; + std::partial_sort(param.begin(), param.begin() + nonZeroNum, param.end(), + sortPairAscend); + for (size_t i = 0; i < nonZeroNum; i++) + maskTempData[param[i].second] = 1.0; // Currently just use a mask vector for hack. if (para->useGpu()) { @@ -85,7 +85,7 @@ public: } } - void init(Parameter* para) { + void init(Parameter *para) { generateMask(para); size_t initCount = this->initCount_.fetch_add(1); CHECK_EQ(initCount, 0UL) << "Currently the StaticPruningHook must invoke " @@ -93,7 +93,7 @@ public: VLOG(3) << "Initialize Parameter " << para; SetDevice device(para->getDeviceId()); - auto& paraVec = para->getBuf(PARAMETER_VALUE); + auto ¶Vec = para->getBuf(PARAMETER_VALUE); paraVec->dotMul(*maskVec_); } @@ -118,7 +118,7 @@ IParameterUpdaterHook::~IParameterUpdaterHook() {} */ class StringIntPairHasher { public: - size_t operator()(const std::pair& k) const { + size_t operator()(const std::pair &k) const { return intHasher_(strHasher_(k.first) + k.second); } @@ -127,17 +127,15 @@ private: std::hash intHasher_; }; -static WeakKVCache, - IParameterUpdaterHook, - StringIntPairHasher> - g_hookCache_; +static WeakKVCache, IParameterUpdaterHook, + StringIntPairHasher> g_hookCache_; /** * ParameterUpdaterHook actually factory method. */ -static IParameterUpdaterHook* createImpl( - const ParameterUpdaterHookConfig& config) { - auto& type = config.type(); +static IParameterUpdaterHook * +createImpl(const ParameterUpdaterHookConfig &config) { + auto &type = config.type(); if (type == "pruning") { return new StaticPruningHook(config); } @@ -146,11 +144,11 @@ static IParameterUpdaterHook* createImpl( return nullptr; } -std::shared_ptr IParameterUpdaterHook::create( - const ParameterConfig& paramConfig, int idx) { +std::shared_ptr +IParameterUpdaterHook::create(const ParameterConfig ¶mConfig, int idx) { std::pair key = {paramConfig.name(), idx}; return g_hookCache_.get( key, [&] { return createImpl(paramConfig.update_hooks(idx)); }); } -} // namespace paddle +} // namespace paddle -- GitLab From 86a679b0c485cac9df354e2c37abaacc8ea9771d Mon Sep 17 00:00:00 2001 From: hedaoyuan Date: Wed, 21 Jun 2017 17:07:55 +0800 Subject: [PATCH 0724/3256] Add unit test of ImageExpandOp. --- paddle/function/CMakeLists.txt | 1 + paddle/function/ImageExpandOp.cpp | 1 + paddle/function/ImageExpandOpTest.cpp | 107 ++++++++++++++++++++++++++ 3 files changed, 109 insertions(+) create mode 100644 paddle/function/ImageExpandOpTest.cpp diff --git a/paddle/function/CMakeLists.txt b/paddle/function/CMakeLists.txt index 5e170714cf5..19f64eefd18 100644 --- a/paddle/function/CMakeLists.txt +++ b/paddle/function/CMakeLists.txt @@ -29,6 +29,7 @@ if(WITH_GPU) add_simple_unittest(MulOpTest) add_simple_unittest(CosSimOpTest) add_simple_unittest(RowConvOpTest) + add_simple_unittest(ImageExpandOpTest) endif() add_simple_unittest(ConvOpTest) diff --git a/paddle/function/ImageExpandOp.cpp b/paddle/function/ImageExpandOp.cpp index ca1d117db88..00a2571936b 100644 --- a/paddle/function/ImageExpandOp.cpp +++ b/paddle/function/ImageExpandOp.cpp @@ -116,6 +116,7 @@ public: CHECK_EQ(numInputs_, inputs.size()); CHECK_EQ(numOutputs_, outputs.size()); check(inputs, outputs); + CHECK_EQ(outputs[0].getArgType(), ASSIGN_TO); const TensorShape& image = inputs[0].shape(); const TensorShape& sequence = outputs[0].shape(); diff --git a/paddle/function/ImageExpandOpTest.cpp b/paddle/function/ImageExpandOpTest.cpp new file mode 100644 index 00000000000..fb312549dc7 --- /dev/null +++ b/paddle/function/ImageExpandOpTest.cpp @@ -0,0 +1,107 @@ +/* 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 "FunctionTest.h" + +namespace paddle { + +TEST(ImageExpandForward, real) { + for (size_t batchSize : {5, 32}) { + for (size_t channels : {1, 5, 32}) { + for (size_t inputHeight : {5, 33, 100}) { + for (size_t inputWidth : {5, 32, 96}) { + for (size_t block : {1, 3, 5}) { + for (size_t stride : {1, 2}) { + for (size_t padding : {0, 1}) { + // init Test object + std::vector strides = {stride, stride}; + std::vector paddings = {padding, padding}; + std::vector blocks = {block, block}; + CpuGpuFuncCompare test("ImageExpand", + FuncConfig() + .set("strides", strides) + .set("paddings", paddings) + .set("blocks", blocks)); + + size_t outputHeight = + 1 + + (inputHeight + 2 * padding - block + stride - 1) / stride; + size_t outputWidth = + 1 + + (inputWidth + 2 * padding - block + stride - 1) / stride; + TensorShape inputShape = + TensorShape({batchSize, channels, inputHeight, inputWidth}); + TensorShape outputShape = + TensorShape({batchSize, + outputHeight * outputWidth, + channels * block * block}); + test.addInputs(BufferArg(VALUE_TYPE_FLOAT, inputShape)); + test.addOutputs(BufferArg(VALUE_TYPE_FLOAT, outputShape)); + // run Function + test.run(); + } + } + } + } + } + } + } +} + +TEST(ImageExpandBackward, real) { + for (size_t batchSize : {5, 32}) { + for (size_t channels : {1, 5, 32}) { + for (size_t inputHeight : {5, 33, 100}) { + for (size_t inputWidth : {5, 32, 96}) { + for (size_t block : {1, 3, 5}) { + for (size_t stride : {1, 2}) { + for (size_t padding : {0, 1}) { + // init Test object + std::vector strides = {stride, stride}; + std::vector paddings = {padding, padding}; + std::vector blocks = {block, block}; + CpuGpuFuncCompare test("ImageExpandGrad", + FuncConfig() + .set("strides", strides) + .set("paddings", paddings) + .set("blocks", blocks)); + + size_t outputHeight = + 1 + + (inputHeight + 2 * padding - block + stride - 1) / stride; + size_t outputWidth = + 1 + + (inputWidth + 2 * padding - block + stride - 1) / stride; + TensorShape inputShape = + TensorShape({batchSize, channels, inputHeight, inputWidth}); + TensorShape outputShape = + TensorShape({batchSize, + outputHeight * outputWidth, + channels * block * block}); + test.addInputs(BufferArg(VALUE_TYPE_FLOAT, outputShape)); + test.addOutputs(BufferArg(VALUE_TYPE_FLOAT, inputShape), + ADD_TO); + // run Function + test.run(); + } + } + } + } + } + } + } +} + +} // namespace paddle -- GitLab From bcac91a463bd90867974ed6d39b1eda5accdac25 Mon Sep 17 00:00:00 2001 From: qiaolongfei Date: Wed, 21 Jun 2017 18:21:58 +0800 Subject: [PATCH 0725/3256] scope design doc --- doc/refactor/scope.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 doc/refactor/scope.md diff --git a/doc/refactor/scope.md b/doc/refactor/scope.md new file mode 100644 index 00000000000..15759d62176 --- /dev/null +++ b/doc/refactor/scope.md @@ -0,0 +1,30 @@ +# Scope + +### Define + +Scope is a context to manage Variables. It mainly contains a map from Variable name to Variable. Net will get and update variable throw scope. + +```cpp +class Scope { + Variable GetVar(); + +private: + // var_name -> var + std::map var_map_; + Scope* parent_scope_; +} +``` + +You need to specify a scope to run a Net. One net can run in different scopes and update different variable in the scope. If you did not specify one, It will run in a default scope. +```python +with ScopeGuard(scope): + Net net = Net(); + Net.run() +``` + +### Chain structure + +Scope has a pointer point to it's parent scope, this is mainly used in RNN when it need to create many stepNet. + + +### Scope Guard \ No newline at end of file -- GitLab From a266292a57613d16806cccd939d68f436731927c Mon Sep 17 00:00:00 2001 From: zlx Date: Wed, 21 Jun 2017 18:37:37 +0800 Subject: [PATCH 0726/3256] modify format --- paddle/parameter/ParameterUpdaterHook.cpp | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/paddle/parameter/ParameterUpdaterHook.cpp b/paddle/parameter/ParameterUpdaterHook.cpp index ba2cb37fa2c..968803fc0f0 100644 --- a/paddle/parameter/ParameterUpdaterHook.cpp +++ b/paddle/parameter/ParameterUpdaterHook.cpp @@ -71,10 +71,9 @@ public: for (size_t i = 0; i < para->getSize(); i++) param.push_back(std::make_pair(fabs(paraCpuCopy->getData()[i]), i)); - std::partial_sort(param.begin(), param.begin() + nonZeroNum, param.end(), - sortPairAscend); - for (size_t i = 0; i < nonZeroNum; i++) - maskTempData[param[i].second] = 1.0; + std::partial_sort( + param.begin(), param.begin() + nonZeroNum, param.end(), sortPairAscend); + for (size_t i = 0; i < nonZeroNum; i++) maskTempData[param[i].second] = 1.0; // Currently just use a mask vector for hack. if (para->useGpu()) { @@ -127,14 +126,16 @@ private: std::hash intHasher_; }; -static WeakKVCache, IParameterUpdaterHook, - StringIntPairHasher> g_hookCache_; +static WeakKVCache, + IParameterUpdaterHook, + StringIntPairHasher> + g_hookCache_; /** * ParameterUpdaterHook actually factory method. */ -static IParameterUpdaterHook * -createImpl(const ParameterUpdaterHookConfig &config) { +static IParameterUpdaterHook *createImpl( + const ParameterUpdaterHookConfig &config) { auto &type = config.type(); if (type == "pruning") { return new StaticPruningHook(config); @@ -144,11 +145,11 @@ createImpl(const ParameterUpdaterHookConfig &config) { return nullptr; } -std::shared_ptr -IParameterUpdaterHook::create(const ParameterConfig ¶mConfig, int idx) { +std::shared_ptr IParameterUpdaterHook::create( + const ParameterConfig ¶mConfig, int idx) { std::pair key = {paramConfig.name(), idx}; return g_hookCache_.get( key, [&] { return createImpl(paramConfig.update_hooks(idx)); }); } -} // namespace paddle +} // namespace paddle -- GitLab From dbfe58ca47e8ff59d8d020739529ed6f02f5b9f0 Mon Sep 17 00:00:00 2001 From: gongweibao Date: Wed, 21 Jun 2017 19:27:12 +0800 Subject: [PATCH 0727/3256] add local recordio reader interface --- python/paddle/v2/reader/creator.py | 21 +++++++++++++++++- python/paddle/v2/reader/tests/creator_test.py | 9 ++++++++ .../v2/reader/tests/test_recordio_creator.dat | Bin 0 -> 88 bytes 3 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 python/paddle/v2/reader/tests/test_recordio_creator.dat diff --git a/python/paddle/v2/reader/creator.py b/python/paddle/v2/reader/creator.py index 07142056f87..994062096fb 100644 --- a/python/paddle/v2/reader/creator.py +++ b/python/paddle/v2/reader/creator.py @@ -16,7 +16,7 @@ Creator package contains some simple reader creator, which could be used in user program. """ -__all__ = ['np_array', 'text_file'] +__all__ = ['np_array', 'text_file', "RecordIO"] def np_array(x): @@ -55,3 +55,22 @@ def text_file(path): f.close() return reader + + +def RecordIO(path): + """ + Creates a data reader that outputs record one one by one from given recordio file + :path: path of recordio file + :returns: data reader of recordio file + """ + + def reader(): + f = recordio.reader(path) + while True: + r = f.read() + if r is None: + break + yield r + f.close() + + return reader diff --git a/python/paddle/v2/reader/tests/creator_test.py b/python/paddle/v2/reader/tests/creator_test.py index 9f8d7133b86..dd84fbb0025 100644 --- a/python/paddle/v2/reader/tests/creator_test.py +++ b/python/paddle/v2/reader/tests/creator_test.py @@ -36,5 +36,14 @@ class TestTextFile(unittest.TestCase): self.assertEqual(e, str(idx * 2) + " " + str(idx * 2 + 1)) +class TestRecordIO(unittest.TestCase): + def test_RecordIO(self): + path = os.path.join( + os.path.dirname(__file__), "test_recordio_creator.dat") + reader = paddle.v2.reader.creator.RecordIO(path) + for idx, r in enumerate(reader()): + self.assertSequenceEqual(r, str(idx)) + + if __name__ == '__main__': unittest.main() diff --git a/python/paddle/v2/reader/tests/test_recordio_creator.dat b/python/paddle/v2/reader/tests/test_recordio_creator.dat new file mode 100644 index 0000000000000000000000000000000000000000..17aa89b6796184407e83246d3f342a55a66b4a69 GIT binary patch literal 88 zcmZQ!W@2QOHw Date: Wed, 21 Jun 2017 21:12:09 +0800 Subject: [PATCH 0728/3256] Rearrange docs --- doc/{refactor => design}/scope.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename doc/{refactor => design}/scope.md (97%) diff --git a/doc/refactor/scope.md b/doc/design/scope.md similarity index 97% rename from doc/refactor/scope.md rename to doc/design/scope.md index 15759d62176..c8ca62688c0 100644 --- a/doc/refactor/scope.md +++ b/doc/design/scope.md @@ -27,4 +27,4 @@ with ScopeGuard(scope): Scope has a pointer point to it's parent scope, this is mainly used in RNN when it need to create many stepNet. -### Scope Guard \ No newline at end of file +### Scope Guard -- GitLab From 674b1d34625bb876299c4500c5a85b0aad8ef808 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Wed, 21 Jun 2017 21:31:27 +0800 Subject: [PATCH 0729/3256] Update code --- doc/design/scope.md | 48 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 38 insertions(+), 10 deletions(-) diff --git a/doc/design/scope.md b/doc/design/scope.md index c8ca62688c0..b8390a37140 100644 --- a/doc/design/scope.md +++ b/doc/design/scope.md @@ -5,21 +5,49 @@ Scope is a context to manage Variables. It mainly contains a map from Variable name to Variable. Net will get and update variable throw scope. ```cpp -class Scope { - Variable GetVar(); +class Variable; +using VariablePtr = std::shared_ptr; + +class Scope final { +public: + Scope(); + Scope(const std::shared_ptr& parent); + + //! Get Variable in this scope. + //! @return nullptr if no such variable. + const VariablePtr& getVar(const std::string& name) const; + + //! Create or get a variable in this scope. + VariablePtr& createOrGetVar(const std::string& name); private: - // var_name -> var - std::map var_map_; - Scope* parent_scope_; -} + /// variable name -> variable + std::unordered_map vars_; + std::shared_ptr parent_{nullptr}; +}; ``` You need to specify a scope to run a Net. One net can run in different scopes and update different variable in the scope. If you did not specify one, It will run in a default scope. -```python -with ScopeGuard(scope): - Net net = Net(); - Net.run() + +```cpp +Scope global; +auto x = newVar("X"); // x is created in scope global, implicitly. +auto y = newVar("Y"); +Net net1; +net1.addOp("add", {x, y}, {x}); // x = x + y; +net1.run(); + +for (size_t i=0; i<10; ++i) { + Scope local; + auto tmp = newVar("tmp"); // tmp is created in scope local. + Net net2; + net2.addOp("add", {x, y}, {tmp}); + net2.run(); // tmp = x + y; +} + +Net net3; +net3.addOp("add", {x, y}, {"tmp"}); // error! cannot found "tmp" in global scope. + ``` ### Chain structure -- GitLab From 09cc4408e5d5424fba49fcacbd813a846413f9cf Mon Sep 17 00:00:00 2001 From: wanghaoshuang Date: Tue, 20 Jun 2017 16:03:49 +0800 Subject: [PATCH 0730/3256] modified xmap reader to process sample by order --- python/paddle/v2/reader/decorator.py | 36 ++++++++++++++++--- .../paddle/v2/reader/tests/decorator_test.py | 18 ++++++++++ 2 files changed, 50 insertions(+), 4 deletions(-) diff --git a/python/paddle/v2/reader/decorator.py b/python/paddle/v2/reader/decorator.py index c76faa596c9..68ffbd6f3d4 100644 --- a/python/paddle/v2/reader/decorator.py +++ b/python/paddle/v2/reader/decorator.py @@ -230,7 +230,7 @@ class XmapEndSignal(): pass -def xmap_readers(mapper, reader, process_num, buffer_size): +def xmap_readers(mapper, reader, process_num, buffer_size, order=False): """ Use multiprocess to map samples from reader by a mapper defined by user. And this function contains a buffered decorator. @@ -242,21 +242,32 @@ def xmap_readers(mapper, reader, process_num, buffer_size): :type process_num: int :param buffer_size: max buffer size :type buffer_size: int + :param order: keep the order of reader + :type order: bool :return: the decarated reader :rtype: callable """ end = XmapEndSignal() in_queue = Queue(buffer_size) out_queue = Queue(buffer_size) - + out_order = [0] # define a worker to read samples from reader to in_queue def read_worker(reader, in_queue): for i in reader(): in_queue.put(i) in_queue.put(end) + + # define a worker to read samples from reader to in_queue with order flag + def order_read_worker(reader, in_queue): + in_order = 0 + for i in reader(): + in_queue.put((in_order,i)) + in_order+=1 + in_queue.put(end) # start a read worker in a thread - t = Thread(target=read_worker, args=(reader, in_queue)) + target = order_read_worker if order else read_worker + t = Thread(target=target, args=(reader, in_queue)) t.daemon = True t.start() @@ -270,12 +281,29 @@ def xmap_readers(mapper, reader, process_num, buffer_size): sample = in_queue.get() in_queue.put(end) out_queue.put(end) + + # define a worker to handle samples from in_queue by mapper + # and put mapped samples into out_queue by order + def order_handle_worker(in_queue, out_queue, mapper, out_order): + ins = in_queue.get() + while not isinstance(ins, XmapEndSignal): + order, sample = ins + r = mapper(sample) + while order != out_order[0]: + pass + out_queue.put(r) + out_order[0] += 1 + ins = in_queue.get() + in_queue.put(end) + out_queue.put(end) # start several handle_workers + target = order_handle_worker if order else handle_worker + args = (in_queue, out_queue, mapper, out_order) if order else (in_queue, out_queue, mapper) workers = [] for i in xrange(process_num): worker = Thread( - target=handle_worker, args=(in_queue, out_queue, mapper)) + target=target, args=args) worker.daemon = True workers.append(worker) for w in workers: diff --git a/python/paddle/v2/reader/tests/decorator_test.py b/python/paddle/v2/reader/tests/decorator_test.py index 734154b9790..76db91a44b8 100644 --- a/python/paddle/v2/reader/tests/decorator_test.py +++ b/python/paddle/v2/reader/tests/decorator_test.py @@ -120,6 +120,24 @@ class TestShuffle(unittest.TestCase): total += 1 self.assertEqual(total, 10) +class TestXmap(unittest.TestCase): + def test_xmap(self): + def mapper(x): + return (x + 1) + orders = (True, False) + thread_nums = (1, 2, 4, 8, 16) + buffered_size = (1, 2, 4, 8, 16) + for order in orders: + for tNum in thread_nums: + for size in buffered_size: + result = [] + for i in paddle.v2.reader.xmap_readers(mapper, reader_creator_10(), tNum, size, order)(): + result.append(i) + if not order: + result.sort() + for idx, e in enumerate(result): + self.assertEqual(e, mapper(idx)) + if __name__ == '__main__': unittest.main() -- GitLab From cadea35a107167edee23b8e3ca0a92ca3d85e859 Mon Sep 17 00:00:00 2001 From: wanghaoshuang Date: Tue, 20 Jun 2017 16:11:14 +0800 Subject: [PATCH 0731/3256] format code --- python/paddle/v2/reader/decorator.py | 17 +++++++++-------- python/paddle/v2/reader/tests/decorator_test.py | 8 ++++++-- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/python/paddle/v2/reader/decorator.py b/python/paddle/v2/reader/decorator.py index 68ffbd6f3d4..e432003129d 100644 --- a/python/paddle/v2/reader/decorator.py +++ b/python/paddle/v2/reader/decorator.py @@ -251,18 +251,19 @@ def xmap_readers(mapper, reader, process_num, buffer_size, order=False): in_queue = Queue(buffer_size) out_queue = Queue(buffer_size) out_order = [0] + # define a worker to read samples from reader to in_queue def read_worker(reader, in_queue): for i in reader(): in_queue.put(i) in_queue.put(end) - + # define a worker to read samples from reader to in_queue with order flag def order_read_worker(reader, in_queue): in_order = 0 for i in reader(): - in_queue.put((in_order,i)) - in_order+=1 + in_queue.put((in_order, i)) + in_order += 1 in_queue.put(end) # start a read worker in a thread @@ -281,7 +282,7 @@ def xmap_readers(mapper, reader, process_num, buffer_size, order=False): sample = in_queue.get() in_queue.put(end) out_queue.put(end) - + # define a worker to handle samples from in_queue by mapper # and put mapped samples into out_queue by order def order_handle_worker(in_queue, out_queue, mapper, out_order): @@ -292,18 +293,18 @@ def xmap_readers(mapper, reader, process_num, buffer_size, order=False): while order != out_order[0]: pass out_queue.put(r) - out_order[0] += 1 + out_order[0] += 1 ins = in_queue.get() in_queue.put(end) out_queue.put(end) # start several handle_workers target = order_handle_worker if order else handle_worker - args = (in_queue, out_queue, mapper, out_order) if order else (in_queue, out_queue, mapper) + args = (in_queue, out_queue, mapper, out_order) if order else ( + in_queue, out_queue, mapper) workers = [] for i in xrange(process_num): - worker = Thread( - target=target, args=args) + worker = Thread(target=target, args=args) worker.daemon = True workers.append(worker) for w in workers: diff --git a/python/paddle/v2/reader/tests/decorator_test.py b/python/paddle/v2/reader/tests/decorator_test.py index 76db91a44b8..0bd77339550 100644 --- a/python/paddle/v2/reader/tests/decorator_test.py +++ b/python/paddle/v2/reader/tests/decorator_test.py @@ -120,10 +120,12 @@ class TestShuffle(unittest.TestCase): total += 1 self.assertEqual(total, 10) + class TestXmap(unittest.TestCase): def test_xmap(self): def mapper(x): return (x + 1) + orders = (True, False) thread_nums = (1, 2, 4, 8, 16) buffered_size = (1, 2, 4, 8, 16) @@ -131,13 +133,15 @@ class TestXmap(unittest.TestCase): for tNum in thread_nums: for size in buffered_size: result = [] - for i in paddle.v2.reader.xmap_readers(mapper, reader_creator_10(), tNum, size, order)(): + for i in paddle.v2.reader.xmap_readers(mapper, + reader_creator_10(), + tNum, size, order)(): result.append(i) if not order: result.sort() for idx, e in enumerate(result): self.assertEqual(e, mapper(idx)) - + if __name__ == '__main__': unittest.main() -- GitLab From d322c94243ef2039c633c3e455a6d3660193804c Mon Sep 17 00:00:00 2001 From: wanghaoshuang Date: Wed, 21 Jun 2017 21:41:53 +0800 Subject: [PATCH 0732/3256] fix unittest --- python/paddle/v2/reader/tests/decorator_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/paddle/v2/reader/tests/decorator_test.py b/python/paddle/v2/reader/tests/decorator_test.py index 0bd77339550..bb3c5d220b9 100644 --- a/python/paddle/v2/reader/tests/decorator_test.py +++ b/python/paddle/v2/reader/tests/decorator_test.py @@ -134,7 +134,7 @@ class TestXmap(unittest.TestCase): for size in buffered_size: result = [] for i in paddle.v2.reader.xmap_readers(mapper, - reader_creator_10(), + reader_creator_10(0), tNum, size, order)(): result.append(i) if not order: -- GitLab From 7a4850771006937d264039ae782fe7e302545362 Mon Sep 17 00:00:00 2001 From: qiaolongfei Date: Wed, 21 Jun 2017 23:45:26 +0800 Subject: [PATCH 0733/3256] fix code style --- doc/design/scope.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/doc/design/scope.md b/doc/design/scope.md index b8390a37140..a0b0be50dc7 100644 --- a/doc/design/scope.md +++ b/doc/design/scope.md @@ -9,16 +9,16 @@ class Variable; using VariablePtr = std::shared_ptr; class Scope final { -public: + public: Scope(); Scope(const std::shared_ptr& parent); //! Get Variable in this scope. //! @return nullptr if no such variable. - const VariablePtr& getVar(const std::string& name) const; + const VariablePtr& GetVar(const std::string& name) const; //! Create or get a variable in this scope. - VariablePtr& createOrGetVar(const std::string& name); + VariablePtr& GetOrCreateVar(const std::string& name); private: /// variable name -> variable @@ -31,22 +31,22 @@ You need to specify a scope to run a Net. One net can run in different scopes an ```cpp Scope global; -auto x = newVar("X"); // x is created in scope global, implicitly. -auto y = newVar("Y"); +auto x = NewVar("X"); // x is created in scope global, implicitly. +auto y = NewVar("Y"); Net net1; -net1.addOp("add", {x, y}, {x}); // x = x + y; -net1.run(); +net1.AddOp("add", {x, y}, {x}); // x = x + y; +net1.Run(); for (size_t i=0; i<10; ++i) { Scope local; - auto tmp = newVar("tmp"); // tmp is created in scope local. + auto tmp = NewVar("tmp"); // tmp is created in scope local. Net net2; - net2.addOp("add", {x, y}, {tmp}); - net2.run(); // tmp = x + y; + net2.AddOp("add", {x, y}, {tmp}); + net2.Run(); // tmp = x + y; } Net net3; -net3.addOp("add", {x, y}, {"tmp"}); // error! cannot found "tmp" in global scope. +net3.AddOp("add", {x, y}, {"tmp"}); // error! cannot found "tmp" in global scope. ``` -- GitLab From 5128714c004f3b3c54d4c389131599c08e5413fd Mon Sep 17 00:00:00 2001 From: dzhwinter Date: Thu, 22 Jun 2017 03:59:51 +0800 Subject: [PATCH 0734/3256] "integrate go and optimizer library" --- go/pserver/cclient/cclient.go | 12 +++----- go/pserver/optimizer.c | 58 ----------------------------------- go/pserver/optimizer.go | 20 +++++------- go/pserver/optimizer.h | 22 ------------- go/pserver/service.go | 18 ++++++++--- 5 files changed, 26 insertions(+), 104 deletions(-) delete mode 100644 go/pserver/optimizer.c delete mode 100644 go/pserver/optimizer.h diff --git a/go/pserver/cclient/cclient.go b/go/pserver/cclient/cclient.go index 92a41b7f543..6aaaff7409d 100644 --- a/go/pserver/cclient/cclient.go +++ b/go/pserver/cclient/cclient.go @@ -123,9 +123,8 @@ func paddle_begin_init_params(client C.paddle_pserver_client) C.int { func paddle_init_param(client C.paddle_pserver_client, param C.paddle_parameter, param_config unsafe.Pointer, config_len C.int) C.int { et := pserver.ElementType(param.element_type) name := C.GoString(param.name) - content := cArrayToSlice(unsafe.Pointer(param.content), int(param.content_len)) pc := pserver.ParameterWithConfig{ - Param: pserver.Parameter{Name: name, ElementType: et, Content: content}, + Param: pserver.Parameter{Name: name, ElementType: et, Content: param.content, Length: para.content_len}, Config: cArrayToSlice(param_config, int(config_len)), } c := get(client) @@ -167,8 +166,7 @@ func paddle_send_grads(client C.paddle_pserver_client, grads **C.paddle_gradient grad := *(**C.paddle_gradient)(unsafe.Pointer((uintptr(unsafe.Pointer(grads)) + uintptr(i)*unsafe.Sizeof(*grads)))) et := pserver.ElementType(grad.element_type) name := C.GoString(grad.name) - content := cArrayToSlice(unsafe.Pointer(grad.content), int(grad.content_len)) - gs = append(gs, pserver.Gradient{Name: name, ElementType: et, Content: content}) + gs = append(gs, pserver.Gradient{Name: name, ElementType: et, Content: grad.content, Length: grad.content_len}) } c := get(client) @@ -225,14 +223,14 @@ func paddle_get_params(client C.paddle_pserver_client, dst **C.paddle_parameter, } if unsafe.Pointer(param.content) != nullPtr { - if int(param.content_len) != len(p.Content) { + if int(param.content_len) != p.Length { log.Errorf("the pre-allocated content len does not match parameter content len. Pre-allocated len: %d, returned len: %d", param.content_len, len(p.Content)) return C.PSERVER_ERROR } } - C.memcpy(unsafe.Pointer(param.content), unsafe.Pointer(&p.Content[0]), C.size_t(len(p.Content))) - param.content_len = C.int(len(p.Content)) + C.memcpy(unsafe.Pointer(param.content), unsafe.Pointer(p.Content), C.size_t(p.Length)) + param.content_len = C.int(p.Length) param.element_type = C.paddle_element_type(p.ElementType) } diff --git a/go/pserver/optimizer.c b/go/pserver/optimizer.c deleted file mode 100644 index f16ba2cbf8e..00000000000 --- a/go/pserver/optimizer.c +++ /dev/null @@ -1,58 +0,0 @@ -#include - -#include "optimizer.h" - -typedef int (*update_func)(void*, void*, paddle_element_type, const void*, int); -typedef void (*release_func)(void*); - -typedef struct paddle_optimizer { - update_func update; - release_func release; - void* optimizer; -} paddle_optimizer; - -void paddle_release_optimizer(paddle_optimizer* o) { - o->release(o->optimizer); - free(o); -} - -int paddle_update_parameter(paddle_optimizer* o, - void* buffer, - paddle_element_type element_type, - const void* gradient, - int num_bytes) { - return o->update(o->optimizer, buffer, element_type, gradient, num_bytes); -} - -typedef struct { double learning_rate; } SGD_optimizer; - -int update_SGD(void* optimizer, - void* buffer, - paddle_element_type element_type, - const void* gradient, - int num_bytes) { - SGD_optimizer* o = (SGD_optimizer*)optimizer; - float* parameter = (float*)buffer; - float* grad = (float*)gradient; - - int i; - for (i = 0; i < num_bytes / sizeof(float); ++i) { - parameter[i] -= o->learning_rate * grad[i]; - } - return 0; -} - -void release_SGD(void* optimizer) { - SGD_optimizer* o = (SGD_optimizer*)optimizer; - // nothing allocated on heap -} - -paddle_optimizer* paddle_create_SGD_optimizer(double learning_rate) { - SGD_optimizer* impl = (SGD_optimizer*)malloc(sizeof(SGD_optimizer)); - impl->learning_rate = learning_rate; - paddle_optimizer* opt = (paddle_optimizer*)malloc(sizeof(paddle_optimizer)); - opt->update = update_SGD; - opt->release = release_SGD; - opt->optimizer = impl; - return opt; -} diff --git a/go/pserver/optimizer.go b/go/pserver/optimizer.go index 417f8c50938..5abbca538f2 100644 --- a/go/pserver/optimizer.go +++ b/go/pserver/optimizer.go @@ -1,7 +1,7 @@ package pserver /* -#include "optimizer.h" +#include "paddle/optimizer/optimizer.h" */ import "C" import ( @@ -9,34 +9,30 @@ import ( "unsafe" ) -type optimizerType int - -const ( - sgd optimizerType = iota -) - var nullPtr = unsafe.Pointer(uintptr(0)) type optimizer struct { opt *C.struct_paddle_optimizer } -func newOptimizer(t optimizerType, learning_rate float64) *optimizer { +func newOptimizer(paramWithConfigs ParameterWithConfig) *optimizer { o := &optimizer{} - o.opt = C.paddle_create_SGD_optimizer(C.double(learning_rate)) + p := paramWithConfigs.Param + c := paramWithConfigs.Config + o.opt = C.paddle_create_optimizer(C.uchar(c), C.int(len(c)), unsafe.Pointer(p.Content), c.int(p.Length), nullPtr, 0) return o } func (o *optimizer) UpdateParameter(p Parameter, g Gradient) error { - if len(p.Content) != len(g.Content) { - return fmt.Errorf("Name: %s, parameter and gradient length not match, parameter: %d, gradient: %d", p.Name, len(p.Content), len(g.Content)) + if p.Length != g.Length { + return fmt.Errorf("Name: %s, parameter and gradient length not match, parameter: %d, gradient: %d", p.Name, p.Length, g.Length) } if p.ElementType != g.ElementType { return fmt.Errorf("Name: %s, parameter and gradient element type not match, parameter: %v, gradient: %v", p.Name, p.ElementType, g.ElementType) } - r := C.paddle_update_parameter(o.opt, unsafe.Pointer(&p.Content[0]), C.paddle_element_type(p.ElementType), unsafe.Pointer(&g.Content[0]), C.int(len(g.Content))) + r := C.paddle_update_parameter(o.opt, C.paddle_element_type(p.ElementType), unsafe.Pointer(g.Content), C.int(g.Length)) if r != 0 { return fmt.Errorf("optimizer update returned error code: %d", r) } diff --git a/go/pserver/optimizer.h b/go/pserver/optimizer.h deleted file mode 100644 index a7e3ff05300..00000000000 --- a/go/pserver/optimizer.h +++ /dev/null @@ -1,22 +0,0 @@ -#ifndef PADDLE_PSERVER_OPTIMIZER_H -#define PADDLE_PSERVER_OPTIMIZER_H - -typedef enum { - PADDLE_ELEMENT_TYPE_INT32 = 0, - PADDLE_ELEMENT_TYPE_UINT32 = 1, - PADDLE_ELEMENT_TYPE_INT64 = 2, - PADDLE_ELEMENT_TYPE_UINT64 = 3, - PADDLE_ELEMENT_TYPE_FLOAT32 = 4, - PADDLE_ELEMENT_TYPE_FLOAT64 = 5, -} paddle_element_type; - -struct paddle_optimizer; -struct paddle_optimizer* paddle_create_SGD_optimizer(double learning_rate); -void paddle_release_optimizer(struct paddle_optimizer* o); -int paddle_update_parameter(struct paddle_optimizer* o, - void* buffer, - paddle_element_type element_type, - const void* gradient, - int num_bytes); - -#endif /* PADDLE_PSERVER_OPTIMIZER_H */ diff --git a/go/pserver/service.go b/go/pserver/service.go index 78a2bfaf634..c721388b6a7 100644 --- a/go/pserver/service.go +++ b/go/pserver/service.go @@ -28,7 +28,8 @@ const ( type Parameter struct { Name string ElementType ElementType - Content []byte + Content *byte + Length int } // ParameterWithConfig contains the parameter and the configuration. @@ -44,14 +45,16 @@ type Gradient Parameter type Service struct { initialized chan struct{} - mu sync.Mutex - opt *optimizer + mu sync.Mutex + // injection from parameter to optimizer + optMap map[string]*optimizer paramMap map[string]Parameter } // NewService creates a new service. func NewService() *Service { - s := &Service{opt: newOptimizer(sgd, 0.005)} + s := &Service{} + s.optMap = make(map[string]*optimizer) s.paramMap = make(map[string]Parameter) s.initialized = make(chan struct{}) return s @@ -74,6 +77,7 @@ func (s *Service) InitParam(paramWithConfigs ParameterWithConfig, dummy *int) er // properly memory aligned, if not, make copy to a memory // aligned region. s.paramMap[paramWithConfigs.Param.Name] = paramWithConfigs.Param + s.optMap[paramWithConfigs.Param.Name] = newOptimizer(paramWithConfigs) return nil } @@ -106,8 +110,12 @@ func (s *Service) SendGrad(g Gradient, dummy *int) error { if !ok { return fmt.Errorf("parameter: %s does not exist", g.Name) } + o, ok := s.optMap[g.Name] + if !ok { + return fmt.Errorf("optimizer: %s does not exist", g.Name) + } - return s.opt.UpdateParameter(p, g) + return o.UpdateParameter(p, g) } // GetParam gets parameters from the parameter server. -- GitLab From 7cb68a8d9315bd3c3c769e47ee3752867854ee12 Mon Sep 17 00:00:00 2001 From: Yi Wang Date: Wed, 21 Jun 2017 13:19:40 -0700 Subject: [PATCH 0735/3256] Add paddle/memory/README.md --- paddle/README.md | 141 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100644 paddle/README.md diff --git a/paddle/README.md b/paddle/README.md new file mode 100644 index 00000000000..24af37987e0 --- /dev/null +++ b/paddle/README.md @@ -0,0 +1,141 @@ +In my mind, the memory package works like the following: + +## Design + +### Usage + +To allocate 4KB CPU memory: + +```cpp +p = memory::Alloc(platform::CPUPlace(), 4*1024); +``` + +To allocate 4KB memory on the 3rd GPU: + +```cpp +p = memory::Alloc(platform::GPUPlace(2), 4*1024); +``` + +To free memory and check the so-far used amount of memory on a place: + +```cpp +auto pl = platform::GPUPlace(0); +p = memory::Alloc(pl, 4*1024); +cout << memory::Used(pl); +memory::Free(pl, p); +``` + +### The API + +In `paddle/memory/memory.h` we have: + +```cpp +template void* Alloc(Place, size_t); +template void Free(Place, void*); +} +``` + +These function templates have specializations on either `platform::CPUPlace` or `platform::GPUPlace`: + +```cpp +template<> +void Alloc(CPUPlace p, size_t size) { + return GetCPUBuddyAllocator()->Alloc(size); +} +``` + +and + +```cpp +template<> +void Alloc(GPUPlace)(GPUPlace p, size_t size) { + return GetGPUBuddyAllocator(p.id)->Alloc(size); +} +``` + +### The Implementation + +`GetCPUBuddyAllocator` and `GetGPUBuddyAllocator` are singletions. + +```cpp +BuddyAllocator* GetCPUBuddyAllocator() { + static BuddyAllocator* a = NULL; + if (a == NULL) { + a = new BuddyAllocator(new CPUAllocator /*backup allocator*/, ...); + } + return a; +} + +BuddyAllocator* GetGPUBuddyAllocator(int gpu_id) { + static BuddyAllocator* as = NULL; + if (as == NULL) { + as = new BuddyAllocator*[platform::NumGPUs()]; + for (int gpu = 0; gpu < platform::NumGPUs(); gpu++) { + as[gpu] = new BuddyAllocator(new GPUAllocator(gpu) /* backup allocator */, ...); + } + } + return as[gpu_id); +``` + +#### `BuddyAllocator` + +`BuddyAllocator` implements the buddy allocation algorithm. Its constructor takes parameters only related with the algorithm: + +```cpp +BuddyAllocator::BuddyAllocator(initial_pool_size, max_pool_size) { + ... +} +``` + +Please be aware that **`BuddyAllocator` always allocate aligned memory**, aligned on 32-bytes, which can hold a `BuddyAllocator::Block` object: + +```cpp +class BuddyAllocator { + private: + struct Block { + size_t size; + Blobk* left, right; + }; + ... +}; +``` + +#### System Allocators + +The `GPUAllocator` and `CPUAllocator` are calls *system allocators*. They hold information about the device, including the amount of memory has been allocated. So that we can call + +- `GPUAllocator::Used` and +- `CPUAllocator::Used` + +to get the amount of memory that has been allocated so far. + + +## Why Such a Design + +I got inspiration from Majel and Caffe2, though above design look different from both. + +### Caffe2 + +In Caffe2, `Tensor::mutable_data()` allocates the memroy. In particular, [`Tensor::mutable_data`](https://github.com/caffe2/caffe2/blob/v0.7.0/caffe2/core/tensor.h#L523) calls [`Tensor::raw_mutable_data`](https://github.com/caffe2/caffe2/blob/v0.7.0/caffe2/core/tensor.h#L459), which in turn calls [`Context::New`](https://github.com/caffe2/caffe2/blob/v0.7.0/caffe2/core/tensor.h#L479). + +There are two implementations of `Context`: + +1. [`CPUContext`](https://github.com/caffe2/caffe2/blob/v0.7.0/caffe2/core/context.h#L105), whose [`New` method](https://github.com/caffe2/caffe2/blob/v0.7.0/caffe2/core/context.h#L131) calls [`g_cpu_allocator.get()->New(size_t)`](https://github.com/caffe2/caffe2/blob/v0.7.0/caffe2/core/context.cc#L15) to allocate the memory. + +1. [`CUDAContext`](https://github.com/caffe2/caffe2/blob/v0.7.0/caffe2/core/context_gpu.h#L99), which has a data member [`int gpu_id_`](https://github.com/caffe2/caffe2/blob/v0.7.0/caffe2/core/context_gpu.h#L202). This looks very similar to class `majel::GPUPlace`, who also has an `int id_` data member. `CUDAContext::New(size_t)` calls [`g_cub_allocator->DeviceAllocate(&ptr, nbytes)`](https://github.com/caffe2/caffe2/blob/v0.7.0/caffe2/core/context_gpu.cu#L355) to allocate the memory. + +### Majel + +In Majel, there are basically two allocator types: + +1. `cpu::SystemAllocator`, which has similar functionality to `caffe2::CPUContext::New/Delete`. +1. `gpu::SystemAllocator`, which has similar functionality to `caffe2::CUDAContext::New/Delete`. + +However, memory allocation is not via these two allocators. Instead, these two allocators are defined in hidden namespaces. + +In Majel there are hidden global variables like: + +1. `cpu::SystemAllocator g_cpu_allocator`, and +1. `vector g_gpu_allocators(NUM_GPUS)`. + +Programs allocate memory via a BuddyAllocator, which can take the `g_cpu_allocator` or a `g_gpu_allocators[gpu_id]` as its *fallback allocator*, so that if BuddyAllocator cannot find a block in its memory pool, it extends its memory pool by calling the fallback allocator's `New(size_t)`. -- GitLab From 0a92908b5ea68daa040155a7088b7f520c16c51d Mon Sep 17 00:00:00 2001 From: Yi Wang Date: Wed, 21 Jun 2017 17:02:30 -0700 Subject: [PATCH 0736/3256] Has to auto format networks.py because CI complains about it. --- python/paddle/trainer_config_helpers/networks.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/paddle/trainer_config_helpers/networks.py b/python/paddle/trainer_config_helpers/networks.py index 1bf59ed4840..67154a8d7d3 100755 --- a/python/paddle/trainer_config_helpers/networks.py +++ b/python/paddle/trainer_config_helpers/networks.py @@ -1381,7 +1381,7 @@ def inputs(layers, *args): if len(args) != 0: layers.extend(args) - Inputs(*[l.name for l in layers]) + Inputs(* [l.name for l in layers]) def outputs(layers, *args): @@ -1424,7 +1424,7 @@ def outputs(layers, *args): assert len(layers) > 0 if HasInputsSet(): # input already set - Outputs(*[l.name for l in layers]) + Outputs(* [l.name for l in layers]) return # just return outputs. if len(layers) != 1: -- GitLab From 8bffa4a72fb28f4ca019e7dead41773731a2e33c Mon Sep 17 00:00:00 2001 From: gongweibao Date: Thu, 22 Jun 2017 11:09:00 +0800 Subject: [PATCH 0737/3256] fix bugs --- python/paddle/v2/reader/creator.py | 6 ++++-- python/paddle/v2/reader/tests/creator_test.py | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/python/paddle/v2/reader/creator.py b/python/paddle/v2/reader/creator.py index 994062096fb..8888faae36a 100644 --- a/python/paddle/v2/reader/creator.py +++ b/python/paddle/v2/reader/creator.py @@ -57,15 +57,17 @@ def text_file(path): return reader -def RecordIO(path): +def recordio(path): """ Creates a data reader that outputs record one one by one from given recordio file :path: path of recordio file :returns: data reader of recordio file """ + import recordio as rec + def reader(): - f = recordio.reader(path) + f = rec.reader(path) while True: r = f.read() if r is None: diff --git a/python/paddle/v2/reader/tests/creator_test.py b/python/paddle/v2/reader/tests/creator_test.py index dd84fbb0025..e20af9e5e4c 100644 --- a/python/paddle/v2/reader/tests/creator_test.py +++ b/python/paddle/v2/reader/tests/creator_test.py @@ -37,10 +37,10 @@ class TestTextFile(unittest.TestCase): class TestRecordIO(unittest.TestCase): - def test_RecordIO(self): + def test_recordio(self): path = os.path.join( os.path.dirname(__file__), "test_recordio_creator.dat") - reader = paddle.v2.reader.creator.RecordIO(path) + reader = paddle.v2.reader.creator.recordio(path) for idx, r in enumerate(reader()): self.assertSequenceEqual(r, str(idx)) -- GitLab From 0adb9e01e9bd9ed662ef653b6a62417b18dd1937 Mon Sep 17 00:00:00 2001 From: gongweibao Date: Thu, 22 Jun 2017 11:12:26 +0800 Subject: [PATCH 0738/3256] fix bugs --- python/paddle/v2/reader/creator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/paddle/v2/reader/creator.py b/python/paddle/v2/reader/creator.py index 8888faae36a..9f888b16d6b 100644 --- a/python/paddle/v2/reader/creator.py +++ b/python/paddle/v2/reader/creator.py @@ -16,7 +16,7 @@ Creator package contains some simple reader creator, which could be used in user program. """ -__all__ = ['np_array', 'text_file', "RecordIO"] +__all__ = ['np_array', 'text_file', "recordio"] def np_array(x): -- GitLab From 1f217f0ab319254b901d9f8df8be447e0bed17a6 Mon Sep 17 00:00:00 2001 From: dzhwinter Date: Thu, 22 Jun 2017 12:26:47 +0800 Subject: [PATCH 0739/3256] "add c testing, python testing TODO" --- go/pserver/cclient/cclient.go | 9 +-------- go/pserver/cclient/test/dump_optimizer_proto.py | 13 +++++++++++++ go/pserver/cclient/test/main.c | 13 ++++++++++++- go/pserver/cclient/test/optimizer.pb.txt | Bin 0 -> 51 bytes go/pserver/cclient/test/test_train.py | 2 ++ go/pserver/client_test.go | 8 ++++++-- python/paddle/v2/optimizer.py | 2 ++ 7 files changed, 36 insertions(+), 11 deletions(-) create mode 100644 go/pserver/cclient/test/dump_optimizer_proto.py create mode 100644 go/pserver/cclient/test/optimizer.pb.txt diff --git a/go/pserver/cclient/cclient.go b/go/pserver/cclient/cclient.go index 6aaaff7409d..ba2a235de37 100644 --- a/go/pserver/cclient/cclient.go +++ b/go/pserver/cclient/cclient.go @@ -121,14 +121,7 @@ func paddle_begin_init_params(client C.paddle_pserver_client) C.int { //export paddle_init_param func paddle_init_param(client C.paddle_pserver_client, param C.paddle_parameter, param_config unsafe.Pointer, config_len C.int) C.int { - et := pserver.ElementType(param.element_type) - name := C.GoString(param.name) - pc := pserver.ParameterWithConfig{ - Param: pserver.Parameter{Name: name, ElementType: et, Content: param.content, Length: para.content_len}, - Config: cArrayToSlice(param_config, int(config_len)), - } - c := get(client) - err := c.InitParam(pc) + et if err != nil { if err.Error() == pserver.AlreadyInitialized { diff --git a/go/pserver/cclient/test/dump_optimizer_proto.py b/go/pserver/cclient/test/dump_optimizer_proto.py new file mode 100644 index 00000000000..2ed4db97f90 --- /dev/null +++ b/go/pserver/cclient/test/dump_optimizer_proto.py @@ -0,0 +1,13 @@ +import OptimizerConfig_pb2 as pb + +config = pb.OptimizerConfig() +config.clip_norm = 0.1 +config.lr_policy = pb.OptimizerConfig.Const +config.optimizer = pb.OptimizerConfig.SGD +config.sgd.momentum = 0.0 +config.sgd.decay = 0.0 +config.sgd.nesterov = False +config.const_lr.learning_rate = 0.1 +s = config.SerializeToString() +with open("optimizer.pb.txt", 'w') as f: + f.write(s) diff --git a/go/pserver/cclient/test/main.c b/go/pserver/cclient/test/main.c index 03f749d4e46..7d26127b600 100644 --- a/go/pserver/cclient/test/main.c +++ b/go/pserver/cclient/test/main.c @@ -45,9 +45,20 @@ void getParams(paddle_pserver_client c) { } } + + int main() { char addr[] = "localhost:3000"; paddle_pserver_client c = paddle_new_pserver_client(addr, 1); + char config_proto[1024]; + size_t config_proto_len = 0; + ssize_t nread; + FILE *fp = fopen("optimizer.pb.txt", "r"); + if(!fp) { fail(); } + while((nread = getline(&config_proto, &config_proto_len, fp)) != -1) { + printf("%s", config_proto); + } + fclose(fp); retry: if (paddle_begin_init_params(c)) { paddle_parameter param; @@ -59,7 +70,7 @@ retry: param.name = name_a; param.content = content_a; param.content_len = 2000; - int error = paddle_init_param(c, param, NULL, 0); + int error = paddle_init_param(c, param, config_proto, config_proto_len); if (error != 0) { goto retry; } diff --git a/go/pserver/cclient/test/optimizer.pb.txt b/go/pserver/cclient/test/optimizer.pb.txt new file mode 100644 index 0000000000000000000000000000000000000000..27c8a584df40ab714edfd730f0ff7b7bd3783964 GIT binary patch literal 51 lcmd;JloDUb$N&X9;j9CU3=s@ToSd^}g1}Dum25B;7XZ}t4FdoG literal 0 HcmV?d00001 diff --git a/go/pserver/cclient/test/test_train.py b/go/pserver/cclient/test/test_train.py index 3f8d5d793bd..68e1d9b2692 100644 --- a/go/pserver/cclient/test/test_train.py +++ b/go/pserver/cclient/test/test_train.py @@ -22,6 +22,8 @@ def main(): # create optimizer optimizer = paddle.optimizer.Momentum(momentum=0) + #TODO(zhihong) : replace optimizer with new OptimizerConfig + trainer = paddle.trainer.SGD(cost=cost, parameters=parameters, update_equation=optimizer, diff --git a/go/pserver/client_test.go b/go/pserver/client_test.go index d0371a26a13..c5d38e41129 100644 --- a/go/pserver/client_test.go +++ b/go/pserver/client_test.go @@ -75,7 +75,9 @@ func TestClientFull(t *testing.T) { var p pserver.Parameter p.Name = "p_" + strconv.Itoa(i) p.ElementType = pserver.Float32 - p.Content = make([]byte, (i+1)*100) + ElementValue := make([]byte, (i+1)*100) + p.Content = &ElementValue[0] + p.Length = len(ElementValue) err := c.InitParam(pserver.ParameterWithConfig{Param: p}) if err != nil { t.Fatal(err) @@ -92,7 +94,9 @@ func TestClientFull(t *testing.T) { var g pserver.Gradient g.Name = "p_" + strconv.Itoa(i) g.ElementType = pserver.Float32 - g.Content = make([]byte, (i+1)*100) + ElementValue := make([]byte, (i+1)*100) + g.Content = &ElementValue[0] + g.Length = len(ElementValue) grads = append(grads, g) } diff --git a/python/paddle/v2/optimizer.py b/python/paddle/v2/optimizer.py index 1ef2dceca91..8124e219ba4 100644 --- a/python/paddle/v2/optimizer.py +++ b/python/paddle/v2/optimizer.py @@ -5,6 +5,8 @@ import paddle.trainer_config_helpers.optimizers as v1_optimizers """ Optimizers(update equation) for SGD method. +TODO(zhihong) : create new optimizer with proto config, add new optimizer here + TODO(yuyang18): Complete comments. """ -- GitLab From 106dd4bed2de2b3e71700de4487e9f4ca009df8e Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Thu, 22 Jun 2017 12:37:51 +0800 Subject: [PATCH 0740/3256] Using previous image for travis-ci Because travis-ci has been updated Ubuntu Trusty image, it causes Paddle CI building error. Just using old image now for hot-fix, I will add another issue to fix Paddle building in new TravisCI image. Related link https://blog.travis-ci.com/2017-06-21-trusty-updates-2017-Q2-launch --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index f9b4a7e0831..87cef10b2b1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,3 +1,4 @@ +group: deprecated-2017Q2 language: cpp cache: directories: -- GitLab From cc487c09f73e2dc0e29d2ab07401061facd9b782 Mon Sep 17 00:00:00 2001 From: dzhwinter Date: Thu, 22 Jun 2017 12:52:18 +0800 Subject: [PATCH 0741/3256] "fix typo delete" --- go/pserver/cclient/cclient.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/go/pserver/cclient/cclient.go b/go/pserver/cclient/cclient.go index ba2a235de37..6aaaff7409d 100644 --- a/go/pserver/cclient/cclient.go +++ b/go/pserver/cclient/cclient.go @@ -121,7 +121,14 @@ func paddle_begin_init_params(client C.paddle_pserver_client) C.int { //export paddle_init_param func paddle_init_param(client C.paddle_pserver_client, param C.paddle_parameter, param_config unsafe.Pointer, config_len C.int) C.int { - et + et := pserver.ElementType(param.element_type) + name := C.GoString(param.name) + pc := pserver.ParameterWithConfig{ + Param: pserver.Parameter{Name: name, ElementType: et, Content: param.content, Length: para.content_len}, + Config: cArrayToSlice(param_config, int(config_len)), + } + c := get(client) + err := c.InitParam(pc) if err != nil { if err.Error() == pserver.AlreadyInitialized { -- GitLab From 3e099787b803b30a4f76cbd0c6738a81e1d16c93 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Thu, 22 Jun 2017 14:21:37 +0800 Subject: [PATCH 0742/3256] Add scope doc --- doc/design/scope.md | 66 ++++++++++++++------------------------------- 1 file changed, 20 insertions(+), 46 deletions(-) diff --git a/doc/design/scope.md b/doc/design/scope.md index b8390a37140..b7e0a10d031 100644 --- a/doc/design/scope.md +++ b/doc/design/scope.md @@ -1,58 +1,32 @@ -# Scope +# What is a scope. -### Define +## Overview -Scope is a context to manage Variables. It mainly contains a map from Variable name to Variable. Net will get and update variable throw scope. +预期使用场景。 -```cpp -class Variable; -using VariablePtr = std::shared_ptr; +引出Scope的两个属性。 + 1. Scope是Variable的Container + 2. Scope可以共享 -class Scope final { -public: - Scope(); - Scope(const std::shared_ptr& parent); +## Scope 是一个Variable的Container - //! Get Variable in this scope. - //! @return nullptr if no such variable. - const VariablePtr& getVar(const std::string& name) const; +解释下为啥Scope是Variable的container。解释下面几个小点的原因。 - //! Create or get a variable in this scope. - VariablePtr& createOrGetVar(const std::string& name); + * 他只包含variable + * 每一个variable也只属于一个Scope + * 每一个Scope析构的时候,会同时析构variable + * 只能通过Scope创建Vairable。 + * 只能通过Scope获取Variable。 -private: - /// variable name -> variable - std::unordered_map vars_; - std::shared_ptr parent_{nullptr}; -}; -``` +## Scope 可以被继承或者叫共享 -You need to specify a scope to run a Net. One net can run in different scopes and update different variable in the scope. If you did not specify one, It will run in a default scope. +解释下Scope如何被共享,如何查找Variable的算法。 + * Scope永远从本地寻找Variable,找不到会从他的父亲Scope寻找Variable + * 嵌套深度不做要求。 -```cpp -Scope global; -auto x = newVar("X"); // x is created in scope global, implicitly. -auto y = newVar("Y"); -Net net1; -net1.addOp("add", {x, y}, {x}); // x = x + y; -net1.run(); +# 接口实现 -for (size_t i=0; i<10; ++i) { - Scope local; - auto tmp = newVar("tmp"); // tmp is created in scope local. - Net net2; - net2.addOp("add", {x, y}, {tmp}); - net2.run(); // tmp = x + y; -} +C++ code. -Net net3; -net3.addOp("add", {x, y}, {"tmp"}); // error! cannot found "tmp" in global scope. -``` - -### Chain structure - -Scope has a pointer point to it's parent scope, this is mainly used in RNN when it need to create many stepNet. - - -### Scope Guard +## 各个接口是啥意思,为啥这么设计 -- GitLab From 0b936e9399f2a5f01f6fde1d1b78b56306a8f9ac Mon Sep 17 00:00:00 2001 From: wuyi05 Date: Thu, 22 Jun 2017 15:00:39 +0800 Subject: [PATCH 0743/3256] update pserver etcd --- go/cmd/pserver/pserver.go | 3 +- go/pserver/service.go | 75 ++++++++++++--------- go/utils/{ => networkhelper}/helper.go | 2 +- go/utils/{ => networkhelper}/helper_test.go | 2 +- 4 files changed, 47 insertions(+), 35 deletions(-) rename go/utils/{ => networkhelper}/helper.go (97%) rename go/utils/{ => networkhelper}/helper_test.go (87%) diff --git a/go/cmd/pserver/pserver.go b/go/cmd/pserver/pserver.go index f42c90c6c6d..fe1fe5f6f03 100644 --- a/go/cmd/pserver/pserver.go +++ b/go/cmd/pserver/pserver.go @@ -18,7 +18,8 @@ func main() { etcdEndpoint := flag.String("etcd-endpoint", "http://127.0.0.1:2379", "comma separated endpoint string for pserver to connect to etcd") etcdTimeout := flag.Int("etcd-timeout", 5, "timeout for etcd calls") - logLevel := flag.String("log-level", "info", "log level, one of debug") + logLevel := flag.String("log-level", "info", + "log level, possible values: debug, info, warning, error, fatal, panic") flag.Parse() level, err := log.ParseLevel(*logLevel) diff --git a/go/pserver/service.go b/go/pserver/service.go index a5c76857abe..7400b488325 100644 --- a/go/pserver/service.go +++ b/go/pserver/service.go @@ -9,7 +9,7 @@ import ( "sync" "time" - "github.com/PaddlePaddle/Paddle/go/utils" + "github.com/PaddlePaddle/Paddle/go/utils/networkhelper" "github.com/coreos/etcd/clientv3" "github.com/coreos/etcd/clientv3/concurrency" log "github.com/sirupsen/logrus" @@ -33,6 +33,9 @@ const ( Float64 ) +// PsDesired is etcd path for store desired pserver count +const PsDesired = "/ps_desired" + // Parameter is a piece of data to sync with the parameter server. type Parameter struct { Name string @@ -68,7 +71,8 @@ type Service struct { externalIP string } -// NewService creates a new service. +// NewService creates a new service, will bypass etcd registration if no +// endpoints specified. func NewService(endpoints string, timeout time.Duration) (*Service, error) { s := &Service{opt: newOptimizer(sgd, 0.005)} s.paramMap = make(map[string]Parameter) @@ -77,7 +81,7 @@ func NewService(endpoints string, timeout time.Duration) (*Service, error) { s.etcdTimeout = timeout var err error - s.externalIP, err = utils.GetExternalIP() + s.externalIP, err = networkhelper.GetExternalIP() if err != nil { return nil, err } @@ -102,67 +106,74 @@ func NewService(endpoints string, timeout time.Duration) (*Service, error) { // wait and set s.desired init value for { ctx, cancel := context.WithTimeout(context.Background(), time.Second) - resp, err := s.etcdClient.Get(ctx, "/ps_desired") + resp, err := s.etcdClient.Get(ctx, PsDesired) cancel() if err != nil { - log.Errorf("getting /ps_desired error: %v", err) + log.Errorf("getting %s error: %v", PsDesired, err) time.Sleep(s.etcdTimeout) continue } - for _, ev := range resp.Kvs { - log.Debugf("key: %s, value: %s", ev.Key, ev.Value) - if string(ev.Key) == "/ps_desired" { - s.desired, err = strconv.Atoi(string(ev.Value)) - if err != nil { - log.Errorf("value of /ps_desired invalid %v\n", err) - time.Sleep(s.etcdTimeout) - // NOTE: wait util ps_desired value change - continue - } + if len(resp.Kvs) != 0 { + s.desired, err = strconv.Atoi(string(resp.Kvs[0].Value)) + if err != nil { + log.Errorf("value of %s invalid %v\n", PsDesired, err) + time.Sleep(s.etcdTimeout) + // NOTE: wait util ps_desired value change + continue } + break + } + } + // try register pserver node on etcd + for { + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + _, err := s.registerPserverEtcd(ctx) + cancel() + if err != nil { + log.Warn(err) + time.Sleep(s.etcdTimeout) + continue } break } - s.registerPserverEtcd() } // if endpoints != "" // Bypass etcd registration if no endpoints specified return s, nil } // registerPserverEtcd registers pserver node on etcd using transaction. -func (s *Service) registerPserverEtcd() (*clientv3.TxnResponse, error) { - return concurrency.NewSTMRepeatable(context.TODO(), s.etcdClient, func(c concurrency.STM) error { +func (s *Service) registerPserverEtcd(ctx context.Context) (*clientv3.TxnResponse, error) { + return concurrency.NewSTM(s.etcdClient, func(c concurrency.STM) error { + registered := false for i := 0; i < s.desired; i++ { psKey := "/ps/" + strconv.Itoa(i) log.Debugf("checking %s", psKey) ps := c.Get(psKey) log.Debugf("got value (%s) for key: %s", ps, psKey) - resp, err := s.etcdClient.Grant(context.TODO(), 5) - if err != nil { - log.Fatal(err) - } - if ps == "" { + resp, err := s.etcdClient.Grant(context.TODO(), 5) + if err != nil { + log.Fatal(err) + } // find the first id and write info c.Put(psKey, s.externalIP, clientv3.WithLease(resp.ID)) log.Debugf("set pserver node %s with value %s", psKey, s.externalIP) - ch, kaerr := s.etcdClient.KeepAlive(context.TODO(), resp.ID) + _, kaerr := s.etcdClient.KeepAlive(context.TODO(), resp.ID) if kaerr != nil { log.Errorf("keepalive etcd node error: %v", kaerr) return kaerr } - // FIXME: does this really needed? - go func(ch <-chan *clientv3.LeaseKeepAliveResponse) { - ka := <-ch - log.Debugf("keepalive: %d\n", ka.TTL) - }(ch) + log.Debug("register finished") + registered = true break } } - log.Debug("register finished") - return nil - }) + if registered == true { + return nil + } + return errors.New("not registerd, may due to already have enough pservers") + }, concurrency.WithAbortContext(ctx), concurrency.WithIsolation(concurrency.RepeatableReads)) } // InitParam initializes a parameter. diff --git a/go/utils/helper.go b/go/utils/networkhelper/helper.go similarity index 97% rename from go/utils/helper.go rename to go/utils/networkhelper/helper.go index 3220fd6c78b..fbeaea8f5e7 100644 --- a/go/utils/helper.go +++ b/go/utils/networkhelper/helper.go @@ -1,4 +1,4 @@ -package utils +package networkhelper import ( "errors" diff --git a/go/utils/helper_test.go b/go/utils/networkhelper/helper_test.go similarity index 87% rename from go/utils/helper_test.go rename to go/utils/networkhelper/helper_test.go index aa7c509768e..4208f9e358f 100644 --- a/go/utils/helper_test.go +++ b/go/utils/networkhelper/helper_test.go @@ -1,4 +1,4 @@ -package utils +package networkhelper import "testing" -- GitLab From 04ad9b6b02f0ca69c6b8b879397906edf1fec61f Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Thu, 22 Jun 2017 15:10:37 +0800 Subject: [PATCH 0744/3256] Add Scope Parent & Local section --- doc/design/scope.md | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/doc/design/scope.md b/doc/design/scope.md index e73d3c231c8..3ebbd26338b 100644 --- a/doc/design/scope.md +++ b/doc/design/scope.md @@ -17,11 +17,34 @@ * 每一个Scope析构的时候,会同时析构variable * 只能通过Scope创建Vairable。 * 只能通过Scope获取Variable。 -## Scope 可以被继承或者叫共享 -解释下Scope如何被共享,如何查找Variable的算法。 - * Scope永远从本地寻找Variable,找不到会从他的父亲Scope寻找Variable - * 嵌套深度不做要求。 +## Parent scope and local scope + +Just like [scope](https://en.wikipedia.org/wiki/Scope_(computer_science)) in programming languages, `Scope` in the neural network also can be local. There are two attributes about local scope. + +* We can create local variables in a local scope, and when that local scope are destroyed, all local variables should also be destroyed. +* Variables in a parent scope can be retrieved from that parent scope's local scope, i.e., when user get a variable from a scope, it will search this variable in current scope firstly. If there is no such variable in local scope, `scope` will keep searching from its parent, until the variable is found or there is no parent. + +```cpp +class Scope { +public: + Scope(const std::shared_ptr& scope): parent_(scope) {} + + Variable* Get(const std::string& name) const { + Variable* var = GetVarLocally(name); + if (var != nullptr) { + return var; + } else if (parent_ != nullptr) { + return parent_->Get(name); + } else { + return nullptr; + } + } + +private: + std::shared_ptr parent_ {nullptr}; +}; +``` # 接口实现 -- GitLab From 581e4c1cbd2208896dbbc6facef4099a607bfc8c Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Thu, 22 Jun 2017 15:27:16 +0800 Subject: [PATCH 0745/3256] Parent & local scope done --- doc/design/scope.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/doc/design/scope.md b/doc/design/scope.md index 3ebbd26338b..27ceed961fc 100644 --- a/doc/design/scope.md +++ b/doc/design/scope.md @@ -20,10 +20,10 @@ ## Parent scope and local scope -Just like [scope](https://en.wikipedia.org/wiki/Scope_(computer_science)) in programming languages, `Scope` in the neural network also can be local. There are two attributes about local scope. +Just like [scope](https://en.wikipedia.org/wiki/Scope_(computer_science)) in programming languages, `Scope` in the neural network also can be local. There are two attributes about local scope. -* We can create local variables in a local scope, and when that local scope are destroyed, all local variables should also be destroyed. -* Variables in a parent scope can be retrieved from that parent scope's local scope, i.e., when user get a variable from a scope, it will search this variable in current scope firstly. If there is no such variable in local scope, `scope` will keep searching from its parent, until the variable is found or there is no parent. +1. We can create local variables in a local scope, and when that local scope are destroyed, all local variables should also be destroyed. +2. Variables in a parent scope can be retrieved from that parent scope's local scope, i.e., when user get a variable from a scope, it will search this variable in current scope firstly. If there is no such variable in local scope, `scope` will keep searching from its parent, until the variable is found or there is no parent. ```cpp class Scope { @@ -46,6 +46,10 @@ private: }; ``` +In `Scope` class, there is a private data member called `parent_`. `parent_` is a smart pointer to its parent scope. When user `Get` a variable by its `name`, the `name` will be searched locally inside the current scope. If the variable cannot be found locally and parent scope is not a `nullptr`, the variable will be searched inside that parent scope. `parent_` pointer's default value is `nullptr`. It means that the scope is a global scope when `parent_` is nullptr. + +A local scope is very useful when we implement Recurrent Neural Network. Each timestep of an RNN should be a `Net`. Each `Net` of timestep (`StepNet` for short) should use an independent local scope. Just like each variable in a while loop is inside a local scope in programming languages. By using a single `StepNet` and changing local scope, we can implement an RNN easily. + # 接口实现 # 各个接口是啥意思,为啥这么设计 -- GitLab From 0b70361a0ee71ed04ca6925a25bbdd3e434c2bfe Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Thu, 22 Jun 2017 15:35:00 +0800 Subject: [PATCH 0746/3256] Refining english --- doc/design/scope.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/design/scope.md b/doc/design/scope.md index 27ceed961fc..341ec16d79a 100644 --- a/doc/design/scope.md +++ b/doc/design/scope.md @@ -20,10 +20,10 @@ ## Parent scope and local scope -Just like [scope](https://en.wikipedia.org/wiki/Scope_(computer_science)) in programming languages, `Scope` in the neural network also can be local. There are two attributes about local scope. +Just like [scope](https://en.wikipedia.org/wiki/Scope_(computer_science)) in programming languages, `Scope` in the neural network can also be local. There are two attributes about local scope. -1. We can create local variables in a local scope, and when that local scope are destroyed, all local variables should also be destroyed. -2. Variables in a parent scope can be retrieved from that parent scope's local scope, i.e., when user get a variable from a scope, it will search this variable in current scope firstly. If there is no such variable in local scope, `scope` will keep searching from its parent, until the variable is found or there is no parent. +1. We can create local variables in a local scope. When that local scope are destroyed, all local variables should also be destroyed. +2. Variables in a parent scope can be retrieved from that parent scope's local scopes, i.e., when user get a variable from a scope, it will try to search this variable in current scope. If there is no such variable in the local scope, `scope` will keep searching from its parent, until the variable is found or there is no parent. ```cpp class Scope { @@ -46,9 +46,9 @@ private: }; ``` -In `Scope` class, there is a private data member called `parent_`. `parent_` is a smart pointer to its parent scope. When user `Get` a variable by its `name`, the `name` will be searched locally inside the current scope. If the variable cannot be found locally and parent scope is not a `nullptr`, the variable will be searched inside that parent scope. `parent_` pointer's default value is `nullptr`. It means that the scope is a global scope when `parent_` is nullptr. +In `Scope` class, there is a private data member called `parent_`. `parent_` is a smart pointer to its parent scope. When user `Get` a variable by its `name`, the `name` will be searched inside the current scope. If the variable cannot be found locally and parent scope is not a `nullptr`, the variable will be searched inside that parent scope. `parent_` pointer's default value is `nullptr`. It means that the scope is a global scope when `parent_` is nullptr. -A local scope is very useful when we implement Recurrent Neural Network. Each timestep of an RNN should be a `Net`. Each `Net` of timestep (`StepNet` for short) should use an independent local scope. Just like each variable in a while loop is inside a local scope in programming languages. By using a single `StepNet` and changing local scope, we can implement an RNN easily. +A local scope is very useful when we implement Recurrent Neural Network. Each timestep of an RNN should be a `Net`. Each `Net` of timestep (`StepNet` for short) should use an independent local scope. Just like variables in a while loop is inside a local scope in programming languages. By using a single `StepNet` and changing local scope, we can implement an RNN easily. # 接口实现 -- GitLab From 76e2a3cd95236ab29fb1b5562d5aab52afa99470 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Thu, 22 Jun 2017 15:40:08 +0800 Subject: [PATCH 0747/3256] Refine English --- doc/design/scope.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/design/scope.md b/doc/design/scope.md index 341ec16d79a..695426b2f25 100644 --- a/doc/design/scope.md +++ b/doc/design/scope.md @@ -20,10 +20,10 @@ ## Parent scope and local scope -Just like [scope](https://en.wikipedia.org/wiki/Scope_(computer_science)) in programming languages, `Scope` in the neural network can also be local. There are two attributes about local scope. +Just like [scope](https://en.wikipedia.org/wiki/Scope_(computer_science)) in programming languages, `Scope` in the neural network can also be a local scope. There are two attributes about local scope. 1. We can create local variables in a local scope. When that local scope are destroyed, all local variables should also be destroyed. -2. Variables in a parent scope can be retrieved from that parent scope's local scopes, i.e., when user get a variable from a scope, it will try to search this variable in current scope. If there is no such variable in the local scope, `scope` will keep searching from its parent, until the variable is found or there is no parent. +2. Variables in a parent scope can be retrieved from local scopes of that parent scope, i.e., when user get a variable from a scope, it will try to search this variable in current scope. If there is no such variable in the local scope, `scope` will keep searching from its parent, until the variable is found or there is no parent. ```cpp class Scope { -- GitLab From 8282138047cd443aa579d04320bb10ffd5bde5ca Mon Sep 17 00:00:00 2001 From: qiaolongfei Date: Thu, 22 Jun 2017 15:49:52 +0800 Subject: [PATCH 0748/3256] some properties of scope --- doc/design/scope.md | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/doc/design/scope.md b/doc/design/scope.md index e73d3c231c8..b0ee7445358 100644 --- a/doc/design/scope.md +++ b/doc/design/scope.md @@ -8,15 +8,29 @@ 1. Scope是Variable的Container 2. Scope可以共享 -## Scope 是一个Variable的Container +## Scope is a Container of Variables. -解释下为啥Scope是Variable的container。解释下面几个小点的原因。 + * Scope contains Variables as it's data member. + * Scope contains methods that are used to manage Variables, such as Create/Get/Delete. + * every variable only belong to one certain Scope. + * Scope should destruct all Variables within it when itself is destructed. + * Variable can only be created by Scope. + * Variable can only be got from Scope. + + * Scope do not contains Operators and have no information to run them. + +```cpp +class Scope { + public: + Variable* CreateVariable(const std::string& name); + const Variable* GetVariable(const std::string& name) const; + bool DeleteVariable(const std::string& name); + + private: + std::unordered_map> variable_map_; +}; +``` - * 他只包含variable - * 每一个variable也只属于一个Scope - * 每一个Scope析构的时候,会同时析构variable - * 只能通过Scope创建Vairable。 - * 只能通过Scope获取Variable。 ## Scope 可以被继承或者叫共享 解释下Scope如何被共享,如何查找Variable的算法。 -- GitLab From d7aca775c5bc5b9d914435cd4b4648c2e7cbd687 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Thu, 22 Jun 2017 16:31:03 +0800 Subject: [PATCH 0749/3256] Update API --- doc/design/scope.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/design/scope.md b/doc/design/scope.md index 695426b2f25..6694e275b60 100644 --- a/doc/design/scope.md +++ b/doc/design/scope.md @@ -30,7 +30,7 @@ class Scope { public: Scope(const std::shared_ptr& scope): parent_(scope) {} - Variable* Get(const std::string& name) const { + Variable* GetVar(const std::string& name) const { Variable* var = GetVarLocally(name); if (var != nullptr) { return var; -- GitLab From 2d5507fab26fe990e88cbe3569b5a1f1a810b6b8 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Thu, 22 Jun 2017 16:46:41 +0800 Subject: [PATCH 0750/3256] Add interfaces --- doc/design/scope.md | 42 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/doc/design/scope.md b/doc/design/scope.md index 6694e275b60..68395435dd4 100644 --- a/doc/design/scope.md +++ b/doc/design/scope.md @@ -50,6 +50,44 @@ In `Scope` class, there is a private data member called `parent_`. `parent_` is A local scope is very useful when we implement Recurrent Neural Network. Each timestep of an RNN should be a `Net`. Each `Net` of timestep (`StepNet` for short) should use an independent local scope. Just like variables in a while loop is inside a local scope in programming languages. By using a single `StepNet` and changing local scope, we can implement an RNN easily. -# 接口实现 +# Interface Design -# 各个接口是啥意思,为啥这么设计 +```cpp +class Variable { +private: + Variable() = default; + friend class Scope; +}; + +using VariablePtr = std::weak_ptr; + +class Scope { +public: + Scope(const std::shared_ptr& parent = nullptr); + + // return nullptr if not found. + VariablePtr GetVariable(const std::string& name) const; + + // return Error if already contains same name variable. + Error CreateVariable(const std::string& name); + +private: + std::shared_ptr parent_; + std::unordered_map> attrs_; +}; +``` +## Only scope can create a variable + +To ensure `only scope can create a variable`, we should mark `Variable`'s constructor as a private member function, and Scope is a friend class of Variable. And then only `CreateVariable` can construct `Variable`. + +## When scope destroyed, all variables inside this scope should be destroyed together + +The `VariablePtr` is a `weak_ptr`. `Net` and `Op` can only get a Variable from `Scope`, but cannot hold it. When scope is destroyed, all `VariablePtr`s belong to this Scope will be changed to `nullptr`. + +## Sharing a parent scope + +Local scope contains a `parent_` pointer. It is a linked-list for scopes. Using a `shared_ptr` because when a local scope is using, its parents cannot be destroyed. + +## Orthogonal interface + +`GetVariable` will return `nullptr` when `name` is not found. It can be used as `Contains` method. `CreateVariable` will return a `Error` when there is a name conflict locally. Combine `GetVariable` and `CreateVariable`, we can implement `CreateOrGetVariable` easily. -- GitLab From 73b1c5bd96d5bae8e8558839681f955261f4d197 Mon Sep 17 00:00:00 2001 From: Yibing Liu Date: Thu, 22 Jun 2017 16:51:08 +0800 Subject: [PATCH 0751/3256] add overview for scope design doc --- doc/design/scope.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/doc/design/scope.md b/doc/design/scope.md index de2e67d3272..5023d4b0e4c 100644 --- a/doc/design/scope.md +++ b/doc/design/scope.md @@ -2,11 +2,12 @@ ## Overview -预期使用场景。 +Scope is an important concept in programming languages, which defines a program region that a set of bindings between names and entities applies. In a specific scope, a valid name is uniquely associated with an entity, such as a variable. And in another scope, this name may refer to other entity or nothing at all. It clearly restricts the visibility and validity of names in a program. Hence **Scope** is introduced to PaddlePaddle to manage variables in context. But different from the original abstract concept, Scope now becomes an object with two important attributes: -引出Scope的两个属性。 - 1. Scope是Variable的Container - 2. Scope可以共享 +- Scope is a container of variables +- Scope can be inherited or shared + +A detailed explanation of these two attributes goes as following. ## Scope is a Container of Variables. -- GitLab From 1f0056b242f79f34fa472dcf93014866a787753c Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Thu, 22 Jun 2017 16:52:35 +0800 Subject: [PATCH 0752/3256] Update interface --- doc/design/scope.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/doc/design/scope.md b/doc/design/scope.md index 74bb6242e49..76af6c30c10 100644 --- a/doc/design/scope.md +++ b/doc/design/scope.md @@ -76,9 +76,12 @@ private: using VariablePtr = std::weak_ptr; class Scope { -public: +private: Scope(const std::shared_ptr& parent = nullptr); +public: + static std::shared_ptr Create(const std::shared_ptr& parent = nullptr); + // return nullptr if not found. VariablePtr GetVariable(const std::string& name) const; @@ -102,6 +105,8 @@ The `VariablePtr` is a `weak_ptr`. `Net` and `Op` can only get a Variable from ` Local scope contains a `parent_` pointer. It is a linked-list for scopes. Using a `shared_ptr` because when a local scope is using, its parents cannot be destroyed. +Also, as the parent scope is a `shared_ptr`, we can only `Create()` a scope shared pointer. We cannot construct a scope variable, because it cannot be passed to other scope as `parent` pointer. + ## Orthogonal interface `GetVariable` will return `nullptr` when `name` is not found. It can be used as `Contains` method. `CreateVariable` will return a `Error` when there is a name conflict locally. Combine `GetVariable` and `CreateVariable`, we can implement `CreateOrGetVariable` easily. -- GitLab From 17eed332af53894b7525ef7584e37e622bca3f4d Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Thu, 22 Jun 2017 16:58:03 +0800 Subject: [PATCH 0753/3256] Update key attributes --- doc/design/scope.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/design/scope.md b/doc/design/scope.md index 5ba3deb8478..1bd36beb5a0 100644 --- a/doc/design/scope.md +++ b/doc/design/scope.md @@ -4,8 +4,8 @@ Scope is an important concept in programming languages, which defines a program region that a set of bindings between names and entities applies. In a specific scope, a valid name is uniquely associated with an entity, such as a variable. And in another scope, this name may refer to other entity or nothing at all. It clearly restricts the visibility and validity of names in a program. Hence **Scope** is introduced to PaddlePaddle to manage variables in context. But different from the original abstract concept, Scope now becomes an object with two important attributes: -- Scope is a container of variables -- Scope can be inherited or shared +- Scope is an association of a name to variable. +- Variables in a parent scope can be retrieved from local scope. A detailed explanation of these two attributes goes as following. -- GitLab From 37fd48bf159711a525a3f3d67ee055b5a05a5d3a Mon Sep 17 00:00:00 2001 From: qiaolongfei Date: Thu, 22 Jun 2017 17:01:59 +0800 Subject: [PATCH 0754/3256] some detailed explaination of the Scope properties --- doc/design/scope.md | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/doc/design/scope.md b/doc/design/scope.md index 1bd36beb5a0..fac9643b4b7 100644 --- a/doc/design/scope.md +++ b/doc/design/scope.md @@ -9,16 +9,30 @@ Scope is an important concept in programming languages, which defines a program A detailed explanation of these two attributes goes as following. + ## Scope is a Container of Variables. - * Scope contains Variables as it's data member. - * Scope contains methods that are used to manage Variables, such as Create/Get/Delete. - * every variable only belong to one certain Scope. - * Scope should destruct all Variables within it when itself is destructed. - * Variable can only be created by Scope. - * Variable can only be got from Scope. +Scope is used to provide a running environment for Net. + +1. Scope mainly has Variables as it's data member. + Scope is a running environment for Net. Net should get all it need to do computation from a scope, such as data buffer, state(momentum) etc. + All these data/state can be abstracted and create as variable in Paddle, so the only thing Scope need to care about is Variable. +1. Variable can only be created by Scope. +1. Variable can only be got from Scope. +1. Scope contains methods that are used to manage Variables, such as Create/Get/Delete. + Because we only need to care about Variable, we only need method to manage the lifecycle of Variable. + - `Create` is used to create a Variable by its name and add the mapping relation. + - `Get` is used to find a Variable by name. + - `Delete` is used to remove a Variable because sometimes we want to release memory or other resources. + +1. Every variable only belongs to one certain Scope. + Variable can not be shared between nets, if we want to use variables from different scope we can use `Parent scope`. + +1. Scope should destruct all Variables within it when itself is destructed. + Because Variable can only be got from Scope, when destroying Scope, we also need to destroy all the Vars in it. - * Scope do not contains Operators and have no information to run them. +1. Scope do not contain Operators and have no information to run them. + Net is designed to drive the computation, Scope is only used to provide a running environment. ```cpp class Scope { -- GitLab From c3a4b8bc4407b2496378b57dfea54be0dd05f745 Mon Sep 17 00:00:00 2001 From: qiaolongfei Date: Thu, 22 Jun 2017 17:03:56 +0800 Subject: [PATCH 0755/3256] refine style of markdown --- doc/design/scope.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/doc/design/scope.md b/doc/design/scope.md index fac9643b4b7..a255b361d07 100644 --- a/doc/design/scope.md +++ b/doc/design/scope.md @@ -15,23 +15,28 @@ A detailed explanation of these two attributes goes as following. Scope is used to provide a running environment for Net. 1. Scope mainly has Variables as it's data member. + Scope is a running environment for Net. Net should get all it need to do computation from a scope, such as data buffer, state(momentum) etc. All these data/state can be abstracted and create as variable in Paddle, so the only thing Scope need to care about is Variable. 1. Variable can only be created by Scope. 1. Variable can only be got from Scope. 1. Scope contains methods that are used to manage Variables, such as Create/Get/Delete. + Because we only need to care about Variable, we only need method to manage the lifecycle of Variable. - `Create` is used to create a Variable by its name and add the mapping relation. - `Get` is used to find a Variable by name. - `Delete` is used to remove a Variable because sometimes we want to release memory or other resources. 1. Every variable only belongs to one certain Scope. + Variable can not be shared between nets, if we want to use variables from different scope we can use `Parent scope`. 1. Scope should destruct all Variables within it when itself is destructed. + Because Variable can only be got from Scope, when destroying Scope, we also need to destroy all the Vars in it. 1. Scope do not contain Operators and have no information to run them. + Net is designed to drive the computation, Scope is only used to provide a running environment. ```cpp -- GitLab From db96c0eef9dbe860f8ee080d91b157c77bf4ddbc Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Thu, 22 Jun 2017 17:10:47 +0800 Subject: [PATCH 0756/3256] Use unique_ptr instead of shared_ptr/weak_ptr. But user can not hold this pointers. --- doc/design/scope.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/doc/design/scope.md b/doc/design/scope.md index 76af6c30c10..8d5744c227b 100644 --- a/doc/design/scope.md +++ b/doc/design/scope.md @@ -73,8 +73,6 @@ private: friend class Scope; }; -using VariablePtr = std::weak_ptr; - class Scope { private: Scope(const std::shared_ptr& parent = nullptr); @@ -83,14 +81,14 @@ public: static std::shared_ptr Create(const std::shared_ptr& parent = nullptr); // return nullptr if not found. - VariablePtr GetVariable(const std::string& name) const; + Variable* GetVariable(const std::string& name) const; // return Error if already contains same name variable. Error CreateVariable(const std::string& name); private: std::shared_ptr parent_; - std::unordered_map> attrs_; + std::unordered_map> attrs_; }; ``` ## Only scope can create a variable @@ -99,7 +97,7 @@ To ensure `only scope can create a variable`, we should mark `Variable`'s constr ## When scope destroyed, all variables inside this scope should be destroyed together -The `VariablePtr` is a `weak_ptr`. `Net` and `Op` can only get a Variable from `Scope`, but cannot hold it. When scope is destroyed, all `VariablePtr`s belong to this Scope will be changed to `nullptr`. +The scope hold unique pointers for all variables. User can `GetVariable` from scope, but he should not hold this pointer as a member variable. Because when scope is destroyed, all variables inside this scope will be destroyed together. ## Sharing a parent scope -- GitLab From f104ce2cd2f047f59d09422643d8791feeba483c Mon Sep 17 00:00:00 2001 From: qiaolongfei Date: Thu, 22 Jun 2017 17:12:57 +0800 Subject: [PATCH 0757/3256] fix a mistake share by nets -> share by scopes --- doc/design/scope.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/design/scope.md b/doc/design/scope.md index a255b361d07..dec033ad632 100644 --- a/doc/design/scope.md +++ b/doc/design/scope.md @@ -29,7 +29,7 @@ Scope is used to provide a running environment for Net. 1. Every variable only belongs to one certain Scope. - Variable can not be shared between nets, if we want to use variables from different scope we can use `Parent scope`. + Variable can not be shared between scopes, if we want to use variables from different scope we can use `Parent scope`. 1. Scope should destruct all Variables within it when itself is destructed. -- GitLab From eab0e5229f1d020c8b495dab4187221517dbfb67 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Thu, 22 Jun 2017 17:20:48 +0800 Subject: [PATCH 0758/3256] To google code style --- doc/design/scope.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/doc/design/scope.md b/doc/design/scope.md index f2869bc254a..76616cc6a64 100644 --- a/doc/design/scope.md +++ b/doc/design/scope.md @@ -61,7 +61,7 @@ Just like [scope](https://en.wikipedia.org/wiki/Scope_(computer_science)) in pro ```cpp class Scope { -public: + public: Scope(const std::shared_ptr& scope): parent_(scope) {} Variable* GetVar(const std::string& name) const { @@ -75,7 +75,7 @@ public: } } -private: + private: std::shared_ptr parent_ {nullptr}; }; ``` @@ -88,16 +88,16 @@ A local scope is very useful when we implement Recurrent Neural Network. Each ti ```cpp class Variable { -private: + private: Variable() = default; friend class Scope; }; class Scope { -private: + private: Scope(const std::shared_ptr& parent = nullptr); -public: + public: static std::shared_ptr Create(const std::shared_ptr& parent = nullptr); // return nullptr if not found. @@ -106,7 +106,7 @@ public: // return Error if already contains same name variable. Error CreateVariable(const std::string& name); -private: + private: std::shared_ptr parent_; std::unordered_map> attrs_; }; -- GitLab From 63a56b4a5182d31bdcbabdd15573dfec54a04bb9 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Thu, 22 Jun 2017 17:24:30 +0800 Subject: [PATCH 0759/3256] Change typo --- doc/design/scope.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/design/scope.md b/doc/design/scope.md index 76616cc6a64..cd92ba2aa86 100644 --- a/doc/design/scope.md +++ b/doc/design/scope.md @@ -108,7 +108,7 @@ class Scope { private: std::shared_ptr parent_; - std::unordered_map> attrs_; + std::unordered_map> attrs_; }; ``` ## Only scope can create a variable -- GitLab From 921fa13eef21c63455bfbb9972ac7f16fb345c51 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Thu, 22 Jun 2017 17:25:19 +0800 Subject: [PATCH 0760/3256] Remove delete --- doc/design/scope.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/doc/design/scope.md b/doc/design/scope.md index cd92ba2aa86..e08aebfb019 100644 --- a/doc/design/scope.md +++ b/doc/design/scope.md @@ -20,12 +20,11 @@ Scope is used to provide a running environment for Net. All these data/state can be abstracted and create as variable in Paddle, so the only thing Scope need to care about is Variable. 1. Variable can only be created by Scope. 1. Variable can only be got from Scope. -1. Scope contains methods that are used to manage Variables, such as Create/Get/Delete. +1. Scope contains methods that are used to manage Variables, such as Create/Get. Because we only need to care about Variable, we only need method to manage the lifecycle of Variable. - `Create` is used to create a Variable by its name and add the mapping relation. - `Get` is used to find a Variable by name. - - `Delete` is used to remove a Variable because sometimes we want to release memory or other resources. 1. Every variable only belongs to one certain Scope. @@ -44,10 +43,9 @@ class Scope { public: Variable* CreateVariable(const std::string& name); const Variable* GetVariable(const std::string& name) const; - bool DeleteVariable(const std::string& name); private: - std::unordered_map> variable_map_; + std::unordered_map> vars_; }; ``` @@ -108,7 +106,7 @@ class Scope { private: std::shared_ptr parent_; - std::unordered_map> attrs_; + std::unordered_map> vars_; }; ``` ## Only scope can create a variable -- GitLab From 5d88249125039a5fcc5f1a33fd84d7accbd2c526 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Thu, 22 Jun 2017 17:26:15 +0800 Subject: [PATCH 0761/3256] Typo --- doc/design/scope.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/design/scope.md b/doc/design/scope.md index e08aebfb019..23893308b2a 100644 --- a/doc/design/scope.md +++ b/doc/design/scope.md @@ -62,7 +62,7 @@ class Scope { public: Scope(const std::shared_ptr& scope): parent_(scope) {} - Variable* GetVar(const std::string& name) const { + Variable* GetVariable(const std::string& name) const { Variable* var = GetVarLocally(name); if (var != nullptr) { return var; -- GitLab From f8a209c2dee0ce16dbf12857ae88556c9917e458 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Thu, 22 Jun 2017 17:48:19 +0800 Subject: [PATCH 0762/3256] Rearrange description. --- doc/design/scope.md | 31 ++++++++++++++----------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/doc/design/scope.md b/doc/design/scope.md index 23893308b2a..1740019f3d5 100644 --- a/doc/design/scope.md +++ b/doc/design/scope.md @@ -10,33 +10,30 @@ Scope is an important concept in programming languages, which defines a program A detailed explanation of these two attributes goes as following. -## Scope is a Container of Variables. +## Scope is an association of a name to variable. -Scope is used to provide a running environment for Net. +Scope is an association of a name to variable. All variables belong to `Scope`. You need to specify a scope to run a Net, i.e., `net.Run(&scope)`. One net can run in different scopes and update different variable in the scope. -1. Scope mainly has Variables as it's data member. - Scope is a running environment for Net. Net should get all it need to do computation from a scope, such as data buffer, state(momentum) etc. - All these data/state can be abstracted and create as variable in Paddle, so the only thing Scope need to care about is Variable. -1. Variable can only be created by Scope. -1. Variable can only be got from Scope. -1. Scope contains methods that are used to manage Variables, such as Create/Get. +1. Scope only contains a map of a name to variable. - Because we only need to care about Variable, we only need method to manage the lifecycle of Variable. + All parameters, data, states in a Net should be variables and stored inside a scope. Each op should get inputs and outputs to do computation from a scope, such as data buffer, state(momentum) etc. + +1. Variable can only be created by Scope and a variable can only be got from Scope. User cannot create or get a variable outside a scope. This is a constraints of our framework, and will keep our framework simple and clear. + +1. Scope only contains methods that are used to Create and Get Variables. Scope do not contain Operators and have no information to run them. + + `Net` is designed to drive the computation and Scope only contains a map of variables. There is no computation logic inside a `Scope`. Scope just handles the lifetime management of variables. - `Create` is used to create a Variable by its name and add the mapping relation. - `Get` is used to find a Variable by name. 1. Every variable only belongs to one certain Scope. - Variable can not be shared between scopes, if we want to use variables from different scope we can use `Parent scope`. - -1. Scope should destruct all Variables within it when itself is destructed. - - Because Variable can only be got from Scope, when destroying Scope, we also need to destroy all the Vars in it. + Variable can not belong to many scopes. If you want to use variables from parent scope, you can use `parent scope`. -1. Scope do not contain Operators and have no information to run them. +1. Scope should destruct all Variables inside it when itself is destructed. User can never store `Variable` pointer somewhere else. - Net is designed to drive the computation, Scope is only used to provide a running environment. + Because Variable can only be got from Scope. When destroying Scope, we also need to destroy all the Variables in it. If user store `Variable` pointer to private data member or some global variable, the pointer will be a invalid pointer when associated `Scope` is destroyed. ```cpp class Scope { @@ -45,7 +42,7 @@ class Scope { const Variable* GetVariable(const std::string& name) const; private: - std::unordered_map> vars_; + std::unordered_map> vars_; }; ``` -- GitLab From c5ad89a24d54f934bb40ec586f3f42fdf007ee16 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Thu, 22 Jun 2017 17:51:04 +0800 Subject: [PATCH 0763/3256] Change title --- doc/design/scope.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/design/scope.md b/doc/design/scope.md index 1740019f3d5..d00ab37da44 100644 --- a/doc/design/scope.md +++ b/doc/design/scope.md @@ -1,4 +1,4 @@ -# What is a scope. +# Design of Scope in Paddle ## Overview -- GitLab From 237efc2f263b7cdf7404a065c95b7e45260f4dda Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Thu, 22 Jun 2017 18:00:28 +0800 Subject: [PATCH 0764/3256] Fix markdown --- doc/design/scope.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/doc/design/scope.md b/doc/design/scope.md index d00ab37da44..149e1ea1b98 100644 --- a/doc/design/scope.md +++ b/doc/design/scope.md @@ -22,8 +22,7 @@ Scope is an association of a name to variable. All variables belong to `Scope`. 1. Variable can only be created by Scope and a variable can only be got from Scope. User cannot create or get a variable outside a scope. This is a constraints of our framework, and will keep our framework simple and clear. 1. Scope only contains methods that are used to Create and Get Variables. Scope do not contain Operators and have no information to run them. - - `Net` is designed to drive the computation and Scope only contains a map of variables. There is no computation logic inside a `Scope`. Scope just handles the lifetime management of variables. + `Net` is designed to drive the computation and Scope only contains a map of variables. There is no computation logic inside a `Scope`. Scope just handles the lifetime management of variables. - `Create` is used to create a Variable by its name and add the mapping relation. - `Get` is used to find a Variable by name. -- GitLab From 3bac2d0d7e50f67fd82f96bccaf3bfdeabd930f7 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Thu, 22 Jun 2017 22:00:25 +0800 Subject: [PATCH 0765/3256] Typo --- doc/design/scope.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/design/scope.md b/doc/design/scope.md index 149e1ea1b98..2ff416f06e8 100644 --- a/doc/design/scope.md +++ b/doc/design/scope.md @@ -63,7 +63,7 @@ class Scope { if (var != nullptr) { return var; } else if (parent_ != nullptr) { - return parent_->Get(name); + return parent_->GetVariable(name); } else { return nullptr; } @@ -102,7 +102,7 @@ class Scope { private: std::shared_ptr parent_; - std::unordered_map> vars_; + std::unordered_map> vars_; }; ``` ## Only scope can create a variable -- GitLab From d3e2db4b4f3efa537a2b85bb88d8d8f3e780f09c Mon Sep 17 00:00:00 2001 From: Yi Wang Date: Thu, 22 Jun 2017 08:12:10 -0700 Subject: [PATCH 0766/3256] Revert changes made by misleading errors from Travis CI --- python/paddle/trainer_config_helpers/networks.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/paddle/trainer_config_helpers/networks.py b/python/paddle/trainer_config_helpers/networks.py index 67154a8d7d3..1bf59ed4840 100755 --- a/python/paddle/trainer_config_helpers/networks.py +++ b/python/paddle/trainer_config_helpers/networks.py @@ -1381,7 +1381,7 @@ def inputs(layers, *args): if len(args) != 0: layers.extend(args) - Inputs(* [l.name for l in layers]) + Inputs(*[l.name for l in layers]) def outputs(layers, *args): @@ -1424,7 +1424,7 @@ def outputs(layers, *args): assert len(layers) > 0 if HasInputsSet(): # input already set - Outputs(* [l.name for l in layers]) + Outputs(*[l.name for l in layers]) return # just return outputs. if len(layers) != 1: -- GitLab From eaed87b00ff4254c261136b4ed6de8eb794b8090 Mon Sep 17 00:00:00 2001 From: dzhwinter Date: Thu, 22 Jun 2017 23:33:47 +0800 Subject: [PATCH 0767/3256] "fix optimizer include error" --- go/pserver/cclient/CMakeLists.txt | 5 +++-- go/pserver/optimizer.go | 1 + go/pserver/optimizer_test.go | 6 ++++++ go/pserver/service_test.go | 8 ++++++-- 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/go/pserver/cclient/CMakeLists.txt b/go/pserver/cclient/CMakeLists.txt index fff7ae78582..65a38ba1add 100644 --- a/go/pserver/cclient/CMakeLists.txt +++ b/go/pserver/cclient/CMakeLists.txt @@ -10,5 +10,6 @@ include(golang) include(flags) go_library(paddle_pserver_cclient STATIC) - -add_subdirectory(test) +if(WITH_TESTING) + add_subdirectory(test) +endif() diff --git a/go/pserver/optimizer.go b/go/pserver/optimizer.go index 5abbca538f2..3ee4c746528 100644 --- a/go/pserver/optimizer.go +++ b/go/pserver/optimizer.go @@ -1,6 +1,7 @@ package pserver /* +#cgo CFLAGS: -I ../../ #include "paddle/optimizer/optimizer.h" */ import "C" diff --git a/go/pserver/optimizer_test.go b/go/pserver/optimizer_test.go index 64d6d092aa1..4930f0d95f9 100644 --- a/go/pserver/optimizer_test.go +++ b/go/pserver/optimizer_test.go @@ -3,6 +3,12 @@ package pserver import "testing" func TestSGDCreateRelease(t *testing.T) { + param := pserver.ParameterWithConfig{ + Param : pserver.Parameter{Name : "a", + ElementType: , + Content: , + Length : } + } o := newOptimizer(sgd, 1) o.Cleanup() } diff --git a/go/pserver/service_test.go b/go/pserver/service_test.go index b746d13e1ca..1b2626f7db8 100644 --- a/go/pserver/service_test.go +++ b/go/pserver/service_test.go @@ -13,7 +13,9 @@ func TestFull(t *testing.T) { s := pserver.NewService() var p pserver.Parameter p.Name = "param_a" - p.Content = []byte{1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0} + ElementValue := []byte{1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0} + p.Content = &ElementValue[0] + p.Length = len(ElementValue) p.ElementType = pserver.Int32 err := s.InitParam(pserver.ParameterWithConfig{Param: p, Config: nil}, nil) if err != nil { @@ -22,7 +24,9 @@ func TestFull(t *testing.T) { var p1 pserver.Parameter p1.Name = "param_b" - p1.Content = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} + ElementValue = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} + p1.Content = &ElementValue[0] + p1.Length = len(ElementValue) p1.ElementType = pserver.Float32 err = s.InitParam(pserver.ParameterWithConfig{Param: p1, Config: nil}, nil) if err != nil { -- GitLab From 7386b06ccdec68f71dc71cdcef1588cc0ff68cb3 Mon Sep 17 00:00:00 2001 From: dzhwinter Date: Fri, 23 Jun 2017 01:01:15 +0800 Subject: [PATCH 0768/3256] "add optimizer naive link option" --- go/pserver/cclient/test/CMakeLists.txt | 4 - go/pserver/cclient/test/main.c | 104 ---------------- go/pserver/cclient/test/test_cclient.c | 159 ++++++++++++------------- go/pserver/optimizer.go | 3 + 4 files changed, 76 insertions(+), 194 deletions(-) delete mode 100644 go/pserver/cclient/test/main.c diff --git a/go/pserver/cclient/test/CMakeLists.txt b/go/pserver/cclient/test/CMakeLists.txt index 1a3dd7e5e9e..722bd45d2f0 100644 --- a/go/pserver/cclient/test/CMakeLists.txt +++ b/go/pserver/cclient/test/CMakeLists.txt @@ -1,7 +1,5 @@ cmake_minimum_required(VERSION 3.0) -add_executable(main main.c) -add_dependencies(main paddle_pserver_cclient) add_executable(test_cclient test_cclient.c) add_dependencies(test_cclient paddle_pserver_cclient) @@ -13,10 +11,8 @@ endif() if(PROJ_ROOT) include_directories(${CMAKE_CURRENT_BINARY_DIR}/..) - target_link_libraries(main ${CMAKE_CURRENT_BINARY_DIR}/../libpaddle_pserver_cclient.a pthread) target_link_libraries(test_cclient ${CMAKE_CURRENT_BINARY_DIR}/../libpaddle_pserver_cclient.a pthread) else(PROJ_ROOT) include_directories(${CMAKE_BINARY_DIR}) - target_link_libraries(main ${CMAKE_BINARY_DIR}/libpaddle_pserver_cclient.a pthread) target_link_libraries(test_cclient ${CMAKE_BINARY_DIR}/libpaddle_pserver_cclient.a pthread) endif(PROJ_ROOT) diff --git a/go/pserver/cclient/test/main.c b/go/pserver/cclient/test/main.c deleted file mode 100644 index 7d26127b600..00000000000 --- a/go/pserver/cclient/test/main.c +++ /dev/null @@ -1,104 +0,0 @@ -#include -#include - -#include "libpaddle_pserver_cclient.h" - -// TODO(helin): Fix: gtest using cmake is not working, using this -// hacky way for now. -#define fail() \ - fprintf(stderr, "info: %s:%d: ", __FILE__, __LINE__); \ - exit(-1); - -void sendGrads(paddle_pserver_client c) { - unsigned char grad_a[2000] = {2}; - unsigned char grad_b[3000] = {3}; - paddle_gradient grad1 = { - "param_a", PADDLE_ELEMENT_TYPE_FLOAT32, grad_a, 2000}; - paddle_gradient grad2 = { - "param_b", PADDLE_ELEMENT_TYPE_FLOAT32, grad_b, 3000}; - paddle_gradient* grads[2] = {&grad1, &grad2}; - if (paddle_send_grads(c, grads, 2)) { - fail(); - } -} - -void getParams(paddle_pserver_client c) { - paddle_parameter param_a; - paddle_parameter param_b; - char name_a[] = "param_a"; - char name_b[] = "param_b"; - // Must pre-allocate the prameter content before calling paddle_get_params. - unsigned char content_a[2000] = {}; - unsigned char content_b[3000] = {}; - param_a.element_type = PADDLE_ELEMENT_TYPE_FLOAT32; - param_a.name = name_a; - param_a.content = content_a; - param_a.content_len = 2000; - param_b.element_type = PADDLE_ELEMENT_TYPE_FLOAT32; - param_b.name = name_b; - param_b.content = content_b; - param_b.content_len = 3000; - - paddle_parameter* params[2] = {¶m_a, ¶m_b}; - if (paddle_get_params(c, params, 2)) { - fail(); - } -} - - - -int main() { - char addr[] = "localhost:3000"; - paddle_pserver_client c = paddle_new_pserver_client(addr, 1); - char config_proto[1024]; - size_t config_proto_len = 0; - ssize_t nread; - FILE *fp = fopen("optimizer.pb.txt", "r"); - if(!fp) { fail(); } - while((nread = getline(&config_proto, &config_proto_len, fp)) != -1) { - printf("%s", config_proto); - } - fclose(fp); -retry: - if (paddle_begin_init_params(c)) { - paddle_parameter param; - char name_a[] = "param_a"; - char name_b[] = "param_b"; - unsigned char content_a[2000] = {1}; - unsigned char content_b[3000] = {0}; - param.element_type = PADDLE_ELEMENT_TYPE_FLOAT32; - param.name = name_a; - param.content = content_a; - param.content_len = 2000; - int error = paddle_init_param(c, param, config_proto, config_proto_len); - if (error != 0) { - goto retry; - } - - param.element_type = PADDLE_ELEMENT_TYPE_FLOAT32; - param.name = name_b; - param.content = content_b; - param.content_len = 3000; - error = paddle_init_param(c, param, NULL, 0); - if (error != 0) { - goto retry; - } - - error = paddle_finish_init_params(c); - if (error != 0) { - goto retry; - } - } - - int i; - for (i = 0; i < 100; i++) { - sendGrads(c); - getParams(c); - } - - if (paddle_save_model(c, "/tmp/")) { - fail(); - } - - return 0; -} diff --git a/go/pserver/cclient/test/test_cclient.c b/go/pserver/cclient/test/test_cclient.c index 0f9c2ef8011..7d26127b600 100644 --- a/go/pserver/cclient/test/test_cclient.c +++ b/go/pserver/cclient/test/test_cclient.c @@ -3,113 +3,100 @@ #include "libpaddle_pserver_cclient.h" -typedef float real; - -void fail() { - // TODO(helin): fix: gtest using cmake is not working, using this - // hacky way for now. - printf("test failed.\n"); +// TODO(helin): Fix: gtest using cmake is not working, using this +// hacky way for now. +#define fail() \ + fprintf(stderr, "info: %s:%d: ", __FILE__, __LINE__); \ exit(-1); + +void sendGrads(paddle_pserver_client c) { + unsigned char grad_a[2000] = {2}; + unsigned char grad_b[3000] = {3}; + paddle_gradient grad1 = { + "param_a", PADDLE_ELEMENT_TYPE_FLOAT32, grad_a, 2000}; + paddle_gradient grad2 = { + "param_b", PADDLE_ELEMENT_TYPE_FLOAT32, grad_b, 3000}; + paddle_gradient* grads[2] = {&grad1, &grad2}; + if (paddle_send_grads(c, grads, 2)) { + fail(); + } } -void print_parameter(paddle_gradient* param) { - if (param == NULL) { - printf("param is NULL!!\n"); - } else { - printf("==== parameter ====\n"); - printf("name: %s\n", param->name); - printf("content_len: %d\n", param->content_len); - printf("content_type: %d\n", param->element_type); - int i; - for (i = 0; i < param->content_len / (int)sizeof(real); ++i) { - printf("%f ", ((float*)param->content)[i]); - } - printf("\n\n"); +void getParams(paddle_pserver_client c) { + paddle_parameter param_a; + paddle_parameter param_b; + char name_a[] = "param_a"; + char name_b[] = "param_b"; + // Must pre-allocate the prameter content before calling paddle_get_params. + unsigned char content_a[2000] = {}; + unsigned char content_b[3000] = {}; + param_a.element_type = PADDLE_ELEMENT_TYPE_FLOAT32; + param_a.name = name_a; + param_a.content = content_a; + param_a.content_len = 2000; + param_b.element_type = PADDLE_ELEMENT_TYPE_FLOAT32; + param_b.name = name_b; + param_b.content = content_b; + param_b.content_len = 3000; + + paddle_parameter* params[2] = {¶m_a, ¶m_b}; + if (paddle_get_params(c, params, 2)) { + fail(); } } + + int main() { char addr[] = "localhost:3000"; paddle_pserver_client c = paddle_new_pserver_client(addr, 1); - - char* names[] = {"param_a", "param_b"}; - + char config_proto[1024]; + size_t config_proto_len = 0; + ssize_t nread; + FILE *fp = fopen("optimizer.pb.txt", "r"); + if(!fp) { fail(); } + while((nread = getline(&config_proto, &config_proto_len, fp)) != -1) { + printf("%s", config_proto); + } + fclose(fp); retry: - printf("init parameter to pserver:\n"); - - real param_content1[] = {0.1, 0.2, 0.3}; - real param_content2[] = {0.4, 0.5, 0.6}; - paddle_parameter** params = - (paddle_parameter**)malloc(sizeof(paddle_parameter*) * 2); - params[0] = (paddle_parameter*)malloc(sizeof(paddle_parameter)); - params[0]->name = names[0]; - params[0]->content = (unsigned char*)param_content1; - params[0]->content_len = 3 * sizeof(real); - params[0]->element_type = PADDLE_ELEMENT_TYPE_FLOAT32; - - params[1] = (paddle_parameter*)malloc(sizeof(paddle_parameter)); - params[1]->name = names[1]; - params[1]->content = (unsigned char*)param_content2; - params[1]->content_len = 3 * sizeof(real); - params[1]->element_type = PADDLE_ELEMENT_TYPE_INT32; - if (paddle_begin_init_params(c)) { - if (paddle_init_param(c, *params[0], NULL, 0) != 0) { + paddle_parameter param; + char name_a[] = "param_a"; + char name_b[] = "param_b"; + unsigned char content_a[2000] = {1}; + unsigned char content_b[3000] = {0}; + param.element_type = PADDLE_ELEMENT_TYPE_FLOAT32; + param.name = name_a; + param.content = content_a; + param.content_len = 2000; + int error = paddle_init_param(c, param, config_proto, config_proto_len); + if (error != 0) { goto retry; } - if (paddle_init_param(c, *params[1], NULL, 0) != 0) { + + param.element_type = PADDLE_ELEMENT_TYPE_FLOAT32; + param.name = name_b; + param.content = content_b; + param.content_len = 3000; + error = paddle_init_param(c, param, NULL, 0); + if (error != 0) { goto retry; } - if (paddle_finish_init_params(c) != 0) { + + error = paddle_finish_init_params(c); + if (error != 0) { goto retry; } - } else { - fail(); - } - - printf("get inited parameters from pserver:\n"); - // get parameters again by reusing the allocated parameter buffers. - if (paddle_get_params(c, params, 2) != 0) { - fail(); - } - print_parameter(params[0]); - print_parameter(params[1]); - - printf("send gradient to pserver:\n"); - real gradient_content1[] = {0.01, 0.02, 0.03}; - real gradinet_content2[] = {0.04, 0.05, 0.06}; - - paddle_gradient** grads = - (paddle_gradient**)malloc(sizeof(paddle_gradient*) * 2); - grads[0] = (paddle_gradient*)malloc(sizeof(paddle_gradient)); - grads[0]->name = names[0]; - grads[0]->content = (unsigned char*)gradient_content1; - grads[0]->content_len = 3 * sizeof(real); - grads[0]->element_type = PADDLE_ELEMENT_TYPE_FLOAT32; - - grads[1] = (paddle_gradient*)malloc(sizeof(paddle_gradient)); - grads[1]->name = names[1]; - grads[1]->content = (unsigned char*)gradinet_content2; - grads[1]->content_len = 3 * sizeof(real); - grads[1]->element_type = PADDLE_ELEMENT_TYPE_INT32; - - printf("print gradient sent to pserver:\n"); - print_parameter(grads[0]); - print_parameter(grads[1]); - - if (paddle_send_grads(c, grads, 2) != 0) { - fail(); } - printf("get updated parameters from pserver:\n"); - // get parameters again by reusing the allocated parameter buffers. - if (paddle_get_params(c, params, 2) != 0) { - fail(); + int i; + for (i = 0; i < 100; i++) { + sendGrads(c); + getParams(c); } - print_parameter(params[0]); - print_parameter(params[1]); - if (paddle_save_model(c, "/tmp/") != 0) { + if (paddle_save_model(c, "/tmp/")) { fail(); } diff --git a/go/pserver/optimizer.go b/go/pserver/optimizer.go index 3ee4c746528..df2219aa84d 100644 --- a/go/pserver/optimizer.go +++ b/go/pserver/optimizer.go @@ -1,7 +1,10 @@ package pserver /* +// TODO(zhihong): move compile flags to cmake go_library +#cgo pkg-config: protobuf #cgo CFLAGS: -I ../../ +#cgo LDFLAGS: ../../build/paddle/optimizer/libpaddle_optimizer.a ../../build/proto/libpaddle_proto.a ../../third_party/install/glog/lib/libglog.a ../../third_party/install/gtest/lib/libgtest.a ../../third_party/install/gflags/lib/libgflags.a ../../third_party/install/openblas/lib/libopenblas.a -I/usr/local/lib/ -lprotobuf #include "paddle/optimizer/optimizer.h" */ import "C" -- GitLab From 8cfa48dc88c0c702b30094ca558bf2182e00faba Mon Sep 17 00:00:00 2001 From: Yi Wang Date: Thu, 22 Jun 2017 10:27:36 -0700 Subject: [PATCH 0769/3256] Move README.md from paddle/ to paddle/memory/ --- paddle/{ => memory}/README.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename paddle/{ => memory}/README.md (100%) diff --git a/paddle/README.md b/paddle/memory/README.md similarity index 100% rename from paddle/README.md rename to paddle/memory/README.md -- GitLab From c617520776c58791d77d1382eba67ac4264916f0 Mon Sep 17 00:00:00 2001 From: Yi Wang Date: Thu, 22 Jun 2017 10:35:52 -0700 Subject: [PATCH 0770/3256] In response to comments from Liao Gang and Yu Yang --- paddle/memory/README.md | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/paddle/memory/README.md b/paddle/memory/README.md index 24af37987e0..b71ca296965 100644 --- a/paddle/memory/README.md +++ b/paddle/memory/README.md @@ -25,14 +25,16 @@ cout << memory::Used(pl); memory::Free(pl, p); ``` -### The API +### API In `paddle/memory/memory.h` we have: ```cpp -template void* Alloc(Place, size_t); -template void Free(Place, void*); -} +namespace memory { +template void* Alloc(Place, size_t); +template void Free(Place, void*); +template void Used(Place); +} // namespace memory ``` These function templates have specializations on either `platform::CPUPlace` or `platform::GPUPlace`: @@ -48,12 +50,14 @@ and ```cpp template<> -void Alloc(GPUPlace)(GPUPlace p, size_t size) { +void Alloc(GPUPlace p, size_t size) { return GetGPUBuddyAllocator(p.id)->Alloc(size); } ``` -### The Implementation +Similar specializations exist for `Free` and `Used`. + +### Implementation `GetCPUBuddyAllocator` and `GetGPUBuddyAllocator` are singletions. @@ -94,7 +98,7 @@ class BuddyAllocator { private: struct Block { size_t size; - Blobk* left, right; + Block* left, right; }; ... }; @@ -102,15 +106,15 @@ class BuddyAllocator { #### System Allocators -The `GPUAllocator` and `CPUAllocator` are calls *system allocators*. They hold information about the device, including the amount of memory has been allocated. So that we can call +The `GPUAllocator` and `CPUAllocator` are calls *system allocators*. They work as the fallback allocators of `BuddyAllocator`. A system allocator holds information about a device, including the amount of memory has been allocated, so we can call -- `GPUAllocator::Used` and -- `CPUAllocator::Used` +- `GPUAllocator::Used()` and +- `CPUAllocator::Used()` to get the amount of memory that has been allocated so far. -## Why Such a Design +## Justification I got inspiration from Majel and Caffe2, though above design look different from both. -- GitLab From e558ed0d5eade5d2bf6a1bb37beeb39486e9dd76 Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Fri, 23 Jun 2017 01:48:04 +0000 Subject: [PATCH 0771/3256] fix etcd lease I made a comment in WuYi's PR that this is not necessary, so WuYi removed it. Turns out it's necessary after confirming with coreOS developer. --- go/pserver/service.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/go/pserver/service.go b/go/pserver/service.go index 7400b488325..7e2b841dd8e 100644 --- a/go/pserver/service.go +++ b/go/pserver/service.go @@ -159,11 +159,18 @@ func (s *Service) registerPserverEtcd(ctx context.Context) (*clientv3.TxnRespons // find the first id and write info c.Put(psKey, s.externalIP, clientv3.WithLease(resp.ID)) log.Debugf("set pserver node %s with value %s", psKey, s.externalIP) - _, kaerr := s.etcdClient.KeepAlive(context.TODO(), resp.ID) + ch, kaerr := s.etcdClient.KeepAlive(context.TODO(), resp.ID) if kaerr != nil { log.Errorf("keepalive etcd node error: %v", kaerr) return kaerr } + + // Eat the keep alive message so etcd + // will not expire the lease. + go func(ch <-chan *clientv3.LeaseKeepAliveResponse) { + ka := <-ch + log.Debugf("keepalive: %d\n", ka.TTL) + }(ch) log.Debug("register finished") registered = true break -- GitLab From c2fc896f5b2896fc6509e720e7dc08527495927f Mon Sep 17 00:00:00 2001 From: Yi Wang Date: Thu, 22 Jun 2017 19:05:28 -0700 Subject: [PATCH 0772/3256] Simplify Travis CI configuration --- .travis.yml | 2 -- paddle/scripts/travis/build_and_test.sh | 12 ------------ paddle/scripts/travis/{docs.sh => build_doc.sh} | 13 ++++++++----- .../scripts/travis/{precommit.sh => check_style.sh} | 8 ++++---- paddle/scripts/travis/main.sh | 12 +++++------- 5 files changed, 17 insertions(+), 30 deletions(-) delete mode 100755 paddle/scripts/travis/build_and_test.sh rename paddle/scripts/travis/{docs.sh => build_doc.sh} (84%) rename paddle/scripts/travis/{precommit.sh => check_style.sh} (54%) diff --git a/.travis.yml b/.travis.yml index 87cef10b2b1..915c23b7ab5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,6 @@ group: deprecated-2017Q2 language: cpp cache: directories: - - $HOME/third_party - $HOME/.ccache - $HOME/.cache/pip sudo: required @@ -18,7 +17,6 @@ addons: packages: - gcc-4.8 - g++-4.8 - - gfortran-4.8 - git - build-essential - python diff --git a/paddle/scripts/travis/build_and_test.sh b/paddle/scripts/travis/build_and_test.sh deleted file mode 100755 index f2cbc561652..00000000000 --- a/paddle/scripts/travis/build_and_test.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash -source ./common.sh - -NPROC=1 -export PYTHONPATH=/opt/python/2.7.12/lib/python2.7/site-packages -export PYTHONHOME=/opt/python/2.7.12 -export PATH=/opt/python/2.7.12/bin:${PATH} -cmake .. -DCMAKE_Fortran_COMPILER=/usr/bin/gfortran-4.8 -DON_TRAVIS=ON -DWITH_COVERAGE=ON -DCOVERALLS_UPLOAD=ON ${EXTRA_CMAKE_OPTS} -NRPOC=`nproc` -make -j $NPROC -make coveralls -sudo make install diff --git a/paddle/scripts/travis/docs.sh b/paddle/scripts/travis/build_doc.sh similarity index 84% rename from paddle/scripts/travis/docs.sh rename to paddle/scripts/travis/build_doc.sh index c784293695b..88264d8c262 100755 --- a/paddle/scripts/travis/docs.sh +++ b/paddle/scripts/travis/build_doc.sh @@ -1,15 +1,18 @@ #!/bin/bash +set -e + +# Create the build directory for CMake. +mkdir -p $TRAVIS_BUILD_DIR/build +cd $TRAVIS_BUILD_DIR/build -# Add set -e, cd to directory. -source ./common.sh # Compile Documentation only. -cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_Fortran_COMPILER=/usr/bin/gfortran-4.8 -DWITH_GPU=OFF -DWITH_DOC=OFF -DWITH_STYLE_CHECK=OFF ${EXTRA_CMAKE_OPTS} +cmake .. -DCMAKE_BUILD_TYPE=Debug -DWITH_GPU=OFF -DWITH_DOC=OFF -DWITH_STYLE_CHECK=OFF mkdir output make -j `nproc` find .. -name '*whl' | xargs pip install # install all wheels. rm -rf * -cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_Fortran_COMPILER=/usr/bin/gfortran-4.8 -DWITH_GPU=OFF -DWITH_DOC=ON ${EXTRA_CMAKE_OPTS} -make paddle_docs paddle_docs_cn +cmake .. -DCMAKE_BUILD_TYPE=Debug -DWITH_GPU=OFF -DWITH_DOC=ON +make -j `nproc` paddle_docs paddle_docs_cn # check websites for broken links linkchecker doc/en/html/index.html diff --git a/paddle/scripts/travis/precommit.sh b/paddle/scripts/travis/check_style.sh similarity index 54% rename from paddle/scripts/travis/precommit.sh rename to paddle/scripts/travis/check_style.sh index 7a59b1131d0..4754bdd4c80 100755 --- a/paddle/scripts/travis/precommit.sh +++ b/paddle/scripts/travis/check_style.sh @@ -1,14 +1,14 @@ #!/bin/bash function abort(){ - echo "Your commit not fit PaddlePaddle code style" 1>&2 - echo "Please use pre-commit scripts to auto-format your code" 1>&2 + echo "Your change doesn't follow PaddlePaddle's code style." 1>&2 + echo "Please use pre-commit to reformat your code and git push again." 1>&2 exit 1 } trap 'abort' 0 set -e -source common.sh -cd .. + +cd $TRAVIS_BUILD_DIR export PATH=/usr/bin:$PATH pre-commit install clang-format --version diff --git a/paddle/scripts/travis/main.sh b/paddle/scripts/travis/main.sh index 13f2552d29d..30afe60f608 100755 --- a/paddle/scripts/travis/main.sh +++ b/paddle/scripts/travis/main.sh @@ -1,13 +1,11 @@ #!/bin/bash cd `dirname $0` -if [ ${JOB} == "BUILD_AND_TEST" ]; then - ./build_and_test.sh -elif [ ${JOB} == "DOCS" ]; then - ./docs.sh +if [ ${JOB} == "DOCS" ]; then + ./build_doc.sh elif [ ${JOB} == "PRE_COMMIT" ]; then - ./precommit.sh + ./check_style.sh else - echo Unknown job ${JOB} - exit 1 + echo "Unknown Travis CI job: ${JOB}" + exit 0 # Don't fail due to unknown Travis CI job. fi -- GitLab From 7cf640b58ddeb2cc91d027ade8a6f326d42b5a8d Mon Sep 17 00:00:00 2001 From: Peng Li Date: Fri, 23 Jun 2017 10:26:46 +0800 Subject: [PATCH 0773/3256] add coeff parameter to classification_cost --- python/paddle/trainer_config_helpers/layers.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/python/paddle/trainer_config_helpers/layers.py b/python/paddle/trainer_config_helpers/layers.py index b8ce0373c0e..84ed1607730 100755 --- a/python/paddle/trainer_config_helpers/layers.py +++ b/python/paddle/trainer_config_helpers/layers.py @@ -3839,7 +3839,8 @@ def classification_cost(input, weight=None, name=None, evaluator=classification_error_evaluator, - layer_attr=None): + layer_attr=None, + coeff=1.): """ classification cost Layer. @@ -3855,6 +3856,8 @@ def classification_cost(input, :param evaluator: Evaluator method. :param layer_attr: layer's extra attribute. :type layer_attr: ExtraLayerAttribute + :param coeff: The coefficient affects the gradient in the backward. + :type coeff: float :return: LayerOutput object. :rtype: LayerOutput """ @@ -3868,6 +3871,7 @@ def classification_cost(input, name=name, type="multi-class-cross-entropy", inputs=ipts, + coeff=coeff, **ExtraLayerAttribute.to_kwargs(layer_attr)) def __add_evaluator__(e): -- GitLab From fba4649bcac265ce720fc8e71f0625f228ad2812 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Fri, 23 Jun 2017 10:31:21 +0800 Subject: [PATCH 0774/3256] Remove `BUILD_AND_TEST` section in travis.yaml --- .travis.yml | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/.travis.yml b/.travis.yml index 915c23b7ab5..6b4fb4c4b65 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,7 +10,6 @@ os: - linux env: - JOB=DOCS - - JOB=BUILD_AND_TEST - JOB=PRE_COMMIT addons: apt: @@ -33,17 +32,6 @@ addons: - libtool - ccache before_install: - - | - if [ ${JOB} == "BUILD_AND_TEST" ]; then - local change_list=`git diff --name-only $TRAVIS_COMMIT_RANGE` - if [ $? -eq 0 ]; then # if git diff return no zero, then rerun unit test. - if ! echo ${change_list} | grep -qvE '(\.md$)|(\.rst$)|(\.jpg$)|(\.png$)' - then - echo "Only markdown docs were updated, stopping build process." - exit - fi - fi - fi - if [[ "$JOB" == "PRE_COMMIT" ]]; then sudo ln -s /usr/bin/clang-format-3.8 /usr/bin/clang-format; fi # Paddle is using protobuf 3.1 currently. Protobuf 3.2 breaks the compatibility. So we specify the python # protobuf version. -- GitLab From 260416559264c7a8d4dc63cd79619752a862cdf4 Mon Sep 17 00:00:00 2001 From: dongzhihong Date: Fri, 23 Jun 2017 10:37:19 +0800 Subject: [PATCH 0775/3256] "resolve clock skewed" --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 39af60966b6..bf227737c5a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -25,7 +25,7 @@ COPY ./paddle/scripts/docker/root/ /root/ RUN apt-get update && \ apt-get install -y \ git python-pip python-dev openssh-server bison \ - wget unzip tar xz-utils bzip2 gzip coreutils \ + wget unzip tar xz-utils bzip2 gzip coreutils ntp \ curl sed grep graphviz libjpeg-dev zlib1g-dev \ python-numpy python-matplotlib gcc g++ \ automake locales clang-format-3.8 swig doxygen cmake \ -- GitLab From fdde4eff0da95a170f2f727a8345057f20be09ef Mon Sep 17 00:00:00 2001 From: zlx Date: Fri, 23 Jun 2017 12:00:45 +0800 Subject: [PATCH 0776/3256] modify some topo --- paddle/parameter/ParameterUpdaterHook.cpp | 2 +- python/paddle/trainer_config_helpers/attrs.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/paddle/parameter/ParameterUpdaterHook.cpp b/paddle/parameter/ParameterUpdaterHook.cpp index 968803fc0f0..c8b47687f5d 100644 --- a/paddle/parameter/ParameterUpdaterHook.cpp +++ b/paddle/parameter/ParameterUpdaterHook.cpp @@ -32,7 +32,7 @@ namespace paddle { /** * The static pruning hook * Static means user specify a sparsity_ratio before training started, and the - * network will prune the parameters based on the sparsity_ratio. More deatils + * network will prune the parameters based on the sparsity_ratio. More details * can be found https://arxiv.org/pdf/1506.02626.pdf. */ diff --git a/python/paddle/trainer_config_helpers/attrs.py b/python/paddle/trainer_config_helpers/attrs.py index 66163bdc8dc..c02306f3945 100644 --- a/python/paddle/trainer_config_helpers/attrs.py +++ b/python/paddle/trainer_config_helpers/attrs.py @@ -84,7 +84,7 @@ class HookAttribute(object): assert is_compatible_with( self.sparsity_ratio, float), 'sparisity_ratio must be float type' - assert self.sparsity_ratio <= 1 and self.sparsity_ratio >= 0, 'sparisity_ratio must be a float between [0, 1] ' + assert self.sparsity_ratio <= 1 and self.sparsity_ratio >= 0, 'sparsity_ratio must be a float between [0, 1] ' def __call__(self): return ParameterHook(self.type, sparsity_ratio=self.sparsity_ratio) -- GitLab From 72c1a7fb5e2871ba3f6384ea28eaeed10aa5e76a Mon Sep 17 00:00:00 2001 From: Yi Wang Date: Thu, 22 Jun 2017 21:06:07 -0700 Subject: [PATCH 0777/3256] Remove common.sh --- paddle/scripts/travis/common.sh | 6 ------ 1 file changed, 6 deletions(-) delete mode 100755 paddle/scripts/travis/common.sh diff --git a/paddle/scripts/travis/common.sh b/paddle/scripts/travis/common.sh deleted file mode 100755 index f05c7530a3b..00000000000 --- a/paddle/scripts/travis/common.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/bash -set -e -mkdir -p ../../../build -cd ../../../build -mkdir -p $HOME/third_party -EXTRA_CMAKE_OPTS="-DTHIRD_PARTY_PATH=${HOME}/third_party" -- GitLab From 0cbe120d8c06a1c064293918986264cb320bdb78 Mon Sep 17 00:00:00 2001 From: Yi Wang Date: Thu, 22 Jun 2017 21:16:07 -0700 Subject: [PATCH 0778/3256] Remove paddle/script/travis/main.sh --- .travis.yml | 10 ++++------ paddle/scripts/travis/main.sh | 11 ----------- 2 files changed, 4 insertions(+), 17 deletions(-) delete mode 100755 paddle/scripts/travis/main.sh diff --git a/.travis.yml b/.travis.yml index 6b4fb4c4b65..2c46da71e75 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,8 +9,8 @@ dist: trusty os: - linux env: - - JOB=DOCS - - JOB=PRE_COMMIT + - JOB=build_doc + - JOB=check_style addons: apt: packages: @@ -32,7 +32,7 @@ addons: - libtool - ccache before_install: - - if [[ "$JOB" == "PRE_COMMIT" ]]; then sudo ln -s /usr/bin/clang-format-3.8 /usr/bin/clang-format; fi + - if [[ "$JOB" == "check_style" ]]; then sudo ln -s /usr/bin/clang-format-3.8 /usr/bin/clang-format; fi # Paddle is using protobuf 3.1 currently. Protobuf 3.2 breaks the compatibility. So we specify the python # protobuf version. - pip install numpy wheel 'protobuf==3.1' sphinx==1.5.6 recommonmark sphinx-rtd-theme==0.1.9 virtualenv pre-commit requests==2.9.2 LinkChecker @@ -41,9 +41,7 @@ before_install: - | function timeout() { perl -e 'alarm shift; exec @ARGV' "$@"; } script: - - | - timeout 2580 paddle/scripts/travis/main.sh # 43min timeout - RESULT=$?; if [ $RESULT -eq 0 ] || [ $RESULT -eq 142 ]; then true; else false; fi; + - paddle/scripts/travis/$JOB.sh notifications: email: on_success: change diff --git a/paddle/scripts/travis/main.sh b/paddle/scripts/travis/main.sh deleted file mode 100755 index 30afe60f608..00000000000 --- a/paddle/scripts/travis/main.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/bash -cd `dirname $0` - -if [ ${JOB} == "DOCS" ]; then - ./build_doc.sh -elif [ ${JOB} == "PRE_COMMIT" ]; then - ./check_style.sh -else - echo "Unknown Travis CI job: ${JOB}" - exit 0 # Don't fail due to unknown Travis CI job. -fi -- GitLab From 1d6b8595490d0d679a18329eccfa53d8bb285b96 Mon Sep 17 00:00:00 2001 From: zlx Date: Fri, 23 Jun 2017 13:11:31 +0800 Subject: [PATCH 0779/3256] modity topo --- python/paddle/trainer_config_helpers/attrs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/paddle/trainer_config_helpers/attrs.py b/python/paddle/trainer_config_helpers/attrs.py index c02306f3945..9b9f979bb61 100644 --- a/python/paddle/trainer_config_helpers/attrs.py +++ b/python/paddle/trainer_config_helpers/attrs.py @@ -68,7 +68,7 @@ class HookAttribute(object): The specific usage can be paddle.layer.img_conv(input=img, filter_size=3, num_channels=3, num_filters=64, param_attr=ParameterAttribute(update_hooks=hk) ) - The pruning deatils can be found https://arxiv.org/pdf/1506.02626.pdf + 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', -- GitLab From 85e42cbeaa105b34fd3dba864ba75b95adcfe73f Mon Sep 17 00:00:00 2001 From: liaogang Date: Fri, 23 Jun 2017 15:16:37 +0800 Subject: [PATCH 0780/3256] ENH: supoort commnad `make target_name` --- cmake/generic.cmake | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/cmake/generic.cmake b/cmake/generic.cmake index 69e8164a00d..19f0db52737 100644 --- a/cmake/generic.cmake +++ b/cmake/generic.cmake @@ -97,15 +97,12 @@ function(merge_static_libs TARGET_NAME) endforeach() if(APPLE) # Use OSX's libtool to merge archives - add_custom_target(${TARGET_NAME}_archive - COMMAND libtool -static -o "${CMAKE_CURRENT_BINARY_DIR}/lib${TARGET_NAME}.a" ${libfiles} - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} - DEPENDS ${libs} - ) - add_library(${TARGET_NAME} STATIC IMPORTED GLOBAL) - set_property(TARGET ${TARGET_NAME} PROPERTY - IMPORTED_LOCATION "${CMAKE_CURRENT_BINARY_DIR}/lib${TARGET_NAME}.a") - add_dependencies(${TARGET_NAME} ${TARGET_NAME}_archive) + set(dummyfile ${CMAKE_CURRENT_BINARY_DIR}/${TARGET_NAME}_dummy.c) + file(WRITE ${dummyfile} "const char * dummy = \"${dummyfile}\";") + add_library(${TARGET_NAME} STATIC ${dummyfile}) + add_custom_command(TARGET ${TARGET_NAME} POST_BUILD + COMMAND rm "${CMAKE_CURRENT_BINARY_DIR}/lib${TARGET_NAME}.a" + COMMAND /usr/bin/libtool -static -o "${CMAKE_CURRENT_BINARY_DIR}/lib${TARGET_NAME}.a" ${libfiles}) else() # general UNIX: use "ar" to extract objects and re-add to a common lib foreach(lib ${libs}) set(objlistfile ${lib}.objlist) # list of objects in the input library -- GitLab From c89fe83a775b0c8264f00de589d263fc6faec615 Mon Sep 17 00:00:00 2001 From: Liu Yiqun Date: Fri, 23 Jun 2017 16:32:05 +0800 Subject: [PATCH 0781/3256] Fix the problem that protobuf cannot be used as a DEPS argument in cc_library. --- cmake/external/protobuf.cmake | 61 ++++++++++++++++++++++++++++------- 1 file changed, 49 insertions(+), 12 deletions(-) diff --git a/cmake/external/protobuf.cmake b/cmake/external/protobuf.cmake index 7340394b1e1..ce32b2531e1 100644 --- a/cmake/external/protobuf.cmake +++ b/cmake/external/protobuf.cmake @@ -14,11 +14,41 @@ INCLUDE(ExternalProject) +# Print and set the protobuf library information, +# finish this cmake process and exit from this file. macro(PROMPT_PROTOBUF_LIB) + SET(protobuf_DEPS ${ARGN}) + MESSAGE(STATUS "Protobuf protoc executable: ${PROTOBUF_PROTOC_EXECUTABLE}") MESSAGE(STATUS "Protobuf library: ${PROTOBUF_LIBRARY}") MESSAGE(STATUS "Protobuf version: ${PROTOBUF_VERSION}") INCLUDE_DIRECTORIES(${PROTOBUF_INCLUDE_DIR}) + + # Assuming that all the protobuf libraries are of the same type. + IF(${PROTOBUF_LIBRARY} MATCHES "${STATIC_LIBRARY_SUFFIX}$") + SET(protobuf_LIBTYPE STATIC) + ELSEIF(${PROTOBUF_LIBRARY} MATCHES "${DYNAMIC_LIBRARY_SUFFIX}$") + SET(protobuf_LIBTYPE SHARED) + ELSE() + MESSAGE(FATAL_ERROR "Unknown library type: ${PROTOBUF_LIBRARY}") + ENDIF() + + ADD_LIBRARY(protobuf ${protobuf_LIBTYPE} IMPORTED GLOBAL) + SET_PROPERTY(TARGET protobuf PROPERTY IMPORTED_LOCATION ${PROTOBUF_LIBRARY}) + + ADD_LIBRARY(protobuf_lite ${protobuf_LIBTYPE} IMPORTED GLOBAL) + SET_PROPERTY(TARGET protobuf_lite PROPERTY IMPORTED_LOCATION ${PROTOBUF_LITE_LIBRARY}) + + ADD_LIBRARY(protoc ${protobuf_LIBTYPE} IMPORTED GLOBAL) + SET_PROPERTY(TARGET protoc PROPERTY IMPORTED_LOCATION ${PROTOC_LIBRARY}) + + FOREACH(dep ${protobuf_DEPS}) + ADD_DEPENDENCIES(protobuf ${dep}) + ADD_DEPENDENCIES(protobuf_lite ${dep}) + ADD_DEPENDENCIES(protoc ${dep}) + ENDFOREACH() + + LIST(APPEND external_project_dependencies protobuf) RETURN() endmacro() macro(SET_PROTOBUF_VERSION) @@ -43,8 +73,9 @@ if (NOT "${PROTOBUF_ROOT}" STREQUAL "") endif() FUNCTION(build_protobuf TARGET_NAME BUILD_FOR_HOST) - SET(PROTOBUF_SOURCES_DIR ${THIRD_PARTY_PATH}/${TARGET_NAME}) - SET(PROTOBUF_INSTALL_DIR ${THIRD_PARTY_PATH}/install/${TARGET_NAME}) + STRING(REPLACE "extern_" "" TARGET_DIR_NAME "${TARGET_NAME}") + SET(PROTOBUF_SOURCES_DIR ${THIRD_PARTY_PATH}/${TARGET_DIR_NAME}) + SET(PROTOBUF_INSTALL_DIR ${THIRD_PARTY_PATH}/install/${TARGET_DIR_NAME}) SET(${TARGET_NAME}_INCLUDE_DIR "${PROTOBUF_INSTALL_DIR}/include" PARENT_SCOPE) SET(PROTOBUF_INCLUDE_DIR "${PROTOBUF_INSTALL_DIR}/include" PARENT_SCOPE) @@ -109,6 +140,8 @@ IF(NOT CMAKE_CROSSCOMPILING) SET_PROTOBUF_VERSION() IF("${PROTOBUF_VERSION}" VERSION_LESS "3.1.0") SET(PROTOBUF_FOUND OFF) + ELSE() + PROMPT_PROTOBUF_LIB() ENDIF() ENDIF(PROTOBUF_FOUND) ELSE() @@ -120,18 +153,22 @@ ELSE() ENDIF() IF(NOT PROTOBUF_FOUND) - build_protobuf(protobuf FALSE) - LIST(APPEND external_project_dependencies protobuf) + build_protobuf(extern_protobuf FALSE) - SET(PROTOBUF_INCLUDE_DIR ${protobuf_INCLUDE_DIR} + SET(PROTOBUF_INCLUDE_DIR ${extern_protobuf_INCLUDE_DIR} CACHE PATH "protobuf include directory." FORCE) - IF(NOT CMAKE_CROSSCOMPILING) - SET(PROTOBUF_PROTOC_EXECUTABLE ${protobuf_PROTOC_EXECUTABLE} + SET(PROTOBUF_LITE_LIBRARY ${extern_protobuf_LITE_LIBRARY} + CACHE FILEPATH "protobuf lite library." FORCE) + SET(PROTOBUF_LIBRARY ${extern_protobuf_LIBRARY} + CACHE FILEPATH "protobuf library." FORCE) + SET(PROTOBUF_PROTOC_LIBRARY ${extern_protobuf_PROTOC_LIBRARY} + CACHE FILEPATH "protoc library." FORCE) + + IF(CMAKE_CROSSCOMPILING) + PROMPT_PROTOBUF_LIB(protobuf_host extern_protobuf) + ELSE() + SET(PROTOBUF_PROTOC_EXECUTABLE ${extern_protobuf_PROTOC_EXECUTABLE} CACHE FILEPATH "protobuf executable." FORCE) + PROMPT_PROTOBUF_LIB(extern_protobuf) ENDIF() - SET(PROTOBUF_LITE_LIBRARY ${protobuf_LITE_LIBRARY} CACHE FILEPATH "protobuf lite library." FORCE) - SET(PROTOBUF_LIBRARY ${protobuf_LIBRARY} CACHE FILEPATH "protobuf library." FORCE) - SET(PROTOBUF_PROTOC_LIBRARY ${protobuf_PROTOC_LIBRARY} CACHE FILEPATH "protoc library." FORCE) ENDIF(NOT PROTOBUF_FOUND) - -PROMPT_PROTOBUF_LIB() \ No newline at end of file -- GitLab From 869f2b3861990c430941d1992f9b22a711e89670 Mon Sep 17 00:00:00 2001 From: liaogang Date: Fri, 23 Jun 2017 17:26:49 +0800 Subject: [PATCH 0782/3256] FIX: Polish go library --- cmake/generic.cmake | 34 +++++++++--------- cmake/system.cmake | 1 + go/cmake/CMakeDetermineGoCompiler.cmake | 44 ----------------------- go/cmake/CMakeGoCompiler.cmake.in | 8 ----- go/cmake/CMakeGoInformation.cmake | 7 ---- go/cmake/CMakeTestGoCompiler.cmake | 1 - go/cmake/flags.cmake | 45 ----------------------- go/cmake/golang.cmake | 48 ------------------------- go/pserver/cclient/CMakeLists.txt | 15 +++----- go/pserver/cclient/test/CMakeLists.txt | 23 ++---------- 10 files changed, 24 insertions(+), 202 deletions(-) delete mode 100644 go/cmake/CMakeDetermineGoCompiler.cmake delete mode 100644 go/cmake/CMakeGoCompiler.cmake.in delete mode 100644 go/cmake/CMakeGoInformation.cmake delete mode 100644 go/cmake/CMakeTestGoCompiler.cmake delete mode 100644 go/cmake/flags.cmake delete mode 100644 go/cmake/golang.cmake diff --git a/cmake/generic.cmake b/cmake/generic.cmake index 69e8164a00d..76810432e0b 100644 --- a/cmake/generic.cmake +++ b/cmake/generic.cmake @@ -257,31 +257,31 @@ file(MAKE_DIRECTORY ${GOPATH}) # tensor # Because ops depend on tensor, this line is optional. # ops) function(go_library TARGET_NAME) - set(options OPTIONAL) + set(options STATIC static SHARED shared) set(oneValueArgs "") - set(multiValueArgs SRCS DEPS) + set(multiValueArgs DEPS) cmake_parse_arguments(go_library "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) - if (${go_library_OPTIONAL} STREQUAL "SHARED") + + if (go_library_SHARED OR go_library_shared) set(BUILD_MODE "-buildmode=c-shared") - if(APPLE) - set(LIB_NAME "lib${TARGET_NAME}.dylib") - else() - set(LIB_NAME "lib${TARGET_NAME}.so") - endif() + set(LIB_NAME "${LIBRARY_PREFIX}${TARGET_NAME}${SHARED_LIBRARY_SUFFIX}") else() set(BUILD_MODE "-buildmode=c-archive") - set(LIB_NAME "lib${TARGET_NAME}.a") + set(LIB_NAME "${LIBRARY_PREFIX}${TARGET_NAME}${STATIC_LIBRARY_SUFFIX}") endif() - add_custom_command(OUTPUT ${TARGET_NAME}_timestamp + + set(dummyfile ${CMAKE_CURRENT_BINARY_DIR}/${TARGET_NAME}_dummy.c) + file(WRITE ${dummyfile} "const char * dummy = \"${dummyfile}\";") + add_library(${TARGET_NAME} STATIC ${dummyfile}) + add_dependencies(${TARGET_NAME} ${go_library_DEPS}) + + file(GLOB GO_SOURCE RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "*.go") + add_custom_command(TARGET ${TARGET_NAME} POST_BUILD + COMMAND rm "${CMAKE_CURRENT_BINARY_DIR}/${LIB_NAME}" COMMAND env GOPATH=${GOPATH} ${CMAKE_Go_COMPILER} build ${BUILD_MODE} -o "${CMAKE_CURRENT_BINARY_DIR}/${LIB_NAME}" - ${go_library_SRCS} + ${GO_SOURCE} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) - add_custom_target(${TARGET_NAME}_lib ALL DEPENDS ${TARGET_NAME}_timestamp ${go_library_DEPS}) - add_library(${TARGET_NAME} STATIC IMPORTED) - set_property(TARGET ${TARGET_NAME} PROPERTY - IMPORTED_LOCATION "${CMAKE_CURRENT_BINARY_DIR}/${LIB_NAME}") - add_dependencies(${TARGET_NAME} ${TARGET_NAME}_lib) endfunction(go_library) function(go_binary TARGET_NAME) @@ -316,5 +316,5 @@ endfunction(go_test) # go_extern(target_name extern_source) # go_extern(go_redis github.com/hoisie/redis) function(go_extern TARGET_NAME) - add_custom_target(${TARGET_NAME} env GOPATH=${GOPATH} ${CMAKE_Go_COMPILER} get ${ARGN}) + add_custom_target(${TARGET_NAME} env GOPATH=${GOPATH} ${CMAKE_Go_COMPILER} get -d ${ARGN}) endfunction(go_extern) diff --git a/cmake/system.cmake b/cmake/system.cmake index 904652413e0..40a1b2f67f8 100644 --- a/cmake/system.cmake +++ b/cmake/system.cmake @@ -33,6 +33,7 @@ ELSE(WIN32) SET(CMAKE_OSX_DEPLOYMENT_TARGET ${MACOS_VERSION} CACHE STRING "Minimum OS X version to target for deployment (at runtime); newer APIs weak linked. Set to empty string for default value.") ENDIF() + set(CMAKE_EXE_LINKER_FLAGS "-framework CoreFoundation -framework Security") ELSE(APPLE) IF(EXISTS "/etc/issue") diff --git a/go/cmake/CMakeDetermineGoCompiler.cmake b/go/cmake/CMakeDetermineGoCompiler.cmake deleted file mode 100644 index a9bb6906c74..00000000000 --- a/go/cmake/CMakeDetermineGoCompiler.cmake +++ /dev/null @@ -1,44 +0,0 @@ -if(NOT CMAKE_Go_COMPILER) - if(NOT $ENV{GO_COMPILER} STREQUAL "") - get_filename_component(CMAKE_Go_COMPILER_INIT $ENV{GO_COMPILER} PROGRAM PROGRAM_ARGS CMAKE_Go_FLAGS_ENV_INIT) - - if(CMAKE_Go_FLAGS_ENV_INIT) - set(CMAKE_Go_COMPILER_ARG1 "${CMAKE_Go_FLAGS_ENV_INIT}" CACHE STRING "First argument to Go compiler") - endif() - - if(NOT EXISTS ${CMAKE_Go_COMPILER_INIT}) - message(SEND_ERROR "Could not find compiler set in environment variable GO_COMPILER:\n$ENV{GO_COMPILER}.") - endif() - - endif() - - set(Go_BIN_PATH - $ENV{GOPATH} - $ENV{GOROOT} - $ENV{GOROOT}/../bin - $ENV{GO_COMPILER} - /usr/bin - /usr/local/bin - ) - - if(CMAKE_Go_COMPILER_INIT) - set(CMAKE_Go_COMPILER ${CMAKE_Go_COMPILER_INIT} CACHE PATH "Go Compiler") - else() - find_program(CMAKE_Go_COMPILER - NAMES go - PATHS ${Go_BIN_PATH} - ) - EXEC_PROGRAM(${CMAKE_Go_COMPILER} ARGS version OUTPUT_VARIABLE GOLANG_VERSION) - STRING(REGEX MATCH "go[0-9]+.[0-9]+.[0-9]+[ /A-Za-z0-9]*" VERSION "${GOLANG_VERSION}") - message("-- The Golang compiler identification is ${VERSION}") - message("-- Check for working Golang compiler: ${CMAKE_Go_COMPILER}") - endif() - -endif() - -mark_as_advanced(CMAKE_Go_COMPILER) - -configure_file(${CMAKE_MODULE_PATH}/CMakeGoCompiler.cmake.in - ${CMAKE_PLATFORM_INFO_DIR}/CMakeGoCompiler.cmake @ONLY) - -set(CMAKE_Go_COMPILER_ENV_VAR "GO_COMPILER") diff --git a/go/cmake/CMakeGoCompiler.cmake.in b/go/cmake/CMakeGoCompiler.cmake.in deleted file mode 100644 index a71f08e0646..00000000000 --- a/go/cmake/CMakeGoCompiler.cmake.in +++ /dev/null @@ -1,8 +0,0 @@ -set(CMAKE_Go_COMPILER "@CMAKE_Go_COMPILER@") -set(CMAKE_Go_COMPILER_LOADED 1) - -set(CMAKE_Go_SOURCE_FILE_EXTENSIONS go) -set(CMAKE_Go_LINKER_PREFERENCE 40) -set(CMAKE_Go_OUTPUT_EXTENSION .o) -set(CMAKE_Go_OUTPUT_EXTENSION_REPLACE 1) -set(CMAKE_Go_COMPILER_ENV_VAR "GO_COMPILER") diff --git a/go/cmake/CMakeGoInformation.cmake b/go/cmake/CMakeGoInformation.cmake deleted file mode 100644 index ba51ac93fcd..00000000000 --- a/go/cmake/CMakeGoInformation.cmake +++ /dev/null @@ -1,7 +0,0 @@ -if(NOT CMAKE_Go_COMPILE_OBJECT) - set(CMAKE_Go_COMPILE_OBJECT "go tool compile -l -N -o ") -endif() - -if(NOT CMAKE_Go_LINK_EXECUTABLE) - set(CMAKE_Go_LINK_EXECUTABLE "go tool link -o ") -endif() diff --git a/go/cmake/CMakeTestGoCompiler.cmake b/go/cmake/CMakeTestGoCompiler.cmake deleted file mode 100644 index b9891b015ba..00000000000 --- a/go/cmake/CMakeTestGoCompiler.cmake +++ /dev/null @@ -1 +0,0 @@ -set(CMAKE_Go_COMPILER_WORKS 1 CACHE INTERNAL "") diff --git a/go/cmake/flags.cmake b/go/cmake/flags.cmake deleted file mode 100644 index a167c432a92..00000000000 --- a/go/cmake/flags.cmake +++ /dev/null @@ -1,45 +0,0 @@ -# Setting Paddle Compile Flags -include(CheckCXXCompilerFlag) -include(CheckCCompilerFlag) -include(CheckCXXSymbolExists) -include(CheckTypeSize) - -function(CheckCompilerCXX11Flag) - if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") - if(${CMAKE_CXX_COMPILER_VERSION} VERSION_LESS 4.8) - message(FATAL_ERROR "Unsupported GCC version. GCC >= 4.8 required.") - endif() - elseif(CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang" OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang") - # cmake >= 3.0 compiler id "AppleClang" on Mac OS X, otherwise "Clang" - # Apple Clang is a different compiler than upstream Clang which havs different version numbers. - # https://gist.github.com/yamaya/2924292 - if(APPLE) # cmake < 3.0 compiler id "Clang" on Mac OS X - if(${CMAKE_CXX_COMPILER_VERSION} VERSION_LESS 5.1) - message(FATAL_ERROR "Unsupported AppleClang version. AppleClang >= 5.1 required.") - endif() - else() - if (${CMAKE_CXX_COMPILER_VERSION} VERSION_LESS 3.3) - message(FATAL_ERROR "Unsupported Clang version. Clang >= 3.3 required.") - endif() - endif() - endif() -endfunction() - -CheckCompilerCXX11Flag() -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") - -# Common gpu architectures: Kepler, Maxwell -foreach(capability 30 35 50) - list(APPEND __arch_flags " -gencode arch=compute_${capability},code=sm_${capability}") -endforeach() - -if (CUDA_VERSION VERSION_GREATER "7.0" OR CUDA_VERSION VERSION_EQUAL "7.0") - list(APPEND __arch_flags " -gencode arch=compute_52,code=sm_52") -endif() - -# Modern gpu architectures: Pascal -if (CUDA_VERSION VERSION_GREATER "8.0" OR CUDA_VERSION VERSION_EQUAL "8.0") - list(APPEND __arch_flags " -gencode arch=compute_60,code=sm_60") -endif() - -set(CUDA_NVCC_FLAGS ${__arch_flags} ${CUDA_NVCC_FLAGS}) diff --git a/go/cmake/golang.cmake b/go/cmake/golang.cmake deleted file mode 100644 index a5a43886f88..00000000000 --- a/go/cmake/golang.cmake +++ /dev/null @@ -1,48 +0,0 @@ -set(GOPATH "${CMAKE_CURRENT_BINARY_DIR}/go") -file(MAKE_DIRECTORY ${GOPATH}) -set(PADDLE_IN_GOPATH "${GOPATH}/src/github.com/PaddlePaddle") -file(MAKE_DIRECTORY ${PADDLE_IN_GOPATH}) - -function(GO_LIBRARY NAME BUILD_TYPE) - if(BUILD_TYPE STREQUAL "STATIC") - set(BUILD_MODE -buildmode=c-archive) - set(LIB_NAME "lib${NAME}.a") - else() - set(BUILD_MODE -buildmode=c-shared) - if(APPLE) - set(LIB_NAME "lib${NAME}.dylib") - else() - set(LIB_NAME "lib${NAME}.so") - endif() - endif() - - file(GLOB GO_SOURCE RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "*.go") - file(RELATIVE_PATH rel ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}) - - # find Paddle directory. - get_filename_component(PARENT_DIR ${CMAKE_CURRENT_SOURCE_DIR} DIRECTORY) - get_filename_component(PARENT_DIR ${PARENT_DIR} DIRECTORY) - get_filename_component(PADDLE_DIR ${PARENT_DIR} DIRECTORY) - - # automatically get all dependencies specified in the source code - # for given target. - add_custom_target(${NAME}_goGet env GOPATH=${GOPATH} ${CMAKE_Go_COMPILER} get -d ${rel}/...) - - # make a symlink that references Paddle inside $GOPATH, so go get - # will use the local changes in Paddle rather than checkout Paddle - # in github. - add_custom_target(${NAME}_copyPaddle - COMMAND rm -rf ${PADDLE_IN_GOPATH}/Paddle - COMMAND ln -sf ${PADDLE_DIR} ${PADDLE_IN_GOPATH}/Paddle) - add_dependencies(${NAME}_goGet ${NAME}_copyPaddle) - - add_custom_command(OUTPUT ${OUTPUT_DIR}/.timestamp - COMMAND env GOPATH=${GOPATH} ${CMAKE_Go_COMPILER} build ${BUILD_MODE} - -o "${CMAKE_CURRENT_BINARY_DIR}/${LIB_NAME}" - ${CMAKE_GO_FLAGS} ${GO_SOURCE} - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) - - add_custom_target(${NAME} ALL DEPENDS ${OUTPUT_DIR}/.timestamp ${ARGN}) - add_dependencies(${NAME} ${NAME}_goGet) - -endfunction(GO_LIBRARY) diff --git a/go/pserver/cclient/CMakeLists.txt b/go/pserver/cclient/CMakeLists.txt index fff7ae78582..8af6bc2e508 100644 --- a/go/pserver/cclient/CMakeLists.txt +++ b/go/pserver/cclient/CMakeLists.txt @@ -1,14 +1,7 @@ -cmake_minimum_required(VERSION 3.0) +file(RELATIVE_PATH rel ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}) -get_filename_component(PARENT_DIR ${CMAKE_CURRENT_SOURCE_DIR} DIRECTORY) -get_filename_component(PARENT_DIR ${PARENT_DIR} DIRECTORY) -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${PARENT_DIR}/cmake") - -project(cxx_go C Go) - -include(golang) -include(flags) - -go_library(paddle_pserver_cclient STATIC) +go_extern(go_pserver ${rel}/...) +go_extern(go_logrus github.com/sirupsen/logrus) +go_library(paddle_pserver_cclient STATIC DEPS go_logrus go_pserver) add_subdirectory(test) diff --git a/go/pserver/cclient/test/CMakeLists.txt b/go/pserver/cclient/test/CMakeLists.txt index 1a3dd7e5e9e..916e4e99a24 100644 --- a/go/pserver/cclient/test/CMakeLists.txt +++ b/go/pserver/cclient/test/CMakeLists.txt @@ -1,22 +1,3 @@ -cmake_minimum_required(VERSION 3.0) -add_executable(main main.c) -add_dependencies(main paddle_pserver_cclient) -add_executable(test_cclient test_cclient.c) -add_dependencies(test_cclient paddle_pserver_cclient) - -if(APPLE) - set(CMAKE_EXE_LINKER_FLAGS "-framework CoreFoundation -framework Security") -else() - set(CMAKE_EXE_LINKER_FLAGS "-pthread") -endif() - -if(PROJ_ROOT) - include_directories(${CMAKE_CURRENT_BINARY_DIR}/..) - target_link_libraries(main ${CMAKE_CURRENT_BINARY_DIR}/../libpaddle_pserver_cclient.a pthread) - target_link_libraries(test_cclient ${CMAKE_CURRENT_BINARY_DIR}/../libpaddle_pserver_cclient.a pthread) -else(PROJ_ROOT) - include_directories(${CMAKE_BINARY_DIR}) - target_link_libraries(main ${CMAKE_BINARY_DIR}/libpaddle_pserver_cclient.a pthread) - target_link_libraries(test_cclient ${CMAKE_BINARY_DIR}/libpaddle_pserver_cclient.a pthread) -endif(PROJ_ROOT) +cc_library(main SRCS main.c DEPS paddle_pserver_cclient) +cc_test(test_cclient SRCS test_cclient.c DEPS paddle_pserver_cclient) -- GitLab From 16f8508d74bd7d40776ad442a927f89d17960d6b Mon Sep 17 00:00:00 2001 From: Liu Yiqun Date: Fri, 23 Jun 2017 17:46:32 +0800 Subject: [PATCH 0783/3256] Use CMake system variables, such as CMAKE_STATIC_LIBRARY_PREFIX/SUFFIX, instead. --- cmake/external/openblas.cmake | 3 ++- cmake/external/protobuf.cmake | 12 ++++++------ cmake/system.cmake | 18 ------------------ 3 files changed, 8 insertions(+), 25 deletions(-) diff --git a/cmake/external/openblas.cmake b/cmake/external/openblas.cmake index 2341e3785bd..5b9d9844ed2 100644 --- a/cmake/external/openblas.cmake +++ b/cmake/external/openblas.cmake @@ -21,7 +21,8 @@ IF(NOT ${CBLAS_FOUND}) SET(CBLAS_INSTALL_DIR ${THIRD_PARTY_PATH}/install/openblas) SET(CBLAS_INC_DIR "${CBLAS_INSTALL_DIR}/include" CACHE PATH "openblas include directory." FORCE) - SET(CBLAS_LIBRARIES "${CBLAS_INSTALL_DIR}/lib/${LIBRARY_PREFIX}openblas${STATIC_LIBRARY_SUFFIX}" + SET(CBLAS_LIBRARIES + "${CBLAS_INSTALL_DIR}/lib/${CMAKE_STATIC_LIBRARY_PREFIX}openblas${CMAKE_STATIC_LIBRARY_SUFFIX}" CACHE FILEPATH "openblas library." FORCE) SET(COMMON_ARGS CC=${CMAKE_C_COMPILER} NO_SHARED=1 NO_LAPACK=1 libs) diff --git a/cmake/external/protobuf.cmake b/cmake/external/protobuf.cmake index ce32b2531e1..d43badc1da5 100644 --- a/cmake/external/protobuf.cmake +++ b/cmake/external/protobuf.cmake @@ -25,9 +25,9 @@ macro(PROMPT_PROTOBUF_LIB) INCLUDE_DIRECTORIES(${PROTOBUF_INCLUDE_DIR}) # Assuming that all the protobuf libraries are of the same type. - IF(${PROTOBUF_LIBRARY} MATCHES "${STATIC_LIBRARY_SUFFIX}$") + IF(${PROTOBUF_LIBRARY} MATCHES "${CMAKE_STATIC_LIBRARY_SUFFIX}$") SET(protobuf_LIBTYPE STATIC) - ELSEIF(${PROTOBUF_LIBRARY} MATCHES "${DYNAMIC_LIBRARY_SUFFIX}$") + ELSEIF(${PROTOBUF_LIBRARY} MATCHES "${CMAKE_SHARED_LIBRARY_SUFFIX}$") SET(protobuf_LIBTYPE SHARED) ELSE() MESSAGE(FATAL_ERROR "Unknown library type: ${PROTOBUF_LIBRARY}") @@ -80,16 +80,16 @@ FUNCTION(build_protobuf TARGET_NAME BUILD_FOR_HOST) SET(${TARGET_NAME}_INCLUDE_DIR "${PROTOBUF_INSTALL_DIR}/include" PARENT_SCOPE) SET(PROTOBUF_INCLUDE_DIR "${PROTOBUF_INSTALL_DIR}/include" PARENT_SCOPE) SET(${TARGET_NAME}_LITE_LIBRARY - "${PROTOBUF_INSTALL_DIR}/lib/libprotobuf-lite${STATIC_LIBRARY_SUFFIX}" + "${PROTOBUF_INSTALL_DIR}/lib/libprotobuf-lite${CMAKE_STATIC_LIBRARY_SUFFIX}" PARENT_SCOPE) SET(${TARGET_NAME}_LIBRARY - "${PROTOBUF_INSTALL_DIR}/lib/libprotobuf${STATIC_LIBRARY_SUFFIX}" + "${PROTOBUF_INSTALL_DIR}/lib/libprotobuf${CMAKE_STATIC_LIBRARY_SUFFIX}" PARENT_SCOPE) SET(${TARGET_NAME}_PROTOC_LIBRARY - "${PROTOBUF_INSTALL_DIR}/lib/libprotoc${STATIC_LIBRARY_SUFFIX}" + "${PROTOBUF_INSTALL_DIR}/lib/libprotoc${CMAKE_STATIC_LIBRARY_SUFFIX}" PARENT_SCOPE) SET(${TARGET_NAME}_PROTOC_EXECUTABLE - "${PROTOBUF_INSTALL_DIR}/bin/protoc${EXECUTABLE_SUFFIX}" + "${PROTOBUF_INSTALL_DIR}/bin/protoc${CMAKE_EXECUTABLE_SUFFIX}" PARENT_SCOPE) SET(OPTIONAL_CACHE_ARGS "") diff --git a/cmake/system.cmake b/cmake/system.cmake index 904652413e0..3b5cbfdd631 100644 --- a/cmake/system.cmake +++ b/cmake/system.cmake @@ -84,24 +84,6 @@ IF(DEFINED CMAKE_SYSTEM_NAME) ENDIF() ENDIF() -# prefix and suffix on different os -IF(WIN32) - SET(LIBRARY_PREFIX "") - SET(SHARED_LIBRARY_SUFFIX ".dll") - SET(STATIC_LIBRARY_SUFFIX ".lib") - SET(EXECUTABLE_SUFFIX ".exe") -ELSE(WIN32) - SET(LIBRARY_PREFIX "lib") - IF(APPLE) - SET(SHARED_LIBRARY_SUFFIX ".dylib") - ELSE(APPLE) - SET(SHARED_LIBRARY_SUFFIX ".so") - ENDIF(APPLE) - - SET(STATIC_LIBRARY_SUFFIX ".a") - SET(EXECUTABLE_SUFFIX "") -ENDIF(WIN32) - # external dependencies log output SET(EXTERNAL_PROJECT_LOG_ARGS LOG_DOWNLOAD 0 # Wrap download in script to log output -- GitLab From 09a50cb9b3eda517400ddda1ec6abfc5ab13204e Mon Sep 17 00:00:00 2001 From: liaogang Date: Fri, 23 Jun 2017 17:52:15 +0800 Subject: [PATCH 0784/3256] ENH: Change to CMAKE variable --- cmake/generic.cmake | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmake/generic.cmake b/cmake/generic.cmake index 76810432e0b..dd927b82d33 100644 --- a/cmake/generic.cmake +++ b/cmake/generic.cmake @@ -264,10 +264,10 @@ function(go_library TARGET_NAME) if (go_library_SHARED OR go_library_shared) set(BUILD_MODE "-buildmode=c-shared") - set(LIB_NAME "${LIBRARY_PREFIX}${TARGET_NAME}${SHARED_LIBRARY_SUFFIX}") + set(LIB_NAME "${CMAKE_SHARED_LIBRARY_PREFIX}${TARGET_NAME}${CMAKE_SHARED_LIBRARY_SUFFIX}") else() set(BUILD_MODE "-buildmode=c-archive") - set(LIB_NAME "${LIBRARY_PREFIX}${TARGET_NAME}${STATIC_LIBRARY_SUFFIX}") + set(LIB_NAME "${CMAKE_STATIC_LIBRARY_PREFIX}${TARGET_NAME}${CMAKE_STATIC_LIBRARY_SUFFIX}") endif() set(dummyfile ${CMAKE_CURRENT_BINARY_DIR}/${TARGET_NAME}_dummy.c) -- GitLab From 5e87e27c757efc1b6f0cea06a39a5ebc6dea5ec7 Mon Sep 17 00:00:00 2001 From: lianxiaochen Date: Fri, 23 Jun 2017 10:53:26 -0700 Subject: [PATCH 0785/3256] fix error clipping --- paddle/gserver/layers/Layer.cpp | 9 ++++----- python/paddle/trainer/config_parser.py | 10 +++++++++- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/paddle/gserver/layers/Layer.cpp b/paddle/gserver/layers/Layer.cpp index 125aaf947f3..b8a1c8d0fc2 100644 --- a/paddle/gserver/layers/Layer.cpp +++ b/paddle/gserver/layers/Layer.cpp @@ -354,12 +354,11 @@ void Layer::backwardActivation() { /* Do error clipping */ if (config_.error_clipping_threshold() > 0.0f) { if (FLAGS_log_error_clipping) { - CpuVector outGradVec(0, nullptr); - outGradVec.subVecFrom( - output_.grad->getData(), 0, output_.grad->getElementCnt()); - real maxAbsGrad = outGradVec.getAbsMax(); + VectorPtr outGradVec = Vector::create( + output_.grad->getData(), output_.grad->getElementCnt(), useGpu_); + real maxAbsGrad = outGradVec->getAbsMax(); if (maxAbsGrad > config_.error_clipping_threshold()) { - real avgAbsGrad = outGradVec.getAbsSum() / outGradVec.getSize(); + real avgAbsGrad = outGradVec->getAbsSum() / outGradVec->getSize(); LOG(INFO) << " layer=" << config_.name() << " need clipping," << " max error=" << maxAbsGrad << " avg error=" << avgAbsGrad; } diff --git a/python/paddle/trainer/config_parser.py b/python/paddle/trainer/config_parser.py index 58e4902f57a..8dec50221f3 100644 --- a/python/paddle/trainer/config_parser.py +++ b/python/paddle/trainer/config_parser.py @@ -1571,7 +1571,13 @@ class MultiClassCrossEntropySelfNormCostLayer(LayerBase): @config_layer('fc') class FCLayer(LayerBase): - def __init__(self, name, size, inputs, bias=True, **xargs): + def __init__(self, + name, + size, + inputs, + bias=True, + error_clipping_threshold=None, + **xargs): super(FCLayer, self).__init__(name, 'fc', size, inputs=inputs, **xargs) for input_index in xrange(len(self.inputs)): input_layer = self.get_input_layer(input_index) @@ -1588,6 +1594,8 @@ class FCLayer(LayerBase): self.create_input_parameter(input_index, psize, dims, sparse, format) self.create_bias_parameter(bias, self.config.size) + if error_clipping_threshold is not None: + self.config.error_clipping_threshold = error_clipping_threshold @config_layer('selective_fc') -- GitLab From b55df90dfdf6b9720548613885d291ae8769705b Mon Sep 17 00:00:00 2001 From: Yi Wang Date: Fri, 23 Jun 2017 11:42:48 -0700 Subject: [PATCH 0786/3256] Remove unnecessary preamble --- paddle/memory/README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/paddle/memory/README.md b/paddle/memory/README.md index b71ca296965..fd32d07ef40 100644 --- a/paddle/memory/README.md +++ b/paddle/memory/README.md @@ -1,5 +1,3 @@ -In my mind, the memory package works like the following: - ## Design ### Usage -- GitLab From 5a22d736513124fb03fab1fb792a35739d2dd333 Mon Sep 17 00:00:00 2001 From: Yi Wang Date: Fri, 23 Jun 2017 15:09:42 -0700 Subject: [PATCH 0787/3256] Add variable.h and test --- paddle/framework/CMakeLists.txt | 2 + paddle/framework/ddim_test.cc | 2 - paddle/framework/variable.h | 88 +++++++++++++++++++++++++++++++ paddle/framework/variable_test.cc | 40 ++++++++++++++ 4 files changed, 130 insertions(+), 2 deletions(-) create mode 100644 paddle/framework/variable.h create mode 100644 paddle/framework/variable_test.cc diff --git a/paddle/framework/CMakeLists.txt b/paddle/framework/CMakeLists.txt index 673cfa19ac3..e3c3155aa90 100644 --- a/paddle/framework/CMakeLists.txt +++ b/paddle/framework/CMakeLists.txt @@ -2,3 +2,5 @@ cc_library(ddim SRCS ddim.cc) cc_test(ddim_test SRCS ddim_test.cc DEPS ddim) nv_test(dim_test SRCS dim_test.cu DEPS ddim) + +cc_test(variable_test SRCS variable_test.cc) diff --git a/paddle/framework/ddim_test.cc b/paddle/framework/ddim_test.cc index e5c84d7abe9..36eef02370e 100644 --- a/paddle/framework/ddim_test.cc +++ b/paddle/framework/ddim_test.cc @@ -1,5 +1,3 @@ -//#include -//#include #include #include diff --git a/paddle/framework/variable.h b/paddle/framework/variable.h new file mode 100644 index 00000000000..249b9b1b37f --- /dev/null +++ b/paddle/framework/variable.h @@ -0,0 +1,88 @@ +/* + 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 + +namespace paddle { +namespace framework { + +class Variable { + public: + template + const T& Get() const { + return *static_cast(holder_->Ptr()); + } + + template + T* GetMutable() { + if (holder_ != nullptr && typeid(T) == holder_->Type()) { + return static_cast(holder_->Ptr()); + } else { + return Reset(new T(), DefaultDeleter()); + } + } + + ~Variable() { + if (holder_ != nullptr) delete holder_; + } + + private: + // DefaultDeleter is functor which uses C++'s delete(T*). + template + struct DefaultDeleter { + void operator()(T* ptr) { delete ptr; } + }; + + struct Placeholder { + virtual ~Placeholder() {} + virtual const std::type_info& Type() const = 0; + virtual void* Ptr() const = 0; + }; + + // Placeholder hides type T, so it doesn't appear as a template + // parameter of Variable. + template + struct PlaceholderImpl : public Placeholder { + typedef std::function Deleter; + + PlaceholderImpl(T* ptr) : ptr_(ptr), type_(typeid(T)) {} + PlaceholderImpl(T* ptr, Deleter d) + : ptr_(ptr), type_(typeid(T)), deleter_(d) {} + + virtual ~PlaceholderImpl() { + deleter_(ptr_); + ptr_ = nullptr; + } + virtual const std::type_info& Type() const { return type_; } + virtual void* Ptr() const { return ptr_; } + + T* ptr_ = nullptr; + const std::type_info& type_; + std::function deleter_ = DefaultDeleter(); + }; + + template + T* Reset(T* allocated, typename PlaceholderImpl::Deleter deleter) { + if (holder_ != nullptr) { + delete holder_; + } + holder_ = new PlaceholderImpl(allocated, deleter); + return allocated; + } + + Placeholder* holder_; // pointers to a PlaceholderImpl object indeed. +}; + +} // namespace framework +} // namespace paddle diff --git a/paddle/framework/variable_test.cc b/paddle/framework/variable_test.cc new file mode 100644 index 00000000000..aea03bcf571 --- /dev/null +++ b/paddle/framework/variable_test.cc @@ -0,0 +1,40 @@ +/* + 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 "gtest/gtest.h" +#include "paddle/framework/variable.h" + +TEST(Variable, GetMutable) { + using paddle::framework::Variable; + + struct Tensor { + int content_; + }; + + std::unique_ptr v(new Variable()); + + Tensor* t = v->GetMutable(); + t->content_ = 1234; + + const Tensor& tt = v->Get(); + EXPECT_EQ(1234, tt.content_); + + std::string* s = v->GetMutable(); + *s = "hello"; + + const std::string& ss = v->Get(); + EXPECT_EQ("hello", ss); +} -- GitLab From 58efbf41b32e3495b038978c9a06a0285542cd57 Mon Sep 17 00:00:00 2001 From: Yi Wang Date: Fri, 23 Jun 2017 16:13:00 -0700 Subject: [PATCH 0788/3256] Follow comments from Xu Wei --- paddle/framework/variable.h | 45 +++++++++---------------------------- 1 file changed, 10 insertions(+), 35 deletions(-) diff --git a/paddle/framework/variable.h b/paddle/framework/variable.h index 249b9b1b37f..b21c95a1a6d 100644 --- a/paddle/framework/variable.h +++ b/paddle/framework/variable.h @@ -12,6 +12,8 @@ */ #pragma once +#include +#include #include namespace paddle { @@ -26,24 +28,14 @@ class Variable { template T* GetMutable() { - if (holder_ != nullptr && typeid(T) == holder_->Type()) { - return static_cast(holder_->Ptr()); - } else { - return Reset(new T(), DefaultDeleter()); + if (holder_ == nullptr || + std::type_index(typeid(T)) != std::type_index(holder_->Type())) { + holder_.reset(new PlaceholderImpl(new T())); } - } - - ~Variable() { - if (holder_ != nullptr) delete holder_; + return static_cast(holder_->Ptr()); } private: - // DefaultDeleter is functor which uses C++'s delete(T*). - template - struct DefaultDeleter { - void operator()(T* ptr) { delete ptr; } - }; - struct Placeholder { virtual ~Placeholder() {} virtual const std::type_info& Type() const = 0; @@ -54,34 +46,17 @@ class Variable { // parameter of Variable. template struct PlaceholderImpl : public Placeholder { - typedef std::function Deleter; - PlaceholderImpl(T* ptr) : ptr_(ptr), type_(typeid(T)) {} - PlaceholderImpl(T* ptr, Deleter d) - : ptr_(ptr), type_(typeid(T)), deleter_(d) {} - virtual ~PlaceholderImpl() { - deleter_(ptr_); - ptr_ = nullptr; - } virtual const std::type_info& Type() const { return type_; } - virtual void* Ptr() const { return ptr_; } + virtual void* Ptr() const { return static_cast(ptr_.get()); } - T* ptr_ = nullptr; + std::unique_ptr ptr_; const std::type_info& type_; - std::function deleter_ = DefaultDeleter(); }; - template - T* Reset(T* allocated, typename PlaceholderImpl::Deleter deleter) { - if (holder_ != nullptr) { - delete holder_; - } - holder_ = new PlaceholderImpl(allocated, deleter); - return allocated; - } - - Placeholder* holder_; // pointers to a PlaceholderImpl object indeed. + std::unique_ptr + holder_; // pointers to a PlaceholderImpl object indeed. }; } // namespace framework -- GitLab From ac28fad6f37f007cb36c92daff7492a31e5c68b3 Mon Sep 17 00:00:00 2001 From: Yi Wang Date: Fri, 23 Jun 2017 16:59:36 -0700 Subject: [PATCH 0789/3256] Add type assertion in Variable::Get --- paddle/framework/variable.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/paddle/framework/variable.h b/paddle/framework/variable.h index b21c95a1a6d..b33e10e6820 100644 --- a/paddle/framework/variable.h +++ b/paddle/framework/variable.h @@ -16,6 +16,8 @@ #include #include +#include "paddle/platform/assert.h" + namespace paddle { namespace framework { @@ -23,6 +25,9 @@ class Variable { public: template const T& Get() const { + PADDLE_ASSERT(holder_ != nullptr); + PADDLE_ASSERT(std::type_index(typeid(T)) == + std::type_index(holder_->Type())); return *static_cast(holder_->Ptr()); } -- GitLab From bd4559abbee5413d322b8659929bdb203de6abaf Mon Sep 17 00:00:00 2001 From: Yi Wang Date: Fri, 23 Jun 2017 16:59:48 -0700 Subject: [PATCH 0790/3256] Add design doc --- paddle/framework/variable.md | 52 ++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 paddle/framework/variable.md diff --git a/paddle/framework/variable.md b/paddle/framework/variable.md new file mode 100644 index 00000000000..f44d5ea46e7 --- /dev/null +++ b/paddle/framework/variable.md @@ -0,0 +1,52 @@ +# Design Doc: Variable + + +Variable is also known as *blob* in MxNet and Caffe2. It is the input and output type of operators, where a neural network is a graph of operators. + +## Requirements: Lazy Memory Allocation + +For the flexibility of a DL system, a variable should be able to contain any typed value -- a tensor in most cases, but could also be some integer IDs or a scope of other variables in the case of RNN. + +To use the minimum amount of memory, we'd like that a variable to allocate memory when it has to, or, lazy memory allocation. Let's take the following example: + +```cpp +Variable vr, v1, v2; + +Tensor* t1 = new Tensor(); +Tensor* t2 = new Tensor(); + +Randomize( + /* malloc */ v1.GetMutable().mutable_data(DDim(100,200)), + /* size */ t1.Size()); + +Randomize( + /* malloc */ v2.GetMutable().mutable_data(DDim(200,300)), + /* size */ t2.Size()); + +Mult( + /*result*/ vr.GetMutable().mutable_data(SizeOfMult(v1, v2)), + /*input1*/ v1.Get().data(), + /*input2*/ v2.Get().data()); +``` + +We see that a variable holds nothing until `Variable::GetMutable()` allocates a tensor and puts it in the variable. Similarly, a tensor gets its memory until `Tensor::mutable_data()`. + +This syntax for lazy memory allocation when we call `Randomize` and `Mult`, those functions that mutate the variable, so it saves us some line of C++ code. + + +## Implementation: Type Hiding + +To make memory allocation lazy, we cannot assume that we know the type held by a variable at definition time. In other words, `class Variable` cannot be a template `template class Variable`. + +Because we don't know the type `T`, we cannot save a `T*` as `Variable's` data member. Instead, we save an interface object `Placeholder`, who can return the pointer to the saved object via `Placeholder::Ptr()` as `void*`. + +But anyway, Variable needs to know `T` so could it `delete(ptr)` and so could `Variable::Get` checks the expected type and the saved object's type. + +We save `T` in `PlaceholderImpl`, the implementation of `Placeholder`. Please be aware that `PlaceholderImpl` is a class template and `T` is passed in as a template parameter. + +Because `PlaceholderImpl` knows `T`, it can save and return `typeid(T)` for the type comparison in `Variable::Get` and `Variable::GetMutable`. + + +## Conclusion + +The technique type hiding utilizes C++ class templates, interface and derivation, and C++ RTTI (typeid). This combination saves us from definition something like `caffe2::TypeMata`, which takes hundreds of lines of C++ code. -- GitLab From fd8937556f95db4086ce095efa1e83041c896334 Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Tue, 20 Jun 2017 23:57:07 +0000 Subject: [PATCH 0791/3256] Master save and load state from etcd --- go/cmd/master/master.go | 55 ++++++++++-- go/master/client_internal_test.go | 21 ++++- go/master/client_test.go | 21 ++++- go/master/etcd_store.go | 133 ++++++++++++++++++++++++++++ go/master/service.go | 142 +++++++++++++++++++++++------- go/pserver/cclient/cclient.go | 6 +- 6 files changed, 330 insertions(+), 48 deletions(-) create mode 100644 go/master/etcd_store.go diff --git a/go/cmd/master/master.go b/go/cmd/master/master.go index 25cd1cafcdf..49ad0300b83 100644 --- a/go/cmd/master/master.go +++ b/go/cmd/master/master.go @@ -5,41 +5,80 @@ import ( "net/http" "net/rpc" "strconv" + "strings" + "sync" "time" "github.com/namsral/flag" + log "github.com/sirupsen/logrus" "github.com/PaddlePaddle/Paddle/go/master" ) +type inMemStore struct { + mu sync.Mutex + buf []byte +} + +func (m *inMemStore) Save(b []byte) error { + m.mu.Lock() + defer m.mu.Unlock() + + m.buf = b + return nil +} + +func (m *inMemStore) Load() ([]byte, error) { + m.mu.Lock() + defer m.mu.Unlock() + + return m.buf, nil +} + func main() { port := flag.Int("port", 8080, "port of the master server.") - faultTolerance := flag.Bool("fault_tolerance", false, "enable fault tolerance (requires etcd).") + ttlSec := flag.Int("ttl", 60, "etcd lease TTL in seconds.") + endpoints := flag.String("endpoints", "", "comma separated etcd endpoints. If empty, fault tolerance will not be enabled.") taskTimeoutDur := flag.Duration("task_timout_dur", 20*time.Minute, "task timout duration.") taskTimeoutMax := flag.Int("task_timeout_max", 3, "max timtout count for each task before it being declared failed task.") chunkPerTask := flag.Int("chunk_per_task", 10, "chunk per task.") flag.Parse() - if *faultTolerance { - panic("fault tolernance not implemented.") + if *endpoints == "" { + log.Warningln("-endpoints not set, fault tolerance not be enabled.") + } + + var store master.Store + if *endpoints != "" { + eps := strings.Split(*endpoints, ",") + var err error + store, err = master.NewEtcdStore(eps, master.DefaultLockPath, master.DefaultStatePath, *ttlSec) + if err != nil { + log.Fatal(err) + } + } else { + store = &inMemStore{} + } + s, err := master.NewService(store, *chunkPerTask, *taskTimeoutDur, *taskTimeoutMax) + if err != nil { + log.Fatal(err) } - s := master.NewService(*chunkPerTask, *taskTimeoutDur, *taskTimeoutMax) - err := rpc.Register(s) + err = rpc.Register(s) if err != nil { - panic(err) + log.Fatal(err) } rpc.HandleHTTP() l, err := net.Listen("tcp", ":"+strconv.Itoa(*port)) if err != nil { - panic(err) + log.Fatal(err) } err = http.Serve(l, nil) if err != nil { - panic(err) + log.Fatal(err) } } diff --git a/go/master/client_internal_test.go b/go/master/client_internal_test.go index 00fcca0e2cf..a5b76fe8530 100644 --- a/go/master/client_internal_test.go +++ b/go/master/client_internal_test.go @@ -32,6 +32,19 @@ func (a TestAddresser) Address() string { return string(a) } +type myStore struct { + buf []byte +} + +func (m *myStore) Save(b []byte) error { + m.buf = b + return nil +} + +func (m *myStore) Load() ([]byte, error) { + return m.buf, nil +} + func TestGetFinishTask(t *testing.T) { const path = "/tmp/master_client_test_0" @@ -47,9 +60,13 @@ func TestGetFinishTask(t *testing.T) { } go func(l net.Listener) { - s := NewService(chunkPerTask, time.Second, 1) + s, err := NewService(&myStore{}, chunkPerTask, time.Second, 1) + if err != nil { + panic(err) + } + server := rpc.NewServer() - err := server.Register(s) + err = server.Register(s) if err != nil { panic(err) } diff --git a/go/master/client_test.go b/go/master/client_test.go index 2b3f873ecf3..ae5f17c2d49 100644 --- a/go/master/client_test.go +++ b/go/master/client_test.go @@ -15,6 +15,19 @@ import ( "github.com/PaddlePaddle/recordio" ) +type myStore struct { + buf []byte +} + +func (m *myStore) Save(b []byte) error { + m.buf = b + return nil +} + +func (m *myStore) Load() ([]byte, error) { + return m.buf, nil +} + func TestNextRecord(t *testing.T) { const ( path = "/tmp/master_client_TestFull" @@ -33,9 +46,13 @@ func TestNextRecord(t *testing.T) { } go func(l net.Listener) { - s := master.NewService(10, time.Second, 1) + s, err := master.NewService(&myStore{}, 10, time.Second, 1) + if err != nil { + panic(err) + } + server := rpc.NewServer() - err := server.Register(s) + err = server.Register(s) if err != nil { panic(err) } diff --git a/go/master/etcd_store.go b/go/master/etcd_store.go new file mode 100644 index 00000000000..ce178370ff9 --- /dev/null +++ b/go/master/etcd_store.go @@ -0,0 +1,133 @@ +package master + +import ( + "context" + "sync" + + "github.com/coreos/etcd/clientv3" + "github.com/coreos/etcd/clientv3/concurrency" + log "github.com/sirupsen/logrus" +) + +const ( + // DefaultLockPath is the default etcd master lock path. + DefaultLockPath = "/master/lock" + // DefaultStatePath is the default etcd key for master state. + DefaultStatePath = "/master/state" +) + +// EtcdStore is the Store implementation backed by etcd. +type EtcdStore struct { + lockPath string + statePath string + ttlSec int + client *clientv3.Client + + mu sync.Mutex + lock *concurrency.Mutex +} + +// NewEtcdStore creates a new EtcdStore. +func NewEtcdStore(endpoints []string, lockPath, statePath string, ttlSec int) (*EtcdStore, error) { + cli, err := clientv3.New(clientv3.Config{ + Endpoints: endpoints, + DialTimeout: dialTimeout, + }) + if err != nil { + return nil, err + } + + sess, err := concurrency.NewSession(cli, concurrency.WithTTL(ttlSec)) + if err != nil { + return nil, err + } + + lock := concurrency.NewMutex(sess, lockPath) + // It's fine for the lock to get stuck, in this case we have + // multiple master servers running (only configured to have + // one master running, but split-brain problem may cuase + // multiple master servers running), and the cluster management + // software will kill one of them. + log.Infof("Trying to acquire lock at %s.", lockPath) + err = lock.Lock(context.TODO()) + if err != nil { + return nil, err + } + log.Infof("Successfully acquired lock at %s.", lockPath) + + e := &EtcdStore{} + e.client = cli + e.lock = lock + e.lockPath = lockPath + e.statePath = statePath + e.ttlSec = ttlSec + return e, nil +} + +// Save saves the state into the etcd. +func (e *EtcdStore) Save(state []byte) error { + e.mu.Lock() + defer e.mu.Unlock() + + ctx := context.TODO() + put := clientv3.OpPut(e.statePath, string(state)) + resp, err := e.client.Txn(ctx).If(e.lock.IsOwner()).Then(put).Commit() + if err != nil { + return err + } + + if !resp.Succeeded { + log.Errorln("No longer owns the lock, trying to lock and save again.") + sess, err := concurrency.NewSession(e.client, concurrency.WithTTL(e.ttlSec)) + if err != nil { + return err + } + + e.lock = concurrency.NewMutex(sess, e.lockPath) + log.Infof("Try to acquire lock at %s.", e.lockPath) + err = e.lock.Lock(context.TODO()) + if err != nil { + return err + } + log.Infof("Successfully acquired lock at %s.", e.lockPath) + return e.Save(state) + } + + return nil +} + +// Load loads the state from etcd. +func (e *EtcdStore) Load() ([]byte, error) { + e.mu.Lock() + ctx := context.TODO() + get := clientv3.OpGet(e.statePath) + + resp, err := e.client.Txn(ctx).If(e.lock.IsOwner()).Then(get).Commit() + if err != nil { + return nil, err + } + + if !resp.Succeeded { + log.Errorln("No longer owns the lock, trying to lock and load again.") + sess, err := concurrency.NewSession(e.client) + if err != nil { + return nil, err + } + + e.lock = concurrency.NewMutex(sess, e.lockPath) + e.lock.Lock(context.TODO()) + e.mu.Unlock() + return e.Load() + } + + kvs := resp.Responses[0].GetResponseRange().Kvs + if len(kvs) == 0 { + // No state exists + e.mu.Unlock() + return nil, nil + } + + state := kvs[0].Value + e.mu.Unlock() + return state, nil +} diff --git a/go/master/service.go b/go/master/service.go index 55e1e2d1a4a..d453777b055 100644 --- a/go/master/service.go +++ b/go/master/service.go @@ -1,6 +1,9 @@ package master import ( + "bytes" + "compress/gzip" + "encoding/gob" "errors" "os" "path/filepath" @@ -12,24 +15,54 @@ import ( "github.com/PaddlePaddle/recordio" ) +const ( + dialTimeout = 5 * time.Second +) + +// Store is the interface for save and load the master state. +type Store interface { + Save([]byte) error + Load() ([]byte, error) +} + +// Chunk is a chunk of data consisted of several data instances. +type Chunk struct { + Path string + Index recordio.Index // chunk index +} + +// Task is the basic unit of data instances assigned to trainers. +type Task struct { + ID int + Chunks []Chunk +} + +type taskEntry struct { + Epoch int + NumTimeout int + Task Task +} + +type taskQueues struct { + Todo []taskEntry + Pending map[int]taskEntry // map from task ID to task entry + Done []taskEntry + Failed []Task +} + // Service is the master server service. type Service struct { chunksPerTask int timeoutDur time.Duration timeoutMax int ready chan struct{} + store Store mu sync.Mutex initDone bool taskQueues taskQueues } -// Recover recovers service state from etcd. -func Recover() (*Service, error) { - // TODO(helin): recover from snapshot state from etcd. - return nil, nil -} - func partition(chunks []Chunk, chunksPerTask int) []taskEntry { id := 0 if chunksPerTask <= 0 { @@ -58,7 +91,7 @@ func partition(chunks []Chunk, chunksPerTask int) []taskEntry { } // NewService creates a new service. -func NewService(chunksPerTask int, timeoutDur time.Duration, timeoutMax int) *Service { +func NewService(store Store, chunksPerTask int, timeoutDur time.Duration, timeoutMax int) (*Service, error) { s := &Service{} s.chunksPerTask = chunksPerTask s.timeoutDur = timeoutDur @@ -66,38 +99,81 @@ func NewService(chunksPerTask int, timeoutDur time.Duration, timeoutMax int) *Se s.taskQueues = taskQueues{} s.taskQueues.Pending = make(map[int]taskEntry) s.ready = make(chan struct{}) - return s -} + s.store = store + recovered, err := s.recover() + if err != nil { + return nil, err + } -// Chunk is a chunk of data consisted of several data instances. -type Chunk struct { - Path string - Index recordio.Index // chunk index -} + if recovered { + // Recovered. Now the state is already initialized, + // and the master is ready. + s.initDone = true + close(s.ready) + } -// Task is the basic unit of data instances assigned to trainers. -type Task struct { - ID int - Chunks []Chunk + return s, nil } -type taskEntry struct { - Epoch int - NumTimeout int - Task Task -} +// recover recovers service state from etcd. +func (s *Service) recover() (bool, error) { + state, err := s.store.Load() + if err != nil { + return false, err + } -type taskQueues struct { - Todo []taskEntry - Pending map[int]taskEntry // map from task ID to task entry - Done []taskEntry - Failed []Task + if state == nil { + log.Infoln("No state exists, not recovered.") + return false, nil + } + + log.Infof("Loaded snapshot of size: %d bytes.", len(state)) + gr, err := gzip.NewReader(bytes.NewReader(state)) + if err != nil { + return false, err + } + + dec := gob.NewDecoder(gr) + var tqs taskQueues + err = dec.Decode(&tqs) + if err != nil { + return false, err + } + + err = gr.Close() + if err != nil { + // Only close failed, recover actually succeed, so + // just log error. + log.Errorln(err) + } + + s.taskQueues = tqs + return true, nil } -// *must* be called with s.mu being held. +// snapshot *must* be called with s.mu being held. func (s *Service) snapshot() error { - // TODO(helin): snapshot state on etcd. - return nil + // TOOD(helin): etcd request has a size limit, so the snapshot + // size is limited by the max request size. We should either + // divide the snapshot into smaller chunks and save under + // different keys, or configure the request size to be big + // enough: + // https://github.com/coreos/etcd/blob/2f84f3d8d8ed8f9537ab6ffa44a3a1c7eddfa9b1/embed/config.go#L44 + var buf bytes.Buffer + gw := gzip.NewWriter(&buf) + enc := gob.NewEncoder(gw) + err := enc.Encode(s.taskQueues) + if err != nil { + return err + } + err = gw.Close() + if err != nil { + return err + } + + state := buf.Bytes() + log.Infof("Saving snapshot of size: %d bytes.", len(state)) + return s.store.Save(state) } func readChunks(globPaths []string) ([]Chunk, error) { @@ -207,12 +283,12 @@ func (s *Service) checkTimeoutFunc(taskID int, epoch int) func() { t.NumTimeout++ if t.NumTimeout > s.timeoutMax { - log.Warningf("Task %v timed out %d times, discard.\n", t.Task, t.NumTimeout) + log.Warningf("Task %v timed out %d times, discard.", t.Task, t.NumTimeout) s.taskQueues.Failed = append(s.taskQueues.Failed, t.Task) return } - log.Warningf("Task %v timed out %d times, retry.\n", t.Task, t.NumTimeout) + log.Warningf("Task %v timed out %d times, retry.", t.Task, t.NumTimeout) s.taskQueues.Todo = append(s.taskQueues.Todo, t) } } diff --git a/go/pserver/cclient/cclient.go b/go/pserver/cclient/cclient.go index 92a41b7f543..bbaf43d9f14 100644 --- a/go/pserver/cclient/cclient.go +++ b/go/pserver/cclient/cclient.go @@ -133,7 +133,7 @@ func paddle_init_param(client C.paddle_pserver_client, param C.paddle_parameter, if err != nil { if err.Error() == pserver.AlreadyInitialized { - log.Warningf("parameter %s already initialized, treat paddle_init_param as sucessful.\n", name) + log.Warningf("parameter %s already initialized, treat paddle_init_param as sucessful.", name) return C.PSERVER_OK } log.Errorln(err) @@ -200,7 +200,7 @@ func paddle_get_params(client C.paddle_pserver_client, dst **C.paddle_parameter, for i, p := range ps { pn[i] = p.Name } - log.Errorf("pserver returned wrong number of parameters. Requested: %s, returned: %s.\n", strings.Join(pn, ", "), strings.Join(ns, ", ")) + log.Errorf("pserver returned wrong number of parameters. Requested: %s, returned: %s.", strings.Join(pn, ", "), strings.Join(ns, ", ")) return C.PSERVER_ERROR } @@ -210,7 +210,7 @@ func paddle_get_params(client C.paddle_pserver_client, dst **C.paddle_parameter, for i, p := range ps { pn[i] = p.Name } - log.Errorf("pserver returned wrong parameters, or not in requested order. Requested: %s, returned: %s.\n", strings.Join(pn, ", "), strings.Join(ns, ", ")) + log.Errorf("pserver returned wrong parameters, or not in requested order. Requested: %s, returned: %s.", strings.Join(pn, ", "), strings.Join(ns, ", ")) return C.PSERVER_ERROR } } -- GitLab From 44226853029119e195530e78ff7d0ab883b72dff Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Wed, 21 Jun 2017 18:55:49 +0000 Subject: [PATCH 0792/3256] put InMemStore into master package --- go/cmd/master/master.go | 23 +---------------------- go/master/client_internal_test.go | 15 +-------------- go/master/client_test.go | 15 +-------------- go/master/inmem_store.go | 28 ++++++++++++++++++++++++++++ 4 files changed, 31 insertions(+), 50 deletions(-) create mode 100644 go/master/inmem_store.go diff --git a/go/cmd/master/master.go b/go/cmd/master/master.go index 49ad0300b83..48fe2e6f75a 100644 --- a/go/cmd/master/master.go +++ b/go/cmd/master/master.go @@ -6,7 +6,6 @@ import ( "net/rpc" "strconv" "strings" - "sync" "time" "github.com/namsral/flag" @@ -15,26 +14,6 @@ import ( "github.com/PaddlePaddle/Paddle/go/master" ) -type inMemStore struct { - mu sync.Mutex - buf []byte -} - -func (m *inMemStore) Save(b []byte) error { - m.mu.Lock() - defer m.mu.Unlock() - - m.buf = b - return nil -} - -func (m *inMemStore) Load() ([]byte, error) { - m.mu.Lock() - defer m.mu.Unlock() - - return m.buf, nil -} - func main() { port := flag.Int("port", 8080, "port of the master server.") @@ -58,7 +37,7 @@ func main() { log.Fatal(err) } } else { - store = &inMemStore{} + store = &master.InMemStore{} } s, err := master.NewService(store, *chunkPerTask, *taskTimeoutDur, *taskTimeoutMax) diff --git a/go/master/client_internal_test.go b/go/master/client_internal_test.go index a5b76fe8530..251225780ae 100644 --- a/go/master/client_internal_test.go +++ b/go/master/client_internal_test.go @@ -32,19 +32,6 @@ func (a TestAddresser) Address() string { return string(a) } -type myStore struct { - buf []byte -} - -func (m *myStore) Save(b []byte) error { - m.buf = b - return nil -} - -func (m *myStore) Load() ([]byte, error) { - return m.buf, nil -} - func TestGetFinishTask(t *testing.T) { const path = "/tmp/master_client_test_0" @@ -60,7 +47,7 @@ func TestGetFinishTask(t *testing.T) { } go func(l net.Listener) { - s, err := NewService(&myStore{}, chunkPerTask, time.Second, 1) + s, err := NewService(&InMemStore{}, chunkPerTask, time.Second, 1) if err != nil { panic(err) } diff --git a/go/master/client_test.go b/go/master/client_test.go index ae5f17c2d49..85a86761c2e 100644 --- a/go/master/client_test.go +++ b/go/master/client_test.go @@ -15,19 +15,6 @@ import ( "github.com/PaddlePaddle/recordio" ) -type myStore struct { - buf []byte -} - -func (m *myStore) Save(b []byte) error { - m.buf = b - return nil -} - -func (m *myStore) Load() ([]byte, error) { - return m.buf, nil -} - func TestNextRecord(t *testing.T) { const ( path = "/tmp/master_client_TestFull" @@ -46,7 +33,7 @@ func TestNextRecord(t *testing.T) { } go func(l net.Listener) { - s, err := master.NewService(&myStore{}, 10, time.Second, 1) + s, err := master.NewService(&master.InMemStore{}, 10, time.Second, 1) if err != nil { panic(err) } diff --git a/go/master/inmem_store.go b/go/master/inmem_store.go new file mode 100644 index 00000000000..bcd549b20e4 --- /dev/null +++ b/go/master/inmem_store.go @@ -0,0 +1,28 @@ +package master + +import "sync" + +// InMemStore is an in memory implementation of Store interface. +// +// It does not tolerate the fault that casues the program to crash. +type InMemStore struct { + mu sync.Mutex + buf []byte +} + +// Save saves the state into the in-memory store. +func (m *InMemStore) Save(state []byte) error { + m.mu.Lock() + defer m.mu.Unlock() + + m.buf = state + return nil +} + +// Load loads the state from the in-memory store. +func (m *InMemStore) Load() ([]byte, error) { + m.mu.Lock() + defer m.mu.Unlock() + + return m.buf, nil +} -- GitLab From a4ba403e792fc21b5e032ad6116f1fc00fb4ba8d Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Wed, 21 Jun 2017 19:00:25 +0000 Subject: [PATCH 0793/3256] add comment for gracefully stop etcd store --- go/master/etcd_store.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/go/master/etcd_store.go b/go/master/etcd_store.go index ce178370ff9..d8e95056d5d 100644 --- a/go/master/etcd_store.go +++ b/go/master/etcd_store.go @@ -29,6 +29,10 @@ type EtcdStore struct { // NewEtcdStore creates a new EtcdStore. func NewEtcdStore(endpoints []string, lockPath, statePath string, ttlSec int) (*EtcdStore, error) { + // TODO(helin): gracefully shutdown etcd store. Becuase etcd + // store holds a etcd lock, even though the lock will expire + // when the lease timeout, we need to implement graceful + // shutdown to release the lock. cli, err := clientv3.New(clientv3.Config{ Endpoints: endpoints, DialTimeout: dialTimeout, -- GitLab From bf79c9e5bba41dd9f1e122a779e27e3e8dca9ee3 Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Wed, 21 Jun 2017 19:02:21 +0000 Subject: [PATCH 0794/3256] add log when master recovered from saved state. --- go/master/service.go | 1 + 1 file changed, 1 insertion(+) diff --git a/go/master/service.go b/go/master/service.go index d453777b055..58e68e74485 100644 --- a/go/master/service.go +++ b/go/master/service.go @@ -110,6 +110,7 @@ func NewService(store Store, chunksPerTask int, timeoutDur time.Duration, timeou // and the master is ready. s.initDone = true close(s.ready) + log.Info("Master recovered from saved state.") } return s, nil -- GitLab From 42313a3c35637b8d706aa4dbdef65c671e7d6665 Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Fri, 23 Jun 2017 22:11:45 +0000 Subject: [PATCH 0795/3256] rename EtcdStore to Etcd --- go/cmd/master/master.go | 2 +- go/master/etcd_store.go | 21 +++++++++++++-------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/go/cmd/master/master.go b/go/cmd/master/master.go index 48fe2e6f75a..a62bc4310e6 100644 --- a/go/cmd/master/master.go +++ b/go/cmd/master/master.go @@ -32,7 +32,7 @@ func main() { if *endpoints != "" { eps := strings.Split(*endpoints, ",") var err error - store, err = master.NewEtcdStore(eps, master.DefaultLockPath, master.DefaultStatePath, *ttlSec) + store, err = master.NewEtcd(eps, master.DefaultLockPath, master.DefaultStatePath, *ttlSec) if err != nil { log.Fatal(err) } diff --git a/go/master/etcd_store.go b/go/master/etcd_store.go index d8e95056d5d..21b3e2cb0f5 100644 --- a/go/master/etcd_store.go +++ b/go/master/etcd_store.go @@ -16,8 +16,9 @@ const ( DefaultStatePath = "/master/state" ) -// EtcdStore is the Store implementation backed by etcd. -type EtcdStore struct { +// Etcd is the etcd abstraction that master uses for fault tolerance +// and service registry. +type Etcd struct { lockPath string statePath string ttlSec int @@ -27,8 +28,8 @@ type EtcdStore struct { lock *concurrency.Mutex } -// NewEtcdStore creates a new EtcdStore. -func NewEtcdStore(endpoints []string, lockPath, statePath string, ttlSec int) (*EtcdStore, error) { +// NewEtcd creates a new Etcd. +func NewEtcd(endpoints []string, lockPath, statePath string, ttlSec int) (*Etcd, error) { // TODO(helin): gracefully shutdown etcd store. Becuase etcd // store holds a etcd lock, even though the lock will expire // when the lease timeout, we need to implement graceful @@ -59,7 +60,7 @@ func NewEtcdStore(endpoints []string, lockPath, statePath string, ttlSec int) (* } log.Infof("Successfully acquired lock at %s.", lockPath) - e := &EtcdStore{} + e := &Etcd{} e.client = cli e.lock = lock e.lockPath = lockPath @@ -69,7 +70,7 @@ func NewEtcdStore(endpoints []string, lockPath, statePath string, ttlSec int) (* } // Save saves the state into the etcd. -func (e *EtcdStore) Save(state []byte) error { +func (e *Etcd) Save(state []byte) error { e.mu.Lock() defer e.mu.Unlock() @@ -101,7 +102,7 @@ func (e *EtcdStore) Save(state []byte) error { } // Load loads the state from etcd. -func (e *EtcdStore) Load() ([]byte, error) { +func (e *Etcd) Load() ([]byte, error) { e.mu.Lock() ctx := context.TODO() get := clientv3.OpGet(e.statePath) @@ -119,8 +120,12 @@ func (e *EtcdStore) Load() ([]byte, error) { } e.lock = concurrency.NewMutex(sess, e.lockPath) - e.lock.Lock(context.TODO()) + err = e.lock.Lock(context.TODO()) e.mu.Unlock() + if err != nil { + return nil, err + } + return e.Load() } -- GitLab From 7dad02661f1cd7406eac871354c94cebf4d38345 Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Sat, 24 Jun 2017 00:04:26 +0000 Subject: [PATCH 0796/3256] Master server registers itself to etcd. --- go/cmd/master/master.go | 14 +++- go/master/{etcd_store.go => etcd_client.go} | 90 +++++++++++---------- 2 files changed, 56 insertions(+), 48 deletions(-) rename go/master/{etcd_store.go => etcd_client.go} (56%) diff --git a/go/cmd/master/master.go b/go/cmd/master/master.go index a62bc4310e6..54fa2548631 100644 --- a/go/cmd/master/master.go +++ b/go/cmd/master/master.go @@ -1,6 +1,7 @@ package main import ( + "fmt" "net" "net/http" "net/rpc" @@ -12,13 +13,13 @@ import ( log "github.com/sirupsen/logrus" "github.com/PaddlePaddle/Paddle/go/master" + "github.com/PaddlePaddle/Paddle/go/utils/networkhelper" ) func main() { port := flag.Int("port", 8080, "port of the master server.") - ttlSec := flag.Int("ttl", 60, "etcd lease TTL in seconds.") - endpoints := flag.String("endpoints", "", "comma separated etcd endpoints. If empty, fault tolerance will not be enabled.") + endpoints := flag.String("endpoints", "http://127.0.0.1:2379", "comma separated etcd endpoints. If empty, fault tolerance will not be enabled.") taskTimeoutDur := flag.Duration("task_timout_dur", 20*time.Minute, "task timout duration.") taskTimeoutMax := flag.Int("task_timeout_max", 3, "max timtout count for each task before it being declared failed task.") chunkPerTask := flag.Int("chunk_per_task", 10, "chunk per task.") @@ -31,8 +32,13 @@ func main() { var store master.Store if *endpoints != "" { eps := strings.Split(*endpoints, ",") - var err error - store, err = master.NewEtcd(eps, master.DefaultLockPath, master.DefaultStatePath, *ttlSec) + ip, err := networkhelper.GetExternalIP() + if err != nil { + log.Fatal(err) + } + + addr := fmt.Sprintf("%s:%d", ip, *port) + store, err = master.NewEtcdClient(eps, addr, master.DefaultLockPath, master.DefaultAddrPath, master.DefaultStatePath, *ttlSec) if err != nil { log.Fatal(err) } diff --git a/go/master/etcd_store.go b/go/master/etcd_client.go similarity index 56% rename from go/master/etcd_store.go rename to go/master/etcd_client.go index 21b3e2cb0f5..b7293a75989 100644 --- a/go/master/etcd_store.go +++ b/go/master/etcd_client.go @@ -2,7 +2,7 @@ package master import ( "context" - "sync" + "time" "github.com/coreos/etcd/clientv3" "github.com/coreos/etcd/clientv3/concurrency" @@ -14,22 +14,22 @@ const ( DefaultLockPath = "/master/lock" // DefaultStatePath is the default etcd key for master state. DefaultStatePath = "/master/state" + // DefaultAddrPath is the default etcd key for master address. + DefaultAddrPath = "/master/addr" ) -// Etcd is the etcd abstraction that master uses for fault tolerance +// EtcdClient is the etcd client that master uses for fault tolerance // and service registry. -type Etcd struct { +type EtcdClient struct { lockPath string statePath string - ttlSec int client *clientv3.Client - - mu sync.Mutex - lock *concurrency.Mutex + lock *concurrency.Mutex } -// NewEtcd creates a new Etcd. -func NewEtcd(endpoints []string, lockPath, statePath string, ttlSec int) (*Etcd, error) { +// NewEtcdClient creates a new EtcdClient. +func NewEtcdClient(endpoints []string, addr string, lockPath, addrPath, statePath string, ttlSec int) (*EtcdClient, error) { + log.Debugf("Connecting to etcd at %v", endpoints) // TODO(helin): gracefully shutdown etcd store. Becuase etcd // store holds a etcd lock, even though the lock will expire // when the lease timeout, we need to implement graceful @@ -53,27 +53,35 @@ func NewEtcd(endpoints []string, lockPath, statePath string, ttlSec int) (*Etcd, // one master running, but split-brain problem may cuase // multiple master servers running), and the cluster management // software will kill one of them. - log.Infof("Trying to acquire lock at %s.", lockPath) + log.Debugf("Trying to acquire lock at %s.", lockPath) err = lock.Lock(context.TODO()) if err != nil { return nil, err } - log.Infof("Successfully acquired lock at %s.", lockPath) - - e := &Etcd{} - e.client = cli - e.lock = lock - e.lockPath = lockPath - e.statePath = statePath - e.ttlSec = ttlSec + log.Debugf("Successfully acquired lock at %s.", lockPath) + + put := clientv3.OpPut(addrPath, string(addr)) + resp, err := cli.Txn(context.Background()).If(lock.IsOwner()).Then(put).Commit() + if err != nil { + return nil, err + } + + if !resp.Succeeded { + log.Fatal("No longer owns the master lock. Exiting.") + } + + e := &EtcdClient{ + lockPath: lockPath, + statePath: statePath, + client: cli, + lock: lock, + } + return e, nil } // Save saves the state into the etcd. -func (e *Etcd) Save(state []byte) error { - e.mu.Lock() - defer e.mu.Unlock() - +func (e *EtcdClient) Save(state []byte) error { ctx := context.TODO() put := clientv3.OpPut(e.statePath, string(state)) resp, err := e.client.Txn(ctx).If(e.lock.IsOwner()).Then(put).Commit() @@ -82,17 +90,21 @@ func (e *Etcd) Save(state []byte) error { } if !resp.Succeeded { - log.Errorln("No longer owns the lock, trying to lock and save again.") - sess, err := concurrency.NewSession(e.client, concurrency.WithTTL(e.ttlSec)) - if err != nil { - return err - } - - e.lock = concurrency.NewMutex(sess, e.lockPath) - log.Infof("Try to acquire lock at %s.", e.lockPath) - err = e.lock.Lock(context.TODO()) + log.Errorln("No longer owns the lock, trying to lock again") + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + err := e.lock.Lock(ctx) + cancel() if err != nil { - return err + // We lost the master lock and can not acquire + // it back, it means some other master is + // already started. We don't want cluster + // managment system to kill the master server + // who is holding the lock and running + // correctly. So the most feasible solution is + // to kill current master server. The current + // state is not saved, but the trainer's RPC + // call will fail, so the trainer will retry. + log.Fatalf("Could not acquire the lock at %s: %v. Exiting.", e.lockPath, err) } log.Infof("Successfully acquired lock at %s.", e.lockPath) return e.Save(state) @@ -102,8 +114,7 @@ func (e *Etcd) Save(state []byte) error { } // Load loads the state from etcd. -func (e *Etcd) Load() ([]byte, error) { - e.mu.Lock() +func (e *EtcdClient) Load() ([]byte, error) { ctx := context.TODO() get := clientv3.OpGet(e.statePath) @@ -114,14 +125,7 @@ func (e *Etcd) Load() ([]byte, error) { if !resp.Succeeded { log.Errorln("No longer owns the lock, trying to lock and load again.") - sess, err := concurrency.NewSession(e.client) - if err != nil { - return nil, err - } - - e.lock = concurrency.NewMutex(sess, e.lockPath) - err = e.lock.Lock(context.TODO()) - e.mu.Unlock() + err = e.lock.Lock(context.Background()) if err != nil { return nil, err } @@ -132,11 +136,9 @@ func (e *Etcd) Load() ([]byte, error) { kvs := resp.Responses[0].GetResponseRange().Kvs if len(kvs) == 0 { // No state exists - e.mu.Unlock() return nil, nil } state := kvs[0].Value - e.mu.Unlock() return state, nil } -- GitLab From 59b40ecb78781c8ba05973e0f9befc7ed34471e2 Mon Sep 17 00:00:00 2001 From: dzhwinter Date: Sat, 24 Jun 2017 16:20:16 +0800 Subject: [PATCH 0797/3256] revert parameter with []byte --- go/pserver/optimizer.go | 1 + go/pserver/service.go | 14 +++----------- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/go/pserver/optimizer.go b/go/pserver/optimizer.go index df2219aa84d..40748d03c1d 100644 --- a/go/pserver/optimizer.go +++ b/go/pserver/optimizer.go @@ -5,6 +5,7 @@ package pserver #cgo pkg-config: protobuf #cgo CFLAGS: -I ../../ #cgo LDFLAGS: ../../build/paddle/optimizer/libpaddle_optimizer.a ../../build/proto/libpaddle_proto.a ../../third_party/install/glog/lib/libglog.a ../../third_party/install/gtest/lib/libgtest.a ../../third_party/install/gflags/lib/libgflags.a ../../third_party/install/openblas/lib/libopenblas.a -I/usr/local/lib/ -lprotobuf +#cgo LDFLAGS: /Users/dzh/.go/src/github.com/PaddlePaddle/Paddle/build/lib/libdep.a #include "paddle/optimizer/optimizer.h" */ import "C" diff --git a/go/pserver/service.go b/go/pserver/service.go index c721388b6a7..32449f66b7e 100644 --- a/go/pserver/service.go +++ b/go/pserver/service.go @@ -28,8 +28,7 @@ const ( type Parameter struct { Name string ElementType ElementType - Content *byte - Length int + Content []byte } // ParameterWithConfig contains the parameter and the configuration. @@ -47,15 +46,13 @@ type Service struct { mu sync.Mutex // injection from parameter to optimizer - optMap map[string]*optimizer - paramMap map[string]Parameter + optMap map[string]*optimizer } // NewService creates a new service. func NewService() *Service { s := &Service{} s.optMap = make(map[string]*optimizer) - s.paramMap = make(map[string]Parameter) s.initialized = make(chan struct{}) return s } @@ -76,7 +73,6 @@ func (s *Service) InitParam(paramWithConfigs ParameterWithConfig, dummy *int) er // TODO(helin): check if paramWithConfigs.Param.Content is // properly memory aligned, if not, make copy to a memory // aligned region. - s.paramMap[paramWithConfigs.Param.Name] = paramWithConfigs.Param s.optMap[paramWithConfigs.Param.Name] = newOptimizer(paramWithConfigs) return nil } @@ -106,13 +102,9 @@ func (s *Service) SendGrad(g Gradient, dummy *int) error { s.mu.Lock() defer s.mu.Unlock() - p, ok := s.paramMap[g.Name] - if !ok { - return fmt.Errorf("parameter: %s does not exist", g.Name) - } o, ok := s.optMap[g.Name] if !ok { - return fmt.Errorf("optimizer: %s does not exist", g.Name) + return fmt.Errorf("parameter: %s does not exist", g.Name) } return o.UpdateParameter(p, g) -- GitLab From a7865a37768b8d320378c50b517ddd1fdf6db934 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Sat, 24 Jun 2017 17:01:48 +0800 Subject: [PATCH 0798/3256] Fix macos compile Please use `override` not `virtual` in sub-classes. `override` can check if there is a method in `parent` while compiling. --- .../gserver/gradientmachines/NeuralNetwork.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/paddle/gserver/gradientmachines/NeuralNetwork.cpp b/paddle/gserver/gradientmachines/NeuralNetwork.cpp index 514c0759e17..2e839f64050 100644 --- a/paddle/gserver/gradientmachines/NeuralNetwork.cpp +++ b/paddle/gserver/gradientmachines/NeuralNetwork.cpp @@ -309,35 +309,35 @@ public: void addEvaluator(std::unique_ptr&& evaluator) { evaluators_.emplace_back(std::move(evaluator)); } - virtual void start() { + void start() override { for (auto& evaluator : evaluators_) { evaluator->start(); } } - virtual void finish() { + void finish() override { for (auto& evaluator : evaluators_) { evaluator->finish(); } } - virtual void eval(const NeuralNetwork& nn) override { + void eval(const NeuralNetwork& nn) override { for (auto& evaluator : evaluators_) { evaluator->eval(nn); } } - virtual real evalImp(std::vector& arguments) { + real evalImp(std::vector& arguments) override { (void)arguments; return -1; } - virtual void printStats(std::ostream& os) const { + void printStats(std::ostream& os) const override { for (auto& evaluator : evaluators_) { evaluator->printStats(os); os << ' '; } } - virtual void distributeEval(ParameterClient2* client) { + void distributeEval(ParameterClient2* client) override { for (auto& evaluator : evaluators_) { evaluator->distributeEval(client); } @@ -352,7 +352,7 @@ public: * @brief getNames will return all inside evaluators' names. * @param names [out]: return names. */ - void getNames(std::vector* names) { + void getNames(std::vector* names) override { for (auto& eval : evaluators_) { eval->getNames(names); } @@ -361,7 +361,7 @@ public: /** * @brief getValue could get all inside evaluators' value. */ - real getValue(const std::string& name, Error* err) const { + real getValue(const std::string& name, Error* err) const override { return this->getMethodHelper( name, err, [&name, err](const std::unique_ptr& eval) { return eval->getValue(name, err); @@ -371,7 +371,7 @@ public: /** * @brief getType could get all inside evaluators' type. */ - std::string getType(const std::string& name, Error* err) const { + std::string getType(const std::string& name, Error* err) const override { return this->getMethodHelper( name, err, [&name, err](const std::unique_ptr& eval) { return eval->getType(name, err); -- GitLab From b34a05d1141c4758803c084cda7e7d4c976567d8 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Sat, 24 Jun 2017 16:18:14 +0800 Subject: [PATCH 0799/3256] Fix travis-ci in new image Fix issue #2562 --- .travis.yml | 1 - python/paddle/trainer_config_helpers/networks.py | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2c46da71e75..ff41551ba70 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,3 @@ -group: deprecated-2017Q2 language: cpp cache: directories: diff --git a/python/paddle/trainer_config_helpers/networks.py b/python/paddle/trainer_config_helpers/networks.py index 1bf59ed4840..67154a8d7d3 100755 --- a/python/paddle/trainer_config_helpers/networks.py +++ b/python/paddle/trainer_config_helpers/networks.py @@ -1381,7 +1381,7 @@ def inputs(layers, *args): if len(args) != 0: layers.extend(args) - Inputs(*[l.name for l in layers]) + Inputs(* [l.name for l in layers]) def outputs(layers, *args): @@ -1424,7 +1424,7 @@ def outputs(layers, *args): assert len(layers) > 0 if HasInputsSet(): # input already set - Outputs(*[l.name for l in layers]) + Outputs(* [l.name for l in layers]) return # just return outputs. if len(layers) != 1: -- GitLab From 8c735c8b092c9f21161bf7b8f8deb8b2f2047184 Mon Sep 17 00:00:00 2001 From: gongweibao Date: Sat, 24 Jun 2017 18:37:48 +0800 Subject: [PATCH 0800/3256] add dependy --- python/setup.py.in | 1 + 1 file changed, 1 insertion(+) diff --git a/python/setup.py.in b/python/setup.py.in index 2e22f640cb5..86fc0fc5c03 100644 --- a/python/setup.py.in +++ b/python/setup.py.in @@ -13,6 +13,7 @@ packages=['paddle', setup_requires=["requests", "numpy", "protobuf==3.1", + "recordio", "matplotlib", "rarfile"] -- GitLab From b359d5c5cdffb05679245886dbb3193981a4d442 Mon Sep 17 00:00:00 2001 From: gongweibao Date: Sat, 24 Jun 2017 18:48:02 +0800 Subject: [PATCH 0801/3256] restore creator.py --- python/paddle/v2/reader/creator.py | 21 +-------------------- 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/python/paddle/v2/reader/creator.py b/python/paddle/v2/reader/creator.py index 994062096fb..07142056f87 100644 --- a/python/paddle/v2/reader/creator.py +++ b/python/paddle/v2/reader/creator.py @@ -16,7 +16,7 @@ Creator package contains some simple reader creator, which could be used in user program. """ -__all__ = ['np_array', 'text_file', "RecordIO"] +__all__ = ['np_array', 'text_file'] def np_array(x): @@ -55,22 +55,3 @@ def text_file(path): f.close() return reader - - -def RecordIO(path): - """ - Creates a data reader that outputs record one one by one from given recordio file - :path: path of recordio file - :returns: data reader of recordio file - """ - - def reader(): - f = recordio.reader(path) - while True: - r = f.read() - if r is None: - break - yield r - f.close() - - return reader -- GitLab From 90c909ac7c0ba7155151b3af6aea655e0cd8ce98 Mon Sep 17 00:00:00 2001 From: gongweibao Date: Sat, 24 Jun 2017 18:51:03 +0800 Subject: [PATCH 0802/3256] restore creator_test.py --- python/paddle/v2/reader/tests/creator_test.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/python/paddle/v2/reader/tests/creator_test.py b/python/paddle/v2/reader/tests/creator_test.py index dd84fbb0025..9f8d7133b86 100644 --- a/python/paddle/v2/reader/tests/creator_test.py +++ b/python/paddle/v2/reader/tests/creator_test.py @@ -36,14 +36,5 @@ class TestTextFile(unittest.TestCase): self.assertEqual(e, str(idx * 2) + " " + str(idx * 2 + 1)) -class TestRecordIO(unittest.TestCase): - def test_RecordIO(self): - path = os.path.join( - os.path.dirname(__file__), "test_recordio_creator.dat") - reader = paddle.v2.reader.creator.RecordIO(path) - for idx, r in enumerate(reader()): - self.assertSequenceEqual(r, str(idx)) - - if __name__ == '__main__': unittest.main() -- GitLab From ae79b9ac1ccdf99713241c2e2b9f5c6bddcc0193 Mon Sep 17 00:00:00 2001 From: gongweibao Date: Sat, 24 Jun 2017 18:52:09 +0800 Subject: [PATCH 0803/3256] restore --- .../v2/reader/tests/test_recordio_creator.dat | Bin 88 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 python/paddle/v2/reader/tests/test_recordio_creator.dat diff --git a/python/paddle/v2/reader/tests/test_recordio_creator.dat b/python/paddle/v2/reader/tests/test_recordio_creator.dat deleted file mode 100644 index 17aa89b6796184407e83246d3f342a55a66b4a69..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 88 zcmZQ!W@2QOHw Date: Sat, 24 Jun 2017 19:53:28 +0800 Subject: [PATCH 0804/3256] set ps_desired when pserver init --- go/cmd/pserver/pserver.go | 3 ++- go/pserver/service.go | 28 +++++++++++++++++++++++++++- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/go/cmd/pserver/pserver.go b/go/cmd/pserver/pserver.go index fe1fe5f6f03..6c85b1804bb 100644 --- a/go/cmd/pserver/pserver.go +++ b/go/cmd/pserver/pserver.go @@ -18,6 +18,7 @@ func main() { etcdEndpoint := flag.String("etcd-endpoint", "http://127.0.0.1:2379", "comma separated endpoint string for pserver to connect to etcd") etcdTimeout := flag.Int("etcd-timeout", 5, "timeout for etcd calls") + numPservers := flag.Int("num-pservers", 1, "total pserver count in a training job") logLevel := flag.String("log-level", "info", "log level, possible values: debug, info, warning, error, fatal, panic") flag.Parse() @@ -29,7 +30,7 @@ func main() { log.SetLevel(level) timeout := time.Second * time.Duration((*etcdTimeout)) - s, err := pserver.NewService(*etcdEndpoint, timeout) + s, err := pserver.NewService(*etcdEndpoint, *numPservers, timeout) if err != nil { panic(err) } diff --git a/go/pserver/service.go b/go/pserver/service.go index 7e2b841dd8e..f966595fdcc 100644 --- a/go/pserver/service.go +++ b/go/pserver/service.go @@ -73,7 +73,7 @@ type Service struct { // NewService creates a new service, will bypass etcd registration if no // endpoints specified. -func NewService(endpoints string, timeout time.Duration) (*Service, error) { +func NewService(endpoints string, numPservers int, timeout time.Duration) (*Service, error) { s := &Service{opt: newOptimizer(sgd, 0.005)} s.paramMap = make(map[string]Parameter) s.initialized = make(chan struct{}) @@ -103,6 +103,22 @@ func NewService(endpoints string, timeout time.Duration) (*Service, error) { log.Debugf("inited client to %s", s.etcdEndpoints) break } + // init /ps_desired using transaction, for multiple pservers may want to write + // it at the same time. + for { + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + _, err := s.initDesiredPsercers(ctx, numPservers) + cancel() + if err != nil { + log.Warn(err) + time.Sleep(s.etcdTimeout) + continue + } + break + } + // TODO: when implementing extending or reducing pservers, /ps_desired is + // changed, then we need to watch /ps_desired node for events. For now, just + // write once when init and read from it. // wait and set s.desired init value for { ctx, cancel := context.WithTimeout(context.Background(), time.Second) @@ -141,6 +157,16 @@ func NewService(endpoints string, timeout time.Duration) (*Service, error) { return s, nil } +func (s *Service) initDesiredPsercers(ctx context.Context, numPservers int) (*clientv3.TxnResponse, error) { + return concurrency.NewSTM(s.etcdClient, func(c concurrency.STM) error { + dsStr := c.Get(PsDesired) + if dsStr == "" { + c.Put(PsDesired, strconv.Itoa(numPservers)) + } + return nil + }, concurrency.WithAbortContext(ctx), concurrency.WithIsolation(concurrency.RepeatableReads)) +} + // registerPserverEtcd registers pserver node on etcd using transaction. func (s *Service) registerPserverEtcd(ctx context.Context) (*clientv3.TxnResponse, error) { return concurrency.NewSTM(s.etcdClient, func(c concurrency.STM) error { -- GitLab From 55684af208071bd788381946ac76c9da2b5b7329 Mon Sep 17 00:00:00 2001 From: qiaolongfei Date: Sun, 25 Jun 2017 13:13:10 +0800 Subject: [PATCH 0805/3256] fix MultiGradientMachine train and infer --- .../gradientmachines/MultiGradientMachine.cpp | 12 ++++++------ .../gserver/gradientmachines/MultiGradientMachine.h | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/paddle/gserver/gradientmachines/MultiGradientMachine.cpp b/paddle/gserver/gradientmachines/MultiGradientMachine.cpp index 3159026e6b9..9abda18d544 100644 --- a/paddle/gserver/gradientmachines/MultiGradientMachine.cpp +++ b/paddle/gserver/gradientmachines/MultiGradientMachine.cpp @@ -171,6 +171,12 @@ MultiGradientMachine::MultiGradientMachine(const ModelConfig& config, } } +MultiGradientMachine::~MultiGradientMachine() { + for (auto& thread : threads_) { + thread->stop(); + } +} + std::vector*> MultiGradientMachine::getSlaveParameters() { std::vector*> vec; @@ -326,12 +332,6 @@ void MultiGradientMachine::onPassEnd() { } } -void MultiGradientMachine::finish() { - for (auto& thread : threads_) { - thread->stop(); - } -} - Evaluator* MultiGradientMachine::makeEvaluator() const { return threads_[0]->getGradientMachine()->makeEvaluator(); } diff --git a/paddle/gserver/gradientmachines/MultiGradientMachine.h b/paddle/gserver/gradientmachines/MultiGradientMachine.h index 70203bbb97f..c005c0ed67f 100644 --- a/paddle/gserver/gradientmachines/MultiGradientMachine.h +++ b/paddle/gserver/gradientmachines/MultiGradientMachine.h @@ -176,6 +176,8 @@ public: explicit MultiGradientMachine(const ModelConfig& config, bool useGpu); + virtual ~MultiGradientMachine(); + virtual void prefetch(const std::vector& inArgs); virtual void forward(const std::vector& inArgs, @@ -193,8 +195,6 @@ public: virtual void onPassEnd(); - virtual void finish(); - virtual Evaluator* makeEvaluator() const; virtual void eval(Evaluator* evaluator) const; -- GitLab From 84d1c734ca2fe7a17e000467823d49891507cf0b Mon Sep 17 00:00:00 2001 From: Yi Wang Date: Sun, 25 Jun 2017 15:40:45 -0700 Subject: [PATCH 0806/3256] add paddle/memory/detail/cpu_allocator* --- paddle/CMakeLists.txt | 1 + paddle/memory/CMakeLists.txt | 1 + paddle/memory/README.md | 14 ++--- paddle/memory/detail/CMakeLists.txt | 1 + paddle/memory/detail/cpu_allocator.h | 63 ++++++++++++++++++++++ paddle/memory/detail/cpu_allocator_test.cc | 32 +++++++++++ paddle/memory/memory.cc | 51 ++++++++++++++++++ paddle/memory/memory.h | 27 ++++++++++ paddle/platform/place.cc | 12 ++--- paddle/platform/place.h | 45 ++++++++++------ paddle/platform/place_test.cc | 14 ++--- 11 files changed, 224 insertions(+), 37 deletions(-) create mode 100644 paddle/memory/CMakeLists.txt create mode 100644 paddle/memory/detail/CMakeLists.txt create mode 100644 paddle/memory/detail/cpu_allocator.h create mode 100644 paddle/memory/detail/cpu_allocator_test.cc create mode 100644 paddle/memory/memory.cc create mode 100644 paddle/memory/memory.h diff --git a/paddle/CMakeLists.txt b/paddle/CMakeLists.txt index 573bd937a35..0cddb95244f 100644 --- a/paddle/CMakeLists.txt +++ b/paddle/CMakeLists.txt @@ -10,6 +10,7 @@ add_subdirectory(trainer) add_subdirectory(scripts) add_subdirectory(optimizer) add_subdirectory(strings) +add_subdirectory(memory) # Do not build go directory until go cmake is working smoothly. # if(CMAKE_Go_COMPILER) diff --git a/paddle/memory/CMakeLists.txt b/paddle/memory/CMakeLists.txt new file mode 100644 index 00000000000..3943c3cfad3 --- /dev/null +++ b/paddle/memory/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(detail) diff --git a/paddle/memory/README.md b/paddle/memory/README.md index fd32d07ef40..e5f7880e4ca 100644 --- a/paddle/memory/README.md +++ b/paddle/memory/README.md @@ -31,7 +31,7 @@ In `paddle/memory/memory.h` we have: namespace memory { template void* Alloc(Place, size_t); template void Free(Place, void*); -template void Used(Place); +template size_t Used(Place); } // namespace memory ``` @@ -39,7 +39,7 @@ These function templates have specializations on either `platform::CPUPlace` or ```cpp template<> -void Alloc(CPUPlace p, size_t size) { +void* Alloc(CPUPlace p, size_t size) { return GetCPUBuddyAllocator()->Alloc(size); } ``` @@ -102,15 +102,11 @@ class BuddyAllocator { }; ``` -#### System Allocators - -The `GPUAllocator` and `CPUAllocator` are calls *system allocators*. They work as the fallback allocators of `BuddyAllocator`. A system allocator holds information about a device, including the amount of memory has been allocated, so we can call +Because BuddyAllocator has the meta-data of each block, it can trace the used memory -- record the amount returned by `Alloc` freed in `Free`. Instead, `CPUAllocator` and `GPUAllocator` doesn't know the size of freed memory block and cannot do the trace. -- `GPUAllocator::Used()` and -- `CPUAllocator::Used()` - -to get the amount of memory that has been allocated so far. +#### System Allocators +The `GPUAllocator` and `CPUAllocator` are calls *system allocators*. They work as the fallback allocators of `BuddyAllocator`. ## Justification diff --git a/paddle/memory/detail/CMakeLists.txt b/paddle/memory/detail/CMakeLists.txt new file mode 100644 index 00000000000..fb8a11062da --- /dev/null +++ b/paddle/memory/detail/CMakeLists.txt @@ -0,0 +1 @@ +cc_test(cpu_allocator_test SRCS cpu_allocator_test.cc) diff --git a/paddle/memory/detail/cpu_allocator.h b/paddle/memory/detail/cpu_allocator.h new file mode 100644 index 00000000000..8a872d3800d --- /dev/null +++ b/paddle/memory/detail/cpu_allocator.h @@ -0,0 +1,63 @@ +/* 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 // for malloc and free +#include // for size_t + +namespace paddle { +namespace memory { +namespace detail { + +// CPUAllocator calls cudaMallocHost, which returns +// pinned and mlocked memory as staging areas for data exchange +// between host and device. Allocates too much would reduce the +// amount of memory available to the system for paging. So, by +// default, we should use CPUAllocator. +template +class CPUAllocator { +public: + void* Alloc(size_t size); + void Free(void* p); +}; + +template <> +class CPUAllocator { +public: + void* Alloc(size_t size) { return malloc(size); } + void Free(void* p) { free(p); } +}; + +// If CMake macro WITH_GPU is OFF, C++ compiler won't generate the +// following specialization that depends on the CUDA library. +#ifdef WITH_GPU +template <> +class CPUAllocator { +public: + void* Alloc(size_t size) { + void* p; + if (cudaMallocHost(&p, size) != cudaSuccess) { + return NULL; + } + return *p; + } + + void Free(void* p) { cudaFreeHost(p); } +}; +#endif // WITH_GPU + +} // namespace detail +} // namespace memory +} // namespace paddle diff --git a/paddle/memory/detail/cpu_allocator_test.cc b/paddle/memory/detail/cpu_allocator_test.cc new file mode 100644 index 00000000000..0aa33a22fd0 --- /dev/null +++ b/paddle/memory/detail/cpu_allocator_test.cc @@ -0,0 +1,32 @@ +/* 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/memory/detail/cpu_allocator.h" +#include "gtest/gtest.h" + +TEST(CPUAllocator, NonStaging) { + paddle::memory::detail::CPUAllocator a; + void* p = a.Alloc(4096); + EXPECT_NE(p, nullptr); + a.Free(p); +} + +#ifdef WITH_GPU +TEST(CPUAllocator, Staging) { + paddle::memory::detail::CPUAllocator a; + void* p = a.Alloc(4096); + EXPECT_NE(p, nullptr); + a.Free(p); +} +#endif // WITH_GPU diff --git a/paddle/memory/memory.cc b/paddle/memory/memory.cc new file mode 100644 index 00000000000..5f1253ede68 --- /dev/null +++ b/paddle/memory/memory.cc @@ -0,0 +1,51 @@ +/* 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/memory/memory.h" + +namespace paddle { +namespace memory { + +template <> +void* Alloc(CPUPlace, size_t size) { + return GetCPUBuddyAllocator()->Alloc(size); +} + +template <> +void* Alloc(GPUPlace pl, size_t size) { + return GetGPUBuddyAllocator(pl.device)->Alloc(size); +} + +template <> +void Free(CPUPlace, void* p) { + return GetCPUBuddyAllocator()->Free(p); +} + +template <> +void* Alloc(GPUPlace pl, void* p) { + return GetGPUBuddyAllocator(pl.device)->Free(p); +} + +template <> +size_t Used(CPUPlace) { + return GetCPUBuddyAllocator()->Used(); +} + +template <> +size_t Alloc(GPUPlace pl) { + return GetGPUBuddyAllocator(pl.device)->Used(); +} + +} // namespace memory +} // namespace paddle diff --git a/paddle/memory/memory.h b/paddle/memory/memory.h new file mode 100644 index 00000000000..ae8ac6ca523 --- /dev/null +++ b/paddle/memory/memory.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/frameowork/place.h" + +namespace paddle { +namespace memory { + +typename void* Alloc(Place, size_t); +typename void Free(Place, void*); +typename size_t Used(Place); + +} // namespace memory +} // namespace paddle diff --git a/paddle/platform/place.cc b/paddle/platform/place.cc index 1afd03c0116..0704820aa05 100644 --- a/paddle/platform/place.cc +++ b/paddle/platform/place.cc @@ -8,8 +8,8 @@ namespace detail { class PlacePrinter : public boost::static_visitor<> { public: PlacePrinter(std::ostream &os) : os_(os) {} - void operator()(const CpuPlace &) { os_ << "CpuPlace"; } - void operator()(const GpuPlace &p) { os_ << "GpuPlace(" << p.device << ")"; } + void operator()(const CPUPlace &) { os_ << "CPUPlace"; } + void operator()(const GPUPlace &p) { os_ << "GPUPlace(" << p.device << ")"; } private: std::ostream &os_; @@ -22,14 +22,14 @@ static Place the_default_place; void set_place(const Place &place) { the_default_place = place; } const Place &get_place() { return the_default_place; } -const GpuPlace default_gpu() { return GpuPlace(0); } -const CpuPlace default_cpu() { return CpuPlace(); } +const GPUPlace default_gpu() { return GPUPlace(0); } +const CPUPlace default_cpu() { return CPUPlace(); } bool is_gpu_place(const Place &p) { - return boost::apply_visitor(IsGpuPlace(), p); + return boost::apply_visitor(IsGPUPlace(), p); } bool is_cpu_place(const Place &p) { - return !boost::apply_visitor(IsGpuPlace(), p); + return !boost::apply_visitor(IsGPUPlace(), p); } bool places_are_same_class(const Place &p1, const Place &p2) { diff --git a/paddle/platform/place.h b/paddle/platform/place.h index 489572c526e..7cead183884 100644 --- a/paddle/platform/place.h +++ b/paddle/platform/place.h @@ -1,43 +1,58 @@ +/* Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserve. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. */ + #pragma once + #include #include namespace paddle { namespace platform { -struct CpuPlace { +struct CPUPlace { // WORKAROUND: for some reason, omitting this constructor // causes errors with boost 1.59 and OSX - CpuPlace() {} + CPUPlace() {} // needed for variant equality comparison - inline bool operator==(const CpuPlace &) const { return true; } - inline bool operator!=(const CpuPlace &) const { return false; } + inline bool operator==(const CPUPlace &) const { return true; } + inline bool operator!=(const CPUPlace &) const { return false; } }; -struct GpuPlace { - GpuPlace() : GpuPlace(0) {} - GpuPlace(int d) : device(d) {} +struct GPUPlace { + GPUPlace() : GPUPlace(0) {} + GPUPlace(int d) : device(d) {} // needed for variant equality comparison - inline bool operator==(const GpuPlace &o) const { return device == o.device; } - inline bool operator!=(const GpuPlace &o) const { return !(*this == o); } + inline bool operator==(const GPUPlace &o) const { return device == o.device; } + inline bool operator!=(const GPUPlace &o) const { return !(*this == o); } int device; }; -struct IsGpuPlace : public boost::static_visitor { - bool operator()(const CpuPlace &) const { return false; } - bool operator()(const GpuPlace &gpu) const { return true; } +struct IsGPUPlace : public boost::static_visitor { + bool operator()(const CPUPlace &) const { return false; } + bool operator()(const GPUPlace &gpu) const { return true; } }; -typedef boost::variant Place; +typedef boost::variant Place; void set_place(const Place &); const Place &get_place(); -const GpuPlace default_gpu(); -const CpuPlace default_cpu(); +const GPUPlace default_gpu(); +const CPUPlace default_cpu(); bool is_gpu_place(const Place &); bool is_cpu_place(const Place &); diff --git a/paddle/platform/place_test.cc b/paddle/platform/place_test.cc index 73fccceedf6..33e2e5a439c 100644 --- a/paddle/platform/place_test.cc +++ b/paddle/platform/place_test.cc @@ -3,8 +3,8 @@ #include "gtest/gtest.h" TEST(Place, Equality) { - paddle::platform::CpuPlace cpu; - paddle::platform::GpuPlace g0(0), g1(1), gg0(0); + paddle::platform::CPUPlace cpu; + paddle::platform::GPUPlace g0(0), g1(1), gg0(0); EXPECT_EQ(cpu, cpu); EXPECT_EQ(g0, g0); @@ -22,19 +22,19 @@ TEST(Place, Default) { EXPECT_TRUE(paddle::platform::is_gpu_place(paddle::platform::default_gpu())); EXPECT_TRUE(paddle::platform::is_cpu_place(paddle::platform::default_cpu())); - paddle::platform::set_place(paddle::platform::CpuPlace()); + paddle::platform::set_place(paddle::platform::CPUPlace()); EXPECT_TRUE(paddle::platform::is_cpu_place(paddle::platform::get_place())); } TEST(Place, Print) { { std::stringstream ss; - ss << paddle::platform::GpuPlace(1); - EXPECT_EQ("GpuPlace(1)", ss.str()); + ss << paddle::platform::GPUPlace(1); + EXPECT_EQ("GPUPlace(1)", ss.str()); } { std::stringstream ss; - ss << paddle::platform::CpuPlace(); - EXPECT_EQ("CpuPlace", ss.str()); + ss << paddle::platform::CPUPlace(); + EXPECT_EQ("CPUPlace", ss.str()); } } -- GitLab From ab2550c6400bce5d2596f5bff8629ef67ed195b8 Mon Sep 17 00:00:00 2001 From: Yi Wang Date: Sun, 25 Jun 2017 15:44:55 -0700 Subject: [PATCH 0807/3256] Update design --- paddle/memory/README.md | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/paddle/memory/README.md b/paddle/memory/README.md index fd32d07ef40..e5f7880e4ca 100644 --- a/paddle/memory/README.md +++ b/paddle/memory/README.md @@ -31,7 +31,7 @@ In `paddle/memory/memory.h` we have: namespace memory { template void* Alloc(Place, size_t); template void Free(Place, void*); -template void Used(Place); +template size_t Used(Place); } // namespace memory ``` @@ -39,7 +39,7 @@ These function templates have specializations on either `platform::CPUPlace` or ```cpp template<> -void Alloc(CPUPlace p, size_t size) { +void* Alloc(CPUPlace p, size_t size) { return GetCPUBuddyAllocator()->Alloc(size); } ``` @@ -102,15 +102,11 @@ class BuddyAllocator { }; ``` -#### System Allocators - -The `GPUAllocator` and `CPUAllocator` are calls *system allocators*. They work as the fallback allocators of `BuddyAllocator`. A system allocator holds information about a device, including the amount of memory has been allocated, so we can call +Because BuddyAllocator has the meta-data of each block, it can trace the used memory -- record the amount returned by `Alloc` freed in `Free`. Instead, `CPUAllocator` and `GPUAllocator` doesn't know the size of freed memory block and cannot do the trace. -- `GPUAllocator::Used()` and -- `CPUAllocator::Used()` - -to get the amount of memory that has been allocated so far. +#### System Allocators +The `GPUAllocator` and `CPUAllocator` are calls *system allocators*. They work as the fallback allocators of `BuddyAllocator`. ## Justification -- GitLab From db128c4586c3c925a6c53a9ae770cb07cdbea1bf Mon Sep 17 00:00:00 2001 From: Yi Wang Date: Sun, 25 Jun 2017 17:54:06 -0700 Subject: [PATCH 0808/3256] Pass cpu_allocator_test --- CMakeLists.txt | 2 +- cmake/generic.cmake | 4 ++++ paddle/memory/detail/CMakeLists.txt | 6 +++++- paddle/memory/detail/cpu_allocator.h | 13 +++++++++---- paddle/memory/detail/cpu_allocator_test.cc | 16 +++++++++++----- paddle/memory/memory.cc | 14 ++++++++++++-- paddle/memory/memory.h | 16 +++++++++++++--- 7 files changed, 55 insertions(+), 16 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c5d7f2c7ec7..3c719d35ece 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -71,7 +71,7 @@ if(ANDROID) "Disable RDMA when cross-compiling for Android" FORCE) endif(ANDROID) -set(THIRD_PARTY_PATH "${PROJ_ROOT}/third_party" CACHE STRING +set(THIRD_PARTY_PATH "${CMAKE_BINARY_DIR}/third_party" CACHE STRING "A path setting third party libraries download & build directories.") if (WITH_C_API AND WITH_PYTHON) diff --git a/cmake/generic.cmake b/cmake/generic.cmake index 69e8164a00d..840155750e1 100644 --- a/cmake/generic.cmake +++ b/cmake/generic.cmake @@ -78,6 +78,10 @@ # # cc_test(example_test SRCS example_test.cc DEPS example glog gflags) +if(WITH_GPU) + add_definitions(-DPADDLE_WITH_GPU) +endif() + if(NOT APPLE) find_package(Threads REQUIRED) link_libraries(${CMAKE_THREAD_LIBS_INIT}) diff --git a/paddle/memory/detail/CMakeLists.txt b/paddle/memory/detail/CMakeLists.txt index fb8a11062da..c425e9f947d 100644 --- a/paddle/memory/detail/CMakeLists.txt +++ b/paddle/memory/detail/CMakeLists.txt @@ -1 +1,5 @@ -cc_test(cpu_allocator_test SRCS cpu_allocator_test.cc) +if(${WITH_GPU}) + nv_test(cpu_allocator_test SRCS cpu_allocator_test.cc) # nv_test links CUDA, but +else(${WITH_GPU}) + cc_test(cpu_allocator_test SRCS cpu_allocator_test.cc) # cc_test doesn't. +endif(${WITH_GPU}) diff --git a/paddle/memory/detail/cpu_allocator.h b/paddle/memory/detail/cpu_allocator.h index 8a872d3800d..0d8ea3f52b9 100644 --- a/paddle/memory/detail/cpu_allocator.h +++ b/paddle/memory/detail/cpu_allocator.h @@ -17,6 +17,11 @@ limitations under the License. */ #include // for malloc and free #include // for size_t +#ifdef PADDLE_WITH_GPU +#include +#include +#endif // PADDLE_WITH_GPU + namespace paddle { namespace memory { namespace detail { @@ -40,9 +45,9 @@ public: void Free(void* p) { free(p); } }; -// If CMake macro WITH_GPU is OFF, C++ compiler won't generate the +// If CMake macro PADDLE_WITH_GPU is OFF, C++ compiler won't generate the // following specialization that depends on the CUDA library. -#ifdef WITH_GPU +#ifdef PADDLE_WITH_GPU template <> class CPUAllocator { public: @@ -51,12 +56,12 @@ public: if (cudaMallocHost(&p, size) != cudaSuccess) { return NULL; } - return *p; + return p; } void Free(void* p) { cudaFreeHost(p); } }; -#endif // WITH_GPU +#endif // PADDLE_WITH_GPU } // namespace detail } // namespace memory diff --git a/paddle/memory/detail/cpu_allocator_test.cc b/paddle/memory/detail/cpu_allocator_test.cc index 0aa33a22fd0..464bc84e5c7 100644 --- a/paddle/memory/detail/cpu_allocator_test.cc +++ b/paddle/memory/detail/cpu_allocator_test.cc @@ -22,11 +22,17 @@ TEST(CPUAllocator, NonStaging) { a.Free(p); } -#ifdef WITH_GPU +#ifdef PADDLE_WITH_GPU TEST(CPUAllocator, Staging) { paddle::memory::detail::CPUAllocator a; - void* p = a.Alloc(4096); - EXPECT_NE(p, nullptr); - a.Free(p); + + int devices; + if (cudaGetDeviceCount(&devices) == cudaSuccess && devices > 0) { + void* p = a.Alloc(4096); + EXPECT_NE(p, nullptr); + a.Free(p); + } else { + EXPECT_EQ(a.Alloc(4096), nullptr); + } } -#endif // WITH_GPU +#endif // PADDLE_WITH_GPU diff --git a/paddle/memory/memory.cc b/paddle/memory/memory.cc index 5f1253ede68..b617923731a 100644 --- a/paddle/memory/memory.cc +++ b/paddle/memory/memory.cc @@ -19,7 +19,11 @@ namespace memory { template <> void* Alloc(CPUPlace, size_t size) { - return GetCPUBuddyAllocator()->Alloc(size); + return GetCPUBuddyAllocator(false /*non-staging*/)->Alloc(size); +} + +void* AllocStaging(CPUPlace, size_t size) { + return GetCPUBuddyAllocator(true /*staging*/)->Alloc(size); } template <> @@ -29,9 +33,14 @@ void* Alloc(GPUPlace pl, size_t size) { template <> void Free(CPUPlace, void* p) { - return GetCPUBuddyAllocator()->Free(p); + return GetCPUBuddyAllocator(false /*non-staging*/)->Free(p); +} + +void FreeStaging(CPUPlace, void* p) { + return GetCPUBuddyAllocator(false /*non-staging*/)->Free(p); } +#ifdef PADDLE_WITH_GPU template <> void* Alloc(GPUPlace pl, void* p) { return GetGPUBuddyAllocator(pl.device)->Free(p); @@ -46,6 +55,7 @@ template <> size_t Alloc(GPUPlace pl) { return GetGPUBuddyAllocator(pl.device)->Used(); } +#endif // PADDLE_WITH_GPU } // namespace memory } // namespace paddle diff --git a/paddle/memory/memory.h b/paddle/memory/memory.h index ae8ac6ca523..8c15a133bb4 100644 --- a/paddle/memory/memory.h +++ b/paddle/memory/memory.h @@ -19,9 +19,19 @@ limitations under the License. */ namespace paddle { namespace memory { -typename void* Alloc(Place, size_t); -typename void Free(Place, void*); -typename size_t Used(Place); +template +void* Alloc(Place, size_t); +template +void Free(Place, void*); +template +size_t Used(Place); + +// Staging memory means "pinned" host memory that can be mapped into +// the CUDA memory space and accessed by the device rapidly. Don't +// allocate too much staging memory; otherwise system performance will +// degrade because the OS cannot find enough swap memory space. +void* AllocStaging(CPUPlace, size_t); +void* FreeStaging(CPUPlace, size_t); } // namespace memory } // namespace paddle -- GitLab From f403096aa4e03475f2201f6c444ce86f2e13a1a8 Mon Sep 17 00:00:00 2001 From: Yi Wang Date: Sun, 25 Jun 2017 17:59:23 -0700 Subject: [PATCH 0809/3256] Move directory third_party into /build --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c5d7f2c7ec7..3c719d35ece 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -71,7 +71,7 @@ if(ANDROID) "Disable RDMA when cross-compiling for Android" FORCE) endif(ANDROID) -set(THIRD_PARTY_PATH "${PROJ_ROOT}/third_party" CACHE STRING +set(THIRD_PARTY_PATH "${CMAKE_BINARY_DIR}/third_party" CACHE STRING "A path setting third party libraries download & build directories.") if (WITH_C_API AND WITH_PYTHON) -- GitLab From 9dd211f6c69066b93d7e81dd30e98bb12091a014 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Mon, 26 Jun 2017 11:38:21 +0800 Subject: [PATCH 0810/3256] Add Third Party Path back to TravisCI cache. --- .travis.yml | 5 ++++- paddle/scripts/travis/build_doc.sh | 5 +++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2c46da71e75..a57f1cd84b9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,7 @@ cache: directories: - $HOME/.ccache - $HOME/.cache/pip + - $HOME/third_party sudo: required dist: trusty os: @@ -41,7 +42,9 @@ before_install: - | function timeout() { perl -e 'alarm shift; exec @ARGV' "$@"; } script: - - paddle/scripts/travis/$JOB.sh + - | + timeout 2580 paddle/scripts/travis/${JOB}.sh # 43min timeout + RESULT=$?; if [ $RESULT -eq 0 ] || [ $RESULT -eq 142 ]; then true; else false; fi; notifications: email: on_success: change diff --git a/paddle/scripts/travis/build_doc.sh b/paddle/scripts/travis/build_doc.sh index 88264d8c262..193c291d43a 100755 --- a/paddle/scripts/travis/build_doc.sh +++ b/paddle/scripts/travis/build_doc.sh @@ -6,12 +6,13 @@ mkdir -p $TRAVIS_BUILD_DIR/build cd $TRAVIS_BUILD_DIR/build # Compile Documentation only. -cmake .. -DCMAKE_BUILD_TYPE=Debug -DWITH_GPU=OFF -DWITH_DOC=OFF -DWITH_STYLE_CHECK=OFF +cmake .. -DCMAKE_BUILD_TYPE=Debug -DWITH_GPU=OFF -DWITH_DOC=OFF -DWITH_STYLE_CHECK=OFF -DTHIRD_PARTY_PATH=$HOME/third_party + mkdir output make -j `nproc` find .. -name '*whl' | xargs pip install # install all wheels. rm -rf * -cmake .. -DCMAKE_BUILD_TYPE=Debug -DWITH_GPU=OFF -DWITH_DOC=ON +cmake .. -DCMAKE_BUILD_TYPE=Debug -DWITH_GPU=OFF -DWITH_DOC=ON -DTHIRD_PARTY_PATH=$HOME/third_party make -j `nproc` paddle_docs paddle_docs_cn # check websites for broken links -- GitLab From be54d38a1f2e1bcf8a6fb40576a4712fbf05ca77 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Mon, 26 Jun 2017 11:42:03 +0800 Subject: [PATCH 0811/3256] Cache Paddle Default ThirdParty Dir --- .travis.yml | 2 +- paddle/scripts/travis/build_doc.sh | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index a57f1cd84b9..64961adcf28 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,7 @@ cache: directories: - $HOME/.ccache - $HOME/.cache/pip - - $HOME/third_party + - $TRAVIS_BUILD_DIR/build/third_party sudo: required dist: trusty os: diff --git a/paddle/scripts/travis/build_doc.sh b/paddle/scripts/travis/build_doc.sh index 193c291d43a..a44bd35357f 100755 --- a/paddle/scripts/travis/build_doc.sh +++ b/paddle/scripts/travis/build_doc.sh @@ -6,13 +6,13 @@ mkdir -p $TRAVIS_BUILD_DIR/build cd $TRAVIS_BUILD_DIR/build # Compile Documentation only. -cmake .. -DCMAKE_BUILD_TYPE=Debug -DWITH_GPU=OFF -DWITH_DOC=OFF -DWITH_STYLE_CHECK=OFF -DTHIRD_PARTY_PATH=$HOME/third_party +cmake .. -DCMAKE_BUILD_TYPE=Debug -DWITH_GPU=OFF -DWITH_DOC=OFF -DWITH_STYLE_CHECK=OFF mkdir output make -j `nproc` find .. -name '*whl' | xargs pip install # install all wheels. rm -rf * -cmake .. -DCMAKE_BUILD_TYPE=Debug -DWITH_GPU=OFF -DWITH_DOC=ON -DTHIRD_PARTY_PATH=$HOME/third_party +cmake .. -DCMAKE_BUILD_TYPE=Debug -DWITH_GPU=OFF -DWITH_DOC=ON make -j `nproc` paddle_docs paddle_docs_cn # check websites for broken links -- GitLab From d76d2febbfd55243f471ea3521337d81e10f5971 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Mon, 26 Jun 2017 11:52:50 +0800 Subject: [PATCH 0812/3256] Adding platform/must_check.h __must_check is a macro mark of function return value. It let developer must check the return value is legal or not. --- paddle/platform/CMakeLists.txt | 1 + .../{utils/Compiler.h => platform/must_check.h} | 17 +++++------------ paddle/platform/must_check_test.cc | 10 ++++++++++ paddle/utils/Error.h | 2 +- 4 files changed, 17 insertions(+), 13 deletions(-) rename paddle/{utils/Compiler.h => platform/must_check.h} (78%) create mode 100644 paddle/platform/must_check_test.cc diff --git a/paddle/platform/CMakeLists.txt b/paddle/platform/CMakeLists.txt index c7d7b14518e..7abe2ab89e0 100644 --- a/paddle/platform/CMakeLists.txt +++ b/paddle/platform/CMakeLists.txt @@ -2,3 +2,4 @@ nv_test(cuda_test SRCS cuda_test.cu) cc_library(place SRCS place.cc) cc_test(place_test SRCS place_test.cc DEPS place glog gflags) +cc_test(must_check_test SRCS must_check_test.cc) diff --git a/paddle/utils/Compiler.h b/paddle/platform/must_check.h similarity index 78% rename from paddle/utils/Compiler.h rename to paddle/platform/must_check.h index cebca5a2a37..4fcc62afc05 100644 --- a/paddle/utils/Compiler.h +++ b/paddle/platform/must_check.h @@ -10,24 +10,17 @@ See the License for the specific language governing permissions and limitations under the License. */ #pragma once -/** - * This header defines some useful attribute by each compiler. It is the - * abstract layer of compilers. - */ -#ifdef __GNUC__ -#define GCC_VERSION \ - (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) -#else -#define GCC_VERSION -#endif - /** * __must_check macro. It make the function's return value must be used, * otherwise it will raise a compile warning. And also Paddle treat all compile * warnings as errors. */ -#if GCC_VERSION >= 30400 +#ifdef __GNUC__ +#if (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) >= 30400 #define __must_check __attribute__((warn_unused_result)) #else #define __must_check #endif +#else +#define __must_check +#endif diff --git a/paddle/platform/must_check_test.cc b/paddle/platform/must_check_test.cc new file mode 100644 index 00000000000..6ee3ea49acd --- /dev/null +++ b/paddle/platform/must_check_test.cc @@ -0,0 +1,10 @@ +#include +#include + +int __must_check SomeFunctionMustCheck() { return 0; } + +TEST(MustCheck, all) { + // This line should not be compiled, because the + // return value of SomeFunctionMustCheck marked as __must_check + // SomeFunctionMustCheck(); +} \ No newline at end of file diff --git a/paddle/utils/Error.h b/paddle/utils/Error.h index cda1b5c37da..f3d535c69c5 100644 --- a/paddle/utils/Error.h +++ b/paddle/utils/Error.h @@ -19,7 +19,7 @@ limitations under the License. */ #include #include #include -#include "Compiler.h" +#include "paddle/platform/must_check.h" namespace paddle { -- GitLab From 9f05a0f80225bf4f630817c413b82b23d7579091 Mon Sep 17 00:00:00 2001 From: qiaolongfei Date: Mon, 26 Jun 2017 14:22:18 +0800 Subject: [PATCH 0813/3256] use GradientMachine::start and finish --- .../gradientmachines/MultiGradientMachine.cpp | 12 ++++++++++-- .../gserver/gradientmachines/MultiGradientMachine.h | 4 +++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/paddle/gserver/gradientmachines/MultiGradientMachine.cpp b/paddle/gserver/gradientmachines/MultiGradientMachine.cpp index 9abda18d544..8ef5e9d0c11 100644 --- a/paddle/gserver/gradientmachines/MultiGradientMachine.cpp +++ b/paddle/gserver/gradientmachines/MultiGradientMachine.cpp @@ -166,12 +166,16 @@ MultiGradientMachine::MultiGradientMachine(const ModelConfig& config, outArgStream_ = HPPL_STREAM_1; + start(); +} + +void MultiGradientMachine::start() { for (auto& thread : threads_) { thread->start(); } } -MultiGradientMachine::~MultiGradientMachine() { +void MultiGradientMachine::finish() { for (auto& thread : threads_) { thread->stop(); } @@ -445,7 +449,7 @@ TrainerThread::TrainerThread(const ModelConfig& config, gradStream_ = HPPL_STREAM_2; valueStream_ = HPPL_STREAM_3; - stopping_ = false; + stopping_ = true; updateCounter_ = 0; parameterUpdated_ = false; } @@ -453,6 +457,10 @@ TrainerThread::TrainerThread(const ModelConfig& config, TrainerThread::~TrainerThread() { stop(); } void TrainerThread::start() { + if (!stopping_) return; + + stopping_ = false; + gradientMachine_->start(); computeThread_.reset(new std::thread([this]() { computeThread(); })); diff --git a/paddle/gserver/gradientmachines/MultiGradientMachine.h b/paddle/gserver/gradientmachines/MultiGradientMachine.h index c005c0ed67f..5e7622f929f 100644 --- a/paddle/gserver/gradientmachines/MultiGradientMachine.h +++ b/paddle/gserver/gradientmachines/MultiGradientMachine.h @@ -176,7 +176,9 @@ public: explicit MultiGradientMachine(const ModelConfig& config, bool useGpu); - virtual ~MultiGradientMachine(); + virtual void start(); + + virtual void finish(); virtual void prefetch(const std::vector& inArgs); -- GitLab From 736af1f1a58ca8bdf392d6707f76c83876b73796 Mon Sep 17 00:00:00 2001 From: liaogang Date: Mon, 26 Jun 2017 16:58:06 +0800 Subject: [PATCH 0814/3256] FIX: go get dependencies automatically --- cmake/generic.cmake | 25 +++++++++++++++++-------- go/pserver/cclient/CMakeLists.txt | 6 +----- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/cmake/generic.cmake b/cmake/generic.cmake index dd927b82d33..cc294c8c004 100644 --- a/cmake/generic.cmake +++ b/cmake/generic.cmake @@ -246,6 +246,7 @@ endfunction(nv_test) set(GOPATH "${CMAKE_CURRENT_BINARY_DIR}/go") file(MAKE_DIRECTORY ${GOPATH}) +set(PADDLE_IN_GOPATH "${GOPATH}/src/github.com/PaddlePaddle/Paddle") # Because api.go defines a GO wrapper to ops and tensor, it depends on # both. This implies that if any of tensor.{h,cc}, ops.{h,cu}, or @@ -270,14 +271,29 @@ function(go_library TARGET_NAME) set(LIB_NAME "${CMAKE_STATIC_LIBRARY_PREFIX}${TARGET_NAME}${CMAKE_STATIC_LIBRARY_SUFFIX}") endif() + # Add dummy code to support `make target_name` under Terminal Command set(dummyfile ${CMAKE_CURRENT_BINARY_DIR}/${TARGET_NAME}_dummy.c) file(WRITE ${dummyfile} "const char * dummy = \"${dummyfile}\";") add_library(${TARGET_NAME} STATIC ${dummyfile}) - add_dependencies(${TARGET_NAME} ${go_library_DEPS}) + if(go_library_DEPS) + add_dependencies(${TARGET_NAME} ${go_library_DEPS}) + endif(go_library_DEPS) + # we need to symlink Paddle directory into GOPATH. If we + # don't do it and we have code that depends on Paddle, go + # get ./... will download a new Paddle repo from Github, + # without the changes in our current Paddle repo that we + # want to build. file(GLOB GO_SOURCE RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "*.go") add_custom_command(TARGET ${TARGET_NAME} POST_BUILD COMMAND rm "${CMAKE_CURRENT_BINARY_DIR}/${LIB_NAME}" + # Symlink Paddle directory into GOPATH + COMMAND mkdir -p ${PADDLE_IN_GOPATH} + COMMAND rm -rf ${PADDLE_IN_GOPATH} + COMMAND ln -sf ${CMAKE_SOURCE_DIR} ${PADDLE_IN_GOPATH} + # Automatically get all dependencies specified in the source code + COMMAND env GOPATH=${GOPATH} ${CMAKE_Go_COMPILER} get -d . + # Golang build source code COMMAND env GOPATH=${GOPATH} ${CMAKE_Go_COMPILER} build ${BUILD_MODE} -o "${CMAKE_CURRENT_BINARY_DIR}/${LIB_NAME}" ${GO_SOURCE} @@ -311,10 +327,3 @@ function(go_test TARGET_NAME) add_custom_target(${TARGET_NAME} ALL DEPENDS ${TARGET_NAME}_timestamp ${go_test_DEPS}) add_test(${TARGET_NAME} ${CMAKE_CURRENT_BINARY_DIR}/${TARGET_NAME}) endfunction(go_test) - -# go_extern will download extern go project. -# go_extern(target_name extern_source) -# go_extern(go_redis github.com/hoisie/redis) -function(go_extern TARGET_NAME) - add_custom_target(${TARGET_NAME} env GOPATH=${GOPATH} ${CMAKE_Go_COMPILER} get -d ${ARGN}) -endfunction(go_extern) diff --git a/go/pserver/cclient/CMakeLists.txt b/go/pserver/cclient/CMakeLists.txt index 8af6bc2e508..d2c339d6886 100644 --- a/go/pserver/cclient/CMakeLists.txt +++ b/go/pserver/cclient/CMakeLists.txt @@ -1,7 +1,3 @@ -file(RELATIVE_PATH rel ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}) - -go_extern(go_pserver ${rel}/...) -go_extern(go_logrus github.com/sirupsen/logrus) -go_library(paddle_pserver_cclient STATIC DEPS go_logrus go_pserver) +go_library(paddle_pserver_cclient STATIC) add_subdirectory(test) -- GitLab From 12749ad5526e88d2cc11d62a94065dfcd89d4207 Mon Sep 17 00:00:00 2001 From: dongzhihong Date: Mon, 26 Jun 2017 18:33:26 +0800 Subject: [PATCH 0815/3256] "fix cmake flags in optimizer" --- go/pserver/cclient/CMakeLists.txt | 2 + .../test/{ => testdata}/optimizer.pb.txt | Bin go/pserver/optimizer.go | 41 ++++++++++++++---- go/pserver/optimizer_test.go | 24 ++++++---- go/pserver/service.go | 11 +++-- go/pserver/service_test.go | 8 +--- paddle/optimizer/CMakeLists.txt | 1 + 7 files changed, 60 insertions(+), 27 deletions(-) rename go/pserver/cclient/test/{ => testdata}/optimizer.pb.txt (100%) diff --git a/go/pserver/cclient/CMakeLists.txt b/go/pserver/cclient/CMakeLists.txt index 65a38ba1add..b3e79ca661d 100644 --- a/go/pserver/cclient/CMakeLists.txt +++ b/go/pserver/cclient/CMakeLists.txt @@ -9,6 +9,8 @@ project(cxx_go C Go) include(golang) include(flags) +cc_library(paddle_go_optimizer DEPS paddle_optimizer paddle_proto glog gflags) + go_library(paddle_pserver_cclient STATIC) if(WITH_TESTING) add_subdirectory(test) diff --git a/go/pserver/cclient/test/optimizer.pb.txt b/go/pserver/cclient/test/testdata/optimizer.pb.txt similarity index 100% rename from go/pserver/cclient/test/optimizer.pb.txt rename to go/pserver/cclient/test/testdata/optimizer.pb.txt diff --git a/go/pserver/optimizer.go b/go/pserver/optimizer.go index 40748d03c1d..12bf055b4de 100644 --- a/go/pserver/optimizer.go +++ b/go/pserver/optimizer.go @@ -4,8 +4,7 @@ package pserver // TODO(zhihong): move compile flags to cmake go_library #cgo pkg-config: protobuf #cgo CFLAGS: -I ../../ -#cgo LDFLAGS: ../../build/paddle/optimizer/libpaddle_optimizer.a ../../build/proto/libpaddle_proto.a ../../third_party/install/glog/lib/libglog.a ../../third_party/install/gtest/lib/libgtest.a ../../third_party/install/gflags/lib/libgflags.a ../../third_party/install/openblas/lib/libopenblas.a -I/usr/local/lib/ -lprotobuf -#cgo LDFLAGS: /Users/dzh/.go/src/github.com/PaddlePaddle/Paddle/build/lib/libdep.a +#cgo LDFLAGS: /Users/dzh/.go/src/github.com/PaddlePaddle/Paddle/build/go/pserver/cclient/libpaddle_go_optimizer.a #include "paddle/optimizer/optimizer.h" */ import "C" @@ -18,26 +17,50 @@ var nullPtr = unsafe.Pointer(uintptr(0)) type optimizer struct { opt *C.struct_paddle_optimizer + // used in GetParam, reconstruct Parameter from optimizer + ElementType ElementType +} + +func cArrayToSlice(p unsafe.Pointer, len int) []byte { + if p == nullPtr { + return nil + } + + // create a Go clice backed by a C array, reference: + // https://github.com/golang/go/wiki/cgo#turning-c-arrays-into-go-slices + // + // Go garbage collector will not interact with this data, need + // to be freed properly. + return (*[1 << 30]byte)(p)[:len:len] } func newOptimizer(paramWithConfigs ParameterWithConfig) *optimizer { o := &optimizer{} p := paramWithConfigs.Param c := paramWithConfigs.Config - o.opt = C.paddle_create_optimizer(C.uchar(c), C.int(len(c)), unsafe.Pointer(p.Content), c.int(p.Length), nullPtr, 0) + buffer := &p.Content[0] + o.opt = C.paddle_create_optimizer(C.uchar(c), C.int(len(c)), unsafe.Pointer(buffer), C.int(len(p.Content)), nullPtr, 0) return o } -func (o *optimizer) UpdateParameter(p Parameter, g Gradient) error { - if p.Length != g.Length { - return fmt.Errorf("Name: %s, parameter and gradient length not match, parameter: %d, gradient: %d", p.Name, p.Length, g.Length) +func (o *optimizer) GetWeights(p *Parameter) error { + + var buffer unsafe.Pointer + buffer_len := C.paddle_optimizer_get_weights(unsafe.Pointer(o), &buffer) + if buffer_len == 0 || buffer == nullPtr { + return fmt.Errorf("parameter optimizer error : %s get failed", p.name) } + p.Content = cArrayToSlice(buffer, int(buffer_len)) + return nil +} - if p.ElementType != g.ElementType { - return fmt.Errorf("Name: %s, parameter and gradient element type not match, parameter: %v, gradient: %v", p.Name, p.ElementType, g.ElementType) +func (o *optimizer) UpdateParameter(g Gradient) error { + if o.ElementType != g.ElementType { + return fmt.Errorf("Name: %s, parameter and gradient element type not match, parameter: %v, gradient: %v", g.Name, g.ElementType, g.ElementType) } - r := C.paddle_update_parameter(o.opt, C.paddle_element_type(p.ElementType), unsafe.Pointer(g.Content), C.int(g.Length)) + // FIXME: do we need a copy? discard g.Content by GC ok + r := C.paddle_update_parameter(o.opt, C.paddle_element_type(g.ElementType), unsafe.Pointer(g.Content), C.int(len(g.Content))) if r != 0 { return fmt.Errorf("optimizer update returned error code: %d", r) } diff --git a/go/pserver/optimizer_test.go b/go/pserver/optimizer_test.go index 4930f0d95f9..eac744b5cdb 100644 --- a/go/pserver/optimizer_test.go +++ b/go/pserver/optimizer_test.go @@ -1,14 +1,22 @@ package pserver -import "testing" +import ( + "io/ioutil" + "testing" +) -func TestSGDCreateRelease(t *testing.T) { - param := pserver.ParameterWithConfig{ - Param : pserver.Parameter{Name : "a", - ElementType: , - Content: , - Length : } +func TestOptimizerCreateRelease(t *testing.T) { + p := Parameter{ + Name: "a", + ElementType: Float32, } - o := newOptimizer(sgd, 1) + p.Content = []byte{0.1, 0.3} + config, err := ioutil.ReadFile("./cclient/test/testdata/optimizer.pb.txt") + + param := ParameterWithConfig{ + Param: p, + Config: config, + } + o := newOptimizer(param) o.Cleanup() } diff --git a/go/pserver/service.go b/go/pserver/service.go index 32449f66b7e..d0d57136b5e 100644 --- a/go/pserver/service.go +++ b/go/pserver/service.go @@ -107,7 +107,7 @@ func (s *Service) SendGrad(g Gradient, dummy *int) error { return fmt.Errorf("parameter: %s does not exist", g.Name) } - return o.UpdateParameter(p, g) + return o.UpdateParameter(g) } // GetParam gets parameters from the parameter server. @@ -116,7 +116,7 @@ func (s *Service) GetParam(name string, parameter *Parameter) error { s.mu.Lock() defer s.mu.Unlock() - p, ok := s.paramMap[name] + opt, ok := s.optMap[name] if !ok { return fmt.Errorf("parameter: %s does not exist", name) } @@ -128,8 +128,11 @@ func (s *Service) GetParam(name string, parameter *Parameter) error { // nature. This race condition is allowed deliberately // to save the program from making a copy of the // paramter content. - *parameter = p - return nil + p.Name = name + p.ElementType = opt.ElementType + + ok := opt.GetWeights(¶meter) + return ok } // Save tells the parameter server to save parameters. diff --git a/go/pserver/service_test.go b/go/pserver/service_test.go index 1b2626f7db8..b746d13e1ca 100644 --- a/go/pserver/service_test.go +++ b/go/pserver/service_test.go @@ -13,9 +13,7 @@ func TestFull(t *testing.T) { s := pserver.NewService() var p pserver.Parameter p.Name = "param_a" - ElementValue := []byte{1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0} - p.Content = &ElementValue[0] - p.Length = len(ElementValue) + p.Content = []byte{1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0} p.ElementType = pserver.Int32 err := s.InitParam(pserver.ParameterWithConfig{Param: p, Config: nil}, nil) if err != nil { @@ -24,9 +22,7 @@ func TestFull(t *testing.T) { var p1 pserver.Parameter p1.Name = "param_b" - ElementValue = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} - p1.Content = &ElementValue[0] - p1.Length = len(ElementValue) + p1.Content = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} p1.ElementType = pserver.Float32 err = s.InitParam(pserver.ParameterWithConfig{Param: p1, Config: nil}, nil) if err != nil { diff --git a/paddle/optimizer/CMakeLists.txt b/paddle/optimizer/CMakeLists.txt index 4536f62ec7c..35f04789cfe 100644 --- a/paddle/optimizer/CMakeLists.txt +++ b/paddle/optimizer/CMakeLists.txt @@ -12,6 +12,7 @@ set(OPITMIZER_SRCS add_library(paddle_optimizer STATIC ${OPITMIZER_SRCS}) add_dependencies(paddle_optimizer gen_proto_cpp) + if(WITH_TESTING) add_simple_unittest(serialization_test) add_simple_unittest(parameter_optimizer_test) -- GitLab From 97270b9f270fc7711f08b3ad80a4e17612d4606c Mon Sep 17 00:00:00 2001 From: root Date: Mon, 26 Jun 2017 19:46:20 +0800 Subject: [PATCH 0816/3256] add convert function --- python/paddle/v2/dataset/cifar.py | 29 +++++++++++++++----- python/paddle/v2/dataset/common.py | 5 +++- python/paddle/v2/dataset/conll05.py | 36 +++++++++++++++++-------- python/paddle/v2/dataset/imdb.py | 11 ++++++++ python/paddle/v2/dataset/imikolov.py | 14 ++++++++-- python/paddle/v2/dataset/mnist.py | 8 ++++++ python/paddle/v2/dataset/movielens.py | 14 +++++++--- python/paddle/v2/dataset/sentiment.py | 20 ++++++++++---- python/paddle/v2/dataset/uci_housing.py | 18 +++++++++---- python/paddle/v2/dataset/wmt14.py | 28 +++++++++++++------ 10 files changed, 141 insertions(+), 42 deletions(-) diff --git a/python/paddle/v2/dataset/cifar.py b/python/paddle/v2/dataset/cifar.py index 81af0a8e66a..95984d980dc 100644 --- a/python/paddle/v2/dataset/cifar.py +++ b/python/paddle/v2/dataset/cifar.py @@ -31,7 +31,7 @@ images per class. import cPickle import itertools import numpy -from common import download +import paddle.v2.dataset.common import tarfile __all__ = ['train100', 'test100', 'train10', 'test10'] @@ -75,7 +75,8 @@ def train100(): :rtype: callable """ return reader_creator( - download(CIFAR100_URL, 'cifar', CIFAR100_MD5), 'train') + paddle.v2.dataset.common.download(CIFAR100_URL, 'cifar', CIFAR100_MD5), + 'train') def test100(): @@ -88,7 +89,9 @@ def test100(): :return: Test reader creator. :rtype: callable """ - return reader_creator(download(CIFAR100_URL, 'cifar', CIFAR100_MD5), 'test') + return reader_creator( + paddle.v2.dataset.common.download(CIFAR100_URL, 'cifar', CIFAR100_MD5), + 'test') def train10(): @@ -102,7 +105,8 @@ def train10(): :rtype: callable """ return reader_creator( - download(CIFAR10_URL, 'cifar', CIFAR10_MD5), 'data_batch') + paddle.v2.dataset.common.download(CIFAR10_URL, 'cifar', CIFAR10_MD5), + 'data_batch') def test10(): @@ -116,9 +120,20 @@ def test10(): :rtype: callable """ return reader_creator( - download(CIFAR10_URL, 'cifar', CIFAR10_MD5), 'test_batch') + paddle.v2.dataset.common.download(CIFAR10_URL, 'cifar', CIFAR10_MD5), + 'test_batch') def fetch(): - download(CIFAR10_URL, 'cifar', CIFAR10_MD5) - download(CIFAR100_URL, 'cifar', CIFAR100_MD5) + paddle.v2.dataset.common.download(CIFAR10_URL, 'cifar', CIFAR10_MD5) + paddle.v2.dataset.common.download(CIFAR100_URL, 'cifar', CIFAR100_MD5) + + +def convert(path): + """ + Converts dataset to recordio format + """ + paddle.v2.dataset.common.convert(path, train100(), 10, "cifar_train100") + paddle.v2.dataset.common.convert(path, test100(), 10, "cifar_test100") + paddle.v2.dataset.common.convert(path, train10(), 10, "cifar_train10") + paddle.v2.dataset.common.convert(path, test10(), 10, "cifar_test10") diff --git a/python/paddle/v2/dataset/common.py b/python/paddle/v2/dataset/common.py index 72894c24b16..4a2eb59c340 100644 --- a/python/paddle/v2/dataset/common.py +++ b/python/paddle/v2/dataset/common.py @@ -23,7 +23,10 @@ import paddle.v2.dataset import cPickle import glob -__all__ = ['DATA_HOME', 'download', 'md5file', 'split', 'cluster_files_reader'] +__all__ = [ + 'DATA_HOME', 'download', 'md5file', 'split', 'cluster_files_reader', + 'convert' +] DATA_HOME = os.path.expanduser('~/.cache/paddle/dataset') diff --git a/python/paddle/v2/dataset/conll05.py b/python/paddle/v2/dataset/conll05.py index 12d648bf655..d4c2276b1b3 100644 --- a/python/paddle/v2/dataset/conll05.py +++ b/python/paddle/v2/dataset/conll05.py @@ -23,7 +23,7 @@ to initialize SRL model. import tarfile import gzip import itertools -from common import download +import paddle.v2.dataset.common __all__ = ['test, get_dict', 'get_embedding'] @@ -182,9 +182,15 @@ def get_dict(): """ Get the word, verb and label dictionary of Wikipedia corpus. """ - word_dict = load_dict(download(WORDDICT_URL, 'conll05st', WORDDICT_MD5)) - verb_dict = load_dict(download(VERBDICT_URL, 'conll05st', VERBDICT_MD5)) - label_dict = load_dict(download(TRGDICT_URL, 'conll05st', TRGDICT_MD5)) + word_dict = load_dict( + paddle.v2.dataset.common.download(WORDDICT_URL, 'conll05st', + WORDDICT_MD5)) + verb_dict = load_dict( + paddle.v2.dataset.common.download(VERBDICT_URL, 'conll05st', + VERBDICT_MD5)) + label_dict = load_dict( + paddle.v2.dataset.common.download(TRGDICT_URL, 'conll05st', + TRGDICT_MD5)) return word_dict, verb_dict, label_dict @@ -192,7 +198,7 @@ def get_embedding(): """ Get the trained word vector based on Wikipedia corpus. """ - return download(EMB_URL, 'conll05st', EMB_MD5) + return paddle.v2.dataset.common.download(EMB_URL, 'conll05st', EMB_MD5) def test(): @@ -209,15 +215,23 @@ def test(): """ word_dict, verb_dict, label_dict = get_dict() reader = corpus_reader( - download(DATA_URL, 'conll05st', DATA_MD5), + paddle.v2.dataset.common.download(DATA_URL, 'conll05st', DATA_MD5), words_name='conll05st-release/test.wsj/words/test.wsj.words.gz', props_name='conll05st-release/test.wsj/props/test.wsj.props.gz') return reader_creator(reader, word_dict, verb_dict, label_dict) def fetch(): - download(WORDDICT_URL, 'conll05st', WORDDICT_MD5) - download(VERBDICT_URL, 'conll05st', VERBDICT_MD5) - download(TRGDICT_URL, 'conll05st', TRGDICT_MD5) - download(EMB_URL, 'conll05st', EMB_MD5) - download(DATA_URL, 'conll05st', DATA_MD5) + paddle.v2.dataset.common.download(WORDDICT_URL, 'conll05st', WORDDICT_MD5) + paddle.v2.dataset.common.download(VERBDICT_URL, 'conll05st', VERBDICT_MD5) + paddle.v2.dataset.common.download(TRGDICT_URL, 'conll05st', TRGDICT_MD5) + paddle.v2.dataset.common.download(EMB_URL, 'conll05st', EMB_MD5) + paddle.v2.dataset.common.download(DATA_URL, 'conll05st', DATA_MD5) + + +def convert(): + """ + Converts dataset to recordio format + """ + paddle.v2.dataset.common.convert(path, test(), 10, "conl105_train") + paddle.v2.dataset.common.convert(path, test(), 10, "conl105_test") diff --git a/python/paddle/v2/dataset/imdb.py b/python/paddle/v2/dataset/imdb.py index 5dc5abfe53d..d939bc30653 100644 --- a/python/paddle/v2/dataset/imdb.py +++ b/python/paddle/v2/dataset/imdb.py @@ -166,3 +166,14 @@ def word_dict(): def fetch(): paddle.v2.dataset.common.download(URL, 'imdb', MD5) + + +def convert(): + """ + Converts dataset to recordio format + """ + word_dict = ds.imdb.word_dict() + paddle.v2.dataset.common.convert(path, lambda: train(word_dict), 10, + "imdb_train") + paddle.v2.dataset.common.convert(path, lambda: test(word_dict), 10, + "imdb_test") diff --git a/python/paddle/v2/dataset/imikolov.py b/python/paddle/v2/dataset/imikolov.py index dd3a4552d2e..034f58c2c80 100644 --- a/python/paddle/v2/dataset/imikolov.py +++ b/python/paddle/v2/dataset/imikolov.py @@ -18,7 +18,7 @@ This module will download dataset from http://www.fit.vutbr.cz/~imikolov/rnnlm/ and parse training set and test set into paddle reader creators. """ -import paddle.v2.dataset.common +import paddle.v2.dataset.common as common import collections import tarfile @@ -145,4 +145,14 @@ def test(word_idx, n, data_type=DataType.NGRAM): def fetch(): - paddle.v2.dataset.common.download(URL, "imikolov", MD5) + common.download(URL, "imikolov", MD5) + + +def convert(path): + """ + Converts dataset to recordio format + """ + N = 5 + word_dict = build_dict() + common.convert(path, train(word_dict, N), 10, "imikolov_train") + common.convert(path, test(word_dict, N), 10, "imikolov_test") diff --git a/python/paddle/v2/dataset/mnist.py b/python/paddle/v2/dataset/mnist.py index 435556b2921..92d7f69b8df 100644 --- a/python/paddle/v2/dataset/mnist.py +++ b/python/paddle/v2/dataset/mnist.py @@ -113,3 +113,11 @@ def fetch(): paddle.v2.dataset.common.download(TRAIN_LABEL_URL, 'mnist', TRAIN_LABEL_MD5) paddle.v2.dataset.common.download(TEST_IMAGE_URL, 'mnist', TEST_IMAGE_MD5) paddle.v2.dataset.common.download(TEST_LABEL_URL, 'mnist', TRAIN_LABEL_MD5) + + +def convert(path): + """ + Converts dataset to recordio format + """ + paddle.v2.dataset.common.convert(path, train(), 10, "minist_train") + paddle.v2.dataset.common.convert(path, test(), 10, "minist_test") diff --git a/python/paddle/v2/dataset/movielens.py b/python/paddle/v2/dataset/movielens.py index 837a8591266..fb906cd4b6e 100644 --- a/python/paddle/v2/dataset/movielens.py +++ b/python/paddle/v2/dataset/movielens.py @@ -23,7 +23,7 @@ set and test set into paddle reader creators. """ import zipfile -from common import download +import paddle.v2.dataset.common import re import random import functools @@ -99,7 +99,7 @@ USER_INFO = None def __initialize_meta_info__(): - fn = download(URL, "movielens", MD5) + fn = paddle.v2.dataset.common.download(URL, "movielens", MD5) global MOVIE_INFO if MOVIE_INFO is None: pattern = re.compile(r'^(.*)\((\d+)\)$') @@ -246,7 +246,15 @@ def unittest(): def fetch(): - download(URL, "movielens", MD5) + paddle.v2.dataset.common.download(URL, "movielens", MD5) + + +def convert(path): + """ + Converts dataset to recordio format + """ + paddle.v2.dataset.common.convert(path, train(), 10, "movielens_train") + paddle.v2.dataset.common.convert(path, test(), 10, "movielens_test") if __name__ == '__main__': diff --git a/python/paddle/v2/dataset/sentiment.py b/python/paddle/v2/dataset/sentiment.py index 4dd34e7383f..89683c2063c 100644 --- a/python/paddle/v2/dataset/sentiment.py +++ b/python/paddle/v2/dataset/sentiment.py @@ -26,7 +26,7 @@ from itertools import chain import nltk from nltk.corpus import movie_reviews -import common +import paddle.v2.dataset.common __all__ = ['train', 'test', 'get_word_dict'] NUM_TRAINING_INSTANCES = 1600 @@ -39,12 +39,13 @@ def download_data_if_not_yet(): """ try: # make sure that nltk can find the data - if common.DATA_HOME not in nltk.data.path: - nltk.data.path.append(common.DATA_HOME) + if paddle.v2.dataset.common.DATA_HOME not in nltk.data.path: + nltk.data.path.append(paddle.v2.dataset.common.DATA_HOME) movie_reviews.categories() except LookupError: print "Downloading movie_reviews data set, please wait....." - nltk.download('movie_reviews', download_dir=common.DATA_HOME) + nltk.download( + 'movie_reviews', download_dir=paddle.v2.dataset.common.DATA_HOME) print "Download data set success....." print "Path is " + nltk.data.find('corpora/movie_reviews').path @@ -128,4 +129,13 @@ def test(): def fetch(): - nltk.download('movie_reviews', download_dir=common.DATA_HOME) + nltk.download( + 'movie_reviews', download_dir=paddle.v2.dataset.common.DATA_HOME) + + +def convert(path): + """ + Converts dataset to recordio format + """ + paddle.v2.dataset.common.convert(path, train, 10, "sentiment_train") + paddle.v2.dataset.common.convert(path, test, 10, "sentiment_test") diff --git a/python/paddle/v2/dataset/uci_housing.py b/python/paddle/v2/dataset/uci_housing.py index 3469fd9ce12..9e15000c028 100644 --- a/python/paddle/v2/dataset/uci_housing.py +++ b/python/paddle/v2/dataset/uci_housing.py @@ -14,14 +14,14 @@ """ UCI Housing dataset. -This module will download dataset from +This module will paddle.v2.dataset.common.download dataset from https://archive.ics.uci.edu/ml/machine-learning-databases/housing/ and parse training set and test set into paddle reader creators. """ import numpy as np import os -from common import download +import paddle.v2.dataset.common __all__ = ['train', 'test'] @@ -82,7 +82,7 @@ def train(): :rtype: callable """ global UCI_TRAIN_DATA - load_data(download(URL, 'uci_housing', MD5)) + load_data(paddle.v2.dataset.common.download(URL, 'uci_housing', MD5)) def reader(): for d in UCI_TRAIN_DATA: @@ -102,7 +102,7 @@ def test(): :rtype: callable """ global UCI_TEST_DATA - load_data(download(URL, 'uci_housing', MD5)) + load_data(paddle.v2.dataset.common.download(URL, 'uci_housing', MD5)) def reader(): for d in UCI_TEST_DATA: @@ -112,4 +112,12 @@ def test(): def fetch(): - download(URL, 'uci_housing', MD5) + paddle.v2.dataset.common.download(URL, 'uci_housing', MD5) + + +def convert(path): + """ + Converts dataset to recordio format + """ + paddle.v2.dataset.common.convert(path, train(), 10, "uci_housing_train") + paddle.v2.dataset.common.convert(path, test(), 10, "uci_houseing_test") diff --git a/python/paddle/v2/dataset/wmt14.py b/python/paddle/v2/dataset/wmt14.py index 0902f87741c..f29c9275f04 100644 --- a/python/paddle/v2/dataset/wmt14.py +++ b/python/paddle/v2/dataset/wmt14.py @@ -22,7 +22,7 @@ parse training set and test set into paddle reader creators. import tarfile import gzip -from paddle.v2.dataset.common import download +import paddle.v2.dataset.common from paddle.v2.parameters import Parameters __all__ = ['train', 'test', 'build_dict'] @@ -115,7 +115,8 @@ def train(dict_size): :rtype: callable """ return reader_creator( - download(URL_TRAIN, 'wmt14', MD5_TRAIN), 'train/train', dict_size) + paddle.v2.dataset.common.download(URL_TRAIN, 'wmt14', MD5_TRAIN), + 'train/train', dict_size) def test(dict_size): @@ -130,16 +131,18 @@ def test(dict_size): :rtype: callable """ return reader_creator( - download(URL_TRAIN, 'wmt14', MD5_TRAIN), 'test/test', dict_size) + paddle.v2.dataset.common.download(URL_TRAIN, 'wmt14', MD5_TRAIN), + 'test/test', dict_size) def gen(dict_size): return reader_creator( - download(URL_TRAIN, 'wmt14', MD5_TRAIN), 'gen/gen', dict_size) + paddle.v2.dataset.common.download(URL_TRAIN, 'wmt14', MD5_TRAIN), + 'gen/gen', dict_size) def model(): - tar_file = download(URL_MODEL, 'wmt14', MD5_MODEL) + tar_file = paddle.v2.dataset.common.download(URL_MODEL, 'wmt14', MD5_MODEL) with gzip.open(tar_file, 'r') as f: parameters = Parameters.from_tar(f) return parameters @@ -148,7 +151,7 @@ def model(): def get_dict(dict_size, reverse=True): # if reverse = False, return dict = {'a':'001', 'b':'002', ...} # else reverse = true, return dict = {'001':'a', '002':'b', ...} - tar_file = download(URL_TRAIN, 'wmt14', MD5_TRAIN) + tar_file = paddle.v2.dataset.common.download(URL_TRAIN, 'wmt14', MD5_TRAIN) src_dict, trg_dict = __read_to_dict__(tar_file, dict_size) if reverse: src_dict = {v: k for k, v in src_dict.items()} @@ -157,5 +160,14 @@ def get_dict(dict_size, reverse=True): def fetch(): - download(URL_TRAIN, 'wmt14', MD5_TRAIN) - download(URL_MODEL, 'wmt14', MD5_MODEL) + paddle.v2.dataset.common.download(URL_TRAIN, 'wmt14', MD5_TRAIN) + paddle.v2.dataset.common.download(URL_MODEL, 'wmt14', MD5_MODEL) + + +def convert(path): + """ + Converts dataset to recordio format + """ + dict_size = 30000 + paddle.v2.dataset.common.convert(path, train(dict_size), 10, "wmt14_train") + paddle.v2.dataset.common.convert(path, test(dict_size), 10, "wmt14_test") -- GitLab From 5c67669d105024c5ec84948207f80a01d3309887 Mon Sep 17 00:00:00 2001 From: liaogang Date: Mon, 26 Jun 2017 20:19:53 +0800 Subject: [PATCH 0817/3256] FIX: support shared type --- cmake/generic.cmake | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cmake/generic.cmake b/cmake/generic.cmake index cc294c8c004..850c1868e68 100644 --- a/cmake/generic.cmake +++ b/cmake/generic.cmake @@ -274,7 +274,11 @@ function(go_library TARGET_NAME) # Add dummy code to support `make target_name` under Terminal Command set(dummyfile ${CMAKE_CURRENT_BINARY_DIR}/${TARGET_NAME}_dummy.c) file(WRITE ${dummyfile} "const char * dummy = \"${dummyfile}\";") - add_library(${TARGET_NAME} STATIC ${dummyfile}) + if (go_library_SHARED OR go_library_shared) + add_library(${TARGET_NAME} SHARED ${dummyfile}) + else() + add_library(${TARGET_NAME} STATIC ${dummyfile}) + endif() if(go_library_DEPS) add_dependencies(${TARGET_NAME} ${go_library_DEPS}) endif(go_library_DEPS) -- GitLab From e915aa9cf1784a82dce2b8cd0b77486c1219f6c3 Mon Sep 17 00:00:00 2001 From: Your Name Date: Mon, 26 Jun 2017 20:27:07 +0800 Subject: [PATCH 0818/3256] fix bugs --- python/paddle/v2/dataset/cifar.py | 2 +- python/paddle/v2/dataset/conll05.py | 4 ++-- python/paddle/v2/dataset/imdb.py | 12 +++++------- python/paddle/v2/dataset/imikolov.py | 12 +++++++----- python/paddle/v2/dataset/mnist.py | 2 +- python/paddle/v2/dataset/movielens.py | 3 ++- python/paddle/v2/dataset/sentiment.py | 2 +- python/paddle/v2/dataset/uci_housing.py | 2 +- python/paddle/v2/dataset/wmt14.py | 2 +- 9 files changed, 21 insertions(+), 20 deletions(-) diff --git a/python/paddle/v2/dataset/cifar.py b/python/paddle/v2/dataset/cifar.py index 95984d980dc..f885b2834e8 100644 --- a/python/paddle/v2/dataset/cifar.py +++ b/python/paddle/v2/dataset/cifar.py @@ -34,7 +34,7 @@ import numpy import paddle.v2.dataset.common import tarfile -__all__ = ['train100', 'test100', 'train10', 'test10'] +__all__ = ['train100', 'test100', 'train10', 'test10', 'convert'] URL_PREFIX = 'https://www.cs.toronto.edu/~kriz/' CIFAR10_URL = URL_PREFIX + 'cifar-10-python.tar.gz' diff --git a/python/paddle/v2/dataset/conll05.py b/python/paddle/v2/dataset/conll05.py index d4c2276b1b3..f8aae52e7c2 100644 --- a/python/paddle/v2/dataset/conll05.py +++ b/python/paddle/v2/dataset/conll05.py @@ -25,7 +25,7 @@ import gzip import itertools import paddle.v2.dataset.common -__all__ = ['test, get_dict', 'get_embedding'] +__all__ = ['test, get_dict', 'get_embedding', 'convert'] DATA_URL = 'http://www.cs.upc.edu/~srlconll/conll05st-tests.tar.gz' DATA_MD5 = '387719152ae52d60422c016e92a742fc' @@ -229,7 +229,7 @@ def fetch(): paddle.v2.dataset.common.download(DATA_URL, 'conll05st', DATA_MD5) -def convert(): +def convert(path): """ Converts dataset to recordio format """ diff --git a/python/paddle/v2/dataset/imdb.py b/python/paddle/v2/dataset/imdb.py index d939bc30653..c0ec5992e0e 100644 --- a/python/paddle/v2/dataset/imdb.py +++ b/python/paddle/v2/dataset/imdb.py @@ -28,7 +28,7 @@ import re import string import threading -__all__ = ['build_dict', 'train', 'test'] +__all__ = ['build_dict', 'train', 'test', 'convert'] URL = 'http://ai.stanford.edu/%7Eamaas/data/sentiment/aclImdb_v1.tar.gz' MD5 = '7c2ac02c03563afcf9b574c7e56c153a' @@ -168,12 +168,10 @@ def fetch(): paddle.v2.dataset.common.download(URL, 'imdb', MD5) -def convert(): +def convert(path): """ Converts dataset to recordio format """ - word_dict = ds.imdb.word_dict() - paddle.v2.dataset.common.convert(path, lambda: train(word_dict), 10, - "imdb_train") - paddle.v2.dataset.common.convert(path, lambda: test(word_dict), 10, - "imdb_test") + w = word_dict() + paddle.v2.dataset.common.convert(path, lambda: train(w), 10, "imdb_train") + paddle.v2.dataset.common.convert(path, lambda: test(w), 10, "imdb_test") diff --git a/python/paddle/v2/dataset/imikolov.py b/python/paddle/v2/dataset/imikolov.py index 034f58c2c80..b18ee8e9ba9 100644 --- a/python/paddle/v2/dataset/imikolov.py +++ b/python/paddle/v2/dataset/imikolov.py @@ -18,11 +18,11 @@ This module will download dataset from http://www.fit.vutbr.cz/~imikolov/rnnlm/ and parse training set and test set into paddle reader creators. """ -import paddle.v2.dataset.common as common +import paddle.v2.dataset.common import collections import tarfile -__all__ = ['train', 'test', 'build_dict'] +__all__ = ['train', 'test', 'build_dict', 'convert'] URL = 'http://www.fit.vutbr.cz/~imikolov/rnnlm/simple-examples.tgz' MD5 = '30177ea32e27c525793142b6bf2c8e2d' @@ -145,7 +145,7 @@ def test(word_idx, n, data_type=DataType.NGRAM): def fetch(): - common.download(URL, "imikolov", MD5) + paddle.v2.dataset.common.download(URL, "imikolov", MD5) def convert(path): @@ -154,5 +154,7 @@ def convert(path): """ N = 5 word_dict = build_dict() - common.convert(path, train(word_dict, N), 10, "imikolov_train") - common.convert(path, test(word_dict, N), 10, "imikolov_test") + paddle.v2.dataset.common.convert(path, + train(word_dict, N), 10, "imikolov_train") + paddle.v2.dataset.common.convert(path, + test(word_dict, N), 10, "imikolov_test") diff --git a/python/paddle/v2/dataset/mnist.py b/python/paddle/v2/dataset/mnist.py index 92d7f69b8df..ea5891f4f3f 100644 --- a/python/paddle/v2/dataset/mnist.py +++ b/python/paddle/v2/dataset/mnist.py @@ -21,7 +21,7 @@ import paddle.v2.dataset.common import subprocess import numpy import platform -__all__ = ['train', 'test'] +__all__ = ['train', 'test', 'convert'] URL_PREFIX = 'http://yann.lecun.com/exdb/mnist/' TEST_IMAGE_URL = URL_PREFIX + 't10k-images-idx3-ubyte.gz' diff --git a/python/paddle/v2/dataset/movielens.py b/python/paddle/v2/dataset/movielens.py index fb906cd4b6e..d9372d422a3 100644 --- a/python/paddle/v2/dataset/movielens.py +++ b/python/paddle/v2/dataset/movielens.py @@ -30,7 +30,8 @@ import functools __all__ = [ 'train', 'test', 'get_movie_title_dict', 'max_movie_id', 'max_user_id', - 'age_table', 'movie_categories', 'max_job_id', 'user_info', 'movie_info' + 'age_table', 'movie_categories', 'max_job_id', 'user_info', 'movie_info', + 'convert' ] age_table = [1, 18, 25, 35, 45, 50, 56] diff --git a/python/paddle/v2/dataset/sentiment.py b/python/paddle/v2/dataset/sentiment.py index 89683c2063c..e33f120c873 100644 --- a/python/paddle/v2/dataset/sentiment.py +++ b/python/paddle/v2/dataset/sentiment.py @@ -28,7 +28,7 @@ from nltk.corpus import movie_reviews import paddle.v2.dataset.common -__all__ = ['train', 'test', 'get_word_dict'] +__all__ = ['train', 'test', 'get_word_dict', 'convert'] NUM_TRAINING_INSTANCES = 1600 NUM_TOTAL_INSTANCES = 2000 diff --git a/python/paddle/v2/dataset/uci_housing.py b/python/paddle/v2/dataset/uci_housing.py index 9e15000c028..c715ea96819 100644 --- a/python/paddle/v2/dataset/uci_housing.py +++ b/python/paddle/v2/dataset/uci_housing.py @@ -29,7 +29,7 @@ URL = 'https://archive.ics.uci.edu/ml/machine-learning-databases/housing/housing MD5 = 'd4accdce7a25600298819f8e28e8d593' feature_names = [ 'CRIM', 'ZN', 'INDUS', 'CHAS', 'NOX', 'RM', 'AGE', 'DIS', 'RAD', 'TAX', - 'PTRATIO', 'B', 'LSTAT' + 'PTRATIO', 'B', 'LSTAT', 'convert' ] UCI_TRAIN_DATA = None diff --git a/python/paddle/v2/dataset/wmt14.py b/python/paddle/v2/dataset/wmt14.py index f29c9275f04..e1dc4f4c300 100644 --- a/python/paddle/v2/dataset/wmt14.py +++ b/python/paddle/v2/dataset/wmt14.py @@ -25,7 +25,7 @@ import gzip import paddle.v2.dataset.common from paddle.v2.parameters import Parameters -__all__ = ['train', 'test', 'build_dict'] +__all__ = ['train', 'test', 'build_dict', 'convert'] URL_DEV_TEST = 'http://www-lium.univ-lemans.fr/~schwenk/cslm_joint_paper/data/dev+test.tgz' MD5_DEV_TEST = '7d7897317ddd8ba0ae5c5fa7248d3ff5' -- GitLab From a243bdfbcf2e2ad718d2140b66964187b4deab9e Mon Sep 17 00:00:00 2001 From: Your Name Date: Mon, 26 Jun 2017 20:38:18 +0800 Subject: [PATCH 0819/3256] rm not need --- python/paddle/v2/reader/creator.py | 21 +------------------ python/paddle/v2/reader/tests/creator_test.py | 11 ---------- 2 files changed, 1 insertion(+), 31 deletions(-) diff --git a/python/paddle/v2/reader/creator.py b/python/paddle/v2/reader/creator.py index 994062096fb..07142056f87 100644 --- a/python/paddle/v2/reader/creator.py +++ b/python/paddle/v2/reader/creator.py @@ -16,7 +16,7 @@ Creator package contains some simple reader creator, which could be used in user program. """ -__all__ = ['np_array', 'text_file', "RecordIO"] +__all__ = ['np_array', 'text_file'] def np_array(x): @@ -55,22 +55,3 @@ def text_file(path): f.close() return reader - - -def RecordIO(path): - """ - Creates a data reader that outputs record one one by one from given recordio file - :path: path of recordio file - :returns: data reader of recordio file - """ - - def reader(): - f = recordio.reader(path) - while True: - r = f.read() - if r is None: - break - yield r - f.close() - - return reader diff --git a/python/paddle/v2/reader/tests/creator_test.py b/python/paddle/v2/reader/tests/creator_test.py index dd84fbb0025..359f3eeefbe 100644 --- a/python/paddle/v2/reader/tests/creator_test.py +++ b/python/paddle/v2/reader/tests/creator_test.py @@ -13,9 +13,7 @@ # limitations under the License. import os import unittest - import numpy as np - import paddle.v2.reader.creator @@ -36,14 +34,5 @@ class TestTextFile(unittest.TestCase): self.assertEqual(e, str(idx * 2) + " " + str(idx * 2 + 1)) -class TestRecordIO(unittest.TestCase): - def test_RecordIO(self): - path = os.path.join( - os.path.dirname(__file__), "test_recordio_creator.dat") - reader = paddle.v2.reader.creator.RecordIO(path) - for idx, r in enumerate(reader()): - self.assertSequenceEqual(r, str(idx)) - - if __name__ == '__main__': unittest.main() -- GitLab From b9d015cbc4975f9513f106356e8f7848737cf0f9 Mon Sep 17 00:00:00 2001 From: Your Name Date: Mon, 26 Jun 2017 20:40:12 +0800 Subject: [PATCH 0820/3256] rm not need --- .../v2/reader/tests/test_recordio_creator.dat | Bin 88 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 python/paddle/v2/reader/tests/test_recordio_creator.dat diff --git a/python/paddle/v2/reader/tests/test_recordio_creator.dat b/python/paddle/v2/reader/tests/test_recordio_creator.dat deleted file mode 100644 index 17aa89b6796184407e83246d3f342a55a66b4a69..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 88 zcmZQ!W@2QOHw Date: Mon, 26 Jun 2017 23:32:46 +0800 Subject: [PATCH 0821/3256] FIX: Pinned memory --- paddle/memory/README.md | 1 + paddle/memory/detail/CMakeLists.txt | 6 +--- paddle/memory/detail/cpu_allocator.h | 39 ++++++++++++---------- paddle/memory/detail/cpu_allocator_test.cc | 16 +++------ 4 files changed, 27 insertions(+), 35 deletions(-) diff --git a/paddle/memory/README.md b/paddle/memory/README.md index e5f7880e4ca..96a331a486f 100644 --- a/paddle/memory/README.md +++ b/paddle/memory/README.md @@ -97,6 +97,7 @@ class BuddyAllocator { struct Block { size_t size; Block* left, right; + size_t index; // allocator id }; ... }; diff --git a/paddle/memory/detail/CMakeLists.txt b/paddle/memory/detail/CMakeLists.txt index c425e9f947d..fb8a11062da 100644 --- a/paddle/memory/detail/CMakeLists.txt +++ b/paddle/memory/detail/CMakeLists.txt @@ -1,5 +1 @@ -if(${WITH_GPU}) - nv_test(cpu_allocator_test SRCS cpu_allocator_test.cc) # nv_test links CUDA, but -else(${WITH_GPU}) - cc_test(cpu_allocator_test SRCS cpu_allocator_test.cc) # cc_test doesn't. -endif(${WITH_GPU}) +cc_test(cpu_allocator_test SRCS cpu_allocator_test.cc) diff --git a/paddle/memory/detail/cpu_allocator.h b/paddle/memory/detail/cpu_allocator.h index 0d8ea3f52b9..a487fecef49 100644 --- a/paddle/memory/detail/cpu_allocator.h +++ b/paddle/memory/detail/cpu_allocator.h @@ -14,20 +14,19 @@ limitations under the License. */ #pragma once -#include // for malloc and free #include // for size_t +#include // for malloc and free -#ifdef PADDLE_WITH_GPU -#include -#include -#endif // PADDLE_WITH_GPU +#ifndef _WIN32 +#include // for mlock and munlock +#endif namespace paddle { namespace memory { namespace detail { -// CPUAllocator calls cudaMallocHost, which returns -// pinned and mlocked memory as staging areas for data exchange +// CPUAllocator calls mlock, which returns +// pinned and locked memory as staging areas for data exchange // between host and device. Allocates too much would reduce the // amount of memory available to the system for paging. So, by // default, we should use CPUAllocator. @@ -35,33 +34,37 @@ template class CPUAllocator { public: void* Alloc(size_t size); - void Free(void* p); + void Free(void* p, size_t size); }; template <> class CPUAllocator { public: - void* Alloc(size_t size) { return malloc(size); } - void Free(void* p) { free(p); } + void* Alloc(size_t size) { return std::malloc(size); } + void Free(void* p, size_t size) { std::free(p); } }; -// If CMake macro PADDLE_WITH_GPU is OFF, C++ compiler won't generate the -// following specialization that depends on the CUDA library. -#ifdef PADDLE_WITH_GPU template <> class CPUAllocator { public: void* Alloc(size_t size) { - void* p; - if (cudaMallocHost(&p, size) != cudaSuccess) { - return NULL; + void* p = std::malloc(size); + if (p == nullptr) { + return p; } +#ifndef _WIN32 + mlock(p, size); +#endif return p; } - void Free(void* p) { cudaFreeHost(p); } + void Free(void* p, size_t size) { +#ifndef _WIN32 + munlock(p, size); +#endif + std::free(p); + } }; -#endif // PADDLE_WITH_GPU } // namespace detail } // namespace memory diff --git a/paddle/memory/detail/cpu_allocator_test.cc b/paddle/memory/detail/cpu_allocator_test.cc index 464bc84e5c7..4e45266cd8a 100644 --- a/paddle/memory/detail/cpu_allocator_test.cc +++ b/paddle/memory/detail/cpu_allocator_test.cc @@ -19,20 +19,12 @@ TEST(CPUAllocator, NonStaging) { paddle::memory::detail::CPUAllocator a; void* p = a.Alloc(4096); EXPECT_NE(p, nullptr); - a.Free(p); + a.Free(p, 4096); } -#ifdef PADDLE_WITH_GPU TEST(CPUAllocator, Staging) { paddle::memory::detail::CPUAllocator a; - - int devices; - if (cudaGetDeviceCount(&devices) == cudaSuccess && devices > 0) { - void* p = a.Alloc(4096); - EXPECT_NE(p, nullptr); - a.Free(p); - } else { - EXPECT_EQ(a.Alloc(4096), nullptr); - } + void* p = a.Alloc(4096); + EXPECT_NE(p, nullptr); + a.Free(p, 4096); } -#endif // PADDLE_WITH_GPU -- GitLab From c44a94b4dc82db7b4493fe27a60270bee8cf9273 Mon Sep 17 00:00:00 2001 From: dongzhihong Date: Mon, 26 Jun 2017 23:55:30 +0800 Subject: [PATCH 0822/3256] "fix cmake build flags" --- go/pserver/cclient/cclient.go | 12 +++++++----- go/pserver/cclient/test/test_cclient.c | 8 ++++---- go/pserver/client_test.go | 8 ++------ go/pserver/optimizer.go | 17 +++++++++++------ go/pserver/optimizer_test.go | 8 +++++--- go/pserver/service.go | 8 ++++---- 6 files changed, 33 insertions(+), 28 deletions(-) diff --git a/go/pserver/cclient/cclient.go b/go/pserver/cclient/cclient.go index 6aaaff7409d..92a41b7f543 100644 --- a/go/pserver/cclient/cclient.go +++ b/go/pserver/cclient/cclient.go @@ -123,8 +123,9 @@ func paddle_begin_init_params(client C.paddle_pserver_client) C.int { func paddle_init_param(client C.paddle_pserver_client, param C.paddle_parameter, param_config unsafe.Pointer, config_len C.int) C.int { et := pserver.ElementType(param.element_type) name := C.GoString(param.name) + content := cArrayToSlice(unsafe.Pointer(param.content), int(param.content_len)) pc := pserver.ParameterWithConfig{ - Param: pserver.Parameter{Name: name, ElementType: et, Content: param.content, Length: para.content_len}, + Param: pserver.Parameter{Name: name, ElementType: et, Content: content}, Config: cArrayToSlice(param_config, int(config_len)), } c := get(client) @@ -166,7 +167,8 @@ func paddle_send_grads(client C.paddle_pserver_client, grads **C.paddle_gradient grad := *(**C.paddle_gradient)(unsafe.Pointer((uintptr(unsafe.Pointer(grads)) + uintptr(i)*unsafe.Sizeof(*grads)))) et := pserver.ElementType(grad.element_type) name := C.GoString(grad.name) - gs = append(gs, pserver.Gradient{Name: name, ElementType: et, Content: grad.content, Length: grad.content_len}) + content := cArrayToSlice(unsafe.Pointer(grad.content), int(grad.content_len)) + gs = append(gs, pserver.Gradient{Name: name, ElementType: et, Content: content}) } c := get(client) @@ -223,14 +225,14 @@ func paddle_get_params(client C.paddle_pserver_client, dst **C.paddle_parameter, } if unsafe.Pointer(param.content) != nullPtr { - if int(param.content_len) != p.Length { + if int(param.content_len) != len(p.Content) { log.Errorf("the pre-allocated content len does not match parameter content len. Pre-allocated len: %d, returned len: %d", param.content_len, len(p.Content)) return C.PSERVER_ERROR } } - C.memcpy(unsafe.Pointer(param.content), unsafe.Pointer(p.Content), C.size_t(p.Length)) - param.content_len = C.int(p.Length) + C.memcpy(unsafe.Pointer(param.content), unsafe.Pointer(&p.Content[0]), C.size_t(len(p.Content))) + param.content_len = C.int(len(p.Content)) param.element_type = C.paddle_element_type(p.ElementType) } diff --git a/go/pserver/cclient/test/test_cclient.c b/go/pserver/cclient/test/test_cclient.c index 7d26127b600..5bd4913ba3c 100644 --- a/go/pserver/cclient/test/test_cclient.c +++ b/go/pserver/cclient/test/test_cclient.c @@ -50,10 +50,10 @@ void getParams(paddle_pserver_client c) { int main() { char addr[] = "localhost:3000"; paddle_pserver_client c = paddle_new_pserver_client(addr, 1); - char config_proto[1024]; + char *config_proto; size_t config_proto_len = 0; ssize_t nread; - FILE *fp = fopen("optimizer.pb.txt", "r"); + FILE *fp = fopen("testdata/optimizer.pb.txt", "r"); if(!fp) { fail(); } while((nread = getline(&config_proto, &config_proto_len, fp)) != -1) { printf("%s", config_proto); @@ -70,7 +70,7 @@ retry: param.name = name_a; param.content = content_a; param.content_len = 2000; - int error = paddle_init_param(c, param, config_proto, config_proto_len); + int error = paddle_init_param(c, param, (void *)config_proto, config_proto_len); if (error != 0) { goto retry; } @@ -79,7 +79,7 @@ retry: param.name = name_b; param.content = content_b; param.content_len = 3000; - error = paddle_init_param(c, param, NULL, 0); + error = paddle_init_param(c, param, (void *)config_proto, config_proto_len); if (error != 0) { goto retry; } diff --git a/go/pserver/client_test.go b/go/pserver/client_test.go index c5d38e41129..d0371a26a13 100644 --- a/go/pserver/client_test.go +++ b/go/pserver/client_test.go @@ -75,9 +75,7 @@ func TestClientFull(t *testing.T) { var p pserver.Parameter p.Name = "p_" + strconv.Itoa(i) p.ElementType = pserver.Float32 - ElementValue := make([]byte, (i+1)*100) - p.Content = &ElementValue[0] - p.Length = len(ElementValue) + p.Content = make([]byte, (i+1)*100) err := c.InitParam(pserver.ParameterWithConfig{Param: p}) if err != nil { t.Fatal(err) @@ -94,9 +92,7 @@ func TestClientFull(t *testing.T) { var g pserver.Gradient g.Name = "p_" + strconv.Itoa(i) g.ElementType = pserver.Float32 - ElementValue := make([]byte, (i+1)*100) - g.Content = &ElementValue[0] - g.Length = len(ElementValue) + g.Content = make([]byte, (i+1)*100) grads = append(grads, g) } diff --git a/go/pserver/optimizer.go b/go/pserver/optimizer.go index 12bf055b4de..4ecae0911c2 100644 --- a/go/pserver/optimizer.go +++ b/go/pserver/optimizer.go @@ -4,7 +4,7 @@ package pserver // TODO(zhihong): move compile flags to cmake go_library #cgo pkg-config: protobuf #cgo CFLAGS: -I ../../ -#cgo LDFLAGS: /Users/dzh/.go/src/github.com/PaddlePaddle/Paddle/build/go/pserver/cclient/libpaddle_go_optimizer.a +#cgo LDFLAGS: /Users/dzh/.go/src/github.com/PaddlePaddle/Paddle/build/go/pserver/cclient/libpaddle_go_optimizer.a -lstdc++ #include "paddle/optimizer/optimizer.h" */ import "C" @@ -38,17 +38,20 @@ func newOptimizer(paramWithConfigs ParameterWithConfig) *optimizer { o := &optimizer{} p := paramWithConfigs.Param c := paramWithConfigs.Config - buffer := &p.Content[0] - o.opt = C.paddle_create_optimizer(C.uchar(c), C.int(len(c)), unsafe.Pointer(buffer), C.int(len(p.Content)), nullPtr, 0) + var cbuffer unsafe.Pointer + cbuffer = unsafe.Pointer(&p.Content[0]) + o.opt = C.paddle_create_optimizer((*C.uchar)(&c[0]), C.int(len(c)), + C.paddle_element_type(p.ElementType), cbuffer, C.int(len(p.Content)), + (*C.char)(nullPtr), 0) return o } func (o *optimizer) GetWeights(p *Parameter) error { var buffer unsafe.Pointer - buffer_len := C.paddle_optimizer_get_weights(unsafe.Pointer(o), &buffer) + buffer_len := C.paddle_optimizer_get_weights(o.opt, &buffer) if buffer_len == 0 || buffer == nullPtr { - return fmt.Errorf("parameter optimizer error : %s get failed", p.name) + return fmt.Errorf("parameter optimizer error : %s get failed", p.Name) } p.Content = cArrayToSlice(buffer, int(buffer_len)) return nil @@ -60,7 +63,9 @@ func (o *optimizer) UpdateParameter(g Gradient) error { } // FIXME: do we need a copy? discard g.Content by GC ok - r := C.paddle_update_parameter(o.opt, C.paddle_element_type(g.ElementType), unsafe.Pointer(g.Content), C.int(len(g.Content))) + var cbuffer unsafe.Pointer + cbuffer = unsafe.Pointer(&g.Content[0]) + r := C.paddle_update_parameter(o.opt, C.paddle_element_type(g.ElementType), cbuffer, C.int(len(g.Content))) if r != 0 { return fmt.Errorf("optimizer update returned error code: %d", r) } diff --git a/go/pserver/optimizer_test.go b/go/pserver/optimizer_test.go index eac744b5cdb..368047d6f89 100644 --- a/go/pserver/optimizer_test.go +++ b/go/pserver/optimizer_test.go @@ -8,11 +8,13 @@ import ( func TestOptimizerCreateRelease(t *testing.T) { p := Parameter{ Name: "a", - ElementType: Float32, + ElementType: Int32, } - p.Content = []byte{0.1, 0.3} + p.Content = []byte{1, 3} config, err := ioutil.ReadFile("./cclient/test/testdata/optimizer.pb.txt") - + if err != nil { + t.Fatalf("read optimizer proto failed") + } param := ParameterWithConfig{ Param: p, Config: config, diff --git a/go/pserver/service.go b/go/pserver/service.go index d0d57136b5e..cdd433260af 100644 --- a/go/pserver/service.go +++ b/go/pserver/service.go @@ -128,11 +128,11 @@ func (s *Service) GetParam(name string, parameter *Parameter) error { // nature. This race condition is allowed deliberately // to save the program from making a copy of the // paramter content. - p.Name = name - p.ElementType = opt.ElementType + parameter.Name = name + parameter.ElementType = opt.ElementType - ok := opt.GetWeights(¶meter) - return ok + err := opt.GetWeights(parameter) + return err } // Save tells the parameter server to save parameters. -- GitLab From ce70df86b1e8c892cdde5312caa0c2699f368f7d Mon Sep 17 00:00:00 2001 From: liaogang Date: Tue, 27 Jun 2017 00:15:36 +0800 Subject: [PATCH 0823/3256] Add gpu_allocator --- paddle/memory/.clang-format | 5 ++ paddle/memory/detail/CMakeLists.txt | 1 + paddle/memory/detail/cpu_allocator.h | 6 +- paddle/memory/detail/gpu_allocator.h | 92 ++++++++++++++++++++++ paddle/memory/detail/gpu_allocator_test.cc | 30 +++++++ 5 files changed, 131 insertions(+), 3 deletions(-) create mode 100644 paddle/memory/.clang-format create mode 100644 paddle/memory/detail/gpu_allocator.h create mode 100644 paddle/memory/detail/gpu_allocator_test.cc diff --git a/paddle/memory/.clang-format b/paddle/memory/.clang-format new file mode 100644 index 00000000000..29282dc87e2 --- /dev/null +++ b/paddle/memory/.clang-format @@ -0,0 +1,5 @@ +--- +Language: Cpp +BasedOnStyle: Google +Standard: Cpp11 +... diff --git a/paddle/memory/detail/CMakeLists.txt b/paddle/memory/detail/CMakeLists.txt index fb8a11062da..81ca8a0bbf0 100644 --- a/paddle/memory/detail/CMakeLists.txt +++ b/paddle/memory/detail/CMakeLists.txt @@ -1 +1,2 @@ cc_test(cpu_allocator_test SRCS cpu_allocator_test.cc) +nv_test(gpu_allocator_test SRCS gpu_allocator_test.cc) diff --git a/paddle/memory/detail/cpu_allocator.h b/paddle/memory/detail/cpu_allocator.h index a487fecef49..17753ccef71 100644 --- a/paddle/memory/detail/cpu_allocator.h +++ b/paddle/memory/detail/cpu_allocator.h @@ -32,21 +32,21 @@ namespace detail { // default, we should use CPUAllocator. template class CPUAllocator { -public: + public: void* Alloc(size_t size); void Free(void* p, size_t size); }; template <> class CPUAllocator { -public: + public: void* Alloc(size_t size) { return std::malloc(size); } void Free(void* p, size_t size) { std::free(p); } }; template <> class CPUAllocator { -public: + public: void* Alloc(size_t size) { void* p = std::malloc(size); if (p == nullptr) { diff --git a/paddle/memory/detail/gpu_allocator.h b/paddle/memory/detail/gpu_allocator.h new file mode 100644 index 00000000000..9452c41fb89 --- /dev/null +++ b/paddle/memory/detail/gpu_allocator.h @@ -0,0 +1,92 @@ +/* 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 // for size_t + +#include +#include + +namespace paddle { +namespace memory { +namespace detail { + +inline void throw_on_error(cudaError_t e, const char* message) { + if (e) { + throw thrust::system_error(e, thrust::cuda_category(), message); + } +} + +// GPUAllocator calls cudaHostMalloc, which returns +// pinned and locked memory as staging areas for data exchange +// between host and device. Allocates too much would reduce the +// amount of memory available to the system for paging. So, by +// default, we should use GPUAllocator. +template +class GPUAllocator { +public: + void* Alloc(size_t size); + void Free(void* p, size_t size); +}; + +template <> +class GPUAllocator { +public: + void* Alloc(size_t size) { + void* p = 0; + cudaError_t result = cudaMalloc(&p, size); + if (result == cudaSuccess) { + return p; + } + // clear last error + cudaGetLastError(); + return nullptr; + } + + void Free(void* p, size_t size) { + // Purposefully allow cudaErrorCudartUnloading, because + // that is returned if you ever call cudaFree after the + // driver has already shutdown. This happens only if the + // process is terminating, in which case we don't care if + // cudaFree succeeds. + auto err = cudaFree(p); + if (err != cudaErrorCudartUnloading) { + throw_on_error(err, "cudaFree failed"); + } + } +}; + +template <> +class GPUAllocator { +public: + void* Alloc(size_t size) { + void* p = 0; + cudaError_t result = cudaMallocHost(&p, size); + if (result == cudaSuccess) { + return p; + } + // clear last error + cudaGetLastError(); + return nullptr; + } + + void Free(void* p, size_t size) { + throw_on_error(cudaFreeHost(p), "cudaFreeHost failed"); + } +}; + +} // namespace detail +} // namespace memory +} // namespace paddle diff --git a/paddle/memory/detail/gpu_allocator_test.cc b/paddle/memory/detail/gpu_allocator_test.cc new file mode 100644 index 00000000000..18c1c9ab430 --- /dev/null +++ b/paddle/memory/detail/gpu_allocator_test.cc @@ -0,0 +1,30 @@ +/* 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/memory/detail/gpu_allocator.h" +#include "gtest/gtest.h" + +TEST(GPUAllocator, NonStaging) { + paddle::memory::detail::GPUAllocator a; + void* p = a.Alloc(4096); + EXPECT_NE(p, nullptr); + a.Free(p, 4096); +} + +TEST(GPUAllocator, Staging) { + paddle::memory::detail::GPUAllocator a; + void* p = a.Alloc(4096); + EXPECT_NE(p, nullptr); + a.Free(p, 4096); +} -- GitLab From cebfae94678b86bbe890077e01bd1a21364b5e2e Mon Sep 17 00:00:00 2001 From: dongzhihong Date: Tue, 27 Jun 2017 01:24:38 +0800 Subject: [PATCH 0824/3256] "move proto.txt to testdata folder" --- .../cclient/test/dump_optimizer_proto.py | 13 -- .../cclient/test/testdata/optimizer.pb.txt | Bin 51 -> 50 bytes go/pserver/service_test.go | 134 ++++++++++-------- 3 files changed, 72 insertions(+), 75 deletions(-) delete mode 100644 go/pserver/cclient/test/dump_optimizer_proto.py diff --git a/go/pserver/cclient/test/dump_optimizer_proto.py b/go/pserver/cclient/test/dump_optimizer_proto.py deleted file mode 100644 index 2ed4db97f90..00000000000 --- a/go/pserver/cclient/test/dump_optimizer_proto.py +++ /dev/null @@ -1,13 +0,0 @@ -import OptimizerConfig_pb2 as pb - -config = pb.OptimizerConfig() -config.clip_norm = 0.1 -config.lr_policy = pb.OptimizerConfig.Const -config.optimizer = pb.OptimizerConfig.SGD -config.sgd.momentum = 0.0 -config.sgd.decay = 0.0 -config.sgd.nesterov = False -config.const_lr.learning_rate = 0.1 -s = config.SerializeToString() -with open("optimizer.pb.txt", 'w') as f: - f.write(s) diff --git a/go/pserver/cclient/test/testdata/optimizer.pb.txt b/go/pserver/cclient/test/testdata/optimizer.pb.txt index 27c8a584df40ab714edfd730f0ff7b7bd3783964..27dd3bc5f19e2964b4b674cff8860233cbdb445a 100644 GIT binary patch delta 4 LcmXpunqUL~0=NMv delta 6 NcmXpqo?yht1poyT0W$yq diff --git a/go/pserver/service_test.go b/go/pserver/service_test.go index b746d13e1ca..a88e2df73ad 100644 --- a/go/pserver/service_test.go +++ b/go/pserver/service_test.go @@ -1,7 +1,7 @@ package pserver_test import ( - "reflect" + "io/ioutil" "sync" "testing" "time" @@ -15,73 +15,79 @@ func TestFull(t *testing.T) { p.Name = "param_a" p.Content = []byte{1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0} p.ElementType = pserver.Int32 - err := s.InitParam(pserver.ParameterWithConfig{Param: p, Config: nil}, nil) + config, err := ioutil.ReadFile("./cclient/test/testdata/optimizer.pb.txt") if err != nil { - t.FailNow() - } - - var p1 pserver.Parameter - p1.Name = "param_b" - p1.Content = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} - p1.ElementType = pserver.Float32 - err = s.InitParam(pserver.ParameterWithConfig{Param: p1, Config: nil}, nil) - if err != nil { - t.FailNow() - } - - err = s.FinishInitParams(0, nil) - if err != nil { - t.FailNow() - } - - var param pserver.Parameter - err = s.GetParam("param_b", ¶m) - if err != nil { - t.FailNow() - } - - if !reflect.DeepEqual(param, p1) { - t.FailNow() - } - - g1, g2 := pserver.Gradient(p1), pserver.Gradient(p) - err = s.SendGrad(g1, nil) - if err != nil { - t.FailNow() - } - err = s.SendGrad(g2, nil) - - if err != nil { - t.FailNow() - } - - var param1 pserver.Parameter - err = s.GetParam("param_a", ¶m1) - if err != nil { - t.FailNow() - } - - // don't compare content, since it's already changed by - // gradient update. - param1.Content = nil - p.Content = nil - - if !reflect.DeepEqual(param1, p) { - t.FailNow() + t.Fatalf("read optimizer proto failed") } -} -func TestMultipleInit(t *testing.T) { - s := pserver.NewService() - err := s.FinishInitParams(0, nil) + err = s.InitParam(pserver.ParameterWithConfig{Param: p, Config: config}, nil) if err != nil { t.FailNow() } - err = s.FinishInitParams(0, nil) - if err.Error() != pserver.AlreadyInitialized { - t.FailNow() - } + // var p1 pserver.Parameter + // p1.Name = "param_b" + // p1.Content = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} + // p1.ElementType = pserver.Float32 + // fmt.Println("paddle passed") + // err = s.InitParam(pserver.ParameterWithConfig{Param: p1, Config: config}, nil) + // if err != nil { + // t.FailNow() + // } + + // err = s.FinishInitParams(0, nil) + // if err != nil { + // t.FailNow() + // } + + // var param pserver.Parameter + // err = s.GetParam("param_b", ¶m) + // if err != nil { + // t.FailNow() + // } + + // if !reflect.DeepEqual(param, p1) { + // t.FailNow() + // } + + // g1, g2 := pserver.Gradient(p1), pserver.Gradient(p) + // err = s.SendGrad(g1, nil) + // if err != nil { + // t.FailNow() + // } + // err = s.SendGrad(g2, nil) + + // if err != nil { + // t.FailNow() + // } + + // var param1 pserver.Parameter + // err = s.GetParam("param_a", ¶m1) + // if err != nil { + // t.FailNow() + // } + + // // don't compare content, since it's already changed by + // // gradient update. + // param1.Content = nil + // p.Content = nil + + // if !reflect.DeepEqual(param1, p) { + // t.FailNow() + // } + // } + + // func TestMultipleInit(t *testing.T) { + // s := pserver.NewService() + // err := s.FinishInitParams(0, nil) + // if err != nil { + // t.FailNow() + // } + + // err = s.FinishInitParams(0, nil) + // if err.Error() != pserver.AlreadyInitialized { + // t.FailNow() + // } } func TestUninitialized(t *testing.T) { @@ -133,7 +139,11 @@ func TestBlockUntilInitialized(t *testing.T) { p.Name = "param_a" p.Content = []byte{1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0} p.ElementType = pserver.Int32 - err := s.InitParam(pserver.ParameterWithConfig{Param: p, Config: nil}, nil) + config, err := ioutil.ReadFile("./cclient/test/testdata/optimizer.pb.txt") + if err != nil { + t.Fatalf("read optimizer proto failed") + } + err = s.InitParam(pserver.ParameterWithConfig{Param: p, Config: config}, nil) if err != nil { t.FailNow() } -- GitLab From e2e0fbd4188fcbcc6bf69d1ef22b3f6f0a927f84 Mon Sep 17 00:00:00 2001 From: Yi Wang Date: Mon, 26 Jun 2017 10:36:49 -0700 Subject: [PATCH 0825/3256] Add tesnor.h --- paddle/framework/tensor.h | 91 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 paddle/framework/tensor.h diff --git a/paddle/framework/tensor.h b/paddle/framework/tensor.h new file mode 100644 index 00000000000..a658537430e --- /dev/null +++ b/paddle/framework/tensor.h @@ -0,0 +1,91 @@ +/* + 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 + +namespace paddle { +namespace framework { + +class Tensor { + using paddle::platform::Place; + using paddle::platform::get_place; + + public: + explicit Tensor(DDim dims) : dims_(dims), place_(get_place()) {} + explicit Tensor(DDim dims, Place place) : dims_(dims), place_(place) {} + + template + const T* data() const { + PADDLE_ASSERT(holder_ != nullptr); + PADDLE_ASSERT(holder_->Place() == place_); + PADDLE_ASSERT(holder_->Size() >= dims_.product() * sizeof(T)); + return static_cast(holder->Ptr()); + } + + template ::value>::type> + T* mutable_data() { + if (holder_ == nullptr || holder_->Place() != place_ || + holder_->Size() < dims_.product() * sizeof(T)) { + holder_.reset(new PlaceholderImpl(place_, dims.product() * sizeof(T))); + } + return static_cast(holder_->Ptr()); + } + + template ::value>::type> + T* mutable_data(DDim dims) { + dims_ = dims; + return mutable_data(); + } + + template ::value>::type> + T* mutable_data(DDim dims, Place place) { + dims_ = dims; + place_ = place; + return mutable_data(); + } + + private: + // Placeholder hides type T, so it doesn't appear as a template + // parameter of Variable. + struct Placeholder { + virtual ~Placeholder() {} + virtual void* Ptr() const = 0; + virtual Place Place() const = 0; + virtual size_t Size() const = 0; + }; + + template + struct PlaceholderImpl : public Placeholder { + PlaceholderImpl(Place pl, size_t size) + : ptr_(memory::Alloc(pl, size), paddle::memory::Deleter(pl)), + place_(pl), + size_(size) {} + + virtual void* Ptr() const { return static_cast(ptr_.get()); } + virtual size_t Size() const { return size_; } + virtual Place Place() const { return place_; } + + std::unique_ptr ptr_; + Place place_; // record the place of ptr_. + size_t size_; // size of the memory block. + }; + + std::unique_ptr holder_; // holds the memory block if allocated. + DDim dims_; // could be smallers than the holder_->Size(). + paddle::platform::Place place_; +}; + +} // namespace framework +} // namespace paddle -- GitLab From 864386d59682307ba9e033cfce8355029beda9b5 Mon Sep 17 00:00:00 2001 From: dongzhihong Date: Tue, 27 Jun 2017 01:51:10 +0800 Subject: [PATCH 0826/3256] "change log in optimizer" --- go/pserver/optimizer.go | 4 +- go/pserver/service_test.go | 129 +++++++++++++++++++------------------ 2 files changed, 67 insertions(+), 66 deletions(-) diff --git a/go/pserver/optimizer.go b/go/pserver/optimizer.go index 4ecae0911c2..af7faad2549 100644 --- a/go/pserver/optimizer.go +++ b/go/pserver/optimizer.go @@ -47,7 +47,7 @@ func newOptimizer(paramWithConfigs ParameterWithConfig) *optimizer { } func (o *optimizer) GetWeights(p *Parameter) error { - + // FIXME: get weigths from optimizer has bug var buffer unsafe.Pointer buffer_len := C.paddle_optimizer_get_weights(o.opt, &buffer) if buffer_len == 0 || buffer == nullPtr { @@ -59,7 +59,7 @@ func (o *optimizer) GetWeights(p *Parameter) error { func (o *optimizer) UpdateParameter(g Gradient) error { if o.ElementType != g.ElementType { - return fmt.Errorf("Name: %s, parameter and gradient element type not match, parameter: %v, gradient: %v", g.Name, g.ElementType, g.ElementType) + return fmt.Errorf("Name: %s, parameter and gradient element type not match, parameter: %v, gradient: %v", g.Name, o.ElementType, g.ElementType) } // FIXME: do we need a copy? discard g.Content by GC ok diff --git a/go/pserver/service_test.go b/go/pserver/service_test.go index a88e2df73ad..a09b25dec08 100644 --- a/go/pserver/service_test.go +++ b/go/pserver/service_test.go @@ -2,6 +2,7 @@ package pserver_test import ( "io/ioutil" + "reflect" "sync" "testing" "time" @@ -9,7 +10,7 @@ import ( "github.com/PaddlePaddle/Paddle/go/pserver" ) -func TestFull(t *testing.T) { +func TestNewName(t *testing.T) { s := pserver.NewService() var p pserver.Parameter p.Name = "param_a" @@ -25,69 +26,69 @@ func TestFull(t *testing.T) { t.FailNow() } - // var p1 pserver.Parameter - // p1.Name = "param_b" - // p1.Content = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} - // p1.ElementType = pserver.Float32 - // fmt.Println("paddle passed") - // err = s.InitParam(pserver.ParameterWithConfig{Param: p1, Config: config}, nil) - // if err != nil { - // t.FailNow() - // } - - // err = s.FinishInitParams(0, nil) - // if err != nil { - // t.FailNow() - // } - - // var param pserver.Parameter - // err = s.GetParam("param_b", ¶m) - // if err != nil { - // t.FailNow() - // } - - // if !reflect.DeepEqual(param, p1) { - // t.FailNow() - // } - - // g1, g2 := pserver.Gradient(p1), pserver.Gradient(p) - // err = s.SendGrad(g1, nil) - // if err != nil { - // t.FailNow() - // } - // err = s.SendGrad(g2, nil) - - // if err != nil { - // t.FailNow() - // } - - // var param1 pserver.Parameter - // err = s.GetParam("param_a", ¶m1) - // if err != nil { - // t.FailNow() - // } - - // // don't compare content, since it's already changed by - // // gradient update. - // param1.Content = nil - // p.Content = nil - - // if !reflect.DeepEqual(param1, p) { - // t.FailNow() - // } - // } - - // func TestMultipleInit(t *testing.T) { - // s := pserver.NewService() - // err := s.FinishInitParams(0, nil) - // if err != nil { - // t.FailNow() - // } - - // err = s.FinishInitParams(0, nil) - // if err.Error() != pserver.AlreadyInitialized { - // t.FailNow() - // } + var p1 pserver.Parameter + p1.Name = "param_b" + p1.Content = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} + p1.ElementType = pserver.Float32 + err = s.InitParam(pserver.ParameterWithConfig{Param: p1, Config: config}, nil) + if err != nil { + t.FailNow() + } + + err = s.FinishInitParams(0, nil) + if err != nil { + t.FailNow() + } + + var param pserver.Parameter + err = s.GetParam("param_b", ¶m) + if err != nil { + t.FailNow() + } + + if !reflect.DeepEqual(param, p1) { + t.FailNow() + } + + g1, g2 := pserver.Gradient(p1), pserver.Gradient(p) + + err = s.SendGrad(g1, nil) + if err != nil { + t.FailNow() + } + err = s.SendGrad(g2, nil) + + if err != nil { + t.FailNow() + } + + var param1 pserver.Parameter + err = s.GetParam("param_a", ¶m1) + if err != nil { + t.FailNow() + } + + // don't compare content, since it's already changed by + // gradient update. + param1.Content = nil + p.Content = nil + + if !reflect.DeepEqual(param1, p) { + t.FailNow() + } +} + +func TestMultipleInit(t *testing.T) { + s := pserver.NewService() + err := s.FinishInitParams(0, nil) + if err != nil { + t.FailNow() + } + + err = s.FinishInitParams(0, nil) + if err.Error() != pserver.AlreadyInitialized { + t.FailNow() + } } func TestUninitialized(t *testing.T) { -- GitLab From e02859c0f53dfe4616976b015d4fefd8aaa6eb39 Mon Sep 17 00:00:00 2001 From: Yi Wang Date: Mon, 26 Jun 2017 15:27:01 -0700 Subject: [PATCH 0827/3256] Replace {cpu,gpu}_allocator.h and {cpu,gpu}_allocator_test.cc by system_allocator{.h,_test.cc} --- paddle/memory/CMakeLists.txt | 6 ++ paddle/memory/detail/CMakeLists.txt | 3 +- paddle/memory/detail/cpu_allocator.h | 71 ----------------- paddle/memory/detail/cpu_allocator_test.cc | 30 ------- .../{gpu_allocator.h => system_allocator.h} | 79 +++++++++++-------- ...cator_test.cc => system_allocator_test.cc} | 20 ++++- paddle/memory/memory.cc | 67 +++++++--------- paddle/memory/memory.h | 16 +--- 8 files changed, 106 insertions(+), 186 deletions(-) delete mode 100644 paddle/memory/detail/cpu_allocator.h delete mode 100644 paddle/memory/detail/cpu_allocator_test.cc rename paddle/memory/detail/{gpu_allocator.h => system_allocator.h} (58%) rename paddle/memory/detail/{gpu_allocator_test.cc => system_allocator_test.cc} (69%) diff --git a/paddle/memory/CMakeLists.txt b/paddle/memory/CMakeLists.txt index 3943c3cfad3..86625124967 100644 --- a/paddle/memory/CMakeLists.txt +++ b/paddle/memory/CMakeLists.txt @@ -1 +1,7 @@ add_subdirectory(detail) + +if(${WITH_GPU}) + nv_library(memory SRCS memory.cc) +else(${WITH_GPU}) + cc_library(memory SRCS memroy.cc) +endif(${WITH_GPU}) diff --git a/paddle/memory/detail/CMakeLists.txt b/paddle/memory/detail/CMakeLists.txt index 81ca8a0bbf0..3b5bbd7a12f 100644 --- a/paddle/memory/detail/CMakeLists.txt +++ b/paddle/memory/detail/CMakeLists.txt @@ -1,2 +1 @@ -cc_test(cpu_allocator_test SRCS cpu_allocator_test.cc) -nv_test(gpu_allocator_test SRCS gpu_allocator_test.cc) +cc_test(system_allocator_test SRCS system_allocator_test.cc) diff --git a/paddle/memory/detail/cpu_allocator.h b/paddle/memory/detail/cpu_allocator.h deleted file mode 100644 index 17753ccef71..00000000000 --- a/paddle/memory/detail/cpu_allocator.h +++ /dev/null @@ -1,71 +0,0 @@ -/* 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 // for size_t -#include // for malloc and free - -#ifndef _WIN32 -#include // for mlock and munlock -#endif - -namespace paddle { -namespace memory { -namespace detail { - -// CPUAllocator calls mlock, which returns -// pinned and locked memory as staging areas for data exchange -// between host and device. Allocates too much would reduce the -// amount of memory available to the system for paging. So, by -// default, we should use CPUAllocator. -template -class CPUAllocator { - public: - void* Alloc(size_t size); - void Free(void* p, size_t size); -}; - -template <> -class CPUAllocator { - public: - void* Alloc(size_t size) { return std::malloc(size); } - void Free(void* p, size_t size) { std::free(p); } -}; - -template <> -class CPUAllocator { - public: - void* Alloc(size_t size) { - void* p = std::malloc(size); - if (p == nullptr) { - return p; - } -#ifndef _WIN32 - mlock(p, size); -#endif - return p; - } - - void Free(void* p, size_t size) { -#ifndef _WIN32 - munlock(p, size); -#endif - std::free(p); - } -}; - -} // namespace detail -} // namespace memory -} // namespace paddle diff --git a/paddle/memory/detail/cpu_allocator_test.cc b/paddle/memory/detail/cpu_allocator_test.cc deleted file mode 100644 index 4e45266cd8a..00000000000 --- a/paddle/memory/detail/cpu_allocator_test.cc +++ /dev/null @@ -1,30 +0,0 @@ -/* 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/memory/detail/cpu_allocator.h" -#include "gtest/gtest.h" - -TEST(CPUAllocator, NonStaging) { - paddle::memory::detail::CPUAllocator a; - void* p = a.Alloc(4096); - EXPECT_NE(p, nullptr); - a.Free(p, 4096); -} - -TEST(CPUAllocator, Staging) { - paddle::memory::detail::CPUAllocator a; - void* p = a.Alloc(4096); - EXPECT_NE(p, nullptr); - a.Free(p, 4096); -} diff --git a/paddle/memory/detail/gpu_allocator.h b/paddle/memory/detail/system_allocator.h similarity index 58% rename from paddle/memory/detail/gpu_allocator.h rename to paddle/memory/detail/system_allocator.h index 9452c41fb89..0a645531889 100644 --- a/paddle/memory/detail/gpu_allocator.h +++ b/paddle/memory/detail/system_allocator.h @@ -14,20 +14,58 @@ limitations under the License. */ #pragma once -#include // for size_t +#include // for size_t +#include // for mlock and munlock +#include // for malloc and free -#include +#ifndef PADDLE_ONLY_CPU #include +#include +#endif // PADDLE_ONLY_CPU namespace paddle { namespace memory { namespace detail { +class SystemAllocator { + public: + virtual void* Alloc(size_t size) = 0; + virtual void* Free(void* p) = 0; +}; + +// CPUAllocator calls mlock, which returns pinned +// and locked memory as staging areas for data exchange between host +// and device. Allocates too much would reduce the amount of memory +// available to the system for paging. So, by default, we should use +// CPUAllocator. +template +class CPUAllocator : public SystemAllocator { + public: + virtual void* Alloc(size_t size) { + void* p = std::malloc(size); + if (p != nullptr && lock_memory) { + mlock(p, size); + } + return p; + } + + virtual void Free(void* p, size_t size) { + if (p != nullptr && lock_memory) { + munlock(p, size); + } + std::free(p); + } +}; + +#ifndef PADDLE_ONLY_CPU // The following code are for CUDA. + +namespace { inline void throw_on_error(cudaError_t e, const char* message) { if (e) { throw thrust::system_error(e, thrust::cuda_category(), message); } } +} // namespace // GPUAllocator calls cudaHostMalloc, which returns // pinned and locked memory as staging areas for data exchange @@ -36,17 +74,11 @@ inline void throw_on_error(cudaError_t e, const char* message) { // default, we should use GPUAllocator. template class GPUAllocator { -public: - void* Alloc(size_t size); - void Free(void* p, size_t size); -}; - -template <> -class GPUAllocator { -public: + public: void* Alloc(size_t size) { void* p = 0; - cudaError_t result = cudaMalloc(&p, size); + cudaError_t result = + staging ? cudaMallocHost(&p, size) : cudaMalloc(&p, size); if (result == cudaSuccess) { return p; } @@ -60,32 +92,15 @@ public: // that is returned if you ever call cudaFree after the // driver has already shutdown. This happens only if the // process is terminating, in which case we don't care if - // cudaFree succeeds. - auto err = cudaFree(p); + // cudaFree succeeds. + auto err = staging ? cudaFreeHost(p) : cudaFree(p); if (err != cudaErrorCudartUnloading) { - throw_on_error(err, "cudaFree failed"); + throw_on_error(err, "cudaFree failed"); } } }; -template <> -class GPUAllocator { -public: - void* Alloc(size_t size) { - void* p = 0; - cudaError_t result = cudaMallocHost(&p, size); - if (result == cudaSuccess) { - return p; - } - // clear last error - cudaGetLastError(); - return nullptr; - } - - void Free(void* p, size_t size) { - throw_on_error(cudaFreeHost(p), "cudaFreeHost failed"); - } -}; +#endif // PADDLE_ONLY_CPU } // namespace detail } // namespace memory diff --git a/paddle/memory/detail/gpu_allocator_test.cc b/paddle/memory/detail/system_allocator_test.cc similarity index 69% rename from paddle/memory/detail/gpu_allocator_test.cc rename to paddle/memory/detail/system_allocator_test.cc index 18c1c9ab430..4e7b8018b6a 100644 --- a/paddle/memory/detail/gpu_allocator_test.cc +++ b/paddle/memory/detail/system_allocator_test.cc @@ -12,9 +12,25 @@ 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/memory/detail/gpu_allocator.h" +#include "paddle/memory/detail/system_allocator.h" #include "gtest/gtest.h" +TEST(CPUAllocator, NoLockMem) { + paddle::memory::detail::CPUAllocator a; + void* p = a.Alloc(4096); + EXPECT_NE(p, nullptr); + a.Free(p, 4096); +} + +TEST(CPUAllocator, LockMem) { + paddle::memory::detail::CPUAllocator a; + void* p = a.Alloc(4096); + EXPECT_NE(p, nullptr); + a.Free(p, 4096); +} + +#ifndef PADDLE_ONLY_CPU + TEST(GPUAllocator, NonStaging) { paddle::memory::detail::GPUAllocator a; void* p = a.Alloc(4096); @@ -28,3 +44,5 @@ TEST(GPUAllocator, Staging) { EXPECT_NE(p, nullptr); a.Free(p, 4096); } + +#endif // PADDLE_ONLY_CPU diff --git a/paddle/memory/memory.cc b/paddle/memory/memory.cc index b617923731a..ca3c01ebdb0 100644 --- a/paddle/memory/memory.cc +++ b/paddle/memory/memory.cc @@ -14,48 +14,41 @@ limitations under the License. */ #include "paddle/memory/memory.h" +#include "paddle/memory/detail/cpu_allocator.h" +#include "paddle/memory/detail/gpu_allocator.h" + namespace paddle { namespace memory { -template <> -void* Alloc(CPUPlace, size_t size) { - return GetCPUBuddyAllocator(false /*non-staging*/)->Alloc(size); -} - -void* AllocStaging(CPUPlace, size_t size) { - return GetCPUBuddyAllocator(true /*staging*/)->Alloc(size); -} - -template <> -void* Alloc(GPUPlace pl, size_t size) { - return GetGPUBuddyAllocator(pl.device)->Alloc(size); -} - -template <> -void Free(CPUPlace, void* p) { - return GetCPUBuddyAllocator(false /*non-staging*/)->Free(p); -} - -void FreeStaging(CPUPlace, void* p) { - return GetCPUBuddyAllocator(false /*non-staging*/)->Free(p); -} - -#ifdef PADDLE_WITH_GPU -template <> -void* Alloc(GPUPlace pl, void* p) { - return GetGPUBuddyAllocator(pl.device)->Free(p); -} - -template <> -size_t Used(CPUPlace) { +void Alloc(paddle::platform::Place pl, size_t size) { +#ifndef PADDLE_ONLY_CPU + if (paddle::platform::is_gpu_place(pl)) { + return GetGPUBuddyAllocator(pl.device)->Alloc(size); + } +#endif // PADDLE_ONLY_CPU + PADDLE_ASSERT(paddle::platform::is_cpu_place(pl)); + return GetCPUBuddyAllocator()->Alloc(size); +} + +void Free(paddle::platform::Place pl, void* p) { +#ifndef PADDLE_ONLY_CPU + if (paddle::platform::is_gpu_place(pl)) { + GetGPUBuddyAllocator(pl.device)->Free(p); + } +#endif // PADDLE_ONLY_CPU + PADDLE_ASSERT(paddle::platform::is_cpu_place(pl)); + GetCPUBuddyAllocator()->Free(p); +} + +size_t Used(paddle::platform::Place pl) { +#ifndef PADDLE_ONLY_CPU + if (paddle::platform::is_gpu_place(pl)) { + return GetGPUBuddyAllocator(pl.device)->Used(); + } +#endif // PADDLE_ONLY_CPU + PADDLE_ASSERT(paddle::platform::is_cpu_place(pl)); return GetCPUBuddyAllocator()->Used(); } -template <> -size_t Alloc(GPUPlace pl) { - return GetGPUBuddyAllocator(pl.device)->Used(); -} -#endif // PADDLE_WITH_GPU - } // namespace memory } // namespace paddle diff --git a/paddle/memory/memory.h b/paddle/memory/memory.h index 8c15a133bb4..0bc609205ec 100644 --- a/paddle/memory/memory.h +++ b/paddle/memory/memory.h @@ -19,19 +19,9 @@ limitations under the License. */ namespace paddle { namespace memory { -template -void* Alloc(Place, size_t); -template -void Free(Place, void*); -template -size_t Used(Place); - -// Staging memory means "pinned" host memory that can be mapped into -// the CUDA memory space and accessed by the device rapidly. Don't -// allocate too much staging memory; otherwise system performance will -// degrade because the OS cannot find enough swap memory space. -void* AllocStaging(CPUPlace, size_t); -void* FreeStaging(CPUPlace, size_t); +void* Alloc(paddle::framework::Place, size_t); +void Free(paddle::framework::Place, void*); +size_t Used(paddle::framework::Place); } // namespace memory } // namespace paddle -- GitLab From 9cee78c380fd497fe2011868219eed89b7bc6808 Mon Sep 17 00:00:00 2001 From: gongweibao Date: Tue, 27 Jun 2017 09:29:22 +0800 Subject: [PATCH 0828/3256] fix precommit --- python/paddle/trainer_config_helpers/networks.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/paddle/trainer_config_helpers/networks.py b/python/paddle/trainer_config_helpers/networks.py index 1bf59ed4840..67154a8d7d3 100755 --- a/python/paddle/trainer_config_helpers/networks.py +++ b/python/paddle/trainer_config_helpers/networks.py @@ -1381,7 +1381,7 @@ def inputs(layers, *args): if len(args) != 0: layers.extend(args) - Inputs(*[l.name for l in layers]) + Inputs(* [l.name for l in layers]) def outputs(layers, *args): @@ -1424,7 +1424,7 @@ def outputs(layers, *args): assert len(layers) > 0 if HasInputsSet(): # input already set - Outputs(*[l.name for l in layers]) + Outputs(* [l.name for l in layers]) return # just return outputs. if len(layers) != 1: -- GitLab From 6250d108bfd39afb3b2beba438ecb22eca8991bc Mon Sep 17 00:00:00 2001 From: liaogang Date: Tue, 27 Jun 2017 09:51:55 +0800 Subject: [PATCH 0829/3256] FIX: clang-format --- paddle/memory/detail/gpu_allocator.h | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/paddle/memory/detail/gpu_allocator.h b/paddle/memory/detail/gpu_allocator.h index 9452c41fb89..682afdf7d33 100644 --- a/paddle/memory/detail/gpu_allocator.h +++ b/paddle/memory/detail/gpu_allocator.h @@ -16,8 +16,8 @@ limitations under the License. */ #include // for size_t -#include #include +#include namespace paddle { namespace memory { @@ -36,14 +36,14 @@ inline void throw_on_error(cudaError_t e, const char* message) { // default, we should use GPUAllocator. template class GPUAllocator { -public: + public: void* Alloc(size_t size); void Free(void* p, size_t size); }; template <> class GPUAllocator { -public: + public: void* Alloc(size_t size) { void* p = 0; cudaError_t result = cudaMalloc(&p, size); @@ -60,22 +60,22 @@ public: // that is returned if you ever call cudaFree after the // driver has already shutdown. This happens only if the // process is terminating, in which case we don't care if - // cudaFree succeeds. + // cudaFree succeeds. auto err = cudaFree(p); if (err != cudaErrorCudartUnloading) { - throw_on_error(err, "cudaFree failed"); + throw_on_error(err, "cudaFree failed"); } } }; template <> class GPUAllocator { -public: + public: void* Alloc(size_t size) { void* p = 0; cudaError_t result = cudaMallocHost(&p, size); if (result == cudaSuccess) { - return p; + return p; } // clear last error cudaGetLastError(); -- GitLab From 0990b0a5d41de2f6a82795f808b3ab36e9634a72 Mon Sep 17 00:00:00 2001 From: gongweibao Date: Tue, 27 Jun 2017 10:20:30 +0800 Subject: [PATCH 0830/3256] add testdata --- .../v2/reader/tests/test_recordio_creator.dat | Bin 0 -> 88 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 python/paddle/v2/reader/tests/test_recordio_creator.dat diff --git a/python/paddle/v2/reader/tests/test_recordio_creator.dat b/python/paddle/v2/reader/tests/test_recordio_creator.dat new file mode 100644 index 0000000000000000000000000000000000000000..17aa89b6796184407e83246d3f342a55a66b4a69 GIT binary patch literal 88 zcmZQ!W@2QOHw Date: Tue, 27 Jun 2017 10:50:13 +0800 Subject: [PATCH 0831/3256] "copy parameter content with memcpy" --- go/pserver/optimizer.go | 17 ++++++----------- go/pserver/service.go | 8 +++----- 2 files changed, 9 insertions(+), 16 deletions(-) diff --git a/go/pserver/optimizer.go b/go/pserver/optimizer.go index af7faad2549..df0ea373bb8 100644 --- a/go/pserver/optimizer.go +++ b/go/pserver/optimizer.go @@ -39,22 +39,20 @@ func newOptimizer(paramWithConfigs ParameterWithConfig) *optimizer { p := paramWithConfigs.Param c := paramWithConfigs.Config var cbuffer unsafe.Pointer - cbuffer = unsafe.Pointer(&p.Content[0]) + cbuffer_len := int(unsafe.Sizeof(p.Content[0])) * len(p.Content) + cbuffer = C.malloc(C.size_t(cbuffer_len)) + C.memcpy(cbuffer, unsafe.Pointer(&p.Content[0]), C.size_t(cbuffer_len)) o.opt = C.paddle_create_optimizer((*C.uchar)(&c[0]), C.int(len(c)), C.paddle_element_type(p.ElementType), cbuffer, C.int(len(p.Content)), (*C.char)(nullPtr), 0) return o } -func (o *optimizer) GetWeights(p *Parameter) error { +func (o *optimizer) GetWeights() []byte { // FIXME: get weigths from optimizer has bug var buffer unsafe.Pointer buffer_len := C.paddle_optimizer_get_weights(o.opt, &buffer) - if buffer_len == 0 || buffer == nullPtr { - return fmt.Errorf("parameter optimizer error : %s get failed", p.Name) - } - p.Content = cArrayToSlice(buffer, int(buffer_len)) - return nil + return cArrayToSlice(buffer, int(buffer_len)) } func (o *optimizer) UpdateParameter(g Gradient) error { @@ -62,10 +60,7 @@ func (o *optimizer) UpdateParameter(g Gradient) error { return fmt.Errorf("Name: %s, parameter and gradient element type not match, parameter: %v, gradient: %v", g.Name, o.ElementType, g.ElementType) } - // FIXME: do we need a copy? discard g.Content by GC ok - var cbuffer unsafe.Pointer - cbuffer = unsafe.Pointer(&g.Content[0]) - r := C.paddle_update_parameter(o.opt, C.paddle_element_type(g.ElementType), cbuffer, C.int(len(g.Content))) + r := C.paddle_update_parameter(o.opt, C.paddle_element_type(g.ElementType), unsafe.Pointer(&g.Content[0]), C.int(len(g.Content))) if r != 0 { return fmt.Errorf("optimizer update returned error code: %d", r) } diff --git a/go/pserver/service.go b/go/pserver/service.go index cdd433260af..2ab622d7902 100644 --- a/go/pserver/service.go +++ b/go/pserver/service.go @@ -44,8 +44,7 @@ type Gradient Parameter type Service struct { initialized chan struct{} - mu sync.Mutex - // injection from parameter to optimizer + mu sync.Mutex optMap map[string]*optimizer } @@ -130,9 +129,8 @@ func (s *Service) GetParam(name string, parameter *Parameter) error { // paramter content. parameter.Name = name parameter.ElementType = opt.ElementType - - err := opt.GetWeights(parameter) - return err + parameter.Content = opt.GetWeights() + return nil } // Save tells the parameter server to save parameters. -- GitLab From 5b31bd7ae3345e555493c26b1b941f6dfcfd2387 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Tue, 27 Jun 2017 11:29:30 +0800 Subject: [PATCH 0832/3256] Add `Variable::IsType` Make user can check what type is in variable --- paddle/framework/variable.h | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/paddle/framework/variable.h b/paddle/framework/variable.h index b33e10e6820..72c4a7a2a1d 100644 --- a/paddle/framework/variable.h +++ b/paddle/framework/variable.h @@ -25,21 +25,24 @@ class Variable { public: template const T& Get() const { - PADDLE_ASSERT(holder_ != nullptr); - PADDLE_ASSERT(std::type_index(typeid(T)) == - std::type_index(holder_->Type())); + PADDLE_ASSERT(IsType()); return *static_cast(holder_->Ptr()); } template T* GetMutable() { - if (holder_ == nullptr || - std::type_index(typeid(T)) != std::type_index(holder_->Type())) { + if (!IsType()) { holder_.reset(new PlaceholderImpl(new T())); } return static_cast(holder_->Ptr()); } + template + bool IsType() const { + return holder_ != nullptr && + std::type_index(typeid(T)) == std::type_index(holder_->Type()); + } + private: struct Placeholder { virtual ~Placeholder() {} -- GitLab From 4bfd757e7851dca20419fc09e726f6aac004864c Mon Sep 17 00:00:00 2001 From: liaogang Date: Tue, 27 Jun 2017 11:31:14 +0800 Subject: [PATCH 0833/3256] ENH: Add go doc --- cmake/generic.cmake | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/cmake/generic.cmake b/cmake/generic.cmake index 850c1868e68..6839abc1a7b 100644 --- a/cmake/generic.cmake +++ b/cmake/generic.cmake @@ -77,6 +77,15 @@ # /cmake/external/*.cmake: # # cc_test(example_test SRCS example_test.cc DEPS example glog gflags) +# +# To build a go static library using Golang, use the go_ prefixed version: +# +# go_library(example STATIC) +# +# To build a go shared library using Golang, use the go_ prefixed version: +# +# go_library(example SHARED) +# if(NOT APPLE) find_package(Threads REQUIRED) @@ -248,15 +257,6 @@ set(GOPATH "${CMAKE_CURRENT_BINARY_DIR}/go") file(MAKE_DIRECTORY ${GOPATH}) set(PADDLE_IN_GOPATH "${GOPATH}/src/github.com/PaddlePaddle/Paddle") -# Because api.go defines a GO wrapper to ops and tensor, it depends on -# both. This implies that if any of tensor.{h,cc}, ops.{h,cu}, or -# api.go is changed, api need to be re-built. -# go_library(api -# SRCS -# api.go -# DEPS -# tensor # Because ops depend on tensor, this line is optional. -# ops) function(go_library TARGET_NAME) set(options STATIC static SHARED shared) set(oneValueArgs "") @@ -296,7 +296,7 @@ function(go_library TARGET_NAME) COMMAND rm -rf ${PADDLE_IN_GOPATH} COMMAND ln -sf ${CMAKE_SOURCE_DIR} ${PADDLE_IN_GOPATH} # Automatically get all dependencies specified in the source code - COMMAND env GOPATH=${GOPATH} ${CMAKE_Go_COMPILER} get -d . + COMMAND env GOPATH=${GOPATH} ${CMAKE_Go_COMPILER} get -d ./.. # Golang build source code COMMAND env GOPATH=${GOPATH} ${CMAKE_Go_COMPILER} build ${BUILD_MODE} -o "${CMAKE_CURRENT_BINARY_DIR}/${LIB_NAME}" -- GitLab From a489a54de0f730586c22f40881dc2b510fbe6890 Mon Sep 17 00:00:00 2001 From: gongweibao Date: Tue, 27 Jun 2017 11:32:43 +0800 Subject: [PATCH 0834/3256] fix style --- python/paddle/v2/reader/creator.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/python/paddle/v2/reader/creator.py b/python/paddle/v2/reader/creator.py index 5e052026f61..9f888b16d6b 100644 --- a/python/paddle/v2/reader/creator.py +++ b/python/paddle/v2/reader/creator.py @@ -16,7 +16,6 @@ Creator package contains some simple reader creator, which could be used in user program. """ - __all__ = ['np_array', 'text_file', "recordio"] @@ -76,4 +75,4 @@ def recordio(path): yield r f.close() - return reader \ No newline at end of file + return reader -- GitLab From f149d183f7d78fdaa171f2afabaf8a138596c8ff Mon Sep 17 00:00:00 2001 From: Yi Wang Date: Mon, 26 Jun 2017 20:41:33 -0700 Subject: [PATCH 0835/3256] Add system_allocator --- paddle/memory/detail/CMakeLists.txt | 6 +- paddle/memory/detail/system_allocator.h | 84 ++++++++++++------- paddle/memory/detail/system_allocator_test.cc | 44 +++++----- 3 files changed, 81 insertions(+), 53 deletions(-) diff --git a/paddle/memory/detail/CMakeLists.txt b/paddle/memory/detail/CMakeLists.txt index 3b5bbd7a12f..c16dfadeb21 100644 --- a/paddle/memory/detail/CMakeLists.txt +++ b/paddle/memory/detail/CMakeLists.txt @@ -1 +1,5 @@ -cc_test(system_allocator_test SRCS system_allocator_test.cc) +if(${WITH_GPU}) + nv_test(system_allocator_test SRCS system_allocator_test.cc) +else(${WITH_GPU}) + cc_test(system_allocator_test SRCS system_allocator_test.cc) +endif(${WITH_GPU}) diff --git a/paddle/memory/detail/system_allocator.h b/paddle/memory/detail/system_allocator.h index 0a645531889..1768f9a0da6 100644 --- a/paddle/memory/detail/system_allocator.h +++ b/paddle/memory/detail/system_allocator.h @@ -23,14 +23,31 @@ limitations under the License. */ #include #endif // PADDLE_ONLY_CPU +#include "paddle/platform/assert.h" + namespace paddle { namespace memory { namespace detail { -class SystemAllocator { +class CPUDeleter { public: - virtual void* Alloc(size_t size) = 0; - virtual void* Free(void* p) = 0; + CPUDeleter(void* ptr, size_t size, bool locked) + : ptr_(ptr), size_(size), locked_(locked) {} + + void* Ptr() { return ptr_; } + + void operator()(void* ptr) { + PADDLE_ASSERT(ptr == ptr_); + if (ptr_ != nullptr && locked_) { + munlock(ptr_, size_); + } + std::free(ptr_); + } + + private: + void* ptr_; + size_t size_; + bool locked_; }; // CPUAllocator calls mlock, which returns pinned @@ -39,21 +56,14 @@ class SystemAllocator { // available to the system for paging. So, by default, we should use // CPUAllocator. template -class CPUAllocator : public SystemAllocator { +class CPUAllocator { public: - virtual void* Alloc(size_t size) { + static CPUDeleter Alloc(size_t size) { void* p = std::malloc(size); if (p != nullptr && lock_memory) { mlock(p, size); } - return p; - } - - virtual void Free(void* p, size_t size) { - if (p != nullptr && lock_memory) { - munlock(p, size); - } - std::free(p); + return CPUDeleter(p, size, lock_memory); } }; @@ -67,6 +77,32 @@ inline void throw_on_error(cudaError_t e, const char* message) { } } // namespace +class GPUDeleter { + public: + GPUDeleter(void* ptr, size_t size, bool staging) + : ptr_(ptr), size_(size), staging_(staging) {} + + void* Ptr() { return ptr_; } + + void operator()(void* ptr) { + PADDLE_ASSERT(ptr == ptr_); + // Purposefully allow cudaErrorCudartUnloading, because + // that is returned if you ever call cudaFree after the + // driver has already shutdown. This happens only if the + // process is terminating, in which case we don't care if + // cudaFree succeeds. + cudaError_t err = staging_ ? cudaFreeHost(ptr) : cudaFree(ptr); + if (err != cudaErrorCudartUnloading) { + throw_on_error(err, "cudaFree{Host} failed"); + } + } + + private: + void* ptr_; + size_t size_; + bool staging_; +}; + // GPUAllocator calls cudaHostMalloc, which returns // pinned and locked memory as staging areas for data exchange // between host and device. Allocates too much would reduce the @@ -75,28 +111,14 @@ inline void throw_on_error(cudaError_t e, const char* message) { template class GPUAllocator { public: - void* Alloc(size_t size) { + static GPUDeleter Alloc(size_t size) { void* p = 0; cudaError_t result = staging ? cudaMallocHost(&p, size) : cudaMalloc(&p, size); - if (result == cudaSuccess) { - return p; - } - // clear last error - cudaGetLastError(); - return nullptr; - } - - void Free(void* p, size_t size) { - // Purposefully allow cudaErrorCudartUnloading, because - // that is returned if you ever call cudaFree after the - // driver has already shutdown. This happens only if the - // process is terminating, in which case we don't care if - // cudaFree succeeds. - auto err = staging ? cudaFreeHost(p) : cudaFree(p); - if (err != cudaErrorCudartUnloading) { - throw_on_error(err, "cudaFree failed"); + if (result != cudaSuccess) { + cudaGetLastError(); // clear error if there is any. } + return GPUDeleter(result == cudaSuccess ? p : nullptr, size, staging); } }; diff --git a/paddle/memory/detail/system_allocator_test.cc b/paddle/memory/detail/system_allocator_test.cc index 4e7b8018b6a..fec70a65b77 100644 --- a/paddle/memory/detail/system_allocator_test.cc +++ b/paddle/memory/detail/system_allocator_test.cc @@ -13,36 +13,38 @@ See the License for the specific language governing permissions and limitations under the License. */ #include "paddle/memory/detail/system_allocator.h" + +#include +#include + #include "gtest/gtest.h" -TEST(CPUAllocator, NoLockMem) { - paddle::memory::detail::CPUAllocator a; - void* p = a.Alloc(4096); - EXPECT_NE(p, nullptr); - a.Free(p, 4096); +template +void TestAllocator() { + { + auto d = Allocator::Alloc(sizeof(int)); + EXPECT_NE(d.Ptr(), nullptr); + std::unique_ptr p(static_cast(d.Ptr()), d); + } + { + auto d = Allocator::Alloc(0); + EXPECT_EQ(d.Ptr(), nullptr); + std::unique_ptr p(static_cast(d.Ptr()), d); + } } +TEST(CPUAllocator, NoLockMem) { + TestAllocator>(); +} TEST(CPUAllocator, LockMem) { - paddle::memory::detail::CPUAllocator a; - void* p = a.Alloc(4096); - EXPECT_NE(p, nullptr); - a.Free(p, 4096); + TestAllocator>(); } #ifndef PADDLE_ONLY_CPU - -TEST(GPUAllocator, NonStaging) { - paddle::memory::detail::GPUAllocator a; - void* p = a.Alloc(4096); - EXPECT_NE(p, nullptr); - a.Free(p, 4096); +TEST(GPUAllocator, NoStaging) { + TestAllocator>(); } - TEST(GPUAllocator, Staging) { - paddle::memory::detail::GPUAllocator a; - void* p = a.Alloc(4096); - EXPECT_NE(p, nullptr); - a.Free(p, 4096); + TestAllocator>(); } - #endif // PADDLE_ONLY_CPU -- GitLab From c7610106032f63a0dea4d87bca88a61fc21fe8e3 Mon Sep 17 00:00:00 2001 From: hedaoyuan Date: Tue, 27 Jun 2017 13:32:06 +0800 Subject: [PATCH 0836/3256] Add unit test for im2col. --- paddle/function/CMakeLists.txt | 1 + paddle/function/Im2ColTest.cpp | 110 +++++++++++++++++++++++++++++++++ 2 files changed, 111 insertions(+) create mode 100644 paddle/function/Im2ColTest.cpp diff --git a/paddle/function/CMakeLists.txt b/paddle/function/CMakeLists.txt index 19f64eefd18..178d1153f43 100644 --- a/paddle/function/CMakeLists.txt +++ b/paddle/function/CMakeLists.txt @@ -33,6 +33,7 @@ if(WITH_GPU) endif() add_simple_unittest(ConvOpTest) +add_simple_unittest(Im2ColTest) endif() add_style_check_target(paddle_function ${h_files}) diff --git a/paddle/function/Im2ColTest.cpp b/paddle/function/Im2ColTest.cpp new file mode 100644 index 00000000000..d7dbf087c5f --- /dev/null +++ b/paddle/function/Im2ColTest.cpp @@ -0,0 +1,110 @@ +/* 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 "Im2Col.h" +#include +#include "Function.h" +#include "paddle/math/Matrix.h" +#include "paddle/math/tests/TensorCheck.h" + +namespace paddle { + +TEST(Im2ColFunctor, real) { + for (size_t channels : {1, 5, 32}) { + for (size_t inputHeight : {5, 33, 100}) { + for (size_t inputWidth : {5, 32, 96}) { + for (size_t filterHeight : {1, 5}) { + for (size_t filterWidth : {3, 7}) { + for (size_t stride : {1, 2}) { + for (size_t padding : {0, 1}) { + if (inputHeight <= filterHeight || inputWidth <= filterWidth) + break; + if (padding >= filterHeight || padding >= filterWidth) break; + size_t outputHeight = + (inputHeight - filterHeight + 2 * padding + stride) / + stride; + size_t outputWidth = + (inputWidth - filterWidth + 2 * padding + stride) / stride; + + TensorShape imShape = + TensorShape({channels, inputHeight, inputWidth}); + TensorShape colShape1 = TensorShape({channels, + filterHeight, + filterWidth, + outputHeight, + outputWidth}); + TensorShape colShape2 = TensorShape({outputHeight, + outputWidth, + channels, + filterHeight, + filterWidth}); + + VectorPtr input = Vector::create(imShape.getElements(), false); + size_t height = channels * filterHeight * filterWidth; + size_t width = outputHeight * outputWidth; + MatrixPtr output1 = Matrix::create(height, width, false, false); + MatrixPtr output2 = Matrix::create(width, height, false, false); + Im2ColFunctor im2col1; + Im2ColFunctor im2col2; + + input->uniform(0.001, 1); + im2col1(input->getData(), + imShape, + output1->getData(), + colShape1, + stride, + stride, + padding, + padding); + im2col2(input->getData(), + imShape, + output2->getData(), + colShape2, + stride, + stride, + padding, + padding); + + MatrixPtr test; + output2->transpose(test, true); + autotest::TensorCheckErr(*output1, *test); + } + } + } + } + } + } + } +} + +#if 0 +TEST(Col2ImFunctor, real) { + for (size_t channels : {1, 5, 32}) { + for (size_t inputHeight : {5, 33, 100}) { + for (size_t inputWidth : {5, 32, 96}) { + for (size_t filterHeight : {1, 5}) { + for (size_t filterWidth : {3, 7}) { + for (size_t stride : {1, 2}) { + for (size_t padding : {0, 1}) { + } + } + } + } + } + } + } +} +#endif + +} // namespace paddle -- GitLab From 49e87ee327a7115cca08384e24e043fbe73413c5 Mon Sep 17 00:00:00 2001 From: hedaoyuan Date: Tue, 27 Jun 2017 14:19:34 +0800 Subject: [PATCH 0837/3256] Change the CpuMatrix::copyFrom and CpuVector::copyFrom with the stream parameter to the synchronous interface. --- paddle/gserver/layers/Layer.cpp | 5 +++++ paddle/math/Matrix.cpp | 2 ++ paddle/math/Matrix.h | 3 ++- paddle/math/Vector.cpp | 2 ++ paddle/math/Vector.h | 8 ++++---- paddle/math/tests/test_matrixCompare.cpp | 14 ++++++++++++++ 6 files changed, 29 insertions(+), 5 deletions(-) diff --git a/paddle/gserver/layers/Layer.cpp b/paddle/gserver/layers/Layer.cpp index 125aaf947f3..4b92b5d163a 100644 --- a/paddle/gserver/layers/Layer.cpp +++ b/paddle/gserver/layers/Layer.cpp @@ -191,6 +191,11 @@ void Layer::addOutputArgument(int deviceId) { void Layer::copyOutputToOtherDevice() { for (size_t i = 0; i != outputOtherDevice_.size(); i++) { SetDevice device(outputOtherDevice_[i].deviceId); + // If outputOtherDevice_[i].value is a CpuMatrix, + // the copyFrom is a synchronous interface. + // If outputOtherDevice_[i].value is a GpuMatrix, since subsequent + // calculations are all on HPPL_STREAM_DEFAULT, + // copyFrom can be an asynchronous interface. outputOtherDevice_[i].value->copyFrom(*getOutputValue(), HPPL_STREAM_DEFAULT); outputOtherDevice_[i].sequenceStartPositions = diff --git a/paddle/math/Matrix.cpp b/paddle/math/Matrix.cpp index c910146164e..4431d613f65 100644 --- a/paddle/math/Matrix.cpp +++ b/paddle/math/Matrix.cpp @@ -1565,6 +1565,8 @@ void CpuMatrix::copyFrom(const Matrix& src, hl_stream_t stream) { const_cast(src.getData()), sizeof(real) * elementCnt_, stream); + // There is a need to add synchronization to ensure that the data is copied. + hl_stream_synchronize(stream); } else if (typeid(src) == typeid(CpuMatrix)) { memcpy(data_, src.getData(), sizeof(real) * elementCnt_); } else { diff --git a/paddle/math/Matrix.h b/paddle/math/Matrix.h index 748be850b4c..7dfd5932250 100644 --- a/paddle/math/Matrix.h +++ b/paddle/math/Matrix.h @@ -239,7 +239,8 @@ public: LOG(FATAL) << "Not implemented"; } - // asynchronous copy + // For GpuMatrix this is an asynchronous copy interface + // For CpuMatrix this is an synchronous copy interface virtual void copyFrom(const Matrix& src, hl_stream_t stream) { LOG(FATAL) << "Not implemented"; } diff --git a/paddle/math/Vector.cpp b/paddle/math/Vector.cpp index c519ca500af..eb87ee9bb79 100644 --- a/paddle/math/Vector.cpp +++ b/paddle/math/Vector.cpp @@ -657,6 +657,8 @@ void CpuVectorT::copyFrom(const VectorT& src, hl_stream_t stream) { (void*)src.getData(), sizeof(T) * this->getSize(), stream); + // There is a need to add synchronization to ensure that the data is copied. + hl_stream_synchronize(stream); } else { src.copyTo(this); } diff --git a/paddle/math/Vector.h b/paddle/math/Vector.h index 9af6e30c9e1..80b9775fccf 100644 --- a/paddle/math/Vector.h +++ b/paddle/math/Vector.h @@ -168,11 +168,11 @@ public: virtual void copyFrom(const VectorT& src) = 0; /** - * If use_gpu, this function will push the copy-task to the specifed-stream - * and return immediately. + * If GpuVector, this function is an asynchronous interface, + * will push the copy-task to the specifed-stream and return immediately. * - * If not use GPU, this function is same as - * the copyFrom(const VectorT& src), which use stream HPPL_STREAM_DEFAULT. + * If CpuVector, this function is an synchronous interface, + * same as the copyFrom(const VectorT& src). */ virtual void copyFrom(const VectorT& src, hl_stream_t stream) = 0; diff --git a/paddle/math/tests/test_matrixCompare.cpp b/paddle/math/tests/test_matrixCompare.cpp index 5a0dffe086c..354f58df393 100644 --- a/paddle/math/tests/test_matrixCompare.cpp +++ b/paddle/math/tests/test_matrixCompare.cpp @@ -1127,4 +1127,18 @@ TEST(Matrix, MaxOutFwdBwd) { } } +TEST(CpuMatrix, copyFrom) { + const size_t height = 1000; + const size_t width = 1000; + CpuMatrix cpu(height, width); + GpuMatrix gpu(height, width); + CpuMatrix copy(height, width); + + cpu.randomizeUniform(); + gpu.copyFrom(cpu); + copy.copyFrom(gpu, HPPL_STREAM_DEFAULT); + + TensorCheckEqual(cpu, copy); +} + #endif -- GitLab From 714c2c2d4253fdcf27df781efda1dfe13fdffa68 Mon Sep 17 00:00:00 2001 From: wuyi05 Date: Tue, 27 Jun 2017 15:06:03 +0800 Subject: [PATCH 0838/3256] fix go get -d ./.. --- cmake/generic.cmake | 2 +- paddle/scripts/docker/build.sh | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/cmake/generic.cmake b/cmake/generic.cmake index 6839abc1a7b..11c1f677ae5 100644 --- a/cmake/generic.cmake +++ b/cmake/generic.cmake @@ -296,7 +296,7 @@ function(go_library TARGET_NAME) COMMAND rm -rf ${PADDLE_IN_GOPATH} COMMAND ln -sf ${CMAKE_SOURCE_DIR} ${PADDLE_IN_GOPATH} # Automatically get all dependencies specified in the source code - COMMAND env GOPATH=${GOPATH} ${CMAKE_Go_COMPILER} get -d ./.. + COMMAND env GOPATH=${GOPATH} ${CMAKE_Go_COMPILER} get -d ./... # Golang build source code COMMAND env GOPATH=${GOPATH} ${CMAKE_Go_COMPILER} build ${BUILD_MODE} -o "${CMAKE_CURRENT_BINARY_DIR}/${LIB_NAME}" diff --git a/paddle/scripts/docker/build.sh b/paddle/scripts/docker/build.sh index 2b48e4dc0f8..a182e5f4aef 100644 --- a/paddle/scripts/docker/build.sh +++ b/paddle/scripts/docker/build.sh @@ -31,6 +31,7 @@ Configuring cmake in /paddle/build ... -DWITH_DOC=OFF -DWITH_GPU=${WITH_GPU:-OFF} -DWITH_AVX=${WITH_AVX:-OFF} + -DWITH_GOLANG=${WITH_GOLANG:-OFF} -DWITH_SWIG_PY=ON -DCUDNN_ROOT=/usr/ -DWITH_STYLE_CHECK=${WITH_STYLE_CHECK:-OFF} @@ -43,6 +44,7 @@ cmake .. \ -DWITH_DOC=OFF \ -DWITH_GPU=${WITH_GPU:-OFF} \ -DWITH_AVX=${WITH_AVX:-OFF} \ + -DWITH_GOLANG=${WITH_GOLANG:-OFF} \ -DWITH_SWIG_PY=ON \ -DCUDNN_ROOT=/usr/ \ -DWITH_STYLE_CHECK=${WITH_STYLE_CHECK:-OFF} \ -- GitLab From a83d52151cbe6ed82b0b35eb21219442a8ac926a Mon Sep 17 00:00:00 2001 From: hedaoyuan Date: Tue, 27 Jun 2017 17:34:24 +0800 Subject: [PATCH 0839/3256] Add unit test for Col2ImFunctor. --- paddle/function/Im2ColTest.cpp | 63 +++++++++++++++++++++------------- 1 file changed, 39 insertions(+), 24 deletions(-) diff --git a/paddle/function/Im2ColTest.cpp b/paddle/function/Im2ColTest.cpp index d7dbf087c5f..acc88a553ab 100644 --- a/paddle/function/Im2ColTest.cpp +++ b/paddle/function/Im2ColTest.cpp @@ -20,7 +20,8 @@ limitations under the License. */ namespace paddle { -TEST(Im2ColFunctor, real) { +template +void TestIm2ColFunctor() { for (size_t channels : {1, 5, 32}) { for (size_t inputHeight : {5, 33, 100}) { for (size_t inputWidth : {5, 32, 96}) { @@ -50,16 +51,18 @@ TEST(Im2ColFunctor, real) { filterHeight, filterWidth}); - VectorPtr input = Vector::create(imShape.getElements(), false); size_t height = channels * filterHeight * filterWidth; size_t width = outputHeight * outputWidth; + VectorPtr input1 = Vector::create(imShape.getElements(), false); + VectorPtr input2 = Vector::create(imShape.getElements(), false); MatrixPtr output1 = Matrix::create(height, width, false, false); MatrixPtr output2 = Matrix::create(width, height, false, false); - Im2ColFunctor im2col1; - Im2ColFunctor im2col2; + input1->uniform(0.001, 1); + input2->copyFrom(*input1); - input->uniform(0.001, 1); - im2col1(input->getData(), + Im2ColFunctor im2Col1; + Im2ColFunctor im2Col2; + im2Col1(input1->getData(), imShape, output1->getData(), colShape1, @@ -67,7 +70,7 @@ TEST(Im2ColFunctor, real) { stride, padding, padding); - im2col2(input->getData(), + im2Col2(input2->getData(), imShape, output2->getData(), colShape2, @@ -76,27 +79,32 @@ TEST(Im2ColFunctor, real) { padding, padding); + // The transposition of the result of ColFormat == kCFO + // is equal to the result of ColFormat == kOCF. MatrixPtr test; output2->transpose(test, true); autotest::TensorCheckErr(*output1, *test); - } - } - } - } - } - } - } -} -#if 0 -TEST(Col2ImFunctor, real) { - for (size_t channels : {1, 5, 32}) { - for (size_t inputHeight : {5, 33, 100}) { - for (size_t inputWidth : {5, 32, 96}) { - for (size_t filterHeight : {1, 5}) { - for (size_t filterWidth : {3, 7}) { - for (size_t stride : {1, 2}) { - for (size_t padding : {0, 1}) { + Col2ImFunctor col2Im1; + Col2ImFunctor col2Im2; + col2Im1(input1->getData(), + imShape, + output1->getData(), + colShape1, + stride, + stride, + padding, + padding); + col2Im2(input2->getData(), + imShape, + output2->getData(), + colShape2, + stride, + stride, + padding, + padding); + + autotest::TensorCheckErr(*input1, *input2); } } } @@ -105,6 +113,13 @@ TEST(Col2ImFunctor, real) { } } } + +TEST(Im2ColFunctor, CPU) { TestIm2ColFunctor(); } + +#ifndef PADDLE_ONLY_CPU + +TEST(Im2ColFunctor, GPU) { TestIm2ColFunctor(); } + #endif } // namespace paddle -- GitLab From a7ff11404d097f759aaa2142458750631a9b7641 Mon Sep 17 00:00:00 2001 From: hedaoyuan Date: Tue, 27 Jun 2017 17:53:31 +0800 Subject: [PATCH 0840/3256] Change the ImageFunction name to BlockFunction(Consistent with the name of Layer). --- paddle/function/ImageExpandOp.cpp | 18 +++++++++--------- paddle/function/ImageExpandOpTest.cpp | 8 ++++---- paddle/gserver/layers/BlockExpandLayer.cpp | 4 ++-- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/paddle/function/ImageExpandOp.cpp b/paddle/function/ImageExpandOp.cpp index 00a2571936b..a89b6bba458 100644 --- a/paddle/function/ImageExpandOp.cpp +++ b/paddle/function/ImageExpandOp.cpp @@ -32,7 +32,7 @@ namespace paddle { * \param inputs[0] Sequence data of NST format. * \param outputs[0] Image data of NCHW format. */ -class ImageExpandFunction : public FunctionBase { +class BlockExpandFunction : public FunctionBase { public: void init(const FuncConfig& config) override { // function arguments @@ -100,10 +100,10 @@ protected: }; template -class ImageExpandForward : public ImageExpandFunction { +class BlockExpandForward : public BlockExpandFunction { public: void init(const FuncConfig& config) override { - ImageExpandFunction::init(config); + BlockExpandFunction::init(config); } void check(const BufferArgs& inputs, const BufferArgs& outputs) override { @@ -148,10 +148,10 @@ public: }; template -class ImageExpandBackward : public ImageExpandFunction { +class BlockExpandBackward : public BlockExpandFunction { public: void init(const FuncConfig& config) override { - ImageExpandFunction::init(config); + BlockExpandFunction::init(config); } void check(const BufferArgs& inputs, const BufferArgs& outputs) override { @@ -192,11 +192,11 @@ public: } }; -REGISTER_TYPED_FUNC(ImageExpand, CPU, ImageExpandForward); -REGISTER_TYPED_FUNC(ImageExpandGrad, CPU, ImageExpandBackward); +REGISTER_TYPED_FUNC(BlockExpand, CPU, BlockExpandForward); +REGISTER_TYPED_FUNC(BlockExpandGrad, CPU, BlockExpandBackward); #ifndef PADDLE_ONLY_CPU -REGISTER_TYPED_FUNC(ImageExpand, GPU, ImageExpandForward); -REGISTER_TYPED_FUNC(ImageExpandGrad, GPU, ImageExpandBackward); +REGISTER_TYPED_FUNC(BlockExpand, GPU, BlockExpandForward); +REGISTER_TYPED_FUNC(BlockExpandGrad, GPU, BlockExpandBackward); #endif } // namespace paddle diff --git a/paddle/function/ImageExpandOpTest.cpp b/paddle/function/ImageExpandOpTest.cpp index fb312549dc7..5e4897e72ba 100644 --- a/paddle/function/ImageExpandOpTest.cpp +++ b/paddle/function/ImageExpandOpTest.cpp @@ -17,7 +17,7 @@ limitations under the License. */ namespace paddle { -TEST(ImageExpandForward, real) { +TEST(BlockExpandForward, real) { for (size_t batchSize : {5, 32}) { for (size_t channels : {1, 5, 32}) { for (size_t inputHeight : {5, 33, 100}) { @@ -29,7 +29,7 @@ TEST(ImageExpandForward, real) { std::vector strides = {stride, stride}; std::vector paddings = {padding, padding}; std::vector blocks = {block, block}; - CpuGpuFuncCompare test("ImageExpand", + CpuGpuFuncCompare test("BlockExpand", FuncConfig() .set("strides", strides) .set("paddings", paddings) @@ -60,7 +60,7 @@ TEST(ImageExpandForward, real) { } } -TEST(ImageExpandBackward, real) { +TEST(BlockExpandBackward, real) { for (size_t batchSize : {5, 32}) { for (size_t channels : {1, 5, 32}) { for (size_t inputHeight : {5, 33, 100}) { @@ -72,7 +72,7 @@ TEST(ImageExpandBackward, real) { std::vector strides = {stride, stride}; std::vector paddings = {padding, padding}; std::vector blocks = {block, block}; - CpuGpuFuncCompare test("ImageExpandGrad", + CpuGpuFuncCompare test("BlockExpandGrad", FuncConfig() .set("strides", strides) .set("paddings", paddings) diff --git a/paddle/gserver/layers/BlockExpandLayer.cpp b/paddle/gserver/layers/BlockExpandLayer.cpp index adc9a814ffb..3b1f3463591 100644 --- a/paddle/gserver/layers/BlockExpandLayer.cpp +++ b/paddle/gserver/layers/BlockExpandLayer.cpp @@ -41,13 +41,13 @@ bool BlockExpandLayer::init(const LayerMap& layerMap, std::vector paddings = {(size_t)paddingH_, (size_t)paddingW_}; std::vector blocks = {(size_t)blockH_, (size_t)blockW_}; createFunction(forward_, - "ImageExpand", + "BlockExpand", FuncConfig() .set("strides", strides) .set("paddings", paddings) .set("blocks", blocks)); createFunction(backward_, - "ImageExpandGrad", + "BlockExpandGrad", FuncConfig() .set("strides", strides) .set("paddings", paddings) -- GitLab From 7a550f90d8a7a1aea81f300d127f3aef975f8693 Mon Sep 17 00:00:00 2001 From: hedaoyuan Date: Tue, 27 Jun 2017 18:05:14 +0800 Subject: [PATCH 0841/3256] Fix the function file name. --- paddle/function/{ImageExpandOp.cpp => BlockExpandOp.cpp} | 0 .../function/{ImageExpandOpTest.cpp => BlockExpandOpTest.cpp} | 0 paddle/function/CMakeLists.txt | 2 +- 3 files changed, 1 insertion(+), 1 deletion(-) rename paddle/function/{ImageExpandOp.cpp => BlockExpandOp.cpp} (100%) rename paddle/function/{ImageExpandOpTest.cpp => BlockExpandOpTest.cpp} (100%) diff --git a/paddle/function/ImageExpandOp.cpp b/paddle/function/BlockExpandOp.cpp similarity index 100% rename from paddle/function/ImageExpandOp.cpp rename to paddle/function/BlockExpandOp.cpp diff --git a/paddle/function/ImageExpandOpTest.cpp b/paddle/function/BlockExpandOpTest.cpp similarity index 100% rename from paddle/function/ImageExpandOpTest.cpp rename to paddle/function/BlockExpandOpTest.cpp diff --git a/paddle/function/CMakeLists.txt b/paddle/function/CMakeLists.txt index 178d1153f43..bef4d2955b3 100644 --- a/paddle/function/CMakeLists.txt +++ b/paddle/function/CMakeLists.txt @@ -29,7 +29,7 @@ if(WITH_GPU) add_simple_unittest(MulOpTest) add_simple_unittest(CosSimOpTest) add_simple_unittest(RowConvOpTest) - add_simple_unittest(ImageExpandOpTest) + add_simple_unittest(BlockExpandOpTest) endif() add_simple_unittest(ConvOpTest) -- GitLab From ab91232cf6bad3c9ff5595c6d655eb538a651f24 Mon Sep 17 00:00:00 2001 From: qijun Date: Tue, 27 Jun 2017 20:16:52 +0800 Subject: [PATCH 0842/3256] add cmake external project for eigen --- CMakeLists.txt | 1 + cmake/external/eigen.cmake | 20 ++++++++++++++++ paddle/framework/ddim_test.cc | 44 +++++++++++++++++++++++++++++++++++ 3 files changed, 65 insertions(+) create mode 100644 cmake/external/eigen.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 3c719d35ece..9be75f4a7de 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -92,6 +92,7 @@ include(external/openblas) # download, build, install openblas include(external/swig) # download, build, install swig include(external/warpctc) # download, build, install warpctc include(external/any) # download libn::any +include(external/eigen) # download eigen3 include(generic) # simplify cmake module include(package) # set paddle packages diff --git a/cmake/external/eigen.cmake b/cmake/external/eigen.cmake new file mode 100644 index 00000000000..543504a274d --- /dev/null +++ b/cmake/external/eigen.cmake @@ -0,0 +1,20 @@ +INCLUDE(ExternalProject) + +SET(EIGEN_SOURCE_DIR ${THIRD_PARTY_PATH}/eigen3) + +INCLUDE_DIRECTORIES(${EIGEN_SOURCE_DIR}/src/) + +ExternalProject_Add( + eigen3 + ${EXTERNAL_PROJECT_LOG_ARGS} + URL "https://bitbucket.org/eigen/eigen/get/f3a22f35b044.tar.gz" + URL_MD5 "4645c66075982da6fa0bcf6b20f3e8f7" + PREFIX ${EIGEN_SOURCE_DIR} + UPDATE_COMMAND "" + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "" + TEST_COMMAND "" +) + +LIST(APPEND external_project_dependencies eigen3) \ No newline at end of file diff --git a/paddle/framework/ddim_test.cc b/paddle/framework/ddim_test.cc index 36eef02370e..3e4ad840f31 100644 --- a/paddle/framework/ddim_test.cc +++ b/paddle/framework/ddim_test.cc @@ -1,6 +1,9 @@ #include #include +#include "eigen3/Eigen/Core" +#include "eigen3/Eigen/Dense" +#include "eigen3/unsupported/Eigen/CXX11/Tensor" #include "gtest/gtest.h" #include "paddle/framework/ddim.h" @@ -61,3 +64,44 @@ TEST(DDim, Print) { ss << ddim; EXPECT_EQ("2, 3, 4", ss.str()); } + +template +using Vec = + Eigen::TensorMap, + Eigen::Aligned>; + +template +using Matrix = + Eigen::TensorMap, + Eigen::Aligned>; + +template +void print(T* input, int size) { + for (int i = 0; i < size; i++) { + std::cout << input[i] << " "; + } + std::cout << std::endl; +} + +TEST(Eigen, start) { + int size = 4; + + float* t_a = (float*)malloc(size * sizeof(float)); + float* t_b = (float*)malloc(size * sizeof(float)); + float* t_c = (float*)malloc(size * sizeof(float)); + for (int i = 0; i < size; i++) { + t_a[i] = i; + t_b[i] = i; + } + Vec a(t_a, size); + Vec b(t_b, size); + Vec c(t_c, size); + + Eigen::DefaultDevice dd; + c.device(dd) = a + b; + print(t_c, size); + + free(t_a); + free(t_b); + free(t_c); +} -- GitLab From 155e40ef26e337deb82914a7702b669c8d5a5c44 Mon Sep 17 00:00:00 2001 From: wuyi05 Date: Tue, 27 Jun 2017 20:16:53 +0800 Subject: [PATCH 0843/3256] using glide for go package vendor --- CMakeLists.txt | 4 +- cmake/generic.cmake | 72 +++++++++++++++++++++-------------- go/.gitignore | 1 + go/CMakeLists.txt | 30 +++++++++++++++ go/cmd/master/CMakeLists.txt | 15 ++++++++ go/cmd/pserver/CMakeLists.txt | 15 ++++++++ go/glide.lock | 61 +++++++++++++++++++++++++++++ go/glide.yaml | 12 ++++++ 8 files changed, 179 insertions(+), 31 deletions(-) create mode 100644 go/.gitignore create mode 100644 go/CMakeLists.txt create mode 100644 go/cmd/master/CMakeLists.txt create mode 100644 go/cmd/pserver/CMakeLists.txt create mode 100644 go/glide.lock create mode 100644 go/glide.yaml diff --git a/CMakeLists.txt b/CMakeLists.txt index 3c719d35ece..18e5ebeac24 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -48,6 +48,7 @@ option(COVERALLS_UPLOAD "Package code coverage data to coveralls" OFF) option(ON_TRAVIS "Exclude special unit test on Travis CI" OFF) option(WITH_C_API "Compile PaddlePaddle with C-API(Prediction)" OFF) option(WITH_GOLANG "Compile PaddlePaddle with GOLANG" OFF) +option(GLIDE_INSTALL "Download and install go dependencies " ON) # CMAKE_BUILD_TYPE if(NOT CMAKE_BUILD_TYPE) @@ -131,8 +132,7 @@ add_subdirectory(paddle) add_subdirectory(python) if(WITH_GOLANG) - #TODO (add go/master/c back when fixed) - add_subdirectory(go/pserver/cclient) + add_subdirectory(go) endif(WITH_GOLANG) if(WITH_DOC) diff --git a/cmake/generic.cmake b/cmake/generic.cmake index 11c1f677ae5..0d8bfa17d3a 100644 --- a/cmake/generic.cmake +++ b/cmake/generic.cmake @@ -17,7 +17,7 @@ # generic.cmake defines CMakes functions that look like Bazel's # building rules (https://bazel.build/). # -# +# # ------------------------------------------- # C++ CUDA C++ Go # ------------------------------------------- @@ -25,51 +25,51 @@ # cc_binary nv_binary go_binary # cc_test nv_test go_test # ------------------------------------------- -# +# # To build a static library example.a from example.cc using the system # compiler (like GCC): -# +# # cc_library(example SRCS example.cc) -# +# # To build a static library example.a from multiple source files # example{1,2,3}.cc: -# +# # cc_library(example SRCS example1.cc example2.cc example3.cc) -# +# # To build a shared library example.so from example.cc: -# +# # cc_library(example SHARED SRCS example.cc) -# +# # To build a library using Nvidia's NVCC from .cu file(s), use the nv_ # prefixed version: -# +# # nv_library(example SRCS example.cu) -# +# # To specify that a library new_example.a depends on other libraies: -# +# # cc_library(new_example SRCS new_example.cc DEPS example) -# +# # Static libraries can be composed of other static libraries: -# +# # cc_library(composed DEPS dependent1 dependent2 dependent3) -# +# # To build an executable binary file from some source files and # dependent libraries: -# +# # cc_binary(example SRCS main.cc something.cc DEPS example1 example2) -# +# # To build an executable binary file using NVCC, use the nv_ prefixed # version: -# +# # nv_binary(example SRCS main.cc something.cu DEPS example1 example2) -# +# # To build a unit test binary, which is an executable binary with # GoogleTest linked: -# +# # cc_test(example_test SRCS example_test.cc DEPS example) -# +# # To build a unit test binary using NVCC, use the nv_ prefixed version: -# +# # nv_test(example_test SRCS example_test.cu DEPS example) # # It is pretty often that executable and test binaries depend on @@ -256,6 +256,8 @@ endfunction(nv_test) set(GOPATH "${CMAKE_CURRENT_BINARY_DIR}/go") file(MAKE_DIRECTORY ${GOPATH}) set(PADDLE_IN_GOPATH "${GOPATH}/src/github.com/PaddlePaddle/Paddle") +file(MAKE_DIRECTORY "${PADDLE_IN_GOPATH}") +set(PADDLE_GO_SRC "${CMAKE_SOURCE_DIR}/go") function(go_library TARGET_NAME) set(options STATIC static SHARED shared) @@ -280,7 +282,7 @@ function(go_library TARGET_NAME) add_library(${TARGET_NAME} STATIC ${dummyfile}) endif() if(go_library_DEPS) - add_dependencies(${TARGET_NAME} ${go_library_DEPS}) + add_dependencies(${TARGET_NAME} ${go_library_DEPS} paddle_go_path_link) endif(go_library_DEPS) # we need to symlink Paddle directory into GOPATH. If we @@ -289,19 +291,23 @@ function(go_library TARGET_NAME) # without the changes in our current Paddle repo that we # want to build. file(GLOB GO_SOURCE RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "*.go") + string(REPLACE "${PADDLE_GO_SRC}/" "" CMAKE_CURRENT_SOURCE_REL_DIR ${CMAKE_CURRENT_SOURCE_DIR}) add_custom_command(TARGET ${TARGET_NAME} POST_BUILD COMMAND rm "${CMAKE_CURRENT_BINARY_DIR}/${LIB_NAME}" # Symlink Paddle directory into GOPATH COMMAND mkdir -p ${PADDLE_IN_GOPATH} - COMMAND rm -rf ${PADDLE_IN_GOPATH} + COMMAND rm -rf ${PADDLE_IN_GOPATH} COMMAND ln -sf ${CMAKE_SOURCE_DIR} ${PADDLE_IN_GOPATH} - # Automatically get all dependencies specified in the source code - COMMAND env GOPATH=${GOPATH} ${CMAKE_Go_COMPILER} get -d ./... + WORKING_DIRECTORY ${PADDLE_GO_SRC}) + add_custom_command(TARGET ${TARGET_NAME} POST_BUILD + # Automatically get all dependencies specified in the source code + #COMMAND env GOPATH=${GOPATH} ${CMAKE_Go_COMPILER} get -d ./... # Golang build source code COMMAND env GOPATH=${GOPATH} ${CMAKE_Go_COMPILER} build ${BUILD_MODE} -o "${CMAKE_CURRENT_BINARY_DIR}/${LIB_NAME}" - ${GO_SOURCE} - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) + "./${CMAKE_CURRENT_SOURCE_REL_DIR}/${GO_SOURCE}" + # must run under GOPATH + WORKING_DIRECTORY "${PADDLE_IN_GOPATH}/go") endfunction(go_library) function(go_binary TARGET_NAME) @@ -309,12 +315,20 @@ function(go_binary TARGET_NAME) set(oneValueArgs "") set(multiValueArgs SRCS DEPS) cmake_parse_arguments(go_binary "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + string(REPLACE "${PADDLE_GO_SRC}/" "" CMAKE_CURRENT_SOURCE_REL_DIR ${CMAKE_CURRENT_SOURCE_DIR}) + add_custom_command(OUTPUT ${TARGET_NAME}_link + # Symlink Paddle directory into GOPATH + COMMAND mkdir -p ${PADDLE_IN_GOPATH} + COMMAND rm -rf ${PADDLE_IN_GOPATH} + COMMAND ln -sf ${CMAKE_SOURCE_DIR} ${PADDLE_IN_GOPATH} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) + add_custom_command(OUTPUT ${TARGET_NAME}_timestamp COMMAND env GOPATH=${GOPATH} ${CMAKE_Go_COMPILER} build -o "${CMAKE_CURRENT_BINARY_DIR}/${TARGET_NAME}" ${go_library_SRCS} - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) - add_custom_target(${TARGET_NAME} ALL DEPENDS ${TARGET_NAME}_timestamp ${go_binary_DEPS}) + WORKING_DIRECTORY "${PADDLE_IN_GOPATH}/go/${CMAKE_CURRENT_SOURCE_REL_DIR}") + add_custom_target(${TARGET_NAME} ALL DEPENDS ${TARGET_NAME}_link ${TARGET_NAME}_timestamp ${go_binary_DEPS}) install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/${TARGET_NAME} DESTINATION bin) endfunction(go_binary) diff --git a/go/.gitignore b/go/.gitignore new file mode 100644 index 00000000000..48b8bf9072d --- /dev/null +++ b/go/.gitignore @@ -0,0 +1 @@ +vendor/ diff --git a/go/CMakeLists.txt b/go/CMakeLists.txt new file mode 100644 index 00000000000..fb7bd14b89e --- /dev/null +++ b/go/CMakeLists.txt @@ -0,0 +1,30 @@ +# 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. +# +# FIXME(typhoonzero): Download glide into cmake build temprary GOPATH +if(EXISTS $ENV{GOPATH}/bin/glide) + set(GLIDE "$ENV{GOPATH}/bin/glide") +else() + message(FATAL_ERROR "no glide executeble found: $ENV{GOPATH}/bin/glide") +endif() + +set(PADDLE_GO_PATH "${CMAKE_SOURCE_DIR}/go") + +if (GLIDE_INSTALL) + message(STATUS ${PADDLE_GO_PATH}) + execute_process(COMMAND ${GLIDE} install WORKING_DIRECTORY ${PADDLE_GO_PATH}) +endif() + +add_subdirectory(go/pserver/cclient) +#TODO (add go/master/c back when fixed) diff --git a/go/cmd/master/CMakeLists.txt b/go/cmd/master/CMakeLists.txt new file mode 100644 index 00000000000..a604272a087 --- /dev/null +++ b/go/cmd/master/CMakeLists.txt @@ -0,0 +1,15 @@ +# 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. + +go_binary(master) diff --git a/go/cmd/pserver/CMakeLists.txt b/go/cmd/pserver/CMakeLists.txt new file mode 100644 index 00000000000..ad7da915e70 --- /dev/null +++ b/go/cmd/pserver/CMakeLists.txt @@ -0,0 +1,15 @@ +# 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. + +go_binary(pserver) diff --git a/go/glide.lock b/go/glide.lock new file mode 100644 index 00000000000..190a222338b --- /dev/null +++ b/go/glide.lock @@ -0,0 +1,61 @@ +hash: b8f18ce6784bd3fadd9fed0b8443e7b658234ea785ae1f220723ae2c1f652aa7 +updated: 2017-06-27T14:05:48.925262819+08:00 +imports: +- name: github.com/coreos/etcd + version: 61fc123e7a8b14a0a258aa3f5c4159861b1ec2e7 + subpackages: + - auth/authpb + - clientv3 + - clientv3/concurrency + - etcdserver/api/v3rpc/rpctypes + - etcdserver/etcdserverpb + - mvcc/mvccpb +- name: github.com/golang/protobuf + version: 4bd1920723d7b7c925de087aa32e2187708897f7 + subpackages: + - jsonpb + - proto +- name: github.com/golang/snappy + version: 553a641470496b2327abcac10b36396bd98e45c9 +- name: github.com/namsral/flag + version: 71ceffbeb0ba60fccc853971bb3ed4d7d90bfd04 +- name: github.com/PaddlePaddle/recordio + version: edfb82af0739c84f241c87390ec5649c7b28c129 +- name: github.com/sirupsen/logrus + version: 202f25545ea4cf9b191ff7f846df5d87c9382c2b +- name: golang.org/x/net + version: c8c74377599bd978aee1cf3b9b63a8634051cec2 + subpackages: + - context + - http2 + - http2/hpack + - idna + - internal/timeseries + - lex/httplex + - trace +- name: golang.org/x/sys + version: f7928cfef4d09d1b080aa2b6fd3ca9ba1567c733 + subpackages: + - unix +- name: golang.org/x/text + version: 4e9ab9ee170f2a39bd66c92b3e0a47ff47a4bc77 + subpackages: + - secure/bidirule + - transform + - unicode/bidi + - unicode/norm +- name: google.golang.org/grpc + version: 8050b9cbc271307e5a716a9d782803d09b0d6f2d + subpackages: + - codes + - credentials + - grpclog + - internal + - keepalive + - metadata + - naming + - peer + - stats + - tap + - transport +testImports: [] diff --git a/go/glide.yaml b/go/glide.yaml new file mode 100644 index 00000000000..05c5d15ca22 --- /dev/null +++ b/go/glide.yaml @@ -0,0 +1,12 @@ +package: github.com/PaddlePaddle/Paddle/go +import: +- package: github.com/PaddlePaddle/recordio +- package: github.com/coreos/etcd + version: ^3.2.1 + subpackages: + - clientv3 + - clientv3/concurrency +- package: github.com/namsral/flag + version: ^1.7.4-pre +- package: github.com/sirupsen/logrus + version: ^1.0.0 -- GitLab From c5d9ca8617869e4d8bc12b9302c5594279ab93ad Mon Sep 17 00:00:00 2001 From: qijun Date: Tue, 27 Jun 2017 15:06:00 +0000 Subject: [PATCH 0844/3256] fix eigen include path --- cmake/external/eigen.cmake | 8 +++---- paddle/framework/ddim_test.cc | 44 ----------------------------------- 2 files changed, 4 insertions(+), 48 deletions(-) diff --git a/cmake/external/eigen.cmake b/cmake/external/eigen.cmake index 543504a274d..253d436bcc0 100644 --- a/cmake/external/eigen.cmake +++ b/cmake/external/eigen.cmake @@ -2,13 +2,13 @@ INCLUDE(ExternalProject) SET(EIGEN_SOURCE_DIR ${THIRD_PARTY_PATH}/eigen3) -INCLUDE_DIRECTORIES(${EIGEN_SOURCE_DIR}/src/) +INCLUDE_DIRECTORIES(${EIGEN_SOURCE_DIR}/src/eigen3) ExternalProject_Add( eigen3 ${EXTERNAL_PROJECT_LOG_ARGS} - URL "https://bitbucket.org/eigen/eigen/get/f3a22f35b044.tar.gz" - URL_MD5 "4645c66075982da6fa0bcf6b20f3e8f7" + URL "https://bitbucket.org/eigen/eigen/get/3.3.4.tar.gz" + URL_MD5 "1a47e78efe365a97de0c022d127607c3" PREFIX ${EIGEN_SOURCE_DIR} UPDATE_COMMAND "" CONFIGURE_COMMAND "" @@ -17,4 +17,4 @@ ExternalProject_Add( TEST_COMMAND "" ) -LIST(APPEND external_project_dependencies eigen3) \ No newline at end of file +LIST(APPEND external_project_dependencies eigen3) diff --git a/paddle/framework/ddim_test.cc b/paddle/framework/ddim_test.cc index 3e4ad840f31..36eef02370e 100644 --- a/paddle/framework/ddim_test.cc +++ b/paddle/framework/ddim_test.cc @@ -1,9 +1,6 @@ #include #include -#include "eigen3/Eigen/Core" -#include "eigen3/Eigen/Dense" -#include "eigen3/unsupported/Eigen/CXX11/Tensor" #include "gtest/gtest.h" #include "paddle/framework/ddim.h" @@ -64,44 +61,3 @@ TEST(DDim, Print) { ss << ddim; EXPECT_EQ("2, 3, 4", ss.str()); } - -template -using Vec = - Eigen::TensorMap, - Eigen::Aligned>; - -template -using Matrix = - Eigen::TensorMap, - Eigen::Aligned>; - -template -void print(T* input, int size) { - for (int i = 0; i < size; i++) { - std::cout << input[i] << " "; - } - std::cout << std::endl; -} - -TEST(Eigen, start) { - int size = 4; - - float* t_a = (float*)malloc(size * sizeof(float)); - float* t_b = (float*)malloc(size * sizeof(float)); - float* t_c = (float*)malloc(size * sizeof(float)); - for (int i = 0; i < size; i++) { - t_a[i] = i; - t_b[i] = i; - } - Vec a(t_a, size); - Vec b(t_b, size); - Vec c(t_c, size); - - Eigen::DefaultDevice dd; - c.device(dd) = a + b; - print(t_c, size); - - free(t_a); - free(t_b); - free(t_c); -} -- GitLab From 2f47562df8e9cbd81e96bba642646b0036e7bab6 Mon Sep 17 00:00:00 2001 From: qiaolongfei Date: Tue, 27 Jun 2017 23:39:59 +0800 Subject: [PATCH 0845/3256] scope-impl --- doc/design/scope.md | 2 +- paddle/framework/CMakeLists.txt | 5 +++ paddle/framework/scope.cc | 54 +++++++++++++++++++++++++++++++++ paddle/framework/scope.h | 51 +++++++++++++++++++++++++++++++ paddle/framework/scope_test.cc | 47 ++++++++++++++++++++++++++++ 5 files changed, 158 insertions(+), 1 deletion(-) create mode 100644 paddle/framework/scope.cc create mode 100644 paddle/framework/scope.h create mode 100644 paddle/framework/scope_test.cc diff --git a/doc/design/scope.md b/doc/design/scope.md index 2ff416f06e8..4d14a64977c 100644 --- a/doc/design/scope.md +++ b/doc/design/scope.md @@ -41,7 +41,7 @@ class Scope { const Variable* GetVariable(const std::string& name) const; private: - std::unordered_map> vars_; + std::unordered_map> vars_; }; ``` diff --git a/paddle/framework/CMakeLists.txt b/paddle/framework/CMakeLists.txt index e3c3155aa90..7ea17f7114c 100644 --- a/paddle/framework/CMakeLists.txt +++ b/paddle/framework/CMakeLists.txt @@ -1,6 +1,11 @@ +# ddim lib cc_library(ddim SRCS ddim.cc) cc_test(ddim_test SRCS ddim_test.cc DEPS ddim) nv_test(dim_test SRCS dim_test.cu DEPS ddim) cc_test(variable_test SRCS variable_test.cc) + +# scope lib +cc_library(scope SRCS scope.cc) +cc_test(scope_test SRCS scope_test.cc DEPS scope) diff --git a/paddle/framework/scope.cc b/paddle/framework/scope.cc new file mode 100644 index 00000000000..ed75aece015 --- /dev/null +++ b/paddle/framework/scope.cc @@ -0,0 +1,54 @@ +#include "paddle/framework/scope.h" + +namespace paddle { +namespace framework { + +Error Scope::CreateVariable(const std::string &name) { + if (name == "") { + return Error("Variable name should not be empty"); + } + + if (HaveVariable(name)) { + return AlreadyCreated; + } + vars_[name] = std::unique_ptr(new Variable()); + return Error(); +} + +Variable* Scope::GetVarLocally(const std::string& name) const { + if (vars_.count(name)) { + return vars_.at(name).get(); + } + return nullptr; +} + +Variable* Scope::GetVariable(const std::string &name) const { + Variable* var = GetVarLocally(name); + if (var != nullptr) { + return var; + } else if (parent_ != nullptr) { + return parent_->GetVariable(name); + } else { + return nullptr; + } +} + +Variable* Scope::GetOrCreateVariable(const std::string &name) { + Variable* var; + var = GetVariable(name); + if (var == nullptr) { + auto err = CreateVariable(name); + if (!err.isOK()) { + return nullptr; + } + } + return GetVariable(name); +} + +bool Scope::HaveVariable(const std::string &name) { + return vars_.count(name) != 0; +} + +} // namespace framework +} // namespace paddle + diff --git a/paddle/framework/scope.h b/paddle/framework/scope.h new file mode 100644 index 00000000000..ad1ed2ddab9 --- /dev/null +++ b/paddle/framework/scope.h @@ -0,0 +1,51 @@ +#pragma once + +#include +#include +#include "paddle/framework/variable.h" +#include "paddle/utils/Error.h" + +namespace paddle { +namespace framework { + +const static Error AlreadyCreated("Variable has already been created"); + +/** + * Scope is an association of a name to Variable. All variables belong to `Scope`. + * You need to specify a scope to run a Net, i.e., `net.Run(&scope)`. One net can + * run in different scopes and update different variable in the scope. + */ +class Scope { + public: + Scope() {} + + explicit Scope(const std::shared_ptr &scope): + parent_(scope) {} + + ~Scope() {} + + // Create Variable in this Scope. Return error if Variable already been + // created. + Error __must_check CreateVariable(const std::string& name); + + // Get Variable from this Scope, this function will recursive find Variable + // from it's parent scope. + // Return nullptr if not found. + Variable* GetVariable(const std::string& name) const; + + // find and return Variables in the scope it self. + Variable* GetVarLocally(const std::string& name) const; + + // Get a Variable from Scope, if the Variable is not exist then create it. + // User should call this function most of time. + Variable* GetOrCreateVariable(const std::string& name); + + bool HaveVariable(const std::string& name); + + private: + std::unordered_map> vars_; + std::shared_ptr parent_ {nullptr}; +}; + +} // namespace framework +} // namespace paddle diff --git a/paddle/framework/scope_test.cc b/paddle/framework/scope_test.cc new file mode 100644 index 00000000000..09fbb78d69f --- /dev/null +++ b/paddle/framework/scope_test.cc @@ -0,0 +1,47 @@ +#include "paddle/framework/scope.h" +#include "gtest/gtest.h" + +TEST(Scope, Create) { + using paddle::framework::Scope; + using paddle::Error; + using paddle::framework::Variable; + using paddle::framework::AlreadyCreated; + + Scope* scope = new Scope(); + + Error err = scope->CreateVariable(""); + EXPECT_FALSE(err.isOK()); + + Variable* var1 = scope->GetVariable("a"); + EXPECT_EQ(var1, nullptr); + + Error err1 = scope->CreateVariable("a"); + EXPECT_TRUE(err1.isOK()); + + Error err2 = scope->CreateVariable("a"); + EXPECT_EQ(err2, AlreadyCreated); + + Variable* var2 = scope->GetVariable("a"); + EXPECT_NE(var2, nullptr); + + Variable* var3 = scope->GetOrCreateVariable("b"); + EXPECT_NE(var3, nullptr); +} + +TEST(Scope, Parent) { + using paddle::framework::Scope; + using paddle::framework::Variable; + using paddle::Error; + + const auto parent_scope_ptr = std::shared_ptr(new Scope()); + Scope* scope = new Scope(parent_scope_ptr); + + Error err = parent_scope_ptr->CreateVariable("a"); + EXPECT_TRUE(err.isOK()); + + Variable* var1 = scope->GetVarLocally("a"); + EXPECT_EQ(var1, nullptr); + + Variable* var2 = scope->GetVariable("a"); + EXPECT_NE(var2, nullptr); +} \ No newline at end of file -- GitLab From 5e8d8e073bf0e208927c20103d3fc383f9e90316 Mon Sep 17 00:00:00 2001 From: qiaolongfei Date: Tue, 27 Jun 2017 23:53:37 +0800 Subject: [PATCH 0846/3256] refine GetOrCreateVariable --- paddle/framework/scope.cc | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/paddle/framework/scope.cc b/paddle/framework/scope.cc index ed75aece015..31956aecb96 100644 --- a/paddle/framework/scope.cc +++ b/paddle/framework/scope.cc @@ -34,15 +34,17 @@ Variable* Scope::GetVariable(const std::string &name) const { } Variable* Scope::GetOrCreateVariable(const std::string &name) { - Variable* var; - var = GetVariable(name); - if (var == nullptr) { - auto err = CreateVariable(name); - if (!err.isOK()) { - return nullptr; - } + Variable* var = GetVariable(name); + if (var != nullptr) { + return var; + } + + Error err = CreateVariable(name); + if (!err.isOK()) { + return nullptr; + } else { + return GetVariable(name); } - return GetVariable(name); } bool Scope::HaveVariable(const std::string &name) { -- GitLab From 7f03817806591ba508652263b2a83788d6baf397 Mon Sep 17 00:00:00 2001 From: dongzhihong Date: Wed, 28 Jun 2017 00:11:31 +0800 Subject: [PATCH 0847/3256] "cgo ldflags config" --- go/pserver/optimizer.go | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/go/pserver/optimizer.go b/go/pserver/optimizer.go index df0ea373bb8..5824a529fcb 100644 --- a/go/pserver/optimizer.go +++ b/go/pserver/optimizer.go @@ -1,13 +1,14 @@ package pserver -/* -// TODO(zhihong): move compile flags to cmake go_library -#cgo pkg-config: protobuf -#cgo CFLAGS: -I ../../ -#cgo LDFLAGS: /Users/dzh/.go/src/github.com/PaddlePaddle/Paddle/build/go/pserver/cclient/libpaddle_go_optimizer.a -lstdc++ -#include "paddle/optimizer/optimizer.h" -*/ +// #cgo pkg-config: protobuf +// #cgo CFLAGS: -I ../../ +// FIXME: ldflags contain "build" path +// #cgo LDFLAGS: ../../build/go/pserver/cclient/libpaddle_go_optimizer.a -lstdc++ +// #include "paddle/optimizer/optimizer.h" +// #include +// #include import "C" + import ( "fmt" "unsafe" -- GitLab From 7d138593d206f8bc16ddacb2a9aa072c08cb829d Mon Sep 17 00:00:00 2001 From: qiaolongfei Date: Wed, 28 Jun 2017 00:15:00 +0800 Subject: [PATCH 0848/3256] refine code of scope with style check --- paddle/framework/scope.cc | 9 ++++----- paddle/framework/scope.h | 17 ++++++++--------- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/paddle/framework/scope.cc b/paddle/framework/scope.cc index 31956aecb96..e9855988490 100644 --- a/paddle/framework/scope.cc +++ b/paddle/framework/scope.cc @@ -3,7 +3,7 @@ namespace paddle { namespace framework { -Error Scope::CreateVariable(const std::string &name) { +Error Scope::CreateVariable(const std::string& name) { if (name == "") { return Error("Variable name should not be empty"); } @@ -22,7 +22,7 @@ Variable* Scope::GetVarLocally(const std::string& name) const { return nullptr; } -Variable* Scope::GetVariable(const std::string &name) const { +Variable* Scope::GetVariable(const std::string& name) const { Variable* var = GetVarLocally(name); if (var != nullptr) { return var; @@ -33,7 +33,7 @@ Variable* Scope::GetVariable(const std::string &name) const { } } -Variable* Scope::GetOrCreateVariable(const std::string &name) { +Variable* Scope::GetOrCreateVariable(const std::string& name) { Variable* var = GetVariable(name); if (var != nullptr) { return var; @@ -47,10 +47,9 @@ Variable* Scope::GetOrCreateVariable(const std::string &name) { } } -bool Scope::HaveVariable(const std::string &name) { +bool Scope::HaveVariable(const std::string& name) { return vars_.count(name) != 0; } } // namespace framework } // namespace paddle - diff --git a/paddle/framework/scope.h b/paddle/framework/scope.h index ad1ed2ddab9..90c8141e4f4 100644 --- a/paddle/framework/scope.h +++ b/paddle/framework/scope.h @@ -1,7 +1,7 @@ #pragma once -#include #include +#include #include "paddle/framework/variable.h" #include "paddle/utils/Error.h" @@ -11,16 +11,16 @@ namespace framework { const static Error AlreadyCreated("Variable has already been created"); /** - * Scope is an association of a name to Variable. All variables belong to `Scope`. - * You need to specify a scope to run a Net, i.e., `net.Run(&scope)`. One net can - * run in different scopes and update different variable in the scope. + * Scope is an association of a name to Variable. All variables belong to + * `Scope`. You need to specify a scope to run a Net, i.e., `net.Run(&scope)`. + * One net can run in different scopes and update different variable in the + * scope. */ class Scope { public: Scope() {} - explicit Scope(const std::shared_ptr &scope): - parent_(scope) {} + explicit Scope(const std::shared_ptr& scope) : parent_(scope) {} ~Scope() {} @@ -29,8 +29,7 @@ class Scope { Error __must_check CreateVariable(const std::string& name); // Get Variable from this Scope, this function will recursive find Variable - // from it's parent scope. - // Return nullptr if not found. + // from it's parent scope. Return nullptr if not found. Variable* GetVariable(const std::string& name) const; // find and return Variables in the scope it self. @@ -44,7 +43,7 @@ class Scope { private: std::unordered_map> vars_; - std::shared_ptr parent_ {nullptr}; + std::shared_ptr parent_{nullptr}; }; } // namespace framework -- GitLab From 80d915049f62630bb902c0e62ffc4a3dffef49de Mon Sep 17 00:00:00 2001 From: dongzhihong Date: Wed, 28 Jun 2017 00:29:05 +0800 Subject: [PATCH 0849/3256] " add elementtype in optimizer" --- go/pserver/optimizer.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/go/pserver/optimizer.go b/go/pserver/optimizer.go index 5824a529fcb..4872139c566 100644 --- a/go/pserver/optimizer.go +++ b/go/pserver/optimizer.go @@ -2,7 +2,7 @@ package pserver // #cgo pkg-config: protobuf // #cgo CFLAGS: -I ../../ -// FIXME: ldflags contain "build" path +// //FIXME: ldflags contain "build" path // #cgo LDFLAGS: ../../build/go/pserver/cclient/libpaddle_go_optimizer.a -lstdc++ // #include "paddle/optimizer/optimizer.h" // #include @@ -37,6 +37,7 @@ func cArrayToSlice(p unsafe.Pointer, len int) []byte { func newOptimizer(paramWithConfigs ParameterWithConfig) *optimizer { o := &optimizer{} + o.ElementType = paramWithConfigs.Param.ElementType p := paramWithConfigs.Param c := paramWithConfigs.Config var cbuffer unsafe.Pointer -- GitLab From b49c9baa3501210b5fe859723174570381a6dd60 Mon Sep 17 00:00:00 2001 From: dongzhihong Date: Wed, 28 Jun 2017 00:32:34 +0800 Subject: [PATCH 0850/3256] "remove unuse comment" --- go/pserver/optimizer.go | 1 - 1 file changed, 1 deletion(-) diff --git a/go/pserver/optimizer.go b/go/pserver/optimizer.go index 4872139c566..5575fab3c0a 100644 --- a/go/pserver/optimizer.go +++ b/go/pserver/optimizer.go @@ -51,7 +51,6 @@ func newOptimizer(paramWithConfigs ParameterWithConfig) *optimizer { } func (o *optimizer) GetWeights() []byte { - // FIXME: get weigths from optimizer has bug var buffer unsafe.Pointer buffer_len := C.paddle_optimizer_get_weights(o.opt, &buffer) return cArrayToSlice(buffer, int(buffer_len)) -- GitLab From a2fabcc1144c480d0f972026194693b80ca6c397 Mon Sep 17 00:00:00 2001 From: dongzhihong Date: Wed, 28 Jun 2017 00:57:52 +0800 Subject: [PATCH 0851/3256] "fix client test" --- go/pserver/client_test.go | 9 +++++++-- go/pserver/optimizer.go | 1 + 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/go/pserver/client_test.go b/go/pserver/client_test.go index d0371a26a13..d746bf3f269 100644 --- a/go/pserver/client_test.go +++ b/go/pserver/client_test.go @@ -1,6 +1,7 @@ package pserver_test import ( + "io/ioutil" "net" "net/http" "net/rpc" @@ -71,18 +72,22 @@ func TestClientFull(t *testing.T) { } const numParameter = 100 + config, err := ioutil.ReadFile("./cclient/test/testdata/optimizer.pb.txt") + if err != nil { + t.Fatalf("read optimizer proto failed") + } for i := 0; i < numParameter; i++ { var p pserver.Parameter p.Name = "p_" + strconv.Itoa(i) p.ElementType = pserver.Float32 p.Content = make([]byte, (i+1)*100) - err := c.InitParam(pserver.ParameterWithConfig{Param: p}) + err := c.InitParam(pserver.ParameterWithConfig{Param: p, Config: config}) if err != nil { t.Fatal(err) } } - err := c.FinishInitParams() + err = c.FinishInitParams() if err != nil { t.Fatal(err) } diff --git a/go/pserver/optimizer.go b/go/pserver/optimizer.go index 5575fab3c0a..9cb2801f30d 100644 --- a/go/pserver/optimizer.go +++ b/go/pserver/optimizer.go @@ -61,6 +61,7 @@ func (o *optimizer) UpdateParameter(g Gradient) error { return fmt.Errorf("Name: %s, parameter and gradient element type not match, parameter: %v, gradient: %v", g.Name, o.ElementType, g.ElementType) } + fmt.Println(g) r := C.paddle_update_parameter(o.opt, C.paddle_element_type(g.ElementType), unsafe.Pointer(&g.Content[0]), C.int(len(g.Content))) if r != 0 { return fmt.Errorf("optimizer update returned error code: %d", r) -- GitLab From dd08d337c0138c9def5f7ce95f88bae5599e5f92 Mon Sep 17 00:00:00 2001 From: liaogang Date: Wed, 28 Jun 2017 01:30:57 +0800 Subject: [PATCH 0852/3256] FIX: fix cmake type error --- CMakeLists.txt | 2 ++ paddle/CMakeLists.txt | 10 +--------- paddle/memory/CMakeLists.txt | 2 +- 3 files changed, 4 insertions(+), 10 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3c719d35ece..b779caefb9a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,6 +27,7 @@ if(NOT CMAKE_CROSSCOMPILING) endif(NOT CMAKE_CROSSCOMPILING) find_package(Git REQUIRED) find_package(Threads REQUIRED) +find_package(Boost QUIET) include(simd) @@ -109,6 +110,7 @@ include_directories("${PROJ_ROOT}") include_directories("${PROJ_ROOT}/paddle/cuda/include") include_directories("${CMAKE_CURRENT_BINARY_DIR}/proto") include_directories("${CMAKE_CURRENT_BINARY_DIR}/go/pserver/cclient") +include_directories(${Boost_INCLUDE_DIRS}) set(EXTERNAL_LIBS ${GFLAGS_LIBRARIES} diff --git a/paddle/CMakeLists.txt b/paddle/CMakeLists.txt index 0cddb95244f..979b68e8272 100644 --- a/paddle/CMakeLists.txt +++ b/paddle/CMakeLists.txt @@ -10,17 +10,9 @@ add_subdirectory(trainer) add_subdirectory(scripts) add_subdirectory(optimizer) add_subdirectory(strings) -add_subdirectory(memory) - -# Do not build go directory until go cmake is working smoothly. -# if(CMAKE_Go_COMPILER) -# add_subdirectory(go) -# endif() - -find_package(Boost QUIET) if(Boost_FOUND) - include_directories(${Boost_INCLUDE_DIRS}) + add_subdirectory(memory) add_subdirectory(platform) add_subdirectory(framework) endif() diff --git a/paddle/memory/CMakeLists.txt b/paddle/memory/CMakeLists.txt index 86625124967..e74ce75c939 100644 --- a/paddle/memory/CMakeLists.txt +++ b/paddle/memory/CMakeLists.txt @@ -3,5 +3,5 @@ add_subdirectory(detail) if(${WITH_GPU}) nv_library(memory SRCS memory.cc) else(${WITH_GPU}) - cc_library(memory SRCS memroy.cc) + cc_library(memory SRCS memory.cc) endif(${WITH_GPU}) -- GitLab From dde0da9e0ffee7a49510061a139ab2abc7ab55b9 Mon Sep 17 00:00:00 2001 From: liaogang Date: Wed, 28 Jun 2017 01:31:24 +0800 Subject: [PATCH 0853/3256] ENH: Add cuda.h in platform --- paddle/platform/cuda.h | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 paddle/platform/cuda.h diff --git a/paddle/platform/cuda.h b/paddle/platform/cuda.h new file mode 100644 index 00000000000..864a5d3340a --- /dev/null +++ b/paddle/platform/cuda.h @@ -0,0 +1,41 @@ +/* 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 + +#ifndef PADDLE_ONLY_CPU + +#include +#include + +namespace paddle { +namespace platform { + +inline void throw_on_error(cudaError_t e, const char* message) { + if (e) { + throw thrust::system_error(e, thrust::cuda_category(), message); + } +} + +int GetDeviceCount(void) { + int count; + throw_on_error(cudaGetDeviceCount(&count), + "cudaGetDeviceCount failed"); + return count; +} + +} // namespace platform +} // namespace paddle + +#endif // PADDLE_ONLY_CPU -- GitLab From 29c7512b3ce13ca7b89d3ff3f4aea2c7d7f27478 Mon Sep 17 00:00:00 2001 From: liaogang Date: Wed, 28 Jun 2017 01:31:46 +0800 Subject: [PATCH 0854/3256] FIX: fix memory.h/cc --- paddle/memory/memory.cc | 23 ++++++++++++++--------- paddle/memory/memory.h | 8 ++++---- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/paddle/memory/memory.cc b/paddle/memory/memory.cc index ca3c01ebdb0..0d123d99e23 100644 --- a/paddle/memory/memory.cc +++ b/paddle/memory/memory.cc @@ -13,41 +13,46 @@ See the License for the specific language governing permissions and limitations under the License. */ #include "paddle/memory/memory.h" +#include "paddle/memory/detail/buddy_allocator.h" +#include "paddle/memory/detail/system_allocator.h" +#include "paddle/platform/assert.h" -#include "paddle/memory/detail/cpu_allocator.h" -#include "paddle/memory/detail/gpu_allocator.h" +#include namespace paddle { namespace memory { -void Alloc(paddle::platform::Place pl, size_t size) { +void* Alloc(platform::Place pl, size_t size) { #ifndef PADDLE_ONLY_CPU if (paddle::platform::is_gpu_place(pl)) { - return GetGPUBuddyAllocator(pl.device)->Alloc(size); + size_t gpu_id = boost::get(pl).device; + return detail::GetGPUBuddyAllocator(gpu_id)->Alloc(size); } #endif // PADDLE_ONLY_CPU PADDLE_ASSERT(paddle::platform::is_cpu_place(pl)); - return GetCPUBuddyAllocator()->Alloc(size); + return detail::GetCPUBuddyAllocator()->Alloc(size); } void Free(paddle::platform::Place pl, void* p) { #ifndef PADDLE_ONLY_CPU if (paddle::platform::is_gpu_place(pl)) { - GetGPUBuddyAllocator(pl.device)->Free(p); + size_t gpu_id = boost::get(pl).device; + detail::GetGPUBuddyAllocator(gpu_id)->Free(p); } #endif // PADDLE_ONLY_CPU PADDLE_ASSERT(paddle::platform::is_cpu_place(pl)); - GetCPUBuddyAllocator()->Free(p); + detail::GetCPUBuddyAllocator()->Free(p); } size_t Used(paddle::platform::Place pl) { #ifndef PADDLE_ONLY_CPU if (paddle::platform::is_gpu_place(pl)) { - return GetGPUBuddyAllocator(pl.device)->Used(); + size_t gpu_id = boost::get(pl).device; + return detail::GetGPUBuddyAllocator(gpu_id)->Used(); } #endif // PADDLE_ONLY_CPU PADDLE_ASSERT(paddle::platform::is_cpu_place(pl)); - return GetCPUBuddyAllocator()->Used(); + return detail::GetCPUBuddyAllocator()->Used(); } } // namespace memory diff --git a/paddle/memory/memory.h b/paddle/memory/memory.h index 0bc609205ec..a33092bade6 100644 --- a/paddle/memory/memory.h +++ b/paddle/memory/memory.h @@ -14,14 +14,14 @@ limitations under the License. */ #pragma once -#include "paddle/frameowork/place.h" +#include "paddle/platform/place.h" namespace paddle { namespace memory { -void* Alloc(paddle::framework::Place, size_t); -void Free(paddle::framework::Place, void*); -size_t Used(paddle::framework::Place); +void* Alloc(paddle::platform::Place, size_t); +void Free(paddle::platform::Place, void*); +size_t Used(paddle::platform::Place); } // namespace memory } // namespace paddle -- GitLab From b22dd12854150c31b9cb9e3e550bdee4b5df5977 Mon Sep 17 00:00:00 2001 From: liaogang Date: Wed, 28 Jun 2017 01:32:06 +0800 Subject: [PATCH 0855/3256] ENH: Add buddy allocator draft --- paddle/memory/detail/CMakeLists.txt | 4 +- paddle/memory/detail/buddy_allocator.h | 79 ++++++++++++++++++++++++++ 2 files changed, 81 insertions(+), 2 deletions(-) create mode 100644 paddle/memory/detail/buddy_allocator.h diff --git a/paddle/memory/detail/CMakeLists.txt b/paddle/memory/detail/CMakeLists.txt index c16dfadeb21..cd5622203ff 100644 --- a/paddle/memory/detail/CMakeLists.txt +++ b/paddle/memory/detail/CMakeLists.txt @@ -1,5 +1,5 @@ if(${WITH_GPU}) - nv_test(system_allocator_test SRCS system_allocator_test.cc) + nv_test(system_allocator_test SRCS system_allocator_test.cc DEPS gflags glog) else(${WITH_GPU}) - cc_test(system_allocator_test SRCS system_allocator_test.cc) + cc_test(system_allocator_test SRCS system_allocator_test.cc DEPS gflags glog) endif(${WITH_GPU}) diff --git a/paddle/memory/detail/buddy_allocator.h b/paddle/memory/detail/buddy_allocator.h new file mode 100644 index 00000000000..35e96fd5078 --- /dev/null +++ b/paddle/memory/detail/buddy_allocator.h @@ -0,0 +1,79 @@ +/* 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/memory/detail/system_allocator.h" + +namespace paddle { +namespace memory { +namespace detail { + +template +class BuddyAllocator { + public: + // TODO(gangliao): This is a draft, add Buddy Allocator Algorithm soon + BuddyAllocator() {} + ~BuddyAllocator() {} + + public: + void* Alloc(size_t size) { + return Allocator::Alloc(size); + } + void Free(void*) { + // Because all info like size are stored in meta data, + // thus it's duplicate if add the parameter `size` in + // `Free(void*)` interface. + } + size_t Used(); + + public: + BuddyAllocator(const BuddyAllocator&) = delete; + BuddyAllocator& operator=(const BuddyAllocator&) = delete; + + private: + size_t min_alloc_size_; + size_t max_alloc_size_; + + private: + std::mutex mutex_; +}; + +BuddyAllocator* GetCPUBuddyAllocator() { + static BuddyAllocator* a = nullptr; + if (a == nullptr) { + a = new BuddyAllocator(); + } + return a; +} + +#ifndef PADDLE_ONLY_CPU // The following code are for CUDA. + +BuddyAllocator* GetGPUBuddyAllocator(int gpu_id) { + static BuddyAllocator** as = NULL; + if (as == NULL) { + int gpu_num = platform::GetDeviceCount(); + as = new BuddyAllocator*[gpu_num]; + for (int gpu = 0; gpu < gpu_num; gpu++) { + as[gpu] = new BuddyAllocator(); + } + } + return as[gpu_id]; +} + +#endif // PADDLE_ONLY_CPU + +} // namespace detail +} // namespace memory +} // namespace paddle -- GitLab From 79373dabc8d2e4edc87fbef40efdfa1f54b35a9f Mon Sep 17 00:00:00 2001 From: liaogang Date: Wed, 28 Jun 2017 01:33:06 +0800 Subject: [PATCH 0856/3256] TEST: Add test for system allocator and deleter --- paddle/memory/detail/system_allocator.h | 108 ++++++------------ paddle/memory/detail/system_allocator_test.cc | 40 ++++--- 2 files changed, 60 insertions(+), 88 deletions(-) diff --git a/paddle/memory/detail/system_allocator.h b/paddle/memory/detail/system_allocator.h index 1768f9a0da6..f411019854e 100644 --- a/paddle/memory/detail/system_allocator.h +++ b/paddle/memory/detail/system_allocator.h @@ -18,107 +18,69 @@ limitations under the License. */ #include // for mlock and munlock #include // for malloc and free -#ifndef PADDLE_ONLY_CPU -#include -#include -#endif // PADDLE_ONLY_CPU - +#include #include "paddle/platform/assert.h" +#include "paddle/platform/cuda.h" + +DEFINE_bool(uses_pinned_memory, false, + "If set, allocate cpu/gpu pinned memory."); namespace paddle { namespace memory { namespace detail { -class CPUDeleter { - public: - CPUDeleter(void* ptr, size_t size, bool locked) - : ptr_(ptr), size_(size), locked_(locked) {} - - void* Ptr() { return ptr_; } - - void operator()(void* ptr) { - PADDLE_ASSERT(ptr == ptr_); - if (ptr_ != nullptr && locked_) { - munlock(ptr_, size_); - } - std::free(ptr_); - } - - private: - void* ptr_; - size_t size_; - bool locked_; -}; - -// CPUAllocator calls mlock, which returns pinned -// and locked memory as staging areas for data exchange between host -// and device. Allocates too much would reduce the amount of memory -// available to the system for paging. So, by default, we should use -// CPUAllocator. -template +// If uses_pinned_memory is true, CPUAllocator calls mlock, which +// returns pinned and locked memory as staging areas for data exchange +// between host and device. Allocates too much would reduce the amount +// of memory available to the system for paging. So, by default, we +// should set false to uses_pinned_memory. class CPUAllocator { public: - static CPUDeleter Alloc(size_t size) { + static void* Alloc(size_t size) { void* p = std::malloc(size); - if (p != nullptr && lock_memory) { + if (p != nullptr && FLAGS_uses_pinned_memory) { mlock(p, size); } - return CPUDeleter(p, size, lock_memory); + return p; } -}; - -#ifndef PADDLE_ONLY_CPU // The following code are for CUDA. - -namespace { -inline void throw_on_error(cudaError_t e, const char* message) { - if (e) { - throw thrust::system_error(e, thrust::cuda_category(), message); - } -} -} // namespace - -class GPUDeleter { - public: - GPUDeleter(void* ptr, size_t size, bool staging) - : ptr_(ptr), size_(size), staging_(staging) {} - - void* Ptr() { return ptr_; } - void operator()(void* ptr) { - PADDLE_ASSERT(ptr == ptr_); - // Purposefully allow cudaErrorCudartUnloading, because - // that is returned if you ever call cudaFree after the - // driver has already shutdown. This happens only if the - // process is terminating, in which case we don't care if - // cudaFree succeeds. - cudaError_t err = staging_ ? cudaFreeHost(ptr) : cudaFree(ptr); - if (err != cudaErrorCudartUnloading) { - throw_on_error(err, "cudaFree{Host} failed"); + static void Free(void* p, size_t size) { + if (p != nullptr && FLAGS_uses_pinned_memory) { + munlock(p, size); } + std::free(p); } - - private: - void* ptr_; - size_t size_; - bool staging_; }; +#ifndef PADDLE_ONLY_CPU // The following code are for CUDA. + // GPUAllocator calls cudaHostMalloc, which returns // pinned and locked memory as staging areas for data exchange // between host and device. Allocates too much would reduce the // amount of memory available to the system for paging. So, by // default, we should use GPUAllocator. -template class GPUAllocator { public: - static GPUDeleter Alloc(size_t size) { + static void* Alloc(size_t size) { void* p = 0; - cudaError_t result = - staging ? cudaMallocHost(&p, size) : cudaMalloc(&p, size); + cudaError_t result = FLAGS_uses_pinned_memory ? cudaMallocHost(&p, size) + : cudaMalloc(&p, size); if (result != cudaSuccess) { cudaGetLastError(); // clear error if there is any. } - return GPUDeleter(result == cudaSuccess ? p : nullptr, size, staging); + return result == cudaSuccess ? p : nullptr; + } + + static void Free(void* p, size_t size) { + // Purposefully allow cudaErrorCudartUnloading, because + // that is returned if you ever call cudaFree after the + // driver has already shutdown. This happens only if the + // process is terminating, in which case we don't care if + // cudaFree succeeds. + cudaError_t err = FLAGS_uses_pinned_memory ? cudaFreeHost(p) : cudaFree(p); + if (err != cudaErrorCudartUnloading) { + platform::throw_on_error(err, "cudaFree{Host} failed"); + } } }; diff --git a/paddle/memory/detail/system_allocator_test.cc b/paddle/memory/detail/system_allocator_test.cc index fec70a65b77..829d3558ba4 100644 --- a/paddle/memory/detail/system_allocator_test.cc +++ b/paddle/memory/detail/system_allocator_test.cc @@ -17,34 +17,44 @@ limitations under the License. */ #include #include +#include "glog/logging.h" #include "gtest/gtest.h" template -void TestAllocator() { - { - auto d = Allocator::Alloc(sizeof(int)); - EXPECT_NE(d.Ptr(), nullptr); - std::unique_ptr p(static_cast(d.Ptr()), d); - } - { - auto d = Allocator::Alloc(0); - EXPECT_EQ(d.Ptr(), nullptr); - std::unique_ptr p(static_cast(d.Ptr()), d); - } +void TestAllocator(void* p) { + p = Allocator::Alloc(1024); + + int* i = static_cast(p); + std::shared_ptr ptr(i, [](int* p) { Allocator::Free(p, 1024); }); + + EXPECT_NE(p, nullptr); } TEST(CPUAllocator, NoLockMem) { - TestAllocator>(); + void* p = nullptr; + FLAGS_uses_pinned_memory = false; + TestAllocator(p); + EXPECT_EQ(p, nullptr); } + TEST(CPUAllocator, LockMem) { - TestAllocator>(); + void* p = nullptr; + FLAGS_uses_pinned_memory = true; + TestAllocator(p); + EXPECT_EQ(p, nullptr); } #ifndef PADDLE_ONLY_CPU TEST(GPUAllocator, NoStaging) { - TestAllocator>(); + void* p = nullptr; + FLAGS_uses_pinned_memory = false; + TestAllocator(p); + EXPECT_EQ(p, nullptr); } TEST(GPUAllocator, Staging) { - TestAllocator>(); + void* p = nullptr; + FLAGS_uses_pinned_memory = true; + TestAllocator(p); + EXPECT_EQ(p, nullptr); } #endif // PADDLE_ONLY_CPU -- GitLab From 2c188a20de53741e6f965738636eb7d6f797a821 Mon Sep 17 00:00:00 2001 From: Yi Wang Date: Tue, 27 Jun 2017 12:00:41 -0700 Subject: [PATCH 0857/3256] Follow QingQing's suggestion --- paddle/framework/tensor.h | 31 ++++++++----------------------- 1 file changed, 8 insertions(+), 23 deletions(-) diff --git a/paddle/framework/tensor.h b/paddle/framework/tensor.h index a658537430e..8962b76a12c 100644 --- a/paddle/framework/tensor.h +++ b/paddle/framework/tensor.h @@ -20,23 +20,19 @@ class Tensor { using paddle::platform::get_place; public: - explicit Tensor(DDim dims) : dims_(dims), place_(get_place()) {} - explicit Tensor(DDim dims, Place place) : dims_(dims), place_(place) {} - template const T* data() const { - PADDLE_ASSERT(holder_ != nullptr); - PADDLE_ASSERT(holder_->Place() == place_); - PADDLE_ASSERT(holder_->Size() >= dims_.product() * sizeof(T)); + PADDLE_ASSERT(holder_ != nullptr, + "Tensor::data must be called after Tensor::mutable_data"); return static_cast(holder->Ptr()); } template ::value>::type> - T* mutable_data() { - if (holder_ == nullptr || holder_->Place() != place_ || - holder_->Size() < dims_.product() * sizeof(T)) { - holder_.reset(new PlaceholderImpl(place_, dims.product() * sizeof(T))); + T* mutable_data(DDim dims, Place place) { + if (holder_ == nullptr || holder_->Place() != place || + holder_->Size() < dims.product() * sizeof(T)) { + holder_.reset(new PlaceholderImpl(place, dims.product() * sizeof(T))); } return static_cast(holder_->Ptr()); } @@ -44,16 +40,7 @@ class Tensor { template ::value>::type> T* mutable_data(DDim dims) { - dims_ = dims; - return mutable_data(); - } - - template ::value>::type> - T* mutable_data(DDim dims, Place place) { - dims_ = dims; - place_ = place; - return mutable_data(); + return mutable_data(dims, paddle::platform::get_place()); } private: @@ -69,7 +56,7 @@ class Tensor { template struct PlaceholderImpl : public Placeholder { PlaceholderImpl(Place pl, size_t size) - : ptr_(memory::Alloc(pl, size), paddle::memory::Deleter(pl)), + : ptr_(paddle::memory::Alloc(pl, size), paddle::memory::Deleter(pl)), place_(pl), size_(size) {} @@ -83,8 +70,6 @@ class Tensor { }; std::unique_ptr holder_; // holds the memory block if allocated. - DDim dims_; // could be smallers than the holder_->Size(). - paddle::platform::Place place_; }; } // namespace framework -- GitLab From b8f5922d88e5f7949eb9a469f761ad49981d677a Mon Sep 17 00:00:00 2001 From: Yi Wang Date: Tue, 27 Jun 2017 16:32:24 -0700 Subject: [PATCH 0858/3256] Make CPUAllocator and GPUAllocator subclasses of SystemAllocator --- paddle/memory/detail/CMakeLists.txt | 6 +- paddle/memory/detail/system_allocator.h | 80 +++++-------------- paddle/memory/detail/system_allocator_test.cc | 57 +++++++------ 3 files changed, 59 insertions(+), 84 deletions(-) diff --git a/paddle/memory/detail/CMakeLists.txt b/paddle/memory/detail/CMakeLists.txt index cd5622203ff..72d3749ad78 100644 --- a/paddle/memory/detail/CMakeLists.txt +++ b/paddle/memory/detail/CMakeLists.txt @@ -1,5 +1,7 @@ if(${WITH_GPU}) - nv_test(system_allocator_test SRCS system_allocator_test.cc DEPS gflags glog) + nv_library(system_allocator SRCS system_allocator.cc DEPS gflags) + nv_test(system_allocator_test SRCS system_allocator_test.cc DEPS system_allocator gflags) else(${WITH_GPU}) - cc_test(system_allocator_test SRCS system_allocator_test.cc DEPS gflags glog) + cc_library(system_allocator SRCS system_allocator.cc DEPS gflags) + cc_test(system_allocator_test SRCS system_allocator_test.cc DEPS system_allocator gflags) endif(${WITH_GPU}) diff --git a/paddle/memory/detail/system_allocator.h b/paddle/memory/detail/system_allocator.h index f411019854e..184b383f7f7 100644 --- a/paddle/memory/detail/system_allocator.h +++ b/paddle/memory/detail/system_allocator.h @@ -14,76 +14,38 @@ limitations under the License. */ #pragma once -#include // for size_t -#include // for mlock and munlock -#include // for malloc and free - -#include -#include "paddle/platform/assert.h" -#include "paddle/platform/cuda.h" - -DEFINE_bool(uses_pinned_memory, false, - "If set, allocate cpu/gpu pinned memory."); +#include // for size_t namespace paddle { namespace memory { namespace detail { -// If uses_pinned_memory is true, CPUAllocator calls mlock, which -// returns pinned and locked memory as staging areas for data exchange -// between host and device. Allocates too much would reduce the amount -// of memory available to the system for paging. So, by default, we -// should set false to uses_pinned_memory. -class CPUAllocator { +// SystemAllocator is the parent class of CPUAllocator and +// GPUAllocator. A BuddyAllocator object uses a SystemAllocator* +// pointing to the underlying system allocator. An alternative to +// this class hierarchy is to pass a system allocator class to +// BuddyAllocator as a template parameter. This approach makes +// BuddyAllocator a class template, and it's very complicated +// algorithm would make the buddy_allocator.h messy. +class SystemAllocator { public: - static void* Alloc(size_t size) { - void* p = std::malloc(size); - if (p != nullptr && FLAGS_uses_pinned_memory) { - mlock(p, size); - } - return p; - } - - static void Free(void* p, size_t size) { - if (p != nullptr && FLAGS_uses_pinned_memory) { - munlock(p, size); - } - std::free(p); - } + virtual ~SystemAllocator() {} + virtual void* Alloc(size_t size) = 0; + virtual void Free(void* p, size_t size) = 0; }; -#ifndef PADDLE_ONLY_CPU // The following code are for CUDA. - -// GPUAllocator calls cudaHostMalloc, which returns -// pinned and locked memory as staging areas for data exchange -// between host and device. Allocates too much would reduce the -// amount of memory available to the system for paging. So, by -// default, we should use GPUAllocator. -class GPUAllocator { +class CPUAllocator : public SystemAllocator { public: - static void* Alloc(size_t size) { - void* p = 0; - cudaError_t result = FLAGS_uses_pinned_memory ? cudaMallocHost(&p, size) - : cudaMalloc(&p, size); - if (result != cudaSuccess) { - cudaGetLastError(); // clear error if there is any. - } - return result == cudaSuccess ? p : nullptr; - } - - static void Free(void* p, size_t size) { - // Purposefully allow cudaErrorCudartUnloading, because - // that is returned if you ever call cudaFree after the - // driver has already shutdown. This happens only if the - // process is terminating, in which case we don't care if - // cudaFree succeeds. - cudaError_t err = FLAGS_uses_pinned_memory ? cudaFreeHost(p) : cudaFree(p); - if (err != cudaErrorCudartUnloading) { - platform::throw_on_error(err, "cudaFree{Host} failed"); - } - } + virtual void* Alloc(size_t size); + virtual void Free(void* p, size_t size); }; +#ifndef PADDLE_ONLY_CPU +class GPUAllocator : public SystemAllocator { + public: + virtual void* Alloc(size_t size); + virtual void Free(void* p, size_t size); +}; #endif // PADDLE_ONLY_CPU } // namespace detail diff --git a/paddle/memory/detail/system_allocator_test.cc b/paddle/memory/detail/system_allocator_test.cc index 829d3558ba4..c461d8ac626 100644 --- a/paddle/memory/detail/system_allocator_test.cc +++ b/paddle/memory/detail/system_allocator_test.cc @@ -17,44 +17,55 @@ limitations under the License. */ #include #include -#include "glog/logging.h" +#include "gflags/gflags.h" #include "gtest/gtest.h" -template -void TestAllocator(void* p) { - p = Allocator::Alloc(1024); +DECLARE_bool(use_pinned_memory); - int* i = static_cast(p); - std::shared_ptr ptr(i, [](int* p) { Allocator::Free(p, 1024); }); +void TestAllocator(paddle::memory::detail::SystemAllocator* a, size_t size) { + bool freed = false; + { + void* p = a->Alloc(size); + if (size > 0) { + EXPECT_NE(p, nullptr); + } else { + EXPECT_EQ(p, nullptr); + } - EXPECT_NE(p, nullptr); + int* i = static_cast(p); + std::shared_ptr ptr(i, [&freed, a, size](void* p) { + freed = true; + a->Free(p, size); + }); + } + EXPECT_TRUE(freed); } TEST(CPUAllocator, NoLockMem) { - void* p = nullptr; - FLAGS_uses_pinned_memory = false; - TestAllocator(p); - EXPECT_EQ(p, nullptr); + FLAGS_use_pinned_memory = false; + paddle::memory::detail::CPUAllocator a; + TestAllocator(&a, 2048); + TestAllocator(&a, 0); } TEST(CPUAllocator, LockMem) { - void* p = nullptr; - FLAGS_uses_pinned_memory = true; - TestAllocator(p); - EXPECT_EQ(p, nullptr); + FLAGS_use_pinned_memory = true; + paddle::memory::detail::CPUAllocator a; + TestAllocator(&a, 2048); + TestAllocator(&a, 0); } #ifndef PADDLE_ONLY_CPU TEST(GPUAllocator, NoStaging) { - void* p = nullptr; - FLAGS_uses_pinned_memory = false; - TestAllocator(p); - EXPECT_EQ(p, nullptr); + FLAGS_use_pinned_memory = false; + paddle::memory::detail::GPUAllocator a; + TestAllocator(&a, 2048); + TestAllocator(&a, 0); } TEST(GPUAllocator, Staging) { - void* p = nullptr; - FLAGS_uses_pinned_memory = true; - TestAllocator(p); - EXPECT_EQ(p, nullptr); + FLAGS_use_pinned_memory = true; + paddle::memory::detail::GPUAllocator a; + TestAllocator(&a, 2048); + TestAllocator(&a, 0); } #endif // PADDLE_ONLY_CPU -- GitLab From 2f52cb7909c5e8f372015454e3af33166713bfa7 Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Tue, 27 Jun 2017 23:46:54 +0000 Subject: [PATCH 0859/3256] fix pserver test --- go/pserver/client_test.go | 2 +- go/pserver/service_test.go | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/go/pserver/client_test.go b/go/pserver/client_test.go index 6ecf1fa08a0..4a62ae88a4a 100644 --- a/go/pserver/client_test.go +++ b/go/pserver/client_test.go @@ -31,7 +31,7 @@ func init() { port[i] = p go func(l net.Listener) { - s, err := pserver.NewService("", time.Second*5) + s, err := pserver.NewService("", 1, time.Second*5) if err != nil { panic(err) } diff --git a/go/pserver/service_test.go b/go/pserver/service_test.go index f3175355921..1d84f15d78a 100644 --- a/go/pserver/service_test.go +++ b/go/pserver/service_test.go @@ -10,7 +10,7 @@ import ( ) func TestFull(t *testing.T) { - s, err := pserver.NewService("", time.Second*5) + s, err := pserver.NewService("", 1, time.Second*5) if err != nil { t.Error(err) } @@ -75,7 +75,7 @@ func TestFull(t *testing.T) { } func TestMultipleInit(t *testing.T) { - s, err := pserver.NewService("", time.Second*5) + s, err := pserver.NewService("", 1, time.Second*5) if err != nil { t.Error(err) } @@ -91,7 +91,7 @@ func TestMultipleInit(t *testing.T) { } func TestUninitialized(t *testing.T) { - s, err := pserver.NewService("", time.Second*5) + s, err := pserver.NewService("", 1, time.Second*5) err = s.SendGrad(pserver.Gradient{}, nil) if err.Error() != pserver.Uninitialized { t.FailNow() @@ -99,7 +99,7 @@ func TestUninitialized(t *testing.T) { } func TestBlockUntilInitialized(t *testing.T) { - s, err := pserver.NewService("", time.Second*5) + s, err := pserver.NewService("", 1, time.Second*5) if err != nil { t.Error(err) } -- GitLab From 3e087f763e9c6c15a4f1d542fb3bdc327f7441c7 Mon Sep 17 00:00:00 2001 From: Yi Wang Date: Tue, 27 Jun 2017 16:48:25 -0700 Subject: [PATCH 0860/3256] Add buddy_allocator.cc and system_allocator.cc --- paddle/memory/detail/buddy_allocator.cc | 35 ++++++++ paddle/memory/detail/buddy_allocator.h | 76 ++++++++-------- paddle/memory/detail/system_allocator.cc | 90 +++++++++++++++++++ paddle/memory/detail/system_allocator_test.cc | 24 ++--- 4 files changed, 177 insertions(+), 48 deletions(-) create mode 100644 paddle/memory/detail/buddy_allocator.cc create mode 100644 paddle/memory/detail/system_allocator.cc diff --git a/paddle/memory/detail/buddy_allocator.cc b/paddle/memory/detail/buddy_allocator.cc new file mode 100644 index 00000000000..895bf319d77 --- /dev/null +++ b/paddle/memory/detail/buddy_allocator.cc @@ -0,0 +1,35 @@ +/* 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/memory/detail/system_allocator.h" + +namespace paddle { +namespace memory { +namespace detail { + +BuddyAllocator::BuddyAllocator(size_t pool_size, size_t max_pools, + SystemAllocator* system_allocator) + : pool_size_(pool_size), + max_pools_(max_pools), + system_allocator_(system_allocator) { + PADDLE_ASSERT(pool_size > 0); + PADDLE_ASSERT(max_pools > 0); + PADDLE_ASSERT(system_allocator != nullptr); +} + +} // namespace detail +} // namespace memory +} // namespace paddle diff --git a/paddle/memory/detail/buddy_allocator.h b/paddle/memory/detail/buddy_allocator.h index 35e96fd5078..129b137ed73 100644 --- a/paddle/memory/detail/buddy_allocator.h +++ b/paddle/memory/detail/buddy_allocator.h @@ -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. */ #pragma once @@ -20,34 +20,38 @@ namespace paddle { namespace memory { namespace detail { -template class BuddyAllocator { - public: - // TODO(gangliao): This is a draft, add Buddy Allocator Algorithm soon - BuddyAllocator() {} - ~BuddyAllocator() {} - - public: - void* Alloc(size_t size) { - return Allocator::Alloc(size); - } - void Free(void*) { - // Because all info like size are stored in meta data, - // thus it's duplicate if add the parameter `size` in - // `Free(void*)` interface. - } - size_t Used(); + public: + BuddyAllocator(size_t pool_size, size_t max_pools, + SystemAllocator* system_allocator); + ~BuddyAllocator(); + + void* Alloc(size_t size); + void Free(void*); + size_t Used(); + + private: + struct Block { + size_t size_; + Block* left_; // left buddy + Block* right_; // right buddy + }; + + // Initially, there is only one pool. If a Alloc founds not enough + // memory from that pool, and there has not been max_num_pools_, + // create a new pool by calling system_allocator_.Alloc(pool_size_). + std::vector pools_; + + size_t pool_size_; // the size of each pool; + size_t max_num_pools_; // the size of all pools; - public: - BuddyAllocator(const BuddyAllocator&) = delete; - BuddyAllocator& operator=(const BuddyAllocator&) = delete; + SystemAllocator* system_allocator_; - private: - size_t min_alloc_size_; - size_t max_alloc_size_; + std::mutex mutex_; - private: - std::mutex mutex_; + // Disable copy and assignment. + BuddyAllocator(const BuddyAllocator&) = delete; + BuddyAllocator& operator=(const BuddyAllocator&) = delete; }; BuddyAllocator* GetCPUBuddyAllocator() { @@ -63,16 +67,16 @@ BuddyAllocator* GetCPUBuddyAllocator() { BuddyAllocator* GetGPUBuddyAllocator(int gpu_id) { static BuddyAllocator** as = NULL; if (as == NULL) { - int gpu_num = platform::GetDeviceCount(); + int gpu_num = platform::GetDeviceCount(); as = new BuddyAllocator*[gpu_num]; for (int gpu = 0; gpu < gpu_num; gpu++) { - as[gpu] = new BuddyAllocator(); + as[gpu] = new BuddyAllocator(); } } return as[gpu_id]; } -#endif // PADDLE_ONLY_CPU +#endif // PADDLE_ONLY_CPU } // namespace detail } // namespace memory diff --git a/paddle/memory/detail/system_allocator.cc b/paddle/memory/detail/system_allocator.cc new file mode 100644 index 00000000000..50bec926f83 --- /dev/null +++ b/paddle/memory/detail/system_allocator.cc @@ -0,0 +1,90 @@ +/* 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/memory/detail/system_allocator.h" + +#include // for malloc and free +#include // for mlock and munlock + +#include "gflags/gflags.h" +#include "paddle/platform/assert.h" +#include "paddle/platform/cuda.h" + +// If use_pinned_memory is true, CPUAllocator calls mlock, which +// returns pinned and locked memory as staging areas for data exchange +// between host and device. Allocates too much would reduce the amount +// of memory available to the system for paging. So, by default, we +// should set false to use_pinned_memory. +DEFINE_bool(use_pinned_memory, false, + "If set, allocate cpu/gpu pinned memory."); + +namespace paddle { +namespace memory { +namespace detail { + +void* CPUAllocator::Alloc(size_t size) { + // According to http://www.cplusplus.com/reference/cstdlib/malloc/, + // malloc might not return nullptr if size is zero, but the returned + // pointer shall not be dereferenced -- so we make it nullptr. + if (size <= 0) return nullptr; + + void* p = malloc(size); + if (p != nullptr && FLAGS_use_pinned_memory) { + mlock(p, size); + } + return p; +} + +void CPUAllocator::Free(void* p, size_t size) { + if (p != nullptr && FLAGS_use_pinned_memory) { + munlock(p, size); + } + free(p); +} + +#ifndef PADDLE_ONLY_CPU + +void* GPUAllocator::Alloc(size_t size) { + // CUDA documentation doesn't explain if cudaMalloc returns nullptr + // if size is 0. We just make sure it does. + if (size <= 0) { + return nullptr; + } + + void* p = 0; + cudaError_t result = + FLAGS_use_pinned_memory ? cudaMallocHost(&p, size) : cudaMalloc(&p, size); + if (result != cudaSuccess) { + cudaGetLastError(); // clear error if there is any. + } + return result == cudaSuccess ? p : nullptr; +} + +void GPUAllocator::Free(void* p, size_t size) { + // Purposefully allow cudaErrorCudartUnloading, because + // that is returned if you ever call cudaFree after the + // driver has already shutdown. This happens only if the + // process is terminating, in which case we don't care if + // cudaFree succeeds. + cudaError_t err = FLAGS_use_pinned_memory ? cudaFreeHost(p) : cudaFree(p); + if (err != cudaErrorCudartUnloading) { + platform::throw_on_error(err, "cudaFree{Host} failed"); + } +} + +#endif // PADDLE_ONLY_CPU + +} // namespace detail +} // namespace memory +} // namespace paddle diff --git a/paddle/memory/detail/system_allocator_test.cc b/paddle/memory/detail/system_allocator_test.cc index c461d8ac626..9bd5706a4e4 100644 --- a/paddle/memory/detail/system_allocator_test.cc +++ b/paddle/memory/detail/system_allocator_test.cc @@ -22,10 +22,10 @@ limitations under the License. */ DECLARE_bool(use_pinned_memory); -void TestAllocator(paddle::memory::detail::SystemAllocator* a, size_t size) { +void TestAllocator(paddle::memory::detail::SystemAllocator& a, size_t size) { bool freed = false; { - void* p = a->Alloc(size); + void* p = a.Alloc(size); if (size > 0) { EXPECT_NE(p, nullptr); } else { @@ -33,9 +33,9 @@ void TestAllocator(paddle::memory::detail::SystemAllocator* a, size_t size) { } int* i = static_cast(p); - std::shared_ptr ptr(i, [&freed, a, size](void* p) { + std::shared_ptr ptr(i, [&](void* p) { freed = true; - a->Free(p, size); + a.Free(p, size); }); } EXPECT_TRUE(freed); @@ -44,28 +44,28 @@ void TestAllocator(paddle::memory::detail::SystemAllocator* a, size_t size) { TEST(CPUAllocator, NoLockMem) { FLAGS_use_pinned_memory = false; paddle::memory::detail::CPUAllocator a; - TestAllocator(&a, 2048); - TestAllocator(&a, 0); + TestAllocator(a, 2048); + TestAllocator(a, 0); } TEST(CPUAllocator, LockMem) { FLAGS_use_pinned_memory = true; paddle::memory::detail::CPUAllocator a; - TestAllocator(&a, 2048); - TestAllocator(&a, 0); + TestAllocator(a, 2048); + TestAllocator(a, 0); } #ifndef PADDLE_ONLY_CPU TEST(GPUAllocator, NoStaging) { FLAGS_use_pinned_memory = false; paddle::memory::detail::GPUAllocator a; - TestAllocator(&a, 2048); - TestAllocator(&a, 0); + TestAllocator(a, 2048); + TestAllocator(a, 0); } TEST(GPUAllocator, Staging) { FLAGS_use_pinned_memory = true; paddle::memory::detail::GPUAllocator a; - TestAllocator(&a, 2048); - TestAllocator(&a, 0); + TestAllocator(a, 2048); + TestAllocator(a, 0); } #endif // PADDLE_ONLY_CPU -- GitLab From 80642bee00c3f723d213b0475749aeee60d89795 Mon Sep 17 00:00:00 2001 From: wanghaoshuang Date: Wed, 28 Jun 2017 08:22:58 +0800 Subject: [PATCH 0861/3256] fix_xmap and refine flowers dataset --- python/paddle/v2/dataset/__init__.py | 3 +- python/paddle/v2/dataset/flowers.py | 67 ++++++++++--------- .../paddle/v2/dataset/tests/flowers_test.py | 4 +- python/paddle/v2/reader/decorator.py | 47 +++++++------ .../paddle/v2/reader/tests/decorator_test.py | 18 ++--- 5 files changed, 72 insertions(+), 67 deletions(-) diff --git a/python/paddle/v2/dataset/__init__.py b/python/paddle/v2/dataset/__init__.py index 26252d5bbd7..2e4beb68827 100644 --- a/python/paddle/v2/dataset/__init__.py +++ b/python/paddle/v2/dataset/__init__.py @@ -25,8 +25,9 @@ import uci_housing import sentiment import wmt14 import mq2007 +import flowers __all__ = [ 'mnist', 'imikolov', 'imdb', 'cifar', 'movielens', 'conll05', 'sentiment' - 'uci_housing', 'wmt14', 'mq2007' + 'uci_housing', 'wmt14', 'mq2007', 'flowers' ] diff --git a/python/paddle/v2/dataset/flowers.py b/python/paddle/v2/dataset/flowers.py index 07c13cf719a..a181f3881a6 100644 --- a/python/paddle/v2/dataset/flowers.py +++ b/python/paddle/v2/dataset/flowers.py @@ -13,18 +13,18 @@ # limitations under the License. """ This module will download dataset from -http://www.robots.ox.ac.uk/~vgg/data/flowers/102/index.html +http://www.robots.ox.ac.uk/~vgg/data/flowers/102/index.html and parse train/test set intopaddle reader creators. -This set contains images of flowers belonging to 102 different categories. +This set contains images of flowers belonging to 102 different categories. The images were acquired by searching the web and taking pictures. There are a minimum of 40 images for each category. The database was used in: Nilsback, M-E. and Zisserman, A. Automated flower classification over a large - number of classes.Proceedings of the Indian Conference on Computer Vision, -Graphics and Image Processing (2008) + number of classes.Proceedings of the Indian Conference on Computer Vision, +Graphics and Image Processing (2008) http://www.robots.ox.ac.uk/~vgg/publications/papers/nilsback08.{pdf,ps.gz}. """ @@ -34,9 +34,9 @@ from common import download import tarfile import scipy.io as scio from paddle.v2.image import * +from paddle.v2.reader import * import os import numpy as np -import paddle.v2 as paddle from multiprocessing import cpu_count __all__ = ['train', 'test', 'valid'] @@ -53,8 +53,8 @@ def default_mapper(sample): map image bytes data to type needed by model input layer ''' img, label = sample - img = paddle.image.load_image_bytes(img) - img = paddle.image.simple_transform(img, 256, 224, True) + img = load_image_bytes(img) + img = simple_transform(img, 256, 224, True) return img.flatten().astype('float32'), label @@ -63,22 +63,23 @@ def reader_creator(data_file, setid_file, dataset_name, mapper=default_mapper, - buffered_size=1024): + buffered_size=1024, + useXmap=True): ''' - 1. read images from tar file and + 1. read images from tar file and merge images into batch files in 102flowers.tgz_batch/ 2. get a reader to read sample from batch file - - :param data_file: downloaded data file + + :param data_file: downloaded data file :type data_file: string - :param label_file: downloaded label file + :param label_file: downloaded label file :type label_file: string :param setid_file: downloaded setid file containing information about how to split dataset :type setid_file: string :param dataset_name: data set name (tstid|trnid|valid) :type dataset_name: string - :param mapper: a function to map image bytes data to type + :param mapper: a function to map image bytes data to type needed by model input layer :type mapper: callable :param buffered_size: the size of buffer used to process images @@ -105,15 +106,17 @@ def reader_creator(data_file, for sample, label in itertools.izip(data, batch['label']): yield sample, int(label) - return paddle.reader.xmap_readers(mapper, reader, - cpu_count(), buffered_size) + if useXmap: + return xmap_readers(mapper, reader, cpu_count(), buffered_size) + else: + return map_readers(mapper, reader) -def train(mapper=default_mapper, buffered_size=1024): +def train(mapper=default_mapper, buffered_size=1024, useXmap=True): ''' - Create flowers training set reader. - It returns a reader, each sample in the reader is - image pixels in [0, 1] and label in [1, 102] + Create flowers training set reader. + It returns a reader, each sample in the reader is + image pixels in [0, 1] and label in [1, 102] translated from original color image by steps: 1. resize to 256*256 2. random crop to 224*224 @@ -128,15 +131,15 @@ def train(mapper=default_mapper, buffered_size=1024): return reader_creator( download(DATA_URL, 'flowers', DATA_MD5), download(LABEL_URL, 'flowers', LABEL_MD5), - download(SETID_URL, 'flowers', SETID_MD5), 'trnid', mapper, - buffered_size) + download(SETID_URL, 'flowers', SETID_MD5), 'tstid', mapper, + buffered_size, useXmap) -def test(mapper=default_mapper, buffered_size=1024): +def test(mapper=default_mapper, buffered_size=1024, useXmap=True): ''' - Create flowers test set reader. - It returns a reader, each sample in the reader is - image pixels in [0, 1] and label in [1, 102] + Create flowers test set reader. + It returns a reader, each sample in the reader is + image pixels in [0, 1] and label in [1, 102] translated from original color image by steps: 1. resize to 256*256 2. random crop to 224*224 @@ -151,15 +154,15 @@ def test(mapper=default_mapper, buffered_size=1024): return reader_creator( download(DATA_URL, 'flowers', DATA_MD5), download(LABEL_URL, 'flowers', LABEL_MD5), - download(SETID_URL, 'flowers', SETID_MD5), 'tstid', mapper, - buffered_size) + download(SETID_URL, 'flowers', SETID_MD5), 'trnid', mapper, + buffered_size, useXmap) -def valid(mapper=default_mapper, buffered_size=1024): +def valid(mapper=default_mapper, buffered_size=1024, useXmap=True): ''' - Create flowers validation set reader. - It returns a reader, each sample in the reader is - image pixels in [0, 1] and label in [1, 102] + Create flowers validation set reader. + It returns a reader, each sample in the reader is + image pixels in [0, 1] and label in [1, 102] translated from original color image by steps: 1. resize to 256*256 2. random crop to 224*224 @@ -175,7 +178,7 @@ def valid(mapper=default_mapper, buffered_size=1024): download(DATA_URL, 'flowers', DATA_MD5), download(LABEL_URL, 'flowers', LABEL_MD5), download(SETID_URL, 'flowers', SETID_MD5), 'valid', mapper, - buffered_size) + buffered_size, useXmap) def fetch(): diff --git a/python/paddle/v2/dataset/tests/flowers_test.py b/python/paddle/v2/dataset/tests/flowers_test.py index cc0626f4fea..a8ae9a07acc 100644 --- a/python/paddle/v2/dataset/tests/flowers_test.py +++ b/python/paddle/v2/dataset/tests/flowers_test.py @@ -31,13 +31,13 @@ class TestFlowers(unittest.TestCase): def test_train(self): instances, max_label_value = self.check_reader( paddle.v2.dataset.flowers.train()) - self.assertEqual(instances, 1020) + self.assertEqual(instances, 6149) self.assertEqual(max_label_value, 102) def test_test(self): instances, max_label_value = self.check_reader( paddle.v2.dataset.flowers.test()) - self.assertEqual(instances, 6149) + self.assertEqual(instances, 1020) self.assertEqual(max_label_value, 102) def test_valid(self): diff --git a/python/paddle/v2/reader/decorator.py b/python/paddle/v2/reader/decorator.py index e432003129d..45a4288751e 100644 --- a/python/paddle/v2/reader/decorator.py +++ b/python/paddle/v2/reader/decorator.py @@ -166,12 +166,12 @@ def buffered(reader, size): The buffered data reader will read and save data entries into a buffer. Reading from the buffered data reader will proceed as long as the buffer is not empty. - + :param reader: the data reader to read from. :type reader: callable :param size: max buffer size. :type size: int - + :returns: the buffered data reader. """ @@ -238,7 +238,7 @@ def xmap_readers(mapper, reader, process_num, buffer_size, order=False): :type mapper: callable :param reader: the data reader to read from :type reader: callable - :param process_num: process number to handle original sample + :param process_num: process number to handle original sample :type process_num: int :param buffer_size: max buffer size :type buffer_size: int @@ -248,9 +248,6 @@ def xmap_readers(mapper, reader, process_num, buffer_size, order=False): :rtype: callable """ end = XmapEndSignal() - in_queue = Queue(buffer_size) - out_queue = Queue(buffer_size) - out_order = [0] # define a worker to read samples from reader to in_queue def read_worker(reader, in_queue): @@ -266,12 +263,6 @@ def xmap_readers(mapper, reader, process_num, buffer_size, order=False): in_order += 1 in_queue.put(end) - # start a read worker in a thread - target = order_read_worker if order else read_worker - t = Thread(target=target, args=(reader, in_queue)) - t.daemon = True - t.start() - # define a worker to handle samples from in_queue by mapper # and put mapped samples into out_queue def handle_worker(in_queue, out_queue, mapper): @@ -298,19 +289,27 @@ def xmap_readers(mapper, reader, process_num, buffer_size, order=False): in_queue.put(end) out_queue.put(end) - # start several handle_workers - target = order_handle_worker if order else handle_worker - args = (in_queue, out_queue, mapper, out_order) if order else ( - in_queue, out_queue, mapper) - workers = [] - for i in xrange(process_num): - worker = Thread(target=target, args=args) - worker.daemon = True - workers.append(worker) - for w in workers: - w.start() - def xreader(): + in_queue = Queue(buffer_size) + out_queue = Queue(buffer_size) + out_order = [0] + # start a read worker in a thread + target = order_read_worker if order else read_worker + t = Thread(target=target, args=(reader, in_queue)) + t.daemon = True + t.start() + # start several handle_workers + target = order_handle_worker if order else handle_worker + args = (in_queue, out_queue, mapper, out_order) if order else ( + in_queue, out_queue, mapper) + workers = [] + for i in xrange(process_num): + worker = Thread(target=target, args=args) + worker.daemon = True + workers.append(worker) + for w in workers: + w.start() + sample = out_queue.get() while not isinstance(sample, XmapEndSignal): yield sample diff --git a/python/paddle/v2/reader/tests/decorator_test.py b/python/paddle/v2/reader/tests/decorator_test.py index bb3c5d220b9..5a92951b100 100644 --- a/python/paddle/v2/reader/tests/decorator_test.py +++ b/python/paddle/v2/reader/tests/decorator_test.py @@ -132,15 +132,17 @@ class TestXmap(unittest.TestCase): for order in orders: for tNum in thread_nums: for size in buffered_size: - result = [] - for i in paddle.v2.reader.xmap_readers(mapper, + reader = paddle.v2.reader.xmap_readers(mapper, reader_creator_10(0), - tNum, size, order)(): - result.append(i) - if not order: - result.sort() - for idx, e in enumerate(result): - self.assertEqual(e, mapper(idx)) + tNum, size, order) + for n in xrange(3): + result = [] + for i in reader(): + result.append(i) + if not order: + result.sort() + for idx, e in enumerate(result): + self.assertEqual(e, mapper(idx)) if __name__ == '__main__': -- GitLab From c263c21f7e0feebca20ab33cd606330de81e9aee Mon Sep 17 00:00:00 2001 From: Yi Wang Date: Tue, 27 Jun 2017 17:35:27 -0700 Subject: [PATCH 0862/3256] Update copyright informaiton --- paddle/framework/tensor.h | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/paddle/framework/tensor.h b/paddle/framework/tensor.h index 8962b76a12c..067f2a85264 100644 --- a/paddle/framework/tensor.h +++ b/paddle/framework/tensor.h @@ -1,15 +1,17 @@ -/* - 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. -*/ +/* 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 namespace paddle { -- GitLab From 4cc9680cc60296f6071fa34893fda4f3d6806b97 Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Wed, 28 Jun 2017 01:16:28 +0000 Subject: [PATCH 0863/3256] Make pserver able to get server index without etcd (decouple pserver with etcd) The pserver need to have server index for saving model on the distributed file system. The server index comes from etcd if etcd is used, or user can manually specify them. So we need pserver.NewService() to take index as an argument. Since index could come from etcd, it would be strange if pserver takes an index as argument, at the same time get the index from etcd. so we will need to decouple pserver with etcd. --- go/cmd/pserver/pserver.go | 8 +- go/master/etcd_client.go | 4 +- go/pserver/client_test.go | 3 +- go/pserver/etcd_client.go | 181 +++++++++++++++++++++++++++++++++++++ go/pserver/service.go | 156 ++------------------------------ go/pserver/service_test.go | 8 +- 6 files changed, 201 insertions(+), 159 deletions(-) create mode 100644 go/pserver/etcd_client.go diff --git a/go/cmd/pserver/pserver.go b/go/cmd/pserver/pserver.go index 6c85b1804bb..8a42d4f8af1 100644 --- a/go/cmd/pserver/pserver.go +++ b/go/cmd/pserver/pserver.go @@ -30,7 +30,13 @@ func main() { log.SetLevel(level) timeout := time.Second * time.Duration((*etcdTimeout)) - s, err := pserver.NewService(*etcdEndpoint, *numPservers, timeout) + e := pserver.NewEtcdClient(*etcdEndpoint, *numPservers, timeout) + idx, err := e.Register() + if err != nil { + panic(err) + } + + s, err := pserver.NewService(idx) if err != nil { panic(err) } diff --git a/go/master/etcd_client.go b/go/master/etcd_client.go index b7293a75989..f7b46385773 100644 --- a/go/master/etcd_client.go +++ b/go/master/etcd_client.go @@ -18,8 +18,8 @@ const ( DefaultAddrPath = "/master/addr" ) -// EtcdClient is the etcd client that master uses for fault tolerance -// and service registry. +// EtcdClient is the etcd client that the master uses for fault +// tolerance and service registry. type EtcdClient struct { lockPath string statePath string diff --git a/go/pserver/client_test.go b/go/pserver/client_test.go index 4a62ae88a4a..5bd16118a7f 100644 --- a/go/pserver/client_test.go +++ b/go/pserver/client_test.go @@ -7,7 +7,6 @@ import ( "strconv" "strings" "testing" - "time" "github.com/PaddlePaddle/Paddle/go/pserver" ) @@ -31,7 +30,7 @@ func init() { port[i] = p go func(l net.Listener) { - s, err := pserver.NewService("", 1, time.Second*5) + s, err := pserver.NewService(0) if err != nil { panic(err) } diff --git a/go/pserver/etcd_client.go b/go/pserver/etcd_client.go new file mode 100644 index 00000000000..4d88243edd4 --- /dev/null +++ b/go/pserver/etcd_client.go @@ -0,0 +1,181 @@ +package pserver + +import ( + "context" + "errors" + "strconv" + "strings" + "time" + + "github.com/PaddlePaddle/Paddle/go/utils/networkhelper" + "github.com/coreos/etcd/clientv3" + "github.com/coreos/etcd/clientv3/concurrency" + log "github.com/sirupsen/logrus" +) + +// EtcdClient is the etcd client that the pserver uses for fault +// tolerance, service registry and coordination. +type EtcdClient struct { + numPservers int + etcdEndpoints string + etcdClient *clientv3.Client + // etcdTimeout is also used as retry intervals. + etcdTimeout time.Duration + // FIXME: ensure GetExternalIP gets the correct ip for trainers to connect. + externalIP string + // desired number of pservers in the job. + // assume desired will not change during one training job. + desired int +} + +// NewEtcdClient creates an EtcdClient +func NewEtcdClient(endpoints string, numPservers int, timeout time.Duration) *EtcdClient { + return &EtcdClient{ + etcdTimeout: timeout, + numPservers: numPservers, + etcdEndpoints: endpoints, + } +} + +// Register registers the pserver on etcd +// +// Register returns the index of the current pserver. +func (e *EtcdClient) Register() (int, error) { + + var err error + e.externalIP, err = networkhelper.GetExternalIP() + if err != nil { + return 0, err + } + + // initialize connection to etcd. + ep := strings.Split(e.etcdEndpoints, ",") + for { + cli, err := clientv3.New(clientv3.Config{ + Endpoints: ep, + DialTimeout: e.etcdTimeout, + }) + if err != nil { + log.Errorf("connect to etcd error: %v", err) + time.Sleep(e.etcdTimeout) + continue + } + e.etcdClient = cli + log.Debugf("inited client to %s", e.etcdEndpoints) + break + } + // init /ps_desired using transaction, for multiple pservers may want to write + // it at the same time. + for { + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + _, err := e.initDesiredPsercers(ctx, e.numPservers) + cancel() + if err != nil { + log.Warn(err) + time.Sleep(e.etcdTimeout) + continue + } + break + } + // TODO: when implementing extending or reducing pservers, /ps_desired is + // changed, then we need to watch /ps_desired node for events. For now, just + // write once when init and read from it. + // wait and set s.desired init value + for { + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + resp, err := e.etcdClient.Get(ctx, PsDesired) + cancel() + if err != nil { + log.Errorf("getting %s error: %v", PsDesired, err) + time.Sleep(e.etcdTimeout) + continue + } + if len(resp.Kvs) != 0 { + e.desired, err = strconv.Atoi(string(resp.Kvs[0].Value)) + if err != nil { + log.Errorf("value of %s invalid %v\n", PsDesired, err) + time.Sleep(e.etcdTimeout) + // NOTE: wait util ps_desired value change + continue + } + break + } + } + + var pserverIdx int + // try register pserver node on etcd + for { + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + var err error + pserverIdx, err = e.registerPserverEtcd(ctx) + cancel() + if err != nil { + log.Warn(err) + time.Sleep(e.etcdTimeout) + continue + } + break + } + + return pserverIdx, nil +} + +func (e *EtcdClient) initDesiredPsercers(ctx context.Context, numPservers int) (*clientv3.TxnResponse, error) { + return concurrency.NewSTM(e.etcdClient, func(c concurrency.STM) error { + dsStr := c.Get(PsDesired) + if dsStr == "" { + c.Put(PsDesired, strconv.Itoa(numPservers)) + } + return nil + }, concurrency.WithAbortContext(ctx), concurrency.WithIsolation(concurrency.RepeatableReads)) +} + +// registerPserverEtcd registers pserver node on etcd using transaction. +func (e *EtcdClient) registerPserverEtcd(ctx context.Context) (int, error) { + var idx int + _, err := concurrency.NewSTM(e.etcdClient, func(c concurrency.STM) error { + registered := false + for i := 0; i < e.desired; i++ { + psKey := "/ps/" + strconv.Itoa(i) + log.Debugf("checking %s", psKey) + ps := c.Get(psKey) + log.Debugf("got value (%s) for key: %s", ps, psKey) + + if ps == "" { + resp, err := e.etcdClient.Grant(context.TODO(), 5) + if err != nil { + log.Fatal(err) + } + // find the first id and write info + c.Put(psKey, e.externalIP, clientv3.WithLease(resp.ID)) + log.Debugf("set pserver node %s with value %s", psKey, e.externalIP) + ch, kaerr := e.etcdClient.KeepAlive(context.TODO(), resp.ID) + if kaerr != nil { + log.Errorf("keepalive etcd node error: %v", kaerr) + return kaerr + } + + // Eat the keep alive message so etcd + // will not expire the lease. + go func(ch <-chan *clientv3.LeaseKeepAliveResponse) { + ka := <-ch + log.Debugf("keepalive: %d\n", ka.TTL) + }(ch) + log.Debug("register finished") + idx = i + registered = true + break + } + } + if registered == true { + return nil + } + return errors.New("not registerd, may due to already have enough pservers") + }, concurrency.WithAbortContext(ctx), concurrency.WithIsolation(concurrency.RepeatableReads)) + + if err != nil { + return 0, err + } + + return idx, nil +} diff --git a/go/pserver/service.go b/go/pserver/service.go index f966595fdcc..f386ebea1eb 100644 --- a/go/pserver/service.go +++ b/go/pserver/service.go @@ -1,18 +1,9 @@ package pserver import ( - "context" "errors" "fmt" - "strconv" - "strings" "sync" - "time" - - "github.com/PaddlePaddle/Paddle/go/utils/networkhelper" - "github.com/coreos/etcd/clientv3" - "github.com/coreos/etcd/clientv3/concurrency" - log "github.com/sirupsen/logrus" ) // ElementType is the type of elements of a Parameter. @@ -55,160 +46,25 @@ type Gradient Parameter // Service is the RPC service for pserver. type Service struct { initialized chan struct{} + idx int mu sync.Mutex opt *optimizer paramMap map[string]Parameter - - etcdEndpoints string - etcdClient *clientv3.Client - // etcdTimeout is also used as retry intervals. - etcdTimeout time.Duration - // desired number of pservers in the job. - // assume desired will not change during one training job. - desired int - // FIXME: ensure GetExternalIP gets the correct ip for trainers to connect. - externalIP string } // NewService creates a new service, will bypass etcd registration if no // endpoints specified. -func NewService(endpoints string, numPservers int, timeout time.Duration) (*Service, error) { - s := &Service{opt: newOptimizer(sgd, 0.005)} +func NewService(idx int) (*Service, error) { + s := &Service{ + idx: idx, + opt: newOptimizer(sgd, 0.005), + } s.paramMap = make(map[string]Parameter) s.initialized = make(chan struct{}) - s.etcdEndpoints = endpoints - s.etcdTimeout = timeout - - var err error - s.externalIP, err = networkhelper.GetExternalIP() - if err != nil { - return nil, err - } - - if endpoints != "" { - // initialize connection to etcd, try - ep := strings.Split(s.etcdEndpoints, ",") - for { - cli, err := clientv3.New(clientv3.Config{ - Endpoints: ep, - DialTimeout: s.etcdTimeout, - }) - if err != nil { - log.Errorf("connect to etcd error: %v", err) - time.Sleep(s.etcdTimeout) - continue - } - s.etcdClient = cli - log.Debugf("inited client to %s", s.etcdEndpoints) - break - } - // init /ps_desired using transaction, for multiple pservers may want to write - // it at the same time. - for { - ctx, cancel := context.WithTimeout(context.Background(), time.Second) - _, err := s.initDesiredPsercers(ctx, numPservers) - cancel() - if err != nil { - log.Warn(err) - time.Sleep(s.etcdTimeout) - continue - } - break - } - // TODO: when implementing extending or reducing pservers, /ps_desired is - // changed, then we need to watch /ps_desired node for events. For now, just - // write once when init and read from it. - // wait and set s.desired init value - for { - ctx, cancel := context.WithTimeout(context.Background(), time.Second) - resp, err := s.etcdClient.Get(ctx, PsDesired) - cancel() - if err != nil { - log.Errorf("getting %s error: %v", PsDesired, err) - time.Sleep(s.etcdTimeout) - continue - } - if len(resp.Kvs) != 0 { - s.desired, err = strconv.Atoi(string(resp.Kvs[0].Value)) - if err != nil { - log.Errorf("value of %s invalid %v\n", PsDesired, err) - time.Sleep(s.etcdTimeout) - // NOTE: wait util ps_desired value change - continue - } - break - } - } - // try register pserver node on etcd - for { - ctx, cancel := context.WithTimeout(context.Background(), time.Second) - _, err := s.registerPserverEtcd(ctx) - cancel() - if err != nil { - log.Warn(err) - time.Sleep(s.etcdTimeout) - continue - } - break - } - } // if endpoints != "" - // Bypass etcd registration if no endpoints specified return s, nil } -func (s *Service) initDesiredPsercers(ctx context.Context, numPservers int) (*clientv3.TxnResponse, error) { - return concurrency.NewSTM(s.etcdClient, func(c concurrency.STM) error { - dsStr := c.Get(PsDesired) - if dsStr == "" { - c.Put(PsDesired, strconv.Itoa(numPservers)) - } - return nil - }, concurrency.WithAbortContext(ctx), concurrency.WithIsolation(concurrency.RepeatableReads)) -} - -// registerPserverEtcd registers pserver node on etcd using transaction. -func (s *Service) registerPserverEtcd(ctx context.Context) (*clientv3.TxnResponse, error) { - return concurrency.NewSTM(s.etcdClient, func(c concurrency.STM) error { - registered := false - for i := 0; i < s.desired; i++ { - psKey := "/ps/" + strconv.Itoa(i) - log.Debugf("checking %s", psKey) - ps := c.Get(psKey) - log.Debugf("got value (%s) for key: %s", ps, psKey) - - if ps == "" { - resp, err := s.etcdClient.Grant(context.TODO(), 5) - if err != nil { - log.Fatal(err) - } - // find the first id and write info - c.Put(psKey, s.externalIP, clientv3.WithLease(resp.ID)) - log.Debugf("set pserver node %s with value %s", psKey, s.externalIP) - ch, kaerr := s.etcdClient.KeepAlive(context.TODO(), resp.ID) - if kaerr != nil { - log.Errorf("keepalive etcd node error: %v", kaerr) - return kaerr - } - - // Eat the keep alive message so etcd - // will not expire the lease. - go func(ch <-chan *clientv3.LeaseKeepAliveResponse) { - ka := <-ch - log.Debugf("keepalive: %d\n", ka.TTL) - }(ch) - log.Debug("register finished") - registered = true - break - } - } - if registered == true { - return nil - } - return errors.New("not registerd, may due to already have enough pservers") - }, concurrency.WithAbortContext(ctx), concurrency.WithIsolation(concurrency.RepeatableReads)) -} - // InitParam initializes a parameter. func (s *Service) InitParam(paramWithConfigs ParameterWithConfig, dummy *int) error { select { diff --git a/go/pserver/service_test.go b/go/pserver/service_test.go index 1d84f15d78a..d9d887cffd4 100644 --- a/go/pserver/service_test.go +++ b/go/pserver/service_test.go @@ -10,7 +10,7 @@ import ( ) func TestFull(t *testing.T) { - s, err := pserver.NewService("", 1, time.Second*5) + s, err := pserver.NewService(0) if err != nil { t.Error(err) } @@ -75,7 +75,7 @@ func TestFull(t *testing.T) { } func TestMultipleInit(t *testing.T) { - s, err := pserver.NewService("", 1, time.Second*5) + s, err := pserver.NewService(0) if err != nil { t.Error(err) } @@ -91,7 +91,7 @@ func TestMultipleInit(t *testing.T) { } func TestUninitialized(t *testing.T) { - s, err := pserver.NewService("", 1, time.Second*5) + s, err := pserver.NewService(0) err = s.SendGrad(pserver.Gradient{}, nil) if err.Error() != pserver.Uninitialized { t.FailNow() @@ -99,7 +99,7 @@ func TestUninitialized(t *testing.T) { } func TestBlockUntilInitialized(t *testing.T) { - s, err := pserver.NewService("", 1, time.Second*5) + s, err := pserver.NewService(0) if err != nil { t.Error(err) } -- GitLab From 98bb8ee295c6bbbb573f0ea33df1f7175a8b6b41 Mon Sep 17 00:00:00 2001 From: qiaolongfei Date: Wed, 28 Jun 2017 10:01:23 +0800 Subject: [PATCH 0864/3256] fix dim problem --- paddle/py_paddle/dataprovider_converter.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/paddle/py_paddle/dataprovider_converter.py b/paddle/py_paddle/dataprovider_converter.py index edc2e029237..f8242d9f7bc 100644 --- a/paddle/py_paddle/dataprovider_converter.py +++ b/paddle/py_paddle/dataprovider_converter.py @@ -109,7 +109,10 @@ class DenseScanner(IScanner): if len(self.__shape__) > 3: raise ValueError( "The dimension of input cannot be greater than 3.") - self.__dim__ = reduce(lambda x, y: x * y, self.__shape__) + if len(self.__shape__) == 0: + self.__dim__ = 1 + else: + self.__dim__ = reduce(lambda x, y: x * y, self.__shape__) if len(self.__shape__) == 1 and self.__dim__ != self.input_type.dim: raise ValueError( "The data size must be equal to it in data layer.") -- GitLab From 8b69c1348c17cf7aca83aacc2c63ef9eaad97467 Mon Sep 17 00:00:00 2001 From: qiaolongfei Date: Wed, 28 Jun 2017 10:34:22 +0800 Subject: [PATCH 0865/3256] check shape of vector input, should not be a scalar --- paddle/py_paddle/dataprovider_converter.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/paddle/py_paddle/dataprovider_converter.py b/paddle/py_paddle/dataprovider_converter.py index f8242d9f7bc..7df4a6967fc 100644 --- a/paddle/py_paddle/dataprovider_converter.py +++ b/paddle/py_paddle/dataprovider_converter.py @@ -110,9 +110,9 @@ class DenseScanner(IScanner): raise ValueError( "The dimension of input cannot be greater than 3.") if len(self.__shape__) == 0: - self.__dim__ = 1 - else: - self.__dim__ = reduce(lambda x, y: x * y, self.__shape__) + raise ValueError( + "The input should be a vector, please check your input data.") + self.__dim__ = reduce(lambda x, y: x * y, self.__shape__) if len(self.__shape__) == 1 and self.__dim__ != self.input_type.dim: raise ValueError( "The data size must be equal to it in data layer.") -- GitLab From 3e9aa7fd8bfac7434057afcdd6ae62ea7a92bff1 Mon Sep 17 00:00:00 2001 From: liaogang Date: Wed, 28 Jun 2017 10:42:54 +0800 Subject: [PATCH 0866/3256] FIX: Pass CI --- cmake/generic.cmake | 4 ---- paddle/memory/CMakeLists.txt | 6 ------ paddle/memory/detail/buddy_allocator.cc | 2 +- paddle/memory/detail/buddy_allocator.h | 3 +++ 4 files changed, 4 insertions(+), 11 deletions(-) diff --git a/cmake/generic.cmake b/cmake/generic.cmake index 840155750e1..69e8164a00d 100644 --- a/cmake/generic.cmake +++ b/cmake/generic.cmake @@ -78,10 +78,6 @@ # # cc_test(example_test SRCS example_test.cc DEPS example glog gflags) -if(WITH_GPU) - add_definitions(-DPADDLE_WITH_GPU) -endif() - if(NOT APPLE) find_package(Threads REQUIRED) link_libraries(${CMAKE_THREAD_LIBS_INIT}) diff --git a/paddle/memory/CMakeLists.txt b/paddle/memory/CMakeLists.txt index e74ce75c939..3943c3cfad3 100644 --- a/paddle/memory/CMakeLists.txt +++ b/paddle/memory/CMakeLists.txt @@ -1,7 +1 @@ add_subdirectory(detail) - -if(${WITH_GPU}) - nv_library(memory SRCS memory.cc) -else(${WITH_GPU}) - cc_library(memory SRCS memory.cc) -endif(${WITH_GPU}) diff --git a/paddle/memory/detail/buddy_allocator.cc b/paddle/memory/detail/buddy_allocator.cc index 895bf319d77..ebe680f5eea 100644 --- a/paddle/memory/detail/buddy_allocator.cc +++ b/paddle/memory/detail/buddy_allocator.cc @@ -14,7 +14,7 @@ #pragma once -#include "paddle/memory/detail/system_allocator.h" +#include "paddle/memory/detail/buddy_allocator.h" namespace paddle { namespace memory { diff --git a/paddle/memory/detail/buddy_allocator.h b/paddle/memory/detail/buddy_allocator.h index 129b137ed73..702c7d28ee5 100644 --- a/paddle/memory/detail/buddy_allocator.h +++ b/paddle/memory/detail/buddy_allocator.h @@ -16,6 +16,9 @@ #include "paddle/memory/detail/system_allocator.h" +#include +#include + namespace paddle { namespace memory { namespace detail { -- GitLab From 9490d243dd0255021b288f9c2e43c57b30264b9b Mon Sep 17 00:00:00 2001 From: liaogang Date: Wed, 28 Jun 2017 10:46:14 +0800 Subject: [PATCH 0867/3256] ENH: clang-format --- paddle/memory/detail/buddy_allocator.h | 2 +- paddle/platform/cuda.h | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/paddle/memory/detail/buddy_allocator.h b/paddle/memory/detail/buddy_allocator.h index 702c7d28ee5..82e6aaedc71 100644 --- a/paddle/memory/detail/buddy_allocator.h +++ b/paddle/memory/detail/buddy_allocator.h @@ -16,8 +16,8 @@ #include "paddle/memory/detail/system_allocator.h" -#include #include +#include namespace paddle { namespace memory { diff --git a/paddle/platform/cuda.h b/paddle/platform/cuda.h index 864a5d3340a..8fe891f9ce6 100644 --- a/paddle/platform/cuda.h +++ b/paddle/platform/cuda.h @@ -29,13 +29,12 @@ inline void throw_on_error(cudaError_t e, const char* message) { } int GetDeviceCount(void) { - int count; - throw_on_error(cudaGetDeviceCount(&count), - "cudaGetDeviceCount failed"); - return count; + int count; + throw_on_error(cudaGetDeviceCount(&count), "cudaGetDeviceCount failed"); + return count; } } // namespace platform } // namespace paddle -#endif // PADDLE_ONLY_CPU +#endif // PADDLE_ONLY_CPU -- GitLab From a402cf908160c5fb8a4f0d1aa9efdca7109c0375 Mon Sep 17 00:00:00 2001 From: qiaolongfei Date: Wed, 28 Jun 2017 10:46:23 +0800 Subject: [PATCH 0868/3256] correct the demo code for dense_vector label input --- doc/getstarted/concepts/use_concepts_cn.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/getstarted/concepts/use_concepts_cn.rst b/doc/getstarted/concepts/use_concepts_cn.rst index e63ca11102c..f15b11bd780 100644 --- a/doc/getstarted/concepts/use_concepts_cn.rst +++ b/doc/getstarted/concepts/use_concepts_cn.rst @@ -111,7 +111,7 @@ PaddlePaddle支持不同类型的输入数据,主要包括四种类型,和 # define training dataset reader def train_reader(): train_x = np.array([[1, 1], [1, 2], [3, 4], [5, 2]]) - train_y = np.array([-2, -3, -7, -7]) + train_y = np.array([[-2], [-3], [-7], [-7]]) def reader(): for i in xrange(train_y.shape[0]): yield train_x[i], train_y[i] -- GitLab From e93c3e4070c37dd6bdf31ec4d3fa4033f3208e2e Mon Sep 17 00:00:00 2001 From: qiaolongfei Date: Wed, 28 Jun 2017 11:07:52 +0800 Subject: [PATCH 0869/3256] fix format --- paddle/py_paddle/dataprovider_converter.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/paddle/py_paddle/dataprovider_converter.py b/paddle/py_paddle/dataprovider_converter.py index 7df4a6967fc..218cb5ec560 100644 --- a/paddle/py_paddle/dataprovider_converter.py +++ b/paddle/py_paddle/dataprovider_converter.py @@ -111,7 +111,8 @@ class DenseScanner(IScanner): "The dimension of input cannot be greater than 3.") if len(self.__shape__) == 0: raise ValueError( - "The input should be a vector, please check your input data.") + "The input should be a vector, please check your input data." + ) self.__dim__ = reduce(lambda x, y: x * y, self.__shape__) if len(self.__shape__) == 1 and self.__dim__ != self.input_type.dim: raise ValueError( -- GitLab From 05ddf23e1dd89dee9c1eeca188f782f38992ce60 Mon Sep 17 00:00:00 2001 From: dongzhihong Date: Wed, 28 Jun 2017 11:43:57 +0800 Subject: [PATCH 0870/3256] "add log of create optimizer" --- go/pserver/optimizer.go | 24 ++++++++++++++---------- go/pserver/service.go | 2 +- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/go/pserver/optimizer.go b/go/pserver/optimizer.go index 9cb2801f30d..46e614d3a16 100644 --- a/go/pserver/optimizer.go +++ b/go/pserver/optimizer.go @@ -12,14 +12,15 @@ import "C" import ( "fmt" "unsafe" + + log "github.com/sirupsen/logrus" ) var nullPtr = unsafe.Pointer(uintptr(0)) type optimizer struct { - opt *C.struct_paddle_optimizer - // used in GetParam, reconstruct Parameter from optimizer - ElementType ElementType + opt *C.struct_paddle_optimizer + elementType ElementType } func cArrayToSlice(p unsafe.Pointer, len int) []byte { @@ -37,13 +38,17 @@ func cArrayToSlice(p unsafe.Pointer, len int) []byte { func newOptimizer(paramWithConfigs ParameterWithConfig) *optimizer { o := &optimizer{} - o.ElementType = paramWithConfigs.Param.ElementType + o.elementType = paramWithConfigs.Param.ElementType p := paramWithConfigs.Param c := paramWithConfigs.Config + log.WithFields(log.Fields{ + "ElementType": p.ElementType, + "ParamSize": len(p.Content), + "ConfigSize": len(c), + }).Info("New Optimizer Created with config:") var cbuffer unsafe.Pointer - cbuffer_len := int(unsafe.Sizeof(p.Content[0])) * len(p.Content) - cbuffer = C.malloc(C.size_t(cbuffer_len)) - C.memcpy(cbuffer, unsafe.Pointer(&p.Content[0]), C.size_t(cbuffer_len)) + cbuffer = C.malloc(C.size_t(len(p.Content))) + C.memcpy(cbuffer, unsafe.Pointer(&p.Content[0]), C.size_t(len(p.Content))) o.opt = C.paddle_create_optimizer((*C.uchar)(&c[0]), C.int(len(c)), C.paddle_element_type(p.ElementType), cbuffer, C.int(len(p.Content)), (*C.char)(nullPtr), 0) @@ -57,11 +62,10 @@ func (o *optimizer) GetWeights() []byte { } func (o *optimizer) UpdateParameter(g Gradient) error { - if o.ElementType != g.ElementType { - return fmt.Errorf("Name: %s, parameter and gradient element type not match, parameter: %v, gradient: %v", g.Name, o.ElementType, g.ElementType) + if o.elementType != g.ElementType { + return fmt.Errorf("Name: %s, parameter and gradient element type not match, parameter: %v, gradient: %v", g.Name, o.elementType, g.ElementType) } - fmt.Println(g) r := C.paddle_update_parameter(o.opt, C.paddle_element_type(g.ElementType), unsafe.Pointer(&g.Content[0]), C.int(len(g.Content))) if r != 0 { return fmt.Errorf("optimizer update returned error code: %d", r) diff --git a/go/pserver/service.go b/go/pserver/service.go index 2ab622d7902..555d379bcbe 100644 --- a/go/pserver/service.go +++ b/go/pserver/service.go @@ -128,7 +128,7 @@ func (s *Service) GetParam(name string, parameter *Parameter) error { // to save the program from making a copy of the // paramter content. parameter.Name = name - parameter.ElementType = opt.ElementType + parameter.ElementType = opt.elementType parameter.Content = opt.GetWeights() return nil } -- GitLab From 60a65b5d90052d27fbd5928b71304a8f91fce181 Mon Sep 17 00:00:00 2001 From: wuyi05 Date: Wed, 28 Jun 2017 13:33:00 +0800 Subject: [PATCH 0871/3256] design doc for go package management --- doc/design/build_system/README.md | 34 +++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/doc/design/build_system/README.md b/doc/design/build_system/README.md index 310739f37ae..96af6566d0d 100644 --- a/doc/design/build_system/README.md +++ b/doc/design/build_system/README.md @@ -105,3 +105,37 @@ shared_library(api ### Implementation As above example CMakeLists.txt executes, each function invocation adds "nodes" to a dependency graph. It also use this graph to generate CMake commands including `add_executable`, `add_dependencies`, `target_link_libraries`, and `add_test`. + +### Using Package Manager For Go + +Building go binaries and libraries need to satisfy their dependencies, generally +we can do `go get ./...` to download and compile all external dependencies. The +problems are: + +1. `go get` will always get the latest code from master branch, so when an external + project updated and deprecates something or made changes to their APIs, builds + may not pass. This is very different with what we already have in `cmake/external` + which download a specific version or commit id of the dependency. +1. Some locations can not access external dependencies through the internet, as mentioned + in https://github.com/PaddlePaddle/Paddle/issues/2605. Using package management + tools can package the dependencies as a "vendor" package, which can be mirrored + at many cloud file hosting, so users what to compile paddle by themselves can + download this "vendor" package from a mirror site. + +#### Godep vs. Glide + +Here's a brief comparison for current Go ecosystem: https://github.com/Masterminds/glide/wiki/Go-Package-Manager-Comparison. There are +also many complaints about `Godep`. A new "official" pakcage management tool has been +started: https://github.com/golang/dep to resolve such problems, but it's currently +at Alpha stage. So the best choice now is glide obviously. + +#### Manage Go Packages + +- Dependencies: `go/glide.yaml` will store the dependencies and their versions which + is directly imported by paddle. `go/glide.lock` will store all dependencies recursively + with their commit id. Builds will "lock" to these packages if we don't `glide up` + them +- Vendor package: `go/vendor` directory will generated when running `cmake` command. `cmake` + will download the code corresponding to `go/glide.lock`. If we put a vendor folder + under `go/`, cmake will just check the commit id to the packages under the folder, + if commit id matches, there will be no download at all. -- GitLab From 6ad1d21c4b22adcb6fb970875256a08622d1af6e Mon Sep 17 00:00:00 2001 From: qiaolongfei Date: Wed, 28 Jun 2017 14:12:13 +0800 Subject: [PATCH 0872/3256] refine code of operator --- paddle/framework/scope.cc | 45 +++++++++++++++------------------- paddle/framework/scope.h | 30 +++++++++++++++-------- paddle/framework/scope_test.cc | 42 ++++++++++++++++++------------- 3 files changed, 65 insertions(+), 52 deletions(-) diff --git a/paddle/framework/scope.cc b/paddle/framework/scope.cc index e9855988490..5c197cec2a0 100644 --- a/paddle/framework/scope.cc +++ b/paddle/framework/scope.cc @@ -1,18 +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. */ + #include "paddle/framework/scope.h" namespace paddle { namespace framework { -Error Scope::CreateVariable(const std::string& name) { - if (name == "") { - return Error("Variable name should not be empty"); +Variable* Scope::CreateVariable(const std::string& name) { + if (!HasVariable(name)) { + vars_[name] = std::unique_ptr(new Variable()); } - - if (HaveVariable(name)) { - return AlreadyCreated; - } - vars_[name] = std::unique_ptr(new Variable()); - return Error(); + return GetVariable(name); } Variable* Scope::GetVarLocally(const std::string& name) const { @@ -33,22 +42,8 @@ Variable* Scope::GetVariable(const std::string& name) const { } } -Variable* Scope::GetOrCreateVariable(const std::string& name) { - Variable* var = GetVariable(name); - if (var != nullptr) { - return var; - } - - Error err = CreateVariable(name); - if (!err.isOK()) { - return nullptr; - } else { - return GetVariable(name); - } -} - -bool Scope::HaveVariable(const std::string& name) { - return vars_.count(name) != 0; +bool Scope::HasVariable(const std::string &name) { + return (vars_.count(name) > 0 || (parent_ && parent_->HasVariable(name))); } } // namespace framework diff --git a/paddle/framework/scope.h b/paddle/framework/scope.h index 90c8141e4f4..81491f34d8c 100644 --- a/paddle/framework/scope.h +++ b/paddle/framework/scope.h @@ -1,15 +1,28 @@ +/* Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserve. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. */ + #pragma once #include #include +#include + #include "paddle/framework/variable.h" -#include "paddle/utils/Error.h" namespace paddle { namespace framework { -const static Error AlreadyCreated("Variable has already been created"); - /** * Scope is an association of a name to Variable. All variables belong to * `Scope`. You need to specify a scope to run a Net, i.e., `net.Run(&scope)`. @@ -26,20 +39,17 @@ class Scope { // Create Variable in this Scope. Return error if Variable already been // created. - Error __must_check CreateVariable(const std::string& name); + Variable* CreateVariable(const std::string& name); // Get Variable from this Scope, this function will recursive find Variable // from it's parent scope. Return nullptr if not found. Variable* GetVariable(const std::string& name) const; - // find and return Variables in the scope it self. + // Find and return Variables in the scope it self. Variable* GetVarLocally(const std::string& name) const; - // Get a Variable from Scope, if the Variable is not exist then create it. - // User should call this function most of time. - Variable* GetOrCreateVariable(const std::string& name); - - bool HaveVariable(const std::string& name); + // Find if there is a Variable in this scope and it's parent scope + bool HasVariable(const std::string &name); private: std::unordered_map> vars_; diff --git a/paddle/framework/scope_test.cc b/paddle/framework/scope_test.cc index 09fbb78d69f..25c144868b4 100644 --- a/paddle/framework/scope_test.cc +++ b/paddle/framework/scope_test.cc @@ -1,47 +1,55 @@ +/* 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/scope.h" #include "gtest/gtest.h" TEST(Scope, Create) { using paddle::framework::Scope; - using paddle::Error; using paddle::framework::Variable; - using paddle::framework::AlreadyCreated; Scope* scope = new Scope(); - Error err = scope->CreateVariable(""); - EXPECT_FALSE(err.isOK()); + Variable* var0 = scope->CreateVariable(""); + EXPECT_NE(var0, nullptr); Variable* var1 = scope->GetVariable("a"); EXPECT_EQ(var1, nullptr); - Error err1 = scope->CreateVariable("a"); - EXPECT_TRUE(err1.isOK()); - - Error err2 = scope->CreateVariable("a"); - EXPECT_EQ(err2, AlreadyCreated); - - Variable* var2 = scope->GetVariable("a"); + Variable* var2 = scope->CreateVariable("a"); EXPECT_NE(var2, nullptr); - Variable* var3 = scope->GetOrCreateVariable("b"); - EXPECT_NE(var3, nullptr); + Variable* var3 = scope->CreateVariable("a"); + EXPECT_EQ(var2, var3); + + Variable* var4 = scope->GetVariable("a"); + EXPECT_EQ(var2, var4); } TEST(Scope, Parent) { using paddle::framework::Scope; using paddle::framework::Variable; - using paddle::Error; const auto parent_scope_ptr = std::shared_ptr(new Scope()); Scope* scope = new Scope(parent_scope_ptr); - Error err = parent_scope_ptr->CreateVariable("a"); - EXPECT_TRUE(err.isOK()); + Variable* var0 = parent_scope_ptr->CreateVariable("a"); + EXPECT_NE(var0, nullptr); Variable* var1 = scope->GetVarLocally("a"); EXPECT_EQ(var1, nullptr); Variable* var2 = scope->GetVariable("a"); - EXPECT_NE(var2, nullptr); + EXPECT_EQ(var2, var0); } \ No newline at end of file -- GitLab From 3e16f5fdfbb52d4381d2eea2d4a60d414e72cf0d Mon Sep 17 00:00:00 2001 From: qiaolongfei Date: Wed, 28 Jun 2017 14:15:54 +0800 Subject: [PATCH 0873/3256] clang format --- paddle/framework/scope.cc | 2 +- paddle/framework/scope.h | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/paddle/framework/scope.cc b/paddle/framework/scope.cc index 5c197cec2a0..72cb744707d 100644 --- a/paddle/framework/scope.cc +++ b/paddle/framework/scope.cc @@ -42,7 +42,7 @@ Variable* Scope::GetVariable(const std::string& name) const { } } -bool Scope::HasVariable(const std::string &name) { +bool Scope::HasVariable(const std::string& name) { return (vars_.count(name) > 0 || (parent_ && parent_->HasVariable(name))); } diff --git a/paddle/framework/scope.h b/paddle/framework/scope.h index 81491f34d8c..a624fe3bbef 100644 --- a/paddle/framework/scope.h +++ b/paddle/framework/scope.h @@ -14,9 +14,9 @@ limitations under the License. */ #pragma once +#include #include #include -#include #include "paddle/framework/variable.h" @@ -49,7 +49,7 @@ class Scope { Variable* GetVarLocally(const std::string& name) const; // Find if there is a Variable in this scope and it's parent scope - bool HasVariable(const std::string &name); + bool HasVariable(const std::string& name); private: std::unordered_map> vars_; -- GitLab From 7062be0fbdbe2a916d7dd81e81c013bc7ee4a914 Mon Sep 17 00:00:00 2001 From: hedaoyuan Date: Wed, 28 Jun 2017 15:12:30 +0800 Subject: [PATCH 0874/3256] Add cmake for compile NNPACKConvOp.cpp. --- CMakeLists.txt | 5 +++++ paddle/function/CMakeLists.txt | 5 +++++ paddle/function/nnpack/NNPACKConvOp.cpp | 29 +++++++++++++++++-------- paddle/function/nnpack/nnpack.cmake | 16 ++++++++++++++ 4 files changed, 46 insertions(+), 9 deletions(-) create mode 100644 paddle/function/nnpack/nnpack.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 3c719d35ece..f645ed04a1e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -48,6 +48,7 @@ option(COVERALLS_UPLOAD "Package code coverage data to coveralls" OFF) option(ON_TRAVIS "Exclude special unit test on Travis CI" OFF) option(WITH_C_API "Compile PaddlePaddle with C-API(Prediction)" OFF) option(WITH_GOLANG "Compile PaddlePaddle with GOLANG" OFF) +option(USE_NNPACK "Compile PaddlePaddle with NNPACK library" OFF) # CMAKE_BUILD_TYPE if(NOT CMAKE_BUILD_TYPE) @@ -126,6 +127,10 @@ if(WITH_GPU) endif(NOT WITH_DSO) endif(WITH_GPU) +if(USE_NNPACK) + list(APPEND EXTERNAL_LIBS ${NNPACK_LIB} ${PTHREADPOOL_LIB} "rt") +endif(USE_NNPACK) + add_subdirectory(proto) add_subdirectory(paddle) add_subdirectory(python) diff --git a/paddle/function/CMakeLists.txt b/paddle/function/CMakeLists.txt index 5e170714cf5..daa2aa150e0 100644 --- a/paddle/function/CMakeLists.txt +++ b/paddle/function/CMakeLists.txt @@ -10,6 +10,11 @@ if(WITH_GPU) cuda_compile(cu_objs ${cu_files}) endif() +if(USE_NNPACK) + include(nnpack/nnpack.cmake) + list(APPEND cpp_files nnpack/NNPACKConvOp.cpp) +endif() + add_library(paddle_function STATIC ${cpp_files} ${cu_objs}) add_dependencies(paddle_function ${external_project_dependencies}) add_dependencies(paddle_function gen_proto_cpp) diff --git a/paddle/function/nnpack/NNPACKConvOp.cpp b/paddle/function/nnpack/NNPACKConvOp.cpp index 57a6681f294..5e4de55469d 100644 --- a/paddle/function/nnpack/NNPACKConvOp.cpp +++ b/paddle/function/nnpack/NNPACKConvOp.cpp @@ -12,8 +12,8 @@ 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 "ConvOp.h" #include "nnpack.h" +#include "paddle/function/ConvOp.h" DEFINE_bool(nnpack_allocate_outside, false, @@ -72,14 +72,22 @@ public: } } + virtual void check(const BufferArgs& inputs, + const BufferArgs& outputs) override { + const TensorShape& output = inputs[0].shape(); + const TensorShape& filter = inputs[1].shape(); + const TensorShape& input = outputs[0].shape(); + checkShape(input, filter, output); + } + void calc(const BufferArgs& inputs, const BufferArgs& outputs) override { CHECK_EQ(numInputs_, inputs.size()); CHECK_EQ(numOutputs_, outputs.size()); CHECK_EQ(outputs[0].getArgType(), ASSIGN_TO); + check(inputs, outputs); const TensorShape& input = inputs[0].shape(); const TensorShape& filter = inputs[1].shape(); const TensorShape& output = outputs[0].shape(); - check(input, filter, output); size_t batchSize = input[0]; size_t inputChannels = input[1]; @@ -92,12 +100,13 @@ public: // size_t outputWidth = output[3]; nnp_size inputSize = {.width = inputWidth, .height = inputHeight}; - nnp_padding padding = {.top = paddingH(), - .right = paddingW(), - .bottom = paddingH(), - .left = paddingW()}; + nnp_padding padding = {.top = (size_t)paddingH(), + .right = (size_t)paddingW(), + .bottom = (size_t)paddingH(), + .left = (size_t)paddingW()}; nnp_size kernelSize = {.width = filterWidth, .height = filterHeight}; - nnp_size outputSubsampling = {.width = strideW(), .height = strideH()}; + nnp_size outputSubsampling = {.width = (size_t)strideW(), + .height = (size_t)strideH()}; float* inputData = inputs[0].data(); float* filterData = inputs[1].data(); @@ -129,7 +138,8 @@ public: CHECK_EQ(status, nnp_status_success); } else { // only supports stride = 1 - CHECK_EQ(stride_, 1); + CHECK_EQ(strideH(), 1); + CHECK_EQ(strideW(), 1); nnp_status status = nnp_convolution_output(algorithm_, batchSize, inputChannels, @@ -189,7 +199,8 @@ public: CHECK_EQ(status, nnp_status_success); } else { // only supports stride = 1 - CHECK_EQ(stride_, 1); + CHECK_EQ(strideH(), 1); + CHECK_EQ(strideW(), 1); nnp_status status = nnp_convolution_output(algorithm_, batchSize, inputChannels, diff --git a/paddle/function/nnpack/nnpack.cmake b/paddle/function/nnpack/nnpack.cmake new file mode 100644 index 00000000000..7182730ae8f --- /dev/null +++ b/paddle/function/nnpack/nnpack.cmake @@ -0,0 +1,16 @@ +# Find the NNPACK library +# NNPACK_ROOT - where to find NNPACK include and library. +# + +set(NNPACK_FOUND OFF) +set(NNPACK_ROOT $ENV{NNPACK_ROOT} CACHE PATH "Folder contains NNPACK") +find_path(NNPACK_INC_DIR nnpack.h PATHS ${NNPACK_ROOT}/include) +find_library(NNPACK_LIB NAMES nnpack PATHS ${NNPACK_ROOT}/lib) +find_library(PTHREADPOOL_LIB NAMES pthreadpool PATHS ${NNPACK_ROOT}/lib) + +if(NNPACK_INC_DIR AND NNPACK_LIB AND PTHREADPOOL_LIB) + set(NNPACK_FOUND ON) + INCLUDE_DIRECTORIES(${NNPACK_INC_DIR}) +else() + message(FATAL_ERROR "Cannot find NNPACK in (${NNPACK_ROOT})") +endif() -- GitLab From 2d9113dac13000851d0d95818299f3e7c0d532c4 Mon Sep 17 00:00:00 2001 From: hedaoyuan Date: Wed, 28 Jun 2017 15:47:23 +0800 Subject: [PATCH 0875/3256] Add test for NNPACKConvFunc. --- paddle/function/CMakeLists.txt | 3 + paddle/function/nnpack/NNPACKConvOpTest.cpp | 96 +++++++++++++++++++++ 2 files changed, 99 insertions(+) create mode 100644 paddle/function/nnpack/NNPACKConvOpTest.cpp diff --git a/paddle/function/CMakeLists.txt b/paddle/function/CMakeLists.txt index daa2aa150e0..4ef8d80ff12 100644 --- a/paddle/function/CMakeLists.txt +++ b/paddle/function/CMakeLists.txt @@ -13,6 +13,9 @@ endif() if(USE_NNPACK) include(nnpack/nnpack.cmake) list(APPEND cpp_files nnpack/NNPACKConvOp.cpp) + if(WITH_TESTING) + add_unittest(NNPACKConvOpTest nnpack/NNPACKConvOpTest.cpp) + endif() endif() add_library(paddle_function STATIC ${cpp_files} ${cu_objs}) diff --git a/paddle/function/nnpack/NNPACKConvOpTest.cpp b/paddle/function/nnpack/NNPACKConvOpTest.cpp new file mode 100644 index 00000000000..e7ce61cc6c4 --- /dev/null +++ b/paddle/function/nnpack/NNPACKConvOpTest.cpp @@ -0,0 +1,96 @@ +/* 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/function/Function.h" +#include "paddle/function/FunctionTest.h" + +DEFINE_string(algo, + "auto", + "The algorithm (auto, ft8x8, ft16x16, wt8x8, " + "implicit-gemm, or direct) for computing convolution of NNPACK."); + +namespace paddle { + +#define IS_NNPACK_SUPPORT(algo, filterSize, stride) \ + if (algo == "direct" && filterSize != 1) continue; \ + if (algo == "direct" && batchSize != 1) continue; \ + if (algo == "wt8x8" && filterSize != 3) continue; \ + if (algo == "implicit-gemm" && batchSize != 1) continue; \ + if (algo != "auto" && algo != "implicit-gemm" && stride > 1) continue; + +class ConvolutionTest { +public: + ConvolutionTest(const std::string& conv1, + const std::string& conv2, + std::string algo = "auto") { + for (size_t batchSize : {1, 32}) { + for (size_t inputSize : {7, 14, 54}) { + for (size_t filterSize : {1, 3, 5}) { + for (size_t inputChannels : {3, 64}) { + for (size_t outputChannels : {3, 64, 128}) { + if (inputChannels < outputChannels) break; + for (size_t stride : {1, 2}) { + // if batchSize > 1 NNPACKConv only supports stride = 1 + if (batchSize > 1 && stride > 1) break; + for (size_t padding : {0, 1}) { + if (padding >= filterSize) break; + size_t outputSize = + (inputSize - filterSize + 2 * padding + stride) / stride; + IS_NNPACK_SUPPORT(algo, filterSize, stride); + LOG(INFO) << " batchSize=" << batchSize + << " inputChannels=" << inputChannels + << " inputHeight=" << inputSize + << " inputWidth=" << inputSize + << " outputChannels=" << outputChannels + << " filterHeight=" << filterSize + << " filterWidth=" << filterSize + << " outputHeight=" << outputSize + << " outputWidth=" << outputSize + << " stride=" << stride << " padding=" << padding; + + Compare2Function test( + conv1, + conv2, + FuncConfig() + .set("padding", padding) + .set("stride", stride) + .set("algo", algo)); + + TensorShape shape0{ + batchSize, inputChannels, inputSize, inputSize}; + TensorShape shape1{ + outputChannels, inputChannels, filterSize, filterSize}; + TensorShape shape2{ + batchSize, outputChannels, outputSize, outputSize}; + test.addInputs(BufferArg(VALUE_TYPE_FLOAT, shape0)); + test.addInputs(BufferArg(VALUE_TYPE_FLOAT, shape1)); + test.addOutputs(BufferArg(VALUE_TYPE_FLOAT, shape2)); + test.run(); + } + } + } + } + } + } + } + } +}; + +TEST(Convolution, NNPACK) { + // NNPACK only supports stride = 1 + ConvolutionTest test("GemmConv-CPU", "NNPACKConv-CPU", FLAGS_algo); +} + +} // namespace paddle -- GitLab From b8ffa8b9e9f468f79fea7f0bd452be2f8c64d17a Mon Sep 17 00:00:00 2001 From: qiaolongfei Date: Wed, 28 Jun 2017 15:57:00 +0800 Subject: [PATCH 0876/3256] move code to scope.h, remove scope.cc --- paddle/framework/CMakeLists.txt | 4 +-- paddle/framework/scope.cc | 50 --------------------------- paddle/framework/scope.h | 61 ++++++++++++++++++++++----------- paddle/framework/scope_test.cc | 16 ++++----- 4 files changed, 49 insertions(+), 82 deletions(-) delete mode 100644 paddle/framework/scope.cc diff --git a/paddle/framework/CMakeLists.txt b/paddle/framework/CMakeLists.txt index 7ea17f7114c..6caeb1be3a8 100644 --- a/paddle/framework/CMakeLists.txt +++ b/paddle/framework/CMakeLists.txt @@ -6,6 +6,4 @@ nv_test(dim_test SRCS dim_test.cu DEPS ddim) cc_test(variable_test SRCS variable_test.cc) -# scope lib -cc_library(scope SRCS scope.cc) -cc_test(scope_test SRCS scope_test.cc DEPS scope) +cc_test(scope_test SRCS scope_test.cc) diff --git a/paddle/framework/scope.cc b/paddle/framework/scope.cc deleted file mode 100644 index 72cb744707d..00000000000 --- a/paddle/framework/scope.cc +++ /dev/null @@ -1,50 +0,0 @@ -/* 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/scope.h" - -namespace paddle { -namespace framework { - -Variable* Scope::CreateVariable(const std::string& name) { - if (!HasVariable(name)) { - vars_[name] = std::unique_ptr(new Variable()); - } - return GetVariable(name); -} - -Variable* Scope::GetVarLocally(const std::string& name) const { - if (vars_.count(name)) { - return vars_.at(name).get(); - } - return nullptr; -} - -Variable* Scope::GetVariable(const std::string& name) const { - Variable* var = GetVarLocally(name); - if (var != nullptr) { - return var; - } else if (parent_ != nullptr) { - return parent_->GetVariable(name); - } else { - return nullptr; - } -} - -bool Scope::HasVariable(const std::string& name) { - return (vars_.count(name) > 0 || (parent_ && parent_->HasVariable(name))); -} - -} // namespace framework -} // namespace paddle diff --git a/paddle/framework/scope.h b/paddle/framework/scope.h index a624fe3bbef..2f8d6dbd976 100644 --- a/paddle/framework/scope.h +++ b/paddle/framework/scope.h @@ -19,37 +19,58 @@ limitations under the License. */ #include #include "paddle/framework/variable.h" +#include "paddle/platform/assert.h" namespace paddle { namespace framework { /** * Scope is an association of a name to Variable. All variables belong to - * `Scope`. You need to specify a scope to run a Net, i.e., `net.Run(&scope)`. + * Scope. You need to specify a scope to run a Net, i.e., `net.Run(&scope)`. * One net can run in different scopes and update different variable in the * scope. */ class Scope { public: - Scope() {} - - explicit Scope(const std::shared_ptr& scope) : parent_(scope) {} - - ~Scope() {} - - // Create Variable in this Scope. Return error if Variable already been - // created. - Variable* CreateVariable(const std::string& name); - - // Get Variable from this Scope, this function will recursive find Variable - // from it's parent scope. Return nullptr if not found. - Variable* GetVariable(const std::string& name) const; - - // Find and return Variables in the scope it self. - Variable* GetVarLocally(const std::string& name) const; - - // Find if there is a Variable in this scope and it's parent scope - bool HasVariable(const std::string& name); + explicit Scope(const std::shared_ptr& parent = nullptr) + : parent_(parent) {} + + /// Create Variable in this Scope. Failed if Variable already been + /// created. + Variable* CreateVariable(const std::string& name) { + PADDLE_ASSERT(!HasVariable(name)); + vars_[name] = std::unique_ptr(new Variable()); + return GetVariable(name); + } + + /// Get Variable from this Scope, this function will recursive find Variable + /// from it's parent scope. Return nullptr if not found. + Variable* GetVariable(const std::string& name) const { + auto it = vars_.find(name); + if (it != vars_.end()) { + return it->second.get(); + } else if (parent_ != nullptr) { + return parent_->GetVariable(name); + } else { + return nullptr; + } + } + + /// Get Variable from scope, if Variable is not exist, creat one and return. + Variable* GetOrCreateVariable(const std::string& name) { + auto var = GetVariable(name); + if (var) { + return var; + } else { + return CreateVariable(name); + } + } + + /// Find if there is a Variable in this scope and it's parent scope + bool HasVariable(const std::string& name) const { + return (vars_.find(name) != vars_.end() || + (parent_ && parent_->HasVariable(name))); + } private: std::unordered_map> vars_; diff --git a/paddle/framework/scope_test.cc b/paddle/framework/scope_test.cc index 25c144868b4..34ee21e1aaa 100644 --- a/paddle/framework/scope_test.cc +++ b/paddle/framework/scope_test.cc @@ -28,12 +28,13 @@ TEST(Scope, Create) { EXPECT_EQ(var1, nullptr); Variable* var2 = scope->CreateVariable("a"); - EXPECT_NE(var2, nullptr); - Variable* var3 = scope->CreateVariable("a"); + ASSERT_DEATH({ scope->CreateVariable("a"); }, ""); + + Variable* var3 = scope->GetVariable("a"); EXPECT_EQ(var2, var3); - Variable* var4 = scope->GetVariable("a"); + Variable* var4 = scope->GetOrCreateVariable("a"); EXPECT_EQ(var2, var4); } @@ -47,9 +48,6 @@ TEST(Scope, Parent) { Variable* var0 = parent_scope_ptr->CreateVariable("a"); EXPECT_NE(var0, nullptr); - Variable* var1 = scope->GetVarLocally("a"); - EXPECT_EQ(var1, nullptr); - - Variable* var2 = scope->GetVariable("a"); - EXPECT_EQ(var2, var0); -} \ No newline at end of file + Variable* var1 = scope->GetVariable("a"); + EXPECT_EQ(var0, var1); +} -- GitLab From 3a119efedad1a15f587c9415c70f661853a8d579 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Wed, 28 Jun 2017 16:18:22 +0800 Subject: [PATCH 0877/3256] Adding Enforce to platform Basically from caffe2::logging.h, but only expose `PADDLE_ENFORCE` interface. --- paddle/platform/CMakeLists.txt | 1 + paddle/platform/enforce.h | 116 ++++++++++++++++++++++++++++++++ paddle/platform/enforce_test.cc | 25 +++++++ 3 files changed, 142 insertions(+) create mode 100644 paddle/platform/enforce.h create mode 100644 paddle/platform/enforce_test.cc diff --git a/paddle/platform/CMakeLists.txt b/paddle/platform/CMakeLists.txt index 7abe2ab89e0..8435410564b 100644 --- a/paddle/platform/CMakeLists.txt +++ b/paddle/platform/CMakeLists.txt @@ -3,3 +3,4 @@ nv_test(cuda_test SRCS cuda_test.cu) cc_library(place SRCS place.cc) cc_test(place_test SRCS place_test.cc DEPS place glog gflags) cc_test(must_check_test SRCS must_check_test.cc) +cc_test(enforce_test SRCS enforce_test.cc) diff --git a/paddle/platform/enforce.h b/paddle/platform/enforce.h new file mode 100644 index 00000000000..e501e80c557 --- /dev/null +++ b/paddle/platform/enforce.h @@ -0,0 +1,116 @@ +/* + Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserve. + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#pragma once +#include +#include + +namespace paddle { +namespace platform { + +/** + * @brief Enforce exception. Inherits std::exception + * + * All enforce condition not met, will throw an EnforceNotMet exception. + */ +class EnforceNotMet : public std::exception { + public: + EnforceNotMet(const std::string& msg, const char* file, int fileline) + : file_(file), fileline_(fileline) { + std::ostringstream sout; + sout << msg << " at [" << file_ << ":" << fileline_ << "];"; + all_msg_ = sout.str(); + } + + const char* what() const noexcept override { return all_msg_.c_str(); } + + private: + std::string all_msg_; + const char* file_; + int fileline_; +}; + +namespace details { + +inline void MakeStringInternal(std::ostringstream& stream) {} + +template +inline void MakeStringInternal(std::ostringstream& stream, T v) { + stream << v; +} + +template +inline void MakeStringInternal(std::ostringstream& stream, T v, ARGS... args) { + MakeStringInternal(stream, v); + MakeStringInternal(stream, args...); +}; + +/** + * @brief Make string will concat all args into a string. + */ +template +inline std::string MakeString(ARGS... args) { + std::ostringstream sout; + details::MakeStringInternal(sout, args...); + return sout.str(); +} + +/** + * @brief special handle string + */ +template <> +inline std::string MakeString(std::string str) { + return str; +} + +/** + * @brief special handle const char* + */ +template <> +inline std::string MakeString(const char* str) { + return std::string(str); +} +} // namespace details + +// From https://stackoverflow.com/questions/30130930/ +// __buildin_expect is in C++ 11 standard. Since the condition which enforced +// should be true in most situation, it will make the compiler generate faster +// code by adding `UNLIKELY` macro. +#define UNLIKELY(condition) __builtin_expect(static_cast(condition), 0) + +/** + * @brief Throw a EnforceNotMet exception, automatically filled __FILE__ & + * __LINE__ + * + * This macro take __VA_ARGS__, user can pass any type if that type can + * serialize to std::ostream + */ +#define PADDLE_THROW(...) \ + do { \ + throw ::paddle::platform::EnforceNotMet( \ + ::paddle::platform::details::MakeString(__VA_ARGS__), __FILE__, \ + __LINE__); \ + } while (0) + +/** + * @brief Enforce a condition, otherwise throw an EnforceNotMet + */ +#define PADDLE_ENFORCE(condition, ...) \ + do { \ + if (UNLIKELY(!(condition))) { \ + PADDLE_THROW(__VA_ARGS__); \ + } \ + } while (0) + +} // namespace platform +} // namespace paddle diff --git a/paddle/platform/enforce_test.cc b/paddle/platform/enforce_test.cc new file mode 100644 index 00000000000..d3e945e972e --- /dev/null +++ b/paddle/platform/enforce_test.cc @@ -0,0 +1,25 @@ +#include +#include + +TEST(ENFORCE, OK) { + PADDLE_ENFORCE(true, "Enforce is ok", 123, "now", 0.345); + size_t val = 1; + const size_t limit = 10; + PADDLE_ENFORCE(val < limit, "Enforce is OK too"); +} + +TEST(ENFORCE, FAILED) { + bool in_catch = false; + try { + PADDLE_ENFORCE(false, "Enforce is not ok ", 123, " at all"); + } catch (paddle::platform::EnforceNotMet err) { + in_catch = true; + std::string msg = "Enforce is not ok 123 at all"; + const char* what = err.what(); + for (size_t i = 0; i < msg.length(); ++i) { + ASSERT_EQ(what[i], msg[i]); + } + } + + ASSERT_TRUE(in_catch); +} \ No newline at end of file -- GitLab From cdf8d99080c1c36c505cd5dbe7572fe2f71bac6d Mon Sep 17 00:00:00 2001 From: hedaoyuan Date: Wed, 28 Jun 2017 16:26:16 +0800 Subject: [PATCH 0878/3256] Bug fix. --- paddle/function/nnpack/NNPACKConvOp.cpp | 4 ++-- paddle/function/nnpack/NNPACKConvOpTest.cpp | 7 +++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/paddle/function/nnpack/NNPACKConvOp.cpp b/paddle/function/nnpack/NNPACKConvOp.cpp index 5e4de55469d..d75fab04033 100644 --- a/paddle/function/nnpack/NNPACKConvOp.cpp +++ b/paddle/function/nnpack/NNPACKConvOp.cpp @@ -74,9 +74,9 @@ public: virtual void check(const BufferArgs& inputs, const BufferArgs& outputs) override { - const TensorShape& output = inputs[0].shape(); + const TensorShape& input = inputs[0].shape(); const TensorShape& filter = inputs[1].shape(); - const TensorShape& input = outputs[0].shape(); + const TensorShape& output = outputs[0].shape(); checkShape(input, filter, output); } diff --git a/paddle/function/nnpack/NNPACKConvOpTest.cpp b/paddle/function/nnpack/NNPACKConvOpTest.cpp index e7ce61cc6c4..48180112111 100644 --- a/paddle/function/nnpack/NNPACKConvOpTest.cpp +++ b/paddle/function/nnpack/NNPACKConvOpTest.cpp @@ -60,12 +60,15 @@ public: << " outputWidth=" << outputSize << " stride=" << stride << " padding=" << padding; + std::vector paddings = {padding, padding}; + std::vector strides = {stride, stride}; Compare2Function test( conv1, conv2, FuncConfig() - .set("padding", padding) - .set("stride", stride) + .set("paddings", paddings) + .set("strides", strides) + .set("groups", (size_t)1) .set("algo", algo)); TensorShape shape0{ -- GitLab From 1678ad7b3067a8c72ac504fd8cb00e83766cbba2 Mon Sep 17 00:00:00 2001 From: qiaolongfei Date: Wed, 28 Jun 2017 16:33:43 +0800 Subject: [PATCH 0879/3256] add Create for scope --- paddle/framework/scope.h | 8 +++++++- paddle/framework/scope_test.cc | 8 ++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/paddle/framework/scope.h b/paddle/framework/scope.h index 2f8d6dbd976..bb22c4b834f 100644 --- a/paddle/framework/scope.h +++ b/paddle/framework/scope.h @@ -31,10 +31,16 @@ namespace framework { * scope. */ class Scope { - public: + private: explicit Scope(const std::shared_ptr& parent = nullptr) : parent_(parent) {} + public: + static std::shared_ptr Create( + const std::shared_ptr& parent = nullptr) { + return std::make_shared(Scope(parent)); + } + /// Create Variable in this Scope. Failed if Variable already been /// created. Variable* CreateVariable(const std::string& name) { diff --git a/paddle/framework/scope_test.cc b/paddle/framework/scope_test.cc index 34ee21e1aaa..d73391d9770 100644 --- a/paddle/framework/scope_test.cc +++ b/paddle/framework/scope_test.cc @@ -19,7 +19,7 @@ TEST(Scope, Create) { using paddle::framework::Scope; using paddle::framework::Variable; - Scope* scope = new Scope(); + auto scope = Scope::Create(); Variable* var0 = scope->CreateVariable(""); EXPECT_NE(var0, nullptr); @@ -42,10 +42,10 @@ TEST(Scope, Parent) { using paddle::framework::Scope; using paddle::framework::Variable; - const auto parent_scope_ptr = std::shared_ptr(new Scope()); - Scope* scope = new Scope(parent_scope_ptr); + auto parent_scope = Scope::Create(); + auto scope = Scope::Create(parent_scope); - Variable* var0 = parent_scope_ptr->CreateVariable("a"); + Variable* var0 = parent_scope->CreateVariable("a"); EXPECT_NE(var0, nullptr); Variable* var1 = scope->GetVariable("a"); -- GitLab From 42dcffc29c9a6d0cbd27cc5d32d10c53400d287d Mon Sep 17 00:00:00 2001 From: dongzhihong Date: Wed, 28 Jun 2017 17:19:31 +0800 Subject: [PATCH 0880/3256] "add optimizer full test" --- go/pserver/service_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go/pserver/service_test.go b/go/pserver/service_test.go index a09b25dec08..57397fe586e 100644 --- a/go/pserver/service_test.go +++ b/go/pserver/service_test.go @@ -10,7 +10,7 @@ import ( "github.com/PaddlePaddle/Paddle/go/pserver" ) -func TestNewName(t *testing.T) { +func TestServiceFull(t *testing.T) { s := pserver.NewService() var p pserver.Parameter p.Name = "param_a" -- GitLab From 6bb84b963d421394c04c43c202ccea874bdb53f6 Mon Sep 17 00:00:00 2001 From: dongzhihong Date: Wed, 28 Jun 2017 17:20:55 +0800 Subject: [PATCH 0881/3256] "remove unused debug info" --- go/pserver/optimizer_test.go | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/go/pserver/optimizer_test.go b/go/pserver/optimizer_test.go index 368047d6f89..49d9df5898d 100644 --- a/go/pserver/optimizer_test.go +++ b/go/pserver/optimizer_test.go @@ -2,6 +2,7 @@ package pserver import ( "io/ioutil" + "reflect" "testing" ) @@ -22,3 +23,26 @@ func TestOptimizerCreateRelease(t *testing.T) { o := newOptimizer(param) o.Cleanup() } + +func TestOptimizerFull(t *testing.T) { + p := Parameter{ + Name: "a", + ElementType: Float32, + } + p.Content = []byte{1, 3} + config, err := ioutil.ReadFile("./cclient/test/testdata/optimizer.pb.txt") + if err != nil { + t.Fatalf("read optimizer proto failed") + } + param := ParameterWithConfig{ + Param: p, + Config: config, + } + o := newOptimizer(param) + g := Gradient(p) + if !reflect.DeepEqual(p.Content, o.GetWeights()) { + t.FailNow() + } + o.UpdateParameter(g) + o.Cleanup() +} -- GitLab From d2581f34e8179bdd7e0b9ce8a9d3e847758ff52d Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Wed, 28 Jun 2017 17:48:20 +0800 Subject: [PATCH 0882/3256] change copy right format --- paddle/platform/enforce.h | 22 ++++++++++------------ paddle/platform/enforce_test.cc | 11 +++++++++++ 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/paddle/platform/enforce.h b/paddle/platform/enforce.h index e501e80c557..fbd3405a24f 100644 --- a/paddle/platform/enforce.h +++ b/paddle/platform/enforce.h @@ -1,15 +1,13 @@ -/* - 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. -*/ +/* 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 diff --git a/paddle/platform/enforce_test.cc b/paddle/platform/enforce_test.cc index d3e945e972e..23b32444add 100644 --- a/paddle/platform/enforce_test.cc +++ b/paddle/platform/enforce_test.cc @@ -1,3 +1,14 @@ +/* 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 -- GitLab From fc5972ba2c0c2565d4255fda19f1b68f02c18e62 Mon Sep 17 00:00:00 2001 From: wanghaoshuang Date: Wed, 28 Jun 2017 19:54:25 +0800 Subject: [PATCH 0883/3256] fix requirement config for flowers dataset --- python/setup.py.in | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/python/setup.py.in b/python/setup.py.in index 86fc0fc5c03..aa6771709ca 100644 --- a/python/setup.py.in +++ b/python/setup.py.in @@ -15,7 +15,8 @@ setup_requires=["requests", "protobuf==3.1", "recordio", "matplotlib", - "rarfile"] + "rarfile", + "scipy>=0.19.0"] if '${CMAKE_SYSTEM_PROCESSOR}' not in ['arm', 'armv7-a', 'aarch64']: setup_requires+=["opencv-python"] -- GitLab From 3919b75884749684e0bd8b502e426fa4949f2c1f Mon Sep 17 00:00:00 2001 From: gongweibao Date: Wed, 28 Jun 2017 12:01:32 +0000 Subject: [PATCH 0884/3256] modify cmake --- go/master/c/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go/master/c/CMakeLists.txt b/go/master/c/CMakeLists.txt index acce698051e..3eb598a877f 100644 --- a/go/master/c/CMakeLists.txt +++ b/go/master/c/CMakeLists.txt @@ -6,7 +6,7 @@ set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${PARENT_DIR}/cmake") project(cxx_go C Go) -include(golang) +#include(golang) include(flags) set(MASTER_LIB_NAME "paddle_master") -- GitLab From b93e863a1c5f31e9404dee8a2a6684119b876a2a Mon Sep 17 00:00:00 2001 From: dangqingqing Date: Wed, 28 Jun 2017 20:02:52 +0800 Subject: [PATCH 0885/3256] Fix bug in MultiGradientMachine. --- paddle/gserver/gradientmachines/MultiGradientMachine.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paddle/gserver/gradientmachines/MultiGradientMachine.cpp b/paddle/gserver/gradientmachines/MultiGradientMachine.cpp index 8ef5e9d0c11..018da6c76dc 100644 --- a/paddle/gserver/gradientmachines/MultiGradientMachine.cpp +++ b/paddle/gserver/gradientmachines/MultiGradientMachine.cpp @@ -601,7 +601,7 @@ void TrainerThread::backward() { void TrainerThread::backwardCallback(Parameter* para) { // CPU parameters are merged in the end - if (!para->useGpu()) return; + if (!para->useGpu() || para->isStatic()) return; int paramId = para->getID(); if (multiMachine_->getNumThreads() == 1) { -- GitLab From 9ad846ecee27ff1860debc4658090f1cfa75140f Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Wed, 28 Jun 2017 21:20:33 +0800 Subject: [PATCH 0886/3256] Remove must_check in paddle::platform --- paddle/platform/CMakeLists.txt | 1 - paddle/platform/must_check.h | 26 -------------------------- paddle/platform/must_check_test.cc | 10 ---------- paddle/utils/Error.h | 16 +++++++++++++++- 4 files changed, 15 insertions(+), 38 deletions(-) delete mode 100644 paddle/platform/must_check.h delete mode 100644 paddle/platform/must_check_test.cc diff --git a/paddle/platform/CMakeLists.txt b/paddle/platform/CMakeLists.txt index 7abe2ab89e0..c7d7b14518e 100644 --- a/paddle/platform/CMakeLists.txt +++ b/paddle/platform/CMakeLists.txt @@ -2,4 +2,3 @@ nv_test(cuda_test SRCS cuda_test.cu) cc_library(place SRCS place.cc) cc_test(place_test SRCS place_test.cc DEPS place glog gflags) -cc_test(must_check_test SRCS must_check_test.cc) diff --git a/paddle/platform/must_check.h b/paddle/platform/must_check.h deleted file mode 100644 index 4fcc62afc05..00000000000 --- a/paddle/platform/must_check.h +++ /dev/null @@ -1,26 +0,0 @@ -/* Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved. -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#pragma once -/** - * __must_check macro. It make the function's return value must be used, - * otherwise it will raise a compile warning. And also Paddle treat all compile - * warnings as errors. - */ -#ifdef __GNUC__ -#if (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) >= 30400 -#define __must_check __attribute__((warn_unused_result)) -#else -#define __must_check -#endif -#else -#define __must_check -#endif diff --git a/paddle/platform/must_check_test.cc b/paddle/platform/must_check_test.cc deleted file mode 100644 index 6ee3ea49acd..00000000000 --- a/paddle/platform/must_check_test.cc +++ /dev/null @@ -1,10 +0,0 @@ -#include -#include - -int __must_check SomeFunctionMustCheck() { return 0; } - -TEST(MustCheck, all) { - // This line should not be compiled, because the - // return value of SomeFunctionMustCheck marked as __must_check - // SomeFunctionMustCheck(); -} \ No newline at end of file diff --git a/paddle/utils/Error.h b/paddle/utils/Error.h index f3d535c69c5..27ddaab3f00 100644 --- a/paddle/utils/Error.h +++ b/paddle/utils/Error.h @@ -19,7 +19,21 @@ limitations under the License. */ #include #include #include -#include "paddle/platform/must_check.h" + +/** + * __must_check macro. It make the function's return value must be used, + * otherwise it will raise a compile warning. And also Paddle treat all compile + * warnings as errors. + */ +#ifdef __GNUC__ +#if (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) >= 30400 +#define __must_check __attribute__((warn_unused_result)) +#else +#define __must_check +#endif +#else +#define __must_check +#endif namespace paddle { -- GitLab From b1a311c44d9554a1710d26c78f487f9786dd1934 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Wed, 28 Jun 2017 21:48:40 +0800 Subject: [PATCH 0887/3256] Add pb_cc_library in generic.cmake Fix #2567 --- cmake/external/protobuf.cmake | 8 ++++++-- cmake/generic.cmake | 33 +++++++++++++++++++++++++++++++++ paddle/api/CMakeLists.txt | 2 +- paddle/capi/CMakeLists.txt | 2 +- paddle/function/CMakeLists.txt | 2 +- paddle/gserver/CMakeLists.txt | 2 +- paddle/math/CMakeLists.txt | 2 +- paddle/optimizer/CMakeLists.txt | 2 +- paddle/parameter/CMakeLists.txt | 2 +- paddle/pserver/CMakeLists.txt | 4 ++-- paddle/testing/CMakeLists.txt | 4 ++-- paddle/trainer/CMakeLists.txt | 2 +- paddle/utils/CMakeLists.txt | 2 +- proto/CMakeLists.txt | 16 +--------------- 14 files changed, 53 insertions(+), 30 deletions(-) diff --git a/cmake/external/protobuf.cmake b/cmake/external/protobuf.cmake index d43badc1da5..891fb291187 100644 --- a/cmake/external/protobuf.cmake +++ b/cmake/external/protobuf.cmake @@ -39,12 +39,16 @@ macro(PROMPT_PROTOBUF_LIB) ADD_LIBRARY(protobuf_lite ${protobuf_LIBTYPE} IMPORTED GLOBAL) SET_PROPERTY(TARGET protobuf_lite PROPERTY IMPORTED_LOCATION ${PROTOBUF_LITE_LIBRARY}) - ADD_LIBRARY(protoc ${protobuf_LIBTYPE} IMPORTED GLOBAL) - SET_PROPERTY(TARGET protoc PROPERTY IMPORTED_LOCATION ${PROTOC_LIBRARY}) + ADD_LIBRARY(libprotoc ${protobuf_LIBTYPE} IMPORTED GLOBAL) + SET_PROPERTY(TARGET libprotoc PROPERTY IMPORTED_LOCATION ${PROTOC_LIBRARY}) + + ADD_EXECUTABLE(protoc IMPORTED GLOBAL) + SET_PROPERTY(TARGET protoc PROPERTY IMPORTED_LOCATION ${PROTOBUF_PROTOC_EXECUTABLE}) FOREACH(dep ${protobuf_DEPS}) ADD_DEPENDENCIES(protobuf ${dep}) ADD_DEPENDENCIES(protobuf_lite ${dep}) + ADD_DEPENDENCIES(libprotoc ${dep}) ADD_DEPENDENCIES(protoc ${dep}) ENDFOREACH() diff --git a/cmake/generic.cmake b/cmake/generic.cmake index 11c1f677ae5..0370ab31f34 100644 --- a/cmake/generic.cmake +++ b/cmake/generic.cmake @@ -331,3 +331,36 @@ function(go_test TARGET_NAME) add_custom_target(${TARGET_NAME} ALL DEPENDS ${TARGET_NAME}_timestamp ${go_test_DEPS}) add_test(${TARGET_NAME} ${CMAKE_CURRENT_BINARY_DIR}/${TARGET_NAME}) endfunction(go_test) + +function(pb_cc_library TARGET_NAME) + set(oneValueArgs "") + set(multiValueArgs SRCS) + cmake_parse_arguments(pb_cc_library "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + set(proto_srcs) + set(proto_hdrs) + foreach(FIL ${pb_cc_library_SRCS}) + get_filename_component(ABS_FIL ${FIL} ABSOLUTE) + get_filename_component(FIL_WE ${FIL} NAME_WE) + if(NOT PROTOBUF_GENERATE_CPP_APPEND_PATH) + get_filename_component(FIL_DIR ${FIL} DIRECTORY) + if(FIL_DIR) + set(FIL_WE "${FIL_DIR}/${FIL_WE}") + endif() + endif() + + list(APPEND proto_srcs "${CMAKE_CURRENT_BINARY_DIR}/${FIL_WE}.pb.cc") + list(APPEND proto_hdrs "${CMAKE_CURRENT_BINARY_DIR}/${FIL_WE}.pb.h") + + add_custom_command( + OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${FIL_WE}.pb.cc" + "${CMAKE_CURRENT_BINARY_DIR}/${FIL_WE}.pb.h" + COMMAND ${PROTOBUF_PROTOC_EXECUTABLE} + ARGS "--cpp_out=${DLL_EXPORT_DECL}${CMAKE_CURRENT_BINARY_DIR}" "-I" ${CMAKE_CURRENT_SOURCE_DIR} ${ABS_FIL} + DEPENDS ${ABS_FIL} protoc + COMMENT "Running C++ protocol buffer compiler on ${FIL}" + VERBATIM ) + endforeach() + set_source_files_properties(${proto_srcs} ${proto_hdrs} PROPERTIES GENERATED TRUE) + include_directories(${CMAKE_CURRENT_BINARY_DIR}) + cc_library(${TARGET_NAME} SRCS ${proto_srcs}) +endfunction() \ No newline at end of file diff --git a/paddle/api/CMakeLists.txt b/paddle/api/CMakeLists.txt index f2315e31cc0..39d8aa075bc 100644 --- a/paddle/api/CMakeLists.txt +++ b/paddle/api/CMakeLists.txt @@ -16,7 +16,7 @@ set(API_HEADER Internal.h) add_library(paddle_api STATIC ${API_SOURCES}) -add_dependencies(paddle_api gen_proto_cpp paddle_trainer_lib) +add_dependencies(paddle_api paddle_proto paddle_trainer_lib) INCLUDE(${SWIG_USE_FILE}) INCLUDE_DIRECTORIES(${PROJ_ROOT}/paddle) diff --git a/paddle/capi/CMakeLists.txt b/paddle/capi/CMakeLists.txt index 206f5125634..11022d17541 100644 --- a/paddle/capi/CMakeLists.txt +++ b/paddle/capi/CMakeLists.txt @@ -26,7 +26,7 @@ target_include_directories(paddle_capi PUBLIC ${CMAKE_CURRENT_BINARY_DIR}) add_style_check_target(paddle_capi ${CAPI_SOURCES} ${CAPI_HEADER} ${CAPI_PRIVATE_HEADER}) -add_dependencies(paddle_capi gen_proto_cpp) +add_dependencies(paddle_capi paddle_proto) # combine all paddle static libraries together, into libpaddle_capi_whole.a diff --git a/paddle/function/CMakeLists.txt b/paddle/function/CMakeLists.txt index 5e170714cf5..1c39ced3c9e 100644 --- a/paddle/function/CMakeLists.txt +++ b/paddle/function/CMakeLists.txt @@ -12,7 +12,7 @@ endif() add_library(paddle_function STATIC ${cpp_files} ${cu_objs}) add_dependencies(paddle_function ${external_project_dependencies}) -add_dependencies(paddle_function gen_proto_cpp) +add_dependencies(paddle_function paddle_proto) if(WITH_TESTING) if(WITH_GPU) diff --git a/paddle/gserver/CMakeLists.txt b/paddle/gserver/CMakeLists.txt index 93a6a99848a..3bd583773ad 100644 --- a/paddle/gserver/CMakeLists.txt +++ b/paddle/gserver/CMakeLists.txt @@ -58,7 +58,7 @@ endif() add_style_check_target(paddle_gserver ${GSERVER_SOURCES}) add_style_check_target(paddle_gserver ${GSERVER_HEADER}) -add_dependencies(paddle_gserver gen_proto_cpp) +add_dependencies(paddle_gserver paddle_proto) if(WITH_TESTING) add_subdirectory(tests) endif() diff --git a/paddle/math/CMakeLists.txt b/paddle/math/CMakeLists.txt index f5657c4690c..326cdb156c3 100644 --- a/paddle/math/CMakeLists.txt +++ b/paddle/math/CMakeLists.txt @@ -33,7 +33,7 @@ endif() add_style_check_target(paddle_math ${MATH_SOURCES}) add_style_check_target(paddle_math ${MATH_HEADERS}) -add_dependencies(paddle_math gen_proto_cpp) # depends +add_dependencies(paddle_math paddle_proto) # depends if(WITH_TESTING) add_subdirectory(tests) endif() diff --git a/paddle/optimizer/CMakeLists.txt b/paddle/optimizer/CMakeLists.txt index 4536f62ec7c..bf878baaf0c 100644 --- a/paddle/optimizer/CMakeLists.txt +++ b/paddle/optimizer/CMakeLists.txt @@ -10,7 +10,7 @@ set(OPITMIZER_SRCS ) add_library(paddle_optimizer STATIC ${OPITMIZER_SRCS}) -add_dependencies(paddle_optimizer gen_proto_cpp) +add_dependencies(paddle_optimizer paddle_proto) if(WITH_TESTING) add_simple_unittest(serialization_test) diff --git a/paddle/parameter/CMakeLists.txt b/paddle/parameter/CMakeLists.txt index a35e46997fb..a9e344afdcc 100644 --- a/paddle/parameter/CMakeLists.txt +++ b/paddle/parameter/CMakeLists.txt @@ -7,7 +7,7 @@ add_library(paddle_parameter STATIC ${PARAMETERS_SOURCES}) add_style_check_target(paddle_parameter ${PARAMETERS_SOURCES}) add_style_check_target(paddle_parameter ${PARAMETERS_HEADERS}) -add_dependencies(paddle_parameter gen_proto_cpp) +add_dependencies(paddle_parameter paddle_proto) if(WITH_TESTING) add_subdirectory(tests) endif() diff --git a/paddle/pserver/CMakeLists.txt b/paddle/pserver/CMakeLists.txt index b7f85ea1a6d..92dd286f04f 100644 --- a/paddle/pserver/CMakeLists.txt +++ b/paddle/pserver/CMakeLists.txt @@ -17,7 +17,7 @@ add_library(paddle_network STATIC add_style_check_target(paddle_network ${NETWORK_SOURCES}) add_style_check_target(paddle_network ${NETWORK_HEADERS}) -add_dependencies(paddle_network gen_proto_cpp) +add_dependencies(paddle_network paddle_proto) ################### paddle_pserver ###################### set(PSERVER_SOURCES @@ -40,7 +40,7 @@ add_library(paddle_pserver STATIC add_style_check_target(paddle_pserver ${PSERVER_SOURCES}) add_style_check_target(paddle_pserver ${PSERVER_HEADERS}) -add_dependencies(paddle_pserver gen_proto_cpp) +add_dependencies(paddle_pserver paddle_proto) set(PSERVER_MAIN_SOURCES ParameterServer2Main.cpp) diff --git a/paddle/testing/CMakeLists.txt b/paddle/testing/CMakeLists.txt index c47add04b08..4aa6eae681c 100644 --- a/paddle/testing/CMakeLists.txt +++ b/paddle/testing/CMakeLists.txt @@ -2,7 +2,7 @@ if(WITH_TESTING) add_library(paddle_test_main STATIC TestMain.cpp) - add_dependencies(paddle_test_main gen_proto_cpp) + add_dependencies(paddle_test_main paddle_proto) add_library(paddle_test_util STATIC TestUtil.cpp) - add_dependencies(paddle_test_util gen_proto_cpp) + add_dependencies(paddle_test_util paddle_proto) endif() diff --git a/paddle/trainer/CMakeLists.txt b/paddle/trainer/CMakeLists.txt index f34d53ae99f..b8f03fa7e73 100644 --- a/paddle/trainer/CMakeLists.txt +++ b/paddle/trainer/CMakeLists.txt @@ -41,7 +41,7 @@ add_style_check_target(paddle_trainer_lib add_style_check_target(paddle_trainer_lib ${TRAINER_HEADERS}) add_dependencies(paddle_trainer_lib - gen_proto_cpp) + paddle_proto) macro(add_paddle_exe TARGET_NAME) add_executable(${TARGET_NAME} ${ARGN}) diff --git a/paddle/utils/CMakeLists.txt b/paddle/utils/CMakeLists.txt index af59951752d..f5c399256ac 100644 --- a/paddle/utils/CMakeLists.txt +++ b/paddle/utils/CMakeLists.txt @@ -17,7 +17,7 @@ add_library(paddle_utils STATIC add_style_check_target(paddle_utils ${UTIL_HEADERS}) add_style_check_target(paddle_utils ${UTIL_SOURCES} ${UTIL_ARCH_SOURCES}) -add_dependencies(paddle_utils gen_proto_cpp) +add_dependencies(paddle_utils paddle_proto) if(WITH_TESTING) add_subdirectory(tests) endif() diff --git a/proto/CMakeLists.txt b/proto/CMakeLists.txt index c9426209907..948d7db6b25 100644 --- a/proto/CMakeLists.txt +++ b/proto/CMakeLists.txt @@ -13,18 +13,6 @@ set(PROTO_GEN_PY) foreach(filename ${proto_filenames}) get_filename_component(base_filename ${filename} NAME_WE) - set(CUR_PROTO_GEN - ${CMAKE_CURRENT_BINARY_DIR}/${base_filename}.pb.h - ${CMAKE_CURRENT_BINARY_DIR}/${base_filename}.pb.cc) - set(PROTO_GEN - ${PROTO_GEN} - ${CUR_PROTO_GEN}) - add_custom_command(OUTPUT ${CUR_PROTO_GEN} - COMMAND env ${py_env} ${PROTOBUF_PROTOC_EXECUTABLE} - --cpp_out ${CMAKE_CURRENT_BINARY_DIR} - --proto_path ${PROJ_ROOT}/proto ${PROJ_ROOT}/proto/${filename} - DEPENDS ${filename} ${external_project_dependencies}) - set(CUR_PROTO_GEN_PY ${PROJ_ROOT}/paddle/python/paddle/proto/${base_filename}_pb2.py) set(PROTO_GEN_PY @@ -36,8 +24,6 @@ foreach(filename ${proto_filenames}) DEPENDS ${filename} ${external_project_dependencies}) endforeach() -add_custom_target(gen_proto_cpp ALL DEPENDS ${PROTO_GEN}) add_custom_target(gen_proto_py ALL DEPENDS ${PROTO_GEN_PY}) -add_library(paddle_proto STATIC ${PROTO_GEN}) -target_include_directories(paddle_proto PUBLIC ${CMAKE_CURRENT_BINARY_DIR}) +pb_cc_library(paddle_proto SRCS ${proto_filenames}) -- GitLab From 30b75a51035dee978225f5e5eff2c0d4b9c09aec Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Wed, 28 Jun 2017 22:05:04 +0800 Subject: [PATCH 0888/3256] Also add pb_py_library --- cmake/generic.cmake | 31 ++++++++++++++++++++++++------- proto/CMakeLists.txt | 30 ++---------------------------- 2 files changed, 26 insertions(+), 35 deletions(-) diff --git a/cmake/generic.cmake b/cmake/generic.cmake index 0370ab31f34..14b69098297 100644 --- a/cmake/generic.cmake +++ b/cmake/generic.cmake @@ -341,13 +341,6 @@ function(pb_cc_library TARGET_NAME) foreach(FIL ${pb_cc_library_SRCS}) get_filename_component(ABS_FIL ${FIL} ABSOLUTE) get_filename_component(FIL_WE ${FIL} NAME_WE) - if(NOT PROTOBUF_GENERATE_CPP_APPEND_PATH) - get_filename_component(FIL_DIR ${FIL} DIRECTORY) - if(FIL_DIR) - set(FIL_WE "${FIL_DIR}/${FIL_WE}") - endif() - endif() - list(APPEND proto_srcs "${CMAKE_CURRENT_BINARY_DIR}/${FIL_WE}.pb.cc") list(APPEND proto_hdrs "${CMAKE_CURRENT_BINARY_DIR}/${FIL_WE}.pb.h") @@ -363,4 +356,28 @@ function(pb_cc_library TARGET_NAME) set_source_files_properties(${proto_srcs} ${proto_hdrs} PROPERTIES GENERATED TRUE) include_directories(${CMAKE_CURRENT_BINARY_DIR}) cc_library(${TARGET_NAME} SRCS ${proto_srcs}) +endfunction() + +function(pb_py_library TARGET_NAME) + set(oneValueArgs TARGET_DIR) + set(multiValueArgs SRCS) + cmake_parse_arguments(pb_py_library "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + if (NOT ${pb_py_library_TARGET_DIR}) + set(pb_py_library_TARGET_DIR ${CMAKE_CURRENT_BINARY_DIR}) + endif() + + set(py_srcs) + foreach(FIL ${pb_py_library_SRCS}) + get_filename_component(ABS_FIL ${FIL} ABSOLUTE) + get_filename_component(FIL_WE ${FIL} NAME_WE) + set(cur_py_src ${pb_py_library_TARGET_DIR}/${FIL_WE}_pb2.py) + list(APPEND py_srcs "${cur_py_src}") + add_custom_command(OUTPUT ${cur_py_src} + COMMAND ${PROTOBUF_PROTOC_EXECUTABLE} + ARGS "--python_out=${pb_py_library_TARGET_DIR}" "-I" ${CMAKE_CURRENT_SOURCE_DIR} ${ABS_FIL} + DEPENDS ${ABS_FIL} protoc + COMMENT "Running Python protocol buffer compiler on ${FIL}") + endforeach() + + add_custom_target(${TARGET_NAME} ALL DEPENDS ${py_srcs}) endfunction() \ No newline at end of file diff --git a/proto/CMakeLists.txt b/proto/CMakeLists.txt index 948d7db6b25..9b29d43d73c 100644 --- a/proto/CMakeLists.txt +++ b/proto/CMakeLists.txt @@ -1,29 +1,3 @@ -set(proto_filenames - DataConfig.proto - DataFormat.proto - ModelConfig.proto - ParameterConfig.proto - ParameterService.proto - TrainerConfig.proto - OptimizerConfig.proto - ParameterServerConfig.proto) - -set(PROTO_GEN) -set(PROTO_GEN_PY) - -foreach(filename ${proto_filenames}) - get_filename_component(base_filename ${filename} NAME_WE) - set(CUR_PROTO_GEN_PY - ${PROJ_ROOT}/paddle/python/paddle/proto/${base_filename}_pb2.py) - set(PROTO_GEN_PY - ${CUR_PROTO_GEN_PY} - ${PROTO_GEN_PY}) - add_custom_command(OUTPUT ${CUR_PROTO_GEN_PY} - COMMAND env ${py_env} ${PROTOBUF_PROTOC_EXECUTABLE} --python_out ${PROJ_ROOT}/python/paddle/proto - --proto_path ${PROJ_ROOT}/proto ${PROJ_ROOT}/proto/${filename} - DEPENDS ${filename} ${external_project_dependencies}) -endforeach() - -add_custom_target(gen_proto_py ALL DEPENDS ${PROTO_GEN_PY}) - +file(GLOB proto_filenames . *.proto) pb_cc_library(paddle_proto SRCS ${proto_filenames}) +pb_py_library(gen_proto_py SRCS ${proto_filenames} TARGET_DIR ${CMAKE_CURRENT_SOURCE_DIR}) -- GitLab From 64b78b1656bd023e916447e7ea6c08de3d5c1f88 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Wed, 28 Jun 2017 22:27:50 +0800 Subject: [PATCH 0889/3256] Fix TravisCI --- cmake/generic.cmake | 3 +-- doc/CMakeLists.txt | 7 ------- paddle/gserver/CMakeLists.txt | 2 +- paddle/math/CMakeLists.txt | 2 +- paddle/optimizer/CMakeLists.txt | 2 +- paddle/parameter/CMakeLists.txt | 2 +- paddle/pserver/CMakeLists.txt | 2 +- paddle/trainer/CMakeLists.txt | 3 ++- paddle/utils/CMakeLists.txt | 2 +- proto/CMakeLists.txt | 2 +- 10 files changed, 10 insertions(+), 17 deletions(-) diff --git a/cmake/generic.cmake b/cmake/generic.cmake index 14b69098297..24a07c0a241 100644 --- a/cmake/generic.cmake +++ b/cmake/generic.cmake @@ -362,10 +362,9 @@ function(pb_py_library TARGET_NAME) set(oneValueArgs TARGET_DIR) set(multiValueArgs SRCS) cmake_parse_arguments(pb_py_library "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) - if (NOT ${pb_py_library_TARGET_DIR}) + if (NOT pb_py_library_TARGET_DIR) set(pb_py_library_TARGET_DIR ${CMAKE_CURRENT_BINARY_DIR}) endif() - set(py_srcs) foreach(FIL ${pb_py_library_SRCS}) get_filename_component(ABS_FIL ${FIL} ABSOLUTE) diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt index 6fa42fd0c71..94dd3457fb5 100644 --- a/doc/CMakeLists.txt +++ b/doc/CMakeLists.txt @@ -27,10 +27,6 @@ sphinx_add_target(paddle_docs ${CMAKE_CURRENT_SOURCE_DIR} ${SPHINX_HTML_DIR_EN}) -add_dependencies(paddle_docs - gen_proto_py) - - # configured documentation tools and intermediate build results set(BINARY_BUILD_DIR_CN "${CMAKE_CURRENT_BINARY_DIR}/cn/_build") @@ -51,6 +47,3 @@ sphinx_add_target(paddle_docs_cn ${SPHINX_CACHE_DIR_CN} ${CMAKE_CURRENT_SOURCE_DIR} ${SPHINX_HTML_DIR_CN}) - -add_dependencies(paddle_docs_cn - gen_proto_py) diff --git a/paddle/gserver/CMakeLists.txt b/paddle/gserver/CMakeLists.txt index 3bd583773ad..0012636b8f6 100644 --- a/paddle/gserver/CMakeLists.txt +++ b/paddle/gserver/CMakeLists.txt @@ -58,7 +58,7 @@ endif() add_style_check_target(paddle_gserver ${GSERVER_SOURCES}) add_style_check_target(paddle_gserver ${GSERVER_HEADER}) -add_dependencies(paddle_gserver paddle_proto) +add_dependencies(paddle_gserver paddle_proto ${external_project_dependencies}) if(WITH_TESTING) add_subdirectory(tests) endif() diff --git a/paddle/math/CMakeLists.txt b/paddle/math/CMakeLists.txt index 326cdb156c3..9981de61606 100644 --- a/paddle/math/CMakeLists.txt +++ b/paddle/math/CMakeLists.txt @@ -33,7 +33,7 @@ endif() add_style_check_target(paddle_math ${MATH_SOURCES}) add_style_check_target(paddle_math ${MATH_HEADERS}) -add_dependencies(paddle_math paddle_proto) # depends +add_dependencies(paddle_math paddle_proto ${external_project_dependencies}) # depends if(WITH_TESTING) add_subdirectory(tests) endif() diff --git a/paddle/optimizer/CMakeLists.txt b/paddle/optimizer/CMakeLists.txt index bf878baaf0c..9996d01d18b 100644 --- a/paddle/optimizer/CMakeLists.txt +++ b/paddle/optimizer/CMakeLists.txt @@ -10,7 +10,7 @@ set(OPITMIZER_SRCS ) add_library(paddle_optimizer STATIC ${OPITMIZER_SRCS}) -add_dependencies(paddle_optimizer paddle_proto) +add_dependencies(paddle_optimizer paddle_proto ${external_project_dependencies}) if(WITH_TESTING) add_simple_unittest(serialization_test) diff --git a/paddle/parameter/CMakeLists.txt b/paddle/parameter/CMakeLists.txt index a9e344afdcc..d2ae1c16c6b 100644 --- a/paddle/parameter/CMakeLists.txt +++ b/paddle/parameter/CMakeLists.txt @@ -7,7 +7,7 @@ add_library(paddle_parameter STATIC ${PARAMETERS_SOURCES}) add_style_check_target(paddle_parameter ${PARAMETERS_SOURCES}) add_style_check_target(paddle_parameter ${PARAMETERS_HEADERS}) -add_dependencies(paddle_parameter paddle_proto) +add_dependencies(paddle_parameter paddle_proto ${external_project_dependencies}) if(WITH_TESTING) add_subdirectory(tests) endif() diff --git a/paddle/pserver/CMakeLists.txt b/paddle/pserver/CMakeLists.txt index 92dd286f04f..f2e0b4b76bc 100644 --- a/paddle/pserver/CMakeLists.txt +++ b/paddle/pserver/CMakeLists.txt @@ -40,7 +40,7 @@ add_library(paddle_pserver STATIC add_style_check_target(paddle_pserver ${PSERVER_SOURCES}) add_style_check_target(paddle_pserver ${PSERVER_HEADERS}) -add_dependencies(paddle_pserver paddle_proto) +add_dependencies(paddle_pserver paddle_proto ${external_project_dependencies}) set(PSERVER_MAIN_SOURCES ParameterServer2Main.cpp) diff --git a/paddle/trainer/CMakeLists.txt b/paddle/trainer/CMakeLists.txt index b8f03fa7e73..6414c399561 100644 --- a/paddle/trainer/CMakeLists.txt +++ b/paddle/trainer/CMakeLists.txt @@ -41,7 +41,8 @@ add_style_check_target(paddle_trainer_lib add_style_check_target(paddle_trainer_lib ${TRAINER_HEADERS}) add_dependencies(paddle_trainer_lib - paddle_proto) + paddle_proto + ${external_project_dependencies}) macro(add_paddle_exe TARGET_NAME) add_executable(${TARGET_NAME} ${ARGN}) diff --git a/paddle/utils/CMakeLists.txt b/paddle/utils/CMakeLists.txt index f5c399256ac..7a4977935ed 100644 --- a/paddle/utils/CMakeLists.txt +++ b/paddle/utils/CMakeLists.txt @@ -17,7 +17,7 @@ add_library(paddle_utils STATIC add_style_check_target(paddle_utils ${UTIL_HEADERS}) add_style_check_target(paddle_utils ${UTIL_SOURCES} ${UTIL_ARCH_SOURCES}) -add_dependencies(paddle_utils paddle_proto) +add_dependencies(paddle_utils paddle_proto ${external_project_dependencies}) if(WITH_TESTING) add_subdirectory(tests) endif() diff --git a/proto/CMakeLists.txt b/proto/CMakeLists.txt index 9b29d43d73c..4402f2c8994 100644 --- a/proto/CMakeLists.txt +++ b/proto/CMakeLists.txt @@ -1,3 +1,3 @@ file(GLOB proto_filenames . *.proto) pb_cc_library(paddle_proto SRCS ${proto_filenames}) -pb_py_library(gen_proto_py SRCS ${proto_filenames} TARGET_DIR ${CMAKE_CURRENT_SOURCE_DIR}) +pb_py_library(gen_proto_py SRCS ${proto_filenames} TARGET_DIR ${PROJ_ROOT}/python/paddle/proto) -- GitLab From 44e39246639fe5b3ba1dbf5158531f7eb4fc6175 Mon Sep 17 00:00:00 2001 From: dongzhihong Date: Wed, 28 Jun 2017 23:04:35 +0800 Subject: [PATCH 0890/3256] "fix client send empty gradients bug" --- go/pserver/client.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/go/pserver/client.go b/go/pserver/client.go index dda91597728..a8d2d710d82 100644 --- a/go/pserver/client.go +++ b/go/pserver/client.go @@ -123,6 +123,10 @@ func (c *Client) FinishInitParams() error { // SendGrads sends gradients to parameter servers for updating // parameters. func (c *Client) SendGrads(grads []Gradient) error { + if len(grads) == 0 { + log.Info("Send Empty Gradient") + return nil + } errCh := make(chan error, len(grads)) for _, g := range grads { go func(g Gradient) { -- GitLab From 01f44bff669442ffdb67a5baac14aa693cba08c6 Mon Sep 17 00:00:00 2001 From: wanghaoshuang Date: Wed, 28 Jun 2017 23:12:19 +0800 Subject: [PATCH 0891/3256] rename args and add comments 1. rename 'useXmap' to 'use_xmap' 2. add comments about exchanging train data and test data --- python/paddle/v2/dataset/flowers.py | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/python/paddle/v2/dataset/flowers.py b/python/paddle/v2/dataset/flowers.py index a181f3881a6..158cfe158c4 100644 --- a/python/paddle/v2/dataset/flowers.py +++ b/python/paddle/v2/dataset/flowers.py @@ -46,6 +46,12 @@ SETID_URL = 'http://www.robots.ox.ac.uk/~vgg/data/flowers/102/setid.mat' DATA_MD5 = '52808999861908f626f3c1f4e79d11fa' LABEL_MD5 = 'e0620be6f572b9609742df49c70aed4d' SETID_MD5 = 'a5357ecc9cb78c4bef273ce3793fc85c' +# In official 'readme', tstid is the flag of test data +# and trnid is the flag of train data. But test data is more than train data. +# So we exchange the train data and test data. +TRAIN_FLAG = 'tstid' +TEST_FLAG = 'trnid' +VALID_FLAG = 'valid' def default_mapper(sample): @@ -64,7 +70,7 @@ def reader_creator(data_file, dataset_name, mapper=default_mapper, buffered_size=1024, - useXmap=True): + use_xmap=True): ''' 1. read images from tar file and merge images into batch files in 102flowers.tgz_batch/ @@ -106,13 +112,13 @@ def reader_creator(data_file, for sample, label in itertools.izip(data, batch['label']): yield sample, int(label) - if useXmap: + if use_xmap: return xmap_readers(mapper, reader, cpu_count(), buffered_size) else: return map_readers(mapper, reader) -def train(mapper=default_mapper, buffered_size=1024, useXmap=True): +def train(mapper=default_mapper, buffered_size=1024, use_xmap=True): ''' Create flowers training set reader. It returns a reader, each sample in the reader is @@ -131,11 +137,11 @@ def train(mapper=default_mapper, buffered_size=1024, useXmap=True): return reader_creator( download(DATA_URL, 'flowers', DATA_MD5), download(LABEL_URL, 'flowers', LABEL_MD5), - download(SETID_URL, 'flowers', SETID_MD5), 'tstid', mapper, - buffered_size, useXmap) + download(SETID_URL, 'flowers', SETID_MD5), TRAIN_FLAG, mapper, + buffered_size, use_xmap) -def test(mapper=default_mapper, buffered_size=1024, useXmap=True): +def test(mapper=default_mapper, buffered_size=1024, use_xmap=True): ''' Create flowers test set reader. It returns a reader, each sample in the reader is @@ -154,11 +160,11 @@ def test(mapper=default_mapper, buffered_size=1024, useXmap=True): return reader_creator( download(DATA_URL, 'flowers', DATA_MD5), download(LABEL_URL, 'flowers', LABEL_MD5), - download(SETID_URL, 'flowers', SETID_MD5), 'trnid', mapper, - buffered_size, useXmap) + download(SETID_URL, 'flowers', SETID_MD5), TEST_FLAG, mapper, + buffered_size, use_xmap) -def valid(mapper=default_mapper, buffered_size=1024, useXmap=True): +def valid(mapper=default_mapper, buffered_size=1024, use_xmap=True): ''' Create flowers validation set reader. It returns a reader, each sample in the reader is @@ -177,8 +183,8 @@ def valid(mapper=default_mapper, buffered_size=1024, useXmap=True): return reader_creator( download(DATA_URL, 'flowers', DATA_MD5), download(LABEL_URL, 'flowers', LABEL_MD5), - download(SETID_URL, 'flowers', SETID_MD5), 'valid', mapper, - buffered_size, useXmap) + download(SETID_URL, 'flowers', SETID_MD5), VALID_FLAG, mapper, + buffered_size, use_xmap) def fetch(): -- GitLab From c9865824a718e8361941f669e4ca879be6c24bcb Mon Sep 17 00:00:00 2001 From: dangqingqing Date: Thu, 29 Jun 2017 01:10:30 +0800 Subject: [PATCH 0892/3256] Support to init partial network parameters from the tar file. --- python/paddle/v2/parameters.py | 23 +++++---- python/paddle/v2/tests/test_parameters.py | 57 +++++++++++++++++++++-- 2 files changed, 65 insertions(+), 15 deletions(-) diff --git a/python/paddle/v2/parameters.py b/python/paddle/v2/parameters.py index ad20241b983..f730ea10bb5 100644 --- a/python/paddle/v2/parameters.py +++ b/python/paddle/v2/parameters.py @@ -51,7 +51,7 @@ class Parameters(object): def __init__(self): self.__param_conf__ = dict() self.__gradient_machines__ = [] - self.__tmp_params__ = [] + self.__tmp_params__ = dict() def __append_config__(self, param_conf): """ @@ -128,13 +128,10 @@ class Parameters(object): if len(self.__gradient_machines__) == 0: # create new parameter in python numpy. - if len(self.__tmp_params__) != 0: - ret_list = [ - mat for name, mat in self.__tmp_params__ if name == key - ] - if len(ret_list) == 1: - return ret_list[0] - return np.ndarray(shape=shape, dtype=np.float32) + if key in self.__tmp_params__: + return self.__tmp_params__[key] + else: + return np.ndarray(shape=shape, dtype=np.float32) else: for each_gradient_machine in self.__gradient_machines__: param = __get_parameter_in_gradient_machine__( @@ -187,7 +184,7 @@ class Parameters(object): (shape, value.shape)) if len(self.__gradient_machines__) == 0: - self.__tmp_params__.append((key, value)) + self.__tmp_params__[key] = value else: for each_gradient_machine in self.__gradient_machines__: __copy_parameter_to_gradient_machine__(each_gradient_machine, @@ -231,7 +228,7 @@ class Parameters(object): raise ValueError("gradient_machine should be api.GradientMachine") if len(self.__tmp_params__) != 0: - for name, val in self.__tmp_params__: + for name, val in self.__tmp_params__.iteritems(): try: __copy_parameter_to_gradient_machine__(gradient_machine, name, val) @@ -302,6 +299,12 @@ class Parameters(object): params.deserialize(param_name, f) return params + def init_from_tar(self, f): + tar_param = self.from_tar(f) + for pname in tar_param.names(): + if pname in self.names(): + self.set(pname, tar_param.get(pname)) + def __get_parameter_in_gradient_machine__(gradient_machine, name): """ diff --git a/python/paddle/v2/tests/test_parameters.py b/python/paddle/v2/tests/test_parameters.py index 45372e7dd0e..7ba8a939fbd 100644 --- a/python/paddle/v2/tests/test_parameters.py +++ b/python/paddle/v2/tests/test_parameters.py @@ -20,14 +20,17 @@ import cStringIO import numpy -def __rand_param_config__(name): +def __rand_param_config__(name, psize=None): conf = ParameterConfig() conf.name = name size = 1 - for i in xrange(2): - dim = random.randint(1, 1000) - conf.dims.append(dim) - size *= dim + if psize is None: + for i in xrange(2): + dim = random.randint(1, 1000) + conf.dims.append(dim) + size *= dim + else: + size = psize conf.size = size assert conf.IsInitialized() return conf @@ -77,6 +80,50 @@ class TestParameters(unittest.TestCase): expected = numpy.array([[1, 1], [1, 2], [1, 1]], numpy.float32) assert numpy.logical_and.reduce(numpy.reshape(val == expected, 6)) + def test_init_from_tar(self): + def get_param(names, size): + p = parameters.Parameters() + for k, v in zip(names, size): + p.__append_config__(__rand_param_config__(k, v)) + for name in p.names(): + param = p.get(name) + param[:] = numpy.random.uniform( + -1.0, 1.0, size=p.get_shape(name)) + p.set(name, param) + return p + + def get_parames(): + name1 = ['param_0', 'param_1'] + size1 = [128, 256] + p1 = get_param(name1, size1) + file1 = cStringIO.StringIO() + p1.to_tar(file1) + file1.seek(0) + + name2 = ['param_0', 'param_1', 'param_2'] + size2 = [128, 256, 288] + p2 = get_param(name2, size2) + file2 = cStringIO.StringIO() + p2.to_tar(file2) + file2.seek(0) + return p1, file1, p2, file2 + + p1, file1, p2, file2 = get_parames() + p2.init_from_tar(file1) + for name in p1.names(): + self.assertEqual(p1.get_shape(name), p2.get_shape(name)) + v1 = p1.get(name) + v2 = p2.get(name) + self.assertTrue(numpy.isclose(v1, v2).all()) + + p1, file1, p2, file2 = get_parames() + p1.init_from_tar(file2) + for name in p1.names(): + self.assertEqual(p1.get_shape(name), p2.get_shape(name)) + v1 = p1.get(name) + v2 = p2.get(name) + self.assertTrue(numpy.isclose(v1, v2).all()) + if __name__ == '__main__': unittest.main() -- GitLab From 555540fcc1b44323161c3dfd56a6f3fc7307433c Mon Sep 17 00:00:00 2001 From: dangqingqing Date: Thu, 29 Jun 2017 01:11:58 +0800 Subject: [PATCH 0893/3256] fix typo --- paddle/py_paddle/dataprovider_converter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paddle/py_paddle/dataprovider_converter.py b/paddle/py_paddle/dataprovider_converter.py index 218cb5ec560..43614b9779d 100644 --- a/paddle/py_paddle/dataprovider_converter.py +++ b/paddle/py_paddle/dataprovider_converter.py @@ -144,7 +144,7 @@ class DenseScanner(IScanner): if len(self.__shape__) > 1: # The last-two dimenstions are the frame height and width. # For example, the layout is CHW for 3-D feature of image. - # The H and W are the fram height and width. + # The H and W are the frame height and width. h, w = self.__shape__[-2:] argument.setSlotFrameHeight(self.pos, h) argument.setSlotFrameWidth(self.pos, w) -- GitLab From 6215f47c7c572edd94900a9ef4b90fce6726ee70 Mon Sep 17 00:00:00 2001 From: Yi Wang Date: Wed, 28 Jun 2017 14:44:40 -0700 Subject: [PATCH 0894/3256] Rename paddle/strings/ to paddle/string/ --- paddle/{strings => string}/CMakeLists.txt | 0 paddle/{strings => string}/stringpiece.cc | 0 paddle/{strings => string}/stringpiece.h | 0 paddle/{strings => string}/stringpiece_test.cc | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename paddle/{strings => string}/CMakeLists.txt (100%) rename paddle/{strings => string}/stringpiece.cc (100%) rename paddle/{strings => string}/stringpiece.h (100%) rename paddle/{strings => string}/stringpiece_test.cc (100%) diff --git a/paddle/strings/CMakeLists.txt b/paddle/string/CMakeLists.txt similarity index 100% rename from paddle/strings/CMakeLists.txt rename to paddle/string/CMakeLists.txt diff --git a/paddle/strings/stringpiece.cc b/paddle/string/stringpiece.cc similarity index 100% rename from paddle/strings/stringpiece.cc rename to paddle/string/stringpiece.cc diff --git a/paddle/strings/stringpiece.h b/paddle/string/stringpiece.h similarity index 100% rename from paddle/strings/stringpiece.h rename to paddle/string/stringpiece.h diff --git a/paddle/strings/stringpiece_test.cc b/paddle/string/stringpiece_test.cc similarity index 100% rename from paddle/strings/stringpiece_test.cc rename to paddle/string/stringpiece_test.cc -- GitLab From ea1d3acfb4012f491703266fa4caaf8e7e99e8c3 Mon Sep 17 00:00:00 2001 From: Yi Wang Date: Wed, 28 Jun 2017 14:52:54 -0700 Subject: [PATCH 0895/3256] Rename string/stringpiece* into string/piece --- paddle/CMakeLists.txt | 2 +- paddle/string/CMakeLists.txt | 4 +- paddle/string/piece.cc | 138 +++++++++++++++++ paddle/string/{stringpiece.h => piece.h} | 64 ++++---- .../{stringpiece_test.cc => piece_test.cc} | 100 +++++++------ paddle/string/stringpiece.cc | 141 ------------------ 6 files changed, 225 insertions(+), 224 deletions(-) create mode 100644 paddle/string/piece.cc rename paddle/string/{stringpiece.h => piece.h} (57%) rename paddle/string/{stringpiece_test.cc => piece_test.cc} (77%) delete mode 100644 paddle/string/stringpiece.cc diff --git a/paddle/CMakeLists.txt b/paddle/CMakeLists.txt index 979b68e8272..307e99bbe3a 100644 --- a/paddle/CMakeLists.txt +++ b/paddle/CMakeLists.txt @@ -9,7 +9,7 @@ add_subdirectory(pserver) add_subdirectory(trainer) add_subdirectory(scripts) add_subdirectory(optimizer) -add_subdirectory(strings) +add_subdirectory(string) if(Boost_FOUND) add_subdirectory(memory) diff --git a/paddle/string/CMakeLists.txt b/paddle/string/CMakeLists.txt index 4e55eecd484..0f39660a90a 100644 --- a/paddle/string/CMakeLists.txt +++ b/paddle/string/CMakeLists.txt @@ -1,2 +1,2 @@ -cc_library(stringpiece SRCS stringpiece.cc) -cc_test(stringpiece_test SRCS stringpiece_test.cc DEPS stringpiece glog gflags) +cc_library(stringpiece SRCS piece.cc) +cc_test(stringpiece_test SRCS piece_test.cc DEPS stringpiece glog gflags) diff --git a/paddle/string/piece.cc b/paddle/string/piece.cc new file mode 100644 index 00000000000..b80afdec82d --- /dev/null +++ b/paddle/string/piece.cc @@ -0,0 +1,138 @@ +/* + 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/string/piece.h" + +#include + +#include +#include +#include + +namespace paddle { +namespace string { + +Piece::Piece() : data_(NULL), size_(0) {} + +Piece::Piece(const char* d, size_t n) : data_(d), size_(n) { + if (d == NULL && n != 0) + throw std::invalid_argument("Piece requires len to be 0 for NULL data"); +} + +Piece::Piece(const char* s) : data_(s) { size_ = (s == NULL) ? 0 : strlen(s); } + +Piece::Piece(const std::string& s) : data_(s.data()), size_(s.size()) {} + +char Piece::operator[](size_t n) const { + if (n >= len()) throw std::invalid_argument("index out of Piece length"); + return data_[n]; +} + +int Compare(Piece a, Piece b) { + const size_t min_len = (a.len() < b.len()) ? a.len() : b.len(); + int r = memcmp(a.data(), b.data(), min_len); + if (r == 0) { + if (a.len() < b.len()) + return -1; + else if (a.len() > b.len()) + return 1; + } + return r; +} + +bool operator==(Piece x, Piece y) { + return ((x.len() == y.len()) && + (x.data() == y.data() || memcmp(x.data(), y.data(), x.len()) == 0)); +} + +bool operator!=(Piece x, Piece y) { return !(x == y); } + +bool operator<(Piece x, Piece y) { return Compare(x, y) < 0; } +bool operator>(Piece x, Piece y) { return Compare(x, y) > 0; } + +bool operator<=(Piece x, Piece y) { return Compare(x, y) <= 0; } +bool operator>=(Piece x, Piece y) { return Compare(x, y) >= 0; } + +bool HasPrefix(Piece s, Piece x) { + return ((s.len() >= x.len()) && (memcmp(s.data(), x.data(), x.len()) == 0)); +} + +bool HasSuffix(Piece s, Piece x) { + return ((s.len() >= x.len()) && + (memcmp(s.data() + (s.len() - x.len()), x.data(), x.len()) == 0)); +} + +Piece SkipPrefix(Piece s, size_t n) { + if (n > s.len()) + throw std::invalid_argument("Skip distance larger than Piece length"); + return Piece(s.data() + n, s.len() - n); +} + +Piece SkipSuffix(Piece s, size_t n) { + if (n > s.len()) + throw std::invalid_argument("Skip distance larger than Piece length"); + return Piece(s.data(), s.len() - n); +} + +Piece TrimPrefix(Piece s, Piece x) { + return HasPrefix(s, x) ? SkipPrefix(s, x.len()) : s; +} + +Piece TrimSuffix(Piece s, Piece x) { + return HasSuffix(s, x) ? SkipSuffix(s, x.len()) : s; +} + +bool Contains(Piece s, Piece sub) { + return std::search(s.begin(), s.end(), sub.begin(), sub.end()) != s.end(); +} + +size_t Index(Piece s, Piece sub) { + auto e = std::search(s.begin(), s.end(), sub.begin(), sub.end()); + return e != s.end() ? e - s.data() : Piece::npos; +} + +size_t Find(Piece s, char c, size_t pos) { + if (pos >= s.len()) { + return Piece::npos; + } + const char* result = + reinterpret_cast(memchr(s.data() + pos, c, s.len() - pos)); + return result != nullptr ? result - s.data() : Piece::npos; +} + +size_t RFind(Piece s, char c, size_t pos) { + if (s.len() == 0) return Piece::npos; + for (const char* p = s.data() + std::min(pos, s.len() - 1); p >= s.data(); + p--) { + if (*p == c) { + return p - s.data(); + } + } + return Piece::npos; +} + +Piece SubStr(Piece s, size_t pos, size_t n) { + if (pos > s.len()) pos = s.len(); + if (n > s.len() - pos) n = s.len() - pos; + return Piece(s.data() + pos, n); +} + +std::ostream& operator<<(std::ostream& o, Piece piece) { + return o << piece.ToString(); +} + +} // namespace string +} // namespace paddle diff --git a/paddle/string/stringpiece.h b/paddle/string/piece.h similarity index 57% rename from paddle/string/stringpiece.h rename to paddle/string/piece.h index adff713e86f..db7c3e69804 100644 --- a/paddle/string/stringpiece.h +++ b/paddle/string/piece.h @@ -20,33 +20,34 @@ #include namespace paddle { +namespace string { -// StringPiece points into a std::string object but doesn't own the +// Piece points into a std::string object but doesn't own the // string. It is for efficient access to strings. Like Go's string -// type. Not that StringPiece doesn't mutate the underlying string, +// type. Not that Piece doesn't mutate the underlying string, // so it is thread-safe given that the underlying string doesn't -// change. Because StringPiece contains a little data members, and +// change. Because Piece contains a little data members, and // its syntax is simple as it doesn't own/manage the string, it is -// cheap to construct StringPieces and pass them around. -class StringPiece { +// cheap to construct Pieces and pass them around. +class Piece { public: static const size_t npos = static_cast(-1); // We provide non-explicit singleton constructors so users can - // pass in a "const char*" or a "string" wherever a "StringPiece" + // pass in a "const char*" or a "string" wherever a "Piece" // is expected. These contructors ensure that if data_ is NULL, // size_ is 0. - StringPiece(); - StringPiece(const char* d, size_t n); - StringPiece(const char* d); - StringPiece(const std::string& s); + Piece(); + Piece(const char* d, size_t n); + Piece(const char* d); + Piece(const std::string& s); const char* data() const { return data_; } size_t len() const { return size_; } char operator[](size_t n) const; - // StringPiece doesn't own the string, so both iterator and const + // Piece doesn't own the string, so both iterator and const // iterator are const char* indeed. typedef const char* const_iterator; typedef const char* iterator; @@ -63,43 +64,44 @@ private: // Intentionally copyable }; -int Compare(StringPiece a, StringPiece b); +int Compare(Piece a, Piece b); -bool operator==(StringPiece x, StringPiece y); -bool operator!=(StringPiece x, StringPiece y); -bool operator<(StringPiece x, StringPiece y); -bool operator>(StringPiece x, StringPiece y); -bool operator<=(StringPiece x, StringPiece y); -bool operator>=(StringPiece x, StringPiece y); +bool operator==(Piece x, Piece y); +bool operator!=(Piece x, Piece y); +bool operator<(Piece x, Piece y); +bool operator>(Piece x, Piece y); +bool operator<=(Piece x, Piece y); +bool operator>=(Piece x, Piece y); -bool HasPrefix(StringPiece s, StringPiece prefix); -bool HasSuffix(StringPiece s, StringPiece suffix); +bool HasPrefix(Piece s, Piece prefix); +bool HasSuffix(Piece s, Piece suffix); -StringPiece SkipPrefix(StringPiece s, size_t n); -StringPiece SkipSuffix(StringPiece s, size_t n); +Piece SkipPrefix(Piece s, size_t n); +Piece SkipSuffix(Piece s, size_t n); // Skip the prefix (or suffix) if it matches with the string. -StringPiece TrimPrefix(StringPiece s, StringPiece prefix); -StringPiece TrimSuffix(StringPiece s, StringPiece suffix); +Piece TrimPrefix(Piece s, Piece prefix); +Piece TrimSuffix(Piece s, Piece suffix); // Returns if s contains sub. Any s except for empty s contains an // empty sub. -bool Contains(StringPiece s, StringPiece sub); +bool Contains(Piece s, Piece sub); // Return the first occurrence of sub in s, or npos. If both s and // sub is empty, it returns npos; otherwise, if only sub is empty, it // returns 0. -size_t Index(StringPiece s, StringPiece sub); +size_t Index(Piece s, Piece sub); // Return the first occurrence of c in s[pos:end], or npos. -size_t Find(StringPiece s, char c, size_t pos); +size_t Find(Piece s, char c, size_t pos); // Search range is [0..pos] inclusive. If pos == npos, search everything. -size_t RFind(StringPiece s, char c, size_t pos); +size_t RFind(Piece s, char c, size_t pos); -StringPiece SubStr(StringPiece s, size_t pos, size_t n); +Piece SubStr(Piece s, size_t pos, size_t n); -// allow StringPiece to be logged -std::ostream& operator<<(std::ostream& o, StringPiece piece); +// allow Piece to be logged +std::ostream& operator<<(std::ostream& o, Piece piece); +} // namespace string } // namespace paddle diff --git a/paddle/string/stringpiece_test.cc b/paddle/string/piece_test.cc similarity index 77% rename from paddle/string/stringpiece_test.cc rename to paddle/string/piece_test.cc index 2ba66a04f64..cf5152ff5a3 100644 --- a/paddle/string/stringpiece_test.cc +++ b/paddle/string/piece_test.cc @@ -14,7 +14,7 @@ limitations under the License. */ -#include "paddle/strings/stringpiece.h" +#include "paddle/string/piece.h" #include @@ -22,42 +22,44 @@ TEST(StringPiece, Construct) { { - paddle::StringPiece s; + paddle::string::Piece s; EXPECT_EQ(NULL, s.data()); EXPECT_EQ(0U, s.len()); } - { EXPECT_THROW(paddle::StringPiece s(NULL, 10000U), std::invalid_argument); } { - paddle::StringPiece s(NULL); + EXPECT_THROW(paddle::string::Piece s(NULL, 10000U), std::invalid_argument); + } + { + paddle::string::Piece s(NULL); EXPECT_EQ(0U, s.len()); } { std::string a; EXPECT_EQ(0U, a.size()); - paddle::StringPiece s(a); + paddle::string::Piece s(a); EXPECT_EQ(0U, s.len()); } } TEST(StringPiece, CopyAndAssign) { - paddle::StringPiece empty; + paddle::string::Piece empty; EXPECT_EQ(0U, empty.len()); - paddle::StringPiece a("hello"); - paddle::StringPiece b = a; + paddle::string::Piece a("hello"); + paddle::string::Piece b = a; EXPECT_EQ(b.len(), strlen("hello")); EXPECT_EQ(a, b); std::string storage("hello"); - paddle::StringPiece c(storage); + paddle::string::Piece c(storage); EXPECT_EQ(a, c); EXPECT_NE(a.data(), c.data()); } TEST(StringPiece, Compare) { { - paddle::StringPiece a("hello"); - paddle::StringPiece b("world"); + paddle::string::Piece a("hello"); + paddle::string::Piece b("world"); EXPECT_TRUE(a != b); EXPECT_FALSE(a == b); EXPECT_TRUE(a < b); @@ -68,7 +70,7 @@ TEST(StringPiece, Compare) { EXPECT_GT(Compare(b, a), 0); } { - paddle::StringPiece a, b; + paddle::string::Piece a, b; EXPECT_TRUE(a == b); EXPECT_FALSE(a != b); EXPECT_FALSE(a < b); @@ -82,31 +84,31 @@ TEST(StringPiece, Compare) { TEST(StringPiece, ToString) { { - paddle::StringPiece s; + paddle::string::Piece s; EXPECT_EQ(std::string(""), s.ToString()); } { - paddle::StringPiece s(NULL); + paddle::string::Piece s(NULL); EXPECT_EQ(std::string(""), s.ToString()); } { - paddle::StringPiece s("hello"); + paddle::string::Piece s("hello"); EXPECT_EQ(std::string("hello"), s.ToString()); } } TEST(StringPiece, HasPrefixSuffix) { - using paddle::HasPrefix; - using paddle::HasSuffix; + using paddle::string::HasPrefix; + using paddle::string::HasSuffix; { - paddle::StringPiece s; + paddle::string::Piece s; EXPECT_FALSE(HasPrefix(s, "something")); EXPECT_TRUE(HasPrefix(s, "")); EXPECT_FALSE(HasSuffix(s, "something")); EXPECT_TRUE(HasSuffix(s, "")); } { - paddle::StringPiece s("app"); + paddle::string::Piece s("app"); EXPECT_TRUE(HasPrefix(s, "")); EXPECT_TRUE(HasPrefix(s, "a")); EXPECT_TRUE(HasPrefix(s, "ap")); @@ -120,10 +122,10 @@ TEST(StringPiece, HasPrefixSuffix) { } TEST(StringPiece, SkipPrefixSuffix) { - using paddle::SkipPrefix; - using paddle::SkipSuffix; + using paddle::string::SkipPrefix; + using paddle::string::SkipSuffix; { - paddle::StringPiece s; + paddle::string::Piece s; EXPECT_EQ("", SkipPrefix(s, 0)); EXPECT_THROW(SkipPrefix(s, 1), std::invalid_argument); @@ -131,7 +133,7 @@ TEST(StringPiece, SkipPrefixSuffix) { EXPECT_THROW(SkipSuffix(s, 1), std::invalid_argument); } { - paddle::StringPiece s("app"); + paddle::string::Piece s("app"); EXPECT_EQ("app", SkipPrefix(s, 0)); EXPECT_EQ("pp", SkipPrefix(s, 1)); EXPECT_EQ("p", SkipPrefix(s, 2)); @@ -147,10 +149,10 @@ TEST(StringPiece, SkipPrefixSuffix) { } TEST(StringPiece, TrimPrefixSuffix) { - using paddle::TrimPrefix; - using paddle::TrimSuffix; + using paddle::string::TrimPrefix; + using paddle::string::TrimSuffix; { - paddle::StringPiece s; + paddle::string::Piece s; EXPECT_EQ("", TrimPrefix(s, "")); EXPECT_EQ("", TrimPrefix(s, "something")); @@ -158,7 +160,7 @@ TEST(StringPiece, TrimPrefixSuffix) { EXPECT_EQ("", TrimSuffix(s, "something")); } { - paddle::StringPiece s("app"); + paddle::string::Piece s("app"); EXPECT_EQ("app", TrimPrefix(s, "")); EXPECT_EQ("pp", TrimPrefix(s, "a")); EXPECT_EQ("p", TrimPrefix(s, "ap")); @@ -174,14 +176,14 @@ TEST(StringPiece, TrimPrefixSuffix) { } TEST(StringPiece, Contains) { - using paddle::Contains; + using paddle::string::Contains; { - paddle::StringPiece s; + paddle::string::Piece s; EXPECT_FALSE(Contains(s, "")); EXPECT_FALSE(Contains(s, "something")); } { - paddle::StringPiece s("app"); + paddle::string::Piece s("app"); EXPECT_TRUE(Contains(s, "")); EXPECT_TRUE(Contains(s, "a")); EXPECT_TRUE(Contains(s, "p")); @@ -193,15 +195,15 @@ TEST(StringPiece, Contains) { } TEST(StringPiece, Index) { - using paddle::Index; - auto npos = paddle::StringPiece::npos; + using paddle::string::Index; + auto npos = paddle::string::Piece::npos; { - paddle::StringPiece s; + paddle::string::Piece s; EXPECT_EQ(npos, Index(s, "")); EXPECT_EQ(npos, Index(s, "something")); } { - paddle::StringPiece s("app"); + paddle::string::Piece s("app"); EXPECT_EQ(0U, Index(s, "")); EXPECT_EQ(0U, Index(s, "a")); EXPECT_EQ(1U, Index(s, "p")); @@ -213,14 +215,14 @@ TEST(StringPiece, Index) { } TEST(StringPiece, Find) { - using paddle::Find; - auto npos = paddle::StringPiece::npos; + using paddle::string::Find; + auto npos = paddle::string::Piece::npos; { - paddle::StringPiece s; + paddle::string::Piece s; EXPECT_EQ(npos, Find(s, 'a', 0U)); } { - paddle::StringPiece s("app"); + paddle::string::Piece s("app"); EXPECT_EQ(0U, Find(s, 'a', 0U)); EXPECT_EQ(1U, Find(s, 'p', 0U)); EXPECT_EQ(1U, Find(s, 'p', 1U)); @@ -230,14 +232,14 @@ TEST(StringPiece, Find) { } TEST(StringPiece, RFind) { - using paddle::RFind; - auto npos = paddle::StringPiece::npos; + using paddle::string::RFind; + auto npos = paddle::string::Piece::npos; { - paddle::StringPiece s; + paddle::string::Piece s; EXPECT_EQ(npos, RFind(s, 'a', 0U)); } { - paddle::StringPiece s("app"); + paddle::string::Piece s("app"); EXPECT_EQ(2U, RFind(s, 'p', 2U)); EXPECT_EQ(0U, RFind(s, 'a', 2U)); EXPECT_EQ(1U, RFind(s, 'p', 1U)); @@ -247,15 +249,15 @@ TEST(StringPiece, RFind) { } TEST(StringPiece, SubStr) { - using paddle::SubStr; + using paddle::string::SubStr; { - paddle::StringPiece s; + paddle::string::Piece s; EXPECT_EQ("", SubStr(s, 0, 0)); EXPECT_EQ("", SubStr(s, 0, 1)); EXPECT_EQ("", SubStr(s, 1, 0)); } { - paddle::StringPiece s("app"); + paddle::string::Piece s("app"); EXPECT_EQ("", SubStr(s, 0, 0)); EXPECT_EQ("", SubStr(s, 1, 0)); EXPECT_EQ("", SubStr(s, 2, 0)); @@ -279,15 +281,15 @@ TEST(StringPiece, SubStr) { } TEST(StringPiece, StreamOutput) { - using paddle::StringPiece; + using paddle::string::Piece; std::stringstream o; - o << StringPiece(); + o << paddle::string::Piece(); EXPECT_EQ("", o.str()); - o << StringPiece("hello"); + o << paddle::string::Piece("hello"); EXPECT_EQ("hello", o.str()); - o << StringPiece(); + o << paddle::string::Piece(); EXPECT_EQ("hello", o.str()); } diff --git a/paddle/string/stringpiece.cc b/paddle/string/stringpiece.cc deleted file mode 100644 index 415b3558d5d..00000000000 --- a/paddle/string/stringpiece.cc +++ /dev/null @@ -1,141 +0,0 @@ -/* - 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/strings/stringpiece.h" - -#include - -#include -#include -#include - -namespace paddle { - -StringPiece::StringPiece() : data_(NULL), size_(0) {} - -StringPiece::StringPiece(const char* d, size_t n) : data_(d), size_(n) { - if (d == NULL && n != 0) - throw std::invalid_argument( - "StringPiece requires len to be 0 for NULL data"); -} - -StringPiece::StringPiece(const char* s) : data_(s) { - size_ = (s == NULL) ? 0 : strlen(s); -} - -StringPiece::StringPiece(const std::string& s) - : data_(s.data()), size_(s.size()) {} - -char StringPiece::operator[](size_t n) const { - if (n >= len()) - throw std::invalid_argument("index out of StringPiece length"); - return data_[n]; -} - -int Compare(StringPiece a, StringPiece b) { - const size_t min_len = (a.len() < b.len()) ? a.len() : b.len(); - int r = memcmp(a.data(), b.data(), min_len); - if (r == 0) { - if (a.len() < b.len()) - return -1; - else if (a.len() > b.len()) - return 1; - } - return r; -} - -bool operator==(StringPiece x, StringPiece y) { - return ((x.len() == y.len()) && - (x.data() == y.data() || memcmp(x.data(), y.data(), x.len()) == 0)); -} - -bool operator!=(StringPiece x, StringPiece y) { return !(x == y); } - -bool operator<(StringPiece x, StringPiece y) { return Compare(x, y) < 0; } -bool operator>(StringPiece x, StringPiece y) { return Compare(x, y) > 0; } - -bool operator<=(StringPiece x, StringPiece y) { return Compare(x, y) <= 0; } -bool operator>=(StringPiece x, StringPiece y) { return Compare(x, y) >= 0; } - -bool HasPrefix(StringPiece s, StringPiece x) { - return ((s.len() >= x.len()) && (memcmp(s.data(), x.data(), x.len()) == 0)); -} - -bool HasSuffix(StringPiece s, StringPiece x) { - return ((s.len() >= x.len()) && - (memcmp(s.data() + (s.len() - x.len()), x.data(), x.len()) == 0)); -} - -StringPiece SkipPrefix(StringPiece s, size_t n) { - if (n > s.len()) - throw std::invalid_argument("Skip distance larger than StringPiece length"); - return StringPiece(s.data() + n, s.len() - n); -} - -StringPiece SkipSuffix(StringPiece s, size_t n) { - if (n > s.len()) - throw std::invalid_argument("Skip distance larger than StringPiece length"); - return StringPiece(s.data(), s.len() - n); -} - -StringPiece TrimPrefix(StringPiece s, StringPiece x) { - return HasPrefix(s, x) ? SkipPrefix(s, x.len()) : s; -} - -StringPiece TrimSuffix(StringPiece s, StringPiece x) { - return HasSuffix(s, x) ? SkipSuffix(s, x.len()) : s; -} - -bool Contains(StringPiece s, StringPiece sub) { - return std::search(s.begin(), s.end(), sub.begin(), sub.end()) != s.end(); -} - -size_t Index(StringPiece s, StringPiece sub) { - auto e = std::search(s.begin(), s.end(), sub.begin(), sub.end()); - return e != s.end() ? e - s.data() : StringPiece::npos; -} - -size_t Find(StringPiece s, char c, size_t pos) { - if (pos >= s.len()) { - return StringPiece::npos; - } - const char* result = - reinterpret_cast(memchr(s.data() + pos, c, s.len() - pos)); - return result != nullptr ? result - s.data() : StringPiece::npos; -} - -size_t RFind(StringPiece s, char c, size_t pos) { - if (s.len() == 0) return StringPiece::npos; - for (const char* p = s.data() + std::min(pos, s.len() - 1); p >= s.data(); - p--) { - if (*p == c) { - return p - s.data(); - } - } - return StringPiece::npos; -} - -StringPiece SubStr(StringPiece s, size_t pos, size_t n) { - if (pos > s.len()) pos = s.len(); - if (n > s.len() - pos) n = s.len() - pos; - return StringPiece(s.data() + pos, n); -} - -std::ostream& operator<<(std::ostream& o, StringPiece piece) { - return o << piece.ToString(); -} - -} // namespace paddle -- GitLab From 6cb7cb36911ec36be344a5800c142284983ae2f6 Mon Sep 17 00:00:00 2001 From: Yi Wang Date: Wed, 28 Jun 2017 17:23:17 -0700 Subject: [PATCH 0896/3256] Add paddle/string/printf and tests --- paddle/string/CMakeLists.txt | 2 + paddle/string/printf.h | 105 +++ paddle/string/printf_test.cc | 16 + paddle/string/tinyformat/tinyformat.h | 902 ++++++++++++++++++++++++++ 4 files changed, 1025 insertions(+) create mode 100644 paddle/string/printf.h create mode 100644 paddle/string/printf_test.cc create mode 100644 paddle/string/tinyformat/tinyformat.h diff --git a/paddle/string/CMakeLists.txt b/paddle/string/CMakeLists.txt index 0f39660a90a..5becf62672d 100644 --- a/paddle/string/CMakeLists.txt +++ b/paddle/string/CMakeLists.txt @@ -1,2 +1,4 @@ cc_library(stringpiece SRCS piece.cc) cc_test(stringpiece_test SRCS piece_test.cc DEPS stringpiece glog gflags) + +cc_test(stringprintf_test SRCS printf_test.cc DEPS glog gflags) diff --git a/paddle/string/printf.h b/paddle/string/printf.h new file mode 100644 index 00000000000..0767f8f5b58 --- /dev/null +++ b/paddle/string/printf.h @@ -0,0 +1,105 @@ +/* + 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. +*/ + +// Compared with std::stringstream, there are primary purpose of +// string::Printf: +// +// 1. Type-safe printing, with why and how explained in +// http://www.drdobbs.com/stringprintf-a-typesafe-printf-family-fo/184401999. +// Implementation includes +// +// https://github.com/c42f/tinyformat +// boost::format +// std::stringstream +// +// std::stringstream is not convenient enough in many cases. For example: +// +// std::cout << std::setprecision(2) << std::fixed << 1.23456 << "\n"; +// +// boost::format is the most convenient one. We can have +// +// std::cout << format("%2% %1%") % 36 % 77; +// +// or +// +// format fmter("%2% %1%"); +// fmter % 36; fmter % 77; +// std::cout << fmter.c_str(); +// +// But the overloading of % might be overkilling and it would be +// more efficient if it can write to std::cout directly. +// +// tinyformat has an interface compatible with the C-printf style, +// and it can writes to a stream or returns a std::string: +// +// std::cout << tfm::printf( +// "%s, %s %d, %.2d:%.2d\n", +// weekday, month, day, hour, min); +// +// or +// +// tfm::format(std::cout, +// "%s, %s %d, %.2d:%.2d\n", +// weekday, month, day, hour, min); +// +// 2. High-performance -- most printed strings are not too long and +// doens't need dynamic memory allocation. Many StringPrintf +// implementations doesn't enforce type-safe, but are +// high-performance, including +// +// https://developers.google.com/optimization/reference/base/stringprintf/ +// https://github.com/adobe/chromium/blob/master/base/stringprintf.h +// https://github.com/google/protobuf/blob/master/src/google/protobuf/stubs/stringprintf.h +// +// According to +// https://github.com/c42f/tinyformat#compile-time-and-code-bloat, +// boost::format runs too slow and results in large executable binary +// files. So here we port tinyformat. + +#pragma once + +#include +#include +#include "paddle/string/tinyformat/tinyformat.h" // https://github.com/c42f/tinyformat + +namespace paddle { +namespace string { + +template +void Fprintf(std::ostream& out, const char* fmt, const Args&... args) { + tinyformat::vformat(out, fmt, makeFormatList(args...)); +} + +template +std::string Sprintf(const char* fmt, const Args&... args) { + std::ostringstream oss; + tinyformat::format(oss, fmt, args...); + return oss.str(); +} + +template +void printf(const char* fmt, const Args&... args) { + tinyformat::format(std::cout, fmt, args...); +} + +template +void printfln(const char* fmt, const Args&... args) { + tinyformat::format(std::cout, fmt, args...); + std::cout << '\n'; +} + +} // namespace string +} // namespace paddle diff --git a/paddle/string/printf_test.cc b/paddle/string/printf_test.cc new file mode 100644 index 00000000000..d8f2454165d --- /dev/null +++ b/paddle/string/printf_test.cc @@ -0,0 +1,16 @@ +#include "paddle/string/printf.h" + +#include + +#include "gtest/gtest.h" + +TEST(StringPrintf, StringPrintf) { + std::string weekday = "Wednesday"; + const char* month = "July"; + size_t day = 27; + long hour = 14; + int min = 44; + EXPECT_EQ(std::string("Wednesday, July 27, 14:44"), + paddle::string::Sprintf( + "%s, %s %d, %.2d:%.2d", weekday, month, day, hour, min)); +} diff --git a/paddle/string/tinyformat/tinyformat.h b/paddle/string/tinyformat/tinyformat.h new file mode 100644 index 00000000000..f0e5e0160fb --- /dev/null +++ b/paddle/string/tinyformat/tinyformat.h @@ -0,0 +1,902 @@ +// tinyformat.h +// Copyright (C) 2011, Chris Foster [chris42f (at) gmail (d0t) com] +// +// Boost Software License - Version 1.0 +// +// Permission is hereby granted, free of charge, to any person or organization +// obtaining a copy of the software and accompanying documentation covered by +// this license (the "Software") to use, reproduce, display, distribute, +// execute, and transmit the Software, and to prepare derivative works of the +// Software, and to permit third-parties to whom the Software is furnished to +// do so, all subject to the following: +// +// The copyright notices in the Software and this entire statement, including +// the above license grant, this restriction and the following disclaimer, +// must be included in all copies of the Software, in whole or in part, and +// all derivative works of the Software, unless such copies or derivative +// works are solely in the form of machine-executable object code generated by +// a source language processor. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +// SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +// FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +//------------------------------------------------------------------------------ +// Tinyformat: A minimal type safe printf replacement +// +// tinyformat.h is a type safe printf replacement library in a single C++ +// header file. Design goals include: +// +// * Type safety and extensibility for user defined types. +// * C99 printf() compatibility, to the extent possible using std::ostream +// * Simplicity and minimalism. A single header file to include and distribute +// with your projects. +// * Augment rather than replace the standard stream formatting mechanism +// * C++98 support, with optional C++11 niceties +// +// +// Main interface example usage +// ---------------------------- +// +// To print a date to std::cout: +// +// std::string weekday = "Wednesday"; +// const char* month = "July"; +// size_t day = 27; +// long hour = 14; +// int min = 44; +// +// tfm::printf("%s, %s %d, %.2d:%.2d\n", weekday, month, day, hour, min); +// +// The strange types here emphasize the type safety of the interface; it is +// possible to print a std::string using the "%s" conversion, and a +// size_t using the "%d" conversion. A similar result could be achieved +// using either of the tfm::format() functions. One prints on a user provided +// stream: +// +// tfm::format(std::cerr, "%s, %s %d, %.2d:%.2d\n", +// weekday, month, day, hour, min); +// +// The other returns a std::string: +// +// std::string date = tfm::format("%s, %s %d, %.2d:%.2d\n", +// weekday, month, day, hour, min); +// std::cout << date; +// +// These are the three primary interface functions. There is also a +// convenience function printfln() which appends a newline to the usual result +// of printf() for super simple logging. +// +// +// User defined format functions +// ----------------------------- +// +// Simulating variadic templates in C++98 is pretty painful since it requires +// writing out the same function for each desired number of arguments. To make +// this bearable tinyformat comes with a set of macros which are used +// internally to generate the API, but which may also be used in user code. +// +// The three macros TINYFORMAT_ARGTYPES(n), TINYFORMAT_VARARGS(n) and +// TINYFORMAT_PASSARGS(n) will generate a list of n argument types, +// type/name pairs and argument names respectively when called with an integer +// n between 1 and 16. We can use these to define a macro which generates the +// desired user defined function with n arguments. To generate all 16 user +// defined function bodies, use the macro TINYFORMAT_FOREACH_ARGNUM. For an +// example, see the implementation of printf() at the end of the source file. +// +// Sometimes it's useful to be able to pass a list of format arguments through +// to a non-template function. The FormatList class is provided as a way to do +// this by storing the argument list in a type-opaque way. Continuing the +// example from above, we construct a FormatList using makeFormatList(): +// +// FormatListRef formatList = tfm::makeFormatList(weekday, month, day, hour, +// min); +// +// The format list can now be passed into any non-template function and used +// via a call to the vformat() function: +// +// tfm::vformat(std::cout, "%s, %s %d, %.2d:%.2d\n", formatList); +// +// +// Additional API information +// -------------------------- +// +// Error handling: Define TINYFORMAT_ERROR to customize the error handling for +// format strings which are unsupported or have the wrong number of format +// specifiers (calls assert() by default). +// +// User defined types: Uses operator<< for user defined types by default. +// Overload formatValue() for more control. + +#pragma once + +#include +#include +#include +#include + +namespace paddle { +namespace string { +namespace tinyformat { + +#ifndef TINYFORMAT_ERROR +#define TINYFORMAT_ERROR(reason) assert(0 && reason) +#endif + +//------------------------------------------------------------------------------ +namespace detail { + +// Test whether type T1 is convertible to type T2 +template +struct is_convertible { +private: + // two types of different size + struct fail { + char dummy[2]; + }; + struct succeed { + char dummy; + }; + // Try to convert a T1 to a T2 by plugging into tryConvert + static fail tryConvert(...); + static succeed tryConvert(const T2 &); + static const T1 &makeT1(); + +public: + // Standard trick: the (...) version of tryConvert will be chosen from + // the overload set only if the version taking a T2 doesn't match. + // Then we compare the sizes of the return types to check which + // function matched. Very neat, in a disgusting kind of way :) + static const bool value = sizeof(tryConvert(makeT1())) == sizeof(succeed); +}; + +// Format the value by casting to type fmtT. This default implementation +// should never be called. +template ::value> +struct formatValueAsType { + static void invoke(std::ostream & /*out*/, const T & /*value*/) { assert(0); } +}; +// Specialized version for types that can actually be converted to fmtT, as +// indicated by the "convertible" template parameter. +template +struct formatValueAsType { + static void invoke(std::ostream &out, const T &value) { + out << static_cast(value); + } +}; + +// Convert an arbitrary type to integer. The version with convertible=false +// throws an error. +template ::value> +struct convertToInt { + static int invoke(const T & /*value*/) { + TINYFORMAT_ERROR( + "tinyformat: Cannot convert from argument type to " + "integer for use as variable width or precision"); + return 0; + } +}; +// Specialization for convertToInt when conversion is possible +template +struct convertToInt { + static int invoke(const T &value) { return static_cast(value); } +}; + +// Format at most ntrunc characters to the given stream. +template +inline void formatTruncated(std::ostream &out, const T &value, int ntrunc) { + std::ostringstream tmp; + tmp << value; + std::string result = tmp.str(); + out.write(result.c_str(), + (std::min)(ntrunc, static_cast(result.size()))); +} +#define TINYFORMAT_DEFINE_FORMAT_TRUNCATED_CSTR(type) \ + inline void formatTruncated(std::ostream &out, type *value, int ntrunc) { \ + std::streamsize len = 0; \ + while (len < ntrunc && value[len] != 0) ++len; \ + out.write(value, len); \ + } +// Overload for const char* and char*. Could overload for signed & unsigned +// char too, but these are technically unneeded for printf compatibility. +TINYFORMAT_DEFINE_FORMAT_TRUNCATED_CSTR(const char) +TINYFORMAT_DEFINE_FORMAT_TRUNCATED_CSTR(char) +#undef TINYFORMAT_DEFINE_FORMAT_TRUNCATED_CSTR + +} // namespace detail + +//------------------------------------------------------------------------------ +// Variable formatting functions. May be overridden for user-defined types if +// desired. + +/// Format a value into a stream, delegating to operator<< by default. +/// +/// Users may override this for their own types. When this function is called, +/// the stream flags will have been modified according to the format string. +/// The format specification is provided in the range [fmtBegin, fmtEnd). For +/// truncating conversions, ntrunc is set to the desired maximum number of +/// characters, for example "%.7s" calls formatValue with ntrunc = 7. +/// +/// By default, formatValue() uses the usual stream insertion operator +/// operator<< to format the type T, with special cases for the %c and %p +/// conversions. +template +inline void formatValue(std::ostream &out, + const char * /*fmtBegin*/, + const char *fmtEnd, + int ntrunc, + const T &value) { + // The mess here is to support the %c and %p conversions: if these + // conversions are active we try to convert the type to a char or const + // void* respectively and format that instead of the value itself. For the + // %p conversion it's important to avoid dereferencing the pointer, which + // could otherwise lead to a crash when printing a dangling (const char*). + const bool canConvertToChar = detail::is_convertible::value; + const bool canConvertToVoidPtr = + detail::is_convertible::value; + if (canConvertToChar && *(fmtEnd - 1) == 'c') + detail::formatValueAsType::invoke(out, value); + else if (canConvertToVoidPtr && *(fmtEnd - 1) == 'p') + detail::formatValueAsType::invoke(out, value); + else if (ntrunc >= 0) { + // Take care not to overread C strings in truncating conversions like + // "%.4s" where at most 4 characters may be read. + detail::formatTruncated(out, value, ntrunc); + } else + out << value; +} + +// Overloaded version for char types to support printing as an integer +#define TINYFORMAT_DEFINE_FORMATVALUE_CHAR(charType) \ + inline void formatValue(std::ostream &out, \ + const char * /*fmtBegin*/, \ + const char *fmtEnd, \ + int /**/, \ + charType value) { \ + switch (*(fmtEnd - 1)) { \ + case 'u': \ + case 'd': \ + case 'i': \ + case 'o': \ + case 'X': \ + case 'x': \ + out << static_cast(value); \ + break; \ + default: \ + out << value; \ + break; \ + } \ + } +// per 3.9.1: char, signed char and unsigned char are all distinct types +TINYFORMAT_DEFINE_FORMATVALUE_CHAR(char) +TINYFORMAT_DEFINE_FORMATVALUE_CHAR(signed char) +TINYFORMAT_DEFINE_FORMATVALUE_CHAR(unsigned char) +#undef TINYFORMAT_DEFINE_FORMATVALUE_CHAR + +//------------------------------------------------------------------------------ +// Tools for emulating variadic templates in C++98. The basic idea here is +// stolen from the boost preprocessor metaprogramming library and cut down to +// be just general enough for what we need. + +#define TINYFORMAT_ARGTYPES(n) TINYFORMAT_ARGTYPES_##n +#define TINYFORMAT_VARARGS(n) TINYFORMAT_VARARGS_##n +#define TINYFORMAT_PASSARGS(n) TINYFORMAT_PASSARGS_##n +#define TINYFORMAT_PASSARGS_TAIL(n) TINYFORMAT_PASSARGS_TAIL_##n + +// To keep it as transparent as possible, the macros below have been generated +// using python via the excellent cog.py code generation script. This avoids +// the need for a bunch of complex (but more general) preprocessor tricks as +// used in boost.preprocessor. +// +// To rerun the code generation in place, use `cog.py -r tinyformat.h` +// (see http://nedbatchelder.com/code/cog). Alternatively you can just create +// extra versions by hand. + +/*[[[cog +maxParams = 16 + +def makeCommaSepLists(lineTemplate, elemTemplate, startInd=1): + for j in range(startInd,maxParams+1): + list = ', '.join([elemTemplate % {'i':i} for i in range(startInd,j+1)]) + cog.outl(lineTemplate % {'j':j, 'list':list}) + +makeCommaSepLists('#define TINYFORMAT_ARGTYPES_%(j)d %(list)s', + 'class T%(i)d') + +cog.outl() +makeCommaSepLists('#define TINYFORMAT_VARARGS_%(j)d %(list)s', + 'const T%(i)d& v%(i)d') + +cog.outl() +makeCommaSepLists('#define TINYFORMAT_PASSARGS_%(j)d %(list)s', 'v%(i)d') + +cog.outl() +cog.outl('#define TINYFORMAT_PASSARGS_TAIL_1') +makeCommaSepLists('#define TINYFORMAT_PASSARGS_TAIL_%(j)d , %(list)s', + 'v%(i)d', startInd = 2) + +cog.outl() +cog.outl('#define TINYFORMAT_FOREACH_ARGNUM(m) \\\n ' + + ' '.join(['m(%d)' % (j,) for j in range(1,maxParams+1)])) +]]]*/ +#define TINYFORMAT_ARGTYPES_1 class T1 +#define TINYFORMAT_ARGTYPES_2 class T1, class T2 +#define TINYFORMAT_ARGTYPES_3 class T1, class T2, class T3 +#define TINYFORMAT_ARGTYPES_4 class T1, class T2, class T3, class T4 +#define TINYFORMAT_ARGTYPES_5 class T1, class T2, class T3, class T4, class T5 +#define TINYFORMAT_ARGTYPES_6 \ + class T1, class T2, class T3, class T4, class T5, class T6 +#define TINYFORMAT_ARGTYPES_7 \ + class T1, class T2, class T3, class T4, class T5, class T6, class T7 +#define TINYFORMAT_ARGTYPES_8 \ + class T1, class T2, class T3, class T4, class T5, class T6, class T7, class T8 +#define TINYFORMAT_ARGTYPES_9 \ + class T1, class T2, class T3, class T4, class T5, class T6, class T7, \ + class T8, class T9 +#define TINYFORMAT_ARGTYPES_10 \ + class T1, class T2, class T3, class T4, class T5, class T6, class T7, \ + class T8, class T9, class T10 +#define TINYFORMAT_ARGTYPES_11 \ + class T1, class T2, class T3, class T4, class T5, class T6, class T7, \ + class T8, class T9, class T10, class T11 +#define TINYFORMAT_ARGTYPES_12 \ + class T1, class T2, class T3, class T4, class T5, class T6, class T7, \ + class T8, class T9, class T10, class T11, class T12 +#define TINYFORMAT_ARGTYPES_13 \ + class T1, class T2, class T3, class T4, class T5, class T6, class T7, \ + class T8, class T9, class T10, class T11, class T12, class T13 +#define TINYFORMAT_ARGTYPES_14 \ + class T1, class T2, class T3, class T4, class T5, class T6, class T7, \ + class T8, class T9, class T10, class T11, class T12, class T13, \ + class T14 +#define TINYFORMAT_ARGTYPES_15 \ + class T1, class T2, class T3, class T4, class T5, class T6, class T7, \ + class T8, class T9, class T10, class T11, class T12, class T13, \ + class T14, class T15 +#define TINYFORMAT_ARGTYPES_16 \ + class T1, class T2, class T3, class T4, class T5, class T6, class T7, \ + class T8, class T9, class T10, class T11, class T12, class T13, \ + class T14, class T15, class T16 + +#define TINYFORMAT_VARARGS_1 const T1 &v1 +#define TINYFORMAT_VARARGS_2 const T1 &v1, const T2 &v2 +#define TINYFORMAT_VARARGS_3 const T1 &v1, const T2 &v2, const T3 &v3 +#define TINYFORMAT_VARARGS_4 \ + const T1 &v1, const T2 &v2, const T3 &v3, const T4 &v4 +#define TINYFORMAT_VARARGS_5 \ + const T1 &v1, const T2 &v2, const T3 &v3, const T4 &v4, const T5 &v5 +#define TINYFORMAT_VARARGS_6 \ + const T1 &v1, const T2 &v2, const T3 &v3, const T4 &v4, const T5 &v5, \ + const T6 &v6 +#define TINYFORMAT_VARARGS_7 \ + const T1 &v1, const T2 &v2, const T3 &v3, const T4 &v4, const T5 &v5, \ + const T6 &v6, const T7 &v7 +#define TINYFORMAT_VARARGS_8 \ + const T1 &v1, const T2 &v2, const T3 &v3, const T4 &v4, const T5 &v5, \ + const T6 &v6, const T7 &v7, const T8 &v8 +#define TINYFORMAT_VARARGS_9 \ + const T1 &v1, const T2 &v2, const T3 &v3, const T4 &v4, const T5 &v5, \ + const T6 &v6, const T7 &v7, const T8 &v8, const T9 &v9 +#define TINYFORMAT_VARARGS_10 \ + const T1 &v1, const T2 &v2, const T3 &v3, const T4 &v4, const T5 &v5, \ + const T6 &v6, const T7 &v7, const T8 &v8, const T9 &v9, const T10 &v10 +#define TINYFORMAT_VARARGS_11 \ + const T1 &v1, const T2 &v2, const T3 &v3, const T4 &v4, const T5 &v5, \ + const T6 &v6, const T7 &v7, const T8 &v8, const T9 &v9, const T10 &v10, \ + const T11 &v11 +#define TINYFORMAT_VARARGS_12 \ + const T1 &v1, const T2 &v2, const T3 &v3, const T4 &v4, const T5 &v5, \ + const T6 &v6, const T7 &v7, const T8 &v8, const T9 &v9, const T10 &v10, \ + const T11 &v11, const T12 &v12 +#define TINYFORMAT_VARARGS_13 \ + const T1 &v1, const T2 &v2, const T3 &v3, const T4 &v4, const T5 &v5, \ + const T6 &v6, const T7 &v7, const T8 &v8, const T9 &v9, const T10 &v10, \ + const T11 &v11, const T12 &v12, const T13 &v13 +#define TINYFORMAT_VARARGS_14 \ + const T1 &v1, const T2 &v2, const T3 &v3, const T4 &v4, const T5 &v5, \ + const T6 &v6, const T7 &v7, const T8 &v8, const T9 &v9, const T10 &v10, \ + const T11 &v11, const T12 &v12, const T13 &v13, const T14 &v14 +#define TINYFORMAT_VARARGS_15 \ + const T1 &v1, const T2 &v2, const T3 &v3, const T4 &v4, const T5 &v5, \ + const T6 &v6, const T7 &v7, const T8 &v8, const T9 &v9, const T10 &v10, \ + const T11 &v11, const T12 &v12, const T13 &v13, const T14 &v14, \ + const T15 &v15 +#define TINYFORMAT_VARARGS_16 \ + const T1 &v1, const T2 &v2, const T3 &v3, const T4 &v4, const T5 &v5, \ + const T6 &v6, const T7 &v7, const T8 &v8, const T9 &v9, const T10 &v10, \ + const T11 &v11, const T12 &v12, const T13 &v13, const T14 &v14, \ + const T15 &v15, const T16 &v16 + +#define TINYFORMAT_PASSARGS_1 v1 +#define TINYFORMAT_PASSARGS_2 v1, v2 +#define TINYFORMAT_PASSARGS_3 v1, v2, v3 +#define TINYFORMAT_PASSARGS_4 v1, v2, v3, v4 +#define TINYFORMAT_PASSARGS_5 v1, v2, v3, v4, v5 +#define TINYFORMAT_PASSARGS_6 v1, v2, v3, v4, v5, v6 +#define TINYFORMAT_PASSARGS_7 v1, v2, v3, v4, v5, v6, v7 +#define TINYFORMAT_PASSARGS_8 v1, v2, v3, v4, v5, v6, v7, v8 +#define TINYFORMAT_PASSARGS_9 v1, v2, v3, v4, v5, v6, v7, v8, v9 +#define TINYFORMAT_PASSARGS_10 v1, v2, v3, v4, v5, v6, v7, v8, v9, v10 +#define TINYFORMAT_PASSARGS_11 v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11 +#define TINYFORMAT_PASSARGS_12 v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12 +#define TINYFORMAT_PASSARGS_13 \ + v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13 +#define TINYFORMAT_PASSARGS_14 \ + v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14 +#define TINYFORMAT_PASSARGS_15 \ + v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15 +#define TINYFORMAT_PASSARGS_16 \ + v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16 + +#define TINYFORMAT_PASSARGS_TAIL_1 +#define TINYFORMAT_PASSARGS_TAIL_2 , v2 +#define TINYFORMAT_PASSARGS_TAIL_3 , v2, v3 +#define TINYFORMAT_PASSARGS_TAIL_4 , v2, v3, v4 +#define TINYFORMAT_PASSARGS_TAIL_5 , v2, v3, v4, v5 +#define TINYFORMAT_PASSARGS_TAIL_6 , v2, v3, v4, v5, v6 +#define TINYFORMAT_PASSARGS_TAIL_7 , v2, v3, v4, v5, v6, v7 +#define TINYFORMAT_PASSARGS_TAIL_8 , v2, v3, v4, v5, v6, v7, v8 +#define TINYFORMAT_PASSARGS_TAIL_9 , v2, v3, v4, v5, v6, v7, v8, v9 +#define TINYFORMAT_PASSARGS_TAIL_10 , v2, v3, v4, v5, v6, v7, v8, v9, v10 +#define TINYFORMAT_PASSARGS_TAIL_11 , v2, v3, v4, v5, v6, v7, v8, v9, v10, v11 +#define TINYFORMAT_PASSARGS_TAIL_12 \ + , v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12 +#define TINYFORMAT_PASSARGS_TAIL_13 \ + , v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13 +#define TINYFORMAT_PASSARGS_TAIL_14 \ + , v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14 +#define TINYFORMAT_PASSARGS_TAIL_15 \ + , v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15 +#define TINYFORMAT_PASSARGS_TAIL_16 \ + , v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16 + +#define TINYFORMAT_FOREACH_ARGNUM(m) \ + m(1) m(2) m(3) m(4) m(5) m(6) m(7) m(8) m(9) m(10) m(11) m(12) m(13) m(14) \ + m(15) m(16) +//[[[end]]] + +namespace detail { + +// Type-opaque holder for an argument to format(), with associated actions on +// the type held as explicit function pointers. This allows FormatArg's for +// each argument to be allocated as a homogenous array inside FormatList +// whereas a naive implementation based on inheritance does not. +class FormatArg { +public: + FormatArg() {} + + template + FormatArg(const T &value) + : m_value(static_cast(&value)), + m_formatImpl(&formatImpl), + m_toIntImpl(&toIntImpl) {} + + void format(std::ostream &out, + const char *fmtBegin, + const char *fmtEnd, + int ntrunc) const { + m_formatImpl(out, fmtBegin, fmtEnd, ntrunc, m_value); + } + + int toInt() const { return m_toIntImpl(m_value); } + +private: + template + static void formatImpl(std::ostream &out, + const char *fmtBegin, + const char *fmtEnd, + int ntrunc, + const void *value) { + formatValue(out, fmtBegin, fmtEnd, ntrunc, *static_cast(value)); + } + + template + static int toIntImpl(const void *value) { + return convertToInt::invoke(*static_cast(value)); + } + + const void *m_value; + void (*m_formatImpl)(std::ostream &out, + const char *fmtBegin, + const char *fmtEnd, + int ntrunc, + const void *value); + int (*m_toIntImpl)(const void *value); +}; + +// Parse and return an integer from the string c, as atoi() +// On return, c is set to one past the end of the integer. +inline int parseIntAndAdvance(const char *&c) { + int i = 0; + for (; *c >= '0' && *c <= '9'; ++c) i = 10 * i + (*c - '0'); + return i; +} + +// Print literal part of format string and return next format spec +// position. +// +// Skips over any occurrences of '%%', printing a literal '%' to the +// output. The position of the first % character of the next +// nontrivial format spec is returned, or the end of string. +inline const char *printFormatStringLiteral(std::ostream &out, + const char *fmt) { + const char *c = fmt; + for (;; ++c) { + switch (*c) { + case '\0': + out.write(fmt, c - fmt); + return c; + case '%': + out.write(fmt, c - fmt); + if (*(c + 1) != '%') return c; + // for "%%", tack trailing % onto next literal section. + fmt = ++c; + break; + default: + break; + } + } +} + +// Parse a format string and set the stream state accordingly. +// +// The format mini-language recognized here is meant to be the one from C99, +// with the form "%[flags][width][.precision][length]type". +// +// Formatting options which can't be natively represented using the ostream +// state are returned in spacePadPositive (for space padded positive numbers) +// and ntrunc (for truncating conversions). argIndex is incremented if +// necessary to pull out variable width and precision . The function returns a +// pointer to the character after the end of the current format spec. +inline const char *streamStateFromFormat(std::ostream &out, + bool &spacePadPositive, + int &ntrunc, + const char *fmtStart, + const detail::FormatArg *formatters, + int &argIndex, + int numFormatters) { + if (*fmtStart != '%') { + TINYFORMAT_ERROR( + "tinyformat: Not enough conversion specifiers in format string"); + return fmtStart; + } + // Reset stream state to defaults. + out.width(0); + out.precision(6); + out.fill(' '); + // Reset most flags; ignore irrelevant unitbuf & skipws. + out.unsetf(std::ios::adjustfield | std::ios::basefield | + std::ios::floatfield | std::ios::showbase | std::ios::boolalpha | + std::ios::showpoint | std::ios::showpos | std::ios::uppercase); + bool precisionSet = false; + bool widthSet = false; + int widthExtra = 0; + const char *c = fmtStart + 1; + // 1) Parse flags + for (;; ++c) { + switch (*c) { + case '#': + out.setf(std::ios::showpoint | std::ios::showbase); + continue; + case '0': + // overridden by left alignment ('-' flag) + if (!(out.flags() & std::ios::left)) { + // Use internal padding so that numeric values are + // formatted correctly, eg -00010 rather than 000-10 + out.fill('0'); + out.setf(std::ios::internal, std::ios::adjustfield); + } + continue; + case '-': + out.fill(' '); + out.setf(std::ios::left, std::ios::adjustfield); + continue; + case ' ': + // overridden by show positive sign, '+' flag. + if (!(out.flags() & std::ios::showpos)) spacePadPositive = true; + continue; + case '+': + out.setf(std::ios::showpos); + spacePadPositive = false; + widthExtra = 1; + continue; + default: + break; + } + break; + } + // 2) Parse width + if (*c >= '0' && *c <= '9') { + widthSet = true; + out.width(parseIntAndAdvance(c)); + } + if (*c == '*') { + widthSet = true; + int width = 0; + if (argIndex < numFormatters) + width = formatters[argIndex++].toInt(); + else + TINYFORMAT_ERROR( + "tinyformat: Not enough arguments to read variable width"); + if (width < 0) { + // negative widths correspond to '-' flag set + out.fill(' '); + out.setf(std::ios::left, std::ios::adjustfield); + width = -width; + } + out.width(width); + ++c; + } + // 3) Parse precision + if (*c == '.') { + ++c; + int precision = 0; + if (*c == '*') { + ++c; + if (argIndex < numFormatters) + precision = formatters[argIndex++].toInt(); + else + TINYFORMAT_ERROR( + "tinyformat: Not enough arguments to read variable precision"); + } else { + if (*c >= '0' && *c <= '9') + precision = parseIntAndAdvance(c); + else if (*c == '-') // negative precisions ignored, treated as zero. + parseIntAndAdvance(++c); + } + out.precision(precision); + precisionSet = true; + } + // 4) Ignore any C99 length modifier + while (*c == 'l' || *c == 'h' || *c == 'L' || *c == 'j' || *c == 'z' || + *c == 't') + ++c; + // 5) We're up to the conversion specifier character. + // Set stream flags based on conversion specifier (thanks to the + // boost::format class for forging the way here). + bool intConversion = false; + switch (*c) { + case 'u': + case 'd': + case 'i': + out.setf(std::ios::dec, std::ios::basefield); + intConversion = true; + break; + case 'o': + out.setf(std::ios::oct, std::ios::basefield); + intConversion = true; + break; + case 'X': + out.setf(std::ios::uppercase); + case 'x': + case 'p': + out.setf(std::ios::hex, std::ios::basefield); + intConversion = true; + break; + case 'E': + out.setf(std::ios::uppercase); + case 'e': + out.setf(std::ios::scientific, std::ios::floatfield); + out.setf(std::ios::dec, std::ios::basefield); + break; + case 'F': + out.setf(std::ios::uppercase); + case 'f': + out.setf(std::ios::fixed, std::ios::floatfield); + break; + case 'G': + out.setf(std::ios::uppercase); + case 'g': + out.setf(std::ios::dec, std::ios::basefield); + // As in boost::format, let stream decide float format. + out.flags(out.flags() & ~std::ios::floatfield); + break; + case 'a': + case 'A': + TINYFORMAT_ERROR( + "tinyformat: the %a and %A conversion specs " + "are not supported"); + break; + case 'c': + // Handled as special case inside formatValue() + break; + case 's': + if (precisionSet) ntrunc = static_cast(out.precision()); + // Make %s print booleans as "true" and "false" + out.setf(std::ios::boolalpha); + break; + case 'n': + // Not supported - will cause problems! + TINYFORMAT_ERROR("tinyformat: %n conversion spec not supported"); + break; + case '\0': + TINYFORMAT_ERROR( + "tinyformat: Conversion spec incorrectly " + "terminated by end of string"); + return c; + default: + break; + } + if (intConversion && precisionSet && !widthSet) { + // "precision" for integers gives the minimum number of digits (to be + // padded with zeros on the left). This isn't really supported by the + // iostreams, but we can approximately simulate it with the width if + // the width isn't otherwise used. + out.width(out.precision() + widthExtra); + out.setf(std::ios::internal, std::ios::adjustfield); + out.fill('0'); + } + return c + 1; +} + +//------------------------------------------------------------------------------ +inline void formatImpl(std::ostream &out, + const char *fmt, + const detail::FormatArg *formatters, + int numFormatters) { + // Saved stream state + std::streamsize origWidth = out.width(); + std::streamsize origPrecision = out.precision(); + std::ios::fmtflags origFlags = out.flags(); + char origFill = out.fill(); + + for (int argIndex = 0; argIndex < numFormatters; ++argIndex) { + // Parse the format string + fmt = printFormatStringLiteral(out, fmt); + bool spacePadPositive = false; + int ntrunc = -1; + const char *fmtEnd = streamStateFromFormat(out, + spacePadPositive, + ntrunc, + fmt, + formatters, + argIndex, + numFormatters); + if (argIndex >= numFormatters) { + // Check args remain after reading any variable width/precision + TINYFORMAT_ERROR("tinyformat: Not enough format arguments"); + return; + } + const FormatArg &arg = formatters[argIndex]; + // Format the arg into the stream. + if (!spacePadPositive) + arg.format(out, fmt, fmtEnd, ntrunc); + else { + // The following is a special case with no direct correspondence + // between stream formatting and the printf() behaviour. Simulate + // it crudely by formatting into a temporary string stream and + // munging the resulting string. + std::ostringstream tmpStream; + tmpStream.copyfmt(out); + tmpStream.setf(std::ios::showpos); + arg.format(tmpStream, fmt, fmtEnd, ntrunc); + std::string result = tmpStream.str(); // allocates... yuck. + for (size_t i = 0, iend = result.size(); i < iend; ++i) + if (result[i] == '+') result[i] = ' '; + out << result; + } + fmt = fmtEnd; + } + + // Print remaining part of format string. + fmt = printFormatStringLiteral(out, fmt); + if (*fmt != '\0') + TINYFORMAT_ERROR( + "tinyformat: Too many conversion specifiers in format string"); + + // Restore stream state + out.width(origWidth); + out.precision(origPrecision); + out.flags(origFlags); + out.fill(origFill); +} + +} // namespace detail + +/// List of template arguments format(), held in a type-opaque way. +/// +/// A const reference to FormatList (typedef'd as FormatListRef) may be +/// conveniently used to pass arguments to non-template functions: All type +/// information has been stripped from the arguments, leaving just enough of a +/// common interface to perform formatting as required. +class FormatList { +public: + FormatList(detail::FormatArg *formatters, int N) + : m_formatters(formatters), m_N(N) {} + + friend void vformat(std::ostream &out, + const char *fmt, + const FormatList &list); + +private: + const detail::FormatArg *m_formatters; + int m_N; +}; + +/// Reference to type-opaque format list for passing to vformat() +typedef const FormatList &FormatListRef; + +namespace detail { + +// Format list subclass with fixed storage to avoid dynamic allocation +template +class FormatListN : public FormatList { +public: + template + FormatListN(const Args &... args) + : FormatList(&m_formatterStore[0], N), + m_formatterStore{FormatArg(args)...} { + static_assert(sizeof...(args) == N, "Number of args must be N"); + } + +private: + FormatArg m_formatterStore[N]; +}; + +// Special 0-arg version - MSVC says zero-sized C array in struct is nonstandard +template <> +class FormatListN<0> : public FormatList { +public: + FormatListN() : FormatList(0, 0) {} +}; + +} // namespace detail + +//------------------------------------------------------------------------------ +// Primary API functions + +/// Make type-agnostic format list from list of template arguments. +/// +/// The exact return type of this function is an implementation detail and +/// shouldn't be relied upon. Instead it should be stored as a FormatListRef: +/// +/// FormatListRef formatList = makeFormatList( /*...*/ ); +template +detail::FormatListN makeFormatList(const Args &... args) { + return detail::FormatListN(args...); +} + +/// Format list of arguments to the stream according to the given format string. +/// +/// The name vformat() is chosen for the semantic similarity to vprintf(): the +/// list of format arguments is held in a single function argument. +inline void vformat(std::ostream &out, const char *fmt, FormatListRef list) { + detail::formatImpl(out, fmt, list.m_formatters, list.m_N); +} + +/// Format list of arguments to the stream according to given format string. +template +void format(std::ostream &out, const char *fmt, const Args &... args) { + vformat(out, fmt, makeFormatList(args...)); +} + +/// Format list of arguments according to the given format string and return +/// the result as a string. +template +std::string format(const char *fmt, const Args &... args) { + std::ostringstream oss; + format(oss, fmt, args...); + return oss.str(); +} + +/// Format list of arguments to std::cout, according to the given format string +template +void printf(const char *fmt, const Args &... args) { + format(std::cout, fmt, args...); +} + +template +void printfln(const char *fmt, const Args &... args) { + format(std::cout, fmt, args...); + std::cout << '\n'; +} + +} // namespace tinyformat +} // namespace string +} // namespace paddle -- GitLab From 098f956c098f5980a231575ed663b4704b6df5c5 Mon Sep 17 00:00:00 2001 From: Yi Wang Date: Wed, 28 Jun 2017 17:36:32 -0700 Subject: [PATCH 0897/3256] Correct function implementation dependencies --- paddle/string/printf.h | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/paddle/string/printf.h b/paddle/string/printf.h index 0767f8f5b58..8b5ce63a8e8 100644 --- a/paddle/string/printf.h +++ b/paddle/string/printf.h @@ -80,25 +80,19 @@ namespace string { template void Fprintf(std::ostream& out, const char* fmt, const Args&... args) { - tinyformat::vformat(out, fmt, makeFormatList(args...)); + tinyformat::vformat(out, fmt, tinyformat::makeFormatList(args...)); } template std::string Sprintf(const char* fmt, const Args&... args) { std::ostringstream oss; - tinyformat::format(oss, fmt, args...); + Fprintf(oss, fmt, args...); return oss.str(); } template -void printf(const char* fmt, const Args&... args) { - tinyformat::format(std::cout, fmt, args...); -} - -template -void printfln(const char* fmt, const Args&... args) { - tinyformat::format(std::cout, fmt, args...); - std::cout << '\n'; +void Printf(const char* fmt, const Args&... args) { + Fprintf(std::cout, fmt, args...); } } // namespace string -- GitLab From 5157ba692d53657c96f41c0a380219fe7a7a6b5a Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Wed, 28 Jun 2017 20:25:56 +0000 Subject: [PATCH 0898/3256] create save model design doc --- doc/design/cluster_train/save_model.md | 100 +++++++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 doc/design/cluster_train/save_model.md diff --git a/doc/design/cluster_train/save_model.md b/doc/design/cluster_train/save_model.md new file mode 100644 index 00000000000..3a9a24fb9ce --- /dev/null +++ b/doc/design/cluster_train/save_model.md @@ -0,0 +1,100 @@ +# Design Doc: Save Model + +## Overview + +The model is the output of the training process. There are two +ways from which user can obtain a model: + +- Save model triggered by user code: user code asks PaddlePaddle to + save a model. +- Convert model from the snapshot: model being converted from + pservers' periodic snapshot. In this way, the user can cancel a job + at any time, and still have a relatively fresh model (we snapshot + around every 5 minutes). + +### Save Model Triggered by User Code + +Both trainers and pservers have access to the model. So the model can +be saved from a trainer or pservers. We need to decide on where the +model is saved from. + +#### Dense Model vs. Sparse Model + +There are two types of model: dense and sparse model (when the +parameter is configured to be sparse). Pservers always jointly have +the entire model at any given time. Trainers only have the entire +dense model, but only have a fraction of the sparse model at any given +time. + +#### Pservers Saving Model + +The benefit of letting pservers save model is they have the entire +model all the time. However, since pservers are on different nodes, it +requires a merging process to merge model shards into the same +model. Thus requires the pservers to write models to a distributed +filesystem, making the snapshot shards visible to the merge program. + +#### Trainer Saving Model + +The benefit of letting one trainer to save the model is it does not +require a distributed filesystem. And it's reusing the same save model +logic when the trainer is training locally - except when training +sparse model, the trainer needs to download the entire sparse model +during the saving process. + +#### Conclusion + +Given trainer saving model does not require a distributed filesystem, +and is an intuitive extension to training locally, we decide to let +the trainer save the model. + + +### Convert Model from Snapshot + +TODO + + +## Timeline + +We first implement trainer save the model. Converting the latest +snapshot to a model will be a TODO for future. + + +## Trainer Save Model + +### Trainer Election + +One trainer will be elected as the one to save the model. When using +etcd, trainer ID is a randomly generated UUID, we will utilize etcd to +elect one trainer. When not using etcd, unique trainer IDs will be +given by the administrator, the trainer whose ID is "0" is elected to +save the model. + +### Model Save Path + +Each trainer will be given the directory to save the model. The +elected trainer will save the model to +`given-directory/trainerID`. Since the tainerID is unique, this would +prevent concurrent save to the same file when multiple trainers are +elected to save the model when split-brain problem happens. + +### What Happens When Model Is Saving + +It takes some time to save model, we need to define what will happen +when save model is taking place. + +When saving a dense model, the trainer uses the local model. Pservers +does not need to pause model update. + +When saving a sparse model. The trainer needs to download the entire +sparse model while saving. To get the most accurate model, the model +update needs to be paused before the download starts and resumed after +the download finishes. Otherwise, the trainer gets a model that is +"polluted": some part of the model is old, some part of the model is +new. + +It's unclear that the "polluted" model will be inferiod due to the +stochastic nature of deep learning, and pausing the model update will +add more complexity to the system. Since supporting sparse model is a +TODO item. We defer the evaluation of pause the model update or not +during saving model to the future. -- GitLab From fc3d03142582dcd673cc97fb3b0239bac59815f4 Mon Sep 17 00:00:00 2001 From: gongweibao Date: Thu, 29 Jun 2017 09:38:25 +0800 Subject: [PATCH 0899/3256] first add --- go/master/c/client.go | 5 ++ go/master/client.go | 3 +- python/paddle/v2/master/client.py | 3 ++ python/paddle/v2/reader/creator.py | 49 ++++++++++++++----- python/paddle/v2/reader/tests/creator_test.py | 2 +- 5 files changed, 49 insertions(+), 13 deletions(-) diff --git a/go/master/c/client.go b/go/master/c/client.go index b186474dc33..b88911b858f 100644 --- a/go/master/c/client.go +++ b/go/master/c/client.go @@ -88,7 +88,12 @@ func paddle_set_dataset(client C.paddle_master_client, path **C.char, size C.int func paddle_next_record(client C.paddle_master_client, record **C.uchar) C.int { c := get(client) r := c.NextRecord() + if r == nil { + // EOF + return -1 + } if len(r) == 0 { + // Empty record *record = (*C.uchar)(nullPtr) return 0 } diff --git a/go/master/client.go b/go/master/client.go index 8451820c196..4f8df5ba66c 100644 --- a/go/master/client.go +++ b/go/master/client.go @@ -60,6 +60,7 @@ func (c *Client) getRecords() { } err = f.Close() + c.ch <- nil if err != nil { log.Errorln(err) } @@ -112,7 +113,7 @@ func (c *Client) monitorMaster(addr Addresser) { // // SetDataset can be call multiple times from different nodes. But // only the first call will be honored. -func (c *Client) SetDataset(globPaths []string) error { +func (c *Client) SetDataset(globPaths ...string) error { return c.conn.Call("Service.SetDataset", globPaths, nil) } diff --git a/python/paddle/v2/master/client.py b/python/paddle/v2/master/client.py index de8e9bb88e1..9fd3ef08600 100644 --- a/python/paddle/v2/master/client.py +++ b/python/paddle/v2/master/client.py @@ -30,6 +30,9 @@ class client(object): p = ctypes.c_char_p() ret = ctypes.pointer(p) size = lib.paddle_next_record(self.c, ret) + if size < 0: + # EOF + return None if size == 0: # Empty record return "" diff --git a/python/paddle/v2/reader/creator.py b/python/paddle/v2/reader/creator.py index 9f888b16d6b..669867fd10b 100644 --- a/python/paddle/v2/reader/creator.py +++ b/python/paddle/v2/reader/creator.py @@ -57,22 +57,49 @@ def text_file(path): return reader -def recordio(path): +def recordio_local(paths): """ - Creates a data reader that outputs record one one by one from given recordio file - :path: path of recordio file - :returns: data reader of recordio file + Creates a data reader that outputs record one one by one + from given local recordio fils path. + :path: path of recordio files. + :returns: data reader of recordio files. """ import recordio as rec def reader(): - f = rec.reader(path) - while True: - r = f.read() - if r is None: - break - yield r - f.close() + for i, path in enumerate(paths): + f = rec.reader(path) + while True: + r = f.read() + if r is None: + break + yield r + f.close() return reader + + +def recordio(paths, addr="", buf_size=100): + """ + Creates a data reader that outputs record one one by one + from given local or cloud recordio path. + :path: path of recordio files. + :returns: data reader of recordio files. + """ + import os + import paddle.v2.master.client as cloud + + if len(os.environ["KUBERNETES_SERVICE_HOST"]) == 0: + return recordio_local(path) + + c = cloud(addr, buf_size) + c.set_dataset(paths) + + while True: + r = client.next_record() + if r is None: + break + yield r + + c.close() diff --git a/python/paddle/v2/reader/tests/creator_test.py b/python/paddle/v2/reader/tests/creator_test.py index ba4f558874a..b42d273ecfe 100644 --- a/python/paddle/v2/reader/tests/creator_test.py +++ b/python/paddle/v2/reader/tests/creator_test.py @@ -38,7 +38,7 @@ class TestRecordIO(unittest.TestCase): def test_recordio(self): path = os.path.join( os.path.dirname(__file__), "test_recordio_creator.dat") - reader = paddle.v2.reader.creator.recordio(path) + reader = paddle.v2.reader.creator.recordio([path]) for idx, r in enumerate(reader()): self.assertSequenceEqual(r, str(idx)) -- GitLab From 48fdfd5a0350fea69d070716bd9c74ff56ebbc24 Mon Sep 17 00:00:00 2001 From: gongweibao Date: Thu, 29 Jun 2017 09:40:29 +0800 Subject: [PATCH 0900/3256] fix bugs --- python/paddle/v2/dataset/uci_housing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/paddle/v2/dataset/uci_housing.py b/python/paddle/v2/dataset/uci_housing.py index c715ea96819..ec10ce646eb 100644 --- a/python/paddle/v2/dataset/uci_housing.py +++ b/python/paddle/v2/dataset/uci_housing.py @@ -14,7 +14,7 @@ """ UCI Housing dataset. -This module will paddle.v2.dataset.common.download dataset from +This module will download dataset from https://archive.ics.uci.edu/ml/machine-learning-databases/housing/ and parse training set and test set into paddle reader creators. """ -- GitLab From 0e6ddcc7bc63eb6ddfe5f12f4d9060625befe41a Mon Sep 17 00:00:00 2001 From: liaogang Date: Thu, 29 Jun 2017 10:01:10 +0800 Subject: [PATCH 0901/3256] ENH: Add GPU throw error --- paddle/platform/error.h | 87 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 paddle/platform/error.h diff --git a/paddle/platform/error.h b/paddle/platform/error.h new file mode 100644 index 00000000000..93424bb6109 --- /dev/null +++ b/paddle/platform/error.h @@ -0,0 +1,87 @@ +#pragma once + +#include +#include +#include + +#ifndef PADDLE_ONLY_CPU + +#include +#include +#include +#include +#include + +#endif // PADDLE_ONLY_CPU + +namespace paddle { +namespace platform { + +#ifndef PADDLE_ONLY_CPU + +inline void throw_on_error(cudaError_t e, const char* message) { + if (e) { + throw thrust::system_error(e, thrust::cuda_category(), message); + } +} + +inline void throw_on_error(curandStatus_t stat, const char* message) { + if (stat != CURAND_STATUS_SUCCESS) { + throw thrust::system_error(cudaErrorLaunchFailure, thrust::cuda_category(), + message); + } +} + +inline void throw_on_error(cudnnStatus_t stat, const char* message) { + std::stringstream ss; + if (stat == CUDNN_STATUS_SUCCESS) { + return; + } else { + ss << cudnnGetErrorString(stat); + ss << ", " << message; + throw std::runtime_error(ss.str()); + } +} + +inline void throw_on_error(cublasStatus_t stat, const char* message) { + std::stringstream ss; + if (stat == CUBLAS_STATUS_SUCCESS) { + return; + } else if (stat == CUBLAS_STATUS_NOT_INITIALIZED) { + ss << "CUBLAS: not initialized"; + } else if (stat == CUBLAS_STATUS_ALLOC_FAILED) { + ss << "CUBLAS: alloc failed"; + } else if (stat == CUBLAS_STATUS_INVALID_VALUE) { + ss << "CUBLAS: invalid value"; + } else if (stat == CUBLAS_STATUS_ARCH_MISMATCH) { + ss << "CUBLAS: arch mismatch"; + } else if (stat == CUBLAS_STATUS_MAPPING_ERROR) { + ss << "CUBLAS: mapping error"; + } else if (stat == CUBLAS_STATUS_EXECUTION_FAILED) { + ss << "CUBLAS: execution failed"; + } else if (stat == CUBLAS_STATUS_INTERNAL_ERROR) { + ss << "CUBLAS: internal error"; + } else if (stat == CUBLAS_STATUS_NOT_SUPPORTED) { + ss << "CUBLAS: not supported"; + } else if (stat == CUBLAS_STATUS_LICENSE_ERROR) { + ss << "CUBLAS: license error"; + } + ss << ", " << message; + throw std::runtime_error(ss.str()); +} + +inline void throw_on_error(cublasStatus_t stat) { + const char* message = ""; + throw_on_error(stat, message); +} + +#endif // PADDLE_ONLY_CPU + +inline void throw_on_error(int stat, const char* message) { + if (stat) { + throw std::runtime_error(message + (", stat = " + std::to_string(stat))); + } +} + +} // namespace platform +} // namespace paddle -- GitLab From c5e8e27ba6783a947965900931ed41c9ef2123fb Mon Sep 17 00:00:00 2001 From: dongzhihong Date: Thu, 29 Jun 2017 10:02:20 +0800 Subject: [PATCH 0902/3256] "change empty gradient to error" --- go/pserver/client.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/go/pserver/client.go b/go/pserver/client.go index a8d2d710d82..6938b9d5ce6 100644 --- a/go/pserver/client.go +++ b/go/pserver/client.go @@ -1,6 +1,7 @@ package pserver import ( + "errors" "hash/fnv" "sort" "time" @@ -124,8 +125,7 @@ func (c *Client) FinishInitParams() error { // parameters. func (c *Client) SendGrads(grads []Gradient) error { if len(grads) == 0 { - log.Info("Send Empty Gradient") - return nil + return errors.New("no gradient received") } errCh := make(chan error, len(grads)) for _, g := range grads { -- GitLab From 11a8dfe78e1626a6535a1d0ba8220c2dd3fa050c Mon Sep 17 00:00:00 2001 From: dangqingqing Date: Thu, 29 Jun 2017 10:57:40 +0800 Subject: [PATCH 0903/3256] Use Parameters.from_tar for static method. --- python/paddle/v2/parameters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/paddle/v2/parameters.py b/python/paddle/v2/parameters.py index f730ea10bb5..4c4ff4c7c21 100644 --- a/python/paddle/v2/parameters.py +++ b/python/paddle/v2/parameters.py @@ -300,7 +300,7 @@ class Parameters(object): return params def init_from_tar(self, f): - tar_param = self.from_tar(f) + tar_param = Parameters.from_tar(f) for pname in tar_param.names(): if pname in self.names(): self.set(pname, tar_param.get(pname)) -- GitLab From f0a3fb6e36e06512d537068ecd7c5f553a88da83 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Thu, 29 Jun 2017 11:10:59 +0800 Subject: [PATCH 0904/3256] Using paddle::string in enforce --- paddle/framework/CMakeLists.txt | 3 +- paddle/{platform => framework}/enforce.h | 63 +++---------------- .../{platform => framework}/enforce_test.cc | 9 ++- paddle/platform/CMakeLists.txt | 1 - 4 files changed, 14 insertions(+), 62 deletions(-) rename paddle/{platform => framework}/enforce.h (60%) rename paddle/{platform => framework}/enforce_test.cc (82%) diff --git a/paddle/framework/CMakeLists.txt b/paddle/framework/CMakeLists.txt index e3c3155aa90..b06ecc26286 100644 --- a/paddle/framework/CMakeLists.txt +++ b/paddle/framework/CMakeLists.txt @@ -1,6 +1,5 @@ cc_library(ddim SRCS ddim.cc) cc_test(ddim_test SRCS ddim_test.cc DEPS ddim) - nv_test(dim_test SRCS dim_test.cu DEPS ddim) - cc_test(variable_test SRCS variable_test.cc) +cc_test(enforce_test SRCS enforce_test.cc) diff --git a/paddle/platform/enforce.h b/paddle/framework/enforce.h similarity index 60% rename from paddle/platform/enforce.h rename to paddle/framework/enforce.h index fbd3405a24f..56cb7f95647 100644 --- a/paddle/platform/enforce.h +++ b/paddle/framework/enforce.h @@ -10,11 +10,12 @@ See the License for the specific language governing permissions and limitations under the License. */ #pragma once +#include #include #include namespace paddle { -namespace platform { +namespace framework { /** * @brief Enforce exception. Inherits std::exception @@ -23,10 +24,9 @@ namespace platform { */ class EnforceNotMet : public std::exception { public: - EnforceNotMet(const std::string& msg, const char* file, int fileline) - : file_(file), fileline_(fileline) { + EnforceNotMet(const std::string& msg, const char* file, int fileline) { std::ostringstream sout; - sout << msg << " at [" << file_ << ":" << fileline_ << "];"; + sout << msg << " at [" << file << ":" << fileline << "];"; all_msg_ = sout.str(); } @@ -34,52 +34,8 @@ class EnforceNotMet : public std::exception { private: std::string all_msg_; - const char* file_; - int fileline_; }; -namespace details { - -inline void MakeStringInternal(std::ostringstream& stream) {} - -template -inline void MakeStringInternal(std::ostringstream& stream, T v) { - stream << v; -} - -template -inline void MakeStringInternal(std::ostringstream& stream, T v, ARGS... args) { - MakeStringInternal(stream, v); - MakeStringInternal(stream, args...); -}; - -/** - * @brief Make string will concat all args into a string. - */ -template -inline std::string MakeString(ARGS... args) { - std::ostringstream sout; - details::MakeStringInternal(sout, args...); - return sout.str(); -} - -/** - * @brief special handle string - */ -template <> -inline std::string MakeString(std::string str) { - return str; -} - -/** - * @brief special handle const char* - */ -template <> -inline std::string MakeString(const char* str) { - return std::string(str); -} -} // namespace details - // From https://stackoverflow.com/questions/30130930/ // __buildin_expect is in C++ 11 standard. Since the condition which enforced // should be true in most situation, it will make the compiler generate faster @@ -93,11 +49,10 @@ inline std::string MakeString(const char* str) { * This macro take __VA_ARGS__, user can pass any type if that type can * serialize to std::ostream */ -#define PADDLE_THROW(...) \ - do { \ - throw ::paddle::platform::EnforceNotMet( \ - ::paddle::platform::details::MakeString(__VA_ARGS__), __FILE__, \ - __LINE__); \ +#define PADDLE_THROW(...) \ + do { \ + throw ::paddle::framework::EnforceNotMet( \ + ::paddle::string::Sprintf(__VA_ARGS__), __FILE__, __LINE__); \ } while (0) /** @@ -110,5 +65,5 @@ inline std::string MakeString(const char* str) { } \ } while (0) -} // namespace platform +} // namespace framework } // namespace paddle diff --git a/paddle/platform/enforce_test.cc b/paddle/framework/enforce_test.cc similarity index 82% rename from paddle/platform/enforce_test.cc rename to paddle/framework/enforce_test.cc index 23b32444add..f8da1a192f6 100644 --- a/paddle/platform/enforce_test.cc +++ b/paddle/framework/enforce_test.cc @@ -10,10 +10,10 @@ See the License for the specific language governing permissions and limitations under the License. */ #include -#include +#include TEST(ENFORCE, OK) { - PADDLE_ENFORCE(true, "Enforce is ok", 123, "now", 0.345); + PADDLE_ENFORCE(true, "Enforce is ok %d now %f", 123, 0.345); size_t val = 1; const size_t limit = 10; PADDLE_ENFORCE(val < limit, "Enforce is OK too"); @@ -22,8 +22,8 @@ TEST(ENFORCE, OK) { TEST(ENFORCE, FAILED) { bool in_catch = false; try { - PADDLE_ENFORCE(false, "Enforce is not ok ", 123, " at all"); - } catch (paddle::platform::EnforceNotMet err) { + PADDLE_ENFORCE(false, "Enforce is not ok %d at all", 123); + } catch (paddle::framework::EnforceNotMet err) { in_catch = true; std::string msg = "Enforce is not ok 123 at all"; const char* what = err.what(); @@ -31,6 +31,5 @@ TEST(ENFORCE, FAILED) { ASSERT_EQ(what[i], msg[i]); } } - ASSERT_TRUE(in_catch); } \ No newline at end of file diff --git a/paddle/platform/CMakeLists.txt b/paddle/platform/CMakeLists.txt index bc72e62be41..c7d7b14518e 100644 --- a/paddle/platform/CMakeLists.txt +++ b/paddle/platform/CMakeLists.txt @@ -2,4 +2,3 @@ nv_test(cuda_test SRCS cuda_test.cu) cc_library(place SRCS place.cc) cc_test(place_test SRCS place_test.cc DEPS place glog gflags) -cc_test(enforce_test SRCS enforce_test.cc) -- GitLab From aabe171566ca455925dd5a597497106e240656cb Mon Sep 17 00:00:00 2001 From: qiaolongfei Date: Thu, 29 Jun 2017 11:32:25 +0800 Subject: [PATCH 0905/3256] merge CreateVar and GetOrCreateVar --- paddle/framework/scope.h | 21 +++++++-------------- paddle/framework/scope_test.cc | 13 +++++++++---- 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/paddle/framework/scope.h b/paddle/framework/scope.h index bb22c4b834f..88a13145ca9 100644 --- a/paddle/framework/scope.h +++ b/paddle/framework/scope.h @@ -19,7 +19,6 @@ limitations under the License. */ #include #include "paddle/framework/variable.h" -#include "paddle/platform/assert.h" namespace paddle { namespace framework { @@ -44,9 +43,13 @@ class Scope { /// Create Variable in this Scope. Failed if Variable already been /// created. Variable* CreateVariable(const std::string& name) { - PADDLE_ASSERT(!HasVariable(name)); - vars_[name] = std::unique_ptr(new Variable()); - return GetVariable(name); + auto var = GetVariable(name); + if (var) { + return var; + } else { + vars_[name] = std::unique_ptr(new Variable()); + return GetVariable(name); + } } /// Get Variable from this Scope, this function will recursive find Variable @@ -62,16 +65,6 @@ class Scope { } } - /// Get Variable from scope, if Variable is not exist, creat one and return. - Variable* GetOrCreateVariable(const std::string& name) { - auto var = GetVariable(name); - if (var) { - return var; - } else { - return CreateVariable(name); - } - } - /// Find if there is a Variable in this scope and it's parent scope bool HasVariable(const std::string& name) const { return (vars_.find(name) != vars_.end() || diff --git a/paddle/framework/scope_test.cc b/paddle/framework/scope_test.cc index d73391d9770..ec6236ec621 100644 --- a/paddle/framework/scope_test.cc +++ b/paddle/framework/scope_test.cc @@ -24,18 +24,22 @@ TEST(Scope, Create) { Variable* var0 = scope->CreateVariable(""); EXPECT_NE(var0, nullptr); + /// GetVariable will return nullptr if not exist. Variable* var1 = scope->GetVariable("a"); EXPECT_EQ(var1, nullptr); + /// CreateVariable will return one. Variable* var2 = scope->CreateVariable("a"); + EXPECT_NE(var2, nullptr); - ASSERT_DEATH({ scope->CreateVariable("a"); }, ""); - + /// Get the created variable. Variable* var3 = scope->GetVariable("a"); EXPECT_EQ(var2, var3); - Variable* var4 = scope->GetOrCreateVariable("a"); - EXPECT_EQ(var2, var4); + /// CreateVariable will just return the variable if it's + /// already exist. + Variable* var4 = scope->CreateVariable("a"); + EXPECT_EQ(var4, var2); } TEST(Scope, Parent) { @@ -48,6 +52,7 @@ TEST(Scope, Parent) { Variable* var0 = parent_scope->CreateVariable("a"); EXPECT_NE(var0, nullptr); + /// GetVariable will get Variable from parent scope if exist. Variable* var1 = scope->GetVariable("a"); EXPECT_EQ(var0, var1); } -- GitLab From 456f9cc89f6ec5a80e08522ff1fafb8f20a21fa6 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Thu, 29 Jun 2017 12:11:03 +0800 Subject: [PATCH 0906/3256] Remove Python protobuf function --- cmake/generic.cmake | 23 ----------------------- proto/CMakeLists.txt | 19 ++++++++++++++++++- 2 files changed, 18 insertions(+), 24 deletions(-) diff --git a/cmake/generic.cmake b/cmake/generic.cmake index 24a07c0a241..8736d30059e 100644 --- a/cmake/generic.cmake +++ b/cmake/generic.cmake @@ -357,26 +357,3 @@ function(pb_cc_library TARGET_NAME) include_directories(${CMAKE_CURRENT_BINARY_DIR}) cc_library(${TARGET_NAME} SRCS ${proto_srcs}) endfunction() - -function(pb_py_library TARGET_NAME) - set(oneValueArgs TARGET_DIR) - set(multiValueArgs SRCS) - cmake_parse_arguments(pb_py_library "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) - if (NOT pb_py_library_TARGET_DIR) - set(pb_py_library_TARGET_DIR ${CMAKE_CURRENT_BINARY_DIR}) - endif() - set(py_srcs) - foreach(FIL ${pb_py_library_SRCS}) - get_filename_component(ABS_FIL ${FIL} ABSOLUTE) - get_filename_component(FIL_WE ${FIL} NAME_WE) - set(cur_py_src ${pb_py_library_TARGET_DIR}/${FIL_WE}_pb2.py) - list(APPEND py_srcs "${cur_py_src}") - add_custom_command(OUTPUT ${cur_py_src} - COMMAND ${PROTOBUF_PROTOC_EXECUTABLE} - ARGS "--python_out=${pb_py_library_TARGET_DIR}" "-I" ${CMAKE_CURRENT_SOURCE_DIR} ${ABS_FIL} - DEPENDS ${ABS_FIL} protoc - COMMENT "Running Python protocol buffer compiler on ${FIL}") - endforeach() - - add_custom_target(${TARGET_NAME} ALL DEPENDS ${py_srcs}) -endfunction() \ No newline at end of file diff --git a/proto/CMakeLists.txt b/proto/CMakeLists.txt index 4402f2c8994..1cf39d69443 100644 --- a/proto/CMakeLists.txt +++ b/proto/CMakeLists.txt @@ -1,3 +1,20 @@ file(GLOB proto_filenames . *.proto) pb_cc_library(paddle_proto SRCS ${proto_filenames}) -pb_py_library(gen_proto_py SRCS ${proto_filenames} TARGET_DIR ${PROJ_ROOT}/python/paddle/proto) + +set(PROTO_GEN) +set(PROTO_GEN_PY) + +foreach(filename ${proto_filenames}) + get_filename_component(base_filename ${filename} NAME_WE) + set(CUR_PROTO_GEN_PY + ${PROJ_ROOT}/paddle/python/paddle/proto/${base_filename}_pb2.py) + set(PROTO_GEN_PY + ${CUR_PROTO_GEN_PY} + ${PROTO_GEN_PY}) + add_custom_command(OUTPUT ${CUR_PROTO_GEN_PY} + COMMAND env ${py_env} ${PROTOBUF_PROTOC_EXECUTABLE} --python_out ${PROJ_ROOT}/python/paddle/proto + --proto_path ${PROJ_ROOT}/proto ${PROJ_ROOT}/proto/${filename} + DEPENDS ${filename} ${external_project_dependencies}) +endforeach() + +add_custom_target(gen_proto_py ALL DEPENDS ${PROTO_GEN_PY}) -- GitLab From 4874810ba5a1e6f8f6b4a9530e6854f65077a59e Mon Sep 17 00:00:00 2001 From: gongweibao Date: Thu, 29 Jun 2017 04:28:44 +0000 Subject: [PATCH 0907/3256] fix bugs --- go/master/client.go | 2 +- python/paddle/v2/reader/creator.py | 20 ++++++++++++-------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/go/master/client.go b/go/master/client.go index 4f8df5ba66c..fa479338c59 100644 --- a/go/master/client.go +++ b/go/master/client.go @@ -113,7 +113,7 @@ func (c *Client) monitorMaster(addr Addresser) { // // SetDataset can be call multiple times from different nodes. But // only the first call will be honored. -func (c *Client) SetDataset(globPaths ...string) error { +func (c *Client) SetDataset(globPaths []string) error { return c.conn.Call("Service.SetDataset", globPaths, nil) } diff --git a/python/paddle/v2/reader/creator.py b/python/paddle/v2/reader/creator.py index 669867fd10b..3376d7accb4 100644 --- a/python/paddle/v2/reader/creator.py +++ b/python/paddle/v2/reader/creator.py @@ -93,13 +93,17 @@ def recordio(paths, addr="", buf_size=100): if len(os.environ["KUBERNETES_SERVICE_HOST"]) == 0: return recordio_local(path) - c = cloud(addr, buf_size) - c.set_dataset(paths) + def reader(): + c = cloud(addr, buf_size) + c.set_dataset(paths) + + while True: + r = client.next_record() + if r is None: + break + yield r - while True: - r = client.next_record() - if r is None: - break - yield r + c.close() + + return reader - c.close() -- GitLab From 32d6587242e5a2e97fe5b9e675273fa96fd99c5a Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Thu, 29 Jun 2017 12:28:48 +0800 Subject: [PATCH 0908/3256] Use protobuf_generate_cpp --- cmake/external/protobuf.cmake | 7 +++++++ cmake/generic.cmake | 17 +---------------- 2 files changed, 8 insertions(+), 16 deletions(-) diff --git a/cmake/external/protobuf.cmake b/cmake/external/protobuf.cmake index 891fb291187..2f267adc203 100644 --- a/cmake/external/protobuf.cmake +++ b/cmake/external/protobuf.cmake @@ -13,6 +13,10 @@ # limitations under the License. INCLUDE(ExternalProject) +# Always invoke `FIND_PACKAGE(Protobuf)` for importing function protobuf_generate_cpp +FIND_PACKAGE(Protobuf QUIET) +SET(PROTOBUF_FOUND "OFF") + # Print and set the protobuf library information, # finish this cmake process and exit from this file. @@ -44,6 +48,9 @@ macro(PROMPT_PROTOBUF_LIB) ADD_EXECUTABLE(protoc IMPORTED GLOBAL) SET_PROPERTY(TARGET protoc PROPERTY IMPORTED_LOCATION ${PROTOBUF_PROTOC_EXECUTABLE}) + # FIND_Protobuf.cmake uses `Protobuf_PROTOC_EXECUTABLE`. + # make `protobuf_generate_cpp` happy. + SET(Protobuf_PROTOC_EXECUTABLE ${PROTOBUF_PROTOC_EXECUTABLE}) FOREACH(dep ${protobuf_DEPS}) ADD_DEPENDENCIES(protobuf ${dep}) diff --git a/cmake/generic.cmake b/cmake/generic.cmake index 8736d30059e..cdf917a1e99 100644 --- a/cmake/generic.cmake +++ b/cmake/generic.cmake @@ -338,22 +338,7 @@ function(pb_cc_library TARGET_NAME) cmake_parse_arguments(pb_cc_library "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) set(proto_srcs) set(proto_hdrs) - foreach(FIL ${pb_cc_library_SRCS}) - get_filename_component(ABS_FIL ${FIL} ABSOLUTE) - get_filename_component(FIL_WE ${FIL} NAME_WE) - list(APPEND proto_srcs "${CMAKE_CURRENT_BINARY_DIR}/${FIL_WE}.pb.cc") - list(APPEND proto_hdrs "${CMAKE_CURRENT_BINARY_DIR}/${FIL_WE}.pb.h") - - add_custom_command( - OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${FIL_WE}.pb.cc" - "${CMAKE_CURRENT_BINARY_DIR}/${FIL_WE}.pb.h" - COMMAND ${PROTOBUF_PROTOC_EXECUTABLE} - ARGS "--cpp_out=${DLL_EXPORT_DECL}${CMAKE_CURRENT_BINARY_DIR}" "-I" ${CMAKE_CURRENT_SOURCE_DIR} ${ABS_FIL} - DEPENDS ${ABS_FIL} protoc - COMMENT "Running C++ protocol buffer compiler on ${FIL}" - VERBATIM ) - endforeach() - set_source_files_properties(${proto_srcs} ${proto_hdrs} PROPERTIES GENERATED TRUE) + protobuf_generate_cpp(proto_srcs proto_hdrs ${pb_cc_library_SRCS}) include_directories(${CMAKE_CURRENT_BINARY_DIR}) cc_library(${TARGET_NAME} SRCS ${proto_srcs}) endfunction() -- GitLab From 23d6c594eca369820b5f4dfcd0a38a9f4cd6122e Mon Sep 17 00:00:00 2001 From: dangqingqing Date: Thu, 29 Jun 2017 12:33:07 +0800 Subject: [PATCH 0909/3256] add comments --- python/paddle/v2/parameters.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/python/paddle/v2/parameters.py b/python/paddle/v2/parameters.py index 4c4ff4c7c21..bbaf8bfa979 100644 --- a/python/paddle/v2/parameters.py +++ b/python/paddle/v2/parameters.py @@ -284,6 +284,18 @@ class Parameters(object): @staticmethod def from_tar(f): + """ + Create a `Parameters` object from the given file. And + the `Parameters` only contains the parameters in this + file. It is adapted the parameters are same in the + defined network and the given file. For example, it + can be used in the inference. + + :param f: the initialized model file. + :type f: tar file + :return: A Parameters object. + :rtype: Parameters. + """ params = Parameters() tar = tarfile.TarFile(fileobj=f, mode='r') for finfo in tar: @@ -300,6 +312,15 @@ class Parameters(object): return params def init_from_tar(self, f): + """ + Different from `from_tar`, this interface can be used to + init partial network parameters from another saved model. + + :param f: the initialized model file. + :type f: tar file + :return: Nothing. + """ + tar_param = Parameters.from_tar(f) for pname in tar_param.names(): if pname in self.names(): -- GitLab From b88ca542baa4bb5e5822912dceeb4ceb2080c660 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Thu, 29 Jun 2017 12:43:51 +0800 Subject: [PATCH 0910/3256] Rename pb_cc_library -> proto_library --- cmake/generic.cmake | 6 +++--- proto/CMakeLists.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cmake/generic.cmake b/cmake/generic.cmake index cdf917a1e99..779f6271154 100644 --- a/cmake/generic.cmake +++ b/cmake/generic.cmake @@ -332,13 +332,13 @@ function(go_test TARGET_NAME) add_test(${TARGET_NAME} ${CMAKE_CURRENT_BINARY_DIR}/${TARGET_NAME}) endfunction(go_test) -function(pb_cc_library TARGET_NAME) +function(proto_library TARGET_NAME) set(oneValueArgs "") set(multiValueArgs SRCS) - cmake_parse_arguments(pb_cc_library "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + cmake_parse_arguments(proto_library "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) set(proto_srcs) set(proto_hdrs) - protobuf_generate_cpp(proto_srcs proto_hdrs ${pb_cc_library_SRCS}) + protobuf_generate_cpp(proto_srcs proto_hdrs ${proto_library_SRCS}) include_directories(${CMAKE_CURRENT_BINARY_DIR}) cc_library(${TARGET_NAME} SRCS ${proto_srcs}) endfunction() diff --git a/proto/CMakeLists.txt b/proto/CMakeLists.txt index 1cf39d69443..436bea53e53 100644 --- a/proto/CMakeLists.txt +++ b/proto/CMakeLists.txt @@ -1,5 +1,5 @@ file(GLOB proto_filenames . *.proto) -pb_cc_library(paddle_proto SRCS ${proto_filenames}) +proto_library(paddle_proto SRCS ${proto_filenames}) set(PROTO_GEN) set(PROTO_GEN_PY) -- GitLab From 4a4ec31e0d2f07b5e29acfd1b5b0b62d40f7ab91 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Thu, 29 Jun 2017 12:53:10 +0800 Subject: [PATCH 0911/3256] Fix TravisCI --- paddle/cuda/CMakeLists.txt | 2 +- proto/CMakeLists.txt | 12 +++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/paddle/cuda/CMakeLists.txt b/paddle/cuda/CMakeLists.txt index f9061e96deb..73ffa690d9d 100755 --- a/paddle/cuda/CMakeLists.txt +++ b/paddle/cuda/CMakeLists.txt @@ -83,7 +83,7 @@ else() ${CUDA_CXX_SOURCES}) endif() -add_dependencies(paddle_cuda ${external_project_dependencies}) +add_dependencies(paddle_cuda paddle_proto ${external_project_dependencies}) add_style_check_target(paddle_cuda ${CUDA_SOURCES} diff --git a/proto/CMakeLists.txt b/proto/CMakeLists.txt index 436bea53e53..70dd4d674cd 100644 --- a/proto/CMakeLists.txt +++ b/proto/CMakeLists.txt @@ -5,16 +5,18 @@ set(PROTO_GEN) set(PROTO_GEN_PY) foreach(filename ${proto_filenames}) - get_filename_component(base_filename ${filename} NAME_WE) + get_filename_component(ABS_FIL ${filename} ABSOLUTE) + get_filename_component(FIL_WE ${filename} NAME_WE) set(CUR_PROTO_GEN_PY - ${PROJ_ROOT}/paddle/python/paddle/proto/${base_filename}_pb2.py) + ${PROJ_ROOT}/paddle/python/paddle/proto/${FIL_WE}_pb2.py) set(PROTO_GEN_PY ${CUR_PROTO_GEN_PY} ${PROTO_GEN_PY}) add_custom_command(OUTPUT ${CUR_PROTO_GEN_PY} - COMMAND env ${py_env} ${PROTOBUF_PROTOC_EXECUTABLE} --python_out ${PROJ_ROOT}/python/paddle/proto - --proto_path ${PROJ_ROOT}/proto ${PROJ_ROOT}/proto/${filename} - DEPENDS ${filename} ${external_project_dependencies}) + COMMAND ${PROTOBUF_PROTOC_EXECUTABLE} + ARGS "--python_out=${PROJ_ROOT}/python/paddle/proto" + "-I" ${CMAKE_CURRENT_SOURCE_DIR} ${ABS_FIL} + DEPENDS ${ABS_FIL} ${external_project_dependencies}) endforeach() add_custom_target(gen_proto_py ALL DEPENDS ${PROTO_GEN_PY}) -- GitLab From 9af8d86b7ceedbc244873ee5207392231bab540a Mon Sep 17 00:00:00 2001 From: Yancey Date: Thu, 29 Jun 2017 13:20:13 +0800 Subject: [PATCH 0912/3256] Trainer library discover master by etcd (#2551) * add trainer library * modifty file name * move trainer to master client * update * update * modify monitor master to receive a chan * update * use etcd client from etcd_client.go * update * update * remove etcd client without lock * update * update the comment * update commonts --- go/master/c/client.go | 30 +++++++++++++++++++++++++----- go/master/client.go | 24 ++++-------------------- go/master/client_internal_test.go | 13 ++++--------- go/master/client_test.go | 8 +++----- go/master/etcd_client.go | 28 ++++++++++++++++++++++++++++ 5 files changed, 64 insertions(+), 39 deletions(-) diff --git a/go/master/c/client.go b/go/master/c/client.go index b186474dc33..9e35e986002 100644 --- a/go/master/c/client.go +++ b/go/master/c/client.go @@ -13,10 +13,13 @@ typedef int paddle_master_client; import "C" import ( + "strings" "sync" + "time" "unsafe" "github.com/PaddlePaddle/Paddle/go/master" + "github.com/coreos/etcd/clientv3" log "github.com/sirupsen/logrus" ) @@ -48,16 +51,33 @@ func remove(client C.paddle_master_client) *master.Client { return h } -type addresser string - -func (a addresser) Address() string { - return string(a) +//export paddle_new_etcd_master_client +func paddle_new_etcd_master_client(etcdEndpoints *C.char, timeout int, bufSize int) C.paddle_master_client { + p := C.GoString(etcdEndpoints) + cli, err := clientv3.New(clientv3.Config{ + Endpoints: strings.Split(p, ","), + DialTimeout: time.Second * time.Duration(timeout), + }) + if err != nil { + panic(err) + } + ch := make(chan string, 1) + a, err := master.GetKey(cli, master.DefaultAddrPath, timeout) + if err != nil { + panic(err) + } + ch <- a + go master.WatchKey(cli, master.DefaultAddrPath, ch) + c := master.NewClient(ch, bufSize) + return add(c) } //export paddle_new_master_client func paddle_new_master_client(addr *C.char, bufSize int) C.paddle_master_client { a := C.GoString(addr) - c := master.NewClient(addresser(a), bufSize) + ch := make(chan string, 1) + ch <- a + c := master.NewClient(ch, bufSize) return add(c) } diff --git a/go/master/client.go b/go/master/client.go index 8451820c196..d3bea49d0a8 100644 --- a/go/master/client.go +++ b/go/master/client.go @@ -2,18 +2,12 @@ package master import ( "os" - "time" "github.com/PaddlePaddle/Paddle/go/connection" "github.com/PaddlePaddle/recordio" log "github.com/sirupsen/logrus" ) -// Addresser provide the address of the master server. -type Addresser interface { - Address() string -} - // Client is the client of the master server. type Client struct { conn *connection.Conn @@ -24,11 +18,11 @@ type Client struct { // // bufSize is the record buffer size. NextRecord will read from this // buffer. -func NewClient(addr Addresser, bufSize int) *Client { +func NewClient(addrCh <-chan string, bufSize int) *Client { c := &Client{} c.conn = connection.New() c.ch = make(chan []byte, bufSize) - go c.monitorMaster(addr) + go c.monitorMaster(addrCh) go c.getRecords() return c } @@ -72,12 +66,10 @@ func (c *Client) getRecords() { } } -func (c *Client) monitorMaster(addr Addresser) { +func (c *Client) monitorMaster(addrCh <-chan string) { lastMaster := "" - monitor := func() { - // get the lastest address of the master server, + for curMaster := range addrCh { // connect to the new address once address changed. - curMaster := addr.Address() if curMaster != lastMaster { if curMaster == "" { err := c.conn.Close() @@ -94,18 +86,10 @@ func (c *Client) monitorMaster(addr Addresser) { // to retry next time. curMaster = lastMaster } - } } - lastMaster = curMaster } - - monitor() - ticker := time.NewTicker(10 * time.Second) - for _ = range ticker.C { - monitor() - } } // SetDataset set dataset for the master server to dispatch. diff --git a/go/master/client_internal_test.go b/go/master/client_internal_test.go index 251225780ae..364dce7b58c 100644 --- a/go/master/client_internal_test.go +++ b/go/master/client_internal_test.go @@ -26,12 +26,6 @@ func init() { log.SetLevel(log.ErrorLevel) } -type TestAddresser string - -func (a TestAddresser) Address() string { - return string(a) -} - func TestGetFinishTask(t *testing.T) { const path = "/tmp/master_client_test_0" @@ -45,7 +39,6 @@ func TestGetFinishTask(t *testing.T) { if err != nil { panic(err) } - go func(l net.Listener) { s, err := NewService(&InMemStore{}, chunkPerTask, time.Second, 1) if err != nil { @@ -82,9 +75,11 @@ func TestGetFinishTask(t *testing.T) { // Manually intialize client to avoid calling c.getRecords() c := &Client{} c.conn = connection.New() - go c.monitorMaster(TestAddresser(fmt.Sprintf(":%d", p))) + addr := fmt.Sprintf(":%d", p) + ch := make(chan string, 1) + ch <- addr + go c.monitorMaster(ch) c.SetDataset([]string{path}) - checkOnePass := func(i int) { var tasks []Task for idx := 0; idx < totalTask; idx++ { diff --git a/go/master/client_test.go b/go/master/client_test.go index 85a86761c2e..c00aeebfd5d 100644 --- a/go/master/client_test.go +++ b/go/master/client_test.go @@ -20,7 +20,6 @@ func TestNextRecord(t *testing.T) { path = "/tmp/master_client_TestFull" total = 50 ) - l, err := net.Listen("tcp", ":0") if err != nil { panic(err) @@ -31,7 +30,6 @@ func TestNextRecord(t *testing.T) { if err != nil { panic(err) } - go func(l net.Listener) { s, err := master.NewService(&master.InMemStore{}, 10, time.Second, 1) if err != nil { @@ -63,10 +61,10 @@ func TestNextRecord(t *testing.T) { } w.Close() f.Close() - - c := master.NewClient(master.TestAddresser(fmt.Sprintf(":%d", p)), 10) + curAddr := make(chan string, 1) + curAddr <- fmt.Sprintf(":%d", p) + c := master.NewClient(curAddr, 10) c.SetDataset([]string{path}) - for pass := 0; pass < 50; pass++ { received := make(map[byte]bool) for i := 0; i < total; i++ { diff --git a/go/master/etcd_client.go b/go/master/etcd_client.go index f7b46385773..e27c014792f 100644 --- a/go/master/etcd_client.go +++ b/go/master/etcd_client.go @@ -142,3 +142,31 @@ func (e *EtcdClient) Load() ([]byte, error) { state := kvs[0].Value return state, nil } + +// GetKey gets the value by the specify key. +func GetKey(c *clientv3.Client, key string, timeout int) (string, error) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*time.Duration(timeout)) + resp, err := c.Get(ctx, key) + cancel() + if err != nil { + return "", err + } + kvs := resp.Kvs + if len(kvs) == 0 { + return "", nil + } + v := kvs[0].Value + return string(v), nil +} + +// WatchKey watches the specify key and send to valChan if there is some event. +func WatchKey(c *clientv3.Client, key string, valChan chan<- string) { + rch := c.Watch(context.Background(), key) + for wresp := range rch { + for _, ev := range wresp.Events { + // if received event is DELETE, the value will be an empty string + log.Infof("received event %s, %q : %q\n", ev.Type, ev.Kv.Key, ev.Kv.Value) + valChan <- string(ev.Kv.Value) + } + } +} -- GitLab From d3b77a5bc053b77309ecc094450e755604217674 Mon Sep 17 00:00:00 2001 From: liaogang Date: Thu, 29 Jun 2017 13:56:38 +0800 Subject: [PATCH 0913/3256] ENH: Add Gpu info --- paddle/platform/gpu_info.cc | 49 +++++++++++++++++++++++++++++++++++++ paddle/platform/gpu_info.h | 36 +++++++++++++++++++++++++++ 2 files changed, 85 insertions(+) create mode 100644 paddle/platform/gpu_info.cc create mode 100644 paddle/platform/gpu_info.h diff --git a/paddle/platform/gpu_info.cc b/paddle/platform/gpu_info.cc new file mode 100644 index 00000000000..4208d83078c --- /dev/null +++ b/paddle/platform/gpu_info.cc @@ -0,0 +1,49 @@ +/* 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/platform/gpu_info.h" +#include "gflags/gflags.h" +#include "paddle/platform/error.h" + +DEFINE_double(fraction_of_gpu_memory_to_use, 0.95, + "Default use 95% of GPU memory for PaddlePaddle," + "reserve the rest for page tables, etc"); + +namespace paddle { +namespace platform { + +int GpuDeviceCount() { + int count; + throw_on_error( + cudaGetDeviceCount(&count), + "cudaGetDeviceCount failed in paddle::platform::GpuDeviceCount"); + return count; +} + +void GpuMemoryUsage(size_t& available, size_t& total) { + throw_on_error(cudaMemGetInfo(&available, &total), + "cudaMemGetInfo failed in paddle::platform::GetMemoryUsage"); +} + +size_t GpuMaxAllocSize() { + size_t total = 0; + size_t available = 0; + + GpuMemoryUsage(available, total); + + return total * FLAGS_fraction_of_gpu_memory_to_use; +} + +} // namespace platform +} // namespace paddle diff --git a/paddle/platform/gpu_info.h b/paddle/platform/gpu_info.h new file mode 100644 index 00000000000..174f093b435 --- /dev/null +++ b/paddle/platform/gpu_info.h @@ -0,0 +1,36 @@ +/* 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 + +#ifndef PADDLE_ONLY_CPU + +#include + +namespace paddle { +namespace platform { + +//! Get the total number of GPU devices in system. +int GpuDeviceCount(); + +//!Get the memory usage of current GPU device. +void GpuMemoryUsage(size_t& available, size_t& total); + +//! Get the maximum allocation size of current GPU device. +size_t GpuMaxAllocSize(); + +} // namespace platform +} // namespace paddle + +#endif // PADDLE_ONLY_CPU -- GitLab From b29923f902dc6da1416a94bc153448f1546e62b2 Mon Sep 17 00:00:00 2001 From: liaogang Date: Thu, 29 Jun 2017 13:56:57 +0800 Subject: [PATCH 0914/3256] ENH: Add CPU info --- paddle/platform/cpu_info.cc | 55 +++++++++++++++++++++++++++++++++++++ paddle/platform/cpu_info.h | 26 ++++++++++++++++++ 2 files changed, 81 insertions(+) create mode 100644 paddle/platform/cpu_info.cc create mode 100644 paddle/platform/cpu_info.h diff --git a/paddle/platform/cpu_info.cc b/paddle/platform/cpu_info.cc new file mode 100644 index 00000000000..deff76502e8 --- /dev/null +++ b/paddle/platform/cpu_info.cc @@ -0,0 +1,55 @@ +/* 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/platform/cpu_info.h" + +#ifdef __APPLE__ +#include +#include +#else +#include +#endif + +#include "gflags/gflags.h" +#include "paddle/platform/error.h" + +DEFINE_double(fraction_of_cpu_memory_to_use, 1, + "Default use 100% of CPU memory for PaddlePaddle," + "reserve the rest for page tables, etc"); + +namespace paddle { +namespace platform { + +inline size_t CpuTotalPhysicalMemory() { +#ifdef __APPLE__ + int mib[2]; + mib[0] = CTL_HW; + mib[1] = HW_MEMSIZE; + int64_t size = 0; + size_t len = sizeof(size); + if (sysctl(mib, 2, &size, &len, NULL, 0) == 0) return (size_t)size; + return 0L; +#else + long pages = sysconf(_SC_PHYS_PAGES); + long page_size = sysconf(_SC_PAGE_SIZE); + return pages * page_size; +#endif +} + +size_t CpuTotalMemory() { + return FLAGS_fraction_of_cpu_memory_to_use * CpuTotalPhysicalMemory(); +} + +} // namespace platform +} // namespace paddle diff --git a/paddle/platform/cpu_info.h b/paddle/platform/cpu_info.h new file mode 100644 index 00000000000..3b768589e15 --- /dev/null +++ b/paddle/platform/cpu_info.h @@ -0,0 +1,26 @@ +/* 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 + +namespace paddle { +namespace platform { + +//! Get the total memory on host machine. +size_t CpuTotalMemory(); + +} // namespace platform +} // namespace paddle -- GitLab From 169022d0148a77cd10f16a82e841a75750e7e173 Mon Sep 17 00:00:00 2001 From: liaogang Date: Thu, 29 Jun 2017 14:04:47 +0800 Subject: [PATCH 0915/3256] FIX: Improve fallback gpu allocator --- paddle/memory/detail/CMakeLists.txt | 4 +- paddle/memory/detail/system_allocator.cc | 64 ++++++++++++++----- paddle/memory/detail/system_allocator.h | 15 +++-- paddle/memory/detail/system_allocator_test.cc | 14 ++-- paddle/platform/CMakeLists.txt | 4 ++ paddle/platform/cpu_info_test.cc | 18 ++++++ paddle/platform/cuda.h | 40 ------------ 7 files changed, 85 insertions(+), 74 deletions(-) create mode 100644 paddle/platform/cpu_info_test.cc delete mode 100644 paddle/platform/cuda.h diff --git a/paddle/memory/detail/CMakeLists.txt b/paddle/memory/detail/CMakeLists.txt index 72d3749ad78..6caa97a76bb 100644 --- a/paddle/memory/detail/CMakeLists.txt +++ b/paddle/memory/detail/CMakeLists.txt @@ -1,6 +1,8 @@ if(${WITH_GPU}) nv_library(system_allocator SRCS system_allocator.cc DEPS gflags) - nv_test(system_allocator_test SRCS system_allocator_test.cc DEPS system_allocator gflags) + nv_test(system_allocator_test + SRCS system_allocator_test.cc + DEPS system_allocator gpu_info gflags) else(${WITH_GPU}) cc_library(system_allocator SRCS system_allocator.cc DEPS gflags) cc_test(system_allocator_test SRCS system_allocator_test.cc DEPS system_allocator gflags) diff --git a/paddle/memory/detail/system_allocator.cc b/paddle/memory/detail/system_allocator.cc index 50bec926f83..332ff062d47 100644 --- a/paddle/memory/detail/system_allocator.cc +++ b/paddle/memory/detail/system_allocator.cc @@ -13,32 +13,39 @@ See the License for the specific language governing permissions and limitations under the License. */ #include "paddle/memory/detail/system_allocator.h" +#include "paddle/platform/assert.h" +#include "paddle/platform/error.h" +#include "paddle/platform/gpu_info.h" #include // for malloc and free #include // for mlock and munlock #include "gflags/gflags.h" -#include "paddle/platform/assert.h" -#include "paddle/platform/cuda.h" // If use_pinned_memory is true, CPUAllocator calls mlock, which // returns pinned and locked memory as staging areas for data exchange // between host and device. Allocates too much would reduce the amount // of memory available to the system for paging. So, by default, we // should set false to use_pinned_memory. -DEFINE_bool(use_pinned_memory, false, - "If set, allocate cpu/gpu pinned memory."); +DEFINE_bool(use_pinned_memory, false, "If set, allocate cpu pinned memory."); namespace paddle { namespace memory { namespace detail { -void* CPUAllocator::Alloc(size_t size) { +void* CPUAllocator::Alloc(size_t& index, size_t size) { // According to http://www.cplusplus.com/reference/cstdlib/malloc/, // malloc might not return nullptr if size is zero, but the returned // pointer shall not be dereferenced -- so we make it nullptr. if (size <= 0) return nullptr; + if (FLAGS_use_pinned_memory) { + void* p = malloc(size); + if (p != nullptr) { + mlock(p, size); + } + } + void* p = malloc(size); if (p != nullptr && FLAGS_use_pinned_memory) { mlock(p, size); @@ -46,7 +53,7 @@ void* CPUAllocator::Alloc(size_t size) { return p; } -void CPUAllocator::Free(void* p, size_t size) { +void CPUAllocator::Free(void* p, size_t size, size_t index) { if (p != nullptr && FLAGS_use_pinned_memory) { munlock(p, size); } @@ -55,29 +62,52 @@ void CPUAllocator::Free(void* p, size_t size) { #ifndef PADDLE_ONLY_CPU -void* GPUAllocator::Alloc(size_t size) { +void* GPUAllocator::Alloc(size_t& index, size_t size) { // CUDA documentation doesn't explain if cudaMalloc returns nullptr // if size is 0. We just make sure it does. - if (size <= 0) { - return nullptr; - } + if (size <= 0) return nullptr; + size_t available = 0; + size_t capacity = 0; + paddle::platform::GpuMemoryUsage(available, capacity); + + // Reserve memory for page tables, etc. + size_t reserving = capacity - paddle::platform::GpuMaxAllocSize(); + size_t remaining = available > reserving ? available - reserving : 0; + + // If remaining size no less than expected size, using general + // cudaMalloc to allocate GPU memory. void* p = 0; - cudaError_t result = - FLAGS_use_pinned_memory ? cudaMallocHost(&p, size) : cudaMalloc(&p, size); - if (result != cudaSuccess) { - cudaGetLastError(); // clear error if there is any. + if (size <= remaining) { + cudaError_t result = cudaMalloc(&p, size); + if (result == cudaSuccess) { + index = 0; + total_alloc_size_ += size; + return p; + } } - return result == cudaSuccess ? p : nullptr; + + // If remaining size less than expected size or cudaMalloc failed, + // cudaMallocHost will be considered as a fallback allocator. + cudaError_t result = cudaMallocHost(&p, size); + if (result == cudaSuccess) { + index = 1; + total_alloc_size_ += size; + return p; + } + + return nullptr; } -void GPUAllocator::Free(void* p, size_t size) { +void GPUAllocator::Free(void* p, size_t size, size_t index) { // Purposefully allow cudaErrorCudartUnloading, because // that is returned if you ever call cudaFree after the // driver has already shutdown. This happens only if the // process is terminating, in which case we don't care if // cudaFree succeeds. - cudaError_t err = FLAGS_use_pinned_memory ? cudaFreeHost(p) : cudaFree(p); + PADDLE_ASSERT(total_alloc_size_ >= size); + total_alloc_size_ -= size; + cudaError_t err = index == 1 ? cudaFreeHost(p) : cudaFree(p); if (err != cudaErrorCudartUnloading) { platform::throw_on_error(err, "cudaFree{Host} failed"); } diff --git a/paddle/memory/detail/system_allocator.h b/paddle/memory/detail/system_allocator.h index 184b383f7f7..e15302ce4f0 100644 --- a/paddle/memory/detail/system_allocator.h +++ b/paddle/memory/detail/system_allocator.h @@ -30,21 +30,24 @@ namespace detail { class SystemAllocator { public: virtual ~SystemAllocator() {} - virtual void* Alloc(size_t size) = 0; - virtual void Free(void* p, size_t size) = 0; + virtual void* Alloc(size_t& index, size_t size) = 0; + virtual void Free(void* p, size_t size, size_t index) = 0; }; class CPUAllocator : public SystemAllocator { public: - virtual void* Alloc(size_t size); - virtual void Free(void* p, size_t size); + virtual void* Alloc(size_t& index, size_t size); + virtual void Free(void* p, size_t size, size_t index); }; #ifndef PADDLE_ONLY_CPU class GPUAllocator : public SystemAllocator { public: - virtual void* Alloc(size_t size); - virtual void Free(void* p, size_t size); + virtual void* Alloc(size_t& index, size_t size); + virtual void Free(void* p, size_t size, size_t index); + + private: + size_t total_alloc_size_ = 0; }; #endif // PADDLE_ONLY_CPU diff --git a/paddle/memory/detail/system_allocator_test.cc b/paddle/memory/detail/system_allocator_test.cc index 9bd5706a4e4..ba44e06ddb6 100644 --- a/paddle/memory/detail/system_allocator_test.cc +++ b/paddle/memory/detail/system_allocator_test.cc @@ -25,7 +25,8 @@ DECLARE_bool(use_pinned_memory); void TestAllocator(paddle::memory::detail::SystemAllocator& a, size_t size) { bool freed = false; { - void* p = a.Alloc(size); + size_t index; + void* p = a.Alloc(index, size); if (size > 0) { EXPECT_NE(p, nullptr); } else { @@ -35,7 +36,7 @@ void TestAllocator(paddle::memory::detail::SystemAllocator& a, size_t size) { int* i = static_cast(p); std::shared_ptr ptr(i, [&](void* p) { freed = true; - a.Free(p, size); + a.Free(p, size, index); }); } EXPECT_TRUE(freed); @@ -56,14 +57,7 @@ TEST(CPUAllocator, LockMem) { } #ifndef PADDLE_ONLY_CPU -TEST(GPUAllocator, NoStaging) { - FLAGS_use_pinned_memory = false; - paddle::memory::detail::GPUAllocator a; - TestAllocator(a, 2048); - TestAllocator(a, 0); -} -TEST(GPUAllocator, Staging) { - FLAGS_use_pinned_memory = true; +TEST(GPUAllocator, Alloc) { paddle::memory::detail::GPUAllocator a; TestAllocator(a, 2048); TestAllocator(a, 0); diff --git a/paddle/platform/CMakeLists.txt b/paddle/platform/CMakeLists.txt index 7abe2ab89e0..17342356d60 100644 --- a/paddle/platform/CMakeLists.txt +++ b/paddle/platform/CMakeLists.txt @@ -1,3 +1,7 @@ +cc_library(cpu_info SRCS cpu_info.cc) +cc_test(cpu_info_test SRCS cpu_info_test.cc DEPS cpu_info gflags) + +nv_library(gpu_info SRCS gpu_info.cc) nv_test(cuda_test SRCS cuda_test.cu) cc_library(place SRCS place.cc) diff --git a/paddle/platform/cpu_info_test.cc b/paddle/platform/cpu_info_test.cc new file mode 100644 index 00000000000..5b7ce7c763e --- /dev/null +++ b/paddle/platform/cpu_info_test.cc @@ -0,0 +1,18 @@ +#include "paddle/platform/cpu_info.h" + +#include +#include + +#include "gflags/gflags.h" +#include "gtest/gtest.h" + +DECLARE_double(fraction_of_cpu_memory_to_use); + +TEST(CpuMemoryUsage, Print) { + std::stringstream ss; + size_t mem_size = paddle::platform::CpuTotalMemory() / 1024 / 1024 / 1024; + ss << std::to_string( + static_cast(FLAGS_fraction_of_cpu_memory_to_use * 100)) + << "% of CPU Memory Usage: " << mem_size << " GB"; + std::cout << ss.str(); +} diff --git a/paddle/platform/cuda.h b/paddle/platform/cuda.h deleted file mode 100644 index 8fe891f9ce6..00000000000 --- a/paddle/platform/cuda.h +++ /dev/null @@ -1,40 +0,0 @@ -/* 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 - -#ifndef PADDLE_ONLY_CPU - -#include -#include - -namespace paddle { -namespace platform { - -inline void throw_on_error(cudaError_t e, const char* message) { - if (e) { - throw thrust::system_error(e, thrust::cuda_category(), message); - } -} - -int GetDeviceCount(void) { - int count; - throw_on_error(cudaGetDeviceCount(&count), "cudaGetDeviceCount failed"); - return count; -} - -} // namespace platform -} // namespace paddle - -#endif // PADDLE_ONLY_CPU -- GitLab From 2d3c186a96178132784ecb9ba81403b67c3b7f67 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Thu, 29 Jun 2017 14:14:55 +0800 Subject: [PATCH 0916/3256] Follow comments --- cmake/generic.cmake | 6 ++++-- proto/CMakeLists.txt | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/cmake/generic.cmake b/cmake/generic.cmake index 779f6271154..8117dbc53e4 100644 --- a/cmake/generic.cmake +++ b/cmake/generic.cmake @@ -87,6 +87,9 @@ # go_library(example SHARED) # +# including binary directory for generated headers. +include_directories(${CMAKE_CURRENT_BINARY_DIR}) + if(NOT APPLE) find_package(Threads REQUIRED) link_libraries(${CMAKE_THREAD_LIBS_INIT}) @@ -339,6 +342,5 @@ function(proto_library TARGET_NAME) set(proto_srcs) set(proto_hdrs) protobuf_generate_cpp(proto_srcs proto_hdrs ${proto_library_SRCS}) - include_directories(${CMAKE_CURRENT_BINARY_DIR}) - cc_library(${TARGET_NAME} SRCS ${proto_srcs}) + cc_library(${TARGET_NAME} SRCS ${proto_srcs} DEPS protobuf) endfunction() diff --git a/proto/CMakeLists.txt b/proto/CMakeLists.txt index 70dd4d674cd..18584cafe79 100644 --- a/proto/CMakeLists.txt +++ b/proto/CMakeLists.txt @@ -1,4 +1,5 @@ file(GLOB proto_filenames . *.proto) +include_directories(${CMAKE_CURRENT_BINARY_DIR}) proto_library(paddle_proto SRCS ${proto_filenames}) set(PROTO_GEN) -- GitLab From 9af54c9ee98cabb9e70a10261a1c19cd8e8b3297 Mon Sep 17 00:00:00 2001 From: qiaolongfei Date: Thu, 29 Jun 2017 14:39:37 +0800 Subject: [PATCH 0917/3256] do not use default argument in Scope --- paddle/framework/scope.h | 41 +++++++++++++++++++++++----------- paddle/framework/scope_test.cc | 6 ++--- 2 files changed, 31 insertions(+), 16 deletions(-) diff --git a/paddle/framework/scope.h b/paddle/framework/scope.h index 88a13145ca9..a4470f726fb 100644 --- a/paddle/framework/scope.h +++ b/paddle/framework/scope.h @@ -24,24 +24,31 @@ namespace paddle { namespace framework { /** + * @brief Scope that manage all variables. + * * Scope is an association of a name to Variable. All variables belong to * Scope. You need to specify a scope to run a Net, i.e., `net.Run(&scope)`. * One net can run in different scopes and update different variable in the * scope. */ class Scope { - private: - explicit Scope(const std::shared_ptr& parent = nullptr) - : parent_(parent) {} - public: - static std::shared_ptr Create( - const std::shared_ptr& parent = nullptr) { - return std::make_shared(Scope(parent)); - } + /** + * @brief Initialize s Scope without parent. + */ + Scope() {} + + /** + * @brief Initialize a Scope with parent. + */ + explicit Scope(const std::shared_ptr& parent) : parent_(parent) {} - /// Create Variable in this Scope. Failed if Variable already been - /// created. + /** + * @brief Create Variable + * + * Create Variable in this Scope. Return the exist one if Variable already + * been created. + */ Variable* CreateVariable(const std::string& name) { auto var = GetVariable(name); if (var) { @@ -52,8 +59,12 @@ class Scope { } } - /// Get Variable from this Scope, this function will recursive find Variable - /// from it's parent scope. Return nullptr if not found. + /** + * @brief Get Variable. + * + * Get Variable from this Scope, this function will recursive find Variable + * from it's parent scope. Return nullptr if not found. + */ Variable* GetVariable(const std::string& name) const { auto it = vars_.find(name); if (it != vars_.end()) { @@ -65,7 +76,11 @@ class Scope { } } - /// Find if there is a Variable in this scope and it's parent scope + /** + * @brief If this scope has a Var named name. + * + * Find if there is a Variable in this scope and it's parent scope + */ bool HasVariable(const std::string& name) const { return (vars_.find(name) != vars_.end() || (parent_ && parent_->HasVariable(name))); diff --git a/paddle/framework/scope_test.cc b/paddle/framework/scope_test.cc index ec6236ec621..df1afb200ce 100644 --- a/paddle/framework/scope_test.cc +++ b/paddle/framework/scope_test.cc @@ -19,7 +19,7 @@ TEST(Scope, Create) { using paddle::framework::Scope; using paddle::framework::Variable; - auto scope = Scope::Create(); + auto scope = std::make_shared(); Variable* var0 = scope->CreateVariable(""); EXPECT_NE(var0, nullptr); @@ -46,8 +46,8 @@ TEST(Scope, Parent) { using paddle::framework::Scope; using paddle::framework::Variable; - auto parent_scope = Scope::Create(); - auto scope = Scope::Create(parent_scope); + auto parent_scope = std::make_shared(); + auto scope = std::make_shared(parent_scope); Variable* var0 = parent_scope->CreateVariable("a"); EXPECT_NE(var0, nullptr); -- GitLab From 3c925feb71a9b8c40cad60cf4c453ba083ed69a9 Mon Sep 17 00:00:00 2001 From: qiaolongfei Date: Thu, 29 Jun 2017 14:52:50 +0800 Subject: [PATCH 0918/3256] update design doc --- doc/design/scope.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/design/scope.md b/doc/design/scope.md index 4d14a64977c..afe6bc028ca 100644 --- a/doc/design/scope.md +++ b/doc/design/scope.md @@ -59,9 +59,9 @@ class Scope { Scope(const std::shared_ptr& scope): parent_(scope) {} Variable* GetVariable(const std::string& name) const { - Variable* var = GetVarLocally(name); - if (var != nullptr) { - return var; + auto it = vars_.find(name); + if (it != vars_.end()) { + return it->second.get(); } else if (parent_ != nullptr) { return parent_->GetVariable(name); } else { @@ -97,8 +97,8 @@ class Scope { // return nullptr if not found. Variable* GetVariable(const std::string& name) const; - // return Error if already contains same name variable. - Error CreateVariable(const std::string& name); + // return if already contains same name variable. + Variable* CreateVariable(const std::string& name); private: std::shared_ptr parent_; -- GitLab From 3d44fd5bf38b8cd74fccc17081972b5a9a0eaa2e Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Thu, 29 Jun 2017 14:53:29 +0800 Subject: [PATCH 0919/3256] Follow yiqun's comments --- cmake/generic.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/generic.cmake b/cmake/generic.cmake index 8117dbc53e4..61353a4a262 100644 --- a/cmake/generic.cmake +++ b/cmake/generic.cmake @@ -88,7 +88,7 @@ # # including binary directory for generated headers. -include_directories(${CMAKE_CURRENT_BINARY_DIR}) +include_directories(${CMAKE_BINARY_DIR}) if(NOT APPLE) find_package(Threads REQUIRED) -- GitLab From c18275ffb3d80047cb77eedcd88d1ffb11d72ea5 Mon Sep 17 00:00:00 2001 From: qijun Date: Thu, 29 Jun 2017 14:56:16 +0800 Subject: [PATCH 0920/3256] add more choice for eigen downloading --- cmake/external/eigen.cmake | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/cmake/external/eigen.cmake b/cmake/external/eigen.cmake index 253d436bcc0..45f44f617dc 100644 --- a/cmake/external/eigen.cmake +++ b/cmake/external/eigen.cmake @@ -7,8 +7,17 @@ INCLUDE_DIRECTORIES(${EIGEN_SOURCE_DIR}/src/eigen3) ExternalProject_Add( eigen3 ${EXTERNAL_PROJECT_LOG_ARGS} - URL "https://bitbucket.org/eigen/eigen/get/3.3.4.tar.gz" - URL_MD5 "1a47e78efe365a97de0c022d127607c3" + # for latest version, please get from official website + # URL "https://bitbucket.org/eigen/eigen/get/3.3.4.tar.gz" + # URL_MD5 "1a47e78efe365a97de0c022d127607c3" + + # for no-ssl http support, please get from bazel's mirror + # URL "http://mirror.bazel.build/bitbucket.org/eigen/eigen/get/f3a22f35b044.tar.gz" + # URL_MD5 "4645c66075982da6fa0bcf6b20f3e8f7" + + # get from github mirror + GIT_REPOSITORY "https://github.com/RLovelett/eigen.git" + GIT_TAG "a46d2e7337c4656f00abe54a8115f6d76153a048" PREFIX ${EIGEN_SOURCE_DIR} UPDATE_COMMAND "" CONFIGURE_COMMAND "" -- GitLab From 52efb243b82b7b68868cfe5391eafb0376ddb839 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Thu, 29 Jun 2017 15:06:46 +0800 Subject: [PATCH 0921/3256] Fix CI tests --- paddle/testing/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/paddle/testing/CMakeLists.txt b/paddle/testing/CMakeLists.txt index 4aa6eae681c..4245df5ab72 100644 --- a/paddle/testing/CMakeLists.txt +++ b/paddle/testing/CMakeLists.txt @@ -2,7 +2,7 @@ if(WITH_TESTING) add_library(paddle_test_main STATIC TestMain.cpp) - add_dependencies(paddle_test_main paddle_proto) + add_dependencies(paddle_test_main paddle_proto ${external_project_dependencies}) add_library(paddle_test_util STATIC TestUtil.cpp) - add_dependencies(paddle_test_util paddle_proto) + add_dependencies(paddle_test_util paddle_proto ${external_project_dependencies}) endif() -- GitLab From b5ab4b69bcfa604a1ebbb964da1765ff2c586a6a Mon Sep 17 00:00:00 2001 From: yangyaming Date: Thu, 29 Jun 2017 15:11:40 +0800 Subject: [PATCH 0922/3256] Follow comments, mainly use std::copy to simplify logic. --- .../gserver/layers/DetectionOutputLayer.cpp | 20 ++-- paddle/gserver/layers/DetectionOutputLayer.h | 6 +- paddle/gserver/layers/MultiBoxLossLayer.cpp | 109 ++++++++++-------- python/paddle/trainer/config_parser.py | 4 +- .../paddle/trainer_config_helpers/layers.py | 7 +- 5 files changed, 74 insertions(+), 72 deletions(-) diff --git a/paddle/gserver/layers/DetectionOutputLayer.cpp b/paddle/gserver/layers/DetectionOutputLayer.cpp index 2a4d7f8b5b2..8ab838e1913 100644 --- a/paddle/gserver/layers/DetectionOutputLayer.cpp +++ b/paddle/gserver/layers/DetectionOutputLayer.cpp @@ -48,8 +48,6 @@ void DetectionOutputLayer::forward(PassType passType) { Matrix::resizeOrCreate(locTmpBuffer_, 1, locSizeSum_, false, useGpu_); Matrix::resizeOrCreate( confTmpBuffer_, confSizeSum_ / numClasses_, numClasses_, false, useGpu_); - locBuffer_ = locTmpBuffer_; - confBuffer_ = confTmpBuffer_; size_t locOffset = 0; size_t confOffset = 0; @@ -68,7 +66,7 @@ void DetectionOutputLayer::forward(PassType passType) { locSizeSum_, locOffset, batchSize, - *locBuffer_, + *locTmpBuffer_, kNCHWToNHWC); confOffset += appendWithPermute(*inConf, height, @@ -76,7 +74,7 @@ void DetectionOutputLayer::forward(PassType passType) { confSizeSum_, confOffset, batchSize, - *confBuffer_, + *confTmpBuffer_, kNCHWToNHWC); } CHECK_EQ(locOffset, locSizeSum_ / batchSize); @@ -100,23 +98,25 @@ void DetectionOutputLayer::forward(PassType passType) { priorValue = priorCpuValue_; } else { priorValue = getInputValue(*getPriorBoxLayer()); + locBuffer_ = locTmpBuffer_; + confBuffer_ = confTmpBuffer_; } confBuffer_->softmax(*confBuffer_); size_t numPriors = priorValue->getElementCnt() / 8; - vector> allDecodedBBoxes; + std::vector> allDecodedBBoxes; for (size_t n = 0; n < batchSize; ++n) { - vector decodedBBoxes; + std::vector decodedBBoxes; for (size_t i = 0; i < numPriors; ++i) { size_t priorOffset = i * 8; size_t locPredOffset = n * numPriors * 4 + i * 4; - vector priorBBoxVec; + std::vector priorBBoxVec; getBBoxFromPriorData( priorValue->getData() + priorOffset, 1, priorBBoxVec); - vector> priorBBoxVar; + std::vector> priorBBoxVar; getBBoxVarFromPriorData( priorValue->getData() + priorOffset, 1, priorBBoxVar); - vector locPredData; + std::vector locPredData; for (size_t j = 0; j < 4; ++j) locPredData.push_back(*(locBuffer_->getData() + locPredOffset + j)); NormalizedBBox bbox = @@ -126,7 +126,7 @@ void DetectionOutputLayer::forward(PassType passType) { allDecodedBBoxes.push_back(decodedBBoxes); } - vector>> allIndices; + std::vector>> allIndices; size_t numKept = getDetectionIndices(confBuffer_->getData(), numPriors, numClasses_, diff --git a/paddle/gserver/layers/DetectionOutputLayer.h b/paddle/gserver/layers/DetectionOutputLayer.h index 38271cb0540..9cc568219ca 100644 --- a/paddle/gserver/layers/DetectionOutputLayer.h +++ b/paddle/gserver/layers/DetectionOutputLayer.h @@ -19,17 +19,13 @@ limitations under the License. */ #include "DetectionUtil.h" #include "Layer.h" -using std::vector; -using std::map; -using std::pair; - namespace paddle { /** * The detection output layer for a SSD detection task. This layer apply the * Non-maximum suppression to the all predicted bounding box and keep the * Top-K bounding boxes. - * - Input: This layer need three input layers: This first input layer + * - Input: This layer needs three input layers: This first input layer * is the priorbox layer. The rest two input layers are convolution * layers for generating bbox location offset and the classification * confidence. diff --git a/paddle/gserver/layers/MultiBoxLossLayer.cpp b/paddle/gserver/layers/MultiBoxLossLayer.cpp index 27a2cc3fa4a..f2d7b8eb1da 100644 --- a/paddle/gserver/layers/MultiBoxLossLayer.cpp +++ b/paddle/gserver/layers/MultiBoxLossLayer.cpp @@ -17,10 +17,6 @@ limitations under the License. */ #include #include "DataLayer.h" -using std::vector; -using std::map; -using std::pair; - namespace paddle { REGISTER_LAYER(multibox_loss, MultiBoxLossLayer); @@ -133,7 +129,7 @@ void MultiBoxLossLayer::forward(PassType passType) { } // Get max scores for each prior bbox. Used in negative mining - vector> allMaxConfScore; + std::vector> allMaxConfScore; numPriors_ = priorValue->getElementCnt() / 8; getMaxConfidenceScores(confBuffer_->getData(), batchSize, @@ -151,18 +147,18 @@ void MultiBoxLossLayer::forward(PassType passType) { allMatchIndices_.clear(); allNegIndices_.clear(); - pair retPair = generateMatchIndices(*priorValue, - numPriors_, - *labelValue, - labelIndex, - seqNum, - allMaxConfScore, - batchSize, - overlapThreshold_, - negOverlap_, - negPosRatio_, - &allMatchIndices_, - &allNegIndices_); + std::pair retPair = generateMatchIndices(*priorValue, + numPriors_, + *labelValue, + labelIndex, + seqNum, + allMaxConfScore, + batchSize, + overlapThreshold_, + negOverlap_, + negPosRatio_, + &allMatchIndices_, + &allNegIndices_); numMatches_ = retPair.first; numNegs_ = retPair.second; @@ -175,30 +171,31 @@ void MultiBoxLossLayer::forward(PassType passType) { Matrix::resizeOrCreate(locGTData_, numMatches_ * 4, 1, false, false); Matrix::resizeOrCreate(locDiff_, numMatches_ * 4, 1, false, false); locDiff_->zeroMem(); - vector locGTData; + std::vector locGTData; + real* locDiffData = locDiff_->getData(); + const real* locBufferData = locBuffer_->getData(); for (size_t n = 0; n < batchSize; ++n) { for (size_t i = 0; i < numPriors_; ++i) { if (allMatchIndices_[n][i] == -1) continue; // match none size_t locOffset = n * (locBuffer_->getElementCnt() / batchSize) + i * 4; - locDiff_->getData()[count++] = (locBuffer_->getData() + locOffset)[0]; - locDiff_->getData()[count++] = (locBuffer_->getData() + locOffset)[1]; - locDiff_->getData()[count++] = (locBuffer_->getData() + locOffset)[2]; - locDiff_->getData()[count++] = (locBuffer_->getData() + locOffset)[3]; - + std::copy(locBufferData + locOffset, + locBufferData + locOffset + 4, + locDiffData + count); + count += 4; const int gtIdx = allMatchIndices_[n][i]; size_t priorOffset = i * 8; - vector priorBBoxVec; + std::vector priorBBoxVec; getBBoxFromPriorData( priorValue->getData() + priorOffset, 1, priorBBoxVec); - vector> priorBBoxVar; + std::vector> priorBBoxVar; getBBoxVarFromPriorData( priorValue->getData() + priorOffset, 1, priorBBoxVar); size_t labelOffset = (labelIndex[n] + gtIdx) * 6; - vector gtBBoxVec; + std::vector gtBBoxVec; getBBoxFromLabelData(labelValue->getData() + labelOffset, 1, gtBBoxVec); - vector gtEncode; + std::vector gtEncode; encodeBBoxWithVar( priorBBoxVec[0], priorBBoxVar[0], gtBBoxVec[0], gtEncode); locGTData.insert(locGTData.end(), gtEncode.begin(), gtEncode.end()); @@ -218,7 +215,9 @@ void MultiBoxLossLayer::forward(PassType passType) { confProb_->zeroMem(); size_t count = 0; - vector confPredData; + std::vector confPredData; + real* confProbData = confProb_->getData(); + const real* confBufferData = confBuffer_->getData(); for (size_t n = 0; n < batchSize; ++n) { for (size_t i = 0; i < numPriors_; ++i) { if (allMatchIndices_[n][i] == -1) continue; @@ -226,11 +225,13 @@ void MultiBoxLossLayer::forward(PassType passType) { const int gtLabel = (labelValue->getData() + labelOffset)[0]; confGTData_->getData()[count] = gtLabel; size_t confOffset = n * numPriors_ * numClasses_ + i * numClasses_; - for (size_t j = 0; j < numClasses_; ++j) { - confProb_->getData()[count * numClasses_ + j] = - (confBuffer_->getData() + confOffset)[j]; - confPredData.push_back((confBuffer_->getData() + confOffset)[j]); - } + std::copy(confBufferData + confOffset, + confBufferData + confOffset + numClasses_, + confProbData + count * numClasses_); + confPredData.reserve(confPredData.size() + numClasses_); + confPredData.insert(confPredData.end(), + confBufferData + confOffset, + confBufferData + confOffset + numClasses_); ++count; } // Negative mining samples @@ -238,14 +239,17 @@ void MultiBoxLossLayer::forward(PassType passType) { confGTData_->getData()[count] = backgroundId_; size_t confOffset = n * numPriors_ * numClasses_ + allNegIndices_[n][i] * numClasses_; - for (size_t j = 0; j < numClasses_; ++j) { - confProb_->getData()[count * numClasses_ + j] = - (confBuffer_->getData() + confOffset)[j]; - confPredData.push_back((confBuffer_->getData() + confOffset)[j]); - } - count++; + std::copy(confBufferData + confOffset, + confBufferData + confOffset + numClasses_, + confProbData + count * numClasses_); + confPredData.reserve(confPredData.size() + numClasses_); + confPredData.insert(confPredData.end(), + confBufferData + confOffset, + confBufferData + confOffset + numClasses_); + ++count; } } + CHECK_EQ(numConf_, count); confProb_->softmax(*confProb_); MatrixPtr confLossOutput; Matrix::resizeOrCreate(confLossOutput, numConf_, 1, false, false); @@ -254,7 +258,7 @@ void MultiBoxLossLayer::forward(PassType passType) { } real loss = locLoss_ + confLoss_; MatrixPtr outV = getOutputValue(); - vector tmp(batchSize, loss); + std::vector tmp(batchSize, loss); outV->copyFrom(&tmp[0], batchSize); } @@ -274,16 +278,18 @@ void MultiBoxLossLayer::backward(const UpdateCallback& callback) { locDiff_->getData()[i] *= (1. / numMatches_); // Copy gradient back size_t count = 0; - for (size_t n = 0; n < batchSize; ++n) + const real* locDiffData = locDiff_->getData(); + for (size_t n = 0; n < batchSize; ++n) { for (size_t i = 0; i < numPriors_; ++i) { if (allMatchIndices_[n][i] == -1) continue; - real* locDiffData = locBuffer_->getData() + n * numPriors_ * 4 + i * 4; - locDiffData[0] = (locDiff_->getData() + count * 4)[0]; - locDiffData[1] = (locDiff_->getData() + count * 4)[1]; - locDiffData[2] = (locDiff_->getData() + count * 4)[2]; - locDiffData[3] = (locDiff_->getData() + count * 4)[3]; + real* locBufferData = + locBuffer_->getData() + n * numPriors_ * 4 + i * 4; + std::copy(locDiffData + count * 4, + locDiffData + (count + 1) * 4, + locBufferData); ++count; } + } CHECK_EQ(count, numMatches_); } @@ -293,21 +299,24 @@ void MultiBoxLossLayer::backward(const UpdateCallback& callback) { for (size_t i = 0; i < numConf_ * numClasses_; ++i) confProb_->getData()[i] *= (1. / numMatches_); size_t count = 0; + const real* confProbData = confProb_->getData(); for (size_t n = 0; n < batchSize; ++n) { for (size_t i = 0; i < numPriors_; ++i) { if (allMatchIndices_[n][i] == -1) continue; real* confDiffData = confBuffer_->getData() + n * numPriors_ * numClasses_ + i * numClasses_; - for (size_t j = 0; j < numClasses_; ++j) - confDiffData[j] = (confProb_->getData() + count * numClasses_)[j]; + std::copy(confProbData + count * numClasses_, + confProbData + (count + 1) * numClasses_, + confDiffData); ++count; } for (size_t i = 0; i < allNegIndices_[n].size(); ++i) { int idx = allNegIndices_[n][i]; real* confDiffData = confBuffer_->getData() + n * numPriors_ * numClasses_ + idx * numClasses_; - for (size_t j = 0; j < numClasses_; ++j) - confDiffData[j] = (confProb_->getData() + count * numClasses_)[j]; + std::copy(confProbData + count * numClasses_, + confProbData + (count + 1) * numClasses_, + confDiffData); ++count; } } diff --git a/python/paddle/trainer/config_parser.py b/python/paddle/trainer/config_parser.py index c46b335d992..17f6704ea10 100644 --- a/python/paddle/trainer/config_parser.py +++ b/python/paddle/trainer/config_parser.py @@ -1679,7 +1679,7 @@ class PriorBoxLayer(LayerBase): @config_layer('multibox_loss') class MultiBoxLossLayer(LayerBase): def __init__(self, name, inputs, input_num, num_classes, overlap_threshold, - neg_pos_ratio, neg_overlap, background_id): + neg_pos_ratio, neg_overlap, background_id, **xargs): super(MultiBoxLossLayer, self).__init__(name, 'multibox_loss', 0, inputs) config_assert( @@ -1701,7 +1701,7 @@ class MultiBoxLossLayer(LayerBase): class DetectionOutputLayer(LayerBase): def __init__(self, name, inputs, size, input_num, num_classes, nms_threshold, nms_top_k, keep_top_k, confidence_threshold, - background_id): + background_id, **xargs): super(DetectionOutputLayer, self).__init__(name, 'detection_output', 0, inputs) config_assert( diff --git a/python/paddle/trainer_config_helpers/layers.py b/python/paddle/trainer_config_helpers/layers.py index 770559dc770..1286ed198e0 100755 --- a/python/paddle/trainer_config_helpers/layers.py +++ b/python/paddle/trainer_config_helpers/layers.py @@ -1092,22 +1092,19 @@ def multibox_loss_layer(input_loc, :type background_id: int :return: LayerOutput """ - input_loc_num = 0 - input_conf_num = 0 - if isinstance(input_loc, LayerOutput): input_loc = [input_loc] assert isinstance(input_loc, collections.Sequence) # list or tuple for each in input_loc: assert isinstance(each, LayerOutput) - input_loc_num += 1 + input_loc_num = len(input_loc) if isinstance(input_conf, LayerOutput): input_conf = [input_conf] assert isinstance(input_conf, collections.Sequence) # list or tuple for each in input_conf: assert isinstance(each, LayerOutput) - input_conf_num += 1 + input_conf_num = len(input_conf) # Check the input layer number. assert input_loc_num == input_conf_num -- GitLab From a1e7284ad139e6578036b7d872563aa1a2642351 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Thu, 29 Jun 2017 15:24:12 +0800 Subject: [PATCH 0923/3256] Fix TravisCI --- paddle/pserver/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paddle/pserver/CMakeLists.txt b/paddle/pserver/CMakeLists.txt index f2e0b4b76bc..2245c7d88ca 100644 --- a/paddle/pserver/CMakeLists.txt +++ b/paddle/pserver/CMakeLists.txt @@ -17,7 +17,7 @@ add_library(paddle_network STATIC add_style_check_target(paddle_network ${NETWORK_SOURCES}) add_style_check_target(paddle_network ${NETWORK_HEADERS}) -add_dependencies(paddle_network paddle_proto) +add_dependencies(paddle_network paddle_proto ${external_project_dependencies}) ################### paddle_pserver ###################### set(PSERVER_SOURCES -- GitLab From b0ad9c907422e1256bc5ae6881913f71cd9d4aed Mon Sep 17 00:00:00 2001 From: caoying03 Date: Wed, 28 Jun 2017 10:12:23 +0800 Subject: [PATCH 0924/3256] enable intializing memory state for lstmemory_group. --- .../paddle/trainer_config_helpers/layers.py | 51 ++++++++++--------- .../paddle/trainer_config_helpers/networks.py | 48 ++++++++++------- 2 files changed, 57 insertions(+), 42 deletions(-) diff --git a/python/paddle/trainer_config_helpers/layers.py b/python/paddle/trainer_config_helpers/layers.py index 84ed1607730..a601d5c84ad 100755 --- a/python/paddle/trainer_config_helpers/layers.py +++ b/python/paddle/trainer_config_helpers/layers.py @@ -1149,10 +1149,10 @@ def pooling_layer(input, @layer_support(DROPOUT) def lstmemory(input, name=None, + size=None, reverse=False, act=None, gate_act=None, - size=None, state_act=None, bias_attr=None, param_attr=None, @@ -1194,6 +1194,8 @@ def lstmemory(input, :param name: The lstmemory layer name. :type name: basestring + :param size: DEPRECATED. size of the lstm cell + :type size: int :param input: input layer name. :type input: LayerOutput :param reverse: is sequence process reversed or not. @@ -1220,15 +1222,15 @@ def lstmemory(input, assert state_act.support_hppl assert act.support_hppl assert input.size is not None and input.size % 4 == 0 + if size is not None: if input.size / 4 == size: plog = logger.warning else: plog = logger.fatal - - plog("NOTE: The lstmemory layer[%s]'s size is set by previous input " - "layer. The lstm size should be equal with input layer size/4. The" - " size which is set explicitly will be ignored." % name) + plog("size of lstmemory layer: %s is automatically set to " + "size of input layer / 4. The parameter size passing to " + "this layer is ignored." % (name)) Layer( name=name, @@ -1255,11 +1257,11 @@ def lstmemory(input, @wrap_name_default("gru") @layer_support(DROPOUT) def grumemory(input, + size=None, name=None, reverse=False, act=None, gate_act=None, - size=None, bias_attr=None, param_attr=None, layer_attr=None): @@ -1318,6 +1320,8 @@ def grumemory(input, :type name: None|basestring :param input: input layer. :type input: LayerOutput. + :param size: DEPRECATED. size of the gru cell + :type size: int :param reverse: Whether sequence process is reversed or not. :type reverse: bool :param act: activation type, TanhActivation by default. This activation @@ -1334,9 +1338,6 @@ def grumemory(input, :type param_attr: ParameterAttribute|None|False :param layer_attr: Extra Layer attribute :type layer_attr: ExtraLayerAttribute|None - :param size: Stub parameter of size, but actually not used. If set this size - will get a warning. - :type size: None :return: LayerOutput object. :rtype: LayerOutput """ @@ -1348,9 +1349,9 @@ def grumemory(input, plog = logger.warning else: plog = logger.fatal - plog("NOTE: the gru memory layer's size is set by previous input layer," - " and should be input size / 3. Set size explicitly will be " - "ignored.") + plog("size of grumemory layer: %s is automatically set to " + "size of input layer / 3. The parameter size passing to this " + "layer is ignored." % (name)) Layer( name=name, @@ -2524,8 +2525,8 @@ def img_cmrnorm_layer(input, @wrap_bias_attr_default() -@wrap_param_attr_default(default_factory=lambda _: ParamAttr(initial_mean=1.0, - initial_std=0.)) +@wrap_param_attr_default( + default_factory=lambda _: ParamAttr(initial_mean=1.0, initial_std=0.)) @wrap_act_default(act=ReluActivation()) @wrap_name_default("batch_norm") @layer_support(DROPOUT) @@ -3013,25 +3014,25 @@ def lstm_step_layer(input, bias_attr=None, layer_attr=None): """ - LSTM Step Layer. It used in recurrent_group. The lstm equations are shown - as follow. + LSTM Step Layer. This function is used only in recurrent_group. + The lstm equations are shown as follows. .. math:: - i_t & = \\sigma(W_{xi}x_{t} + W_{hi}h_{t-1} + W_{ci}c_{t-1} + b_i) + i_t & = \\sigma(W_{x_i}x_{t} + W_{h_i}h_{t-1} + W_{c_i}c_{t-1} + b_i) - f_t & = \\sigma(W_{xf}x_{t} + W_{hf}h_{t-1} + W_{cf}c_{t-1} + b_f) + f_t & = \\sigma(W_{x_f}x_{t} + W_{h_f}h_{t-1} + W_{c_f}c_{t-1} + b_f) - c_t & = f_tc_{t-1} + i_t tanh (W_{xc}x_t+W_{hc}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_{xo}x_{t} + W_{ho}h_{t-1} + W_{co}c_t + b_o) + o_t & = \\sigma(W_{x_o}x_{t} + W_{h_o}h_{t-1} + W_{c_o}c_t + b_o) h_t & = o_t tanh(c_t) The input of lstm step is :math:`Wx_t + Wh_{t-1}`, and user should use :code:`mixed_layer` and :code:`full_matrix_projection` to calculate these - input vector. + input vectors. The state of lstm step is :math:`c_{t-1}`. And lstm step layer will do @@ -3042,14 +3043,14 @@ def lstm_step_layer(input, ... - This layer contains two outputs. Default output is :math:`h_t`. The other - output is :math:`o_t`, which name is 'state' and can use + This layer has two outputs. Default output is :math:`h_t`. The other + output is :math:`o_t`, whose name is 'state' and can use :code:`get_output_layer` to extract this output. :param name: Layer's name. :type name: basestring - :param size: Layer's size. NOTE: lstm layer's size, should be equal as - :code:`input.size/4`, and should be equal as + :param size: Layer's size. NOTE: lstm layer's size, should be equal to + :code:`input.size/4`, and should be equal to :code:`state.size`. :type size: int :param input: input layer. :math:`Wx_t + Wh_{t-1}` diff --git a/python/paddle/trainer_config_helpers/networks.py b/python/paddle/trainer_config_helpers/networks.py index 67154a8d7d3..0d730e09951 100755 --- a/python/paddle/trainer_config_helpers/networks.py +++ b/python/paddle/trainer_config_helpers/networks.py @@ -614,6 +614,7 @@ def simple_lstm(input, @wrap_name_default('lstm_unit') def lstmemory_unit(input, + memory_boot=None, name=None, size=None, param_attr=None, @@ -626,9 +627,9 @@ def lstmemory_unit(input, lstm_layer_attr=None, get_output_layer_attr=None): """ - Define calculations that a LSTM unit performs in a single time step. - This function itself is not a recurrent layer, so that it can not be - directly applied to sequence input. This function is always used in + Define calculations that a LSTM unit performs during a single time step. + This function itself is not a recurrent layer, so it can not be + directly used to process sequence inputs. This function is always used in recurrent_group (see layers.py for more details) to implement attention mechanism. @@ -638,13 +639,13 @@ def lstmemory_unit(input, .. math:: - i_t & = \\sigma(W_{xi}x_{t} + W_{hi}h_{t-1} + W_{ci}c_{t-1} + b_i) + i_t & = \\sigma(W_{x_i}x_{t} + W_{h_i}h_{t-1} + W_{c_i}c_{t-1} + b_i) - f_t & = \\sigma(W_{xf}x_{t} + W_{hf}h_{t-1} + W_{cf}c_{t-1} + b_f) + f_t & = \\sigma(W_{x_f}x_{t} + W_{h_f}h_{t-1} + W_{c_f}c_{t-1} + b_f) - c_t & = f_tc_{t-1} + i_t tanh (W_{xc}x_t+W_{hc}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_{xo}x_{t} + W_{ho}h_{t-1} + W_{co}c_t + b_o) + o_t & = \\sigma(W_{x_o}x_{t} + W_{h_o}h_{t-1} + W_{c_o}c_t + b_o) h_t & = o_t tanh(c_t) @@ -661,6 +662,8 @@ def lstmemory_unit(input, :param input: input layer name. :type input: LayerOutput + :param memory_boot: the initialization state of the LSTM cell. + :type memory_boot: LayerOutput | None :param name: lstmemory unit name. :type name: basestring :param size: lstmemory unit size. @@ -692,7 +695,8 @@ def lstmemory_unit(input, assert input.size % 4 == 0 size = input.size / 4 out_mem = memory(name=name, size=size) - state_mem = memory(name="%s_state" % name, size=size) + state_mem = memory( + name="%s_state" % name, size=size, boot_layer=memory_boot) with mixed_layer( name="%s_input_recurrent" % name, @@ -726,6 +730,7 @@ def lstmemory_unit(input, def lstmemory_group(input, size=None, name=None, + memory_boot=None, reverse=False, param_attr=None, act=None, @@ -737,7 +742,7 @@ def lstmemory_group(input, lstm_layer_attr=None, get_output_layer_attr=None): """ - lstm_group is a recurrent layer group version of Long Short Term Memory. It + lstm_group is a recurrent_group version of Long Short Term Memory. It does exactly the same calculation as the lstmemory layer (see lstmemory in layers.py for the maths) does. A promising benefit is that LSTM memory cell states, or hidden states in every time step are accessible to the @@ -748,8 +753,8 @@ def lstmemory_group(input, NOTE: In PaddlePaddle's implementation, the following input-to-hidden multiplications: - :math:`W_{xi}x_{t}` , :math:`W_{xf}x_{t}`, - :math:`W_{xc}x_t`, :math:`W_{xo}x_{t}` are not done in lstmemory_unit to + :math:`W_{x_i}x_{t}` , :math:`W_{x_f}x_{t}`, + :math:`W_{x_c}x_t`, :math:`W_{x_o}x_{t}` are not done in lstmemory_unit to speed up the calculations. Consequently, an additional mixed_layer with full_matrix_projection must be included before lstmemory_unit is called. @@ -765,8 +770,10 @@ def lstmemory_group(input, :param input: input layer name. :type input: LayerOutput - :param name: lstmemory group name. + :param name: name of the lstmemory group. :type name: basestring + :param memory_boot: the initialization state of LSTM cell. + :type memory_boot: LayerOutput | None :param size: lstmemory group size. :type size: int :param reverse: is lstm reversed @@ -798,6 +805,7 @@ def lstmemory_group(input, def __lstm_step__(ipt): return lstmemory_unit( input=ipt, + memory_boot=memory_boot, name=name, size=size, mixed_bias_attr=mixed_bias_attr, @@ -819,6 +827,7 @@ def lstmemory_group(input, @wrap_name_default('gru_unit') def gru_unit(input, + memory_boot=None, size=None, name=None, gru_bias_attr=None, @@ -829,8 +838,8 @@ def gru_unit(input, naive=False): """ Define calculations that a gated recurrent unit performs in a single time - step. This function itself is not a recurrent layer, so that it can not be - directly applied to sequence input. This function is almost always used in + step. This function itself is not a recurrent layer, so it can not be + directly used to process sequence inputs. This function is always used in the recurrent_group (see layers.py for more details) to implement attention mechanism. @@ -838,6 +847,8 @@ def gru_unit(input, :param input: input layer name. :type input: LayerOutput + :param memory_boot: the initialization state of the LSTM cell. + :type memory_boot: LayerOutput | None :param name: name of the gru group. :type name: basestring :param size: hidden size of the gru. @@ -856,7 +867,7 @@ def gru_unit(input, if size is None: size = input.size / 3 - out_mem = memory(name=name, size=size) + out_mem = memory(name=name, size=size, boot_layer=memory_boot) if naive: __step__ = gru_step_naive_layer @@ -878,6 +889,7 @@ def gru_unit(input, @wrap_name_default('gru_group') def gru_group(input, + memory_boot=None, size=None, name=None, reverse=False, @@ -888,7 +900,7 @@ def gru_group(input, gru_layer_attr=None, naive=False): """ - gru_group is a recurrent layer group version of Gated Recurrent Unit. It + gru_group is a recurrent_group version of Gated Recurrent Unit. It does exactly the same calculation as the grumemory layer does. A promising benefit is that gru hidden states are accessible to the user. This is especially useful in attention model. If you do not need to access @@ -908,6 +920,8 @@ def gru_group(input, :param input: input layer name. :type input: LayerOutput + :param memory_boot: the initialization state of the LSTM cell. + :type memory_boot: LayerOutput | None :param name: name of the gru group. :type name: basestring :param size: hidden size of the gru. @@ -929,6 +943,7 @@ def gru_group(input, def __gru_step__(ipt): return gru_unit( input=ipt, + memory_boot=memory_boot, name=name, size=size, gru_bias_attr=gru_bias_attr, @@ -1083,7 +1098,6 @@ def simple_gru2(input, return grumemory( name=name, - size=size, input=m, reverse=reverse, bias_attr=gru_bias_attr, -- GitLab From e6c14f7e000d047cf3d3a1e18e2a13e3349b1ff9 Mon Sep 17 00:00:00 2001 From: liaogang Date: Thu, 29 Jun 2017 16:30:03 +0800 Subject: [PATCH 0925/3256] ENH: Polish cpu info interface --- paddle/platform/CMakeLists.txt | 3 +- paddle/platform/cpu_info.cc | 14 +++++++- paddle/platform/cpu_info.h | 10 ++++-- paddle/platform/cpu_info_test.cc | 13 ++++--- paddle/platform/cuda_test.cu | 59 -------------------------------- 5 files changed, 30 insertions(+), 69 deletions(-) delete mode 100644 paddle/platform/cuda_test.cu diff --git a/paddle/platform/CMakeLists.txt b/paddle/platform/CMakeLists.txt index d0bedf6ba92..969c91985d0 100644 --- a/paddle/platform/CMakeLists.txt +++ b/paddle/platform/CMakeLists.txt @@ -1,8 +1,7 @@ cc_library(cpu_info SRCS cpu_info.cc) -cc_test(cpu_info_test SRCS cpu_info_test.cc DEPS cpu_info gflags) +cc_test(cpu_info_test SRCS cpu_info_test.cc DEPS cpu_info gflags glog) nv_library(gpu_info SRCS gpu_info.cc) -nv_test(cuda_test SRCS cuda_test.cu) cc_library(place SRCS place.cc) cc_test(place_test SRCS place_test.cc DEPS place glog gflags) diff --git a/paddle/platform/cpu_info.cc b/paddle/platform/cpu_info.cc index deff76502e8..3da04420e57 100644 --- a/paddle/platform/cpu_info.cc +++ b/paddle/platform/cpu_info.cc @@ -47,9 +47,21 @@ inline size_t CpuTotalPhysicalMemory() { #endif } -size_t CpuTotalMemory() { +size_t CpuMaxAllocSize() { + // For distributed systems, it requires configuring and limiting + // the fraction of memory to use. return FLAGS_fraction_of_cpu_memory_to_use * CpuTotalPhysicalMemory(); } +size_t CpuMinChunkSize() { + // Allow to allocate the minimum chunk size is 256 bytes. + return 1 << 8; +} + +size_t CpuMaxChunkSize() { + // Allow to allocate the maximum chunk size is roughly 3% of CPU memory. + return CpuMaxAllocSize() / 32; +} + } // namespace platform } // namespace paddle diff --git a/paddle/platform/cpu_info.h b/paddle/platform/cpu_info.h index 3b768589e15..8df7c7b4bca 100644 --- a/paddle/platform/cpu_info.h +++ b/paddle/platform/cpu_info.h @@ -19,8 +19,14 @@ limitations under the License. */ namespace paddle { namespace platform { -//! Get the total memory on host machine. -size_t CpuTotalMemory(); +//! Get the maximum allocation size for a machine. +size_t CpuMaxAllocSize(); + +//! Get the minimum chunk size for buddy allocator. +size_t CpuMinChunkSize(); + +//! Get the maximum chunk size for buddy allocator. +size_t CpuMaxChunkSize(); } // namespace platform } // namespace paddle diff --git a/paddle/platform/cpu_info_test.cc b/paddle/platform/cpu_info_test.cc index 5b7ce7c763e..8fb195aa7c0 100644 --- a/paddle/platform/cpu_info_test.cc +++ b/paddle/platform/cpu_info_test.cc @@ -1,18 +1,21 @@ #include "paddle/platform/cpu_info.h" +#include "paddle/string/printf.h" #include #include #include "gflags/gflags.h" +#include "glog/logging.h" #include "gtest/gtest.h" DECLARE_double(fraction_of_cpu_memory_to_use); TEST(CpuMemoryUsage, Print) { std::stringstream ss; - size_t mem_size = paddle::platform::CpuTotalMemory() / 1024 / 1024 / 1024; - ss << std::to_string( - static_cast(FLAGS_fraction_of_cpu_memory_to_use * 100)) - << "% of CPU Memory Usage: " << mem_size << " GB"; - std::cout << ss.str(); + size_t memory_size = paddle::platform::CpuMaxAllocSize() / 1024 / 1024 / 1024; + float use_percent = FLAGS_fraction_of_cpu_memory_to_use * 100; + + std::cout << paddle::string::Sprintf("\n%.2f %% of CPU Memory Usage: %d GB\n", + use_percent, memory_size) + << std::endl; } diff --git a/paddle/platform/cuda_test.cu b/paddle/platform/cuda_test.cu deleted file mode 100644 index 4067dda2f19..00000000000 --- a/paddle/platform/cuda_test.cu +++ /dev/null @@ -1,59 +0,0 @@ -#include -#include -#include "gtest/gtest.h" - -#define CHECK_ERR(x) \ - if (x != cudaSuccess) { \ - fprintf(stderr, \ - "%s in %s at line %d\n", \ - cudaGetErrorString(err), \ - __FILE__, \ - __LINE__); \ - exit(-1); \ - } - -__global__ void vecAdd(float *d_A, float *d_B, float *d_C, int n) { - int i = blockDim.x * blockIdx.x + threadIdx.x; - if (i < n) { - d_C[i] = d_A[i] + d_B[i]; - } -} - -TEST(Cuda, Equality) { - int n = 10; - // Memory allocation for h_A, h_B and h_C (in the host) - float h_A[10] = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 0.0}; - float h_B[10] = {0.0, 9.0, 8.0, 7.0, 6.0, 5.0, 4.0, 3.0, 2.0, 1.0}; - float h_C[10]; - float *d_A, *d_B, *d_C; - cudaError_t err; - // Memory allocation for d_A, d_B and d_C (in the device) - err = cudaMalloc((void **)&d_A, sizeof(float) * n); - CHECK_ERR(err); - - err = cudaMalloc((void **)&d_B, sizeof(float) * n); - CHECK_ERR(err); - - err = cudaMalloc((void **)&d_C, sizeof(float) * n); - CHECK_ERR(err); - - // Copying memory to device - err = cudaMemcpy(d_A, h_A, sizeof(float) * n, cudaMemcpyHostToDevice); - CHECK_ERR(err); - - err = cudaMemcpy(d_B, h_B, sizeof(float) * n, cudaMemcpyHostToDevice); - CHECK_ERR(err); - - // Calling the kernel - vecAdd<<>>(d_A, d_B, d_C, n); - - // Copying results back to host - err = cudaMemcpy(h_C, d_C, sizeof(float) * n, cudaMemcpyDeviceToHost); - CHECK_ERR(err); - - EXPECT_EQ(h_C[0], 1.0); - for (int i = 1; i < n - 1; ++i) { - EXPECT_EQ(h_C[i], 11.0); - } - EXPECT_EQ(h_C[9], 1.0); -} -- GitLab From 6e7209f0584f73eb22313d98c676333379736d1e Mon Sep 17 00:00:00 2001 From: liaogang Date: Thu, 29 Jun 2017 16:30:32 +0800 Subject: [PATCH 0926/3256] ENH: Add gpu info interface --- paddle/platform/gpu_info.cc | 25 +++++++++++++++++++++++++ paddle/platform/gpu_info.h | 6 ++++++ 2 files changed, 31 insertions(+) diff --git a/paddle/platform/gpu_info.cc b/paddle/platform/gpu_info.cc index 4208d83078c..d6c6fe34ef4 100644 --- a/paddle/platform/gpu_info.cc +++ b/paddle/platform/gpu_info.cc @@ -42,8 +42,33 @@ size_t GpuMaxAllocSize() { GpuMemoryUsage(available, total); + // Reserve the rest for page tables, etc. return total * FLAGS_fraction_of_gpu_memory_to_use; } +size_t GpuMinChunkSize() { + // Allow to allocate the minimum chunk size is 256 bytes. + return 1 << 8; +} + +size_t GpuMaxChunkSize() { + // Allow to allocate the maximum chunk size is roughly 3% of CPU memory. + size_t total = 0; + size_t available = 0; + + GpuMemoryUsage(available, total); + + // Reserving the rest memory for page tables, etc. + size_t reserving = (1 - FLAGS_fraction_of_gpu_memory_to_use) * total; + + // If available less than minimum chunk size, no usable memory exists. + available = std::max(available, GpuMinChunkSize()) - GpuMinChunkSize(); + + // If available less than reserving, no usable memory exists. + size_t usable = std::max(available, reserving) - reserving; + + return usable; +} + } // namespace platform } // namespace paddle diff --git a/paddle/platform/gpu_info.h b/paddle/platform/gpu_info.h index 174f093b435..d7bf0f4093e 100644 --- a/paddle/platform/gpu_info.h +++ b/paddle/platform/gpu_info.h @@ -30,6 +30,12 @@ void GpuMemoryUsage(size_t& available, size_t& total); //! Get the maximum allocation size of current GPU device. size_t GpuMaxAllocSize(); +//! Get the minimum chunk size for GPU buddy allocator. +size_t GpuMinChunkSize(); + +//! Get the maximum chunk size for GPU buddy allocator. +size_t GpuMaxChunkSize(); + } // namespace platform } // namespace paddle -- GitLab From 464886bf56cd91ffcd6617390d62dbd13c90a093 Mon Sep 17 00:00:00 2001 From: liaogang Date: Thu, 29 Jun 2017 16:31:05 +0800 Subject: [PATCH 0927/3256] FIX: fix typo in piece.h --- paddle/string/piece.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paddle/string/piece.h b/paddle/string/piece.h index db7c3e69804..0272529d1c9 100644 --- a/paddle/string/piece.h +++ b/paddle/string/piece.h @@ -35,7 +35,7 @@ public: // We provide non-explicit singleton constructors so users can // pass in a "const char*" or a "string" wherever a "Piece" - // is expected. These contructors ensure that if data_ is NULL, + // is expected. These constructors ensure that if data_ is NULL, // size_ is 0. Piece(); Piece(const char* d, size_t n); -- GitLab From 5c68aacad1abe9eefc4f1039aca4962b6c6d601f Mon Sep 17 00:00:00 2001 From: caoying03 Date: Thu, 29 Jun 2017 16:48:33 +0800 Subject: [PATCH 0928/3256] follow comments. --- python/paddle/trainer_config_helpers/networks.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/paddle/trainer_config_helpers/networks.py b/python/paddle/trainer_config_helpers/networks.py index 0d730e09951..b77932ce5f0 100755 --- a/python/paddle/trainer_config_helpers/networks.py +++ b/python/paddle/trainer_config_helpers/networks.py @@ -770,12 +770,12 @@ def lstmemory_group(input, :param input: input layer name. :type input: LayerOutput + :param size: lstmemory group size. + :type size: int :param name: name of the lstmemory group. :type name: basestring :param memory_boot: the initialization state of LSTM cell. :type memory_boot: LayerOutput | None - :param size: lstmemory group size. - :type size: int :param reverse: is lstm reversed :type reverse: bool :param param_attr: Parameter config, None if use default. -- GitLab From 0fa409246b98c636d4dd32553782ca962f70a6f7 Mon Sep 17 00:00:00 2001 From: gongweibao Date: Thu, 29 Jun 2017 09:43:00 +0000 Subject: [PATCH 0929/3256] fix bugs --- go/master/c/client.go | 18 ++++++++++++++++-- go/master/client.go | 21 +++++++++++++++------ go/master/client_test.go | 18 ++++++++++++++---- python/paddle/v2/reader/creator.py | 6 ++---- 4 files changed, 47 insertions(+), 16 deletions(-) diff --git a/go/master/c/client.go b/go/master/c/client.go index b88911b858f..79e13e4b63b 100644 --- a/go/master/c/client.go +++ b/go/master/c/client.go @@ -13,6 +13,7 @@ typedef int paddle_master_client; import "C" import ( + "io" "sync" "unsafe" @@ -84,14 +85,27 @@ func paddle_set_dataset(client C.paddle_master_client, path **C.char, size C.int return C.PADDLE_MASTER_OK } +// return value: +// 0:ok +// -1:EOF +// -2:error //export paddle_next_record func paddle_next_record(client C.paddle_master_client, record **C.uchar) C.int { c := get(client) - r := c.NextRecord() - if r == nil { + r, err := c.NextRecord() + if err == io.EOF { // EOF + *record = (*C.uchar)(nullPtr) return -1 } + + if err != nil { + // Error + // TODO: return the type of error? + *record = (*C.uchar)(nullPtr) + return -2 + } + if len(r) == 0 { // Empty record *record = (*C.uchar)(nullPtr) diff --git a/go/master/client.go b/go/master/client.go index fa479338c59..c122d17c8fe 100644 --- a/go/master/client.go +++ b/go/master/client.go @@ -1,6 +1,7 @@ package master import ( + "io" "os" "time" @@ -17,7 +18,12 @@ type Addresser interface { // Client is the client of the master server. type Client struct { conn *connection.Conn - ch chan []byte + ch chan record +} + +type record struct { + r []byte + err error } // NewClient creates a new Client. @@ -27,7 +33,7 @@ type Client struct { func NewClient(addr Addresser, bufSize int) *Client { c := &Client{} c.conn = connection.New() - c.ch = make(chan []byte, bufSize) + c.ch = make(chan record, bufSize) go c.monitorMaster(addr) go c.getRecords() return c @@ -52,18 +58,20 @@ func (c *Client) getRecords() { s := recordio.NewRangeScanner(f, &chunk.Index, -1, -1) for s.Scan() { - c.ch <- s.Record() + c.ch <- record{s.Record(), nil} } if s.Err() != nil { + c.ch <- record{nil, s.Err()} log.Errorln(err, chunk.Path) } err = f.Close() - c.ch <- nil if err != nil { log.Errorln(err) } + + c.ch <- record{nil, io.EOF} } // We treat a task as finished whenever the last data @@ -133,6 +141,7 @@ func (c *Client) taskFinished(taskID int) error { // // NextRecord will block until the next record is available. It is // thread-safe. -func (c *Client) NextRecord() []byte { - return <-c.ch +func (c *Client) NextRecord() ([]byte, error) { + r := <-c.ch + return r.r, r.err } diff --git a/go/master/client_test.go b/go/master/client_test.go index 85a86761c2e..05201941e36 100644 --- a/go/master/client_test.go +++ b/go/master/client_test.go @@ -2,6 +2,7 @@ package master_test import ( "fmt" + "io" "net" "net/http" "net/rpc" @@ -69,13 +70,22 @@ func TestNextRecord(t *testing.T) { for pass := 0; pass < 50; pass++ { received := make(map[byte]bool) - for i := 0; i < total; i++ { - r := c.NextRecord() + for i := 0; i <= total; i++ { + r, err := c.NextRecord() + if err == io.EOF { + break + } + + if err != nil { + t.Fatal(pass, i, "Read error:", err) + } + if len(r) != 1 { - t.Fatal("Length should be 1.", r) + t.Fatal(pass, i, "Length should be 1.", r) } + if received[r[0]] { - t.Fatal("Received duplicate.", received, r) + t.Fatal(pass, i, "Received duplicate.", received, r) } received[r[0]] = true } diff --git a/python/paddle/v2/reader/creator.py b/python/paddle/v2/reader/creator.py index 3376d7accb4..b575f57dc67 100644 --- a/python/paddle/v2/reader/creator.py +++ b/python/paddle/v2/reader/creator.py @@ -79,7 +79,6 @@ def recordio_local(paths): return reader - def recordio(paths, addr="", buf_size=100): """ Creates a data reader that outputs record one one by one @@ -90,8 +89,8 @@ def recordio(paths, addr="", buf_size=100): import os import paddle.v2.master.client as cloud - if len(os.environ["KUBERNETES_SERVICE_HOST"]) == 0: - return recordio_local(path) + if "KUBERNETES_SERVICE_HOST" not in os.environ.keys(): + return recordio_local(paths) def reader(): c = cloud(addr, buf_size) @@ -106,4 +105,3 @@ def recordio(paths, addr="", buf_size=100): c.close() return reader - -- GitLab From b79784ee9e0fd67933d4793e8ab4564f7a30c780 Mon Sep 17 00:00:00 2001 From: gongweibao Date: Thu, 29 Jun 2017 09:52:21 +0000 Subject: [PATCH 0930/3256] fix bugs --- python/paddle/v2/master/client.py | 18 ++++++++++++++---- python/paddle/v2/reader/creator.py | 2 +- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/python/paddle/v2/master/client.py b/python/paddle/v2/master/client.py index 9fd3ef08600..0cc01b73104 100644 --- a/python/paddle/v2/master/client.py +++ b/python/paddle/v2/master/client.py @@ -26,17 +26,27 @@ class client(object): holder[idx] = c_ptr lib.paddle_set_dataset(self.c, holder, len(paths)) + # return format: (record, errno) + # errno = 0: ok + # = -1: EOF + # < -1: error def next_record(self): p = ctypes.c_char_p() ret = ctypes.pointer(p) size = lib.paddle_next_record(self.c, ret) - if size < 0: + if size == -1: # EOF - return None + return None, -1 + + if size < -1: + # Error + return None, size + if size == 0: # Empty record - return "" + return "", 0 + record = ret.contents.value[:size] # Memory created from C should be freed. lib.mem_free(ret.contents) - return record + return record, 0 diff --git a/python/paddle/v2/reader/creator.py b/python/paddle/v2/reader/creator.py index b575f57dc67..2e8626e565c 100644 --- a/python/paddle/v2/reader/creator.py +++ b/python/paddle/v2/reader/creator.py @@ -97,7 +97,7 @@ def recordio(paths, addr="", buf_size=100): c.set_dataset(paths) while True: - r = client.next_record() + r, err = client.next_record() if r is None: break yield r -- GitLab From cc0704227024b58d3721bdd305b9814b57a9d139 Mon Sep 17 00:00:00 2001 From: wuyi05 Date: Thu, 29 Jun 2017 18:50:11 +0800 Subject: [PATCH 0931/3256] update design doc --- doc/design/build_system/README.md | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/doc/design/build_system/README.md b/doc/design/build_system/README.md index 96af6566d0d..3e540e072d0 100644 --- a/doc/design/build_system/README.md +++ b/doc/design/build_system/README.md @@ -108,14 +108,14 @@ As above example CMakeLists.txt executes, each function invocation adds "nodes" ### Using Package Manager For Go -Building go binaries and libraries need to satisfy their dependencies, generally +Building Go binaries and libraries need to satisfy their dependencies, generally we can do `go get ./...` to download and compile all external dependencies. The problems are: -1. `go get` will always get the latest code from master branch, so when an external - project updated and deprecates something or made changes to their APIs, builds - may not pass. This is very different with what we already have in `cmake/external` - which download a specific version or commit id of the dependency. +1. `go get` will always get the latest code from the default branch of the + remote repo, so changes of dependents might break the build. This is very + different with what we already have in `cmake/external` which download a + specific version or commit id of the dependency. 1. Some locations can not access external dependencies through the internet, as mentioned in https://github.com/PaddlePaddle/Paddle/issues/2605. Using package management tools can package the dependencies as a "vendor" package, which can be mirrored @@ -124,10 +124,20 @@ problems are: #### Godep vs. Glide -Here's a brief comparison for current Go ecosystem: https://github.com/Masterminds/glide/wiki/Go-Package-Manager-Comparison. There are -also many complaints about `Godep`. A new "official" pakcage management tool has been -started: https://github.com/golang/dep to resolve such problems, but it's currently -at Alpha stage. So the best choice now is glide obviously. +As mentioned by @wangkuiyi, [Here](https://github.com/golang/go/wiki/PackageManagementTools) +list dozens of Go package managers. We choose the tool using following principles: + +- Most "active" projects with more stars, more pull requests or commits +- Commonly used project + +Then we shall choose between the most popular tools: Godep and Glide. + +Here's a brief comparison between Godep and Glide +: https://github.com/Masterminds/glide/wiki/Go-Package-Manager-Comparison. There are +also many complaints about using `Godep`. There's also a new "official" pakcage +management tool has been started at: https://github.com/golang/dep to resolve +such problems, but it's currently at Alpha stage. So the best choice now is +glide obviously. #### Manage Go Packages -- GitLab From 7993ff794d23f07804cbe1308e6d8c143b9cdc1f Mon Sep 17 00:00:00 2001 From: wuyi05 Date: Thu, 29 Jun 2017 19:04:03 +0800 Subject: [PATCH 0932/3256] update design doc --- doc/design/build_system/README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/doc/design/build_system/README.md b/doc/design/build_system/README.md index 3e540e072d0..bf0e4dddc1b 100644 --- a/doc/design/build_system/README.md +++ b/doc/design/build_system/README.md @@ -122,15 +122,16 @@ problems are: at many cloud file hosting, so users what to compile paddle by themselves can download this "vendor" package from a mirror site. -#### Godep vs. Glide +#### Choose A Suitable Tool As mentioned by @wangkuiyi, [Here](https://github.com/golang/go/wiki/PackageManagementTools) list dozens of Go package managers. We choose the tool using following principles: - Most "active" projects with more stars, more pull requests or commits -- Commonly used project +- Widely used project -Then we shall choose between the most popular tools: Godep and Glide. +After comparing all these projects, we shall choose between the most popular +tools: Godep and Glide. Here's a brief comparison between Godep and Glide : https://github.com/Masterminds/glide/wiki/Go-Package-Manager-Comparison. There are -- GitLab From 26cd0bb5a59d913f8c216ceee0c6abb46317e31e Mon Sep 17 00:00:00 2001 From: liaogang Date: Thu, 29 Jun 2017 19:13:24 +0800 Subject: [PATCH 0933/3256] ENH: count allocated fallback size for performance --- paddle/memory/detail/system_allocator.cc | 52 +++++++++++++------ paddle/memory/detail/system_allocator.h | 3 +- .../paddle/trainer_config_helpers/networks.py | 4 +- 3 files changed, 39 insertions(+), 20 deletions(-) diff --git a/paddle/memory/detail/system_allocator.cc b/paddle/memory/detail/system_allocator.cc index 332ff062d47..2b0fbfa87e2 100644 --- a/paddle/memory/detail/system_allocator.cc +++ b/paddle/memory/detail/system_allocator.cc @@ -39,22 +39,22 @@ void* CPUAllocator::Alloc(size_t& index, size_t size) { // pointer shall not be dereferenced -- so we make it nullptr. if (size <= 0) return nullptr; - if (FLAGS_use_pinned_memory) { - void* p = malloc(size); - if (p != nullptr) { - mlock(p, size); - } - } + index = 0; // unlock memory void* p = malloc(size); - if (p != nullptr && FLAGS_use_pinned_memory) { - mlock(p, size); + + if (p != nullptr) { + if (FLAGS_use_pinned_memory) { + index = 1; + mlock(p, size); // lock memory + } } + return p; } void CPUAllocator::Free(void* p, size_t size, size_t index) { - if (p != nullptr && FLAGS_use_pinned_memory) { + if (p != nullptr && index == 1) { munlock(p, size); } free(p); @@ -73,26 +73,34 @@ void* GPUAllocator::Alloc(size_t& index, size_t size) { // Reserve memory for page tables, etc. size_t reserving = capacity - paddle::platform::GpuMaxAllocSize(); - size_t remaining = available > reserving ? available - reserving : 0; + size_t usable = available > reserving ? available - reserving : 0; // If remaining size no less than expected size, using general // cudaMalloc to allocate GPU memory. void* p = 0; - if (size <= remaining) { + if (size <= usable) { cudaError_t result = cudaMalloc(&p, size); if (result == cudaSuccess) { index = 0; - total_alloc_size_ += size; + gpu_alloc_size_ += size; return p; } } // If remaining size less than expected size or cudaMalloc failed, // cudaMallocHost will be considered as a fallback allocator. + // + // NOTE: here, we use GpuMaxAllocSize() as the maximum memory size + // of host fallback allocation. Allocates too much would reduce + // the amount of memory available to the underlying system for paging. + usable = paddle::platform::GpuMaxAllocSize() - fallback_alloc_size_; + + if (size > usable) return nullptr; + cudaError_t result = cudaMallocHost(&p, size); if (result == cudaSuccess) { index = 1; - total_alloc_size_ += size; + fallback_alloc_size_ += size; return p; } @@ -100,16 +108,26 @@ void* GPUAllocator::Alloc(size_t& index, size_t size) { } void GPUAllocator::Free(void* p, size_t size, size_t index) { + cudaError_t err; + + if (index == 0) { + PADDLE_ASSERT(gpu_alloc_size_ >= size); + gpu_alloc_size_ -= size; + err = cudaFree(p); + } else { + PADDLE_ASSERT(fallback_alloc_size_ >= size); + fallback_alloc_size_ -= size; + err = cudaFreeHost(p); + } + // Purposefully allow cudaErrorCudartUnloading, because // that is returned if you ever call cudaFree after the // driver has already shutdown. This happens only if the // process is terminating, in which case we don't care if // cudaFree succeeds. - PADDLE_ASSERT(total_alloc_size_ >= size); - total_alloc_size_ -= size; - cudaError_t err = index == 1 ? cudaFreeHost(p) : cudaFree(p); if (err != cudaErrorCudartUnloading) { - platform::throw_on_error(err, "cudaFree{Host} failed"); + platform::throw_on_error(err, + "cudaFree{Host} failed in GPUAllocator::Free."); } } diff --git a/paddle/memory/detail/system_allocator.h b/paddle/memory/detail/system_allocator.h index e15302ce4f0..7093c429671 100644 --- a/paddle/memory/detail/system_allocator.h +++ b/paddle/memory/detail/system_allocator.h @@ -47,7 +47,8 @@ class GPUAllocator : public SystemAllocator { virtual void Free(void* p, size_t size, size_t index); private: - size_t total_alloc_size_ = 0; + size_t gpu_alloc_size_ = 0; + size_t fallback_alloc_size_ = 0; }; #endif // PADDLE_ONLY_CPU diff --git a/python/paddle/trainer_config_helpers/networks.py b/python/paddle/trainer_config_helpers/networks.py index 67154a8d7d3..1bf59ed4840 100755 --- a/python/paddle/trainer_config_helpers/networks.py +++ b/python/paddle/trainer_config_helpers/networks.py @@ -1381,7 +1381,7 @@ def inputs(layers, *args): if len(args) != 0: layers.extend(args) - Inputs(* [l.name for l in layers]) + Inputs(*[l.name for l in layers]) def outputs(layers, *args): @@ -1424,7 +1424,7 @@ def outputs(layers, *args): assert len(layers) > 0 if HasInputsSet(): # input already set - Outputs(* [l.name for l in layers]) + Outputs(*[l.name for l in layers]) return # just return outputs. if len(layers) != 1: -- GitLab From fb51c3dc895b78df966dd0d9713657289b1986b3 Mon Sep 17 00:00:00 2001 From: liaogang Date: Thu, 29 Jun 2017 19:57:10 +0800 Subject: [PATCH 0934/3256] FIX: add compile dependency gflags --- paddle/platform/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/paddle/platform/CMakeLists.txt b/paddle/platform/CMakeLists.txt index 969c91985d0..5cbe491b2b5 100644 --- a/paddle/platform/CMakeLists.txt +++ b/paddle/platform/CMakeLists.txt @@ -1,7 +1,7 @@ -cc_library(cpu_info SRCS cpu_info.cc) +cc_library(cpu_info SRCS cpu_info.cc DEPS gflags) cc_test(cpu_info_test SRCS cpu_info_test.cc DEPS cpu_info gflags glog) -nv_library(gpu_info SRCS gpu_info.cc) +nv_library(gpu_info SRCS gpu_info.cc DEPS gflags) cc_library(place SRCS place.cc) cc_test(place_test SRCS place_test.cc DEPS place glog gflags) -- GitLab From 3a0919bab31fd64ea6ae73a61755b92c619a411e Mon Sep 17 00:00:00 2001 From: yangyaming Date: Thu, 29 Jun 2017 21:31:42 +0800 Subject: [PATCH 0935/3256] Add test for configuration and add doc. --- doc/api/v2/config/layer.rst | 13 ++++++++++ .../tests/configs/file_list.sh | 2 +- .../configs/test_detection_output_layer.py | 23 +++++++++++++++++ .../tests/configs/test_multibox_loss_layer.py | 25 +++++++++++++++++++ 4 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 python/paddle/trainer_config_helpers/tests/configs/test_detection_output_layer.py create mode 100644 python/paddle/trainer_config_helpers/tests/configs/test_multibox_loss_layer.py diff --git a/doc/api/v2/config/layer.rst b/doc/api/v2/config/layer.rst index c7b017bc07b..0a8465919d9 100644 --- a/doc/api/v2/config/layer.rst +++ b/doc/api/v2/config/layer.rst @@ -445,6 +445,11 @@ smooth_l1_cost .. autoclass:: paddle.v2.layer.smooth_l1_cost :noindex: +multibox_loss +-------------- +.. autoclass:: paddle.v2.layer.multibox_loss + :noindex: + Check Layer ============ @@ -468,3 +473,11 @@ prelu -------- .. autoclass:: paddle.v2.layer.prelu :noindex: + +Detection output Layer +====================== + +detection_output +--- +.. autoclass:: paddle.v2.layer.detection_output + :noindex: diff --git a/python/paddle/trainer_config_helpers/tests/configs/file_list.sh b/python/paddle/trainer_config_helpers/tests/configs/file_list.sh index c24102255f5..45fb8488865 100755 --- a/python/paddle/trainer_config_helpers/tests/configs/file_list.sh +++ b/python/paddle/trainer_config_helpers/tests/configs/file_list.sh @@ -6,6 +6,6 @@ img_layers img_trans_layers util_layers simple_rnn_layers unused_layers test_cos test_rnn_group shared_fc shared_lstm shared_gru test_cost_layers_with_weight test_spp_layer test_bilinear_interp test_maxout test_bi_grumemory math_ops test_seq_concat_reshape test_pad test_smooth_l1 test_multiplex_layer -test_prelu_layer test_row_conv) +test_prelu_layer test_row_conv test_detection_output_layer test_multibox_loss_layer) export whole_configs=(test_split_datasource) diff --git a/python/paddle/trainer_config_helpers/tests/configs/test_detection_output_layer.py b/python/paddle/trainer_config_helpers/tests/configs/test_detection_output_layer.py new file mode 100644 index 00000000000..3572a2cb07d --- /dev/null +++ b/python/paddle/trainer_config_helpers/tests/configs/test_detection_output_layer.py @@ -0,0 +1,23 @@ +from paddle.trainer_config_helpers import * + +settings(batch_size=1000, learning_rate=1e-5) + +input_loc = data_layer(name='input_loc', size=16, height=16, width=1) + +input_conf = data_layer(name='input_conf', size=8, height=1, width=8) + +priorbox = data_layer(name='priorbox', size=32, height=4, width=8) + +detout = detection_output_layer( + input_loc=input_loc, + input_conf=input_conf, + priorbox=priorbox, + num_classes=21, + nms_threshold=0.45, + nms_top_k=400, + keep_top_k=200, + confidence_threshold=0.01, + background_id=0, + name='test_detection_output') + +outputs(detout) diff --git a/python/paddle/trainer_config_helpers/tests/configs/test_multibox_loss_layer.py b/python/paddle/trainer_config_helpers/tests/configs/test_multibox_loss_layer.py new file mode 100644 index 00000000000..c3376c47bde --- /dev/null +++ b/python/paddle/trainer_config_helpers/tests/configs/test_multibox_loss_layer.py @@ -0,0 +1,25 @@ +from paddle.trainer_config_helpers import * + +settings(batch_size=1000, learning_rate=1e-5) + +input_loc = data_layer(name='input_loc', size=16, height=16, width=1) + +input_conf = data_layer(name='input_conf', size=8, height=1, width=8) + +priorbox = data_layer(name='priorbox', size=32, height=4, width=8) + +label = data_layer(name='label', size=24, height=4, width=6) + +multibox_loss = multibox_loss_layer( + input_loc=input_loc, + input_conf=input_conf, + priorbox=priorbox, + label=label, + num_classes=21, + overlap_threshold=0.5, + neg_pos_ratio=3.0, + neg_overlap=0.5, + background_id=0, + name='test_multibox_loss') + +outputs(multibox_loss) -- GitLab From 7c066f6e3e43cfc2b43d46f5e860a291b125b3d4 Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Fri, 30 Jun 2017 00:45:07 +0000 Subject: [PATCH 0936/3256] fix according to comments --- doc/design/cluster_train/save_model.md | 52 +++++++++++++++----------- 1 file changed, 31 insertions(+), 21 deletions(-) diff --git a/doc/design/cluster_train/save_model.md b/doc/design/cluster_train/save_model.md index 3a9a24fb9ce..76ac8d83870 100644 --- a/doc/design/cluster_train/save_model.md +++ b/doc/design/cluster_train/save_model.md @@ -7,24 +7,34 @@ ways from which user can obtain a model: - Save model triggered by user code: user code asks PaddlePaddle to save a model. -- Convert model from the snapshot: model being converted from - pservers' periodic snapshot. In this way, the user can cancel a job - at any time, and still have a relatively fresh model (we snapshot - around every 5 minutes). +- Convert model from the checkpoint: model being converted from + pservers' periodic checkpoint. In this way, the user can cancel a + job at any time, and still have a relatively fresh model (we + checkpoint around every 5 minutes). -### Save Model Triggered by User Code +### Trainer Saving Model vs. Pservers Saving Model Both trainers and pservers have access to the model. So the model can be saved from a trainer or pservers. We need to decide on where the model is saved from. -#### Dense Model vs. Sparse Model +#### Dense Update vs. Sparse Update + +There are two types of model update methods: dense update and sparse +update (when the parameter is configured to be sparse). + +- Dense update + + Every trainer has it's own full copy of the model. Every model + update will update the entire model. + +- Sparse update + + The training input is sparse, and the trainer does not have the + entire model. It will only download the sub-model necessary related + to the input. When updating the model, only the sub-model related to + the training input is updated. -There are two types of model: dense and sparse model (when the -parameter is configured to be sparse). Pservers always jointly have -the entire model at any given time. Trainers only have the entire -dense model, but only have a fraction of the sparse model at any given -time. #### Pservers Saving Model @@ -32,15 +42,15 @@ The benefit of letting pservers save model is they have the entire model all the time. However, since pservers are on different nodes, it requires a merging process to merge model shards into the same model. Thus requires the pservers to write models to a distributed -filesystem, making the snapshot shards visible to the merge program. +filesystem, making the checkpoint shards visible to the merge program. #### Trainer Saving Model The benefit of letting one trainer to save the model is it does not require a distributed filesystem. And it's reusing the same save model -logic when the trainer is training locally - except when training -sparse model, the trainer needs to download the entire sparse model -during the saving process. +logic when the trainer is training locally - except when doing sparse +update, the trainer needs to download the entire model during the +saving process. #### Conclusion @@ -49,7 +59,7 @@ and is an intuitive extension to training locally, we decide to let the trainer save the model. -### Convert Model from Snapshot +### Convert Model from Checkpoint TODO @@ -86,15 +96,15 @@ when save model is taking place. When saving a dense model, the trainer uses the local model. Pservers does not need to pause model update. -When saving a sparse model. The trainer needs to download the entire -sparse model while saving. To get the most accurate model, the model -update needs to be paused before the download starts and resumed after -the download finishes. Otherwise, the trainer gets a model that is +When doing sparse update. The trainer needs to download the entire +model while saving. To get the most accurate model, the model update +needs to be paused before the download starts and resumed after the +download finishes. Otherwise, the trainer gets a model that is "polluted": some part of the model is old, some part of the model is new. It's unclear that the "polluted" model will be inferiod due to the stochastic nature of deep learning, and pausing the model update will -add more complexity to the system. Since supporting sparse model is a +add more complexity to the system. Since supporting sparse update is a TODO item. We defer the evaluation of pause the model update or not during saving model to the future. -- GitLab From 59cf5e77962d743b7535e3ffd72e7ebe00c29502 Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Wed, 28 Jun 2017 22:32:33 +0000 Subject: [PATCH 0937/3256] Fix Go cmake --- CMakeLists.txt | 31 ++++++++++++++++++++++---- cmake/generic.cmake | 26 ++++++--------------- go/master/c/CMakeLists.txt | 20 +---------------- go/pserver/cclient/test/CMakeLists.txt | 2 +- paddle/trainer/CMakeLists.txt | 4 ++-- python/CMakeLists.txt | 18 ++++++++++----- python/paddle/v2/__init__.py | 1 + python/setup.py.in | 2 ++ 8 files changed, 54 insertions(+), 50 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 24a7066adc5..edea8279dfa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -130,14 +130,37 @@ if(WITH_GPU) endif(WITH_GPU) add_subdirectory(proto) -add_subdirectory(paddle) -add_subdirectory(python) +# "add_subdirectory(paddle)" and "add_subdirectory(python)" should be +# placed after this block, because they depends on it. if(WITH_GOLANG) - #TODO (add go/master/c back when fixed) - add_subdirectory(go/pserver/cclient) + # we need to symlink Paddle directory into GOPATH. If we + # don't do it and we have code that depends on Paddle, go + # get ./... will download a new Paddle repo from Github, + # without the changes in our current Paddle repo that we + # want to build. + set(GOPATH "${CMAKE_CURRENT_BINARY_DIR}/go") + file(MAKE_DIRECTORY ${GOPATH}) + set(PADDLE_IN_GOPATH "${GOPATH}/src/github.com/PaddlePaddle/Paddle") + add_custom_target(go_path) + add_custom_command(TARGET go_path + # Symlink Paddle directory into GOPATH + COMMAND mkdir -p ${PADDLE_IN_GOPATH} + COMMAND rm -rf ${PADDLE_IN_GOPATH} + COMMAND ln -sf ${CMAKE_SOURCE_DIR} ${PADDLE_IN_GOPATH} + # Automatically get all dependencies specified in the source code + # We can't run `go get -d ./...` for every target, because + # multiple `go get` can not run concurrently, but make need to be + # able to run with multiple jobs. + COMMAND env GOPATH=${GOPATH} ${CMAKE_Go_COMPILER} get -d ./go/... + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + ) + add_subdirectory(go/master/c) + add_subdirectory(go/pserver/cclient) endif(WITH_GOLANG) +add_subdirectory(paddle) +add_subdirectory(python) if(WITH_DOC) add_subdirectory(doc) endif() diff --git a/cmake/generic.cmake b/cmake/generic.cmake index 11c1f677ae5..8a9bf12cccb 100644 --- a/cmake/generic.cmake +++ b/cmake/generic.cmake @@ -253,10 +253,6 @@ function(nv_test TARGET_NAME) endif() endfunction(nv_test) -set(GOPATH "${CMAKE_CURRENT_BINARY_DIR}/go") -file(MAKE_DIRECTORY ${GOPATH}) -set(PADDLE_IN_GOPATH "${GOPATH}/src/github.com/PaddlePaddle/Paddle") - function(go_library TARGET_NAME) set(options STATIC static SHARED shared) set(oneValueArgs "") @@ -265,10 +261,10 @@ function(go_library TARGET_NAME) if (go_library_SHARED OR go_library_shared) set(BUILD_MODE "-buildmode=c-shared") - set(LIB_NAME "${CMAKE_SHARED_LIBRARY_PREFIX}${TARGET_NAME}${CMAKE_SHARED_LIBRARY_SUFFIX}") + set(${TARGET_NAME}_LIB_NAME "${CMAKE_SHARED_LIBRARY_PREFIX}${TARGET_NAME}${CMAKE_SHARED_LIBRARY_SUFFIX}" CACHE STRING "output library name for target ${TARGET_NAME}") else() set(BUILD_MODE "-buildmode=c-archive") - set(LIB_NAME "${CMAKE_STATIC_LIBRARY_PREFIX}${TARGET_NAME}${CMAKE_STATIC_LIBRARY_SUFFIX}") + set(${TARGET_NAME}_LIB_NAME "${CMAKE_STATIC_LIBRARY_PREFIX}${TARGET_NAME}${CMAKE_STATIC_LIBRARY_SUFFIX}" CACHE STRING "output library name for target ${TARGET_NAME}") endif() # Add dummy code to support `make target_name` under Terminal Command @@ -283,25 +279,17 @@ function(go_library TARGET_NAME) add_dependencies(${TARGET_NAME} ${go_library_DEPS}) endif(go_library_DEPS) - # we need to symlink Paddle directory into GOPATH. If we - # don't do it and we have code that depends on Paddle, go - # get ./... will download a new Paddle repo from Github, - # without the changes in our current Paddle repo that we - # want to build. + set(${TARGET_NAME}_LIB_PATH "${CMAKE_CURRENT_BINARY_DIR}/${${TARGET_NAME}_LIB_NAME}" CACHE STRING "output library path for target ${TARGET_NAME}") + file(GLOB GO_SOURCE RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "*.go") add_custom_command(TARGET ${TARGET_NAME} POST_BUILD - COMMAND rm "${CMAKE_CURRENT_BINARY_DIR}/${LIB_NAME}" - # Symlink Paddle directory into GOPATH - COMMAND mkdir -p ${PADDLE_IN_GOPATH} - COMMAND rm -rf ${PADDLE_IN_GOPATH} - COMMAND ln -sf ${CMAKE_SOURCE_DIR} ${PADDLE_IN_GOPATH} - # Automatically get all dependencies specified in the source code - COMMAND env GOPATH=${GOPATH} ${CMAKE_Go_COMPILER} get -d ./... + COMMAND rm "${${TARGET_NAME}_LIB_PATH}" # Golang build source code COMMAND env GOPATH=${GOPATH} ${CMAKE_Go_COMPILER} build ${BUILD_MODE} - -o "${CMAKE_CURRENT_BINARY_DIR}/${LIB_NAME}" + -o "${${TARGET_NAME}_LIB_PATH}" ${GO_SOURCE} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) + add_dependencies(${TARGET_NAME} go_path) endfunction(go_library) function(go_binary TARGET_NAME) diff --git a/go/master/c/CMakeLists.txt b/go/master/c/CMakeLists.txt index acce698051e..a4e92635bab 100644 --- a/go/master/c/CMakeLists.txt +++ b/go/master/c/CMakeLists.txt @@ -1,21 +1,3 @@ cmake_minimum_required(VERSION 3.0) -get_filename_component(PARENT_DIR ${CMAKE_CURRENT_SOURCE_DIR} DIRECTORY) -get_filename_component(PARENT_DIR ${PARENT_DIR} DIRECTORY) -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${PARENT_DIR}/cmake") - -project(cxx_go C Go) - -include(golang) -include(flags) - -set(MASTER_LIB_NAME "paddle_master") -go_library(${MASTER_LIB_NAME} SHARED) - -if(PROJ_ROOT) - add_custom_command(OUTPUT ${PROJ_ROOT}/python/paddle/v2/master/lib${MASTER_LIB_NAME}.so - COMMAND rm ${CMAKE_CURRENT_BINARY_DIR}/lib${MASTER_LIB_NAME}.h - COMMAND cp ${CMAKE_CURRENT_BINARY_DIR}/lib${MASTER_LIB_NAME}.so ${PROJ_ROOT}/python/paddle/v2/master/ - DEPENDS ${MASTER_LIB_NAME}) - add_custom_target(paddle_master_shared ALL DEPENDS ${PROJ_ROOT}/python/paddle/v2/master/lib${MASTER_LIB_NAME}.so) -endif(PROJ_ROOT) +go_library(paddle_master SHARED) diff --git a/go/pserver/cclient/test/CMakeLists.txt b/go/pserver/cclient/test/CMakeLists.txt index 916e4e99a24..170730ccebb 100644 --- a/go/pserver/cclient/test/CMakeLists.txt +++ b/go/pserver/cclient/test/CMakeLists.txt @@ -1,3 +1,3 @@ -cc_library(main SRCS main.c DEPS paddle_pserver_cclient) +cc_binary(main SRCS main.c DEPS paddle_pserver_cclient) cc_test(test_cclient SRCS test_cclient.c DEPS paddle_pserver_cclient) diff --git a/paddle/trainer/CMakeLists.txt b/paddle/trainer/CMakeLists.txt index f34d53ae99f..54e74248e7a 100644 --- a/paddle/trainer/CMakeLists.txt +++ b/paddle/trainer/CMakeLists.txt @@ -72,6 +72,6 @@ endif() if(WITH_GOLANG) add_dependencies(paddle_trainer_lib paddle_pserver_cclient) - target_link_libraries(paddle_trainer ${CMAKE_BINARY_DIR}/go/pserver/cclient/libpaddle_pserver_cclient.a) - target_link_libraries(paddle_trainer_lib ${CMAKE_BINARY_DIR}/go/pserver/cclient/libpaddle_pserver_cclient.a) + target_link_libraries(paddle_trainer paddle_pserver_cclient) + target_link_libraries(paddle_trainer_lib paddle_pserver_cclient) endif(WITH_GOLANG) diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index 3640dd3a75e..a9842152c86 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -7,10 +7,18 @@ file(GLOB UTILS_PY_FILES . ./paddle/utils/*.py) file(GLOB_RECURSE V2_PY_FILES ./paddle/v2/ *.py) set(PY_FILES paddle/__init__.py - ${TRAINER_PY_FILES} - ${HELPERS_PY_FILES} - ${UTILS_PY_FILES} - ${V2_PY_FILES}) + ${TRAINER_PY_FILES} + ${HELPERS_PY_FILES} + ${UTILS_PY_FILES} + ${V2_PY_FILES}) + +add_custom_target(copy_paddle_master) +if(WITH_GOLANG) + add_custom_command(TARGET copy_paddle_master + COMMAND cp ${paddle_master_LIB_PATH} ${PROJ_ROOT}/python/paddle/v2/master/ + ) + add_dependencies(copy_paddle_master paddle_master) +endif(WITH_GOLANG) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/setup.py.in ${CMAKE_CURRENT_BINARY_DIR}/setup.py) @@ -18,7 +26,7 @@ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/setup.py.in add_custom_command(OUTPUT ${OUTPUT_DIR}/.timestamp COMMAND env ${py_env} ${PYTHON_EXECUTABLE} setup.py bdist_wheel COMMAND ${CMAKE_COMMAND} -E touch ${OUTPUT_DIR}/.timestamp - DEPENDS gen_proto_py ${PY_FILES} ${external_project_dependencies}) + DEPENDS gen_proto_py ${PY_FILES} ${external_project_dependencies} copy_paddle_master) add_custom_target(paddle_python ALL DEPENDS ${OUTPUT_DIR}/.timestamp) diff --git a/python/paddle/v2/__init__.py b/python/paddle/v2/__init__.py index 6a1e23a343d..3ba5c318718 100644 --- a/python/paddle/v2/__init__.py +++ b/python/paddle/v2/__init__.py @@ -56,6 +56,7 @@ __all__ = [ 'plot', 'evaluator', 'image', + 'master', ] diff --git a/python/setup.py.in b/python/setup.py.in index 86fc0fc5c03..e507acaf213 100644 --- a/python/setup.py.in +++ b/python/setup.py.in @@ -8,6 +8,7 @@ packages=['paddle', 'paddle.v2', 'paddle.v2.dataset', 'paddle.v2.reader', + 'paddle.v2.master', 'paddle.v2.plot'] setup_requires=["requests", @@ -25,6 +26,7 @@ setup(name='paddle', description='Parallel Distributed Deep Learning', install_requires=setup_requires, packages=packages, + package_data={'paddle.v2.master': ['${paddle_master_LIB_NAME}'], }, package_dir={ '': '${CMAKE_CURRENT_SOURCE_DIR}' }, -- GitLab From b3c5808e13bc94fbc933c803c59fed979a11f515 Mon Sep 17 00:00:00 2001 From: gongweibao Date: Fri, 30 Jun 2017 03:11:57 +0000 Subject: [PATCH 0938/3256] rm cloud EOF --- go/master/c/client.go | 7 ------- go/master/client.go | 3 --- go/master/client_test.go | 7 +------ python/paddle/v2/master/client.py | 5 ----- 4 files changed, 1 insertion(+), 21 deletions(-) diff --git a/go/master/c/client.go b/go/master/c/client.go index 79e13e4b63b..a37894fefec 100644 --- a/go/master/c/client.go +++ b/go/master/c/client.go @@ -13,7 +13,6 @@ typedef int paddle_master_client; import "C" import ( - "io" "sync" "unsafe" @@ -93,12 +92,6 @@ func paddle_set_dataset(client C.paddle_master_client, path **C.char, size C.int func paddle_next_record(client C.paddle_master_client, record **C.uchar) C.int { c := get(client) r, err := c.NextRecord() - if err == io.EOF { - // EOF - *record = (*C.uchar)(nullPtr) - return -1 - } - if err != nil { // Error // TODO: return the type of error? diff --git a/go/master/client.go b/go/master/client.go index c122d17c8fe..985b96b0af4 100644 --- a/go/master/client.go +++ b/go/master/client.go @@ -1,7 +1,6 @@ package master import ( - "io" "os" "time" @@ -70,8 +69,6 @@ func (c *Client) getRecords() { if err != nil { log.Errorln(err) } - - c.ch <- record{nil, io.EOF} } // We treat a task as finished whenever the last data diff --git a/go/master/client_test.go b/go/master/client_test.go index 05201941e36..0a401d8a43c 100644 --- a/go/master/client_test.go +++ b/go/master/client_test.go @@ -2,7 +2,6 @@ package master_test import ( "fmt" - "io" "net" "net/http" "net/rpc" @@ -70,12 +69,8 @@ func TestNextRecord(t *testing.T) { for pass := 0; pass < 50; pass++ { received := make(map[byte]bool) - for i := 0; i <= total; i++ { + for i := 0; i < total; i++ { r, err := c.NextRecord() - if err == io.EOF { - break - } - if err != nil { t.Fatal(pass, i, "Read error:", err) } diff --git a/python/paddle/v2/master/client.py b/python/paddle/v2/master/client.py index 0cc01b73104..6ddb09e4e89 100644 --- a/python/paddle/v2/master/client.py +++ b/python/paddle/v2/master/client.py @@ -28,16 +28,11 @@ class client(object): # return format: (record, errno) # errno = 0: ok - # = -1: EOF # < -1: error def next_record(self): p = ctypes.c_char_p() ret = ctypes.pointer(p) size = lib.paddle_next_record(self.c, ret) - if size == -1: - # EOF - return None, -1 - if size < -1: # Error return None, size -- GitLab From 97bbd179569f48bfcf1a3ff3225c331ad8e3fbf4 Mon Sep 17 00:00:00 2001 From: gongweibao Date: Fri, 30 Jun 2017 03:14:29 +0000 Subject: [PATCH 0939/3256] rm cloud EOF --- go/master/c/client.go | 1 - 1 file changed, 1 deletion(-) diff --git a/go/master/c/client.go b/go/master/c/client.go index a37894fefec..13ed3b76804 100644 --- a/go/master/c/client.go +++ b/go/master/c/client.go @@ -86,7 +86,6 @@ func paddle_set_dataset(client C.paddle_master_client, path **C.char, size C.int // return value: // 0:ok -// -1:EOF // -2:error //export paddle_next_record func paddle_next_record(client C.paddle_master_client, record **C.uchar) C.int { -- GitLab From a53952165bbaebb34d2ce91ca361b470a2a3238a Mon Sep 17 00:00:00 2001 From: liaogang Date: Fri, 30 Jun 2017 11:22:49 +0800 Subject: [PATCH 0940/3256] FIX: merge objects to static lib --- cmake/generic.cmake | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cmake/generic.cmake b/cmake/generic.cmake index 61353a4a262..f2e3934c27f 100644 --- a/cmake/generic.cmake +++ b/cmake/generic.cmake @@ -146,9 +146,9 @@ function(merge_static_libs TARGET_NAME) set(outlibfile "$") foreach(lib ${libs}) - add_custom_command(TARGET ${TARGET_NAME} POST_BUILD - COMMAND ${CMAKE_AR} ru ${outlibfile} @"../${objlistfile}" - WORKING_DIRECTORY ${objdir}) + add_custom_command(TARGET ${TARGET_NAME} POST_BUILD + COMMAND ${CMAKE_AR} ru ${outlibfile} @"../${lib}.objlist" + WORKING_DIRECTORY ${lib}.objdir) endforeach() add_custom_command(TARGET ${TARGET_NAME} POST_BUILD -- GitLab From 38790c1c210c57b9cab5f1abe14203e053831ec5 Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Thu, 29 Jun 2017 21:30:43 -0700 Subject: [PATCH 0941/3256] fix according to comment --- CMakeLists.txt | 21 --------------------- cmake/configure.cmake | 24 ++++++++++++++++++++++++ 2 files changed, 24 insertions(+), 21 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index edea8279dfa..b2481912232 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -134,27 +134,6 @@ add_subdirectory(proto) # "add_subdirectory(paddle)" and "add_subdirectory(python)" should be # placed after this block, because they depends on it. if(WITH_GOLANG) - # we need to symlink Paddle directory into GOPATH. If we - # don't do it and we have code that depends on Paddle, go - # get ./... will download a new Paddle repo from Github, - # without the changes in our current Paddle repo that we - # want to build. - set(GOPATH "${CMAKE_CURRENT_BINARY_DIR}/go") - file(MAKE_DIRECTORY ${GOPATH}) - set(PADDLE_IN_GOPATH "${GOPATH}/src/github.com/PaddlePaddle/Paddle") - add_custom_target(go_path) - add_custom_command(TARGET go_path - # Symlink Paddle directory into GOPATH - COMMAND mkdir -p ${PADDLE_IN_GOPATH} - COMMAND rm -rf ${PADDLE_IN_GOPATH} - COMMAND ln -sf ${CMAKE_SOURCE_DIR} ${PADDLE_IN_GOPATH} - # Automatically get all dependencies specified in the source code - # We can't run `go get -d ./...` for every target, because - # multiple `go get` can not run concurrently, but make need to be - # able to run with multiple jobs. - COMMAND env GOPATH=${GOPATH} ${CMAKE_Go_COMPILER} get -d ./go/... - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} - ) add_subdirectory(go/master/c) add_subdirectory(go/pserver/cclient) endif(WITH_GOLANG) diff --git a/cmake/configure.cmake b/cmake/configure.cmake index e8425aedbdd..f6dca6d5759 100644 --- a/cmake/configure.cmake +++ b/cmake/configure.cmake @@ -69,3 +69,27 @@ endif(NOT WITH_GPU) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${SIMD_FLAG}") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${SIMD_FLAG}") + +if(WITH_GOLANG) + # we need to symlink Paddle directory into GOPATH. If we + # don't do it and we have code that depends on Paddle, go + # get ./... will download a new Paddle repo from Github, + # without the changes in our current Paddle repo that we + # want to build. + set(GOPATH "${CMAKE_CURRENT_BINARY_DIR}/go") + file(MAKE_DIRECTORY ${GOPATH}) + set(PADDLE_IN_GOPATH "${GOPATH}/src/github.com/PaddlePaddle/Paddle") + add_custom_target(go_path) + add_custom_command(TARGET go_path + # Symlink Paddle directory into GOPATH + COMMAND mkdir -p ${PADDLE_IN_GOPATH} + COMMAND rm -rf ${PADDLE_IN_GOPATH} + COMMAND ln -sf ${CMAKE_SOURCE_DIR} ${PADDLE_IN_GOPATH} + # Automatically get all dependencies specified in the source code + # We can't run `go get -d ./...` for every target, because + # multiple `go get` can not run concurrently, but make need to be + # able to run with multiple jobs. + COMMAND env GOPATH=${GOPATH} ${CMAKE_Go_COMPILER} get -d ./go/... + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + ) +endif(WITH_GOLANG) -- GitLab From d09bbb559d67848b6a17ff87432580f2c32db9e8 Mon Sep 17 00:00:00 2001 From: liaogang Date: Fri, 30 Jun 2017 12:44:18 +0800 Subject: [PATCH 0942/3256] FIX: Always build protobuf from source --- cmake/external/protobuf.cmake | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/cmake/external/protobuf.cmake b/cmake/external/protobuf.cmake index 2f267adc203..3c74944bc21 100644 --- a/cmake/external/protobuf.cmake +++ b/cmake/external/protobuf.cmake @@ -144,18 +144,7 @@ FUNCTION(build_protobuf TARGET_NAME BUILD_FOR_HOST) ENDFUNCTION() SET(PROTOBUF_VERSION 3.1) -IF(NOT CMAKE_CROSSCOMPILING) - FIND_PACKAGE(Protobuf ${PROTOBUF_VERSION}) - - IF(PROTOBUF_FOUND) - SET_PROTOBUF_VERSION() - IF("${PROTOBUF_VERSION}" VERSION_LESS "3.1.0") - SET(PROTOBUF_FOUND OFF) - ELSE() - PROMPT_PROTOBUF_LIB() - ENDIF() - ENDIF(PROTOBUF_FOUND) -ELSE() +IF(CMAKE_CROSSCOMPILING) build_protobuf(protobuf_host TRUE) LIST(APPEND external_project_dependencies protobuf_host) -- GitLab From 5fc9b116d5d5c8346e2c36e5dc75046ea5456086 Mon Sep 17 00:00:00 2001 From: liaogang Date: Fri, 30 Jun 2017 12:58:02 +0800 Subject: [PATCH 0943/3256] ENH: Remove deprecated python build --- cmake/external/python.cmake | 187 ------------------------------------ 1 file changed, 187 deletions(-) diff --git a/cmake/external/python.cmake b/cmake/external/python.cmake index f4d0daab06c..6546b2c83bc 100644 --- a/cmake/external/python.cmake +++ b/cmake/external/python.cmake @@ -32,193 +32,6 @@ IF(PYTHONINTERP_FOUND) MESSAGE(FATAL_ERROR "Found Python Protobuf ${PY_GOOGLE.PROTOBUF_VERSION} < 3.0.0, " "please use pip to upgrade protobuf. pip install -U protobuf") ENDIF() -ELSE(PYTHONINTERP_FOUND) - MESSAGE(FATAL_ERROR "Please install python 2.7 before building PaddlePaddle.") - ##################################### PYTHON ######################################## - SET(PYTHON_SOURCES_DIR ${THIRD_PARTY_PATH}/python) - SET(PYTHON_INSTALL_DIR ${THIRD_PARTY_PATH}/install/python) - SET(_python_DIR ${PYTHON_INSTALL_DIR}) - - IF(UNIX) - SET(PYTHON_FOUND ON) - SET(PYTHON_INCLUDE_DIR "${PYTHON_INSTALL_DIR}/include/python2.7" CACHE PATH "Python include dir" FORCE) - SET(PYTHON_LIBRARIES "${PYTHON_INSTALL_DIR}/lib/libpython2.7.a" CACHE FILEPATH "Python library" FORCE) - SET(PYTHON_EXECUTABLE ${PYTHON_INSTALL_DIR}/bin/python CACHE FILEPATH "Python executable" FORCE) - SET(PY_SITE_PACKAGES_PATH "${PYTHON_INSTALL_DIR}/lib/python2.7/site-packages" CACHE PATH "Python site-packages path" FORCE) - ELSEIF(WIN32) - SET(PYTHON_FOUND ON) - SET(PYTHON_INCLUDE_DIR "${PYTHON_INSTALL_DIR}/include" CACHE PATH "Python include dir" FORCE) - SET(PYTHON_LIBRARIES "${PYTHON_INSTALL_DIR}/libs/python27.lib" CACHE FILEPATH "Python library" FORCE) - SET(PYTHON_EXECUTABLE "${PYTHON_INSTALL_DIR}/bin/python.exe" CACHE FILEPATH "Python executable" FORCE) - SET(PY_SITE_PACKAGES_PATH "${PYTHON_INSTALL_DIR}/Lib/site-packages" CACHE PATH "Python site-packages path" FORCE) - ELSE() - MESSAGE(FATAL_ERROR "Unknown system !") - ENDIF() - - IF(APPLE) - LIST(APPEND EXTERNAL_PROJECT_OPTIONAL_CMAKE_ARGS - -DCMAKE_BUILD_WITH_INSTALL_RPATH:BOOL=ON - ) - ENDIF() - - SET(EXTERNAL_PROJECT_OPTIONAL_CMAKE_CACHE_ARGS) - - # Force Python build to "Release". - IF(CMAKE_CONFIGURATION_TYPES) - SET(SAVED_CMAKE_CFG_INTDIR ${CMAKE_CFG_INTDIR}) - SET(CMAKE_CFG_INTDIR "Release") - ELSE() - LIST(APPEND EXTERNAL_PROJECT_OPTIONAL_CMAKE_CACHE_ARGS - -DCMAKE_BUILD_TYPE:STRING=Release - ) - ENDIF() - - ExternalProject_Add(python - ${EXTERNAL_PROJECT_LOG_ARGS} - GIT_REPOSITORY "https://github.com/python-cmake-buildsystem/python-cmake-buildsystem.git" - PREFIX ${PYTHON_SOURCES_DIR} - UPDATE_COMMAND "" - CMAKE_ARGS -DPYTHON_VERSION=2.7.12 - CMAKE_ARGS -DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER} - CMAKE_ARGS -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} - CMAKE_CACHE_ARGS - -DCMAKE_INSTALL_PREFIX:PATH=${PYTHON_INSTALL_DIR} - -DBUILD_LIBPYTHON_SHARED:BOOL=OFF - -DUSE_SYSTEM_LIBRARIES:BOOL=OFF - -DZLIB_ROOT:FILEPATH=${ZLIB_ROOT} - -DZLIB_INCLUDE_DIR:PATH=${ZLIB_INCLUDE_DIR} - -DZLIB_LIBRARY:FILEPATH=${ZLIB_LIBRARIES} - -DDOWNLOAD_SOURCES:BOOL=ON - -DINSTALL_WINDOWS_TRADITIONAL:BOOL=OFF - ${EXTERNAL_PROJECT_OPTIONAL_CMAKE_CACHE_ARGS} - ${EXTERNAL_PROJECT_OPTIONAL_CMAKE_ARGS} - DEPENDS zlib - ) - - SET(py_env - PATH=${PYTHON_INSTALL_DIR}/bin - PYTHONHOME=${PYTHON_INSTALL_DIR} - PYTHONPATH=${PYTHON_INSTALL_DIR}/lib:${PYTHON_INSTALL_DIR}/lib/python2.7:${PY_SITE_PACKAGES_PATH}) - #################################################################################### - - ##################################### SETUPTOOLS ################################### - SET(SETUPTOOLS_SOURCES_DIR ${PYTHON_SOURCES_DIR}/setuptools) - ExternalProject_Add(setuptools - ${EXTERNAL_PROJECT_LOG_ARGS} - PREFIX ${SETUPTOOLS_SOURCES_DIR} - URL "https://pypi.python.org/packages/source/s/setuptools/setuptools-18.3.2.tar.gz" - BUILD_IN_SOURCE 1 - PATCH_COMMAND "" - UPDATE_COMMAND "" - CONFIGURE_COMMAND "" - INSTALL_COMMAND "" - BUILD_COMMAND env ${py_env} ${PYTHON_EXECUTABLE} setup.py install - DEPENDS python zlib - ) - ##################################################################################### - - ##################################### SIX ########################################### - SET(SIX_SOURCES_DIR ${PYTHON_SOURCES_DIR}/six) - ExternalProject_Add(six - ${EXTERNAL_PROJECT_LOG_ARGS} - PREFIX ${SIX_SOURCES_DIR} - URL https://pypi.python.org/packages/source/s/six/six-1.10.0.tar.gz - BUILD_IN_SOURCE 1 - PATCH_COMMAND "" - UPDATE_COMMAND "" - CONFIGURE_COMMAND "" - INSTALL_COMMAND "" - BUILD_COMMAND env ${py_env} ${PYTHON_EXECUTABLE} setup.py install - DEPENDS python setuptools - ) - ##################################################################################### - - ##################################### CYTHON ######################################## - SET(CYTHON_SOURCES_DIR ${PYTHON_SOURCES_DIR}/cython) - ExternalProject_Add(cython - ${EXTERNAL_PROJECT_LOG_ARGS} - PREFIX ${CYTHON_SOURCES_DIR} - URL https://github.com/cython/cython/archive/0.25.2.tar.gz - GIT_TAG 0.25.2 - BUILD_IN_SOURCE 1 - CONFIGURE_COMMAND "" - PATCH_COMMAND "" - UPDATE_COMMAND "" - INSTALL_COMMAND "" - BUILD_COMMAND env ${py_env} ${PYTHON_EXECUTABLE} setup.py install - DEPENDS python - ) - #################################################################################### - - ##################################### NUMPY ######################################## - SET(NUMPY_SOURCES_DIR ${PYTHON_SOURCES_DIR}/numpy) - SET(NUMPY_TAG_VERSION "v1.11.3") - SET(NUMPY_VERSION "1.11.3") - - SET(EGG_NAME "") - SET(PYTHON_NUMPY_INCLUDE_DIR "") - IF(WIN32) - SET(EGG_NAME "numpy-${NUMPY_VERSION}-py2.7-${HOST_SYSTEM}.egg") - ELSE(WIN32) - IF(APPLE) - SET(EGG_NAME "numpy-${NUMPY_VERSION}-py2.7-${HOST_SYSTEM}-${MACOS_VERSION}") - ELSE(APPLE) - SET(EGG_NAME "numpy-${NUMPY_VERSION}-py2.7-linux") - SET(EGG_NAME "numpy-${NUMPY_VERSION}-py2.7-linux") - ENDIF(APPLE) - - FOREACH(suffix x86_64 intel fat64 fat32 universal) - LIST(APPEND PYTHON_NUMPY_INCLUDE_DIR ${PY_SITE_PACKAGES_PATH}/${EGG_NAME}-${suffix}.egg/numpy/core/include) - ENDFOREACH() - ENDIF(WIN32) - - ExternalProject_Add(numpy - ${EXTERNAL_PROJECT_LOG_ARGS} - GIT_REPOSITORY https://github.com/numpy/numpy.git - GIT_TAG ${NUMPY_TAG_VERSION} - CONFIGURE_COMMAND "" - UPDATE_COMMAND "" - PREFIX ${NUMPY_SOURCES_DIR} - BUILD_COMMAND env ${py_env} ${PYTHON_EXECUTABLE} setup.py build - INSTALL_COMMAND env ${py_env} ${PYTHON_EXECUTABLE} setup.py install - BUILD_IN_SOURCE 1 - DEPENDS python setuptools cython - ) - #################################################################################### - - ##################################### WHEEL ######################################## - SET(WHEEL_SOURCES_DIR ${PYTHON_SOURCES_DIR}/wheel) - ExternalProject_Add(wheel - ${EXTERNAL_PROJECT_LOG_ARGS} - URL https://pypi.python.org/packages/source/w/wheel/wheel-0.29.0.tar.gz - PREFIX ${WHEEL_SOURCES_DIR} - CONFIGURE_COMMAND "" - UPDATE_COMMAND "" - BUILD_COMMAND "" - INSTALL_COMMAND env ${py_env} ${PYTHON_EXECUTABLE} setup.py install - BUILD_IN_SOURCE 1 - DEPENDS python setuptools - ) - #################################################################################### - - ################################### PROTOBUF ####################################### - SET(PY_PROTOBUF_SOURCES_DIR ${PYTHON_SOURCES_DIR}/protobuf) - ExternalProject_Add(python-protobuf - ${EXTERNAL_PROJECT_LOG_ARGS} - URL https://pypi.python.org/packages/e0/b0/0a1b364fe8a7d177b4b7d4dca5b798500dc57a7273b93cca73931b305a6a/protobuf-3.1.0.post1.tar.gz - URL_MD5 38b5fb160c768d2f8444d0c6d637ff91 - PREFIX ${PY_PROTOBUF_SOURCES_DIR} - BUILD_IN_SOURCE 1 - PATCH_COMMAND "" - CONFIGURE_COMMAND "" - BUILD_COMMAND env ${py_env} ${PYTHON_EXECUTABLE} setup.py build - INSTALL_COMMAND env ${py_env} ${PYTHON_EXECUTABLE} setup.py install - DEPENDS python setuptools six - ) - #################################################################################### - - LIST(APPEND external_project_dependencies python setuptools six cython wheel python-protobuf numpy) - ENDIF(PYTHONINTERP_FOUND) IF(WITH_PYTHON) -- GitLab From 0c70f34c60845f08563f031ce815c1d565dfab6b Mon Sep 17 00:00:00 2001 From: dangqingqing Date: Fri, 30 Jun 2017 16:59:52 +0800 Subject: [PATCH 0944/3256] Fix bug for flowers dataset and row_conv. --- python/paddle/trainer/config_parser.py | 4 ++-- .../configs/protostr/test_row_conv.protostr | 2 +- python/paddle/v2/dataset/flowers.py | 17 +++++++++++------ 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/python/paddle/trainer/config_parser.py b/python/paddle/trainer/config_parser.py index 58e4902f57a..b7418101d83 100644 --- a/python/paddle/trainer/config_parser.py +++ b/python/paddle/trainer/config_parser.py @@ -2082,10 +2082,10 @@ class MaxOutLayer(LayerBase): class RowConvLayer(LayerBase): def __init__(self, name, inputs, context_length, **xargs): super(RowConvLayer, self).__init__( - name, 'maxout', 0, inputs=inputs, **xargs) + name, 'row_conv', 0, inputs=inputs, **xargs) config_assert( len(self.inputs) == 1, - 'TransLayer must have one and only one input') + 'row convolution layer must have one and only one input.') input_layer = self.get_input_layer(0) row_conv_conf = self.config.inputs[0].row_conv_conf row_conv_conf.context_length = context_length diff --git a/python/paddle/trainer_config_helpers/tests/configs/protostr/test_row_conv.protostr b/python/paddle/trainer_config_helpers/tests/configs/protostr/test_row_conv.protostr index 9ec15d2a19e..19c9f16574c 100644 --- a/python/paddle/trainer_config_helpers/tests/configs/protostr/test_row_conv.protostr +++ b/python/paddle/trainer_config_helpers/tests/configs/protostr/test_row_conv.protostr @@ -7,7 +7,7 @@ layers { } layers { name: "__row_conv_layer_0__" - type: "maxout" + type: "row_conv" size: 2560 active_type: "relu" inputs { diff --git a/python/paddle/v2/dataset/flowers.py b/python/paddle/v2/dataset/flowers.py index 158cfe158c4..ef92fec75f3 100644 --- a/python/paddle/v2/dataset/flowers.py +++ b/python/paddle/v2/dataset/flowers.py @@ -30,6 +30,7 @@ http://www.robots.ox.ac.uk/~vgg/publications/papers/nilsback08.{pdf,ps.gz}. """ import cPickle import itertools +import functools from common import download import tarfile import scipy.io as scio @@ -54,21 +55,25 @@ TEST_FLAG = 'trnid' VALID_FLAG = 'valid' -def default_mapper(sample): +def default_mapper(is_train, sample): ''' map image bytes data to type needed by model input layer ''' img, label = sample img = load_image_bytes(img) - img = simple_transform(img, 256, 224, True) + img = simple_transform(img, 256, 224, is_train) return img.flatten().astype('float32'), label +train_mapper = functools.partial(default_mapper, True) +test_mapper = functools.partial(default_mapper, False) + + def reader_creator(data_file, label_file, setid_file, dataset_name, - mapper=default_mapper, + mapper, buffered_size=1024, use_xmap=True): ''' @@ -118,7 +123,7 @@ def reader_creator(data_file, return map_readers(mapper, reader) -def train(mapper=default_mapper, buffered_size=1024, use_xmap=True): +def train(mapper=train_mapper, buffered_size=1024, use_xmap=True): ''' Create flowers training set reader. It returns a reader, each sample in the reader is @@ -141,7 +146,7 @@ def train(mapper=default_mapper, buffered_size=1024, use_xmap=True): buffered_size, use_xmap) -def test(mapper=default_mapper, buffered_size=1024, use_xmap=True): +def test(mapper=test_mapper, buffered_size=1024, use_xmap=True): ''' Create flowers test set reader. It returns a reader, each sample in the reader is @@ -164,7 +169,7 @@ def test(mapper=default_mapper, buffered_size=1024, use_xmap=True): buffered_size, use_xmap) -def valid(mapper=default_mapper, buffered_size=1024, use_xmap=True): +def valid(mapper=test_mapper, buffered_size=1024, use_xmap=True): ''' Create flowers validation set reader. It returns a reader, each sample in the reader is -- GitLab From 9bb33f27f8313c2515b6dbfcfe8352b4a2c3bef6 Mon Sep 17 00:00:00 2001 From: qijun Date: Fri, 30 Jun 2017 17:10:40 +0800 Subject: [PATCH 0945/3256] fix input shape of train_y --- doc/getstarted/concepts/src/train.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/getstarted/concepts/src/train.py b/doc/getstarted/concepts/src/train.py index 679d0a931a7..7e604f23de3 100644 --- a/doc/getstarted/concepts/src/train.py +++ b/doc/getstarted/concepts/src/train.py @@ -31,7 +31,7 @@ def event_handler(event): # define training dataset reader def train_reader(): train_x = np.array([[1, 1], [1, 2], [3, 4], [5, 2]]) - train_y = np.array([-2, -3, -7, -7]) + train_y = np.array([[-2], [-3], [-7], [-7]]) def reader(): for i in xrange(train_y.shape[0]): -- GitLab From 9e445eca89ae936ec82034c21b8311ccecdfc0ef Mon Sep 17 00:00:00 2001 From: liaogang Date: Fri, 30 Jun 2017 17:16:59 +0800 Subject: [PATCH 0946/3256] FIX: Replace static libs check via system warning --- cmake/generic.cmake | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/cmake/generic.cmake b/cmake/generic.cmake index 03dabe72832..88be13b2ac9 100644 --- a/cmake/generic.cmake +++ b/cmake/generic.cmake @@ -101,23 +101,16 @@ function(merge_static_libs TARGET_NAME) # First get the file names of the libraries to be merged foreach(lib ${libs}) - get_target_property(libtype ${lib} TYPE) - if(NOT libtype STREQUAL "STATIC_LIBRARY") - message(FATAL_ERROR "merge_static_libs can only process static libraries") - endif() set(libfiles ${libfiles} $) endforeach() if(APPLE) # Use OSX's libtool to merge archives - add_custom_target(${TARGET_NAME}_archive - COMMAND libtool -static -o "${CMAKE_CURRENT_BINARY_DIR}/lib${TARGET_NAME}.a" ${libfiles} - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} - DEPENDS ${libs} - ) - add_library(${TARGET_NAME} STATIC IMPORTED GLOBAL) - set_property(TARGET ${TARGET_NAME} PROPERTY - IMPORTED_LOCATION "${CMAKE_CURRENT_BINARY_DIR}/lib${TARGET_NAME}.a") - add_dependencies(${TARGET_NAME} ${TARGET_NAME}_archive) + set(dummyfile ${CMAKE_CURRENT_BINARY_DIR}/${TARGET_NAME}_dummy.c) + file(WRITE ${dummyfile} "const char * dummy = \"${dummyfile}\";") + add_library(${TARGET_NAME} STATIC ${dummyfile}) + add_custom_command(TARGET ${TARGET_NAME} POST_BUILD + COMMAND rm "${CMAKE_CURRENT_BINARY_DIR}/lib${TARGET_NAME}.a" + COMMAND /usr/bin/libtool -static -o "${CMAKE_CURRENT_BINARY_DIR}/lib${TARGET_NAME}.a" ${libfiles}) else() # general UNIX: use "ar" to extract objects and re-add to a common lib foreach(lib ${libs}) set(objlistfile ${lib}.objlist) # list of objects in the input library -- GitLab From e287034d73109a652a47b4f5132b5366f251711f Mon Sep 17 00:00:00 2001 From: dangqingqing Date: Fri, 30 Jun 2017 17:29:07 +0800 Subject: [PATCH 0947/3256] minus mean in flowers dataset. --- python/paddle/v2/dataset/flowers.py | 3 ++- python/paddle/v2/image.py | 26 ++++++++++++++++++++++---- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/python/paddle/v2/dataset/flowers.py b/python/paddle/v2/dataset/flowers.py index ef92fec75f3..fb9062fbb4f 100644 --- a/python/paddle/v2/dataset/flowers.py +++ b/python/paddle/v2/dataset/flowers.py @@ -61,7 +61,8 @@ def default_mapper(is_train, sample): ''' img, label = sample img = load_image_bytes(img) - img = simple_transform(img, 256, 224, is_train) + img = simple_transform( + img, 256, 224, is_train, mean=[103.94, 116.78, 123, 68]) return img.flatten().astype('float32'), label diff --git a/python/paddle/v2/image.py b/python/paddle/v2/image.py index 0d648e9ae69..965d965335a 100644 --- a/python/paddle/v2/image.py +++ b/python/paddle/v2/image.py @@ -262,7 +262,12 @@ def left_right_flip(im): return im[:, ::-1, :] -def simple_transform(im, resize_size, crop_size, is_train, is_color=True): +def simple_transform(im, + resize_size, + crop_size, + is_train, + is_color=True, + mean=None): """ Simply data argumentation for training. These operations include resizing, croping and flipping. @@ -288,7 +293,19 @@ def simple_transform(im, resize_size, crop_size, is_train, is_color=True): im = left_right_flip(im) else: im = center_crop(im, crop_size) - im = to_chw(im) + if len(im.shape) == 3: + im = to_chw(im) + + im = im.astype('float32') + if mean is not None: + mean = np.array(mean, dtype=np.float32) + # mean value, may be one value per channel + if mean.ndim == 1: + mean = mean[:, np.newaxis, np.newaxis] + else: + # elementwise mean + assert len(mean.shape) == len(im) + im -= mean return im @@ -297,7 +314,8 @@ def load_and_transform(filename, resize_size, crop_size, is_train, - is_color=True): + is_color=True, + mean=None): """ Load image from the input file `filename` and transform image for data argumentation. Please refer to the `simple_transform` interface @@ -318,5 +336,5 @@ def load_and_transform(filename, :type is_train: bool """ im = load_image(filename) - im = simple_transform(im, resize_size, crop_size, is_train, is_color) + im = simple_transform(im, resize_size, crop_size, is_train, is_color, mean) return im -- GitLab From 0925681543ed8d2b50a67bd6695614a17fea9006 Mon Sep 17 00:00:00 2001 From: dangqingqing Date: Fri, 30 Jun 2017 17:45:28 +0800 Subject: [PATCH 0948/3256] fix typo. --- python/paddle/v2/dataset/flowers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/paddle/v2/dataset/flowers.py b/python/paddle/v2/dataset/flowers.py index fb9062fbb4f..e2a21e6e3e0 100644 --- a/python/paddle/v2/dataset/flowers.py +++ b/python/paddle/v2/dataset/flowers.py @@ -62,7 +62,7 @@ def default_mapper(is_train, sample): img, label = sample img = load_image_bytes(img) img = simple_transform( - img, 256, 224, is_train, mean=[103.94, 116.78, 123, 68]) + img, 256, 224, is_train, mean=[103.94, 116.78, 123.68]) return img.flatten().astype('float32'), label -- GitLab From b5514602b6019a4b30515079e4be17bf4276cb19 Mon Sep 17 00:00:00 2001 From: hedaoyuan Date: Fri, 30 Jun 2017 17:46:48 +0800 Subject: [PATCH 0949/3256] Add the use_nnpack parameter in ExpandConvLayer, so that the convolution calculation can be switched to the NNPACK function. --- paddle/function/nnpack/NNPACKConvOp.cpp | 5 +- paddle/gserver/layers/ExpandConvLayer.cpp | 56 +++++++++++++++-------- 2 files changed, 40 insertions(+), 21 deletions(-) diff --git a/paddle/function/nnpack/NNPACKConvOp.cpp b/paddle/function/nnpack/NNPACKConvOp.cpp index d75fab04033..e8080c3d714 100644 --- a/paddle/function/nnpack/NNPACKConvOp.cpp +++ b/paddle/function/nnpack/NNPACKConvOp.cpp @@ -70,6 +70,9 @@ public: if (threadpool_) { pthreadpool_destroy(threadpool_); } + if (workspaceBuffer_) { + free(workspaceBuffer_); + } } virtual void check(const BufferArgs& inputs, @@ -160,7 +163,7 @@ public: CHECK_EQ(status, nnp_status_success); } - LOG(INFO) << "workspace size is " << needSize; + VLOG(3) << "workspace size is " << needSize; if (needSize > workspaceSize_) { workspaceSize_ = needSize; if (workspaceBuffer_) { diff --git a/paddle/gserver/layers/ExpandConvLayer.cpp b/paddle/gserver/layers/ExpandConvLayer.cpp index 914689e66cd..29e2113afff 100644 --- a/paddle/gserver/layers/ExpandConvLayer.cpp +++ b/paddle/gserver/layers/ExpandConvLayer.cpp @@ -16,6 +16,10 @@ limitations under the License. */ #include "paddle/utils/Logging.h" #include "paddle/utils/Stat.h" +DEFINE_bool(use_nnpack, + false, + "Whether to use nnpack for convolution calculation."); + namespace paddle { /* @@ -37,26 +41,38 @@ bool ExpandConvLayer::init(const LayerMap &layerMap, for (int i = 0; i < config_.inputs_size(); i++) { std::vector paddings = {(size_t)paddingY_[i], (size_t)padding_[i]}; std::vector strides = {(size_t)strideY_[i], (size_t)stride_[i]}; - createFunction(forward_, - !isDeconv_ ? "GemmConv" : "GemmConvGradInput", - FuncConfig() - .set("paddings", paddings) - .set("strides", strides) - .set("groups", (size_t)groups_[i])); - - createFunction(backward_, - !isDeconv_ ? "GemmConvGradInput" : "GemmConv", - FuncConfig() - .set("paddings", paddings) - .set("strides", strides) - .set("groups", (size_t)groups_[i])); - - createFunction(backward_, - "GemmConvGradFilter", - FuncConfig() - .set("paddings", paddings) - .set("strides", strides) - .set("groups", (size_t)groups_[i])); + + if (FLAGS_use_nnpack) { + CHECK_EQ(isDeconv_, false); + createFunction(forward_, + "NNPACKConv", + FuncConfig() + .set("paddings", paddings) + .set("strides", strides) + .set("groups", (size_t)groups_[i]) + .set("algo", "auto")); + } else { + createFunction(forward_, + !isDeconv_ ? "GemmConv" : "GemmConvGradInput", + FuncConfig() + .set("paddings", paddings) + .set("strides", strides) + .set("groups", (size_t)groups_[i])); + + createFunction(backward_, + !isDeconv_ ? "GemmConvGradInput" : "GemmConv", + FuncConfig() + .set("paddings", paddings) + .set("strides", strides) + .set("groups", (size_t)groups_[i])); + + createFunction(backward_, + "GemmConvGradFilter", + FuncConfig() + .set("paddings", paddings) + .set("strides", strides) + .set("groups", (size_t)groups_[i])); + } } return true; } -- GitLab From 47f1031fb7e0644ab2797343f818d32f1c45fa38 Mon Sep 17 00:00:00 2001 From: hedaoyuan Date: Fri, 30 Jun 2017 18:06:49 +0800 Subject: [PATCH 0950/3256] Modify the type of alog parameter. --- paddle/gserver/layers/ExpandConvLayer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paddle/gserver/layers/ExpandConvLayer.cpp b/paddle/gserver/layers/ExpandConvLayer.cpp index 29e2113afff..af79e65a7c0 100644 --- a/paddle/gserver/layers/ExpandConvLayer.cpp +++ b/paddle/gserver/layers/ExpandConvLayer.cpp @@ -50,7 +50,7 @@ bool ExpandConvLayer::init(const LayerMap &layerMap, .set("paddings", paddings) .set("strides", strides) .set("groups", (size_t)groups_[i]) - .set("algo", "auto")); + .set("algo", std::string("auto"))); } else { createFunction(forward_, !isDeconv_ ? "GemmConv" : "GemmConvGradInput", -- GitLab From 260427d2df5398ab3dac0ea3b8d6c54e2aa087fb Mon Sep 17 00:00:00 2001 From: dongzhihong Date: Fri, 30 Jun 2017 18:20:21 +0800 Subject: [PATCH 0951/3256] "fix copy go master lib2python" --- python/CMakeLists.txt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index a9842152c86..361e764e25b 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -13,8 +13,11 @@ set(PY_FILES paddle/__init__.py ${V2_PY_FILES}) add_custom_target(copy_paddle_master) + +SET(COPY_PADDLE_MASTER "") if(WITH_GOLANG) - add_custom_command(TARGET copy_paddle_master + SET(COPY_PADDLE_MASTER "copy_paddle_master") + add_custom_command(TARGET ${COPY_PADDLE_MASTER} COMMAND cp ${paddle_master_LIB_PATH} ${PROJ_ROOT}/python/paddle/v2/master/ ) add_dependencies(copy_paddle_master paddle_master) @@ -26,7 +29,7 @@ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/setup.py.in add_custom_command(OUTPUT ${OUTPUT_DIR}/.timestamp COMMAND env ${py_env} ${PYTHON_EXECUTABLE} setup.py bdist_wheel COMMAND ${CMAKE_COMMAND} -E touch ${OUTPUT_DIR}/.timestamp - DEPENDS gen_proto_py ${PY_FILES} ${external_project_dependencies} copy_paddle_master) + DEPENDS gen_proto_py ${PY_FILES} ${external_project_dependencies} ${COPY_PADDLE_MASTER}) add_custom_target(paddle_python ALL DEPENDS ${OUTPUT_DIR}/.timestamp) -- GitLab From f712b027a75fbfdf2456c42d66010dc3c5f100f7 Mon Sep 17 00:00:00 2001 From: dongzhihong Date: Fri, 30 Jun 2017 18:40:32 +0800 Subject: [PATCH 0952/3256] "fix protobuf depend" --- go/pserver/cclient/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go/pserver/cclient/CMakeLists.txt b/go/pserver/cclient/CMakeLists.txt index e12cf880683..7fe74c62f10 100644 --- a/go/pserver/cclient/CMakeLists.txt +++ b/go/pserver/cclient/CMakeLists.txt @@ -1,4 +1,4 @@ -cc_library(paddle_go_optimizer DEPS paddle_optimizer paddle_proto glog gflags) +cc_library(paddle_go_optimizer DEPS paddle_optimizer paddle_proto glog gflags protobuf) go_library(paddle_pserver_cclient STATIC) if(WITH_TESTING) add_subdirectory(test) -- GitLab From 62e582e8109ff08089f72e88511162fe51ae031f Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Fri, 30 Jun 2017 18:23:46 +0000 Subject: [PATCH 0953/3256] polish wording and grammar. --- doc/design/cluster_train/save_model.md | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/doc/design/cluster_train/save_model.md b/doc/design/cluster_train/save_model.md index 76ac8d83870..b70f00176b6 100644 --- a/doc/design/cluster_train/save_model.md +++ b/doc/design/cluster_train/save_model.md @@ -15,13 +15,13 @@ ways from which user can obtain a model: ### Trainer Saving Model vs. Pservers Saving Model Both trainers and pservers have access to the model. So the model can -be saved from a trainer or pservers. We need to decide on where the -model is saved from. +be saved from a trainer or pservers. We need to decide where the model +is saved from. #### Dense Update vs. Sparse Update There are two types of model update methods: dense update and sparse -update (when the parameter is configured to be sparse). +update (when the model parameter is configured to be sparse). - Dense update @@ -48,15 +48,15 @@ filesystem, making the checkpoint shards visible to the merge program. The benefit of letting one trainer to save the model is it does not require a distributed filesystem. And it's reusing the same save model -logic when the trainer is training locally - except when doing sparse -update, the trainer needs to download the entire model during the -saving process. +logic when training locally - except when doing sparse update, the +trainer needs to download the entire model during the saving process. #### Conclusion Given trainer saving model does not require a distributed filesystem, -and is an intuitive extension to training locally, we decide to let -the trainer save the model. +and is an intuitive extension to trainer saving model when training +locally, we decide to let the trainer save the model when doing +distributed training. ### Convert Model from Checkpoint @@ -84,16 +84,16 @@ save the model. Each trainer will be given the directory to save the model. The elected trainer will save the model to -`given-directory/trainerID`. Since the tainerID is unique, this would -prevent concurrent save to the same file when multiple trainers are -elected to save the model when split-brain problem happens. +`given-directory/trainerID`. Since the trainer ID is unique, this +would prevent concurrent save to the same file when multiple trainers +are elected to save the model when split-brain problem happens. ### What Happens When Model Is Saving It takes some time to save model, we need to define what will happen when save model is taking place. -When saving a dense model, the trainer uses the local model. Pservers +When doing dense update, the trainer uses the local model. Pservers does not need to pause model update. When doing sparse update. The trainer needs to download the entire @@ -103,7 +103,7 @@ download finishes. Otherwise, the trainer gets a model that is "polluted": some part of the model is old, some part of the model is new. -It's unclear that the "polluted" model will be inferiod due to the +It's unclear that the "polluted" model will be inferior due to the stochastic nature of deep learning, and pausing the model update will add more complexity to the system. Since supporting sparse update is a TODO item. We defer the evaluation of pause the model update or not -- GitLab From a7f9625391f95bbd44b13a946d595b88e1464d42 Mon Sep 17 00:00:00 2001 From: "yi.wu" Date: Sat, 1 Jul 2017 18:38:19 +0800 Subject: [PATCH 0954/3256] update --- paddle/scripts/docker/build.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/paddle/scripts/docker/build.sh b/paddle/scripts/docker/build.sh index 54e80fee34d..a182e5f4aef 100644 --- a/paddle/scripts/docker/build.sh +++ b/paddle/scripts/docker/build.sh @@ -50,7 +50,6 @@ cmake .. \ -DWITH_STYLE_CHECK=${WITH_STYLE_CHECK:-OFF} \ -DWITH_TESTING=${WITH_TESTING:-OFF} \ -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -exit 1 cat < Date: Sun, 2 Jul 2017 16:14:18 +0800 Subject: [PATCH 0955/3256] "polish cgo link option" --- go/pserver/optimizer.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/go/pserver/optimizer.go b/go/pserver/optimizer.go index 46e614d3a16..070896f7c76 100644 --- a/go/pserver/optimizer.go +++ b/go/pserver/optimizer.go @@ -1,6 +1,5 @@ package pserver -// #cgo pkg-config: protobuf // #cgo CFLAGS: -I ../../ // //FIXME: ldflags contain "build" path // #cgo LDFLAGS: ../../build/go/pserver/cclient/libpaddle_go_optimizer.a -lstdc++ @@ -50,7 +49,7 @@ func newOptimizer(paramWithConfigs ParameterWithConfig) *optimizer { cbuffer = C.malloc(C.size_t(len(p.Content))) C.memcpy(cbuffer, unsafe.Pointer(&p.Content[0]), C.size_t(len(p.Content))) o.opt = C.paddle_create_optimizer((*C.uchar)(&c[0]), C.int(len(c)), - C.paddle_element_type(p.ElementType), cbuffer, C.int(len(p.Content)), + C.paddle_element_type(p.ElementType), cbuffer, C.int(len(p.Content)*C.sizeof_float), (*C.char)(nullPtr), 0) return o } -- GitLab From e2c842353739ce3577b0f79267976928a44deb55 Mon Sep 17 00:00:00 2001 From: fengjiayi Date: Sun, 2 Jul 2017 17:06:28 +0800 Subject: [PATCH 0956/3256] add simple_op_design.md --- doc/design/simple_op_design.md | 273 +++++++++++++++++++++++++++++++++ 1 file changed, 273 insertions(+) create mode 100644 doc/design/simple_op_design.md diff --git a/doc/design/simple_op_design.md b/doc/design/simple_op_design.md new file mode 100644 index 00000000000..93c0f68ca9b --- /dev/null +++ b/doc/design/simple_op_design.md @@ -0,0 +1,273 @@ +## Interaction between C++ and Python + +Users employ API in Python to describe their own network, however, the network construction actually happens in C++. so Protobuf is introduced to send the message between Python and C++. + +The Interaction between Python and C++ can be simplified as two steps: + +1. C++ tells Python how many Ops there are, and what parameter do users need to offer to initialize a new Op. Python then builds API for each Op at compile time. + +2. Users invoke APIs built by Python and provide necessary parameters. These parameters will be sent to C++ fo finish Op construction task. + +### Message form C++ to Python + +We define a Protobuf message class `OpProto` to hold message needed in the first step. What should an `OpProto` contain? This question is equivalent to “What message do we need to offer, to build a Python API which is legal and user oriented and can use to describe a whole Op.” + +Following message are necessary: + +1. Op's name, and its simple comment. +2. Input and output variable number; each variable's name, type, and comment. +3. Op's attributes; each attribute includes name, type, comment, **default value** and **value range**. + +So `OpProto` can be defined as follows: + +```proto +enum AttrType { + INT = 1; + FLOAT = 2; + STRING = 3; + INTS = 4; + FLOATS = 5; + STRINGS = 6; +}; + +message AttrValue { + AttrType type = 1; + optional int iv = 2; + optional float fv = 3; + optional string sv = 4; + repeated int ivs = 5; + repeated float fvs = 6; + repeated string svs = 7; +}; + +message AttrProto { + required string name = 1; + required string comment = 2; + optional AttrValue default = 3; + optional AttrValue max = 4; + optional AttrValue min = 5; + required AttrType type = 6; +}; + +message VarProto { + required string name = 1; + required string comment = 2; +}; + +message OpProto { + repeated VarProto inputs = 1; + repeated VarProto outputs = 2; + repeated AttrProto attrs = 3; + required string type = 4; + required string comment = 5; +}; +``` + +The default value and value range didn't appear in out previous design. By adding these two fields, we are able to check attribute validity in Python and find out possible error as soon as possible. What's more, by providing the message about default value and value range to Python docstring, it helps to automatically generate more comprehensive documents. + +### Message from Python to C++ + +To hold message needed in the above second step, we define Protobuf message class `OpDesc`. It is used to hold user-specified parameters in Op describing. + +```proto +message OpDesc { + required string type = 1; + repeated string inputs = 2; + repeated string outputs = 3; + map attrs = 4; +}; +``` + +## OpProto Register + +Every Op has its own `OpProto`. For using convenience, we need to register them and record all their messages. For each `Op` class, we define a corresponding `OpMaker` class, in whose constructor we implement the `OpProto`'s building process. `OpMaker`'s constructor will be invoked by another function `OpRegistry::RegisterOp()`. + +```cpp +class OpProtoMaker { +public: + OpProtoMaker(OpProto* proto): proto_(proto) {} +protected: + OpProto* proto_; + void AddInput(const std::string& name, const std::string& desc) {...} + void AddAttr(const std::string& name, const std::string& desc, TypeId type) {...} + void AddComment(const std::string& comment) { ... } +}; + +class OpRegistry { +public: + using OpCreator = std::function; + + template + static void RegisterOp(const std::string& name) { + gCreators_[name] = [](const OpDesc& desc) { + return new OpType(desc); + }; + OpProto& opProto = gProtos_[name]; + OpMaker()(&opProto); + } + + static map gCreators_; + static map gProtos_; +}; + +template +class OpRegister { + public: + OpRegister(std::string type) { + OpRegistry::RegisterOp(type); + } +}; + +#define REGISTER_OP(op_class, op_maker_class, type_name) \ + class op_class##Register { \ + private: \ + const static OpRegister<#op_class, #op_maker_class> reg; \ + }; \ + const Register op_class##Register::reg(#type_name); + +class CosineOp { +// ... +} + +struct CosineOpProtoMaker : public OpProtoMaker { + CosineOpProtoMaker(OpProto* proto) : OpProtoMaker(proto) { + AddInput("input", "input of cosine op"); + AddAttr("scale", "scale of cosine op", float).Default(1.0).LargerThan(0.0); + AddType("cos"); + AddComment("This is cos op"); + } +} + +REGISTER_OP(CosineOp, CosineOpProtoMaker, cos); +``` + +In `REGISTER_OP(CosineOp, CosineOpProtoMaker, cos)`, we register not only `CosineOp` but also `CosineOpProto`. As fields of `CosineOpProto`, the default value and value range of `scale` are also registered here. + +## Python API + +Python APIs are divided into two types, high-level API and low-level API. + +### High-Level API + +High-level API is called by users directly, so it should keep its style consistent with existing V2 APIs. + +Here is a sample about how a define a fc layer: + +```python +hd = fc_layer(input=data, size=56, with_bias=True, activation="sigmoid"); +``` + +`hd` is the output of `fc_layer` and it's a `variable`. It can be further sent into other layers as input. + +The definition of `fc_layer()`: + +```python +def fc_layer(input, size, with_bias, activation): + attr_map = {"size":size} + check_attrs(attr_map) + w = make_variable('w') + if with_bias: + b = make_variable('b') + else: + b = None + fc_output = make_variable('fc_output'); + fc_op(input, w, b, fc_output, attr_map) + act_output = make_variable('sigmod_output'); + if activation == "sigmod": + sigmod_op(fc_output, act_output); + elif: + # ... + return act_output; +``` + +### Low Leval API + +In above sample, `fc_op` and `sigmod_op` are low-level API. They build `OpDesc` and invoke corresponding C++ code. + +*TODO* + +## Op and Kernal + +After completely defined, an Op will be run in a network. However, Op's computing method may differ on different devices. One solution is that write an `Op`'s member function `Op::run()`, which contains computing methods of all possible devices. That may be a bad idea because we have to change all `Op`'s code to add a new device. + +Another choice is adding a concept named `kernal`. A `Kernal` describes an op's computing process on a certain device. After stripping `Variable` and `kernal`, `Op` becomes a pure conceptual class, which holds neither data nor detailed computing process. + +```cpp +class KernalBase { +public: + virtual void RunOnDevice(std::vector input_vars, + std::vector input_vars, + const OpAttrs* attrs) = 0; +}; + +template +class CosineKernal : public KernalBase { +public: + virtual void RunOnDevice(std::vector input_vars, + std::vector input_vars, + const OpAttrs* attrs) { + // no implementation + } +}; + +template <> +class CosineKernal : public KernalBase { +public: + virtual void RunOnDevice(std::vector input_vars, + std::vector input_vars, + const OpAttrs* attrs) { + CosineOpAttrs* cosine_attrs = static_cast(attrs); + // computing code + // ... + } +}; + +struct OpAttrs {...}; + +class Op { + public: + std::string get_kernal_name() { + return kernel_name_; + } + const vector& get_input_names() { + return input_names_; + } + const vector& get_output_names() { + return output_names_; + } + // ... + private: + std::vector input_names_; + std::vector output_names_; + std::string kernal_name_; + +} + +struct CosineOpAttrs : public OpAttrs { + float scale_; +} + +class CosineOp : public Op { + public: + const CosineOpAtrrs* get_attrs() { + return &attrs; + } + + private: + CosineOpAttrs attrs; +} + +RunOp(const Op& op, Scope scope) { + Kernal* kernal = get_kernal(scope, op.get_kernal_name()); + std::vector input_vars = + get_variables(scope, op.get_input_name()); + std::vector output_vars = + get_variables(scope, op.get_output_name()); + + kernal->RunOnDevice(input_vars, output_vars, op.get_attrs()); +} +``` + +All `Kernal` need to be registered beforehand, just like `Op`. + +Now, `Op` is no longer has `Run()` function. It only contains names of variables and kernels. During network running, `RunOp()` is called to invoke `Op`'s corresponding `Kernal`. `get_kernal()` is supposed to return `kernal` for current device. -- GitLab From f35c8c42604bd06dbb964a4c26e9ec9d4a2cb94d Mon Sep 17 00:00:00 2001 From: fengjiayi Date: Sun, 2 Jul 2017 17:10:05 +0800 Subject: [PATCH 0957/3256] remove simple_op_design.md --- doc/design/simple_op_design.md | 273 --------------------------------- 1 file changed, 273 deletions(-) delete mode 100644 doc/design/simple_op_design.md diff --git a/doc/design/simple_op_design.md b/doc/design/simple_op_design.md deleted file mode 100644 index 93c0f68ca9b..00000000000 --- a/doc/design/simple_op_design.md +++ /dev/null @@ -1,273 +0,0 @@ -## Interaction between C++ and Python - -Users employ API in Python to describe their own network, however, the network construction actually happens in C++. so Protobuf is introduced to send the message between Python and C++. - -The Interaction between Python and C++ can be simplified as two steps: - -1. C++ tells Python how many Ops there are, and what parameter do users need to offer to initialize a new Op. Python then builds API for each Op at compile time. - -2. Users invoke APIs built by Python and provide necessary parameters. These parameters will be sent to C++ fo finish Op construction task. - -### Message form C++ to Python - -We define a Protobuf message class `OpProto` to hold message needed in the first step. What should an `OpProto` contain? This question is equivalent to “What message do we need to offer, to build a Python API which is legal and user oriented and can use to describe a whole Op.” - -Following message are necessary: - -1. Op's name, and its simple comment. -2. Input and output variable number; each variable's name, type, and comment. -3. Op's attributes; each attribute includes name, type, comment, **default value** and **value range**. - -So `OpProto` can be defined as follows: - -```proto -enum AttrType { - INT = 1; - FLOAT = 2; - STRING = 3; - INTS = 4; - FLOATS = 5; - STRINGS = 6; -}; - -message AttrValue { - AttrType type = 1; - optional int iv = 2; - optional float fv = 3; - optional string sv = 4; - repeated int ivs = 5; - repeated float fvs = 6; - repeated string svs = 7; -}; - -message AttrProto { - required string name = 1; - required string comment = 2; - optional AttrValue default = 3; - optional AttrValue max = 4; - optional AttrValue min = 5; - required AttrType type = 6; -}; - -message VarProto { - required string name = 1; - required string comment = 2; -}; - -message OpProto { - repeated VarProto inputs = 1; - repeated VarProto outputs = 2; - repeated AttrProto attrs = 3; - required string type = 4; - required string comment = 5; -}; -``` - -The default value and value range didn't appear in out previous design. By adding these two fields, we are able to check attribute validity in Python and find out possible error as soon as possible. What's more, by providing the message about default value and value range to Python docstring, it helps to automatically generate more comprehensive documents. - -### Message from Python to C++ - -To hold message needed in the above second step, we define Protobuf message class `OpDesc`. It is used to hold user-specified parameters in Op describing. - -```proto -message OpDesc { - required string type = 1; - repeated string inputs = 2; - repeated string outputs = 3; - map attrs = 4; -}; -``` - -## OpProto Register - -Every Op has its own `OpProto`. For using convenience, we need to register them and record all their messages. For each `Op` class, we define a corresponding `OpMaker` class, in whose constructor we implement the `OpProto`'s building process. `OpMaker`'s constructor will be invoked by another function `OpRegistry::RegisterOp()`. - -```cpp -class OpProtoMaker { -public: - OpProtoMaker(OpProto* proto): proto_(proto) {} -protected: - OpProto* proto_; - void AddInput(const std::string& name, const std::string& desc) {...} - void AddAttr(const std::string& name, const std::string& desc, TypeId type) {...} - void AddComment(const std::string& comment) { ... } -}; - -class OpRegistry { -public: - using OpCreator = std::function; - - template - static void RegisterOp(const std::string& name) { - gCreators_[name] = [](const OpDesc& desc) { - return new OpType(desc); - }; - OpProto& opProto = gProtos_[name]; - OpMaker()(&opProto); - } - - static map gCreators_; - static map gProtos_; -}; - -template -class OpRegister { - public: - OpRegister(std::string type) { - OpRegistry::RegisterOp(type); - } -}; - -#define REGISTER_OP(op_class, op_maker_class, type_name) \ - class op_class##Register { \ - private: \ - const static OpRegister<#op_class, #op_maker_class> reg; \ - }; \ - const Register op_class##Register::reg(#type_name); - -class CosineOp { -// ... -} - -struct CosineOpProtoMaker : public OpProtoMaker { - CosineOpProtoMaker(OpProto* proto) : OpProtoMaker(proto) { - AddInput("input", "input of cosine op"); - AddAttr("scale", "scale of cosine op", float).Default(1.0).LargerThan(0.0); - AddType("cos"); - AddComment("This is cos op"); - } -} - -REGISTER_OP(CosineOp, CosineOpProtoMaker, cos); -``` - -In `REGISTER_OP(CosineOp, CosineOpProtoMaker, cos)`, we register not only `CosineOp` but also `CosineOpProto`. As fields of `CosineOpProto`, the default value and value range of `scale` are also registered here. - -## Python API - -Python APIs are divided into two types, high-level API and low-level API. - -### High-Level API - -High-level API is called by users directly, so it should keep its style consistent with existing V2 APIs. - -Here is a sample about how a define a fc layer: - -```python -hd = fc_layer(input=data, size=56, with_bias=True, activation="sigmoid"); -``` - -`hd` is the output of `fc_layer` and it's a `variable`. It can be further sent into other layers as input. - -The definition of `fc_layer()`: - -```python -def fc_layer(input, size, with_bias, activation): - attr_map = {"size":size} - check_attrs(attr_map) - w = make_variable('w') - if with_bias: - b = make_variable('b') - else: - b = None - fc_output = make_variable('fc_output'); - fc_op(input, w, b, fc_output, attr_map) - act_output = make_variable('sigmod_output'); - if activation == "sigmod": - sigmod_op(fc_output, act_output); - elif: - # ... - return act_output; -``` - -### Low Leval API - -In above sample, `fc_op` and `sigmod_op` are low-level API. They build `OpDesc` and invoke corresponding C++ code. - -*TODO* - -## Op and Kernal - -After completely defined, an Op will be run in a network. However, Op's computing method may differ on different devices. One solution is that write an `Op`'s member function `Op::run()`, which contains computing methods of all possible devices. That may be a bad idea because we have to change all `Op`'s code to add a new device. - -Another choice is adding a concept named `kernal`. A `Kernal` describes an op's computing process on a certain device. After stripping `Variable` and `kernal`, `Op` becomes a pure conceptual class, which holds neither data nor detailed computing process. - -```cpp -class KernalBase { -public: - virtual void RunOnDevice(std::vector input_vars, - std::vector input_vars, - const OpAttrs* attrs) = 0; -}; - -template -class CosineKernal : public KernalBase { -public: - virtual void RunOnDevice(std::vector input_vars, - std::vector input_vars, - const OpAttrs* attrs) { - // no implementation - } -}; - -template <> -class CosineKernal : public KernalBase { -public: - virtual void RunOnDevice(std::vector input_vars, - std::vector input_vars, - const OpAttrs* attrs) { - CosineOpAttrs* cosine_attrs = static_cast(attrs); - // computing code - // ... - } -}; - -struct OpAttrs {...}; - -class Op { - public: - std::string get_kernal_name() { - return kernel_name_; - } - const vector& get_input_names() { - return input_names_; - } - const vector& get_output_names() { - return output_names_; - } - // ... - private: - std::vector input_names_; - std::vector output_names_; - std::string kernal_name_; - -} - -struct CosineOpAttrs : public OpAttrs { - float scale_; -} - -class CosineOp : public Op { - public: - const CosineOpAtrrs* get_attrs() { - return &attrs; - } - - private: - CosineOpAttrs attrs; -} - -RunOp(const Op& op, Scope scope) { - Kernal* kernal = get_kernal(scope, op.get_kernal_name()); - std::vector input_vars = - get_variables(scope, op.get_input_name()); - std::vector output_vars = - get_variables(scope, op.get_output_name()); - - kernal->RunOnDevice(input_vars, output_vars, op.get_attrs()); -} -``` - -All `Kernal` need to be registered beforehand, just like `Op`. - -Now, `Op` is no longer has `Run()` function. It only contains names of variables and kernels. During network running, `RunOp()` is called to invoke `Op`'s corresponding `Kernal`. `get_kernal()` is supposed to return `kernal` for current device. -- GitLab From cee264f3f9e389ed24bbf504d9ab82ebd6354512 Mon Sep 17 00:00:00 2001 From: wuyi05 Date: Mon, 3 Jul 2017 09:51:03 +0800 Subject: [PATCH 0958/3256] update for comments --- Dockerfile | 7 ++++--- cmake/generic.cmake | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index bf227737c5a..d10fad6370a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -34,11 +34,12 @@ RUN apt-get update && \ net-tools && \ apt-get clean -y -# Install Go +# Install Go and glide RUN wget -O go.tgz https://storage.googleapis.com/golang/go1.8.1.linux-amd64.tar.gz && \ tar -C /usr/local -xzf go.tgz && \ mkdir /root/gopath && \ - rm go.tgz + rm go.tgz \ + curl https://glide.sh/get | sh ENV GOROOT=/usr/local/go GOPATH=/root/gopath # should not be in the same line with GOROOT definition, otherwise docker build could not find GOROOT. ENV PATH=${PATH}:${GOROOT}/bin @@ -57,7 +58,7 @@ RUN pip install --upgrade pip && \ pip install -U docopt PyYAML sphinx && \ pip install -U sphinx-rtd-theme==0.1.9 recommonmark && \ pip install pre-commit 'requests==2.9.2' 'ipython==5.3.0' && \ - pip install 'ipykernel==4.6.0' 'jupyter==1.0.0' && \ + pip install 'ipykernel==4.6.0' 'jupyter==1.0.0' && \ pip install rarfile # To fix https://github.com/PaddlePaddle/Paddle/issues/1954, we use diff --git a/cmake/generic.cmake b/cmake/generic.cmake index 92e14f2581a..ca358da8f14 100644 --- a/cmake/generic.cmake +++ b/cmake/generic.cmake @@ -302,7 +302,7 @@ function(go_binary TARGET_NAME) -o "${CMAKE_CURRENT_BINARY_DIR}/${TARGET_NAME}" "./${CMAKE_CURRENT_SOURCE_REL_DIR}/${go_binary_SRCS}" WORKING_DIRECTORY "${PADDLE_IN_GOPATH}/go") - # add_custom_target(${TARGET_NAME} ALL DEPENDS go_vendor ${TARGET_NAME}_link ${TARGET_NAME}_timestamp ${go_binary_DEPS}) + # TODO: don't know what ${TARGET_NAME}_link does add_custom_target(${TARGET_NAME} ALL DEPENDS go_vendor ${TARGET_NAME}_timestamp ${go_binary_DEPS}) install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/${TARGET_NAME} DESTINATION bin) endfunction(go_binary) -- GitLab From 6f7a9dd5c0d6280c663909add5fe2ff4c0f28c71 Mon Sep 17 00:00:00 2001 From: wuyi05 Date: Mon, 3 Jul 2017 09:57:03 +0800 Subject: [PATCH 0959/3256] remove unnessesary comments --- go/CMakeLists.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/go/CMakeLists.txt b/go/CMakeLists.txt index 9774a89e427..014697d1555 100644 --- a/go/CMakeLists.txt +++ b/go/CMakeLists.txt @@ -12,10 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # -# FIXME(typhoonzero): Download glide into cmake build temprary GOPATH add_subdirectory(pserver/cclient) add_subdirectory(cmd/pserver) add_subdirectory(cmd/master) add_subdirectory(master/c) -#TODO (add go/master/c back when fixed) -- GitLab From 26e661bc51e2fac36c3692d748b7db8a950cb370 Mon Sep 17 00:00:00 2001 From: gongweibao Date: Mon, 3 Jul 2017 03:05:36 +0000 Subject: [PATCH 0960/3256] fix by helin's comments --- go/master/c/client.go | 4 ++-- python/paddle/v2/master/client.py | 4 ++-- python/paddle/v2/reader/creator.py | 34 ++++++++++++++++++------------ 3 files changed, 25 insertions(+), 17 deletions(-) diff --git a/go/master/c/client.go b/go/master/c/client.go index 635688f196b..31f43119745 100644 --- a/go/master/c/client.go +++ b/go/master/c/client.go @@ -106,7 +106,7 @@ func paddle_set_dataset(client C.paddle_master_client, path **C.char, size C.int // return value: // 0:ok -// -2:error +// -1:error //export paddle_next_record func paddle_next_record(client C.paddle_master_client, record **C.uchar) C.int { c := get(client) @@ -115,7 +115,7 @@ func paddle_next_record(client C.paddle_master_client, record **C.uchar) C.int { // Error // TODO: return the type of error? *record = (*C.uchar)(nullPtr) - return -2 + return -1 } if len(r) == 0 { diff --git a/python/paddle/v2/master/client.py b/python/paddle/v2/master/client.py index 6ddb09e4e89..70f9e43c968 100644 --- a/python/paddle/v2/master/client.py +++ b/python/paddle/v2/master/client.py @@ -28,12 +28,12 @@ class client(object): # return format: (record, errno) # errno = 0: ok - # < -1: error + # < 0: error def next_record(self): p = ctypes.c_char_p() ret = ctypes.pointer(p) size = lib.paddle_next_record(self.c, ret) - if size < -1: + if size < 0: # Error return None, size diff --git a/python/paddle/v2/reader/creator.py b/python/paddle/v2/reader/creator.py index 2e8626e565c..20624d5286b 100644 --- a/python/paddle/v2/reader/creator.py +++ b/python/paddle/v2/reader/creator.py @@ -57,29 +57,31 @@ def text_file(path): return reader -def recordio_local(paths): +def recordio_local(paths, buf_size=100): """ - Creates a data reader that outputs record one one by one - from given local recordio fils path. + Creates a data reader from given RecordIO file paths separated by ",", + glob pattern is supported. :path: path of recordio files. :returns: data reader of recordio files. """ import recordio as rec + import paddle.v2.reader.decorator as dec def reader(): - for i, path in enumerate(paths): - f = rec.reader(path) - while True: - r = f.read() - if r is None: - break - yield r - f.close() + a = ','.join(paths) + f = rec.reader(a) + while True: + r = f.read() + if r is None: + break + yield r + f.close() + + return dec.buffered(reader, buf_size) - return reader -def recordio(paths, addr="", buf_size=100): +def recordio(paths, buf_size=100): """ Creates a data reader that outputs record one one by one from given local or cloud recordio path. @@ -92,6 +94,12 @@ def recordio(paths, addr="", buf_size=100): if "KUBERNETES_SERVICE_HOST" not in os.environ.keys(): return recordio_local(paths) + host_name = "MASTER_SERVICE_HOST" + if host_name not in os.environ.keys(): + raise Exception('not find ' + host_name + ' in environ.') + + addr = os.environ(host) + def reader(): c = cloud(addr, buf_size) c.set_dataset(paths) -- GitLab From 275e5b7d42903ea3c9bf4e4fed3f9eab45c727bf Mon Sep 17 00:00:00 2001 From: liaogang Date: Mon, 3 Jul 2017 11:12:18 +0800 Subject: [PATCH 0961/3256] FIX: yapf format version --- python/paddle/trainer_config_helpers/networks.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/paddle/trainer_config_helpers/networks.py b/python/paddle/trainer_config_helpers/networks.py index f0b6625dc37..b77932ce5f0 100755 --- a/python/paddle/trainer_config_helpers/networks.py +++ b/python/paddle/trainer_config_helpers/networks.py @@ -1395,7 +1395,7 @@ def inputs(layers, *args): if len(args) != 0: layers.extend(args) - Inputs(*[l.name for l in layers]) + Inputs(* [l.name for l in layers]) def outputs(layers, *args): @@ -1438,7 +1438,7 @@ def outputs(layers, *args): assert len(layers) > 0 if HasInputsSet(): # input already set - Outputs(*[l.name for l in layers]) + Outputs(* [l.name for l in layers]) return # just return outputs. if len(layers) != 1: -- GitLab From b9c15b6fffa55b6c42e6f06cc3eba3ccceea073c Mon Sep 17 00:00:00 2001 From: dongzhihong Date: Mon, 3 Jul 2017 12:23:26 +0800 Subject: [PATCH 0962/3256] "remove unused tests" --- go/pserver/optimizer.go | 6 +++--- go/pserver/optimizer_test.go | 24 ------------------------ go/pserver/service_test.go | 3 +-- 3 files changed, 4 insertions(+), 29 deletions(-) diff --git a/go/pserver/optimizer.go b/go/pserver/optimizer.go index 070896f7c76..b4a040f46bf 100644 --- a/go/pserver/optimizer.go +++ b/go/pserver/optimizer.go @@ -49,7 +49,7 @@ func newOptimizer(paramWithConfigs ParameterWithConfig) *optimizer { cbuffer = C.malloc(C.size_t(len(p.Content))) C.memcpy(cbuffer, unsafe.Pointer(&p.Content[0]), C.size_t(len(p.Content))) o.opt = C.paddle_create_optimizer((*C.uchar)(&c[0]), C.int(len(c)), - C.paddle_element_type(p.ElementType), cbuffer, C.int(len(p.Content)*C.sizeof_float), + C.paddle_element_type(p.ElementType), cbuffer, C.int(len(p.Content)/C.sizeof_float), (*C.char)(nullPtr), 0) return o } @@ -57,7 +57,7 @@ func newOptimizer(paramWithConfigs ParameterWithConfig) *optimizer { func (o *optimizer) GetWeights() []byte { var buffer unsafe.Pointer buffer_len := C.paddle_optimizer_get_weights(o.opt, &buffer) - return cArrayToSlice(buffer, int(buffer_len)) + return cArrayToSlice(buffer, int(buffer_len)*C.sizeof_float) } func (o *optimizer) UpdateParameter(g Gradient) error { @@ -65,7 +65,7 @@ func (o *optimizer) UpdateParameter(g Gradient) error { return fmt.Errorf("Name: %s, parameter and gradient element type not match, parameter: %v, gradient: %v", g.Name, o.elementType, g.ElementType) } - r := C.paddle_update_parameter(o.opt, C.paddle_element_type(g.ElementType), unsafe.Pointer(&g.Content[0]), C.int(len(g.Content))) + r := C.paddle_update_parameter(o.opt, C.paddle_element_type(g.ElementType), unsafe.Pointer(&g.Content[0]), C.int(len(g.Content))/C.sizeof_float) if r != 0 { return fmt.Errorf("optimizer update returned error code: %d", r) } diff --git a/go/pserver/optimizer_test.go b/go/pserver/optimizer_test.go index 49d9df5898d..368047d6f89 100644 --- a/go/pserver/optimizer_test.go +++ b/go/pserver/optimizer_test.go @@ -2,7 +2,6 @@ package pserver import ( "io/ioutil" - "reflect" "testing" ) @@ -23,26 +22,3 @@ func TestOptimizerCreateRelease(t *testing.T) { o := newOptimizer(param) o.Cleanup() } - -func TestOptimizerFull(t *testing.T) { - p := Parameter{ - Name: "a", - ElementType: Float32, - } - p.Content = []byte{1, 3} - config, err := ioutil.ReadFile("./cclient/test/testdata/optimizer.pb.txt") - if err != nil { - t.Fatalf("read optimizer proto failed") - } - param := ParameterWithConfig{ - Param: p, - Config: config, - } - o := newOptimizer(param) - g := Gradient(p) - if !reflect.DeepEqual(p.Content, o.GetWeights()) { - t.FailNow() - } - o.UpdateParameter(g) - o.Cleanup() -} diff --git a/go/pserver/service_test.go b/go/pserver/service_test.go index c62f92e09bb..f86619447c2 100644 --- a/go/pserver/service_test.go +++ b/go/pserver/service_test.go @@ -10,8 +10,7 @@ import ( "github.com/PaddlePaddle/Paddle/go/pserver" ) - -func TestFull(t *testing.T) { +func TestServiceFull(t *testing.T) { s, err := pserver.NewService(0) if err != nil { t.Error(err) -- GitLab From 722853312118c6ed1c0624baede84a2c8c2379a7 Mon Sep 17 00:00:00 2001 From: dongzhihong Date: Mon, 3 Jul 2017 12:49:08 +0800 Subject: [PATCH 0963/3256] test --- go/pserver/cclient/test/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go/pserver/cclient/test/CMakeLists.txt b/go/pserver/cclient/test/CMakeLists.txt index bd50f1db2a1..f287f850719 100644 --- a/go/pserver/cclient/test/CMakeLists.txt +++ b/go/pserver/cclient/test/CMakeLists.txt @@ -1,2 +1,2 @@ cc_test(test_cclient SRCS test_cclient.c DEPS paddle_pserver_cclient) - +add_style_check_target(test_cclient test_cclient.c) -- GitLab From 7dc53ea0ed08b04abf047c2827e339a766bbb983 Mon Sep 17 00:00:00 2001 From: fengjiayi Date: Mon, 3 Jul 2017 14:22:12 +0800 Subject: [PATCH 0964/3256] renew simple_op_design.md --- doc/design/simple_op_design.md | 202 +++++++++++++++++++++++++++++++++ 1 file changed, 202 insertions(+) create mode 100644 doc/design/simple_op_design.md diff --git a/doc/design/simple_op_design.md b/doc/design/simple_op_design.md new file mode 100644 index 00000000000..2c1c7f6f140 --- /dev/null +++ b/doc/design/simple_op_design.md @@ -0,0 +1,202 @@ +## Interaction between C++ and Python + +Users employ API in Python to describe their own network, however, the network construction actually happens in C++. so Protobuf is introduced to send the message between Python and C++. + +The Interaction between Python and C++ can be simplified as two steps: + +1. C++ tells Python how many Ops there are, and what parameter do users need to offer to initialize a new Op. Python then builds API for each Op at compile time. + +2. Users invoke APIs built by Python and provide necessary parameters. These parameters will be sent to C++ fo finish Op construction task. + +### Message form C++ to Python + +We define a Protobuf message class `OpProto` to hold message needed in the first step. What should an `OpProto` contain? This question is equivalent to “What message do we need to offer, to build a Python API which is legal and user oriented and can use to describe a whole Op.” + +Following message are necessary: + +1. Op's name, and its simple comment. +2. Input and output variable number; each variable's name, type, and comment. +3. Op's attributes; each attribute includes name, type, comment, **default value** and **value range**. + +So `OpProto` can be defined as follows: + +```proto +enum AttrType { + INT = 1; + FLOAT = 2; + STRING = 3; + INTS = 4; + FLOATS = 5; + STRINGS = 6; +}; + +message AttrValue { + AttrType type = 1; + optional int iv = 2; + optional float fv = 3; + optional string sv = 4; + repeated int ivs = 5; + repeated float fvs = 6; + repeated string svs = 7; +}; + +message AttrProto { + required string name = 1; + required string comment = 2; + required AttrType type = 3; +}; + +message VarProto { + required string name = 1; + required string comment = 2; + required bool is_tensor = 3; +}; + +message OpProto { + repeated VarProto inputs = 1; + repeated VarProto outputs = 2; + repeated AttrProto attrs = 3; + required string type = 4; + required string comment = 5; +}; +``` + +To generate Python code automatically: + +```python +def create_python_ops_creatation_functions(): + op_protos = paddle.framework.OpRegistry.get_all_op_proto() + for type_name in op_protos: + op_proto = op_protos[type_name] + def __impl__(**kwargs): # User must use key word args in Paddle API + inputs = [kwargs.get(ipt.name, "") for ipt in op_proto.inputs] + outputs = [kwargs.get(opt.name, "") for opt in op_proto.outputs] + attrs = [cast_to_op_attr(attr, kwargs.get(attr.name, None)) for attr in op_proto.attrs] + opdesc = (input, outputs, type_name, attrs) + return paddle.framework.OpRegistry.CreateOp(opdesc) + __impl__.__doc__ = create_doc_string(op_proto) + globals()[type_name] = __impl__ + +create_python_ops_creatation_functions() +``` + +### Message from Python to C++ + +To hold message needed in the above second step, we define Protobuf message class `OpDesc`. It is used to hold user-specified parameters in Op describing. + +```proto +message OpDesc { + required string type = 1; + repeated string inputs = 2; + repeated string outputs = 3; + map attrs = 4; +}; +``` + +## OpProto Register + +Every Op has its own `OpProto`. For using convenience, we need to register them and record all their messages. For each `Op` class, we define a corresponding `OpMaker` class, in whose constructor we implement the `OpProto`'s building process. `OpMaker`'s constructor will be invoked by another function `OpRegistry::RegisterOp()`. + +```cpp +class OpProtoMaker { +public: + OpProtoMaker(OpProto* proto): proto_(proto) {} +protected: + OpProto* proto_; + void AddInput(const std::string& name, const std::string& desc) {...} + void AddAttr(const std::string& name, const std::string& desc, TypeId type) {...} + void AddComment(const std::string& comment) { ... } +}; + +class OpRegistry { +public: + using OpCreator = std::function; + + template + static void RegisterOp(const std::string& name) { + gCreators_[name] = [](const OpDesc& desc) { + return new OpType(desc); + }; + OpProto& opProto = gProtos_[name]; + OpMaker()(&opProto); + } + + static map gCreators_; + static map gProtos_; +}; + +template +class OpRegister { + public: + OpRegister(std::string type) { + OpRegistry::RegisterOp(type); + } +}; + +#define REGISTER_OP(op_class, op_maker_class, type_name) \ + class op_class##Register { \ + private: \ + const static OpRegister<#op_class, #op_maker_class> reg; \ + }; \ + const Register op_class##Register::reg(#type_name); + +class CosineOp { +// ... +} + +struct CosineOpProtoMaker : public OpProtoMaker { + CosineOpProtoMaker(OpProto* proto) : OpProtoMaker(proto) { + AddInput("input", "input of cosine op"); + AddAttr("scale", "scale of cosine op", float).Default(1.0).LargerThan(0.0); + AddType("cos"); + AddComment("This is cos op"); + } +} + +REGISTER_OP(CosineOp, CosineOpProtoMaker, cos); +``` + +In `REGISTER_OP(CosineOp, CosineOpProtoMaker, cos)`, we register not only `CosineOp` but also `CosineOpProto`. As fields of `CosineOpProto`, the default value and value range of `scale` are also registered here. + +## Python API + +Python APIs are divided into two types, high-level API and low-level API. + +### High-Level API + +High-level API is called by users directly, so it should keep its style consistent with existing V2 APIs. + +Here is a sample about how a define a fc layer: + +```python +hd = fc_layer(input=data, size=56, with_bias=True, activation="sigmoid"); +``` + +`hd` is the output of `fc_layer` and it's a `variable`. It can be further sent into other layers as input. + +The definition of `fc_layer()`: + +```python +def fc_layer(input, size, with_bias, activation): + attr_map = {"size":size} + check_attrs(attr_map) + w = make_variable('w') + if with_bias: + b = make_variable('b') + else: + b = None + fc_output = make_variable('fc_output'); + fc_op(input, w, b, fc_output, attr_map) + act_output = make_variable('sigmod_output'); + if activation == "sigmod": + sigmod_op(fc_output, act_output); + elif: + # ... + return act_output; +``` + +### Low Leval API + +In above sample, `fc_op` and `sigmod_op` are low-level API. They build `OpDesc` and invoke corresponding C++ code. + +*TODO* -- GitLab From f4281ceee47fca4a1002efeea93f9ba39f99de76 Mon Sep 17 00:00:00 2001 From: fengjiayi Date: Mon, 3 Jul 2017 14:27:37 +0800 Subject: [PATCH 0965/3256] renew simple_op_design --- doc/design/simple_op_design.md | 110 ++++++--------------------------- 1 file changed, 19 insertions(+), 91 deletions(-) diff --git a/doc/design/simple_op_design.md b/doc/design/simple_op_design.md index 93c0f68ca9b..49ca5db5da9 100644 --- a/doc/design/simple_op_design.md +++ b/doc/design/simple_op_design.md @@ -43,10 +43,7 @@ message AttrValue { message AttrProto { required string name = 1; required string comment = 2; - optional AttrValue default = 3; - optional AttrValue max = 4; - optional AttrValue min = 5; - required AttrType type = 6; + required AttrType type = 3; }; message VarProto { @@ -63,7 +60,24 @@ message OpProto { }; ``` -The default value and value range didn't appear in out previous design. By adding these two fields, we are able to check attribute validity in Python and find out possible error as soon as possible. What's more, by providing the message about default value and value range to Python docstring, it helps to automatically generate more comprehensive documents. +To generate Python code automatically: + +```python +def create_python_ops_creatation_functions(): + op_protos = paddle.framework.OpRegistry.get_all_op_proto() + for type_name in op_protos: + op_proto = op_protos[type_name] + def __impl__(**kwargs): # User must use key word args in Paddle API + inputs = [kwargs.get(ipt.name, "") for ipt in op_proto.inputs] + outputs = [kwargs.get(opt.name, "") for opt in op_proto.outputs] + attrs = [cast_to_op_attr(attr, kwargs.get(attr.name, None)) for attr in op_proto.attrs] + opdesc = (input, outputs, type_name, attrs) + return paddle.framework.OpRegistry.CreateOp(opdesc) + __impl__.__doc__ = create_doc_string(op_proto) + globals()[type_name] = __impl__ + +create_python_ops_creatation_functions() +``` ### Message from Python to C++ @@ -185,89 +199,3 @@ def fc_layer(input, size, with_bias, activation): In above sample, `fc_op` and `sigmod_op` are low-level API. They build `OpDesc` and invoke corresponding C++ code. *TODO* - -## Op and Kernal - -After completely defined, an Op will be run in a network. However, Op's computing method may differ on different devices. One solution is that write an `Op`'s member function `Op::run()`, which contains computing methods of all possible devices. That may be a bad idea because we have to change all `Op`'s code to add a new device. - -Another choice is adding a concept named `kernal`. A `Kernal` describes an op's computing process on a certain device. After stripping `Variable` and `kernal`, `Op` becomes a pure conceptual class, which holds neither data nor detailed computing process. - -```cpp -class KernalBase { -public: - virtual void RunOnDevice(std::vector input_vars, - std::vector input_vars, - const OpAttrs* attrs) = 0; -}; - -template -class CosineKernal : public KernalBase { -public: - virtual void RunOnDevice(std::vector input_vars, - std::vector input_vars, - const OpAttrs* attrs) { - // no implementation - } -}; - -template <> -class CosineKernal : public KernalBase { -public: - virtual void RunOnDevice(std::vector input_vars, - std::vector input_vars, - const OpAttrs* attrs) { - CosineOpAttrs* cosine_attrs = static_cast(attrs); - // computing code - // ... - } -}; - -struct OpAttrs {...}; - -class Op { - public: - std::string get_kernal_name() { - return kernel_name_; - } - const vector& get_input_names() { - return input_names_; - } - const vector& get_output_names() { - return output_names_; - } - // ... - private: - std::vector input_names_; - std::vector output_names_; - std::string kernal_name_; - -} - -struct CosineOpAttrs : public OpAttrs { - float scale_; -} - -class CosineOp : public Op { - public: - const CosineOpAtrrs* get_attrs() { - return &attrs; - } - - private: - CosineOpAttrs attrs; -} - -RunOp(const Op& op, Scope scope) { - Kernal* kernal = get_kernal(scope, op.get_kernal_name()); - std::vector input_vars = - get_variables(scope, op.get_input_name()); - std::vector output_vars = - get_variables(scope, op.get_output_name()); - - kernal->RunOnDevice(input_vars, output_vars, op.get_attrs()); -} -``` - -All `Kernal` need to be registered beforehand, just like `Op`. - -Now, `Op` is no longer has `Run()` function. It only contains names of variables and kernels. During network running, `RunOp()` is called to invoke `Op`'s corresponding `Kernal`. `get_kernal()` is supposed to return `kernal` for current device. -- GitLab From 40573cd56f723ebde6328ccd5dabe4a363c9f3db Mon Sep 17 00:00:00 2001 From: Superjom Date: Mon, 3 Jul 2017 14:41:43 +0800 Subject: [PATCH 0966/3256] add net headers --- paddle/framework/net.cc | 23 +++++ paddle/framework/net.h | 182 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 205 insertions(+) create mode 100644 paddle/framework/net.cc create mode 100644 paddle/framework/net.h diff --git a/paddle/framework/net.cc b/paddle/framework/net.cc new file mode 100644 index 00000000000..0ce92968200 --- /dev/null +++ b/paddle/framework/net.cc @@ -0,0 +1,23 @@ +#include "paddle/framework/net.h" + +namespace paddle { +namespace framework { + +PlainNet::PlainNet(const NetDesc& def) {} + +virtual Error PlainNet::InferShape() { + for (auto& op : ops_) { + // wrong shape + auto err = op.InferShape(); + if (!err) return err; + } + // ok + return Error(); +} + +virtual Error PlainNet::Run(Scope* scope = nullptr, + OpContext* context = nullptr, OpIndex begin = -1, + OpIndex end = -1) const {} + +} // namespace framework +} // namespace paddle diff --git a/paddle/framework/net.h b/paddle/framework/net.h new file mode 100644 index 00000000000..88bdf0bb68b --- /dev/null +++ b/paddle/framework/net.h @@ -0,0 +1,182 @@ +/* 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/scope.h" + +namespace paddle { +namespace framework { + +// operator's index stored in a network. +typedef int OpIndex; +/** + * NOTE following codes are some definitions of unimplemented concepts. + * We write some basic implementation to make Net compilable. These APIs will + * keep updating if the concepts related are implemented. + */ + +// Operator's runtime context. +struct OpContext { + int dev_id; + DevType dev_type{kCPU}; + enum DevType { kCPU, kGPU }; +}; + +// Proto definitions, use `struct`s for simpility. +struct VarDesc { + std::string type; + std::vector dims; +}; +struct OpDesc { + std::string type; + std::vector inputs; + std::vector outputs; +}; +struct struct NetDesc { + std::vector ops; +}; +class Operator { + public: + Operator(const OpDesc &def) {} + Error InferShape() {} + Error Run() {} +}; + +/** + * @brief Network that manage the operators it has. + * + * Network is the container and controller of a set of operators, user can build + * a real network from a NetDesc which is a protobuf message and use + * Network.Run() * to run all the operators in the network. + + * A network object knows all Operators belonging to this network. Variables, + * which are inputs and outputs of these operators, are created and managed by a + * hierarchy of Scope objects. + * + * This is the base class of network, all the networks should implement the apis + * it defines. + */ +class Net { + public: + /** + * @brief Infer shapes of all inputs and outputs of operators. + */ + virtual Error InferShape(Scope *scope) override; + /** + * @brief Run the network. + * + * Run all the operators and return success(true) or not, with all the + * variables are located in `scope`. `context` describes the detail execution + * environment for ops. `begin` and `end` specify the scope of `ops_` to run, + * If no positive indexes are provided, all operators in `ops_` will run. + */ + virtual Error Run(Scope *scope, OpContext *context, OpIndex begin = -1, + OpIndex end = -1) const = 0; + + /** + * @brief Add an Operator according to `def`. + */ + virtual OpIndex AddOp(const proto::OpDef &def) = 0; + + /** + * @brief Add optimizer operators acctording to `attrs`. + */ + virtual Error AddOptimizerOps(const OptAttrs &attrs) = 0; + + /** + * @brief Add backward operators. + */ + virtual Error AddBackwardOps() = 0; + + /** + * @brief Create a network. + */ + static std::unique_ptr Create(const NetDesc &def = NetDesc()); +}; + +/** + * @brief a basic implementation of Net. + * + * PlainNet is a very simple Net, it create a list of operators, and run them + * sequentially following the order they added. + */ +class PlainNet : public Net { + public: + /** + * @brief Initialize a PlainNet. + * + * Initialize from a network describe by `def`. NetDesc is the definition of + * a network. + */ + PlainNet(const NetDesc &def); + + /** + * Infer all the operators' input and output varialbes' shapes, will be called + * before every mini-batch + */ + virtual Error InferShape(Scope *scope) override; + + /** + * @brief Run the network. + * + * Run all the operators with the `scope`, if no scope is provided, default + * scope will be used instead. If no OpContext is provicded, default context + * will be used. + */ + virtual Error Run(Scope *scope = nullptr, OpContext *context = nullptr, + OpIndex begin = -1, OpIndex end = -1) const override; + + /** + * @brief Add an operator to this network. + */ + virtual OpIndex AddOp(const proto::OpDef &def) override; + + /** + * @brief Add all optimizer operators related into the network. + */ + virtual Error AddOptimizerOps(const OptAttrs &attrs) override; + + /** + * @brief Add all backward operators related into the network. + */ + virtual Error AddBackwardOps() override; + + protected: + /** + * @brief Build the network. + * + * Create operators accordding to `def`, will be called by the constructor. + */ + Error BuildNet(const NetDesc &def); + + /** + * @brief Add an operator into this network. + * + * Add a operator which is identified as `type` and has attributes described + * in `attrs`, the `inputs` are the keys of readonly input variables, + * `outputs` are keys of mutable output variables. An `OpIndex` will be + * returned to indicate the offset of the new operator in `ops_`. + */ + OpIndex AddOp(const std::string &type, const std::vector &inputs, + const std::vector &outputs, + const OprAttr &attrs = OprAttr()); + + private: + // the operators owned by `Network`. + std::vector ops_; +}; + +} // namespace framework +} // namespace paddle -- GitLab From 99e22a825d0d86968d24fc6717b765055c8ac73b Mon Sep 17 00:00:00 2001 From: dongzhihong Date: Mon, 3 Jul 2017 14:53:31 +0800 Subject: [PATCH 0967/3256] "fix pre-commit hook failed" --- go/pserver/cclient/test/test_cclient.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/go/pserver/cclient/test/test_cclient.c b/go/pserver/cclient/test/test_cclient.c index 5bd4913ba3c..b16769b433e 100644 --- a/go/pserver/cclient/test/test_cclient.c +++ b/go/pserver/cclient/test/test_cclient.c @@ -16,7 +16,7 @@ void sendGrads(paddle_pserver_client c) { "param_a", PADDLE_ELEMENT_TYPE_FLOAT32, grad_a, 2000}; paddle_gradient grad2 = { "param_b", PADDLE_ELEMENT_TYPE_FLOAT32, grad_b, 3000}; - paddle_gradient* grads[2] = {&grad1, &grad2}; + paddle_gradient *grads[2] = {&grad1, &grad2}; if (paddle_send_grads(c, grads, 2)) { fail(); } @@ -39,14 +39,12 @@ void getParams(paddle_pserver_client c) { param_b.content = content_b; param_b.content_len = 3000; - paddle_parameter* params[2] = {¶m_a, ¶m_b}; + paddle_parameter *params[2] = {¶m_a, ¶m_b}; if (paddle_get_params(c, params, 2)) { fail(); } } - - int main() { char addr[] = "localhost:3000"; paddle_pserver_client c = paddle_new_pserver_client(addr, 1); @@ -54,8 +52,10 @@ int main() { size_t config_proto_len = 0; ssize_t nread; FILE *fp = fopen("testdata/optimizer.pb.txt", "r"); - if(!fp) { fail(); } - while((nread = getline(&config_proto, &config_proto_len, fp)) != -1) { + if (!fp) { + fail(); + } + while ((nread = getline(&config_proto, &config_proto_len, fp)) != -1) { printf("%s", config_proto); } fclose(fp); @@ -70,7 +70,8 @@ retry: param.name = name_a; param.content = content_a; param.content_len = 2000; - int error = paddle_init_param(c, param, (void *)config_proto, config_proto_len); + int error = + paddle_init_param(c, param, (void *)config_proto, config_proto_len); if (error != 0) { goto retry; } -- GitLab From 16b8e59e1ab8cb33d175ce6d4bfe3f19419acb06 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Mon, 3 Jul 2017 15:32:51 +0800 Subject: [PATCH 0968/3256] Update new authors --- AUTHORS.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/AUTHORS.md b/AUTHORS.md index d5baee2161a..08eaab10ea8 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -1,5 +1,23 @@ | Github account | name | |---|---| +| beckett1124 | Bin Qi | +| Canpio | Jiayi Feng | +| chengxiaohua1105 | Xiaohua Cheng | +| xushaoyong | Shaoyong Xu | +| liuyuan | Yuan Liu | +| xujun05 | Jun Xu | +| dzhwinter | Zhihong Dong | +| Guo Sheng | Sheng Guo | +| kuke | Yibing Liu | +| llxxxll | YongFeng Liu | +| cxysteven | Xingyi Cheng | +| NHZlX | Zhaolong Xing | +| pakchoi | Chuanjiang Song | +| pkuyym | Yaming Yang | +| Superjom | Chunwei Yan | +| wanghaoshuang | Haoshuang Wang | +| wangzhen-nlp | Zhen Wang | +| wwhu | Weiwei Hu | | reyoung | Yang Yu | | gangliao | Gang Liao | | luotao01 | Tao Luo | -- GitLab From 85c4352ea8eeaa419d3542f378089fc9e1180565 Mon Sep 17 00:00:00 2001 From: dongzhihong Date: Mon, 3 Jul 2017 15:37:47 +0800 Subject: [PATCH 0969/3256] "pass style check" --- go/pserver/cclient/test/testdata/optimizer.pb.txt | Bin 50 -> 51 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/go/pserver/cclient/test/testdata/optimizer.pb.txt b/go/pserver/cclient/test/testdata/optimizer.pb.txt index 27dd3bc5f19e2964b4b674cff8860233cbdb445a..27c8a584df40ab714edfd730f0ff7b7bd3783964 100644 GIT binary patch delta 6 NcmXpqo?yht1poyT0W$yq delta 4 LcmXpunqUL~0=NMv -- GitLab From 696ba1d2e1f3fdac763c4dd29b5353b512f9b7fa Mon Sep 17 00:00:00 2001 From: fengjiayi Date: Mon, 3 Jul 2017 16:01:50 +0800 Subject: [PATCH 0970/3256] init tensor_test.cc --- paddle/framework/CMakeLists.txt | 1 + paddle/framework/tensor.h | 5 +-- paddle/framework/tensor_test.cc | 71 +++++++++++++++++++++++++++++++++ 3 files changed, 74 insertions(+), 3 deletions(-) create mode 100644 paddle/framework/tensor_test.cc diff --git a/paddle/framework/CMakeLists.txt b/paddle/framework/CMakeLists.txt index 6aa6b9bc2db..41bf3837aa2 100644 --- a/paddle/framework/CMakeLists.txt +++ b/paddle/framework/CMakeLists.txt @@ -2,6 +2,7 @@ cc_library(ddim SRCS ddim.cc) cc_test(ddim_test SRCS ddim_test.cc DEPS ddim) nv_test(dim_test SRCS dim_test.cu DEPS ddim) +cc_test(tensor_test SRCS tensor_test.cc DEPS ddim) cc_test(variable_test SRCS variable_test.cc) cc_test(scope_test SRCS scope_test.cc) cc_test(enforce_test SRCS enforce_test.cc) diff --git a/paddle/framework/tensor.h b/paddle/framework/tensor.h index 067f2a85264..8d658d50972 100644 --- a/paddle/framework/tensor.h +++ b/paddle/framework/tensor.h @@ -19,13 +19,12 @@ namespace framework { class Tensor { using paddle::platform::Place; - using paddle::platform::get_place; public: template const T* data() const { - PADDLE_ASSERT(holder_ != nullptr, - "Tensor::data must be called after Tensor::mutable_data"); + PADDLE_ENFORCE(holder_ != nullptr, + "Tensor::data must be called after Tensor::mutable_data"); return static_cast(holder->Ptr()); } diff --git a/paddle/framework/tensor_test.cc b/paddle/framework/tensor_test.cc new file mode 100644 index 00000000000..fa44b24b645 --- /dev/null +++ b/paddle/framework/tensor_test.cc @@ -0,0 +1,71 @@ +/* + 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/tensor.h" +#include + +TEST(Tensor, Data) { + using namespace paddle::framework; + using namespace paddle::platform; + + Tensor cpu_tensor; +} + +/* mutable_data() is not tested at present + because Memory::Alloc() and Memory::Free() have not been ready. + +TEST(Tensor, MutableData) { + using namespace paddle::framework; + using namespace paddle::platform; + + Tensor cpu_tensor; + float* p1 = nullptr; + float* p2 = nullptr; + // initialization + p1 = cpu_tensor.mutable_data(make_ddim({1, 2, 3}), CPUPlace()); + EXPECT_NE(p1, nullptr); + // set cpu_tensor a new dim with large size + // momery is supposed to be re-allocated + p2 = cpu_tensor.mutable_data(make_ddim({3, 4})); + EXPECT_NE(p2, nullptr); + EXPECT_NE(p1, p2); + // set cpu_tensor a new dim with same size + // momery block is supposed to be unchanged + p1 = cpu_tensor.mutable_data(make_ddim({2, 2, 3})); + EXPECT_EQ(p1, p2); + // set cpu_tensor a new dim with smaller size + // momery block is supposed to be unchanged + p2 = cpu_tensor.mutable_data(make_ddim({2, 2})); + EXPECT_EQ(p1, p2); + + Tensor gpu_tensor; + float* p1 = nullptr; + float* p2 = nullptr; + // initialization + p1 = gpu_tensor.mutable_data(make_ddim({1, 2, 3}), GPUPlace()); + EXPECT_NE(p1, nullptr); + // set gpu_tensor a new dim with large size + // momery is supposed to be re-allocated + p2 = gpu_tensor.mutable_data(make_ddim({3, 4})); + EXPECT_NE(p2, nullptr); + EXPECT_NE(p1, p2); + // set gpu_tensor a new dim with same size + // momery block is supposed to be unchanged + p1 = gpu_tensor.mutable_data(make_ddim({2, 2, 3})); + EXPECT_EQ(p1, p2); + // set gpu_tensor a new dim with smaller size + // momery block is supposed to be unchanged + p2 = gpu_tensor.mutable_data(make_ddim({2, 2})); + EXPECT_EQ(p1, p2); +} +*/ \ No newline at end of file -- GitLab From 9f408dfb1b81daee795d9c0d8ed177e6ab4e10a8 Mon Sep 17 00:00:00 2001 From: fengjiayi Date: Mon, 3 Jul 2017 16:20:25 +0800 Subject: [PATCH 0971/3256] fix some compile error --- paddle/framework/tensor.h | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/paddle/framework/tensor.h b/paddle/framework/tensor.h index 8d658d50972..7fa662fbb54 100644 --- a/paddle/framework/tensor.h +++ b/paddle/framework/tensor.h @@ -14,32 +14,39 @@ limitations under the License. */ #pragma once +#include +#include +#include +#include "paddle/framework/ddim.h" +#include "paddle/framework/enforce.h" +#include "paddle/memory/memory.h" +#include "paddle/platform/assert.h" +#include "paddle/platform/place.h" + namespace paddle { namespace framework { class Tensor { - using paddle::platform::Place; - public: template const T* data() const { PADDLE_ENFORCE(holder_ != nullptr, "Tensor::data must be called after Tensor::mutable_data"); - return static_cast(holder->Ptr()); + return static_cast(holder_->Ptr()); } template ::value>::type> - T* mutable_data(DDim dims, Place place) { + typename std::enable_if::value>::type* = nullptr> + T* mutable_data(DDim dims, paddle::platform::Place place) { if (holder_ == nullptr || holder_->Place() != place || - holder_->Size() < dims.product() * sizeof(T)) { - holder_.reset(new PlaceholderImpl(place, dims.product() * sizeof(T))); + holder_->Size() < product(dims) * sizeof(T)) { + holder_.reset(new PlaceholderImpl(place, product(dims) * sizeof(T))); } return static_cast(holder_->Ptr()); } template ::value>::type> + typename std::enable_if::value>::type* = nullptr> T* mutable_data(DDim dims) { return mutable_data(dims, paddle::platform::get_place()); } @@ -50,24 +57,24 @@ class Tensor { struct Placeholder { virtual ~Placeholder() {} virtual void* Ptr() const = 0; - virtual Place Place() const = 0; + virtual paddle::platform::Place Place() const = 0; virtual size_t Size() const = 0; }; template struct PlaceholderImpl : public Placeholder { - PlaceholderImpl(Place pl, size_t size) + PlaceholderImpl(paddle::platform::Place pl, size_t size) : ptr_(paddle::memory::Alloc(pl, size), paddle::memory::Deleter(pl)), place_(pl), size_(size) {} virtual void* Ptr() const { return static_cast(ptr_.get()); } virtual size_t Size() const { return size_; } - virtual Place Place() const { return place_; } + virtual paddle::platform::Place Place() const { return place_; } std::unique_ptr ptr_; - Place place_; // record the place of ptr_. - size_t size_; // size of the memory block. + paddle::platform::Place place_; // record the place of ptr_. + size_t size_; // size of the memory block. }; std::unique_ptr holder_; // holds the memory block if allocated. -- GitLab From bdd27208778e82ca037b2b3f6d25337403db4092 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Mon, 3 Jul 2017 16:26:33 +0800 Subject: [PATCH 0972/3256] Add OpProto implementation OpProto is a proto message that helps 3rd-party language bindings, e.g. `Python`, to generate operator creation methods. The operator creation method is the low-level API for 3rd-party language bindings. Op creation methods take the user's input in that language, and convert users inputs into `OpDesc` message, then passing that `OpDesc` message to Paddle's C++ core and create an operator. * A separated `attr_type.proto` is added, because that file wound be included by `op_desc.proto` in future. --- paddle/framework/CMakeLists.txt | 1 + paddle/framework/attr_type.proto | 28 +++++++++++++ paddle/framework/op_proto.proto | 69 ++++++++++++++++++++++++++++++++ 3 files changed, 98 insertions(+) create mode 100644 paddle/framework/attr_type.proto create mode 100644 paddle/framework/op_proto.proto diff --git a/paddle/framework/CMakeLists.txt b/paddle/framework/CMakeLists.txt index 6aa6b9bc2db..32840159082 100644 --- a/paddle/framework/CMakeLists.txt +++ b/paddle/framework/CMakeLists.txt @@ -5,3 +5,4 @@ nv_test(dim_test SRCS dim_test.cu DEPS ddim) cc_test(variable_test SRCS variable_test.cc) cc_test(scope_test SRCS scope_test.cc) cc_test(enforce_test SRCS enforce_test.cc) +proto_library(op_proto SRCS op_proto.proto attr_type.proto) diff --git a/paddle/framework/attr_type.proto b/paddle/framework/attr_type.proto new file mode 100644 index 00000000000..2d8e0476d71 --- /dev/null +++ b/paddle/framework/attr_type.proto @@ -0,0 +1,28 @@ +/* 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. */ + +syntax="proto2"; +package paddle.framework; + +// Attribute Type for paddle's Op. +// Op contains many attributes. Each type of attributes could be different. +// The AttrType will be shared between AttrDesc and AttrProto. +enum AttrType { + INT = 0; + FLOAT = 1; + STRING = 2; + INTS = 3; + FLOATS = 4; + STRINGS = 5; +} \ No newline at end of file diff --git a/paddle/framework/op_proto.proto b/paddle/framework/op_proto.proto new file mode 100644 index 00000000000..22df6f9c6b7 --- /dev/null +++ b/paddle/framework/op_proto.proto @@ -0,0 +1,69 @@ +/* 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. */ + +// Protocol Message for 3rd-party language binding. +// +// Paddle Python package will use `OpProto` to generate op creation methods. +// The op creation methods take user's input and generate `OpDesc` proto message, +// then pass `OpDesc` to C++ side and create Op pointer. +// +syntax="proto2"; +package paddle.framework; + +import "attr_type.proto"; + +// Attribute protocol message for 3rd-party language binding. +// It will store the Op support what attribute and what type. +message AttrProto { + // Supported attribute name. e.g. `scale` for cosine op. + required string name = 1; + + // Supported attribute type. + required AttrType type = 2; + + // Supported attribute comments. It helps 3rd-party language generate doc-string. + required string comment = 3; +} + +// Input or output message for 3rd-party language binding. +// It contains parameter name and its comments. +message VarProto { + // Input or output name in that op creation function. + // e.g. `cos(a, b, output, ...)`, "a", "b", "output" are names. + required string name = 1; + + // The comment for that input. It helps 3rd-party language generate doc-string. + required string comment = 2; +} + +// Op protocol message for 3rd-party language binding. +// It contains all information for generating op creation method. +message OpProto { + // The input information to generate op creation method. + repeated VarProto inputs = 1; + + // The output information to generate op creation method. + repeated VarProto outputs = 2; + + // The attribute information to generate op creation method. + repeated AttrProto attrs = 3; + + // The comments for that Op. It helps 3rd-party language generate + // doc-string. The whole documentation of that Op is generated by comment, + // inputs, outputs, attrs together. + required string comment = 4; + + // The type of that Op. + required string type = 5; +} -- GitLab From c9cd5b6e9dd9c92ae236709c61e3cde7a17ee2b9 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Mon, 3 Jul 2017 16:39:36 +0800 Subject: [PATCH 0973/3256] Update Authors.md --- AUTHORS.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/AUTHORS.md b/AUTHORS.md index 08eaab10ea8..09698ac1409 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -18,6 +18,8 @@ | wanghaoshuang | Haoshuang Wang | | wangzhen-nlp | Zhen Wang | | wwhu | Weiwei Hu | +| xinghai-sun | XingHai Sun | +| zhaopu7 | Pu Zhao | | reyoung | Yang Yu | | gangliao | Gang Liao | | luotao01 | Tao Luo | -- GitLab From 9bf98168281952efee1ed5fd1a61b743b0847834 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Mon, 3 Jul 2017 16:47:11 +0800 Subject: [PATCH 0974/3256] Add OpProto unittest. --- paddle/framework/CMakeLists.txt | 4 +++- paddle/framework/op_proto_test.cc | 31 +++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 paddle/framework/op_proto_test.cc diff --git a/paddle/framework/CMakeLists.txt b/paddle/framework/CMakeLists.txt index 32840159082..50107faaed7 100644 --- a/paddle/framework/CMakeLists.txt +++ b/paddle/framework/CMakeLists.txt @@ -5,4 +5,6 @@ nv_test(dim_test SRCS dim_test.cu DEPS ddim) cc_test(variable_test SRCS variable_test.cc) cc_test(scope_test SRCS scope_test.cc) cc_test(enforce_test SRCS enforce_test.cc) -proto_library(op_proto SRCS op_proto.proto attr_type.proto) +proto_library(attr_type SRCS attr_type.proto) +proto_library(op_proto SRCS op_proto.proto) +cc_test(op_proto_test SRCS op_proto_test.cc DEPS attr_type op_proto protobuf) diff --git a/paddle/framework/op_proto_test.cc b/paddle/framework/op_proto_test.cc new file mode 100644 index 00000000000..9c054bde44e --- /dev/null +++ b/paddle/framework/op_proto_test.cc @@ -0,0 +1,31 @@ +#include +#include + +TEST(TestOpProto, ALL) { + paddle::framework::OpProto proto; + { + auto ipt = proto.mutable_inputs()->Add(); + *ipt->mutable_name() = "a"; + *ipt->mutable_comment() = "the one input of cosine op"; + } + { + auto ipt = proto.mutable_inputs()->Add(); + *ipt->mutable_name() = "b"; + *ipt->mutable_comment() = "the other input of cosine op"; + } + { + auto opt = proto.mutable_outputs()->Add(); + *opt->mutable_name() = "output"; + *opt->mutable_comment() = "the output of cosine op"; + } + { + auto attr = proto.mutable_attrs()->Add(); + *attr->mutable_name() = "scale"; + attr->set_type(paddle::framework::AttrType::FLOAT); + *attr->mutable_comment() = "the scale attribute of cosine op"; + } + proto.set_type("cos"); + *proto.mutable_comment() = "cosine op, output = scale * cos(a, b)"; + + ASSERT_TRUE(proto.IsInitialized()); +} \ No newline at end of file -- GitLab From 0e61730039b11861d5a90188987bad2241a08f95 Mon Sep 17 00:00:00 2001 From: Luo Tao Date: Mon, 3 Jul 2017 12:05:38 +0800 Subject: [PATCH 0975/3256] stride pooling for max and average layer --- paddle/gserver/layers/MaxLayer.h | 5 ++ .../layers/SequenceLastInstanceLayer.cpp | 3 +- paddle/gserver/layers/SequencePoolLayer.cpp | 5 +- paddle/gserver/layers/SequencePoolLayer.h | 2 - paddle/gserver/tests/test_LayerGrad.cpp | 12 ++++- paddle/parameter/Argument.cpp | 6 +-- paddle/parameter/Argument.h | 2 +- paddle/parameter/tests/test_argument.cpp | 4 +- python/paddle/trainer/config_parser.py | 8 +++ .../paddle/trainer_config_helpers/layers.py | 12 +++++ .../protostr/test_sequence_pooling.protostr | 51 +++++++++++++++++++ .../tests/configs/test_sequence_pooling.py | 8 +++ 12 files changed, 103 insertions(+), 15 deletions(-) diff --git a/paddle/gserver/layers/MaxLayer.h b/paddle/gserver/layers/MaxLayer.h index baa58ca2d7a..adf7ab4ae47 100644 --- a/paddle/gserver/layers/MaxLayer.h +++ b/paddle/gserver/layers/MaxLayer.h @@ -26,6 +26,11 @@ namespace paddle { * If SequenceLevel = kNonSeq: * Output: output size is the number of input sequences (NOT input instances) * output[i] = max_{for each instance in this sequence}{input[i]} + * If stride_ > 0: + * Output: a shorten sequence. The operation of getting max instance of a + * sequence is independently performed on every slice of the input + * sequence, which is obtained by sliding a window with the window + * size set to stride_. * If SequenceLevel = kSeq: * Check input sequence must has sub-sequence * Output: output size is the number of input sub-sequences diff --git a/paddle/gserver/layers/SequenceLastInstanceLayer.cpp b/paddle/gserver/layers/SequenceLastInstanceLayer.cpp index 944c7051668..8127cbf09c2 100644 --- a/paddle/gserver/layers/SequenceLastInstanceLayer.cpp +++ b/paddle/gserver/layers/SequenceLastInstanceLayer.cpp @@ -73,8 +73,7 @@ bool SequenceLastInstanceLayer::init(const LayerMap& layerMap, void SequenceLastInstanceLayer::forward(PassType passType) { SequencePoolLayer::forward(passType); - auto starts = (stride_ > 0) ? stridePositions_->getData() - : startPositions_->getData(false); + auto starts = startPositions_->getData(false); MatrixPtr inputValue = getInputValue(0); MatrixPtr outputValue = getOutputValue(); diff --git a/paddle/gserver/layers/SequencePoolLayer.cpp b/paddle/gserver/layers/SequencePoolLayer.cpp index 4179a9e7e0c..2a693b110a5 100644 --- a/paddle/gserver/layers/SequencePoolLayer.cpp +++ b/paddle/gserver/layers/SequencePoolLayer.cpp @@ -72,9 +72,8 @@ void SequencePoolLayer::forward(PassType passType) { if (stride_ > 0) { CHECK_EQ(input.hasSubseq(), 0UL) << "sequence stride pooling is invalid for hasSubseq now"; - output_.poolSequenceWithStride( - input, stride_, &stridePositions_, reversed_); - newBatchSize_ = stridePositions_->getSize() - 1; + output_.poolSequenceWithStride(input, stride_, &startPositions_, reversed_); + newBatchSize_ = startPositions_->getSize() - 1; } resetOutput(newBatchSize_, dim); diff --git a/paddle/gserver/layers/SequencePoolLayer.h b/paddle/gserver/layers/SequencePoolLayer.h index 293d1bf2782..058627def8a 100644 --- a/paddle/gserver/layers/SequencePoolLayer.h +++ b/paddle/gserver/layers/SequencePoolLayer.h @@ -47,8 +47,6 @@ protected: size_t newBatchSize_; ICpuGpuVectorPtr startPositions_; int stride_; - // Store the start position of each window. - IVectorPtr stridePositions_; // Whether the input sequence is reversed or not. bool reversed_ = false; diff --git a/paddle/gserver/tests/test_LayerGrad.cpp b/paddle/gserver/tests/test_LayerGrad.cpp index 297756025bc..ed067e7c3a1 100644 --- a/paddle/gserver/tests/test_LayerGrad.cpp +++ b/paddle/gserver/tests/test_LayerGrad.cpp @@ -845,8 +845,12 @@ void testDegradeLayer(bool hasSubseq, TEST(Layer, MaxLayer) { testDegradeLayer(false, "max", "non-seq", -1); // seq max to non-seq - testDegradeLayer(true, "max", "non-seq", -1); // hasSubseq max to non-seq - testDegradeLayer(true, "max", "seq", -1); // hasSubseq max to seq + testDegradeLayer(false, + "max", + "non-seq", + 5); // seq max to a shorten seq, stride window = 5 + testDegradeLayer(true, "max", "non-seq", -1); // hasSubseq max to non-seq + testDegradeLayer(true, "max", "seq", -1); // hasSubseq max to seq } TEST(Layer, SequenceLastInstanceLayer) { @@ -868,6 +872,10 @@ TEST(Layer, SequenceLastInstanceLayer) { TEST(Layer, AverageLayer) { testDegradeLayer(false, "average", "non-seq", -1); // seq average to non-seq + testDegradeLayer(false, + "max", + "non-seq", + 5); // seq average to a shorten seq, stride window = 5 testDegradeLayer( true, "average", "non-seq", -1); // hasSubseq average to non-seq testDegradeLayer(true, "average", "seq", -1); // hasSubseq average to seq diff --git a/paddle/parameter/Argument.cpp b/paddle/parameter/Argument.cpp index 5beced3bb5a..ef72b973c1a 100644 --- a/paddle/parameter/Argument.cpp +++ b/paddle/parameter/Argument.cpp @@ -561,7 +561,7 @@ void Argument::degradeSequence(const Argument& input) { void Argument::poolSequenceWithStride(const Argument& input, size_t stride, - IVectorPtr* stridePostions, + ICpuGpuVectorPtr* stridePostions, bool reversed) { // If input.sequenceStartPositions = [0, 9, 14, 17, 30] and stride = 5, // then sequenceStartPositions = [0, 2, 3, 4, 7]. @@ -598,8 +598,8 @@ void Argument::poolSequenceWithStride(const Argument& input, stridePos.emplace_back(starts[numSequences]); int size = stridePos.size(); CHECK_EQ(size - 1, tgtBuf[numSequences]); - IVector::resizeOrCreate(*stridePostions, size, false); - (*stridePostions)->copyFrom(stridePos.data(), size); + ICpuGpuVector::resizeOrCreate(*stridePostions, size, false); + (*stridePostions)->getMutableVector(false)->copyFrom(stridePos.data(), size); } void Argument::getValueString( diff --git a/paddle/parameter/Argument.h b/paddle/parameter/Argument.h index 09bd6336167..0ccdef802e7 100644 --- a/paddle/parameter/Argument.h +++ b/paddle/parameter/Argument.h @@ -299,7 +299,7 @@ struct Argument { */ void poolSequenceWithStride(const Argument& input, size_t stride, - IVectorPtr* stridePositions, + ICpuGpuVectorPtr* stridePositions, bool reversed = false); /** * @brief getValueString will return the argument's output in string. There diff --git a/paddle/parameter/tests/test_argument.cpp b/paddle/parameter/tests/test_argument.cpp index 98ab0135487..19df6ea9574 100644 --- a/paddle/parameter/tests/test_argument.cpp +++ b/paddle/parameter/tests/test_argument.cpp @@ -31,7 +31,7 @@ TEST(Argument, poolSequenceWithStride) { int strideResultReversed[] = {0, 4, 9, 14, 17, 20, 25, 30}; for (auto reversed : {false, true}) { - IVectorPtr stridePositions; + ICpuGpuVectorPtr stridePositions; output.poolSequenceWithStride( input, 5 /* stride */, &stridePositions, reversed); @@ -45,7 +45,7 @@ TEST(Argument, poolSequenceWithStride) { CHECK_EQ(stridePositions->getSize(), 8UL); auto result = reversed ? strideResultReversed : strideResult; for (int i = 0; i < 8; i++) { - CHECK_EQ(stridePositions->getData()[i], result[i]); + CHECK_EQ(stridePositions->getData(false)[i], result[i]); } } } diff --git a/python/paddle/trainer/config_parser.py b/python/paddle/trainer/config_parser.py index b7418101d83..5ca7df74765 100644 --- a/python/paddle/trainer/config_parser.py +++ b/python/paddle/trainer/config_parser.py @@ -2420,10 +2420,14 @@ class MaxLayer(LayerBase): trans_type='non-seq', bias=False, output_max_index=None, + stride=-1, **xargs): super(MaxLayer, self).__init__(name, 'max', 0, inputs=inputs, **xargs) config_assert(len(self.inputs) == 1, 'MaxLayer must have 1 input') + if trans_type == 'seq': + config_assert(stride == -1, 'subseq does not support stride window') self.config.trans_type = trans_type + self.config.seq_pool_stride = stride for input_index in xrange(len(self.inputs)): input_layer = self.get_input_layer(input_index) self.set_layer_size(input_layer.size) @@ -2685,11 +2689,15 @@ class AverageLayer(LayerBase): average_strategy='average', trans_type='non-seq', bias=False, + stride=-1, **xargs): super(AverageLayer, self).__init__( name, 'average', 0, inputs=inputs, **xargs) self.config.average_strategy = average_strategy + if trans_type == 'seq': + config_assert(stride == -1, 'subseq does not support stride window') self.config.trans_type = trans_type + self.config.seq_pool_stride = stride config_assert(len(inputs) == 1, 'AverageLayer must have 1 input') for input_index in xrange(len(self.inputs)): input_layer = self.get_input_layer(input_index) diff --git a/python/paddle/trainer_config_helpers/layers.py b/python/paddle/trainer_config_helpers/layers.py index a601d5c84ad..5e8bf4b2034 100755 --- a/python/paddle/trainer_config_helpers/layers.py +++ b/python/paddle/trainer_config_helpers/layers.py @@ -1090,10 +1090,16 @@ def pooling_layer(input, name=None, bias_attr=None, agg_level=AggregateLevel.TO_NO_SEQUENCE, + stride=-1, layer_attr=None): """ Pooling layer for sequence inputs, not used for Image. + If stride > 0, this layer slides a window whose size is determined by stride, + and return the pooling value of the window as the output. Thus, a long sequence + will be shorten. Note that for sequence with sub-sequence, the default value + of stride is -1. + The example usage is: .. code-block:: python @@ -1112,6 +1118,8 @@ def pooling_layer(input, :param pooling_type: Type of pooling, MaxPooling(default), AvgPooling, SumPooling, SquareRootNPooling. :type pooling_type: BasePoolingType|None + :param stride: window size. + :type stride: Int :param bias_attr: Bias parameter attribute. False if no bias. :type bias_attr: ParameterAttribute|None|False :param layer_attr: The Extra Attributes for layer, such as dropout. @@ -1129,12 +1137,16 @@ def pooling_layer(input, extra_dict['output_max_index'] = pooling_type.output_max_index extra_dict.update(ExtraLayerAttribute.to_kwargs(layer_attr)) + if agg_level == AggregateLevel.TO_SEQUENCE: + assert stride == -1 + Layer( name=name, type=pooling_type.name, inputs=[Input(input.name)], bias=ParamAttr.to_bias(bias_attr), trans_type=agg_level, + stride=stride, **extra_dict) return LayerOutput( diff --git a/python/paddle/trainer_config_helpers/tests/configs/protostr/test_sequence_pooling.protostr b/python/paddle/trainer_config_helpers/tests/configs/protostr/test_sequence_pooling.protostr index 5a217f5544a..8989561df04 100644 --- a/python/paddle/trainer_config_helpers/tests/configs/protostr/test_sequence_pooling.protostr +++ b/python/paddle/trainer_config_helpers/tests/configs/protostr/test_sequence_pooling.protostr @@ -14,6 +14,7 @@ layers { input_layer_name: "dat_in" } trans_type: "seq" + seq_pool_stride: -1 } layers { name: "__seq_pooling_1__" @@ -24,6 +25,7 @@ layers { input_layer_name: "dat_in" } trans_type: "non-seq" + seq_pool_stride: -1 } layers { name: "__seq_pooling_2__" @@ -35,6 +37,7 @@ layers { } average_strategy: "average" trans_type: "seq" + seq_pool_stride: -1 } layers { name: "__seq_pooling_3__" @@ -46,6 +49,7 @@ layers { } average_strategy: "average" trans_type: "non-seq" + seq_pool_stride: -1 } layers { name: "__seq_pooling_4__" @@ -57,6 +61,7 @@ layers { } average_strategy: "sum" trans_type: "seq" + seq_pool_stride: -1 } layers { name: "__seq_pooling_5__" @@ -68,6 +73,7 @@ layers { } average_strategy: "sum" trans_type: "non-seq" + seq_pool_stride: -1 } layers { name: "__seq_pooling_6__" @@ -77,8 +83,44 @@ layers { inputs { input_layer_name: "dat_in" } + trans_type: "non-seq" + seq_pool_stride: 5 +} +layers { + name: "__seq_pooling_7__" + type: "average" + size: 100 + active_type: "" + inputs { + input_layer_name: "dat_in" + } + average_strategy: "average" + trans_type: "non-seq" + seq_pool_stride: 5 +} +layers { + name: "__seq_pooling_8__" + type: "average" + size: 100 + active_type: "" + inputs { + input_layer_name: "dat_in" + } + average_strategy: "sum" + trans_type: "non-seq" + seq_pool_stride: 5 +} +layers { + name: "__seq_pooling_9__" + type: "max" + size: 100 + active_type: "" + inputs { + input_layer_name: "dat_in" + } output_max_index: true trans_type: "non-seq" + seq_pool_stride: -1 } input_layer_names: "dat_in" output_layer_names: "__seq_pooling_0__" @@ -88,6 +130,9 @@ output_layer_names: "__seq_pooling_3__" output_layer_names: "__seq_pooling_4__" output_layer_names: "__seq_pooling_5__" output_layer_names: "__seq_pooling_6__" +output_layer_names: "__seq_pooling_7__" +output_layer_names: "__seq_pooling_8__" +output_layer_names: "__seq_pooling_9__" sub_models { name: "root" layer_names: "dat_in" @@ -98,6 +143,9 @@ sub_models { layer_names: "__seq_pooling_4__" layer_names: "__seq_pooling_5__" layer_names: "__seq_pooling_6__" + layer_names: "__seq_pooling_7__" + layer_names: "__seq_pooling_8__" + layer_names: "__seq_pooling_9__" input_layer_names: "dat_in" output_layer_names: "__seq_pooling_0__" output_layer_names: "__seq_pooling_1__" @@ -106,6 +154,9 @@ sub_models { output_layer_names: "__seq_pooling_4__" output_layer_names: "__seq_pooling_5__" output_layer_names: "__seq_pooling_6__" + output_layer_names: "__seq_pooling_7__" + output_layer_names: "__seq_pooling_8__" + output_layer_names: "__seq_pooling_9__" is_recurrent_layer_group: false } diff --git a/python/paddle/trainer_config_helpers/tests/configs/test_sequence_pooling.py b/python/paddle/trainer_config_helpers/tests/configs/test_sequence_pooling.py index 3c49eb56c13..3c205eabd80 100644 --- a/python/paddle/trainer_config_helpers/tests/configs/test_sequence_pooling.py +++ b/python/paddle/trainer_config_helpers/tests/configs/test_sequence_pooling.py @@ -14,6 +14,14 @@ for pt in POOL_TYPE: for al in AGG_LEVEL: opts.append(pooling_layer(input=din, agg_level=al, pooling_type=pt())) +for pt in POOL_TYPE: + opts.append( + pooling_layer( + input=din, + agg_level=AggregateLevel.TO_NO_SEQUENCE, + pooling_type=pt(), + stride=5)) + opts.append( pooling_layer( input=din, pooling_type=MaxPooling(output_max_index=True))) -- GitLab From e146fe836bc5178b497329dacddc7a1dc5063bcd Mon Sep 17 00:00:00 2001 From: fengjiayi Date: Mon, 3 Jul 2017 17:22:58 +0800 Subject: [PATCH 0976/3256] fix compile errors and add assert test --- paddle/framework/tensor.h | 25 ++++++-- paddle/framework/tensor_test.cc | 100 ++++++++++++++++++-------------- 2 files changed, 76 insertions(+), 49 deletions(-) diff --git a/paddle/framework/tensor.h b/paddle/framework/tensor.h index 7fa662fbb54..73eedd7375e 100644 --- a/paddle/framework/tensor.h +++ b/paddle/framework/tensor.h @@ -20,7 +20,6 @@ limitations under the License. */ #include "paddle/framework/ddim.h" #include "paddle/framework/enforce.h" #include "paddle/memory/memory.h" -#include "paddle/platform/assert.h" #include "paddle/platform/place.h" namespace paddle { @@ -63,21 +62,35 @@ class Tensor { template struct PlaceholderImpl : public Placeholder { - PlaceholderImpl(paddle::platform::Place pl, size_t size) - : ptr_(paddle::memory::Alloc(pl, size), paddle::memory::Deleter(pl)), - place_(pl), + private: + class Deleter { + public: + Deleter(platform::Place place) : place_(place) {} + void operator()(T* ptr) { + paddle::memory::Free(place_, static_cast(ptr)); + } + + private: + paddle::platform::Place place_; + }; + + public: + PlaceholderImpl(paddle::platform::Place place, size_t size) + : ptr_(static_cast(paddle::memory::Alloc(place, size)), + Deleter(place)), + place_(place), size_(size) {} virtual void* Ptr() const { return static_cast(ptr_.get()); } virtual size_t Size() const { return size_; } virtual paddle::platform::Place Place() const { return place_; } - std::unique_ptr ptr_; + std::unique_ptr ptr_; paddle::platform::Place place_; // record the place of ptr_. size_t size_; // size of the memory block. }; - std::unique_ptr holder_; // holds the memory block if allocated. + std::shared_ptr holder_; // holds the memory block if allocated. }; } // namespace framework diff --git a/paddle/framework/tensor_test.cc b/paddle/framework/tensor_test.cc index fa44b24b645..f76a31e921a 100644 --- a/paddle/framework/tensor_test.cc +++ b/paddle/framework/tensor_test.cc @@ -13,12 +13,23 @@ #include "paddle/framework/tensor.h" #include +#include -TEST(Tensor, Data) { - using namespace paddle::framework; - using namespace paddle::platform; +TEST(Tensor, ASSERT) { + paddle::framework::Tensor cpu_tensor; - Tensor cpu_tensor; + bool caught = false; + try { + const double* p __attribute__((unused)) = cpu_tensor.data(); + } catch (paddle::framework::EnforceNotMet err) { + caught = true; + std::string msg = "Tensor::data must be called after Tensor::mutable_data"; + const char* what = err.what(); + for (size_t i = 0; i < msg.length(); ++i) { + ASSERT_EQ(what[i], msg[i]); + } + } + ASSERT_TRUE(caught); } /* mutable_data() is not tested at present @@ -27,45 +38,48 @@ TEST(Tensor, Data) { TEST(Tensor, MutableData) { using namespace paddle::framework; using namespace paddle::platform; + { + Tensor cpu_tensor; + float* p1 = nullptr; + float* p2 = nullptr; + // initialization + p1 = cpu_tensor.mutable_data(make_ddim({1, 2, 3}), CPUPlace()); + EXPECT_NE(p1, nullptr); + // set cpu_tensor a new dim with large size + // momery is supposed to be re-allocated + p2 = cpu_tensor.mutable_data(make_ddim({3, 4})); + EXPECT_NE(p2, nullptr); + EXPECT_NE(p1, p2); + // set cpu_tensor a new dim with same size + // momery block is supposed to be unchanged + p1 = cpu_tensor.mutable_data(make_ddim({2, 2, 3})); + EXPECT_EQ(p1, p2); + // set cpu_tensor a new dim with smaller size + // momery block is supposed to be unchanged + p2 = cpu_tensor.mutable_data(make_ddim({2, 2})); + EXPECT_EQ(p1, p2); + } - Tensor cpu_tensor; - float* p1 = nullptr; - float* p2 = nullptr; - // initialization - p1 = cpu_tensor.mutable_data(make_ddim({1, 2, 3}), CPUPlace()); - EXPECT_NE(p1, nullptr); - // set cpu_tensor a new dim with large size - // momery is supposed to be re-allocated - p2 = cpu_tensor.mutable_data(make_ddim({3, 4})); - EXPECT_NE(p2, nullptr); - EXPECT_NE(p1, p2); - // set cpu_tensor a new dim with same size - // momery block is supposed to be unchanged - p1 = cpu_tensor.mutable_data(make_ddim({2, 2, 3})); - EXPECT_EQ(p1, p2); - // set cpu_tensor a new dim with smaller size - // momery block is supposed to be unchanged - p2 = cpu_tensor.mutable_data(make_ddim({2, 2})); - EXPECT_EQ(p1, p2); - - Tensor gpu_tensor; - float* p1 = nullptr; - float* p2 = nullptr; - // initialization - p1 = gpu_tensor.mutable_data(make_ddim({1, 2, 3}), GPUPlace()); - EXPECT_NE(p1, nullptr); - // set gpu_tensor a new dim with large size - // momery is supposed to be re-allocated - p2 = gpu_tensor.mutable_data(make_ddim({3, 4})); - EXPECT_NE(p2, nullptr); - EXPECT_NE(p1, p2); - // set gpu_tensor a new dim with same size - // momery block is supposed to be unchanged - p1 = gpu_tensor.mutable_data(make_ddim({2, 2, 3})); - EXPECT_EQ(p1, p2); - // set gpu_tensor a new dim with smaller size - // momery block is supposed to be unchanged - p2 = gpu_tensor.mutable_data(make_ddim({2, 2})); - EXPECT_EQ(p1, p2); + { + Tensor gpu_tensor; + float* p1 = nullptr; + float* p2 = nullptr; + // initialization + p1 = gpu_tensor.mutable_data(make_ddim({1, 2, 3}), GPUPlace()); + EXPECT_NE(p1, nullptr); + // set gpu_tensor a new dim with large size + // momery is supposed to be re-allocated + p2 = gpu_tensor.mutable_data(make_ddim({3, 4})); + EXPECT_NE(p2, nullptr); + EXPECT_NE(p1, p2); + // set gpu_tensor a new dim with same size + // momery block is supposed to be unchanged + p1 = gpu_tensor.mutable_data(make_ddim({2, 2, 3})); + EXPECT_EQ(p1, p2); + // set gpu_tensor a new dim with smaller size + // momery block is supposed to be unchanged + p2 = gpu_tensor.mutable_data(make_ddim({2, 2})); + EXPECT_EQ(p1, p2); + } } */ \ No newline at end of file -- GitLab From 9b1240456342670098a48884182879cef8789425 Mon Sep 17 00:00:00 2001 From: wuyi05 Date: Mon, 3 Jul 2017 18:48:06 +0800 Subject: [PATCH 0977/3256] update dockerfile --- Dockerfile | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index d10fad6370a..91bda8c7348 100644 --- a/Dockerfile +++ b/Dockerfile @@ -38,11 +38,12 @@ RUN apt-get update && \ RUN wget -O go.tgz https://storage.googleapis.com/golang/go1.8.1.linux-amd64.tar.gz && \ tar -C /usr/local -xzf go.tgz && \ mkdir /root/gopath && \ - rm go.tgz \ - curl https://glide.sh/get | sh + rm go.tgz ENV GOROOT=/usr/local/go GOPATH=/root/gopath # should not be in the same line with GOROOT definition, otherwise docker build could not find GOROOT. ENV PATH=${PATH}:${GOROOT}/bin +# install glide +RUN curl https://glide.sh/get | sh # git credential to skip password typing RUN git config --global credential.helper store -- GitLab From d054a5eef806d76458f9155bf5a4ffb98ba474d3 Mon Sep 17 00:00:00 2001 From: fengjiayi Date: Mon, 3 Jul 2017 19:08:27 +0800 Subject: [PATCH 0978/3256] re-submit --- paddle/framework/tensor.h | 2 +- paddle/framework/tensor_test.cc | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/paddle/framework/tensor.h b/paddle/framework/tensor.h index 73eedd7375e..f777661a1cd 100644 --- a/paddle/framework/tensor.h +++ b/paddle/framework/tensor.h @@ -30,7 +30,7 @@ class Tensor { template const T* data() const { PADDLE_ENFORCE(holder_ != nullptr, - "Tensor::data must be called after Tensor::mutable_data"); + "Tensor::data must be called after Tensor::mutable_data."); return static_cast(holder_->Ptr()); } diff --git a/paddle/framework/tensor_test.cc b/paddle/framework/tensor_test.cc index f76a31e921a..727d81f8d72 100644 --- a/paddle/framework/tensor_test.cc +++ b/paddle/framework/tensor_test.cc @@ -23,7 +23,7 @@ TEST(Tensor, ASSERT) { const double* p __attribute__((unused)) = cpu_tensor.data(); } catch (paddle::framework::EnforceNotMet err) { caught = true; - std::string msg = "Tensor::data must be called after Tensor::mutable_data"; + std::string msg = "Tensor::data must be called after Tensor::mutable_data."; const char* what = err.what(); for (size_t i = 0; i < msg.length(); ++i) { ASSERT_EQ(what[i], msg[i]); @@ -82,4 +82,4 @@ TEST(Tensor, MutableData) { EXPECT_EQ(p1, p2); } } -*/ \ No newline at end of file +*/ -- GitLab From 43df61505991fd2c9fa50d08e7cc0717b740ab45 Mon Sep 17 00:00:00 2001 From: wuyi05 Date: Mon, 3 Jul 2017 19:18:47 +0800 Subject: [PATCH 0979/3256] update dockerfile --- Dockerfile | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 91bda8c7348..ed5910d93b4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -38,12 +38,14 @@ RUN apt-get update && \ RUN wget -O go.tgz https://storage.googleapis.com/golang/go1.8.1.linux-amd64.tar.gz && \ tar -C /usr/local -xzf go.tgz && \ mkdir /root/gopath && \ + mkdir /root/gopath/bin && \ + mkdir /root/gopath/src && \ rm go.tgz ENV GOROOT=/usr/local/go GOPATH=/root/gopath # should not be in the same line with GOROOT definition, otherwise docker build could not find GOROOT. -ENV PATH=${PATH}:${GOROOT}/bin +ENV PATH=${PATH}:${GOROOT}/bin:${GOPATH}/bin # install glide -RUN curl https://glide.sh/get | sh +RUN curl -q https://glide.sh/get | sh # git credential to skip password typing RUN git config --global credential.helper store -- GitLab From 2d1f95de873542ae591b4575e14539f26945b162 Mon Sep 17 00:00:00 2001 From: fengjiayi Date: Mon, 3 Jul 2017 19:33:33 +0800 Subject: [PATCH 0980/3256] fix a compile error --- paddle/framework/tensor.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/paddle/framework/tensor.h b/paddle/framework/tensor.h index f777661a1cd..6a152f6a6d7 100644 --- a/paddle/framework/tensor.h +++ b/paddle/framework/tensor.h @@ -37,8 +37,10 @@ class Tensor { template ::value>::type* = nullptr> T* mutable_data(DDim dims, paddle::platform::Place place) { - if (holder_ == nullptr || holder_->Place() != place || - holder_->Size() < product(dims) * sizeof(T)) { + if (holder_ == nullptr || + !(holder_->Place() == + place) /* some versions of boost::variant don't have operator!= */ + || holder_->Size() < product(dims) * sizeof(T)) { holder_.reset(new PlaceholderImpl(place, product(dims) * sizeof(T))); } return static_cast(holder_->Ptr()); -- GitLab From 89110fd2660098bc949a1f13f7b53515e0c931a3 Mon Sep 17 00:00:00 2001 From: liaogang Date: Mon, 3 Jul 2017 19:51:32 +0800 Subject: [PATCH 0981/3256] ENH: Add useGpu in system allocator --- paddle/memory/detail/system_allocator.cc | 4 ++++ paddle/memory/detail/system_allocator.h | 4 +++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/paddle/memory/detail/system_allocator.cc b/paddle/memory/detail/system_allocator.cc index 2b0fbfa87e2..75a2c91ef93 100644 --- a/paddle/memory/detail/system_allocator.cc +++ b/paddle/memory/detail/system_allocator.cc @@ -60,6 +60,8 @@ void CPUAllocator::Free(void* p, size_t size, size_t index) { free(p); } +bool CPUAllocator::UseGpu() { return false; } + #ifndef PADDLE_ONLY_CPU void* GPUAllocator::Alloc(size_t& index, size_t size) { @@ -131,6 +133,8 @@ void GPUAllocator::Free(void* p, size_t size, size_t index) { } } +bool GPUAllocator::UseGpu() { return true; } + #endif // PADDLE_ONLY_CPU } // namespace detail diff --git a/paddle/memory/detail/system_allocator.h b/paddle/memory/detail/system_allocator.h index 7093c429671..f3bbfef8435 100644 --- a/paddle/memory/detail/system_allocator.h +++ b/paddle/memory/detail/system_allocator.h @@ -32,12 +32,14 @@ class SystemAllocator { virtual ~SystemAllocator() {} virtual void* Alloc(size_t& index, size_t size) = 0; virtual void Free(void* p, size_t size, size_t index) = 0; + virtual bool UseGpu() = 0; }; class CPUAllocator : public SystemAllocator { public: virtual void* Alloc(size_t& index, size_t size); virtual void Free(void* p, size_t size, size_t index); + virtual bool UseGpu(); }; #ifndef PADDLE_ONLY_CPU @@ -45,7 +47,7 @@ class GPUAllocator : public SystemAllocator { public: virtual void* Alloc(size_t& index, size_t size); virtual void Free(void* p, size_t size, size_t index); - + virtual bool UseGpu(); private: size_t gpu_alloc_size_ = 0; size_t fallback_alloc_size_ = 0; -- GitLab From 929f9cbdff08090a222495db7db601f164cebb8c Mon Sep 17 00:00:00 2001 From: liaogang Date: Mon, 3 Jul 2017 19:52:04 +0800 Subject: [PATCH 0982/3256] ENH: Add Metadata for memory block --- paddle/memory/detail/metadata.cc | 62 ++++++++++++++++++++++++++++++++ paddle/memory/detail/metadata.h | 53 +++++++++++++++++++++++++++ 2 files changed, 115 insertions(+) create mode 100644 paddle/memory/detail/metadata.cc create mode 100644 paddle/memory/detail/metadata.h diff --git a/paddle/memory/detail/metadata.cc b/paddle/memory/detail/metadata.cc new file mode 100644 index 00000000000..4607cd8512e --- /dev/null +++ b/paddle/memory/detail/metadata.cc @@ -0,0 +1,62 @@ +/* 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/memory/detail/metadata.h" + +#include + +namespace paddle { +namespace memory { +namespace detail { + +Metadata::Metadata(MemoryBlock::Type t, size_t i, size_t s, size_t ts, + MemoryBlock* l, MemoryBlock* r) + : type(t), + index(i), + size(s), + total_size(ts), + left_buddy(l), + right_buddy(r) {} + +template +inline void hash_combine(std::size_t& seed, const T& v) { + std::hash hasher; + seed ^= hasher(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2); +} + +inline size_t hash(const Metadata* metadata, size_t initial_seed) { + size_t seed = initial_seed; + + hash_combine(seed, (size_t)metadata->type); + hash_combine(seed, metadata->index); + hash_combine(seed, metadata->size); + hash_combine(seed, metadata->total_size); + hash_combine(seed, metadata->left_buddy); + hash_combine(seed, metadata->right_buddy); + + return seed; +} + +void Metadata::update_guards() { + guard_begin = hash(this, 1); + guard_end = hash(this, 2); +} + +bool Metadata::check_guards() const { + return guard_begin == hash(this, 1) && guard_end == hash(this, 2); +} + +} // namespace detail +} // namespace memory +} // namespace paddle diff --git a/paddle/memory/detail/metadata.h b/paddle/memory/detail/metadata.h new file mode 100644 index 00000000000..ddb826571b6 --- /dev/null +++ b/paddle/memory/detail/metadata.h @@ -0,0 +1,53 @@ +/* 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/memory/detail/memory_block.h" + +#include + +namespace paddle { +namespace memory { +namespace detail { + +class Metadata { + public: + Metadata(MemoryBlock::Type t, size_t i, size_t s, size_t ts, MemoryBlock* l, + MemoryBlock* r); + + public: + /*! \brief Update the guards when metadata is changed */ + void update_guards(); + + /*! \brief Check consistency to previous modification */ + bool check_guards() const; + + public: + // TODO(gangliao): compress this + // clang-format off + size_t guard_begin = 0; + MemoryBlock::Type type = MemoryBlock::INVALID_CHUNK; + size_t index = 0; + size_t size = 0; + size_t total_size = 0; + MemoryBlock* left_buddy = nullptr; + MemoryBlock* right_buddy = nullptr; + size_t guard_end = 0; + // clang-format on +}; + +} // namespace detail +} // namespace memory +} // namespace paddle -- GitLab From bbd3eab7ee88f02131edb41738a966aa0f1a0e88 Mon Sep 17 00:00:00 2001 From: liaogang Date: Mon, 3 Jul 2017 19:54:32 +0800 Subject: [PATCH 0983/3256] ENH: Add Alloc for buddy Allocator * Free will be added soon --- paddle/memory/detail/buddy_allocator.cc | 157 ++++++++++++++++++++++-- paddle/memory/detail/buddy_allocator.h | 88 +++++++++---- 2 files changed, 209 insertions(+), 36 deletions(-) diff --git a/paddle/memory/detail/buddy_allocator.cc b/paddle/memory/detail/buddy_allocator.cc index ebe680f5eea..2462ba084b9 100644 --- a/paddle/memory/detail/buddy_allocator.cc +++ b/paddle/memory/detail/buddy_allocator.cc @@ -12,22 +12,161 @@ See the License for the specific language governing permissions and limitations under the License. */ -#pragma once - #include "paddle/memory/detail/buddy_allocator.h" +#include "glog/logging.h" namespace paddle { namespace memory { namespace detail { -BuddyAllocator::BuddyAllocator(size_t pool_size, size_t max_pools, - SystemAllocator* system_allocator) - : pool_size_(pool_size), - max_pools_(max_pools), - system_allocator_(system_allocator) { - PADDLE_ASSERT(pool_size > 0); - PADDLE_ASSERT(max_pools > 0); +BuddyAllocator::BuddyAllocator(SystemAllocator* system_allocator, + size_t min_chunk_size, size_t max_chunk_size) { + PADDLE_ASSERT(min_chunk_size > 0); + PADDLE_ASSERT(max_chunk_size > 0); PADDLE_ASSERT(system_allocator != nullptr); + + system_allocator_ = std::move(system_allocator); + min_chunk_size_ = min_chunk_size; + max_chunk_size_ = max_chunk_size; +} + +inline size_t align(size_t size, size_t alignment) { + size_t remaining = size % alignment; + return remaining == 0 ? size : size + (alignment - remaining); +} + +void* BuddyAllocator::Alloc(size_t unaligned_size) { + // adjust allocation alignment + size_t size = align(unaligned_size + sizeof(Metadata), min_chunk_size_); + + // acquire the allocator lock + std::lock_guard lock(mutex_); + + DLOG(INFO) << "Allocate " << unaligned_size << " bytes from chunk size " + << size; + + // if the allocation is huge, send directly to the system allocator + if (size > max_chunk_size_) { + DLOG(INFO) << "Allocate from system allocator."; + + return SystemAlloc(size); + } + + // query and allocate from the existing chunk + auto it = FindExistChunk(size); + + // refill the pool if failure + if (it == pool_.end()) { + it = RefillPool(); + } else { + DLOG(INFO) << " Allocation from existing memory block " << std::get<2>(*it) + << " at address " + << reinterpret_cast(std::get<2>(*it))->data(); + } + + // if still failure, fail fatally + if (it == pool_.end()) { + return nullptr; + } + + total_used_ += size; + total_free_ -= size; + + // split the allocation and return data for use + return reinterpret_cast(SplitToAlloc(it, size))->data(); +} + +void* BuddyAllocator::SystemAlloc(size_t size) { + size_t index = 0; + void* p = system_allocator_->Alloc(index, size); + + DLOG(INFO) << "Allocated " << p << " from system allocator."; + + if (p == nullptr) return nullptr; + + static_cast(p)->init(cache_, MemoryBlock::HUGE_CHUNK, index, + size, nullptr, nullptr); + + return static_cast(p)->data(); +} + +BuddyAllocator::PoolSet::iterator BuddyAllocator::RefillPool() { +#ifndef PADDLE_ONLY_CPU + if (system_allocator_->UseGpu()) { + if ((total_used_ + total_free_) == 0) { + // Compute the maximum allocation size for the first allocation. + max_chunk_size_ = platform::GpuMaxChunkSize(); + } + } +#endif // PADDLE_ONLY_CPU + + // Allocate a new maximum sized block + size_t index = 0; + void* p = system_allocator_->Alloc(index, max_chunk_size_); + + if (p == nullptr) return pool_.end(); + + DLOG(INFO) << " Creating and inserting new block " << p + << " from system allocator"; + + static_cast(p)->init(cache_, MemoryBlock::FREE_CHUNK, index, + max_chunk_size_, nullptr, nullptr); + + total_free_ += max_chunk_size_; + + // dump the block into pool + return pool_.insert({index, max_chunk_size_, p}).first; +} + +BuddyAllocator::PoolSet::iterator BuddyAllocator::FindExistChunk(size_t size) { + size_t index = 0; + + while (1) { + auto it = pool_.lower_bound({index, size, nullptr}); + if (it == pool_.end()) return it; + + if (std::get<0>(*it) > index) { + if (std::get<1>(*it) >= size) { + return it; + } + + index = std::get<0>(*it); + continue; + } + return it; + } +} + +void* BuddyAllocator::SplitToAlloc(BuddyAllocator::PoolSet::iterator it, + size_t size) { + auto block = static_cast(std::get<2>(*it)); + + pool_.erase(it); + + DLOG(INFO) << " Split block (" << block << ", " << block->total_size(cache_) + << ") into"; + + block->split(cache_, size); + + DLOG(INFO) << " Left block (" << block << ", " << block->total_size(cache_) + << ")"; + + block->set_type(cache_, MemoryBlock::ARENA_CHUNK); + + // the rest of memory if exist + if (block->has_right_buddy(cache_)) { + if (block->right_buddy(cache_)->type(cache_) == MemoryBlock::FREE_CHUNK) { + DLOG(INFO) << " Insert right block (" << block->right_buddy(cache_) + << ", " << block->right_buddy(cache_)->total_size(cache_) + << ")"; + + pool_.insert({block->right_buddy(cache_)->index(cache_), + block->right_buddy(cache_)->total_size(cache_), + block->right_buddy(cache_)}); + } + } + + return block; } } // namespace detail diff --git a/paddle/memory/detail/buddy_allocator.h b/paddle/memory/detail/buddy_allocator.h index 82e6aaedc71..38bedc9a183 100644 --- a/paddle/memory/detail/buddy_allocator.h +++ b/paddle/memory/detail/buddy_allocator.h @@ -15,9 +15,15 @@ #pragma once #include "paddle/memory/detail/system_allocator.h" +#include "paddle/memory/detail/metadata.h" +#include "paddle/platform/assert.h" +#include "paddle/platform/cpu_info.h" +#include "paddle/platform/gpu_info.h" +#include #include #include +#include namespace paddle { namespace memory { @@ -25,55 +31,83 @@ namespace detail { class BuddyAllocator { public: - BuddyAllocator(size_t pool_size, size_t max_pools, - SystemAllocator* system_allocator); + BuddyAllocator(SystemAllocator* system_allocator, size_t min_chunk_size, + size_t max_chunk_size); + ~BuddyAllocator(); - void* Alloc(size_t size); + public: + void* Alloc(size_t unaligned_size); void Free(void*); size_t Used(); + public: + // Disable copy and assignment. + BuddyAllocator(const BuddyAllocator&) = delete; + BuddyAllocator& operator=(const BuddyAllocator&) = delete; + private: - struct Block { - size_t size_; - Block* left_; // left buddy - Block* right_; // right buddy - }; + // Tuple type: allocator index, memory size, memory address + using IndexSizeAddress = std::tuple; + using PoolSet = std::set; - // Initially, there is only one pool. If a Alloc founds not enough - // memory from that pool, and there has not been max_num_pools_, - // create a new pool by calling system_allocator_.Alloc(pool_size_). - std::vector pools_; + /*! \brief Allocate fixed-size memory from system */ + void* SystemAlloc(size_t size); - size_t pool_size_; // the size of each pool; - size_t max_num_pools_; // the size of all pools; + /*! \brief If existing chunks are not suitable, refill pool */ + PoolSet::iterator RefillPool(); - SystemAllocator* system_allocator_; + /** + * \brief Find the suitable chunk from existing pool + * + * \param it pool iterator which contains suitable block. + * \param size the size of allocation. + */ + void* SplitToAlloc(PoolSet::iterator it, size_t size); - std::mutex mutex_; + /*! \brief Find the existing chunk which used to allocation */ + PoolSet::iterator FindExistChunk(size_t size); - // Disable copy and assignment. - BuddyAllocator(const BuddyAllocator&) = delete; - BuddyAllocator& operator=(const BuddyAllocator&) = delete; + private: + size_t total_used_ = 0; // the total size of used memory + size_t total_free_ = 0; // the total size of free memory + + size_t min_chunk_size_; // the minimum size of each chunk + size_t max_chunk_size_; // the maximum size of each chunk + + private: + PoolSet pool_; + + private: + // Unify the metadata format between GPU and CPU allocations + using MetadataCache = std::unordered_map; + MetadataCache cache_; + + private: + SystemAllocator* system_allocator_; + std::mutex mutex_; }; -BuddyAllocator* GetCPUBuddyAllocator() { - static BuddyAllocator* a = nullptr; +BuddyAllocator* GetCPUBuddyAllocator() { + static BuddyAllocator* a = nullptr; if (a == nullptr) { - a = new BuddyAllocator(); + a = new BuddyAllocator(new CPUAllocator, platform::CpuMinChunkSize(), + platform::CpuMaxChunkSize()); } return a; } #ifndef PADDLE_ONLY_CPU // The following code are for CUDA. -BuddyAllocator* GetGPUBuddyAllocator(int gpu_id) { - static BuddyAllocator** as = NULL; +BuddyAllocator* GetGPUBuddyAllocator(int gpu_id) { + static BuddyAllocator** as = NULL; if (as == NULL) { - int gpu_num = platform::GetDeviceCount(); - as = new BuddyAllocator*[gpu_num]; + int gpu_num = platform::GpuDeviceCount(); + as = new BuddyAllocator*[gpu_num]; for (int gpu = 0; gpu < gpu_num; gpu++) { - as[gpu] = new BuddyAllocator(); + as[gpu] = + new BuddyAllocator(new GPUAllocator, platform::GpuMinChunkSize(), + platform::GpuMaxChunkSize()); } } return as[gpu_id]; -- GitLab From eefcfed337899b77e56daa12470c8a9a69c69502 Mon Sep 17 00:00:00 2001 From: wuyi05 Date: Mon, 3 Jul 2017 19:58:59 +0800 Subject: [PATCH 0984/3256] fix ci error --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7db8a97381b..5349f59805b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -97,6 +97,7 @@ include(external/warpctc) # download, build, install warpctc include(external/any) # download libn::any include(external/eigen) # download eigen3 +include(cudnn) # set cudnn libraries, must before configure include(configure) # add paddle env configuration include(generic) # simplify cmake module include(package) # set paddle packages @@ -105,7 +106,6 @@ include(ccache) # set ccache for compilation include(util) # set unittest and link libs include(rdma) # set rdma libraries include(flags) # set paddle compile flags -include(cudnn) # set cudnn libraries include(version) # set PADDLE_VERSION include(coveralls) # set code coverage -- GitLab From e48e21da2b2522e4a9e1bca589d68eb02a419fb0 Mon Sep 17 00:00:00 2001 From: fengjiayi Date: Mon, 3 Jul 2017 20:14:30 +0800 Subject: [PATCH 0985/3256] remove unnecessary include --- paddle/framework/tensor.h | 1 - 1 file changed, 1 deletion(-) diff --git a/paddle/framework/tensor.h b/paddle/framework/tensor.h index 6a152f6a6d7..ce5d98b04e6 100644 --- a/paddle/framework/tensor.h +++ b/paddle/framework/tensor.h @@ -16,7 +16,6 @@ limitations under the License. */ #include #include -#include #include "paddle/framework/ddim.h" #include "paddle/framework/enforce.h" #include "paddle/memory/memory.h" -- GitLab From 3ba7a738f3f3e77240d026db57692d66bc9481ed Mon Sep 17 00:00:00 2001 From: qijun Date: Mon, 3 Jul 2017 20:37:42 +0800 Subject: [PATCH 0986/3256] add dynamic_load --- paddle/platform/cublas.h | 87 +++++++++++++++++ paddle/platform/cudnn.h | 114 ++++++++++++++++++++++ paddle/platform/curand.h | 42 ++++++++ paddle/platform/dynamic_loader.cc | 157 ++++++++++++++++++++++++++++++ paddle/platform/dynamic_loader.h | 63 ++++++++++++ 5 files changed, 463 insertions(+) create mode 100644 paddle/platform/cublas.h create mode 100644 paddle/platform/cudnn.h create mode 100644 paddle/platform/curand.h create mode 100644 paddle/platform/dynamic_loader.cc create mode 100644 paddle/platform/dynamic_loader.h diff --git a/paddle/platform/cublas.h b/paddle/platform/cublas.h new file mode 100644 index 00000000000..70c97133252 --- /dev/null +++ b/paddle/platform/cublas.h @@ -0,0 +1,87 @@ +#include +#include "paddle/platform/dynamic_loader.h" + +namespace paddle { +namespace dyload { +namespace dynload { + +std::once_flag cublas_dso_flag; +void *cublas_dso_handle = nullptr; + +/** + * The following macro definition can generate structs + * (for each function) to dynamic load cublas routine + * via operator overloading. + * + * note: default dynamic linked libs + */ +#ifdef PADDLE_USE_DSO +#define DYNAMIC_LOAD_CUBLAS_WRAP(__name) \ + struct DynLoad__##__name { \ + template \ + cublasStatus_t operator()(Args... args) { \ + typedef cublasStatus_t (*cublasFunc)(Args...); \ + std::call_once(cublas_dso_flag, GetCublasDsoHandle, &cublas_dso_handle); \ + void *p_##__name = dlsym(cublas_dso_handle, #__name); \ + return reinterpret_cast(p_##__name)(args...); \ + } \ + } __name; // struct DynLoad__##__name +#else +#define DYNAMIC_LOAD_CUBLAS_WRAP(__name) \ + struct DynLoad__##__name { \ + template \ + cublasStatus_t operator()(Args... args) { \ + return __name(args...); \ + } \ + } __name; // struct DynLoad__##__name +#endif + +#define DYNAMIC_LOAD_CUBLAS_V2_WRAP(__name) DYNAMIC_LOAD_CUBLAS_WRAP(__name) + +// include all needed cublas functions in HPPL +// clang-format off +#define CUBLAS_BLAS_ROUTINE_EACH(__macro) \ + __macro(cublasSgemv) \ + __macro(cublasDgemv) \ + __macro(cublasSgemm) \ + __macro(cublasDgemm) \ + __macro(cublasSgeam) \ + __macro(cublasDgeam) \ + +DYNAMIC_LOAD_CUBLAS_V2_WRAP(cublasCreate) +DYNAMIC_LOAD_CUBLAS_V2_WRAP(cublasDestroy) +DYNAMIC_LOAD_CUBLAS_V2_WRAP(cublasSetStream) +DYNAMIC_LOAD_CUBLAS_V2_WRAP(cublasSetPointerMode) +DYNAMIC_LOAD_CUBLAS_V2_WRAP(cublasGetPointerMode) +DYNAMIC_LOAD_CUBLAS_WRAP(cublasSgemmBatched) +DYNAMIC_LOAD_CUBLAS_WRAP(cublasDgemmBatched) +DYNAMIC_LOAD_CUBLAS_WRAP(cublasCgemmBatched) +DYNAMIC_LOAD_CUBLAS_WRAP(cublasZgemmBatched) +DYNAMIC_LOAD_CUBLAS_WRAP(cublasSgetrfBatched) +DYNAMIC_LOAD_CUBLAS_WRAP(cublasSgetriBatched) +DYNAMIC_LOAD_CUBLAS_WRAP(cublasDgetrfBatched) +DYNAMIC_LOAD_CUBLAS_WRAP(cublasDgetriBatched) +CUBLAS_BLAS_ROUTINE_EACH(DYNAMIC_LOAD_CUBLAS_V2_WRAP) + +#undef DYNAMIC_LOAD_CUBLAS_WRAP +#undef DYNAMIC_LOAD_CUBLAS_V2_WRAP +#undef CUBLAS_BLAS_ROUTINE_EACH + +} /* namespace dynload */ + +// clang-format on +#ifndef PADDLE_TYPE_DOUBLE +#define CUBLAS_GEAM dynload::cublasSgeam +#define CUBLAS_GEMV dynload::cublasSgemv +#define CUBLAS_GEMM dynload::cublasSgemm +#define CUBLAS_GETRF dynload::cublasSgetrfBatched +#define CUBLAS_GETRI dynload::cublasSgetriBatched +#else +#define CUBLAS_GEAM dynload::cublasDgeam +#define CUBLAS_GEMV dynload::cublasDgemv +#define CUBLAS_GEMM dynload::cublasDgemm +#define CUBLAS_GETRF dynload::cublasDgetrfBatched +#define CUBLAS_GETRI dynload::cublasDgetriBatched +#endif +} // namespace dyload +} // namespace paddle diff --git a/paddle/platform/cudnn.h b/paddle/platform/cudnn.h new file mode 100644 index 00000000000..ab878cd5552 --- /dev/null +++ b/paddle/platform/cudnn.h @@ -0,0 +1,114 @@ +#include +#include "paddle/platform/dynamic_loader.h" + +namespace paddle { +namespace dyload { + +std::once_flag cudnn_dso_flag; +void* cudnn_dso_handle = nullptr; + +#ifdef PADDLE_USE_DSO + +#define DYNAMIC_LOAD_CUDNN_WRAP(__name) \ + struct DynLoad__##__name { \ + template \ + auto operator()(Args... args) -> decltype(__name(args...)) { \ + using cudnn_func = decltype(__name(args...)) (*)(Args...); \ + std::call_once(cudnn_dso_flag, GetCudnnDsoHandle, &cudnn_dso_handle); \ + void* p_##__name = dlsym(cudnn_dso_handle, #__name); \ + return reinterpret_cast(p_##__name)(args...); \ + } \ + } __name; /* struct DynLoad__##__name */ + +#else + +#define DYNAMIC_LOAD_CUDNN_WRAP(__name) \ + struct DynLoad__##__name { \ + template \ + auto operator()(Args... args) -> decltype(__name(args...)) { \ + return __name(args...); \ + } \ + } __name; /* struct DynLoad__##__name */ + +#endif + +/** + * include all needed cudnn functions in HPPL + * different cudnn version has different interfaces + **/ +// clang-format off +#define CUDNN_DNN_ROUTINE_EACH(__macro) \ + __macro(cudnnSetTensor4dDescriptor) \ + __macro(cudnnSetTensor4dDescriptorEx) \ + __macro(cudnnGetConvolutionNdForwardOutputDim) \ + __macro(cudnnGetConvolutionForwardAlgorithm) \ + __macro(cudnnCreateTensorDescriptor) \ + __macro(cudnnDestroyTensorDescriptor) \ + __macro(cudnnCreateFilterDescriptor) \ + __macro(cudnnSetFilter4dDescriptor) \ + __macro(cudnnSetPooling2dDescriptor) \ + __macro(cudnnDestroyFilterDescriptor) \ + __macro(cudnnCreateConvolutionDescriptor) \ + __macro(cudnnCreatePoolingDescriptor) \ + __macro(cudnnDestroyPoolingDescriptor) \ + __macro(cudnnSetConvolution2dDescriptor) \ + __macro(cudnnDestroyConvolutionDescriptor) \ + __macro(cudnnCreate) \ + __macro(cudnnDestroy) \ + __macro(cudnnSetStream) \ + __macro(cudnnActivationForward) \ + __macro(cudnnConvolutionForward) \ + __macro(cudnnConvolutionBackwardBias) \ + __macro(cudnnGetConvolutionForwardWorkspaceSize) \ + __macro(cudnnTransformTensor) \ + __macro(cudnnPoolingForward) \ + __macro(cudnnPoolingBackward) \ + __macro(cudnnSoftmaxBackward) \ + __macro(cudnnSoftmaxForward) \ + __macro(cudnnGetVersion) \ + __macro(cudnnGetErrorString) +CUDNN_DNN_ROUTINE_EACH(DYNAMIC_LOAD_CUDNN_WRAP) + +#define CUDNN_DNN_ROUTINE_EACH_R2(__macro) \ + __macro(cudnnAddTensor) \ + __macro(cudnnConvolutionBackwardData) \ + __macro(cudnnConvolutionBackwardFilter) +CUDNN_DNN_ROUTINE_EACH_R2(DYNAMIC_LOAD_CUDNN_WRAP) + +// APIs available after R3: +#if CUDNN_VERSION >= 3000 +#define CUDNN_DNN_ROUTINE_EACH_AFTER_R3(__macro) \ + __macro(cudnnGetConvolutionBackwardFilterWorkspaceSize) \ + __macro(cudnnGetConvolutionBackwardDataAlgorithm) \ + __macro(cudnnGetConvolutionBackwardFilterAlgorithm) \ + __macro(cudnnGetConvolutionBackwardDataWorkspaceSize) +CUDNN_DNN_ROUTINE_EACH_AFTER_R3(DYNAMIC_LOAD_CUDNN_WRAP) +#undef CUDNN_DNN_ROUTINE_EACH_AFTER_R3 +#endif + + +// APIs available after R4: +#if CUDNN_VERSION >= 4007 +#define CUDNN_DNN_ROUTINE_EACH_AFTER_R4(__macro) \ + __macro(cudnnBatchNormalizationForwardTraining) \ + __macro(cudnnBatchNormalizationForwardInference) \ + __macro(cudnnBatchNormalizationBackward) +CUDNN_DNN_ROUTINE_EACH_AFTER_R4(DYNAMIC_LOAD_CUDNN_WRAP) +#undef CUDNN_DNN_ROUTINE_EACH_AFTER_R4 +#endif + +// APIs in R5 +#if CUDNN_VERSION >= 5000 +#define CUDNN_DNN_ROUTINE_EACH_R5(__macro) \ + __macro(cudnnCreateActivationDescriptor) \ + __macro(cudnnSetActivationDescriptor) \ + __macro(cudnnGetActivationDescriptor) \ + __macro(cudnnDestroyActivationDescriptor) +CUDNN_DNN_ROUTINE_EACH_R5(DYNAMIC_LOAD_CUDNN_WRAP) +#undef CUDNN_DNN_ROUTINE_EACH_R5 +#endif + +#undef CUDNN_DNN_ROUTINE_EACH +// clang-format on +} // namespace dyload +} // namespace paddle diff --git a/paddle/platform/curand.h b/paddle/platform/curand.h new file mode 100644 index 00000000000..692c024e6ec --- /dev/null +++ b/paddle/platform/curand.h @@ -0,0 +1,42 @@ +#include +#include "paddle/platform/dynamic_loader.h" + +namespace paddle { +namespace dyload { +#ifdef PADDLE_USE_DSO +#define DYNAMIC_LOAD_CURAND_WRAP(__name) \ + struct DynLoad__##__name { \ + template \ + curandStatus_t operator()(Args... args) { \ + typedef curandStatus_t (*curandFunc)(Args...); \ + std::call_once(curand_dso_flag, GetCurandDsoHandle, &curand_dso_handle); \ + void *p_##__name = dlsym(curand_dso_handle, #__name); \ + return reinterpret_cast(p_##__name)(args...); \ + } \ + } __name; /* struct DynLoad__##__name */ +#else +#define DYNAMIC_LOAD_CURAND_WRAP(__name) \ + struct DynLoad__##__name { \ + template \ + curandStatus_t operator()(Args... args) { \ + return __name(args...); \ + } \ + } __name; /* struct DynLoad__##__name */ +#endif + +/* include all needed curand functions in HPPL */ +// clang-format off +#define CURAND_RAND_ROUTINE_EACH(__macro) \ + __macro(curandCreateGenerator) \ + __macro(curandSetStream) \ + __macro(curandSetPseudoRandomGeneratorSeed)\ + __macro(curandGenerateUniform) \ + __macro(curandGenerateUniformDouble) +// clang-format on + +CURAND_RAND_ROUTINE_EACH(DYNAMIC_LOAD_CURAND_WRAP) + +#undef CURAND_RAND_ROUTINE_EACH +#undef DYNAMIC_LOAD_CURAND_WRAP +} +} // namespace paddle diff --git a/paddle/platform/dynamic_loader.cc b/paddle/platform/dynamic_loader.cc new file mode 100644 index 00000000000..9036eaf6426 --- /dev/null +++ b/paddle/platform/dynamic_loader.cc @@ -0,0 +1,157 @@ +/* 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 "DynamicLoader.h" +#include "Logging.h" + +DEFINE_string(cudnn_dir, "", + "Specify path for loading libcudnn.so. For instance, " + "/usr/local/cudnn/lib. If empty [default], dlopen " + "will search cudnn from LD_LIBRARY_PATH"); + +DEFINE_string(cuda_dir, "", + "Specify path for loading cuda library, such as libcublas, " + "libcurand. For instance, /usr/local/cuda/lib64. If default, " + "dlopen will search cuda from LD_LIBRARY_PATH"); + +DEFINE_string(warpctc_dir, "", "Specify path for loading libwarpctc.so."); + +DEFINE_string(lapack_dir, "", "Specify path for loading liblapack.so."); + +static inline std::string join(const std::string& part1, + const std::string& part2) { + // directory separator + const char sep = '/'; + if (!part2.empty() && part2.front() == sep) { + return part2; + } + std::string ret; + ret.reserve(part1.size() + part2.size() + 1); + ret = part1; + if (!ret.empty() && ret.back() != sep) { + ret += sep; + } + ret += part2; + return ret; +} + +static inline void GetDsoHandleFromDefaultPath(std::string& dso_path, + void** dso_handle, + int dynload_flags) { + VLOG(3) << "Try to find library: " << dso_path + << " from default system path."; + // default search from LD_LIBRARY_PATH/DYLD_LIBRARY_PATH + *dso_handle = dlopen(dso_path.c_str(), dynload_flags); + +// DYLD_LIBRARY_PATH is disabled after Mac OS 10.11 to +// bring System Integrity Projection (SIP), if dso_handle +// is null, search from default package path in Mac OS. +#if defined(__APPLE__) || defined(__OSX__) + if (nullptr == *dso_handle) { + dso_path = join("/usr/local/cuda/lib/", dso_path); + *dso_handle = dlopen(dso_path.c_str(), dynload_flags); + if (nullptr == *dso_handle) { + if (dso_path == "libcudnn.dylib") { + LOG(FATAL) + << "Note: [Recommend] copy cudnn into /usr/local/cuda/ \n" // NOLINT + << "For instance, sudo tar -xzf " + "cudnn-7.5-osx-x64-v5.0-ga.tgz -C " // NOLINT + << "/usr/local \n sudo chmod a+r " + "/usr/local/cuda/include/cudnn.h " // NOLINT + << "/usr/local/cuda/lib/libcudnn*"; + } + } + } +#endif +} + +static inline void GetDsoHandleFromSearchPath(const std::string& search_root, + const std::string& dso_name, + void** dso_handle) { + int dynload_flags = RTLD_LAZY | RTLD_LOCAL; + *dso_handle = nullptr; + + std::string dlPath = dso_name; + if (search_root.empty()) { + GetDsoHandleFromDefaultPath(dlPath, dso_handle, dynload_flags); + } else { + // search xxx.so from custom path + dlPath = join(search_root, dso_name); + *dso_handle = dlopen(dlPath.c_str(), dynload_flags); + // if not found, search from default path + if (nullptr == *dso_handle) { + LOG(WARNING) << "Failed to find dynamic library: " << dlPath << " (" + << dlerror() << ")"; + dlPath = dso_name; + GetDsoHandleFromDefaultPath(dlPath, dso_handle, dynload_flags); + } + } + + CHECK(nullptr != *dso_handle) << "Failed to find dynamic library: " << dlPath + << " (" << dlerror() << ") \n" + << "Please specify its path correctly using " + "following ways: \n" + + << "Method. set environment variable " + "LD_LIBRARY_PATH on Linux or " + << "DYLD_LIBRARY_PATH on Mac OS. \n" + << "For instance, issue command: export " + "LD_LIBRARY_PATH=... \n" + + << "Note: After Mac OS 10.11, using the " + "DYLD_LIBRARY_PATH is impossible " + << "unless System Integrity Protection (SIP) " + "is disabled."; +} + +void GetCublasDsoHandle(void** dso_handle) { +#if defined(__APPLE__) || defined(__OSX__) + GetDsoHandleFromSearchPath(FLAGS_cuda_dir, "libcublas.dylib", dso_handle); +#else + GetDsoHandleFromSearchPath(FLAGS_cuda_dir, "libcublas.so", dso_handle); +#endif +} + +void GetCudnnDsoHandle(void** dso_handle) { +#if defined(__APPLE__) || defined(__OSX__) + GetDsoHandleFromSearchPath(FLAGS_cudnn_dir, "libcudnn.dylib", dso_handle); +#else + GetDsoHandleFromSearchPath(FLAGS_cudnn_dir, "libcudnn.so", dso_handle); +#endif +} + +void GetCurandDsoHandle(void** dso_handle) { +#if defined(__APPLE__) || defined(__OSX__) + GetDsoHandleFromSearchPath(FLAGS_cuda_dir, "libcurand.dylib", dso_handle); +#else + GetDsoHandleFromSearchPath(FLAGS_cuda_dir, "libcurand.so", dso_handle); +#endif +} + +void GetWarpCTCDsoHandle(void** dso_handle) { +#if defined(__APPLE__) || defined(__OSX__) + GetDsoHandleFromSearchPath(FLAGS_warpctc_dir, "libwarpctc.dylib", dso_handle); +#else + GetDsoHandleFromSearchPath(FLAGS_warpctc_dir, "libwarpctc.so", dso_handle); +#endif +} + +void GetLapackDsoHandle(void** dso_handle) { +#if defined(__APPLE__) || defined(__OSX__) + GetDsoHandleFromSearchPath(FLAGS_lapack_dir, "liblapacke.dylib", dso_handle); +#else + GetDsoHandleFromSearchPath(FLAGS_lapack_dir, "liblapacke.so", dso_handle); +#endif +} diff --git a/paddle/platform/dynamic_loader.h b/paddle/platform/dynamic_loader.h new file mode 100644 index 00000000000..9b5ad21724a --- /dev/null +++ b/paddle/platform/dynamic_loader.h @@ -0,0 +1,63 @@ +/* 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. */ + +#ifndef DYNAMIC_LOAD_H_ +#define DYNAMIC_LOAD_H_ + +#include +#include +#include +#include + +/** + * @brief load the DSO of CUBLAS + * + * @param **dso_handle dso handler + * + */ +void GetCublasDsoHandle(void** dso_handle); + +/** + * @brief load the DSO of CUDNN + * + * @param **dso_handle dso handler + * + */ +void GetCudnnDsoHandle(void** dso_handle); + +/** + * @brief load the DSO of CURAND + * + * @param **dso_handle dso handler + * + */ +void GetCurandDsoHandle(void** dso_handle); + +/** + * @brief load the DSO of warp-ctc + * + * @param **dso_handle dso handler + * + */ +void GetWarpCTCDsoHandle(void** dso_handle); + +/** + * @brief load the DSO of lapack + * + * @param **dso_handle dso handler + * + */ +void GetLapackDsoHandle(void** dso_handle); + +#endif // DYNAMIC_LOAD_H_ -- GitLab From a30754b05e1ef58b5803c3d9996ed0cc69100ac5 Mon Sep 17 00:00:00 2001 From: qijun Date: Mon, 3 Jul 2017 20:41:31 +0800 Subject: [PATCH 0987/3256] test device_context --- paddle/platform/CMakeLists.txt | 3 + paddle/platform/device_context.h | 166 +++++++++++++++++++++++++ paddle/platform/device_context_test.cu | 29 +++++ 3 files changed, 198 insertions(+) create mode 100644 paddle/platform/device_context.h create mode 100644 paddle/platform/device_context_test.cu diff --git a/paddle/platform/CMakeLists.txt b/paddle/platform/CMakeLists.txt index c7d7b14518e..c95b54a4dfa 100644 --- a/paddle/platform/CMakeLists.txt +++ b/paddle/platform/CMakeLists.txt @@ -2,3 +2,6 @@ nv_test(cuda_test SRCS cuda_test.cu) cc_library(place SRCS place.cc) cc_test(place_test SRCS place_test.cc DEPS place glog gflags) + +cc_library(dynamic_loader SRCS dynamic_loader.cc) +nv_test(device_context_test SRCS device_context_test.cu DEPS place dynamic_loader glog gflags) diff --git a/paddle/platform/device_context.h b/paddle/platform/device_context.h new file mode 100644 index 00000000000..f95aac4a360 --- /dev/null +++ b/paddle/platform/device_context.h @@ -0,0 +1,166 @@ +/* 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 + +#ifndef PADDLE_ONLY_CPU +#include "paddle/platform/cublas.h" +#include "paddle/platform/cuda.h" +#include "paddle/platform/cudnn.h" +#include "paddle/platform/curand.h" +#define EIGEN_USE_GPU +#endif + +#include "paddle/framework/enforce.h" +#include "paddle/platform/place.h" +#include "unsupported/Eigen/CXX11/Tensor" + +namespace paddle { +namespace platform { + +class DeviceContext { + public: + virtual ~DeviceContext() {} +}; + +class CpuDeviceContext : public DeviceContext { + Eigen::DefaultDevice eigen_device() { + if (!eigen_device_) { + eigen_device_ = new Eigen::DefaultDevice(); + } + return *eigen_device_; + } + + private: + Eigen::DefaultDevice* eigen_device_{nullptr}; +}; + +#ifndef PADDLE_ONLY_CPU +class DeviceGuard { + public: + explicit DeviceGuard(GPUPlace new_place) : previous_(GetCurrentDeviceId()) { + if (previous_ != new_place) { + paddle::platform::SetDeviceId(new_place.device); + } + } + + ~DeviceGuard() { paddle::platform::SetDeviceId(previous_.device); } + + private: + GPUPlace previous_; +}; + +class CudaDeviceContext : public DeviceContext { + public: + explicit CudaDeviceContext(const GPUPlace gpu_place) : gpu_place_(gpu_place) { + DeviceGuard guard(gpu_place_); + paddle::platform::throw_on_error(cudaStreamCreate(&stream_), + "cudaStreamCreate failed"); + eigen_stream_ = new Eigen::CudaStreamDevice(&stream_); + eigen_device_ = new Eigen::GpuDevice(eigen_stream_); + } + + void Wait() { + paddle::platform::throw_on_error(cudaStreamSynchronize(stream_), + "cudaStreamSynchronize failed"); + } + + cudaStream_t stream() { return stream_; } + + Eigen::GpuDevice eigen_device() { return *eigen_device_; } + + cublasHandle_t cublas_handle() { + if (!blas_handle_) { + DeviceGuard guard(gpu_place_); + PADDLE_ENFORCE(cublasCreate(&blas_handle_) == CUBLAS_STATUS_SUCCESS, + "cublasCreate failed"); + PADDLE_ENFORCE( + cublasSetStream(blas_handle_, stream_) == CUBLAS_STATUS_SUCCESS, + "cublasSetStream failed"); + } + return blas_handle_; + } + + cudnnHandle_t cudnn_handle() { + if (!dnn_handle_) { + DeviceGuard guard(gpu_place_); + PADDLE_ENFORCE(cudnnCreate(&dnn_handle_) == CUDNN_STATUS_SUCCESS, + "cudnnCreate failed"); + PADDLE_ENFORCE( + cudnnSetStream(dnn_handle_, stream_) == CUDNN_STATUS_SUCCESS, + "cudnnSetStream failed"); + } + return dnn_handle_; + } + + curandGenerator_t curand_generator() { + if (!rand_generator_) { + DeviceGuard guard(gpu_place_); + PADDLE_ENFORCE( + curandCreateGenerator(&rand_generator_, CURAND_RNG_PSEUDO_DEFAULT) == + CURAND_STATUS_SUCCESS, + "curandCreateGenerator failed"); + PADDLE_ENFORCE( + curandSetPseudoRandomGeneratorSeed(rand_generator_, random_seed_) == + CURAND_STATUS_SUCCESS, + "curandSetPseudoRandomGeneratorSeed failed"); + PADDLE_ENFORCE( + curandSetStream(rand_generator_, stream_) == CURAND_STATUS_SUCCESS, + "curandSetStream failed"); + } + return rand_generator_; + } + + ~CudaDeviceContext() { + Wait(); + if (blas_handle_) { + PADDLE_ENFORCE(cublasDestroy(blas_handle_) == CUBLAS_STATUS_SUCCESS, + "cublasDestroy failed"); + } + + if (dnn_handle_) { + PADDLE_ENFORCE(cudnnDestroy(dnn_handle_) == CUDNN_STATUS_SUCCESS, + "cudnnDestroy failed"); + } + + if (rand_generator_) { + PADDLE_ENFORCE( + curandDestroyGenerator(rand_generator_) == CURAND_STATUS_SUCCESS, + "curandDestroyGenerator failed"); + } + + delete eigen_stream_; + delete eigen_device_; + + paddle::platform::throw_on_error(cudaStreamDestroy(stream_), + "cudaStreamDestroy failed"); + } + + private: + GPUPlace gpu_place_; + cudaStream_t stream_; + + Eigen::CudaStreamDevice* eigen_stream_; + Eigen::GpuDevice* eigen_device_; + + cublasHandle_t blas_handle_{nullptr}; + + cudnnHandle_t dnn_handle_{nullptr}; + + int random_seed_; + curandGenerator_t rand_generator_{nullptr}; +}; +#endif +} // namespace platform +} // namespace paddle diff --git a/paddle/platform/device_context_test.cu b/paddle/platform/device_context_test.cu new file mode 100644 index 00000000000..a15fb53b719 --- /dev/null +++ b/paddle/platform/device_context_test.cu @@ -0,0 +1,29 @@ +/* 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/platform/device_context.h" +#include "gtest/gtest.h" + + +TEST(DeviceContext, CudaDevice) { + int count = paddle::platform::GetDeviceCount(); + for (int i = 0; i < count; i++) { + paddle::platform::CudaDeviceContext* device_context = new paddle::platform::CudaDeviceContext(i); + __attribute__((unused)) Eigen::GpuDevice gpu_device = device_context->eigen_device(); + __attribute__((unused)) cudnnHandle_t cudnn_handle = device_context->cudnn_handle(); + __attribute__((unused)) cublasHandle_t cublas_handle = device_context->cublas_handle(); + __attribute__((unused)) curandGenerator_t curand_handle = device_context->curand_generator(); + delete device_context; + } +} -- GitLab From a77fcef3f99724e85e2239ad91683b7afe913cd8 Mon Sep 17 00:00:00 2001 From: qijun Date: Mon, 3 Jul 2017 12:55:39 +0000 Subject: [PATCH 0988/3256] fix cuda compile error --- paddle/platform/cublas.h | 3 -- paddle/platform/cuda.h | 9 ++++++ paddle/platform/curand.h | 5 ++- paddle/platform/device_context.h | 52 +++++++++++++++++-------------- paddle/platform/dynamic_loader.cc | 4 +-- 5 files changed, 43 insertions(+), 30 deletions(-) diff --git a/paddle/platform/cublas.h b/paddle/platform/cublas.h index 70c97133252..d60eb501e9b 100644 --- a/paddle/platform/cublas.h +++ b/paddle/platform/cublas.h @@ -3,7 +3,6 @@ namespace paddle { namespace dyload { -namespace dynload { std::once_flag cublas_dso_flag; void *cublas_dso_handle = nullptr; @@ -67,8 +66,6 @@ CUBLAS_BLAS_ROUTINE_EACH(DYNAMIC_LOAD_CUBLAS_V2_WRAP) #undef DYNAMIC_LOAD_CUBLAS_V2_WRAP #undef CUBLAS_BLAS_ROUTINE_EACH -} /* namespace dynload */ - // clang-format on #ifndef PADDLE_TYPE_DOUBLE #define CUBLAS_GEAM dynload::cublasSgeam diff --git a/paddle/platform/cuda.h b/paddle/platform/cuda.h index 8fe891f9ce6..05290b0e1e7 100644 --- a/paddle/platform/cuda.h +++ b/paddle/platform/cuda.h @@ -33,6 +33,15 @@ int GetDeviceCount(void) { throw_on_error(cudaGetDeviceCount(&count), "cudaGetDeviceCount failed"); return count; } +int GetCurrentDeviceId(void) { + int device_id; + throw_on_error(cudaGetDevice(&device_id), "cudaGetDevice failed"); + return device_id; +} + +void SetDeviceId(int device_id) { + throw_on_error(cudaSetDevice(device_id), "cudaSetDevice failed"); +} } // namespace platform } // namespace paddle diff --git a/paddle/platform/curand.h b/paddle/platform/curand.h index 692c024e6ec..edff6526bd8 100644 --- a/paddle/platform/curand.h +++ b/paddle/platform/curand.h @@ -3,6 +3,8 @@ namespace paddle { namespace dyload { +std::once_flag curand_dso_flag; +void *curand_dso_handle = nullptr; #ifdef PADDLE_USE_DSO #define DYNAMIC_LOAD_CURAND_WRAP(__name) \ struct DynLoad__##__name { \ @@ -31,7 +33,8 @@ namespace dyload { __macro(curandSetStream) \ __macro(curandSetPseudoRandomGeneratorSeed)\ __macro(curandGenerateUniform) \ - __macro(curandGenerateUniformDouble) + __macro(curandGenerateUniformDouble) \ + __macro(curandDestroyGenerator) // clang-format on CURAND_RAND_ROUTINE_EACH(DYNAMIC_LOAD_CURAND_WRAP) diff --git a/paddle/platform/device_context.h b/paddle/platform/device_context.h index f95aac4a360..65e76666a79 100644 --- a/paddle/platform/device_context.h +++ b/paddle/platform/device_context.h @@ -83,11 +83,12 @@ class CudaDeviceContext : public DeviceContext { cublasHandle_t cublas_handle() { if (!blas_handle_) { DeviceGuard guard(gpu_place_); - PADDLE_ENFORCE(cublasCreate(&blas_handle_) == CUBLAS_STATUS_SUCCESS, - "cublasCreate failed"); PADDLE_ENFORCE( - cublasSetStream(blas_handle_, stream_) == CUBLAS_STATUS_SUCCESS, - "cublasSetStream failed"); + paddle::dyload::cublasCreate(&blas_handle_) == CUBLAS_STATUS_SUCCESS, + "cublasCreate failed"); + PADDLE_ENFORCE(paddle::dyload::cublasSetStream(blas_handle_, stream_) == + CUBLAS_STATUS_SUCCESS, + "cublasSetStream failed"); } return blas_handle_; } @@ -95,11 +96,12 @@ class CudaDeviceContext : public DeviceContext { cudnnHandle_t cudnn_handle() { if (!dnn_handle_) { DeviceGuard guard(gpu_place_); - PADDLE_ENFORCE(cudnnCreate(&dnn_handle_) == CUDNN_STATUS_SUCCESS, - "cudnnCreate failed"); PADDLE_ENFORCE( - cudnnSetStream(dnn_handle_, stream_) == CUDNN_STATUS_SUCCESS, - "cudnnSetStream failed"); + paddle::dyload::cudnnCreate(&dnn_handle_) == CUDNN_STATUS_SUCCESS, + "cudnnCreate failed"); + PADDLE_ENFORCE(paddle::dyload::cudnnSetStream(dnn_handle_, stream_) == + CUDNN_STATUS_SUCCESS, + "cudnnSetStream failed"); } return dnn_handle_; } @@ -107,17 +109,17 @@ class CudaDeviceContext : public DeviceContext { curandGenerator_t curand_generator() { if (!rand_generator_) { DeviceGuard guard(gpu_place_); + PADDLE_ENFORCE(paddle::dyload::curandCreateGenerator( + &rand_generator_, CURAND_RNG_PSEUDO_DEFAULT) == + CURAND_STATUS_SUCCESS, + "curandCreateGenerator failed"); PADDLE_ENFORCE( - curandCreateGenerator(&rand_generator_, CURAND_RNG_PSEUDO_DEFAULT) == - CURAND_STATUS_SUCCESS, - "curandCreateGenerator failed"); - PADDLE_ENFORCE( - curandSetPseudoRandomGeneratorSeed(rand_generator_, random_seed_) == - CURAND_STATUS_SUCCESS, + paddle::dyload::curandSetPseudoRandomGeneratorSeed( + rand_generator_, random_seed_) == CURAND_STATUS_SUCCESS, "curandSetPseudoRandomGeneratorSeed failed"); - PADDLE_ENFORCE( - curandSetStream(rand_generator_, stream_) == CURAND_STATUS_SUCCESS, - "curandSetStream failed"); + PADDLE_ENFORCE(paddle::dyload::curandSetStream( + rand_generator_, stream_) == CURAND_STATUS_SUCCESS, + "curandSetStream failed"); } return rand_generator_; } @@ -125,19 +127,21 @@ class CudaDeviceContext : public DeviceContext { ~CudaDeviceContext() { Wait(); if (blas_handle_) { - PADDLE_ENFORCE(cublasDestroy(blas_handle_) == CUBLAS_STATUS_SUCCESS, - "cublasDestroy failed"); + PADDLE_ENFORCE( + paddle::dyload::cublasDestroy(blas_handle_) == CUBLAS_STATUS_SUCCESS, + "cublasDestroy failed"); } if (dnn_handle_) { - PADDLE_ENFORCE(cudnnDestroy(dnn_handle_) == CUDNN_STATUS_SUCCESS, - "cudnnDestroy failed"); + PADDLE_ENFORCE( + paddle::dyload::cudnnDestroy(dnn_handle_) == CUDNN_STATUS_SUCCESS, + "cudnnDestroy failed"); } if (rand_generator_) { - PADDLE_ENFORCE( - curandDestroyGenerator(rand_generator_) == CURAND_STATUS_SUCCESS, - "curandDestroyGenerator failed"); + PADDLE_ENFORCE(paddle::dyload::curandDestroyGenerator(rand_generator_) == + CURAND_STATUS_SUCCESS, + "curandDestroyGenerator failed"); } delete eigen_stream_; diff --git a/paddle/platform/dynamic_loader.cc b/paddle/platform/dynamic_loader.cc index 9036eaf6426..c34abc392c4 100644 --- a/paddle/platform/dynamic_loader.cc +++ b/paddle/platform/dynamic_loader.cc @@ -12,9 +12,9 @@ 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 "dynamic_loader.h" #include -#include "DynamicLoader.h" -#include "Logging.h" +#include DEFINE_string(cudnn_dir, "", "Specify path for loading libcudnn.so. For instance, " -- GitLab From 5ef1425adb75eb1b0212518e0f12fefd8d9a8970 Mon Sep 17 00:00:00 2001 From: dongzhihong Date: Mon, 3 Jul 2017 21:13:20 +0800 Subject: [PATCH 0989/3256] "init saving model" --- go/pserver/optimizer.go | 16 ++++++++++++++-- go/pserver/service.go | 12 +++++++++--- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/go/pserver/optimizer.go b/go/pserver/optimizer.go index b4a040f46bf..427251f9008 100644 --- a/go/pserver/optimizer.go +++ b/go/pserver/optimizer.go @@ -40,17 +40,23 @@ func newOptimizer(paramWithConfigs ParameterWithConfig) *optimizer { o.elementType = paramWithConfigs.Param.ElementType p := paramWithConfigs.Param c := paramWithConfigs.Config + s := paramWithConfigs.State log.WithFields(log.Fields{ "ElementType": p.ElementType, "ParamSize": len(p.Content), "ConfigSize": len(c), + "StateSize": len(s), }).Info("New Optimizer Created with config:") var cbuffer unsafe.Pointer cbuffer = C.malloc(C.size_t(len(p.Content))) C.memcpy(cbuffer, unsafe.Pointer(&p.Content[0]), C.size_t(len(p.Content))) + var cstate unsafe.Pointer + if len(s) != 0 { + cstate = unsafe.Pointer(&s[0]) + } + o.opt = C.paddle_create_optimizer((*C.uchar)(&c[0]), C.int(len(c)), - C.paddle_element_type(p.ElementType), cbuffer, C.int(len(p.Content)/C.sizeof_float), - (*C.char)(nullPtr), 0) + C.paddle_element_type(p.ElementType), cbuffer, C.int(len(p.Content)/C.sizeof_float), (*C.char)(cstate), C.int(len(s))) return o } @@ -60,6 +66,12 @@ func (o *optimizer) GetWeights() []byte { return cArrayToSlice(buffer, int(buffer_len)*C.sizeof_float) } +func (o *optimizer) GetStates() []byte { + var cbuffer *C.char + cbuffer_len := C.paddle_optimizer_get_state(o.opt, &cbuffer) + return cArrayToSlice(unsafe.Pointer(cbuffer), int(cbuffer_len)) +} + func (o *optimizer) UpdateParameter(g Gradient) error { if o.elementType != g.ElementType { return fmt.Errorf("Name: %s, parameter and gradient element type not match, parameter: %v, gradient: %v", g.Name, o.elementType, g.ElementType) diff --git a/go/pserver/service.go b/go/pserver/service.go index e15a4e5a58a..a5ff8629033 100644 --- a/go/pserver/service.go +++ b/go/pserver/service.go @@ -38,6 +38,7 @@ type Parameter struct { type ParameterWithConfig struct { Param Parameter Config []byte // parameter configuration in Proto Buffer format + State []byte // parameter training state } // Gradient is the gradient of the parameter. @@ -58,7 +59,7 @@ func NewService(idx int) (*Service, error) { s := &Service{ idx: idx, } - s.optMap = make(map[string]*optimizer) + s.optMap = make(map[string]*optimizer) s.initialized = make(chan struct{}) return s, nil } @@ -143,7 +144,12 @@ func (s *Service) GetParam(name string, parameter *Parameter) error { // Save tells the parameter server to save parameters. func (s *Service) Save(path string, dummy *int) error { <-s.initialized - - // TODO + for opt, ok := range s.optMap { + if ok != nil { + return fmt.Errorf("parameter optimizerMap error: ", ok) + } + state := opt.GetStates() + weights := opt.GetWeights() + } return nil } -- GitLab From 1100f97e5707737d2dabe5a47bb111ff246e52e4 Mon Sep 17 00:00:00 2001 From: dongzhihong Date: Mon, 3 Jul 2017 21:26:49 +0800 Subject: [PATCH 0990/3256] "fix style check" --- cmake/cpplint.cmake | 1 + go/pserver/cclient/test/test_cclient.c | 2 +- go/pserver/cclient/test/testdata/optimizer.pb | Bin 0 -> 50 bytes go/pserver/cclient/test/testdata/optimizer.pb.txt | Bin 51 -> 0 bytes go/pserver/client_test.go | 2 +- go/pserver/optimizer_test.go | 2 +- go/pserver/service_test.go | 4 ++-- 7 files changed, 6 insertions(+), 5 deletions(-) create mode 100644 go/pserver/cclient/test/testdata/optimizer.pb delete mode 100644 go/pserver/cclient/test/testdata/optimizer.pb.txt diff --git a/cmake/cpplint.cmake b/cmake/cpplint.cmake index 48f705818b7..6bbcd730e1b 100644 --- a/cmake/cpplint.cmake +++ b/cmake/cpplint.cmake @@ -25,6 +25,7 @@ set(STYLE_FILTER "${STYLE_FILTER}-readability/casting") set(IGNORE_PATTERN .*ImportanceSampler.* .*cblas\\.h.* + .*\\.pb\\.txt .*LtrDataProvider.* .*MultiDataProvider.*) diff --git a/go/pserver/cclient/test/test_cclient.c b/go/pserver/cclient/test/test_cclient.c index b16769b433e..8eababbe339 100644 --- a/go/pserver/cclient/test/test_cclient.c +++ b/go/pserver/cclient/test/test_cclient.c @@ -51,7 +51,7 @@ int main() { char *config_proto; size_t config_proto_len = 0; ssize_t nread; - FILE *fp = fopen("testdata/optimizer.pb.txt", "r"); + FILE *fp = fopen("testdata/optimizer.pb", "r"); if (!fp) { fail(); } diff --git a/go/pserver/cclient/test/testdata/optimizer.pb b/go/pserver/cclient/test/testdata/optimizer.pb new file mode 100644 index 0000000000000000000000000000000000000000..27dd3bc5f19e2964b4b674cff8860233cbdb445a GIT binary patch literal 50 kcmd;JloDUb$N&X9;j9CU3=s@ToSd^}g1}Dum25B;0LStS`2YX_ literal 0 HcmV?d00001 diff --git a/go/pserver/cclient/test/testdata/optimizer.pb.txt b/go/pserver/cclient/test/testdata/optimizer.pb.txt deleted file mode 100644 index 27c8a584df40ab714edfd730f0ff7b7bd3783964..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 51 lcmd;JloDUb$N&X9;j9CU3=s@ToSd^}g1}Dum25B;7XZ}t4FdoG diff --git a/go/pserver/client_test.go b/go/pserver/client_test.go index a248a3fb696..b805efa9216 100644 --- a/go/pserver/client_test.go +++ b/go/pserver/client_test.go @@ -75,7 +75,7 @@ func TestClientFull(t *testing.T) { } const numParameter = 100 - config, err := ioutil.ReadFile("./cclient/test/testdata/optimizer.pb.txt") + config, err := ioutil.ReadFile("./cclient/test/testdata/optimizer.pb") if err != nil { t.Fatalf("read optimizer proto failed") } diff --git a/go/pserver/optimizer_test.go b/go/pserver/optimizer_test.go index 368047d6f89..b99b5a5f0bf 100644 --- a/go/pserver/optimizer_test.go +++ b/go/pserver/optimizer_test.go @@ -11,7 +11,7 @@ func TestOptimizerCreateRelease(t *testing.T) { ElementType: Int32, } p.Content = []byte{1, 3} - config, err := ioutil.ReadFile("./cclient/test/testdata/optimizer.pb.txt") + config, err := ioutil.ReadFile("./cclient/test/testdata/optimizer.pb") if err != nil { t.Fatalf("read optimizer proto failed") } diff --git a/go/pserver/service_test.go b/go/pserver/service_test.go index f86619447c2..30e3ac8ae1c 100644 --- a/go/pserver/service_test.go +++ b/go/pserver/service_test.go @@ -19,7 +19,7 @@ func TestServiceFull(t *testing.T) { p.Name = "param_a" p.Content = []byte{1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0} p.ElementType = pserver.Int32 - config, err := ioutil.ReadFile("./cclient/test/testdata/optimizer.pb.txt") + config, err := ioutil.ReadFile("./cclient/test/testdata/optimizer.pb") if err != nil { t.Fatalf("read optimizer proto failed") } @@ -149,7 +149,7 @@ func TestBlockUntilInitialized(t *testing.T) { p.Name = "param_a" p.Content = []byte{1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0} p.ElementType = pserver.Int32 - config, err := ioutil.ReadFile("./cclient/test/testdata/optimizer.pb.txt") + config, err := ioutil.ReadFile("./cclient/test/testdata/optimizer.pb") if err != nil { t.Fatalf("read optimizer proto failed") } -- GitLab From 3f63d96abec165426bcd464f7aff32e2e42ed021 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Mon, 3 Jul 2017 23:16:11 +0800 Subject: [PATCH 0991/3256] Fix link error in op_proto_test. --- 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 50107faaed7..f7e5753ac2c 100644 --- a/paddle/framework/CMakeLists.txt +++ b/paddle/framework/CMakeLists.txt @@ -7,4 +7,4 @@ cc_test(scope_test SRCS scope_test.cc) cc_test(enforce_test SRCS enforce_test.cc) proto_library(attr_type SRCS attr_type.proto) proto_library(op_proto SRCS op_proto.proto) -cc_test(op_proto_test SRCS op_proto_test.cc DEPS attr_type op_proto protobuf) +cc_test(op_proto_test SRCS op_proto_test.cc DEPS op_proto attr_type protobuf) -- GitLab From f1330e216a1b8130bb578b69ff2d6a67357cdd1b Mon Sep 17 00:00:00 2001 From: dongzhihong Date: Mon, 3 Jul 2017 23:20:39 +0800 Subject: [PATCH 0992/3256] "saving checkpoint" --- go/pserver/service.go | 79 +++++++++++++++++++++++++++++++++++--- go/pserver/service_test.go | 6 +++ 2 files changed, 80 insertions(+), 5 deletions(-) diff --git a/go/pserver/service.go b/go/pserver/service.go index a5ff8629033..a4cf3e47507 100644 --- a/go/pserver/service.go +++ b/go/pserver/service.go @@ -1,9 +1,19 @@ package pserver import ( + "bufio" + "bytes" + "crypto/md5" + "encoding/gob" + "encoding/hex" "errors" "fmt" + "os" + "strconv" "sync" + "time" + + log "github.com/sirupsen/logrus" ) // ElementType is the type of elements of a Parameter. @@ -14,6 +24,10 @@ const ( Uninitialized = "pserver not fully initialized" ) +const ( + checkpoint_path = "/checkpoints/" +) + // Supported element types const ( Int32 ElementType = iota @@ -53,6 +67,24 @@ type Service struct { optMap map[string]*optimizer } +type Checkpoint struct { + uuid string + md5sum string + timestamp string +} + +//serialize ParameterWithConfig to byte stream +func GetBytes(content ...interface{}) ([]byte, error) { + + var buf bytes.Buffer + encoder := gob.NewEncoder(&buf) + err := encoder.Encode(content) + if err != nil { + return nil, err + } + return buf.Bytes(), nil +} + // NewService creates a new service, will bypass etcd registration if no // endpoints specified. func NewService(idx int) (*Service, error) { @@ -143,13 +175,50 @@ func (s *Service) GetParam(name string, parameter *Parameter) error { // Save tells the parameter server to save parameters. func (s *Service) Save(path string, dummy *int) error { + //FIXME: checkpoint is only used by pserver + // and has a constant path of */checkpoints/{pserver_idx}* <-s.initialized - for opt, ok := range s.optMap { - if ok != nil { - return fmt.Errorf("parameter optimizerMap error: ", ok) + s.mu.Lock() + defer s.mu.Unlock() + var paramWithConfig ParameterWithConfig + for name, opt := range s.optMap { + paramWithConfig.Param.Name = name + paramWithConfig.Param.ElementType = opt.elementType + paramWithConfig.Param.Content = opt.GetWeights() + paramWithConfig.State = opt.GetStates() + content, err := GetBytes(paramWithConfig) + if err != nil { + log.Errorln(err) + } + ck := Checkpoint{} + h := md5.New() + ck.md5sum = hex.EncodeToString(h.Sum(content)) + ck.timestamp = time.Now().String() + ck.uuid = checkpoint_path + strconv.Itoa(s.idx) + ckbytes, err := GetBytes(ck) + if err != nil { + log.Errorln(err) + } + // TODO: according design doc, need to save uuid to etcd in json format + // {\"uuid\": [UUID], \"md5\", \"MD5 sum\", \"timestamp\": xxxx} + log.Infof("parameter checkpoint %s", ckbytes) + + if _, err = os.Stat(ck.uuid); os.IsNotExist(err) { + log.Info("checkpoint not exists.") + } else { + err = os.Remove(ck.uuid) + log.Infof("remove %s", ck.uuid) + } + f, err := os.Create(ck.uuid) + defer f.Close() + if err != nil { + log.Errorln(err) + } + writer := bufio.NewWriter(f) + _, err = writer.Write(content) + if err != nil { + log.Errorln(err) } - state := opt.GetStates() - weights := opt.GetWeights() } return nil } diff --git a/go/pserver/service_test.go b/go/pserver/service_test.go index f86619447c2..28956e4d851 100644 --- a/go/pserver/service_test.go +++ b/go/pserver/service_test.go @@ -79,6 +79,8 @@ func TestServiceFull(t *testing.T) { if !reflect.DeepEqual(param1, p) { t.FailNow() } + var dummy int + s.Save("", &dummy) } func TestMultipleInit(t *testing.T) { @@ -166,3 +168,7 @@ func TestBlockUntilInitialized(t *testing.T) { wg.Wait() } + +func TestCheckpointSpeed(t *testing.T) { + //TODO: test speed +} -- GitLab From 65afbe11853c2e32ca4196965e309e33ab843fd1 Mon Sep 17 00:00:00 2001 From: dongzhihong Date: Mon, 3 Jul 2017 23:38:21 +0800 Subject: [PATCH 0993/3256] "fix gob register error" --- go/pserver/service.go | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/go/pserver/service.go b/go/pserver/service.go index a4cf3e47507..decd3682aec 100644 --- a/go/pserver/service.go +++ b/go/pserver/service.go @@ -25,7 +25,7 @@ const ( ) const ( - checkpoint_path = "/checkpoints/" + checkpoint_path = "./checkpoints/" ) // Supported element types @@ -67,10 +67,10 @@ type Service struct { optMap map[string]*optimizer } -type Checkpoint struct { - uuid string - md5sum string - timestamp string +type checkpoint struct { + Uuid string + Md5sum string + Timestamp string } //serialize ParameterWithConfig to byte stream @@ -93,6 +93,8 @@ func NewService(idx int) (*Service, error) { } s.optMap = make(map[string]*optimizer) s.initialized = make(chan struct{}) + gob.Register(ParameterWithConfig{}) + gob.Register(checkpoint{}) return s, nil } @@ -190,32 +192,33 @@ func (s *Service) Save(path string, dummy *int) error { if err != nil { log.Errorln(err) } - ck := Checkpoint{} + ck := checkpoint{} h := md5.New() - ck.md5sum = hex.EncodeToString(h.Sum(content)) - ck.timestamp = time.Now().String() - ck.uuid = checkpoint_path + strconv.Itoa(s.idx) + ck.Md5sum = hex.EncodeToString(h.Sum(content)) + ck.Timestamp = time.Now().String() + ck.Uuid = checkpoint_path + strconv.Itoa(s.idx) ckbytes, err := GetBytes(ck) if err != nil { log.Errorln(err) } - // TODO: according design doc, need to save uuid to etcd in json format - // {\"uuid\": [UUID], \"md5\", \"MD5 sum\", \"timestamp\": xxxx} + // TODO: according design doc, need to save Uuid to etcd in json format + // {\"Uuid\": [UUID], \"md5\", \"MD5 sum\", \"Timestamp\": xxxx} log.Infof("parameter checkpoint %s", ckbytes) - if _, err = os.Stat(ck.uuid); os.IsNotExist(err) { + if _, err = os.Stat(ck.Uuid); os.IsNotExist(err) { log.Info("checkpoint not exists.") } else { - err = os.Remove(ck.uuid) - log.Infof("remove %s", ck.uuid) + err = os.Remove(ck.Uuid) + log.Infof("remove %s", ck.Uuid) } - f, err := os.Create(ck.uuid) + f, err := os.Create(ck.Uuid) defer f.Close() if err != nil { log.Errorln(err) } writer := bufio.NewWriter(f) _, err = writer.Write(content) + writer.Flush() if err != nil { log.Errorln(err) } -- GitLab From 6935dd7bc96e101ec65de39be1d2d8f4f79f1af3 Mon Sep 17 00:00:00 2001 From: dongzhihong Date: Tue, 4 Jul 2017 00:16:45 +0800 Subject: [PATCH 0994/3256] "lr state serialization" --- paddle/optimizer/lr_policy.h | 46 ++++++++++++++++++++++--------- paddle/optimizer/sgd_optimizer.cc | 4 +-- proto/OptimizerConfig.proto | 27 ++++++++---------- 3 files changed, 47 insertions(+), 30 deletions(-) diff --git a/paddle/optimizer/lr_policy.h b/paddle/optimizer/lr_policy.h index d8e33ad37ab..ab5101e2e86 100644 --- a/paddle/optimizer/lr_policy.h +++ b/paddle/optimizer/lr_policy.h @@ -19,34 +19,54 @@ class ConstLr final : public LrPolicy { public: ConstLr(double lr) : learning_rate(lr){}; double LearningRate(const uint64_t num_sample_passed) { - return learning_rate; + return learning_rate_; + } + const char *SerializeState(int *state_len) { + LrPolicyState state; + state.set_learning_rate(learning_rate_); + auto str = state.SerializeAsString(); + *state_len = str.size(); + return str.c_str(); + } + void DeserializeState(const std::string &state) { + LrPolicyState state; + state.ParseFromString(str); + learning_rate_ = state.learning_rate(); } - const char *SerializeState(int *state_len) { return nullptr; } - void DeserializeState(const std::string &state) {} private: - double learning_rate; + double learning_rate_; }; class LinearLr final : public LrPolicy { public: LinearLr(double lr, double lr_decay_a, double lr_decay_b) - : learning_rate(lr), lr_decay_a(lr_decay_a), lr_decay_b(lr_decay_b) {} + : learning_rate_(lr), lr_decay_a_(lr_decay_a), lr_decay_b_(lr_decay_b) {} double LearningRate(const uint64_t num_sample_passed) { - return std::max(learning_rate - lr_decay_a * num_sample_passed, lr_decay_b); + return std::max(learning_rate_ - lr_decay_a_ * num_sample_passed, + lr_decay_b_); } const char *SerializeState(int *state_len) { - // TODO(zhihong) : add lr_policy serialization - return nullptr; + LrPolicyState state; + state.set_learning_rate(learning_rate_); + state.set_lr_decay_a(lr_decay_a_); + state.set_lr_decay_b(lr_decay_b_); + auto str = state.SerializeAsString(); + *state_len = str.size(); + return str.c_str(); } - void DeserializeState(const std::string &state) { - // TODO(zhihong) : add lr_policy serialization + void DeserializeState(const std::string &str) { + LrPolicyState state; + state.ParseFromString(str); + learning_rate_ = state.learning_rate(); + lr_decay_a_ = state.lr_decay_a(); + lr_decay_b_ = state.lr_decay_b(); } private: - double learning_rate; - double lr_decay_a; - double lr_decay_b; + double learning_rate_; + double lr_decay_a_; + double lr_decay_b_; }; } // namespace optimizer diff --git a/paddle/optimizer/sgd_optimizer.cc b/paddle/optimizer/sgd_optimizer.cc index 34e051003fa..9e5477b2ff3 100644 --- a/paddle/optimizer/sgd_optimizer.cc +++ b/paddle/optimizer/sgd_optimizer.cc @@ -30,10 +30,10 @@ void SGDOptimizer::Update(const Tensor *gradient) { const char *SGDOptimizer::SerializeState(int *state_len) { SGDOptimizerState state; state.set_num_sample_passed(num_sample_passed_); - TensorToProto(*parameter_, state.mutable_parameter()); + state.set_lr_ TensorToProto(*parameter_, state.mutable_parameter()); if (momentum_ != 0.0) TensorToProto(*momentums_, state.mutable_momentums()); auto str = state.SerializeAsString(); - *state_len = str.size(); + *state_len += str.size(); return str.c_str(); } diff --git a/proto/OptimizerConfig.proto b/proto/OptimizerConfig.proto index c698d3c2ddb..19ce289ea39 100644 --- a/proto/OptimizerConfig.proto +++ b/proto/OptimizerConfig.proto @@ -78,11 +78,15 @@ enum DataType { repeated bytes content = 2; } +message LrPolicyState { + // learninRate Policy + optional double learning_rate = 1 [default = 1.0]; + optional double lr_decay_a = 2; + optional double lr_decay_b = 3; +} + message SGDOptimizerState { - // learning rate policy - optional double learning_rate = 101; - optional double lr_decay_a = 102; - optional double lr_decay_b = 103; + optional LrPolicyState lrstate = 101; optional double num_sample_passed = 104; // state optional TensorProto parameter = 1; @@ -91,9 +95,7 @@ message SGDOptimizerState { message AdadeltaOptimizerState { // learning rate policy - optional double learning_rate = 101; - optional double lr_decay_a = 102; - optional double lr_decay_b = 103; + optional LrPolicyState lrstate = 101; optional double num_sample_passed = 104; // state optional TensorProto parameter = 1; @@ -102,11 +104,9 @@ message AdadeltaOptimizerState { optional TensorProto update_delta = 4; } + message AdagradOptimizerState { - // learning rate policy - optional double learning_rate = 101; - optional double lr_decay_a = 102; - optional double lr_decay_b = 103; + optional LrPolicyState lrstate = 101; optional double num_sample_passed = 104; // state optional TensorProto parameter = 1; @@ -114,10 +114,7 @@ message AdagradOptimizerState { } message AdamOptimizerState { - // learning rate policy - optional double learning_rate = 101; - optional double lr_decay_a = 102; - optional double lr_decay_b = 103; + optional LrPolicyState lrstate = 101; optional double num_sample_passed = 104; // state optional TensorProto parameter = 1; -- GitLab From e1acd73fab4e17db5700feba09339f09d7152406 Mon Sep 17 00:00:00 2001 From: dongzhihong Date: Tue, 4 Jul 2017 01:13:30 +0800 Subject: [PATCH 0995/3256] "fix typo deleted part" --- paddle/optimizer/lr_policy.h | 4 ++-- paddle/optimizer/sgd_optimizer.cc | 6 +++++- proto/OptimizerConfig.proto | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/paddle/optimizer/lr_policy.h b/paddle/optimizer/lr_policy.h index ab5101e2e86..036c376e10f 100644 --- a/paddle/optimizer/lr_policy.h +++ b/paddle/optimizer/lr_policy.h @@ -17,7 +17,7 @@ public: // constant learning rate policy class ConstLr final : public LrPolicy { public: - ConstLr(double lr) : learning_rate(lr){}; + ConstLr(double lr) : learning_rate_(lr){}; double LearningRate(const uint64_t num_sample_passed) { return learning_rate_; } @@ -28,7 +28,7 @@ public: *state_len = str.size(); return str.c_str(); } - void DeserializeState(const std::string &state) { + void DeserializeState(const std::string &str) { LrPolicyState state; state.ParseFromString(str); learning_rate_ = state.learning_rate(); diff --git a/paddle/optimizer/sgd_optimizer.cc b/paddle/optimizer/sgd_optimizer.cc index 9e5477b2ff3..527e65144da 100644 --- a/paddle/optimizer/sgd_optimizer.cc +++ b/paddle/optimizer/sgd_optimizer.cc @@ -30,7 +30,11 @@ void SGDOptimizer::Update(const Tensor *gradient) { const char *SGDOptimizer::SerializeState(int *state_len) { SGDOptimizerState state; state.set_num_sample_passed(num_sample_passed_); - state.set_lr_ TensorToProto(*parameter_, state.mutable_parameter()); + std::string lr_str = this->lr_policy_->SerializeState(state_len); + LrPolicyState lr_state; + lr_state.ParseFromString(lr_str); + state.mutable_lr_state() = lr_state; + TensorToProto(*parameter_, state.mutable_parameter()); if (momentum_ != 0.0) TensorToProto(*momentums_, state.mutable_momentums()); auto str = state.SerializeAsString(); *state_len += str.size(); diff --git a/proto/OptimizerConfig.proto b/proto/OptimizerConfig.proto index 19ce289ea39..290932898eb 100644 --- a/proto/OptimizerConfig.proto +++ b/proto/OptimizerConfig.proto @@ -95,7 +95,7 @@ message SGDOptimizerState { message AdadeltaOptimizerState { // learning rate policy - optional LrPolicyState lrstate = 101; + optional LrPolicyState lr_state = 101; optional double num_sample_passed = 104; // state optional TensorProto parameter = 1; -- GitLab From 7edabe74d45b9dd35603ac786e6a36e201bb1177 Mon Sep 17 00:00:00 2001 From: dongzhihong Date: Tue, 4 Jul 2017 01:21:13 +0800 Subject: [PATCH 0996/3256] "polish name convention" --- paddle/optimizer/sgd_optimizer.cc | 4 +++- proto/OptimizerConfig.proto | 6 +++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/paddle/optimizer/sgd_optimizer.cc b/paddle/optimizer/sgd_optimizer.cc index 527e65144da..96570eab26b 100644 --- a/paddle/optimizer/sgd_optimizer.cc +++ b/paddle/optimizer/sgd_optimizer.cc @@ -33,7 +33,7 @@ const char *SGDOptimizer::SerializeState(int *state_len) { std::string lr_str = this->lr_policy_->SerializeState(state_len); LrPolicyState lr_state; lr_state.ParseFromString(lr_str); - state.mutable_lr_state() = lr_state; + state.mutable_lr_state()->ParseFromString(lr_str); TensorToProto(*parameter_, state.mutable_parameter()); if (momentum_ != 0.0) TensorToProto(*momentums_, state.mutable_momentums()); auto str = state.SerializeAsString(); @@ -44,6 +44,8 @@ const char *SGDOptimizer::SerializeState(int *state_len) { void SGDOptimizer::DeserializeState(const std::string &str) { SGDOptimizerState state; state.ParseFromString(str); + auto lr_state = state.lr_state(); + this->lr_policy_->DeserializeState(lr_state.SerializeAsString()); num_sample_passed_ = state.num_sample_passed(); ProtoToTensor(state.parameter(), parameter_); if (momentum_ != 0.0) ProtoToTensor(state.parameter(), momentums_); diff --git a/proto/OptimizerConfig.proto b/proto/OptimizerConfig.proto index 290932898eb..2a87e293f64 100644 --- a/proto/OptimizerConfig.proto +++ b/proto/OptimizerConfig.proto @@ -86,7 +86,7 @@ message LrPolicyState { } message SGDOptimizerState { - optional LrPolicyState lrstate = 101; + optional LrPolicyState lr_state = 101; optional double num_sample_passed = 104; // state optional TensorProto parameter = 1; @@ -106,7 +106,7 @@ message AdadeltaOptimizerState { message AdagradOptimizerState { - optional LrPolicyState lrstate = 101; + optional LrPolicyState lr_state = 101; optional double num_sample_passed = 104; // state optional TensorProto parameter = 1; @@ -114,7 +114,7 @@ message AdagradOptimizerState { } message AdamOptimizerState { - optional LrPolicyState lrstate = 101; + optional LrPolicyState lr_state = 101; optional double num_sample_passed = 104; // state optional TensorProto parameter = 1; -- GitLab From dec65aca7ddcbeac1ba54608bc487dc93d2d28f3 Mon Sep 17 00:00:00 2001 From: dongzhihong Date: Tue, 4 Jul 2017 01:24:27 +0800 Subject: [PATCH 0997/3256] "fix parameter accumulate size" --- paddle/optimizer/adadelta_optimizer.cc | 8 +++++--- paddle/optimizer/adagrad_optimizer.cc | 9 ++++++--- paddle/optimizer/adam_optimizer.cc | 9 ++++++--- paddle/optimizer/sgd_optimizer.cc | 2 -- 4 files changed, 17 insertions(+), 11 deletions(-) diff --git a/paddle/optimizer/adadelta_optimizer.cc b/paddle/optimizer/adadelta_optimizer.cc index 465ad5e0d20..6eec5d846fa 100644 --- a/paddle/optimizer/adadelta_optimizer.cc +++ b/paddle/optimizer/adadelta_optimizer.cc @@ -27,22 +27,24 @@ void AdadeltaOptimizer::Update(const Tensor* gradient) { const char* AdadeltaOptimizer::SerializeState(int* state_len) { AdadeltaOptimizerState state; - // TODO(zhihong) : add lr_policy serialization state.set_num_sample_passed(num_sample_passed_); + std::string lr_str = this->lr_policy_->SerializeState(state_len); + state.mutable_lr_state()->ParseFromString(lr_str); TensorToProto(*parameter_, state.mutable_parameter()); TensorToProto(*accum_gradient_, state.mutable_accum_gradient()); TensorToProto(*accum_delta_, state.mutable_accum_delta()); TensorToProto(*update_delta_, state.mutable_update_delta()); auto str = state.SerializeAsString(); - *state_len = str.size(); + *state_len += str.size(); return str.c_str(); } void AdadeltaOptimizer::DeserializeState(const std::string& str) { AdadeltaOptimizerState state; state.ParseFromString(str); - // TODO(zhihong) : add lr_policy DeserializeState + auto lr_state = state.lr_state(); + this->lr_policy_->DeserializeState(lr_state.SerializeAsString()); num_sample_passed_ = state.num_sample_passed(); ProtoToTensor(state.parameter(), parameter_); diff --git a/paddle/optimizer/adagrad_optimizer.cc b/paddle/optimizer/adagrad_optimizer.cc index bdaa7877d2b..5b92610ac54 100644 --- a/paddle/optimizer/adagrad_optimizer.cc +++ b/paddle/optimizer/adagrad_optimizer.cc @@ -19,20 +19,23 @@ void AdagradOptimizer::Update(const Tensor* gradient) { } const char* AdagradOptimizer::SerializeState(int* state_len) { AdagradOptimizerState state; - // TODO(zhihong) : add lr_policy serialization state.set_num_sample_passed(num_sample_passed_); + std::string lr_str = this->lr_policy_->SerializeState(state_len); + state.mutable_lr_state()->ParseFromString(lr_str); TensorToProto(*parameter_, state.mutable_parameter()); TensorToProto(*accum_gradient_, state.mutable_accum_gradient()); auto str = state.SerializeAsString(); - *state_len = str.size(); + *state_len += str.size(); return str.c_str(); } void AdagradOptimizer::DeserializeState(const std::string& str) { AdagradOptimizerState state; state.ParseFromString(str); - // TODO(zhihong) : add lr_policy DeserializeState + auto lr_state = state.lr_state(); + this->lr_policy_->DeserializeState(lr_state.SerializeAsString()); + num_sample_passed_ = state.num_sample_passed(); ProtoToTensor(state.parameter(), parameter_); ProtoToTensor(state.accum_gradient(), accum_gradient_); diff --git a/paddle/optimizer/adam_optimizer.cc b/paddle/optimizer/adam_optimizer.cc index ceab7397d87..1ebb6b1e0f7 100644 --- a/paddle/optimizer/adam_optimizer.cc +++ b/paddle/optimizer/adam_optimizer.cc @@ -24,20 +24,23 @@ void AdamOptimizer::Update(const Tensor *gradient) { const char *AdamOptimizer::SerializeState(int *state_len) { AdamOptimizerState state; - // TODO(zhihong) : add lr_policy serialization + std::string lr_str = this->lr_policy_->SerializeState(state_len); + state.mutable_lr_state()->ParseFromString(lr_str); state.set_num_sample_passed(num_sample_passed_); + TensorToProto(*parameter_, state.mutable_parameter()); TensorToProto(*momentums_, state.mutable_momentums()); TensorToProto(*velocitys_, state.mutable_velocitys()); auto str = state.SerializeAsString(); - *state_len = str.size(); + *state_len += str.size(); return str.c_str(); } void AdamOptimizer::DeserializeState(const std::string &str) { AdamOptimizerState state; state.ParseFromString(str); - // TODO(zhihong) : add lr_policy DeserializeState + auto lr_state = state.lr_state(); + this->lr_policy_->DeserializeState(lr_state.SerializeAsString()); num_sample_passed_ = state.num_sample_passed(); ProtoToTensor(state.parameter(), parameter_); diff --git a/paddle/optimizer/sgd_optimizer.cc b/paddle/optimizer/sgd_optimizer.cc index 96570eab26b..15418faa840 100644 --- a/paddle/optimizer/sgd_optimizer.cc +++ b/paddle/optimizer/sgd_optimizer.cc @@ -31,8 +31,6 @@ const char *SGDOptimizer::SerializeState(int *state_len) { SGDOptimizerState state; state.set_num_sample_passed(num_sample_passed_); std::string lr_str = this->lr_policy_->SerializeState(state_len); - LrPolicyState lr_state; - lr_state.ParseFromString(lr_str); state.mutable_lr_state()->ParseFromString(lr_str); TensorToProto(*parameter_, state.mutable_parameter()); if (momentum_ != 0.0) TensorToProto(*momentums_, state.mutable_momentums()); -- GitLab From e12d7269ff473db5cc87de1344630eb348017a4a Mon Sep 17 00:00:00 2001 From: gongweibao Date: Tue, 4 Jul 2017 01:22:01 +0000 Subject: [PATCH 0998/3256] fix by helin's comments --- python/paddle/v2/reader/creator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/paddle/v2/reader/creator.py b/python/paddle/v2/reader/creator.py index 20624d5286b..61b5cc134fb 100644 --- a/python/paddle/v2/reader/creator.py +++ b/python/paddle/v2/reader/creator.py @@ -106,7 +106,7 @@ def recordio(paths, buf_size=100): while True: r, err = client.next_record() - if r is None: + if err < 0: break yield r -- GitLab From e25c155f3954ee8cde673f39e8f82c5baebd99c6 Mon Sep 17 00:00:00 2001 From: gongweibao Date: Tue, 4 Jul 2017 02:37:31 +0000 Subject: [PATCH 0999/3256] add taskfail interface --- go/master/client.go | 5 +++ go/master/service.go | 99 ++++++++++++++++++++++++++++---------------- 2 files changed, 68 insertions(+), 36 deletions(-) diff --git a/go/master/client.go b/go/master/client.go index d3bea49d0a8..b6ca8cad15a 100644 --- a/go/master/client.go +++ b/go/master/client.go @@ -112,6 +112,11 @@ func (c *Client) taskFinished(taskID int) error { return c.conn.Call("Service.TaskFinished", taskID, nil) } +// TaskFailed tell the master server as task is failed. +func (c *Client) taskFailed(taskID int, epoch int) error { + return c.conn.Call("Service.TaskFinished", taskID, epoch) +} + // NextRecord returns next record in the dataset. // // NextRecord will block until the next record is available. It is diff --git a/go/master/service.go b/go/master/service.go index 58e68e74485..b078f318f57 100644 --- a/go/master/service.go +++ b/go/master/service.go @@ -34,29 +34,30 @@ type Chunk struct { // Task is the basic unit of data instances assigned to trainers. type Task struct { ID int + Epoch int Chunks []Chunk } type taskEntry struct { - Epoch int NumTimeout int Task Task + FailedNum int } type taskQueues struct { Todo []taskEntry Pending map[int]taskEntry // map from task ID to task entry Done []taskEntry - Failed []Task + Failed []taskEntry } // Service is the master server service. type Service struct { - chunksPerTask int - timeoutDur time.Duration - timeoutMax int - ready chan struct{} - store Store + chunksPerTask int + timeoutDur time.Duration + failortimeoutMax int + ready chan struct{} + store Store mu sync.Mutex initDone bool @@ -91,11 +92,11 @@ func partition(chunks []Chunk, chunksPerTask int) []taskEntry { } // NewService creates a new service. -func NewService(store Store, chunksPerTask int, timeoutDur time.Duration, timeoutMax int) (*Service, error) { +func NewService(store Store, chunksPerTask int, timeoutDur time.Duration, failortimeoutMax int) (*Service, error) { s := &Service{} s.chunksPerTask = chunksPerTask s.timeoutDur = timeoutDur - s.timeoutMax = timeoutMax + s.failortimeoutMax = failortimeoutMax s.taskQueues = taskQueues{} s.taskQueues.Pending = make(map[int]taskEntry) s.ready = make(chan struct{}) @@ -257,6 +258,34 @@ func (s *Service) SetDataset(globPaths []string, dummy *int) error { return nil } +func (s *Service) checkTaskStatus(t taskEntry, epoch int) { + if t.Task.Epoch != epoch { + // new epoch, task launched after the + // schedule of this timeout check or failed status report. + return + } + + defer func() { + err := s.snapshot() + if err != nil { + log.Errorln(err) + } + }() + + delete(s.taskQueues.Pending, t.Task.ID) + + t.NumTimeout++ + if t.NumTimeout+t.FailedNum > s.failortimeoutMax { + log.Warningf("Task %v timed out %d times and failed %d times, discard.", t.Task, t.NumTimeout, t.FailedNum) + s.taskQueues.Failed = append(s.taskQueues.Failed, t) + return + } + + log.Warningf("Task %v timed out %d times and failed %d times, discard.", t.Task, t.NumTimeout, t.FailedNum) + s.taskQueues.Todo = append(s.taskQueues.Todo, t) + return +} + func (s *Service) checkTimeoutFunc(taskID int, epoch int) func() { return func() { s.mu.Lock() @@ -267,30 +296,7 @@ func (s *Service) checkTimeoutFunc(taskID int, epoch int) func() { return } - if t.Epoch != epoch { - // new epoch, task launched after the - // schedule of this timeout check. - return - } - - defer func() { - err := s.snapshot() - if err != nil { - log.Errorln(err) - } - }() - - delete(s.taskQueues.Pending, t.Task.ID) - - t.NumTimeout++ - if t.NumTimeout > s.timeoutMax { - log.Warningf("Task %v timed out %d times, discard.", t.Task, t.NumTimeout) - s.taskQueues.Failed = append(s.taskQueues.Failed, t.Task) - return - } - - log.Warningf("Task %v timed out %d times, retry.", t.Task, t.NumTimeout) - s.taskQueues.Todo = append(s.taskQueues.Todo, t) + s.checkTaskStatus(t, epoch) } } @@ -339,7 +345,7 @@ func (s *Service) GetTask(dummy int, task *Task) error { } t := s.taskQueues.Todo[0] - t.Epoch++ + t.Task.Epoch++ s.taskQueues.Todo = s.taskQueues.Todo[1:] s.taskQueues.Pending[t.Task.ID] = t err := s.snapshot() @@ -348,9 +354,9 @@ func (s *Service) GetTask(dummy int, task *Task) error { } *task = t.Task - log.WithFields(s.logFields()).Infof("Task #%d dispatched.", task.ID) + log.WithFields(s.logFields()).Infof("Task #%v dispatched.", t) - time.AfterFunc(s.timeoutDur, s.checkTimeoutFunc(t.Task.ID, t.Epoch)) + time.AfterFunc(s.timeoutDur, s.checkTimeoutFunc(t.Task.ID, t.Task.Epoch)) return nil } @@ -372,6 +378,7 @@ func (s *Service) TaskFinished(taskID int, dummy *int) error { // task finished, reset timeout t.NumTimeout = 0 + t.FailedNum = 0 s.taskQueues.Done = append(s.taskQueues.Done, t) delete(s.taskQueues.Pending, taskID) @@ -389,3 +396,23 @@ func (s *Service) TaskFinished(taskID int, dummy *int) error { } return err } + +// TaskFailed tell the service that a task is failed. +func (s *Service) TaskFailed(taskID int, epoch int) error { + select { + case <-s.ready: + } + + s.mu.Lock() + defer s.mu.Unlock() + + t, ok := s.taskQueues.Pending[taskID] + if !ok { + err := errors.New("pending task not found") + log.WithFields(s.logFields()).Warningln("TaskFailed:Pending task #%d not found.", taskID) + return err + } + + s.checkTaskStatus(t, epoch) + return nil +} -- GitLab From 52cc601b48f6f5e179efa79bb2ba5442d42eac75 Mon Sep 17 00:00:00 2001 From: gongweibao Date: Tue, 4 Jul 2017 02:41:47 +0000 Subject: [PATCH 1000/3256] fix bugs --- go/master/service.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/go/master/service.go b/go/master/service.go index b078f318f57..c47319317ad 100644 --- a/go/master/service.go +++ b/go/master/service.go @@ -41,7 +41,7 @@ type Task struct { type taskEntry struct { NumTimeout int Task Task - FailedNum int + NumFailed int } type taskQueues struct { @@ -275,13 +275,13 @@ func (s *Service) checkTaskStatus(t taskEntry, epoch int) { delete(s.taskQueues.Pending, t.Task.ID) t.NumTimeout++ - if t.NumTimeout+t.FailedNum > s.failortimeoutMax { - log.Warningf("Task %v timed out %d times and failed %d times, discard.", t.Task, t.NumTimeout, t.FailedNum) + if t.NumTimeout+t.NumFailed > s.failortimeoutMax { + log.Warningf("Task %v timed out %d times and failed %d times, discard.", t.Task, t.NumTimeout, t.NumFailed) s.taskQueues.Failed = append(s.taskQueues.Failed, t) return } - log.Warningf("Task %v timed out %d times and failed %d times, discard.", t.Task, t.NumTimeout, t.FailedNum) + log.Warningf("Task %v timed out %d times and failed %d times, discard.", t.Task, t.NumTimeout, t.NumFailed) s.taskQueues.Todo = append(s.taskQueues.Todo, t) return } @@ -378,7 +378,7 @@ func (s *Service) TaskFinished(taskID int, dummy *int) error { // task finished, reset timeout t.NumTimeout = 0 - t.FailedNum = 0 + t.NumFailed = 0 s.taskQueues.Done = append(s.taskQueues.Done, t) delete(s.taskQueues.Pending, taskID) -- GitLab From ed18647e37f4e345f02171f29af6e22fab4790ea Mon Sep 17 00:00:00 2001 From: qijun Date: Tue, 4 Jul 2017 11:00:59 +0800 Subject: [PATCH 1001/3256] finish test --- paddle/platform/CMakeLists.txt | 1 - paddle/platform/cuda.h | 1 + paddle/platform/device_context.h | 170 ------------------------- paddle/platform/device_context_test.cu | 29 ----- 4 files changed, 1 insertion(+), 200 deletions(-) delete mode 100644 paddle/platform/device_context.h delete mode 100644 paddle/platform/device_context_test.cu diff --git a/paddle/platform/CMakeLists.txt b/paddle/platform/CMakeLists.txt index c95b54a4dfa..ffdc23d5995 100644 --- a/paddle/platform/CMakeLists.txt +++ b/paddle/platform/CMakeLists.txt @@ -4,4 +4,3 @@ cc_library(place SRCS place.cc) cc_test(place_test SRCS place_test.cc DEPS place glog gflags) cc_library(dynamic_loader SRCS dynamic_loader.cc) -nv_test(device_context_test SRCS device_context_test.cu DEPS place dynamic_loader glog gflags) diff --git a/paddle/platform/cuda.h b/paddle/platform/cuda.h index 05290b0e1e7..5ed36c0f025 100644 --- a/paddle/platform/cuda.h +++ b/paddle/platform/cuda.h @@ -33,6 +33,7 @@ int GetDeviceCount(void) { throw_on_error(cudaGetDeviceCount(&count), "cudaGetDeviceCount failed"); return count; } + int GetCurrentDeviceId(void) { int device_id; throw_on_error(cudaGetDevice(&device_id), "cudaGetDevice failed"); diff --git a/paddle/platform/device_context.h b/paddle/platform/device_context.h deleted file mode 100644 index 65e76666a79..00000000000 --- a/paddle/platform/device_context.h +++ /dev/null @@ -1,170 +0,0 @@ -/* 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 - -#ifndef PADDLE_ONLY_CPU -#include "paddle/platform/cublas.h" -#include "paddle/platform/cuda.h" -#include "paddle/platform/cudnn.h" -#include "paddle/platform/curand.h" -#define EIGEN_USE_GPU -#endif - -#include "paddle/framework/enforce.h" -#include "paddle/platform/place.h" -#include "unsupported/Eigen/CXX11/Tensor" - -namespace paddle { -namespace platform { - -class DeviceContext { - public: - virtual ~DeviceContext() {} -}; - -class CpuDeviceContext : public DeviceContext { - Eigen::DefaultDevice eigen_device() { - if (!eigen_device_) { - eigen_device_ = new Eigen::DefaultDevice(); - } - return *eigen_device_; - } - - private: - Eigen::DefaultDevice* eigen_device_{nullptr}; -}; - -#ifndef PADDLE_ONLY_CPU -class DeviceGuard { - public: - explicit DeviceGuard(GPUPlace new_place) : previous_(GetCurrentDeviceId()) { - if (previous_ != new_place) { - paddle::platform::SetDeviceId(new_place.device); - } - } - - ~DeviceGuard() { paddle::platform::SetDeviceId(previous_.device); } - - private: - GPUPlace previous_; -}; - -class CudaDeviceContext : public DeviceContext { - public: - explicit CudaDeviceContext(const GPUPlace gpu_place) : gpu_place_(gpu_place) { - DeviceGuard guard(gpu_place_); - paddle::platform::throw_on_error(cudaStreamCreate(&stream_), - "cudaStreamCreate failed"); - eigen_stream_ = new Eigen::CudaStreamDevice(&stream_); - eigen_device_ = new Eigen::GpuDevice(eigen_stream_); - } - - void Wait() { - paddle::platform::throw_on_error(cudaStreamSynchronize(stream_), - "cudaStreamSynchronize failed"); - } - - cudaStream_t stream() { return stream_; } - - Eigen::GpuDevice eigen_device() { return *eigen_device_; } - - cublasHandle_t cublas_handle() { - if (!blas_handle_) { - DeviceGuard guard(gpu_place_); - PADDLE_ENFORCE( - paddle::dyload::cublasCreate(&blas_handle_) == CUBLAS_STATUS_SUCCESS, - "cublasCreate failed"); - PADDLE_ENFORCE(paddle::dyload::cublasSetStream(blas_handle_, stream_) == - CUBLAS_STATUS_SUCCESS, - "cublasSetStream failed"); - } - return blas_handle_; - } - - cudnnHandle_t cudnn_handle() { - if (!dnn_handle_) { - DeviceGuard guard(gpu_place_); - PADDLE_ENFORCE( - paddle::dyload::cudnnCreate(&dnn_handle_) == CUDNN_STATUS_SUCCESS, - "cudnnCreate failed"); - PADDLE_ENFORCE(paddle::dyload::cudnnSetStream(dnn_handle_, stream_) == - CUDNN_STATUS_SUCCESS, - "cudnnSetStream failed"); - } - return dnn_handle_; - } - - curandGenerator_t curand_generator() { - if (!rand_generator_) { - DeviceGuard guard(gpu_place_); - PADDLE_ENFORCE(paddle::dyload::curandCreateGenerator( - &rand_generator_, CURAND_RNG_PSEUDO_DEFAULT) == - CURAND_STATUS_SUCCESS, - "curandCreateGenerator failed"); - PADDLE_ENFORCE( - paddle::dyload::curandSetPseudoRandomGeneratorSeed( - rand_generator_, random_seed_) == CURAND_STATUS_SUCCESS, - "curandSetPseudoRandomGeneratorSeed failed"); - PADDLE_ENFORCE(paddle::dyload::curandSetStream( - rand_generator_, stream_) == CURAND_STATUS_SUCCESS, - "curandSetStream failed"); - } - return rand_generator_; - } - - ~CudaDeviceContext() { - Wait(); - if (blas_handle_) { - PADDLE_ENFORCE( - paddle::dyload::cublasDestroy(blas_handle_) == CUBLAS_STATUS_SUCCESS, - "cublasDestroy failed"); - } - - if (dnn_handle_) { - PADDLE_ENFORCE( - paddle::dyload::cudnnDestroy(dnn_handle_) == CUDNN_STATUS_SUCCESS, - "cudnnDestroy failed"); - } - - if (rand_generator_) { - PADDLE_ENFORCE(paddle::dyload::curandDestroyGenerator(rand_generator_) == - CURAND_STATUS_SUCCESS, - "curandDestroyGenerator failed"); - } - - delete eigen_stream_; - delete eigen_device_; - - paddle::platform::throw_on_error(cudaStreamDestroy(stream_), - "cudaStreamDestroy failed"); - } - - private: - GPUPlace gpu_place_; - cudaStream_t stream_; - - Eigen::CudaStreamDevice* eigen_stream_; - Eigen::GpuDevice* eigen_device_; - - cublasHandle_t blas_handle_{nullptr}; - - cudnnHandle_t dnn_handle_{nullptr}; - - int random_seed_; - curandGenerator_t rand_generator_{nullptr}; -}; -#endif -} // namespace platform -} // namespace paddle diff --git a/paddle/platform/device_context_test.cu b/paddle/platform/device_context_test.cu deleted file mode 100644 index a15fb53b719..00000000000 --- a/paddle/platform/device_context_test.cu +++ /dev/null @@ -1,29 +0,0 @@ -/* 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/platform/device_context.h" -#include "gtest/gtest.h" - - -TEST(DeviceContext, CudaDevice) { - int count = paddle::platform::GetDeviceCount(); - for (int i = 0; i < count; i++) { - paddle::platform::CudaDeviceContext* device_context = new paddle::platform::CudaDeviceContext(i); - __attribute__((unused)) Eigen::GpuDevice gpu_device = device_context->eigen_device(); - __attribute__((unused)) cudnnHandle_t cudnn_handle = device_context->cudnn_handle(); - __attribute__((unused)) cublasHandle_t cublas_handle = device_context->cublas_handle(); - __attribute__((unused)) curandGenerator_t curand_handle = device_context->curand_generator(); - delete device_context; - } -} -- GitLab From 76b7be46da5fe211d25e62712673cc01bea98d54 Mon Sep 17 00:00:00 2001 From: qijun Date: Tue, 4 Jul 2017 11:16:49 +0800 Subject: [PATCH 1002/3256] add deps for dyload cc_library --- paddle/platform/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paddle/platform/CMakeLists.txt b/paddle/platform/CMakeLists.txt index ffdc23d5995..4f6381b8afd 100644 --- a/paddle/platform/CMakeLists.txt +++ b/paddle/platform/CMakeLists.txt @@ -3,4 +3,4 @@ nv_test(cuda_test SRCS cuda_test.cu) cc_library(place SRCS place.cc) cc_test(place_test SRCS place_test.cc DEPS place glog gflags) -cc_library(dynamic_loader SRCS dynamic_loader.cc) +cc_library(dynamic_loader SRCS dynamic_loader.cc DEPS glog gflags) -- GitLab From 9f365d36364d34f2cf186d5bc0569189145c612d Mon Sep 17 00:00:00 2001 From: dongzhihong Date: Tue, 4 Jul 2017 11:23:49 +0800 Subject: [PATCH 1003/3256] "add net proto" --- paddle/framework/CMakeLists.txt | 4 +++ paddle/framework/net.h | 48 ++++++++++---------------------- paddle/framework/net_proto.proto | 16 +++++++++++ 3 files changed, 35 insertions(+), 33 deletions(-) create mode 100644 paddle/framework/net_proto.proto diff --git a/paddle/framework/CMakeLists.txt b/paddle/framework/CMakeLists.txt index f7e5753ac2c..8c34a77c207 100644 --- a/paddle/framework/CMakeLists.txt +++ b/paddle/framework/CMakeLists.txt @@ -7,4 +7,8 @@ cc_test(scope_test SRCS scope_test.cc) cc_test(enforce_test SRCS enforce_test.cc) proto_library(attr_type SRCS attr_type.proto) proto_library(op_proto SRCS op_proto.proto) + cc_test(op_proto_test SRCS op_proto_test.cc DEPS op_proto attr_type protobuf) + +proto_library(net_proto SRCS net_proto.proto) +cc_library(net SRCS net.cc DEPS net_proto attr_type op_proto) diff --git a/paddle/framework/net.h b/paddle/framework/net.h index 88bdf0bb68b..b3064e4f90b 100644 --- a/paddle/framework/net.h +++ b/paddle/framework/net.h @@ -14,6 +14,8 @@ #pragma once +#include "paddle/framework/net_proto.pb.h" +#include "paddle/framework/op_proto.pb.h" #include "paddle/framework/scope.h" namespace paddle { @@ -27,31 +29,11 @@ typedef int OpIndex; * keep updating if the concepts related are implemented. */ -// Operator's runtime context. -struct OpContext { - int dev_id; - DevType dev_type{kCPU}; - enum DevType { kCPU, kGPU }; -}; - -// Proto definitions, use `struct`s for simpility. -struct VarDesc { - std::string type; - std::vector dims; -}; -struct OpDesc { - std::string type; - std::vector inputs; - std::vector outputs; -}; -struct struct NetDesc { - std::vector ops; -}; class Operator { public: Operator(const OpDesc &def) {} - Error InferShape() {} - Error Run() {} + bool InferShape() {} + bool Run() {} }; /** @@ -73,7 +55,7 @@ class Net { /** * @brief Infer shapes of all inputs and outputs of operators. */ - virtual Error InferShape(Scope *scope) override; + virtual bool InferShape(Scope *scope) override; /** * @brief Run the network. * @@ -82,8 +64,8 @@ class Net { * environment for ops. `begin` and `end` specify the scope of `ops_` to run, * If no positive indexes are provided, all operators in `ops_` will run. */ - virtual Error Run(Scope *scope, OpContext *context, OpIndex begin = -1, - OpIndex end = -1) const = 0; + virtual bool Run(Scope *scope, OpContext *context, OpIndex begin = -1, + OpIndex end = -1) const = 0; /** * @brief Add an Operator according to `def`. @@ -93,12 +75,12 @@ class Net { /** * @brief Add optimizer operators acctording to `attrs`. */ - virtual Error AddOptimizerOps(const OptAttrs &attrs) = 0; + virtual bool AddOptimizerOps(const OptAttrs &attrs) = 0; /** * @brief Add backward operators. */ - virtual Error AddBackwardOps() = 0; + virtual bool AddBackwardOps() = 0; /** * @brief Create a network. @@ -126,7 +108,7 @@ class PlainNet : public Net { * Infer all the operators' input and output varialbes' shapes, will be called * before every mini-batch */ - virtual Error InferShape(Scope *scope) override; + virtual bool InferShape(Scope *scope) override; /** * @brief Run the network. @@ -135,8 +117,8 @@ class PlainNet : public Net { * scope will be used instead. If no OpContext is provicded, default context * will be used. */ - virtual Error Run(Scope *scope = nullptr, OpContext *context = nullptr, - OpIndex begin = -1, OpIndex end = -1) const override; + virtual bool Run(Scope *scope = nullptr, OpContext *context = nullptr, + OpIndex begin = -1, OpIndex end = -1) const override; /** * @brief Add an operator to this network. @@ -146,12 +128,12 @@ class PlainNet : public Net { /** * @brief Add all optimizer operators related into the network. */ - virtual Error AddOptimizerOps(const OptAttrs &attrs) override; + virtual bool AddOptimizerOps(const OptAttrs &attrs) override; /** * @brief Add all backward operators related into the network. */ - virtual Error AddBackwardOps() override; + virtual bool AddBackwardOps() override; protected: /** @@ -159,7 +141,7 @@ class PlainNet : public Net { * * Create operators accordding to `def`, will be called by the constructor. */ - Error BuildNet(const NetDesc &def); + bool BuildNet(const NetDesc &def); /** * @brief Add an operator into this network. diff --git a/paddle/framework/net_proto.proto b/paddle/framework/net_proto.proto new file mode 100644 index 00000000000..e9aed8f349b --- /dev/null +++ b/paddle/framework/net_proto.proto @@ -0,0 +1,16 @@ +syntax="proto2"; +package paddle.framework; + +import "op_proto.proto" + +message NetDesc { + // network identification + optional string name = 1; + // operator contains in network + repeated OpProto operators = 2; + // network type to run with. e.g "plainNet", "DAG" + optional string type = 3; + // num worker always + optional int32 num_workers = 4; +} + -- GitLab From 3b073fdc2be1c808db27519e01e3a61c07927959 Mon Sep 17 00:00:00 2001 From: Luo Tao Date: Tue, 4 Jul 2017 11:25:11 +0800 Subject: [PATCH 1004/3256] fix error in test_LayerGrad --- paddle/gserver/tests/test_LayerGrad.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paddle/gserver/tests/test_LayerGrad.cpp b/paddle/gserver/tests/test_LayerGrad.cpp index ed067e7c3a1..d3c99eb8b97 100644 --- a/paddle/gserver/tests/test_LayerGrad.cpp +++ b/paddle/gserver/tests/test_LayerGrad.cpp @@ -873,7 +873,7 @@ TEST(Layer, SequenceLastInstanceLayer) { TEST(Layer, AverageLayer) { testDegradeLayer(false, "average", "non-seq", -1); // seq average to non-seq testDegradeLayer(false, - "max", + "average", "non-seq", 5); // seq average to a shorten seq, stride window = 5 testDegradeLayer( -- GitLab From f535b79820ae97ade802053dc421a893460367c8 Mon Sep 17 00:00:00 2001 From: Luo Tao Date: Tue, 4 Jul 2017 12:05:52 +0800 Subject: [PATCH 1005/3256] sort the Author.md with Alphabetical order --- AUTHORS.md | 74 +++++++++++++++++++++++++++--------------------------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/AUTHORS.md b/AUTHORS.md index 09698ac1409..4db4a4a8e74 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -1,48 +1,48 @@ | Github account | name | |---|---| +| backyes | Yan-Fei Wang | | beckett1124 | Bin Qi | -| Canpio | Jiayi Feng | -| chengxiaohua1105 | Xiaohua Cheng | -| xushaoyong | Shaoyong Xu | -| liuyuan | Yuan Liu | -| xujun05 | Jun Xu | -| dzhwinter | Zhihong Dong | -| Guo Sheng | Sheng Guo | -| kuke | Yibing Liu | -| llxxxll | YongFeng Liu | -| cxysteven | Xingyi Cheng | -| NHZlX | Zhaolong Xing | -| pakchoi | Chuanjiang Song | -| pkuyym | Yaming Yang | -| Superjom | Chunwei Yan | -| wanghaoshuang | Haoshuang Wang | -| wangzhen-nlp | Zhen Wang | -| wwhu | Weiwei Hu | -| xinghai-sun | XingHai Sun | -| zhaopu7 | Pu Zhao | -| reyoung | Yang Yu | +| Canpio | Jia-Yi Feng | +| chengxiaohua1105 | Xiao-Hua Cheng | +| cxwangyi, yiwangbaidu, wangkuiyi | Yi Wang | +| cxysteven | Xing-Yi Cheng | +| dzhwinter | Zhi-Hong Dong | +| emailweixu | Wei Xu | | gangliao | Gang Liao | -| luotao01 | Tao Luo | -| jacquesqiao | Long-Fei Qiao | -| qingqing01 | Qing-Qing Dang | +| gongweibao | Wei-Bao Gong | +| Guo Sheng | Sheng Guo | +| Haichao-Zhang | Hai-Chao Zhang | | hedaoyuan | Dao-Yuan He | -| wangyang59 | Yang Wang | +| helinwang | He-Lin Wang | +| jacquesqiao | Long-Fei Qiao | +| kuke | Yi-Bing Liu | +| lcy-seso | Ying Cao | +| lipeng-unisound | Peng Li | +| liuyuan | Yuan Liu | +| livc | Zhao Li | +| llxxxll | Yong-Feng Liu | +| luotao01 | Tao Luo | +| lzhao4ever | Liang Zhao | +| NHZlX | Zhao-Long Xing | +| pakchoi | Chuan-Jiang Song | +| pengli09 | Peng Li | +| pkuyym | Ya-Ming Yang | | QiJune | Jun Qi | +| qingqing01 | Qing-Qing Dang | +| reyoung | Yang Yu | +| Superjom | Chun-Wei Yan | | tianbingsz | Tian-Bing Xu | -| cxwangyi, yiwangbaidu, wangkuiyi | Yi Wang | | typhoonzero | Yi Wu | -| backyes | Yan-Fei Wang | -| pengli09 | Peng Li | -| livc | Zhao Li | +| wanghaoshuang | Hao-Shuang Wang | +| wangyang59 | Yang Wang | +| wangzhen-nlp | Zhen Wang | +| wen-bo-yang | Wen-Bo Yang | +| wwhu | Wei-Wei Hu | +| xinghai-sun | Xing-Hai Sun | | Xreki | Yi-Qun Liu | +| xujun05 | Jun Xu | +| xushaoyong | Shao-Yong Xu | | Yancey1989 | Xu Yan | -| emailweixu | Wei Xu | -| wen-bo-yang | Wen-Bo Yang | -| helinwang | He-Lin Wang | -| lcy-seso | Ying Cao | -| Zrachel | Rui-Qing Zhang | -| Haichao-Zhang | Hai-Chao Zhang | -| gongweibao | Wei-Bao Gong | -| lzhao4ever | Liang Zhao | +| zhaopu7 | Pu Zhao | | zhouxiao-coder | Xiao Zhou | -| lipeng-unisound | Peng Li | +| Zrachel | Rui-Qing Zhang | -- GitLab From 06156daa281e55fe5d06217cc545cd8c09aa4c9d Mon Sep 17 00:00:00 2001 From: "Superjom (Chunwei Yan)" Date: Tue, 4 Jul 2017 12:07:16 +0800 Subject: [PATCH 1006/3256] net design with NetBuilder (#2598) * move net_design to framework * change CreateNet result to unique_ptr * rename "ScratchNet" -> "PlainNet" * add three methods to NetBase * add NetBuilder * add InferShape to NetBuilder.Run * rename ApplyGradient, ApplyOptimizer -> AddGradientOps, AddOptimiz * rename PlainNet::CreateNet -> BuildNet * add Error and other rename actions --- paddle/framework/net_design.md | 250 +++++++++++++++++++++++++++++++++ 1 file changed, 250 insertions(+) create mode 100644 paddle/framework/net_design.md diff --git a/paddle/framework/net_design.md b/paddle/framework/net_design.md new file mode 100644 index 00000000000..a5f0483081e --- /dev/null +++ b/paddle/framework/net_design.md @@ -0,0 +1,250 @@ +# Network Design + +`Network` is the container and controller of a set of operators, +user can build a real network from a `NetDesc` which is a protobuf message +and use `Network.Run()` to run all the operators in the network. + +A network object knows all Operators belonging to this network. Variables, +which are inputs and outputs of these operators, +are created and managed by a hierarchy of Scope objects. + +# API + +## Net +To make the `Network` extendable, a base class is defined like this + +```c++ +// operator's index stored in a network. +typedef int OpIndex; + +// The minimum a network should be implemented. +class Net { + public: + // run all the operators and return success(true) or not, with all the + // variables are located in `scope`. `context` describes the detail execution + // environment for ops. `begin` and `end` specify the scope of `ops_` to run, + // If no positive indexes are provided, all operators in `ops_` will run. + virtual Error Run(Scope *scope, OpContext *context, OpIndex begin = -1, + OpIndex end = -1) const = 0; + + // Add an Operator according to `def`. + virtual OpIndex AddOp(const proto::OpDef &def) = 0; + + // Add optimizer operators acctording to `attrs`. + virtual Error AddOptimizerOps(const OptAttrs &attrs) = 0; + + // Add backward operators. + virtual Error AddBackwardOps() = 0; + + // Infer the shapes of variables required by operators in the network. The + // `scope` will be mutated according to the inferred shapes. + + static std::unique_ptr Create(const NetDesc &def = NetDesc()); +}; +``` + +All network implementations should build networks from a protobuf message which +describes the structure of a real network; `Run` method should be implemented by +all implementations to offer a universal method to forward or backward compute a network. + +`Net::Create` is a method of factory pattern and can be implemented like + +```c++ +std::unique Net::Create(const NetDesc& def) { + switch (def.model_type()) { + case NN: + return new Network(def); + case Recursive: + return new RecursiveNet(def); + case Recurrent: + return new RecurrentNet(def); + } + return nullptr; +} +``` + +Network is designed as the container of operators. to make it more extendable, +we decouple it from the related variable resources. + +`Run(Scope* scope)` takes the scope as a argument so that it can run in different scopes. + +Finally, `Net` can be used as followed + +```c++ +Scope default_scope; +OpContext default_context; +auto net = Net::CreateNet(def); + +if (net) { + net.Run(&default_scope, &default_context); +} +``` + +## `PlainNet` as a simple implementation of `BaseNet` + +A very basic implementation is as follows. All it does is simply to run every operators in sequence. + +```c++ +class PlainNet : public Net { + public: + // Create a network describe by `def`. NetDesc is the definition of a network. + PlainNet(const NetDesc &def); + + // Infer all the operators' input and output varialbes' shapes, will be called before every mini-batch + training. + virtual Error InferShape(Scope *scope) override; + + // Run all the operators with the `scope`, if no scope is provided, default + // scope will be used instead. If no OpContext is provicded, default context will be used. + virtual Error Run(Scope *scope = nullptr, OpContext *context=nullptr, OpIndex begin = -1, + OpIndex end = -1) const override; + + virtual OpIndex AddOp(const proto::OpDef &def) override; + + virtual Error AddOptimizerOps(const OptAttrs &attrs) override; + + virtual Error AddBackwardOps() override; + + protected: + // Create operators accordding to `def`, will be called by the constructor. + Error BuildNet(const NetDesc &def); + + // Add a operator which is identified as `type` and has attributes described + // in `attrs`, the `inputs` are the keys of readonly input variables, + // `outputs` are keys of mutable output variables. An `OpIndex` will be + // returned to indicate the offset of the new operator in `ops_`. + OpIndex AddOp(const std::string &type, const std::vector &inputs, + const std::vector &outputs, + const OprAttr &attrs = OprAttr()); + + private: + // the operators owned by `Network`. + std::vector ops_; +}; +``` + +`PlainNet` will create operators so that a private member `ops_` is defined, +the operators are created by `CreateNet`, and each operator is created by `AddOp`. + + +## PlainNet Usage +`PlainNet` can be used to define and run a network as follows + +```c++ +// create an empty scope located on CPU device. +Scope scope(CPUPlace()); + +// create and init variables described in `net_desc`. +scope.CreateVariables(net_desc); +scope.InitVariables(net_desc); + +// create a network according to `net_desc` +auto net = Net::CreateNet(net_desc); +// Add more operators if needed. +net->AddOp(add...); +net->AddOp(fc...); + +net->AddBackwardOps(); +net->AddOptimizerOps(); + +// run the network providing the `scope`. +net.Run(&scope); +``` + +## `NetBuilder` as a C++ syntax wrapper +This is a detailed description of the user-related C++ network API, and may not needed in the prototype development stage. + +The `NetBuilder` will give users a much simpler syntax as follows to create a network, and demonstrates how to use the `BaseNet`'s raw interfaces. + +```c++ +Variable* fc_out = builder.AddOp("fc", input=image, size=100, activation="Sigmoid"); +Variable* prediction = builder.AddOp("fc", input=fc_out, size=10, activation="Sigmoid"); +Variable* loss = builder.AddOp("cross_entropy", input=prediction, label=label); +Variable* avg_loss = builder.AddOp("mean", loss); + +builder.BackwardFrom(avg_loss) +builder.AddOptimization(1e-4, "adam"); +builder.Run(); +``` + +`NetBuilder` will call `Net` 's virtual functions to change the real network structure, here is a sample definition + +```c++ +class NetBuilder final { + public: + NetBuilder(Net* net) : net_(net) {} + + Variable* AddOp(const string& type, const vector& inputs, + size_t size, Activation act) { + // much code here. + // ... + net_->AddOp(def); + need_rebuild_net_ = true; + net_->InferShape(); + // ... + } + + Error BackwardFrom(const Variable& cost); + + Error Run(Scope* scope, OpContext* context, bool need_backward = true) { + // backward. + if (need_backward) { + if (need_rebuild_net_) { + AddBackwardOps(); + AddOptimizerOps(); + } + net_->Run(scope, context); + return; + } + // just forward. + net_->Run(scope, context, 0, last_forward_op_); + } + + protected: + Error AddBackwardOps(); + Error AddOptimizerOps(); + + private: + Net* net_; + OpIndex last_forward_op_{-1}; + bool need_rebuild_net_{true}; +} +``` + +## Compatibility with RNN + +Benefitting from the decoupling of `PlainNet.Run` and `Scope`, `PlainNet` is compatible with future RNN design, +for example we can implement a simple recurrent neural network as follows + +```c++ +// copy some `vars` form `source` to `target` +void Copy(const Scope &source, Scope &target, + const std::vector &vars); + +Scope default_scope; +// some initial mutations on `default_scope` here. + +auto rnn_step_net = PlainNet(rnn_step_net_def); + +// Create rnn's states, the last scope is used to store rnn outputs. +Scope *rnn_states = new Scope[num_states + 1]; + +for (int i = 0; i < num_states + 1; i++) { + // Initialize all rnn state scopes, copy parameters and so on. + rnn_states[i].CreateVars(rnn_step_net_def); + Copy(default_scope, rnn_states[i], rnn_related_vars); + // Prepare rnn's inlinks, just copy inlink variables to each state. + Copy(default_scope, rnn_states[i], inlink_vars); +} + +// Run the rnn. +for (int i = 0; i < num_states; i++) { + rnn_step_net.Run(rnn_states[i]); + // Copy current state's state variables to next state, the related variables + // are named like "previous_state_xxx". + Copy(rnn_states[i], rnn_states[i + 1], pre_state_vars) +} + +// Copy rnn's final outputs to `default_scope`. +Copy(rnn_states[num_states], default_scope, outlink_vars); +``` -- GitLab From 3de3894b821c06daf596c0818b6c89f4dd185928 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Tue, 4 Jul 2017 12:53:00 +0800 Subject: [PATCH 1007/3256] Add DEPS to `proto_library` Missing DEPS will cause compile error when parallel is large. --- cmake/generic.cmake | 4 ++-- paddle/framework/CMakeLists.txt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cmake/generic.cmake b/cmake/generic.cmake index ca358da8f14..fb2222440ca 100644 --- a/cmake/generic.cmake +++ b/cmake/generic.cmake @@ -323,10 +323,10 @@ endfunction(go_test) function(proto_library TARGET_NAME) set(oneValueArgs "") - set(multiValueArgs SRCS) + set(multiValueArgs SRCS DEPS) cmake_parse_arguments(proto_library "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) set(proto_srcs) set(proto_hdrs) protobuf_generate_cpp(proto_srcs proto_hdrs ${proto_library_SRCS}) - cc_library(${TARGET_NAME} SRCS ${proto_srcs} DEPS protobuf) + cc_library(${TARGET_NAME} SRCS ${proto_srcs} DEPS ${proto_library_DEPS} protobuf) endfunction() diff --git a/paddle/framework/CMakeLists.txt b/paddle/framework/CMakeLists.txt index f7e5753ac2c..e7818667592 100644 --- a/paddle/framework/CMakeLists.txt +++ b/paddle/framework/CMakeLists.txt @@ -6,5 +6,5 @@ cc_test(variable_test SRCS variable_test.cc) cc_test(scope_test SRCS scope_test.cc) cc_test(enforce_test SRCS enforce_test.cc) proto_library(attr_type SRCS attr_type.proto) -proto_library(op_proto SRCS op_proto.proto) +proto_library(op_proto SRCS op_proto.proto DEPS attr_type) cc_test(op_proto_test SRCS op_proto_test.cc DEPS op_proto attr_type protobuf) -- GitLab From 4e1617d05994bda1a9eb0e0b5b563249cc12f271 Mon Sep 17 00:00:00 2001 From: liaogang Date: Tue, 4 Jul 2017 13:15:00 +0800 Subject: [PATCH 1008/3256] ENH: add buddy alloctor Free --- paddle/memory/detail/CMakeLists.txt | 3 + paddle/memory/detail/buddy_allocator.cc | 19 ++-- paddle/memory/detail/buddy_allocator.h | 10 +- paddle/memory/detail/memory_block.cc | 145 ++++++++++++++++++++++++ paddle/memory/detail/memory_block.h | 97 ++++++++++++++++ paddle/memory/detail/system_allocator.h | 1 + 6 files changed, 262 insertions(+), 13 deletions(-) create mode 100644 paddle/memory/detail/memory_block.cc create mode 100644 paddle/memory/detail/memory_block.h diff --git a/paddle/memory/detail/CMakeLists.txt b/paddle/memory/detail/CMakeLists.txt index 6caa97a76bb..dbc98a8a62f 100644 --- a/paddle/memory/detail/CMakeLists.txt +++ b/paddle/memory/detail/CMakeLists.txt @@ -7,3 +7,6 @@ else(${WITH_GPU}) cc_library(system_allocator SRCS system_allocator.cc DEPS gflags) cc_test(system_allocator_test SRCS system_allocator_test.cc DEPS system_allocator gflags) endif(${WITH_GPU}) + +cc_library(metadata SRCS metadata.cc) +cc_library(buddy_allocator SRCS buddy_allocator.cc) diff --git a/paddle/memory/detail/buddy_allocator.cc b/paddle/memory/detail/buddy_allocator.cc index 2462ba084b9..e8d694327d8 100644 --- a/paddle/memory/detail/buddy_allocator.cc +++ b/paddle/memory/detail/buddy_allocator.cc @@ -58,17 +58,16 @@ void* BuddyAllocator::Alloc(size_t unaligned_size) { // refill the pool if failure if (it == pool_.end()) { it = RefillPool(); + // if still failure, fail fatally + if (it == pool_.end()) { + return nullptr; + } } else { DLOG(INFO) << " Allocation from existing memory block " << std::get<2>(*it) << " at address " << reinterpret_cast(std::get<2>(*it))->data(); } - // if still failure, fail fatally - if (it == pool_.end()) { - return nullptr; - } - total_used_ += size; total_free_ -= size; @@ -76,6 +75,13 @@ void* BuddyAllocator::Alloc(size_t unaligned_size) { return reinterpret_cast(SplitToAlloc(it, size))->data(); } +void BuddyAllocator::Free(void* p) { + auto block = static_cast(p)->metadata(); + + // acquire the allocator lock + std::lock_guard lock(mutex_); +} + void* BuddyAllocator::SystemAlloc(size_t size) { size_t index = 0; void* p = system_allocator_->Alloc(index, size); @@ -140,17 +146,14 @@ BuddyAllocator::PoolSet::iterator BuddyAllocator::FindExistChunk(size_t size) { void* BuddyAllocator::SplitToAlloc(BuddyAllocator::PoolSet::iterator it, size_t size) { auto block = static_cast(std::get<2>(*it)); - pool_.erase(it); DLOG(INFO) << " Split block (" << block << ", " << block->total_size(cache_) << ") into"; - block->split(cache_, size); DLOG(INFO) << " Left block (" << block << ", " << block->total_size(cache_) << ")"; - block->set_type(cache_, MemoryBlock::ARENA_CHUNK); // the rest of memory if exist diff --git a/paddle/memory/detail/buddy_allocator.h b/paddle/memory/detail/buddy_allocator.h index 38bedc9a183..4006bdcce8d 100644 --- a/paddle/memory/detail/buddy_allocator.h +++ b/paddle/memory/detail/buddy_allocator.h @@ -14,16 +14,16 @@ #pragma once -#include "paddle/memory/detail/system_allocator.h" #include "paddle/memory/detail/metadata.h" +#include "paddle/memory/detail/system_allocator.h" #include "paddle/platform/assert.h" #include "paddle/platform/cpu_info.h" #include "paddle/platform/gpu_info.h" -#include #include -#include +#include #include +#include namespace paddle { namespace memory { @@ -57,9 +57,9 @@ class BuddyAllocator { /*! \brief If existing chunks are not suitable, refill pool */ PoolSet::iterator RefillPool(); - /** + /** * \brief Find the suitable chunk from existing pool - * + * * \param it pool iterator which contains suitable block. * \param size the size of allocation. */ diff --git a/paddle/memory/detail/memory_block.cc b/paddle/memory/detail/memory_block.cc new file mode 100644 index 00000000000..1c9e87df497 --- /dev/null +++ b/paddle/memory/detail/memory_block.cc @@ -0,0 +1,145 @@ +#include "paddle/memory/detail/memory_block.h" +#include "paddle/platform/assert.h" + +namespace paddle { +namespace memory { +namespace detail { + +void MemoryBlock::init(MetadataCache& cache, Type t, size_t index, size_t size, + void* left_buddy, void* right_buddy) { + cache.store(this, + MemoryBlockMetadata(t, index, size - overhead(), size, + static_cast(left_buddy), + static_cast(right_buddy))); +} + +MemoryBlock::Type MemoryBlock::type(MetadataCache& cache) const { + return cache.load(this).type; +} + +size_t MemoryBlock::size(MetadataCache& cache) const { + return cache.load(this).size; +} + +size_t MemoryBlock::total_size(MetadataCache& cache) const { + return cache.load(this).total_size; +} + +MemoryBlock* MemoryBlock::left_buddy(MetadataCache& cache) const { + return cache.load(this).left_buddy; +} + +MemoryBlock* MemoryBlock::right_buddy(MetadataCache& cache) const { + return cache.load(this).right_buddy; +} + +void MemoryBlock::split(MetadataCache& cache, size_t size) { + // make sure the split fits + assert(total_size(cache) >= size); + + // bail out if there is no room for another partition + if (total_size(cache) - size <= overhead()) { + return; + } + + // find the position of the split + void* right_partition = reinterpret_cast(this) + size; + + size_t remaining_size = total_size(cache) - size; + + // Add the new block as a buddy + auto metadata = cache.load(this); + + // Write the metadata for the new block + auto new_block_right_buddy = metadata.right_buddy; + + cache.store(static_cast(right_partition), + MemoryBlockMetadata(FREE_MEMORY, index(cache), + remaining_size - overhead(), remaining_size, + this, new_block_right_buddy)); + + metadata.right_buddy = static_cast(right_partition); + metadata.size = size - overhead(); + metadata.total_size = size; + + cache.store(this, metadata); + + // Write metadata for the new block's right buddy + if (new_block_right_buddy != nullptr) { + auto buddy_metadata = cache.load(new_block_right_buddy); + + buddy_metadata.left_buddy = static_cast(right_partition); + + cache.store(new_block_right_buddy, buddy_metadata); + } +} + +void MemoryBlock::merge(MetadataCache& cache, MemoryBlock* right_buddy) { + // only free blocks can be merged + assert(type(cache) == FREE_MEMORY); + assert(right_buddy->type(cache) == FREE_MEMORY); + + auto metadata = cache.load(this); + + // link this->buddy's buddy + metadata.right_buddy = right_buddy->right_buddy(cache); + + // link buddy's buddy -> this + if (metadata.right_buddy != nullptr) { + auto buddy_metadata = cache.load(metadata.right_buddy); + + buddy_metadata.left_buddy = this; + + cache.store(metadata.right_buddy, buddy_metadata); + } + + metadata.size += right_buddy->total_size(cache); + metadata.total_size += right_buddy->total_size(cache); + + cache.store(this, metadata); + cache.store(right_buddy, + MemoryBlockMetadata(INVALID_MEMORY, 0, 0, 0, nullptr, nullptr)); +} + +void MemoryBlock::mark_as_free(MetadataCache& cache) { + // check for double free or corruption + assert(type(cache) != FREE_MEMORY); + assert(type(cache) != INVALID_MEMORY); + + set_type(cache, FREE_MEMORY); +} + +void MemoryBlock::set_type(MetadataCache& cache, Type t) { + auto metadata = cache.load(this); + + metadata.type = t; + + cache.store(this, metadata); +} + +bool MemoryBlock::has_left_buddy(MetadataCache& cache) const { + return left_buddy(cache) != nullptr; +} + +bool MemoryBlock::has_right_buddy(MetadataCache& cache) const { + return right_buddy(cache) != nullptr; +} + +size_t MemoryBlock::index(MetadataCache& cache) const { + return cache.load(this).index; +} + +void* MemoryBlock::data() const { + return const_cast( + reinterpret_cast(this)) + + 1; +} + +MemoryBlock* MemoryBlock::metadata() const { + return const_cast(reinterpret_cast( + reinterpret_cast(this) - 1)); +} + +} // detail +} // memory +} // paddle diff --git a/paddle/memory/detail/memory_block.h b/paddle/memory/detail/memory_block.h new file mode 100644 index 00000000000..e2d39c31cfb --- /dev/null +++ b/paddle/memory/detail/memory_block.h @@ -0,0 +1,97 @@ +/* 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/memory/detail/metadata.h" + +#include +#include + +namespace paddle { +namespace memory { +namespace detail { + +// Forward Declaration +class Metadata; + +/*! \brief A class used to interpret the contents of a memory block */ +class MemoryBlock { + public: + // Unify the metadata format between GPU and CPU allocations + using MetadataCache = std::unordered_map; + + enum Type { + FREE_CHUNK, // memory is free and idle + ARENA_CHUNK, // memory is being occupied + HUGE_CHUNK, // memory is out of management + INVALID_CHUNK // memory is invalid + }; + + public: + void init(MetadataCache& cache, Type t, size_t index, size_t size, + void* left_buddy, void* right_buddy); + + public: + /*! \brief The type of the allocation */ + Type type(MetadataCache& cache) const; + + /*! \brief The size of the data region */ + size_t size(MetadataCache& cache) const; + + /*! \brief An index to track the allocator */ + size_t index(MetadataCache& cache) const; + + /*! \brief The total size of the block */ + size_t total_size(MetadataCache& cache) const; + + /*! \brief Check the left buddy of the block */ + bool has_left_buddy(MetadataCache& cache) const; + + /*! \brief Check the right buddy of the block */ + bool has_right_buddy(MetadataCache& cache) const; + + /*! \brief Get the left buddy */ + MemoryBlock* left_buddy(MetadataCache& cache) const; + + /*! \brief Get the right buddy */ + MemoryBlock* right_buddy(MetadataCache& cache) const; + + public: + /*! \brief Split the allocation into left/right blocks */ + void split(MetadataCache& cache, size_t size); + + /*! \brief Merge left and right blocks together */ + void merge(MetadataCache& cache, MemoryBlock* right_buddy); + + /*! \brief Mark the allocation as free */ + void mark_as_free(MetadataCache& cache); + + /*! \brief Change the type of the allocation */ + void set_type(MetadataCache& cache, Type t); + + public: + /*! \brief Get a pointer to the memory block's data */ + void* data() const; + + /*! \brief Get a pointer to the memory block's metadata */ + MemoryBlock* metadata() const; + + public: + static size_t overhead(); +}; + +} // namespace detail +} // namespace memory +} // namespace paddle diff --git a/paddle/memory/detail/system_allocator.h b/paddle/memory/detail/system_allocator.h index f3bbfef8435..555061a533f 100644 --- a/paddle/memory/detail/system_allocator.h +++ b/paddle/memory/detail/system_allocator.h @@ -48,6 +48,7 @@ class GPUAllocator : public SystemAllocator { virtual void* Alloc(size_t& index, size_t size); virtual void Free(void* p, size_t size, size_t index); virtual bool UseGpu(); + private: size_t gpu_alloc_size_ = 0; size_t fallback_alloc_size_ = 0; -- GitLab From b8cc07920e3cf623250ea0b9b078049ff1348279 Mon Sep 17 00:00:00 2001 From: liaogang Date: Tue, 4 Jul 2017 13:20:55 +0800 Subject: [PATCH 1009/3256] FIX: add eigen3 interface deps --- cmake/external/eigen.cmake | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/cmake/external/eigen.cmake b/cmake/external/eigen.cmake index 45f44f617dc..39b16c3b2bd 100644 --- a/cmake/external/eigen.cmake +++ b/cmake/external/eigen.cmake @@ -5,7 +5,7 @@ SET(EIGEN_SOURCE_DIR ${THIRD_PARTY_PATH}/eigen3) INCLUDE_DIRECTORIES(${EIGEN_SOURCE_DIR}/src/eigen3) ExternalProject_Add( - eigen3 + extern_eigen3 ${EXTERNAL_PROJECT_LOG_ARGS} # for latest version, please get from official website # URL "https://bitbucket.org/eigen/eigen/get/3.3.4.tar.gz" @@ -26,4 +26,7 @@ ExternalProject_Add( TEST_COMMAND "" ) -LIST(APPEND external_project_dependencies eigen3) +ADD_LIBRARY(eigen3 INTERFACE) +ADD_DEPENDENCIES(eigen3 extern_eigen3) + +LIST(APPEND external_project_dependencies extern_eigen3) -- GitLab From 414c2b1734bcf39135935a201f7244d79a72e172 Mon Sep 17 00:00:00 2001 From: liaogang Date: Tue, 4 Jul 2017 13:25:30 +0800 Subject: [PATCH 1010/3256] FIX: add any as interface dep --- cmake/external/any.cmake | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/cmake/external/any.cmake b/cmake/external/any.cmake index 62eea42692b..b61e421871a 100644 --- a/cmake/external/any.cmake +++ b/cmake/external/any.cmake @@ -5,7 +5,7 @@ SET(ANY_SOURCE_DIR ${THIRD_PARTY_PATH}/any) INCLUDE_DIRECTORIES(${ANY_SOURCE_DIR}/src/linb_any) ExternalProject_Add( - linb_any + extern_lib_any ${EXTERNAL_PROJECT_LOG_ARGS} GIT_REPOSITORY "https://github.com/thelink2012/any.git" GIT_TAG "8fef1e93710a0edf8d7658999e284a1142c4c020" @@ -17,5 +17,8 @@ ExternalProject_Add( TEST_COMMAND "" ) +ADD_LIBRARY(lib_any INTERFACE) +ADD_DEPENDENCIES(lib_any extern_lib_any) + add_definitions(-DANY_IMPL_ANY_CAST_MOVEABLE) -LIST(APPEND external_project_dependencies linb_any) \ No newline at end of file +LIST(APPEND external_project_dependencies extern_lib_any) -- GitLab From c602e046132b7e4e38c34f348b2a7fa290d67361 Mon Sep 17 00:00:00 2001 From: Superjom Date: Tue, 4 Jul 2017 13:35:21 +0800 Subject: [PATCH 1011/3256] add fake interfaces to make compilable --- paddle/framework/net.cc | 10 +++++--- paddle/framework/net.h | 44 +++++++++++++++++++------------- paddle/framework/net_proto.proto | 3 +-- 3 files changed, 33 insertions(+), 24 deletions(-) diff --git a/paddle/framework/net.cc b/paddle/framework/net.cc index 0ce92968200..2d9e099dc0c 100644 --- a/paddle/framework/net.cc +++ b/paddle/framework/net.cc @@ -5,7 +5,7 @@ namespace framework { PlainNet::PlainNet(const NetDesc& def) {} -virtual Error PlainNet::InferShape() { +Error PlainNet::InferShape(Scope* scope) { for (auto& op : ops_) { // wrong shape auto err = op.InferShape(); @@ -15,9 +15,11 @@ virtual Error PlainNet::InferShape() { return Error(); } -virtual Error PlainNet::Run(Scope* scope = nullptr, - OpContext* context = nullptr, OpIndex begin = -1, - OpIndex end = -1) const {} +Error PlainNet::Run(Scope* scope, OpContext* context, OpIndex begin, + OpIndex end) const { + // TODO Add implementation here. + return Error(); +} } // namespace framework } // namespace paddle diff --git a/paddle/framework/net.h b/paddle/framework/net.h index b3064e4f90b..76e0ed93307 100644 --- a/paddle/framework/net.h +++ b/paddle/framework/net.h @@ -17,6 +17,7 @@ #include "paddle/framework/net_proto.pb.h" #include "paddle/framework/op_proto.pb.h" #include "paddle/framework/scope.h" +#include "paddle/utils/Error.h" namespace paddle { namespace framework { @@ -29,11 +30,16 @@ typedef int OpIndex; * keep updating if the concepts related are implemented. */ +struct OpDesc; +struct OpDef; +struct OpContext; +struct OpAttrs {}; + class Operator { public: Operator(const OpDesc &def) {} - bool InferShape() {} - bool Run() {} + Error InferShape() { return Error(); } + Error Run() { return Error(); } }; /** @@ -55,7 +61,7 @@ class Net { /** * @brief Infer shapes of all inputs and outputs of operators. */ - virtual bool InferShape(Scope *scope) override; + virtual Error InferShape(Scope *scope) = 0; /** * @brief Run the network. * @@ -64,28 +70,30 @@ class Net { * environment for ops. `begin` and `end` specify the scope of `ops_` to run, * If no positive indexes are provided, all operators in `ops_` will run. */ - virtual bool Run(Scope *scope, OpContext *context, OpIndex begin = -1, - OpIndex end = -1) const = 0; + virtual Error Run(Scope *scope, OpContext *context, OpIndex begin = -1, + OpIndex end = -1) const = 0; /** * @brief Add an Operator according to `def`. */ - virtual OpIndex AddOp(const proto::OpDef &def) = 0; + virtual OpIndex AddOp(const OpDef &def) = 0; /** * @brief Add optimizer operators acctording to `attrs`. */ - virtual bool AddOptimizerOps(const OptAttrs &attrs) = 0; + virtual Error AddOptimizerOps(const OpAttrs &attrs) = 0; /** * @brief Add backward operators. */ - virtual bool AddBackwardOps() = 0; + virtual Error AddBackwardOps() = 0; /** * @brief Create a network. */ static std::unique_ptr Create(const NetDesc &def = NetDesc()); + + virtual ~Net() = 0; }; /** @@ -108,7 +116,7 @@ class PlainNet : public Net { * Infer all the operators' input and output varialbes' shapes, will be called * before every mini-batch */ - virtual bool InferShape(Scope *scope) override; + virtual Error InferShape(Scope *scope) override; /** * @brief Run the network. @@ -117,23 +125,23 @@ class PlainNet : public Net { * scope will be used instead. If no OpContext is provicded, default context * will be used. */ - virtual bool Run(Scope *scope = nullptr, OpContext *context = nullptr, - OpIndex begin = -1, OpIndex end = -1) const override; + virtual Error Run(Scope *scope = nullptr, OpContext *context = nullptr, + OpIndex begin = -1, OpIndex end = -1) const override; /** * @brief Add an operator to this network. */ - virtual OpIndex AddOp(const proto::OpDef &def) override; + virtual OpIndex AddOp(const OpDef &def) override; /** * @brief Add all optimizer operators related into the network. */ - virtual bool AddOptimizerOps(const OptAttrs &attrs) override; + virtual Error AddOptimizerOps(const OpAttrs &attrs) override; /** * @brief Add all backward operators related into the network. */ - virtual bool AddBackwardOps() override; + virtual Error AddBackwardOps() override; protected: /** @@ -141,7 +149,7 @@ class PlainNet : public Net { * * Create operators accordding to `def`, will be called by the constructor. */ - bool BuildNet(const NetDesc &def); + Error BuildNet(const NetDesc &def); /** * @brief Add an operator into this network. @@ -151,9 +159,9 @@ class PlainNet : public Net { * `outputs` are keys of mutable output variables. An `OpIndex` will be * returned to indicate the offset of the new operator in `ops_`. */ - OpIndex AddOp(const std::string &type, const std::vector &inputs, - const std::vector &outputs, - const OprAttr &attrs = OprAttr()); + OpIndex AddOp(const std::string &type, const std::vector &inputs, + const std::vector &outputs, + const OpAttrs &attrs = OpAttrs()); private: // the operators owned by `Network`. diff --git a/paddle/framework/net_proto.proto b/paddle/framework/net_proto.proto index e9aed8f349b..2d042457e33 100644 --- a/paddle/framework/net_proto.proto +++ b/paddle/framework/net_proto.proto @@ -1,7 +1,7 @@ syntax="proto2"; package paddle.framework; -import "op_proto.proto" +import "op_proto.proto"; message NetDesc { // network identification @@ -13,4 +13,3 @@ message NetDesc { // num worker always optional int32 num_workers = 4; } - -- GitLab From 04e20034dfcbb0ceb1de30ddd5b1f8b8ee811d4f Mon Sep 17 00:00:00 2001 From: Superjom Date: Tue, 4 Jul 2017 13:44:01 +0800 Subject: [PATCH 1012/3256] replace Error with void --- paddle/framework/net.cc | 11 +++-------- paddle/framework/net.h | 23 +++++++++++------------ 2 files changed, 14 insertions(+), 20 deletions(-) diff --git a/paddle/framework/net.cc b/paddle/framework/net.cc index 2d9e099dc0c..d49861c343e 100644 --- a/paddle/framework/net.cc +++ b/paddle/framework/net.cc @@ -5,20 +5,15 @@ namespace framework { PlainNet::PlainNet(const NetDesc& def) {} -Error PlainNet::InferShape(Scope* scope) { +void PlainNet::InferShape(Scope* scope) { for (auto& op : ops_) { - // wrong shape - auto err = op.InferShape(); - if (!err) return err; + op.InferShape(); } - // ok - return Error(); } -Error PlainNet::Run(Scope* scope, OpContext* context, OpIndex begin, +void PlainNet::Run(Scope* scope, OpContext* context, OpIndex begin, OpIndex end) const { // TODO Add implementation here. - return Error(); } } // namespace framework diff --git a/paddle/framework/net.h b/paddle/framework/net.h index 76e0ed93307..55dcf147e1d 100644 --- a/paddle/framework/net.h +++ b/paddle/framework/net.h @@ -17,7 +17,6 @@ #include "paddle/framework/net_proto.pb.h" #include "paddle/framework/op_proto.pb.h" #include "paddle/framework/scope.h" -#include "paddle/utils/Error.h" namespace paddle { namespace framework { @@ -38,8 +37,8 @@ struct OpAttrs {}; class Operator { public: Operator(const OpDesc &def) {} - Error InferShape() { return Error(); } - Error Run() { return Error(); } + void InferShape() {} + void Run() {} }; /** @@ -61,7 +60,7 @@ class Net { /** * @brief Infer shapes of all inputs and outputs of operators. */ - virtual Error InferShape(Scope *scope) = 0; + virtual void InferShape(Scope *scope) = 0; /** * @brief Run the network. * @@ -70,7 +69,7 @@ class Net { * environment for ops. `begin` and `end` specify the scope of `ops_` to run, * If no positive indexes are provided, all operators in `ops_` will run. */ - virtual Error Run(Scope *scope, OpContext *context, OpIndex begin = -1, + virtual void Run(Scope *scope, OpContext *context, OpIndex begin = -1, OpIndex end = -1) const = 0; /** @@ -81,12 +80,12 @@ class Net { /** * @brief Add optimizer operators acctording to `attrs`. */ - virtual Error AddOptimizerOps(const OpAttrs &attrs) = 0; + virtual void AddOptimizerOps(const OpAttrs &attrs) = 0; /** * @brief Add backward operators. */ - virtual Error AddBackwardOps() = 0; + virtual void AddBackwardOps() = 0; /** * @brief Create a network. @@ -116,7 +115,7 @@ class PlainNet : public Net { * Infer all the operators' input and output varialbes' shapes, will be called * before every mini-batch */ - virtual Error InferShape(Scope *scope) override; + virtual void InferShape(Scope *scope) override; /** * @brief Run the network. @@ -125,7 +124,7 @@ class PlainNet : public Net { * scope will be used instead. If no OpContext is provicded, default context * will be used. */ - virtual Error Run(Scope *scope = nullptr, OpContext *context = nullptr, + virtual void Run(Scope *scope = nullptr, OpContext *context = nullptr, OpIndex begin = -1, OpIndex end = -1) const override; /** @@ -136,12 +135,12 @@ class PlainNet : public Net { /** * @brief Add all optimizer operators related into the network. */ - virtual Error AddOptimizerOps(const OpAttrs &attrs) override; + virtual void AddOptimizerOps(const OpAttrs &attrs) override; /** * @brief Add all backward operators related into the network. */ - virtual Error AddBackwardOps() override; + virtual void AddBackwardOps() override; protected: /** @@ -149,7 +148,7 @@ class PlainNet : public Net { * * Create operators accordding to `def`, will be called by the constructor. */ - Error BuildNet(const NetDesc &def); + void BuildNet(const NetDesc &def); /** * @brief Add an operator into this network. -- GitLab From b7397031e9c03363c4e3e3119ff371b0d8a13e7c Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Tue, 4 Jul 2017 13:47:02 +0800 Subject: [PATCH 1013/3256] Add target_link_libraries for cc_library It will fix #2728. Maybe it is silly to `target_link_libraries` for static library, because a static library do not need to link other libraries. But it will tell cmake how to propagate dependencies. The solution comes from [here](http://floooh.github.io/2016/01/12/cmake-dependency-juggling.html). * Also change op_proto_test DEPS for testing this fix works. --- cmake/generic.cmake | 2 ++ paddle/framework/CMakeLists.txt | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/cmake/generic.cmake b/cmake/generic.cmake index fb2222440ca..cae9524b2fe 100644 --- a/cmake/generic.cmake +++ b/cmake/generic.cmake @@ -162,6 +162,7 @@ function(cc_library TARGET_NAME) endif() if (cc_library_DEPS) add_dependencies(${TARGET_NAME} ${cc_library_DEPS}) + target_link_libraries(${TARGET_NAME} ${cc_library_DEPS}) endif() else(cc_library_SRCS) if (cc_library_DEPS) @@ -211,6 +212,7 @@ function(nv_library TARGET_NAME) endif() if (nv_library_DEPS) add_dependencies(${TARGET_NAME} ${nv_library_DEPS}) + target_link_libraries(${TARGET_NAME} ${nv_library_DEPS}) endif() else(nv_library_SRCS) if (nv_library_DEPS) diff --git a/paddle/framework/CMakeLists.txt b/paddle/framework/CMakeLists.txt index e7818667592..baad38e3c1e 100644 --- a/paddle/framework/CMakeLists.txt +++ b/paddle/framework/CMakeLists.txt @@ -7,4 +7,4 @@ cc_test(scope_test SRCS scope_test.cc) cc_test(enforce_test SRCS enforce_test.cc) proto_library(attr_type SRCS attr_type.proto) proto_library(op_proto SRCS op_proto.proto DEPS attr_type) -cc_test(op_proto_test SRCS op_proto_test.cc DEPS op_proto attr_type protobuf) +cc_test(op_proto_test SRCS op_proto_test.cc DEPS op_proto protobuf) -- GitLab From 9eeabe986d039b3fe3b28e5ef98f66d6dd2a3e31 Mon Sep 17 00:00:00 2001 From: qijun Date: Tue, 4 Jul 2017 14:03:58 +0800 Subject: [PATCH 1014/3256] follow comments --- paddle/platform/cublas.h | 58 +++++++++++++++++++++---------- paddle/platform/cudnn.h | 38 +++++++++++++++----- paddle/platform/curand.h | 40 +++++++++++++++------ paddle/platform/dynamic_loader.cc | 16 +++++++-- paddle/platform/dynamic_loader.h | 14 ++++---- 5 files changed, 119 insertions(+), 47 deletions(-) diff --git a/paddle/platform/cublas.h b/paddle/platform/cublas.h index d60eb501e9b..90704f37e64 100644 --- a/paddle/platform/cublas.h +++ b/paddle/platform/cublas.h @@ -1,7 +1,24 @@ +/* Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserve. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. */ + +#pragma once + #include #include "paddle/platform/dynamic_loader.h" namespace paddle { +namespace platform { namespace dyload { std::once_flag cublas_dso_flag; @@ -15,15 +32,17 @@ void *cublas_dso_handle = nullptr; * note: default dynamic linked libs */ #ifdef PADDLE_USE_DSO -#define DYNAMIC_LOAD_CUBLAS_WRAP(__name) \ - struct DynLoad__##__name { \ - template \ - cublasStatus_t operator()(Args... args) { \ - typedef cublasStatus_t (*cublasFunc)(Args...); \ - std::call_once(cublas_dso_flag, GetCublasDsoHandle, &cublas_dso_handle); \ - void *p_##__name = dlsym(cublas_dso_handle, #__name); \ - return reinterpret_cast(p_##__name)(args...); \ - } \ +#define DYNAMIC_LOAD_CUBLAS_WRAP(__name) \ + struct DynLoad__##__name { \ + template \ + cublasStatus_t operator()(Args... args) { \ + typedef cublasStatus_t (*cublasFunc)(Args...); \ + std::call_once(cublas_dso_flag, \ + paddle::platform::dyload::GetCublasDsoHandle, \ + &cublas_dso_handle); \ + void *p_##__name = dlsym(cublas_dso_handle, #__name); \ + return reinterpret_cast(p_##__name)(args...); \ + } \ } __name; // struct DynLoad__##__name #else #define DYNAMIC_LOAD_CUBLAS_WRAP(__name) \ @@ -68,17 +87,18 @@ CUBLAS_BLAS_ROUTINE_EACH(DYNAMIC_LOAD_CUBLAS_V2_WRAP) // clang-format on #ifndef PADDLE_TYPE_DOUBLE -#define CUBLAS_GEAM dynload::cublasSgeam -#define CUBLAS_GEMV dynload::cublasSgemv -#define CUBLAS_GEMM dynload::cublasSgemm -#define CUBLAS_GETRF dynload::cublasSgetrfBatched -#define CUBLAS_GETRI dynload::cublasSgetriBatched +#define CUBLAS_GEAM paddle::platform::dynload::cublasSgeam +#define CUBLAS_GEMV paddle::platform::dynload::cublasSgemv +#define CUBLAS_GEMM paddle::platform::dynload::cublasSgemm +#define CUBLAS_GETRF paddle::platform::dynload::cublasSgetrfBatched +#define CUBLAS_GETRI paddle::platform::dynload::cublasSgetriBatched #else -#define CUBLAS_GEAM dynload::cublasDgeam -#define CUBLAS_GEMV dynload::cublasDgemv -#define CUBLAS_GEMM dynload::cublasDgemm -#define CUBLAS_GETRF dynload::cublasDgetrfBatched -#define CUBLAS_GETRI dynload::cublasDgetriBatched +#define CUBLAS_GEAM paddle::platform::dynload::cublasDgeam +#define CUBLAS_GEMV paddle::platform::dynload::cublasDgemv +#define CUBLAS_GEMM paddle::platform::dynload::cublasDgemm +#define CUBLAS_GETRF paddle::platform::dynload::cublasDgetrfBatched +#define CUBLAS_GETRI paddle::platform::dynload::cublasDgetriBatched #endif } // namespace dyload +} // namespace platform } // namespace paddle diff --git a/paddle/platform/cudnn.h b/paddle/platform/cudnn.h index ab878cd5552..06e2a05d862 100644 --- a/paddle/platform/cudnn.h +++ b/paddle/platform/cudnn.h @@ -1,7 +1,24 @@ +/* Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserve. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. */ + +#pragma once + #include #include "paddle/platform/dynamic_loader.h" namespace paddle { +namespace platform { namespace dyload { std::once_flag cudnn_dso_flag; @@ -9,15 +26,17 @@ void* cudnn_dso_handle = nullptr; #ifdef PADDLE_USE_DSO -#define DYNAMIC_LOAD_CUDNN_WRAP(__name) \ - struct DynLoad__##__name { \ - template \ - auto operator()(Args... args) -> decltype(__name(args...)) { \ - using cudnn_func = decltype(__name(args...)) (*)(Args...); \ - std::call_once(cudnn_dso_flag, GetCudnnDsoHandle, &cudnn_dso_handle); \ - void* p_##__name = dlsym(cudnn_dso_handle, #__name); \ - return reinterpret_cast(p_##__name)(args...); \ - } \ +#define DYNAMIC_LOAD_CUDNN_WRAP(__name) \ + struct DynLoad__##__name { \ + template \ + auto operator()(Args... args) -> decltype(__name(args...)) { \ + using cudnn_func = decltype(__name(args...)) (*)(Args...); \ + std::call_once(cudnn_dso_flag, \ + paddle::platform::dyload::GetCudnnDsoHandle, \ + &cudnn_dso_handle); \ + void* p_##__name = dlsym(cudnn_dso_handle, #__name); \ + return reinterpret_cast(p_##__name)(args...); \ + } \ } __name; /* struct DynLoad__##__name */ #else @@ -111,4 +130,5 @@ CUDNN_DNN_ROUTINE_EACH_R5(DYNAMIC_LOAD_CUDNN_WRAP) #undef CUDNN_DNN_ROUTINE_EACH // clang-format on } // namespace dyload +} // namespace platform } // namespace paddle diff --git a/paddle/platform/curand.h b/paddle/platform/curand.h index edff6526bd8..a9cbe48ef81 100644 --- a/paddle/platform/curand.h +++ b/paddle/platform/curand.h @@ -1,20 +1,39 @@ +/* Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserve. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. */ + +#pragma once + #include #include "paddle/platform/dynamic_loader.h" namespace paddle { +namespace platform { namespace dyload { std::once_flag curand_dso_flag; void *curand_dso_handle = nullptr; #ifdef PADDLE_USE_DSO -#define DYNAMIC_LOAD_CURAND_WRAP(__name) \ - struct DynLoad__##__name { \ - template \ - curandStatus_t operator()(Args... args) { \ - typedef curandStatus_t (*curandFunc)(Args...); \ - std::call_once(curand_dso_flag, GetCurandDsoHandle, &curand_dso_handle); \ - void *p_##__name = dlsym(curand_dso_handle, #__name); \ - return reinterpret_cast(p_##__name)(args...); \ - } \ +#define DYNAMIC_LOAD_CURAND_WRAP(__name) \ + struct DynLoad__##__name { \ + template \ + curandStatus_t operator()(Args... args) { \ + typedef curandStatus_t (*curandFunc)(Args...); \ + std::call_once(curand_dso_flag, \ + paddle::platform::dyload::GetCurandDsoHandle, \ + &curand_dso_handle); \ + void *p_##__name = dlsym(curand_dso_handle, #__name); \ + return reinterpret_cast(p_##__name)(args...); \ + } \ } __name; /* struct DynLoad__##__name */ #else #define DYNAMIC_LOAD_CURAND_WRAP(__name) \ @@ -41,5 +60,6 @@ CURAND_RAND_ROUTINE_EACH(DYNAMIC_LOAD_CURAND_WRAP) #undef CURAND_RAND_ROUTINE_EACH #undef DYNAMIC_LOAD_CURAND_WRAP -} +} // namespace dyload +} // namespace platform } // namespace paddle diff --git a/paddle/platform/dynamic_loader.cc b/paddle/platform/dynamic_loader.cc index c34abc392c4..9e0aadf8e29 100644 --- a/paddle/platform/dynamic_loader.cc +++ b/paddle/platform/dynamic_loader.cc @@ -13,8 +13,12 @@ See the License for the specific language governing permissions and limitations under the License. */ #include "dynamic_loader.h" -#include -#include +#include +#include +#include +#include +#include "gflags/gflags.h" +#include "glog/logging.h" DEFINE_string(cudnn_dir, "", "Specify path for loading libcudnn.so. For instance, " @@ -30,6 +34,10 @@ DEFINE_string(warpctc_dir, "", "Specify path for loading libwarpctc.so."); DEFINE_string(lapack_dir, "", "Specify path for loading liblapack.so."); +namespace paddle { +namespace platform { +namespace dyload { + static inline std::string join(const std::string& part1, const std::string& part2) { // directory separator @@ -155,3 +163,7 @@ void GetLapackDsoHandle(void** dso_handle) { GetDsoHandleFromSearchPath(FLAGS_lapack_dir, "liblapacke.so", dso_handle); #endif } + +} // namespace dyload +} // namespace platform +} // namespace paddle diff --git a/paddle/platform/dynamic_loader.h b/paddle/platform/dynamic_loader.h index 9b5ad21724a..bb58fcba173 100644 --- a/paddle/platform/dynamic_loader.h +++ b/paddle/platform/dynamic_loader.h @@ -12,13 +12,11 @@ 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. */ -#ifndef DYNAMIC_LOAD_H_ -#define DYNAMIC_LOAD_H_ +#pragma once -#include -#include -#include -#include +namespace paddle { +namespace platform { +namespace dyload { /** * @brief load the DSO of CUBLAS @@ -60,4 +58,6 @@ void GetWarpCTCDsoHandle(void** dso_handle); */ void GetLapackDsoHandle(void** dso_handle); -#endif // DYNAMIC_LOAD_H_ +} // namespace dyload +} // namespace platform +} // namespace paddle -- GitLab From 3567ea6d7c85bdfdd42f57f6db98e5bc9bbac5e2 Mon Sep 17 00:00:00 2001 From: qijun Date: Tue, 4 Jul 2017 14:58:20 +0800 Subject: [PATCH 1015/3256] move to dynload directory --- paddle/platform/CMakeLists.txt | 4 +-- paddle/platform/dynload/CMakeLists.txt | 1 + paddle/platform/{ => dynload}/cublas.h | 26 +++++++++---------- paddle/platform/{ => dynload}/cudnn.h | 26 +++++++++---------- paddle/platform/{ => dynload}/curand.h | 26 +++++++++---------- .../platform/{ => dynload}/dynamic_loader.cc | 4 +-- .../platform/{ => dynload}/dynamic_loader.h | 4 +-- 7 files changed, 46 insertions(+), 45 deletions(-) create mode 100644 paddle/platform/dynload/CMakeLists.txt rename paddle/platform/{ => dynload}/cublas.h (95%) rename paddle/platform/{ => dynload}/cudnn.h (97%) rename paddle/platform/{ => dynload}/curand.h (93%) rename paddle/platform/{ => dynload}/dynamic_loader.cc (99%) rename paddle/platform/{ => dynload}/dynamic_loader.h (96%) diff --git a/paddle/platform/CMakeLists.txt b/paddle/platform/CMakeLists.txt index 4f6381b8afd..cc6b52e9271 100644 --- a/paddle/platform/CMakeLists.txt +++ b/paddle/platform/CMakeLists.txt @@ -1,6 +1,6 @@ +add_subdirectory(dynload) + nv_test(cuda_test SRCS cuda_test.cu) cc_library(place SRCS place.cc) cc_test(place_test SRCS place_test.cc DEPS place glog gflags) - -cc_library(dynamic_loader SRCS dynamic_loader.cc DEPS glog gflags) diff --git a/paddle/platform/dynload/CMakeLists.txt b/paddle/platform/dynload/CMakeLists.txt new file mode 100644 index 00000000000..9f829b70128 --- /dev/null +++ b/paddle/platform/dynload/CMakeLists.txt @@ -0,0 +1 @@ +cc_library(dynamic_loader SRCS dynamic_loader.cc DEPS glog gflags) diff --git a/paddle/platform/cublas.h b/paddle/platform/dynload/cublas.h similarity index 95% rename from paddle/platform/cublas.h rename to paddle/platform/dynload/cublas.h index 90704f37e64..c9150ac5738 100644 --- a/paddle/platform/cublas.h +++ b/paddle/platform/dynload/cublas.h @@ -19,7 +19,7 @@ limitations under the License. */ namespace paddle { namespace platform { -namespace dyload { +namespace dynload { std::once_flag cublas_dso_flag; void *cublas_dso_handle = nullptr; @@ -32,17 +32,17 @@ void *cublas_dso_handle = nullptr; * note: default dynamic linked libs */ #ifdef PADDLE_USE_DSO -#define DYNAMIC_LOAD_CUBLAS_WRAP(__name) \ - struct DynLoad__##__name { \ - template \ - cublasStatus_t operator()(Args... args) { \ - typedef cublasStatus_t (*cublasFunc)(Args...); \ - std::call_once(cublas_dso_flag, \ - paddle::platform::dyload::GetCublasDsoHandle, \ - &cublas_dso_handle); \ - void *p_##__name = dlsym(cublas_dso_handle, #__name); \ - return reinterpret_cast(p_##__name)(args...); \ - } \ +#define DYNAMIC_LOAD_CUBLAS_WRAP(__name) \ + struct DynLoad__##__name { \ + template \ + cublasStatus_t operator()(Args... args) { \ + typedef cublasStatus_t (*cublasFunc)(Args...); \ + std::call_once(cublas_dso_flag, \ + paddle::platform::dynload::GetCublasDsoHandle, \ + &cublas_dso_handle); \ + void *p_##__name = dlsym(cublas_dso_handle, #__name); \ + return reinterpret_cast(p_##__name)(args...); \ + } \ } __name; // struct DynLoad__##__name #else #define DYNAMIC_LOAD_CUBLAS_WRAP(__name) \ @@ -99,6 +99,6 @@ CUBLAS_BLAS_ROUTINE_EACH(DYNAMIC_LOAD_CUBLAS_V2_WRAP) #define CUBLAS_GETRF paddle::platform::dynload::cublasDgetrfBatched #define CUBLAS_GETRI paddle::platform::dynload::cublasDgetriBatched #endif -} // namespace dyload +} // namespace dynload } // namespace platform } // namespace paddle diff --git a/paddle/platform/cudnn.h b/paddle/platform/dynload/cudnn.h similarity index 97% rename from paddle/platform/cudnn.h rename to paddle/platform/dynload/cudnn.h index 06e2a05d862..c03424b375e 100644 --- a/paddle/platform/cudnn.h +++ b/paddle/platform/dynload/cudnn.h @@ -19,24 +19,24 @@ limitations under the License. */ namespace paddle { namespace platform { -namespace dyload { +namespace dynload { std::once_flag cudnn_dso_flag; void* cudnn_dso_handle = nullptr; #ifdef PADDLE_USE_DSO -#define DYNAMIC_LOAD_CUDNN_WRAP(__name) \ - struct DynLoad__##__name { \ - template \ - auto operator()(Args... args) -> decltype(__name(args...)) { \ - using cudnn_func = decltype(__name(args...)) (*)(Args...); \ - std::call_once(cudnn_dso_flag, \ - paddle::platform::dyload::GetCudnnDsoHandle, \ - &cudnn_dso_handle); \ - void* p_##__name = dlsym(cudnn_dso_handle, #__name); \ - return reinterpret_cast(p_##__name)(args...); \ - } \ +#define DYNAMIC_LOAD_CUDNN_WRAP(__name) \ + struct DynLoad__##__name { \ + template \ + auto operator()(Args... args) -> decltype(__name(args...)) { \ + using cudnn_func = decltype(__name(args...)) (*)(Args...); \ + std::call_once(cudnn_dso_flag, \ + paddle::platform::dynload::GetCudnnDsoHandle, \ + &cudnn_dso_handle); \ + void* p_##__name = dlsym(cudnn_dso_handle, #__name); \ + return reinterpret_cast(p_##__name)(args...); \ + } \ } __name; /* struct DynLoad__##__name */ #else @@ -129,6 +129,6 @@ CUDNN_DNN_ROUTINE_EACH_R5(DYNAMIC_LOAD_CUDNN_WRAP) #undef CUDNN_DNN_ROUTINE_EACH // clang-format on -} // namespace dyload +} // namespace dynload } // namespace platform } // namespace paddle diff --git a/paddle/platform/curand.h b/paddle/platform/dynload/curand.h similarity index 93% rename from paddle/platform/curand.h rename to paddle/platform/dynload/curand.h index a9cbe48ef81..1ef7a8c833d 100644 --- a/paddle/platform/curand.h +++ b/paddle/platform/dynload/curand.h @@ -19,21 +19,21 @@ limitations under the License. */ namespace paddle { namespace platform { -namespace dyload { +namespace dynload { std::once_flag curand_dso_flag; void *curand_dso_handle = nullptr; #ifdef PADDLE_USE_DSO -#define DYNAMIC_LOAD_CURAND_WRAP(__name) \ - struct DynLoad__##__name { \ - template \ - curandStatus_t operator()(Args... args) { \ - typedef curandStatus_t (*curandFunc)(Args...); \ - std::call_once(curand_dso_flag, \ - paddle::platform::dyload::GetCurandDsoHandle, \ - &curand_dso_handle); \ - void *p_##__name = dlsym(curand_dso_handle, #__name); \ - return reinterpret_cast(p_##__name)(args...); \ - } \ +#define DYNAMIC_LOAD_CURAND_WRAP(__name) \ + struct DynLoad__##__name { \ + template \ + curandStatus_t operator()(Args... args) { \ + typedef curandStatus_t (*curandFunc)(Args...); \ + std::call_once(curand_dso_flag, \ + paddle::platform::dynload::GetCurandDsoHandle, \ + &curand_dso_handle); \ + void *p_##__name = dlsym(curand_dso_handle, #__name); \ + return reinterpret_cast(p_##__name)(args...); \ + } \ } __name; /* struct DynLoad__##__name */ #else #define DYNAMIC_LOAD_CURAND_WRAP(__name) \ @@ -60,6 +60,6 @@ CURAND_RAND_ROUTINE_EACH(DYNAMIC_LOAD_CURAND_WRAP) #undef CURAND_RAND_ROUTINE_EACH #undef DYNAMIC_LOAD_CURAND_WRAP -} // namespace dyload +} // namespace dynload } // namespace platform } // namespace paddle diff --git a/paddle/platform/dynamic_loader.cc b/paddle/platform/dynload/dynamic_loader.cc similarity index 99% rename from paddle/platform/dynamic_loader.cc rename to paddle/platform/dynload/dynamic_loader.cc index 9e0aadf8e29..8ef67bad8c6 100644 --- a/paddle/platform/dynamic_loader.cc +++ b/paddle/platform/dynload/dynamic_loader.cc @@ -36,7 +36,7 @@ DEFINE_string(lapack_dir, "", "Specify path for loading liblapack.so."); namespace paddle { namespace platform { -namespace dyload { +namespace dynload { static inline std::string join(const std::string& part1, const std::string& part2) { @@ -164,6 +164,6 @@ void GetLapackDsoHandle(void** dso_handle) { #endif } -} // namespace dyload +} // namespace dynload } // namespace platform } // namespace paddle diff --git a/paddle/platform/dynamic_loader.h b/paddle/platform/dynload/dynamic_loader.h similarity index 96% rename from paddle/platform/dynamic_loader.h rename to paddle/platform/dynload/dynamic_loader.h index bb58fcba173..a99b05443fe 100644 --- a/paddle/platform/dynamic_loader.h +++ b/paddle/platform/dynload/dynamic_loader.h @@ -16,7 +16,7 @@ limitations under the License. */ namespace paddle { namespace platform { -namespace dyload { +namespace dynload { /** * @brief load the DSO of CUBLAS @@ -58,6 +58,6 @@ void GetWarpCTCDsoHandle(void** dso_handle); */ void GetLapackDsoHandle(void** dso_handle); -} // namespace dyload +} // namespace dynload } // namespace platform } // namespace paddle -- GitLab From a211374d53090733667f2be2cf629cf858757c6d Mon Sep 17 00:00:00 2001 From: liaogang Date: Tue, 4 Jul 2017 15:01:05 +0800 Subject: [PATCH 1016/3256] FIX: interface deps under cmake < 3.3 --- cmake/external/any.cmake | 13 ++++++++++--- cmake/external/eigen.cmake | 13 ++++++++++--- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/cmake/external/any.cmake b/cmake/external/any.cmake index b61e421871a..edf6edc0bd4 100644 --- a/cmake/external/any.cmake +++ b/cmake/external/any.cmake @@ -17,8 +17,15 @@ ExternalProject_Add( TEST_COMMAND "" ) -ADD_LIBRARY(lib_any INTERFACE) -ADD_DEPENDENCIES(lib_any extern_lib_any) +if (${CMAKE_VERSION} VERSION_LESS "3.3.0") + set(dummyfile ${CMAKE_CURRENT_BINARY_DIR}/lib_any_dummy.c) + file(WRITE ${dummyfile} "const char * dummy_any = \"${dummyfile}\";") + add_library(lib_any STATIC ${dummyfile}) +else() + add_library(lib_any INTERFACE) +endif() + +add_dependencies(lib_any extern_lib_any) add_definitions(-DANY_IMPL_ANY_CAST_MOVEABLE) -LIST(APPEND external_project_dependencies extern_lib_any) +LIST(APPEND external_project_dependencies lib_any) diff --git a/cmake/external/eigen.cmake b/cmake/external/eigen.cmake index 39b16c3b2bd..1f2fdcac650 100644 --- a/cmake/external/eigen.cmake +++ b/cmake/external/eigen.cmake @@ -26,7 +26,14 @@ ExternalProject_Add( TEST_COMMAND "" ) -ADD_LIBRARY(eigen3 INTERFACE) -ADD_DEPENDENCIES(eigen3 extern_eigen3) +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}\";") + add_library(eigen3 STATIC ${dummyfile}) +else() + add_library(eigen3 INTERFACE) +endif() -LIST(APPEND external_project_dependencies extern_eigen3) +add_dependencies(eigen3 extern_eigen3) + +LIST(APPEND external_project_dependencies eigen3) -- GitLab From 109937b8d512904d04a1773bdf19ddb756ecd087 Mon Sep 17 00:00:00 2001 From: Superjom Date: Tue, 4 Jul 2017 15:18:20 +0800 Subject: [PATCH 1017/3256] fix ci error --- paddle/framework/CMakeLists.txt | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/paddle/framework/CMakeLists.txt b/paddle/framework/CMakeLists.txt index 42600277f66..ceff1d3581b 100644 --- a/paddle/framework/CMakeLists.txt +++ b/paddle/framework/CMakeLists.txt @@ -7,8 +7,6 @@ cc_test(scope_test SRCS scope_test.cc) cc_test(enforce_test SRCS enforce_test.cc) proto_library(attr_type SRCS attr_type.proto) proto_library(op_proto SRCS op_proto.proto DEPS attr_type) - +proto_library(net_proto SRCS net_proto.proto DEPS op_proto) cc_test(op_proto_test SRCS op_proto_test.cc DEPS op_proto attr_type protobuf) - -proto_library(net_proto SRCS net_proto.proto) -cc_library(net SRCS net.cc DEPS net_proto attr_type op_proto) \ No newline at end of file +cc_library(net SRCS net.cc DEPS net_proto attr_type op_proto) -- GitLab From ff36389452c1af6cc6a5f03b5ca52404ab20f108 Mon Sep 17 00:00:00 2001 From: liaogang Date: Tue, 4 Jul 2017 15:21:24 +0800 Subject: [PATCH 1018/3256] ENH: code style --- paddle/memory/detail/buddy_allocator.cc | 1 - 1 file changed, 1 deletion(-) diff --git a/paddle/memory/detail/buddy_allocator.cc b/paddle/memory/detail/buddy_allocator.cc index e8d694327d8..eddfd9d13c7 100644 --- a/paddle/memory/detail/buddy_allocator.cc +++ b/paddle/memory/detail/buddy_allocator.cc @@ -48,7 +48,6 @@ void* BuddyAllocator::Alloc(size_t unaligned_size) { // if the allocation is huge, send directly to the system allocator if (size > max_chunk_size_) { DLOG(INFO) << "Allocate from system allocator."; - return SystemAlloc(size); } -- GitLab From 817f317bef82eb2c024927e6a62b048a1ba93d4a Mon Sep 17 00:00:00 2001 From: liaogang Date: Tue, 4 Jul 2017 15:39:08 +0800 Subject: [PATCH 1019/3256] FIX: INTERFACE path --- cmake/external/any.cmake | 2 +- cmake/external/eigen.cmake | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cmake/external/any.cmake b/cmake/external/any.cmake index edf6edc0bd4..45e3764e848 100644 --- a/cmake/external/any.cmake +++ b/cmake/external/any.cmake @@ -2,7 +2,7 @@ INCLUDE(ExternalProject) SET(ANY_SOURCE_DIR ${THIRD_PARTY_PATH}/any) -INCLUDE_DIRECTORIES(${ANY_SOURCE_DIR}/src/linb_any) +INCLUDE_DIRECTORIES(${ANY_SOURCE_DIR}/src/extern_lib_any) ExternalProject_Add( extern_lib_any diff --git a/cmake/external/eigen.cmake b/cmake/external/eigen.cmake index 1f2fdcac650..3e6cedbb0d7 100644 --- a/cmake/external/eigen.cmake +++ b/cmake/external/eigen.cmake @@ -2,7 +2,7 @@ INCLUDE(ExternalProject) SET(EIGEN_SOURCE_DIR ${THIRD_PARTY_PATH}/eigen3) -INCLUDE_DIRECTORIES(${EIGEN_SOURCE_DIR}/src/eigen3) +INCLUDE_DIRECTORIES(${EIGEN_SOURCE_DIR}/src/extern_eigen3) ExternalProject_Add( extern_eigen3 -- GitLab From 9045063b535c400ff8ebf20d0b8534103ec6d9ab Mon Sep 17 00:00:00 2001 From: Qiao Longfei Date: Tue, 4 Jul 2017 15:58:15 +0800 Subject: [PATCH 1020/3256] pserver etcd client (#2559) * init etcd cclient * add etcd * add etcd.go * fix compile problem * move code to etcd.go * add etcd_lister.go for pserver client * add etcd_client_test.go * merge etcd_client_test and client_test * refine client_test.go * refine code * format code * add TODO and use interface instead of struct * fix typo of initDesiredPservers * optimize dir structure of go/pserver/client * add a flag to config index for pserver * follow comment * fix path * optimize code * remove err in pserver NewEtcd * restore comment about /ps_desired --- CMakeLists.txt | 2 +- go/CMakeLists.txt | 2 +- go/cmd/pserver/pserver.go | 16 ++- go/master/etcd_client.go | 4 +- .../{cclient => client/c}/CMakeLists.txt | 2 +- go/pserver/{cclient => client/c}/cclient.go | 26 ++-- .../{cclient => client/c}/test/CMakeLists.txt | 0 .../{cclient => client/c}/test/test_cclient.c | 0 .../{cclient => client/c}/test/test_mnist.py | 0 .../{cclient => client/c}/test/test_train.py | 0 .../c}/test/testdata/optimizer.pb | Bin go/pserver/{ => client}/client.go | 17 +-- go/pserver/{ => client}/client_test.go | 77 +++++++++-- go/pserver/client/etcd_client.go | 125 ++++++++++++++++++ go/pserver/etcd_client.go | 13 +- go/pserver/optimizer.go | 2 +- go/pserver/optimizer_test.go | 2 +- go/pserver/service.go | 3 - go/pserver/service_test.go | 8 +- 19 files changed, 246 insertions(+), 53 deletions(-) rename go/pserver/{cclient => client/c}/CMakeLists.txt (67%) rename go/pserver/{cclient => client/c}/cclient.go (88%) rename go/pserver/{cclient => client/c}/test/CMakeLists.txt (100%) rename go/pserver/{cclient => client/c}/test/test_cclient.c (100%) rename go/pserver/{cclient => client/c}/test/test_mnist.py (100%) rename go/pserver/{cclient => client/c}/test/test_train.py (100%) rename go/pserver/{cclient => client/c}/test/testdata/optimizer.pb (100%) rename go/pserver/{ => client}/client.go (92%) rename go/pserver/{ => client}/client_test.go (54%) create mode 100644 go/pserver/client/etcd_client.go diff --git a/CMakeLists.txt b/CMakeLists.txt index 5349f59805b..5bedbbefa85 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -113,7 +113,7 @@ include(coveralls) # set code coverage include_directories("${PROJ_ROOT}") include_directories("${PROJ_ROOT}/paddle/cuda/include") include_directories("${CMAKE_CURRENT_BINARY_DIR}/proto") -include_directories("${CMAKE_CURRENT_BINARY_DIR}/go/pserver/cclient") +include_directories("${CMAKE_CURRENT_BINARY_DIR}/go/pserver/client/c") include_directories(${Boost_INCLUDE_DIRS}) set(EXTERNAL_LIBS diff --git a/go/CMakeLists.txt b/go/CMakeLists.txt index 014697d1555..f00c70a0589 100644 --- a/go/CMakeLists.txt +++ b/go/CMakeLists.txt @@ -13,7 +13,7 @@ # limitations under the License. # -add_subdirectory(pserver/cclient) +add_subdirectory(pserver/client/c) add_subdirectory(cmd/pserver) add_subdirectory(cmd/master) add_subdirectory(master/c) diff --git a/go/cmd/pserver/pserver.go b/go/cmd/pserver/pserver.go index 8a42d4f8af1..31ef450f032 100644 --- a/go/cmd/pserver/pserver.go +++ b/go/cmd/pserver/pserver.go @@ -15,6 +15,7 @@ import ( func main() { port := flag.Int("port", 0, "port of the pserver") + index := flag.Int("index", -1, "index of this pserver, should be larger or equal than 0") etcdEndpoint := flag.String("etcd-endpoint", "http://127.0.0.1:2379", "comma separated endpoint string for pserver to connect to etcd") etcdTimeout := flag.Int("etcd-timeout", 5, "timeout for etcd calls") @@ -29,11 +30,16 @@ func main() { } log.SetLevel(level) - timeout := time.Second * time.Duration((*etcdTimeout)) - e := pserver.NewEtcdClient(*etcdEndpoint, *numPservers, timeout) - idx, err := e.Register() - if err != nil { - panic(err) + var idx int + if *index >= 0 { + idx = *index + } else { + timeout := time.Second * time.Duration((*etcdTimeout)) + e := pserver.NewEtcdClient(*etcdEndpoint, *numPservers, timeout) + idx, err = e.Register() + if err != nil { + panic(err) + } } s, err := pserver.NewService(idx) diff --git a/go/master/etcd_client.go b/go/master/etcd_client.go index e27c014792f..04c1394e963 100644 --- a/go/master/etcd_client.go +++ b/go/master/etcd_client.go @@ -50,7 +50,7 @@ func NewEtcdClient(endpoints []string, addr string, lockPath, addrPath, statePat lock := concurrency.NewMutex(sess, lockPath) // It's fine for the lock to get stuck, in this case we have // multiple master servers running (only configured to have - // one master running, but split-brain problem may cuase + // one master running, but split-brain problem may cause // multiple master servers running), and the cluster management // software will kill one of them. log.Debugf("Trying to acquire lock at %s.", lockPath) @@ -98,7 +98,7 @@ func (e *EtcdClient) Save(state []byte) error { // We lost the master lock and can not acquire // it back, it means some other master is // already started. We don't want cluster - // managment system to kill the master server + // management system to kill the master server // who is holding the lock and running // correctly. So the most feasible solution is // to kill current master server. The current diff --git a/go/pserver/cclient/CMakeLists.txt b/go/pserver/client/c/CMakeLists.txt similarity index 67% rename from go/pserver/cclient/CMakeLists.txt rename to go/pserver/client/c/CMakeLists.txt index 7fe74c62f10..a3fcaeef190 100644 --- a/go/pserver/cclient/CMakeLists.txt +++ b/go/pserver/client/c/CMakeLists.txt @@ -1,5 +1,5 @@ cc_library(paddle_go_optimizer DEPS paddle_optimizer paddle_proto glog gflags protobuf) -go_library(paddle_pserver_cclient STATIC) +go_library(paddle_pserver_cclient STATIC DEPS paddle_go_optimizer) if(WITH_TESTING) add_subdirectory(test) endif() diff --git a/go/pserver/cclient/cclient.go b/go/pserver/client/c/cclient.go similarity index 88% rename from go/pserver/cclient/cclient.go rename to go/pserver/client/c/cclient.go index bbaf43d9f14..7ddaceb7ed3 100644 --- a/go/pserver/cclient/cclient.go +++ b/go/pserver/client/c/cclient.go @@ -30,15 +30,16 @@ import ( "unsafe" "github.com/PaddlePaddle/Paddle/go/pserver" + "github.com/PaddlePaddle/Paddle/go/pserver/client" log "github.com/sirupsen/logrus" ) var nullPtr = unsafe.Pointer(uintptr(0)) var mu sync.Mutex -var handleMap = make(map[C.paddle_pserver_client]*pserver.Client) +var handleMap = make(map[C.paddle_pserver_client]*client.Client) var curHandle C.paddle_pserver_client -func add(c *pserver.Client) C.paddle_pserver_client { +func add(c *client.Client) C.paddle_pserver_client { mu.Lock() defer mu.Unlock() client := curHandle @@ -47,13 +48,13 @@ func add(c *pserver.Client) C.paddle_pserver_client { return client } -func get(client C.paddle_pserver_client) *pserver.Client { +func get(client C.paddle_pserver_client) *client.Client { mu.Lock() defer mu.Unlock() return handleMap[client] } -func remove(client C.paddle_pserver_client) *pserver.Client { +func remove(client C.paddle_pserver_client) *client.Client { mu.Lock() defer mu.Unlock() h := handleMap[client] @@ -80,9 +81,9 @@ func (s selector) Select() bool { return bool(s) } -type lister []pserver.Server +type lister []client.Server -func (l lister) List() []pserver.Server { +func (l lister) List() []client.Server { return l } @@ -90,19 +91,22 @@ func (l lister) List() []pserver.Server { func paddle_new_pserver_client(addrs *C.char, selected int) C.paddle_pserver_client { a := C.GoString(addrs) as := strings.Split(a, ",") - servers := make([]pserver.Server, len(as)) + servers := make([]client.Server, len(as)) for i := range as { servers[i].Index = i servers[i].Addr = as[i] } - c := pserver.NewClient(lister(servers), len(as), selector(selected != 0)) + c := client.NewClient(lister(servers), len(as), selector(selected != 0)) return add(c) } //export paddle_new_etcd_pserver_client -func paddle_new_etcd_pserver_client(etcd_addr *C.char) C.paddle_pserver_client { - // TODO(helin): fault tolerant pserver client using etcd. - panic("not implemented.") +func paddle_new_etcd_pserver_client(etcd_endpoints *C.char, selected int) C.paddle_pserver_client { + // TODO(Longfei: use etcd lock to decide which trainer to initialize the parameters) + addr := C.GoString(etcd_endpoints) + etcd_client := client.NewEtcd(addr) + c := client.NewClient(etcd_client, etcd_client.Desired(), selector(selected != 0)) + return add(c) } //export paddle_pserver_client_release diff --git a/go/pserver/cclient/test/CMakeLists.txt b/go/pserver/client/c/test/CMakeLists.txt similarity index 100% rename from go/pserver/cclient/test/CMakeLists.txt rename to go/pserver/client/c/test/CMakeLists.txt diff --git a/go/pserver/cclient/test/test_cclient.c b/go/pserver/client/c/test/test_cclient.c similarity index 100% rename from go/pserver/cclient/test/test_cclient.c rename to go/pserver/client/c/test/test_cclient.c diff --git a/go/pserver/cclient/test/test_mnist.py b/go/pserver/client/c/test/test_mnist.py similarity index 100% rename from go/pserver/cclient/test/test_mnist.py rename to go/pserver/client/c/test/test_mnist.py diff --git a/go/pserver/cclient/test/test_train.py b/go/pserver/client/c/test/test_train.py similarity index 100% rename from go/pserver/cclient/test/test_train.py rename to go/pserver/client/c/test/test_train.py diff --git a/go/pserver/cclient/test/testdata/optimizer.pb b/go/pserver/client/c/test/testdata/optimizer.pb similarity index 100% rename from go/pserver/cclient/test/testdata/optimizer.pb rename to go/pserver/client/c/test/testdata/optimizer.pb diff --git a/go/pserver/client.go b/go/pserver/client/client.go similarity index 92% rename from go/pserver/client.go rename to go/pserver/client/client.go index 6938b9d5ce6..aa8bfe30c26 100644 --- a/go/pserver/client.go +++ b/go/pserver/client/client.go @@ -1,4 +1,4 @@ -package pserver +package client import ( "errors" @@ -7,6 +7,7 @@ import ( "time" "github.com/PaddlePaddle/Paddle/go/connection" + "github.com/PaddlePaddle/Paddle/go/pserver" log "github.com/sirupsen/logrus" ) @@ -105,7 +106,7 @@ func (c *Client) BeginInitParams() bool { } // InitParam initializes the parameter on parameter servers. -func (c *Client) InitParam(paramWithConfigs ParameterWithConfig) error { +func (c *Client) InitParam(paramWithConfigs pserver.ParameterWithConfig) error { return c.pservers[c.partition(paramWithConfigs.Param.Name)].Call("Service.InitParam", paramWithConfigs, nil) } @@ -123,13 +124,13 @@ func (c *Client) FinishInitParams() error { // SendGrads sends gradients to parameter servers for updating // parameters. -func (c *Client) SendGrads(grads []Gradient) error { +func (c *Client) SendGrads(grads []pserver.Gradient) error { if len(grads) == 0 { return errors.New("no gradient received") } errCh := make(chan error, len(grads)) for _, g := range grads { - go func(g Gradient) { + go func(g pserver.Gradient) { err := c.pservers[c.partition(g.Name)].Call("Service.SendGrad", g, nil) errCh <- err }(g) @@ -151,7 +152,7 @@ func (c *Client) SendGrads(grads []Gradient) error { type result struct { idx int - param Parameter + param pserver.Parameter err error } @@ -170,12 +171,12 @@ func (r results) Swap(i int, j int) { } // GetParams gets parameters from parameter servers. -func (c *Client) GetParams(names []string) ([]Parameter, error) { +func (c *Client) GetParams(names []string) ([]pserver.Parameter, error) { rCh := make(chan result, len(names)) for idx, name := range names { go func(name string, idx int) { - var parameter Parameter + var parameter pserver.Parameter err := c.pservers[c.partition(name)].Call("Service.GetParam", name, ¶meter) rCh <- result{idx: idx, param: parameter, err: err} }(name, idx) @@ -196,7 +197,7 @@ func (c *Client) GetParams(names []string) ([]Parameter, error) { } sort.Sort(rs) - ps := make([]Parameter, len(rs)) + ps := make([]pserver.Parameter, len(rs)) for i := range rs { ps[i] = rs[i].param } diff --git a/go/pserver/client_test.go b/go/pserver/client/client_test.go similarity index 54% rename from go/pserver/client_test.go rename to go/pserver/client/client_test.go index b805efa9216..29b400812c9 100644 --- a/go/pserver/client_test.go +++ b/go/pserver/client/client_test.go @@ -1,6 +1,7 @@ -package pserver_test +package client_test import ( + "context" "io/ioutil" "net" "net/http" @@ -8,15 +9,25 @@ import ( "strconv" "strings" "testing" + "time" "github.com/PaddlePaddle/Paddle/go/pserver" + "github.com/PaddlePaddle/Paddle/go/pserver/client" + "github.com/coreos/etcd/clientv3" + log "github.com/sirupsen/logrus" ) -const numPserver = 10 +const ( + numPserver = 10 + etcdEndpoints = "127.0.0.1:2379" + timeout = 2 * time.Second +) -var port [numPserver]int +var pserverClientPorts [numPserver]int -func init() { +// this function init pserver client and return their ports in an array. +func initClient() [numPserver]int { + var ports [numPserver]int for i := 0; i < numPserver; i++ { l, err := net.Listen("tcp", ":0") if err != nil { @@ -28,7 +39,7 @@ func init() { if err != nil { panic(err) } - port[i] = p + ports[i] = p go func(l net.Listener) { s, err := pserver.NewService(0) @@ -49,6 +60,31 @@ func init() { } }(l) } + return ports +} + +func initNativeClient() { + pserverClientPorts = initClient() +} + +func initEtcdClient() { + client, err := clientv3.New(clientv3.Config{ + Endpoints: []string{etcdEndpoints}, + DialTimeout: time.Second * time.Duration(1), + }) + if err != nil { + log.Errorf("err %v", err) + } + ctx, cancel := context.WithTimeout(context.Background(), timeout) + client.Delete(ctx, pserver.PsDesired) + client.Delete(ctx, pserver.PsPath) + client.Put(ctx, pserver.PsDesired, strconv.Itoa(numPserver)) + ports := initClient() + for i := 0; i < numPserver; i++ { + client.Put(ctx, pserver.PsPath+strconv.Itoa(i), ":"+strconv.Itoa(ports[i])) + } + cancel() + client.Close() } type selector bool @@ -57,25 +93,20 @@ func (s selector) Select() bool { return bool(s) } -type lister []pserver.Server +type lister []client.Server -func (l lister) List() []pserver.Server { +func (l lister) List() []client.Server { return l } -func TestClientFull(t *testing.T) { - servers := make([]pserver.Server, numPserver) - for i := 0; i < numPserver; i++ { - servers[i] = pserver.Server{Index: i, Addr: ":" + strconv.Itoa(port[i])} - } - c := pserver.NewClient(lister(servers), len(servers), selector(true)) +func ClientTest(t *testing.T, c *client.Client) { selected := c.BeginInitParams() if !selected { t.Fatal("should be selected.") } const numParameter = 100 - config, err := ioutil.ReadFile("./cclient/test/testdata/optimizer.pb") + config, err := ioutil.ReadFile("./c/test/testdata/optimizer.pb") if err != nil { t.Fatalf("read optimizer proto failed") } @@ -129,3 +160,21 @@ func TestClientFull(t *testing.T) { } } } + +func TestNativeClient(t *testing.T) { + initNativeClient() + servers := make([]client.Server, numPserver) + for i := 0; i < numPserver; i++ { + servers[i] = client.Server{Index: i, Addr: ":" + strconv.Itoa(pserverClientPorts[i])} + } + c1 := client.NewClient(lister(servers), len(servers), selector(true)) + ClientTest(t, c1) +} + +// TODO: tmperary disable etcdClient test for dependency of etcd) +func EtcdClient(t *testing.T) { + initEtcdClient() + etcd_client := client.NewEtcd(etcdEndpoints) + c2 := client.NewClient(etcd_client, etcd_client.Desired(), selector(true)) + ClientTest(t, c2) +} diff --git a/go/pserver/client/etcd_client.go b/go/pserver/client/etcd_client.go new file mode 100644 index 00000000000..1fd3479aa88 --- /dev/null +++ b/go/pserver/client/etcd_client.go @@ -0,0 +1,125 @@ +package client + +import ( + "context" + "strconv" + "strings" + "time" + + "github.com/PaddlePaddle/Paddle/go/pserver" + "github.com/coreos/etcd/clientv3" + log "github.com/sirupsen/logrus" +) + +const ( + DefaultEtcdTimeout time.Duration = 5 * time.Second +) + +// EtcdClient is used by pserver client that is a part of trainer process. +// TODO: +// 1. add watcher to watch the change state of pservers) +// 1. add etcd lock) +type EtcdClient struct { + client *clientv3.Client + timeout time.Duration + endpoints []string +} + +// Desired read ps desired number from etcd. +func (p *EtcdClient) Desired() int { + var psDesired int + for { + ctx, cancel := context.WithTimeout(context.Background(), p.timeout) + resp, err := p.client.Get(ctx, pserver.PsDesired) + cancel() + if err != nil { + log.Errorf("Get ps dresire number failed! recnnectiong..., %v", err) + time.Sleep(p.timeout) + continue + } + + kvs := resp.Kvs + if len(kvs) == 0 { + log.Infoln("Waiting for ps desired registered ...") + time.Sleep(p.timeout) + continue + } + + psDesired, err = strconv.Atoi(string(resp.Kvs[0].Value)) + if err != nil { + log.Errorf("psDesired %s invalid %v", psDesired, err) + time.Sleep(p.timeout) + continue + } + + log.Debugf("Get psDesired number: %d", psDesired) + break + } + return psDesired +} + +// List return the pserver list read from etcd. +func (p *EtcdClient) List() []Server { + psDesired := p.Desired() + + servers := make([]Server, psDesired) + for { + for i := 0; i < psDesired; i++ { + ctx, cancel := context.WithTimeout(context.Background(), p.timeout) + cancel() + psKey := pserver.PsPath + strconv.Itoa(i) + log.Debugf("checking %s", psKey) + resp, err := p.client.Get(ctx, psKey) + if err != nil { + log.Infof("Get psKey= %s error, %v", psKey, err) + time.Sleep(p.timeout) + continue + } + kvs := resp.Kvs + if len(kvs) == 0 { + log.Infof("Waiting for ps addr registered ...") + time.Sleep(p.timeout) + continue + } + + psAddr := string(resp.Kvs[0].Value) + // TODO(Longfei) check the ps address + if psAddr == "" { + log.Infof("Get psKey = %s, psAddr is empty", psKey) + time.Sleep(p.timeout) + continue + } + log.Infof("got value (%s) for key: %s", psAddr, psKey) + servers[i].Index = i + servers[i].Addr = psAddr + } + break + } + return servers +} + +// NewEtcd create a etcd client to return the state of pserver on etcd. +func NewEtcd(endpoints string) *EtcdClient { + ep := strings.Split(endpoints, ",") + var cli *clientv3.Client + var err error + for { + cli, err = clientv3.New(clientv3.Config{ + Endpoints: ep, + DialTimeout: DefaultEtcdTimeout, + }) + if err != nil { + log.Errorf("Init etcd connection failed: %v", err) + time.Sleep(DefaultEtcdTimeout) + continue + } + break + } + log.Infof("Connected to etcd: %s\n", endpoints) + client := &EtcdClient{ + client: cli, + timeout: DefaultEtcdTimeout, + endpoints: ep, + } + return client +} diff --git a/go/pserver/etcd_client.go b/go/pserver/etcd_client.go index 4d88243edd4..37b8d522c1b 100644 --- a/go/pserver/etcd_client.go +++ b/go/pserver/etcd_client.go @@ -13,6 +13,13 @@ import ( log "github.com/sirupsen/logrus" ) +const ( + // PsDesired is etcd path for store desired pserver count + PsDesired = "/ps_desired" + // PsAddr is the base dir for pserver to store their addr + PsPath = "/ps/" +) + // EtcdClient is the etcd client that the pserver uses for fault // tolerance, service registry and coordination. type EtcdClient struct { @@ -68,7 +75,7 @@ func (e *EtcdClient) Register() (int, error) { // it at the same time. for { ctx, cancel := context.WithTimeout(context.Background(), time.Second) - _, err := e.initDesiredPsercers(ctx, e.numPservers) + _, err := e.initDesiredPservers(ctx, e.numPservers) cancel() if err != nil { log.Warn(err) @@ -120,7 +127,7 @@ func (e *EtcdClient) Register() (int, error) { return pserverIdx, nil } -func (e *EtcdClient) initDesiredPsercers(ctx context.Context, numPservers int) (*clientv3.TxnResponse, error) { +func (e *EtcdClient) initDesiredPservers(ctx context.Context, numPservers int) (*clientv3.TxnResponse, error) { return concurrency.NewSTM(e.etcdClient, func(c concurrency.STM) error { dsStr := c.Get(PsDesired) if dsStr == "" { @@ -136,7 +143,7 @@ func (e *EtcdClient) registerPserverEtcd(ctx context.Context) (int, error) { _, err := concurrency.NewSTM(e.etcdClient, func(c concurrency.STM) error { registered := false for i := 0; i < e.desired; i++ { - psKey := "/ps/" + strconv.Itoa(i) + psKey := PsPath + strconv.Itoa(i) log.Debugf("checking %s", psKey) ps := c.Get(psKey) log.Debugf("got value (%s) for key: %s", ps, psKey) diff --git a/go/pserver/optimizer.go b/go/pserver/optimizer.go index b4a040f46bf..bca3718af32 100644 --- a/go/pserver/optimizer.go +++ b/go/pserver/optimizer.go @@ -2,7 +2,7 @@ package pserver // #cgo CFLAGS: -I ../../ // //FIXME: ldflags contain "build" path -// #cgo LDFLAGS: ../../build/go/pserver/cclient/libpaddle_go_optimizer.a -lstdc++ +// #cgo LDFLAGS: ../../build/go/pserver/client/c/libpaddle_go_optimizer.a -lstdc++ // #include "paddle/optimizer/optimizer.h" // #include // #include diff --git a/go/pserver/optimizer_test.go b/go/pserver/optimizer_test.go index b99b5a5f0bf..0b2f4cfa41a 100644 --- a/go/pserver/optimizer_test.go +++ b/go/pserver/optimizer_test.go @@ -11,7 +11,7 @@ func TestOptimizerCreateRelease(t *testing.T) { ElementType: Int32, } p.Content = []byte{1, 3} - config, err := ioutil.ReadFile("./cclient/test/testdata/optimizer.pb") + config, err := ioutil.ReadFile("./client/c/test/testdata/optimizer.pb") if err != nil { t.Fatalf("read optimizer proto failed") } diff --git a/go/pserver/service.go b/go/pserver/service.go index e15a4e5a58a..7711dc027e1 100644 --- a/go/pserver/service.go +++ b/go/pserver/service.go @@ -24,9 +24,6 @@ const ( Float64 ) -// PsDesired is etcd path for store desired pserver count -const PsDesired = "/ps_desired" - // Parameter is a piece of data to sync with the parameter server. type Parameter struct { Name string diff --git a/go/pserver/service_test.go b/go/pserver/service_test.go index 30e3ac8ae1c..b6d20d2c8b7 100644 --- a/go/pserver/service_test.go +++ b/go/pserver/service_test.go @@ -10,6 +10,10 @@ import ( "github.com/PaddlePaddle/Paddle/go/pserver" ) +const ( + OptimizerConfig = "./client/c/test/testdata/optimizer.pb" +) + func TestServiceFull(t *testing.T) { s, err := pserver.NewService(0) if err != nil { @@ -19,7 +23,7 @@ func TestServiceFull(t *testing.T) { p.Name = "param_a" p.Content = []byte{1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0} p.ElementType = pserver.Int32 - config, err := ioutil.ReadFile("./cclient/test/testdata/optimizer.pb") + config, err := ioutil.ReadFile(OptimizerConfig) if err != nil { t.Fatalf("read optimizer proto failed") } @@ -149,7 +153,7 @@ func TestBlockUntilInitialized(t *testing.T) { p.Name = "param_a" p.Content = []byte{1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0} p.ElementType = pserver.Int32 - config, err := ioutil.ReadFile("./cclient/test/testdata/optimizer.pb") + config, err := ioutil.ReadFile(OptimizerConfig) if err != nil { t.Fatalf("read optimizer proto failed") } -- GitLab From 3f5e5a24c497714530e8f55f2f076fc4e3168d9c Mon Sep 17 00:00:00 2001 From: gongweibao Date: Tue, 4 Jul 2017 08:16:08 +0000 Subject: [PATCH 1021/3256] fix cmake error --- .travis.yml | 2 +- go/master/c/CMakeLists.txt | 2 +- go/pserver/optimizer.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index a53bd180941..4f72e2ca336 100644 --- a/.travis.yml +++ b/.travis.yml @@ -42,7 +42,7 @@ before_install: function timeout() { perl -e 'alarm shift; exec @ARGV' "$@"; } script: - | - timeout 2580 paddle/scripts/travis/${JOB}.sh # 43min timeout + timeout 2580 paddle/scripts/travis/${JOB}.sh -e "WITH_GOLANG=ON" # 43min timeout RESULT=$?; if [ $RESULT -eq 0 ] || [ $RESULT -eq 142 ]; then true; else false; fi; notifications: email: diff --git a/go/master/c/CMakeLists.txt b/go/master/c/CMakeLists.txt index 94d6bb0b2e9..d900850be04 100644 --- a/go/master/c/CMakeLists.txt +++ b/go/master/c/CMakeLists.txt @@ -1 +1 @@ -go_library(paddle_master SHARED) +go_library(paddle_master SHARED DEPS paddle_go_optimizer) diff --git a/go/pserver/optimizer.go b/go/pserver/optimizer.go index bca3718af32..d84f55b9874 100644 --- a/go/pserver/optimizer.go +++ b/go/pserver/optimizer.go @@ -2,7 +2,7 @@ package pserver // #cgo CFLAGS: -I ../../ // //FIXME: ldflags contain "build" path -// #cgo LDFLAGS: ../../build/go/pserver/client/c/libpaddle_go_optimizer.a -lstdc++ +// #cgo LDFLAGS: ../../build/go/pserver/client/c/libpaddle_go_optimizer.a -lstdc++ -lm // #include "paddle/optimizer/optimizer.h" // #include // #include -- GitLab From d8941e67ec5da7333666b31264704dae7d830ca2 Mon Sep 17 00:00:00 2001 From: gongweibao Date: Tue, 4 Jul 2017 08:24:28 +0000 Subject: [PATCH 1022/3256] fix bugs --- .travis.yml | 2 +- paddle/scripts/docker/build.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4f72e2ca336..16432dac0cf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -42,7 +42,7 @@ before_install: function timeout() { perl -e 'alarm shift; exec @ARGV' "$@"; } script: - | - timeout 2580 paddle/scripts/travis/${JOB}.sh -e "WITH_GOLANG=ON" # 43min timeout + export WITH_GOLANG=ON && timeout 2580 paddle/scripts/travis/${JOB}.sh # 43min timeout RESULT=$?; if [ $RESULT -eq 0 ] || [ $RESULT -eq 142 ]; then true; else false; fi; notifications: email: diff --git a/paddle/scripts/docker/build.sh b/paddle/scripts/docker/build.sh index a182e5f4aef..1ccee686df4 100644 --- a/paddle/scripts/docker/build.sh +++ b/paddle/scripts/docker/build.sh @@ -3,7 +3,7 @@ set -xe # Set BASE_IMAGE according to env variables -if [ ${WITH_GPU} == "ON" ]; then +if [[ ${WITH_GPU} == "ON" ]]; then BASE_IMAGE="nvidia/cuda:8.0-cudnn5-runtime-ubuntu16.04" else BASE_IMAGE="ubuntu:16.04" -- GitLab From e95299b58300afda0d61e868998dfceb28e999da Mon Sep 17 00:00:00 2001 From: Superjom Date: Tue, 4 Jul 2017 16:28:21 +0800 Subject: [PATCH 1023/3256] fix ci error --- 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 ceff1d3581b..0abc63a831b 100644 --- a/paddle/framework/CMakeLists.txt +++ b/paddle/framework/CMakeLists.txt @@ -7,6 +7,6 @@ cc_test(scope_test SRCS scope_test.cc) cc_test(enforce_test SRCS enforce_test.cc) proto_library(attr_type SRCS attr_type.proto) proto_library(op_proto SRCS op_proto.proto DEPS attr_type) +cc_test(op_proto_test SRCS op_proto_test.cc DEPS op_proto protobuf) proto_library(net_proto SRCS net_proto.proto DEPS op_proto) -cc_test(op_proto_test SRCS op_proto_test.cc DEPS op_proto attr_type protobuf) cc_library(net SRCS net.cc DEPS net_proto attr_type op_proto) -- GitLab From 86543f7f6a8f0fc073977794abee9ae5b033f78e Mon Sep 17 00:00:00 2001 From: yangyaming Date: Tue, 4 Jul 2017 16:40:00 +0800 Subject: [PATCH 1024/3256] Follow comments. --- doc/api/v2/config/layer.rst | 2 +- paddle/gserver/layers/DetectionOutputLayer.h | 8 +- paddle/gserver/layers/MultiBoxLossLayer.cpp | 6 +- paddle/gserver/layers/MultiBoxLossLayer.h | 2 +- .../paddle/trainer_config_helpers/layers.py | 20 +++-- .../test_detection_output_layer.protostr | 66 ++++++++++++++++ .../test_multibox_loss_layer.protostr | 79 +++++++++++++++++++ 7 files changed, 164 insertions(+), 19 deletions(-) create mode 100644 python/paddle/trainer_config_helpers/tests/configs/protostr/test_detection_output_layer.protostr create mode 100644 python/paddle/trainer_config_helpers/tests/configs/protostr/test_multibox_loss_layer.protostr diff --git a/doc/api/v2/config/layer.rst b/doc/api/v2/config/layer.rst index 0a8465919d9..4f4a9187bcb 100644 --- a/doc/api/v2/config/layer.rst +++ b/doc/api/v2/config/layer.rst @@ -478,6 +478,6 @@ Detection output Layer ====================== detection_output ---- +---------------- .. autoclass:: paddle.v2.layer.detection_output :noindex: diff --git a/paddle/gserver/layers/DetectionOutputLayer.h b/paddle/gserver/layers/DetectionOutputLayer.h index 9cc568219ca..a232af0a691 100644 --- a/paddle/gserver/layers/DetectionOutputLayer.h +++ b/paddle/gserver/layers/DetectionOutputLayer.h @@ -22,14 +22,14 @@ limitations under the License. */ namespace paddle { /** - * The detection output layer for a SSD detection task. This layer apply the - * Non-maximum suppression to the all predicted bounding box and keep the + * The detection output layer for a SSD detection task. This layer applies the + * Non-maximum suppression to the all predicted bounding box and keeps the * Top-K bounding boxes. - * - Input: This layer needs three input layers: This first input layer + * - Input: This layer needs three input layers: The first input layer * is the priorbox layer. The rest two input layers are convolution * layers for generating bbox location offset and the classification * confidence. - * - Output: The predict bounding box location. + * - Output: The predict bounding box locations. */ class DetectionOutputLayer : public Layer { diff --git a/paddle/gserver/layers/MultiBoxLossLayer.cpp b/paddle/gserver/layers/MultiBoxLossLayer.cpp index f2d7b8eb1da..bbf1166dced 100644 --- a/paddle/gserver/layers/MultiBoxLossLayer.cpp +++ b/paddle/gserver/layers/MultiBoxLossLayer.cpp @@ -258,8 +258,7 @@ void MultiBoxLossLayer::forward(PassType passType) { } real loss = locLoss_ + confLoss_; MatrixPtr outV = getOutputValue(); - std::vector tmp(batchSize, loss); - outV->copyFrom(&tmp[0], batchSize); + outV->assign(loss); } void MultiBoxLossLayer::backward(const UpdateCallback& callback) { @@ -336,6 +335,9 @@ void MultiBoxLossLayer::backward(const UpdateCallback& callback) { const MatrixPtr inLocG = getInputGrad(*getLocInputLayer(n)); const MatrixPtr inConfG = getInputGrad(*getConfInputLayer(n)); size_t height = getInput(*getLocInputLayer(n)).getFrameHeight(); + // only for unittest, there are no width and height information + // when constructing matrix in unittest, so we should + // set the shape in configuration if (!height) height = layerConf.height(); size_t width = getInput(*getLocInputLayer(n)).getFrameWidth(); if (!width) width = layerConf.width(); diff --git a/paddle/gserver/layers/MultiBoxLossLayer.h b/paddle/gserver/layers/MultiBoxLossLayer.h index 9767fed7f1c..9935da56446 100644 --- a/paddle/gserver/layers/MultiBoxLossLayer.h +++ b/paddle/gserver/layers/MultiBoxLossLayer.h @@ -30,7 +30,7 @@ namespace paddle { * The loss is composed by the location loss and the confidence loss. * The location loss is a smooth L1 loss and the confidence loss is * a softmax loss. - * - Input: This layer need four input layers: This first input layer + * - Input: This layer needs four input layers: The first input layer * is the priorbox layer and the second layer is a label layer. * The rest two input layers are convolution layers for generating * bbox location offset and the classification confidence. diff --git a/python/paddle/trainer_config_helpers/layers.py b/python/paddle/trainer_config_helpers/layers.py index 1286ed198e0..86e91e2c57f 100755 --- a/python/paddle/trainer_config_helpers/layers.py +++ b/python/paddle/trainer_config_helpers/layers.py @@ -1072,10 +1072,10 @@ def multibox_loss_layer(input_loc, :param name: The Layer Name. :type name: basestring - :param input_loc: The input predict location. - :type input_loc: LayerOutput + :param input_loc: The input predict locations. + :type input_loc: LayerOutput | List of LayerOutput :param input_conf: The input priorbox confidence. - :type input_conf: LayerOutput + :type input_conf: LayerOutput | List of LayerOutput :param priorbox: The input priorbox location and the variance. :type priorbox: LayerOutput :param label: The input label. @@ -1146,10 +1146,10 @@ def detection_output_layer(input_loc, :param name: The Layer Name. :type name: basestring - :param input_loc: The input predict location. - :type input_loc: LayerOutput + :param input_loc: The input predict locations. + :type input_loc: LayerOutput | List of LayerOutput. :param input_conf: The input priorbox confidence. - :type input_conf: LayerOutput + :type input_conf: LayerOutput | List of LayerOutput. :param priorbox: The input priorbox location and the variance. :type priorbox: LayerOutput :param num_classes: The number of the classification. @@ -1166,22 +1166,20 @@ def detection_output_layer(input_loc, :type background_id: int :return: LayerOutput """ - input_loc_num = 0 - input_conf_num = 0 - if isinstance(input_loc, LayerOutput): input_loc = [input_loc] assert isinstance(input_loc, collections.Sequence) # list or tuple for each in input_loc: assert isinstance(each, LayerOutput) - input_loc_num += 1 + input_loc_num = len(input_loc) if isinstance(input_conf, LayerOutput): input_conf = [input_conf] assert isinstance(input_conf, collections.Sequence) # list or tuple for each in input_conf: assert isinstance(each, LayerOutput) - input_conf_num += 1 + input_conf_num = len(input_conf) + # Check the input layer number. assert input_loc_num == input_conf_num diff --git a/python/paddle/trainer_config_helpers/tests/configs/protostr/test_detection_output_layer.protostr b/python/paddle/trainer_config_helpers/tests/configs/protostr/test_detection_output_layer.protostr new file mode 100644 index 00000000000..6690f9852a3 --- /dev/null +++ b/python/paddle/trainer_config_helpers/tests/configs/protostr/test_detection_output_layer.protostr @@ -0,0 +1,66 @@ +type: "nn" +layers { + name: "input_loc" + type: "data" + size: 16 + active_type: "" + height: 16 + width: 1 +} +layers { + name: "input_conf" + type: "data" + size: 8 + active_type: "" + height: 1 + width: 8 +} +layers { + name: "priorbox" + type: "data" + size: 32 + active_type: "" + height: 4 + width: 8 +} +layers { + name: "test_detection_output" + type: "detection_output" + size: 1400 + active_type: "" + inputs { + input_layer_name: "priorbox" + detection_output_conf { + num_classes: 21 + nms_threshold: 0.45 + nms_top_k: 400 + background_id: 0 + input_num: 1 + keep_top_k: 200 + confidence_threshold: 0.01 + } + } + inputs { + input_layer_name: "input_loc" + } + inputs { + input_layer_name: "input_conf" + } +} +input_layer_names: "priorbox" +input_layer_names: "input_loc" +input_layer_names: "input_conf" +output_layer_names: "test_detection_output" +sub_models { + name: "root" + layer_names: "input_loc" + layer_names: "input_conf" + layer_names: "priorbox" + layer_names: "test_detection_output" + input_layer_names: "priorbox" + input_layer_names: "input_loc" + input_layer_names: "input_conf" + output_layer_names: "test_detection_output" + is_recurrent_layer_group: false +} + diff --git a/python/paddle/trainer_config_helpers/tests/configs/protostr/test_multibox_loss_layer.protostr b/python/paddle/trainer_config_helpers/tests/configs/protostr/test_multibox_loss_layer.protostr new file mode 100644 index 00000000000..0ba84dcc6db --- /dev/null +++ b/python/paddle/trainer_config_helpers/tests/configs/protostr/test_multibox_loss_layer.protostr @@ -0,0 +1,79 @@ +type: "nn" +layers { + name: "input_loc" + type: "data" + size: 16 + active_type: "" + height: 16 + width: 1 +} +layers { + name: "input_conf" + type: "data" + size: 8 + active_type: "" + height: 1 + width: 8 +} +layers { + name: "priorbox" + type: "data" + size: 32 + active_type: "" + height: 4 + width: 8 +} +layers { + name: "label" + type: "data" + size: 24 + active_type: "" + height: 4 + width: 6 +} +layers { + name: "test_multibox_loss" + type: "multibox_loss" + size: 1 + active_type: "" + inputs { + input_layer_name: "priorbox" + multibox_loss_conf { + num_classes: 21 + overlap_threshold: 0.5 + neg_pos_ratio: 3.0 + neg_overlap: 0.5 + background_id: 0 + input_num: 1 + } + } + inputs { + input_layer_name: "label" + } + inputs { + input_layer_name: "input_loc" + } + inputs { + input_layer_name: "input_conf" + } +} +input_layer_names: "priorbox" +input_layer_names: "label" +input_layer_names: "input_loc" +input_layer_names: "input_conf" +output_layer_names: "test_multibox_loss" +sub_models { + name: "root" + layer_names: "input_loc" + layer_names: "input_conf" + layer_names: "priorbox" + layer_names: "label" + layer_names: "test_multibox_loss" + input_layer_names: "priorbox" + input_layer_names: "label" + input_layer_names: "input_loc" + input_layer_names: "input_conf" + output_layer_names: "test_multibox_loss" + is_recurrent_layer_group: false +} + -- GitLab From 211f83fa2257716421f7db0431a5e707e788773a Mon Sep 17 00:00:00 2001 From: zlx Date: Tue, 4 Jul 2017 17:05:25 +0800 Subject: [PATCH 1025/3256] set depthwise conv layer interface in python --- python/paddle/trainer/config_parser.py | 57 ++++++++++++ .../paddle/trainer_config_helpers/layers.py | 90 +++++++++++++++++++ 2 files changed, 147 insertions(+) diff --git a/python/paddle/trainer/config_parser.py b/python/paddle/trainer/config_parser.py index b7418101d83..2965c922fa6 100644 --- a/python/paddle/trainer/config_parser.py +++ b/python/paddle/trainer/config_parser.py @@ -1741,6 +1741,59 @@ class ParameterReluLayer(LayerBase): self.create_input_parameter(0, input_layer.size / partial_sum) +@config_layer('depthwise_conv') +class DepthwiseConvLayer(LayerBase): + layer_type = 'depthwise_conv' + + def __init__(self, + name, + inputs=[], + bias=True, + num_filters=None, + shared_biases=False, + **xargs): + super(DepthwiseConvLayer, self).__init__( + name, self.layer_type, 0, inputs=inputs, **xargs) + + if num_filters is not None: + self.config.num_filters = num_filters + + use_gpu = int(g_command_config_args.get("use_gpu", 0)) + parallel_nn = int(g_command_config_args.get("parallel_nn", 0)) + + # Automatically select cudnn_type for GPU and exconv for CPU + # if set type=conv, but still reserve the way user specify + # exconv or cudnn_conv manually. + self.layer_type = "depthwise_conv" + # need to specify layer in config + self.config.type = self.layer_type + + if shared_biases is not None: + self.config.shared_biases = shared_biases + + for input_index in xrange(len(self.inputs)): + input_layer = self.get_input_layer(input_index) + conv_conf = self.config.inputs[input_index].conv_conf + #set the groups + self.inputs[input_index].conv.groups = self.inputs[ + input_index].conv.channels + parse_conv(self.inputs[input_index].conv, input_layer.name, + conv_conf, num_filters) + psize = self.calc_parameter_size(conv_conf) + self.create_input_parameter(input_index, psize) + self.set_cnn_layer(name, conv_conf.output_y, conv_conf.output_x, + self.config.num_filters) + + psize = self.config.size + if shared_biases: + psize = self.config.num_filters + self.create_bias_parameter(bias, psize, [psize, 1]) + + def calc_parameter_size(self, conv_conf): + return self.config.num_filters * conv_conf.filter_channels \ + * (conv_conf.filter_size * conv_conf.filter_size_y) + + @config_layer('conv') class ConvLayerBase(LayerBase): layer_type = 'conv' @@ -3145,6 +3198,10 @@ def ParameterHook(type, **kwargs): if sparsity_ratio is not None: hook.sparsity_ratio = sparsity_ratio return hook + elif type == 'dpruning': + hook = ParameterUpdaterHookConfig() + hook.type = type + return hook else: return None diff --git a/python/paddle/trainer_config_helpers/layers.py b/python/paddle/trainer_config_helpers/layers.py index a601d5c84ad..073e853bc2d 100755 --- a/python/paddle/trainer_config_helpers/layers.py +++ b/python/paddle/trainer_config_helpers/layers.py @@ -57,6 +57,7 @@ __all__ = [ 'classification_cost', 'LayerOutput', 'img_conv_layer', + 'img_depthwise_conv_layer', 'img_pool_layer', 'batch_norm_layer', 'img_cmrnorm_layer', @@ -148,6 +149,7 @@ class LayerType(object): HSIGMOID = 'hsigmoid' CONV_LAYER = 'conv' CONVTRANS_LAYER = 'convt' + DEPTHWISE_CONV_LAYER = 'depthwise_conv' EXCONV_LAYER = 'exconv' EXCONVTRANS_LAYER = 'exconvt' CUDNNCONV_LAYER = 'cudnn_conv' @@ -2085,6 +2087,94 @@ def hsigmoid(input, name, LayerType.HSIGMOID, parents=parents, size=l.config.size) +@wrap_name_default("depthwise_conv") +@wrap_param_attr_default() +@wrap_bias_attr_default() +@wrap_act_default(act=ReluActivation()) +@layer_support(DROPOUT) +def img_depthwise_conv_layer(input, + filter_size, + num_filters, + name=None, + num_channels=None, + act=None, + groups=1, + stride=1, + padding=0, + bias_attr=None, + param_attr=None, + shared_biases=True, + layer_attr=None, + filter_size_y=None, + stride_y=None, + padding_y=None, + trans=False, + layer_type=None): + + if num_channels is None: + assert input.num_filters is not None + num_channels = input.num_filters + + if filter_size_y is None: + if isinstance(filter_size, collections.Sequence): + assert len(filter_size) == 2 + filter_size, filter_size_y = filter_size + else: + filter_size_y = filter_size + + if stride_y is None: + if isinstance(stride, collections.Sequence): + assert len(stride) == 2 + stride, stride_y = stride + else: + stride_y = stride + + if padding_y is None: + if isinstance(padding, collections.Sequence): + assert len(padding) == 2 + padding, padding_y = padding + else: + padding_y = padding + + if param_attr.attr.get('initial_smart'): + # special initial for conv layers. + init_w = (2.0 / (filter_size**2 * num_channels))**0.5 + param_attr.attr["initial_mean"] = 0.0 + param_attr.attr["initial_std"] = init_w + param_attr.attr["initial_strategy"] = 0 + param_attr.attr["initial_smart"] = False + + lt = LayerType.DEPTHWISE_CONV_LAYER + + l = Layer( + name=name, + inputs=Input( + input.name, + conv=Conv( + filter_size=filter_size, + padding=padding, + stride=stride, + channels=num_channels, + groups=groups, + filter_size_y=filter_size_y, + padding_y=padding_y, + stride_y=stride_y), + **param_attr.attr), + active_type=act.name, + num_filters=num_filters, + bias=ParamAttr.to_bias(bias_attr), + shared_biases=shared_biases, + type=lt, + **ExtraLayerAttribute.to_kwargs(layer_attr)) + return LayerOutput( + name, + lt, + parents=[input], + activation=act, + num_filters=num_filters, + size=l.config.size) + + @wrap_name_default("conv") @wrap_param_attr_default() @wrap_bias_attr_default() -- GitLab From eeb17c26fdfed5d3cb157ceabf0a89ec93329414 Mon Sep 17 00:00:00 2001 From: zlx Date: Tue, 4 Jul 2017 17:06:25 +0800 Subject: [PATCH 1026/3256] add depthwise operation and depthwise conv layer --- paddle/function/DepthwiseConvOp.cpp | 308 +++++++++++++++++++ paddle/function/DepthwiseConvOp.h | 91 ++++++ paddle/function/DepthwiseConvOpGpu.cu | 295 ++++++++++++++++++ paddle/gserver/layers/DepthwiseConvLayer.cpp | 165 ++++++++++ paddle/gserver/layers/DepthwiseConvLayer.h | 52 ++++ 5 files changed, 911 insertions(+) create mode 100644 paddle/function/DepthwiseConvOp.cpp create mode 100644 paddle/function/DepthwiseConvOp.h create mode 100644 paddle/function/DepthwiseConvOpGpu.cu create mode 100644 paddle/gserver/layers/DepthwiseConvLayer.cpp create mode 100644 paddle/gserver/layers/DepthwiseConvLayer.h diff --git a/paddle/function/DepthwiseConvOp.cpp b/paddle/function/DepthwiseConvOp.cpp new file mode 100644 index 00000000000..ad332d2931b --- /dev/null +++ b/paddle/function/DepthwiseConvOp.cpp @@ -0,0 +1,308 @@ +/* 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 "DepthwiseConvOp.h" +#include "GemmFunctor.h" +#include "paddle/math/MemoryHandle.h" + +namespace paddle { + +/* + * imData = [input_channels, input_height, input_width] + * colData = [input_channels, filter_height, filter_width, + * output_height, output_width] + */ +template +class DepthwiseConvFunctor { +public: + void operator()(int outputSize, + const T* inputData, + const T* filterData, + int batchSize, + int outputChannels, + int outputHeight, + int outputWidth, + int filterHeight, + int filterWidth, + int strideH, + int strideW, + int paddingH, + int paddingW, + T* outputData) { + // NO_IMPLEMENTATION + } +}; + +template +class DepthwiseConvGradInputFunctor { +public: + void operator()(int inputSize, + const T* outputGrad, + const T* filterData, + int batchSize, + int outputChannels, + int outputHeight, + int outputWidth, + int inputHeight, + int inputWidth, + int filterHeight, + int filterWidth, + int strideH, + int strideW, + int paddingH, + int paddingW, + T* inputGrad) {} +}; + +template +class DepthwiseConvGradFilterFunctor { +public: + void operator()(int num_i, + int colDataSize, + const T* outputGrad, + const T* inputData, + int batchSize, + int outputChannels, + int outputHeight, + int outputWidth, + int inputHeight, + int inputWidth, + int filterHeight, + int filterWidth, + int strideH, + int strideW, + int paddingH, + int paddingW, + T* colData, + T* multiplierData, + T* filterGrad) {} +}; + +/* + * \brief Forward calculation of convolution. + */ +template +class DepthwiseConvFunction : public ConvFunctionBase { +public: + void init(const FuncConfig& config) override { + ConvFunctionBase::init(config); + } + + virtual void check(const BufferArgs& inputs, + const BufferArgs& outputs) override { + const TensorShape& input = inputs[0].shape(); + const TensorShape& filter = inputs[1].shape(); + const TensorShape& output = outputs[0].shape(); + checkShape(input, filter, output); + } + + void calc(const BufferArgs& inputs, const BufferArgs& outputs) override { + CHECK_EQ(numInputs_, inputs.size()); + CHECK_EQ(numOutputs_, outputs.size()); + check(inputs, outputs); + + const TensorShape& input = inputs[0].shape(); + const TensorShape& filter = inputs[1].shape(); + const TensorShape& output = outputs[0].shape(); + + size_t batchSize = input[0]; + // size_t inputChannels = input[1]; + // size_t inputHeight = input[2]; + // size_t inputWidth = input[3]; + size_t filterHeight = getFilterHeight(filter); + size_t filterWidth = getFilterWidth(filter); + size_t outputChannels = output[1]; + size_t outputHeight = output[2]; + size_t outputWidth = output[3]; + + real* inputData = inputs[0].data(); + real* filterData = inputs[1].data(); + real* outputData = outputs[0].data(); + size_t outputSize = batchSize * outputChannels * outputHeight * outputWidth; + + DepthwiseConvFunctor depthwiseConv; + depthwiseConv(outputSize, + inputData, + filterData, + batchSize, + outputChannels, + outputHeight, + outputWidth, + filterHeight, + filterWidth, + strideH(), + strideW(), + paddingH(), + paddingW(), + outputData); + } +}; + +/* + * \brief Backward input calculation of convolution. + */ +template +class DepthwiseConvGradInputFunction : public ConvFunctionBase { +public: + void init(const FuncConfig& config) override { + ConvFunctionBase::init(config); + } + + virtual void check(const BufferArgs& inputs, + const BufferArgs& outputs) override { + const TensorShape& output = inputs[0].shape(); + const TensorShape& filter = inputs[1].shape(); + const TensorShape& input = outputs[0].shape(); + checkShape(input, filter, output); + } + + void calc(const BufferArgs& inputs, const BufferArgs& outputs) override { + CHECK_EQ(numInputs_, inputs.size()); + CHECK_EQ(numOutputs_, outputs.size()); + check(inputs, outputs); + // Since the implementation of Col2ImFunctor is ADD_TO, + // this function only supports ADD_TO mode. + CHECK_EQ(outputs[0].getArgType(), ADD_TO); + const TensorShape& output = inputs[0].shape(); + const TensorShape& filter = inputs[1].shape(); + const TensorShape& input = outputs[0].shape(); + + size_t batchSize = input[0]; + size_t inputChannels = input[1]; + size_t inputHeight = input[2]; + size_t inputWidth = input[3]; + size_t filterHeight = getFilterHeight(filter); + size_t filterWidth = getFilterWidth(filter); + size_t outputChannels = output[1]; + size_t outputHeight = output[2]; + size_t outputWidth = output[3]; + + real* outputGrad = inputs[0].data(); + real* filterData = inputs[1].data(); + real* inputGrad = outputs[0].data(); + + size_t inputSize = batchSize * inputChannels * inputHeight * inputWidth; + + DepthwiseConvGradInputFunctor depthwiseConvGradInput; + depthwiseConvGradInput(inputSize, + outputGrad, + filterData, + batchSize, + outputChannels, + outputHeight, + outputWidth, + inputHeight, + inputWidth, + filterHeight, + filterWidth, + strideH(), + strideW(), + paddingH(), + paddingW(), + inputGrad); + } +}; + +/* + * \brief Backward filter calculation of convolution. + */ +template +class DepthwiseConvGradFilterFunction : public ConvFunctionBase { +public: + void init(const FuncConfig& config) override { + ConvFunctionBase::init(config); + } + + virtual void check(const BufferArgs& inputs, + const BufferArgs& outputs) override { + const TensorShape& output = inputs[0].shape(); + const TensorShape& input = inputs[1].shape(); + const TensorShape& filter = outputs[0].shape(); + checkShape(input, filter, output); + } + + void calc(const BufferArgs& inputs, const BufferArgs& outputs) override { + CHECK_EQ(numInputs_, inputs.size()); + CHECK_EQ(numOutputs_, outputs.size()); + check(inputs, outputs); + const TensorShape& output = inputs[0].shape(); + const TensorShape& input = inputs[1].shape(); + // const TensorShape& multiplier = inputs[2].shape(); + const TensorShape& filter = outputs[0].shape(); + + size_t batchSize = input[0]; + size_t inputChannels = input[1]; + size_t inputHeight = input[2]; + size_t inputWidth = input[3]; + size_t filterHeight = getFilterHeight(filter); + size_t filterWidth = getFilterWidth(filter); + size_t outputChannels = output[1]; + size_t outputHeight = output[2]; + size_t outputWidth = output[3]; + + real* outputGrad = inputs[0].data(); + real* inputData = inputs[1].data(); + real* multiplierData = inputs[2].data(); + real* filterGrad = outputs[0].data(); + + size_t size = + inputChannels * filterHeight * filterWidth * outputHeight * outputWidth; + + resizeBuffer(size); + real* colData = reinterpret_cast(memory_->getBuf()); + + DepthwiseConvGradFilterFunctor depthwiseConvGradFilter; + + for (size_t i = 0; i < batchSize; i++) { + depthwiseConvGradFilter(i, + size, + outputGrad, + inputData, + batchSize, + outputChannels, + outputHeight, + outputWidth, + inputHeight, + inputWidth, + filterHeight, + filterWidth, + strideH(), + strideW(), + paddingH(), + paddingW(), + colData, + multiplierData, + filterGrad); + } + } +}; + +REGISTER_TYPED_FUNC(DepthwiseConv, CPU, DepthwiseConvFunction); +REGISTER_TYPED_FUNC(DepthwiseConvGradInput, + CPU, + DepthwiseConvGradInputFunction); +REGISTER_TYPED_FUNC(DepthwiseConvGradFilter, + CPU, + DepthwiseConvGradFilterFunction); +#ifndef PADDLE_ONLY_CPU +REGISTER_TYPED_FUNC(DepthwiseConv, GPU, DepthwiseConvFunction); +REGISTER_TYPED_FUNC(DepthwiseConvGradInput, + GPU, + DepthwiseConvGradInputFunction); +REGISTER_TYPED_FUNC(DepthwiseConvGradFilter, + GPU, + DepthwiseConvGradFilterFunction); +#endif + +} // namespace paddle diff --git a/paddle/function/DepthwiseConvOp.h b/paddle/function/DepthwiseConvOp.h new file mode 100644 index 00000000000..8af1db974de --- /dev/null +++ b/paddle/function/DepthwiseConvOp.h @@ -0,0 +1,91 @@ +/* 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 "ConvOp.h" + +namespace paddle { + +/* + * imData = [input_channels, input_height, input_width] + * colData = [input_channels, filter_height, filter_width, + * output_height, output_width] + */ +template +class DepthwiseConvFunctor { +public: + void operator()(int outputSize, + const T* inputData, + const T* filterData, + int batchSize, + int outputChannels, + int outputHeight, + int outputWidth, + int filterHeight, + int filterWidth, + int strideH, + int strideW, + int paddingH, + int paddingW, + T* outputData); +}; + +template +class DepthwiseConvGradInputFunctor { +public: + void operator()(int inputSize, + const T* outputGrad, + const T* filterData, + int batchSize, + int outputChannels, + int outputHeight, + int outputWidth, + int inputHeight, + int inputWidth, + int filterHeight, + int filterWidth, + int strideH, + int strideW, + int paddingH, + int paddingW, + T* inputGrad); +}; + +template +class DepthwiseConvGradFilterFunctor { +public: + void operator()(int num_i, + int colDataSize, + const T* outputGrad, + const T* inputData, + int batchSize, + int outputChannels, + int outputHeight, + int outputWidth, + int inputHeight, + int inputWidth, + int filterHeight, + int filterWidth, + int strideH, + int strideW, + int paddingH, + int paddingW, + T* colData, + T* multiplierData, + T* filterGrad); + +}; // namespace paddle + +} // namespace paddle diff --git a/paddle/function/DepthwiseConvOpGpu.cu b/paddle/function/DepthwiseConvOpGpu.cu new file mode 100644 index 00000000000..1b2d5d99ed2 --- /dev/null +++ b/paddle/function/DepthwiseConvOpGpu.cu @@ -0,0 +1,295 @@ +/* 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 "ConvOp.h" +#include "DepthwiseConvOp.h" + +namespace paddle { +template +__global__ void ConvolutionDepthwiseWeightForward(const int nthreads, + const T* const bottom_data, const T* const weight_data, + const int num, const int channels, const int top_height, + const int top_width, const int bottom_height, const int bottom_width, + const int kernel_h, const int kernel_w, const int stride_h, + const int stride_w, const int pad_h, const int pad_w, + const int dilation_h, const int dilation_w, T* const top_data) { + + int index = + (blockIdx.x * gridDim.y + blockIdx.y) * blockDim.x + threadIdx.x; + + if(index < nthreads) { + const int n = index / channels / top_height / top_width; + const int c = (index / top_height / top_width) % channels; + const int h = (index / top_width) % top_height; + const int w = index % top_width; + const T* weight = weight_data + c * kernel_h * kernel_w; + T value = 0; + for (int kh = 0; kh < kernel_h; ++kh) { + for (int kw = 0; kw < kernel_w; ++kw) { + const int h_in = -pad_h + h * stride_h + kh * dilation_h; + const int w_in = -pad_w + w * stride_w + kw * dilation_w; + if ((h_in >= 0) && (h_in < bottom_height) + && (w_in >= 0) && (w_in < bottom_width)) { + const int offset = ((n * channels + c) * bottom_height + h_in) + * bottom_width + w_in; + value += (*weight) * bottom_data[offset]; + } + ++weight; + } + } + top_data[index] = value; + } +} + +template +__global__ void ConvolutionDepthwiseBottomBackward(const int nthreads, + const T* const top_diff, const T* const weight_data, + const int num, const int channels, const int top_height, + const int top_width, const int bottom_height, const int bottom_width, + const int kernel_h, const int kernel_w, const int stride_h, + const int stride_w, const int pad_h, const int pad_w, + const int dilation_h, const int dilation_w, T* const bottom_diff) { + int index = + (blockIdx.x * gridDim.y + blockIdx.y) * blockDim.x + threadIdx.x; + if(index < nthreads) { + const int n = index / channels / bottom_height / bottom_width; + const int c = (index / bottom_height / bottom_width) % channels; + const int h = (index / bottom_width) % bottom_height; + const int w = index % bottom_width; + const T* weight = weight_data + c * kernel_h * kernel_w; + T value = 0; + for (int kh = 0; kh < kernel_h; ++kh) { + for (int kw = 0; kw < kernel_w; ++kw) { + const int h_out_s = h + pad_h - kh * dilation_h; + const int w_out_s = w + pad_w - kw * dilation_w; + if (((h_out_s % stride_h) == 0) && ((w_out_s % stride_w) == 0)) { + const int h_out = h_out_s / stride_h; + const int w_out = w_out_s / stride_w; + //it affect the effectives + if ((h_out >= 0) && (h_out < top_height) + && (w_out >= 0) && (w_out < top_width)) { + const int offset = ((n * channels + c) * top_height + h_out) + * top_width + w_out; + value += (*weight) * top_diff[offset]; + } + } + ++weight; + } + } + bottom_diff[index] += value; + } +} + +template +__global__ void ConvolutionDepthwiseWeightBackward(const int num_i, const int nthreads, + const T* const top_diff, const T* const bottom_data, + const int num, const int channels, const int top_height, + const int top_width, const int bottom_height, const int bottom_width, + const int kernel_h, const int kernel_w, const int stride_h, + const int stride_w, const int pad_h, const int pad_w, + const int dilation_h, const int dilation_w, T* const buffer_data) { + int index = + (blockIdx.x * gridDim.y + blockIdx.y) * blockDim.x + threadIdx.x; + if (index < nthreads) { + const int h = (index / top_width) % top_height; + const int w = index % top_width; + const int kh = (index / kernel_w / top_height / top_width) + % kernel_h; + const int kw = (index / top_height / top_width) % kernel_w; + const int h_in = -pad_h + h * stride_h + kh * dilation_h; + const int w_in = -pad_w + w * stride_w + kw * dilation_w; + if ((h_in >= 0) && (h_in < bottom_height) + && (w_in >= 0) && (w_in < bottom_width)) { + const int c = index / kernel_h / kernel_w / top_height / top_width; + const int n = num_i; + const int top_offset = ((n * channels + c) * top_height + h) + * top_width + w; + const int bottom_offset = ((n * channels + c) * bottom_height + h_in) + * bottom_width + w_in; + buffer_data[index] = top_diff[top_offset] * bottom_data[bottom_offset]; + } else { + buffer_data[index] = 0; + } + } +} + +template +class DepthwiseConvFunctor{ +public: + void operator()(int outputSize, + const T* inputData, + const T* filterData, + int batchSize, + int outputChannels, + int outputHeight, + int outputWidth, + int filterHeight, + int filterWidth, + int strideH, + int strideW, + int paddingH, + int paddingW, + T* outputData){ + + size_t blocks = (outputSize + 1024 -1) / 1024; + size_t blockX = 512; + size_t blockY = (blocks+512-1)/512; + dim3 threads(1024, 1); + dim3 grid(blockX, blockY); + + ConvolutionDepthwiseWeightForward + <<< grid, threads, 0, STREAM_DEFAULT >>>( + outputSize, + inputData, + filterData, + batchSize, + outputChannels, + outputHeight, + outputWidth, + filterHeight, + filterWidth, + strideH, + strideW, + paddingH, + paddingW, + outputData); + } +}; + +template +class DepthwiseConvGradInputFunctor{ +public: + void operator()(int inputSize, + const T* outputGrad, + const T* filterData, + int batchSize, + int outputChannels, + int outputHeight, + int outputWidth, + int inputHeight, + int inputWidth, + int filterHeight, + int filterWidth, + int strideH, + int strideW, + int paddingH, + int paddingW, + T* inputGrad){ + + size_t blocks = (inputSize + 1024 -1) / 1024; + size_t blockX = 512; + size_t blockY = (blocks+512-1)/512; + dim3 threads(1024, 1); + dim3 grid(blockX, blockY); + + ConvolutionDepthwiseBottomBackward + // NOLINT_NEXT_LINE(whitespace/operators) + <<< grid, threads, 0, STREAM_DEFAULT >>>( + inputSize, + outputGrad, + filterData, + batchSize, + outputChannels, + outputHeight, + outputWidth, + inputHeight, + inputWidth, + filterHeight, + filterWidth, + strideH, + strideW, + paddingH, + paddingW, + inputGrad); + } +}; + +template +class DepthwiseConvGradFilterFunctor { +public: + void operator()(int num_i, + int colDataSize, + const T* outputGrad, + const T* inputData, + int batchSize, + int outputChannels, + int outputHeight, + int outputWidth, + int inputHeight, + int inputWidth, + int filterHeight, + int filterWidth, + int strideH, + int strideW, + int paddingH, + int paddingW, + T* colData, + T* multiplierData, + T* filterGrad){ + + size_t blocks = (colDataSize + 1024 -1) / 1024; + size_t blockX = 512; + size_t blockY = (blocks+512-1)/512; + dim3 threads(1024, 1); + dim3 grid(blockX, blockY); + + ConvolutionDepthwiseWeightBackward + <<< grid, threads, 0, STREAM_DEFAULT >>>( + i, + size, + outputGrad, + inputData, + batchSize, + outputChannels, + outputHeight, + outputWidth, + inputHeight, + inputWidth, + filterHeight, + filterWidth, + strideH, + strideW, + paddingH, + paddingW, + colData + ); + GemmFunctor gemm; + int M = size / outputHeight / outputWidth; + int N = 1; + int K = outputHeight * outputWidth; + gemm(CblasNoTrans, + CblasNoTrans, + M, + N, + K, + 1.0f, + colData, + K, + multiplierData, + N, + 1.0f, + filterGrad, + N); + //gemv + } +}; + +template class DepthwiseConvGradInputFunctor; +template class DepthwiseConvGradInputFunctor; +template class DepthwiseConvFunctor; +template class DepthwiseConvFunctor; +template class DepthwiseConvGradFilterFunctor; +template class DepthwiseConvGradFilterFunctor; + +} // namespace paddle diff --git a/paddle/gserver/layers/DepthwiseConvLayer.cpp b/paddle/gserver/layers/DepthwiseConvLayer.cpp new file mode 100644 index 00000000000..9df8a9df7cc --- /dev/null +++ b/paddle/gserver/layers/DepthwiseConvLayer.cpp @@ -0,0 +1,165 @@ +/* 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 "DepthwiseConvLayer.h" +#include "paddle/utils/Logging.h" +#include "paddle/utils/Stat.h" + +namespace paddle { + +/* + * The calculation of the exconvt(convolution transpose (deconv) operation) + * is a swap of forward and backward of the calculation of exconv. + * */ +REGISTER_LAYER(depthwise_conv, DepthwiseConvLayer); + +bool DepthwiseConvLayer::init(const LayerMap &layerMap, + const ParameterMap ¶meterMap) { + /* Initialize the basic convolutional parent class */ + ExpandConvBaseLayer::init(layerMap, parameterMap); + + size_t numInputs = config_.inputs_size(); + inputShape_.resize(numInputs); + filterShape_.resize(numInputs); + outputShape_.resize(numInputs); + multiplierShape_.resize(numInputs); + weightMultiplier_.resize(numInputs); + + for (int i = 0; i < config_.inputs_size(); i++) { + std::vector paddings = {(size_t)paddingY_[i], (size_t)padding_[i]}; + std::vector strides = {(size_t)strideY_[i], (size_t)stride_[i]}; + Matrix::resizeOrCreate(weightMultiplier_[i], + (size_t)outputH_[i] * (size_t)outputW_[i], + (size_t)1, + false, + useGpu_); + weightMultiplier_[i]->one(); + createFunction(forward_, + "DepthwiseConv", + FuncConfig() + .set("paddings", paddings) + .set("strides", strides) + .set("groups", (size_t)groups_[i])); + + createFunction(backward_, + "DepthwiseConvGradInput", + FuncConfig() + .set("paddings", paddings) + .set("strides", strides) + .set("groups", (size_t)groups_[i])); + + createFunction(backward_, + "DepthwiseConvGradFilter", + FuncConfig() + .set("paddings", paddings) + .set("strides", strides) + .set("groups", (size_t)groups_[i])); + } + return true; +} + +// i is the index of input layers +#define BACKWARD_INPUT(i, inputs, outputs) \ + backward_[2 * i]->calc(inputs, outputs) +#define BACKWARD_FILTER(i, inputs, outputs) \ + backward_[2 * i + 1]->calc(inputs, outputs) + +void DepthwiseConvLayer::forward(PassType passType) { + Layer::forward(passType); + + size_t batchSize = inputLayers_[0]->getOutputValue()->getHeight(); + resetOutput(batchSize, getOutputSize()); + + // Calculate the shape of the input, output, and filter. + for (size_t i = 0; i < inputLayers_.size(); ++i) { + inputShape_[i] = TensorShape({(size_t)batchSize, + (size_t)channels_[i], + (size_t)imgSizeH_[i], + (size_t)imgSizeW_[i]}); + multiplierShape_[i] = + TensorShape({(size_t)outputH_[i] * (size_t)outputW_[i], (size_t)1}); + filterShape_[i] = TensorShape({(size_t)groups_[i], + (size_t)numFilters_ / groups_[i], + (size_t)channels_[i] / groups_[i], + (size_t)filterSizeY_[i], + (size_t)filterSize_[i]}); + outputShape_[i] = TensorShape({(size_t)batchSize, + (size_t)numFilters_, + (size_t)outputH_[i], + (size_t)outputW_[i]}); + } + + // Calculate the output value. + for (size_t i = 0; i < inputLayers_.size(); ++i) { + BufferArgs inputs; + BufferArgs outputs; + inputs.addArg(*getInputValue(i), inputShape_[i]); + inputs.addArg(*weights_[i]->getW(), filterShape_[i]); + outputs.addArg( + *getOutputValue(), outputShape_[i], i == 0 ? ASSIGN_TO : ADD_TO); + + forward_[i]->calc(inputs, outputs); + } + + /* add the bias-vector */ + if (biases_.get()) { + if (sharedBiases_) { + addSharedBias(); + } else { + addUnsharedBias(); + } + } + + /* activation */ + forwardActivation(); +} + +void DepthwiseConvLayer::backward(const UpdateCallback &callback) { + backwardActivation(); + + MatrixPtr outGrad = getOutputGrad(); + if (biases_ && biases_->getWGrad()) { + bpropBiases(outGrad); + /* Increasing the number of gradient */ + biases_->getParameterPtr()->incUpdate(callback); + } + + // Calculate the input grad and filter grad. + for (size_t i = 0; i < inputLayers_.size(); ++i) { + if (getInputGrad(i)) { + BufferArgs inputs; + BufferArgs outputs; + inputs.addArg(*getOutputGrad(), outputShape_[i]); + inputs.addArg(*weights_[i]->getW(), filterShape_[i]); + outputs.addArg(*getInputGrad(i), inputShape_[i], ADD_TO); + BACKWARD_INPUT(i, inputs, outputs); + } + + if (weights_[i]->getWGrad()) { + BufferArgs inputs; + BufferArgs outputs; + inputs.addArg(*getOutputGrad(), outputShape_[i]); + inputs.addArg(*getInputValue(i), inputShape_[i]); + inputs.addArg(*weightMultiplier_[i], multiplierShape_[i]); + // weight_multiplier + outputs.addArg(*weights_[i]->getWGrad(), filterShape_[i], ADD_TO); + BACKWARD_FILTER(i, inputs, outputs); + + /* Increasing the number of gradient */ + weights_[i]->getParameterPtr()->incUpdate(callback); + } + } +} + +} // namespace paddle diff --git a/paddle/gserver/layers/DepthwiseConvLayer.h b/paddle/gserver/layers/DepthwiseConvLayer.h new file mode 100644 index 00000000000..61dd87c12a0 --- /dev/null +++ b/paddle/gserver/layers/DepthwiseConvLayer.h @@ -0,0 +1,52 @@ +/* Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserve. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. */ + +#pragma once + +#include +#include "ExpandConvBaseLayer.h" +#include "paddle/math/Matrix.h" + +namespace paddle { + +/** + * @brief A subclass of convolution layer. + * This layer expands input and use matrix multiplication to + * calculate convolution operation. + * + * The config file api is img_conv_layer. + */ + +class DepthwiseConvLayer : public ExpandConvBaseLayer { +public: + explicit DepthwiseConvLayer(const LayerConfig& config) + : ExpandConvBaseLayer(config) {} + + ~DepthwiseConvLayer() {} + + bool init(const LayerMap& layerMap, + const ParameterMap& parameterMap) override; + + void forward(PassType passType) override; + void backward(const UpdateCallback& callback) override; + +protected: + std::vector inputShape_; + std::vector filterShape_; + std::vector outputShape_; + std::vector multiplierShape_; + std::vector weightMultiplier_; +}; + +} // namespace paddle -- GitLab From 379434b243faeaf9fd4d38cf9f95dfe45cc563d5 Mon Sep 17 00:00:00 2001 From: liaogang Date: Tue, 4 Jul 2017 17:21:09 +0800 Subject: [PATCH 1027/3256] Delete cmake in dynload --- paddle/platform/dynload/CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) delete mode 100644 paddle/platform/dynload/CMakeLists.txt diff --git a/paddle/platform/dynload/CMakeLists.txt b/paddle/platform/dynload/CMakeLists.txt deleted file mode 100644 index 9f829b70128..00000000000 --- a/paddle/platform/dynload/CMakeLists.txt +++ /dev/null @@ -1 +0,0 @@ -cc_library(dynamic_loader SRCS dynamic_loader.cc DEPS glog gflags) -- GitLab From e6fcdd47e5c153497efb54ff5737f4fc6a13596d Mon Sep 17 00:00:00 2001 From: qijun Date: Tue, 4 Jul 2017 17:22:43 +0800 Subject: [PATCH 1028/3256] fix wrong including header-file in files in paddle/platform/dynload dir --- paddle/platform/dynload/cublas.h | 4 ++- paddle/platform/dynload/cudnn.h | 4 ++- paddle/platform/dynload/curand.h | 4 ++- paddle/platform/dynload/dynamic_loader.cc | 41 ++++++++++------------- 4 files changed, 26 insertions(+), 27 deletions(-) diff --git a/paddle/platform/dynload/cublas.h b/paddle/platform/dynload/cublas.h index c9150ac5738..258cc88031a 100644 --- a/paddle/platform/dynload/cublas.h +++ b/paddle/platform/dynload/cublas.h @@ -15,7 +15,9 @@ limitations under the License. */ #pragma once #include -#include "paddle/platform/dynamic_loader.h" +#include +#include +#include "paddle/platform/dynload/dynamic_loader.h" namespace paddle { namespace platform { diff --git a/paddle/platform/dynload/cudnn.h b/paddle/platform/dynload/cudnn.h index c03424b375e..0a9562c573c 100644 --- a/paddle/platform/dynload/cudnn.h +++ b/paddle/platform/dynload/cudnn.h @@ -15,7 +15,9 @@ limitations under the License. */ #pragma once #include -#include "paddle/platform/dynamic_loader.h" +#include +#include +#include "paddle/platform/dynload/dynamic_loader.h" namespace paddle { namespace platform { diff --git a/paddle/platform/dynload/curand.h b/paddle/platform/dynload/curand.h index 1ef7a8c833d..9dc0a25c0fb 100644 --- a/paddle/platform/dynload/curand.h +++ b/paddle/platform/dynload/curand.h @@ -15,7 +15,9 @@ limitations under the License. */ #pragma once #include -#include "paddle/platform/dynamic_loader.h" +#include +#include +#include "paddle/platform/dynload/dynamic_loader.h" namespace paddle { namespace platform { diff --git a/paddle/platform/dynload/dynamic_loader.cc b/paddle/platform/dynload/dynamic_loader.cc index 8ef67bad8c6..dd914e006d5 100644 --- a/paddle/platform/dynload/dynamic_loader.cc +++ b/paddle/platform/dynload/dynamic_loader.cc @@ -12,13 +12,14 @@ 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 "dynamic_loader.h" +#include "paddle/platform/dynload/dynamic_loader.h" #include #include #include #include #include "gflags/gflags.h" #include "glog/logging.h" +#include "paddle/framework/enforce.h" DEFINE_string(cudnn_dir, "", "Specify path for loading libcudnn.so. For instance, " @@ -72,13 +73,12 @@ static inline void GetDsoHandleFromDefaultPath(std::string& dso_path, *dso_handle = dlopen(dso_path.c_str(), dynload_flags); if (nullptr == *dso_handle) { if (dso_path == "libcudnn.dylib") { - LOG(FATAL) - << "Note: [Recommend] copy cudnn into /usr/local/cuda/ \n" // NOLINT - << "For instance, sudo tar -xzf " - "cudnn-7.5-osx-x64-v5.0-ga.tgz -C " // NOLINT - << "/usr/local \n sudo chmod a+r " - "/usr/local/cuda/include/cudnn.h " // NOLINT - << "/usr/local/cuda/lib/libcudnn*"; + PADDLE_ENFORCE(true, + "Note: [Recommend] copy cudnn into /usr/local/cuda/ \n " + "For instance, sudo tar -xzf " + "cudnn-7.5-osx-x64-v5.0-ga.tgz -C /usr/local \n sudo " + "chmod a+r /usr/local/cuda/include/cudnn.h " + "/usr/local/cuda/lib/libcudnn*"); } } } @@ -106,22 +106,15 @@ static inline void GetDsoHandleFromSearchPath(const std::string& search_root, GetDsoHandleFromDefaultPath(dlPath, dso_handle, dynload_flags); } } - - CHECK(nullptr != *dso_handle) << "Failed to find dynamic library: " << dlPath - << " (" << dlerror() << ") \n" - << "Please specify its path correctly using " - "following ways: \n" - - << "Method. set environment variable " - "LD_LIBRARY_PATH on Linux or " - << "DYLD_LIBRARY_PATH on Mac OS. \n" - << "For instance, issue command: export " - "LD_LIBRARY_PATH=... \n" - - << "Note: After Mac OS 10.11, using the " - "DYLD_LIBRARY_PATH is impossible " - << "unless System Integrity Protection (SIP) " - "is disabled."; + PADDLE_ENFORCE(nullptr != *dso_handle, + "Failed to find dynamic library: %s ( %s ) \n Please specify " + "its path correctly using following ways: \n Method. set " + "environment variable LD_LIBRARY_PATH on Linux or " + "DYLD_LIBRARY_PATH on Mac OS. \n For instance, issue command: " + "export LD_LIBRARY_PATH=... \n Note: After Mac OS 10.11, " + "using the DYLD_LIBRARY_PATH is impossible unless System " + "Integrity Protection (SIP) is disabled.", + dlPath, dlerror()); } void GetCublasDsoHandle(void** dso_handle) { -- GitLab From 571714159aeb42903fca14d614dcb1e6942b5cc4 Mon Sep 17 00:00:00 2001 From: Qiao Longfei Date: Tue, 4 Jul 2017 20:04:32 +0800 Subject: [PATCH 1029/3256] add op_desc.proto (#2736) * add op_desc.proto In Operator design, we need a proto message to describe an Operator. Third-party language such as python can build this proto message and use AddOp(const OpDesc& op_desc) of Paddle core to construct an Op in the Network. --- paddle/framework/CMakeLists.txt | 3 ++ paddle/framework/op_desc.proto | 56 ++++++++++++++++++++++++++++++++ paddle/framework/op_desc_test.cc | 35 ++++++++++++++++++++ 3 files changed, 94 insertions(+) create mode 100644 paddle/framework/op_desc.proto create mode 100644 paddle/framework/op_desc_test.cc diff --git a/paddle/framework/CMakeLists.txt b/paddle/framework/CMakeLists.txt index baad38e3c1e..a016f57b3e9 100644 --- a/paddle/framework/CMakeLists.txt +++ b/paddle/framework/CMakeLists.txt @@ -8,3 +8,6 @@ cc_test(enforce_test SRCS enforce_test.cc) proto_library(attr_type SRCS attr_type.proto) proto_library(op_proto SRCS op_proto.proto DEPS attr_type) cc_test(op_proto_test SRCS op_proto_test.cc DEPS op_proto protobuf) + +proto_library(op_desc SRCS op_desc.proto DEPS attr_type) +cc_test(op_desc_test SRCS op_desc_test.cc DEPS op_desc protobuf) diff --git a/paddle/framework/op_desc.proto b/paddle/framework/op_desc.proto new file mode 100644 index 00000000000..89497f3c16b --- /dev/null +++ b/paddle/framework/op_desc.proto @@ -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. */ + +syntax="proto2"; +package paddle.framework; + +import "attr_type.proto"; + +// AttrDesc is used to describe Attributes of an Operator. It contain's +// name, type, and value of Attribute. +// +// e.g, for scale=3.0: name=scala, type=AttrType.FLOAT, value=3.0 +message AttrDesc { + required string name = 1; + required AttrType type = 2; + optional int32 i = 3; + optional float f = 4; + optional string s = 5; + repeated int32 ints = 6; + repeated float floats = 7; + repeated string strings = 8; +}; + +// Protocol Message to describe an Operator. +// +// In PaddlePaddle, Operator is used to do a certain computation such +// as "add", "sub", "cosine", etc. +// (1) Operator needs to know the input and output variable names. +// (2) Some ops may have special attributes such as "scale" in "CosineOp". +// +// 3rd-party language can build this proto message and call +// AddOp(const OpDesc& op_desc) of Paddle core to create an Operator. +message OpDesc { + // input names of this Operator. + repeated string inputs = 1; + + // output names of this Operator. + repeated string outputs = 2; + + // type of this Operator, such as "add", "sub", "fc". + required string type = 3; + + // Attributes of this Operator. e.g., scale=3.0 in cosine op. + repeated AttrDesc attrs = 4; +}; \ No newline at end of file diff --git a/paddle/framework/op_desc_test.cc b/paddle/framework/op_desc_test.cc new file mode 100644 index 00000000000..d0c52523b64 --- /dev/null +++ b/paddle/framework/op_desc_test.cc @@ -0,0 +1,35 @@ +/* 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 + +TEST(OpDesc, Create) { + paddle::framework::OpDesc op_desc; + op_desc.set_type("add"); + op_desc.add_inputs("X"); + op_desc.add_inputs("Y"); + op_desc.add_outputs("Z"); + + auto attr = op_desc.mutable_attrs()->Add(); + attr->set_type(paddle::framework::AttrType::FLOAT); + attr->set_f(3.14); + + // required field name is not set, so IsInitialized should be false. + ASSERT_FALSE(op_desc.IsInitialized()); + + attr->set_name("add"); + // after all required fields are set, IsInitialized should be true now. + ASSERT_TRUE(op_desc.IsInitialized()); +} \ No newline at end of file -- GitLab From 1ecddd8174fea793e70071163b7e47a750064499 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Tue, 4 Jul 2017 21:21:02 +0800 Subject: [PATCH 1030/3256] Remove buggy BarrierStat The implementation of BarrierStat is buggy, and it is not necessary for Paddle to diagnose which node in cluster is slow. --- paddle/parameter/tests/test_common.cpp | 50 --- paddle/pserver/ParameterServer2.cpp | 215 ------------- paddle/pserver/ParameterServer2.h | 49 --- paddle/utils/BarrierStat.cpp | 340 -------------------- paddle/utils/BarrierStat.h | 425 ------------------------- paddle/utils/Stat.cpp | 61 ---- paddle/utils/Stat.h | 17 - 7 files changed, 1157 deletions(-) delete mode 100644 paddle/utils/BarrierStat.cpp delete mode 100644 paddle/utils/BarrierStat.h diff --git a/paddle/parameter/tests/test_common.cpp b/paddle/parameter/tests/test_common.cpp index 8bab5a6289e..64d204aea10 100644 --- a/paddle/parameter/tests/test_common.cpp +++ b/paddle/parameter/tests/test_common.cpp @@ -172,53 +172,3 @@ TEST_F(CommonTest, syncThreadPool) { EXPECT_EQ((int)0, nums[i]); } } - -TEST_F(CommonTest, barrierStat) { - const int threadNum = 10; - - SyncThreadPool pool(threadNum); - -#define TEST_BARRIER_RANDOM(statName, numConnThreads, ...) \ - pool.exec([&](int tid, size_t numThreads) { \ - struct timeval time; \ - gettimeofday(&time, nullptr); \ - uint64_t usec = timeToMicroSecond(time); \ - std::srand(usec); \ - auto value = std::rand() % 100000; \ - usleep(value); \ - REGISTER_SLOW_NODES_PROBE( \ - globalStat, statName, numConnThreads, tid, __VA_ARGS__); \ - }); - - for (auto i = 0; i < 10; i++) { - TEST_BARRIER_RANDOM("synThreadBarrier1", threadNum); - TEST_BARRIER_RANDOM("synThreadBarrier2", threadNum); - } - - globalStat.printAllStatus(); - globalStat.reset(); - - for (auto i = 0; i < 10; i++) { - TEST_BARRIER_RANDOM("synThreadBarrier3", threadNum, "tag0"); - TEST_BARRIER_RANDOM("synThreadBarrier4", threadNum, "tag1"); - } - - globalStat.printAllStatus(); - globalStat.reset(); - -// use it to test accurate barrier gap -#define TEST_BARRIER(statName, numConnThreads, ...) \ - pool.exec([&](int tid, size_t numThreads) { \ - usleep(tid * 10000); \ - REGISTER_SLOW_NODES_PROBE( \ - globalStat, statName, numConnThreads, tid, __VA_ARGS__); \ - }); - - for (auto i = 0; i < 10; i++) { - TEST_BARRIER("synThreadBarrier3", threadNum, "tag0"); - TEST_BARRIER("synThreadBarrier4", threadNum, "tag1"); - } - - globalStat.printAllStatus(); - globalStat.reset(); -} diff --git a/paddle/pserver/ParameterServer2.cpp b/paddle/pserver/ParameterServer2.cpp index 41ac15336d3..d7c1d4f788f 100644 --- a/paddle/pserver/ParameterServer2.cpp +++ b/paddle/pserver/ParameterServer2.cpp @@ -217,10 +217,6 @@ void ParameterServer2::setConfig(const SetConfigRequest& request, SetConfigResponse response; callback(response); - - /// always defined, barrier slowest node function need it. - statSet_.reset(new StatSet("ParameterServer" + - str::to_string(static_cast(serverId_)))); } real bufferSum(const std::vector& buffers) { @@ -369,50 +365,7 @@ void ParameterServer2::addGradient(const SendParameterRequest& request, std::vector* outputBuffers) { VLOG(1) << "pserver: addGradient"; - // forwardbackward delta from all trainers - // indicate the fluctuation caused by forwardbackward. - if (!numPassFinishClients_) { - REGISTER_BARRIER_DELTA_SERVER_SET( - *statSet_, - "forwardbackwardDelta", - FLAGS_num_gradient_servers, - request.trainer_id(), - request.forwardbackward_time(), - isSparseServer_ ? "_sparseUpdater" : "_denseUpdater"); - } - { - /// approximately pure network overhead - REGISTER_TIMER_DYNAMIC_SET( - "pushRecv", timeToMicroSecond(*handleRequestBegin_), -1, *statSet_); - } - -#ifndef PADDLE_DISABLE_TIMER - gettimeofday(&(*addGradBegin_), nullptr); -#endif - - /// barrier fluctuation caused by network and previous forwardbackward - if (!numPassFinishClients_) { - REGISTER_BARRIER_TIMER_SERVER_SET( - *statSet_, - "handleReqBegin", - FLAGS_num_gradient_servers, - request.trainer_id(), - (*handleRequestBegin_), - isSparseServer_ ? "_sparseUpdater" : "_denseUpdater"); - } - - if (!numPassFinishClients_) { - REGISTER_BARRIER_TIMER_SERVER( - *statSet_, - "addGradBegin", - FLAGS_num_gradient_servers, - request.trainer_id(), - isSparseServer_ ? "_sparseUpdater" : "_denseUpdater"); - } - - { - REGISTER_TIMER_DYNAMIC("addGradCore", -1, *statSet_); ReadLockGuard guard(parameterMutex_); int bufferIndex = 0; for (const auto& block : request.blocks()) { @@ -444,15 +397,6 @@ void ParameterServer2::addGradient(const SendParameterRequest& request, std::lock_guard guard(*info.lock); simd::addTo(gradientSumBuffer, gradientBuffer, size); } - - if (!numPassFinishClients_) { - REGISTER_BARRIER_TIMER_SERVER( - *statSet_, - "addGradCoreFinish", - FLAGS_num_gradient_servers, - request.trainer_id(), - isSparseServer_ ? "_sparseUpdater" : "_denseUpdater"); - } } if (request.batch_status() == BATCH_FINISH || request.batch_status() == BATCH_START_AND_FINISH) { @@ -461,47 +405,12 @@ void ParameterServer2::addGradient(const SendParameterRequest& request, VLOG(1) << "num samples: " << numSamplesProcessed_ << ", new cost:" << cost_; - /// numPassFinishClients_ means some trainer has entered finishPass - if (!numPassFinishClients_) { - REGISTER_SLOW_NODES_PROBE( - *statSet_, - "SLOW_NODES", - FLAGS_num_gradient_servers, - request.trainer_id(), - isSparseServer_ ? "_sparseUpdater" : "_denseUpdater"); - } - /// notify doOperation gradient ready gradientReadyBarrier_.wait(); - /// if wait pass finish does not start, do check - if (!numPassFinishClients_) { - CHECK_BARRIER_TIMER(*statSet_, - "SLOW_NODES", - FLAGS_num_gradient_servers, - isSparseServer_ ? "_sparseUpdater" : "_denseUpdater"); - } - - /// barrier performance while all parameter add is finished - /// can indicate the fluctation caused by computation at pserver. - if (!numPassFinishClients_) { - REGISTER_BARRIER_TIMER_SERVER( - *statSet_, - "paraReady", - FLAGS_num_gradient_servers, - request.trainer_id(), - isSparseServer_ ? "_sparseUpdater" : "_denseUpdater"); - } /// wait doOperation finish parameterReadyBarrier_.wait(); VLOG(1) << "start send back"; - { - /// total time except overhead of network. - REGISTER_TIMER_DYNAMIC_SET("sendParaNoRecvNoSend", - timeToMicroSecond(*addGradBegin_), - -1, - *statSet_); - } } } @@ -543,57 +452,6 @@ bool ParameterServer2::asyncGrdientCommitCheckAndStat( return commitGradient; } -void ParameterServer2::printAsyncGradientCommitStatAndReset() { - std::stringstream statFormat; - if (asyncUpdateSteps_) { - statFormat << "async discard gradients stat: " << std::endl; - statFormat << "serverId: " << serverId_ - << " serverType: " << isSparseServer_ - << " total updates: " << asyncUpdateSteps_ - << " discard updates: " << asyncLaggedGradientsNum_ - << " discard ratio: " - << (real)asyncLaggedGradientsNum_ / (real)asyncUpdateSteps_; - statFormat << std::endl; - statFormat << std::endl; - - statFormat << "Async Gradient Update Steps distribution: " << std::endl - << "Sample: 1:1912(0.00284449) means " - << "the updates step=1 count 1912 times " - << "and account for 0.284449% of total updates" << std::endl; - size_t index = 0; - for (const auto& stat : asyncUpdateStat_) { - statFormat << index << ":" << stat << "(" - << (real)stat / (real)asyncUpdateSteps_ << ") "; - index++; - } - statFormat << std::endl; - statFormat << std::endl; - - statFormat << "Async Gradient Discard based on trainer_id: " << std::endl - << "Sample: 2:22(0.0016363) means " - << "total discarded updates from trainer_id=2 count 22 " - << "and account for 0.16363% of all updates from trainer_id=2" - << std::endl; - for (auto i = 0; i < FLAGS_num_gradient_servers; i++) { - real ratio = - (real)asyncTrainerDiscardStat_[i] / - (real)(asyncTrainerCommitStat_[i] + asyncTrainerDiscardStat_[i]); - statFormat << i << ":" << asyncTrainerDiscardStat_[i] << "(" << ratio - << ")" - << " "; - } - LOG(INFO) << statFormat.str(); - - /// reset stat - asyncUpdateSteps_ = 0; - asyncTrainerSteps_.assign(asyncTrainerSteps_.size(), 0); - asyncLaggedGradientsNum_ = 0; - asyncUpdateStat_.assign(asyncUpdateStat_.size(), 0); - asyncTrainerDiscardStat_.assign(asyncTrainerDiscardStat_.size(), 0); - asyncTrainerCommitStat_.assign(asyncTrainerCommitStat_.size(), 0); - } -} - static ThreadLocal> localBlockBitset_; void ParameterServer2::asyncSGD(const SendParameterRequest& request, @@ -695,7 +553,6 @@ void ParameterServer2::asyncSGD(const SendParameterRequest& request, if (request.trainer_id() == 0) { /// batchId_ is approximately equal to "real batchId_" batchId_++; - tuningAsyncsgdMidOutput(); } } @@ -881,34 +738,6 @@ void ParameterServer2::sendParameter(const SendParameterRequest& request, } (*requestVec_).clear(); (*callbackVec_).clear(); - - /// barrier perfromance while all data are send finished. - /// indicates network flucatuation for big message. - if (!numPassFinishClients_) { - REGISTER_BARRIER_TIMER_SERVER( - *statSet_, - "sendParamFinish", - FLAGS_num_gradient_servers, - request.trainer_id(), - isSparseServer_ ? "_sparseUpdater" : "_denseUpdater"); - } - /// all time exhausted in parameterServer for big message. - /// it contains network and computation at pserver. - { - /// total time including overhead of network. - REGISTER_TIMER_DYNAMIC_SET("sendParaTotal", - timeToMicroSecond(*handleRequestBegin_), - -1, - *statSet_); - } - /// all time exhausted in pserverServer except recieve network. - { - /// total time except overhead of network receive - REGISTER_TIMER_DYNAMIC_SET("sendParaNoRecv", - timeToMicroSecond(*addGradBegin_), - -1, - *statSet_); - } } break; case PSERVER_UPDATE_MODE_SET_PARAM: @@ -1088,8 +917,6 @@ void ParameterServer2::op_SGD(const Operation& operation, } { - REGISTER_TIMER_DYNAMIC("op_SGD", -1, *statSet_); - parallelExecForEachBlock([&](int64_t blockId, const VectorPtr vecs[]) { BlockInfo& info = blockInfos_[blockId]; const ParameterConfig& config = getParameterConfig(blockId); @@ -1113,7 +940,6 @@ void ParameterServer2::op_SGD(const Operation& operation, } batchId_++; - tuningSgdMidOutput(); } void ParameterServer2::op_start_pass(const Operation& operation, @@ -1146,8 +972,6 @@ void ParameterServer2::op_finish_pass(const Operation& operation, /// finish pass info.optimizer->finishPass(); }); - - tuningSgdFinished(); batchId_ = 0; } @@ -1515,7 +1339,6 @@ void ParameterServer2::asyncFinishPass(const SynchronizeRequest& request, callback(SynchronizeResponse()); if (request.trainer_id() == 0) { - tuningAsyncsgdFinished(); batchId_ = 0; } } @@ -1574,42 +1397,4 @@ void ParameterServer2::releaseMatrix(const ReleaseMatrixRequest& request, callback(response); } -void ParameterServer2::tuningSgdMidOutput() { - if (batchId_ && batchId_ % FLAGS_log_period_server == 0) { - LOG(INFO) << "======== Batch=" << batchId_ << "======="; - statSet_->setThreadInfo(true); - statSet_->printAllStatus(); - /// not reset raw data for reducing the overhead of performance tuning - statSet_->reset(false); - } -} - -void ParameterServer2::tuningSgdFinished() { - LOG(INFO) << "======== Batch=" << batchId_ << " pass END" - << "======="; - statSet_->setThreadInfo(true); - statSet_->printAllStatus(); - /** - * reset raw data at end of pass since some raw data could be not - * complete. Otherwise the raw data will pollute next pass performance - * tuning - */ - statSet_->reset(); -} - -void ParameterServer2::tuningAsyncsgdMidOutput() { -#ifndef PADDLE_DISABLE_TIMER - if (batchId_ && batchId_ % FLAGS_log_period_server == 0) { - LOG(INFO) << "======== [not accurate] Batch=" << batchId_ << "======="; - printAsyncGradientCommitStatAndReset(); - } -#endif -} - -void ParameterServer2::tuningAsyncsgdFinished() { - LOG(INFO) << "======== [not accurate] Batch=" << batchId_ << " pass END" - << "======="; - printAsyncGradientCommitStatAndReset(); -} - } // namespace paddle diff --git a/paddle/pserver/ParameterServer2.h b/paddle/pserver/ParameterServer2.h index 0f5a5895907..f7d3587b88c 100644 --- a/paddle/pserver/ParameterServer2.h +++ b/paddle/pserver/ParameterServer2.h @@ -298,24 +298,6 @@ protected: /// barrier performance tuning sync-sgd required std::atomic batchId_; - /// the beginning of addGradient without network overhead - ThreadLocal addGradBegin_; - - /** - * tuning barrier performance - * to better control log for sparse and dense parameter, - * we use different log entities for different parameterServer - * objects. - * it will output lots of performance stats to perceive the - * overhead of network, fluctuation of computation from - * forwardbackward and network, computation from optimization - * at pserver end, barrier overhead, etc. to understand tuning - * data, focus on the synchronization between addGradient and - * doOperation which indirectly call op_SGD operation controlled - * by remote updater controller - */ - std::unique_ptr statSet_; - public: struct Buffer { real* base; @@ -325,7 +307,6 @@ public: protected: /// async gradient commit control bool asyncGrdientCommitCheckAndStat(const SendParameterRequest& request); - void printAsyncGradientCommitStatAndReset(); public: /// disable default parameter for overloading @@ -710,36 +691,6 @@ public: void op_load(const Operation& operation, OperationResult* result); void op_save(const Operation& operation, OperationResult* result); - - /** - * @brief output log in at the middle stage of training - * - * @note flush log histroy and state at the end for sgd - */ - void tuningSgdMidOutput(); - - /** - * @brief output log in at the end stage of training - * - * @note flush log histroy and state at the end for sgd. it will also - * flush some stateful stat for next pass. - */ - void tuningSgdFinished(); - - /** - * @brief output log in at the middle stage of training - * - * @note flush log histroy and state at the end for async-sgd. - * it will log some performance log if some lagged node are found - */ - void tuningAsyncsgdMidOutput(); - - /** - * @brief output log in at the end stage of training - * - * @note flush log histroy and state at the end for async-sgd. - */ - void tuningAsyncsgdFinished(); }; } // namespace paddle diff --git a/paddle/utils/BarrierStat.cpp b/paddle/utils/BarrierStat.cpp deleted file mode 100644 index a6dbdcae3f3..00000000000 --- a/paddle/utils/BarrierStat.cpp +++ /dev/null @@ -1,340 +0,0 @@ -/* 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/utils/BarrierStat.h" -#include -#include -#include -#include -#include "paddle/utils/Flags.h" -#include "paddle/utils/Stat.h" - -DEFINE_bool(log_barrier_abstract, - true, - "if true, show abstract of barrier performance"); -DEFINE_int32(log_barrier_lowest_nodes, - 5, - "how many lowest node will be logged"); -DEFINE_bool(log_barrier_show_log, - false, // for performance tuning insight - "if true, always show barrier abstract even with little gap"); - -namespace paddle { - -std::ostream &operator<<(std::ostream &output, const BarrierStatBase &stat) { - if (FLAGS_log_barrier_abstract) { - std::lock_guard guard(stat.lock_); - stat.showAbstract(output); - } - return output; -} - -BarrierStatBase::BarrierStatBase(uint16_t numConnThreads, - const std::string &name) - : totSamples_(0), numConnThreads_(numConnThreads), name_(name) { - abstract_.resize(numConnThreads_); - if (FLAGS_log_barrier_show_log) { - rateThreshold_ = 0.0; - } else { - /* probablity of abnormal node - * p = 1/n + (n/8)/(n+1), n = nodes, n > 1 - * if the freq of lowest trainerId larger than p, - * output FLAGS_log_barrier_lowest_nodes lastTrainerId. - * numConnThreads_ indicates nodes - */ - float n = (float)numConnThreads; - rateThreshold_ = 1.0 / n + (n / 8.0) / (n + 1.0); - } -} - -BarrierEndStat::BarrierEndStat(uint16_t numConnThreads, const std::string &name) - : BarrierStatBase(numConnThreads, name) { - timeVector_.reset(new TimeVectorEnd(numConnThreads_)); - reset(true); - LOG(INFO) << " create barrierEndStat: " << name - << " endBarrier warning rate: " << rateThreshold_; -} - -/* - * Note: - * the design different pserver entity owns different statSet to obey - * the background that different pserver runs separately. - */ -void BarrierEndStat::updateStat(struct timeval &cur, int32_t trainerId) { - CHECK_LT(trainerId, numConnThreads_) << "trainerId is invalid in barrier"; - - std::lock_guard guard(lock_); - timeVector_->addTimeval(cur, trainerId); - - if (timeVector_->full()) { - std::lock_guard abstractGuard(abstractLock_); - auto id = timeVector_->getLastTrainerId(); - auto delta = timeToMicroSecond(timeVector_->getDelta()); - auto secondDelta = timeToMicroSecond(timeVector_->get1NDelta()); - auto lastTwoDelta = timeToMicroSecond(timeVector_->getMinus1NDelta()); - auto midDelta = timeToMicroSecond(timeVector_->getMidNDelta()); - // discard first sample, since first sample probably is abnormal. - if (totSamples_) { - abstract_[id].freq++; - - if (delta < abstract_[id].minDelta) { - abstract_[id].minDelta = delta; - } - if (delta > abstract_[id].maxDelta) { - abstract_[id].maxDelta = delta; - } - abstract_[id].totDelta += delta; - abstract_[id].totSecondDelta += secondDelta; - abstract_[id].totLastTwoDelta += lastTwoDelta; - abstract_[id].totMidDelta += midDelta; - - // update totAbstract_ - totAbstract_.freq++; - if (delta < totAbstract_.minDelta) { - totAbstract_.minDelta = delta; - } - if (delta > totAbstract_.maxDelta) { - totAbstract_.maxDelta = delta; - } - totAbstract_.totDelta += delta; - totAbstract_.totSecondDelta += secondDelta; - totAbstract_.totLastTwoDelta += lastTwoDelta; - totAbstract_.totMidDelta += midDelta; - } - - totSamples_++; - timeVector_->reset(); - } -} - -void BarrierEndStat::reset(bool clearRawData) { - int32_t i = 0; - - totSamples_ = 0; - - std::lock_guard guard(abstractLock_); - - if (clearRawData) { - timeVector_->reset(); - } - - for (auto &abstract : abstract_) { - memset((void *)&abstract, 0, sizeof(abstract)); - abstract.minDelta = UINT64_MAX; - abstract.trainerId = i++; - } - memset((void *)&totAbstract_, 0, sizeof(Abstract)); - totAbstract_.minDelta = UINT64_MAX; -} - -void BarrierEndStat::showAbstract(std::ostream &output) const { - // do not support the case "<=2 pserver" - if (numConnThreads_ <= 2 || !totSamples_) { - return; - } - - // duplicate freq info - std::vector outputAbstract = abstract_; - std::sort(outputAbstract.begin(), - outputAbstract.end(), - [](const struct Abstract &a, const struct Abstract &b) { - return a.freq > b.freq; - }); - - auto rate = (float)outputAbstract[0].freq / (float)totSamples_; - if (rate < rateThreshold_) { - return; - } - - output << std::setw(20) << name_ << std::endl; - - /* - * Note: - * avgGap: the average delta between 1 -- n arriving trainers - * avgSecondGap: the average delta between 2 -- n arriving trainers - * avgLastTwoGap: the average delta between n-1 -- n arriving trainers - * avgMidGap: the average delta between n/2 -- n arriving trainers - * rato: samples / totSamples - * - * the stat is based on per trainer if trainer_id is set, totAbstract is - * stat based on all trainers scope. - */ - output << std::setw(42) << " " << std::setw(15) << "trainerId" - << std::setw(15) << "avgGap" << std::setw(15) << "avgSecondGap" - << std::setw(15) << "avgLastTwoGap" << std::setw(15) << "avgMidGap" - << std::setw(10) << "rate" << std::setw(10) << "samples" - << std::setw(10) << "totSamples" << std::endl; - // show totAbstract, it's valuable when lastTrainerId is even-distributed' - if (!totAbstract_.freq) return; - output << std::setw(42) << " " << std::setw(15) << "totAbstract" - << std::setw(15) << (totAbstract_.totDelta / totAbstract_.freq) * 0.001 - << std::setw(15) - << (totAbstract_.totSecondDelta / totAbstract_.freq) * 0.001 - << std::setw(15) - << (totAbstract_.totLastTwoDelta / totAbstract_.freq) * 0.001 - << std::setw(15) - << (totAbstract_.totMidDelta / totAbstract_.freq) * 0.001 - << std::setw(10) << (float)totAbstract_.freq / (float)totSamples_ - << std::setw(10) << (float)totAbstract_.freq << std::setw(10) - << (float)totSamples_ << std::endl; - - // show lastTrainerId abstract - int count = 0; - for (auto &abstract : outputAbstract) { - if (!abstract.freq || count++ >= FLAGS_log_barrier_lowest_nodes) { - break; - } - // output format control - output << std::setw(42) << " " << std::setw(15) << abstract.trainerId - << std::setw(15) << (abstract.totDelta / abstract.freq) * 0.001 - << std::setw(15) << (abstract.totSecondDelta / abstract.freq) * 0.001 - << std::setw(15) - << (abstract.totLastTwoDelta / abstract.freq) * 0.001 - << std::setw(15) << (abstract.totMidDelta / abstract.freq) * 0.001 - << std::setw(10) << (float)abstract.freq / (float)totSamples_ - << std::setw(10) << (float)abstract.freq << std::setw(10) - << (float)totSamples_ << std::endl; - } -} - -BarrierDeltaStat::BarrierDeltaStat(uint16_t numConnThreads, - const std::string &name) - : BarrierStatBase(numConnThreads, name) { - timeVector_.reset(new TimeVectorDelta(numConnThreads_)); - reset(true); - LOG(INFO) << " create barrierDeltaStat: " << name - << " barrierDelta warning rate: " << rateThreshold_; -} - -void BarrierDeltaStat::updateStat(uint64_t delta, int32_t trainerId) { - CHECK_LT(trainerId, numConnThreads_) << "trainerId is invalid in barrier"; - - std::lock_guard guard(lock_); - timeVector_->addTimeval(delta, trainerId); - - if (timeVector_->full()) { - std::lock_guard abstractGuard(abstractLock_); - auto id = timeVector_->getMaxTrainerId(); - auto delta = timeVector_->getDelta(); - // discard first sample, since first sample probably is abnormal. - if (totSamples_) { - abstract_[id].freq++; - - if (delta < abstract_[id].minDelta) { - abstract_[id].minDelta = delta; - } - if (delta > abstract_[id].maxDelta) { - abstract_[id].maxDelta = delta; - } - abstract_[id].totDelta += delta; - - // update totAbstract_ - totAbstract_.freq++; - if (delta < totAbstract_.minDelta) { - totAbstract_.minDelta = delta; - } - if (delta > totAbstract_.maxDelta) { - totAbstract_.maxDelta = delta; - } - totAbstract_.totDelta += delta; - } - - totSamples_++; - timeVector_->reset(); - } -} - -void BarrierDeltaStat::reset(bool clearRawData) { - int32_t i = 0; - - totSamples_ = 0; - - std::lock_guard guard(abstractLock_); - - if (clearRawData) { - timeVector_->reset(); - } - - for (auto &abstract : abstract_) { - memset((void *)&abstract, 0, sizeof(abstract)); - abstract.minDelta = UINT64_MAX; - abstract.trainerId = i++; - } - memset((void *)&totAbstract_, 0, sizeof(Abstract)); - totAbstract_.minDelta = UINT64_MAX; -} - -void BarrierDeltaStat::showAbstract(std::ostream &output) const { - // do not support the case "<=2 pserver" - if (numConnThreads_ <= 2 || !totSamples_) { - return; - } - - // duplicate freq info - std::vector outputAbstract = abstract_; - std::sort(outputAbstract.begin(), - outputAbstract.end(), - [](const struct Abstract &a, const struct Abstract &b) { - return a.freq > b.freq; - }); - - auto rate = (float)outputAbstract[0].freq / (float)totSamples_; - if (rate < rateThreshold_) { - return; - } - - output << std::setw(20) << name_ << std::endl; - - /* Note: - * Gap means the delta from all trainers' forwardbackward - * avgGap: average Gap in log_period batches - * minGap: min Gap in log_period batches - * maxGap: max Gap in log_period batches - * trainerId: the slowest trainer_id - * - * the stat is based on per trainer if trainer_id is set, totAbstract is - * stat based on all trainers scope. - */ - output << std::setw(42) << " " << std::setw(15) << "trainerId" - << std::setw(15) << "avgGap" << std::setw(10) << "minGap" - << std::setw(10) << "maxGap" << std::setw(10) << "rate" - << std::setw(10) << "samples" << std::setw(10) << "totSamples" - << std::endl; - // show totAbstract, it's valuable when lastTrainerId is even-distributed' - if (!totAbstract_.freq) return; - output << std::setw(42) << " " << std::setw(15) << "totAbstract" - << std::setw(15) << (totAbstract_.totDelta / totAbstract_.freq) * 0.001 - << std::setw(10) << totAbstract_.minDelta * 0.001 << std::setw(10) - << totAbstract_.maxDelta * 0.001 << std::setw(10) - << (float)totAbstract_.freq / (float)totSamples_ << std::setw(10) - << (float)totAbstract_.freq << std::setw(10) << (float)totSamples_ - << std::endl; - - // show lastTrainerId abstract - int count = 0; - for (auto &abstract : outputAbstract) { - if (!abstract.freq || count++ >= FLAGS_log_barrier_lowest_nodes) { - break; - } - // output format control - output << std::setw(42) << " " << std::setw(15) << abstract.trainerId - << std::setw(15) << (abstract.totDelta / abstract.freq) * 0.001 - << std::setw(10) << abstract.minDelta * 0.001 << std::setw(10) - << abstract.maxDelta * 0.001 << std::setw(10) - << (float)abstract.freq / (float)totSamples_ << std::setw(10) - << (float)abstract.freq << std::setw(10) << (float)totSamples_ - << std::endl; - } -} -} // namespace paddle diff --git a/paddle/utils/BarrierStat.h b/paddle/utils/BarrierStat.h deleted file mode 100644 index a9c925eff66..00000000000 --- a/paddle/utils/BarrierStat.h +++ /dev/null @@ -1,425 +0,0 @@ -/* Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserve. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "Locks.h" -#include "Logging.h" -#include "ThreadLocal.h" - -namespace paddle { - -inline uint64_t timeToMicroSecond(struct timeval time) { - return time.tv_sec * 1000000LU + time.tv_usec; -} - -class TimeVectorEnd { - /* - * help class for gathering all barrier performance data - * which shows time point property. - * freqently used in barrier performance tuning API, such - * as tuning which is slowest node in sync-sgd mode training. - */ -public: - explicit TimeVectorEnd(uint16_t size) : size_(size) { - index_ = 0; - timeArray_.resize(size); - trainerIds_.resize(size); - } - ~TimeVectorEnd() {} - - uint16_t size() { return size_; } - - bool full() { return index_ == size_; } - - bool empty() { return index_ == 0; } - - void reset() { index_ = 0; } - - void addTimeval(struct timeval time, int32_t trainerId) { - timeArray_[index_] = time; - trainerIds_[index_] = trainerId; - index_++; - } - - struct timeval getDelta() const { - struct timeval delta; - CHECK_GT(size_, 1) << "not support with 1 pserver"; - timersub(&timeArray_[size_ - 1], &timeArray_[0], &delta); - return delta; - } - - /* 2, n delta */ - struct timeval get1NDelta() const { - CHECK_GT(size_, 2) << "not support with less than 2 pservers"; - struct timeval delta; - timersub(&timeArray_[size_ - 1], &timeArray_[1], &delta); - return delta; - } - - /* n-1, n delta */ - struct timeval getMinus1NDelta() const { - CHECK_GT(size_, 2) << "not support with less than 2 pservers"; - struct timeval delta; - timersub(&timeArray_[size_ - 1], &timeArray_[size_ - 2], &delta); - return delta; - } - - /* n/2, n delta */ - struct timeval getMidNDelta() const { - CHECK_GT(size_, 2) << "not support with less than 2 pservers"; - struct timeval delta; - timersub(&timeArray_[size_ - 1], &timeArray_[size_ / 2], &delta); - return delta; - } - - int32_t getLastTrainerId() const { return trainerIds_[index_ - 1]; } - -private: - uint16_t size_; - uint16_t index_; - std::vector timeArray_; - std::vector trainerIds_; -}; - -class TimeVectorDelta { - /* - * help class for gathering performance data which shows time - * delta property, such as tuning the time distribution of - * forwardBackward time from all cluster nodes. - */ -public: - explicit TimeVectorDelta(uint16_t size) - : size_(size), min_(UINT64_MAX), max_(0) { - index_ = 0; - timeArray_.resize(size); - } - ~TimeVectorDelta() {} - - uint16_t size() { return size_; } - - bool full() { return index_ == size_; } - - bool empty() { return index_ == 0; } - - void reset() { - index_ = 0; - min_ = UINT64_MAX; - max_ = 0; - } - - void addTimeval(uint64_t delta, int32_t trainerId) { - timeArray_[index_] = delta; - index_++; - if (delta < min_) { - min_ = delta; - } - if (delta > max_) { - max_ = delta; - maxTrainerId_ = trainerId; - } - } - - uint64_t getDelta() const { - CHECK_GT(size_, 1) << "not support with 1 pserver"; - return max_ - min_; - } - - /* 2, n delta */ - uint64_t get1NDelta() const { - CHECK_GT(size_, 2) << "not support with less than 2 pservers"; - LOG(FATAL) << "Not implemented"; - } - - /* n-1, n delta */ - uint64_t getMinus1NDelta() const { - CHECK_GT(size_, 2) << "not support with less than 2 pservers"; - LOG(FATAL) << "Not implemented"; - } - - /* n/2, n delta */ - uint64_t getMidNDelta() const { - CHECK_GT(size_, 2) << "not support with less than 2 pservers"; - LOG(FATAL) << "Not implemented"; - } - - int32_t getMaxTrainerId() const { return maxTrainerId_; } - -private: - uint16_t size_; - uint16_t index_; - std::vector timeArray_; - -private: - uint64_t min_; - uint64_t max_; - int32_t maxTrainerId_; -}; - -// total samples stats, us -struct Abstract { - // last trainerId for barrier end, maxDelta trainerId for barrier delta - int32_t trainerId; - uint64_t minDelta; - uint64_t maxDelta; - uint64_t totDelta; - // first one is probably itself, so discard it. - uint64_t totSecondDelta; - // to confirm if last node destroy barrier performance. - uint64_t totLastTwoDelta; - // n/2-n delta - uint64_t totMidDelta; - uint64_t freq; -}; - -// barrier performance tunning stats -class BarrierStatBase { -public: - BarrierStatBase(uint16_t numConnThreads, const std::string &name); - - virtual ~BarrierStatBase() {} - - // if called at pserver end, then trainId means trainer's id. - // by default trainer does not use trainerId, so set it to -1 - virtual void updateStat(struct timeval &cur, int32_t trainerId = -1) = 0; - virtual void updateStat(uint64_t delta, int32_t trainerId = -1) = 0; - - const std::string &getName() { return name_; } - - virtual void reset(bool clearRawData = true) {} - // since the timeVector_ is not stateful, so it's not clear whether the - // the barrier delta is correct. if one timestamp was lost, the all data - // from barrier stat becomes rubbish. -_- - virtual bool checkPassBarrier() { - LOG(INFO) << "bug implementation found"; - return false; - } - -protected: - virtual void showAbstract(std::ostream &output) const {} - friend std::ostream &operator<<(std::ostream &output, - const BarrierStatBase &stat); - -protected: - mutable std::mutex lock_; - std::mutex abstractLock_; // see note on updaterStat - // each freqency for each barrier trainer - std::vector abstract_; - // it is valuable when do perf-tuining, if lastTrainerId acts uniform - // distribution - struct Abstract totAbstract_; - uint64_t totSamples_; - -protected: - uint16_t numConnThreads_; // total updates needed - float rateThreshold_; - std::string name_; -}; - -// the end-time of arriving real/forged barrier position -class BarrierEndStat : public BarrierStatBase { -public: - BarrierEndStat(uint16_t numConnThreads, const std::string &name); - ~BarrierEndStat() {} - - virtual void updateStat(struct timeval &cur, int32_t trainerId = -1); - virtual void updateStat(uint64_t delta, int32_t trainerId = -1) { - LOG(INFO) << "have no delta updateStat in BarrierEndStat"; - } - virtual void reset(bool clearRawData = true); - virtual bool checkPassBarrier() { return timeVector_->empty(); } - -protected: - /* - * LOG: - * readAllBlocks_denseUpdater - * trainerId avgGap avgSecondGap avgLastTwoGap avgMidGap rate - * 44 86.702 81.022 9.984 50.472 0.144737 - * 46 87.723 82.939 8.737 50.019 0.118421 - * 35 100.923 96.752 14.305 61.979 - * 0.0657895 - * log_barrier_abstract, log_barrier_lowest_nodes, log_barrier_threshold - * control details. - */ - virtual void showAbstract(std::ostream &output) const; - -private: - std::unique_ptr timeVector_; -}; - -// the delta-time from different trainers, -// eg, find the degree of imbalance of BP time at pserver end -// the entry value in timerVector_ is BP delta, do evaluation to BP delta. -class BarrierDeltaStat : public BarrierStatBase { -public: - BarrierDeltaStat(uint16_t numConnThreads, const std::string &name); - ~BarrierDeltaStat() {} - - virtual void updateStat(uint64_t delta, int32_t trainerId = -1); - virtual void updateStat(struct timeval &cur, int32_t trainerId = -1) { - LOG(INFO) << "have no timeval updateStat in BarrierDeltaStat"; - } - - virtual void reset(bool clearRawData = true); - - virtual bool checkPassBarrier() { return timeVector_->empty(); } - -protected: - virtual void showAbstract(std::ostream &outPut) const; - -private: - // store delta time in uint64_t, eg BP time of all trainers - std::unique_ptr timeVector_; -}; - -// to distinguish different contexts for same parallel threads, and different -// threads with same code-sgement, just use tagName to tag the run-time -// position. -// in Sparse, sendParallel threads can not only run in the stage of push&pull -// with same thread group, but also run in the stage of pull&push with different -// thread group, tag will be used to distinguish different run-time barrier -// position. -// trainerId in REGISTER_BARRIER_TIMER_SERVER is used to retreive lowest trainer -// nodes. - -// end barrier -#define __REGISTER_BARRIER_TIMER_SERVER( \ - set, statName, numConnThreads, trainerId, ...) \ - do { \ - if (numConnThreads > 2) { \ - std::string internalName = \ - std::string(statName) + std::string(__VA_ARGS__); \ - BarrierStatPtr __stat = \ - (set).getStat(numConnThreads, internalName, BARRIER_END); \ - struct timeval cur; \ - gettimeofday(&cur, nullptr); \ - __stat->updateStat(cur, trainerId); \ - } \ - } while (0); - -// end barrier with user-defined timer -#define __REGISTER_BARRIER_TIMER_SERVER_SET( \ - set, statName, numConnThreads, trainerId, cur, ...) \ - do { \ - if (numConnThreads > 2) { \ - std::string internalName = \ - std::string(statName) + std::string(__VA_ARGS__); \ - BarrierStatPtr __stat = \ - (set).getStat(numConnThreads, internalName, BARRIER_END); \ - __stat->updateStat(cur, trainerId); \ - } \ - } while (0); - -// delta barrier -#define __REGISTER_BARRIER_DELTA_SERVER_SET( \ - set, statName, numConnThreads, trainerId, delta, ...) \ - do { \ - if (numConnThreads > 2) { \ - std::string internalName = \ - std::string(statName) + std::string(__VA_ARGS__); \ - BarrierStatPtr __stat = \ - (set).getStat(numConnThreads, internalName, BARRIER_DELTA); \ - __stat->updateStat(delta, trainerId); \ - } \ - } while (0); - -// check end barrier -#define __CHECK_BARRIER_TIMER(set, statName, numConnThreads, ...) \ - do { \ - std::string internalName = \ - std::string(statName) + std::string(__VA_ARGS__); \ - BarrierStatPtr __stat = \ - (set).getStat(numConnThreads, internalName, BARRIER_END); \ - PCHECK(__stat->checkPassBarrier()) << internalName \ - << ": invalid barrier data"; \ - } while (0); - -/* - * Note: - * with sync-sgd algriothm in cluster mode, lots of synchronize action exsit at - * pserve end. these synchronizaton actions have impact on the efficiency of - * parameter exchange. the synchronizaton(barrier) GAP is composed of lots of - * factors, such as the forwardBackward variance, network fluncation. we try - * to have a quantitative analysis on these factor, so we design lots of barrier - * time to capture these performance. these barrier also can be placed at - * implict barrier position. - * - * example: - * in sync-sgd algorithm, each parameter server waits for all gradients from - * all trainers, thus, an explict barrier point exsit before doing optimization. - * the barrier timer located before the point can sense the barrier condition. - * - */ - -// try to capture which trainer is slowest node in sync-sgd at pserver. -#define REGISTER_SLOW_NODES_PROBE( \ - set, statName, numConnThreads, trainerId, ...) \ - __REGISTER_BARRIER_TIMER_SERVER( \ - (set), statName, numConnThreads, trainerId, __VA_ARGS__) -// try to check if all threads or trainers have passed barriers for data -// accuracy. -#define CHECK_BARRIER_TIMER(set, statName, numConnThreads, ...) \ - __CHECK_BARRIER_TIMER((set), statName, numConnThreads, __VA_ARGS__) - -#ifdef PADDLE_DISABLE_TIMER - -#define REGISTER_BARRIER_TIMER_SERVER( \ - set, statName, numConnThreads, trainerId, ...) -#define REGISTER_BARRIER_TIMER_SERVER_SET( \ - set, statName, numConnThreads, trainerId, cur, ...) -#define REGISTER_BARRIER_DELTA_SERVER_SET( \ - set, statName, numConnThreads, trainerId, cur, ...) - -#else - -/* - * sensing barrier time distribution for all parallelization threads. - * it provides low API for slow node check(REGISTER_SLOW_NODES_PROBE) - */ -#define REGISTER_BARRIER_TIMER_SERVER( \ - set, statName, numConnThreads, trainerId, ...) \ - __REGISTER_BARRIER_TIMER_SERVER( \ - (set), statName, numConnThreads, trainerId, __VA_ARGS__) - -/* - * sensing barrier time distribution for all parallelization threads. - * but time point for barrier performance is set by user. - * eg, with this api, you can get implict barrier point such as the beginning - * time distribution - * for receiving data. - */ -#define REGISTER_BARRIER_TIMER_SERVER_SET( \ - set, statName, numConnThreads, trainerId, cur, ...) \ - __REGISTER_BARRIER_TIMER_SERVER_SET( \ - (set), statName, numConnThreads, trainerId, cur, __VA_ARGS__) - -// try to capture time delta from all trainers, such as forwardBackward time -// which implies -// computation fluctuation -#define REGISTER_BARRIER_DELTA_SERVER_SET( \ - set, statName, numConnThreads, trainerId, delta, ...) \ - __REGISTER_BARRIER_DELTA_SERVER_SET( \ - (set), statName, numConnThreads, trainerId, delta, __VA_ARGS__) - -#endif // DISABLE_TIMER -} // namespace paddle diff --git a/paddle/utils/Stat.cpp b/paddle/utils/Stat.cpp index c7194d3bf12..ff1b1bf888f 100644 --- a/paddle/utils/Stat.cpp +++ b/paddle/utils/Stat.cpp @@ -97,34 +97,6 @@ std::ostream& operator<<(std::ostream& outPut, const Stat& stat) { return outPut; } -BarrierStatPtr StatSet::getStat(uint16_t numConnThreads, - const std::string& name, - BarrierStatType bType) { - { - ReadLockGuard guard(lock_); - auto it = barrierStatSet_.find(name); - if (it != barrierStatSet_.end()) { - return it->second; - } - } - - std::lock_guard guard(lock_); - // test again with lock_guard - auto it = barrierStatSet_.find(name); - if (it != barrierStatSet_.end()) { - return it->second; - } - - BarrierStatPtr stat; - if (bType == BARRIER_END) { - stat = std::make_shared(numConnThreads, name); - } else if (bType == BARRIER_DELTA) { - stat = std::make_shared(numConnThreads, name); - } - auto ret = barrierStatSet_.insert(std::make_pair(name, stat)); - return ret.first->second; -} - void StatSet::printSegTimerStatus() { ReadLockGuard guard(lock_); LOG(INFO) << std::setiosflags(std::ios::left) << std::setfill(' ') @@ -135,46 +107,20 @@ void StatSet::printSegTimerStatus() { } } -void StatSet::printBarrierTimerStatus() { - ReadLockGuard guard(lock_); - if (barrierStatSet_.empty()) { - return; - } - // control barrierAbstact in runtime, so enable compliation - LOG(INFO) << std::setiosflags(std::ios::left) << std::setfill(' ') - << "======= BarrierStatSet status ======" << std::endl; - for (auto& stat : barrierStatSet_) { - LOG(INFO) << std::setiosflags(std::ios::left) << std::setfill(' ') - << *(stat.second); - } -} - void StatSet::printAllStatus() { #ifndef PADDLE_DISABLE_TIMER printSegTimerStatus(); #endif - printBarrierTimerStatus(); LOG(INFO) << std::setiosflags(std::ios::left) << "--------------------------------------------------" << std::endl; } -void StatSet::printStatus(const std::string& name) { - ReadLockGuard guard(lock_); - auto iter = statSet_.find(name); - CHECK(iter != statSet_.end()) << name << " is not registed in " << name_; - LOG(INFO) << *(iter->second); -} - void StatSet::reset(bool clearRawData) { ReadLockGuard guard(lock_); for (auto& stat : statSet_) { stat.second->reset(); } - // reset barrierStat - for (auto& stat : barrierStatSet_) { - stat.second->reset(clearRawData); - } } void StatSet::setThreadInfo(const std::string& name, bool flag) { @@ -184,13 +130,6 @@ void StatSet::setThreadInfo(const std::string& name, bool flag) { iter->second->setThreadInfo(flag); } -void StatSet::deleteStat(const std::string& name) { - std::lock_guard guard(lock_); - auto iter = statSet_.find(name); - CHECK(iter != statSet_.end()) << name << " is not registed in " << name_; - statSet_.erase(iter); -} - StatInfo::~StatInfo() { if (stat_) { std::lock_guard guard(stat_->lock_); diff --git a/paddle/utils/Stat.h b/paddle/utils/Stat.h index d9cc6e413a7..79fd3b8cf04 100644 --- a/paddle/utils/Stat.h +++ b/paddle/utils/Stat.h @@ -23,7 +23,6 @@ limitations under the License. */ #include #include -#include "BarrierStat.h" #include "Locks.h" #include "Logging.h" #include "ThreadLocal.h" @@ -60,12 +59,6 @@ public: class Stat; typedef std::shared_ptr StatPtr; -typedef std::shared_ptr BarrierStatPtr; - -enum BarrierStatType { - BARRIER_END = 0, - BARRIER_DELTA = 1, -}; class StatSet { public: @@ -74,11 +67,8 @@ public: // print to LOG(INFO) void printSegTimerStatus(); - void printBarrierTimerStatus(); void printAllStatus(); - void printStatus(const std::string& name); - StatPtr getStat(const std::string& name) { { ReadLockGuard guard(lock_); @@ -93,12 +83,6 @@ public: return ret.first->second; } - BarrierStatPtr getStat(uint16_t numConnThreads, - const std::string& name, - BarrierStatType bType); - - void deleteStat(const std::string& name); - // true for showing stats for each thread // false for showing stats aggragated over threads void setThreadInfo(const std::string& name, bool flag); @@ -120,7 +104,6 @@ public: private: std::unordered_map statSet_; - std::unordered_map barrierStatSet_; const std::string name_; RWLock lock_; }; -- GitLab From 0ba63475659822bd146f1f1dcfc7eabca8b7047d Mon Sep 17 00:00:00 2001 From: liaogang Date: Tue, 4 Jul 2017 21:23:23 +0800 Subject: [PATCH 1031/3256] ENH: Add buddy allocator Free --- paddle/memory/detail/CMakeLists.txt | 9 ++-- paddle/memory/detail/buddy_allocator.cc | 41 ++++++++++++++++++- paddle/memory/detail/buddy_allocator.h | 2 +- paddle/memory/detail/memory_block.h | 2 +- .../detail/{metadata.cc => meta_data.cc} | 2 +- .../memory/detail/{metadata.h => meta_data.h} | 0 paddle/platform/cpu_info.h | 10 ----- paddle/platform/gpu_info.cc | 13 ++++++ paddle/platform/gpu_info.h | 6 +++ 9 files changed, 65 insertions(+), 20 deletions(-) rename paddle/memory/detail/{metadata.cc => meta_data.cc} (97%) rename paddle/memory/detail/{metadata.h => meta_data.h} (100%) diff --git a/paddle/memory/detail/CMakeLists.txt b/paddle/memory/detail/CMakeLists.txt index dbc98a8a62f..c3167cd30aa 100644 --- a/paddle/memory/detail/CMakeLists.txt +++ b/paddle/memory/detail/CMakeLists.txt @@ -1,12 +1,9 @@ if(${WITH_GPU}) - nv_library(system_allocator SRCS system_allocator.cc DEPS gflags) - nv_test(system_allocator_test - SRCS system_allocator_test.cc - DEPS system_allocator gpu_info gflags) + nv_library(system_allocator SRCS system_allocator.cc DEPS gflags gpu_info) else(${WITH_GPU}) cc_library(system_allocator SRCS system_allocator.cc DEPS gflags) - cc_test(system_allocator_test SRCS system_allocator_test.cc DEPS system_allocator gflags) endif(${WITH_GPU}) +cc_test(system_allocator_test SRCS system_allocator_test.cc DEPS system_allocator) -cc_library(metadata SRCS metadata.cc) +cc_library(meta_data SRCS meta_data.cc) cc_library(buddy_allocator SRCS buddy_allocator.cc) diff --git a/paddle/memory/detail/buddy_allocator.cc b/paddle/memory/detail/buddy_allocator.cc index eddfd9d13c7..f677feda0d5 100644 --- a/paddle/memory/detail/buddy_allocator.cc +++ b/paddle/memory/detail/buddy_allocator.cc @@ -75,10 +75,49 @@ void* BuddyAllocator::Alloc(size_t unaligned_size) { } void BuddyAllocator::Free(void* p) { + // Point back to metadata auto block = static_cast(p)->metadata(); - // acquire the allocator lock + // Acquire the allocator lock std::lock_guard lock(mutex_); + + DLOG(INFO) << "Free from address " << block; + + if (block->type(cache_) == MemoryBlock::HUGE_CHUNK) { + DLOG(INFO) << "Free directly from system allocator"; + system_allocator_->Free(block, block->total_size(cache_), + block->index(cache_)); + + // Invalidate GPU allocation from cache + if (system_allocator_->UseGpu()) { + cache_.erase(block); + } + return; + } + + block->mark_as_free(cache_); + + total_used_ -= block->total_size(cache_); + total_free_ += block->total_size(cache_); + + // Trying to merge the right buddy + if (block->has_right_buddy(cache_)) { + DLOG(INFO) << "Merging this block " << block << " with its right buddy " + << block->right_buddy(cache_); + } + + // Trying to merge the left buddy + if (block->has_left_buddy(cache_)) { + DLOG(INFO) << "Merging this block " << block << " with its left buddy " + << block->left_buddy(cache_); + } + + // Dumping this block into pool + DLOG(INFO) << "Inserting free block (" << block << ", " + << block->total_size(cache_) << ")"; + pool_.insert({block->index(cache_), block->total_size(cache_), block}); + + // TODO(gangliao): Clean up if existing too much free memory } void* BuddyAllocator::SystemAlloc(size_t size) { diff --git a/paddle/memory/detail/buddy_allocator.h b/paddle/memory/detail/buddy_allocator.h index 4006bdcce8d..49bd6cf9019 100644 --- a/paddle/memory/detail/buddy_allocator.h +++ b/paddle/memory/detail/buddy_allocator.h @@ -14,7 +14,7 @@ #pragma once -#include "paddle/memory/detail/metadata.h" +#include "paddle/memory/detail/meta_data.h" #include "paddle/memory/detail/system_allocator.h" #include "paddle/platform/assert.h" #include "paddle/platform/cpu_info.h" diff --git a/paddle/memory/detail/memory_block.h b/paddle/memory/detail/memory_block.h index e2d39c31cfb..2945520113a 100644 --- a/paddle/memory/detail/memory_block.h +++ b/paddle/memory/detail/memory_block.h @@ -14,7 +14,7 @@ #pragma once -#include "paddle/memory/detail/metadata.h" +#include "paddle/memory/detail/meta_data.h" #include #include diff --git a/paddle/memory/detail/metadata.cc b/paddle/memory/detail/meta_data.cc similarity index 97% rename from paddle/memory/detail/metadata.cc rename to paddle/memory/detail/meta_data.cc index 4607cd8512e..a3b7a9b4fed 100644 --- a/paddle/memory/detail/metadata.cc +++ b/paddle/memory/detail/meta_data.cc @@ -12,7 +12,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -#include "paddle/memory/detail/metadata.h" +#include "paddle/memory/detail/meta_data.h" #include diff --git a/paddle/memory/detail/metadata.h b/paddle/memory/detail/meta_data.h similarity index 100% rename from paddle/memory/detail/metadata.h rename to paddle/memory/detail/meta_data.h diff --git a/paddle/platform/cpu_info.h b/paddle/platform/cpu_info.h index edd76517a64..8df7c7b4bca 100644 --- a/paddle/platform/cpu_info.h +++ b/paddle/platform/cpu_info.h @@ -28,15 +28,5 @@ size_t CpuMinChunkSize(); //! Get the maximum chunk size for buddy allocator. size_t CpuMaxChunkSize(); -int GetCurrentDeviceId(void) { - int device_id; - throw_on_error(cudaGetDevice(&device_id), "cudaGetDevice failed"); - return device_id; -} - -void SetDeviceId(int device_id) { - throw_on_error(cudaSetDevice(device_id), "cudaSetDevice failed"); -} - } // namespace platform } // namespace paddle diff --git a/paddle/platform/gpu_info.cc b/paddle/platform/gpu_info.cc index d6c6fe34ef4..05a243c5069 100644 --- a/paddle/platform/gpu_info.cc +++ b/paddle/platform/gpu_info.cc @@ -31,6 +31,19 @@ int GpuDeviceCount() { return count; } +int GetCurrentDeviceId() { + int device_id; + throw_on_error( + cudaGetDevice(&device_id), + "cudaGetDevice failed in paddle::platform::GetCurrentDeviceId"); + return device_id; +} + +void SetDeviceId(int id) { + throw_on_error(cudaSetDevice(id), + "cudaSetDevice failed in paddle::platform::SetDeviceId"); +} + void GpuMemoryUsage(size_t& available, size_t& total) { throw_on_error(cudaMemGetInfo(&available, &total), "cudaMemGetInfo failed in paddle::platform::GetMemoryUsage"); diff --git a/paddle/platform/gpu_info.h b/paddle/platform/gpu_info.h index d7bf0f4093e..81ee5f6e0a9 100644 --- a/paddle/platform/gpu_info.h +++ b/paddle/platform/gpu_info.h @@ -24,6 +24,12 @@ namespace platform { //! Get the total number of GPU devices in system. int GpuDeviceCount(); +//! Get the current GPU device id in system. +int GetCurrentDeviceId(); + +//! Set the GPU device id for next execution. +void SetDeviceId(int device_id); + //!Get the memory usage of current GPU device. void GpuMemoryUsage(size_t& available, size_t& total); -- GitLab From 4dc3c9e0cc1b6ec5dbc324f4804974247ca6506f Mon Sep 17 00:00:00 2001 From: liaogang Date: Tue, 4 Jul 2017 23:28:15 +0800 Subject: [PATCH 1032/3256] ENH: Add paddle_memory for external usage --- paddle/memory/CMakeLists.txt | 10 ++++ paddle/memory/detail/CMakeLists.txt | 6 +++ paddle/memory/detail/buddy_allocator.cc | 12 ++--- paddle/memory/detail/buddy_allocator.h | 2 +- paddle/memory/detail/memory_block.cc | 56 +++++++++++-------- paddle/memory/detail/memory_block.h | 10 +--- paddle/memory/detail/meta_cache.cc | 57 ++++++++++++++++++++ paddle/memory/detail/meta_cache.h | 71 +++++++++++++++++++++++++ paddle/memory/detail/meta_data.cc | 8 +++ paddle/memory/detail/meta_data.h | 1 + 10 files changed, 196 insertions(+), 37 deletions(-) create mode 100644 paddle/memory/detail/meta_cache.cc create mode 100644 paddle/memory/detail/meta_cache.h diff --git a/paddle/memory/CMakeLists.txt b/paddle/memory/CMakeLists.txt index 3943c3cfad3..8c290712fc9 100644 --- a/paddle/memory/CMakeLists.txt +++ b/paddle/memory/CMakeLists.txt @@ -1 +1,11 @@ add_subdirectory(detail) + +cc_library(memory + SRCS + memory.cc) + +cc_library(paddle_memory + DEPS + memory meta_data + meta_cache memory_block + buddy_allocator system_allocator) diff --git a/paddle/memory/detail/CMakeLists.txt b/paddle/memory/detail/CMakeLists.txt index c3167cd30aa..4fdabc8eebd 100644 --- a/paddle/memory/detail/CMakeLists.txt +++ b/paddle/memory/detail/CMakeLists.txt @@ -3,7 +3,13 @@ if(${WITH_GPU}) else(${WITH_GPU}) cc_library(system_allocator SRCS system_allocator.cc DEPS gflags) endif(${WITH_GPU}) + cc_test(system_allocator_test SRCS system_allocator_test.cc DEPS system_allocator) cc_library(meta_data SRCS meta_data.cc) + +cc_library(meta_cache SRCS meta_cache.cc) + +cc_library(memory_block SRCS memory_block.cc) + cc_library(buddy_allocator SRCS buddy_allocator.cc) diff --git a/paddle/memory/detail/buddy_allocator.cc b/paddle/memory/detail/buddy_allocator.cc index f677feda0d5..aa5b6b557c5 100644 --- a/paddle/memory/detail/buddy_allocator.cc +++ b/paddle/memory/detail/buddy_allocator.cc @@ -20,14 +20,14 @@ namespace memory { namespace detail { BuddyAllocator::BuddyAllocator(SystemAllocator* system_allocator, - size_t min_chunk_size, size_t max_chunk_size) { + size_t min_chunk_size, size_t max_chunk_size) + : min_chunk_size_(min_chunk_size), + max_chunk_size_(max_chunk_size), + cache_(system_allocator->UseGpu()), + system_allocator_(std::move(system_allocator)) { PADDLE_ASSERT(min_chunk_size > 0); PADDLE_ASSERT(max_chunk_size > 0); PADDLE_ASSERT(system_allocator != nullptr); - - system_allocator_ = std::move(system_allocator); - min_chunk_size_ = min_chunk_size; - max_chunk_size_ = max_chunk_size; } inline size_t align(size_t size, size_t alignment) { @@ -90,7 +90,7 @@ void BuddyAllocator::Free(void* p) { // Invalidate GPU allocation from cache if (system_allocator_->UseGpu()) { - cache_.erase(block); + cache_.invalidate(block); } return; } diff --git a/paddle/memory/detail/buddy_allocator.h b/paddle/memory/detail/buddy_allocator.h index 49bd6cf9019..ecf23b77ae8 100644 --- a/paddle/memory/detail/buddy_allocator.h +++ b/paddle/memory/detail/buddy_allocator.h @@ -14,6 +14,7 @@ #pragma once +#include "paddle/memory/detail/meta_cache.h" #include "paddle/memory/detail/meta_data.h" #include "paddle/memory/detail/system_allocator.h" #include "paddle/platform/assert.h" @@ -80,7 +81,6 @@ class BuddyAllocator { private: // Unify the metadata format between GPU and CPU allocations - using MetadataCache = std::unordered_map; MetadataCache cache_; private: diff --git a/paddle/memory/detail/memory_block.cc b/paddle/memory/detail/memory_block.cc index 1c9e87df497..eaa97e7b4ad 100644 --- a/paddle/memory/detail/memory_block.cc +++ b/paddle/memory/detail/memory_block.cc @@ -1,4 +1,20 @@ +/* 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/memory/detail/memory_block.h" +#include "paddle/memory/detail/meta_cache.h" +#include "paddle/memory/detail/meta_data.h" #include "paddle/platform/assert.h" namespace paddle { @@ -7,10 +23,9 @@ namespace detail { void MemoryBlock::init(MetadataCache& cache, Type t, size_t index, size_t size, void* left_buddy, void* right_buddy) { - cache.store(this, - MemoryBlockMetadata(t, index, size - overhead(), size, - static_cast(left_buddy), - static_cast(right_buddy))); + cache.store(this, Metadata(t, index, size - sizeof(Metadata), size, + static_cast(left_buddy), + static_cast(right_buddy))); } MemoryBlock::Type MemoryBlock::type(MetadataCache& cache) const { @@ -35,10 +50,10 @@ MemoryBlock* MemoryBlock::right_buddy(MetadataCache& cache) const { void MemoryBlock::split(MetadataCache& cache, size_t size) { // make sure the split fits - assert(total_size(cache) >= size); + PADDLE_ASSERT(total_size(cache) >= size); // bail out if there is no room for another partition - if (total_size(cache) - size <= overhead()) { + if (total_size(cache) - size <= sizeof(Metadata)) { return; } @@ -53,13 +68,13 @@ void MemoryBlock::split(MetadataCache& cache, size_t size) { // Write the metadata for the new block auto new_block_right_buddy = metadata.right_buddy; - cache.store(static_cast(right_partition), - MemoryBlockMetadata(FREE_MEMORY, index(cache), - remaining_size - overhead(), remaining_size, - this, new_block_right_buddy)); + cache.store( + static_cast(right_partition), + Metadata(FREE_CHUNK, index(cache), remaining_size - sizeof(Metadata), + remaining_size, this, new_block_right_buddy)); metadata.right_buddy = static_cast(right_partition); - metadata.size = size - overhead(); + metadata.size = size - sizeof(Metadata); metadata.total_size = size; cache.store(this, metadata); @@ -76,8 +91,8 @@ void MemoryBlock::split(MetadataCache& cache, size_t size) { void MemoryBlock::merge(MetadataCache& cache, MemoryBlock* right_buddy) { // only free blocks can be merged - assert(type(cache) == FREE_MEMORY); - assert(right_buddy->type(cache) == FREE_MEMORY); + PADDLE_ASSERT(type(cache) == FREE_MEMORY); + PADDLE_ASSERT(right_buddy->type(cache) == FREE_MEMORY); auto metadata = cache.load(this); @@ -97,16 +112,15 @@ void MemoryBlock::merge(MetadataCache& cache, MemoryBlock* right_buddy) { metadata.total_size += right_buddy->total_size(cache); cache.store(this, metadata); - cache.store(right_buddy, - MemoryBlockMetadata(INVALID_MEMORY, 0, 0, 0, nullptr, nullptr)); + cache.store(right_buddy, Metadata(INVALID_CHUNK, 0, 0, 0, nullptr, nullptr)); } void MemoryBlock::mark_as_free(MetadataCache& cache) { // check for double free or corruption - assert(type(cache) != FREE_MEMORY); - assert(type(cache) != INVALID_MEMORY); + PADDLE_ASSERT(type(cache) != FREE_CHUNK); + PADDLE_ASSERT(type(cache) != INVALID_CHUNK); - set_type(cache, FREE_MEMORY); + set_type(cache, FREE_CHUNK); } void MemoryBlock::set_type(MetadataCache& cache, Type t) { @@ -130,14 +144,12 @@ size_t MemoryBlock::index(MetadataCache& cache) const { } void* MemoryBlock::data() const { - return const_cast( - reinterpret_cast(this)) + - 1; + return const_cast(reinterpret_cast(this)) + 1; } MemoryBlock* MemoryBlock::metadata() const { return const_cast(reinterpret_cast( - reinterpret_cast(this) - 1)); + reinterpret_cast(this) - 1)); } } // detail diff --git a/paddle/memory/detail/memory_block.h b/paddle/memory/detail/memory_block.h index 2945520113a..a5168b519f3 100644 --- a/paddle/memory/detail/memory_block.h +++ b/paddle/memory/detail/memory_block.h @@ -14,24 +14,18 @@ #pragma once -#include "paddle/memory/detail/meta_data.h" - #include -#include namespace paddle { namespace memory { namespace detail { -// Forward Declaration -class Metadata; +// Forward Declarations +class MetadataCache; /*! \brief A class used to interpret the contents of a memory block */ class MemoryBlock { public: - // Unify the metadata format between GPU and CPU allocations - using MetadataCache = std::unordered_map; - enum Type { FREE_CHUNK, // memory is free and idle ARENA_CHUNK, // memory is being occupied diff --git a/paddle/memory/detail/meta_cache.cc b/paddle/memory/detail/meta_cache.cc new file mode 100644 index 00000000000..189ab4fc7bb --- /dev/null +++ b/paddle/memory/detail/meta_cache.cc @@ -0,0 +1,57 @@ +/* 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/memory/detail/meta_cache.h" +#include "paddle/memory/detail/memory_block.h" +#include "paddle/platform/assert.h" + +namespace paddle { +namespace memory { +namespace detail { + +MetadataCache::MetadataCache(bool uses_gpu) : uses_gpu_(uses_gpu) {} + +Metadata MetadataCache::load(const MemoryBlock* block) { + if (uses_gpu_) { + auto existing_metadata = cache_.find(block); + assert(existing_metadata->second.check_guards()); + return existing_metadata->second; + } else { + PADDLE_ASSERT(reinterpret_cast(block)->check_guards()); + return *reinterpret_cast(block); + } +} + +void MetadataCache::store(MemoryBlock* block, + const Metadata& original_metadata) { + auto metadata = original_metadata; + + metadata.update_guards(); + + if (uses_gpu_) { + cache_[block] = metadata; + } else { + *reinterpret_cast(block) = metadata; + } +} + +void MetadataCache::invalidate(MemoryBlock* block) { + if (uses_gpu_) { + cache_.erase(block); + } +} + +} // namespace detail +} // namespace memory +} // namespace paddle diff --git a/paddle/memory/detail/meta_cache.h b/paddle/memory/detail/meta_cache.h new file mode 100644 index 00000000000..3ca1020d22e --- /dev/null +++ b/paddle/memory/detail/meta_cache.h @@ -0,0 +1,71 @@ +/* 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/memory/detail/memory_block.h" +#include "paddle/memory/detail/meta_data.h" + +#include + +namespace paddle { +namespace memory { +namespace detail { + +/*! A cache for accessing memory block meta-data that may be expensive to access + directly. + + Note: this class exists to unify the metadata format between GPU and CPU + allocations. + It should be removed when the CPU can access all GPU allocations directly + via UVM. +*/ +class MetadataCache { + public: + MetadataCache(bool uses_gpu); + + public: + /*! \brief Load the associated metadata for the specified memory block. */ + Metadata load(const MemoryBlock*); + + /*! \brief Store the associated metadata for the specified memory block. */ + void store(MemoryBlock*, const Metadata&); + + public: + /*! \brief Acquire any external metadata updates. */ + void acquire(MemoryBlock*); + + /*! \brief Publish any local updates externally. */ + void release(MemoryBlock*); + + /*! \brief Indicate that the specified metadata will no longer be used */ + void invalidate(MemoryBlock*); + + public: + MetadataCache(const MetadataCache&) = delete; + MetadataCache& operator=(const MetadataCache&) = delete; + + private: + bool uses_gpu_; + + private: + typedef std::unordered_map MetadataMap; + + private: + MetadataMap cache_; +}; + +} // namespace detail +} // namespace memory +} // namespace paddle diff --git a/paddle/memory/detail/meta_data.cc b/paddle/memory/detail/meta_data.cc index a3b7a9b4fed..70c5c1f439e 100644 --- a/paddle/memory/detail/meta_data.cc +++ b/paddle/memory/detail/meta_data.cc @@ -29,6 +29,14 @@ Metadata::Metadata(MemoryBlock::Type t, size_t i, size_t s, size_t ts, left_buddy(l), right_buddy(r) {} +Metadata::Metadata() + : type(MemoryBlock::INVALID_CHUNK), + index(0), + size(0), + total_size(0), + left_buddy(nullptr), + right_buddy(nullptr) {} + template inline void hash_combine(std::size_t& seed, const T& v) { std::hash hasher; diff --git a/paddle/memory/detail/meta_data.h b/paddle/memory/detail/meta_data.h index ddb826571b6..628cf1f2e34 100644 --- a/paddle/memory/detail/meta_data.h +++ b/paddle/memory/detail/meta_data.h @@ -26,6 +26,7 @@ class Metadata { public: Metadata(MemoryBlock::Type t, size_t i, size_t s, size_t ts, MemoryBlock* l, MemoryBlock* r); + Metadata(); public: /*! \brief Update the guards when metadata is changed */ -- GitLab From 9f2357561d939bdeae2a7bc0bd41be43d9ab0fe5 Mon Sep 17 00:00:00 2001 From: Superjom Date: Wed, 5 Jul 2017 10:08:23 +0800 Subject: [PATCH 1033/3256] fix ci error --- paddle/framework/net.cc | 2 +- paddle/framework/net.h | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/paddle/framework/net.cc b/paddle/framework/net.cc index d49861c343e..8c565c28cb9 100644 --- a/paddle/framework/net.cc +++ b/paddle/framework/net.cc @@ -12,7 +12,7 @@ void PlainNet::InferShape(Scope* scope) { } void PlainNet::Run(Scope* scope, OpContext* context, OpIndex begin, - OpIndex end) const { + OpIndex end) const { // TODO Add implementation here. } diff --git a/paddle/framework/net.h b/paddle/framework/net.h index 55dcf147e1d..9564c831eef 100644 --- a/paddle/framework/net.h +++ b/paddle/framework/net.h @@ -70,7 +70,7 @@ class Net { * If no positive indexes are provided, all operators in `ops_` will run. */ virtual void Run(Scope *scope, OpContext *context, OpIndex begin = -1, - OpIndex end = -1) const = 0; + OpIndex end = -1) const = 0; /** * @brief Add an Operator according to `def`. @@ -125,7 +125,7 @@ class PlainNet : public Net { * will be used. */ virtual void Run(Scope *scope = nullptr, OpContext *context = nullptr, - OpIndex begin = -1, OpIndex end = -1) const override; + OpIndex begin = -1, OpIndex end = -1) const override; /** * @brief Add an operator to this network. @@ -142,6 +142,8 @@ class PlainNet : public Net { */ virtual void AddBackwardOps() override; + virtual ~PlainNet() override {} + protected: /** * @brief Build the network. -- GitLab From 5c10a5ad555d834dac4785d8cd2feac18da9b67b Mon Sep 17 00:00:00 2001 From: Superjom Date: Wed, 5 Jul 2017 10:34:49 +0800 Subject: [PATCH 1034/3256] remove virtual --- paddle/framework/net.h | 4 ---- 1 file changed, 4 deletions(-) diff --git a/paddle/framework/net.h b/paddle/framework/net.h index 9564c831eef..e60356dc172 100644 --- a/paddle/framework/net.h +++ b/paddle/framework/net.h @@ -91,8 +91,6 @@ class Net { * @brief Create a network. */ static std::unique_ptr Create(const NetDesc &def = NetDesc()); - - virtual ~Net() = 0; }; /** @@ -142,8 +140,6 @@ class PlainNet : public Net { */ virtual void AddBackwardOps() override; - virtual ~PlainNet() override {} - protected: /** * @brief Build the network. -- GitLab From 166dfbb085ef4ebbccea190abc436524fb80ed57 Mon Sep 17 00:00:00 2001 From: gongweibao Date: Wed, 5 Jul 2017 02:36:10 +0000 Subject: [PATCH 1035/3256] fix cmake errors --- cmake/generic.cmake | 7 ++++--- go/cmd/master/CMakeLists.txt | 2 +- go/cmd/pserver/CMakeLists.txt | 2 +- go/pserver/optimizer.go | 2 +- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/cmake/generic.cmake b/cmake/generic.cmake index cae9524b2fe..97196114ff6 100644 --- a/cmake/generic.cmake +++ b/cmake/generic.cmake @@ -284,7 +284,7 @@ function(go_library TARGET_NAME) add_custom_command(TARGET ${TARGET_NAME} POST_BUILD COMMAND rm "${${TARGET_NAME}_LIB_PATH}" # Golang build source code - COMMAND env GOPATH=${GOPATH} ${CMAKE_Go_COMPILER} build ${BUILD_MODE} + COMMAND env LIBRARY_PATH=${CMAKE_BINARY_DIR}/go/pserver/client/c/:$ENV{LIBRARY_PATH} GOPATH=${GOPATH} ${CMAKE_Go_COMPILER} build ${BUILD_MODE} -o "${${TARGET_NAME}_LIB_PATH}" "./${CMAKE_CURRENT_SOURCE_REL_DIR}/${GO_SOURCE}" # must run under GOPATH @@ -300,10 +300,11 @@ function(go_binary TARGET_NAME) string(REPLACE "${PADDLE_GO_PATH}/" "" CMAKE_CURRENT_SOURCE_REL_DIR ${CMAKE_CURRENT_SOURCE_DIR}) add_custom_command(OUTPUT ${TARGET_NAME}_timestamp - COMMAND env GOPATH=${GOPATH} ${CMAKE_Go_COMPILER} build + COMMAND env LIBRARY_PATH=${CMAKE_BINARY_DIR}/go/pserver/client/c/:$ENV{LIBRARY_PATH} + GOPATH=${GOPATH} ${CMAKE_Go_COMPILER} build -o "${CMAKE_CURRENT_BINARY_DIR}/${TARGET_NAME}" "./${CMAKE_CURRENT_SOURCE_REL_DIR}/${go_binary_SRCS}" - WORKING_DIRECTORY "${PADDLE_IN_GOPATH}/go") + WORKING_DIRECTORY "${PADDLE_IN_GOPATH}/go") # TODO: don't know what ${TARGET_NAME}_link does add_custom_target(${TARGET_NAME} ALL DEPENDS go_vendor ${TARGET_NAME}_timestamp ${go_binary_DEPS}) install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/${TARGET_NAME} DESTINATION bin) diff --git a/go/cmd/master/CMakeLists.txt b/go/cmd/master/CMakeLists.txt index 9e149967e71..1058ffa86b3 100644 --- a/go/cmd/master/CMakeLists.txt +++ b/go/cmd/master/CMakeLists.txt @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -go_binary(master SRC master.go) +go_binary(master SRC master.go DEPS paddle_go_optimizer) diff --git a/go/cmd/pserver/CMakeLists.txt b/go/cmd/pserver/CMakeLists.txt index bc1da3348cc..51db6dff043 100644 --- a/go/cmd/pserver/CMakeLists.txt +++ b/go/cmd/pserver/CMakeLists.txt @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -go_binary(pserver SRCS pserver.go) +go_binary(pserver SRCS pserver.go DEPS paddle_go_optimizer) diff --git a/go/pserver/optimizer.go b/go/pserver/optimizer.go index d84f55b9874..2c9b0d5652a 100644 --- a/go/pserver/optimizer.go +++ b/go/pserver/optimizer.go @@ -2,7 +2,7 @@ package pserver // #cgo CFLAGS: -I ../../ // //FIXME: ldflags contain "build" path -// #cgo LDFLAGS: ../../build/go/pserver/client/c/libpaddle_go_optimizer.a -lstdc++ -lm +// #cgo LDFLAGS: -lpaddle_go_optimizer -lstdc++ -lm // #include "paddle/optimizer/optimizer.h" // #include // #include -- GitLab From 013d0a268591829d7f757deeb3c23c58915c2d95 Mon Sep 17 00:00:00 2001 From: wanghaoshuang Date: Fri, 16 Jun 2017 19:02:46 +0800 Subject: [PATCH 1036/3256] add crop layer --- paddle/function/CMakeLists.txt | 1 + paddle/function/CropOp.cpp | 177 ++++++++++++++++++++++++++++ paddle/function/CropOp.h | 56 +++++++++ paddle/function/CropOpGpu.cu | 109 +++++++++++++++++ paddle/function/CropOpTest.cpp | 47 ++++++++ paddle/gserver/layers/CropLayer.cpp | 101 ++++++++++++++++ paddle/gserver/layers/CropLayer.h | 46 ++++++++ 7 files changed, 537 insertions(+) create mode 100644 paddle/function/CropOp.cpp create mode 100644 paddle/function/CropOp.h create mode 100644 paddle/function/CropOpGpu.cu create mode 100644 paddle/function/CropOpTest.cpp create mode 100644 paddle/gserver/layers/CropLayer.cpp create mode 100644 paddle/gserver/layers/CropLayer.h diff --git a/paddle/function/CMakeLists.txt b/paddle/function/CMakeLists.txt index 1518a8a654c..f19a1eb7774 100644 --- a/paddle/function/CMakeLists.txt +++ b/paddle/function/CMakeLists.txt @@ -37,6 +37,7 @@ if(WITH_GPU) add_simple_unittest(MulOpTest) add_simple_unittest(CosSimOpTest) add_simple_unittest(RowConvOpTest) + add_simple_unittest(CropOpTest) endif() add_simple_unittest(ConvOpTest) diff --git a/paddle/function/CropOp.cpp b/paddle/function/CropOp.cpp new file mode 100644 index 00000000000..4d47d9c149c --- /dev/null +++ b/paddle/function/CropOp.cpp @@ -0,0 +1,177 @@ +/* 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 "CropOp.h" +#include "paddle/math/Vector.h" +#include "paddle/function/TensorShape.h" +namespace paddle { + +static inline CropConf castToCropConf(const FuncConfig& conf) { + return {conf.get>("crop_corner"), + conf.get>("crop_shape")}; +} + +template <> +void Crop(real* outputs, + const real* inputs, + const TensorShape inShape, + const CropConf& crop) { + int cCrop = crop.corner[0]; + int hCrop = crop.corner[1]; + int wCrop = crop.corner[2]; + + int num = inShape[0]; + int inC = inShape[1]; + int inH = inShape[2]; + int inW = inShape[3]; + + int outC = crop.shape[0]; + int outH = crop.shape[1]; + int outW = crop.shape[2]; + + for (int n = 0; n < num; n++) { + for (int c = 0; c < outC; c++) { + for (int h = 0; h < outH; h++) { + int outoff = ((n * outC + c) * outH + h) * outW; + int inoff = ((n * inC + c + cCrop) * inH + h + hCrop) * inW + wCrop; + memcpy(outputs + outoff, inputs + inoff, outW * sizeof(real)); + } + } + } +} + +template <> +void CropGrad(const real* inGrad, + real* outGrad, + const TensorShape outShape, + const CropConf& crop) { + int cCrop = crop.corner[0]; + int hCrop = crop.corner[1]; + int wCrop = crop.corner[2]; + + int num = outShape[0]; + int outC = outShape[1]; + int outH = outShape[2]; + int outW = outShape[3]; + + int inC = crop.shape[0]; + int inH = crop.shape[1]; + int inW = crop.shape[2]; + + for (int n = 0; n < num; n++) { + for (int c = 0; c < inC; c++) { + for (int h = 0; h < inH; h++) { + int outoff = ((n * outC + c + cCrop) * outH + h + hCrop) * outW + wCrop; + int inoff = ((n * inC + c) * inH + h) * inW; + CpuVector inG = CpuVector(inW, const_cast(inGrad + inoff)); + CpuVector outG = CpuVector(inW, outGrad + outoff); + outG += inG; + } + } + } +} + +/** + * \brief Crop input according to the specify corner and shape. + * The input and output is a 4D tensor. In CropFunc, we only + * crop the 2nd to 4th dimension. + * + * Argument in this Function: + * \param pad_ A struct object contains the cropping corner and shape. + * \param inputs A 4D tensor, only one input. + * \param outputs A 4D tensor, the output value after cropping. + * + * For example, + * Input(2,2,2,3) = [ + * [ [[1,2,3], [3,4,5]], + * [[2,3,5], [1,6,7]] ], + * [ [[4,3,1], [1,8,7]], + * [[3,8,9], [2,3,5]] ] + * ] # the input shape is (2,2,2,3) + * + * pad_: if corner = (0,1,1) and crop_shape = (2,1,2) + * Output(2,2,1,2) = [ + * [ [[4,5]], + * [[6,7]] ], + * [ [[8,7]], + * [[3,5]] ] + * ] # the input shape is (2,2,2,3) + */ +template +class CropFunc : public FunctionBase { +public: + void init(const FuncConfig& config) override { + crop_ = castToCropConf(config); + } + + void calc(const BufferArgs& inputs, const BufferArgs& outputs) override { + CHECK_EQ(1UL, inputs.size()); + CHECK_EQ(1UL, outputs.size()); + CHECK_EQ(outputs[0].shape()[1], crop_.shape[0]); + CHECK_EQ(outputs[0].shape()[2], crop_.shape[1]); + CHECK_EQ(outputs[0].shape()[3], crop_.shape[2]); + CHECK_EQ(outputs[0].getArgType(), ASSIGN_TO); + + TensorShape inShape = inputs[0].shape(); + + Crop( + outputs[0].data(), inputs[0].data(), inShape, crop_); + } + +private: + CropConf crop_; +}; + +/** + * \brief The backward propagation of cropping Function. + * + * Argument in this Function: + * \param crop_ The same meaning as it in CropFunc. + * \param inputs The gradient with respect to the output value of CropFunc. + * \param outputs The gradient with respect to the input value of CropFunc. + */ + +template +class CropGradFunc : public FunctionBase { +public: + void init(const FuncConfig& config) override { + crop_ = castToCropConf(config); + } + + void calc(const BufferArgs& inputs, const BufferArgs& outputs) override { + CHECK_EQ(1UL, inputs.size()); + CHECK_EQ(1UL, outputs.size()); + CHECK_EQ(inputs[0].shape()[1], crop_.shape[0]); + CHECK_EQ(inputs[0].shape()[2], crop_.shape[1]); + CHECK_EQ(inputs[0].shape()[3], crop_.shape[2]); + CHECK_EQ(outputs[0].getArgType(), ASSIGN_TO); + + TensorShape outShape = outputs[0].shape(); + + CropGrad( + inputs[0].data(), outputs[0].data(), outShape, crop_); + } + +private: + CropConf crop_; +}; + +REGISTER_TYPED_FUNC(Crop, CPU, CropFunc); +REGISTER_TYPED_FUNC(CropGrad, CPU, CropGradFunc); +#ifndef PADDLE_ONLY_CPU +REGISTER_TYPED_FUNC(Crop, GPU, CropFunc); +REGISTER_TYPED_FUNC(CropGrad, GPU, CropGradFunc); +#endif + +} // namespace paddle diff --git a/paddle/function/CropOp.h b/paddle/function/CropOp.h new file mode 100644 index 00000000000..78a55bd43e9 --- /dev/null +++ b/paddle/function/CropOp.h @@ -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. */ + +#pragma once + +#include "Function.h" + +namespace paddle { + +struct CropConf { + /// The upper left corner of croped result + std::vector corner; + /// The shape of croped result + std::vector shape; +}; + +/** + * \brief This funtion crops inputs according to the specify start point and + *shape. + * + * \param[out] outputs save results. + * \param[in] inputs input data. + * \param[in] inShape the shape of input tensor. + * \param[in] crop the cropping config + */ +template +void Crop(real* outputs, + const real* inputs, + const TensorShape inShape, + const CropConf& crop); + +/** + * \brief Cropping operation backward. + * + * \param[out] inGrad gradients of previous layer + * \param[in] outGrad output gradient + * \param[in] inShape the shape of input tensor. + * \param[in] crop the cropping config + */ +template +void CropGrad(const real* inGrad, + real* outGrad, + const TensorShape inShape, + const CropConf& crop); +} // namespace paddle diff --git a/paddle/function/CropOpGpu.cu b/paddle/function/CropOpGpu.cu new file mode 100644 index 00000000000..f7d7d03abd2 --- /dev/null +++ b/paddle/function/CropOpGpu.cu @@ -0,0 +1,109 @@ +/* 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 "hl_base.h" +#include "CropOp.h" + +namespace paddle { + +__global__ void KeCrop(real* outputs, const real* inputs, + int inC, int inH, int inW, + int cropC, int cropH, int cropW, + int outC, int outH, int outW, int nthreads) { + const int idx = threadIdx.x + blockIdx.x * blockDim.x; + if (idx < nthreads) { + const int w = idx % outW; + const int h = (idx / outW) % outH; + const int c = (idx / outW / outH) % outC; + const int n = idx / outW / outH / outC; + + const int off = ((n * inC + c + cropC) * inH + h + cropH) * inW + cropW + w; + outputs[idx] = inputs[off]; + } +} + +template <> +void Crop(real* outputs, + const real* inputs, + const TensorShape inShape, + const CropConf& crop) { + int cropC = crop.corner[0]; + int cropH = crop.corner[1]; + int cropW = crop.corner[2]; + + int num = inShape[0]; + int inC = inShape[1]; + int inH = inShape[2]; + int inW = inShape[3]; + + int outC = crop.shape[0]; + int outH = crop.shape[1]; + int outW = crop.shape[2]; + + size_t nth = num * outC * outH * outW; + int blockSize = 1024; + int gridSize = (nth + blockSize - 1) / blockSize; + + KeCrop<<>> + (outputs, inputs, inC, inH, inW, cropC, cropH, cropW, + outC, outH, outW, nth); + CHECK_SYNC("Crop"); +} + +__global__ void KeCropDiff(const real* inGrad, real* outGrad, + int inC, int inH, int inW, + int cropC, int cropH, int cropW, + int outC, int outH, int outW, int nthreads) { + const int idx = threadIdx.x + blockIdx.x * blockDim.x; + if (idx < nthreads) { + const int w = idx % inW; + const int h = (idx / inW) % inH; + const int c = (idx / inW / inH) % inC; + const int n = idx / inW / inH / inC; + + const int off = ((n * outC + c + cropC) * outH + h + cropH) * outW + cropW + w; + + outGrad[off] += inGrad[idx]; + } +} + +template <> +void CropGrad(const real* inGrad, + real* outGrad, + const TensorShape outShape, + const CropConf& crop) { + int cropC = crop.corner[0]; + int cropH = crop.corner[1]; + int cropW = crop.corner[2]; + + int num = outShape[0]; + int outC = outShape[1]; + int outH = outShape[2]; + int outW = outShape[3]; + + int inC = crop.shape[0]; + int inH = crop.shape[1]; + int inW = crop.shape[2]; + + size_t nth = num * inC * inH * inW; + int blockSize = 1024; + int gridSize = (nth + blockSize - 1) / blockSize; + + KeCropDiff <<>> + (inGrad, outGrad, inC, inH, inW, cropC, cropH, cropW, + outC, outH, outW, nth); + CHECK_SYNC("CropGrad"); +} + +} // namespace paddle diff --git a/paddle/function/CropOpTest.cpp b/paddle/function/CropOpTest.cpp new file mode 100644 index 00000000000..62b4bd9fdea --- /dev/null +++ b/paddle/function/CropOpTest.cpp @@ -0,0 +1,47 @@ +/* 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 "FunctionTest.h" + +namespace paddle { + +TEST(Crop, real) { + for (size_t numSamples : {5, 32}) { + for (size_t channels : {5, 5, 32}) { + for (size_t imgSizeH : {5, 33, 100}) { + for (size_t imgSizeW : {5, 32, 96}) { + VLOG(3) << " numSamples=" << numSamples << " channels=" << channels + << " imgSizeH=" << imgSizeH << " imgSizeW=" << imgSizeW; + for (bool test_grad : {false, true}) { + FunctionCompare compare( + test_grad ? "CropGrad" : "Crop", + FuncConfig() + .set>("crop_corner", {1, 1, 1}) + .set>("crop_shape", {2, 3, 3})); + TensorShape inDims{numSamples, channels, imgSizeH, imgSizeW}; + TensorShape outDims{numSamples, 2, 3, 3}; + compare.addInputs( + BufferArg(VALUE_TYPE_FLOAT, test_grad ? outDims : inDims)); + compare.addOutputs(BufferArg( + VALUE_TYPE_FLOAT, test_grad ? inDims : outDims, ASSIGN_TO)); + compare.run(); + } + } + } + } + } +} + +} // namespace paddle diff --git a/paddle/gserver/layers/CropLayer.cpp b/paddle/gserver/layers/CropLayer.cpp new file mode 100644 index 00000000000..ab23d4617e3 --- /dev/null +++ b/paddle/gserver/layers/CropLayer.cpp @@ -0,0 +1,101 @@ +/* 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 "CropLayer.h" +#include "paddle/utils/Stat.h" + +namespace paddle { + +REGISTER_LAYER(crop, CropLayer); + +bool CropLayer::init(const LayerMap& layerMap, + const ParameterMap& parameterMap) { + /* Initialize the basic parent class */ + Layer::init(layerMap, parameterMap); + + auto& crop_conf = config_.inputs(0).crop_conf(); + auto& img_conf = crop_conf.image_conf(); + CHECK_EQ(config_.inputs_size(), 1); + inDims_ = TensorShape( + {0, + img_conf.channels(), + img_conf.has_img_size_y() ? img_conf.img_size_y() : img_conf.img_size(), + img_conf.img_size()}); + + crop_corner_ = {crop_conf.crop_corner(0), + crop_conf.crop_corner(1), + crop_conf.crop_corner(2)}; + crop_shape_ = {crop_conf.crop_shape(0), + crop_conf.crop_shape(1), + crop_conf.crop_shape(2)}; + + outDims_ = TensorShape(4); + setOutDims(0); + + createFunction(forward_, + "Crop", + FuncConfig() + .set("crop_corner", crop_corner_) + .set("crop_shape", crop_shape_)); + createFunction(backward_, + "CropGrad", + FuncConfig() + .set("crop_corner", crop_corner_) + .set("crop_shape", crop_shape_)); + + return true; +} + +void CropLayer::setOutDims(const size_t batchSize) { + outDims_.reshape({batchSize, crop_shape_[0], crop_shape_[1], crop_shape_[2]}); +} + +void CropLayer::setTensorDim(const size_t batchSize) { + CHECK_EQ(static_cast(inputLayers_.size()), 1); + inDims_.setDim(0, batchSize); + int h = inputLayers_[0]->getOutput().getFrameHeight(); + if (h != 0) inDims_.setDim(2, h); + int w = inputLayers_[0]->getOutput().getFrameWidth(); + if (w != 0) inDims_.setDim(3, w); + setOutDims(batchSize); +} + +void CropLayer::forward(PassType passType) { + Layer::forward(passType); + MatrixPtr input = inputLayers_[0]->getOutputValue(); + size_t batchSize = input->getHeight(); + setTensorDim(batchSize); + int size = outDims_[1] * outDims_[2] * outDims_[3]; + resetOutput(batchSize, size); + MatrixPtr outV = getOutputValue(); + REGISTER_TIMER_INFO("CropForward", getName().c_str()); + + BufferArgs inputs; + BufferArgs outputs; + inputs.addArg(*getInputValue(0), inDims_); + outputs.addArg(*getOutputValue(), outDims_, ASSIGN_TO); + forward_[0]->calc(inputs, outputs); +} + +void CropLayer::backward(const UpdateCallback& callback) { + (void)callback; + REGISTER_TIMER_INFO("CropBackward", getName().c_str()); + + BufferArgs inputs; + BufferArgs outputs; + inputs.addArg(*getOutputGrad(), outDims_); + outputs.addArg(*getInputGrad(0), inDims_, ADD_TO); + backward_[0]->calc(inputs, outputs); +} +} // namespace paddle diff --git a/paddle/gserver/layers/CropLayer.h b/paddle/gserver/layers/CropLayer.h new file mode 100644 index 00000000000..3ce89707caf --- /dev/null +++ b/paddle/gserver/layers/CropLayer.h @@ -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. */ + +#pragma once + +#include "Layer.h" + +namespace paddle { + +/** + * \brief This layer crop inputs according to the specify corner and shape. + * The input and output is a 4D tensor. Cropping from the 2nd to + * the 4th dimenstion. + */ +class CropLayer : public Layer { +public: + explicit CropLayer(const LayerConfig& config) : Layer(config) {} + + ~CropLayer() {} + + bool init(const LayerMap& layerMap, + const ParameterMap& parameterMap) override; + void forward(PassType passType) override; + void backward(const UpdateCallback& callback = nullptr) override; + +protected: + void setOutDims(const size_t batchSize); + void setTensorDim(const size_t batchSize); + + std::vector crop_corner_; + std::vector crop_shape_; + TensorShape inDims_; + TensorShape outDims_; +}; +} // namespace paddle -- GitLab From 90ed2004a56a955dc6a1413e1d4c624caf31780b Mon Sep 17 00:00:00 2001 From: wanghaoshuang Date: Thu, 22 Jun 2017 16:54:07 +0800 Subject: [PATCH 1037/3256] Refine configure option of crop layer 1. change configure content to 'axis, offset, shape' 2. add an optional input to crop layer as cropping reference --- paddle/function/CropOp.cpp | 63 ++++++++++++--------------- paddle/function/CropOp.h | 15 ++----- paddle/function/CropOpGpu.cu | 32 ++++++++------ paddle/function/CropOpTest.cpp | 4 +- paddle/gserver/layers/CropLayer.cpp | 67 ++++++++++++++++++++++------- paddle/gserver/layers/CropLayer.h | 13 ++++-- 6 files changed, 114 insertions(+), 80 deletions(-) diff --git a/paddle/function/CropOp.cpp b/paddle/function/CropOp.cpp index 4d47d9c149c..0d511ceef5e 100644 --- a/paddle/function/CropOp.cpp +++ b/paddle/function/CropOp.cpp @@ -17,28 +17,27 @@ limitations under the License. */ #include "paddle/function/TensorShape.h" namespace paddle { -static inline CropConf castToCropConf(const FuncConfig& conf) { - return {conf.get>("crop_corner"), - conf.get>("crop_shape")}; -} - template <> void Crop(real* outputs, const real* inputs, const TensorShape inShape, - const CropConf& crop) { - int cCrop = crop.corner[0]; - int hCrop = crop.corner[1]; - int wCrop = crop.corner[2]; + const FuncConfig& conf) { + std::vector crop_corner = + conf.get>("crop_corner"); + std::vector crop_shape = + conf.get>("crop_shape"); + int cCrop = crop_corner[1]; + int hCrop = crop_corner[2]; + int wCrop = crop_corner[3]; int num = inShape[0]; int inC = inShape[1]; int inH = inShape[2]; int inW = inShape[3]; - int outC = crop.shape[0]; - int outH = crop.shape[1]; - int outW = crop.shape[2]; + int outC = crop_shape[1]; + int outH = crop_shape[2]; + int outW = crop_shape[3]; for (int n = 0; n < num; n++) { for (int c = 0; c < outC; c++) { @@ -55,19 +54,23 @@ template <> void CropGrad(const real* inGrad, real* outGrad, const TensorShape outShape, - const CropConf& crop) { - int cCrop = crop.corner[0]; - int hCrop = crop.corner[1]; - int wCrop = crop.corner[2]; + const FuncConfig& conf) { + std::vector crop_corner = + conf.get>("crop_corner"); + std::vector crop_shape = + conf.get>("crop_shape"); + int cCrop = crop_corner[1]; + int hCrop = crop_corner[2]; + int wCrop = crop_corner[3]; int num = outShape[0]; int outC = outShape[1]; int outH = outShape[2]; int outW = outShape[3]; - int inC = crop.shape[0]; - int inH = crop.shape[1]; - int inW = crop.shape[2]; + int inC = crop_shape[1]; + int inH = crop_shape[2]; + int inW = crop_shape[3]; for (int n = 0; n < num; n++) { for (int c = 0; c < inC; c++) { @@ -111,26 +114,21 @@ void CropGrad(const real* inGrad, template class CropFunc : public FunctionBase { public: - void init(const FuncConfig& config) override { - crop_ = castToCropConf(config); - } + void init(const FuncConfig& config) override { conf_ = config; } void calc(const BufferArgs& inputs, const BufferArgs& outputs) override { CHECK_EQ(1UL, inputs.size()); CHECK_EQ(1UL, outputs.size()); - CHECK_EQ(outputs[0].shape()[1], crop_.shape[0]); - CHECK_EQ(outputs[0].shape()[2], crop_.shape[1]); - CHECK_EQ(outputs[0].shape()[3], crop_.shape[2]); CHECK_EQ(outputs[0].getArgType(), ASSIGN_TO); TensorShape inShape = inputs[0].shape(); Crop( - outputs[0].data(), inputs[0].data(), inShape, crop_); + outputs[0].data(), inputs[0].data(), inShape, conf_); } private: - CropConf crop_; + FuncConfig conf_; }; /** @@ -145,26 +143,21 @@ private: template class CropGradFunc : public FunctionBase { public: - void init(const FuncConfig& config) override { - crop_ = castToCropConf(config); - } + void init(const FuncConfig& config) override { conf_ = config; } void calc(const BufferArgs& inputs, const BufferArgs& outputs) override { CHECK_EQ(1UL, inputs.size()); CHECK_EQ(1UL, outputs.size()); - CHECK_EQ(inputs[0].shape()[1], crop_.shape[0]); - CHECK_EQ(inputs[0].shape()[2], crop_.shape[1]); - CHECK_EQ(inputs[0].shape()[3], crop_.shape[2]); CHECK_EQ(outputs[0].getArgType(), ASSIGN_TO); TensorShape outShape = outputs[0].shape(); CropGrad( - inputs[0].data(), outputs[0].data(), outShape, crop_); + inputs[0].data(), outputs[0].data(), outShape, conf_); } private: - CropConf crop_; + FuncConfig conf_; }; REGISTER_TYPED_FUNC(Crop, CPU, CropFunc); diff --git a/paddle/function/CropOp.h b/paddle/function/CropOp.h index 78a55bd43e9..71e8c4c00ee 100644 --- a/paddle/function/CropOp.h +++ b/paddle/function/CropOp.h @@ -18,13 +18,6 @@ limitations under the License. */ namespace paddle { -struct CropConf { - /// The upper left corner of croped result - std::vector corner; - /// The shape of croped result - std::vector shape; -}; - /** * \brief This funtion crops inputs according to the specify start point and *shape. @@ -32,13 +25,13 @@ struct CropConf { * \param[out] outputs save results. * \param[in] inputs input data. * \param[in] inShape the shape of input tensor. - * \param[in] crop the cropping config + * \param[in] conf the cropping config */ template void Crop(real* outputs, const real* inputs, const TensorShape inShape, - const CropConf& crop); + const FuncConfig& conf); /** * \brief Cropping operation backward. @@ -46,11 +39,11 @@ void Crop(real* outputs, * \param[out] inGrad gradients of previous layer * \param[in] outGrad output gradient * \param[in] inShape the shape of input tensor. - * \param[in] crop the cropping config + * \param[in] conf the cropping config */ template void CropGrad(const real* inGrad, real* outGrad, const TensorShape inShape, - const CropConf& crop); + const FuncConfig& conf); } // namespace paddle diff --git a/paddle/function/CropOpGpu.cu b/paddle/function/CropOpGpu.cu index f7d7d03abd2..cadb58b6e99 100644 --- a/paddle/function/CropOpGpu.cu +++ b/paddle/function/CropOpGpu.cu @@ -37,19 +37,21 @@ template <> void Crop(real* outputs, const real* inputs, const TensorShape inShape, - const CropConf& crop) { - int cropC = crop.corner[0]; - int cropH = crop.corner[1]; - int cropW = crop.corner[2]; + const FuncConfig& conf) { + std::vector crop_corner = conf.get>("crop_corner"); + std::vector crop_shape = conf.get>("crop_shape"); + int cropC = crop_corner[1]; + int cropH = crop_corner[2]; + int cropW = crop_corner[3]; int num = inShape[0]; int inC = inShape[1]; int inH = inShape[2]; int inW = inShape[3]; - int outC = crop.shape[0]; - int outH = crop.shape[1]; - int outW = crop.shape[2]; + int outC = crop_shape[1]; + int outH = crop_shape[2]; + int outW = crop_shape[3]; size_t nth = num * outC * outH * outW; int blockSize = 1024; @@ -82,19 +84,21 @@ template <> void CropGrad(const real* inGrad, real* outGrad, const TensorShape outShape, - const CropConf& crop) { - int cropC = crop.corner[0]; - int cropH = crop.corner[1]; - int cropW = crop.corner[2]; + const FuncConfig& conf) { + std::vector crop_corner = conf.get>("crop_corner"); + std::vector crop_shape = conf.get>("crop_shape"); + int cropC = crop_corner[1]; + int cropH = crop_corner[2]; + int cropW = crop_corner[3]; int num = outShape[0]; int outC = outShape[1]; int outH = outShape[2]; int outW = outShape[3]; - int inC = crop.shape[0]; - int inH = crop.shape[1]; - int inW = crop.shape[2]; + int inC = crop_shape[1]; + int inH = crop_shape[2]; + int inW = crop_shape[3]; size_t nth = num * inC * inH * inW; int blockSize = 1024; diff --git a/paddle/function/CropOpTest.cpp b/paddle/function/CropOpTest.cpp index 62b4bd9fdea..c331a70d1f6 100644 --- a/paddle/function/CropOpTest.cpp +++ b/paddle/function/CropOpTest.cpp @@ -28,8 +28,8 @@ TEST(Crop, real) { FunctionCompare compare( test_grad ? "CropGrad" : "Crop", FuncConfig() - .set>("crop_corner", {1, 1, 1}) - .set>("crop_shape", {2, 3, 3})); + .set>("crop_corner", {0, 1, 1, 1}) + .set>("crop_shape", {0, 2, 3, 3})); TensorShape inDims{numSamples, channels, imgSizeH, imgSizeW}; TensorShape outDims{numSamples, 2, 3, 3}; compare.addInputs( diff --git a/paddle/gserver/layers/CropLayer.cpp b/paddle/gserver/layers/CropLayer.cpp index ab23d4617e3..198ceffb463 100644 --- a/paddle/gserver/layers/CropLayer.cpp +++ b/paddle/gserver/layers/CropLayer.cpp @@ -25,20 +25,57 @@ bool CropLayer::init(const LayerMap& layerMap, Layer::init(layerMap, parameterMap); auto& crop_conf = config_.inputs(0).crop_conf(); - auto& img_conf = crop_conf.image_conf(); - CHECK_EQ(config_.inputs_size(), 1); - inDims_ = TensorShape( - {0, - img_conf.channels(), - img_conf.has_img_size_y() ? img_conf.img_size_y() : img_conf.img_size(), - img_conf.img_size()}); - - crop_corner_ = {crop_conf.crop_corner(0), - crop_conf.crop_corner(1), - crop_conf.crop_corner(2)}; - crop_shape_ = {crop_conf.crop_shape(0), - crop_conf.crop_shape(1), - crop_conf.crop_shape(2)}; + crop_axis_ = crop_conf.axis(); + for (int i = 0; i < crop_conf.offset_size(); i++) { + crop_offsets_[i] = crop_conf.offset(i); + } + + // 1. get input_0 shape + auto& input0_img_conf = config_.inputs(0).image_conf(); + inDims_ = TensorShape({0, + input0_img_conf.channels(), + input0_img_conf.has_img_size_y() + ? input0_img_conf.img_size_y() + : input0_img_conf.img_size(), + input0_img_conf.img_size()}); + + // 2. get output shape from input_1 or crop shap conf + if (config_.inputs_size() == 2) { + auto& input1_img_conf = config_.inputs(1).image_conf(); + targetDims_ = TensorShape({0, + input1_img_conf.channels(), + input1_img_conf.has_img_size_y() + ? input1_img_conf.img_size_y() + : input1_img_conf.img_size(), + input1_img_conf.img_size()}); + } else { + targetDims_ = TensorShape({crop_conf.shape(0), + crop_conf.shape(1), + crop_conf.shape(2), + crop_conf.shape(3)}); + } + + // 3. get final crop shape + int dimSize = 4; + for (int i = 0; i < dimSize; i++) { + if (i >= crop_axis_) { + crop_shape_[i] = targetDims_[i]; + } else { + crop_shape_[i] = inDims_[i]; + } + } + + // 4. get final crop corner + crop_corner_ = {0, 0, 0, 0}; + for (int i = 0; i < dimSize; i++) { + if (i >= crop_axis_) { + if (crop_offsets_.size() > 1) { + crop_corner_[i] = crop_offsets_[i - crop_axis_]; + } else { + crop_corner_[i] = crop_offsets_[0]; + } + } + } outDims_ = TensorShape(4); setOutDims(0); @@ -58,7 +95,7 @@ bool CropLayer::init(const LayerMap& layerMap, } void CropLayer::setOutDims(const size_t batchSize) { - outDims_.reshape({batchSize, crop_shape_[0], crop_shape_[1], crop_shape_[2]}); + outDims_.reshape({batchSize, crop_shape_[1], crop_shape_[2], crop_shape_[3]}); } void CropLayer::setTensorDim(const size_t batchSize) { diff --git a/paddle/gserver/layers/CropLayer.h b/paddle/gserver/layers/CropLayer.h index 3ce89707caf..23cede1c3fe 100644 --- a/paddle/gserver/layers/CropLayer.h +++ b/paddle/gserver/layers/CropLayer.h @@ -19,9 +19,13 @@ limitations under the License. */ namespace paddle { /** - * \brief This layer crop inputs according to the specify corner and shape. - * The input and output is a 4D tensor. Cropping from the 2nd to - * the 4th dimenstion. + * \brief This layer crop input according to the specify conf. + * input_0: input to be cropped + * input_1: optional reference input + * axis: start dimension to be croped + * offset: offset of cropping in each dimension + * shape: if reference input layer was not setted, + * crop input as this shape conf */ class CropLayer : public Layer { public: @@ -38,9 +42,12 @@ protected: void setOutDims(const size_t batchSize); void setTensorDim(const size_t batchSize); + int32_t crop_axis_; + std::vector crop_offsets_; std::vector crop_corner_; std::vector crop_shape_; TensorShape inDims_; + TensorShape targetDims_; TensorShape outDims_; }; } // namespace paddle -- GitLab From 701827f59cb5727676818c2fffb2b07766528436 Mon Sep 17 00:00:00 2001 From: wanghaoshuang Date: Wed, 5 Jul 2017 00:53:32 +0800 Subject: [PATCH 1038/3256] Add grad test and python wrapper for crop layer --- paddle/function/CropOp.cpp | 2 +- paddle/function/CropOpTest.cpp | 2 +- paddle/gserver/layers/CropLayer.cpp | 23 ++++---- paddle/gserver/tests/CMakeLists.txt | 2 +- paddle/gserver/tests/test_LayerGrad.cpp | 28 ++++++++++ proto/ModelConfig.proto | 8 ++- python/paddle/trainer/config_parser.py | 45 ++++++++++++++++ .../paddle/trainer_config_helpers/layers.py | 54 +++++++++++++++++++ 8 files changed, 147 insertions(+), 17 deletions(-) diff --git a/paddle/function/CropOp.cpp b/paddle/function/CropOp.cpp index 0d511ceef5e..1bb194a9bc8 100644 --- a/paddle/function/CropOp.cpp +++ b/paddle/function/CropOp.cpp @@ -148,7 +148,7 @@ public: void calc(const BufferArgs& inputs, const BufferArgs& outputs) override { CHECK_EQ(1UL, inputs.size()); CHECK_EQ(1UL, outputs.size()); - CHECK_EQ(outputs[0].getArgType(), ASSIGN_TO); + CHECK_EQ(outputs[0].getArgType(), ADD_TO); TensorShape outShape = outputs[0].shape(); diff --git a/paddle/function/CropOpTest.cpp b/paddle/function/CropOpTest.cpp index c331a70d1f6..71d9b058128 100644 --- a/paddle/function/CropOpTest.cpp +++ b/paddle/function/CropOpTest.cpp @@ -25,7 +25,7 @@ TEST(Crop, real) { VLOG(3) << " numSamples=" << numSamples << " channels=" << channels << " imgSizeH=" << imgSizeH << " imgSizeW=" << imgSizeW; for (bool test_grad : {false, true}) { - FunctionCompare compare( + CpuGpuFuncCompare compare( test_grad ? "CropGrad" : "Crop", FuncConfig() .set>("crop_corner", {0, 1, 1, 1}) diff --git a/paddle/gserver/layers/CropLayer.cpp b/paddle/gserver/layers/CropLayer.cpp index 198ceffb463..b2fa17b400c 100644 --- a/paddle/gserver/layers/CropLayer.cpp +++ b/paddle/gserver/layers/CropLayer.cpp @@ -14,7 +14,6 @@ limitations under the License. */ #include "CropLayer.h" #include "paddle/utils/Stat.h" - namespace paddle { REGISTER_LAYER(crop, CropLayer); @@ -24,10 +23,9 @@ bool CropLayer::init(const LayerMap& layerMap, /* Initialize the basic parent class */ Layer::init(layerMap, parameterMap); - auto& crop_conf = config_.inputs(0).crop_conf(); - crop_axis_ = crop_conf.axis(); - for (int i = 0; i < crop_conf.offset_size(); i++) { - crop_offsets_[i] = crop_conf.offset(i); + crop_axis_ = config_.axis(); + for (int i = 0; i < config_.offset_size(); i++) { + crop_offsets_.push_back(config_.offset(i)); } // 1. get input_0 shape @@ -38,7 +36,6 @@ bool CropLayer::init(const LayerMap& layerMap, ? input0_img_conf.img_size_y() : input0_img_conf.img_size(), input0_img_conf.img_size()}); - // 2. get output shape from input_1 or crop shap conf if (config_.inputs_size() == 2) { auto& input1_img_conf = config_.inputs(1).image_conf(); @@ -49,19 +46,19 @@ bool CropLayer::init(const LayerMap& layerMap, : input1_img_conf.img_size(), input1_img_conf.img_size()}); } else { - targetDims_ = TensorShape({crop_conf.shape(0), - crop_conf.shape(1), - crop_conf.shape(2), - crop_conf.shape(3)}); + targetDims_ = TensorShape({config_.shape(0), + config_.shape(1), + config_.shape(2), + config_.shape(3)}); } // 3. get final crop shape int dimSize = 4; for (int i = 0; i < dimSize; i++) { if (i >= crop_axis_) { - crop_shape_[i] = targetDims_[i]; + crop_shape_.push_back(targetDims_[i]); } else { - crop_shape_[i] = inDims_[i]; + crop_shape_.push_back(inDims_[i]); } } @@ -99,7 +96,7 @@ void CropLayer::setOutDims(const size_t batchSize) { } void CropLayer::setTensorDim(const size_t batchSize) { - CHECK_EQ(static_cast(inputLayers_.size()), 1); + CHECK_EQ(static_cast(inputLayers_.size()), 2); inDims_.setDim(0, batchSize); int h = inputLayers_[0]->getOutput().getFrameHeight(); if (h != 0) inDims_.setDim(2, h); diff --git a/paddle/gserver/tests/CMakeLists.txt b/paddle/gserver/tests/CMakeLists.txt index 92f6cbcfe5a..a43adc7ce7d 100644 --- a/paddle/gserver/tests/CMakeLists.txt +++ b/paddle/gserver/tests/CMakeLists.txt @@ -56,7 +56,7 @@ add_test(NAME test_DetectionOutput add_unittest_without_exec(test_ConvUnify test_ConvUnify.cpp LayerGradUtil.cpp) - + add_test(NAME test_ConvUnify COMMAND test_ConvUnify) ################# test_BatchNorm ####################### diff --git a/paddle/gserver/tests/test_LayerGrad.cpp b/paddle/gserver/tests/test_LayerGrad.cpp index 59d1e9273d4..20a83d7aa12 100644 --- a/paddle/gserver/tests/test_LayerGrad.cpp +++ b/paddle/gserver/tests/test_LayerGrad.cpp @@ -1792,6 +1792,34 @@ TEST(Layer, RowConvLayer) { } } +TEST(Layer, CropLayer) { + TestConfig config; + // config input_0 + config.inputDefs.push_back({INPUT_DATA, "layer_0", 1024, 0}); + LayerInputConfig* input = config.layerConfig.add_inputs(); + ImageConfig* img = input->mutable_image_conf(); + img->set_channels(4); + img->set_img_size(16); + config.layerConfig.set_axis(2); + config.layerConfig.add_offset(0); + config.layerConfig.add_offset(0); + + // config input_1 + config.inputDefs.push_back({INPUT_DATA, "layer_1", 128, 0}); + input = config.layerConfig.add_inputs(); + img = input->mutable_image_conf(); + img->set_channels(2); + img->set_img_size(8); + + // config crop layer + config.layerConfig.set_type("crop"); + config.layerConfig.set_name("cropLayer"); + + for (auto useGpu : {false, true}) { + testLayerGrad(config, "crop", 100, false, useGpu, false); + } +} + int main(int argc, char** argv) { testing::InitGoogleTest(&argc, argv); initMain(argc, argv); diff --git a/proto/ModelConfig.proto b/proto/ModelConfig.proto index 37cd16c7989..83f72c137bd 100644 --- a/proto/ModelConfig.proto +++ b/proto/ModelConfig.proto @@ -472,10 +472,16 @@ message LayerConfig { // blank label used in ctc loss optional uint32 blank = 52 [default = 0]; - // stride parameter for seqlastins layer, AverageLayer, MaxLayer, which + // stride parameter for seqlastins layer, AverageLayer, MaxLayer, which // controls the scope of pooling operation. can be set > 0. // leave empty or set to -1 to disable this stride pooling. optional int32 seq_pool_stride = 53 [default = -1]; + + // for crop layer + optional int32 axis = 54 [default = 2]; + repeated uint32 offset = 55; + repeated uint32 shape = 56; + } message EvaluatorConfig { diff --git a/python/paddle/trainer/config_parser.py b/python/paddle/trainer/config_parser.py index 370529ed97b..8c529fdfd3e 100644 --- a/python/paddle/trainer/config_parser.py +++ b/python/paddle/trainer/config_parser.py @@ -1986,6 +1986,51 @@ class PadLayer(LayerBase): self.config.size = out_ch * out_h * out_w +@config_layer('crop') +class CropLayer(LayerBase): + def __init__(self, inputs, axis, offset, shape, name, **xargs): + super(CropLayer, self).__init__(name, 'crop', 0, inputs=inputs, **xargs) + self.conf.axis = axis + self.conf.axis = offset + self.conf.axis = shape + + crop = self.inputs[0].crop + self.config.inputs[0].crop_conf.axis = crop.axis + self.config.inputs[0].crop_conf.offset.extend(crop.offset) + self.config.inputs[0].crop_conf.shape.extend(crop.shape) + + # get channel, width and height from input_0 layer + input_layer = self.get_input_layer(0) + image_conf = self.config.inputs[0].image_conf + image_conf.img_size = input_layer.width + image_conf.img_size_y = input_layer.height + image_conf.channels = input_layer.size / (input_layer.width * + input_layer.height) + out_ch = image_conf.channels + out_h = image_conf.img_size + out_w = image_conf.img_size_y + if len(self.inputs) == 2: + # get channels, width and height from input_1 layer + input_layer = self.get_input_layer(1) + image_conf = self.config.inputs[1].image_conf + image_conf.img_size = input_layer.width + image_conf.img_size_y = input_layer.height + image_conf.channels = input_layer.size / (input_layer.width * + input_layer.height) + out_ch = image_conf.channels + out_h = image_conf.img_size_y + out_w = image_conf.img_size + else: + # set channels, width and heigth of current layer + if len(shape) > 2: + out_ch = shape[-3] + if len(shape) > 1: + out_h = shape[-2] + if len(shape) > 0: + out_w = shape[-1] + self.set_cnn_layer(name, out_h, out_w, out_ch) + + @config_layer('batch_norm') class BatchNormLayer(LayerBase): layer_type = 'batch_norm' diff --git a/python/paddle/trainer_config_helpers/layers.py b/python/paddle/trainer_config_helpers/layers.py index 206de1f8e1c..f9de086cba7 100755 --- a/python/paddle/trainer_config_helpers/layers.py +++ b/python/paddle/trainer_config_helpers/layers.py @@ -217,6 +217,7 @@ class LayerType(object): SMOOTH_L1 = 'smooth_l1' PRELU = 'prelu' + CROP_LAYER = 'crop' @staticmethod def is_layer_type(type_name): @@ -5853,3 +5854,56 @@ def prelu_layer(input, layer_type=LayerType.PRELU, parents=input, size=l.config.size) + + +@wrap_name_default() +@layer_support() +def crop_layer(input, axis, offset, shape=None, name=None, layer_attr=None): + """ + The crop layer crop images by offset and shape. User can set crop shape by + args 'shape' explicitly or by reference input layer. + + + The example usage is: + + .. code-block:: python + + crop = crop_layer(input=[image_input, reference_input], axis=2, offset=[2, 3]) + + :param input: The input layer.If two inputs were setted, + the second input will be regarded as reference input + :type input: LayerOutput or Sequence + :param axis: start axis to be cropped. To image input layer: + - 0: batch size + - 1: channels + - 2: height + - 3: width + :type partial_sum: int + :param offset: The crop offset + :type offset: Sequence + :param shape: The shape to be cropped. Default is None. + :type shape: Sqquence | None + :param name: Name of this layer. + :type name: basestring + :return: LayerOutput object. + :rtype: LayerOutput + """ + if isinstance(input, LayerOutput): + input = [input] + elif isinstance(input, Projection): + input = [input] + else: + assert isinstance(input, collections.Sequence) + l = Layer( + inputs=[x.name for x in input], + axis=axis, + offset=offset, + shape=shape, + name=name, + type=LayerType.CROP_LAYER, + **ExtraLayerAttribute.to_kwargs(layer_attr)) + return LayerOutput( + name=name, + layer_type=LayerType.CROP_LAYER, + parents=input, + size=l.config.size) -- GitLab From cbd61c7719b148043f4b8a4f3feacca57c17f1ab Mon Sep 17 00:00:00 2001 From: wanghaoshuang Date: Wed, 5 Jul 2017 10:36:22 +0800 Subject: [PATCH 1039/3256] fix crop function test --- paddle/function/CropOpTest.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/paddle/function/CropOpTest.cpp b/paddle/function/CropOpTest.cpp index 71d9b058128..dcba972e106 100644 --- a/paddle/function/CropOpTest.cpp +++ b/paddle/function/CropOpTest.cpp @@ -34,8 +34,10 @@ TEST(Crop, real) { TensorShape outDims{numSamples, 2, 3, 3}; compare.addInputs( BufferArg(VALUE_TYPE_FLOAT, test_grad ? outDims : inDims)); - compare.addOutputs(BufferArg( - VALUE_TYPE_FLOAT, test_grad ? inDims : outDims, ASSIGN_TO)); + compare.addOutputs(BufferArg(VALUE_TYPE_FLOAT, + test_grad ? inDims : outDims, + tes_grad ? ADD_TO : ASSIGN_TO), + test_grad ? ADD_TO : ASSIGN_TO); compare.run(); } } -- GitLab From 568c03ba1d311ac2af2cb9242cefb00537174e50 Mon Sep 17 00:00:00 2001 From: Superjom Date: Wed, 5 Jul 2017 10:51:47 +0800 Subject: [PATCH 1040/3256] add virtual implementation --- paddle/framework/net.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/paddle/framework/net.h b/paddle/framework/net.h index e60356dc172..2025bfa4b23 100644 --- a/paddle/framework/net.h +++ b/paddle/framework/net.h @@ -91,6 +91,8 @@ class Net { * @brief Create a network. */ static std::unique_ptr Create(const NetDesc &def = NetDesc()); + + virtual ~Net() {} }; /** @@ -140,6 +142,8 @@ class PlainNet : public Net { */ virtual void AddBackwardOps() override; + virtual ~PlainNet() override {} + protected: /** * @brief Build the network. -- GitLab From e10040ca8a9b4b9d9eb8275cab468edefd94caf9 Mon Sep 17 00:00:00 2001 From: wanghaoshuang Date: Fri, 16 Jun 2017 19:02:46 +0800 Subject: [PATCH 1041/3256] add crop layer --- paddle/function/CMakeLists.txt | 1 + paddle/function/CropOp.cpp | 177 ++++++++++++++++++++++++++++ paddle/function/CropOp.h | 56 +++++++++ paddle/function/CropOpGpu.cu | 109 +++++++++++++++++ paddle/function/CropOpTest.cpp | 47 ++++++++ paddle/gserver/layers/CropLayer.cpp | 101 ++++++++++++++++ paddle/gserver/layers/CropLayer.h | 46 ++++++++ 7 files changed, 537 insertions(+) create mode 100644 paddle/function/CropOp.cpp create mode 100644 paddle/function/CropOp.h create mode 100644 paddle/function/CropOpGpu.cu create mode 100644 paddle/function/CropOpTest.cpp create mode 100644 paddle/gserver/layers/CropLayer.cpp create mode 100644 paddle/gserver/layers/CropLayer.h diff --git a/paddle/function/CMakeLists.txt b/paddle/function/CMakeLists.txt index 1518a8a654c..f19a1eb7774 100644 --- a/paddle/function/CMakeLists.txt +++ b/paddle/function/CMakeLists.txt @@ -37,6 +37,7 @@ if(WITH_GPU) add_simple_unittest(MulOpTest) add_simple_unittest(CosSimOpTest) add_simple_unittest(RowConvOpTest) + add_simple_unittest(CropOpTest) endif() add_simple_unittest(ConvOpTest) diff --git a/paddle/function/CropOp.cpp b/paddle/function/CropOp.cpp new file mode 100644 index 00000000000..4d47d9c149c --- /dev/null +++ b/paddle/function/CropOp.cpp @@ -0,0 +1,177 @@ +/* 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 "CropOp.h" +#include "paddle/math/Vector.h" +#include "paddle/function/TensorShape.h" +namespace paddle { + +static inline CropConf castToCropConf(const FuncConfig& conf) { + return {conf.get>("crop_corner"), + conf.get>("crop_shape")}; +} + +template <> +void Crop(real* outputs, + const real* inputs, + const TensorShape inShape, + const CropConf& crop) { + int cCrop = crop.corner[0]; + int hCrop = crop.corner[1]; + int wCrop = crop.corner[2]; + + int num = inShape[0]; + int inC = inShape[1]; + int inH = inShape[2]; + int inW = inShape[3]; + + int outC = crop.shape[0]; + int outH = crop.shape[1]; + int outW = crop.shape[2]; + + for (int n = 0; n < num; n++) { + for (int c = 0; c < outC; c++) { + for (int h = 0; h < outH; h++) { + int outoff = ((n * outC + c) * outH + h) * outW; + int inoff = ((n * inC + c + cCrop) * inH + h + hCrop) * inW + wCrop; + memcpy(outputs + outoff, inputs + inoff, outW * sizeof(real)); + } + } + } +} + +template <> +void CropGrad(const real* inGrad, + real* outGrad, + const TensorShape outShape, + const CropConf& crop) { + int cCrop = crop.corner[0]; + int hCrop = crop.corner[1]; + int wCrop = crop.corner[2]; + + int num = outShape[0]; + int outC = outShape[1]; + int outH = outShape[2]; + int outW = outShape[3]; + + int inC = crop.shape[0]; + int inH = crop.shape[1]; + int inW = crop.shape[2]; + + for (int n = 0; n < num; n++) { + for (int c = 0; c < inC; c++) { + for (int h = 0; h < inH; h++) { + int outoff = ((n * outC + c + cCrop) * outH + h + hCrop) * outW + wCrop; + int inoff = ((n * inC + c) * inH + h) * inW; + CpuVector inG = CpuVector(inW, const_cast(inGrad + inoff)); + CpuVector outG = CpuVector(inW, outGrad + outoff); + outG += inG; + } + } + } +} + +/** + * \brief Crop input according to the specify corner and shape. + * The input and output is a 4D tensor. In CropFunc, we only + * crop the 2nd to 4th dimension. + * + * Argument in this Function: + * \param pad_ A struct object contains the cropping corner and shape. + * \param inputs A 4D tensor, only one input. + * \param outputs A 4D tensor, the output value after cropping. + * + * For example, + * Input(2,2,2,3) = [ + * [ [[1,2,3], [3,4,5]], + * [[2,3,5], [1,6,7]] ], + * [ [[4,3,1], [1,8,7]], + * [[3,8,9], [2,3,5]] ] + * ] # the input shape is (2,2,2,3) + * + * pad_: if corner = (0,1,1) and crop_shape = (2,1,2) + * Output(2,2,1,2) = [ + * [ [[4,5]], + * [[6,7]] ], + * [ [[8,7]], + * [[3,5]] ] + * ] # the input shape is (2,2,2,3) + */ +template +class CropFunc : public FunctionBase { +public: + void init(const FuncConfig& config) override { + crop_ = castToCropConf(config); + } + + void calc(const BufferArgs& inputs, const BufferArgs& outputs) override { + CHECK_EQ(1UL, inputs.size()); + CHECK_EQ(1UL, outputs.size()); + CHECK_EQ(outputs[0].shape()[1], crop_.shape[0]); + CHECK_EQ(outputs[0].shape()[2], crop_.shape[1]); + CHECK_EQ(outputs[0].shape()[3], crop_.shape[2]); + CHECK_EQ(outputs[0].getArgType(), ASSIGN_TO); + + TensorShape inShape = inputs[0].shape(); + + Crop( + outputs[0].data(), inputs[0].data(), inShape, crop_); + } + +private: + CropConf crop_; +}; + +/** + * \brief The backward propagation of cropping Function. + * + * Argument in this Function: + * \param crop_ The same meaning as it in CropFunc. + * \param inputs The gradient with respect to the output value of CropFunc. + * \param outputs The gradient with respect to the input value of CropFunc. + */ + +template +class CropGradFunc : public FunctionBase { +public: + void init(const FuncConfig& config) override { + crop_ = castToCropConf(config); + } + + void calc(const BufferArgs& inputs, const BufferArgs& outputs) override { + CHECK_EQ(1UL, inputs.size()); + CHECK_EQ(1UL, outputs.size()); + CHECK_EQ(inputs[0].shape()[1], crop_.shape[0]); + CHECK_EQ(inputs[0].shape()[2], crop_.shape[1]); + CHECK_EQ(inputs[0].shape()[3], crop_.shape[2]); + CHECK_EQ(outputs[0].getArgType(), ASSIGN_TO); + + TensorShape outShape = outputs[0].shape(); + + CropGrad( + inputs[0].data(), outputs[0].data(), outShape, crop_); + } + +private: + CropConf crop_; +}; + +REGISTER_TYPED_FUNC(Crop, CPU, CropFunc); +REGISTER_TYPED_FUNC(CropGrad, CPU, CropGradFunc); +#ifndef PADDLE_ONLY_CPU +REGISTER_TYPED_FUNC(Crop, GPU, CropFunc); +REGISTER_TYPED_FUNC(CropGrad, GPU, CropGradFunc); +#endif + +} // namespace paddle diff --git a/paddle/function/CropOp.h b/paddle/function/CropOp.h new file mode 100644 index 00000000000..78a55bd43e9 --- /dev/null +++ b/paddle/function/CropOp.h @@ -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. */ + +#pragma once + +#include "Function.h" + +namespace paddle { + +struct CropConf { + /// The upper left corner of croped result + std::vector corner; + /// The shape of croped result + std::vector shape; +}; + +/** + * \brief This funtion crops inputs according to the specify start point and + *shape. + * + * \param[out] outputs save results. + * \param[in] inputs input data. + * \param[in] inShape the shape of input tensor. + * \param[in] crop the cropping config + */ +template +void Crop(real* outputs, + const real* inputs, + const TensorShape inShape, + const CropConf& crop); + +/** + * \brief Cropping operation backward. + * + * \param[out] inGrad gradients of previous layer + * \param[in] outGrad output gradient + * \param[in] inShape the shape of input tensor. + * \param[in] crop the cropping config + */ +template +void CropGrad(const real* inGrad, + real* outGrad, + const TensorShape inShape, + const CropConf& crop); +} // namespace paddle diff --git a/paddle/function/CropOpGpu.cu b/paddle/function/CropOpGpu.cu new file mode 100644 index 00000000000..f7d7d03abd2 --- /dev/null +++ b/paddle/function/CropOpGpu.cu @@ -0,0 +1,109 @@ +/* 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 "hl_base.h" +#include "CropOp.h" + +namespace paddle { + +__global__ void KeCrop(real* outputs, const real* inputs, + int inC, int inH, int inW, + int cropC, int cropH, int cropW, + int outC, int outH, int outW, int nthreads) { + const int idx = threadIdx.x + blockIdx.x * blockDim.x; + if (idx < nthreads) { + const int w = idx % outW; + const int h = (idx / outW) % outH; + const int c = (idx / outW / outH) % outC; + const int n = idx / outW / outH / outC; + + const int off = ((n * inC + c + cropC) * inH + h + cropH) * inW + cropW + w; + outputs[idx] = inputs[off]; + } +} + +template <> +void Crop(real* outputs, + const real* inputs, + const TensorShape inShape, + const CropConf& crop) { + int cropC = crop.corner[0]; + int cropH = crop.corner[1]; + int cropW = crop.corner[2]; + + int num = inShape[0]; + int inC = inShape[1]; + int inH = inShape[2]; + int inW = inShape[3]; + + int outC = crop.shape[0]; + int outH = crop.shape[1]; + int outW = crop.shape[2]; + + size_t nth = num * outC * outH * outW; + int blockSize = 1024; + int gridSize = (nth + blockSize - 1) / blockSize; + + KeCrop<<>> + (outputs, inputs, inC, inH, inW, cropC, cropH, cropW, + outC, outH, outW, nth); + CHECK_SYNC("Crop"); +} + +__global__ void KeCropDiff(const real* inGrad, real* outGrad, + int inC, int inH, int inW, + int cropC, int cropH, int cropW, + int outC, int outH, int outW, int nthreads) { + const int idx = threadIdx.x + blockIdx.x * blockDim.x; + if (idx < nthreads) { + const int w = idx % inW; + const int h = (idx / inW) % inH; + const int c = (idx / inW / inH) % inC; + const int n = idx / inW / inH / inC; + + const int off = ((n * outC + c + cropC) * outH + h + cropH) * outW + cropW + w; + + outGrad[off] += inGrad[idx]; + } +} + +template <> +void CropGrad(const real* inGrad, + real* outGrad, + const TensorShape outShape, + const CropConf& crop) { + int cropC = crop.corner[0]; + int cropH = crop.corner[1]; + int cropW = crop.corner[2]; + + int num = outShape[0]; + int outC = outShape[1]; + int outH = outShape[2]; + int outW = outShape[3]; + + int inC = crop.shape[0]; + int inH = crop.shape[1]; + int inW = crop.shape[2]; + + size_t nth = num * inC * inH * inW; + int blockSize = 1024; + int gridSize = (nth + blockSize - 1) / blockSize; + + KeCropDiff <<>> + (inGrad, outGrad, inC, inH, inW, cropC, cropH, cropW, + outC, outH, outW, nth); + CHECK_SYNC("CropGrad"); +} + +} // namespace paddle diff --git a/paddle/function/CropOpTest.cpp b/paddle/function/CropOpTest.cpp new file mode 100644 index 00000000000..62b4bd9fdea --- /dev/null +++ b/paddle/function/CropOpTest.cpp @@ -0,0 +1,47 @@ +/* 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 "FunctionTest.h" + +namespace paddle { + +TEST(Crop, real) { + for (size_t numSamples : {5, 32}) { + for (size_t channels : {5, 5, 32}) { + for (size_t imgSizeH : {5, 33, 100}) { + for (size_t imgSizeW : {5, 32, 96}) { + VLOG(3) << " numSamples=" << numSamples << " channels=" << channels + << " imgSizeH=" << imgSizeH << " imgSizeW=" << imgSizeW; + for (bool test_grad : {false, true}) { + FunctionCompare compare( + test_grad ? "CropGrad" : "Crop", + FuncConfig() + .set>("crop_corner", {1, 1, 1}) + .set>("crop_shape", {2, 3, 3})); + TensorShape inDims{numSamples, channels, imgSizeH, imgSizeW}; + TensorShape outDims{numSamples, 2, 3, 3}; + compare.addInputs( + BufferArg(VALUE_TYPE_FLOAT, test_grad ? outDims : inDims)); + compare.addOutputs(BufferArg( + VALUE_TYPE_FLOAT, test_grad ? inDims : outDims, ASSIGN_TO)); + compare.run(); + } + } + } + } + } +} + +} // namespace paddle diff --git a/paddle/gserver/layers/CropLayer.cpp b/paddle/gserver/layers/CropLayer.cpp new file mode 100644 index 00000000000..ab23d4617e3 --- /dev/null +++ b/paddle/gserver/layers/CropLayer.cpp @@ -0,0 +1,101 @@ +/* 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 "CropLayer.h" +#include "paddle/utils/Stat.h" + +namespace paddle { + +REGISTER_LAYER(crop, CropLayer); + +bool CropLayer::init(const LayerMap& layerMap, + const ParameterMap& parameterMap) { + /* Initialize the basic parent class */ + Layer::init(layerMap, parameterMap); + + auto& crop_conf = config_.inputs(0).crop_conf(); + auto& img_conf = crop_conf.image_conf(); + CHECK_EQ(config_.inputs_size(), 1); + inDims_ = TensorShape( + {0, + img_conf.channels(), + img_conf.has_img_size_y() ? img_conf.img_size_y() : img_conf.img_size(), + img_conf.img_size()}); + + crop_corner_ = {crop_conf.crop_corner(0), + crop_conf.crop_corner(1), + crop_conf.crop_corner(2)}; + crop_shape_ = {crop_conf.crop_shape(0), + crop_conf.crop_shape(1), + crop_conf.crop_shape(2)}; + + outDims_ = TensorShape(4); + setOutDims(0); + + createFunction(forward_, + "Crop", + FuncConfig() + .set("crop_corner", crop_corner_) + .set("crop_shape", crop_shape_)); + createFunction(backward_, + "CropGrad", + FuncConfig() + .set("crop_corner", crop_corner_) + .set("crop_shape", crop_shape_)); + + return true; +} + +void CropLayer::setOutDims(const size_t batchSize) { + outDims_.reshape({batchSize, crop_shape_[0], crop_shape_[1], crop_shape_[2]}); +} + +void CropLayer::setTensorDim(const size_t batchSize) { + CHECK_EQ(static_cast(inputLayers_.size()), 1); + inDims_.setDim(0, batchSize); + int h = inputLayers_[0]->getOutput().getFrameHeight(); + if (h != 0) inDims_.setDim(2, h); + int w = inputLayers_[0]->getOutput().getFrameWidth(); + if (w != 0) inDims_.setDim(3, w); + setOutDims(batchSize); +} + +void CropLayer::forward(PassType passType) { + Layer::forward(passType); + MatrixPtr input = inputLayers_[0]->getOutputValue(); + size_t batchSize = input->getHeight(); + setTensorDim(batchSize); + int size = outDims_[1] * outDims_[2] * outDims_[3]; + resetOutput(batchSize, size); + MatrixPtr outV = getOutputValue(); + REGISTER_TIMER_INFO("CropForward", getName().c_str()); + + BufferArgs inputs; + BufferArgs outputs; + inputs.addArg(*getInputValue(0), inDims_); + outputs.addArg(*getOutputValue(), outDims_, ASSIGN_TO); + forward_[0]->calc(inputs, outputs); +} + +void CropLayer::backward(const UpdateCallback& callback) { + (void)callback; + REGISTER_TIMER_INFO("CropBackward", getName().c_str()); + + BufferArgs inputs; + BufferArgs outputs; + inputs.addArg(*getOutputGrad(), outDims_); + outputs.addArg(*getInputGrad(0), inDims_, ADD_TO); + backward_[0]->calc(inputs, outputs); +} +} // namespace paddle diff --git a/paddle/gserver/layers/CropLayer.h b/paddle/gserver/layers/CropLayer.h new file mode 100644 index 00000000000..3ce89707caf --- /dev/null +++ b/paddle/gserver/layers/CropLayer.h @@ -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. */ + +#pragma once + +#include "Layer.h" + +namespace paddle { + +/** + * \brief This layer crop inputs according to the specify corner and shape. + * The input and output is a 4D tensor. Cropping from the 2nd to + * the 4th dimenstion. + */ +class CropLayer : public Layer { +public: + explicit CropLayer(const LayerConfig& config) : Layer(config) {} + + ~CropLayer() {} + + bool init(const LayerMap& layerMap, + const ParameterMap& parameterMap) override; + void forward(PassType passType) override; + void backward(const UpdateCallback& callback = nullptr) override; + +protected: + void setOutDims(const size_t batchSize); + void setTensorDim(const size_t batchSize); + + std::vector crop_corner_; + std::vector crop_shape_; + TensorShape inDims_; + TensorShape outDims_; +}; +} // namespace paddle -- GitLab From d1d70ec8319a55964231f2e925ef8cb881c94497 Mon Sep 17 00:00:00 2001 From: wanghaoshuang Date: Thu, 22 Jun 2017 16:54:07 +0800 Subject: [PATCH 1042/3256] Refine configure option of crop layer 1. change configure content to 'axis, offset, shape' 2. add an optional input to crop layer as cropping reference --- paddle/function/CropOp.cpp | 63 ++++++++++++--------------- paddle/function/CropOp.h | 15 ++----- paddle/function/CropOpGpu.cu | 32 ++++++++------ paddle/function/CropOpTest.cpp | 4 +- paddle/gserver/layers/CropLayer.cpp | 67 ++++++++++++++++++++++------- paddle/gserver/layers/CropLayer.h | 13 ++++-- 6 files changed, 114 insertions(+), 80 deletions(-) diff --git a/paddle/function/CropOp.cpp b/paddle/function/CropOp.cpp index 4d47d9c149c..0d511ceef5e 100644 --- a/paddle/function/CropOp.cpp +++ b/paddle/function/CropOp.cpp @@ -17,28 +17,27 @@ limitations under the License. */ #include "paddle/function/TensorShape.h" namespace paddle { -static inline CropConf castToCropConf(const FuncConfig& conf) { - return {conf.get>("crop_corner"), - conf.get>("crop_shape")}; -} - template <> void Crop(real* outputs, const real* inputs, const TensorShape inShape, - const CropConf& crop) { - int cCrop = crop.corner[0]; - int hCrop = crop.corner[1]; - int wCrop = crop.corner[2]; + const FuncConfig& conf) { + std::vector crop_corner = + conf.get>("crop_corner"); + std::vector crop_shape = + conf.get>("crop_shape"); + int cCrop = crop_corner[1]; + int hCrop = crop_corner[2]; + int wCrop = crop_corner[3]; int num = inShape[0]; int inC = inShape[1]; int inH = inShape[2]; int inW = inShape[3]; - int outC = crop.shape[0]; - int outH = crop.shape[1]; - int outW = crop.shape[2]; + int outC = crop_shape[1]; + int outH = crop_shape[2]; + int outW = crop_shape[3]; for (int n = 0; n < num; n++) { for (int c = 0; c < outC; c++) { @@ -55,19 +54,23 @@ template <> void CropGrad(const real* inGrad, real* outGrad, const TensorShape outShape, - const CropConf& crop) { - int cCrop = crop.corner[0]; - int hCrop = crop.corner[1]; - int wCrop = crop.corner[2]; + const FuncConfig& conf) { + std::vector crop_corner = + conf.get>("crop_corner"); + std::vector crop_shape = + conf.get>("crop_shape"); + int cCrop = crop_corner[1]; + int hCrop = crop_corner[2]; + int wCrop = crop_corner[3]; int num = outShape[0]; int outC = outShape[1]; int outH = outShape[2]; int outW = outShape[3]; - int inC = crop.shape[0]; - int inH = crop.shape[1]; - int inW = crop.shape[2]; + int inC = crop_shape[1]; + int inH = crop_shape[2]; + int inW = crop_shape[3]; for (int n = 0; n < num; n++) { for (int c = 0; c < inC; c++) { @@ -111,26 +114,21 @@ void CropGrad(const real* inGrad, template class CropFunc : public FunctionBase { public: - void init(const FuncConfig& config) override { - crop_ = castToCropConf(config); - } + void init(const FuncConfig& config) override { conf_ = config; } void calc(const BufferArgs& inputs, const BufferArgs& outputs) override { CHECK_EQ(1UL, inputs.size()); CHECK_EQ(1UL, outputs.size()); - CHECK_EQ(outputs[0].shape()[1], crop_.shape[0]); - CHECK_EQ(outputs[0].shape()[2], crop_.shape[1]); - CHECK_EQ(outputs[0].shape()[3], crop_.shape[2]); CHECK_EQ(outputs[0].getArgType(), ASSIGN_TO); TensorShape inShape = inputs[0].shape(); Crop( - outputs[0].data(), inputs[0].data(), inShape, crop_); + outputs[0].data(), inputs[0].data(), inShape, conf_); } private: - CropConf crop_; + FuncConfig conf_; }; /** @@ -145,26 +143,21 @@ private: template class CropGradFunc : public FunctionBase { public: - void init(const FuncConfig& config) override { - crop_ = castToCropConf(config); - } + void init(const FuncConfig& config) override { conf_ = config; } void calc(const BufferArgs& inputs, const BufferArgs& outputs) override { CHECK_EQ(1UL, inputs.size()); CHECK_EQ(1UL, outputs.size()); - CHECK_EQ(inputs[0].shape()[1], crop_.shape[0]); - CHECK_EQ(inputs[0].shape()[2], crop_.shape[1]); - CHECK_EQ(inputs[0].shape()[3], crop_.shape[2]); CHECK_EQ(outputs[0].getArgType(), ASSIGN_TO); TensorShape outShape = outputs[0].shape(); CropGrad( - inputs[0].data(), outputs[0].data(), outShape, crop_); + inputs[0].data(), outputs[0].data(), outShape, conf_); } private: - CropConf crop_; + FuncConfig conf_; }; REGISTER_TYPED_FUNC(Crop, CPU, CropFunc); diff --git a/paddle/function/CropOp.h b/paddle/function/CropOp.h index 78a55bd43e9..71e8c4c00ee 100644 --- a/paddle/function/CropOp.h +++ b/paddle/function/CropOp.h @@ -18,13 +18,6 @@ limitations under the License. */ namespace paddle { -struct CropConf { - /// The upper left corner of croped result - std::vector corner; - /// The shape of croped result - std::vector shape; -}; - /** * \brief This funtion crops inputs according to the specify start point and *shape. @@ -32,13 +25,13 @@ struct CropConf { * \param[out] outputs save results. * \param[in] inputs input data. * \param[in] inShape the shape of input tensor. - * \param[in] crop the cropping config + * \param[in] conf the cropping config */ template void Crop(real* outputs, const real* inputs, const TensorShape inShape, - const CropConf& crop); + const FuncConfig& conf); /** * \brief Cropping operation backward. @@ -46,11 +39,11 @@ void Crop(real* outputs, * \param[out] inGrad gradients of previous layer * \param[in] outGrad output gradient * \param[in] inShape the shape of input tensor. - * \param[in] crop the cropping config + * \param[in] conf the cropping config */ template void CropGrad(const real* inGrad, real* outGrad, const TensorShape inShape, - const CropConf& crop); + const FuncConfig& conf); } // namespace paddle diff --git a/paddle/function/CropOpGpu.cu b/paddle/function/CropOpGpu.cu index f7d7d03abd2..cadb58b6e99 100644 --- a/paddle/function/CropOpGpu.cu +++ b/paddle/function/CropOpGpu.cu @@ -37,19 +37,21 @@ template <> void Crop(real* outputs, const real* inputs, const TensorShape inShape, - const CropConf& crop) { - int cropC = crop.corner[0]; - int cropH = crop.corner[1]; - int cropW = crop.corner[2]; + const FuncConfig& conf) { + std::vector crop_corner = conf.get>("crop_corner"); + std::vector crop_shape = conf.get>("crop_shape"); + int cropC = crop_corner[1]; + int cropH = crop_corner[2]; + int cropW = crop_corner[3]; int num = inShape[0]; int inC = inShape[1]; int inH = inShape[2]; int inW = inShape[3]; - int outC = crop.shape[0]; - int outH = crop.shape[1]; - int outW = crop.shape[2]; + int outC = crop_shape[1]; + int outH = crop_shape[2]; + int outW = crop_shape[3]; size_t nth = num * outC * outH * outW; int blockSize = 1024; @@ -82,19 +84,21 @@ template <> void CropGrad(const real* inGrad, real* outGrad, const TensorShape outShape, - const CropConf& crop) { - int cropC = crop.corner[0]; - int cropH = crop.corner[1]; - int cropW = crop.corner[2]; + const FuncConfig& conf) { + std::vector crop_corner = conf.get>("crop_corner"); + std::vector crop_shape = conf.get>("crop_shape"); + int cropC = crop_corner[1]; + int cropH = crop_corner[2]; + int cropW = crop_corner[3]; int num = outShape[0]; int outC = outShape[1]; int outH = outShape[2]; int outW = outShape[3]; - int inC = crop.shape[0]; - int inH = crop.shape[1]; - int inW = crop.shape[2]; + int inC = crop_shape[1]; + int inH = crop_shape[2]; + int inW = crop_shape[3]; size_t nth = num * inC * inH * inW; int blockSize = 1024; diff --git a/paddle/function/CropOpTest.cpp b/paddle/function/CropOpTest.cpp index 62b4bd9fdea..c331a70d1f6 100644 --- a/paddle/function/CropOpTest.cpp +++ b/paddle/function/CropOpTest.cpp @@ -28,8 +28,8 @@ TEST(Crop, real) { FunctionCompare compare( test_grad ? "CropGrad" : "Crop", FuncConfig() - .set>("crop_corner", {1, 1, 1}) - .set>("crop_shape", {2, 3, 3})); + .set>("crop_corner", {0, 1, 1, 1}) + .set>("crop_shape", {0, 2, 3, 3})); TensorShape inDims{numSamples, channels, imgSizeH, imgSizeW}; TensorShape outDims{numSamples, 2, 3, 3}; compare.addInputs( diff --git a/paddle/gserver/layers/CropLayer.cpp b/paddle/gserver/layers/CropLayer.cpp index ab23d4617e3..198ceffb463 100644 --- a/paddle/gserver/layers/CropLayer.cpp +++ b/paddle/gserver/layers/CropLayer.cpp @@ -25,20 +25,57 @@ bool CropLayer::init(const LayerMap& layerMap, Layer::init(layerMap, parameterMap); auto& crop_conf = config_.inputs(0).crop_conf(); - auto& img_conf = crop_conf.image_conf(); - CHECK_EQ(config_.inputs_size(), 1); - inDims_ = TensorShape( - {0, - img_conf.channels(), - img_conf.has_img_size_y() ? img_conf.img_size_y() : img_conf.img_size(), - img_conf.img_size()}); - - crop_corner_ = {crop_conf.crop_corner(0), - crop_conf.crop_corner(1), - crop_conf.crop_corner(2)}; - crop_shape_ = {crop_conf.crop_shape(0), - crop_conf.crop_shape(1), - crop_conf.crop_shape(2)}; + crop_axis_ = crop_conf.axis(); + for (int i = 0; i < crop_conf.offset_size(); i++) { + crop_offsets_[i] = crop_conf.offset(i); + } + + // 1. get input_0 shape + auto& input0_img_conf = config_.inputs(0).image_conf(); + inDims_ = TensorShape({0, + input0_img_conf.channels(), + input0_img_conf.has_img_size_y() + ? input0_img_conf.img_size_y() + : input0_img_conf.img_size(), + input0_img_conf.img_size()}); + + // 2. get output shape from input_1 or crop shap conf + if (config_.inputs_size() == 2) { + auto& input1_img_conf = config_.inputs(1).image_conf(); + targetDims_ = TensorShape({0, + input1_img_conf.channels(), + input1_img_conf.has_img_size_y() + ? input1_img_conf.img_size_y() + : input1_img_conf.img_size(), + input1_img_conf.img_size()}); + } else { + targetDims_ = TensorShape({crop_conf.shape(0), + crop_conf.shape(1), + crop_conf.shape(2), + crop_conf.shape(3)}); + } + + // 3. get final crop shape + int dimSize = 4; + for (int i = 0; i < dimSize; i++) { + if (i >= crop_axis_) { + crop_shape_[i] = targetDims_[i]; + } else { + crop_shape_[i] = inDims_[i]; + } + } + + // 4. get final crop corner + crop_corner_ = {0, 0, 0, 0}; + for (int i = 0; i < dimSize; i++) { + if (i >= crop_axis_) { + if (crop_offsets_.size() > 1) { + crop_corner_[i] = crop_offsets_[i - crop_axis_]; + } else { + crop_corner_[i] = crop_offsets_[0]; + } + } + } outDims_ = TensorShape(4); setOutDims(0); @@ -58,7 +95,7 @@ bool CropLayer::init(const LayerMap& layerMap, } void CropLayer::setOutDims(const size_t batchSize) { - outDims_.reshape({batchSize, crop_shape_[0], crop_shape_[1], crop_shape_[2]}); + outDims_.reshape({batchSize, crop_shape_[1], crop_shape_[2], crop_shape_[3]}); } void CropLayer::setTensorDim(const size_t batchSize) { diff --git a/paddle/gserver/layers/CropLayer.h b/paddle/gserver/layers/CropLayer.h index 3ce89707caf..23cede1c3fe 100644 --- a/paddle/gserver/layers/CropLayer.h +++ b/paddle/gserver/layers/CropLayer.h @@ -19,9 +19,13 @@ limitations under the License. */ namespace paddle { /** - * \brief This layer crop inputs according to the specify corner and shape. - * The input and output is a 4D tensor. Cropping from the 2nd to - * the 4th dimenstion. + * \brief This layer crop input according to the specify conf. + * input_0: input to be cropped + * input_1: optional reference input + * axis: start dimension to be croped + * offset: offset of cropping in each dimension + * shape: if reference input layer was not setted, + * crop input as this shape conf */ class CropLayer : public Layer { public: @@ -38,9 +42,12 @@ protected: void setOutDims(const size_t batchSize); void setTensorDim(const size_t batchSize); + int32_t crop_axis_; + std::vector crop_offsets_; std::vector crop_corner_; std::vector crop_shape_; TensorShape inDims_; + TensorShape targetDims_; TensorShape outDims_; }; } // namespace paddle -- GitLab From 5e6e1f636a356b6ae7d25ff8494354349b3b4f5f Mon Sep 17 00:00:00 2001 From: wanghaoshuang Date: Wed, 5 Jul 2017 00:53:32 +0800 Subject: [PATCH 1043/3256] Add grad test and python wrapper for crop layer --- paddle/function/CropOp.cpp | 2 +- paddle/function/CropOpTest.cpp | 2 +- paddle/gserver/layers/CropLayer.cpp | 23 ++++---- paddle/gserver/tests/CMakeLists.txt | 2 +- paddle/gserver/tests/test_LayerGrad.cpp | 28 ++++++++++ proto/ModelConfig.proto | 8 ++- python/paddle/trainer/config_parser.py | 45 ++++++++++++++++ .../paddle/trainer_config_helpers/layers.py | 54 +++++++++++++++++++ 8 files changed, 147 insertions(+), 17 deletions(-) diff --git a/paddle/function/CropOp.cpp b/paddle/function/CropOp.cpp index 0d511ceef5e..1bb194a9bc8 100644 --- a/paddle/function/CropOp.cpp +++ b/paddle/function/CropOp.cpp @@ -148,7 +148,7 @@ public: void calc(const BufferArgs& inputs, const BufferArgs& outputs) override { CHECK_EQ(1UL, inputs.size()); CHECK_EQ(1UL, outputs.size()); - CHECK_EQ(outputs[0].getArgType(), ASSIGN_TO); + CHECK_EQ(outputs[0].getArgType(), ADD_TO); TensorShape outShape = outputs[0].shape(); diff --git a/paddle/function/CropOpTest.cpp b/paddle/function/CropOpTest.cpp index c331a70d1f6..71d9b058128 100644 --- a/paddle/function/CropOpTest.cpp +++ b/paddle/function/CropOpTest.cpp @@ -25,7 +25,7 @@ TEST(Crop, real) { VLOG(3) << " numSamples=" << numSamples << " channels=" << channels << " imgSizeH=" << imgSizeH << " imgSizeW=" << imgSizeW; for (bool test_grad : {false, true}) { - FunctionCompare compare( + CpuGpuFuncCompare compare( test_grad ? "CropGrad" : "Crop", FuncConfig() .set>("crop_corner", {0, 1, 1, 1}) diff --git a/paddle/gserver/layers/CropLayer.cpp b/paddle/gserver/layers/CropLayer.cpp index 198ceffb463..b2fa17b400c 100644 --- a/paddle/gserver/layers/CropLayer.cpp +++ b/paddle/gserver/layers/CropLayer.cpp @@ -14,7 +14,6 @@ limitations under the License. */ #include "CropLayer.h" #include "paddle/utils/Stat.h" - namespace paddle { REGISTER_LAYER(crop, CropLayer); @@ -24,10 +23,9 @@ bool CropLayer::init(const LayerMap& layerMap, /* Initialize the basic parent class */ Layer::init(layerMap, parameterMap); - auto& crop_conf = config_.inputs(0).crop_conf(); - crop_axis_ = crop_conf.axis(); - for (int i = 0; i < crop_conf.offset_size(); i++) { - crop_offsets_[i] = crop_conf.offset(i); + crop_axis_ = config_.axis(); + for (int i = 0; i < config_.offset_size(); i++) { + crop_offsets_.push_back(config_.offset(i)); } // 1. get input_0 shape @@ -38,7 +36,6 @@ bool CropLayer::init(const LayerMap& layerMap, ? input0_img_conf.img_size_y() : input0_img_conf.img_size(), input0_img_conf.img_size()}); - // 2. get output shape from input_1 or crop shap conf if (config_.inputs_size() == 2) { auto& input1_img_conf = config_.inputs(1).image_conf(); @@ -49,19 +46,19 @@ bool CropLayer::init(const LayerMap& layerMap, : input1_img_conf.img_size(), input1_img_conf.img_size()}); } else { - targetDims_ = TensorShape({crop_conf.shape(0), - crop_conf.shape(1), - crop_conf.shape(2), - crop_conf.shape(3)}); + targetDims_ = TensorShape({config_.shape(0), + config_.shape(1), + config_.shape(2), + config_.shape(3)}); } // 3. get final crop shape int dimSize = 4; for (int i = 0; i < dimSize; i++) { if (i >= crop_axis_) { - crop_shape_[i] = targetDims_[i]; + crop_shape_.push_back(targetDims_[i]); } else { - crop_shape_[i] = inDims_[i]; + crop_shape_.push_back(inDims_[i]); } } @@ -99,7 +96,7 @@ void CropLayer::setOutDims(const size_t batchSize) { } void CropLayer::setTensorDim(const size_t batchSize) { - CHECK_EQ(static_cast(inputLayers_.size()), 1); + CHECK_EQ(static_cast(inputLayers_.size()), 2); inDims_.setDim(0, batchSize); int h = inputLayers_[0]->getOutput().getFrameHeight(); if (h != 0) inDims_.setDim(2, h); diff --git a/paddle/gserver/tests/CMakeLists.txt b/paddle/gserver/tests/CMakeLists.txt index 92f6cbcfe5a..a43adc7ce7d 100644 --- a/paddle/gserver/tests/CMakeLists.txt +++ b/paddle/gserver/tests/CMakeLists.txt @@ -56,7 +56,7 @@ add_test(NAME test_DetectionOutput add_unittest_without_exec(test_ConvUnify test_ConvUnify.cpp LayerGradUtil.cpp) - + add_test(NAME test_ConvUnify COMMAND test_ConvUnify) ################# test_BatchNorm ####################### diff --git a/paddle/gserver/tests/test_LayerGrad.cpp b/paddle/gserver/tests/test_LayerGrad.cpp index 59d1e9273d4..20a83d7aa12 100644 --- a/paddle/gserver/tests/test_LayerGrad.cpp +++ b/paddle/gserver/tests/test_LayerGrad.cpp @@ -1792,6 +1792,34 @@ TEST(Layer, RowConvLayer) { } } +TEST(Layer, CropLayer) { + TestConfig config; + // config input_0 + config.inputDefs.push_back({INPUT_DATA, "layer_0", 1024, 0}); + LayerInputConfig* input = config.layerConfig.add_inputs(); + ImageConfig* img = input->mutable_image_conf(); + img->set_channels(4); + img->set_img_size(16); + config.layerConfig.set_axis(2); + config.layerConfig.add_offset(0); + config.layerConfig.add_offset(0); + + // config input_1 + config.inputDefs.push_back({INPUT_DATA, "layer_1", 128, 0}); + input = config.layerConfig.add_inputs(); + img = input->mutable_image_conf(); + img->set_channels(2); + img->set_img_size(8); + + // config crop layer + config.layerConfig.set_type("crop"); + config.layerConfig.set_name("cropLayer"); + + for (auto useGpu : {false, true}) { + testLayerGrad(config, "crop", 100, false, useGpu, false); + } +} + int main(int argc, char** argv) { testing::InitGoogleTest(&argc, argv); initMain(argc, argv); diff --git a/proto/ModelConfig.proto b/proto/ModelConfig.proto index 37cd16c7989..83f72c137bd 100644 --- a/proto/ModelConfig.proto +++ b/proto/ModelConfig.proto @@ -472,10 +472,16 @@ message LayerConfig { // blank label used in ctc loss optional uint32 blank = 52 [default = 0]; - // stride parameter for seqlastins layer, AverageLayer, MaxLayer, which + // stride parameter for seqlastins layer, AverageLayer, MaxLayer, which // controls the scope of pooling operation. can be set > 0. // leave empty or set to -1 to disable this stride pooling. optional int32 seq_pool_stride = 53 [default = -1]; + + // for crop layer + optional int32 axis = 54 [default = 2]; + repeated uint32 offset = 55; + repeated uint32 shape = 56; + } message EvaluatorConfig { diff --git a/python/paddle/trainer/config_parser.py b/python/paddle/trainer/config_parser.py index 370529ed97b..8c529fdfd3e 100644 --- a/python/paddle/trainer/config_parser.py +++ b/python/paddle/trainer/config_parser.py @@ -1986,6 +1986,51 @@ class PadLayer(LayerBase): self.config.size = out_ch * out_h * out_w +@config_layer('crop') +class CropLayer(LayerBase): + def __init__(self, inputs, axis, offset, shape, name, **xargs): + super(CropLayer, self).__init__(name, 'crop', 0, inputs=inputs, **xargs) + self.conf.axis = axis + self.conf.axis = offset + self.conf.axis = shape + + crop = self.inputs[0].crop + self.config.inputs[0].crop_conf.axis = crop.axis + self.config.inputs[0].crop_conf.offset.extend(crop.offset) + self.config.inputs[0].crop_conf.shape.extend(crop.shape) + + # get channel, width and height from input_0 layer + input_layer = self.get_input_layer(0) + image_conf = self.config.inputs[0].image_conf + image_conf.img_size = input_layer.width + image_conf.img_size_y = input_layer.height + image_conf.channels = input_layer.size / (input_layer.width * + input_layer.height) + out_ch = image_conf.channels + out_h = image_conf.img_size + out_w = image_conf.img_size_y + if len(self.inputs) == 2: + # get channels, width and height from input_1 layer + input_layer = self.get_input_layer(1) + image_conf = self.config.inputs[1].image_conf + image_conf.img_size = input_layer.width + image_conf.img_size_y = input_layer.height + image_conf.channels = input_layer.size / (input_layer.width * + input_layer.height) + out_ch = image_conf.channels + out_h = image_conf.img_size_y + out_w = image_conf.img_size + else: + # set channels, width and heigth of current layer + if len(shape) > 2: + out_ch = shape[-3] + if len(shape) > 1: + out_h = shape[-2] + if len(shape) > 0: + out_w = shape[-1] + self.set_cnn_layer(name, out_h, out_w, out_ch) + + @config_layer('batch_norm') class BatchNormLayer(LayerBase): layer_type = 'batch_norm' diff --git a/python/paddle/trainer_config_helpers/layers.py b/python/paddle/trainer_config_helpers/layers.py index 206de1f8e1c..f9de086cba7 100755 --- a/python/paddle/trainer_config_helpers/layers.py +++ b/python/paddle/trainer_config_helpers/layers.py @@ -217,6 +217,7 @@ class LayerType(object): SMOOTH_L1 = 'smooth_l1' PRELU = 'prelu' + CROP_LAYER = 'crop' @staticmethod def is_layer_type(type_name): @@ -5853,3 +5854,56 @@ def prelu_layer(input, layer_type=LayerType.PRELU, parents=input, size=l.config.size) + + +@wrap_name_default() +@layer_support() +def crop_layer(input, axis, offset, shape=None, name=None, layer_attr=None): + """ + The crop layer crop images by offset and shape. User can set crop shape by + args 'shape' explicitly or by reference input layer. + + + The example usage is: + + .. code-block:: python + + crop = crop_layer(input=[image_input, reference_input], axis=2, offset=[2, 3]) + + :param input: The input layer.If two inputs were setted, + the second input will be regarded as reference input + :type input: LayerOutput or Sequence + :param axis: start axis to be cropped. To image input layer: + - 0: batch size + - 1: channels + - 2: height + - 3: width + :type partial_sum: int + :param offset: The crop offset + :type offset: Sequence + :param shape: The shape to be cropped. Default is None. + :type shape: Sqquence | None + :param name: Name of this layer. + :type name: basestring + :return: LayerOutput object. + :rtype: LayerOutput + """ + if isinstance(input, LayerOutput): + input = [input] + elif isinstance(input, Projection): + input = [input] + else: + assert isinstance(input, collections.Sequence) + l = Layer( + inputs=[x.name for x in input], + axis=axis, + offset=offset, + shape=shape, + name=name, + type=LayerType.CROP_LAYER, + **ExtraLayerAttribute.to_kwargs(layer_attr)) + return LayerOutput( + name=name, + layer_type=LayerType.CROP_LAYER, + parents=input, + size=l.config.size) -- GitLab From 86bdb2f33fa9e9e806e8248b14a172ce4e0557c6 Mon Sep 17 00:00:00 2001 From: wanghaoshuang Date: Wed, 5 Jul 2017 10:36:22 +0800 Subject: [PATCH 1044/3256] fix crop function test --- paddle/function/CropOpTest.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/paddle/function/CropOpTest.cpp b/paddle/function/CropOpTest.cpp index 71d9b058128..dcba972e106 100644 --- a/paddle/function/CropOpTest.cpp +++ b/paddle/function/CropOpTest.cpp @@ -34,8 +34,10 @@ TEST(Crop, real) { TensorShape outDims{numSamples, 2, 3, 3}; compare.addInputs( BufferArg(VALUE_TYPE_FLOAT, test_grad ? outDims : inDims)); - compare.addOutputs(BufferArg( - VALUE_TYPE_FLOAT, test_grad ? inDims : outDims, ASSIGN_TO)); + compare.addOutputs(BufferArg(VALUE_TYPE_FLOAT, + test_grad ? inDims : outDims, + tes_grad ? ADD_TO : ASSIGN_TO), + test_grad ? ADD_TO : ASSIGN_TO); compare.run(); } } -- GitLab From cf868918012f29b94628cff7e80cfc6e65bf0ee6 Mon Sep 17 00:00:00 2001 From: wanghaoshuang Date: Wed, 5 Jul 2017 11:34:16 +0800 Subject: [PATCH 1045/3256] fix unittest of crop layer --- paddle/function/CropOpTest.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paddle/function/CropOpTest.cpp b/paddle/function/CropOpTest.cpp index dcba972e106..6f11abfdf6f 100644 --- a/paddle/function/CropOpTest.cpp +++ b/paddle/function/CropOpTest.cpp @@ -36,7 +36,7 @@ TEST(Crop, real) { BufferArg(VALUE_TYPE_FLOAT, test_grad ? outDims : inDims)); compare.addOutputs(BufferArg(VALUE_TYPE_FLOAT, test_grad ? inDims : outDims, - tes_grad ? ADD_TO : ASSIGN_TO), + test_grad ? ADD_TO : ASSIGN_TO), test_grad ? ADD_TO : ASSIGN_TO); compare.run(); } -- GitLab From acfd2fc6dfc1bf06bbfd6e25496ca1dfde881551 Mon Sep 17 00:00:00 2001 From: wanghaoshuang Date: Wed, 5 Jul 2017 11:54:47 +0800 Subject: [PATCH 1046/3256] fix cpp format --- paddle/function/CropOp.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/paddle/function/CropOp.cpp b/paddle/function/CropOp.cpp index 1bb194a9bc8..39e06fc120a 100644 --- a/paddle/function/CropOp.cpp +++ b/paddle/function/CropOp.cpp @@ -13,8 +13,9 @@ See the License for the specific language governing permissions and limitations under the License. */ #include "CropOp.h" -#include "paddle/math/Vector.h" #include "paddle/function/TensorShape.h" +#include "paddle/math/Vector.h" + namespace paddle { template <> -- GitLab From cd437f5072b0482685d107c386e587bc1fe59044 Mon Sep 17 00:00:00 2001 From: gongweibao Date: Wed, 5 Jul 2017 05:16:41 +0000 Subject: [PATCH 1047/3256] fix bugs --- go/pserver/client/c/test/CMakeLists.txt | 4 +++- go/pserver/optimizer.go | 1 - 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/go/pserver/client/c/test/CMakeLists.txt b/go/pserver/client/c/test/CMakeLists.txt index f287f850719..44bc1837383 100644 --- a/go/pserver/client/c/test/CMakeLists.txt +++ b/go/pserver/client/c/test/CMakeLists.txt @@ -1,2 +1,4 @@ -cc_test(test_cclient SRCS test_cclient.c DEPS paddle_pserver_cclient) +# FIXME:It's ugly +#cc_test(test_cclient SRCS test_cclient.c DEPS paddle_pserver_cclient paddle_go_optimizer) +cc_test(test_cclient SRCS test_cclient.c DEPS paddle_pserver_cclient paddle_optimizer paddle_proto glog gflags protobuf) add_style_check_target(test_cclient test_cclient.c) diff --git a/go/pserver/optimizer.go b/go/pserver/optimizer.go index 2c9b0d5652a..93389b93a77 100644 --- a/go/pserver/optimizer.go +++ b/go/pserver/optimizer.go @@ -1,7 +1,6 @@ package pserver // #cgo CFLAGS: -I ../../ -// //FIXME: ldflags contain "build" path // #cgo LDFLAGS: -lpaddle_go_optimizer -lstdc++ -lm // #include "paddle/optimizer/optimizer.h" // #include -- GitLab From 1409b17e4f20afdd922b8566be324581ed3f0e54 Mon Sep 17 00:00:00 2001 From: gongweibao Date: Wed, 5 Jul 2017 06:06:13 +0000 Subject: [PATCH 1048/3256] add fixme --- cmake/generic.cmake | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cmake/generic.cmake b/cmake/generic.cmake index 97196114ff6..74396abdbbb 100644 --- a/cmake/generic.cmake +++ b/cmake/generic.cmake @@ -281,6 +281,7 @@ function(go_library TARGET_NAME) file(GLOB GO_SOURCE RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "*.go") string(REPLACE "${PADDLE_GO_PATH}/" "" CMAKE_CURRENT_SOURCE_REL_DIR ${CMAKE_CURRENT_SOURCE_DIR}) + # FIXME: link path add_custom_command(TARGET ${TARGET_NAME} POST_BUILD COMMAND rm "${${TARGET_NAME}_LIB_PATH}" # Golang build source code @@ -299,6 +300,7 @@ function(go_binary TARGET_NAME) cmake_parse_arguments(go_binary "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) string(REPLACE "${PADDLE_GO_PATH}/" "" CMAKE_CURRENT_SOURCE_REL_DIR ${CMAKE_CURRENT_SOURCE_DIR}) + # FIXME: link path add_custom_command(OUTPUT ${TARGET_NAME}_timestamp COMMAND env LIBRARY_PATH=${CMAKE_BINARY_DIR}/go/pserver/client/c/:$ENV{LIBRARY_PATH} GOPATH=${GOPATH} ${CMAKE_Go_COMPILER} build -- GitLab From 7364fcd4c3c6b08b569ed2bb809bed9904b55030 Mon Sep 17 00:00:00 2001 From: wuyi05 Date: Wed, 5 Jul 2017 15:42:17 +0800 Subject: [PATCH 1049/3256] add golang precommit --- .pre-commit-config.yaml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4cd8eb12f6b..a7c450176d8 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -21,3 +21,10 @@ sha: 28c0ea8a67a3e2dbbf4822ef44e85b63a0080a29 hooks: - id: clang-formater +- repo: https://github.com/dnephin/pre-commit-golang + sha: e4693a4c282b4fc878eda172a929f7a6508e7d16 + hooks: + - id: go-fmt + - id: go-vet + - id: go-lint + - id: gometalinter -- GitLab From e7b071f33a2af3168586ef2710835b694f61e958 Mon Sep 17 00:00:00 2001 From: Luo Tao Date: Wed, 5 Jul 2017 15:55:26 +0800 Subject: [PATCH 1050/3256] update annotation with comments --- paddle/gserver/layers/AverageLayer.h | 4 ++++ paddle/gserver/layers/MaxLayer.h | 7 +++---- paddle/gserver/layers/SequenceLastInstanceLayer.cpp | 7 +++---- paddle/gserver/layers/SequencePoolLayer.h | 5 +++-- python/paddle/trainer_config_helpers/layers.py | 11 +++++++---- 5 files changed, 20 insertions(+), 14 deletions(-) diff --git a/paddle/gserver/layers/AverageLayer.h b/paddle/gserver/layers/AverageLayer.h index 332552a3047..db4a17bfb07 100644 --- a/paddle/gserver/layers/AverageLayer.h +++ b/paddle/gserver/layers/AverageLayer.h @@ -25,6 +25,10 @@ namespace paddle { * If SequenceLevel = kNonSeq: * Output: output size is the number of input sequences (NOT input instances) * output[i] = average_{for each instance in this sequence}{input[i]} + * If stride_ > 0: + * Output: a shorten sequence. Stride is the step size by which we slide a + * window upon the input sequence, and the average pooling + * operation is then applied to each interval independently. * If SequenceLevel = kSeq: * Check input sequence must has sub-sequence * Output: output size is the number of input sub-sequences diff --git a/paddle/gserver/layers/MaxLayer.h b/paddle/gserver/layers/MaxLayer.h index adf7ab4ae47..fa536fce2b4 100644 --- a/paddle/gserver/layers/MaxLayer.h +++ b/paddle/gserver/layers/MaxLayer.h @@ -27,10 +27,9 @@ namespace paddle { * Output: output size is the number of input sequences (NOT input instances) * output[i] = max_{for each instance in this sequence}{input[i]} * If stride_ > 0: - * Output: a shorten sequence. The operation of getting max instance of a - * sequence is independently performed on every slice of the input - * sequence, which is obtained by sliding a window with the window - * size set to stride_. + * Output: a shorten sequence. Stride is the step size by which we slide a + * window upon the input sequence, and the max pooling operation is + * then applied to each interval independently. * If SequenceLevel = kSeq: * Check input sequence must has sub-sequence * Output: output size is the number of input sub-sequences diff --git a/paddle/gserver/layers/SequenceLastInstanceLayer.cpp b/paddle/gserver/layers/SequenceLastInstanceLayer.cpp index 8127cbf09c2..323cc47df19 100644 --- a/paddle/gserver/layers/SequenceLastInstanceLayer.cpp +++ b/paddle/gserver/layers/SequenceLastInstanceLayer.cpp @@ -26,10 +26,9 @@ namespace paddle { * If SequenceLevel = kNonseq: * Output: a sequence containing only the last instance of the input sequence * If stride_ > 0: - * Output: a shorten sequence. The operation of getting last instance of a - * sequence is independently performed on every slice of the input - * sequence, which is obtained by sliding a window with the window - * size set to stride_. + * Output: a shorten sequence. Stride is the step size by which we slide a + * window upon the input sequence, and getting last instance + * operation is then applied to each interval independently. * If SequenceLevel = kSeq: * Check input sequence must has sub-sequence * Output: a sequence containing only the last instance of each sub-sequence diff --git a/paddle/gserver/layers/SequencePoolLayer.h b/paddle/gserver/layers/SequencePoolLayer.h index 058627def8a..e207afd1dce 100644 --- a/paddle/gserver/layers/SequencePoolLayer.h +++ b/paddle/gserver/layers/SequencePoolLayer.h @@ -28,8 +28,9 @@ namespace paddle { * sequence}{input[i]} * If stride_ > 0: * Check input sequence must not have sub-sequence - * Output: a shorten sequence, pooling is performed upon a small local - * area + * Output: a shorten sequence. Stride is the step size by which we slide + * a window upon the input sequence, and the pooling operation + * is then applied to each interval independently. * If SequenceLevel = kSeq: * Check input sequence must has sub-sequence * Output: output size is the number of input sub-sequences diff --git a/python/paddle/trainer_config_helpers/layers.py b/python/paddle/trainer_config_helpers/layers.py index 5e8bf4b2034..2f52a27e60d 100755 --- a/python/paddle/trainer_config_helpers/layers.py +++ b/python/paddle/trainer_config_helpers/layers.py @@ -1097,7 +1097,10 @@ def pooling_layer(input, If stride > 0, this layer slides a window whose size is determined by stride, and return the pooling value of the window as the output. Thus, a long sequence - will be shorten. Note that for sequence with sub-sequence, the default value + will be shorten. + + The parameter stride specifies the intervals at which to apply the pooling + operation. Note that for sequence with sub-sequence, the default value of stride is -1. The example usage is: @@ -1118,7 +1121,7 @@ def pooling_layer(input, :param pooling_type: Type of pooling, MaxPooling(default), AvgPooling, SumPooling, SquareRootNPooling. :type pooling_type: BasePoolingType|None - :param stride: window size. + :param stride: The step size between successive pooling regions. :type stride: Int :param bias_attr: Bias parameter attribute. False if no bias. :type bias_attr: ParameterAttribute|None|False @@ -1408,7 +1411,7 @@ def last_seq(input, :type name: basestring :param input: Input layer name. :type input: LayerOutput - :param stride: window size. + :param stride: The step size between successive pooling regions. :type stride: Int :param layer_attr: extra layer attributes. :type layer_attr: ExtraLayerAttribute. @@ -1464,7 +1467,7 @@ def first_seq(input, :type name: basestring :param input: Input layer name. :type input: LayerOutput - :param stride: window size. + :param stride: The step size between successive pooling regions. :type stride: Int :param layer_attr: extra layer attributes. :type layer_attr: ExtraLayerAttribute. -- GitLab From 7ed6463ee91e0b71e7beca313554eae36da1c4e4 Mon Sep 17 00:00:00 2001 From: yangyaming Date: Wed, 24 May 2017 13:55:58 +0800 Subject: [PATCH 1051/3256] fix bugs for CrossChannelNormLayer --- .../gserver/layers/CrossChannelNormLayer.cpp | 32 ++++++++++++++----- paddle/gserver/layers/NormLayer.cpp | 10 ------ paddle/gserver/tests/LayerGradUtil.cpp | 7 +++- paddle/gserver/tests/LayerGradUtil.h | 6 ++++ paddle/gserver/tests/test_LayerGrad.cpp | 5 ++- 5 files changed, 40 insertions(+), 20 deletions(-) diff --git a/paddle/gserver/layers/CrossChannelNormLayer.cpp b/paddle/gserver/layers/CrossChannelNormLayer.cpp index 3fbccc11032..4dfe460561e 100644 --- a/paddle/gserver/layers/CrossChannelNormLayer.cpp +++ b/paddle/gserver/layers/CrossChannelNormLayer.cpp @@ -36,6 +36,16 @@ MatrixPtr CrossChannelNormLayer::createSpatialMatrix(MatrixPtr data, data->getData() + iter * spatialDim, 1, spatialDim, false, useGpu_); } +bool CrossChannelNormLayer::init(const LayerMap& layerMap, + const ParameterMap& parameterMap) { + Layer::init(layerMap, parameterMap); + CHECK(parameters_[0]); + const NormConfig& conf = config_.inputs(0).norm_conf(); + channels_ = conf.channels(); + scale_.reset(new Weight(channels_, 1, parameters_[0])); + return true; +} + void CrossChannelNormLayer::forward(PassType passType) { Layer::forward(passType); MatrixPtr inV = getInputValue(0); @@ -63,6 +73,7 @@ void CrossChannelNormLayer::forward(PassType passType) { // compute norm. spatialBuffer_->sumCols(*dataTmp, 1, 0); + spatialBuffer_->add(*normTmp); spatialBuffer_->sqrt2(*spatialBuffer_); normTmp->copyFrom(*spatialBuffer_); outVTmp->copyFrom(*inVTmp); @@ -82,6 +93,9 @@ void CrossChannelNormLayer::backward(const UpdateCallback& callback) { size_t dataDim = inG->getWidth(); size_t spatialDim = dataDim / channels_; + MatrixPtr inGBuffer; + Matrix::resizeOrCreate(inGBuffer, channels_, spatialDim, false, useGpu_); + dataBuffer_->dotMul(*outG, *outV); Matrix::resizeOrCreate(scaleDiff_, channels_, 1, false, useGpu_); Matrix::resizeOrCreate(channelBuffer_, channels_, 1, false, useGpu_); @@ -100,22 +114,24 @@ void CrossChannelNormLayer::backward(const UpdateCallback& callback) { scaleDiff_->add(*channelBuffer_, 1.); sampleBuffer_->dotMul(*inVTmp, *outGTmp); - spatialBuffer_->sumCols(*sampleBuffer_, 1., 1.); + spatialBuffer_->sumCols(*sampleBuffer_, 1., 0.); // scale the grad - inGTmp->copyFrom(*inVTmp); - inGTmp->mulRowVector(*spatialBuffer_); + inGBuffer->copyFrom(*inVTmp); + inGBuffer->mulRowVector(*spatialBuffer_); // divide by square of norm spatialBuffer_->dotMul(*normTmp, *normTmp); - inGTmp->divRowVector(*spatialBuffer_); + inGBuffer->divRowVector(*spatialBuffer_); // subtract - inGTmp->add(*outGTmp, -1, 1); + inGBuffer->add(*outGTmp, -1, 1); // divide by norm - inGTmp->divRowVector(*normTmp); + inGBuffer->divRowVector(*normTmp); // scale the diff - inGTmp->mulColVector(*scale_->getW()); + inGBuffer->mulColVector(*scale_->getW()); + + inGTmp->add(*inGBuffer); } // updata scale - if (scale_->getWGrad()) scale_->getWGrad()->copyFrom(*scaleDiff_); + if (scale_->getWGrad()) scale_->getWGrad()->add(*scaleDiff_); scale_->getParameterPtr()->incUpdate(callback); } diff --git a/paddle/gserver/layers/NormLayer.cpp b/paddle/gserver/layers/NormLayer.cpp index e094078bfe8..caef7100929 100644 --- a/paddle/gserver/layers/NormLayer.cpp +++ b/paddle/gserver/layers/NormLayer.cpp @@ -56,14 +56,4 @@ bool ResponseNormLayer::init(const LayerMap& layerMap, return true; } -bool CrossChannelNormLayer::init(const LayerMap& layerMap, - const ParameterMap& parameterMap) { - Layer::init(layerMap, parameterMap); - CHECK(parameters_[0]); - const NormConfig& conf = config_.inputs(0).norm_conf(); - channels_ = conf.channels(); - scale_.reset(new Weight(channels_, 1, parameters_[0])); - return true; -} - } // namespace paddle diff --git a/paddle/gserver/tests/LayerGradUtil.cpp b/paddle/gserver/tests/LayerGradUtil.cpp index e3591ba4df8..66aafba844d 100644 --- a/paddle/gserver/tests/LayerGradUtil.cpp +++ b/paddle/gserver/tests/LayerGradUtil.cpp @@ -465,7 +465,6 @@ void initTestLayer(TestConfig testConf, ParameterConfig paraConfig) { paraConfig.set_name(paraName); paraConfig.set_size(paraSize); - paraConfig.set_initial_std(1); paraConfig.set_is_static(isStatic); auto para = std::make_shared(paraConfig, FLAGS_use_gpu, initialize); @@ -499,6 +498,12 @@ void initTestLayer(TestConfig testConf, paraConfig.add_dims((*layerMap)[input.input_layer_name()]->getSize()); paraConfig.add_dims(testConf.layerConfig.size()); } + if (testConf.hasParamInitialValue) { + paraConfig.set_initial_mean(testConf.paramInitialMean); + paraConfig.set_initial_std(testConf.paramInitialStd); + } else { + paraConfig.set_initial_std(1); + } initParameter(paraName, paraSize, inputDef.isStatic, false, paraConfig); } } diff --git a/paddle/gserver/tests/LayerGradUtil.h b/paddle/gserver/tests/LayerGradUtil.h index 18a6525a145..5ea7ca0f24c 100644 --- a/paddle/gserver/tests/LayerGradUtil.h +++ b/paddle/gserver/tests/LayerGradUtil.h @@ -125,12 +125,18 @@ struct TestConfig { LayerConfig layerConfig; std::vector inputDefs; size_t biasSize; + real paramInitialMean; + real paramInitialStd; + bool hasParamInitialValue; bool testAccumulate; bool testState; bool staticBias; bool testBatchState; TestConfig() : biasSize(0), + paramInitialMean(0), + paramInitialStd(1), + hasParamInitialValue(false), testAccumulate(true), testState(false), staticBias(false), diff --git a/paddle/gserver/tests/test_LayerGrad.cpp b/paddle/gserver/tests/test_LayerGrad.cpp index 59d1e9273d4..6441e08b48d 100644 --- a/paddle/gserver/tests/test_LayerGrad.cpp +++ b/paddle/gserver/tests/test_LayerGrad.cpp @@ -1661,6 +1661,9 @@ TEST(Layer, PadLayer) { TEST(Layer, CrossChannelNormLayer) { TestConfig config; + config.hasParamInitialValue = true; + config.paramInitialMean = 1.; + config.paramInitialStd = 0.; config.layerConfig.set_type("norm"); config.layerConfig.set_size(100); LayerInputConfig* input = config.layerConfig.add_inputs(); @@ -1674,7 +1677,7 @@ TEST(Layer, CrossChannelNormLayer) { config.inputDefs.push_back({INPUT_DATA, "layer_0", 100, 10}); for (auto useGpu : {false, true}) { - testLayerGrad(config, "cross-channel-norm", 10, false, useGpu, false, 5); + testLayerGrad(config, "cross-channel-norm", 10, false, useGpu, false); } } -- GitLab From 2bf4f1bbc1e4abc9c173b89aeb96c40b404e94f4 Mon Sep 17 00:00:00 2001 From: yangyaming Date: Wed, 24 May 2017 14:22:41 +0800 Subject: [PATCH 1052/3256] make adding eps more clear --- paddle/gserver/layers/CrossChannelNormLayer.cpp | 7 +++---- paddle/gserver/tests/LayerGradUtil.h | 4 ++-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/paddle/gserver/layers/CrossChannelNormLayer.cpp b/paddle/gserver/layers/CrossChannelNormLayer.cpp index 4dfe460561e..d72503217f1 100644 --- a/paddle/gserver/layers/CrossChannelNormLayer.cpp +++ b/paddle/gserver/layers/CrossChannelNormLayer.cpp @@ -61,9 +61,7 @@ void CrossChannelNormLayer::forward(PassType passType) { Matrix::resizeOrCreate(dataBuffer_, batchSize, dataDim, false, useGpu_); Matrix::resizeOrCreate(spatialBuffer_, 1, spatialDim, false, useGpu_); Matrix::resizeOrCreate(normBuffer_, batchSize, spatialDim, false, useGpu_); - normBuffer_->zeroMem(); - // add eps to avoid overflow - normBuffer_->addScalar(*normBuffer_, 1e-6); + inV->square2(*dataBuffer_); for (size_t i = 0; i < batchSize; i++) { const MatrixPtr inVTmp = createSampleMatrix(inV, i, spatialDim); @@ -73,7 +71,8 @@ void CrossChannelNormLayer::forward(PassType passType) { // compute norm. spatialBuffer_->sumCols(*dataTmp, 1, 0); - spatialBuffer_->add(*normTmp); + // add eps to avoid overflow + spatialBuffer_->add(1e-6); spatialBuffer_->sqrt2(*spatialBuffer_); normTmp->copyFrom(*spatialBuffer_); outVTmp->copyFrom(*inVTmp); diff --git a/paddle/gserver/tests/LayerGradUtil.h b/paddle/gserver/tests/LayerGradUtil.h index 5ea7ca0f24c..9dbd2027578 100644 --- a/paddle/gserver/tests/LayerGradUtil.h +++ b/paddle/gserver/tests/LayerGradUtil.h @@ -134,8 +134,8 @@ struct TestConfig { bool testBatchState; TestConfig() : biasSize(0), - paramInitialMean(0), - paramInitialStd(1), + paramInitialMean(0.0), + paramInitialStd(1.0), hasParamInitialValue(false), testAccumulate(true), testState(false), -- GitLab From 7c6aa04f6185e92082b9a742d5c746b335406711 Mon Sep 17 00:00:00 2001 From: wuyi05 Date: Wed, 5 Jul 2017 16:24:53 +0800 Subject: [PATCH 1053/3256] add go pre-commit and travis build --- .pre-commit-config.yaml | 4 ++-- .travis.yml | 4 ++-- go/pserver/service.go | 6 ++++-- paddle/scripts/travis/build_doc.sh | 11 ++++++----- 4 files changed, 14 insertions(+), 11 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a7c450176d8..61b989dc698 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -25,6 +25,6 @@ sha: e4693a4c282b4fc878eda172a929f7a6508e7d16 hooks: - id: go-fmt - - id: go-vet + files: (.*\.go) - id: go-lint - - id: gometalinter + files: (.*\.go) diff --git a/.travis.yml b/.travis.yml index 16432dac0cf..aafeeba0271 100644 --- a/.travis.yml +++ b/.travis.yml @@ -33,7 +33,7 @@ addons: - ccache before_install: - if [[ "$JOB" == "check_style" ]]; then sudo ln -s /usr/bin/clang-format-3.8 /usr/bin/clang-format; fi - # Paddle is using protobuf 3.1 currently. Protobuf 3.2 breaks the compatibility. So we specify the python + # Paddle is using protobuf 3.1 currently. Protobuf 3.2 breaks the compatibility. So we specify the python # protobuf version. - pip install numpy wheel 'protobuf==3.1' sphinx==1.5.6 recommonmark sphinx-rtd-theme==0.1.9 virtualenv pre-commit requests==2.9.2 LinkChecker - pip install rarfile @@ -42,7 +42,7 @@ before_install: function timeout() { perl -e 'alarm shift; exec @ARGV' "$@"; } script: - | - export WITH_GOLANG=ON && timeout 2580 paddle/scripts/travis/${JOB}.sh # 43min timeout + timeout 2580 paddle/scripts/travis/${JOB}.sh # 43min timeout RESULT=$?; if [ $RESULT -eq 0 ] || [ $RESULT -eq 142 ]; then true; else false; fi; notifications: email: diff --git a/go/pserver/service.go b/go/pserver/service.go index 7711dc027e1..ad16a5708d1 100644 --- a/go/pserver/service.go +++ b/go/pserver/service.go @@ -10,8 +10,10 @@ import ( type ElementType int const ( + // AlreadyInitialized is true if pserver is initialized AlreadyInitialized = "pserver already initialized" - Uninitialized = "pserver not fully initialized" + // Uninitialized is true if pserver not fully initialized + Uninitialized = "pserver not fully initialized" ) // Supported element types @@ -55,7 +57,7 @@ func NewService(idx int) (*Service, error) { s := &Service{ idx: idx, } - s.optMap = make(map[string]*optimizer) + s.optMap = make(map[string]*optimizer) s.initialized = make(chan struct{}) return s, nil } diff --git a/paddle/scripts/travis/build_doc.sh b/paddle/scripts/travis/build_doc.sh index a44bd35357f..a4438515804 100755 --- a/paddle/scripts/travis/build_doc.sh +++ b/paddle/scripts/travis/build_doc.sh @@ -5,13 +5,14 @@ set -e mkdir -p $TRAVIS_BUILD_DIR/build cd $TRAVIS_BUILD_DIR/build -# Compile Documentation only. -cmake .. -DCMAKE_BUILD_TYPE=Debug -DWITH_GPU=OFF -DWITH_DOC=OFF -DWITH_STYLE_CHECK=OFF +# Compile paddle binaries first +cmake .. -DCMAKE_BUILD_TYPE=Debug -DWITH_GPU=OFF -DWITH_DOC=OFF -DWITH_GOLANG=ON -DWITH_STYLE_CHECK=OFF mkdir output make -j `nproc` find .. -name '*whl' | xargs pip install # install all wheels. rm -rf * +# Compile Documentation only. cmake .. -DCMAKE_BUILD_TYPE=Debug -DWITH_GPU=OFF -DWITH_DOC=ON make -j `nproc` paddle_docs paddle_docs_cn @@ -25,7 +26,7 @@ SSH_REPO=${REPO/https:\/\/github.com\//git@github.com:} SHA=`git rev-parse --verify HEAD` # Documentation branch name -# gh-pages branch is used for PaddlePaddle.org. The English version of +# gh-pages branch is used for PaddlePaddle.org. The English version of # documentation in `doc` directory, and the chinese version in `doc_cn` # directory. TARGET_BRANCH="gh-pages" @@ -51,7 +52,7 @@ function deploy_docs() { # checkout github page branch git checkout $TARGET_BRANCH || git checkout --orphan $TARGET_BRANCH - + mkdir -p ${DIR} # remove old docs. mv new docs. set +e @@ -62,7 +63,7 @@ function deploy_docs() { git add . } -deploy_docs "master" "." +deploy_docs "master" "." deploy_docs "develop" "./develop/" # Check is there anything changed. -- GitLab From 81bfd47eb3fdbf7a0c398f6ad7e62f1d6e7350c1 Mon Sep 17 00:00:00 2001 From: wuyi05 Date: Wed, 5 Jul 2017 16:32:14 +0800 Subject: [PATCH 1054/3256] add glide in travis --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index aafeeba0271..498674469b2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -37,6 +37,7 @@ before_install: # protobuf version. - pip install numpy wheel 'protobuf==3.1' sphinx==1.5.6 recommonmark sphinx-rtd-theme==0.1.9 virtualenv pre-commit requests==2.9.2 LinkChecker - pip install rarfile + - curl https://glide.sh/get | bash - eval "$(GIMME_GO_VERSION=1.8.3 gimme)" - | function timeout() { perl -e 'alarm shift; exec @ARGV' "$@"; } -- GitLab From d0ad0314bb868b9e0c1aa77f74ca0d2d3e8b8ef0 Mon Sep 17 00:00:00 2001 From: liaogang Date: Wed, 5 Jul 2017 16:33:18 +0800 Subject: [PATCH 1055/3256] FIX: glog dependency --- paddle/memory/detail/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paddle/memory/detail/CMakeLists.txt b/paddle/memory/detail/CMakeLists.txt index 4fdabc8eebd..6cb6422e473 100644 --- a/paddle/memory/detail/CMakeLists.txt +++ b/paddle/memory/detail/CMakeLists.txt @@ -12,4 +12,4 @@ cc_library(meta_cache SRCS meta_cache.cc) cc_library(memory_block SRCS memory_block.cc) -cc_library(buddy_allocator SRCS buddy_allocator.cc) +cc_library(buddy_allocator SRCS buddy_allocator.cc DEPS glog) -- GitLab From 2f085a7bcf11f5501bded27862988022e32299a0 Mon Sep 17 00:00:00 2001 From: wuyi05 Date: Wed, 5 Jul 2017 17:08:19 +0800 Subject: [PATCH 1056/3256] add go pserver deps --- go/cmd/pserver/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go/cmd/pserver/CMakeLists.txt b/go/cmd/pserver/CMakeLists.txt index bc1da3348cc..51db6dff043 100644 --- a/go/cmd/pserver/CMakeLists.txt +++ b/go/cmd/pserver/CMakeLists.txt @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -go_binary(pserver SRCS pserver.go) +go_binary(pserver SRCS pserver.go DEPS paddle_go_optimizer) -- GitLab From 5eb8bf0324ba7de923760dc05aa7e850a9ae103f Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Wed, 5 Jul 2017 17:23:41 +0800 Subject: [PATCH 1057/3256] Correct GLOG CHECK in Paddle Use CHECK instead of PCHECK, because PCHECK is used for errno. --- paddle/pserver/LightNetwork.cpp | 28 ++++++++++++++-------------- paddle/pserver/SocketChannel.cpp | 22 +++++++++++----------- paddle/pserver/test/SocketTest.cpp | 28 ++++++++++++++-------------- paddle/trainer/Tester.cpp | 2 +- paddle/utils/ThreadLocal.h | 12 ++++++------ 5 files changed, 46 insertions(+), 46 deletions(-) diff --git a/paddle/pserver/LightNetwork.cpp b/paddle/pserver/LightNetwork.cpp index 922f25734de..8616fd2d5ae 100644 --- a/paddle/pserver/LightNetwork.cpp +++ b/paddle/pserver/LightNetwork.cpp @@ -142,7 +142,7 @@ SocketServer::SocketServer(const std::string &addr, int port, int rdmaCpu) } /// trigger to initialize RDMA lib - PCHECK(RdmaClientDaemons::get()) << "initilizate RDMA failed\n"; + CHECK(RdmaClientDaemons::get()) << "initilizate RDMA failed\n"; } SocketServer::~SocketServer() { @@ -168,7 +168,7 @@ void SocketServer::tcpServer() { /// First call to socket() function socket_ = socket(AF_INET, SOCK_STREAM, 0); - PCHECK(socket_ >= 0) << "ERROR opening socket"; + CHECK(socket_ >= 0) << "ERROR opening socket"; /// Initialize socket structure bzero((char *)&serv_addr, sizeof(serv_addr)); @@ -176,7 +176,7 @@ void SocketServer::tcpServer() { serv_addr.sin_port = htons(port_); if (!addr_.empty()) { server = gethostbyname(addr_.c_str()); - PCHECK(server) << "ERROR, no such host: " << addr_; + CHECK(server) << "ERROR, no such host: " << addr_; bcopy((char *)server->h_addr, (char *)&serv_addr.sin_addr.s_addr, server->h_length); @@ -187,7 +187,7 @@ void SocketServer::tcpServer() { setOption(socket_); /// Now bind the host address using bind() call. - PCHECK(bind(socket_, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) >= 0) + CHECK(bind(socket_, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) >= 0) << "ERROR on binding " << addr_; /// Now start listening for the clients, here process will @@ -201,7 +201,7 @@ void SocketServer::tcpServer() { if (stopping_) { break; } - PCHECK(newsockfd >= 0) << "ERROR on accept"; + CHECK(newsockfd >= 0) << "ERROR on accept"; constexpr int kPeerNameLen = 128; char peerName[kPeerNameLen]; CHECK(inet_ntop(AF_INET, &cli_addr.sin_addr, peerName, kPeerNameLen)); @@ -227,14 +227,14 @@ void SocketServer::rdmaServer() { /// First call to socket() function rdmaSocket_ = rdma::ssocket(rdmaCpu_); - PCHECK(rdmaSocket_) << "ERROR opening RDMA socket"; + CHECK(rdmaSocket_) << "ERROR opening RDMA socket"; - PCHECK(rdma::bind(rdmaSocket_, rdmaUri_.c_str()) == 0) + CHECK(rdma::bind(rdmaSocket_, rdmaUri_.c_str()) == 0) << "ERROR bind RDMA socket"; /// Now start listening for the clients, here process will /// go in sleep mode and will wait for the incoming connection - PCHECK(rdma::listen(rdmaSocket_) == 0) << "ERROR listen RDMA socket"; + CHECK(rdma::listen(rdmaSocket_) == 0) << "ERROR listen RDMA socket"; while (true) { /// Accept actual connection from the client @@ -242,7 +242,7 @@ void SocketServer::rdmaServer() { if (stopping_) { break; } - PCHECK(newsock) << "ERROR on accept"; + CHECK(newsock) << "ERROR on accept"; constexpr int kPeerNameLen = 128; char peerName[kPeerNameLen]; @@ -290,7 +290,7 @@ RdmaClientDaemons::RdmaClientDaemons() { onlineCpus_ = rdma::numCpus(); for (auto i = 0; i < onlineCpus_; i++) { socket = rdma::csocket(i); - PCHECK(socket) << "ERROR open client socket daemon"; + CHECK(socket) << "ERROR open client socket daemon"; rdmaClientSocket_.push_back(socket); } @@ -355,7 +355,7 @@ void SocketClient::TcpClient(const std::string &serverAddr, int serverPort) { /// Create a socket point int sockfd = socket(AF_INET, SOCK_STREAM, 0); - PCHECK(sockfd >= 0) << "ERROR opening socket"; + CHECK(sockfd >= 0) << "ERROR opening socket"; #if defined(__OSX__) || defined(__APPLE__) server = getipnodebyname(serverAddr.c_str(), AF_INET, AI_DEFAULT, &errRet); @@ -396,8 +396,8 @@ void SocketClient::TcpClient(const std::string &serverAddr, int serverPort) { } std::this_thread::sleep_for(std::chrono::seconds(1)); } else { - PCHECK(errno != 0) << "ERROR connecting to " << serverAddr << ":" - << serverPort << "errorno: " << errno; + CHECK(errno != 0) << "ERROR connecting to " << serverAddr << ":" + << serverPort << "errorno: " << errno; } } while (errno == ECONNREFUSED); @@ -426,7 +426,7 @@ void SocketClient::RdmaClient(const std::string &serverAddr, int serverPort) { /// connect to server with socket daemon sock = rdma::connect(socketDaemon_, rdmaUri.c_str()); - PCHECK(sock) << "ERROR connect to server" << rdmaUri; + CHECK(sock) << "ERROR connect to server" << rdmaUri; std::vector seg; str::split(rdmaUri, '/', &seg); diff --git a/paddle/pserver/SocketChannel.cpp b/paddle/pserver/SocketChannel.cpp index 05998891649..12e3bc6552f 100644 --- a/paddle/pserver/SocketChannel.cpp +++ b/paddle/pserver/SocketChannel.cpp @@ -51,7 +51,7 @@ size_t SocketChannel::read(void* buf, size_t size) { else len = rdma::read(rdmaSocket_, (char*)buf + total, size - total); - PCHECK(len >= 0) << " peer=" << peerName_; + CHECK(len >= 0) << " peer=" << peerName_; if (len <= 0) { return total; } @@ -69,7 +69,7 @@ size_t SocketChannel::write(const void* buf, size_t size) { else len = rdma::write(rdmaSocket_, (char*)buf + total, size - total); - PCHECK(len >= 0) << " peer=" << peerName_; + CHECK(len >= 0) << " peer=" << peerName_; if (len <= 0) { return total; } @@ -98,10 +98,10 @@ static size_t readwritev(IOFunc iofunc, while (size < total) { ssize_t len = iofunc(socket, &iovs[curIov], std::min(iovcnt - curIov, maxiovs)); - PCHECK(len > 0) << " peer=" << peerName << " curIov=" << curIov - << " iovCnt=" << iovcnt - << " iovs[curIov].base=" << iovs[curIov].iov_base - << " iovs[curIov].iov_len=" << iovs[curIov].iov_len; + CHECK(len > 0) << " peer=" << peerName << " curIov=" << curIov + << " iovCnt=" << iovcnt + << " iovs[curIov].base=" << iovs[curIov].iov_base + << " iovs[curIov].iov_len=" << iovs[curIov].iov_len; size += len; /// restore iovs[curIov] to the original value @@ -183,7 +183,7 @@ void SocketChannel::writeMessage(const std::vector& userIovs) { header.totalLength += iov.iov_len; } - PCHECK(writev(iovs) == (size_t)header.totalLength); + CHECK(writev(iovs) == (size_t)header.totalLength); } std::unique_ptr SocketChannel::readMessage() { @@ -194,7 +194,7 @@ std::unique_ptr SocketChannel::readMessage() { return nullptr; } - PCHECK(len == sizeof(header)); + CHECK(len == sizeof(header)); std::unique_ptr msgReader(new MsgReader(this, header.numIovs)); @@ -209,7 +209,7 @@ std::unique_ptr SocketChannel::readMessage() { MsgReader::MsgReader(SocketChannel* channel, size_t numBlocks) : channel_(channel), blockLengths_(numBlocks), currentBlockIndex_(0) { size_t size = numBlocks * sizeof(blockLengths_[0]); - PCHECK(channel_->read(&blockLengths_[0], size) == size); + CHECK(channel_->read(&blockLengths_[0], size) == size); } void MsgReader::readBlocks(const std::vector& bufs) { @@ -223,12 +223,12 @@ void MsgReader::readBlocks(const std::vector& bufs) { ++currentBlockIndex_; } - PCHECK(channel_->readv(&iovs) == totalLength); + CHECK(channel_->readv(&iovs) == totalLength); } void MsgReader::readNextBlock(void* buf) { CHECK_LT(currentBlockIndex_, blockLengths_.size()); - PCHECK(channel_->read(buf, getNextBlockLength()) == getNextBlockLength()); + CHECK(channel_->read(buf, getNextBlockLength()) == getNextBlockLength()); ++currentBlockIndex_; } diff --git a/paddle/pserver/test/SocketTest.cpp b/paddle/pserver/test/SocketTest.cpp index 066a6c02939..6f6c9e596cf 100644 --- a/paddle/pserver/test/SocketTest.cpp +++ b/paddle/pserver/test/SocketTest.cpp @@ -113,7 +113,7 @@ void SocketServer::run() { /* First call to socket() function */ socket_ = socket(AF_INET, SOCK_STREAM, 0); - PCHECK(socket_ >= 0) << "ERROR opening socket"; + CHECK(socket_ >= 0) << "ERROR opening socket"; /* Initialize socket structure */ bzero((char*)&serv_addr, sizeof(serv_addr)); @@ -122,7 +122,7 @@ void SocketServer::run() { serv_addr.sin_port = htons(port_); /* Now bind the host address using bind() call.*/ - PCHECK(bind(socket_, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) >= 0) + CHECK(bind(socket_, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) >= 0) << "ERROR on binding"; /* Now start listening for the clients, here process will @@ -134,7 +134,7 @@ void SocketServer::run() { while (true) { /* Accept actual connection from the client */ newsockfd = accept(socket_, (struct sockaddr*)&cli_addr, &clilen); - PCHECK(newsockfd >= 0) << "ERROR on accept"; + CHECK(newsockfd >= 0) << "ERROR on accept"; SocketWorker* worker = new SocketWorker(newsockfd); worker->start(); @@ -146,17 +146,17 @@ void SocketWorker::run() { while (true) { int64_t n = channel_.readAll(&header, sizeof(header)); - PCHECK(n == sizeof(header)) << "ERROR reading from socket"; + CHECK(n == sizeof(header)) << "ERROR reading from socket"; buffer_.resize(header.dataLength); n = channel_.readAll(&buffer_[0], header.dataLength); - PCHECK(n == header.dataLength) << "ERROR reading from socket"; + CHECK(n == header.dataLength) << "ERROR reading from socket"; /* Write a response to the client */ n = channel_.writeAll(&header, sizeof(header)); - PCHECK(n == sizeof(header)) << "ERROR reading from socket"; + CHECK(n == sizeof(header)) << "ERROR reading from socket"; n = channel_.writeAll(buffer_.data(), buffer_.size()); - PCHECK(n == header.dataLength) << "ERROR writing to socket"; + CHECK(n == header.dataLength) << "ERROR writing to socket"; } } @@ -177,9 +177,9 @@ SocketClient::SocketClient(const std::string& serverAddr, int serverPort) { /* Create a socket point */ int sockfd = socket(AF_INET, SOCK_STREAM, 0); - PCHECK(sockfd >= 0) << "ERROR opening socket"; + CHECK(sockfd >= 0) << "ERROR opening socket"; server = gethostbyname(serverAddr.c_str()); - PCHECK(server) << "ERROR, no such host: " << serverAddr; + CHECK(server) << "ERROR, no such host: " << serverAddr; bzero((char*)&serv_addr, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; @@ -189,7 +189,7 @@ SocketClient::SocketClient(const std::string& serverAddr, int serverPort) { serv_addr.sin_port = htons(serverPort); /* Now connect to the server */ - PCHECK(connect(sockfd, (sockaddr*)&serv_addr, sizeof(serv_addr)) >= 0) + CHECK(connect(sockfd, (sockaddr*)&serv_addr, sizeof(serv_addr)) >= 0) << "ERROR connecting"; channel_.reset(new SocketChannel(sockfd)); @@ -234,18 +234,18 @@ int main(int argc, char** argv) { cpuGrad.copyFrom(gpuGrad); header.dataLength = dataSize; - PCHECK(channel->writeAll(&header, sizeof(header)) == sizeof(header)) + CHECK(channel->writeAll(&header, sizeof(header)) == sizeof(header)) << "Client write header error"; - PCHECK(channel->writeAll(cpuGrad.getData(), dataSize) == dataSize) + CHECK(channel->writeAll(cpuGrad.getData(), dataSize) == dataSize) << "Client write data error"; /* Now read server response */ - PCHECK(channel->readAll(&header, sizeof(header)) == sizeof(header)) + CHECK(channel->readAll(&header, sizeof(header)) == sizeof(header)) << "Client read header error"; CHECK_EQ((uint64_t)header.dataLength, dataSize); - PCHECK(channel->readAll(cpuParam.getData(), dataSize) == dataSize) + CHECK(channel->readAll(cpuParam.getData(), dataSize) == dataSize) << "Client read data error"; gpuParam.copyFrom(cpuParam); diff --git a/paddle/trainer/Tester.cpp b/paddle/trainer/Tester.cpp index 80664fa877b..16e676d6024 100644 --- a/paddle/trainer/Tester.cpp +++ b/paddle/trainer/Tester.cpp @@ -175,7 +175,7 @@ real Tester::forwardOneBatch(const DataBatch& dataBatch, } hl_stream_synchronize(HPPL_STREAM_DEFAULT); FILE* fp = fopen(featFile.c_str(), "ab+"); - PCHECK(!ferror(fp)) << "Fail to open " << featFile; + CHECK(!ferror(fp)) << "Fail to open " << featFile; size_t sampleNum = featMatrices[0]->getHeight(); for (size_t i = 0; i < sampleNum; ++i) { diff --git a/paddle/utils/ThreadLocal.h b/paddle/utils/ThreadLocal.h index a4987c9ec26..b5e28625462 100644 --- a/paddle/utils/ThreadLocal.h +++ b/paddle/utils/ThreadLocal.h @@ -51,7 +51,7 @@ template class ThreadLocal { public: ThreadLocal() { - PCHECK(pthread_key_create(&threadSpecificKey_, dataDestructor) == 0); + CHECK(pthread_key_create(&threadSpecificKey_, dataDestructor) == 0); } ~ThreadLocal() { pthread_key_delete(threadSpecificKey_); } @@ -65,7 +65,7 @@ public: if (!p && createLocal) { p = new T(); int ret = pthread_setspecific(threadSpecificKey_, p); - PCHECK(ret == 0); + CHECK(ret == 0); } return p; } @@ -79,7 +79,7 @@ public: if (T* q = get(false)) { dataDestructor(q); } - PCHECK(pthread_setspecific(threadSpecificKey_, p) == 0); + CHECK(pthread_setspecific(threadSpecificKey_, p) == 0); } /** @@ -112,7 +112,7 @@ private: template class ThreadLocalD { public: - ThreadLocalD() { PCHECK(pthread_key_create(&threadSpecificKey_, NULL) == 0); } + ThreadLocalD() { CHECK(pthread_key_create(&threadSpecificKey_, NULL) == 0); } ~ThreadLocalD() { pthread_key_delete(threadSpecificKey_); for (auto t : threadMap_) { @@ -127,7 +127,7 @@ public: T* p = (T*)pthread_getspecific(threadSpecificKey_); if (!p) { p = new T(); - PCHECK(pthread_setspecific(threadSpecificKey_, p) == 0); + CHECK(pthread_setspecific(threadSpecificKey_, p) == 0); updateMap(p); } return p; @@ -141,7 +141,7 @@ public: if (T* q = (T*)pthread_getspecific(threadSpecificKey_)) { dataDestructor(q); } - PCHECK(pthread_setspecific(threadSpecificKey_, p) == 0); + CHECK(pthread_setspecific(threadSpecificKey_, p) == 0); updateMap(p); } -- GitLab From 2e302085d7c0a79a8516533b29450a1febc25d79 Mon Sep 17 00:00:00 2001 From: gongweibao Date: Wed, 5 Jul 2017 09:49:30 +0000 Subject: [PATCH 1058/3256] fix bugs --- cmake/generic.cmake | 4 ++-- go/pserver/client/c/CMakeLists.txt | 3 ++- go/pserver/client/c/test/CMakeLists.txt | 4 +--- paddle/api/CMakeLists.txt | 1 + 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cmake/generic.cmake b/cmake/generic.cmake index 74396abdbbb..d51b95a5d7f 100644 --- a/cmake/generic.cmake +++ b/cmake/generic.cmake @@ -192,9 +192,9 @@ function(cc_test TARGET_NAME) set(multiValueArgs SRCS DEPS) cmake_parse_arguments(cc_test "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) add_executable(${TARGET_NAME} ${cc_test_SRCS}) - target_link_libraries(${TARGET_NAME} ${cc_test_DEPS} gtest gtest_main) + target_link_libraries(${TARGET_NAME} ${cc_test_DEPS} gtest gtest_main -lstdc++ -lm) add_dependencies(${TARGET_NAME} ${cc_test_DEPS} gtest gtest_main) - add_test(${TARGET_NAME} ${TARGET_NAME}) + add_test(NAME ${TARGET_NAME} COMMAND ${TARGET_NAME} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) endif() endfunction(cc_test) diff --git a/go/pserver/client/c/CMakeLists.txt b/go/pserver/client/c/CMakeLists.txt index a3fcaeef190..d2ac20e25cc 100644 --- a/go/pserver/client/c/CMakeLists.txt +++ b/go/pserver/client/c/CMakeLists.txt @@ -1,5 +1,6 @@ cc_library(paddle_go_optimizer DEPS paddle_optimizer paddle_proto glog gflags protobuf) go_library(paddle_pserver_cclient STATIC DEPS paddle_go_optimizer) if(WITH_TESTING) - add_subdirectory(test) + # TODO: add unit test + #add_subdirectory(test) endif() diff --git a/go/pserver/client/c/test/CMakeLists.txt b/go/pserver/client/c/test/CMakeLists.txt index 44bc1837383..dce8645ce75 100644 --- a/go/pserver/client/c/test/CMakeLists.txt +++ b/go/pserver/client/c/test/CMakeLists.txt @@ -1,4 +1,2 @@ -# FIXME:It's ugly -#cc_test(test_cclient SRCS test_cclient.c DEPS paddle_pserver_cclient paddle_go_optimizer) -cc_test(test_cclient SRCS test_cclient.c DEPS paddle_pserver_cclient paddle_optimizer paddle_proto glog gflags protobuf) +cc_test(test_cclient SRCS test_cclient.c DEPS paddle_pserver_cclient paddle_go_optimizer) add_style_check_target(test_cclient test_cclient.c) diff --git a/paddle/api/CMakeLists.txt b/paddle/api/CMakeLists.txt index 39d8aa075bc..84da89a1422 100644 --- a/paddle/api/CMakeLists.txt +++ b/paddle/api/CMakeLists.txt @@ -66,6 +66,7 @@ SWIG_LINK_LIBRARIES(swig_paddle paddle_trainer_lib paddle_network paddle_parameter + paddle_optimizer paddle_math paddle_utils paddle_proto -- GitLab From 204869c2dae9b03b1155be106484ef328e942132 Mon Sep 17 00:00:00 2001 From: gongweibao Date: Wed, 5 Jul 2017 10:10:18 +0000 Subject: [PATCH 1059/3256] fix bugs --- 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 1ccee686df4..ab60f1a38dd 100644 --- a/paddle/scripts/docker/build.sh +++ b/paddle/scripts/docker/build.sh @@ -78,7 +78,7 @@ paddle version # PaddlePaddle. This awkwardness is due to # https://github.com/PaddlePaddle/Paddle/issues/1854. It also # describes a solution. -if [ ${WITH_DOC} == "ON" ]; then +if [[ ${WITH_DOC} == "ON" ]]; then cat < Date: Wed, 5 Jul 2017 18:18:32 +0800 Subject: [PATCH 1060/3256] fix auto cgo LDFLAGS --- go/pserver/optimizer.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/go/pserver/optimizer.go b/go/pserver/optimizer.go index d84f55b9874..54d10820940 100644 --- a/go/pserver/optimizer.go +++ b/go/pserver/optimizer.go @@ -2,7 +2,7 @@ package pserver // #cgo CFLAGS: -I ../../ // //FIXME: ldflags contain "build" path -// #cgo LDFLAGS: ../../build/go/pserver/client/c/libpaddle_go_optimizer.a -lstdc++ -lm +// #cgo LDFLAGS: ${SRCDIR}/../../build/go/pserver/client/c/libpaddle_go_optimizer.a -lstdc++ -lm // #include "paddle/optimizer/optimizer.h" // #include // #include @@ -56,8 +56,8 @@ func newOptimizer(paramWithConfigs ParameterWithConfig) *optimizer { func (o *optimizer) GetWeights() []byte { var buffer unsafe.Pointer - buffer_len := C.paddle_optimizer_get_weights(o.opt, &buffer) - return cArrayToSlice(buffer, int(buffer_len)*C.sizeof_float) + bufferLen := C.paddle_optimizer_get_weights(o.opt, &buffer) + return cArrayToSlice(buffer, int(bufferLen)*C.sizeof_float) } func (o *optimizer) UpdateParameter(g Gradient) error { -- GitLab From c37da0bd3ba14318198bfc6dd8f8ba5e13c1a269 Mon Sep 17 00:00:00 2001 From: yangyaming Date: Wed, 5 Jul 2017 18:36:47 +0800 Subject: [PATCH 1061/3256] Remove hasParamInitialValue flag. --- paddle/gserver/tests/LayerGradUtil.cpp | 9 +++------ paddle/gserver/tests/LayerGradUtil.h | 2 -- paddle/gserver/tests/test_LayerGrad.cpp | 1 - 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/paddle/gserver/tests/LayerGradUtil.cpp b/paddle/gserver/tests/LayerGradUtil.cpp index 66aafba844d..15b8cedeb83 100644 --- a/paddle/gserver/tests/LayerGradUtil.cpp +++ b/paddle/gserver/tests/LayerGradUtil.cpp @@ -498,12 +498,9 @@ void initTestLayer(TestConfig testConf, paraConfig.add_dims((*layerMap)[input.input_layer_name()]->getSize()); paraConfig.add_dims(testConf.layerConfig.size()); } - if (testConf.hasParamInitialValue) { - paraConfig.set_initial_mean(testConf.paramInitialMean); - paraConfig.set_initial_std(testConf.paramInitialStd); - } else { - paraConfig.set_initial_std(1); - } + CHECK_GE(testConf.paramInitialStd, 0); + paraConfig.set_initial_mean(testConf.paramInitialMean); + paraConfig.set_initial_std(testConf.paramInitialStd); initParameter(paraName, paraSize, inputDef.isStatic, false, paraConfig); } } diff --git a/paddle/gserver/tests/LayerGradUtil.h b/paddle/gserver/tests/LayerGradUtil.h index 9dbd2027578..d299b4dd094 100644 --- a/paddle/gserver/tests/LayerGradUtil.h +++ b/paddle/gserver/tests/LayerGradUtil.h @@ -127,7 +127,6 @@ struct TestConfig { size_t biasSize; real paramInitialMean; real paramInitialStd; - bool hasParamInitialValue; bool testAccumulate; bool testState; bool staticBias; @@ -136,7 +135,6 @@ struct TestConfig { : biasSize(0), paramInitialMean(0.0), paramInitialStd(1.0), - hasParamInitialValue(false), testAccumulate(true), testState(false), staticBias(false), diff --git a/paddle/gserver/tests/test_LayerGrad.cpp b/paddle/gserver/tests/test_LayerGrad.cpp index 6441e08b48d..bf0136a10f1 100644 --- a/paddle/gserver/tests/test_LayerGrad.cpp +++ b/paddle/gserver/tests/test_LayerGrad.cpp @@ -1661,7 +1661,6 @@ TEST(Layer, PadLayer) { TEST(Layer, CrossChannelNormLayer) { TestConfig config; - config.hasParamInitialValue = true; config.paramInitialMean = 1.; config.paramInitialStd = 0.; config.layerConfig.set_type("norm"); -- GitLab From ada1c20bbc2520d566b7d2bd2a56cf94cbcddd27 Mon Sep 17 00:00:00 2001 From: liaogang Date: Wed, 5 Jul 2017 19:16:02 +0800 Subject: [PATCH 1062/3256] FIX: Buddy Allocator Free with Merge feature --- paddle/memory/detail/buddy_allocator.cc | 33 ++++++++++++++++++++++--- paddle/memory/detail/buddy_allocator.h | 15 +++++++---- paddle/memory/detail/memory_block.cc | 4 +-- paddle/platform/CMakeLists.txt | 2 +- 4 files changed, 42 insertions(+), 12 deletions(-) diff --git a/paddle/memory/detail/buddy_allocator.cc b/paddle/memory/detail/buddy_allocator.cc index aa5b6b557c5..9f334a7048f 100644 --- a/paddle/memory/detail/buddy_allocator.cc +++ b/paddle/memory/detail/buddy_allocator.cc @@ -89,9 +89,8 @@ void BuddyAllocator::Free(void* p) { block->index(cache_)); // Invalidate GPU allocation from cache - if (system_allocator_->UseGpu()) { - cache_.invalidate(block); - } + cache_.invalidate(block); + return; } @@ -104,12 +103,35 @@ void BuddyAllocator::Free(void* p) { if (block->has_right_buddy(cache_)) { DLOG(INFO) << "Merging this block " << block << " with its right buddy " << block->right_buddy(cache_); + + auto right_buddy = block->right_buddy(cache_); + + if (right_buddy->type(cache_) == MemoryBlock::FREE_CHUNK) { + // Take away right buddy from pool + pool_.erase({right_buddy->index(cache_), right_buddy->total_size(cache_), + right_buddy}); + + // merge its right buddy to the block + block->merge(cache_, right_buddy); + } } // Trying to merge the left buddy if (block->has_left_buddy(cache_)) { DLOG(INFO) << "Merging this block " << block << " with its left buddy " << block->left_buddy(cache_); + + auto left_buddy = block->left_buddy(cache_); + + if (left_buddy->type(cache_) == MemoryBlock::FREE_CHUNK) { + // Take away right buddy from pool + pool_.erase({left_buddy->index(cache_), left_buddy->total_size(cache_), + left_buddy}); + + // merge the block to its left buddy + left_buddy->merge(cache_, block); + block = left_buddy; + } } // Dumping this block into pool @@ -167,13 +189,16 @@ BuddyAllocator::PoolSet::iterator BuddyAllocator::FindExistChunk(size_t size) { while (1) { auto it = pool_.lower_bound({index, size, nullptr}); + + // no match chunk memory if (it == pool_.end()) return it; if (std::get<0>(*it) > index) { + // find suitable one if (std::get<1>(*it) >= size) { return it; } - + // update and continue index = std::get<0>(*it); continue; } diff --git a/paddle/memory/detail/buddy_allocator.h b/paddle/memory/detail/buddy_allocator.h index ecf23b77ae8..2fd9c8162a9 100644 --- a/paddle/memory/detail/buddy_allocator.h +++ b/paddle/memory/detail/buddy_allocator.h @@ -42,14 +42,14 @@ class BuddyAllocator { void Free(void*); size_t Used(); - public: + private: // Disable copy and assignment. BuddyAllocator(const BuddyAllocator&) = delete; BuddyAllocator& operator=(const BuddyAllocator&) = delete; - private: - // Tuple type: allocator index, memory size, memory address + // Tuple (allocator index, memory size, memory address) using IndexSizeAddress = std::tuple; + // Each element in PoolSet is a free allocation using PoolSet = std::set; /*! \brief Allocate fixed-size memory from system */ @@ -57,7 +57,6 @@ class BuddyAllocator { /*! \brief If existing chunks are not suitable, refill pool */ PoolSet::iterator RefillPool(); - /** * \brief Find the suitable chunk from existing pool * @@ -77,13 +76,19 @@ class BuddyAllocator { size_t max_chunk_size_; // the maximum size of each chunk private: + /** + * \brief A list of free allocation + * + * \note Only store free chunk memory in pool + */ PoolSet pool_; private: - // Unify the metadata format between GPU and CPU allocations + /*! Unify the metadata format between GPU and CPU allocations */ MetadataCache cache_; private: + /*! Allocate CPU/GPU memory from system */ SystemAllocator* system_allocator_; std::mutex mutex_; }; diff --git a/paddle/memory/detail/memory_block.cc b/paddle/memory/detail/memory_block.cc index eaa97e7b4ad..bc67bcef0fd 100644 --- a/paddle/memory/detail/memory_block.cc +++ b/paddle/memory/detail/memory_block.cc @@ -91,8 +91,8 @@ void MemoryBlock::split(MetadataCache& cache, size_t size) { void MemoryBlock::merge(MetadataCache& cache, MemoryBlock* right_buddy) { // only free blocks can be merged - PADDLE_ASSERT(type(cache) == FREE_MEMORY); - PADDLE_ASSERT(right_buddy->type(cache) == FREE_MEMORY); + PADDLE_ASSERT(type(cache) == FREE_CHUNK); + PADDLE_ASSERT(right_buddy->type(cache) == FREE_CHUNK); auto metadata = cache.load(this); diff --git a/paddle/platform/CMakeLists.txt b/paddle/platform/CMakeLists.txt index 2f3d1c061e3..0ad11f1b106 100644 --- a/paddle/platform/CMakeLists.txt +++ b/paddle/platform/CMakeLists.txt @@ -6,4 +6,4 @@ nv_library(gpu_info SRCS gpu_info.cc DEPS gflags) cc_library(place SRCS place.cc) cc_test(place_test SRCS place_test.cc DEPS place glog gflags) -cc_library(dynamic_loader SRCS dynload/dynamic_loader.cc) +cc_library(dynamic_loader SRCS dynload/dynamic_loader.cc DEPS gflags) -- GitLab From b68e90be820f7a925e114f76f27156e728fc9e79 Mon Sep 17 00:00:00 2001 From: "yi.wu" Date: Wed, 5 Jul 2017 21:30:28 +0800 Subject: [PATCH 1063/3256] fix go test building --- go/pserver/client/c/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/go/pserver/client/c/CMakeLists.txt b/go/pserver/client/c/CMakeLists.txt index a3fcaeef190..34aa7ca5ff5 100644 --- a/go/pserver/client/c/CMakeLists.txt +++ b/go/pserver/client/c/CMakeLists.txt @@ -1,4 +1,5 @@ cc_library(paddle_go_optimizer DEPS paddle_optimizer paddle_proto glog gflags protobuf) +target_link_libraries(paddle_go_optimizer stdc++ m) go_library(paddle_pserver_cclient STATIC DEPS paddle_go_optimizer) if(WITH_TESTING) add_subdirectory(test) -- GitLab From 78f1274d6e2c75d0036ae2a7da6cbccfc844b8f0 Mon Sep 17 00:00:00 2001 From: "yi.wu" Date: Wed, 5 Jul 2017 21:40:12 +0800 Subject: [PATCH 1064/3256] remove unnessesary cc_test link --- cmake/generic.cmake | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmake/generic.cmake b/cmake/generic.cmake index d51b95a5d7f..c2962e35efd 100644 --- a/cmake/generic.cmake +++ b/cmake/generic.cmake @@ -192,7 +192,7 @@ function(cc_test TARGET_NAME) set(multiValueArgs SRCS DEPS) cmake_parse_arguments(cc_test "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) add_executable(${TARGET_NAME} ${cc_test_SRCS}) - target_link_libraries(${TARGET_NAME} ${cc_test_DEPS} gtest gtest_main -lstdc++ -lm) + target_link_libraries(${TARGET_NAME} ${cc_test_DEPS} gtest gtest_main) add_dependencies(${TARGET_NAME} ${cc_test_DEPS} gtest gtest_main) add_test(NAME ${TARGET_NAME} COMMAND ${TARGET_NAME} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) endif() @@ -285,7 +285,7 @@ function(go_library TARGET_NAME) add_custom_command(TARGET ${TARGET_NAME} POST_BUILD COMMAND rm "${${TARGET_NAME}_LIB_PATH}" # Golang build source code - COMMAND env LIBRARY_PATH=${CMAKE_BINARY_DIR}/go/pserver/client/c/:$ENV{LIBRARY_PATH} GOPATH=${GOPATH} ${CMAKE_Go_COMPILER} build ${BUILD_MODE} + COMMAND GOPATH=${GOPATH} ${CMAKE_Go_COMPILER} build ${BUILD_MODE} -o "${${TARGET_NAME}_LIB_PATH}" "./${CMAKE_CURRENT_SOURCE_REL_DIR}/${GO_SOURCE}" # must run under GOPATH -- GitLab From 4d2a83c750c6168d16a4ee302b0c69e553bd0b34 Mon Sep 17 00:00:00 2001 From: "yi.wu" Date: Wed, 5 Jul 2017 21:58:46 +0800 Subject: [PATCH 1065/3256] update again --- go/pserver/client/c/test/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go/pserver/client/c/test/CMakeLists.txt b/go/pserver/client/c/test/CMakeLists.txt index f287f850719..dce8645ce75 100644 --- a/go/pserver/client/c/test/CMakeLists.txt +++ b/go/pserver/client/c/test/CMakeLists.txt @@ -1,2 +1,2 @@ -cc_test(test_cclient SRCS test_cclient.c DEPS paddle_pserver_cclient) +cc_test(test_cclient SRCS test_cclient.c DEPS paddle_pserver_cclient paddle_go_optimizer) add_style_check_target(test_cclient test_cclient.c) -- GitLab From 74691789e9e5ee782adb003642f66699603b20e2 Mon Sep 17 00:00:00 2001 From: liaogang Date: Thu, 6 Jul 2017 01:16:03 +0800 Subject: [PATCH 1066/3256] ENH: add memory unit test --- paddle/memory/CMakeLists.txt | 6 ++-- paddle/memory/detail/CMakeLists.txt | 4 +-- paddle/memory/detail/buddy_allocator.cc | 33 +++++++++++------ paddle/memory/detail/buddy_allocator.h | 27 -------------- paddle/memory/memory.cc | 42 ++++++++++++++++++---- paddle/memory/memory_test.cc | 48 +++++++++++++++++++++++++ paddle/platform/gpu_info.cc | 2 +- 7 files changed, 112 insertions(+), 50 deletions(-) create mode 100644 paddle/memory/memory_test.cc diff --git a/paddle/memory/CMakeLists.txt b/paddle/memory/CMakeLists.txt index 8c290712fc9..fac442cca56 100644 --- a/paddle/memory/CMakeLists.txt +++ b/paddle/memory/CMakeLists.txt @@ -1,11 +1,11 @@ add_subdirectory(detail) -cc_library(memory - SRCS - memory.cc) +cc_library(memory SRCS memory.cc) cc_library(paddle_memory DEPS memory meta_data meta_cache memory_block buddy_allocator system_allocator) + +cc_test(memory_test SRCS memory_test.cc DEPS place paddle_memory) diff --git a/paddle/memory/detail/CMakeLists.txt b/paddle/memory/detail/CMakeLists.txt index 6cb6422e473..b9c3fc31c15 100644 --- a/paddle/memory/detail/CMakeLists.txt +++ b/paddle/memory/detail/CMakeLists.txt @@ -1,7 +1,7 @@ if(${WITH_GPU}) - nv_library(system_allocator SRCS system_allocator.cc DEPS gflags gpu_info) + nv_library(system_allocator SRCS system_allocator.cc DEPS gflags cpu_info gpu_info) else(${WITH_GPU}) - cc_library(system_allocator SRCS system_allocator.cc DEPS gflags) + cc_library(system_allocator SRCS system_allocator.cc DEPS gflags cpu_info) endif(${WITH_GPU}) cc_test(system_allocator_test SRCS system_allocator_test.cc DEPS system_allocator) diff --git a/paddle/memory/detail/buddy_allocator.cc b/paddle/memory/detail/buddy_allocator.cc index 9f334a7048f..ed2eedf9af8 100644 --- a/paddle/memory/detail/buddy_allocator.cc +++ b/paddle/memory/detail/buddy_allocator.cc @@ -24,10 +24,20 @@ BuddyAllocator::BuddyAllocator(SystemAllocator* system_allocator, : min_chunk_size_(min_chunk_size), max_chunk_size_(max_chunk_size), cache_(system_allocator->UseGpu()), - system_allocator_(std::move(system_allocator)) { - PADDLE_ASSERT(min_chunk_size > 0); - PADDLE_ASSERT(max_chunk_size > 0); - PADDLE_ASSERT(system_allocator != nullptr); + system_allocator_(std::move(system_allocator)) {} + +BuddyAllocator::~BuddyAllocator() { + DLOG(INFO) << "BuddyAllocator Disconstructor makes sure that all of these " + "have actually been freed"; + while (!pool_.empty()) { + auto block = static_cast(std::get<2>(*pool_.begin())); + DLOG(INFO) << "Free from block (" << block << ", " << max_chunk_size_ + << ")"; + + system_allocator_->Free(block, max_chunk_size_, block->index(cache_)); + cache_.invalidate(block); + pool_.erase(pool_.begin()); + } } inline size_t align(size_t size, size_t alignment) { @@ -62,7 +72,7 @@ void* BuddyAllocator::Alloc(size_t unaligned_size) { return nullptr; } } else { - DLOG(INFO) << " Allocation from existing memory block " << std::get<2>(*it) + DLOG(INFO) << "Allocation from existing memory block " << std::get<2>(*it) << " at address " << reinterpret_cast(std::get<2>(*it))->data(); } @@ -142,6 +152,8 @@ void BuddyAllocator::Free(void* p) { // TODO(gangliao): Clean up if existing too much free memory } +size_t BuddyAllocator::Used() { return total_used_; } + void* BuddyAllocator::SystemAlloc(size_t size) { size_t index = 0; void* p = system_allocator_->Alloc(index, size); @@ -172,7 +184,7 @@ BuddyAllocator::PoolSet::iterator BuddyAllocator::RefillPool() { if (p == nullptr) return pool_.end(); - DLOG(INFO) << " Creating and inserting new block " << p + DLOG(INFO) << "Creating and inserting new block " << p << " from system allocator"; static_cast(p)->init(cache_, MemoryBlock::FREE_CHUNK, index, @@ -211,20 +223,19 @@ void* BuddyAllocator::SplitToAlloc(BuddyAllocator::PoolSet::iterator it, auto block = static_cast(std::get<2>(*it)); pool_.erase(it); - DLOG(INFO) << " Split block (" << block << ", " << block->total_size(cache_) + DLOG(INFO) << "Split block (" << block << ", " << block->total_size(cache_) << ") into"; block->split(cache_, size); - DLOG(INFO) << " Left block (" << block << ", " << block->total_size(cache_) + DLOG(INFO) << "Left block (" << block << ", " << block->total_size(cache_) << ")"; block->set_type(cache_, MemoryBlock::ARENA_CHUNK); // the rest of memory if exist if (block->has_right_buddy(cache_)) { if (block->right_buddy(cache_)->type(cache_) == MemoryBlock::FREE_CHUNK) { - DLOG(INFO) << " Insert right block (" << block->right_buddy(cache_) - << ", " << block->right_buddy(cache_)->total_size(cache_) - << ")"; + DLOG(INFO) << "Insert right block (" << block->right_buddy(cache_) << ", " + << block->right_buddy(cache_)->total_size(cache_) << ")"; pool_.insert({block->right_buddy(cache_)->index(cache_), block->right_buddy(cache_)->total_size(cache_), diff --git a/paddle/memory/detail/buddy_allocator.h b/paddle/memory/detail/buddy_allocator.h index 2fd9c8162a9..eeb2dc88364 100644 --- a/paddle/memory/detail/buddy_allocator.h +++ b/paddle/memory/detail/buddy_allocator.h @@ -93,33 +93,6 @@ class BuddyAllocator { std::mutex mutex_; }; -BuddyAllocator* GetCPUBuddyAllocator() { - static BuddyAllocator* a = nullptr; - if (a == nullptr) { - a = new BuddyAllocator(new CPUAllocator, platform::CpuMinChunkSize(), - platform::CpuMaxChunkSize()); - } - return a; -} - -#ifndef PADDLE_ONLY_CPU // The following code are for CUDA. - -BuddyAllocator* GetGPUBuddyAllocator(int gpu_id) { - static BuddyAllocator** as = NULL; - if (as == NULL) { - int gpu_num = platform::GpuDeviceCount(); - as = new BuddyAllocator*[gpu_num]; - for (int gpu = 0; gpu < gpu_num; gpu++) { - as[gpu] = - new BuddyAllocator(new GPUAllocator, platform::GpuMinChunkSize(), - platform::GpuMaxChunkSize()); - } - } - return as[gpu_id]; -} - -#endif // PADDLE_ONLY_CPU - } // namespace detail } // namespace memory } // namespace paddle diff --git a/paddle/memory/memory.cc b/paddle/memory/memory.cc index 0d123d99e23..dde6ff0ef3e 100644 --- a/paddle/memory/memory.cc +++ b/paddle/memory/memory.cc @@ -22,37 +22,67 @@ limitations under the License. */ namespace paddle { namespace memory { +detail::BuddyAllocator* GetCPUBuddyAllocator() { + static detail::BuddyAllocator* a = nullptr; + if (a == nullptr) { + a = new detail::BuddyAllocator(new detail::CPUAllocator, + platform::CpuMinChunkSize(), + platform::CpuMaxChunkSize()); + } + return a; +} + +#ifndef PADDLE_ONLY_CPU // The following code are for CUDA. + +detail::BuddyAllocator* GetGPUBuddyAllocator(int gpu_id) { + static detail::BuddyAllocator** as = NULL; + if (as == NULL) { + int gpu_num = platform::GpuDeviceCount(); + as = new detail::BuddyAllocator*[gpu_num]; + for (int gpu = 0; gpu < gpu_num; gpu++) { + platform::SetDeviceId(gpu); + as[gpu] = new detail::BuddyAllocator(new detail::GPUAllocator, + platform::GpuMinChunkSize(), + platform::GpuMaxChunkSize()); + } + } + return as[gpu_id]; +} + +#endif // PADDLE_ONLY_CPU + void* Alloc(platform::Place pl, size_t size) { #ifndef PADDLE_ONLY_CPU if (paddle::platform::is_gpu_place(pl)) { size_t gpu_id = boost::get(pl).device; - return detail::GetGPUBuddyAllocator(gpu_id)->Alloc(size); + return GetGPUBuddyAllocator(gpu_id)->Alloc(size); } #endif // PADDLE_ONLY_CPU PADDLE_ASSERT(paddle::platform::is_cpu_place(pl)); - return detail::GetCPUBuddyAllocator()->Alloc(size); + return GetCPUBuddyAllocator()->Alloc(size); } void Free(paddle::platform::Place pl, void* p) { #ifndef PADDLE_ONLY_CPU if (paddle::platform::is_gpu_place(pl)) { size_t gpu_id = boost::get(pl).device; - detail::GetGPUBuddyAllocator(gpu_id)->Free(p); + GetGPUBuddyAllocator(gpu_id)->Free(p); + return; } #endif // PADDLE_ONLY_CPU PADDLE_ASSERT(paddle::platform::is_cpu_place(pl)); - detail::GetCPUBuddyAllocator()->Free(p); + GetCPUBuddyAllocator()->Free(p); } size_t Used(paddle::platform::Place pl) { #ifndef PADDLE_ONLY_CPU if (paddle::platform::is_gpu_place(pl)) { size_t gpu_id = boost::get(pl).device; - return detail::GetGPUBuddyAllocator(gpu_id)->Used(); + return GetGPUBuddyAllocator(gpu_id)->Used(); } #endif // PADDLE_ONLY_CPU PADDLE_ASSERT(paddle::platform::is_cpu_place(pl)); - return detail::GetCPUBuddyAllocator()->Used(); + return GetCPUBuddyAllocator()->Used(); } } // namespace memory diff --git a/paddle/memory/memory_test.cc b/paddle/memory/memory_test.cc new file mode 100644 index 00000000000..a98ccbb8068 --- /dev/null +++ b/paddle/memory/memory_test.cc @@ -0,0 +1,48 @@ +/* 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/memory/memory.h" +#include "paddle/platform/place.h" + +#include "gtest/gtest.h" + +TEST(BuddyAllocator, CPUAllocation) { + void *p = nullptr; + + EXPECT_EQ(p, nullptr); + + paddle::platform::CPUPlace cpu; + p = paddle::memory::Alloc(cpu, 4096); + + EXPECT_NE(p, nullptr); + + paddle::memory::Free(cpu, p); +} + +#ifndef PADDLE_ONLY_CPU + +TEST(BuddyAllocator, GPUAllocation) { + void *p = nullptr; + + EXPECT_EQ(p, nullptr); + + paddle::platform::GPUPlace gpu(0); + p = paddle::memory::Alloc(gpu, 4096); + + EXPECT_NE(p, nullptr); + + paddle::memory::Free(gpu, p); +} + +#endif // PADDLE_ONLY_CPU diff --git a/paddle/platform/gpu_info.cc b/paddle/platform/gpu_info.cc index 05a243c5069..fe475d23ce0 100644 --- a/paddle/platform/gpu_info.cc +++ b/paddle/platform/gpu_info.cc @@ -56,7 +56,7 @@ size_t GpuMaxAllocSize() { GpuMemoryUsage(available, total); // Reserve the rest for page tables, etc. - return total * FLAGS_fraction_of_gpu_memory_to_use; + return static_cast(total * FLAGS_fraction_of_gpu_memory_to_use); } size_t GpuMinChunkSize() { -- GitLab From 936cd1e11885daf35ae34b9fbf11a798e9e51022 Mon Sep 17 00:00:00 2001 From: liaogang Date: Thu, 6 Jul 2017 01:34:51 +0800 Subject: [PATCH 1067/3256] FIX: code format --- paddle/memory/memory.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paddle/memory/memory.cc b/paddle/memory/memory.cc index dde6ff0ef3e..43f2084e8df 100644 --- a/paddle/memory/memory.cc +++ b/paddle/memory/memory.cc @@ -29,7 +29,7 @@ detail::BuddyAllocator* GetCPUBuddyAllocator() { platform::CpuMinChunkSize(), platform::CpuMaxChunkSize()); } - return a; + return a; } #ifndef PADDLE_ONLY_CPU // The following code are for CUDA. -- GitLab From 5d2e8edb3ec86e13b7684cbe372650d21fe7954d Mon Sep 17 00:00:00 2001 From: liaogang Date: Thu, 6 Jul 2017 01:37:20 +0800 Subject: [PATCH 1068/3256] FIX: dynamic loader deps --- paddle/platform/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paddle/platform/CMakeLists.txt b/paddle/platform/CMakeLists.txt index 0ad11f1b106..bf3e8f31abe 100644 --- a/paddle/platform/CMakeLists.txt +++ b/paddle/platform/CMakeLists.txt @@ -6,4 +6,4 @@ nv_library(gpu_info SRCS gpu_info.cc DEPS gflags) cc_library(place SRCS place.cc) cc_test(place_test SRCS place_test.cc DEPS place glog gflags) -cc_library(dynamic_loader SRCS dynload/dynamic_loader.cc DEPS gflags) +cc_library(dynamic_loader SRCS dynload/dynamic_loader.cc DEPS gflags glog) -- GitLab From 7848a3fb5c6de5c21a6c1c34a9d12e8e866c760c Mon Sep 17 00:00:00 2001 From: wuyi05 Date: Thu, 6 Jul 2017 09:45:01 +0800 Subject: [PATCH 1069/3256] remove cclient test --- go/pserver/client/c/CMakeLists.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/go/pserver/client/c/CMakeLists.txt b/go/pserver/client/c/CMakeLists.txt index a3fcaeef190..d5c1ed38e55 100644 --- a/go/pserver/client/c/CMakeLists.txt +++ b/go/pserver/client/c/CMakeLists.txt @@ -1,5 +1,7 @@ cc_library(paddle_go_optimizer DEPS paddle_optimizer paddle_proto glog gflags protobuf) go_library(paddle_pserver_cclient STATIC DEPS paddle_go_optimizer) if(WITH_TESTING) - add_subdirectory(test) + # FIXME: this test requires pserver which is not managed by the test + # we need some kind of e2e testing machanism. + # add_subdirectory(test) endif() -- GitLab From 1264480b048cf68e29f3dffa91e228425df55908 Mon Sep 17 00:00:00 2001 From: Superjom Date: Thu, 6 Jul 2017 10:48:00 +0800 Subject: [PATCH 1070/3256] fix ci --- paddle/framework/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/paddle/framework/CMakeLists.txt b/paddle/framework/CMakeLists.txt index b33014210fb..fc2fbf88f12 100644 --- a/paddle/framework/CMakeLists.txt +++ b/paddle/framework/CMakeLists.txt @@ -11,5 +11,6 @@ proto_library(op_proto SRCS op_proto.proto DEPS attr_type) cc_test(op_proto_test SRCS op_proto_test.cc DEPS op_proto protobuf) proto_library(net_proto SRCS net_proto.proto DEPS op_proto) #cc_library(net SRCS net.cc DEPS net_proto attr_type op_proto) + proto_library(op_desc SRCS op_desc.proto DEPS attr_type) cc_test(op_desc_test SRCS op_desc_test.cc DEPS op_desc protobuf) -- GitLab From 3ad8e364715915fba5909c137834e34f38b6e9ac Mon Sep 17 00:00:00 2001 From: liaogang Date: Thu, 6 Jul 2017 11:24:01 +0800 Subject: [PATCH 1071/3256] FIX: merge static libs with propagation dependencies --- cmake/generic.cmake | 51 ++++++++++++++++++++++++++------------------- 1 file changed, 30 insertions(+), 21 deletions(-) diff --git a/cmake/generic.cmake b/cmake/generic.cmake index cae9524b2fe..87d8caaec40 100644 --- a/cmake/generic.cmake +++ b/cmake/generic.cmake @@ -99,15 +99,37 @@ function(merge_static_libs TARGET_NAME) set(libs ${ARGN}) list(REMOVE_DUPLICATES libs) - # First get the file names of the libraries to be merged + # Get all propagation dependencies from the merged libraries foreach(lib ${libs}) + list(APPEND libs_deps ${${lib}_LIB_DEPENDS}) + endforeach() + + # To produce a library we need at least one source file. + # It is created by add_custom_command below and will helps + # also help to track dependencies. + set(dummyfile ${CMAKE_CURRENT_BINARY_DIR}/${TARGET_NAME}_dummy.c) + + # Make the generated dummy source file depended on all static input + # libs. If input lib changes,the source file is touched + # which causes the desired effect (relink). + add_custom_command(OUTPUT ${dummyfile} + COMMAND ${CMAKE_COMMAND} -E touch ${dummyfile} + DEPENDS ${libs}) + + # Generate dummy staic lib + file(WRITE ${dummyfile} "const char * dummy = \"${dummyfile}\";") + add_library(${TARGET_NAME} STATIC ${dummyfile}) + target_link_libraries(${TARGET_NAME} ${libs_deps}) + + foreach(lib ${libs}) + # Get the file names of the libraries to be merged set(libfiles ${libfiles} $) endforeach() + # Get the file name of the generated library + set(outlibfile "$") + if(APPLE) # Use OSX's libtool to merge archives - set(dummyfile ${CMAKE_CURRENT_BINARY_DIR}/${TARGET_NAME}_dummy.c) - file(WRITE ${dummyfile} "const char * dummy = \"${dummyfile}\";") - add_library(${TARGET_NAME} STATIC ${dummyfile}) add_custom_command(TARGET ${TARGET_NAME} POST_BUILD COMMAND rm "${CMAKE_CURRENT_BINARY_DIR}/lib${TARGET_NAME}.a" COMMAND /usr/bin/libtool -static -o "${CMAKE_CURRENT_BINARY_DIR}/lib${TARGET_NAME}.a" ${libfiles}) @@ -117,7 +139,8 @@ function(merge_static_libs TARGET_NAME) set(objdir ${lib}.objdir) add_custom_command(OUTPUT ${objdir} - COMMAND ${CMAKE_COMMAND} -E make_directory ${objdir}) + COMMAND ${CMAKE_COMMAND} -E make_directory ${objdir} + DEPENDS ${lib}) add_custom_command(OUTPUT ${objlistfile} COMMAND ${CMAKE_AR} -x "$" @@ -125,23 +148,9 @@ function(merge_static_libs TARGET_NAME) DEPENDS ${lib} ${objdir} WORKING_DIRECTORY ${objdir}) - # Empty dummy source file that goes into merged library - set(mergebase ${lib}.mergebase.c) - add_custom_command(OUTPUT ${mergebase} - COMMAND ${CMAKE_COMMAND} -E touch ${mergebase} - DEPENDS ${objlistfile}) - - list(APPEND mergebases "${mergebase}") - endforeach() - - # We need a target for the output merged library - add_library(${TARGET_NAME} STATIC ${mergebases}) - set(outlibfile "$") - - foreach(lib ${libs}) add_custom_command(TARGET ${TARGET_NAME} POST_BUILD - COMMAND ${CMAKE_AR} ru ${outlibfile} @"../${lib}.objlist" - WORKING_DIRECTORY ${lib}.objdir) + COMMAND ${CMAKE_AR} ru ${outlibfile} *.o + WORKING_DIRECTORY ${objdir}) endforeach() add_custom_command(TARGET ${TARGET_NAME} POST_BUILD -- GitLab From d6ecae779a28d51e669a4c029d00ec57a98f2bc8 Mon Sep 17 00:00:00 2001 From: liaogang Date: Thu, 6 Jul 2017 11:25:28 +0800 Subject: [PATCH 1072/3256] FIX: propagation dependencies and out of date rebuild --- cmake/generic.cmake | 51 ++++++++++++++++++++++++++------------------- 1 file changed, 30 insertions(+), 21 deletions(-) diff --git a/cmake/generic.cmake b/cmake/generic.cmake index cae9524b2fe..87d8caaec40 100644 --- a/cmake/generic.cmake +++ b/cmake/generic.cmake @@ -99,15 +99,37 @@ function(merge_static_libs TARGET_NAME) set(libs ${ARGN}) list(REMOVE_DUPLICATES libs) - # First get the file names of the libraries to be merged + # Get all propagation dependencies from the merged libraries foreach(lib ${libs}) + list(APPEND libs_deps ${${lib}_LIB_DEPENDS}) + endforeach() + + # To produce a library we need at least one source file. + # It is created by add_custom_command below and will helps + # also help to track dependencies. + set(dummyfile ${CMAKE_CURRENT_BINARY_DIR}/${TARGET_NAME}_dummy.c) + + # Make the generated dummy source file depended on all static input + # libs. If input lib changes,the source file is touched + # which causes the desired effect (relink). + add_custom_command(OUTPUT ${dummyfile} + COMMAND ${CMAKE_COMMAND} -E touch ${dummyfile} + DEPENDS ${libs}) + + # Generate dummy staic lib + file(WRITE ${dummyfile} "const char * dummy = \"${dummyfile}\";") + add_library(${TARGET_NAME} STATIC ${dummyfile}) + target_link_libraries(${TARGET_NAME} ${libs_deps}) + + foreach(lib ${libs}) + # Get the file names of the libraries to be merged set(libfiles ${libfiles} $) endforeach() + # Get the file name of the generated library + set(outlibfile "$") + if(APPLE) # Use OSX's libtool to merge archives - set(dummyfile ${CMAKE_CURRENT_BINARY_DIR}/${TARGET_NAME}_dummy.c) - file(WRITE ${dummyfile} "const char * dummy = \"${dummyfile}\";") - add_library(${TARGET_NAME} STATIC ${dummyfile}) add_custom_command(TARGET ${TARGET_NAME} POST_BUILD COMMAND rm "${CMAKE_CURRENT_BINARY_DIR}/lib${TARGET_NAME}.a" COMMAND /usr/bin/libtool -static -o "${CMAKE_CURRENT_BINARY_DIR}/lib${TARGET_NAME}.a" ${libfiles}) @@ -117,7 +139,8 @@ function(merge_static_libs TARGET_NAME) set(objdir ${lib}.objdir) add_custom_command(OUTPUT ${objdir} - COMMAND ${CMAKE_COMMAND} -E make_directory ${objdir}) + COMMAND ${CMAKE_COMMAND} -E make_directory ${objdir} + DEPENDS ${lib}) add_custom_command(OUTPUT ${objlistfile} COMMAND ${CMAKE_AR} -x "$" @@ -125,23 +148,9 @@ function(merge_static_libs TARGET_NAME) DEPENDS ${lib} ${objdir} WORKING_DIRECTORY ${objdir}) - # Empty dummy source file that goes into merged library - set(mergebase ${lib}.mergebase.c) - add_custom_command(OUTPUT ${mergebase} - COMMAND ${CMAKE_COMMAND} -E touch ${mergebase} - DEPENDS ${objlistfile}) - - list(APPEND mergebases "${mergebase}") - endforeach() - - # We need a target for the output merged library - add_library(${TARGET_NAME} STATIC ${mergebases}) - set(outlibfile "$") - - foreach(lib ${libs}) add_custom_command(TARGET ${TARGET_NAME} POST_BUILD - COMMAND ${CMAKE_AR} ru ${outlibfile} @"../${lib}.objlist" - WORKING_DIRECTORY ${lib}.objdir) + COMMAND ${CMAKE_AR} ru ${outlibfile} *.o + WORKING_DIRECTORY ${objdir}) endforeach() add_custom_command(TARGET ${TARGET_NAME} POST_BUILD -- GitLab From 3e4ba647eec7bc16511e1146d5a696cd124c6a27 Mon Sep 17 00:00:00 2001 From: liaogang Date: Thu, 6 Jul 2017 11:28:52 +0800 Subject: [PATCH 1073/3256] FIX: remove duplicate --- cmake/generic.cmake | 1 + 1 file changed, 1 insertion(+) diff --git a/cmake/generic.cmake b/cmake/generic.cmake index 87d8caaec40..1a4600ef4be 100644 --- a/cmake/generic.cmake +++ b/cmake/generic.cmake @@ -103,6 +103,7 @@ function(merge_static_libs TARGET_NAME) foreach(lib ${libs}) list(APPEND libs_deps ${${lib}_LIB_DEPENDS}) endforeach() + list(REMOVE_DUPLICATES libs_deps) # To produce a library we need at least one source file. # It is created by add_custom_command below and will helps -- GitLab From a669bf48d966a92206c57d72258bb625b5ff2fbc Mon Sep 17 00:00:00 2001 From: liaogang Date: Thu, 6 Jul 2017 13:38:11 +0800 Subject: [PATCH 1074/3256] FIX: explicit construct pool element --- paddle/memory/detail/buddy_allocator.cc | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/paddle/memory/detail/buddy_allocator.cc b/paddle/memory/detail/buddy_allocator.cc index ed2eedf9af8..2cfacec46c1 100644 --- a/paddle/memory/detail/buddy_allocator.cc +++ b/paddle/memory/detail/buddy_allocator.cc @@ -118,8 +118,9 @@ void BuddyAllocator::Free(void* p) { if (right_buddy->type(cache_) == MemoryBlock::FREE_CHUNK) { // Take away right buddy from pool - pool_.erase({right_buddy->index(cache_), right_buddy->total_size(cache_), - right_buddy}); + pool_.erase(IndexSizeAddress(right_buddy->index(cache_), + right_buddy->total_size(cache_), + right_buddy)); // merge its right buddy to the block block->merge(cache_, right_buddy); @@ -135,8 +136,8 @@ void BuddyAllocator::Free(void* p) { if (left_buddy->type(cache_) == MemoryBlock::FREE_CHUNK) { // Take away right buddy from pool - pool_.erase({left_buddy->index(cache_), left_buddy->total_size(cache_), - left_buddy}); + pool_.erase(IndexSizeAddress(left_buddy->index(cache_), + left_buddy->total_size(cache_), left_buddy)); // merge the block to its left buddy left_buddy->merge(cache_, block); @@ -147,7 +148,8 @@ void BuddyAllocator::Free(void* p) { // Dumping this block into pool DLOG(INFO) << "Inserting free block (" << block << ", " << block->total_size(cache_) << ")"; - pool_.insert({block->index(cache_), block->total_size(cache_), block}); + pool_.insert( + IndexSizeAddress(block->index(cache_), block->total_size(cache_), block)); // TODO(gangliao): Clean up if existing too much free memory } @@ -193,14 +195,14 @@ BuddyAllocator::PoolSet::iterator BuddyAllocator::RefillPool() { total_free_ += max_chunk_size_; // dump the block into pool - return pool_.insert({index, max_chunk_size_, p}).first; + return pool_.insert(IndexSizeAddress(index, max_chunk_size_, p)).first; } BuddyAllocator::PoolSet::iterator BuddyAllocator::FindExistChunk(size_t size) { size_t index = 0; while (1) { - auto it = pool_.lower_bound({index, size, nullptr}); + auto it = pool_.lower_bound(IndexSizeAddress(index, size, nullptr)); // no match chunk memory if (it == pool_.end()) return it; @@ -237,9 +239,10 @@ void* BuddyAllocator::SplitToAlloc(BuddyAllocator::PoolSet::iterator it, DLOG(INFO) << "Insert right block (" << block->right_buddy(cache_) << ", " << block->right_buddy(cache_)->total_size(cache_) << ")"; - pool_.insert({block->right_buddy(cache_)->index(cache_), - block->right_buddy(cache_)->total_size(cache_), - block->right_buddy(cache_)}); + pool_.insert( + IndexSizeAddress(block->right_buddy(cache_)->index(cache_), + block->right_buddy(cache_)->total_size(cache_), + block->right_buddy(cache_))); } } -- GitLab From 108b0fad2ffdf8faf281e34ea64437abe7a3eca3 Mon Sep 17 00:00:00 2001 From: gongweibao Date: Thu, 6 Jul 2017 06:40:58 +0000 Subject: [PATCH 1075/3256] fix by helin and wuyi's comments --- go/master/service.go | 37 ++++++++++++++++++------------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/go/master/service.go b/go/master/service.go index c47319317ad..29ff63bcc90 100644 --- a/go/master/service.go +++ b/go/master/service.go @@ -39,9 +39,9 @@ type Task struct { } type taskEntry struct { - NumTimeout int - Task Task - NumFailed int + Task Task + // A task fails if it's timeout or trainer reports it exits unnormally. + NumFailure int } type taskQueues struct { @@ -53,11 +53,11 @@ type taskQueues struct { // Service is the master server service. type Service struct { - chunksPerTask int - timeoutDur time.Duration - failortimeoutMax int - ready chan struct{} - store Store + chunksPerTask int + timeoutDur time.Duration + failureMax int + ready chan struct{} + store Store mu sync.Mutex initDone bool @@ -92,11 +92,11 @@ func partition(chunks []Chunk, chunksPerTask int) []taskEntry { } // NewService creates a new service. -func NewService(store Store, chunksPerTask int, timeoutDur time.Duration, failortimeoutMax int) (*Service, error) { +func NewService(store Store, chunksPerTask int, timeoutDur time.Duration, failureMax int) (*Service, error) { s := &Service{} s.chunksPerTask = chunksPerTask s.timeoutDur = timeoutDur - s.failortimeoutMax = failortimeoutMax + s.failureMax = failureMax s.taskQueues = taskQueues{} s.taskQueues.Pending = make(map[int]taskEntry) s.ready = make(chan struct{}) @@ -258,7 +258,7 @@ func (s *Service) SetDataset(globPaths []string, dummy *int) error { return nil } -func (s *Service) checkTaskStatus(t taskEntry, epoch int) { +func (s *Service) procFailedTask(t taskEntry, epoch int) { if t.Task.Epoch != epoch { // new epoch, task launched after the // schedule of this timeout check or failed status report. @@ -274,14 +274,14 @@ func (s *Service) checkTaskStatus(t taskEntry, epoch int) { delete(s.taskQueues.Pending, t.Task.ID) - t.NumTimeout++ - if t.NumTimeout+t.NumFailed > s.failortimeoutMax { - log.Warningf("Task %v timed out %d times and failed %d times, discard.", t.Task, t.NumTimeout, t.NumFailed) + t.NumFailure++ + if t.NumFailure > s.failureMax { + log.Warningf("Task %v failed %d times, discard.", t.Task, t.NumFailure) s.taskQueues.Failed = append(s.taskQueues.Failed, t) return } - log.Warningf("Task %v timed out %d times and failed %d times, discard.", t.Task, t.NumTimeout, t.NumFailed) + log.Warningf("Task %v failed %d times, discard.", t.Task, t.NumFailure) s.taskQueues.Todo = append(s.taskQueues.Todo, t) return } @@ -296,7 +296,7 @@ func (s *Service) checkTimeoutFunc(taskID int, epoch int) func() { return } - s.checkTaskStatus(t, epoch) + s.procFailedTask(t, epoch) } } @@ -377,8 +377,7 @@ func (s *Service) TaskFinished(taskID int, dummy *int) error { } // task finished, reset timeout - t.NumTimeout = 0 - t.NumFailed = 0 + t.NumFailure = 0 s.taskQueues.Done = append(s.taskQueues.Done, t) delete(s.taskQueues.Pending, taskID) @@ -413,6 +412,6 @@ func (s *Service) TaskFailed(taskID int, epoch int) error { return err } - s.checkTaskStatus(t, epoch) + s.procFailedTask(t, epoch) return nil } -- GitLab From 1b366dc2fff2b896fc92c1aa161183e6c88f6b7e Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Thu, 6 Jul 2017 14:44:40 +0800 Subject: [PATCH 1076/3256] Fix CI error on test_LayerGrad.LSTM * We should not EXPECT_EQ between a float value and a int value. Use ASSERT_NEAR instead. --- paddle/gserver/tests/LayerGradUtil.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paddle/gserver/tests/LayerGradUtil.cpp b/paddle/gserver/tests/LayerGradUtil.cpp index 15b8cedeb83..9eca58f1a1b 100644 --- a/paddle/gserver/tests/LayerGradUtil.cpp +++ b/paddle/gserver/tests/LayerGradUtil.cpp @@ -241,7 +241,7 @@ void testBatchState(LayerPtr testLayer, std::vector args; args.push_back(out); - EXPECT_EQ(0, Argument::sum(args)) << "testBatchState failed"; + ASSERT_NEAR(0, Argument::sum(args), 1e-5) << "testBatchState failed"; for (size_t seqId = 0; seqId < numSequences; ++seqId) { start[seqId] += seqLens[seqId]; } -- GitLab From a94d217487a222526e303c443aaa3370321447ae Mon Sep 17 00:00:00 2001 From: gongweibao Date: Thu, 6 Jul 2017 07:09:55 +0000 Subject: [PATCH 1077/3256] add TaskID --- go/master/client.go | 4 ++-- go/master/service.go | 14 ++++++++++---- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/go/master/client.go b/go/master/client.go index b6ca8cad15a..bf2612d91b6 100644 --- a/go/master/client.go +++ b/go/master/client.go @@ -113,8 +113,8 @@ func (c *Client) taskFinished(taskID int) error { } // TaskFailed tell the master server as task is failed. -func (c *Client) taskFailed(taskID int, epoch int) error { - return c.conn.Call("Service.TaskFinished", taskID, epoch) +func (c *Client) taskFailed(taskID TaskID) error { + return c.conn.Call("Service.TaskFinished", taskID, nil) } // NextRecord returns next record in the dataset. diff --git a/go/master/service.go b/go/master/service.go index 29ff63bcc90..b1334a2d8e6 100644 --- a/go/master/service.go +++ b/go/master/service.go @@ -396,8 +396,14 @@ func (s *Service) TaskFinished(taskID int, dummy *int) error { return err } -// TaskFailed tell the service that a task is failed. -func (s *Service) TaskFailed(taskID int, epoch int) error { +// TaskID is a struct which client uses for reports failure. +type TaskID struct { + ID int + Epoch int +} + +// TaskFailed tells the service that a task is failed. +func (s *Service) TaskFailed(taskID TaskID, dummy *int) error { select { case <-s.ready: } @@ -405,13 +411,13 @@ func (s *Service) TaskFailed(taskID int, epoch int) error { s.mu.Lock() defer s.mu.Unlock() - t, ok := s.taskQueues.Pending[taskID] + t, ok := s.taskQueues.Pending[taskID.ID] if !ok { err := errors.New("pending task not found") log.WithFields(s.logFields()).Warningln("TaskFailed:Pending task #%d not found.", taskID) return err } - s.procFailedTask(t, epoch) + s.procFailedTask(t, taskID.Epoch) return nil } -- GitLab From 8f7088590c7031dedd554f62762a559a4efe6b9c Mon Sep 17 00:00:00 2001 From: gongweibao Date: Thu, 6 Jul 2017 07:14:10 +0000 Subject: [PATCH 1078/3256] fix bugs --- go/master/service.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go/master/service.go b/go/master/service.go index b1334a2d8e6..daf39282302 100644 --- a/go/master/service.go +++ b/go/master/service.go @@ -414,7 +414,7 @@ func (s *Service) TaskFailed(taskID TaskID, dummy *int) error { t, ok := s.taskQueues.Pending[taskID.ID] if !ok { err := errors.New("pending task not found") - log.WithFields(s.logFields()).Warningln("TaskFailed:Pending task #%d not found.", taskID) + log.WithFields(s.logFields()).Warningln("TaskFailed:Pending task #%v not found.", taskID) return err } -- GitLab From e2ea1f42e9202e5591e2de1ce5f96c573dcc6484 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Thu, 6 Jul 2017 14:12:45 +0800 Subject: [PATCH 1079/3256] Generate python protobufs for paddle.v2.framework Python should be able to manipulate Protobuf message because: 1. Python's `create_op_creation_methods` take the `OpProto` array to generate all `op_creation_methods` in RunTime. 2. All `op_creation_methods` will create an `OpDesc` and pass it to Paddle C++ method `CreateOp` and return the Op handle. Here is the list of what is added in this commit: * Add `protobuf_generate_python` if it is not defined. * Before cmake 3.4, `protobuf_generate_python` is not defined. Just copy the implementation of that function in `protobuf.cmake` * Add `py_proto_compile` function in `cmake/generic.cmake`. * It follows bazel's API interface. * https://github.com/pubref/rules_protobuf#rules * Add an empty package named `paddle.v2.framework`, all python code of `paddle::framework` will be in that package. * Generate protobuf's python module `__init__.py` by `touch` while compiling. * Change setup.py.in, make `paddle.v2.framework.proto` uses the generated protobuf pythons. --- cmake/external/protobuf.cmake | 59 +++++++++++++++++++ cmake/generic.cmake | 9 +++ paddle/framework/CMakeLists.txt | 5 +- python/CMakeLists.txt | 3 +- python/paddle/v2/framework/__init__.py | 1 + .../paddle/v2/framework/tests/CMakeLists.txt | 1 + .../v2/framework/tests/test_protobuf.py | 26 ++++++++ python/setup.py.in | 9 ++- 8 files changed, 109 insertions(+), 4 deletions(-) create mode 100644 python/paddle/v2/framework/__init__.py create mode 100644 python/paddle/v2/framework/tests/CMakeLists.txt create mode 100644 python/paddle/v2/framework/tests/test_protobuf.py diff --git a/cmake/external/protobuf.cmake b/cmake/external/protobuf.cmake index 3c74944bc21..e629d61585c 100644 --- a/cmake/external/protobuf.cmake +++ b/cmake/external/protobuf.cmake @@ -17,6 +17,65 @@ INCLUDE(ExternalProject) FIND_PACKAGE(Protobuf QUIET) SET(PROTOBUF_FOUND "OFF") +if(NOT COMMAND protobuf_generate_python) # before cmake 3.4, protobuf_genrerate_python is not defined. + function(protobuf_generate_python SRCS) + # shameless copy from https://github.com/Kitware/CMake/blob/master/Modules/FindProtobuf.cmake + if(NOT ARGN) + message(SEND_ERROR "Error: PROTOBUF_GENERATE_PYTHON() called without any proto files") + return() + endif() + + if(PROTOBUF_GENERATE_CPP_APPEND_PATH) + # Create an include path for each file specified + foreach(FIL ${ARGN}) + get_filename_component(ABS_FIL ${FIL} ABSOLUTE) + get_filename_component(ABS_PATH ${ABS_FIL} PATH) + list(FIND _protobuf_include_path ${ABS_PATH} _contains_already) + if(${_contains_already} EQUAL -1) + list(APPEND _protobuf_include_path -I ${ABS_PATH}) + endif() + endforeach() + else() + set(_protobuf_include_path -I ${CMAKE_CURRENT_SOURCE_DIR}) + endif() + + if(DEFINED PROTOBUF_IMPORT_DIRS AND NOT DEFINED Protobuf_IMPORT_DIRS) + set(Protobuf_IMPORT_DIRS "${PROTOBUF_IMPORT_DIRS}") + endif() + + if(DEFINED Protobuf_IMPORT_DIRS) + foreach(DIR ${Protobuf_IMPORT_DIRS}) + get_filename_component(ABS_PATH ${DIR} ABSOLUTE) + list(FIND _protobuf_include_path ${ABS_PATH} _contains_already) + if(${_contains_already} EQUAL -1) + list(APPEND _protobuf_include_path -I ${ABS_PATH}) + endif() + endforeach() + endif() + + set(${SRCS}) + foreach(FIL ${ARGN}) + get_filename_component(ABS_FIL ${FIL} ABSOLUTE) + get_filename_component(FIL_WE ${FIL} NAME_WE) + if(NOT PROTOBUF_GENERATE_CPP_APPEND_PATH) + get_filename_component(FIL_DIR ${FIL} DIRECTORY) + if(FIL_DIR) + set(FIL_WE "${FIL_DIR}/${FIL_WE}") + endif() + endif() + + list(APPEND ${SRCS} "${CMAKE_CURRENT_BINARY_DIR}/${FIL_WE}_pb2.py") + add_custom_command( + OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${FIL_WE}_pb2.py" + COMMAND ${Protobuf_PROTOC_EXECUTABLE} --python_out ${CMAKE_CURRENT_BINARY_DIR} ${_protobuf_include_path} ${ABS_FIL} + DEPENDS ${ABS_FIL} ${Protobuf_PROTOC_EXECUTABLE} + COMMENT "Running Python protocol buffer compiler on ${FIL}" + VERBATIM ) + endforeach() + + set(${SRCS} ${${SRCS}} PARENT_SCOPE) + endfunction() +endif() # Print and set the protobuf library information, # finish this cmake process and exit from this file. diff --git a/cmake/generic.cmake b/cmake/generic.cmake index d51b95a5d7f..a92671ae623 100644 --- a/cmake/generic.cmake +++ b/cmake/generic.cmake @@ -335,3 +335,12 @@ function(proto_library TARGET_NAME) protobuf_generate_cpp(proto_srcs proto_hdrs ${proto_library_SRCS}) cc_library(${TARGET_NAME} SRCS ${proto_srcs} DEPS ${proto_library_DEPS} protobuf) endfunction() + +function(py_proto_compile TARGET_NAME) + set(oneValueArgs "") + set(multiValueArgs SRCS) + cmake_parse_arguments(py_proto_compile "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + set(py_srcs) + protobuf_generate_python(py_srcs ${py_proto_compile_SRCS}) + add_custom_target(${TARGET_NAME} ALL DEPENDS ${py_srcs}) +endfunction() \ No newline at end of file diff --git a/paddle/framework/CMakeLists.txt b/paddle/framework/CMakeLists.txt index dcd70d28517..970b2b9abde 100644 --- a/paddle/framework/CMakeLists.txt +++ b/paddle/framework/CMakeLists.txt @@ -9,6 +9,9 @@ cc_test(enforce_test SRCS enforce_test.cc) proto_library(attr_type SRCS attr_type.proto) proto_library(op_proto SRCS op_proto.proto DEPS attr_type) cc_test(op_proto_test SRCS op_proto_test.cc DEPS op_proto protobuf) - proto_library(op_desc SRCS op_desc.proto DEPS attr_type) cc_test(op_desc_test SRCS op_desc_test.cc DEPS op_desc protobuf) +py_proto_compile(framework_py_proto SRCS attr_type.proto op_proto.proto op_desc.proto) +# Generate an empty __init__.py to make framework_py_proto as a valid python module. +add_custom_target(framework_py_proto_init ALL COMMAND ${CMAKE_COMMAND} -E touch __init__.py) +add_dependencies(framework_py_proto framework_py_proto_init) diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index 361e764e25b..13a1802ee37 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -29,7 +29,7 @@ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/setup.py.in add_custom_command(OUTPUT ${OUTPUT_DIR}/.timestamp COMMAND env ${py_env} ${PYTHON_EXECUTABLE} setup.py bdist_wheel COMMAND ${CMAKE_COMMAND} -E touch ${OUTPUT_DIR}/.timestamp - DEPENDS gen_proto_py ${PY_FILES} ${external_project_dependencies} ${COPY_PADDLE_MASTER}) + DEPENDS gen_proto_py framework_py_proto ${PY_FILES} ${external_project_dependencies} ${COPY_PADDLE_MASTER}) add_custom_target(paddle_python ALL DEPENDS ${OUTPUT_DIR}/.timestamp) @@ -43,6 +43,7 @@ if (WITH_TESTING) add_subdirectory(paddle/v2/tests) add_subdirectory(paddle/v2/reader/tests) add_subdirectory(paddle/v2/plot/tests) + add_subdirectory(paddle/v2/framework/tests) endif() endif() install(DIRECTORY ${PADDLE_PYTHON_PACKAGE_DIR} diff --git a/python/paddle/v2/framework/__init__.py b/python/paddle/v2/framework/__init__.py new file mode 100644 index 00000000000..c942373c667 --- /dev/null +++ b/python/paddle/v2/framework/__init__.py @@ -0,0 +1 @@ +__all__ = ['proto'] diff --git a/python/paddle/v2/framework/tests/CMakeLists.txt b/python/paddle/v2/framework/tests/CMakeLists.txt new file mode 100644 index 00000000000..8cb0c5c3765 --- /dev/null +++ b/python/paddle/v2/framework/tests/CMakeLists.txt @@ -0,0 +1 @@ +add_python_test(test_framework test_protobuf.py) diff --git a/python/paddle/v2/framework/tests/test_protobuf.py b/python/paddle/v2/framework/tests/test_protobuf.py new file mode 100644 index 00000000000..f0e60191991 --- /dev/null +++ b/python/paddle/v2/framework/tests/test_protobuf.py @@ -0,0 +1,26 @@ +import paddle.v2.framework.proto.op_proto_pb2 +import paddle.v2.framework.proto.attr_type_pb2 +import unittest + + +class TestFrameworkProto(unittest.TestCase): + def test_all(self): + op_proto_lib = paddle.v2.framework.proto.op_proto_pb2 + attr_type_lib = paddle.v2.framework.proto.attr_type_pb2 + op_proto = op_proto_lib.OpProto() + ipt0 = op_proto.inputs.add() + ipt0.name = "a" + ipt0.comment = "the input of cosine op" + ipt1 = op_proto.inputs.add() + ipt1.name = "b" + ipt1.comment = "the other input of cosine op" + opt = op_proto.outputs.add() + opt.name = "output" + opt.comment = "the output of cosine op" + op_proto.comment = "cosine op, output = scale*cos(a, b)" + attr = op_proto.attrs.add() + attr.name = "scale" + attr.comment = "scale of cosine op" + attr.type = attr_type_lib.FLOAT + op_proto.type = "cos" + self.assertTrue(op_proto.IsInitialized()) diff --git a/python/setup.py.in b/python/setup.py.in index dae01664876..78423614a6b 100644 --- a/python/setup.py.in +++ b/python/setup.py.in @@ -9,7 +9,9 @@ packages=['paddle', 'paddle.v2.dataset', 'paddle.v2.reader', 'paddle.v2.master', - 'paddle.v2.plot'] + 'paddle.v2.plot', + 'paddle.v2.framework', + 'paddle.v2.framework.proto'] setup_requires=["requests", "numpy", @@ -29,6 +31,9 @@ setup(name='paddle', packages=packages, package_data={'paddle.v2.master': ['${paddle_master_LIB_NAME}'], }, package_dir={ - '': '${CMAKE_CURRENT_SOURCE_DIR}' + '': '${CMAKE_CURRENT_SOURCE_DIR}', + # The paddle.v2.framework.proto will be generated while compiling. + # So that package points to other directory. + 'paddle.v2.framework.proto': '${CMAKE_BINARY_DIR}/paddle/framework' }, ) -- GitLab From adf8c95b62fc5ef1f608bc06dce32bb4b396828c Mon Sep 17 00:00:00 2001 From: liaogang Date: Thu, 6 Jul 2017 15:40:22 +0800 Subject: [PATCH 1080/3256] FIX: propagation dependencies under linux --- cmake/generic.cmake | 68 ++++++++++++++++++++++++++------------------- 1 file changed, 39 insertions(+), 29 deletions(-) diff --git a/cmake/generic.cmake b/cmake/generic.cmake index 87d8caaec40..3900ea26048 100644 --- a/cmake/generic.cmake +++ b/cmake/generic.cmake @@ -104,36 +104,32 @@ function(merge_static_libs TARGET_NAME) list(APPEND libs_deps ${${lib}_LIB_DEPENDS}) endforeach() - # To produce a library we need at least one source file. - # It is created by add_custom_command below and will helps - # also help to track dependencies. - set(dummyfile ${CMAKE_CURRENT_BINARY_DIR}/${TARGET_NAME}_dummy.c) - - # Make the generated dummy source file depended on all static input - # libs. If input lib changes,the source file is touched - # which causes the desired effect (relink). - add_custom_command(OUTPUT ${dummyfile} - COMMAND ${CMAKE_COMMAND} -E touch ${dummyfile} - DEPENDS ${libs}) - - # Generate dummy staic lib - file(WRITE ${dummyfile} "const char * dummy = \"${dummyfile}\";") - add_library(${TARGET_NAME} STATIC ${dummyfile}) - target_link_libraries(${TARGET_NAME} ${libs_deps}) + if(APPLE) # Use OSX's libtool to merge archives + # To produce a library we need at least one source file. + # It is created by add_custom_command below and will helps + # also help to track dependencies. + set(dummyfile ${CMAKE_CURRENT_BINARY_DIR}/${TARGET_NAME}_dummy.c) - foreach(lib ${libs}) - # Get the file names of the libraries to be merged - set(libfiles ${libfiles} $) - endforeach() + # Make the generated dummy source file depended on all static input + # libs. If input lib changes,the source file is touched + # which causes the desired effect (relink). + add_custom_command(OUTPUT ${dummyfile} + COMMAND ${CMAKE_COMMAND} -E touch ${dummyfile} + DEPENDS ${libs}) - # Get the file name of the generated library - set(outlibfile "$") + # Generate dummy staic lib + file(WRITE ${dummyfile} "const char * dummy = \"${dummyfile}\";") + add_library(${TARGET_NAME} STATIC ${dummyfile}) + target_link_libraries(${TARGET_NAME} ${libs_deps}) - if(APPLE) # Use OSX's libtool to merge archives + foreach(lib ${libs}) + # Get the file names of the libraries to be merged + set(libfiles ${libfiles} $) + endforeach() add_custom_command(TARGET ${TARGET_NAME} POST_BUILD COMMAND rm "${CMAKE_CURRENT_BINARY_DIR}/lib${TARGET_NAME}.a" COMMAND /usr/bin/libtool -static -o "${CMAKE_CURRENT_BINARY_DIR}/lib${TARGET_NAME}.a" ${libfiles}) - else() # general UNIX: use "ar" to extract objects and re-add to a common lib + else() # general UNIX: use "ar" to extract objects and re-add to a common lib foreach(lib ${libs}) set(objlistfile ${lib}.objlist) # list of objects in the input library set(objdir ${lib}.objdir) @@ -148,13 +144,27 @@ function(merge_static_libs TARGET_NAME) DEPENDS ${lib} ${objdir} WORKING_DIRECTORY ${objdir}) - add_custom_command(TARGET ${TARGET_NAME} POST_BUILD - COMMAND ${CMAKE_AR} ru ${outlibfile} *.o - WORKING_DIRECTORY ${objdir}) + # Empty dummy source file that goes into merged library + set(mergebase ${lib}.mergebase.c) + add_custom_command(OUTPUT ${mergebase} + COMMAND ${CMAKE_COMMAND} -E touch ${mergebase} + DEPENDS ${objlistfile}) + + list(APPEND mergebases "${mergebase}") endforeach() - add_custom_command(TARGET ${TARGET_NAME} POST_BUILD - COMMAND ${CMAKE_RANLIB} ${outlibfile}) + add_library(${TARGET_NAME} STATIC ${mergebases}) + target_link_libraries(${TARGET_NAME} ${libs_deps}) + + # Get the file name of the generated library + set(outlibfile "$") + + foreach(lib ${libs}) + add_custom_command(TARGET ${TARGET_NAME} POST_BUILD + COMMAND ${CMAKE_AR} cr ${outlibfile} *.o + COMMAND ${CMAKE_RANLIB} ${outlibfile} + WORKING_DIRECTORY ${lib}.objdir) + endforeach() endif() endfunction(merge_static_libs) -- GitLab From 847535f4fe6cea0b954a67fffea4c7b9ed96bd77 Mon Sep 17 00:00:00 2001 From: liaogang Date: Thu, 6 Jul 2017 15:42:29 +0800 Subject: [PATCH 1081/3256] FIX: propagation dependencies under linux --- cmake/generic.cmake | 69 +++++++++++++++++++++++++-------------------- 1 file changed, 39 insertions(+), 30 deletions(-) diff --git a/cmake/generic.cmake b/cmake/generic.cmake index 1a4600ef4be..3900ea26048 100644 --- a/cmake/generic.cmake +++ b/cmake/generic.cmake @@ -103,38 +103,33 @@ function(merge_static_libs TARGET_NAME) foreach(lib ${libs}) list(APPEND libs_deps ${${lib}_LIB_DEPENDS}) endforeach() - list(REMOVE_DUPLICATES libs_deps) - # To produce a library we need at least one source file. - # It is created by add_custom_command below and will helps - # also help to track dependencies. - set(dummyfile ${CMAKE_CURRENT_BINARY_DIR}/${TARGET_NAME}_dummy.c) - - # Make the generated dummy source file depended on all static input - # libs. If input lib changes,the source file is touched - # which causes the desired effect (relink). - add_custom_command(OUTPUT ${dummyfile} - COMMAND ${CMAKE_COMMAND} -E touch ${dummyfile} - DEPENDS ${libs}) - - # Generate dummy staic lib - file(WRITE ${dummyfile} "const char * dummy = \"${dummyfile}\";") - add_library(${TARGET_NAME} STATIC ${dummyfile}) - target_link_libraries(${TARGET_NAME} ${libs_deps}) + if(APPLE) # Use OSX's libtool to merge archives + # To produce a library we need at least one source file. + # It is created by add_custom_command below and will helps + # also help to track dependencies. + set(dummyfile ${CMAKE_CURRENT_BINARY_DIR}/${TARGET_NAME}_dummy.c) - foreach(lib ${libs}) - # Get the file names of the libraries to be merged - set(libfiles ${libfiles} $) - endforeach() + # Make the generated dummy source file depended on all static input + # libs. If input lib changes,the source file is touched + # which causes the desired effect (relink). + add_custom_command(OUTPUT ${dummyfile} + COMMAND ${CMAKE_COMMAND} -E touch ${dummyfile} + DEPENDS ${libs}) - # Get the file name of the generated library - set(outlibfile "$") + # Generate dummy staic lib + file(WRITE ${dummyfile} "const char * dummy = \"${dummyfile}\";") + add_library(${TARGET_NAME} STATIC ${dummyfile}) + target_link_libraries(${TARGET_NAME} ${libs_deps}) - if(APPLE) # Use OSX's libtool to merge archives + foreach(lib ${libs}) + # Get the file names of the libraries to be merged + set(libfiles ${libfiles} $) + endforeach() add_custom_command(TARGET ${TARGET_NAME} POST_BUILD COMMAND rm "${CMAKE_CURRENT_BINARY_DIR}/lib${TARGET_NAME}.a" COMMAND /usr/bin/libtool -static -o "${CMAKE_CURRENT_BINARY_DIR}/lib${TARGET_NAME}.a" ${libfiles}) - else() # general UNIX: use "ar" to extract objects and re-add to a common lib + else() # general UNIX: use "ar" to extract objects and re-add to a common lib foreach(lib ${libs}) set(objlistfile ${lib}.objlist) # list of objects in the input library set(objdir ${lib}.objdir) @@ -149,13 +144,27 @@ function(merge_static_libs TARGET_NAME) DEPENDS ${lib} ${objdir} WORKING_DIRECTORY ${objdir}) - add_custom_command(TARGET ${TARGET_NAME} POST_BUILD - COMMAND ${CMAKE_AR} ru ${outlibfile} *.o - WORKING_DIRECTORY ${objdir}) + # Empty dummy source file that goes into merged library + set(mergebase ${lib}.mergebase.c) + add_custom_command(OUTPUT ${mergebase} + COMMAND ${CMAKE_COMMAND} -E touch ${mergebase} + DEPENDS ${objlistfile}) + + list(APPEND mergebases "${mergebase}") endforeach() - add_custom_command(TARGET ${TARGET_NAME} POST_BUILD - COMMAND ${CMAKE_RANLIB} ${outlibfile}) + add_library(${TARGET_NAME} STATIC ${mergebases}) + target_link_libraries(${TARGET_NAME} ${libs_deps}) + + # Get the file name of the generated library + set(outlibfile "$") + + foreach(lib ${libs}) + add_custom_command(TARGET ${TARGET_NAME} POST_BUILD + COMMAND ${CMAKE_AR} cr ${outlibfile} *.o + COMMAND ${CMAKE_RANLIB} ${outlibfile} + WORKING_DIRECTORY ${lib}.objdir) + endforeach() endif() endfunction(merge_static_libs) -- GitLab From 203364281ed8b86c53c520142b881f00aca5485e Mon Sep 17 00:00:00 2001 From: caoying03 Date: Thu, 6 Jul 2017 16:44:54 +0800 Subject: [PATCH 1082/3256] enable error clipping in FC layer. --- python/paddle/trainer/config_parser.py | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/python/paddle/trainer/config_parser.py b/python/paddle/trainer/config_parser.py index 370529ed97b..e020be93784 100644 --- a/python/paddle/trainer/config_parser.py +++ b/python/paddle/trainer/config_parser.py @@ -13,6 +13,7 @@ # limitations under the License. from __future__ import print_function +import pdb ''' The following functions are available in the config file: @@ -761,8 +762,8 @@ class DotMulOperator(Operator): def check_dims(self): for i in range(2): - config_assert(self.operator_conf.input_sizes[i] == - self.operator_conf.output_size, + config_assert(self.operator_conf.input_sizes[ + i] == self.operator_conf.output_size, "DotMul input_size != output_size") def calc_output_size(self, input_sizes): @@ -1193,8 +1194,7 @@ def parse_image(image, input_layer_name, image_conf): def parse_norm(norm, input_layer_name, norm_conf): norm_conf.norm_type = norm.norm_type config_assert( - norm.norm_type in - ['rnorm', 'cmrnorm-projection', 'cross-channel-norm'], + norm.norm_type in ['rnorm', 'cmrnorm-projection', 'cross-channel-norm'], "norm-type %s is not in [rnorm, cmrnorm-projection, cross-channel-norm]" % norm.norm_type) norm_conf.channels = norm.channels @@ -1571,7 +1571,13 @@ class MultiClassCrossEntropySelfNormCostLayer(LayerBase): @config_layer('fc') class FCLayer(LayerBase): - def __init__(self, name, size, inputs, bias=True, **xargs): + def __init__(self, + name, + size, + inputs, + bias=True, + error_clipping_threshold=None, + **xargs): super(FCLayer, self).__init__(name, 'fc', size, inputs=inputs, **xargs) for input_index in xrange(len(self.inputs)): input_layer = self.get_input_layer(input_index) @@ -1589,6 +1595,9 @@ class FCLayer(LayerBase): format) self.create_bias_parameter(bias, self.config.size) + if error_clipping_threshold is not None: + self.config.error_clipping_threshold = error_clipping_threshold + @config_layer('selective_fc') class SelectiveFCLayer(LayerBase): @@ -3425,7 +3434,8 @@ DEFAULT_SETTING = dict( settings = copy.deepcopy(DEFAULT_SETTING) -settings_deprecated = dict(usage_ratio=1., ) +settings_deprecated = dict( + usage_ratio=1., ) trainer_settings = dict( save_dir="./output/model", -- GitLab From ddfa6cf0d1fe91f8bf2e1d55841afee9e30d1859 Mon Sep 17 00:00:00 2001 From: liaogang Date: Thu, 6 Jul 2017 17:07:04 +0800 Subject: [PATCH 1083/3256] FIX: remove boost from memory folder --- paddle/memory/memory.cc | 56 +++++++++++++++++++---------------------- paddle/memory/memory.h | 11 +++++--- 2 files changed, 34 insertions(+), 33 deletions(-) diff --git a/paddle/memory/memory.cc b/paddle/memory/memory.cc index 43f2084e8df..def580f7a4b 100644 --- a/paddle/memory/memory.cc +++ b/paddle/memory/memory.cc @@ -32,7 +32,22 @@ detail::BuddyAllocator* GetCPUBuddyAllocator() { return a; } -#ifndef PADDLE_ONLY_CPU // The following code are for CUDA. +template <> +void* Alloc(platform::CPUPlace place, size_t size) { + return GetCPUBuddyAllocator()->Alloc(size); +} + +template <> +void Free(platform::CPUPlace place, void* p) { + GetCPUBuddyAllocator()->Free(p); +} + +template <> +size_t Used(platform::CPUPlace place) { + return GetCPUBuddyAllocator()->Used(); +} + +#ifndef PADDLE_ONLY_CPU detail::BuddyAllocator* GetGPUBuddyAllocator(int gpu_id) { static detail::BuddyAllocator** as = NULL; @@ -49,41 +64,22 @@ detail::BuddyAllocator* GetGPUBuddyAllocator(int gpu_id) { return as[gpu_id]; } -#endif // PADDLE_ONLY_CPU +template <> +void* Alloc(platform::GPUPlace place, size_t size) { + return GetGPUBuddyAllocator(place.device)->Alloc(size); +} -void* Alloc(platform::Place pl, size_t size) { -#ifndef PADDLE_ONLY_CPU - if (paddle::platform::is_gpu_place(pl)) { - size_t gpu_id = boost::get(pl).device; - return GetGPUBuddyAllocator(gpu_id)->Alloc(size); - } -#endif // PADDLE_ONLY_CPU - PADDLE_ASSERT(paddle::platform::is_cpu_place(pl)); - return GetCPUBuddyAllocator()->Alloc(size); +template <> +void Free(platform::GPUPlace place, void* p) { + GetGPUBuddyAllocator(place.device)->Free(p); } -void Free(paddle::platform::Place pl, void* p) { -#ifndef PADDLE_ONLY_CPU - if (paddle::platform::is_gpu_place(pl)) { - size_t gpu_id = boost::get(pl).device; - GetGPUBuddyAllocator(gpu_id)->Free(p); - return; - } -#endif // PADDLE_ONLY_CPU - PADDLE_ASSERT(paddle::platform::is_cpu_place(pl)); - GetCPUBuddyAllocator()->Free(p); +template <> +size_t Used(platform::GPUPlace place) { + return GetGPUBuddyAllocator(place.device)->Used(); } -size_t Used(paddle::platform::Place pl) { -#ifndef PADDLE_ONLY_CPU - if (paddle::platform::is_gpu_place(pl)) { - size_t gpu_id = boost::get(pl).device; - return GetGPUBuddyAllocator(gpu_id)->Used(); - } #endif // PADDLE_ONLY_CPU - PADDLE_ASSERT(paddle::platform::is_cpu_place(pl)); - return GetCPUBuddyAllocator()->Used(); -} } // namespace memory } // namespace paddle diff --git a/paddle/memory/memory.h b/paddle/memory/memory.h index a33092bade6..2d6f4fd2a08 100644 --- a/paddle/memory/memory.h +++ b/paddle/memory/memory.h @@ -19,9 +19,14 @@ limitations under the License. */ namespace paddle { namespace memory { -void* Alloc(paddle::platform::Place, size_t); -void Free(paddle::platform::Place, void*); -size_t Used(paddle::platform::Place); +template +void* Alloc(Place, size_t); + +template +void Free(Place, void*); + +template +size_t Used(Place); } // namespace memory } // namespace paddle -- GitLab From 075954c17ceaf422478961d9a5d6aaa364458415 Mon Sep 17 00:00:00 2001 From: caoying03 Date: Thu, 6 Jul 2017 17:40:58 +0800 Subject: [PATCH 1084/3256] follow comment. --- python/paddle/trainer/config_parser.py | 28 +++++++------------------- 1 file changed, 7 insertions(+), 21 deletions(-) diff --git a/python/paddle/trainer/config_parser.py b/python/paddle/trainer/config_parser.py index 1fed6db33c1..826ba2834a8 100644 --- a/python/paddle/trainer/config_parser.py +++ b/python/paddle/trainer/config_parser.py @@ -1353,7 +1353,8 @@ class LayerBase(object): device=None, active_type="", drop_rate=0., - coeff=None): + coeff=None, + error_clipping_threshold=None): config_assert('@' not in name, "layer name: %s contain special character @" % name) global g_current_submodel @@ -1387,6 +1388,9 @@ class LayerBase(object): elif g_default_device is not None: self.config.device = g_default_device + if error_clipping_threshold is not None: + self.config.error_clipping_threshold = error_clipping_threshold + for input_index in xrange(len(self.inputs)): input = self.inputs[input_index] input_config = None @@ -1571,13 +1575,7 @@ class MultiClassCrossEntropySelfNormCostLayer(LayerBase): @config_layer('fc') class FCLayer(LayerBase): - def __init__(self, - name, - size, - inputs, - bias=True, - error_clipping_threshold=None, - **xargs): + def __init__(self, name, size, inputs, bias=True, **xargs): super(FCLayer, self).__init__(name, 'fc', size, inputs=inputs, **xargs) for input_index in xrange(len(self.inputs)): input_layer = self.get_input_layer(input_index) @@ -1595,9 +1593,6 @@ class FCLayer(LayerBase): format) self.create_bias_parameter(bias, self.config.size) - if error_clipping_threshold is not None: - self.config.error_clipping_threshold = error_clipping_threshold - @config_layer('selective_fc') class SelectiveFCLayer(LayerBase): @@ -2791,13 +2786,7 @@ class TensorLayer(LayerBase): @config_layer('mixed') class MixedLayer(LayerBase): - def __init__(self, - name, - inputs, - size=0, - bias=True, - error_clipping_threshold=None, - **xargs): + def __init__(self, name, inputs, size=0, bias=True, **xargs): config_assert(inputs, 'inputs cannot be empty') super(MixedLayer, self).__init__( name, 'mixed', size, inputs=inputs, **xargs) @@ -2879,9 +2868,6 @@ class MixedLayer(LayerBase): self.config.bias_size = psize self.create_bias_parameter(bias, psize) - if error_clipping_threshold is not None: - self.config.error_clipping_threshold = error_clipping_threshold - # like MixedLayer, but no bias parameter @config_func -- GitLab From f2a82b16a25c2eb825ddb0a46b4966b01f248f22 Mon Sep 17 00:00:00 2001 From: gongweibao Date: Thu, 6 Jul 2017 11:58:43 +0000 Subject: [PATCH 1085/3256] add print messages --- python/CMakeLists.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index 361e764e25b..7a57d922ef9 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -17,15 +17,21 @@ add_custom_target(copy_paddle_master) SET(COPY_PADDLE_MASTER "") if(WITH_GOLANG) SET(COPY_PADDLE_MASTER "copy_paddle_master") + message("paddle_master_lib_path:" ${paddle_master_LIB_PATH}) + message("PROJ_ROOT:" ${PROJ_ROOT}) add_custom_command(TARGET ${COPY_PADDLE_MASTER} COMMAND cp ${paddle_master_LIB_PATH} ${PROJ_ROOT}/python/paddle/v2/master/ ) add_dependencies(copy_paddle_master paddle_master) endif(WITH_GOLANG) +message("paddle_master_LIB_NAME:" ${paddle_master_LIB_NAME}) +message("CMAKE_CURRENT_BINARY_DIR:" ${CMAKE_CURRENT_BINARY_DIR}) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/setup.py.in ${CMAKE_CURRENT_BINARY_DIR}/setup.py) +message("OUTPUT_DIR:" ${OUTPUT_DIR}) +message("py_env:" ${py_env}) add_custom_command(OUTPUT ${OUTPUT_DIR}/.timestamp COMMAND env ${py_env} ${PYTHON_EXECUTABLE} setup.py bdist_wheel COMMAND ${CMAKE_COMMAND} -E touch ${OUTPUT_DIR}/.timestamp -- GitLab From 660475b5ab1c6cc295420a527d549dc1f38ba03a Mon Sep 17 00:00:00 2001 From: gongweibao Date: Thu, 6 Jul 2017 12:14:30 +0000 Subject: [PATCH 1086/3256] modify to add paddle_master name --- python/CMakeLists.txt | 1 + python/setup.py.in | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index 7a57d922ef9..633d2b37863 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -27,6 +27,7 @@ endif(WITH_GOLANG) message("paddle_master_LIB_NAME:" ${paddle_master_LIB_NAME}) message("CMAKE_CURRENT_BINARY_DIR:" ${CMAKE_CURRENT_BINARY_DIR}) +message("CMAKE_CURRENT_SOURCE_DIR:" ${CMAKE_CURRENT_SOURCE_DIR}) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/setup.py.in ${CMAKE_CURRENT_BINARY_DIR}/setup.py) diff --git a/python/setup.py.in b/python/setup.py.in index dae01664876..9c77bed15f2 100644 --- a/python/setup.py.in +++ b/python/setup.py.in @@ -27,7 +27,7 @@ setup(name='paddle', description='Parallel Distributed Deep Learning', install_requires=setup_requires, packages=packages, - package_data={'paddle.v2.master': ['${paddle_master_LIB_NAME}'], }, + package_data={'paddle.v2.master': ['libpaddle_master.so'], }, package_dir={ '': '${CMAKE_CURRENT_SOURCE_DIR}' }, -- GitLab From b396055499c5bd34bea5753e7ca19e18e2f7044b Mon Sep 17 00:00:00 2001 From: gongweibao Date: Thu, 6 Jul 2017 13:34:40 +0000 Subject: [PATCH 1087/3256] add -V --- 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 ab60f1a38dd..0579bfcc7a9 100644 --- a/paddle/scripts/docker/build.sh +++ b/paddle/scripts/docker/build.sh @@ -60,7 +60,7 @@ EOF make -j `nproc` if [ ${WITH_TESTING:-OFF} == "ON" ] && [ ${RUN_TEST:-OFF} == "ON" ] ; then pip uninstall -y py-paddle paddle || true - ctest --output-on-failure + ctest -V --output-on-failure fi -- GitLab From 4daa247d80a3f94b8f60fe084bd3887b4b5c698e Mon Sep 17 00:00:00 2001 From: gongweibao Date: Fri, 7 Jul 2017 01:12:48 +0000 Subject: [PATCH 1088/3256] rm -v --- 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 0579bfcc7a9..ab60f1a38dd 100644 --- a/paddle/scripts/docker/build.sh +++ b/paddle/scripts/docker/build.sh @@ -60,7 +60,7 @@ EOF make -j `nproc` if [ ${WITH_TESTING:-OFF} == "ON" ] && [ ${RUN_TEST:-OFF} == "ON" ] ; then pip uninstall -y py-paddle paddle || true - ctest -V --output-on-failure + ctest --output-on-failure fi -- GitLab From 126e64fc830ba5b787a787fdd2e2b7f7e2ef1939 Mon Sep 17 00:00:00 2001 From: gongweibao Date: Fri, 7 Jul 2017 01:35:16 +0000 Subject: [PATCH 1089/3256] add cmake --- python/CMakeLists.txt | 7 ------- 1 file changed, 7 deletions(-) diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index 633d2b37863..361e764e25b 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -17,22 +17,15 @@ add_custom_target(copy_paddle_master) SET(COPY_PADDLE_MASTER "") if(WITH_GOLANG) SET(COPY_PADDLE_MASTER "copy_paddle_master") - message("paddle_master_lib_path:" ${paddle_master_LIB_PATH}) - message("PROJ_ROOT:" ${PROJ_ROOT}) add_custom_command(TARGET ${COPY_PADDLE_MASTER} COMMAND cp ${paddle_master_LIB_PATH} ${PROJ_ROOT}/python/paddle/v2/master/ ) add_dependencies(copy_paddle_master paddle_master) endif(WITH_GOLANG) -message("paddle_master_LIB_NAME:" ${paddle_master_LIB_NAME}) -message("CMAKE_CURRENT_BINARY_DIR:" ${CMAKE_CURRENT_BINARY_DIR}) -message("CMAKE_CURRENT_SOURCE_DIR:" ${CMAKE_CURRENT_SOURCE_DIR}) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/setup.py.in ${CMAKE_CURRENT_BINARY_DIR}/setup.py) -message("OUTPUT_DIR:" ${OUTPUT_DIR}) -message("py_env:" ${py_env}) add_custom_command(OUTPUT ${OUTPUT_DIR}/.timestamp COMMAND env ${py_env} ${PYTHON_EXECUTABLE} setup.py bdist_wheel COMMAND ${CMAKE_COMMAND} -E touch ${OUTPUT_DIR}/.timestamp -- GitLab From c78f41a331ddc181f98e5885f0aa64c29acb8182 Mon Sep 17 00:00:00 2001 From: liaogang Date: Fri, 7 Jul 2017 11:53:29 +0800 Subject: [PATCH 1090/3256] FIX: explicitly specify glog install path --- cmake/external/glog.cmake | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cmake/external/glog.cmake b/cmake/external/glog.cmake index b70e94a170f..bd401faa6eb 100644 --- a/cmake/external/glog.cmake +++ b/cmake/external/glog.cmake @@ -38,12 +38,14 @@ ExternalProject_Add( CMAKE_ARGS -DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS} CMAKE_ARGS -DCMAKE_C_FLAGS=${CMAKE_C_FLAGS} CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${GLOG_INSTALL_DIR} + CMAKE_ARGS -DCMAKE_INSTALL_LIBDIR=${GLOG_INSTALL_DIR}/lib CMAKE_ARGS -DCMAKE_POSITION_INDEPENDENT_CODE=ON CMAKE_ARGS -DWITH_GFLAGS=ON CMAKE_ARGS -Dgflags_DIR=${GFLAGS_INSTALL_DIR}/lib/cmake/gflags CMAKE_ARGS -DBUILD_TESTING=OFF CMAKE_ARGS -DCMAKE_BUILD_TYPE=Release CMAKE_CACHE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=${GLOG_INSTALL_DIR} + -DCMAKE_INSTALL_LIBDIR:PATH=${GLOG_INSTALL_DIR}/lib -DCMAKE_POSITION_INDEPENDENT_CODE:BOOL=ON -DCMAKE_BUILD_TYPE:STRING=Release ) -- GitLab From 1d2ef1db82136de8817229252774a797323f8eac Mon Sep 17 00:00:00 2001 From: fengjiayi Date: Fri, 7 Jul 2017 14:19:47 +0800 Subject: [PATCH 1091/3256] [draft] add registry for Op, OpProto and OpAttrChecker (#2739) * init op_registry.h * dev op_registry.h * add 'attr_checker.h', which is a draft of op attribute checker. * rename some macro parameters * 1. Use `Attribute` and `AttributeMap` instead of `OpDesc`. `AttributeMap` is a unordered_map of , and `Attribute` is a boost::variant object to hold multiple types of attribute value. 2. Use `PADDLE_ENFORCE` to print checkers' fail message. 3. Abstract default value operations to a new function: `DefaultChecker`. * rename DefaultChecker to DefaultValueSetter ZZ * Finish op_registry 1. Complete the development of interfaces between OpRegistry and Protobuf. 2. Add unit test for op_registry.h * Add demo and test of custome checker * fix merge conflict --- paddle/framework/CMakeLists.txt | 1 + paddle/framework/attr_checker.h | 119 +++++++++++++ paddle/framework/op_registry.h | 253 +++++++++++++++++++++++++++ paddle/framework/op_registry_test.cc | 122 +++++++++++++ 4 files changed, 495 insertions(+) create mode 100644 paddle/framework/attr_checker.h create mode 100644 paddle/framework/op_registry.h create mode 100644 paddle/framework/op_registry_test.cc diff --git a/paddle/framework/CMakeLists.txt b/paddle/framework/CMakeLists.txt index 970b2b9abde..4409c6feae2 100644 --- a/paddle/framework/CMakeLists.txt +++ b/paddle/framework/CMakeLists.txt @@ -11,6 +11,7 @@ proto_library(op_proto SRCS op_proto.proto DEPS attr_type) cc_test(op_proto_test SRCS op_proto_test.cc DEPS op_proto protobuf) proto_library(op_desc SRCS op_desc.proto DEPS attr_type) cc_test(op_desc_test SRCS op_desc_test.cc DEPS op_desc protobuf) +cc_test(op_registry_test SRCS op_registry_test.cc DEPS op_proto op_desc) py_proto_compile(framework_py_proto SRCS attr_type.proto op_proto.proto op_desc.proto) # Generate an empty __init__.py to make framework_py_proto as a valid python module. add_custom_target(framework_py_proto_init ALL COMMAND ${CMAKE_COMMAND} -E touch __init__.py) diff --git a/paddle/framework/attr_checker.h b/paddle/framework/attr_checker.h new file mode 100644 index 00000000000..c0c33d81149 --- /dev/null +++ b/paddle/framework/attr_checker.h @@ -0,0 +1,119 @@ +#pragma once + +#include +#include +#include +#include +#include +#include "paddle/framework/enforce.h" + +namespace paddle { +namespace framework { + +typedef boost::variant, + std::vector, std::vector> + Attribute; +typedef std::unordered_map AttributeMap; + +// check whether a value(attribute) fit a certain limit +template +class LargerThanChecker { + public: + LargerThanChecker(T lower_bound) : lower_bound_(lower_bound) {} + void operator()(T& value) const { + PADDLE_ENFORCE(value > lower_bound_, "larger_than check fail"); + } + + private: + T lower_bound_; +}; + +// we can provide users more common Checker, like 'LessThanChecker', +// 'BetweenChecker'... + +template +class DefaultValueSetter { + public: + DefaultValueSetter(T default_value) : default_value_(default_value) {} + void operator()(T& value) const { value = default_value_; } + + private: + T default_value_; +}; + +// check whether a certain attribute fit its limits +// an attribute can have more than one limits +template +class TypedAttrChecker { + typedef std::function ValueChecker; + + public: + TypedAttrChecker(const std::string& attr_name) : attr_name_(attr_name) {} + + TypedAttrChecker& LargerThan(const T& lower_bound) { + value_checkers_.push_back(LargerThanChecker(lower_bound)); + return *this; + } + + // we can add more common limits, like LessThan(), Between()... + + TypedAttrChecker& SetDefault(const T& default_value) { + PADDLE_ENFORCE(default_value_setter_.empty(), + "%s can't have more than one default value!", attr_name_); + default_value_setter_.push_back(DefaultValueSetter(default_value)); + return *this; + } + + // allow users provide their own checker + TypedAttrChecker& AddCustomChecker(const ValueChecker& checker) { + value_checkers_.push_back(checker); + return *this; + } + + void operator()(AttributeMap& attr_map) const { + if (!attr_map.count(attr_name_)) { + // user do not set this attr + PADDLE_ENFORCE(!default_value_setter_.empty(), + "Attribute '%s' is required!", attr_name_); + // default_value_setter_ has no more than one element + T val; + (default_value_setter_[0])(val); + attr_map[attr_name_] = val; + } + Attribute& attr = attr_map.at(attr_name_); + T& attr_value = boost::get(attr); + for (const auto& checker : value_checkers_) { + checker(attr_value); + } + } + + private: + std::string attr_name_; + std::vector value_checkers_; + std::vector default_value_setter_; +}; + +// check whether op's all attributes fit their own limits +class OpAttrChecker { + typedef std::function AttrChecker; + + public: + template + TypedAttrChecker& AddAttrChecker(const std::string& attr_name) { + attr_checkers_.push_back(TypedAttrChecker(attr_name)); + AttrChecker& checker = attr_checkers_.back(); + return *(checker.target>()); + } + + void Check(AttributeMap& attr_map) const { + for (const auto& checker : attr_checkers_) { + checker(attr_map); + } + } + + private: + std::vector attr_checkers_; +}; + +} // namespace framework +} // namespace paddle diff --git a/paddle/framework/op_registry.h b/paddle/framework/op_registry.h new file mode 100644 index 00000000000..81241b5342d --- /dev/null +++ b/paddle/framework/op_registry.h @@ -0,0 +1,253 @@ +#pragma once + +#include "paddle/framework/attr_checker.h" + +//#include "paddle/framework/op_base.h" +#include "paddle/framework/op_desc.pb.h" +#include "paddle/framework/op_proto.pb.h" + +namespace paddle { +namespace framework { + +//==================For test================// +class OpBase { + public: + std::vector inputs_; + std::vector outputs_; + AttributeMap attr_map_; + + virtual std::string Run() const = 0; + virtual ~OpBase() {} +}; +//=========================================// + +// helper class to set attribute type +struct AttrTypeHelper { + template + static void SetAttrType(AttrProto* attr); + + static Attribute GetAttrValue(const AttrDesc& attr_desc) { + switch (attr_desc.type()) { + case paddle::framework::AttrType::INT: { + return attr_desc.i(); + } + case paddle::framework::AttrType::FLOAT: { + return attr_desc.f(); + } + case paddle::framework::AttrType::STRING: { + return attr_desc.s(); + } + case paddle::framework::AttrType::INTS: { + std::vector val(attr_desc.ints_size()); + for (int i = 0; i < attr_desc.ints_size(); ++i) { + val[i] = attr_desc.ints(i); + } + return val; + } + case paddle::framework::AttrType::FLOATS: { + std::vector val(attr_desc.floats_size()); + for (int i = 0; i < attr_desc.floats_size(); ++i) { + val[i] = attr_desc.floats(i); + } + return val; + } + case paddle::framework::AttrType::STRINGS: { + std::vector val(attr_desc.strings_size()); + for (int i = 0; i < attr_desc.strings_size(); ++i) { + val[i] = attr_desc.strings(i); + } + return val; + } + } + PADDLE_ENFORCE(false, "Unknown OpDesc::AttrDesc::type !"); + return boost::blank(); + } +}; + +template <> +void AttrTypeHelper::SetAttrType(AttrProto* attr) { + attr->set_type(paddle::framework::AttrType::INT); +} + +template <> +void AttrTypeHelper::SetAttrType(AttrProto* attr) { + attr->set_type(paddle::framework::AttrType::FLOAT); +} + +template <> +void AttrTypeHelper::SetAttrType(AttrProto* attr) { + attr->set_type(paddle::framework::AttrType::STRING); +} + +template <> +void AttrTypeHelper::SetAttrType>(AttrProto* attr) { + attr->set_type(paddle::framework::AttrType::INTS); +} + +template <> +void AttrTypeHelper::SetAttrType>(AttrProto* attr) { + attr->set_type(paddle::framework::AttrType::FLOATS); +} + +template <> +void AttrTypeHelper::SetAttrType>(AttrProto* attr) { + attr->set_type(paddle::framework::AttrType::STRINGS); +} + +// this class not only make proto but also init attribute checkers. +class OpProtoAndCheckerMaker { + public: + OpProtoAndCheckerMaker(OpProto* proto, OpAttrChecker* op_checker) + : proto_(proto), op_checker_(op_checker) {} + + protected: + void AddInput(const std::string& name, const std::string& comment) { + auto input = proto_->mutable_inputs()->Add(); + *(input->mutable_name()) = name; + *(input->mutable_comment()) = comment; + } + + void AddOutput(const std::string& name, const std::string& comment) { + auto output = proto_->mutable_outputs()->Add(); + *(output->mutable_name()) = name; + *(output->mutable_comment()) = comment; + } + + template + TypedAttrChecker& AddAttr(const std::string& name, + const std::string& comment) { + auto attr = proto_->mutable_attrs()->Add(); + *(attr->mutable_name()) = name; + *(attr->mutable_comment()) = comment; + AttrTypeHelper::SetAttrType(attr); + return op_checker_->AddAttrChecker(name); + } + + void AddType(const std::string& op_type) { proto_->set_type(op_type); } + + void AddComment(const std::string& comment) { + *(proto_->mutable_comment()) = comment; + } + + OpProto* proto_; + OpAttrChecker* op_checker_; +}; + +class OpRegistry { + typedef std::function OpCreator; + + public: + template + static void RegisterOp(const std::string& op_type) { + creators_[op_type] = []() { return new OpType; }; + OpProto& op_proto = protos_[op_type]; + OpAttrChecker& op_checker = op_checkers_[op_type]; + ProtoMakerType(&op_proto, &op_checker); + PADDLE_ENFORCE(op_proto.IsInitialized() == true, + "Fail to initialize %s's OpProto !", op_type); + } + + static OpBase* CreateOp(const OpDesc& op_desc) { + std::string op_type = op_desc.type(); + OpBase* op = (creators_.at(op_type))(); + (op->inputs_).resize(op_desc.inputs_size()); + for (int i = 0; i < op_desc.inputs_size(); ++i) { + (op->inputs_)[i] = op_desc.inputs(i); + } + (op->outputs_).resize(op_desc.outputs_size()); + for (int i = 0; i < op_desc.outputs_size(); ++i) { + (op->outputs_)[i] = op_desc.outputs(i); + } + for (int i = 0; i < op_desc.attrs_size(); ++i) { + const AttrDesc& ith_attr = op_desc.attrs(i); + std::string name = ith_attr.name(); + (op->attr_map_)[name] = AttrTypeHelper::GetAttrValue(ith_attr); + } + const OpAttrChecker& op_checker = op_checkers_.at(op_type); + op_checker.Check(op->attr_map_); + return op; + } + + private: + static std::unordered_map creators_; + static std::unordered_map protos_; + static std::unordered_map op_checkers_; +}; + +std::unordered_map> OpRegistry::creators_; +std::unordered_map OpRegistry::protos_; +std::unordered_map OpRegistry::op_checkers_; + +template +class OpRegisterHelper { + public: + OpRegisterHelper(std::string op_type) { + OpRegistry::RegisterOp(op_type); + } +}; + +#define REGISTER_OP(__op_class, __op_maker_class, __op_type) \ + class __op_class##Register { \ + private: \ + const static OpRegisterHelper<__op_class, __op_maker_class> reg; \ + }; \ + const OpRegisterHelper<__op_class, __op_maker_class> \ + __op_class##Register::reg(#__op_type); + +// Demos + +class CosineOp : public OpBase { + public: + virtual std::string Run() const { + std::string msg = "CosineOp runs! scale = " + + std::to_string(boost::get(attr_map_.at("scale"))); + return msg; + } +}; + +class CosineOpProtoAndCheckerMaker : public OpProtoAndCheckerMaker { + public: + CosineOpProtoAndCheckerMaker(OpProto* proto, OpAttrChecker* op_checker) + : OpProtoAndCheckerMaker(proto, op_checker) { + AddInput("input", "input of cosine op"); + AddOutput("output", "output of cosine op"); + AddAttr("scale", "scale of cosine op") + .SetDefault(1.0) + .LargerThan(0.0); + AddType("cos"); + AddComment("This is cos op"); + } +}; + +REGISTER_OP(CosineOp, CosineOpProtoAndCheckerMaker, cos_sim) + +class MyTestOp : public OpBase { + public: + virtual std::string Run() const { + std::string msg = + "MyTestOp runs! test_attr = " + + std::to_string(boost::get(attr_map_.at("test_attr"))); + return msg; + } +}; + +class MyTestOpProtoAndCheckerMaker : public OpProtoAndCheckerMaker { + public: + MyTestOpProtoAndCheckerMaker(OpProto* proto, OpAttrChecker* op_checker) + : OpProtoAndCheckerMaker(proto, op_checker) { + AddInput("input", "input of cosine op"); + AddOutput("output", "output of cosine op"); + auto my_checker = [](int i) { + PADDLE_ENFORCE(i % 2 == 0, "'test_attr' must be even!"); + }; + AddAttr("test_attr", "a simple test attribute") + .AddCustomChecker(my_checker); + AddType("my_test_op"); + AddComment("This is my_test op"); + } +}; + +REGISTER_OP(MyTestOp, MyTestOpProtoAndCheckerMaker, my_test_op) + +} // namespace framework +} // namespace paddle diff --git a/paddle/framework/op_registry_test.cc b/paddle/framework/op_registry_test.cc new file mode 100644 index 00000000000..17849ca0191 --- /dev/null +++ b/paddle/framework/op_registry_test.cc @@ -0,0 +1,122 @@ +#include "paddle/framework/op_registry.h" +#include + +TEST(OpRegistry, CreateOp) { + paddle::framework::OpDesc op_desc; + op_desc.set_type("cos_sim"); + op_desc.add_inputs("aa"); + op_desc.add_outputs("bb"); + + auto attr = op_desc.mutable_attrs()->Add(); + attr->set_name("scale"); + attr->set_type(paddle::framework::AttrType::FLOAT); + attr->set_f(3.3); + + paddle::framework::OpBase* op = + paddle::framework::OpRegistry::CreateOp(op_desc); + std::string debug_str = op->Run(); + std::string str = "CosineOp runs! scale = " + std::to_string(3.3); + ASSERT_EQ(str.size(), debug_str.size()); + for (size_t i = 0; i < debug_str.length(); ++i) { + ASSERT_EQ(debug_str[i], str[i]); + } +} + +TEST(OpRegistry, IllegalAttr) { + paddle::framework::OpDesc op_desc; + op_desc.set_type("cos_sim"); + op_desc.add_inputs("aa"); + op_desc.add_outputs("bb"); + + auto attr = op_desc.mutable_attrs()->Add(); + attr->set_name("scale"); + attr->set_type(paddle::framework::AttrType::FLOAT); + attr->set_f(-2.0); + + bool caught = false; + try { + paddle::framework::OpBase* op __attribute__((unused)) = + paddle::framework::OpRegistry::CreateOp(op_desc); + } catch (paddle::framework::EnforceNotMet err) { + caught = true; + std::string msg = "larger_than check fail"; + const char* err_msg = err.what(); + for (size_t i = 0; i < msg.length(); ++i) { + ASSERT_EQ(err_msg[i], msg[i]); + } + } + ASSERT_TRUE(caught); +} + +TEST(OpRegistry, DefaultValue) { + paddle::framework::OpDesc op_desc; + op_desc.set_type("cos_sim"); + op_desc.add_inputs("aa"); + op_desc.add_outputs("bb"); + + paddle::framework::OpBase* op = + paddle::framework::OpRegistry::CreateOp(op_desc); + std::string debug_str = op->Run(); + float default_value = 1.0; + std::string str = "CosineOp runs! scale = " + std::to_string(default_value); + ASSERT_EQ(str.size(), debug_str.size()); + for (size_t i = 0; i < debug_str.length(); ++i) { + ASSERT_EQ(debug_str[i], str[i]); + } +} + +TEST(OpRegistry, CustomChecker) { + paddle::framework::OpDesc op_desc; + op_desc.set_type("my_test_op"); + op_desc.add_inputs("ii"); + op_desc.add_outputs("oo"); + + // attr 'test_attr' is not set + bool caught = false; + try { + paddle::framework::OpBase* op __attribute__((unused)) = + paddle::framework::OpRegistry::CreateOp(op_desc); + } catch (paddle::framework::EnforceNotMet err) { + caught = true; + std::string msg = "Attribute 'test_attr' is required!"; + const char* err_msg = err.what(); + for (size_t i = 0; i < msg.length(); ++i) { + ASSERT_EQ(err_msg[i], msg[i]); + } + } + ASSERT_TRUE(caught); + + // set 'test_attr' set to an illegal value + auto attr = op_desc.mutable_attrs()->Add(); + attr->set_name("test_attr"); + attr->set_type(paddle::framework::AttrType::INT); + attr->set_i(3); + caught = false; + try { + paddle::framework::OpBase* op __attribute__((unused)) = + paddle::framework::OpRegistry::CreateOp(op_desc); + } catch (paddle::framework::EnforceNotMet err) { + caught = true; + std::string msg = "'test_attr' must be even!"; + const char* err_msg = err.what(); + for (size_t i = 0; i < msg.length(); ++i) { + ASSERT_EQ(err_msg[i], msg[i]); + } + } + ASSERT_TRUE(caught); + + // set 'test_attr' set to a legal value + op_desc.mutable_attrs()->Clear(); + attr = op_desc.mutable_attrs()->Add(); + attr->set_name("test_attr"); + attr->set_type(paddle::framework::AttrType::INT); + attr->set_i(4); + paddle::framework::OpBase* op = + paddle::framework::OpRegistry::CreateOp(op_desc); + std::string debug_str = op->Run(); + std::string str = "MyTestOp runs! test_attr = " + std::to_string(4); + ASSERT_EQ(str.size(), debug_str.size()); + for (size_t i = 0; i < debug_str.length(); ++i) { + ASSERT_EQ(debug_str[i], str[i]); + } +} \ No newline at end of file -- GitLab From 50e29bac38f485dca831b62ddcc40da2f38521ff Mon Sep 17 00:00:00 2001 From: Luo Tao Date: Fri, 7 Jul 2017 15:39:00 +0800 Subject: [PATCH 1092/3256] mistaken: Folk -> Fork in develop branch --- doc_theme/templates/layout.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc_theme/templates/layout.html b/doc_theme/templates/layout.html index 65e61c5f298..9fca69dc4e7 100644 --- a/doc_theme/templates/layout.html +++ b/doc_theme/templates/layout.html @@ -101,7 +101,7 @@

  • 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ξTV~UbudW_yU={*W+BQ7)J4yU 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.Ksl035 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 diff --git a/doc/design/images/trainer.graffle b/doc/design/images/trainer.graffle index e779dda3189cf3e4d87ac242d22a7b2f7fdbcc6d..def467ba991971fac1d3de1131c8f1b18f6d9b2b 100644 GIT binary patch literal 8566 zcmZ{pWmgZ$0({Jz27dDuhsolJcPi{@FTiR$vyF|@C>|8y+;SLd4d=IkE=ff zHTTn$x-AHTpRoiNS|VeNltkk>`$4ejKe#`ZTl#~*H`iu3JJk8FFT*wRj2PVp`@@_& z)V;#?pMZT}@XjAuTepkL+s3QCgnbl9|bc)!T*GBRB9OMVoAwZ$9gYtR9XKku)re{(Zy^o551wc2mX6{DBWrQ$*Y+CF?D!zM`IZji+|?Oa3@}|915D z`a|OFqW>l#80~}CQ@OU#h8&UgV>^sQR4+>D6gDwpOCFMlbM2l^dja6o85MQph{4}QBOLs-dL--YdF$1o5Ih&*A+ z5Hf9241#?lSCzzX13(-W#%70zd)(aRwd+u--til6z(YW_xcw= zTlzt7U_r))dRW8uTd0uu+uirdFaG9l-7l}Ce>avNPm>n0$`uH23J_?9*Gut5Q0+dcAf%8p);l{n^o{w=) z!nSbit@q$#FotR_4vDOyY`U~ERG^H(>_WJzrO#@A4BjeXlVCjz_%gnC___Szjqi_+ z$3qryzx1pL9dfaMJpB@`IacvHfw+B)yHm&Z*E%tjlL15so;}(dO_E3C6DP;&+OnMt zdcNOq6o(asaZjBWh$Cm7A z!M+T6K1Yd0hq|sWsHHMdny40Usk22>f0I(A!2@>V%wG_N)59X zzX*i>@KKZ%9@@`mArw3 z7Yche#-3EhK2$sVWrVGt9Mme<@cfA}R(OTS1Xwz@PH>}m zoj&??cW36l`q*r>rC`q1SNFoCe4ZHUMfl!O!S7d{36dt*ZCSh4$3_wt2r)@1Hd)yI}FM=ESm8)k>_o5vnB`Gt!6t#Qi%Po8%!%ywI2A#rmr zvrW6scKy$YFHPifOi0}jhCRP7jdB1dycgh|48}A(YkW}cB3A~n^0SdH9GxGLEKLZS zz$xwgVJDnZFFC2X?DKA6;=L&a9sF(*-0pN~H`g0w-dCQX{CC`f=*uwHG_(H95MP#K z_4?36haX>GypbxkkRA{-3;yh?5J8jz4SiCDs>vy+(##O#u))$oXFaex%0=DcQ>rmfJH*TKI7QXHQJE=|V7m2z#dWQV` zwp3k$4hfHufVYKSG4NX*7v%89eWc)6{6eVGY7M_OUo7>2>_?FwjI3T+BIC1GaH?=%P0F2 zb7`X?8i+^K$8KyTfg}HD&!@^@1nf-HsB&t0_6^S?1_z4gg)a;{G{7=~7O<~o-nthT zkn4HfHi?x7Sn_G3Mr7t&$a>iN>M#r`4KnjB^nYeDbURoxP79+s%pAtw)LTD$!mTf+ z5Bg$_%lyGfk!3Vs^p?B&bJ~SKE&i-BWd;WhCyj-5eRxU?wDOTh-ZsCo>wv4(`x;Hs zcez!)*88r59>X2819`JvSVg<)r1-RjFDIN5@RdW-7qs1+%IexK36aq|F<@TV<&==W z)EaP2IB#A_`lR`3{V zKXNP23AU$4cUox<3cB5qWfBZRFLqMio3o z%iy||tzkZ~{4Nzi=3naPk~=ITDb-{DuquY1DMe0UW_FMui^WNuFLa3A-_-@FYpv2J zK@j*+ZQ^66^l$5H>F26)p@$qmKU(@#ak&92jR*G5)2>ANYL>JU%V8>y`9I$nykL`< zIXylQ{Vm}`xgdcGuiS$}=`qiMfEWq2{R+|VG|_1$tiS;|3lfQ?_!{v9#<5Oq0e1t^ zhDx24@0WbfNxT?F6{2tSAgtLf)Jp4>mpLAI-&Dzw1 zE+LxG9i+r?G9r+L5#pY_;n(36XX;`y!p8M4O-TUn>X&8JRb7l z+xHJ#@k3}f0$sn*a5{e>x3z&tib<7H{t9d``*oRQ#Cu*l+f$TubM@ocKeU!)!ZD z1Ep|DmZxGE?gSNC!X^Vs`b|VcUZ(JiEeHN>@9&wvbInUsOKTwURw_5~cu%s-XVgSu zn`|dD@P6_GkCPWYKNMji+c;8v(PKx6;E(T8C+Jc{^gez6<*{0mAHDQ^F}%K7{&bsy z=-yFT&ELlhfx<=`t}+%8fr3!Z@8eRY&D8R7w_W=GBzHJa*>WPsj|F?8pH0 zB15|bZ{AEe$G!Yzh8 zDOdPemPS+6SS)~imAFJwqk%ksC0CkgO=V|=M^6T0hH9qo8(zd}V)gh5Ef=aTE`IFE z2ck+f{(|QZL_rR42t;^A*={{Uc!ibSfg~f0MW1xquF%Q4S5rEoC&e>GR57`Nff>24C3h!h3SNarpvWrJSFEVNXzM zm6&pzSgz=k7@jGvnB)QR%$s=mK(#LA-`k8Q<@uS<9+niXz^2*qw0BvG+|u-;vMlZ9 ze-1hWzN^3@Ev}TVhiVqXhFc1jyy<{m2^49_XEc~Je7%rc-3Gj@m6~(t;QeTMjJzqT z>EQOaso$OUX)Q(wc~`nP3Ux>Z?R3_?>6^9gORb{}XbNH@?mF~?Os4*9j^gvApo3I! zkocj?eTiV#-*PjKMP`%QMKL#!2r?epxRemi@Apn<uQ}V%o-UE zIa8_pLKiAH)_G)-7G$Vr%Wjy**M`}MxYb~FBOyKT@IMHTF&d3NEFE4T?l(kSadV0P zM^NIG!)Ux2Ud9_Y_C^c+p-Eh5^%YHUWzpAHfd?@qldMnZ#F4p0&R->M!iG!?RPq;PA}`UsxqyX{nVeo?-(7^L*3X{TgaYT_HatAR(W*Tz;$Zo|lNsFW z4%r#ZBlp&46lF}+i(M%EPtQNAE z#I(wGT*K(91QCZfStY{~AVFYGgW2w3Ns__sWNZ-ByPD1H*W~cQbCFY!N@||)j^z&w z_I8f(_p5s?_ep*B#Vdjxy)mRxfQIqSfu{70rp-)hM$~#4LVgj0{f)@C>rOw=3+bj< zuK9URDU&rwy%55|#aU5f1JbSw970b6h?mZA6VzvjnWV|U?!&gqLd@gjd(BOnEYYO8 zaCN_)>_@WH$;%tF=>43|D*dR&5_3GeTsmUZ&lpfP1ISz>1pW8elLP-+-^&W(lo(~X zVu=*>FPp>rYuWl|&X6^MzF?GC7#(UiTqE1R|0L5nC2rq~Gcfm)@EpD!&4J1I z5)YG9XWzj~5Ooow+BJl+vz)?Tkv}&+e#!2E0E-&yZM*l=GMPA`+HR!1-9xIg)`?Yw zr>))>TC0h)!1+}Fi>I|KsEl2FQ>g)RI2<-oE zdBrT4`k)AFTvnGw83oMR*18TpahE;dQs{^8rp!XMtzs2&p4hAhtoxaDbRso1W9;-) zV66X|-CSL&JGJZQ<&II;yG-=t1Gr5D*9!wDLpX5rbv$3mfI3P74&1q*7aIi|oUtaq zJG%BR6QVnO$Ic8sf+<`_kK--|P9o2)Pe{H#9aU->B3j?Tx9RV>T*e%zpEYemHW;O$ zBWLjFW^jUVf?vs&$b@w=d}O(|UCj3gxf;DGrjR192dC3`l$o+m`3o$d; zJHTBhq#OM0kC<+w3Py)HM^ch&#<)VI6&wauqX^;v%-sBICmEdH3>*y^a;WD2UU!N} zrN`2tnnXyYl3W-_r7U>yx$i{mV8R*)+VuGg3xIOXlsa~4um&5yQk_#EW$W;Ut3j)_ z*qZ&36+j`N(8R#*^7}zgiDdlYeYnl**;Q59X#Gl57Mxo<_2}uBRRK317{%!bUJvF{GzkB@@y>{y4 zxP3d!U#!NdS(|5)xkJ+Z*Vad?Z|g{Ca{LqptJF)yqt`sAx24(WTrMZ{W`dFd4BKgT z$t(RpiDJO@{0e$#^5#ylNI<5SrHhEsbzN_0?C{f5guFuv&y%&MO|~_pi(%@Ek_CI+ zx&JwQ_3Z9 zqv-7ViFYQ#>?E7p5}Q~4>#u!`ey*|87#mAua|A9IDpj|nno%uw#55!}_)LMMa>9*Y zT^`rq(kU7_e*osHK3o}`RuaAz+2SiX{BrM7fbvH>~XDa3i>P=G8NOQ|@v58xkb+T=IJsj zZS1QRxsf!FK7Z#&!?mcYO#~7T?=awWI)Whnu#P6}M}<@o-y@G&L(8t+YWj*wheG)8 zsgCsgDOkGP)axt^JE3x_wV$Z3K3J!mV9bl2jVK@}G}BP#IBLzH z>!I;RG96OxK1}RRw8JHeN9(E>lgr>?huSAOCPWFjY2DsF2kJn&bB}4&?3}7lFbUPN z-RFe&D2pDUJ4C*3JCXhs-pcpJk3u9ZW;VUb#EXH$VVs)wX0nDSIIao>5_8ZIIPBmD z%P+70Z9f#sYZ6KggMgH6dKc~4YKoFOuBgEDnUJ>mcmlbzbZ?K}9pHbqaW``u^Tf9A z0}r)4(ABv{7=7}3WJlO=CLGsIlf##J{~%`wn7!p_QK!AexR$JBq{}By7Cn6tH9fxg zKxW=mVWZPy%!T80Rr@Y)I(-{Yw&;mT(i)4d;Y)`un&X7hOV z0>rVAP+{}ULqaqu=G2w5-`VIB@L@wd)AS){3Ywn2MEQBgTmS~Wp>g?(=dXvx`5467 zC}|L_f^?hH&gHPX>xYM^kyp>_$wcmBGAXUWJaPlcP91&aa_`g75u0Z1Q)!Iww!`pH zs_{w*tIe+bSj-A;;!JZBG#w+yHv1N)7PZRuomk$}s~Z1kbpLak`vCugZQuovOvS~`Vv$=lK?*O|o z-DzNU%VVpZo?=7=AT* zZAWLGYb4mjQ9^>DhiDs)&LOGhZi=wv^C*V{si_hH(v{kE>A9G7t%ckt4DI{G()p|# zIx*}rM;N9DCEby=Re~v&@hqpj1j_%!)$E;&cI<0Ahyy4mZG0QKtI)a-VzGAIm_YH~ z^J-yT0oqf?<}`oy6U>oV#-&(z%W4490Blc(s1-nS8dU3@St@A21L; zA;ei`zD*{3TKmK`nwR#vKRmBDupG5oFYd2ZB&cz6?iUi6&{RU7`PAe27Qcd7Gq>hX zO7QBAS`C!VKnq9~o}(V*%2U^tO2#WoM2hzq{E!md<9$HQWKqhk%c5I~WGQA;(a{{f zTSB!K=V!4rqNT~4QsZ3Sl&^-KUd#aQ8DQS$fS+Xz=7z{BVr1bd2vYb=(=sOLf4?5`8 zTy}V#eh=H>;eSTMvQ3kTt&*{rNyBustUhjT7fHdI3Rt=1*ygJ8iQNXeCL@O8wMdH^ z3DbkFx!cn)<RA60!TbZ zb0RAMq6@n|6|Bq)^+5(nc=-fhhkU*b(&yoWbyiyD^R3E=qd~{U;fdIc2$8ze+M10?K%h8!H63ITxFNhbG87y!xD^t;j7JZ|qVM?MgPEZY( zO(KwL^dQAcSHP=Ak>V(t;5s4pZrc$C5#g1f(IDZGOHF*9R2jb6m}#Z(iM4C5Hm|+i z*auR;ZSmz_V}FjO(cmW0;AvkyTmCa>K|dU?9;^LDUkC*g6_eWF0kAtoQ+Sq-vgIVm z%}xqNOTAW#j&7k^d|v6JI28HnRSL5 z5riznEYi-N+)J*N)=9F_AaKpEY*7>7RIaf~1v&{O??*Cc6T?s9fE5!o6i!XFNTqrx z60vr5W3rzXKN7|u9Qsfu8(tfNChPxR0emI8mb99z@Flu5j>sjt6eMlUX{kXL)-wUQ zP%MV*I90AXQI6tE9@TpR9eMw628QEh7M9cscyeasCW7!C$~?e*4MnRs!Jd*s!H)Gg zR^8LmT;Toi(|bw_hVX{j^4!2N@zqog*zhWp@^v8cF{-}~Ff6b-O8{>Wf%Z^sBbuSW zKNj!dFfMf#E%}2kw3!2$&Rh}+_Zu2ly=^e7wom=^*5>e;cZQ$Uq)4BGOl%UqKnZXV1qk2*gdR)L#%Vu4$P^ACxrfcL6G+^mOSOi^ane@(LFXSc>% zZWRe-!}h~Dz)t~K>8Wa;6pV%C=1gFA$r`2+OEL3G)iO?;Bc}Q7%;CK)F2B|Q)X5SU zD(hm4yBs+9?;M}Z3)G5Qm-!;Y_1v}gZN>IZV~xDFPr5lOJcZvn-7-OU3r;0B5G=)1 zn86?GOA&9WNS4EDVNECK*cYGjj*UJ!) zPN})vB8|+1b(1(XT=tf9XJyAtl+D$Dqt2V&U{X|h-|x#Pk=JT$gPu{z0=ObXH*AL7 z>ten-7EkD}1KQTqvi>tS^`P(5baTH4t%;!q1ja8^67&{5=2>RLuyD!A2>~zLzwVV? zXnSY$Py?b_jiiz-;!&5S=ZW+A^|o@V=Yx4Q(}@nLbI$rcr3KY+UkV*qAW$=b51O_M zY(INCZ*00Ei1v(&@ubP?BKc1T#}^oe5QzD%&7rmZql4v5h_v@}B zs}S7$#1@j8dkEd52We62jn`-^HV*Mr#*;C~JikPi4J7|d4e!VLY+6i_Q;7--VEa^; zlP2EN`!XtG*})x@e#WZPR0Wri+%O(_IU6=ZmY6uZN-s5I#mP2xO#!NOkji;iy;aPn zx8~6Hy?Y7e_sM+FHO7yIqs8ZQ{@3;FIfv9$%{i4K;MK)>zVf7k(rEzI%i^ns6{A?W zzsGqxw(-j7X*(xPXdpoZ`(nXcs(9pY*ZScfLm|Dd2LgEs;V(ix=seojiI|2>^>VDL zMw1&{;`thNR5S|2Mf)3tWP(jI|A`Xp-~S&`Qcho(y3wq_+1ap$Wbo^nCP_6=7=M5) z-;e9p|J(oGJJ>(+ta_2#Btl^zXG(FquV+G?MzX?N0)<8bF9%cEP`;Td*+ro81-5~X z${Rh-TL=IC9Hdn`OYC80Tw?f0I8^O|+zncJME#W_DX0f~qoR9fYDj(9i354EvLQ?X z4?JUO68tAu&x53b(9Q1F_35f@O+bA?z})b=yDOCG6sjE8!nA+>*R5+;1rosvUX6?Fg^6RG+b(DZ zFsy%-%cj5kIa~eY_`p-(BdI3k9fM|?FO_nB&US;WHa_(%gAN^I58oh{S3%qiuznO5 oP$SA$b3R|vB{RK@BPQ*%Ek%I)Kk7TMod5s; literal 6434 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 u=fDEtK Date: Wed, 29 Mar 2017 10:35:40 +0800 Subject: [PATCH 0050/3256] update design doc --- doc/design/cluster_design.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/doc/design/cluster_design.md b/doc/design/cluster_design.md index 44f0591d77d..e20a4491147 100644 --- a/doc/design/cluster_design.md +++ b/doc/design/cluster_design.md @@ -9,13 +9,13 @@ -为了完成一个深度学习的训练任务,集群中会运行多个trainer和parameter server,每个trainer启动时,会先尝试从parameter server集群下载最新的参数,然后以mini-batch为单位读取训练数据集中的一部分数据(Data shard)。在完成这个mini-batch数据的神经网络前馈和反向传播计算后,将参数梯度发送给对应的parameter server。随后trainer开始下一轮计算。 +为了完成一个深度学习的训练任务,集群中会运行多个trainer和parameter server,每个trainer启动时,会先尝试从parameter server集群下载最新的参数,然后以mini-batch为单位读取训练数据集中的一部分数据(Data shard)。trainer会在训练过程中持续与parameter server通讯,上传计算出来的梯度以及下载最新的模型。 每个parameter server保存所有parameter的一个分片(Global model shard),并负责接受所有trainer发送的梯度,完成SGD和优化算法,然后发送更新后的parameter到每个trainer。 -这样,通过trainer和parameter server的分布式协作,可以完成神经网络的SGD方法的训练。Paddle可以同时支持同步SGD(synchronize SGD)和异步(asynchronize SGD)。 +这样,通过trainer和parameter server的分布式协作,可以完成神经网络的SGD方法的训练。Paddle可以同时支持同步SGD(synchronize SGD)和异步SGD(asynchronize SGD)。 -在使用同步SGD训练神经网络时,Paddle使用同步屏障(barrier),使梯度的提交和参数的更新按照顺序方式执行。在异步SGD中,则并不会等待所有trainer提交梯度才更新参数,这样极大的提高了计算的并行性:parameter server不之间不相互依赖并行的接收梯度和更新参数,parameter server也不会等待trainer全部都提交梯度之后才开始下一步,trainer之间也不会相互依赖,并行的执行模型的训练。可以看出,虽然异步SGD方式会使参数的更新并不能保证参数的顺序的同步的更新,在任意时间某一台parameter server上保存的参数可能比另一台要更新,这样反而会给参数优化过程带来更多的随机性。在实践中,异步SGD在带来更高效率的同时并没有特别影响算法的准确性。 +在使用同步SGD训练神经网络时,Paddle使用同步屏障(barrier),使梯度的提交和参数的更新按照顺序方式执行。在异步SGD中,则并不会等待所有trainer提交梯度才更新参数,这样极大的提高了计算的并行性:parameter server之间不相互依赖,并行的接收梯度和更新参数,parameter server也不会等待trainer全部都提交梯度之后才开始下一步,trainer之间也不会相互依赖,并行的执行模型的训练。可以看出,虽然异步SGD方式会提高参数更新并行度, 但是并不能保证参数同步更新,在任意时间某一台parameter server上保存的参数可能比另一台要更新,与同步SGD相比,梯度会有噪声。 在上面的分布式计算模型中,使用异步SGD比同步SGD可以一定程度的提供训练任务的容灾性。假设在某一时刻,一个trainer进程停止工作,其他的trainer仍然可以完成对部分数据的训练。 @@ -27,7 +27,7 @@ 1. 支持训练任务的前置任务和后置任务,支持训练任务的定时调度和对在线流式数据的处理 ## 模型参数检查点(Checkpointing) -模型数据检查点的实现,可以有效的避免parameter server的单点或多点同时故障。模型参数检查点通过定期向磁盘上保存一份存储在parameter server内存中的模型数据的完整镜像,来保证训练过程可以从中间状态重新启动。在一个不可中断并缺少备份的训练任务中,可以通过阶段性的在每个parameter server的 ***本地磁盘/分布式存储挂载点*** 保存检查点快照达到容灾的目的,比如每个pass或每n个mini-batch保存一次快照。在出现单点故障时,只需要恢复这台节点,或者将这台节点迁移到另一个节点并启动即可恢复训练任务。 +模型数据检查点的实现,可以有效的避免parameter server的单点或多点同时故障。模型参数检查点通过定期向磁盘上保存一份存储在parameter server内存中的模型数据的完整镜像,来保证训练过程可以从中间状态重新启动。在一个不可中断并缺少备份的训练任务中,可以通过阶段性的保存每个parameter server的数据快照(snapshot)到 ***分布式存储服务/分布式存储挂载点*** 达到容灾的目的,比如每隔10分钟或1小时保存最新的快照,并删除更早的快照。在出现单点故障时,只需要恢复这台节点,或者将这台节点迁移到另一个节点并启动即可恢复训练任务。 @@ -82,10 +82,11 @@ -如图,数据存储在分布式文件系统中,并将预处理之后的文件切割成3个block存储在不同的机器上。在训练任务开始时,master读取这个分布式文件的元数据,并将一个block分配给一个trainer,然后将分配信息写入etcd中。随后trainer从etcd中获取到数据的分配信息并开始执行训练。一个block数据训练完成后,master负责在将新的block分配给一个trainer(途中虚线所示)。 +如图,数据存储在分布式文件系统中,并将预处理之后的文件切割成3个block存储在不同的机器上。在训练任务开始时,master读取这个分布式文件的元数据,并将一个block分配给一个trainer,然后将分配信息写入etcd中。随后trainer从etcd中获取到数据的分配信息并开始执行训练。一个block数据训练完成后,master负责在将新的block分配给一个trainer(图中虚线所示)。 master不会直接发送数据给Trainer而是负责协调训练数据的分配,并以ETCD为协调中心。所以master是一个无状态程序,任务运行过程中,master停止后只需要重新启动即可。 +## 第一版**不需要**支持的特性 ### 推测执行/加速执行(TODO) 在异构集群中,如果存在某些trainer执行速度过慢会影响整体集群的速度(如图中Trainer 1),此时master将负责启动一个新的Trainer(Accelerate Trainer 2),使用同样的训练数据block。哪个trainer先完成block的训练,则把另一个慢速的kill掉。 @@ -94,7 +95,7 @@ master不会直接发送数据给Trainer而是负责协调训练数据的分配 * 支持流式数据接口和常规文件接口 * 对不同的分布式存储,需要实现不同的reader wrapper -## 动态扩容/缩容 +### 动态扩容/缩容 虽然故障恢复可以提供任意时刻的节点新增和删除仍然可以保证任务正常运行,但通常这样是比较暴力的。为了能graceful的关闭多个节点,master需要提供对应的API接口: ```python -- GitLab From 6802b65cd203f3302263e6f11156d693bac58b47 Mon Sep 17 00:00:00 2001 From: qiaolongfei Date: Fri, 14 Apr 2017 00:20:55 +0800 Subject: [PATCH 0051/3256] init support remote updater --- paddle/api/PaddleAPI.h | 1 + python/paddle/v2/topology.py | 10 ++++++++++ python/paddle/v2/trainer.py | 30 +++++++++++++++++++++--------- 3 files changed, 32 insertions(+), 9 deletions(-) diff --git a/paddle/api/PaddleAPI.h b/paddle/api/PaddleAPI.h index c4f5dca26cc..f5ead40682c 100644 --- a/paddle/api/PaddleAPI.h +++ b/paddle/api/PaddleAPI.h @@ -469,6 +469,7 @@ private: enum GradientMatchineCreateMode { CREATE_MODE_NORMAL = 0, + CREATE_MODE_SGD_SPARSE_CPU_TRAINING = 3, CREATE_MODE_TESTING = 4 }; diff --git a/python/paddle/v2/topology.py b/python/paddle/v2/topology.py index 737b6bf1e2e..86e7549e972 100644 --- a/python/paddle/v2/topology.py +++ b/python/paddle/v2/topology.py @@ -73,6 +73,16 @@ class Topology(object): assert isinstance(self.__model_config__, ModelConfig) + def use_sparse_updater(self): + """ + check if any parameter require to use sparse_update + :return: + """ + for parameter in self.__model_config__.parameters: + if parameter.sparse_update or parameter.sparse_remote_update: + return True + return False + def proto(self): return self.__model_config__ diff --git a/python/paddle/v2/trainer.py b/python/paddle/v2/trainer.py index f5797a86c2b..2dac95b63d5 100644 --- a/python/paddle/v2/trainer.py +++ b/python/paddle/v2/trainer.py @@ -42,7 +42,7 @@ class SGD(object): :type extra_layers: paddle.v2.config_base.Layer """ - def __init__(self, cost, parameters, update_equation, extra_layers=None): + def __init__(self, cost, parameters, update_equation, extra_layers=None, is_local=True): if not isinstance(parameters, v2_parameters.Parameters): raise TypeError('parameters should be parameters') @@ -55,15 +55,21 @@ class SGD(object): self.__topology__ = topology self.__parameters__ = parameters self.__topology_in_proto__ = topology.proto() - - # In local mode, disable sparse_remote_update. - for param in self.__topology_in_proto__.parameters: - if param.sparse_remote_update: - param.sparse_remote_update = False - + self.__is_local__ = is_local + + self.__use_sparse_updater__ = self.__topology__.use_sparse_updater() + # # In local mode, disable sparse_remote_update. + if is_local: + self.__use_sparse_updater__ = False + for param in self.__topology_in_proto__.parameters: + if param.sparse_remote_update: + param.sparse_remote_update = False + + self.__gm_create_mode__ = api.CREATE_MODE_NORMAL if not \ + self.__use_sparse_updater__ else api.CREATE_MODE_SGD_SPARSE_CPU_TRAINING self.__data_types__ = topology.data_type() gm = api.GradientMachine.createFromConfigProto( - self.__topology_in_proto__, api.CREATE_MODE_NORMAL, + self.__topology_in_proto__, self.__gm_create_mode__, self.__optimizer__.enable_types()) assert isinstance(gm, api.GradientMachine) self.__gradient_machine__ = gm @@ -88,7 +94,10 @@ class SGD(object): event_handler = default_event_handler __check_train_args__(**locals()) - updater = self.__optimizer__.create_local_updater() + if self.__is_local__: + updater = self.__optimizer__.create_local_updater() + else: + updater = self.__optimizer__.create_remote_updater(num_passes) updater.init(self.__gradient_machine__) self.__gradient_machine__.start() @@ -108,6 +117,9 @@ class SGD(object): v2_event.BeginIteration( pass_id=pass_id, batch_id=batch_id)) pass_type = updater.startBatch(len(data_batch)) + if self.__use_sparse_updater__: + self.__gradient_machine__.prefetch(feeder(data_batch)) + updater.getParametersRemote() self.__gradient_machine__.forwardBackward( feeder(data_batch), out_args, pass_type) self.__gradient_machine__.eval(pass_evaluator) -- GitLab From 87dfc12a295c17ea02188b0fe0e6b55943191582 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Fri, 14 Apr 2017 14:40:01 +0800 Subject: [PATCH 0052/3256] Follow comments --- .../examples/model_inference/dense/main.c | 6 +++++ .../model_inference/multi_thread/main.c | 22 ++++++++++--------- .../examples/model_inference/sequence/main.c | 7 ++++++ .../model_inference/sparse_binary/main.c | 6 +++++ paddle/capi/tests/test_Init.cpp | 0 5 files changed, 31 insertions(+), 10 deletions(-) delete mode 100644 paddle/capi/tests/test_Init.cpp diff --git a/paddle/capi/examples/model_inference/dense/main.c b/paddle/capi/examples/model_inference/dense/main.c index 4dddd65bbfb..e03fe748f61 100644 --- a/paddle/capi/examples/model_inference/dense/main.c +++ b/paddle/capi/examples/model_inference/dense/main.c @@ -59,5 +59,11 @@ int main() { } printf("\n"); + CHECK(paddle_matrix_destroy(prob)); + CHECK(paddle_arguments_destroy(out_args)); + CHECK(paddle_matrix_destroy(mat)); + CHECK(paddle_arguments_destroy(in_args)); + CHECK(paddle_gradient_machine_destroy(machine)); + return 0; } diff --git a/paddle/capi/examples/model_inference/multi_thread/main.c b/paddle/capi/examples/model_inference/multi_thread/main.c index 23f8629765d..ab0eb32c582 100644 --- a/paddle/capi/examples/model_inference/multi_thread/main.c +++ b/paddle/capi/examples/model_inference/multi_thread/main.c @@ -4,24 +4,24 @@ #include "../common/common.h" #define CONFIG_BIN "./trainer_config.bin" -#define NUM_THREAD 1000 +#define NUM_THREAD 4 #define NUM_ITER 1000 pthread_mutex_t mutex; void* thread_main(void* gm_ptr) { paddle_gradient_machine machine = (paddle_gradient_machine)(gm_ptr); - + paddle_arguments in_args = paddle_arguments_create_none(); + // Create input matrix. + paddle_matrix mat = paddle_matrix_create(/* sample_num */ 1, + /* size */ 784, + /* useGPU */ false); + paddle_arguments out_args = paddle_arguments_create_none(); + paddle_matrix prob = paddle_matrix_create_none(); for (int iter = 0; iter < NUM_ITER; ++iter) { - paddle_arguments in_args = paddle_arguments_create_none(); // There is only one input of this network. CHECK(paddle_arguments_resize(in_args, 1)); - // Create input matrix. - paddle_matrix mat = paddle_matrix_create(/* sample_num */ 1, - /* size */ 784, - /* useGPU */ false); - paddle_real* array; // Get First row. @@ -33,12 +33,10 @@ void* thread_main(void* gm_ptr) { CHECK(paddle_arguments_set_value(in_args, 0, mat)); - paddle_arguments out_args = paddle_arguments_create_none(); CHECK(paddle_gradient_machine_forward(machine, in_args, out_args, /* isTrain */ false)); - paddle_matrix prob = paddle_matrix_create_none(); CHECK(paddle_arguments_value(out_args, 0, prob)); @@ -53,6 +51,10 @@ void* thread_main(void* gm_ptr) { pthread_mutex_unlock(&mutex); } + CHECK(paddle_matrix_destroy(prob)); + CHECK(paddle_arguments_destroy(out_args)); + CHECK(paddle_matrix_destroy(mat)); + CHECK(paddle_arguments_destroy(in_args)); CHECK(paddle_gradient_machine_destroy(machine)); return NULL; } diff --git a/paddle/capi/examples/model_inference/sequence/main.c b/paddle/capi/examples/model_inference/sequence/main.c index 7e71bb8b8af..142793cdb3e 100644 --- a/paddle/capi/examples/model_inference/sequence/main.c +++ b/paddle/capi/examples/model_inference/sequence/main.c @@ -59,5 +59,12 @@ int main() { } printf("\n"); + CHECK(paddle_matrix_destroy(prob)); + CHECK(paddle_arguments_destroy(out_args)); + CHECK(paddle_ivector_destroy(seq_pos)); + CHECK(paddle_ivector_destroy(sentence)); + CHECK(paddle_arguments_destroy(in_args)); + CHECK(paddle_gradient_machine_destroy(machine)); + return 0; } diff --git a/paddle/capi/examples/model_inference/sparse_binary/main.c b/paddle/capi/examples/model_inference/sparse_binary/main.c index c5e653dbc28..776ad878911 100644 --- a/paddle/capi/examples/model_inference/sparse_binary/main.c +++ b/paddle/capi/examples/model_inference/sparse_binary/main.c @@ -60,5 +60,11 @@ int main() { } printf("\n"); + CHECK(paddle_matrix_destroy(prob)); + CHECK(paddle_arguments_destroy(out_args)); + CHECK(paddle_matrix_destroy(mat)); + CHECK(paddle_arguments_destroy(in_args)); + CHECK(paddle_gradient_machine_destroy(machine)); + return 0; } diff --git a/paddle/capi/tests/test_Init.cpp b/paddle/capi/tests/test_Init.cpp deleted file mode 100644 index e69de29bb2d..00000000000 -- GitLab From bda2008630ad22b0be6da96c9fcb53e520c0e70e Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Fri, 14 Apr 2017 14:41:46 +0800 Subject: [PATCH 0053/3256] Add TODO for GPU unittest --- paddle/capi/tests/test_Arguments.cpp | 1 + paddle/capi/tests/test_GradientMachine.cpp | 1 + paddle/capi/tests/test_Matrix.cpp | 1 + paddle/capi/tests/test_Vector.cpp | 1 + 4 files changed, 4 insertions(+) diff --git a/paddle/capi/tests/test_Arguments.cpp b/paddle/capi/tests/test_Arguments.cpp index f56391d51e3..012901a4916 100644 --- a/paddle/capi/tests/test_Arguments.cpp +++ b/paddle/capi/tests/test_Arguments.cpp @@ -29,6 +29,7 @@ static std::vector randomBuffer(size_t bufSize) { } TEST(CAPIArguments, create) { + //! TODO(yuyang18): Test GPU Code. paddle_arguments args = paddle_arguments_create_none(); uint64_t size; ASSERT_EQ(kPD_NO_ERROR, paddle_arguments_size(args, &size)); diff --git a/paddle/capi/tests/test_GradientMachine.cpp b/paddle/capi/tests/test_GradientMachine.cpp index be7dfadddc3..6c8d74c90b2 100644 --- a/paddle/capi/tests/test_GradientMachine.cpp +++ b/paddle/capi/tests/test_GradientMachine.cpp @@ -33,6 +33,7 @@ static std::vector randomBuffer(size_t bufSize) { } TEST(GradientMachine, testPredict) { + //! TODO(yuyang18): Test GPU Code. paddle::TrainerConfigHelper config("./test_predict_network.py"); std::string buffer; ASSERT_TRUE(config.getModelConfig().SerializeToString(&buffer)); diff --git a/paddle/capi/tests/test_Matrix.cpp b/paddle/capi/tests/test_Matrix.cpp index 71dc2064dd0..4bf9a9d6a9f 100644 --- a/paddle/capi/tests/test_Matrix.cpp +++ b/paddle/capi/tests/test_Matrix.cpp @@ -16,6 +16,7 @@ limitations under the License. */ #include "gtest/gtest.h" TEST(CAPIMatrix, create) { + //! TODO(yuyang18): Test GPU Code. paddle_matrix mat = paddle_matrix_create(128, 32, false); std::vector sampleRow; sampleRow.resize(32); diff --git a/paddle/capi/tests/test_Vector.cpp b/paddle/capi/tests/test_Vector.cpp index c5c57b7288d..365160dc9a0 100644 --- a/paddle/capi/tests/test_Vector.cpp +++ b/paddle/capi/tests/test_Vector.cpp @@ -16,6 +16,7 @@ limitations under the License. */ #include "gtest/gtest.h" TEST(CAPIVector, create) { + //! TODO(yuyang18): Test GPU Code. paddle_ivector vec; int array[3] = {1, 2, 3}; vec = paddle_ivector_create(array, 3, true, false); -- GitLab From c9ba6f0d0ed96aa2c1c80ac02b26e4c90bad966e Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Fri, 14 Apr 2017 15:54:28 +0800 Subject: [PATCH 0054/3256] Paddle need requests package to download file. --- python/setup.py.in | 3 +++ 1 file changed, 3 insertions(+) diff --git a/python/setup.py.in b/python/setup.py.in index 4ac35e3b8d6..228e762d56f 100644 --- a/python/setup.py.in +++ b/python/setup.py.in @@ -13,6 +13,9 @@ packages=['paddle', setup(name='paddle', version='${PADDLE_VERSION}', description='Parallel Distributed Deep Learning', + install_requires=[ + "requests", + ], packages=packages, package_dir={ '': '${CMAKE_CURRENT_SOURCE_DIR}' -- GitLab From bad503ff08e36f6af19b8e7203cf0ce3507bd80d Mon Sep 17 00:00:00 2001 From: qiaolongfei Date: Fri, 14 Apr 2017 23:33:29 +0800 Subject: [PATCH 0055/3256] support RemoteSparseUpdater --- paddle/api/PaddleAPI.h | 9 +++++---- paddle/api/ParameterUpdater.cpp | 15 ++++++++++++--- python/paddle/v2/optimizer.py | 4 ++-- python/paddle/v2/trainer.py | 3 ++- 4 files changed, 21 insertions(+), 10 deletions(-) diff --git a/paddle/api/PaddleAPI.h b/paddle/api/PaddleAPI.h index f5ead40682c..c8800519bd2 100644 --- a/paddle/api/PaddleAPI.h +++ b/paddle/api/PaddleAPI.h @@ -21,6 +21,7 @@ limitations under the License. */ #include #include "paddle/utils/Common.h" #include "paddle/utils/GlobalConstants.h" +#include "paddle/gserver/gradientmachines/GradientMachine.h" /// Import PaddlePaddle's enumeration into global namespace. using namespace paddle::enumeration_wrapper; // NOLINT @@ -468,9 +469,9 @@ private: }; enum GradientMatchineCreateMode { - CREATE_MODE_NORMAL = 0, - CREATE_MODE_SGD_SPARSE_CPU_TRAINING = 3, - CREATE_MODE_TESTING = 4 + CREATE_MODE_NORMAL = paddle::GradientMachine::kNormal, + CREATE_MODE_SGD_SPARSE_CPU_TRAINING = paddle::GradientMachine::kSgdSparseCpuTraining, + CREATE_MODE_TESTING = paddle::GradientMachine::kTesting }; struct ParameterConfigPrivate; @@ -818,7 +819,7 @@ private: public: static ParameterUpdater* createLocalUpdater(OptimizationConfig* config); static ParameterUpdater* createRemoteUpdater(OptimizationConfig* config, - int passCount); + int passCount, bool userSparseUpdater); ~ParameterUpdater(); /** diff --git a/paddle/api/ParameterUpdater.cpp b/paddle/api/ParameterUpdater.cpp index 75b0ae7cb6c..e96ccc92854 100644 --- a/paddle/api/ParameterUpdater.cpp +++ b/paddle/api/ParameterUpdater.cpp @@ -29,10 +29,19 @@ ParameterUpdater *ParameterUpdater::createLocalUpdater( } ParameterUpdater *ParameterUpdater::createRemoteUpdater( - OptimizationConfig *config, int passCount) { + OptimizationConfig *config, int passCount, bool userSparseUpdater) { auto updater = new ParameterUpdater(); - updater->m->updater.reset(new paddle::RemoteParameterUpdater( - config->m->getConfig(), passCount, nullptr)); + auto remoteUpdater = new paddle::RemoteParameterUpdater( + config->m->getConfig(), passCount, nullptr); + if (userSparseUpdater) { + std::unique_ptr remoteUpdaterPtr; + remoteUpdaterPtr.reset(remoteUpdater); + auto sparseRemoteUpdater = new paddle::SparseRemoteParameterUpdaterComposite( + config->m->getConfig(), passCount, false, std::move(remoteUpdaterPtr)); + updater->m->updater.reset(sparseRemoteUpdater); + } else { + updater->m->updater.reset(remoteUpdater); + } return updater; } diff --git a/python/paddle/v2/optimizer.py b/python/paddle/v2/optimizer.py index 1a01d95c205..6fefd7b2f24 100644 --- a/python/paddle/v2/optimizer.py +++ b/python/paddle/v2/optimizer.py @@ -41,9 +41,9 @@ class Optimizer(object): def create_local_updater(self): return swig_api.ParameterUpdater.createLocalUpdater(self.__opt_conf__) - def create_remote_updater(self, pass_num): + def create_remote_updater(self, pass_num, use_sparse_updater): return swig_api.ParameterUpdater.createRemoteUpdater(self.__opt_conf__, - pass_num) + pass_num, use_sparse_updater) class Momentum(Optimizer): diff --git a/python/paddle/v2/trainer.py b/python/paddle/v2/trainer.py index 2dac95b63d5..dc23eb5b0d7 100644 --- a/python/paddle/v2/trainer.py +++ b/python/paddle/v2/trainer.py @@ -97,7 +97,8 @@ class SGD(object): if self.__is_local__: updater = self.__optimizer__.create_local_updater() else: - updater = self.__optimizer__.create_remote_updater(num_passes) + updater = self.__optimizer__.create_remote_updater(num_passes, + self.__use_sparse_updater__) updater.init(self.__gradient_machine__) self.__gradient_machine__.start() -- GitLab From 64bfd8147fc574466f7b5972de926ed0cec00f66 Mon Sep 17 00:00:00 2001 From: qiaolongfei Date: Sat, 15 Apr 2017 17:13:34 +0800 Subject: [PATCH 0056/3256] fix style probelm --- paddle/api/PaddleAPI.h | 8 +++++--- paddle/api/ParameterUpdater.cpp | 10 +++++++--- paddle/function/BufferArgTest.cpp | 2 +- paddle/function/FunctionTest.cpp | 2 +- paddle/function/TensorShapeTest.cpp | 2 +- paddle/function/TensorTypeTest.cpp | 2 +- python/paddle/v2/optimizer.py | 4 ++-- python/paddle/v2/trainer.py | 11 ++++++++--- 8 files changed, 26 insertions(+), 15 deletions(-) diff --git a/paddle/api/PaddleAPI.h b/paddle/api/PaddleAPI.h index c8800519bd2..725328ce4d2 100644 --- a/paddle/api/PaddleAPI.h +++ b/paddle/api/PaddleAPI.h @@ -19,9 +19,9 @@ limitations under the License. */ #include #include #include +#include "paddle/gserver/gradientmachines/GradientMachine.h" #include "paddle/utils/Common.h" #include "paddle/utils/GlobalConstants.h" -#include "paddle/gserver/gradientmachines/GradientMachine.h" /// Import PaddlePaddle's enumeration into global namespace. using namespace paddle::enumeration_wrapper; // NOLINT @@ -470,7 +470,8 @@ private: enum GradientMatchineCreateMode { CREATE_MODE_NORMAL = paddle::GradientMachine::kNormal, - CREATE_MODE_SGD_SPARSE_CPU_TRAINING = paddle::GradientMachine::kSgdSparseCpuTraining, + CREATE_MODE_SGD_SPARSE_CPU_TRAINING = + paddle::GradientMachine::kSgdSparseCpuTraining, CREATE_MODE_TESTING = paddle::GradientMachine::kTesting }; @@ -819,7 +820,8 @@ private: public: static ParameterUpdater* createLocalUpdater(OptimizationConfig* config); static ParameterUpdater* createRemoteUpdater(OptimizationConfig* config, - int passCount, bool userSparseUpdater); + int passCount, + bool userSparseUpdater); ~ParameterUpdater(); /** diff --git a/paddle/api/ParameterUpdater.cpp b/paddle/api/ParameterUpdater.cpp index e96ccc92854..708379ded5b 100644 --- a/paddle/api/ParameterUpdater.cpp +++ b/paddle/api/ParameterUpdater.cpp @@ -32,12 +32,16 @@ ParameterUpdater *ParameterUpdater::createRemoteUpdater( OptimizationConfig *config, int passCount, bool userSparseUpdater) { auto updater = new ParameterUpdater(); auto remoteUpdater = new paddle::RemoteParameterUpdater( - config->m->getConfig(), passCount, nullptr); + config->m->getConfig(), passCount, nullptr); if (userSparseUpdater) { std::unique_ptr remoteUpdaterPtr; remoteUpdaterPtr.reset(remoteUpdater); - auto sparseRemoteUpdater = new paddle::SparseRemoteParameterUpdaterComposite( - config->m->getConfig(), passCount, false, std::move(remoteUpdaterPtr)); + auto sparseRemoteUpdater = + new paddle::SparseRemoteParameterUpdaterComposite( + config->m->getConfig(), + passCount, + false, + std::move(remoteUpdaterPtr)); updater->m->updater.reset(sparseRemoteUpdater); } else { updater->m->updater.reset(remoteUpdater); diff --git a/paddle/function/BufferArgTest.cpp b/paddle/function/BufferArgTest.cpp index 1744f377808..f1a234ab1a1 100644 --- a/paddle/function/BufferArgTest.cpp +++ b/paddle/function/BufferArgTest.cpp @@ -12,8 +12,8 @@ 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 "BufferArg.h" #include +#include "BufferArg.h" #include "paddle/math/MemoryHandle.h" namespace paddle { diff --git a/paddle/function/FunctionTest.cpp b/paddle/function/FunctionTest.cpp index fdf7e631e5a..f9ea7c7e4f6 100644 --- a/paddle/function/FunctionTest.cpp +++ b/paddle/function/FunctionTest.cpp @@ -12,8 +12,8 @@ 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 "Function.h" #include +#include "Function.h" #include "paddle/math/SparseMatrix.h" namespace paddle { diff --git a/paddle/function/TensorShapeTest.cpp b/paddle/function/TensorShapeTest.cpp index 45a2e106e7f..e19afe0c4d5 100644 --- a/paddle/function/TensorShapeTest.cpp +++ b/paddle/function/TensorShapeTest.cpp @@ -12,8 +12,8 @@ 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 "TensorShape.h" #include +#include "TensorShape.h" namespace paddle { diff --git a/paddle/function/TensorTypeTest.cpp b/paddle/function/TensorTypeTest.cpp index e50e46f3e99..5b5c504ae2a 100644 --- a/paddle/function/TensorTypeTest.cpp +++ b/paddle/function/TensorTypeTest.cpp @@ -12,8 +12,8 @@ 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 "TensorType.h" #include +#include "TensorType.h" namespace paddle { diff --git a/python/paddle/v2/optimizer.py b/python/paddle/v2/optimizer.py index 6fefd7b2f24..7bac1ea3b98 100644 --- a/python/paddle/v2/optimizer.py +++ b/python/paddle/v2/optimizer.py @@ -42,8 +42,8 @@ class Optimizer(object): return swig_api.ParameterUpdater.createLocalUpdater(self.__opt_conf__) def create_remote_updater(self, pass_num, use_sparse_updater): - return swig_api.ParameterUpdater.createRemoteUpdater(self.__opt_conf__, - pass_num, use_sparse_updater) + return swig_api.ParameterUpdater.createRemoteUpdater( + self.__opt_conf__, pass_num, use_sparse_updater) class Momentum(Optimizer): diff --git a/python/paddle/v2/trainer.py b/python/paddle/v2/trainer.py index dc23eb5b0d7..80f243b4137 100644 --- a/python/paddle/v2/trainer.py +++ b/python/paddle/v2/trainer.py @@ -42,7 +42,12 @@ class SGD(object): :type extra_layers: paddle.v2.config_base.Layer """ - def __init__(self, cost, parameters, update_equation, extra_layers=None, is_local=True): + def __init__(self, + cost, + parameters, + update_equation, + extra_layers=None, + is_local=True): if not isinstance(parameters, v2_parameters.Parameters): raise TypeError('parameters should be parameters') @@ -97,8 +102,8 @@ class SGD(object): if self.__is_local__: updater = self.__optimizer__.create_local_updater() else: - updater = self.__optimizer__.create_remote_updater(num_passes, - self.__use_sparse_updater__) + updater = self.__optimizer__.create_remote_updater( + num_passes, self.__use_sparse_updater__) updater.init(self.__gradient_machine__) self.__gradient_machine__.start() -- GitLab From 8210350819cbaa83e8ac0a541d14d0819d9a1e34 Mon Sep 17 00:00:00 2001 From: qiaolongfei Date: Sat, 15 Apr 2017 23:39:47 +0800 Subject: [PATCH 0057/3256] add getParametersRemote for ParameterUpdater in api --- paddle/api/PaddleAPI.h | 7 +++++++ paddle/api/ParameterUpdater.cpp | 4 ++++ python/paddle/v2/topology.py | 6 ++++-- python/paddle/v2/trainer.py | 21 ++++++++++----------- 4 files changed, 25 insertions(+), 13 deletions(-) diff --git a/paddle/api/PaddleAPI.h b/paddle/api/PaddleAPI.h index 725328ce4d2..be6be556a73 100644 --- a/paddle/api/PaddleAPI.h +++ b/paddle/api/PaddleAPI.h @@ -859,6 +859,13 @@ public: */ void update(Parameter* param); + /** + * @breif only get required sparse rows by default. + * @param fullSize: get full matrix parameter if *fullSize* set + * @param apply: get PARAMETER_APPLY on pserver if *apply* set + */ + void getParametersRemote(bool fullSize = false, bool apply = false); + /** * @brief restore the average parameter. * @note It is only used in AverageOptimizer. Restore will get the current diff --git a/paddle/api/ParameterUpdater.cpp b/paddle/api/ParameterUpdater.cpp index 708379ded5b..ce2ac33d449 100644 --- a/paddle/api/ParameterUpdater.cpp +++ b/paddle/api/ParameterUpdater.cpp @@ -72,6 +72,10 @@ void ParameterUpdater::update(Parameter *param) { m->updater->update(paddleParam); } +void ParameterUpdater::getParametersRemote(bool fullSize, bool apply) { + m->updater->getParametersRemote(fullSize, apply); +} + void ParameterUpdater::restore() { m->updater->restore(); } void ParameterUpdater::apply() { m->updater->apply(); } diff --git a/python/paddle/v2/topology.py b/python/paddle/v2/topology.py index 86e7549e972..ff28c85c53d 100644 --- a/python/paddle/v2/topology.py +++ b/python/paddle/v2/topology.py @@ -78,10 +78,12 @@ class Topology(object): check if any parameter require to use sparse_update :return: """ + use_sparse = False for parameter in self.__model_config__.parameters: if parameter.sparse_update or parameter.sparse_remote_update: - return True - return False + use_sparse = True + break + return use_sparse def proto(self): return self.__model_config__ diff --git a/python/paddle/v2/trainer.py b/python/paddle/v2/trainer.py index 80f243b4137..30fc2a0886b 100644 --- a/python/paddle/v2/trainer.py +++ b/python/paddle/v2/trainer.py @@ -65,7 +65,6 @@ class SGD(object): self.__use_sparse_updater__ = self.__topology__.use_sparse_updater() # # In local mode, disable sparse_remote_update. if is_local: - self.__use_sparse_updater__ = False for param in self.__topology_in_proto__.parameters: if param.sparse_remote_update: param.sparse_remote_update = False @@ -100,11 +99,11 @@ class SGD(object): __check_train_args__(**locals()) if self.__is_local__: - updater = self.__optimizer__.create_local_updater() + parameter_updater = self.__optimizer__.create_local_updater() else: - updater = self.__optimizer__.create_remote_updater( + parameter_updater = self.__optimizer__.create_remote_updater( num_passes, self.__use_sparse_updater__) - updater.init(self.__gradient_machine__) + parameter_updater.init(self.__gradient_machine__) self.__gradient_machine__.start() batch_evaluator = self.__gradient_machine__.makeEvaluator() @@ -116,26 +115,26 @@ class SGD(object): for pass_id in xrange(num_passes): event_handler(v2_event.BeginPass(pass_id)) pass_evaluator.start() - updater.startPass() + parameter_updater.startPass() for batch_id, data_batch in enumerate(reader()): batch_evaluator.start() event_handler( v2_event.BeginIteration( pass_id=pass_id, batch_id=batch_id)) - pass_type = updater.startBatch(len(data_batch)) - if self.__use_sparse_updater__: + pass_type = parameter_updater.startBatch(len(data_batch)) + if self.__use_sparse_updater__ and not self.__is_local__: self.__gradient_machine__.prefetch(feeder(data_batch)) - updater.getParametersRemote() + parameter_updater.getParametersRemote() self.__gradient_machine__.forwardBackward( feeder(data_batch), out_args, pass_type) self.__gradient_machine__.eval(pass_evaluator) self.__gradient_machine__.eval(batch_evaluator) for each_param in self.__gradient_machine__.getNonStaticParameters( ): - updater.update(each_param) + parameter_updater.update(each_param) cost_sum = out_args.sum() cost = cost_sum / len(data_batch) - updater.finishBatch(cost) + parameter_updater.finishBatch(cost) batch_evaluator.finish() event_handler( v2_event.EndIteration( @@ -144,7 +143,7 @@ class SGD(object): cost=cost, evaluator=batch_evaluator)) - updater.finishPass() + parameter_updater.finishPass() pass_evaluator.finish() event_handler(v2_event.EndPass(pass_id, evaluator=pass_evaluator)) self.__gradient_machine__.finish() -- GitLab From f6c5b6fd4602c84882da86c32111391d70dfd8bd Mon Sep 17 00:00:00 2001 From: qiaolongfei Date: Sun, 16 Apr 2017 15:56:09 +0800 Subject: [PATCH 0058/3256] add prefetch for trainer.test --- python/paddle/v2/trainer.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/python/paddle/v2/trainer.py b/python/paddle/v2/trainer.py index 30fc2a0886b..c1f964a8106 100644 --- a/python/paddle/v2/trainer.py +++ b/python/paddle/v2/trainer.py @@ -79,6 +79,10 @@ class SGD(object): self.__gradient_machine__ = gm self.__gradient_machine__.randParameters() parameters.append_gradient_machine(gm) + self.__parameter_updater__ = None + + def use_remote_sparse_updater(self): + return self.__use_sparse_updater__ and not self.__is_local__ def train(self, reader, num_passes=1, event_handler=None, feeding=None): """ @@ -103,6 +107,7 @@ class SGD(object): else: parameter_updater = self.__optimizer__.create_remote_updater( num_passes, self.__use_sparse_updater__) + self.__parameter_updater__ = parameter_updater parameter_updater.init(self.__gradient_machine__) self.__gradient_machine__.start() @@ -122,11 +127,12 @@ class SGD(object): v2_event.BeginIteration( pass_id=pass_id, batch_id=batch_id)) pass_type = parameter_updater.startBatch(len(data_batch)) - if self.__use_sparse_updater__ and not self.__is_local__: - self.__gradient_machine__.prefetch(feeder(data_batch)) + in_args = feeder(data_batch) + if self.use_remote_sparse_updater(): + self.__gradient_machine__.prefetch(in_args) parameter_updater.getParametersRemote() - self.__gradient_machine__.forwardBackward( - feeder(data_batch), out_args, pass_type) + self.__gradient_machine__.forwardBackward(in_args, out_args, + pass_type) self.__gradient_machine__.eval(pass_evaluator) self.__gradient_machine__.eval(batch_evaluator) for each_param in self.__gradient_machine__.getNonStaticParameters( @@ -157,8 +163,11 @@ class SGD(object): num_samples = 0.0 for data_batch in reader(): num_samples += len(data_batch) - self.__gradient_machine__.forward( - feeder(data_batch), out_args, api.PASS_TEST) + in_args = feeder(data_batch) + if self.use_remote_sparse_updater(): + self.__gradient_machine__.prefetch(in_args) + self.__parameter_updater__.getParametersRemote() + self.__gradient_machine__.forward(in_args, out_args, api.PASS_TEST) total_cost += out_args.sum() self.__gradient_machine__.eval(evaluator) -- GitLab From ea25eef375c33d01e2f28f875dbcb5650596466d Mon Sep 17 00:00:00 2001 From: qiaolongfei Date: Sun, 16 Apr 2017 16:16:17 +0800 Subject: [PATCH 0059/3256] word2vec demo support sparse remote update --- .../word2vec/{train_v2.py => api_train_v2.py} | 28 +++++++++++++++---- paddle/api/PaddleAPI.h | 2 +- paddle/api/ParameterUpdater.cpp | 4 +-- 3 files changed, 25 insertions(+), 9 deletions(-) rename demo/word2vec/{train_v2.py => api_train_v2.py} (80%) diff --git a/demo/word2vec/train_v2.py b/demo/word2vec/api_train_v2.py similarity index 80% rename from demo/word2vec/train_v2.py rename to demo/word2vec/api_train_v2.py index 7d952b446f9..eb61a7250fb 100644 --- a/demo/word2vec/train_v2.py +++ b/demo/word2vec/api_train_v2.py @@ -2,26 +2,38 @@ import math import paddle.v2 as paddle -dictsize = 1953 embsize = 32 hiddensize = 256 N = 5 def wordemb(inlayer): - wordemb = paddle.layer.table_projection( + wordemb = paddle.layer.embedding( input=inlayer, size=embsize, param_attr=paddle.attr.Param( name="_proj", initial_std=0.001, learning_rate=1, - l2_rate=0, )) + l2_rate=0, + sparse_update=True)) return wordemb def main(): - paddle.init(use_gpu=False, trainer_count=1) + # for local training + cluster_train = False + + if not cluster_train: + paddle.init(use_gpu=False, trainer_count=1) + else: + paddle.init( + use_gpu=False, + trainer_count=1, + port=7164, + ports_num=1, + ports_num_for_sparse=1, + num_gradient_servers=1) word_dict = paddle.dataset.imikolov.build_dict() dict_size = len(word_dict) firstword = paddle.layer.data( @@ -65,11 +77,15 @@ def main(): result.metrics) cost = paddle.layer.classification_cost(input=predictword, label=nextword) + parameters = paddle.parameters.create(cost) - adam_optimizer = paddle.optimizer.Adam( + adagrad = paddle.optimizer.AdaGrad( learning_rate=3e-3, regularization=paddle.optimizer.L2Regularization(8e-4)) - trainer = paddle.trainer.SGD(cost, parameters, adam_optimizer) + trainer = paddle.trainer.SGD(cost, + parameters, + adagrad, + is_local=not cluster_train) trainer.train( paddle.batch(paddle.dataset.imikolov.train(word_dict, N), 32), num_passes=30, diff --git a/paddle/api/PaddleAPI.h b/paddle/api/PaddleAPI.h index be6be556a73..d5120401217 100644 --- a/paddle/api/PaddleAPI.h +++ b/paddle/api/PaddleAPI.h @@ -821,7 +821,7 @@ public: static ParameterUpdater* createLocalUpdater(OptimizationConfig* config); static ParameterUpdater* createRemoteUpdater(OptimizationConfig* config, int passCount, - bool userSparseUpdater); + bool useSparseUpdater); ~ParameterUpdater(); /** diff --git a/paddle/api/ParameterUpdater.cpp b/paddle/api/ParameterUpdater.cpp index ce2ac33d449..9dfd12ccbe7 100644 --- a/paddle/api/ParameterUpdater.cpp +++ b/paddle/api/ParameterUpdater.cpp @@ -29,11 +29,11 @@ ParameterUpdater *ParameterUpdater::createLocalUpdater( } ParameterUpdater *ParameterUpdater::createRemoteUpdater( - OptimizationConfig *config, int passCount, bool userSparseUpdater) { + OptimizationConfig *config, int passCount, bool useSparseUpdater) { auto updater = new ParameterUpdater(); auto remoteUpdater = new paddle::RemoteParameterUpdater( config->m->getConfig(), passCount, nullptr); - if (userSparseUpdater) { + if (useSparseUpdater) { std::unique_ptr remoteUpdaterPtr; remoteUpdaterPtr.reset(remoteUpdater); auto sparseRemoteUpdater = -- GitLab From 6295f2d6db2ed81bc7a1ade9992083a1ad42640a Mon Sep 17 00:00:00 2001 From: qiaolongfei Date: Sun, 16 Apr 2017 20:23:16 +0800 Subject: [PATCH 0060/3256] fix style problem --- paddle/function/BufferArgTest.cpp | 2 +- paddle/function/FunctionTest.cpp | 2 +- paddle/function/TensorShapeTest.cpp | 2 +- paddle/function/TensorTypeTest.cpp | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/paddle/function/BufferArgTest.cpp b/paddle/function/BufferArgTest.cpp index f1a234ab1a1..1744f377808 100644 --- a/paddle/function/BufferArgTest.cpp +++ b/paddle/function/BufferArgTest.cpp @@ -12,8 +12,8 @@ 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 "BufferArg.h" +#include #include "paddle/math/MemoryHandle.h" namespace paddle { diff --git a/paddle/function/FunctionTest.cpp b/paddle/function/FunctionTest.cpp index f9ea7c7e4f6..fdf7e631e5a 100644 --- a/paddle/function/FunctionTest.cpp +++ b/paddle/function/FunctionTest.cpp @@ -12,8 +12,8 @@ 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 "Function.h" +#include #include "paddle/math/SparseMatrix.h" namespace paddle { diff --git a/paddle/function/TensorShapeTest.cpp b/paddle/function/TensorShapeTest.cpp index e19afe0c4d5..45a2e106e7f 100644 --- a/paddle/function/TensorShapeTest.cpp +++ b/paddle/function/TensorShapeTest.cpp @@ -12,8 +12,8 @@ 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 "TensorShape.h" +#include namespace paddle { diff --git a/paddle/function/TensorTypeTest.cpp b/paddle/function/TensorTypeTest.cpp index 5b5c504ae2a..e50e46f3e99 100644 --- a/paddle/function/TensorTypeTest.cpp +++ b/paddle/function/TensorTypeTest.cpp @@ -12,8 +12,8 @@ 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 "TensorType.h" +#include namespace paddle { -- GitLab From cfff9467ac4df2731c3aadaf4060f93c49a885e8 Mon Sep 17 00:00:00 2001 From: qiaolongfei Date: Mon, 17 Apr 2017 10:08:45 +0800 Subject: [PATCH 0061/3256] optimizer parameter_updater --- python/paddle/v2/optimizer.py | 8 ++++++++ python/paddle/v2/trainer.py | 23 ++++++++++------------- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/python/paddle/v2/optimizer.py b/python/paddle/v2/optimizer.py index 7bac1ea3b98..887b2567a14 100644 --- a/python/paddle/v2/optimizer.py +++ b/python/paddle/v2/optimizer.py @@ -45,6 +45,14 @@ class Optimizer(object): return swig_api.ParameterUpdater.createRemoteUpdater( self.__opt_conf__, pass_num, use_sparse_updater) + def create_updater(self, is_local, num_passes, use_sparse_updater): + if is_local: + parameter_updater = self.create_local_updater() + else: + parameter_updater = self.create_remote_updater(num_passes, + use_sparse_updater) + return parameter_updater + class Momentum(Optimizer): def __init__(self, momentum=None, sparse=False, **kwargs): diff --git a/python/paddle/v2/trainer.py b/python/paddle/v2/trainer.py index c1f964a8106..9caaeca2efe 100644 --- a/python/paddle/v2/trainer.py +++ b/python/paddle/v2/trainer.py @@ -102,13 +102,9 @@ class SGD(object): event_handler = default_event_handler __check_train_args__(**locals()) - if self.__is_local__: - parameter_updater = self.__optimizer__.create_local_updater() - else: - parameter_updater = self.__optimizer__.create_remote_updater( - num_passes, self.__use_sparse_updater__) - self.__parameter_updater__ = parameter_updater - parameter_updater.init(self.__gradient_machine__) + self.__parameter_updater__ = self.__optimizer__.create_updater( + self.__is_local__, num_passes, self.__use_sparse_updater__) + self.__parameter_updater__.init(self.__gradient_machine__) self.__gradient_machine__.start() batch_evaluator = self.__gradient_machine__.makeEvaluator() @@ -120,27 +116,28 @@ class SGD(object): for pass_id in xrange(num_passes): event_handler(v2_event.BeginPass(pass_id)) pass_evaluator.start() - parameter_updater.startPass() + self.__parameter_updater__.startPass() for batch_id, data_batch in enumerate(reader()): batch_evaluator.start() event_handler( v2_event.BeginIteration( pass_id=pass_id, batch_id=batch_id)) - pass_type = parameter_updater.startBatch(len(data_batch)) + pass_type = self.__parameter_updater__.startBatch( + len(data_batch)) in_args = feeder(data_batch) if self.use_remote_sparse_updater(): self.__gradient_machine__.prefetch(in_args) - parameter_updater.getParametersRemote() + self.__parameter_updater__.getParametersRemote() self.__gradient_machine__.forwardBackward(in_args, out_args, pass_type) self.__gradient_machine__.eval(pass_evaluator) self.__gradient_machine__.eval(batch_evaluator) for each_param in self.__gradient_machine__.getNonStaticParameters( ): - parameter_updater.update(each_param) + self.__parameter_updater__.update(each_param) cost_sum = out_args.sum() cost = cost_sum / len(data_batch) - parameter_updater.finishBatch(cost) + self.__parameter_updater__.finishBatch(cost) batch_evaluator.finish() event_handler( v2_event.EndIteration( @@ -149,7 +146,7 @@ class SGD(object): cost=cost, evaluator=batch_evaluator)) - parameter_updater.finishPass() + self.__parameter_updater__.finishPass() pass_evaluator.finish() event_handler(v2_event.EndPass(pass_id, evaluator=pass_evaluator)) self.__gradient_machine__.finish() -- GitLab From 2c2947f80e1b1d3aee6b06acfe993069db021c8b Mon Sep 17 00:00:00 2001 From: dangqingqing Date: Tue, 18 Apr 2017 20:26:34 +0800 Subject: [PATCH 0062/3256] Fix a bug for TransLayer and add unit testing. --- paddle/gserver/layers/TransLayer.cpp | 9 ++++++++- paddle/gserver/tests/test_LayerGrad.cpp | 16 ++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/paddle/gserver/layers/TransLayer.cpp b/paddle/gserver/layers/TransLayer.cpp index d1fa90f3841..4150f1727d8 100644 --- a/paddle/gserver/layers/TransLayer.cpp +++ b/paddle/gserver/layers/TransLayer.cpp @@ -56,7 +56,14 @@ void TransLayer::backward(const UpdateCallback& callback) { return; } MatrixPtr preGrad = getInputGrad(0); - outputGrad->transpose(preGrad, false); + if (preGrad) { + MatrixPtr transGrad = Matrix::create(preGrad->getHeight(), + preGrad->getWidth(), + /* trans= */ false, + preGrad->useGpu()); + outputGrad->transpose(transGrad, false); + preGrad->add(*transGrad); + } } } // namespace paddle diff --git a/paddle/gserver/tests/test_LayerGrad.cpp b/paddle/gserver/tests/test_LayerGrad.cpp index 193b876c316..0d7bd8c3b85 100644 --- a/paddle/gserver/tests/test_LayerGrad.cpp +++ b/paddle/gserver/tests/test_LayerGrad.cpp @@ -1689,6 +1689,22 @@ TEST(Layer, smooth_l1) { } } +TEST(Layer, TransLayer) { + TestConfig config; + const int height = 128; + const int width = 1028; + config.layerConfig.set_type("trans"); + config.layerConfig.set_size(width); + + config.inputDefs.push_back( + {INPUT_DATA, "layer_0", /* dim= */ height * width, /* paraSize= */ 0}); + config.layerConfig.add_inputs(); + + for (auto useGpu : {false, true}) { + testLayerGrad(config, "trans", height, /* trans= */ false, useGpu); + } +} + int main(int argc, char** argv) { testing::InitGoogleTest(&argc, argv); initMain(argc, argv); -- GitLab From cf86ca04b4682c0f1ecf24324ed3dcc7769cea63 Mon Sep 17 00:00:00 2001 From: qiaolongfei Date: Wed, 19 Apr 2017 10:23:26 +0800 Subject: [PATCH 0063/3256] refine code --- paddle/api/ParameterUpdater.cpp | 3 +-- python/paddle/v2/optimizer.py | 25 ++++++++++++++++++++----- python/paddle/v2/trainer.py | 24 ++++++++++++++++-------- 3 files changed, 37 insertions(+), 15 deletions(-) diff --git a/paddle/api/ParameterUpdater.cpp b/paddle/api/ParameterUpdater.cpp index 9dfd12ccbe7..79921ea6e78 100644 --- a/paddle/api/ParameterUpdater.cpp +++ b/paddle/api/ParameterUpdater.cpp @@ -34,8 +34,7 @@ ParameterUpdater *ParameterUpdater::createRemoteUpdater( auto remoteUpdater = new paddle::RemoteParameterUpdater( config->m->getConfig(), passCount, nullptr); if (useSparseUpdater) { - std::unique_ptr remoteUpdaterPtr; - remoteUpdaterPtr.reset(remoteUpdater); + std::unique_ptr remoteUpdaterPtr(remoteUpdater); auto sparseRemoteUpdater = new paddle::SparseRemoteParameterUpdaterComposite( config->m->getConfig(), diff --git a/python/paddle/v2/optimizer.py b/python/paddle/v2/optimizer.py index 887b2567a14..17c56a2b993 100644 --- a/python/paddle/v2/optimizer.py +++ b/python/paddle/v2/optimizer.py @@ -38,19 +38,34 @@ class Optimizer(object): assert isinstance(tmp, swig_api.ParameterOptimizer) return tmp.getParameterTypes() - def create_local_updater(self): + def __create_local_updater__(self): return swig_api.ParameterUpdater.createLocalUpdater(self.__opt_conf__) - def create_remote_updater(self, pass_num, use_sparse_updater): + def __create_remote_updater__(self, pass_num, use_sparse_updater): return swig_api.ParameterUpdater.createRemoteUpdater( self.__opt_conf__, pass_num, use_sparse_updater) def create_updater(self, is_local, num_passes, use_sparse_updater): + """ + create proper parameter_updater by configuration. + :param is_local: create local or remote parameter updater + :param num_passes: remote parameter updater will use this to config + parameter server. + :param use_sparse_updater: when use remote updater, if some parameter is + sparse, updater should do some extra thing: + + .. code-block:: python + + if use_sparse_remote_updater: + gradient_machine.prefetch(in_args) + parameter_updater.getParametersRemote() + :return: parameter_updater + """ if is_local: - parameter_updater = self.create_local_updater() + parameter_updater = self.__create_local_updater__() else: - parameter_updater = self.create_remote_updater(num_passes, - use_sparse_updater) + parameter_updater = self.__create_remote_updater__( + num_passes, use_sparse_updater) return parameter_updater diff --git a/python/paddle/v2/trainer.py b/python/paddle/v2/trainer.py index 9caaeca2efe..552c6690a60 100644 --- a/python/paddle/v2/trainer.py +++ b/python/paddle/v2/trainer.py @@ -78,12 +78,24 @@ class SGD(object): assert isinstance(gm, api.GradientMachine) self.__gradient_machine__ = gm self.__gradient_machine__.randParameters() - parameters.append_gradient_machine(gm) + self.__parameters__.append_gradient_machine(gm) self.__parameter_updater__ = None - def use_remote_sparse_updater(self): + def __use_remote_sparse_updater__(self): return self.__use_sparse_updater__ and not self.__is_local__ + def __prepare_parameter__(self, in_args): + """ + prepare parameter before forward backward. + 1. When use remote sparse updater, parameters should be got + from ps according to input arguments. + :param in_args: input arguments of this batch. + :return: + """ + if self.__use_remote_sparse_updater__(): + self.__gradient_machine__.prefetch(in_args) + self.__parameter_updater__.getParametersRemote() + def train(self, reader, num_passes=1, event_handler=None, feeding=None): """ Training method. Will train num_passes of input data. @@ -125,9 +137,7 @@ class SGD(object): pass_type = self.__parameter_updater__.startBatch( len(data_batch)) in_args = feeder(data_batch) - if self.use_remote_sparse_updater(): - self.__gradient_machine__.prefetch(in_args) - self.__parameter_updater__.getParametersRemote() + self.__prepare_parameter__(in_args) self.__gradient_machine__.forwardBackward(in_args, out_args, pass_type) self.__gradient_machine__.eval(pass_evaluator) @@ -161,9 +171,7 @@ class SGD(object): for data_batch in reader(): num_samples += len(data_batch) in_args = feeder(data_batch) - if self.use_remote_sparse_updater(): - self.__gradient_machine__.prefetch(in_args) - self.__parameter_updater__.getParametersRemote() + self.__prepare_parameter__(in_args) self.__gradient_machine__.forward(in_args, out_args, api.PASS_TEST) total_cost += out_args.sum() self.__gradient_machine__.eval(evaluator) -- GitLab From 81c6211c52bcceef45774e2c715cb97716e38faa Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Wed, 19 Apr 2017 11:10:59 +0800 Subject: [PATCH 0064/3256] Releasing Paddle Standard. --- .../01.how_to_release_paddle.md | 22 +++++++++++++++++++ .../02.paddle_branching_model.md | 16 ++++++++++++++ .../03.regression_test_list.md | 18 +++++++++++++++ 3 files changed, 56 insertions(+) create mode 100644 doc/design/releasing_process/01.how_to_release_paddle.md create mode 100644 doc/design/releasing_process/02.paddle_branching_model.md create mode 100644 doc/design/releasing_process/03.regression_test_list.md diff --git a/doc/design/releasing_process/01.how_to_release_paddle.md b/doc/design/releasing_process/01.how_to_release_paddle.md new file mode 100644 index 00000000000..bf11f94c9df --- /dev/null +++ b/doc/design/releasing_process/01.how_to_release_paddle.md @@ -0,0 +1,22 @@ +# Paddle发行规范 + +Paddle使用[git-flow](./02.paddle_branching_model.md) branching model做分支管理,使用[Semantic Versioning](http://semver.org/)标准表示Paddle版本号。 + +Paddle每次发新的版本,遵循以下流程: + +1. 从`develop`分支派生出新的分支,分支名为`版本号rc`。例如,`0.10.0rc` +2. 将新分支的版本打上tag,tag为`版本号rc.Patch号`。第一个tag为`0.10.0rc0`,第二个为`0.10.0rc1`,依次类推。 +3. 对这个版本的提交,做如下几个操作: + * 编译这个版本的Docker发行镜像,发布到dockerhub。如果失败,Patch号加一,返回第二步 + * 编译这个版本的Ubuntu Deb包。如果失败,Patch号加一,返回第二步。 + * 使用[Regression Test List](./03.regression_test_list.md)作为检查列表,测试Docker镜像/ubuntu安装包的功能正确性 + * 如果失败,记录下所有失败的例子,在这个`rc`分支中,修复所有bug后,Patch号加一,返回第二步 +4. 第三步完成后,将`rc`分支合入master分支,并删除`rc`分支。将master分支的合入commit打上tag,tag为`版本号`。同时再将`master`分支合入`develop`分支。最后删除`rc`分支。 +5. 编译master分支的Docker发行镜像,发布到dockerhub。编译ubuntu的deb包,发布到github release页面 +6. 协同完成Release Note的书写 + + +需要注意的是: + +* `rc`分支一旦建立,一般不允许再从`develop`分支合入`rc`。这样保证`rc`分支功能的封闭,方便测试人员测试Paddle的行为。 +* 在`rc`分支存在的时候,如果有bugfix的行为,需要将bugfix的分支同时merge到`master`, `develop`和`rc`这三个分支。 diff --git a/doc/design/releasing_process/02.paddle_branching_model.md b/doc/design/releasing_process/02.paddle_branching_model.md new file mode 100644 index 00000000000..562f0a07576 --- /dev/null +++ b/doc/design/releasing_process/02.paddle_branching_model.md @@ -0,0 +1,16 @@ +# Paddle 分支规范 + +Paddle开发过程使用[git-flow](http://nvie.com/posts/a-successful-git-branching-model/)分支规范,并适应github的特性做了一些区别。 + +* Paddle的主版本库遵循[git-flow](http://nvie.com/posts/a-successful-git-branching-model/)分支规范。其中: + * `master`分支为稳定(stable branch)版本分支。每一个`master`分支的版本都是经过单元测试和回归测试的版本。 + * `develop`分支为开发(develop branch)版本分支。每一个`develop`分支的版本都经过单元测试,但并没有经过回归测试。 + * `rc`分支为每一次Release时建立的临时分支。在这个阶段的代码正在经历回归测试。 + +* 其他用户的fork版本库并不需要严格遵守[git-flow](http://nvie.com/posts/a-successful-git-branching-model/)分支规范,但所有fork的版本库的所有分支都相当于特性分支。 + * 建议,开发者fork的版本库使用`develop`分支同步主版本库的`develop`分支 + * 建议,开发者fork的版本库中,再基于`develop`版本fork出自己的功能分支。 + * 当功能分支开发完毕后,向Paddle的主版本库提交`Pull Reuqest`,进而进行代码评审。 + * 在评审过程中,开发者修改自己的代码,可以继续在自己的功能分支提交代码。 + +* BugFix分支也是在开发者自己的fork版本库维护,与功能分支不同的是,BugFix分支需要分别给主版本库的`master`、`develop`与可能有的`rc`分支,同时提起`Pull Request`。 diff --git a/doc/design/releasing_process/03.regression_test_list.md b/doc/design/releasing_process/03.regression_test_list.md new file mode 100644 index 00000000000..dbd091deeef --- /dev/null +++ b/doc/design/releasing_process/03.regression_test_list.md @@ -0,0 +1,18 @@ +# Paddle回归测试列表 + +本列表说明Paddle发版之前需要测试的功能点。 + +## Paddle Book中所有章节 + +Paddle每次发版本首先要保证Paddle Book中所有章节功能的正确性。功能的正确性包括验证Paddle目前的`paddle_trainer`训练和纯使用`Python`训练模型正确性。 + +| | 新手入门章节 | 识别数字 | 图像分类 | 词向量 | 情感分析 | 语意角色标注 | 机器翻译 | 个性化推荐 | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | +| API.V2 + Docker + GPU | +| API.V2 + Docker + CPU | +| `paddle_trainer` + Docker + GPU | +| `paddle_trainer` + Docker + CPU | +| API.V2 + Ubuntu + GPU | +| API.V2 + Ubuntu + CPU | +| `paddle_trainer` + Ubuntu + GPU | +| `paddle_trainer` + Ubuntu + CPU | -- GitLab From 6f6dca3bb8cc78aa0b0876e7791d3456c8968e87 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Wed, 19 Apr 2017 15:46:17 +0800 Subject: [PATCH 0065/3256] Follow comments --- .../01.how_to_release_paddle.md | 16 ++++++++-------- .../02.paddle_branching_model.md | 4 ++-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/doc/design/releasing_process/01.how_to_release_paddle.md b/doc/design/releasing_process/01.how_to_release_paddle.md index bf11f94c9df..39faf9504e1 100644 --- a/doc/design/releasing_process/01.how_to_release_paddle.md +++ b/doc/design/releasing_process/01.how_to_release_paddle.md @@ -4,19 +4,19 @@ Paddle使用[git-flow](./02.paddle_branching_model.md) branching model做分支 Paddle每次发新的版本,遵循以下流程: -1. 从`develop`分支派生出新的分支,分支名为`版本号rc`。例如,`0.10.0rc` -2. 将新分支的版本打上tag,tag为`版本号rc.Patch号`。第一个tag为`0.10.0rc0`,第二个为`0.10.0rc1`,依次类推。 +1. 从`develop`分支派生出新的分支,分支名为`release/版本号`。例如,`release/0.10.0` +2. 将新分支的版本打上tag,tag为`版本号rc.Patch号`。第一个tag为`0.10.0rc1`,第二个为`0.10.0rc2`,依次类推。 3. 对这个版本的提交,做如下几个操作: - * 编译这个版本的Docker发行镜像,发布到dockerhub。如果失败,Patch号加一,返回第二步 - * 编译这个版本的Ubuntu Deb包。如果失败,Patch号加一,返回第二步。 + * 编译这个版本的Docker发行镜像,发布到dockerhub。如果失败,修复Docker编译镜像问题,Patch号加一,返回第二步 + * 编译这个版本的Ubuntu Deb包。如果失败,修复Ubuntu Deb包编译问题,Patch号加一,返回第二步。 * 使用[Regression Test List](./03.regression_test_list.md)作为检查列表,测试Docker镜像/ubuntu安装包的功能正确性 - * 如果失败,记录下所有失败的例子,在这个`rc`分支中,修复所有bug后,Patch号加一,返回第二步 -4. 第三步完成后,将`rc`分支合入master分支,并删除`rc`分支。将master分支的合入commit打上tag,tag为`版本号`。同时再将`master`分支合入`develop`分支。最后删除`rc`分支。 + * 如果失败,记录下所有失败的例子,在这个`release/版本号`分支中,修复所有bug后,Patch号加一,返回第二步 +4. 第三步完成后,将`release/版本号`分支合入master分支,并删除`release/版本号`分支。将master分支的合入commit打上tag,tag为`版本号`。同时再将`master`分支合入`develop`分支。最后删除`release/版本号`分支。 5. 编译master分支的Docker发行镜像,发布到dockerhub。编译ubuntu的deb包,发布到github release页面 6. 协同完成Release Note的书写 需要注意的是: -* `rc`分支一旦建立,一般不允许再从`develop`分支合入`rc`。这样保证`rc`分支功能的封闭,方便测试人员测试Paddle的行为。 -* 在`rc`分支存在的时候,如果有bugfix的行为,需要将bugfix的分支同时merge到`master`, `develop`和`rc`这三个分支。 +* `release/版本号`分支一旦建立,一般不允许再从`develop`分支合入`release/版本号`。这样保证`release/版本号`分支功能的封闭,方便测试人员测试Paddle的行为。 +* 在`release/版本号`分支存在的时候,如果有bugfix的行为,需要将bugfix的分支同时merge到`master`, `develop`和`release/版本号`这三个分支。 diff --git a/doc/design/releasing_process/02.paddle_branching_model.md b/doc/design/releasing_process/02.paddle_branching_model.md index 562f0a07576..0b2a1bf32f9 100644 --- a/doc/design/releasing_process/02.paddle_branching_model.md +++ b/doc/design/releasing_process/02.paddle_branching_model.md @@ -5,7 +5,7 @@ Paddle开发过程使用[git-flow](http://nvie.com/posts/a-successful-git-branch * Paddle的主版本库遵循[git-flow](http://nvie.com/posts/a-successful-git-branching-model/)分支规范。其中: * `master`分支为稳定(stable branch)版本分支。每一个`master`分支的版本都是经过单元测试和回归测试的版本。 * `develop`分支为开发(develop branch)版本分支。每一个`develop`分支的版本都经过单元测试,但并没有经过回归测试。 - * `rc`分支为每一次Release时建立的临时分支。在这个阶段的代码正在经历回归测试。 + * `release/版本号`分支为每一次Release时建立的临时分支。在这个阶段的代码正在经历回归测试。 * 其他用户的fork版本库并不需要严格遵守[git-flow](http://nvie.com/posts/a-successful-git-branching-model/)分支规范,但所有fork的版本库的所有分支都相当于特性分支。 * 建议,开发者fork的版本库使用`develop`分支同步主版本库的`develop`分支 @@ -13,4 +13,4 @@ Paddle开发过程使用[git-flow](http://nvie.com/posts/a-successful-git-branch * 当功能分支开发完毕后,向Paddle的主版本库提交`Pull Reuqest`,进而进行代码评审。 * 在评审过程中,开发者修改自己的代码,可以继续在自己的功能分支提交代码。 -* BugFix分支也是在开发者自己的fork版本库维护,与功能分支不同的是,BugFix分支需要分别给主版本库的`master`、`develop`与可能有的`rc`分支,同时提起`Pull Request`。 +* BugFix分支也是在开发者自己的fork版本库维护,与功能分支不同的是,BugFix分支需要分别给主版本库的`master`、`develop`与可能有的`release/版本号`分支,同时提起`Pull Request`。 -- GitLab From 8af90f54dc5d87985ef9d91542fc2725764d45dd Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Wed, 19 Apr 2017 16:41:25 +0800 Subject: [PATCH 0066/3256] Make table shows better --- .../03.regression_test_list.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/doc/design/releasing_process/03.regression_test_list.md b/doc/design/releasing_process/03.regression_test_list.md index dbd091deeef..0ff012bc47a 100644 --- a/doc/design/releasing_process/03.regression_test_list.md +++ b/doc/design/releasing_process/03.regression_test_list.md @@ -7,12 +7,12 @@ Paddle每次发版本首先要保证Paddle Book中所有章节功能的正确性。功能的正确性包括验证Paddle目前的`paddle_trainer`训练和纯使用`Python`训练模型正确性。 | | 新手入门章节 | 识别数字 | 图像分类 | 词向量 | 情感分析 | 语意角色标注 | 机器翻译 | 个性化推荐 | -| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | -| API.V2 + Docker + GPU | -| API.V2 + Docker + CPU | -| `paddle_trainer` + Docker + GPU | -| `paddle_trainer` + Docker + CPU | -| API.V2 + Ubuntu + GPU | -| API.V2 + Ubuntu + CPU | -| `paddle_trainer` + Ubuntu + GPU | -| `paddle_trainer` + Ubuntu + CPU | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | +| API.V2 + Docker + GPU | | | | | | | | | +| API.V2 + Docker + CPU | | | | | | | | | +| `paddle_trainer` + Docker + GPU | | | | | | | | | +| `paddle_trainer` + Docker + CPU | | | | | | | | | +| API.V2 + Ubuntu + GPU | | | | | | | | | +| API.V2 + Ubuntu + CPU | | | | | | | | | +| `paddle_trainer` + Ubuntu + GPU | | | | | | | | | +| `paddle_trainer` + Ubuntu + CPU | | | | | | | | | -- GitLab From 39d6d1c2e9fa3ba03eb32ac2bd246f50c78408cf Mon Sep 17 00:00:00 2001 From: typhoonzero Date: Wed, 19 Apr 2017 19:17:47 +0800 Subject: [PATCH 0067/3256] update cluster design --- doc/design/cluster_design.md | 146 ------------------ doc/design/{dist => cluster_train}/README.md | 21 ++- doc/design/cluster_train/checkpointing.md | 76 +++++++++ doc/design/cluster_train/data_dispatch.md | 67 ++++++++ .../src}/checkpointing.png | Bin .../cluster_train/src/data_dispatch.png | Bin 0 -> 33872 bytes .../src/paddle-etcd.graffle | Bin .../src/paddle-etcd.png | Bin .../src/paddle-model-sharding.graffle | Bin .../src/paddle-model-sharding.png | Bin .../src/paddle-ps-0.png | Bin .../src/paddle-ps-1.png | Bin .../src/paddle-ps.graffle | Bin .../src/paddle-task-queues.graffle | Bin .../src/paddle-task-queues.png | Bin .../src/paddle-task-states.graffle | Bin .../src/paddle-task-states.png | Bin doc/design/cluster_train/src/trainer.graffle | Bin 0 -> 5644 bytes .../{images => cluster_train/src}/trainer.png | Bin doc/design/images/master.png | Bin 166142 -> 0 bytes doc/design/images/trainer.graffle | Bin 8566 -> 0 bytes doc/design/images/trainer_data.png | Bin 138135 -> 0 bytes 22 files changed, 157 insertions(+), 153 deletions(-) delete mode 100644 doc/design/cluster_design.md rename doc/design/{dist => cluster_train}/README.md (95%) create mode 100644 doc/design/cluster_train/checkpointing.md create mode 100644 doc/design/cluster_train/data_dispatch.md rename doc/design/{images => cluster_train/src}/checkpointing.png (100%) create mode 100644 doc/design/cluster_train/src/data_dispatch.png rename doc/design/{dist => cluster_train}/src/paddle-etcd.graffle (100%) rename doc/design/{dist => cluster_train}/src/paddle-etcd.png (100%) rename doc/design/{dist => cluster_train}/src/paddle-model-sharding.graffle (100%) rename doc/design/{dist => cluster_train}/src/paddle-model-sharding.png (100%) rename doc/design/{dist => cluster_train}/src/paddle-ps-0.png (100%) rename doc/design/{dist => cluster_train}/src/paddle-ps-1.png (100%) rename doc/design/{dist => cluster_train}/src/paddle-ps.graffle (100%) rename doc/design/{dist => cluster_train}/src/paddle-task-queues.graffle (100%) rename doc/design/{dist => cluster_train}/src/paddle-task-queues.png (100%) rename doc/design/{dist => cluster_train}/src/paddle-task-states.graffle (100%) rename doc/design/{dist => cluster_train}/src/paddle-task-states.png (100%) create mode 100644 doc/design/cluster_train/src/trainer.graffle rename doc/design/{images => cluster_train/src}/trainer.png (100%) delete mode 100644 doc/design/images/master.png delete mode 100644 doc/design/images/trainer.graffle delete mode 100644 doc/design/images/trainer_data.png diff --git a/doc/design/cluster_design.md b/doc/design/cluster_design.md deleted file mode 100644 index e20a4491147..00000000000 --- a/doc/design/cluster_design.md +++ /dev/null @@ -1,146 +0,0 @@ -# Paddle大规模分布式训练设计 - -## 概览 -参考[这里](https://github.com/PaddlePaddle/Paddle/pull/1620/files) - -## 分布式训练架构 - -常见的深度学习分布式训练的架构如图: - - - -为了完成一个深度学习的训练任务,集群中会运行多个trainer和parameter server,每个trainer启动时,会先尝试从parameter server集群下载最新的参数,然后以mini-batch为单位读取训练数据集中的一部分数据(Data shard)。trainer会在训练过程中持续与parameter server通讯,上传计算出来的梯度以及下载最新的模型。 - -每个parameter server保存所有parameter的一个分片(Global model shard),并负责接受所有trainer发送的梯度,完成SGD和优化算法,然后发送更新后的parameter到每个trainer。 - -这样,通过trainer和parameter server的分布式协作,可以完成神经网络的SGD方法的训练。Paddle可以同时支持同步SGD(synchronize SGD)和异步SGD(asynchronize SGD)。 - -在使用同步SGD训练神经网络时,Paddle使用同步屏障(barrier),使梯度的提交和参数的更新按照顺序方式执行。在异步SGD中,则并不会等待所有trainer提交梯度才更新参数,这样极大的提高了计算的并行性:parameter server之间不相互依赖,并行的接收梯度和更新参数,parameter server也不会等待trainer全部都提交梯度之后才开始下一步,trainer之间也不会相互依赖,并行的执行模型的训练。可以看出,虽然异步SGD方式会提高参数更新并行度, 但是并不能保证参数同步更新,在任意时间某一台parameter server上保存的参数可能比另一台要更新,与同步SGD相比,梯度会有噪声。 - -在上面的分布式计算模型中,使用异步SGD比同步SGD可以一定程度的提供训练任务的容灾性。假设在某一时刻,一个trainer进程停止工作,其他的trainer仍然可以完成对部分数据的训练。 - -参考上面所描述的Paddle实现细节,可以进一步的优化以下方面: -1. 目前模型的参数是保存在parameter server进程的内存中的。在同步SGD或异步SGD训练过程中任意一台parameter server不能异常退出,否则参数丢失,训练不能继续执行。需要考虑每个模型分片(model shard)保存多个副本(replica)防止parameter server单点故障。 -1. 不能在一个训练任务中动态的增加或减少Trainer个数或parameter个数(异步SGD是否可以增加Trainer?) -1. 在同步SGD训练过程中,需要保证参数更新满足事务性操作。即可能在更新参数过程中,存放这个参数的shard所在的服务器故障,就需要rollback并重新更新这个参数shard的其他存活副本。 -1. 为了支持大量的训练任务和使用模型的应用在一个集群上,需要支持训练任务节点的伸缩。 -1. 支持训练任务的前置任务和后置任务,支持训练任务的定时调度和对在线流式数据的处理 - -## 模型参数检查点(Checkpointing) -模型数据检查点的实现,可以有效的避免parameter server的单点或多点同时故障。模型参数检查点通过定期向磁盘上保存一份存储在parameter server内存中的模型数据的完整镜像,来保证训练过程可以从中间状态重新启动。在一个不可中断并缺少备份的训练任务中,可以通过阶段性的保存每个parameter server的数据快照(snapshot)到 ***分布式存储服务/分布式存储挂载点*** 达到容灾的目的,比如每隔10分钟或1小时保存最新的快照,并删除更早的快照。在出现单点故障时,只需要恢复这台节点,或者将这台节点迁移到另一个节点并启动即可恢复训练任务。 - - - -### 快照保存的设计如下: - -前置要求: -* 所有parameter server在etcd上注册自己的id节点为TTL节点`/paddle/pservers/[id]`,并保持心跳。同时使用watcher监听`/paddle/pservers`目录,监听parameter server增加或丢失的消息。 -* 所有trainers在etcd `/paddle/trainers/[id]` 下注册节点。并监听暂停信号:`/paddle/trainers/pause`(监听节点创建和删除),`re-fetch` 信号。trainer在收到pause创建的信号之后,需要保存trainer的reader所读取的文件信息(文件名/文件元数据),和读取的offset到:`/paddle/trainers/[id]`的内容中。 - -程序流程: -1. 满足条件""每个pass或每n个mini-batch"时,parameter server原子写入`/paddle/trainers/pause`暂停所有trainer上传新的梯度 -2. parameter server在etcd服务中创建`/paddle/checkpoints/[snapshot uuid]/[parameter server id]`TTL节点,标识快照开始更新。然后开始向磁盘/存储服务中一个新的文件写入快照数据,并在写入过程中定时更新 etcd的checkpoint TTL节点已保证心跳。 -3. 任意一个parameter server完成检查点更新后,创建etcd目录`/paddle/checkpoints/[snapshot uuid]/finished/[parameter server id]`,写入完成的timestamp。然后检查是否所有的parameter server都完成。如果是,跳到第5步;否则循环等待。 -4. 如果在任意时间点,收到parameter server增加或丢失的消息,则需要回滚整个集群训练过程到上一个检查点: - - * 如果没有处在暂停状态,则暂停所有的参数更新 - * 删除etcd中`/paddle/checkpoints/[snapshot uuid]`的路径,清理没有成功执行的检查点任务。 - * 从etcd中读取检查点的uuid和timestamp,然后解析所有存储在磁盘上的检查点文件(可能有多个文件),判断对应uuid是否相同,如果都不同,则报错退出(FATAL error)。如果有相同的文件,则加载这个检查点文件,并覆盖内存中的参数。 - * 原子性创建etcd节点:`/paddle/trainer/re-fetch` (即多个parameter server不重复创建),通知trainer重新获取参数 - * 删除`/paddle/trainers/pause` 节点,重新开启训练过程,trainer需要从`/paddle/checkpoints/latest`中找到上一个检查点的file和对应的offset,并将reader重新设置到这个位置。 - -5. 尝试获取`/paddle/checkpoints/finish_lock`分布式锁(使用etcd3或者客户端wrapper)。获取锁之后,更新 `/paddle/checkpoints/latest`的内容为最新的checkpoint的uuid,timestamp;从`/paddle/trainers/[id]`中获取file和offset并更新到`/paddle/checkpoints/latest/files/[id]`中;删除每个pserver的上一个snapshot文件;释放锁;删除`/paddle/trainers/pause`节点。 - -这里需要用户额外注意,在您的实际环境中,训练任务的运行可能会占满trainer和parameter server之间的网络带宽,如果parameter server此时还需要通过网络访问分布式存储以保存快照,可能会造成网络拥塞,而出现阶段性的运行停滞。 - -### ETCD文件一览 -***注:TTL节点表示这个节点在创建者消失时,在TTL时间内也会消失*** - -* `/paddle/pservers/[id]`: TTL节点。id是parameter server的id,保存parameter server的信息。 -* `/paddle/checkpoints/latest`: 最新的checkpoint的信息。json格式保存timestamp, uuid -* `/paddle/checkpoints/latest/files/[trainer id]`: 保存最新的checkpoint对应的每个trainer读取数据的文件和offset -* `/paddle/checkpoints/[snapshot uuid]/[parameter server id]`: TTL节点。uuid是checkpoint生成的唯一snapshot id -* `/paddle/checkpoints/[snapshot uuid]/finished/[parameter server id]`: 同上 -* `/paddle/trainers/[id]`: TTL节点,保存trainer信息。如果发生全局暂停,则节点中以json格式保存trainer正在读取的文件和offset -* `/paddle/trainers/pause`: 控制trainer暂停上传梯度 -* `/paddle/trainers/re-fetch`: 控制trainer重新从parameter server读取参数并覆盖本地参数 - -## 训练数据的存储和分发 - -### 现在的方法 -生产环境中的训练数据集通常体积很大,并被存储在诸如Hadoop HDFS, Ceph, AWS S3之类的分布式存储之上。这些分布式存储服务通常会把数据切割成多个分片分布式的存储在多个节点之上,而多个trainer通常也需要预先完成文件的切割。但通常的方法是从HDFS上将数据拷贝到训练集群,然后切割到多个trainer服务器上,但这样的效率是底下的。如图(Mount/Copy): - - - -### 期望的方法 - -考虑到HDFS实际上已经完成了数据切割的任务,而且如果存在前置的数据预处理任务(Map-Reduce或Spark SQL),这些任务的输出也都存放于HDFS之上,则trainer可以直接调用HDFS LowLevel API,从元数据节点获得每个数据分片存储的位置,直接获得分片。 - -***注:每个数据分片保存多个mini_batch*** - -我们将使用如下的设计完成数据分发: - - - -如图,数据存储在分布式文件系统中,并将预处理之后的文件切割成3个block存储在不同的机器上。在训练任务开始时,master读取这个分布式文件的元数据,并将一个block分配给一个trainer,然后将分配信息写入etcd中。随后trainer从etcd中获取到数据的分配信息并开始执行训练。一个block数据训练完成后,master负责在将新的block分配给一个trainer(图中虚线所示)。 - -master不会直接发送数据给Trainer而是负责协调训练数据的分配,并以ETCD为协调中心。所以master是一个无状态程序,任务运行过程中,master停止后只需要重新启动即可。 - -## 第一版**不需要**支持的特性 -### 推测执行/加速执行(TODO) -在异构集群中,如果存在某些trainer执行速度过慢会影响整体集群的速度(如图中Trainer 1),此时master将负责启动一个新的Trainer(Accelerate Trainer 2),使用同样的训练数据block。哪个trainer先完成block的训练,则把另一个慢速的kill掉。 - -### 关于存储的考虑 -* 图像/音频类数据,预处理之后以何种方式分布式存储,如何切割? -* 支持流式数据接口和常规文件接口 -* 对不同的分布式存储,需要实现不同的reader wrapper - -### 动态扩容/缩容 -虽然故障恢复可以提供任意时刻的节点新增和删除仍然可以保证任务正常运行,但通常这样是比较暴力的。为了能graceful的关闭多个节点,master需要提供对应的API接口: - -```python - def resize(n): - save_checkpoint() - pause_all_trainers() - start_and_wait_trainers(n - self.num_trainers) - start_and_wait_pservers(n - self.num_pservers) - do_parameter_re_hash() - trainers_re_fetch() - start_all_trainers() - return success -``` - -要实现`do_parameter_re_hash()`,将现有的parameter能够在增加parameter servers时,完成重新分布,需要实现以下的细节: - -``` -parameters = large vector -<..............................> -|___| |___| |___| - ^ - | -parameter block -需要: - hash to map to -parameter block --------> 128~1024 slots --------> parameter servers -``` - -接口完成先发送信号暂停训练任务,保存参数的checkpoint,然后重新开启训练。这样可以避免程序bug导致的数据不同步问题出现。 - -## 实现考虑 -由于两阶段提交和数据备份同步、选举部分实现比较复杂,可以考虑使用一些开源库函数,比如2pc,raft库等,后期在优化过程中逐步替换。 - -## 附录 -### 引用 - -* [Large Scale Distributed Deep Networks](http://papers.nips.cc/paper/4687-large-scale-distributed-deep-networks.pdf), Jeffrey Dean, Greg S. Corrado, Rajat Monga, Kai Chen, Matthieu Devin, Quoc V. Le, Mark Z. Mao, Marc’Aurelio Ranzato, Andrew Senior, Paul Tucker, Ke Yang, Andrew Y. Ng - -### 术语 -* model: 指深度学习训练之后得到的所有参数,使用这个神经网络可以完成对新数据的预测 -* parameters: 神经网络中的参数,包括权重w和偏置b。一个神经网络的模型由大量的参数组成 -* shard: 分片,通常指将一个整体拆分成多份的其中的一份。 -* model shard: 将一个神经网络参数拆分成多份,每个shard分别存储在其中一台parameter server之上 -* parameter block: 多个parameter block构成一个model shard -* 单点故障: 任意时刻只可能同时有一台服务器故障。由于集群中同时存在两台机器故障的概率极低((平均故障率*平均故障修复时间)^2)只对特殊在线系统考虑两台以上同时故障的容灾。 - -### TODO: -All-Reduce和Ring的不同设计考虑 diff --git a/doc/design/dist/README.md b/doc/design/cluster_train/README.md similarity index 95% rename from doc/design/dist/README.md rename to doc/design/cluster_train/README.md index 1788208bcab..6a94a82825e 100644 --- a/doc/design/dist/README.md +++ b/doc/design/cluster_train/README.md @@ -31,7 +31,7 @@ The master process will: - Keep track of training progress on the dataset with [task queue](#task-queue). A training job will iterate on the dataset for a full pass until it goes into next pass. -#### Task +#### Task A task is a data shard to be trained. The total number of tasks will be much bigger than the total number of trainers. The number of data instances inside a task will be much bigger than the mini-batch size. @@ -78,7 +78,7 @@ The communication pattern between the trainers and the parameter servers depends - Synchronous Stochastic Gradient Descent (sync-SGD) Parameter server will wait for all trainer finish n-th mini-batch calculation and send their gradients before broadcasting new parameters to every trainer. Every trainer will wait for the new parameters before starting n+1-th mini-batch. - + - Asynchronous Stochastic Gradient Descent (async-SGD) There will no synchronization between different trainers, and parameter server updates its parameter as soon as it receives new gradient: @@ -118,8 +118,6 @@ When the master is started by the Kubernetes, it executes the following steps at 1. Watches the trainer prefix keys `/trainer/` on etcd to find the live trainers. 1. Starts dispatching the tasks to the trainers, and updates task queue using an etcd transaction to ensure lock is held during the update. -The master process will kill itself if its etcd lease expires. - When the master process is dead for any reason, Kubernetes will restart it. It will be online again with all states recovered from etcd in few minutes. ### Trainer Process @@ -132,6 +130,8 @@ When the trainer is started by the Kubernetes, it executes the following steps a If trainer's etcd lease expires, it will try set key `/trainer/` again so that the master process can discover the trainer again. +Whenever a trainer fails, the master process is responsible to schedule the failed task back to "todo queue". then kubernetes will try to start the trainer somewhere else, then the recovered trainer will try to fetch new task to continue the training. + ### Parameter Server Process When the parameter server is started by Kubernetes, it executes the following steps at startup: @@ -140,11 +140,11 @@ When the parameter server is started by Kubernetes, it executes the following st 1. Search through etcd keys `/ps/` (`/ps/0`, `/ps/1`, ...) to find the first non-existant key whose index is smaller than the total number of parameter servers. Set the key using a transaction to avoid concurrent writes. The parameter server's index is inferred from the key name. The desired number of parameter servers is 3: - + - + The third parameter server joined: - + 1. The parameter server can load parameters if there are already saved parameters in the save path (inferred from its index). @@ -153,6 +153,13 @@ When the parameter server is started by Kubernetes, it executes the following st If the parameter server's etcd lease expires, the parameter server will kill itself. +## Parameter Server Checkpointing +See [here](./checkpointing.md) + +## Store and dispatching trainning data +See [here](./data_dispatch.md) + + ## Dynamic Scaling ### Trainer Scaling diff --git a/doc/design/cluster_train/checkpointing.md b/doc/design/cluster_train/checkpointing.md new file mode 100644 index 00000000000..df2dc81c86e --- /dev/null +++ b/doc/design/cluster_train/checkpointing.md @@ -0,0 +1,76 @@ +# Paddle大规模分布式训练设计 + +## 概览 +参考[这里](./README.md) + +## 分布式训练架构 + +常见的深度学习分布式训练的架构如图: + + + +为了完成一个深度学习的训练任务,集群中会运行多个trainer和parameter server,每个trainer启动时,会先尝试从parameter server集群下载最新的参数,然后以mini-batch为单位读取训练数据集中的一部分数据(Data shard)。trainer会在训练过程中持续与parameter server通讯,上传计算出来的梯度以及下载最新的模型。 + +每个parameter server保存所有parameter的一个分片(Global model shard),并负责接受所有trainer发送的梯度,完成SGD和优化算法,然后发送更新后的parameter到每个trainer。 + +这样,通过trainer和parameter server的分布式协作,可以完成神经网络的SGD方法的训练。Paddle可以同时支持同步SGD(synchronize SGD)和异步SGD(asynchronize SGD)。 + +在使用同步SGD训练神经网络时,Paddle使用同步屏障(barrier),使梯度的提交和参数的更新按照顺序方式执行。在异步SGD中,则并不会等待所有trainer提交梯度才更新参数,这样极大的提高了计算的并行性:parameter server之间不相互依赖,并行的接收梯度和更新参数,parameter server也不会等待trainer全部都提交梯度之后才开始下一步,trainer之间也不会相互依赖,并行的执行模型的训练。可以看出,虽然异步SGD方式会提高参数更新并行度, 但是并不能保证参数同步更新,在任意时间某一台parameter server上保存的参数可能比另一台要更新,与同步SGD相比,梯度会有噪声。 + +在上面的分布式计算模型中,使用异步SGD比同步SGD可以一定程度的提供训练任务的容灾性。假设在某一时刻,一个trainer进程停止工作,其他的trainer仍然可以完成对部分数据的训练。 + +参考上面所描述的Paddle实现细节,可以进一步的优化以下方面: +1. 目前模型的参数是保存在parameter server进程的内存中的。在同步SGD或异步SGD训练过程中任意一台parameter server不能异常退出,否则参数丢失,训练不能继续执行。需要考虑每个模型分片(model shard)保存多个副本(replica)防止parameter server单点故障。 +1. 不能在一个训练任务中动态的增加或减少Trainer个数或parameter个数(异步SGD是否可以增加Trainer?) +1. 在同步SGD训练过程中,需要保证参数更新满足事务性操作。即可能在更新参数过程中,存放这个参数的shard所在的服务器故障,就需要rollback并重新更新这个参数shard的其他存活副本。 +1. 为了支持大量的训练任务和使用模型的应用在一个集群上,需要支持训练任务节点的伸缩。 +1. 支持训练任务的前置任务和后置任务,支持训练任务的定时调度和对在线流式数据的处理 + +## 模型参数检查点(Checkpointing) +模型数据检查点的实现,可以有效的避免parameter server的单点或多点同时故障。模型参数检查点通过定期向磁盘上保存一份存储在parameter server内存中的模型数据的完整镜像,来保证训练过程可以从中间状态重新启动。在一个不可中断并缺少备份的训练任务中,可以通过阶段性的保存每个parameter server的数据快照(snapshot)到 ***分布式存储服务/分布式存储挂载点*** 达到容灾的目的,比如每隔10分钟或1小时保存最新的快照,并删除更早的快照。在出现单点故障时,只需要恢复这台节点,或者将这台节点迁移到另一个节点并启动即可恢复训练任务。 + + + +### 快照保存的设计如下: + +说明: + +* parameter server在集群中启动后,自动挂载分布式存储目录,并把快照保存到这个目录下。 +* 所有parameter server和trainer在etcd上注册自己的id节点为TTL节点`/ps/[id]`和`/trainer/[id]`,并保持心跳。 +* ***注:trainer在故障恢复后,master会将失败的task重新分配给恢复的trainer执行。这样会引入更大的随机性。*** +* ***注:parameter server在保存检查点时,利用了Linux内核的“写时复制”技术,在fork的进程中保存检查点,原进程可以继续接收trainer的梯度更新请求,而不影响检查点数据的保存。*** +* ***注:每个parameter server的检查点各自独立保存,暂时不考虑多个parameter server同步的保存一个特定时间点的全局检查点,同样会引入随机性。*** + + +检查点保存程序流程: + +1. 如果满足条件""每个pass或每n个mini-batch"时,parameter server会`fork`自己,子进程中执行保存检查点任务,父进程继续工作。如果已经有子进程在进行保存检查点工作,则忽略。 +2. parameter server生成一个UUID,向指定的目录中一个新的文件(文件名为此UUID)写入快照数据。在快照写入完成后,计算这个文件的MD5 sum。然后在etcd的`/checkpoints/[pserver_id]`中写入json内容:`{"uuid": [UUID], "md5", "MD5 sum", "timestamp": xxxx}`。 +3. 删除磁盘目录中不是当前uuid的快照文件。 +4. 关闭fork出来的进程。 + +这里需要用户额外注意,在您的实际环境中,训练任务的运行可能会占满trainer和parameter server之间的网络带宽,如果parameter server此时还需要通过网络访问分布式存储以保存快照,可能会造成网络拥塞,而出现阶段性的运行停滞。 + +### 从快照恢复 + +在parameter server第一次启动或任意时间parameter server故障后被Kubernetes重新启动,则需要回滚到上一个检查点: + + 1. 从etcd中读取节点:`/checkpoints/[pserver_id]`获取最新的检查点的文件uuid + 1. 从磁盘文件中加载uuid文件名的检查点快照文件,并加载其中的参数 + 1. 如果上面两步出现错误,则使用启动参数定义的初始化方法初始化参数 + 1. 开始提供服务 + +## TODO List +### 推测执行/加速执行(TODO) +在异构集群中,如果存在某些trainer执行速度过慢会影响整体集群的速度(如图中Trainer 1),此时master将负责启动一个新的Trainer(Accelerate Trainer 2),使用同样的训练数据block。哪个trainer先完成block的训练,则把另一个慢速的kill掉。 + +### 动态扩容/缩容 +目前只考虑动态扩容trainer数量,可以减小系统复杂性。 + +## 术语 +* model: 指深度学习训练之后得到的所有参数,使用这个神经网络可以完成对新数据的预测 +* parameters: 神经网络中的参数,包括权重w和偏置b。一个神经网络的模型由大量的参数组成 +* shard: 分片,通常指将一个整体拆分成多份的其中的一份。 +* model shard: 将一个神经网络参数拆分成多份,每个shard分别存储在其中一台parameter server之上 +* parameter block: 多个parameter block构成一个model shard +* 单点故障: 任意时刻只可能同时有一台服务器故障。由于集群中同时存在两台机器故障的概率极低((平均故障率*平均故障修复时间)^2)只对特殊在线系统考虑两台以上同时故障的容灾。 diff --git a/doc/design/cluster_train/data_dispatch.md b/doc/design/cluster_train/data_dispatch.md new file mode 100644 index 00000000000..7bd01cf2d27 --- /dev/null +++ b/doc/design/cluster_train/data_dispatch.md @@ -0,0 +1,67 @@ +## 训练数据的存储和分发 + +### 流程介绍 +生产环境中的训练数据集通常体积很大,并被存储在诸如Hadoop HDFS, Ceph, AWS S3之类的分布式存储之上。这些分布式存储服务通常会把数据切割成多个分片分布式的存储在多个节点之上。这样就可以在云端执行多种数据类计算任务,包括: + +* 数据预处理任务 +* Paddle训练任务 +* 在线模型预测服务 + + + +### 训练数据的存储 + +选择GlusterFS作为训练数据的存储服务(后续的实现考虑HDFS)。 + +在Kubernetes上运行的不同的计算框架,可以通过Volume或PersistentVolume挂载存储空间到每个容器中。 + +在存储中的共享位置,需要保存PaddlePaddle book中的所有dataset数据,并且可以被提交的job直接使用。 + +### 上传训练文件 + +使用下面命令,可以把本地的训练数据上传到存储集群中 + +``` +paddle upload train_data.list +``` + +其中`.list`文件描述了训练数据的文件和对应的label,对于图像类数据,`.list文件`样例如下,每一行包含了图片文件的路径和其label: + +``` +/data/image1.jpg 1 +/data/image1.jpg 5 +/data/image1.jpg 2 +/data/image1.jpg 5 +/data/image1.jpg 1 +/data/image1.jpg 8 +... +``` + +对于文本类训练数据样例如下(机器翻译),一行中包含源语言,目标语言的文本(label): + +``` +L' inflation , en Europe , a dérapé sur l' alimentation Food : Where European inflation slipped up + +L' inflation accélérée , mesurée dans la zone euro , est due principalement à l' augmentation rapide des prix de l' alimentation . The skyward zoom in food prices is the dominant force behind the speed up in eurozone inflation . +... +``` + +### 使用reader + +使用v2 API编写训练任务是,可以编写如下简单的reader,返回文件中的各列,然后在调用`trainer.train()`时传入,完成训练数据的读取: + +```python +def train(): + fp = open("/glusterfs/mount/dir/yourfile_%d.list" % TRAINER_ID, "r") + + def reader(): + for l in fp: + yield l[:-1].split("\t") + + return reader +``` + +## TODO + +### 支持用户自定义的数据预处理job +### 支持SSTable格式的key-value数据 diff --git a/doc/design/images/checkpointing.png b/doc/design/cluster_train/src/checkpointing.png similarity index 100% rename from doc/design/images/checkpointing.png rename to doc/design/cluster_train/src/checkpointing.png diff --git a/doc/design/cluster_train/src/data_dispatch.png b/doc/design/cluster_train/src/data_dispatch.png new file mode 100644 index 0000000000000000000000000000000000000000..5bdcc24d6a6d193cb014f8c38b362451fded5e54 GIT binary patch literal 33872 zcmdqJbx>7N8~;ml=x&hi4yC&kNkJN43n2cRb(rs)PSv zo!`hy!F(Gb-vKXB9A4@;!@!{7L4ROjvT}&QfN5(DZ5M4NMIlprTMlD0dlPdG4_gN? z8U{whLkRq8Ywlu9xxoy@78bMSF+ z(TJf^QBjFFnOO+Ek&*lF;oy}hjg^awgAgaDySqDwJ1>X5lO-p&pr9Zp7Y`>74?7sa z?(AvjV(h_g=S=&bll+hK$e25uI$1loSlioCLCwEq)Ue(Ip z#U8AJleOt9I~Q{&aIlLpbarAq|2_KudBp$yTq;i1=HROT9nJmUqyP8!{yScT6MEtQ z+d}+jmH)j9mRSr{g!6yiGBMPuHJ$|+7zvnHGLjk|u=~00%W0E$y|F|4O7J8kh1gL{ zg+n#6a+6oWFkBaikN6#W*km|dP%clHkaz9eyU#Lr1 zrJcCH-K*>+cOIH}&ACwWDmj*a(IeA5ZO$}aboqj?rEj%VIrp}YHeM!$&7NA-5M0M# zbfuF!B<7i@SMBf%X^+;UUw0P6S)q`nTFX)15dqVy1^3a!ZavK*jDx>F+~){)u5Y&T zf6O}#>e;tktVVM_F6S>5NCq2%sU7@S^1IxhleJJj4X5&ip1-Ur(7zMMT_>IckHd!oc&PQ5{60P^~btwBPntf5@G2`9p=r1MH~*X&ULD`&WTWdIw`ot?TeZ}Rst(kuQ(za1vM+kg(+%9l zB#}38{ktM?vh9D}ym`$&T__#i`^_ z)3GWHbz5-NBJYYR+Wk`bi-zDeGVz0^p6!N0iZld2IN+SOyP&MDfu`++=~_>$H0P4G z?f2=&`!khG-s~5)3U1MCH4(Ni!MO;0uX#sB@ZQN;`IJ0@g`p0n^xm(gTLL$RDM+x` z_HX^-e5q1)4`Ya@D4Ij(hX3_SI5=SPh2M<6^DuZKmV*_dJkfeYjoM&pV#`pupE|L` z%~IF&R`Dg`lOV9t5~odGQlyZnFIrfbWV%T=qvB8Zgnt^GEr~Eb5F+SZk?cZZN1Xyg zK79R7i2LJV434@r{Eyh532MGGa?yTr>WAfEEIGfY`?FD;zu-CC=qA(~N*BpvNs(2A zH#rm=JJ&&nOInJSRIzrJg-J|P($H>LTjQWl5}v1IUi6uDca`kBi!6O!c!G1^Z`MoU z?xK?kAB(!{vDI+9`(;gTWz8oCei}Q6ls)z=(y<_uMA&VgxhyB>6S}L-?+TJH$b=Zb z=(sg>hA>{twx!GDOBE0!(6tf;=S^%z!QACU^zYXo;mv~wy>F-L+DNrsjP=`38tXE} z%+vg*C+VWA&B$DIiz6(t>w=ld`K^3^F&>YxrM)v?7mX^4;yRQC!>!;cGQKisbTKZT z4b7rWWQjPTA20jg?M))3$L=A=dF1A>>8RN{42v?Zr@?;dm*ME@0-0?c4jSUMu$;hU z@J*h}#H(M^@JIbsA8)pcK zvAi~GbSmWVi_>Vhc7g=b;9&c6;!4nuqQLU8Yw0^8-i80QYhvHW3dS4ZuU?6if)CrS zKzANs4S$J2_F~@u=|NEZVoF`5(0Pl~Lh!5ZcT~IM%iU=-Zy6iBY8&Gw0@(Y@zxLre z_SM~l3yrR41flI>q;BiCnY@-Jv%wZiG!BGB2YAzxJMpTcJR9-ik49p&_`bKmJq_-k z?v&+kDyLTJ`=SQI;YhS*=2E)$VR9FhtV5_&P3e9u2chX&cDLO(pN*_-C$mOS#SAQL z%~|Hh8ZL@O`mBT!t%lLo=3D1+pK!l3K5b)4t|5K?K7@G?IFm!COQ8%H)iSDVvbT^6 zx{z@_a(l83RZu36ZHi$V|>K+T@+jr9lCz4M*>VRLbI3Iz>?8*B-jcx5DWmtz>Uel+tRc(GC38`ndb78!mK zqncI2sD&rSLN8ahje|D1`}XsFFG+Az_c*KC;0pVPdEGyo@TjTM( z7dY$)VCYM54pAH$SrAJlAR2UVt49QLHZ-~rFf<_ooP$Ot3e4i)EW4IiYvyF?{>4@t za1NOWd`ajy)kH8UEwkE4D{%G@2^w&YNNnaH=(qx?xmEUA;QjyO!Y*)4OGu0t{r#-d z%4^<}z&8F}@_#ZIgp?mL(@Lbju9dks;v3i0A(2`xisop1db|^MKlIuCRAv#umPmu3 z5DwOJr%FRPHMs4a|HCxM306A8DSeLHC(Bgw?Zuc`Sj$ditVbZ_dRyqJG@3YY#d5K_ z)*n;<)Z6|AaUBHmpHc5iKSKhasHq+w#o5+BQy_e6-2H4CCm41MqP=rp0`+%w$LYFK z>;%8k(k#8y+~~Huqt+$h-BrYH%s_m(2Z;eGznzH1PzHDtm4aH;9P-kyvIgm`YZ8ux zk@#u^tI|V7#JMV+<#a5?z z-ceDs{?w|H&=o4^!Tl6U>i}kAQ#UThJ(OF4V!POU6M;jE$|=VjDv<^zKlxy*h%`_XYOYHB@H*1UYhEBZd!FTiQrRAh=c(Z)q|oz!8e|N9%MrvIE{Pa zFaPl_;Il_VqVg|_{~p~NFk4RX9+pBGh!4n4tomC)09`%%>$#N+%vAE7n9UczyMrGi zXCT7so2S0@xb3{@67aj-Vf4mk;Tr)ryf>PrQ1TQXZUdS(`^>Uzp-LO|)2EXQI+f7g z0@oed-J~KIBGC;D*w9glA?N|3@3r&sLq&B@6zeySDK+_r(?L!Il`KAM@cr`7wnj3c zX%?;s2tj0aKdG-GPWoN>9`!dff%RQBJY4?GeFE+}EdCBXElAWHq?w5)THuiZIsABc z^Zn_dmVxWrm!u*dRc9b+nu3KpS9<_1mFoCn<{ht;#c1BaN5Z(~KkvV^ob-_8``udK zT^=;_^upH7g7UTkk)IuJ25%j&bk68lQk=F^ z`l8uii(bx}4re`IAr*scoy{C>zWbtQxT;f|^MdP) z`9OPNKK4tzKu@NaUxvtd(NPs{2t!59AuWO_t!FMQHMOa5uVqEH_9q^$mPTReMZolS zWC?W;kmcXF|Fz|hMmh!o%!0{Zd@Y8T2Y|A!dgFgXx>RMYdvfWUiAHSy>&`0_W6Jt=t7K_#M6|H z#C6JZo2gT4!Cu{k3zEfNWJ;g0pN^(zx7Ta&AiLcC6PPh_;Mx{<1x}r;EQAvH@N+HB zS+ek>&uKp+hf4Yia8@tJza3`V)@KfuaD|6i8vkS_$W^^{cMTqpGiumK%NBGloo-kB zkgy~S^7hJit<37BKL`DwSu;^=pW%-mkH;T4ScrKohCsoQIA@?*mGUv&@J}{la+Iv0 zaVUXD@5PnBpZNW8CvZyo3?Q{h0oJQR;y|Efg7EVs(N^&GcyE|-x=B*scRrqz>!UuJesal3NpiIC% z0G3(R=Wz;2A@Bk#(YRoco=X0|3eQv|SVa@nn!oW)&r}3f5Z1T9|JFsL;2-5R@V%H8 zP|c!^{9!e|+H$qn`E3&__<(XDfV+gJ5& zEqS30&eRoEIjNr;aSFCT79*?^-_kIwpk>9d5&Z&7j3Wmy_Iv z`(-Id(>@Q3tZ~#Q_#xk;mMc(CpTGEPlN{kBgkuQ2O&o==$;CY=8prQ5Ru|lte7AC( zF}LW#F)4IKFnn*%sr5whwM3EzsBr>oF!_~n+pY@&c@aP%$rOa|J-#~|8T%#)Lb7p0 zOw@#e7tE`S>xA@>rf1iW=`a3xy;hwlF;E}-yneqP6y~F1cQLflRTk?hNqq4MQbN&~{d)?oezX?g|o!ANl&Vym==bgrfAT z4(*|Y>L}U>yO4;3Vbsn@X)Mu{3e`VG%OaK|wZ61L2 z?)?2xIr=_`wOl}Rt;IBr4pf@mq@zs?-Ith6)`PK4CgN0f*q6 z4$trkEHalTgrA{|IGg8NWDnEXW`9ivFGjgEh`a}&!tK(LkR9i>oBs(IO2LBpMO)-5 zSkgYH=+mn-f}>4E_zrT*665C}!2K!?(TI8XxeCM{=M8ceWCXGFw+NhF)VX@UW#_9vv4Bly0-e1u^w#DT#;drvb3=klnf3EeiMfz}sxY4r=_AfDosg z1ZG$QL3ty)#+}lG`oAN;W31B_$G+gCN@XU>oFT9#r>R>&Yq>Lp0BI$9M+W z;PvFcDn!Z@tIBbwGvJU4O%zA5rizPN9zxjMkq6TCg~*)+lm@XjBN~HA$Y=yxq%@Jq z9bh1hA}gEF3N-)a$2_1)$(!WeV##&iWm(R=xpL2h# zQDnHCKGZ_`;Oxq8vtw!JQP@b&%08Z2=;ut~FqSY%VXghQKNV}%U>5hW*FMsv zRH>@ie{OV2&#t;&+1xYYHsFqkKXJF)sH=|Q3u{w$tw`?d+G*_DVPMRJ?M_w^v%YCN zoVyOiT9#;AWrCL<(#O%qL)csAjmopk^9&1`ZLFo6$LkF+S-{>na>8^Kc(hC5Vr;OQ zO4Oa*C^&YbndzdN`j6L`TN^bxfOPbQw*IZ? z415*iDZsEc+&j2rT5fV(eQnfuS~?s-=D{l!86TtCAtmaIvbSD4AJV?W-0|SkIXu%| z)JXG#qqBDbE84gUm1Qn*e!NFpE4)>mN$Y|5vx5YFs$odO-%Lowy9NaOaCa-yl%Zor zCw$S64n307gpi~zbFz&inh+s!e|`nk@J1`Ea8=xbjBeydyFDkO0d}H{*%K1p34`?{ zVHoz}yD#Mov+u*j{I-9#Y{4W>0ykw-p_%ba?Jv%I_xKFAyv(*YCq&a25q}w5y2wHp zzc8YmK5m?fmzw-WHdLwOTU41Re`T3w@mSF66>L>FsUn^W+nKU_5%1)F@Fx|s_f6cl zA=uY%sxi$QPrKvGqOKRS-}qQxFoPO3g2b-m!mXq=McLh)hC9m?r!Br=J8@Hp~V>PdWV_S7Nkp%Oz5w3PNx|5!i0PN(Yiofd{< z5XmW_!JF6e{?8;UN1<;Kx68y+k_VSh{tlUGD%Nq3*@iXVtkN$@Nbzfrj#zv+|X zM-^=Pl&3eG^x~9E-lL%Mkc1m@&@QBEv&hTccxznD8e3{nOe4GhC}LtHb@$OTJ7?K% z6aikEgIxiu{_R0>?jgXG9VpWu*Fvhcor+mooWwb?ZechB=ByAFT z$qM<;y=q1~%UEv*Ia^(7MJ+}5B@i}+i1llTRQT*zt{8fAY2W_RxW$K~moM-!NW>BH zJT>FzE6Axve>s63&Z{p!Z+K{n&a<)h9vPA1jZN8%hby6n> zD4b)`kc9DuN>r7M`m9JrORaY0X$6c;i)3l;eugd6eKAu$Zx{ALRB^g%;#5n>O* zO5GaGd*7Ozj;L;mHBAY*(Fo;(1yv~vmx~QD8T%5RwLM~Wx1X~IO|}+47TZ@#1Un|P z;|fL5PEL;Ro|A1-(|JXiKJ+mz1d(U7agqC7Oly78MSoi^UyXe+o|dSxJPe^1#ojiebcK9Q;d=<1k3q_h-d82FlFMtYUpvz z^3IcE&-GG4VllR}^;NJ6)U4^&jzWr%OXwpMbV->)2MJUAkJt zu^1&TUc@dqA}}5ijVxQ!1Ygv_tn5YmeUB1>+N0rZVt@Ut30lGr%A71EvFHG%Fwam5 zpTK9&d}XpfNIJl?TZOj7)`^bpQq2rnwq zFS30OWhT*b%^{C4x)=P~_e}ojWSET{ETn0h+7}rm8yK=i@-KT`h7WTukyhl8Upu+K zp9$R*n=nW+AwSQFT0iQZ(y>;Trpk_q+zkimK_;mtH==o36>%0p3L?U`AVGa=7UGml z@P9}ew78G3z)k#@Z8w8jXOxVG{y*3b0gCO$6=~l5!*=}Om8Wzm!oPu05>Q5%^-2a6 z%KlE}0PVByO97M-a623*x0B(GeEF~VUj(nVFfwuf0}0`vkkE<&hZze0`pCemW@>e~ ze*#SKU-gX%3z^>Op`?(y!36SU?)zvFA&gaRNG{^nC$mMoueE{fcy z60EGv#%!h3WZE<@$X`Zlk`hff)j~ea zbi*(?z=Y=|v--F0e*R)~n9QIQ)n1nswCJ&h(N_375*(?9Ez4b6e*Nj@KX?5VH)1F# z*NB2M+PEFI1qDys8Kh189&fg|Gc?~}UzjTtA9BCNC2ZwM~xS%$fSSDh61+7koXh$$u06a9336-hUD zO-H}}HhZ4AY!4bSPf;L9q2T?WlTv_;)c>`6?QN{;4tkdqfW0o(3!il1#fx5Qmn?$J ze>ju3%zQ8nV2B3U>yute9b~rJAs$xHr3BeZ(62sv(2Nj<_FU=HwBZc;04a?1R9{vttaR*2Ixn-aXKn0GKk;eXs%3KqLQe;dx!0c44Yr(iweCRlwM{`cCLpc zQ0J1Hj0!8(F|}vnWnjFV@kn8DV0(zh@e^h}-(xirv_3}waOLtsd)P>SN2q604dUD3 z0BfbpEFdP2I+nSihT*E`M>cN{!MuJfda*jgPUyogUwQ&Fd4 zsD@A){~GODpC+P43d|@VCXZ>Q1VzgBKcu*gq!4C(T(eN*L?=c0IVM_L6U;Ke+{yOeJgFfgsKWcW~PeXg;Zgb{D z*RD*Y$1Yo~0OR?Cxa|{1DgFrh$2)ViWoSV&v>oCP=aZXwSp%{hJ!+L1T?(xKycH(G z2(Zn=g(f`%qor~${g$zkBhV^;=W~KMaSMv<5S$XUQ$bMM3vLC6#BC360junKrwPvi z$!q}(d^6tCiiN0wSf2Uj4m4AH9(!m&(-l1QH6hGlY#)JQK>|_|36pgWknf8+nM+;c zUeE|U0JK0UD;l0SF`vUJ{>VS`?E2VY6ju6bMO6*u z`tCJnOI<%hHdH22p}$QJfI|lq8;%HWwW%2RIza6LQ0Ksz-mTKk#4b&~Uj+jX`rtzM z#}NGGsJJ5FcR-Sj9>9-1jgn>Cti!|xi8|K?kkLVswt&O3*0=SOTq!K@3 zFw2bP&d2mTZ?o0>CD77UKCqgC`qZl>{~rn)Z$5M}2_X~erbY)dRZ1feCvh<>;T{9O z)VU;|D!e8gE~~Ki6PqlCRxajP_DlOH&?mtEB@R0S&;QD2EQMGye!Hb#qU58VVL}32 zh|!iw61A+PAIU=PZzXTXCe-dQM{pzfx z&+)w{jj&0#&G8eevrKLk6+fFLb0|BNgS2&NLkU+%Z6@j&);_;#yuhg+02TlkV@u;~ z;qA7$n0@{J`_ye_%t~zZ8aASQFqbk1Mk+maN~-saUgatIuZ0m0lk!@$TrnJQrN+&w zItU)+cS@v1;??1xHGhNMYy`$lKj+$w=1zfx zV(kU>rS(zWK*)AzhHeHmI}hPYF8LSUH+*YlAVLfTWw?5Nj{J&s7jr`v zDv{DU_#Uu&Pnm#j%Aln?BLW+8^99n7gsJCqAMvW(Yb+>IP2{;BOe&nWE#N z3UD*BVV8oAdsp*^8yM0}6~$4tIiV*HgjHz+h87*b^$uQT*UXYzniku&rzTB8Q? z<%EXF5x0Dx8PXFqB4^!e&=?I(7DV2KiFx1hv8~%}(ZEuKo zbciicEreijX+^Wo*HE-66=6yK>Df9C*SiqSS@)R5*w{NP@%u4@IXqdfF-iq(8i^$S z2$iYlo56C6WL>(ED((YqWEw2|xO}XdcalgxX`zd4f$+gMjK8zqvhzHzw^}Due0F5& z8Y5_oBeMzOM%<`+AwdX?NY?k7PgdHPHTtF(yaR^*Kg{BdwMH!|#}^f|0XnWC8@zV^s%7YkU-**6kFwq07-jcI?jbZ2ub~mW0z;tfaO~7JL=zAMQ#P{K2KnIiv4Z2J@K| ztj~6`*-brk5=dRf&dDxMf;N9}@VtS?HJ^%m!OBoyIQOOQdf&UsAW}JuV~}refk)Q8 zD`-5IcKJTVeZ;6KsX|r$r<1yaQ4rzVlWSdRvS=L(`4_PVI5kw?ZL%7=QLniMNwbwj zj8_n2b5SH{Uvc^0XmG&oBT;V`ESchve9l49j1?u@{}i4bVl6Mg^L?*ihISn3>2Mt=rTNGr;gI|ymEg=j92gNvxw!BDog%{EKPHv7m zq6rDXo*6bZb9>Lt9mfZ@By=TVcj-RZOP+kM&(KCLT!@lH`p%rjrIC+_eK+)?n@6W7 zzsduoP@tI=0)lAB4(ty4Se47^1cUoW+5m@uS-&~+bZ+Orf_M7wW!$0dT3_yHk^q<2 zF(o>3V;f_wv zCiY3*k=T<9vk<-)59a>KikaFCRF+$~CL1jSEMJJ}$v>~UWFi`)1dJp;59D@P_gy!4 zb2R8+l85H29F%knFUUY2d>@90M<9R1uZOHx??dH7Omye0#0L^xAwv1ZGolO$2j&EN zGr4u2ObT@v*r6$#Eb5fY4u)xFv+5hkKBw+(0V!*TlQn6Bx^s%ckWjBu6}>7MXwqxs zQyE{I&#hAXL4vAQMQ^aCaG*?w#7vG*E0Ru*)fj;j?&sE`Lm2wVJ25H$lfp$hw(nI~#uyPj z!5WJNPCvOBR(#le>FWeSFfUW2Qu|(huX=I5U>6p_$cY-r^mIKxK%t;c6I@%`b4so4 zd&x2@VE%~qyaSOhsWvg2Ht__~6~3u0BUl8j4|U=u&W0!DGmdzd<`sxrf%!3-*~V`6 z&NXbvzD0-Qmh>dRp(I(Y{u9vpckPQdNi}EeWzb4zB&7y~*ia91+j8tJSGaL}h$G9@ zJMLhUHi~?I?m|NZhxDXYB5BRENiXWN3D5(JRbM!Sdi*`?OlZNdm+(@nE}mUEO zW#`0VLl)-h33I@kw*Q|%395Ob%5WyelXhTJiikiPaI7ri z^YxI!NG+-iHPGQiExcn#!5NVGM>xQtUyC8nH!&ZMU53)t)SJk1b}!cpuD$8qSjO3a zScw7KsFKyA0C(V+{}y~=#N*$4$YpW$9FI6X8M14amfkKEqKMBUS+TCpg7B|M%WPXI z(%EldRLn4n-QEb#NJfaq!2PPMjER)_791V6P;ss>Ey>DCiQd0(qD8qO#ehmesd~#s z{htz6ak;Icn$I=fqZx=7_{t9wO^8PPK3z@G)psi*DVJa6ZLwVSw^P+BY#!;Wj@(Jo z?-!(|61XA+5fx!C3eNy9$UmhxoOnR?qID{;jHj-Q#q zY1KNuPa@+M{L}yVxmH~HY~3h9hyKhRQ@{bPh|ZuEmv_a-X3Dt{%q>&FW?P|9r-h?* zCKZLe;+??Zh$s{J_x28owAhoT%xb)l2lRPz{|ahuzb#VZqd?D*i8DA{6hVj1@44ih zoJ+pw)I$R~{8HHn<7O5TcaSnIwDA)0+Q6LSY=pL4jC0!$D5Zn*hI78M|PM?yvlB6mu30sDV=< z{L=uwgh|!^hyK(6aO3^MMf3Wf^+VNsvT)_=|3F|2R0B93hNJWE)&G!z5Ff0xK5MD7XTn1N2Qm&i4|WKN>-`{~!4yfvW9`F=+oI#s8;M zqzh2sK!5^67C8MjBx~-OD$n43h5G+OxwoGFbb!VoE>SX@HMh`T`E4kr1_?vq8Ggho zHkvT5*k0mu1WZxJ_`AS`)B&J=yWOuH{e1f9zAJn1^NB)E#n;p;p8sW^$GF2GanJhd zR3t6*Xd&uREh~I*BgG1u^Wl*B{@uttnH9V9Kc1nfS`)D0y57P{<$8clnJlofl-oT> z3tE(0E=*W|42K%cBp+PwMNZDOa58mn1=7+xC7*6)cQcP<_tmaQky^4zPFDJeyo3mJ zR}^zaopMvC?aYXBx03QAtILcz<%H-X8aEL+ObOad-DW{+{C z(e3)eoRwbO8(&@UpHnSgxEzuv8sFP|pVkDRXIcN}RwgpnsNfcPrQ05mNSQYn&4~fs zd>2#;tk2vnTt8=#?XQq`X9*;>AI6Kijl(H6efKCl!wZ@>k37s`EKb?-uASDgp~fPp zFv_n&X$&TD{bmt#=r0fcCf7cs={xlUY{e<|>Q^DqiqoEf{E}-ag!=@~8$hR_9Ko}9 zbdPub{~H99Mwe{61_ZE4Am#*+j)u_`3Q31@#hQnq?=yyft4-xIg0O;gujh?|G2G@I z<*^}OZ8-Y=yK@03#Gp0rFCZT(T!uE|K`mAd7HIN5%{E`m7i<+E)s^`qG6OXAy-zm_ zPJ`?~T6GSDQvu8bZGsN)0lEArAf`|${u6+GErKz`+(BVmm7ch;rS^wB{YPL!#K&|PxeFkT zSSVMcCBG)3YE5QAB4Ldd@E zYxZ<5`n?(77%XA6fR`Ak(+YwGfI#oS7LyjieE@#4ZEE@fE*-r624E#%B7`*Xn3~#z zwx{d;OmANT3DO6s{D^~n?YJaaS=V{^IY}%sg$p(*0b0iy;Ab+eOQ15PjTf2dj&}m$ zt0z#E+yk}1Gq3H!a)HQ4?moKGw;@3|FZW#|1&beOM}S^?&!i*cuyK!rG0N`8f}2&f zipWV9$VMq1=oSc?c!)?0XwYuW({wBnWeE!>iwx=tu9ZCi3#m@<1!_Hgu~5NPZ-bL% zfzKH%4(casfEfeTzuTjL#3n$$3qW0;iMu(J2@m;4UL}%DDPRPSAHcp71mDEh9$i@iVQ9Bo0 zSNyY)rWZ%Rn42I7^E>SD5h!h``esmCPK8BDYIZCl>*(boj$A_n@mN~lIkY2ELB&)B z!saDy+p+b)|AZ>B$;|=1Pyexuh6#4zEOm-?(!%##_r=Ux9^~VGrh1LGmv&G=i2ftE z#ox3l7YoCmy_b{;<_s^bfiyf_1WNzkNHAs~XnI(Jm2k^9?1jqVPXV{Y8ZGb(3$#qc z`JO?|(YQ?`X8iCI=$7pyA(%0ICEj8ry3MI0Q4>Kv{?a$Xj>MhXw`)u>t;t-^0Nie@F* zmQJYfLQhO-+Vn2rm-6=Y9Z+jw2%CyI+z{;qY?hOo1QjJM+uuJ#(8fvRj~s!G2=Yeq zV{%4PPb0);^r9ZeMuUNn0^?c(Rq@HsZ&|few?=X^X>Vj8dhb7d>{~+QUL~%wu*3An zQre)|+Bl{O5Y`cwrh7n@4zPVS@ zKc>Oq#2x3=O{e+C13@((UrG_E7L6mkWcBTrod{J=PaA78={% zG;{mfxoIX!Vo&-!-d0c`Z1dh@qhYJfjYvvac1MyRK?xzW?zo9d0w4%v6=at|IT=!$({dmdDagas|oj6V`R+A0!qul0gLad zw=J|pC=}z_R%`*+gYVnuB`xGP%pX0%N}Up;9aG-;^`c@U$w<=>6H2R3m&h`};W3(c z?}ZAqonuB{dG$1bjVs+iM|QvU^kfsLC^hxzq~EttjzB63sKnpK!`ziG(&9`J86XT% zAPb9X`fmDhV*2PJ2RGDykGO^N<$I68%j3u$oQwS(z0rE8-+AuR{bni>Ug@^x>9Ki; z!esVCD8&*eL}!yu``Rq6NlxMGy6L`GtK9AVI-T2u=cg zf!x;vL~8NtWq5mnn7f~d^bF%1DTv$ZDx&5^Fqc7nYvrUyb27)~t4RXLgs@eJ^)%c# zkvQH4_clUCoE7#)GUz{iuNH`uQ;BjVdNIEey%{s|rVW*rrC&Y*;RyG)U*CR(zQDgY zK@LzZ*Yo_ISa_$CmcW|;y85Div|mGVrFvXH3bg`bCmPYIfAe#hBQWuK?aPyFRM`;o zD+tAWG1=`16DBDlHW!Xg-iS~}CoWCMpyPntpuL9iO9-?jti@E1JJ@LwfC~G&9sT`* zf}Ds&ewxZ?W^3G8yR(ogY}}|m;yqk{Osv=!hCerW$^x*#-$-~ZxN;Kf;=~_4f)d7d z{9ZDaNYTcpLh_h6R@)_8oB!H16@D>3A)(~0EiAX1cl zWE|~5pCbd$v$i86zBXkum@WQ04KPf`uhc)L_i<*lAQ;XQP0?yAs0 zvr$Tye(xJ{Bj(&sO^ht;_yTuFcD)D76@jdbi#a;-0lhh6#k#PZ0UM32?~9y#ejHb? z8hTN6_wHT@04BJ8VD|>(RQm)evVSIgwJ50zL6*KJT@vJM!$ILQ>T4kEy>8-!yK#=6h_P9X-_Y}bp#R+=R?`)K!SS%2hUxWwlW$|j=9 zke^5A5-|&~@);0owxObqP;(a&!OnN@TJHCmtG;flh>Jo}Rv@{Qwhe!a{Y7MUveobL zNq?vVKYV`~tM7(vGcIJX2$zJUnn;wWk@mLQJYcp%3=0nhm(@Z=5x(j==Zu4erk0PD zC*-Qg>4uTpDZyGidQ?GhiU*EdsYvBCy5@2%1c4!5EFxhdAt$FFxlB9vFHxEOfP9NYtj2@6hhe1sej?xL5Qcm^R`yhtW}Z zN2BPlYZkUIUsqAlP-?>4rBm9{rhT=sDU#{@nJT(MY_f{>rX199-veNx$u`BObdE7J z-lo3YFu4&<);s*2V3p~4YX|lajikMR5PC@r#m`!~hBE{?n)u9a_j*KAyp9eoXg5X> zIl#lycyT^FtUurtgrPF|t9$(u6}755#37BqIWg)=9(%TA_o@3~ zm*i5%Vriw?_!a~wmO33Qo-lOq~zt;$7MDFVUL@RC&!evLkYU`_Coae~dV7;qsJbQZNt zwX<$mVOdyVe0_gfye?2<+oB?Et=#>he6bYc#`>$yI{yQdL!81W=mwGRq>ggh`-N(x z!trz!EMaXN>yK^VMa%q}`g{}4z^77Lxjb(?4kG_UqoH!{+qi*nJFe zRZblJtJmlg=r0hDy@UkAV{(d;Ad#a{=C$vOSi})*dN%E+;`fV!a>Qpesc36Wt<8&1 z0)%Tt>L3@N*FC2-|lCaT8EW`S6&>L$u z!XAVPuPRVt3TcmO6^w{xw5*PKp=8tuv-`TLtCWhPPahP{C* zeRL!?=~Y%~hptF;$)|#lzD0tUiG&t{nxg3xmt65Cfo3exw zQndzx<$TlU{S9{EbXNG@zxtywwQy-1X=$&u8~Bw`7AgqF*yRYq@8c4l>5~7gViTA@ z$0P0CBbl}?x^J`HHd8@eNzo+(s_F|XSq-Ra5NRf2;+X)flt;g6QoPN5#AI2%}FBP;IppgUfYaL zsK#I>7faf$y1uK#I*umBdbqs9Ueth=x8IdUo2t^}IeU{bh7+ua@>)$(3<1}hB8IHV zj91s$n%hafJvNg>4!hm5+vtjzqF|%=$T9v$t)g>(F?sj-+a+V^5vd}h7?!@9-m69# z2YoS5!KhCb3z+?8kIL`blbAULJ~Q*U2^?O3sWdc~Lb4b(<|drcB)4Z?t~y&KE>zAF znPc5FLToo%4)HpgT!S$Kd{RXIbPaA<^iMiax;KhL^N~w zX1Ug6R^2Hm1nu39fz@uyr`3gzyI*p2R$x&|)2>$`u}7|v%XFKRq=g#O6~WZjtJMC* zewUZ38dlSHysou<7+)KVH8XNRCGW=#AX%v zl%_1k-}n1{ft?PIxxVGoEy?`3#JpVYvK&)H3g9%c zb21)_f>FvD4Cj9*OKkH(oCnL%Xf!*S?wkW{X)|97 z?wB-&6c$m#J_0{ME*6da08t(Sp-!`V9@Wd|AS7Q!G|QNR!AS>(Q=A2U_Gbfwup1iX z{`@21rMa&&EqZV!YIyWoPw=<8vbgFbb-j_J%Ln;ON3MuN>24hP@cPN)N7jlnZb|nd z{p%p2SKH!RA8Smd&KvhzSo1bRI!qN!P<#*%mv^t=IikkE*3e7^5%DE~i2uK||60b3 zObL_iUmjEE0-0 zkay_WPzv;kAxC>rVG)pFF`;HAC-L7K6qj;$FcQ&O2WL$42j>}{Awy=_`Z*GC*4*fH z&WFHJ!D>5r+>?=@)|7_hS^WaZPOwpvz}ux7_M+4z;*^F!i80>$X$u#Z<3S?0fmlrJ zK-(`F&~AC=-qZyH$zIP$+WpxQP>umdug`Uyo|XP8kk-vhO~WF8KAM5h1dK#ev!BI% zR*1M?LN6QfEbcdeqjUm|J%(om9|e4X;cMjY)c^nE{XGI`>b(Em+oZ{2r;)$_3c#U;#9y7t?L)ni(aN+-W`8e8pcDKLeReB@ z?Du6Lb$sgZu;S=%R{g5M{wsjithvvsK~kOoQBnt1x(_sm!1iJC@`A`a`he}T*%Pwb z^iIO#cuA{B{(JxXm+<&T`bdfr+WNzGHETc{CXM3)RgAEhqGzBs?;R*o2YUPysARQ{ z)5J~*?H1r^5r!sD2tHv%muKrXZ|o=JS)j$QE!~{D8;^E}Xv~AQG&BJnO&51Bpy39obN7 zE>IE-Dt8@=KE9{~IoA|s?dfqohq+-IaS&o{`~B^e0nNRx5E!AlKf1kj-vJpxHuRQZ z**aMb6l|YBDh@$T%LE9$aiA~;gzh#-rA~wuHUVU34FokoTDJqFG$AoH5O3~5VXfLM zm(~Xc$!Wve)rI?EEfH`hEULd^T;v^|*m)!r7j!HV0~gu^UzG zqh5AK^uDCcyZkjL^RdA_ZvJ?+iPI!_V*c)q=y%~r&^j$qT|6PB(3`>~c*M33<#r29 z0zOo!iLNr`aowo|4;~iT8aVUxVJDyH8gl({p#$Icx#X@4{L6#ZhGM*>) z04#zl_@BJ+aO%Q8>Be)mGexXL?{||yqP8W&7ll1(f(HDi%N+0J@kuKo-o)-m@(#14 z*9LGkAQnMN*Eu)7kt^Jitc}q?uNb!-vmFj~HK>*_}K9)TksTR@U#7X ze!vq*U|XIK5Dsk$pH`(7$Ra_4+Nw11!F~AU;NZe1b_o2$Lp1{byuspapCrY~1<8ff) zODHS<1k$3D0)UE)dt18bG#HV9^i%i!72knKz((hywz^7<-ekYTEWG_vV~p z!kzC&K6n55s|x#0fFdyWIsVd;RSLW;aPHn*I^s6cfyk+|mA7kdn72xI(dHZT_gKGy z{qa555+h;79gG;;HpaPDFz!IMM)%%|~Y1CDh-nh>uPEFQ%hL+=JbTw!72W{K(>pygj~eyYhm zx6pl)zkS-cu-&pi7~bncnV1lR%#QwY3q$0%yN$AZ{X+}Y;yYkSk)^ipCaL&I5kI2Y z>;Z$8Is7h)E$hR0?sorLOi#PdOkLKj2B7E#{gYencHomeOJ&-(TKrIjNt8h9HjZbvV{|^$ju;(NR*HBZa!V zOVNU=OCo^00!giSTPmVUU>kA0P;8e_Sq3wPRySew( zvBvkZB>2>d$s5e{b%noCXUee7XsG_S5u&$TN(S=QEN%4+X`#EVrwY)YqV%k>GnMz> z4yK6UK_6@OI0NS0j#m&Tj9)-mu7@QqX+5!-W@mmENZx{H*Lb~gkN)3_3FY(psKDJ@ z$8Rb&nqlrE*Y&`(V6|NokM`!qh#8s86+w)bwRpV~W4x1JRr&!56mTDbD^J8@9XUnW zIrW48VKqbLPw>UxU7rKM_()ON<@Rb^W8ywvuklkk9~jc1)Cwf*o}m1T^U$5p)0d!X zd-F}?lE9AB5Bb`V3PxR<7Et1#ppU-@-S%gMUf(6*hB{e*;Cs(jupsUjvg)lNBSyG# z?V9G4c|+1eETsg+$qjf|w&t|>Lmj=3)y|D~BYQumRTXXCKUrpusNq^qtnSLNsjFu= zaPoQCxepz6k}Exw=%>|m%MYI;!*uhFrMO8zJo9y$>*+DH&&wU97Z^~ImAvo`y0pZn zYXkVN#4 z0hRi|&`!Mh^j!1Y+wc88*C55d((%%E_y)q^s zD9=%5z17agr$6Neje*&XgNu%t4PCY6c+wN$_><+%nW*;~m)7fWx-h51$aDR@p;$)z z!zzFxfhB=>^NJhxE)!f(Kd~fsB|fwh8{mn>eenRPa%T`B0Q5lHC$=TxrG49RKsVfK z=I#&Y^uncabhYA2?8w`fC#`?_bL3Wo|7{+a_WgYBLlDoG%l6){)$?>xdsAp1^Q{7z zi)>tCOe}(i!Yz76Aex}FylOmHI5e7n0}BR;moGSN5JONdgHmBpQQ=F|dO{HEAEifZ zFGSvke(TR7@)!qSfd^AyX4F07>F$qgmj&gUEj1$-}R61%ge{vCkN_eOOq0d1PImy zts2yRxJ)1s9IkN(7XXu9LVgk84FJAqYLCl6k1Cfu;PE`tBq5q8EI5^VlxT1*%rm|A zorS#%H=V!)jdt6*76R8e{HjRtHAw#0(#CN0rHAD2zVUZ#3 zSqO72_iDYJMmhSvHUnirU_IBnf%b3er~=ulyMz>#=$a)Z3!x z>$5*(!yR98)1RehPl5@|!fpfWM~f_p#zW81*T>h3HRMm=*(~g7u{|Y)LL`jAu(_GE z*TsDO#T-tVTZt(?1#6Q+VVs32_CR3KzzThkj%N{g^vFB=oD8gRg`4u!aRoy(E5nwL zRKnzd36bM6g!lp)br3scW9xw-7gY7RIG$i5Cg!9H;t(9Uzu3+&KSLWm^}3LW7;{F~ z&6~^GQ74ZoQ-NAw7$?&|M%wLMOZ z>wuILMeGUG(vr^Pxq(HMouxJx5<+>Q1gh77^qt@ zP~>MU@TiMv6KHB_`KTFaNpM5E;o~vrF__T;yjVln`FbHvu~T{p)E{^EI;3@3D}h+* z2)-df&zG<4FWxU0Mj!{qpjn)Erqgrvx+zhSVpFIGorhntHNm1oB@-kY`$-)(hcuN9 zFft3KyeFda-58cP+!{lF5zUc(P5kYwv%pgPwM_%Z@!LAh5W?9os*ujm0D#kSDe|`C z4?l^-r$%8_pmP&dLBARQiCLaR;k5~kC6hfQK4J8xT8@$KGo*-Kt%IFEqa#*5D|_tc z@PxRxdZ2hWOJRl)(_0>x0S)X?YJRwc4MYqwMHj`LSz?5;Ikj_tNTqSn@2^mkK6q7#8o;QoeyNP?A;4Xh;Hws(Y|)kklkWvy#$TdHlX5+C0;Is<3&mH% zdcf2V7lE*0$*eJ}VaUi#;4XuZ7t0kow%MhL#mhfP&klN7UQYJr!;9~Gs@I>!#QR08 z1WiCK9CsF6e4yzqDwK|nWeL{YmJLf=lQ@r}w{_aJFK6-oo|jy6H*fGo#=j?Uh~2*WwcvhAiiX?)sO7yI@sL(mZE zQ)Vu2& zk$h^j2x36Oi0G?1xR*U~+`u9PpC6sBAm+&DvON#7|oI?-x{vC90!J87D|lm)Wu3J zqE^W7&RGPOI8{P4PUcLj(B?vb_QRJvasgK4t5X{m?hJMtZd8Q7xtJtxQ}mbW>(F2{ z#&zymGy&i&4k^^QA;n5r4i|@{A`zSEp3Vv(>en{#oNBnZZO=;3Aq);H*P@u$!s)RrhP?6~#w6or3 zw9O{{sA{1K3VC6gkGD21MuzJKzx9n&Cr6`&JVz8MZP;-r44Npmt`s>uEt_u7EEP%d zUtU%3%h+J-ZkSyNvU99RT;3|ggW@_;py{gGo++2MGsCZ2#^sVwK-{!)^p|Z13{no1 zb4kHm?{F?EUQ1j!YsiwOS~7(~IN3Y|Wq95Adfy?GnkwQ6Jg#I;#-#*pUZt{P#VXf? zZQfg2B{EmyB?HtdBV7jpndv~)n8u4qENu1 zPhY8Hi!F&7Bpu$S>?M-IE*p36oDDN2UaVUCyTaAbHp?!nD$b*fXehSDKjN}ut)Itc zG$rNpmu&|)=#f)h@c_GOA2O=#PRmJaS8nQ8U)J6Yyl)13G|y3YX#~66gtb}GSy}QH zZM2VAV}u;=+d`;SE19d!5mbA>J~xv{NgiZ76c_RuO#tj~kgw%;73Y#Yuno>(Nd1}r z`jZFO6_7UWKz?dpjWrG9^@6_5JQd9H_NlQ~e`^Iowv;Xbh{qCOne$R+FU~n+RX@_YZ|N3Uxe`bRyc#=Lq5h#mjRDO=!7DFPp|93B0kNGon_$)do z8EXvhF}5#`=0Zd0ivvB~Uo|+=4{Xp(v(OvU^M&8D=zTK|)pXJQEE9~YRV}lkb?pVl&lld;=N#Q;a@b00`KMeZ$J{hUSpjANY2s~lGHz5?u& z;L&^X#fGG?*IFBAlv!O|I2I4KH`7eg7lgdlA0YMS`TLtQq(2^Ryj}NDZCpyyrzFmk z^)bCM>9~!~vC$F$F1I%w|D8lj3%WCz<=X4M0nD|4GjJ(e>FA~A_ZSpxr~zfNZsoJ2 z1EX3NoSPY-yFtpCAsew*W)KS`w?mY#=WSS=AzsFV=w&y(Pj**jqFRv2aZW8S!A|22%Z~;<6ePqk0H-)yoyyj0f7Z7#u(0P_j~0*7Cgf ziECj7ArM&IE+QQh23H+2ASmH4T8Cl9_7Cam8T-P-G+ZH_GI6goz8QKSE~=)lSh}aw zH^5fQX!b*tYhooBvC!z7qmm}5uWinYjWKp=*(2Z#V1!wmxQzS8={+AX69)l(=_aSk zWV~rE4<_n;X zJ;VFr)uUeTz*UP=?}Pn#!}?svR^bY%FpfNk3(4oHd#ZR;jXSI#47F_=7| zQ+4XDQcPK1@8dO{5Ey;~b>c6`FSkr@ldokDju}GOi+*}71tj?)(!6nCgb$LI!|fI} zQV}*gbA18Tzj!sfmv$d9S6%bYm~D}M6l_Y@05;pF51se>*__RrFg2sW+}P+_*ty68 zJg#3wF#MXKGaM}(L=_vr&Kts41nllX%2K6DAUa^kQ|d(GQvX_Peg9^G66_PWPR*Ou zU|i2!-AwDPW;0gXKJi4IQwq7#X~W~jxV8yV7t+_up5qj|()pPfF|xOvP5Nj6w@_|E6p55v##s`Xs_X zoC?mZJEDF^d@~SsbB9CR^B#xK|IrJtZNIhv5H1PAhv}RAcr*&u1Okesiyy!yq>aog zG+roh3S^x=PDtK~*N36L$H4XXPQ*`K`0I5sLL(W-Y*!F8RG~FiEA?w;+q|&*SI^PT znrK@8kD{?zGAt!My$_X;D*VQ|Gjxs}w(G%#1?lBYP>Efu zsmFV4eRaB#ufYH^LsPG%Kpfox+3?2?ZJU};CrS&Qph++cgCa+DA_5VuTy_=LU59Xuz6~>6ah>NJyN5sg8fe3Zc6; zJ;b_D<@D+)##BY~p^O0pczVeB@?$8w~^qpMy7YzTFOv;8Y zgt4TN$_-1T7jMpt_6qK!w>EY8B$iV94!NmOQx9{gX7e>G6c!Xcs->O`{eWK}MJbTI zy*z%-3@n!=C3se4p#8Z%IzVy7prpFHoWTukK`O+UbE;}EPT))?(|SL$SFQ>7Byh~y z07D~!4UNEyxp~g}*Y|RNVjaB%vgf2JbH4B6)MW0i(6TYF{0{QPci43;G3IdqAP$A7 zF@68ob+?UrDK+sM^}_#C_Km~84yk7Bp!>oti&SU8#;3ll6H*O#_E453+>JRE->L*! zze~O#)<5z9J88>2$uzqQE33et2OzOci!qB3@V&Y%l#Mf?^IrCkEq#Ac1qTvh{+fL} zY>67z@Z-lW-^+eVH~*9ug27!>RGQgK4mmjHE6y$Oye?9LpER!r6WegG6P`M7Ua?DL zCHg5j1F^BideV~QLGgfiPcrbm)KVU;h}Fx9S)W?pP|adnLI(`rZ!qh8`$<12FRJ9u z-d_Gmity(t@}ewe!0;p3kAvOg5BQpM{%c!?H|kurZqVEvC;&QX9f&E%1j`I)rSj=% z71)!Lj57i&L^pKCSQz5$$N?czvIbywW$P5s@LM~6Qz-XDFR%iSuk9xuRX)h5Z+`>9 z;rNr{t582A9KKwFl|8xK50mb;L$@q+=J+Cn=vs4MFJ_;X^2|e*{kaj17S7@lC%e3YPp69$*4`H=G^vFBaCw{xg>nR9Tzf> z?5T6Tl6qlhhK-?Km=Xs!Vaxf)T}NX#_AD+l+B2Sd$dR2S8)aG}{P2|^#egaIJRHZb zNCNop6ayH0F9|~}@lI-J?Eifq?3ULOvzVW4e4~FO#4XDBYVK+&+|sOngs0AiFl8M^ z=hftk{0_LAj%8TMWH;cZBx4l~wh!OP+xlB>7k&*#Ck?M8mz)&69G@P%E{#^02YRcr z9M#Z=CHGIzmcsD!=FHBfcSRzV(FE`x(aE5lFi1=vi0`tJ!x9B1oJqqtziS$Zpu;{M z0d6Wk9e2CbUvTkRhY*u-7M}D7!fA4vrS=ClzHzfFoqaL%F;}KU>y60Oy$K?JXTu-3 zOrIKT-~Jm$FSEK}DRwZ!xm}qJ4|cAo~7yIPLW*U@T`(fw7acaW)~1d& zD7e^4$S@Jy9uv_ULw}_x-oQQ4UzKNKi4$y%Wo`Df z_k{Mz=mh7@i+?GG#^3H#54!QA9=&FV zj%qb7_G+X~veBRRN?mJ6ck`tECoS>;lm%((&Djob5)V95kSa|9b#kL^h1}C*J-+C0 zcl@fM_i-?#K=TC>Y(NWBs;`U1(WCkD8uH|n0arkW1cgH&;cQOryP=+mxA}0yMF%hj z*|2vFl;Xx6W2hqISU0=^+DHJo%*Tr&6ZY`h;%Y58CZx@-g)c`%pQ{xNAt+F=HFf=J9&a$twEF+ z6jwpnixx$_8(SCuRQWc}jj=gx&z!LPVt+>DPbl4SsVv)^tZ_~DO^ep`6ukka4@Ki= znQdM7J(_N)?+MIimnDAxR2$Dd>Gq;<887sRIe(tPkvNj14fP4P^FSl+BHA6`=Up_K zjSAIM1ukC3UQ6&U4z=G7st#WImV0{XwSl-Ja5h7C>WdXKx$yOwk?eSqlIhN~*+}?0Y5#_{yd^v&1~x zr|=iIU>S4!Yd$8T*jr+D)sg>1esOAb7LaR2<47W;=WnSCsCQud#m9uLL0^RVddGum zpDe_Qae)1d+81uva~Mrdmk8Ph{NAvZMV$fNWissPgmY-!4>F~dSEu720YUZ&_>eEa z!a?UZz*KSV`2qwN7k`BlIp3SN<3UR@@|l9bUk2@Xc0dwL8omoGAL|NUuQXKbdTy96 zFhahhsP)UVjn5yu`sNNB4TwL*Kq|y6JRZ;b=R(=_Ug%p~p_A}A5Ap&o6wCt8W4G82 zECs^HdxkC`Q)m4}L&!Z`usAaKURyu{8=SwB?I}@CH`#sWcWQ)417b_ZlVjSiZ->UiDQ`|Q zS}!k2y0S@0@p<1LeEBOl0f+8UiX5?cyxuYaE%tAv^WQJt&8I z1?gDgUaNnTcs2}YszwGq0**^u`-&-dzO^dDh;V$?pIW6d!_flEm|vF|8Q`T%b`6gX zx$Ei6mc!;f#9KeIx8wUe-0V9WT)7O|b+9dsrGI_N$d10hB;bSNw(}F?i!_mw|NiD< zrDyM1Yo>nf&xVSydxY@Gls>ap)(J99f1~njepN;7u}BYspDN0sfCrrzN+Y>zar=JW zJf>GbhzQ{YLHa=%vFu;Q0i_Dqs zb2p2ESTIecV;kE%e*pM6hnOz6RkWeF_4p(FTL2>io5b)VMAz3Lppj2v*%*!$7&;&E zTtE%iouzBMRXl~5UtWtJ*cki)WH1#DZhQr#6ar}U%$e;>SEt5-4fHiw3_&Q5kuMtg z;_c^vS5p8Y8F>n7xP@JSW_8+nRMT%yU;&cX9=v98=lE(!){y@_{z6UEqH%ZwV29Kw z&UTuBqjGb)0dZuvUn+TdioU!LuK_U>;F}eVW^-tlsuJ@%;Kc#0giwP*aPOyx zlhMInCsEKz>0sg>Y=KCDdk9?k?Q3_Z6GZWcW8heLyvmdN4X7|#l!T$M=$NOj32#Ad zmG|dKR{f}eitan!>nX@x1iqR)gd5n$+O%b45u-c1Jh{G+FVL= zST;Bt2?BTrX_PTK3#mV>a5J)!G7M@Bs2nP{uv1}i`Z;%FuvEt}am2i(c2ju}6rDKI zcZh@QDjeJCdHRFPr}#Y?+}70Zvop(nKXGpdE3hn<+nRh8=(lnTY7%Klc>6Q$^6tNE zto>`J67+w(1KYJ~VC4JJ6rX~Hc{|%u=Lg@KLNH*C0EcK8G-bk`1NsC-jwLgfz_REi zq$$EtRXxT`#|N6tdW*661MzW%fG|m6$lMg-mRO?bpO0FwJav|Ypz+q6+G;$v>{N3F zBzqzjpMsf+D7N+_GDHf?-$2?IY@e+~2ryiTJN^MoK-=)MKbM==z@e}mCl9a=xDo}h zv7M3*rDTS4$7C@-MK+{HxddTdJK}0TBb|$0OX_7)uvOR9c*$)r!C$J9ielz{R+=QhF4F)ovBu6LG$f+B* z-*yzES9=saBacX1u4odX2!xk=(s665^Luzvs=(sJv9Gt&3{<;<5dkZbV@`G{1cs3z zF4l(JCbi?-?FlYoY<5?BwK5h)10P2U;=j#fzY8bIP;TAYonL>+0nCoW^|zpxXl@DG zA8!3UU>!k!$a-i${1g+pImx<9y0J&AzyzLOg|*fb+K(Ud?4IrmFZMq=X7C%mClWxg z@T>bO^k$)I`h^VP)c!#aBBdjl(BK?7yAT_ZN1qnn^VLYH z#xiL2SW$!+J%Scf)LUSj$Yi(Ze=)6nr%K9>mnzIcej~S~kn@=Yg^3iH?Sp-<<~kX`rCwVN?0aOX4};hUjLaBZCzw6>)d`U zjh$V(^W5Z{Yarx~_keZ0uDgWow~V9~q7~i01xBdvDsoYJ^|uw2*I?iJGYuuJ1Kb*h zIbcuSCy6PvizSYTNudhIX5k1U`Ht7;%3htzP?d~UA&yQ!98|o8fHrurh-+&qBefQN zXZ_{hMX~>V4dk1^tojG)oL%{-R;&bCrlnD_DZ|jhEOVpCi|Kb~_0Zr76U|vJ(s1?| zK3fLrt~kgA#$nIfjho4oHmK!aDCQ9UV&sK+1EQ=$KI9| zmQL^X6Gl03{Rq~@o|xRQZYF+%_64@7f&1%i*}rILGwNMh0j;Kh!8LDE3X69FsK&>B z+vH0h2w)xk_ED;hAy3$q8dVvD?pd;^3IYQ7l1t9Ii;`beae0+4DHZj4=;HjZu+cNi zOF3r~n3D$Nv`F_TX-MVeQoA4zD*!^of+&-71~kuW>;yz`U=o$$ z{{XK5_xk^|7!Ufjbqevluj5)V*f13|XTmx|1TPtf;=%@;)aZxn+(KCOw{L3wi z^CAfG)b@t20E~O%lO>>F;_6A(E=7-oZhIRfHT{9P5E>H0F+YNO0 z5-KVmknP!Pg7%a|^Z397m)^Yg6+{?~r^d3qjT!oqm4H~vBQWBuR@7v|x-#+mjLon) zEdjbb^xGaIv3+6j5ig-V%XA zAI@!-Si*^VKg-e-e(QwS-&j`E_r}q$`7u{ts#!4ah^Lt;1>ui5cpF~C2T$?c3#L!6mpQXR*Z9Lczy$h>`pT}7xq;Jr#Yy*6N?=6srET6uI>o(ACgeF&oX;%KJ1S9xJh^-mh5Mjnb%jCR|KYqK4rNa+)2v;g?5{p z$SP>rsu`00pi-KW&mIfoM`D8_=RfB;=s)>u@ZH|`|h>ii#G zdNdQUBc|1@58c|V=~-W25Aj7JVQYqycJ89aqO-pf&yjRSl0d>j!ZHqlYiVxli$L@| z!Tsavnz6jRp4rxvp6fjoU#RikQ+Lrfyy-}1u?l?f0v1-+s}Q5`=d;km1_xoU2A>c9 z|11C7)rq{`8N&RGU^9*a^VMo-A-OTwU6CjXpLOB(bVH_he$$o5$+?C0-BS$IE{FeO z6Llq4;6{;h>eZ60Xpj;uMv%GVK6XFpp~TW zEl1w|a3bxiZ;pIS);>><-k`_Or-8qq9hBIIKX|iYDEQGe{IW4SJSZ^H8`u1IT~rA) za?##ENUiVzZL#K7iDyOtDF!Sq`p5_xdzl3007MkvAH6tT&$kCaXQ|`GFB>3%_cK=_ zK)e*((=lCgF$Cn?mT>&w_h^s&~lc!Wb+DzQ8^K z^X~!D_c4Xjtke0b`hzJ-$1Jkds2f z-?+`=Esc}jC4j%kjbssU7!%R^6w@7IYd%t3#V<)9QWwpGcBT}ff}n3KF^@H5%M^l$ zNrqpho(mG`;)P7-=F4?9x5vvn?h`pe$<6rQ1JU?^u&_P+y4~lx5^P!Mij-E|kC(6) z+-`+`gN+|LF%KQ+(^*U1M5q=MZ_Z?*K37ben=nz=!(7*OHe2}>uI;i>IN(j<&mx9)1N5p4dfaFJfOJ01eKMUAPMrX- zFQpZYs$6yk`Fw7Um5NUdv}obLZ5^>GgE?h`gmDrg|L^)z*abvWQ1Gej_q!Re2z~=W zDQTWQWDSsNUShd}SvnlfWp6Y!k(a`V8s2f-@9&>RqecfX*n;+#U_l3_IWPvf^r2X* z&AKR}BX$fBmSY#cnEA%~-CxkF@W`tz-d`S!_)q&^?xUL|_zuPr7iu+tFB8Sq5`nOU z%ZU^OdFt1K3uI*vyc9IlAt^qvfc-m`&dF;#-kk_puYoKDMV6S;ayeR8I;UBlN$3w$ zLXM1*R)wT6fv9UhBTCfS;)8iyhizX4Kk8N^ly^FZ2^doX-WR)jW9d8mvtLZw^a>=Q zZ>W7(bt|n&evr^1$4h*-P-VEK%=;G%b7UO43@!_pri6FBp~ztD zIkuV;R2jFp!W+@~ho+Oq?E3x867l3nN@dlbVYuV(m=GJ(dUPIkDOm>~~tCNK%SU5VoqW!n+Cn97$ZvICT;1~q6* z#7bMyd{HVgPp7|cYmEZR>>u+31GhapTU-5Q3V=8WwvGHWNi5c|<}yy%{BPuSFeb1E z^Td49q_IOAlw#X`Z)@BRCT&sj6RKuyNeg(sfD0rKGcqNRkT98@2y&=Kb`~{3` z?7`tl_cGvEW0rlSzFR-*q%E$Z2gNJ>$MU_e6v3EZhE5j=i;4<_S?4L`JW-*-5-r!$A7Y+_(s)@Mw~V0}N{JQD9Fd&k zc;G`W{yrHni#^*J zGW*hqm4-%o>yHxHXk4Z&kiTfE8?o^$@G~5Oy9@Cz?aiu`>)MIXnkz;W+zIXb$smeC zj!eXZ*u+aQpY{6T_X%6v$}=S@2PYRquLsYNv6D^j4sOSa19P}7UaZ+wqn!!1?=^HU zzaDqjdxO$P{&qjjJ%JN3nO++21=)_K6&1tWK|rJff&LAkE4a^oZOqAvqM;aK)h|7 z$VZ8}+3p;bAy{)(MGO5kqNn7j;pd;O0ZAVVO-bvlB)AAucj$`Nt?aY=6N^5IHY#|X zm<|H5SBVUtRd2jH^g2hxb5bmZS!V!i;K4P43+xIwBGnH z3Fh)8OTmXz)U9eQ_;^8y>|!Z*NB?g!yx8+$mm#Jc|UiGUmv|l(A zokp3K@%DE^B9<4<1!>Jy#xOfHCv|1hnoRFlP@z9&%R`!Ts0hl=}FVv{2i49pklSiY}F=Bax-&)q&?%%2MH#@KA8Ty=(jyn;k7}K|FlFx~h z%+>#XikN;#>yKL{c+DKE~4JV#D2D^@wx0fhA2v@Q#&w*J-#}nT%v9nZ91C#GKCRw*-p%41>-# zZcB1h++dhXxT6DK(Dx!fUL!rZyotHW3xBMwn8Yvm2#@bCN;F+Xkl==4JO~(Bp=k^L zB|yuRMg2fJJlg%J6q~>#flEt~Oz_;vG6{oI|IN}h_!KOQ??DdxE)%iYJo zU$lS2+2mEJEJpEZ(<DUwORQkX)?1#4vIynyN+0 z9RvTov_0_X>SuX`6*pdhZ;<0|D8pyFxwFrIG`S@+PTFhD@9r;VN(1M`<|Q7t&dbLy z+Jt#Dq}oJ8mjuAAWwTz*X691|&>iT{y>ZKprC8gk1D7x6FS#oc@^7u%0=&Eo++OIF zQ+SeEno31?RYb(!G8xUO_G>w^l#_o?t35Y$+ic*f{Mx#5vo;;w7+7BNJI$Ff@G(8U zt)%p1Yt)&vX}_DWa-BK;D3GhPG&G~bQOUB>L@X|AMd5fliTkS5H;Y*=TBK5Yp43X$ zMwBAb;6kRq8M7sBZ2F`L_Ht&K75MabN(7R*r{hW~Dj>i9)PUK1emyKSWL)@B>hUu0 z;X3eW@vvI4O6C$ft4^?0uk@YL=Ky}6Cc}J8*k@C} zy$_G;_ls*Q;jq+zry7ivN0_<^Sp!9vinv$m;+W-knvj>K zp>h+*`pd<6ZxSGA?s`}8J0?b2RE2%M*bwY;mplxz52jm_H|~~s=@S?dbTH@%4GM_R zdjb0Rme*Wo9Fej4Wu_DQn8N)M_UOIw=E=QrmKZFBQK!})r3HVAE>UrBm& z$?)rH2bl3;?RNHXY2Hqqm(k7b;zil6ns#6LuzcLIzfA`#wTWBn^z^tc zstDP)ZeS{j)I_Na5-z16m1}`ghU(q7dCipfP!LO?aq3i}A`zU)YO$}r9{)Jjh45`j z{)yT*93^Fj6yNhr+Q+Z6Nbvq|DZ-XPwg){4vGOdO1`pz^OF|4qisuLM*p1^2Bxx|O zS20UaA{s2NQ;)f;arw{RN%}vPr-X==qkLU;x-633w7MdM29QsUeK{`DD3F%$oBT)) z+Ivaj6{+$1Amj(CKr6M=`k-RD_ATZH^R9}MpSGC%6$NKelv=A|OM|@lH_~UzdxS0! zsn1t_tU8~s7CwY}QAARMRBFhZyyiV0v>wDS2rpzYL8L1Y1#e@o)9+Iydkya>!!p>n zY-aJDmxVLP`8b2IOS0_2H2em;m!h39Cw0N`z8{Sw((=YV5R~9>afs)Ixh{L`O?Rx{ zD7*y;ML9IjhYP>`LCM<;J59)u^5d$n$L0L;uf#@cnUmU94R9q{U#MZG}58(m&s zgUpkiUT#%h9Bd$lOh7Da(7eujc6wtYE|pBrT!%7|?zDV^9XOf{asWxryx{yhAW8)0 zEBE4XGNUEaT$`}uP)l1&&-Hh)Oaa-ZB*v~d`fgQ|OgI;oXwUWne{Z22g`D6v#u!B# z8v;+X;EvcGU=Oe_jPoN9u2D%}bK8h&dO2btf7`)Jp{hgKg(eOVFC?~yI{#v8yrl@t zA}W+mV$VXw(NR49DaWG%TB(HgnipH?7f0?*43x~bUAAQ>nKwgij$&z#9q96-I9OVS z63$)mKrSJ|>23&P(;NCHCL$J89~)jZ7LfE1*-d#quTyQx!*w3~DOi+fq5HtT6;ts0 z^-tB8W%i0kgSokzNp2UPv~s1^9d+)%^|C8=1rV_qTy^1FX+FO5Tue>f%%u>NGq|^5 zOnzWJHXC?ceDdMm$Cah1WpkpvQI^~wh!usHbtZ*Bq3FIoHazb^Awdg;@${g(2Dox< z#5-^;69f9i+QbRYbw%;Ry{z9+vrejsobUC$!fdoLdB4G=>>2k@`F5jvU#5^fA*x~_ z6#*O%ejMvG`FfgyrTyWqw4U{N`J@AdAjCQ2Z2MJPF;aO-r~GNfk{z+4gQM!@>%yK! z2QY*6sJf}#d#kA%!DZqZr&uPj8rr@!fXKg444bJlzR!EHdeHuMO+XZG6w^bo&epeP zv$$%b5WA5mH`2X}X7xb*$Yr?eDEQ;e@pfd(KWNxi79Pp=>)a-)n~XH=YfxO@NOy zArCJc3$TG1Z1c@}cIbl~u9jXRw?XLzSxBFgT7=r`C8y?>Un3)v$++Zpm)3Xz`0%hj zE63|GX{S$Ee+y$JGlE~A<;aB3@Dc>o0q_C%nteG{(A=HE-@;bGHVdi?+t0W45L1g<4%p+g3qc{ z^m^;W(Iz|;1^-eQMwzBp%6f+B*cfo(8ngMU9*_Nqmgf|KIH-PariqK_i%@WXh2j_w zIH%U|FBaMl*woRYBu~2DxQI3GWGvRS$oYW5gf-bCKKW#E^&O)`n4?jYE3@rFSE*E( zYGb;inhgRkW(~bVlCdLGse)XbtRlX*OHV{};O1xX#gXy>0;6mMqZDX}vhWYSz)$Gj zFhqIhmASkRzPJ~j48fccGDHEY9BYt!ibg2;qgE2!s%Ew_$S1wOP1kjDfO*U8tmAHA z?ydT?Gd~k#*dt|)vt$|#^zm;*o(4W+`oUXqh$-$hXz?;h#zm$aYwD@C3S zLs$!lRIi!d5g!Vw6FhtZW=#;PL4POUGYL;yQ{tW*+$yGY_+q9-r;d6)5ItOQiLk(Q z$kb0wZ|aQRNskZJ`#NYW#hKSSZylIp-j zenHdn&vSVu2lkVr1Z-{nz%n`#esN3Hch-<6dUREYz~3fj zhmL7`+YQ~*r?+|vrriP-&s{TMjmOR9$=p%kS;FPy(=6hr3d1QR+kq5Rb%YUmkyMfA zbv_)AVrEH5r$hBPzEHFfZTeikhOLYB@6o=u{mxTT26f_~3I&@(8j6$dL6!nt!!MFN zW2|FE(+88sr>7HdL8IyrX8j6P;4bm*#J+$umTM@|dA@{OVMSu2+ezj->LzK%ksWF8ZcypEafQKyU!R^9o zP*bfu%M%^GtrELt8Bltqxx|_d&D}OcsjR08NEng4ccHo(C%mt)U9J-+*yE%|wNe41 z_{e^}{i?{bEC=ECuO|PqiTa+@sg1)yWa{yF0k(P9N7hYH&&EJKZx~899{;?eMYi?Y z!mCk{)5EmGl0b=C`wkH1yq!A>Ir~~v25zOo3UUDE>iNxWAs%XqQ`M)!vcR)8sV-DN zGZ!l@!3=kFdSOSjWJ%Y}qWS$8MhW{Se{8+jEnJ_ed4U4e}-X$ zW;0L!_o>{=^>d_7ox~=Ml7wHJ>Le6M$5!1*Cb|TA-qaP_x{|o6?A6RCq{ohVo)t0| zegk56=BJAadOfwT0$mRYva1r)lm(;^i+K*O5fl}wt78)(wqH!-OvxzkbNnRGGg`=k z%Np}>FxNPeB6@0x?C`YTr)9^Esx$OsvE_r=Oa{C7!b5ot*-Y+b=X-0mLdQ7ZI^koy zs+Ho7ZX^;4_MY{vg{#h=Tbhi$+_HzsAKxSPQNXJ-Q;n>l+N5q(i(% zBlHA%29htpTEjBS>MgJzq3LCuGrqs@`40|)4t{qmq%JOKA*WsEhzIB6CXK{rKM9k> zCrbMC1U(Z&a|O6^OxBjX5l9CfyGuhZ-=wS@3)-_Z-*(CTnH`^-S^w5zk8a!^W)<_*U&_PK-k)G zIYsWlsyDtoi_^rii5Zbk4mH8--m&&yd$MlobATPTfr$x$4X{jQDeP}1U3Zaoxd z_EPpl|!x5v=eDFdqnR+bzCQDL#7>hMB2n_se4&i^jh51ua$4puj(#){mfP%h;dimRU6wg_!3M z{ME$pTWL9fgZ{ha*lU+O88|~UpLBA+#k#{UdH^keb}|UtY~buocRjJ5{YCg059esd z86M}T(>{^WKXDn42uLTEwN5!j1Ni!H7N6|Hm!8 z)b73!-%4;e?N2u>)%W=F)7gBior|B+b*6RF8oy??-P#kxs#A~ zF&BcYhg20x15K86zb_-qsyj*w^uXRU05IE-I|`WVK52%ja@*>W(77N5DsK zU&+uW+>d}t^eW5gIW3A0Dcj?aiC3s;5K_;!i1oS86R2Agsm$8efM9+#5|NNC5Fyb+ z8#E`y{AUnXGFRDFx~ho``dUhCCeL2la4Z||M-n$9ivjnje9KOYcJVu(r7QZ}?4CyI z!93j$Fk*gl!6#PSd^BC3drPT@V74SqMt!boc9UOY4A`BImBBIjAr>+AqTqr?_2NNN zqTbw|&CyNoX-@rpXUTSHg_35p-rKQfb~e}Rbr9G$MMeqWy(ab=JgvP+*gW(Qub{8O zn6y^wCV43bP6acBY>p+X>`oJL&{=Wjnp`UiayYWdcrRv)U*K>inUM3O0=K^c)A2Kb ztE9)5cCvv|04Qw}_?Ka$3hgrk9(^Mlr&o2z0*w8SpQe2vaST<+&Ks-)DqwhTHS+YY zm!_}|Ze0s;@cuud?2J&RohA7H3`P8Z4b?Q)_SaCngnr7}eRxt4e+Ei|3LkbK$P^=O zP3NC^A18Od;Rs&&Y>fYH@q>q9exFvFJB>m9r;)nW{F-jQoed_$%FN4^ACWwRJnTGv z13B@O8R5ETHgs7riJ~5hjCsci#zioxr{fZYRO&*{R_7bP@p})nk!#oYsr4B1EPk3| zDIT?UbLAQD(4K4c`Lt;$>R1r;-ogSXDvpV-baXGypU!E#l|ZOSdVi)X?zIu06P zY(7q*)EkkxdkVw9^2iT;Ty>^sTe2n7kPL}b2yaU*3yKg26WB zpyk=N0IFIRK$5#^GI*j=x)JoP>wkbM@jsw?ktv~H{o48`66j?)i%7FKb<<$fwAy~T z!~DNg)vy6QvQQ~V+{BCpEo#;%s?(W;D37!;7qtf;SS+v3MB`_MKU271{}pfoD@zh$ zKea63sgWM|_q*hO`ahuc@k%Kz*|V65RYd~)9v3?M+n90X9dPBS3SJ;8u&%eTiKWg% zbx>s3icicvATczacgX#KIk7Sz+wmNrb1ON(s6T_ou!zMs8O!&=&2F+9suiZIKwz#o z#(#HTwgY!y3_AQ(a62}7%`!A}U3@32pS;yRx;)6Jgya9XrwZjPRmh8rT}g>)=eJdWLm>H(zAb6XlIFQuk6|#s zdD(81ApQ%rQ5wr4DwwdOcFnN5qCl@rD~0C?i#sY}#!{)+AG4rLk_dlZ$2|S`NE4Wd zRbE@=#q5)8M=#oxAVP7I1+6$?xim_yML9fKr+{o7Y*UI+#-X2`_X<5j^TE@ZXTI!K z9@(IN(#tB2?{6kp+zyz3ov=0-MLO(WPEm9AJ`VRz zu5XMAF(^GAe+&fP1poe`#K+Srv6ufpP@?{}xhGWimKm#9HI2%Tpyqep3f~ z7GpC96LS_1dq;3I0)n6iKls(&+{Ku}!`=?!%X=A;4dL6D;F0>epXg@cXt+dP8J6zOICJ1 zK0a194pt5hW^e?vvnRyG*n=73O#Sys{^vZB=FX;0){ZXL4iF0Xd5ujRTwR2ysNi4p zpMQUk)5Y52f4>Rh{Lf{93uJ}A!^+OW#`>RggO3WrU*(r@uy=GacXkHn7v>iHedM** z{`Y(SK3>Jj!Nmd0f|IqW9K^-k34GYa7(O~-j(oxs;u(&B0aw{xthPpZ@c; ze~uSqgwj*UFk0z6$0P!RD1w}%n1;u#jV4c+(#ZMMRS{Ph zhcx4N3M%W2D6|-^ybul9Jof-R^KiT-ntleggvi@!Uy>+e(la8d@S@Bxqz=5BHjTzC zg^q+Rr&x!fxj60G$B>$c@Q3_|I9Z9orZfdcAkKq-a|MY*E z7Q}e!S9Da)g+!?Tnf(8Z`j#N}uiO9SBD+b!JMpIdvj1h&;MVZ|-&^zViT=O0=Kt=R z|KE2FayhoxrxzDfzQ+drKGVL(WWqitx9*TezH?|vil+LUK^Mq0*HdWGP8hQKX9S@r zLMmsaHR%c`6Y@SvDqZJ4nF%;X;+4F*yimCC=C=O+P*Ws?w7(iMc{XjV5bBxV*&mqf zQ@ZlaFaGXjp6|om5m=q`a)ij`W2Xh#i(k5xR^O^@$Bf&8`V)B_&W|?pl5s|_g&99Z zobKkhkZ+kXtVYc+QyQaOy!cseFH6J5Ps>zhgL;?JSb}R&m16Gq z6@Gqoi+m_ELb%>-a~ylQ_drdYVk+b1ZUb;pMpZ$LC%_{BcR~9LC zv{>aU<9RIlwSyIfB`bC0{&zT~Da^Q=__~#&o)r$A9j=4RxE#?o61q4TB1#nb>(Bdw zfapmVY0i#)hIDp3{c^qPE*^cF^StNFmyaY<5#EqraLfAy%@)+z{VYGN>;34 z5B6@zRka#NlTXD8rpT|-%`ycjeH6}P9m|JZBX%SCF^N$hRWqean$OlQ&$qXmR*4#l zdF;mp!Fs_II*1&jPh{4s{pFkmhTS z`=t7n9g-agE=ZHGyD>%IQK)Y5HkdY z2!whHqBmfz+UmiNgn#WwROG!v8TN?(|LVOC zYln$yC9K5&l%@P%d%dh-Uu46ynkf5^=lo=494n~Q@j^rv_Cx3;b2G{_w_yc zX?d1Zu;bLPLbnhjCORU=TE9Yt>}ypmr^C6(70-RXi`3-bD5ajQsOpQ_#i@6rV&EbKBs%T_r7CX!(L_~uPzCFwDg@G zct}DEI3CZ|yRH+L4eic+Fc`>cdH`S@{61X@~DE^vCDlG0|bD}2EBA&y%(BQ%BA!jKIYNkv? zwIQxr`59j;w71hDBkUuwL32exN%Ocq&geR-Q`N!7PiGL6dA?#%M%;zUyqan^DbDWK zp<&xVcBMCM3ql{or|QlzRbFM~%4IN0XOLAP3QxmezP|W^P|doC?)X}a#X363Jnv<$ zsnvTES1$ZYJ5NnYZ#u5YaQ@fn{#X9>!gPt?pcM~;vk!W8PK%cp=MF#1X(V;LTa|@R z7aI2m^h%$Jg(Qq+Icz&Ye}M(D%XJuNw7kHiRaon?`rU)S<%laD9b=)oJKcHQItQKf zvE%YUGNgg&<>l$}9)Zx=O6Hkw!zQIu`5ST*MvJjt)=!0{h!^)vj&x1=wWi+&qh#cl zk|Eo+XEJV!f{R}KrFJ40Lgt!f?T3T3($CyAQu&y0m8tN{vRKHm|44y$P4UM|wG9TO z>v4V`sk{vr!~vITadzz4I2V2W;68&Azn*h4=RWq9Ox*9*u-Y=Ut=>?qui3)y3#8LZ z+&^6$G3ixrgwmztp3>vBZfd^v1c6kmSbvqLzP$NK0Z)2(ZG+gnn#yp~FO0DFgEf#L zZL{VlHY*dgRqeRn&3L99M~f4Xxow7b_>7SBjdz4O##P-{2Gb21+>Y#OBZo7B6VZ-( zKBZBFN4g%9%bl*>vBas6%3)iec}}Jo_IA+WDV?9s+4!uUkb+UHdtx@SJXV?#L)VCl z5H^93aYPqxIM`L|PRviKZ$v67M56GP?heQCnAKeWt|xa0OmrQUEo%il&FSNK>=T4C zGI9BxN~EXd!42wg(KnuI^hCZ(z-7*Nw=zmAlV>D5`xoBXd|Ta=?o`Gyz)i~ z2}OoByK^1cw3vZ5MD-2}kk$F*0oh=kF^j$-BF2W5=Bg8`>4J)TUdT=1q(VJ|&jHS; z+mF4RX$jagpGT#9Q@n#_Z`-8MX^$98>eT(I|LXixNz+jR(J>iIWU8!s)g2mo&)2Pn z2W=-?4ci-e!^JPc*(QgGT-Szov$b!kbqo(LpMjM3V}-W;K=R`kyOX8Ul24aTpA3u~Tad$MLQYxs zWNN32rPNf(*}03@qoiW@UZdi^?VAmL0Esw9nwB1Sm8!BCSqpyfVn?5@IY+JGY?h6? zay@j%P`-us%;3~wagy75r=Itn^`xOx4b`9 z0L<`<2bGU#ZQvi(h$tS4nID}v(SyeRAQ<{?B(e}TYx9YTpm~V_K9jzsWM5MfzR@5O zz#W7Tl?l2DJccMk#l`jFh5?s_OBE|$MQ+1h1irK{FtIOk0JWPDV8pkc! zWOxhWPMHb^nj*RL)ep;8zJCp*6Tf`qFwUsX0f3*oN@~*h2xf3zx^D`~#cL>2J1@;~ zwe?VTrxBkFj79m$EMi?sNc*jED2|R&jYST}^YMyVf4viREJZqedJbJNvc>Ys>eI_HveQS_?8x|&c7thtu0qE!=lthZH8>hv z8h36LO7SR#Gt`beh&dI>%_Q^$0JG45Q)5%P;T;3abha!Xlt;xHxakO1K}%jV z`ps1xuZ<{lvCJLj8-zYA)c!_K>n(mpKri-+ClyIs9h>whWSy$AGtQsH_#tx1U`auq z4~b;bNjojp1x5&ZC1|)_n_^)B}8J;~E?n3Zv;V z{H78G@>}*6u&K1d?Xni>8mSSQMuVg=`U;4HAYT_=gf$0=gRo(PnkhZbRP>>kuuN)ookx7i zoBc;ijHbu#?`vt|;IBT@GcgkDq7HvN7f5Ira{8=PHT1T|66cStYWfF$lQu9?1Co?- z6W*R!){-Igqe5lbXq{>LX1(L5l2i~Bq2fW*z<2jZc=|ZAkcbY~M)u=J?6(Mntmr;U zmEC5H!^uc11{ZPS*gVBK@VIRZr_nVU!kC)Ha{No-j6jxC4k?Xy7FJ{t&aLz|NO|Z< zcpT!5I4Xu16&|X>&%aqBsYLSj8AHDZSJkAFkHnnC##q#z2hV&JQ>Pdo@?t=w-Yf_s z?{b!^6o&9@MhmT-YST)ZXYX)V$5 zXC4h^UvP%L(YZ%E{ssd`P3QQ-5P$ zNWISR!hyiX_Td6U{q?a;Dp1s?gn%gMHCm`c zp4{%NDKBC+O>R~cfH_K^>i=Hr1EXe-uHwl?#aAd)RTVSTgK2|7`{Z;vbrKOt&XdYV z6QASRC>a&KZ6=AGVl_=R9o5*Il;M?GA}asvz2-Bq#98J$WG*VT;vNoX1l=UOVz14jxw)=2y)k1&ddpLcw{pBmSl);V26Dx} z<4qdds@G`cmhh_58(qCU&6l9h>eVgUQjEm=-HT1L;6ajvuHN?=(Wy#Yq_|-l0#-xV z08x?ACt)$+;_S8bg>M1TGikXuD!hkSs>c5AslAK)4|6&;qa-+xa~HkjG7GRLASEV1@y7Z zn7wMP<17x1{Evzrq;#R`I!Y*?nx=?D)8XjHJ(W0aP5wb`qq~VUUq5AIQLUKCG*10J zoyDR!zW7GfcJb~7dr1HRpuT(5vzt%a6`#WB8CEi>u(_!;m4T+e;oqqh}sPkxw2 z{QMa#9*EX-Jmn2IiYG896?J30rBIZ-9wHyMU+=(1evDot^~NM6-75F_QeXV2CSYqwC$W~yOLKhu*H`az+d!p{8QL31 zitS*v&)FLqI{~OBQZ*>{rr$lmXxf{;GR!}l#fa*Xa#zZdd~&*$W2O~KB6o^NBV5Vl zT$W&&;+ZYbUNik-eX0|>c(nIrwDPtwHRMMT_3tn43d?P2f(Ic|VvTY4S+lpcje^2E(hT&Y*wtBo@A|~x94d~d0o{RYI4fw-~ z(LZNIA-!MdQtfI2ZlmL{fcJE(?eda{r$#bmFtvw1JaT`tmC)0W6h89xyzqT_W5PEgRU=YBjC$pPH-M?oOE)CXq+Fwqm7dRGyY zBp-#FmQS96=~I-Eqk?XKhG-TIU09`>rqv=+8G3JWdkba{W=Or^Fz55(#~0wDS#`+_=2C>F(Q8vsn@cwNsN%`jcLq zt;#zX^EfxHpp~#uTflUkm_M2xIt#_agyP_rf>CGK53ew9zt2q9sj#4YNwzs&9ukND5|rmk;(Lr89ZvnCu*0eH?RRfJ=6@HU&)N%Aw%sEO=osdQQv3B zE57U?G$Pe_60K9GZD)nO|E|E}UGx+FFL-Ex=c32tJ{xu^Ffa$KfuedVkdn6ZeSm3#)wt$FMig1^3C)8k8^{_e(3iAKuM6p&Ee z1?0n?8%7;Q2)#l5K~D^mx#anG@MAqBxLj}rrCy$d(a!j2<0tEBW3MTM6yZ}T^5A;ZUdb7Uy*Pep!t)pgGPqR4X@S+YmhnZ}D0yj+q z5zQg`C*@S(u(8rM>IW}QW>M*q()tsFhO(@4OIJ>I=VQoQ8Q-oc20KiwJ~4CxD?xg9 zlF>pk9V=GfDfC6j+INz?p1c90B=;##{{51IXD$8;A0B~{CcIg;;gFk{$IfHIz9zn^ z@lkzPs15Pa;;+Jrf%n?75NatTD;M@k)%W<71Pn$OX3LQ>7aGheYD3+xd%oF z7j>Qi828ykS9K?#0MPE>f;W zCfrnL=ks6WtoGfe(rE1!Go6wLn-gQl=Gk z*S>PBTruG!CzWtBWusnE$h-H$biUhp040XAsgvW*HpxyA%DUm)iR&5}eku`+q3NU4 z$Nlc-k1AX3Z6(vo^?f!RT-#>;nTQ5Ug ze5rl+`+4!*Bj@MOdh-1V`NPT8)lqk(!%6dGgX5YYnax+qDS|pi^D&p7?IY-z5XD>Y za*~|n1(9vXZbi5AT3B<~Mu#$V-}#su3}~kI4Of!GP(y1xO?Js!cSlo>rL@PtedHILTyV&HZpZ0Mn`gmm6@vK*~JK!?giBz{t{DIGo*zwk+^RVO%dKAwO#gnQs zPeu1mMEF4PGII3$$2Nb7fP_0TmLmL#vIX{C&^)W z3?}brt!$Z<{Tk`y@n_8yRR|Q5(bPGsJ&jzVwB?3Ji}Y+=S|aLTTOpyn zt9lph%aJ(!jgM5+aJ$cKyxP8?Q(C^hOjuwVj-Y#dr;b6d%7)uvN-#?Y_2@g|HL_1K z>9b*2OGr0fjQLEovhX)E4RST^U+)t}qzDbT+%}$WW82SYMz@krS6UB28PuVrqPs_E zj)NA5ai-y_zr_2lBdSF%u>iXNMfQzL}(?;{m6`@0*mOf9_a6GN{*hLF5V5Dludp z7vD74rPALkUzz2UGj`C`775%}Y}Y8i3k^{klIyZzJPs37>iL;|4{wjHCt5RI9NBzV zHof=DQpEKzY!)Q?S`gdQa~4Zk(y(vu(KW>T_n~<-Sk!xbRewE0zebP$qgUJq0#y*= z^`-IqEx$@!AYZ$+-@E>Wzqjx!plln{UoS`go_kpVa6W69K7>EF$^W~rqJO5Qbr;|= z7R`LtCRt;nyac-s@WtSASMmDkiV+ZUJ#{Finhi$|JL>q5pLI(-k?}Gf|LGy^go+-_h#3Pr4qR z!MT^JW!3IG8bfDjx+M=@{(8R99d-7l%IaH&?N~8@XFw;L0yqPg^5swS*q`}wwSXMl zWVpbuH2?^vg5`P<0IyXdk=e1;bDlKK29W>?I#qJzU~QxTydY*iF~Fc@GLo-3o6TeQ z;~77JKlY{Uu-9Spo*2EIByPjv2R+#81^5WiT7Cg#Yxrrsi*-$JG$;ryOFo(Ren=DF z(%ri?!qwCYP_lr_3Y+81=eWw_Np~Ypq7;w=v3}z8-1Fpe{pr;USkV1 zlhME%8;oKfDT5q0o$S!QX3`=h(6*$>?bpe!(N?|uhexaP+`eZAEC%&)B2bPR&; zN0it;=Y=G#uDtC8GOIy_g_5nlXb9 z44hfSsa|Ugr|J5s$vA-?o>b5@jW6=MqNjt;?_P^%W^tm!LfBigr=VyJ98~QTuHiNG z64^X(c{DxJ_i_(7tO{cp)wBDiywCSvtq3&MFNAi#bVxRE;Iinu-y!fLDJEJIf})vJ zMSxrHzWrjSQJf7>)3rkXWu^WS z80q^;v5OsWzUJ|@6ZWk`eczm=OAzUTTSaJH&8+lL<}e%;s9jo_Et zC258rc1dCa;DZ-KOww?$J1T#+@%9Qnf$Fc2v8*2A(ZEP{MBPB3R}TiMi%QnIix#8d zOIT0VR~?7gw69cl7zdMC5GaBm_L?pg$f%<$sFVi6N|2j7B1Y8Bo9f`_6_7Ib5jsGE{%aEog z;enW;anwS}4B)D2%YJtD+xv^Xu{9GRC?c09^XDM=ftqK*@bZEqH-=HG@W(fq2SqVB z&RLQpd?C<2eQ79>MxcVeZFOP3c)@h2tHH~QL)tFf1TJfn6qM&DpzH#Uj9K**0e1+1 zbi*Edpjvkw)q!jm>Qs=wI5DnD^4_XDy>xkhKG%IM7l|UFn$^&WKlpI9pL*5a?)6QI zP^Uroabl_oNFAI#-B6UffrBMrXi-#vJ@0;LGL(K&fKv5jcAQS38 zQ$^jUPuIBJ=2=(fb{!_a$-i|Wck+$%wj zsX!#O2PLkHNk>1;D&s@?UOqSxQV&7-%4E!-{qk&;j?=Xjl@5SLf3pSJhf-Hd5l9r}e-s1(nz| zE7ebu`ArHCad&Tzh|^1pTKJ}<1z_;YvSt`|^wz#P1cEw!h^UOXlly|?Dj&GKW1zqP z6x1BaF#7ZY(yR$c+5bvHaU?mMuW1V`KT|f=>Y&ci*5k9)RzSnNO7vY!-wC7kVwO)xqsE6j8ib$QDm1#T zk2*A;N>lDK=2Z$Cc(L>h}oGbL=E@~GM3!xOMK>&Y-HR~{twVZIqne$U4MoQ_Bx2b#Lv6 zaaURKuD{3}4bq9i7E?0vY3BJVmi)G=y>E_TS-cSyUcq0?uzrf*5W5}Y{4pPf)gx`a zP!;0O$}2VqO%mw&v~pVvCC!A(KOtkYd;}Glj1o29lexG}d`N{6E!EU zQdw~K^;ds)?>_dfINiPnPy2G9+-)iEDct20xruK?Ye|4#_9bhLB3xNMPlj$v>qKFI zyw=2f3rLmSqA?sM12UH9AP^Y<*FuSQFfkqx_VBQ_3sEJLA@RUWK)kgl0I{yX*7ekW zywVeZhkl}Tvhr~8c6AKvSZdK=F-5VTye@+hbR2~pNbj=x-WU={m#7-v%kp4D#)Sjd zJW*Ft38oQ%5Y`V-!}3*N!QAzMJVop&6xteq$I)zRRUfQ00zNRXYUr|?du*OItkahP zDKIO3$IF14{m}aL^=bi22~qV}GIuG%PhhtlS83{9D&mbjx==>2_91mgy z2VeZagBpkj;%|)NYD}w=qc^}FVG8&6O*76(#ACE}HSP5{o{09vbJAre zl&EU*L0`4KC~)%3g#p@C$=AkheTje_GyJd<7j_?ObtW|APMF(uT6qqMYBN-2Tton~ zMDpJ590teRr&TzIKc`7F1SSBK z7A^-JK8NYxF{nwN(n2da7R1IvBWvkeb5`p)#XB=@n9oIrQQ)$WDKq4@ygAle6oKBD z+|!41z~CoNH^8?{n)pd?`Wta31N~EKvAuk*^c7U^yCa8OeAq2Y`;__e8u8ugJ-stq zr@rjiTcj>78G!2y{XxoU@s@Vp0Fv-N?kl5}_y_I6r?ZDZ^0MiHGJF{3T%vB4RZd%So;_$4ssS>!0j zbI|5UMv{oHvO)catFlvrpUly}%$c|kHK6nV`Uy*;ngFPX?JhSN1<6!VJX0#X`woy0 zVBqr@ue7e4*vScKu1@geO_WLrDL3z>y=(?xiq|}=O;*r4>;0?ouwH*X%8`4WxcbWN z0Alo?mjY}Jw~VSYD05_(Jy;5IRR7Qnq#i~*oB2*W{oeynpSC@)PH3Z9z`m|y-a?U# z6vY$5W})4*n7r>@V_VbBpB#1kd~ra9;BDi?msiM(Mv6Yr08HT@nuc{wS>7a6=RO3% zWNuGG%1adTh+qVq<_r&;3{w-%Wy#HBkT2E>N;Fv8HejC1$q+s@Ti$d)U(%LBF1Q)${6eN+Ci5j{WYv#a`S8vgzo>PIJa6j4o@!^mMI&kXx&M-7Opu5D!OC=WwoX zazf)vepfmtalTD@YcafLvecTr&!3q-6XKBxQa0r5VAZfBQHwLBc}y5kZR)a&S{*n2 zt`u0vh^hyYE?cluzdPhWjTX3l@BR$?+Gx=PKx=EXzA%FgfTO#qBlLl?$9v2E@%nIH zMFVZL-}4wx%3W3c8mZaVK;Pz9SOP!-(TsbQ?DM=rJ4kTRwN8i8^_f_rPl>?`tWz)O z*>Ho>npC5V)2|v(x5EvoIoY~7pyWIvH1e69#CRa=voq7uQ%^h;vMv+(QtTE-=tpEA zl@#kZ|H~|e;8#e2^=*g!b5LPrl11yk5@?2g6$X z?g&D5d1AvXFg8_ZQZ-u|i1@!(s)hL^Y!wL*jrkV@+`CrW{j>LXJ_EnWWHbJ{t+S*P zQ!VhwI6t-8W87Ua5OCWFxnyliOh?OOJT{}M7`e-c^_k0n^%Zvz3gkl%5O8RkM>#DA zxGIXVxyjvO&qv{A?dUdSvf9Vn(+bbgccB5HbO&uK<5dN#>u-Y;e-<$%c`n6oZT=$> z;s?#s&K&53oT6Ogx8~Doxcjm@k`fpd-x71LReTlZ zL>>zvo_ze~OEewRa)@;&E^w9eoA=a`p1#NbKjDK)qVQo^3mddC=x1v0i>AcD}~q96tMZ0Q$%A8GRvpb>(5Awn5# zC?MQ9gxrzLlYp{0wfG|+g*=f4uIjbg*QBJ`JDnsBjc02GZX|dIKr(oG4ysW*9lAnx z8L{U?PML8DHg1(RBRvqMT5yokhfJ|2xJ^-2CrC7M&zNZ!GyOUF!2rB2XcQ@!fHsXEr~jfPSOM zjYrWuwhB;Oo&Tg}aa*Yf81Gx}vbxYQEDr!$$Ugy1SG5jTdmmKE==ph4QxDvg?&336 zI?jrxT7;L(toE8`%U5ot!99szp%4I;X4YU%z06_ByLqcJhrl6FXH)*@T(}Bc=o+|C z-qsrzs)Tq&D804i7TJcnqFyIX$x^p~!<^4`xY`VqB>tD_lELE&>x@rJ)5>)LmXa~X zfQ$qMp_6p>n%ozj+gby51h|06efE}!lTo`E2JWEO;Tl7iU-hxm9r}HL3~I$x;mKq} zxPa)bTf-|!ph=dNYtT>)<4sBMSAsgsjoX%JQiQU2`ao0F#ssz-O=(23^6r3@TR=)G zHV+a~oD*+t=)*isuV*f>N&`ZCR!yp-L56r>P=Ze#la;ZB^>XR}2wYafa{QibmJ?It#CfY0cV*J{O4 z9J{GYb{gnFVMqURCw+1Wkm`3Z$qRMwvAa@Dd3?Tg=N=iq%i3@rzY*3xlH}iVUN}fc z-?b$<$F{Rb(Z?%oRivV7J6QYD=HwSj2cO^ObH`D9r_ z!wQpkd}9G}y zV%_e65f~>8lfIb2cTUFxo@BBFl|nx%LbziZwRr5a??cmaG$?)M8t}L;VnzKWTU-Db zH+p+Z#XBkq7Br8j9?i=qrAJd+6C+n#qrN_;D83TJX3)EEYAA-rzxhZh#iZ}GniZd< z3%t>Al^-vh-o1^Xp%`ymF@_;fm0nU@JOAEN>i`f6n{t5NF7Zv5C(?}t7LH^h8L)iP zx20ApkJMm``1Y=!kFsbwmY0mC$j>H>D4(zKgnw3rU=S2*DJV7M3!U#sju*1#CMhE! zh>X;0=IeIfG6J7WfAoI5A#sf|^``a9^`-)G#qMrqV+1WVP;~NW20=3hJjM_Oqh*2! z$y3!?c0)A#40oT-j(&t|2Gw>yP1I!o1W=QG26P+17T2ixJppv)serl_k@~f1LO{|- zR4qwPXRQQX6TeZJQaym+VI-_-A44Dj_GO+SZuL@tG1k$hwPWQgWm0A9=t6kLSO)^G zok%nY6^wwA)Oo3o50G{TRinWcfMoQkJ7k|F03R*|tc2o+yZwiPo>PB5}ii^S3NFjye-6D`0Z_hIj!l=KeqZDm*r8Ogy)ZA7!BH zhDPDTM%9=h(8QRvi}eLj;LRR@o{VMI(<*(|0=60+lh&gY+D%j(E1-Ww%GZhdA%Z&v zJdu~6Zz*x6;)WX%>y_vc&X09Ku6~*Nx$LzB+K(X9%@^&VxS*E}A`M!2j({LHUPRXj zaJ)^D$jj!t@G{#jQAk+8eGAHD30fS0ZOQ<2AObpG6=p0_{QVzTGdsoT`~m%F-%(7 z?SimnO6a5c{!%}PyZ0YE&H&SZf5{`>cJo++Be?#=V!;1{q+$_NVW5ReC4yYY{!fMMd7%o$o7_fQ7L@Bky~fSbzzJkll|`5S7%TH>R4-r@XJOK1p;)K6+Y8ku7`^ z*i%G`plm#659ZV}{-0+7V3P^BX?USH?1J90R6tC_I|%_74+A`LzET788c=>m0Dsz` z>4jpy1YB)8V0t^=5XRE0w$=kmLZ%d8D+jMIBiUKOJNNE80<%4>i6dYv0poN9ir|zN zXV$4NfZWsc0T8^vjHmkzw_fvUm>?dD=d}7wmhzQoHSUjjL2M8S%DB~s+kmm(w5E6m zbfonX%~sK8&4J8ZpjoiQxKURA-E9io*RAz8iokm~Abf6zDgs;^08t48u?3U>@!^g4 zw2geLemx$Y!!2$)(*25?egtm%=vXiFHS`KL5eN?6ALzuVr$E4S*ZTzUef$P4G~>H2 z9=jh&s}+F7HUO2lF$Mik_hVI3**bXX-FF}1gFy+y%z@TiwNBn?ALkjrJL3VxGt!y# zr&$w$H%dNm&)zA{8jafd>GMt#cv~mVAPv=Li#Fm_c{dbzb1jy8iS%~DX7#9 zwdXSJBj#vEIWfHLQCKXLv53ZS4pm+BW)$!%q%F1dq{1;`%K{CTrB9~jh^+HsW+R@b#WvEcV0tAVAzR5pq#;aQ$q zRyf~M9N@=fQG{`(sHg!Tiv!hK7(NqpR@__wv=xLnE%tB&3f^!Ejb{88sG9^UKg{QMBQb6d#WjQ@%m2XP@?ZjZAdqvNqdhG&3l6Mp0B`6($ z@qj9YWAj>)0@1rv25az=ZEg$z`UD>P{IJB`QHeW93vW+AMQzgD4Rojn+bysvUO))& z4(d5Hxq?p9pr`7G?+S|R*MKu=SLpus5$KYvJzYwa7vkryru~hDK4#fK9FW;KGzp>@ zM$PXvzGVK64GTJh)`7r`U-wQ}2btDpICnM$J<31K2$Zp`jFO}LKwT>~5J(L?m;|i{ zAkRE}9I2N+R24Zh|L=?JoaRwqlg+6P4qEnwyyf3eGX_d%Ymd*fNquI z*jm(wmh?wRHE{L-t5tf(7FY?MeMZ>UiYElzn~A|Fgqe2bVFze)YnlKn&qjZ}yyy@( z9^h6LThP*M_BBOleXN94`Sn=tsTG8uJi`Ukuor|**UhRi#^@z+ zy*Uq|gJQQ(oFwcf>oxP}rvNbyxQg$M%n6@C${4SGZo17NW%(52ZXKtc7igrDO%?X- zbLy*493@e9yg&irHzH@rHg=+07bZwH8ghfuML_f>@UtT4_*wfrb91?h9F5Bgq)}j> z38)xJ`2~8>+~$#8Y$yx|P!tSSf8NurAn2g|v7H9m;zzg8D(LEJ`yV1y+-5vg-GmO@pu2GS6jZ|z6mP6Q_FGHi4)6%ZV*JXJnvlFHM|JQRfyRZDN}29nIz) z$fA`>JnH6`qA`4^r`YU$;MX`H<^`QeWthdfYUOJtRxS;fK*~mXFkNk*$bBYAI7*JU zK>wV?fO@pp^ig#!zd3bmwCfEm5nvLqf~1sRo7{pe-0evmZ1miB+(J{XUCDT(C_^XU zlSfd<6xa${>iQGTHK&OUW_y@QDkq*f;dysV+O(=xaFBx4Tfa!6tXX^;;g$O^Xf}&vI}85NmwCHsiQgRGLv9)ORz`6N}yktPoEwK@QUem^7WeK81&@yQ@X@(QB1f(%+x z9ynu8j?w>$D6l$)uAIqcP&ofC zCa&GLf955zU(DNyCSMWB3+(RokpjyEqL9$Xi%@mD$7J-mT15#a?9i<5#0Fxi7p@sC z4H%%^vPPjiaflL|_cgid%4@VYamk>yGCt{6i<-sK;}6Q}L*b_(=c9W z@d4O0+xfd39m9weRqQ(2^Yr+_6v86 zqv|QQE1;=tjNv3pzQ1bD6S9$$x0d=btgrw~h;*n&1k!{i%flaW;{;=&GyANs?=1|u zysK5t^97YxzTcXsBj^&zY-8oN9VHcc(O3}lFEsJ<3564QLIh4TkZs!52&yYPJ^a^0 z2N^;EwcatLsCiBktG@@5Q|AF$Ze!S!5c*q^0LCc9FlL<*ckw7)-N5dDXCC;N5re3jm2NiyVw6B~`Wcj^0hAdjUTb*} zg^)+)HDi`NoAPyqX7Tlc5p5tUi+~9KRrp*nhQsGCW}Wf{z?u1iXDVdb89?ZRi8Z4t zL0i~pVr`l;)@+psE-${idKV3-2De=MQ$<9wpOiiag~aI7YCE0i0DrrDymgQ%npiZ& z<$P8HMy+o8JpHV2LXzk*fK~DA2y%x2mW0iR<5Ho)*g9QQ9zS2fpgPFwliWH;V|!LA z;=ZNtNt5Ee#aHt=2ZOAeh~2dFUT;aB8nHZIncYtg2Morqd5t&>@ya&uYF{9o5-zyO zJ+u(RvLh}=wmm?atp^f)A9{L53P?Ma67fo+Xx@~~%vk*<>$D?e`uo-LCavdIF)ap?ta(68t<^(H z=1{?E2TxIe6;*{h)J8uS!UjI5OyK5u^OovSqbwm!|s3agrNNX7l2j3$ysiADmRn3GZwBYGekH(1` z17%3QXU2e%#EwKYlsN{W6=IMRN<8^9KQ2p8sGS@YttYB(`;#^(%>2>v?DMpW#zDns z%rot^&J3Vnak#%EOVh!3kAz~QOc0!Qhp#nw!RvTGJ34q);Q3+R17$AKHnvf}n~Sub4~;8XjTI*+D$FI=7MHQ4QmDJtOU9^7#-DT5 z{n@qtdpZm0(hOXXST_}12|foFu&yzFCrVlaNDEHaXsdhg0Rt>D_sAabDaL273AYdL z5?9gj_5)F~Yq$}3d=+K<8|lVBbPYpEr1F>y7J-5{m6NBf6{IkvY9$J)&fyjHp`xQr z0BAXtP{nN3_7@XXs>0J4Y#9LJwpD>(ukWFP$NM;PJJc-_NLik^aBX&xd-rLor(<0 zrdGaM1Ny@Z5GX9g=fAXC&cf2xUZkrWY*c_{wd=;aeFiyMM zZ=)UnIV@m(fl?uhp91)fw1qq1UT{DY?z*UfR7KA( zksk7vbm(dAeiV}#DABUyW1L8o$btCzj&~||vbpN@-94|j4@3+H0^@9)ptU-JN;2jg za5r`h4$iTptPT+ssKtn#3_GhH=>(eto9mXXi^HyDEm!0X*U*9kqG}(maXbF`4p2hQ zF~pvhDA&$~G2yXd%?0hO<}7GgN>?(&!My({=z`35O4)^Baj1cZQ8h_{ZR5n1yMkF~ zE&cXB_E&VuyWlRW(@TbDzt>c;zZ)n>uSnf>C&u#t7z9wpiSS#@U z)1z@)e&4i+E3`e#jIlfFoD{aFVVn>hpnhVS)iUE6K-Gpo)r)c7g5JkrE8)o7t)8G3D=q-+zam7d{iEg5A z6_(1$-Ge^<&Vx)fHY4&k3Xq!IP6eDWS?8h)rjk9g_s%9{XJ(76lB{fz8M2ct zTSoRsW_Ctq$Vx=WmXYy2uI~4Jzwi5ge}4b`9uI$bq;g*8`8r>(<9Mz^<6e9umY!dv z*YWdrSO58+pkca!gJbj7_z7|fkr{ZAZ03Z>{KCKMmnZ>1IfjBr-(wM>B1n{Lpi2Hf zirZ6EaN=39&CiBtmdNq!ui7}==>G$IblD;-ih5TTKg@Tlf#4QOD}YJJMP2WM#-Kq^ z!yA8ePH4M}uHvBmrO3apHGq&AfASU+a!78jq<%dKbPEGO08c-XIe7W+*N8fuV9r@y z+~DJV?^N&o>HV@!N;~*n)s5$Ng8sc5wPE5DMPFvB)Kr;+GeiRFa0_j56q66&Mhz^z z0!pOkzi$b?Fk&GzWGaS>=HF`;njgmI@8MqWYXA3UOHwrGGTyu~VXhe5XeReJ^jkqL)wIYeYggO`FZ(@I16b?pbdPYB;oqy9U<)y}CaI z4&)r$DZ}w@j4(=ymvwXLlQ$h}l3-PNf;6%r(Gc=%P!9PYMWz2Fi!8z=f40lwT;^)d za@rldSwY@Cj%T@liTL4Q)4_b$*RuwodQ+xF73I~UB;SqV_)eu^>t>Pt}Fx!&(?en&_NpGl!C3hMy6%20q?1xq+V~&P(ZGqo_zW6n^t2)(+zeVvvSfP!?E?EMBU6f0DA2 z7ciIW-7d&?cgk4AGR>pVF_3D%Yp>lMx^*joBQ=sNNOuQwAz9A%;7jmE{f78Pj#3nGIzfw>W_%LYR;RKOX**2SDL;{fEOD+O^H^)T_}_V?Wcg(+ZJMul%c+h zR25!~1EAgNzR-m~Uoh(TNa$`*IfIsNACE;01>9-bET^G3M9E;6J~4c0FhsDuy+!wV z@tW?=3j_N^J{!&ICdQrFyI5VggOJE{AGm%Hl8~@y)qYE|BbHBI%PfJMrtwm<@;a|i zIjP#9B@6rUwjX!`V3uKt#3)XTlpR9zcOPC#Y^6^)fB zYWQ*TD!LL@^UK2T$!0wdnN(Aj7i;&}SO*^gJ9=9j`@-Mv?m53~Op6GnR!41P$8H9Z z)Gwahpq7~A*abxKEnG1f1d@PL=>d;-(#!*>h=B~_14k|#4INsW{Qb#-8-Seqe2oyC zXV}#JbggFK(_B>Pi&Qc0FJ=7viuo zUS~{z`;Q??m%lzEH}hq`*iJ8mQlcQ{mYJv*kSz!r5|neRC9m>U0!HU#81eN?(hXO* zt7F=^%2ytIZ*bKPXgE9lwKS0aP_3N}Jt<~%1Dpxf^D~mqX2&=`Uf4sPTn2h=r8}tu zFa!FfB%k+=TIdBmO}VpPaDIX{5p75R0OxHDn54xJyY~p4_@@<>#yBOu5#|?yW)1{% zyWf}GJV&*4Aq8S^WS4Gjs;*+1_CE~le`@>sKuOn>{*P9!-dOwRzSFxu&)!9Q2l@-B z2xq=d`lbP(1E0KaFi9XPo>g~!w#ipaFu+Qxz6!8++5Ql?B@5QKXJ_`NMVEt0G4=w_ z;B70=E6e<$TFfe|5IYe?Fq+dGFsv&2V6NG(1-Ie`Di8;XT3rq-Z=;$H2bu{vyc*Iq zZ?FK7=*$5%C}OMFxzc&9>l5>9?9Upd@?x(cbufWf`Vkej#MX_~dX-@W1LntMW&c8b zz%A-9?;1Py9n6Fo{JAmy{#?|zuq=X^S_!AW-w#}Q98{q*b^>fu%ih&%VCZ~y8AmhGV(nSDV$4WJF%5bq7RQ=zHwq0BZN4o3Agw9zfi%5S@O8)J95n=7dGg!D5|8z zpnbC%2)d}q?onG{`sVmzj{sr$`V1Z956mf>S2HKc7!!Wd$Z5tn334Iq%6mg`DuZ`p z0jm4qN=Yn4Wn#7q{{!NG7G>0|zZ1}wc4pm|C?a!tWQm!h-iO9OE2rF#sb+EoJ69!{ zG4d%PcfQ8iN>{_x>8j|)%&BS8*u56RD-6As)K-21L(fKaoBgFp-o>7Q*fd%E66?3w zv>{2-5c>#5l$=*dz^rCl6y!FFR*JhxS5>dz$Fa>pZ>)p#%i96pA#+^w?pG{S7?;o+ zBPMfa{BOW8ZQHls{AFyW(HaJY>57Tv^2aMgpBP@{M*ut?kgWZ%EAvBS)`Q~5-L;7# z3sEws4^C&#MrkOZD^*Hx7`U-t8=*w`s+8=6Yr(Yqr*l!`6+E(CQ80l9Vw0?VuGs)N z8S-|M@nQC^XRJ)8i|%(z_Va-Q_mh67ktIfr@=^TrKA8^CM7=M8y!beODN)}(3(&(2 zJzl$(m!0pTc60Vwz4Q0^mDR%X61ZI}yp5TJZ0jcwk@)fXTTn5VlrMmQ^s{QQom##O zl}OOsu!eC5cwt$wGW#u22dVKJ*C&my8eCJk2UlOBw_jpDHcsR$lJo!hLrM3#TEM;h z&;o`|wS60&)qWOcR=@lV{a@hQ%Gg*?(y(Fo5FM=?{>_?T`5UqrCpc0q|L{-W{~?76 z7pxjyyv|DX%8)#*DBx<#GRddhZY7&CI9)e%VxvW6^l^+HhklCR9<9MzV0Js?-%A2d zPcG;COvfZ>nQu1m4k8m0_uW_VhomA@uQyUa2+CcmUgc zQx1dBFc~Dr+_{|1p6{S;azxX6EkW_%V8i!}ys~I5a%>!6k__@zk1C^)X#9Hqgn%kH zX>fJ*7CUUVU_@Tw?N|!p^6pcidLPd?d`CLqk5C^aa+_{Zlv2sk*LfO$y~0s``Q_7` znUN)LC#qW8l9YP6$LwvfB;s@J+19rtAYpH@Nelg12Dx-(p4Z-2mFxHFeO6GpBy}{( zx2;?k)GG{Xfo6jVSJotUXUXaSO3p?7!S6cTpzZ(l-S?g5T{MTG0;6&$ZnibN5`;biEL$F@{9lENV~8R(GU^D-sqr;@d@#FDNxF!rMQ1|xM&>Sh*Tp4it<94TWnfYHw8ClAunTQ3< zCq!;WT-APKe1q3qdYyEdQcV_AaoD*5cDY`A?}K;t(m+((_+qYj`{|vm^2Fux9Zp-d zSp=j>o@=PHH08(rS3q7AJCc@y=oRh7NfWFbfif&cy14S{`XMz3w7Ax0KJE3E1tfaA z&@sqt0FEdI93?$SfiVjpvb^9huB%dY9Er?}8d>S&C9Jq1#ToyrD91$m?j16PpF|ih{YIT>P)f4D^*s7g z)I;0^q@Romb=Y%Al<}AU6N;y$^gfPgOP1N%WN8MJ$n_}WwuyF8r405xrrZqKcnHKP zD{?ICgH`Uq%amH`818tdnq_X4cDvqo@mXFVwju>% z-F`e@x@ouk`OR~oo$OfxQqGJ}vxIr%&9@3Vt^+96QZpGEqv2^I=qZ%|MbcEF%yN9FiDI0 z;u%7(HAPD%JjAA%t?*nVH$yXt@s=LASU?e@`5a8BsZa(Ne(OQUCWaI|RMv8wl)A>M zeF7^qjr|SU2-ST7LsCCC>^}~*X_tZkyP#d$nxbLcMkl^H^;-hIub&` zmDSk~{odIyVRG>nChEjo{--={NQyc$Ed*Qz@1XIUxj5_gHR8)6j<=z|3(^aFz4#f$ z+nPpo^arw_;ayFWjoL;fAYF?PVK+xdG-xH+uOL6=GS#<`*=}j$;I7oOgL}i_p^>o4 zy=fZ%a!w(W(*%4I#mz@{hrl~cO{Tl+J69xz%o!}YMP@tfeZ`H={mI83)h9skvRIxviPV*y0)hcNH(#$T zf$KI&ixRscR|x}1`I=I9!M^P8ZA8xHbjG@Wyqvn!p^eWt4XS(VwcYz6-X}mJM&Y3` z=?#KkxnJK(6b&+EyY6{+40}WJZ@CQ^@T$OQ)Qm&AQQeleD8 z*l&B=AgDaoxJ7@d!(#)G{|!+1p$3<1S;AX^&C?csiWk(Rk<@!3?@P5cK%dcZtDXGw zu{-6<;_N{C*0>_cVEL{*y{j5cFf#*-C*xKu4sW-6e+8L)aUp=(!f$DMKbWc=B0mjt+m0|ttltiCuLn#FlvwR+ z+Yq-ZU?J;LxBjZ_tLScz@0n-8O0cLbCD}B8JfZMnVS0={gFXG?#khlyL*bLQjQs9# z|7>oYi9#}em#)h#{GSYC+5GobKP3llS}j)SRa!@X*DAg)nJ=lO63<~P{X&J&| zKwWNmG@#tb;wQ#qKg@tG)&ImK?v$d2}-PVyL|Z``-ZfLj>JX0GW#(y*;YVP~01OPmUz@1N>);85^j2c_J0tck z3sTovez=17ju8W$LZlYX%PDL!?qHp?CX|E!>$5Y|Zu)qA%7$HBN))o_>Nz?q@SuuI z4&TrjRi4TS0&zod!ll8l9*;%&oj~Ky+7LL=yO+uOt~LDSBfIV@RIc8hh2y>{>WYOI zJosa;GDunG6uNcP0U;1S_w--#o$qv;zR5p!BckIL(av9RI|8ub4FKPo18;jKFF}OM zDjdyDU(I~tCggSoGE~+PQPtMDbU9Bt*&o8{miTu@cQyw z_B1bOxIx)8I99?wgo>Wuv3?IKYtFib7!*|#C-a{o4ck@-5?#e8`hG$LgeTx#kXBBd zKGfj(SkOMxedk-rmLca`^1GSBwcQkkzq)&D?wnZUMc+Ij=1|w{AI@Sx;8tx^I<1GH9G61nfnqvTR;GWwJtal?GT zNm9)Pen8Y`0T~|0Fxc&vor6Bp9zy0zr4|9uHQFS2Khj}^DiD6t(96 z{0uVpu!kP&50ZM{f*ZFzoS9g9ose@ccShlqBDE>%R_0{M-{R%2aoBrD0J1pa`uV{< zF3zwL^US26I*A2B%`u$Acw&N#!S;{xiaHVwn+W)E35z?^G_rLaog%*fP#UJ@*)d!19=0MMqA#5Xwe*-qD09t%KMFVJQf_Fcfe8crdqQ~#*2iYD@@ z=3PBk99@0Ll+!hGi8t=~v|g|BJAL?b7{^-$s>Fv7MS%iOjZ8M)dfBcQUlUQvFZ*Qd zsejj))QID99VMG?{yUmG9s}h%^qgrSe8r6oF82cBAj9l--v@a5_&tBCSANTzLq2{F z2e!Bn`)RPIfrs~SKgAeS1515^v&wDJ8o=j1Fo%Q*KIc$EhCc%2uI0VLd;)$syIV!O z7{V;KqzLe_b5Z&lM=f!n;DRnu+{uuXzLW(UM!E&intB>x5%8MA6&N=K?Xq5&#j{m3W?~uS z0$4Hp(s5Ln47qWPZgrlp+~Unb2Y6AS<_n zX~kQJmojf^{yA<@PKwEL2(UN7+7rB2%o-LWg}Qh*p8_I+G0Ai7ISs(Or|{FhnS?^~Zb7EP}@vG;b4dr#mwGUTLyrxD3(l!gGyEW{}vn<1|P4?Pgj}Aer zu7`jqF2c|u!}NFdDv3Nfr6+}g1o(a{l21&$*vj>{{9J&cI%Jq;6lH|rFg zwg~LLdmF*cgY%0T_vj+F{nf^*ac;^F6qz?R+xz+)C;7*ru5gNLtIJ|By4oz7(qpo0 zyM%3Jc)(NVkiT~iZn2WSSYL_`y%_;M8vMFvG?IFcMz-vQZHQ8|dzGs2QGd*Q5g|N| z9@p-{r}+DC!C(CQmmfV*6eDf7+D#{-zyI+c-}@=iQLD)2+|Ve#iIkQ6_g9RvWKsyd z1Vx|9zkdRMmnGr}A3^+2*(%C^{=VP;u>y}Da<@Bi}Z7^J^*C zc^W19wB>Lh_orV~yRjEnUj#54wdW@Qqi=v_D1rA^`3H{Az&#beG6~^DJJN>$W{I;y zwNEsOAJ|V00mwaqb;D--?f`lVCU@b9PQeKaqV2dJpz>h6J8T+bA&7ARt{wS@8Q&_P z4o3-!7DNThn9(V*)vUlcx{IZp6r1Fo6(Jx33??}kXsd(abd7v8&CYnA=j`ZHlfssL zLSa!kc5Kau5ot;p115C$xG{s=RuCRMd7t$~hS6ni^T+GkdHpaW#RF130An8Ey6ru4 z>v`kM`gFa_3}fk!>d)Io8Uk_{nL)b_b05E?* zlvDZnnp7`IQ*g;au-XJz+7hum+nbFzKohn*G zc7$SECdS_SjsF@g(vM~5h=W8JZt_(BLGYt!rpeS-(xB5fWayd1)6AN|GF2cN%4f)B)Z8ZuZhYX2WPh!Xz08 z^6@utPFvG{&JMt^4HHE*1v`R2Ums=`v`Ru$MV%ewAX70b{&W29gsxR;H!QlF57&Bm z3up}S)&>Bl_l1Nalp{y~mooxdN0FUgUT=Qc$soC;bCXek*z#orZ88B8Mg^8}!W|5j zLrB;reW`k2i)nHIbPuWxoY1^S$Ypl_N~wqG51UbZRaEd3xGQk6v+%F1a(TRRley^? zr=RE1PNECJ1H!VnQh50npky2ct`ul2db?n`JC2ikCDoI*dKV_*K!Vn-^#lC+(4O(| zBYBQZ5_QU1$mZ2D=G59Y3@r)rxez3oq018$OSNY{2ZwJ;gR>Vyo?!m*MI71;YKy@5 z+Bf_Mzh=*#*Gs`MfuN%u&bdYQ{DI8RTOf~Lj1u_h0H{Sf16m<=_`>G{!TJh_lvBeOwAldU*P3@yYjkF@6tU+AP%eQsoT2) zeTzFtKAxJ|5a#c4aa1ef^K> zCzn;Pcnu#k9zMtGYR-fDkGTo4eQFu~;##>e-y;FEMx!soPB8}z`b0%yC_*qnC;uq0 z_SMBx+52)VA(C>0QIF;2#C6z}{@nGb3H=Q0>lSCvPHTu5#?cIxp(3uoB`x+2h+Xhg zpg^!<&V>oAuNO`Eb()zFc22{$=N>mp@p-bl2KoN_Jex8Og00OZDjtgv8!GicupF1c zp*p5!ix$`p3EJzQh{Vh*`UIRjRSP+1km@pXgUyl#52{E)LX^Ld$}uecIOtAh3GSok zrcNQ3c*K1!@C(b}HT7$f-(a^yWrF3YB;RxTww>bC-?I1Kwn&m0BZX&;Fl|>}5<}Xv z2z;gCA*|w&LDRL@t>q&rBZ|eN2@DG%Pq^ouoVu06r_ncc-;&%q^#+CN>Nk)mmE+5E9uSq06i_4LmF6A>^4~4gQofV$ zpQV+}@Z|F`w6E{kjki4a*Hd-m;}z+iFf_G?mezrtfG$?V6w<9zTqjwHbQagP;JCkG zw~7sD(h08M)LaiRry@szU`|4}m|`%y{!#rU4`R9#B5l1C(;+Nu%Ux##I zK$z}Uwb)NRl&*V1lu-lr8NC!t!H+Nx@X0Oo&j-!5%XXUk0kDk7*9i+;i7MrqnhUC2 z{LRgmQXf~}(lz}Y&KolECF{v$5ocx^mh5t_GStuF zM+UWCN}bKZQ5N5e**GUctvg_T-3ohu*_Snn>6w(RX&{0__|u-bMS$x7={B%`$Rgq6 zs;@l()$OClU#ZUK!h)+mkTHLe#{MOHELV`ij5wYrN)Ecd6{d#&Lbk7Xjqx_k%oWQ{ zBO=m2HztKkXvWO2{k)_nMZ5o^x5V$9%eo35m+z2>ULWY4V-#`ya6FK{tM)8dcI#xd zt0VZ!;zDOuuT$vrUej1>W@FWMJLp9mQETJ27{y)6XFnFMxFvf%RtMKl4=U&5>2Hvd z@TJDZQpeU7bL_)M*pDzb;IvISPS!@Jq($~C|KRda*$R4o%3RSuhDYXq*O8GQd6JR4 z)$|O-JKHY7UKF=?bm=O65!U06n=(cj`Plh1db(I-n&V6{N8ws@i1?dOa`brS-iiLt z!U&PPgcN#MGrX$sU~!kpGU&2<*x@b>j-WNU>q?YF#{V-W8ao8=-nXqM?V zoxAAON3cu>)M&#iSMUU>4i1RuGA>M#Hg#~`jhQ2tLbgg$EBdHQ__Tp*MdQK24P;Mv z;7TV;H-p&>tLRz6<;eKpcywAPlpvpWr;me3*5V-X0X|U&Vt=13ezNE zDJjmC2HlI62I+hY$r6exh<5p^NdG*J{Zg>NkVB(HuDY!fKJpX{2Q@~T)H9S@1Rt$8 zg`r2m{J1=;!cN3RTg8(dWIudise(~1_1vA8C^bum5<9Ygp@PNS%ROw^Y&yoag@?{O z3=~eD-WW<$%e*KKR` zS5ZHK6&2VbHt;@2xNc8yypdY~cZ;I=VXHS{kl}W{v~JQtIpx;=Xj?hh9oWG@i6gsVw{Xz!-1k=;4zuA?_wC+OEl*Qs8ni*-$!;0M^7f3 ze9gN{-Ho1Xd}ePe>_TqCgCaOLmde2aa)$roEBT>8IM%9w(ktvBj*%?j#d(eKs+nV6 z;@@}-6eRuA3xlj#22-#^G{$b7ivyhN<5*0$)ca4e2lt3e%(}@m^O?-AX+Z(cl);up zau!l0`PUi=N3MK&lfGr#gNwsl)E~pIr()}elM1d?o-v{TY3j8z;GQg1F$!8~`1QSS z@KmvnAid;dCd=5$Fdt`GyLuf;2G4r!;MOY@imet^6kTh0@X_$v1BJkj3DK9kN|CN(>91Sy|FCDq5TK-T+ z2Wt3iC5pN8#EY(#o-ezFxn-@2q~aMO@efM9{vCAOvUDSdroJhFnpC!kN5Ux%S4 zrZmc5jKClln^t`b+0!BVG#0~)GxEkyy-fM~$T#Ozy;Y%M6ul4ywMnYrL*rL7=QVF? zMEte8=o9Hd8eP_a#N1sfg>)$ic$`x}=!Seh0B1n*&%N_+kmk%FOXJN`j{Q=`a&ky! zgE+-+=lFGjz&)}Z%ROX5F4w-$ba^?FXPExPqtz*#@x_hQ+!(hdUt(2A~B|HlqfA)KYs8b3M1mvxcm(i*E zpYBlKuky87gu3v{^?h&z9{9$9qv}(WudhXE1U+U2R_sP+j}CQKE${uTTqt#XjG1E< zef;v}vOfi#cYS34L%@obd@DCi>cB>cdio$_c<%xWv(amD`EJ5Q(v2*?9g;K2EU7DO1|PzV(B5p3g`s+ZV1ba&}PZ7 zhTF330|} z2Do*nlz#0rUl@Opo-S&42a`mys^^8|rd5grg}hI1rZp{>OjVw|jD!}+n9M1=#H`CC zQ;rhqozB-mwhmKWP(QE@;~&#|uXedbqZ4-H2OH#VO4R-ohZmi;Pcd<<5`6AU7vc~< z6Y*=-lWS<(4A>`<>APiW+t@*?l7Suce4Ba4fWk!B-1Ti~>b08$aRE;mF6Dr_$ChII zYB-O7Li2;_i4JX2&+frbA9}gT9IxBB>AFLL?T?er$9)PB+KH8cmwz0mh4AZ2Az9KS zhZ=Yf`$Q)z^ z#N872i>h1nuq!OCE@u?JL2VcR_*H<+(v78&pjxgdrvP1X9&?CJZ&iEU6ROpGlTP?n z2V3jdHn^${CJcFXpzI}OzoX}!5tR~%WbCk1Rim$}vz5)D#iE~PMCgWrM*jUPrm0Jz zZ#3`z7=PLlfiQYg--Z_((&E+zy>2qc?UN)2bTT2lCg`E{s6{YGzTEpPkIQU`a2n}l zektQ_*1q6_b>P+6lC9(0V_@j2I?o41L`;jbLAa)uq7`UQjM(nqrU?Ek>fj}Y=_9>?l!iHoHqI~7wPdgAq`tk)^0?7T|PE;R1$J- zv;0PXAppj^nS+P~1s148eyevHGH`qm{_s0=>0HaEIzrZSCQlXH!AAuo+Om%KxLGjt z(!4}|3vBemI0cd0veHSbFEF(T{jxvHz5{i9P_l#?jV)#yTRq+)C}d1&The)3uc1O% z4(RkGNK9&B?yXyac_N6g_D1nX!pQ|MHHl)w&|n##rjI0QZz?agjRyx9pgTXi{=<>) zE$Y+Lr=N43>wm_zsx_#8P;cI!QjFzWld4@;NFy0J-J?a_AeU?4K9b;taPJk)#tK$* z-G|XTVFwio%dBfRrmaA7I3}YSNO+0DI}Ku6&oMn%j}`E`o54C2f>~o0-#GNFXMrv8 z96l;-F`4OfP{>-H5lU#$yuKu}RhHlN?eD(Jl1Sk#qrYj>&vPH0?+8~t=@=QveFRH7 zuSfkUI|KH`U)k~3_j_P(iDkf_3B}j*s~_6FK2vpmXD3W&7awyp9Xsx) zQE;d0_zbJFqBbYyn+Rkv{agCe9hz8h%hYZp`L!>?jN7}=I?i1(P5AQj(($Wx;Q7+g z_`IxF1+vw*u>yM8BMsga8-eb5%mJdL$8jbtClNNU)^f@Kd0Sw{3aN`@C>f$Z&tph> zcX>p^d@N&yM+Mi&Y%8fsX1*vRnMKr}0;R z!-gPHrmnrMvaR|4?C5}ZfA+%hk*(&p-6!#x&eZvM>2@V`9i;0so|t)S_KOedttnnd z38E>JIM8>vVW^;)B??V+)w9}#T+H{ZpR|ZoDHY*Gq&gezFTr2=Clp-8Z$IQk_tbR`_hoJcoA%b9fK<>t4WSX2_JH zO{oO4fl`TNRMdJMX*rLAn8wa~{DkiJ6tJP1q)PF+l>Fp#6c@yZ*GL2X@qRou&ff-+ zkd4t7$FSU9!^=f8Bq6C%w!*WXA-%I`i3@C3KI0}+Hg*6qSJr-t`MA%4^^f-efv1o_ zs~(Mvp)4z>eMRP}vHmy-bB05pJU#I9iISPbyghp60}@a3m8Nl|!)<4oHNesQ%B1bl z>0Pd)5ctt&=L%|5lG2K=FO{uXD5Uq6#5dfy9)*>B0}e?b$R@0YiVXxstE)UtHz)5! zrkgtAmb-TdQ_=BPYvw;a%m{J=Dro8ZJ>lD2e-}+)LJe zxx-|5QBiT7-b`VWvv+Sx?7?^`jcV?>g^z(4&H%$56zE}n90zsW$f-Miiq?c{Dnc|bsf z+hWD?hVnVP=n;1B%on|9q8#r;D)>_=I(ju)2NE1;0=;R`k6-7r5-70wZI8)rUaW*- z1>;j4eJzg!Z$J(Aa=tG6Dej6kt?EQEDmS#}5%$5)ieb9)OTk0O(uX=+5d`gi7%*OGtf6+gORQ-; zkSrKUaJZ@W$ZT#nTam~RZD)rErH=r5!e{3C_b4Vg944O_cTKLP9Awk1e{TwyXQ>~B z&Ki_at-{Kg$y1|}M`s$uyaz+HZbB1SRf{FM4Gb+SdSh+Lg+&RXeWtsnfc6 z{eWnTETo4nNHBOy!c zO@WTcoWzC0hUo0nrRQ6y;2}V+zs_P`uXqL_JtQhhk{#OtuO#MkN&!S9M)1@bJN5?3 z&ziFhrVt5KC^KDfw6F*M86|Mx*IK6mPZ364U zTfghMex#6W17K{SN^_l}$`_HHf>RtkfnM@e5!g>9PHDI?4QqCrvBU}lF|ACKAinCG zmi{)@0^jG5))e|z^eYfN1k?n@OhCGXF%BvRuTn0NevsCTJNoA+ZIz-~u<8#9AKdRN zTY6G1Bq8xH3alC(-${)^ibt#@-0QQOjq%u=T-T&5lU!}R3rICLnPT4ZjfMb1&73Dh zfVI#+DDuo;Mr`Oc%~SwZ<Se6O4}7$MLw5yjnS%$y*sHTm5|pej`;YeOUJP<>W_~TCNztJ- z?fD>S?J(q1D%9tqu63X9OWsvz(hTL2~M;#i&|5&xDSf--xARvfkcLcBR zo)g{`B7wtNtc=%P0F}oaD|U(y(mg;Z>KU+>YJal|J0Ur##mUg)HdT0tARcxXJhOALpHkSJftY8IWT9e+WA0-?>~)1c zK+t>ZarHkB+FGvQ&l2Ha3vti)(aYj~Nn*0-M!SgFd#$`*tn^ruJ$p`#zLdl08S3sm zOb!_m$MV_q__<~y|Hz)_xQ;1_ySwt(h|dF?cHPuK2y_7~FM0bfo)$E2Q2NJ=N8(10 zs#B3o0RMi}DawXlgBh-Rk}=DMeMK2*;_;n9nSQ&UDAWgy^CRqcz5Cja+79HtMEK$N zh)Z&8eGg<0rQyNtdD<-h#QuB|wd*gofco(d2Y45ol)^C`u(}5pk+XB~RvkmLP~{#o zY7TRLQtI?oxUusur#y6eO~zm)3-ov=3zHv*M7!iGaXFyts3j0W<6mtC-kigJCw^ANhk*9<%F?H2kI(lWDI}|GB1YQux?goQS4Ewkm{>6?4%`nH-dRe(e87)( zwXf!R`nyQ+Bj(6JVp{~x56_BwsaP1i!YY^3rNs5?T@cGx4`{x=ccLK><Qy1Xx%UaU7`Vm}2b($_W2v0yO-vhgZKk<-_Nbh? z(e*jS`-Qzzhe~pO?-q*nBHGU+(eards-SWgKv4gK^1QDrgmv0QwyhLg&)8|&Q@e)E zcK9PE1FKdGf`r2*_U}Xe*2l@fxFyS5ZN$+@eeFYqQH_oq%fVK|c^QXqMEO`v*@XaT z^>vp@bmFxe$6%8C)nPok)+J+;NG?mw~ zmp%i+Wh(U5V#|08nb4BiD!`m+Vd*ylbQM(#H!Vwt&y$9HVm6_Ce%p}9SVq|BOB4Wl>iC^{cIr7ncfB>7%3vdz1DtPYb3uKa%ELi0I(unbwHFAQDd`FE&li0zLKQqf} zjjgnylEy8Bnd~E7hGC7;zTgSWf@|4Em93NRPfI_sE=&A2}?)NW&ixsbGoP`v3mrfQ%^# z<+4NMl2bCi?nz^|Pro)&|3zq@(fj-eauNI7QPW8L8IOoJ%;7gV5wSjBpOSj+Mu(12LbpI91jucE<(!}JGYaQ?e=P`) z%AViN0$;w1W0=%y7XO`VH+#Aco4TUTC4|HHW0H7`@2bO$ym)*gIe&s!f8JLu8ARxq zEdI}d@`r>WG_liCBoYFS4329X7c8yn;l;2_l>|Q#*7M~yYjW;snKAo9dFuI=#-?B< z)NM4CeCiG*$I<=a?%}HgtUZj{}=0q*-AD=~b++eq>!w}tjJ%XWt?|#f9K7zIn zr9S1PUS{*jr>iDsM?ki&-)R6QdF&mCCUrOz(D;#GxG<$w_B%eESGzX<_$dDWc)Jde zG5Mj*Tr(s1>~|`urM|-0pn0{o0hZNCTQ@oBRN6p(aMioP;C@*k7SW}rPomve^;8u6 zs55@H5qt)*noPj?8ZQ-OqU@l!5G&U9V;}JIG8%4D)A^L*Nx!&5JOo9WFXR#c)LH)O zH#CSwpl}t9?UZe62R7N%%I`Mf|G|d=nT1Q)d{vR$-XSiWg!zv5`1_)+e5w?2__8tH zVT5LsPMb;i%)nKB+_bINx<}WNxjGzqGywFnom=bqMws!+5>oKI2 z;0cHz7pz`>39KBYF}ZLtZ5{!`A3`MjSjo7{9o)g{PJgZeKYWDZh-_)BAvAr{em|kN z^8(k_{F_JMun1(!>ajm$+QdiGyT~n~iu_V1v9nxpSw$SQb<5M{lXDku3d&Fq z7R=%^v;x8W?NdP9`KuVKJ(ubYKZB2kF+29%!!3@m$d0q)fwQRbJ^zaw4kxD1{UR@P z5V$`s4EoPq_)8?E-h#RvD1WRcOp=BaT2MfW?tD0@;t~hr#3tV@ASUlK1X6|W*pA@^K1xPo~Q+p|aMy3oa>U6kA1G}*-^o?QH z766l@#w$>=8J+(~dT*ASF1F(E$b|zr2dL5`@avV%KBj2c12xTdKv1 z(tpm(|6Y!IF($nh(U-Iq)ST%SVxb3&)zXA%5pO~_cVvSGBD!Pe2fbm8;J(9`g(-$; zmNKTS-e7cdOw*V~(qVuL0kmJ@0VAOiTyHW8P|egQXTB*XY+ zq?yQz`v^*p?E~vByb0_4zaG?|-^f!BCWWNBPIgb7VqtWdRrS>e1g1WonUHUSDG$fl zcsl8Kgn$?4-gTfd$Iiy4ueb+FG;kn?@(4PU`iv>38O7-^e!bmJ_yu5rG6R~xE4=qm zLuMG~9!uvuwBm-566VieTI8Z;+iWkys!bYs`4?TwaR6uT;h;9K`>ycr-Lzr8^5=^F z^Ki-+M-mo?&T$`fP6}W7LAF5vV*@4~4a|LfA$i+^;^Kq$h3;6DHPF3OIc{HM!@vE! zXy=-+V|F(_y+7tv*DEYm~Q_1>R15%rCq;cql9-Dc@+ z^?di;5s-q@eL4e|vznG4KuY(GV;p?>zHQ<@K}?M@c0#*inP( zaVU-(cENX~WvqdN0*I?m#8$zB3Bx~2fhmO|ObCl-VusV`ZCuwyAv%c7Z9d~E#p@J( z|0Yx~h=nB?gs=xFow*8^0L@GriZr9aqIyReqSn0x0C7cJy26md^2b+ga|#sJu$5E0 zKBZD1e2Sj5lMRs|t+7YI4|O%dP$#tvwoMmBl%TfcyTtunh~Rl*39;E_AG1NVd)KI$ z?+VGkDht_+NkR}c4&3wwg`jzz!=z1KS-gu8+tCiu&@}vRPdF9s2t?s`FHFs+pmI-L z)5ty~a5kL|sO5eKeBG%ngkYS)g0SmOr2kx^RT>RpFatx2f?rf3jRvnq*qrMlh$XaG zIfn3{FJqvBH*2I6YTA@?k1kLItl*_gZoS6J{`g%yL#-g3EAs)s6&rZ*aXk+i(&72< z-u1`kp+13(Sx=3&I1<7%-9u$07R4@nOiTR%9I<40Xi;k@VY6t^8eUsymq;Od^|(JD z=_6>`G4E5Uf1fLec9BIwNZDmdsSwyNUpz^?a1_TJD))%JB^jV>GWuoh*6oEOy*8vfbJ;> zPF@NHNi&NrjJQK*z0N3W^Fp$pP?6>jY|xEChRacu(GB?FJFbD zWWOx|$r-B5K^V|Wzt;isK&Kh^rNeH{ZDVv&T40ZpA0mdq7GQX1d%g4-IVn<_WI+Et z0P%k{0q{*6x?fYB0!C;ms|LyD_~YSZ5f@uXnIIA!g@zbE**ArM)0{dS!L+$9(;L#( zFZ?O=1o$Fwl+)92g&22Vofxf<*EGs>=&mXZ*=~EVClSwbjuf*(1Yw0wnyr~o;1Vi{hK0`*lF{z0 z@Y7tvMHMYrUH7ktsz}|;c?tr>M69L*;KyAEbF~_s)KfzIZ`Ajl9upVU%en?swW2b% z<8K!l)eBr_#}=Yp2{Jl6CN3RNT3Lax$;TfiVCV;{jnSPjOf59D)_sw9S(5M^n!lF# z>oiGfE#FbKj2L+~CY1n7+Wc&cjH(dHOw8JVEDH&mGKEaW-0l#`ZLD{@OH7A5BRD81 z;E%721>`eJEtCAf!$WGIy?;r&6#BrAG_k}$q{wl{3L5OKMnw{J;J+nV{NC}N=DOxw z{c|VuE2qvdIXj5F{#}3o%&e7ig!_X%Ebw@UUo?a4Py~i5`ag=gya!q(w);yfDoB=> zlJ%}X^riv6HE6LL8Iq+gLqeYevu(x9T_$isOQvDoMP`Q&yzYeD6_=w+0o(Ad#x;7^ zieiYNvbb|rR01Rd*b%nW*49d};Xb!BKDpX@azy5t(X%e`oq!dKBjk3I=Y=*iy?1Wh z!k1WnMO(YjsuLoZsljy5EA6+fw6sLU-fh7LBQk$4ssJLly;0rnp&LN?J{diK4&lH``)n0*P-k&2Dcne-}s)a!r&SDFntKImG*B|2hOj2 z1cKh}Ekt&8L{e?s|)gQNx!IKYOjxM#^P z$AVHL0@myNe2Iq%19aK%HOD*dK&8A@v^G{uZqr|+iH=$!An67QviVX)NFuM5IbWK^ z^Q2_VI8n3$nw}FZ+f&pl0(C6}EE9; z_dN1%zS6>22X!z^XN?D0V^pE=#=9NVtdt&WR&bWsmpqRE07=j8RFfa?cE{U$8;u;$^TQ5SdiN&9K)jVEXM|iXOd>G$*hJ#7)_p3>W&6Dn$uv(MkIcws{`d1b^8${* zE2E@~csF*{C5;n)FWx(FRK~q%k|+CHU8sj87z}I;B?XccLr7eRHrf8|cM#`QL!8cS zRpIt+=L@DcfEzB!bvqOt46PL+#u7GcDzS`<2r!TYLnN01xR==vlE6jgUNY%Gb+gzPmiAJ%=>AbTyHWz~0`1 zgo4MS3#9R%%I@9_Gf;M$5lPssMhml zMFJsRb2ZX3enV{x{j#D^138`9w|s!c`5VxuYBg;^&5mtFl+qXc*CSjC{tj5;szgl|KA5#0FhDSq3Wp+EF`)L792%06r&XLmc>V#ZNerMl>tGJr@<6|(vbElJvnr=Xihk^Vt+CLK_kAz~Fh zLxVqzm)!;LHSWCKowx|7f?uX&Fr~TC?%5a=!0rp%vkFL5&53l*{`)#pBhJ##ao|9^ z;F~2Mfq63LXzcJ5Gc3W#^@<3lzie$rIDva&taP*?@8)T9UyDi*Tbj+%y-Qc2oZsKE zmN%3xF|XD%@QkBWaQ=U6eRnk0{r`Ws?2OBbvUgT?X7=79Tgb>J%1A=?$liODEh}5L zjFM48l$B9JG$`?VzU#j4KIeD7=lJWs&*{3Z_xm-T&*x)FKvG>&JQ;uQ@IK>^Qa$vS zkQ1@lpBgzCWs6+$1wp8iC{9Mrn{5AcoFpW6gU^XrG{^UT2-?XdrC|V6BZ?T)0i%f5 z-X(Jz$zM>=*YSCZ$yq}Ftc$phXifPi(34g`ys7bK0v=(MO_&*St9%xj8lkA2ypom7 z-m4YlSH$NBKrFGu2BXnEPXb~=IM+XcYU3xA)KngMAAU3Bd+}At(PJt;|9jppllj7g zWk^`gHzeKCfB36X$vu!|k{Wt~{jAufRe|nn@zR6JS>eBFeysZ&Oda@Fu;3=RAs8Ci z@5h?lk*Nef=;4efw;r>Y3`L_jKzYxFA0+r2aKI2EPsl+V^b|SN4;fn05(+4~GPqP# z&I(>c)(UjQ6_43NhNN5AL7RIaD#Fy2PD6nACG?zGp z(2gSTRY#7_{&2GY+9^&J%nnI9aR1rL2PMbO!%h$;dT-7--tPG1 zyKrt@W~+1{n#+NEF+JZ&UK7mdh&gmRND)hY!5d*%o`=nMj$5wokyF1_|NBlx!cNtY zBN=EXW;&O~z+Q<+GBSjxXTYuG1g>+cl0I1u2!DvkmdYaxHY-nu@D`6=pc=b;Sm>Q5 z)#{5=s){?1zGl?>(GA{`dH(iQDLZiHgd$v~f9I++QhCXmVsbyIuHG5}d<;jJaNC{l z?)0y)hNojCHXL4$FPKP=!fs_zPHpk|(Pef;dDIJZ0oKA9_}UfhAx*GI9NGO|{dW)jXlTvr_Rm?mQSW}#TNhbEq<5#lX3P9O>2F=qX$AmR^8#(k0IKcMTdk-X6 z>O(){zcQ2tNk2O041U2X@ua2P&ZD<4V6Zenclo;EXL@YKYYQJbadj=tKUWY%u+w^O z&kkz++v5Jo*h5}&u33GB2b8M5Y)g@m$y=9@3dPzXhA-S|^x}dydDkz;+w9lL6YC}( zy@OnKM{J1nID$S_P^~`FY>rn->2ru0_oLrLz~57aRdR$AAYS$k(CdfVxlRqpZ81C- z^omuDvs%!;Cx`JH7^xuy*e%}2;Ohoav{Cz!|CQfWp{ho~oFg-Tfb8Z+ z5`w92o;;zH?M)_K`#S{KC zFDnm;IUv^i0Tvn9eCJ?9r$v`Vv3$TrCWJqRn7(2jco!6^QeOE>rR~Khy-^8<`?T^2 z?!DMaw!N(Wi==oLw9_q$f;4JZiZrRJJO^#YLT5^FHN_+CDeJg%#Bx|Yx#MK}o5Z86 z{nS%%U-t_OihoT|qrOKh@4M)$(T#VSFNd0)9<{~VH3-q_Tf7pNNh**VI=Bo)U}vt! z_v>Yi#hdY67160iDCj83SK^U-gLH|A}R{U?9_Nv0;QdugFAq;#G9>K9p z#*aO&8*rv4a84z}!bW*OR%YqRjOiC8-*KS3{^o7Q)Xy6MM@o~5D0!V+kV>&qsl8t@ zOOZ|L&}F)m1AQ>J&s;eQ#1Vfk97>%<$T0wMs$}BXEuS1roOs%oXKsG7$J6a@pvvYy z6yzzOj*_Xmq>gb9GlH3eQaL3LOK|PEx@A0QAJ2O<2c^$6lMRjv^SIqSC`3jF*tAL( z2s2Ir^VO(ipHBC%`!gt`H%>f$*j-Ix5I?zclkXb-A>`IlV;te@&F+$47rDat151u! zi%du+M197tVDX%ZErAsJdeLvn#P5|)Ms@(DKHrlA8%)l5k3OOtwU*3Ux!g;4B;*P( zrxUUB9rP@O5LfIb)U#IT5}+pW(N4aog%U->?8ijg)r)M<$R=4!v=E)sS7EiZl^Fhcp$>O?J6UOmH%hbL#G=tj44g} zu`uwX!HiHy0^{VF)n7w~S2hW#>I8Kk{r$Qn=x*HL{cz~rjzG1$B>Yy31WP~m+h=>b zd#2drqFN|VP6A2V2|jjk8vLR7D#>}h{nue3h=f&04r6kK~ibqC?b_dB%s4 ziB}^>pyzN=<6Hlai3X0Kcw?cvD91c?3z2Vw=Y-03TK+5i2DQ8R8+iO;Uv77|jQ!ef zQ}2~1VXtD|WEZNtQkv&qkt#%K>2i`1zmqq8a4kDXp|Esj^YLBLXtLVfl7*IwYH<7o@(JqBOwT!CviW?zne z49^kVMtKXM;se0&aTm3OE#jOUg15^@@Bq}{ zc`&J|+la*?60#txY5~eF(L_w@4hJCM zmL*bbT%N&CC|~%h8ICv4y|+C-=~om!)Sk>qG_1VC9splVCGkQ_EFrbEyFW#A`e^ZD zvQY#aJP+6symwA({)h?`29w={F9W2kQj;(vF-R&C&ml>WSq3xBy%Pf5k&JVsY#6T| zGdM6YUQ0jHa-VMzcoN2xS@!v9A1ld;=Cm78j0L|lA zog_uvdIq!*fXkx}4xd`|%ES~Ty~Qhe300{q$w2!T5wxPqeX()TH!jzb0!gm?&t~q$ zPvD5zr|;Eo^SgiU;{9UxcbAJt)^v0=rEbyO?>B2@Z^FxGZOZ36z_#S*s0|~GcsCF; z?KUiWlnr zE9)@xD9U^n*t*(dots^pr2*mq#9{0MqP2w5&L%v9z1L6>GURXLEzz43-HPq^T27a& zE~4vtT4T{}8Ug(ku_YPGyZaP|NI&3N@|*-mpHM>g%9~nSXl&0UctQ_Usmy-NuVryngYi?=smSE<&mU`kHyjXJH7Hv9s5b*H`F?9O znqmTntM4juI_#dyQtEdfP>4kng9{LE=(W6VYkhh`R<{Rl@~^#DmkR6l)2F9<=o&&5 z^xe`%zaUpmrBBk@w2s+uZ#la3be?3*-J^5?uHhZF22~-0W_XfO?7F7w37W;l$pDBP z@C$cqZo}kEBh^9Y5(HnJ>_}Q-W!fkkZ>Ya^aB{zL&oxB436PMP55F)A88pLl#p^vu29tfv+jqrD^AGb*?e2Q_4)#-ABI;fTmTD(MR*tA;9lh^74#FJUbqCc zqPpubbX}$oezlltJ;aPAJ4t&N^~!6{isa0Na|YVCG=7)jf9hq@MUP1G0+%2X z=v8xLH7iA{=`5vOt{thIr!7nb)T|+ANN%SxA6&Go`)t&6`>d_G%*^574*zA6wF2w& zN`h)s74+N|WM#7o>%x3iuguDV5pIU89XZU+G0jfN9AJh!fI$G^-P0Ky2QsQh10XSP%wem)Yy&U#&Q5kY4n%X18H`G8sI-8+KOX7c3G3(O_05W>v+|rdKT_ zbP_K|n6Lw}U8gi^q7l}VVhdyQ7H-DdhnksW^r+wt-I7jk25mA-a;stE&#me?a?keo z8P~2tkmYsiV1|;^$?t8?mdHWuNm9 zu;5%Lbr^`fl4==IB_6rtdC!bgDX%Gh1xouLro`^u!I@;0)3NiA%>y0hJiQ_1j3UB) z7o|Pxzs!0qrYAnm@CWw&U!SjgjA}+bKsxfUeqPU@$PRwGOy^^VH ziz&g*%KeX3D1;jD#&X0sVrICE>MA_PyN$9!zd#%2;GZM3bWz%KS#iB2`C};C1*Bpp zg=M!4v-c*4YnhBy>?MnY?F^};O9#!WpO$Wu1;U|x^p4Ii#-Fl(3XEe97=C}(ow?u-tOOU7j;l_Q6 zrn|4rD6cc);lz&&pTBH80qzuvyL&cOe~t(S1kj(^79L^w9QOGs;Z|_*mxN?6Gsc!00H@KJqX`(&4nA(AuL&MGDTuU{8tgQui9^Cj_iI4v8~!Zkb{WGoPBUpwU<&#k$;fo#tnd; z4-+A5#l^HwN*F!OwIknpojIT{y4%LDjZ!o+_pJOJpdYF=Q(#_SF- z#jzD~9I3Tzsd_+pzSV8_VLbnZURP{9}M+uhtfI zc@Vm_j3$RhGAQSr-aV0SBcr{DDac3QT_E^c<0VaOl5=IwYTX|H;f5z|fLe}8?=Sqr zT$Y5;fw>_HgWiXK`5UtM_`AejU|P~ruy8c8pLE#+7q?jgr+#rhjDSq8QAwVEQfBAe zlUzq@Ke8bgILHchEZw8i#~KJ|g>|P%?=JMT0xLt@ikpY?D?4o71-z=HM(pJL$$YYr zm4#bxoEsxlBP!>AT>C!ukY3=z4FBUVLAW zbT165*4c#(>?6irue-`ST-B`GWQ0^a9?gK>#Wf0+r-Ht=X=HZ_=f~l%v%`#kh#?XN7xl(&c#Uozui-?_o3U#5p-xU&ye8hy@Drw$Rb>7dfL z`i=tsABit;#IgkfXc2unh16{Es9e!rM$U&}o~wRzP5<=9F2n>7fwT5&G8 z-;p_zqT4-IoM)9Haw1+oDy%EtuE8mfTjFLQ-2w@#3>HX*Buw#N4hDwkv6Q4`uo7rAueN^?5>%@e9)_ol}%hmj3{1$4!Do2_T?p5%svg0sO3fm^~ZS z@BLfn1IG$;0u%?zvHUT=H27MPWSfEP3~{>M`xNiLvV7FEyz|xYv}rE2z)-`;Q#7L+ z+xBS~(DSaf!NeJ<0aw1HHSuoBN96|Buff&KzK7i^Iu$)owVHArth#(dNTpJ+YGL+F z+%>03)g$m!9|rFrUQZC)IUV^^6!XaqrjLEQuc#16BJoR)N$C=bI+uq!qYg2s2RnRw zXICPi@2m&K|OJx~O@lRVq$=hD!Z zy&H?}hq%>W)goQ*j%|VG=wyF#MU_U-*tq2dXn5$EO%lu$Q@;FUGAO;9*%VtCu~S60 z(c^C8PHm6}KP^y`;Hu`|<0^@K@p~?B8KL(8#w3((mtU?hXxdqX6H>WhpqFJC%ML+Q z`_aEUi$&WA{l;`&y@NxnIC_SQLxAP7ffnjA^WFdqw{q+VEQMHYUO48F{kHT_VT=OS z8PLq=NjN?nbM? z-aTNG%{pBl-dbY8>eIgv^m0Whmn;~6ci9$iPlc-gj1|oAxAAaG!$IUlb#t%A%jMel zH|{bcv@De8-5xkHj}t2fBtN^obYS=8)0@{K9G8Z)E-s|_7&&pmk>HBFkRF_NAf0_v zjkMp=S1{3X!e|cyx2Nt8_@WcwB-GBMTnG`jVneMYp0vERN<+qFyjwL5P7NC7+|R#b zF!UnM{BCw!yXWprMJ%ut)TPE4iNRR4+N<$&iTUHvmzXMS#}~+doP8-!Zu4^Y0O%-m zK7{+dz^-Ag&fxkFrcl=|fR1}iK3E8c*uJ8h;1%&9;c`~64u;AP;%l#{6q zOC-FKdM}MsH_eS;J3qAmd8~NYC#lEH@C#<0nJz3*wFn#Pi~Qb^X|iM@XjcCYfC^mN zj86eu?cVc>S15wAVX&3lhjEyxw3F%l%vL=$HU;iON!d>6fP>wtVkw{qu#-nVR1460 zSB7CI{lkS^+YDic0_3UU6EiM{1SZR#Hp8zJP^-kJ(0x%XQX3!Uf){xxkamEU1|N}m z8YS6hkgq;F70)J@_NdXuU^3M&-D7$Qq6NtRPSp*hF?zW(>OKIgjNImvad&Gbd++^U zE8c-%x79k1<4c3ggrLPN=e;0Ln>vB=A|vZwe<@m2`&qySeQu2@+-K0jXW{)(j9+4d zZH=Gz19te?uToC6Z=eEa(w7MrWyrK&s!e7}7*f1Jko610c{vOj25L@Vi)fQ{grUW~ z!Zn+my$X7roGbN!P_ipve8PXT*O*kM$Ai`2EZ?#j#HzvG``~7z<0+YM1~~mKt1s?A zWRzBtypww*_Oez&HOph}%AU5#9n!~<3j`FkPxskgNPOg}62cVJ{0Ur)uLG(Sh$GQ6 zKKv!e-VL8B%l~aIy2+=P! z*1{;hOy@V8dzT+-dj3jI5snTBpphTVb|)TQ@hy&dSLzS_N*O!q__YOZCPO_3YHaXO zBVq+dAfgU`)d9b`5tvaIN#_z6hHx1I_@|-?Dx6$&8p|R1=O3F3G^j$v+(YX2@$kdyja{5EwZ1)n)0<+tRHb7(n9O{6KvV6ea@3M zkmIIxM;<>AMqeUI{bw662BUl{&oAaB8>NHYueH7$*Yn+hy|oofE>ke(5EXc?^K$*d3y;6 zFoswB@PD_gED7dXsQne{eA}Gu9mkZrQKds;GOH7FB&RwDnxm{3ZETAQ;{`7!_rwC* z!7h_MiV7iq^Dk9lrx2bTt;+~~Z36e8l25_HnZfMv8-;VkJemFD8l_9C`%T#S%@_5v zB2SV2ne#xOSA>^cI^bWE4B4I zgm%ZlO&i#M>@(-c;mrm&_H)+wdkmGe3=5hzFN*`&$Joy2MhPUUxq$LZy!^D_SMsu_ zjO1*3-FMg-SNNi&qN50|Fgfm2{5RucJVfb|drZf{1u|gy6&#ShlP9kW-1V=GwCaew zrW4&`Kj%%-a!u;%%gb(&2TNRan8-+eBd)+;CSpxs|3Hh!jg9INpSjY}rC;>*;nCf< z(_b6W2Rnyd2{F1Jd;UoK2Jgx}m#7z!3u~qJFCKFb13U*Pgk53O5@}xF-G+<%3?voa z6jVU+x?i6V@mE-J_asF#97cWnY`;uXJ~2jswuKr)P+w-6(nuPt%H#t|$u)H_+%?4IfR{7}qi1R4*5)k5aWO z`j@DtiiaUHO__a|(ZKB9Y)4wDR~+6cMaugTa2OKr`zsaX-PE;cVTp>YY!DDvM9O*LmmgR}RUhjIfo+)?I5ZhI5AR zL{;m-$q_ulxZ9<7ZsBU&>WRif%?_r>%ew<-uGShojR_`EzK%W2W0COwT9s;3q|V7v zLQTk~^>OzXUDQmxn>H4@g#1K$x;^0VhJUxwz#X0$eeF(4wTyj&F}3m7-AaXr`Zf*; z1m_bWA(};TlFuUj{q-0aQ=QfWIn1UY>C~oN$MUZI*Ti@gdOE7 zB-f(Wv-o3`av4U_uG3YSL~`F6VTI*s=tnHOS$*}RP$gg6LeFsQH>JIcF`-|1DB5$O z54#JP%8or#u0^`1R)DYYqQ$uO$GR;m(I%*hKj6Dr3&lv_;Dyrr2(glvhOMgRdZnCw z`;K~yt%<|n5Y99s`5`@a9^jz|laZI54vW5`H;u9-*PD*lN&dr|DKo$n8=lj{m@dz} zk*s8XDcg20u@yMc8nfkX;VwbXw^Ql%ppCEUgXyZ6UygOPU_7RRSK3M&^O3lU3hh4W z5i{$|75ZXDmpx#IWD8(Ah@;Jv&Nu@R%4`!6F>qTp;%RSk9iB174sYAO0{v^%r6+NN zd%zD`UlyQoke%kCrejL^3Fgb zF-%IcFemWIX4qOBC{;c&L5a7wTT8jU&gul}u8RbX6o9dgv24ND?m>ze4clET5lZE` zyoM`eR6X_C*UW-3*uyJJFniLH$D%HcZOmLxAdcFpw?{)GzDlV{vlOa|a+|BOq=oc>M zfRWj0*D?u<-a&6IsTnYz~zlHTV)S28TILdoyGRm%WrJhhL+?&J@EA_aPg~ zjZqgHqe-o#Gu3c_yeMBcv0si&hlYlwpyAB z>uSnGR_8f;{XQVbG3%ph^YJOwmAm6v=ipT*Jy*c`57<9aD*$^x2(a_w=VzV)D%En3 z38e16k40h|j!NREKY;^GSQm~Tt@(2>ir}3prQ6H)s0jmi)}Lj?_nGgfx)WLRkvZOG zi8P%1v8x_ZRZ3v>Gs+QPKv2w|Ng3yn^g*~-9kg3NpsLk$B`d{|q4+}gMb^VJR&tz2 zSNWWLfoG2SvxUKMMqci3gnYcxICQH5YL8d=mpHLcTLMd#gmqR6UB5unr1)EJ5$vP{ zbRVa$E#z&+g7GuY1no`>VEk<`qt%0QOOzgNEwq0Jb3rnP76=twdM{TbAR)IPi>Dbw zfZn+ET;#Q_hNk|Dp!;6(?Zo0u85~Jd2Z++4QeYMJ_I7epCHvFLaimSlw%aoXfAZ2%8F!`JzqHugI zsHR-bfrq!v&sWqjN=C5CNyLUY7gyt@T;7g#`f9}$SO<*m_fPRxUO>Fy%N*9Iy4Yn} zhnLY;pHM+*{(F!&%(@F&5p+s0!}W(g0E{)&H=0qQM1D#QgUycLhg4@86_WDE=gXN$Ze2o1ScZ3+PV8?@@ zOOq%eYEIT8+FWIrD_(vb;*z~=6rW@OSO$nm@?%OuhePet3e-h6ZC%NjgA56m_~*|= z@f03J^&c{aoh~thZ)vA)l`5Fq{Lo?>75NHDTgSZ;e6I8*DF*u=qYpTmxL9Pp6=L@n zwi7$Kt%WmLptps6T1Wkdimm{xqn_kr_+`M+QnBRK}~ z{&2^JJaw(ld&WDtH>ZbLX&(fFY+@~F*!tc;P~;?)yVmmg0H6r^p|WEq@3PZODXuQ% z%9-P6;HIhUYhE5HreDC7z&_IQjYPj9jSYDls+h;OaDI+cSkCy?sz94aPel>D`0b?E zQWDK(e^3<{<}Y-dL>MVw#MlHyB@k&7euVO}YN|cgS;K$7n$?3;u_uARA!X{hPvMML z^Cz;jMv$P_R zJJx$i%E{tU8rMLT*aJXq8~Q1pI?otTjMd#2_Wlf&eG>$deQgRTw+vr}qYDp8LkxQDHQo zg`DYMAMz!vhp`b73)((mtig3VOZ809P|TSKR~tyb?vUcA_LSQZ5No57k*I@bX-rjGYqx1jS0d#C)fEAck{+9hGR!oQ zGV#&OPKdazl+zSd6qKyp75HmD#SOT0kS#rs`4$%F2^dbdSkYh)lbv-CbmE554J{A> zSl3OiBmL7qf4Z+w(5U7@7;XD0T;s8LI!V#7(_*R|pRjQbz2%$6pd)T=!Sei3HPZA@ zYp9Q{XKt-iowR>rdrZ3u#Qq7XRan9xeA)L_itp_Cz7>lQ9U^PGmV4<$G=`D%S&m!j-4kzMHjrM#ITBnS|GF3g2Y(a`_r6= zwmX~SITdnrsjC{*xl0>Rfk!#Sjpo-wDJ3tJV8Pa*c3@E-3dq6yus7fo4}6H+i`wBi zQX+ok1c5E4S^ofs@PW3hO%iC!HN*UkQC{>} zHf`4X7WW*P_jb2bX(MGg8Uad8_D?qFG6(%^WL1vm+UD(#-Qt01<6_BrW^m3VfwOwi zYP>A@e=C+WxPo0)`5t0c6z31g8o5wNc-rs6_<#AmT$Q@SEHGg}d1?Bv-E)e475$M( z`;FLc%v=-r`+bv^$XM>R~B%n(*f4iHCFp`F6A?S5SFAg(A3=JaFpOvb!<^vP#{f^nTvi&}b zhix=ADnbx3D%Jo7K1(otGEcjl&L*twjx12!CkV;(okMBne-}3h?NA6eWa|uQDIHAR z8Yh&YH7*DB##pB>5yV?Tx@>E7B8$p49EoqA3vy?PGLdB87t5o=f2zo*vsb~dW)sus z^y&jB|KfzKHJOHVuGub^_9A64=IIBAY|a)g#_nT zPz%GA*XTU(n%zEa+*~ES1FAzZJXP|<(9!~NSSA1b$r_R}<`2MFa6uK+o3luH65Hmn z)Qd<5zk0y9k(gdeOxs=Ux*4gKBKbO`p$W z)G~nq7!9HJXH}8eS`^!E*IljXA3c#Vu{{EDEzuGRH~HXE6XCl&eszU*zSVc}mB*ne zkg#RHvIc#?*uii923Z@C6nUOvJZ4Q>@O*EYeFuV4z~1VxTlU$~7fH==E7oY07`?qP zl}uRx^4(jO!a=f5Vy(#vgrU1I%2t7^Y0wUz?Ky!m6`#fi5;X}7`o>;FBVQ(ksq|X0 zARJMya+5&F0DKt?lnpy;4AR)7pgKTdqxur-TJLlJD)-);9U|XK)gVxg}-1+5(Pp%IC3e6)c@N zv1Azlcr?!|r;QVM>_#alxGEKxqB+~NLMM>iVG^;$Q|$)7$tUXaLAA#F>n|=0kDvX0 zOu~Pl6Lh11Vhg+Y(c@{;nG017V-lYBAyJoxDa=Vi=@p-%OPg04+33!rr56JB#@YKn zZ>bW8Rtd?eC=^YDDcnk)!n}Hs(IxPv_k5x!M$idUJzy@|Y{1aowlw!vuRX^{VOF>V zbhl9z+T_7!dkym5YisR8FU5Hk6Y&QVM!)}gAdHOPrSNhfTowE}Fo}Ng2m5_l$pR7> zx%BOC-gdVN)9wrG5GPNK$DYm5r_aK3&Qp1_DjO-7?Em#c;!$cXT5QwGO4afO=#KmA z>gDRb!a3 z<4uu!ed@J3HxhW`f`$s;IScH>+I;;Ijv2dZl4eH?@DFK3`d#ug*_j%cKQxRM2R}<- zQi$9mWzP9JZJ+IGziC7TQNIvrAZ8-pK+FL(#{YSQ_mX?q?`O16i9Gqv_fGC@Uo9_^ zJq63%(7)UA)T3kG=6S)Lw@I_(zdz7;|A>NS*p}>4B{JRt#NS7dlYQtenvWr{8F-B_ zV!kP8iyxQFEF8eN0HO+2OelDgb}58${{=hYU9)1y&crXcF1;StMg`y33! zFG?P(g~5k`?jnm>1I!ZZZ%gmx7O>AIo^gbM=YTSl_3~2cy9A zTk>Dzj#$vjdHWK1u*1lM-J6=2|A%4)0|rJ1%$W;d@Mb@p#MWs0$akmnyUmY0uM3t~ zqM^5B<7Sb_;LhyS%dzEAk+U6|;?w1q9iRn;ye!*e;JpcG%ck9B?V^kWwQjuhw*ua) z*PgftG=#2Q#C?pg@G+snF~G1TV*0y^3{@5 z9383KP-&#l!80A%PIT=3Z_P3%B%Yey11Y(8v*!ZhN2WZ1cTv?=wnAGpbf{k3HpaN8 z2KS-*kb9g!wu-5Q!c~(D6q&un5s({xLj-aKJZ^XN!B;@4W475KTmw^?qom zGdl9p?cr+vcTaBO2Y}3R25z_HH)W^s_&2pvr!-W`K0$Irar~tZD3$xP!)yHV#e!4< zhVp^ilmfYuBh>TxmpO_@`lHC3=bX4}WIcg`58-Y+V?{_DHOM8}>1#T_nM%Y@WzQk? z8VbHG}wrIdZ)xK^UW|kwd7Owj}k*YkQ-MXd9F}U?|EhbpooSlXsGo zKd(+H4oR|fIds7P+8Z#t7AJE3{#i`BP)CQokNgR2U%=C0>=6U9TvQq1{gEB$3bXzD zG%?UaaZvoPJ{v51Ro;&)i6*J~qJNtd@r=I1BF%?%(}T(1AY> zrYCC#cY(E))GIXRj~v<{Q<-)hRXb55)71|i+^jX9o5OhG!n`ac_(TfGP+rocZF^v& z5i{NVqkQvAZF^#3gFeV*+!9noG5+@^+|&$K62*UA${?Cd89t{)6XjXx0(sUL*W+)%6bO<2s(!%WUXKb#=0lpYF_+O_OTq{ z>8%~;z9BK;{bXB*ofdgw{_QZ4boP)7B%aD=g|By?=~d$I$zd8xzsFP9uX9rxYRdp1 z&U*S^M%F(ov=hwuI&xnBQ~BoL*M1!!qjw-Mt${d+nG2+X{Ogt-I3Qf?o9Ht{jD~?- zAL7%n7b9SZug!pBcVrIgK122>8g8|SJ#fH11ca4o%x42y>?WQ<2qrVHwUYaDf`P6X z3erQC@xR@RB3uoqRMtjTUhxomnZORokg?KYnKz`ndx-tYbEp|NAXH|3!7x)4I#kBN zengkp?BU2v8v)@bKVU|2n16Ek?$aO)>?OQ6NY`VQ>O&#rBh6bsfv3=uy|@V;>SsjT zlwXK|J^ya=3U&{G%dAiBD0jjIIziW;iLoi8h8fKyom(&=`)FyBp?|YsYi=;g*wOTK z%PWW!+?uv5!Ee4k(TkbBC?R}(yg&@gdhgy+{3uzQ_on{7M)XuEvfWamVY@wGe&qQ- zEvODA;sgOwUh``KoO}?%Cnfo#VHOm%iUD%z3=^*3_^W{`nb{=bDo?LWG%eaiO>U>o zGtwBfGY)kb$MBh(u!HnW?UL|q@dvb6l1o0oKTd{p5}`7TS~`E9CiH?(nAF zhN9ZC`e5T#F=#9hCV{^f&aUEBmr!Q8RGogW&sK8fb(@0E%x1hn)BGF|QW^iQEGB z{mSc5J%hHgBl}ei_3y^>=BZBT7WZzx*BG@qgimWamG8&%F6{aOL?$_EcqGHP`7?wW zjTJM90Q-uQ3He;Ss|x?qE)k=^QYHo+>g)kZq>GSw8!>pk2xLs+0LLtZ*TR7YxQRBp z+>+9B>#Qbup%^;zi7@zXyw~`+y(e-%GiTsI^<$nwIG>N;pcEvkMr!5%>)v4u1`9Ov zo39LOn&6Ot^UqS3Io&ySwa(o~c@H6m_*!BR!-xVmTFlkU&+R?$XnGUKdEAIvus>JF zG|<cXM*rw!XJZGfme!#U>I1TI)C z%Ia$ut&~BFl|#&3>`Ln@pVO2ocVU_9-~&hp^YHfVE`4g8g2Ico4rsT1#F8$ch2NeA zmV`niTq#3|A7pmkQR|U9(t@s$#oBwCBxUAlZCNPMVxUct6FaX`DF&|1a9mU zW13|^uSpVvtdvN>{Uoc=GrnMdqu5Wyx?a#CXWEG^3dBWHh2|RJImLJ6*?x}|Vs4e<)hMI+32vLC z1%wwm60WCnUVz-ADkSE?nH-rwQ6zMq;MBhQ-x#)n8Pt7=4`t$BYYF~d(DdQb`&#uc z4LQhha|A%W1iI*L^tp$Wx8cH?@oqI!B&C(`zHVBlUeN)a+CtRDAJ{)V1l{cjw)a3U zRKmXI$KIvW!a&5+Bl4G?>XGC5O-SeUocw?3Uv zL?VXDGIO$63U>HYal;^>a1nIzk(+jxuf#KpnH1IV0XBWGyEIP?T4pE_qdgUMIrl_N zjZ7?EA>x_93PhqrA;iVZ`%asxPHj!G)79U%xnNjr$W&as*4!eu~!hn0XJRWBGcVi*49j zoCmfeWeQ)2kGjUe&Dk4cgC&w-_~iU`&OOUscu2atyvDVsZeqRFJi0JlAcy78qN9>6 z>iSS8f7}0-p3p-2QVa%t-DBWmo(^1Y^AVqI!o{Drtf{^!nqnnn{2z43`6|Iz-h8Eoq5yn|Ha0p>7mLues@y0C7IvC8COUINs_tWKS^d(=+BcXfXmUVETI< zjPE->Du>{)IUNrs)D!Ng^mlsjh0lXp-PP$pVK6PK zx_%+a0uByE{h4pp6i_@|JWW#~y80H*7V`JOmzAl1vHs+RK$nX-81Sf5e0d|NGwOFlULr^2Yhf}k^T5_wcib%Y01@U)4oebaVNDWW*2KAFL1kI`9z+S6Twem&&(s&L z%ZB0&Myd|J>V)r9NRB5%!2Lzwr62~r%WQqvYr#aF)3dIEOcO7lXK-)uP-5YcHSN5h*=#g=vxn>8%7ZSBttj-xi?WdR-^m>Vb zVE9{>hwrZy9@11DvXiQ8dD}#1C9%=KbttF zOFm*AuyOIB?I=v+W}csnq;g;D8g$6>855vo{;5thSRN0z90 zE=Czp@XfdyyvqcWsgxCrrJ4!$Z7_o+RlGiR1MBBXhV=JM^sD^Kxh?hTpI{*EmS@W$=rpHLN{K!%|zj9VWDTm}EmbhR` z45vKiO2f_D6{1aTsnq_l9oV#Us}e;GCxpCndxuApMb4pp z`fXn2F*8~<61@7m@6fj}0fOWU>-HcwiJ!Lm5%5Z`{8_S1BGy)1Q^ZIPwX@&c$HFRhjd9(r@2%M$AoRt2a%qiWK!umM;JT zym+wp`SjC8OYkXKd>(SuE?(@KD^~QUGK#eT(fhX6FZTBzr*>gno%pI!r zT_6x5b-$cI9M@BzCfyIkh|4_>nCV&STl8CrPwQ^%CyU@HIOCyJbujA{OV+(eYMlZm zdnGlm7S4aXpSAF9KIRfz8eVfU^vo<@21q3Ry*|3qnBiOq18g27_L635Vti>Kq-;!> zW8}kjTOWsFfeLyv<3pVfsikqft(qhXJLQE!qfd)Z`5znB1RK;=4UXTn9&tilA~cf@)M;2+|Ju%bJSwNp8iqRE*> zs9zqrD7^3ah;OVBY8dy`U-9b&Oh*_!Mhc&tVtu}+HW)RE#piWvP(g^e@{--hUxXzu zy4znUM@*i2z{&8A;G|vt(b5i^%+DbveekTgoqovMa{IfRS@9o@wd`3kJON=AQ(Mya zxiWX3Y{rF#Wylo0@^bp3fEPQw7%8|6y{1hAXQiH!C~k*=jnMV__z*X?ywbtDp~8(v z(F)0fsw-IPyyd84 zHa9`|Av)>dmQ{Bi*ji*t(dp&J$YzlzurWWEIW=4tU``GAy489#L#R$hcqc_kHv{!s zr5T(SBk*_X%Ts=kAnD__e0!mWbONA%hJ~~L@J1&ix+@GTiRmQz{j2Hoe4HKHoll-w zfT0%W#oHIJ=zn*)$(@+~w(D-!YiEQr@SHR>CJ#U~OOkY(U&SYO7<=lQ;1{{;9V{xV zkcOutUg?GAN7+V=b1>oKk#a}g4bJ5(B3V-Ed<(#AM`G{~=$2tYC0DV_2SOp)2E z!(ChLpPyY0{Z#&sM9)#)3Z}^ncfKT=RDJ#N&F0f;vSsUy2b(lu_6-jEHU%l04S-Yh zyVU1^kG6mF66>4qFk~bWPEouZZn<@4dG58U{-};z=rif8@Lv^NvVAPQAFiNyFiezEl>?5$&5Bh`8pWyjORh1I#M7du%oDWjIE9XmqJvw9pbP2 zf*&|{=BL>E$Bt8j#liaJW&z5(%mUnd+RbFaSo;{=>nC7WqRtjnca=~;1t3d70Tqxj zms9?X{Gn+Z6)B;AlM^!&4a-N!t zrU2e^*?R?`<+1kzuR$6?Th)NHtkB}Q>Mg)*o&bNM?2#1|l)&8)UikL$^@R_=O06S| zzf>9V=)@JUUSNFr=;}Mww#CUk0M>5{g#Lh;#Xrk+0^j9L)AL*#K~rWM#^;}y^-X?H zO&HO5#yf61dp@86=tG@K*oAXe>eQ!1dRtd*Nd?a*j85fAXL>f08|bq@fYqVubY$Jd?` zV<9HSpL>06@wsLDige{rQf!PGp{2gb5BH&R4NU)qHBmraHU2e|RegMl=lX={eIE1# zb2*}wLhkgL@zeE^=R6fJP|2r#1UNM&uTJhXz0?6P9b~*W63$>fu+4{{ix;WfXEc6- zwGLA9Tpvbt#WP-QHwSQUgEvN`e+G%@RkpqBv2>EtQ>>4t$?CwF z-Q@e^CzjH$gGq1{*OgS)^)fBaNfJgGr0$VR(2bf=; zh9~qz4Mu-uuK5|iSVYe*)UYkX1V=uF`*Z#*(DpX8_0#au!+%#zQ0}^14_|oEZ#BF+ zVP#HEuR0wLfPOQaWP^-k_a>5k@IF!nyVn*)h|ZvLJY$(w1Mq#~NSoM#k(X*5_}&Z_OsZbm{O5QCR8?ixT<( zVDMJv;)p?qujk-E+W%wg%j2Qi|Nj{iW1m5?#n>Zck4W}?D-w~NvLqygvNN(TS&A%? zWTz~lYzZksMaUjXwh$$LuT%GS@BMs#k4JxWt2@q|^FFWT`FcK|ugGYlMgAO|8f3ms zxY-&e#2<6y>cj3Z@%Lq0oTutN9sD2sRqHs@H~U-y!;=!6 zRa>ygg(e<&+dz>|s=q9^AIU>raX*j&WS!Dn7^X z;XN=V?Jfx!-%mIojY4@vU3z%exb*C4o4nMBU%BbmL+nsHaBhZ_sl#=0du<-_x-N*fi?9<0S%j}l?k3Z)|vIFQC-xno%XlvBggnI(>IyOtr zrz6P#hKI5E_u%Wle~Lk8jpgt)sqUOTu1=W|MY0rr%BrbzXba+U`4s}p-W&=jtG!i>=y^Gdcg3g5fl?`&1%aNs^ON*Ta=6Hk96>XtPYfz}=`1E?4WI-ZJm(H6Yi!_t3bF-h z$NJ)#x{oHsCNKZw{`Y8X%npDKX58Au3dc0=h_K1Yv0B$SgTJYib0F38Y*|TexD))7 zGeVPgEiGxmj*Y&xHTgozRn#9}Wr2~bx;`{zGXSKG`0a1yGpu&h6S@GCl*%Usz9sVC zMlSm}UK*0r2c2Zm?4-PD&>*^<0Itxw9Yuv+^Tf z<2g>asoQ^5XTi`+(b$ScGBAY%jD$T*4-d;V-v)sAwm+auRwbsD3`9wIo#ty-pbAB9 zIfR9mDO)(r5V{mIG8(oE;@A9+o_2|9mf3%&FvjPyQ7=~l3OT32k?D*&6XkKA&AJ6n z)GJ%bD$)CldPM#>)6O5kwO%If(q&Zyj4JVji$P~NpK31tUE~QE00h;6mz)M>_9wUm zU=V_eNu^Awjd9?(qlfe*%dMXUo72_AEEnwT#>uZ$BGo04zWC9Cp#3E` z>vDXRc@+o`8Rn3hcHs2d#`}URf_!)9D@D`wzQBT@!(|2`o7nSZj!@Yt!MVku<3xC| z#Nv7MZ1ehrgjfXlkP@joGqwh!ZM~F|!;PawMukx9)?MThxNE0cO07ufxMstTM2R6*Gs79SMx@6CmUzjzxFzU(bFkjC!hbxE2@i zp2x2uP88OtDz0=qX5l1m)W@RspjgJnRGbvOpq}s}nZqvZO%5+^601;ao}4FyQ?U0E zL?zP82&{<=s!(8&4UVuvi2Z{y%sf9j4pWsU8!ojfxxs13_xbS9Df!}4*I{ugYE@eJ zd&V)cK@MyQ!XMgJir_d{9x>W6`fOYjeMF^aEsL3P_tomu;p*{25J}X&GVhtnY5k>q z=noDWOZ3Ct3SK5@I1;l~W-<;2yrGxTi9waG^EJ04eW6?&hgq)g?lRCbv(5u^=#8#wYQ0|?t-R$+a zGa8p*wp#)3M#Z82$IWt#aVyv`Iu4v=@2*dlM+fu$L-ZnbejCo;1TXQ(jNS@zpu!%3 zEzd}ayYe8=!MoD>qvG<7>?mICl$B+@Us7E>q^jn#T#HM*f}fww#hqEt5A?h&kp=#q zTLmDP{Ib}cCl0%ri7uJAHLj{(7c~61&p$Pl03RDdUV(-S>kPGDy)>n9(5*tVa%EE|*Pt?>o#pT>`JrRnvm71|gjb+s zvI1*GGtoo#E*E(QdFszX(yh93h7b#6eLb&3aZFp!yQy$ol2V8#X|OTZ3WU%=m^+mm zf-*(AM!t0)m+4Ax;BImw(|w$o1X}&7{cX?;eEZ7Ao%3#U|7WPNQF8ZGA6CJlK!g4$*v&X)oR~nZP2m$f>{= zRtl)l%64Ssz@rMRzoEo0@Z93(F%`|$kM2hoW0>}-5nX~?Eoo4znWg=&VjlC zb#Zz#F0R}{e>FUYSnWJIIJrB`Ffz`;c;6v0GF2a>Hc`m*0OpDv81e1kVV4~~KV;mQ z4P19`d4!4B;rxY)b~p##|LRV%Fsg?oL!aK1*}1zraW7zsmYM5ilm9oGpGVFftBhYe zdvzpuiYSYYAv7xZ8wW9&qVDY5f3kh6SXZeF&Aqqqv$_A?ZajGGLo)IJ@h<%BVzBhoAClczNx!tE3y z`zD!X$wj(2TeA(=TzqkARLjt}#?J}Gsqtk|%MQB7DPExZNpODiz`?~YV8cE1*`w43 z_iJ4A0X1%C?BJ~RV9+&dj&IK*wB?CH~FBu}!qwr!&!1 z5)^6eqGe{)9TuvQNkqhw0>ewT?^haB_TQloddf_4WIx;5wGKJol?mXr>+&vtkf0o2 zsn;p2p_2 zm)X{e+G@wuZHD;Zv5vybRB@_j2h=8Y~fM@JdQz71mj0dp4M5Iq#&=2z-} zJM>bETTPM31?7z6qw4kw&F57wfLT<|K6v*&;IB>JvoB?m*3nPc{574RKpswRzVyEz zPBXk@F>TW2meo}En#XfNSMf0OxPQ$TctVat;=mw!bHU+u&GBNabTBOJe%(mQFZ&Mc zwp~x~I5cmz3&=Ju)lJiwmn)yb;-fkMS48NR#pY2?Q8BCqI%(=cZ|feTEeKNL-E_Ew zctJHh6~J;~#CpgJ^w+QEKn&mtth(%2YNds+duJXPI|14+J(zhR4(_nc&HGU1H8&VD zA3;G&(FD_vm-8@p3K3vCaeW8GE8-68s!sIf3r?e&7(8{fFFY9F(cD>xG#1F;Za=sH zYz2u=)e1jh+w;xB4W}FC9#~^JZp2HHW+DxhXH(1^^PiP`ukSE$%rI6 zxxeE~_Pc4YRW71EAhH2HrJ`4o5?zX08C`iCY7Z9d?Tq{wEgRUPYYyH6kj0S(@Z0%7mM-1+%W97l|;nfr=LJq{gDk4M)*O$ZP~+929`L z@xlN6^f0aF2*-)(i5mx&#-Hv5ld(_CegfUWWoW$zkacIV5qm%)-~Z9~v2YD?Bp=^I z)S6JUc5gu=VqeHI5_Lh6mK?7c^^Wf9WsMl7lXdPk5_Gas_jzdNQv{4e)lLfNghlo$ zo-5>qv)I2@>GJx|VSJ6Z9*=w97Xx(!4T~fp=qot4PeK1G5o%mwx(ZY0kqR3XkbvD4 zEBQ{q5znX@$bFF{wX<^G5Rkj_$?Idr`Hl;uEPN(txYJi)e6loJPv1g-VeM@j*^2;V z!2RFe-Aeb)|Jw^7&pV2C-v+e%yfcC(U;Aaa5~`3IepsV8{EvySuQri`+(Y&rtSOhw zGk*@&cF;*yYNY!GG=daJTgDIE=E(KFc`1C7|AdCfH~`a@fl{rKX3KS8_(XqK{3NP` z$sr0cMt`q@9dU)Oj^PW$C!=6Ht~q76loDU(Ln&&UraHV6Jkv%4nSa=WQwG*h^mV=y ziwEQb@<*C!40x8{(rg?fo*bM)E!t?dxWl;o+5Ww`{U4~7phNkl-pJ$I=N8*tq(Kzj z2KLAv1Uwa)6?t_t!a{_ve@{q9_0w^$(nFi*bsw?K9;v*)pSz+Uwt@@izq!~_&YCM% zEoku#=J`KBDvywAfiHYYP)I8Fo#7+r)2iy!M2%+?_B8F779M#?a>L4vsOxA?&I?*~ z(HdTF2^w|3b{X172kjnnq)cP^%?I(-56bpA(zH%@VppC+i-e$PkA^;FVSv~?4N&<# zppe5itnKA3H`sW#8t0y?9IiXp{$BUC^vj0eQw`_mIV%(KyGp~~pCs__E9S~I1>F+GpkJB#CLVMU{_|~G}Xi7Yq)guNY5DkI&o)`u@cJrKWIyjAG9dxS;%Dew`P{nup*S&LHL~nC}?e*>=9? zq#DG=+(CD;()L-HxXKFE`ZKM&w6;k?Z?0R>xj*^-gl9tKb;^_u-qFf*_7!0{^zHng z*YBU-Bv>M^LHS(in}4X7f1?f&8365|L* z;FX_x@!yppf8_v0KV%-zbf?zjUo`nYSG0=6$$)Jaum8`dY94`?C;#2ox1ja@KUai* zg4VyWyz={6r_`*0cm{~)1rW`F^v(>X4zN$J-G%Sp1XuVj{WsuSIBnEDGKJf_hm!%% z+>n^i2e5z$KqC6$c5nvuT_{NV*p)mN_{I1i6~V9XN)Uu^nRmk|3qlAFf3V&2xSxlo zt-m8%C^j)AbZ0C))IfK!273uIUHVJ@bIo`q1f$8JRUd%QT)CwP3!liln|CXK0s=Ad z{Ul5odTGUJoOf5_17WPonj9v(_VK05?Y}zke=06^VgZwg=XyTKB^(b1oW2-j>j;?$ zpkX-h(thjnplv|fucg-ApUZ*e;M6u$VFTCIopuCS4E(P6O!;v2`Q`Da;}`G!2CM!# z0P|?uIr6(kU-21|SdvvMghuol3ag;WoBC(xgOJhY7bZO)?fuFy2HLGLEq zZE+kM#`4b9j~Q+fe|y;c{meqJge+d$D;+p{L8Mdx$(>@3@9LC~Bb)9=V6A1I2vq2x zq@o8-U$a{4s}(5U0z{^BNWW3o-NX|}D8n!QH5UGJnL#IrdjVK=TIV?Zt82vipmKLU zt5%SD(y{ey!n(h!kRoEr&`)rkT0^`6V7E&zLE}upD0HEe4l;$@Bzj+qnf^aMw#Y$k z-oveo(!GY&e-Xn^t~K~Vc*_?IM$}AjFt#vtl=2`pHavabr1U)y2fw`K*JPFm+kPjo zzMY^t9T5|Xpfb8BL;99gFgVx&I$KRP5N7mV$lOPW#jKJS6t_s9&;j-w5_R$3ZwT}6 z?*jvf-ay8$3M;UlNyq%j**Z)B`2&GMEq(fhZm(Xq|6OcIQDaE}BZ3fVd>ZE1p4(pcyIbxheJw&M4UA$zqn6WZdz) zIP|p@tL{Nq6xRE{#}m(syqHPxQc?#tKuovVdbK-6ps2oa@a&rtyQ{PxwqZHh*O5z4 z4~p5M>BzmWA>`+VC778P0<8qxr8|^ujue;@V?dDp%Sm0o4;s@lU|jQ!@b567E8EAo)PEH?VQ}H97Lrlpb4NLO#z_tw!<=XV=OlJ1!>Gjm>M5t=2Ci6W4y)9H zRwiMsBFyZ&G;^T*Z2a<^I|$HDtoOTE28<2m8;GYT?{>iZDXZ7j$}rF1pLr1U+nk$dRZ9=l_JwQxQm$`}L5~ zRnB&YmB&M631b2+o14brGrWq9)Y2aSW}h5WaK^#zq||dSKt$~Y_Qb1e99PIC`CPAk zXXuN7p8Hqh0ntodtn1g6iv!F>uVa!ih!@nh&rg4%<_y7TX5ZR{GdMan( zD|4UrKWPhp8hJGy7)Teqb$BQ^#w(!MR*;Bt@S^X8UE9=EP6#g^<5_TbSJr6U(ypQ!ouYZ0Q{~0L~LrJOiWjOVyi)1}#@Z@ftp zZXwymaO;MaMo}Qhx?T=*0^#jP$QUq<7OR&YrJ1aO%m}dzPE|aHCO{YT;wJ!GgH%%g z@*l%$KkLkWAXu!#mHNe@h%>*Y9kF7sxC=}b7+XqD@SeWM6b#+!`r`0q zz!Wvv6GU4{4^$L=di#0a#bn|SlA@-8-q1oBHOi*&5|H&j1tk3DBoEY(3^osn=jl6pI9p>FcAVGG-({cH z%Yjk|uwgDiIuN)|{Ct}A3Z_x;;4CX`)s**ZZheQqKCC0C7-XFaZHmEdX=*Dvv-sguF)|7iIYUCb+r5FPYT#D@zy~Tg^NQU7aBvMceDTbG3m8RT zcovxW+eXCxn`jNGdlY! z9_8S)9XIu;LDHnLH#Y4~aDq!J7FI6$fjW~A0!|X7a4^dV%jvLK4qQ*o0ICZ?HWx~G zFPerpbdF9+8~tW${9W%UBaefSGbluZRwjRfDbQoPSH@|l8B~($TG&+uW=HzsZgriqpei=!g6OTyNev+$yd}9z2TMpF?&vvM1Van)_NPS_G5TA&cPHPEbi~)k< zED)-INdG<9EFHCNCjcs=B`LYhUhnvu;rOelSmlO`*coz}$wB*;zGQu$qqn;}1vCWy zD@{TI1zIG7SkQH!^rBu#v55=-y?jPE$8o=u%n?EdTLJZW*v5B(QcQcz~Z2)xl#aM9d3b_FAUYe>x{s z{NDrG_H(;1lY)_1((KL(?ek0WFC3i{6wvx$7yzcNNvU)Np&;s^`W$*rt{c-h2V8_L zh?XaSin#B9skjnFT_$#09N<$BlVDq*52Wa!W;%!&a9>ugoCPQu@oC0UKRbh}-q%5? z%fuu;`akb05gd`e=g^CUv3rX_g@Ha7dT;+36dac?@5;xKH1E4=OK1B2P$)1M5j_Ub z)El^^;Ja`pJlFeb9~N|ja5 zG#x({4Q#yE(8g<#1eXP8&^QT@AGW5v29_}!OJlc%=sdCj{YIVgm#15^b>`l!<=RxG(HviV9^`G4Y7!h7Tne3`qQQasHo_&sF!*0I~n zO?|HzE4|zTV6%KI$B8@CUqK9hq<)4j+7{p)ZjHDSi+XWl3<9)x#%v~0409RgsYXHc z`Ux6D_2UfJ3@I{gA5=J*=v|sQHmNiUw6J`+$@#$|`XQgip~TY4LDPRV&@cyu#2#ZH z{A{ifI9E`v@R>*3)589tOxLlz4kvxQ!5w+mYiG(V>fzn8Z1P*Z@d3ptJ`d(fyx-0P zAumz6VnZ-YDMvGrOGaaYOKFRAd;n$*-k`hEzA31wU)MCr7|hQw^dd!|tyF&~q@pj4!wVU8lyg2DCRcLEHXeV0ilMeFV-+-W-}N=KI-srwSBP?%YK zPG|*7nCSoHE*=E2NcuKh@Udq&8y|F_7vxeYLP(SCU4= z70olKI1yBm40@MlWt+094PIh0CzhqYM!-v=TQIS`!&{0HY4EiXiH?(b?^ z=sfH4Dw9Zz1(1WOi}FdljbSh~Z@d_tZwpRZ?zb*{Xq5fLg9PHHeB~Lm=ufb6cz%|v zFFoi)Zyp>fx4A=upa+}>PyG&QRg)B?t8We>hQ^%z|1=#@T5t>614NvT9S&(b0h`Y% z4m=f`#{BReJA~RrzXG{4NU*Mhy#T})yEB3HDp_$xpE~gy_T%6c^9S^Gu)(=gUCXco#+AjViyhN`Kxcz9QS2mbLA5h# zJ#9{k^2>!97S{OrLhD?q?K=MCCsYs(V5cf}x{f4wO6hSjY=M<@c^mWa;QiR72X@dH zATCHF|Cp5{HQg4SjKGsN0K?qLE7I($TP#D`21i;E_a>Oc7)8|+&t3KJ5?)as{9@>U zBZv^I(l2+(XwE7+#x4vL-mU4x24Kh;Cno3#pW8vksGiq7PgDBA0yXEzhU_Q1soIzD z{hJjtS;$>Ci9vN)P+@7z0zshdsi;K!UPmDgp`($0)tci3 z(*9e4X(AMkc_`m^2pzVacoFFMRs9*iQ8 z)p{nJ%CeYw{H>4q$-9o;{N0Yw;mfl;)P(Jn0iZ`lI`VdiG5fm@Isd)O52-G(oDdVsB#a=HW9-Fp}cZMM`>=qlxpJB(p1%ZH-~&P#OTL?24hgtCBwIWE2#|c+ueE7ceQ<+nT2RpLp=H=~L zkg@Zi&o%i;^|Q}wBj&v=m``I$wCwVl{jpF3TY0@!yAWb+%*Fd0L{YDc{wGOeIfI|d zmZ%SZxlcTt zw@HtIbI1co*hg$DS=J@9xilsR#R>rEQ2V_Z{R$~h0fL0-VWgp%;qg7UOu>vCJrEQW z@RCwq^sCNc8+NF$O7);(5+zC`P_s(ouSD6PzM>6YZpM;3Idv$#gjh@vYc=I1Y#2Ua z|L-Ma!D&-IUy!=WGvQaQb6Vt%fwJBCY@%C)d9%D+LJcoP8X`x#hA`&{2P7|jVB@#} za4Afo#s=@YxnJ+FG4iwz)E!H_h%$}CbmJvMiKso@p}+G0=>{}M-r<@HDTV3;vS9Y1 zc)+QQp#_E%iwr*jAL!alc>FKfXa7yO9}C}SlCO@L(1>On$gSr;6bzKl@gxs$Upe;4 zXy(fgsC6_)6-oO5|7qUrgt(y_RqMz_`0 zCb*ASoEUQAw-Cf>(C?xbrG!hjk^aC5LudJDAqF zb1Jd10gQOLFmi;FOjfn1-O&SC58WOt!dKhy-zGP~AWVUzOQ|{bm1Z2XODb2weqt`s zj<|!-(d{sC94deR@hyKIJod&L{JEi!hepX0hzg)r<~gtSibOaHT(w4_!?kzXsN}c? zaT&@dkQ-%b1H1^^g#nVfy0f*)6uNF;HkzHo_q#qt&K(9R&=3bY`_Q0MzK$fRxA;hc zE$TfdH{;p4jts$E>$-QT_uE0QrBUYEJ!A_26ecy6;b>3?sRCOFF))ChesTP1Ab9@+ z6X}S@(V5Fv&T9scDIMKvfstn=WJq8WaBU(rqq=A7e~P1gS_nM-b7n3D@D`Bk<>+RJ z-&b4w(8NltO!x}$Ld}h#~lfV)xn5J0o)f58s4NdVDut_`SMai zdQ0u#3^)grzk`?3zyJZJ6HfuU1Zm_Tcqf2_^Re(>1ovO40s|jD>lefNkz~DO;ovmY z!*p@cl_a?|qR`aoz^m8t?8=_aM1fdw6I;Q?{t7R! zPs={d;Hq1P5;Z9$S`YpZfhOrohC2S$Br6IcgQ%N#9M6_K3k*@MAv1Mqi$?(srES|w%?qlVMvsy%e4b?!wPJaMw# z(`kJLeQqE7<*3qi0qe^xw<L2(H(0CwVJgoIh7B zPUBxLjAlEYs#7g-vW2d>+4$&mPJ}yE3=Zm|HqjO`qCLzAl^rAPtx%Q_IRI?KPltqt zJklNh&rPU8L}tU;IUkRpx-=%BpaDF@@q=qm1Qqid_`m$vYP9xiHV%IA9jL#My|y|rs1>NB)X*}bGsS$$sz zBHoPjiwlgHEJZsyJ+hjVA?;C}d-Aa1G%4)8J#!859o!)nu0YLADswI30oE6D&AjMh zbQa_ehtotV0>PaR}iRX%l0%+{()tcIstLAq+pxCp*j=!o3c23}& zSzPNYqmCv2Cvb+KQmsr*S zfVL1jC9qk;ZYaid0J8AhJ1ff)+7alY5XkECG!%d@uE&eBDo;OXjy1kjY@(KQ?)WC9 zON64%T2c&2JgH;c-aak1`fySZz^xrk%rP1QitAUKkEdn(BV=aiom$(|C5k(gJ%D*n zy7O?_UMY@Lo?E>o1wrBNb4MrAlwHT_hRV^p zF|f5o(iT?1NeD0!)m^FQj*k4#?1_h&`1;x>Y;Ze&vySK2H?|Eox2oSzh0y%4d>=Bj|))dCrl_(w_H>~seSfPTo3kN0JPiwx#Ul~P-~+(%bx&Qxz$aQUf$2) z8^f}6x?5BERxy)8fQX0lBA=vUo-Hu^6sVb40?97R-C|HUQ|SQ}$~%i^VZlw!g~X|p z9JA4Y4A);AeLe0wO})cym?nDg`_2IgieqU@O({+0w|W&5v!}$ZP3!~6xRyoh{f1yL zabj<~7f9|DQVz@;0%_j>1{ww){m{D&?eT*B~3GXVT=J~MI%^e@!8bY}xv>>ea60+H- z4uaD|P%mm}3o*oO?kt^_ryS~~UsUA1gF~y44iY(X8jLLhpge((J3s}+K~#bY&kPV0}9Aq)UX zU9vw&9z!d0bWJtg@5k^21NFY-_#o`}9$&!}d-)yOO+<317^1(v?K~Az3Lm8wmnaz;F*|aPB4%Gr$5U+g zysJ#txY8i0Ckg2KAKt_I*}c-8hHa8<8QDDCbxw6EVg2b{lmS}M4F5^X!v;*iE(h%IMxHzXJ{vJpHk}^NlnEMV&ez~Y@ zm_0IPWGpRFt!nS%m1hhSnsIQD#!YB*Km84U?moH=dMM*nI9 zy(IAXUI9Vvpq|rKkPSW=(U%c*@vVPSYo&p$4CFgIHN=f3GJ}J;`_myC@l8DSRnj zgK8d{M5fQ=?bvEjROIm4TN$i(4)BPn>7Y)dJ~C_1Ycxwe5Wj&oeAM3F>@j}rn@taI zQ0JER#0xt;JF4fQ<~Kc7UO&q*Y4W>bceB|^xmWB)yb7|#{x=z@4l3*4;>Cnf^oq79 zAWV6Z5^fSKTnpUJbJ2KP#2g^JSXUw79=7`$ylJ)pOq-X60v0_&CaKa_T72NboFN#g79xH5alqXilYWgm>Ge+yB^GOl){RWh*-sXm2-gx1w3QS4#KTHmWIVfh%2DSovCYA< z@Nn@CjCb{><$k(Y#c+!KgnOLPU*0j3i3qFCoBC&D3FmRzlkoB%+7xIvK|BN$_5&NF ziu&J$&kCabVLv*y|0r_htl#aK(to~jD$MH89kyIOtI(M;>`Kqx8(gN8;JCzVEc#NQ zVZA3Giw&i^pfPbyQVcg5_a(H1(XCct7F@))2h#^M)oF(~Z_YDR)v78XyV(>jx=$<* zRdScNG95a$Fho@-x`hkP8v2IxOXkvFN+UPtQ@&TH1Q^6^V6N<{xH#crShD$ zb&g{MajRM|QrHiip;oHMV+&xr7IaB%zNQgv(Qxk6KOqZ>Pb2S-pKPQoBN z>(+mOV}H&ICU+{-M-QBhgDen)%farC0&Vxm z^-*@DaRPtBHp{AgI~tl+t>!-Fca^8;51q=#uw%EBTt752>m@_ClT0N9Q}kxXIeqr$ z830CKfn#z6EHJlRVcg>gyw)YMwu|NeB3o?%`rQh>G<`~tWdrYgs8*ws4P|q3=GEM% zgj=D3>7-Kk^>0Phf(XA;_x%+HLF3L8EzFIi?9xK_mVF1(jgq+O&MjN!=~i3=D-wcH z#fIzaiy0`n$3XZ#WgV>_fwH6UU==$1@ID|W85y!%Y+1p|W-RWoSyR*Z833m95!n@8 zUZwm?{S{Jd$NdJ+5rpl?VMpmtBZK1(F2X6~Cee0`d#U4nY0p{M zS3?sLyGeF<6E!_E;c zm`5=3Vmf}JNR1*y*Ddh*g68FNNLJsFLgv>lkQ1 z$5Ksw{28f}MtV>~Cf?yg)9v6xXs3HR9{fR+wCWF`O)Y`t80vY?eufQ+XLIP%$-KuO z40*)0G!WN~rdu((6=ZaS!ud)P3jU)lBgjZQn5>jHpENlAnjAxxO`t$gLLYj|mVbRm zvL4Ot4mS6851NUCEs_+QDP0ijweA#j8GVe#H@9J9 zJwAdXjbNo{kITLa&UstmXn63*e9?U`1`aPVa=gpt_^9 zbXL^Vnyo$RBhZsR-qnB1DDMEAjyB4;g9&|<9yf-eh$k3B}0t?P3QeHiI@xg^c#s$CX3%w?tcajGEO}!8e*Ln0sEaiIr zvpecCeqlOoiYiI_8ketD$5Mwd#CzN3GJtbu3@51utxFO4tjHrRMpQtlp~R#jUAXL5 zF=-r|j9vhAAm00#DhLY`ii%-@z1v*VA}vbVEbHPICH|>|PTdBCvFK;OqhruA?`ia* z%P-32b>_KtB$`FKb^%_LHhkJB{;=Ftk!2@~?&X!2eWdOI!-a+=YU`>NjPWb{YD z-RNh1|0;6u8wdf;uh;l6Lb?q$#o~uGnA$3E&jiEHUk3(ZTiv-@9&Td0507CtMY{Yh z4j52k5#%BHqq7029*rxll*UuMVMVW)_}m>l|21;{mv*<}`qqtCn$dHmYJBp<{ox6U zoQ+ON-*DNuq;yHQQJF?|uQO|OcsbXGm{4A9m|8k5Sp<2W>@Lid8${3!{O8#Q!KiEy z8B*X66+cC#CQ%@n3IPlo5y0@nUu!uOM=@xF4;ix$HGdMM=b%$Qx&LKVB}={_ahR~V z1Dgy2)_USt!sRt!70u*rZA^3)!P34O$-5a0{Lhz?OD)o7aV1h<2yd&{UM+#q7bB*okC!;Z( z(5SX)BfWb#ShovOg6lT0y2B(Z*#?mTEf5yOOvq_GAJu7jU-bXyOi$^+JaKF7-7f-s zf<0REI(Uz?&g*{%+TO&zEf|}Ft<>)PX32+BQO#QXSz%8}Gyu{}tQ~#mME``2iO&Q? zDS+R-zlm7OqkY`vVKp;G%edTf%#;^1d&smR=ilFp4Th&UjV+dAtZ0U;L~2uKL6jw3 zx{OEOf$`@sj)lqetnYh}p!K|1C|+jZRFpAz6mu80tqC~+TErI;O4|=sFJHJ|TGhM3 z{2X|*9!15*MRZI1A(m8R0j0VoN53QgK>JP4oA% z_wK%fb#{`}s2cvor!XG{ms{76$8rbBGt)c(uFiP2JX#NPwyk9t*9g%FU6`SmWdfe) z&445^Ev7bA5Jg|C9%8_b*`r|I75CDeMeVQTMHdTkB5G4rk^{jOG%uh)F{h*4Ymltt zk1CLegO5`VnGu57fm8(f4xfe3or}!R0i!XvwI5bHr1NZDg2-SSl;9EN=?6+^7{GGZ zzhxqNhvmZRpmvNx&&Gbvz5?RpcqTEf)XSQAmB3>cv+JOeuo)eN4l7$V`JFL0Cwz6D zb+1c{n7R`jc1Itm|K{gwI-6Tx zN#B70lP>-tu{8I;*TyAq)td3l0?lUa#mKrM9hQ{KT9x#h zX2K^+oF)vvneT0Nf2Gw?);pCP#Z8=aYC7;d&0xy3O!5|uaQ2@g{woSq@+ms^sxy<3 zAngQjQXvO$1gsE~D}8a*WYE?=K;sNBx`w{tw1@|w1H;lb3`{SYqRGJxc>gSB##$%k z(4-~el7wHttSonc|I*Tli#_xQk9n<>pV4EXo5;R~LceUK6Z*Abm~iuRo8Y}V?d82;-q5LM8A*d-e~ki+IAn^nocPa`634aXZ&OZ60_hLl5Kj-$E05J ztDK4fwb*>d`8!U-RlKq?f+i(HpeF?00I)=AYbxV4!%BT9^S1%~{Q|y4iY=vQkkimY zUPprq2*XbSxU_zV8jci-% zVYv_TO?)l}uR_oa%jyTKWaaG?BYn=+7bcJIQJ5FOQ{V?7%)PnG?;iZ&V&*AMfM1i{ zQ=1w#pjMDLr>L)Z!Y99tcqJ+)K_i44W}$1rY>K9f8dmX)Yt$^AMl{t-^mvDe{n0Iw zFg(+f2!@j}?0eYj5(qy&i&o7bW96`GxbH0|C{=wEQ|o|+IhT1^KSSK1(?HiVdr?IC z@j>xokkfoEM{E_IuVYTPpN*-LZD6nFmi%dU@O zpE9x;nbt4@lAQl1D~$h_-E+^H)Kdv{Cs2BSlP^{A`!Ngxr^2$w*n}J*4jnRLX+v|e zs>`2fRC@wjj&>CIo}UMoo3_tNh=!;=DCqNY&_+v~FR&LYG^AQg+TB-UVTKp^<&6+! zx)|=ZVthy8EQAg{>XBW@Q$DaCS;PEYVbk$gw@9Ac#n*SqqHUeLG3HFtu1vR{Ksm^% zl2{s(kDYYiG3uEXrI!Iy>q?$<@uW70&T7w}zY@0gd{wb>QZG z1DkCza7(tB9M!4ov@!B_(aku)=&d~nB8niEJ;y=Sy3L?#9kr)kGq28;zgFR$cw$De z!Tt!bVL(bo8Jx9FkQ~XSH=4~Yl#&P$>n;%OC-owY4+6333b05eer&T^)*@yg__Jtt zu?j)aFuI0R%m+_ak^<(#)q4KT899eNpuyg{%{kiPYC%TLVq9h41=sejl-4xPEwXV$ zV>m4nPy7<=h?)_b%|4<_m$;Yy?Sjl=Bb2C@Ut)=0>)nyhAk+gvk#p;580|1?&ff8c8dt2f14Wql(bg<)ZT zNw&@K5w`|vnU=XwDdyw&Y-yhB9Yk>63lL_``K~ZuAb635+hv=#%57ZuGO5RR!>5Z5 zr4UmdHS9i7KnGt<(W8!j_CPtGl#UjVGut;q2Q_0tq;3#s{SS02Ers%>QE;+;42roJ zG1M1Wytlk(Z&j71+j95Oj~KdaBi+Sgs`yoM)2IVa%A!t~zl4DVo(=^ByP)l8P0pn2 zUga2H4SbA25w&JqH?6{lR^Z%yIA_gd$ zo*VBN#M>>faQ8J*4S7QTt!V@L&|#PvFh6@l#{6vVl-JXP3Rw+>Yf%*(B>R&>p#@Hw zmUT+~-RcDL2l>s8ALcqXeTD7%>m7xf@&&F>`bXjMswy^L+lW1bTJHHNr*fs=WEbc` z_Ir;Ccf-NVeGQb=_wKk;tutJ`yENW2I2yS3b1BXbXi>YucSkt8qwPaP?^<85_OtI^ z;YeYjBVgYp9KB?DdA}98JU6O_>eRw(cNy^nRHz5U&X@qw8%dhmb^2`H+DlJAB&f2q zS*{7Bh@5-BLD+`c=NgOsx52xQ&ik;iKT6|m<*oor%s*W0T@CzNU(DXd&6BO9VlcnI zK5ut(r9K}q4oJsY*Dh$QJ!U=WLj$xNMgRV{IcpbQDC|=v1< zLQ-&)XmM3#h-tb(LJ%ess;fV7(d1o_aCOPL+ZQvMgSSclB-8EZT>X|_f3HDkk5U{I z#^ul5hesbp$Lm|~Y?a;N&&zSJfjFrIH?HN1|TQHU#&)0!7 zocQOyw`Yn?Vs{vr1OFSNfsq8W&0!@gl+jN(`BSKZBWq}8RpmkA_N(PM+caGXx^G#87%YY1kd z@bq?k8wix3lgA!9QZ|yKJ`Q*cXdz`F6bU^gDz&@7^}cK{=IJHpB}JnwrXoszy=hv& zTA5QO3r-2vKUVH?_Z^zZ`(&tQ1@7;ebiT?HbZPe&|MmiiFmP}ztb367QKGsSRwjCV z8P1A1@WtU5(eszW^>+Z?!!jjID%wQ<>}6v&K{}Fpl{Qr=skuF^WQG2^W-V^=JuGOyyo%4*S!UgqEtv|n!~rW^_Ha^M@C7>egIo!zJVLGf;;rXJ-`$MD zQdOOu`no(2Vc%54C-`ILkhkVXN-z4w^;<_TxxL6eK=8p-R=#UD3pw^5Nw~2TIZ^MRV`SFIYt9eaAI7`FR+3a+Ev{JN_4_toTC*n&OEXM~JiRsb; zM!e#P-T>4W>w-FjY6Mi)+gONl9y2sxNP)hvWy%Fq(YbCm$JAu5lvjGU=e;{OW!7^g zTrz}pR2{RzEII7!CMa;bDkWzh-O#83nl8wvwyk2_^kPnG_^fem^8M10=>GHiw(__` zMHYFr&<(P_rZEaUzqD)m6}QSs$SBq*qJzVRpLW;y<4o8G@nB_UhMy9vx8}^=;ti+s=2{WcvN*N&LNn`*#53N|-|X z9%tXCSO{xRt;q(F!H9TxC%u?ztxC2#Y0YRpr3XKo?KaSAS}q6Py57hxqMAnEgU#pj zXgih8?xc-fgq@+TP(P;gN4{S7xFV^XkE>ul_;|zeuJ`jBW@f2`^xU_KI9$asTrQI# zH;Wb$TmKT$BM6Wosw1mKdI3HkJS4t2t)Le6gWi*Mz!9PgP`XmaQmc>Nb3G@F5L+hB zIb4;3cl~lOM+;PLFYEH>T>l??U*b(w+s4gt%tPlGGRt@fAwne_9Ak(w&qN|JR8mP! zAw-#zA!8_W5kiGa6VQM>fls3pF_7D_d;yM9q}|x?Qbw?qW!GCXD#@XNfQ`E6;MB}nYEa+CvEz&Cb=WWa+Mq3Sb_)%Q=+5EX`|VUE{g3=# zfU9RbZSO>+*pGs5|C}Jm7yL?S$hkW=H>7q+8##Ip#G`VF02=fswqmNn6>N!QQkOxC z{n+J12vjjuP7%=H`&nyMHUSl<@3p8w^4XO+$4gu}-q3joWjioU?_kb(_KfxO@Jv;1 z{S*?dYTcJ_EM)z<5FZ%TF6C0GQhTc7n@7`0>B9t$&a;OP(dvi_-d&P^Oe0J|PH4q) z-})9)#J;~P75Ph3Y8kdqM%SBeDDXG5UqSjE`r-laMK-HdXd2j0CB^)=o@ETV|^XxO~BikyIT-a19S z)W^I#%FO~JaNa!5!|oiZ{!SyhtLx+dfl^yshvIu0%BZx=iu2Gz>1ao~|;3=l0TiXkKGd+Znc z)z@L329m_nx6^H})0`2aN{7?aQfYGZMch0qKZ~&W{EO$;>Q!!l1tQ|?-zpX+e|C0o z;B_8AAYlf*Z}QJ4$+;Tl_Sas%Bu9!nqVgi|9yEvF!c@j!%bWudMeYJvVf$ul23W17 zxA^Z*rv;DXq@*VX%AaER%|s>j4&?&q@Y?TcNyMIN$}RD}V~1OMdesBopUxY$9roIF zY&O)m10k7fx+2;^WSN6UxkdR&V;~CYTx0KKXn3$%bgPBqz@U$?p(%~kZ5o0hf*{A7 zk7ZKUPu<@o#bR$;En1n7$qF>$vxq%wBA~CEKoj{o2?dvKnuFm5(ObuwAQR28|gCP+Rb{fxgSDcc}tPEEmL&Qy$uQ z)E&A_v3d7KC-&B5OmI<00LR_D9XOTxw;hh6}j^^mVz?OFJBlBIr4TR8| zs9`B+o8Z}hmiPhgHb@8ru>6Z@$&S$3eg07G0X5GEE=U5M$hw$|IYVLRMzWVt9&d?( z`a#7Ql!zp)neq3KHphs?`Jr1)>L^OE$X-9~DesKXMjsfM~>VK65J%MB{DP3PF9x!YwK<(m(gL(yB z7<~i`gjudUM(n@w!swM}E?eWqG}*fX>Cmc!0G|Kw68EaSTKUDD<4su4o?`9O{`f5U zIP}aH7|D%OW~}d9rHuR_Q}33k9M)Dl!7JaWg?omD=WPdm~y}MO(=#z%!u}XUwCIsN+9f*(d(UUKy;eJ-kk8 zM2EilUjKb7Mw@T%JlA7`xkiwYobdV~O?cJ>Jmwt~+H<>~nD;daofuXv!)&)}y6op> zI*x1EYP_PNB9AVC{E))(? zRfwDM(mug^!mMN>GANzg6vsoZ(m<;yYM=w#s{n23yY>Zd(Yr3kxvIV%w+US8(pKF3 zfqy33k}bO84X2J@gH}GGT;3`nA#U>*=gPiQrIJa1$J&FVn!@Y`g#yhz z*_Nih-Sf<>@7cH|k3;4~QsawrSI<*eacr4ic1O;`&R6>LR@EJ;*@5DR32$Q_Xn4_* z&L1B7+W+>avC#^)ewzi0KC@C&zH})zCKFBK2 zc#uv?AZg*k%caZ1Y_KZQg!@8PMnG4QfhVFKcki?i3iT?~S=q_C%CMlgdPsU(TjM1X z86rx5kgJ+2@v&kf$yt?4XspPuN{_Dj)(x zt_Y!1aCmg&<FP4ko0DJC*l6MI~X<4KrL*oY0*thM_q`*CXPO(-eWz+zuLKFEPQ zt0Vf5Fohc&9cb$N=nh1JpD}ujo+(GH14L(tt|p&lNqeB$Vn40ficke8mr>rO^3irA zPlvXBXDSYDa<9Nt$}fG8dDtW|jNng~tkHvRTsIe*4t%Hnvufq1rJd7ughtnIq{f^; z)_!-5UB(n7!`DBxjWaVdZ`h3AzIWv?k|7rc_?QE`mC)y~o{w3B0&QtLUCDNdna;A~6i3cHc4!GxX^d;ZDRj#5zY)p?l}0 zEK2JoSv#&|2YJ4K1}8hP|Iw=CI7#QB9NxZ`4*WGXZ?i!FKKI7aE4sWU^PwD8(i1~h+z{z0b2q^<~ zEZaTXm!S$`Rjs>)I=qFJSXjM@?=~|f6H%jy3AYe?xUbrAKOw+ zKYd{(n8ZfAc>3A08Mx&c3t(1RZp}$FiN~&4vmB_;Kcs!`XGFwZmZ48dKX=Vm6X#ln znnpc@F#!ij3}()%yw7T9d!2aBOFCI=b60Lrix08v3(zp0-PU0_pZJ%!N>uM`n?^C+zGq!0tH3izl%eBWEW`keQUHsJ^GwWcJwP-K<$1vBF!Ng5YNZ z_EPhAHlW}O340HkoiBrYIXrar{FNz(i$$`)_r~(EBBRmyM|%eNJnqriVQ9v}pHE9P zU1Zs|W}(AN^Q9u$9~kl@0L2dPwmT>F3zt8NHqz&(zg8_w5)wBrj&!T)@Ywxb z+f;#2XT21!*91UK3w(owQlY@thYcH*Jo)mdJV?in64;c z)>_-3>1A+*EBn@^_O{6Sy&**ilr@PjbMyYX%8m8)W=w3Pra|0L)&oI-` zOIQ<^#)Z|-=ZSH(?p4cWsGef))OIlOdNNX$P%qoaP7^PLVkp^@bIXE9OCim!23U8b1@term1Uve2f^heRS`RfnS##reA-b zP)5TaGK$sai7Dqzu?B+;5eB-#)bb(GEn#xP(L0gi)C?R!O*%R(DBWID*~?aaY@MM9 zCo*C6S+k=vVm;BilIJnp`PhQdkY|5puUiI&(ji0l!LdaiHGbg=?Tia}PhkT>(2>C- z*&pZWw%baldIL{ok+rYe9=ncgK}I~Nwlrg&*MA}Y83Y>O3tT8DdaPH$(YV)wN=@Bl z?KQn_3TBVA8_=Nu6ukr^yQU9oFSNJ8XO22J+)i!W(7dnQ?G50KRA}ZS9S5f+eO>1k z5)YTB$v8GTUgu2|J0U+zW*}KJB!SI3C5VcxJt!2g-}Y4qeucNBw}fFjV!IN@;~-V} zGo_u7fr6NCW8$3rSt|6$Q$z+Xou4a#4o`OG8lXqPXCs9u?6?I`TOwIj2y7T=aB~b! zs=Tknxj39g2Ab(iNyc60EI;`OE|jCbXz7f1phPM#6%+_8F*`G&_?}_zJ}1qJ^6wT> z+GR*=bNlto&b8OcyOHmlXt@uG>JWgs51tvFfyL&RJ3)Lriwq_M0Gm0h4}k;!_v88} zx|h5xO$s1C#kE7#d#D}>M3|*NaJxI%LHH(~$*^DpBxRT1$ASb_+V<(&;!sw=ypAW+ zWci2cj8pP&K2m;UoRy-07T#-BW@sXYA=Z4HZ@lbcfn3A|a*K?Vg40OJ#LtfECXA{d zLBi`{0MT{>zcXbgnd23eb5n1Dpfd#ggi-r$$7E_k;JhbD1fQJMQK$Pfoe9!p%cT}4 z4J#LFnKBLga7?GPFi?l^-LoWTlU8Zs%s2n8T3Jbj@lVM!WaB?@4-|iXWHj=Tp?Mom z52}${=rB=+_2z8IFRV^*ay^!-FEH{MR8$9H7a|pezU^_}~c*V0~0Z547#P!`1@ zOa1dV1((z5tc9`(6rJ78j^Z4cAGzXUVFjOyle~S*5D6iN;M_Y9oT~}?a$gbt*+?7O z2Vj?rXF>~pqifakA4@PdOy0Nqoe@wsrwo<8o&}7ZS(SNu+Qdn-__>nM53I3khLYj3 zH80ko-_4}J>??3~{G5OorYn&yrppx1Q<;IK-gBlb?0ijZpi9-0kgXm9+G2c{+#Y+# zmX>9PuwmIj`B%T(YciZ@Ft*ZP>u2u6p!8eyflga+r($j-z8+ktmrqG0=j_Gf5jFc~rN$(RTnCPH_fgu&7Xd zn1=A=bHWb^ic0k>uoo}g?X@*Srs#e)+p)+4Wn8?v`jw*?-3^64?PG@Qm1-BV^{7TR)Y>N$DWZc z&3kAliMjSJN8BNH?)mmjm?O$1Sny4QYvRxkZ_9- zdLa2JS@4tPFZkr#kz#+df^_aK|)<&X3$&}_c>KJ#)&{tUq2d7gqqbrvyV2}79p z%gWr&^T#%Sj%u{v*R|5z4wi@vY7UP+uN5*t!3g$4(KpHZ4qmtgC zb_{lpx4}1vsRhE}wQ3_x999gJAl@0u8hVx$IPr>Xj6>vmEGV)=VLo3?YXi|f5Whxy z-Wic?UfGz}7PSZQ$ig*N*990Q&U~%>^sPm`%Ny(iYk_AucjVwjFaZr0alDPh`na%( zN8@P&Tr$kVYvtMeOXSP3kTI%U3(lEJuMU%&&WINj#@;TL{W=PBU%4BPAj{=P_hIQ* z<<}Qt5Px7G-Cx*O8bGEa+nj!Q!QE%>aG0^v!!UV+=NOi^Q7dps2-Qv>M}}#JA^Ek_PbYCH91p%iihS>V zIyRk*eYLV=h48}B)x78Gr$KVpF1#mLd})@(TzQ_Tu`x7qB;#nL&PS1MV}m4_2F29! zJ0(DJKIz^0Io`UM$4ziXF5_?3<)|LnBKaFa0FcnAdA!L|7Gac%eH--U)($hCe1Tq7 zr8l&GPGbBsUCyL1EC72>%rycp&WZOu*8Uu{n*`xSQ<7aqiqT=L+w>;P1lJ*OQEvm3 zRTgT&4c?6nE+8?fu}6O`?JIA>IgOCQz5SwQK}Q92$$1!As=qG<3ayhDYF0oav<7Sk zdX!2SXJKK$`#Ioll5c?$nM)vd5LxRnnfVGP!bYKJ@21fKjj&`YPeY(Bf$HQjP^sBp zHSJ|mR8J1u|B>?5FX;@_3@{l8dR2M=HRs=8eeq__OKTWGr^Dz2W{@#a&sg}EgeV82 z98N`->-r#sIU?n56|V2O(`jtltrHVA{=MS9V(A8o1FS9tsKShHS3S85+uotMkoH9| z)hPfXcUX`V+a0`HfJ=~>Wv#qsZ5@{(y6p~G|8<~HZNf?lM-fG-w^H|jYZY)xZwlI@ zWM2&IKJ4IV_4nOpO(*lgfQ`_#dm8+L=KH{Tdnbt;HmO=X5QfSwo&%&9 z`lMQTZ$9ryH`_mp0&J>zlsw7p(xB6gJ$IpZg!(=&As)Y%Hoo1Q>SV&0^|AK#f-+`W zhvE+{ z2j(T;6lWmk9;O4E$;?uRS&SPM%@6-#x8#$d2tuC)uAQId_9#^wnVb1=LG;Mgb}%@E z+tg3v5XI?_u{`(A!a~x?2hX6xaL^CQX|9s8sCdUqudpz&vQ%Nsxq*+3ghpoYVLOGcOOP|O^^`e+!C`&b@Fc&e6h7zE|*WKuGaOmfv8xtAl~F0dBiUqALD z_L``;iRk`Qb6nInc8t$3DXi*hm$l81SuKW9Y7rH{f&p;{Qa^`DP3^Rw(Dv(h6BWe9 zyIL%SEoFsiZudB!f4&IK*a3;!eLq4AC0-+?QK+QCnn+eLfju#YW^o92ewjGu>=x{w`rdaH?N__z#jd8mFuZsKs~V z5*r0$@p3(gX@$$HqK8X#55vWtU3$Rw>w)~bM)=?O{p1P-&Zm5DI9E@{;y)yZ>&<~Z zCNuvc)O~`og0)1y7t%0ePdfAp68Glo%z3JYiogZSpT!%z@}ec*R_$C{Aj)Q5~8#574WMT;78p9Um?|P;md{A0+Ynv?&<|#lzsrLOmMt+Zlg{E^&SF$(u39E5gBk$6B83#+COL)Rqnz56j$;*Fb+&>v^V#iR91~c*D<fG$0vcrCSr;{YT z&dtw9_>?K{B-Czxw{BP5;EPQZLh1MSlsMl3LgB4~va8b37A?lj;>OIDPlD;;H6Bj+ zdtdqYHKrtQ6hT-4TSsa68tx`~(}J+8{NmsX zhk8Ty$v#N(H9YFdbFsFWz|1scRBOoxPgcaD2b?y9+(g__S_JzcSDO|QlAsYf6!0US zJ~4g924*9DnKKuKLgcnMRx6UjF;ABsOOEs8&L@ckuU1nuMb*C} z9rF;&)xYDk+)d!1`&Ym4&(a+^927>&!CtkB4I73*w_s!=*u{cxWf$DlzVS^EmRtAI z>_d@iYQFcM0&MSnf|=C|XPUL%Obu!VmH-#@`W2Se;-QgKu^+jfM<52#fPn@_dE0sd zR|9Yvc=hv&Tv-`G8=cJeQP!g_g)3(TQ^Hn0(!V17z483L1IgloR>^kDoNP%9 z1P>&HB5IW7#pxxYfSZlLeSNJ2k0}khz&EIaa{=5fKYX7K8pEBnJETU@ zp5W3||5`P{+bZZj*9mh~-%*`H!;K}89O(fl&L2(}byvjVvbqOk4SqL<{Q1DP)X1!n z@ybTF_%K?Vibs+8L7X|}UF^Q}7bPyH*6_&7ztX6>$&wxo$^4+30;ZXld3L@4rsXW^ zCDWIujR-b>%uO(hti0=tB!)5@fWY@QXQI;WmH_irym5Trs;ARk4C53Ruq)8u#qE3HVyo7Ll)o5N|Lzg{ ziIU$}Z_-F{!;ZEIj@7-~i_()auP6lTS|5Y|sLes)K2aa3!t1c%S{}GaY|dqV&E@cb ziDi24d=)Tt!_mX1EEGO?$f4|bfHxh!aL-oAfZ+DYzUY@O;qOI2$XKEc)(+FLQS}<{ zBdY@x5!mzwHCACky5k6Q?dOR%On}lhkh`P3m?P?M)umZ&6g41+Yby#9p4<9f? z3>;zL>^z24ox`=)$8^k2{R%@ym_YD{h+=VY<#s{8#k@*U{4v0DvI3lQSjc>@^ZL~j zQKFPL-QD^7V z%oZr~TFf>fDRLiH#82aq{p(W4Cd4kTKYJ?xC|eI_b@u#&A^zGYBM$RVemf{bDX3OT zRFo&RNtCoNZo`z{4vf`;oEeV1w{+#TyZx~2{23u-SH!D!e}@>E9PjDDnhW^?D(f?H zr>dYrMwkG2z5w!W1Z zCr<>|{j{2qdR@tB!?(Y=sUUZfKNPJaunKU-2NKM@WF+#$qwR&>-J`qz30ii~k07PL z&DQ6pLyTk#KH-h%&HfHoEAIK81x7BI{(2P(xc^^S&c!uRI*g;?a*Z89-inV@T%N44>qL6ND7S zCeWLc*@6ps(KeW9wsJL|v`|Z?(a8L}qx*L)pVOr?Sx1;VW&lqC z#zx>y-q-1>yRO{>F3@RsoH&2U8xe!Fc8=v^(~M|eCJ`Ju_XE@#x6=)eu4gEYx!z7M z65Esp<{Z#UqC=UBWM7TNz06Y2H4jVSkWH2P{Q!&SBlkTrH|oK}MJOTRxTwF95)4xm ziaiz*NGnuIvgf=ODE^jjwx^wiDm63xe|LOf?K(2u4V+DWhMqDL?jD zCP}??px*tOn(^P$nwp%^A=}+0^C#Mq8mJmNN*eg`_8U&1-Isx{sL;NT?L|kGsV;liUFJl&n|C748S0Z z-Nu_(_u+i(h8?&fX2Ed{bS9Svk0+G65~_Cb#tkd7%0CD32YK@jk*AS_qE`TfUIq!_ ze8C#fx2!cJhGeh5+ z3dH%fKnbQ)$UGGTlaojHH-OLGn^fC_2VWz;T_T?w$jDtjz>dM= zvk731@a$q*1bMdP(tVy3kB78=n3;F;Z|nW|eX98;HsFkIVPKF)Yh*j$x2;ytq7#!u zn%bTl5#+WM*&Z+rA%>1rXRww0bY%Y_c*7+l^lHpI<$=S8t!RY7+~GDJ z-wl}~<;>+H+B-Spt00cUpI?u0@%pVZd>}_5LkXKLPLT{tpvG!y|blkr0cXnX6 zV=KnQUm2UOm|GgwpF_>=$U3wl>mQ%=ipHI3o0;QB@9`aqQU_^9^xz-Jt6F~(x4dHH zzGCj_WGNXD2nq9}7q#3*&QG^lej<^IO@vShF+sa^cbl}Vo_alo=MPQq<}kd^qe5$S zoE5cq!@I>>PfND6f=?x&%9f5b;EWZYWFHwd`e<70In))`OB;SW?tQcW{uNAOw5f>$ z#itA_Yu+xo$T&W}8}9CQJHvh1Y=|<4*1^$n`959E(7p?FgsVCty2{e~_fFKCe)Mv3 z$-Cx|9rNINA$jgp=etE*?|8H=@qF>H2J4c0S1s?u@YZjijpxixyzK-Us=^E6e3H1sN5gd+JC*k+DrcAK@lKc;ubU(90)mrfkXPfPU ziqOMs{%>0Rg05*_=F366Lfb`9x7I9XnRTPS1gJkk#^xUDo5ArDlKWAc>v?zjkI7u0 zxG@sNuS9bbhP?53Uk={mUHTsMFx+#}T8X2Le?-MGA?oeEUWI>VNP*reTc)#l<3`EM z??HZ(Lw_ja8k62Z#&}dfg-sZ_U1kcd@q%DyunY@_^b8Cxh~pk_ePX<)w2o?`i*a{H zVjQ1RjHsalQ&JCcY41b1$z%0<-O$Y1J`G;^&$9&P#pOv|YP;k7X+^OG!Yy8S#$l;D zk)IB1@$Za7?a{z9I$jhI?`8FS82c@B*`ndpqk86X1WQ6v!8UL-lfeTvv*qpx|Jq5Y)zO%DY|K zm(qJS#^_oB=@CJxek-+}k#wvVcphQnN#&+G_wJZrb-M4kz2)3Gj;e^mb#};uSuS%eezxB|u=*P-fAIQHl^rm)21&v?LhCG24BMgS9 z^XNIx83tqTH{U;_y{mmg%CREdbUEFuggfCS1RardvzE&6QkyofXUjX2lT9wKEx%SE zm1L&A%tG0&=B+*p5A66O9tXheW@d{gPS@X02%wGdm>s#$LV@*3ZT(U_{^o%ER) zn&UUjuFk~~j*Qw@e$mdu$Wp0r@dR51z^-HLJ||r!Dp`(R<)6SDyMg(FdjCYzkDtr4 z)!RleM7s)KSZrm&Jwq@&IGH1MMvm1ucv0}t2;cZHZrnw7>VGCb3+jL}*EqDF5e4i? z6Zj%&TvzcsoO8>AZr-^`h%k8QRfXmuT+Jzf{NWgX>LrbqD8saaprJibbL64JK_cGo z6Ul7+d5r(=Q289DbB_dv#x&V&S(`Zf<9+MGdlLTj#rW%E8x5nKocGudOOD8qms5Ze zQfN`YTc}}o+yepc3IzL@o@^b zJYRvi=M2ho$b`9AL*Oe-pW^=35zExE(M{Y=*ZzO}IR06ES(s>?Al*Hummn-^XAIBf zpX{xhf*@$mma@~Xr`EwJ4eGDJM0(6Q- zFSS_O8NkZv6znisD1y(*Ta?A_rMFpn!B&^>k9q1znij3}I z+*7&z-~e|>subGVV(Zv&#IA*$E2Ju%DDX!_6g)9DO*~dYA|jx-iki=#zn09y=z{UB z%q?2c)KVDp6an~8aCi8;2B@KweQjzm7rc^Xk4|%d9rRQ<3}3k}lc)5;@{QtCP9~QE z)gGXl*Y_;JEqr+o$>S+l01tHEGEs9&mjnQfgI|5V@;+?RA9l3Vi&OW}383ugf@wn& z)m@^0`mL-{n5z(?ofCn3@D=)%Z7$QR+iH5UCQhyx3e@E`fZf$j9;q?*{>bkrDi7fm zNskh|-Ht6*<$%oS=^%JjJ%TJK-)r9a!+ze6@})@z?Vm^ACH!|P6ht$MtUsFxwg-EZ zh7sOvbAt1tYC!Yw4l_vn)#szJmN`3NG-Yq+Q+io*AV_Nn_Wu@NM^H}Kj1&cSJExyK z7&hbm)>wOWAT<8Pm>Q}JTJrC(ax@}m_`qsh=7L`ra)1LyZNq5AT+Og0+xc}JPh`ra2n)=w}mVSC2R8_W03MMr-27HWVLU|_fTPyhbc7kDZ3 zXj%;YGt}d;2 zn0I)6Yda}1LB50j$C|P+zbT`N5MYec?+V@n|19)CD2q@xT7l9(24GLl#PK(FD|u7m zX3H=>B`IY?`vr4oUXINtM0Q5{6ZE;x^Cja)wa#Dj(YwyX>)=AJw#fui%1wd)Cb1!N zFggL)Y3UmH+>F{W`;87Fvlp0TupJBo;uniz7uWj-ru4H6D6OIA#BOr7Yqn0=H?9Uz zd?oW*$2Mgif5b*DARgB*iK&Qbx~OB-3%nwrJc*wVKfSlG_v{-30}3ai%$*0Hc&2GT z1vS<=yIeG6aBFxc=IO&x78twJyIQCJ2`x*)8L<9OWtND?Byhw!h!LHxUhzDKo1rtt zCAu3IQ+w#orO}?9PCwzpvN`_02Nh__Sg;-I3qK0|EPOUDZ*?%Qk3u`Q@#xzwSL?$4 zTgC}_aYc{&#XH|Xc%zL0o&}4!vAFxRba3%#*gIkR^DM_J^bNqk_V>adL!YKqDO>;a zS;Lp0+u?k?gJ(Z$&J@iXhDJx<1bkBO=^W-`zXhcK{rK-7TS>brSxn+5kpKJYdc!z@ z5w^55PTU*XHUS1w;IDX~%SNy)-2Fel?&q&=ivvp8*o9AW*3c3a(bB4=fW>BZfHAc_Q4} z16)#jM&iX0nEc_bV{Q+>4gYi5hT-osM;j3C5B6=8oB}!@Y&Bnu@N*?E8LWU_&3pc1 z%qX8TRZ-2V?7|T}gq-)s3d+Ip8xY(Tp2kMjCt9D@)o*h)H~E!O{ojLYlD1VzNLfht zk8l6=pQAV6My?9p5B%SI4F69zJR`yX|E;ja{y%%0=ngwT{)I%*{wat)=#BX$Li*3E zfEPdrxH?a#tpB^Nf35MMbl||Pi0(k*u>bRIKmU_87G9r_oPyd#?IAD%yEVkNP7 zqw<}Kil;}Ip#PA4|Gi=SD)2ADI-Ygv3u1wCD6G%?=jSC>!6gb7pVwOa^KX#LJ1Pv9 zw|ns2QJc6LIlcK}cY{Cf_?Qkn3E!IOgp02hUgy89JQDXS|L5NuN;IX98iHXemrB42 zm=w_|N3ThHqQK!J+o{$9cQ9?XA!)!Ysg<$FR# z=zf5>w)W;468Nc6+gm^CsF`?@G^oKq?+Ocq*S+~9D*nfFU=6`He4=jS$`bowd=1_N zXMKE~A*awWivK)`R%+ta4}@M}Adq%~j#|m|CqGR*xE>;L7JwRP-rD(>sN$cyPljrK z0~ZA4UQotQf@d=`|Ld@QShh>_`=0*xdgJe~h*h1}y0iFh;m;oU=g&e~&Xs-tQy~00 zRU{E!^-J?-f4&tz7t9Jg+&Q=ZB;fwDTN_y*oor&s#Xq0N&!0O7FYWLfqyMIG{`J~q zlp=4Hr2X?Hm#pOZL(lm#p8@>#zNs z^WKG7(Ia;O?o+Llvp`%0n638gBqA{Of?+Gzx}EOM34g&6 z1L?9OfTkoVc=p5VKT)IwrSPd9VOk{9x2A!LJITV|5wf$OYVHMqV`G->ISlc8pbU;O z0Wg7)Xs zZLpNQ4a0)IN%w*xzfLvkJ~t;K7BCZK$PO0x!WT^t3=w?blqfJK)R5 z2P6$>22KORkU|!!FU~{$-WjoP5WhjNRx!I}ca2MFnV#$rJTee2ZNW9JE*BpHT?R~r zK`rwG;C;iIDfjx5$34rMcTPClY(g_b-vaQ_Cob{I{7j?YJm8U6H^T%fq->Mf8P%R< zKCm?G=U@c_r~L@@gHB3Os}u=u@{W9ho=7Hb?j5F9riB$qHP2%OCtrWm9RySl5@wzP zAb#w=Kq`ku4FHH@Ka&Zs(9{LMARo9K3Is;t9K07`94U5|eB#)~<0rTGX6IX1egoKB zedjvtRf>=>mOv1%(arM#<}}Z_x5l+xg7=fe|6D#9Bt#D7q`2>eFXZOFy!iw2t;$ZV zXbo99iyqzu89-HtXjvuv)5I1WBc3}4NkF2DfhEOeOpAxfzyN#&j!s?_^b_3spG_MMnfyrDtnwqz z?|F0}JCxso!qczMh24hDJDI<|0T_cmT0<3wZgThVj?=Es)~Q}3cMlu)1~0)^>8jhX zx|}7xZMmd-HL?P98h3J`{%~Nsh|E#%SQxRXte>G0k)mJ{Lh>g!vv)e4++ZzKOx6Ad zsI1aC%6(iUgHk;~J^pKrSN-YE>Uc@@P&DB5$Km}^2BalmopNUP+?KaC(8lzc-^Xs7 zZvVKl^VDq{q8M}4rOr$x7Xi@Msfu&QD{ZU;9lbNNB-Y_E^v5(yasF)^2yRt(cLLBuOW_ro1*)&vDr_o7 zxuKm5wYfo?3W?Y%4-maXP3$ndYrZzqKK;JL>(y?=s&?m01~B9a?xgAl_6+?h?NFSz zP#`Di^;z@2RTg67ZPJq_L(bdy6nI0Pyffl&JMoB38_;#o298NxhR3Dz4JJ6>ShJ~^ z% z%J6S#1$~kZO+^AS)${(Ou!8YF3Xp#`YzFc^LVSA>gEdX{GVlai%r%ns4GW+$cb3KS zC+3ud$~e-k@UN&~WcDSiCGj#^t*72^W3!&IRwM zK+|Kvvq~iJ`t_>0(|peUgHnKhft)#`Uh{RmV@zE8#GFemfjVTJRSHNRo{#%lO6Fe* zzjU7Ulm{29BxX!)SFw|x!&nZb>hUokA7P^RHjz)U4B%0XwYR7q?;sX%re(1Ru!o*f zx1h68XKr#{;k`(SrpN&?m9}}w7M;1VE^JAwNM4s*&bsC1bU>H}CB;z1Ev%qxrD1Ydm6?#GpLXzE zu3;KLf){4M(UgLUfxk-QsAkiHg5u=2o5E!u%HT8>x*m@s?kT#3~ zdVY6TN-yTW=n1?{cuQ>SSaoMlU~SlqKX0z$jx_S@c=NrAF8qBAHM( z!odYn{(fYEFpkoW0T)D0InQtGml4UKeDQ3Ra!5s6c~D!^c^Lq5zjvRczR1G;5@lf( zWna&WGv#2i|4&TouVPLY1*Fp#AJ%SZid68=iP(&y%;U^B_Sz55ikfoXX4K~fT^_|% zy*cSgh4vo;rYz>Swo!(h&V4ew7^hy09Pnr+a-t>==`0eJXax?unCBd{CKcoi^7n_( zt`CbA%6bw-uD9l#v!Zn?IQ(Z#W7LP#Nqw^p0bf*I+E|^Got9-ljaTS;0Vq2^h^ZnH*@4ftH`fK&k zQVOG>SJ!N%EoX+BkegPeh;sJY@nI@o=^OQvxufO=oPqPoa0Om=BI8$O!vh%#RdCAkM2f%o~7i)s(O81jX-UaIy zR;lcQCoK32;%RPVLMoyAJ3XNkC9diDJdiew{}4>?dh@{JtVqE-D=cB}euU)pfcJCr zuH+M9s8vLxy0IWRJ*X4ss6`%>50=6EnHU7W0oBVy7MAWrwi4`ZT+J67Z`I+euA3<5 z6{#YmwuQe*Z3=4i*RF?!r-A95nc2@$(@7`D{yi{ZNZw9tO83B#+zVO-B<@h$kBjRW z9*tnhqIdnX@M#6s*w(9LJ1Z`)Ee$i8?eu&{K#MCu(<8pl8`-r{Hmug*!EzAMsu*I35R4CvE()_rQybR{k;lm%rz4b;T20@(z9RbKco=aOk z%t5(kWzwsWooS|J!Leq!F7HNJVurNG!-Q_)8P$d!NhTMB)0p_7uFP^vtn2ODrW1wO zJ+PDl>Y8w62vDYh!wf8jK-fK&CAtV0x9$=|e(|*&@m5&>p)KMlA4qN;2`#|uyUHKL z0ZBaNBH;lwyczJ>+`_1-wHUvflk1+4^&Y}MJZEu6!r{w{j{();D=Q2ignv?xh-BpL zRftgIQ<4O~(c`>tM>?R2Z?h*tu5jX>>)qBX0D@grr4)p0g&4N68=AjvA0bRDL z1kAflPxcE&(?glo8gjRviWUY7_3D~`EBHPv05IhFWGqpPUd6%(rU(aob3VMwuECQ# zs_;S`c=ByNHqFVdq3*V=A7?+^-Gl*|R0uC*Jxup>tl}Lod&@ogWNSoQFDOADpfBaA zpafMX6nh?dX8g*cX56_(cT+w4iPMkX?YaamJN{>cH(02j1GdPy$ z(Rj%mXo}sAuI41<>&ToloI%16e~i7Li5AOF&cb%yI@)D)Dx;Tq&UUPe>ABxsHrc)9 z?OW0cF6A9!eG<;HT>I?Ng8_>M)kUPN*XET|+GtskIXGg@eeHjz62`s*<@ zH1TAx_pYc?iht1yLhr0`y}`UEmRiY!Q%0BO7F%QtCGMgBSFKdem=>Ab0gp$oj=uN= z*^^z@P9UvX!F6!{xHt7`sw|WL_1HvL_TG|GyI;Dx6EwYQ6qB& zH0Iw2FL}*qi@WP_DV|3K66#@%9r(fY?i!7=TF}Z#=d>LpmCt<%GqaXUxj4apPX3wk zj@jL_8}m86KoDaVeSY&9K&mx2{h@rf<)_M09daK&Otp`aw9pV8E6FWzkUl`f`#A?B zVcBI2yVFyN=zai(XC=hh$`$Ww>Q29wP!?Vww~JL#MiVF+l7xJxZlo95OH`Kr5^;brN(418P|EDbOPR(vxE#E8oJKM$L8dSx|8viQ@I9`tk~JA9 znfL;XF&gwjP)d2 zGe$_}bS~luwVRHWPlJ7r70LU<<}7|ktebgk2_Cg(`~VLT9ZYCGU``A6ugG!25~gIg z8G0XoVHNr-Qi#%S^j0FwWC~qgChC?vkiClr* zKbDb zeW_3dY1}hTqP;KBN6I_5M#Id>e!L-6qzl(5g=L~WtSrd#3E(b7aPzJJV~wi0%9JmV zBz^)5MBML*3B4qLx$~e~cf6eFZu>A?M^kHA~t7;^yx;R7g?)) z$#|{F{}`Dze=X`F{WZkn??59&0(CxNzX#rt2KF^1L z?*EYW-tkob@BesCM-C;9kx?1P-kA}Z$KHEoW{o%Y~U1bKs?SyRnWYAYEU1iUUrCDi}W&YoV-7LN~zrj9$&->KlnD9=yr z9!dX{l^BUazkk6C)3jJvL0=a^g)S6_m=7HqxsG~=n>1UpmTnLBZ_+-#Y!I04sCS*i+lIXscftq00{f0 zZFnxsiv<=MO&>fV7xN!lcY!#(*?|_DsYpwb(+U|nwomA&cc`oNpNxQBR@jAYF$lW` ztbx=jIN0*4LW}601jff!2K&`WyDvtQUG&QScY(%pebb^H=%Ae>WWP;D-<4e<=)Q%ISvHFs)+U1-m-$ zH=!CJNr%!n0kToAe5wY~5;!`P;BUE$Ai+oqh!VQyULy!PRl{<0d8|ybFnN&!_Q)nj zsO5{id(B-rDM_oM5VKXyf~SPHcVYt>$15gJ}EU8lgf>}y9XmK7XS0^TA@Hfb2WEp z%ycM6LRjs2+ZybC`oV3X8d}S-)dbSl02Pl&?GLSKvSwPLuq^ng{C~2?I;NU2rSsUQ zdw>TF;^>}q8afbM)BJzV%^^`3%$)XF26~=67du4XU!%0|mT@W(m)naxf5X}w%GPWN zxvp;qF?@t@=BcweBF=jJlLv%W$)}#4L1Q=-5@$N@5jmzN>sQgGEzY~dM#65n`V?9Hu}@3kQJG=i5r$26g2XtuM<(bweto$4c6PiD6P+>KatehoA$!4Oi`^ z0U@?8z8x$syug6y4!q_7HUxDu!1+SnVh!04A@#%0F3$gXNGn>Q`CF9i8hxp#MZWu_ z=N~$kC#NP9Mjt|zh$!#e{sU92DD?NdCCo%|y!oZ7dMG%pjDogk8{QO4QUjQfP87Zs zLbiBx^FDn4^{u&v0Ta+Y;9U0E)`LjG@UB85(n%NXYmn3cVtqC6n@9SNV?QUY^7vW9 zOwq8v$Ez}#N>MenMbS|Gm$gC@a08h4+A6GrWrpQ9V3KVjd%}Rv*F&nO-bZOoNMiZ7 zyXnN(EoLpg1oiD`?w#+`HC2P3=$kH4SXtOc-}zFUPLXWA&>jXx$C2pS1Ta}jK3OmI zwEIRHTA}|6Bgd|(FliA*^MR{;EwcxJ&g8;(zvM@@4_6|W?GvT&LGr7LxDe`HLV5rX@)VPWzT0I{%o zj`hnYQCFzLyqFT_7A_g_D7zxx$?7dNxF{q9MD}JK<6E(-J$tQ)mqm+Pe{C1%F!&wc z0loNr^}USO=L<-UE&yT76Ewfjl!geA0aeCjk#y4~f_GTSxPwEM+&?$XVi*S5xgu4{ zzhwh(BG{BdFMBmD6C$L3=|6|pGs!Qzyo+j~4H7%N*Jt${JT|JK7yhn04QrgHKwuQ< z-!oduxE`;1aPD2!(?lzjoz_Z0yfW4Iu<3)?dp!h17ZNSZVFk|e26DoIlICGnX;u$+ z@yrAxYz;tc^>wyEK!(cU4vS{EoPI$Lu>ndGUG1}16&F z;b&J2-?se?us-+Pe|Kr8U<`i^RDg>@FS${RXm;&fjVS|;d*e1;q?bhk7_z|y?F|${ zAlQjZiaO)Y#FMOOhsuh7wsr zt|7XGu-Bj;^=m=yU}%IGNf~mBGXDcAdr>_WDez7@hf!TnK1VX?E;^59) z@$21hnTI9cM5+FLjvDZ&UTylCymi)a1iG~k6o_;?*|Pi`^fo$zXd_Sosq;6nPE0){ zL%A+;8?AWKaC6fM&XlwLvs zKl!s_U4I_Qu3p4#@P8Rj&>k89l*V1pCqr_t#~OT8Odi2}&I)!)etTzwC00kL*|Ay|3RJd*N=*9_v6Pu&XArKb~7gm@wrv$d^);>e$KmV(RYU zV`$Cw(Bl-cv%au|x*{l*4tusAJM49@h{616MIoPJ3LpNI`okR(j0gQwuDsdIUur3Z zpGNl`c+P5QCrIt7+%>pMWAzi1I~FpJrDV_C$QArN)6RBs{YRsKvof3 zqr3sZ9-Lw-tl|{xM9g( zMh0exg34{RG&&l>AmJ^;&m}rbJMlo8X*i3~g@=r1uaj`sW~&4pFY)2aXs~y={z<`g zn9)P*V5lRtx4QjU_(9+Gymf9G)aR_QK+Z4C53gM@HphmZm^VRJ_;TKW`6$szsFMwh zu9$R)TZB==IGGD4Fm@`;km9iQFg**{y9q{aB(Q2W_E=7hvis7bGnlx-vvj2Bc zhS5@7%Es`{6vaj@l8&eTm*H)|QHntT05HN^3KSwO(C^gSE?)R*#!`Zjs4QrOTPM7p$UvI^%!hr`x$lR{4B1e`Qabuo}L?qGO zVotW}K3Reg`Rpe^SnmK-c(y|=h~<0u@tg2lQTJ(G9wY|$t#zGCy3on|8`&dGDgZ$K z3#8l`#QIL-Mg8N&>Izr1efvL>y(N1^UCOGko(GOu?RKb6Z2ChQ{a#G1353otTjnOdrQIOI*@y}O}8Ekr=djf!CZCpT-m*iy~^&1Ai84kO|^Idtcx zKTTg(Kog(USL(1i;?&YxJ%F_;(?@|Ts`t^}%AfD-Iu0ize=dN17{XIOnZEI@^Rxh~ z)C435RI=CZK&MAO^cffhpAG$%CSqNphHoQdUVevJQfkIq=%vgwlTHs2Wr%T>5d8@r zU++2{(H<2OgI9CAqY%k{BrJBZ2t}OanZ9?JsVZrt7Y1&n$QZbw88~5RI}*xX2tbk( zoqYEO1>^PyzuCClBd%Jtw)RT=aDwzmUH*~rZy}OLfX6T8-Yagf+D@-+k~J3rs)Iq- zQKu2RfItZyzh;AB8BkzgK7Nbmbh6*NG@&2`v{YQ~k7BgL>yruKq&I4xbW6?J8Qig` z;$$UUaQ@kWfG!Y{r#wymeZE;}5kH>ftle5 z5RB65ZMX2{T%qVKvA)CuaA#xS%+(tfOUTPKPzZk~Ea+9T#Pi&)pTg?Q0|qG}k6}!Z zZLMZIHEz?Q-KP)+>EOH!;ZV#ivJk3)Ml(dcpz|ef9X3zU82U z?E9&@Cu|(CUas;p>&gH20B`XNkFfluL^rp+8O8nOwH#+EY;j5huw+nLL~L?f>JmU%~)v0gOB9Q9Wf}2zDMLFFa$+6bs%07jF$~hPodC6cpJ*pnRVD=m$`Z^2hTS_TXOs z?pl#kJ$mMaFna7CF`?owY!9|WCjS0rY@-0h8KC(Go0!H=c2&88*H4`i0=Ee^1Bz{7 z!ONF;He2 z_CCUWu+a>9qNA~6Sa`Xlw{6O^#?p$dgT@go>Y^;-V6ZpdbcM?tg(#43bC&59$F$LK znlqU-V(e6zZV@HyFvKh*yZ7jxmGjTRxV?{~I$s0gK%R-?fIEvkESLnEMAmQjd<`qj zM2|O5qWsp$)KZ|R&+?lxOJ$%)&VD?J8a!=TV=`NDIv|>|W9_hsmV^WxVeZQm?-Z|K zA2V`ve(o9!3K0s<+-#`sWu9b_#=HvU`PCrMI~waN9enJcy$DG4je}^yZz#O3s7>sx z9X3@eZ&6NOjbwR%no!BXIT(&;zLSgxp&62js>?qjzo-?`l3;8@vS^vr&+%W&;8Y_0 z83pa=7%OxCI2hhqk$sf5GVgvYRd8jD`Bavjr&%~-F|R{0K)XSx?~W?~z4QH9TM@=d z=WabAqY(>U8E{;rex>(rI$la!GvH`ncadity(Aktwf^0UP!xZmq(~x4mW@rvPYes> z#+c0r%$vUJVL&Db&+BOU$gL}4GEIG)e!lzox(pq8&0FwNMOI>dG2XAz9#c)tB;iBT zhqN*d(~+@d!NM~&n1qAUe?rzTdhI$j6^j^)@ikAyvi|&WcA9%!EHx~RpR-&kBKDzX zZAiJ3=IM1NT@joUgn`i1jYqG39h%Fw+hk};O}7X~qAmH-x+;K)P(Mgrso$74{e%HL zg|a(MSL9M_`~8lrZ2*s-iuvDOaH&bdgv7Pao{~x+N2rF;c7k?&6B|yDQQl(LMSTWEZDPdJ;VXp2MpmG1`~K3c|{f zoo|#@ev1f}wXHkq!`jZDNyOGCOV(BdZ5KpzTZTbScIeShY5L&&`$e6>Y%hMaXf3+e zl-miY$*YBgterMCkf=Etf>pbC?3~q-Q@HTrs|(&kg6!TP6Pf0rGS2)s8cRx5+irLK z?HyhNqfIE^;BRQM>hHMFZSy2L0`RPGB06S+qI!822YX}LtM^xW3wKE_1CF6_2MQ6yp`^KMr=Nn2jQ2|HRXT;5yGq;riBY zG2oR)S1)*IY0boR#cW!V-v zHYsjpcFplT{qfiWudDC|{gzlGE@zhT32Q#-7(RboKEja_p&T-`p@*WtZS9CKd+K~3 z(H!^g+hu10D|ISLsY|RerR;5h74`{eANYNrcBEV)IV*LM5a&%C4<+^-dSFnJg@nni zlX&4ikX(rG`bUT?TD)x4kw0^i=t@MJ3|E9)TJ?d^0PCT z#k^p@dsqTSx)*@9lEfk_`h$4KPOHzuSiz)rN;>72w~;B@7T!}gT=M)Gzh4<*$VA>K zzat>PGqfO8+G1!c60d)1()9D=4z}cLXUsioNZFs9$fatz$G^_ z_`JsCg>a!2T}oZGpp6) z4rM9uUGzVbbbxfjywgwI=UBi){4F2Jgd|6mAm+Z9Vkt|UCs|Ui%8X*8WRN29Nw)^3 zgvU>Lpk)Xz&|y|awM2I)Oe)skX08fc^xn`Jh9wV^&a12_F8va6YqIkDoRXskd1(q{ zSAAe44p;2P;F!9qsmn;u27MV*TWAd#kyuTPWl?iGzfYB~UE#P%=Lu;FPp?Dgd)RJ5 z9ZcU>miXmV9XTy=>cxu>@}y8#sXTGJ5vp#v>7yF94W%ys7TZ&-+c&E4GMb`3sez99 zw2DG__G^>{7%cOek1E=W1K105bPn%MemTE0)}Eds^VKB0AcN_rh0;|OaTeHT*oCWi zGNX51d(=93cCZ`Y(No??{QQy?YEi1JM4)40DmMKW7+Z(H@>z9)ob$BGkixZAz+~Bq zKN~08k_#r&6f#y?w!RGMA8=wj{JlO^%VPcw|0p7w?Hq&+l^nNNe(Q%iYzBioMbZvx5?OV@X8sRj~Dr>UA^Zz*lD z6pnb@q)ql?)5PoX0$3P?t)bc58|kANfBqW*Zyxrg9Lr@o9B9q9q2V@dNxfzGN+xE2 zO*@n<**<{704;`PYqR6iLB}ZMZ@A&6^e9e^H*gb)XrJnyodtbhjj^(6do5V7>25c7 z-2X^cxaBDx*9DVHLMh?)=x>^tsw0^3ydaduCMO!`M%P}SIm1Z$u8LI+j9ar)Ri9{5y|rEJM!X1>z~=QQmDYI9-p)#ew1M z&mjnO?_4^J05tB~*zmw&qU?@^!%!e+g^>Pjq zcq9G{g=-|=5&wmrWwv`>n0RB~mso+q?Hu$*Ygh1cN>ZsozFR+Q-SvW-4Q~P{8XxM0 z<6cf_e@4WfiLU9UOFVf89df{J$6kE(>2Lehgi^1Phu>TI*VcjJ(52sSehK)t-@Id} zeXc?1$BJxzON(%nktVnGEi3TkQma$&`*ozJfR^T$ar}_QBm}Vaq15CJ|`!I8~}H_g=*fC@a9i8K{x%Im^@-oX1F#-v?tV6;ZP*VP?stG zx}c13m};G$Osa#ACc5Nu%HLZwR*bL_(| z4b6dYb9SbeMen^MCw#g63M#I`)7fN>P!<>PRQ@2eyAeNX-k!`+P56*FY;PP-UNf`h z3R?ysWx9ZKWI%MkayWM^6SyL-l}mN*-(y!oTMyoX9pDUWtwi^&E-jv#_6HYb%255| z(Zi-uM|LruAns~$jS-@pS3V+;J06x9=2;fL{$u1<*ljy$w(>*x`UfR}!&+L5F}6=P`8lH>RU_kUF#@d*1PC0OJ%Ozx%{ARfrWZUR zV;|(sq;hJqf?!1lgL0r`*2FXKl#Rw9tDo0<0%2o<85gDeYs~wA)V^$fIlX=TIT{;v zGE8V3pUB=7LXa&s$Z`s6H>>8G;-4T_boCbSS4CqE&@QLH@+DSutSgX&hjlA=k)1NF zm8J*iX1unS!By^ZMgZ_0fE>kG7v8N-_`2^SnPm&dS^B5U=Tj!gS?~H2`DH=p=zG33 zH1BK=6Z_2&EC33jmDO}+l=5BxXKJSQ8xMmp-TEWTTtuM;nLFW#npha1u>AuF{}oab zz)xKHaUbOykN*h}aL@b}1eb_7AD?b5=A#jN`J|9yeg=9#1#ISI;(wf?q0!{wICqJO zW4;66CYy9rb?j8=G{c!i*$TAl3QcanXF5qFBeL>BYdeqActW;RKNY6g{fQz^XoOv1 zi`x%I%lGCtug=Ffu5X*^#NzpxNL?0s2sx89hqduukeTiV+$8oCm$hfj6}HGf!pa+>)UYHSeZ zoQOQ1dsh93J3!!m1)C|ok;^eZ&}uV(OAyNixnhFz8Si}4_31JFW~CGBL`Q%5#xWSK zus)*A)DL32d<8=xdXyaScgSSb+^seuO_h{-sj^FXkk{#u$%f5FwX%}Pc;O#?#%#;P zw(k4-1P@cn5B*sB9Y4Phb>v&0VN0xfAw``0J6FZ)hHStD6bdsCeRGUbyBiL6#q{2! z1|o1x11a^;V#23mAX6>VdCVR)x5cApBf!3Efpfu+K2#81jR^>@BWMx z;!`8FcYMvc{6dduGqro8uq>FIL3i&j)Q3s(=5jkKw^$^gedc^fH2 zz%spJ|I7gI-!0oNPEo>63^|QjN_Z&{S6$v$T?|og9Gla3yV7{ky1TL`kJ*f&MfjM0 z;OI9zGAH*Mqf13|I&+*l5!UvhX7|PO*_#j$7}uR(9AhyA?1_$C#*gRZ?<4z^qQ`lc zY*1IYz9xPSqW?fN@sb-KW@t^~2~3D9q8HXreUm)2LgF1ey!uuER*OXf6-KMaz$+pH zZ^4H?2kgYOyP?zg!&5iXY(HleVT#zS1(*-t<{6owa0`?R2LTAOA@ZU&4Ilczm~-aaS_d#DPYw=){Zk#d{?dX0I-Z-JG9u zkOmOudb~uBR@dpdZUe^bmJo+n>Y4jR&z9~v3RSg#XJAWW#IZUnwAF!IX_(er>GrvA zbKawDX}dg#K)%hqg5kK_h@F|Ap3+LFCH6)9qXf|g)D?j?<|O>49>ZzHL*0Q-ZeSEa z6(8E9zk9#bNd~jXPo4W-Gc#xF?kOKu}QC z(Z9UAd_(;X(Jx?UU14o>u-%_?oq7pcnk~!1b?HhE$f+Fuk z(%^^N*xcvMzF$9E>A_ANhOq&4N&-nAf)=OOEWoZ4Q^EI(l>&^3w+sT5{m~D}F-BB8 zS=a}b?-dP6NXP{A6p5b2NuSI4={3G+KMD=sAY&XGt z@q3UC!Wudwye1L+P*NTfRNrs;rp*D!u;vGFx)2K3vuiQdXx-G#xGguMd+d4amI{c} z>TlssFIP?~CA;Lfg0ufE>FcVFYub^$`fetPWI;@t4$xb$j|d-0M{&>}reEviJ4^w=oUR~pi#EjhiPV%w1NT>`XkeuuU`y)CzQMR_)qUWl2k8XDx&U-9aro&gR zi^*V@ucFiSQ0>7W8On{#FZFE;zf?vifweq|oHbi456M{dLG)X3?1uPjTwr@BfsxfB zVI47u4o;7v&hq}4N=0^4v~dtQ&v)ta|KaZ4w@!b}b6TdhP(Z^t+;@4Kk%$;Im0b|) z_)(>tGm^miV5N>y#;@xazlQ`BGWQ06=w;7Sn)CfHW<2|(9?-wO7*+~!#Wuh)4ROHsAI zb=7jpfU1{|KD@V*EW7)DxWPeSEq`4q{6v4U|8VGuwj5!{5shzTq#(GECb;x+NWUA! zVw4`DY7d=ssH0+T31&C)#C&gyxGGy|AT;DHP;;Zwq=m#3-S85}14Z_{sACl(&@2|` zr3m3Q(mRQ%P%M{|oXnQ?6FGVJc4WndpU{J@dHa~9qFF8rJ0F$AKKFMi-FW_{X z5@5*h{^=Qq8N!PfoRLgC41xcCt@AqBbgo#QBbG+4TgZjq1|HG|s1lPdUj83t ze%OF!=$jYkzN?(2-)&0AzxVo5+sGw;KUtqe+Pj<=7=wj%bEO$Wl)oM$oBMw2B)p68 zFPqr@Zz7~vP#V2{Kq;rEfqI?Btz5}-7L9l|WuMB_1UDXaMC2WNqD>JY)(_;IaKMJM zp1A9VTiRD;!j2=G9gO?@n^!4=1yujz1pqn?PIi?;>bx(8F_{4Jaoy0@U_XLA|6=?i z#D~3wJ*ogZ{$48-e%?4W+k;EI!Pi;*w^#8_w4E9`v`! z!ris1Dbn=d%iAXaurH^?Pn`KAl?e^OD6aEcfbo?gyHF_{n>2@b)+a_Y8@)Y`y$?IAk{#odvbqPC0);3=0>% zwF5B925$tru%X6?rS|$*fHYg0auX8dfxYs$fsa!4ZXQBk`C27DX>vVI$kcb-zA)0G zP3zz4eIPuPurie$cjH!0-Wt#ZLLd?92w;Ke^$@5+mY_`b%(jzSWqBX#Kc7xJ62ruc zF7u&8JZ=vR$h&SAaixic-F3wwj0LdzN)y9Z^0T8al|$3;W6loTa^8c*eTD|F^jjvT zw-9jL5_IHM@*LK*?@c`2>X$%s!Twhtc>m_)z5LSNlZe2aL>UIHy%Ej}%N4ihVOjD< z`kL>A;kQc=DOvH0t05BPX|oqc16vPmfti-_Is7TBPO*A^w(m_BPI&jY2sSSZv9TW= zQnZ(gy-In|JEU_2H^nDd+`a>4ZrFLg`@ha_jGo=JIar_2R9&R8t6#eZ_k>y~WTV*z zQ>rdE`FzpqWH{KIz5H1}ZuA>)zeZc5_BEnh<0T`r7s2qW8N^pT0WY$$F{!KwZSG${ zMPhP0AP!=X0|TtDw2f)ih^WawM=>W?`$JDuYwK!=za{=r{e^2|*d;$2(E=`Mw7+WF z8;M$V@08z!wku(E&Q_8K{48#}+KSeKx4$(kZZx7B1i-F3W@1i+4$Dai-6Dv%U=mok ziQ1fPumUU|2AyxW44+fnPLtq?$9oR3`R75VlYq4zlK%oz>n^Lc7(C3VRnqZ_IwmL) z=iUrd_5Vh3~^)&aq%lrEpcIJM%)iBL1b|Aag^9XAvloxy5hWT!q01gBEwm^&<@ zxtL`o5c@an>VFkSd@15@v1xhZC^eCRHx`pS0sWZxu$DJAOb7=bMEAjs&k$f6yRu&K z&g%Z+mVWl^1&2&^vcJ9anY~t`n}7di?KLV|h&(kYEGjzl+Fd9uAZm!NQ`7G08!0Z* zLWAT~=3VMc`DHJ~#$t6fgN9Ugi!oW;mNs|WlhJJ725;#ux*yZG^WOit-Y^2^d0v3>}wID?J`wK40bXui)#fZ+QFIN2| zHtY>HkplaYE2RUMNQYXEVz)s%vj~k}gTHGql`AEwEe7qX?C0`{nfD0T$M6gQ9%!a!_$#LF{=Bw|IlQ2mgwkk6vCO8ID?h?%<-MEw$SXO9+?BI6C2;-zOZ(?$pCz zok&H%g;ibDv&Tj;g+ZZB7Zo@wz7YDZr$;^)4xRRD=j}YDrZ!2>68YbCJ+pOFts2#uQPcl+A4!g zv>3nK#rs682g+x)Me{?~-BTAuT?|~}{(Zt33it%M3>P){gwV@wuu3EOdzC}a z7&igmBq|mV@eyK`=)NPWz7tpsNf@|lAsp4BT8%j-%K=ijHUQ^|wkphmFQ9{&DKBba zNO9?5s({rz-&G_O4jI6rtEUoPuS-eEhqpih`FRG=aBBw2Xq0@rqT`ytWhUX|Mmi~- zO?Z*seFXa$xG%B~BgU$@*l;uZ*_~^Rip&?f_*<4@c);|cn~Hvn4Sv&UCMGuAd4q`( zOnCN*IwX+Z=$IBB+74J0ChnK2rEwd1gT)&Ni?19m+#`NH2Mn^^^sg20*vT>Cc^EFN zTt>9Jx{SdGf)V+#5C-rJZg3u>3R*EYlh-lSm3tuC2>_iYc<+7% z2x#`zo!f$ZYe3$IZ(f`Yj*LX!1FAFDbe*t%9pG;PY!R-x6nPfim*Htxplo$S9d1YW zj&PD{URRB4KR(+3yZ!giXcb)6Pn)pClc`l;=kvcFISE>YdKUsn5ypMQ*%H9uVXSze z?|4Qfn13(%HyPSs9?D2^I>ulh|PyFlz8i?uMfMhN(XApCXd6uf!Q?N-+M4SRQXVP3Z z4`Boi{ByK=8B(fBu4y8=th=)9M|jRFESR@RS)@RiwT+N(egWKG{7~^ESY;+tV8c|0 z-jVmwc9KTdM>^sQsttuE(r4fuP>F+L{jTu~H@1%k#j1E4n(fx@^ree9TNB_&OBZFf z%PawXz;9FnrbH>=kz&4rb)J&Oojb_)KuvIjUyccK_{B!XIDOZd^5eOnX`xyFD6TI22)b*h&-^M%(Ngmu ztRSK0Mhsa_#DTgnmD_M25n?&ELHK`T=J^*e;Tk=@`*bWumI*nN(%7(^JbYXykvrHu zvc@u*juxj|X06#Lm23+{-R;!KYQ!b>WDp)#odWyXneaiYQ$AF z0C(J-8~18Q9AT3KwbdB1I^3Q!#)uW;!)>$b+*!Z+VdHIDMQ+X?$M973`6akOVk?s< zR5-m)CFOXgZOlU7PIbud2X{zVddqDgEDU~!*5zb%zYhlud5@qA$u>tD6+8>q;}R)X zhhKY~bs-4XcyY3$-#ZBuK@1y4p&n&g`dk=t~#_JkTZ|M^;iC>H3fi z(MNi6XnExm%nl$^&1iLg%bt88VkDR@M>-5NK6k0RK@LQewLV#Sp37kTram9in@5KR z;p)&v!=S^b(h5xzH+km`5;q9vi!7Iu;&8oV1x_dsZ0kvcXj%Kj2!c|t(bEt**=4_& z8RaHmkkgD@&}`g8*-`r@p5LBpde%=4?S;MPE>kx5Ec_is_%%s{RB#dn*NwHV_L30D zyPZFL!tKK0n5?$?o8?-?JR?CQR;rHeQ%CX3DyZQtM?!o~ZORLumD-V&SXOqlOrayb zDLS=5;^i>d&qL>5#)FbTN;A=$iDGDXur-QT`^~2d6;Dgvtv&>vB!q)nOnx+b<`5?f zQ>0yXsJdC{if~<=*`{M!s6MV`P|LAiS(JejCG-26J`|&&B4~u`L9ZGnq}>D0q?;1T z?gh(&li+BE|V3Rz8ZTikPX4;)yE8~QfBIw>dB|V*gj#@UUc6x;)D&4 zVm#i@h4Q=!q?8y}n8;a-N5n$xbL|5IbvQ=p@Hm!}U;PBzNBG{}r4Y71(aer~9Db2? z34&m@ufi=7Fj#BTb;?*D;hvnKfSxD_C!`|Eq?vRjEN>F{)Dn#HH;eG-W%G1O;WtbW z%q0Cat6~ap&VgvUbqJ|u;)0`^+*yp!{HNKh9|VH)>oeBmnq2=YxE}{{L>If4=s$uK z)vww3D=skvIz=UEsfy3D-;nqAAUQy04hEsV1PkO6ZpeER|wprQush8hut> z`#{!%IPoY)@hV6wMW9w&dS(>BH*zr-yM~3!@gvxgdsL>|c)vNtX+Oq>aUx&%%Jjq(o@oQ^ z2e&Eont{;9wY(|UeH=;Z2vh5!B}H`yy+z=I^WV81{+8fam}gwC=34bm@;sU7V;biK zLuAPcJi6qQ;-4(gPddP~KU>gDl0`ga=^h)NA&D#^9;%%%MD;KR8qmO(lZk^9tj{`u za4BA3HlSyL2k;xXaY_jc7LY6U)d4IHfSS@{VcQu_y!>Lw=%b4T`2rc*jV*ox&ULppo`8f+9M|jIH8+|KmcRLax)~wN698aD~Q?K z=o5o4 z1a`sT&wu7wGe~{lH}yU2(Mf*IFY*{%3J~R{>V&T}4h@bePJa&={ibZ-`j6>*Dsl$f zcOFjUo_vNm>}wT-nlrW6m03@f$wh@~qn9Zc zDc5rR_JJb52{&5C4zSZc0AwLi&&Klz?&q7YpLJeNjDuz0Z{9!dCD_jBUv^BxsYv>w zBJ|U`tn`ndTw!<#I^|R?np`Hh_YBhb%qKVSxlXe$_ym-|EkP~tMLWGf7oJH6okPK# z7bQEa?hwaZ6<04+ISj(}&#+4zar-546Z+ASC+&_W`)EZ$;{P?(7z^2{JJTn8K>(2` z4bA8mVT04~xlPQWr#cFA%Z;nqZK!9$>2~2C0DABdh*c5Aq}TO_fz)9-SdTHf0ZMFH zfclI{xLNS|%{mb8U{|AxvN1b^dN`DngsEVhljgYMn8=89S0w468lwyHS`R|$u%oNV z0%Cig5W$wb$veuwQBV)2TR&JddMtu0FlJ%H%~58C9>AJ0 z`J$d8lD15I4V%TShm#jJ%)I2YHP-~MZFqh7nMFNi`$G46Km->U;V=4AYov|T4;GyQ zdBSrCta&Sd$SU7Z{|XA1fG0U#YS8t<8~9r*6a7c7Uuj6|5CHrj^j3}#X zkpz>Za(P{4H9Tht7mE^nR*J^NxqH(<=cTA~C^8j=(Fq1$_%LDP1mweTI!#_HP+JA8 zULAuek5_*=_|AF5Tn4c`(a#)5M~r&m1&~*l9iuCF^wQuA!rA7#&X&nyvLKwsjtYoN zjgr+;H;M%{Z8!9>&RNvvp8Ga=X*b?Ig;(r5@mlreU$G!=j|y!=o4noxAr@3{kdIle zZ|Tm%8X3}9@_Z*v{e6GB#s^Y{$x=euiW$Ltld>NXH@YDE5J_koNaIOwGV-2FR=-_P zFYF~nX}SgI`~kcX^;#;oyg=ya*rXteNEsC2(AQy%HKB7_PHYD_32{N@BjCd=VmTHWClI`vkwn$Zl-*<1w&z# zI(`nqpRg?0J_Qv26@>uYp2zpcf_7Zkv5ej&WkIJmOy1YKH|^&dJzpD7nnH2Yfgu4K zx?!Z?t_J9iQnNq{ek0S$qps7}Yu?sdL>9x7<5;5UnFb}|wM1Dz*i-4ci z#iTl2{0R*xWK%Po*MDDS0WRJ&mAlR&xWp&O75=5$bqbfb3Rk!{kgA|?3dK`I+Ct9- z8$aezd&nq9+WgfZk>g`z*j02~9xYOKD}pIntYEim@oER6ne?3OSqE-R5aC7S3uA$Y zYm*~7jXYdB)QhL9U}B++BX&)?UkgFAjKH0PbM_0GdZD**X4tS3lyK37a`ei)g^;#M zM54g&#mm;&CZNV27zi#a$gc3~5nL)f`Xw6t6*_tFC^9+V#@W}HD1_Ic5mWP&tdGwCJH3f=!)>Wb~-h1a3xY2~ZgO#AbNlYMF4u+q!!0F#ys zVMi^K*joj3$UyA9L;PU3?wLOdYWd^eoVAS;2<{MM{|IO?|N9kOAR@3NOKx)q5Fp&a zpu%MAB{Tv8pu#c%`Vj<5rPj;*u2HxXCHs(g1CU@mhbhP2&wil|*~3V7 z>vo#bBMf`d*e?DA&nd!kn39$naV#SVY3|K-z=t{3WAkl7{MylZNXIvt#EXoNbOiG? zU~ujZ(|%Fa9Q4M+@zD2JdJl^Nm>2*OX2|1C68`kA7GoC`dOL-e4X1!tm>vT2uJ7RZ zlbtXGQ<_CqdnlQb8or%_Q*{5;-#@n-%vPZi2f|N3nT$s;2aFKB5OTQ}r`h35VNexx zO5xn9&!*Dhw7bdYHiF^`stsgKjQ)kcI_#2xQk1M3`dVnDo?=nAz!e(i8t7-=^>mr9 zA+-I&>u_X`CDb}Pt1OzA!uuis{k*&=&-FYX8MW?e!#BCJs&WoSAoSf6^ypkHT;e_I zot2JSfrChPtxZ=Xz;bF#6{%QUq7;nJtfRbj#&CZgfmGwVKc+%FWY^`&f9* z)4o8dr(`m>kHMtk|Dh3&Sq%a_bL%M{$w*a~Jkic$Shi6|y2c{w^EPrXd`=fm0lF*u zGx@&|SphPKQN3klf!cjnB_B{M-gQ-%2I!x&>Sc!UHwqBtAy6nhVGNhMl(nL=>tlcV zRk|;^$I25@n3+Zc1RUNuRjid6h55(Xx_mxP1t3*h4)m;0>L9e*iS)(Ij|}!Q_u|Tt zS>pc(#+XpdEIP1o01u_)ONH*UFPgW0&L*sbsm=$0_nwVhafObf>7q8^u`1U)#NYy% zGb@}wsou_o-HXO-TsXQ(J5TBA@@(YGcG+CcDa6T3hYa6y{vUQPZ$&H+1{N_eppa!w+`gn^AK`#@xiT&oNtMQ0 z_MEL2`ACZW3@z7}MsSX(kaJ|4Wmg2}CMPQc;) zd=?a6a!mj3JG9LeJB20qQPvUe1<|puu-3XB%c{s0&Hc#LrhWKf0Q?nS5Ge2LRb&Bz zH0^*5%-ok%%=!P5&S45PQz?6G~!2y#gN>ZKMkdHSpouFk-nJ8mCg9kBtj z1)TVcehN4o8wgZ?^@YnncY+K+^PmSOv(yWZkIl(N=%s!xce_u$=$&c6~?J z`a+AK`%oby&!ODd;?@(_Yj8!=p(EFH8 zc1;JkjVJ_%CRCr@daqRwQ6XmOlyD`3>Ob>YQDdps2=UH6XED}8{T%Om_Oeo8h@c7< ztA0V?c>??3xa!BqfgNL&y+P{^|A8a7M8O@)Vt-{829OR;OPJ_7aV>4{r6#OAItax> zW@yKl_+PFSW)S%@{8Emk?Kr(yC|+Z94enp!3XiWoW;A@}FLETj9fl0STP!nkIZ#fg zhGnC==`%frsy|iDA>Br*T`z4-TS&Qw0Kw)9e5&~92c`HdJ%$|_;5dG)jTQFe_d z^NQ1K|FL!qF!~6?HaK${uIV>S*aSR~*F=m5VJvX>UHSodL@%TJZSYt?_GTk`2Qrt=;DA#rSVD8;zn<4Yq~hUBW;A; z`5%_$b))FRuG%k9kDiTMlsCV@wtw1ymYjKj7xVC$|C2kuh;0W z@Ar}}>%uQl;wh=~baog}W=)o>>-^udTuE(ZJ|%6lOW9qtjW z1;`aSN)u8eyN_<4xqGh`~~ODg~XK{mhqdW2)r47Pj;%9 zY8siyR?#`y=(Z-QMKXGkzSG@X*{w3YfV$(JPni?(g|3|`_l7;hZEepdvo$sfk`brm zEQz;Gm)QBi97*zlBf2p12e19DgoquicT7S_wui*aQvtY&i?fvJh&y(^(Uh{6UiY>5 z9=ZJcj}-B;I#H#eUf7f)9Q&2711-^9PFsG*1w_$?9BlsWH+oJ|JrsAog`MD1cdN?u zVSK4iv&bf!k*&{o7&ovfI~UV;7_u@+aga+N2AA1jZ~y{^79QyVYYpz8@bu8hk(!UP zn{dtxWlzu*-u(D#K55~=16s;v7O5RTw%$rjHM39w`%%+~P!js8Jb&%lvZhLp`29}E zBIp!u`@uWSACSBHfr)50HUrF-f4D`j!0xkm-Di9L(XH5J)2E<{yx1|Zgk*Ikq~AFP z{8<{9emCy{ETLexh+%#p%ZF7Hbz}34eT7Df-LgRMK`3jpn8xwC=abRJ#I!1*`#W#9v;JN^vYMEYJLB6X z|D~bg9Ms=Vd1%Z`9Mt^a6;726o%Kw(+R#2;aA@nF;6FG&-`Z=DIYd9Qd6(e>7~z|`OJPqMaB!hwM&Q24 zHC8%hkF{mWH@65_NrSe#kDC04cV&dImnb3Ej|W6}Sjlhhft~EAgmTebK0mTN|6enn z4u-%B*GR;@?TkWSGwaPz zBs&|Pi6vOc3zV!-Y1!BBQf-W}#fPTsQ2Z@x zbTtGZAmsN~DkmH&^fFM+1A{odvnI>sDB zW{zW)WQfc|9ka~IoLNaogv?_{$UJ2pGK7*@DMK=)P=rXPq9Qbih;Ki7-~amF|N6eQ zTEF34z2}_gx$k@5dtdvyuFWO-Spw##d)wAE95UkB@LfTl^s0V67nN8X#pEZqpnjKN zk^eNW@zng|UhAy?d8KHiE;yy_+eQj`D?$0W!4Y9^!pN*<3Z0ZE8TWuOE+{NFK@5yE z$)CtH3{sj^Q>Q10jh4YH6+%N7@Sh~wTeTxdGxf8q1wV>-9(rqc&$la6h?<{H;}_S9 z!LCH`r9r_cbnad*t17w9ua@N?T-!r!qia1*lRwOK+x*tfQe@P>^^cILu#6y`KBudU zp`bUB_BrHmLR6jB))ZW?*9&6P(EHit?|HM+U0&v1T%86Cb(2Y_-J%i63N)aTfnKQY zcLB={6?E;rEZ?VHtZ>0w;A{jM%aoqGAzscIzr&r5KoOytjuk5b0BGff{mgE`r`x-j z>g*f5KfsE=`SO%YPc2>@fWp|J|{fAe*$Xd=GgMEbSZU`BxVRmEyOw6?lV_S!37YrPcX(CvQu=s%AqV6Je+j zdGchlk3n$vMakZ>WE#9?L+ z&D?|6b7l7-_o%mF#PdwT;S>Kbr=uu%!`DG96dx><4=#D!P ze3|%5fd(6PhReU1g@KvUR^e^Ne*CTpr@@V|!0Vz&x;B(chS+9ltC=z(}%b^Uu_CRKWsai0|r1| zi`)UMu)fq;BC=OFT*14k``m_43NdAuZQ$zJ3kyz>M@==cXM%y}wVDD`9S4zXwrXf6{}VVXoNVIs;+77i*aZ%5obux6~TDg|yf zSij$>Nv)wdPIRk;w)VD75ZSGX)y!X`APn^6wt01E&EiohU@l(^o5_A)f~;3Zff}(5 zwD^FXVDPyjycc-NU)Pr=Y+_9{b4vbk;M9iYhg+_9uZ$N`P71(^Yh~STId9NZW4vR} z)A8+4>9{%xr=%0XQ|5pU@I=C(yab^NfJw)nF4qI^~OEcjV zywRhi!zjRjPQ8Zt;#jKFL0ROQeexHB&-;w1e|=ZSdFA=#I_&c1>eOQaAhwNcA`v|l zj2<7C3oZBuft-cz>f9}uc|Dmf?DeG`N>3wf_X03vze0(Sj|TAl=GdG)Jx#<<<+v2N zS1XURuGp*IPMD%=_>~&=E3k@)Mx`Iz_VSN;LQ@8r9Rr-DCka|QQJxjA*9brJwVK^m zXKBLMY=2+Ai}V=LbcQ_xXuaYXq)B@W1s(N|Hr&EbD6~Er71W}=gBPx-&XQAI^In9E zRpLB_m5)>pTn26v7+gib6+ajNlgt!xXLLjq-2G&W<&p$jb^%y;79D<8eAxrGOWzTI zIQJotTNsv{il)5w^bC>Aw{FA$7TjBh1@@Q$@puTr{tI(pC)s7;r4~~VZ^x^ zw-kCph0}GHyYf2RxLUV}?!Rn+e2JjyPk|Hwn^k_`Zowo?`HMCbS{c!AIzM!>&B>`e zK`B!`YyYy)pUW>Yf4qA<5BoWb+qsmcs^9ptM0p!G(w#+mD*{=_49f4D5wG!pyFEU~ zWFio;0Cj>_X8G&DjfLVkA2z=ucV`8OGpoCI_%~h?L7}3J>9{l)@)Zmp3r3^wu1JN0 zd3Wo6R-fnRt81TOd>^)tlre8EgzK+YbQc9;TTqEQ6a^b?;?9kRyVcclE`u!AT;{KE zdk%_Nt$EcSq{iymlFkkyvm9#F&oG)Cb4vOco8JrH$9r z@XIOalML_?h2B;)_aB3=-ffyv*Pc%C=FJK)E(}-2Ops;s8It$2Am&T}*S(TRDNgx8 z*FyT;Zb(^!Q>)V-f5`h~{eV0h9{kCsAF{k*wBXqYo(;JNck_|knD@^Bx;DoqtfX&^ zF(}R#3sz{Oo`Z3LUHv1M&-3?iY*)a%7a3_^cdX(G?13m~fD+ah z-7Y|6!L1)9W?X_|{&ti-(h7o!ld(8R5HQeXLIenO60KvMC=06>qrO~6@ps_Ur36= zCpTOg_ki9txIKE!u`f!kP1Xy>U61EdvZO%iU|BxB_IOMkOoLMz+M~HO91CC`T}`UB z9P8Q`f9z~I^&RPj)vC@P6yj@eKC2r~zrtlQocxn4U4=!LrLNYUj?7# zFmC1M7|_E#pn}@5@A=VbelTo>+rKHr$|#`^13fe#l>KS%qtPcBAPxtEwuD{jHhMwl z<6_j=jR3}3c1hLSNsY06IhxElY3Z2#8N8{S?m8p%PC9-7Op8zCu*E^o4$X%Tdv+XA zcrYC`1LMW%8WNqZ=Kx(i0H;YXS3^@sGPr4_7oSp9#u#Py7sf9@84OJKKM%-zt03CC zz7ZA1WPixsc%zj%c>DDUUHL?qOzqMZEDcm_IvV7gBof)855DP4*!XN)lLIH+tDHyX zUjWw2?Ir)lth~|90C~RFLn9kHeYn!$Y{&|Vdzpc#7kX_@E`R>4h5CJnXA$AY@v_v9 zqW8<`zPctrF9@N`TYT0+7&cLP0ASS)6|Nvt#=;wrCd|n=K#g!ESyrlPR@z5GT%|Qp z!HpAFErU*bKS1hVLeii)dhme6^+No#Mdgjgn^MjVX#~CGK@9~&uR?Jfzw5g;qr`4g zaRxs0KN|vj40VAHIC+akMPPGuf#J=OPsfe}s1rlOuAd`wtmcjHm06_uB8&+ys1j%G zd$a*XwBrWcNQolJ!WJevc)NDeR6&CtvYtU+v8UR(;nL;^2D+F8&%x1kZXDSPbfPF( z)5BoNyiWjk#1_rh(NxS$K*a=dAV|rt*AAvjXYRL-s_1+=QE^G? z6wwf~)n2w~FK{siv~qwniN}KZFCHHlA}xJFd54YS02UdQ}hYq%C}112h_W~Fjs!sBWKq^)cqo@cO?z|*w-N2zUO!@Q12j8 zUwSdGw=}&hIg`u=cQ1h+aw4PKXA;yAJJmWZ1g3U28X}`Qk_#2Yy&TX2Z-9|y8&K!K z<#?XVU#>H?5HL%p!(ika5Mkz(}> zdEXMhAwwbMO`ks@ISi3A4~n!iga|`^J3p>@{d5_V8w+*CDEjK6+R6bA|4J zhmp+L;r7!28FC8=8}MMEmrqW)1s zB*b@|M~QxhfqjF}*NVe>eaVh@hDQG|5A?KQ9vMJtFV9~cwfg**k}=BjS?4@N?RB!N zr}D6>^%GKoZYMpaTfokS!lq0I+2e*l>x_B=rXa}ax`?r}7=rZVOo1||CdDO!>^gKL z3luXKE2QqfYmgs)HYr%R9KRxEb~u{{-nbNp)W`q~8rX;}K7brYmMLHW2ek0 zf{-)%g%E&GM79yy^-i9`lyWaC0>LCaAiEXMZ-9C{iS zaU!LNG?z_mvJ%OtHzkmll2IpY=2f8DF#={D{FQg1B%>u5e@OE!Mkfcho&aA}-w9bx zEW({n;V(nlI`D^|0c)2P{X0X<<~Q_}-azwOY@#jTDg-aWIE%DfS}~DM%(5fRfm%fK z^JkHZ2;=R7#;X-cKjQ3IZPSaZ4}y$=haffHWQh>B|FaD1*+j>WA;1u{1K>9WBe(Vp zY=^$z-&=I!*-ze@&QQ)N{YuF{TP#&d$26dMs9qB`HuOSg%FeJWmg}sj>)>MvK@~8% zeK>S~=ePC z9d3n+m(@KW@Br-suEWaj$WLn^5GC=PMu=rnUjfE;rT7g~cXr?J|9KDTYlQO^Ws8M0 zv?*9ID8C5$+KU()=U|*NvSlt?Zjfuo3gssrc2)5Es>kIZ-<5g?AXVib6=JYTP|fS$ zO5j6+5KJe0+)|kE!vL1oS<<4*=!s#W5AffXw zdH`)zT_TzYb)^yB>zgaFGWIbkGb!pP*<{?eptL0$|3R?Sh%t(|;O+(N68Zj4&NoOh z5@HOuPy1X9`%MW`9lV5BUGPnxCJ;*1+tMY1S3|D*uZm3sIz9sr*06N0pH#TT%f5H1VQ5I27Bl%p>li|UqJU=#kQ`wHzzf>!X{yj3GQX7{ zl+i}z3n+egrB?0HHKvRi^w&;%XldaIaYKiR2!qSw5-Q9gcSN< zv)lqPgGNRnfye7SG8#fWCMgFr6WjQxAc3cDdyo_x0<)@mAEJ}_pOYahVkbyg9pF{O z-!J5ymBJUNG!L7kQCcea7JQyU{zx zZpw8YVoH<-F=_j<1|f0ypl=5N4-*FNU{4ub~+(WCDlnrXsk zj|f1NfBU^^_xC^#(~96@$u&uMPBdJPe9MaZiM*faN| z2S3aqFdmflZH^fbs^_d)Ap-?uuH+F|jRt~uP`^Q!eB{LIx7;KhKejq*>ogs)-?=B&aS^OcduY*6;=1p>Y8k7~X3Xh+u=) zfFeNg2TdYQbU=9}8q~1O@fr{=ueIY#wvbhYwCec-xN{7*OjI{lCmt+7L|MKE#1Ouk zALZwNLUNiU2&vU#KmJnI!Kz$G0n;zI%FfZ(fPD}OkbW<1v0pGnsDJmyX_dZdn!nl) zztqC~P+oB`PX(pO5V-%=ls=^p#wdrX0AAqvk4yru(l3e5q;nn5gA(BiX#9_$_uluHm;ff#~R`ZqYH&JMnn2-tD%$ z>tAN>2%T|HP%y->nMonPd7T~h@b7=-97ZLE9s9fu0?j0?uIi;Td%!NGm0vD|5Pan@ z=M$a}Zyk_Z2sdt0v`59$G5!sh7mW;XuZfk$&^o6PUCM-}Z>+#C_{Tsokt(@dbuh7H z3*t69_%L$yL4QXFt``gI1xU^W?4uH(FCK!(njl{Sv-MgCthKbF%-0?*Q-6raCqSC> z`P0L0xr1||<8+5|4`}9d>{mm?VO|XHLtcaqVH6k)U?`GkFd^|#`W+jn$KZO)Cc3!U zUk&}}I;c_~`mVt>?GNIkIWaXeU2iLmfZb0CXGT~*ZM+_vm$jc((&(G}w12!GE1_d;%(Vv)=$7%T$Hb zZMu3Kqjp&XdfXmwXKtZl^MEjBF{1w}qXnn3NFk zhkqvxdCR@P{CA-IcpT4Odn{fdV- zn+2Rm%u8!dPiXmZiI(XFha8&z_{Ut2_194`d+v57-)Xn6$?F591j~sD=OtPym4fsWD(upA_Tc47zB>H^2)*d?GfIVv5&(1-Hy+5 zKb7vfgOkiEFi3ub{BO|usr}_x*Xvxtz#I~!cdbo4&gh$q#ZI*>{+hqPf8_hfk3(F~ zpHk{xbG=QZLwc8t%Xa26+#o+fN;$4}zxB5D_}2?Sxz3e$X26QDy(}_uC5`h*i0Dnd zCeD~wKu&H?8A`!H;4o=}iU)~|(Kafda)#Pkr zMuN)sfQ|FUHXL|Em5}aP&NGu23Eb^WZ1?Ot)XwaplbEK} z6qxFpm&s|bKjoO6{j)sq)qmjUYCzUwb+2LU_dpm3-kl`)0JqIpx#0Hjw;5sbYAa`K zW#VEtMuxfB;X{ODG>4DTu^?Z^fot8Izd}j}dsN$^w-U`+qy`N`yDvSZU~pEPf_BG; zZPT*hL=I%CB9TyvEfLjY5t9&AmC&lG+&D~Tf{H= zDH>49Iu^hf7wRU!IFZ@nbtM{%*sW)eE>G5x1$Xk1dlE}`?qE(vIqiI_fAT%4H|JgM zoK5HM;wtZ2!fMIXqbMR%FK2~Immfx&5{y9U^y_MT=h`KRh-jyY{*sH(!b1Gb2C5Uk zCx-4u@^&_>T%k%IgSe|3QNp)8itZ1ZcSiPzTnFpt75*_4*(z47j$ zE=T8s<~A9vvw3Y>JJSQ?a#aNws~(>=>x3}W8c?Y{x`n?tGEx4dDcTHS^FT_F_8Pfn08#5-T>cs*5Pu6Su!aY zG%#fh*1m>b^b(Zh(aHoZqvaXS?wUcFTiwhpZGC&Y^5GpzOaj&J5zo$3$_z-5k>dw=lN{0qmUDA5Eomz~o$xxXm*y!ORW~&qD6FLVB`12B zhnXSwX@*dnOPyLCY~8Z)rb&r1;`{Qo=&eYB6D>+_!%ZV|^$HH>A=wpZ28v2x7E}J{ zUBpTpDzobMnp^>;AbL}tpc@k##`GCnj4S>22$}fq;#9hu+ats%mO(k$b*f6JCDN>x zH}_81_q0!ESRV*>Y7j`(^4CQe;S{ealjR&;?;ogOxyhkTSA92DwFuG)#+&Mg{+_jO zaYU>O@UE}bK|a{fpQEa*>o5}89}yV*Bl`Y+3s5=xX|eLM?}&%UxMRW$!mnn09K*45 zC({f0J9gP*FI+n!t%i#@%)mG!rnPeFI`fKa>?BMsi0eu}XM1r{59n!I;Agn5$1*eW z;f=R-2f^eic|#ilGB6omy_DGO7m|SCb*OvVLM>?~-q}^$A^I&&Dd(_0>#p0J>zohe zVl04UYv(3F7=bd9vMV$jpz0C6UG8NJo zan$ZvWne9&ZGfqFhLsQk8k9;)#IAy_lS|=RwK;Sc6UBG2S~8uD&^!b>vm<2!>34ea zXs)?4+9p`T-D}-l1F~P{p9?t+k&%p3_)wM`nXbEQUHr=6U?^@s0ODE%!m>bBx%^#% zE}dC`C|AHe*pFY=j2ICT=`%omuM=w8wtV``tdZNFwVd?FH%adAyjts=FA6aQ7QCrBJ@be)21niGxMa zJgdK&#nUIaXVzyV|PTlS)@BAAnj#}O1$UJ!?BHc(#GN0p6z<4FS5M#RH z6jVh+f{WDKW=>A`#2!`>6jOYCFs<`C}+*rCeY7xNL~OhoLO^o5)y-$mk>f z%{|{ZoNT=UUvuQn&%Q{uxIjh0g+H!p=%x1jqu?Odv(8O@kB;7=Et%HG9+05>w!F3h z!?uldUo+BOKCd?xuE(rflDM?#)$XFleQ(fmB6LD!E*K{pBBu0(8tofy*8m)~w{&HC z*^qFi8XoQqUAR)W{+Tf&t{i)K=)QuoWe|r-%}imt&&uLHmXXqgU$3mfx>Yi+uSF@@ z<5R=^OX?l2kb4D@-~0M^Pj%sm_)(w28~JiaM7#TP%p}J|t){Il?dlZHpKV4>xaWrA>0wY|@qBXV>+bAyqJvW#ka$QvrnZ4EpF6A8tL9 zrsJIU_un&El4kacs-XTz&`pt|6dTL#r&-Y^`tKZ%S<~lb{m)s>>RJQ?XcB0g?G7W9|dv4EK5Lylj@N= z&2XAVT%JGO-s{fnE>nulrXoW_?3qgZ&0M{Q@@Kwi=xqISRg~kEtNt zY%r=*~0dsU@Y0OBPAuOA?&sx_^Y~Pf6I4x?2y!k8qZ7`ZKdd zm(4WF*DdG_UWC{%*`jo=a?Z4-en3)6EJ~YUwk*MC!~N*Fdp=~eWwW!Yc(J{E?nmx_ z8RHbXAweZClSAJ*)mqvQs-r$y;*=Wmr)_O7^bc>3iM0EI&Psx{7`lZ}O9q4_s5F#3 z!wk*;v}UmxG~9Z(Ny$)VSi5wbP?L4uw{2f9H1T2**Y@GBg%PW>5ec~cZ`{^;XM!2S zN0C%9AqSBrAep~BadB@z1dE!cwY~zs&w4oE~L+sO!_e4JCOE1@p z?cg-SRGiphUU)LQH7$Af^$M_Z#BhGkl>?vonv~Q?xjy|&t^P5prc*hBk!9NS9z*Ak zv?fg4{N1V9P)7-n$hW3`Hf{m!LLF>e?VQ04+*(aZ9hwiJ#^KVy;R0DS>@cZ+YT|4y z*a5xf)C|S zRDIYDmCD<4XgaLG(B!Q@@p}of#HUb`2q{@bU(%57rWOlhpo}rEMN!56X^Rt!sSz)x z824}7=Cco!FD@MHwZ)6{A`05Sl40k{t|HW4%MMre#Z5ida@r>#ky;rq> zx|1DYAn+C$j$`=+O5`8&&n3xpR#}MOob7B3e?dh$!ktJFMIrO**rbNso-dMO=x z4Ydjp`zo!&%e0JrJ2m6Swa+vK9Voc>=%9NVtLeJ#c4YM%6q(qXY84Igc4jU{0D7;< z0O1@EhR$Y-Bjia$#vy8wH$U4>(Q&qQk?uP$G;6s~dU6i5o!V-#%Q`5YD}S zs?kSnZ{Rd3l9tIGe`rn0LA*CG_-B~2sN;O`2X|ah3d%}vo?`F)ezjd7$kr-`PJTyb z`d;dM171#hapTRS&!tkFg)zywW;8knn1NcD!a_4;6JoSbYVnLEAu^KecAem|>BWa} z_UYe*LCECjJV5Ph??SO&T?Y;bm!#DAQJC-T`+tsSu7CT90EaNf%;gNF<0a7#{-9#b z{s1tD=3^SLLAu(4HWiuxTI067>n1;Op%iIY};+OXAx76(A@>@p4>($#+wCO;|9|8I$_MzXQ*Vp zI0HjCgY~8o?HSu~MO#ZnqgqU7cXCRsCuMG1J`NwlDH<-P+g8HHi9kWH?l6v;-{+{SGbh6Hmn!Go3(h0p%sF z4(avozGPm=@IJjAtU}45t`KT@xIt8d7_QjToR6fz5ZkKcn$_A%y|?1v_xDuW6q@GW zx>cJ+y7z(=rAF4BuStaL;bdPvS^o>3r??e~l1$DmNFiau>raj|sjikFZ z%WYm|XE1RyzbTK*x)IM3WmlgazyC#oj9vN6ZfG~EA?oVA`FWdsUC-~NA;lyIe|`b@6CPHi>33@!MFJ{ zl74qO%|#EvBPvr+VV4~!rwJ!#I@sg9#AgbhK-&40J>T7gr)H?@rYN*;5#L8}9=6ju zgscCaO%&Snf(n_Og!$M{Lq4MMl{|94CSPQtrPI!cMbi4CRMl`o@c6KWXyo6d7^NGQ z6Y`ZLiGA7zRaST89EhIQ9|VE84Y(m#R$T4trD7>S@+ag5PCkWDVd@iO-;}XtxL}=V`lfl9}&R1?khmWUuGUF_%VCLLp4pjCf7nD ze1dWGtO<`HEEC9f{8^P*b$ z3>)3fP6xF-@&~1O%WKe$#M(ZcukDriuK<e0)P!FA#38tF9>| zCZo=humpUkb$$PCgn2Xoz92-_zm}Eh$ro7xd{66RoM>WTMMv4CeCzLq0j+O82S;7M zQ*X#^K|igNf!GEwV#KVP;u}*hXO(`|<9VjBh`AV+%-8YwkRNvX#>8@q)Z89l3U9(- zVZ${Ir!r#_;QfLSi!KWkchG@6|64#}2h?r7Ql~vu4DZ9(Z3FyU^q8suh5us-*(!YFPVQYJZeo?FTYh_X@kE0+!L;q<4 zn)s_Kf&MIBgHQ&%B7%S}LPaL1qS4jR8-}E;`r{wy0K{}yXD|O1&HGnWWJ$qwgyRjs zdCO3rm!E09Drg}NE6RNR(nReX80^jKHh;#GqSR)AurjCc9t=bT&x3Ius}WMU^J88G z{4IUE(Hy|*09Vcj4?Wv9dZ4rk1`L2AC5lMVNhrb8ARRCnR^Dj znqW}PCQe{#8_hqZ2#;z{RGOp$caFPznD8PMgzZQZOQq`mlw7=3p~j-vsmL5Jsc9Xl5QR2}OLQG7oIVA-wm! zL-eLwN1HT|iR@tZ_L+Ns>yU>U;zG46Sa>IR*YzIPK@w0$3JgNX9)%S7K-L#7V!yx_ zIa*I<1r1pkwpt2gHBfTR4~6bsbrO!cX7^GsqM@WEKtWx&fh1*)N1zt4KSb|E9kyl< zZOeKIditXXqo-l$I>VNia|u=GTMlu%4&Zh!QyIcc=N#Z|*+c!BbUWVjFLFxDwaKP2x~A*vE|mFB%X|K3dC9-x&j%QC5i=m@u)Q^st7s zOMKpW1Q#(o_ziA0FoKbZFG+fVGZZ4;I|ZgfQ3fBNP4(iOQpg>zJ$Gc70wVf$cy=?o zsW4JaASwO)Cibv-)8BT|*6FZw45ZHMx8}hT>N;@MS`q+#=dQk#?;TLy+0zT*D6^J! zidK2cu9ALlP`~K$>{}iGR;3&u7%(Q&{?>q6XOim0EdS@0D+>TT{(^}A%`sY^?csaw z>Cqh!0H%H$Z{JIY!a?n`7HJ(mY(ElWvChmPQuL87EGs@pv)Uvsl>eSy+Zg&?!R1}A zZ<~ZPLR;Q-8fzp$-o=Iu@lBRa_}4l68lYsgT`nG-vHAtO?!}LYgv4np4vSjMu6Ay@ z?bmII)1fOQeoR+m1<#Vc-snyJ&4=2BixqEZHrJw)2VkOgYj);kkkX5MSZ9P}zJ8$Sz^O+ixo{GIxs zMKJaMPNK zTb!4AfTa@7Js8F!AcZf)qxd2$+WaMwnO6{tT_8&{`t}3bAN@6KjU==NI^xIf-oAAs z88R*Lm$%&y+K6-v9s318l_wF&jwJ6lHqx^w6}`pSEPnP3W_P&g8b|19-R_e9!Z~Gv z>nO=$YWBuj_;Z@MvjoYRI5x&`dN8}g1uA1)P6JA;71<2(lPBL{&m2{ewGSAV2Q#79 zG*zaRui(<8PlkE9i#LggnPKYhHe{~9*FyKFMi2cuWLu$Kad?`qP`Oqbj7~&DQ=)IF zABqF^RWXaK`~mp|mt=E5wQ0&u@Id?9);Yz>xxUOfd3=wC;+KRwOm4e7Y5LIT;tVlP z8;q82C|NZXnAbhWvD7R)bcpG$IMrFJZ?=db<}Q|s7x=P;^aP#S93QhxcX0!J2MufC ztwM^cJ3H_9J+2{co|XwhTP7JQN4}N+DA?aU^e()a0)N=Zu4a0E?DEXfQLv$`D7l4G zO;y@&{r(CUO8Bs<1=q*w=%v|%!!3&HmSc$Pyaw5k1FKPqzx&(g3~eVzunvR#{Txga z(^qDSN2@IumfWTg&l5&EQEM_L9mGPf6ra|o!&Il!@mTVOn@1)L!@@sV+Bz-R|lJoxtxNBrp6ABnQ9n_DF;M54sw z!a2rFfcPt4&zAWQGUEb+KyO!0#ul?Ebct=uYXdZ2I#M?)Ah~h^ zn>1mqRw?^)o3H6Lq##zbJ*P>ASr6~{{qsu=b7)W=;kiE)OxyO`aM@UFEJ14NBFxGi zlWoZiVgG(?{ca~Mi#WDX!zUfhryFLi-*U8WX40`LgF!It8=>~XPcRV8+KLt)UrY{xG77pMGY=dO**o}a zKu&z@!<)M6Ji^Bi8ReIVA9Uzyi&g!EB_86m3ueuKKm-b4%cX~r+J|@T1+*engs2J{ zCbT1jEH+P7=FyjCs*C{(V#xYZ>A`YyqVP%cA3J)k+scUKbGNvjN?d!e`->B3mGs!YpnfG_Z0w(>@jQL_deGR~r_gCY^RaIxMutfC!<4rN*Dw>@<+6b1pwg>n0O%gdyI?jpeju6PR zXWaNRw$IbQKfTtopRe=75O?xy5`EFy0V<26UrAO|4HU&?@BIC1+`g8yKTJL1-ZpW? z_WixvOj>nkXH_=F5Q4&0%0%j7#g@q~&j?iDfEiX!UGIZosLu#1zs7dV^(;x{S<+oS z`Z@J*dNr^Bwe`1|W5uZQ&m-<8z*2be5qzw71YY|KS8|A3ivEQOSIam%FEqI30h=J3orL!DSc!6nX(1n@rG)ux~8QJft#ZlO@sO4$hZ~*qHwKY5Li?@UE9J>~;wJpK<-nn=Wwo zm>UmE#eNMZm9kXi<>n9J*ERn6-(WsQmPY5YqYE z$$cjBo}QcDB8B4MexXcQDQuO+z-Jj#1h#gcV&u| zf;9YCA1Q$ZcM-Fnjt!`9elbucn^Z@f*ya<{v5(z7{y*h688=8edKRiGS zH8}dP^uQ9@gVJ=pfJmpncOW%o|M8*vUKlf!n#nu`=RUE|hhl{Z5B1$~%k}Sd2`bDF(w+|PSXjbG_Zq$XO zKKt=(<;O^2ZFeq-H>ZbJC zNa81wbs^&e6ks#|tlSwCpT(9m@5UPYDH8Q5W-Xcrk4`*_Yv~frnzWR0`D|`j#hX`xOnHxVAH8VO?{(mpX=V_n_Z@I!YEmW4WSE} zLhL9G^P1H5>y*nePylFAy4apYS~JOh7|cX3<4WPwzL>)9U0L%%T^ZH6;2zJiaf1Z! z@Q>>%t>KBI{Tgd-Cjap_g5S6(OzhzluxY0N_-ve7M_$8g0c+w)+(#R(@|IP7839&L zDihV464*V)d`1D|LY)?xJQTO%&IRXYKVSc!f+rXJ^8_&q8w;9ie&tc86+E@eg@ZiB7i(9p>3-)6G6DO|Nyo}ot^Wp0wMP>0h z!0bLq8f-w2W3Y>s<8EP?rTrS$_60K99*#N3+e7p% z{=>>vMj8VCx#iI;(WrnfMoK*8xhCEX?iyNJHb%J2W|~MZ8#XecNyW9nbM*=>;x+RS zTvZj5x(baSIYGJ}cjVZ-<({{;Y4#q8_*>l3z1je(v=Q2yZ<((TG?3q)PpYmz>!zyH zM#;%wP?c5jU@KAIudu^HJvAL|2IrO?kR7MUyKfP|5dmBb>1UItRz9*uqI6#fE?=JG zJhrAVg3&7yKj${U5kJ`*Xkj9FhUw`g@dWH&-4^VF<9P{K+&X23-a7rxf zh$&sJZ#Qk))+nd?3sd2-Zlu$}ZNivJnjxI9@pI`5o)%}+ymtJVJrufTWY!*8 zD5R{OY}hQ#e6pb77S&3b7ja0&i3)f#-0$+4Kiyk#^G^D`0$+V{wOwXT?m~H6;OE+r z`%GAYmTFt5^1OB7Zu8i#0ww0b8lmZkZ9nBmD_&gc77Yy3RyoGKsStp=+?BrSZyY$o zSAT^fa7J#4R5z%6b5mi@KkZhVSuNRRd>kM#RovcH#kzu_n@eb9 zg*Wu2M13dxN1GorQQlhZJok=r#)PAkHuUAnHV+e}QF)T_Q7bt3iv9?D%p|Mz z{A(JgPvBAi)9gZ0dbfJHAo z2|Q!ZK9zlBqW%g`4wJhIWFxItmmH{vHlfp?2^t5R2`VO`Db403?{j5g=5~RWLN4I> zH3%@#DYDN7dGbFCBWXp->Ja~Af~po!GSQ#9``!Opy*@?a{QK1-wL5qxt}{fIixjQ1 zG=6C%%U9SdZ5}msg0(rSA5B8E7b=izT$^mdP+*u zq1F5MK`;(dXUCqYbCCxrJRO`Llq5#-^gplM+W-&@1Tb@2yhElRi+)L;FYz5Q+j@Sy zeVUdQ#8SY_lwf+by#@0AcF^s->4E`+@7_DgU;b=~{2sIbfODO>%wTcxH>H)sg5pH9 zMwM37FmwA1d^*sGX+>j!3lHumoZr1{TUMKiuoaBk=J0lgP7S8Xh09?SX%Q8%?d6luiJ7MIwjXD@ptynBPJ3%0NTlGWZsN zVcH$xJ?hwzzP|!FXkH^n%dT7l(K*7$6T;q;ijKAayh}vW{;8^6Q1& z4*WE*;mfShZUfRssfsTU6cF3xnmiCfn$}oK|B*qIv+x_U!LR2Z@8yF9SZn% zaz`{9$e(tK79tvGq$k+^iPnj=dnn0&8rVd>AbyhB2Opjp07!n=Ab&Hw4}saXh2IeU z8wx27o!H|lOlA)bFFTxg- z$sHY;wZO&O?pSyaFe6w8+VieLN(<=ZH~Uex669?jNY=>z?j88geO?fy?LRQ=KacX5 zi&wOeLrva+I~|lfq~8IV1H3qM=egD6gOewQkfHg5e}ZSoK{chu3(x{%9;Tr(woPd} zgU{jHV*mH0Ay-RKQDj0zR2$>*uQq{LQ&lB^Hk$H(zBkkxF$f-R?sP>!>NahhIavwN z06}8t9=i-yV2VLoURB_ppD7Kamb!IK4`Nm^G7cxr#l#WmWFU*;gXk-5A|5KAg+K<3 zC-1wL7q=b)OnL15lQ!6$9%o+`Q^U}aVd!{2pG43_YK;2X*9S$-e}zJDEf^!n`8K{Z z!eG2q4fGqVFiR?pST%kN+TL*y5@0I<(D%$s*iX&yP3mK#|KE=(mP!-EF$FyopgDNB z5mxXxJ_focYL@44nyI3JmQG85_H8Ai_69xEu$%lr3+z9N5hT=k13D6wMp*2qy!IBV z0nE&DJeUt+3hnv&jvxav{Y5~$-Iv0zEBgfeG?FV)e9||3eL$)Dzmy@!tKvf+Y!+=p zld%Sh+N=-o-8I?kBAAI#XwD(ZOPQp6rNETzR|8Gw0X~GdcIg8=>UgC|Z=l&hJ_aZ; z=n`G;NgMw?I~bwtuwp44mr^zhEP}gt9l6i~R9?B@!rE?o(17D2+-a($I@p74X@nBadnptyB#a4@1Gj4f zh1CscE2|MFZd{TlB4PBDy0?LJXpmLB z0!s%L>GR^Btjj0Ya=~Dln?C6nxLmNCjm|3Evr*L=C2kuhBI@IERttt8dVV zyKc3Z{fj^)blsbxP#U;QydhuxaSpK6=4&yb@2Uh3)&SA&spYXw{(HH#B1;L77%DV> zvlaL#M_QfMVOuH0a`B!<8Ym2c`VDi_pfWbn3CCp1y;6nc_XfZRf3L6V{67zlMi6hX zSjXxc;4ga@vC86G%wy2;OoC`#(|~^(nHZL2#KYx3g9Q#&4<8;Dgew-0vuBUgJ;!=y zI36179$s=Ct?Il0QViT0HlfZN1SI}ozri8+U$-S<+|Iz;kmO1~ zYbs3x@ft=IfXkfX1{mCsM5^3@3xn|?V;|t+t_n}!5(8dcnVNp)!Rs|ga=tHv{T~0U zDUgfr-0Y(_m$qUI2QSvC%G#qCyj47lL|4x%z-^$H-!{x6H3*5sL?xhgkhR?ZH^^d; zjoOu3RrK!*7eTBIM_4PZ?_a`WSy4GBJ!RI|r%6RjE0PB9gjXp*uLjII?aW}iA+|sc zD~~`LN_ba@l3*K6+c%P^_5p4cHU+9aytk#a&_BOWD-Sk~v1|F!RD=iw3~|Kqi2uE+ z;k_)lTcMZm;`ZNeS`*827U3(bWxt_y_!*QQqW|W(KOk2p%;dV^|2c|DM<5=RMjJ2- zY9q&=_QeDo`dE?6%V%z%Kmwd@w;^OZh~HBq^j{%<2lCq%A6&R}tgy#$Qt52}A}vla z@Z%((8rAS=VA*v2l|iioT@ztm#O(GE4O`m8@V?Ir>2VHlGcd~@I{|_F_H6WBFp~xq z3!(;GkrLYZHw}7<+))nWQhH`Dm^46_7v)D`(47Xi)5I*ubq!&6bdg*3NfQ zX&R`3$t_VvT2}HfB?DSoh#{}(YktRnKXC{7L`gFj?xyQosV2BC_Pmky(0%X(NN%l$ z{JLB}FXi?qSZ#o#__!EY`3zWjK29I~a8PHVco0QbDT84Bzn^A>gyUm%Zq4E-`^1<2 z5H1mVHTxu7#0YVNS!Q7VmJK8tFqfj14N#)mv6D9+sV*T`33eA2H7b-=8ujnC6Z#uU z;=Rv0MkAej`}mg~o&;w}cRSc3iZJkvirYzB^TuIrk4u*!Z?WIq+TcGse$YrqD?w~4 zDmX=Ra7V$%OX~hrG1l9SSr(C=T4j*f+;8?<3O_fv!`st;orG! z9q3Gvt~7~jC~Oh7qWb+#>rK=p?O5IDDgvJ_KYh9oQ%(E1_rHEhysr-)l3dEozMB8N zSK@p2P|!!IMAq`PwjozmNhfghc_{Yh{8f-R_Ox|4(T_=~uL_I0LPI7+E=m!N4O6Wk z6N!1aY05=RN~cOhOd7;ocqVGCl##{24z@qUpoYiLX1JMoEbG%vHO1Zar)DQ<4i{m# zB`cSN%X5qUyP-q>8nPR`fjM*d1S}&8k2`fGr-Ba!A5Mg7W$;Qyzr`=oJ~kg(WWz(R zsvd#G9fnoYH}|#R5valjt|@n9uK2AI4b5PO6_DRT=pZ@!nmQf9{=--?o zg}kFGC1KQ;SPNFtQ{37j97%E8Yo{JL!zc4)F64!yx(a^$KlznBDh= z><6ux6O6{l{k9-^*jb5rgLDV8zkARk_d5>v`&2-4?`T02hHJJeq3ozAo?f7RqkLZGB~kCsYPz@;Qz!bVYyJ}a_0et+^j z)BZPlxlpOQLKo9~oM8+^#23iy2hO#b3xU6k)>NkT9C*)q04wB^8gSmqcM#LE2ArIe z$U?G!g#`Qb?>1?`GtVYOQOvwd4ykI61*3c7kI)Yod6Na3xBn{Hb6-QZO;GUz^qH@X z$a^3;5!&^hjzcy;3*6tYokFp{S0yw(2JT+$+q)tobmTEp^Z?y6NxJ07!8GH5^2c@E zBo1I61csBgkAgPa2yVR3NKSWf(}Q1b6@uFfQqwzm{M7MK@Il;^*A%vC-bA?5V^g`o zEskGMXl8?*#AWUHskjI5@pP4TXoTDX;iVp)*sbDyyIvcf#NAMR_F(iPT$hMsA@1>Y z5v#*<_c9cV@>|y>;+G)Ii#P5OE zT#}-k>qd*=s`e_ixL(X`%tRH7Iw9WALhgCZ!2)t=+4>gb!H-XdLVaHpoOXSx>y^CwJShwr70DM##Yq z&2y@uF~P_@tf&(>nS|us-HM6UM^6@q1ML`vHAz|)iiAu| z1J187vxZ#>Ut{I$sENy-T*&HxD8dNz2Vlay>v>MR!w>Uf8ocLZ=i)>M&NpL(?yhzh zv?5Rol?{wXcnO{Hn-yvurCo9K)~=TsqViyc)9@NZy=mpbQQ|vy^V>X+elhjhg6GU{ zV)E$C;k*4u@8g?L?m0!MlfPX_JU5KK?P$s|TLEw|&|_ZR;u*MpV*QN*25rdDQJy03eBH@W=i#h_btuqE|_dva%P z^rc3iqg%mm<_0A=0eGTSjVeqKLA}{^!0#E`$+4dc8JxhzCDwx%D>r2%NDPY)ogK{u%_GIIk*f{w*X2Cs$NlXM&3X_+^c#(HL~lF%;)yO8E2Fq@l#&%64efvu~TpJ zmeRr%q{-s)ibg^g%OgI+J02QWx*Kc`0|Zt)QU*WiO2MxtMZ?*pxh+L|{9fCsj}j>A zMY0dOhInTwg+aOsby8LrIL+t-Q}5Pz;q9RK!%{~cFgcFN5VKEtU9H)|pP&f$^OPUW zfqK_Uop^LfXu{)Eig0FEhg;=^^@akd%Fg$JK1M82`Ja3z6`o{ahm!5_^~K1y)<#3_s5`MS zVQDkPPT?=z0<(I)<|F1^`r6W*^;plP#%;5mi)9!X{>Yy_0N@>xs;t;&9w*@wSnc6< z)%24oj@L>)MXr`&-^9vAb36DbhV$28icF)2(uA1Zb!I{jk8Z>|7dlQ5(VM{n=KBZ< z*V*B2HTalYIN3Nw{71_cegqpIWjHg}uT9d0{8C1N4bLzbguX}ua^a{L)k||@EE4Hw zHbhrDODJK%qO%Df{M|t0QPghDqNmTIQX<#l@V=di2Dh^=pwD_yz^SwX9fQ}#3fMS7UWfNQ#3-?VY003ug$O@Mi8bUJZ(K)kh)~~*uZd2?>d~d zFJSBU?V3Woa)_~;UbV8TI zxCF!I`{iV7CM~~7FPzo5G{6-tQ>GuOwdoT}f@rRubQ2;T1HCRiG6xAhWObtL2F!%WykOmW|&o)<nDgv@o1-b|O%d$*F$H6qFRaz*H|5-` zEWHG_L86aaU|2BpymfL$*95-5b(e1D1DVyT#JE#IDsttG_2ffvu_10msTX2~qP}c;@Ae3`bEygX*TLH&2N#83`HTNDecy!MdYzRJF9;#ug9l)fsXi z&&wCLyDqGPVvuxU-1>9~GUb+^vn1KK+m;aVe0*3*n<>U2>X7$AO?yXi;Og?2g`oecZ^n(<8yCQ;-JQbHw|XY&1k zt9AWU9KZ1>>hjh1XV2Pdr)Pv^KLw*uh%5>gm11Gb%mH8B+lkILJbQ*brgXLT%hnjthd5PwbN- z^d?g;$Fpx2r#qbb^_=q^#J4IUc4Pn*^mmHhzhz1(&5%r-Wd{xa0LOjE!=h}Lw%2(* zEt+-3#9kv`Hfoz)Y6Vk3u8b+1v0K;mPg#PiRb6&b`t%}u<4Kv~j)KhxR(F_uPM`NGvGvfMg+g3hL)GgKyKB(V zMsb2NA9bcIH5y(MoF_|o6WjDP(W;A<4)dy#m5=ZfHukhjBhDH{j@v2= z25yRj`k4%<8Hk3)y$aDp4htVjXzt6a*eARNmlC@e^5$A*w^nSBRSdl$fx`NZQc}=q`^qdMzMq2r@(bU zut%V~S({H`vQQx-zCHqdghr?bVty`}4sCiZ*HK?Z?rOupFYH90w}XtmMOw81^M^<4lOq z3VG6KvR|cJ{AtWvE0v>?;N6x1^pGz8Klf7>0IW_lH8H_wa#?8jwO{+WXk;_LPNTAf`{~+#r^9)0om9&|G3n^BB=fwq}IqJA+T)hb>g~La*x8HYeocF!VC%n zkw4s`O?AUU;SK(TdA(VXn@L`}vHEr*b*Vd5Z3|uvGOCq=VC8u~dvSH_dNKZadfA{` ziue*paIJva;|;G+#NU+52u{fMX5q*kbq~W0xX4f1MZ0Rvd*y5BS&&cCnJvNj&GtDj6L z{&X{vZQwlX+XNc_GsE+&yjqt;Ol$1&?oq59d zSS=dHF>`n@?Yy3+px4+4MN`x&?7dGR@|cp&^Km7KuN|wJ>-@H?CPR_IE>?^webY>p zMh+J}kaS#dr?Z!^ps@xlO;${3_--k2ixHPzcT>Uh;iM-P57!lnyQ_WvZ2*k}R{kT6 z#5X^gnyA8o4t@ql*k$~dD^nj&2Ma2D#N#FX#ei!t1`iFCU>Ac;kpR^ zEgJD(kmeJo3|R5eM)wBC-cxC2cJfaE=>#%kYK!^wlS&DOp-Xa?WPl{!2a*#oB-Tof zh;kB^gmOy1sC@hlpn>bNO!uAvX6hiA=j$DR-hHih7(Bt=32Q`Yds&Ez(MBfh*sA_s z88FI+76NS~hE%?)H)gsaAGdrRpLn4G6Po1;bcaEa5~5Zz$%(5kyavFTduCSU-V6*L z8X&o8;?KvSme&y1Zb@8Q!jcr$A7e%3a0VoNySGh23Vj$hV9}W!;*Dg00XVmJ(%(%< zbJPFL!Q{_E0~3PFx2c=#6o_VUwqRhkb?VN}-JosN7|5v?ASrvi6&mRcDwLqv{@FD{ z4*7UivD9LJz9kDUXvCX-{B&t&yU&}>u(@j=n3w~-QcF8o_b z*PV7M`Dec}Z>`joP0GzGQQXzk*5&F1>9?3&ly=_La2(6NFM1>t0WRXXrLTqEXR?=a zj#8xmsKd#*$+3qogh^1SP3dlo*INFfWZ8jtVv4_mhmrlW^d1tmHLyT^9Ku#>_bYUX zRIS+V*BjWT(#^(>wVjx`h>WuWTg1@uv=J418D`4=Ut{`rA+2DiBUSrVpu1jcvajU*Ju#p zcdXrgP15bOupSgYT-E#NzE4&bv*NG@*&+y_Xw#Do?xvf4Pc~MCH=n z)A52j+ublej_LmCnp6>f% zhv2xp2&IHl)c*#@%`44;++y!-W($xZTUrvOGt6mP>>@QTll~AYWY!zz#Lg5&HV#x| zMxWC0sM4m`NdF_Efg!Vak&%%p-dj+#+I2HNw^WYB+V(paJo{=nF~j0Uiw?$z@`pnF z9EAiUp>{~lU`KdfQPkJ;x=o6dK_SI??=^avJxEzPfIl?T{AqefMI(dnfxlYzC&8>Byu5@q_vx z?v<-4*{U6olz&whkG+E}GyA@-#f>@*UqcnvsKk^6ae{K=>{uaVPlx&Tev!V*})sf2VpkX#`FY3%MQ zr_T|GuX_nn<_$}0ow{s$$a&G`4SA1d-W2_mGWp7KuKmgF@t6KA+|0rNJ&44{q|3F1 z`n~AZOq7%lzksoj70ZZlhW=>1;H5V8MQ0SFN9Z%fuO3yOUFs4tL?0+K(){xMrpb*G zG|DD^iTfv|pj?X>5^v*R35uC!-vP$s1;9_-AVEQ2r~A>0=7iBUiBQv3ao%T+k@d4H z=sYC>60x+fp!A_HHTIvJf(WjEx_C0H>^p44!LQj<1u5pcC-XDs*#yU-H@7roU1>+G zCVAOD^BM_Xn-Z!&XxY2YLBa!!k#6#F@kMy2=3nC<9O+O}GoNVdye<|~xZ9es3Wlu- znanvIxYS#`%iOcqS(%X6n+dNsN7oZ|T;8>F049>Ues3y7d?JjgA2OEI1VdYIPpKN4 zzI~(;e?AANS-Ium*ueNpt}+B1)D!p=qcz8U!3LV5(TRrEL3r%^IxuI2^2U&*7M+#8 z>CzWp->aD_N;Y;drMSqhpP+|#_D2v9akEez5(q$?G3V8&-iXaUG1JFqUn3PI$WhL- z#r@P@kN9{OK@Z};ARjrR231^VRCiMq!_rLUi*=7t%8&1+pLUw*emogpU2>8Esel#` zDYWNpy5FE9<9cA-kEbHWAP)`L$%TeYapzwo>FW%NCD@(d%wgfL&bPi}mPfD=0`v;e ze0boKDC(8@e7`%i#_>Qb8$79-DyQ${cPi1unDmXfM)U|xPLCgTpLkqx)CUE4%dAP( z&_$5O|KfFA#)T`i?tHkK6D8uBQ9KShNJ#-inz%aUN5xoDN=4(|`wgmq&UZu20lwo! zMwBi!oqm~tI>sAueFZ9Kxql#ud^5DUK1DR-RFVXrNpJIct{RRX#|Rk0yNCVVP_%%{ zw;@x0G7ks}#V`CoTnaNL5>#Bl1%QBMKCAG z{`MO$Cx4BgQmdd-s$M*(-igu?#|Mi_O+_m#$pRiLBM~m8_cNH7nX1%Gw%8r z&0KHXgSyd1Z$~nY86TK}?Ne8IlM$x2f9Vgr()VINgn;?veS4X2o$J z%ag%zz~AV}ylRmgg(QW=Tam%9?PdnB zADq8K>lQPupu$6#f04H>Ti}(q_-|uU`LGLM4Diz5-dI)syYJG%6#=FJ2Q(9-!Ak~5 zMPE;h7c!<8372VduD?MNx&jjP$3H`FRjBu008oLt`w2;0$&F0odT`R4f_K26uHcQq zD8#0*egr7oLI0rxUtR~&pp|Z3;4{t3LxV$mAz^}TFXjZXMb$kux zb!q)s7GT4yQc$l1<)4dLPhY>!`!FN_Jqa)!VytYsx#%Z-@0Y(S} zg!uaqd`Bsm&(i#Ynz}dp8G|CXZh^F2U(9OQbWi5neQ=2`U5Y#}1KbOB0lXT;)Z*3- zfKSFDvPXG2Ba{(n4*h|BTU*)3I1zmEo<5lxqCc~c-wsh%TU;3+QD8gWGi(2tts+O4 zGnP-Ef{cv$T9|r+;$!acCt#e#?NH$BjmJ+d{3JK}T|sXmjso#1JXy-T^8q{qy$bgO z^A8R>3f$5eJ(bk=DSQ9?c3}7fLID`AAZsf@i4;fMxX`A5a7N{L=VPY-0s-qaky%qF*@Zxdg1-OuT9)=2! zDf1I|_>`#hmhNmpMW8A4rBbt}t@LzwsL=k~OSY%wrGZFY%F<60hiUC(S~3z-4vS}8 z)@$NZv$TK~I?dr0^U{#-$Tfoai9CFE|8$?|DH?HA-EjhM-oEEe%Ob|#$t=>djQu{H zcqvF+dl{pU2o3L*bw5Wlt_Vrbj(zP5YxDaA_KdwSrCZtN>QVNhm}?#y9|G)UoR$Ps?_$yNq6W?ldaWsW|XczChREYSdCB{?kQPwLi$xiE9=)3?h>nTo+V`Kg>MD^JS4x-*1qa#>25oZC8r{0sIi15Af-9yP`mK5*gL_wO5mY7X(wtzQ~ z=!g{xEvN~&XnS*+CD@~%+W(9L>{;49=m)YH)u3KirHpYpK18Y0kNOt*4KBBQDu8fT+7c1rPkKc~Vh)4uDwX2=+eXlZ(Wj1LA*L8|8jR zFmVHN;$oT|r{iS?)2-BrO7tTaOAS+A4>UkA zsX|Lwvx3E`ix-Bh;Bm=pM0m1-tpUfl z_Y9iI(7n-GEU8$0yLI>O=PqT>hO$rhW|0{=Ft%AATqUL}#%SGUq=TG@O;R*SYT?0R zKFjbPz4N3S08Yb9pp-A_Hh;FEW7YFp?c420m`3xZf>P<*ykfvhgQtp*9%YGC+NkZ6 z@GpEEy-StTwb%?KjR#74P9K=FQCssO3J9v%J1N2&ceILGL*$*wC|GCB4LQ5&MYfBT>wwHxj*CW%^<7v47|N~#m)Ko0}9pezm9&nzsY$|h97?UWz~c4>=^anDf%L*k-uQdQF?#$S*CiU0d6|NF#_7u;XqnJSE_%Y?E4*ct?A*0tfht4n|MG2|za9@wBj2I8)H!&hR65KZ%oDMe3l;{{c9^ zFW9O9I8qNW9+I#|@W5t)F(802+7x`q;}d<=r98a)&iUJL&o1wz+bgmK^?NW=;HTN( z=Dm9}cjpc+b(@QP=S46^_}kxf3;|w!*5JdKZf&+9V;O#K&52eqbHce$1L#P;mg+H( zjumo|1ctl(%5tovmvG9cXVzE02i9b#fCD=JCKokoC zpgfSq{QbXr*zkdFMn;y>&3Gh}r9DXD2J*VFPX zIuiE%g}3(p98P|NfL|KefwV`zNApxp6bg(1N{Fn-Gg*NpGhQM)P z4C(OsWI>bzc+ww4#z7C#o|1F>LxfTKLx}Y^;I!iU@2&$`&8ge~ERvB1D^LJ+`|g0} z%VjbYppl|$rY%9u4BCI7l))~)Fh~>=%rL$x*Ek!)7plj$LZOOXn+2mCW3yySe4qjN z-2bMX#b*id6dk1d0DggHFtlcEAN+*2EffhJqNfyiX^h|st~GlOoH-7Lq14d2y+5+n zPwjXbDmOTmoSuOk=l%uXAQ&?9b@D3EYauDRA%}9)T_##e#++i_`LO|*H$cM^k8~bx zch8i}Quxg$s{oIsV098`V9=t!?F%Ov*eLt#F=Q`72LL$8HO{;oWikw%?61;LNzQN) zqqM~c*Ai(Js>)46eW@Ejz*OBMF;05=T&sAJk5Y~E_z$2TJ=N;uF+a~om;>W^R_Y2q zf?bA?c>_5)E+t9H%zG#TZEuP(ti$LX%KLw|Q#$)$2Qb_ZfkQ)8Czyioljj`w@wYq` z1cHAgG{R=#c^KUezYn+_tmkr*zB{)Y#J4&?H&d1}ICK@^>4nXwRw^=~fT2(TzEMC| z5Co`tIH$nnXJl%3bow65d#JJ4ch_rCd;Q)Am`k!?u{9MfLUH@m;r!%#FdtSwZT$rGjt~QU8%%o*1Wvz*)r#68k)9BO7~$Rgii>- z2CGms7uv26Q-xz7)K<3O*Lnx$fux59piN^?3ZO(r7NKaWqU{Dm)Op+Tkf%xxdoUiEB>7yy56Wx}Yn2yEeVXqMoVaH$I8#i%X99Mw9jYg;{A*w zn1=k9jgsOT<&))uyHquoZR>KoHzZOzdVE$x$i#m4JHqK zYdvvlU}hyZ&*dY-jLx#da;{>0Pq7a%MJGIpAK5lT+H zJ;=+0bdlEqN`_dei~jmOEY!ojRmEdP38M^GFbeV}5_Cqr(OxrsU5%*#ON}*#(g{AX zGme>g($}N4IZwUz_~T}K;L*#x`}0X3UByd_v>U(=&}Jy~y4k=bczen%@Yn`Pg*kZ0+XJ%l{hj4Cr>B7BUnqPZ70GKYT|^Oh&q{;mXP2_1*w$Tr zC*O&x)t%k9L}T?@nThe|Z`!NH?Ey~+(f)qpCd`%+4S*BiCidb7)v1`AA9g?S*-cgJ%u}z}hiD#Q&Qg#lK>4l|)_U_5d3S{*{RJCf z9mhyXSbe(gc1O{MyRHW($jOWE-7SxpHJ<~&W*Hgjko~%?g(rO%WoSzb?cvyW{`lZh zVCo|nYGe*uDOncF{D=vTp2ihBR~V@id-*XW`7ajRKx~_k*_GX3)kz%hX3rBN#|W7C zRI`i4bxYuH*OexVr1M|4T4j*!SEAqk(r~7(hslmFYrZ|Q+~DCYB|g-T#vX)L#_lpk zdcRRw$q*Do(?7;yNca>tV%{u^G$^1%sK;&dWF#6B-(HmN^m!53WZCi{2OT*6^V`T$ zjJDJaZbYD#+2j6hC-lr*U>-LvF|zmDK3fTrAMJfjoRu^;!1>RXY(Z*qBS2if(D!6) z#+{Po+D3~JL9HAL(L_$($~fA6I3ML)r|1bDO1O|+M`1RB6i57<)-&X2QCy?M$j*aX$3?k#@s6xDLo+PN z&48v~40rl{mLFY|?67|Sp1~&%*bQ~>n66vf@IC=46ma*SH4Dp%e{1??=Weaz|I9w7 z7qp&ZMI5J#UxG^H-sAW8P6~!y=t#Q~&WE=*vLwUxXP$aSYj+_f@RrBKu5wU|v&e0y zfj~|7aioRG$`JFatSEz7=}IdPE;eosRx+VV8`9v0d}5Do1e_@;jpesOfdF-xxmHm% z=khaalSn@%zNS0SUS6+Tp5mUE@CMH9Gszq3t63YQy%VhqN5KJCgLR6KqVgUCi`AK{W-qjN; z?YnNy=L5`=sLMej(eKiQj3owd{@k3QRp^tV2DwQ5%?d}oJT_h{TeoQBNv-j=r?m$- zc~R;6d&F!OA9%clcPRI>?(O^&UAOO%XIwDs$(f=z$`}Jb@!FO#I{-CfCn8H8P8oSv zcesSve|NZkD_5B+=0yC@CFNB9?*dkUNj}XNqp%1CW4{Jkav+dBv-qKHVoS6oQgR_u zxwh9Wdtc%6w~GM3fk-*}!$xBgcW+X|2k6?>y_#g}E27a+mxHwx_*PcB)3fTZS&!vm zPB3s+cSr2dIndiSnHwJYD$Bq&3qrnu_i`=?%(q8*$gpuCl!kU4or72*SVSXn*+jeu zGbuYr{w8#tQ&}JRDET(1CVlqqcC(Q7ne;Y(w_&QrA$4&|P>C36R3c4Sda;%4>;r`s zN#z#2o3Dt6)Gk;)ajDu(^2;QVY2IW$Gx;?2K~88(=jBE;TovolRV~FX!2n*wd)~WY zk!GlbJZr=c+(qA}NOz)wgMs%pt#(`qvZ8TTioPnNqb>^^MmsGtsAvUdSTDLquTP@q zM7YCe`U{N3IwJ$lx~m+aAa0&+iY(=+;6M;QFy>s)?9P_R+@(6d~8#2G?x$VlK#{Dp4l^y5V#6S`3Nt zT%Y@f)BUe4nTW5FM=3}2)|X#tN3=#;xIaJhKOkNH`+DWjEg#~6EZ7zewF%q!IG~s! z1dd=xY!A#m{@XySqNEOhxIN~Tv9H#9czOif<+I1WBXh1#3ODo*_(JwOh4Dhi2AO&- z3mgCVeEYWKuW=Y5`Um8le$STi(5`s*fMSt2lIuQ2S|t3gZG@w&xSeZDCKF6)zB!Up| zzkZR-iqfv4`;9;~RvqEg6w=N}UJkWFjd%0NxT;g1w7A(91pjGJWZ zPt*mCT)Xoeqv7=-UmZC*eMRBuG^ii%irra#OSD+8HQS}E)r`nmUflwciUr>}mYY04 z%%6pY5_J|yV2@%DMejroL(63R*fejR8rh`TK0pk<;F|lwg2-q9t4%p~V2KiV`=2n$ z6q#3Gs1w;oTMKy@)47bFR^tSaf)9b|FopthCHGfz<5Qq1F01bZmA%t|Mv*EvSBxdx6OU55WEN$+wcjVZ>EJ^RzUu(e-FbGFGtX5l9nh-+d)2xg26e&X9f$ zh5}q#$_=AV>HV1uXi;x&;!3NNwP6`eE1)we6|cYzzjTq*VJ!E-$(2j)E2WRhPDI2E zUYSCiSO8K0iCS)OsCv1{lUdlXk60v{IUTuU94ntIZxM1jG!l0bQh&e@1d%P}-lInB zImx~kH$yxcQ=+q&za-JtsNNfuHbia>1rf*@ZGI-?;kZg7F5lL;9+0ICV_=re!j zt!@iFLqUdoN(1fCXVf1nQ8%5u6sX`>_Jm_u@Aku-t3l%&eol~so_J+27o~7gx=_ubp}{^bqdtJe$ZKAmjn`}NG-AqS-lR2v)`*fq68&%RhX$##@>rsFhDR8zwL}6V+&;@vn7fI}v z!hyPFhl`tS-R@A3%uW-A>B2pXO4mExesY*K)j=TI^kF3 zK7LS24rt5*rL+oao$v&Iqw=mUm`}#eps#2XJ7w@L0Sd~?jr(?p6mIGwvDo`8ag8;8 zRAdMy0$heG$RmR?2ToN_Da71rR)v7J(4nK9EQ@b%{&GV{8>_|HN=AYqN^+`K+Wwpe zAyNSXuy4@$w_6|l2gf&^zjETlNh$Oi+d!tg?5Mrl0jR6&u14Wj?GLB(uWx4X`McK3 zF#XuA_dhS@G=gasNV)ZJI5EV5G%rQGwa~WdQ%07$u3I7WR)lrZHPZ%8wx5se;Fn`U zKM2~lzurqu6Qgbyc@j@cY+wP@ffS;*DY$HZj;L0=A5Wd@;>zUZ(V0Elb-=N-r@0w- z)01}4KT=1#?VQ&jOEtgn*cyE2YBR{^rOI7KbeXIu2qXD{mnbkiV@aQv-KX@CG zibB_}%)guYV2y!!jaX`<%dj%(DEqrY5T)Ty$wnGRuE`{@*|K z(*a8Tl1b$ldHk<02}YM8`Uo0!*?+$FU;nKT0V@M99Enl>X&4?~vH^0*yvhIy{M&aQ zbvv#=xZn3-Jf_P2`z4nm0E=PVKOKO7eFDgWDpgLTG%G8F!HT)h3)mjt8t;V%2@=T!gaB^$ye|L=?Wzy4w{!hkH$A?sKKwEI{a}7`q%d&qTfo&Imh4}a7jCHhXLei2ey+$y z?Tcyxj|=vLoY|Lu>@6=PYGt`GMq=TFP{1a;>VijubXD7Vs=>6%&1mAGsiecen}?y? zoS$)|U#3aRSiJNm+57dpDed`1W46!Fu*}cYi(pBH@XlWbROjYP>>NZ+Pxzu0s4Xt( zm?u`kzq38s@aUGlwSaS9_2({BRm&9dSEI%EA!pCt7`S{%H{l^8b9Tj4PY%rkh%GmU zX_Ba5w8Co#y;8lRV1sCf4r=b#EezK3Nk@9$qzA3QUvMEnC<@DI1bPyyb97|&aVC}xAM>#@ykGFKKtB*^Hp;EQNb@w1U`>u?mF7azt%vnC=vm8 zdI^Z#QU3}b&*?xfg$&w&+Uk+M>yu7es1|wxnWU`0!A?l2M&_A3s~qDjmEHC^xK54SNeT@-7;DDpkMBV2!@rLfd@L& z{$i~&RG4>B2p<<%_U12d-4xDtTl*)Rod`iHHK@ywNruQ&;0u#b)eV6_kTEAq-v$L+ zm+Df&5y5hVBUd5`%wm9^!WVB1j&p|xyRx871IKv%oG_J5lfUvZ6=xsj;N`UOMyn%n1hxmQ8*H07w4QQ>hA>bxt z3sZuDbH5ML0CHUipn<}`j`EOlRR1XHQ;3YLpP5!*WfO+$a>p~9fvg8i14_wB%I5B&Ad=`I6^cb29DDGgq{!(?_YtU&EDk$ zEH&jEw`V;P7XV}xgxMMjr!I+E(Pg*65kc`QK#>gLXlxQ0RGRO_r7Qp*AgS#!(>g-RB`E$V^arOn|!t@+tw9EzwzJt4fd7abc*-uL>c87PJ!xVUa zBkqe}GR7ej-i*hE=E!e#;Z8@Adhi4wvnfgn7ue@hVG{0c($-opqIkT0CqbqbK{)_A zdu&)vI}WFz?nxLAx9 z3?M#0Sa2-{fo1k3IJ9rCJj3rAvww6SP^bbOff%ik8w_`&xo+$I<&-5|2|*CHEj&D? znvocp+K%iAUR!7JQ}txyg~{19`E{pEb=jjbGSw7YLCdV<;t)r zW!6Yd{&i@a*prt$50Bv3rpus>N6#45SiFVekqQjXOW)?f`0&+(UWR(mX{?*tphMyEWez8$z! zRnKwfVbqT?ILHJf@^l2Ud<@?CaK;)9B#G~n>mA3DD4yKgabjm8xbErY$~`m{(<+wS zPV!55pK;wZ|FsLowFA6T>@hdYoGd13#GmRR4+YK(u)M&XcSEi>B$USFnQeTdpWTy_ zHTzzx`&8%FSRxWqgJs3x1`x4jux6Kn&L)}V4hN#+=@OM z{^JI>upy21li8G__UJD~Mv}zh1<$af2U_L$Bpy7?CpE44u-lX#yE6^t4&m=h`H@Gj z6OS{OwvJf2VKZEbdtlOd&9!z!ouMF>Q$p{`DH65Ra4v~XE~n1GvYQd6*BW%<4evFe zk0@Rd9J#KxB!b+j6-K0Wso@O2=^WkU2sGXoRw-&q_NeKK;jvnFowP-( zAFm&H@ztq)`fR5RONmto2P(?J`VuSR{Wufds5UFIeFM!l=lyF1doflu$pGq-Z^a0R4U;1wSefI z8#S>QZl^q{hPkXLKfK2hM6gq_vCsS(6tvh#y}^I{K0+l|>mg?6oGxfn#wJ z;Lh9Mb7R#z%%2!_IoMolA>SV?mwwB;;_5|Lg~^c*Aqw;Jd7Zl(t&U%YJx@ z4qc1h?|bo$)leU77q2SHz1DN;YYcq;F@x{+Yx>{&Ri@f_dyOS${!>`rM6@KJs!K$o z$$j;j*3&gnEB$B+o=Rcg)+l@|4y_V<_HOjZXdS=wU}m>&w8Jh0d5Zhq+hkdy^iT$$AZ zyYq1c)!o1{&QRY^FI<>%&7<1zowr;Xy+a9?r^pf%a=GC|qwaKGMEy;$Svj$lB&o}K z_4?ZnZ(A(Yy6|lEKkj?sh~H8wckAtJzqJdmZc7r~$i@th{atB!w6k&rd*Ujw4byFu zMB3@(e64!!Te;o`ah6-fjzC_II$w?SHEfWEfzeH#V_6McwyHY%@j2?>svf%YD61#h z*c8!Y+r^HP0+phndfn!UqeSv5otla188$(~kJuMuEc>jByEHU&J~!qs>5uA}4hIBu z_-~#Ep(`9#yu9D15)tIE zKRWUM{>Vcj;+UCA=ildyyu0HUmZ$0({Jz27dDuhsolJcPi{@FTiR$vyF|@C>|8y+;SLd4d=IkE=ff zHTTn$x-AHTpRoiNS|VeNltkk>`$4ejKe#`ZTl#~*H`iu3JJk8FFT*wRj2PVp`@@_& z)V;#?pMZT}@XjAuTepkL+s3QCgnbl9|bc)!T*GBRB9OMVoAwZ$9gYtR9XKku)re{(Zy^o551wc2mX6{DBWrQ$*Y+CF?D!zM`IZji+|?Oa3@}|915D z`a|OFqW>l#80~}CQ@OU#h8&UgV>^sQR4+>D6gDwpOCFMlbM2l^dja6o85MQph{4}QBOLs-dL--YdF$1o5Ih&*A+ z5Hf9241#?lSCzzX13(-W#%70zd)(aRwd+u--til6z(YW_xcw= zTlzt7U_r))dRW8uTd0uu+uirdFaG9l-7l}Ce>avNPm>n0$`uH23J_?9*Gut5Q0+dcAf%8p);l{n^o{w=) z!nSbit@q$#FotR_4vDOyY`U~ERG^H(>_WJzrO#@A4BjeXlVCjz_%gnC___Szjqi_+ z$3qryzx1pL9dfaMJpB@`IacvHfw+B)yHm&Z*E%tjlL15so;}(dO_E3C6DP;&+OnMt zdcNOq6o(asaZjBWh$Cm7A z!M+T6K1Yd0hq|sWsHHMdny40Usk22>f0I(A!2@>V%wG_N)59X zzX*i>@KKZ%9@@`mArw3 z7Yche#-3EhK2$sVWrVGt9Mme<@cfA}R(OTS1Xwz@PH>}m zoj&??cW36l`q*r>rC`q1SNFoCe4ZHUMfl!O!S7d{36dt*ZCSh4$3_wt2r)@1Hd)yI}FM=ESm8)k>_o5vnB`Gt!6t#Qi%Po8%!%ywI2A#rmr zvrW6scKy$YFHPifOi0}jhCRP7jdB1dycgh|48}A(YkW}cB3A~n^0SdH9GxGLEKLZS zz$xwgVJDnZFFC2X?DKA6;=L&a9sF(*-0pN~H`g0w-dCQX{CC`f=*uwHG_(H95MP#K z_4?36haX>GypbxkkRA{-3;yh?5J8jz4SiCDs>vy+(##O#u))$oXFaex%0=DcQ>rmfJH*TKI7QXHQJE=|V7m2z#dWQV` zwp3k$4hfHufVYKSG4NX*7v%89eWc)6{6eVGY7M_OUo7>2>_?FwjI3T+BIC1GaH?=%P0F2 zb7`X?8i+^K$8KyTfg}HD&!@^@1nf-HsB&t0_6^S?1_z4gg)a;{G{7=~7O<~o-nthT zkn4HfHi?x7Sn_G3Mr7t&$a>iN>M#r`4KnjB^nYeDbURoxP79+s%pAtw)LTD$!mTf+ z5Bg$_%lyGfk!3Vs^p?B&bJ~SKE&i-BWd;WhCyj-5eRxU?wDOTh-ZsCo>wv4(`x;Hs zcez!)*88r59>X2819`JvSVg<)r1-RjFDIN5@RdW-7qs1+%IexK36aq|F<@TV<&==W z)EaP2IB#A_`lR`3{V zKXNP23AU$4cUox<3cB5qWfBZRFLqMio3o z%iy||tzkZ~{4Nzi=3naPk~=ITDb-{DuquY1DMe0UW_FMui^WNuFLa3A-_-@FYpv2J zK@j*+ZQ^66^l$5H>F26)p@$qmKU(@#ak&92jR*G5)2>ANYL>JU%V8>y`9I$nykL`< zIXylQ{Vm}`xgdcGuiS$}=`qiMfEWq2{R+|VG|_1$tiS;|3lfQ?_!{v9#<5Oq0e1t^ zhDx24@0WbfNxT?F6{2tSAgtLf)Jp4>mpLAI-&Dzw1 zE+LxG9i+r?G9r+L5#pY_;n(36XX;`y!p8M4O-TUn>X&8JRb7l z+xHJ#@k3}f0$sn*a5{e>x3z&tib<7H{t9d``*oRQ#Cu*l+f$TubM@ocKeU!)!ZD z1Ep|DmZxGE?gSNC!X^Vs`b|VcUZ(JiEeHN>@9&wvbInUsOKTwURw_5~cu%s-XVgSu zn`|dD@P6_GkCPWYKNMji+c;8v(PKx6;E(T8C+Jc{^gez6<*{0mAHDQ^F}%K7{&bsy z=-yFT&ELlhfx<=`t}+%8fr3!Z@8eRY&D8R7w_W=GBzHJa*>WPsj|F?8pH0 zB15|bZ{AEe$G!Yzh8 zDOdPemPS+6SS)~imAFJwqk%ksC0CkgO=V|=M^6T0hH9qo8(zd}V)gh5Ef=aTE`IFE z2ck+f{(|QZL_rR42t;^A*={{Uc!ibSfg~f0MW1xquF%Q4S5rEoC&e>GR57`Nff>24C3h!h3SNarpvWrJSFEVNXzM zm6&pzSgz=k7@jGvnB)QR%$s=mK(#LA-`k8Q<@uS<9+niXz^2*qw0BvG+|u-;vMlZ9 ze-1hWzN^3@Ev}TVhiVqXhFc1jyy<{m2^49_XEc~Je7%rc-3Gj@m6~(t;QeTMjJzqT z>EQOaso$OUX)Q(wc~`nP3Ux>Z?R3_?>6^9gORb{}XbNH@?mF~?Os4*9j^gvApo3I! zkocj?eTiV#-*PjKMP`%QMKL#!2r?epxRemi@Apn<uQ}V%o-UE zIa8_pLKiAH)_G)-7G$Vr%Wjy**M`}MxYb~FBOyKT@IMHTF&d3NEFE4T?l(kSadV0P zM^NIG!)Ux2Ud9_Y_C^c+p-Eh5^%YHUWzpAHfd?@qldMnZ#F4p0&R->M!iG!?RPq;PA}`UsxqyX{nVeo?-(7^L*3X{TgaYT_HatAR(W*Tz;$Zo|lNsFW z4%r#ZBlp&46lF}+i(M%EPtQNAE z#I(wGT*K(91QCZfStY{~AVFYGgW2w3Ns__sWNZ-ByPD1H*W~cQbCFY!N@||)j^z&w z_I8f(_p5s?_ep*B#Vdjxy)mRxfQIqSfu{70rp-)hM$~#4LVgj0{f)@C>rOw=3+bj< zuK9URDU&rwy%55|#aU5f1JbSw970b6h?mZA6VzvjnWV|U?!&gqLd@gjd(BOnEYYO8 zaCN_)>_@WH$;%tF=>43|D*dR&5_3GeTsmUZ&lpfP1ISz>1pW8elLP-+-^&W(lo(~X zVu=*>FPp>rYuWl|&X6^MzF?GC7#(UiTqE1R|0L5nC2rq~Gcfm)@EpD!&4J1I z5)YG9XWzj~5Ooow+BJl+vz)?Tkv}&+e#!2E0E-&yZM*l=GMPA`+HR!1-9xIg)`?Yw zr>))>TC0h)!1+}Fi>I|KsEl2FQ>g)RI2<-oE zdBrT4`k)AFTvnGw83oMR*18TpahE;dQs{^8rp!XMtzs2&p4hAhtoxaDbRso1W9;-) zV66X|-CSL&JGJZQ<&II;yG-=t1Gr5D*9!wDLpX5rbv$3mfI3P74&1q*7aIi|oUtaq zJG%BR6QVnO$Ic8sf+<`_kK--|P9o2)Pe{H#9aU->B3j?Tx9RV>T*e%zpEYemHW;O$ zBWLjFW^jUVf?vs&$b@w=d}O(|UCj3gxf;DGrjR192dC3`l$o+m`3o$d; zJHTBhq#OM0kC<+w3Py)HM^ch&#<)VI6&wauqX^;v%-sBICmEdH3>*y^a;WD2UU!N} zrN`2tnnXyYl3W-_r7U>yx$i{mV8R*)+VuGg3xIOXlsa~4um&5yQk_#EW$W;Ut3j)_ z*qZ&36+j`N(8R#*^7}zgiDdlYeYnl**;Q59X#Gl57Mxo<_2}uBRRK317{%!bUJvF{GzkB@@y>{y4 zxP3d!U#!NdS(|5)xkJ+Z*Vad?Z|g{Ca{LqptJF)yqt`sAx24(WTrMZ{W`dFd4BKgT z$t(RpiDJO@{0e$#^5#ylNI<5SrHhEsbzN_0?C{f5guFuv&y%&MO|~_pi(%@Ek_CI+ zx&JwQ_3Z9 zqv-7ViFYQ#>?E7p5}Q~4>#u!`ey*|87#mAua|A9IDpj|nno%uw#55!}_)LMMa>9*Y zT^`rq(kU7_e*osHK3o}`RuaAz+2SiX{BrM7fbvH>~XDa3i>P=G8NOQ|@v58xkb+T=IJsj zZS1QRxsf!FK7Z#&!?mcYO#~7T?=awWI)Whnu#P6}M}<@o-y@G&L(8t+YWj*wheG)8 zsgCsgDOkGP)axt^JE3x_wV$Z3K3J!mV9bl2jVK@}G}BP#IBLzH z>!I;RG96OxK1}RRw8JHeN9(E>lgr>?huSAOCPWFjY2DsF2kJn&bB}4&?3}7lFbUPN z-RFe&D2pDUJ4C*3JCXhs-pcpJk3u9ZW;VUb#EXH$VVs)wX0nDSIIao>5_8ZIIPBmD z%P+70Z9f#sYZ6KggMgH6dKc~4YKoFOuBgEDnUJ>mcmlbzbZ?K}9pHbqaW``u^Tf9A z0}r)4(ABv{7=7}3WJlO=CLGsIlf##J{~%`wn7!p_QK!AexR$JBq{}By7Cn6tH9fxg zKxW=mVWZPy%!T80Rr@Y)I(-{Yw&;mT(i)4d;Y)`un&X7hOV z0>rVAP+{}ULqaqu=G2w5-`VIB@L@wd)AS){3Ywn2MEQBgTmS~Wp>g?(=dXvx`5467 zC}|L_f^?hH&gHPX>xYM^kyp>_$wcmBGAXUWJaPlcP91&aa_`g75u0Z1Q)!Iww!`pH zs_{w*tIe+bSj-A;;!JZBG#w+yHv1N)7PZRuomk$}s~Z1kbpLak`vCugZQuovOvS~`Vv$=lK?*O|o z-DzNU%VVpZo?=7=AT* zZAWLGYb4mjQ9^>DhiDs)&LOGhZi=wv^C*V{si_hH(v{kE>A9G7t%ckt4DI{G()p|# zIx*}rM;N9DCEby=Re~v&@hqpj1j_%!)$E;&cI<0Ahyy4mZG0QKtI)a-VzGAIm_YH~ z^J-yT0oqf?<}`oy6U>oV#-&(z%W4490Blc(s1-nS8dU3@St@A21L; zA;ei`zD*{3TKmK`nwR#vKRmBDupG5oFYd2ZB&cz6?iUi6&{RU7`PAe27Qcd7Gq>hX zO7QBAS`C!VKnq9~o}(V*%2U^tO2#WoM2hzq{E!md<9$HQWKqhk%c5I~WGQA;(a{{f zTSB!K=V!4rqNT~4QsZ3Sl&^-KUd#aQ8DQS$fS+Xz=7z{BVr1bd2vYb=(=sOLf4?5`8 zTy}V#eh=H>;eSTMvQ3kTt&*{rNyBustUhjT7fHdI3Rt=1*ygJ8iQNXeCL@O8wMdH^ z3DbkFx!cn)<RA60!TbZ zb0RAMq6@n|6|Bq)^+5(nc=-fhhkU*b(&yoWbyiyD^R3E=qd~{U;fdIc2$8ze+M10?K%h8!H63ITxFNhbG87y!xD^t;j7JZ|qVM?MgPEZY( zO(KwL^dQAcSHP=Ak>V(t;5s4pZrc$C5#g1f(IDZGOHF*9R2jb6m}#Z(iM4C5Hm|+i z*auR;ZSmz_V}FjO(cmW0;AvkyTmCa>K|dU?9;^LDUkC*g6_eWF0kAtoQ+Sq-vgIVm z%}xqNOTAW#j&7k^d|v6JI28HnRSL5 z5riznEYi-N+)J*N)=9F_AaKpEY*7>7RIaf~1v&{O??*Cc6T?s9fE5!o6i!XFNTqrx z60vr5W3rzXKN7|u9Qsfu8(tfNChPxR0emI8mb99z@Flu5j>sjt6eMlUX{kXL)-wUQ zP%MV*I90AXQI6tE9@TpR9eMw628QEh7M9cscyeasCW7!C$~?e*4MnRs!Jd*s!H)Gg zR^8LmT;Toi(|bw_hVX{j^4!2N@zqog*zhWp@^v8cF{-}~Ff6b-O8{>Wf%Z^sBbuSW zKNj!dFfMf#E%}2kw3!2$&Rh}+_Zu2ly=^e7wom=^*5>e;cZQ$Uq)4BGOl%UqKnZXV1qk2*gdR)L#%Vu4$P^ACxrfcL6G+^mOSOi^ane@(LFXSc>% zZWRe-!}h~Dz)t~K>8Wa;6pV%C=1gFA$r`2+OEL3G)iO?;Bc}Q7%;CK)F2B|Q)X5SU zD(hm4yBs+9?;M}Z3)G5Qm-!;Y_1v}gZN>IZV~xDFPr5lOJcZvn-7-OU3r;0B5G=)1 zn86?GOA&9WNS4EDVNECK*cYGjj*UJ!) zPN})vB8|+1b(1(XT=tf9XJyAtl+D$Dqt2V&U{X|h-|x#Pk=JT$gPu{z0=ObXH*AL7 z>ten-7EkD}1KQTqvi>tS^`P(5baTH4t%;!q1ja8^67&{5=2>RLuyD!A2>~zLzwVV? zXnSY$Py?b_jiiz-;!&5S=ZW+A^|o@V=Yx4Q(}@nLbI$rcr3KY+UkV*qAW$=b51O_M zY(INCZ*00Ei1v(&@ubP?BKc1T#}^oe5QzD%&7rmZql4v5h_v@}B zs}S7$#1@j8dkEd52We62jn`-^HV*Mr#*;C~JikPi4J7|d4e!VLY+6i_Q;7--VEa^; zlP2EN`!XtG*})x@e#WZPR0Wri+%O(_IU6=ZmY6uZN-s5I#mP2xO#!NOkji;iy;aPn zx8~6Hy?Y7e_sM+FHO7yIqs8ZQ{@3;FIfv9$%{i4K;MK)>zVf7k(rEzI%i^ns6{A?W zzsGqxw(-j7X*(xPXdpoZ`(nXcs(9pY*ZScfLm|Dd2LgEs;V(ix=seojiI|2>^>VDL zMw1&{;`thNR5S|2Mf)3tWP(jI|A`Xp-~S&`Qcho(y3wq_+1ap$Wbo^nCP_6=7=M5) z-;e9p|J(oGJJ>(+ta_2#Btl^zXG(FquV+G?MzX?N0)<8bF9%cEP`;Td*+ro81-5~X z${Rh-TL=IC9Hdn`OYC80Tw?f0I8^O|+zncJME#W_DX0f~qoR9fYDj(9i354EvLQ?X z4?JUO68tAu&x53b(9Q1F_35f@O+bA?z})b=yDOCG6sjE8!nA+>*R5+;1rosvUX6?Fg^6RG+b(DZ zFsy%-%cj5kIa~eY_`p-(BdI3k9fM|?FO_nB&US;WHa_(%gAN^I58oh{S3%qiuznO5 oP$SA$b3R|vB{RK@BPQ*%Ek%I)Kk7TMod5s; diff --git a/doc/design/images/trainer_data.png b/doc/design/images/trainer_data.png deleted file mode 100644 index 27ec4c98c4afd5f60228baeeefb8dd0d2ef9ac67..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 138135 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 z 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

    4F-YnH^&&hyFtN7J4 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 Xc$AaDbHPU#@IQH(o6>nw#=idp!+67i diff --git a/doc/design/images/trainer.graffle b/doc/design/images/trainer.graffle index 37b3f0b469c14a1eefcd644de16884f87a282841..e779dda3189cf3e4d87ac242d22a7b2f7fdbcc6d 100644 GIT binary patch literal 6434 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 u=fDEtKF$z-U2>^Kq@@>Gqy%Y{T0r9U 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%0DrZVH_a1)FDYJ*0SAWQKe$b>$h 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